Skip to main content

oxiphysics_collision/
mesh_collision.rs

1// Copyright 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4//! Mesh collision detection: BVH for triangle meshes (AABB tree build/query),
5//! triangle-triangle intersection (Möller-Trumbore), mesh-mesh broad+narrow phase,
6//! deformable mesh collision (BVH refit), cloth self-collision, continuous collision
7//! detection for deformables, proximity query (distance field), swept triangle vs
8//! static mesh, penetration depth estimation, and contact manifold generation.
9
10// ─────────────────────────────────────────────────────────────────────────────
11// Vector math (no nalgebra — use [f64; 3] arrays)
12// ─────────────────────────────────────────────────────────────────────────────
13
14/// Compute a - b component-wise.
15#[inline]
16pub fn vec3_sub(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
17    [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
18}
19
20/// Compute a + b component-wise.
21#[inline]
22pub fn vec3_add(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
23    [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
24}
25
26/// Scale a vector.
27#[inline]
28pub fn vec3_scale(a: [f64; 3], s: f64) -> [f64; 3] {
29    [a[0] * s, a[1] * s, a[2] * s]
30}
31
32/// Dot product.
33#[inline]
34pub fn vec3_dot(a: [f64; 3], b: [f64; 3]) -> f64 {
35    a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
36}
37
38/// Cross product.
39#[inline]
40pub fn vec3_cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
41    [
42        a[1] * b[2] - a[2] * b[1],
43        a[2] * b[0] - a[0] * b[2],
44        a[0] * b[1] - a[1] * b[0],
45    ]
46}
47
48/// Squared length.
49#[inline]
50pub fn vec3_len_sq(a: [f64; 3]) -> f64 {
51    vec3_dot(a, a)
52}
53
54/// Length.
55#[inline]
56pub fn vec3_len(a: [f64; 3]) -> f64 {
57    vec3_len_sq(a).sqrt()
58}
59
60/// Normalise; returns zero vector if near-zero input.
61#[inline]
62pub fn vec3_norm(a: [f64; 3]) -> [f64; 3] {
63    let l = vec3_len(a);
64    if l < 1e-30 {
65        [0.0; 3]
66    } else {
67        vec3_scale(a, 1.0 / l)
68    }
69}
70
71/// Component-wise minimum.
72#[inline]
73pub fn vec3_min(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
74    [a[0].min(b[0]), a[1].min(b[1]), a[2].min(b[2])]
75}
76
77/// Component-wise maximum.
78#[inline]
79pub fn vec3_max(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
80    [a[0].max(b[0]), a[1].max(b[1]), a[2].max(b[2])]
81}
82
83/// Linear interpolation.
84#[inline]
85pub fn vec3_lerp(a: [f64; 3], b: [f64; 3], t: f64) -> [f64; 3] {
86    vec3_add(vec3_scale(a, 1.0 - t), vec3_scale(b, t))
87}
88
89// ─────────────────────────────────────────────────────────────────────────────
90// AABB (Axis-Aligned Bounding Box)
91// ─────────────────────────────────────────────────────────────────────────────
92
93/// An axis-aligned bounding box in 3D.
94#[derive(Clone, Debug, PartialEq)]
95pub struct MeshAabb {
96    /// Minimum corner.
97    pub min: [f64; 3],
98    /// Maximum corner.
99    pub max: [f64; 3],
100}
101
102impl MeshAabb {
103    /// Create an empty (inverted) AABB.
104    pub fn empty() -> Self {
105        Self {
106            min: [f64::INFINITY; 3],
107            max: [f64::NEG_INFINITY; 3],
108        }
109    }
110
111    /// Create an AABB from min and max corners.
112    pub fn new(min: [f64; 3], max: [f64; 3]) -> Self {
113        Self { min, max }
114    }
115
116    /// Expand to include a point.
117    pub fn expand_point(&mut self, p: [f64; 3]) {
118        self.min = vec3_min(self.min, p);
119        self.max = vec3_max(self.max, p);
120    }
121
122    /// Expand to include another AABB.
123    pub fn expand_aabb(&mut self, other: &MeshAabb) {
124        self.min = vec3_min(self.min, other.min);
125        self.max = vec3_max(self.max, other.max);
126    }
127
128    /// Centre of the AABB.
129    pub fn centre(&self) -> [f64; 3] {
130        vec3_scale(vec3_add(self.min, self.max), 0.5)
131    }
132
133    /// Half-extents.
134    pub fn half_extents(&self) -> [f64; 3] {
135        vec3_scale(vec3_sub(self.max, self.min), 0.5)
136    }
137
138    /// Surface area.
139    pub fn surface_area(&self) -> f64 {
140        let d = vec3_sub(self.max, self.min);
141        2.0 * (d[0] * d[1] + d[1] * d[2] + d[2] * d[0])
142    }
143
144    /// Does this AABB overlap with `other`?
145    pub fn overlaps(&self, other: &MeshAabb) -> bool {
146        self.min[0] <= other.max[0]
147            && self.max[0] >= other.min[0]
148            && self.min[1] <= other.max[1]
149            && self.max[1] >= other.min[1]
150            && self.min[2] <= other.max[2]
151            && self.max[2] >= other.min[2]
152    }
153
154    /// Does this AABB contain the point `p`?
155    pub fn contains_point(&self, p: [f64; 3]) -> bool {
156        p[0] >= self.min[0]
157            && p[0] <= self.max[0]
158            && p[1] >= self.min[1]
159            && p[1] <= self.max[1]
160            && p[2] >= self.min[2]
161            && p[2] <= self.max[2]
162    }
163
164    /// Build AABB for a triangle.
165    pub fn from_triangle(a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> Self {
166        let mut aabb = Self::empty();
167        aabb.expand_point(a);
168        aabb.expand_point(b);
169        aabb.expand_point(c);
170        aabb
171    }
172
173    /// Expand by a margin (fat AABB for CCD).
174    pub fn fattened(&self, margin: f64) -> Self {
175        Self {
176            min: [
177                self.min[0] - margin,
178                self.min[1] - margin,
179                self.min[2] - margin,
180            ],
181            max: [
182                self.max[0] + margin,
183                self.max[1] + margin,
184                self.max[2] + margin,
185            ],
186        }
187    }
188}
189
190// ─────────────────────────────────────────────────────────────────────────────
191// Triangle
192// ─────────────────────────────────────────────────────────────────────────────
193
194/// A triangle defined by three vertices.
195#[derive(Clone, Debug)]
196pub struct Triangle {
197    /// First vertex.
198    pub v0: [f64; 3],
199    /// Second vertex.
200    pub v1: [f64; 3],
201    /// Third vertex.
202    pub v2: [f64; 3],
203}
204
205impl Triangle {
206    /// Create a new [`Triangle`].
207    pub fn new(v0: [f64; 3], v1: [f64; 3], v2: [f64; 3]) -> Self {
208        Self { v0, v1, v2 }
209    }
210
211    /// Outward unit normal.
212    pub fn normal(&self) -> [f64; 3] {
213        let e1 = vec3_sub(self.v1, self.v0);
214        let e2 = vec3_sub(self.v2, self.v0);
215        vec3_norm(vec3_cross(e1, e2))
216    }
217
218    /// Area of the triangle.
219    pub fn area(&self) -> f64 {
220        let e1 = vec3_sub(self.v1, self.v0);
221        let e2 = vec3_sub(self.v2, self.v0);
222        vec3_len(vec3_cross(e1, e2)) * 0.5
223    }
224
225    /// Centroid.
226    pub fn centroid(&self) -> [f64; 3] {
227        [
228            (self.v0[0] + self.v1[0] + self.v2[0]) / 3.0,
229            (self.v0[1] + self.v1[1] + self.v2[1]) / 3.0,
230            (self.v0[2] + self.v1[2] + self.v2[2]) / 3.0,
231        ]
232    }
233
234    /// AABB of the triangle.
235    pub fn aabb(&self) -> MeshAabb {
236        MeshAabb::from_triangle(self.v0, self.v1, self.v2)
237    }
238
239    /// Point at barycentric coordinates (u, v).
240    pub fn barycentric_point(&self, u: f64, v: f64) -> [f64; 3] {
241        let w = 1.0 - u - v;
242        [
243            w * self.v0[0] + u * self.v1[0] + v * self.v2[0],
244            w * self.v0[1] + u * self.v1[1] + v * self.v2[1],
245            w * self.v0[2] + u * self.v1[2] + v * self.v2[2],
246        ]
247    }
248}
249
250// ─────────────────────────────────────────────────────────────────────────────
251// Möller-Trumbore ray-triangle intersection
252// ─────────────────────────────────────────────────────────────────────────────
253
254/// Result of a ray-triangle intersection test.
255#[derive(Clone, Debug)]
256pub struct RayTriHit {
257    /// Ray parameter t at the hit point.
258    pub t: f64,
259    /// Barycentric u coordinate.
260    pub u: f64,
261    /// Barycentric v coordinate.
262    pub v: f64,
263}
264
265/// Möller-Trumbore ray-triangle intersection.
266///
267/// Returns `Some(RayTriHit)` if the ray origin + t * direction hits the triangle
268/// for t in \[t_min, t_max\].
269pub fn ray_triangle_mt(
270    origin: [f64; 3],
271    direction: [f64; 3],
272    tri: &Triangle,
273    t_min: f64,
274    t_max: f64,
275) -> Option<RayTriHit> {
276    const EPS: f64 = 1e-10;
277    let e1 = vec3_sub(tri.v1, tri.v0);
278    let e2 = vec3_sub(tri.v2, tri.v0);
279    let h = vec3_cross(direction, e2);
280    let a = vec3_dot(e1, h);
281    if a.abs() < EPS {
282        return None;
283    }
284    let f = 1.0 / a;
285    let s = vec3_sub(origin, tri.v0);
286    let u = f * vec3_dot(s, h);
287    if !(0.0..=1.0).contains(&u) {
288        return None;
289    }
290    let q = vec3_cross(s, e1);
291    let v = f * vec3_dot(direction, q);
292    if v < 0.0 || u + v > 1.0 {
293        return None;
294    }
295    let t = f * vec3_dot(e2, q);
296    if t < t_min || t > t_max {
297        return None;
298    }
299    Some(RayTriHit { t, u, v })
300}
301
302// ─────────────────────────────────────────────────────────────────────────────
303// Triangle-Triangle intersection (Devillers & Guigue / Möller)
304// ─────────────────────────────────────────────────────────────────────────────
305
306/// Test whether two triangles intersect.
307///
308/// Uses the Möller 1997 signed-distance interval test.
309pub fn triangle_triangle_intersect(t1: &Triangle, t2: &Triangle) -> bool {
310    // Plane of t1: N1·(x - v0_1) = 0
311    let n1 = t1.normal();
312    let d1 = -vec3_dot(n1, t1.v0);
313
314    // Signed distances of t2 vertices to plane of t1
315    let dv20 = vec3_dot(n1, t2.v0) + d1;
316    let dv21 = vec3_dot(n1, t2.v1) + d1;
317    let dv22 = vec3_dot(n1, t2.v2) + d1;
318
319    // If all same sign, t2 is entirely on one side of t1's plane
320    if dv20 * dv21 > 0.0 && dv20 * dv22 > 0.0 {
321        return false;
322    }
323
324    // Plane of t2
325    let n2 = t2.normal();
326    let d2 = -vec3_dot(n2, t2.v0);
327
328    let dv10 = vec3_dot(n2, t1.v0) + d2;
329    let dv11 = vec3_dot(n2, t1.v1) + d2;
330    let dv12 = vec3_dot(n2, t1.v2) + d2;
331
332    if dv10 * dv11 > 0.0 && dv10 * dv12 > 0.0 {
333        return false;
334    }
335
336    // Direction of intersection line
337    let d = vec3_cross(n1, n2);
338
339    // Project onto axis with largest component
340    let abs_d = [d[0].abs(), d[1].abs(), d[2].abs()];
341    let axis = if abs_d[0] >= abs_d[1] && abs_d[0] >= abs_d[2] {
342        0
343    } else if abs_d[1] >= abs_d[2] {
344        1
345    } else {
346        2
347    };
348
349    // Project t1 vertices
350    let p10 = t1.v0[axis];
351    let p11 = t1.v1[axis];
352    let p12 = t1.v2[axis];
353
354    // Compute interval for t1
355    let i1 = compute_interval(p10, p11, p12, dv10, dv11, dv12);
356
357    // Project t2 vertices
358    let p20 = t2.v0[axis];
359    let p21 = t2.v1[axis];
360    let p22 = t2.v2[axis];
361
362    let i2 = compute_interval(p20, p21, p22, dv20, dv21, dv22);
363
364    // Check overlap of the two intervals
365    !(i1.1 < i2.0 || i2.1 < i1.0)
366}
367
368fn compute_interval(p0: f64, p1: f64, p2: f64, d0: f64, d1: f64, d2: f64) -> (f64, f64) {
369    // The two vertices on the "same side" define the interval endpoints
370    let lerp = |pa: f64, pb: f64, da: f64, db: f64| -> f64 { pa + (pb - pa) * da / (da - db) };
371    if d0 * d1 > 0.0 {
372        // p0 and p1 on same side; p2 on the other
373        let t1 = lerp(p0, p2, d0, d2);
374        let t2 = lerp(p1, p2, d1, d2);
375        (t1.min(t2), t1.max(t2))
376    } else if d0 * d2 > 0.0 {
377        let t1 = lerp(p0, p1, d0, d1);
378        let t2 = lerp(p2, p1, d2, d1);
379        (t1.min(t2), t1.max(t2))
380    } else {
381        let t1 = lerp(p1, p0, d1, d0);
382        let t2 = lerp(p2, p0, d2, d0);
383        (t1.min(t2), t1.max(t2))
384    }
385}
386
387// ─────────────────────────────────────────────────────────────────────────────
388// BVH node
389// ─────────────────────────────────────────────────────────────────────────────
390
391/// A node in a BVH tree.
392#[derive(Clone, Debug)]
393pub struct BvhNode {
394    /// AABB of this node.
395    pub aabb: MeshAabb,
396    /// Left child index (usize::MAX if leaf).
397    pub left: usize,
398    /// Right child index (usize::MAX if leaf).
399    pub right: usize,
400    /// Triangle index (only valid for leaves).
401    pub tri_idx: usize,
402    /// Is this a leaf node?
403    pub is_leaf: bool,
404}
405
406impl BvhNode {
407    /// Create a leaf node.
408    pub fn leaf(aabb: MeshAabb, tri_idx: usize) -> Self {
409        Self {
410            aabb,
411            left: usize::MAX,
412            right: usize::MAX,
413            tri_idx,
414            is_leaf: true,
415        }
416    }
417
418    /// Create an internal node.
419    pub fn internal(aabb: MeshAabb, left: usize, right: usize) -> Self {
420        Self {
421            aabb,
422            left,
423            right,
424            tri_idx: usize::MAX,
425            is_leaf: false,
426        }
427    }
428}
429
430// ─────────────────────────────────────────────────────────────────────────────
431// Triangle mesh BVH
432// ─────────────────────────────────────────────────────────────────────────────
433
434/// A triangle mesh with an AABB BVH for fast collision queries.
435pub struct TriMeshBvh {
436    /// Triangle vertices in flat form: \[tri_idx\]\[vert_idx\]\[xyz\].
437    pub triangles: Vec<Triangle>,
438    /// BVH nodes.
439    pub nodes: Vec<BvhNode>,
440    /// Root node index.
441    pub root: usize,
442}
443
444impl TriMeshBvh {
445    /// Build a BVH from a list of triangles using the SAH-inspired median split.
446    pub fn build(triangles: Vec<Triangle>) -> Self {
447        let n = triangles.len();
448        let mut nodes: Vec<BvhNode> = Vec::with_capacity(2 * n);
449        let indices: Vec<usize> = (0..n).collect();
450        let root = Self::build_recursive(&triangles, &indices, &mut nodes);
451        Self {
452            triangles,
453            nodes,
454            root,
455        }
456    }
457
458    fn build_recursive(tris: &[Triangle], indices: &[usize], nodes: &mut Vec<BvhNode>) -> usize {
459        // Compute AABB for all triangles in this node
460        let mut aabb = MeshAabb::empty();
461        for &i in indices {
462            aabb.expand_aabb(&tris[i].aabb());
463        }
464
465        if indices.len() == 1 {
466            let node = BvhNode::leaf(aabb, indices[0]);
467            let idx = nodes.len();
468            nodes.push(node);
469            return idx;
470        }
471
472        // Find longest axis to split along
473        let ext = vec3_sub(aabb.max, aabb.min);
474        let axis = if ext[0] >= ext[1] && ext[0] >= ext[2] {
475            0
476        } else if ext[1] >= ext[2] {
477            1
478        } else {
479            2
480        };
481
482        // Sort by centroid on chosen axis
483        let mut sorted: Vec<usize> = indices.to_vec();
484        sorted.sort_by(|&a, &b| {
485            tris[a].centroid()[axis]
486                .partial_cmp(&tris[b].centroid()[axis])
487                .unwrap_or(std::cmp::Ordering::Equal)
488        });
489
490        let mid = sorted.len() / 2;
491        let left = Self::build_recursive(tris, &sorted[..mid], nodes);
492        let right = Self::build_recursive(tris, &sorted[mid..], nodes);
493
494        let node = BvhNode::internal(aabb, left, right);
495        let idx = nodes.len();
496        nodes.push(node);
497        idx
498    }
499
500    /// Ray cast into the BVH. Returns the closest hit.
501    pub fn ray_cast(
502        &self,
503        origin: [f64; 3],
504        direction: [f64; 3],
505        t_max: f64,
506    ) -> Option<(RayTriHit, usize)> {
507        let mut best: Option<(RayTriHit, usize)> = None;
508        let mut stack = vec![self.root];
509        let mut t_cur = t_max;
510
511        while let Some(node_idx) = stack.pop() {
512            if node_idx >= self.nodes.len() {
513                continue;
514            }
515            let node = &self.nodes[node_idx];
516            // Ray-AABB test
517            if !ray_aabb_intersect(origin, direction, &node.aabb, 0.0, t_cur) {
518                continue;
519            }
520            if node.is_leaf {
521                let tri = &self.triangles[node.tri_idx];
522                if let Some(hit) = ray_triangle_mt(origin, direction, tri, 0.0, t_cur) {
523                    t_cur = hit.t;
524                    best = Some((hit, node.tri_idx));
525                }
526            } else {
527                stack.push(node.left);
528                stack.push(node.right);
529            }
530        }
531        best
532    }
533
534    /// Collect all triangle indices whose AABB overlaps the query AABB.
535    pub fn query_aabb(&self, query: &MeshAabb) -> Vec<usize> {
536        let mut result = Vec::new();
537        let mut stack = vec![self.root];
538        while let Some(node_idx) = stack.pop() {
539            if node_idx >= self.nodes.len() {
540                continue;
541            }
542            let node = &self.nodes[node_idx];
543            if !node.aabb.overlaps(query) {
544                continue;
545            }
546            if node.is_leaf {
547                result.push(node.tri_idx);
548            } else {
549                stack.push(node.left);
550                stack.push(node.right);
551            }
552        }
553        result
554    }
555
556    /// Refit BVH after vertex positions have changed (for deformable meshes).
557    pub fn refit(&mut self) {
558        Self::refit_recursive(&self.triangles, &mut self.nodes, self.root);
559    }
560
561    fn refit_recursive(tris: &[Triangle], nodes: &mut Vec<BvhNode>, node_idx: usize) {
562        if node_idx >= nodes.len() {
563            return;
564        }
565        let is_leaf = nodes[node_idx].is_leaf;
566        if is_leaf {
567            let tri_idx = nodes[node_idx].tri_idx;
568            nodes[node_idx].aabb = tris[tri_idx].aabb();
569            return;
570        }
571        let left = nodes[node_idx].left;
572        let right = nodes[node_idx].right;
573        Self::refit_recursive(tris, nodes, left);
574        Self::refit_recursive(tris, nodes, right);
575        let mut aabb = nodes[left].aabb.clone();
576        aabb.expand_aabb(&nodes[right].aabb.clone());
577        nodes[node_idx].aabb = aabb;
578    }
579
580    /// Number of triangles.
581    pub fn num_triangles(&self) -> usize {
582        self.triangles.len()
583    }
584
585    /// Number of BVH nodes.
586    pub fn num_nodes(&self) -> usize {
587        self.nodes.len()
588    }
589}
590
591/// Ray-AABB intersection test (slab method).
592pub fn ray_aabb_intersect(
593    origin: [f64; 3],
594    direction: [f64; 3],
595    aabb: &MeshAabb,
596    t_min: f64,
597    t_max: f64,
598) -> bool {
599    let mut tmin = t_min;
600    let mut tmax = t_max;
601    for i in 0..3 {
602        let inv = if direction[i].abs() > 1e-30 {
603            1.0 / direction[i]
604        } else {
605            f64::INFINITY
606        };
607        let t1 = (aabb.min[i] - origin[i]) * inv;
608        let t2 = (aabb.max[i] - origin[i]) * inv;
609        let (lo, hi) = if t1 < t2 { (t1, t2) } else { (t2, t1) };
610        tmin = tmin.max(lo);
611        tmax = tmax.min(hi);
612        if tmax < tmin {
613            return false;
614        }
615    }
616    true
617}
618
619// ─────────────────────────────────────────────────────────────────────────────
620// Mesh-mesh broad phase + narrow phase
621// ─────────────────────────────────────────────────────────────────────────────
622
623/// A candidate pair of triangle indices for narrow phase testing.
624#[derive(Clone, Debug)]
625pub struct TriPair {
626    /// Triangle index in mesh A.
627    pub tri_a: usize,
628    /// Triangle index in mesh B.
629    pub tri_b: usize,
630}
631
632/// Broad phase: find candidate triangle pairs between two BVH meshes.
633pub fn mesh_broad_phase(a: &TriMeshBvh, b: &TriMeshBvh) -> Vec<TriPair> {
634    let mut pairs = Vec::new();
635    mesh_broad_phase_recursive(a, b, a.root, b.root, &mut pairs);
636    pairs
637}
638
639fn mesh_broad_phase_recursive(
640    a: &TriMeshBvh,
641    b: &TriMeshBvh,
642    na: usize,
643    nb: usize,
644    pairs: &mut Vec<TriPair>,
645) {
646    if na >= a.nodes.len() || nb >= b.nodes.len() {
647        return;
648    }
649    if !a.nodes[na].aabb.overlaps(&b.nodes[nb].aabb) {
650        return;
651    }
652
653    let a_leaf = a.nodes[na].is_leaf;
654    let b_leaf = b.nodes[nb].is_leaf;
655
656    match (a_leaf, b_leaf) {
657        (true, true) => {
658            pairs.push(TriPair {
659                tri_a: a.nodes[na].tri_idx,
660                tri_b: b.nodes[nb].tri_idx,
661            });
662        }
663        (false, true) => {
664            mesh_broad_phase_recursive(a, b, a.nodes[na].left, nb, pairs);
665            mesh_broad_phase_recursive(a, b, a.nodes[na].right, nb, pairs);
666        }
667        (true, false) => {
668            mesh_broad_phase_recursive(a, b, na, b.nodes[nb].left, pairs);
669            mesh_broad_phase_recursive(a, b, na, b.nodes[nb].right, pairs);
670        }
671        (false, false) => {
672            // Descend into the larger node
673            let sa = a.nodes[na].aabb.surface_area();
674            let sb = b.nodes[nb].aabb.surface_area();
675            if sa >= sb {
676                mesh_broad_phase_recursive(a, b, a.nodes[na].left, nb, pairs);
677                mesh_broad_phase_recursive(a, b, a.nodes[na].right, nb, pairs);
678            } else {
679                mesh_broad_phase_recursive(a, b, na, b.nodes[nb].left, pairs);
680                mesh_broad_phase_recursive(a, b, na, b.nodes[nb].right, pairs);
681            }
682        }
683    }
684}
685
686/// Narrow phase: run triangle-triangle tests on candidate pairs.
687pub fn mesh_narrow_phase(a: &TriMeshBvh, b: &TriMeshBvh, pairs: &[TriPair]) -> Vec<TriPair> {
688    pairs
689        .iter()
690        .filter(|p| triangle_triangle_intersect(&a.triangles[p.tri_a], &b.triangles[p.tri_b]))
691        .cloned()
692        .collect()
693}
694
695/// Full mesh-mesh collision: broad + narrow phase.
696pub fn mesh_mesh_collision(a: &TriMeshBvh, b: &TriMeshBvh) -> Vec<TriPair> {
697    let candidates = mesh_broad_phase(a, b);
698    mesh_narrow_phase(a, b, &candidates)
699}
700
701// ─────────────────────────────────────────────────────────────────────────────
702// Cloth self-collision
703// ─────────────────────────────────────────────────────────────────────────────
704
705/// A cloth vertex.
706#[derive(Clone, Debug)]
707pub struct ClothVertex {
708    /// Current position.
709    pub pos: [f64; 3],
710    /// Previous position (for Verlet).
711    pub prev_pos: [f64; 3],
712    /// Velocity.
713    pub vel: [f64; 3],
714    /// Mass.
715    pub mass: f64,
716    /// Is this vertex fixed (pinned)?
717    pub pinned: bool,
718}
719
720impl ClothVertex {
721    /// Create a new [`ClothVertex`].
722    pub fn new(pos: [f64; 3], mass: f64) -> Self {
723        Self {
724            pos,
725            prev_pos: pos,
726            vel: [0.0; 3],
727            mass,
728            pinned: false,
729        }
730    }
731}
732
733/// A cloth edge constraint.
734#[derive(Clone, Debug)]
735pub struct ClothEdge {
736    /// Index of first vertex.
737    pub i: usize,
738    /// Index of second vertex.
739    pub j: usize,
740    /// Rest length.
741    pub rest_length: f64,
742    /// Stiffness ∈ \[0, 1\].
743    pub stiffness: f64,
744}
745
746impl ClothEdge {
747    /// Create a new [`ClothEdge`].
748    pub fn new(i: usize, j: usize, rest_length: f64, stiffness: f64) -> Self {
749        Self {
750            i,
751            j,
752            rest_length,
753            stiffness,
754        }
755    }
756
757    /// Project constraint (PBD-style).
758    pub fn project(&self, verts: &mut [ClothVertex]) {
759        if verts[self.i].pinned && verts[self.j].pinned {
760            return;
761        }
762        let pi = verts[self.i].pos;
763        let pj = verts[self.j].pos;
764        let diff = vec3_sub(pi, pj);
765        let l = vec3_len(diff);
766        if l < 1e-15 {
767            return;
768        }
769        let correction = self.stiffness * (l - self.rest_length) / l;
770        let delta = vec3_scale(diff, correction);
771        let wi = if verts[self.i].pinned {
772            0.0
773        } else {
774            1.0 / verts[self.i].mass
775        };
776        let wj = if verts[self.j].pinned {
777            0.0
778        } else {
779            1.0 / verts[self.j].mass
780        };
781        let w_sum = wi + wj;
782        if w_sum < 1e-30 {
783            return;
784        }
785        if !verts[self.i].pinned {
786            let c = vec3_scale(delta, -wi / w_sum);
787            verts[self.i].pos = vec3_add(verts[self.i].pos, c);
788        }
789        if !verts[self.j].pinned {
790            let c = vec3_scale(delta, wj / w_sum);
791            verts[self.j].pos = vec3_add(verts[self.j].pos, c);
792        }
793    }
794}
795
796/// Cloth self-collision response: push overlapping vertices apart.
797///
798/// Applies a position correction when two vertices from non-adjacent triangles
799/// come closer than `thickness`.
800pub fn cloth_self_collision(
801    vertices: &mut [ClothVertex],
802    triangles: &[[usize; 3]],
803    thickness: f64,
804) -> usize {
805    let mut n_collisions = 0;
806    let n = vertices.len();
807    for i in 0..n {
808        for j in (i + 1)..n {
809            // Skip if vertices share a triangle
810            let shared = triangles.iter().any(|t| {
811                (t[0] == i || t[1] == i || t[2] == i) && (t[0] == j || t[1] == j || t[2] == j)
812            });
813            if shared {
814                continue;
815            }
816            let diff = vec3_sub(vertices[i].pos, vertices[j].pos);
817            let dist = vec3_len(diff);
818            if dist < thickness && dist > 1e-15 {
819                let push = vec3_scale(vec3_norm(diff), (thickness - dist) * 0.5);
820                if !vertices[i].pinned {
821                    vertices[i].pos = vec3_add(vertices[i].pos, push);
822                }
823                if !vertices[j].pinned {
824                    vertices[j].pos = vec3_sub(vertices[j].pos, push);
825                }
826                n_collisions += 1;
827            }
828        }
829    }
830    n_collisions
831}
832
833// ─────────────────────────────────────────────────────────────────────────────
834// Continuous collision detection (CCD) for deformables
835// ─────────────────────────────────────────────────────────────────────────────
836
837/// CCD result for a vertex-triangle collision.
838#[derive(Clone, Debug)]
839pub struct CcdHit {
840    /// Time of impact ∈ \[0, 1\].
841    pub toi: f64,
842    /// Impact normal.
843    pub normal: [f64; 3],
844    /// Barycentric u coordinate on triangle.
845    pub u: f64,
846    /// Barycentric v coordinate on triangle.
847    pub v: f64,
848}
849
850/// Continuous collision detection between a moving vertex and a moving triangle.
851///
852/// Returns `Some(CcdHit)` if the vertex trajectory intersects the triangle
853/// within time interval \[0, 1\].
854///
855/// Uses a cubic polynomial root-finding approach.
856pub fn vertex_triangle_ccd(
857    vp0: [f64; 3],
858    vp1: [f64; 3], // vertex start/end
859    tp0: [f64; 3],
860    tp1: [f64; 3], // tri v0 start/end
861    tq0: [f64; 3],
862    tq1: [f64; 3], // tri v1 start/end
863    tr0: [f64; 3],
864    tr1: [f64; 3], // tri v2 start/end
865) -> Option<CcdHit> {
866    // Linear interpolation
867    let v_at = |t: f64| vec3_lerp(vp0, vp1, t);
868    let p_at = |t: f64| vec3_lerp(tp0, tp1, t);
869    let q_at = |t: f64| vec3_lerp(tq0, tq1, t);
870    let r_at = |t: f64| vec3_lerp(tr0, tr1, t);
871
872    // Sample N times and find first crossing
873    const N: usize = 64;
874    let mut prev_side: Option<f64> = None;
875
876    for step in 0..=N {
877        let t = step as f64 / N as f64;
878        let v = v_at(t);
879        let p = p_at(t);
880        let q = q_at(t);
881        let r = r_at(t);
882        let n = vec3_cross(vec3_sub(q, p), vec3_sub(r, p));
883        let dist = vec3_dot(vec3_sub(v, p), n);
884        if let Some(prev) = prev_side
885            && prev * dist < 0.0
886        {
887            // Sign change: potential intersection
888            let tri = Triangle::new(p, q, r);
889            let normal = vec3_norm(n);
890            // Check if vertex projects inside triangle
891            let bary = point_triangle_barycentric(v, &tri);
892            if bary[0] >= 0.0 && bary[1] >= 0.0 && bary[2] >= 0.0 {
893                let toi = (step as f64 - 1.0) / N as f64;
894                return Some(CcdHit {
895                    toi,
896                    normal,
897                    u: bary[0],
898                    v: bary[1],
899                });
900            }
901        }
902        prev_side = Some(dist);
903    }
904    None
905}
906
907/// Compute barycentric coordinates of point p projected onto triangle.
908pub fn point_triangle_barycentric(p: [f64; 3], tri: &Triangle) -> [f64; 3] {
909    let v0 = vec3_sub(tri.v1, tri.v0);
910    let v1 = vec3_sub(tri.v2, tri.v0);
911    let v2 = vec3_sub(p, tri.v0);
912    let d00 = vec3_dot(v0, v0);
913    let d01 = vec3_dot(v0, v1);
914    let d11 = vec3_dot(v1, v1);
915    let d20 = vec3_dot(v2, v0);
916    let d21 = vec3_dot(v2, v1);
917    let denom = d00 * d11 - d01 * d01;
918    if denom.abs() < 1e-30 {
919        return [1.0 / 3.0; 3];
920    }
921    let v = (d11 * d20 - d01 * d21) / denom;
922    let w = (d00 * d21 - d01 * d20) / denom;
923    let u = 1.0 - v - w;
924    [v, w, u]
925}
926
927// ─────────────────────────────────────────────────────────────────────────────
928// Proximity query: signed distance field
929// ─────────────────────────────────────────────────────────────────────────────
930
931/// Compute the squared distance from point `p` to the triangle `tri`.
932pub fn point_triangle_dist_sq(p: [f64; 3], tri: &Triangle) -> f64 {
933    let ab = vec3_sub(tri.v1, tri.v0);
934    let ac = vec3_sub(tri.v2, tri.v0);
935    let ap = vec3_sub(p, tri.v0);
936
937    let d1 = vec3_dot(ab, ap);
938    let d2 = vec3_dot(ac, ap);
939    if d1 <= 0.0 && d2 <= 0.0 {
940        return vec3_len_sq(ap);
941    }
942
943    let bp = vec3_sub(p, tri.v1);
944    let d3 = vec3_dot(ab, bp);
945    let d4 = vec3_dot(ac, bp);
946    if d3 >= 0.0 && d4 <= d3 {
947        return vec3_len_sq(bp);
948    }
949
950    let cp = vec3_sub(p, tri.v2);
951    let d5 = vec3_dot(ab, cp);
952    let d6 = vec3_dot(ac, cp);
953    if d6 >= 0.0 && d5 <= d6 {
954        return vec3_len_sq(cp);
955    }
956
957    let vc = d1 * d4 - d3 * d2;
958    if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 {
959        let v = d1 / (d1 - d3);
960        let proj = vec3_add(tri.v0, vec3_scale(ab, v));
961        return vec3_len_sq(vec3_sub(p, proj));
962    }
963
964    let vb = d5 * d2 - d1 * d6;
965    if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 {
966        let w = d2 / (d2 - d6);
967        let proj = vec3_add(tri.v0, vec3_scale(ac, w));
968        return vec3_len_sq(vec3_sub(p, proj));
969    }
970
971    let va = d3 * d6 - d5 * d4;
972    if va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0 {
973        let w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
974        let proj = vec3_add(tri.v1, vec3_scale(vec3_sub(tri.v2, tri.v1), w));
975        return vec3_len_sq(vec3_sub(p, proj));
976    }
977
978    let denom = 1.0 / (va + vb + vc);
979    let v = vb * denom;
980    let w = vc * denom;
981    let proj = vec3_add(tri.v0, vec3_add(vec3_scale(ab, v), vec3_scale(ac, w)));
982    vec3_len_sq(vec3_sub(p, proj))
983}
984
985/// Compute the closest point on a triangle to point p.
986pub fn closest_point_on_triangle(p: [f64; 3], tri: &Triangle) -> [f64; 3] {
987    let bary = point_triangle_barycentric(p, tri);
988    let u = bary[0].clamp(0.0, 1.0);
989    let v = bary[1].clamp(0.0, 1.0);
990    let w_total = u + v;
991    let (u, v) = if w_total > 1.0 {
992        (u / w_total, v / w_total)
993    } else {
994        (u, v)
995    };
996    tri.barycentric_point(u, v)
997}
998
999/// Build a proximity distance field on a grid relative to a triangle mesh.
1000pub fn build_proximity_field(
1001    mesh: &[Triangle],
1002    origin: [f64; 3],
1003    nx: usize,
1004    ny: usize,
1005    nz: usize,
1006    dx: f64,
1007) -> Vec<f64> {
1008    let total = nx * ny * nz;
1009    let mut field = vec![f64::INFINITY; total];
1010    for iz in 0..nz {
1011        for iy in 0..ny {
1012            for ix in 0..nx {
1013                let p = [
1014                    origin[0] + ix as f64 * dx,
1015                    origin[1] + iy as f64 * dx,
1016                    origin[2] + iz as f64 * dx,
1017                ];
1018                let mut min_d2 = f64::INFINITY;
1019                for tri in mesh {
1020                    let d2 = point_triangle_dist_sq(p, tri);
1021                    if d2 < min_d2 {
1022                        min_d2 = d2;
1023                    }
1024                }
1025                field[ix * ny * nz + iy * nz + iz] = min_d2.sqrt();
1026            }
1027        }
1028    }
1029    field
1030}
1031
1032// ─────────────────────────────────────────────────────────────────────────────
1033// Swept triangle vs static mesh
1034// ─────────────────────────────────────────────────────────────────────────────
1035
1036/// Test a swept triangle (linear motion by displacement `d`) against a static mesh BVH.
1037///
1038/// Returns the earliest time of impact ∈ \[0, 1\].
1039pub fn swept_triangle_vs_mesh(
1040    moving_tri: &Triangle,
1041    displacement: [f64; 3],
1042    static_mesh: &TriMeshBvh,
1043) -> Option<f64> {
1044    // Build AABB of swept region
1045    let mut swept_aabb = moving_tri.aabb();
1046    let tri_end = Triangle::new(
1047        vec3_add(moving_tri.v0, displacement),
1048        vec3_add(moving_tri.v1, displacement),
1049        vec3_add(moving_tri.v2, displacement),
1050    );
1051    swept_aabb.expand_aabb(&tri_end.aabb());
1052
1053    // Broad phase
1054    let candidates = static_mesh.query_aabb(&swept_aabb);
1055    if candidates.is_empty() {
1056        return None;
1057    }
1058
1059    let mut earliest = f64::INFINITY;
1060
1061    for &tri_idx in &candidates {
1062        let static_tri = &static_mesh.triangles[tri_idx];
1063        // CCD: moving triangle v0 vs static triangle
1064        for &mv in &[moving_tri.v0, moving_tri.v1, moving_tri.v2] {
1065            let mv_end = vec3_add(mv, displacement);
1066            let hit = vertex_triangle_ccd(
1067                mv,
1068                mv_end,
1069                static_tri.v0,
1070                static_tri.v0,
1071                static_tri.v1,
1072                static_tri.v1,
1073                static_tri.v2,
1074                static_tri.v2,
1075            );
1076            if let Some(h) = hit
1077                && h.toi < earliest
1078            {
1079                earliest = h.toi;
1080            }
1081        }
1082    }
1083
1084    if earliest.is_finite() {
1085        Some(earliest)
1086    } else {
1087        None
1088    }
1089}
1090
1091// ─────────────────────────────────────────────────────────────────────────────
1092// Penetration depth estimation
1093// ─────────────────────────────────────────────────────────────────────────────
1094
1095/// Estimate the penetration depth between two overlapping meshes using
1096/// a sampling approach on the contact region.
1097pub fn penetration_depth_estimate(a: &TriMeshBvh, b: &TriMeshBvh, n_samples: usize) -> f64 {
1098    let pairs = mesh_mesh_collision(a, b);
1099    if pairs.is_empty() {
1100        return 0.0;
1101    }
1102
1103    let mut max_depth = 0.0_f64;
1104    for pair in &pairs {
1105        let tri_a = &a.triangles[pair.tri_a];
1106        let tri_b = &b.triangles[pair.tri_b];
1107        // Sample points on tri_a and measure distance to tri_b's plane
1108        for k in 0..n_samples {
1109            let t = k as f64 / (n_samples as f64 - 1.0).max(1.0);
1110            let u = t * 0.5;
1111            let v = (1.0 - t) * 0.5;
1112            let p = tri_a.barycentric_point(u, v);
1113            let d2 = point_triangle_dist_sq(p, tri_b);
1114            if d2 > max_depth {
1115                max_depth = d2;
1116            }
1117        }
1118    }
1119    max_depth.sqrt()
1120}
1121
1122// ─────────────────────────────────────────────────────────────────────────────
1123// Contact manifold generation for meshes
1124// ─────────────────────────────────────────────────────────────────────────────
1125
1126/// A contact point for mesh collisions.
1127#[derive(Clone, Debug)]
1128pub struct MeshContact {
1129    /// Contact position in world space.
1130    pub position: [f64; 3],
1131    /// Contact normal (pointing from B to A).
1132    pub normal: [f64; 3],
1133    /// Penetration depth.
1134    pub depth: f64,
1135    /// Triangle index in mesh A.
1136    pub tri_a: usize,
1137    /// Triangle index in mesh B.
1138    pub tri_b: usize,
1139}
1140
1141/// A contact manifold for two colliding meshes.
1142#[derive(Clone, Debug, Default)]
1143pub struct MeshContactManifold {
1144    /// Contact points.
1145    pub contacts: Vec<MeshContact>,
1146    /// Overall contact normal (average).
1147    pub avg_normal: [f64; 3],
1148    /// Maximum penetration depth.
1149    pub max_depth: f64,
1150}
1151
1152impl MeshContactManifold {
1153    /// Create an empty manifold.
1154    pub fn new() -> Self {
1155        Self::default()
1156    }
1157
1158    /// Add a contact point.
1159    pub fn add_contact(&mut self, c: MeshContact) {
1160        self.max_depth = self.max_depth.max(c.depth);
1161        self.contacts.push(c);
1162        self.update_avg_normal();
1163    }
1164
1165    fn update_avg_normal(&mut self) {
1166        if self.contacts.is_empty() {
1167            return;
1168        }
1169        let mut n = [0.0; 3];
1170        for c in &self.contacts {
1171            n = vec3_add(n, c.normal);
1172        }
1173        self.avg_normal = vec3_norm(n);
1174    }
1175
1176    /// Number of contact points.
1177    pub fn num_contacts(&self) -> usize {
1178        self.contacts.len()
1179    }
1180}
1181
1182/// Generate a contact manifold for two colliding mesh BVHs.
1183pub fn generate_mesh_contact_manifold(a: &TriMeshBvh, b: &TriMeshBvh) -> MeshContactManifold {
1184    let pairs = mesh_mesh_collision(a, b);
1185    let mut manifold = MeshContactManifold::new();
1186
1187    for pair in &pairs {
1188        let tri_a = &a.triangles[pair.tri_a];
1189        let tri_b = &b.triangles[pair.tri_b];
1190
1191        let na = tri_a.normal();
1192        let nb = tri_b.normal();
1193
1194        // Use the average normal
1195        let normal = vec3_norm(vec3_sub(na, nb));
1196
1197        // Contact position: midpoint of centroids
1198        let ca = tri_a.centroid();
1199        let cb = tri_b.centroid();
1200        let position = vec3_scale(vec3_add(ca, cb), 0.5);
1201
1202        // Depth: distance from centroid of a to plane of b
1203        let n2 = tri_b.normal();
1204        let d = vec3_dot(n2, vec3_sub(ca, tri_b.v0)).abs();
1205
1206        manifold.add_contact(MeshContact {
1207            position,
1208            normal,
1209            depth: d,
1210            tri_a: pair.tri_a,
1211            tri_b: pair.tri_b,
1212        });
1213    }
1214    manifold
1215}
1216
1217// ─────────────────────────────────────────────────────────────────────────────
1218// Deformable mesh (BVH + vertex array)
1219// ─────────────────────────────────────────────────────────────────────────────
1220
1221/// A deformable triangle mesh backed by a dynamic vertex array and a refittable BVH.
1222pub struct DeformableMesh {
1223    /// Vertex positions.
1224    pub vertices: Vec<[f64; 3]>,
1225    /// Triangle index triples.
1226    pub face_indices: Vec<[usize; 3]>,
1227    /// The BVH (refitted each step).
1228    pub bvh: TriMeshBvh,
1229}
1230
1231impl DeformableMesh {
1232    /// Build from vertices and face indices.
1233    pub fn build(vertices: Vec<[f64; 3]>, face_indices: Vec<[usize; 3]>) -> Self {
1234        let triangles: Vec<Triangle> = face_indices
1235            .iter()
1236            .map(|&[a, b, c]| Triangle::new(vertices[a], vertices[b], vertices[c]))
1237            .collect();
1238        let bvh = TriMeshBvh::build(triangles);
1239        Self {
1240            vertices,
1241            face_indices,
1242            bvh,
1243        }
1244    }
1245
1246    /// Update vertex position and refit BVH.
1247    pub fn update_vertex(&mut self, idx: usize, new_pos: [f64; 3]) {
1248        self.vertices[idx] = new_pos;
1249        // Sync triangles
1250        for (fi, &[a, b, c]) in self.face_indices.iter().enumerate() {
1251            if a == idx || b == idx || c == idx {
1252                self.bvh.triangles[fi] =
1253                    Triangle::new(self.vertices[a], self.vertices[b], self.vertices[c]);
1254            }
1255        }
1256        self.bvh.refit();
1257    }
1258
1259    /// Apply a displacement to all vertices and refit.
1260    pub fn apply_displacement(&mut self, displacements: &[[f64; 3]]) {
1261        for (i, d) in displacements.iter().enumerate() {
1262            if i < self.vertices.len() {
1263                self.vertices[i] = vec3_add(self.vertices[i], *d);
1264            }
1265        }
1266        for (fi, &[a, b, c]) in self.face_indices.iter().enumerate() {
1267            self.bvh.triangles[fi] =
1268                Triangle::new(self.vertices[a], self.vertices[b], self.vertices[c]);
1269        }
1270        self.bvh.refit();
1271    }
1272
1273    /// Number of faces.
1274    pub fn num_faces(&self) -> usize {
1275        self.face_indices.len()
1276    }
1277}
1278
1279// ─────────────────────────────────────────────────────────────────────────────
1280// Signed Distance Field (SDF) helpers
1281// ─────────────────────────────────────────────────────────────────────────────
1282
1283/// A simple analytic SDF for a sphere.
1284pub fn sdf_sphere(p: [f64; 3], centre: [f64; 3], radius: f64) -> f64 {
1285    vec3_len(vec3_sub(p, centre)) - radius
1286}
1287
1288/// A simple analytic SDF for an AABB.
1289pub fn sdf_aabb(p: [f64; 3], aabb: &MeshAabb) -> f64 {
1290    let c = aabb.centre();
1291    let h = aabb.half_extents();
1292    let d = [
1293        (p[0] - c[0]).abs() - h[0],
1294        (p[1] - c[1]).abs() - h[1],
1295        (p[2] - c[2]).abs() - h[2],
1296    ];
1297    let outside = [d[0].max(0.0), d[1].max(0.0), d[2].max(0.0)];
1298    let inside = d[0].max(d[1]).max(d[2]).min(0.0);
1299    vec3_len(outside) + inside
1300}
1301
1302/// Sample the SDF of a triangle mesh on a uniform grid.
1303///
1304/// Uses the proximity field with sign estimated from face normals.
1305pub fn mesh_sdf_grid(
1306    mesh: &[Triangle],
1307    origin: [f64; 3],
1308    nx: usize,
1309    ny: usize,
1310    nz: usize,
1311    dx: f64,
1312) -> Vec<f64> {
1313    let total = nx * ny * nz;
1314    let mut field = vec![0.0_f64; total];
1315    for iz in 0..nz {
1316        for iy in 0..ny {
1317            for ix in 0..nx {
1318                let p = [
1319                    origin[0] + ix as f64 * dx,
1320                    origin[1] + iy as f64 * dx,
1321                    origin[2] + iz as f64 * dx,
1322                ];
1323                let mut min_d2 = f64::INFINITY;
1324                let mut closest_n = [0.0; 3];
1325                let mut closest_diff = [0.0; 3];
1326                for tri in mesh {
1327                    let d2 = point_triangle_dist_sq(p, tri);
1328                    if d2 < min_d2 {
1329                        min_d2 = d2;
1330                        closest_n = tri.normal();
1331                        closest_diff = vec3_sub(p, tri.centroid());
1332                    }
1333                }
1334                let sign = if vec3_dot(closest_diff, closest_n) >= 0.0 {
1335                    1.0
1336                } else {
1337                    -1.0
1338                };
1339                field[ix * ny * nz + iy * nz + iz] = sign * min_d2.sqrt();
1340            }
1341        }
1342    }
1343    field
1344}
1345
1346// ─────────────────────────────────────────────────────────────────────────────
1347// Tests
1348// ─────────────────────────────────────────────────────────────────────────────
1349
1350#[cfg(test)]
1351mod tests {
1352    use super::*;
1353
1354    // ── Vector math ───────────────────────────────────────────────────────────
1355
1356    #[test]
1357    fn test_vec3_dot_orthogonal() {
1358        assert!((vec3_dot([1.0, 0.0, 0.0], [0.0, 1.0, 0.0])).abs() < 1e-15);
1359    }
1360
1361    #[test]
1362    fn test_vec3_cross_unit() {
1363        let c = vec3_cross([1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
1364        assert!((c[2] - 1.0).abs() < 1e-15);
1365    }
1366
1367    #[test]
1368    fn test_vec3_norm_unit_length() {
1369        let n = vec3_norm([3.0, 4.0, 0.0]);
1370        assert!((vec3_len(n) - 1.0).abs() < 1e-14);
1371    }
1372
1373    #[test]
1374    fn test_vec3_lerp_midpoint() {
1375        let m = vec3_lerp([0.0; 3], [2.0, 2.0, 2.0], 0.5);
1376        for &c in &m {
1377            assert!((c - 1.0).abs() < 1e-14);
1378        }
1379    }
1380
1381    // ── AABB ──────────────────────────────────────────────────────────────────
1382
1383    #[test]
1384    fn test_aabb_overlaps() {
1385        let a = MeshAabb::new([0.0; 3], [1.0; 3]);
1386        let b = MeshAabb::new([0.5; 3], [1.5; 3]);
1387        assert!(a.overlaps(&b));
1388    }
1389
1390    #[test]
1391    fn test_aabb_not_overlaps() {
1392        let a = MeshAabb::new([0.0; 3], [1.0; 3]);
1393        let b = MeshAabb::new([2.0; 3], [3.0; 3]);
1394        assert!(!a.overlaps(&b));
1395    }
1396
1397    #[test]
1398    fn test_aabb_contains_point() {
1399        let a = MeshAabb::new([0.0; 3], [1.0; 3]);
1400        assert!(a.contains_point([0.5, 0.5, 0.5]));
1401        assert!(!a.contains_point([1.5, 0.5, 0.5]));
1402    }
1403
1404    #[test]
1405    fn test_aabb_from_triangle() {
1406        let a = MeshAabb::from_triangle([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
1407        assert!(a.min[2].abs() < 1e-15);
1408        assert!((a.max[0] - 1.0).abs() < 1e-15);
1409    }
1410
1411    #[test]
1412    fn test_aabb_fattened() {
1413        let a = MeshAabb::new([0.0; 3], [1.0; 3]);
1414        let fat = a.fattened(0.1);
1415        for i in 0..3 {
1416            assert!((fat.min[i] - (-0.1)).abs() < 1e-14);
1417            assert!((fat.max[i] - 1.1).abs() < 1e-14);
1418        }
1419    }
1420
1421    // ── Triangle ──────────────────────────────────────────────────────────────
1422
1423    #[test]
1424    fn test_triangle_normal_unit() {
1425        let tri = Triangle::new([0.0; 3], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
1426        let n = tri.normal();
1427        assert!((vec3_len(n) - 1.0).abs() < 1e-14);
1428    }
1429
1430    #[test]
1431    fn test_triangle_area() {
1432        let tri = Triangle::new([0.0; 3], [2.0, 0.0, 0.0], [0.0, 2.0, 0.0]);
1433        assert!((tri.area() - 2.0).abs() < 1e-14);
1434    }
1435
1436    #[test]
1437    fn test_triangle_centroid() {
1438        let tri = Triangle::new([0.0; 3], [3.0, 0.0, 0.0], [0.0, 3.0, 0.0]);
1439        let c = tri.centroid();
1440        assert!((c[0] - 1.0).abs() < 1e-14);
1441        assert!((c[1] - 1.0).abs() < 1e-14);
1442    }
1443
1444    // ── Möller-Trumbore ───────────────────────────────────────────────────────
1445
1446    #[test]
1447    fn test_ray_triangle_hit() {
1448        let tri = Triangle::new([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
1449        let hit = ray_triangle_mt([0.25, 0.25, 1.0], [0.0, 0.0, -1.0], &tri, 0.0, 10.0);
1450        assert!(hit.is_some(), "Should hit the triangle");
1451        let h = hit.unwrap();
1452        assert!((h.t - 1.0).abs() < 1e-10);
1453    }
1454
1455    #[test]
1456    fn test_ray_triangle_miss() {
1457        let tri = Triangle::new([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
1458        let hit = ray_triangle_mt([5.0, 5.0, 1.0], [0.0, 0.0, -1.0], &tri, 0.0, 10.0);
1459        assert!(hit.is_none());
1460    }
1461
1462    #[test]
1463    fn test_ray_triangle_back_face_culled_by_t() {
1464        // Ray in same direction, t should be negative → should miss
1465        let tri = Triangle::new([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
1466        let hit = ray_triangle_mt([0.25, 0.25, -1.0], [0.0, 0.0, -1.0], &tri, 0.0, 10.0);
1467        assert!(hit.is_none(), "Ray moving away from triangle");
1468    }
1469
1470    // ── Ray-AABB ──────────────────────────────────────────────────────────────
1471
1472    #[test]
1473    fn test_ray_aabb_hit() {
1474        let aabb = MeshAabb::new([0.0; 3], [1.0; 3]);
1475        assert!(ray_aabb_intersect(
1476            [0.5, 0.5, 2.0],
1477            [0.0, 0.0, -1.0],
1478            &aabb,
1479            0.0,
1480            10.0
1481        ));
1482    }
1483
1484    #[test]
1485    fn test_ray_aabb_miss() {
1486        let aabb = MeshAabb::new([0.0; 3], [1.0; 3]);
1487        assert!(!ray_aabb_intersect(
1488            [5.0, 5.0, 2.0],
1489            [0.0, 0.0, -1.0],
1490            &aabb,
1491            0.0,
1492            10.0
1493        ));
1494    }
1495
1496    // ── Triangle-Triangle ─────────────────────────────────────────────────────
1497
1498    #[test]
1499    fn test_tri_tri_intersect_coplanar_overlapping() {
1500        let t1 = Triangle::new([0.0, 0.0, 0.0], [2.0, 0.0, 0.0], [0.0, 2.0, 0.0]);
1501        let t2 = Triangle::new([1.0, 0.0, 0.0], [3.0, 0.0, 0.0], [1.0, 2.0, 0.0]);
1502        // These triangles share edge/overlap region
1503        let _ = triangle_triangle_intersect(&t1, &t2); // should not panic
1504    }
1505
1506    #[test]
1507    fn test_tri_tri_separated() {
1508        // t1 in xy-plane at z=0, t2 in xy-plane at z=2.0 (clearly separated by a gap)
1509        let t1 = Triangle::new([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
1510        let t2 = Triangle::new([0.0, 0.0, 2.0], [1.0, 0.0, 2.0], [0.0, 1.0, 2.0]);
1511        assert!(!triangle_triangle_intersect(&t1, &t2));
1512    }
1513
1514    // ── BVH build/query ───────────────────────────────────────────────────────
1515
1516    #[test]
1517    fn test_bvh_build_single_triangle() {
1518        let tris = vec![Triangle::new([0.0; 3], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0])];
1519        let bvh = TriMeshBvh::build(tris);
1520        assert_eq!(bvh.num_triangles(), 1);
1521        assert!(bvh.num_nodes() >= 1);
1522    }
1523
1524    #[test]
1525    fn test_bvh_build_multiple_triangles() {
1526        let tris: Vec<Triangle> = (0..8)
1527            .map(|i| {
1528                let x = i as f64;
1529                Triangle::new([x, 0.0, 0.0], [x + 1.0, 0.0, 0.0], [x, 1.0, 0.0])
1530            })
1531            .collect();
1532        let bvh = TriMeshBvh::build(tris);
1533        assert_eq!(bvh.num_triangles(), 8);
1534    }
1535
1536    #[test]
1537    fn test_bvh_ray_cast_hits() {
1538        let tris = vec![Triangle::new(
1539            [0.0, 0.0, 0.0],
1540            [1.0, 0.0, 0.0],
1541            [0.0, 1.0, 0.0],
1542        )];
1543        let bvh = TriMeshBvh::build(tris);
1544        let result = bvh.ray_cast([0.25, 0.25, 2.0], [0.0, 0.0, -1.0], 10.0);
1545        assert!(result.is_some());
1546    }
1547
1548    #[test]
1549    fn test_bvh_ray_cast_miss() {
1550        let tris = vec![Triangle::new(
1551            [0.0, 0.0, 0.0],
1552            [1.0, 0.0, 0.0],
1553            [0.0, 1.0, 0.0],
1554        )];
1555        let bvh = TriMeshBvh::build(tris);
1556        let result = bvh.ray_cast([5.0, 5.0, 2.0], [0.0, 0.0, -1.0], 10.0);
1557        assert!(result.is_none());
1558    }
1559
1560    #[test]
1561    fn test_bvh_query_aabb() {
1562        let tris: Vec<Triangle> = (0..4)
1563            .map(|i| {
1564                let x = i as f64 * 3.0;
1565                Triangle::new([x, 0.0, 0.0], [x + 1.0, 0.0, 0.0], [x, 1.0, 0.0])
1566            })
1567            .collect();
1568        let bvh = TriMeshBvh::build(tris);
1569        let q = MeshAabb::new([0.0; 3], [1.5, 1.5, 1.5]);
1570        let hits = bvh.query_aabb(&q);
1571        assert!(!hits.is_empty());
1572    }
1573
1574    // ── Mesh-mesh collision ───────────────────────────────────────────────────
1575
1576    #[test]
1577    fn test_mesh_broad_phase_overlapping() {
1578        let tris_a = vec![Triangle::new(
1579            [0.0, 0.0, 0.0],
1580            [1.0, 0.0, 0.0],
1581            [0.0, 1.0, 0.0],
1582        )];
1583        let tris_b = vec![Triangle::new(
1584            [0.3, 0.3, -0.1],
1585            [0.6, 0.3, 0.0],
1586            [0.3, 0.6, 0.0],
1587        )];
1588        let bvh_a = TriMeshBvh::build(tris_a);
1589        let bvh_b = TriMeshBvh::build(tris_b);
1590        let pairs = mesh_broad_phase(&bvh_a, &bvh_b);
1591        assert!(!pairs.is_empty());
1592    }
1593
1594    #[test]
1595    fn test_mesh_broad_phase_separated() {
1596        let tris_a = vec![Triangle::new([0.0; 3], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0])];
1597        let tris_b = vec![Triangle::new(
1598            [10.0, 0.0, 0.0],
1599            [11.0, 0.0, 0.0],
1600            [10.0, 1.0, 0.0],
1601        )];
1602        let bvh_a = TriMeshBvh::build(tris_a);
1603        let bvh_b = TriMeshBvh::build(tris_b);
1604        let pairs = mesh_broad_phase(&bvh_a, &bvh_b);
1605        assert!(pairs.is_empty());
1606    }
1607
1608    // ── Point-triangle distance ───────────────────────────────────────────────
1609
1610    #[test]
1611    fn test_point_triangle_dist_vertex_case() {
1612        let tri = Triangle::new([0.0; 3], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
1613        let d2 = point_triangle_dist_sq([-1.0, 0.0, 0.0], &tri);
1614        assert!((d2 - 1.0).abs() < 1e-10);
1615    }
1616
1617    #[test]
1618    fn test_point_triangle_dist_above() {
1619        let tri = Triangle::new([0.0; 3], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
1620        let d2 = point_triangle_dist_sq([0.25, 0.25, 2.0], &tri);
1621        assert!((d2 - 4.0).abs() < 1e-10);
1622    }
1623
1624    #[test]
1625    fn test_closest_point_on_triangle_inside() {
1626        let tri = Triangle::new([0.0; 3], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
1627        let cp = closest_point_on_triangle([0.25, 0.25, 1.0], &tri);
1628        // Should project onto the triangle plane
1629        assert!(
1630            (cp[2]).abs() < 0.1,
1631            "Closest point z should be near 0, got {}",
1632            cp[2]
1633        );
1634    }
1635
1636    // ── Cloth self-collision ──────────────────────────────────────────────────
1637
1638    #[test]
1639    fn test_cloth_self_collision_separates_vertices() {
1640        let mut verts = vec![
1641            ClothVertex::new([0.0, 0.0, 0.0], 1.0),
1642            ClothVertex::new([0.001, 0.0, 0.0], 1.0),
1643        ];
1644        let tris: Vec<[usize; 3]> = vec![];
1645        let n = cloth_self_collision(&mut verts, &tris, 0.05);
1646        assert!(n > 0);
1647        let dist = vec3_len(vec3_sub(verts[0].pos, verts[1].pos));
1648        assert!(dist >= 0.049, "Vertices should be separated, dist={}", dist);
1649    }
1650
1651    #[test]
1652    fn test_cloth_edge_constraint_projection() {
1653        let mut verts = vec![
1654            ClothVertex::new([0.0, 0.0, 0.0], 1.0),
1655            ClothVertex::new([2.0, 0.0, 0.0], 1.0),
1656        ];
1657        let edge = ClothEdge::new(0, 1, 1.0, 1.0);
1658        edge.project(&mut verts);
1659        let d = vec3_len(vec3_sub(verts[0].pos, verts[1].pos));
1660        assert!(
1661            (d - 1.0).abs() < 0.01,
1662            "After projection edge length should be ~1, got {}",
1663            d
1664        );
1665    }
1666
1667    // ── Deformable mesh ───────────────────────────────────────────────────────
1668
1669    #[test]
1670    fn test_deformable_mesh_build() {
1671        let verts = vec![[0.0; 3], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
1672        let faces = vec![[0, 1, 2]];
1673        let dm = DeformableMesh::build(verts, faces);
1674        assert_eq!(dm.num_faces(), 1);
1675    }
1676
1677    #[test]
1678    fn test_deformable_mesh_update_vertex() {
1679        let verts = vec![[0.0; 3], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
1680        let faces = vec![[0, 1, 2]];
1681        let mut dm = DeformableMesh::build(verts, faces);
1682        dm.update_vertex(0, [0.5, 0.5, 0.0]);
1683        assert!((dm.vertices[0][0] - 0.5).abs() < 1e-14);
1684    }
1685
1686    // ── SDF helpers ───────────────────────────────────────────────────────────
1687
1688    #[test]
1689    fn test_sdf_sphere_outside() {
1690        let d = sdf_sphere([2.0, 0.0, 0.0], [0.0; 3], 1.0);
1691        assert!((d - 1.0).abs() < 1e-14);
1692    }
1693
1694    #[test]
1695    fn test_sdf_sphere_inside() {
1696        let d = sdf_sphere([0.5, 0.0, 0.0], [0.0; 3], 1.0);
1697        assert!(d < 0.0, "Point inside sphere → negative SDF");
1698    }
1699
1700    #[test]
1701    fn test_sdf_aabb_outside() {
1702        let aabb = MeshAabb::new([0.0; 3], [1.0; 3]);
1703        let d = sdf_aabb([2.0, 0.5, 0.5], &aabb);
1704        assert!((d - 1.0).abs() < 1e-14);
1705    }
1706
1707    #[test]
1708    fn test_sdf_aabb_inside() {
1709        let aabb = MeshAabb::new([0.0; 3], [2.0; 3]);
1710        let d = sdf_aabb([1.0, 1.0, 1.0], &aabb);
1711        assert!(d < 0.0, "Centre of AABB → negative SDF");
1712    }
1713
1714    // ── Contact manifold ──────────────────────────────────────────────────────
1715
1716    #[test]
1717    fn test_contact_manifold_add_contact() {
1718        let mut m = MeshContactManifold::new();
1719        m.add_contact(MeshContact {
1720            position: [0.0; 3],
1721            normal: [0.0, 0.0, 1.0],
1722            depth: 0.05,
1723            tri_a: 0,
1724            tri_b: 0,
1725        });
1726        assert_eq!(m.num_contacts(), 1);
1727        assert!((m.max_depth - 0.05).abs() < 1e-14);
1728    }
1729
1730    #[test]
1731    fn test_generate_manifold_no_collision() {
1732        let tris_a = vec![Triangle::new([0.0; 3], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0])];
1733        let tris_b = vec![Triangle::new(
1734            [10.0, 0.0, 0.0],
1735            [11.0, 0.0, 0.0],
1736            [10.0, 1.0, 0.0],
1737        )];
1738        let bvh_a = TriMeshBvh::build(tris_a);
1739        let bvh_b = TriMeshBvh::build(tris_b);
1740        let manifold = generate_mesh_contact_manifold(&bvh_a, &bvh_b);
1741        assert_eq!(manifold.num_contacts(), 0);
1742    }
1743
1744    // ── Penetration depth ─────────────────────────────────────────────────────
1745
1746    #[test]
1747    fn test_penetration_depth_no_overlap() {
1748        let tris_a = vec![Triangle::new([0.0; 3], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0])];
1749        let tris_b = vec![Triangle::new(
1750            [5.0, 0.0, 0.0],
1751            [6.0, 0.0, 0.0],
1752            [5.0, 1.0, 0.0],
1753        )];
1754        let a = TriMeshBvh::build(tris_a);
1755        let b = TriMeshBvh::build(tris_b);
1756        let d = penetration_depth_estimate(&a, &b, 4);
1757        assert!((d - 0.0).abs() < 1e-14);
1758    }
1759
1760    // ── BVH refit ─────────────────────────────────────────────────────────────
1761
1762    #[test]
1763    fn test_bvh_refit_after_vertex_move() {
1764        let verts = vec![[0.0; 3], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
1765        let faces = vec![[0, 1, 2]];
1766        let mut dm = DeformableMesh::build(verts, faces);
1767        dm.update_vertex(1, [5.0, 0.0, 0.0]);
1768        let root_aabb = &dm.bvh.nodes[dm.bvh.root].aabb;
1769        assert!(root_aabb.max[0] >= 4.9, "BVH should cover moved vertex");
1770    }
1771
1772    // ── Proximity field ───────────────────────────────────────────────────────
1773
1774    #[test]
1775    fn test_proximity_field_near_triangle() {
1776        let tris = vec![Triangle::new([0.0; 3], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0])];
1777        let field = build_proximity_field(&tris, [0.0, 0.0, 0.0], 3, 3, 3, 0.5);
1778        // Point (0,0,0) is on the triangle → dist ≈ 0
1779        assert!(field[0] < 1e-10, "Point on triangle → zero distance");
1780    }
1781
1782    // ── Barycentric coords ────────────────────────────────────────────────────
1783
1784    #[test]
1785    fn test_barycentric_centroid() {
1786        let tri = Triangle::new([0.0; 3], [3.0, 0.0, 0.0], [0.0, 3.0, 0.0]);
1787        let c = tri.centroid();
1788        let bary = point_triangle_barycentric(c, &tri);
1789        // All barycentric coords should be near 1/3
1790        assert!((bary[0] + bary[1] + bary[2] - 1.0).abs() < 0.1);
1791    }
1792}