Skip to main content

manifold_rust/
collider.rs

1// Copyright 2026 Lars Brubaker
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Collider — BVH-based broadphase collision detection.
16// Uses a radix tree built from Morton codes for O(n log n) queries,
17// matching the C++ Manifold implementation.
18
19use crate::impl_mesh::ManifoldImpl;
20use crate::linalg::{cross, distance2, dot, Mat3x4, Vec3};
21use crate::sort::{get_face_box_morton, morton_code};
22use crate::types::Box as BBox;
23
24// Node encoding (matches C++):
25// - Even indices are leaf nodes: leaf i -> node 2*i
26// - Odd indices are internal nodes: internal i -> node 2*i + 1
27// - Root is always node index 1 (internal node 0)
28const K_ROOT: i32 = 1;
29
30fn leaf_to_node(leaf: i32) -> i32 {
31    leaf * 2
32}
33fn node_to_leaf(node: i32) -> i32 {
34    node / 2
35}
36fn internal_to_node(internal: i32) -> i32 {
37    internal * 2 + 1
38}
39fn node_to_internal(node: i32) -> i32 {
40    node / 2
41}
42fn is_leaf(node: i32) -> bool {
43    node % 2 == 0
44}
45fn is_internal(node: i32) -> bool {
46    node % 2 == 1
47}
48
49/// Face BVH matching the C++ Collider's storage layout exactly: one interleaved
50/// node array (leaves at even indices, internals at odd), the radix-tree
51/// topology, and parent links for bottom-up box refits. Leaf boxes live inside
52/// `node_bbox` — no separate leaf copy, and the morton codes are consumed at
53/// construction rather than stored, which matters because a collider is cached
54/// on every ManifoldImpl.
55#[derive(Clone, Debug, Default)]
56pub struct Collider {
57    node_bbox: Vec<BBox>,             // AABBs for all nodes (2*num_leaves - 1)
58    node_parent: Vec<i32>,            // parent node per node (-1 at root)
59    internal_children: Vec<[i32; 2]>, // child pairs for internal nodes (num_leaves - 1)
60}
61
62impl Collider {
63    /// Create a new Collider from leaf bounding boxes and morton codes.
64    /// Like the C++ constructor, leaves must already be sorted by morton code
65    /// — every production caller builds them from a morton-sorted source
66    /// (sort_geometry sorts faces, merge sorts open verts).
67    pub fn new(leaf_bbox: Vec<BBox>, leaf_morton: Vec<u32>) -> Self {
68        debug_assert_eq!(leaf_bbox.len(), leaf_morton.len());
69        debug_assert!(
70            leaf_morton.windows(2).all(|w| w[0] <= w[1]),
71            "Collider leaves must be pre-sorted by morton code"
72        );
73        let n = leaf_bbox.len();
74        if n == 0 {
75            return Self::default();
76        }
77        let num_nodes = 2 * n - 1;
78        let mut collider = Self {
79            node_bbox: vec![BBox::default(); num_nodes],
80            node_parent: vec![-1; num_nodes],
81            internal_children: vec![[-1, -1]; n.saturating_sub(1)],
82        };
83        collider.create_radix_tree(&leaf_morton);
84        collider.update_boxes(leaf_bbox);
85        collider
86    }
87
88    pub(crate) fn num_leaves(&self) -> usize {
89        if self.node_bbox.is_empty() {
90            0
91        } else {
92            self.internal_children.len() + 1
93        }
94    }
95
96
97    /// Build radix tree from sorted Morton codes (matches C++ CreateRadixTree).
98    /// Only fills in topology (internal_children, node_parent); boxes are
99    /// populated afterwards by update_boxes.
100    fn create_radix_tree(&mut self, leaf_morton: &[u32]) {
101        let num_leaves = leaf_morton.len();
102        if num_leaves <= 1 {
103            return;
104        }
105        let num_internal = num_leaves - 1;
106
107        const K_INITIAL_LENGTH: i32 = 128;
108        const K_LENGTH_MULTIPLE: i32 = 4;
109
110        // Helper: count leading zeros of XOR of two morton codes
111        let prefix_length = |i: i32, j: i32| -> i32 {
112            if j < 0 || j >= num_leaves as i32 {
113                return -1;
114            }
115            let mi = leaf_morton[i as usize];
116            let mj = leaf_morton[j as usize];
117            let xor = mi ^ mj;
118            if xor == 0 {
119                // Same morton code, use index as tiebreaker (matches C++ clz)
120                32 + ((i as u32 ^ j as u32).leading_zeros() as i32)
121            } else {
122                xor.leading_zeros() as i32
123            }
124        };
125
126        // RangeEnd: find the other end of the range for internal node i
127        let range_end = |i: i32| -> i32 {
128            let dir_val = prefix_length(i, i + 1) - prefix_length(i, i - 1);
129            let dir = if dir_val > 0 { 1i32 } else if dir_val < 0 { -1i32 } else { -1i32 };
130            let common_prefix = prefix_length(i, i - dir);
131            let mut max_length = K_INITIAL_LENGTH;
132            while prefix_length(i, i + dir * max_length) > common_prefix {
133                max_length *= K_LENGTH_MULTIPLE;
134            }
135            let mut length = 0i32;
136            let mut step = max_length / 2;
137            while step > 0 {
138                if prefix_length(i, i + dir * (length + step)) > common_prefix {
139                    length += step;
140                }
141                step /= 2;
142            }
143            i + dir * length
144        };
145
146        // FindSplit: find where the split occurs within [first, last]
147        let find_split = |first: i32, last: i32| -> i32 {
148            let common_prefix = prefix_length(first, last);
149            let mut split = first;
150            let mut step = last - first;
151            loop {
152                step = (step + 1) >> 1; // divide by 2, rounding up
153                let new_split = split + step;
154                if new_split < last {
155                    let split_prefix = prefix_length(first, new_split);
156                    if split_prefix > common_prefix {
157                        split = new_split;
158                    }
159                }
160                if step <= 1 { break; }
161            }
162            split
163        };
164
165        // For each internal node, find its range and split point
166        for internal in 0..num_internal {
167            let i = internal as i32;
168            let mut first = i;
169            let mut last = range_end(i);
170            if first > last {
171                std::mem::swap(&mut first, &mut last);
172            }
173
174            let split = find_split(first, last);
175
176            // Assign children (matches C++ exactly)
177            let child1 = if split == first {
178                leaf_to_node(split)
179            } else {
180                internal_to_node(split)
181            };
182            // C++ increments split before computing child2
183            let split2 = split + 1;
184            let child2 = if split2 == last {
185                leaf_to_node(split2)
186            } else if (split2 as usize) < num_internal {
187                internal_to_node(split2)
188            } else {
189                // Degenerate case: split2 exceeds internal node range.
190                // This mirrors C++ UB when dir=0 in RangeEnd (clz(0) is UB).
191                // Make child2 = child1 so traversal still works.
192                child1
193            };
194
195            self.internal_children[internal] = [child1, child2];
196            let node = internal_to_node(i);
197            self.node_parent[child1 as usize] = node;
198            self.node_parent[child2 as usize] = node;
199        }
200    }
201
202    /// Refit internal boxes bottom-up from the leaf boxes already present in
203    /// node_bbox, using the parent links (matches C++ BuildInternalBoxes).
204    fn build_internal_boxes(&mut self) {
205        let num_leaves = self.num_leaves();
206        let num_internal = self.internal_children.len();
207        if num_internal == 0 {
208            return;
209        }
210        let mut counter = vec![0u32; num_internal];
211
212        for leaf in 0..num_leaves {
213            let mut node = leaf_to_node(leaf as i32);
214            loop {
215                let parent = self.node_parent[node as usize];
216                if parent < 0 {
217                    break; // at root
218                }
219                let internal = node_to_internal(parent);
220                if internal < 0 || internal >= num_internal as i32 {
221                    break;
222                }
223                let idx = internal as usize;
224                counter[idx] += 1;
225                if counter[idx] < 2 {
226                    break; // wait for second child
227                }
228                // Both children ready, compute union
229                let [c1, c2] = self.internal_children[idx];
230                let b1 = self.node_bbox[c1 as usize];
231                let b2 = self.node_bbox[c2 as usize];
232                self.node_bbox[parent as usize] = b1.union_box(&b2);
233                node = parent;
234            }
235        }
236    }
237
238    /// Run a single query box against the BVH, invoking `record(query_idx,
239    /// leaf_idx)` per overlap. Same traversal as `collisions_fn` for one
240    /// index — the per-query entry point for parallel callers (`&self` only,
241    /// so queries can run concurrently with thread-local recording).
242    pub fn collisions_one<R: FnMut(usize, usize)>(
243        &self,
244        query: &BBox,
245        query_idx: usize,
246        mut record: R,
247    ) {
248        if query.is_empty() {
249            return;
250        }
251        // No internal nodes (0-1 leaves): no collisions, matching C++
252        // Collisions' early return on internalChildren_.empty().
253        if self.internal_children.is_empty() {
254            return;
255        }
256        self.traverse_bvh(query, query_idx, false, &mut record);
257    }
258
259    /// BVH-accelerated collision query with function-generated query boxes.
260    /// For each query index 0..n, calls query_box_fn(i) to get the query AABB,
261    /// then traverses the BVH to find overlapping leaves.
262    pub fn collisions_fn<F, R>(
263        &self,
264        query_box_fn: F,
265        n: usize,
266        mut record: R,
267    ) where
268        F: Fn(usize) -> BBox,
269        R: FnMut(usize, usize),
270    {
271        if self.internal_children.is_empty() {
272            return;
273        }
274
275        for query_idx in 0..n {
276            let query = query_box_fn(query_idx);
277            if query.is_empty() {
278                continue;
279            }
280            self.traverse_bvh(&query, query_idx, false, &mut record);
281        }
282    }
283
284    /// BVH-accelerated collision query with pre-computed query boxes.
285    pub fn collisions_with_boxes<F: FnMut(usize, usize)>(
286        &self,
287        queries: &[BBox],
288        self_collision: bool,
289        mut record: F,
290    ) {
291        if self.internal_children.is_empty() {
292            return;
293        }
294
295        for (query_idx, query) in queries.iter().enumerate() {
296            if query.is_empty() {
297                continue;
298            }
299            self.traverse_bvh(query, query_idx, self_collision, &mut record);
300        }
301    }
302
303    /// Point-based collision query using BVH.
304    pub fn collisions_point<F, R>(
305        &self,
306        point_fn: F,
307        n: usize,
308        mut record: R,
309    ) where
310        F: Fn(usize) -> Vec3,
311        R: FnMut(usize, usize),
312    {
313        if self.internal_children.is_empty() {
314            return;
315        }
316
317        for query_idx in 0..n {
318            let pt = point_fn(query_idx);
319            let query = BBox::from_point(pt);
320            self.traverse_bvh(&query, query_idx, false, &mut record);
321        }
322    }
323
324    /// Stack-based depth-first BVH traversal (matches C++ FindCollision).
325    fn traverse_bvh<F: FnMut(usize, usize)>(
326        &self,
327        query: &BBox,
328        query_idx: usize,
329        self_collision: bool,
330        record: &mut F,
331    ) {
332        let mut stack = [0i32; 64];
333        let mut top: i32 = -1;
334        let mut node = K_ROOT;
335
336        loop {
337            let internal = node_to_internal(node);
338            if internal < 0 || internal as usize >= self.internal_children.len() {
339                if top < 0 { break; }
340                node = stack[top as usize];
341                top -= 1;
342                continue;
343            }
344            let [child1, child2] = self.internal_children[internal as usize];
345
346            let traverse1 = self.check_node(query, child1, query_idx, self_collision, record);
347            let traverse2 = self.check_node(query, child2, query_idx, self_collision, record);
348
349            if !traverse1 && !traverse2 {
350                if top < 0 {
351                    break;
352                }
353                node = stack[top as usize];
354                top -= 1;
355            } else {
356                node = if traverse1 { child1 } else { child2 };
357                if traverse1 && traverse2 {
358                    top += 1;
359                    debug_assert!((top as usize) < 64, "BVH stack overflow");
360                    stack[top as usize] = child2;
361                }
362            }
363        }
364    }
365
366    /// Check if a node's AABB overlaps the query. If it's a leaf, record the hit.
367    /// Returns true if the node is internal and overlaps (should traverse deeper).
368    #[inline]
369    fn check_node<F: FnMut(usize, usize)>(
370        &self,
371        query: &BBox,
372        node: i32,
373        query_idx: usize,
374        self_collision: bool,
375        record: &mut F,
376    ) -> bool {
377        if node < 0 || node as usize >= self.node_bbox.len() {
378            return false;
379        }
380        let node_box = &self.node_bbox[node as usize];
381        let overlaps = query.does_overlap_box(node_box);
382        if overlaps && is_leaf(node) {
383            // Leaves are stored in tree order == input order (pre-sorted by
384            // morton), so the leaf index IS the caller's index — no mapping.
385            let leaf_idx = node_to_leaf(node) as usize;
386            if !self_collision || leaf_idx != query_idx {
387                record(query_idx, leaf_idx);
388            }
389        }
390        overlaps && is_internal(node)
391    }
392
393    /// Replace the leaf boxes (given in leaf/tree order — for a face collider
394    /// that is face order, since faces are morton-sorted) and refit the
395    /// internal boxes. Tree topology is untouched (C++ UpdateBoxes).
396    pub fn update_boxes(&mut self, leaf_bbox: Vec<BBox>) {
397        debug_assert_eq!(leaf_bbox.len(), self.num_leaves());
398        for (i, bbox) in leaf_bbox.into_iter().enumerate() {
399            self.node_bbox[leaf_to_node(i as i32) as usize] = bbox;
400        }
401        self.build_internal_boxes();
402    }
403
404    /// Map every node box through an axis-aligned transform (C++
405    /// Collider::Transform) — no refit needed since axis-aligned transforms
406    /// map AABBs to exact AABBs.
407    pub fn transform(&mut self, transform: &Mat3x4) {
408        debug_assert!(Self::is_axis_aligned(transform));
409        for bbox in &mut self.node_bbox {
410            *bbox = bbox.transform(transform);
411        }
412    }
413
414    pub fn morton_code(position: Vec3, bbox: &BBox) -> u32 {
415        morton_code(position, bbox)
416    }
417
418    pub fn is_axis_aligned(transform: &Mat3x4) -> bool {
419        for row in 0..3 {
420            let mut zero_count = 0;
421            for col in 0..3 {
422                if transform[col][row] == 0.0 {
423                    zero_count += 1;
424                }
425            }
426            if zero_count != 2 {
427                return false;
428            }
429        }
430        true
431    }
432
433}
434
435pub fn edge_edge_dist(p: Vec3, a: Vec3, q: Vec3, b: Vec3) -> (Vec3, Vec3) {
436    let t_vec = q - p;
437    let a_dot_a = dot(a, a);
438    let b_dot_b = dot(b, b);
439    let a_dot_b = dot(a, b);
440    let a_dot_t = dot(a, t_vec);
441    let b_dot_t = dot(b, t_vec);
442
443    let denom = a_dot_a * b_dot_b - a_dot_b * a_dot_b;
444    let mut t = if denom != 0.0 {
445        ((a_dot_t * b_dot_b - b_dot_t * a_dot_b) / denom).clamp(0.0, 1.0)
446    } else {
447        0.0
448    };
449
450    let u = if b_dot_b != 0.0 {
451        let u = (t * a_dot_b - b_dot_t) / b_dot_b;
452        if u < 0.0 {
453            t = if a_dot_a != 0.0 { (a_dot_t / a_dot_a).clamp(0.0, 1.0) } else { 0.0 };
454            0.0
455        } else if u > 1.0 {
456            t = if a_dot_a != 0.0 {
457                ((a_dot_b + a_dot_t) / a_dot_a).clamp(0.0, 1.0)
458            } else {
459                0.0
460            };
461            1.0
462        } else {
463            u
464        }
465    } else {
466        t = if a_dot_a != 0.0 { (a_dot_t / a_dot_a).clamp(0.0, 1.0) } else { 0.0 };
467        0.0
468    };
469
470    (p + a * t, q + b * u)
471}
472
473pub fn distance_triangle_triangle_squared(p: [Vec3; 3], q: [Vec3; 3]) -> f64 {
474    let sv = [p[1] - p[0], p[2] - p[1], p[0] - p[2]];
475    let tv = [q[1] - q[0], q[2] - q[1], q[0] - q[2]];
476
477    let mut shown_disjoint = false;
478    let mut mindd = f64::MAX;
479
480    for i in 0..3 {
481        for j in 0..3 {
482            let (cp, cq) = edge_edge_dist(p[i], sv[i], q[j], tv[j]);
483            let v = cq - cp;
484            let dd = dot(v, v);
485
486            if dd <= mindd {
487                mindd = dd;
488
489                let mut id = i + 2;
490                if id >= 3 { id -= 3; }
491                let z = p[id] - cp;
492                let mut a = dot(z, v);
493
494                id = j + 2;
495                if id >= 3 { id -= 3; }
496                let z = q[id] - cq;
497                let mut b = dot(z, v);
498
499                if a <= 0.0 && b >= 0.0 {
500                    return dot(v, v);
501                }
502
503                if a <= 0.0 { a = 0.0; } else if b > 0.0 { b = 0.0; }
504
505                if mindd - a + b > 0.0 {
506                    shown_disjoint = true;
507                }
508            }
509        }
510    }
511
512    let sn = cross(sv[0], sv[1]);
513    let snl = dot(sn, sn);
514    if snl > 1e-15 {
515        let tp = Vec3::new(dot(p[0] - q[0], sn), dot(p[0] - q[1], sn), dot(p[0] - q[2], sn));
516        let mut index = None;
517        if tp.x > 0.0 && tp.y > 0.0 && tp.z > 0.0 {
518            let mut idx = if tp.x < tp.y { 0 } else { 1 };
519            if tp.z < tp[idx] { idx = 2; }
520            index = Some(idx);
521        } else if tp.x < 0.0 && tp.y < 0.0 && tp.z < 0.0 {
522            let mut idx = if tp.x > tp.y { 0 } else { 1 };
523            if tp.z > tp[idx] { idx = 2; }
524            index = Some(idx);
525        }
526
527        if let Some(index) = index {
528            shown_disjoint = true;
529            let q_index = q[index];
530            let v = q_index - p[0];
531            let z = cross(sn, sv[0]);
532            if dot(v, z) > 0.0 {
533                let v = q_index - p[1];
534                let z = cross(sn, sv[1]);
535                if dot(v, z) > 0.0 {
536                    let v = q_index - p[2];
537                    let z = cross(sn, sv[2]);
538                    if dot(v, z) > 0.0 {
539                        let cp = q_index + sn * (tp[index] / snl);
540                        let cq = q_index;
541                        return dot(cp - cq, cp - cq);
542                    }
543                }
544            }
545        }
546    }
547
548    let tn = cross(tv[0], tv[1]);
549    let tnl = dot(tn, tn);
550    if tnl > 1e-15 {
551        let sp = Vec3::new(dot(q[0] - p[0], tn), dot(q[0] - p[1], tn), dot(q[0] - p[2], tn));
552        let mut index = None;
553        if sp.x > 0.0 && sp.y > 0.0 && sp.z > 0.0 {
554            let mut idx = if sp.x < sp.y { 0 } else { 1 };
555            if sp.z < sp[idx] { idx = 2; }
556            index = Some(idx);
557        } else if sp.x < 0.0 && sp.y < 0.0 && sp.z < 0.0 {
558            let mut idx = if sp.x > sp.y { 0 } else { 1 };
559            if sp.z > sp[idx] { idx = 2; }
560            index = Some(idx);
561        }
562
563        if let Some(index) = index {
564            shown_disjoint = true;
565            let p_index = p[index];
566            let v = p_index - q[0];
567            let z = cross(tn, tv[0]);
568            if dot(v, z) > 0.0 {
569                let v = p_index - q[1];
570                let z = cross(tn, tv[1]);
571                if dot(v, z) > 0.0 {
572                    let v = p_index - q[2];
573                    let z = cross(tn, tv[2]);
574                    if dot(v, z) > 0.0 {
575                        let cp = p_index;
576                        let cq = p_index + tn * (sp[index] / tnl);
577                        return dot(cp - cq, cp - cq);
578                    }
579                }
580            }
581        }
582    }
583
584    if shown_disjoint { mindd } else { 0.0 }
585}
586
587pub fn ray_triangle_intersection(
588    origin: Vec3,
589    direction: Vec3,
590    tri: [Vec3; 3],
591) -> Option<f64> {
592    let eps = 1e-9;
593    let edge1 = tri[1] - tri[0];
594    let edge2 = tri[2] - tri[0];
595    let h = cross(direction, edge2);
596    let a = dot(edge1, h);
597    if a.abs() < eps {
598        return None;
599    }
600    let f = 1.0 / a;
601    let s = origin - tri[0];
602    let u = f * dot(s, h);
603    if !(0.0..=1.0).contains(&u) {
604        return None;
605    }
606    let q = cross(s, edge1);
607    let v = f * dot(direction, q);
608    if v < 0.0 || u + v > 1.0 {
609        return None;
610    }
611    let t = f * dot(edge2, q);
612    if t > eps { Some(t) } else { None }
613}
614
615impl ManifoldImpl {
616    pub fn is_self_intersecting(&self) -> bool {
617        let ep = 2.0 * self.epsilon;
618        let epsilon_sq = ep * ep;
619        // Fresh face boxes for the queries; the tree itself is the cached
620        // collider (C++ SelfIntersecting queries collider_ the same way).
621        let (face_box, _face_morton) = get_face_box_morton(self);
622        let collider = &self.collider;
623        let mut intersecting = false;
624
625        collider.collisions_with_boxes(&face_box, true, |tri0, tri1| {
626            if intersecting {
627                return;
628            }
629            let tri_verts0 = self.face_triangle_vertices(tri0);
630            let tri_verts1 = self.face_triangle_vertices(tri1);
631
632            for a in &tri_verts0 {
633                for b in &tri_verts1 {
634                    if distance2(*a, *b) <= epsilon_sq {
635                        return;
636                    }
637                }
638            }
639
640            if distance_triangle_triangle_squared(tri_verts0, tri_verts1) == 0.0 {
641                let mut tmp0 = tri_verts0;
642                let mut tmp1 = tri_verts1;
643                for i in 0..3 {
644                    tmp0[i] = tri_verts0[i] + self.face_normal[tri1] * ep;
645                }
646                if distance_triangle_triangle_squared(tmp0, tri_verts1) > 0.0 {
647                    return;
648                }
649                for i in 0..3 {
650                    tmp0[i] = tri_verts0[i] - self.face_normal[tri1] * ep;
651                }
652                if distance_triangle_triangle_squared(tmp0, tri_verts1) > 0.0 {
653                    return;
654                }
655                for i in 0..3 {
656                    tmp1[i] = tri_verts1[i] + self.face_normal[tri0] * ep;
657                }
658                if distance_triangle_triangle_squared(tri_verts0, tmp1) > 0.0 {
659                    return;
660                }
661                for i in 0..3 {
662                    tmp1[i] = tri_verts1[i] - self.face_normal[tri0] * ep;
663                }
664                if distance_triangle_triangle_squared(tri_verts0, tmp1) > 0.0 {
665                    return;
666                }
667                intersecting = true;
668            }
669        });
670
671        intersecting
672    }
673
674    pub fn min_gap(&self, other: &ManifoldImpl, search_length: f64) -> f64 {
675        let (mut other_box, _) = get_face_box_morton(other);
676        for bbox in &mut other_box {
677            bbox.min = bbox.min - Vec3::splat(search_length);
678            bbox.max = bbox.max + Vec3::splat(search_length);
679        }
680
681        // Query self's cached face BVH (C++ MinGap queries collider_).
682        let collider = &self.collider;
683        let mut min_distance = f64::INFINITY;
684        collider.collisions_with_boxes(&other_box, false, |tri_other, tri| {
685            let p = self.face_triangle_vertices(tri);
686            let q = other.face_triangle_vertices(tri_other);
687            min_distance = min_distance.min(distance_triangle_triangle_squared(p, q));
688        });
689
690        min_distance.min(search_length * search_length).sqrt()
691    }
692
693    fn face_triangle_vertices(&self, tri: usize) -> [Vec3; 3] {
694        [
695            self.vert_pos[self.halfedge[3 * tri].start_vert as usize],
696            self.vert_pos[self.halfedge[3 * tri + 1].start_vert as usize],
697            self.vert_pos[self.halfedge[3 * tri + 2].start_vert as usize],
698        ]
699    }
700}
701
702#[cfg(test)]
703mod tests {
704    use super::*;
705    use crate::linalg::{mat4_to_mat3x4, translation_matrix};
706
707    #[test]
708    fn test_collider_box_overlap() {
709        let boxes = vec![
710            BBox::from_points(Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 1.0, 1.0)),
711            BBox::from_points(Vec3::new(2.0, 2.0, 2.0), Vec3::new(3.0, 3.0, 3.0)),
712        ];
713        let collider = Collider::new(boxes.clone(), vec![0, 1]);
714        let mut hits = Vec::new();
715        collider.collisions_with_boxes(&boxes, true, |a, b| hits.push((a, b)));
716        assert!(hits.is_empty());
717
718        let queries = vec![BBox::from_points(Vec3::new(0.5, 0.5, 0.5), Vec3::new(2.5, 2.5, 2.5))];
719        collider.collisions_with_boxes(&queries, false, |a, b| hits.push((a, b)));
720        assert_eq!(hits, vec![(0, 0), (0, 1)]);
721    }
722
723    #[test]
724    fn test_ray_triangle_intersection() {
725        let tri = [
726            Vec3::new(0.0, 0.0, 0.0),
727            Vec3::new(1.0, 0.0, 0.0),
728            Vec3::new(0.0, 1.0, 0.0),
729        ];
730        let hit = ray_triangle_intersection(Vec3::new(0.25, 0.25, -1.0), Vec3::new(0.0, 0.0, 1.0), tri);
731        assert!(hit.is_some());
732    }
733
734    #[test]
735    fn test_triangle_triangle_distance_zero_for_intersection() {
736        let a = [
737            Vec3::new(0.0, 0.0, 0.0),
738            Vec3::new(1.0, 0.0, 0.0),
739            Vec3::new(0.0, 1.0, 0.0),
740        ];
741        let b = [
742            Vec3::new(0.25, 0.25, -1.0),
743            Vec3::new(0.25, 0.25, 1.0),
744            Vec3::new(0.75, 0.25, 0.0),
745        ];
746        assert_eq!(distance_triangle_triangle_squared(a, b), 0.0);
747    }
748
749    #[test]
750    fn test_cube_not_self_intersecting() {
751        let m = ManifoldImpl::cube(&Mat3x4::identity());
752        assert!(!m.is_self_intersecting());
753    }
754
755    #[test]
756    fn test_min_gap_between_cubes() {
757        let a = ManifoldImpl::cube(&Mat3x4::identity());
758        let b = ManifoldImpl::cube(&mat4_to_mat3x4(translation_matrix(Vec3::new(2.0, 0.0, 0.0))));
759        let gap = a.min_gap(&b, 5.0);
760        assert!((gap - 1.0).abs() < 1e-8, "gap = {}", gap);
761    }
762
763    #[test]
764    fn test_bvh_many_boxes() {
765        // Create many non-overlapping boxes and verify BVH finds the right pairs
766        let n = 100;
767        let mut boxes = Vec::new();
768        let mut mortons = Vec::new();
769        for i in 0..n {
770            let x = i as f64 * 3.0;
771            boxes.push(BBox::from_points(
772                Vec3::new(x, 0.0, 0.0),
773                Vec3::new(x + 1.0, 1.0, 1.0),
774            ));
775            mortons.push(i as u32);
776        }
777        let collider = Collider::new(boxes.clone(), mortons);
778
779        // Query that overlaps box 50
780        let query = vec![BBox::from_points(
781            Vec3::new(150.5, 0.5, 0.5),
782            Vec3::new(150.6, 0.6, 0.6),
783        )];
784        let mut hits = Vec::new();
785        collider.collisions_with_boxes(&query, false, |a, b| hits.push((a, b)));
786        assert_eq!(hits, vec![(0, 50)]);
787    }
788}