1use fast_mesh_bvh::Bvh;
4use fast_mesh_query::{ClosestHit, ClosestOptions, QueryError, closest_point, closest_point_many};
5use fast_mesh_store::{Mesh, par};
6
7use crate::{FieldsError, Result, SignData};
8
9pub(crate) const PAR_MIN_POINTS: usize = 512;
14
15pub(crate) const POINT_CHUNK: usize = 128;
17
18pub fn signed_distances(
46 mesh_b: &Mesh,
47 bvh_b: &Bvh,
48 sign: &SignData,
49 points_in_b: &[[f32; 3]],
50 max_distance: f32,
51 out: &mut [f32],
52) -> Result<()> {
53 if points_in_b.len() != out.len() {
54 return Err(FieldsError::InvalidInput(
55 "points and out must have the same length",
56 ));
57 }
58 if max_distance.is_nan() || max_distance < 0.0 {
59 return Err(FieldsError::InvalidInput(
60 "max_distance must be zero or greater, and not NaN",
61 ));
62 }
63 if !points_in_b.iter().flatten().all(|c| c.is_finite()) {
64 return Err(FieldsError::InvalidInput("points must be finite"));
65 }
66 if bvh_b.mesh_uid() != mesh_b.uid() {
67 return Err(QueryError::MeshMismatch.into());
68 }
69 if !bvh_b.is_current(mesh_b) {
70 return Err(QueryError::StaleBvh.into());
71 }
72 if !sign.is_current(mesh_b, bvh_b) {
73 return Err(FieldsError::InvalidInput(
74 "sign data is not current for this mesh and BVH",
75 ));
76 }
77 let opts = ClosestOptions { max_distance };
81 let sequential =
82 points_in_b.len() < PAR_MIN_POINTS || !par::enabled() || par::force_sequential();
83 let mut hits: Vec<Option<ClosestHit>> = Vec::new();
84 hits.try_reserve_exact(points_in_b.len())
85 .map_err(|_| FieldsError::OutOfMemory)?;
86 hits.resize(points_in_b.len(), None);
87 if sequential {
88 par::set_force_sequential(true);
89 }
90 let found = closest_point_many(mesh_b, bvh_b, points_in_b, &opts, &mut hits);
91 if sequential {
92 par::set_force_sequential(false);
93 }
94 found?;
95 if sequential {
96 for ((&p, hit), v) in points_in_b.iter().zip(&hits).zip(out.iter_mut()) {
97 *v = hit_value(mesh_b, bvh_b, sign, p, hit.as_ref());
98 }
99 return Ok(());
100 }
101 let oom = || FieldsError::OutOfMemory;
103 let parts = par::map_range(points_in_b.len(), POINT_CHUNK, PAR_MIN_POINTS, oom, |r| {
104 let mut part = Vec::new();
105 part.try_reserve_exact(r.len()).map_err(|_| oom())?;
106 for i in r {
107 part.push(hit_value(
108 mesh_b,
109 bvh_b,
110 sign,
111 points_in_b[i],
112 hits[i].as_ref(),
113 ));
114 }
115 Ok(part)
116 })?;
117 for (chunk, part) in out.chunks_mut(POINT_CHUNK).zip(parts) {
118 chunk.copy_from_slice(&part);
119 }
120 Ok(())
121}
122
123pub(crate) fn point_value(
126 mesh: &Mesh,
127 bvh: &Bvh,
128 sign: &SignData,
129 p: [f32; 3],
130 max_distance: f32,
131) -> Result<f32> {
132 let opts = ClosestOptions { max_distance };
133 let hit = closest_point(mesh, bvh, p, &opts)?;
134 Ok(hit_value(mesh, bvh, sign, p, hit.as_ref()))
135}
136
137pub(crate) fn hit_value(
139 mesh: &Mesh,
140 bvh: &Bvh,
141 sign: &SignData,
142 p: [f32; 3],
143 hit: Option<&ClosestHit>,
144) -> f32 {
145 match hit {
146 None => f32::INFINITY,
147 Some(hit) if hit.distance == 0.0 => 0.0,
148 Some(hit) if sign.is_inside(mesh, bvh, p, hit) => -hit.distance,
149 Some(hit) => hit.distance,
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use fast_mesh_query::{ClosestOptions, Feature, QueryError, closest_point};
156 use fast_mesh_store::{Mesh, par};
157
158 use super::*;
159 use crate::test_util::*;
160 use crate::{FieldsError, SignMode};
161
162 fn distances(mesh: &mut Mesh, points: &[[f32; 3]], max_distance: f32) -> (SignData, Vec<f32>) {
163 let bvh = build_bvh(mesh);
164 let sign = SignData::build(mesh, &bvh, SignMode::Auto).unwrap();
165 let mut out = vec![f32::NAN; points.len()];
166 signed_distances(mesh, &bvh, &sign, points, max_distance, &mut out).unwrap();
167 (sign, out)
168 }
169
170 #[test]
171 fn closed_meshes_match_exact_sign() {
172 let mut rng = Lcg(7);
173 let meshes = [
174 ("cube", cube()),
175 ("icosphere", icosphere()),
176 ("torus", torus()),
177 ("tetrahedron", tetrahedron()),
178 ("star prism", star_prism()),
179 ];
180 for (name, mut mesh) in meshes {
181 let near = diagonal(&mesh) as f32 / 20.0;
182 let points = sample_points(&mesh, &mut rng, 2000, near);
183 let (sign, out) = distances(&mut mesh, &points, f32::INFINITY);
184 assert_eq!(sign.status().mode, SignMode::Pseudonormal, "{name}");
185 assert!(sign.status().closed, "{name}");
186 let checked = check_against_exact(&mesh, &points, &out);
187 assert!(checked > 1900, "{name}: only {checked} signs checked");
188 assert!(out.iter().any(|&v| v < 0.0), "{name}: no point inside");
189 assert!(out.iter().any(|&v| v > 0.0), "{name}: no point outside");
190 }
191 }
192
193 #[test]
194 fn closed_meshes_match_exact_sign_at_vertices_and_edges() {
195 let mut rng = Lcg(11);
198 let meshes = [
199 ("cube", cube()),
200 ("icosphere", icosphere()),
201 ("torus", torus()),
202 ("tetrahedron", tetrahedron()),
203 ("star prism", star_prism()),
204 ];
205 for (name, mut mesh) in meshes {
206 let d = diagonal(&mesh) as f32;
207 let mut points = Vec::new();
208 for t in 0..mesh.triangle_slots() as u32 {
209 let tri = mesh
210 .triangle(t)
211 .unwrap()
212 .map(|v| mesh.positions()[v as usize]);
213 for k in 0..3 {
214 let (a, b) = (tri[k], tri[(k + 1) % 3]);
215 let mid: [f32; 3] = std::array::from_fn(|i| (a[i] + b[i]) / 2.0);
216 for base in [a, mid] {
217 let off = rng.point([-0.05 * d; 3], [0.05 * d; 3]);
218 points.push(std::array::from_fn(|i| base[i] + off[i]));
219 }
220 }
221 }
222 let (_, out) = distances(&mut mesh, &points, f32::INFINITY);
223 let checked = check_against_exact(&mesh, &points, &out);
224 assert!(
225 checked * 10 > points.len() * 9,
226 "{name}: only {checked} of {} signs checked",
227 points.len()
228 );
229 }
230 }
231
232 #[test]
233 fn open_sheet_sign_past_the_rim_uses_boundary_edges() {
234 let n = 16;
238 let mut mesh = terrain(n);
239 let mut rng = Lcg(13);
240 let mut points = Vec::new();
241 let mut below = Vec::new();
242 for i in 0..1000 {
243 let x = 1.0 + (n - 2) as f32 * rng.next();
244 let y = -(0.02 + 0.3 * rng.next());
245 let h = 0.02 + 0.3 * rng.next();
246 let z = terrain_surface(&mesh, n, f64::from(x), 0.0) as f32;
247 let under = i % 2 == 0;
248 points.push([x, y, if under { z - h } else { z + h }]);
249 below.push(under);
250 }
251 let (_, out) = distances(&mut mesh, &points, f32::INFINITY);
252 let bvh = build_bvh(&mesh);
253 let mut on_edge = 0;
254 for ((&p, &value), &under) in points.iter().zip(&out).zip(&below) {
255 let hit = closest_point(&mesh, &bvh, p, &ClosestOptions::default());
256 if matches!(hit.unwrap().unwrap().feature, Feature::Edge(_)) {
257 on_edge += 1;
258 }
259 assert_eq!(value < 0.0, under, "sign at {p:?}: value {value}");
260 }
261 assert!(on_edge > 900, "only {on_edge} closest points on edges");
262 }
263
264 #[test]
265 fn boundary_signal_marks_open_rim() {
266 let n = 16;
269 let mut mesh = terrain(n);
270 let bvh = build_bvh(&mesh);
271 let sign = SignData::build(&mut mesh, &bvh, SignMode::Auto).unwrap();
272 assert!(!sign.status().closed);
273 let mut rng = Lcg(13);
274 let mut rim_boundary = 0;
275 for _ in 0..200 {
276 let x = 1.0 + (n - 2) as f32 * rng.next();
277 let y = -(0.02 + 0.3 * rng.next());
278 let z = terrain_surface(&mesh, n, f64::from(x), 0.0) as f32;
279 let p = [x, y, z + 0.1];
280 let hit = closest_point(&mesh, &bvh, p, &ClosestOptions::default())
281 .unwrap()
282 .unwrap();
283 assert_eq!(
284 SignData::is_boundary_hit(&mesh, &hit),
285 Some(true),
286 "at {p:?}"
287 );
288 rim_boundary += 1;
289 }
290 assert_eq!(rim_boundary, 200);
291 let mut interior_clean = 0;
292 for _ in 0..200 {
293 let (x, y) = (4.0 + 8.0 * rng.next(), 4.0 + 8.0 * rng.next());
294 let z = terrain_surface(&mesh, n, f64::from(x), f64::from(y)) as f32;
295 let p = [x, y, z + 0.1];
296 let hit = closest_point(&mesh, &bvh, p, &ClosestOptions::default())
297 .unwrap()
298 .unwrap();
299 if hit.feature == Feature::Face {
300 assert_eq!(SignData::is_boundary_hit(&mesh, &hit), Some(false));
301 interior_clean += 1;
302 }
303 }
304 assert!(interior_clean > 150, "only {interior_clean} face hits");
305 }
306
307 #[test]
308 fn open_sheet_penetration_is_negative() {
309 let n = 16;
310 let mut mesh = terrain(n);
311 let mut rng = Lcg(3);
312 let mut points = Vec::new();
313 let mut below = Vec::new();
314 for i in 0..2000 {
315 let (x, y) = (4.0 + 8.0 * rng.next(), 4.0 + 8.0 * rng.next());
316 let h = 0.01 + 1.5 * rng.next();
317 let z = terrain_surface(&mesh, n, f64::from(x), f64::from(y)) as f32;
318 let under = i % 2 == 0;
319 points.push([x, y, if under { z - h } else { z + h }]);
320 below.push(under);
321 }
322 let (sign, out) = distances(&mut mesh, &points, f32::INFINITY);
323 let status = sign.status();
324 assert_eq!(status.mode, SignMode::Pseudonormal);
325 assert!(!status.closed);
326 assert!(status.consistent_orientation);
327 let tol = 1e-5 * diagonal(&mesh);
328 for ((&p, &value), &under) in points.iter().zip(&out).zip(&below) {
329 let brute = brute_distance(&mesh, p);
330 assert!(
331 (f64::from(value.abs()) - brute).abs() <= tol,
332 "magnitude at {p:?}"
333 );
334 if brute > tol {
335 assert_eq!(value < 0.0, under, "sign at {p:?}: value {value}");
336 }
337 }
338 }
339
340 fn split_with_sliver(
346 positions: &mut Vec<[f32; 3]>,
347 tris: &mut Vec<[u32; 3]>,
348 t: usize,
349 k: usize,
350 ) -> ([u32; 3], [f32; 3]) {
351 let tri = tris[t];
352 let (a, b, c) = (tri[k], tri[(k + 1) % 3], tri[(k + 2) % 3]);
353 let (pa, pb) = (positions[a as usize], positions[b as usize]);
354 let pm = std::array::from_fn(|i| (pa[i] + pb[i]) / 2.0);
355 positions.push(pm);
356 let m = (positions.len() - 1) as u32;
357 tris[t] = [a, m, c];
358 tris.push([m, b, c]);
359 ([a, b, m], pm)
360 }
361
362 #[test]
363 fn degenerate_faces_do_not_break_signs() {
364 let cases = [
365 (cube_buffers(), vec![(0, 2), (2, 0), (6, 1)]),
367 (tetrahedron_buffers(), vec![(0, 0), (3, 1)]),
370 ];
371 for ((mut positions, mut tris), splits) in cases {
372 let mut slivers = Vec::new();
373 let mut mids = Vec::new();
374 for (t, k) in splits {
375 let (s, m) = split_with_sliver(&mut positions, &mut tris, t, k);
376 slivers.push(s);
377 mids.push(m);
378 }
379 let count = slivers.len();
380 slivers.extend(tris);
382 let mut mesh = mesh(&positions, &slivers);
383 let bvh = build_bvh(&mesh);
384 let sign = SignData::build(&mut mesh, &bvh, SignMode::Auto).unwrap();
385 let status = sign.status();
386 assert_eq!(status.degenerate_triangles, count);
387 assert!(status.closed);
388 assert!(status.consistent_orientation);
389 assert_eq!(status.mode, SignMode::Pseudonormal);
390
391 let mut rng = Lcg(5);
392 let mut points = sample_points(&mesh, &mut rng, 1000, 0.2);
393 for m in &mids {
394 for _ in 0..300 {
395 let off = rng.point([-0.4; 3], [0.4; 3]);
396 points.push(std::array::from_fn(|i| m[i] + off[i]));
397 }
398 points.push(m.map(|c| c * 1.5));
402 points.push(m.map(|c| c * 0.5));
403 }
404 let mut out = vec![f32::NAN; points.len()];
405 signed_distances(&mesh, &bvh, &sign, &points, f32::INFINITY, &mut out).unwrap();
406 let checked = check_against_exact(&mesh, &points, &out);
407 assert!(
408 checked * 10 > points.len() * 9,
409 "only {checked} of {} signs checked",
410 points.len()
411 );
412
413 let on_sliver = points
415 .iter()
416 .filter(|&&p| {
417 let hit = closest_point(&mesh, &bvh, p, &ClosestOptions::default());
418 (hit.unwrap().unwrap().triangle as usize) < count
419 })
420 .count();
421 assert!(on_sliver > 0);
422 }
423 }
424
425 #[test]
426 fn pinch_vertex_signs_match_exact_away_from_pinch() {
427 let (mut p, mut t) = cube_buffers();
430 let (p2, t2) = cube_buffers();
431 p.extend(p2[1..].iter().map(|q| q.map(|c| c + 2.0)));
432 t.extend(
433 t2.iter()
434 .map(|tri| tri.map(|v| if v == 0 { 7 } else { v + 7 })),
435 );
436 let mut mesh = mesh(&p, &t);
437 let bvh = build_bvh(&mesh);
438 let sign = SignData::build(&mut mesh, &bvh, SignMode::Auto).unwrap();
439 let status = sign.status();
440 assert!(status.closed && status.consistent_orientation);
441 assert_eq!(status.non_manifold_vertices, 1);
442 assert_eq!(status.mode, SignMode::Pseudonormal);
443 let pinch = [1.0f32, 1.0, 1.0];
444 let mut rng = Lcg(41);
445 let points = sample_points(&mesh, &mut rng, 2000, 0.2);
446 let mut out = vec![f32::NAN; points.len()];
447 signed_distances(&mesh, &bvh, &sign, &points, f32::INFINITY, &mut out).unwrap();
448 let tol = 1e-5 * diagonal(&mesh);
449 let mut checked = 0;
450 for (&pt, &value) in points.iter().zip(&out) {
451 let to_pinch = ((pt[0] - pinch[0]).powi(2)
452 + (pt[1] - pinch[1]).powi(2)
453 + (pt[2] - pinch[2]).powi(2))
454 .sqrt() as f64;
455 if to_pinch < 0.2 || brute_distance(&mesh, pt) <= tol {
456 continue;
457 }
458 let inside = exact_winding(&mesh, pt) >= 0.5;
459 assert_eq!(value < 0.0, inside, "sign at {pt:?}: value {value}");
460 checked += 1;
461 }
462 assert!(checked > 1500, "only {checked} signs checked");
463 }
464
465 #[test]
466 fn t_junction_vertex_signs_are_consistent() {
467 let pos = vec![
472 [0.0, 0.0, 0.0],
473 [1.0, 0.0, 0.0],
474 [1.0, 1.0, 0.0],
475 [0.0, 1.0, 0.0],
476 [2.0, 0.0, 0.0],
477 [2.0, 1.0, 0.0],
478 [1.0, 0.5, 0.0],
479 [2.0, 0.5, 0.0],
480 ];
481 let tris = vec![
482 [0, 1, 2],
483 [0, 2, 3],
484 [1, 4, 7],
485 [1, 7, 6],
486 [6, 7, 5],
487 [6, 5, 2],
488 ];
489 let mut mesh = mesh(&pos, &tris);
490 let bvh = build_bvh(&mesh);
491 let sign = SignData::build(&mut mesh, &bvh, SignMode::Auto).unwrap();
492 let status = sign.status();
493 assert!(!status.closed);
494 assert!(status.consistent_orientation);
495 assert_eq!(status.mode, SignMode::Pseudonormal);
496 let mut rng = Lcg(43);
497 for _ in 0..500 {
498 let x = rng.next() * 2.0;
499 let y = rng.next();
500 if (x - 1.0).abs() < 0.1 {
501 continue;
502 }
503 let h = 0.05 + rng.next() * 0.2;
504 for under in [false, true] {
505 let p = [x, y, if under { -h } else { h }];
506 let mut out = [f32::NAN; 1];
507 signed_distances(&mesh, &bvh, &sign, &[p], f32::INFINITY, &mut out).unwrap();
508 assert_eq!(out[0] < 0.0, under, "sign at {p:?}: {}", out[0]);
509 }
510 }
511 }
512
513 #[test]
514 fn open_mesh_with_flipped_patch_reports_inconsistent() {
515 let base = terrain(8);
518 let n = 8u32;
519 let flip = (4 * n + 4) * 2;
520 let mut tris: Vec<[u32; 3]> = (0..base.triangle_slots() as u32)
521 .map(|t| base.triangle(t).unwrap())
522 .collect();
523 tris[flip as usize].swap(1, 2);
524 let positions: Vec<[f32; 3]> = base.positions().to_vec();
525 let mut mesh = mesh(&positions, &tris);
526 let bvh = build_bvh(&mesh);
527 let sign = SignData::build(&mut mesh, &bvh, SignMode::Auto).unwrap();
528 let status = sign.status();
529 assert!(!status.closed);
530 assert!(!status.consistent_orientation);
531 assert_eq!(status.mode, SignMode::Pseudonormal);
532 let tri = tris[flip as usize].map(|v| positions[v as usize]);
533 let centroid: [f32; 3] = std::array::from_fn(|k| (tri[0][k] + tri[1][k] + tri[2][k]) / 3.0);
534 for h in [0.05, 0.15] {
535 for under in [false, true] {
536 let p = [
537 centroid[0],
538 centroid[1],
539 centroid[2] + if under { -h } else { h },
540 ];
541 let mut out = [f32::NAN; 1];
542 signed_distances(&mesh, &bvh, &sign, &[p], f32::INFINITY, &mut out).unwrap();
543 assert_ne!(out[0] < 0.0, under, "flipped sign at {p:?}: {}", out[0]);
544 }
545 }
546 }
547
548 #[test]
549 fn pseudonormal_signs_on_open_cube() {
550 let (p, mut t) = cube_buffers();
553 t.drain(2..4);
554 let mut mesh = mesh(&p, &t);
555 let bvh = build_bvh(&mesh);
556 let sign = SignData::build(&mut mesh, &bvh, SignMode::Pseudonormal).unwrap();
557 let status = sign.status();
558 assert!(!status.closed);
559 assert!(status.consistent_orientation);
560 assert_eq!(status.mode, SignMode::Pseudonormal);
561 let points = [
562 ([0.0, 0.0, 0.0], true),
563 ([0.5, -0.3, 0.2], true),
564 ([3.0, 0.0, 0.0], false),
565 ([0.0, 3.0, 0.0], false),
566 ([0.0, 0.0, -3.0], false),
567 ];
568 let pts: Vec<[f32; 3]> = points.iter().map(|(p, _)| *p).collect();
569 let mut out = vec![f32::NAN; pts.len()];
570 signed_distances(&mesh, &bvh, &sign, &pts, f32::INFINITY, &mut out).unwrap();
571 for ((p, inside), value) in points.iter().zip(out) {
572 assert_eq!(value < 0.0, *inside, "at {p:?}: {value}");
573 }
574 }
575
576 #[test]
577 fn far_points_are_infinite() {
578 let mut mesh = cube();
579 let bvh = build_bvh(&mesh);
580 let sign = SignData::build(&mut mesh, &bvh, SignMode::Pseudonormal).unwrap();
581 let points = [
582 [3.0, 0.0, 0.0], [1.25, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, -0.75], [1.0, 0.5, 0.25], [1.5, 0.0, 0.0], ];
589 let mut out = [f32::NAN; 6];
590 signed_distances(&mesh, &bvh, &sign, &points, 0.5, &mut out).unwrap();
591 assert_eq!(out[0], f32::INFINITY);
592 assert_eq!(out[1], 0.25);
593 assert_eq!(out[2], f32::INFINITY);
594 assert_eq!(out[3], -0.25);
595 assert_eq!(out[4].to_bits(), 0.0f32.to_bits());
596 assert_eq!(out[5], 0.5);
597
598 signed_distances(&mesh, &bvh, &sign, &points, f32::INFINITY, &mut out).unwrap();
600 assert_eq!(out[0], 2.0);
601 assert_eq!(out[2], -1.0);
602
603 let mut out = [7.0f32; 6];
605 let mut bad = points;
606 bad[3][1] = f32::NAN;
607 let invalid = |r: Result<()>| matches!(r, Err(FieldsError::InvalidInput(_)));
608 assert!(invalid(signed_distances(
609 &mesh, &bvh, &sign, &bad, 0.5, &mut out
610 )));
611 bad[3][1] = f32::INFINITY;
612 assert!(invalid(signed_distances(
613 &mesh, &bvh, &sign, &bad, 0.5, &mut out
614 )));
615 assert!(invalid(signed_distances(
616 &mesh,
617 &bvh,
618 &sign,
619 &points,
620 f32::NAN,
621 &mut out
622 )));
623 assert!(invalid(signed_distances(
624 &mesh, &bvh, &sign, &points, -1.0, &mut out
625 )));
626 assert!(invalid(signed_distances(
627 &mesh,
628 &bvh,
629 &sign,
630 &points[..5],
631 0.5,
632 &mut out
633 )));
634 assert_eq!(out, [7.0; 6]);
635
636 let mut empty = Mesh::new();
638 let ebvh = build_bvh(&empty);
639 let esign = SignData::build(&mut empty, &ebvh, SignMode::Auto).unwrap();
640 let mut out = [0.0f32; 2];
641 signed_distances(&empty, &ebvh, &esign, &points[..2], f32::INFINITY, &mut out).unwrap();
642 assert_eq!(out, [f32::INFINITY; 2]);
643 }
644
645 const POOLS: [usize; 3] = [1, 2, 8];
647
648 fn bits(values: &[f32]) -> Vec<u32> {
649 values.iter().map(|x| x.to_bits()).collect()
650 }
651
652 #[test]
653 fn signed_distances_bit_identical() {
654 let mut mesh = terrain(317);
656 assert!(mesh.triangle_count() >= 200_000);
657 let bvh = build_bvh(&mesh);
658 let points = sample_points(&mesh, &mut Lcg(31), 30_000, 2.0);
661 for (mode, count) in [(SignMode::Pseudonormal, 30_000), (SignMode::Winding, 8_000)] {
662 let sign = SignData::build(&mut mesh, &bvh, mode).unwrap();
663 let points = &points[..count];
664 for max_distance in [f32::INFINITY, 0.5] {
665 let run = || {
666 let mut out = vec![f32::NAN; points.len()];
667 signed_distances(&mesh, &bvh, &sign, points, max_distance, &mut out).unwrap();
668 bits(&out)
669 };
670 par::set_force_sequential(true);
671 let reference = run();
672 par::set_force_sequential(false);
673 if !par::enabled() {
674 continue;
676 }
677 for k in POOLS {
678 let got = par::with_threads(k, run);
679 assert!(got == reference, "{mode:?}, {max_distance}: {k} threads");
680 }
681 let before = par::parallel_entries();
682 assert!(run() == reference, "{mode:?}, {max_distance}: global pool");
683 assert!(par::parallel_entries() > before, "ran sequentially");
684 }
685 }
686 }
687
688 #[test]
689 fn small_inputs_stay_sequential() {
690 let mut mesh = icosphere();
691 let before = par::parallel_entries();
692 let points = sample_points(&mesh, &mut Lcg(2), 400, 0.2);
693 for mode in [SignMode::Pseudonormal, SignMode::Winding] {
694 let bvh = build_bvh(&mesh);
695 let sign = SignData::build(&mut mesh, &bvh, mode).unwrap();
696 let mut out = vec![f32::NAN; points.len()];
697 signed_distances(&mesh, &bvh, &sign, &points, f32::INFINITY, &mut out).unwrap();
698 }
699 assert_eq!(par::parallel_entries(), before);
700 }
701
702 #[test]
703 fn stale_or_foreign_inputs_are_rejected() {
704 let mut mesh = cube();
705 let mut bvh = build_bvh(&mesh);
706 let sign = SignData::build(&mut mesh, &bvh, SignMode::Auto).unwrap();
707 let points = [[2.0, 0.0, 0.0]];
708 let mut out = [7.0f32];
709
710 let other = cube();
711 let other_bvh = build_bvh(&other);
712 assert_eq!(
713 signed_distances(&mesh, &other_bvh, &sign, &points, 1.0, &mut out),
714 Err(FieldsError::Query(QueryError::MeshMismatch))
715 );
716 assert_eq!(
717 signed_distances(&other, &other_bvh, &sign, &points, 1.0, &mut out),
718 Err(FieldsError::InvalidInput(
719 "sign data is not current for this mesh and BVH"
720 ))
721 );
722
723 let mut tx = mesh.begin_edit();
724 tx.set_positions(&[0], &[[-1.0, -1.0, -1.5]]).unwrap();
725 let patch = tx.commit().unwrap();
726 assert_eq!(
727 signed_distances(&mesh, &bvh, &sign, &points, 1.0, &mut out),
728 Err(FieldsError::Query(QueryError::StaleBvh))
729 );
730 assert_eq!(
731 SignData::build(&mut mesh, &bvh, SignMode::Auto).err(),
732 Some(FieldsError::Query(QueryError::StaleBvh))
733 );
734 bvh.update(&mut mesh, patch.changes()).unwrap();
735 assert_eq!(
736 signed_distances(&mesh, &bvh, &sign, &points, 1.0, &mut out),
737 Err(FieldsError::InvalidInput(
738 "sign data is not current for this mesh and BVH"
739 ))
740 );
741 assert_eq!(out, [7.0]);
742 }
743}