Skip to main content

box3d_rust/hull/
types.rs

1//! Convex hull types and accessors.
2//!
3//! # C trailing-blob layout
4//!
5//! In C, `b3HullData` is a 136-byte header with vertex/point/edge/face/plane arrays
6//! hanging off the end at the recorded byte offsets (`b3AlignUp8` between sections).
7//! `b3BoxHull` embeds the same header plus fixed arrays (total 440 bytes).
8//!
9//! Rust stores the header fields plus owned `Vec`s for the arrays. Offsets and
10//! `byte_count` are kept identical to C so [`HullData::to_bytes`] / [`BoxHull::to_bytes`]
11//! reproduce the contiguous layout used by `b3Hash` and `memcmp`.
12
13use crate::math_functions::{Aabb, Matrix3, Plane, Vec3, MAT3_ZERO, VEC3_ZERO};
14
15/// 64-bit hull version. Useful for validating serialized data. (B3_HULL_VERSION)
16pub const HULL_VERSION: u64 = 0x9D4716CE3793900E;
17
18/// Size of the C `b3HullData` header. (_Static_assert in hull.c)
19pub const HULL_DATA_SIZE: usize = 136;
20
21/// Size of the C `b3BoxHull`. (_Static_assert in hull.c)
22pub const BOX_HULL_SIZE: usize = 440;
23
24/// A hull vertex. Identified by a half-edge with this vertex as its tail.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
26#[repr(C)]
27pub struct HullVertex {
28    /// A half-edge that has this vertex as the origin.
29    pub edge: u8,
30}
31
32/// Half-edge for hull data structure.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
34#[repr(C)]
35pub struct HullHalfEdge {
36    /// Next edge index CCW.
37    pub next: u8,
38    /// Twin edge index.
39    pub twin: u8,
40    /// Index of origin vertex and point.
41    pub origin: u8,
42    /// Face to the left of this edge.
43    pub face: u8,
44}
45
46/// A hull face. Hulls use a half-edge data structure, so a face
47/// can be determined from a single half-edge index.
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
49#[repr(C)]
50pub struct HullFace {
51    /// An arbitrary half-edge on this face.
52    pub edge: u8,
53}
54
55/// A convex hull.
56///
57/// Maps to C's `b3HullData` + trailing blob. See module docs for the layout mapping.
58#[derive(Debug, Clone)]
59pub struct HullData {
60    pub version: u64,
61    pub byte_count: i32,
62    pub hash: u32,
63    pub aabb: Aabb,
64    pub surface_area: f32,
65    pub volume: f32,
66    pub inner_radius: f32,
67    pub center: Vec3,
68    pub central_inertia: Matrix3,
69    pub vertex_count: i32,
70    pub vertex_offset: i32,
71    pub point_offset: i32,
72    pub edge_count: i32,
73    pub edge_offset: i32,
74    pub face_count: i32,
75    pub face_offset: i32,
76    pub plane_offset: i32,
77    pub padding: i32,
78    pub vertices: Vec<HullVertex>,
79    pub points: Vec<Vec3>,
80    pub edges: Vec<HullHalfEdge>,
81    pub faces: Vec<HullFace>,
82    pub planes: Vec<Plane>,
83}
84
85impl Default for HullData {
86    fn default() -> Self {
87        Self {
88            version: HULL_VERSION,
89            byte_count: 0,
90            hash: 0,
91            aabb: Aabb::default(),
92            surface_area: 0.0,
93            volume: 0.0,
94            inner_radius: 0.0,
95            center: VEC3_ZERO,
96            central_inertia: MAT3_ZERO,
97            vertex_count: 0,
98            vertex_offset: 0,
99            point_offset: 0,
100            edge_count: 0,
101            edge_offset: 0,
102            face_count: 0,
103            face_offset: 0,
104            plane_offset: 0,
105            padding: 0,
106            vertices: Vec::new(),
107            points: Vec::new(),
108            edges: Vec::new(),
109            faces: Vec::new(),
110            planes: Vec::new(),
111        }
112    }
113}
114
115/// Efficient box hull with embedded arrays matching C `b3BoxHull`.
116#[derive(Debug, Clone)]
117pub struct BoxHull {
118    pub base: HullData,
119    pub box_vertices: [HullVertex; 8],
120    pub box_points: [Vec3; 8],
121    pub box_edges: [HullHalfEdge; 24],
122    pub box_faces: [HullFace; 6],
123    pub padding: [u8; 2],
124    pub box_planes: [Plane; 6],
125}
126
127impl BoxHull {
128    /// View the embedded arrays through the base `HullData` accessors pattern.
129    pub fn as_hull_data(&self) -> HullDataView<'_> {
130        HullDataView {
131            header: &self.base,
132            vertices: &self.box_vertices,
133            points: &self.box_points,
134            edges: &self.box_edges,
135            faces: &self.box_faces,
136            planes: &self.box_planes,
137        }
138    }
139
140    /// Mirror the embedded box arrays into `base`'s owned `Vec`s so
141    /// [`get_hull_points`] / [`get_hull_planes`] work on `&box.base`, matching
142    /// C's contiguous `b3BoxHull` layout where offsets point into the same
143    /// allocation.
144    pub fn sync_base_arrays(&mut self) {
145        self.base.vertices = self.box_vertices.to_vec();
146        self.base.points = self.box_points.to_vec();
147        self.base.edges = self.box_edges.to_vec();
148        self.base.faces = self.box_faces.to_vec();
149        self.base.planes = self.box_planes.to_vec();
150    }
151}
152
153/// Borrowed view of hull geometry (owned hull or box hull).
154pub struct HullDataView<'a> {
155    pub header: &'a HullData,
156    pub vertices: &'a [HullVertex],
157    pub points: &'a [Vec3],
158    pub edges: &'a [HullHalfEdge],
159    pub faces: &'a [HullFace],
160    pub planes: &'a [Plane],
161}
162
163/// Get hull vertices. (collision.h: b3GetHullVertices)
164pub fn get_hull_vertices(hull: &HullData) -> &[HullVertex] {
165    &hull.vertices[..hull.vertex_count as usize]
166}
167
168/// Get hull points. (collision.h: b3GetHullPoints)
169pub fn get_hull_points(hull: &HullData) -> &[Vec3] {
170    &hull.points[..hull.vertex_count as usize]
171}
172
173/// Get hull half-edges. (collision.h: b3GetHullEdges)
174pub fn get_hull_edges(hull: &HullData) -> &[HullHalfEdge] {
175    &hull.edges[..hull.edge_count as usize]
176}
177
178/// Get hull faces. (collision.h: b3GetHullFaces)
179pub fn get_hull_faces(hull: &HullData) -> &[HullFace] {
180    &hull.faces[..hull.face_count as usize]
181}
182
183/// Get hull face planes. (collision.h: b3GetHullPlanes)
184pub fn get_hull_planes(hull: &HullData) -> &[Plane] {
185    &hull.planes[..hull.face_count as usize]
186}
187
188fn write_u32_le(buf: &mut Vec<u8>, v: u32) {
189    buf.extend_from_slice(&v.to_le_bytes());
190}
191
192fn write_i32_le(buf: &mut Vec<u8>, v: i32) {
193    buf.extend_from_slice(&v.to_le_bytes());
194}
195
196fn write_f32_le(buf: &mut Vec<u8>, v: f32) {
197    buf.extend_from_slice(&v.to_le_bytes());
198}
199
200fn write_u64_le(buf: &mut Vec<u8>, v: u64) {
201    buf.extend_from_slice(&v.to_le_bytes());
202}
203
204fn write_vec3(buf: &mut Vec<u8>, v: Vec3) {
205    write_f32_le(buf, v.x);
206    write_f32_le(buf, v.y);
207    write_f32_le(buf, v.z);
208}
209
210fn write_aabb(buf: &mut Vec<u8>, a: Aabb) {
211    write_vec3(buf, a.lower_bound);
212    write_vec3(buf, a.upper_bound);
213}
214
215fn write_matrix3(buf: &mut Vec<u8>, m: Matrix3) {
216    write_vec3(buf, m.cx);
217    write_vec3(buf, m.cy);
218    write_vec3(buf, m.cz);
219}
220
221fn write_plane(buf: &mut Vec<u8>, p: Plane) {
222    write_vec3(buf, p.normal);
223    write_f32_le(buf, p.offset);
224}
225
226fn write_header(buf: &mut Vec<u8>, h: &HullData, hash_override: Option<u32>) {
227    write_u64_le(buf, h.version);
228    write_i32_le(buf, h.byte_count);
229    write_u32_le(buf, hash_override.unwrap_or(h.hash));
230    write_aabb(buf, h.aabb);
231    write_f32_le(buf, h.surface_area);
232    write_f32_le(buf, h.volume);
233    write_f32_le(buf, h.inner_radius);
234    write_vec3(buf, h.center);
235    write_matrix3(buf, h.central_inertia);
236    write_i32_le(buf, h.vertex_count);
237    write_i32_le(buf, h.vertex_offset);
238    write_i32_le(buf, h.point_offset);
239    write_i32_le(buf, h.edge_count);
240    write_i32_le(buf, h.edge_offset);
241    write_i32_le(buf, h.face_count);
242    write_i32_le(buf, h.face_offset);
243    write_i32_le(buf, h.plane_offset);
244    write_i32_le(buf, h.padding);
245    debug_assert_eq!(buf.len(), HULL_DATA_SIZE);
246}
247
248fn pad_to(buf: &mut Vec<u8>, len: usize) {
249    if buf.len() < len {
250        buf.resize(len, 0);
251    }
252}
253
254fn read_u32_le(buf: &[u8], off: usize) -> u32 {
255    u32::from_le_bytes(buf[off..off + 4].try_into().unwrap())
256}
257
258fn read_i32_le(buf: &[u8], off: usize) -> i32 {
259    read_u32_le(buf, off) as i32
260}
261
262fn read_u64_le(buf: &[u8], off: usize) -> u64 {
263    u64::from_le_bytes(buf[off..off + 8].try_into().unwrap())
264}
265
266fn read_f32_le(buf: &[u8], off: usize) -> f32 {
267    f32::from_le_bytes(buf[off..off + 4].try_into().unwrap())
268}
269
270fn read_vec3(buf: &[u8], off: usize) -> Vec3 {
271    Vec3 {
272        x: read_f32_le(buf, off),
273        y: read_f32_le(buf, off + 4),
274        z: read_f32_le(buf, off + 8),
275    }
276}
277
278fn read_aabb(buf: &[u8], off: usize) -> Aabb {
279    Aabb {
280        lower_bound: read_vec3(buf, off),
281        upper_bound: read_vec3(buf, off + 12),
282    }
283}
284
285fn read_matrix3(buf: &[u8], off: usize) -> Matrix3 {
286    Matrix3 {
287        cx: read_vec3(buf, off),
288        cy: read_vec3(buf, off + 12),
289        cz: read_vec3(buf, off + 24),
290    }
291}
292
293fn read_plane(buf: &[u8], off: usize) -> Plane {
294    Plane {
295        normal: read_vec3(buf, off),
296        offset: read_f32_le(buf, off + 12),
297    }
298}
299
300/// Restore a hull from a contiguous blob. (inverse of [`HullData::to_bytes`])
301pub fn convert_bytes_to_hull(bytes: &[u8]) -> Option<HullData> {
302    if bytes.len() < HULL_DATA_SIZE {
303        return None;
304    }
305    let version = read_u64_le(bytes, 0);
306    if version != HULL_VERSION {
307        return None;
308    }
309    let byte_count = read_i32_le(bytes, 8);
310    if byte_count < HULL_DATA_SIZE as i32 || bytes.len() != byte_count as usize {
311        return None;
312    }
313    let hash = read_u32_le(bytes, 12);
314    let aabb = read_aabb(bytes, 16);
315    let surface_area = read_f32_le(bytes, 40);
316    let volume = read_f32_le(bytes, 44);
317    let inner_radius = read_f32_le(bytes, 48);
318    let center = read_vec3(bytes, 52);
319    let central_inertia = read_matrix3(bytes, 64);
320    let vertex_count = read_i32_le(bytes, 100);
321    let vertex_offset = read_i32_le(bytes, 104);
322    let point_offset = read_i32_le(bytes, 108);
323    let edge_count = read_i32_le(bytes, 112);
324    let edge_offset = read_i32_le(bytes, 116);
325    let face_count = read_i32_le(bytes, 120);
326    let face_offset = read_i32_le(bytes, 124);
327    let plane_offset = read_i32_le(bytes, 128);
328    let padding = read_i32_le(bytes, 132);
329
330    if vertex_count < 0 || edge_count < 0 || face_count < 0 {
331        return None;
332    }
333    let vc = vertex_count as usize;
334    let ec = edge_count as usize;
335    let fc = face_count as usize;
336
337    let mut vertices = Vec::with_capacity(vc);
338    let voff = vertex_offset as usize;
339    if voff + vc > bytes.len() {
340        return None;
341    }
342    for i in 0..vc {
343        vertices.push(HullVertex {
344            edge: bytes[voff + i],
345        });
346    }
347
348    let mut points = Vec::with_capacity(vc);
349    let poff = point_offset as usize;
350    if poff + vc * 12 > bytes.len() {
351        return None;
352    }
353    for i in 0..vc {
354        points.push(read_vec3(bytes, poff + i * 12));
355    }
356
357    let mut edges = Vec::with_capacity(ec);
358    let eoff = edge_offset as usize;
359    if eoff + ec * 4 > bytes.len() {
360        return None;
361    }
362    for i in 0..ec {
363        let o = eoff + i * 4;
364        edges.push(HullHalfEdge {
365            next: bytes[o],
366            twin: bytes[o + 1],
367            origin: bytes[o + 2],
368            face: bytes[o + 3],
369        });
370    }
371
372    let mut faces = Vec::with_capacity(fc);
373    let foff = face_offset as usize;
374    if foff + fc > bytes.len() {
375        return None;
376    }
377    for i in 0..fc {
378        faces.push(HullFace {
379            edge: bytes[foff + i],
380        });
381    }
382
383    let mut planes = Vec::with_capacity(fc);
384    let ploff = plane_offset as usize;
385    if ploff + fc * 16 > bytes.len() {
386        return None;
387    }
388    for i in 0..fc {
389        planes.push(read_plane(bytes, ploff + i * 16));
390    }
391
392    Some(HullData {
393        version,
394        byte_count,
395        hash,
396        aabb,
397        surface_area,
398        volume,
399        inner_radius,
400        center,
401        central_inertia,
402        vertex_count,
403        vertex_offset,
404        point_offset,
405        edge_count,
406        edge_offset,
407        face_count,
408        face_offset,
409        plane_offset,
410        padding,
411        vertices,
412        points,
413        edges,
414        faces,
415        planes,
416    })
417}
418
419impl HullData {
420    /// Serialize to the C contiguous trailing-blob layout (for hash / memcmp parity).
421    pub fn to_bytes(&self) -> Vec<u8> {
422        self.to_bytes_with_hash(self.hash)
423    }
424
425    /// Like [`to_bytes`], but with an explicit hash field (use 0 when computing the hash).
426    pub fn to_bytes_with_hash(&self, hash: u32) -> Vec<u8> {
427        let mut buf = Vec::with_capacity(self.byte_count as usize);
428        write_header(&mut buf, self, Some(hash));
429        pad_to(&mut buf, self.vertex_offset as usize);
430        for v in &self.vertices {
431            buf.push(v.edge);
432        }
433        pad_to(&mut buf, self.point_offset as usize);
434        for p in &self.points {
435            write_vec3(&mut buf, *p);
436        }
437        pad_to(&mut buf, self.edge_offset as usize);
438        for e in &self.edges {
439            buf.push(e.next);
440            buf.push(e.twin);
441            buf.push(e.origin);
442            buf.push(e.face);
443        }
444        pad_to(&mut buf, self.face_offset as usize);
445        for f in &self.faces {
446            buf.push(f.edge);
447        }
448        pad_to(&mut buf, self.plane_offset as usize);
449        for p in &self.planes {
450            write_plane(&mut buf, *p);
451        }
452        pad_to(&mut buf, self.byte_count as usize);
453        debug_assert_eq!(buf.len(), self.byte_count as usize);
454        buf
455    }
456}
457
458impl BoxHull {
459    /// Serialize to the C `b3BoxHull` layout (440 bytes).
460    pub fn to_bytes(&self) -> Vec<u8> {
461        self.to_bytes_with_hash(self.base.hash)
462    }
463
464    pub fn to_bytes_with_hash(&self, hash: u32) -> Vec<u8> {
465        let mut buf = Vec::with_capacity(BOX_HULL_SIZE);
466        write_header(&mut buf, &self.base, Some(hash));
467        for v in &self.box_vertices {
468            buf.push(v.edge);
469        }
470        for p in &self.box_points {
471            write_vec3(&mut buf, *p);
472        }
473        for e in &self.box_edges {
474            buf.push(e.next);
475            buf.push(e.twin);
476            buf.push(e.origin);
477            buf.push(e.face);
478        }
479        for f in &self.box_faces {
480            buf.push(f.edge);
481        }
482        buf.extend_from_slice(&self.padding);
483        for p in &self.box_planes {
484            write_plane(&mut buf, *p);
485        }
486        debug_assert_eq!(buf.len(), BOX_HULL_SIZE);
487        buf
488    }
489}