Skip to main content

box3d_rust/mesh/
types.rs

1//! Mesh types and blob serialization.
2//!
3//! Maps C's `b3MeshData` header + trailing arrays (nodes, vertices, triangles,
4//! materials, flags).
5//!
6//! SPDX-FileCopyrightText: 2026 Erin Catto
7//! SPDX-License-Identifier: MIT
8
9use crate::math_functions::{Aabb, Vec3, VEC3_ONE};
10
11/// 64-bit mesh version. (B3_MESH_VERSION)
12pub const MESH_VERSION: u64 = 0xABD11AB62A6E886D;
13
14/// Size of the C `b3MeshData` header.
15pub const MESH_DATA_SIZE: usize = 88;
16
17/// Size of the C `b3MeshNode`.
18pub const MESH_NODE_SIZE: usize = 32;
19
20/// Size of the C `b3MeshTriangle`.
21pub const MESH_TRIANGLE_SIZE: usize = 12;
22
23/// Leaf node type tag in the packed node bitfield. (B3_LEAF_NODE)
24pub const LEAF_NODE: u32 = 3;
25
26/// BVH traversal stack size. (B3_MESH_STACK_SIZE)
27pub const MESH_STACK_SIZE: usize = 256;
28
29/// Triangle mesh edge flags. (b3MeshEdgeFlags)
30pub const CONCAVE_EDGE1: i32 = 0x01;
31pub const CONCAVE_EDGE2: i32 = 0x02;
32pub const CONCAVE_EDGE3: i32 = 0x04;
33pub const INVERSE_CONCAVE_EDGE1: i32 = 0x10;
34pub const INVERSE_CONCAVE_EDGE2: i32 = 0x20;
35pub const INVERSE_CONCAVE_EDGE3: i32 = 0x40;
36pub const ALL_CONCAVE_EDGES: i32 = CONCAVE_EDGE1 | CONCAVE_EDGE2 | CONCAVE_EDGE3;
37pub const FLAT_EDGE1: i32 = CONCAVE_EDGE1 | INVERSE_CONCAVE_EDGE1;
38pub const FLAT_EDGE2: i32 = CONCAVE_EDGE2 | INVERSE_CONCAVE_EDGE2;
39pub const FLAT_EDGE3: i32 = CONCAVE_EDGE3 | INVERSE_CONCAVE_EDGE3;
40pub const ALL_FLAT_EDGES: i32 = FLAT_EDGE1 | FLAT_EDGE2 | FLAT_EDGE3;
41
42/// Data used to create a re-usable collision mesh. (b3MeshDef)
43#[derive(Debug, Clone, Default)]
44pub struct MeshDef {
45    /// Triangle vertices.
46    pub vertices: Vec<Vec3>,
47    /// Triangle vertex indices (3 per triangle).
48    pub indices: Vec<i32>,
49    /// Triangle material index (1 per triangle). Empty = all zero.
50    pub material_indices: Vec<u8>,
51    /// Tolerance for vertex welding in length units.
52    pub weld_tolerance: f32,
53    /// Optionally weld nearby vertices.
54    pub weld_vertices: bool,
55    /// Use median split instead of SAH (good for grid-like meshes).
56    pub use_median_split: bool,
57    /// Compute triangle adjacency information using shared edges.
58    pub identify_edges: bool,
59}
60
61/// A mesh triangle. (b3MeshTriangle)
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
63#[repr(C)]
64pub struct MeshTriangle {
65    pub index1: i32,
66    pub index2: i32,
67    pub index3: i32,
68}
69
70/// A mesh BVH node. (b3MeshNode)
71///
72/// The C union bitfield is packed into `data`:
73/// - bits 0–1: axis (internal) or type (leaf; 3 = leaf)
74/// - bits 2–31: childOffset (internal) or triangleCount (leaf)
75#[derive(Debug, Clone, Copy, PartialEq, Default)]
76#[repr(C)]
77pub struct MeshNode {
78    pub lower_bound: Vec3,
79    pub data: u32,
80    pub upper_bound: Vec3,
81    pub triangle_offset: u32,
82}
83
84impl MeshNode {
85    #[inline]
86    pub fn is_leaf(&self) -> bool {
87        (self.data & 0x3) == LEAF_NODE
88    }
89
90    #[inline]
91    pub fn axis(&self) -> u32 {
92        self.data & 0x3
93    }
94
95    #[inline]
96    pub fn child_offset(&self) -> u32 {
97        self.data >> 2
98    }
99
100    #[inline]
101    pub fn triangle_count(&self) -> u32 {
102        self.data >> 2
103    }
104
105    #[inline]
106    pub fn store_leaf(aabb: Aabb, triangle_count: i32, triangle_offset: i32) -> Self {
107        debug_assert!(triangle_count >= 0);
108        Self {
109            lower_bound: aabb.lower_bound,
110            data: LEAF_NODE | ((triangle_count as u32) << 2),
111            upper_bound: aabb.upper_bound,
112            triangle_offset: triangle_offset as u32,
113        }
114    }
115
116    #[inline]
117    pub fn store_internal(aabb: Aabb, axis: i32, child_offset: i32) -> Self {
118        debug_assert!((0..3).contains(&axis));
119        debug_assert!(child_offset > 1);
120        Self {
121            lower_bound: aabb.lower_bound,
122            data: (axis as u32) | ((child_offset as u32) << 2),
123            upper_bound: aabb.upper_bound,
124            triangle_offset: 0,
125        }
126    }
127
128    #[inline]
129    pub fn aabb(&self) -> Aabb {
130        Aabb {
131            lower_bound: self.lower_bound,
132            upper_bound: self.upper_bound,
133        }
134    }
135}
136
137/// Sorted triangle collision bounding volume hierarchy. (b3MeshData)
138///
139/// Maps to C's header + trailing blob. Offsets and `byte_count` match C so
140/// [`MeshData::to_bytes`] reproduces the contiguous layout used by `b3Hash`.
141#[derive(Debug, Clone)]
142pub struct MeshData {
143    pub version: u64,
144    pub byte_count: i32,
145    pub hash: u32,
146    pub bounds: Aabb,
147    pub surface_area: f32,
148    pub tree_height: i32,
149    pub degenerate_count: i32,
150    pub node_offset: i32,
151    pub node_count: i32,
152    pub vertex_offset: i32,
153    pub vertex_count: i32,
154    pub triangle_offset: i32,
155    pub triangle_count: i32,
156    pub material_offset: i32,
157    pub material_count: i32,
158    pub flags_offset: i32,
159    pub nodes: Vec<MeshNode>,
160    pub vertices: Vec<Vec3>,
161    pub triangles: Vec<MeshTriangle>,
162    pub material_indices: Vec<u8>,
163    pub flags: Vec<u8>,
164}
165
166impl Default for MeshData {
167    fn default() -> Self {
168        Self {
169            version: MESH_VERSION,
170            byte_count: 0,
171            hash: 0,
172            bounds: Aabb::default(),
173            surface_area: 0.0,
174            tree_height: 0,
175            degenerate_count: 0,
176            node_offset: 0,
177            node_count: 0,
178            vertex_offset: 0,
179            vertex_count: 0,
180            triangle_offset: 0,
181            triangle_count: 0,
182            material_offset: 0,
183            material_count: 0,
184            flags_offset: 0,
185            nodes: Vec::new(),
186            vertices: Vec::new(),
187            triangles: Vec::new(),
188            material_indices: Vec::new(),
189            flags: Vec::new(),
190        }
191    }
192}
193
194/// Mesh data re-used with different scales. (b3Mesh)
195#[derive(Debug, Clone, Copy)]
196pub struct Mesh<'a> {
197    pub data: &'a MeshData,
198    pub scale: Vec3,
199}
200
201impl<'a> Mesh<'a> {
202    pub fn new(data: &'a MeshData, scale: Vec3) -> Self {
203        Self { data, scale }
204    }
205
206    pub fn with_unit_scale(data: &'a MeshData) -> Self {
207        Self {
208            data,
209            scale: VEC3_ONE,
210        }
211    }
212}
213
214/// Mesh nodes. (b3GetMeshNodes)
215pub fn get_mesh_nodes(mesh: &MeshData) -> &[MeshNode] {
216    &mesh.nodes
217}
218
219/// Mesh vertices. (b3GetMeshVertices)
220pub fn get_mesh_vertices(mesh: &MeshData) -> &[Vec3] {
221    &mesh.vertices
222}
223
224/// Mesh triangles. (b3GetMeshTriangles)
225pub fn get_mesh_triangles(mesh: &MeshData) -> &[MeshTriangle] {
226    &mesh.triangles
227}
228
229/// Mesh material indices. (b3GetMeshMaterialIndices)
230pub fn get_mesh_material_indices(mesh: &MeshData) -> &[u8] {
231    &mesh.material_indices
232}
233
234/// Mesh triangle flags. (b3GetMeshFlags)
235pub fn get_mesh_flags(mesh: &MeshData) -> &[u8] {
236    &mesh.flags
237}
238
239fn write_u64_le(buf: &mut Vec<u8>, v: u64) {
240    buf.extend_from_slice(&v.to_le_bytes());
241}
242
243fn read_u64_le(buf: &[u8], off: usize) -> u64 {
244    u64::from_le_bytes(buf[off..off + 8].try_into().unwrap())
245}
246
247fn read_u32_le(buf: &[u8], off: usize) -> u32 {
248    u32::from_le_bytes(buf[off..off + 4].try_into().unwrap())
249}
250
251fn read_i32_le(buf: &[u8], off: usize) -> i32 {
252    read_u32_le(buf, off) as i32
253}
254
255fn read_f32_le(buf: &[u8], off: usize) -> f32 {
256    f32::from_le_bytes(buf[off..off + 4].try_into().unwrap())
257}
258
259fn read_vec3_at(buf: &[u8], off: usize) -> Vec3 {
260    Vec3 {
261        x: read_f32_le(buf, off),
262        y: read_f32_le(buf, off + 4),
263        z: read_f32_le(buf, off + 8),
264    }
265}
266
267fn read_aabb_at(buf: &[u8], off: usize) -> Aabb {
268    Aabb {
269        lower_bound: read_vec3_at(buf, off),
270        upper_bound: read_vec3_at(buf, off + 12),
271    }
272}
273
274/// Restore a mesh from a contiguous blob. (inverse of [`MeshData::to_bytes`])
275pub fn convert_bytes_to_mesh(bytes: &[u8]) -> Option<MeshData> {
276    if bytes.len() < MESH_DATA_SIZE {
277        return None;
278    }
279    let version = read_u64_le(bytes, 0);
280    if version != MESH_VERSION {
281        return None;
282    }
283    let byte_count = read_i32_le(bytes, 8);
284    if byte_count < MESH_DATA_SIZE as i32 || bytes.len() != byte_count as usize {
285        return None;
286    }
287    let hash = read_u32_le(bytes, 12);
288    let bounds = read_aabb_at(bytes, 16);
289    let surface_area = read_f32_le(bytes, 40);
290    let tree_height = read_i32_le(bytes, 44);
291    let degenerate_count = read_i32_le(bytes, 48);
292    let node_offset = read_i32_le(bytes, 52);
293    let node_count = read_i32_le(bytes, 56);
294    let vertex_offset = read_i32_le(bytes, 60);
295    let vertex_count = read_i32_le(bytes, 64);
296    let triangle_offset = read_i32_le(bytes, 68);
297    let triangle_count = read_i32_le(bytes, 72);
298    let material_offset = read_i32_le(bytes, 76);
299    let material_count = read_i32_le(bytes, 80);
300    let flags_offset = read_i32_le(bytes, 84);
301
302    if node_count < 0 || vertex_count < 0 || triangle_count < 0 || material_count < 0 {
303        return None;
304    }
305    let nc = node_count as usize;
306    let vc = vertex_count as usize;
307    let tc = triangle_count as usize;
308    let mc = material_count as usize;
309
310    let noff = node_offset as usize;
311    if noff + nc * MESH_NODE_SIZE > bytes.len() {
312        return None;
313    }
314    let mut nodes = Vec::with_capacity(nc);
315    for i in 0..nc {
316        let o = noff + i * MESH_NODE_SIZE;
317        nodes.push(MeshNode {
318            lower_bound: read_vec3_at(bytes, o),
319            data: read_u32_le(bytes, o + 12),
320            upper_bound: read_vec3_at(bytes, o + 16),
321            triangle_offset: read_u32_le(bytes, o + 28),
322        });
323    }
324
325    let voff = vertex_offset as usize;
326    if voff + vc * 12 > bytes.len() {
327        return None;
328    }
329    let mut vertices = Vec::with_capacity(vc);
330    for i in 0..vc {
331        vertices.push(read_vec3_at(bytes, voff + i * 12));
332    }
333
334    let toff = triangle_offset as usize;
335    if toff + tc * MESH_TRIANGLE_SIZE > bytes.len() {
336        return None;
337    }
338    let mut triangles = Vec::with_capacity(tc);
339    for i in 0..tc {
340        let o = toff + i * MESH_TRIANGLE_SIZE;
341        triangles.push(MeshTriangle {
342            index1: read_i32_le(bytes, o),
343            index2: read_i32_le(bytes, o + 4),
344            index3: read_i32_le(bytes, o + 8),
345        });
346    }
347
348    let moff = material_offset as usize;
349    if moff + mc > bytes.len() {
350        return None;
351    }
352    let material_indices = bytes[moff..moff + mc].to_vec();
353
354    let foff = flags_offset as usize;
355    if foff + tc > bytes.len() {
356        return None;
357    }
358    let flags = bytes[foff..foff + tc].to_vec();
359
360    Some(MeshData {
361        version,
362        byte_count,
363        hash,
364        bounds,
365        surface_area,
366        tree_height,
367        degenerate_count,
368        node_offset,
369        node_count,
370        vertex_offset,
371        vertex_count,
372        triangle_offset,
373        triangle_count,
374        material_offset,
375        material_count,
376        flags_offset,
377        nodes,
378        vertices,
379        triangles,
380        material_indices,
381        flags,
382    })
383}
384
385fn write_u32_le(buf: &mut Vec<u8>, v: u32) {
386    buf.extend_from_slice(&v.to_le_bytes());
387}
388
389fn write_i32_le(buf: &mut Vec<u8>, v: i32) {
390    buf.extend_from_slice(&v.to_le_bytes());
391}
392
393fn write_f32_le(buf: &mut Vec<u8>, v: f32) {
394    buf.extend_from_slice(&v.to_le_bytes());
395}
396
397fn write_vec3(buf: &mut Vec<u8>, v: Vec3) {
398    write_f32_le(buf, v.x);
399    write_f32_le(buf, v.y);
400    write_f32_le(buf, v.z);
401}
402
403fn write_aabb(buf: &mut Vec<u8>, a: Aabb) {
404    write_vec3(buf, a.lower_bound);
405    write_vec3(buf, a.upper_bound);
406}
407
408fn pad_to(buf: &mut Vec<u8>, len: usize) {
409    if buf.len() < len {
410        buf.resize(len, 0);
411    }
412}
413
414fn write_header(buf: &mut Vec<u8>, m: &MeshData, hash_override: Option<u32>) {
415    write_u64_le(buf, m.version);
416    write_i32_le(buf, m.byte_count);
417    write_u32_le(buf, hash_override.unwrap_or(m.hash));
418    write_aabb(buf, m.bounds);
419    write_f32_le(buf, m.surface_area);
420    write_i32_le(buf, m.tree_height);
421    write_i32_le(buf, m.degenerate_count);
422    write_i32_le(buf, m.node_offset);
423    write_i32_le(buf, m.node_count);
424    write_i32_le(buf, m.vertex_offset);
425    write_i32_le(buf, m.vertex_count);
426    write_i32_le(buf, m.triangle_offset);
427    write_i32_le(buf, m.triangle_count);
428    write_i32_le(buf, m.material_offset);
429    write_i32_le(buf, m.material_count);
430    write_i32_le(buf, m.flags_offset);
431    debug_assert_eq!(buf.len(), MESH_DATA_SIZE);
432}
433
434fn write_node(buf: &mut Vec<u8>, n: &MeshNode) {
435    write_vec3(buf, n.lower_bound);
436    write_u32_le(buf, n.data);
437    write_vec3(buf, n.upper_bound);
438    write_u32_le(buf, n.triangle_offset);
439}
440
441impl MeshData {
442    /// Restore a mesh from a contiguous blob. (inverse of [`to_bytes`])
443    pub fn from_bytes(bytes: &[u8]) -> Option<MeshData> {
444        convert_bytes_to_mesh(bytes)
445    }
446
447    /// Serialize to the C contiguous trailing-blob layout (for hash parity).
448    pub fn to_bytes(&self) -> Vec<u8> {
449        self.to_bytes_with_hash(self.hash)
450    }
451
452    /// Like [`to_bytes`], but with an explicit hash field (use 0 when computing the hash).
453    pub fn to_bytes_with_hash(&self, hash: u32) -> Vec<u8> {
454        let mut buf = Vec::with_capacity(self.byte_count as usize);
455        write_header(&mut buf, self, Some(hash));
456        pad_to(&mut buf, self.node_offset as usize);
457        for n in &self.nodes {
458            write_node(&mut buf, n);
459        }
460        pad_to(&mut buf, self.vertex_offset as usize);
461        for v in &self.vertices {
462            write_vec3(&mut buf, *v);
463        }
464        pad_to(&mut buf, self.triangle_offset as usize);
465        for t in &self.triangles {
466            write_i32_le(&mut buf, t.index1);
467            write_i32_le(&mut buf, t.index2);
468            write_i32_le(&mut buf, t.index3);
469        }
470        pad_to(&mut buf, self.material_offset as usize);
471        buf.extend_from_slice(&self.material_indices);
472        pad_to(&mut buf, self.flags_offset as usize);
473        buf.extend_from_slice(&self.flags);
474        pad_to(&mut buf, self.byte_count as usize);
475        debug_assert_eq!(buf.len(), self.byte_count as usize);
476        buf
477    }
478}