Skip to main content

proof_engine/math/
geometry.rs

1//! Computational geometry: primitives, intersection, distance, convex hull,
2//! triangulation, polygon operations, GJK/EPA collision detection.
3
4use std::f64;
5
6// ============================================================
7// PRIMITIVE TYPES
8// ============================================================
9
10/// 2D point / vector.
11#[derive(Clone, Copy, Debug, PartialEq)]
12pub struct Point2 {
13    pub x: f64,
14    pub y: f64,
15}
16
17impl Point2 {
18    pub fn new(x: f64, y: f64) -> Self { Point2 { x, y } }
19    pub fn zero() -> Self { Point2 { x: 0.0, y: 0.0 } }
20    pub fn dot(self, other: Self) -> f64 { self.x * other.x + self.y * other.y }
21    pub fn cross(self, other: Self) -> f64 { self.x * other.y - self.y * other.x }
22    pub fn len_sq(self) -> f64 { self.x * self.x + self.y * self.y }
23    pub fn len(self) -> f64 { self.len_sq().sqrt() }
24    pub fn normalize(self) -> Self {
25        let l = self.len();
26        if l < 1e-300 { return Self::zero(); }
27        Point2 { x: self.x / l, y: self.y / l }
28    }
29    pub fn dist(self, other: Self) -> f64 { (self - other).len() }
30    pub fn perp(self) -> Self { Point2 { x: -self.y, y: self.x } }
31}
32
33impl std::ops::Add for Point2 {
34    type Output = Self;
35    fn add(self, o: Self) -> Self { Point2 { x: self.x + o.x, y: self.y + o.y } }
36}
37impl std::ops::Sub for Point2 {
38    type Output = Self;
39    fn sub(self, o: Self) -> Self { Point2 { x: self.x - o.x, y: self.y - o.y } }
40}
41impl std::ops::Mul<f64> for Point2 {
42    type Output = Self;
43    fn mul(self, s: f64) -> Self { Point2 { x: self.x * s, y: self.y * s } }
44}
45impl std::ops::Neg for Point2 {
46    type Output = Self;
47    fn neg(self) -> Self { Point2 { x: -self.x, y: -self.y } }
48}
49
50/// 3D point / vector.
51#[derive(Clone, Copy, Debug, PartialEq)]
52pub struct Point3 {
53    pub x: f64,
54    pub y: f64,
55    pub z: f64,
56}
57
58impl Point3 {
59    pub fn new(x: f64, y: f64, z: f64) -> Self { Point3 { x, y, z } }
60    pub fn zero() -> Self { Point3 { x: 0.0, y: 0.0, z: 0.0 } }
61    pub fn dot(self, other: Self) -> f64 { self.x * other.x + self.y * other.y + self.z * other.z }
62    pub fn cross(self, other: Self) -> Self {
63        Point3 {
64            x: self.y * other.z - self.z * other.y,
65            y: self.z * other.x - self.x * other.z,
66            z: self.x * other.y - self.y * other.x,
67        }
68    }
69    pub fn len_sq(self) -> f64 { self.x * self.x + self.y * self.y + self.z * self.z }
70    pub fn len(self) -> f64 { self.len_sq().sqrt() }
71    pub fn normalize(self) -> Self {
72        let l = self.len();
73        if l < 1e-300 { return Self::zero(); }
74        Point3 { x: self.x / l, y: self.y / l, z: self.z / l }
75    }
76    pub fn dist(self, other: Self) -> f64 { (self - other).len() }
77    pub fn lerp(self, other: Self, t: f64) -> Self {
78        self + (other - self) * t
79    }
80    pub fn abs(self) -> Self { Point3 { x: self.x.abs(), y: self.y.abs(), z: self.z.abs() } }
81    pub fn component_min(self, other: Self) -> Self {
82        Point3 { x: self.x.min(other.x), y: self.y.min(other.y), z: self.z.min(other.z) }
83    }
84    pub fn component_max(self, other: Self) -> Self {
85        Point3 { x: self.x.max(other.x), y: self.y.max(other.y), z: self.z.max(other.z) }
86    }
87}
88
89impl std::ops::Add for Point3 {
90    type Output = Self;
91    fn add(self, o: Self) -> Self { Point3 { x: self.x + o.x, y: self.y + o.y, z: self.z + o.z } }
92}
93impl std::ops::Sub for Point3 {
94    type Output = Self;
95    fn sub(self, o: Self) -> Self { Point3 { x: self.x - o.x, y: self.y - o.y, z: self.z - o.z } }
96}
97impl std::ops::Mul<f64> for Point3 {
98    type Output = Self;
99    fn mul(self, s: f64) -> Self { Point3 { x: self.x * s, y: self.y * s, z: self.z * s } }
100}
101impl std::ops::Div<f64> for Point3 {
102    type Output = Self;
103    fn div(self, s: f64) -> Self { Point3 { x: self.x / s, y: self.y / s, z: self.z / s } }
104}
105impl std::ops::Neg for Point3 {
106    type Output = Self;
107    fn neg(self) -> Self { Point3 { x: -self.x, y: -self.y, z: -self.z } }
108}
109impl std::ops::AddAssign for Point3 {
110    fn add_assign(&mut self, o: Self) { self.x += o.x; self.y += o.y; self.z += o.z; }
111}
112
113/// 2D line segment.
114#[derive(Clone, Copy, Debug)]
115pub struct Segment2 {
116    pub a: Point2,
117    pub b: Point2,
118}
119
120/// 2D triangle.
121#[derive(Clone, Copy, Debug)]
122pub struct Triangle2 {
123    pub a: Point2,
124    pub b: Point2,
125    pub c: Point2,
126}
127
128/// 3D triangle.
129#[derive(Clone, Copy, Debug)]
130pub struct Triangle3 {
131    pub a: Point3,
132    pub b: Point3,
133    pub c: Point3,
134}
135
136/// 2D axis-aligned bounding box.
137#[derive(Clone, Copy, Debug)]
138pub struct Aabb2 {
139    pub min: Point2,
140    pub max: Point2,
141}
142
143impl Aabb2 {
144    pub fn new(min: Point2, max: Point2) -> Self { Aabb2 { min, max } }
145    pub fn contains(&self, p: Point2) -> bool {
146        p.x >= self.min.x && p.x <= self.max.x && p.y >= self.min.y && p.y <= self.max.y
147    }
148    pub fn center(&self) -> Point2 {
149        Point2 { x: (self.min.x + self.max.x) * 0.5, y: (self.min.y + self.max.y) * 0.5 }
150    }
151}
152
153/// 3D axis-aligned bounding box.
154#[derive(Clone, Copy, Debug)]
155pub struct Aabb3 {
156    pub min: Point3,
157    pub max: Point3,
158}
159
160impl Aabb3 {
161    pub fn new(min: Point3, max: Point3) -> Self { Aabb3 { min, max } }
162    pub fn center(&self) -> Point3 { (self.min + self.max) * 0.5 }
163    pub fn half_extents(&self) -> Point3 { (self.max - self.min) * 0.5 }
164    pub fn expand(&self, amount: f64) -> Self {
165        let a = Point3::new(amount, amount, amount);
166        Aabb3 { min: self.min - a, max: self.max + a }
167    }
168}
169
170/// Circle in 2D.
171#[derive(Clone, Copy, Debug)]
172pub struct Circle {
173    pub center: Point2,
174    pub radius: f64,
175}
176
177/// Sphere in 3D.
178#[derive(Clone, Copy, Debug)]
179pub struct Sphere {
180    pub center: Point3,
181    pub radius: f64,
182}
183
184/// Plane: normal·x + d = 0. Normal should be unit length.
185#[derive(Clone, Copy, Debug)]
186pub struct Plane {
187    pub normal: Point3,
188    pub d: f64,
189}
190
191impl Plane {
192    pub fn from_point_normal(p: Point3, n: Point3) -> Self {
193        let n = n.normalize();
194        Plane { normal: n, d: -n.dot(p) }
195    }
196    pub fn from_triangle(a: Point3, b: Point3, c: Point3) -> Self {
197        let n = (b - a).cross(c - a).normalize();
198        Self::from_point_normal(a, n)
199    }
200    pub fn signed_dist(&self, p: Point3) -> f64 {
201        self.normal.dot(p) + self.d
202    }
203}
204
205/// 2D ray.
206#[derive(Clone, Copy, Debug)]
207pub struct Ray2 {
208    pub origin: Point2,
209    pub dir: Point2,
210}
211
212/// 3D ray.
213#[derive(Clone, Copy, Debug)]
214pub struct Ray3 {
215    pub origin: Point3,
216    pub dir: Point3,
217}
218
219impl Ray3 {
220    pub fn at(&self, t: f64) -> Point3 { self.origin + self.dir * t }
221}
222
223/// Capsule: cylinder with hemispherical caps.
224#[derive(Clone, Copy, Debug)]
225pub struct Capsule {
226    pub a: Point3,
227    pub b: Point3,
228    pub radius: f64,
229}
230
231/// Oriented bounding box.
232#[derive(Clone, Copy, Debug)]
233pub struct Obb3 {
234    pub center: Point3,
235    pub axes: [Point3; 3],       // orthonormal local axes
236    pub half_extents: Point3,    // half-widths along each axis
237}
238
239/// Result of a halfspace test for AABB vs plane.
240#[derive(Clone, Copy, Debug, PartialEq)]
241pub enum Halfspace {
242    Front,
243    Back,
244    Intersect,
245}
246
247// ============================================================
248// INTERSECTION TESTS
249// ============================================================
250
251/// Ray vs AABB — returns t of first positive intersection or None.
252pub fn ray_aabb(ray: &Ray3, aabb: &Aabb3) -> Option<f64> {
253    let inv_dir = Point3::new(1.0 / ray.dir.x, 1.0 / ray.dir.y, 1.0 / ray.dir.z);
254    let t1 = (aabb.min.x - ray.origin.x) * inv_dir.x;
255    let t2 = (aabb.max.x - ray.origin.x) * inv_dir.x;
256    let t3 = (aabb.min.y - ray.origin.y) * inv_dir.y;
257    let t4 = (aabb.max.y - ray.origin.y) * inv_dir.y;
258    let t5 = (aabb.min.z - ray.origin.z) * inv_dir.z;
259    let t6 = (aabb.max.z - ray.origin.z) * inv_dir.z;
260    let tmin = t1.min(t2).max(t3.min(t4)).max(t5.min(t6));
261    let tmax = t1.max(t2).min(t3.max(t4)).min(t5.max(t6));
262    if tmax < 0.0 || tmin > tmax { None } else { Some(tmin.max(0.0)) }
263}
264
265/// Ray vs sphere.
266pub fn ray_sphere(ray: &Ray3, sphere: &Sphere) -> Option<f64> {
267    let oc = ray.origin - sphere.center;
268    let a = ray.dir.dot(ray.dir);
269    let b = 2.0 * oc.dot(ray.dir);
270    let c = oc.dot(oc) - sphere.radius * sphere.radius;
271    let disc = b * b - 4.0 * a * c;
272    if disc < 0.0 { return None; }
273    let sqrt_d = disc.sqrt();
274    let t1 = (-b - sqrt_d) / (2.0 * a);
275    let t2 = (-b + sqrt_d) / (2.0 * a);
276    if t1 >= 0.0 { Some(t1) } else if t2 >= 0.0 { Some(t2) } else { None }
277}
278
279/// Ray vs triangle using Möller–Trumbore algorithm.
280/// Returns (t, u, v) — barycentric coords (w = 1 - u - v).
281pub fn ray_triangle(ray: &Ray3, tri: &Triangle3) -> Option<(f64, f64, f64)> {
282    const EPS: f64 = 1e-10;
283    let edge1 = tri.b - tri.a;
284    let edge2 = tri.c - tri.a;
285    let h = ray.dir.cross(edge2);
286    let det = edge1.dot(h);
287    if det.abs() < EPS { return None; }
288    let inv_det = 1.0 / det;
289    let s = ray.origin - tri.a;
290    let u = inv_det * s.dot(h);
291    if !(0.0..=1.0).contains(&u) { return None; }
292    let q = s.cross(edge1);
293    let v = inv_det * ray.dir.dot(q);
294    if v < 0.0 || u + v > 1.0 { return None; }
295    let t = inv_det * edge2.dot(q);
296    if t < EPS { return None; }
297    Some((t, u, v))
298}
299
300/// Ray vs plane.
301pub fn ray_plane(ray: &Ray3, plane: &Plane) -> Option<f64> {
302    let denom = plane.normal.dot(ray.dir);
303    if denom.abs() < 1e-12 { return None; }
304    let t = -(plane.normal.dot(ray.origin) + plane.d) / denom;
305    if t < 0.0 { None } else { Some(t) }
306}
307
308/// Ray vs capsule.
309pub fn ray_capsule(ray: &Ray3, cap: &Capsule) -> Option<f64> {
310    let ab = cap.b - cap.a;
311    let ao = ray.origin - cap.a;
312    let ab_len_sq = ab.dot(ab);
313    let ab_dot_d = ab.dot(ray.dir);
314    let ab_dot_ao = ab.dot(ao);
315    let a = ab_len_sq * ray.dir.dot(ray.dir) - ab_dot_d * ab_dot_d;
316    let bv = ab_len_sq * ray.dir.dot(ao) - ab_dot_d * ab_dot_ao;
317    let c = ab_len_sq * (ao.dot(ao) - cap.radius * cap.radius) - ab_dot_ao * ab_dot_ao;
318    let mut t_hit = f64::MAX;
319    if a.abs() > 1e-10 {
320        let disc = bv * bv - a * c;
321        if disc >= 0.0 {
322            let t = (-bv - disc.sqrt()) / a;
323            let m = ab_dot_d * t + ab_dot_ao;
324            if m >= 0.0 && m <= ab_len_sq {
325                t_hit = t_hit.min(t);
326            }
327        }
328    }
329    // Check sphere caps
330    for cap_center in [cap.a, cap.b] {
331        let sphere = Sphere { center: cap_center, radius: cap.radius };
332        if let Some(t) = ray_sphere(ray, &sphere) {
333            t_hit = t_hit.min(t);
334        }
335    }
336    if t_hit < f64::MAX && t_hit >= 0.0 { Some(t_hit) } else { None }
337}
338
339/// Ray vs oriented bounding box.
340pub fn ray_obb(ray: &Ray3, obb: &Obb3) -> Option<f64> {
341    // Transform ray into OBB local space
342    let d = ray.origin - obb.center;
343    let mut t_min = f64::NEG_INFINITY;
344    let mut t_max = f64::INFINITY;
345    let half = [obb.half_extents.x, obb.half_extents.y, obb.half_extents.z];
346    for (i, axis) in obb.axes.iter().enumerate() {
347        let e = axis.dot(d);
348        let f = axis.dot(ray.dir);
349        if f.abs() > 1e-12 {
350            let t1 = (-e - half[i]) / f;
351            let t2 = (-e + half[i]) / f;
352            let (t1, t2) = if t1 > t2 { (t2, t1) } else { (t1, t2) };
353            t_min = t_min.max(t1);
354            t_max = t_max.min(t2);
355            if t_min > t_max { return None; }
356        } else if e.abs() > half[i] {
357            return None;
358        }
359    }
360    if t_max < 0.0 { return None; }
361    let t = if t_min >= 0.0 { t_min } else { t_max };
362    Some(t)
363}
364
365/// AABB vs AABB overlap test.
366pub fn aabb_aabb(a: &Aabb3, b: &Aabb3) -> bool {
367    a.min.x <= b.max.x && a.max.x >= b.min.x &&
368    a.min.y <= b.max.y && a.max.y >= b.min.y &&
369    a.min.z <= b.max.z && a.max.z >= b.min.z
370}
371
372/// Sphere vs sphere overlap.
373pub fn sphere_sphere(a: &Sphere, b: &Sphere) -> bool {
374    let d = a.center.dist(b.center);
375    d <= a.radius + b.radius
376}
377
378/// Sphere vs AABB overlap.
379pub fn sphere_aabb(s: &Sphere, b: &Aabb3) -> bool {
380    let closest = Point3::new(
381        s.center.x.clamp(b.min.x, b.max.x),
382        s.center.y.clamp(b.min.y, b.max.y),
383        s.center.z.clamp(b.min.z, b.max.z),
384    );
385    s.center.dist(closest) <= s.radius
386}
387
388/// AABB vs plane halfspace classification.
389pub fn aabb_plane(b: &Aabb3, p: &Plane) -> Halfspace {
390    let center = b.center();
391    let half = b.half_extents();
392    let r = half.x * p.normal.x.abs()
393           + half.y * p.normal.y.abs()
394           + half.z * p.normal.z.abs();
395    let dist = p.normal.dot(center) + p.d;
396    if dist > r { Halfspace::Front }
397    else if dist < -r { Halfspace::Back }
398    else { Halfspace::Intersect }
399}
400
401/// OBB vs OBB — Separating Axis Theorem.
402pub fn obb_obb(a: &Obb3, b: &Obb3) -> bool {
403    let t = b.center - a.center;
404    let half_a = [a.half_extents.x, a.half_extents.y, a.half_extents.z];
405    let half_b = [b.half_extents.x, b.half_extents.y, b.half_extents.z];
406
407    // Test 15 axes: 3 from A, 3 from B, 9 cross products
408    let axes_a = a.axes;
409    let axes_b = b.axes;
410
411    // Helper: project OBB half-extent onto axis
412    let project_half = |axes: &[Point3; 3], halves: &[f64; 3], axis: Point3| -> f64 {
413        halves[0] * axes[0].dot(axis).abs()
414        + halves[1] * axes[1].dot(axis).abs()
415        + halves[2] * axes[2].dot(axis).abs()
416    };
417
418    let test_axis = |axis: Point3| -> bool {
419        let len = axis.len_sq();
420        if len < 1e-10 { return true; } // degenerate
421        let axis = axis * (1.0 / len.sqrt());
422        let dist = t.dot(axis).abs();
423        let ra = project_half(&axes_a, &half_a, axis);
424        let rb = project_half(&axes_b, &half_b, axis);
425        dist <= ra + rb
426    };
427
428    // A's axes
429    for i in 0..3 {
430        if !test_axis(axes_a[i]) { return false; }
431    }
432    // B's axes
433    for i in 0..3 {
434        if !test_axis(axes_b[i]) { return false; }
435    }
436    // Cross products
437    for i in 0..3 {
438        for j in 0..3 {
439            let axis = axes_a[i].cross(axes_b[j]);
440            if !test_axis(axis) { return false; }
441        }
442    }
443    true
444}
445
446/// Capsule vs capsule overlap.
447pub fn capsule_capsule(a: &Capsule, b: &Capsule) -> bool {
448    let dist = segment_segment_dist_3d(a.a, a.b, b.a, b.b);
449    dist <= a.radius + b.radius
450}
451
452/// Point in 2D triangle test.
453pub fn point_in_triangle(p: Point2, tri: &Triangle2) -> bool {
454    let d1 = (p - tri.a).cross(tri.b - tri.a);
455    let d2 = (p - tri.b).cross(tri.c - tri.b);
456    let d3 = (p - tri.c).cross(tri.a - tri.c);
457    let has_neg = d1 < 0.0 || d2 < 0.0 || d3 < 0.0;
458    let has_pos = d1 > 0.0 || d2 > 0.0 || d3 > 0.0;
459    !(has_neg && has_pos)
460}
461
462/// Point inside AABB.
463pub fn point_in_aabb(p: Point3, b: &Aabb3) -> bool {
464    p.x >= b.min.x && p.x <= b.max.x &&
465    p.y >= b.min.y && p.y <= b.max.y &&
466    p.z >= b.min.z && p.z <= b.max.z
467}
468
469/// Point inside OBB.
470pub fn point_in_obb(p: Point3, obb: &Obb3) -> bool {
471    let d = p - obb.center;
472    let hx = d.dot(obb.axes[0]).abs();
473    let hy = d.dot(obb.axes[1]).abs();
474    let hz = d.dot(obb.axes[2]).abs();
475    hx <= obb.half_extents.x && hy <= obb.half_extents.y && hz <= obb.half_extents.z
476}
477
478/// 2D segment-segment intersection. Returns intersection point or None.
479pub fn segment_segment_2d(a: &Segment2, b: &Segment2) -> Option<Point2> {
480    let r = a.b - a.a;
481    let s = b.b - b.a;
482    let denom = r.cross(s);
483    if denom.abs() < 1e-12 { return None; }
484    let diff = b.a - a.a;
485    let t = diff.cross(s) / denom;
486    let u = diff.cross(r) / denom;
487    if (0.0..=1.0).contains(&t) && (0.0..=1.0).contains(&u) {
488        Some(a.a + r * t)
489    } else {
490        None
491    }
492}
493
494// ============================================================
495// DISTANCE QUERIES
496// ============================================================
497
498fn clamp01(x: f64) -> f64 { x.clamp(0.0, 1.0) }
499
500/// Closest point on a 3D segment to point p (treating segment as 3D using z=0 for Point2).
501pub fn point_to_segment(p: Point3, seg_a: Point3, seg_b: Point3) -> f64 {
502    let ab = seg_b - seg_a;
503    let t = clamp01((p - seg_a).dot(ab) / ab.dot(ab).max(1e-300));
504    (seg_a + ab * t).dist(p)
505}
506
507/// 3D segment-to-segment minimum distance.
508fn segment_segment_dist_3d(a0: Point3, a1: Point3, b0: Point3, b1: Point3) -> f64 {
509    let d1 = a1 - a0;
510    let d2 = b1 - b0;
511    let r = a0 - b0;
512    let a = d1.dot(d1);
513    let e = d2.dot(d2);
514    let f = d2.dot(r);
515    let (s, t) = if a < 1e-10 {
516        (0.0, clamp01(f / e.max(1e-10)))
517    } else {
518        let c = d1.dot(r);
519        if e < 1e-10 {
520            (clamp01(-c / a), 0.0)
521        } else {
522            let b = d1.dot(d2);
523            let denom = a * e - b * b;
524            let s = if denom.abs() > 1e-10 { clamp01((b * f - c * e) / denom) } else { 0.0 };
525            let t = (b * s + f) / e;
526            if t < 0.0 {
527                (clamp01(-c / a), 0.0)
528            } else if t > 1.0 {
529                (clamp01((b - c) / a), 1.0)
530            } else {
531                (s, t)
532            }
533        }
534    };
535    let p1 = a0 + d1 * s;
536    let p2 = b0 + d2 * t;
537    p1.dist(p2)
538}
539
540/// 2D segment-to-segment distance (embedding in 3D).
541pub fn segment_to_segment(a: &Segment2, b: &Segment2) -> f64 {
542    let a0 = Point3::new(a.a.x, a.a.y, 0.0);
543    let a1 = Point3::new(a.b.x, a.b.y, 0.0);
544    let b0 = Point3::new(b.a.x, b.a.y, 0.0);
545    let b1 = Point3::new(b.b.x, b.b.y, 0.0);
546    segment_segment_dist_3d(a0, a1, b0, b1)
547}
548
549/// Signed distance from point to plane.
550pub fn point_to_plane(p: Point3, plane: &Plane) -> f64 {
551    plane.normal.dot(p) + plane.d
552}
553
554/// Distance from point to AABB (0 if inside).
555pub fn point_to_aabb(p: Point3, b: &Aabb3) -> f64 {
556    let dx = (b.min.x - p.x).max(0.0).max(p.x - b.max.x);
557    let dy = (b.min.y - p.y).max(0.0).max(p.y - b.max.y);
558    let dz = (b.min.z - p.z).max(0.0).max(p.z - b.max.z);
559    (dx * dx + dy * dy + dz * dz).sqrt()
560}
561
562/// Distance from point to triangle in 3D.
563pub fn point_to_triangle(p: Point3, tri: &Triangle3) -> f64 {
564    let ab = tri.b - tri.a;
565    let ac = tri.c - tri.a;
566    let ap = p - tri.a;
567    let d1 = ab.dot(ap);
568    let d2 = ac.dot(ap);
569    if d1 <= 0.0 && d2 <= 0.0 { return p.dist(tri.a); }
570    let bp = p - tri.b;
571    let d3 = ab.dot(bp);
572    let d4 = ac.dot(bp);
573    if d3 >= 0.0 && d4 <= d3 { return p.dist(tri.b); }
574    let vc = d1 * d4 - d3 * d2;
575    if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 {
576        let v = d1 / (d1 - d3);
577        return p.dist(tri.a + ab * v);
578    }
579    let cp = p - tri.c;
580    let d5 = ab.dot(cp);
581    let d6 = ac.dot(cp);
582    if d6 >= 0.0 && d5 <= d6 { return p.dist(tri.c); }
583    let vb = d5 * d2 - d1 * d6;
584    if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 {
585        let w = d2 / (d2 - d6);
586        return p.dist(tri.a + ac * w);
587    }
588    let va = d3 * d6 - d5 * d4;
589    if va <= 0.0 && d4 - d3 >= 0.0 && d5 - d6 >= 0.0 {
590        let w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
591        return p.dist(tri.b + (tri.c - tri.b) * w);
592    }
593    let denom = 1.0 / (va + vb + vc);
594    let v = vb * denom;
595    let w = vc * denom;
596    let closest = tri.a + ab * v + ac * w;
597    p.dist(closest)
598}
599
600/// Distance from sphere to AABB surface. Negative means overlap.
601pub fn sphere_to_aabb(s: &Sphere, b: &Aabb3) -> f64 {
602    point_to_aabb(s.center, b) - s.radius
603}
604
605// ============================================================
606// CONVEX HULL
607// ============================================================
608
609/// 2D convex hull via Jarvis march (gift wrapping).
610pub fn convex_hull_2d(points: &[Point2]) -> Vec<Point2> {
611    let n = points.len();
612    if n < 3 { return points.to_vec(); }
613    // Find leftmost point
614    let start = points
615        .iter()
616        .enumerate()
617        .min_by(|(_, a), (_, b)| a.x.partial_cmp(&b.x).unwrap().then(a.y.partial_cmp(&b.y).unwrap()))
618        .unwrap()
619        .0;
620    let mut hull = Vec::new();
621    let mut current = start;
622    loop {
623        hull.push(points[current]);
624        let mut next = (current + 1) % n;
625        for i in 0..n {
626            let cross = (points[next] - points[current]).cross(points[i] - points[current]);
627            if cross < 0.0 { next = i; }
628        }
629        current = next;
630        if current == start { break; }
631        if hull.len() > n { break; } // safety
632    }
633    hull
634}
635
636/// 3D convex hull via incremental Quickhull. Returns triangle face indices.
637pub fn convex_hull_3d(points: &[Point3]) -> Vec<[usize; 3]> {
638    let n = points.len();
639    if n < 4 { return vec![]; }
640
641    // Find initial tetrahedron
642    let mut extreme = [0usize; 6];
643    for (i, p) in points.iter().enumerate() {
644        if p.x < points[extreme[0]].x { extreme[0] = i; }
645        if p.x > points[extreme[1]].x { extreme[1] = i; }
646        if p.y < points[extreme[2]].y { extreme[2] = i; }
647        if p.y > points[extreme[3]].y { extreme[3] = i; }
648        if p.z < points[extreme[4]].z { extreme[4] = i; }
649        if p.z > points[extreme[5]].z { extreme[5] = i; }
650    }
651
652    // Pick 4 non-coplanar points
653    let mut simplex = vec![extreme[0], extreme[1]];
654    for &e in &extreme[2..] {
655        if !simplex.contains(&e) { simplex.push(e); }
656        if simplex.len() == 4 { break; }
657    }
658    // Fill with any remaining if needed
659    for i in 0..n {
660        if simplex.len() >= 4 { break; }
661        if !simplex.contains(&i) { simplex.push(i); }
662    }
663    if simplex.len() < 4 { return vec![]; }
664
665    // Initial faces of tetrahedron
666    let (i0, i1, i2, i3) = (simplex[0], simplex[1], simplex[2], simplex[3]);
667    let mut faces: Vec<[usize; 3]> = vec![
668        [i0, i1, i2], [i0, i2, i3], [i0, i3, i1], [i1, i3, i2],
669    ];
670
671    // Make all faces outward-facing
672    let centroid = points.iter().fold(Point3::zero(), |acc, p| acc + *p) * (1.0 / n as f64);
673    for face in &mut faces {
674        let n = (points[face[1]] - points[face[0]]).cross(points[face[2]] - points[face[0]]);
675        if n.dot(centroid - points[face[0]]) > 0.0 {
676            face.swap(0, 1);
677        }
678    }
679
680    // Iteratively add remaining points
681    for p_idx in 0..n {
682        if simplex.contains(&p_idx) { continue; }
683        let p = points[p_idx];
684
685        // Find visible faces
686        let visible: Vec<bool> = faces.iter().map(|face| {
687            let normal = (points[face[1]] - points[face[0]]).cross(points[face[2]] - points[face[0]]);
688            normal.dot(p - points[face[0]]) > 1e-10
689        }).collect();
690
691        if !visible.iter().any(|&v| v) { continue; }
692
693        // Find horizon edges
694        let mut horizon: Vec<(usize, usize)> = Vec::new();
695        for (fi, face) in faces.iter().enumerate() {
696            if !visible[fi] { continue; }
697            let edges = [(face[0], face[1]), (face[1], face[2]), (face[2], face[0])];
698            for &(ea, eb) in &edges {
699                // Check if this edge is shared with an invisible face
700                let shared = faces.iter().enumerate().any(|(fj, f2)| {
701                    !visible[fj] && (
702                        (f2[0] == eb && f2[1] == ea) ||
703                        (f2[1] == eb && f2[2] == ea) ||
704                        (f2[2] == eb && f2[0] == ea)
705                    )
706                });
707                if shared { horizon.push((ea, eb)); }
708            }
709        }
710
711        // Remove visible faces and add new faces from horizon
712        let kept: Vec<[usize; 3]> = faces.iter().enumerate()
713            .filter(|(i, _)| !visible[*i])
714            .map(|(_, f)| *f)
715            .collect();
716        faces = kept;
717        for (ea, eb) in horizon {
718            let normal = (points[eb] - points[ea]).cross(points[p_idx] - points[ea]);
719            let centroid_side = normal.dot(centroid - points[ea]);
720            if centroid_side > 0.0 {
721                faces.push([ea, p_idx, eb]);
722            } else {
723                faces.push([ea, eb, p_idx]);
724            }
725        }
726    }
727    faces
728}
729
730/// Point in convex polygon (hull assumed CCW).
731pub fn point_in_convex_polygon(p: Point2, hull: &[Point2]) -> bool {
732    let n = hull.len();
733    if n < 3 { return false; }
734    for i in 0..n {
735        let a = hull[i];
736        let b = hull[(i + 1) % n];
737        if (b - a).cross(p - a) < 0.0 { return false; }
738    }
739    true
740}
741
742/// Area of a convex polygon.
743pub fn convex_polygon_area(hull: &[Point2]) -> f64 {
744    polygon_area(hull).abs()
745}
746
747// ============================================================
748// TRIANGULATION
749// ============================================================
750
751/// Ear clipping triangulation for simple (non-self-intersecting) polygons.
752/// Returns triangle indices.
753pub fn ear_clipping(polygon: &[Point2]) -> Vec<[usize; 3]> {
754    let n = polygon.len();
755    if n < 3 { return vec![]; }
756    let mut result = Vec::new();
757    let mut indices: Vec<usize> = (0..n).collect();
758
759    while indices.len() > 3 {
760        let len = indices.len();
761        let mut found_ear = false;
762        for i in 0..len {
763            let prev = (i + len - 1) % len;
764            let next = (i + 1) % len;
765            let a = polygon[indices[prev]];
766            let b = polygon[indices[i]];
767            let c = polygon[indices[next]];
768            // Check if angle at b is convex (CCW polygon)
769            let cross = (b - a).cross(c - b);
770            if cross <= 0.0 { continue; }
771            // Check no other vertex is inside triangle ABC
772            let tri = Triangle2 { a, b, c };
773            let ear_valid = !indices.iter().enumerate().any(|(j, &vi)| {
774                j != prev && j != i && j != next
775                    && point_in_triangle(polygon[vi], &tri)
776            });
777            if ear_valid {
778                result.push([indices[prev], indices[i], indices[next]]);
779                indices.remove(i);
780                found_ear = true;
781                break;
782            }
783        }
784        if !found_ear { break; }
785    }
786    if indices.len() == 3 {
787        result.push([indices[0], indices[1], indices[2]]);
788    }
789    result
790}
791
792/// Bowyer-Watson Delaunay triangulation in 2D.
793/// Returns triangle index triples.
794pub fn delaunay_2d(points: &[Point2]) -> Vec<[usize; 3]> {
795    let n = points.len();
796    if n < 3 { return vec![]; }
797
798    // Add super-triangle containing all points
799    let mut pts = points.to_vec();
800    let min_x = pts.iter().map(|p| p.x).fold(f64::INFINITY, f64::min);
801    let max_x = pts.iter().map(|p| p.x).fold(f64::NEG_INFINITY, f64::max);
802    let min_y = pts.iter().map(|p| p.y).fold(f64::INFINITY, f64::min);
803    let max_y = pts.iter().map(|p| p.y).fold(f64::NEG_INFINITY, f64::max);
804    let dx = max_x - min_x;
805    let dy = max_y - min_y;
806    let delta_max = dx.max(dy);
807    let mid_x = (min_x + max_x) * 0.5;
808    let mid_y = (min_y + max_y) * 0.5;
809
810    let st0 = Point2::new(mid_x - 20.0 * delta_max, mid_y - delta_max);
811    let st1 = Point2::new(mid_x, mid_y + 20.0 * delta_max);
812    let st2 = Point2::new(mid_x + 20.0 * delta_max, mid_y - delta_max);
813    let si0 = n;
814    let si1 = n + 1;
815    let si2 = n + 2;
816    pts.push(st0); pts.push(st1); pts.push(st2);
817
818    let mut triangles: Vec<[usize; 3]> = vec![[si0, si1, si2]];
819
820    for i in 0..n {
821        let p = pts[i];
822        let mut bad_triangles: Vec<[usize; 3]> = Vec::new();
823        let mut good_triangles: Vec<[usize; 3]> = Vec::new();
824
825        for &tri in &triangles {
826            if circumcircle_contains(&pts[tri[0]], &pts[tri[1]], &pts[tri[2]], &p) {
827                bad_triangles.push(tri);
828            } else {
829                good_triangles.push(tri);
830            }
831        }
832
833        // Find the boundary polygon of the bad triangle cavity
834        let mut boundary: Vec<(usize, usize)> = Vec::new();
835        for &tri in &bad_triangles {
836            let edges = [(tri[0], tri[1]), (tri[1], tri[2]), (tri[2], tri[0])];
837            for &(ea, eb) in &edges {
838                let shared = bad_triangles.iter().any(|&t2| {
839                    t2 != tri && (
840                        (t2[0] == eb && t2[1] == ea)
841                        || (t2[1] == eb && t2[2] == ea)
842                        || (t2[2] == eb && t2[0] == ea)
843                        || (t2[0] == ea && t2[1] == eb)
844                        || (t2[1] == ea && t2[2] == eb)
845                        || (t2[2] == ea && t2[0] == eb)
846                    )
847                });
848                if !shared { boundary.push((ea, eb)); }
849            }
850        }
851
852        triangles = good_triangles;
853        for (ea, eb) in boundary {
854            triangles.push([ea, eb, i]);
855        }
856    }
857
858    // Remove triangles sharing super-triangle vertices
859    triangles.retain(|t| t[0] < n && t[1] < n && t[2] < n);
860    triangles
861}
862
863fn circumcircle_contains(a: &Point2, b: &Point2, c: &Point2, p: &Point2) -> bool {
864    let ax = a.x - p.x; let ay = a.y - p.y;
865    let bx = b.x - p.x; let by = b.y - p.y;
866    let cx = c.x - p.x; let cy = c.y - p.y;
867    let det = ax * (by * (cx * cx + cy * cy) - cy * (bx * bx + by * by))
868            - ay * (bx * (cx * cx + cy * cy) - cx * (bx * bx + by * by))
869            + (ax * ax + ay * ay) * (bx * cy - by * cx);
870    det > 0.0
871}
872
873/// Compute approximate Voronoi diagram from Delaunay triangulation dual.
874/// Returns one polygon (as point list) per input point.
875pub fn voronoi_2d(points: &[Point2], bounds: Aabb2) -> Vec<Vec<Point2>> {
876    let tris = delaunay_2d(points);
877    let n = points.len();
878    let mut cells: Vec<Vec<Point2>> = vec![Vec::new(); n];
879
880    // For each Delaunay triangle, compute circumcenter
881    let circumcenter = |a: &Point2, b: &Point2, c: &Point2| -> Point2 {
882        let ax = a.x; let ay = a.y;
883        let bx = b.x; let by = b.y;
884        let cx = c.x; let cy = c.y;
885        let d = 2.0 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
886        if d.abs() < 1e-12 { return Point2::new((ax + bx + cx) / 3.0, (ay + by + cy) / 3.0); }
887        let ux = ((ax * ax + ay * ay) * (by - cy)
888                + (bx * bx + by * by) * (cy - ay)
889                + (cx * cx + cy * cy) * (ay - by)) / d;
890        let uy = ((ax * ax + ay * ay) * (cx - bx)
891                + (bx * bx + by * by) * (ax - cx)
892                + (cx * cx + cy * cy) * (bx - ax)) / d;
893        Point2::new(ux, uy)
894    };
895
896    // For each point, collect circumcenters of adjacent triangles
897    for tri in &tris {
898        let cc = circumcenter(&points[tri[0]], &points[tri[1]], &points[tri[2]]);
899        // Clamp to bounds
900        let cc = Point2::new(
901            cc.x.clamp(bounds.min.x, bounds.max.x),
902            cc.y.clamp(bounds.min.y, bounds.max.y),
903        );
904        for &vi in tri {
905            cells[vi].push(cc);
906        }
907    }
908
909    // Sort each cell's points angularly around the site
910    for (i, cell) in cells.iter_mut().enumerate() {
911        let center = points[i];
912        cell.sort_by(|a, b| {
913            let angle_a = (a.y - center.y).atan2(a.x - center.x);
914            let angle_b = (b.y - center.y).atan2(b.x - center.x);
915            angle_a.partial_cmp(&angle_b).unwrap()
916        });
917        cell.dedup_by(|a, b| (a.x - b.x).abs() < 1e-10 && (a.y - b.y).abs() < 1e-10);
918    }
919    cells
920}
921
922// ============================================================
923// POLYGON OPERATIONS
924// ============================================================
925
926/// Signed polygon area via Shoelace formula.
927pub fn polygon_area(pts: &[Point2]) -> f64 {
928    let n = pts.len();
929    if n < 3 { return 0.0; }
930    let mut area = 0.0;
931    for i in 0..n {
932        let j = (i + 1) % n;
933        area += pts[i].x * pts[j].y;
934        area -= pts[j].x * pts[i].y;
935    }
936    area * 0.5
937}
938
939/// Polygon centroid.
940pub fn polygon_centroid(pts: &[Point2]) -> Point2 {
941    let n = pts.len();
942    if n == 0 { return Point2::zero(); }
943    let area = polygon_area(pts);
944    if area.abs() < 1e-12 {
945        let x = pts.iter().map(|p| p.x).sum::<f64>() / n as f64;
946        let y = pts.iter().map(|p| p.y).sum::<f64>() / n as f64;
947        return Point2::new(x, y);
948    }
949    let mut cx = 0.0;
950    let mut cy = 0.0;
951    for i in 0..n {
952        let j = (i + 1) % n;
953        let cross = pts[i].x * pts[j].y - pts[j].x * pts[i].y;
954        cx += (pts[i].x + pts[j].x) * cross;
955        cy += (pts[i].y + pts[j].y) * cross;
956    }
957    let factor = 1.0 / (6.0 * area);
958    Point2::new(cx * factor, cy * factor)
959}
960
961/// Test if polygon is convex.
962pub fn polygon_is_convex(pts: &[Point2]) -> bool {
963    let n = pts.len();
964    if n < 3 { return true; }
965    let mut sign = 0.0f64;
966    for i in 0..n {
967        let a = pts[i];
968        let b = pts[(i + 1) % n];
969        let c = pts[(i + 2) % n];
970        let cross = (b - a).cross(c - b);
971        if cross != 0.0 {
972            if sign == 0.0 { sign = cross.signum(); }
973            else if cross.signum() != sign { return false; }
974        }
975    }
976    true
977}
978
979/// Winding number of polygon. Positive = CCW.
980pub fn polygon_winding(pts: &[Point2]) -> f64 {
981    polygon_area(pts).signum()
982}
983
984/// Sutherland-Hodgman polygon clipping.
985pub fn sutherland_hodgman(subject: &[Point2], clip: &[Point2]) -> Vec<Point2> {
986    if subject.is_empty() || clip.is_empty() { return vec![]; }
987    let mut output = subject.to_vec();
988    let m = clip.len();
989    for i in 0..m {
990        if output.is_empty() { return vec![]; }
991        let input = output.clone();
992        output.clear();
993        let edge_start = clip[i];
994        let edge_end = clip[(i + 1) % m];
995        let inside = |p: Point2| -> bool {
996            (edge_end - edge_start).cross(p - edge_start) >= 0.0
997        };
998        let intersect = |a: Point2, b: Point2| -> Point2 {
999            let ab = b - a;
1000            let es = edge_end - edge_start;
1001            let num = (edge_start - a).cross(es);
1002            let den = ab.cross(es);
1003            if den.abs() < 1e-12 { return a; }
1004            a + ab * (num / den)
1005        };
1006        for j in 0..input.len() {
1007            let current = input[j];
1008            let previous = input[(j + input.len() - 1) % input.len()];
1009            if inside(current) {
1010                if !inside(previous) { output.push(intersect(previous, current)); }
1011                output.push(current);
1012            } else if inside(previous) {
1013                output.push(intersect(previous, current));
1014            }
1015        }
1016    }
1017    output
1018}
1019
1020/// Minkowski sum of two convex polygons (CCW winding assumed).
1021pub fn minkowski_sum(a: &[Point2], b: &[Point2]) -> Vec<Point2> {
1022    if a.is_empty() || b.is_empty() { return vec![]; }
1023    // Find bottom-most (then left-most) point for each
1024    let start_of = |pts: &[Point2]| -> usize {
1025        pts.iter().enumerate()
1026           .min_by(|(_, p), (_, q)| {
1027               p.y.partial_cmp(&q.y).unwrap().then(p.x.partial_cmp(&q.x).unwrap())
1028           })
1029           .unwrap()
1030           .0
1031    };
1032    let ia = start_of(a);
1033    let ib = start_of(b);
1034    let na = a.len();
1035    let nb = b.len();
1036    let mut result = Vec::new();
1037    let mut i = 0;
1038    let mut j = 0;
1039    while i < na || j < nb {
1040        result.push(a[(ia + i) % na] + b[(ib + j) % nb]);
1041        let ea = a[(ia + i + 1) % na] - a[(ia + i) % na];
1042        let eb = b[(ib + j + 1) % nb] - b[(ib + j) % nb];
1043        let cross = ea.cross(eb);
1044        if i >= na { j += 1; }
1045        else if j >= nb { i += 1; }
1046        else if cross > 0.0 { i += 1; }
1047        else if cross < 0.0 { j += 1; }
1048        else { i += 1; j += 1; }
1049    }
1050    result
1051}
1052
1053// ============================================================
1054// GJK / EPA COLLISION DETECTION
1055// ============================================================
1056
1057fn support_3d(shape: &[Point3], dir: Point3) -> Point3 {
1058    shape.iter().copied()
1059        .max_by(|a, b| a.dot(dir).partial_cmp(&b.dot(dir)).unwrap())
1060        .unwrap_or(Point3::zero())
1061}
1062
1063fn support_minkowski_diff(a: &[Point3], b: &[Point3], dir: Point3) -> Point3 {
1064    support_3d(a, dir) - support_3d(b, -dir)
1065}
1066
1067fn triple_product(a: Point3, b: Point3, c: Point3) -> Point3 {
1068    b * a.dot(c) - c * a.dot(b)
1069}
1070
1071/// GJK distance test — returns true if shapes overlap.
1072pub fn gjk(shape_a: &[Point3], shape_b: &[Point3]) -> bool {
1073    let mut dir = support_minkowski_diff(shape_a, shape_b, Point3::new(1.0, 0.0, 0.0));
1074    if dir.len_sq() < 1e-20 {
1075        dir = Point3::new(0.0, 1.0, 0.0);
1076    }
1077    let mut simplex: Vec<Point3> = Vec::new();
1078    simplex.push(dir);
1079    let mut search_dir = -dir;
1080    for _ in 0..64 {
1081        let a = support_minkowski_diff(shape_a, shape_b, search_dir);
1082        if a.dot(search_dir) < 0.0 { return false; }
1083        simplex.push(a);
1084        if do_simplex(&mut simplex, &mut search_dir) { return true; }
1085    }
1086    false
1087}
1088
1089fn do_simplex(simplex: &mut Vec<Point3>, dir: &mut Point3) -> bool {
1090    match simplex.len() {
1091        2 => {
1092            let b = simplex[0];
1093            let a = simplex[1];
1094            let ab = b - a;
1095            let ao = -a;
1096            if ab.dot(ao) > 0.0 {
1097                *dir = triple_product(ab, ao, ab);
1098            } else {
1099                *simplex = vec![a];
1100                *dir = ao;
1101            }
1102            false
1103        }
1104        3 => {
1105            let c = simplex[0]; let b = simplex[1]; let a = simplex[2];
1106            let ab = b - a; let ac = c - a; let ao = -a;
1107            let abc = ab.cross(ac);
1108            if abc.cross(ac).dot(ao) > 0.0 {
1109                if ac.dot(ao) > 0.0 {
1110                    *simplex = vec![c, a]; *dir = triple_product(ac, ao, ac);
1111                } else {
1112                    *simplex = vec![b, a]; return do_simplex(simplex, dir);
1113                }
1114            } else if ab.cross(abc).dot(ao) > 0.0 {
1115                *simplex = vec![b, a]; return do_simplex(simplex, dir);
1116            } else if abc.dot(ao) > 0.0 {
1117                *dir = abc;
1118            } else {
1119                simplex.swap(0, 1); *dir = -abc;
1120            }
1121            false
1122        }
1123        4 => {
1124            let d = simplex[0]; let c = simplex[1];
1125            let b = simplex[2]; let a = simplex[3];
1126            let ab = b - a; let ac = c - a;
1127            let ad = d - a; let ao = -a;
1128            let abc = ab.cross(ac);
1129            let acd = ac.cross(ad);
1130            let adb = ad.cross(ab);
1131            if abc.dot(ao) > 0.0 {
1132                *simplex = vec![c, b, a];
1133                return do_simplex(simplex, dir);
1134            }
1135            if acd.dot(ao) > 0.0 {
1136                *simplex = vec![d, c, a];
1137                return do_simplex(simplex, dir);
1138            }
1139            if adb.dot(ao) > 0.0 {
1140                *simplex = vec![b, d, a];
1141                return do_simplex(simplex, dir);
1142            }
1143            true // origin inside tetrahedron
1144        }
1145        _ => { *dir = Point3::new(1.0, 0.0, 0.0); false }
1146    }
1147}
1148
1149/// GJK closest points between two shapes. Returns (point_on_a, point_on_b, distance) or None if overlapping.
1150pub fn gjk_closest(shape_a: &[Point3], shape_b: &[Point3]) -> Option<(Point3, Point3, f64)> {
1151    if gjk(shape_a, shape_b) { return None; }
1152    // Simple iterative closest point using support functions
1153    let mut dir = shape_a[0] - shape_b[0];
1154    if dir.len_sq() < 1e-20 { return None; }
1155    let mut best_dist = f64::INFINITY;
1156    let mut best_pa = Point3::zero();
1157    let mut best_pb = Point3::zero();
1158    for _ in 0..64 {
1159        dir = dir.normalize();
1160        let pa = support_3d(shape_a, dir);
1161        let pb = support_3d(shape_b, -dir);
1162        let d = (pa - pb).len();
1163        if d < best_dist {
1164            best_dist = d;
1165            best_pa = pa;
1166            best_pb = pb;
1167        }
1168        dir = pb - pa;
1169        if dir.len_sq() < 1e-20 { break; }
1170    }
1171    Some((best_pa, best_pb, best_dist))
1172}
1173
1174/// EPA (Expanding Polytope Algorithm) — finds penetration depth and normal.
1175pub fn epa(shape_a: &[Point3], shape_b: &[Point3]) -> Option<(Point3, f64)> {
1176    if !gjk(shape_a, shape_b) { return None; }
1177    // Start with a simplex from GJK — build initial tetrahedron
1178    let dirs = [
1179        Point3::new(1.0, 0.0, 0.0), Point3::new(-1.0, 0.0, 0.0),
1180        Point3::new(0.0, 1.0, 0.0), Point3::new(0.0, -1.0, 0.0),
1181        Point3::new(0.0, 0.0, 1.0), Point3::new(0.0, 0.0, -1.0),
1182    ];
1183    let mut polytope: Vec<Point3> = dirs.iter()
1184        .map(|&d| support_minkowski_diff(shape_a, shape_b, d))
1185        .collect();
1186    let mut faces: Vec<[usize; 3]> = vec![
1187        [0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1],
1188        [5, 2, 1], [5, 3, 2], [5, 4, 3], [5, 1, 4],
1189    ];
1190
1191    for _ in 0..64 {
1192        // Find closest face to origin
1193        let (min_dist, min_face_idx, min_normal) = faces.iter().enumerate()
1194            .map(|(fi, face)| {
1195                let a = polytope[face[0]]; let b = polytope[face[1]]; let c = polytope[face[2]];
1196                let n = (b - a).cross(c - a);
1197                let len = n.len();
1198                if len < 1e-12 { return (f64::INFINITY, fi, Point3::new(0.0, 0.0, 1.0)); }
1199                let n = n * (1.0 / len);
1200                let d = n.dot(a);
1201                (d.abs(), fi, if d >= 0.0 { n } else { -n })
1202            })
1203            .min_by(|a, b| a.0.partial_cmp(&b.0).unwrap())
1204            .unwrap();
1205
1206        let _ = min_face_idx;
1207        let support = support_minkowski_diff(shape_a, shape_b, min_normal);
1208        let new_dist = min_normal.dot(support);
1209
1210        if (new_dist - min_dist).abs() < 1e-6 {
1211            return Some((min_normal, min_dist));
1212        }
1213
1214        // Expand polytope
1215        let si = polytope.len();
1216        polytope.push(support);
1217        let mut edge_set: Vec<(usize, usize)> = Vec::new();
1218        faces.retain(|face| {
1219            let a = polytope[face[0]]; let b = polytope[face[1]]; let c = polytope[face[2]];
1220            let n = (b - a).cross(c - a);
1221            if n.dot(support - a) > 0.0 {
1222                for &(ea, eb) in &[(face[0], face[1]), (face[1], face[2]), (face[2], face[0])] {
1223                    let rev_pos = edge_set.iter().position(|&(x, y)| x == eb && y == ea);
1224                    if let Some(pos) = rev_pos { edge_set.remove(pos); }
1225                    else { edge_set.push((ea, eb)); }
1226                }
1227                false
1228            } else { true }
1229        });
1230        for (ea, eb) in edge_set {
1231            faces.push([ea, eb, si]);
1232        }
1233    }
1234    None
1235}
1236
1237// ============================================================
1238// POLYGON OPERATIONS (continued)
1239// ============================================================
1240
1241// segment_to_segment is already defined, segment_to_segment (Segment2 version) delegates to 3D
1242
1243// ============================================================
1244// TESTS
1245// ============================================================
1246
1247#[cfg(test)]
1248mod tests {
1249    use super::*;
1250
1251    #[test]
1252    fn test_ray_aabb_hit() {
1253        let ray = Ray3 { origin: Point3::new(0.0, 0.0, 0.0), dir: Point3::new(1.0, 0.0, 0.0) };
1254        let aabb = Aabb3 { min: Point3::new(2.0, -1.0, -1.0), max: Point3::new(4.0, 1.0, 1.0) };
1255        let t = ray_aabb(&ray, &aabb).unwrap();
1256        assert!((t - 2.0).abs() < 1e-10);
1257    }
1258
1259    #[test]
1260    fn test_ray_aabb_miss() {
1261        let ray = Ray3 { origin: Point3::new(0.0, 0.0, 0.0), dir: Point3::new(0.0, 0.0, 1.0) };
1262        let aabb = Aabb3 { min: Point3::new(2.0, -1.0, -1.0), max: Point3::new(4.0, 1.0, 1.0) };
1263        assert!(ray_aabb(&ray, &aabb).is_none());
1264    }
1265
1266    #[test]
1267    fn test_ray_sphere() {
1268        let ray = Ray3 { origin: Point3::new(-5.0, 0.0, 0.0), dir: Point3::new(1.0, 0.0, 0.0) };
1269        let sphere = Sphere { center: Point3::new(0.0, 0.0, 0.0), radius: 1.0 };
1270        let t = ray_sphere(&ray, &sphere).unwrap();
1271        assert!((t - 4.0).abs() < 1e-10);
1272    }
1273
1274    #[test]
1275    fn test_ray_triangle_moller_trumbore() {
1276        let ray = Ray3 { origin: Point3::new(0.0, 0.0, -1.0), dir: Point3::new(0.0, 0.0, 1.0) };
1277        let tri = Triangle3 {
1278            a: Point3::new(-1.0, -1.0, 0.0),
1279            b: Point3::new(1.0, -1.0, 0.0),
1280            c: Point3::new(0.0, 1.0, 0.0),
1281        };
1282        let (t, u, v) = ray_triangle(&ray, &tri).unwrap();
1283        assert!((t - 1.0).abs() < 1e-10);
1284        let _ = (u, v);
1285    }
1286
1287    #[test]
1288    fn test_aabb_aabb_overlap() {
1289        let a = Aabb3 { min: Point3::new(0.0, 0.0, 0.0), max: Point3::new(2.0, 2.0, 2.0) };
1290        let b = Aabb3 { min: Point3::new(1.0, 1.0, 1.0), max: Point3::new(3.0, 3.0, 3.0) };
1291        assert!(aabb_aabb(&a, &b));
1292    }
1293
1294    #[test]
1295    fn test_aabb_aabb_no_overlap() {
1296        let a = Aabb3 { min: Point3::new(0.0, 0.0, 0.0), max: Point3::new(1.0, 1.0, 1.0) };
1297        let b = Aabb3 { min: Point3::new(2.0, 2.0, 2.0), max: Point3::new(3.0, 3.0, 3.0) };
1298        assert!(!aabb_aabb(&a, &b));
1299    }
1300
1301    #[test]
1302    fn test_sphere_sphere() {
1303        let a = Sphere { center: Point3::new(0.0, 0.0, 0.0), radius: 1.0 };
1304        let b = Sphere { center: Point3::new(1.5, 0.0, 0.0), radius: 1.0 };
1305        assert!(sphere_sphere(&a, &b));
1306    }
1307
1308    #[test]
1309    fn test_point_in_triangle() {
1310        let tri = Triangle2 {
1311            a: Point2::new(0.0, 0.0),
1312            b: Point2::new(1.0, 0.0),
1313            c: Point2::new(0.5, 1.0),
1314        };
1315        assert!(point_in_triangle(Point2::new(0.5, 0.4), &tri));
1316        assert!(!point_in_triangle(Point2::new(2.0, 0.0), &tri));
1317    }
1318
1319    #[test]
1320    fn test_segment_segment_2d() {
1321        let a = Segment2 { a: Point2::new(0.0, 0.0), b: Point2::new(2.0, 2.0) };
1322        let b = Segment2 { a: Point2::new(0.0, 2.0), b: Point2::new(2.0, 0.0) };
1323        let p = segment_segment_2d(&a, &b).unwrap();
1324        assert!((p.x - 1.0).abs() < 1e-10);
1325        assert!((p.y - 1.0).abs() < 1e-10);
1326    }
1327
1328    #[test]
1329    fn test_polygon_area() {
1330        let square = vec![
1331            Point2::new(0.0, 0.0), Point2::new(1.0, 0.0),
1332            Point2::new(1.0, 1.0), Point2::new(0.0, 1.0),
1333        ];
1334        assert!((polygon_area(&square) - 1.0).abs() < 1e-10);
1335    }
1336
1337    #[test]
1338    fn test_polygon_centroid() {
1339        let square = vec![
1340            Point2::new(0.0, 0.0), Point2::new(2.0, 0.0),
1341            Point2::new(2.0, 2.0), Point2::new(0.0, 2.0),
1342        ];
1343        let c = polygon_centroid(&square);
1344        assert!((c.x - 1.0).abs() < 1e-10);
1345        assert!((c.y - 1.0).abs() < 1e-10);
1346    }
1347
1348    #[test]
1349    fn test_convex_hull_2d() {
1350        let pts = vec![
1351            Point2::new(0.0, 0.0), Point2::new(1.0, 0.0),
1352            Point2::new(1.0, 1.0), Point2::new(0.0, 1.0),
1353            Point2::new(0.5, 0.5), // interior
1354        ];
1355        let hull = convex_hull_2d(&pts);
1356        assert_eq!(hull.len(), 4);
1357    }
1358
1359    #[test]
1360    fn test_point_in_convex_polygon() {
1361        let square = vec![
1362            Point2::new(0.0, 0.0), Point2::new(1.0, 0.0),
1363            Point2::new(1.0, 1.0), Point2::new(0.0, 1.0),
1364        ];
1365        assert!(point_in_convex_polygon(Point2::new(0.5, 0.5), &square));
1366        assert!(!point_in_convex_polygon(Point2::new(2.0, 0.5), &square));
1367    }
1368
1369    #[test]
1370    fn test_ear_clipping() {
1371        let quad = vec![
1372            Point2::new(0.0, 0.0), Point2::new(1.0, 0.0),
1373            Point2::new(1.0, 1.0), Point2::new(0.0, 1.0),
1374        ];
1375        let tris = ear_clipping(&quad);
1376        assert_eq!(tris.len(), 2);
1377    }
1378
1379    #[test]
1380    fn test_delaunay_2d() {
1381        let pts = vec![
1382            Point2::new(0.0, 0.0), Point2::new(1.0, 0.0),
1383            Point2::new(0.5, 1.0), Point2::new(0.5, 0.3),
1384        ];
1385        let tris = delaunay_2d(&pts);
1386        assert!(!tris.is_empty());
1387    }
1388
1389    #[test]
1390    fn test_sutherland_hodgman() {
1391        let subject = vec![
1392            Point2::new(0.0, 0.0), Point2::new(2.0, 0.0),
1393            Point2::new(2.0, 2.0), Point2::new(0.0, 2.0),
1394        ];
1395        let clip = vec![
1396            Point2::new(1.0, 1.0), Point2::new(3.0, 1.0),
1397            Point2::new(3.0, 3.0), Point2::new(1.0, 3.0),
1398        ];
1399        let result = sutherland_hodgman(&subject, &clip);
1400        assert!(!result.is_empty());
1401    }
1402
1403    #[test]
1404    fn test_point_to_plane() {
1405        let plane = Plane { normal: Point3::new(0.0, 1.0, 0.0), d: -2.0 };
1406        let p = Point3::new(0.0, 5.0, 0.0);
1407        let d = point_to_plane(p, &plane);
1408        assert!((d - 3.0).abs() < 1e-10);
1409    }
1410
1411    #[test]
1412    fn test_point_to_aabb() {
1413        let aabb = Aabb3 { min: Point3::new(0.0, 0.0, 0.0), max: Point3::new(1.0, 1.0, 1.0) };
1414        assert!(point_to_aabb(Point3::new(0.5, 0.5, 0.5), &aabb).abs() < 1e-10);
1415        assert!((point_to_aabb(Point3::new(3.0, 0.5, 0.5), &aabb) - 2.0).abs() < 1e-10);
1416    }
1417
1418    #[test]
1419    fn test_obb_obb_overlap() {
1420        let center = Point3::new(0.0, 0.0, 0.0);
1421        let axes = [
1422            Point3::new(1.0, 0.0, 0.0),
1423            Point3::new(0.0, 1.0, 0.0),
1424            Point3::new(0.0, 0.0, 1.0),
1425        ];
1426        let a = Obb3 { center, axes, half_extents: Point3::new(1.0, 1.0, 1.0) };
1427        let b = Obb3 {
1428            center: Point3::new(1.5, 0.0, 0.0),
1429            axes,
1430            half_extents: Point3::new(1.0, 1.0, 1.0),
1431        };
1432        assert!(obb_obb(&a, &b));
1433    }
1434
1435    #[test]
1436    fn test_gjk_overlapping() {
1437        let a: Vec<Point3> = vec![
1438            Point3::new(-1.0, -1.0, -1.0), Point3::new(1.0, -1.0, -1.0),
1439            Point3::new(0.0, 1.0, -1.0), Point3::new(0.0, 0.0, 1.0),
1440        ];
1441        let b: Vec<Point3> = vec![
1442            Point3::new(-0.5, -0.5, -0.5), Point3::new(0.5, -0.5, -0.5),
1443            Point3::new(0.0, 0.5, -0.5), Point3::new(0.0, 0.0, 0.5),
1444        ];
1445        assert!(gjk(&a, &b));
1446    }
1447
1448    #[test]
1449    fn test_polygon_is_convex() {
1450        let convex = vec![
1451            Point2::new(0.0, 0.0), Point2::new(1.0, 0.0),
1452            Point2::new(1.0, 1.0), Point2::new(0.0, 1.0),
1453        ];
1454        assert!(polygon_is_convex(&convex));
1455    }
1456
1457    #[test]
1458    fn test_ray_plane() {
1459        let plane = Plane { normal: Point3::new(0.0, 1.0, 0.0), d: -5.0 };
1460        let ray = Ray3 { origin: Point3::new(0.0, 0.0, 0.0), dir: Point3::new(0.0, 1.0, 0.0) };
1461        let t = ray_plane(&ray, &plane).unwrap();
1462        assert!((t - 5.0).abs() < 1e-10);
1463    }
1464
1465    #[test]
1466    fn test_point_in_obb() {
1467        let obb = Obb3 {
1468            center: Point3::new(0.0, 0.0, 0.0),
1469            axes: [
1470                Point3::new(1.0, 0.0, 0.0),
1471                Point3::new(0.0, 1.0, 0.0),
1472                Point3::new(0.0, 0.0, 1.0),
1473            ],
1474            half_extents: Point3::new(2.0, 2.0, 2.0),
1475        };
1476        assert!(point_in_obb(Point3::new(1.0, 1.0, 1.0), &obb));
1477        assert!(!point_in_obb(Point3::new(3.0, 0.0, 0.0), &obb));
1478    }
1479
1480    #[test]
1481    fn test_sphere_aabb() {
1482        let s = Sphere { center: Point3::new(2.0, 0.0, 0.0), radius: 1.5 };
1483        let b = Aabb3 { min: Point3::new(0.0, 0.0, 0.0), max: Point3::new(1.0, 1.0, 1.0) };
1484        assert!(sphere_aabb(&s, &b));
1485    }
1486
1487    #[test]
1488    fn test_aabb_plane() {
1489        let aabb = Aabb3 { min: Point3::new(0.0, 0.0, 0.0), max: Point3::new(1.0, 1.0, 1.0) };
1490        let plane = Plane { normal: Point3::new(0.0, 1.0, 0.0), d: -10.0 };
1491        assert_eq!(aabb_plane(&aabb, &plane), Halfspace::Back);
1492    }
1493
1494    #[test]
1495    fn test_point3_ops() {
1496        let a = Point3::new(1.0, 2.0, 3.0);
1497        let b = Point3::new(4.0, 5.0, 6.0);
1498        let c = a + b;
1499        assert_eq!(c, Point3::new(5.0, 7.0, 9.0));
1500        let d = b - a;
1501        assert_eq!(d, Point3::new(3.0, 3.0, 3.0));
1502        let e = a.cross(b);
1503        let dot = a.dot(b);
1504        assert!((dot - 32.0).abs() < 1e-10);
1505        let _ = e;
1506    }
1507}