git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

src/remesh.rs (25K)

  1 //! Incremental isotropic remeshing.
  2 //!
  3 //! The load-bearing half of Phase 3. Without topology that keeps primitives
  4 //! proportional to surface area, every growth simulation degenerates within a
  5 //! few dozen frames: `develop` pushes points apart, the triangles between them
  6 //! stretch, and an attribute diffused across a stretched mesh is being
  7 //! averaged over distances that no longer mean what they meant.
  8 //!
  9 //! The algorithm is Botsch and Kobbelt's, four passes over the mesh repeated a
 10 //! few times:
 11 //!
 12 //! 1. **Split** every edge longer than 4/3 of the target length.
 13 //! 2. **Collapse** every edge shorter than 4/5 of it.
 14 //! 3. **Flip** edges that would bring their four points closer to valence 6.
 15 //! 4. **Relax** each point toward the centroid of its neighbours, with the
 16 //!    normal component removed so the pass smooths the triangulation without
 17 //!    moving the surface.
 18 //!
 19 //! The 4/3 and 4/5 are the paper's, and they are not arbitrary: a window
 20 //! narrower than that lets a split produce two edges short enough for the next
 21 //! collapse to undo, and the mesh oscillates instead of converging.
 22 //!
 23 //! ## What it does with the simulation's data
 24 //!
 25 //! This is the node that Phase 2's contract was built for, so it is careful
 26 //! about identity and attributes:
 27 //!
 28 //! - A **split** allocates a new point and interpolates every attribute from
 29 //!   the two endpoints. A place that did not exist gets values consistent with
 30 //!   its neighbourhood rather than zeros.
 31 //! - A **collapse** keeps one endpoint — its identity and its values — rather
 32 //!   than averaging into a new point. The surviving point is one the solver
 33 //!   has been writing to, and a remesh should cost the simulation as little
 34 //!   memory as it can.
 35 //! - A **flip** and a **relax** change no attributes at all.
 36 //!
 37 //! The paper's fifth pass is here too: after relaxing, every point is pulled
 38 //! back onto the surface the remesh started from. Tangential relaxation alone
 39 //! lets a surface creep — each point slides a little, and over a few dozen
 40 //! iterations a sphere quietly shrinks — so the projection is what makes it
 41 //! safe to run a remesh every frame of a solve, which is the whole point.
 42 
 43 use crate::detail::{AttribData, AttribValue, Detail, PointId};
 44 use glam::Vec3;
 45 use std::collections::HashMap;
 46 
 47 /// How the four passes are tuned. Defaults are the paper's.
 48 #[derive(Clone, Copy, Debug)]
 49 pub struct Settings {
 50     /// The edge length the mesh is steered toward.
 51     pub target: f32,
 52     /// How many times the four passes run.
 53     pub iterations: usize,
 54     /// Strength of the tangential relaxation, 0 to 1.
 55     pub relax: f32,
 56     pub split: bool,
 57     pub collapse: bool,
 58     pub flip: bool,
 59     /// Pull relaxed points back onto the input surface.
 60     pub project: bool,
 61 }
 62 
 63 impl Default for Settings {
 64     fn default() -> Self {
 65         Self {
 66             target: 0.1,
 67             iterations: 3,
 68             relax: 0.5,
 69             split: true,
 70             collapse: true,
 71             flip: true,
 72             project: true,
 73         }
 74     }
 75 }
 76 
 77 /// A triangle mesh in a form that can be edited in place.
 78 ///
 79 /// [`Detail`]'s CSR storage is compact and good for reading, which is what
 80 /// every other operator does to it. Remeshing is the one thing that rewires
 81 /// topology per edge, so it converts in, edits, and converts back rather than
 82 /// making every other operator pay for an edit-friendly layout.
 83 ///
 84 /// Points and triangles are tombstoned rather than removed during a pass:
 85 /// compacting mid-pass would invalidate every index the pass is holding.
 86 struct Mesh {
 87     pos: Vec<Vec3>,
 88     ids: Vec<PointId>,
 89     /// Per point, its attribute values as loose components, in `attr_names`
 90     /// order — the form the split interpolation works in.
 91     attrs: Vec<Vec<f32>>,
 92     attr_names: Vec<String>,
 93     attr_types: Vec<crate::detail::AttribType>,
 94     groups: Vec<(String, Vec<bool>)>,
 95     tris: Vec<[u32; 3]>,
 96     dead_point: Vec<bool>,
 97     dead_tri: Vec<bool>,
 98     /// Point to the triangles that have referenced it. Maintained as
 99     /// triangles are added and rewired, and read through a filter that drops
100     /// dead entries and ones the point has since been rewired out of — so a
101     /// stale entry is harmless and nothing has to be removed eagerly.
102     ///
103     /// Without this every adjacency question is a scan of the whole mesh, and
104     /// the flip pass alone asks four per edge.
105     p2t: Vec<Vec<usize>>,
106     next_id: PointId,
107 }
108 
109 impl Mesh {
110     fn from_detail(d: &Detail) -> Mesh {
111         let attr_names: Vec<String> = d.points().names().iter().map(|s| s.to_string()).collect();
112         let attr_types: Vec<crate::detail::AttribType> = attr_names
113             .iter()
114             .filter_map(|n| d.points().get(n).map(|a| a.ty()))
115             .collect();
116         let attrs: Vec<Vec<f32>> = (0..d.num_points())
117             .map(|p| {
118                 attr_names
119                     .iter()
120                     .flat_map(|n| match d.points().value(n, p) {
121                         Some(AttribValue::Float(x)) => vec![x],
122                         Some(AttribValue::Float2(x)) => x.to_vec(),
123                         Some(AttribValue::Float3(x)) => x.to_vec(),
124                         Some(AttribValue::Float4(x)) => x.to_vec(),
125                         Some(AttribValue::Int(x)) => vec![x as f32],
126                         None => vec![],
127                     })
128                     .collect()
129             })
130             .collect();
131         let groups: Vec<(String, Vec<bool>)> = d
132             .points()
133             .group_names()
134             .iter()
135             .map(|g| {
136                 (
137                     g.to_string(),
138                     (0..d.num_points()).map(|p| d.points().in_group(g, p)).collect(),
139                 )
140             })
141             .collect();
142 
143         // Only triangles are remeshed. A polygon fans on the way in, which is
144         // what the renderer does with it anyway.
145         let mut tris = Vec::new();
146         for prim in 0..d.num_prims() {
147             let pts = d.prim_points(prim);
148             for i in 1..pts.len().saturating_sub(1) {
149                 tris.push([pts[0], pts[i], pts[i + 1]]);
150             }
151         }
152 
153         let n = d.num_points();
154         let max_id = d.ids().iter().copied().max().map(|m| m + 1).unwrap_or(0);
155         let mut p2t: Vec<Vec<usize>> = vec![Vec::new(); n];
156         for (t, tri) in tris.iter().enumerate() {
157             for &q in tri {
158                 p2t[q as usize].push(t);
159             }
160         }
161         Mesh {
162             pos: (0..n).map(|p| d.pos(p)).collect(),
163             ids: d.ids().to_vec(),
164             attrs,
165             attr_names,
166             attr_types,
167             groups,
168             dead_tri: vec![false; tris.len()],
169             tris,
170             dead_point: vec![false; n],
171             p2t,
172             next_id: max_id,
173         }
174     }
175 
176     /// Add a triangle, keeping the incidence in step.
177     fn add_tri(&mut self, tri: [u32; 3]) {
178         let t = self.tris.len();
179         self.tris.push(tri);
180         self.dead_tri.push(false);
181         for &q in &tri {
182             self.p2t[q as usize].push(t);
183         }
184     }
185 
186     /// Point `from` to `to` in triangle `t`, keeping the incidence in step.
187     fn rewire(&mut self, t: usize, from: u32, to: u32) {
188         for slot in self.tris[t].iter_mut() {
189             if *slot == from {
190                 *slot = to;
191             }
192         }
193         self.p2t[to as usize].push(t);
194     }
195 
196     /// Live triangles using both endpoints of an edge.
197     fn tris_on_edge(&self, a: u32, b: u32) -> Vec<usize> {
198         let mut out: Vec<usize> = self
199             .p2t
200             .get(a as usize)
201             .map(|ts| {
202                 ts.iter()
203                     .copied()
204                     .filter(|&t| {
205                         !self.dead_tri[t] && self.tris[t].contains(&a) && self.tris[t].contains(&b)
206                     })
207                     .collect()
208             })
209             .unwrap_or_default();
210         out.sort_unstable();
211         out.dedup();
212         out
213     }
214 
215     fn into_detail(mut self) -> Detail {
216         self.dead_tri.resize(self.tris.len(), false);
217         // Drop points nothing references any more, as well as the ones
218         // collapse tombstoned: a split-then-collapse can strand a point that
219         // was never itself collapsed.
220         let mut used = vec![false; self.pos.len()];
221         for (t, tri) in self.tris.iter().enumerate() {
222             if self.dead_tri[t] {
223                 continue;
224             }
225             for &p in tri {
226                 used[p as usize] = true;
227             }
228         }
229 
230         let mut d = Detail::new();
231         let mut remap = vec![u32::MAX; self.pos.len()];
232         let mut kept: Vec<usize> = Vec::new();
233         for p in 0..self.pos.len() {
234             if self.dead_point[p] || !used[p] {
235                 continue;
236             }
237             remap[p] = d.add_point(self.pos[p]);
238             kept.push(p);
239         }
240         // Identities are restored rather than re-allocated: a point that
241         // survived a remesh is the same point, and the solver has been writing
242         // to it.
243         // `kept` and the points just added are the same list, so this cannot
244         // fail. Asserted rather than discarded because the failure mode is a
245         // silently renumbered mesh, which a simulation would experience as
246         // every point forgetting itself at once.
247         d.set_ids(kept.iter().map(|&p| self.ids[p]).collect(), self.next_id)
248             .expect("one identity per surviving point");
249 
250         for (t, tri) in self.tris.iter().enumerate() {
251             if self.dead_tri[t] {
252                 continue;
253             }
254             let mapped = [remap[tri[0] as usize], remap[tri[1] as usize], remap[tri[2] as usize]];
255             if mapped.iter().any(|&m| m == u32::MAX) || mapped[0] == mapped[1] || mapped[1] == mapped[2] || mapped[0] == mapped[2] {
256                 continue;
257             }
258             d.add_prim(&mapped);
259         }
260 
261         let mut offset = 0usize;
262         for (i, name) in self.attr_names.iter().enumerate() {
263             let ty = self.attr_types[i];
264             let k = ty.components();
265             let mut data = AttribData::zeroed(ty, kept.len());
266             for (new, &old) in kept.iter().enumerate() {
267                 let row = &self.attrs[old];
268                 let comps: Vec<f32> = (0..k).map(|c| row.get(offset + c).copied().unwrap_or(0.0)).collect();
269                 let _ = data.set(new, components(ty, &comps));
270             }
271             let _ = d.points_mut().insert(name, data);
272             offset += k;
273         }
274         for (name, members) in &self.groups {
275             d.points_mut().create_group(name);
276             for (new, &old) in kept.iter().enumerate() {
277                 if members.get(old).copied().unwrap_or(false) {
278                     d.points_mut().add_to_group(name, new);
279                 }
280             }
281         }
282         d
283     }
284 
285     /// A point halfway along an edge, with every attribute interpolated.
286     fn split_point(&mut self, a: u32, b: u32) -> u32 {
287         let (a, b) = (a as usize, b as usize);
288         let pos = (self.pos[a] + self.pos[b]) * 0.5;
289         let attrs: Vec<f32> = self.attrs[a]
290             .iter()
291             .zip(self.attrs[b].iter())
292             .map(|(x, y)| (x + y) * 0.5)
293             .collect();
294         self.pos.push(pos);
295         self.attrs.push(attrs);
296         self.ids.push(self.next_id);
297         self.next_id += 1;
298         self.dead_point.push(false);
299         self.p2t.push(Vec::new());
300         // A new point joins a group only where BOTH its parents were in it: a
301         // point that is half in a selection is not in it, and the alternative
302         // grows every group along its own boundary every time the mesh is
303         // remeshed.
304         for (_, members) in self.groups.iter_mut() {
305             let inherits = members.get(a).copied().unwrap_or(false)
306                 && members.get(b).copied().unwrap_or(false);
307             members.push(inherits);
308         }
309         (self.pos.len() - 1) as u32
310     }
311 
312     /// Live triangles touching a point.
313     fn tris_of(&self, p: u32) -> Vec<usize> {
314         let mut out: Vec<usize> = self
315             .p2t
316             .get(p as usize)
317             .map(|ts| {
318                 ts.iter()
319                     .copied()
320                     .filter(|&t| !self.dead_tri[t] && self.tris[t].contains(&p))
321                     .collect()
322             })
323             .unwrap_or_default();
324         out.sort_unstable();
325         out.dedup();
326         out
327     }
328 
329     /// Unique live edges, each as `[low, high]`, with the triangles on them.
330     fn edges(&self) -> Vec<([u32; 2], Vec<usize>)> {
331         let mut map: HashMap<[u32; 2], Vec<usize>> = HashMap::new();
332         for (t, tri) in self.tris.iter().enumerate() {
333             if self.dead_tri[t] {
334                 continue;
335             }
336             for i in 0..3 {
337                 let (a, b) = (tri[i], tri[(i + 1) % 3]);
338                 map.entry([a.min(b), a.max(b)]).or_default().push(t);
339             }
340         }
341         let mut out: Vec<([u32; 2], Vec<usize>)> = map.into_iter().collect();
342         // Sorted, because HashMap order would make the result depend on the
343         // hasher's seed and a remesh has to be reproducible.
344         out.sort_unstable_by_key(|(e, _)| *e);
345         out
346     }
347 
348     fn len_of(&self, e: [u32; 2]) -> f32 {
349         (self.pos[e[1] as usize] - self.pos[e[0] as usize]).length()
350     }
351 }
352 
353 fn components(ty: crate::detail::AttribType, c: &[f32]) -> AttribValue {
354     let at = |i: usize| c.get(i).copied().unwrap_or(0.0);
355     match ty {
356         crate::detail::AttribType::Float => AttribValue::Float(at(0)),
357         crate::detail::AttribType::Float2 => AttribValue::Float2([at(0), at(1)]),
358         crate::detail::AttribType::Float3 => AttribValue::Float3([at(0), at(1), at(2)]),
359         crate::detail::AttribType::Float4 => AttribValue::Float4([at(0), at(1), at(2), at(3)]),
360         crate::detail::AttribType::Int => AttribValue::Int(at(0).round() as i32),
361     }
362 }
363 
364 /// Split every edge longer than 4/3 of the target.
365 fn split_pass(m: &mut Mesh, target: f32) -> usize {
366     let long = target * 4.0 / 3.0;
367     let mut done = 0;
368     // The edge LIST is a snapshot — the pass decides up front which edges it
369     // will consider, so a split cannot cascade within one pass. The TRIANGLES
370     // are looked up at the moment of the split: an earlier split in the same
371     // pass has already replaced the faces this edge sits on, and acting on
372     // the snapshot's stale indices is what tears the surface open.
373     for (e, _) in m.edges() {
374         if m.dead_point[e[0] as usize] || m.dead_point[e[1] as usize] || m.len_of(e) <= long {
375             continue;
376         }
377         let tris = m.tris_on_edge(e[0], e[1]);
378         if tris.is_empty() {
379             continue;
380         }
381         let mid = m.split_point(e[0], e[1]);
382         for t in tris {
383             if m.dead_tri[t] {
384                 continue;
385             }
386             let tri = m.tris[t];
387             // The corner opposite the split edge; the triangle becomes two,
388             // each keeping the original winding.
389             let Some(i) = (0..3).find(|&i| !e.contains(&tri[i])) else { continue };
390             let (opp, x, y) = (tri[i], tri[(i + 1) % 3], tri[(i + 2) % 3]);
391             m.dead_tri[t] = true;
392             m.add_tri([opp, x, mid]);
393             m.add_tri([opp, mid, y]);
394         }
395         done += 1;
396     }
397     done
398 }
399 
400 /// Collapse every edge shorter than 4/5 of the target.
401 ///
402 /// The survivor keeps its identity and values; the other endpoint is
403 /// tombstoned and every triangle referencing it is rewired. Collapses that
404 /// would leave a neighbour edge too long are refused, which is what stops the
405 /// pass from undoing the splits that just ran.
406 fn collapse_pass(m: &mut Mesh, target: f32) -> usize {
407     let short = target * 4.0 / 5.0;
408     let long = target * 4.0 / 3.0;
409     let mut done = 0;
410     for (e, _) in m.edges() {
411         let (a, b) = (e[0], e[1]);
412         if m.dead_point[a as usize] || m.dead_point[b as usize] || m.len_of(e) >= short {
413             continue;
414         }
415         // Would the survivor end up with an edge that the next split pass
416         // would just cut again? Then leave it: two passes undoing each other
417         // is how a remesh oscillates instead of converging.
418         let keep = m.pos[a as usize];
419         let too_long = m.tris_of(b).iter().any(|&t| {
420             m.tris[t]
421                 .iter()
422                 .any(|&q| q != b && q != a && (m.pos[q as usize] - keep).length() > long)
423         });
424         if too_long {
425             continue;
426         }
427         // Refuse a collapse that would flip a triangle over: if any triangle
428         // keeping both points would end up facing the other way, the surface
429         // would self-intersect where it used to be flat.
430         let folds = m.tris_of(b).iter().any(|&t| {
431             let tri = m.tris[t];
432             if tri.contains(&a) {
433                 return false;
434             }
435             let before = face_normal(m, tri);
436             let after_tri = tri.map(|q| if q == b { a } else { q });
437             let after = face_normal(m, after_tri);
438             before.dot(after) <= 0.0
439         });
440         if folds {
441             continue;
442         }
443 
444         m.dead_point[b as usize] = true;
445         for t in m.tris_of(b) {
446             if m.dead_tri[t] {
447                 continue;
448             }
449             if m.tris[t].contains(&a) {
450                 // The two triangles along the collapsed edge fold to nothing.
451                 m.dead_tri[t] = true;
452                 continue;
453             }
454             m.rewire(t, b, a);
455         }
456         done += 1;
457     }
458     done
459 }
460 
461 fn face_normal(m: &Mesh, tri: [u32; 3]) -> Vec3 {
462     let (a, b, c) = (
463         m.pos[tri[0] as usize],
464         m.pos[tri[1] as usize],
465         m.pos[tri[2] as usize],
466     );
467     (b - a).cross(c - a)
468 }
469 
470 /// Flip edges whose two triangles would be better shaped the other way.
471 ///
472 /// "Better" is total deviation from valence 6, which is the valence a regular
473 /// triangulation of a plane has — the measure the paper uses, and the one that
474 /// drives a mesh toward equilateral triangles.
475 fn flip_pass(m: &mut Mesh) -> usize {
476     let mut done = 0;
477     for (e, _) in m.edges() {
478         // Looked up now rather than taken from the snapshot, for the same
479         // reason the split pass does: an earlier flip has rewired faces.
480         let tris = m.tris_on_edge(e[0], e[1]);
481         if tris.len() != 2 {
482             continue; // a boundary edge has nothing to flip into
483         }
484         let (t0, t1) = (tris[0], tris[1]);
485         let Some(&o0) = m.tris[t0].iter().find(|q| !e.contains(q)) else { continue };
486         let Some(&o1) = m.tris[t1].iter().find(|q| !e.contains(q)) else { continue };
487         if o0 == o1 {
488             continue;
489         }
490 
491         let val = |p: u32| m.tris_of(p).len() as i32;
492         let dev = |v: i32| (v - 6).abs();
493         let before = dev(val(e[0])) + dev(val(e[1])) + dev(val(o0)) + dev(val(o1));
494         // The flip moves one triangle off each endpoint and onto each opposite
495         // corner.
496         let after = dev(val(e[0]) - 1) + dev(val(e[1]) - 1) + dev(val(o0) + 1) + dev(val(o1) + 1);
497         if after >= before {
498             continue;
499         }
500         // Refuse a flip that would fold either new triangle against the
501         // surface it came from.
502         let n0 = face_normal(m, m.tris[t0]);
503         let (new0, new1) = ([o0, e[0], o1], [o1, e[1], o0]);
504         if face_normal(m, new0).dot(n0) <= 0.0 || face_normal(m, new1).dot(n0) <= 0.0 {
505             continue;
506         }
507         // Rewiring both triangles wholesale, so the incidence is rebuilt for
508         // the corners that changed.
509         m.tris[t0] = new0;
510         m.tris[t1] = new1;
511         for &q in new0.iter().chain(new1.iter()) {
512             m.p2t[q as usize].push(t0);
513             m.p2t[q as usize].push(t1);
514         }
515         done += 1;
516     }
517     done
518 }
519 
520 /// Move each point toward the centroid of its neighbours, with the normal
521 /// component removed.
522 ///
523 /// Removing the normal component is what makes this a retriangulation rather
524 /// than a smooth: the points slide within the surface to even out the
525 /// triangles, and the shape they describe is left where it was.
526 fn relax_pass(m: &mut Mesh, amount: f32) {
527     if amount <= 0.0 {
528         return;
529     }
530     let mut nbrs: Vec<Vec<u32>> = vec![Vec::new(); m.pos.len()];
531     for (t, tri) in m.tris.iter().enumerate() {
532         if m.dead_tri[t] {
533             continue;
534         }
535         for i in 0..3 {
536             let (a, b) = (tri[i], tri[(i + 1) % 3]);
537             if !nbrs[a as usize].contains(&b) {
538                 nbrs[a as usize].push(b);
539             }
540             if !nbrs[b as usize].contains(&a) {
541                 nbrs[b as usize].push(a);
542             }
543         }
544     }
545     let mut normals: Vec<Vec3> = vec![Vec3::ZERO; m.pos.len()];
546     for (t, tri) in m.tris.iter().enumerate() {
547         if m.dead_tri[t] {
548             continue;
549         }
550         let n = face_normal(m, *tri);
551         for &p in tri {
552             normals[p as usize] += n;
553         }
554     }
555 
556     let before = m.pos.clone();
557     for p in 0..m.pos.len() {
558         if m.dead_point[p] || nbrs[p].is_empty() {
559             continue;
560         }
561         let centroid: Vec3 =
562             nbrs[p].iter().map(|&q| before[q as usize]).sum::<Vec3>() / nbrs[p].len() as f32;
563         let mut delta = (centroid - before[p]) * amount;
564         let n = normals[p].normalize_or_zero();
565         if n != Vec3::ZERO {
566             delta -= n * delta.dot(n);
567         }
568         m.pos[p] = before[p] + delta;
569     }
570 }
571 
572 /// Pull every point back onto the surface the remesh started from.
573 ///
574 /// Relaxation slides points within the surface, but "within" is only true to
575 /// first order: on anything curved the slide leaves the surface slightly, and
576 /// the error compounds. Without this a sphere remeshed for fifty iterations is
577 /// visibly smaller than the one it started as.
578 fn project_pass(m: &mut Mesh, rest: &crate::spatial::TriGrid) {
579     if rest.is_empty() {
580         return;
581     }
582     for p in 0..m.pos.len() {
583         if m.dead_point[p] {
584             continue;
585         }
586         if let Some(hit) = rest.closest(m.pos[p]) {
587             m.pos[p] = hit.point;
588         }
589     }
590 }
591 
592 /// Subdivide every triangle into four, `depth` times.
593 ///
594 /// Distinct from remeshing, and deliberately so: this makes a predictable,
595 /// uniform refinement of the mesh it is given — every edge gets a midpoint,
596 /// every triangle becomes four, and the shape does not move. Remesh steers
597 /// toward a length and rearranges topology to get there; Subdivide multiplies
598 /// what is already there.
599 ///
600 /// It does NOT smooth, which the Houdini SOP of this name does. A subdivision
601 /// that also moved points would be two operations wearing one name, and the
602 /// smoothing one is already available as Remesh's relaxation.
603 ///
604 /// Attributes interpolate onto the midpoints, the same way a remesh split
605 /// does, so a field defined on a coarse mesh survives being refined.
606 pub fn subdivide(input: &Detail, depth: usize) -> Detail {
607     if input.num_prims() == 0 || depth == 0 {
608         return input.clone();
609     }
610     let mut m = Mesh::from_detail(input);
611     // Capped because this is exponential: each level is four times the
612     // triangles, so six levels is four thousand times the input and anything
613     // past that is a hang rather than a render.
614     for _ in 0..depth.min(6) {
615         let mut mids: HashMap<[u32; 2], u32> = HashMap::new();
616         for (e, _) in m.edges() {
617             let mid = m.split_point(e[0], e[1]);
618             mids.insert(e, mid);
619         }
620         let key = |a: u32, b: u32| [a.min(b), a.max(b)];
621         // Snapshotted, because the loop adds triangles as it goes and the new
622         // ones are already subdivided.
623         let count = m.tris.len();
624         for t in 0..count {
625             if m.dead_tri[t] {
626                 continue;
627             }
628             let tri = m.tris[t];
629             let (Some(&ab), Some(&bc), Some(&ca)) = (
630                 mids.get(&key(tri[0], tri[1])),
631                 mids.get(&key(tri[1], tri[2])),
632                 mids.get(&key(tri[2], tri[0])),
633             ) else {
634                 continue;
635             };
636             m.dead_tri[t] = true;
637             // Three corner triangles and the middle one, each keeping the
638             // original winding.
639             m.add_tri([tri[0], ab, ca]);
640             m.add_tri([ab, tri[1], bc]);
641             m.add_tri([ca, bc, tri[2]]);
642             m.add_tri([ab, bc, ca]);
643         }
644     }
645     m.into_detail()
646 }
647 
648 /// Remesh toward `settings.target` edge length.
649 pub fn remesh(input: &Detail, settings: Settings) -> Detail {
650     if input.num_prims() == 0 || settings.target <= 0.0 {
651         return input.clone();
652     }
653     // Built once from the INPUT and reused by every iteration: projecting onto
654     // the previous iteration's surface would chase the creep rather than
655     // correct it, since each iteration's drift would become the next one's
656     // idea of where the surface is.
657     let rest = settings.project.then(|| crate::spatial::TriGrid::build(input));
658     let mut m = Mesh::from_detail(input);
659     for _ in 0..settings.iterations.min(20) {
660         if settings.split {
661             split_pass(&mut m, settings.target);
662         }
663         if settings.collapse {
664             collapse_pass(&mut m, settings.target);
665         }
666         if settings.flip {
667             flip_pass(&mut m);
668         }
669         relax_pass(&mut m, settings.relax.clamp(0.0, 1.0));
670         if let Some(rest) = &rest {
671             project_pass(&mut m, rest);
672         }
673     }
674     m.into_detail()
675 }