Added egui display
This commit is contained in:
3308
Cargo.lock
generated
3308
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -9,4 +9,6 @@ rayon = "1.10.0"
|
|||||||
num = "0.4.3"
|
num = "0.4.3"
|
||||||
nalgebra = "0.33.2"
|
nalgebra = "0.33.2"
|
||||||
anyhow = "1.0.98"
|
anyhow = "1.0.98"
|
||||||
clap = { version = "4.5.40", features = ["derive"] }
|
clap = { version = "4.5.40", features = ["derive"] }
|
||||||
|
egui = "0.31"
|
||||||
|
eframe = { version = "0.31", features = ["wayland", "glow"] }
|
||||||
|
|||||||
29
shader/mandelia.frag
Normal file
29
shader/mandelia.frag
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#define cx_mul(a,b) vec2(a.x*b.x-a.y*b.y, a.x*b.y+a.y*b.x)
|
||||||
|
#define cx_mag2(x) dot(x,x)
|
||||||
|
|
||||||
|
#define E 2.718281828
|
||||||
|
|
||||||
|
uniform int maxiter;
|
||||||
|
uniform float escape2;
|
||||||
|
|
||||||
|
in vec2 base;
|
||||||
|
in vec2 delta;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 diffuseColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 pos = base;
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < maxiter; i++) {
|
||||||
|
if (cx_mag2(pos) > escape2) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pos = cx_mul(pos, pos) + delta;
|
||||||
|
}
|
||||||
|
float mag = cx_mag2(pos);
|
||||||
|
float sm_color = log(log(mag / 2.)) / log(escape2) * 2;
|
||||||
|
|
||||||
|
float shade = i - 1 + sm_color;
|
||||||
|
// TODO: palette mapping
|
||||||
|
diffuseColor = vec4(shade, shade, shade, 1);
|
||||||
|
}
|
||||||
114
src/lib.rs
Normal file
114
src/lib.rs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
//#![feature(portable_simd)]
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use clap::{Arg, Parser};
|
||||||
|
use image::{GrayImage, ImageBuffer, Luma, Rgb};
|
||||||
|
use image::buffer::ConvertBuffer;
|
||||||
|
use rayon::iter::ParallelIterator;
|
||||||
|
|
||||||
|
pub mod transform;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(short, long, default_value_t = 3840)]
|
||||||
|
pub width: u32,
|
||||||
|
#[clap(short, long, default_value_t = 2160)]
|
||||||
|
pub height: u32,
|
||||||
|
|
||||||
|
#[clap(short, long, default_value_t = 100)]
|
||||||
|
pub maxiter: usize,
|
||||||
|
|
||||||
|
#[clap(short, long, default_value="out.png")]
|
||||||
|
pub output: PathBuf,
|
||||||
|
|
||||||
|
pub transform: Vec<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Float = f32;
|
||||||
|
pub type Point = Complex<Float>;
|
||||||
|
use num::complex::Complex;
|
||||||
|
pub type Transform = [(Point, Point); 3];
|
||||||
|
|
||||||
|
trait ComplexExt<T> {
|
||||||
|
fn norm2(self) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComplexExt<Float> for Complex<Float> {
|
||||||
|
fn norm2(self) -> Float {
|
||||||
|
self.im * self.im + self.re * self.re
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_point(transform: &Transform, point: Point) -> (Point, Point) {
|
||||||
|
let start = transform[0].0 * point.re + transform[1].0 * point.im + transform[2].0;
|
||||||
|
let delta = transform[0].1 * point.re + transform[1].1 * point.im + transform[2].1;
|
||||||
|
(start, delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Config {
|
||||||
|
pub transform: Transform,
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub maxiter: usize,
|
||||||
|
// We represent escape as the square of its magnitude
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
fn render_point(&self, (base, delta): (Point, Point)) -> f32 {
|
||||||
|
const ESCAPE: Float = 2.;
|
||||||
|
const ESCAPE2: Float = ESCAPE * ESCAPE;
|
||||||
|
|
||||||
|
let mut base = base;
|
||||||
|
let result =
|
||||||
|
std::iter::successors(Some(base), |i| Some(*i * *i + delta))
|
||||||
|
.take(self.maxiter)
|
||||||
|
.enumerate()
|
||||||
|
.skip_while(|(_, i)| i.norm2() <= ESCAPE2)
|
||||||
|
.next()
|
||||||
|
.map_or(0.,
|
||||||
|
|(iter, val)|
|
||||||
|
iter as Float + 1. - val.norm().ln().ln() / ESCAPE.ln()
|
||||||
|
);
|
||||||
|
result as f32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_image(&self) -> ImageBuffer<Rgb<u8>, Vec<u8>> {
|
||||||
|
let scale = 2.0 / std::cmp::min(self.width, self.height) as Float;
|
||||||
|
|
||||||
|
ImageBuffer::<image::Rgb<u8>, _>::from_par_fn(self.width, self.height, |x, y| {
|
||||||
|
let x = (x as Float) * scale - 1.;
|
||||||
|
let y = (y as Float) * scale - 1.;
|
||||||
|
let point = transform_point(&self.transform, Complex::new(x, y));
|
||||||
|
map_palette(self.render_point(point) / self.maxiter as Float)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map_palette(val: f32) -> image::Rgb<u8> {
|
||||||
|
let magnitude = f32::clamp(val * 255., 0., 255.) as u8;
|
||||||
|
image::Rgb([magnitude, magnitude, magnitude])
|
||||||
|
}
|
||||||
|
pub fn main() -> anyhow::Result<()> {
|
||||||
|
|
||||||
|
// let transform = [
|
||||||
|
// (Point::new(0., 0.), Point::new(1., 0.)),
|
||||||
|
// (Point::new(0., 0.), Point::new(0., 1.)),
|
||||||
|
// (Point::new(0., 0.), Point::new(0., 0.)),
|
||||||
|
// ];
|
||||||
|
|
||||||
|
let args = <Args as clap::Parser>::parse();
|
||||||
|
|
||||||
|
let config = Config {
|
||||||
|
transform: transform::parse_transforms(args.transform.iter())?,
|
||||||
|
width: args.width,
|
||||||
|
height: args.height,
|
||||||
|
maxiter: args.maxiter,
|
||||||
|
};
|
||||||
|
|
||||||
|
let img = config.render_image();
|
||||||
|
|
||||||
|
img.save(&args.output)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
242
src/main.rs
242
src/main.rs
@@ -1,111 +1,141 @@
|
|||||||
//#![feature(portable_simd)]
|
// extern crate mandelia;
|
||||||
|
use eframe::{CreationContext, Frame, Result};
|
||||||
|
use egui::Context;
|
||||||
|
use image::EncodableLayout;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
fn main() -> eframe::Result {
|
||||||
use clap::{Arg, Parser};
|
// mandelia::main()
|
||||||
use image::{GrayImage, ImageBuffer, Luma};
|
|
||||||
use image::buffer::ConvertBuffer;
|
|
||||||
use rayon::iter::ParallelIterator;
|
|
||||||
|
|
||||||
mod transform;
|
let options = eframe::NativeOptions {
|
||||||
|
..eframe::NativeOptions::default()
|
||||||
#[derive(Parser)]
|
|
||||||
pub struct Args {
|
|
||||||
#[clap(short, long, default_value_t = 3840)]
|
|
||||||
pub width: u32,
|
|
||||||
#[clap(short, long, default_value_t = 2160)]
|
|
||||||
pub height: u32,
|
|
||||||
|
|
||||||
#[clap(short, long, default_value_t = 100)]
|
|
||||||
pub maxiter: usize,
|
|
||||||
|
|
||||||
#[clap(short, long, default_value="out.png")]
|
|
||||||
pub output: PathBuf,
|
|
||||||
|
|
||||||
pub transform: Vec<String>
|
|
||||||
}
|
|
||||||
|
|
||||||
type Float = f32;
|
|
||||||
type Point = Complex<Float>;
|
|
||||||
use num::complex::Complex;
|
|
||||||
pub type Transform = [(Point, Point); 3];
|
|
||||||
|
|
||||||
trait ComplexExt<T> {
|
|
||||||
fn norm2(self) -> T;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ComplexExt<Float> for Complex<Float> {
|
|
||||||
fn norm2(self) -> Float {
|
|
||||||
self.im * self.im + self.re * self.re
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn transform_point(transform: &Transform, point: Point) -> (Point, Point) {
|
|
||||||
let start = transform[0].0 * point.re + transform[1].0 * point.im + transform[2].0;
|
|
||||||
let delta = transform[0].1 * point.re + transform[1].1 * point.im + transform[2].1;
|
|
||||||
(start, delta)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Config {
|
|
||||||
pub transform: Transform,
|
|
||||||
pub width: u32,
|
|
||||||
pub height: u32,
|
|
||||||
pub maxiter: usize,
|
|
||||||
// We represent escape as the square of its magnitude
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
fn render_point(&self, (base, delta): (Point, Point)) -> f32 {
|
|
||||||
const ESCAPE: Float = 2.;
|
|
||||||
const ESCAPE2: Float = ESCAPE * ESCAPE;
|
|
||||||
|
|
||||||
let mut base = base;
|
|
||||||
let result =
|
|
||||||
std::iter::successors(Some(base), |i| Some(*i * *i + delta))
|
|
||||||
.take(self.maxiter)
|
|
||||||
.enumerate()
|
|
||||||
.skip_while(|(_, i)| i.norm2() <= ESCAPE2)
|
|
||||||
.next()
|
|
||||||
.map_or(0.,
|
|
||||||
|(iter, val)|
|
|
||||||
iter as Float + 1. - val.norm().ln().ln() / ESCAPE.ln()
|
|
||||||
);
|
|
||||||
result as f32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
|
||||||
|
|
||||||
// let transform = [
|
|
||||||
// (Point::new(0., 0.), Point::new(1., 0.)),
|
|
||||||
// (Point::new(0., 0.), Point::new(0., 1.)),
|
|
||||||
// (Point::new(0., 0.), Point::new(0., 0.)),
|
|
||||||
// ];
|
|
||||||
|
|
||||||
let args = <Args as clap::Parser>::parse();
|
|
||||||
|
|
||||||
let config = Config {
|
|
||||||
transform: transform::parse_transforms(args.transform.iter())?,
|
|
||||||
width: args.width,
|
|
||||||
height: args.height,
|
|
||||||
maxiter: args.maxiter,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let scale = 2.0 / std::cmp::min(config.width, config.height) as Float;
|
eframe::run_native(
|
||||||
|
"Mandelia",
|
||||||
let mut img = ImageBuffer::<image::Luma<f32>, _>::from_par_fn(config.width, config.height, |x, y| {
|
options,
|
||||||
let x = (x as Float) * scale - 1.;
|
Box::new(|cc| {
|
||||||
let y = (y as Float) * scale - 1.;
|
Ok(Box::new(App::new(cc)))
|
||||||
let point = transform_point(&config.transform, Complex::new(x, y));
|
})
|
||||||
image::Luma([config.render_point(point)])
|
)
|
||||||
});
|
|
||||||
let img_norm = img.pixels()
|
|
||||||
.map(|p: &Luma<f32>| p.0[0])
|
|
||||||
.reduce(f32::max)
|
|
||||||
.unwrap_or(1.);
|
|
||||||
img.par_pixels_mut().for_each(|Luma([p])| *p /= img_norm);
|
|
||||||
let img: GrayImage = img.convert();
|
|
||||||
img.save(&args.output)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this is backend-independent
|
||||||
|
pub struct App {
|
||||||
|
tx_mat: nalgebra::Matrix5<mandelia::Float>,
|
||||||
|
transform: mandelia::Transform,
|
||||||
|
texture: egui::TextureHandle,
|
||||||
|
last_render: RenderData,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Debug, Default)]
|
||||||
|
struct RenderData {
|
||||||
|
transform: mandelia::Transform,
|
||||||
|
render_size: [usize; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
fn new(cc: &CreationContext) -> Self {
|
||||||
|
let mut result = Self {
|
||||||
|
transform: Default::default(),
|
||||||
|
tx_mat: nalgebra::Matrix5::identity(),
|
||||||
|
texture: cc.egui_ctx.load_texture("rendering", egui::ColorImage::example(), egui::TextureOptions::LINEAR),
|
||||||
|
last_render: Default::default(),
|
||||||
|
};
|
||||||
|
result.reset_transform();
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_transform(&mut self) {
|
||||||
|
self.tx_mat = nalgebra::Matrix5::identity();
|
||||||
|
self.update_transform();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_transform(&mut self) {
|
||||||
|
use mandelia::Point;
|
||||||
|
let mat = &self.tx_mat;
|
||||||
|
let bx = Point::new(mat[(0,0)], mat[(0,1)]);
|
||||||
|
let by = Point::new(mat[(1,0)], mat[(1,1)]);
|
||||||
|
let bd = Point::new(mat[(4,0)], mat[(4,1)]);
|
||||||
|
|
||||||
|
let cx = Point::new(mat[(0,2)], mat[(0,3)]);
|
||||||
|
let cy = Point::new(mat[(1,2)], mat[(1,3)]);
|
||||||
|
let cd = Point::new(mat[(4,2)], mat[(4,3)]);
|
||||||
|
|
||||||
|
self.transform = [
|
||||||
|
(bx, cx),
|
||||||
|
(by, cy),
|
||||||
|
(bd, cd),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_transform(&mut self, xf: mandelia::transform::Type) {
|
||||||
|
self.tx_mat *= mandelia::transform::MTransform::from(xf);
|
||||||
|
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() {
|
||||||
|
self.add_transform(action(-1.));
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.centered_and_justified(|ui| {ui.label(label);});
|
||||||
|
if ui.button("+").clicked() {
|
||||||
|
self.add_transform(action(1.));
|
||||||
|
}
|
||||||
|
ui.end_row();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_texture(&mut self, size: [usize; 2]) {
|
||||||
|
let new_rdata = RenderData {
|
||||||
|
transform: self.transform,
|
||||||
|
render_size: size,
|
||||||
|
};
|
||||||
|
if self.last_render != new_rdata {
|
||||||
|
self.last_render = new_rdata;
|
||||||
|
let config = mandelia::Config {
|
||||||
|
transform: self.transform,
|
||||||
|
width: size[0] as u32,
|
||||||
|
height: size[1] as u32,
|
||||||
|
maxiter: 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
let img = config.render_image();
|
||||||
|
|
||||||
|
let image_data = egui::ColorImage::from_rgb([config.width as usize, config.height as usize], img.as_bytes());
|
||||||
|
self.texture.set(image_data, egui::TextureOptions::LINEAR);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl eframe::App for App {
|
||||||
|
fn update(&mut self, ctx: &Context, frame: &mut Frame) {
|
||||||
|
egui::SidePanel::left("nav").show(ctx, |ui| {
|
||||||
|
use mandelia::transform::Type;
|
||||||
|
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, "scale", |scale| Type::Scale(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));
|
||||||
|
})
|
||||||
|
});
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
let size = ui.available_size();
|
||||||
|
let size = [size.x as usize, size.y as usize];
|
||||||
|
self.update_texture(size);
|
||||||
|
ui.add(egui::Image::from_texture(&self.texture));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
157
src/transform.rs
157
src/transform.rs
@@ -1,15 +1,53 @@
|
|||||||
use std::cmp::{max, min};
|
|
||||||
use num::Num;
|
use num::Num;
|
||||||
use super::Float;
|
use super::{Float, Point};
|
||||||
type MTransform = nalgebra::Matrix5<Float>;
|
pub type MTransform = nalgebra::Matrix5<Float>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum Type {
|
pub enum Type {
|
||||||
Rotation(u8, u8),
|
Rotation(u8, u8, f32),
|
||||||
Translation(u8),
|
Translation(u8, f32),
|
||||||
CoordSwap(u8, u8),
|
CoordSwap(u8, u8),
|
||||||
|
Scale(f32),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Type> for MTransform {
|
||||||
|
fn from(ty: Type) -> Self {
|
||||||
|
match ty {
|
||||||
|
Type::Rotation(a0, a1, _) if a0 == a1=> MTransform::identity(),
|
||||||
|
Type::Rotation(a0, a1, delta) => {
|
||||||
|
let a0 = a0 as usize;
|
||||||
|
let a1 = a1 as usize;
|
||||||
|
let delta = delta.to_radians();
|
||||||
|
let s = delta.sin();
|
||||||
|
let c = delta.cos();
|
||||||
|
let mut m = MTransform::identity();
|
||||||
|
m[(a0,a0)] = c;
|
||||||
|
m[(a0,a1)] = -s;
|
||||||
|
m[(a1,a0)] = s;
|
||||||
|
m[(a1,a1)] = c;
|
||||||
|
m
|
||||||
|
}
|
||||||
|
Type::Translation(a, delta) => {
|
||||||
|
let mut m = MTransform::identity();
|
||||||
|
m[(4, a as usize)] = -delta;
|
||||||
|
m
|
||||||
|
}
|
||||||
|
Type::CoordSwap(a, b) => {
|
||||||
|
let mut m = MTransform::identity();
|
||||||
|
let a = a as usize;
|
||||||
|
let b = b as usize;
|
||||||
|
m[(a,a)] = 0.;
|
||||||
|
m[(b,b)] = 0.;
|
||||||
|
m[(a,b)] = 1.;
|
||||||
|
m[(b,a)] = 1.;
|
||||||
|
m
|
||||||
|
}
|
||||||
|
Type::Scale(delta) => {
|
||||||
|
MTransform::identity() / delta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
fn parse_axis(c: char) -> Option<u8> {
|
fn parse_axis(c: char) -> Option<u8> {
|
||||||
Some(match c {
|
Some(match c {
|
||||||
'x' => 0,
|
'x' => 0,
|
||||||
@@ -24,85 +62,41 @@ fn parse_axis(c: char) -> Option<u8> {
|
|||||||
/// We have four axes, x,y,X,Y. The first two adjust the base point; the second two adjust the delta.
|
/// We have four axes, x,y,X,Y. The first two adjust the base point; the second two adjust the delta.
|
||||||
///
|
///
|
||||||
/// Given that, a plane can be defined with two axes, and a translation can be defined with one.
|
/// Given that, a plane can be defined with two axes, and a translation can be defined with one.
|
||||||
pub fn parse_step(s: &str) -> Option<MTransform> {
|
///
|
||||||
|
/// 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);
|
||||||
|
}
|
||||||
if s.len() < 2 {
|
if s.len() < 2 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let mut chars = s.chars();
|
let mut chars = s.chars();
|
||||||
let axis0 = chars.next().and_then(parse_axis)?;
|
let axis0 = chars.next().and_then(parse_axis)?;
|
||||||
let chars0 = chars.clone();
|
Some(match chars.clone().next()? {
|
||||||
let ttyp = if let Some(c) = chars.next().and_then(parse_axis) {
|
'=' => {
|
||||||
if c == axis0 {
|
chars.next();
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Type::Rotation(min(axis0, c), max(axis0, c))
|
|
||||||
} else {
|
|
||||||
chars = chars0.clone();
|
|
||||||
if chars.next() == Some('=') {
|
|
||||||
let axis1 = chars.next().and_then(parse_axis)?;
|
let axis1 = chars.next().and_then(parse_axis)?;
|
||||||
eprintln!("Transform: coord swap {axis0} {axis1}");
|
Type::CoordSwap(axis0, axis1)
|
||||||
if chars.next().is_some() {
|
}
|
||||||
|
'x' | 'X' | 'y' | 'Y' => {
|
||||||
|
chars.next();
|
||||||
|
let axis1 = chars.next().and_then(parse_axis)?;
|
||||||
|
if axis0 == axis1 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let mut mat = nalgebra::Matrix5::identity();
|
let val = f32::from_str_radix(chars.as_str().trim(), 10).ok()?;
|
||||||
mat[(axis0 as usize, axis0 as usize)] = 0.0;
|
Type::Rotation(axis0, axis1, val)
|
||||||
mat[(axis1 as usize, axis1 as usize)] = 0.0;
|
|
||||||
mat[(axis0 as usize, axis1 as usize)] = 1.0;
|
|
||||||
mat[(axis1 as usize, axis0 as usize)] = 1.0;
|
|
||||||
return Some(mat);
|
|
||||||
}
|
}
|
||||||
chars = chars0;
|
'0' ..= '9' => {
|
||||||
|
let val = f32::from_str_radix(chars.as_str().trim(), 10).ok()?;
|
||||||
|
Type::Translation(axis0, val)
|
||||||
Type::Translation(axis0)
|
|
||||||
};
|
|
||||||
|
|
||||||
// take what's left
|
|
||||||
let s = chars.as_str();
|
|
||||||
let delta = f32::from_str_radix(chars.as_str().trim(), 10).ok()?;
|
|
||||||
|
|
||||||
eprintln!("Transform: {:?} {}", ttyp, delta);
|
|
||||||
|
|
||||||
match ttyp {
|
|
||||||
Type::Rotation(a, b) => {
|
|
||||||
let (a0, a1) = match (a,b) {
|
|
||||||
(0,1) => (2,3),
|
|
||||||
(0,2) => (1,3),
|
|
||||||
(0,3) => (1,2),
|
|
||||||
(1,2) => (0,3),
|
|
||||||
(1,3) => (0,2),
|
|
||||||
(2,3) => (0,1),
|
|
||||||
_ => unreachable!("Invalid rotation axes"),
|
|
||||||
};
|
|
||||||
let delta = delta.to_radians();
|
|
||||||
let s = delta.sin();
|
|
||||||
let c = delta.cos();
|
|
||||||
let mut m = nalgebra::Matrix5::identity();
|
|
||||||
m[(a0,a0)] = c;
|
|
||||||
m[(a0,a1)] = -s;
|
|
||||||
m[(a1,a0)] = s;
|
|
||||||
m[(a1,a1)] = c;
|
|
||||||
Some(m)
|
|
||||||
}
|
}
|
||||||
Type::Translation(a) => {
|
_ => return None,
|
||||||
let mut m = nalgebra::Matrix5::identity();
|
})
|
||||||
m[(4, a as usize)] = -delta;
|
|
||||||
Some(m)
|
|
||||||
}
|
|
||||||
Type::CoordSwap(a, b) => {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_transforms<'a>(transforms: impl Iterator<Item=impl AsRef<str>>) ->anyhow::Result<super::Transform> {
|
pub fn transform_to_coords(mat: &MTransform) -> super::Transform {
|
||||||
use super::Point;
|
|
||||||
let mat = transforms
|
|
||||||
.map(|s| parse_step(s.as_ref())
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("Invalid step: {:?}", s.as_ref())))
|
|
||||||
.try_fold(nalgebra::Matrix5::identity(),
|
|
||||||
|acc, new| new.map(|new| acc * new))?;
|
|
||||||
|
|
||||||
let bx = Point::new(mat[(0,0)], mat[(0,1)]);
|
let bx = Point::new(mat[(0,0)], mat[(0,1)]);
|
||||||
let by = Point::new(mat[(1,0)], mat[(1,1)]);
|
let by = Point::new(mat[(1,0)], mat[(1,1)]);
|
||||||
let bd = Point::new(mat[(4,0)], mat[(4,1)]);
|
let bd = Point::new(mat[(4,0)], mat[(4,1)]);
|
||||||
@@ -110,12 +104,23 @@ pub fn parse_transforms<'a>(transforms: impl Iterator<Item=impl AsRef<str>>) ->a
|
|||||||
let cx = Point::new(mat[(0,2)], mat[(0,3)]);
|
let cx = Point::new(mat[(0,2)], mat[(0,3)]);
|
||||||
let cy = Point::new(mat[(1,2)], mat[(1,3)]);
|
let cy = Point::new(mat[(1,2)], mat[(1,3)]);
|
||||||
let cd = Point::new(mat[(4,2)], mat[(4,3)]);
|
let cd = Point::new(mat[(4,2)], mat[(4,3)]);
|
||||||
|
[
|
||||||
Ok([
|
|
||||||
(bx, cx),
|
(bx, cx),
|
||||||
(by, cy),
|
(by, cy),
|
||||||
(bd, cd),
|
(bd, cd),
|
||||||
])
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_transforms<'a>(transforms: impl Iterator<Item=impl AsRef<str>>) ->anyhow::Result<super::Transform> {
|
||||||
|
use super::Point;
|
||||||
|
let mat = transforms
|
||||||
|
.map(|s| parse_step(s.as_ref())
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Invalid step: {:?}", s.as_ref())))
|
||||||
|
.try_fold(MTransform::identity(),
|
||||||
|
|acc, new| new.map(|new| acc * MTransform::from(new)))?;
|
||||||
|
|
||||||
|
|
||||||
|
Ok(transform_to_coords(&mat))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user