diff options
-rw-r--r-- | CHANGELOG.md | 7 | ||||
-rw-r--r-- | source/benoit/benoit.rs | 12 | ||||
-rw-r--r-- | source/benoit/benoit/application.rs | 12 | ||||
-rw-r--r-- | source/benoit/benoit/application/animate.rs | 2 | ||||
-rw-r--r-- | source/benoit/benoit/application/draw.rs | 2 | ||||
-rw-r--r-- | source/benoit/benoit/application/get_row_renderer.rs | 34 | ||||
-rw-r--r-- | source/benoit/benoit/application/handle_keys.rs | 42 | ||||
-rw-r--r-- | source/benoit/benoit/application/initialise.rs | 7 | ||||
-rw-r--r-- | source/benoit/benoit/application/loop.rs | 7 | ||||
-rw-r--r-- | source/benoit/benoit/application/render.rs | 24 | ||||
-rw-r--r-- | source/benoit/benoit/application/render_row_julia.rs | 106 | ||||
-rw-r--r-- | source/benoit/benoit/application/render_row_mandelbrot.rs (renamed from source/benoit/benoit/application/render_row.rs) | 17 | ||||
-rw-r--r-- | source/benoit/benoit/configuration.rs | 7 | ||||
-rw-r--r-- | source/benoit/benoit/configuration/default.rs | 7 | ||||
-rw-r--r-- | source/benoit/benoit/configuration/load.rs | 24 |
15 files changed, 279 insertions, 31 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index c61efe4..6fcd2ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 22 + +* Support rendering of Julia sets +* Update controls +* Refactor code +* Update configuration + # 21 * Update controls guide (fix typo) diff --git a/source/benoit/benoit.rs b/source/benoit/benoit.rs index 4251fa8..15e8b74 100644 --- a/source/benoit/benoit.rs +++ b/source/benoit/benoit.rs @@ -21,8 +21,20 @@ If not, see <https://www.gnu.org/licenses/>. */ +extern crate rug; + +use rug::Float; + pub mod application; pub mod configuration; pub mod video; +type RowRenderer = fn(&mut [u32], u32, u32, u32, Float, Float, Float, u32, Float, Float); + pub const PRECISION: u32 = 0x100; + +#[derive(Clone, Copy)] +pub enum Fractal { + Julia, + Mandelbrot, +} diff --git a/source/benoit/benoit/application.rs b/source/benoit/benoit/application.rs index 3b8571c..9e679e5 100644 --- a/source/benoit/benoit/application.rs +++ b/source/benoit/benoit/application.rs @@ -21,6 +21,7 @@ If not, see <https://www.gnu.org/licenses/>. */ +use crate::benoit::{Fractal, RowRenderer}; use crate::benoit::video::Video; extern crate rug; @@ -31,17 +32,21 @@ pub mod animate; pub mod colour; pub mod draw; pub mod dump; +pub mod get_row_renderer; pub mod handle_keys; pub mod initialise; pub mod r#loop; pub mod poll_events; -pub mod render_row; +pub mod render_row_julia; +pub mod render_row_mandelbrot; pub mod render; pub mod run; pub struct Application { thread_count: u32, + fractal: Fractal, + canvas_width: u32, canvas_height: u32, scale: u32, @@ -52,6 +57,9 @@ pub struct Application { zoom: Float, maximum_iteration_count: u32, + julia_real: Float, + julia_imaginary: Float, + dump_path: String, video: Option<Video>, @@ -59,4 +67,6 @@ pub struct Application { interactive: bool, do_draw: bool, do_dump: bool, + + render_row: RowRenderer, } diff --git a/source/benoit/benoit/application/animate.rs b/source/benoit/benoit/application/animate.rs index 6fd6fe4..0f2c796 100644 --- a/source/benoit/benoit/application/animate.rs +++ b/source/benoit/benoit/application/animate.rs @@ -73,7 +73,7 @@ impl Application { for frame in 0x0..self.frame_count { eprint!("{frame:010}: "); - self.render(&mut data[..], &self.center_real, &self.center_imaginary, &zoom, self.maximum_iteration_count); + self.render(&mut data[..], &self.center_real, &self.center_imaginary, &zoom, self.maximum_iteration_count, &self.julia_real, &self.julia_imaginary); self.colour(&mut image[..], &data[..]); self.dump(format!("{}/frame{frame:010}.webp", self.dump_path), &image, self.canvas_width, self.canvas_height); diff --git a/source/benoit/benoit/application/draw.rs b/source/benoit/benoit/application/draw.rs index 715b3da..1ac3331 100644 --- a/source/benoit/benoit/application/draw.rs +++ b/source/benoit/benoit/application/draw.rs @@ -32,7 +32,7 @@ impl Application { pub fn draw(&mut self, data: &mut [u32], image: &mut [u8]) { let canvas_size = self.canvas_height * self.canvas_width; - self.render(&mut data[..], &self.center_real, &self.center_imaginary, &self.zoom, self.maximum_iteration_count); + self.render(&mut data[..], &self.center_real, &self.center_imaginary, &self.zoom, self.maximum_iteration_count, &self.julia_real, &self.julia_imaginary); self.colour(&mut image[..], &data[..]); for pixel in 0x0..canvas_size { diff --git a/source/benoit/benoit/application/get_row_renderer.rs b/source/benoit/benoit/application/get_row_renderer.rs new file mode 100644 index 0000000..ef333c4 --- /dev/null +++ b/source/benoit/benoit/application/get_row_renderer.rs @@ -0,0 +1,34 @@ +/* + Copyright 2021, 2023 Gabriel Bjørnager Jensen. + + This file is part of Benoit. + + Benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::benoit::{Fractal, RowRenderer}; +use crate::benoit::application::Application; + +impl Application { + pub fn get_row_renderer(fractal: Fractal) -> RowRenderer { + return match fractal { + Fractal::Julia => Application::render_row_julia, + Fractal::Mandelbrot => Application::render_row_mandelbrot, + }; + } +} diff --git a/source/benoit/benoit/application/handle_keys.rs b/source/benoit/benoit/application/handle_keys.rs index 5d52024..a0c10d7 100644 --- a/source/benoit/benoit/application/handle_keys.rs +++ b/source/benoit/benoit/application/handle_keys.rs @@ -21,7 +21,7 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::PRECISION; +use crate::benoit::{Fractal, PRECISION}; use crate::benoit::application::Application; extern crate rug; @@ -29,41 +29,49 @@ extern crate sdl2; use rug::Float; use sdl2::keyboard::Scancode; -use std::ops::{AddAssign, DivAssign, MulAssign, SubAssign}; impl Application { pub fn handle_keys(&mut self, scan_code: Scancode) -> bool { + let print_values = |fractal: Fractal, center_real: &Float, center_imaginary: &Float, zoom: &Float, maximum_iteration_count: u32, julia_real: &Float, julia_imaginary: &Float| { + let base = format!("c = {center_real}{center_imaginary:+}i -- {zoom}x @ {maximum_iteration_count} iter."); + + match fractal { + Fractal::Julia => eprintln!("{base}:\n c(M) = {julia_real}{julia_imaginary:+}i"), + Fractal::Mandelbrot => eprintln!("{base}"), + }; + }; + match scan_code { Scancode::Escape => return true, Scancode::C => self.do_draw = true, Scancode::X => self.do_dump = true, - Scancode::Z => eprintln!("{}{:+}i -- {}x @ {} iter.", self.center_real, self.center_imaginary, self.zoom, self.maximum_iteration_count), + Scancode::Z => print_values(self.fractal, &self.center_real, &self.center_imaginary, &self.zoom, self.maximum_iteration_count, &self.julia_real, &self.julia_imaginary), _ => {}, } match scan_code { - Scancode::E => self.zoom.mul_assign(4.0), - Scancode::Q => self.zoom.div_assign(4.0), + Scancode::E => self.zoom *= 4.0, + Scancode::Q => self.zoom /= 4.0, _ => {}, }; let translate_ammount = { let mut ammount = Float::with_val(PRECISION, 1.0); - ammount.div_assign(4.0); - ammount.div_assign(&self.zoom); + ammount /= 4.0; + ammount /= &self.zoom; ammount }; match scan_code { - Scancode::A => self.center_real.sub_assign(&translate_ammount), - Scancode::D => self.center_real.add_assign(&translate_ammount), + Scancode::A => self.center_real -= &translate_ammount, + Scancode::D => self.center_real += &translate_ammount, _ => {}, }; match scan_code { - Scancode::S => self.center_imaginary.add_assign(&translate_ammount), - Scancode::W => self.center_imaginary.sub_assign(&translate_ammount), + Scancode::S => self.center_imaginary += &translate_ammount, + Scancode::W => self.center_imaginary -= &translate_ammount, _ => {}, }; @@ -73,6 +81,18 @@ impl Application { _ => self.maximum_iteration_count, }; + match scan_code { + Scancode::Down => self.julia_real += &translate_ammount, + Scancode::Up => self.julia_real -= &translate_ammount, + _ => {}, + }; + + match scan_code { + Scancode::Left => self.julia_imaginary -= &translate_ammount, + Scancode::Right => self.julia_imaginary += &translate_ammount, + _ => {}, + }; + return false; } } diff --git a/source/benoit/benoit/application/initialise.rs b/source/benoit/benoit/application/initialise.rs index 7891810..fec750c 100644 --- a/source/benoit/benoit/application/initialise.rs +++ b/source/benoit/benoit/application/initialise.rs @@ -60,6 +60,8 @@ impl Application { return Application { thread_count: thread_count, + fractal: configuration.fractal, + canvas_width: configuration.canvas_width, canvas_height: configuration.canvas_height, scale: configuration.scale, @@ -70,6 +72,9 @@ impl Application { zoom: Float::with_val(PRECISION, configuration.zoom), maximum_iteration_count: configuration.maximum_iteration_count, + julia_real: configuration.julia_real, + julia_imaginary: configuration.julia_imaginary, + dump_path: configuration.dump_path, video: video, @@ -77,6 +82,8 @@ impl Application { interactive: configuration.interactive, do_draw: true, do_dump: false, + + render_row: Application::get_row_renderer(configuration.fractal), }; } } diff --git a/source/benoit/benoit/application/loop.rs b/source/benoit/benoit/application/loop.rs index 045b139..a4d355b 100644 --- a/source/benoit/benoit/application/loop.rs +++ b/source/benoit/benoit/application/loop.rs @@ -44,6 +44,11 @@ impl Application { eprintln!("- X Dump frame"); eprintln!("- C Render frame"); eprintln!(); + eprintln!("- \u{2191} Translate Julia center up"); + eprintln!("- \u{2190} Translate Julia center left"); + eprintln!("- \u{2192} Translate Julia center down"); + eprintln!("- \u{2193} Translate Julia center right"); + eprintln!(); let mut event_pump = self.video.as_mut().unwrap().sdl.event_pump().expect("unable to get event pump"); @@ -73,4 +78,4 @@ impl Application { return 0x0; } -} +}
\ No newline at end of file diff --git a/source/benoit/benoit/application/render.rs b/source/benoit/benoit/application/render.rs index 726857c..5d1f2eb 100644 --- a/source/benoit/benoit/application/render.rs +++ b/source/benoit/benoit/application/render.rs @@ -32,7 +32,7 @@ use std::time::Instant; use std::ptr::addr_of_mut; impl Application { - pub fn render(&self, buffer: &mut [u32], center_real: &Float, center_imaginary: &Float, zoom: &Float, maximum_iteration_count: u32) { + pub fn render(&self, buffer: &mut [u32], center_real: &Float, center_imaginary: &Float, zoom: &Float, maximum_iteration_count: u32, julia_real: &Float, julia_imaginary: &Float) { eprint!("rendering..."); let mut threads = Vec::<JoinHandle<()>>::with_capacity(self.thread_count as usize); @@ -60,13 +60,18 @@ impl Application { // We should stop if there are no remaining rows. if y == self.canvas_height { break 'render_loop; } + let render_row = self.render_row; + let buffer_slice = get_slice(buffer, y, self.canvas_width); - let center_real = center_real.clone(); + let center_real = center_real.clone(); let center_imaginary = center_imaginary.clone(); - let zoom = zoom.clone(); + let zoom = zoom.clone(); + + let julia_real = julia_real.clone(); + let julia_imaginary = julia_imaginary.clone(); - threads.push(spawn(move || { Application::render_row(buffer_slice, y, canvas_width, canvas_height, center_real, center_imaginary, zoom, maximum_iteration_count) })); + threads.push(spawn(move || { render_row(buffer_slice, y, canvas_width, canvas_height, center_real, center_imaginary, zoom, maximum_iteration_count, julia_real, julia_imaginary) })); y += 0x1; } @@ -79,13 +84,18 @@ impl Application { for y in 0x0..self.canvas_height { threads.remove(0x0).join().unwrap(); + let render_row = self.render_row; + let buffer_slice = get_slice(buffer, y, self.canvas_width); - let center_real = center_real.clone(); + let center_real = center_real.clone(); let center_imaginary = center_imaginary.clone(); - let zoom = zoom.clone(); + let zoom = zoom.clone(); + + let julia_real = julia_real.clone(); + let julia_imaginary = julia_imaginary.clone(); - threads.push(spawn(move || { Application::render_row(buffer_slice, y, canvas_width, canvas_height, center_real, center_imaginary, zoom, maximum_iteration_count) })); + threads.push(spawn(move || { render_row(buffer_slice, y, canvas_width, canvas_height, center_real, center_imaginary, zoom, maximum_iteration_count, julia_real, julia_imaginary) })); } } diff --git a/source/benoit/benoit/application/render_row_julia.rs b/source/benoit/benoit/application/render_row_julia.rs new file mode 100644 index 0000000..5caea3b --- /dev/null +++ b/source/benoit/benoit/application/render_row_julia.rs @@ -0,0 +1,106 @@ +/* + Copyright 2021, 2023 Gabriel Bjørnager Jensen. + + This file is part of Benoit. + + Benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::benoit::PRECISION; +use crate::benoit::application::Application; + +extern crate rug; + +use rug::Float; + +impl Application { + pub fn render_row_julia(data: &mut [u32], y: u32, canvas_width: u32, canvas_height: u32, center_real: Float, center_imaginary: Float, zoom: Float, maximum_iteration_count: u32, julia_real: Float, julia_imaginary: Float) { + for x in 0x0..canvas_width { + let canvas_width = Float::with_val(PRECISION, canvas_width); + let canvas_height = Float::with_val(PRECISION, canvas_height); + + let x_float = Float::with_val(PRECISION, x); + let y_float = Float::with_val(PRECISION, y); + + // The Julia set of the Mandelbrot set is similar + // but not quite identical: The start value of (z) + // is now relative to the canvas and (c) is + // constant corresponds to a point in the + // Mandelbrot Set. + + let ca = &julia_real; + let cb = &julia_imaginary; + + // Re(z) = (x-canvas_width/2)*4/canvas_width/zoom+Re(z) + let mut za = { + let tmp0 = Float::with_val(PRECISION, &canvas_width / 2.0); + + let mut za = Float::with_val(PRECISION, &x_float - &tmp0); + za *= 4.0; + za /= &canvas_width; + za /= &zoom; + za += ¢er_real; + + za + }; + + // Im(Z) = (x-canvas_height/2)*4/canvas_height/zoom+Im(z) + let mut zb = { + let tmp0 = Float::with_val(PRECISION, &canvas_height / 2.0); + + let mut zb = Float::with_val(PRECISION, &y_float - &tmp0); + zb *= 4.0; + zb /= &canvas_height; + zb /= &zoom; + zb += ¢er_imaginary; + + zb + }; + + let mut iteration_count: u32 = 0x0; + while { + let square_distance = Float::with_val(PRECISION, &za * &za + &zb * &zb); + square_distance <= 4.0 && iteration_count < maximum_iteration_count + } { + { + // The overall iterations of the Julia of M are + // identical to those of M: + // + // z = z^2+c + // + // with only the initial value of (z) and (c) + // differing. + + let za_temporary = Float::with_val(PRECISION, &za); + + za = za.square(); + za -= &zb * &zb; + za += ca; + + zb *= &za_temporary; + zb *= 2.0; + zb += cb; + } + + iteration_count += 0x1; + } + + unsafe { *data.get_unchecked_mut(x as usize) = iteration_count } + } + } +} diff --git a/source/benoit/benoit/application/render_row.rs b/source/benoit/benoit/application/render_row_mandelbrot.rs index 757e4d3..ec07cdc 100644 --- a/source/benoit/benoit/application/render_row.rs +++ b/source/benoit/benoit/application/render_row_mandelbrot.rs @@ -29,7 +29,7 @@ extern crate rug; use rug::Float; impl Application { - pub fn render_row(data: &mut [u32], y: u32, canvas_width: u32, canvas_height: u32, center_real: Float, center_imaginary: Float, zoom: Float, maximum_iteration_count: u32) { + pub fn render_row_mandelbrot(data: &mut [u32], y: u32, canvas_width: u32, canvas_height: u32, center_real: Float, center_imaginary: Float, zoom: Float, maximum_iteration_count: u32, _julia_real: Float, _julia_imaginary: Float) { for x in 0x0..canvas_width { let canvas_width = Float::with_val(PRECISION, canvas_width); let canvas_height = Float::with_val(PRECISION, canvas_height); @@ -37,26 +37,26 @@ impl Application { let x_float = Float::with_val(PRECISION, x); let y_float = Float::with_val(PRECISION, y); - // Re(c) = (x-canvas_width/2)/canvas_width*4/zoom+Re(z) + // Re(c) = (x-canvas_width/2)*4/canvas_width/zoom+Re(z) let ca = { let tmp0 = Float::with_val(PRECISION, &canvas_width / 2.0); let mut ca = Float::with_val(PRECISION, &x_float - &tmp0); - ca /= &canvas_width; ca *= 4.0; + ca /= &canvas_width; ca /= &zoom; ca += ¢er_real; ca }; - // Im(c) = (x-canvas_height/2)/canvas_height*4/zoom+Im(z) + // Im(c) = (x-canvas_height/2)*4/canvas_height/zoom+Im(z) let cb = { let tmp0 = Float::with_val(PRECISION, &canvas_height / 2.0); let mut cb = Float::with_val(PRECISION, &y_float - &tmp0); - cb /= &canvas_height; cb *= 4.0; + cb /= &canvas_height; cb /= &zoom; cb += ¢er_imaginary; @@ -84,7 +84,9 @@ impl Application { square_distance <= 4.0 && iteration_count < maximum_iteration_count } { { - // The Mandelbrot Set (M) is defined as the set of values in the complex plane where the iterating function + // The Mandelbrot Set (M) is defined as the set of + // values in the complex plane where the iterating + // function // // z = z^2+c // @@ -94,7 +96,8 @@ impl Application { let za_temporary = Float::with_val(PRECISION, &za); - // We can calculate the square of a complex number (z) as: + // We can calculate the square of a complex number + // (z) as: // // z^2 = (a+ib)^2 = (a+ib)(a+ib) = a^2+iab+iab-b^2 = a^2-b^2+2iab diff --git a/source/benoit/benoit/configuration.rs b/source/benoit/benoit/configuration.rs index 3b00c0f..4190cf4 100644 --- a/source/benoit/benoit/configuration.rs +++ b/source/benoit/benoit/configuration.rs @@ -21,6 +21,8 @@ If not, see <https://www.gnu.org/licenses/>. */ +use crate::benoit::Fractal; + extern crate rug; use rug::Float; @@ -31,6 +33,8 @@ pub mod load; pub struct Configuration { pub thread_count: u32, + pub fractal: Fractal, + pub canvas_width: u32, pub canvas_height: u32, pub scale: u32, @@ -41,6 +45,9 @@ pub struct Configuration { pub zoom: Float, pub maximum_iteration_count: u32, + pub julia_real: Float, + pub julia_imaginary: Float, + pub dump_path: String, pub interactive: bool, diff --git a/source/benoit/benoit/configuration/default.rs b/source/benoit/benoit/configuration/default.rs index a185837..e524895 100644 --- a/source/benoit/benoit/configuration/default.rs +++ b/source/benoit/benoit/configuration/default.rs @@ -21,7 +21,7 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::PRECISION; +use crate::benoit::{Fractal, PRECISION}; use crate::benoit::configuration::Configuration; extern crate rug; @@ -33,6 +33,8 @@ impl Configuration { return Configuration { thread_count: 0x0, + fractal: Fractal::Mandelbrot, + canvas_width: 0x100, canvas_height: 0x100, scale: 0x1, @@ -43,6 +45,9 @@ impl Configuration { zoom: Float::with_val(PRECISION, 1.0), maximum_iteration_count: 0x100, + julia_real: Float::with_val(PRECISION, 0.0), + julia_imaginary: Float::with_val(PRECISION, 0.0), + dump_path: "./render/".to_string(), interactive: true, diff --git a/source/benoit/benoit/configuration/load.rs b/source/benoit/benoit/configuration/load.rs index c9f39d8..7f0e066 100644 --- a/source/benoit/benoit/configuration/load.rs +++ b/source/benoit/benoit/configuration/load.rs @@ -21,7 +21,7 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::PRECISION; +use crate::benoit::{Fractal, PRECISION}; use crate::benoit::configuration::Configuration; extern crate rug; @@ -73,8 +73,27 @@ impl Configuration { }; }; + let get_string = |table: &Table, name: &str| -> Option<&String> { + if !table.contains_key(name) { return None } + + match &configuration_table[name] { + Value::String(value) => return Some(value), + _ => panic!("mismatched type of {name}"), + }; + }; + get_integer(&mut configuration.thread_count, &configuration_table, "thread_count"); + configuration.fractal = if let Some(name) = get_string(&configuration_table, "fractal") { + match name.as_str() { + "julia" => Fractal::Julia, + "mandelbrot" => Fractal::Mandelbrot, + name => panic!("invalid fractal name {name}"), + } + } else { + configuration.fractal + }; + get_integer(&mut configuration.canvas_width, &configuration_table, "canvas_width"); get_integer(&mut configuration.canvas_height, &configuration_table, "canvas_height"); get_integer(&mut configuration.scale, &configuration_table, "scale"); @@ -85,6 +104,9 @@ impl Configuration { get_float( &mut configuration.zoom, &configuration_table, "zoom"); get_integer(&mut configuration.maximum_iteration_count, &configuration_table, "maximum_iteration_count"); + get_float(&mut configuration.julia_real, &configuration_table, "julia_real"); + get_float(&mut configuration.julia_imaginary, &configuration_table, "julia_imaginary"); + return configuration; } } |