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

src/remesh.rs (37.3K)

  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 //!    unless the new edge would be one the next split cuts.
 16 //! 4. **Relax** each point toward the centroid of its neighbours, with the
 17 //!    normal component removed so the pass smooths the triangulation without
 18 //!    moving the surface.
 19 //!
 20 //! The 4/3 and 4/5 are the paper's, and they are not arbitrary: a window
 21 //! narrower than that lets a split produce two edges short enough for the next
 22 //! collapse to undo, and the mesh oscillates instead of converging.
 23 //!
 24 //! ## What it does with the simulation's data
 25 //!
 26 //! This is the node that Phase 2's contract was built for, so it is careful
 27 //! about identity and attributes:
 28 //!
 29 //! - A **split** allocates a new point and interpolates every attribute from
 30 //!   the two endpoints. A place that did not exist gets values consistent with
 31 //!   its neighbourhood rather than zeros.
 32 //! - A **collapse** keeps one endpoint — its identity and its values — rather
 33 //!   than averaging into a new point. The surviving point is one the solver
 34 //!   has been writing to, and a remesh should cost the simulation as little
 35 //!   memory as it can.
 36 //! - A **flip** and a **relax** change no attributes at all.
 37 //!
 38 //! The paper's fifth pass is here too: after relaxing, every point is pulled
 39 //! back onto the surface the remesh started from. Tangential relaxation alone
 40 //! lets a surface creep — each point slides a little, and over a few dozen
 41 //! iterations a sphere quietly shrinks — so the projection is what makes it
 42 //! safe to run a remesh every frame of a solve, which is the whole point.
 43 
 44 use crate::detail::{AttribData, AttribValue, Detail, PointId};
 45 use glam::Vec3;
 46 use std::collections::HashMap;
 47 
 48 /// How the four passes are tuned. Defaults are the paper's.
 49 #[derive(Clone, Copy, Debug)]
 50 pub struct Settings {
 51     /// The edge length the mesh is steered toward.
 52     pub target: f32,
 53     /// How many times the four passes run.
 54     pub iterations: usize,
 55     /// Strength of the tangential relaxation, 0 to 1.
 56     pub relax: f32,
 57     pub split: bool,
 58     pub collapse: bool,
 59     pub flip: bool,
 60     /// Pull relaxed points back onto the input surface.
 61     pub project: bool,
 62 }
 63 
 64 impl Default for Settings {
 65     fn default() -> Self {
 66         Self {
 67             target: 0.1,
 68             iterations: 3,
 69             relax: 0.5,
 70             split: true,
 71             collapse: true,
 72             flip: true,
 73             project: true,
 74         }
 75     }
 76 }
 77 
 78 /// A triangle mesh in a form that can be edited in place.
 79 ///
 80 /// [`Detail`]'s CSR storage is compact and good for reading, which is what
 81 /// every other operator does to it. Remeshing is the one thing that rewires
 82 /// topology per edge, so it converts in, edits, and converts back rather than
 83 /// making every other operator pay for an edit-friendly layout.
 84 ///
 85 /// Points and triangles are tombstoned rather than removed during a pass:
 86 /// compacting mid-pass would invalidate every index the pass is holding.
 87 struct Mesh {
 88     pos: Vec<Vec3>,
 89     ids: Vec<PointId>,
 90     /// Per point, its attribute values as loose components, in `attr_names`
 91     /// order — the form the split interpolation works in.
 92     attrs: Vec<Vec<f32>>,
 93     attr_names: Vec<String>,
 94     attr_types: Vec<crate::detail::AttribType>,
 95     groups: Vec<(String, Vec<bool>)>,
 96     tris: Vec<[u32; 3]>,
 97     dead_point: Vec<bool>,
 98     dead_tri: Vec<bool>,
 99     /// Point to the triangles that have referenced it. Maintained as
100     /// triangles are added and rewired, and read through a filter that drops
101     /// dead entries and ones the point has since been rewired out of — so a
102     /// stale entry is harmless and nothing has to be removed eagerly.
103     ///
104     /// Without this every adjacency question is a scan of the whole mesh, and
105     /// the flip pass alone asks four per edge.
106     p2t: Vec<Vec<usize>>,
107     next_id: PointId,
108 }
109 
110 impl Mesh {
111     fn from_detail(d: &Detail) -> Mesh {
112         let attr_names: Vec<String> = d.points().names().iter().map(|s| s.to_string()).collect();
113         let attr_types: Vec<crate::detail::AttribType> = attr_names
114             .iter()
115             .filter_map(|n| d.points().get(n).map(|a| a.ty()))
116             .collect();
117         let attrs: Vec<Vec<f32>> = (0..d.num_points())
118             .map(|p| {
119                 attr_names
120                     .iter()
121                     .flat_map(|n| match d.points().value(n, p) {
122                         Some(AttribValue::Float(x)) => vec![x],
123                         Some(AttribValue::Float2(x)) => x.to_vec(),
124                         Some(AttribValue::Float3(x)) => x.to_vec(),
125                         Some(AttribValue::Float4(x)) => x.to_vec(),
126                         Some(AttribValue::Int(x)) => vec![x as f32],
127                         None => vec![],
128                     })
129                     .collect()
130             })
131             .collect();
132         let groups: Vec<(String, Vec<bool>)> = d
133             .points()
134             .group_names()
135             .iter()
136             .map(|g| {
137                 (
138                     g.to_string(),
139                     (0..d.num_points()).map(|p| d.points().in_group(g, p)).collect(),
140                 )
141             })
142             .collect();
143 
144         // Only triangles are remeshed. A polygon fans on the way in, which is
145         // what the renderer does with it anyway.
146         let mut tris = Vec::new();
147         for prim in 0..d.num_prims() {
148             let pts = d.prim_points(prim);
149             for i in 1..pts.len().saturating_sub(1) {
150                 tris.push([pts[0], pts[i], pts[i + 1]]);
151             }
152         }
153 
154         let n = d.num_points();
155         // The counter the detail carries, not one past the highest identity
156         // left: a point a collapse removed is not handed its identity back
157         // by the next split, which a remesh run as separate passes — the
158         // Remesh subnet — would otherwise do between every two of them.
159         let max_id = d.ids().iter().copied().max().map(|m| m + 1).unwrap_or(0).max(d.next_id());
160         let mut p2t: Vec<Vec<usize>> = vec![Vec::new(); n];
161         for (t, tri) in tris.iter().enumerate() {
162             for &q in tri {
163                 p2t[q as usize].push(t);
164             }
165         }
166         Mesh {
167             pos: (0..n).map(|p| d.pos(p)).collect(),
168             ids: d.ids().to_vec(),
169             attrs,
170             attr_names,
171             attr_types,
172             groups,
173             dead_tri: vec![false; tris.len()],
174             tris,
175             dead_point: vec![false; n],
176             p2t,
177             next_id: max_id,
178         }
179     }
180 
181     /// Add a triangle, keeping the incidence in step.
182     fn add_tri(&mut self, tri: [u32; 3]) {
183         let t = self.tris.len();
184         self.tris.push(tri);
185         self.dead_tri.push(false);
186         for &q in &tri {
187             self.p2t[q as usize].push(t);
188         }
189     }
190 
191     /// Point `from` to `to` in triangle `t`, keeping the incidence in step.
192     fn rewire(&mut self, t: usize, from: u32, to: u32) {
193         for slot in self.tris[t].iter_mut() {
194             if *slot == from {
195                 *slot = to;
196             }
197         }
198         self.p2t[to as usize].push(t);
199     }
200 
201     /// Live triangles using both endpoints of an edge.
202     fn tris_on_edge(&self, a: u32, b: u32) -> Vec<usize> {
203         let mut out: Vec<usize> = self
204             .p2t
205             .get(a as usize)
206             .map(|ts| {
207                 ts.iter()
208                     .copied()
209                     .filter(|&t| {
210                         !self.dead_tri[t] && self.tris[t].contains(&a) && self.tris[t].contains(&b)
211                     })
212                     .collect()
213             })
214             .unwrap_or_default();
215         out.sort_unstable();
216         out.dedup();
217         out
218     }
219 
220     fn into_detail(mut self) -> Detail {
221         self.dead_tri.resize(self.tris.len(), false);
222         // Drop points nothing references any more, as well as the ones
223         // collapse tombstoned: a split-then-collapse can strand a point that
224         // was never itself collapsed.
225         let mut used = vec![false; self.pos.len()];
226         for (t, tri) in self.tris.iter().enumerate() {
227             if self.dead_tri[t] {
228                 continue;
229             }
230             for &p in tri {
231                 used[p as usize] = true;
232             }
233         }
234 
235         let mut d = Detail::new();
236         let mut remap = vec![u32::MAX; self.pos.len()];
237         let mut kept: Vec<usize> = Vec::new();
238         for p in 0..self.pos.len() {
239             if self.dead_point[p] || !used[p] {
240                 continue;
241             }
242             remap[p] = d.add_point(self.pos[p]);
243             kept.push(p);
244         }
245         // Identities are restored rather than re-allocated: a point that
246         // survived a remesh is the same point, and the solver has been writing
247         // to it.
248         // `kept` and the points just added are the same list, so this cannot
249         // fail. Asserted rather than discarded because the failure mode is a
250         // silently renumbered mesh, which a simulation would experience as
251         // every point forgetting itself at once.
252         d.set_ids(kept.iter().map(|&p| self.ids[p]).collect(), self.next_id)
253             .expect("one identity per surviving point");
254 
255         for (t, tri) in self.tris.iter().enumerate() {
256             if self.dead_tri[t] {
257                 continue;
258             }
259             let mapped = [remap[tri[0] as usize], remap[tri[1] as usize], remap[tri[2] as usize]];
260             if mapped.iter().any(|&m| m == u32::MAX) || mapped[0] == mapped[1] || mapped[1] == mapped[2] || mapped[0] == mapped[2] {
261                 continue;
262             }
263             d.add_prim(&mapped);
264         }
265 
266         let mut offset = 0usize;
267         for (i, name) in self.attr_names.iter().enumerate() {
268             let ty = self.attr_types[i];
269             let k = ty.components();
270             let mut data = AttribData::zeroed(ty, kept.len());
271             for (new, &old) in kept.iter().enumerate() {
272                 let row = &self.attrs[old];
273                 let comps: Vec<f32> = (0..k).map(|c| row.get(offset + c).copied().unwrap_or(0.0)).collect();
274                 let _ = data.set(new, components(ty, &comps));
275             }
276             let _ = d.points_mut().insert(name, data);
277             offset += k;
278         }
279         for (name, members) in &self.groups {
280             d.points_mut().create_group(name);
281             for (new, &old) in kept.iter().enumerate() {
282                 if members.get(old).copied().unwrap_or(false) {
283                     d.points_mut().add_to_group(name, new);
284                 }
285             }
286         }
287         d
288     }
289 
290     /// A point halfway along an edge, with every attribute interpolated.
291     fn split_point(&mut self, a: u32, b: u32) -> u32 {
292         let (a, b) = (a as usize, b as usize);
293         let pos = (self.pos[a] + self.pos[b]) * 0.5;
294         let attrs: Vec<f32> = self.attrs[a]
295             .iter()
296             .zip(self.attrs[b].iter())
297             .map(|(x, y)| (x + y) * 0.5)
298             .collect();
299         self.pos.push(pos);
300         self.attrs.push(attrs);
301         self.ids.push(self.next_id);
302         self.next_id += 1;
303         self.dead_point.push(false);
304         self.p2t.push(Vec::new());
305         // A new point joins a group only where BOTH its parents were in it: a
306         // point that is half in a selection is not in it, and the alternative
307         // grows every group along its own boundary every time the mesh is
308         // remeshed.
309         for (_, members) in self.groups.iter_mut() {
310             let inherits = members.get(a).copied().unwrap_or(false)
311                 && members.get(b).copied().unwrap_or(false);
312             members.push(inherits);
313         }
314         (self.pos.len() - 1) as u32
315     }
316 
317     /// Live triangles touching a point.
318     fn tris_of(&self, p: u32) -> Vec<usize> {
319         let mut out: Vec<usize> = self
320             .p2t
321             .get(p as usize)
322             .map(|ts| {
323                 ts.iter()
324                     .copied()
325                     .filter(|&t| !self.dead_tri[t] && self.tris[t].contains(&p))
326                     .collect()
327             })
328             .unwrap_or_default();
329         out.sort_unstable();
330         out.dedup();
331         out
332     }
333 
334     /// Unique live edges, each as `[low, high]`, with the triangles on them.
335     fn edges(&self) -> Vec<([u32; 2], Vec<usize>)> {
336         let mut map: HashMap<[u32; 2], Vec<usize>> = HashMap::new();
337         for (t, tri) in self.tris.iter().enumerate() {
338             if self.dead_tri[t] {
339                 continue;
340             }
341             for i in 0..3 {
342                 let (a, b) = (tri[i], tri[(i + 1) % 3]);
343                 map.entry([a.min(b), a.max(b)]).or_default().push(t);
344             }
345         }
346         let mut out: Vec<([u32; 2], Vec<usize>)> = map.into_iter().collect();
347         // Sorted, because HashMap order would make the result depend on the
348         // hasher's seed and a remesh has to be reproducible.
349         out.sort_unstable_by_key(|(e, _)| *e);
350         out
351     }
352 
353     fn len_of(&self, e: [u32; 2]) -> f32 {
354         (self.pos[e[1] as usize] - self.pos[e[0] as usize]).length()
355     }
356 }
357 
358 fn components(ty: crate::detail::AttribType, c: &[f32]) -> AttribValue {
359     let at = |i: usize| c.get(i).copied().unwrap_or(0.0);
360     match ty {
361         crate::detail::AttribType::Float => AttribValue::Float(at(0)),
362         crate::detail::AttribType::Float2 => AttribValue::Float2([at(0), at(1)]),
363         crate::detail::AttribType::Float3 => AttribValue::Float3([at(0), at(1), at(2)]),
364         crate::detail::AttribType::Float4 => AttribValue::Float4([at(0), at(1), at(2), at(3)]),
365         crate::detail::AttribType::Int => AttribValue::Int(at(0).round() as i32),
366     }
367 }
368 
369 /// Split every edge longer than 4/3 of the target.
370 fn split_pass(m: &mut Mesh, target: f32) -> usize {
371     let long = target * 4.0 / 3.0;
372     let mut done = 0;
373     // The edge LIST is a snapshot — the pass decides up front which edges it
374     // will consider, so a split cannot cascade within one pass. The TRIANGLES
375     // are looked up at the moment of the split: an earlier split in the same
376     // pass has already replaced the faces this edge sits on, and acting on
377     // the snapshot's stale indices is what tears the surface open.
378     for (e, _) in m.edges() {
379         if m.dead_point[e[0] as usize] || m.dead_point[e[1] as usize] || m.len_of(e) <= long {
380             continue;
381         }
382         let tris = m.tris_on_edge(e[0], e[1]);
383         if tris.is_empty() {
384             continue;
385         }
386         let mid = m.split_point(e[0], e[1]);
387         for t in tris {
388             if m.dead_tri[t] {
389                 continue;
390             }
391             let tri = m.tris[t];
392             // The corner opposite the split edge; the triangle becomes two,
393             // each keeping the original winding.
394             let Some(i) = (0..3).find(|&i| !e.contains(&tri[i])) else { continue };
395             let (opp, x, y) = (tri[i], tri[(i + 1) % 3], tri[(i + 2) % 3]);
396             m.dead_tri[t] = true;
397             m.add_tri([opp, x, mid]);
398             m.add_tri([opp, mid, y]);
399         }
400         done += 1;
401     }
402     done
403 }
404 
405 /// Collapse every edge shorter than 4/5 of the target.
406 ///
407 /// The survivor — the end in more groups, else the lower index — keeps its
408 /// identity and values and joins the other end's groups; the other end is
409 /// tombstoned and every triangle referencing it is rewired. Collapses that
410 /// would leave a neighbour edge too long are refused, which is what stops the
411 /// pass from undoing the splits that just ran.
412 fn collapse_pass(m: &mut Mesh, target: f32) -> usize {
413     let short = target * 4.0 / 5.0;
414     let long = target * 4.0 / 3.0;
415     let mut done = 0;
416     for (e, _) in m.edges() {
417         if m.dead_point[e[0] as usize] || m.dead_point[e[1] as usize] || m.len_of(e) >= short {
418             continue;
419         }
420         // Which end survives. The one in more groups: a point in a group is
421         // a point something downstream names — the pull's, a pin's — and
422         // the other end is not. Until 2026-09-29 the lower index always
423         // survived, so a pulled point was collapsed into the neighbour it
424         // had been pulled towards, its identity, its values and its
425         // membership gone with it, and the pull went on with nothing to
426         // pull. The lower index still survives a tie, as it always did.
427         let in_groups = |p: u32| m.groups.iter().filter(|(_, members)| members.get(p as usize).copied().unwrap_or(false)).count();
428         let (a, b) = if in_groups(e[1]) > in_groups(e[0]) { (e[1], e[0]) } else { (e[0], e[1]) };
429         // Would the survivor end up with an edge that the next split pass
430         // would just cut again? Then leave it: two passes undoing each other
431         // is how a remesh oscillates instead of converging.
432         let keep = m.pos[a as usize];
433         let too_long = m.tris_of(b).iter().any(|&t| {
434             m.tris[t]
435                 .iter()
436                 .any(|&q| q != b && q != a && (m.pos[q as usize] - keep).length() > long)
437         });
438         if too_long {
439             continue;
440         }
441         // Refuse a collapse that would flip a triangle over: if any triangle
442         // keeping both points would end up facing the other way, the surface
443         // would self-intersect where it used to be flat.
444         let folds = m.tris_of(b).iter().any(|&t| {
445             let tri = m.tris[t];
446             if tri.contains(&a) {
447                 return false;
448             }
449             let before = face_normal(m, tri);
450             let after_tri = tri.map(|q| if q == b { a } else { q });
451             let after = face_normal(m, after_tri);
452             before.dot(after) <= 0.0
453         });
454         if folds {
455             continue;
456         }
457         // Refuse a collapse that would strand a corner. The triangles on
458         // the edge fold to nothing, and each takes one triangle from its
459         // third corner: a corner left with fewer than three has no fan
460         // left to stand in — at two it is a fold, at none it is a point on
461         // no triangle, which `into_detail` drops. Until 2026-09-29 a point
462         // could be dropped that way with its identity, values and groups:
463         // a pulled point at the tip of a spike, its neighbours collapsing
464         // around it, went from the pull group with nothing to say so.
465         let strands = m.tris_of(b).iter().any(|&t| {
466             let tri = m.tris[t];
467             if !tri.contains(&a) {
468                 return false;
469             }
470             tri.iter().any(|&q| q != a && q != b && m.tris_of(q).len() < 4)
471         });
472         if strands {
473             continue;
474         }
475 
476         m.dead_point[b as usize] = true;
477         // The survivor stands for both: what the other end was in, it is
478         // in. A group is a set of places named downstream, and a collapse
479         // that dropped one lost what named it.
480         for (_, members) in m.groups.iter_mut() {
481             if members.get(b as usize).copied().unwrap_or(false) {
482                 if let Some(slot) = members.get_mut(a as usize) {
483                     *slot = true;
484                 }
485             }
486         }
487         for t in m.tris_of(b) {
488             if m.dead_tri[t] {
489                 continue;
490             }
491             if m.tris[t].contains(&a) {
492                 // The two triangles along the collapsed edge fold to nothing.
493                 m.dead_tri[t] = true;
494                 continue;
495             }
496             m.rewire(t, b, a);
497         }
498         done += 1;
499     }
500     done
501 }
502 
503 fn face_normal(m: &Mesh, tri: [u32; 3]) -> Vec3 {
504     let (a, b, c) = (
505         m.pos[tri[0] as usize],
506         m.pos[tri[1] as usize],
507         m.pos[tri[2] as usize],
508     );
509     (b - a).cross(c - a)
510 }
511 
512 /// Flip edges whose two triangles would be better shaped the other way.
513 ///
514 /// "Better" is total deviation from valence 6, which is the valence a regular
515 /// triangulation of a plane has — the measure the paper uses, and the one that
516 /// drives a mesh toward equilateral triangles.
517 fn flip_pass(m: &mut Mesh, target: f32) -> usize {
518     let long = target * 4.0 / 3.0;
519     // Each point's valence, counted once and kept in step with the flips.
520     // The first version asked `tris_of` for it — a list gathered, sorted
521     // and counted — eight times an edge, which at 525 points was nine
522     // milliseconds of a remesh that changed nothing.
523     let mut valence = vec![0i32; m.pos.len()];
524     for (t, tri) in m.tris.iter().enumerate() {
525         if m.dead_tri[t] {
526             continue;
527         }
528         for (i, &q) in tri.iter().enumerate() {
529             if !tri[..i].contains(&q) {
530                 valence[q as usize] += 1;
531             }
532         }
533     }
534     let mut done = 0;
535     for (e, _) in m.edges() {
536         let tris = m.tris_on_edge(e[0], e[1]);
537         if tris.len() != 2 {
538             continue; // a boundary edge has nothing to flip into
539         }
540         let (t0, t1) = (tris[0], tris[1]);
541         let Some(&o0) = m.tris[t0].iter().find(|q| !e.contains(q)) else { continue };
542         let Some(&o1) = m.tris[t1].iter().find(|q| !e.contains(q)) else { continue };
543         if o0 == o1 {
544             continue;
545         }
546         let val = |p: u32| valence[p as usize];
547         let dev = |v: i32| (v - 6).abs();
548         let before = dev(val(e[0])) + dev(val(e[1])) + dev(val(o0)) + dev(val(o1));
549         let after = dev(val(e[0]) - 1) + dev(val(e[1]) - 1) + dev(val(o0) + 1) + dev(val(o1) + 1);
550         if after >= before {
551             continue;
552         }
553         // Refuse a flip whose new edge the next split would cut. A long edge
554         // between two thin triangles is split, and the midpoint collapsed
555         // into a corner — which is that edge turned to its short diagonal —
556         // and until 2026-09-29 this pass, judging by valence alone, turned
557         // it back: three passes undoing one another on the same edges at
558         // every step of a mesh that had stopped moving.
559         if (m.pos[o1 as usize] - m.pos[o0 as usize]).length() > long {
560             continue;
561         }
562         let n0 = face_normal(m, m.tris[t0]);
563         let (new0, new1) = ([o0, e[0], o1], [o1, e[1], o0]);
564         if face_normal(m, new0).dot(n0) <= 0.0 || face_normal(m, new1).dot(n0) <= 0.0 {
565             continue;
566         }
567         m.tris[t0] = new0;
568         m.tris[t1] = new1;
569         for &q in new0.iter().chain(new1.iter()) {
570             m.p2t[q as usize].push(t0);
571             m.p2t[q as usize].push(t1);
572         }
573         valence[e[0] as usize] -= 1;
574         valence[e[1] as usize] -= 1;
575         valence[o0 as usize] += 1;
576         valence[o1 as usize] += 1;
577         done += 1;
578     }
579     done
580 }
581 
582 /// [`flip_pass`] as it was first written, kept to hold the faster one to.
583 #[cfg(test)]
584 fn flip_pass_reference(m: &mut Mesh, target: f32) -> usize {
585     let long = target * 4.0 / 3.0;
586     let mut done = 0;
587     for (e, _) in m.edges() {
588         // Looked up now rather than taken from the snapshot, for the same
589         // reason the split pass does: an earlier flip has rewired faces.
590         let tris = m.tris_on_edge(e[0], e[1]);
591         if tris.len() != 2 {
592             continue; // a boundary edge has nothing to flip into
593         }
594         let (t0, t1) = (tris[0], tris[1]);
595         let Some(&o0) = m.tris[t0].iter().find(|q| !e.contains(q)) else { continue };
596         let Some(&o1) = m.tris[t1].iter().find(|q| !e.contains(q)) else { continue };
597         if o0 == o1 {
598             continue;
599         }
600 
601         let val = |p: u32| m.tris_of(p).len() as i32;
602         let dev = |v: i32| (v - 6).abs();
603         let before = dev(val(e[0])) + dev(val(e[1])) + dev(val(o0)) + dev(val(o1));
604         // The flip moves one triangle off each endpoint and onto each opposite
605         // corner.
606         let after = dev(val(e[0]) - 1) + dev(val(e[1]) - 1) + dev(val(o0) + 1) + dev(val(o1) + 1);
607         if after >= before {
608             continue;
609         }
610         // Refuse a flip whose new edge the next split would cut. A long edge
611         // between two thin triangles is split, and the midpoint collapsed
612         // into a corner — which is that edge turned to its short diagonal —
613         // and until 2026-09-29 this pass, judging by valence alone, turned
614         // it back: three passes undoing one another on the same edges at
615         // every step of a mesh that had stopped moving.
616         if (m.pos[o1 as usize] - m.pos[o0 as usize]).length() > long {
617             continue;
618         }
619         // Refuse a flip that would fold either new triangle against the
620         // surface it came from.
621         let n0 = face_normal(m, m.tris[t0]);
622         let (new0, new1) = ([o0, e[0], o1], [o1, e[1], o0]);
623         if face_normal(m, new0).dot(n0) <= 0.0 || face_normal(m, new1).dot(n0) <= 0.0 {
624             continue;
625         }
626         // Rewiring both triangles wholesale, so the incidence is rebuilt for
627         // the corners that changed.
628         m.tris[t0] = new0;
629         m.tris[t1] = new1;
630         for &q in new0.iter().chain(new1.iter()) {
631             m.p2t[q as usize].push(t0);
632             m.p2t[q as usize].push(t1);
633         }
634         done += 1;
635     }
636     done
637 }
638 
639 /// Move each point toward the centroid of its neighbours, with the normal
640 /// component removed.
641 ///
642 /// Removing the normal component is what makes this a retriangulation rather
643 /// than a smooth: the points slide within the surface to even out the
644 /// triangles, and the shape they describe is left where it was.
645 fn relax_pass(m: &mut Mesh, amount: f32) {
646     if amount <= 0.0 {
647         return;
648     }
649     let mut nbrs: Vec<Vec<u32>> = vec![Vec::new(); m.pos.len()];
650     for (t, tri) in m.tris.iter().enumerate() {
651         if m.dead_tri[t] {
652             continue;
653         }
654         for i in 0..3 {
655             let (a, b) = (tri[i], tri[(i + 1) % 3]);
656             if !nbrs[a as usize].contains(&b) {
657                 nbrs[a as usize].push(b);
658             }
659             if !nbrs[b as usize].contains(&a) {
660                 nbrs[b as usize].push(a);
661             }
662         }
663     }
664     let mut normals: Vec<Vec3> = vec![Vec3::ZERO; m.pos.len()];
665     for (t, tri) in m.tris.iter().enumerate() {
666         if m.dead_tri[t] {
667             continue;
668         }
669         let n = face_normal(m, *tri);
670         for &p in tri {
671             normals[p as usize] += n;
672         }
673     }
674 
675     let before = m.pos.clone();
676     for p in 0..m.pos.len() {
677         if m.dead_point[p] || nbrs[p].is_empty() {
678             continue;
679         }
680         let centroid: Vec3 =
681             nbrs[p].iter().map(|&q| before[q as usize]).sum::<Vec3>() / nbrs[p].len() as f32;
682         let mut delta = (centroid - before[p]) * amount;
683         let n = normals[p].normalize_or_zero();
684         if n != Vec3::ZERO {
685             delta -= n * delta.dot(n);
686         }
687         m.pos[p] = before[p] + delta;
688     }
689 }
690 
691 /// Pull every point back onto the surface the remesh started from.
692 ///
693 /// Relaxation slides points within the surface, but "within" is only true to
694 /// first order: on anything curved the slide leaves the surface slightly, and
695 /// the error compounds. Without this a sphere remeshed for fifty iterations is
696 /// visibly smaller than the one it started as.
697 fn project_pass(m: &mut Mesh, rest: &crate::spatial::TriGrid, reference: bool) {
698     if rest.is_empty() {
699         return;
700     }
701     let _ = reference;
702     for p in 0..m.pos.len() {
703         if m.dead_point[p] {
704             continue;
705         }
706         #[cfg(test)]
707         let hit = if reference { rest.closest_reference(m.pos[p]) } else { rest.closest(m.pos[p]) };
708         #[cfg(not(test))]
709         let hit = rest.closest(m.pos[p]);
710         if let Some(hit) = hit {
711             m.pos[p] = hit.point;
712         }
713     }
714 }
715 
716 /// Subdivide every triangle into four, `depth` times.
717 ///
718 /// Distinct from remeshing, and deliberately so: this makes a predictable,
719 /// uniform refinement of the mesh it is given — every edge gets a midpoint,
720 /// every triangle becomes four, and the shape does not move. Remesh steers
721 /// toward a length and rearranges topology to get there; Subdivide multiplies
722 /// what is already there.
723 ///
724 /// It does NOT smooth, which the Houdini SOP of this name does. A subdivision
725 /// that also moved points would be two operations wearing one name, and the
726 /// smoothing one is already available as Remesh's relaxation.
727 ///
728 /// Attributes interpolate onto the midpoints, the same way a remesh split
729 /// does, so a field defined on a coarse mesh survives being refined.
730 pub fn subdivide(input: &Detail, depth: usize) -> Detail {
731     if input.num_prims() == 0 || depth == 0 {
732         return input.clone();
733     }
734     let mut m = Mesh::from_detail(input);
735     // Capped because this is exponential: each level is four times the
736     // triangles, so six levels is four thousand times the input and anything
737     // past that is a hang rather than a render.
738     for _ in 0..depth.min(6) {
739         let mut mids: HashMap<[u32; 2], u32> = HashMap::new();
740         for (e, _) in m.edges() {
741             let mid = m.split_point(e[0], e[1]);
742             mids.insert(e, mid);
743         }
744         let key = |a: u32, b: u32| [a.min(b), a.max(b)];
745         // Snapshotted, because the loop adds triangles as it goes and the new
746         // ones are already subdivided.
747         let count = m.tris.len();
748         for t in 0..count {
749             if m.dead_tri[t] {
750                 continue;
751             }
752             let tri = m.tris[t];
753             let (Some(&ab), Some(&bc), Some(&ca)) = (
754                 mids.get(&key(tri[0], tri[1])),
755                 mids.get(&key(tri[1], tri[2])),
756                 mids.get(&key(tri[2], tri[0])),
757             ) else {
758                 continue;
759             };
760             m.dead_tri[t] = true;
761             // Three corner triangles and the middle one, each keeping the
762             // original winding.
763             m.add_tri([tri[0], ab, ca]);
764             m.add_tri([ab, tri[1], bc]);
765             m.add_tri([ca, bc, tri[2]]);
766             m.add_tri([ab, bc, ca]);
767         }
768     }
769     m.into_detail()
770 }
771 
772 /// One pass of a kind, run on its own — what the Remesh subnet's pass
773 /// nodes are. A pass that changes nothing hands its input back as it came,
774 /// its primitives and every attribute untouched, as a remesh that changes
775 /// nothing does; one that changes something gives what a remesh would
776 /// after that pass, the same mesh bit for bit (which
777 /// `the_remesh_subnet_is_the_remesh` holds it to).
778 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
779 pub enum Pass {
780     Split,
781     Collapse,
782     Flip,
783 }
784 
785 /// Run one [`Pass`] toward `target` edge length.
786 pub fn edge_pass(input: &Detail, pass: Pass, target: f32) -> Detail {
787     if input.num_prims() == 0 || target <= 0.0 {
788         return input.clone();
789     }
790     let mut m = Mesh::from_detail(input);
791     let done = match pass {
792         Pass::Split => split_pass(&mut m, target),
793         Pass::Collapse => collapse_pass(&mut m, target),
794         Pass::Flip => flip_pass(&mut m, target),
795     };
796     EDGE_CHANGES.with(|c| c.set(c.get() + done));
797     if done == 0 {
798         return input.clone();
799     }
800     m.into_detail()
801 }
802 
803 /// The remesh's relaxation, `iterations` times: each point toward the
804 /// centroid of its neighbours by `amount`, less the part along its normal.
805 /// Only positions move, so everything else the input carries — polygons,
806 /// primitive and vertex attributes — comes through untouched.
807 pub fn relax_tangential(input: &Detail, amount: f32, iterations: usize) -> Detail {
808     let amount = amount.clamp(0.0, 1.0);
809     if input.num_prims() == 0 || amount <= 0.0 || iterations == 0 {
810         return input.clone();
811     }
812     let mut m = Mesh::from_detail(input);
813     for _ in 0..iterations {
814         relax_pass(&mut m, amount);
815     }
816     let mut out = input.clone();
817     for (p, q) in m.pos.iter().enumerate() {
818         out.set_pos(p, *q);
819     }
820     out
821 }
822 
823 thread_local! {
824     /// The last surface projected onto, by a hash of it: a Remesh subnet
825     /// projects onto one surface every iteration, and the native remesh
826     /// built the grid once for all of them.
827     static PROJECT_GRID: std::cell::RefCell<Option<(u64, std::rc::Rc<crate::spatial::TriGrid>)>> = const { std::cell::RefCell::new(None) };
828 }
829 
830 fn surface_hash(d: &Detail) -> u64 {
831     use std::hash::{Hash, Hasher};
832     let mut h = std::collections::hash_map::DefaultHasher::new();
833     d.num_points().hash(&mut h);
834     for p in d.positions() {
835         for c in p {
836             c.to_bits().hash(&mut h);
837         }
838     }
839     d.num_prims().hash(&mut h);
840     for prim in 0..d.num_prims() {
841         d.prim_points(prim).hash(&mut h);
842     }
843     h.finish()
844 }
845 
846 /// Every point of `input` moved to the nearest place on `surface`: the
847 /// remesh's projection, which keeps a relaxed mesh from creeping off the
848 /// shape it started as. A mesh that IS the surface is handed back as it
849 /// came, since projecting a surface onto itself moves nothing — which is
850 /// what lets a Remesh subnet that finds nothing to do return its input
851 /// untouched, as the native remesh does.
852 pub fn project_onto(input: &Detail, surface: &Detail) -> Detail {
853     if surface.num_prims() == 0 || input.num_points() == 0 || input == surface {
854         return input.clone();
855     }
856     let key = surface_hash(surface);
857     let grid = PROJECT_GRID.with(|g| {
858         let mut g = g.borrow_mut();
859         match g.as_ref() {
860             Some((k, grid)) if *k == key => grid.clone(),
861             _ => {
862                 let grid = std::rc::Rc::new(crate::spatial::TriGrid::build(surface));
863                 *g = Some((key, grid.clone()));
864                 grid
865             }
866         }
867     });
868     let mut out = input.clone();
869     if grid.is_empty() {
870         return out;
871     }
872     for p in 0..out.num_points() {
873         if let Some(hit) = grid.closest(out.pos(p)) {
874             out.set_pos(p, hit.point);
875         }
876     }
877     out
878 }
879 
880 /// Remesh toward `settings.target` edge length.
881 pub fn remesh(input: &Detail, settings: Settings) -> Detail {
882     remesh_by(input, settings, false)
883 }
884 
885 /// [`remesh`] by the passes as they were first written: the same mesh, bit
886 /// for bit, which is what makes the faster ones optimizations.
887 #[cfg(test)]
888 pub fn remesh_reference(input: &Detail, settings: Settings) -> Detail {
889     remesh_by(input, settings, true)
890 }
891 
892 thread_local! {
893     /// How many edges the last remesh on this thread split, collapsed or
894     /// flipped: what a test and the profile read to see a mesh settle.
895     static LAST_CHANGES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
896 }
897 
898 /// The edges the last [`remesh`] on this thread split, collapsed or flipped.
899 pub fn last_changes() -> usize {
900     LAST_CHANGES.with(|c| c.get())
901 }
902 
903 thread_local! {
904     /// Every edge split, collapsed or flipped on this thread since the last
905     /// [`take_edge_changes`], by a remesh or a pass node: what a profile
906     /// reads per frame, the Remesh subnet running as several passes where
907     /// [`last_changes`] sees one remesh.
908     static EDGE_CHANGES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
909 }
910 
911 /// The edges changed on this thread since the last call, and start again.
912 pub fn take_edge_changes() -> usize {
913     EDGE_CHANGES.with(|c| c.replace(0))
914 }
915 
916 fn remesh_by(input: &Detail, settings: Settings, reference: bool) -> Detail {
917     if input.num_prims() == 0 || settings.target <= 0.0 {
918         return input.clone();
919     }
920     // Built once from the INPUT and reused by every iteration: projecting onto
921     // the previous iteration's surface would chase the creep rather than
922     // correct it, since each iteration's drift would become the next one's
923     // idea of where the surface is.
924     let mut rest: Option<crate::spatial::TriGrid> = None;
925     let mut m = Mesh::from_detail(input);
926     let mut changed = 0;
927     LAST_CHANGES.with(|c| c.set(0));
928     for iteration in 0..settings.iterations.min(20) {
929         let mut done = 0;
930         if settings.split {
931             done += split_pass(&mut m, settings.target);
932         }
933         if settings.collapse {
934             done += collapse_pass(&mut m, settings.target);
935         }
936         if settings.flip {
937             #[cfg(test)]
938             if reference {
939                 done += flip_pass_reference(&mut m, settings.target);
940             } else {
941                 done += flip_pass(&mut m, settings.target);
942             }
943             #[cfg(not(test))]
944             {
945                 done += flip_pass(&mut m, settings.target);
946             }
947         }
948         // Nothing to split, collapse or flip, and nothing relaxed: the mesh
949         // is as this iteration found it, and every later one would find it
950         // the same. On the FIRST that is the input itself, handed back as it
951         // came — its primitives, their order and its polygons untouched, so
952         // what is kept by a mesh's topology downstream (the detangle's
953         // lists) is kept across a step that remeshed nothing.
954         if done == 0 && settings.relax <= 0.0 {
955             if iteration == 0 {
956                 return input.clone();
957             }
958             break;
959         }
960         changed += done;
961         relax_pass(&mut m, settings.relax.clamp(0.0, 1.0));
962         if settings.project {
963             // Built when first wanted: a remesh that finds nothing to do
964             // never asks.
965             let rest = rest.get_or_insert_with(|| crate::spatial::TriGrid::build(input));
966             project_pass(&mut m, rest, reference);
967         }
968     }
969     LAST_CHANGES.with(|c| c.set(changed));
970     EDGE_CHANGES.with(|c| c.set(c.get() + changed));
971     m.into_detail()
972 }