From caf3613c4a4014c9e31e462b6153348d6d7c97db Mon Sep 17 00:00:00 2001 From: Jort van Waes Date: Tue, 19 Sep 2023 16:10:43 +0200 Subject: [PATCH 01/14] Created the model, view, and controller modules --- .vscode/extensions.json | 5 + Cargo.toml | 3 +- rustfmt.toml | 1 + src/coloring.rs | 49 +++-- src/complex.rs | 8 +- src/complex_plane.rs | 25 +-- src/config.rs | 47 +++-- src/controller/mod.rs | 0 src/key_bindings.rs | 14 +- src/lib.rs | 268 ++++++++++++++++++++------ src/main.rs | 2 +- src/mandelbrot_set.rs | 14 +- src/model/mandelbrot_model.rs | 14 ++ src/model/mod.rs | 0 src/pixel_buffer.rs | 58 ++++-- src/{pixel_buffer => }/pixel_plane.rs | 19 +- src/rendering.rs | 162 +++++++++++----- src/user_input.rs | 44 +++-- src/view/mod.rs | 0 19 files changed, 512 insertions(+), 221 deletions(-) create mode 100644 .vscode/extensions.json create mode 100644 rustfmt.toml create mode 100644 src/controller/mod.rs create mode 100644 src/model/mandelbrot_model.rs create mode 100644 src/model/mod.rs rename src/{pixel_buffer => }/pixel_plane.rs (50%) create mode 100644 src/view/mod.rs diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..7a79ec8 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "swellaby.rust-pack" + ] +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 25b152b..233a38f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..93470b6 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +max_width = 140 \ No newline at end of file diff --git a/src/coloring.rs b/src/coloring.rs index c282999..a81bd38 100644 --- a/src/coloring.rs +++ b/src/coloring.rs @@ -2,9 +2,9 @@ use std::{fmt, str::FromStr}; use angular_units::Deg; use num::traits::Pow; -use prisma::{Hsv, Rgb, FromColor}; +use prisma::{FromColor, Hsv, Rgb}; -#[derive(Debug,Clone,Copy)] +#[derive(Debug, Clone, Copy)] ///A mapping from ColorChannelMapping -> RGB, the first character denotes the new red channel, the second character the new green channel, /// the third character the new blue channel.
/// E.g: Ok(ColorChannelMapping::BGR) means that red will get the value of blue, green the value of green, and blue the value of red: @@ -40,7 +40,7 @@ pub enum ColorChannelMapping { } impl ColorChannelMapping { - pub fn new(r_g_b: &str) -> Result { + pub fn new(r_g_b: &str) -> Result { match &r_g_b.to_uppercase()[..] { "BBB" => Ok(ColorChannelMapping::BBB), "BBG" => Ok(ColorChannelMapping::BBG), @@ -69,13 +69,15 @@ impl ColorChannelMapping { "RRG" => Ok(ColorChannelMapping::RRG), "RRR" => Ok(ColorChannelMapping::RRR), "RGB" => Ok(ColorChannelMapping::RGB), - _ => Err(String::from("Invalid r_g_b string, should be a string of length three with characters 'R', 'G' or 'B'")) + _ => Err(String::from( + "Invalid r_g_b string, should be a string of length three with characters 'R', 'G' or 'B'", + )), } } ///Returns (x,y,z) where x,y,z ∈ {'R', 'G', 'B'} - pub fn get_r_g_b_mapping(&self) -> (char,char,char) { + pub fn get_r_g_b_mapping(&self) -> (char, char, char) { let r_g_b = self.to_string().chars().collect::>(); - (r_g_b[0],r_g_b[1],r_g_b[2]) + (r_g_b[0], r_g_b[1], r_g_b[2]) } } @@ -97,12 +99,12 @@ impl FromStr for ColorChannelMapping { pub struct TrueColor { pub red: u8, pub green: u8, - pub blue: u8 + pub blue: u8, } impl TrueColor { - pub fn new(red: u8, green: u8, blue: u8) -> TrueColor { - TrueColor { red, green, blue } + pub fn new(red: u8, green: u8, blue: u8) -> TrueColor { + TrueColor { red, green, blue } } /// Creates a 32-bit color. The encoding for each pixel is `0RGB`: @@ -110,12 +112,20 @@ impl TrueColor { /// afterwards for the green channel, and the lower 8-bits for the blue channel. pub fn to_32_bit(&self, mapping: &ColorChannelMapping) -> u32 { let (r_map, g_map, b_map) = mapping.get_r_g_b_mapping(); - let (r, g, b) = (u32::from(self.get_color(r_map)), u32::from(self.get_color(g_map)), u32::from(self.get_color(b_map))); + let (r, g, b) = ( + u32::from(self.get_color(r_map)), + u32::from(self.get_color(g_map)), + u32::from(self.get_color(b_map)), + ); (r << 16) | (g << 8) | b } pub fn get_color(&self, color: char) -> u8 { - assert!(color == 'R' || color == 'G' || color == 'B', "Error: color should be equal to R, G, or B, color = {}",color); + assert!( + color == 'R' || color == 'G' || color == 'B', + "Error: color should be equal to R, G, or B, color = {}", + color + ); match color { 'R' => self.red, 'G' => self.green, @@ -130,11 +140,15 @@ impl TrueColor { /// Source: [Bernstein polynomial coloring](https://solarianprogrammer.com/2013/02/28/mandelbrot-set-cpp-11/) fn new_from_bernstein_polynomials_normalized(t: f64) -> TrueColor { let t = t.abs().min(0.999); - let one_minus_t = 1.0-t; + let one_minus_t = 1.0 - t; let red: f64 = 9.0 * one_minus_t * t.pow(3) * 255.0; let green: f64 = 15.0 * one_minus_t * t.pow(2) * 255.0; let blue: f64 = 8.5 * one_minus_t * t * 255.0; - TrueColor { red: red as u8, green: green as u8, blue: blue as u8 } + TrueColor { + red: red as u8, + green: green as u8, + blue: blue as u8, + } } ///A `coloring_function` @@ -150,10 +164,10 @@ impl TrueColor { ///A `coloring_function` pub fn new_from_hsv_colors(iterations: u32, max_iterations: u32) -> TrueColor { let hue = 0.3 * f64::from(iterations); - let saturation = 1.0;//0.8; - let value: f64 = if iterations < max_iterations {1.0} else {0.0}; + let saturation = 1.0; //0.8; + let value: f64 = if iterations < max_iterations { 1.0 } else { 0.0 }; let hue_degree = Deg(hue % 359.999); - let hsv = Hsv::new(hue_degree,saturation,value); + let hsv = Hsv::new(hue_degree, saturation, value); let rgb = Rgb::from_color(&hsv); let red = normalized_to_byte(rgb.red()); let green = normalized_to_byte(rgb.green()); @@ -181,12 +195,11 @@ impl TrueColor { let green = green as u8; TrueColor { red, green, blue } } - } ///Maps a number t ∈ [0.0, 1.0] to a byte b ∈ [0, 255] fn normalized_to_byte(t: f64) -> u8 { let t = t.abs().min(1.0); let byte = (t * 255.0) as i16; - byte.unsigned_abs() as u8 + byte.unsigned_abs() as u8 } diff --git a/src/complex.rs b/src/complex.rs index c229842..fcc85da 100644 --- a/src/complex.rs +++ b/src/complex.rs @@ -55,13 +55,7 @@ impl Complex { impl fmt::Debug for Complex { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{} {} {}i", - self.x, - if self.y > 0.0 { '+' } else { '-' }, - self.y.abs() - ) + write!(f, "{} {} {}i", self.x, if self.y > 0.0 { '+' } else { '-' }, self.y.abs()) } } //Complex diff --git a/src/complex_plane.rs b/src/complex_plane.rs index 396a7ac..c3a6f43 100644 --- a/src/complex_plane.rs +++ b/src/complex_plane.rs @@ -88,11 +88,7 @@ impl ComplexPlane { pub fn print(&self) { println!("Complex plane:\tR ∈ [{},{}]", self.min_x, self.max_x); println!("\t\tC ∈ [{},{}]", self.min_y, self.max_y); - println!( - "\t\tCenter is {:?} and scale is {}", - self.center(), - self.get_scale() - ); + println!("\t\tCenter is {:?} and scale is {}", self.center(), self.get_scale()); } /// Resets the total translation and scaling applied to the Complex plane by the translate() and scale() functions @@ -135,10 +131,10 @@ impl ComplexPlane { let old = self.center(); let mut translation = center.subtract(&old); translation.y = -translation.y; //Negate because the Complex plane and pixel plane are flipped - /*println!("DEBUG set_center():"); - println!("\tcenter: {:?}", center); - println!("\told: {:?}", old); - println!("\ttranslation: {:?}", translation);*/ + /*println!("DEBUG set_center():"); + println!("\tcenter: {:?}", center); + println!("\told: {:?}", old); + println!("\ttranslation: {:?}", translation);*/ self.translate(translation.x, translation.y); translation } @@ -151,13 +147,12 @@ impl ComplexPlane { } /// Set the Complex plane at Center (x,y) at the given scale, where scale == 1 => max_x-min_x=2.5 - pub fn set_view(&mut self, view: &View) - { + pub fn set_view(&mut self, view: &View) { self.set_view_separated(view.x, view.y, view.scale); } pub fn pixels_to_imaginary(&self, amount: u8) -> f64 { - f64::from(amount) * self.increment_y + f64::from(amount) * self.increment_y } pub fn pixels_to_real(&self, amount: u8) -> f64 { @@ -182,12 +177,12 @@ impl ComplexPlane { pub struct View { x: f64, y: f64, - scale: f64 + scale: f64, } impl View { pub const fn new(x: f64, y: f64, scale: f64) -> View { - View {x,y,scale} + View { x, y, scale } } } @@ -195,4 +190,4 @@ impl std::fmt::Debug for View { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "z = {:?}, scale = {}", Complex::new(self.x, self.y), self.scale) } -} \ No newline at end of file +} diff --git a/src/config.rs b/src/config.rs index f24a9bd..23576af 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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; @@ -14,17 +17,16 @@ pub struct Config { 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 { /// Parse the command line arguments from e.g. `env::args` in the following format /// ```ignore @@ -36,7 +38,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)?; @@ -53,15 +55,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
///If Some(arg) == "-", return default /// # Errors /// Return an Error if the given argument cannot be parsed to a T type - pub fn parse_argument(name: &str, argument: Option, default: T) -> Result - where ::Err: Display{ + pub fn parse_argument(name: &str, argument: Option, default: T) -> Result + where + ::Err: Display, + { match argument { Some(arg) => { if arg == "-" { @@ -72,8 +85,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) } @@ -86,7 +99,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() } } diff --git a/src/controller/mod.rs b/src/controller/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/key_bindings.rs b/src/key_bindings.rs index 6ae4b85..4a5f901 100644 --- a/src/key_bindings.rs +++ b/src/key_bindings.rs @@ -12,11 +12,7 @@ pub struct KeyAction { impl KeyAction { pub fn new(key: Key, description: &'static str, action: Box) -> KeyAction { - KeyAction { - key, - description, - action, - } + KeyAction { key, description, action } } ///Run self.action @@ -27,7 +23,7 @@ impl KeyAction { impl fmt::Debug for KeyAction { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f,"{:?} -> {}", &self.key, &self.description) + write!(f, "{:?} -> {}", &self.key, &self.description) } } @@ -59,7 +55,7 @@ impl KeyBindings { /// Prints all `KeyAction`s in these `KeyBindings` to stdout pub fn print(&self) { - println!("{:?}",self); + println!("{:?}", self); } pub fn print_key(&self, key: &Key) { @@ -88,8 +84,8 @@ impl Default for KeyBindings { /// Define all your keybindings here fn default() -> KeyBindings { let mut key_bindings = KeyBindings::new(Vec::new()); - key_bindings.add(Key::A, "This is the A key", ||println!("Action A")); - key_bindings.add(Key::B, "This is the B key", ||println!("Action B")); + key_bindings.add(Key::A, "This is the A key", || println!("Action A")); + key_bindings.add(Key::B, "This is the B key", || println!("Action B")); key_bindings } } diff --git a/src/lib.rs b/src/lib.rs index 594f74e..421ea86 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,20 @@ clippy::cast_sign_loss )] +pub mod coloring; +pub mod complex; +pub mod complex_plane; +pub mod config; +pub mod controller; +pub mod key_bindings; +pub mod mandelbrot_set; +pub mod model; +pub mod pixel_buffer; +pub mod pixel_plane; +pub mod rendering; +pub mod user_input; +pub mod view; + use std::error::Error; use std::sync::atomic::{AtomicBool, Ordering}; @@ -38,22 +52,12 @@ use crate::coloring::TrueColor; use crate::complex_plane::{ComplexPlane, View}; use crate::key_bindings::KeyBindings; use crate::pixel_buffer::PixelBuffer; -use crate::pixel_buffer::pixel_plane::PixelPlane; +use crate::pixel_plane::PixelPlane; use crate::user_input::{ask, pick_option}; -pub mod complex_plane; -pub mod complex; -pub mod pixel_buffer; -pub mod mandelbrot_set; -pub mod rendering; -pub mod key_bindings; -pub mod coloring; -pub mod user_input; -pub mod config; - //Coloring function type ColoringFunction = fn(iterations: u32, max_iterations: u32) -> TrueColor; -static COLORING_FUNCTION : ColoringFunction = TrueColor::new_from_bernstein_polynomials; +static COLORING_FUNCTION: ColoringFunction = TrueColor::new_from_bernstein_polynomials; //Color channel mapping static COLOR_CHANNEL_MAPPING: ColorChannelMapping = ColorChannelMapping::RGB; @@ -65,15 +69,15 @@ static VIEW_3: View = View::new(-0.4624999999999999, 0.55, 0.1); static VIEW_4: View = View::new(-0.46395833333333325, 0.5531250000000001, 0.03); static VIEW_5: View = View::new(-0.4375218333333333, 0.5632133750000003, 0.00002000000000000002); static VIEW_6: View = View::new(-0.7498100000000001, -0.020300000000000054, 0.00006400000000000002); -static VIEW_7: View = View::new(-1.7862712000000047, 0.000052399999999991516, 0.00001677721600000001); -static VIEW_8: View = View::new(-1.7862581627050718, 0.00005198056959995248, 0.000006039797760000003); -static VIEW_9: View = View::new( -0.4687339999999999, 0.5425518958333333, 0.000010000000000000003); -static VIEW_0: View = View::new( -0.437520465811966, 0.5632133750000006, 0.000004000000000000004); +static VIEW_7: View = View::new(-1.7862712000000047, 0.000052399999999991516, 0.00001677721600000001); +static VIEW_8: View = View::new(-1.7862581627050718, 0.00005198056959995248, 0.000006039797760000003); +static VIEW_9: View = View::new(-0.4687339999999999, 0.5425518958333333, 0.000010000000000000003); +static VIEW_0: View = View::new(-0.437520465811966, 0.5632133750000006, 0.000004000000000000004); //Banner values static VERSION: &str = "1.4"; -pub struct InteractionVariables{ +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 @@ -81,9 +85,13 @@ pub struct InteractionVariables{ pub scale_numerator: f64, } -impl InteractionVariables{ +impl InteractionVariables { pub fn new(translation_amount: u8, scale_numerator: f64, scale_denominator: f64) -> InteractionVariables { - InteractionVariables { translation_amount, scale_denominator, scale_numerator } + InteractionVariables { + translation_amount, + scale_denominator, + scale_numerator, + } } pub fn scaling_factor(&self) -> f64 { @@ -100,7 +108,7 @@ impl InteractionVariables{ pub fn decrement_translation_amount(&mut self) { if self.translation_amount > 1 { - self.translation_amount -=1; + self.translation_amount -= 1; } } @@ -117,22 +125,69 @@ impl InteractionVariables{ } } -impl Default for InteractionVariables{ +impl Default for InteractionVariables { fn default() -> Self { - InteractionVariables { translation_amount:10, scale_numerator: 9.0, scale_denominator: 10.0 } + InteractionVariables { + translation_amount: 10, + scale_numerator: 9.0, + scale_denominator: 10.0, + } } } // Handle any key events -fn handle_key_events(window: &Window, c: &mut ComplexPlane, p: &mut PixelBuffer, m: &mut MandelbrotSet, vars: &mut InteractionVariables, k: &KeyBindings, supersampling_amount: &mut u8, image_supersampling_amount: &mut u8,coloring_function: &mut ColoringFunction, config: &Config) { +fn handle_key_events( + window: &Window, + c: &mut ComplexPlane, + p: &mut PixelBuffer, + m: &mut MandelbrotSet, + vars: &mut InteractionVariables, + k: &KeyBindings, + supersampling_amount: &mut u8, + image_supersampling_amount: &mut u8, + coloring_function: &mut ColoringFunction, + config: &Config, +) { if let Some(key) = window.get_keys_pressed(minifb::KeyRepeat::No).first() { print!("\nKey pressed: "); k.print_key(key); match key { - Key::Up => rendering::translate_and_render_efficiently(c, p, m, vars.translation_amount.into(), 0, *supersampling_amount, *coloring_function), - Key::Down => rendering::translate_and_render_efficiently(c, p, m, -i16::from(vars.translation_amount), 0, *supersampling_amount, *coloring_function), - Key::Left => rendering::translate_and_render_efficiently(c, p, m, 0, -i16::from(vars.translation_amount), *supersampling_amount, *coloring_function), - Key::Right => rendering::translate_and_render_efficiently(c, p, m, 0, vars.translation_amount.into(), *supersampling_amount, *coloring_function), + Key::Up => rendering::translate_and_render_efficiently( + c, + p, + m, + vars.translation_amount.into(), + 0, + *supersampling_amount, + *coloring_function, + ), + Key::Down => rendering::translate_and_render_efficiently( + c, + p, + m, + -i16::from(vars.translation_amount), + 0, + *supersampling_amount, + *coloring_function, + ), + Key::Left => rendering::translate_and_render_efficiently( + c, + p, + m, + 0, + -i16::from(vars.translation_amount), + *supersampling_amount, + *coloring_function, + ), + Key::Right => rendering::translate_and_render_efficiently( + c, + p, + m, + 0, + vars.translation_amount.into(), + *supersampling_amount, + *coloring_function, + ), Key::R => c.reset(), Key::NumPadPlus => vars.increment_translation_amount(), Key::NumPadMinus => vars.decrement_translation_amount(), @@ -166,22 +221,47 @@ fn handle_key_events(window: &Window, c: &mut ComplexPlane, p: &mut PixelBuffer, } } Key::I => c.set_view(&View::new(ask("x"), ask("y"), ask("scale"))), - Key::A => *coloring_function = pick_option(&[("HSV", TrueColor::new_from_hsv_colors), ("Bernstein polynomials", TrueColor::new_from_bernstein_polynomials)]), + Key::A => { + *coloring_function = pick_option(&[ + ("HSV", TrueColor::new_from_hsv_colors), + ("Bernstein polynomials", TrueColor::new_from_bernstein_polynomials), + ]) + } Key::M => m.max_iterations = ask("max_iterations"), Key::O => p.color_channel_mapping = ask("color_channel_mapping"), - Key::Q => {*supersampling_amount = ask::("supersampling_amount").clamp(1, 64); *image_supersampling_amount = *supersampling_amount;}, + Key::Q => { + *supersampling_amount = ask::("supersampling_amount").clamp(1, 64); + *image_supersampling_amount = *supersampling_amount; + } Key::X => *image_supersampling_amount = ask::("image_supersampling_amount").clamp(1, 64), Key::C => println!("{:?}", config), _ => (), } match key { Key::NumPadPlus | Key::NumPadMinus => println!("translation_amount: {}", vars.translation_amount), - Key::NumPadSlash | Key::NumPadAsterisk => println!("scale factor: {}/{}",vars.scale_numerator,vars.scale_denominator), + Key::NumPadSlash | Key::NumPadAsterisk => println!("scale factor: {}/{}", vars.scale_numerator, vars.scale_denominator), Key::Up | Key::Down | Key::Left | Key::Right => c.print(), - Key::R | Key::Key1 | Key::Key2 | Key::Key3 | Key::Key4 | Key::Key5 | Key::Key6 | Key::Key7 | Key::Key8 | Key::Key9 | Key::Key0 | Key::LeftBracket | Key::RightBracket | Key::I | Key::A | Key::M | Key::O | Key::Q => { + Key::R + | Key::Key1 + | Key::Key2 + | Key::Key3 + | Key::Key4 + | Key::Key5 + | Key::Key6 + | Key::Key7 + | Key::Key8 + | Key::Key9 + | Key::Key0 + | Key::LeftBracket + | Key::RightBracket + | Key::I + | Key::A + | Key::M + | Key::O + | Key::Q => { rendering::render_complex_plane_into_buffer(p, c, m, *supersampling_amount, *coloring_function); c.print(); - }, + } _ => (), } } @@ -200,7 +280,15 @@ fn handle_left_mouse_clicked(x: f32, y: f32, c: &ComplexPlane) { println!(); } -fn handle_right_mouse_clicked(x: f32, y: f32, c: &mut ComplexPlane, p: &mut PixelBuffer, m: &MandelbrotSet, supersampling_amount: u8, coloring_function: ColoringFunction) { +fn handle_right_mouse_clicked( + x: f32, + y: f32, + c: &mut ComplexPlane, + p: &mut PixelBuffer, + m: &MandelbrotSet, + supersampling_amount: u8, + coloring_function: ColoringFunction, +) { println!("\nMouseButton::Right -> Move to ({x}, {y})"); let new_center = c.complex_from_pixel_plane(x.into(), y.into()); println!("c.center: {:?}", c.center()); @@ -211,16 +299,19 @@ fn handle_right_mouse_clicked(x: f32, y: f32, c: &mut ComplexPlane, p: &mut Pixe println!(); } -/////Mouse click recorder with interior mutability to toggle mouse clicks; +/////Mouse click recorder with interior mutability to toggle mouse clicks; /// without such a (static function) variable, clicking the screen once would result in multiple actions struct MouseClickRecorder { mouse_button: MouseButton, - previous: AtomicBool + previous: AtomicBool, } impl MouseClickRecorder { pub const fn new(mouse_button: MouseButton) -> MouseClickRecorder { - MouseClickRecorder { mouse_button, previous: AtomicBool::new(false) } + MouseClickRecorder { + mouse_button, + previous: AtomicBool::new(false), + } } ///Returns whether the `mouse_button` was clicked once @@ -228,17 +319,25 @@ impl MouseClickRecorder { let current = window.get_mouse_down(self.mouse_button); let previous = self.previous.load(Ordering::Relaxed); let result = was_clicked(current, previous); - if current != previous {self.previous.store(current, Ordering::Relaxed)} + if current != previous { + self.previous.store(current, Ordering::Relaxed) + } result } } -fn handle_mouse_events(window: &Window, c: &mut ComplexPlane, p: &mut PixelBuffer, m: &MandelbrotSet, supersampling_amount: u8, coloring_function: ColoringFunction) { +fn handle_mouse_events( + window: &Window, + c: &mut ComplexPlane, + p: &mut PixelBuffer, + m: &MandelbrotSet, + supersampling_amount: u8, + coloring_function: ColoringFunction, +) { static LEFT_MOUSE_RECORDER: MouseClickRecorder = MouseClickRecorder::new(MouseButton::Left); //Static variable with interior mutability to toggle mouse clicks; without such a variable, clicking the screen once would result in multiple actions - static RIGHT_MOUSE_RECORDER: MouseClickRecorder = MouseClickRecorder::new(MouseButton::Right); + static RIGHT_MOUSE_RECORDER: MouseClickRecorder = MouseClickRecorder::new(MouseButton::Right); if let Some((x, y)) = window.get_mouse_pos(MouseMode::Discard) { - //Left mouse actions if LEFT_MOUSE_RECORDER.was_clicked(window) { handle_left_mouse_clicked(x, y, c); @@ -248,31 +347,29 @@ fn handle_mouse_events(window: &Window, c: &mut ComplexPlane, p: &mut PixelBuffe if RIGHT_MOUSE_RECORDER.was_clicked(window) { handle_right_mouse_clicked(x, y, c, p, m, supersampling_amount, coloring_function); } - } } -///Prints Mandelbrot ASCII art :)
+///Prints Mandelbrot ASCII art :)
///Prints the `application_banner`, `author_banner`, and `version` -fn print_banner() -{ -//Made using: https://patorjk.com/software/taag/#p=display&f=Big&t=Mandelbrot -let application_banner = r" +fn print_banner() { + //Made using: https://patorjk.com/software/taag/#p=display&f=Big&t=Mandelbrot + let application_banner = r" __ __ _ _ _ _ | \/ | | | | | | | | | \ / | __ _ _ __ __| | ___| | |__ _ __ ___ | |_ | |\/| |/ _` | '_ \ / _` |/ _ \ | '_ \| '__/ _ \| __| | | | | (_| | | | | (_| | __/ | |_) | | | (_) | |_ |_| |_|\__,_|_| |_|\__,_|\___|_|_.__/|_| \___/ \__|"; -//Made using: https://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=by%20Jort -let author_banner = r" + //Made using: https://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=by%20Jort + let author_banner = r" __ __ __ / / __ __ __ / /__ ____/ /_ / _ \/ // / / // / _ \/ __/ __/ /_.__/\_, / \___/\___/_/ \__/ /___/ "; -let version = VERSION; -println!("{}{}v{}\n\n", application_banner, author_banner, version); + let version = VERSION; + println!("{}{}v{}\n\n", application_banner, author_banner, version); } ///Prints a command info tip for the users benefit @@ -280,7 +377,7 @@ fn print_command_info() { let tip = "Run Mandelbrot using:"; let command = "cargo run --release -- "; let command_info = "where means substitute with the value of arg\nuse '-' to use the default value of arg"; - println!("{}\n\t{}\n{}\n",tip, command, command_info); + println!("{}\n\t{}\n{}\n", tip, command, command_info); } ///Holds all the logic currently in the main function that isn't involved with setting up configuration or handling errors, to make `main` concise and @@ -298,7 +395,7 @@ pub fn run(config: &Config) -> Result<(), Box> { let mut vars = InteractionVariables::default(); // Multithreading variables let amount_of_threads = num_cpus::get(); //Amount of CPU threads to use, TODO: use this value in rendering functions - // Mandelbrot set iterator + // Mandelbrot set iterator let mut m: MandelbrotSet = MandelbrotSet::new(config.max_iterations, config.orbit_radius); //Coloring function let mut coloring_function = COLORING_FUNCTION; @@ -322,7 +419,7 @@ pub fn run(config: &Config) -> Result<(), Box> { print_banner(); //Print command info print_command_info(); - //Initialize keybindings TODO: I want to have a vector of structs containing functions with different signatures, this is not easily possible. All functionality should be placed here, in the future, when + //Initialize keybindings TODO: I want to have a vector of structs containing functions with different signatures, this is not easily possible. All functionality should be placed here, in the future, when //I've figured out how to have closures with different signatures in the same struct field //For now, use empty_closure, to have a closure that does nothing as action let mut key_bindings: KeyBindings = KeyBindings::new(Vec::new()); @@ -336,9 +433,21 @@ pub fn run(config: &Config) -> Result<(), Box> { key_bindings.add(Key::NumPadMinus, "Decrement translation amount", empty_closure); key_bindings.add(Key::NumPadAsterisk, "Increment scale_numerator", empty_closure); key_bindings.add(Key::NumPadSlash, "Decrement scale_numerator", empty_closure); - key_bindings.add(Key::LeftBracket, "Scale the view by scaling_factor, effectively zooming in",empty_closure); - key_bindings.add(Key::RightBracket, "Scale the view by inverse_scaling_factor, effectively zooming out", empty_closure); - key_bindings.add(Key::V, "Prints the current Mandelbrot set view; the center and scale", empty_closure); + key_bindings.add( + Key::LeftBracket, + "Scale the view by scaling_factor, effectively zooming in", + empty_closure, + ); + key_bindings.add( + Key::RightBracket, + "Scale the view by inverse_scaling_factor, effectively zooming out", + empty_closure, + ); + key_bindings.add( + Key::V, + "Prints the current Mandelbrot set view; the center and scale", + empty_closure, + ); key_bindings.add(Key::Key1, "Renders VIEW_1", empty_closure); key_bindings.add(Key::Key2, "Renders VIEW_2", empty_closure); key_bindings.add(Key::Key3, "Renders VIEW_3", empty_closure); @@ -350,19 +459,38 @@ pub fn run(config: &Config) -> Result<(), Box> { key_bindings.add(Key::Key9, "Renders VIEW_9", empty_closure); key_bindings.add(Key::Key0, "Renders VIEW_0", empty_closure); key_bindings.add(Key::K, "Prints the keybindings", empty_closure); - key_bindings.add(Key::S, "Saves the current Mandelbrot set view as an image in the saved folder", empty_closure); + key_bindings.add( + Key::S, + "Saves the current Mandelbrot set view as an image in the saved folder", + empty_closure, + ); key_bindings.add(Key::I, "Manually input a Mandelbrot set view", empty_closure); key_bindings.add(Key::A, "Pick an algorithm to color the Mandelbrot set view", empty_closure); key_bindings.add(Key::M, "Change the Mandelbrot set view max_iterations", empty_closure); - key_bindings.add(Key::O, "Change the Mandelbrot set view color channel mapping, xyz -> RGB, where x,y,z ∈ {{'R','G','B'}} (case-insensitive)", empty_closure); - key_bindings.add(Key::Q, "Change the window and image quality of the Mandelbrot set rendering by setting the SSAA multiplier, clamped from 1x to 64x", empty_closure); - key_bindings.add(Key::X, "Change the image quality of the Mandelbrot set rendering by setting the SSAA multiplier, clamped from 1x to 64x", empty_closure); + key_bindings.add( + Key::O, + "Change the Mandelbrot set view color channel mapping, xyz -> RGB, where x,y,z ∈ {{'R','G','B'}} (case-insensitive)", + empty_closure, + ); + key_bindings.add( + Key::Q, + "Change the window and image quality of the Mandelbrot set rendering by setting the SSAA multiplier, clamped from 1x to 64x", + empty_closure, + ); + key_bindings.add( + Key::X, + "Change the image quality of the Mandelbrot set rendering by setting the SSAA multiplier, clamped from 1x to 64x", + empty_closure, + ); key_bindings.add(Key::C, "Prints the configuration variables", empty_closure); key_bindings.print(); p.pixel_plane.print(); c.print(); - println!("Mandelbrot set parameters: max. iterations is {} and orbit radius is {}", config.max_iterations, config.orbit_radius); + println!( + "Mandelbrot set parameters: max. iterations is {} and orbit radius is {}", + config.max_iterations, config.orbit_radius + ); println!("Amount of CPU threads that will be used for rendering: {}", amount_of_threads); println!("Supersampling amount used for rendering: {}x", supersampling_amount); println!(); @@ -372,12 +500,24 @@ pub fn run(config: &Config) -> Result<(), Box> { // Main loop while window.is_open() && !window.is_key_down(Key::Escape) { - // Update the window with the new buffer - window.update_with_buffer(&p.pixels, config.window_width, config.window_height).unwrap(); + window + .update_with_buffer(&p.pixels, config.window_width, config.window_height) + .unwrap(); // Handle any window events - handle_key_events(&window, &mut c, &mut p, &mut m, &mut vars, &key_bindings, &mut supersampling_amount, &mut image_supersampling_amount, &mut coloring_function, config); + handle_key_events( + &window, + &mut c, + &mut p, + &mut m, + &mut vars, + &key_bindings, + &mut supersampling_amount, + &mut image_supersampling_amount, + &mut coloring_function, + config, + ); //Handle any mouse events handle_mouse_events(&window, &mut c, &mut p, &m, supersampling_amount, coloring_function); diff --git a/src/main.rs b/src/main.rs index 04d8f71..e598066 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use std::env; use std::process; -use mandelbrot::{Config, self}; +use mandelbrot::{self, Config}; fn main() { let config = Config::build(env::args()).unwrap_or_else(|err| { diff --git a/src/mandelbrot_set.rs b/src/mandelbrot_set.rs index f5790bb..b34d322 100644 --- a/src/mandelbrot_set.rs +++ b/src/mandelbrot_set.rs @@ -4,12 +4,15 @@ use crate::complex::Complex; pub struct MandelbrotSet { pub max_iterations: u32, ///If z remains within the orbit_radius in max_iterations, we assume c does not tend to infinity - pub orbit_radius: f64, + pub orbit_radius: f64, } impl MandelbrotSet { pub fn new(max_iterations: u32, orbit_radius: f64) -> MandelbrotSet { - MandelbrotSet { max_iterations, orbit_radius } + MandelbrotSet { + max_iterations, + orbit_radius, + } } /// Run the Mandelbrot set algorithm for a single Complex number @@ -17,11 +20,12 @@ impl MandelbrotSet { pub fn iterate(&self, c: &Complex) -> u32 { let mut z = Complex::new(0.0, 0.0); let mut iterations: u32 = 0; - let orbit_radius_squared = self.orbit_radius*self.orbit_radius; + let orbit_radius_squared = self.orbit_radius * self.orbit_radius; for _ in 0..self.max_iterations { z = z.squared().add(c); - if (z.x * z.x + z.y * z.y) > orbit_radius_squared { //Optimization: square both sides of the Mandelbrot set function, saves us taking the square root + if (z.x * z.x + z.y * z.y) > orbit_radius_squared { + //Optimization: square both sides of the Mandelbrot set function, saves us taking the square root break; } iterations += 1; @@ -34,4 +38,4 @@ impl std::fmt::Debug for MandelbrotSet { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "max_iterations = {}, orbit_radius = {}", self.max_iterations, self.orbit_radius) } -} \ No newline at end of file +} diff --git a/src/model/mandelbrot_model.rs b/src/model/mandelbrot_model.rs new file mode 100644 index 0000000..fc86c3f --- /dev/null +++ b/src/model/mandelbrot_model.rs @@ -0,0 +1,14 @@ +use lazy_static::lazy_static; +use std::sync::Mutex; + +lazy_static! { + static ref SINGLETON_INSTANCE: Mutex = Mutex::new(MandelbrotModel::new()); +} + +struct MandelbrotModel {} + +impl MandelbrotModel { + fn new() -> MandelbrotModel { + MandelbrotModel {} + } +} diff --git a/src/model/mod.rs b/src/model/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/pixel_buffer.rs b/src/pixel_buffer.rs index d4e8934..4f6ddbd 100644 --- a/src/pixel_buffer.rs +++ b/src/pixel_buffer.rs @@ -1,10 +1,11 @@ -use std::{path::Path, fs::File, io::BufWriter}; +use std::{fs::File, io::BufWriter, path::Path}; -use crate::{coloring::{TrueColor, ColorChannelMapping}, complex_plane::View, mandelbrot_set::MandelbrotSet}; - -use self::pixel_plane::PixelPlane; - -pub mod pixel_plane; +use crate::{ + coloring::{ColorChannelMapping, TrueColor}, + complex_plane::View, + mandelbrot_set::MandelbrotSet, + pixel_plane::PixelPlane, +}; #[derive(Clone)] pub struct PixelBuffer { @@ -21,7 +22,12 @@ impl PixelBuffer { let colors: Vec = vec![black; pixel_plane.width * pixel_plane.height]; let color_channel_mapping = ColorChannelMapping::RGB; let pixels: Vec = PixelBuffer::colors_to_pixels(&colors, &color_channel_mapping); - PixelBuffer { pixel_plane, colors, pixels, color_channel_mapping} + PixelBuffer { + pixel_plane, + colors, + pixels, + color_channel_mapping, + } } /// Converts a buffer index to a screen coordinate @@ -50,7 +56,7 @@ impl PixelBuffer { let pixel = self.buffer[index]; let iterations = crate::iterations_from_hsv_pixel(pixel, max_iterations); iterations - }*/ + }*/ /// Translate the complex plane in the `buffer` `rows` up and `columns` to the right. /// This operation is significantly less expensive than the `render_box_render_complex_plane_into_buffer` function, as it does not rerender anything in the complex plane, it simply @@ -58,12 +64,20 @@ impl PixelBuffer { /// Note: The removed rows and columns should be rerendered by the `render_box_render_complex_plane_into_buffer` function. pub fn translate_buffer(&mut self, rows_up: i128, columns_right: i128) { //Iterate over the correct y's in the correct order - let y_range : Vec = if rows_up > 0 {((rows_up as usize)..self.pixel_plane.height).rev().collect()} else {(0..((self.pixel_plane.height as i128 + rows_up) as usize)).collect()}; + let y_range: Vec = if rows_up > 0 { + ((rows_up as usize)..self.pixel_plane.height).rev().collect() + } else { + (0..((self.pixel_plane.height as i128 + rows_up) as usize)).collect() + }; //Iterate over the correct x's in the correct order - let x_range : Vec = if columns_right > 0 {((columns_right as usize)..self.pixel_plane.width).rev().collect()} else {(0..((self.pixel_plane.width as i128 + columns_right) as usize)).collect()}; + let x_range: Vec = if columns_right > 0 { + ((columns_right as usize)..self.pixel_plane.width).rev().collect() + } else { + (0..((self.pixel_plane.width as i128 + columns_right) as usize)).collect() + }; for y in y_range { - let other_y = (y as i128-rows_up) as usize; + let other_y = (y as i128 - rows_up) as usize; //println!("y: {y} and other_y: {other_y}"); for x in &x_range { let other_x = (*x as i128 - columns_right) as usize; @@ -85,7 +99,8 @@ impl PixelBuffer { pub fn save_as_png(&self, file_name_without_extension: &str, view: &View, m: &MandelbrotSet, supersampling_amount: u8) { let file_name_without_extension = file_name_without_extension.replace(':', "-").replace(' ', "_"); //Replace ':' with '-' for Windows file system. Replace ' ' with '_' because spaces are annoying in filenames. let file_name = format!("saved{}{}.png", std::path::MAIN_SEPARATOR_STR, file_name_without_extension); - match std::fs::create_dir_all("saved") { //Create the saved folder if it does not exist + match std::fs::create_dir_all("saved") { + //Create the saved folder if it does not exist Ok(()) => (), //Currently not doing anything with the Result of trying to create the saved folder Err(err) => eprintln!("{}", err), } @@ -100,11 +115,22 @@ impl PixelBuffer { let mandelbrot_set_text = format!("{:?}", m); encoder.add_text_chunk(String::from("mandelbrot_set"), mandelbrot_set_text).unwrap(); let supersampling_amount_text = format!("{}x", supersampling_amount); - encoder.add_text_chunk(String::from("supersampling_amount"), supersampling_amount_text).unwrap(); - encoder.add_text_chunk(String::from("application"), String::from("Mandelbrot by Jort (https://github.com/jortrr/mandelbrot)")).unwrap(); - encoder.add_text_chunk(String::from("author"), String::from("jortrr (https://github.com/jortrr/)")).unwrap(); + encoder + .add_text_chunk(String::from("supersampling_amount"), supersampling_amount_text) + .unwrap(); + encoder + .add_text_chunk( + String::from("application"), + String::from("Mandelbrot by Jort (https://github.com/jortrr/mandelbrot)"), + ) + .unwrap(); + encoder + .add_text_chunk(String::from("author"), String::from("jortrr (https://github.com/jortrr/)")) + .unwrap(); let color_channel_mapping_text = format!("{:?}", self.color_channel_mapping); - encoder.add_text_chunk(String::from("color_channel_mapping"), color_channel_mapping_text).unwrap(); + encoder + .add_text_chunk(String::from("color_channel_mapping"), color_channel_mapping_text) + .unwrap(); let mut data: Vec = Vec::new(); let (r_map, g_map, b_map) = self.color_channel_mapping.get_r_g_b_mapping(); for color in &self.colors { diff --git a/src/pixel_buffer/pixel_plane.rs b/src/pixel_plane.rs similarity index 50% rename from src/pixel_buffer/pixel_plane.rs rename to src/pixel_plane.rs index 974b9ad..e0c05c6 100644 --- a/src/pixel_buffer/pixel_plane.rs +++ b/src/pixel_plane.rs @@ -1,20 +1,27 @@ - #[derive(Clone)] pub struct PixelPlane { pub width: usize, pub height: usize, aspect_ratio_width: usize, - aspect_ratio_height: usize + aspect_ratio_height: usize, } impl PixelPlane { - pub fn new(width: usize, height: usize) -> PixelPlane{ + pub fn new(width: usize, height: usize) -> PixelPlane { let gcd = num::integer::gcd(width, height); //Needed to compute the aspect ratio of the pixel plane - PixelPlane {width, height, aspect_ratio_width: width/gcd, aspect_ratio_height: height/gcd} + PixelPlane { + width, + height, + aspect_ratio_width: width / gcd, + aspect_ratio_height: height / gcd, + } } /// Prints: "Pixel plane: size is {}x{} and aspect ratio is {}:{}", `self.width`, `self.height`, `self.aspect_ratio_width`, `self.aspect_ratio_height`) pub fn print(&self) { - println!("Pixel plane: size is {}x{} and aspect ratio is {}:{}",self.width, self.height, self.aspect_ratio_width, self.aspect_ratio_height); + println!( + "Pixel plane: size is {}x{} and aspect ratio is {}:{}", + self.width, self.height, self.aspect_ratio_width, self.aspect_ratio_height + ); } -} \ No newline at end of file +} diff --git a/src/rendering.rs b/src/rendering.rs index 1196623..9e299d1 100644 --- a/src/rendering.rs +++ b/src/rendering.rs @@ -1,30 +1,50 @@ //Temporary file to group together all rendering functionality -use std::{time::Instant, thread, sync::{Arc, Mutex, atomic::{AtomicU8, Ordering}}, io::{self, Write}}; +use std::{ + io::{self, Write}, + sync::{ + atomic::{AtomicU8, Ordering}, + Arc, Mutex, + }, + thread, + time::Instant, +}; use rand::Rng; -use crate::{pixel_buffer::PixelBuffer, complex_plane::ComplexPlane, mandelbrot_set::MandelbrotSet, complex::Complex, coloring::TrueColor}; +use crate::{coloring::TrueColor, complex::Complex, complex_plane::ComplexPlane, mandelbrot_set::MandelbrotSet, pixel_buffer::PixelBuffer}; ///A box representing the area to render by rendering functions -#[derive(Clone,Copy)] +#[derive(Clone, Copy)] pub struct RenderBox { min_x: usize, max_x: usize, min_y: usize, - max_y: usize + max_y: usize, } impl RenderBox { - pub fn new(min_x: usize, max_x: usize, min_y: usize, max_y: usize) -> RenderBox { - RenderBox { min_x, max_x, min_y, max_y } + pub fn new(min_x: usize, max_x: usize, min_y: usize, max_y: usize) -> RenderBox { + RenderBox { + min_x, + max_x, + min_y, + max_y, + } } pub fn print(&self) { - println!("RenderBox: ({},{}) -> ({},{}) {{{} pixels}}",self.min_x,self.min_y,self.max_x,self.max_y, self.compute_pixel_count()); + println!( + "RenderBox: ({},{}) -> ({},{}) {{{} pixels}}", + self.min_x, + self.min_y, + self.max_x, + self.max_y, + self.compute_pixel_count() + ); } pub fn compute_pixel_count(&self) -> usize { - (self.max_x-self.min_x)*(self.max_y-self.min_y) + (self.max_x - self.min_x) * (self.max_y - self.min_y) } ///Returns whether the point (x,y) is inside the `RenderBox` @@ -33,18 +53,23 @@ impl RenderBox { } } - -/// Render the Complex plane c into the 32-bit pixel buffer by applying the Mandelbrot formula iteratively to every Complex point mapped to a pixel in the buffer. +/// Render the Complex plane c into the 32-bit pixel buffer by applying the Mandelbrot formula iteratively to every Complex point mapped to a pixel in the buffer. /// The buffer should have a size of width*height. /// `orbit_radius` determines when Zn is considered to have gone to infinity. /// `max_iterations` concerns the maximum amount of times the Mandelbrot formula will be applied to each Complex number. /// Note: This function is computationally intensive, and should not be used for translations -pub fn render_complex_plane_into_buffer(p: &mut PixelBuffer, c: &ComplexPlane, m: &MandelbrotSet, supersampling_amount: u8,coloring_function: fn(iterations: u32, max_iterations: u32) -> TrueColor) { +pub fn render_complex_plane_into_buffer( + p: &mut PixelBuffer, + c: &ComplexPlane, + m: &MandelbrotSet, + supersampling_amount: u8, + coloring_function: fn(iterations: u32, max_iterations: u32) -> TrueColor, +) { let render_box = RenderBox::new(0, p.pixel_plane.width, 0, p.pixel_plane.height); - render_box_render_complex_plane_into_buffer(p, c, m, render_box, supersampling_amount,coloring_function); + render_box_render_complex_plane_into_buffer(p, c, m, render_box, supersampling_amount, coloring_function); } -/// Render the Complex plane c into the 32-bit pixel buffer by applying the Mandelbrot formula iteratively to every Complex point mapped to a pixel in the buffer. +/// Render the Complex plane c into the 32-bit pixel buffer by applying the Mandelbrot formula iteratively to every Complex point mapped to a pixel in the buffer. /// The buffer should have a size of width*height. /// Only renders Pixels inside the render box denoted by `render_min_x`, `render_max_x`, `render_min_y`, `render_max_y` /// `orbit_radius` determines when Zn is considered to have gone to infinity. @@ -52,9 +77,16 @@ pub fn render_complex_plane_into_buffer(p: &mut PixelBuffer, c: &ComplexPlane, m /// Note: This function is computationally intensive, and should not be used for translations /// Note: This function is multithreaded /// * `coloring_function` - e.g. `TrueColor::new_from_hsv` -/// # Panics +/// # Panics /// If `lock().unwrap()` panics -pub fn render_box_render_complex_plane_into_buffer(p: &mut PixelBuffer, c: &ComplexPlane, m: &MandelbrotSet, render_box: RenderBox, supersampling_amount: u8, coloring_function: fn(iterations: u32, max_iterations: u32) -> TrueColor) { +pub fn render_box_render_complex_plane_into_buffer( + p: &mut PixelBuffer, + c: &ComplexPlane, + m: &MandelbrotSet, + render_box: RenderBox, + supersampling_amount: u8, + coloring_function: fn(iterations: u32, max_iterations: u32) -> TrueColor, +) { let time = benchmark_start(); let supersampling_amount = supersampling_amount.clamp(1, 64); //Supersampling_amount should be at least 1 and atmost 64 render_box.print(); @@ -68,7 +100,7 @@ pub fn render_box_render_complex_plane_into_buffer(p: &mut PixelBuffer, c: &Comp let global_mutex = Arc::new(Mutex::new(0)); let max_progress: u8 = 30; let chunks_len_over_max_progress = chunks_len / max_progress as usize; - let current_progress_atomic: Arc>= Arc::new(Mutex::new(AtomicU8::new(0))); + let current_progress_atomic: Arc> = Arc::new(Mutex::new(AtomicU8::new(0))); for _thread_id in 0..amount_of_threads { let plane = (*c).clone(); @@ -84,7 +116,7 @@ pub fn render_box_render_complex_plane_into_buffer(p: &mut PixelBuffer, c: &Comp loop { let mut data = thread_mutex.lock().unwrap(); let current_chunk = *data; - *data+=1; + *data += 1; drop(data); if current_chunk >= chunks_len { return thread_chunks; @@ -93,19 +125,17 @@ pub fn render_box_render_complex_plane_into_buffer(p: &mut PixelBuffer, c: &Comp if current_chunk % chunks_len_over_max_progress == 0 { let current_progress = atm.lock().unwrap().load(Ordering::Relaxed); print_progress_bar(current_progress, max_progress); - if current_progress < u8::MAX - { - atm.lock().unwrap().store(current_progress+1, Ordering::Relaxed); + if current_progress < u8::MAX { + atm.lock().unwrap().store(current_progress + 1, Ordering::Relaxed); } } let chunk_start = chunk_size * current_chunk; let mut chunk = buf[current_chunk].clone(); - + for (i, pixel) in chunk.iter_mut().enumerate() { let point = pixel_buffer.index_to_point(i + chunk_start); - if !render_box.contains(point) - { + if !render_box.contains(point) { continue; //Do not render Pixel points outside of the render box } let original_x: f64 = f64::from(point.0 as u32); @@ -113,12 +143,12 @@ pub fn render_box_render_complex_plane_into_buffer(p: &mut PixelBuffer, c: &Comp //Supersampling, see: https://darkeclipz.github.io/fractals/paper/Fractals%20&%20Rendering%20Techniques.html let mut colors: Vec = Vec::new(); for _ in 0..supersampling_amount { - let (random_x, random_y): (f64, f64) = rand::thread_rng().gen::<(f64,f64)>(); - let (x, y) : (f64, f64) = (original_x+random_x, original_y+random_y); + let (random_x, random_y): (f64, f64) = rand::thread_rng().gen::<(f64, f64)>(); + let (x, y): (f64, f64) = (original_x + random_x, original_y + random_y); let complex = plane.complex_from_pixel_plane(x, y); let iterations = ms.iterate(&complex); let color = coloring_function(iterations, ms.max_iterations); - colors.push(color); + colors.push(color); } let supersampled_color = TrueColor::average(&colors); *pixel = supersampled_color; @@ -131,11 +161,11 @@ pub fn render_box_render_complex_plane_into_buffer(p: &mut PixelBuffer, c: &Comp for handle in handles { let thread_chunks = handle.join().unwrap(); - for (i, chunk) in thread_chunks{ - let mut index = i*p.pixel_plane.width; + for (i, chunk) in thread_chunks { + let mut index = i * p.pixel_plane.width; for color in chunk { p.colors[index] = color; - index+=1; + index += 1; } } } @@ -144,17 +174,24 @@ pub fn render_box_render_complex_plane_into_buffer(p: &mut PixelBuffer, c: &Comp benchmark("render_box_render_complex_plane_into_buffer()", time); } -pub fn translate_and_render_complex_plane_buffer(p: &mut PixelBuffer, c: &ComplexPlane, m: &MandelbrotSet, rows: i128, columns: i128, supersampling_amount: u8,coloring_function: fn(iterations: u32, max_iterations: u32) -> TrueColor) { - println!("rows: {}, columns: {}",rows, columns); - let max_x: usize = if columns > 0 {columns as usize} else {p.pixel_plane.width-1}; - let max_y: usize = if rows > 0 {rows as usize} else {p.pixel_plane.height-1}; +pub fn translate_and_render_complex_plane_buffer( + p: &mut PixelBuffer, + c: &ComplexPlane, + m: &MandelbrotSet, + rows: i128, + columns: i128, + supersampling_amount: u8, + coloring_function: fn(iterations: u32, max_iterations: u32) -> TrueColor, +) { + println!("rows: {}, columns: {}", rows, columns); + let max_x: usize = if columns > 0 { columns as usize } else { p.pixel_plane.width - 1 }; + let max_y: usize = if rows > 0 { rows as usize } else { p.pixel_plane.height - 1 }; p.translate_buffer(rows, columns); if rows == 0 { - let render_box = RenderBox::new((max_x as i128-columns.abs()) as usize, max_x, 0, p.pixel_plane.height); + let render_box = RenderBox::new((max_x as i128 - columns.abs()) as usize, max_x, 0, p.pixel_plane.height); render_box_render_complex_plane_into_buffer(p, c, m, render_box, supersampling_amount, coloring_function); - } - else if columns == 0 { - let render_box = RenderBox::new(0, p.pixel_plane.width, (max_y as i128 -rows.abs()) as usize, max_y); + } else if columns == 0 { + let render_box = RenderBox::new(0, p.pixel_plane.width, (max_y as i128 - rows.abs()) as usize, max_y); render_box_render_complex_plane_into_buffer(p, c, m, render_box, supersampling_amount, coloring_function); } else { println!("ERROR: translate_and_render_complex_plane_buffer() requires that rows == 0 || columns == 0"); @@ -163,16 +200,45 @@ pub fn translate_and_render_complex_plane_buffer(p: &mut PixelBuffer, c: &Comple ///# Panics /// If `rows_up` != 0 && `columns_right` != 0 -pub fn translate_and_render_efficiently(c: &mut ComplexPlane, p: &mut PixelBuffer, m: &MandelbrotSet, rows_up: i16, columns_right: i16, supersampling_amount: u8,coloring_function: fn(iterations: u32, max_iterations: u32) -> TrueColor) { - assert!(rows_up == 0 || columns_right == 0, "translate_and_render_efficiently: rows_up should be 0 or columns_right should be 0!"); +pub fn translate_and_render_efficiently( + c: &mut ComplexPlane, + p: &mut PixelBuffer, + m: &MandelbrotSet, + rows_up: i16, + columns_right: i16, + supersampling_amount: u8, + coloring_function: fn(iterations: u32, max_iterations: u32) -> TrueColor, +) { + assert!( + rows_up == 0 || columns_right == 0, + "translate_and_render_efficiently: rows_up should be 0 or columns_right should be 0!" + ); - let row_sign: f64 = if rows_up > 0 {-1.0} else {1.0}; - let column_sign: f64 = if columns_right > 0 {1.0} else {-1.0}; - c.translate(column_sign*c.pixels_to_real(columns_right.unsigned_abs() as u8), row_sign*c.pixels_to_imaginary(rows_up.unsigned_abs() as u8)); - translate_and_render_complex_plane_buffer(p, c, m, rows_up.into(), (-columns_right).into(), supersampling_amount, coloring_function); + let row_sign: f64 = if rows_up > 0 { -1.0 } else { 1.0 }; + let column_sign: f64 = if columns_right > 0 { 1.0 } else { -1.0 }; + c.translate( + column_sign * c.pixels_to_real(columns_right.unsigned_abs() as u8), + row_sign * c.pixels_to_imaginary(rows_up.unsigned_abs() as u8), + ); + translate_and_render_complex_plane_buffer( + p, + c, + m, + rows_up.into(), + (-columns_right).into(), + supersampling_amount, + coloring_function, + ); } -pub fn translate_to_center_and_render_efficiently(c: &mut ComplexPlane, p: &mut PixelBuffer, m: &MandelbrotSet, new_center: &Complex, supersampling_amount: u8, coloring_function: fn(iterations: u32, max_iterations: u32) -> TrueColor) { +pub fn translate_to_center_and_render_efficiently( + c: &mut ComplexPlane, + p: &mut PixelBuffer, + m: &MandelbrotSet, + new_center: &Complex, + supersampling_amount: u8, + coloring_function: fn(iterations: u32, max_iterations: u32) -> TrueColor, +) { let mut translation: Complex = new_center.subtract(&c.center()); //Mirror the y translation because the screen y is mirrored compared to the complex plane y axis translation.y = -translation.y; @@ -194,17 +260,17 @@ fn benchmark_start() -> Instant { Instant::now() } -fn benchmark(function: &str,time: Instant) { - println!("[Benchmark] {}: {:.2?}",function, time.elapsed()); +fn benchmark(function: &str, time: Instant) { + println!("[Benchmark] {}: {:.2?}", function, time.elapsed()); } ///Prints a progress bar on the current line, will print over the contents of the cursor's current line, so make sure the function is given a newline to print over fn print_progress_bar(current_progress: u8, max_progress: u8) { print!("\rProgress: ["); //Print a \r carriage return to return the cursor to the beginning of the line: https://stackoverflow.com/questions/59890270/how-do-i-overwrite-console-output for i in 0..max_progress { - let symbol = if i <= current_progress {'+'} else {'.'}; + let symbol = if i <= current_progress { '+' } else { '.' }; print!("{}", symbol); } print!("]"); io::stdout().flush().unwrap(); -} \ No newline at end of file +} diff --git a/src/user_input.rs b/src/user_input.rs index 9f65d3b..9329b22 100644 --- a/src/user_input.rs +++ b/src/user_input.rs @@ -1,24 +1,30 @@ -use std::{str::FromStr, fmt::Display, io::{self, Write}}; +use std::{ + fmt::Display, + io::{self, Write}, + str::FromStr, +}; /// Ask the user for `result` named `name` from stdin. If `result` can be parsed to a `T`, return `result`. In any other case, /// call `ask` again. /// # Panics /// If `io::stdout().flush().unwrap()` panics pub fn ask(name: &str) -> T - where ::Err: Display { - print!("Enter the {}:", name); - io::stdout().flush().unwrap(); - - let input = get_user_input(); - - //Try to convert input to a T, or ask again - return match input.parse::() { - Ok(value) => value, - Err(err) => { - println!("\tError: {}: {}", err, input); - ask(name) - } +where + ::Err: Display, +{ + print!("Enter the {}:", name); + io::stdout().flush().unwrap(); + + let input = get_user_input(); + + //Try to convert input to a T, or ask again + return match input.parse::() { + Ok(value) => value, + Err(err) => { + println!("\tError: {}: {}", err, input); + ask(name) } + }; } ///Reads a line from stdin, does not trim the input @@ -26,8 +32,8 @@ pub fn get_user_input_untrimmed() -> String { let mut user_input = String::new(); // Read the user's input from the standard input stdin io::stdin() - .read_line(&mut user_input) - .expect("Error: Failed to read the user's input from stdin."); + .read_line(&mut user_input) + .expect("Error: Failed to read the user's input from stdin."); user_input } @@ -43,9 +49,9 @@ pub fn get_user_input() -> String { pub fn pick_option(options: &[(&str, T)]) -> T { println!("Please pick an option:"); for (i, option) in options.iter().enumerate() { - println!("\t[{}]: {}",i,option.0); + println!("\t[{}]: {}", i, option.0); } - + let mut input: usize = ask("option"); while input >= options.len() { println!("\tError: the option is out of bounds: {}", input); @@ -55,4 +61,4 @@ pub fn pick_option(options: &[(&str, T)]) -> T { let (name, value) = options[input]; println!("\tPicked: {}", name); value -} \ No newline at end of file +} diff --git a/src/view/mod.rs b/src/view/mod.rs new file mode 100644 index 0000000..e69de29 From 970436ce45557abf2c289e32c611ce75a6660315 Mon Sep 17 00:00:00 2001 From: Jort van Waes Date: Tue, 19 Sep 2023 16:24:38 +0200 Subject: [PATCH 02/14] Created the MandelbrotModel singleton --- src/lib.rs | 1 + src/model/mandelbrot_model.rs | 21 ++++++++++++++++----- src/model/mod.rs | 1 + 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 421ea86..14ed90b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,7 @@ use minifb::{Key, MouseButton, MouseMode, Window, WindowOptions}; use crate::coloring::TrueColor; use crate::complex_plane::{ComplexPlane, View}; use crate::key_bindings::KeyBindings; +use crate::model::mandelbrot_model::MandelbrotModel; use crate::pixel_buffer::PixelBuffer; use crate::pixel_plane::PixelPlane; use crate::user_input::{ask, pick_option}; diff --git a/src/model/mandelbrot_model.rs b/src/model/mandelbrot_model.rs index fc86c3f..7ae10b8 100644 --- a/src/model/mandelbrot_model.rs +++ b/src/model/mandelbrot_model.rs @@ -1,14 +1,25 @@ use lazy_static::lazy_static; -use std::sync::Mutex; +use std::sync::{Mutex, MutexGuard}; lazy_static! { - static ref SINGLETON_INSTANCE: Mutex = Mutex::new(MandelbrotModel::new()); + static ref MANDELBROT_MODEL_INSTANCE: Mutex = Mutex::new(MandelbrotModel::new()); } -struct MandelbrotModel {} +pub struct MandelbrotModel { + number: i32, +} impl MandelbrotModel { - fn new() -> MandelbrotModel { - MandelbrotModel {} + pub fn new() -> MandelbrotModel { + MandelbrotModel { number: 0 } + } + + pub fn add_one(&mut self) { + self.number += 1; + } + + /// Returns the singleton MandelbrotModel instance. + pub fn get_instance() -> MutexGuard<'static, MandelbrotModel> { + MANDELBROT_MODEL_INSTANCE.lock().unwrap() } } diff --git a/src/model/mod.rs b/src/model/mod.rs index e69de29..5c97d26 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -0,0 +1 @@ +pub mod mandelbrot_model; From 87a1ba167021cc39bced45378c19368915d7e444 Mon Sep 17 00:00:00 2001 From: Jort van Waes Date: Tue, 19 Sep 2023 16:36:46 +0200 Subject: [PATCH 03/14] Reorganized the existing code into the MVC pattern --- REQUIREMENTS.md | 2 +- benches/bench.rs | 18 ++++++--- src/{ => controller}/config.rs | 0 src/{ => controller}/key_bindings.rs | 0 src/controller/mod.rs | 3 ++ src/{ => controller}/user_input.rs | 0 src/lib.rs | 40 ++++++++----------- src/{ => model}/complex.rs | 0 src/{ => model}/complex_plane.rs | 2 +- .../mandelbrot_function.rs} | 12 +++--- src/model/mod.rs | 6 +++ src/{ => model}/pixel_buffer.rs | 14 +++---- src/{ => model}/pixel_plane.rs | 0 src/{ => model}/rendering.rs | 13 +++--- src/{ => view}/coloring.rs | 0 src/view/mod.rs | 1 + 16 files changed, 59 insertions(+), 52 deletions(-) rename src/{ => controller}/config.rs (100%) rename src/{ => controller}/key_bindings.rs (100%) rename src/{ => controller}/user_input.rs (100%) rename src/{ => model}/complex.rs (100%) rename src/{ => model}/complex_plane.rs (99%) rename src/{mandelbrot_set.rs => model/mandelbrot_function.rs} (86%) rename src/{ => model}/pixel_buffer.rs (94%) rename src/{ => model}/pixel_plane.rs (100%) rename src/{ => model}/rendering.rs (97%) rename src/{ => view}/coloring.rs (100%) diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index 5816d0f..3921ed6 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -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 diff --git a/benches/bench.rs b/benches/bench.rs index 5a0a3ee..d3248da 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -2,7 +2,14 @@ extern crate test; -use mandelbrot::{mandelbrot_set::MandelbrotSet, complex::Complex, pixel_buffer::{PixelBuffer, pixel_plane::PixelPlane}, complex_plane::ComplexPlane, coloring::TrueColor, rendering}; +use mandelbrot::{ + coloring::TrueColor, + complex::Complex, + complex_plane::ComplexPlane, + mandelbrot_function::MandelbrotFunction, + pixel_buffer::{pixel_plane::PixelPlane, PixelBuffer}, + rendering, +}; use test::Bencher; //Mandelbrot set parameters @@ -15,12 +22,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); @@ -33,11 +39,11 @@ fn bench_render_mandelbrot_set_default_view_720p_1x_ssaa(b: &mut Bencher) { //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); }) -} \ No newline at end of file +} diff --git a/src/config.rs b/src/controller/config.rs similarity index 100% rename from src/config.rs rename to src/controller/config.rs diff --git a/src/key_bindings.rs b/src/controller/key_bindings.rs similarity index 100% rename from src/key_bindings.rs rename to src/controller/key_bindings.rs diff --git a/src/controller/mod.rs b/src/controller/mod.rs index e69de29..373ace4 100644 --- a/src/controller/mod.rs +++ b/src/controller/mod.rs @@ -0,0 +1,3 @@ +pub mod config; +pub mod key_bindings; +pub mod user_input; diff --git a/src/user_input.rs b/src/controller/user_input.rs similarity index 100% rename from src/user_input.rs rename to src/controller/user_input.rs diff --git a/src/lib.rs b/src/lib.rs index 14ed90b..530f465 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,35 +26,27 @@ clippy::cast_sign_loss )] -pub mod coloring; -pub mod complex; -pub mod complex_plane; -pub mod config; pub mod controller; -pub mod key_bindings; -pub mod mandelbrot_set; pub mod model; -pub mod pixel_buffer; -pub mod pixel_plane; -pub mod rendering; -pub mod user_input; pub mod view; use std::error::Error; use std::sync::atomic::{AtomicBool, Ordering}; -use coloring::ColorChannelMapping; -pub use config::Config; -use mandelbrot_set::MandelbrotSet; use minifb::{Key, MouseButton, MouseMode, Window, WindowOptions}; -use crate::coloring::TrueColor; -use crate::complex_plane::{ComplexPlane, View}; -use crate::key_bindings::KeyBindings; -use crate::model::mandelbrot_model::MandelbrotModel; -use crate::pixel_buffer::PixelBuffer; -use crate::pixel_plane::PixelPlane; -use crate::user_input::{ask, pick_option}; +//Crate includes +pub use controller::config::Config; +use controller::key_bindings::KeyBindings; +use controller::user_input::{ask, pick_option}; +use model::complex_plane::{ComplexPlane, View}; +use model::mandelbrot_function::MandelbrotFunction; +use model::mandelbrot_model::MandelbrotModel; +use model::{pixel_buffer, pixel_plane, rendering}; +use pixel_buffer::PixelBuffer; +use pixel_plane::PixelPlane; +use view::coloring::ColorChannelMapping; +use view::coloring::TrueColor; //Coloring function type ColoringFunction = fn(iterations: u32, max_iterations: u32) -> TrueColor; @@ -141,7 +133,7 @@ fn handle_key_events( window: &Window, c: &mut ComplexPlane, p: &mut PixelBuffer, - m: &mut MandelbrotSet, + m: &mut MandelbrotFunction, vars: &mut InteractionVariables, k: &KeyBindings, supersampling_amount: &mut u8, @@ -286,7 +278,7 @@ fn handle_right_mouse_clicked( y: f32, c: &mut ComplexPlane, p: &mut PixelBuffer, - m: &MandelbrotSet, + m: &MandelbrotFunction, supersampling_amount: u8, coloring_function: ColoringFunction, ) { @@ -331,7 +323,7 @@ fn handle_mouse_events( window: &Window, c: &mut ComplexPlane, p: &mut PixelBuffer, - m: &MandelbrotSet, + m: &MandelbrotFunction, supersampling_amount: u8, coloring_function: ColoringFunction, ) { @@ -397,7 +389,7 @@ pub fn run(config: &Config) -> Result<(), Box> { // Multithreading variables let amount_of_threads = num_cpus::get(); //Amount of CPU threads to use, TODO: use this value in rendering functions // Mandelbrot set iterator - let mut m: MandelbrotSet = MandelbrotSet::new(config.max_iterations, config.orbit_radius); + let mut m: MandelbrotFunction = MandelbrotFunction::new(config.max_iterations, config.orbit_radius); //Coloring function let mut coloring_function = COLORING_FUNCTION; //Color channel mapping diff --git a/src/complex.rs b/src/model/complex.rs similarity index 100% rename from src/complex.rs rename to src/model/complex.rs diff --git a/src/complex_plane.rs b/src/model/complex_plane.rs similarity index 99% rename from src/complex_plane.rs rename to src/model/complex_plane.rs index c3a6f43..42c7152 100644 --- a/src/complex_plane.rs +++ b/src/model/complex_plane.rs @@ -1,4 +1,4 @@ -use crate::complex::Complex; +use crate::model::complex::Complex; /// # Complex plane /// In mathematics, the complex plane is the plane formed by the complex numbers, with a Cartesian coordinate system such that the x-axis, called the real axis, is formed by the real numbers, and the y-axis, called the imaginary axis, is formed by the imaginary numbers. diff --git a/src/mandelbrot_set.rs b/src/model/mandelbrot_function.rs similarity index 86% rename from src/mandelbrot_set.rs rename to src/model/mandelbrot_function.rs index b34d322..5334fe5 100644 --- a/src/mandelbrot_set.rs +++ b/src/model/mandelbrot_function.rs @@ -1,15 +1,15 @@ -use crate::complex::Complex; +use crate::model::complex::Complex; #[derive(Clone)] -pub struct MandelbrotSet { +pub struct MandelbrotFunction { pub max_iterations: u32, ///If z remains within the orbit_radius in max_iterations, we assume c does not tend to infinity pub orbit_radius: f64, } -impl MandelbrotSet { - pub fn new(max_iterations: u32, orbit_radius: f64) -> MandelbrotSet { - MandelbrotSet { +impl MandelbrotFunction { + pub fn new(max_iterations: u32, orbit_radius: f64) -> MandelbrotFunction { + MandelbrotFunction { max_iterations, orbit_radius, } @@ -34,7 +34,7 @@ impl MandelbrotSet { } } -impl std::fmt::Debug for MandelbrotSet { +impl std::fmt::Debug for MandelbrotFunction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "max_iterations = {}, orbit_radius = {}", self.max_iterations, self.orbit_radius) } diff --git a/src/model/mod.rs b/src/model/mod.rs index 5c97d26..cba7b63 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1 +1,7 @@ +pub mod complex; +pub mod complex_plane; +pub mod mandelbrot_function; pub mod mandelbrot_model; +pub mod pixel_buffer; +pub mod pixel_plane; +pub mod rendering; diff --git a/src/pixel_buffer.rs b/src/model/pixel_buffer.rs similarity index 94% rename from src/pixel_buffer.rs rename to src/model/pixel_buffer.rs index 4f6ddbd..97e1af2 100644 --- a/src/pixel_buffer.rs +++ b/src/model/pixel_buffer.rs @@ -1,11 +1,7 @@ use std::{fs::File, io::BufWriter, path::Path}; -use crate::{ - coloring::{ColorChannelMapping, TrueColor}, - complex_plane::View, - mandelbrot_set::MandelbrotSet, - pixel_plane::PixelPlane, -}; +use crate::model::{complex_plane::View, mandelbrot_function::MandelbrotFunction, pixel_plane::PixelPlane}; +use crate::view::coloring::{ColorChannelMapping, TrueColor}; #[derive(Clone)] pub struct PixelBuffer { @@ -96,7 +92,7 @@ impl PixelBuffer { ///Also stores author and application metadata /// # Panics /// If the file `saved/{file_name_without_extension}.png` cannot be created - pub fn save_as_png(&self, file_name_without_extension: &str, view: &View, m: &MandelbrotSet, supersampling_amount: u8) { + pub fn save_as_png(&self, file_name_without_extension: &str, view: &View, m: &MandelbrotFunction, supersampling_amount: u8) { let file_name_without_extension = file_name_without_extension.replace(':', "-").replace(' ', "_"); //Replace ':' with '-' for Windows file system. Replace ' ' with '_' because spaces are annoying in filenames. let file_name = format!("saved{}{}.png", std::path::MAIN_SEPARATOR_STR, file_name_without_extension); match std::fs::create_dir_all("saved") { @@ -113,7 +109,9 @@ impl PixelBuffer { let view_text = format!("{:?}", view); encoder.add_text_chunk(String::from("view"), view_text).unwrap(); let mandelbrot_set_text = format!("{:?}", m); - encoder.add_text_chunk(String::from("mandelbrot_set"), mandelbrot_set_text).unwrap(); + encoder + .add_text_chunk(String::from("mandelbrot_function"), mandelbrot_set_text) + .unwrap(); let supersampling_amount_text = format!("{}x", supersampling_amount); encoder .add_text_chunk(String::from("supersampling_amount"), supersampling_amount_text) diff --git a/src/pixel_plane.rs b/src/model/pixel_plane.rs similarity index 100% rename from src/pixel_plane.rs rename to src/model/pixel_plane.rs diff --git a/src/rendering.rs b/src/model/rendering.rs similarity index 97% rename from src/rendering.rs rename to src/model/rendering.rs index 9e299d1..4745960 100644 --- a/src/rendering.rs +++ b/src/model/rendering.rs @@ -11,7 +11,8 @@ use std::{ use rand::Rng; -use crate::{coloring::TrueColor, complex::Complex, complex_plane::ComplexPlane, mandelbrot_set::MandelbrotSet, pixel_buffer::PixelBuffer}; +use crate::model::{complex::Complex, complex_plane::ComplexPlane, mandelbrot_function::MandelbrotFunction, pixel_buffer::PixelBuffer}; +use crate::view::coloring::TrueColor; ///A box representing the area to render by rendering functions #[derive(Clone, Copy)] @@ -61,7 +62,7 @@ impl RenderBox { pub fn render_complex_plane_into_buffer( p: &mut PixelBuffer, c: &ComplexPlane, - m: &MandelbrotSet, + m: &MandelbrotFunction, supersampling_amount: u8, coloring_function: fn(iterations: u32, max_iterations: u32) -> TrueColor, ) { @@ -82,7 +83,7 @@ pub fn render_complex_plane_into_buffer( pub fn render_box_render_complex_plane_into_buffer( p: &mut PixelBuffer, c: &ComplexPlane, - m: &MandelbrotSet, + m: &MandelbrotFunction, render_box: RenderBox, supersampling_amount: u8, coloring_function: fn(iterations: u32, max_iterations: u32) -> TrueColor, @@ -177,7 +178,7 @@ pub fn render_box_render_complex_plane_into_buffer( pub fn translate_and_render_complex_plane_buffer( p: &mut PixelBuffer, c: &ComplexPlane, - m: &MandelbrotSet, + m: &MandelbrotFunction, rows: i128, columns: i128, supersampling_amount: u8, @@ -203,7 +204,7 @@ pub fn translate_and_render_complex_plane_buffer( pub fn translate_and_render_efficiently( c: &mut ComplexPlane, p: &mut PixelBuffer, - m: &MandelbrotSet, + m: &MandelbrotFunction, rows_up: i16, columns_right: i16, supersampling_amount: u8, @@ -234,7 +235,7 @@ pub fn translate_and_render_efficiently( pub fn translate_to_center_and_render_efficiently( c: &mut ComplexPlane, p: &mut PixelBuffer, - m: &MandelbrotSet, + m: &MandelbrotFunction, new_center: &Complex, supersampling_amount: u8, coloring_function: fn(iterations: u32, max_iterations: u32) -> TrueColor, diff --git a/src/coloring.rs b/src/view/coloring.rs similarity index 100% rename from src/coloring.rs rename to src/view/coloring.rs diff --git a/src/view/mod.rs b/src/view/mod.rs index e69de29..e045169 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -0,0 +1 @@ +pub mod coloring; From 5265b9986e537ddccc3a9c714fc694ccb86b6786 Mon Sep 17 00:00:00 2001 From: Jort van Waes Date: Tue, 19 Sep 2023 16:46:19 +0200 Subject: [PATCH 04/14] Stored Config struct in MandelbrotModel singleton --- benches/bench.rs | 8 ++------ src/controller/config.rs | 12 ++++++++++++ src/lib.rs | 2 +- src/main.rs | 6 +++--- src/model/mandelbrot_model.rs | 10 ++++------ 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index d3248da..e142d66 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -3,12 +3,8 @@ extern crate test; use mandelbrot::{ - coloring::TrueColor, - complex::Complex, - complex_plane::ComplexPlane, - mandelbrot_function::MandelbrotFunction, - pixel_buffer::{pixel_plane::PixelPlane, PixelBuffer}, - rendering, + 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; diff --git a/src/controller/config.rs b/src/controller/config.rs index 23576af..5167925 100644 --- a/src/controller/config.rs +++ b/src/controller/config.rs @@ -28,6 +28,18 @@ pub struct Config { } 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 diff --git a/src/lib.rs b/src/lib.rs index 530f465..ea82860 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,7 +41,7 @@ use controller::key_bindings::KeyBindings; use controller::user_input::{ask, pick_option}; use model::complex_plane::{ComplexPlane, View}; use model::mandelbrot_function::MandelbrotFunction; -use model::mandelbrot_model::MandelbrotModel; +pub use model::mandelbrot_model::MandelbrotModel; use model::{pixel_buffer, pixel_plane, rendering}; use pixel_buffer::PixelBuffer; use pixel_plane::PixelPlane; diff --git a/src/main.rs b/src/main.rs index e598066..9dc3515 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,15 @@ use std::env; use std::process; -use mandelbrot::{self, Config}; +use mandelbrot::{self, Config, MandelbrotModel}; fn main() { - let config = Config::build(env::args()).unwrap_or_else(|err| { + MandelbrotModel::get_instance().config = Config::build(env::args()).unwrap_or_else(|err| { eprintln!("Problem parsing arguments: {}", err); process::exit(1); }); - if let Err(err) = mandelbrot::run(&config) { + if let Err(err) = mandelbrot::run(&MandelbrotModel::get_instance().config) { eprintln!("Application error: {}", err); process::exit(1); } diff --git a/src/model/mandelbrot_model.rs b/src/model/mandelbrot_model.rs index 7ae10b8..8436c40 100644 --- a/src/model/mandelbrot_model.rs +++ b/src/model/mandelbrot_model.rs @@ -1,21 +1,19 @@ use lazy_static::lazy_static; use std::sync::{Mutex, MutexGuard}; +use crate::Config; + lazy_static! { static ref MANDELBROT_MODEL_INSTANCE: Mutex = Mutex::new(MandelbrotModel::new()); } pub struct MandelbrotModel { - number: i32, + pub config: Config, } impl MandelbrotModel { pub fn new() -> MandelbrotModel { - MandelbrotModel { number: 0 } - } - - pub fn add_one(&mut self) { - self.number += 1; + MandelbrotModel { config: Config::new() } } /// Returns the singleton MandelbrotModel instance. From 0379adc5270dd380f252eecaa2b5452922e7f21b Mon Sep 17 00:00:00 2001 From: Jort van Waes Date: Tue, 19 Sep 2023 17:05:53 +0200 Subject: [PATCH 05/14] This version does not work, singleton is incorrect --- src/controller/config.rs | 1 + src/lib.rs | 157 +++++++++++++++++++++------------- src/main.rs | 7 +- src/model/mandelbrot_model.rs | 27 +++++- 4 files changed, 122 insertions(+), 70 deletions(-) diff --git a/src/controller/config.rs b/src/controller/config.rs index 5167925..ed64adf 100644 --- a/src/controller/config.rs +++ b/src/controller/config.rs @@ -11,6 +11,7 @@ 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, diff --git a/src/lib.rs b/src/lib.rs index ea82860..f6e8eb3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -149,7 +149,7 @@ fn handle_key_events( c, p, m, - vars.translation_amount.into(), + MandelbrotModel::get_instance().vars.translation_amount.into(), 0, *supersampling_amount, *coloring_function, @@ -158,7 +158,7 @@ fn handle_key_events( c, p, m, - -i16::from(vars.translation_amount), + -i16::from(MandelbrotModel::get_instance().vars.translation_amount), 0, *supersampling_amount, *coloring_function, @@ -168,7 +168,7 @@ fn handle_key_events( p, m, 0, - -i16::from(vars.translation_amount), + -i16::from(MandelbrotModel::get_instance().vars.translation_amount), *supersampling_amount, *coloring_function, ), @@ -177,51 +177,71 @@ fn handle_key_events( p, m, 0, - vars.translation_amount.into(), + MandelbrotModel::get_instance().vars.translation_amount.into(), *supersampling_amount, *coloring_function, ), - Key::R => c.reset(), - Key::NumPadPlus => vars.increment_translation_amount(), - Key::NumPadMinus => vars.decrement_translation_amount(), - Key::NumPadAsterisk => vars.increment_scale_numerator(), - Key::NumPadSlash => vars.decrement_scale_numerator(), - Key::LeftBracket => c.scale(vars.scaling_factor()), - Key::RightBracket => c.scale(vars.inverse_scaling_factor()), - Key::V => println!("Center: {:?}, scale: {:?}", c.center(), c.get_scale()), - Key::Key1 => c.set_view(&VIEW_1), - Key::Key2 => c.set_view(&VIEW_2), - Key::Key3 => c.set_view(&VIEW_3), - Key::Key4 => c.set_view(&VIEW_4), - Key::Key5 => c.set_view(&VIEW_5), - Key::Key6 => c.set_view(&VIEW_6), - Key::Key7 => c.set_view(&VIEW_7), - Key::Key8 => c.set_view(&VIEW_8), - Key::Key9 => c.set_view(&VIEW_9), - Key::Key0 => c.set_view(&VIEW_0), + Key::R => MandelbrotModel::get_instance().c.reset(), + Key::NumPadPlus => MandelbrotModel::get_instance().vars.increment_translation_amount(), + Key::NumPadMinus => MandelbrotModel::get_instance().vars.decrement_translation_amount(), + Key::NumPadAsterisk => MandelbrotModel::get_instance().vars.increment_scale_numerator(), + Key::NumPadSlash => MandelbrotModel::get_instance().vars.decrement_scale_numerator(), + Key::LeftBracket => MandelbrotModel::get_instance() + .c + .scale(MandelbrotModel::get_instance().vars.scaling_factor()), + Key::RightBracket => MandelbrotModel::get_instance() + .c + .scale(MandelbrotModel::get_instance().vars.inverse_scaling_factor()), + Key::V => println!( + "Center: {:?}, scale: {:?}", + MandelbrotModel::get_instance().c.center(), + MandelbrotModel::get_instance().c.get_scale() + ), + Key::Key1 => MandelbrotModel::get_instance().c.set_view(&VIEW_1), + Key::Key2 => MandelbrotModel::get_instance().c.set_view(&VIEW_2), + Key::Key3 => MandelbrotModel::get_instance().c.set_view(&VIEW_3), + Key::Key4 => MandelbrotModel::get_instance().c.set_view(&VIEW_4), + Key::Key5 => MandelbrotModel::get_instance().c.set_view(&VIEW_5), + Key::Key6 => MandelbrotModel::get_instance().c.set_view(&VIEW_6), + Key::Key7 => MandelbrotModel::get_instance().c.set_view(&VIEW_7), + Key::Key8 => MandelbrotModel::get_instance().c.set_view(&VIEW_8), + Key::Key9 => MandelbrotModel::get_instance().c.set_view(&VIEW_9), + Key::Key0 => MandelbrotModel::get_instance().c.set_view(&VIEW_0), Key::K => k.print(), Key::S => { let time_stamp = chrono::Utc::now().to_string(); if config.window_scale == 1.0 { - p.save_as_png(&time_stamp, &c.get_view(), m, *image_supersampling_amount); + MandelbrotModel::get_instance().p.save_as_png( + &time_stamp, + &MandelbrotModel::get_instance().c.get_view(), + m, + *image_supersampling_amount, + ); } else { let mut image_p: PixelBuffer = PixelBuffer::new(PixelPlane::new(config.image_width, config.image_height)); let mut image_c: ComplexPlane = ComplexPlane::new(config.image_width, config.image_height); - image_p.color_channel_mapping = p.color_channel_mapping; - image_c.set_view(&c.get_view()); + image_p.color_channel_mapping = MandelbrotModel::get_instance().p.color_channel_mapping; + image_c.set_view(&MandelbrotModel::get_instance().c.get_view()); rendering::render_complex_plane_into_buffer(&mut image_p, &image_c, m, *image_supersampling_amount, *coloring_function); - image_p.save_as_png(&time_stamp, &c.get_view(), m, *image_supersampling_amount); + image_p.save_as_png( + &time_stamp, + &MandelbrotModel::get_instance().c.get_view(), + m, + *image_supersampling_amount, + ); } } - Key::I => c.set_view(&View::new(ask("x"), ask("y"), ask("scale"))), + Key::I => MandelbrotModel::get_instance() + .c + .set_view(&View::new(ask("x"), ask("y"), ask("scale"))), Key::A => { *coloring_function = pick_option(&[ ("HSV", TrueColor::new_from_hsv_colors), ("Bernstein polynomials", TrueColor::new_from_bernstein_polynomials), ]) } - Key::M => m.max_iterations = ask("max_iterations"), - Key::O => p.color_channel_mapping = ask("color_channel_mapping"), + Key::M => MandelbrotModel::get_instance().m.max_iterations = ask("max_iterations"), + Key::O => MandelbrotModel::get_instance().p.color_channel_mapping = ask("color_channel_mapping"), Key::Q => { *supersampling_amount = ask::("supersampling_amount").clamp(1, 64); *image_supersampling_amount = *supersampling_amount; @@ -231,9 +251,15 @@ fn handle_key_events( _ => (), } match key { - Key::NumPadPlus | Key::NumPadMinus => println!("translation_amount: {}", vars.translation_amount), - Key::NumPadSlash | Key::NumPadAsterisk => println!("scale factor: {}/{}", vars.scale_numerator, vars.scale_denominator), - Key::Up | Key::Down | Key::Left | Key::Right => c.print(), + Key::NumPadPlus | Key::NumPadMinus => { + println!("translation_amount: {}", MandelbrotModel::get_instance().vars.translation_amount) + } + Key::NumPadSlash | Key::NumPadAsterisk => println!( + "scale factor: {}/{}", + MandelbrotModel::get_instance().vars.scale_numerator, + MandelbrotModel::get_instance().vars.scale_denominator + ), + Key::Up | Key::Down | Key::Left | Key::Right => MandelbrotModel::get_instance().c.print(), Key::R | Key::Key1 | Key::Key2 @@ -253,7 +279,7 @@ fn handle_key_events( | Key::O | Key::Q => { rendering::render_complex_plane_into_buffer(p, c, m, *supersampling_amount, *coloring_function); - c.print(); + MandelbrotModel::get_instance().c.print(); } _ => (), } @@ -266,8 +292,8 @@ fn was_clicked(current: bool, previous: bool) -> bool { fn handle_left_mouse_clicked(x: f32, y: f32, c: &ComplexPlane) { println!("\nMouseButton::Left -> Info at ({x}, {y})"); - //let iterations = p.iterations_at_point(x as usize, y as usize, m.max_iterations); //TODO: fix this - let complex = c.complex_from_pixel_plane(x.into(), y.into()); + //let iterations = MandelbrotModel::get_instance().p.iterations_at_point(x as usize, y as usize, MandelbrotModel::get_instance().m.max_iterations); //TODO: fix this + let complex = MandelbrotModel::get_instance().c.complex_from_pixel_plane(x.into(), y.into()); println!("Complex: {:?}", complex); //println!("iterations: {}", iterations); println!(); @@ -283,12 +309,15 @@ fn handle_right_mouse_clicked( coloring_function: ColoringFunction, ) { println!("\nMouseButton::Right -> Move to ({x}, {y})"); - let new_center = c.complex_from_pixel_plane(x.into(), y.into()); - println!("c.center: {:?}", c.center()); + let new_center = MandelbrotModel::get_instance().c.complex_from_pixel_plane(x.into(), y.into()); + println!( + "MandelbrotModel::get_instance().c.center: {:?}", + MandelbrotModel::get_instance().c.center() + ); println!("new_center: {:?}", new_center); rendering::translate_to_center_and_render_efficiently(c, p, m, &new_center, supersampling_amount, coloring_function); - c.print(); + MandelbrotModel::get_instance().c.print(); println!(); } @@ -346,7 +375,7 @@ fn handle_mouse_events( ///Prints Mandelbrot ASCII art :)
///Prints the `application_banner`, `author_banner`, and `version` fn print_banner() { - //Made using: https://patorjk.com/software/taag/#p=display&f=Big&t=Mandelbrot + //Made using: https://patorjk.com/software/taag/#MandelbrotModel::get_instance().p=display&f=Big&t=Mandelbrot let application_banner = r" __ __ _ _ _ _ | \/ | | | | | | | | @@ -354,7 +383,7 @@ __ __ _ _ _ _ | |\/| |/ _` | '_ \ / _` |/ _ \ | '_ \| '__/ _ \| __| | | | | (_| | | | | (_| | __/ | |_) | | | (_) | |_ |_| |_|\__,_|_| |_|\__,_|\___|_|_.__/|_| \___/ \__|"; - //Made using: https://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=by%20Jort + //Made using: https://patorjk.com/software/taag/#MandelbrotModel::get_instance().p=display&f=Small%20Slant&t=by%20Jort let author_banner = r" __ __ __ / / __ __ __ / /__ ____/ /_ @@ -380,20 +409,10 @@ fn print_command_info() { /// # Errors /// Currently does not return any Errors pub fn run(config: &Config) -> Result<(), Box> { - // Complex plane dimensions and increments - let mut c = ComplexPlane::new(config.window_width, config.window_height); - // Pixel plane and buffer - let mut p = PixelBuffer::new(PixelPlane::new(config.window_width, config.window_height)); - // User interaction variables - let mut vars = InteractionVariables::default(); - // Multithreading variables - let amount_of_threads = num_cpus::get(); //Amount of CPU threads to use, TODO: use this value in rendering functions - // Mandelbrot set iterator - let mut m: MandelbrotFunction = MandelbrotFunction::new(config.max_iterations, config.orbit_radius); //Coloring function let mut coloring_function = COLORING_FUNCTION; //Color channel mapping - p.color_channel_mapping = COLOR_CHANNEL_MAPPING; + //MandelbrotModel::get_instance().p.color_channel_mapping = COLOR_CHANNEL_MAPPING; //SSAA multiplier let mut supersampling_amount = config.supersampling_amount; //Image SSAA multiplier @@ -478,33 +497,42 @@ pub fn run(config: &Config) -> Result<(), Box> { key_bindings.add(Key::C, "Prints the configuration variables", empty_closure); key_bindings.print(); - p.pixel_plane.print(); - c.print(); + MandelbrotModel::get_instance().p.pixel_plane.print(); + MandelbrotModel::get_instance().c.print(); println!( "Mandelbrot set parameters: max. iterations is {} and orbit radius is {}", config.max_iterations, config.orbit_radius ); - println!("Amount of CPU threads that will be used for rendering: {}", amount_of_threads); + println!( + "Amount of CPU threads that will be used for rendering: {}", + MandelbrotModel::get_instance().amount_of_threads + ); println!("Supersampling amount used for rendering: {}x", supersampling_amount); println!(); println!("Rendering Mandelbrot set default view"); - rendering::render_complex_plane_into_buffer(&mut p, &c, &m, supersampling_amount, coloring_function); + rendering::render_complex_plane_into_buffer( + &mut MandelbrotModel::get_instance().p, + &MandelbrotModel::get_instance().c, + &MandelbrotModel::get_instance().m, + supersampling_amount, + coloring_function, + ); // Main loop while window.is_open() && !window.is_key_down(Key::Escape) { // Update the window with the new buffer window - .update_with_buffer(&p.pixels, config.window_width, config.window_height) + .update_with_buffer(&MandelbrotModel::get_instance().p.pixels, config.window_width, config.window_height) .unwrap(); // Handle any window events handle_key_events( &window, - &mut c, - &mut p, - &mut m, - &mut vars, + &mut MandelbrotModel::get_instance().c, + &mut MandelbrotModel::get_instance().p, + &mut MandelbrotModel::get_instance().m, + &mut MandelbrotModel::get_instance().vars, &key_bindings, &mut supersampling_amount, &mut image_supersampling_amount, @@ -513,7 +541,14 @@ pub fn run(config: &Config) -> Result<(), Box> { ); //Handle any mouse events - handle_mouse_events(&window, &mut c, &mut p, &m, supersampling_amount, coloring_function); + handle_mouse_events( + &window, + &mut MandelbrotModel::get_instance().c, + &mut MandelbrotModel::get_instance().p, + &MandelbrotModel::get_instance().m, + supersampling_amount, + coloring_function, + ); } Ok(()) diff --git a/src/main.rs b/src/main.rs index 9dc3515..a7e97d1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,9 @@ use std::env; use std::process; -use mandelbrot::{self, Config, MandelbrotModel}; +use mandelbrot::{self, MandelbrotModel}; fn main() { - MandelbrotModel::get_instance().config = Config::build(env::args()).unwrap_or_else(|err| { - eprintln!("Problem parsing arguments: {}", err); - process::exit(1); - }); - if let Err(err) = mandelbrot::run(&MandelbrotModel::get_instance().config) { eprintln!("Application error: {}", err); process::exit(1); diff --git a/src/model/mandelbrot_model.rs b/src/model/mandelbrot_model.rs index 8436c40..fe4d5ab 100644 --- a/src/model/mandelbrot_model.rs +++ b/src/model/mandelbrot_model.rs @@ -1,7 +1,12 @@ use lazy_static::lazy_static; -use std::sync::{Mutex, MutexGuard}; +use std::{ + env, process, + sync::{Mutex, MutexGuard}, +}; -use crate::Config; +use crate::{Config, InteractionVariables}; + +use super::{complex_plane::ComplexPlane, mandelbrot_function::MandelbrotFunction, pixel_buffer::PixelBuffer, pixel_plane::PixelPlane}; lazy_static! { static ref MANDELBROT_MODEL_INSTANCE: Mutex = Mutex::new(MandelbrotModel::new()); @@ -9,11 +14,27 @@ lazy_static! { pub struct MandelbrotModel { pub config: Config, + pub c: ComplexPlane, + pub p: PixelBuffer, + pub vars: InteractionVariables, + pub amount_of_threads: usize, + pub m: MandelbrotFunction, } impl MandelbrotModel { pub fn new() -> MandelbrotModel { - MandelbrotModel { config: Config::new() } + let config = Config::build(env::args()).unwrap_or_else(|err| { + eprintln!("Problem parsing arguments: {}", err); + process::exit(1); + }); + MandelbrotModel { + config: config.clone(), + c: ComplexPlane::new(config.window_width, config.window_height), + p: PixelBuffer::new(PixelPlane::new(config.window_width, config.window_height)), + vars: InteractionVariables::default(), + amount_of_threads: num_cpus::get(), + m: MandelbrotFunction::new(config.max_iterations, config.orbit_radius), + } } /// Returns the singleton MandelbrotModel instance. From 73cf56b7430d0e6dd015c486e9083a1715b0aae0 Mon Sep 17 00:00:00 2001 From: Jort van Waes Date: Tue, 19 Sep 2023 22:41:37 +0200 Subject: [PATCH 06/14] Working MandelbrotModel singleton version --- src/lib.rs | 277 ++++++++++++++++------------------------- src/main.rs | 3 +- src/model/rendering.rs | 111 ++++++++--------- 3 files changed, 161 insertions(+), 230 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f6e8eb3..68a02fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,7 +42,7 @@ use controller::user_input::{ask, pick_option}; use model::complex_plane::{ComplexPlane, View}; use model::mandelbrot_function::MandelbrotFunction; pub use model::mandelbrot_model::MandelbrotModel; -use model::{pixel_buffer, pixel_plane, rendering}; +use model::{mandelbrot_model, pixel_buffer, pixel_plane, rendering}; use pixel_buffer::PixelBuffer; use pixel_plane::PixelPlane; use view::coloring::ColorChannelMapping; @@ -129,137 +129,108 @@ impl Default for InteractionVariables { } // Handle any key events -fn handle_key_events( - window: &Window, - c: &mut ComplexPlane, - p: &mut PixelBuffer, - m: &mut MandelbrotFunction, - vars: &mut InteractionVariables, - k: &KeyBindings, - supersampling_amount: &mut u8, - image_supersampling_amount: &mut u8, - coloring_function: &mut ColoringFunction, - config: &Config, -) { +fn handle_key_events(window: &Window, k: &KeyBindings, coloring_function: &mut ColoringFunction) { + let mut mandelbrot_model = MandelbrotModel::get_instance(); if let Some(key) = window.get_keys_pressed(minifb::KeyRepeat::No).first() { print!("\nKey pressed: "); k.print_key(key); match key { - Key::Up => rendering::translate_and_render_efficiently( - c, - p, - m, - MandelbrotModel::get_instance().vars.translation_amount.into(), - 0, - *supersampling_amount, - *coloring_function, - ), - Key::Down => rendering::translate_and_render_efficiently( - c, - p, - m, - -i16::from(MandelbrotModel::get_instance().vars.translation_amount), - 0, - *supersampling_amount, - *coloring_function, - ), - Key::Left => rendering::translate_and_render_efficiently( - c, - p, - m, - 0, - -i16::from(MandelbrotModel::get_instance().vars.translation_amount), - *supersampling_amount, - *coloring_function, - ), - Key::Right => rendering::translate_and_render_efficiently( - c, - p, - m, - 0, - MandelbrotModel::get_instance().vars.translation_amount.into(), - *supersampling_amount, - *coloring_function, - ), - Key::R => MandelbrotModel::get_instance().c.reset(), - Key::NumPadPlus => MandelbrotModel::get_instance().vars.increment_translation_amount(), - Key::NumPadMinus => MandelbrotModel::get_instance().vars.decrement_translation_amount(), - Key::NumPadAsterisk => MandelbrotModel::get_instance().vars.increment_scale_numerator(), - Key::NumPadSlash => MandelbrotModel::get_instance().vars.decrement_scale_numerator(), - Key::LeftBracket => MandelbrotModel::get_instance() - .c - .scale(MandelbrotModel::get_instance().vars.scaling_factor()), - Key::RightBracket => MandelbrotModel::get_instance() - .c - .scale(MandelbrotModel::get_instance().vars.inverse_scaling_factor()), + Key::Up => { + let rows = mandelbrot_model.vars.translation_amount; + rendering::translate_and_render_efficiently(&mut mandelbrot_model, rows.into(), 0, *coloring_function) + } + Key::Down => { + let rows = -i16::from(mandelbrot_model.vars.translation_amount); + rendering::translate_and_render_efficiently(&mut mandelbrot_model, rows, 0, *coloring_function) + } + Key::Left => { + let columns = -i16::from(mandelbrot_model.vars.translation_amount); + rendering::translate_and_render_efficiently(&mut mandelbrot_model, 0, columns, *coloring_function) + } + Key::Right => { + let columns = mandelbrot_model.vars.translation_amount.into(); + rendering::translate_and_render_efficiently(&mut mandelbrot_model, 0, columns, *coloring_function) + } + Key::R => mandelbrot_model.c.reset(), + Key::NumPadPlus => mandelbrot_model.vars.increment_translation_amount(), + Key::NumPadMinus => mandelbrot_model.vars.decrement_translation_amount(), + Key::NumPadAsterisk => mandelbrot_model.vars.increment_scale_numerator(), + Key::NumPadSlash => mandelbrot_model.vars.decrement_scale_numerator(), + Key::LeftBracket => { + let scaling_factor = mandelbrot_model.vars.scaling_factor(); + mandelbrot_model.c.scale(scaling_factor) + } + Key::RightBracket => { + let inverse_scaling_factor = mandelbrot_model.vars.inverse_scaling_factor(); + mandelbrot_model.c.scale(inverse_scaling_factor) + } Key::V => println!( "Center: {:?}, scale: {:?}", - MandelbrotModel::get_instance().c.center(), - MandelbrotModel::get_instance().c.get_scale() + mandelbrot_model.c.center(), + mandelbrot_model.c.get_scale() ), - Key::Key1 => MandelbrotModel::get_instance().c.set_view(&VIEW_1), - Key::Key2 => MandelbrotModel::get_instance().c.set_view(&VIEW_2), - Key::Key3 => MandelbrotModel::get_instance().c.set_view(&VIEW_3), - Key::Key4 => MandelbrotModel::get_instance().c.set_view(&VIEW_4), - Key::Key5 => MandelbrotModel::get_instance().c.set_view(&VIEW_5), - Key::Key6 => MandelbrotModel::get_instance().c.set_view(&VIEW_6), - Key::Key7 => MandelbrotModel::get_instance().c.set_view(&VIEW_7), - Key::Key8 => MandelbrotModel::get_instance().c.set_view(&VIEW_8), - Key::Key9 => MandelbrotModel::get_instance().c.set_view(&VIEW_9), - Key::Key0 => MandelbrotModel::get_instance().c.set_view(&VIEW_0), + Key::Key1 => mandelbrot_model.c.set_view(&VIEW_1), + Key::Key2 => mandelbrot_model.c.set_view(&VIEW_2), + Key::Key3 => mandelbrot_model.c.set_view(&VIEW_3), + Key::Key4 => mandelbrot_model.c.set_view(&VIEW_4), + Key::Key5 => mandelbrot_model.c.set_view(&VIEW_5), + Key::Key6 => mandelbrot_model.c.set_view(&VIEW_6), + Key::Key7 => mandelbrot_model.c.set_view(&VIEW_7), + Key::Key8 => mandelbrot_model.c.set_view(&VIEW_8), + Key::Key9 => mandelbrot_model.c.set_view(&VIEW_9), + Key::Key0 => mandelbrot_model.c.set_view(&VIEW_0), Key::K => k.print(), Key::S => { let time_stamp = chrono::Utc::now().to_string(); - if config.window_scale == 1.0 { - MandelbrotModel::get_instance().p.save_as_png( + if mandelbrot_model.config.window_scale == 1.0 { + mandelbrot_model.p.save_as_png( &time_stamp, - &MandelbrotModel::get_instance().c.get_view(), - m, - *image_supersampling_amount, + &mandelbrot_model.c.get_view(), + &mandelbrot_model.m, + mandelbrot_model.config.supersampling_amount, ); } else { - let mut image_p: PixelBuffer = PixelBuffer::new(PixelPlane::new(config.image_width, config.image_height)); - let mut image_c: ComplexPlane = ComplexPlane::new(config.image_width, config.image_height); - image_p.color_channel_mapping = MandelbrotModel::get_instance().p.color_channel_mapping; - image_c.set_view(&MandelbrotModel::get_instance().c.get_view()); - rendering::render_complex_plane_into_buffer(&mut image_p, &image_c, m, *image_supersampling_amount, *coloring_function); + let mut image_p: PixelBuffer = PixelBuffer::new(PixelPlane::new( + mandelbrot_model.config.image_width, + mandelbrot_model.config.image_height, + )); + let mut image_c: ComplexPlane = + ComplexPlane::new(mandelbrot_model.config.image_width, mandelbrot_model.config.image_height); + image_p.color_channel_mapping = mandelbrot_model.p.color_channel_mapping; + image_c.set_view(&mandelbrot_model.c.get_view()); + rendering::render_complex_plane_into_buffer(&mut mandelbrot_model, *coloring_function); image_p.save_as_png( &time_stamp, - &MandelbrotModel::get_instance().c.get_view(), - m, - *image_supersampling_amount, + &mandelbrot_model.c.get_view(), + &mandelbrot_model.m, + mandelbrot_model.config.supersampling_amount, ); } } - Key::I => MandelbrotModel::get_instance() - .c - .set_view(&View::new(ask("x"), ask("y"), ask("scale"))), + Key::I => mandelbrot_model.c.set_view(&View::new(ask("x"), ask("y"), ask("scale"))), Key::A => { *coloring_function = pick_option(&[ ("HSV", TrueColor::new_from_hsv_colors), ("Bernstein polynomials", TrueColor::new_from_bernstein_polynomials), ]) } - Key::M => MandelbrotModel::get_instance().m.max_iterations = ask("max_iterations"), - Key::O => MandelbrotModel::get_instance().p.color_channel_mapping = ask("color_channel_mapping"), + Key::M => mandelbrot_model.m.max_iterations = ask("max_iterations"), + Key::O => mandelbrot_model.p.color_channel_mapping = ask("color_channel_mapping"), Key::Q => { - *supersampling_amount = ask::("supersampling_amount").clamp(1, 64); - *image_supersampling_amount = *supersampling_amount; + mandelbrot_model.config.supersampling_amount = ask::("supersampling_amount").clamp(1, 64); } - Key::X => *image_supersampling_amount = ask::("image_supersampling_amount").clamp(1, 64), - Key::C => println!("{:?}", config), + Key::C => println!("{:?}", mandelbrot_model.config), _ => (), } match key { Key::NumPadPlus | Key::NumPadMinus => { - println!("translation_amount: {}", MandelbrotModel::get_instance().vars.translation_amount) + println!("translation_amount: {}", mandelbrot_model.vars.translation_amount) } Key::NumPadSlash | Key::NumPadAsterisk => println!( "scale factor: {}/{}", - MandelbrotModel::get_instance().vars.scale_numerator, - MandelbrotModel::get_instance().vars.scale_denominator + mandelbrot_model.vars.scale_numerator, mandelbrot_model.vars.scale_denominator ), - Key::Up | Key::Down | Key::Left | Key::Right => MandelbrotModel::get_instance().c.print(), + Key::Up | Key::Down | Key::Left | Key::Right => mandelbrot_model.c.print(), Key::R | Key::Key1 | Key::Key2 @@ -278,8 +249,8 @@ fn handle_key_events( | Key::M | Key::O | Key::Q => { - rendering::render_complex_plane_into_buffer(p, c, m, *supersampling_amount, *coloring_function); - MandelbrotModel::get_instance().c.print(); + rendering::render_complex_plane_into_buffer(&mut mandelbrot_model, *coloring_function); + mandelbrot_model.c.print(); } _ => (), } @@ -299,25 +270,15 @@ fn handle_left_mouse_clicked(x: f32, y: f32, c: &ComplexPlane) { println!(); } -fn handle_right_mouse_clicked( - x: f32, - y: f32, - c: &mut ComplexPlane, - p: &mut PixelBuffer, - m: &MandelbrotFunction, - supersampling_amount: u8, - coloring_function: ColoringFunction, -) { +fn handle_right_mouse_clicked(x: f32, y: f32, coloring_function: ColoringFunction) { + let mut mandelbrot_model = MandelbrotModel::get_instance(); println!("\nMouseButton::Right -> Move to ({x}, {y})"); - let new_center = MandelbrotModel::get_instance().c.complex_from_pixel_plane(x.into(), y.into()); - println!( - "MandelbrotModel::get_instance().c.center: {:?}", - MandelbrotModel::get_instance().c.center() - ); + let new_center = mandelbrot_model.c.complex_from_pixel_plane(x.into(), y.into()); + println!("mandelbrot_model.c.center: {:?}", mandelbrot_model.c.center()); println!("new_center: {:?}", new_center); - rendering::translate_to_center_and_render_efficiently(c, p, m, &new_center, supersampling_amount, coloring_function); - MandelbrotModel::get_instance().c.print(); + rendering::translate_to_center_and_render_efficiently(&mut mandelbrot_model, &new_center, coloring_function); + mandelbrot_model.c.print(); println!(); } @@ -348,26 +309,20 @@ impl MouseClickRecorder { } } -fn handle_mouse_events( - window: &Window, - c: &mut ComplexPlane, - p: &mut PixelBuffer, - m: &MandelbrotFunction, - supersampling_amount: u8, - coloring_function: ColoringFunction, -) { +fn handle_mouse_events(window: &Window, coloring_function: ColoringFunction) { + let mandelbrot_model = MandelbrotModel::get_instance(); static LEFT_MOUSE_RECORDER: MouseClickRecorder = MouseClickRecorder::new(MouseButton::Left); //Static variable with interior mutability to toggle mouse clicks; without such a variable, clicking the screen once would result in multiple actions static RIGHT_MOUSE_RECORDER: MouseClickRecorder = MouseClickRecorder::new(MouseButton::Right); if let Some((x, y)) = window.get_mouse_pos(MouseMode::Discard) { //Left mouse actions if LEFT_MOUSE_RECORDER.was_clicked(window) { - handle_left_mouse_clicked(x, y, c); + handle_left_mouse_clicked(x, y, &mandelbrot_model.c); } //Right mouse actions if RIGHT_MOUSE_RECORDER.was_clicked(window) { - handle_right_mouse_clicked(x, y, c, p, m, supersampling_amount, coloring_function); + handle_right_mouse_clicked(x, y, coloring_function); } } } @@ -408,20 +363,17 @@ fn print_command_info() { /// Will panic if minifb cannot open a Window /// # Errors /// Currently does not return any Errors -pub fn run(config: &Config) -> Result<(), Box> { +pub fn run() -> Result<(), Box> { + let mut mandelbrot_model = MandelbrotModel::get_instance(); //Coloring function let mut coloring_function = COLORING_FUNCTION; //Color channel mapping //MandelbrotModel::get_instance().p.color_channel_mapping = COLOR_CHANNEL_MAPPING; - //SSAA multiplier - let mut supersampling_amount = config.supersampling_amount; - //Image SSAA multiplier - let mut image_supersampling_amount = supersampling_amount; // Create a new window let mut window = Window::new( "Mandelbrot set viewer", - config.window_width, - config.window_height, + mandelbrot_model.config.window_width, + mandelbrot_model.config.window_height, WindowOptions::default(), ) .unwrap_or_else(|e| { @@ -497,58 +449,43 @@ pub fn run(config: &Config) -> Result<(), Box> { key_bindings.add(Key::C, "Prints the configuration variables", empty_closure); key_bindings.print(); - MandelbrotModel::get_instance().p.pixel_plane.print(); - MandelbrotModel::get_instance().c.print(); + mandelbrot_model.p.pixel_plane.print(); + mandelbrot_model.c.print(); println!( "Mandelbrot set parameters: max. iterations is {} and orbit radius is {}", - config.max_iterations, config.orbit_radius + mandelbrot_model.config.max_iterations, mandelbrot_model.config.orbit_radius ); println!( "Amount of CPU threads that will be used for rendering: {}", - MandelbrotModel::get_instance().amount_of_threads + mandelbrot_model.amount_of_threads + ); + println!( + "Supersampling amount used for rendering: {}x", + mandelbrot_model.config.supersampling_amount ); - println!("Supersampling amount used for rendering: {}x", supersampling_amount); println!(); println!("Rendering Mandelbrot set default view"); - rendering::render_complex_plane_into_buffer( - &mut MandelbrotModel::get_instance().p, - &MandelbrotModel::get_instance().c, - &MandelbrotModel::get_instance().m, - supersampling_amount, - coloring_function, - ); + rendering::render_complex_plane_into_buffer(&mut mandelbrot_model, coloring_function); + drop(mandelbrot_model); // Main loop while window.is_open() && !window.is_key_down(Key::Escape) { - // Update the window with the new buffer - window - .update_with_buffer(&MandelbrotModel::get_instance().p.pixels, config.window_width, config.window_height) - .unwrap(); - // Handle any window events - handle_key_events( - &window, - &mut MandelbrotModel::get_instance().c, - &mut MandelbrotModel::get_instance().p, - &mut MandelbrotModel::get_instance().m, - &mut MandelbrotModel::get_instance().vars, - &key_bindings, - &mut supersampling_amount, - &mut image_supersampling_amount, - &mut coloring_function, - config, - ); + handle_key_events(&window, &key_bindings, &mut coloring_function); //Handle any mouse events - handle_mouse_events( - &window, - &mut MandelbrotModel::get_instance().c, - &mut MandelbrotModel::get_instance().p, - &MandelbrotModel::get_instance().m, - supersampling_amount, - coloring_function, - ); + handle_mouse_events(&window, coloring_function); + + let mandelbrot_model = MandelbrotModel::get_instance(); + // Update the window with the new buffer + window + .update_with_buffer( + &mandelbrot_model.p.pixels, + mandelbrot_model.config.window_width, + mandelbrot_model.config.window_height, + ) + .unwrap(); } Ok(()) diff --git a/src/main.rs b/src/main.rs index a7e97d1..a648425 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,9 @@ -use std::env; use std::process; use mandelbrot::{self, MandelbrotModel}; fn main() { - if let Err(err) = mandelbrot::run(&MandelbrotModel::get_instance().config) { + if let Err(err) = mandelbrot::run() { eprintln!("Application error: {}", err); process::exit(1); } diff --git a/src/model/rendering.rs b/src/model/rendering.rs index 4745960..fa9a7c0 100644 --- a/src/model/rendering.rs +++ b/src/model/rendering.rs @@ -11,8 +11,8 @@ use std::{ use rand::Rng; -use crate::model::{complex::Complex, complex_plane::ComplexPlane, mandelbrot_function::MandelbrotFunction, pixel_buffer::PixelBuffer}; use crate::view::coloring::TrueColor; +use crate::{model::complex::Complex, MandelbrotModel}; ///A box representing the area to render by rendering functions #[derive(Clone, Copy)] @@ -60,14 +60,11 @@ impl RenderBox { /// `max_iterations` concerns the maximum amount of times the Mandelbrot formula will be applied to each Complex number. /// Note: This function is computationally intensive, and should not be used for translations pub fn render_complex_plane_into_buffer( - p: &mut PixelBuffer, - c: &ComplexPlane, - m: &MandelbrotFunction, - supersampling_amount: u8, + mandelbrot_model: &mut MandelbrotModel, coloring_function: fn(iterations: u32, max_iterations: u32) -> TrueColor, ) { - let render_box = RenderBox::new(0, p.pixel_plane.width, 0, p.pixel_plane.height); - render_box_render_complex_plane_into_buffer(p, c, m, render_box, supersampling_amount, coloring_function); + let render_box = RenderBox::new(0, mandelbrot_model.p.pixel_plane.width, 0, mandelbrot_model.p.pixel_plane.height); + render_box_render_complex_plane_into_buffer(mandelbrot_model, render_box, coloring_function); } /// Render the Complex plane c into the 32-bit pixel buffer by applying the Mandelbrot formula iteratively to every Complex point mapped to a pixel in the buffer. @@ -81,19 +78,16 @@ pub fn render_complex_plane_into_buffer( /// # Panics /// If `lock().unwrap()` panics pub fn render_box_render_complex_plane_into_buffer( - p: &mut PixelBuffer, - c: &ComplexPlane, - m: &MandelbrotFunction, + mandelbrot_model: &mut MandelbrotModel, render_box: RenderBox, - supersampling_amount: u8, coloring_function: fn(iterations: u32, max_iterations: u32) -> TrueColor, ) { let time = benchmark_start(); - let supersampling_amount = supersampling_amount.clamp(1, 64); //Supersampling_amount should be at least 1 and atmost 64 + let supersampling_amount = mandelbrot_model.config.supersampling_amount.clamp(1, 64); //Supersampling_amount should be at least 1 and atmost 64 render_box.print(); println!("SSAA: {}x", supersampling_amount); - let chunk_size = p.pixel_plane.width; - let chunks: Vec> = p.colors.chunks(chunk_size).map(ToOwned::to_owned).collect(); + let chunk_size = mandelbrot_model.p.pixel_plane.width; + let chunks: Vec> = mandelbrot_model.p.colors.chunks(chunk_size).map(ToOwned::to_owned).collect(); let chunks_len = chunks.len(); //println!("chunks.len(): {}", chunks.len()); let mut handles = Vec::new(); @@ -104,11 +98,11 @@ pub fn render_box_render_complex_plane_into_buffer( let current_progress_atomic: Arc> = Arc::new(Mutex::new(AtomicU8::new(0))); for _thread_id in 0..amount_of_threads { - let plane = (*c).clone(); + let plane = (mandelbrot_model.c).clone(); let buf = chunks.clone(); let thread_mutex = Arc::clone(&global_mutex); - let pixel_buffer = (*p).clone(); - let ms = (*m).clone(); + let pixel_buffer = (mandelbrot_model.p).clone(); + let ms = (mandelbrot_model.m).clone(); let atm = Arc::clone(¤t_progress_atomic); let handle = thread::spawn(move || { @@ -163,37 +157,52 @@ pub fn render_box_render_complex_plane_into_buffer( for handle in handles { let thread_chunks = handle.join().unwrap(); for (i, chunk) in thread_chunks { - let mut index = i * p.pixel_plane.width; + let mut index = i * mandelbrot_model.p.pixel_plane.width; for color in chunk { - p.colors[index] = color; + mandelbrot_model.p.colors[index] = color; index += 1; } } } - p.update_pixels(); + mandelbrot_model.p.update_pixels(); println!(); benchmark("render_box_render_complex_plane_into_buffer()", time); } pub fn translate_and_render_complex_plane_buffer( - p: &mut PixelBuffer, - c: &ComplexPlane, - m: &MandelbrotFunction, + mandelbrot_model: &mut MandelbrotModel, rows: i128, columns: i128, - supersampling_amount: u8, coloring_function: fn(iterations: u32, max_iterations: u32) -> TrueColor, ) { println!("rows: {}, columns: {}", rows, columns); - let max_x: usize = if columns > 0 { columns as usize } else { p.pixel_plane.width - 1 }; - let max_y: usize = if rows > 0 { rows as usize } else { p.pixel_plane.height - 1 }; - p.translate_buffer(rows, columns); + let max_x: usize = if columns > 0 { + columns as usize + } else { + mandelbrot_model.p.pixel_plane.width - 1 + }; + let max_y: usize = if rows > 0 { + rows as usize + } else { + mandelbrot_model.p.pixel_plane.height - 1 + }; + mandelbrot_model.p.translate_buffer(rows, columns); if rows == 0 { - let render_box = RenderBox::new((max_x as i128 - columns.abs()) as usize, max_x, 0, p.pixel_plane.height); - render_box_render_complex_plane_into_buffer(p, c, m, render_box, supersampling_amount, coloring_function); + let render_box = RenderBox::new( + (max_x as i128 - columns.abs()) as usize, + max_x, + 0, + mandelbrot_model.p.pixel_plane.height, + ); + render_box_render_complex_plane_into_buffer(mandelbrot_model, render_box, coloring_function); } else if columns == 0 { - let render_box = RenderBox::new(0, p.pixel_plane.width, (max_y as i128 - rows.abs()) as usize, max_y); - render_box_render_complex_plane_into_buffer(p, c, m, render_box, supersampling_amount, coloring_function); + let render_box = RenderBox::new( + 0, + mandelbrot_model.p.pixel_plane.width, + (max_y as i128 - rows.abs()) as usize, + max_y, + ); + render_box_render_complex_plane_into_buffer(mandelbrot_model, render_box, coloring_function); } else { println!("ERROR: translate_and_render_complex_plane_buffer() requires that rows == 0 || columns == 0"); } @@ -202,12 +211,9 @@ pub fn translate_and_render_complex_plane_buffer( ///# Panics /// If `rows_up` != 0 && `columns_right` != 0 pub fn translate_and_render_efficiently( - c: &mut ComplexPlane, - p: &mut PixelBuffer, - m: &MandelbrotFunction, + mandelbrot_model: &mut MandelbrotModel, rows_up: i16, columns_right: i16, - supersampling_amount: u8, coloring_function: fn(iterations: u32, max_iterations: u32) -> TrueColor, ) { assert!( @@ -217,44 +223,33 @@ pub fn translate_and_render_efficiently( let row_sign: f64 = if rows_up > 0 { -1.0 } else { 1.0 }; let column_sign: f64 = if columns_right > 0 { 1.0 } else { -1.0 }; - c.translate( - column_sign * c.pixels_to_real(columns_right.unsigned_abs() as u8), - row_sign * c.pixels_to_imaginary(rows_up.unsigned_abs() as u8), - ); - translate_and_render_complex_plane_buffer( - p, - c, - m, - rows_up.into(), - (-columns_right).into(), - supersampling_amount, - coloring_function, + mandelbrot_model.c.translate( + column_sign * mandelbrot_model.c.pixels_to_real(columns_right.unsigned_abs() as u8), + row_sign * mandelbrot_model.c.pixels_to_imaginary(rows_up.unsigned_abs() as u8), ); + translate_and_render_complex_plane_buffer(mandelbrot_model, rows_up.into(), (-columns_right).into(), coloring_function); } pub fn translate_to_center_and_render_efficiently( - c: &mut ComplexPlane, - p: &mut PixelBuffer, - m: &MandelbrotFunction, + mandelbrot_model: &mut MandelbrotModel, new_center: &Complex, - supersampling_amount: u8, coloring_function: fn(iterations: u32, max_iterations: u32) -> TrueColor, ) { - let mut translation: Complex = new_center.subtract(&c.center()); + let mut translation: Complex = new_center.subtract(&mandelbrot_model.c.center()); //Mirror the y translation because the screen y is mirrored compared to the complex plane y axis translation.y = -translation.y; //Translate x, to the right - c.translate(translation.x, 0.0); - let columns_right = -c.real_to_pixels(translation.x); + mandelbrot_model.c.translate(translation.x, 0.0); + let columns_right = -mandelbrot_model.c.real_to_pixels(translation.x); dbg!(columns_right); - translate_and_render_complex_plane_buffer(p, c, m, 0, columns_right.into(), supersampling_amount, coloring_function); + translate_and_render_complex_plane_buffer(mandelbrot_model, 0, columns_right.into(), coloring_function); //Translate y, up - c.translate(0.0, translation.y); - let rows_up = -c.imaginary_to_pixels(translation.y); + mandelbrot_model.c.translate(0.0, translation.y); + let rows_up = -mandelbrot_model.c.imaginary_to_pixels(translation.y); dbg!(rows_up); - translate_and_render_complex_plane_buffer(p, c, m, rows_up.into(), 0, supersampling_amount, coloring_function); + translate_and_render_complex_plane_buffer(mandelbrot_model, rows_up.into(), 0, coloring_function); } fn benchmark_start() -> Instant { From 3d4d22274ef488a565ea1ea2dc8034d53732a647 Mon Sep 17 00:00:00 2001 From: Jort van Waes Date: Tue, 19 Sep 2023 23:07:14 +0200 Subject: [PATCH 07/14] Implemented a few KeyBindings --- src/controller/key_bindings.rs | 27 ++++++++++++------- src/lib.rs | 48 ++++++++++++++-------------------- src/main.rs | 7 +++-- src/model/mandelbrot_model.rs | 6 ++++- 4 files changed, 47 insertions(+), 41 deletions(-) diff --git a/src/controller/key_bindings.rs b/src/controller/key_bindings.rs index 4a5f901..c6d5504 100644 --- a/src/controller/key_bindings.rs +++ b/src/controller/key_bindings.rs @@ -16,7 +16,7 @@ impl KeyAction { } ///Run self.action - pub fn action(&self) { + pub fn run(&self) { (self.action)(); } } @@ -28,20 +28,20 @@ impl fmt::Debug for KeyAction { } pub struct KeyBindings { - key_actions: Vec, + key_bindings: Vec, } impl KeyBindings { - pub fn new(key_actions: Vec) -> KeyBindings { - KeyBindings { key_actions } + pub fn new(key_bindings: Vec) -> KeyBindings { + KeyBindings { key_bindings } } ///Adds a `KeyAction` to these `KeyBindings`, will remove any existing `KeyAction` `x` where `x.key` == `key_action.key` pub fn add_key(&mut self, key_action: KeyAction) { //Remove any KeyAction x where x.key == key_action.key - self.key_actions.retain(|x| x.key != key_action.key); + self.key_bindings.retain(|x| x.key != key_action.key); - self.key_actions.push(key_action); + self.key_bindings.push(key_action); } ///Adds a `KeyAction` to these `KeyBindings`, will remove any existing `KeyAction` `x` where `x.key` == `key` @@ -50,7 +50,7 @@ impl KeyBindings { } pub fn key_actions(&self) -> &Vec { - &self.key_actions + &self.key_bindings } /// Prints all `KeyAction`s in these `KeyBindings` to stdout @@ -59,7 +59,7 @@ impl KeyBindings { } pub fn print_key(&self, key: &Key) { - for key_action in &self.key_actions { + for key_action in &self.key_bindings { if key_action.key == *key { println!("{:?}", key_action); return; @@ -68,12 +68,21 @@ impl KeyBindings { //KeyBindings does not contain a KeyAction x with x.key == key, unbound println!("{:?}", key); } + + pub fn run(&self, key: &Key) { + for key_binding in &self.key_bindings { + if key_binding.key == *key { + key_binding.run(); + break; + } + } + } } impl fmt::Debug for KeyBindings { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, "KeyBindings {{")?; - for key_action in &self.key_actions { + for key_action in &self.key_bindings { writeln!(f, " {:?},", key_action)?; } write!(f, "}}") diff --git a/src/lib.rs b/src/lib.rs index 68a02fc..3cee4f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -130,10 +130,11 @@ impl Default for InteractionVariables { // Handle any key events fn handle_key_events(window: &Window, k: &KeyBindings, coloring_function: &mut ColoringFunction) { - let mut mandelbrot_model = MandelbrotModel::get_instance(); if let Some(key) = window.get_keys_pressed(minifb::KeyRepeat::No).first() { print!("\nKey pressed: "); k.print_key(key); + k.run(key); + let mut mandelbrot_model = MandelbrotModel::get_instance(); match key { Key::Up => { let rows = mandelbrot_model.vars.translation_amount; @@ -169,16 +170,6 @@ fn handle_key_events(window: &Window, k: &KeyBindings, coloring_function: &mut C mandelbrot_model.c.center(), mandelbrot_model.c.get_scale() ), - Key::Key1 => mandelbrot_model.c.set_view(&VIEW_1), - Key::Key2 => mandelbrot_model.c.set_view(&VIEW_2), - Key::Key3 => mandelbrot_model.c.set_view(&VIEW_3), - Key::Key4 => mandelbrot_model.c.set_view(&VIEW_4), - Key::Key5 => mandelbrot_model.c.set_view(&VIEW_5), - Key::Key6 => mandelbrot_model.c.set_view(&VIEW_6), - Key::Key7 => mandelbrot_model.c.set_view(&VIEW_7), - Key::Key8 => mandelbrot_model.c.set_view(&VIEW_8), - Key::Key9 => mandelbrot_model.c.set_view(&VIEW_9), - Key::Key0 => mandelbrot_model.c.set_view(&VIEW_0), Key::K => k.print(), Key::S => { let time_stamp = chrono::Utc::now().to_string(); @@ -261,23 +252,22 @@ fn was_clicked(current: bool, previous: bool) -> bool { current && !previous } -fn handle_left_mouse_clicked(x: f32, y: f32, c: &ComplexPlane) { +fn handle_left_mouse_clicked(mandelbrot_model: &MandelbrotModel, x: f32, y: f32) { println!("\nMouseButton::Left -> Info at ({x}, {y})"); //let iterations = MandelbrotModel::get_instance().p.iterations_at_point(x as usize, y as usize, MandelbrotModel::get_instance().m.max_iterations); //TODO: fix this - let complex = MandelbrotModel::get_instance().c.complex_from_pixel_plane(x.into(), y.into()); + let complex = mandelbrot_model.c.complex_from_pixel_plane(x.into(), y.into()); println!("Complex: {:?}", complex); //println!("iterations: {}", iterations); println!(); } -fn handle_right_mouse_clicked(x: f32, y: f32, coloring_function: ColoringFunction) { - let mut mandelbrot_model = MandelbrotModel::get_instance(); +fn handle_right_mouse_clicked(mandelbrot_model: &mut MandelbrotModel, x: f32, y: f32, coloring_function: ColoringFunction) { println!("\nMouseButton::Right -> Move to ({x}, {y})"); let new_center = mandelbrot_model.c.complex_from_pixel_plane(x.into(), y.into()); println!("mandelbrot_model.c.center: {:?}", mandelbrot_model.c.center()); println!("new_center: {:?}", new_center); - rendering::translate_to_center_and_render_efficiently(&mut mandelbrot_model, &new_center, coloring_function); + rendering::translate_to_center_and_render_efficiently(mandelbrot_model, &new_center, coloring_function); mandelbrot_model.c.print(); println!(); } @@ -310,19 +300,19 @@ impl MouseClickRecorder { } fn handle_mouse_events(window: &Window, coloring_function: ColoringFunction) { - let mandelbrot_model = MandelbrotModel::get_instance(); + let mut mandelbrot_model = MandelbrotModel::get_instance(); static LEFT_MOUSE_RECORDER: MouseClickRecorder = MouseClickRecorder::new(MouseButton::Left); //Static variable with interior mutability to toggle mouse clicks; without such a variable, clicking the screen once would result in multiple actions static RIGHT_MOUSE_RECORDER: MouseClickRecorder = MouseClickRecorder::new(MouseButton::Right); if let Some((x, y)) = window.get_mouse_pos(MouseMode::Discard) { //Left mouse actions if LEFT_MOUSE_RECORDER.was_clicked(window) { - handle_left_mouse_clicked(x, y, &mandelbrot_model.c); + handle_left_mouse_clicked(&mandelbrot_model, x, y); } //Right mouse actions if RIGHT_MOUSE_RECORDER.was_clicked(window) { - handle_right_mouse_clicked(x, y, coloring_function); + handle_right_mouse_clicked(&mut mandelbrot_model, x, y, coloring_function); } } } @@ -412,16 +402,16 @@ pub fn run() -> Result<(), Box> { "Prints the current Mandelbrot set view; the center and scale", empty_closure, ); - key_bindings.add(Key::Key1, "Renders VIEW_1", empty_closure); - key_bindings.add(Key::Key2, "Renders VIEW_2", empty_closure); - key_bindings.add(Key::Key3, "Renders VIEW_3", empty_closure); - key_bindings.add(Key::Key4, "Renders VIEW_4", empty_closure); - key_bindings.add(Key::Key5, "Renders VIEW_5", empty_closure); - key_bindings.add(Key::Key6, "Renders VIEW_6", empty_closure); - key_bindings.add(Key::Key7, "Renders VIEW_7", empty_closure); - key_bindings.add(Key::Key8, "Renders VIEW_8", empty_closure); - key_bindings.add(Key::Key9, "Renders VIEW_9", empty_closure); - key_bindings.add(Key::Key0, "Renders VIEW_0", empty_closure); + key_bindings.add(Key::Key1, "Renders VIEW_1", || MandelbrotModel::get_instance().c.set_view(&VIEW_1)); + key_bindings.add(Key::Key2, "Renders VIEW_2", || MandelbrotModel::get_instance().c.set_view(&VIEW_2)); + key_bindings.add(Key::Key3, "Renders VIEW_3", || MandelbrotModel::get_instance().c.set_view(&VIEW_3)); + key_bindings.add(Key::Key4, "Renders VIEW_4", || MandelbrotModel::get_instance().c.set_view(&VIEW_4)); + key_bindings.add(Key::Key5, "Renders VIEW_5", || MandelbrotModel::get_instance().c.set_view(&VIEW_5)); + key_bindings.add(Key::Key6, "Renders VIEW_6", || MandelbrotModel::get_instance().c.set_view(&VIEW_6)); + key_bindings.add(Key::Key7, "Renders VIEW_7", || MandelbrotModel::get_instance().c.set_view(&VIEW_7)); + key_bindings.add(Key::Key8, "Renders VIEW_8", || MandelbrotModel::get_instance().c.set_view(&VIEW_8)); + key_bindings.add(Key::Key9, "Renders VIEW_9", || MandelbrotModel::get_instance().c.set_view(&VIEW_9)); + key_bindings.add(Key::Key0, "Renders VIEW_0", || MandelbrotModel::get_instance().c.set_view(&VIEW_0)); key_bindings.add(Key::K, "Prints the keybindings", empty_closure); key_bindings.add( Key::S, diff --git a/src/main.rs b/src/main.rs index a648425..05747ba 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,11 @@ -use std::process; +use std::{env, process}; -use mandelbrot::{self, MandelbrotModel}; +use mandelbrot::{self}; fn main() { + //Turn on Rust backtrace + env::set_var("RUST_BACKTRACE", "1"); + if let Err(err) = mandelbrot::run() { eprintln!("Application error: {}", err); process::exit(1); diff --git a/src/model/mandelbrot_model.rs b/src/model/mandelbrot_model.rs index fe4d5ab..787a4ff 100644 --- a/src/model/mandelbrot_model.rs +++ b/src/model/mandelbrot_model.rs @@ -39,6 +39,10 @@ impl MandelbrotModel { /// Returns the singleton MandelbrotModel instance. pub fn get_instance() -> MutexGuard<'static, MandelbrotModel> { - MANDELBROT_MODEL_INSTANCE.lock().unwrap() + let lock = MANDELBROT_MODEL_INSTANCE.try_lock(); + if let Ok(instance) = lock { + return instance; + } + panic!("You have called the singleton twice! This should never happen. It means that within the same scope, MandelbrotModel::get_instance() was called more than once."); } } From d9ac0bb2cfe8af4f5a02e46c9d5b854ada4cf61e Mon Sep 17 00:00:00 2001 From: Jort van Waes Date: Tue, 19 Sep 2023 23:46:05 +0200 Subject: [PATCH 08/14] Implemented KeyBinding action closures using the MandelbrotModel singleton --- src/controller/key_bindings.rs | 30 ++-- src/lib.rs | 315 ++++++++++++++++++--------------- 2 files changed, 187 insertions(+), 158 deletions(-) diff --git a/src/controller/key_bindings.rs b/src/controller/key_bindings.rs index c6d5504..f10a0bc 100644 --- a/src/controller/key_bindings.rs +++ b/src/controller/key_bindings.rs @@ -4,15 +4,15 @@ use minifb::Key; //https://stackoverflow.com/questions/68066875/how-to-store-a-closure-inside-rust-struct //https://stackoverflow.com/questions/65756096/how-can-i-store-a-closure-object-in-a-struct -pub struct KeyAction { +pub struct KeyBinding { pub key: Key, pub description: &'static str, action: Box, } -impl KeyAction { - pub fn new(key: Key, description: &'static str, action: Box) -> KeyAction { - KeyAction { key, description, action } +impl KeyBinding { + pub fn new(key: Key, description: &'static str, action: Box) -> KeyBinding { + KeyBinding { key, description, action } } ///Run self.action @@ -21,39 +21,39 @@ impl KeyAction { } } -impl fmt::Debug for KeyAction { +impl fmt::Debug for KeyBinding { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?} -> {}", &self.key, &self.description) } } pub struct KeyBindings { - key_bindings: Vec, + key_bindings: Vec, } impl KeyBindings { - pub fn new(key_bindings: Vec) -> KeyBindings { + pub fn new(key_bindings: Vec) -> KeyBindings { KeyBindings { key_bindings } } - ///Adds a `KeyAction` to these `KeyBindings`, will remove any existing `KeyAction` `x` where `x.key` == `key_action.key` - pub fn add_key(&mut self, key_action: KeyAction) { - //Remove any KeyAction x where x.key == key_action.key + ///Adds a `KeyBinding` to these `KeyBindings`, will remove any existing `KeyBinding` `x` where `x.key` == `key_action.key` + pub fn add_key(&mut self, key_action: KeyBinding) { + //Remove any KeyBinding x where x.key == key_action.key self.key_bindings.retain(|x| x.key != key_action.key); self.key_bindings.push(key_action); } - ///Adds a `KeyAction` to these `KeyBindings`, will remove any existing `KeyAction` `x` where `x.key` == `key` + ///Adds a `KeyBinding` to these `KeyBindings`, will remove any existing `KeyBinding` `x` where `x.key` == `key` pub fn add(&mut self, key: Key, description: &'static str, action: F) { - self.add_key(KeyAction::new(key, description, Box::new(action))); + self.add_key(KeyBinding::new(key, description, Box::new(action))); } - pub fn key_actions(&self) -> &Vec { + pub fn key_actions(&self) -> &Vec { &self.key_bindings } - /// Prints all `KeyAction`s in these `KeyBindings` to stdout + /// Prints all `KeyBinding`s in these `KeyBindings` to stdout pub fn print(&self) { println!("{:?}", self); } @@ -65,7 +65,7 @@ impl KeyBindings { return; } } - //KeyBindings does not contain a KeyAction x with x.key == key, unbound + //KeyBindings does not contain a KeyBinding x with x.key == key, unbound println!("{:?}", key); } diff --git a/src/lib.rs b/src/lib.rs index 3cee4f8..da665f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,9 +40,8 @@ pub use controller::config::Config; use controller::key_bindings::KeyBindings; use controller::user_input::{ask, pick_option}; use model::complex_plane::{ComplexPlane, View}; -use model::mandelbrot_function::MandelbrotFunction; pub use model::mandelbrot_model::MandelbrotModel; -use model::{mandelbrot_model, pixel_buffer, pixel_plane, rendering}; +use model::{pixel_buffer, pixel_plane, rendering}; use pixel_buffer::PixelBuffer; use pixel_plane::PixelPlane; use view::coloring::ColorChannelMapping; @@ -136,112 +135,13 @@ fn handle_key_events(window: &Window, k: &KeyBindings, coloring_function: &mut C k.run(key); let mut mandelbrot_model = MandelbrotModel::get_instance(); match key { - Key::Up => { - let rows = mandelbrot_model.vars.translation_amount; - rendering::translate_and_render_efficiently(&mut mandelbrot_model, rows.into(), 0, *coloring_function) - } - Key::Down => { - let rows = -i16::from(mandelbrot_model.vars.translation_amount); - rendering::translate_and_render_efficiently(&mut mandelbrot_model, rows, 0, *coloring_function) - } - Key::Left => { - let columns = -i16::from(mandelbrot_model.vars.translation_amount); - rendering::translate_and_render_efficiently(&mut mandelbrot_model, 0, columns, *coloring_function) - } - Key::Right => { - let columns = mandelbrot_model.vars.translation_amount.into(); - rendering::translate_and_render_efficiently(&mut mandelbrot_model, 0, columns, *coloring_function) - } - Key::R => mandelbrot_model.c.reset(), - Key::NumPadPlus => mandelbrot_model.vars.increment_translation_amount(), - Key::NumPadMinus => mandelbrot_model.vars.decrement_translation_amount(), - Key::NumPadAsterisk => mandelbrot_model.vars.increment_scale_numerator(), - Key::NumPadSlash => mandelbrot_model.vars.decrement_scale_numerator(), - Key::LeftBracket => { - let scaling_factor = mandelbrot_model.vars.scaling_factor(); - mandelbrot_model.c.scale(scaling_factor) - } - Key::RightBracket => { - let inverse_scaling_factor = mandelbrot_model.vars.inverse_scaling_factor(); - mandelbrot_model.c.scale(inverse_scaling_factor) - } - Key::V => println!( - "Center: {:?}, scale: {:?}", - mandelbrot_model.c.center(), - mandelbrot_model.c.get_scale() - ), Key::K => k.print(), - Key::S => { - let time_stamp = chrono::Utc::now().to_string(); - if mandelbrot_model.config.window_scale == 1.0 { - mandelbrot_model.p.save_as_png( - &time_stamp, - &mandelbrot_model.c.get_view(), - &mandelbrot_model.m, - mandelbrot_model.config.supersampling_amount, - ); - } else { - let mut image_p: PixelBuffer = PixelBuffer::new(PixelPlane::new( - mandelbrot_model.config.image_width, - mandelbrot_model.config.image_height, - )); - let mut image_c: ComplexPlane = - ComplexPlane::new(mandelbrot_model.config.image_width, mandelbrot_model.config.image_height); - image_p.color_channel_mapping = mandelbrot_model.p.color_channel_mapping; - image_c.set_view(&mandelbrot_model.c.get_view()); - rendering::render_complex_plane_into_buffer(&mut mandelbrot_model, *coloring_function); - image_p.save_as_png( - &time_stamp, - &mandelbrot_model.c.get_view(), - &mandelbrot_model.m, - mandelbrot_model.config.supersampling_amount, - ); - } - } - Key::I => mandelbrot_model.c.set_view(&View::new(ask("x"), ask("y"), ask("scale"))), Key::A => { *coloring_function = pick_option(&[ ("HSV", TrueColor::new_from_hsv_colors), ("Bernstein polynomials", TrueColor::new_from_bernstein_polynomials), - ]) - } - Key::M => mandelbrot_model.m.max_iterations = ask("max_iterations"), - Key::O => mandelbrot_model.p.color_channel_mapping = ask("color_channel_mapping"), - Key::Q => { - mandelbrot_model.config.supersampling_amount = ask::("supersampling_amount").clamp(1, 64); - } - Key::C => println!("{:?}", mandelbrot_model.config), - _ => (), - } - match key { - Key::NumPadPlus | Key::NumPadMinus => { - println!("translation_amount: {}", mandelbrot_model.vars.translation_amount) - } - Key::NumPadSlash | Key::NumPadAsterisk => println!( - "scale factor: {}/{}", - mandelbrot_model.vars.scale_numerator, mandelbrot_model.vars.scale_denominator - ), - Key::Up | Key::Down | Key::Left | Key::Right => mandelbrot_model.c.print(), - Key::R - | Key::Key1 - | Key::Key2 - | Key::Key3 - | Key::Key4 - | Key::Key5 - | Key::Key6 - | Key::Key7 - | Key::Key8 - | Key::Key9 - | Key::Key0 - | Key::LeftBracket - | Key::RightBracket - | Key::I - | Key::A - | Key::M - | Key::O - | Key::Q => { - rendering::render_complex_plane_into_buffer(&mut mandelbrot_model, *coloring_function); - mandelbrot_model.c.print(); + ]); + render(&mut mandelbrot_model, COLORING_FUNCTION); } _ => (), } @@ -317,6 +217,16 @@ fn handle_mouse_events(window: &Window, coloring_function: ColoringFunction) { } } +fn render(mandelbrot_model: &mut MandelbrotModel, coloring_function: ColoringFunction) { + rendering::render_complex_plane_into_buffer(mandelbrot_model, coloring_function); + mandelbrot_model.c.print(); +} + +fn set_view(mandelbrot_model: &mut MandelbrotModel, view: &View) { + mandelbrot_model.c.set_view(view); + render(mandelbrot_model, COLORING_FUNCTION); +} + ///Prints Mandelbrot ASCII art :)
///Prints the `application_banner`, `author_banner`, and `version` fn print_banner() { @@ -373,70 +283,189 @@ pub fn run() -> Result<(), Box> { print_banner(); //Print command info print_command_info(); - //Initialize keybindings TODO: I want to have a vector of structs containing functions with different signatures, this is not easily possible. All functionality should be placed here, in the future, when - //I've figured out how to have closures with different signatures in the same struct field - //For now, use empty_closure, to have a closure that does nothing as action - let mut key_bindings: KeyBindings = KeyBindings::new(Vec::new()); + //Initialize keybindings + let mut key_bindings: KeyBindings = KeyBindings::new(Vec::new()); //TODO: Make KeyBindings a singleton let empty_closure = || (); - key_bindings.add(Key::Up, "Move up translation_amount pixels", empty_closure); - key_bindings.add(Key::Down, "Move down translation_amount pixels", empty_closure); - key_bindings.add(Key::Left, "Move left translation_amount pixels", empty_closure); - key_bindings.add(Key::Right, "Move right translation_amount pixels", empty_closure); - key_bindings.add(Key::R, "Reset the Mandelbrot set view to the starting view", empty_closure); - key_bindings.add(Key::NumPadPlus, "Increment translation_amount", empty_closure); - key_bindings.add(Key::NumPadMinus, "Decrement translation amount", empty_closure); - key_bindings.add(Key::NumPadAsterisk, "Increment scale_numerator", empty_closure); - key_bindings.add(Key::NumPadSlash, "Decrement scale_numerator", empty_closure); - key_bindings.add( - Key::LeftBracket, - "Scale the view by scaling_factor, effectively zooming in", - empty_closure, - ); + key_bindings.add(Key::Up, "Move up translation_amount pixels", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + let rows = mandelbrot_model.vars.translation_amount; + rendering::translate_and_render_efficiently(&mut mandelbrot_model, rows.into(), 0, COLORING_FUNCTION); + mandelbrot_model.c.print(); + }); + key_bindings.add(Key::Down, "Move down translation_amount pixels", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + let rows = -i16::from(mandelbrot_model.vars.translation_amount); + rendering::translate_and_render_efficiently(&mut mandelbrot_model, rows, 0, COLORING_FUNCTION); + mandelbrot_model.c.print(); + }); + key_bindings.add(Key::Left, "Move left translation_amount pixels", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + let columns = -i16::from(mandelbrot_model.vars.translation_amount); + rendering::translate_and_render_efficiently(&mut mandelbrot_model, 0, columns, COLORING_FUNCTION); + mandelbrot_model.c.print(); + }); + key_bindings.add(Key::Right, "Move right translation_amount pixels", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + let columns = mandelbrot_model.vars.translation_amount.into(); + rendering::translate_and_render_efficiently(&mut mandelbrot_model, 0, columns, COLORING_FUNCTION); + mandelbrot_model.c.print(); + }); + key_bindings.add(Key::R, "Reset the Mandelbrot set view to the starting view", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + mandelbrot_model.c.reset(); + render(&mut mandelbrot_model, COLORING_FUNCTION); + }); + key_bindings.add(Key::NumPadPlus, "Increment translation_amount", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + mandelbrot_model.vars.increment_translation_amount(); + println!("translation_amount: {}", mandelbrot_model.vars.translation_amount); + }); + + key_bindings.add(Key::NumPadMinus, "Decrement translation amount", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + mandelbrot_model.vars.decrement_translation_amount(); + println!("translation_amount: {}", mandelbrot_model.vars.translation_amount); + }); + key_bindings.add(Key::NumPadAsterisk, "Increment scale_numerator", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + mandelbrot_model.vars.increment_scale_numerator(); + println!( + "scale factor: {}/{}", + mandelbrot_model.vars.scale_numerator, mandelbrot_model.vars.scale_denominator + ); + }); + key_bindings.add(Key::NumPadSlash, "Decrement scale_numerator", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + mandelbrot_model.vars.decrement_scale_numerator(); + println!( + "scale factor: {}/{}", + mandelbrot_model.vars.scale_numerator, mandelbrot_model.vars.scale_denominator + ); + }); + key_bindings.add(Key::LeftBracket, "Scale the view by scaling_factor, effectively zooming in", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + let scaling_factor = mandelbrot_model.vars.scaling_factor(); + mandelbrot_model.c.scale(scaling_factor); + render(&mut mandelbrot_model, COLORING_FUNCTION); + }); key_bindings.add( Key::RightBracket, "Scale the view by inverse_scaling_factor, effectively zooming out", - empty_closure, + || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + let inverse_scaling_factor = mandelbrot_model.vars.inverse_scaling_factor(); + mandelbrot_model.c.scale(inverse_scaling_factor); + render(&mut mandelbrot_model, COLORING_FUNCTION); + }, ); - key_bindings.add( - Key::V, - "Prints the current Mandelbrot set view; the center and scale", - empty_closure, - ); - key_bindings.add(Key::Key1, "Renders VIEW_1", || MandelbrotModel::get_instance().c.set_view(&VIEW_1)); - key_bindings.add(Key::Key2, "Renders VIEW_2", || MandelbrotModel::get_instance().c.set_view(&VIEW_2)); - key_bindings.add(Key::Key3, "Renders VIEW_3", || MandelbrotModel::get_instance().c.set_view(&VIEW_3)); - key_bindings.add(Key::Key4, "Renders VIEW_4", || MandelbrotModel::get_instance().c.set_view(&VIEW_4)); - key_bindings.add(Key::Key5, "Renders VIEW_5", || MandelbrotModel::get_instance().c.set_view(&VIEW_5)); - key_bindings.add(Key::Key6, "Renders VIEW_6", || MandelbrotModel::get_instance().c.set_view(&VIEW_6)); - key_bindings.add(Key::Key7, "Renders VIEW_7", || MandelbrotModel::get_instance().c.set_view(&VIEW_7)); - key_bindings.add(Key::Key8, "Renders VIEW_8", || MandelbrotModel::get_instance().c.set_view(&VIEW_8)); - key_bindings.add(Key::Key9, "Renders VIEW_9", || MandelbrotModel::get_instance().c.set_view(&VIEW_9)); - key_bindings.add(Key::Key0, "Renders VIEW_0", || MandelbrotModel::get_instance().c.set_view(&VIEW_0)); + key_bindings.add(Key::V, "Prints the current Mandelbrot set view; the center and scale", || { + let mandelbrot_model = MandelbrotModel::get_instance(); + println!( + "Center: {:?}, scale: {:?}", + mandelbrot_model.c.center(), + mandelbrot_model.c.get_scale() + ); + }); + key_bindings.add(Key::Key1, "Renders VIEW_1", || { + set_view(&mut MandelbrotModel::get_instance(), &VIEW_1) + }); + key_bindings.add(Key::Key2, "Renders VIEW_2", || { + set_view(&mut MandelbrotModel::get_instance(), &VIEW_2) + }); + key_bindings.add(Key::Key3, "Renders VIEW_3", || { + set_view(&mut MandelbrotModel::get_instance(), &VIEW_3) + }); + key_bindings.add(Key::Key4, "Renders VIEW_4", || { + set_view(&mut MandelbrotModel::get_instance(), &VIEW_4) + }); + key_bindings.add(Key::Key5, "Renders VIEW_5", || { + set_view(&mut MandelbrotModel::get_instance(), &VIEW_5) + }); + key_bindings.add(Key::Key6, "Renders VIEW_6", || { + set_view(&mut MandelbrotModel::get_instance(), &VIEW_6) + }); + key_bindings.add(Key::Key7, "Renders VIEW_7", || { + set_view(&mut MandelbrotModel::get_instance(), &VIEW_7) + }); + key_bindings.add(Key::Key8, "Renders VIEW_8", || { + set_view(&mut MandelbrotModel::get_instance(), &VIEW_8) + }); + key_bindings.add(Key::Key9, "Renders VIEW_9", || { + set_view(&mut MandelbrotModel::get_instance(), &VIEW_9) + }); + key_bindings.add(Key::Key0, "Renders VIEW_0", || { + set_view(&mut MandelbrotModel::get_instance(), &VIEW_0) + }); key_bindings.add(Key::K, "Prints the keybindings", empty_closure); key_bindings.add( Key::S, "Saves the current Mandelbrot set view as an image in the saved folder", - empty_closure, + || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + let time_stamp = chrono::Utc::now().to_string(); + if mandelbrot_model.config.window_scale == 1.0 { + mandelbrot_model.p.save_as_png( + &time_stamp, + &mandelbrot_model.c.get_view(), + &mandelbrot_model.m, + mandelbrot_model.config.supersampling_amount, + ); + } else { + let mut image_p: PixelBuffer = PixelBuffer::new(PixelPlane::new( + mandelbrot_model.config.image_width, + mandelbrot_model.config.image_height, + )); + let mut image_c: ComplexPlane = + ComplexPlane::new(mandelbrot_model.config.image_width, mandelbrot_model.config.image_height); + image_p.color_channel_mapping = mandelbrot_model.p.color_channel_mapping; + image_c.set_view(&mandelbrot_model.c.get_view()); + rendering::render_complex_plane_into_buffer(&mut mandelbrot_model, COLORING_FUNCTION); + image_p.save_as_png( + &time_stamp, + &mandelbrot_model.c.get_view(), + &mandelbrot_model.m, + mandelbrot_model.config.supersampling_amount, + ); + } + }, ); - key_bindings.add(Key::I, "Manually input a Mandelbrot set view", empty_closure); + key_bindings.add(Key::I, "Manually input a Mandelbrot set view", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + mandelbrot_model.c.set_view(&View::new(ask("x"), ask("y"), ask("scale"))); + render(&mut mandelbrot_model, COLORING_FUNCTION); + }); key_bindings.add(Key::A, "Pick an algorithm to color the Mandelbrot set view", empty_closure); - key_bindings.add(Key::M, "Change the Mandelbrot set view max_iterations", empty_closure); + key_bindings.add(Key::M, "Change the Mandelbrot set view max_iterations", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + mandelbrot_model.m.max_iterations = ask("max_iterations"); + render(&mut mandelbrot_model, COLORING_FUNCTION); + }); key_bindings.add( Key::O, "Change the Mandelbrot set view color channel mapping, xyz -> RGB, where x,y,z ∈ {{'R','G','B'}} (case-insensitive)", - empty_closure, + || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + mandelbrot_model.p.color_channel_mapping = ask("color_channel_mapping"); + render(&mut mandelbrot_model, COLORING_FUNCTION); + }, ); key_bindings.add( Key::Q, "Change the window and image quality of the Mandelbrot set rendering by setting the SSAA multiplier, clamped from 1x to 64x", - empty_closure, + || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + mandelbrot_model.config.supersampling_amount = ask::("supersampling_amount").clamp(1, 64); + render(&mut mandelbrot_model, COLORING_FUNCTION); + }, ); key_bindings.add( Key::X, "Change the image quality of the Mandelbrot set rendering by setting the SSAA multiplier, clamped from 1x to 64x", empty_closure, ); - key_bindings.add(Key::C, "Prints the configuration variables", empty_closure); + key_bindings.add(Key::C, "Prints the configuration variables", || { + println!("{:?}", MandelbrotModel::get_instance().config); + }); key_bindings.print(); mandelbrot_model.p.pixel_plane.print(); From 724d507d61ce0645e72ae7621ee2ef26a5c65009 Mon Sep 17 00:00:00 2001 From: Jort van Waes Date: Wed, 20 Sep 2023 00:02:39 +0200 Subject: [PATCH 09/14] Added coloring_function to MandelbrotModel --- src/lib.rs | 63 +++++++++++++++++++---------------- src/model/mandelbrot_model.rs | 6 +++- src/model/rendering.rs | 44 +++++++----------------- 3 files changed, 51 insertions(+), 62 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index da665f1..14b1537 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,7 @@ pub use controller::config::Config; use controller::key_bindings::KeyBindings; use controller::user_input::{ask, pick_option}; use model::complex_plane::{ComplexPlane, View}; +use model::mandelbrot_model::ColoringFunction; pub use model::mandelbrot_model::MandelbrotModel; use model::{pixel_buffer, pixel_plane, rendering}; use pixel_buffer::PixelBuffer; @@ -48,7 +49,6 @@ use view::coloring::ColorChannelMapping; use view::coloring::TrueColor; //Coloring function -type ColoringFunction = fn(iterations: u32, max_iterations: u32) -> TrueColor; static COLORING_FUNCTION: ColoringFunction = TrueColor::new_from_bernstein_polynomials; //Color channel mapping @@ -128,7 +128,7 @@ impl Default for InteractionVariables { } // Handle any key events -fn handle_key_events(window: &Window, k: &KeyBindings, coloring_function: &mut ColoringFunction) { +fn handle_key_events(window: &Window, k: &KeyBindings) { if let Some(key) = window.get_keys_pressed(minifb::KeyRepeat::No).first() { print!("\nKey pressed: "); k.print_key(key); @@ -137,11 +137,11 @@ fn handle_key_events(window: &Window, k: &KeyBindings, coloring_function: &mut C match key { Key::K => k.print(), Key::A => { - *coloring_function = pick_option(&[ + mandelbrot_model.coloring_function = pick_option(&[ ("HSV", TrueColor::new_from_hsv_colors), ("Bernstein polynomials", TrueColor::new_from_bernstein_polynomials), ]); - render(&mut mandelbrot_model, COLORING_FUNCTION); + render(&mut mandelbrot_model); } _ => (), } @@ -161,13 +161,13 @@ fn handle_left_mouse_clicked(mandelbrot_model: &MandelbrotModel, x: f32, y: f32) println!(); } -fn handle_right_mouse_clicked(mandelbrot_model: &mut MandelbrotModel, x: f32, y: f32, coloring_function: ColoringFunction) { +fn handle_right_mouse_clicked(mandelbrot_model: &mut MandelbrotModel, x: f32, y: f32) { println!("\nMouseButton::Right -> Move to ({x}, {y})"); let new_center = mandelbrot_model.c.complex_from_pixel_plane(x.into(), y.into()); println!("mandelbrot_model.c.center: {:?}", mandelbrot_model.c.center()); println!("new_center: {:?}", new_center); - rendering::translate_to_center_and_render_efficiently(mandelbrot_model, &new_center, coloring_function); + rendering::translate_to_center_and_render_efficiently(mandelbrot_model, &new_center); mandelbrot_model.c.print(); println!(); } @@ -199,7 +199,7 @@ impl MouseClickRecorder { } } -fn handle_mouse_events(window: &Window, coloring_function: ColoringFunction) { +fn handle_mouse_events(window: &Window) { let mut mandelbrot_model = MandelbrotModel::get_instance(); static LEFT_MOUSE_RECORDER: MouseClickRecorder = MouseClickRecorder::new(MouseButton::Left); //Static variable with interior mutability to toggle mouse clicks; without such a variable, clicking the screen once would result in multiple actions static RIGHT_MOUSE_RECORDER: MouseClickRecorder = MouseClickRecorder::new(MouseButton::Right); @@ -212,19 +212,20 @@ fn handle_mouse_events(window: &Window, coloring_function: ColoringFunction) { //Right mouse actions if RIGHT_MOUSE_RECORDER.was_clicked(window) { - handle_right_mouse_clicked(&mut mandelbrot_model, x, y, coloring_function); + handle_right_mouse_clicked(&mut mandelbrot_model, x, y); } } } -fn render(mandelbrot_model: &mut MandelbrotModel, coloring_function: ColoringFunction) { - rendering::render_complex_plane_into_buffer(mandelbrot_model, coloring_function); +fn render(mandelbrot_model: &mut MandelbrotModel) { + //TODO: Observer pattern view -> model to update the view, instead of rendering manually + rendering::render_complex_plane_into_buffer(mandelbrot_model); mandelbrot_model.c.print(); } fn set_view(mandelbrot_model: &mut MandelbrotModel, view: &View) { mandelbrot_model.c.set_view(view); - render(mandelbrot_model, COLORING_FUNCTION); + render(mandelbrot_model); } ///Prints Mandelbrot ASCII art :)
@@ -266,9 +267,9 @@ fn print_command_info() { pub fn run() -> Result<(), Box> { let mut mandelbrot_model = MandelbrotModel::get_instance(); //Coloring function - let mut coloring_function = COLORING_FUNCTION; + mandelbrot_model.coloring_function = COLORING_FUNCTION; //Color channel mapping - //MandelbrotModel::get_instance().p.color_channel_mapping = COLOR_CHANNEL_MAPPING; + mandelbrot_model.p.color_channel_mapping = COLOR_CHANNEL_MAPPING; // Create a new window let mut window = Window::new( "Mandelbrot set viewer", @@ -289,31 +290,31 @@ pub fn run() -> Result<(), Box> { key_bindings.add(Key::Up, "Move up translation_amount pixels", || { let mut mandelbrot_model = MandelbrotModel::get_instance(); let rows = mandelbrot_model.vars.translation_amount; - rendering::translate_and_render_efficiently(&mut mandelbrot_model, rows.into(), 0, COLORING_FUNCTION); + rendering::translate_and_render_efficiently(&mut mandelbrot_model, rows.into(), 0); mandelbrot_model.c.print(); }); key_bindings.add(Key::Down, "Move down translation_amount pixels", || { let mut mandelbrot_model = MandelbrotModel::get_instance(); let rows = -i16::from(mandelbrot_model.vars.translation_amount); - rendering::translate_and_render_efficiently(&mut mandelbrot_model, rows, 0, COLORING_FUNCTION); + rendering::translate_and_render_efficiently(&mut mandelbrot_model, rows, 0); mandelbrot_model.c.print(); }); key_bindings.add(Key::Left, "Move left translation_amount pixels", || { let mut mandelbrot_model = MandelbrotModel::get_instance(); let columns = -i16::from(mandelbrot_model.vars.translation_amount); - rendering::translate_and_render_efficiently(&mut mandelbrot_model, 0, columns, COLORING_FUNCTION); + rendering::translate_and_render_efficiently(&mut mandelbrot_model, 0, columns); mandelbrot_model.c.print(); }); key_bindings.add(Key::Right, "Move right translation_amount pixels", || { let mut mandelbrot_model = MandelbrotModel::get_instance(); let columns = mandelbrot_model.vars.translation_amount.into(); - rendering::translate_and_render_efficiently(&mut mandelbrot_model, 0, columns, COLORING_FUNCTION); + rendering::translate_and_render_efficiently(&mut mandelbrot_model, 0, columns); mandelbrot_model.c.print(); }); key_bindings.add(Key::R, "Reset the Mandelbrot set view to the starting view", || { let mut mandelbrot_model = MandelbrotModel::get_instance(); mandelbrot_model.c.reset(); - render(&mut mandelbrot_model, COLORING_FUNCTION); + render(&mut mandelbrot_model); }); key_bindings.add(Key::NumPadPlus, "Increment translation_amount", || { let mut mandelbrot_model = MandelbrotModel::get_instance(); @@ -346,7 +347,7 @@ pub fn run() -> Result<(), Box> { let mut mandelbrot_model = MandelbrotModel::get_instance(); let scaling_factor = mandelbrot_model.vars.scaling_factor(); mandelbrot_model.c.scale(scaling_factor); - render(&mut mandelbrot_model, COLORING_FUNCTION); + render(&mut mandelbrot_model); }); key_bindings.add( Key::RightBracket, @@ -355,7 +356,7 @@ pub fn run() -> Result<(), Box> { let mut mandelbrot_model = MandelbrotModel::get_instance(); let inverse_scaling_factor = mandelbrot_model.vars.inverse_scaling_factor(); mandelbrot_model.c.scale(inverse_scaling_factor); - render(&mut mandelbrot_model, COLORING_FUNCTION); + render(&mut mandelbrot_model); }, ); key_bindings.add(Key::V, "Prints the current Mandelbrot set view; the center and scale", || { @@ -419,7 +420,7 @@ pub fn run() -> Result<(), Box> { ComplexPlane::new(mandelbrot_model.config.image_width, mandelbrot_model.config.image_height); image_p.color_channel_mapping = mandelbrot_model.p.color_channel_mapping; image_c.set_view(&mandelbrot_model.c.get_view()); - rendering::render_complex_plane_into_buffer(&mut mandelbrot_model, COLORING_FUNCTION); + rendering::render_complex_plane_into_buffer(&mut mandelbrot_model); image_p.save_as_png( &time_stamp, &mandelbrot_model.c.get_view(), @@ -432,13 +433,13 @@ pub fn run() -> Result<(), Box> { key_bindings.add(Key::I, "Manually input a Mandelbrot set view", || { let mut mandelbrot_model = MandelbrotModel::get_instance(); mandelbrot_model.c.set_view(&View::new(ask("x"), ask("y"), ask("scale"))); - render(&mut mandelbrot_model, COLORING_FUNCTION); + render(&mut mandelbrot_model); }); key_bindings.add(Key::A, "Pick an algorithm to color the Mandelbrot set view", empty_closure); key_bindings.add(Key::M, "Change the Mandelbrot set view max_iterations", || { let mut mandelbrot_model = MandelbrotModel::get_instance(); mandelbrot_model.m.max_iterations = ask("max_iterations"); - render(&mut mandelbrot_model, COLORING_FUNCTION); + render(&mut mandelbrot_model); }); key_bindings.add( Key::O, @@ -446,7 +447,7 @@ pub fn run() -> Result<(), Box> { || { let mut mandelbrot_model = MandelbrotModel::get_instance(); mandelbrot_model.p.color_channel_mapping = ask("color_channel_mapping"); - render(&mut mandelbrot_model, COLORING_FUNCTION); + render(&mut mandelbrot_model); }, ); key_bindings.add( @@ -455,7 +456,7 @@ pub fn run() -> Result<(), Box> { || { let mut mandelbrot_model = MandelbrotModel::get_instance(); mandelbrot_model.config.supersampling_amount = ask::("supersampling_amount").clamp(1, 64); - render(&mut mandelbrot_model, COLORING_FUNCTION); + render(&mut mandelbrot_model); }, ); key_bindings.add( @@ -485,16 +486,16 @@ pub fn run() -> Result<(), Box> { println!(); println!("Rendering Mandelbrot set default view"); - rendering::render_complex_plane_into_buffer(&mut mandelbrot_model, coloring_function); + rendering::render_complex_plane_into_buffer(&mut mandelbrot_model); drop(mandelbrot_model); // Main loop while window.is_open() && !window.is_key_down(Key::Escape) { // Handle any window events - handle_key_events(&window, &key_bindings, &mut coloring_function); + handle_key_events(&window, &key_bindings); //Handle any mouse events - handle_mouse_events(&window, coloring_function); + handle_mouse_events(&window); let mandelbrot_model = MandelbrotModel::get_instance(); // Update the window with the new buffer @@ -506,6 +507,10 @@ pub fn run() -> Result<(), Box> { ) .unwrap(); } - + if window.is_key_down(Key::Escape) { + println!("Escape pressed, application exited gracefully."); + } else { + println!("Window closed, application exited gracefully.") + } Ok(()) } diff --git a/src/model/mandelbrot_model.rs b/src/model/mandelbrot_model.rs index 787a4ff..ebc5c9c 100644 --- a/src/model/mandelbrot_model.rs +++ b/src/model/mandelbrot_model.rs @@ -4,7 +4,7 @@ use std::{ sync::{Mutex, MutexGuard}, }; -use crate::{Config, InteractionVariables}; +use crate::{view::coloring::TrueColor, Config, InteractionVariables}; use super::{complex_plane::ComplexPlane, mandelbrot_function::MandelbrotFunction, pixel_buffer::PixelBuffer, pixel_plane::PixelPlane}; @@ -12,6 +12,8 @@ lazy_static! { static ref MANDELBROT_MODEL_INSTANCE: Mutex = Mutex::new(MandelbrotModel::new()); } +pub type ColoringFunction = fn(iterations: u32, max_iterations: u32) -> TrueColor; + pub struct MandelbrotModel { pub config: Config, pub c: ComplexPlane, @@ -19,6 +21,7 @@ pub struct MandelbrotModel { pub vars: InteractionVariables, pub amount_of_threads: usize, pub m: MandelbrotFunction, + pub coloring_function: ColoringFunction, } impl MandelbrotModel { @@ -34,6 +37,7 @@ impl MandelbrotModel { vars: InteractionVariables::default(), amount_of_threads: num_cpus::get(), m: MandelbrotFunction::new(config.max_iterations, config.orbit_radius), + coloring_function: TrueColor::new_from_bernstein_polynomials, } } diff --git a/src/model/rendering.rs b/src/model/rendering.rs index fa9a7c0..9eabb57 100644 --- a/src/model/rendering.rs +++ b/src/model/rendering.rs @@ -59,12 +59,9 @@ impl RenderBox { /// `orbit_radius` determines when Zn is considered to have gone to infinity. /// `max_iterations` concerns the maximum amount of times the Mandelbrot formula will be applied to each Complex number. /// Note: This function is computationally intensive, and should not be used for translations -pub fn render_complex_plane_into_buffer( - mandelbrot_model: &mut MandelbrotModel, - coloring_function: fn(iterations: u32, max_iterations: u32) -> TrueColor, -) { +pub fn render_complex_plane_into_buffer(mandelbrot_model: &mut MandelbrotModel) { let render_box = RenderBox::new(0, mandelbrot_model.p.pixel_plane.width, 0, mandelbrot_model.p.pixel_plane.height); - render_box_render_complex_plane_into_buffer(mandelbrot_model, render_box, coloring_function); + render_box_render_complex_plane_into_buffer(mandelbrot_model, render_box); } /// Render the Complex plane c into the 32-bit pixel buffer by applying the Mandelbrot formula iteratively to every Complex point mapped to a pixel in the buffer. @@ -77,11 +74,7 @@ pub fn render_complex_plane_into_buffer( /// * `coloring_function` - e.g. `TrueColor::new_from_hsv` /// # Panics /// If `lock().unwrap()` panics -pub fn render_box_render_complex_plane_into_buffer( - mandelbrot_model: &mut MandelbrotModel, - render_box: RenderBox, - coloring_function: fn(iterations: u32, max_iterations: u32) -> TrueColor, -) { +pub fn render_box_render_complex_plane_into_buffer(mandelbrot_model: &mut MandelbrotModel, render_box: RenderBox) { let time = benchmark_start(); let supersampling_amount = mandelbrot_model.config.supersampling_amount.clamp(1, 64); //Supersampling_amount should be at least 1 and atmost 64 render_box.print(); @@ -104,6 +97,7 @@ pub fn render_box_render_complex_plane_into_buffer( let pixel_buffer = (mandelbrot_model.p).clone(); let ms = (mandelbrot_model.m).clone(); let atm = Arc::clone(¤t_progress_atomic); + let coloring_function = (mandelbrot_model.coloring_function).clone(); let handle = thread::spawn(move || { let mut thread_chunks = Vec::new(); @@ -169,12 +163,7 @@ pub fn render_box_render_complex_plane_into_buffer( benchmark("render_box_render_complex_plane_into_buffer()", time); } -pub fn translate_and_render_complex_plane_buffer( - mandelbrot_model: &mut MandelbrotModel, - rows: i128, - columns: i128, - coloring_function: fn(iterations: u32, max_iterations: u32) -> TrueColor, -) { +pub fn translate_and_render_complex_plane_buffer(mandelbrot_model: &mut MandelbrotModel, rows: i128, columns: i128) { println!("rows: {}, columns: {}", rows, columns); let max_x: usize = if columns > 0 { columns as usize @@ -194,7 +183,7 @@ pub fn translate_and_render_complex_plane_buffer( 0, mandelbrot_model.p.pixel_plane.height, ); - render_box_render_complex_plane_into_buffer(mandelbrot_model, render_box, coloring_function); + render_box_render_complex_plane_into_buffer(mandelbrot_model, render_box); } else if columns == 0 { let render_box = RenderBox::new( 0, @@ -202,7 +191,7 @@ pub fn translate_and_render_complex_plane_buffer( (max_y as i128 - rows.abs()) as usize, max_y, ); - render_box_render_complex_plane_into_buffer(mandelbrot_model, render_box, coloring_function); + render_box_render_complex_plane_into_buffer(mandelbrot_model, render_box); } else { println!("ERROR: translate_and_render_complex_plane_buffer() requires that rows == 0 || columns == 0"); } @@ -210,12 +199,7 @@ pub fn translate_and_render_complex_plane_buffer( ///# Panics /// If `rows_up` != 0 && `columns_right` != 0 -pub fn translate_and_render_efficiently( - mandelbrot_model: &mut MandelbrotModel, - rows_up: i16, - columns_right: i16, - coloring_function: fn(iterations: u32, max_iterations: u32) -> TrueColor, -) { +pub fn translate_and_render_efficiently(mandelbrot_model: &mut MandelbrotModel, rows_up: i16, columns_right: i16) { assert!( rows_up == 0 || columns_right == 0, "translate_and_render_efficiently: rows_up should be 0 or columns_right should be 0!" @@ -227,14 +211,10 @@ pub fn translate_and_render_efficiently( column_sign * mandelbrot_model.c.pixels_to_real(columns_right.unsigned_abs() as u8), row_sign * mandelbrot_model.c.pixels_to_imaginary(rows_up.unsigned_abs() as u8), ); - translate_and_render_complex_plane_buffer(mandelbrot_model, rows_up.into(), (-columns_right).into(), coloring_function); + translate_and_render_complex_plane_buffer(mandelbrot_model, rows_up.into(), (-columns_right).into()); } -pub fn translate_to_center_and_render_efficiently( - mandelbrot_model: &mut MandelbrotModel, - new_center: &Complex, - coloring_function: fn(iterations: u32, max_iterations: u32) -> TrueColor, -) { +pub fn translate_to_center_and_render_efficiently(mandelbrot_model: &mut MandelbrotModel, new_center: &Complex) { let mut translation: Complex = new_center.subtract(&mandelbrot_model.c.center()); //Mirror the y translation because the screen y is mirrored compared to the complex plane y axis translation.y = -translation.y; @@ -243,13 +223,13 @@ pub fn translate_to_center_and_render_efficiently( mandelbrot_model.c.translate(translation.x, 0.0); let columns_right = -mandelbrot_model.c.real_to_pixels(translation.x); dbg!(columns_right); - translate_and_render_complex_plane_buffer(mandelbrot_model, 0, columns_right.into(), coloring_function); + translate_and_render_complex_plane_buffer(mandelbrot_model, 0, columns_right.into()); //Translate y, up mandelbrot_model.c.translate(0.0, translation.y); let rows_up = -mandelbrot_model.c.imaginary_to_pixels(translation.y); dbg!(rows_up); - translate_and_render_complex_plane_buffer(mandelbrot_model, rows_up.into(), 0, coloring_function); + translate_and_render_complex_plane_buffer(mandelbrot_model, rows_up.into(), 0); } fn benchmark_start() -> Instant { From d90e92c672d039e108280902d64bfe5b790fe9f6 Mon Sep 17 00:00:00 2001 From: Jort van Waes Date: Wed, 20 Sep 2023 11:13:40 +0200 Subject: [PATCH 10/14] Refactored lib.rs --- benches/bench.rs | 1 + src/controller/interaction_variables.rs | 57 +++ src/controller/minifb_controller.rs | 335 ++++++++++++++++++ src/controller/mod.rs | 3 + src/controller/mouse_click_recorder.rs | 34 ++ src/lib.rs | 451 +----------------------- src/main.rs | 1 + src/model/mandelbrot_model.rs | 4 +- src/model/rendering.rs | 13 + src/view/mod.rs | 1 + src/view/terminal.rs | 28 ++ 11 files changed, 481 insertions(+), 447 deletions(-) create mode 100644 src/controller/interaction_variables.rs create mode 100644 src/controller/minifb_controller.rs create mode 100644 src/controller/mouse_click_recorder.rs create mode 100644 src/view/terminal.rs diff --git a/benches/bench.rs b/benches/bench.rs index e142d66..6d3bc3c 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -32,6 +32,7 @@ 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); diff --git a/src/controller/interaction_variables.rs b/src/controller/interaction_variables.rs new file mode 100644 index 0000000..2a28eaf --- /dev/null +++ b/src/controller/interaction_variables.rs @@ -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, + } + } +} diff --git a/src/controller/minifb_controller.rs b/src/controller/minifb_controller.rs new file mode 100644 index 0000000..107697c --- /dev/null +++ b/src/controller/minifb_controller.rs @@ -0,0 +1,335 @@ +use std::error::Error; + +use minifb::{Key, MouseButton, MouseMode, Window, WindowOptions}; + +use crate::{ + controller::{mouse_click_recorder::MouseClickRecorder, user_input::pick_option}, + model::{ + complex_plane::{ComplexPlane, View}, + pixel_buffer::PixelBuffer, + pixel_plane::PixelPlane, + rendering::{self, render, set_view}, + }, + view::{ + coloring::TrueColor, + terminal::{print_banner, print_command_info}, + }, + MandelbrotModel, COLORING_FUNCTION, COLOR_CHANNEL_MAPPING, VERSION, VIEW_0, VIEW_1, VIEW_2, VIEW_3, VIEW_4, VIEW_5, VIEW_6, VIEW_7, + VIEW_8, VIEW_9, +}; + +use super::{key_bindings::KeyBindings, user_input::ask}; + +// Handle any key events +pub fn handle_key_events(window: &Window, k: &KeyBindings) { + if let Some(key) = window.get_keys_pressed(minifb::KeyRepeat::No).first() { + print!("\nKey pressed: "); + k.print_key(key); + k.run(key); + let mut mandelbrot_model = MandelbrotModel::get_instance(); + match key { + Key::K => k.print(), + Key::A => { + mandelbrot_model.coloring_function = pick_option(&[ + ("HSV", TrueColor::new_from_hsv_colors), + ("Bernstein polynomials", TrueColor::new_from_bernstein_polynomials), + ]); + render(&mut mandelbrot_model); + } + _ => (), + } + } +} + +pub fn handle_left_mouse_clicked(mandelbrot_model: &MandelbrotModel, x: f32, y: f32) { + println!("\nMouseButton::Left -> Info at ({x}, {y})"); + //let iterations = MandelbrotModel::get_instance().p.iterations_at_point(x as usize, y as usize, MandelbrotModel::get_instance().m.max_iterations); //TODO: fix this + let complex = mandelbrot_model.c.complex_from_pixel_plane(x.into(), y.into()); + println!("Complex: {:?}", complex); + //println!("iterations: {}", iterations); + println!(); +} + +pub fn handle_right_mouse_clicked(mandelbrot_model: &mut MandelbrotModel, x: f32, y: f32) { + println!("\nMouseButton::Right -> Move to ({x}, {y})"); + let new_center = mandelbrot_model.c.complex_from_pixel_plane(x.into(), y.into()); + println!("mandelbrot_model.c.center: {:?}", mandelbrot_model.c.center()); + println!("new_center: {:?}", new_center); + + rendering::translate_to_center_and_render_efficiently(mandelbrot_model, &new_center); + mandelbrot_model.c.print(); + println!(); +} + +pub fn handle_mouse_events(window: &Window) { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + static LEFT_MOUSE_RECORDER: MouseClickRecorder = MouseClickRecorder::new(MouseButton::Left); //Static variable with interior mutability to toggle mouse clicks; without such a variable, clicking the screen once would result in multiple actions + static RIGHT_MOUSE_RECORDER: MouseClickRecorder = MouseClickRecorder::new(MouseButton::Right); + + if let Some((x, y)) = window.get_mouse_pos(MouseMode::Discard) { + //Left mouse actions + if LEFT_MOUSE_RECORDER.was_clicked(window) { + handle_left_mouse_clicked(&mandelbrot_model, x, y); + } + + //Right mouse actions + if RIGHT_MOUSE_RECORDER.was_clicked(window) { + handle_right_mouse_clicked(&mut mandelbrot_model, x, y); + } + } +} + +pub fn initialize_keybindings(key_bindings: &mut KeyBindings) { + let empty_closure = || (); + key_bindings.add(Key::Up, "Move up translation_amount pixels", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + let rows = mandelbrot_model.vars.translation_amount; + rendering::translate_and_render_efficiently(&mut mandelbrot_model, rows.into(), 0); + mandelbrot_model.c.print(); + }); + key_bindings.add(Key::Down, "Move down translation_amount pixels", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + let rows = -i16::from(mandelbrot_model.vars.translation_amount); + rendering::translate_and_render_efficiently(&mut mandelbrot_model, rows, 0); + mandelbrot_model.c.print(); + }); + key_bindings.add(Key::Left, "Move left translation_amount pixels", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + let columns = -i16::from(mandelbrot_model.vars.translation_amount); + rendering::translate_and_render_efficiently(&mut mandelbrot_model, 0, columns); + mandelbrot_model.c.print(); + }); + key_bindings.add(Key::Right, "Move right translation_amount pixels", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + let columns = mandelbrot_model.vars.translation_amount.into(); + rendering::translate_and_render_efficiently(&mut mandelbrot_model, 0, columns); + mandelbrot_model.c.print(); + }); + key_bindings.add(Key::R, "Reset the Mandelbrot set view to the starting view", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + mandelbrot_model.c.reset(); + render(&mut mandelbrot_model); + }); + key_bindings.add(Key::NumPadPlus, "Increment translation_amount", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + mandelbrot_model.vars.increment_translation_amount(); + println!("translation_amount: {}", mandelbrot_model.vars.translation_amount); + }); + + key_bindings.add(Key::NumPadMinus, "Decrement translation amount", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + mandelbrot_model.vars.decrement_translation_amount(); + println!("translation_amount: {}", mandelbrot_model.vars.translation_amount); + }); + key_bindings.add(Key::NumPadAsterisk, "Increment scale_numerator", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + mandelbrot_model.vars.increment_scale_numerator(); + println!( + "scale factor: {}/{}", + mandelbrot_model.vars.scale_numerator, mandelbrot_model.vars.scale_denominator + ); + }); + key_bindings.add(Key::NumPadSlash, "Decrement scale_numerator", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + mandelbrot_model.vars.decrement_scale_numerator(); + println!( + "scale factor: {}/{}", + mandelbrot_model.vars.scale_numerator, mandelbrot_model.vars.scale_denominator + ); + }); + key_bindings.add(Key::LeftBracket, "Scale the view by scaling_factor, effectively zooming in", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + let scaling_factor = mandelbrot_model.vars.scaling_factor(); + mandelbrot_model.c.scale(scaling_factor); + render(&mut mandelbrot_model); + }); + key_bindings.add( + Key::RightBracket, + "Scale the view by inverse_scaling_factor, effectively zooming out", + || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + let inverse_scaling_factor = mandelbrot_model.vars.inverse_scaling_factor(); + mandelbrot_model.c.scale(inverse_scaling_factor); + render(&mut mandelbrot_model); + }, + ); + key_bindings.add(Key::V, "Prints the current Mandelbrot set view; the center and scale", || { + let mandelbrot_model = MandelbrotModel::get_instance(); + println!( + "Center: {:?}, scale: {:?}", + mandelbrot_model.c.center(), + mandelbrot_model.c.get_scale() + ); + }); + key_bindings.add(Key::Key1, "Renders VIEW_1", || { + set_view(&mut MandelbrotModel::get_instance(), &VIEW_1) + }); + key_bindings.add(Key::Key2, "Renders VIEW_2", || { + set_view(&mut MandelbrotModel::get_instance(), &VIEW_2) + }); + key_bindings.add(Key::Key3, "Renders VIEW_3", || { + set_view(&mut MandelbrotModel::get_instance(), &VIEW_3) + }); + key_bindings.add(Key::Key4, "Renders VIEW_4", || { + set_view(&mut MandelbrotModel::get_instance(), &VIEW_4) + }); + key_bindings.add(Key::Key5, "Renders VIEW_5", || { + set_view(&mut MandelbrotModel::get_instance(), &VIEW_5) + }); + key_bindings.add(Key::Key6, "Renders VIEW_6", || { + set_view(&mut MandelbrotModel::get_instance(), &VIEW_6) + }); + key_bindings.add(Key::Key7, "Renders VIEW_7", || { + set_view(&mut MandelbrotModel::get_instance(), &VIEW_7) + }); + key_bindings.add(Key::Key8, "Renders VIEW_8", || { + set_view(&mut MandelbrotModel::get_instance(), &VIEW_8) + }); + key_bindings.add(Key::Key9, "Renders VIEW_9", || { + set_view(&mut MandelbrotModel::get_instance(), &VIEW_9) + }); + key_bindings.add(Key::Key0, "Renders VIEW_0", || { + set_view(&mut MandelbrotModel::get_instance(), &VIEW_0) + }); + key_bindings.add(Key::K, "Prints the keybindings", empty_closure); + key_bindings.add( + Key::S, + "Saves the current Mandelbrot set view as an image in the saved folder", + || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + let time_stamp = chrono::Utc::now().to_string(); + if mandelbrot_model.config.window_scale == 1.0 { + mandelbrot_model.p.save_as_png( + &time_stamp, + &mandelbrot_model.c.get_view(), + &mandelbrot_model.m, + mandelbrot_model.config.supersampling_amount, + ); + } else { + let mut image_p: PixelBuffer = PixelBuffer::new(PixelPlane::new( + mandelbrot_model.config.image_width, + mandelbrot_model.config.image_height, + )); + let mut image_c: ComplexPlane = + ComplexPlane::new(mandelbrot_model.config.image_width, mandelbrot_model.config.image_height); + image_p.color_channel_mapping = mandelbrot_model.p.color_channel_mapping; + image_c.set_view(&mandelbrot_model.c.get_view()); + rendering::render_complex_plane_into_buffer(&mut mandelbrot_model); + image_p.save_as_png( + &time_stamp, + &mandelbrot_model.c.get_view(), + &mandelbrot_model.m, + mandelbrot_model.config.supersampling_amount, + ); + } + }, + ); + key_bindings.add(Key::I, "Manually input a Mandelbrot set view", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + mandelbrot_model.c.set_view(&View::new(ask("x"), ask("y"), ask("scale"))); + render(&mut mandelbrot_model); + }); + key_bindings.add(Key::A, "Pick an algorithm to color the Mandelbrot set view", empty_closure); + key_bindings.add(Key::M, "Change the Mandelbrot set view max_iterations", || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + mandelbrot_model.m.max_iterations = ask("max_iterations"); + render(&mut mandelbrot_model); + }); + key_bindings.add( + Key::O, + "Change the Mandelbrot set view color channel mapping, xyz -> RGB, where x,y,z ∈ {{'R','G','B'}} (case-insensitive)", + || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + mandelbrot_model.p.color_channel_mapping = ask("color_channel_mapping"); + render(&mut mandelbrot_model); + }, + ); + key_bindings.add( + Key::Q, + "Change the window and image quality of the Mandelbrot set rendering by setting the SSAA multiplier, clamped from 1x to 64x", + || { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + mandelbrot_model.config.supersampling_amount = ask::("supersampling_amount").clamp(1, 64); + render(&mut mandelbrot_model); + }, + ); + key_bindings.add( + Key::X, + "Change the image quality of the Mandelbrot set rendering by setting the SSAA multiplier, clamped from 1x to 64x", + empty_closure, + ); + key_bindings.add(Key::C, "Prints the configuration variables", || { + println!("{:?}", MandelbrotModel::get_instance().config); + }); +} + +pub fn run() -> Result<(), Box> { + let mut mandelbrot_model = MandelbrotModel::get_instance(); + //Coloring function + mandelbrot_model.coloring_function = COLORING_FUNCTION; + //Color channel mapping + mandelbrot_model.p.color_channel_mapping = COLOR_CHANNEL_MAPPING; + // Create a new window + let mut window = Window::new( + "Mandelbrot set viewer", + mandelbrot_model.config.window_width, + mandelbrot_model.config.window_height, + WindowOptions::default(), + ) + .unwrap_or_else(|e| { + panic!("{}", e); + }); + //Print the banner + print_banner(VERSION); + //Print command info + print_command_info(); + //Initialize keybindings + let mut key_bindings: KeyBindings = KeyBindings::new(Vec::new()); //TODO: Make KeyBindings a singleton + initialize_keybindings(&mut key_bindings); + key_bindings.print(); + + mandelbrot_model.p.pixel_plane.print(); + mandelbrot_model.c.print(); + println!( + "Mandelbrot set parameters: max. iterations is {} and orbit radius is {}", + mandelbrot_model.config.max_iterations, mandelbrot_model.config.orbit_radius + ); + println!( + "Amount of CPU threads that will be used for rendering: {}", + mandelbrot_model.amount_of_threads + ); + println!( + "Supersampling amount used for rendering: {}x", + mandelbrot_model.config.supersampling_amount + ); + println!(); + + println!("Rendering Mandelbrot set default view"); + rendering::render_complex_plane_into_buffer(&mut mandelbrot_model); + drop(mandelbrot_model); + + // Main loop + while window.is_open() && !window.is_key_down(Key::Escape) { + // Handle any window events + handle_key_events(&window, &key_bindings); + + //Handle any mouse events + handle_mouse_events(&window); + + let mandelbrot_model = MandelbrotModel::get_instance(); + // Update the window with the new buffer + window + .update_with_buffer( + &mandelbrot_model.p.pixels, + mandelbrot_model.config.window_width, + mandelbrot_model.config.window_height, + ) + .unwrap(); + } + if window.is_key_down(Key::Escape) { + println!("Escape pressed, application exited gracefully."); + } else { + println!("Window closed, application exited gracefully.") + } + Ok(()) +} diff --git a/src/controller/mod.rs b/src/controller/mod.rs index 373ace4..1737df0 100644 --- a/src/controller/mod.rs +++ b/src/controller/mod.rs @@ -1,3 +1,6 @@ pub mod config; +pub mod interaction_variables; pub mod key_bindings; +pub mod minifb_controller; +pub mod mouse_click_recorder; pub mod user_input; diff --git a/src/controller/mouse_click_recorder.rs b/src/controller/mouse_click_recorder.rs new file mode 100644 index 0000000..8757051 --- /dev/null +++ b/src/controller/mouse_click_recorder.rs @@ -0,0 +1,34 @@ +use std::sync::atomic::{AtomicBool, Ordering}; + +use minifb::{MouseButton, Window}; + +fn was_clicked(current: bool, previous: bool) -> bool { + current && !previous +} + +/////Mouse click recorder with interior mutability to toggle mouse clicks; +/// without such a (static function) variable, clicking the screen once would result in multiple actions +pub struct MouseClickRecorder { + mouse_button: MouseButton, + previous: AtomicBool, +} + +impl MouseClickRecorder { + pub const fn new(mouse_button: MouseButton) -> MouseClickRecorder { + MouseClickRecorder { + mouse_button, + previous: AtomicBool::new(false), + } + } + + ///Returns whether the `mouse_button` was clicked once + pub fn was_clicked(&self, window: &Window) -> bool { + let current = window.get_mouse_down(self.mouse_button); + let previous = self.previous.load(Ordering::Relaxed); + let result = was_clicked(current, previous); + if current != previous { + self.previous.store(current, Ordering::Relaxed) + } + result + } +} diff --git a/src/lib.rs b/src/lib.rs index 14b1537..6319ca9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,26 +25,22 @@ clippy::many_single_char_names, clippy::cast_sign_loss )] +#![forbid(unsafe_code)] pub mod controller; pub mod model; pub mod view; use std::error::Error; -use std::sync::atomic::{AtomicBool, Ordering}; -use minifb::{Key, MouseButton, MouseMode, Window, WindowOptions}; +use controller::minifb_controller; //Crate includes -pub use controller::config::Config; -use controller::key_bindings::KeyBindings; -use controller::user_input::{ask, pick_option}; -use model::complex_plane::{ComplexPlane, View}; +use controller::config::Config; + +use model::complex_plane::View; use model::mandelbrot_model::ColoringFunction; pub use model::mandelbrot_model::MandelbrotModel; -use model::{pixel_buffer, pixel_plane, rendering}; -use pixel_buffer::PixelBuffer; -use pixel_plane::PixelPlane; use view::coloring::ColorChannelMapping; use view::coloring::TrueColor; @@ -69,195 +65,6 @@ static VIEW_0: View = View::new(-0.437520465811966, 0.5632133750000006, 0.000004 //Banner values static VERSION: &str = "1.4"; -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, - } - } -} - -// Handle any key events -fn handle_key_events(window: &Window, k: &KeyBindings) { - if let Some(key) = window.get_keys_pressed(minifb::KeyRepeat::No).first() { - print!("\nKey pressed: "); - k.print_key(key); - k.run(key); - let mut mandelbrot_model = MandelbrotModel::get_instance(); - match key { - Key::K => k.print(), - Key::A => { - mandelbrot_model.coloring_function = pick_option(&[ - ("HSV", TrueColor::new_from_hsv_colors), - ("Bernstein polynomials", TrueColor::new_from_bernstein_polynomials), - ]); - render(&mut mandelbrot_model); - } - _ => (), - } - } -} - -fn was_clicked(current: bool, previous: bool) -> bool { - current && !previous -} - -fn handle_left_mouse_clicked(mandelbrot_model: &MandelbrotModel, x: f32, y: f32) { - println!("\nMouseButton::Left -> Info at ({x}, {y})"); - //let iterations = MandelbrotModel::get_instance().p.iterations_at_point(x as usize, y as usize, MandelbrotModel::get_instance().m.max_iterations); //TODO: fix this - let complex = mandelbrot_model.c.complex_from_pixel_plane(x.into(), y.into()); - println!("Complex: {:?}", complex); - //println!("iterations: {}", iterations); - println!(); -} - -fn handle_right_mouse_clicked(mandelbrot_model: &mut MandelbrotModel, x: f32, y: f32) { - println!("\nMouseButton::Right -> Move to ({x}, {y})"); - let new_center = mandelbrot_model.c.complex_from_pixel_plane(x.into(), y.into()); - println!("mandelbrot_model.c.center: {:?}", mandelbrot_model.c.center()); - println!("new_center: {:?}", new_center); - - rendering::translate_to_center_and_render_efficiently(mandelbrot_model, &new_center); - mandelbrot_model.c.print(); - println!(); -} - -/////Mouse click recorder with interior mutability to toggle mouse clicks; -/// without such a (static function) variable, clicking the screen once would result in multiple actions -struct MouseClickRecorder { - mouse_button: MouseButton, - previous: AtomicBool, -} - -impl MouseClickRecorder { - pub const fn new(mouse_button: MouseButton) -> MouseClickRecorder { - MouseClickRecorder { - mouse_button, - previous: AtomicBool::new(false), - } - } - - ///Returns whether the `mouse_button` was clicked once - pub fn was_clicked(&self, window: &Window) -> bool { - let current = window.get_mouse_down(self.mouse_button); - let previous = self.previous.load(Ordering::Relaxed); - let result = was_clicked(current, previous); - if current != previous { - self.previous.store(current, Ordering::Relaxed) - } - result - } -} - -fn handle_mouse_events(window: &Window) { - let mut mandelbrot_model = MandelbrotModel::get_instance(); - static LEFT_MOUSE_RECORDER: MouseClickRecorder = MouseClickRecorder::new(MouseButton::Left); //Static variable with interior mutability to toggle mouse clicks; without such a variable, clicking the screen once would result in multiple actions - static RIGHT_MOUSE_RECORDER: MouseClickRecorder = MouseClickRecorder::new(MouseButton::Right); - - if let Some((x, y)) = window.get_mouse_pos(MouseMode::Discard) { - //Left mouse actions - if LEFT_MOUSE_RECORDER.was_clicked(window) { - handle_left_mouse_clicked(&mandelbrot_model, x, y); - } - - //Right mouse actions - if RIGHT_MOUSE_RECORDER.was_clicked(window) { - handle_right_mouse_clicked(&mut mandelbrot_model, x, y); - } - } -} - -fn render(mandelbrot_model: &mut MandelbrotModel) { - //TODO: Observer pattern view -> model to update the view, instead of rendering manually - rendering::render_complex_plane_into_buffer(mandelbrot_model); - mandelbrot_model.c.print(); -} - -fn set_view(mandelbrot_model: &mut MandelbrotModel, view: &View) { - mandelbrot_model.c.set_view(view); - render(mandelbrot_model); -} - -///Prints Mandelbrot ASCII art :)
-///Prints the `application_banner`, `author_banner`, and `version` -fn print_banner() { - //Made using: https://patorjk.com/software/taag/#MandelbrotModel::get_instance().p=display&f=Big&t=Mandelbrot - let application_banner = r" -__ __ _ _ _ _ -| \/ | | | | | | | | -| \ / | __ _ _ __ __| | ___| | |__ _ __ ___ | |_ -| |\/| |/ _` | '_ \ / _` |/ _ \ | '_ \| '__/ _ \| __| -| | | | (_| | | | | (_| | __/ | |_) | | | (_) | |_ -|_| |_|\__,_|_| |_|\__,_|\___|_|_.__/|_| \___/ \__|"; - //Made using: https://patorjk.com/software/taag/#MandelbrotModel::get_instance().p=display&f=Small%20Slant&t=by%20Jort - let author_banner = r" - __ __ __ - / / __ __ __ / /__ ____/ /_ - / _ \/ // / / // / _ \/ __/ __/ -/_.__/\_, / \___/\___/_/ \__/ - /___/ "; - let version = VERSION; - println!("{}{}v{}\n\n", application_banner, author_banner, version); -} - -///Prints a command info tip for the users benefit -fn print_command_info() { - let tip = "Run Mandelbrot using:"; - let command = "cargo run --release -- "; - let command_info = "where means substitute with the value of arg\nuse '-' to use the default value of arg"; - println!("{}\n\t{}\n{}\n", tip, command, command_info); -} - ///Holds all the logic currently in the main function that isn't involved with setting up configuration or handling errors, to make `main` concise and ///easy to verify by inspection /// # Panics @@ -265,252 +72,6 @@ fn print_command_info() { /// # Errors /// Currently does not return any Errors pub fn run() -> Result<(), Box> { - let mut mandelbrot_model = MandelbrotModel::get_instance(); - //Coloring function - mandelbrot_model.coloring_function = COLORING_FUNCTION; - //Color channel mapping - mandelbrot_model.p.color_channel_mapping = COLOR_CHANNEL_MAPPING; - // Create a new window - let mut window = Window::new( - "Mandelbrot set viewer", - mandelbrot_model.config.window_width, - mandelbrot_model.config.window_height, - WindowOptions::default(), - ) - .unwrap_or_else(|e| { - panic!("{}", e); - }); - //Print the banner - print_banner(); - //Print command info - print_command_info(); - //Initialize keybindings - let mut key_bindings: KeyBindings = KeyBindings::new(Vec::new()); //TODO: Make KeyBindings a singleton - let empty_closure = || (); - key_bindings.add(Key::Up, "Move up translation_amount pixels", || { - let mut mandelbrot_model = MandelbrotModel::get_instance(); - let rows = mandelbrot_model.vars.translation_amount; - rendering::translate_and_render_efficiently(&mut mandelbrot_model, rows.into(), 0); - mandelbrot_model.c.print(); - }); - key_bindings.add(Key::Down, "Move down translation_amount pixels", || { - let mut mandelbrot_model = MandelbrotModel::get_instance(); - let rows = -i16::from(mandelbrot_model.vars.translation_amount); - rendering::translate_and_render_efficiently(&mut mandelbrot_model, rows, 0); - mandelbrot_model.c.print(); - }); - key_bindings.add(Key::Left, "Move left translation_amount pixels", || { - let mut mandelbrot_model = MandelbrotModel::get_instance(); - let columns = -i16::from(mandelbrot_model.vars.translation_amount); - rendering::translate_and_render_efficiently(&mut mandelbrot_model, 0, columns); - mandelbrot_model.c.print(); - }); - key_bindings.add(Key::Right, "Move right translation_amount pixels", || { - let mut mandelbrot_model = MandelbrotModel::get_instance(); - let columns = mandelbrot_model.vars.translation_amount.into(); - rendering::translate_and_render_efficiently(&mut mandelbrot_model, 0, columns); - mandelbrot_model.c.print(); - }); - key_bindings.add(Key::R, "Reset the Mandelbrot set view to the starting view", || { - let mut mandelbrot_model = MandelbrotModel::get_instance(); - mandelbrot_model.c.reset(); - render(&mut mandelbrot_model); - }); - key_bindings.add(Key::NumPadPlus, "Increment translation_amount", || { - let mut mandelbrot_model = MandelbrotModel::get_instance(); - mandelbrot_model.vars.increment_translation_amount(); - println!("translation_amount: {}", mandelbrot_model.vars.translation_amount); - }); - - key_bindings.add(Key::NumPadMinus, "Decrement translation amount", || { - let mut mandelbrot_model = MandelbrotModel::get_instance(); - mandelbrot_model.vars.decrement_translation_amount(); - println!("translation_amount: {}", mandelbrot_model.vars.translation_amount); - }); - key_bindings.add(Key::NumPadAsterisk, "Increment scale_numerator", || { - let mut mandelbrot_model = MandelbrotModel::get_instance(); - mandelbrot_model.vars.increment_scale_numerator(); - println!( - "scale factor: {}/{}", - mandelbrot_model.vars.scale_numerator, mandelbrot_model.vars.scale_denominator - ); - }); - key_bindings.add(Key::NumPadSlash, "Decrement scale_numerator", || { - let mut mandelbrot_model = MandelbrotModel::get_instance(); - mandelbrot_model.vars.decrement_scale_numerator(); - println!( - "scale factor: {}/{}", - mandelbrot_model.vars.scale_numerator, mandelbrot_model.vars.scale_denominator - ); - }); - key_bindings.add(Key::LeftBracket, "Scale the view by scaling_factor, effectively zooming in", || { - let mut mandelbrot_model = MandelbrotModel::get_instance(); - let scaling_factor = mandelbrot_model.vars.scaling_factor(); - mandelbrot_model.c.scale(scaling_factor); - render(&mut mandelbrot_model); - }); - key_bindings.add( - Key::RightBracket, - "Scale the view by inverse_scaling_factor, effectively zooming out", - || { - let mut mandelbrot_model = MandelbrotModel::get_instance(); - let inverse_scaling_factor = mandelbrot_model.vars.inverse_scaling_factor(); - mandelbrot_model.c.scale(inverse_scaling_factor); - render(&mut mandelbrot_model); - }, - ); - key_bindings.add(Key::V, "Prints the current Mandelbrot set view; the center and scale", || { - let mandelbrot_model = MandelbrotModel::get_instance(); - println!( - "Center: {:?}, scale: {:?}", - mandelbrot_model.c.center(), - mandelbrot_model.c.get_scale() - ); - }); - key_bindings.add(Key::Key1, "Renders VIEW_1", || { - set_view(&mut MandelbrotModel::get_instance(), &VIEW_1) - }); - key_bindings.add(Key::Key2, "Renders VIEW_2", || { - set_view(&mut MandelbrotModel::get_instance(), &VIEW_2) - }); - key_bindings.add(Key::Key3, "Renders VIEW_3", || { - set_view(&mut MandelbrotModel::get_instance(), &VIEW_3) - }); - key_bindings.add(Key::Key4, "Renders VIEW_4", || { - set_view(&mut MandelbrotModel::get_instance(), &VIEW_4) - }); - key_bindings.add(Key::Key5, "Renders VIEW_5", || { - set_view(&mut MandelbrotModel::get_instance(), &VIEW_5) - }); - key_bindings.add(Key::Key6, "Renders VIEW_6", || { - set_view(&mut MandelbrotModel::get_instance(), &VIEW_6) - }); - key_bindings.add(Key::Key7, "Renders VIEW_7", || { - set_view(&mut MandelbrotModel::get_instance(), &VIEW_7) - }); - key_bindings.add(Key::Key8, "Renders VIEW_8", || { - set_view(&mut MandelbrotModel::get_instance(), &VIEW_8) - }); - key_bindings.add(Key::Key9, "Renders VIEW_9", || { - set_view(&mut MandelbrotModel::get_instance(), &VIEW_9) - }); - key_bindings.add(Key::Key0, "Renders VIEW_0", || { - set_view(&mut MandelbrotModel::get_instance(), &VIEW_0) - }); - key_bindings.add(Key::K, "Prints the keybindings", empty_closure); - key_bindings.add( - Key::S, - "Saves the current Mandelbrot set view as an image in the saved folder", - || { - let mut mandelbrot_model = MandelbrotModel::get_instance(); - let time_stamp = chrono::Utc::now().to_string(); - if mandelbrot_model.config.window_scale == 1.0 { - mandelbrot_model.p.save_as_png( - &time_stamp, - &mandelbrot_model.c.get_view(), - &mandelbrot_model.m, - mandelbrot_model.config.supersampling_amount, - ); - } else { - let mut image_p: PixelBuffer = PixelBuffer::new(PixelPlane::new( - mandelbrot_model.config.image_width, - mandelbrot_model.config.image_height, - )); - let mut image_c: ComplexPlane = - ComplexPlane::new(mandelbrot_model.config.image_width, mandelbrot_model.config.image_height); - image_p.color_channel_mapping = mandelbrot_model.p.color_channel_mapping; - image_c.set_view(&mandelbrot_model.c.get_view()); - rendering::render_complex_plane_into_buffer(&mut mandelbrot_model); - image_p.save_as_png( - &time_stamp, - &mandelbrot_model.c.get_view(), - &mandelbrot_model.m, - mandelbrot_model.config.supersampling_amount, - ); - } - }, - ); - key_bindings.add(Key::I, "Manually input a Mandelbrot set view", || { - let mut mandelbrot_model = MandelbrotModel::get_instance(); - mandelbrot_model.c.set_view(&View::new(ask("x"), ask("y"), ask("scale"))); - render(&mut mandelbrot_model); - }); - key_bindings.add(Key::A, "Pick an algorithm to color the Mandelbrot set view", empty_closure); - key_bindings.add(Key::M, "Change the Mandelbrot set view max_iterations", || { - let mut mandelbrot_model = MandelbrotModel::get_instance(); - mandelbrot_model.m.max_iterations = ask("max_iterations"); - render(&mut mandelbrot_model); - }); - key_bindings.add( - Key::O, - "Change the Mandelbrot set view color channel mapping, xyz -> RGB, where x,y,z ∈ {{'R','G','B'}} (case-insensitive)", - || { - let mut mandelbrot_model = MandelbrotModel::get_instance(); - mandelbrot_model.p.color_channel_mapping = ask("color_channel_mapping"); - render(&mut mandelbrot_model); - }, - ); - key_bindings.add( - Key::Q, - "Change the window and image quality of the Mandelbrot set rendering by setting the SSAA multiplier, clamped from 1x to 64x", - || { - let mut mandelbrot_model = MandelbrotModel::get_instance(); - mandelbrot_model.config.supersampling_amount = ask::("supersampling_amount").clamp(1, 64); - render(&mut mandelbrot_model); - }, - ); - key_bindings.add( - Key::X, - "Change the image quality of the Mandelbrot set rendering by setting the SSAA multiplier, clamped from 1x to 64x", - empty_closure, - ); - key_bindings.add(Key::C, "Prints the configuration variables", || { - println!("{:?}", MandelbrotModel::get_instance().config); - }); - key_bindings.print(); - - mandelbrot_model.p.pixel_plane.print(); - mandelbrot_model.c.print(); - println!( - "Mandelbrot set parameters: max. iterations is {} and orbit radius is {}", - mandelbrot_model.config.max_iterations, mandelbrot_model.config.orbit_radius - ); - println!( - "Amount of CPU threads that will be used for rendering: {}", - mandelbrot_model.amount_of_threads - ); - println!( - "Supersampling amount used for rendering: {}x", - mandelbrot_model.config.supersampling_amount - ); - println!(); - - println!("Rendering Mandelbrot set default view"); - rendering::render_complex_plane_into_buffer(&mut mandelbrot_model); - drop(mandelbrot_model); - - // Main loop - while window.is_open() && !window.is_key_down(Key::Escape) { - // Handle any window events - handle_key_events(&window, &key_bindings); - - //Handle any mouse events - handle_mouse_events(&window); - - let mandelbrot_model = MandelbrotModel::get_instance(); - // Update the window with the new buffer - window - .update_with_buffer( - &mandelbrot_model.p.pixels, - mandelbrot_model.config.window_width, - mandelbrot_model.config.window_height, - ) - .unwrap(); - } - if window.is_key_down(Key::Escape) { - println!("Escape pressed, application exited gracefully."); - } else { - println!("Window closed, application exited gracefully.") - } + minifb_controller::run()?; Ok(()) } diff --git a/src/main.rs b/src/main.rs index 05747ba..76e7c17 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ fn main() { //Turn on Rust backtrace env::set_var("RUST_BACKTRACE", "1"); + //Run the application if let Err(err) = mandelbrot::run() { eprintln!("Application error: {}", err); process::exit(1); diff --git a/src/model/mandelbrot_model.rs b/src/model/mandelbrot_model.rs index ebc5c9c..705b3a7 100644 --- a/src/model/mandelbrot_model.rs +++ b/src/model/mandelbrot_model.rs @@ -4,7 +4,7 @@ use std::{ sync::{Mutex, MutexGuard}, }; -use crate::{view::coloring::TrueColor, Config, InteractionVariables}; +use crate::{controller::interaction_variables::InteractionVariables, view::coloring::TrueColor, Config}; use super::{complex_plane::ComplexPlane, mandelbrot_function::MandelbrotFunction, pixel_buffer::PixelBuffer, pixel_plane::PixelPlane}; @@ -47,6 +47,6 @@ impl MandelbrotModel { if let Ok(instance) = lock { return instance; } - panic!("You have called the singleton twice! This should never happen. It means that within the same scope, MandelbrotModel::get_instance() was called more than once."); + panic!("[DEADLOCK]: You have called the singleton twice! This should never happen. It means that within the same scope, MandelbrotModel::get_instance() was called more than once."); } } diff --git a/src/model/rendering.rs b/src/model/rendering.rs index 9eabb57..9f57974 100644 --- a/src/model/rendering.rs +++ b/src/model/rendering.rs @@ -14,6 +14,8 @@ use rand::Rng; use crate::view::coloring::TrueColor; use crate::{model::complex::Complex, MandelbrotModel}; +use super::complex_plane::View; + ///A box representing the area to render by rendering functions #[derive(Clone, Copy)] pub struct RenderBox { @@ -250,3 +252,14 @@ fn print_progress_bar(current_progress: u8, max_progress: u8) { print!("]"); io::stdout().flush().unwrap(); } + +pub fn render(mandelbrot_model: &mut MandelbrotModel) { + //TODO: Observer pattern view -> model to update the view, instead of rendering manually + render_complex_plane_into_buffer(mandelbrot_model); + mandelbrot_model.c.print(); +} + +pub fn set_view(mandelbrot_model: &mut MandelbrotModel, view: &View) { + mandelbrot_model.c.set_view(view); + render(mandelbrot_model); +} diff --git a/src/view/mod.rs b/src/view/mod.rs index e045169..f3f265a 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -1 +1,2 @@ pub mod coloring; +pub mod terminal; diff --git a/src/view/terminal.rs b/src/view/terminal.rs new file mode 100644 index 0000000..65bc2ce --- /dev/null +++ b/src/view/terminal.rs @@ -0,0 +1,28 @@ +///Prints Mandelbrot ASCII art :)
+///Prints the `application_banner`, `author_banner`, and `version` +pub fn print_banner(version: &str) { + //Made using: https://patorjk.com/software/taag/#MandelbrotModel::get_instance().p=display&f=Big&t=Mandelbrot + let application_banner = r" +__ __ _ _ _ _ +| \/ | | | | | | | | +| \ / | __ _ _ __ __| | ___| | |__ _ __ ___ | |_ +| |\/| |/ _` | '_ \ / _` |/ _ \ | '_ \| '__/ _ \| __| +| | | | (_| | | | | (_| | __/ | |_) | | | (_) | |_ +|_| |_|\__,_|_| |_|\__,_|\___|_|_.__/|_| \___/ \__|"; + //Made using: https://patorjk.com/software/taag/#MandelbrotModel::get_instance().p=display&f=Small%20Slant&t=by%20Jort + let author_banner = r" + __ __ __ + / / __ __ __ / /__ ____/ /_ + / _ \/ // / / // / _ \/ __/ __/ +/_.__/\_, / \___/\___/_/ \__/ + /___/ "; + println!("{}{}v{}\n\n", application_banner, author_banner, version); +} + +///Prints a command info tip for the users benefit +pub fn print_command_info() { + let tip = "Run Mandelbrot using:"; + let command = "cargo run --release -- "; + let command_info = "where means substitute with the value of arg\nuse '-' to use the default value of arg"; + println!("{}\n\t{}\n{}\n", tip, command, command_info); +} From df1009efdb44ab7a82366fb3db141d490babf04f Mon Sep 17 00:00:00 2001 From: Jort van Waes Date: Wed, 20 Sep 2023 11:28:45 +0200 Subject: [PATCH 11/14] Changed KeyBindings formatting --- src/controller/key_bindings.rs | 8 +++++++- src/lib.rs | 6 ++---- src/model/rendering.rs | 1 + 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/controller/key_bindings.rs b/src/controller/key_bindings.rs index f10a0bc..ae3150a 100644 --- a/src/controller/key_bindings.rs +++ b/src/controller/key_bindings.rs @@ -1,7 +1,11 @@ use std::fmt; +use chrono::format::format; use minifb::Key; +static KEY_BINDING_DESCRIPTION_OFFSET: usize = 20; +static KEY_BINDING_SEPARATOR: &str = " -> "; + //https://stackoverflow.com/questions/68066875/how-to-store-a-closure-inside-rust-struct //https://stackoverflow.com/questions/65756096/how-can-i-store-a-closure-object-in-a-struct pub struct KeyBinding { @@ -23,7 +27,9 @@ impl KeyBinding { impl fmt::Debug for KeyBinding { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?} -> {}", &self.key, &self.description) + let key = format!("{:?}", &self.key); + let offset = KEY_BINDING_DESCRIPTION_OFFSET - key.len() - KEY_BINDING_SEPARATOR.len(); + write!(f, "{}{}{}{}", key, KEY_BINDING_SEPARATOR, " ".repeat(offset), &self.description) } } diff --git a/src/lib.rs b/src/lib.rs index 6319ca9..92dac2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,14 +33,12 @@ pub mod view; use std::error::Error; -use controller::minifb_controller; - //Crate includes use controller::config::Config; - +use controller::minifb_controller; use model::complex_plane::View; use model::mandelbrot_model::ColoringFunction; -pub use model::mandelbrot_model::MandelbrotModel; +use model::mandelbrot_model::MandelbrotModel; use view::coloring::ColorChannelMapping; use view::coloring::TrueColor; diff --git a/src/model/rendering.rs b/src/model/rendering.rs index 9f57974..23ad265 100644 --- a/src/model/rendering.rs +++ b/src/model/rendering.rs @@ -80,6 +80,7 @@ pub fn render_box_render_complex_plane_into_buffer(mandelbrot_model: &mut Mandel let time = benchmark_start(); let supersampling_amount = mandelbrot_model.config.supersampling_amount.clamp(1, 64); //Supersampling_amount should be at least 1 and atmost 64 render_box.print(); + println!("View: {:?}", mandelbrot_model.c.get_view()); println!("SSAA: {}x", supersampling_amount); let chunk_size = mandelbrot_model.p.pixel_plane.width; let chunks: Vec> = mandelbrot_model.p.colors.chunks(chunk_size).map(ToOwned::to_owned).collect(); From d6702d1ccd2ff6fe7b73fdf2398375ac17ef0bc2 Mon Sep 17 00:00:00 2001 From: Jort van Waes Date: Wed, 20 Sep 2023 12:13:48 +0200 Subject: [PATCH 12/14] Added minifb_mandelbrot_view.rs --- docs/MVC.md | 33 ++++++++ src/controller/minifb_controller.rs | 78 ++++--------------- ...key_bindings.rs => minifb_key_bindings.rs} | 15 +++- ...rder.rs => minifb_mouse_click_recorder.rs} | 0 src/controller/mod.rs | 4 +- src/lib.rs | 19 ++++- src/{view => model}/coloring.rs | 0 src/model/mandelbrot_model.rs | 15 +++- src/model/mod.rs | 1 + src/model/pixel_buffer.rs | 2 +- src/model/rendering.rs | 2 +- src/view/minifb_mandelbrot_view.rs | 59 ++++++++++++++ src/view/mod.rs | 2 +- 13 files changed, 152 insertions(+), 78 deletions(-) create mode 100644 docs/MVC.md rename src/controller/{key_bindings.rs => minifb_key_bindings.rs} (87%) rename src/controller/{mouse_click_recorder.rs => minifb_mouse_click_recorder.rs} (100%) rename src/{view => model}/coloring.rs (100%) create mode 100644 src/view/minifb_mandelbrot_view.rs diff --git a/docs/MVC.md b/docs/MVC.md new file mode 100644 index 0000000..393062e --- /dev/null +++ b/docs/MVC.md @@ -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. \ No newline at end of file diff --git a/src/controller/minifb_controller.rs b/src/controller/minifb_controller.rs index 107697c..5c235fc 100644 --- a/src/controller/minifb_controller.rs +++ b/src/controller/minifb_controller.rs @@ -1,24 +1,21 @@ use std::error::Error; -use minifb::{Key, MouseButton, MouseMode, Window, WindowOptions}; +use minifb::{Key, MouseButton, MouseMode, Window}; use crate::{ - controller::{mouse_click_recorder::MouseClickRecorder, user_input::pick_option}, + controller::{minifb_mouse_click_recorder::MouseClickRecorder, user_input::pick_option}, model::{ + coloring::TrueColor, complex_plane::{ComplexPlane, View}, pixel_buffer::PixelBuffer, pixel_plane::PixelPlane, rendering::{self, render, set_view}, }, - view::{ - coloring::TrueColor, - terminal::{print_banner, print_command_info}, - }, - MandelbrotModel, COLORING_FUNCTION, COLOR_CHANNEL_MAPPING, VERSION, VIEW_0, VIEW_1, VIEW_2, VIEW_3, VIEW_4, VIEW_5, VIEW_6, VIEW_7, - VIEW_8, VIEW_9, + view::minifb_mandelbrot_view::MandelbrotView, + MandelbrotModel, VIEW_0, VIEW_1, VIEW_2, VIEW_3, VIEW_4, VIEW_5, VIEW_6, VIEW_7, VIEW_8, VIEW_9, }; -use super::{key_bindings::KeyBindings, user_input::ask}; +use super::{minifb_key_bindings::KeyBindings, user_input::ask}; // Handle any key events pub fn handle_key_events(window: &Window, k: &KeyBindings) { @@ -263,73 +260,30 @@ pub fn initialize_keybindings(key_bindings: &mut KeyBindings) { }); } -pub fn run() -> Result<(), Box> { +pub fn run(mandelbrot_view: &mut MandelbrotView) -> Result<(), Box> { let mut mandelbrot_model = MandelbrotModel::get_instance(); - //Coloring function - mandelbrot_model.coloring_function = COLORING_FUNCTION; - //Color channel mapping - mandelbrot_model.p.color_channel_mapping = COLOR_CHANNEL_MAPPING; - // Create a new window - let mut window = Window::new( - "Mandelbrot set viewer", - mandelbrot_model.config.window_width, - mandelbrot_model.config.window_height, - WindowOptions::default(), - ) - .unwrap_or_else(|e| { - panic!("{}", e); - }); - //Print the banner - print_banner(VERSION); - //Print command info - print_command_info(); //Initialize keybindings let mut key_bindings: KeyBindings = KeyBindings::new(Vec::new()); //TODO: Make KeyBindings a singleton initialize_keybindings(&mut key_bindings); key_bindings.print(); - mandelbrot_model.p.pixel_plane.print(); - mandelbrot_model.c.print(); - println!( - "Mandelbrot set parameters: max. iterations is {} and orbit radius is {}", - mandelbrot_model.config.max_iterations, mandelbrot_model.config.orbit_radius - ); - println!( - "Amount of CPU threads that will be used for rendering: {}", - mandelbrot_model.amount_of_threads - ); - println!( - "Supersampling amount used for rendering: {}x", - mandelbrot_model.config.supersampling_amount - ); - println!(); - - println!("Rendering Mandelbrot set default view"); - rendering::render_complex_plane_into_buffer(&mut mandelbrot_model); + println!("\nRendering Mandelbrot set starting view"); + render(&mut mandelbrot_model); drop(mandelbrot_model); // Main loop - while window.is_open() && !window.is_key_down(Key::Escape) { + while mandelbrot_view.window.is_open() && !mandelbrot_view.window.is_key_down(Key::Escape) { // Handle any window events - handle_key_events(&window, &key_bindings); + handle_key_events(&mandelbrot_view.window, &key_bindings); //Handle any mouse events - handle_mouse_events(&window); + handle_mouse_events(&mandelbrot_view.window); - let mandelbrot_model = MandelbrotModel::get_instance(); // Update the window with the new buffer - window - .update_with_buffer( - &mandelbrot_model.p.pixels, - mandelbrot_model.config.window_width, - mandelbrot_model.config.window_height, - ) - .unwrap(); - } - if window.is_key_down(Key::Escape) { - println!("Escape pressed, application exited gracefully."); - } else { - println!("Window closed, application exited gracefully.") + let mandelbrot_model = MandelbrotModel::get_instance(); + mandelbrot_view.update(&mandelbrot_model); } + + mandelbrot_view.exit(); Ok(()) } diff --git a/src/controller/key_bindings.rs b/src/controller/minifb_key_bindings.rs similarity index 87% rename from src/controller/key_bindings.rs rename to src/controller/minifb_key_bindings.rs index ae3150a..4f76ec3 100644 --- a/src/controller/key_bindings.rs +++ b/src/controller/minifb_key_bindings.rs @@ -1,6 +1,5 @@ use std::fmt; -use chrono::format::format; use minifb::Key; static KEY_BINDING_DESCRIPTION_OFFSET: usize = 20; @@ -23,6 +22,14 @@ impl KeyBinding { pub fn run(&self) { (self.action)(); } + + pub fn to_formatted_str(&self) -> String { + let mut key_binding_formatted = format!("{:?}", self); + while key_binding_formatted.contains(" ") { + key_binding_formatted = key_binding_formatted.replace(" ", " "); + } + key_binding_formatted + } } impl fmt::Debug for KeyBinding { @@ -67,7 +74,7 @@ impl KeyBindings { pub fn print_key(&self, key: &Key) { for key_action in &self.key_bindings { if key_action.key == *key { - println!("{:?}", key_action); + println!("{}", key_action.to_formatted_str()); return; } } @@ -88,8 +95,8 @@ impl KeyBindings { impl fmt::Debug for KeyBindings { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, "KeyBindings {{")?; - for key_action in &self.key_bindings { - writeln!(f, " {:?},", key_action)?; + for key_binding in &self.key_bindings { + writeln!(f, " {:?},", key_binding)?; } write!(f, "}}") } diff --git a/src/controller/mouse_click_recorder.rs b/src/controller/minifb_mouse_click_recorder.rs similarity index 100% rename from src/controller/mouse_click_recorder.rs rename to src/controller/minifb_mouse_click_recorder.rs diff --git a/src/controller/mod.rs b/src/controller/mod.rs index 1737df0..5192e4a 100644 --- a/src/controller/mod.rs +++ b/src/controller/mod.rs @@ -1,6 +1,6 @@ pub mod config; pub mod interaction_variables; -pub mod key_bindings; pub mod minifb_controller; -pub mod mouse_click_recorder; +pub mod minifb_key_bindings; +pub mod minifb_mouse_click_recorder; pub mod user_input; diff --git a/src/lib.rs b/src/lib.rs index 92dac2a..8be60e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,11 +36,14 @@ use std::error::Error; //Crate includes use controller::config::Config; use controller::minifb_controller; +use model::coloring::ColorChannelMapping; +use model::coloring::TrueColor; use model::complex_plane::View; use model::mandelbrot_model::ColoringFunction; use model::mandelbrot_model::MandelbrotModel; -use view::coloring::ColorChannelMapping; -use view::coloring::TrueColor; +use view::minifb_mandelbrot_view::MandelbrotView; +use view::terminal::print_banner; +use view::terminal::print_command_info; //Coloring function static COLORING_FUNCTION: ColoringFunction = TrueColor::new_from_bernstein_polynomials; @@ -70,6 +73,16 @@ static VERSION: &str = "1.4"; /// # Errors /// Currently does not return any Errors pub fn run() -> Result<(), Box> { - minifb_controller::run()?; + let mandelbrot_model = MandelbrotModel::get_instance(); + + //Print the banner + print_banner(VERSION); + //Print command info + print_command_info(); + //Create the view + let mut mandelbrot_view = MandelbrotView::new(&mandelbrot_model); + //Run the controller + drop(mandelbrot_model); //TODO: Get rid of MandelbrotModel mutex, as this can cause deadlocks + minifb_controller::run(&mut mandelbrot_view)?; Ok(()) } diff --git a/src/view/coloring.rs b/src/model/coloring.rs similarity index 100% rename from src/view/coloring.rs rename to src/model/coloring.rs diff --git a/src/model/mandelbrot_model.rs b/src/model/mandelbrot_model.rs index 705b3a7..f143d71 100644 --- a/src/model/mandelbrot_model.rs +++ b/src/model/mandelbrot_model.rs @@ -4,7 +4,9 @@ use std::{ sync::{Mutex, MutexGuard}, }; -use crate::{controller::interaction_variables::InteractionVariables, view::coloring::TrueColor, Config}; +use crate::{ + controller::interaction_variables::InteractionVariables, model::coloring::TrueColor, Config, COLORING_FUNCTION, COLOR_CHANNEL_MAPPING, +}; use super::{complex_plane::ComplexPlane, mandelbrot_function::MandelbrotFunction, pixel_buffer::PixelBuffer, pixel_plane::PixelPlane}; @@ -30,15 +32,20 @@ impl MandelbrotModel { eprintln!("Problem parsing arguments: {}", err); process::exit(1); }); - MandelbrotModel { + + let mut result = MandelbrotModel { config: config.clone(), c: ComplexPlane::new(config.window_width, config.window_height), p: PixelBuffer::new(PixelPlane::new(config.window_width, config.window_height)), vars: InteractionVariables::default(), amount_of_threads: num_cpus::get(), m: MandelbrotFunction::new(config.max_iterations, config.orbit_radius), - coloring_function: TrueColor::new_from_bernstein_polynomials, - } + coloring_function: COLORING_FUNCTION, + }; + //Color channel mapping + result.p.color_channel_mapping = COLOR_CHANNEL_MAPPING; + + result } /// Returns the singleton MandelbrotModel instance. diff --git a/src/model/mod.rs b/src/model/mod.rs index cba7b63..5046ffc 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,3 +1,4 @@ +pub mod coloring; pub mod complex; pub mod complex_plane; pub mod mandelbrot_function; diff --git a/src/model/pixel_buffer.rs b/src/model/pixel_buffer.rs index 97e1af2..008fe6d 100644 --- a/src/model/pixel_buffer.rs +++ b/src/model/pixel_buffer.rs @@ -1,7 +1,7 @@ use std::{fs::File, io::BufWriter, path::Path}; +use crate::model::coloring::{ColorChannelMapping, TrueColor}; use crate::model::{complex_plane::View, mandelbrot_function::MandelbrotFunction, pixel_plane::PixelPlane}; -use crate::view::coloring::{ColorChannelMapping, TrueColor}; #[derive(Clone)] pub struct PixelBuffer { diff --git a/src/model/rendering.rs b/src/model/rendering.rs index 23ad265..57a2bfc 100644 --- a/src/model/rendering.rs +++ b/src/model/rendering.rs @@ -11,7 +11,7 @@ use std::{ use rand::Rng; -use crate::view::coloring::TrueColor; +use crate::model::coloring::TrueColor; use crate::{model::complex::Complex, MandelbrotModel}; use super::complex_plane::View; diff --git a/src/view/minifb_mandelbrot_view.rs b/src/view/minifb_mandelbrot_view.rs new file mode 100644 index 0000000..6f7d31b --- /dev/null +++ b/src/view/minifb_mandelbrot_view.rs @@ -0,0 +1,59 @@ +use minifb::{Key, Window, WindowOptions}; + +use crate::model::mandelbrot_model::MandelbrotModel; + +pub struct MandelbrotView { + pub window: Window, +} + +impl MandelbrotView { + pub fn new(mandelbrot_model: &MandelbrotModel) -> MandelbrotView { + // Create a new window + let window = Window::new( + "Mandelbrot set viewer", + mandelbrot_model.config.window_width, + mandelbrot_model.config.window_height, + WindowOptions::default(), + ) + .unwrap_or_else(|e| { + panic!("{}", e); + }); + + //Print info about the MandelbrotModel + mandelbrot_model.p.pixel_plane.print(); + mandelbrot_model.c.print(); + println!( + "Mandelbrot set parameters: max. iterations is {} and orbit radius is {}", + mandelbrot_model.config.max_iterations, mandelbrot_model.config.orbit_radius + ); + println!( + "Amount of CPU threads that will be used for rendering: {}", + mandelbrot_model.amount_of_threads + ); + println!( + "Supersampling amount used for rendering: {}x", + mandelbrot_model.config.supersampling_amount + ); + println!(); + + MandelbrotView { window } + } + + pub fn update(&mut self, mandelbrot_model: &MandelbrotModel) { + self.window + .update_with_buffer( + &mandelbrot_model.p.pixels, + mandelbrot_model.config.window_width, + mandelbrot_model.config.window_height, + ) + .unwrap(); + } + + pub fn exit(&self) { + if self.window.is_key_down(Key::Escape) { + println!("Escape pressed, application exited gracefully."); + } else { + println!("Window closed, application exited gracefully.") + } + } +} diff --git a/src/view/mod.rs b/src/view/mod.rs index f3f265a..f76371f 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -1,2 +1,2 @@ -pub mod coloring; +pub mod minifb_mandelbrot_view; pub mod terminal; From 00a7bb7c54d86789f17db97e4005d0830ff36ed5 Mon Sep 17 00:00:00 2001 From: Jort van Waes Date: Wed, 20 Sep 2023 12:38:27 +0200 Subject: [PATCH 13/14] Added view::image::save(). --- src/controller/minifb_controller.rs | 43 ++++++--------------------- src/controller/minifb_key_bindings.rs | 4 +-- src/view/image.rs | 35 ++++++++++++++++++++++ src/view/mod.rs | 1 + 4 files changed, 47 insertions(+), 36 deletions(-) create mode 100644 src/view/image.rs diff --git a/src/controller/minifb_controller.rs b/src/controller/minifb_controller.rs index 5c235fc..da3c8d9 100644 --- a/src/controller/minifb_controller.rs +++ b/src/controller/minifb_controller.rs @@ -6,12 +6,10 @@ use crate::{ controller::{minifb_mouse_click_recorder::MouseClickRecorder, user_input::pick_option}, model::{ coloring::TrueColor, - complex_plane::{ComplexPlane, View}, - pixel_buffer::PixelBuffer, - pixel_plane::PixelPlane, + complex_plane::View, rendering::{self, render, set_view}, }, - view::minifb_mandelbrot_view::MandelbrotView, + view::{image, minifb_mandelbrot_view::MandelbrotView}, MandelbrotModel, VIEW_0, VIEW_1, VIEW_2, VIEW_3, VIEW_4, VIEW_5, VIEW_6, VIEW_7, VIEW_8, VIEW_9, }; @@ -20,8 +18,9 @@ use super::{minifb_key_bindings::KeyBindings, user_input::ask}; // Handle any key events pub fn handle_key_events(window: &Window, k: &KeyBindings) { if let Some(key) = window.get_keys_pressed(minifb::KeyRepeat::No).first() { - print!("\nKey pressed: "); + print!("\n{{Key pressed: "); k.print_key(key); + println!("}}"); k.run(key); let mut mandelbrot_model = MandelbrotModel::get_instance(); match key { @@ -35,11 +34,12 @@ pub fn handle_key_events(window: &Window, k: &KeyBindings) { } _ => (), } + println!(); } } pub fn handle_left_mouse_clicked(mandelbrot_model: &MandelbrotModel, x: f32, y: f32) { - println!("\nMouseButton::Left -> Info at ({x}, {y})"); + println!("\n{{MouseButton::Left -> Info at ({x}, {y})}}"); //let iterations = MandelbrotModel::get_instance().p.iterations_at_point(x as usize, y as usize, MandelbrotModel::get_instance().m.max_iterations); //TODO: fix this let complex = mandelbrot_model.c.complex_from_pixel_plane(x.into(), y.into()); println!("Complex: {:?}", complex); @@ -48,7 +48,7 @@ pub fn handle_left_mouse_clicked(mandelbrot_model: &MandelbrotModel, x: f32, y: } pub fn handle_right_mouse_clicked(mandelbrot_model: &mut MandelbrotModel, x: f32, y: f32) { - println!("\nMouseButton::Right -> Move to ({x}, {y})"); + println!("\n{{MouseButton::Right -> Move to ({x}, {y})}}"); let new_center = mandelbrot_model.c.complex_from_pixel_plane(x.into(), y.into()); println!("mandelbrot_model.c.center: {:?}", mandelbrot_model.c.center()); println!("new_center: {:?}", new_center); @@ -112,7 +112,6 @@ pub fn initialize_keybindings(key_bindings: &mut KeyBindings) { mandelbrot_model.vars.increment_translation_amount(); println!("translation_amount: {}", mandelbrot_model.vars.translation_amount); }); - key_bindings.add(Key::NumPadMinus, "Decrement translation amount", || { let mut mandelbrot_model = MandelbrotModel::get_instance(); mandelbrot_model.vars.decrement_translation_amount(); @@ -193,32 +192,8 @@ pub fn initialize_keybindings(key_bindings: &mut KeyBindings) { Key::S, "Saves the current Mandelbrot set view as an image in the saved folder", || { - let mut mandelbrot_model = MandelbrotModel::get_instance(); - let time_stamp = chrono::Utc::now().to_string(); - if mandelbrot_model.config.window_scale == 1.0 { - mandelbrot_model.p.save_as_png( - &time_stamp, - &mandelbrot_model.c.get_view(), - &mandelbrot_model.m, - mandelbrot_model.config.supersampling_amount, - ); - } else { - let mut image_p: PixelBuffer = PixelBuffer::new(PixelPlane::new( - mandelbrot_model.config.image_width, - mandelbrot_model.config.image_height, - )); - let mut image_c: ComplexPlane = - ComplexPlane::new(mandelbrot_model.config.image_width, mandelbrot_model.config.image_height); - image_p.color_channel_mapping = mandelbrot_model.p.color_channel_mapping; - image_c.set_view(&mandelbrot_model.c.get_view()); - rendering::render_complex_plane_into_buffer(&mut mandelbrot_model); - image_p.save_as_png( - &time_stamp, - &mandelbrot_model.c.get_view(), - &mandelbrot_model.m, - mandelbrot_model.config.supersampling_amount, - ); - } + let mandelbrot_model = MandelbrotModel::get_instance(); + image::save(&mandelbrot_model); }, ); key_bindings.add(Key::I, "Manually input a Mandelbrot set view", || { diff --git a/src/controller/minifb_key_bindings.rs b/src/controller/minifb_key_bindings.rs index 4f76ec3..e47dba5 100644 --- a/src/controller/minifb_key_bindings.rs +++ b/src/controller/minifb_key_bindings.rs @@ -74,12 +74,12 @@ impl KeyBindings { pub fn print_key(&self, key: &Key) { for key_action in &self.key_bindings { if key_action.key == *key { - println!("{}", key_action.to_formatted_str()); + print!("{}", key_action.to_formatted_str()); return; } } //KeyBindings does not contain a KeyBinding x with x.key == key, unbound - println!("{:?}", key); + print!("{:?}", key); } pub fn run(&self, key: &Key) { diff --git a/src/view/image.rs b/src/view/image.rs new file mode 100644 index 0000000..b8c7cbc --- /dev/null +++ b/src/view/image.rs @@ -0,0 +1,35 @@ +use crate::model::mandelbrot_model::MandelbrotModel; + +pub fn save(mandelbrot_model: &MandelbrotModel) { + let time_stamp = chrono::Utc::now().to_string(); + if mandelbrot_model.config.window_scale == 1.0 { + mandelbrot_model.p.save_as_png( + &time_stamp, + &mandelbrot_model.c.get_view(), + &mandelbrot_model.m, + mandelbrot_model.config.supersampling_amount, + ); + } else { + println!( + "[UNSUPPORTED]: There is currently no support for saving images in the window scale is not 1.0. The window scale is {}.", + mandelbrot_model.config.window_scale + ) + } + + /*else { + let mut image_p: PixelBuffer = PixelBuffer::new(PixelPlane::new( + mandelbrot_model.config.image_width, + mandelbrot_model.config.image_height, + )); + let mut image_c: ComplexPlane = ComplexPlane::new(mandelbrot_model.config.image_width, mandelbrot_model.config.image_height); + image_p.color_channel_mapping = mandelbrot_model.p.color_channel_mapping; + image_c.set_view(&mandelbrot_model.c.get_view()); + rendering::render_complex_plane_into_buffer(mandelbrot_model); + image_p.save_as_png( + &time_stamp, + &mandelbrot_model.c.get_view(), + &mandelbrot_model.m, + mandelbrot_model.config.supersampling_amount, + ); + }*/ +} diff --git a/src/view/mod.rs b/src/view/mod.rs index f76371f..6a27261 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -1,2 +1,3 @@ +pub mod image; pub mod minifb_mandelbrot_view; pub mod terminal; From 5d8b35d43c60262ae97dcad47264ce9b36617143 Mon Sep 17 00:00:00 2001 From: Jort van Waes Date: Wed, 20 Sep 2023 13:02:16 +0200 Subject: [PATCH 14/14] Added a program icon. Changed the window title to Mandelbrot by Jort + the version. Release of v1.5 --- icons/rust.ico | Bin 0 -> 67646 bytes src/lib.rs | 9 ++++++--- src/view/minifb_mandelbrot_view.rs | 26 +++++++++++++++++++++----- 3 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 icons/rust.ico diff --git a/icons/rust.ico b/icons/rust.ico new file mode 100644 index 0000000000000000000000000000000000000000..8a8f98d9c594ea6e9a4e397baa045736bc2b09fa GIT binary patch literal 67646 zcmeI437BR@dGEiT3=RWcnPCw@kY*F2QQV@BA}}BU6ctnyS7h^|#w8jr=4!YMNUjt0 zCKH!yjE~+?jKLixYTS@bMG1>&T!R=TfJBWVZm9JA|IRtT>6$a&+Wj@%J?WR{f2!W9 zx88b}syfTpL#H#5|JJVUEIDfr*k*#QZeQT5MSco&WY35e%Ca7CorF1Nu6U5 zDsyBm-=214f75{a-v*E}Wqp@0Uk}v(&D6I>Afyd7H+`iYUFH1Ky-T%05h&wEz2qHh zu8vCsx%qVi>R$-%7C?+u_K4K!|6Nm3zoV(INePvCTOIz2K>fEj^~a}#)LV1)wQX-V z+e`VS`lbBlFSV^Np-JN;%t} z7a0g!JTp#7o%aglwf*l?hxD!HmahFGa8){qA!jr3_KZF^_3Z-5eP(}|C&%07-j(lG zmNLC`n*U2QBIT;!-GOtKHh-~^Te=6-dw_GY+g>m}JEs4~O)Sj^rDrtH;wJ|02<-2c zOWVi=7)BNu3!P3V0_#4jY}v@|Lu@X7w>m8;MvD6VH&19a~tX?K?bv+phl) zPFt^-xASO>d&$pbdpS3CY%Aw0`|p=NNFUq2*S*uRkl#{Db9_)7dTnrIV4Jba>La(70F4cId1UU6(>Al7?(%|9k>#KiAP#wAT#1i9uP5^p@i$<03p z&j@^$Gynf9ZSX(|%*{qrkXOZV7OmlpDl&&e}1>BRn!TvH= z(F{DmRtsB=gEg0(c&O-Z&#>zud5(j-96q8~5(Z_!7rI&BV-wODq%QyXZ1aArc zCU||oZ@;wBcUgceW4f)KL30gjb6d{29-eujy9E7WS_v(Hi)(Su*z%9TCBeGj@xkr^ zKDd@2n>v2?%9Al@$EF*PEIDtF`AOO?3V5G>M)06u=fG!>R0k68X#D*EW6|-N;Io0> zB(=x2(6+(;c8UBT-#H^8Vn*zC3%GA$18ejAVD|v(wLa?JJLhWtKa%P2w>;bVz2)lQ z4Z%x-9|Xv>Gylm4q@+nlWUN*i66fX*q7Scjzya<^X_*6 zWNdHC8>gEh^TvSlk{p)Anl!J}u~9oqxn6bU+j>fQ^gklt9)c~{scogrw&Bn9!FLNe z`%3w4bz(_Oi7ow~2_W}g&CSo%*uO5*V;gqr1ODMRZJ_P1IsSP19vSQwJRo>;fNUvm z{o1sj9NaB{%oTPF+P~F+SzMTlIhf0NQHL!j1>XKz3>}zg*uEYa+Qh;s4?tA6_p1~pG>6-r2w4twz0kJ55M`3Qj*gKn; zlex)(c{n?D^JHHCC2iXUkUmo9KK1QDA1MbxokvF&GWIn$KUed$XQt=;#)opvcJs-8 z?rnPo5YJr36rK3?)qwG?`|DHRCP2=4Q=NA*|Hs4<>}74czE5qeW8a&9>d=dV(m(3G zV9aid%x(d+5MqE&63;8x?>g<}i~UzM14|t=8PB(-jt+hoq2&DnS_tdDwxqtAhdsl( z2lUbRIb|53{fDHEU&>PN1=rIRk(n>T2XX`OL2zG#c#p<@_iHa-^!MwLb>FADk<35$ z26PzHwT*=R4Lk3J>zDYn&n(aC(q`+U+%t_S?;a3mi1DXHCn(oh*s+m5c2lT7r>Aq6 z{Fm$Ek&Qyu5PDb-(2l`PL8*`L2N3=)G}0#5tY=pFu4ayu{cxPXKlgSSmv-I!bI*W` zdChHl?U$=fC5PkG_mGG`FE}B1O7OVgh=4PWzhSr~z;5qVZ68YBGvLdSzefPMcJYn4 zA#ZFr111AVxtd!TSM$NUJ9d*Vtc)v&QO7hlWR6#=9TH8=aK_uZFA)u=XV-}M=L&&Cw&d#K8-yE<8Jlp!M zQ)Rf<-8X=G<+<)%99QDv-W`X<#$>-J`;TDQ_`0{gpE2A|u!}W?zL~CT{A&BAeS4?x zr@^T9eJo@6-g#PZa&XE#0~@r1H4P0LFXW8Jy;6T#aB85wnfS(7Vnd9079zgpc9{Le zJLm2Jq;1r(m0W^hsEh%1*3|ujCkLGUmj}bz*3a&R(C4}N`1k}36C+|`OwUQ3_c?s= zoSP57Tql>sj?;tVf(HiM2km^OsVBK^cwS)*ULCNmO5doL{ILEqhG$!1z7T*dyoY1| zu=zKZ+zZP+ia+D>4LviBZ+jfMyE6D-@J9h;j%#lEYWu!v`gzb^)B5OI8#eC?Sq`7D zkKgT~LHpkry*;vwWBs&aKA--Q8`q(BS@NxAGJy1>=IX1y9~KrL_~wE>$Iqu{)1f^l#AqH#R#bCZoLj zEChe`@zb$g(jR$C&xQM>f%5|Um-hekemSd&$zs_1W$u0)yYC%9=D6lIzb!;_X=_2H)w2FEYCpt_pI0qncJG{ef9ouZbY`^HDG-D<=_3XcVfXa z5%E|IXQIBnE%rj@wC4I>UGqCc>|4QNTJzXCER0dV{2MpE%b?dW!_F}ua-L889`0Mb5t;H82xi0<38!N z|2+#2Y;2qhTd{$dQI_>_%!Lm1kW+GtujCps_XnkZQ((?1wQ1P6JI}E4b16q0`Q~+d zAJFgf{1K@`=DX&m-Jf0y*^k!B>pCHuL-^xptD~r+H>~e@s+;+8QeYKxAcAbfZsay3b-eBBiF-w z(urR6_nLo{cH_qR{EC40izfsx4K4`$U2z<7!7k<_x2${kj|9^DQlZVhQT#9_A8M2t zGj;bWm3wytm=D`VnYWv)ww&Bd+AFV3ci+OZk>C2pxkkoN2#xqA1!JK5 z1;*dpbz6JGwrS(@D(%yM1IB&;#*J?NF6Fua-P$nhJl*8X)wR*LO91I7ziqhQlEo#nnp1yX-Dq;`W{SHsFc&lz`9G;vz0sG3 z?OPBZh}uhi?wNcS8MgQTBf}47s>Hz@}mCGXI?C5cx6gRYP;Z4%?C8 zZ%BPl(C%*Q%lyBv(KFxq_k4U)>^U+xE_mV)K<>H0+kzhiuKRwzcZ)~AHgoaO=!XX7 zzLa7AYE!>C+I`wjE?*w-E(?)s=P&CArv2&wJ&tYbEO~w6+Xdtrs=5AGWyiVyOWS5s z$FE`hA7u_>b#xOJ{qGkgbM${B#54Bw!S{mi2Db#m&aW@mWXvN22;JJSed=!slpEH6 z_uHc~7E)i$^}i}R7x~97d>93sWxyB=8{1*zJSVxQLf&g?Zt#^{t!&`)JL`zK%|Smq z@QGL`^VZaN2%yIWHwN?>FXaZ6#_C}i3z^fJ>wi^tF7iLf?mM&J{QPIaG3cGYSH#No zV%#1cVf-)$!}wI@mz+N~_>V@bs%$Bb2GadWBz^DpuF_z zH`g~s2ez>ekS))LUSqqpKR9C{ZL7KdS7Wgy?tgPnTp!VdVU%~HevvcAGui)Te*Zm_ zJf@jw$CPgm%q?>IU|Y9R8GSUTjoQA2vRhjIjS0`^dp8kS(KiyKbsDz=GmM-#U$^|& zh738Ce{1SK|Dn${bx7*`HiXNz5M znadnFZ@1E1lG}qCd&s#tDC>7g`zr&!HKEI~-SoPqo)$TXvFfZE`dpOTlJif@eiA&f zi37PSpXHn8p$c(DRK}wG454E>q6eJjq-DW zK6KM0n{Q}ILzRsjX2f7&h`T**-_v)VdYTV2R zt$PRmC-8mUxc9S3`!CNp^U!N<+gC*Pg8_OS-)|1(w1@Ju!Lfno4gQ$#R4bC7i~J9= zca(l&;655=`9DjJqJ<8zN+`Yi>F> z|1Axl4v-(kXJlF9zYa>fz8?Ph!Kwf%=belEJNH{N*D=Af0`8Ne;DpHkNx(PvF9*b% zc&MBDOyN23$I$^9gPNPZFYr4Jze!yhI6o!vWgVes)cj!cMZtCf z4)T+jwBzDhpk3QJvz{D4t^wyC=l(Y)r_@tbXI@jCQ9*3jf1eMCMcdaw@oD!P5B4(O zZy@7g9(GQf_g>_gvsb>$7`NwY^XBx5=wlAZx$l|!zXXg?u06iZ>kBdG{_?)yD*xgN`)+_9d_WiV7X^^(g}UdRGRVyo>Npj}A?BGaxlSJzMc6gl+@r(1{#)wA3UW_> zCUs-7JHH!sFwrlm1u9o#2?%5ZvFTL+9;0Tbfg7CH4g&rC|=HQW4Smp)Oh3$Vw1I}_X5W7KtU^g^yx z;==P8@}AXiP935Tdw}cZkkpZF=gBtS>0TJ@8c2*cS7lqd-tB*91klwjKZiGi*~@~a z(}Hr$&%^IcM@YTMLp&28YwoDe6zVuNIv`_ObHiV;akt>4;Fd5O4=kWmngl*(c`gkhHmie)MZX;C2oH@$zjmRbi^6XRSzXzo))Lm;grG8`p zp%a`O{4zjJA8$zg%-}DAa~e=TBk;XbS;oy2+VyYI!F%r2!Br#T%E)6AYgRjqr~Ej} z2V@?o%gNl z;Q5qw`0Ym4JZR5iEy=s_AEyt-=SR`MV*nX*&&9t=8*^fVafWBlT^D#??Pi~C>S^mQd2BP@W&2XSj$u0v>;u}k&qKz% z=BA&K_@;_nnFI1pyoXtzjyomtPIhjSovhIcC(jnmfKJ-M3NRp36DQGw7l;GMDRT)OnZWd*|Q1|LP|2z0PIxap^2` zVGhxwPRquGs?d%@n)!Z}a@ccbDc`;GUB<>-d_A$fTNU-`oHXpl!n68CX+ekUrqnm6 z?!AIsB70~6nd_RXubvD0g!SFW^MkS2jt#7dLGv#4ZyqD#MlK!^K*qf0rmyZ#zVUn^ zAfArz7h7b=5xFvVmV84Xw@|NnZGUuR&JTEh<{d>H%uT)I``NS9SLcp=n7=2d4t3ju zbDB!3{>O70aT>JWTw9-y?CAmX@b@L;uAR>`4J6iPdwpuxp6Oe9 zH>mT{YYmex_N@E%y43yKmwve?SK`9d(XWnvbJ6$HV6Ol&SJcthEuZuaLySis_af?i zPr5N+&Oz(4m(5#ZJF)QoYg{P*F4!)B+Iy;QoxZv@UX>k}|tQg8UIL^I67jP~DuInDLN$Yi>N(&qd8l?JLJ!5|k7(?3p7#91@{Y$&-cKNY&t{&*2=YBj z&LdutapgPUWdZTySz_2Yc9S!<^`Vb-E1q{^piE< zGpqBYX^SU)-7%hC9uOO2QpTxUofsSMG6&Sj4P*Xy069<1&FtzLIxxeT1O3D1xtsmA z>F?K~Z|?vahrP9?g?rV;LQ0!fbH5c$Pd@kEg5r201=;I@1NCxMRLI>bAM3k#>yBBij^@XR>b<@0r%v` z1w4PA5IjHl!vHd8H8=ZfKYjZ3(oBONqpWBBXAHk}LBwiYYqwI^y5NiJjCtmAE~ZAm zYnuJTyCQMY_d&TYWBNZb4l?F7x9PQCt~Qk%jdKk%H!yZQzd>&dN?V8V3mMKVW5M6N z_&00jC{1G{eCQS*^E0-IqR4omlV^P1b@3Np@p;(zAm^TXbLvq0nc1Aog&*~dgRlJ7 zWIjjn4Oz}Nef)mvj}IXCYPX!#62o8J=l95v*9I2{?+p$PAp2`>=GV5(_;N#F9zDxP ziIMAtZ%ooSVxq2@maexwqTm;Sxg5o}QkK18d^igx1IWEJQ=iT4-qH1)fH{pR^*;_E z_R&n|U|e}-?Gc{!g?@bUjDsH)#)N15Jp;(t*WCPEjVsTB_%s_IjY)Yw_*rzmD&YKt zh%s}H;=4BPmT}(>n1{cu#a7N7Z7%PvqpX4U+FTW#X9fO#i~e%0>F21Wc0MusApNSj z`MI**p>+ZNc!pDF&Ehw0z%@Lq-(}ox%J^3VJ{ypqcHGmn85yIyr{^m{X`}W~YWJCq zhC%0mb5pujuvu0X z{708@;BQ*~I=D|zQn!3)8)wHAfiW!eV!SPp`O8KzYscJ{>rK6lSm*asd0p_sfbngc zd-*nOJN^?>V#}CI14!FyZho%D;zOAp+seFiuljPpGb`_g?hA7{tpCWFGshB-&jim2 zb_t*|9`231rk!}9gE5R(N4dXjM+Vu~2M}ujNY1fq8cq(d!7=E-fAo&x8?xHOcY&7# z%zt&@GZiw%_WaZ#V_$RgbJd2)O#hDozY%dhJt4SPK)>;OOzQYCllT}<;$$9e;~C)- z0dae9uuTAM9egrCt{n5t@Xrj`w|vuL@4C0t&zQFc5;~E6S-@Pk18e9-e29U9@x%s`iFWOen?)QNMQztI7iBlO@_Je!Ec69%H@PXix zfOcez?^jaaA%L*W7(ONSQipoUHT&(xpl!GFx7*b-N~wJZXC7!p&@ZNy&;q&;M}W;6AHZNx-hM~P2AS>jJF$~~^Ub6Fl6If(dTAGU23K<#^&W7New%)wma$hmqc zU2jLE4jD6TB&O5_GK+=u<>(k(`da!=otTazuH?{?yeRF+kz-1r zp7vU>f0VMHf9BusUdAjYEPB$HjXGA}wkGz{c zBVb)9k1R2Qj9JYs{;T)fgW}3x1w0GtqwfsH=1J)zCZ37L$CCX*ta|k;*R3SS-SUji zZt{cLumM|ojn_urFClznZyOKFqXNE}5F5z8n%i=&ZNK2lShyY@koI12C!S^g%6hLc z?RLhve$Z!*+H!;Va&GGV^gJm2kUqA3uY0%L-0NTanag%c*9Xr5KMaV!F*awkeJkKu z;+;i*zg)35+v}#6ym{=GOUEL6Zg5iYwSackRcSMA+;7TnPGL8pelgiZ@V^k_ebL;R zkM|@7tPkmPsSsoP&5dPwE}Iwox|O9&IkuFw{R9Ix?NN2gLF6VE+K(d4X7aueew0KMjy8_b%r!Z5IS90*H4^ z+U%n)YK-dDX4caHTEMP1?92G$NR&z_&UJ)?2`$phV0s9MT z$4{R9MXcSI=JMjS`EJRYA*WXa$a*%HbXnC!~QKao&1nT$#Gh35->CP3p+HkIVf3QQ9G7o$qd`bN;B` zGo5!-NWC>zU)%PotCU%)4~~F&>$P`m-zKX5H6TZTf1|!j0G0W%{(Zep~90 zGSto6-BbT+p#JZqe%AofhMJqc(vB{5O!q9+#u)+QdvKIq6nrq)BY?_W!kep|QvY6H zKKUl>dQm1#6{%zQaPJE)3=XP#mU7#KVcrXJR_2Ym>!NL(`8*~ayi@RZJpImc#-ppP zzvPiwlr#n1@s{K6^OiHKYIV8euyv~so91u*gR_p>{;Y=_qrh$J*REeVvGM@p7O``Ld(^#rzF{x?6YGH}s;*Vh8;&#NQ1)5@BE_x?4%<8zw+bv3`b z>EB@gR(1b|x;qbdL-FTM%YTjmYbG7Pd|B8#-Zag>=S=%|zBxj5{~BtO%aK}^{yX4f z(T)W_(V3d+tliju`10+hrf!?ur;?vq(^)fd$kfztHyqYszNr&Cuc;ghzdY>ZWM`Y2 ze|l$m8jhaq9A5G3(|>LHGuyU|Tc3A~^smXh6@N`C%W89WpughJtoked@~S`6P{SX2 zOU-ufzox1jfAOaLYY*L&Kf5-g)4BiJs$=yU`)ht#U35BsP*)~4=Xc2S;IgUvb*g_L zpZ--7;rYYDi5waeYifQf9~CC@aJg(lXXi>jc1}DzOZM2#eii@v^qo=lS3ED0%l>?A zXRA}2{`EC~@Ab(`a;P9qD?c`Rx2ZmeC_6r#pW`CjLAyd&l*j=7~StlwWp!_{*o| zw|aiQ8M~j-yiMjh+Wi)O$BVMx5+`Zj^RgeaU!^x3oBdkt@2X|l{gaD=zw33?{+{ei zoXK{dddDl)cJ5N0Cu=MDZL?(S^W@lyKl4lBtMg>vN`C5H^rL_KoMvZEO(k5>fAnN$ z>(5Tzyx-LCR@LX#WM|niQ`f$B>eft`{tTGdVd}hnr!KFit0p{r>e%h4_NvrX2iU3$ zH>^Bk>uS1+U%u0tVB|%L-?(#`@s~|nvb?n#ZRVjt7(4Onu6ay_0Wz@|N3<`@BM1C``LMQzxTV} k+)&>SA9j7Mf2wuAoNC`s+xOee?#HeB^;GMAUf%EjKWq8@DF6Tf literal 0 HcmV?d00001 diff --git a/src/lib.rs b/src/lib.rs index 8be60e9..7914d0d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,12 @@ static COLORING_FUNCTION: ColoringFunction = TrueColor::new_from_bernstein_polyn //Color channel mapping static COLOR_CHANNEL_MAPPING: ColorChannelMapping = ColorChannelMapping::RGB; +//Window title +static WINDOW_TITLE: &str = "Mandelbrot by Jort"; + +//Banner values +static VERSION: &str = "1.5"; + //Views static VIEW_1: View = View::new(-0.6604166666666667, 0.4437500000000001, 0.1); static VIEW_2: View = View::new(-1.0591666666666668, 0.2629166666666668, 0.01); @@ -63,9 +69,6 @@ static VIEW_8: View = View::new(-1.7862581627050718, 0.00005198056959995248, 0.0 static VIEW_9: View = View::new(-0.4687339999999999, 0.5425518958333333, 0.000010000000000000003); static VIEW_0: View = View::new(-0.437520465811966, 0.5632133750000006, 0.000004000000000000004); -//Banner values -static VERSION: &str = "1.4"; - ///Holds all the logic currently in the main function that isn't involved with setting up configuration or handling errors, to make `main` concise and ///easy to verify by inspection /// # Panics diff --git a/src/view/minifb_mandelbrot_view.rs b/src/view/minifb_mandelbrot_view.rs index 6f7d31b..b5ab171 100644 --- a/src/view/minifb_mandelbrot_view.rs +++ b/src/view/minifb_mandelbrot_view.rs @@ -1,6 +1,8 @@ -use minifb::{Key, Window, WindowOptions}; +use std::str::FromStr; -use crate::model::mandelbrot_model::MandelbrotModel; +use minifb::{Icon, Key, Window, WindowOptions}; + +use crate::{model::mandelbrot_model::MandelbrotModel, VERSION, WINDOW_TITLE}; pub struct MandelbrotView { pub window: Window, @@ -9,15 +11,29 @@ pub struct MandelbrotView { impl MandelbrotView { pub fn new(mandelbrot_model: &MandelbrotModel) -> MandelbrotView { // Create a new window - let window = Window::new( - "Mandelbrot set viewer", + let mut window = Window::new( + &format!("{} v{}", WINDOW_TITLE, VERSION), mandelbrot_model.config.window_width, mandelbrot_model.config.window_height, - WindowOptions::default(), + WindowOptions { + borderless: false, + title: true, + resize: true, + scale: minifb::Scale::X1, + scale_mode: minifb::ScaleMode::Center, + topmost: false, + transparency: false, + none: false, + }, ) .unwrap_or_else(|e| { panic!("{}", e); }); + //Set window background color to darkgray + window.set_background_color(27, 27, 27); + + #[cfg(target_os = "windows")] + window.set_icon(Icon::from_str("icons/rust.ico").unwrap()); //Print info about the MandelbrotModel mandelbrot_model.p.pixel_plane.print();