diff options
33 files changed, 957 insertions, 711 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c9a622..30a4fc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +# 2.3.0 + +* Bump minor version +* Re-enable window border +* Support translation when rendering Julias +* Rework key handling +* Update naming convention +* Update controls guide +* Add key for resetting viewport +* Support perturbation +* Refactor and modulise code structure +* Rework animations +* Update configuration +* Rework row colourisers as point colourisers +* Fix image file extensions +* Use our own complex type + # 2.2.0 * Bump minor version @@ -1,6 +1,6 @@ [package] name = "benoit" -version = "2.2.0" +version = "2.3.0" authors = ["Gabriel Bjørnager Jensen"] edition = "2021" description = "Mandelbrot renderer." diff --git a/source/benoit/benoit.rs b/source/benoit/benoit.rs index a366c61..715a311 100644 --- a/source/benoit/benoit.rs +++ b/source/benoit/benoit.rs @@ -21,22 +21,19 @@ If not, see <https://www.gnu.org/licenses/>. */ -extern crate rug; - -use rug::Float; - pub mod app; +pub mod complex; pub mod configuration; pub mod fractal; pub mod palette; -pub mod rendering; +pub mod renderer; pub mod render; pub mod video; pub const VERSION: [u32; 0x3] = [ - 0x2, - 0x2, - 0x0, + 0x2, // Major + 0x3, // Minor + 0x0, // Patch ]; pub const PRECISION: u32 = 0x80; @@ -49,15 +46,6 @@ pub enum ImageFormat { Webp, } -pub struct FeedbackInfo<'a> { - prev_centre_real: &'a Float, - prev_centre_imag: &'a Float, - prev_zoom: &'a Float, - next_centre_real: &'a Float, - next_centre_imag: &'a Float, - next_zoom: &'a Float, -} - pub fn width_height_ratio(width: u32, height: u32) -> (f32, f32) { return if width > height { (1.0, height as f32 / width as f32) diff --git a/source/benoit/benoit/app.rs b/source/benoit/benoit/app.rs index fa047f9..a281304 100644 --- a/source/benoit/benoit/app.rs +++ b/source/benoit/benoit/app.rs @@ -22,10 +22,10 @@ */ use crate::benoit::ImageFormat; +use crate::benoit::complex::Complex; use crate::benoit::fractal::Fractal; use crate::benoit::palette::Palette; -use crate::benoit::rendering::Rendering; -use crate::benoit::render::{IteratorFunction, PointRenderer}; +use crate::benoit::renderer::Renderer; use crate::benoit::video::Video; extern crate rug; @@ -37,47 +37,38 @@ pub mod animate; pub mod drop; pub mod dump; pub mod handle_keys; -pub mod image_filename; pub mod initialise; pub mod interactive; pub mod poll_events; pub mod run; -pub mod still; pub struct App { - #[allow(dead_code)] thread_count: u32, fractal: Fractal, - rendering: Rendering, + renderer: Renderer, canvas_width: u32, canvas_height: u32, scale: u32, - frame_count: u32, + frame_start: u32, + frame_stop: u32, - centre_real: Float, - centre_imag: Float, - zoom: Float, + centre: Complex, + extra: Complex, + zoom: Float, max_iter_count: u32, - inverse: bool, - - multibrot_exponent: f32, - palette: Palette, colour_range: f32, dump_path: String, image_format: ImageFormat, - video: Option<Video>, - interactive: bool, do_render: bool, do_textual_feedback: bool, - point_renderer: PointRenderer, - iterator_function: IteratorFunction, + video: Option<Video>, } diff --git a/source/benoit/benoit/app/animate.rs b/source/benoit/benoit/app/animate.rs index 9a6b4ba..157c213 100644 --- a/source/benoit/benoit/app/animate.rs +++ b/source/benoit/benoit/app/animate.rs @@ -21,9 +21,12 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::PRECISION; +use crate::benoit::{ImageFormat, PRECISION}; use crate::benoit::app::App; -use crate::benoit::render::{colour, render}; +use crate::benoit::complex::Complex; +use crate::benoit::fractal::Fractal; +use crate::benoit::palette::Palette; +use crate::benoit::renderer::Renderer; extern crate rug; extern crate sdl2; @@ -34,7 +37,9 @@ use std::time::Instant; impl App { pub fn animate(&self) -> i32 { - assert!(self.frame_count > 0x1); + let frame_count = self.frame_stop - self.frame_start; + + if frame_count == 0x0 { return self.still() }; let (mut iter_count_buffer, mut square_dist_buffer, mut image) = App::allocate_buffers(self.canvas_width, self.canvas_height); @@ -43,61 +48,159 @@ impl App { let zoom_stop = Float::with_val(PRECISION, &self.zoom); - let zoom_factor = { - // To get the zoom factor, we first want the 'a' - // value of the growth function from (0) to - // (frame_count) on the x-dimension and from - // (zoom_start) to (zoom_stop) on the y-dimension: - // - // a = nroot(x1-x0,y1/y0), - // - // but this may be simplified for use with Rug - // because - // - // nroot(a,b) = b^(1/a), - // - // making the final equation - // - // (y1/y0)^(1/(x1-x0)) = (zoom_stop/zoom_start)^(1/(frame_count-1)). - // - // N.b. that we subtract one from frame_count as we - // want the final render to have exactly the - // specified zoom. - - let exponent = Float::with_val(PRECISION, 1.0 / (self.frame_count as f32 - 1.0)); - - let mut factor = Float::with_val(PRECISION, &zoom_stop / &zoom); - factor.pow_assign(exponent); - - factor - }; - - eprintln!("animating {} frames at {}{:+}i to {:.3} (fac. {:.3})", self.frame_count, self.centre_real.to_f32(), self.centre_imag.to_f32(), zoom_stop.to_f32(), zoom_factor.to_f32()); - - for frame in 0x0..self.frame_count { - eprint!("{frame:010} (2^{:.9}x)...", zoom.to_f64().log2()); - - let time_start = Instant::now(); + let zoom_factor = get_zoom_factor(&zoom, &zoom_stop, self.frame_stop); - render(&mut iter_count_buffer[..], &mut square_dist_buffer[..], self.canvas_width, self.canvas_height, &self.centre_real, &self.centre_imag, &zoom, self.max_iter_count, self.inverse, self.point_renderer, self.iterator_function); + zoom = if self.frame_start > 0x0 { + let mut zoom = zoom_factor.clone(); + zoom.pow_assign(frame_count); - let render_time = time_start.elapsed(); - eprint!(" {:.3}ms, colouring...", render_time.as_micros() as f32 / 1000.0); - - colour(&mut image[..], self.canvas_width, self.canvas_height, self.multibrot_exponent, self.max_iter_count, self.colour_range, self.palette, &iter_count_buffer[..], &square_dist_buffer[..]); + zoom + } else { + zoom + }; - let colour_time = time_start.elapsed() - render_time; - eprint!(" {:.3}ms...", colour_time.as_micros() as f32 / 1000.0); + eprintln!("animating from #{} to #{} ({} frame(s)) at {}{:+}i to {:.3}x (fac. ~{:.3})", self.frame_start, self.frame_stop, frame_count + 0x1, self.centre.real.to_f32(), self.centre.imag.to_f32(), zoom_stop.to_f32(), zoom_factor.to_f32()); + + for frame in 0x0..=frame_count { + let frame_name = format!("frame{frame:010}"); + + dump_frame( + self.dump_path.as_str(), + frame_name.as_str(), + &mut image[..], + &mut iter_count_buffer[..], + &mut square_dist_buffer[..], + self.renderer, + &self.fractal, + self.palette, + self.canvas_width, + self.canvas_height, + &self.centre, + &self.extra, + &zoom, + self.max_iter_count, + self.colour_range, + self.image_format, + ); - let path = App::image_filename(format!("{}/frame{frame:010}.webp", self.dump_path).as_str(), self.image_format); + zoom *= &zoom_factor; + } - self.dump(path.as_str(), &image, self.canvas_width, self.canvas_height); + return 0x0; + } - eprintln!(" done"); + fn still(&self) -> i32 { + let (mut iter_count_buffer, mut square_dist_buffer, mut image) = App::allocate_buffers(self.canvas_width, self.canvas_height); - zoom *= &zoom_factor; - } + const FRAME_NAME: &str = "render"; + + dump_frame( + self.dump_path.as_str(), + FRAME_NAME, + &mut image[..], + &mut iter_count_buffer[..], + &mut square_dist_buffer[..], + self.renderer, + &self.fractal, + self.palette, + self.canvas_width, + self.canvas_height, + &self.centre, + &self.extra, + &self.zoom, + self.max_iter_count, + self.colour_range, + self.image_format, + ); return 0x0; } } + +fn dump_frame( + dump_path: &str, + name: &str, + image: &mut [u8], + iter_count_buffer: &mut [u32], + square_dist_buffer: &mut [f32], + renderer: Renderer, + fractal: &Fractal, + palette: Palette, + canvas_width: u32, + canvas_height: u32, + centre: &Complex, + extra: &Complex, + zoom: &Float, + max_iter_count: u32, + colour_range: f32, + image_format: ImageFormat, +) { + eprint!("\"{name}\" (2^{:.9}x)...", zoom.to_f64().log2()); + + let time_start = Instant::now(); + + renderer.render( + iter_count_buffer, + square_dist_buffer, + fractal, + canvas_width, + canvas_height, + centre, + zoom, + extra, + max_iter_count, + ); + + let render_time = time_start.elapsed(); + eprint!(" {:.3}ms, colouring...", render_time.as_micros() as f32 / 1000.0); + + renderer.colour( + image, + palette, + canvas_width, + canvas_height, + fractal.exponent(), + max_iter_count, + colour_range, + iter_count_buffer, + square_dist_buffer, + ); + + let colour_time = time_start.elapsed() - render_time; + eprint!(" {:.3}ms...", colour_time.as_micros() as f32 / 1000.0); + + let path = format!("{dump_path}/{name}"); + + App::dump(path.as_str(), &image, canvas_width, canvas_height, image_format); + eprintln!(" done"); +} + +fn get_zoom_factor(zoom_start: &Float, zoom_stop: &Float, frame_count: u32) -> Float { + assert!(frame_count > 0x0); + + // To get the zoom factor, we first want the 'a' + // value of the growth function from (0) to + // (frame_count) on the x-dimension and from + // (zoom_start) to (zoom_stop) on the y-dimension: + // + // a = nroot(x1-x0,y1/y0), + // + // but this may be simplified for use with Rug + // because + // + // nroot(a,b) = b^(1/a), + // + // making the final equation + // + // (y1/y0)^(1/(x1-x0)) = (zoom_stop/zoom_start)^(1/(frame_count-1)). + + let frame_start: f32 = 0.0; + let frame_stop: f32 = frame_count as f32; + + let exponent = Float::with_val(PRECISION, 1.0 / (frame_stop - frame_start)); + + let mut factor = Float::with_val(PRECISION, zoom_stop / zoom_start); + factor.pow_assign(exponent); + + factor +} diff --git a/source/benoit/benoit/app/dump.rs b/source/benoit/benoit/app/dump.rs index a0fb3c5..fe92558 100644 --- a/source/benoit/benoit/app/dump.rs +++ b/source/benoit/benoit/app/dump.rs @@ -31,8 +31,8 @@ use std::fs::{File, write}; use std::io::BufWriter; impl App { - pub fn dump(&self, path: &str, image: &[u8], canvas_width: u32, canvas_height: u32) { - match self.image_format { + pub fn dump(path: &str, image: &[u8], canvas_width: u32, canvas_height: u32, image_format: ImageFormat) { + match image_format { ImageFormat::Png => dump_png( path, image, canvas_width, canvas_height), ImageFormat::Webp => dump_webp(path, image, canvas_width, canvas_height), } @@ -40,6 +40,8 @@ impl App { } fn dump_png(path: &str, image: &[u8], canvas_width: u32, canvas_height: u32) { + let path = path.to_owned() + ".png"; + let file = File::create(path).expect("unable to create file"); let file_buffer = BufWriter::new(file); @@ -54,6 +56,8 @@ fn dump_png(path: &str, image: &[u8], canvas_width: u32, canvas_height: u32) { } fn dump_webp(path: &str, image: &[u8], canvas_width: u32, canvas_height: u32) { + let path = path.to_owned() + ".webp"; + let encoder = webp::Encoder::from_rgb(&image[..], canvas_width, canvas_height); let data = encoder.encode_lossless(); diff --git a/source/benoit/benoit/app/handle_keys.rs b/source/benoit/benoit/app/handle_keys.rs index 3f9c0dd..73d47f7 100644 --- a/source/benoit/benoit/app/handle_keys.rs +++ b/source/benoit/benoit/app/handle_keys.rs @@ -21,39 +21,41 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::{PRECISION}; +use crate::benoit::PRECISION; use crate::benoit::app::App; -use crate::benoit::fractal::Fractal; -use crate::benoit::palette::Palette; -use crate::benoit::render::{IteratorFunction, PointRenderer}; -use crate::benoit::rendering::Rendering; +use crate::benoit::configuration::Configuration; +use crate::benoit::renderer::Renderer; extern crate rug; extern crate sdl2; -use rug::Float; -use sdl2::keyboard::Scancode; +use rug::{Assign, Float}; +use sdl2::keyboard::{KeyboardState, Scancode}; pub const MIN_COLOUR_RANGE: f32 = 2.0; impl App { #[must_use] - pub fn handle_keys(&mut self, scan_code: Scancode) -> bool { + pub fn handle_keys(&mut self, scan_code: Scancode, state: KeyboardState) -> bool { + if scan_code == Scancode::C { self.do_render = true }; + + if state.is_scancode_pressed(Scancode::LShift) { return self.handle_shift_keys(scan_code) }; + match scan_code { - Scancode::C => self.do_render = true, Scancode::Escape => return true, Scancode::F1 => self.do_textual_feedback = !self.do_textual_feedback, - Scancode::LAlt => (self.fractal, self.multibrot_exponent, self.iterator_function) = cycle_fractal(self.fractal, -0x1), - Scancode::LCtrl => self.inverse = toggle_inverse(self.inverse), - Scancode::Left => self.palette = cycle_palette(self.palette, -0x1), - Scancode::RAlt => (self.fractal, self.multibrot_exponent, self.iterator_function) = cycle_fractal(self.fractal, 0x1), - Scancode::Right => self.palette = cycle_palette(self.palette, 0x1), - Scancode::Tab => (self.rendering, self.point_renderer) = toggle_julia(self.rendering), - Scancode::Z => eprintln!("c = {}{:+}i, {}x @ {} iter. (range.: {:.3})", &self.centre_real, &self.centre_imag, &self.zoom, self.max_iter_count, self.colour_range), + Scancode::LAlt => self.cycle_fractal(-0x1), + Scancode::LCtrl => self.toggle_inverse(), + Scancode::Left => self.cycle_palette(-0x1), + Scancode::RAlt => self.cycle_fractal(0x1), + Scancode::Right => self.cycle_palette(0x1), + Scancode::Tab => self.cycle_rendering(), + Scancode::X => self.reset_viewport(), + Scancode::Z => self.dump_info(), _ => {}, - } + }; - self.handle_translation(scan_code); + self.translate(scan_code); self.max_iter_count = match scan_code { Scancode::F => self.max_iter_count * 0x2, @@ -72,7 +74,26 @@ impl App { return false; } - fn handle_translation(&mut self, scan_code: Scancode) { + #[must_use] + fn handle_shift_keys(&mut self, scan_code: Scancode) -> bool { + let translate_ammount = Float::with_val(PRECISION, 4.0 / 64.0 / &self.zoom); + + match scan_code { + Scancode::A => self.extra.real -= &translate_ammount, + Scancode::D => self.extra.real += &translate_ammount, + _ => {}, + }; + + match scan_code { + Scancode::S => self.extra.imag -= &translate_ammount, + Scancode::W => self.extra.imag += &translate_ammount, + _ => {}, + }; + + return false; + } + + fn translate(&mut self, scan_code: Scancode) { const ZOOM_FACTOR: f32 = 1.0 + 1.0 / 4.0; match scan_code { @@ -81,67 +102,74 @@ impl App { _ => {}, }; - let translate_ammount = { - let mut ammount = Float::with_val(PRECISION, 4.0); - ammount /= 16.0; - ammount /= &self.zoom; - - ammount - }; + let translate_ammount = Float::with_val(PRECISION, 4.0 / 16.0 / &self.zoom); match scan_code { - Scancode::A => self.centre_real -= &translate_ammount, - Scancode::D => self.centre_real += &translate_ammount, + Scancode::A => self.centre.real -= &translate_ammount, + Scancode::D => self.centre.real += &translate_ammount, _ => {}, }; match scan_code { - Scancode::S => self.centre_imag -= &translate_ammount, - Scancode::W => self.centre_imag += &translate_ammount, + Scancode::S => self.centre.imag -= &translate_ammount, + Scancode::W => self.centre.imag += &translate_ammount, _ => {}, }; } -} -fn cycle_fractal(fractal: Fractal, distance: i8) -> (Fractal, f32, IteratorFunction) { - let fractal = fractal + distance; - let exponent = fractal.get_exponent(); + fn cycle_fractal(&mut self, distance: i8) { + self.fractal.cycle(distance); + + eprintln!("renderer the {}", self.fractal.kind().name()); + } - let iterator_function = fractal.get_iterator(); + fn cycle_rendering(&mut self) { + let renderer = self.renderer.cycle(); - eprintln!("rendering the {}", fractal.get_name()); + match renderer { + Renderer::Julia => eprintln!("enabled the julia set"), + Renderer::Normal => eprintln!("disabled the julia set"), + }; - return (fractal, exponent, iterator_function); -} + self.renderer = renderer; + } -fn toggle_julia(rendering: Rendering) -> (Rendering, PointRenderer) { - let rendering = rendering.cycle(); + fn toggle_inverse(&mut self) { + let inverse = !self.fractal.inverse(); - let point_renderer = rendering.get_point_renderer(); + match inverse { + false => eprintln!("reverting fractal"), + true => eprintln!("inverting fractals"), + }; + + self.fractal.set_inverse(inverse); + } - match rendering { - Rendering::Julia => eprintln!("enabled the julia set"), - Rendering::Normal => eprintln!("disabled the julia set"), - }; + fn cycle_palette(&mut self, direction: i8) { + let palette = self.palette.cycle(direction); - return (rendering, point_renderer); -} + eprintln!("using palette \"{}\"", palette.name()); -fn toggle_inverse(inverse: bool) -> bool { - let inverse = !inverse; + self.palette = palette; + } - match inverse { - false => eprintln!("reverting fractal"), - true => eprintln!("inverting fractals"), - }; + fn reset_viewport(&mut self) { + self.centre.real.assign(Configuration::DEFAULT_CENTRE_REAL); + self.centre.imag.assign(Configuration::DEFAULT_CENTRE_IMAG); + self.zoom.assign( Configuration::DEFAULT_ZOOM); - return inverse; -} + self.extra.real.assign(Configuration::DEFAULT_EXTRA_REAL); + self.extra.imag.assign(Configuration::DEFAULT_EXTRA_IMAG); -fn cycle_palette(palette: Palette, direction: i8) -> Palette { - let palette = palette.cycle(direction); + self.max_iter_count = Configuration::DEFAULT_MAX_ITER_COUNT; - eprintln!("using palette \"{}\"", palette.get_name()); + self.colour_range = Configuration::DEFAULT_COLOUR_RANGE; + } - return palette; + fn dump_info(&self) { + eprintln!("info dump:"); + eprintln!(" c = {}{:+}i ({}x)", &self.centre.real, &self.centre.imag, &self.zoom); + eprintln!(" w = {}{:+}i", &self.extra.real, &self.extra.imag); + eprintln!(" max. iter.: {}, col. range: {}", self.max_iter_count, self.colour_range); + } } diff --git a/source/benoit/benoit/app/initialise.rs b/source/benoit/benoit/app/initialise.rs index f4b4f8e..2876e97 100644 --- a/source/benoit/benoit/app/initialise.rs +++ b/source/benoit/benoit/app/initialise.rs @@ -22,6 +22,7 @@ */ use crate::benoit::app::App; +use crate::benoit::complex::Complex; use crate::benoit::configuration::Configuration; use crate::benoit::video::Video; @@ -62,23 +63,21 @@ impl App { return App { thread_count: thread_count, - fractal: configuration.fractal, - rendering: configuration.rendering, + fractal: configuration.fractal, + renderer: configuration.renderer, canvas_width: configuration.canvas_width, canvas_height: configuration.canvas_height, scale: configuration.scale, - frame_count: configuration.frame_count, + frame_start: configuration.frame_start, + frame_stop: configuration.frame_stop, - centre_real: configuration.centre_real, - centre_imag: configuration.centre_imag, - zoom: configuration.zoom, + centre: Complex { real: configuration.centre_real, imag: configuration.centre_imag }, + zoom: configuration.zoom, - max_iter_count: configuration.max_iter_count, - - inverse: configuration.inverse, + extra: Complex { real: configuration.extra_real, imag: configuration.extra_imag }, - multibrot_exponent: configuration.fractal.get_exponent(), + max_iter_count: configuration.max_iter_count, palette: configuration.palette, colour_range: configuration.colour_range, @@ -86,14 +85,11 @@ impl App { dump_path: configuration.dump_path, image_format: configuration.image_format, - video: video, - interactive: configuration.interactive, do_render: true, do_textual_feedback: false, - point_renderer: configuration.rendering.get_point_renderer(), - iterator_function: configuration.fractal.get_iterator(), + video: video, }; } } diff --git a/source/benoit/benoit/app/interactive.rs b/source/benoit/benoit/app/interactive.rs index a5cc51f..94db797 100644 --- a/source/benoit/benoit/app/interactive.rs +++ b/source/benoit/benoit/app/interactive.rs @@ -21,10 +21,8 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::FeedbackInfo; use crate::benoit::app::App; -use crate::benoit::render::{colour, render}; -use crate::benoit::rendering::Rendering; +use crate::benoit::complex::Complex; use crate::benoit::video::Video; extern crate rug; @@ -46,12 +44,11 @@ impl App { // Used for colouring: let mut prev_max_iter_count = self.max_iter_count; - let mut prev_multibrot_exponent = self.multibrot_exponent; + let mut prev_multibrot_exponent = self.fractal.exponent(); // Used for translation feedback: - let mut prev_centre_real = self.centre_real.clone(); - let mut prev_centre_imag = self.centre_imag.clone(); - let mut prev_zoom = self.zoom.clone(); + let mut prev_centre = self.centre.clone(); + let mut prev_zoom = self.zoom.clone(); loop { let frame_start = Instant::now(); @@ -59,29 +56,31 @@ impl App { if self.poll_events(&mut event_pump) { break } if self.do_render { - eprint!("rendering..."); - - let time_start = Instant::now(); - - render(&mut iter_count_buffer[..], &mut square_dist_buffer[..], self.canvas_width, self.canvas_height, &self.centre_real, &self.centre_imag, &self.zoom, self.max_iter_count, self.inverse, self.point_renderer, self.iterator_function); - let render_time = time_start.elapsed(); - - eprintln!(" {:.3}ms", render_time.as_micros() as f32 / 1000.0); + self.render(&mut iter_count_buffer[..], &mut square_dist_buffer[..]); prev_max_iter_count = self.max_iter_count; - prev_multibrot_exponent = self.multibrot_exponent; + prev_multibrot_exponent = self.fractal.exponent(); - prev_centre_real.assign(&self.centre_real); - prev_centre_imag.assign(&self.centre_imag); - prev_zoom.assign(&self.zoom); + prev_centre.assign(&self.centre); + prev_zoom.assign( &self.zoom); self.do_render = false; - } + }; - colour(&mut image[..], self.canvas_width, self.canvas_height, prev_multibrot_exponent, prev_max_iter_count.min(self.max_iter_count), self.colour_range, self.palette, &iter_count_buffer[..], &square_dist_buffer[..]); + self.renderer.colour( + &mut image[..], + self.palette, + self.canvas_width, + self.canvas_height, + prev_multibrot_exponent, + prev_max_iter_count.min(self.max_iter_count), + self.colour_range, + &iter_count_buffer[..], + &square_dist_buffer[..], + ); video.draw(&image[..], self.canvas_width, self.canvas_height, self.scale); - self.draw_feedback(&mut video, &prev_centre_real, &prev_centre_imag, &prev_zoom); + self.draw_feedback(&mut video, &prev_centre, &prev_zoom); video.update(); @@ -91,43 +90,50 @@ impl App { return 0x0; } - pub fn draw_feedback(&self, video: &mut Video, prev_centre_real: &Float, prev_centre_imag: &Float, prev_zoom: &Float) { - let julia = match self.rendering { - Rendering::Julia => true, - _ => false, - }; + pub fn render(&self, iter_count_buffer: &mut [u32], square_dist_buffer: &mut [f32]) { + eprint!("rendering..."); + + let time_start = Instant::now(); + + self.renderer.render( + iter_count_buffer, + square_dist_buffer, + &self.fractal, + self.canvas_width, + self.canvas_height, + &self.centre, + &self.zoom, + &self.extra, + self.max_iter_count, + ); + + let render_time = time_start.elapsed(); + eprintln!(" {:.3}ms", render_time.as_micros() as f32 / 1000.0); + } + + pub fn draw_feedback(&self, video: &mut Video, prev_centre: &Complex, prev_zoom: &Float) { if { // Don't draw translation feedback if rendering a // Julia set or if we haven't done any viewport // translations. - !julia - && (&self.centre_real != prev_centre_real - || &self.centre_imag != prev_centre_imag - || &self.zoom != prev_zoom) + &self.centre.real != &prev_centre.real + || &self.centre.imag != &prev_centre.imag + || &self.zoom != prev_zoom }{ - let feedback_info = FeedbackInfo { - prev_centre_real: prev_centre_real, - prev_centre_imag: prev_centre_imag, - prev_zoom: prev_zoom, - next_centre_real: &self.centre_real, - next_centre_imag: &self.centre_imag, - next_zoom: &self.zoom, - }; - - video.draw_translation_feedback(self.canvas_width, self.canvas_height, self.scale, &feedback_info); + video.draw_translation_feedback(self.canvas_width, self.canvas_height, self.scale, prev_centre, prev_zoom, &self.centre, &self.zoom); } - if self.do_textual_feedback { video.draw_textual_feedback(&self.centre_real, &self.centre_imag, &self.zoom, self.max_iter_count) }; + if self.do_textual_feedback { video.draw_textual_feedback(&self.centre, &self.zoom, self.max_iter_count) }; } pub fn print_controls() { println!("Controls:"); - println!("- \u{1B}[1mW\u{1B}[0m Translate up"); - println!("- \u{1B}[1mA\u{1B}[0m Translate left"); - println!("- \u{1B}[1mS\u{1B}[0m Translate down"); - println!("- \u{1B}[1mD\u{1B}[0m Translate right"); + println!("- \u{1B}[1mW\u{1B}[0m Translate +Im"); + println!("- \u{1B}[1mA\u{1B}[0m Translate -Re"); + println!("- \u{1B}[1mS\u{1B}[0m Translate -Im"); + println!("- \u{1B}[1mD\u{1B}[0m Translate +Re"); println!(); println!("- \u{1B}[1mQ\u{1B}[0m Zoom out"); println!("- \u{1B}[1mE\u{1B}[0m Zoom in"); @@ -135,20 +141,28 @@ impl App { println!("- \u{1B}[1mR\u{1B}[0m Decrease max. iteration count"); println!("- \u{1B}[1mF\u{1B}[0m Increase max. iteration count"); println!(); - println!("- \u{1B}[1mLeft Alt\u{1B}[0m Cycle to previous fractal"); - println!("- \u{1B}[1mRight Alt\u{1B}[0m Cycle to next fractal"); - println!("- \u{1B}[1mTab\u{1B}[0m Toggle Julia"); - println!("- \u{1B}[1mLeft Ctrl\u{1B}[0m Toggle inverse"); + println!("- \u{1B}[1mLEFT ALT\u{1B}[0m Cycle to previous fractal"); + println!("- \u{1B}[1mRIGHT ALT\u{1B}[0m Cycle to next fractal"); + println!("- \u{1B}[1mTAB\u{1B}[0m Toggle Julia"); + println!("- \u{1B}[1mLEFT CTRL\u{1B}[0m Toggle inverse"); println!(); - println!("- \u{1B}[1mLeft\u{1B}[0m Cycle to previous palette"); - println!("- \u{1B}[1mRight\u{1B}[0m Cycle to next palette"); - println!("- \u{1B}[1mUp\u{1B}[0m Increase colour range"); - println!("- \u{1B}[1mDown\u{1B}[0m Decrease colour range"); + println!("- \u{1B}[1mLEFT\u{1B}[0m Cycle to previous palette"); + println!("- \u{1B}[1mRIGHT\u{1B}[0m Cycle to next palette"); + println!("- \u{1B}[1mUP\u{1B}[0m Increase colour range"); + println!("- \u{1B}[1mDOWN\u{1B}[0m Decrease colour range"); println!(); println!("- \u{1B}[1mF1\u{1B}[0m Toggle textual feedback"); println!("- \u{1B}[1mZ\u{1B}[0m Print centre value (c)"); println!(); println!("- \u{1B}[1mC\u{1B}[0m Render frame"); println!(); + println!("Controls (holding \u{1B}[1mSHIFT\u{1B}[0m):"); + println!("- \u{1B}[1mW\u{1B}[0m Perturbate/translate +Im"); + println!("- \u{1B}[1mA\u{1B}[0m Perturbate/translate -Re"); + println!("- \u{1B}[1mS\u{1B}[0m Perturbate/translate -Im"); + println!("- \u{1B}[1mD\u{1B}[0m Perturbate/translate +Re"); + println!(); + println!("- \u{1B}[1mC\u{1B}[0m Render frame"); + println!(); } }
\ No newline at end of file diff --git a/source/benoit/benoit/app/poll_events.rs b/source/benoit/benoit/app/poll_events.rs index 2ba81fa..af200ec 100644 --- a/source/benoit/benoit/app/poll_events.rs +++ b/source/benoit/benoit/app/poll_events.rs @@ -30,8 +30,13 @@ use sdl2::event::Event; impl App { #[must_use] - pub fn poll_events(&mut self, event_pump: &mut EventPump) -> bool { - for event in event_pump.poll_iter() { + pub fn poll_events(&mut self, pump: &mut EventPump) -> bool { + loop { + let event = match pump.poll_event() { + Some(event) => event, + None => break, + }; + let quit = match event { Event::KeyDown { timestamp: _, @@ -40,7 +45,11 @@ impl App { scancode: scan_code, keymod: _, repeat: _, - } => self.handle_keys(scan_code.unwrap()), + } => { + let state = pump.keyboard_state(); + + self.handle_keys(scan_code.unwrap(), state) + }, Event::Quit { .. } => true, _ => false, }; diff --git a/source/benoit/benoit/app/run.rs b/source/benoit/benoit/app/run.rs index cc8e3bb..8d6ee0d 100644 --- a/source/benoit/benoit/app/run.rs +++ b/source/benoit/benoit/app/run.rs @@ -38,10 +38,7 @@ impl App { return match self.interactive { true => self.interactive(), - false => match self.frame_count { - 0x1 => self.still(), - _ => self.animate(), - }, + false => self.animate(), }; } } diff --git a/source/benoit/benoit/app/still.rs b/source/benoit/benoit/app/still.rs deleted file mode 100644 index a5b6cc1..0000000 --- a/source/benoit/benoit/app/still.rs +++ /dev/null @@ -1,55 +0,0 @@ -/* - 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::app::App; -use crate::benoit::render::{colour, render}; - -use std::time::Instant; - -impl App { - pub fn still(&self) -> i32 { - assert_eq!(self.frame_count, 0x1); - - let (mut iter_count_buffer, mut square_dist_buffer, mut image) = App::allocate_buffers(self.canvas_width, self.canvas_height); - - eprint!("rendering at {}{:+}i ({}x)...", self.centre_real.to_f32(), self.centre_imag.to_f32(), self.zoom.to_f32()); - let time_start = Instant::now(); - - render(&mut iter_count_buffer[..], &mut square_dist_buffer[..], self.canvas_width, self.canvas_height, &self.centre_real, &self.centre_imag, &self.zoom, self.max_iter_count, self.inverse, self.point_renderer, self.iterator_function); - let render_time = time_start.elapsed(); - - eprint!(" {:.3}ms, colouring...", render_time.as_micros() as f32 / 1000.0); - - colour(&mut image[..], self.canvas_width, self.canvas_height, self.multibrot_exponent, self.max_iter_count, self.colour_range, self.palette, &iter_count_buffer[..], &square_dist_buffer[..]); - let colour_time = time_start.elapsed() - render_time; - - eprint!(" {:.3}ms...", colour_time.as_micros() as f32 / 1000.0); - - let path = App::image_filename(format!("{}/render.webp", self.dump_path).as_str(), self.image_format); - self.dump(path.as_str(), &image, self.canvas_width, self.canvas_height); - - eprintln!(" done"); - - return 0x0; - } -} diff --git a/source/benoit/benoit/app/image_filename.rs b/source/benoit/benoit/complex.rs index 7abc71a..146bd18 100644 --- a/source/benoit/benoit/app/image_filename.rs +++ b/source/benoit/benoit/complex.rs @@ -21,17 +21,25 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::ImageFormat; -use crate::benoit::app::App; - -impl App { - #[must_use] - pub fn image_filename(name: &str, image_format: ImageFormat) -> String { - let file_extension = match image_format { - ImageFormat::Png => ".png", - ImageFormat::Webp => ".webp", +extern crate rug; + +use rug::{Assign, Float}; + +pub struct Complex { + pub real: Float, + pub imag: Float, +} + +impl Complex { + pub fn clone(&self) -> Self { + return Complex { + real: self.real.clone(), + imag: self.imag.clone(), }; + } - return name.to_owned() + file_extension; + pub fn assign(&mut self, other: &Self) { + self.real.assign(&other.real); + self.imag.assign(&other.imag); } } diff --git a/source/benoit/benoit/configuration.rs b/source/benoit/benoit/configuration.rs index 2514ef8..7e0c97a 100644 --- a/source/benoit/benoit/configuration.rs +++ b/source/benoit/benoit/configuration.rs @@ -21,37 +21,37 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::{ImageFormat, PRECISION}; +use crate::benoit::{fractal, ImageFormat, PRECISION}; use crate::benoit::fractal::Fractal; use crate::benoit::palette::Palette; -use crate::benoit::rendering::Rendering; +use crate::benoit::renderer::Renderer; extern crate rug; -extern crate toml; use rug::Float; -use std::fs::read; -use std::str::FromStr; -use toml::{Table, Value}; + +pub mod load; pub struct Configuration { pub thread_count: u32, - pub fractal: Fractal, - pub rendering: Rendering, + pub fractal: Fractal, + pub renderer: Renderer, pub canvas_width: u32, pub canvas_height: u32, pub scale: u32, - pub frame_count: u32, + pub frame_start: u32, + pub frame_stop: u32, pub centre_real: Float, pub centre_imag: Float, pub zoom: Float, - pub max_iter_count: u32, + pub extra_real: Float, + pub extra_imag: Float, - pub inverse: bool, + pub max_iter_count: u32, pub palette: Palette, pub colour_range: f32, @@ -63,29 +63,41 @@ pub struct Configuration { } impl Configuration { + pub const DEFAULT_CENTRE_REAL: f64 = 0.0; + pub const DEFAULT_CENTRE_IMAG: f64 = 0.0; + pub const DEFAULT_ZOOM: f64 = 1.0; + + pub const DEFAULT_EXTRA_REAL: f64 = 0.0; + pub const DEFAULT_EXTRA_IMAG: f64 = 0.0; + + pub const DEFAULT_MAX_ITER_COUNT: u32 = 0x100; + pub const DEFAULT_COLOUR_RANGE: f32 = 64.0; + #[must_use] pub fn default() -> Configuration { return Configuration { thread_count: 0x0, - fractal: Fractal::Mandelbrot, - rendering: Rendering::Normal, + fractal: Fractal::new(fractal::Kind::Mandelbrot, false), + renderer: Renderer::Normal, canvas_width: 0x100, canvas_height: 0xC0, scale: 0x2, - frame_count: 0x10, + frame_start: 0x10, + frame_stop: 0x10, - centre_real: Float::with_val(PRECISION, 0.0), - centre_imag: Float::with_val(PRECISION, 0.0), - zoom: Float::with_val(PRECISION, 1.0), + centre_real: Float::with_val(PRECISION, Self::DEFAULT_CENTRE_REAL), + centre_imag: Float::with_val(PRECISION, Self::DEFAULT_CENTRE_IMAG), + zoom: Float::with_val(PRECISION, Self::DEFAULT_ZOOM), - max_iter_count: 0x100, + extra_real: Float::with_val(PRECISION, Self::DEFAULT_EXTRA_REAL), + extra_imag: Float::with_val(PRECISION, Self::DEFAULT_EXTRA_IMAG), - inverse: false, + max_iter_count: Self::DEFAULT_MAX_ITER_COUNT, palette: Palette::Fire, - colour_range: 64.0, + colour_range: Self::DEFAULT_COLOUR_RANGE, dump_path: "./render".to_string(), image_format: ImageFormat::Png, @@ -93,142 +105,4 @@ impl Configuration { interactive: true, }; } - - #[must_use] - pub fn load(path: &str) -> Configuration { - eprintln!("loading configuration at \"{path}\""); - - let mut configuration = Configuration::default(); - - configuration.interactive = false; - - let configuration_text = match read(path) { - Ok(content) => String::from_utf8_lossy(&content).to_string(), - Err(..) => panic!("unable to read configuration file"), - }; - - let configuration_table = Table::from_str(configuration_text.as_str()).expect("unable to parse configuration"); - - get_integer(&mut configuration.thread_count, &configuration_table, "thread_count"); - - if let Some(name) = get_string(&configuration_table, "fractal") { - configuration.fractal = match name.as_str() { - "burningship" => Fractal::BurningShip, - "mandelbrot" => Fractal::Mandelbrot, - "multibrot3" => Fractal::Multibrot3, - "tricorn" => Fractal::Tricorn, - name => panic!("invalid fractal kind \"{name}\""), - } - } - - if let Some(name) = get_string(&configuration_table, "rendering") { - configuration.rendering = match name.as_str() { - "julia" => Rendering::Julia, - "normal" => Rendering::Normal, - name => panic!("invalid rendering method \"{name}\""), - }; - } - - get_integer(&mut configuration.canvas_width, &configuration_table, "canvas_width"); - get_integer(&mut configuration.canvas_height, &configuration_table, "canvas_height"); - get_integer(&mut configuration.frame_count, &configuration_table, "frame_count"); - - get_bigfloat(&mut configuration.centre_real, &configuration_table, "real"); - get_bigfloat(&mut configuration.centre_imag, &configuration_table, "imaginary"); - get_bigfloat(&mut configuration.zoom, &configuration_table, "zoom"); - - get_integer(&mut configuration.max_iter_count, &configuration_table, "maximum_iteration_count"); - - if let Some(name) = get_string(&configuration_table, "palette") { - configuration.palette = match name.as_str() { - "ancient" => Palette::Ancient, - "fire" => Palette::Fire, - "greyscale" => Palette::Greyscale, - "hsv" => Palette::Hsv, - "lch" => Palette::Lch, - "sapphire" => Palette::Sapphire, - name => panic!("invalid palette \"{name}\""), - }; - } - - get_float(&mut configuration.colour_range, &configuration_table, "colour_range"); - - if let Some(path) = get_string(&configuration_table, "dump_path") { - configuration.dump_path = path.clone(); - } - - if let Some(name) = get_string(&configuration_table, "image_format") { - configuration.image_format = match name.as_str() { - "png" => ImageFormat::Png, - "webp" => ImageFormat::Webp, - name => panic!("invalid image format \"{name}\""), - }; - } - - match check_configuration(&configuration) { - Err(message) => panic!("invalid configuration: {message}"), - _ => {}, - } - - return configuration; - } -} - -fn check_configuration(configuration: &Configuration) -> Result<(), &str> { - // We allow thread counts of zero as those signal - // automatic thread count detection. - if configuration.canvas_width == 0x0 { - return Err("only non-zero values for canvas_width are allowed"); - } else if configuration.scale == 0x0 { - return Err("only non-zero values for scale are allowed"); - } else if configuration.frame_count == 0x0 { - return Err("only non-zero values for frame_count are allowed"); - } else if configuration.max_iter_count == 0x0 { - return Err("only non-zero values for maximum_iteration_count are allowed"); - } - - return Ok(()); -} - -fn get_value<'a>(table: &'a Table, name: &str) -> Option<&'a Value> { - if !table.contains_key(name) { return None }; - - return Some(&table[name]); -} - -fn get_integer(buffer: &mut u32, table: &Table, name: &str) { - match get_value(table, name) { - Some(Value::Integer(value)) => *buffer = (*value) as u32, - Some(_) => panic!("\"{name}\" should be an integer"), - _ => {}, - }; -} - -fn get_float(buffer: &mut f32, table: &Table, name: &str) { - match get_value(table, name) { - Some(Value::Float(value)) => *buffer = (*value) as f32, - Some(_) => panic!("\"{name}\" should be a float"), - _ => {}, - }; -} - -fn get_bigfloat(buffer: &mut Float, table: &Table, name: &str) { - return match get_value(table, name) { - Some(Value::String(string)) => { - *buffer = match Float::parse(string) { - Ok(value) => Float::with_val(PRECISION, value), - _ => panic!("invalid format of \"{name}\""), - } - }, - Some(_) => panic!("\"{name}“ should be a quoted float"), - _ => {}, - }; -} - -fn get_string(table: &Table, name: &str) -> Option<String> { - return match get_value(table, name) { - Some(Value::String(value)) => Some(value.clone()), - Some(_) => panic!("\"{name}\" should be a string"), - _ => None, - }; } diff --git a/source/benoit/benoit/configuration/load.rs b/source/benoit/benoit/configuration/load.rs new file mode 100644 index 0000000..3f5ec11 --- /dev/null +++ b/source/benoit/benoit/configuration/load.rs @@ -0,0 +1,199 @@ +/* + 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, ImageFormat, PRECISION}; +use crate::benoit::configuration::Configuration; +use crate::benoit::palette::Palette; +use crate::benoit::renderer::Renderer; + +extern crate rug; +extern crate toml; + +use rug::Float; +use std::fs::read; +use std::str::FromStr; +use toml::{Table, Value}; + +impl Configuration { + #[must_use] + pub fn load(path: &str) -> Configuration { + eprintln!("loading configuration at \"{path}\""); + + let mut configuration = Configuration::default(); + + configuration.interactive = false; + + let configuration_text = match read(path) { + Ok(content) => String::from_utf8_lossy(&content).to_string(), + Err(..) => panic!("unable to read configuration file"), + }; + + let configuration_table = Table::from_str(configuration_text.as_str()).expect("unable to parse configuration"); + + get_integer(&mut configuration.thread_count, &configuration_table, "thread_count"); + + if let Some(name) = get_string(&configuration_table, "renderer") { + configuration.renderer = match name.as_str() { + "julia" => Renderer::Julia, + "normal" => Renderer::Normal, + name => panic!("invalid renderer method \"{name}\""), + }; + } + + if let Some(name) = get_string(&configuration_table, "fractal") { + configuration.fractal.set_kind(match name.as_str() { + "burningship" => fractal::Kind::BurningShip, + "mandelbrot" => fractal::Kind::Mandelbrot, + "multibrot3" => fractal::Kind::Multibrot3, + "tricorn" => fractal::Kind::Tricorn, + name => panic!("invalid fractal kind \"{name}\""), + }); + } + + { + let mut inverse = false; + get_boolean(&mut inverse, &configuration_table, "inverse"); + configuration.fractal.set_inverse(inverse); + } + + get_integer(&mut configuration.canvas_width, &configuration_table, "canvas_width"); + get_integer(&mut configuration.canvas_height, &configuration_table, "canvas_height"); + get_integer(&mut configuration.frame_start, &configuration_table, "frame_start"); + get_integer(&mut configuration.frame_stop, &configuration_table, "frame_stop"); + + get_bigfloat(&mut configuration.centre_real, &configuration_table, "real"); + get_bigfloat(&mut configuration.centre_imag, &configuration_table, "imaginary"); + get_bigfloat(&mut configuration.extra_real, &configuration_table, "extra_real"); + get_bigfloat(&mut configuration.extra_imag, &configuration_table, "extra_imaginary"); + get_bigfloat(&mut configuration.zoom, &configuration_table, "zoom"); + + get_integer(&mut configuration.max_iter_count, &configuration_table, "maximum_iteration_count"); + + if let Some(name) = get_string(&configuration_table, "palette") { + configuration.palette = match name.as_str() { + "ancient" => Palette::Ancient, + "fire" => Palette::Fire, + "greyscale" => Palette::Greyscale, + "hsv" => Palette::Hsv, + "lch" => Palette::Lch, + "sapphire" => Palette::Sapphire, + name => panic!("invalid palette \"{name}\""), + }; + } + + get_float(&mut configuration.colour_range, &configuration_table, "colour_range"); + + if let Some(path) = get_string(&configuration_table, "dump_path") { + configuration.dump_path = path.clone(); + } + + if let Some(name) = get_string(&configuration_table, "image_format") { + configuration.image_format = match name.as_str() { + "png" => ImageFormat::Png, + "webp" => ImageFormat::Webp, + name => panic!("invalid image format \"{name}\""), + }; + } + + match check_configuration(&configuration) { + Err(message) => panic!("invalid configuration: {message}"), + _ => {}, + } + + return configuration; + } +} + +fn check_configuration(configuration: &Configuration) -> Result<(), &str> { + // We allow thread counts of zero as those signal + // automatic thread count detection. + + if configuration.canvas_width == 0x0 { + return Err("only non-zero values for canvas_width are allowed"); + } + + if configuration.scale == 0x0 { + return Err("only non-zero values for scale are allowed"); + } + + if configuration.frame_start > configuration.frame_stop { + return Err("frame_start may not be greater than frame_stop"); + } + + if configuration.max_iter_count == 0x0 { + return Err("only non-zero values for maximum_iteration_count are allowed"); + } + + return Ok(()); +} + +fn get_value<'a>(table: &'a Table, name: &str) -> Option<&'a Value> { + if !table.contains_key(name) { return None }; + + return Some(&table[name]); +} + +fn get_boolean(buffer: &mut bool, table: &Table, name: &str) { + match get_value(table, name) { + Some(Value::Boolean(value)) => *buffer = *value, + Some(_) => panic!("\"{name}\" should be a boolean"), + _ => {}, + }; +} + +fn get_integer(buffer: &mut u32, table: &Table, name: &str) { + match get_value(table, name) { + Some(Value::Integer(value)) => *buffer = (*value) as u32, + Some(_) => panic!("\"{name}\" should be an integer"), + _ => {}, + }; +} + +fn get_float(buffer: &mut f32, table: &Table, name: &str) { + match get_value(table, name) { + Some(Value::Float(value)) => *buffer = (*value) as f32, + Some(_) => panic!("\"{name}\" should be a float"), + _ => {}, + }; +} + +fn get_bigfloat(buffer: &mut Float, table: &Table, name: &str) { + return match get_value(table, name) { + Some(Value::String(string)) => { + *buffer = match Float::parse(string) { + Ok(value) => Float::with_val(PRECISION, value), + _ => panic!("invalid format of \"{name}\""), + } + }, + Some(_) => panic!("\"{name}“ should be a quoted float"), + _ => {}, + }; +} + +fn get_string(table: &Table, name: &str) -> Option<String> { + return match get_value(table, name) { + Some(Value::String(value)) => Some(value.clone()), + Some(_) => panic!("\"{name}\" should be a string"), + _ => None, + }; +} diff --git a/source/benoit/benoit/fractal.rs b/source/benoit/benoit/fractal.rs index 5cefcfe..7ca6035 100644 --- a/source/benoit/benoit/fractal.rs +++ b/source/benoit/benoit/fractal.rs @@ -25,10 +25,15 @@ use crate::benoit::render::IteratorFunction; use crate::benoit::render::iterate; use std::mem::transmute; -use std::ops::Add; + +pub struct Fractal { + kind: Kind, + inverse: bool, +} #[derive(Clone, Copy)] -pub enum Fractal { +#[repr(u8)] +pub enum Kind { BurningShip, Mandelbrot, Multibrot3, @@ -36,54 +41,80 @@ pub enum Fractal { } impl Fractal { - const MAX: u8 = Fractal::Tricorn as u8; - - pub fn get_name(self) -> &'static str { - return match self { - Fractal::BurningShip => "burning ship", - Fractal::Mandelbrot => "mandelbrot set", - Fractal::Multibrot3 => "multibrot (d=3)", - Fractal::Tricorn => "tricorn", + pub const fn new(kind: Kind, inverse: bool) -> Self { + let fractal = Fractal { + kind: kind, + inverse: inverse, }; + + return fractal; } - pub fn get_exponent(self) -> f32 { - return match self { - Fractal::BurningShip => 2.0, - Fractal::Mandelbrot => 2.0, - Fractal::Multibrot3 => 3.0, - Fractal::Tricorn => 2.0, + #[must_use] + pub fn kind(&self) -> Kind { + return self.kind; + } + + #[must_use] + pub fn inverse(&self) -> bool { + return self.inverse; + } + + #[must_use] + pub fn exponent(&self) -> f32 { + return match self.kind { + Kind::BurningShip => 2.0, + Kind::Mandelbrot => 2.0, + Kind::Multibrot3 => 3.0, + Kind::Tricorn => 2.0, }; } - pub fn get_iterator(self) -> IteratorFunction { - return match self { - Fractal::BurningShip => iterate::burning_ship, - Fractal::Mandelbrot => iterate::mandelbrot, - Fractal::Multibrot3 => iterate::multibrot3, - Fractal::Tricorn => iterate::tricorn, + pub fn iterator(&self) -> IteratorFunction { + return match self.kind { + Kind::BurningShip => iterate::burning_ship, + Kind::Mandelbrot => iterate::mandelbrot, + Kind::Multibrot3 => iterate::multibrot3, + Kind::Tricorn => iterate::tricorn, }; } -} + pub fn set_kind(&mut self, kind: Kind) { + self.kind = kind; + } -impl Add<i8> for Fractal { - type Output = Fractal; + pub fn set_inverse(&mut self, inverse: bool) { + self.inverse = inverse; + } - fn add(self, direction: i8) -> Self { - assert!(direction != 0x0); + pub fn cycle(&mut self, direction: i8) { + // Not important. + debug_assert!(direction != 0x0); - let raw = self as i8 + direction; + let raw = self.kind as i8 + direction; let raw: u8 = if raw < 0x0 { - Fractal::MAX - } else if raw > Fractal::MAX as i8 { + Kind::MAX + } else if raw > Kind::MAX as i8 { 0x0 } else { raw as u8 }; - let new: Self = unsafe { transmute(raw) }; + let new: Kind = unsafe { transmute(raw) }; + + self.kind = new; + } +} + +impl Kind { + const MAX: u8 = Kind::Tricorn as u8; - return new; + pub fn name(self) -> &'static str { + return match self { + Kind::BurningShip => "burning ship", + Kind::Mandelbrot => "mandelbrot set", + Kind::Multibrot3 => "multibrot (d=3) set", + Kind::Tricorn => "tricorn", + }; } } diff --git a/source/benoit/benoit/palette.rs b/source/benoit/benoit/palette.rs index 5477242..d270682 100644 --- a/source/benoit/benoit/palette.rs +++ b/source/benoit/benoit/palette.rs @@ -62,7 +62,7 @@ static mut DATA_SAPPHIRE: PaletteData = default_palette_data(); fn calculate_palettes() { for palette in all::<Palette>() { let data = palette.get_data_mut(); - let function = palette.get_function(); + let function = palette.function(); for index in 0x0..PALETTE_DATA_LENGTH { let factor = index as f32 / PALETTE_DATA_LENGTH as f32; @@ -79,7 +79,7 @@ impl Palette { const MAX: Self = Palette::Sapphire; #[must_use] - pub fn get_name(self) -> &'static str { + pub fn name(self) -> &'static str { return match self { Palette::Ancient => "ancient", Palette::Fire => "fire", @@ -111,7 +111,7 @@ impl Palette { } #[must_use] - fn get_function(self) -> fn(f32) -> (f32, f32, f32) { + fn function(self) -> fn(f32) -> (f32, f32, f32) { return match self { Palette::Ancient => paint::ancient, Palette::Fire => paint::fire, diff --git a/source/benoit/benoit/render.rs b/source/benoit/benoit/render.rs index 45da9ee..b81679c 100644 --- a/source/benoit/benoit/render.rs +++ b/source/benoit/benoit/render.rs @@ -21,22 +21,14 @@ If not, see <https://www.gnu.org/licenses/>. */ +use crate::benoit::complex::Complex; use crate::benoit::render::render_data::RenderData; -extern crate rug; - -use rug::Float; - -pub mod colour; pub mod colour_data; pub mod iterate; -pub mod render; pub mod render_data; pub mod render_point; -pub use colour::*; -pub use render::*; - -pub type IteratorFunction = fn(&mut Float, &mut Float, &Float, &Float); +pub type IteratorFunction = fn(&mut Complex, &Complex); pub type PointRenderer = fn(&RenderData, u32, u32, IteratorFunction) -> (u32, f32); diff --git a/source/benoit/benoit/render/colour.rs b/source/benoit/benoit/render/colour.rs deleted file mode 100644 index 9debac4..0000000 --- a/source/benoit/benoit/render/colour.rs +++ /dev/null @@ -1,74 +0,0 @@ -/* - 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::palette::{Palette, PALETTE_DATA_LENGTH}; -use crate::benoit::render::colour_data::ColourData; - -extern crate rayon; - -use rayon::prelude::*; -use std::sync::Arc; - -pub fn colour(buffer: &mut [u8], canvas_width: u32, canvas_height: u32, multibrot_exponent: f32, max_iter_count: u32, colour_range: f32, palette: Palette, iter_count_buffer: &[u32], square_dist_buffer: &[f32]) { - let data = Arc::new(ColourData::new(buffer, canvas_width, canvas_height, multibrot_exponent, max_iter_count, colour_range, palette, iter_count_buffer, square_dist_buffer)); - - (0x0..canvas_height).into_par_iter().for_each(|row| { - colour_row(data.clone(), row as u32); - }); -} - -fn colour_row(data: Arc<ColourData>, y: u32) { - let (iter_count_buffer, square_dist_buffer) = data.input_buffers(y); - - let image = data.output_buffers(y); - - let (canvas_width, exponent, max_iter_count, colour_range, palette_data) = data.consts(); - - for x in 0x0..canvas_width { - let x = x as usize; - - let iter_count = unsafe { *iter_count_buffer.get_unchecked( x) }; - let distance = unsafe { *square_dist_buffer.get_unchecked(x) }.sqrt(); - - let (red, green, blue) = if iter_count < max_iter_count { - let factor = (iter_count as f32 + 1.0 - distance.log(exponent).log(exponent)) / colour_range; - - let index = (factor * PALETTE_DATA_LENGTH as f32).round() as usize % PALETTE_DATA_LENGTH; - unsafe { *palette_data.get_unchecked(index) } - } else { - (0.0, 0.0, 0.0) - }; - - let red = (red * 255.0).round() as u8; - let green = (green * 255.0).round() as u8; - let blue = (blue * 255.0).round() as u8; - - unsafe { - let x = x * 0x3; - - *image.get_unchecked_mut(x) = red; - *image.get_unchecked_mut(x + 0x1) = green; - *image.get_unchecked_mut(x + 0x2) = blue; - } - } -} diff --git a/source/benoit/benoit/render/colour_data.rs b/source/benoit/benoit/render/colour_data.rs index 33123db..114a8b0 100644 --- a/source/benoit/benoit/render/colour_data.rs +++ b/source/benoit/benoit/render/colour_data.rs @@ -26,8 +26,7 @@ use crate::benoit::palette::{Palette, PaletteData}; use std::slice::{from_raw_parts, from_raw_parts_mut}; pub struct ColourData { - canvas_width: u32, - canvas_height: u32, + canvas_size: usize, exponent: f32, max_iter_count: u32, @@ -45,8 +44,7 @@ impl ColourData { #[must_use] pub fn new(image: &mut [u8], canvas_width: u32, canvas_height: u32, exponent: f32, max_iter_count: u32, colour_range: f32, palette: Palette, iter_count_buffer: &[u32], square_dist_buffer: &[f32]) -> ColourData { return ColourData { - canvas_width: canvas_width, - canvas_height: canvas_height, + canvas_size: canvas_height as usize * canvas_width as usize, exponent: exponent, max_iter_count: max_iter_count, @@ -62,31 +60,23 @@ impl ColourData { } #[must_use] - pub fn input_buffers(&self, row: u32) -> (&[u32], &[f32]) { - assert!(row < self.canvas_height); - - let offset = row as usize * self.canvas_width as usize; - - let iter_count = unsafe { from_raw_parts(self.iter_count_buffer.add(offset), self.canvas_width as usize) }; - let dist = unsafe { from_raw_parts(self.square_dist_buffer.add(offset), self.canvas_width as usize) }; + pub fn input_buffers(&self) -> (&[u32], &[f32]) { + let iter_count = unsafe { from_raw_parts(self.iter_count_buffer, self.canvas_size) }; + let dist = unsafe { from_raw_parts(self.square_dist_buffer, self.canvas_size) }; return (iter_count, dist); } #[must_use] - pub fn output_buffers(&self, row: u32) -> &mut [u8] { - assert!(row < self.canvas_height); - - let offset = row as usize * self.canvas_width as usize * 0x3; - - let image = unsafe { from_raw_parts_mut(self.image.add(offset), self.canvas_width as usize * 0x3) }; + pub fn output_buffers(&self) -> &mut [u8] { + let image = unsafe { from_raw_parts_mut(self.image, self.canvas_size * 0x3) }; return image; } #[must_use] - pub fn consts(&self) -> (u32, f32, u32, f32, &'static PaletteData) { - return (self.canvas_width, self.exponent, self.max_iter_count, self.colour_range, self.palette_data); + pub fn consts(&self) -> (f32, u32, f32, &'static PaletteData) { + return (self.exponent, self.max_iter_count, self.colour_range, self.palette_data); } } diff --git a/source/benoit/benoit/render/iterate/burning_ship.rs b/source/benoit/benoit/render/iterate/burning_ship.rs index 43e6916..9e4f029 100644 --- a/source/benoit/benoit/render/iterate/burning_ship.rs +++ b/source/benoit/benoit/render/iterate/burning_ship.rs @@ -21,27 +21,25 @@ If not, see <https://www.gnu.org/licenses/>. */ -extern crate rug; +use crate::benoit::complex::Complex; -use rug::Float; - -pub fn burning_ship(za: &mut Float, zb: &mut Float, ca: &Float, cb: &Float) { +pub fn burning_ship(z: &mut Complex, c: &Complex) { // The Burning Ship is different in that - during // iteration - the real and imaginary parts of (z) // are made absolute: // // z(n+1) = (abs(Re(z(n)))+i*abs(Im(z(n))))^2+c. - za.abs_mut(); - zb.abs_mut(); + z.real.abs_mut(); // abs(a) + z.imag.abs_mut(); // abs(b) - let za_temporary = za.clone(); + let za_temporary = z.real.clone(); // abs(a) - za.square_mut(); - *za -= &*zb * &*zb; - *za += ca; + z.real.square_mut(); // abs(a)^2 + z.real -= &z.imag * &z.imag; // abs(a)^2-abs(b)^2 + z.real += &c.real; // abs(a)^2-abs(b)^2+Re(c) - *zb *= za_temporary; - *zb *= 2.0; - *zb += cb; + z.imag *= &za_temporary; // abs(a) + z.imag *= 2.0; // 2*abs(a) + z.imag += &c.imag; // 2*abs(a)+Im(c) } diff --git a/source/benoit/benoit/render/iterate/mandelbrot.rs b/source/benoit/benoit/render/iterate/mandelbrot.rs index 015e2e7..078cc18 100644 --- a/source/benoit/benoit/render/iterate/mandelbrot.rs +++ b/source/benoit/benoit/render/iterate/mandelbrot.rs @@ -21,11 +21,9 @@ If not, see <https://www.gnu.org/licenses/>. */ -extern crate rug; +use crate::benoit::complex::Complex; -use rug::Float; - -pub fn mandelbrot(za: &mut Float, zb: &mut Float, ca: &Float, cb: &Float) { +pub fn mandelbrot(z: &mut Complex, c: &Complex) { // The Mandelbrot Set (M) is defined as the set of // values in the complex plane where the iterating // function @@ -36,7 +34,7 @@ pub fn mandelbrot(za: &mut Float, zb: &mut Float, ca: &Float, cb: &Float) { // // abs(z) = sqrt(Re(z)^2+Im(z)^2) <= 2^2 = 4. - let za_temporary = za.clone(); // a + let za_temporary = z.real.clone(); // a // We can calculate the square of a complex number // as: @@ -46,11 +44,11 @@ pub fn mandelbrot(za: &mut Float, zb: &mut Float, ca: &Float, cb: &Float) { // = a^2+abi+abi-b^2 // = a^2-b^2+2abi. - za.square_mut(); // a^2 - *za -= &*zb * &*zb; // a^2-b^2 - *za += ca; // a^2-b^2+Re(c) + z.real.square_mut(); // a^2 + z.real -= &z.imag * &z.imag; // a^2-b^2 + z.real += &c.real; // a^2-b^2+Re(c) - *zb *= za_temporary; // ab - *zb *= 2.0; // 2ab - *zb += cb; // 2ab+Im(c) + z.imag *= &za_temporary; // ab + z.imag *= 2.0; // 2ab + z.imag += &c.imag; // 2ab+Im(c) } diff --git a/source/benoit/benoit/render/iterate/multibrot3.rs b/source/benoit/benoit/render/iterate/multibrot3.rs index d0a7c2f..fc6f872 100644 --- a/source/benoit/benoit/render/iterate/multibrot3.rs +++ b/source/benoit/benoit/render/iterate/multibrot3.rs @@ -21,14 +21,16 @@ If not, see <https://www.gnu.org/licenses/>. */ +use crate::benoit::complex::Complex; + use crate::benoit::PRECISION; extern crate rug; use rug::Float; -pub fn multibrot3(za: &mut Float, zb: &mut Float, ca: &Float, cb: &Float) { - let za_temporary = za.clone(); // a +pub fn multibrot3(z: &mut Complex, c: &Complex) { + let za_temporary = z.real.clone(); // a // (a+bi)^3 // = (a+bi)(a+bi)(a+bi) @@ -39,22 +41,22 @@ pub fn multibrot3(za: &mut Float, zb: &mut Float, ca: &Float, cb: &Float) { // <=> z_a = a^3-3ab^2 // z_b = 3(a^2)b-b^3 - let mut temporary0 = Float::with_val(PRECISION, &*zb * &*zb); // b^2 + let mut temporary0 = Float::with_val(PRECISION, &z.imag * &z.imag); // b^2 - let temporary1 = Float::with_val(PRECISION, &temporary0 * &*zb); // b^3 + let temporary1 = Float::with_val(PRECISION, &temporary0 * &z.imag); // b^3 - temporary0 *= &*za; // ab^2 - temporary0 *= 0x3; // 3ab^2 + temporary0 *= &z.real; // ab^2 + temporary0 *= 0x3; // 3ab^2 - za.square_mut(); // a^2 + z.real.square_mut(); // a^2 - *zb *= &*za; // (a^2)b + z.imag *= &z.real; // (a^2)b - *za *= &za_temporary; // a^3 - *za -= &temporary0; // a^3-3ab^2 - *za += ca; // a^3-3ab^2+Re(c) + z.real *= &za_temporary; // a^3 + z.real -= &temporary0; // a^3-3ab^2 + z.real += &c.real; // a^3-3ab^2+Re(c) - *zb *= 3.0; // 3(a^2)b - *zb -= &temporary1; // 3(a^2)b-b^3 - *zb += cb; // 3(a^2)b-b^3+Im(c) + z.imag *= 3.0; // 3(a^2)b + z.imag -= &temporary1; // 3(a^2)b-b^3 + z.imag += &c.imag; // 3(a^2)b-b^3+Im(c) } diff --git a/source/benoit/benoit/render/iterate/tricorn.rs b/source/benoit/benoit/render/iterate/tricorn.rs index 2f11ecf..b562d66 100644 --- a/source/benoit/benoit/render/iterate/tricorn.rs +++ b/source/benoit/benoit/render/iterate/tricorn.rs @@ -21,30 +21,28 @@ If not, see <https://www.gnu.org/licenses/>. */ -extern crate rug; +use crate::benoit::complex::Complex; -use rug::Float; - -pub fn tricorn(za: &mut Float, zb: &mut Float, ca: &Float, cb: &Float) { +pub fn tricorn(z: &mut Complex, c: &Complex) { // The Tricorn is only different from the // Mandelbrot Set in that the conjugate of (z) is // used instead of just (z): // // z(n+1) = (Re(z(n))-Im(z(n))i)^2+c. - let za_temporary = za.clone(); // a + let za_temporary = z.real.clone(); // a - za.square_mut(); // a^2 - *za -= &*zb * &*zb; // a^2-b^2 - *za += ca; // a^2 + z.real.square_mut(); // a^2 + z.real -= &z.imag * &z.imag; // a^2-b^2 + z.real += &c.real; // a^2 - *zb *= za_temporary; + z.imag *= &za_temporary; // We can negate the value by multiplying with // (-1). A multiplication can be saved, as // // a*2*(-1) = a*(-2). // // Thus, we may combine these two multiplications. - *zb *= -2.0; - *zb += cb; + z.imag *= -2.0; + z.imag += &c.imag; } diff --git a/source/benoit/benoit/render/render_data.rs b/source/benoit/benoit/render/render_data.rs index a1d2fdd..86a21ee 100644 --- a/source/benoit/benoit/render/render_data.rs +++ b/source/benoit/benoit/render/render_data.rs @@ -22,6 +22,7 @@ */ use crate::benoit::{PRECISION, width_height_ratio}; +use crate::benoit::complex::Complex; extern crate rug; @@ -34,9 +35,9 @@ pub struct RenderData { canvas_size: usize, - centre_real: Float, - centre_imag: Float, - zoom: Float, + centre: Complex, + extra: Complex, + zoom: Float, max_iter_count: u32, @@ -54,7 +55,17 @@ pub struct RenderData { impl RenderData { #[must_use] - pub fn new(iter_count_buffer: &mut [u32], square_dist_buffer: &mut [f32], canvas_width: u32, canvas_height: u32, centre_real: Float, centre_imag: Float, zoom: Float, max_iter_count: u32, inverse: bool) -> RenderData { + pub fn new( + iter_count_buffer: &mut [u32], + square_dist_buffer: &mut [f32], + canvas_width: u32, + canvas_height: u32, + centre: Complex, + extra: Complex, + zoom: Float, + max_iter_count: u32, + inverse: bool, + ) -> RenderData { let (width_ratio, height_ratio) = width_height_ratio(canvas_width, canvas_height); return RenderData { @@ -63,9 +74,9 @@ impl RenderData { canvas_size: canvas_height as usize * canvas_width as usize, - centre_real: centre_real, - centre_imag: centre_imag, - zoom: zoom, + centre: centre, + extra: extra, + zoom: zoom, max_iter_count: max_iter_count, @@ -83,10 +94,10 @@ impl RenderData { } #[must_use] - pub fn inverse_factor(&self, a: &Float, b: &Float) -> Float { + pub fn inverse_factor(&self, val: &Complex) -> Float { return if self.inverse { - let mut inverse_factor = Float::with_val(PRECISION, a * a); - inverse_factor += b * b; + let mut inverse_factor = Float::with_val(PRECISION, &val.real * &val.real); + inverse_factor += &val.imag * &val.imag; inverse_factor.recip_mut(); inverse_factor @@ -101,8 +112,8 @@ impl RenderData { } #[must_use] - pub fn input(&self) -> (&Float, &Float, &Float, u32) { - return (&self.centre_real, &self.centre_imag, &self.zoom, self.max_iter_count); + pub fn input(&self) -> (&Complex, &Complex, &Float, u32) { + return (&self.centre, &self.extra, &self.zoom, self.max_iter_count); } #[must_use] diff --git a/source/benoit/benoit/render/render_point/julia.rs b/source/benoit/benoit/render/render_point/julia.rs index fa7329c..63cd4cb 100644 --- a/source/benoit/benoit/render/render_point/julia.rs +++ b/source/benoit/benoit/render/render_point/julia.rs @@ -22,6 +22,7 @@ */ use crate::benoit::{BAILOUT, PRECISION}; +use crate::benoit::complex::Complex; use crate::benoit::render::IteratorFunction; use crate::benoit::render::render_data::RenderData; @@ -33,41 +34,48 @@ use rug::float::Special; pub fn julia(data: &RenderData, x: u32, y: u32, iterator: IteratorFunction) -> (u32, f32) { // For more information, see render_point::normal. - let (centre_real, centre_imag, _zoom, max_iter_count) = data.input(); + let (centre, extra, zoom, max_iter_count) = data.input(); let (x_offset, y_offset, x_factor, y_factor) = data.consts(); let x_temporary = (x as f32 + x_offset) * x_factor; let y_temporary = (y as f32 + y_offset) * y_factor; - let ca = centre_real; - let cb = centre_imag; + let c = extra; - let mut za = Float::with_val(PRECISION, x_temporary); - let mut zb = Float::with_val(PRECISION, y_temporary); + let mut z = { + let mut a = Float::with_val(PRECISION, x_temporary / zoom); + a += ¢re.real; - let inverse_factor = data.inverse_factor(&za, &zb); + let mut b = Float::with_val(PRECISION, y_temporary / zoom); + b -= ¢re.imag; - za *= &inverse_factor; - zb *= &inverse_factor; + Complex {real: a, imag: b} + }; - let mut za_prev = Float::with_val(PRECISION, Special::Nan); - let mut zb_prev = Float::with_val(PRECISION, Special::Nan); + let inverse_factor = data.inverse_factor(&z); + + z.real *= &inverse_factor; + z.imag *= &inverse_factor; + + let mut z_prev = Complex { + real: Float::with_val(PRECISION, Special::Nan), + imag: Float::with_val(PRECISION, Special::Nan), + }; let mut iter_count: u32 = 0x1; let mut square_dist = Float::with_val(PRECISION, Special::Nan); while { - square_dist.assign(&za * &za + &zb * &zb); + square_dist.assign(&z.real * &z.real + &z.imag * &z.imag); - let periodic = za == za_prev && zb == zb_prev; + let periodic = z.real == z_prev.real && z.imag == z_prev.imag; if periodic { iter_count = max_iter_count } square_dist <= BAILOUT && iter_count < max_iter_count } { - za_prev.assign(&za); - zb_prev.assign(&zb); + z_prev.assign(&z); - iterator(&mut za, &mut zb, ca, cb); + iterator(&mut z, c); iter_count += 0x1; } diff --git a/source/benoit/benoit/render/render_point/normal.rs b/source/benoit/benoit/render/render_point/normal.rs index 8bf88f3..167bdb6 100644 --- a/source/benoit/benoit/render/render_point/normal.rs +++ b/source/benoit/benoit/render/render_point/normal.rs @@ -22,6 +22,7 @@ */ use crate::benoit::{BAILOUT, PRECISION}; +use crate::benoit::complex::Complex; use crate::benoit::render::IteratorFunction; use crate::benoit::render::render_data::RenderData; @@ -31,56 +32,55 @@ use rug::{Assign, Float}; use rug::float::Special; pub fn normal(data: &RenderData, x: u32, y: u32, iterator: IteratorFunction) -> (u32, f32) { - let (centre_real, centre_imag, zoom, max_iter_count) = data.input(); + let (centre, extra, zoom, max_iter_count) = data.input(); let (x_offset, y_offset, x_factor, y_factor) = data.consts(); let x_temporary = (x as f32 + x_offset) * x_factor; let y_temporary = (y as f32 + y_offset) * y_factor; - let ca = { - let mut ca = Float::with_val(PRECISION, x_temporary / zoom); - ca += centre_real; + let mut z = { + let mut a = Float::with_val(PRECISION, x_temporary / zoom); + a += ¢re.real; - ca - }; - - let cb = { - let mut cb = Float::with_val(PRECISION, y_temporary / zoom); - cb -= centre_imag; + let mut b = Float::with_val(PRECISION, y_temporary / zoom); + b -= ¢re.imag; - cb + Complex {real: a, imag: b} }; - let inverse_factor = data.inverse_factor(&ca, &cb); + let inverse_factor = data.inverse_factor(&z); - let ca = ca * &inverse_factor; - let cb = cb * &inverse_factor; + z.real *= &inverse_factor; + z.imag *= &inverse_factor; - let mut za = ca.clone(); - let mut zb = cb.clone(); + let c = z.clone(); - let mut za_prev = Float::with_val(PRECISION, Special::Nan); - let mut zb_prev = Float::with_val(PRECISION, Special::Nan); + let mut z_prev = Complex { + real: Float::with_val(PRECISION, Special::Nan), + imag: Float::with_val(PRECISION, Special::Nan), + }; let mut iter_count: u32 = 0x1; - let mut square_dist = Float::with_val(PRECISION, Special::Nan); + let mut square_dist = Float::with_val(PRECISION, Special::Nan); while { - square_dist.assign(&za * &za + &zb * &zb); + square_dist.assign(&z.real * &z.real + &z.imag * &z.imag); // Having a larger escape radius gives better // results with regard to smoothing. // Check if the value is periodic, i.e. its // sequence repeats. - let periodic = za == za_prev && zb == zb_prev; + let periodic = z.real == z_prev.real && z.imag == z_prev.imag; if periodic { iter_count = max_iter_count } square_dist <= BAILOUT && iter_count < max_iter_count } { - za_prev.assign(&za); - zb_prev.assign(&zb); + z_prev.assign(&z); + + iterator(&mut z, &c); - iterator(&mut za, &mut zb, &ca, &cb); + z.real += &extra.real; + z.imag -= &extra.imag; iter_count += 0x1; } diff --git a/source/benoit/benoit/rendering.rs b/source/benoit/benoit/renderer.rs index 923c01f..6b9aaca 100644 --- a/source/benoit/benoit/rendering.rs +++ b/source/benoit/benoit/renderer.rs @@ -26,17 +26,20 @@ use crate::benoit::render::render_point; use std::mem::transmute; +pub mod colour; +pub mod render; + #[derive(Clone, Copy)] -pub enum Rendering { +pub enum Renderer { Julia, Normal, } -impl Rendering { - pub fn get_point_renderer(self) -> PointRenderer { +impl Renderer { + pub fn point_renderer(self) -> PointRenderer { return match self { - Rendering::Julia => render_point::julia, - Rendering::Normal => render_point::normal, + Renderer::Julia => render_point::julia, + Renderer::Normal => render_point::normal, }; } diff --git a/source/benoit/benoit/renderer/colour.rs b/source/benoit/benoit/renderer/colour.rs new file mode 100644 index 0000000..d4e8e74 --- /dev/null +++ b/source/benoit/benoit/renderer/colour.rs @@ -0,0 +1,86 @@ +/* + 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::palette::{Palette, PALETTE_DATA_LENGTH}; +use crate::benoit::render::colour_data::ColourData; +use crate::benoit::renderer::Renderer; + +extern crate rayon; + +use rayon::prelude::*; + +impl Renderer { + pub fn colour( + self, + buffer: &mut [u8], + palette: Palette, + canvas_width: u32, + canvas_height: u32, + multibrot_exponent: f32, + max_iter_count: u32, + colour_range: f32, + iter_count_buffer: &[u32], + square_dist_buffer: &[f32], + ) { + let data = ColourData::new(buffer, canvas_width, canvas_height, multibrot_exponent, max_iter_count, colour_range, palette, iter_count_buffer, square_dist_buffer); + + let (canvas_size, overflow) = canvas_height.overflowing_mul(canvas_width); + if overflow { panic!("overflow when calculating canvas size") }; + + (0x0..canvas_size).into_par_iter().for_each(|index| { + colour_point(&data, index as usize); + }); + } +} + +fn colour_point(data: &ColourData, index: usize) { + let (iter_count_buffer, square_dist_buffer) = data.input_buffers(); + + let image = data.output_buffers(); + + let (exponent, max_iter_count, colour_range, palette_data) = data.consts(); + + let iter_count = unsafe { *iter_count_buffer.get_unchecked( index) }; + let distance = unsafe { *square_dist_buffer.get_unchecked(index) }.sqrt(); + + let (red, green, blue) = if iter_count < max_iter_count { + let factor = (iter_count as f32 + 1.0 - distance.log(exponent).log(exponent)) / colour_range % 1.0; + + let index = (factor * PALETTE_DATA_LENGTH as f32).round() as usize; + unsafe { *palette_data.get_unchecked(index) } + } else { + (0.0, 0.0, 0.0) + }; + + let red = (red * 255.0).round() as u8; + let green = (green * 255.0).round() as u8; + let blue = (blue * 255.0).round() as u8; + + unsafe { + let index = index * 0x3; + + *image.get_unchecked_mut(index) = red; + *image.get_unchecked_mut(index + 0x1) = green; + *image.get_unchecked_mut(index + 0x2) = blue; + } +} diff --git a/source/benoit/benoit/render/render.rs b/source/benoit/benoit/renderer/render.rs index 1ff0931..9ec5e73 100644 --- a/source/benoit/benoit/render/render.rs +++ b/source/benoit/benoit/renderer/render.rs @@ -21,8 +21,11 @@ If not, see <https://www.gnu.org/licenses/>. */ +use crate::benoit::complex::Complex; +use crate::benoit::fractal::Fractal; use crate::benoit::render::{IteratorFunction, PointRenderer}; use crate::benoit::render::render_data::RenderData; +use crate::benoit::renderer::Renderer; extern crate rayon; extern crate rug; @@ -30,24 +33,50 @@ extern crate rug; use rayon::prelude::*; use rug::Float; -pub fn render(iter_count_buffer: &mut [u32], square_dist_buffer: &mut [f32], canvas_width: u32, canvas_height: u32, centre_real: &Float, centre_imag: &Float, zoom: &Float, max_iter_count: u32, inverse: bool, point_renderer: PointRenderer, iterator: IteratorFunction) { - let data = RenderData::new(iter_count_buffer, square_dist_buffer, canvas_width, canvas_height, centre_real.clone(), centre_imag.clone(), zoom.clone(), max_iter_count, inverse); +impl Renderer { + pub fn render( + self, + iter_count_buffer: &mut [u32], + square_dist_buffer: &mut [f32], + fractal: &Fractal, + canvas_width: u32, + canvas_height: u32, + centre: &Complex, + zoom: &Float, + extra: &Complex, + max_iter_count: u32, + ) { + let data = RenderData::new( + iter_count_buffer, + square_dist_buffer, + canvas_width, + canvas_height, + centre.clone(), + extra.clone(), + zoom.clone(), + max_iter_count, + fractal.inverse(), + ); - let (canvas_size, overflow) = canvas_height.overflowing_mul(canvas_width); - if overflow { panic!("overflow when calculating canvas size") }; + let (canvas_size, overflow) = canvas_height.overflowing_mul(canvas_width); + if overflow { panic!("overflow when calculating canvas size") }; - (0x0..canvas_size).into_par_iter().for_each(|index| { - render_point(&data, index as u32, point_renderer, iterator); - }); + let point_renderer = self.point_renderer(); + let iterator = fractal.iterator(); + + (0x0..canvas_size).into_par_iter().for_each(|index| { + render_point(&data, index as usize, point_renderer, iterator); + }); + } } -fn render_point(data: &RenderData, index: u32, point_renderer: PointRenderer, iterator: IteratorFunction) { +fn render_point(data: &RenderData, index: usize, point_renderer: PointRenderer, iterator: IteratorFunction) { let (iter_count_buffer, square_dist_buffer) = data.output_buffers(); let (canvas_width, _) = data.canvas_size(); - let x = index % canvas_width; - let y = index / canvas_width; + let x = (index % canvas_width as usize) as u32; + let y = (index / canvas_width as usize) as u32; let (iter_count, square_dist) = point_renderer(&data, x, y, iterator); diff --git a/source/benoit/benoit/video/draw_textual_feedback.rs b/source/benoit/benoit/video/draw_textual_feedback.rs index cfa2307..72c9d2a 100644 --- a/source/benoit/benoit/video/draw_textual_feedback.rs +++ b/source/benoit/benoit/video/draw_textual_feedback.rs @@ -21,6 +21,7 @@ If not, see <https://www.gnu.org/licenses/>. */ +use crate::benoit::complex::Complex; use crate::benoit::video::Video; extern crate rug; @@ -32,9 +33,9 @@ use sdl2::rect::Rect; use sdl2::render::WindowCanvas; impl Video { - pub fn draw_textual_feedback(&mut self, real: &Float, imag: &Float, zoom: &Float, max_iter_count: u32) { - let real_text = format!("REAL: {:.18}", real.to_f64()); - let imag_text = format!("IMAG: {:.18}", imag.to_f64()); + pub fn draw_textual_feedback(&mut self, centre: &Complex, zoom: &Float, max_iter_count: u32) { + let real_text = format!("REAL: {:.18}", centre.real.to_f64()); + let imag_text = format!("IMAG: {:.18}", centre.imag.to_f64()); let zoom_text = format!("ZOOM: 2^{:.9}", zoom.to_f64().log2()); let iter_text = format!("ITER: {}", max_iter_count); diff --git a/source/benoit/benoit/video/draw_translation_feedback.rs b/source/benoit/benoit/video/draw_translation_feedback.rs index aeb7463..05ac70e 100644 --- a/source/benoit/benoit/video/draw_translation_feedback.rs +++ b/source/benoit/benoit/video/draw_translation_feedback.rs @@ -21,7 +21,8 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::{FeedbackInfo, PRECISION, width_height_ratio}; +use crate::benoit::{PRECISION, width_height_ratio}; +use crate::benoit::complex::Complex; use crate::benoit::video::Video; extern crate rug; @@ -32,7 +33,7 @@ use sdl2::pixels::Color; use sdl2::rect::Rect; impl Video { - pub fn draw_translation_feedback(&mut self, canvas_width: u32, canvas_height: u32, scale: u32, feedback_info: &FeedbackInfo) { + pub fn draw_translation_feedback(&mut self, canvas_width: u32, canvas_height: u32, scale: u32, prev_centre: &Complex, prev_zoom: &Float, next_centre: &Complex, next_zoom: &Float) { let (width_ratio, height_ratio) = width_height_ratio(canvas_width, canvas_height); let canvas_width = Float::with_val(PRECISION, canvas_width * scale); @@ -40,7 +41,7 @@ impl Video { let viewport = { let (offset_x, offset_y, width, height) = { - let zoom_ratio = Float::with_val(PRECISION, feedback_info.next_zoom / feedback_info.prev_zoom); + let zoom_ratio = Float::with_val(PRECISION, next_zoom / prev_zoom); let mut width = Float::with_val(PRECISION, 1.0 / &zoom_ratio); let mut height = Float::with_val(PRECISION, 1.0 / &zoom_ratio); @@ -49,17 +50,17 @@ impl Video { // inverted vertical axis compared to those of // SDL's coordinate system. - let mut offset_x = feedback_info.next_centre_real.clone(); - let mut offset_y = Float::with_val(PRECISION, -feedback_info.next_centre_imag); + let mut offset_x = next_centre.real.clone(); + let mut offset_y = Float::with_val(PRECISION, -&next_centre.imag); - offset_x -= feedback_info.prev_centre_real; - offset_y += feedback_info.prev_centre_imag; + offset_x -= &prev_centre.real; + offset_y += &prev_centre.imag; offset_x /= 4.0; offset_y /= 4.0; - offset_x *= feedback_info.prev_zoom; - offset_y *= feedback_info.prev_zoom; + offset_x *= prev_zoom; + offset_y *= prev_zoom; offset_x /= width_ratio; offset_y /= height_ratio; diff --git a/source/benoit/benoit/video/initialise.rs b/source/benoit/benoit/video/initialise.rs index 8719b4e..b06d774 100644 --- a/source/benoit/benoit/video/initialise.rs +++ b/source/benoit/benoit/video/initialise.rs @@ -37,7 +37,6 @@ impl Video { let window_title = format!("BENO\u{CE}T {:X}.{:X}.{:X}", VERSION[0x0], VERSION[0x1], VERSION[0x2]); let mut window_builder = sdl_video.window(window_title.as_str(), canvas_width * scale, canvas_height * scale); - window_builder.borderless(); window_builder.position_centered(); let window = window_builder.build().expect("unable to open window"); |