Skip to main content

box3d_rust/manifold/
types.rs

1//! Manifold and feature-pair types from `include/box3d/types.h` and
2//! `src/manifold.h`.
3//!
4//! SPDX-FileCopyrightText: 2025 Erin Catto
5//! SPDX-License-Identifier: MIT
6
7use crate::constants::{MAX_MANIFOLD_POINTS, MAX_POINTS_PER_TRIANGLE};
8use crate::math_functions::{Vec3, VEC3_ZERO};
9
10/// Which shape owns a clipped feature edge. (b3FeatureOwner)
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12#[repr(u8)]
13pub enum FeatureOwner {
14    #[default]
15    ShapeA = 0,
16    ShapeB = 1,
17}
18
19/// Cached triangle feature for mesh/height-field contacts. (b3TriangleFeature)
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
21#[repr(u8)]
22pub enum TriangleFeature {
23    #[default]
24    None = 0,
25    TriangleFace = 1,
26    HullFace = 2,
27    /// v1-v2
28    Edge1 = 3,
29    /// v2-v3
30    Edge2 = 4,
31    /// v3-v1
32    Edge3 = 5,
33    Vertex1 = 6,
34    Vertex2 = 7,
35    Vertex3 = 8,
36}
37
38/// Contact points are always the result of two edges intersecting.
39/// It can be two edges of the same shape, which is just a shape vertex.
40/// Or a contact point can be the result of two edges crossing from different shapes.
41/// (b3FeaturePair)
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
43pub struct FeaturePair {
44    /// Incoming type (either edge on shape A or shape B)
45    pub owner1: u8,
46    /// Incoming edge index (into associated shape array)
47    pub index1: u8,
48    /// Outgoing type (either edge on shape A or shape B)
49    pub owner2: u8,
50    /// Outgoing edge index (into associated shape array)
51    pub index2: u8,
52}
53
54/// For single point contact, such as sphere-sphere, sphere-capsule, sphere-triangle.
55/// (b3FeaturePair_single)
56pub const FEATURE_PAIR_SINGLE: FeaturePair = FeaturePair {
57    owner1: 0,
58    index1: 0,
59    owner2: 0,
60    index2: 0,
61};
62
63/// Build a feature pair. (b3MakeFeaturePair)
64pub fn make_feature_pair(
65    owner1: FeatureOwner,
66    index1: i32,
67    owner2: FeatureOwner,
68    index2: i32,
69) -> FeaturePair {
70    debug_assert!((0..=u8::MAX as i32).contains(&index1));
71    debug_assert!((0..=u8::MAX as i32).contains(&index2));
72    FeaturePair {
73        owner1: owner1 as u8,
74        index1: index1 as u8,
75        owner2: owner2 as u8,
76        index2: index2 as u8,
77    }
78}
79
80/// Pack a feature pair into a 32-bit id. (b3MakeFeatureId)
81pub fn make_feature_id(pair: FeaturePair) -> u32 {
82    ((pair.owner1 as u32) << 24)
83        | ((pair.index1 as u32) << 16)
84        | ((pair.owner2 as u32) << 8)
85        | (pair.index2 as u32)
86}
87
88/// A local manifold point and normal in frame A. (b3LocalManifoldPoint)
89#[derive(Debug, Clone, Copy, PartialEq, Default)]
90pub struct LocalManifoldPoint {
91    /// Local point in frame A.
92    pub point: Vec3,
93    /// The contact point separation. Negative for overlap.
94    pub separation: f32,
95    /// The feature pair for this point.
96    pub pair: FeaturePair,
97    /// The triangle index when collide with a mesh or height-field.
98    pub triangle_index: i32,
99}
100
101/// A local manifold with no dynamic information. Used by collide functions.
102///
103/// Unlike C (which holds a `b3LocalManifoldPoint*` into an external buffer),
104/// this embeds a fixed [`MAX_POINTS_PER_TRIANGLE`] point array so mesh
105/// narrow-phase can gather up to 32 clip points before cluster reduction.
106/// Convex callers still pass `capacity == MAX_MANIFOLD_POINTS`.
107/// (b3LocalManifold)
108#[derive(Debug, Clone, Copy, PartialEq)]
109pub struct LocalManifold {
110    /// Local normal in frame A.
111    pub normal: Vec3,
112    /// The triangle normal.
113    pub triangle_normal: Vec3,
114    /// The manifold points.
115    pub points: [LocalManifoldPoint; MAX_POINTS_PER_TRIANGLE],
116    /// The number of manifold points. Only bounded by the buffer capacity.
117    pub point_count: i32,
118    /// The index of the triangle.
119    pub triangle_index: i32,
120    /// Vertex 1 index.
121    pub i1: i32,
122    /// Vertex 2 index.
123    pub i2: i32,
124    /// Vertex 3 index.
125    pub i3: i32,
126    /// The squared distance of a sphere from a triangle. For ghost collision reduction.
127    pub squared_distance: f32,
128    /// The triangle feature involved.
129    pub feature: TriangleFeature,
130    /// b3MeshEdgeFlags.
131    pub triangle_flags: i32,
132}
133
134impl Default for LocalManifold {
135    fn default() -> Self {
136        LocalManifold {
137            normal: VEC3_ZERO,
138            triangle_normal: VEC3_ZERO,
139            points: [LocalManifoldPoint::default(); MAX_POINTS_PER_TRIANGLE],
140            point_count: 0,
141            triangle_index: 0,
142            i1: 0,
143            i2: 0,
144            i3: 0,
145            squared_distance: 0.0,
146            feature: TriangleFeature::None,
147            triangle_flags: 0,
148        }
149    }
150}
151
152/// Clip vertex used by capsule/hull clipping. (b3ClipVertex)
153#[derive(Debug, Clone, Copy, PartialEq, Default)]
154pub(crate) struct ClipVertex {
155    pub position: Vec3,
156    pub separation: f32,
157    pub pair: FeaturePair,
158}
159
160/// Maximum vertices in a clipped polygon buffer. (B3_MAX_CLIP_POINTS)
161pub(crate) const MAX_CLIP_POINTS: usize = 64;
162
163/// Face SAT query result. (b3FaceQuery)
164#[derive(Debug, Clone, Copy, PartialEq, Default)]
165pub(crate) struct FaceQuery {
166    pub separation: f32,
167    pub face_index: i32,
168    pub vertex_index: i32,
169}
170
171/// Edge-pair SAT query result. (b3EdgeQuery)
172#[derive(Debug, Clone, Copy, PartialEq, Default)]
173pub(crate) struct EdgeQuery {
174    pub separation: f32,
175    pub index_a: i32,
176    pub index_b: i32,
177}
178
179/// Cached separating axis feature. (b3SeparatingFeature)
180#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
181#[repr(u8)]
182pub enum SeparatingFeature {
183    #[default]
184    InvalidAxis = 0,
185    BacksideAxis = 1,
186    FaceAxisA = 2,
187    FaceAxisB = 3,
188    EdgePairAxis = 4,
189    ClosestPointsAxis = 5,
190    /// Testing only
191    ManualFaceAxisA = 6,
192    /// Testing only
193    ManualFaceAxisB = 7,
194    /// Testing only
195    ManualEdgePairAxis = 8,
196}
197
198/// Separating axis test cache. Provides temporal acceleration of collision routines.
199/// (b3SATCache)
200#[derive(Debug, Clone, Copy, PartialEq, Default)]
201pub struct SatCache {
202    /// The separation when the cache is populated. Negative for overlap.
203    pub separation: f32,
204    /// [`SeparatingFeature`].
205    pub type_: u8,
206    /// Index of the feature on shape A.
207    pub index_a: u8,
208    /// Index of the feature on shape B.
209    pub index_b: u8,
210    /// Was the cache re-used?
211    pub hit: u8,
212}
213
214/// A manifold point is a contact point belonging to a contact manifold.
215/// Box3D uses speculative collision so some contact points may be separated.
216/// (b3ManifoldPoint)
217#[derive(Debug, Clone, Copy, PartialEq, Default)]
218pub struct ManifoldPoint {
219    /// Location of the contact point relative to body A center of mass in world space.
220    pub anchor_a: Vec3,
221    /// Location of the contact point relative to body B center of mass in world space.
222    pub anchor_b: Vec3,
223    /// Separation of the contact point; negative if penetrating.
224    pub separation: f32,
225    /// Cached separation used for contact recycling.
226    pub base_separation: f32,
227    /// Impulse along the manifold normal from the final sub-step.
228    pub normal_impulse: f32,
229    /// Total normal impulse applied during sub-stepping.
230    pub total_normal_impulse: f32,
231    /// Relative normal velocity pre-solve. Negative means approaching.
232    pub normal_velocity: f32,
233    /// Uniquely identifies a contact point between two shapes.
234    pub feature_id: u32,
235    /// Triangle index if one of the shapes is a mesh or height field.
236    pub triangle_index: i32,
237    /// Did this contact point exist in the previous step?
238    pub persisted: bool,
239}
240
241/// A contact manifold describes the contact points between colliding shapes.
242/// (b3Manifold)
243#[derive(Debug, Clone, Copy, PartialEq)]
244pub struct Manifold {
245    /// The manifold points. There may be 0 to [`MAX_MANIFOLD_POINTS`] valid points.
246    pub points: [ManifoldPoint; MAX_MANIFOLD_POINTS],
247    /// Unit normal in world space, points from shape A to shape B.
248    pub normal: Vec3,
249    /// Central friction angular impulse (applied about the normal).
250    pub twist_impulse: f32,
251    /// Central friction linear impulse.
252    pub friction_impulse: Vec3,
253    /// Rolling resistance angular impulse.
254    pub rolling_impulse: Vec3,
255    /// The number of contact points, 0 to 4.
256    pub point_count: i32,
257}
258
259impl Default for Manifold {
260    fn default() -> Self {
261        Manifold {
262            points: [ManifoldPoint::default(); MAX_MANIFOLD_POINTS],
263            normal: VEC3_ZERO,
264            twist_impulse: 0.0,
265            friction_impulse: VEC3_ZERO,
266            rolling_impulse: VEC3_ZERO,
267            point_count: 0,
268        }
269    }
270}