Skip to main content

box3d_rust/compound/
create.rs

1//! Compound creation and destruction.
2//!
3//! Port of `b3CreateCompound` / `b3DestroyCompound` from compound.c.
4//!
5//! SPDX-FileCopyrightText: 2025 Erin Catto
6//! SPDX-License-Identifier: MIT
7
8use super::types::{
9    CompoundCapsule, CompoundData, CompoundDef, CompoundSphere, HullInstance, MeshInstance,
10    COMPOUND_CONVEX_SIZE, COMPOUND_DATA_SIZE, COMPOUND_VERSION, HULL_INSTANCE_SIZE,
11    MAX_COMPOUND_MESH_MATERIALS, MESH_INSTANCE_SIZE, TREE_NODE_SIZE,
12};
13use crate::constants::MAX_CHILD_SHAPES;
14use crate::core::NULL_INDEX;
15use crate::dynamic_tree::DynamicTree;
16use crate::geometry::{
17    compute_capsule_aabb, compute_sphere_aabb, SurfaceMaterial, SURFACE_MATERIAL_SIZE,
18};
19use crate::hull::{compare_hull_data, compute_hull_aabb, HullData};
20use crate::math_functions::TRANSFORM_IDENTITY;
21use crate::mesh::{compute_mesh_aabb, MeshData};
22
23struct SharedHull {
24    hull: HullData,
25    hull_offset: i32,
26}
27
28struct SharedMesh {
29    mesh_data: MeshData,
30    mesh_offset: i32,
31}
32
33fn materials_equal(a: &SurfaceMaterial, b: &SurfaceMaterial) -> bool {
34    a.to_bytes() == b.to_bytes()
35}
36
37fn get_or_insert_material(materials: &mut Vec<SurfaceMaterial>, mat: &SurfaceMaterial) -> i32 {
38    if let Some(i) = materials.iter().position(|m| materials_equal(m, mat)) {
39        return i as i32;
40    }
41    let index = materials.len() as i32;
42    materials.push(*mat);
43    index
44}
45
46fn meshes_equal(a: &MeshData, b: &MeshData) -> bool {
47    if core::ptr::eq(a, b) {
48        return true;
49    }
50    if a.byte_count != b.byte_count {
51        return false;
52    }
53    a.to_bytes() == b.to_bytes()
54}
55
56/// Create a baked compound shape. (b3CreateCompound)
57pub fn create_compound(def: &CompoundDef<'_>) -> Option<CompoundData> {
58    let capsule_count = def.capsules.len() as i32;
59    let hull_count = def.hulls.len() as i32;
60    let mesh_count = def.meshes.len() as i32;
61    let sphere_count = def.spheres.len() as i32;
62
63    let convex_count = capsule_count + hull_count + sphere_count;
64    let shape_count = convex_count + mesh_count;
65
66    if shape_count >= MAX_CHILD_SHAPES {
67        debug_assert!(false);
68        return None;
69    }
70
71    let mut tree = DynamicTree::new(shape_count);
72    let mut child_index = 0i32;
73
74    let mut capsule_instances = vec![CompoundCapsule::default(); capsule_count as usize];
75    let mut hull_instances = vec![
76        HullInstance {
77            transform: TRANSFORM_IDENTITY,
78            shared_index: 0,
79            material_index: 0,
80            hull_offset: 0,
81        };
82        hull_count as usize
83    ];
84    let mut mesh_instances = vec![
85        MeshInstance {
86            transform: TRANSFORM_IDENTITY,
87            scale: crate::math_functions::VEC3_ONE,
88            shared_index: 0,
89            material_indices: [0; MAX_COMPOUND_MESH_MATERIALS],
90            mesh_offset: 0,
91        };
92        mesh_count as usize
93    ];
94    let mut sphere_instances = vec![CompoundSphere::default(); sphere_count as usize];
95
96    let mut material_capacity = convex_count;
97    for mesh in def.meshes {
98        debug_assert!(!mesh.materials.is_empty());
99        material_capacity += mesh.materials.len() as i32;
100    }
101
102    let mut materials: Vec<SurfaceMaterial> = Vec::with_capacity(material_capacity.max(0) as usize);
103
104    for (i, capsule_def) in def.capsules.iter().enumerate() {
105        capsule_instances[i].capsule = capsule_def.capsule;
106        let material_index = get_or_insert_material(&mut materials, &capsule_def.material);
107        capsule_instances[i].material_index = material_index;
108
109        let aabb = compute_capsule_aabb(&capsule_def.capsule, TRANSFORM_IDENTITY);
110        tree.create_proxy(aabb, !0u64, child_index as u64);
111        child_index += 1;
112    }
113
114    let mut shared_hulls: Vec<SharedHull> = Vec::with_capacity(hull_count as usize);
115
116    for (i, hull_def) in def.hulls.iter().enumerate() {
117        let hull = hull_def.hull;
118        let aabb = compute_hull_aabb(hull, hull_def.transform);
119        tree.create_proxy(aabb, !0u64, child_index as u64);
120        child_index += 1;
121
122        let material_index = get_or_insert_material(&mut materials, &hull_def.material);
123        hull_instances[i].material_index = material_index as u32;
124        hull_instances[i].transform = hull_def.transform;
125
126        let shared_index = if let Some(idx) = shared_hulls
127            .iter()
128            .position(|s| compare_hull_data(&s.hull, hull))
129        {
130            idx
131        } else {
132            let idx = shared_hulls.len();
133            shared_hulls.push(SharedHull {
134                hull: hull.clone(),
135                hull_offset: NULL_INDEX,
136            });
137            idx
138        };
139        hull_instances[i].shared_index = shared_index as u32;
140        // Temporarily store shared index in hull_offset (C does the same).
141        hull_instances[i].hull_offset = shared_index as u32;
142    }
143
144    let mut shared_meshes: Vec<SharedMesh> = Vec::with_capacity(mesh_count as usize);
145
146    for (i, mesh_def) in def.meshes.iter().enumerate() {
147        let mesh_data = mesh_def.mesh_data;
148        let aabb = compute_mesh_aabb(mesh_data, mesh_def.transform, mesh_def.scale);
149        tree.create_proxy(aabb, !0u64, child_index as u64);
150        child_index += 1;
151
152        debug_assert_eq!(mesh_data.material_count as usize, mesh_def.materials.len());
153
154        for (j, mat) in mesh_def.materials.iter().enumerate() {
155            let material_index = get_or_insert_material(&mut materials, mat);
156            mesh_instances[i].material_indices[j] = material_index as u32;
157        }
158
159        let shared_index = if let Some(idx) = shared_meshes
160            .iter()
161            .position(|s| meshes_equal(&s.mesh_data, mesh_data))
162        {
163            idx
164        } else {
165            let idx = shared_meshes.len();
166            shared_meshes.push(SharedMesh {
167                mesh_data: mesh_data.clone(),
168                mesh_offset: NULL_INDEX,
169            });
170            idx
171        };
172
173        mesh_instances[i].transform = mesh_def.transform;
174        mesh_instances[i].scale = mesh_def.scale;
175        mesh_instances[i].shared_index = shared_index as u32;
176        mesh_instances[i].mesh_offset = shared_index as u32;
177    }
178
179    for (i, sphere_def) in def.spheres.iter().enumerate() {
180        sphere_instances[i].sphere = sphere_def.sphere;
181        let material_index = get_or_insert_material(&mut materials, &sphere_def.material);
182        sphere_instances[i].material_index = material_index;
183
184        let aabb = compute_sphere_aabb(&sphere_def.sphere, TRANSFORM_IDENTITY);
185        tree.create_proxy(aabb, !0u64, child_index as u64);
186        child_index += 1;
187    }
188
189    let material_count = materials.len() as i32;
190    debug_assert!(material_count <= material_capacity);
191    debug_assert!(tree.node_count() > 0);
192
193    tree.rebuild(true);
194
195    let shared_hull_count = shared_hulls.len() as i32;
196    let shared_mesh_count = shared_meshes.len() as i32;
197
198    let mut byte_count = COMPOUND_DATA_SIZE;
199
200    let node_offset = byte_count as i32;
201    byte_count += tree.node_capacity() as usize * TREE_NODE_SIZE;
202
203    let material_offset = byte_count as i32;
204    byte_count += material_count as usize * SURFACE_MATERIAL_SIZE;
205
206    let capsule_offset = byte_count as i32;
207    byte_count += capsule_count as usize * COMPOUND_CONVEX_SIZE;
208
209    let hull_array_offset = byte_count as i32;
210    byte_count += hull_count as usize * HULL_INSTANCE_SIZE;
211
212    for shared in &mut shared_hulls {
213        shared.hull_offset = byte_count as i32;
214        byte_count += shared.hull.byte_count as usize;
215    }
216
217    let mesh_array_offset = byte_count as i32;
218    byte_count += mesh_count as usize * MESH_INSTANCE_SIZE;
219
220    for shared in &mut shared_meshes {
221        shared.mesh_offset = byte_count as i32;
222        byte_count += shared.mesh_data.byte_count as usize;
223    }
224
225    let sphere_offset = byte_count as i32;
226    byte_count += sphere_count as usize * COMPOUND_CONVEX_SIZE;
227
228    // Fix up hull/mesh instance offsets to absolute blob offsets (C does this
229    // before memcpy into the compound).
230    for inst in &mut hull_instances {
231        let shared_index = inst.hull_offset as usize;
232        debug_assert!(shared_index < shared_hulls.len());
233        inst.hull_offset = shared_hulls[shared_index].hull_offset as u32;
234    }
235    for inst in &mut mesh_instances {
236        let shared_index = inst.mesh_offset as usize;
237        debug_assert!(shared_index < shared_meshes.len());
238        inst.mesh_offset = shared_meshes[shared_index].mesh_offset as u32;
239    }
240
241    // Immutable tree: scrub rebuild scratch and force free_list like C.
242    tree.free_list = 0;
243    tree.leaf_indices.clear();
244    tree.leaf_centers.clear();
245    tree.rebuild_capacity = 0;
246
247    debug_assert!(material_count > 0);
248
249    Some(CompoundData {
250        version: COMPOUND_VERSION,
251        byte_count: byte_count as i32,
252        node_offset,
253        tree,
254        material_offset,
255        material_count,
256        capsule_offset,
257        capsule_count,
258        hull_offset: hull_array_offset,
259        hull_count,
260        shared_hull_count,
261        mesh_offset: mesh_array_offset,
262        mesh_count,
263        shared_mesh_count,
264        sphere_offset,
265        sphere_count,
266        materials,
267        capsules: capsule_instances,
268        hull_instances,
269        shared_hulls: shared_hulls.into_iter().map(|s| s.hull).collect(),
270        mesh_instances,
271        shared_meshes: shared_meshes.into_iter().map(|s| s.mesh_data).collect(),
272        spheres: sphere_instances,
273    })
274}
275
276/// Destroy a compound shape. (b3DestroyCompound)
277pub fn destroy_compound(_compound: CompoundData) {}