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}