use super::generated::types::{Flector, Line, Motor, Point, Trivector};
use crate::scalar::Float;
use crate::specialized::euclidean::dim2::Vector as EuclideanVector;
impl<T: Float> Point<T> {
#[inline]
pub fn from_cartesian(x: T, y: T) -> Self {
Self::new(x, y, T::one())
}
#[inline]
pub fn origin() -> Self {
Self::from_cartesian(T::zero(), T::zero())
}
#[inline]
pub fn cartesian_x(&self) -> T {
self.x() / self.w()
}
#[inline]
pub fn cartesian_y(&self) -> T {
self.y() / self.w()
}
#[inline]
pub fn to_cartesian(&self) -> Option<(T, T)> {
if self.w().abs() < T::epsilon() {
None
} else {
Some((self.x() / self.w(), self.y() / self.w()))
}
}
pub fn distance(&self, other: &Point<T>) -> T {
self.distance_squared(other).sqrt()
}
pub fn distance_squared(&self, other: &Point<T>) -> T {
let dx = self.cartesian_x() - other.cartesian_x();
let dy = self.cartesian_y() - other.cartesian_y();
dx * dx + dy * dy
}
pub fn midpoint(&self, other: &Point<T>) -> Point<T> {
let two = T::TWO;
Point::new(
(self.x() * other.w() + other.x() * self.w()) / two,
(self.y() * other.w() + other.y() * self.w()) / two,
self.w() * other.w(),
)
}
}
impl<T: Float> Line<T> {
pub fn from_point_and_direction(point: &Point<T>, direction: &EuclideanVector<T>) -> Self {
let ideal = Point::new(direction.x(), direction.y(), T::zero());
crate::ops::Wedge::wedge(point, &ideal)
}
#[inline]
pub fn from_equation(a: T, b: T, c: T) -> Self {
Self::new(c, a, b)
}
#[inline]
pub fn x_axis() -> Self {
Self::new(T::zero(), T::zero(), T::one())
}
#[inline]
pub fn y_axis() -> Self {
Self::new(T::zero(), T::one(), T::zero())
}
#[inline]
pub fn normal(&self) -> EuclideanVector<T> {
EuclideanVector::new(self.nx(), self.ny())
}
#[inline]
pub fn distance_from_origin(&self) -> T {
self.d()
}
pub fn through_origin(&self, epsilon: T) -> bool {
self.d().abs() < epsilon
}
pub fn is_parallel(&self, other: &Line<T>, epsilon: T) -> bool {
let cross = self.nx() * other.ny() - self.ny() * other.nx();
cross.abs() < epsilon
}
pub fn angle(&self, other: &Line<T>) -> T {
use crate::norm::DegenerateNormed;
let n1 = self.try_unitize().unwrap_or(*self);
let n2 = other.try_unitize().unwrap_or(*other);
let cos_angle = (n1.nx() * n2.nx() + n1.ny() * n2.ny()).abs().min(T::one());
cos_angle.acos()
}
pub fn signed_distance(&self, point: &Point<T>) -> T {
use crate::norm::DegenerateNormed;
let l = self.try_unitize().unwrap_or(*self);
(l.nx() * point.x() + l.ny() * point.y() + l.d() * point.w()) / point.w()
}
pub fn distance_to_point(&self, point: &Point<T>) -> T {
self.signed_distance(point).abs()
}
#[inline]
pub fn is_zero(&self, epsilon: T) -> bool {
use crate::norm::DegenerateNormed;
self.weight_norm() < epsilon
}
pub fn project_point(&self, point: &Point<T>) -> Point<T> {
let dist = self.signed_distance(point);
let n = self.normal();
let n_norm_sq = n.x() * n.x() + n.y() * n.y();
if n_norm_sq < T::epsilon() {
return *point;
}
Point::new(
point.x() - dist * n.x() * point.w() / n_norm_sq.sqrt(),
point.y() - dist * n.y() * point.w() / n_norm_sq.sqrt(),
point.w(),
)
}
}
impl<T: Float> Motor<T> {
#[inline]
pub fn identity() -> Self {
Self::new(T::zero(), T::zero(), T::zero(), T::one())
}
pub fn from_translation(dx: T, dy: T) -> Self {
let half = T::one() / T::TWO;
Self::new(
dy * half, -dx * half, T::zero(), T::one(), )
}
pub fn from_rotation(angle: T) -> Self {
let half = angle / T::TWO;
Self::new(
T::zero(), T::zero(), -half.sin(), half.cos(), )
}
pub fn from_rotation_around(center: &Point<T>, angle: T) -> Self {
use crate::ops::Versor;
let to_origin = Motor::from_translation(-center.cartesian_x(), -center.cartesian_y());
let rotation = Motor::from_rotation(angle);
let from_origin = Motor::from_translation(center.cartesian_x(), center.cartesian_y());
from_origin.compose(&rotation.compose(&to_origin))
}
pub fn rotation_angle(&self) -> T {
let cos_half = self.ps().min(T::one()).max(-T::one());
let sin_half = self.r();
sin_half.atan2(cos_half) * T::TWO
}
pub fn translation(&self) -> EuclideanVector<T> {
EuclideanVector::new(-T::TWO * self.tx(), T::TWO * self.ty())
}
}
impl<T: Float> Flector<T> {
pub fn from_line(line: &Line<T>) -> Self {
use crate::norm::DegenerateNormed;
let l = line.try_unitize().unwrap_or(*line);
Self::new(T::zero(), l.d(), l.nx(), l.ny())
}
#[inline]
pub fn reflect_y_axis() -> Self {
Self::from_line(&Line::y_axis())
}
#[inline]
pub fn reflect_x_axis() -> Self {
Self::from_line(&Line::x_axis())
}
pub fn reflect_through_angle(angle: T) -> Self {
let line = Line::new(T::zero(), angle.sin(), -angle.cos());
Self::from_line(&line)
}
#[inline]
pub fn line_part(&self) -> Line<T> {
Line::new(self.d(), self.nx(), self.ny())
}
#[inline]
pub fn is_pure_reflection(&self, epsilon: T) -> bool {
self.s().abs() < epsilon
}
}
impl<T: Float> Trivector<T> {
#[inline]
pub fn unit() -> Self {
Self::new(T::one())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ops::{Join, Meet, Transform};
use crate::test_utils::RELATIVE_EQ_EPS;
use approx::relative_eq;
#[test]
fn motor_identity_preserves_point() {
let p = Point::<f64>::from_cartesian(3.0, 4.0);
let m = Motor::<f64>::identity();
let result = m.transform(&p);
assert!(relative_eq!(
result.x(),
p.x(),
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
result.y(),
p.y(),
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
result.w(),
p.w(),
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn motor_translation_x() {
let origin = Point::<f64>::origin();
let t = Motor::<f64>::from_translation(2.0, 0.0);
let result = t.transform(&origin);
assert!(relative_eq!(
result.cartesian_x(),
2.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
result.cartesian_y(),
0.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn motor_translation_y() {
let origin = Point::<f64>::origin();
let t = Motor::<f64>::from_translation(0.0, 3.0);
let result = t.transform(&origin);
assert!(relative_eq!(
result.cartesian_x(),
0.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
result.cartesian_y(),
3.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn motor_rotation_90_degrees() {
use std::f64::consts::FRAC_PI_2;
let p = Point::<f64>::from_cartesian(1.0, 0.0);
let r = Motor::<f64>::from_rotation(FRAC_PI_2);
let result = r.transform(&p);
assert!(relative_eq!(
result.cartesian_x(),
0.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
result.cartesian_y(),
1.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn motor_transforms_point_and_line_consistently() {
use crate::ops::Join;
use std::f64::consts::FRAC_PI_4;
let p1 = Point::<f64>::from_cartesian(1.0, 0.0);
let p2 = Point::<f64>::from_cartesian(0.0, 1.0);
let line = p1.join(&p2);
eprintln!("Original p1: ({}, {})", p1.cartesian_x(), p1.cartesian_y());
eprintln!("Original p2: ({}, {})", p2.cartesian_x(), p2.cartesian_y());
eprintln!(
"Original line: nx={}, ny={}, d={}",
line.nx(),
line.ny(),
line.d()
);
let motor = Motor::<f64>::from_rotation(FRAC_PI_4);
eprintln!(
"Motor: ty={}, tx={}, r={}, ps={}",
motor.ty(),
motor.tx(),
motor.r(),
motor.ps()
);
let p1_transformed = motor.transform(&p1);
let p2_transformed = motor.transform(&p2);
let line_transformed = motor.transform(&line);
eprintln!(
"Transformed p1: ({}, {})",
p1_transformed.cartesian_x(),
p1_transformed.cartesian_y()
);
eprintln!(
"Transformed p2: ({}, {})",
p2_transformed.cartesian_x(),
p2_transformed.cartesian_y()
);
eprintln!(
"Transformed line (antisandwich): nx={}, ny={}, d={}",
line_transformed.nx(),
line_transformed.ny(),
line_transformed.d()
);
let line_from_transformed = p1_transformed.join(&p2_transformed);
eprintln!(
"Line from transformed points: nx={}, ny={}, d={}",
line_from_transformed.nx(),
line_from_transformed.ny(),
line_from_transformed.d()
);
let scale = if line_transformed.nx().abs() > 0.1 {
line_from_transformed.nx() / line_transformed.nx()
} else if line_transformed.ny().abs() > 0.1 {
line_from_transformed.ny() / line_transformed.ny()
} else {
line_from_transformed.d() / line_transformed.d()
};
eprintln!("Scale factor: {}", scale);
assert!(
relative_eq!(
line_transformed.nx() * scale,
line_from_transformed.nx(),
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
),
"nx mismatch: transformed={}, from_points={}",
line_transformed.nx(),
line_from_transformed.nx()
);
assert!(
relative_eq!(
line_transformed.ny() * scale,
line_from_transformed.ny(),
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
),
"ny mismatch: transformed={}, from_points={}",
line_transformed.ny(),
line_from_transformed.ny()
);
assert!(
relative_eq!(
line_transformed.d() * scale,
line_from_transformed.d(),
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
),
"d mismatch: transformed={}, from_points={}",
line_transformed.d(),
line_from_transformed.d()
);
}
#[test]
fn point_join_creates_line() {
let p1 = Point::<f64>::origin();
let p2 = Point::<f64>::from_cartesian(1.0, 0.0);
let line = p1.join(&p2);
assert!(relative_eq!(
line.nx(),
0.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
line.ny(),
1.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
line.d(),
0.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn line_meet_finds_intersection() {
let l1 = Line::<f64>::x_axis(); let l2 = Line::<f64>::y_axis();
let intersection = l1.meet(&l2);
let (x, y) = intersection.to_cartesian().expect("should be finite");
assert!(relative_eq!(
x,
0.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
y,
0.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn flector_reflect_x_axis() {
let f = Flector::<f64>::reflect_x_axis();
let p = Point::<f64>::from_cartesian(1.0, 2.0);
let result = f.transform(&p);
assert!(relative_eq!(
result.cartesian_x(),
1.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
result.cartesian_y(),
-2.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn flector_reflect_y_axis() {
let f = Flector::<f64>::reflect_y_axis();
let p = Point::<f64>::from_cartesian(1.0, 2.0);
let result = f.transform(&p);
assert!(relative_eq!(
result.cartesian_x(),
-1.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
result.cartesian_y(),
2.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn point_distance() {
let p1 = Point::<f64>::origin();
let p2 = Point::<f64>::from_cartesian(3.0, 4.0);
let dist = p1.distance(&p2);
assert!(relative_eq!(
dist,
5.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn point_midpoint() {
let p1 = Point::<f64>::from_cartesian(0.0, 0.0);
let p2 = Point::<f64>::from_cartesian(4.0, 6.0);
let mid = p1.midpoint(&p2);
assert!(relative_eq!(
mid.cartesian_x(),
2.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
mid.cartesian_y(),
3.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
}