use super::multivector::Multivector;
#[derive(Debug, Clone, Copy)]
pub struct Motor {
pub mv: Multivector,
}
impl Motor {
pub fn identity() -> Self {
Self {
mv: Multivector::one(),
}
}
pub fn rotor(axis: [f64; 3], angle: f64) -> Self {
let half = angle / 2.0;
let c = half.cos();
let s = half.sin();
let mut mv = Multivector::ZERO;
mv.data[0] = c;
mv.data[6] = -s * axis[0]; mv.data[5] = s * axis[1]; mv.data[3] = -s * axis[2];
Self { mv }
}
pub fn translator(t: [f64; 3]) -> Self {
let mut mv = Multivector::one();
let half = -0.5;
mv.data[9] += half * t[0]; mv.data[17] += half * t[0]; mv.data[10] += half * t[1]; mv.data[18] += half * t[1]; mv.data[12] += half * t[2]; mv.data[20] += half * t[2];
Self { mv }
}
pub fn from_rotation_translation(axis: [f64; 3], angle: f64, translation: [f64; 3]) -> Self {
let r = Self::rotor(axis, angle);
let t = Self::translator(translation);
Self {
mv: t.mv.geo(&r.mv),
}
}
pub fn compose(&self, other: &Motor) -> Motor {
Motor {
mv: self.mv.geo(&other.mv),
}
}
pub fn apply(&self, point: &Multivector) -> Multivector {
self.mv.sandwich(point)
}
pub fn transform_point(&self, p: [f64; 3]) -> [f64; 3] {
let cp = conformal_point(p);
let result = self.apply(&cp);
extract_euclidean(&result)
}
pub fn reverse(&self) -> Motor {
Motor {
mv: self.mv.reverse(),
}
}
pub fn normalized(&self) -> Motor {
Motor {
mv: self.mv.normalized(),
}
}
}
pub fn conformal_point(p: [f64; 3]) -> Multivector {
let [x, y, z] = p;
let sq = x * x + y * y + z * z;
let mut mv = Multivector::ZERO;
mv.data[1] = x; mv.data[2] = y; mv.data[4] = z;
mv.data[1 << 3] += 0.5 * sq; mv.data[1 << 4] += 0.5 * sq;
mv.data[1 << 3] -= 0.5; mv.data[1 << 4] += 0.5;
mv
}
pub fn extract_euclidean(p: &Multivector) -> [f64; 3] {
let einf = Multivector::infinity();
let dot = p.inner(&einf);
let weight = -dot.scalar();
if weight.abs() < 1e-15 {
return [p.data[1], p.data[2], p.data[4]];
}
[
p.data[1] / weight, p.data[2] / weight, p.data[4] / weight, ]
}
#[cfg(test)]
mod tests {
use super::*;
use std::f64::consts::FRAC_PI_2;
#[test]
fn test_identity_motor() {
let m = Motor::identity();
let p = [1.0, 2.0, 3.0];
let result = m.transform_point(p);
for i in 0..3 {
assert!(
(result[i] - p[i]).abs() < 1e-10,
"Identity motor changed coordinate {}: {} → {}",
i,
p[i],
result[i]
);
}
}
#[test]
fn test_90_deg_rotation_z() {
let m = Motor::rotor([0.0, 0.0, 1.0], FRAC_PI_2);
let result = m.transform_point([1.0, 0.0, 0.0]);
assert!((result[0] - 0.0).abs() < 1e-10, "x = {}", result[0]);
assert!((result[1] - 1.0).abs() < 1e-10, "y = {}", result[1]);
assert!((result[2] - 0.0).abs() < 1e-10, "z = {}", result[2]);
}
#[test]
fn test_translation() {
let m = Motor::translator([1.0, 0.0, 0.0]);
let result = m.transform_point([0.0, 0.0, 0.0]);
assert!((result[0] - 1.0).abs() < 1e-10, "x = {}", result[0]);
assert!((result[1] - 0.0).abs() < 1e-10, "y = {}", result[1]);
assert!((result[2] - 0.0).abs() < 1e-10, "z = {}", result[2]);
}
#[test]
fn test_rotation_then_translation() {
let m = Motor::from_rotation_translation([0.0, 0.0, 1.0], FRAC_PI_2, [3.0, 0.0, 0.0]);
let result = m.transform_point([1.0, 0.0, 0.0]);
assert!((result[0] - 3.0).abs() < 1e-10, "x = {}", result[0]);
assert!((result[1] - 1.0).abs() < 1e-10, "y = {}", result[1]);
assert!((result[2] - 0.0).abs() < 1e-10, "z = {}", result[2]);
}
#[test]
fn test_conformal_round_trip() {
let p = [1.234, -5.678, 9.012];
let cp = conformal_point(p);
let ep = extract_euclidean(&cp);
for i in 0..3 {
assert!(
(ep[i] - p[i]).abs() < 1e-10,
"Round-trip error at {}: {} vs {}",
i,
ep[i],
p[i]
);
}
}
#[test]
fn test_motor_composition() {
let r90 = Motor::rotor([0.0, 0.0, 1.0], FRAC_PI_2);
let r180 = r90.compose(&r90);
let result = r180.transform_point([1.0, 0.0, 0.0]);
assert!((result[0] - (-1.0)).abs() < 1e-10, "x = {}", result[0]);
assert!((result[1] - 0.0).abs() < 1e-10, "y = {}", result[1]);
}
}