130 lines
3.8 KiB
Rust
130 lines
3.8 KiB
Rust
use num::Num;
|
|
use super::{Float, Point};
|
|
pub type MTransform = nalgebra::Matrix5<Float>;
|
|
|
|
#[derive(Debug)]
|
|
pub enum Type {
|
|
Rotation(u8, u8, Float),
|
|
Translation(u8, Float),
|
|
CoordSwap(u8, u8),
|
|
Scale(Float),
|
|
}
|
|
|
|
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) => {
|
|
let mut xf = MTransform::identity() / delta;
|
|
xf[(4,4)] = 1.;
|
|
xf
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fn parse_axis(c: char) -> Option<u8> {
|
|
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<Type> {
|
|
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<Item=impl AsRef<str>>) ->anyhow::Result<super::Transform> {
|
|
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<super::Transform> {
|
|
parse_transforms(s.split_whitespace())
|
|
} |