oxijolt/shape/mesh.rs
1//! Triangle meshes.
2
3use std::ptr::null;
4
5use oxijolt_sys::*;
6
7use super::geometry::{cross, length, length_sq, sub, v3, V3};
8use super::{initialize, Shape, ShapeSettings};
9use crate::{limits, MeshError, PhysicsMaterial, ShapeError, Vec3};
10
11/// Most materials Jolt accepts for one mesh (`MeshShape::FLAGS_MATERIAL_BITS` = 5).
12const MAX_MESH_MATERIALS: usize = 32;
13/// Most triangles Jolt stores per leaf of a mesh's tree (`MeshShape::MaxTrianglesPerLeaf`).
14const MAX_TRIANGLES_PER_LEAF: u32 = 8;
15/// Smallest `|(v1 - v0) x (v2 - v0)|` (twice the area, m²) of a triangle given to Jolt. Jolt's
16/// collision detection asserts on a triangle whose `f32` cross product has a squared length of
17/// at most 1e-12 (`EPAPenetrationDepth::GetPenetrationDepthStepGJK`, `IsNearZero`); the floor
18/// is 1e-6 raised by a relative 1e-6, about eight times the rounding of that squared length.
19const MIN_TRIANGLE_CROSS: f64 = 1.000_001e-6;
20/// How many times the largest change Jolt's rounding can make to a triangle's cross product the
21/// cross product must keep above [`MIN_TRIANGLE_CROSS`]; see [`is_collidable`].
22const CROSS_ERROR_MARGIN: f64 = 2.0;
23/// Steps of Jolt's 21-bit vertex quantization across a mesh's bounds on each axis
24/// (`TriangleCodecIndexed8BitPackSOA4Flags::COMPONENT_MASK`).
25const QUANTIZATION_STEPS: f64 = ((1u32 << 21) - 1) as f64;
26
27/// How Jolt builds a mesh's bounding volume tree (Jolt `MeshShapeSettings::EBuildQuality`).
28#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
29pub enum MeshBuildQuality {
30 /// A tree that is slower to build and faster to query. Jolt's default.
31 #[default]
32 FavorRuntimePerformance,
33 /// A tree that is faster to build and slower to query.
34 FavorBuildSpeed,
35}
36
37impl MeshBuildQuality {
38 fn to_jph(self) -> JPH_Mesh_Shape_BuildQuality {
39 match self {
40 Self::FavorRuntimePerformance => JPH_Mesh_Shape_BuildQuality_FavorRuntimePerformance,
41 Self::FavorBuildSpeed => JPH_Mesh_Shape_BuildQuality_FavorBuildSpeed,
42 }
43 }
44}
45
46/// Settings of [`Shape::new_mesh_with_settings`] other than the geometry. The defaults are
47/// Jolt's (`MeshShapeSettings`): no materials, an active-edge threshold of 5 degrees, 8
48/// triangles per leaf, [`MeshBuildQuality::FavorRuntimePerformance`]; and convex shapes up to
49/// [`MeshSettings::DEFAULT_MAX_CONVEX_EXTENT`].
50#[derive(Clone, Debug)]
51pub struct MeshSettings<'a> {
52 materials: Option<(&'a [&'a PhysicsMaterial], &'a [u8])>,
53 active_edge_cos_threshold_angle: f32,
54 max_triangles_per_leaf: u32,
55 build_quality: MeshBuildQuality,
56 max_convex_extent: f32,
57}
58
59impl Default for MeshSettings<'_> {
60 fn default() -> Self {
61 Self {
62 materials: None,
63 active_edge_cos_threshold_angle: 0.996195,
64 max_triangles_per_leaf: 8,
65 build_quality: MeshBuildQuality::default(),
66 max_convex_extent: Self::DEFAULT_MAX_CONVEX_EXTENT,
67 }
68 }
69}
70
71impl<'a> MeshSettings<'a> {
72 /// The default of [`max_convex_extent`](Self::max_convex_extent), metres: the largest round
73 /// extent at which every prop of the real models tested in CI loses under 0.1 % of its area
74 /// to the triangle rule ([docs/real-meshes.md]). A mesh of small detailed objects that only
75 /// meets small bodies keeps more of its triangles with a smaller extent. Convex shapes larger than the extent a mesh
76 /// was built for can trip Jolt's assertions on its thinnest triangles in an asserts build;
77 /// a mesh that must meet them is built with a larger extent. See
78 /// [docs/limits.md#convex-shapes-against-meshes].
79 ///
80 /// [docs/real-meshes.md]: https://github.com/pockerhead/oxijolt/blob/main/docs/real-meshes.md
81 /// [docs/limits.md#convex-shapes-against-meshes]: https://github.com/pockerhead/oxijolt/blob/main/docs/limits.md#convex-shapes-against-meshes
82 pub const DEFAULT_MAX_CONVEX_EXTENT: f32 = 300.0;
83
84 /// Gives triangle `i` the material `list[indices[i]]`. `list` holds 1 to 32 materials,
85 /// `indices` one entry per triangle, each naming a material of the list. Without materials
86 /// every triangle uses Jolt's default material. The shape holds its own reference to each
87 /// material.
88 #[must_use]
89 pub fn materials(mut self, list: &'a [&'a PhysicsMaterial], indices: &'a [u8]) -> Self {
90 self.materials = Some((list, indices));
91 self
92 }
93
94 /// Cosine of the angle between two triangles above which their shared edge counts as
95 /// active, in `[-1, 1]`; a negative value makes every edge active. Concave edges are never
96 /// active. Smaller values give more ghost collisions with edges, larger ones slower
97 /// depenetration (Jolt's wording). Default `0.996195`, the cosine of 5 degrees.
98 #[must_use]
99 pub fn active_edge_cos_threshold_angle(mut self, value: f32) -> Self {
100 self.active_edge_cos_threshold_angle = value;
101 self
102 }
103
104 /// Most triangles per leaf of the tree, in `1..=8`. Default 8.
105 #[must_use]
106 pub fn max_triangles_per_leaf(mut self, value: u32) -> Self {
107 self.max_triangles_per_leaf = value;
108 self
109 }
110
111 /// How the tree is built. Default [`MeshBuildQuality::FavorRuntimePerformance`].
112 #[must_use]
113 pub fn build_quality(mut self, value: MeshBuildQuality) -> Self {
114 self.build_quality = value;
115 self
116 }
117
118 /// The size of the largest convex shape the mesh must collide with reliably, metres: the
119 /// largest absolute coordinate of the convex shape's local bounds (relative to its centre of
120 /// mass, after scaling) plus the separation distance of the collide query. Jolt collides a
121 /// triangle in the convex shape's space, so its rounding grows with this extent, and the
122 /// mesh drops the triangles too thin for it. In `0..=2 * limits::MAX_SHAPE_EXTENT`; default
123 /// [`Self::DEFAULT_MAX_CONVEX_EXTENT`]. A larger convex shape against a thin triangle can
124 /// trip Jolt's assertions in an asserts build and gets a distorted contact otherwise; see
125 /// [docs/limits.md#convex-shapes-against-meshes].
126 ///
127 /// [docs/limits.md#convex-shapes-against-meshes]: https://github.com/pockerhead/oxijolt/blob/main/docs/limits.md#convex-shapes-against-meshes
128 #[must_use]
129 pub fn max_convex_extent(mut self, metres: f32) -> Self {
130 // -0.0 is in range; keep it as 0.0 so that errors name it as 0 m.
131 self.max_convex_extent = if metres == 0.0 { 0.0 } else { metres };
132 self
133 }
134
135 /// Checks everything but the geometry against `triangle_count`.
136 fn validate(&self, triangle_count: usize) -> Result<(), ShapeError> {
137 let invalid = |what| Err(ShapeError::InvalidValue(what));
138 let threshold = self.active_edge_cos_threshold_angle;
139 if !(threshold.is_finite() && (-1.0..=1.0).contains(&threshold)) {
140 return invalid("active_edge_cos_threshold_angle must be between -1 and 1");
141 }
142 if !(1..=MAX_TRIANGLES_PER_LEAF).contains(&self.max_triangles_per_leaf) {
143 return invalid("max_triangles_per_leaf must be between 1 and 8");
144 }
145 if !(0.0..=2.0 * limits::MAX_SHAPE_EXTENT).contains(&self.max_convex_extent) {
146 return invalid("max_convex_extent must be between 0 and 2 * limits::MAX_SHAPE_EXTENT");
147 }
148 if let Some((list, indices)) = self.materials {
149 if !(1..=MAX_MESH_MATERIALS).contains(&list.len()) {
150 return invalid("mesh material list must hold 1 to 32 materials");
151 }
152 if indices.len() != triangle_count {
153 return invalid("mesh material indices must hold one value per triangle");
154 }
155 if indices
156 .iter()
157 .any(|&index| usize::from(index) >= list.len())
158 {
159 return invalid("material indices must name a material of the list");
160 }
161 }
162 Ok(())
163 }
164}
165
166/// Whether Jolt can index `vertices` vertices and `triangles` triangles: its mesh code stores
167/// both as `int`.
168fn mesh_counts_fit(vertices: usize, triangles: usize) -> bool {
169 let max = i32::MAX as usize;
170 vertices <= max && triangles <= max
171}
172
173/// The geometry checks of [`Shape::new_mesh_with_settings`].
174fn validate_geometry(vertices: &[Vec3], triangles: &[[u32; 3]]) -> Result<(), ShapeError> {
175 if vertices.is_empty() || triangles.is_empty() {
176 return Err(ShapeError::InvalidValue(
177 "a mesh needs vertices and triangles",
178 ));
179 }
180 if !mesh_counts_fit(vertices.len(), triangles.len()) {
181 return Err(ShapeError::InvalidValue(
182 "a mesh has at most i32::MAX vertices and triangles",
183 ));
184 }
185 if !vertices
186 .iter()
187 .all(|&vertex| limits::is_local_offset(vertex))
188 {
189 return Err(ShapeError::InvalidValue(
190 "mesh vertices must be finite and within limits::MAX_SHAPE_EXTENT",
191 ));
192 }
193 // Jolt reads `vertices[index]` without a range check when it sanitizes the triangles.
194 if triangles
195 .iter()
196 .flatten()
197 .any(|&index| index as usize >= vertices.len())
198 {
199 return Err(ShapeError::InvalidValue(
200 "triangle index beyond the vertex list",
201 ));
202 }
203 Ok(())
204}
205
206/// The triangles [`Shape::new_mesh`] left out because they are too small or too thin for Jolt to
207/// collide with reliably (degenerate triangles among them).
208#[derive(Clone, Debug, Default, PartialEq)]
209pub struct DroppedTriangles {
210 indices: Vec<usize>,
211 area: f64,
212}
213
214impl DroppedTriangles {
215 /// How many triangles were dropped.
216 pub fn count(&self) -> usize {
217 self.indices.len()
218 }
219
220 /// Whether every triangle was kept.
221 pub fn is_empty(&self) -> bool {
222 self.indices.is_empty()
223 }
224
225 /// The positions of the dropped triangles in the `triangles` given to the constructor, in
226 /// ascending order.
227 pub fn indices(&self) -> &[usize] {
228 &self.indices
229 }
230
231 /// The total area of the dropped triangles, m².
232 pub fn area(&self) -> f32 {
233 self.area as f32
234 }
235
236 fn push(&mut self, index: usize, [a, b, c]: [V3; 3]) {
237 self.indices.push(index);
238 self.area += 0.5 * length(cross(sub(b, a), sub(c, a)));
239 }
240}
241
242impl Shape {
243 /// A triangle mesh of `vertices` (shape space, metres) and `triangles` (three indices into
244 /// `vertices` each) with [`MeshSettings::default`], and the triangles it dropped; see
245 /// [`new_mesh_with_settings`](Self::new_mesh_with_settings).
246 pub fn new_mesh(
247 vertices: &[Vec3],
248 triangles: &[[u32; 3]],
249 ) -> Result<(Self, DroppedTriangles), ShapeError> {
250 Self::new_mesh_with_settings(vertices, triangles, &MeshSettings::default())
251 }
252
253 /// A triangle mesh of `vertices` (shape space, metres) and `triangles` (three indices into
254 /// `vertices` each), and the triangles it dropped.
255 ///
256 /// A triangle's front face is the side from which its vertices run counter-clockwise.
257 /// Triangles too small or too thin for Jolt to collide with reliably are dropped and
258 /// reported in [`DroppedTriangles`]: twice a triangle's area must be above 1e-6 m² plus
259 /// twice the largest change Jolt's 21-bit vertex quantization and `f32` rounding can make
260 /// to it. That margin follows the triangle's own shape and distance from the shape origin,
261 /// the quantization step of the mesh's bounds on each axis and the size of the convex shapes
262 /// it collides with ([`MeshSettings::max_convex_extent`]); with the defaults a strip 1 m long
263 /// near the origin is kept from about 0.15 mm wide along the axes and from 0.26 mm in any
264 /// orientation ([docs/limits.md#triangle-meshes]). Jolt itself
265 /// keeps one copy of duplicate triangles and reorders the rest, so sub-shape ids do not
266 /// follow the input order. Closest-hit rays hit back faces too.
267 ///
268 /// ```
269 /// # use oxijolt::{Shape, Vec3};
270 /// let vertices = [Vec3::ZERO, Vec3::new(0.0, 0.0, 1.0), Vec3::new(1.0, 0.0, 0.0)];
271 /// // The second triangle repeats a vertex.
272 /// let (mesh, dropped) = Shape::new_mesh(&vertices, &[[0, 1, 2], [0, 0, 1]])?;
273 /// assert_eq!(dropped.indices(), [1]);
274 /// # drop(mesh);
275 /// # Ok::<(), oxijolt::ShapeError>(())
276 /// ```
277 ///
278 /// Meshes have no volume. They suit static bodies, and kinematic bodies with an explicit
279 /// [`BodySettings::mass`](crate::BodySettings::mass);
280 /// [`PhysicsWorld::create_body`](crate::PhysicsWorld::create_body) refuses them for
281 /// dynamic bodies.
282 ///
283 /// # Errors
284 /// - [`ShapeError::InvalidValue`]: no vertices or no triangles, more than `i32::MAX` of
285 /// either, an index beyond `vertices`, or a setting out of range (see [`MeshSettings`]);
286 /// - [`ShapeError::InvalidValue`]: a vertex (referenced or not) that is not finite or
287 /// has a component beyond [`limits::MAX_SHAPE_EXTENT`] in absolute value;
288 /// - [`ShapeError::Mesh`]: no triangle is left after dropping small, thin and degenerate
289 /// ones;
290 /// - [`ShapeError::Rejected`]: anything else Jolt refuses.
291 ///
292 /// Building cost grows with the triangle count; see [docs/benchmarks.md] and
293 /// [docs/limits.md#triangle-meshes].
294 ///
295 /// [docs/benchmarks.md]: https://github.com/pockerhead/oxijolt/blob/main/docs/benchmarks.md
296 /// [docs/limits.md#triangle-meshes]: https://github.com/pockerhead/oxijolt/blob/main/docs/limits.md#triangle-meshes
297 pub fn new_mesh_with_settings(
298 vertices: &[Vec3],
299 triangles: &[[u32; 3]],
300 settings: &MeshSettings<'_>,
301 ) -> Result<(Self, DroppedTriangles), ShapeError> {
302 validate_geometry(vertices, triangles)?;
303 settings.validate(triangles.len())?;
304 initialize()?;
305 let (kept, dropped) = collidable_triangles(vertices, triangles, settings.max_convex_extent);
306 if kept.is_empty() {
307 return Err(ShapeError::Mesh(MeshError::NoTriangles));
308 }
309 let jolt_settings = mesh_settings(vertices, triangles, &kept, settings)?;
310 let mesh: *mut JPH_MeshShapeSettings = jolt_settings.as_ptr();
311 // Jolt's own clean-up has run. After `collidable_triangles` it can only drop duplicates,
312 // which leaves a copy; the count is Jolt's own verdict on what is left.
313 // SAFETY: the settings are live, owned by the guard and were created as mesh settings.
314 if unsafe { JPH_MeshShapeSettings_GetTriangleCount(mesh) } == 0 {
315 return Err(ShapeError::Mesh(MeshError::NoTriangles));
316 }
317 let shape = jolt_settings.create()?.within_extent_bounds()?;
318 shape.record_convex_extent(settings.max_convex_extent);
319 Ok((shape, dropped))
320 }
321
322 /// Keeps `extent` in the new mesh's Jolt user data (`Shape::mUserData`), with
323 /// [`CONVEX_EXTENT_TAG`] in the high half and the bits of `extent` in the low half, so that
324 /// [`Shape::new_scaled`] checks the mesh against the extent it was built for. The safe API does
325 /// not expose a shape's user data otherwise.
326 fn record_convex_extent(&self, extent: f32) {
327 let data = CONVEX_EXTENT_TAG | u64::from(extent.to_bits());
328 // SAFETY: the mesh is live and was just created by the caller, which has not handed it
329 // out, so nothing reads it while the user data changes.
330 unsafe { JPH_Shape_SetUserData(self.0.as_ptr(), data) };
331 }
332}
333
334/// Marks the user data of a mesh built by [`Shape::new_mesh_with_settings`].
335const CONVEX_EXTENT_TAG: u64 = 1 << 32;
336
337/// The [`MeshSettings::max_convex_extent`] the mesh or heightfield `leaf` was built for; the
338/// default for heightfields and for meshes built outside [`Shape::new_mesh_with_settings`].
339///
340/// # Safety
341/// `leaf` is a live shape.
342pub(super) unsafe fn convex_extent_of(leaf: *const JPH_Shape) -> f32 {
343 // SAFETY: `leaf` is live (contract); the getter only reads it.
344 let data = unsafe { JPH_Shape_GetUserData(leaf) };
345 if data >> 32 == CONVEX_EXTENT_TAG >> 32 {
346 f32::from_bits(data as u32)
347 } else {
348 MeshSettings::DEFAULT_MAX_CONVEX_EXTENT
349 }
350}
351
352/// The indices of the triangles Jolt can collide with reliably, in input order, and the rest.
353///
354/// Jolt stores vertices quantized to 21 bits over the bounds of the triangles it keeps, one step
355/// per axis. A triangle that fails [`is_collidable`] without quantization fails with any, so the
356/// bounds leave it out; the triangles kept then lie within bounds no larger than those Jolt
357/// quantizes over. See [docs/limits.md#triangle-meshes].
358///
359/// [docs/limits.md#triangle-meshes]: https://github.com/pockerhead/oxijolt/blob/main/docs/limits.md#triangle-meshes
360fn collidable_triangles(
361 vertices: &[Vec3],
362 triangles: &[[u32; 3]],
363 convex_extent: f32,
364) -> (Vec<usize>, DroppedTriangles) {
365 let corners = |triangle: &[u32; 3]| triangle.map(|index| v3(vertices[index as usize]));
366 let candidates = triangles
367 .iter()
368 .map(corners)
369 .filter(|&corners| is_collidable(corners, [0.0; 3], convex_extent));
370 let step = quantization_step(candidates);
371 let mut kept = Vec::new();
372 let mut dropped = DroppedTriangles::default();
373 for (index, triangle) in triangles.iter().enumerate() {
374 let corners = corners(triangle);
375 if is_collidable(corners, step, convex_extent) {
376 kept.push(index);
377 } else {
378 dropped.push(index, corners);
379 }
380 }
381 (kept, dropped)
382}
383
384/// The step of Jolt's vertex quantization on each axis over the bounds of `triangles`; zero for
385/// none.
386fn quantization_step(triangles: impl Iterator<Item = [V3; 3]>) -> V3 {
387 let mut low = [f64::INFINITY; 3];
388 let mut high = [f64::NEG_INFINITY; 3];
389 for vertex in triangles.flatten() {
390 for axis in 0..3 {
391 low[axis] = low[axis].min(vertex[axis]);
392 high[axis] = high[axis].max(vertex[axis]);
393 }
394 }
395 [0, 1, 2].map(|axis| ((high[axis] - low[axis]) / QUANTIZATION_STEPS).max(0.0))
396}
397
398/// How far Jolt's `f32` arithmetic can move a corner of a triangle on each axis when it collides
399/// the triangle with a convex shape (`CollideConvexVsTriangles::Collide`): the transform of the
400/// triangle's own coordinates, at most `distance` from their origin, and the rounding of the
401/// result in the convex shape's space. There every coordinate is at most `convex_extent` plus
402/// the triangle's `longest_edge`, because Jolt only goes on with triangles whose bounds overlap
403/// the convex shape's.
404fn rounding_displacement(distance: f64, convex_extent: f64, longest_edge: f64) -> f64 {
405 let epsilon = f64::from(f32::EPSILON);
406 4.0 * epsilon * distance + epsilon * (convex_extent + longest_edge)
407}
408
409/// Whether the triangle with `corners` keeps a cross product above [`MIN_TRIANGLE_CROSS`] under
410/// Jolt's rounding against convex shapes up to `convex_extent`
411/// ([`MeshSettings::max_convex_extent`]), with a factor [`CROSS_ERROR_MARGIN`] to spare.
412///
413/// Each corner can move by up to `step[axis]` on each axis (the quantization of a mesh being
414/// built, zero for triangles Jolt has stored) plus [`rounding_displacement`]; an edge moves by
415/// the difference of two corner moves. With edges `ab`, `ac` from one corner moved by `e1`,
416/// `e2`, the cross product changes by `ab × e2 + e1 × ac + e1 × e2`. Its length is at least its
417/// component along the unit normal `n`, which changes by `e2 · (n × ab) + e1 · (ac × n) + n ·
418/// (e1 × e2)`: moves within the triangle's plane across an edge shrink it, moves out of the
419/// plane do not. The cross product is the same from every corner, so the smallest of the three
420/// corners' bounds holds, and the rule does not depend on the order of the corners. Jolt's `f32`
421/// cross product, from whichever corner it starts, rounds it once more.
422pub(super) fn is_collidable(corners: [V3; 3], step: V3, convex_extent: f32) -> bool {
423 let [a, b, c] = corners;
424 let normal = cross(sub(b, a), sub(c, a));
425 let twice_area = length(normal);
426 if twice_area < MIN_TRIANGLE_CROSS {
427 return false;
428 }
429 let unit = normal.map(|component| component / twice_area);
430 let distance = corners.map(length).into_iter().fold(0.0, f64::max);
431 // The two edges leaving each corner, in the order that gives the same normal.
432 let edge_pairs = [
433 (sub(b, a), sub(c, a)),
434 (sub(c, b), sub(a, b)),
435 (sub(a, c), sub(b, c)),
436 ];
437 let longest_edge = edge_pairs
438 .iter()
439 .map(|&(edge, _)| length(edge))
440 .fold(0.0, f64::max);
441 let rounding = rounding_displacement(distance, f64::from(convex_extent), longest_edge);
442 let edge_move = step.map(|axis_step| 2.0 * (axis_step + rounding));
443 let shrink = edge_pairs
444 .iter()
445 .map(|&(first, second)| {
446 reach(cross(unit, first), edge_move) + reach(cross(second, unit), edge_move)
447 })
448 .fold(f64::INFINITY, f64::min);
449 let cross_rounding = edge_pairs
450 .iter()
451 .map(|&(first, second)| 2.0 * f64::from(f32::EPSILON) * length(first) * length(second))
452 .fold(0.0, f64::max);
453 let change = shrink + length_sq(edge_move) + cross_rounding;
454 twice_area >= MIN_TRIANGLE_CROSS + CROSS_ERROR_MARGIN * change
455}
456
457/// The largest `|e · direction|` over every `e` with `|e[axis]| <= bound[axis]`.
458fn reach(direction: V3, bound: V3) -> f64 {
459 (0..3).map(|axis| bound[axis] * direction[axis].abs()).sum()
460}
461
462/// Jolt mesh settings holding the validated geometry, of the triangles `kept`, and `settings`.
463fn mesh_settings(
464 vertices: &[Vec3],
465 triangles: &[[u32; 3]],
466 kept: &[usize],
467 settings: &MeshSettings<'_>,
468) -> Result<ShapeSettings, ShapeError> {
469 let jolt_vertices: Vec<JPH_Vec3> = vertices.iter().map(|vertex| vertex.to_jph()).collect();
470 let material_indices = settings.materials.map(|(_, indices)| indices);
471 let jolt_triangles: Vec<JPH_IndexedTriangle> = kept
472 .iter()
473 .map(|&i| {
474 let [i1, i2, i3] = triangles[i];
475 JPH_IndexedTriangle {
476 i1,
477 i2,
478 i3,
479 materialIndex: material_indices.map_or(0, |indices| u32::from(indices[i])),
480 userData: 0,
481 }
482 })
483 .collect();
484 let list: Vec<*const JPH_PhysicsMaterial> = settings
485 .materials
486 .map(|(list, _)| list.iter().map(|material| material.as_ptr()).collect())
487 .unwrap_or_default();
488 // SAFETY: Jolt is initialised. Every array is live for the call and holds the count passed;
489 // the counts fit `i32` and every index is below the vertex count (`validate_geometry`), and
490 // every material index addresses `list`, or is 0 without one (`MeshSettings::validate`).
491 // The materials are live, borrowed by `settings`; Jolt's list takes its own references.
492 // The returned settings hold one reference, which the guard takes over.
493 let jolt_settings = unsafe {
494 ShapeSettings::from_raw(
495 JPH_MeshShapeSettings_Create3(
496 jolt_vertices.as_ptr(),
497 jolt_vertices.len() as u32,
498 jolt_triangles.as_ptr(),
499 jolt_triangles.len() as u32,
500 if list.is_empty() {
501 null()
502 } else {
503 list.as_ptr()
504 },
505 list.len() as u32,
506 )
507 .cast(),
508 )
509 }?;
510 let mesh: *mut JPH_MeshShapeSettings = jolt_settings.as_ptr();
511 // SAFETY: the settings are live, owned by the guard, were created as mesh settings and have
512 // not created a shape yet; every value was validated.
513 unsafe {
514 JPH_MeshShapeSettings_SetActiveEdgeCosThresholdAngle(
515 mesh,
516 settings.active_edge_cos_threshold_angle,
517 );
518 JPH_MeshShapeSettings_SetMaxTrianglesPerLeaf(mesh, settings.max_triangles_per_leaf);
519 JPH_MeshShapeSettings_SetBuildQuality(mesh, settings.build_quality.to_jph());
520 }
521 Ok(jolt_settings)
522}
523
524#[cfg(test)]
525mod tests;