1use fast_mesh_bvh::Bvh;
25use fast_mesh_query::{Cull, RayOptions, raycast_many};
26use fast_mesh_store::Mesh;
27
28use crate::FieldsError;
29use crate::Result;
30
31fn self_gap(magnitude: f32) -> f32 {
35 (32.0 * f32::EPSILON * magnitude).max(f32::MIN_POSITIVE)
36}
37
38fn coordinate_magnitude(mesh: &Mesh) -> f32 {
41 mesh.positions()
42 .iter()
43 .flatten()
44 .map(|c| c.abs())
45 .fold(0.0, f32::max)
46}
47
48pub fn thicknesses(mesh: &Mesh, bvh: &Bvh, max_distance: f32, out: &mut [f32]) -> Result<()> {
63 if out.len() != mesh.vertex_slots() {
64 return Err(FieldsError::InvalidInput(
65 "out must hold one entry per vertex slot",
66 ));
67 }
68 if max_distance.is_nan() || max_distance < 0.0 {
69 return Err(FieldsError::InvalidInput(
70 "max_distance must be zero or greater, and not NaN",
71 ));
72 }
73 if bvh.mesh_uid() != mesh.uid() {
74 return Err(fast_mesh_query::QueryError::MeshMismatch.into());
75 }
76 if !bvh.is_current(mesh) {
77 return Err(fast_mesh_query::QueryError::StaleBvh.into());
78 }
79 let slots = mesh.vertex_slots();
80 let mut normals = vec![[0.0f32; 3]; slots];
81 for t in 0..mesh.triangle_slots() as u32 {
84 let Some(tri) = mesh.triangle(t) else {
85 continue;
86 };
87 if !mesh.is_triangle_alive(t) {
88 continue;
89 }
90 let p = mesh.positions();
91 let [a, b, c] = tri.map(|v| p[v as usize]);
92 let e1 = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
93 let e2 = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
94 let n = [
95 e1[1] * e2[2] - e1[2] * e2[1],
96 e1[2] * e2[0] - e1[0] * e2[2],
97 e1[0] * e2[1] - e1[1] * e2[0],
98 ];
99 for v in tri {
100 for k in 0..3 {
101 normals[v as usize][k] += n[k];
102 }
103 }
104 }
105 for n in &mut normals {
107 let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
108 if len > 0.0 {
109 for c in n.iter_mut() {
110 *c /= len;
111 }
112 }
113 }
114 let gap = self_gap(coordinate_magnitude(mesh));
115 let opts = RayOptions {
116 min_t: gap,
117 max_t: f32::INFINITY,
118 cull: Cull::None,
119 };
120 let mut origins: Vec<[f32; 3]> = Vec::new();
122 let mut dirs: Vec<[f32; 3]> = Vec::new();
123 let mut slots_of: Vec<u32> = Vec::new();
124 for v in 0..slots as u32 {
125 let n = normals[v as usize];
126 let defined = mesh.is_vertex_alive(v)
127 && mesh.corner_count(v) > 0
128 && n != [0.0; 3]
129 && mesh
130 .position(v)
131 .is_some_and(|p| p.iter().all(|c| c.is_finite()));
132 if !defined {
133 continue;
134 }
135 let p = mesh.position(v).unwrap_or([0.0; 3]);
136 origins.push(p);
137 dirs.push(n);
138 origins.push(p);
139 dirs.push([-n[0], -n[1], -n[2]]);
140 slots_of.push(v);
141 slots_of.push(v);
142 }
143 let mut hits = vec![None; origins.len()];
144 if !origins.is_empty() {
145 raycast_many(mesh, bvh, &origins, &dirs, &opts, &mut hits)?;
146 }
147 for v in out.iter_mut() {
148 *v = f32::NAN;
149 }
150 debug_assert_eq!(hits.len(), slots_of.len());
151 for (i, &v) in slots_of.iter().enumerate() {
152 let t = hits[i].as_ref().map(|h| h.t);
153 let cur = out[v as usize];
154 match (cur.is_nan(), t) {
155 (_, None) => {}
157 (true, Some(t)) => {
159 out[v as usize] = if t > max_distance { f32::INFINITY } else { t };
160 }
161 (false, Some(t)) => {
163 let got = if t > max_distance { f32::INFINITY } else { t };
164 if got < cur {
165 out[v as usize] = got;
166 }
167 }
168 }
169 }
170 Ok(())
173}
174
175#[cfg(test)]
176mod tests {
177 use fast_mesh_bvh::{BuildOptions, Bvh};
178 use fast_mesh_store::Mesh;
179
180 use super::*;
181 use crate::test_util::*;
182 use fast_mesh_store::par;
183
184 fn build(mesh: &Mesh) -> Bvh {
185 Bvh::build(
186 mesh,
187 &BuildOptions {
188 max_leaf_size: 2,
189 ..Default::default()
190 },
191 )
192 .unwrap()
193 }
194
195 fn run(mesh: &Mesh, max_distance: f32) -> Vec<f32> {
196 let bvh = build(mesh);
197 let mut out = vec![f32::NAN; mesh.vertex_slots()];
198 thicknesses(mesh, &bvh, max_distance, &mut out).unwrap();
199 out
200 }
201
202 #[test]
203 fn cube_vertices_read_wall_to_wall() {
204 let mesh = cube();
208 let out = run(&mesh, f32::INFINITY);
209 assert_eq!(out.len(), 8);
210 for &v in &out {
211 assert!(v.is_finite(), "{out:?}");
212 assert!((2.0..=2.0 * 3f32.sqrt() + 1e-4).contains(&v), "{out:?}");
213 }
214 }
215
216 fn plate(n: u32) -> Mesh {
219 let h = 0.25;
220 let w = n + 1;
221 let mut positions = Vec::new();
222 for z in [h, -h] {
223 for y in 0..=n {
224 for x in 0..=n {
225 positions.push([x as f32 / n as f32, y as f32 / n as f32, z]);
226 }
227 }
228 }
229 let (top, bottom) = (0, w * w);
230 let t = |x: u32, y: u32| top + y * w + x;
231 let b = |x: u32, y: u32| bottom + y * w + x;
232 let mut tris = Vec::new();
233 for y in 0..n {
234 for x in 0..n {
235 tris.extend([
236 [t(x, y), t(x + 1, y), t(x + 1, y + 1)],
237 [t(x, y), t(x + 1, y + 1), t(x, y + 1)],
238 ]);
239 tris.extend([
240 [b(x, y), b(x + 1, y + 1), b(x + 1, y)],
241 [b(x, y), b(x, y + 1), b(x + 1, y + 1)],
242 ]);
243 }
244 }
245 for x in 0..n {
246 tris.extend([
248 [t(x, 0), b(x, 0), b(x + 1, 0)],
249 [t(x, 0), b(x + 1, 0), t(x + 1, 0)],
250 [t(x, n), t(x + 1, n), b(x + 1, n)],
251 [t(x, n), b(x + 1, n), b(x, n)],
252 ]);
253 tris.extend([
255 [t(0, x), t(0, x + 1), b(0, x + 1)],
256 [t(0, x), b(0, x + 1), b(0, x)],
257 [t(n, x), b(n, x), b(n, x + 1)],
258 [t(n, x), b(n, x + 1), t(n, x + 1)],
259 ]);
260 }
261 mesh(&positions, &tris)
262 }
263
264 #[test]
265 fn slab_faces_read_depth() {
266 let m = plate(4);
268 let out = run(&m, f32::INFINITY);
269 let w = 5;
270 for y in 1..4 {
271 for x in 1..4 {
272 for &v in &[y * w + x, w * w + y * w + x] {
273 assert!((out[v as usize] - 0.5).abs() < 1e-3, "{out:?}");
274 }
275 }
276 }
277 assert!(out.iter().all(|v| v.is_finite()), "{out:?}");
278 }
279
280 #[test]
281 fn sphere_reads_diameter() {
282 let mesh = icosphere();
283 let out = run(&mesh, f32::INFINITY);
284 assert_eq!(out.len(), mesh.vertex_slots());
285 for &v in &out {
286 assert!(v.is_finite(), "{out:?}");
287 assert!((v - 2.0).abs() < 0.05, "{v}");
288 }
289 }
290
291 #[test]
292 fn open_sheet_reads_nan() {
293 let mesh = terrain(4);
295 let out = run(&mesh, f32::INFINITY);
296 assert!(!out.is_empty());
297 for &v in &out {
298 assert!(v.is_nan(), "{v}");
299 }
300 }
301
302 #[test]
303 fn open_cube_rim_reads_nan_while_walls_read_finite() {
304 let (p, mut t) = cube_buffers();
308 t.drain(2..4);
309 let m = mesh(&p, &t);
310 let out = run(&m, f32::INFINITY);
311 assert_eq!(out.len(), 8);
312 for &v in &out {
313 assert!(v.is_nan() || v > 1.0, "{out:?}");
314 }
315 assert!(out.iter().any(|v| v.is_finite()), "{out:?}");
316 }
317
318 #[test]
319 fn far_reads_infinite() {
320 let mesh = cube();
321 let out = run(&mesh, 1.0);
322 for &v in &out {
323 assert_eq!(v, f32::INFINITY, "{out:?}");
324 }
325 let out = run(&mesh, 4.0);
326 for &v in &out {
327 assert!(v.is_finite(), "{out:?}");
328 }
329 }
330
331 #[test]
332 fn dead_and_unused_slots_read_nan() {
333 let (mut positions, tris) = cube_buffers();
336 positions.push([5.0, 5.0, 5.0]);
337 let m = mesh(&positions, &tris);
338 assert_eq!(m.vertex_slots(), 9);
339 assert!(m.is_vertex_alive(8));
340 assert_eq!(m.corner_count(8), 0);
341 let bvh = build(&m);
342 let mut out = vec![7.0f32; m.vertex_slots()];
343 thicknesses(&m, &bvh, f32::INFINITY, &mut out).unwrap();
344 assert!(out[8].is_nan(), "{out:?}");
345 assert!(out[..8].iter().all(|v| v.is_finite()), "{out:?}");
346 }
347
348 #[test]
349 fn invalid_input_writes_nothing() {
350 let mesh = cube();
351 let bvh = build(&mesh);
352 let mut out = vec![7.0f32; mesh.vertex_slots()];
353 let bad_len = thicknesses(&mesh, &bvh, f32::INFINITY, &mut out[..4]);
354 assert!(matches!(bad_len, Err(FieldsError::InvalidInput(_))));
355 assert!(matches!(
356 thicknesses(&mesh, &bvh, f32::NAN, &mut out),
357 Err(FieldsError::InvalidInput(_))
358 ));
359 assert!(matches!(
360 thicknesses(&mesh, &bvh, -1.0, &mut out),
361 Err(FieldsError::InvalidInput(_))
362 ));
363 assert_eq!(out, vec![7.0; mesh.vertex_slots()]);
364 let other = cube();
365 let other_bvh = build(&other);
366 assert!(matches!(
367 thicknesses(&mesh, &other_bvh, 1.0, &mut out),
368 Err(FieldsError::Query(_))
369 ));
370 assert_eq!(out, vec![7.0; mesh.vertex_slots()]);
371 }
372
373 #[test]
374 fn thickness_bit_identical() {
375 let mesh = torus();
379 let bvh = build(&mesh);
380 assert!(mesh.triangle_count() >= 500);
381 let run = || {
382 let mut out = vec![f32::NAN; mesh.vertex_slots()];
383 thicknesses(&mesh, &bvh, f32::INFINITY, &mut out).unwrap();
384 out.iter().map(|x| x.to_bits()).collect::<Vec<_>>()
385 };
386 par::set_force_sequential(true);
387 let reference = run();
388 par::set_force_sequential(false);
389 if par::enabled() {
390 for k in [1usize, 2, 8] {
391 assert_eq!(par::with_threads(k, run), reference, "{k} threads");
392 }
393 }
394 let mut out = vec![f32::NAN; mesh.vertex_slots()];
396 thicknesses(&mesh, &bvh, f32::INFINITY, &mut out).unwrap();
397 assert!(out.iter().all(|v| v.is_finite()), "{out:?}");
398 }
399}