Skip to main content

oxiphysics_collision/gjk_enhanced/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::{
6    ConvexShape, johnson_segment, johnson_tetrahedron, johnson_triangle, vlen, vlen_sq, vneg, vsub,
7};
8
9/// A sphere support shape.
10#[derive(Debug, Clone, Copy)]
11pub struct EnhSphere {
12    /// Centre of the sphere.
13    pub centre: [f64; 3],
14    /// Radius.
15    pub radius: f64,
16}
17impl EnhSphere {
18    /// Create a new sphere.
19    pub fn new(centre: [f64; 3], radius: f64) -> Self {
20        Self { centre, radius }
21    }
22}
23/// Helper: a shape translated by an offset.
24pub(super) struct TranslatedShape<'a> {
25    pub(super) shape: &'a dyn ConvexShape,
26    pub(super) offset: [f64; 3],
27}
28/// A Minkowski difference support point carrying witness points.
29#[derive(Debug, Clone, Copy, PartialEq)]
30pub struct SupportPoint {
31    /// Point in the Minkowski difference: `w = support_a(d) - support_b(-d)`.
32    pub w: [f64; 3],
33    /// Witness point on shape A.
34    pub a: [f64; 3],
35    /// Witness point on shape B.
36    pub b: [f64; 3],
37}
38impl SupportPoint {
39    /// Create a new support point.
40    pub fn new(w: [f64; 3], a: [f64; 3], b: [f64; 3]) -> Self {
41        Self { w, a, b }
42    }
43}
44/// A cached simplex used to warm-start the next GJK query.
45///
46/// Stores up to 4 support points from the previous frame.
47#[derive(Debug, Clone, Default)]
48pub struct WarmStartCache {
49    /// Cached simplex vertices.
50    pub points: Vec<SupportPoint>,
51    /// Last known search direction.
52    pub direction: [f64; 3],
53    /// Whether the cache is valid for use.
54    pub valid: bool,
55}
56impl WarmStartCache {
57    /// Create an empty warm-start cache.
58    pub fn new() -> Self {
59        Self {
60            points: Vec::with_capacity(4),
61            direction: [1.0, 0.0, 0.0],
62            valid: false,
63        }
64    }
65    /// Invalidate the cache (call after large body motions).
66    pub fn invalidate(&mut self) {
67        self.valid = false;
68        self.points.clear();
69    }
70    /// Update the cache after a successful GJK run.
71    pub fn update(&mut self, points: &[SupportPoint], direction: [f64; 3]) {
72        self.points.clear();
73        self.points.extend_from_slice(points);
74        self.direction = direction;
75        self.valid = true;
76    }
77}
78/// Result of the EPA penetration-depth query.
79#[derive(Debug, Clone)]
80pub struct EpaEnhancedResult {
81    /// Penetration depth.
82    pub depth: f64,
83    /// Contact normal pointing from B into A.
84    pub normal: [f64; 3],
85    /// Contact point (midpoint on both shapes).
86    pub contact_point: [f64; 3],
87    /// Number of EPA iterations performed.
88    pub iterations: u32,
89}
90/// Unified result from the combined GJK-EPA pipeline.
91#[derive(Debug, Clone)]
92pub struct ContactResult {
93    /// Whether the shapes are in contact (intersecting).
94    pub in_contact: bool,
95    /// Separation distance (positive if separated, negative if penetrating).
96    pub signed_distance: f64,
97    /// Contact or closest point on shape A.
98    pub point_a: [f64; 3],
99    /// Contact or closest point on shape B.
100    pub point_b: [f64; 3],
101    /// Normal pointing from B to A (only meaningful on contact).
102    pub normal: [f64; 3],
103    /// GJK metrics.
104    pub gjk_metrics: GjkMetrics,
105    /// EPA iterations (0 if no EPA was run).
106    pub epa_iterations: u32,
107}
108/// Result of an enhanced GJK query.
109#[derive(Debug, Clone)]
110pub struct GjkResult {
111    /// Whether the two shapes are intersecting.
112    pub intersecting: bool,
113    /// Closest point on shape A (only valid if not intersecting).
114    pub closest_a: [f64; 3],
115    /// Closest point on shape B (only valid if not intersecting).
116    pub closest_b: [f64; 3],
117    /// Squared distance between closest points (0 if intersecting).
118    pub dist_sq: f64,
119    /// The final simplex.
120    pub simplex: Vec<SupportPoint>,
121    /// Performance metrics.
122    pub metrics: GjkMetrics,
123}
124impl GjkResult {
125    /// Distance between the closest points (0 if intersecting).
126    pub fn distance(&self) -> f64 {
127        self.dist_sq.sqrt()
128    }
129}
130/// Approximate a torus as a convex shape for GJK by treating it as a
131/// rounded-rectangle cross-section sweep (approximate support).
132///
133/// The torus has major radius `R` (from centre to tube centre) and minor
134/// radius `r` (tube radius).
135#[derive(Debug, Clone, Copy)]
136pub struct TorusApprox {
137    /// Major radius.
138    pub major_r: f64,
139    /// Minor radius (tube radius).
140    pub minor_r: f64,
141    /// Centre of the torus.
142    pub centre: [f64; 3],
143}
144impl TorusApprox {
145    /// Create a new approximate torus shape.
146    pub fn new(centre: [f64; 3], major_r: f64, minor_r: f64) -> Self {
147        Self {
148            major_r,
149            minor_r,
150            centre,
151        }
152    }
153}
154/// Cached support query result for reducing redundant support evaluations.
155#[derive(Debug, Clone)]
156pub struct SupportCache {
157    /// Cached direction (last query direction).
158    pub last_dir: [f64; 3],
159    /// Cached support point for shape A.
160    pub support_a: [f64; 3],
161    /// Cached support point for shape B.
162    pub support_b: [f64; 3],
163    /// Number of cache hits.
164    pub hits: u64,
165    /// Total number of queries.
166    pub total: u64,
167}
168impl SupportCache {
169    /// Create a new empty support cache.
170    pub fn new() -> Self {
171        Self {
172            last_dir: [f64::NAN; 3],
173            support_a: [0.0; 3],
174            support_b: [0.0; 3],
175            hits: 0,
176            total: 0,
177        }
178    }
179    /// Attempt to retrieve cached results for the given direction.
180    ///
181    /// Returns `Some((sa, sb))` if the direction matches within tolerance.
182    pub fn get(&mut self, dir: [f64; 3], tol: f64) -> Option<([f64; 3], [f64; 3])> {
183        self.total += 1;
184        if self.last_dir[0].is_nan() {
185            return None;
186        }
187        let diff = vsub(dir, self.last_dir);
188        if vlen(diff) < tol {
189            self.hits += 1;
190            Some((self.support_a, self.support_b))
191        } else {
192            None
193        }
194    }
195    /// Store a new support result for the given direction.
196    pub fn put(&mut self, dir: [f64; 3], sa: [f64; 3], sb: [f64; 3]) {
197        self.last_dir = dir;
198        self.support_a = sa;
199        self.support_b = sb;
200    }
201    /// Cache hit rate as a fraction in \[0, 1\].
202    pub fn hit_rate(&self) -> f64 {
203        if self.total == 0 {
204            return 0.0;
205        }
206        self.hits as f64 / self.total as f64
207    }
208}
209/// Result of a time-of-impact (TOI) query.
210#[derive(Debug, Clone)]
211pub struct ToiResult {
212    /// Whether a collision occurs during `[0, 1]`.
213    pub hit: bool,
214    /// Time of impact in `[0, 1]` (1.0 if no hit).
215    pub toi: f64,
216    /// Contact normal at impact.
217    pub normal: [f64; 3],
218    /// Number of GJK iterations used.
219    pub iterations: u32,
220}
221/// A GJK simplex of dimension 0–3 (point, line, triangle, tetrahedron).
222#[derive(Debug, Clone)]
223pub struct EnhSimplex {
224    /// Simplex vertices (up to 4).
225    pub verts: Vec<SupportPoint>,
226}
227impl EnhSimplex {
228    /// Create an empty simplex.
229    pub fn new() -> Self {
230        Self {
231            verts: Vec::with_capacity(4),
232        }
233    }
234    /// Create a simplex pre-populated from a warm-start cache.
235    pub fn from_cache(cache: &WarmStartCache) -> Self {
236        Self {
237            verts: cache.points.clone(),
238        }
239    }
240    /// Current dimension (number of vertices − 1; −1 if empty).
241    pub fn dim(&self) -> i32 {
242        self.verts.len() as i32 - 1
243    }
244    /// Add a new vertex to the simplex.
245    pub fn add(&mut self, p: SupportPoint) {
246        self.verts.push(p);
247    }
248    /// Reduce the simplex to the sub-simplex closest to the origin.
249    ///
250    /// Returns the new search direction and whether the origin is contained.
251    pub fn reduce(&mut self) -> ([f64; 3], bool) {
252        match self.verts.len() {
253            0 => ([1.0, 0.0, 0.0], false),
254            1 => {
255                let w = vneg(self.verts[0].w);
256                (w, vlen_sq(self.verts[0].w) < 1e-18)
257            }
258            2 => self.reduce_line(),
259            3 => self.reduce_triangle(),
260            4 => self.reduce_tetrahedron(),
261            _ => ([1.0, 0.0, 0.0], false),
262        }
263    }
264    fn reduce_line(&mut self) -> ([f64; 3], bool) {
265        let a = self.verts[1].w;
266        let b = self.verts[0].w;
267        let (_, _, pt) = johnson_segment(a, b);
268        let dist = vlen_sq(pt);
269        if dist < 1e-18 {
270            return ([1.0, 0.0, 0.0], true);
271        }
272        let (la, lb, _) = johnson_segment(a, b);
273        if lb < 1e-10 {
274            self.verts.remove(0);
275        } else if la < 1e-10 {
276            self.verts.remove(1);
277        }
278        (vneg(pt), false)
279    }
280    fn reduce_triangle(&mut self) -> ([f64; 3], bool) {
281        let a = self.verts[2].w;
282        let b = self.verts[1].w;
283        let c = self.verts[0].w;
284        let (la, lb, lc, pt) = johnson_triangle(a, b, c);
285        let dist = vlen_sq(pt);
286        if dist < 1e-18 {
287            return ([1.0, 0.0, 0.0], true);
288        }
289        let mut new_verts = Vec::with_capacity(3);
290        let weights = [lc, lb, la];
291        for (i, &w) in weights.iter().enumerate() {
292            if w > 1e-10 {
293                new_verts.push(self.verts[i]);
294            }
295        }
296        self.verts = new_verts;
297        (vneg(pt), false)
298    }
299    fn reduce_tetrahedron(&mut self) -> ([f64; 3], bool) {
300        let a = self.verts[3].w;
301        let b = self.verts[2].w;
302        let c = self.verts[1].w;
303        let d = self.verts[0].w;
304        let (pt, inside) = johnson_tetrahedron(a, b, c, d);
305        if inside {
306            return ([1.0, 0.0, 0.0], true);
307        }
308        let faces = [(3usize, 2usize, 1usize), (3, 2, 0), (3, 1, 0), (2, 1, 0)];
309        let _ = pt;
310        let mut best_dist = f64::INFINITY;
311        let mut best_face = (3, 2, 1);
312        for &(i, j, k) in &faces {
313            let wi = self.verts[i].w;
314            let wj = self.verts[j].w;
315            let wk = self.verts[k].w;
316            let (_, _, _, fpt) = johnson_triangle(wi, wj, wk);
317            let d = vlen_sq(fpt);
318            if d < best_dist {
319                best_dist = d;
320                best_face = (i, j, k);
321            }
322        }
323        let (fi, fj, fk) = best_face;
324        self.verts = vec![self.verts[fk], self.verts[fj], self.verts[fi]];
325        let (_, _, _, closest) =
326            johnson_triangle(self.verts[2].w, self.verts[1].w, self.verts[0].w);
327        (vneg(closest), false)
328    }
329}
330/// A capsule support shape (cylinder with hemispherical caps).
331#[derive(Debug, Clone, Copy)]
332pub struct EnhCapsule {
333    /// Start point of the capsule axis.
334    pub a: [f64; 3],
335    /// End point of the capsule axis.
336    pub b: [f64; 3],
337    /// Radius.
338    pub radius: f64,
339}
340impl EnhCapsule {
341    /// Create a new capsule.
342    pub fn new(a: [f64; 3], b: [f64; 3], radius: f64) -> Self {
343        Self { a, b, radius }
344    }
345}
346/// Minkowski sum of two convex shapes (for rounded shapes).
347///
348/// The support of `A ⊕ B` in direction `d` is `support_A(d) + support_B(d)`.
349pub struct MinkowskiSum<'a> {
350    /// First shape.
351    pub a: &'a dyn ConvexShape,
352    /// Second shape (e.g. a sphere for rounding).
353    pub b: &'a dyn ConvexShape,
354}
355impl<'a> MinkowskiSum<'a> {
356    /// Create a Minkowski sum from two shapes.
357    pub fn new(a: &'a dyn ConvexShape, b: &'a dyn ConvexShape) -> Self {
358        Self { a, b }
359    }
360}
361/// An axis-aligned box support shape.
362#[derive(Debug, Clone, Copy)]
363pub struct EnhBox {
364    /// Centre of the box.
365    pub centre: [f64; 3],
366    /// Half-extents.
367    pub half: [f64; 3],
368}
369impl EnhBox {
370    /// Create a new axis-aligned box.
371    pub fn new(centre: [f64; 3], half: [f64; 3]) -> Self {
372        Self { centre, half }
373    }
374}
375/// A convex hull support shape (arbitrary point cloud).
376#[derive(Debug, Clone)]
377pub struct ConvexHull {
378    /// Vertices of the convex hull.
379    pub vertices: Vec<[f64; 3]>,
380}
381impl ConvexHull {
382    /// Create a convex hull from a list of vertices.
383    pub fn new(vertices: Vec<[f64; 3]>) -> Self {
384        Self { vertices }
385    }
386}
387/// An ellipsoid support shape.
388#[derive(Debug, Clone, Copy)]
389pub struct EnhEllipsoid {
390    /// Centre.
391    pub centre: [f64; 3],
392    /// Semi-axes (a, b, c).
393    pub semi_axes: [f64; 3],
394}
395impl EnhEllipsoid {
396    /// Create a new ellipsoid.
397    pub fn new(centre: [f64; 3], semi_axes: [f64; 3]) -> Self {
398        Self { centre, semi_axes }
399    }
400}
401/// Performance metrics collected during a GJK run.
402#[derive(Debug, Clone, Default)]
403pub struct GjkMetrics {
404    /// Number of iterations performed.
405    pub iterations: u32,
406    /// Number of support function calls.
407    pub support_calls: u32,
408    /// Number of simplex reductions performed.
409    pub simplex_reductions: u32,
410    /// Whether warm-starting was used.
411    pub warm_started: bool,
412    /// Whether the algorithm terminated early (convergence before max iter).
413    pub converged_early: bool,
414    /// Final distance squared (or 0 if intersecting).
415    pub final_dist_sq: f64,
416}
417impl GjkMetrics {
418    /// Create a new zeroed metrics structure.
419    pub fn new() -> Self {
420        Self::default()
421    }
422}
423/// An EPA polytope face.
424#[derive(Debug, Clone, Copy)]
425pub(super) struct EpaFaceE {
426    pub(super) indices: [usize; 3],
427    pub(super) normal: [f64; 3],
428    pub(super) dist: f64,
429}