Skip to main content

oxiphysics_collision/query/
types.rs

1// Auto-generated module
2//
3// 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6
7/// A child shape entry: a boxed shape query together with its 4×4 world transform.
8pub type ShapeEntry = (Box<dyn ShapeQuery>, [[f64; 4]; 4]);
9
10/// A view frustum defined by 6 half-space planes.
11///
12/// Each plane is `[nx, ny, nz, d]` where `n·p + d >= 0` is the "inside" half-space.
13/// Planes should be: near, far, left, right, top, bottom.
14pub struct ViewFrustum {
15    /// The 6 frustum planes.
16    pub planes: [[f64; 4]; 6],
17}
18impl ViewFrustum {
19    /// Create a frustum from 6 plane coefficients.
20    pub fn new(planes: [[f64; 4]; 6]) -> Self {
21        Self { planes }
22    }
23    /// Test whether a sphere is potentially visible (intersects the frustum).
24    ///
25    /// Returns `false` only if the sphere is fully outside at least one plane.
26    pub fn test_sphere(&self, center: [f64; 3], radius: f64) -> bool {
27        for plane in &self.planes {
28            let n = [plane[0], plane[1], plane[2]];
29            let d = plane[3];
30            let sd = dot(center, n) + d;
31            if sd < -radius {
32                return false;
33            }
34        }
35        true
36    }
37    /// Test whether an AABB is potentially visible.
38    ///
39    /// Uses the p-vertex / n-vertex method for conservative rejection.
40    pub fn test_aabb(&self, min: [f64; 3], max: [f64; 3]) -> bool {
41        for plane in &self.planes {
42            let n = [plane[0], plane[1], plane[2]];
43            let d = plane[3];
44            let p = [
45                if n[0] >= 0.0 { max[0] } else { min[0] },
46                if n[1] >= 0.0 { max[1] } else { min[1] },
47                if n[2] >= 0.0 { max[2] } else { min[2] },
48            ];
49            if dot(p, n) + d < 0.0 {
50                return false;
51            }
52        }
53        true
54    }
55}
56/// Result of a ray cast in the all-pairs query.
57pub struct AllPairsRayResult {
58    /// Index into the input shapes array of the hit shape.
59    pub shape_index: usize,
60    /// Time of impact.
61    pub toi: f64,
62    /// Surface normal at the hit point.
63    pub normal: [f64; 3],
64    /// Hit point in world space.
65    pub point: [f64; 3],
66}
67/// Result of a sphere cast against the scene.
68pub struct SphereCastResult {
69    /// Index of the first shape hit.
70    pub shape_index: usize,
71    /// Time of impact.
72    pub toi: f64,
73    /// Contact normal at the hit (pointing from the hit shape toward the sphere centre).
74    pub normal: [f64; 3],
75}
76/// Result of a continuous time-of-impact (TOI) query between two moving spheres.
77pub struct ToiResult {
78    /// Time of impact in `[0, max_t]`.
79    pub toi: f64,
80    /// Contact normal at impact (from B toward A).
81    pub normal: [f64; 3],
82    /// Contact point in world space.
83    pub point: [f64; 3],
84}
85/// A shape and its distance from the query point.
86pub struct NearestShape {
87    /// Index of the shape in the input array.
88    pub index: usize,
89    /// Signed distance from the query point to the shape surface.
90    /// Negative if the point is inside the shape.
91    pub distance: f64,
92    /// Closest point on the shape surface to the query point.
93    pub closest_point: [f64; 3],
94}
95/// Compound shape composed of transformed child shapes.
96pub struct CompoundQuery {
97    /// Child shapes together with their world transforms.
98    pub shapes: Vec<ShapeEntry>,
99}
100impl CompoundQuery {
101    /// Create an empty compound shape.
102    pub fn new() -> Self {
103        CompoundQuery { shapes: Vec::new() }
104    }
105    /// Add a child shape with the given 4×4 world transform.
106    pub fn add_shape(&mut self, shape: Box<dyn ShapeQuery>, transform: [[f64; 4]; 4]) {
107        self.shapes.push((shape, transform));
108    }
109    /// Cast a ray against all child shapes and return the first (closest) hit.
110    pub fn ray_cast_any(
111        &self,
112        ray_origin: [f64; 3],
113        ray_dir: [f64; 3],
114        max_toi: f64,
115    ) -> Option<RayCastResult> {
116        let mut best: Option<RayCastResult> = None;
117        for (shape, transform) in &self.shapes {
118            let (local_origin, local_dir) = transform_ray(ray_origin, ray_dir, *transform);
119            let current_max = best.as_ref().map_or(max_toi, |b| b.toi);
120            if let Some(local_res) = shape.ray_cast(local_origin, local_dir, current_max) {
121                let world_point = transform_point(local_res.point, *transform);
122                let n = local_res.normal;
123                let t = transform;
124                let world_normal = normalize([
125                    t[0][0] * n[0] + t[0][1] * n[1] + t[0][2] * n[2],
126                    t[1][0] * n[0] + t[1][1] * n[1] + t[1][2] * n[2],
127                    t[2][0] * n[0] + t[2][1] * n[1] + t[2][2] * n[2],
128                ]);
129                let candidate = RayCastResult {
130                    toi: local_res.toi,
131                    normal: world_normal,
132                    point: world_point,
133                };
134                let replace = match &best {
135                    None => true,
136                    Some(b) => candidate.toi < b.toi,
137                };
138                if replace {
139                    best = Some(candidate);
140                }
141            }
142        }
143        best
144    }
145}
146/// Descriptor for a shape in the all-pairs query.
147pub enum QueryShapeRef<'a> {
148    /// A sphere.
149    Sphere(&'a QuerySphere),
150    /// An axis-aligned box.
151    Box(&'a QueryBox),
152    /// A capsule.
153    Capsule(&'a QueryCapsule),
154}
155/// Result of a successful ray cast against a shape.
156pub struct RayCastResult {
157    /// Time of impact along the ray (parametric distance).
158    pub toi: f64,
159    /// Surface normal at the hit point (world-space unit vector).
160    pub normal: [f64; 3],
161    /// Hit point in world space.
162    pub point: [f64; 3],
163}
164/// Sphere shape for queries.
165pub struct QuerySphere {
166    /// Centre of the sphere.
167    pub center: [f64; 3],
168    /// Radius.
169    pub radius: f64,
170}
171/// Result of a segment-to-segment closest-point query.
172pub struct SegmentDistResult {
173    /// Closest point on segment A.
174    pub point_a: [f64; 3],
175    /// Closest point on segment B.
176    pub point_b: [f64; 3],
177    /// Distance between the two closest points.
178    pub distance: f64,
179}
180/// Result of a closest-point query on a shape.
181pub struct PointQueryResult {
182    /// Closest point on (or inside) the shape surface.
183    pub point: [f64; 3],
184    /// Signed distance: negative when the query point is inside the shape.
185    pub distance: f64,
186    /// `true` when the query point lies inside the shape.
187    pub inside: bool,
188}
189/// Capsule shape for queries (line segment + radius).
190pub struct QueryCapsule {
191    /// First endpoint of the central segment.
192    pub p0: [f64; 3],
193    /// Second endpoint of the central segment.
194    pub p1: [f64; 3],
195    /// Radius of the capsule.
196    pub radius: f64,
197}
198impl QueryCapsule {
199    /// Closest point on the segment p0→p1 to `point`, returning the point and
200    /// the parameter t ∈ \[0,1\].
201    pub(super) fn closest_point_on_segment(&self, point: [f64; 3]) -> ([f64; 3], f64) {
202        let ab = sub(self.p1, self.p0);
203        let ap = sub(point, self.p0);
204        let len_sq = dot(ab, ab);
205        if len_sq < 1e-30 {
206            return (self.p0, 0.0);
207        }
208        let t = clamp(dot(ap, ab) / len_sq, 0.0, 1.0);
209        let closest = add(self.p0, scale(ab, t));
210        (closest, t)
211    }
212}
213/// Axis-aligned box shape for queries.
214pub struct QueryBox {
215    /// Centre of the box.
216    pub center: [f64; 3],
217    /// Half-extents along each axis.
218    pub half_extents: [f64; 3],
219}