use num::Num; use super::{Float, Point}; pub type MTransform = nalgebra::Matrix5; #[derive(Debug)] pub enum Type { Rotation(u8, u8, Float), Translation(u8, Float), CoordSwap(u8, u8), Scale(Float), } impl From 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) => { let mut xf = MTransform::identity() / delta; xf[(4,4)] = 1.; xf } } } } fn parse_axis(c: char) -> Option { Some(match c { 'x' => 0, 'y' => 1, 'X' => 2, 'Y' => 3, _ => return None, }) } /// We take a type and distance. /// 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. /// /// TODO: replace this with a nom parser pub fn parse_step(s: &str) -> Option { if let Some(r) = s.strip_prefix("z") { return Float::from_str_radix(r, 10).ok().map(Type::Scale); } if s.len() < 2 { return None; } let mut chars = s.chars(); let axis0 = chars.next().and_then(parse_axis)?; Some(match chars.clone().next()? { '=' => { chars.next(); let axis1 = chars.next().and_then(parse_axis)?; Type::CoordSwap(axis0, axis1) } 'x' | 'X' | 'y' | 'Y' => { chars.next(); let axis1 = chars.next().and_then(parse_axis)?; if axis0 == axis1 { return None; } let val = Float::from_str_radix(chars.as_str().trim(), 10).ok()?; Type::Rotation(axis0, axis1, val) } '0' ..= '9' => { let val = Float::from_str_radix(chars.as_str().trim(), 10).ok()?; Type::Translation(axis0, val) } _ => return None, }) } pub fn transform_to_coords(mat: &MTransform) -> super::Transform { 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)]); [ (bx, cx), (by, cy), (bd, cd), ] } pub fn parse_transforms<'a>(transforms: impl Iterator>) ->anyhow::Result { 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)) } pub fn parse_transform(s: &str) -> anyhow::Result { parse_transforms(s.split_whitespace()) }