Skip to main content

oxiphysics_collision/sat_collision/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6
7/// Contact result from the arbitrary convex polyhedra SAT test.
8#[derive(Clone, Debug)]
9pub struct PolyhedraContact {
10    /// Minimum-overlap separating axis (contact normal).
11    pub normal: [f64; 3],
12    /// Penetration depth.
13    pub depth: f64,
14    /// Feature type that produced the minimum overlap.
15    pub feature: ContactFeature,
16}
17/// An oriented bounding box defined by a center, three orthogonal unit axes,
18/// and per-axis half-extents.
19#[derive(Clone, Debug)]
20pub struct Obb {
21    /// Center of the box in world space.
22    pub center: [f64; 3],
23    /// Three orthogonal unit axes (columns of the rotation matrix).
24    pub axes: [[f64; 3]; 3],
25    /// Half-extents along each axis.
26    pub half_extents: [f64; 3],
27}
28impl Obb {
29    /// Construct an axis-aligned OBB from a center point and half-extents.
30    pub fn axis_aligned(center: [f64; 3], half_extents: [f64; 3]) -> Self {
31        Obb {
32            center,
33            axes: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
34            half_extents,
35        }
36    }
37    /// Construct an OBB from an axis-aligned bounding box given by `min` and `max`.
38    pub fn from_aabb(min: [f64; 3], max: [f64; 3]) -> Self {
39        let center = [
40            (min[0] + max[0]) * 0.5,
41            (min[1] + max[1]) * 0.5,
42            (min[2] + max[2]) * 0.5,
43        ];
44        let half_extents = [
45            (max[0] - min[0]) * 0.5,
46            (max[1] - min[1]) * 0.5,
47            (max[2] - min[2]) * 0.5,
48        ];
49        Obb {
50            center,
51            axes: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
52            half_extents,
53        }
54    }
55    /// Return all 8 corner vertices of the OBB in world space.
56    pub fn vertices(&self) -> [[f64; 3]; 8] {
57        let c = self.center;
58        let a = self.axes;
59        let h = self.half_extents;
60        let mut verts = [[0.0f64; 3]; 8];
61        for (i, s) in [
62            [-1.0f64, -1.0, -1.0],
63            [1.0, -1.0, -1.0],
64            [-1.0, 1.0, -1.0],
65            [1.0, 1.0, -1.0],
66            [-1.0, -1.0, 1.0],
67            [1.0, -1.0, 1.0],
68            [-1.0, 1.0, 1.0],
69            [1.0, 1.0, 1.0],
70        ]
71        .iter()
72        .enumerate()
73        {
74            verts[i] = add(
75                add(add(c, scale(a[0], s[0] * h[0])), scale(a[1], s[1] * h[1])),
76                scale(a[2], s[2] * h[2]),
77            );
78        }
79        verts
80    }
81    /// Return the support point (furthest vertex) in direction `dir`.
82    pub fn support(&self, dir: [f64; 3]) -> [f64; 3] {
83        let verts = self.vertices();
84        let mut best = verts[0];
85        let mut best_d = dot(best, dir);
86        for &v in verts.iter().skip(1) {
87            let d = dot(v, dir);
88            if d > best_d {
89                best_d = d;
90                best = v;
91            }
92        }
93        best
94    }
95    /// Project the OBB onto `axis` and return the (min, max) scalar interval.
96    pub fn project_onto_axis(&self, axis: [f64; 3]) -> (f64, f64) {
97        let center_proj = dot(self.center, axis);
98        let radius = self.half_extents[0] * dot(self.axes[0], axis).abs()
99            + self.half_extents[1] * dot(self.axes[1], axis).abs()
100            + self.half_extents[2] * dot(self.axes[2], axis).abs();
101        (center_proj - radius, center_proj + radius)
102    }
103}
104/// Describes which geometric features are in contact.
105#[derive(Clone, Debug, PartialEq)]
106pub enum ContactFeature {
107    /// Face-face contact.
108    FaceFace { face_a: usize, face_b: usize },
109    /// Face-edge contact.
110    FaceEdge { face: usize, edge: usize },
111    /// Edge-edge contact.
112    EdgeEdge { edge_a: usize, edge_b: usize },
113    /// Vertex-face contact.
114    VertexFace { vertex: usize, face: usize },
115}
116/// Stateless namespace for OBB–OBB collision tests using SAT.
117pub struct ObbCollision;
118impl ObbCollision {
119    /// Project both OBBs onto `axis`. Returns `None` if separated, or
120    /// `Some(overlap)` if overlapping.
121    pub fn test_overlap_on_axis(obb_a: &Obb, obb_b: &Obb, axis: [f64; 3]) -> Option<f64> {
122        let len = length(axis);
123        if len < 1e-12 {
124            return None;
125        }
126        let norm_axis = scale(axis, 1.0 / len);
127        let (min_a, max_a) = obb_a.project_onto_axis(norm_axis);
128        let (min_b, max_b) = obb_b.project_onto_axis(norm_axis);
129        let overlap = f64::min(max_a, max_b) - f64::max(min_a, min_b);
130        if overlap < 0.0 { None } else { Some(overlap) }
131    }
132    /// Full OBB–OBB SAT test using 15 candidate axes.
133    /// Returns `None` if separated, or `Some(SatContact)` with the minimum
134    /// penetration axis.
135    pub fn obb_obb_sat(obb_a: &Obb, obb_b: &Obb) -> Option<SatContact> {
136        let mut min_depth = f64::MAX;
137        let mut best_normal = [0.0f64; 3];
138        let mut best_feature = ContactFeature::FaceFace {
139            face_a: 0,
140            face_b: 0,
141        };
142        let mut test = |axis: [f64; 3], feature: ContactFeature| -> bool {
143            match Self::test_overlap_on_axis(obb_a, obb_b, axis) {
144                None => false,
145                Some(overlap) => {
146                    if overlap < min_depth {
147                        min_depth = overlap;
148                        let diff = sub(obb_a.center, obb_b.center);
149                        let norm = normalize(axis);
150                        best_normal = if dot(norm, diff) >= 0.0 {
151                            norm
152                        } else {
153                            scale(norm, -1.0)
154                        };
155                        best_feature = feature;
156                    }
157                    true
158                }
159            }
160        };
161        for (i, &ax) in obb_a.axes.iter().enumerate() {
162            if !test(
163                ax,
164                ContactFeature::FaceFace {
165                    face_a: i * 2,
166                    face_b: 0,
167                },
168            ) {
169                return None;
170            }
171        }
172        for (j, &bx) in obb_b.axes.iter().enumerate() {
173            if !test(
174                bx,
175                ContactFeature::FaceFace {
176                    face_a: 0,
177                    face_b: j * 2,
178                },
179            ) {
180                return None;
181            }
182        }
183        for (i, &ai) in obb_a.axes.iter().enumerate() {
184            for (j, &bj) in obb_b.axes.iter().enumerate() {
185                let axis = cross(ai, bj);
186                if length(axis) > 1e-6
187                    && !test(
188                        axis,
189                        ContactFeature::EdgeEdge {
190                            edge_a: i,
191                            edge_b: j,
192                        },
193                    )
194                {
195                    return None;
196                }
197            }
198        }
199        Some(SatContact {
200            normal: best_normal,
201            depth: min_depth,
202            contact_type: best_feature,
203        })
204    }
205    /// Fast boolean overlap test for two OBBs.
206    pub fn box_box_fast(a: &Obb, b: &Obb) -> bool {
207        Self::obb_obb_sat(a, b).is_some()
208    }
209}
210/// Contact information produced by a SAT test.
211#[derive(Clone, Debug)]
212pub struct SatContact {
213    /// Contact normal pointing from B towards A.
214    pub normal: [f64; 3],
215    /// Penetration depth (positive means overlap).
216    pub depth: f64,
217    /// The type of contact feature.
218    pub contact_type: ContactFeature,
219}
220/// Generate contact points from SAT overlap information.
221pub struct SatContactPointGenerator;
222impl SatContactPointGenerator {
223    /// Generate contact points from an OBB-OBB SAT result.
224    ///
225    /// Uses the minimum-overlap axis to determine contact type, then
226    /// extracts the relevant vertex/edge/face points.
227    pub fn from_obb_obb(obb_a: &Obb, obb_b: &Obb, contact: &SatContact) -> Vec<[f64; 3]> {
228        match &contact.contact_type {
229            ContactFeature::FaceFace { .. } => {
230                let support_b = obb_b.support(scale(contact.normal, -1.0));
231                let ref_d = dot(obb_a.center, contact.normal) + contact.depth * 0.5;
232                let proj = dot(support_b, contact.normal);
233                let adjusted = add(support_b, scale(contact.normal, ref_d - proj));
234                vec![adjusted]
235            }
236            ContactFeature::EdgeEdge { edge_a, edge_b } => {
237                let dir_a = obb_a.axes[edge_a % 3];
238                let dir_b = obb_b.axes[edge_b % 3];
239                let p_a = obb_a.support(scale(contact.normal, -1.0));
240                let p_b = obb_b.support(contact.normal);
241                let (cp, _) = edge_edge_contact(p_a, dir_a, p_b, dir_b);
242                vec![cp]
243            }
244            ContactFeature::VertexFace { .. } | ContactFeature::FaceEdge { .. } => {
245                let v = obb_b.support(scale(contact.normal, -1.0));
246                vec![v]
247            }
248        }
249    }
250}
251/// SAT test between an OBB and a triangle.
252pub struct ObbTriangleCollision;
253impl ObbTriangleCollision {
254    /// Test OBB vs triangle for intersection using SAT.
255    ///
256    /// Tests 3 OBB face axes, 1 triangle normal, and 9 edge-edge axes (3x3).
257    pub fn test(obb: &Obb, tri: [[f64; 3]; 3]) -> Option<SatContact> {
258        let mut min_depth = f64::MAX;
259        let mut best_normal = [0.0f64; 3];
260        let mut best_feature = ContactFeature::FaceFace {
261            face_a: 0,
262            face_b: 0,
263        };
264        let tri_edges = [
265            sub(tri[1], tri[0]),
266            sub(tri[2], tri[1]),
267            sub(tri[0], tri[2]),
268        ];
269        let test_axis = |axis: [f64; 3]| -> Option<f64> {
270            let len = length(axis);
271            if len < 1e-12 {
272                return Some(f64::MAX);
273            }
274            let norm = scale(axis, 1.0 / len);
275            let (min_obb, max_obb) = obb.project_onto_axis(norm);
276            let tri_proj: Vec<f64> = tri.iter().map(|&v| dot(v, norm)).collect();
277            let min_tri = tri_proj.iter().cloned().fold(f64::MAX, f64::min);
278            let max_tri = tri_proj.iter().cloned().fold(f64::MIN, f64::max);
279            let overlap = f64::min(max_obb, max_tri) - f64::max(min_obb, min_tri);
280            if overlap < 0.0 { None } else { Some(overlap) }
281        };
282        for (i, &ax) in obb.axes.iter().enumerate() {
283            match test_axis(ax) {
284                None => return None,
285                Some(depth) if depth < min_depth => {
286                    min_depth = depth;
287                    best_normal = ax;
288                    best_feature = ContactFeature::FaceFace {
289                        face_a: i,
290                        face_b: 0,
291                    };
292                }
293                _ => {}
294            }
295        }
296        let tri_normal = normalize(cross(tri_edges[0], tri_edges[2]));
297        match test_axis(tri_normal) {
298            None => return None,
299            Some(depth) if depth < min_depth => {
300                min_depth = depth;
301                best_normal = tri_normal;
302                best_feature = ContactFeature::FaceFace {
303                    face_a: 0,
304                    face_b: 1,
305                };
306            }
307            _ => {}
308        }
309        for (i, &ai) in obb.axes.iter().enumerate() {
310            for (j, &te) in tri_edges.iter().enumerate() {
311                let axis = cross(ai, te);
312                if length(axis) < 1e-9 {
313                    continue;
314                }
315                match test_axis(axis) {
316                    None => return None,
317                    Some(depth) if depth < min_depth => {
318                        min_depth = depth;
319                        best_normal = normalize(axis);
320                        best_feature = ContactFeature::EdgeEdge {
321                            edge_a: i,
322                            edge_b: j,
323                        };
324                    }
325                    _ => {}
326                }
327            }
328        }
329        let tri_centroid = scale(add(add(tri[0], tri[1]), tri[2]), 1.0 / 3.0);
330        let dir_to_tri = sub(tri_centroid, obb.center);
331        if dot(best_normal, dir_to_tri) < 0.0 {
332            best_normal = scale(best_normal, -1.0);
333        }
334        Some(SatContact {
335            normal: best_normal,
336            depth: min_depth,
337            contact_type: best_feature,
338        })
339    }
340}
341/// A convex polytope stored as vertex, face, and edge lists.
342#[derive(Clone, Debug)]
343pub struct ConvexPolytope {
344    /// Vertices in local space.
345    pub vertices: Vec<[f64; 3]>,
346    /// Faces as lists of vertex indices.
347    pub faces: Vec<Vec<usize>>,
348    /// Edges as pairs of vertex indices.
349    pub edges: Vec<[usize; 2]>,
350    /// Outward-pointing face normals (one per face).
351    pub face_normals: Vec<[f64; 3]>,
352}
353impl ConvexPolytope {
354    /// Decompose an OBB into a convex polytope (box with 6 faces and 12 edges).
355    pub fn from_obb(obb: &Obb) -> Self {
356        let verts = obb.vertices();
357        let vertices: Vec<[f64; 3]> = verts.to_vec();
358        let faces: Vec<Vec<usize>> = vec![
359            vec![0, 2, 3, 1],
360            vec![4, 5, 7, 6],
361            vec![0, 1, 5, 4],
362            vec![2, 6, 7, 3],
363            vec![0, 4, 6, 2],
364            vec![1, 3, 7, 5],
365        ];
366        let face_normals: Vec<[f64; 3]> = vec![
367            scale(obb.axes[2], -1.0),
368            obb.axes[2],
369            scale(obb.axes[1], -1.0),
370            obb.axes[1],
371            scale(obb.axes[0], -1.0),
372            obb.axes[0],
373        ];
374        let edges: Vec<[usize; 2]> = vec![
375            [0, 1],
376            [2, 3],
377            [4, 5],
378            [6, 7],
379            [0, 2],
380            [1, 3],
381            [4, 6],
382            [5, 7],
383            [0, 4],
384            [1, 5],
385            [2, 6],
386            [3, 7],
387        ];
388        ConvexPolytope {
389            vertices,
390            faces,
391            edges,
392            face_normals,
393        }
394    }
395    /// Return the furthest vertex in direction `dir` under transform `(translation, rotation)`.
396    /// The rotation matrix columns are the local axes.
397    pub fn support(&self, dir: [f64; 3], transform: ([f64; 3], [[f64; 3]; 3])) -> [f64; 3] {
398        let (translation, axes) = transform;
399        let local_dir = [dot(dir, axes[0]), dot(dir, axes[1]), dot(dir, axes[2])];
400        let mut best_local = self.vertices[0];
401        let mut best_d = dot(self.vertices[0], local_dir);
402        for &v in self.vertices.iter().skip(1) {
403            let d = dot(v, local_dir);
404            if d > best_d {
405                best_d = d;
406                best_local = v;
407            }
408        }
409        add(translation, rotate(axes, best_local))
410    }
411}
412/// Stateless SAT collision test for general convex polytopes.
413pub struct PolytopeCollision;
414impl PolytopeCollision {
415    /// SAT test between two convex polytopes each under their own transform.
416    /// Tests all face normals of A, all face normals of B, and edge cross-products.
417    pub fn sat_test(
418        a: &ConvexPolytope,
419        ta: ([f64; 3], [[f64; 3]; 3]),
420        b: &ConvexPolytope,
421        tb: ([f64; 3], [[f64; 3]; 3]),
422    ) -> Option<SatContact> {
423        let mut min_depth = f64::MAX;
424        let mut best_normal = [0.0f64; 3];
425        let mut best_feature = ContactFeature::FaceFace {
426            face_a: 0,
427            face_b: 0,
428        };
429        let project = |poly: &ConvexPolytope, t: ([f64; 3], [[f64; 3]; 3]), axis: [f64; 3]| {
430            let (trans, axes) = t;
431            let mut mn = f64::MAX;
432            let mut mx = f64::MIN;
433            for &lv in &poly.vertices {
434                let wv = add(trans, rotate(axes, lv));
435                let p = dot(wv, axis);
436                if p < mn {
437                    mn = p;
438                }
439                if p > mx {
440                    mx = p;
441                }
442            }
443            (mn, mx)
444        };
445        let mut test_axis = |axis: [f64; 3], feature: ContactFeature| -> bool {
446            let len = length(axis);
447            if len < 1e-9 {
448                return true;
449            }
450            let norm = scale(axis, 1.0 / len);
451            let (min_a, max_a) = project(a, ta, norm);
452            let (min_b, max_b) = project(b, tb, norm);
453            let overlap = f64::min(max_a, max_b) - f64::max(min_a, min_b);
454            if overlap < 0.0 {
455                return false;
456            }
457            if overlap < min_depth {
458                min_depth = overlap;
459                let diff = sub(ta.0, tb.0);
460                best_normal = if dot(norm, diff) >= 0.0 {
461                    norm
462                } else {
463                    scale(norm, -1.0)
464                };
465                best_feature = feature;
466            }
467            true
468        };
469        for (fi, &ln) in a.face_normals.iter().enumerate() {
470            let wn = rotate(ta.1, ln);
471            if !test_axis(
472                wn,
473                ContactFeature::FaceFace {
474                    face_a: fi,
475                    face_b: 0,
476                },
477            ) {
478                return None;
479            }
480        }
481        for (fi, &ln) in b.face_normals.iter().enumerate() {
482            let wn = rotate(tb.1, ln);
483            if !test_axis(
484                wn,
485                ContactFeature::FaceFace {
486                    face_a: 0,
487                    face_b: fi,
488                },
489            ) {
490                return None;
491            }
492        }
493        for (ei, &[ia0, ia1]) in a.edges.iter().enumerate() {
494            let ea = normalize(sub(
495                add(ta.0, rotate(ta.1, a.vertices[ia1])),
496                add(ta.0, rotate(ta.1, a.vertices[ia0])),
497            ));
498            for (ej, &[ib0, ib1]) in b.edges.iter().enumerate() {
499                let eb = normalize(sub(
500                    add(tb.0, rotate(tb.1, b.vertices[ib1])),
501                    add(tb.0, rotate(tb.1, b.vertices[ib0])),
502                ));
503                let axis = cross(ea, eb);
504                if !test_axis(
505                    axis,
506                    ContactFeature::EdgeEdge {
507                        edge_a: ei,
508                        edge_b: ej,
509                    },
510                ) {
511                    return None;
512                }
513            }
514        }
515        Some(SatContact {
516            normal: best_normal,
517            depth: min_depth,
518            contact_type: best_feature,
519        })
520    }
521}
522/// A convex polyhedron for SAT tests, described by vertices and face normals.
523#[derive(Clone, Debug)]
524pub struct ConvexPolyhedron {
525    /// Vertices in world space.
526    pub vertices: Vec<[f64; 3]>,
527    /// Face normals (outward-pointing, unit length).
528    pub face_normals: Vec<[f64; 3]>,
529    /// Edge directions (one per unique edge pair).
530    pub edge_directions: Vec<[f64; 3]>,
531}
532impl ConvexPolyhedron {
533    /// Create a convex polyhedron from vertices, face normals, and edge directions.
534    pub fn new(
535        vertices: Vec<[f64; 3]>,
536        face_normals: Vec<[f64; 3]>,
537        edge_directions: Vec<[f64; 3]>,
538    ) -> Self {
539        ConvexPolyhedron {
540            vertices,
541            face_normals,
542            edge_directions,
543        }
544    }
545    /// Support function: vertex furthest along `dir`.
546    pub fn support(&self, dir: [f64; 3]) -> [f64; 3] {
547        self.vertices
548            .iter()
549            .max_by(|a, b| {
550                dot(**a, dir)
551                    .partial_cmp(&dot(**b, dir))
552                    .unwrap_or(std::cmp::Ordering::Equal)
553            })
554            .copied()
555            .unwrap_or([0.0; 3])
556    }
557    /// Project all vertices onto `axis`, returning `(min, max)`.
558    pub fn project(&self, axis: [f64; 3]) -> (f64, f64) {
559        let mut mn = f64::INFINITY;
560        let mut mx = f64::NEG_INFINITY;
561        for &v in &self.vertices {
562            let d = dot(v, axis);
563            mn = mn.min(d);
564            mx = mx.max(d);
565        }
566        (mn, mx)
567    }
568}
569/// Cached separating axis from the previous frame.
570///
571/// Stores the best axis from the last SAT query so it can be checked first,
572/// dramatically reducing the average number of axes tested when objects
573/// move slowly relative to each other.
574#[derive(Clone, Debug)]
575pub struct SatAxisCache {
576    /// The cached separating or minimum-penetration axis.
577    pub axis: [f64; 3],
578    /// The overlap (negative = separation) along the cached axis.
579    pub overlap: f64,
580    /// Whether the cache is valid for the current frame.
581    pub valid: bool,
582}
583impl SatAxisCache {
584    /// Create an empty (invalid) cache.
585    pub fn new() -> Self {
586        SatAxisCache {
587            axis: [0.0, 1.0, 0.0],
588            overlap: 0.0,
589            valid: false,
590        }
591    }
592    /// Invalidate the cache (e.g. when a body teleports or is removed).
593    pub fn invalidate(&mut self) {
594        self.valid = false;
595    }
596    /// Update the cache with a new axis and overlap.
597    pub fn update(&mut self, axis: [f64; 3], overlap: f64) {
598        self.axis = axis;
599        self.overlap = overlap;
600        self.valid = true;
601    }
602    /// Check the cached axis against two OBBs.
603    ///
604    /// Returns `Some(overlap)` if still overlapping in the cached direction,
605    /// or `None` if the cache is invalid or the axis separates the OBBs.
606    pub fn check(&self, obb_a: &Obb, obb_b: &Obb) -> Option<f64> {
607        if !self.valid {
608            return None;
609        }
610        ObbCollision::test_overlap_on_axis(obb_a, obb_b, self.axis)
611    }
612}
613/// A flat-array OBB BVH (distinct from the recursive `ObbTree`).
614#[derive(Clone, Debug, Default)]
615pub struct ObbBvh {
616    /// All nodes (root = index 0).
617    pub nodes: Vec<ObbBvhNode>,
618}
619impl ObbBvh {
620    /// Create an empty BVH.
621    pub fn new() -> Self {
622        ObbBvh { nodes: Vec::new() }
623    }
624    /// Build a balanced BVH from a slice of leaf OBBs.
625    pub fn build(leaf_obbs: &[Obb]) -> Self {
626        if leaf_obbs.is_empty() {
627            return Self::new();
628        }
629        let mut bvh = ObbBvh { nodes: Vec::new() };
630        let indices: Vec<usize> = (0..leaf_obbs.len()).collect();
631        bvh.build_recursive(leaf_obbs, &indices);
632        bvh
633    }
634    fn build_recursive(&mut self, obbs: &[Obb], indices: &[usize]) -> usize {
635        let node_idx = self.nodes.len();
636        if indices.len() == 1 {
637            self.nodes.push(ObbBvhNode {
638                bounds: obbs[indices[0]].clone(),
639                left: usize::MAX,
640                right: usize::MAX,
641                geometry_index: Some(indices[0]),
642            });
643            return node_idx;
644        }
645        let mut mn = [f64::INFINITY; 3];
646        let mut mx = [f64::NEG_INFINITY; 3];
647        for &i in indices {
648            let verts = obbs[i].vertices();
649            for v in &verts {
650                for k in 0..3 {
651                    mn[k] = mn[k].min(v[k]);
652                    mx[k] = mx[k].max(v[k]);
653                }
654            }
655        }
656        let merged = Obb::from_aabb(mn, mx);
657        let extents = [mx[0] - mn[0], mx[1] - mn[1], mx[2] - mn[2]];
658        let split_axis = if extents[0] >= extents[1] && extents[0] >= extents[2] {
659            0
660        } else if extents[1] >= extents[2] {
661            1
662        } else {
663            2
664        };
665        let split_val = (mn[split_axis] + mx[split_axis]) * 0.5;
666        let (mut left_idx, mut right_idx): (Vec<usize>, Vec<usize>) = indices
667            .iter()
668            .partition(|&&i| obbs[i].center[split_axis] <= split_val);
669        if left_idx.is_empty() || right_idx.is_empty() {
670            left_idx = indices[..indices.len() / 2].to_vec();
671            right_idx = indices[indices.len() / 2..].to_vec();
672        }
673        self.nodes.push(ObbBvhNode {
674            bounds: merged,
675            left: usize::MAX,
676            right: usize::MAX,
677            geometry_index: None,
678        });
679        let left_child = self.build_recursive(obbs, &left_idx);
680        let right_child = self.build_recursive(obbs, &right_idx);
681        self.nodes[node_idx].left = left_child;
682        self.nodes[node_idx].right = right_child;
683        node_idx
684    }
685    /// Query all leaf geometry indices whose bounds overlap `query_obb`.
686    pub fn query_overlapping(&self, query_obb: &Obb) -> Vec<usize> {
687        if self.nodes.is_empty() {
688            return Vec::new();
689        }
690        let mut results = Vec::new();
691        self.query_recursive(query_obb, 0, &mut results);
692        results
693    }
694    fn query_recursive(&self, query: &Obb, node_idx: usize, results: &mut Vec<usize>) {
695        let node = &self.nodes[node_idx];
696        if ObbCollision::obb_obb_sat(&node.bounds, query).is_none() {
697            return;
698        }
699        if node.is_leaf() {
700            if let Some(g) = node.geometry_index {
701                results.push(g);
702            }
703        } else {
704            if node.left < self.nodes.len() {
705                self.query_recursive(query, node.left, results);
706            }
707            if node.right < self.nodes.len() {
708                self.query_recursive(query, node.right, results);
709            }
710        }
711    }
712    /// Number of leaf nodes.
713    pub fn leaf_count(&self) -> usize {
714        self.nodes.iter().filter(|n| n.is_leaf()).count()
715    }
716    /// Total number of nodes.
717    pub fn node_count(&self) -> usize {
718        self.nodes.len()
719    }
720}
721/// A capsule defined by two endpoint centers and a radius.
722#[derive(Clone, Debug)]
723pub struct Capsule {
724    /// Center of the first end-cap.
725    pub p0: [f64; 3],
726    /// Center of the second end-cap.
727    pub p1: [f64; 3],
728    /// Radius of the capsule.
729    pub radius: f64,
730}
731impl Capsule {
732    /// Create a new capsule.
733    pub fn new(p0: [f64; 3], p1: [f64; 3], radius: f64) -> Self {
734        Self { p0, p1, radius }
735    }
736    /// Axis direction (p1 - p0), not normalised.
737    pub fn axis(&self) -> [f64; 3] {
738        sub(self.p1, self.p0)
739    }
740    /// Capsule half-length (excluding radius).
741    pub fn half_length(&self) -> f64 {
742        length(self.axis()) * 0.5
743    }
744    /// Closest point on the capsule axis segment to a query point.
745    pub fn closest_axis_point(&self, q: [f64; 3]) -> [f64; 3] {
746        let ax = self.axis();
747        let ax_len_sq = dot(ax, ax);
748        if ax_len_sq < 1e-24 {
749            return self.p0;
750        }
751        let t = dot(sub(q, self.p0), ax) / ax_len_sq;
752        let t_clamped = t.clamp(0.0, 1.0);
753        add(self.p0, scale(ax, t_clamped))
754    }
755    /// Support function in direction `dir`.
756    pub fn support(&self, dir: [f64; 3]) -> [f64; 3] {
757        let d = dot(self.axis(), dir);
758        let axis_pt = if d >= 0.0 { self.p1 } else { self.p0 };
759        let dir_len = length(dir);
760        if dir_len < 1e-12 {
761            return axis_pt;
762        }
763        add(axis_pt, scale(dir, self.radius / dir_len))
764    }
765}
766/// A bounding volume hierarchy of OBBs for triangle meshes.
767#[derive(Debug)]
768pub struct ObbTree {
769    /// The OBB for this node.
770    pub obb: Obb,
771    /// Left child.
772    pub left: Option<Box<ObbTree>>,
773    /// Right child.
774    pub right: Option<Box<ObbTree>>,
775    /// Triangle indices stored at this leaf (empty for internal nodes).
776    pub triangle_indices: Vec<usize>,
777}
778impl ObbTree {
779    /// Build an OBB tree from a triangle mesh.
780    pub fn build(vertices: &[[f64; 3]], triangles: &[[usize; 3]]) -> Self {
781        let indices: Vec<usize> = (0..triangles.len()).collect();
782        Self::build_recursive(vertices, triangles, &indices)
783    }
784    fn triangle_centroid(vertices: &[[f64; 3]], tri: [usize; 3]) -> [f64; 3] {
785        let (a, b, c) = (vertices[tri[0]], vertices[tri[1]], vertices[tri[2]]);
786        scale(add(add(a, b), c), 1.0 / 3.0)
787    }
788    fn compute_obb_for_triangles(
789        vertices: &[[f64; 3]],
790        triangles: &[[usize; 3]],
791        indices: &[usize],
792    ) -> Obb {
793        let mut pts: Vec<[f64; 3]> = Vec::new();
794        for &ti in indices {
795            for &vi in &triangles[ti] {
796                pts.push(vertices[vi]);
797            }
798        }
799        if pts.is_empty() {
800            return Obb {
801                center: [0.0; 3],
802                axes: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
803                half_extents: [0.0; 3],
804            };
805        }
806        let mut mn = pts[0];
807        let mut mx = pts[0];
808        for &p in &pts {
809            for k in 0..3 {
810                if p[k] < mn[k] {
811                    mn[k] = p[k];
812                }
813                if p[k] > mx[k] {
814                    mx[k] = p[k];
815                }
816            }
817        }
818        Obb::from_aabb(mn, mx)
819    }
820    fn build_recursive(vertices: &[[f64; 3]], triangles: &[[usize; 3]], indices: &[usize]) -> Self {
821        let obb = Self::compute_obb_for_triangles(vertices, triangles, indices);
822        if indices.len() <= 1 {
823            return ObbTree {
824                obb,
825                left: None,
826                right: None,
827                triangle_indices: indices.to_vec(),
828            };
829        }
830        let longest_axis = {
831            let he = obb.half_extents;
832            if he[0] >= he[1] && he[0] >= he[2] {
833                0
834            } else if he[1] >= he[2] {
835                1
836            } else {
837                2
838            }
839        };
840        let mut sorted_indices = indices.to_vec();
841        sorted_indices.sort_by(|&ia, &ib| {
842            let ca = Self::triangle_centroid(vertices, triangles[ia]);
843            let cb = Self::triangle_centroid(vertices, triangles[ib]);
844            ca[longest_axis]
845                .partial_cmp(&cb[longest_axis])
846                .unwrap_or(std::cmp::Ordering::Equal)
847        });
848        let mid = sorted_indices.len() / 2;
849        let left_indices = &sorted_indices[..mid];
850        let right_indices = &sorted_indices[mid..];
851        let left = Box::new(Self::build_recursive(vertices, triangles, left_indices));
852        let right = Box::new(Self::build_recursive(vertices, triangles, right_indices));
853        ObbTree {
854            obb,
855            left: Some(left),
856            right: Some(right),
857            triangle_indices: Vec::new(),
858        }
859    }
860    /// Query overlapping triangle pairs between two trees.
861    pub fn query_overlap(&self, other: &ObbTree) -> Vec<(usize, usize)> {
862        let mut result = Vec::new();
863        self.query_recursive(other, &mut result);
864        result
865    }
866    fn query_recursive(&self, other: &ObbTree, result: &mut Vec<(usize, usize)>) {
867        if ObbCollision::box_box_fast(&self.obb, &other.obb) {
868            match (&self.left, &self.right, &other.left, &other.right) {
869                (None, None, None, None) => {
870                    for &ta in &self.triangle_indices {
871                        for &tb in &other.triangle_indices {
872                            result.push((ta, tb));
873                        }
874                    }
875                }
876                _ => {
877                    let self_children: Vec<&ObbTree> = [&self.left, &self.right]
878                        .iter()
879                        .filter_map(|c| c.as_deref())
880                        .collect();
881                    let other_children: Vec<&ObbTree> = [&other.left, &other.right]
882                        .iter()
883                        .filter_map(|c| c.as_deref())
884                        .collect();
885                    if self_children.is_empty() {
886                        for oc in &other_children {
887                            self.query_recursive(oc, result);
888                        }
889                    } else {
890                        for sc in &self_children {
891                            sc.query_recursive(other, result);
892                        }
893                    }
894                }
895            }
896        }
897    }
898}
899/// Builds contact manifolds via polygon clipping.
900pub struct ContactManifoldBuilder;
901impl ContactManifoldBuilder {
902    /// Clip `polygon` by the half-space `dot(p, plane_normal) >= plane_d`.
903    /// Returns the clipped polygon vertices.
904    pub fn clip_polygon_by_plane(
905        polygon: &[[f64; 3]],
906        plane_normal: [f64; 3],
907        plane_d: f64,
908    ) -> Vec<[f64; 3]> {
909        if polygon.is_empty() {
910            return Vec::new();
911        }
912        let mut output = Vec::new();
913        let n = polygon.len();
914        for i in 0..n {
915            let a = polygon[i];
916            let b = polygon[(i + 1) % n];
917            let da = dot(a, plane_normal) - plane_d;
918            let db = dot(b, plane_normal) - plane_d;
919            if da >= 0.0 {
920                output.push(a);
921            }
922            if (da >= 0.0) != (db >= 0.0) {
923                let t = da / (da - db);
924                output.push(add(a, scale(sub(b, a), t)));
925            }
926        }
927        output
928    }
929    /// Build a contact manifold by clipping the incident face polygon against
930    /// the planes bounding the reference face (Sutherland-Hodgman algorithm).
931    pub fn build_face_face(
932        poly_a: &ConvexPolytope,
933        face_a_idx: usize,
934        ta: ([f64; 3], [[f64; 3]; 3]),
935        poly_b: &ConvexPolytope,
936        face_b_idx: usize,
937        tb: ([f64; 3], [[f64; 3]; 3]),
938    ) -> Vec<[f64; 3]> {
939        let face_a = &poly_a.faces[face_a_idx];
940        let ref_verts: Vec<[f64; 3]> = face_a
941            .iter()
942            .map(|&vi| add(ta.0, rotate(ta.1, poly_a.vertices[vi])))
943            .collect();
944        let face_b = &poly_b.faces[face_b_idx];
945        let mut clipped: Vec<[f64; 3]> = face_b
946            .iter()
947            .map(|&vi| add(tb.0, rotate(tb.1, poly_b.vertices[vi])))
948            .collect();
949        let nref = ref_verts.len();
950        let ref_normal = rotate(ta.1, poly_a.face_normals[face_a_idx]);
951        for i in 0..nref {
952            if clipped.is_empty() {
953                break;
954            }
955            let edge_start = ref_verts[i];
956            let edge_end = ref_verts[(i + 1) % nref];
957            let edge_dir = sub(edge_end, edge_start);
958            let side_normal = normalize(cross(ref_normal, edge_dir));
959            let plane_d = dot(side_normal, edge_start);
960            clipped = Self::clip_polygon_by_plane(&clipped, side_normal, plane_d);
961        }
962        let ref_plane_d = dot(ref_normal, ref_verts[0]);
963        clipped.retain(|p| dot(*p, ref_normal) <= ref_plane_d + 1e-9);
964        clipped
965    }
966}
967/// SAT test between an OBB and a capsule.
968pub struct ObbCapsuleCollision;
969impl ObbCapsuleCollision {
970    /// Compute the closest point on the OBB surface to a query point.
971    pub fn closest_point_on_obb(obb: &Obb, q: [f64; 3]) -> [f64; 3] {
972        let d = sub(q, obb.center);
973        let mut result = obb.center;
974        for i in 0..3 {
975            let dist = dot(d, obb.axes[i]);
976            let clamped = dist.clamp(-obb.half_extents[i], obb.half_extents[i]);
977            result = add(result, scale(obb.axes[i], clamped));
978        }
979        result
980    }
981    /// Test OBB vs Capsule for intersection.
982    ///
983    /// Returns `Some(depth, contact_point, normal)` if intersecting.
984    pub fn test(obb: &Obb, capsule: &Capsule) -> Option<(f64, [f64; 3], [f64; 3])> {
985        let closest_on_axis = capsule.closest_axis_point(obb.center);
986        let closest_on_obb = Self::closest_point_on_obb(obb, closest_on_axis);
987        let diff = sub(closest_on_axis, closest_on_obb);
988        let dist = length(diff);
989        if dist < capsule.radius {
990            let depth = capsule.radius - dist;
991            let normal = if dist > 1e-10 {
992                scale(diff, 1.0 / dist)
993            } else {
994                let center_diff = sub(closest_on_axis, obb.center);
995                let cl = length(center_diff);
996                if cl > 1e-10 {
997                    scale(center_diff, 1.0 / cl)
998                } else {
999                    [0.0, 1.0, 0.0]
1000                }
1001            };
1002            let contact_point = add(closest_on_obb, scale(normal, depth * 0.5));
1003            Some((depth, contact_point, normal))
1004        } else {
1005            None
1006        }
1007    }
1008}
1009/// SAT test between two arbitrary convex polyhedra.
1010pub struct ConvexPolyhedraSat;
1011impl ConvexPolyhedraSat {
1012    /// Test intersection between two convex polyhedra.
1013    ///
1014    /// Tests all face normals of both polyhedra plus all edge-cross-product axes.
1015    /// Returns `Some(contact)` if the polyhedra overlap, `None` if separated.
1016    pub fn test(a: &ConvexPolyhedron, b: &ConvexPolyhedron) -> Option<PolyhedraContact> {
1017        let mut min_depth = f64::INFINITY;
1018        let mut best_normal = [0.0_f64, 1.0, 0.0];
1019        let mut best_feature = ContactFeature::FaceFace {
1020            face_a: 0,
1021            face_b: 0,
1022        };
1023        for (i, &n) in a.face_normals.iter().enumerate() {
1024            let len = length(n);
1025            if len < 1e-12 {
1026                continue;
1027            }
1028            let axis = scale(n, 1.0 / len);
1029            let (a_min, a_max) = a.project(axis);
1030            let (b_min, b_max) = b.project(axis);
1031            let overlap = a_max.min(b_max) - a_min.max(b_min);
1032            if overlap < 0.0 {
1033                return None;
1034            }
1035            if overlap < min_depth {
1036                min_depth = overlap;
1037                best_normal = axis;
1038                best_feature = ContactFeature::FaceFace {
1039                    face_a: i,
1040                    face_b: 0,
1041                };
1042            }
1043        }
1044        for (j, &n) in b.face_normals.iter().enumerate() {
1045            let len = length(n);
1046            if len < 1e-12 {
1047                continue;
1048            }
1049            let axis = scale(n, 1.0 / len);
1050            let (a_min, a_max) = a.project(axis);
1051            let (b_min, b_max) = b.project(axis);
1052            let overlap = a_max.min(b_max) - a_min.max(b_min);
1053            if overlap < 0.0 {
1054                return None;
1055            }
1056            if overlap < min_depth {
1057                min_depth = overlap;
1058                best_normal = axis;
1059                best_feature = ContactFeature::FaceFace {
1060                    face_a: 0,
1061                    face_b: j,
1062                };
1063            }
1064        }
1065        for (ia, &ea) in a.edge_directions.iter().enumerate() {
1066            for (ib, &eb) in b.edge_directions.iter().enumerate() {
1067                let axis = cross(ea, eb);
1068                let len = length(axis);
1069                if len < 1e-12 {
1070                    continue;
1071                }
1072                let axis = scale(axis, 1.0 / len);
1073                let (a_min, a_max) = a.project(axis);
1074                let (b_min, b_max) = b.project(axis);
1075                let overlap = a_max.min(b_max) - a_min.max(b_min);
1076                if overlap < 0.0 {
1077                    return None;
1078                }
1079                if overlap < min_depth {
1080                    min_depth = overlap;
1081                    best_normal = axis;
1082                    best_feature = ContactFeature::EdgeEdge {
1083                        edge_a: ia,
1084                        edge_b: ib,
1085                    };
1086                }
1087            }
1088        }
1089        Some(PolyhedraContact {
1090            normal: best_normal,
1091            depth: min_depth,
1092            feature: best_feature,
1093        })
1094    }
1095    /// Returns `true` if the two convex polyhedra are separated.
1096    pub fn is_separated(a: &ConvexPolyhedron, b: &ConvexPolyhedron) -> bool {
1097        Self::test(a, b).is_none()
1098    }
1099}
1100/// A node in a flat-array OBB BVH.
1101#[derive(Clone, Debug)]
1102pub struct ObbBvhNode {
1103    /// Bounding OBB for this node.
1104    pub bounds: Obb,
1105    /// Left child index (`usize::MAX` = no child / leaf).
1106    pub left: usize,
1107    /// Right child index (`usize::MAX` = no child / leaf).
1108    pub right: usize,
1109    /// For leaf nodes: geometry index.
1110    pub geometry_index: Option<usize>,
1111}
1112impl ObbBvhNode {
1113    /// Returns `true` if this is a leaf node.
1114    pub fn is_leaf(&self) -> bool {
1115        self.left == usize::MAX && self.right == usize::MAX
1116    }
1117}