1use std::ops::Range;
30
31use fast_mesh_bvh::Bvh;
32use fast_mesh_query::{ClosestHit, Feature, QueryError};
33use fast_mesh_store::{
34 Adjacency, AdjacencyBuild, ChangeSet, EdgeKind, Incidence, IncidenceBuild, Mesh, TriangleId,
35 VertexId, par,
36};
37
38use crate::geom::{
39 add, closest_on_triangle, cross, dot, narrow, norm, scale, sub, triangle_normal, unit, widen,
40};
41use crate::winding::{Moments, MomentsBuild};
42use crate::{FieldsError, Result};
43
44const SLIVER_HOPS: u32 = 4;
47
48const PAR_MIN_SIGN_ELEMS: usize = 8 * 1024;
56
57const VERTEX_NORMAL_WEIGHT: usize = 12;
64
65const EDGE_NORMAL_WEIGHT: usize = 2;
68
69const ADJACENCY_WEIGHT: usize = 3;
72
73const SIGN_CHUNK: usize = 2048;
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
78pub enum SignMode {
79 Pseudonormal,
84 Winding,
89 #[default]
92 Auto,
93}
94
95#[derive(Debug, Clone, Copy, PartialEq)]
98pub struct SignStatus {
99 pub mode: SignMode,
101 pub closed: bool,
105 pub consistent_orientation: bool,
108 pub non_manifold_vertices: usize,
111 pub degenerate_triangles: usize,
113 pub signed_volume: f64,
119}
120
121#[derive(Debug, Clone)]
134pub struct SignData {
135 mesh_uid: u64,
136 epoch: u32,
137 revision: u64,
138 bvh_generation: u64,
139 status: SignStatus,
140 vertex_normals: Vec<[f32; 3]>,
141 edge_normals: Vec<[f32; 3]>,
143 degenerate: Vec<TriangleId>,
145 offsets: Vec<u32>,
148 candidates: Vec<TriangleId>,
149 pub(crate) moments: Moments,
151}
152
153fn try_push<T>(v: &mut Vec<T>, value: T) -> Result<()> {
155 v.try_reserve(1).map_err(|_| FieldsError::OutOfMemory)?;
156 v.push(value);
157 Ok(())
158}
159
160pub(crate) fn collect_span<T: Send>(
166 span: Range<usize>,
167 f: impl Fn(usize) -> T + Sync,
168) -> Result<Vec<T>> {
169 let mut v = Vec::new();
170 let total = span.len();
171 extend_span(&mut v, span, total, f)?;
172 Ok(v)
173}
174
175fn extend_span<T: Send>(
179 v: &mut Vec<T>,
180 span: Range<usize>,
181 total: usize,
182 f: impl Fn(usize) -> T + Sync,
183) -> Result<()> {
184 let oom = || FieldsError::OutOfMemory;
185 let (start, len) = (span.start, span.len());
186 let room = total.max(v.len() + len) - v.len();
187 v.try_reserve_exact(room).map_err(|_| oom())?;
188 if len < PAR_MIN_SIGN_ELEMS || !par::enabled() || par::force_sequential() {
189 v.extend(span.map(f));
190 return Ok(());
191 }
192 let parts = par::map_range(len, SIGN_CHUNK, PAR_MIN_SIGN_ELEMS, oom, |r| {
193 let mut part = Vec::new();
194 part.try_reserve_exact(r.len()).map_err(|_| oom())?;
195 part.extend((r.start + start..r.end + start).map(&f));
196 Ok(part)
197 })?;
198 parts.into_iter().for_each(|p| v.extend(p));
199 Ok(())
200}
201
202fn reduce_span<R: Send>(
206 span: Range<usize>,
207 f: impl Fn(Range<usize>) -> R + Sync,
208) -> Result<Vec<R>> {
209 let oom = || FieldsError::OutOfMemory;
210 let start = span.start;
211 par::map_range(span.len(), SIGN_CHUNK, PAR_MIN_SIGN_ELEMS, oom, |r| {
212 Ok(f(r.start + start..r.end + start))
213 })
214}
215
216fn corners(mesh: &Mesh, tri: [VertexId; 3]) -> [[f32; 3]; 3] {
217 tri.map(|v| mesh.positions()[v as usize])
218}
219
220fn topology_span(
224 mesh: &Mesh,
225 adj: &Adjacency,
226 face: &[[f64; 3]],
227 span: Range<usize>,
228) -> Result<(bool, bool, usize)> {
229 let tris = mesh.triangles();
230 let parts = reduce_span(span, |range| {
231 let (mut closed, mut consistent, mut degenerate) = (true, true, 0);
232 for t in range {
233 let tri = tris[t];
234 if !mesh.is_triangle_alive(t as TriangleId) {
235 continue;
236 }
237 if face[t] == [0.0; 3] {
238 degenerate += 1;
239 }
240 for i in 0..3 {
241 let (u, w) = (tri[(i + 1) % 3], tri[(i + 2) % 3]);
242 if u == w {
243 continue;
244 }
245 let c = (3 * t + i) as u32;
246 match adj.edge_kind(c) {
247 EdgeKind::Boundary | EdgeKind::NonManifold => closed = false,
248 EdgeKind::Interior => {
249 if let Some(o) = adj.opposite(c) {
250 let (s, j) = ((o / 3) as usize, (o % 3) as usize);
251 let other = tris[s];
252 if (other[(j + 1) % 3], other[(j + 2) % 3]) != (w, u) {
253 consistent = false;
254 }
255 }
256 }
257 }
258 }
259 }
260 (closed, consistent, degenerate)
261 })?;
262 Ok(parts
263 .into_iter()
264 .fold((true, true, 0), |(a, b, n), (c, d, m)| {
265 (a && c, b && d, n + m)
266 }))
267}
268
269fn tri_volume(mesh: &Mesh, t: usize) -> f64 {
272 let Some(tri) = mesh.triangle(t as TriangleId) else {
273 return 0.0;
274 };
275 let p = corners(mesh, tri).map(widen);
276 dot(p[0], cross(p[1], p[2])) / 6.0
277}
278
279fn non_manifold_span(mesh: &Mesh, adj: &Adjacency, span: Range<usize>) -> Result<usize> {
281 Ok(reduce_span(span, |range| {
282 range
283 .filter(|&v| {
284 let v = v as VertexId;
285 mesh.is_vertex_alive(v) && adj.is_non_manifold_vertex(v)
286 })
287 .count()
288 })?
289 .into_iter()
290 .sum())
291}
292
293fn face_normal(mesh: &Mesh, t: usize) -> [f64; 3] {
295 mesh.triangle(t as TriangleId)
296 .map_or([0.0; 3], |tri| unit(triangle_normal(corners(mesh, tri))))
297}
298
299fn vertex_normal(mesh: &Mesh, inc: &Incidence, face: &[[f64; 3]], v: usize) -> [f32; 3] {
302 let mut sum = [0.0; 3];
303 for &t in inc.triangles(v as VertexId) {
304 let n = face[t as usize];
305 if n == [0.0; 3] {
306 continue;
307 }
308 let tri = mesh.triangles()[t as usize];
309 let Some(i) = tri.iter().position(|&x| x as usize == v) else {
310 continue;
311 };
312 let p = corners(mesh, tri).map(widen);
313 let (e1, e2) = (sub(p[(i + 1) % 3], p[i]), sub(p[(i + 2) % 3], p[i]));
314 let angle = norm(cross(e1, e2)).atan2(dot(e1, e2));
315 sum = add(sum, scale(n, angle));
316 }
317 narrow(unit(sum))
318}
319
320fn longest_edge_corner(mesh: &Mesh, s: usize) -> u32 {
322 let p = corners(mesh, mesh.triangles()[s]).map(widen);
323 let len2 = |j: usize| {
324 let e = sub(p[(j + 2) % 3], p[(j + 1) % 3]);
325 dot(e, e)
326 };
327 let mut best = 0;
328 for j in 1..3 {
329 if len2(j) > len2(best) {
330 best = j;
331 }
332 }
333 best as u32
334}
335
336fn across(mesh: &Mesh, adj: &Adjacency, face: &[[f64; 3]], c: u32, hops: u32) -> [f64; 3] {
343 let Some(o) = adj.opposite(c) else {
344 return [0.0; 3];
345 };
346 let s = (o / 3) as usize;
347 if face[s] != [0.0; 3] {
348 return face[s];
349 }
350 if hops == 0 {
351 return [0.0; 3];
352 }
353 let long = longest_edge_corner(mesh, s);
354 let base = 3 * s as u32;
355 if o % 3 != long {
356 across(mesh, adj, face, base + long, hops - 1)
357 } else {
358 let sum = (0..3)
359 .filter(|&j| j != long)
360 .map(|j| across(mesh, adj, face, base + j, hops - 1))
361 .fold([0.0; 3], add);
362 unit(sum)
363 }
364}
365
366fn edge_normal(
369 mesh: &Mesh,
370 adj: &Adjacency,
371 inc: &Incidence,
372 face: &[[f64; 3]],
373 c: usize,
374) -> [f32; 3] {
375 let tris = mesh.triangles();
376 let (t, i) = (c / 3, c % 3);
377 if !mesh.is_triangle_alive(t as TriangleId) {
378 return [0.0f32; 3];
379 }
380 let tri = tris[t];
381 let mut sum = face[t];
382 match adj.edge_kind(c as u32) {
383 EdgeKind::Interior => sum = add(sum, across(mesh, adj, face, c as u32, SLIVER_HOPS)),
384 EdgeKind::Boundary => {}
385 EdgeKind::NonManifold => {
386 let (u, w) = (tri[(i + 1) % 3], tri[(i + 2) % 3]);
387 for &s in inc.triangles(u) {
388 if s as usize != t && tris[s as usize].contains(&w) {
389 sum = add(sum, face[s as usize]);
390 }
391 }
392 }
393 }
394 narrow(sum)
395}
396
397fn refit_vertex_normal(mesh: &Mesh, inc: &Incidence, v: usize) -> [f32; 3] {
401 let mut sum = [0.0; 3];
402 for &t in inc.triangles(v as VertexId) {
403 let n = face_normal(mesh, t as usize);
404 if n == [0.0; 3] {
405 continue;
406 }
407 let tri = mesh.triangles()[t as usize];
408 let Some(i) = tri.iter().position(|&x| x as usize == v) else {
409 continue;
410 };
411 let p = corners(mesh, tri).map(widen);
412 let (e1, e2) = (sub(p[(i + 1) % 3], p[i]), sub(p[(i + 2) % 3], p[i]));
413 let angle = norm(cross(e1, e2)).atan2(dot(e1, e2));
414 sum = add(sum, scale(n, angle));
415 }
416 narrow(unit(sum))
417}
418
419fn refit_across(mesh: &Mesh, adj: &Adjacency, c: u32, hops: u32) -> [f64; 3] {
421 let Some(o) = adj.opposite(c) else {
422 return [0.0; 3];
423 };
424 let s = (o / 3) as usize;
425 let f = face_normal(mesh, s);
426 if f != [0.0; 3] {
427 return f;
428 }
429 if hops == 0 {
430 return [0.0; 3];
431 }
432 let long = longest_edge_corner(mesh, s);
433 let base = 3 * s as u32;
434 if o % 3 != long {
435 refit_across(mesh, adj, base + long, hops - 1)
436 } else {
437 let sum = (0..3)
438 .filter(|&j| j != long)
439 .map(|j| refit_across(mesh, adj, base + j, hops - 1))
440 .fold([0.0; 3], add);
441 unit(sum)
442 }
443}
444
445fn refit_edge_normal(mesh: &Mesh, adj: &Adjacency, inc: &Incidence, c: usize) -> [f32; 3] {
447 let tris = mesh.triangles();
448 let (t, i) = (c / 3, c % 3);
449 if !mesh.is_triangle_alive(t as TriangleId) {
450 return [0.0f32; 3];
451 }
452 let tri = tris[t];
453 let mut sum = face_normal(mesh, t);
454 match adj.edge_kind(c as u32) {
455 EdgeKind::Interior => sum = add(sum, refit_across(mesh, adj, c as u32, SLIVER_HOPS)),
456 EdgeKind::Boundary => {}
457 EdgeKind::NonManifold => {
458 let (u, w) = (tri[(i + 1) % 3], tri[(i + 2) % 3]);
459 for &s in inc.triangles(u) {
460 if s as usize != t && tris[s as usize].contains(&w) {
461 sum = add(sum, face_normal(mesh, s as usize));
462 }
463 }
464 }
465 }
466 narrow(sum)
467}
468
469#[derive(Debug, Clone, Copy, PartialEq, Eq)]
471enum Stage {
472 Adjacency,
474 Incidence,
476 Faces(usize),
478 Topology(usize),
480 NonManifold(usize),
482 VertexNormals(usize),
484 EdgeNormals(usize),
486 Degenerate(usize),
488 Moments,
490 Done,
492}
493
494#[derive(Debug)]
506pub struct SignBuild {
507 data: SignData,
508 slots: (usize, usize),
511 requested: SignMode,
512 stage: Stage,
513 adjacency: AdjacencyBuild,
514 incidence: IncidenceBuild,
515 face: Vec<[f64; 3]>,
516 moments: Option<MomentsBuild>,
517}
518
519impl SignBuild {
520 pub fn new(mesh: &Mesh, bvh: &Bvh, mode: SignMode) -> Result<SignBuild> {
527 if bvh.mesh_uid() != mesh.uid() {
528 return Err(QueryError::MeshMismatch.into());
529 }
530 if !bvh.is_current(mesh) {
531 return Err(QueryError::StaleBvh.into());
532 }
533 Ok(SignBuild {
534 data: SignData {
535 mesh_uid: mesh.uid(),
536 epoch: mesh.epoch(),
537 revision: mesh.revision(),
538 bvh_generation: bvh.generation(),
539 status: SignStatus {
540 mode: SignMode::Pseudonormal,
541 closed: true,
542 consistent_orientation: true,
543 non_manifold_vertices: 0,
544 degenerate_triangles: 0,
545 signed_volume: 0.0,
546 },
547 vertex_normals: Vec::new(),
548 edge_normals: Vec::new(),
549 degenerate: Vec::new(),
550 offsets: Vec::new(),
551 candidates: Vec::new(),
552 moments: Moments::default(),
553 },
554 slots: (mesh.vertex_slots(), mesh.triangle_slots()),
555 requested: mode,
556 stage: Stage::Adjacency,
557 adjacency: AdjacencyBuild::new(mesh),
558 incidence: IncidenceBuild::new(mesh),
559 face: Vec::new(),
560 moments: None,
561 })
562 }
563
564 pub fn is_for(&self, mesh: &Mesh, bvh: &Bvh) -> bool {
567 self.data.is_current(mesh, bvh)
568 && self.slots == (mesh.vertex_slots(), mesh.triangle_slots())
569 }
570
571 pub fn is_done(&self) -> bool {
573 self.stage == Stage::Done
574 }
575
576 pub fn step(&mut self, mesh: &mut Mesh, bvh: &Bvh, budget: usize) -> Result<bool> {
590 if !self.is_for(mesh, bvh) {
591 return Err(FieldsError::InvalidInput(
592 "the sign data build started from another mesh or BVH state",
593 ));
594 }
595 let mut left = budget.max(1);
596 while left > 0 {
597 match self.stage {
598 Stage::Adjacency => {
599 let budget = (left / ADJACENCY_WEIGHT).max(1);
600 if mesh.step_adjacency(&mut self.adjacency, budget)? {
601 self.stage = Stage::Incidence;
602 }
603 return Ok(false);
605 }
606 Stage::Incidence => {
607 if mesh.step_incidence(&mut self.incidence, left)? {
608 self.stage = Stage::Faces(0);
609 }
610 return Ok(false);
611 }
612 _ => {}
613 }
614 let mesh: &Mesh = mesh;
615 let missing = FieldsError::InvalidInput("mesh adjacency or incidence unavailable");
616 let adj = mesh.adjacency().ok_or(missing.clone())?;
617 let inc = mesh.incidence().ok_or(missing)?;
618 let span =
619 |from: usize, len: usize, left: usize| from..len.min(from.saturating_add(left));
620 match self.stage {
621 Stage::Adjacency | Stage::Incidence => unreachable!("handled above"),
622 Stage::Faces(from) => {
623 let r = span(from, mesh.triangle_slots(), left);
624 let (to, n) = (r.end, r.len());
625 let total = mesh.triangle_slots();
626 extend_span(&mut self.face, r, total, |t| face_normal(mesh, t))?;
627 left -= n.min(left);
628 self.stage = if to == mesh.triangle_slots() {
629 Stage::Topology(0)
630 } else {
631 Stage::Faces(to)
632 };
633 }
634 Stage::Topology(from) => {
635 let r = span(from, mesh.triangles().len(), left);
636 let (to, n) = (r.end, r.len());
637 let (closed, consistent, degenerate) =
638 topology_span(mesh, adj, &self.face, r.clone())?;
639 let st = &mut self.data.status;
640 st.closed &= closed;
641 st.consistent_orientation &= consistent;
642 st.degenerate_triangles += degenerate;
643 for t in r {
645 st.signed_volume += tri_volume(mesh, t);
646 }
647 left -= n.min(left);
648 self.stage = if to == mesh.triangles().len() {
649 Stage::NonManifold(0)
650 } else {
651 Stage::Topology(to)
652 };
653 }
654 Stage::NonManifold(from) => {
655 let r = span(from, mesh.vertex_slots(), left);
656 let (to, n) = (r.end, r.len());
657 self.data.status.non_manifold_vertices += non_manifold_span(mesh, adj, r)?;
658 left -= n.min(left);
659 if to < mesh.vertex_slots() {
660 self.stage = Stage::NonManifold(to);
661 continue;
662 }
663 let st = &mut self.data.status;
664 st.mode = match self.requested {
665 SignMode::Pseudonormal => SignMode::Pseudonormal,
666 SignMode::Auto if !(st.closed && !st.consistent_orientation) => {
667 SignMode::Pseudonormal
668 }
669 SignMode::Auto | SignMode::Winding => SignMode::Winding,
670 };
671 if st.mode == SignMode::Winding {
672 self.moments = Some(MomentsBuild::new(bvh)?);
673 self.stage = Stage::Moments;
674 } else {
675 self.stage = Stage::VertexNormals(0);
676 }
677 }
678 Stage::Moments => {
679 let moments = self.moments.as_mut().ok_or(FieldsError::OutOfMemory)?;
680 let before = moments.progress();
681 let done = moments.step(mesh, bvh, left)?;
682 left -= moments.progress().saturating_sub(before).min(left);
683 if done {
684 let moments = self.moments.take().ok_or(FieldsError::OutOfMemory)?;
685 self.data.moments = moments.finish();
686 self.face = Vec::new();
687 self.stage = Stage::Done;
688 }
689 }
690 Stage::VertexNormals(from) => {
691 let w = VERTEX_NORMAL_WEIGHT;
692 let r = span(from, mesh.vertex_slots(), (left / w).max(1));
693 let (to, n) = (r.end, r.len());
694 let face = &self.face;
695 let total = mesh.vertex_slots();
696 extend_span(&mut self.data.vertex_normals, r, total, |v| {
697 vertex_normal(mesh, inc, face, v)
698 })?;
699 left -= n.saturating_mul(w).min(left);
700 self.stage = if to == mesh.vertex_slots() {
701 Stage::EdgeNormals(0)
702 } else {
703 Stage::VertexNormals(to)
704 };
705 }
706 Stage::EdgeNormals(from) => {
707 let w = EDGE_NORMAL_WEIGHT;
708 let r = span(from, 3 * mesh.triangles().len(), (left / w).max(1));
709 let (to, n) = (r.end, r.len());
710 let face = &self.face;
711 let total = 3 * mesh.triangles().len();
712 extend_span(&mut self.data.edge_normals, r, total, |c| {
713 edge_normal(mesh, adj, inc, face, c)
714 })?;
715 left -= n.saturating_mul(w).min(left);
716 if to < 3 * mesh.triangles().len() {
717 self.stage = Stage::EdgeNormals(to);
718 continue;
719 }
720 try_push(&mut self.data.offsets, 0)?;
721 self.stage = Stage::Degenerate(0);
722 }
723 Stage::Degenerate(from) => {
724 let r = span(from, mesh.triangle_slots(), left);
725 let (to, n) = (r.end, r.len());
726 let SignData {
727 degenerate,
728 offsets,
729 candidates,
730 ..
731 } = &mut self.data;
732 let face = &self.face;
733 for t in r {
734 let Some(tri) = mesh.triangle(t as TriangleId) else {
735 continue;
736 };
737 if face[t] != [0.0; 3] {
738 continue;
739 }
740 let start = candidates.len();
741 for v in tri {
742 for &s in inc.triangles(v) {
743 if face[s as usize] != [0.0; 3] {
744 try_push(candidates, s)?;
745 }
746 }
747 }
748 candidates[start..].sort_unstable();
749 let mut kept = start;
750 for i in start..candidates.len() {
751 if kept == start || candidates[i] != candidates[kept - 1] {
752 candidates[kept] = candidates[i];
753 kept += 1;
754 }
755 }
756 candidates.truncate(kept);
757 try_push(degenerate, t as TriangleId)?;
758 try_push(offsets, candidates.len() as u32)?;
759 }
760 left -= n.min(left);
761 if to == mesh.triangle_slots() {
762 self.face = Vec::new();
763 self.stage = Stage::Done;
764 } else {
765 self.stage = Stage::Degenerate(to);
766 }
767 }
768 Stage::Done => return Ok(true),
769 }
770 if self.stage == Stage::Done {
771 return Ok(true);
772 }
773 }
774 Ok(self.stage == Stage::Done)
775 }
776
777 pub fn finish(self) -> Option<SignData> {
780 (self.stage == Stage::Done).then_some(self.data)
781 }
782}
783
784impl SignData {
785 pub fn build(mesh: &mut Mesh, bvh: &Bvh, mode: SignMode) -> Result<SignData> {
796 let mut build = SignBuild::new(mesh, bvh, mode)?;
797 while !build.step(mesh, bvh, usize::MAX)? {}
798 build.finish().ok_or(FieldsError::OutOfMemory)
799 }
800
801 pub fn try_refit(&mut self, mesh: &Mesh, bvh: &Bvh, changes: &ChangeSet) -> Result<bool> {
814 if self.status.mode != SignMode::Pseudonormal {
815 return Ok(false);
816 }
817 if mesh.uid() != self.mesh_uid || mesh.epoch() != self.epoch {
818 return Ok(false);
819 }
820 if bvh.mesh_uid() != self.mesh_uid || !bvh.is_current(mesh) {
821 return Ok(false);
822 }
823 if mesh.vertex_slots() != self.vertex_normals.len()
824 || 3 * mesh.triangle_slots() != self.edge_normals.len()
825 {
826 return Ok(false);
827 }
828 if changes.topology_changed()
829 || !changes.vertices_added.is_empty()
830 || !changes.vertices_removed.is_empty()
831 {
832 return Ok(false);
833 }
834 let (Some(adj), Some(inc)) = (mesh.adjacency(), mesh.incidence()) else {
835 return Ok(false);
836 };
837 let mut dirty: Vec<TriangleId> = Vec::new();
839 for &v in &changes.vertices_moved {
840 let list = inc.triangles(v);
841 dirty
842 .try_reserve(list.len())
843 .map_err(|_| FieldsError::OutOfMemory)?;
844 dirty.extend_from_slice(list);
845 }
846 dirty.sort_unstable();
847 dirty.dedup();
848 for &t in &dirty {
851 let zero = face_normal(mesh, t as usize) == [0.0; 3];
852 if self.degenerate.binary_search(&t).is_ok() != zero {
853 return Ok(false);
854 }
855 }
856 let tris = mesh.triangles();
859 let mut verts: Vec<VertexId> = Vec::new();
860 verts
861 .try_reserve(dirty.len().saturating_mul(3))
862 .map_err(|_| FieldsError::OutOfMemory)?;
863 for &t in &dirty {
864 verts.extend_from_slice(&tris[t as usize]);
865 }
866 verts.sort_unstable();
867 verts.dedup();
868 for &v in &verts {
869 self.vertex_normals[v as usize] = refit_vertex_normal(mesh, inc, v as usize);
870 }
871 let mut corners: Vec<u32> = Vec::new();
874 corners
875 .try_reserve(dirty.len().saturating_mul(6))
876 .map_err(|_| FieldsError::OutOfMemory)?;
877 for &t in &dirty {
878 for i in 0..3u32 {
879 let c = 3 * t + i;
880 corners.push(c);
881 match adj.edge_kind(c) {
882 EdgeKind::Interior => {
883 if let Some(o) = adj.opposite(c) {
884 corners.push(o);
885 }
886 }
887 EdgeKind::Boundary => {}
888 EdgeKind::NonManifold => {
889 let tri = tris[t as usize];
890 let (u, w) = (tri[((i + 1) % 3) as usize], tri[((i + 2) % 3) as usize]);
891 for &s in inc.triangles(u) {
892 if tris[s as usize].contains(&w) {
893 let stri = tris[s as usize];
894 for j in 0..3u32 {
895 let (a, b) = (
896 stri[((j + 1) % 3) as usize],
897 stri[((j + 2) % 3) as usize],
898 );
899 if (a == u && b == w) || (a == w && b == u) {
900 corners.push(3 * s + j);
901 }
902 }
903 }
904 }
905 }
906 }
907 }
908 }
909 corners.sort_unstable();
910 corners.dedup();
911 for &c in &corners {
912 self.edge_normals[c as usize] = refit_edge_normal(mesh, adj, inc, c as usize);
913 }
914 let mut volume = 0.0;
916 for t in 0..mesh.triangle_slots() {
917 volume += tri_volume(mesh, t);
918 }
919 self.status.signed_volume = volume;
920 self.revision = mesh.revision();
921 self.bvh_generation = bvh.generation();
922 Ok(true)
923 }
924
925 pub fn winding_number(&self, mesh: &Mesh, bvh: &Bvh, p: [f32; 3]) -> f64 {
943 if !self.is_current(mesh, bvh) {
944 return f64::NAN;
945 }
946 self.moments.winding(mesh, bvh, p).0
947 }
948
949 pub fn is_boundary_hit(mesh: &Mesh, hit: &ClosestHit) -> Option<bool> {
958 let adj = mesh.adjacency()?;
959 let t = hit.triangle;
960 if !mesh.is_triangle_alive(t) {
961 return None;
962 }
963 let tri = mesh.triangles()[t as usize];
964 match hit.feature {
965 Feature::Face => Some(false),
966 Feature::Edge(k) => {
967 let c = 3 * t + (u32::from(k) + 2) % 3;
968 Some(adj.edge_kind(c) == EdgeKind::Boundary)
969 }
970 Feature::Vertex(k) => {
971 let inc = mesh.incidence()?;
972 let v = tri[k as usize];
973 for &s in inc.triangles(v) {
974 let stri = mesh.triangles()[s as usize];
975 let Some(pos) = stri.iter().position(|&x| x == v) else {
976 continue;
977 };
978 for j in [(pos + 1) % 3, (pos + 2) % 3] {
979 if adj.edge_kind(3 * s + j as u32) == EdgeKind::Boundary {
980 return Some(true);
981 }
982 }
983 }
984 Some(false)
985 }
986 }
987 }
988
989 pub(crate) fn is_inside(&self, mesh: &Mesh, bvh: &Bvh, p: [f32; 3], hit: &ClosestHit) -> bool {
994 match self.status.mode {
995 SignMode::Winding => self.moments.sign_winding(mesh, bvh, p).0 >= 0.5,
996 _ => self.side(mesh, p, hit) < 0.0,
997 }
998 }
999
1000 pub fn status(&self) -> SignStatus {
1002 self.status
1003 }
1004
1005 pub fn mode(&self) -> SignMode {
1007 self.status.mode
1008 }
1009
1010 pub fn is_current(&self, mesh: &Mesh, bvh: &Bvh) -> bool {
1013 mesh.uid() == self.mesh_uid
1014 && bvh.mesh_uid() == self.mesh_uid
1015 && mesh.epoch() == self.epoch
1016 && mesh.revision() == self.revision
1017 && bvh.generation() == self.bvh_generation
1018 }
1019
1020 pub(crate) fn side(&self, mesh: &Mesh, p: [f32; 3], hit: &ClosestHit) -> f64 {
1024 let q = widen(p);
1025 let t = hit.triangle;
1026 let tri = mesh.triangles()[t as usize];
1027 let n = triangle_normal(corners(mesh, tri));
1028 if n != [0.0; 3] {
1029 return self.side_at(q, widen(hit.point), t, tri, n, hit.feature);
1030 }
1031 let Ok(i) = self.degenerate.binary_search(&t) else {
1033 return 0.0;
1034 };
1035 let range = self.offsets[i] as usize..self.offsets[i + 1] as usize;
1036 let mut best: Option<(f64, TriangleId, [f64; 3], Feature)> = None;
1037 for &s in &self.candidates[range] {
1038 let p = corners(mesh, mesh.triangles()[s as usize]).map(widen);
1039 let (d2, x, f) = closest_on_triangle(q, p);
1040 if best.is_none_or(|b| d2 < b.0) {
1042 best = Some((d2, s, x, f));
1043 }
1044 }
1045 let Some((_, s, x, f)) = best else {
1046 return 0.0;
1047 };
1048 let stri = mesh.triangles()[s as usize];
1049 let n = triangle_normal(corners(mesh, stri));
1050 self.side_at(q, x, s, stri, n, f)
1051 }
1052
1053 fn side_at(
1056 &self,
1057 q: [f64; 3],
1058 x: [f64; 3],
1059 t: TriangleId,
1060 tri: [VertexId; 3],
1061 face: [f64; 3],
1062 feature: Feature,
1063 ) -> f64 {
1064 let n = match feature {
1065 Feature::Face => face,
1066 Feature::Edge(k) => {
1067 widen(self.edge_normals[3 * t as usize + (k as usize + 2) % 3])
1069 }
1070 Feature::Vertex(k) => widen(self.vertex_normals[tri[k as usize] as usize]),
1071 };
1072 dot(sub(q, x), n)
1073 }
1074}
1075
1076#[cfg(test)]
1077mod tests {
1078 use fast_mesh_store::par;
1079
1080 use super::*;
1081 use crate::test_util::*;
1082
1083 fn status_of(mut mesh: Mesh, mode: SignMode) -> Result<SignStatus> {
1084 let bvh = build_bvh(&mesh);
1085 SignData::build(&mut mesh, &bvh, mode).map(|s| s.status())
1086 }
1087
1088 fn flags_of(mesh: Mesh, mode: SignMode) -> Result<SignStatus> {
1091 status_of(mesh, mode).map(|mut s| {
1092 s.signed_volume = 0.0;
1093 s
1094 })
1095 }
1096
1097 #[test]
1098 fn refit_matches_full_rebuild() {
1099 for (name, mut mesh, moved, positions) in [
1102 (
1103 "terrain",
1104 terrain(16),
1105 vec![10, 20, 30],
1106 vec![[0.5, 0.5, 1.0], [1.5, 2.5, 0.5], [3.0, 3.0, 2.0]],
1107 ),
1108 ("cube", cube(), vec![0], vec![[-1.2, -1.0, -0.8]]),
1109 (
1110 "torus",
1111 torus(),
1112 vec![5, 100, 200],
1113 vec![[1.1, 0.1, 0.2], [0.9, -0.1, 0.3], [1.0, 0.2, -0.1]],
1114 ),
1115 ] {
1116 let mut bvh = build_bvh(&mesh);
1117 let sign = SignData::build(&mut mesh, &bvh, SignMode::Pseudonormal).unwrap();
1118 let mut tx = mesh.begin_edit();
1119 tx.set_positions(&moved, &positions).unwrap();
1120 let patch = tx.commit().unwrap();
1121 bvh.update(&mut mesh, patch.changes()).unwrap();
1122 let mut refitted = sign.clone();
1123 assert!(
1124 refitted.try_refit(&mesh, &bvh, patch.changes()).unwrap(),
1125 "{name}"
1126 );
1127 let rebuilt = SignData::build(&mut mesh, &bvh, SignMode::Pseudonormal).unwrap();
1128 assert_eq!(fingerprint(&refitted), fingerprint(&rebuilt), "{name}");
1129 }
1130 let mut mesh = cube();
1132 let mut bvh = build_bvh(&mesh);
1133 let sign = SignData::build(&mut mesh, &bvh, SignMode::Pseudonormal).unwrap();
1134 let mut tx = mesh.begin_edit();
1135 tx.remove_triangles(&[0]).unwrap();
1136 let patch = tx.commit().unwrap();
1137 bvh.update(&mut mesh, patch.changes()).unwrap();
1138 let mut refitted = sign.clone();
1139 assert!(!refitted.try_refit(&mesh, &bvh, patch.changes()).unwrap());
1140 }
1141
1142 #[test]
1143 fn signed_volume_detects_inside_out() {
1144 let closed = status_of(cube(), SignMode::Auto).unwrap();
1147 assert!(closed.closed && closed.consistent_orientation);
1148 assert!(closed.signed_volume > 0.0);
1149 let (p, t) = cube_buffers();
1150 let flipped: Vec<[u32; 3]> = t.iter().map(|tri| [tri[0], tri[2], tri[1]]).collect();
1151 let inside_out = status_of(mesh(&p, &flipped), SignMode::Auto).unwrap();
1152 assert!(inside_out.closed && inside_out.consistent_orientation);
1153 assert!(inside_out.signed_volume < 0.0);
1154 assert!(
1155 (closed.signed_volume + inside_out.signed_volume).abs()
1156 < 1e-6 * closed.signed_volume.abs()
1157 );
1158 }
1159
1160 #[test]
1161 fn status_reports_topology() {
1162 let closed = SignStatus {
1163 mode: SignMode::Pseudonormal,
1164 closed: true,
1165 consistent_orientation: true,
1166 non_manifold_vertices: 0,
1167 degenerate_triangles: 0,
1168 signed_volume: 0.0,
1169 };
1170 assert_eq!(flags_of(cube(), SignMode::Auto), Ok(closed));
1171 assert_eq!(flags_of(cube(), SignMode::Pseudonormal), Ok(closed));
1172 assert_eq!(flags_of(torus(), SignMode::Auto), Ok(closed));
1173
1174 let (p, mut t) = cube_buffers();
1176 t.drain(2..4);
1177 let open = SignStatus {
1178 closed: false,
1179 ..closed
1180 };
1181 assert_eq!(flags_of(mesh(&p, &t), SignMode::Auto), Ok(open));
1182 assert_eq!(flags_of(terrain(4), SignMode::Auto), Ok(open));
1183
1184 let (mut p, mut t) = cube_buffers();
1186 let (p2, t2) = cube_buffers();
1187 p.extend(p2[1..].iter().map(|q| q.map(|c| c + 2.0)));
1189 t.extend(
1190 t2.iter()
1191 .map(|tri| tri.map(|v| if v == 0 { 7 } else { v + 7 })),
1192 );
1193 assert_eq!(
1194 flags_of(mesh(&p, &t), SignMode::Auto),
1195 Ok(SignStatus {
1196 non_manifold_vertices: 1,
1197 ..closed
1198 })
1199 );
1200
1201 let (p, mut t) = cube_buffers();
1203 t.push([0, 0, 1]);
1204 assert_eq!(
1205 flags_of(mesh(&p, &t), SignMode::Auto),
1206 Ok(SignStatus {
1207 closed: false,
1208 non_manifold_vertices: 2,
1209 degenerate_triangles: 1,
1210 ..closed
1211 })
1212 );
1213
1214 let (p, mut t) = cube_buffers();
1217 t[0].swap(1, 2);
1218 let flipped = SignStatus {
1219 consistent_orientation: false,
1220 ..closed
1221 };
1222 assert_eq!(flags_of(mesh(&p, &t), SignMode::Pseudonormal), Ok(flipped));
1223 assert_eq!(
1224 flags_of(mesh(&p, &t), SignMode::Auto),
1225 Ok(SignStatus {
1226 mode: SignMode::Winding,
1227 ..flipped
1228 })
1229 );
1230 t.truncate(11);
1231 assert_eq!(
1232 flags_of(mesh(&p, &t), SignMode::Auto),
1233 Ok(SignStatus {
1234 closed: false,
1235 ..flipped
1236 })
1237 );
1238 assert_eq!(
1239 flags_of(cube(), SignMode::Winding),
1240 Ok(SignStatus {
1241 mode: SignMode::Winding,
1242 ..closed
1243 })
1244 );
1245 }
1246
1247 fn fingerprint(s: &SignData) -> (SignStatus, Vec<u64>) {
1249 let normals = s.vertex_normals.iter().chain(&s.edge_normals).flatten();
1250 let ids = s.degenerate.iter().chain(&s.offsets).chain(&s.candidates);
1251 let v = normals
1252 .map(|x| u64::from(x.to_bits()))
1253 .chain(ids.map(|&i| u64::from(i)))
1254 .chain(s.moments.bits())
1255 .chain(std::iter::once(s.status.signed_volume.to_bits()))
1256 .collect();
1257 (s.status, v)
1258 }
1259
1260 #[test]
1261 fn sign_build_bit_identical() {
1262 let mut mesh = terrain(317);
1264 assert!(mesh.triangle_count() >= 200_000);
1265 let bvh = build_bvh(&mesh);
1266 for mode in [SignMode::Pseudonormal, SignMode::Winding] {
1267 let build = |mesh: &mut Mesh| fingerprint(&SignData::build(mesh, &bvh, mode).unwrap());
1268 par::set_force_sequential(true);
1269 let reference = build(&mut mesh);
1270 par::set_force_sequential(false);
1271 assert!(reference.1.len() > 200_000, "{mode:?}: nothing built");
1272 if !par::enabled() {
1273 continue;
1275 }
1276 for k in [1, 2, 8] {
1277 let got = par::with_threads(k, || build(&mut mesh));
1278 assert!(got == reference, "{mode:?}: {k} threads");
1279 }
1280 let before = par::parallel_entries();
1281 assert!(build(&mut mesh) == reference, "{mode:?}: global pool");
1282 assert!(
1283 par::parallel_entries() > before,
1284 "{mode:?}: ran sequentially"
1285 );
1286 }
1287 }
1288
1289 #[test]
1290 fn small_inputs_stay_sequential() {
1291 let before = par::parallel_entries();
1292 for mode in [SignMode::Pseudonormal, SignMode::Winding] {
1293 for mesh in [cube(), icosphere(), torus(), terrain(16)] {
1294 status_of(mesh, mode).unwrap();
1295 }
1296 }
1297 assert_eq!(par::parallel_entries(), before);
1298 }
1299
1300 #[test]
1301 fn is_current_tracks_revision_and_bvh_generation() {
1302 let mut mesh = cube();
1303 let mut bvh = build_bvh(&mesh);
1304 let sign = SignData::build(&mut mesh, &bvh, SignMode::Auto).unwrap();
1305 assert!(sign.is_current(&mesh, &bvh));
1306 assert_eq!(sign.mode(), SignMode::Pseudonormal);
1307
1308 let other = cube();
1310 let other_bvh = build_bvh(&other);
1311 assert!(!sign.is_current(&other, &other_bvh));
1312 assert!(!sign.is_current(&mesh, &other_bvh));
1313
1314 let mut tx = mesh.begin_edit();
1316 tx.set_positions(&[0], &[[-1.0, -1.0, -1.5]]).unwrap();
1317 let patch = tx.commit().unwrap();
1318 assert!(!sign.is_current(&mesh, &bvh));
1319 bvh.update(&mut mesh, patch.changes()).unwrap();
1320 assert!(!sign.is_current(&mesh, &bvh));
1321 let sign = SignData::build(&mut mesh, &bvh, SignMode::Auto).unwrap();
1322 assert!(sign.is_current(&mesh, &bvh));
1323
1324 bvh.rebuild(&mesh).unwrap();
1326 assert!(!sign.is_current(&mesh, &bvh));
1327 let sign = SignData::build(&mut mesh, &bvh, SignMode::Auto).unwrap();
1328 assert!(sign.is_current(&mesh, &bvh));
1329
1330 let mut tx = mesh.begin_edit();
1332 tx.remove_triangles(&[0]).unwrap();
1333 tx.commit().unwrap();
1334 mesh.compact().unwrap();
1335 let bvh = build_bvh(&mesh);
1336 assert!(!sign.is_current(&mesh, &bvh));
1337 let sign = SignData::build(&mut mesh, &bvh, SignMode::Auto).unwrap();
1338 assert!(sign.is_current(&mesh, &bvh));
1339 }
1340
1341 fn stepped_cases() -> Vec<(&'static str, Mesh, SignMode)> {
1345 let (p, mut t) = cube_buffers();
1346 t[0].swap(1, 2);
1347 let flipped = mesh(&p, &t);
1348 let flipped_pn = mesh(&p, &t);
1349 let (p, mut t) = cube_buffers();
1350 t.push([0, 0, 1]);
1351 let repeated = mesh(&p, &t);
1352 let mut sliver = terrain(24);
1354 let mut tx = sliver.begin_edit();
1355 let v = tx
1356 .add_vertices(&[[0.5, 0.0, terrain_height(0.5, 0.0)]])
1357 .unwrap()[0];
1358 tx.add_triangles(&[[0, v, 1]]).unwrap();
1359 tx.remove_triangles(&[30, 31, 200]).unwrap();
1360 tx.commit().unwrap();
1361 vec![
1362 ("torus, auto", torus(), SignMode::Auto),
1363 ("flipped cube, auto", flipped, SignMode::Auto),
1364 (
1365 "flipped cube, pseudonormal",
1366 flipped_pn,
1367 SignMode::Pseudonormal,
1368 ),
1369 ("repeated vertex", repeated, SignMode::Pseudonormal),
1370 (
1371 "sliver terrain, pseudonormal",
1372 sliver,
1373 SignMode::Pseudonormal,
1374 ),
1375 ("terrain, winding", terrain(20), SignMode::Winding),
1376 ]
1377 }
1378
1379 fn fresh_copy(m: &Mesh) -> Mesh {
1382 let mut c = mesh(m.positions(), m.triangles());
1383 let dead: Vec<TriangleId> = (0..m.triangle_slots() as TriangleId)
1384 .filter(|&t| !m.is_triangle_alive(t))
1385 .collect();
1386 if !dead.is_empty() {
1387 let mut tx = c.begin_edit();
1388 tx.remove_triangles(&dead).unwrap();
1389 tx.commit().unwrap();
1390 }
1391 c
1392 }
1393
1394 fn step_all(build: &mut SignBuild, mesh: &mut Mesh, bvh: &Bvh, budget: usize) -> usize {
1396 let mut steps = 1;
1397 while !build.step(mesh, bvh, budget).unwrap() {
1398 steps += 1;
1399 }
1400 steps
1401 }
1402
1403 #[test]
1404 fn stepped_build_matches_one_call() {
1405 for (name, base, mode) in stepped_cases() {
1406 let mut m = fresh_copy(&base);
1409 let bvh = build_bvh(&m);
1410 let reference = fingerprint(&SignData::build(&mut m, &bvh, mode).unwrap());
1411 for budget in [1, 2, 7, 100, 5000, usize::MAX] {
1412 let mut m = fresh_copy(&base);
1413 let bvh = build_bvh(&m);
1414 let mut build = SignBuild::new(&m, &bvh, mode).unwrap();
1415 let steps = step_all(&mut build, &mut m, &bvh, budget);
1416 assert!(build.is_done(), "{name}");
1417 let got = fingerprint(&build.finish().unwrap());
1418 assert!(got == reference, "{name}: budget {budget}");
1419 if budget == 1 {
1420 assert!(steps > m.triangle_slots(), "{name}: {steps} steps of one");
1421 }
1422 }
1423 }
1424 }
1425
1426 #[test]
1427 fn stepped_build_refuses_another_state() {
1428 let mut m = terrain(8);
1429 let bvh = build_bvh(&m);
1430 let mut build = SignBuild::new(&m, &bvh, SignMode::Pseudonormal).unwrap();
1431 assert!(!build.step(&mut m, &bvh, 3).unwrap());
1432 let mut tx = m.begin_edit();
1433 tx.set_positions(&[0], &[[0.0, 0.0, 1.0]]).unwrap();
1434 tx.commit().unwrap();
1435 assert!(!build.is_for(&m, &bvh));
1436 assert!(matches!(
1437 build.step(&mut m, &bvh, 3),
1438 Err(FieldsError::InvalidInput(_))
1439 ));
1440 assert!(build.finish().is_none());
1441 let other = terrain(8);
1442 assert!(matches!(
1443 SignBuild::new(&other, &bvh, SignMode::Auto),
1444 Err(FieldsError::Query(QueryError::MeshMismatch))
1445 ));
1446 }
1447
1448 #[test]
1449 fn stepped_build_bit_identical_in_parallel() {
1450 let base = terrain(317);
1452 for mode in [SignMode::Pseudonormal, SignMode::Winding] {
1453 par::set_force_sequential(true);
1454 let mut m = fresh_copy(&base);
1455 let bvh = build_bvh(&m);
1456 let reference = fingerprint(&SignData::build(&mut m, &bvh, mode).unwrap());
1457 par::set_force_sequential(false);
1458 for budget in [3_000, 40_000, 1_000_000] {
1459 for k in [1, 8] {
1460 let got = par::with_threads(k, || {
1461 let mut m = fresh_copy(&base);
1462 let bvh = build_bvh(&m);
1463 let mut build = SignBuild::new(&m, &bvh, mode).unwrap();
1464 step_all(&mut build, &mut m, &bvh, budget);
1465 fingerprint(&build.finish().unwrap())
1466 });
1467 assert!(got == reference, "{mode:?}: budget {budget}, {k} threads");
1468 }
1469 }
1470 }
1471 }
1472}