use super::generated::types::{Circle, FlatPoint, Line, Motor, PointPair, RoundPoint};
use crate::scalar::Float;
impl<T: Float> RoundPoint<T> {
#[inline]
pub fn from_euclidean(x: T, y: T) -> Self {
let sq = x * x + y * y;
let half = T::one() / T::TWO;
let ep_coeff = (sq - T::one()) * half;
let em_coeff = (sq + T::one()) * half;
Self::new_unchecked(x, y, ep_coeff, em_coeff)
}
#[inline]
pub fn from_euclidean_weighted(x: T, y: T, weight: T) -> Self {
let sq = x * x + y * y;
let half = T::one() / T::TWO;
let ep_coeff = (sq - T::one()) * half;
let em_coeff = (sq + T::one()) * half;
Self::new_unchecked(x * weight, y * weight, ep_coeff * weight, em_coeff * weight)
}
#[inline]
pub fn origin() -> Self {
let half = T::one() / T::TWO;
Self::new_unchecked(T::zero(), T::zero(), -half, half)
}
#[inline]
pub fn infinity() -> Self {
Self::new_unchecked(T::zero(), T::zero(), T::one(), T::one())
}
#[inline]
pub fn to_euclidean(&self) -> Option<(T, T)> {
let o = self.em() - self.ep();
if o.abs() < T::epsilon() {
None
} else {
Some((self.x() / o, self.y() / o))
}
}
#[inline]
pub fn euclidean_x(&self) -> T {
let o = self.em() - self.ep();
self.x() / o
}
#[inline]
pub fn euclidean_y(&self) -> T {
let o = self.em() - self.ep();
self.y() / o
}
#[inline]
pub fn null_origin_weight(&self) -> T {
self.em() - self.ep()
}
#[inline]
pub fn null_infinity_weight(&self) -> T {
(self.em() + self.ep()) / T::TWO
}
#[inline]
pub fn is_at_infinity(&self, epsilon: T) -> bool {
self.null_origin_weight().abs() < epsilon
}
pub fn distance_squared(&self, other: &RoundPoint<T>) -> T {
let inner = self.x() * other.x() + self.y() * other.y() + self.ep() * other.ep()
- self.em() * other.em();
-T::TWO * inner
}
pub fn distance(&self, other: &RoundPoint<T>) -> T {
self.distance_squared(other).abs().sqrt()
}
}
impl<T: Float> Circle<T> {
pub fn from_center_radius(cx: T, cy: T, radius: T) -> Self {
let p1 = RoundPoint::from_euclidean(cx + radius, cy);
let p2 = RoundPoint::from_euclidean(cx, cy + radius);
let p3 = RoundPoint::from_euclidean(cx - radius, cy);
Self::from_three_points(&p1, &p2, &p3)
}
pub fn from_three_points(p1: &RoundPoint<T>, p2: &RoundPoint<T>, p3: &RoundPoint<T>) -> Self {
use crate::ops::Wedge;
p1.wedge(p2).wedge(p3)
}
#[inline]
pub fn is_line(&self, epsilon: T) -> bool {
(self.w() - self.e12em()).abs() < epsilon
}
pub fn center(&self) -> Option<(T, T)> {
let denom = self.w() - self.e12em();
if denom.abs() < T::epsilon() {
return None;
}
let center_x = self.e2epem() / denom;
let center_y = -self.e1epem() / denom;
Some((center_x, center_y))
}
pub fn radius(&self) -> Option<T> {
let w = self.w();
let e12em = self.e12em();
let denom = e12em - w;
if denom.abs() < T::epsilon() {
return None;
}
let center_denom = w - e12em; let center_x = self.e2epem() / center_denom;
let center_y = -self.e1epem() / center_denom;
let radius_sq = (e12em + w) / denom + center_x * center_x + center_y * center_y;
if radius_sq < T::zero() {
return None;
}
Some(radius_sq.sqrt())
}
pub fn curvature(&self) -> Option<T> {
self.radius().map(|r| T::one() / r)
}
pub fn to_line_params(&self, epsilon: T) -> Option<(T, T, T)> {
if self.is_line(epsilon) {
Some((self.e12em(), self.e1epem(), self.e2epem()))
} else {
None
}
}
}
impl<T: Float> Line<T> {
pub fn from_two_points(p1: &RoundPoint<T>, p2: &RoundPoint<T>) -> Self {
use crate::ops::Wedge;
let inf = RoundPoint::infinity();
let circle: Circle<T> = p1.wedge(p2).wedge(&inf);
Self::new_unchecked(circle.e12em(), circle.e1epem(), circle.e2epem())
}
#[inline]
pub fn from_equation(nx: T, ny: T, d: T) -> Self {
Self::new_unchecked(nx, ny, d)
}
}
impl<T: Float> FlatPoint<T> {
#[inline]
pub fn from_euclidean(x: T, y: T) -> Self {
Self::new_unchecked(x, y, T::one())
}
#[inline]
pub fn to_euclidean(&self) -> Option<(T, T)> {
if self.epem().abs() < T::epsilon() {
None
} else {
Some((self.e1em() / self.epem(), self.e2em() / self.epem()))
}
}
}
impl<T: Float> Motor<T> {
#[inline]
pub fn identity() -> Self {
Self::new_unchecked(
T::one(), T::zero(), T::zero(), T::zero(), T::zero(), T::zero(), T::zero(), T::zero(), )
}
pub fn from_translation(dx: T, dy: T) -> Self {
let half = T::one() / T::TWO;
Self::new_unchecked(
T::one(), T::zero(), -dx * half, -dy * half, -dx * half, -dy * half, T::zero(), T::zero(), )
}
pub fn from_rotation(angle: T) -> Self {
let half = angle / T::TWO;
Self::new_unchecked(
half.cos(), -half.sin(), T::zero(), T::zero(), T::zero(), T::zero(), T::zero(), T::zero(), )
}
pub fn from_dilation(scale: T) -> Self {
let half_log = scale.ln() / T::TWO;
Self::new_unchecked(
half_log.cosh(), T::zero(), T::zero(), T::zero(), T::zero(), T::zero(), half_log.sinh(), T::zero(), )
}
#[inline]
pub fn compose(&self, other: Self) -> Self {
other * *self
}
}
impl<T: Float> PointPair<T> {
pub fn from_points(p1: &RoundPoint<T>, p2: &RoundPoint<T>) -> Self {
use crate::ops::Wedge;
p1.wedge(p2)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::RELATIVE_EQ_EPS;
use approx::relative_eq;
#[test]
fn round_point_euclidean_roundtrip() {
let x = 3.0_f64;
let y = 4.0_f64;
let p = RoundPoint::from_euclidean(x, y);
let (rx, ry) = p.to_euclidean().unwrap();
assert!(relative_eq!(
rx,
x,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
ry,
y,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn round_point_origin() {
let o = RoundPoint::<f64>::origin();
let (x, y) = o.to_euclidean().unwrap();
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 round_point_infinity() {
let inf = RoundPoint::<f64>::infinity();
assert!(inf.to_euclidean().is_none());
assert!(inf.is_at_infinity(RELATIVE_EQ_EPS));
}
#[test]
fn round_point_is_null_vector() {
let p = RoundPoint::from_euclidean(3.0_f64, 4.0);
let p_dot_p = p.x() * p.x() + p.y() * p.y() + p.ep() * p.ep() - p.em() * p.em();
assert!(
relative_eq!(p_dot_p, 0.0, epsilon = 1e-10),
"Point should be a null vector: P·P = {} ≠ 0",
p_dot_p
);
}
#[test]
fn round_point_origin_is_null_vector() {
let o = RoundPoint::<f64>::origin();
let o_dot_o = o.x() * o.x() + o.y() * o.y() + o.ep() * o.ep() - o.em() * o.em();
assert!(
relative_eq!(o_dot_o, 0.0, epsilon = 1e-10),
"Origin should be a null vector: O·O = {} ≠ 0",
o_dot_o
);
}
#[test]
fn round_point_infinity_is_null_vector() {
let inf = RoundPoint::<f64>::infinity();
let inf_dot_inf =
inf.x() * inf.x() + inf.y() * inf.y() + inf.ep() * inf.ep() - inf.em() * inf.em();
assert!(
relative_eq!(inf_dot_inf, 0.0, epsilon = 1e-10),
"Infinity should be a null vector: e∞·e∞ = {} ≠ 0",
inf_dot_inf
);
}
#[test]
fn round_point_distance() {
let p1 = RoundPoint::<f64>::origin();
let p2 = RoundPoint::from_euclidean(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 motor_identity() {
let m = Motor::<f64>::identity();
assert!(relative_eq!(
m.s(),
1.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
assert!(relative_eq!(
m.m(),
0.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn test_circle_is_line_detection() {
use crate::ops::Wedge;
let p1 = RoundPoint::from_euclidean(0.0_f64, 0.0);
let p2 = RoundPoint::from_euclidean(1.0, 0.0);
let p3 = RoundPoint::from_euclidean(2.0, 0.0);
let result: Circle<f64> = p1.wedge(&p2).wedge(&p3);
assert!(
result.is_line(1e-10),
"Collinear points should produce a line (w=0), got w={}",
result.w()
);
let p1 = RoundPoint::from_euclidean(1.0_f64, 0.0);
let p2 = RoundPoint::from_euclidean(0.0, 1.0);
let p3 = RoundPoint::from_euclidean(-1.0, 0.0);
let result: Circle<f64> = p1.wedge(&p2).wedge(&p3);
assert!(
!result.is_line(1e-10),
"Non-collinear points should produce a circle (w≠0), got w={}",
result.w()
);
}
#[test]
fn test_circle_center_radius_extraction() {
let test_cases: Vec<(f64, f64, f64)> = vec![
(0.0, 0.0, 1.0),
(0.0, 0.0, 2.0),
(0.0, 0.0, 0.5),
(1.0, 0.0, 1.0),
(0.0, 1.0, 1.0),
(1.0, 1.0, 1.0),
(2.0, 3.0, 1.0),
(1.0, 1.0, 2.0),
(-1.0, -1.0, 1.5),
(3.5, -2.7, 1.2),
];
for (expected_cx, expected_cy, expected_r) in &test_cases {
let p1 = RoundPoint::from_euclidean(expected_cx + expected_r, *expected_cy);
let p2 = RoundPoint::from_euclidean(*expected_cx, expected_cy + expected_r);
let p3 = RoundPoint::from_euclidean(expected_cx - expected_r, *expected_cy);
let circle = Circle::from_three_points(&p1, &p2, &p3);
let (calc_cx, calc_cy) = circle.center().expect("Circle should have finite center");
assert!(
relative_eq!(
calc_cx,
*expected_cx,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
),
"Center x mismatch for ({}, {}, {}): expected {}, got {}",
expected_cx,
expected_cy,
expected_r,
expected_cx,
calc_cx
);
assert!(
relative_eq!(
calc_cy,
*expected_cy,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
),
"Center y mismatch for ({}, {}, {}): expected {}, got {}",
expected_cx,
expected_cy,
expected_r,
expected_cy,
calc_cy
);
let calc_r = circle.radius().expect("Circle should have finite radius");
assert!(
relative_eq!(
calc_r,
*expected_r,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
),
"Radius mismatch for ({}, {}, {}): expected {}, got {}",
expected_cx,
expected_cy,
expected_r,
expected_r,
calc_r
);
let calc_curv = circle.curvature().expect("Circle should have curvature");
let expected_curv = 1.0 / expected_r;
assert!(
relative_eq!(
calc_curv,
expected_curv,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
),
"Curvature mismatch: expected {}, got {}",
expected_curv,
calc_curv
);
}
}
#[test]
fn test_circle_line_has_no_center_or_radius() {
let p1 = RoundPoint::from_euclidean(0.0_f64, 0.0);
let p2 = RoundPoint::from_euclidean(1.0, 0.0);
let p3 = RoundPoint::from_euclidean(2.0, 0.0);
let line = Circle::from_three_points(&p1, &p2, &p3);
assert!(line.is_line(1e-10), "Collinear points should form a line");
assert!(line.center().is_none(), "Line should have no finite center");
assert!(line.radius().is_none(), "Line should have no finite radius");
assert!(line.curvature().is_none(), "Line should have no curvature");
}
#[test]
fn test_inverse_sandwich_circle_inversion() {
use crate::ops::InverseSandwich;
let inv_circle = Circle::from_center_radius(0.0_f64, 0.0, 2.0);
let point = RoundPoint::from_euclidean(4.0, 0.0);
let inverted = inv_circle.try_inverse_sandwich(&point);
assert!(
inverted.is_some(),
"InverseSandwich should return Some for valid circle and point"
);
let (ix, iy) = inverted.unwrap().to_euclidean().unwrap();
assert!(
relative_eq!(ix, 1.0, epsilon = 1e-10),
"Inverted x should be 1.0, got {}",
ix
);
assert!(
relative_eq!(iy, 0.0, epsilon = 1e-10),
"Inverted y should be 0.0, got {}",
iy
);
}
#[test]
fn test_circle_from_three_points_various_positions() {
use std::f64::consts::PI;
let cx = 2.0_f64;
let cy = 3.0;
let r = 5.0;
let angle1 = 0.0_f64;
let angle2 = 2.0_f64 * PI / 3.0;
let angle3 = 4.0_f64 * PI / 3.0;
let p1 = RoundPoint::from_euclidean(cx + r * angle1.cos(), cy + r * angle1.sin());
let p2 = RoundPoint::from_euclidean(cx + r * angle2.cos(), cy + r * angle2.sin());
let p3 = RoundPoint::from_euclidean(cx + r * angle3.cos(), cy + r * angle3.sin());
let circle = Circle::from_three_points(&p1, &p2, &p3);
let (extracted_cx, extracted_cy) = circle.center().unwrap();
let extracted_r = circle.radius().unwrap();
assert!(
relative_eq!(extracted_cx, cx, epsilon = 1e-10),
"Center x: expected {}, got {}",
cx,
extracted_cx
);
assert!(
relative_eq!(extracted_cy, cy, epsilon = 1e-10),
"Center y: expected {}, got {}",
cy,
extracted_cy
);
assert!(
relative_eq!(extracted_r, r, epsilon = 1e-10),
"Radius: expected {}, got {}",
r,
extracted_r
);
}
}