Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"recommendations": [
"swellaby.rust-pack"
]
}
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ categories = ["mathematics", "rendering", "visualization"]
[dependencies]
angular-units = "0.2.4"
chrono = "0.4.26"
minifb = "0.24.0"
lazy_static = "1.4.0"
minifb = "0.25.0"
num = "0.4.0"
num_cpus = "1.15.0"
png = "0.17.9"
Expand Down
2 changes: 1 addition & 1 deletion REQUIREMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- [x] Create a PixelBuffer struct. This struct should contain its associated PixelPlane struct, and should store the color of each pixel in 8 bits per color, so 8 bits for red, green and blue. This is also called true color (24-bit). The PixelBuffer should be the input for rendering functions.
- [ ] Create a TrueColor struct, containing a 24-bit true color, with 8-bits for the R, G and B channels. The struct should contain methods for conversion to HSV and from HSV. The TrueColor struct should be constructable from a u32 representing a true color.
- [x] Create a PixelPlane struct. This struct should contain a width and height in pixels, and an aspect ratio.
- [x] Create a MandelbrotSet struct. This struct should contain max_iterations, orbit radius, and an iterate function.
- [x] Create a MandelbrotFunction struct. This struct should contain max_iterations, orbit radius, and an iterate function.
- [ ] Create a logging function, with log levels DEBUG, INFO, ERROR, and a commandline argument specifying the level, default should be INFO
- [ ] Create a struct to combine KEY, description, action, to make it easy to display what keys are doing in the future, self-documenting
- [x] Save the current Mandelbrot view with the 'S' key to a .png file, the view is stored in the metadata of the image
Expand Down
15 changes: 9 additions & 6 deletions benches/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

extern crate test;

use mandelbrot::{mandelbrot_set::MandelbrotSet, complex::Complex, pixel_buffer::{PixelBuffer, pixel_plane::PixelPlane}, complex_plane::ComplexPlane, coloring::TrueColor, rendering};
use mandelbrot::{
model::complex::Complex, model::complex_plane::ComplexPlane, model::mandelbrot_function::MandelbrotFunction,
model::pixel_buffer::PixelBuffer, model::pixel_plane::PixelPlane, model::rendering, view::coloring::TrueColor,
};
use test::Bencher;

//Mandelbrot set parameters
Expand All @@ -15,12 +18,11 @@ static POINT_INSIDE_MANDELBROT_SET: Complex = Complex::new(-0.3, 0.0);
static WIDTH: usize = 1280;
static HEIGHT: usize = 720;


#[bench]
///Run MandelbrotSet::iterate on a point inside the Mandelbrot set, with typical Mandelbrot set parameters, 10k max_iterations, orbit_radius of 2.0
///Run MandelbrotFunction::iterate on a point inside the Mandelbrot set, with typical Mandelbrot set parameters, 10k max_iterations, orbit_radius of 2.0
fn bench_mandelbrot_set_iterate(b: &mut Bencher) {
//Setup
let m: MandelbrotSet = MandelbrotSet::new(HIGH_MAX_ITERATIONS, ORBIT_RADIUS);
let m: MandelbrotFunction = MandelbrotFunction::new(HIGH_MAX_ITERATIONS, ORBIT_RADIUS);
//Benchmark
b.iter(|| {
let _ = m.iterate(&POINT_INSIDE_MANDELBROT_SET);
Expand All @@ -30,14 +32,15 @@ fn bench_mandelbrot_set_iterate(b: &mut Bencher) {
#[bench]
///Renders a 1280x720 1x SSAA image of the Mandelbrot set default view using Bernstein polynomal coloring, 1k max_iterations
fn bench_render_mandelbrot_set_default_view_720p_1x_ssaa(b: &mut Bencher) {
//TODO
//Setup
let mut p: PixelBuffer = PixelBuffer::new(PixelPlane::new(WIDTH, HEIGHT));
let c: ComplexPlane = ComplexPlane::new(WIDTH, HEIGHT);
let m: MandelbrotSet = MandelbrotSet::new(DEFAULT_MAX_ITERATIONS, ORBIT_RADIUS);
let m: MandelbrotFunction = MandelbrotFunction::new(DEFAULT_MAX_ITERATIONS, ORBIT_RADIUS);
let supersampling_amount = 1;
let coloring_function = TrueColor::new_from_bernstein_polynomials;
//Benchmark
b.iter(|| {
rendering::render_complex_plane_into_buffer(&mut p, &c, &m, supersampling_amount, coloring_function);
})
}
}
33 changes: 33 additions & 0 deletions docs/MVC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Model-View-Controller design pattern explained
The Model-View-Controller (MVC) design pattern is a widely used architectural pattern in software development that separates an application into three interconnected components: Model, View, and Controller. This separation promotes modularity, maintainability, and reusability of code, making it easier to develop and maintain complex software systems. MVC is commonly used in web and desktop application development, but its principles can be applied to various domains.

Here's an overview of each component in the MVC pattern:

1. **Model:**

* The Model represents the application's data and business logic. It manages the data, enforces rules and constraints, and interacts with the database or any other data source.
* It is responsible for maintaining the application's state, processing data, and notifying the View and Controller when changes occur.
* In a typical implementation, the Model is unaware of the user interface and operates independently of it.
2. **View:**

* The View is responsible for rendering the user interface and presenting data to the user. It displays information from the Model and sends user input (e.g., clicks, keystrokes) to the Controller.
* It is essentially the presentation layer, responsible for presenting data in a format that's understandable and visually appealing to users.
* In an ideal MVC architecture, the View is passive, meaning it doesn't contain application logic. It should only focus on displaying data and forwarding user input to the Controller.
3. **Controller**:

* The Controller acts as an intermediary between the Model and the View. It receives user input from the View, processes it, and interacts with the Model to update the application's state.
* It handles user requests, performs necessary actions, and updates the View to reflect any changes in the Model.
* The Controller is responsible for implementing the application's logic and flow, making decisions based on user input and the state of the Model.


The key principles and advantages of the MVC design pattern include:

* **Separation of Concerns:** MVC enforces a clear separation between data management (Model), user interface (View), and application logic (Controller). This separation makes it easier to develop and maintain each component independently.

* **Modularity and Reusability:** Since each component has a well-defined role, it's easier to reuse and replace parts of the system without affecting the others. This leads to more modular and maintainable code.

* **Testability:** The separation of concerns allows for easier testing of individual components. You can test the Model, View, and Controller independently, which helps in identifying and fixing issues more efficiently.

* **Scalability:** MVC can be scaled by adding new Views or Controllers without significantly modifying the existing components. This makes it adaptable to changing requirements and evolving applications.

MVC has served as a foundation for many other architectural patterns and variations, such as Model-View-Presenter (MVP) and Model-View-ViewModel (MVVM), which adapt the core concepts to different development environments and technologies.
Binary file added icons/rust.ico
Binary file not shown.
1 change: 1 addition & 0 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
max_width = 140
60 changes: 48 additions & 12 deletions src/config.rs → src/controller/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::{str::FromStr, fmt::{Display, self}};
use std::{
fmt::{self, Display},
str::FromStr,
};

//Argument default values
static WIDTH: usize = 1200;
Expand All @@ -8,24 +11,36 @@ static ORBIT_RADIUS: f64 = 2.0;
static SUPERSAMPLING_AMOUNT: u8 = 1;
static WINDOW_SCALE: f64 = 1.0;

#[derive(Clone, Copy)]
pub struct Config {
// Window dimensions in pixels
pub window_width: usize,
pub window_height: usize,
// Mandelbrot set parameters
pub max_iterations: u32,
pub orbit_radius: f64, //If z remains within the orbit_radius in max_iterations, we assume c does not tend to infinity
pub orbit_radius: f64, //If z remains within the orbit_radius in max_iterations, we assume c does not tend to infinity
// Rendering parameters
pub supersampling_amount: u8,
//Window scaling factor
pub window_scale: f64,
//Scaled window dimensions in pixels (used in images)
pub image_width: usize,
pub image_height: usize
pub image_height: usize,
}


impl Config {
pub fn new() -> Config {
Config {
window_width: WIDTH,
window_height: HEIGHT,
max_iterations: MAX_ITERATIONS,
orbit_radius: ORBIT_RADIUS,
supersampling_amount: SUPERSAMPLING_AMOUNT,
window_scale: WINDOW_SCALE,
image_width: WIDTH,
image_height: HEIGHT,
}
}
/// Parse the command line arguments from e.g. `env::args` in the following format
/// ```ignore
/// cargo run -- width height max_iterations
Expand All @@ -36,7 +51,7 @@ impl Config {
args.next(); //Skip the first argument as it is the name of the executable

//First argument
let image_width = Config::parse_argument("width", args.next(), WIDTH)?;
let image_width = Config::parse_argument("width", args.next(), WIDTH)?;

//Second argument
let image_height = Config::parse_argument("height", args.next(), HEIGHT)?;
Expand All @@ -53,15 +68,26 @@ impl Config {
let window_width = (f64::from(image_width as u32) * window_scale) as usize;
let window_height = (f64::from(image_height as u32) * window_scale) as usize;

Ok(Config {window_width, window_height, max_iterations, orbit_radius: ORBIT_RADIUS, supersampling_amount, window_scale, image_width, image_height})
Ok(Config {
window_width,
window_height,
max_iterations,
orbit_radius: ORBIT_RADIUS,
supersampling_amount,
window_scale,
image_width,
image_height,
})
}

///Parses an argument to a T value if possible, returns an error if not. Returns default if argument is None </br>
///If Some(arg) == "-", return default
/// # Errors
/// Return an Error if the given argument cannot be parsed to a T type
pub fn parse_argument<T: FromStr + Display>(name: &str, argument: Option<String>, default: T) -> Result<T, String>
where <T as std::str::FromStr>::Err: Display{
pub fn parse_argument<T: FromStr + Display>(name: &str, argument: Option<String>, default: T) -> Result<T, String>
where
<T as std::str::FromStr>::Err: Display,
{
match argument {
Some(arg) => {
if arg == "-" {
Expand All @@ -72,8 +98,8 @@ impl Config {
Ok(val) => Ok(val),
Err(err) => Err(err.to_string() + &format!(" for {} argument", name)),
}
},
None => {
}
None => {
Config::print_no_argument_given(name, &default);
Ok(default)
}
Expand All @@ -86,7 +112,17 @@ impl Config {
}

impl fmt::Debug for Config {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { //TODO: Improve debug printing format legibility
f.debug_struct("Config").field("window_width", &self.window_width).field("window_height", &self.window_height).field("max_iterations", &self.max_iterations).field("orbit_radius", &self.orbit_radius).field("supersampling_amount", &self.supersampling_amount).field("window_scale", &self.window_scale).field("image_width", &self.image_width).field("image_height", &self.image_height).finish()
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
//TODO: Improve debug printing format legibility
f.debug_struct("Config")
.field("window_width", &self.window_width)
.field("window_height", &self.window_height)
.field("max_iterations", &self.max_iterations)
.field("orbit_radius", &self.orbit_radius)
.field("supersampling_amount", &self.supersampling_amount)
.field("window_scale", &self.window_scale)
.field("image_width", &self.image_width)
.field("image_height", &self.image_height)
.finish()
}
}
57 changes: 57 additions & 0 deletions src/controller/interaction_variables.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
pub struct InteractionVariables {
///Variable determining the amount of rows and columns are translated by pressing the 4 arrow keys
pub translation_amount: u8,
///Variable denoting the user scaling speed; the lower this value, the more aggressive the zooming will become
pub scale_denominator: f64,
pub scale_numerator: f64,
}

impl InteractionVariables {
pub fn new(translation_amount: u8, scale_numerator: f64, scale_denominator: f64) -> InteractionVariables {
InteractionVariables {
translation_amount,
scale_denominator,
scale_numerator,
}
}

pub fn scaling_factor(&self) -> f64 {
self.scale_numerator / self.scale_denominator
}

pub fn inverse_scaling_factor(&self) -> f64 {
self.scale_denominator / self.scale_numerator
}

pub fn increment_translation_amount(&mut self) {
self.translation_amount = self.translation_amount.saturating_add(1);
}

pub fn decrement_translation_amount(&mut self) {
if self.translation_amount > 1 {
self.translation_amount -= 1;
}
}

pub fn increment_scale_numerator(&mut self) {
if self.scale_numerator < 9.0 {
self.scale_numerator += 1.0;
}
}

pub fn decrement_scale_numerator(&mut self) {
if self.scale_numerator > 1.0 {
self.scale_numerator -= 1.0;
}
}
}

impl Default for InteractionVariables {
fn default() -> Self {
InteractionVariables {
translation_amount: 10,
scale_numerator: 9.0,
scale_denominator: 10.0,
}
}
}
Loading