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