Skip to main content

box3d_rust/hull/
create.rs

1//! Hull creation: CreateHull, Clone, Destroy, Cylinder/Cone/Rock.
2
3use super::builder_pool::{compute_hull_work_sizes, HullBuilder, HULL_LIMIT, SENTINEL};
4use super::types::{HullData, HullFace, HullHalfEdge, HullVertex, HULL_DATA_SIZE, HULL_VERSION};
5use super::validate::is_valid_hull;
6use crate::core::{hash, non_zero_hash, HASH_INIT};
7use crate::math_functions::{
8    add, align_up8, clamp_int, compute_cos_sin, cos, cross, length, make_matrix_from_quat,
9    make_plane_from_normal_and_point, max, min, mul, mul_mv, mul_sm, mul_sv, plane_separation,
10    safe_scale, scalar_triple_product, sin, steiner, sub, sub_mm, Transform, Vec3, PI, VEC3_ZERO,
11};
12
13fn update_hull_bounds(hull: &mut HullData) {
14    let points = &hull.points;
15    let vertex_count = hull.vertex_count as usize;
16    debug_assert!(vertex_count > 0);
17    let mut bounds = crate::math_functions::Aabb {
18        lower_bound: points[0],
19        upper_bound: points[0],
20    };
21    for i in 1..vertex_count {
22        let p = points[i];
23        bounds.lower_bound = min(bounds.lower_bound, p);
24        bounds.upper_bound = max(bounds.upper_bound, p);
25    }
26    hull.aabb = bounds;
27}
28
29/// M. Kallay — moment of inertia of a solid defined by a triangle mesh.
30fn update_hull_bulk_properties(hull: &mut HullData) -> bool {
31    let points = &hull.points;
32    let faces = &hull.faces;
33    let edges = &hull.edges;
34    let planes = &hull.planes;
35
36    let mut area = 0.0f32;
37    let mut volume = 0.0f32;
38    let mut center = VEC3_ZERO;
39    let origin = points[0];
40
41    let mut xx = 0.0f32;
42    let mut xy = 0.0f32;
43    let mut yy = 0.0f32;
44    let mut xz = 0.0f32;
45    let mut zz = 0.0f32;
46    let mut yz = 0.0f32;
47
48    let face_count = hull.face_count as usize;
49    for face_index in 0..face_count {
50        let face = faces[face_index];
51        let edge1_i = face.edge as usize;
52        let edge2_i = edges[edge1_i].next as usize;
53        let mut edge3 = edges[edge2_i].next as usize;
54
55        debug_assert!(edge1_i != edge3);
56        debug_assert!((edges[edge1_i].origin as i32) < hull.vertex_count);
57
58        let v1 = sub(points[edges[edge1_i].origin as usize], origin);
59        let mut edge2 = edge2_i;
60
61        loop {
62            debug_assert!((edges[edge2].origin as i32) < hull.vertex_count);
63            debug_assert!((edges[edge3].origin as i32) < hull.vertex_count);
64
65            let v2 = sub(points[edges[edge2].origin as usize], origin);
66            let v3 = sub(points[edges[edge3].origin as usize], origin);
67
68            area += length(cross(sub(v2, v1), sub(v3, v1)));
69
70            let det = scalar_triple_product(v1, v2, v3);
71            volume += det;
72
73            let v4 = add(v1, add(v2, v3));
74            center = add(center, mul_sv(det, v4));
75
76            xx += det * (v1.x * v1.x + v2.x * v2.x + v3.x * v3.x + v4.x * v4.x);
77            yy += det * (v1.y * v1.y + v2.y * v2.y + v3.y * v3.y + v4.y * v4.y);
78            zz += det * (v1.z * v1.z + v2.z * v2.z + v3.z * v3.z + v4.z * v4.z);
79            xy += det * (v1.x * v1.y + v2.x * v2.y + v3.x * v3.y + v4.x * v4.y);
80            xz += det * (v1.x * v1.z + v2.x * v2.z + v3.x * v3.z + v4.x * v4.z);
81            yz += det * (v1.y * v1.z + v2.y * v2.z + v3.y * v3.z + v4.y * v4.z);
82
83            edge2 = edge3;
84            edge3 = edges[edge3].next as usize;
85            if edge1_i == edge3 {
86                break;
87            }
88        }
89    }
90
91    debug_assert!(volume > 0.0);
92
93    let local_center = if volume > 0.0 {
94        mul_sv(0.25 / volume, center)
95    } else {
96        VEC3_ZERO
97    };
98    center = add(local_center, origin);
99
100    let mut radius = f32::MAX;
101    for face_index in 0..face_count {
102        let plane = planes[face_index];
103        let distance = plane_separation(plane, center);
104        debug_assert!(distance < 0.0);
105        radius = crate::math_functions::min_float(radius, -distance);
106    }
107
108    debug_assert!(0.0 < radius && radius < f32::MAX);
109
110    let mut inertia = crate::math_functions::MAT3_ZERO;
111    inertia.cx.x = yy + zz;
112    inertia.cy.x = -xy;
113    inertia.cz.x = -xz;
114    inertia.cx.y = -xy;
115    inertia.cy.y = xx + zz;
116    inertia.cz.y = -yz;
117    inertia.cx.z = -xz;
118    inertia.cy.z = -yz;
119    inertia.cz.z = xx + yy;
120
121    let mass = volume / 6.0;
122    let mut central_inertia = mul_sm(1.0 / 120.0, inertia);
123    central_inertia = sub_mm(central_inertia, steiner(mass, local_center));
124
125    hull.center = center;
126    hull.central_inertia = central_inertia;
127    hull.volume = mass;
128    hull.surface_area = 0.5 * area;
129    hull.inner_radius = radius;
130
131    mass > 0.0 && volume > 0.0 && area > 0.0 && radius > 0.0
132}
133
134fn finalize_hash(hull: &mut HullData) {
135    hull.hash = 0;
136    let bytes = hull.to_bytes_with_hash(0);
137    hull.hash = non_zero_hash(hash(HASH_INIT, &bytes));
138}
139
140/// Create a convex hull from a point cloud. (b3CreateHull)
141pub fn create_hull(points: &[Vec3], max_vertex_count: i32) -> Option<HullData> {
142    let point_count = points.len() as i32;
143    if point_count < 4 {
144        return None;
145    }
146
147    let origin = points[0];
148    let clamped_max_count = clamp_int(max_vertex_count, 4, HULL_LIMIT);
149    let sizes = compute_hull_work_sizes(point_count, clamped_max_count);
150    let mut builder = HullBuilder::new(&sizes);
151    let mut shifted_points = vec![VEC3_ZERO; point_count as usize];
152
153    if !builder.construct(points, clamped_max_count, origin, &mut shifted_points) {
154        return None;
155    }
156
157    if builder.final_vertex_count >= HULL_LIMIT
158        || builder.final_face_count >= HULL_LIMIT
159        || builder.final_half_edge_count >= HULL_LIMIT
160    {
161        return None;
162    }
163
164    let mut temp_vertices = Vec::with_capacity(HULL_LIMIT as usize);
165    let mut vertex_count = 0i32;
166    let mut node = builder.vertex_list.next;
167    while node != SENTINEL {
168        debug_assert!(vertex_count <= HULL_LIMIT - 1);
169        builder.vertices[node as usize].final_index = vertex_count;
170        temp_vertices.push(node);
171        vertex_count += 1;
172        node = builder.vertices[node as usize].link.next;
173    }
174
175    let mut temp_faces = Vec::with_capacity(HULL_LIMIT as usize);
176    let mut temp_edges = vec![0i32; HULL_LIMIT as usize];
177    let mut face_count = 0i32;
178    let mut edge_count = 0i32;
179
180    let mut face_node = builder.face_list.next;
181    while face_node != SENTINEL {
182        debug_assert!(face_count <= HULL_LIMIT - 1);
183        let face = face_node;
184        builder.faces[face as usize].final_index = face_count;
185        temp_faces.push(face);
186        face_count += 1;
187
188        let start = builder.faces[face as usize].edge;
189        let mut edge = start;
190        loop {
191            if builder.edges[edge as usize].final_index < 0 {
192                debug_assert!(edge_count + 1 <= HULL_LIMIT - 1);
193                builder.edges[edge as usize].final_index = edge_count;
194                temp_edges[edge_count as usize] = edge;
195                edge_count += 1;
196                let twin = builder.edges[edge as usize].twin;
197                builder.edges[twin as usize].final_index = edge_count;
198                temp_edges[edge_count as usize] = twin;
199                edge_count += 1;
200            }
201            edge = builder.edges[edge as usize].next;
202            if edge == start {
203                break;
204            }
205        }
206
207        face_node = builder.faces[face as usize].link.next;
208    }
209
210    let mut byte_count = align_up8(HULL_DATA_SIZE);
211    let vertex_offset = byte_count as i32;
212    byte_count += align_up8(vertex_count as usize * core::mem::size_of::<HullVertex>());
213    let point_offset = byte_count as i32;
214    byte_count += align_up8(vertex_count as usize * core::mem::size_of::<Vec3>());
215    let edge_offset = byte_count as i32;
216    byte_count += align_up8(edge_count as usize * core::mem::size_of::<HullHalfEdge>());
217    let face_offset = byte_count as i32;
218    byte_count += align_up8(face_count as usize * core::mem::size_of::<HullFace>());
219    let plane_offset = byte_count as i32;
220    byte_count +=
221        align_up8(face_count as usize * core::mem::size_of::<crate::math_functions::Plane>());
222
223    let mut hull = HullData {
224        version: HULL_VERSION,
225        byte_count: byte_count as i32,
226        hash: 0,
227        aabb: Default::default(),
228        surface_area: 0.0,
229        volume: 0.0,
230        inner_radius: 0.0,
231        center: VEC3_ZERO,
232        central_inertia: crate::math_functions::MAT3_ZERO,
233        vertex_count,
234        vertex_offset,
235        point_offset,
236        edge_count,
237        edge_offset,
238        face_count,
239        face_offset,
240        plane_offset,
241        padding: 0,
242        vertices: vec![HullVertex { edge: 0 }; vertex_count as usize],
243        points: vec![VEC3_ZERO; vertex_count as usize],
244        edges: vec![HullHalfEdge::default(); edge_count as usize],
245        faces: vec![HullFace { edge: 0 }; face_count as usize],
246        planes: vec![
247            crate::math_functions::Plane {
248                normal: VEC3_ZERO,
249                offset: 0.0,
250            };
251            face_count as usize
252        ],
253    };
254
255    for index in 0..vertex_count as usize {
256        hull.vertices[index].edge = 0;
257        hull.points[index] = builder.vertices[temp_vertices[index] as usize].position;
258    }
259
260    for index in 0..edge_count as usize {
261        let edge = temp_edges[index];
262        let e = &builder.edges[edge as usize];
263        hull.edges[index] = HullHalfEdge {
264            next: builder.edges[e.next as usize].final_index as u8,
265            twin: builder.edges[e.twin as usize].final_index as u8,
266            face: builder.faces[e.face as usize].final_index as u8,
267            origin: builder.vertices[e.origin as usize].final_index as u8,
268        };
269        hull.vertices[builder.vertices[e.origin as usize].final_index as usize].edge = index as u8;
270    }
271
272    for index in 0..face_count as usize {
273        let face = temp_faces[index];
274        hull.faces[index].edge =
275            builder.edges[builder.faces[face as usize].edge as usize].final_index as u8;
276        hull.planes[index] = builder.faces[face as usize].plane;
277    }
278
279    update_hull_bounds(&mut hull);
280    if !update_hull_bulk_properties(&mut hull) {
281        return None;
282    }
283    if !is_valid_hull(&hull) {
284        return None;
285    }
286
287    finalize_hash(&mut hull);
288    Some(hull)
289}
290
291/// Clone a hull. (b3CloneHull)
292pub fn clone_hull(hull: &HullData) -> Option<HullData> {
293    if !is_valid_hull(hull) {
294        return None;
295    }
296    Some(hull.clone())
297}
298
299/// Clone a hull, then scale/rotate/translate it. Recomputes planes, bounds, and
300/// bulk properties, so the result is a fully valid hull. A negative net scale
301/// (reflection) reverses each face's edge winding so the hull stays outward-facing.
302/// Returns `None` if `original` is invalid or the transformed hull degenerates.
303/// (b3CloneAndTransformHull)
304pub fn clone_and_transform_hull(
305    original: &HullData,
306    transform: Transform,
307    scale: Vec3,
308) -> Option<HullData> {
309    if !is_valid_hull(original) {
310        return None;
311    }
312
313    // The Rust hull owns its arrays directly (C memcpy's the trailing blob); the
314    // byte layout/offsets are preserved by the clone so the final hash matches C.
315    let mut hull = original.clone();
316
317    let safe_scale = safe_scale(scale);
318
319    let face_count = hull.face_count as usize;
320    let vertex_count = hull.vertex_count as usize;
321
322    if safe_scale.x * safe_scale.y * safe_scale.z < 0.0 {
323        // Reflected: reverse edge winding for each face.
324        for i in 0..face_count {
325            let start_edge_index = hull.faces[i].edge;
326            let mut current_edge_index = start_edge_index;
327            let mut prev_edge_index: u8 = u8::MAX;
328
329            loop {
330                let edge_next = hull.edges[current_edge_index as usize].next;
331                if edge_next == start_edge_index {
332                    prev_edge_index = current_edge_index;
333                    break;
334                }
335                current_edge_index = edge_next;
336                if current_edge_index == start_edge_index {
337                    break;
338                }
339            }
340
341            debug_assert!(prev_edge_index != u8::MAX);
342
343            current_edge_index = start_edge_index;
344
345            loop {
346                let next_index = hull.edges[current_edge_index as usize].next;
347                let twin = hull.edges[current_edge_index as usize].twin;
348                hull.edges[current_edge_index as usize].next = prev_edge_index;
349
350                if current_edge_index < twin {
351                    let a = hull.edges[current_edge_index as usize].origin;
352                    let b = hull.edges[twin as usize].origin;
353                    hull.edges[current_edge_index as usize].origin = b;
354                    hull.edges[twin as usize].origin = a;
355                }
356
357                prev_edge_index = current_edge_index;
358                current_edge_index = next_index;
359                if current_edge_index == start_edge_index {
360                    break;
361                }
362            }
363        }
364
365        for i in 0..vertex_count {
366            let edge = hull.vertices[i].edge;
367            hull.vertices[i].edge = hull.edges[edge as usize].twin;
368        }
369    }
370
371    let matrix = make_matrix_from_quat(transform.q);
372    for i in 0..vertex_count {
373        hull.points[i] = add(mul_mv(matrix, mul(safe_scale, hull.points[i])), transform.p);
374    }
375
376    for i in 0..face_count {
377        let mut count = 0i32;
378        let mut centroid = VEC3_ZERO;
379        let mut normal = VEC3_ZERO;
380
381        let start_edge_index = hull.faces[i].edge;
382        let mut current_edge_index = start_edge_index;
383
384        debug_assert!(hull.edges[start_edge_index as usize].face as usize == i);
385        debug_assert!((hull.edges[start_edge_index as usize].origin as i32) < hull.vertex_count);
386
387        let origin = hull.points[hull.edges[start_edge_index as usize].origin as usize];
388
389        loop {
390            let edge = hull.edges[current_edge_index as usize];
391            let twin = hull.edges[edge.twin as usize];
392            debug_assert!(twin.twin == current_edge_index);
393
394            let v1 = sub(hull.points[edge.origin as usize], origin);
395            let v2 = sub(hull.points[twin.origin as usize], origin);
396
397            count += 1;
398            centroid = add(centroid, v1);
399            normal.x += (v1.y - v2.y) * (v1.z + v2.z);
400            normal.y += (v1.z - v2.z) * (v1.x + v2.x);
401            normal.z += (v1.x - v2.x) * (v1.y + v2.y);
402
403            current_edge_index = edge.next;
404            if current_edge_index == start_edge_index {
405                break;
406            }
407        }
408
409        debug_assert!(count > 0);
410        centroid = mul_sv(1.0 / count as f32, centroid);
411        centroid = add(centroid, origin);
412
413        let area = length(normal);
414        debug_assert!(area > 0.0);
415        normal = mul_sv(1.0 / area, normal);
416
417        hull.planes[i] = make_plane_from_normal_and_point(normal, centroid);
418    }
419
420    update_hull_bounds(&mut hull);
421    if !update_hull_bulk_properties(&mut hull) {
422        return None;
423    }
424
425    finalize_hash(&mut hull);
426
427    debug_assert!(is_valid_hull(&hull));
428
429    Some(hull)
430}
431
432/// Destroy is a no-op for owned Rust hulls; kept for API parity. (b3DestroyHull)
433pub fn destroy_hull(_hull: HullData) {}
434
435/// Create a cylinder hull. (b3CreateCylinder)
436pub fn create_cylinder(height: f32, radius: f32, y_offset: f32, sides: i32) -> Option<HullData> {
437    debug_assert!(height > 0.0);
438    debug_assert!(radius > 0.0);
439    debug_assert!((3..=32).contains(&sides));
440
441    let point_count = 2 * sides;
442    let mut points = Vec::with_capacity(point_count as usize);
443    let mut alpha = 0.0f32;
444    let delta_alpha = 2.0 * PI / sides as f32;
445
446    for _ in 0..sides {
447        let sin_alpha = sin(alpha);
448        let cos_alpha = cos(alpha);
449        points.push(Vec3 {
450            x: radius * cos_alpha,
451            y: y_offset,
452            z: radius * sin_alpha,
453        });
454        points.push(Vec3 {
455            x: radius * cos_alpha,
456            y: y_offset + height,
457            z: radius * sin_alpha,
458        });
459        alpha += delta_alpha;
460    }
461
462    let hull = create_hull(&points, point_count)?;
463    debug_assert!(hull.vertex_count == point_count);
464    debug_assert!(hull.edge_count == 6 * sides);
465    debug_assert!(hull.face_count == sides + 2);
466    Some(hull)
467}
468
469/// Create a cone/frustum hull. (b3CreateCone)
470pub fn create_cone(height: f32, radius1: f32, radius2: f32, slices: i32) -> Option<HullData> {
471    debug_assert!(height > 0.0);
472    debug_assert!(radius1 > 0.0);
473    debug_assert!(radius2 > 0.0);
474    debug_assert!((4..=32).contains(&slices));
475
476    let point_count = 2 * slices;
477    let mut points = Vec::with_capacity(point_count as usize);
478    let mut alpha = 0.0f32;
479    let delta_alpha = 2.0 * PI / slices as f32;
480
481    for _ in 0..slices {
482        let sin_alpha = sin(alpha);
483        let cos_alpha = cos(alpha);
484        points.push(Vec3 {
485            x: radius1 * cos_alpha,
486            y: 0.0,
487            z: radius1 * sin_alpha,
488        });
489        points.push(Vec3 {
490            x: radius2 * cos_alpha,
491            y: height,
492            z: radius2 * sin_alpha,
493        });
494        alpha += delta_alpha;
495    }
496
497    let hull = create_hull(&points, point_count)?;
498    debug_assert!(hull.vertex_count == point_count);
499    debug_assert!(hull.edge_count == 6 * slices);
500    debug_assert!(hull.face_count == slices + 2);
501    Some(hull)
502}
503
504/// Create a rock-like hull from a Fibonacci lattice. (b3CreateRock)
505pub fn create_rock(radius: f32) -> Option<HullData> {
506    let point_count = 10;
507    let phi = (1.0 + 5.0f32.sqrt()) / 2.0;
508    let theta = 2.0 * PI / phi;
509    let mut cs = crate::math_functions::CosSin {
510        cosine: 1.0,
511        sine: 0.0,
512    };
513    let delta_cs = compute_cos_sin(theta);
514    let mut points = [VEC3_ZERO; 10];
515
516    for i in 0..point_count {
517        let z = 1.0 - (2.0 * i as f32 + 1.0) / point_count as f32;
518        let radius_xy = (1.0 - z * z).sqrt();
519        points[i] = Vec3 {
520            x: radius * radius_xy * cs.cosine,
521            y: radius * radius_xy * cs.sine,
522            z: radius * z,
523        };
524        let cs0 = cs;
525        cs.cosine = delta_cs.cosine * cs0.cosine - delta_cs.sine * cs0.sine;
526        cs.sine = delta_cs.sine * cs0.cosine + delta_cs.cosine * cs0.sine;
527    }
528
529    create_hull(&points, point_count as i32)
530}