use super::generated::types::{Circle, FlatPoint, Line, Motor, Plane, RoundPoint, Sphere};
use crate::scalar::Float;
impl<T: Float> RoundPoint<T> {
#[inline]
pub fn from_euclidean(x: T, y: T, z: T) -> Self {
let sq = x * x + y * y + z * z;
let half = T::one() / T::TWO;
let w_coeff = (sq - T::one()) * half;
let u_coeff = (sq + T::one()) * half;
Self::new_unchecked(x, y, z, w_coeff, u_coeff)
}
#[inline]
pub fn from_euclidean_weighted(x: T, y: T, z: T, weight: T) -> Self {
let sq = x * x + y * y + z * z;
let half = T::one() / T::TWO;
let w_coeff = (sq - T::one()) * half;
let u_coeff = (sq + T::one()) * half;
Self::new_unchecked(
x * weight,
y * weight,
z * weight,
w_coeff * weight,
u_coeff * weight,
)
}
#[inline]
pub fn origin() -> Self {
let half = T::one() / T::TWO;
Self::new_unchecked(T::zero(), T::zero(), T::zero(), -half, half)
}
#[inline]
pub fn infinity() -> Self {
Self::new_unchecked(T::zero(), T::zero(), T::zero(), T::one(), T::one())
}
#[inline]
pub fn to_euclidean(&self) -> Option<(T, T, T)> {
let o = self.u() - self.w();
if o.abs() < T::epsilon() {
None
} else {
Some((self.x() / o, self.y() / o, self.z() / o))
}
}
#[inline]
pub fn euclidean_x(&self) -> T {
let o = self.u() - self.w();
self.x() / o
}
#[inline]
pub fn euclidean_y(&self) -> T {
let o = self.u() - self.w();
self.y() / o
}
#[inline]
pub fn euclidean_z(&self) -> T {
let o = self.u() - self.w();
self.z() / o
}
#[inline]
pub fn null_origin_weight(&self) -> T {
self.u() - self.w()
}
#[inline]
pub fn null_infinity_weight(&self) -> T {
(self.u() + self.w()) / 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.z() * other.z()
+ self.w() * other.w()
- self.u() * other.u();
-T::TWO * inner
}
pub fn distance(&self, other: &RoundPoint<T>) -> T {
self.distance_squared(other).abs().sqrt()
}
}
impl<T: Float> Sphere<T> {
pub fn from_center_radius(cx: T, cy: T, cz: T, radius: T) -> Self {
let p1 = RoundPoint::from_euclidean(cx + radius, cy, cz);
let p2 = RoundPoint::from_euclidean(cx, cy + radius, cz);
let p3 = RoundPoint::from_euclidean(cx, cy, cz + radius);
let p4 = RoundPoint::from_euclidean(cx - radius, cy, cz);
Self::from_four_points(&p1, &p2, &p3, &p4)
}
pub fn from_four_points(
p1: &RoundPoint<T>,
p2: &RoundPoint<T>,
p3: &RoundPoint<T>,
p4: &RoundPoint<T>,
) -> Self {
use crate::ops::Wedge;
p1.wedge(p2).wedge(p3).wedge(p4)
}
#[inline]
pub fn is_plane(&self, epsilon: T) -> bool {
self.u().abs() < epsilon
}
pub fn center(&self) -> Option<(T, T, T)> {
let denom = self.x() - self.u();
if denom.abs() < T::epsilon() {
return None; }
let center_x = self.w() / denom;
let center_y = -self.z() / denom;
let center_z = self.y() / denom;
Some((center_x, center_y, center_z))
}
pub fn radius(&self) -> Option<T> {
let denom = self.x() - self.u();
if denom.abs() < T::epsilon() {
return None; }
let cx = self.w() / denom;
let cy = -self.z() / denom;
let cz = self.y() / denom;
let c_sq = cx * cx + cy * cy + cz * cz;
let radius_sq = (self.u() + self.x()) / denom + c_sq;
if radius_sq < T::zero() {
return None;
}
Some(radius_sq.sqrt())
}
pub fn curvature(&self) -> Option<T> {
self.radius().map(|r| T::one() / r)
}
}
impl<T: Float> Circle<T> {
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 {
let eps_sq = epsilon * epsilon;
let g_norm_sq = self.gw() * self.gw()
+ self.gz() * self.gz()
+ self.gy() * self.gy()
+ self.gx() * self.gx();
let m_norm_sq = self.mx() * self.mx() + self.my() * self.my() + self.mz() * self.mz();
g_norm_sq < eps_sq && m_norm_sq < eps_sq
}
pub fn center(&self) -> Option<(T, T, T)> {
let gw_coeff = self.gw();
let denom = gw_coeff - self.mz();
if gw_coeff.abs() < T::epsilon() {
return None;
}
if denom.abs() < T::epsilon() {
return None;
}
let center_x = self.my() / denom;
let center_y = -self.mx() / denom;
let center_z = self.gz() / gw_coeff;
Some((center_x, center_y, center_z))
}
pub fn normal(&self) -> Option<(T, T, T)> {
let gw_coeff = self.gw();
if gw_coeff.abs() < T::epsilon() {
return None;
}
let nx = self.gx();
let ny = self.gy();
let nz = self.gz();
let len = (nx * nx + ny * ny + nz * nz).sqrt();
if len < T::epsilon() {
return None;
}
Some((nx / len, ny / len, nz / len))
}
pub fn radius(&self) -> Option<T> {
let gw_coeff = self.gw();
let denom = gw_coeff - self.mz();
if gw_coeff.abs() < T::epsilon() {
return None;
}
if denom.abs() < T::epsilon() {
return None;
}
let cx = self.my() / denom;
let cy = -self.mx() / denom;
let cz = self.gz() / gw_coeff;
let vx = self.vx();
let vy = self.vy();
let vz = self.vz();
let v_sq = vx * vx + vy * vy + vz * vz;
let c_sq = cx * cx + cy * cy + cz * cz;
let radius_sq = v_sq / (T::TWO * denom * denom) + c_sq;
if radius_sq < T::zero() {
return None;
}
Some(radius_sq.sqrt())
}
pub fn curvature(&self) -> Option<T> {
self.radius().map(|r| T::one() / r)
}
}
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.mz(),
circle.my(),
circle.mx(),
circle.vx(),
circle.vy(),
circle.vz(),
)
}
}
impl<T: Float> Plane<T> {
#[inline]
pub fn from_normal_distance(nx: T, ny: T, nz: T, d: T) -> Self {
Self::new_unchecked(nx, ny, nz, d)
}
pub fn from_three_points(p1: &RoundPoint<T>, p2: &RoundPoint<T>, p3: &RoundPoint<T>) -> Self {
use crate::ops::Wedge;
let inf = RoundPoint::infinity();
let sphere: Sphere<T> = p1.wedge(p2).wedge(p3).wedge(&inf);
Self::new_unchecked(sphere.x(), sphere.y(), sphere.z(), sphere.w())
}
}
impl<T: Float> FlatPoint<T> {
#[inline]
pub fn from_euclidean(x: T, y: T, z: T) -> Self {
Self::new_unchecked(x, y, z, T::one())
}
#[inline]
pub fn to_euclidean(&self) -> Option<(T, T, T)> {
if self.pw().abs() < T::epsilon() {
None
} else {
Some((
self.px() / self.pw(),
self.py() / self.pw(),
self.pz() / self.pw(),
))
}
}
}
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(), T::zero(), T::zero(), T::zero(), T::zero(), T::zero(), T::zero(), T::zero(), T::zero(), )
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::RELATIVE_EQ_EPS;
use approx::relative_eq;
use proptest::prelude::*;
fn coord_strategy() -> impl Strategy<Value = f64> {
-10.0..10.0_f64
}
proptest! {
#[test]
fn round_point_distance_symmetric(
x1 in coord_strategy(),
y1 in coord_strategy(),
z1 in coord_strategy(),
x2 in coord_strategy(),
y2 in coord_strategy(),
z2 in coord_strategy()
) {
let p1 = RoundPoint::from_euclidean(x1, y1, z1);
let p2 = RoundPoint::from_euclidean(x2, y2, z2);
let d12 = p1.distance(&p2);
let d21 = p2.distance(&p1);
prop_assert!(
relative_eq!(d12, d21, epsilon = 1e-10),
"distance should be symmetric: d(p1,p2)={} != d(p2,p1)={}", d12, d21
);
let expected = ((x2-x1).powi(2) + (y2-y1).powi(2) + (z2-z1).powi(2)).sqrt();
prop_assert!(
relative_eq!(d12, expected, epsilon = 1e-8, max_relative = 1e-8),
"distance mismatch: CGA={}, Euclidean={}", d12, expected
);
}
}
#[test]
fn round_point_euclidean_roundtrip() {
let x = 3.0_f64;
let y = 4.0_f64;
let z = 5.0_f64;
let p = RoundPoint::from_euclidean(x, y, z);
let (rx, ry, rz) = 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
));
assert!(relative_eq!(
rz,
z,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn round_point_origin() {
let o = RoundPoint::<f64>::origin();
let (x, y, z) = 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
));
assert!(relative_eq!(
z,
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, 5.0);
let p_dot_p = p.x() * p.x() + p.y() * p.y() + p.z() * p.z() + p.w() * p.w() - p.u() * p.u();
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.z() * o.z() + o.w() * o.w() - o.u() * o.u();
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.z() * inf.z() + inf.w() * inf.w()
- inf.u() * inf.u();
assert!(
relative_eq!(inf_dot_inf, 0.0, epsilon = 1e-10),
"Infinity should be a null vector: einf*einf = {} != 0",
inf_dot_inf
);
}
#[test]
fn round_point_distance() {
let p1 = RoundPoint::<f64>::origin();
let p2 = RoundPoint::from_euclidean(3.0, 4.0, 0.0);
let dist = p1.distance(&p2);
assert!(relative_eq!(
dist,
5.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn round_point_distance_3d() {
let p1 = RoundPoint::<f64>::origin();
let p2 = RoundPoint::from_euclidean(1.0, 2.0, 2.0);
let dist = p1.distance(&p2);
assert!(relative_eq!(
dist,
3.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.mx(),
0.0,
epsilon = RELATIVE_EQ_EPS,
max_relative = RELATIVE_EQ_EPS
));
}
#[test]
fn sphere_from_center_radius_construction() {
let sphere = Sphere::from_center_radius(1.0_f64, 2.0, 3.0, 2.0);
assert!(!sphere.is_plane(1e-10), "Sphere should not be a plane");
}
#[test]
fn sphere_from_four_points() {
use crate::ops::Wedge;
let p1 = RoundPoint::from_euclidean(2.0_f64, 0.0, 0.0);
let p2 = RoundPoint::from_euclidean(1.0, 1.0, 0.0);
let p3 = RoundPoint::from_euclidean(1.0, 0.0, 1.0);
let p4 = RoundPoint::from_euclidean(0.0, 0.0, 0.0);
let sphere: Sphere<f64> = p1.wedge(&p2).wedge(&p3).wedge(&p4);
let norm = sphere.norm();
assert!(
norm > 1e-10,
"Sphere from 4 points should have non-zero norm"
);
}
#[test]
fn circle_from_three_points() {
use crate::ops::Wedge;
let p1 = RoundPoint::from_euclidean(2.0_f64, 1.0, 0.0);
let p2 = RoundPoint::from_euclidean(1.0, 2.0, 0.0);
let p3 = RoundPoint::from_euclidean(0.0, 1.0, 0.0);
let circle: Circle<f64> = p1.wedge(&p2).wedge(&p3);
assert!(
!circle.is_line(1e-10),
"Non-collinear points should form a circle (not a line)"
);
}
#[test]
fn circle_collinear_points_is_line() {
use crate::ops::Wedge;
let p1 = RoundPoint::from_euclidean(0.0_f64, 0.0, 0.0);
let p2 = RoundPoint::from_euclidean(1.0, 0.0, 0.0);
let p3 = RoundPoint::from_euclidean(2.0, 0.0, 0.0);
let result: Circle<f64> = p1.wedge(&p2).wedge(&p3);
assert!(
result.is_line(1e-10),
"Collinear points should produce a line"
);
}
#[test]
fn flat_point_euclidean_roundtrip() {
let p = FlatPoint::from_euclidean(1.0_f64, 2.0, 3.0);
let (x, y, z) = p.to_euclidean().unwrap();
assert!(relative_eq!(x, 1.0, epsilon = RELATIVE_EQ_EPS));
assert!(relative_eq!(y, 2.0, epsilon = RELATIVE_EQ_EPS));
assert!(relative_eq!(z, 3.0, epsilon = RELATIVE_EQ_EPS));
}
#[test]
fn debug_sphere_values() {
fn analyze_sphere(name: &str, cx: f64, cy: f64, cz: f64, r: f64) {
let sphere = Sphere::from_center_radius(cx, cy, cz, r);
println!("\n{} at ({},{},{}) r={}:", name, cx, cy, cz, r);
println!(
" u={:.4}, x={:.4}, y={:.4}, z={:.4}, w={:.4}",
sphere.u(),
sphere.x(),
sphere.y(),
sphere.z(),
sphere.w()
);
let denom = sphere.x() - sphere.u();
println!(" denom (x-u) = {:.4}", denom);
if denom.abs() > 1e-10 {
let ex_cx = sphere.w() / denom;
let ex_cy = -sphere.z() / denom;
let ex_cz = sphere.y() / denom;
println!(
" extracted center: ({:.4}, {:.4}, {:.4})",
ex_cx, ex_cy, ex_cz
);
let c_sq = ex_cx * ex_cx + ex_cy * ex_cy + ex_cz * ex_cz;
let r_sq_term = (sphere.u() + sphere.x()) / denom;
println!(" c^2 = {:.4}, (u+x)/denom = {:.4}", c_sq, r_sq_term);
println!(
" r^2 via formula = {:.4}, expected = {:.4}",
r_sq_term + c_sq,
r * r
);
}
}
analyze_sphere("S1", 0.0, 0.0, 0.0, 1.0);
analyze_sphere("S2", 0.0, 0.0, 0.0, 2.0);
analyze_sphere("S3", 1.0, 0.0, 0.0, 1.0);
analyze_sphere("S4", 1.0, 2.0, 3.0, 2.0);
analyze_sphere("S5", 0.0, 0.0, 1.0, 1.0);
analyze_sphere("S6", 5.0, 0.0, 0.0, 3.0);
println!("\n--- CIRCLES ---");
struct CircleParams {
cx: f64,
cy: f64,
cz: f64,
r: f64,
nx: f64,
ny: f64,
nz: f64,
}
fn analyze_circle(name: &str, p: CircleParams) {
let CircleParams {
cx,
cy,
cz,
r,
nx,
ny,
nz,
} = p;
let (px, py, pz) = if nx.abs() < 0.9 {
let len = (ny * ny + nz * nz).sqrt();
(0.0, -nz / len, ny / len)
} else {
let len = (nx * nx + nz * nz).sqrt();
(nz / len, 0.0, -nx / len)
};
let qx = ny * pz - nz * py;
let qy = nz * px - nx * pz;
let qz = nx * py - ny * px;
let p1 = RoundPoint::from_euclidean(cx + r * px, cy + r * py, cz + r * pz);
let p2 = RoundPoint::from_euclidean(cx + r * qx, cy + r * qy, cz + r * qz);
let p3 = RoundPoint::from_euclidean(cx - r * px, cy - r * py, cz - r * pz);
use crate::ops::Wedge;
let circle: Circle<f64> = p1.wedge(&p2).wedge(&p3);
println!(
"\n{}: center=({},{},{}), r={}, normal=({},{},{})",
name, cx, cy, cz, r, nx, ny, nz
);
println!(
" gw={:.4}, gz={:.4}, gy={:.4}, gx={:.4}",
circle.gw(),
circle.gz(),
circle.gy(),
circle.gx()
);
println!(
" mz={:.4}, my={:.4}, mx={:.4}",
circle.mz(),
circle.my(),
circle.mx()
);
println!(
" vx={:.4}, vy={:.4}, vz={:.4}",
circle.vx(),
circle.vy(),
circle.vz()
);
let gw = circle.gw();
let mz = circle.mz();
let denom = gw - mz;
println!(" gw={:.4}, mz={:.4}, denom(gw-mz)={:.4}", gw, mz, denom);
if denom.abs() > 1e-10 {
let ex_cx = circle.my() / denom;
let ex_cy = -circle.mx() / denom;
let ex_cz = if gw.abs() > 1e-10 {
circle.gz() / gw
} else {
0.0
};
println!(
" formula A center: ({:.4}, {:.4}, {:.4}) expected ({},{},{})",
ex_cx, ex_cy, ex_cz, cx, cy, cz
);
let vx = circle.vx();
let vy = circle.vy();
let vz = circle.vz();
let v_sq = vx * vx + vy * vy + vz * vz;
let c_sq = ex_cx * ex_cx + ex_cy * ex_cy + ex_cz * ex_cz;
let r_sq_term = (gw + mz) / denom;
println!(
" v_sq={:.4}, c_sq={:.4}, (gw+mz)/denom={:.4}, expected r²={:.4}",
v_sq,
c_sq,
r_sq_term,
r * r
);
} else if gw.abs() > 1e-10 {
let ex_cz = circle.gz() / gw;
println!(" denom~0 but gw ok: cz = {:.4}", ex_cz);
} else {
println!(" gw ~ 0 and denom ~ 0, special case");
}
}
analyze_circle(
"C1",
CircleParams {
cx: 0.0,
cy: 0.0,
cz: 0.0,
r: 1.0,
nx: 0.0,
ny: 0.0,
nz: 1.0,
},
);
analyze_circle(
"C2",
CircleParams {
cx: 1.0,
cy: 2.0,
cz: 0.0,
r: 1.0,
nx: 0.0,
ny: 0.0,
nz: 1.0,
},
);
analyze_circle(
"C3",
CircleParams {
cx: 0.0,
cy: 0.0,
cz: 0.0,
r: 1.0,
nx: 0.0,
ny: 1.0,
nz: 0.0,
},
);
analyze_circle(
"C4",
CircleParams {
cx: 1.0,
cy: 1.0,
cz: 1.0,
r: 2.0,
nx: 0.0,
ny: 0.0,
nz: 1.0,
},
);
}
proptest! {
#[test]
fn sphere_center_extraction_roundtrip(
cx in -10.0..10.0_f64,
cy in -10.0..10.0_f64,
cz in -10.0..10.0_f64,
r in 0.5..10.0_f64
) {
let sphere = Sphere::from_center_radius(cx, cy, cz, r);
let (ex_cx, ex_cy, ex_cz) = sphere.center()
.expect("Center extraction should succeed for from_center_radius spheres");
prop_assert!(
relative_eq!(ex_cx, cx, epsilon = 1e-8, max_relative = 1e-8),
"cx mismatch: extracted={}, expected={}", ex_cx, cx
);
prop_assert!(
relative_eq!(ex_cy, cy, epsilon = 1e-8, max_relative = 1e-8),
"cy mismatch: extracted={}, expected={}", ex_cy, cy
);
prop_assert!(
relative_eq!(ex_cz, cz, epsilon = 1e-8, max_relative = 1e-8),
"cz mismatch: extracted={}, expected={}", ex_cz, cz
);
}
#[test]
fn sphere_radius_extraction_roundtrip(
cx in -10.0..10.0_f64,
cy in -10.0..10.0_f64,
cz in -10.0..10.0_f64,
r in 0.5..10.0_f64
) {
let sphere = Sphere::from_center_radius(cx, cy, cz, r);
let ex_r = sphere.radius()
.expect("Radius extraction should succeed for from_center_radius spheres");
prop_assert!(
relative_eq!(ex_r, r, epsilon = 1e-8, max_relative = 1e-8),
"radius mismatch: extracted={}, expected={}", ex_r, r
);
}
#[test]
fn sphere_from_four_points_center_extraction(
cx in -5.0..5.0_f64,
cy in -5.0..5.0_f64,
cz in -5.0..5.0_f64,
r in 0.5..5.0_f64
) {
use crate::ops::Wedge;
let p1 = RoundPoint::from_euclidean(cx + r, cy, cz);
let p2 = RoundPoint::from_euclidean(cx, cy + r, cz);
let p3 = RoundPoint::from_euclidean(cx, cy, cz + r);
let p4 = RoundPoint::from_euclidean(cx - r, cy, cz);
let sphere: Sphere<f64> = p1.wedge(&p2).wedge(&p3).wedge(&p4);
let (ex_cx, ex_cy, ex_cz) = sphere.center()
.expect("Center extraction should succeed for sphere from 4 points");
prop_assert!(
relative_eq!(ex_cx, cx, epsilon = 1e-6, max_relative = 1e-6),
"cx mismatch: extracted={}, expected={}", ex_cx, cx
);
prop_assert!(
relative_eq!(ex_cy, cy, epsilon = 1e-6, max_relative = 1e-6),
"cy mismatch: extracted={}, expected={}", ex_cy, cy
);
prop_assert!(
relative_eq!(ex_cz, cz, epsilon = 1e-6, max_relative = 1e-6),
"cz mismatch: extracted={}, expected={}", ex_cz, cz
);
let ex_r = sphere.radius()
.expect("Radius extraction should succeed for sphere from 4 points");
prop_assert!(
relative_eq!(ex_r, r, epsilon = 1e-6, max_relative = 1e-6),
"radius mismatch: extracted={}, expected={}", ex_r, r
);
}
}
#[test]
fn circle_extraction_known_working_case() {
use crate::ops::Wedge;
let cx = 1.0_f64;
let cy = 1.0;
let cz = 1.0;
let r = 2.0;
let p1 = RoundPoint::from_euclidean(cx + r, cy, cz);
let p2 = RoundPoint::from_euclidean(cx, cy + r, cz);
let p3 = RoundPoint::from_euclidean(cx - r, cy, cz);
let circle: Circle<f64> = p1.wedge(&p2).wedge(&p3);
assert!(
circle.gw().abs() > 1e-10,
"gw should be non-zero for this configuration"
);
assert!(
(circle.gw() - circle.mz()).abs() > 1e-10,
"gw != mz should hold for this configuration"
);
let (ex_cx, ex_cy, ex_cz) = circle.center().expect("Center extraction should work");
assert!(
relative_eq!(ex_cx, cx, epsilon = 1e-6, max_relative = 1e-6),
"cx mismatch: extracted={}, expected={}",
ex_cx,
cx
);
assert!(
relative_eq!(ex_cy, cy, epsilon = 1e-6, max_relative = 1e-6),
"cy mismatch: extracted={}, expected={}",
ex_cy,
cy
);
assert!(
relative_eq!(ex_cz, cz, epsilon = 1e-6, max_relative = 1e-6),
"cz mismatch: extracted={}, expected={}",
ex_cz,
cz
);
let ex_r = circle.radius().expect("Radius extraction should work");
assert!(
relative_eq!(ex_r, r, epsilon = 1e-6, max_relative = 1e-6),
"radius mismatch: extracted={}, expected={}",
ex_r,
r
);
let _ = circle.normal(); }
#[test]
fn circle_degenerate_cases_return_none() {
use crate::ops::Wedge;
let p1 = RoundPoint::from_euclidean(1.0_f64, 0.0, 0.0);
let p2 = RoundPoint::from_euclidean(0.0, 1.0, 0.0);
let p3 = RoundPoint::from_euclidean(-1.0, 0.0, 0.0);
let circle: Circle<f64> = p1.wedge(&p2).wedge(&p3);
assert!(
circle.center().is_none() || circle.normal().is_none(),
"Degenerate circle should return None for center or normal extraction"
);
}
}