Skip to main content

qualia_core_db/specialized_libs/computational_geometry/
distance.rs

1//! Distance and intersection primitive family for 2D and 3D geometry.
2//!
3//! Provides allocation-free distance computations and exact/filtered
4//! intersection tests for the spatial query layer (BVH, kd-tree, box joins).
5//!
6//! All functions are `#[inline]`, zero-heap, and deterministic. Intersection
7//! tests return an enum classifying the result (including degenerate cases:
8//! collinear, coplanar, touching, zero-length, ray-grazes-edge).
9
10use super::primitives::{Point2, Point3};
11
12// ---------------------------------------------------------------------------
13// Distance: 2D
14// ---------------------------------------------------------------------------
15
16/// Squared Euclidean distance between two 2D points.
17#[inline]
18pub fn distance_sq_2d(a: Point2, b: Point2) -> f64 {
19    let dx = a.x - b.x;
20    let dy = a.y - b.y;
21    dx * dx + dy * dy
22}
23
24/// Euclidean distance between two 2D points.
25#[inline]
26pub fn distance_2d(a: Point2, b: Point2) -> f64 {
27    distance_sq_2d(a, b).sqrt()
28}
29
30/// Squared distance from point `p` to segment `ab` in 2D.
31#[inline]
32pub fn point_segment_distance_sq_2d(p: Point2, a: Point2, b: Point2) -> f64 {
33    let abx = b.x - a.x;
34    let aby = b.y - a.y;
35    let apx = p.x - a.x;
36    let apy = p.y - a.y;
37    let ab_sq = abx * abx + aby * aby;
38    if ab_sq == 0.0 {
39        // Degenerate segment (a == b).
40        return apx * apx + apy * apy;
41    }
42    let t = (apx * abx + apy * aby) / ab_sq;
43    let t = t.clamp(0.0, 1.0);
44    let cx = a.x + t * abx;
45    let cy = a.y + t * aby;
46    let dx = p.x - cx;
47    let dy = p.y - cy;
48    dx * dx + dy * dy
49}
50
51/// Distance from point `p` to segment `ab` in 2D.
52#[inline]
53pub fn point_segment_distance_2d(p: Point2, a: Point2, b: Point2) -> f64 {
54    point_segment_distance_sq_2d(p, a, b).sqrt()
55}
56
57/// Squared distance from point `p` to the line through `a` and `b` (infinite line) in 2D.
58#[inline]
59pub fn point_line_distance_sq_2d(p: Point2, a: Point2, b: Point2) -> f64 {
60    let abx = b.x - a.x;
61    let aby = b.y - a.y;
62    let ab_sq = abx * abx + aby * aby;
63    if ab_sq == 0.0 {
64        let dx = p.x - a.x;
65        let dy = p.y - a.y;
66        return dx * dx + dy * dy;
67    }
68    let cross = (b.x - a.x) * (p.y - a.y) - (b.y - a.y) * (p.x - a.x);
69    cross * cross / ab_sq
70}
71
72// ---------------------------------------------------------------------------
73// Distance: 3D
74// ---------------------------------------------------------------------------
75
76/// Squared Euclidean distance between two 3D points.
77#[inline]
78pub fn distance_sq_3d(a: Point3, b: Point3) -> f64 {
79    let dx = a.x - b.x;
80    let dy = a.y - b.y;
81    let dz = a.z - b.z;
82    dx * dx + dy * dy + dz * dz
83}
84
85/// Euclidean distance between two 3D points.
86#[inline]
87pub fn distance_3d(a: Point3, b: Point3) -> f64 {
88    distance_sq_3d(a, b).sqrt()
89}
90
91/// Squared distance from point `p` to segment `ab` in 3D.
92#[inline]
93pub fn point_segment_distance_sq_3d(p: Point3, a: Point3, b: Point3) -> f64 {
94    let abx = b.x - a.x;
95    let aby = b.y - a.y;
96    let abz = b.z - a.z;
97    let apx = p.x - a.x;
98    let apy = p.y - a.y;
99    let apz = p.z - a.z;
100    let ab_sq = abx * abx + aby * aby + abz * abz;
101    if ab_sq == 0.0 {
102        return apx * apx + apy * apy + apz * apz;
103    }
104    let t = (apx * abx + apy * aby + apz * abz) / ab_sq;
105    let t = t.clamp(0.0, 1.0);
106    let cx = a.x + t * abx;
107    let cy = a.y + t * aby;
108    let cz = a.z + t * abz;
109    let dx = p.x - cx;
110    let dy = p.y - cy;
111    let dz = p.z - cz;
112    dx * dx + dy * dy + dz * dz
113}
114
115/// Squared distance from point `p` to triangle `abc` in 3D.
116///
117/// Projects `p` onto the triangle plane, classifies the projection relative to
118/// the triangle edges, and returns the squared distance to the closest feature
119/// (vertex, edge, or interior).
120pub fn point_triangle_distance_sq_3d(p: Point3, a: Point3, b: Point3, c: Point3) -> f64 {
121    let ab = Point3::new(b.x - a.x, b.y - a.y, b.z - a.z);
122    let ac = Point3::new(c.x - a.x, c.y - a.y, c.z - a.z);
123    let ap = Point3::new(p.x - a.x, p.y - a.y, p.z - a.z);
124
125    let d1 = dot_3d(ab, ap);
126    let d2 = dot_3d(ac, ap);
127    if d1 <= 0.0 && d2 <= 0.0 {
128        // Closest to vertex a.
129        return dot_3d(ap, ap);
130    }
131
132    let bp = Point3::new(p.x - b.x, p.y - b.y, p.z - b.z);
133    let d3 = dot_3d(ab, bp);
134    let d4 = dot_3d(ac, bp);
135    if d3 >= 0.0 && d4 <= d3 {
136        // Closest to vertex b.
137        return dot_3d(bp, bp);
138    }
139
140    let cp = Point3::new(p.x - c.x, p.y - c.y, p.z - c.z);
141    let d5 = dot_3d(ab, cp);
142    let d6 = dot_3d(ac, cp);
143    if d6 >= 0.0 && d5 <= d6 {
144        // Closest to vertex c.
145        return dot_3d(cp, cp);
146    }
147
148    // Edge ab.
149    let vc = d1 * d4 - d3 * d2;
150    if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 {
151        let t = d1 / (d1 - d3);
152        let cx = a.x + t * ab.x;
153        let cy = a.y + t * ab.y;
154        let cz = a.z + t * ab.z;
155        let dx = p.x - cx;
156        let dy = p.y - cy;
157        let dz = p.z - cz;
158        return dx * dx + dy * dy + dz * dz;
159    }
160
161    // Edge ac.
162    let vb = d5 * d2 - d1 * d6;
163    if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 {
164        let t = d2 / (d2 - d6);
165        let cx = a.x + t * ac.x;
166        let cy = a.y + t * ac.y;
167        let cz = a.z + t * ac.z;
168        let dx = p.x - cx;
169        let dy = p.y - cy;
170        let dz = p.z - cz;
171        return dx * dx + dy * dy + dz * dz;
172    }
173
174    // Edge bc.
175    let va = d3 * d6 - d5 * d4;
176    if va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0 {
177        let t = (d4 - d3) / ((d4 - d3) + (d5 - d6));
178        let bcx = c.x - b.x;
179        let bcy = c.y - b.y;
180        let bcz = c.z - b.z;
181        let cx = b.x + t * bcx;
182        let cy = b.y + t * bcy;
183        let cz = b.z + t * bcz;
184        let dx = p.x - cx;
185        let dy = p.y - cy;
186        let dz = p.z - cz;
187        return dx * dx + dy * dy + dz * dz;
188    }
189
190    // Interior of the triangle: project onto plane.
191    let denom = 1.0 / (va + vb + vc);
192    let v = vb * denom;
193    let w = vc * denom;
194    let cx = a.x + ab.x * v + ac.x * w;
195    let cy = a.y + ab.y * v + ac.y * w;
196    let cz = a.z + ab.z * v + ac.z * w;
197    let dx = p.x - cx;
198    let dy = p.y - cy;
199    let dz = p.z - cz;
200    dx * dx + dy * dy + dz * dz
201}
202
203#[inline]
204fn dot_3d(a: Point3, b: Point3) -> f64 {
205    a.x * b.x + a.y * b.y + a.z * b.z
206}
207
208// ---------------------------------------------------------------------------
209// Intersection: 2D segment-segment
210// ---------------------------------------------------------------------------
211
212/// Result of a 2D segment-segment intersection test.
213#[derive(Debug, Clone, Copy, PartialEq, Eq)]
214pub enum SegmentIntersection2d {
215    /// Segments do not intersect.
216    Disjoint,
217    /// Segments intersect at a single point.
218    Point,
219    /// Segments overlap over an interval (collinear overlap).
220    Overlap,
221    /// Segments share an endpoint (touching).
222    Touching,
223}
224
225/// Test whether two 2D segments `ab` and `cd` intersect.
226///
227/// Uses orientation predicates for the general case and direct coordinate
228/// comparison for degenerate (collinear) cases. Returns the intersection type.
229pub fn segment_segment_intersect_2d(
230    a: Point2,
231    b: Point2,
232    c: Point2,
233    d: Point2,
234) -> SegmentIntersection2d {
235    use super::primitives::orientation_2;
236
237    let o1 = orientation_2(a, b, c);
238    let o2 = orientation_2(a, b, d);
239    let o3 = orientation_2(c, d, a);
240    let o4 = orientation_2(c, d, b);
241
242    // General case: proper intersection.
243    if o1 != o2 && o3 != o4 {
244        return SegmentIntersection2d::Point;
245    }
246
247    // Degenerate: collinear cases.
248    if o1 == super::primitives::Orientation::Collinear
249        && o2 == super::primitives::Orientation::Collinear
250    {
251        // All four points are collinear. Check for overlap.
252        // Project onto the dominant axis.
253        let (a_t, b_t) = if (b.x - a.x).abs() >= (b.y - a.y).abs() {
254            (a.x, b.x)
255        } else {
256            (a.y, b.y)
257        };
258        let (c_t, d_t) = if (b.x - a.x).abs() >= (b.y - a.y).abs() {
259            (c.x, d.x)
260        } else {
261            (c.y, d.y)
262        };
263
264        let (lo_ab, hi_ab) = (a_t.min(b_t), a_t.max(b_t));
265        let (lo_cd, hi_cd) = (c_t.min(d_t), c_t.max(d_t));
266
267        if hi_ab < lo_cd || hi_cd < lo_ab {
268            return SegmentIntersection2d::Disjoint;
269        }
270        // Check if they just touch at an endpoint.
271        if hi_ab == lo_cd || hi_cd == lo_ab {
272            return SegmentIntersection2d::Touching;
273        }
274        return SegmentIntersection2d::Overlap;
275    }
276
277    // One endpoint lies on the other segment.
278    if o1 == super::primitives::Orientation::Collinear && on_segment_2d(a, b, c) {
279        return SegmentIntersection2d::Touching;
280    }
281    if o2 == super::primitives::Orientation::Collinear && on_segment_2d(a, b, d) {
282        return SegmentIntersection2d::Touching;
283    }
284    if o3 == super::primitives::Orientation::Collinear && on_segment_2d(c, d, a) {
285        return SegmentIntersection2d::Touching;
286    }
287    if o4 == super::primitives::Orientation::Collinear && on_segment_2d(c, d, b) {
288        return SegmentIntersection2d::Touching;
289    }
290
291    SegmentIntersection2d::Disjoint
292}
293
294/// Check if point `p` lies on segment `ab` (assuming collinearity).
295#[inline]
296fn on_segment_2d(a: Point2, b: Point2, p: Point2) -> bool {
297    p.x >= a.x.min(b.x) && p.x <= a.x.max(b.x) && p.y >= a.y.min(b.y) && p.y <= a.y.max(b.y)
298}
299
300// ---------------------------------------------------------------------------
301// Intersection: 3D ray-triangle (Möller–Trumbore)
302// ---------------------------------------------------------------------------
303
304/// Result of a 3D ray-triangle intersection test.
305#[derive(Debug, Clone, Copy, PartialEq)]
306pub struct RayTriangleHit {
307    /// Ray parameter `t` at the hit point: `origin + t * direction`.
308    pub t: f64,
309    /// Barycentric coordinate u.
310    pub u: f64,
311    /// Barycentric coordinate v.
312    pub v: f64,
313}
314
315/// Result of a 3D ray-triangle intersection.
316#[derive(Debug, Clone, Copy, PartialEq)]
317pub enum RayTriangleResult {
318    /// Ray hits the triangle at the given parameters.
319    Hit(RayTriangleHit),
320    /// Ray misses the triangle.
321    Miss,
322    /// Ray is parallel to the triangle plane (grazes).
323    Parallel,
324    /// Degenerate triangle (zero area).
325    DegenerateTriangle,
326}
327
328/// Test intersection of a 3D ray with a triangle using the Möller–Trumbore algorithm.
329///
330/// - `origin`: ray origin.
331/// - `direction`: ray direction (need not be normalized; `t` is in units of `direction`).
332/// - `a, b, c`: triangle vertices.
333///
334/// Zero-heap. Deterministic.
335pub fn ray_triangle_intersect_3d(
336    origin: Point3,
337    direction: Point3,
338    a: Point3,
339    b: Point3,
340    c: Point3,
341) -> RayTriangleResult {
342    let edge1 = Point3::new(b.x - a.x, b.y - a.y, b.z - a.z);
343    let edge2 = Point3::new(c.x - a.x, c.y - a.y, c.z - a.z);
344
345    let h = cross_3d(direction, edge2);
346    let det = dot_3d(edge1, h);
347
348    // Back-face culling tolerance: if det is near zero, ray is parallel.
349    if det.abs() < f64::EPSILON {
350        // Check for degenerate triangle.
351        let edge1_sq = dot_3d(edge1, edge1);
352        let edge2_sq = dot_3d(edge2, edge2);
353        if edge1_sq == 0.0 || edge2_sq == 0.0 {
354            return RayTriangleResult::DegenerateTriangle;
355        }
356        return RayTriangleResult::Parallel;
357    }
358
359    let inv_det = 1.0 / det;
360    let s = Point3::new(origin.x - a.x, origin.y - a.y, origin.z - a.z);
361    let u = inv_det * dot_3d(s, h);
362
363    if u < 0.0 || u > 1.0 {
364        return RayTriangleResult::Miss;
365    }
366
367    let q = cross_3d(s, edge1);
368    let v = inv_det * dot_3d(direction, q);
369
370    if v < 0.0 || u + v > 1.0 {
371        return RayTriangleResult::Miss;
372    }
373
374    let t = inv_det * dot_3d(edge2, q);
375    RayTriangleResult::Hit(RayTriangleHit { t, u, v })
376}
377
378#[inline]
379fn cross_3d(a: Point3, b: Point3) -> Point3 {
380    Point3::new(
381        a.y * b.z - a.z * b.y,
382        a.z * b.x - a.x * b.z,
383        a.x * b.y - a.y * b.x,
384    )
385}
386
387// ---------------------------------------------------------------------------
388// Intersection: 3D AABB overlap
389// ---------------------------------------------------------------------------
390
391/// Axis-aligned bounding box in 3D.
392#[derive(Debug, Clone, Copy, PartialEq)]
393pub struct Aabb {
394    pub min: Point3,
395    pub max: Point3,
396}
397
398impl Aabb {
399    #[inline]
400    pub fn new(min: Point3, max: Point3) -> Self {
401        Self { min, max }
402    }
403
404    #[inline]
405    pub fn overlaps(&self, other: &Aabb) -> bool {
406        self.min.x <= other.max.x
407            && self.max.x >= other.min.x
408            && self.min.y <= other.max.y
409            && self.max.y >= other.min.y
410            && self.min.z <= other.max.z
411            && self.max.z >= other.min.z
412    }
413
414    #[inline]
415    pub fn contains_point(&self, p: Point3) -> bool {
416        p.x >= self.min.x
417            && p.x <= self.max.x
418            && p.y >= self.min.y
419            && p.y <= self.max.y
420            && p.z >= self.min.z
421            && p.z <= self.max.z
422    }
423
424    /// Squared distance from point `p` to this AABB (0 if inside).
425    #[inline]
426    pub fn distance_sq_to_point(&self, p: Point3) -> f64 {
427        let dx = (self.min.x - p.x).max(0.0).max(p.x - self.max.x);
428        let dy = (self.min.y - p.y).max(0.0).max(p.y - self.max.y);
429        let dz = (self.min.z - p.z).max(0.0).max(p.z - self.max.z);
430        dx * dx + dy * dy + dz * dz
431    }
432
433    /// Center point.
434    #[inline]
435    pub fn center(&self) -> Point3 {
436        Point3::new(
437            0.5 * (self.min.x + self.max.x),
438            0.5 * (self.min.y + self.max.y),
439            0.5 * (self.min.z + self.max.z),
440        )
441    }
442
443    /// Surface area (used for SAH-based BVH construction).
444    #[inline]
445    pub fn surface_area(&self) -> f64 {
446        let dx = self.max.x - self.min.x;
447        let dy = self.max.y - self.min.y;
448        let dz = self.max.z - self.min.z;
449        2.0 * (dx * dy + dy * dz + dz * dx)
450    }
451
452    /// Union of two AABBs.
453    #[inline]
454    pub fn union(&self, other: &Aabb) -> Aabb {
455        Aabb::new(
456            Point3::new(
457                self.min.x.min(other.min.x),
458                self.min.y.min(other.min.y),
459                self.min.z.min(other.min.z),
460            ),
461            Point3::new(
462                self.max.x.max(other.max.x),
463                self.max.y.max(other.max.y),
464                self.max.z.max(other.max.z),
465            ),
466        )
467    }
468}
469
470// ---------------------------------------------------------------------------
471// Tests
472// ---------------------------------------------------------------------------
473
474#[cfg(test)]
475mod tests {
476    use super::*;
477
478    // --- 2D distance ---
479
480    #[test]
481    fn distance_2d_basic() {
482        let a = Point2::new(0.0, 0.0);
483        let b = Point2::new(3.0, 4.0);
484        assert_eq!(distance_2d(a, b), 5.0);
485        assert_eq!(distance_sq_2d(a, b), 25.0);
486    }
487
488    #[test]
489    fn point_segment_distance_2d_interior() {
490        let p = Point2::new(0.5, 1.0);
491        let a = Point2::new(0.0, 0.0);
492        let b = Point2::new(1.0, 0.0);
493        assert!((point_segment_distance_2d(p, a, b) - 1.0).abs() < 1e-12);
494    }
495
496    #[test]
497    fn point_segment_distance_2d_endpoint() {
498        let p = Point2::new(2.0, 0.0);
499        let a = Point2::new(0.0, 0.0);
500        let b = Point2::new(1.0, 0.0);
501        assert!((point_segment_distance_2d(p, a, b) - 1.0).abs() < 1e-12);
502    }
503
504    #[test]
505    fn point_segment_distance_2d_degenerate() {
506        let p = Point2::new(1.0, 1.0);
507        let a = Point2::new(0.0, 0.0);
508        let b = Point2::new(0.0, 0.0); // zero-length segment
509        assert!((point_segment_distance_2d(p, a, b) - 2.0f64.sqrt()).abs() < 1e-12);
510    }
511
512    #[test]
513    fn point_line_distance_2d() {
514        let p = Point2::new(0.0, 1.0);
515        let a = Point2::new(-1.0, 0.0);
516        let b = Point2::new(1.0, 0.0);
517        assert!((point_line_distance_sq_2d(p, a, b) - 1.0).abs() < 1e-12);
518    }
519
520    // --- 3D distance ---
521
522    #[test]
523    fn distance_3d_basic() {
524        let a = Point3::new(0.0, 0.0, 0.0);
525        let b = Point3::new(1.0, 2.0, 2.0);
526        assert!((distance_3d(a, b) - 3.0).abs() < 1e-12);
527    }
528
529    #[test]
530    fn point_segment_distance_3d() {
531        let p = Point3::new(0.5, 0.0, 1.0);
532        let a = Point3::new(0.0, 0.0, 0.0);
533        let b = Point3::new(1.0, 0.0, 0.0);
534        assert!((point_segment_distance_sq_3d(p, a, b) - 1.0).abs() < 1e-12);
535    }
536
537    #[test]
538    fn point_triangle_distance_3d_interior() {
539        // Point directly above the centroid of a triangle.
540        let a = Point3::new(0.0, 0.0, 0.0);
541        let b = Point3::new(1.0, 0.0, 0.0);
542        let c = Point3::new(0.0, 1.0, 0.0);
543        let p = Point3::new(1.0 / 3.0, 1.0 / 3.0, 2.0);
544        assert!((point_triangle_distance_sq_3d(p, a, b, c) - 4.0).abs() < 1e-12);
545    }
546
547    #[test]
548    fn point_triangle_distance_3d_vertex() {
549        let a = Point3::new(0.0, 0.0, 0.0);
550        let b = Point3::new(1.0, 0.0, 0.0);
551        let c = Point3::new(0.0, 1.0, 0.0);
552        let p = Point3::new(-1.0, -1.0, 0.0);
553        let d = point_triangle_distance_sq_3d(p, a, b, c);
554        assert!((d - 2.0).abs() < 1e-12, "d={d}");
555    }
556
557    // --- 2D segment intersection ---
558
559    #[test]
560    fn segments_cross_properly() {
561        let a = Point2::new(0.0, 0.0);
562        let b = Point2::new(1.0, 1.0);
563        let c = Point2::new(0.0, 1.0);
564        let d = Point2::new(1.0, 0.0);
565        assert_eq!(
566            segment_segment_intersect_2d(a, b, c, d),
567            SegmentIntersection2d::Point
568        );
569    }
570
571    #[test]
572    fn segments_disjoint() {
573        let a = Point2::new(0.0, 0.0);
574        let b = Point2::new(1.0, 0.0);
575        let c = Point2::new(2.0, 0.0);
576        let d = Point2::new(3.0, 0.0);
577        assert_eq!(
578            segment_segment_intersect_2d(a, b, c, d),
579            SegmentIntersection2d::Disjoint
580        );
581    }
582
583    #[test]
584    fn segments_collinear_overlap() {
585        let a = Point2::new(0.0, 0.0);
586        let b = Point2::new(2.0, 0.0);
587        let c = Point2::new(1.0, 0.0);
588        let d = Point2::new(3.0, 0.0);
589        assert_eq!(
590            segment_segment_intersect_2d(a, b, c, d),
591            SegmentIntersection2d::Overlap
592        );
593    }
594
595    #[test]
596    fn segments_collinear_touching() {
597        let a = Point2::new(0.0, 0.0);
598        let b = Point2::new(1.0, 0.0);
599        let c = Point2::new(1.0, 0.0);
600        let d = Point2::new(2.0, 0.0);
601        assert_eq!(
602            segment_segment_intersect_2d(a, b, c, d),
603            SegmentIntersection2d::Touching
604        );
605    }
606
607    #[test]
608    fn segments_collinear_disjoint() {
609        let a = Point2::new(0.0, 0.0);
610        let b = Point2::new(1.0, 0.0);
611        let c = Point2::new(2.0, 0.0);
612        let d = Point2::new(3.0, 0.0);
613        assert_eq!(
614            segment_segment_intersect_2d(a, b, c, d),
615            SegmentIntersection2d::Disjoint
616        );
617    }
618
619    #[test]
620    fn segments_touching_at_endpoint() {
621        // Two segments sharing endpoint b=c, not collinear.
622        // The intersection is a single point (the shared endpoint).
623        let a = Point2::new(0.0, 0.0);
624        let b = Point2::new(1.0, 0.0);
625        let c = Point2::new(1.0, 0.0);
626        let d = Point2::new(1.0, 1.0);
627        // When segments share an endpoint and diverge, the orientation test
628        // sees o1≠o2 and o3≠o4 → Point (proper crossing at the shared vertex).
629        assert_eq!(
630            segment_segment_intersect_2d(a, b, c, d),
631            SegmentIntersection2d::Point
632        );
633    }
634
635    // --- 3D ray-triangle ---
636
637    #[test]
638    fn ray_hits_triangle() {
639        let origin = Point3::new(0.0, 0.0, 1.0);
640        let dir = Point3::new(0.0, 0.0, -1.0);
641        let a = Point3::new(-1.0, -1.0, 0.0);
642        let b = Point3::new(1.0, -1.0, 0.0);
643        let c = Point3::new(0.0, 1.0, 0.0);
644        match ray_triangle_intersect_3d(origin, dir, a, b, c) {
645            RayTriangleResult::Hit(hit) => {
646                assert!((hit.t - 1.0).abs() < 1e-12);
647                assert!(hit.u >= 0.0 && hit.u <= 1.0);
648                assert!(hit.v >= 0.0 && hit.u + hit.v <= 1.0);
649            }
650            _ => panic!("expected hit"),
651        }
652    }
653
654    #[test]
655    fn ray_misses_triangle() {
656        let origin = Point3::new(5.0, 5.0, 1.0);
657        let dir = Point3::new(0.0, 0.0, -1.0);
658        let a = Point3::new(-1.0, -1.0, 0.0);
659        let b = Point3::new(1.0, -1.0, 0.0);
660        let c = Point3::new(0.0, 1.0, 0.0);
661        assert_eq!(
662            ray_triangle_intersect_3d(origin, dir, a, b, c),
663            RayTriangleResult::Miss
664        );
665    }
666
667    #[test]
668    fn ray_parallel_to_triangle() {
669        let origin = Point3::new(0.0, 0.0, 1.0);
670        let dir = Point3::new(1.0, 0.0, 0.0); // parallel to triangle plane
671        let a = Point3::new(-1.0, -1.0, 0.0);
672        let b = Point3::new(1.0, -1.0, 0.0);
673        let c = Point3::new(0.0, 1.0, 0.0);
674        assert_eq!(
675            ray_triangle_intersect_3d(origin, dir, a, b, c),
676            RayTriangleResult::Parallel
677        );
678    }
679
680    #[test]
681    fn ray_hits_degenerate_triangle() {
682        let origin = Point3::new(0.0, 0.0, 1.0);
683        let dir = Point3::new(0.0, 0.0, -1.0);
684        let a = Point3::new(0.0, 0.0, 0.0);
685        let b = Point3::new(0.0, 0.0, 0.0); // degenerate
686        let c = Point3::new(1.0, 0.0, 0.0);
687        assert_eq!(
688            ray_triangle_intersect_3d(origin, dir, a, b, c),
689            RayTriangleResult::DegenerateTriangle
690        );
691    }
692
693    // --- AABB ---
694
695    #[test]
696    fn aabb_overlaps() {
697        let a = Aabb::new(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 1.0));
698        let b = Aabb::new(Point3::new(0.5, 0.5, 0.5), Point3::new(2.0, 2.0, 2.0));
699        assert!(a.overlaps(&b));
700    }
701
702    #[test]
703    fn aabb_disjoint() {
704        let a = Aabb::new(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 1.0));
705        let b = Aabb::new(Point3::new(2.0, 2.0, 2.0), Point3::new(3.0, 3.0, 3.0));
706        assert!(!a.overlaps(&b));
707    }
708
709    #[test]
710    fn aabb_touching_overlaps() {
711        // Touching faces should overlap (<=, >=).
712        let a = Aabb::new(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 1.0));
713        let b = Aabb::new(Point3::new(1.0, 0.0, 0.0), Point3::new(2.0, 1.0, 1.0));
714        assert!(a.overlaps(&b));
715    }
716
717    #[test]
718    fn aabb_contains_point() {
719        let a = Aabb::new(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 1.0));
720        assert!(a.contains_point(Point3::new(0.5, 0.5, 0.5)));
721        assert!(!a.contains_point(Point3::new(1.5, 0.5, 0.5)));
722    }
723
724    #[test]
725    fn aabb_distance_sq_to_point_inside() {
726        let a = Aabb::new(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 1.0));
727        assert_eq!(a.distance_sq_to_point(Point3::new(0.5, 0.5, 0.5)), 0.0);
728    }
729
730    #[test]
731    fn aabb_distance_sq_to_point_outside() {
732        let a = Aabb::new(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 1.0));
733        let d = a.distance_sq_to_point(Point3::new(2.0, 0.5, 0.5));
734        assert!((d - 1.0).abs() < 1e-12);
735    }
736
737    #[test]
738    fn aabb_surface_area() {
739        let a = Aabb::new(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 2.0, 3.0));
740        // 2*(1*2 + 2*3 + 3*1) = 2*(2+6+3) = 22
741        assert!((a.surface_area() - 22.0).abs() < 1e-12);
742    }
743
744    #[test]
745    fn aabb_union() {
746        let a = Aabb::new(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 1.0));
747        let b = Aabb::new(Point3::new(0.5, 0.5, 0.5), Point3::new(2.0, 2.0, 2.0));
748        let u = a.union(&b);
749        assert_eq!(u.min, Point3::new(0.0, 0.0, 0.0));
750        assert_eq!(u.max, Point3::new(2.0, 2.0, 2.0));
751    }
752}