Skip to main content

oxiphysics_collision/narrowphase/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5pub use super::specialized::*;
6
7use super::functions::{add3, cross3, dot3, len3, normalize3, scale3, shape_shape_contact, sub3};
8
9/// Result of a ray cast against a shape.
10#[derive(Debug, Clone)]
11pub struct RayCastResult {
12    /// Whether the ray hit the shape.
13    pub hit: bool,
14    /// Time of intersection (units of ray direction magnitude).
15    pub toi: f64,
16    /// Hit point in world space.
17    pub hit_point: [f64; 3],
18    /// Outward surface normal at the hit point.
19    pub normal: [f64; 3],
20}
21/// A unified contact result produced by the routing layer.
22#[derive(Debug, Clone)]
23pub struct NarrowPhaseContact {
24    /// Contact normal pointing from shape A toward shape B (unit length).
25    pub normal: [f64; 3],
26    /// Penetration depth (positive = overlapping).
27    pub depth: f64,
28    /// World-space witness point on shape A's surface.
29    pub point_a: [f64; 3],
30    /// World-space witness point on shape B's surface.
31    pub point_b: [f64; 3],
32}
33impl NarrowPhaseContact {
34    /// Return a copy with the normal flipped and witness points swapped.
35    pub fn flipped(&self) -> Self {
36        Self {
37            normal: scale3(self.normal, -1.0),
38            depth: self.depth,
39            point_a: self.point_b,
40            point_b: self.point_a,
41        }
42    }
43    /// Midpoint between the two witness points.
44    pub fn midpoint(&self) -> [f64; 3] {
45        scale3(add3(self.point_a, self.point_b), 0.5)
46    }
47}
48/// A compound shape: a collection of child shapes with local offsets.
49#[derive(Debug, Clone)]
50pub struct CompoundShape {
51    /// Child shapes in world space (pre-transformed by the caller).
52    pub children: Vec<ShapeKind>,
53}
54impl CompoundShape {
55    /// Create a compound shape from world-space children.
56    pub fn new(children: Vec<ShapeKind>) -> Self {
57        CompoundShape { children }
58    }
59    /// Compute the AABB enclosing all children.
60    pub fn aabb(&self) -> ([f64; 3], [f64; 3]) {
61        let mut mn = [f64::INFINITY; 3];
62        let mut mx = [f64::NEG_INFINITY; 3];
63        for child in &self.children {
64            let (cmin, cmax) = child.aabb();
65            for i in 0..3 {
66                mn[i] = mn[i].min(cmin[i]);
67                mx[i] = mx[i].max(cmax[i]);
68            }
69        }
70        (mn, mx)
71    }
72}
73/// Geometric feature that produced a contact (for incremental warm-starting).
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub enum ContactFeature {
76    /// Contact between two faces.
77    FaceFace {
78        /// Index of face A.
79        face_a: u32,
80        /// Index of face B.
81        face_b: u32,
82    },
83    /// Contact between a face and an edge.
84    FaceEdge {
85        /// Index of the face.
86        face: u32,
87        /// Index of the edge.
88        edge: u32,
89    },
90    /// Contact between two edges.
91    EdgeEdge {
92        /// Index of edge A.
93        edge_a: u32,
94        /// Index of edge B.
95        edge_b: u32,
96    },
97    /// Contact between a vertex and a face.
98    VertexFace {
99        /// Index of the vertex.
100        vertex: u32,
101        /// Index of the face.
102        face: u32,
103    },
104    /// Unknown / unclassified feature (e.g. from GJK fallback).
105    Unknown,
106}
107/// Post-processing options applied to a [`NarrowPhaseContact`] after it is
108/// produced by the routing layer.
109#[derive(Debug, Clone)]
110pub struct ContactFilter {
111    /// Minimum depth to report a contact. Contacts shallower than this are
112    /// discarded (default: 0.0).
113    pub min_depth: f64,
114    /// Maximum depth that will be reported; deeper contacts are clamped.
115    /// Set to `f64::INFINITY` to disable clamping.
116    pub max_depth: f64,
117    /// If `true`, negate the contact normal before returning it.
118    pub flip_normal: bool,
119}
120impl ContactFilter {
121    /// Apply this filter to a contact result.
122    ///
123    /// Returns `None` if the contact should be discarded.
124    pub fn apply(&self, mut c: NarrowPhaseContact) -> Option<NarrowPhaseContact> {
125        if c.depth < self.min_depth {
126            return None;
127        }
128        if c.depth > self.max_depth {
129            c.depth = self.max_depth;
130        }
131        if self.flip_normal {
132            c.normal = scale3(c.normal, -1.0);
133        }
134        Some(c)
135    }
136}
137/// A narrowphase contact enriched with feature information.
138#[derive(Debug, Clone)]
139pub struct FeatureContact {
140    /// Underlying geometry contact.
141    pub contact: NarrowPhaseContact,
142    /// The geometric feature responsible for this contact.
143    pub feature: ContactFeature,
144}
145impl FeatureContact {
146    /// Wrap a plain contact with an `Unknown` feature tag.
147    pub fn from_plain(c: NarrowPhaseContact) -> Self {
148        FeatureContact {
149            contact: c,
150            feature: ContactFeature::Unknown,
151        }
152    }
153}
154/// Processes multiple broadphase pairs in a single call.
155///
156/// Pairs are described as indices into a shared slice of [`ShapeKind`] values.
157/// Results are returned in the same order as the input pairs; pairs that did
158/// not produce a contact get `None`.
159#[derive(Default)]
160pub struct BatchNarrowPhase {
161    /// Post-processing filter applied to every contact.
162    pub filter: ContactFilter,
163}
164impl BatchNarrowPhase {
165    /// Create a `BatchNarrowPhase` with default settings.
166    pub fn new() -> Self {
167        Self::default()
168    }
169    /// Run the narrow phase for all `pairs`.
170    ///
171    /// Returns one `Option<NarrowPhaseContact>` per input pair.
172    pub fn run(
173        &self,
174        shapes: &[ShapeKind],
175        pairs: &[(usize, usize)],
176    ) -> Vec<Option<NarrowPhaseContact>> {
177        pairs
178            .iter()
179            .map(|(i, j)| {
180                if *i >= shapes.len() || *j >= shapes.len() {
181                    return None;
182                }
183                let contact = shape_shape_contact(&shapes[*i], &shapes[*j])?;
184                self.filter.apply(contact)
185            })
186            .collect()
187    }
188    /// Run and collect only pairs that produced a contact.
189    pub fn run_compact(
190        &self,
191        shapes: &[ShapeKind],
192        pairs: &[(usize, usize)],
193    ) -> Vec<((usize, usize), NarrowPhaseContact)> {
194        pairs
195            .iter()
196            .filter_map(|(i, j)| {
197                if *i >= shapes.len() || *j >= shapes.len() {
198                    return None;
199                }
200                let contact = shape_shape_contact(&shapes[*i], &shapes[*j])?;
201                let filtered = self.filter.apply(contact)?;
202                Some(((*i, *j), filtered))
203            })
204            .collect()
205    }
206}
207/// Result of a point-in-shape query.
208#[derive(Debug, Clone)]
209pub struct PointQueryResult {
210    /// Whether the point is inside the shape.
211    pub is_inside: bool,
212    /// Closest point on the shape's surface to the query point.
213    pub closest_surface_point: [f64; 3],
214    /// Signed distance from the query point to the surface (negative = inside).
215    pub signed_distance: f64,
216    /// Outward surface normal at the closest point.
217    pub normal: [f64; 3],
218}
219/// A triangle mesh (concave shape) represented as a flat list of triangles.
220///
221/// Each triangle is three consecutive vertices: `[v0, v1, v2]`.
222#[derive(Debug, Clone)]
223pub struct TriangleMesh {
224    /// Flat list of triangle vertices; length must be a multiple of 3.
225    pub triangles: Vec<[f64; 3]>,
226}
227impl TriangleMesh {
228    /// Create a new triangle mesh.
229    pub fn new(triangles: Vec<[f64; 3]>) -> Self {
230        debug_assert!(
231            triangles.len().is_multiple_of(3),
232            "triangle count must be a multiple of 3"
233        );
234        TriangleMesh { triangles }
235    }
236    /// Number of triangles.
237    pub fn tri_count(&self) -> usize {
238        self.triangles.len() / 3
239    }
240    /// Get the three vertices of triangle `i`.
241    pub fn triangle(&self, i: usize) -> [[f64; 3]; 3] {
242        let base = i * 3;
243        [
244            self.triangles[base],
245            self.triangles[base + 1],
246            self.triangles[base + 2],
247        ]
248    }
249    /// Compute the face normal for triangle `i` (not normalized).
250    pub fn face_normal(&self, i: usize) -> [f64; 3] {
251        let [v0, v1, v2] = self.triangle(i);
252        let e0 = sub3(v1, v0);
253        let e1 = sub3(v2, v0);
254        cross3(e0, e1)
255    }
256}
257/// Plain-data descriptor for a convex shape, used by the routing layer.
258///
259/// All geometry is expressed in world space so no transform arithmetic is
260/// needed by the dispatcher.
261#[derive(Debug, Clone)]
262pub enum ShapeKind {
263    /// A sphere with centre and radius.
264    Sphere {
265        /// World-space centre.
266        center: [f64; 3],
267        /// Radius.
268        radius: f64,
269    },
270    /// An axis-aligned box with centre and half-extents.
271    Box {
272        /// World-space centre.
273        center: [f64; 3],
274        /// Half-extents along X, Y, Z.
275        half_extents: [f64; 3],
276    },
277    /// A capsule defined by two endpoint centres and a radius.
278    Capsule {
279        /// First endpoint centre.
280        p0: [f64; 3],
281        /// Second endpoint centre.
282        p1: [f64; 3],
283        /// Radius.
284        radius: f64,
285    },
286    /// An infinite plane through the origin with a given normal.
287    Plane {
288        /// Unit normal.
289        normal: [f64; 3],
290        /// Distance from origin along the normal (plane equation: n·x = d).
291        offset: f64,
292    },
293    /// A convex polyhedron described by its support vertices.
294    Convex {
295        /// Vertices in world space.
296        vertices: Vec<[f64; 3]>,
297    },
298}
299impl ShapeKind {
300    /// Returns an axis-aligned bounding box `(min, max)` for this shape.
301    pub fn aabb(&self) -> ([f64; 3], [f64; 3]) {
302        match self {
303            ShapeKind::Sphere { center, radius } => (
304                [center[0] - radius, center[1] - radius, center[2] - radius],
305                [center[0] + radius, center[1] + radius, center[2] + radius],
306            ),
307            ShapeKind::Box {
308                center,
309                half_extents,
310            } => (sub3(*center, *half_extents), add3(*center, *half_extents)),
311            ShapeKind::Capsule { p0, p1, radius } => {
312                let mn = [
313                    p0[0].min(p1[0]) - radius,
314                    p0[1].min(p1[1]) - radius,
315                    p0[2].min(p1[2]) - radius,
316                ];
317                let mx = [
318                    p0[0].max(p1[0]) + radius,
319                    p0[1].max(p1[1]) + radius,
320                    p0[2].max(p1[2]) + radius,
321                ];
322                (mn, mx)
323            }
324            ShapeKind::Plane { normal, offset } => {
325                let n = *normal;
326                let d = *offset;
327                let _ = (n, d);
328                ([-1e15; 3], [1e15; 3])
329            }
330            ShapeKind::Convex { vertices } => {
331                let mut mn = [f64::INFINITY; 3];
332                let mut mx = [f64::NEG_INFINITY; 3];
333                for v in vertices {
334                    for i in 0..3 {
335                        mn[i] = mn[i].min(v[i]);
336                        mx[i] = mx[i].max(v[i]);
337                    }
338                }
339                (mn, mx)
340            }
341        }
342    }
343    /// The largest sphere that bounds this shape (bounding sphere).
344    pub fn bounding_radius(&self) -> f64 {
345        match self {
346            ShapeKind::Sphere { radius, .. } => *radius,
347            ShapeKind::Box { half_extents, .. } => len3(*half_extents),
348            ShapeKind::Capsule { p0, p1, radius } => len3(sub3(*p1, *p0)) * 0.5 + radius,
349            ShapeKind::Plane { .. } => f64::INFINITY,
350            ShapeKind::Convex { vertices } => {
351                vertices.iter().map(|v| len3(*v)).fold(0.0_f64, f64::max)
352            }
353        }
354    }
355    /// Support function: point on the shape furthest in direction `dir`.
356    pub fn support(&self, dir: [f64; 3]) -> [f64; 3] {
357        match self {
358            ShapeKind::Sphere { center, radius } => {
359                let d = normalize3(dir);
360                add3(*center, scale3(d, *radius))
361            }
362            ShapeKind::Box {
363                center,
364                half_extents,
365            } => [
366                center[0] + half_extents[0] * dir[0].signum(),
367                center[1] + half_extents[1] * dir[1].signum(),
368                center[2] + half_extents[2] * dir[2].signum(),
369            ],
370            ShapeKind::Capsule { p0, p1, radius } => {
371                let d0 = dot3(*p0, dir);
372                let d1 = dot3(*p1, dir);
373                let base = if d0 > d1 { *p0 } else { *p1 };
374                add3(base, scale3(normalize3(dir), *radius))
375            }
376            ShapeKind::Plane { normal, offset } => scale3(*normal, *offset + 1e9),
377            ShapeKind::Convex { vertices } => vertices
378                .iter()
379                .max_by(|a, b| {
380                    dot3(**a, dir)
381                        .partial_cmp(&dot3(**b, dir))
382                        .unwrap_or(std::cmp::Ordering::Equal)
383                })
384                .copied()
385                .unwrap_or([0.0; 3]),
386        }
387    }
388}
389/// Result of a segment cast against a shape.
390#[derive(Debug, Clone)]
391pub struct SegmentCastResult {
392    /// Whether the segment hit the shape.
393    pub hit: bool,
394    /// Parameter along the segment `[0, 1]` where the hit occurred.
395    pub t: f64,
396    /// Hit point in world space.
397    pub hit_point: [f64; 3],
398    /// Outward surface normal at the hit point.
399    pub normal: [f64; 3],
400}