Skip to main content

box3d_rust/compound/
types.rs

1//! Compound shape types and accessors.
2//!
3//! Maps C's compound group in `include/box3d/types.h` and the getter APIs in
4//! `compound.c` / `collision.h`.
5//!
6//! SPDX-FileCopyrightText: 2025 Erin Catto
7//! SPDX-License-Identifier: MIT
8
9use crate::dynamic_tree::{DynamicTree, DYNAMIC_TREE_VERSION};
10use crate::geometry::{Capsule, ShapeType, Sphere, SurfaceMaterial};
11use crate::hull::{HullData, HULL_VERSION};
12use crate::math_functions::{Transform, Vec3, TRANSFORM_IDENTITY, VEC3_ONE};
13use crate::mesh::{Mesh, MeshData, MESH_VERSION};
14
15/// The compound version depends on the tree, mesh, and hull versions.
16/// (B3_COMPOUND_VERSION)
17pub const COMPOUND_VERSION: u64 =
18    0x8307_78DB_0708_6EB4u64 ^ DYNAMIC_TREE_VERSION ^ MESH_VERSION ^ HULL_VERSION;
19
20/// Meshes used in compounds have limited space for materials.
21/// (B3_MAX_COMPOUND_MESH_MATERIALS)
22pub const MAX_COMPOUND_MESH_MATERIALS: usize = 4;
23
24/// Size of the C `b3CompoundData` header on 64-bit (MSVC/GCC).
25pub const COMPOUND_DATA_SIZE: usize = 144;
26
27/// Size of the C `b3DynamicTree` on 64-bit.
28pub const DYNAMIC_TREE_SIZE: usize = 80;
29
30/// Size of the C `b3TreeNode` (union layout).
31pub const TREE_NODE_SIZE: usize = 48;
32
33/// Size of the C `b3HullInstance`.
34pub const HULL_INSTANCE_SIZE: usize = 36;
35
36/// Size of the C `b3MeshInstance`.
37pub const MESH_INSTANCE_SIZE: usize = 60;
38
39/// Size of the C `b3CompoundCapsule` / `b3CompoundSphere`.
40pub const COMPOUND_CONVEX_SIZE: usize = 32;
41
42/// Definition for a capsule in a compound shape. (b3CompoundCapsuleDef)
43#[derive(Debug, Clone, Copy)]
44pub struct CompoundCapsuleDef {
45    pub capsule: Capsule,
46    pub material: SurfaceMaterial,
47}
48
49/// Definition for a convex hull in a compound shape. (b3CompoundHullDef)
50#[derive(Debug, Clone, Copy)]
51pub struct CompoundHullDef<'a> {
52    pub hull: &'a HullData,
53    pub transform: Transform,
54    pub material: SurfaceMaterial,
55}
56
57/// Definition for a triangle mesh in a compound shape. (b3CompoundMeshDef)
58#[derive(Debug, Clone, Copy)]
59pub struct CompoundMeshDef<'a> {
60    pub mesh_data: &'a MeshData,
61    pub transform: Transform,
62    pub scale: Vec3,
63    pub materials: &'a [SurfaceMaterial],
64}
65
66/// Definition for a sphere in a compound shape. (b3CompoundSphereDef)
67#[derive(Debug, Clone, Copy)]
68pub struct CompoundSphereDef {
69    pub sphere: Sphere,
70    pub material: SurfaceMaterial,
71}
72
73/// Definition for creating a compound shape. (b3CompoundDef)
74#[derive(Debug, Clone, Default)]
75pub struct CompoundDef<'a> {
76    pub capsules: &'a [CompoundCapsuleDef],
77    pub hulls: &'a [CompoundHullDef<'a>],
78    pub meshes: &'a [CompoundMeshDef<'a>],
79    pub spheres: &'a [CompoundSphereDef],
80}
81
82/// A capsule that lives in a compound. (b3CompoundCapsule)
83#[derive(Debug, Clone, Copy, PartialEq, Default)]
84pub struct CompoundCapsule {
85    pub capsule: Capsule,
86    pub material_index: i32,
87}
88
89/// A hull that lives in a compound. (b3CompoundHull)
90#[derive(Debug, Clone, Copy)]
91pub struct CompoundHull<'a> {
92    pub hull: &'a HullData,
93    pub transform: Transform,
94    pub material_index: i32,
95}
96
97/// A mesh with non-uniform scale that lives in a compound. (b3CompoundMesh)
98#[derive(Debug, Clone, Copy)]
99pub struct CompoundMesh<'a> {
100    pub mesh_data: &'a MeshData,
101    pub transform: Transform,
102    pub scale: Vec3,
103    pub material_indices: [i32; MAX_COMPOUND_MESH_MATERIALS],
104}
105
106/// A sphere that lives in a compound. (b3CompoundSphere)
107#[derive(Debug, Clone, Copy, PartialEq, Default)]
108pub struct CompoundSphere {
109    pub sphere: Sphere,
110    pub material_index: i32,
111}
112
113/// Internal hull instance stored in the compound (offset resolved at create).
114#[derive(Debug, Clone, Copy)]
115pub(crate) struct HullInstance {
116    pub transform: Transform,
117    /// Index into [`CompoundData::shared_hulls`].
118    pub shared_index: u32,
119    pub material_index: u32,
120    /// Byte offset into the serialized blob (for to_bytes parity).
121    pub hull_offset: u32,
122}
123
124/// Internal mesh instance stored in the compound.
125#[derive(Debug, Clone, Copy)]
126pub(crate) struct MeshInstance {
127    pub transform: Transform,
128    pub scale: Vec3,
129    /// Index into [`CompoundData::shared_meshes`].
130    pub shared_index: u32,
131    pub material_indices: [u32; MAX_COMPOUND_MESH_MATERIALS],
132    /// Byte offset into the serialized blob (for to_bytes parity).
133    pub mesh_offset: u32,
134}
135
136/// Child geometry of a compound. (union half of b3ChildShape)
137#[derive(Debug, Clone, Copy)]
138pub enum ChildGeometry<'a> {
139    Capsule(Capsule),
140    Hull(&'a HullData),
141    Mesh(Mesh<'a>),
142    Sphere(Sphere),
143}
144
145/// Child shape of a compound. (b3ChildShape)
146#[derive(Debug, Clone, Copy)]
147pub struct ChildShape<'a> {
148    pub geometry: ChildGeometry<'a>,
149    pub transform: Transform,
150    pub material_indices: [i32; MAX_COMPOUND_MESH_MATERIALS],
151    pub shape_type: ShapeType,
152}
153
154/// The runtime data for a baked compound shape. (b3CompoundData)
155///
156/// Owned arrays replace C's trailing-blob offsets. Offsets are retained so
157/// [`CompoundData::to_bytes`] / [`from_bytes`](crate::compound::convert_bytes_to_compound)
158/// reproduce the contiguous layout.
159#[derive(Debug, Clone)]
160pub struct CompoundData {
161    pub version: u64,
162    pub byte_count: i32,
163    pub node_offset: i32,
164    pub tree: DynamicTree,
165    pub material_offset: i32,
166    pub material_count: i32,
167    pub capsule_offset: i32,
168    pub capsule_count: i32,
169    pub hull_offset: i32,
170    pub hull_count: i32,
171    pub shared_hull_count: i32,
172    pub mesh_offset: i32,
173    pub mesh_count: i32,
174    pub shared_mesh_count: i32,
175    pub sphere_offset: i32,
176    pub sphere_count: i32,
177    pub materials: Vec<SurfaceMaterial>,
178    pub capsules: Vec<CompoundCapsule>,
179    pub(crate) hull_instances: Vec<HullInstance>,
180    pub shared_hulls: Vec<HullData>,
181    pub(crate) mesh_instances: Vec<MeshInstance>,
182    pub shared_meshes: Vec<MeshData>,
183    pub spheres: Vec<CompoundSphere>,
184}
185
186impl Default for CompoundData {
187    fn default() -> Self {
188        Self {
189            version: COMPOUND_VERSION,
190            byte_count: 0,
191            node_offset: 0,
192            tree: DynamicTree::new(0),
193            material_offset: 0,
194            material_count: 0,
195            capsule_offset: 0,
196            capsule_count: 0,
197            hull_offset: 0,
198            hull_count: 0,
199            shared_hull_count: 0,
200            mesh_offset: 0,
201            mesh_count: 0,
202            shared_mesh_count: 0,
203            sphere_offset: 0,
204            sphere_count: 0,
205            materials: Vec::new(),
206            capsules: Vec::new(),
207            hull_instances: Vec::new(),
208            shared_hulls: Vec::new(),
209            mesh_instances: Vec::new(),
210            shared_meshes: Vec::new(),
211            spheres: Vec::new(),
212        }
213    }
214}
215
216/// Compound materials. (b3GetCompoundMaterials)
217pub fn get_compound_materials(compound: &CompoundData) -> &[SurfaceMaterial] {
218    if compound.material_offset == 0 {
219        return &[];
220    }
221    &compound.materials
222}
223
224/// Capsule at index. (b3GetCompoundCapsule)
225pub fn get_compound_capsule(compound: &CompoundData, index: i32) -> CompoundCapsule {
226    debug_assert!(0 <= index && index < compound.capsule_count && compound.capsule_offset > 0);
227    if compound.capsule_offset == 0 {
228        return CompoundCapsule::default();
229    }
230    compound.capsules[index as usize]
231}
232
233/// Hull at index. (b3GetCompoundHull)
234pub fn get_compound_hull(compound: &CompoundData, index: i32) -> CompoundHull<'_> {
235    debug_assert!(0 <= index && index < compound.hull_count && compound.hull_offset > 0);
236    if compound.hull_offset == 0 {
237        return CompoundHull {
238            hull: &compound.shared_hulls[0],
239            transform: TRANSFORM_IDENTITY,
240            material_index: 0,
241        };
242    }
243    let inst = &compound.hull_instances[index as usize];
244    CompoundHull {
245        hull: &compound.shared_hulls[inst.shared_index as usize],
246        transform: inst.transform,
247        material_index: inst.material_index as i32,
248    }
249}
250
251/// Mesh at index. (b3GetCompoundMesh)
252pub fn get_compound_mesh(compound: &CompoundData, index: i32) -> CompoundMesh<'_> {
253    debug_assert!(0 <= index && index < compound.mesh_count && compound.mesh_offset > 0);
254    if compound.mesh_offset == 0 {
255        return CompoundMesh {
256            mesh_data: &compound.shared_meshes[0],
257            transform: TRANSFORM_IDENTITY,
258            scale: VEC3_ONE,
259            material_indices: [0; MAX_COMPOUND_MESH_MATERIALS],
260        };
261    }
262    let inst = &compound.mesh_instances[index as usize];
263    let mut material_indices = [0i32; MAX_COMPOUND_MESH_MATERIALS];
264    for i in 0..MAX_COMPOUND_MESH_MATERIALS {
265        material_indices[i] = inst.material_indices[i] as i32;
266    }
267    CompoundMesh {
268        mesh_data: &compound.shared_meshes[inst.shared_index as usize],
269        transform: inst.transform,
270        scale: inst.scale,
271        material_indices,
272    }
273}
274
275/// Sphere at index. (b3GetCompoundSphere)
276pub fn get_compound_sphere(compound: &CompoundData, index: i32) -> CompoundSphere {
277    debug_assert!(0 <= index && index < compound.sphere_count && compound.sphere_offset > 0);
278    if compound.sphere_offset == 0 {
279        return CompoundSphere::default();
280    }
281    compound.spheres[index as usize]
282}
283
284/// Child shape by flat index (capsules → hulls → meshes → spheres).
285/// (b3GetCompoundChild)
286pub fn get_compound_child(compound: &CompoundData, mut child_index: i32) -> ChildShape<'_> {
287    // Capsule?
288    if 0 <= child_index && child_index < compound.capsule_count {
289        let compound_capsule = get_compound_capsule(compound, child_index);
290        return ChildShape {
291            geometry: ChildGeometry::Capsule(compound_capsule.capsule),
292            transform: TRANSFORM_IDENTITY,
293            material_indices: [compound_capsule.material_index, 0, 0, 0],
294            shape_type: ShapeType::Capsule,
295        };
296    }
297    child_index -= compound.capsule_count;
298
299    // Hull?
300    if 0 <= child_index && child_index < compound.hull_count {
301        let compound_hull = get_compound_hull(compound, child_index);
302        return ChildShape {
303            geometry: ChildGeometry::Hull(compound_hull.hull),
304            transform: compound_hull.transform,
305            material_indices: [compound_hull.material_index, 0, 0, 0],
306            shape_type: ShapeType::Hull,
307        };
308    }
309    child_index -= compound.hull_count;
310
311    // Mesh?
312    if 0 <= child_index && child_index < compound.mesh_count {
313        let compound_mesh = get_compound_mesh(compound, child_index);
314        debug_assert_eq!(MAX_COMPOUND_MESH_MATERIALS, 4);
315        return ChildShape {
316            geometry: ChildGeometry::Mesh(Mesh {
317                data: compound_mesh.mesh_data,
318                scale: compound_mesh.scale,
319            }),
320            transform: compound_mesh.transform,
321            material_indices: compound_mesh.material_indices,
322            shape_type: ShapeType::Mesh,
323        };
324    }
325    child_index -= compound.mesh_count;
326
327    debug_assert!(0 <= child_index && child_index < compound.sphere_count);
328
329    let compound_sphere = get_compound_sphere(compound, child_index);
330    ChildShape {
331        geometry: ChildGeometry::Sphere(compound_sphere.sphere),
332        transform: TRANSFORM_IDENTITY,
333        material_indices: [compound_sphere.material_index, 0, 0, 0],
334        shape_type: ShapeType::Sphere,
335    }
336}