From 79267ee5e9171e71470b7de86ac52abc4a9d5cad Mon Sep 17 00:00:00 2001 From: TQ Hirsch Date: Thu, 3 Jul 2025 07:50:35 +0200 Subject: [PATCH] transforms appear to work correctly --- TODO.adoc | 5 +++ src/lib.rs | 10 +++--- src/main.rs | 86 ++++++++++++++++++++++++++++++++++++++---------- src/transform.rs | 16 +++++---- 4 files changed, 87 insertions(+), 30 deletions(-) create mode 100644 TODO.adoc diff --git a/TODO.adoc b/TODO.adoc new file mode 100644 index 0000000..c013a64 --- /dev/null +++ b/TODO.adoc @@ -0,0 +1,5 @@ += TODOs + +- Improve deep zoom precision + * https://mathr.co.uk/blog/2021-05-14_deep_zoom_theory_and_practice.html) + \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index f261075..e9cefa4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,7 +24,7 @@ pub struct Args { pub transform: Vec } -pub type Float = f32; +pub type Float = f64; pub type Point = Complex; use num::complex::Complex; pub type Transform = [(Point, Point); 3]; @@ -56,7 +56,7 @@ pub struct Config { impl Config { - fn render_point(&self, (base, delta): (Point, Point)) -> f32 { + fn render_point(&self, (base, delta): (Point, Point)) -> Float { const ESCAPE: Float = 2.; const ESCAPE2: Float = ESCAPE * ESCAPE; @@ -71,7 +71,7 @@ impl Config { |(iter, val)| iter as Float + 1. - val.norm().ln().ln() / ESCAPE.ln() ); - result as f32 + result as Float } pub fn render_image(&self) -> ImageBuffer, Vec> { @@ -86,8 +86,8 @@ impl Config { } } -pub fn map_palette(val: f32) -> image::Rgb { - let magnitude = f32::clamp(val * 255., 0., 255.) as u8; +pub fn map_palette(val: Float) -> image::Rgb { + let magnitude = Float::clamp(val * 255., 0., 255.) as u8; image::Rgb([magnitude, magnitude, magnitude]) } pub fn main() -> anyhow::Result<()> { diff --git a/src/main.rs b/src/main.rs index 54cdd0f..49fdffb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,9 @@ // extern crate mandelia; use eframe::{CreationContext, Frame, Result}; use egui::Context; +use egui::load::SizedTexture; use image::EncodableLayout; - +use mandelia::Float; fn main() -> eframe::Result { // mandelia::main() @@ -69,17 +70,18 @@ impl App { } fn add_transform(&mut self, xf: mandelia::transform::Type) { - self.tx_mat *= mandelia::transform::MTransform::from(xf); + self.tx_mat = mandelia::transform::MTransform::from(xf) * self.tx_mat; self.update_transform(); } - fn mk_adjuster(&mut self, ui: &mut egui::Ui, label: &str, mut action: impl FnMut(f32) -> mandelia::transform::Type) { - if ui.button("-").clicked() { + fn mk_adjuster(&mut self, ui: &mut egui::Ui, label: &str, key_dec: egui::Key, key_inc: egui::Key, mut action: impl Fn(Float) -> mandelia::transform::Type) { + let (dn, up) = ui.ctx().input(|is| (is.key_pressed(key_dec), is.key_pressed(key_inc))); + if ui.button(format!("- ({})", key_dec.symbol_or_name())).clicked() || dn { self.add_transform(action(-1.)); } ui.centered_and_justified(|ui| {ui.label(label);}); - if ui.button("+").clicked() { + if ui.button(format!("+ ({})", key_inc.symbol_or_name())).clicked() || up { self.add_transform(action(1.)); } ui.end_row(); @@ -108,34 +110,82 @@ impl App { } } +// transform functions +fn mk_translation(axis: u8) -> impl Fn(Float) -> mandelia::transform::Type { + move |amt| mandelia::transform::Type::Translation(axis, amt / 100.) +} + +fn mk_rotation(a0: u8, a1: u8) -> impl Fn(Float) -> mandelia::transform::Type { + move |amt| mandelia::transform::Type::Rotation(a0, a1, amt) +} + +fn mk_scale(scale: Float) -> mandelia::transform::Type { + mandelia::transform::Type::Scale(1. + scale / 4.) +} + +enum JumpMode { + Mandelbrot, + Julia, + Origin, +} impl eframe::App for App { fn update(&mut self, ctx: &Context, frame: &mut Frame) { + if let Some(jump_mode) = ctx.input(|is| { + if is.key_pressed(egui::Key::Num0) { + Some(JumpMode::Origin) + } else if is.key_pressed(egui::Key::Num1) { + Some(JumpMode::Mandelbrot) + } else if is.key_pressed(egui::Key::Num2) { + Some(JumpMode::Julia) + } else { + None + } + }) { + use mandelia::transform::Type; + match jump_mode { + JumpMode::Mandelbrot => { + self.reset_transform(); + self.add_transform(Type::CoordSwap(0, 2)); + self.add_transform(Type::CoordSwap(1, 3)); + }, + JumpMode::Julia => { + self.reset_transform(); + self.add_transform(Type::Translation(0, 0.31)); + self.add_transform(Type::Translation(1, 0.31)); + } + JumpMode::Origin => { + self.reset_transform(); + } + } + }; egui::SidePanel::left("nav").show(ctx, |ui| { use mandelia::transform::Type; + use egui::Key; egui::Grid::new("nav_grid") .num_columns(3) .show(ui, |ui| { - self.mk_adjuster(ui, "base x", |delta| Type::Translation(0, delta)); - self.mk_adjuster(ui, "base y", |delta| Type::Translation(1, delta)); - self.mk_adjuster(ui, "delta x", |delta| Type::Translation(2, delta)); - self.mk_adjuster(ui, "delta y", |delta| Type::Translation(3, delta)); + self.mk_adjuster(ui, "base x", Key::A, Key::D, mk_translation(0)); + self.mk_adjuster(ui, "base y", Key::S, Key::W, mk_translation(1)); + self.mk_adjuster(ui, "delta x", Key::J, Key::L, mk_translation(2)); + self.mk_adjuster(ui, "delta y", Key::K, Key::I, mk_translation(3)); - self.mk_adjuster(ui, "scale", |scale| Type::Scale(scale)); + self.mk_adjuster(ui, "scale", Key::OpenBracket, Key::CloseBracket, mk_scale); - self.mk_adjuster(ui, "xy", |delta| Type::Rotation(0, 1, delta)); - self.mk_adjuster(ui, "XY", |delta| Type::Rotation(2, 3, delta)); - self.mk_adjuster(ui, "xX", |delta| Type::Rotation(0, 2, delta)); - self.mk_adjuster(ui, "yY", |delta| Type::Rotation(1, 3, delta)); - self.mk_adjuster(ui, "xY", |delta| Type::Rotation(0, 3, delta)); - self.mk_adjuster(ui, "Xy", |delta| Type::Rotation(1, 2, delta)); + self.mk_adjuster(ui, "xy", Key::Q, Key::E, mk_rotation(0, 1)); + self.mk_adjuster(ui, "XY", Key::U, Key::O, mk_rotation(2, 3)); + self.mk_adjuster(ui, "xX", Key::F, Key::G, mk_rotation(0, 2)); + self.mk_adjuster(ui, "yY", Key::R, Key::T, mk_rotation(1, 3)); + self.mk_adjuster(ui, "xY", Key::Z, Key::X, mk_rotation(0, 3)); + self.mk_adjuster(ui, "Xy", Key::N, Key::M, mk_rotation(1, 2)); }) }); egui::CentralPanel::default().show(ctx, |ui| { - let size = ui.available_size(); + let base_size = ui.available_size(); + let size = base_size * ui.pixels_per_point(); let size = [size.x as usize, size.y as usize]; self.update_texture(size); - ui.add(egui::Image::from_texture(&self.texture)); + ui.add(egui::Image::from_texture(SizedTexture::new(&self.texture, base_size))); }); } } \ No newline at end of file diff --git a/src/transform.rs b/src/transform.rs index b05f9bf..11edc5b 100644 --- a/src/transform.rs +++ b/src/transform.rs @@ -4,10 +4,10 @@ pub type MTransform = nalgebra::Matrix5; #[derive(Debug)] pub enum Type { - Rotation(u8, u8, f32), - Translation(u8, f32), + Rotation(u8, u8, Float), + Translation(u8, Float), CoordSwap(u8, u8), - Scale(f32), + Scale(Float), } impl From for MTransform { @@ -43,7 +43,9 @@ impl From for MTransform { m } Type::Scale(delta) => { - MTransform::identity() / delta + let mut xf = MTransform::identity() / delta; + xf[(4,4)] = 1.; + xf } } } @@ -66,7 +68,7 @@ fn parse_axis(c: char) -> Option { /// TODO: replace this with a nom parser pub fn parse_step(s: &str) -> Option { if let Some(r) = s.strip_prefix("z") { - return f32::from_str_radix(r, 10).ok().map(Type::Scale); + return Float::from_str_radix(r, 10).ok().map(Type::Scale); } if s.len() < 2 { return None; @@ -85,11 +87,11 @@ pub fn parse_step(s: &str) -> Option { if axis0 == axis1 { return None; } - let val = f32::from_str_radix(chars.as_str().trim(), 10).ok()?; + let val = Float::from_str_radix(chars.as_str().trim(), 10).ok()?; Type::Rotation(axis0, axis1, val) } '0' ..= '9' => { - let val = f32::from_str_radix(chars.as_str().trim(), 10).ok()?; + let val = Float::from_str_radix(chars.as_str().trim(), 10).ok()?; Type::Translation(axis0, val) } _ => return None,