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 }