1use std::cmp::Ordering;
11
12use fast_mesh_store::{Mesh, Transaction, TriangleId, VertexId};
13
14use crate::{OpsError, Result, push, reserve};
15
16pub const SPLIT_FACTOR: f64 = 4.0 / 3.0;
18pub const COLLAPSE_FACTOR: f64 = 4.0 / 5.0;
20pub const REMESH_MAX_ROUNDS: u32 = 10;
22
23#[derive(Debug, Clone, Copy, PartialEq)]
26pub struct Region {
27 pub center: [f32; 3],
29 pub radius: f32,
31}
32
33impl Default for Region {
34 fn default() -> Self {
35 Region {
36 center: [0.0; 3],
37 radius: 1.0,
38 }
39 }
40}
41
42#[derive(Debug, Clone, Copy, PartialEq)]
44pub struct RemeshOptions {
45 pub region: Region,
47 pub target_length: Option<f32>,
51 pub max_rounds: u32,
53 pub split_only: bool,
56}
57
58impl Default for RemeshOptions {
59 fn default() -> Self {
60 RemeshOptions {
61 region: Region::default(),
62 target_length: None,
63 max_rounds: REMESH_MAX_ROUNDS,
64 split_only: false,
65 }
66 }
67}
68
69impl RemeshOptions {
70 pub fn builder() -> RemeshOptionsBuilder {
72 RemeshOptionsBuilder::default()
73 }
74}
75
76#[derive(Debug, Clone, Default)]
78pub struct RemeshOptionsBuilder {
79 options: RemeshOptions,
80}
81
82impl RemeshOptionsBuilder {
83 pub fn center(mut self, center: [f32; 3]) -> Self {
85 self.options.region.center = center;
86 self
87 }
88
89 pub fn radius(mut self, radius: f32) -> Self {
91 self.options.region.radius = radius;
92 self
93 }
94
95 pub fn target_length(mut self, length: f32) -> Self {
97 self.options.target_length = Some(length);
98 self
99 }
100
101 pub fn max_rounds(mut self, rounds: u32) -> Self {
103 self.options.max_rounds = rounds;
104 self
105 }
106
107 pub fn split_only(mut self, split_only: bool) -> Self {
109 self.options.split_only = split_only;
110 self
111 }
112
113 pub fn build(self) -> RemeshOptions {
115 self.options
116 }
117}
118
119#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
121pub struct RemeshReport {
122 pub splits: usize,
124 pub collapses: usize,
126 pub flips: usize,
128 pub rounds: usize,
130}
131
132pub fn remesh(tx: &mut Transaction, options: &RemeshOptions) -> Result<RemeshReport> {
145 let region = &options.region;
146 if !region.radius.is_finite() || region.radius <= 0.0 {
147 return Err(OpsError::InvalidInput(
148 "remesh region radius must be positive and finite",
149 ));
150 }
151 if region.center.iter().any(|c| !c.is_finite()) {
152 return Err(OpsError::InvalidInput(
153 "remesh region center must be finite",
154 ));
155 }
156 let mesh = tx.mesh();
157 if !has_member(mesh, region) {
158 return Ok(RemeshReport::default());
159 }
160 let sigma = match options.target_length {
161 Some(t) if t.is_finite() && t > 0.0 => f64::from(t),
162 _ => match global_mean_edge_length(mesh)? {
163 Some(mean) => mean,
164 None => return Ok(RemeshReport::default()),
165 },
166 };
167 let base = mesh.positions();
168 let mut positions: Vec<[f32; 3]> = Vec::new();
169 reserve(&mut positions, base.len())?;
170 positions.extend_from_slice(base);
171 let slots = positions.len();
172 let mut tris: Vec<WorkTri> = Vec::new();
173 reserve(&mut tris, mesh.triangle_slots())?;
174 for t in 0..mesh.triangle_slots() as u32 {
175 if let Some(corners) = mesh.triangle(t) {
176 push(
177 &mut tris,
178 WorkTri {
179 id: Some(t),
180 corners,
181 alive: true,
182 },
183 )?;
184 }
185 }
186 let mut vlive: Vec<bool> = Vec::new();
187 reserve(&mut vlive, slots)?;
188 vlive.extend(std::iter::repeat_n(true, slots));
189 let mut added: Vec<NewVert> = Vec::new();
190 let mut report = RemeshReport::default();
191 for _ in 0..options.max_rounds {
192 let fresh = positions.len();
193 let splits = split_round(
194 &mut positions,
195 &mut vlive,
196 &mut tris,
197 &mut added,
198 region,
199 sigma,
200 )?;
201 let (collapses, flips) = if options.split_only {
202 (0, 0)
203 } else {
204 let collapses = collapse_round(
205 &mut positions,
206 &mut vlive,
207 &mut tris,
208 &mut added,
209 region,
210 sigma,
211 fresh,
212 )?;
213 let flips = flip_round(&positions, &vlive, &mut tris, region, sigma)?;
214 (collapses, flips)
215 };
216 if splits + collapses + flips == 0 {
217 break;
218 }
219 report.splits += splits;
220 report.collapses += collapses;
221 report.flips += flips;
222 report.rounds += 1;
223 }
224 if report.splits + report.collapses + report.flips == 0 {
225 return Ok(report);
226 }
227 commit(tx, slots, &vlive, &tris, &added)?;
228 Ok(report)
229}
230
231struct WorkTri {
233 id: Option<TriangleId>,
234 corners: [u32; 3],
235 alive: bool,
236}
237
238pub(crate) struct NewVert {
241 pub(crate) pos: [f32; 3],
242 pub(crate) parents: Vec<(VertexId, f32)>,
243}
244
245fn in_region(p: &[f32; 3], region: &Region) -> bool {
246 let dx = f64::from(p[0]) - f64::from(region.center[0]);
247 let dy = f64::from(p[1]) - f64::from(region.center[1]);
248 let dz = f64::from(p[2]) - f64::from(region.center[2]);
249 let t = dx.hypot(dy).hypot(dz) / f64::from(region.radius);
250 t.is_finite() && t < 1.0
251}
252
253fn has_member(mesh: &Mesh, region: &Region) -> bool {
257 let positions = mesh.positions();
258 for t in 0..mesh.triangle_slots() as u32 {
259 let corners = match mesh.triangle(t) {
260 Some(c) => c,
261 None => continue,
262 };
263 if degenerate(&corners) {
264 continue;
265 }
266 for k in 0..3 {
267 let mid = midpoint(
268 &positions[corners[k] as usize],
269 &positions[corners[(k + 1) % 3] as usize],
270 );
271 if in_region(&mid, region) {
272 return true;
273 }
274 }
275 }
276 false
277}
278
279fn dist(a: &[f32; 3], b: &[f32; 3]) -> f64 {
280 let dx = f64::from(a[0]) - f64::from(b[0]);
281 let dy = f64::from(a[1]) - f64::from(b[1]);
282 let dz = f64::from(a[2]) - f64::from(b[2]);
283 dx.hypot(dy).hypot(dz)
284}
285
286pub(crate) fn midpoint(a: &[f32; 3], b: &[f32; 3]) -> [f32; 3] {
287 [
288 (a[0] + b[0]) / 2.0,
289 (a[1] + b[1]) / 2.0,
290 (a[2] + b[2]) / 2.0,
291 ]
292}
293
294fn global_mean_edge_length(mesh: &Mesh) -> Result<Option<f64>> {
295 let mut edges: Vec<(u32, u32)> = Vec::new();
296 reserve(&mut edges, 3 * mesh.triangle_slots())?;
297 for t in 0..mesh.triangle_slots() as u32 {
298 let corners = match mesh.triangle(t) {
299 Some(c) => c,
300 None => continue,
301 };
302 if degenerate(&corners) {
303 continue;
304 }
305 for k in 0..3 {
306 let (a, b) = (corners[k], corners[(k + 1) % 3]);
307 push(&mut edges, (a.min(b), a.max(b)))?;
308 }
309 }
310 edges.sort_unstable();
311 edges.dedup();
312 if edges.is_empty() {
313 return Ok(None);
314 }
315 let positions = mesh.positions();
316 let mut sum = 0.0;
317 for &(a, b) in &edges {
318 sum += dist(&positions[a as usize], &positions[b as usize]);
319 }
320 let mean = sum / edges.len() as f64;
321 Ok((mean.is_finite() && mean > 0.0).then_some(mean))
322}
323
324fn split_round(
328 positions: &mut Vec<[f32; 3]>,
329 vlive: &mut Vec<bool>,
330 tris: &mut Vec<WorkTri>,
331 added: &mut Vec<NewVert>,
332 region: &Region,
333 sigma: f64,
334) -> Result<usize> {
335 let slots = positions.len() - added.len();
338 let pairs = snapshot_pairs(tris, vlive)?;
339 let mut splits = 0;
340 let mut start = 0;
341 while start < pairs.len() {
342 let mut end = start + 1;
343 while end < pairs.len() && pairs[end].0 == pairs[start].0 {
344 end += 1;
345 }
346 let (a, b) = pairs[start].0;
347 let mid = midpoint(&positions[a as usize], &positions[b as usize]);
348 if in_region(&mid, region)
349 && dist(&positions[a as usize], &positions[b as usize]) > sigma * SPLIT_FACTOR
350 && pairs[start..end]
351 .iter()
352 .all(|&(_, i)| carries(&tris[i].corners, a, b))
353 {
354 let m = positions.len() as u32;
355 let parents = blend_parents(a, b, slots, added)?;
356 push(positions, mid)?;
357 push(vlive, true)?;
358 push(added, NewVert { pos: mid, parents })?;
359 for &(_, i) in &pairs[start..end] {
360 let corners = tris[i].corners;
361 let apex = apex_of(&corners, a, b);
362 let ab = runs_directed(&corners, a, b);
366 let (first, second) = if ab {
367 ([a, m, apex], [m, b, apex])
368 } else {
369 ([b, m, apex], [m, a, apex])
370 };
371 tris[i].corners = first;
372 push(
373 tris,
374 WorkTri {
375 id: None,
376 corners: second,
377 alive: true,
378 },
379 )?;
380 }
381 splits += 1;
382 }
383 start = end;
384 }
385 Ok(splits)
386}
387
388fn collapse_round(
393 positions: &mut Vec<[f32; 3]>,
394 vlive: &mut Vec<bool>,
395 tris: &mut [WorkTri],
396 added: &mut Vec<NewVert>,
397 region: &Region,
398 sigma: f64,
399 fresh: usize,
400) -> Result<usize> {
401 let slots = positions.len() - added.len();
402 let pairs = snapshot_pairs(tris, vlive)?;
403 let mut locked: Vec<bool> = Vec::new();
406 reserve(&mut locked, vlive.len())?;
407 locked.extend(std::iter::repeat_n(false, vlive.len()));
408 let mut run = 0;
409 while run < pairs.len() {
410 let mut end = run + 1;
411 while end < pairs.len() && pairs[end].0 == pairs[run].0 {
412 end += 1;
413 }
414 if end - run != 2 {
415 locked[pairs[run].0.0 as usize] = true;
416 locked[pairs[run].0.1 as usize] = true;
417 }
418 run = end;
419 }
420 let mut collapses = 0;
421 let mut start = 0;
422 while start < pairs.len() {
423 let mut end = start + 1;
424 while end < pairs.len() && pairs[end].0 == pairs[start].0 {
425 end += 1;
426 }
427 let (a, b) = pairs[start].0;
428 if end - start == 2
429 && (a as usize) < fresh
430 && (b as usize) < fresh
431 && vlive[a as usize]
432 && vlive[b as usize]
433 && !locked[a as usize]
434 && !locked[b as usize]
435 {
436 let mid = midpoint(&positions[a as usize], &positions[b as usize]);
437 let short = matches!(
438 dist(&positions[a as usize], &positions[b as usize])
439 .partial_cmp(&(sigma * COLLAPSE_FACTOR)),
440 Some(Ordering::Less)
441 );
442 if in_region(&mid, region)
443 && short
444 && let Some(plan) = validate_collapse(positions, vlive, tris, a, b)?
445 {
446 let m = positions.len() as u32;
447 let parents = blend_parents(a, b, slots, added)?;
448 push(positions, plan.mid)?;
449 push(vlive, true)?;
450 push(
451 added,
452 NewVert {
453 pos: plan.mid,
454 parents,
455 },
456 )?;
457 tris[plan.owners[0]].alive = false;
458 tris[plan.owners[1]].alive = false;
459 for i in plan.fan {
460 tris[i].corners = tris[i]
461 .corners
462 .map(|v| if v == a || v == b { m } else { v });
463 }
464 vlive[a as usize] = false;
465 vlive[b as usize] = false;
466 collapses += 1;
467 }
468 }
469 start = end;
470 }
471 Ok(collapses)
472}
473
474struct CollapsePlan {
476 owners: [usize; 2],
477 fan: Vec<usize>,
478 mid: [f32; 3],
479}
480
481fn validate_collapse(
485 positions: &[[f32; 3]],
486 vlive: &[bool],
487 tris: &[WorkTri],
488 a: u32,
489 b: u32,
490) -> Result<Option<CollapsePlan>> {
491 let mut owners: Vec<usize> = Vec::new();
492 let mut fan: Vec<usize> = Vec::new();
493 let mut na: Vec<u32> = Vec::new();
494 let mut nb: Vec<u32> = Vec::new();
495 for (i, tri) in tris.iter().enumerate() {
496 if !tri.alive {
497 continue;
498 }
499 let c = tri.corners;
500 if degenerate(&c) || !vlive[c[0] as usize] || !vlive[c[1] as usize] || !vlive[c[2] as usize]
501 {
502 if c.contains(&a) || c.contains(&b) {
505 return Ok(None);
506 }
507 continue;
508 }
509 let has_a = c.contains(&a);
510 let has_b = c.contains(&b);
511 if has_a && has_b {
512 push(&mut owners, i)?;
513 } else if has_a || has_b {
514 push(&mut fan, i)?;
515 let side = if has_a { &mut na } else { &mut nb };
516 for &v in &c {
517 if v != a && v != b {
518 push(side, v)?;
519 }
520 }
521 }
522 }
523 if owners.len() != 2 {
524 return Ok(None);
525 }
526 let c = apex_of(&tris[owners[0]].corners, a, b);
527 let d = apex_of(&tris[owners[1]].corners, a, b);
528 push(&mut na, c)?;
529 push(&mut na, d)?;
530 push(&mut nb, c)?;
531 push(&mut nb, d)?;
532 na.sort_unstable();
533 na.dedup();
534 nb.sort_unstable();
535 nb.dedup();
536 let mut common: Vec<u32> = Vec::new();
537 for &v in &na {
538 if nb.binary_search(&v).is_ok() {
539 push(&mut common, v)?;
540 }
541 }
542 if common.as_slice() != [c.min(d), c.max(d)] {
543 return Ok(None);
544 }
545 let mid = midpoint(&positions[a as usize], &positions[b as usize]);
546 let m = f64v(&mid);
547 for &i in &fan {
548 let old = tris[i].corners;
549 let new = old.map(|v| {
550 if v == a || v == b {
551 m
552 } else {
553 f64v(&positions[v as usize])
554 }
555 });
556 let before = tri_normal(&old, positions);
557 let after = cross3(sub3(new[1], new[0]), sub3(new[2], new[0]));
558 if !matches!(
559 dot3(after, before).partial_cmp(&0.0),
560 Some(Ordering::Greater)
561 ) {
562 return Ok(None);
563 }
564 }
565 Ok(Some(CollapsePlan {
566 owners: [owners[0], owners[1]],
567 fan,
568 mid,
569 }))
570}
571
572fn flip_round(
576 positions: &[[f32; 3]],
577 vlive: &[bool],
578 tris: &mut [WorkTri],
579 region: &Region,
580 sigma: f64,
581) -> Result<usize> {
582 let pairs = snapshot_pairs(tris, vlive)?;
583 let mut unique: Vec<(u32, u32)> = Vec::new();
584 reserve(&mut unique, pairs.len())?;
585 unique.extend(pairs.iter().map(|&(e, _)| e));
586 unique.dedup();
587 let mut created: Vec<(u32, u32)> = Vec::new();
588 let mut flips = 0;
589 let mut start = 0;
590 while start < pairs.len() {
591 let mut end = start + 1;
592 while end < pairs.len() && pairs[end].0 == pairs[start].0 {
593 end += 1;
594 }
595 if end - start == 2 {
596 let (a, b) = pairs[start].0;
597 let mid = midpoint(&positions[a as usize], &positions[b as usize]);
598 let ctx = FlipCtx {
599 positions,
600 tris: &*tris,
601 vlive,
602 unique: &unique,
603 created: &created,
604 sigma,
605 };
606 if in_region(&mid, region)
607 && let Some(plan) = validate_flip(&ctx, a, b, pairs[start].1, pairs[start + 1].1)
608 {
609 tris[plan.owners[0]].corners = plan.new[0];
610 tris[plan.owners[1]].corners = plan.new[1];
611 push(&mut created, plan.created)?;
612 flips += 1;
613 }
614 }
615 start = end;
616 }
617 Ok(flips)
618}
619
620struct FlipCtx<'a> {
622 positions: &'a [[f32; 3]],
623 tris: &'a [WorkTri],
624 vlive: &'a [bool],
625 unique: &'a [(u32, u32)],
626 created: &'a [(u32, u32)],
627 sigma: f64,
628}
629
630struct FlipPlan {
632 owners: [usize; 2],
633 new: [[u32; 3]; 2],
634 created: (u32, u32),
635}
636
637fn validate_flip(ctx: &FlipCtx, a: u32, b: u32, o1: usize, o2: usize) -> Option<FlipPlan> {
642 for &o in &[o1, o2] {
643 let t = &ctx.tris[o];
644 if !t.alive || degenerate(&t.corners) || !carries(&t.corners, a, b) {
645 return None;
646 }
647 if !ctx.vlive[t.corners[0] as usize]
648 || !ctx.vlive[t.corners[1] as usize]
649 || !ctx.vlive[t.corners[2] as usize]
650 {
651 return None;
652 }
653 }
654 let old1 = ctx.tris[o1].corners;
655 let old2 = ctx.tris[o2].corners;
656 let c = apex_of(&old1, a, b);
657 let d = apex_of(&old2, a, b);
658 if c == d {
659 return None;
660 }
661 let ab1 = runs_directed(&old1, a, b);
667 if ab1 == runs_directed(&old2, a, b) {
668 return None;
669 }
670 let (new1, new2) = if ab1 {
671 ([c, a, d], [c, d, b])
672 } else {
673 ([c, d, a], [d, c, b])
674 };
675 let e = (c.min(d), c.max(d));
676 if ctx.unique.binary_search(&e).is_ok() || ctx.created.contains(&e) {
677 return None;
678 }
679 let replacement = dist(&ctx.positions[c as usize], &ctx.positions[d as usize]);
680 if !matches!(
681 replacement.partial_cmp(&(ctx.sigma * SPLIT_FACTOR)),
682 Some(Ordering::Less | Ordering::Equal)
683 ) {
684 return None;
685 }
686 let area_old = doubled_area(&old1, ctx.positions) + doubled_area(&old2, ctx.positions);
692 let area_new = doubled_area(&new1, ctx.positions) + doubled_area(&new2, ctx.positions);
693 if !matches!(
694 area_new.partial_cmp(&(area_old * 1.01)),
695 Some(Ordering::Less | Ordering::Equal)
696 ) {
697 return None;
698 }
699 let old_max = match (
700 max_cos_angle(&old1, ctx.positions),
701 max_cos_angle(&old2, ctx.positions),
702 ) {
703 (Some(x), Some(y)) => x.max(y),
704 _ => return None,
705 };
706 let new_max = match (
707 max_cos_angle(&new1, ctx.positions),
708 max_cos_angle(&new2, ctx.positions),
709 ) {
710 (Some(x), Some(y)) => x.max(y),
711 _ => return None,
712 };
713 if !matches!(new_max.partial_cmp(&old_max), Some(Ordering::Less)) {
714 return None;
715 }
716 if dot3(
718 tri_normal(&new1, ctx.positions),
719 tri_normal(&old1, ctx.positions),
720 ) <= 0.0
721 || dot3(
722 tri_normal(&new2, ctx.positions),
723 tri_normal(&old2, ctx.positions),
724 ) <= 0.0
725 {
726 return None;
727 }
728 Some(FlipPlan {
729 owners: [o1, o2],
730 new: [new1, new2],
731 created: e,
732 })
733}
734
735pub(crate) fn runs_directed(corners: &[u32; 3], a: u32, b: u32) -> bool {
737 (corners[0] == a && corners[1] == b)
738 || (corners[1] == a && corners[2] == b)
739 || (corners[2] == a && corners[0] == b)
740}
741
742fn snapshot_pairs(tris: &[WorkTri], vlive: &[bool]) -> Result<Vec<((u32, u32), usize)>> {
744 let mut pairs: Vec<((u32, u32), usize)> = Vec::new();
745 reserve(&mut pairs, 3 * tris.len())?;
746 for (i, tri) in tris.iter().enumerate() {
747 if !tri.alive || degenerate(&tri.corners) {
748 continue;
749 }
750 let c = tri.corners;
751 if !vlive[c[0] as usize] || !vlive[c[1] as usize] || !vlive[c[2] as usize] {
752 continue;
753 }
754 for k in 0..3 {
755 let (a, b) = (c[k], c[(k + 1) % 3]);
756 push(&mut pairs, ((a.min(b), a.max(b)), i))?;
757 }
758 }
759 pairs.sort_unstable();
760 Ok(pairs)
761}
762
763pub(crate) fn degenerate(corners: &[u32; 3]) -> bool {
764 corners[0] == corners[1] || corners[1] == corners[2] || corners[2] == corners[0]
765}
766
767pub(crate) fn carries(corners: &[u32; 3], a: u32, b: u32) -> bool {
768 corners.contains(&a) && corners.contains(&b)
769}
770
771pub(crate) fn apex_of(corners: &[u32; 3], a: u32, b: u32) -> u32 {
773 if corners[0] != a && corners[0] != b {
774 corners[0]
775 } else if corners[1] != a && corners[1] != b {
776 corners[1]
777 } else {
778 corners[2]
779 }
780}
781
782pub(crate) fn f64v(p: &[f32; 3]) -> [f64; 3] {
783 [f64::from(p[0]), f64::from(p[1]), f64::from(p[2])]
784}
785
786pub(crate) fn sub3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
787 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
788}
789
790pub(crate) fn dot3(a: [f64; 3], b: [f64; 3]) -> f64 {
791 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
792}
793
794pub(crate) fn cross3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
795 [
796 a[1] * b[2] - a[2] * b[1],
797 a[2] * b[0] - a[0] * b[2],
798 a[0] * b[1] - a[1] * b[0],
799 ]
800}
801
802fn tri_normal(corners: &[u32; 3], positions: &[[f32; 3]]) -> [f64; 3] {
803 let p0 = f64v(&positions[corners[0] as usize]);
804 let p1 = f64v(&positions[corners[1] as usize]);
805 let p2 = f64v(&positions[corners[2] as usize]);
806 cross3(sub3(p1, p0), sub3(p2, p0))
807}
808
809fn doubled_area(corners: &[u32; 3], positions: &[[f32; 3]]) -> f64 {
811 let n = tri_normal(corners, positions);
812 dot3(n, n).sqrt()
813}
814
815fn max_cos_angle(corners: &[u32; 3], positions: &[[f32; 3]]) -> Option<f64> {
818 let p = [
819 f64v(&positions[corners[0] as usize]),
820 f64v(&positions[corners[1] as usize]),
821 f64v(&positions[corners[2] as usize]),
822 ];
823 let mut max = f64::NEG_INFINITY;
824 for k in 0..3 {
825 let u = sub3(p[k], p[(k + 1) % 3]);
826 let v = sub3(p[k], p[(k + 2) % 3]);
827 let (nu, nv) = (dot3(u, u).sqrt(), dot3(v, v).sqrt());
828 if nu == 0.0 || nv == 0.0 {
829 return None;
830 }
831 max = max.max(dot3(u, v) / (nu * nv));
832 }
833 Some(max)
834}
835
836pub(crate) fn blend_parents(
840 a: u32,
841 b: u32,
842 slots: usize,
843 added: &[NewVert],
844) -> Result<Vec<(VertexId, f32)>> {
845 let mut entries: Vec<(u32, f32)> = Vec::new();
846 for v in [a, b] {
847 if (v as usize) < slots {
848 push(&mut entries, (v, 0.5))?;
849 } else {
850 for &(id, w) in &added[(v as usize) - slots].parents {
851 push(&mut entries, (id, 0.5 * w))?;
852 }
853 }
854 }
855 entries.sort_unstable_by_key(|x| x.0);
856 let mut parents: Vec<(VertexId, f32)> = Vec::new();
857 reserve(&mut parents, entries.len())?;
858 for (id, w) in entries {
859 match parents.last_mut() {
860 Some(last) if last.0 == id => last.1 += w,
861 _ => push(&mut parents, (id, w))?,
862 }
863 }
864 Ok(parents)
865}
866
867fn commit(
868 tx: &mut Transaction,
869 slots: usize,
870 vlive: &[bool],
871 tris: &[WorkTri],
872 added: &[NewVert],
873) -> Result<()> {
874 let mut n_old = 0;
878 let mut n_new = 0;
879 let mut n_dead_tri = 0;
880 for tri in tris {
881 match tri.id {
882 Some(_) if !tri.alive => n_dead_tri += 1,
883 Some(_) => n_old += 1,
884 None if tri.alive => n_new += 1,
885 None => {}
886 }
887 }
888 let n_dead_vert = vlive.iter().take(slots).filter(|&&live| !live).count();
889 let mut ids: Vec<VertexId> = Vec::new();
890 reserve(&mut ids, added.len())?;
891 let mut set_ids: Vec<TriangleId> = Vec::new();
892 reserve(&mut set_ids, n_old)?;
893 let mut set_tris: Vec<[VertexId; 3]> = Vec::new();
894 reserve(&mut set_tris, n_old)?;
895 let mut new_tris: Vec<[VertexId; 3]> = Vec::new();
896 reserve(&mut new_tris, n_new)?;
897 let mut del_tris: Vec<TriangleId> = Vec::new();
898 reserve(&mut del_tris, n_dead_tri)?;
899 let mut del_verts: Vec<VertexId> = Vec::new();
900 reserve(&mut del_verts, n_dead_vert)?;
901 for (k, nv) in added.iter().enumerate() {
902 if vlive[slots + k] {
903 ids.push(tx.add_vertex_with_parents(nv.pos, &nv.parents)?);
904 } else {
905 ids.push(VertexId::MAX);
907 }
908 }
909 let resolve = |v: u32| -> VertexId {
910 debug_assert!(vlive[v as usize]);
911 let i = v as usize;
912 if i < slots { v } else { ids[i - slots] }
913 };
914 let mesh = tx.mesh();
915 for tri in tris {
916 match tri.id {
917 Some(id) if !tri.alive => del_tris.push(id),
918 Some(id) => {
919 let current = [
920 resolve(tri.corners[0]),
921 resolve(tri.corners[1]),
922 resolve(tri.corners[2]),
923 ];
924 let original = mesh.triangle(id).expect("remesh: working id must exist");
925 if current != original {
926 set_ids.push(id);
927 set_tris.push(current);
928 }
929 }
930 None if tri.alive => new_tris.push([
931 resolve(tri.corners[0]),
932 resolve(tri.corners[1]),
933 resolve(tri.corners[2]),
934 ]),
935 None => {}
936 }
937 }
938 for v in 0..slots as u32 {
939 if !vlive[v as usize] {
940 del_verts.push(v);
941 }
942 }
943 tx.set_triangles(&set_ids, &set_tris)?;
944 tx.add_triangles(&new_tris)?;
945 tx.remove_triangles(&del_tris)?;
946 tx.remove_vertices(&del_verts)?;
947 Ok(())
948}
949
950#[cfg(test)]
951mod tests {
952 use fast_mesh_store::{Mesh, Transaction, VertexId};
953
954 use super::*;
955 use crate::OpsError;
956 use crate::test_util::{edit, mesh_of, parts, sweep, with_triangles};
957 use crate::topology::{EdgeMap, point, sub};
958
959 fn quad_4x1() -> Mesh {
960 mesh_of(
961 &[0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 4.0, 1.0, 0.0, 0.0, 1.0, 0.0],
962 &[[0, 1, 2], [0, 2, 3]],
963 )
964 }
965
966 fn edge_lengths(mesh: &Mesh) -> Vec<f64> {
967 let edges = EdgeMap::build(mesh).unwrap();
968 edges
969 .edges()
970 .map(|((a, b), _)| {
971 let d = sub(point(mesh, a), point(mesh, b));
972 (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt()
973 })
974 .collect()
975 }
976
977 fn all_region() -> RemeshOptions {
978 RemeshOptions::builder()
979 .center([2.0, 0.5, 0.0])
980 .radius(10.0)
981 .target_length(1.0)
982 .build()
983 }
984
985 fn assert_faces_up(mesh: &Mesh) {
987 let (pos, tris) = parts(mesh);
988 assert!(!tris.is_empty());
989 for t in &tris {
990 let x = |v: u32| f64::from(pos[3 * v as usize]);
991 let y = |v: u32| f64::from(pos[3 * v as usize + 1]);
992 let z = (x(t[1]) - x(t[0])) * (y(t[2]) - y(t[0]))
993 - (y(t[1]) - y(t[0])) * (x(t[2]) - x(t[0]));
994 assert!(z > 0.0, "flipped or degenerate: {t:?} z={z}");
995 }
996 }
997
998 #[test]
999 fn split_refines_sparse_quad_to_target() {
1000 let mut mesh = quad_4x1();
1001 let before = parts(&mesh);
1002 let (report, _) = edit(&mut mesh, |tx| remesh(tx, &all_region()));
1003 assert!(report.splits > 0, "expected splits, got {report:?}");
1004 for len in edge_lengths(&mesh) {
1005 assert!(len <= 4.0 / 3.0 + 1e-4, "long edge left: {len}");
1006 }
1007 let (pos_after, tris_after) = parts(&mesh);
1008 assert_eq!(&pos_after[..12], &before.0[..12]);
1009 assert!(tris_after.len() > before.1.len());
1010 assert_faces_up(&mesh);
1011 }
1012
1013 #[test]
1014 fn split_empty_region_and_coarse_target() {
1015 let mut mesh = quad_4x1();
1016 let before = parts(&mesh);
1017 let far = RemeshOptions::builder()
1018 .center([100.0, 0.0, 0.0])
1019 .radius(1.0)
1020 .target_length(1.0)
1021 .build();
1022 let (report, empty) = edit(&mut mesh, |tx| remesh(tx, &far));
1023 assert_eq!((report.splits, report.collapses, report.flips), (0, 0, 0));
1024 assert!(empty);
1025 assert_eq!(parts(&mesh), before);
1026 let mut mesh = quad_4x1();
1027 let coarse = RemeshOptions::builder()
1028 .center([2.0, 0.5, 0.0])
1029 .radius(10.0)
1030 .target_length(100.0)
1031 .build();
1032 let (report, _) = edit(&mut mesh, |tx| remesh(tx, &coarse));
1033 assert_eq!(
1034 (report.splits, report.collapses, report.flips, report.rounds),
1035 (0, 0, 0, 0)
1036 );
1037 }
1038
1039 #[test]
1040 fn split_rejects_invalid_region() {
1041 let mut mesh = quad_4x1();
1042 let zero = RemeshOptions::builder()
1043 .center([2.0, 0.5, 0.0])
1044 .radius(0.0)
1045 .target_length(1.0)
1046 .build();
1047 {
1048 let mut tx = mesh.begin_edit();
1049 assert_eq!(
1050 remesh(&mut tx, &zero),
1051 Err(OpsError::InvalidInput(
1052 "remesh region radius must be positive and finite"
1053 ))
1054 );
1055 }
1056 let nan = RemeshOptions::builder()
1057 .center([f32::NAN, 0.0, 0.0])
1058 .radius(10.0)
1059 .target_length(1.0)
1060 .build();
1061 {
1062 let mut tx = mesh.begin_edit();
1063 assert_eq!(
1064 remesh(&mut tx, &nan),
1065 Err(OpsError::InvalidInput(
1066 "remesh region center must be finite"
1067 ))
1068 );
1069 }
1070 }
1071
1072 #[test]
1073 fn split_keeps_boundary_vertices_live() {
1074 let mut mesh = quad_4x1();
1075 edit(&mut mesh, |tx| remesh(tx, &all_region()));
1076 for v in 0..4u32 {
1077 assert!(mesh.is_vertex_alive(v as VertexId));
1078 }
1079 }
1080
1081 #[test]
1082 fn split_sweep_keeps_atomicity() {
1083 let n = sweep(quad_4x1(), |tx: &mut Transaction| {
1084 remesh(tx, &all_region())?;
1085 Ok(())
1086 });
1087 assert!(n > 0);
1088 }
1089
1090 fn grid_4x4() -> Mesh {
1091 let mut pos = Vec::new();
1092 for j in 0..4 {
1093 for i in 0..4 {
1094 pos.extend([i as f32 * 0.1, j as f32 * 0.1, 0.0]);
1095 }
1096 }
1097 let mut tris = Vec::new();
1098 for j in 0..3 {
1099 for i in 0..3 {
1100 let a = j * 4 + i;
1101 tris.push([a, a + 1, a + 5]);
1102 tris.push([a, a + 5, a + 4]);
1103 }
1104 }
1105 mesh_of(&pos, &tris)
1106 }
1107
1108 fn kite() -> Mesh {
1109 mesh_of(
1112 &[0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 1.0, 0.15, 0.0, 1.0, -2.0, 0.0],
1113 &[[0, 1, 2], [1, 0, 3]],
1114 )
1115 }
1116
1117 #[test]
1118 fn collapse_coarsens_dense_interior() {
1119 let mut mesh = grid_4x4();
1120 let before = parts(&mesh);
1121 let options = RemeshOptions::builder()
1122 .center([0.15, 0.15, 0.0])
1123 .radius(10.0)
1124 .target_length(1.0)
1125 .build();
1126 let (report, _) = edit(&mut mesh, |tx| remesh(tx, &options));
1127 assert_eq!(report.splits, 0);
1128 assert!(report.collapses > 0, "expected collapses, got {report:?}");
1129 let (_, tris_after) = parts(&mesh);
1130 assert!(tris_after.len() < before.1.len());
1131 for t in &tris_after {
1132 assert!(
1133 t[0] != t[1] && t[1] != t[2] && t[2] != t[0],
1134 "degenerate: {t:?}"
1135 );
1136 }
1137 for v in [0, 1, 2, 3, 4, 7, 8, 11, 12, 13, 14, 15] {
1138 assert!(mesh.is_vertex_alive(v as VertexId));
1139 }
1140 assert_faces_up(&mesh);
1141 }
1142
1143 #[test]
1144 fn collapse_skips_all_boundary() {
1145 let mut mesh = mesh_of(&[0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 3.0, 0.0], &[[0, 1, 2]]);
1146 let before = parts(&mesh);
1147 let options = RemeshOptions::builder()
1148 .center([1.0, 1.0, 0.0])
1149 .radius(10.0)
1150 .target_length(10.0)
1151 .build();
1152 let (report, empty) = edit(&mut mesh, |tx| remesh(tx, &options));
1153 assert_eq!(
1154 (report.splits, report.collapses, report.flips, report.rounds),
1155 (0, 0, 0, 0)
1156 );
1157 assert!(empty);
1158 assert_eq!(parts(&mesh), before);
1159 }
1160
1161 #[test]
1162 fn flip_improves_skinny_kite() {
1163 let mut mesh = kite();
1164 let options = RemeshOptions::builder()
1165 .center([1.0, 0.5, 0.0])
1166 .radius(10.0)
1167 .target_length(10.0)
1168 .build();
1169 let (report, _) = edit(&mut mesh, |tx| remesh(tx, &options));
1170 assert_eq!(
1171 (report.splits, report.collapses, report.flips, report.rounds),
1172 (0, 0, 1, 1)
1173 );
1174 let (_, tris) = parts(&mesh);
1175 assert!(
1176 tris.iter().any(|t| t.contains(&2) && t.contains(&3)),
1177 "flipped diagonal missing: {tris:?}"
1178 );
1179 assert_faces_up(&mesh);
1180 }
1181
1182 #[test]
1183 fn remesh_split_only_never_collapses_or_flips() {
1184 let mut mesh = grid_4x4();
1186 let before = parts(&mesh);
1187 let options = RemeshOptions::builder()
1188 .center([0.15, 0.15, 0.0])
1189 .radius(10.0)
1190 .target_length(1.0)
1191 .split_only(true)
1192 .build();
1193 let (report, empty) = edit(&mut mesh, |tx| remesh(tx, &options));
1194 assert_eq!(
1195 (report.splits, report.collapses, report.flips, report.rounds),
1196 (0, 0, 0, 0)
1197 );
1198 assert!(empty);
1199 assert_eq!(parts(&mesh), before);
1200 let mut mesh = quad_4x1();
1202 let options = RemeshOptions::builder()
1203 .center([2.0, 0.5, 0.0])
1204 .radius(10.0)
1205 .target_length(1.0)
1206 .split_only(true)
1207 .build();
1208 let (report, _) = edit(&mut mesh, |tx| remesh(tx, &options));
1209 assert!(report.splits > 0, "expected splits, got {report:?}");
1210 assert_eq!((report.collapses, report.flips), (0, 0));
1211 }
1212
1213 #[test]
1214 fn remesh_respects_max_rounds() {
1215 let mut mesh = quad_4x1();
1216 let before = parts(&mesh);
1217 let options = RemeshOptions::builder()
1218 .center([2.0, 0.5, 0.0])
1219 .radius(10.0)
1220 .target_length(1.0)
1221 .max_rounds(0)
1222 .build();
1223 let (report, empty) = edit(&mut mesh, |tx| remesh(tx, &options));
1224 assert_eq!(
1225 (report.splits, report.collapses, report.flips, report.rounds),
1226 (0, 0, 0, 0)
1227 );
1228 assert!(empty);
1229 assert_eq!(parts(&mesh), before);
1230 }
1231
1232 #[test]
1233 fn collapse_and_flip_keep_atomicity() {
1234 let grid_options = RemeshOptions::builder()
1235 .center([0.15, 0.15, 0.0])
1236 .radius(10.0)
1237 .target_length(1.0)
1238 .build();
1239 let n = sweep(grid_4x4(), |tx: &mut Transaction| {
1240 remesh(tx, &grid_options)?;
1241 Ok(())
1242 });
1243 assert!(n > 0);
1244 let kite_options = RemeshOptions::builder()
1245 .center([1.0, 0.5, 0.0])
1246 .radius(10.0)
1247 .target_length(10.0)
1248 .build();
1249 let n = sweep(kite(), |tx: &mut Transaction| {
1250 remesh(tx, &kite_options)?;
1251 Ok(())
1252 });
1253 assert!(n > 0);
1254 }
1255
1256 #[test]
1257 fn collapse_skips_edges_touching_degenerate_triangles() {
1258 let mut mesh = with_triangles(&grid_4x4(), &[[5, 5, 9]]);
1262 let options = RemeshOptions::builder()
1263 .center([0.15, 0.15, 0.0])
1264 .radius(10.0)
1265 .target_length(1.0)
1266 .build();
1267 let (report, _) = edit(&mut mesh, |tx| remesh(tx, &options));
1268 assert!(report.collapses > 0, "expected collapses, got {report:?}");
1269 let (_, tris) = parts(&mesh);
1270 assert!(tris.contains(&[5, 5, 9]));
1271 for t in &tris {
1272 for &v in t {
1273 assert!(mesh.is_vertex_alive(v));
1274 }
1275 }
1276 }
1277
1278 #[test]
1279 fn flip_rejects_reflex_quad() {
1280 let mut mesh = mesh_of(
1283 &[1.0, 0.0, 0.0, 3.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, -1.0, 0.0],
1284 &[[0, 1, 2], [1, 0, 3]],
1285 );
1286 let before = parts(&mesh);
1287 let options = RemeshOptions::builder()
1288 .center([1.0, 0.0, 0.0])
1289 .radius(10.0)
1290 .target_length(10.0)
1291 .build();
1292 let (report, empty) = edit(&mut mesh, |tx| remesh(tx, &options));
1293 assert_eq!(
1294 (report.splits, report.collapses, report.flips, report.rounds),
1295 (0, 0, 0, 0)
1296 );
1297 assert!(empty);
1298 assert_eq!(parts(&mesh), before);
1299 }
1300
1301 #[test]
1302 fn split_finds_edge_crossing_vertex_free_region() {
1303 let mut mesh = mesh_of(
1306 &[0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.1, 0.0],
1307 &[[0, 1, 2]],
1308 );
1309 let options = RemeshOptions::builder()
1310 .center([5.0, 0.0, 0.0])
1311 .radius(1.0)
1312 .target_length(1.0)
1313 .build();
1314 let (report, _) = edit(&mut mesh, |tx| remesh(tx, &options));
1315 assert!(report.splits > 0, "expected splits, got {report:?}");
1316 }
1317
1318 fn grid_10x10() -> Mesh {
1319 let mut pos = Vec::new();
1320 for j in 0..10 {
1321 for i in 0..10 {
1322 pos.extend([i as f32 * 0.2, j as f32 * 0.2, 0.0]);
1323 }
1324 }
1325 let mut tris = Vec::new();
1326 for j in 0..9 {
1327 for i in 0..9 {
1328 let a = j * 10 + i;
1329 tris.push([a, a + 1, a + 11]);
1330 tris.push([a, a + 11, a + 10]);
1331 }
1332 }
1333 mesh_of(&pos, &tris)
1334 }
1335
1336 #[test]
1337 fn remesh_after_inflate_keeps_orientation() {
1338 use crate::analyze::{AnalyzeOptions, analyze};
1339 use crate::deform::{Falloff, InflateOptions, inflate};
1340
1341 let mut mesh = grid_10x10();
1342 for _ in 0..4 {
1343 edit(&mut mesh, |tx| {
1344 inflate(
1345 tx,
1346 &InflateOptions::builder()
1347 .center([0.9, 0.9, 0.0])
1348 .radius(0.8)
1349 .falloff(Falloff::Smooth)
1350 .strength(0.2)
1351 .build(),
1352 )
1353 .map(drop)
1354 });
1355 }
1356 let options = RemeshOptions::builder()
1357 .center([0.9, 0.9, 0.0])
1358 .radius(1.2)
1359 .max_rounds(4)
1360 .build();
1361 let (report, _) = edit(&mut mesh, |tx| remesh(tx, &options));
1362 assert!(
1363 report.splits > 0 && report.flips > 0,
1364 "must exercise splits and flips, got {report:?}"
1365 );
1366 let a = analyze(&mesh, &AnalyzeOptions::default()).unwrap();
1367 assert_eq!(a.orientation_conflicts, 0);
1368 }
1369
1370 #[test]
1371 fn remesh_after_inflate_keeps_orientation_on_sphere() {
1372 use crate::analyze::{AnalyzeOptions, analyze};
1373 use crate::deform::{Falloff, InflateOptions, inflate};
1374 use crate::test_util::uv_sphere;
1375
1376 let mut mesh = uv_sphere(24, 36, 1.0);
1377 for _ in 0..6 {
1378 edit(&mut mesh, |tx| {
1379 inflate(
1380 tx,
1381 &InflateOptions::builder()
1382 .center([0.0, 0.0, 1.0])
1383 .radius(0.5)
1384 .falloff(Falloff::Smooth)
1385 .strength(0.1)
1386 .build(),
1387 )
1388 .map(drop)
1389 });
1390 }
1391 let options = RemeshOptions::builder()
1392 .center([0.0, 0.0, 1.0])
1393 .radius(1.0)
1394 .max_rounds(4)
1395 .build();
1396 let (report, _) = edit(&mut mesh, |tx| remesh(tx, &options));
1397 assert!(
1398 report.splits > 0 && report.flips > 0,
1399 "must exercise splits and flips, got {report:?}"
1400 );
1401 let a = analyze(&mesh, &AnalyzeOptions::default()).unwrap();
1402 assert_eq!(a.orientation_conflicts, 0);
1403 }
1404
1405 #[test]
1406 fn remesh_after_inflate_keeps_orientation_on_noisy_sphere() {
1407 use crate::analyze::{AnalyzeOptions, analyze};
1408 use crate::deform::{Falloff, InflateOptions, inflate};
1409 use crate::test_util::{mesh_of, parts, uv_sphere};
1410
1411 let (pos, tris) = parts(&uv_sphere(24, 36, 1.0));
1413 let noisy: Vec<f32> = pos
1414 .iter()
1415 .enumerate()
1416 .map(|(i, &p)| p + 0.02 * ((i as f32 * 12.9898).sin() + (i as f32 * 78.233).sin()))
1417 .collect();
1418 let mut mesh = mesh_of(&noisy, &tris);
1419 for _ in 0..6 {
1420 edit(&mut mesh, |tx| {
1421 inflate(
1422 tx,
1423 &InflateOptions::builder()
1424 .center([0.0, 0.0, 1.0])
1425 .radius(0.5)
1426 .falloff(Falloff::Smooth)
1427 .strength(0.1)
1428 .build(),
1429 )
1430 .map(drop)
1431 });
1432 }
1433 let options = RemeshOptions::builder()
1434 .center([0.0, 0.0, 1.0])
1435 .radius(1.0)
1436 .max_rounds(4)
1437 .build();
1438 let (report, _) = edit(&mut mesh, |tx| remesh(tx, &options));
1439 assert!(
1440 report.splits > 0 && report.flips > 0,
1441 "must exercise splits and flips, got {report:?}"
1442 );
1443 let a = analyze(&mesh, &AnalyzeOptions::default()).unwrap();
1444 assert_eq!(a.orientation_conflicts, 0);
1445 }
1446
1447 #[test]
1448 fn split_defaults_target_to_mesh_mean() {
1449 let mut mesh = quad_4x1();
1452 let options = RemeshOptions::builder()
1453 .center([2.0, 0.5, 0.0])
1454 .radius(10.0)
1455 .build();
1456 let (report, _) = edit(&mut mesh, |tx| remesh(tx, &options));
1457 assert_eq!(
1458 (report.splits, report.collapses, report.flips, report.rounds),
1459 (3, 0, 1, 2)
1460 );
1461 for len in edge_lengths(&mesh) {
1462 assert!(len <= 2.8247 * 4.0 / 3.0 + 1e-4, "long edge left: {len}");
1463 }
1464 }
1465}