transforms appear to work correctly

This commit is contained in:
2025-07-03 07:50:35 +02:00
parent 357ada02ac
commit 79267ee5e9
4 changed files with 87 additions and 30 deletions

5
TODO.adoc Normal file
View File

@@ -0,0 +1,5 @@
= TODOs
- Improve deep zoom precision
* https://mathr.co.uk/blog/2021-05-14_deep_zoom_theory_and_practice.html)

View File

@@ -24,7 +24,7 @@ pub struct Args {
pub transform: Vec<String>
}
pub type Float = f32;
pub type Float = f64;
pub type Point = Complex<Float>;
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<Rgb<u8>, Vec<u8>> {
@@ -86,8 +86,8 @@ impl Config {
}
}
pub fn map_palette(val: f32) -> image::Rgb<u8> {
let magnitude = f32::clamp(val * 255., 0., 255.) as u8;
pub fn map_palette(val: Float) -> image::Rgb<u8> {
let magnitude = Float::clamp(val * 255., 0., 255.) as u8;
image::Rgb([magnitude, magnitude, magnitude])
}
pub fn main() -> anyhow::Result<()> {

View File

@@ -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)));
});
}
}

View File

@@ -4,10 +4,10 @@ pub type MTransform = nalgebra::Matrix5<Float>;
#[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<Type> for MTransform {
@@ -43,7 +43,9 @@ impl From<Type> 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<u8> {
/// TODO: replace this with a nom parser
pub fn parse_step(s: &str) -> Option<Type> {
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<Type> {
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,