graphic design tool
git clone https://git.lucas.co/cce-designer.git
src/detangle.rs (66.4K)
1 //! The Detangle node's solve: push a surface off itself.
2 //!
3 //! The ALGORITHM is `geometry::apply_detangle`'s, unchanged, and what it
4 //! does and why is said there. This module is how it is run: what it costs
5 //! is a function of four things the first version paid for every step of a
6 //! simulation, and none of which a step needs to pay for.
7 //!
8 //! - **The topology is built once.** The edge list and each point's excluded
9 //! neighbourhood are connectivity, and a chain of pull, relax and detangle
10 //! never changes connectivity — but every step arrives as a fresh
11 //! `Detail`, whose derived topology is deliberately not cloned. They are
12 //! kept here by a hash of the primitives ([`Topo`]), so a solve of a
13 //! hundred steps builds them on the first.
14 //! - **A pass that separates nothing ends the solve.** The passes gather
15 //! against the positions at their start; if one moves nothing, the next
16 //! starts from the same positions and finds the same nothing.
17 //! - **The grid is built once and reused** while the points stay near where
18 //! it filed them, in a flat array sorted by cell rather than a vector per
19 //! cell. A point that has moved is still found: the search reaches as far
20 //! as anything has moved since the grid was built.
21 //! - **A point that cannot move is not searched for.** With a Group, what
22 //! would push the others was worked out and thrown away.
23 //!
24 //! The results are the first version's BIT FOR BIT, which is what lets these
25 //! be optimizations and not changes: the same pairs, summed in the same
26 //! order. `the_detangle_solve_matches_its_reference` holds them together.
27 //!
28 //! That is the node's `Points` method. Two things here are NOT the first
29 //! version's, and are asked for by name:
30 //!
31 //! - **The `Surface` method** ([`solve_surface`]) tests each point against
32 //! the TRIANGLES near it, where Points tests it against points. A point
33 //! over the middle of a triangle is near no corner of it, so on a mesh
34 //! whose triangles are larger than the thickness the point test sees
35 //! nothing at all. Told where the points were when the step began
36 //! ([`apply_from`]), it also knows which side of a triangle a point
37 //! belongs on, and can hold a step to a length.
38 //! - **The measure** ([`self_intersections`]): every edge that passes
39 //! through a triangle. It is what says whether a change to the solve
40 //! helped, and what the node's `Tangled Group` is written from.
41
42 use crate::app::FsNode;
43 use crate::detail::Detail;
44 use crate::geometry::{node_param_f32, node_param_str};
45 use glam::Vec3;
46 use std::cell::RefCell;
47 use std::rc::Rc;
48
49 /// What a mesh's connectivity gives the solve, for one ring count.
50 struct Topo {
51 key: u64,
52 rings: usize,
53 edges: Vec<[u32; 2]>,
54 /// The primitives as triangles, fanned as `Detail::triangulate` fans
55 /// them, and the primitive each came from.
56 tris: Vec<[u32; 3]>,
57 tri_prims: Vec<u32>,
58 /// Every side of every triangle, once, and each triangle's three. The
59 /// mesh's edges and the diagonals a fan cuts across a quad: what the
60 /// triangles are made of, which is what can pass through itself.
61 sides: Vec<[u32; 2]>,
62 tri_sides: Vec<[u32; 3]>,
63 /// The triangles at each point, and the sides: point `p`'s are
64 /// `at_tris[tri_starts[p]..tri_starts[p + 1]]`, and likewise.
65 tri_starts: Vec<u32>,
66 at_tris: Vec<u32>,
67 side_starts: Vec<u32>,
68 at_sides: Vec<u32>,
69 /// Each point's excluded neighbourhood, itself included, ascending:
70 /// point `p`'s is `excluded[starts[p]..starts[p + 1]]`.
71 starts: Vec<u32>,
72 excluded: Vec<u32>,
73 }
74
75 impl Topo {
76 fn build(geom: &Detail, key: u64, rings: usize) -> Topo {
77 let n = geom.num_points();
78 let mut starts = Vec::with_capacity(n + 1);
79 let mut excluded = Vec::new();
80 let mut seen: Vec<u32> = Vec::new();
81 let mut frontier: Vec<u32> = Vec::new();
82 let mut next: Vec<u32> = Vec::new();
83 // One mark per point instead of a search of `seen` per neighbour.
84 let mut mark = vec![u32::MAX; n];
85 for p in 0..n {
86 starts.push(excluded.len() as u32);
87 seen.clear();
88 frontier.clear();
89 seen.push(p as u32);
90 frontier.push(p as u32);
91 mark[p] = p as u32;
92 for _ in 0..rings {
93 next.clear();
94 for &q in &frontier {
95 for &r in geom.point_neighbours(q as usize) {
96 if mark[r as usize] != p as u32 {
97 mark[r as usize] = p as u32;
98 seen.push(r);
99 next.push(r);
100 }
101 }
102 }
103 if next.is_empty() {
104 break;
105 }
106 std::mem::swap(&mut frontier, &mut next);
107 }
108 seen.sort_unstable();
109 excluded.extend_from_slice(&seen);
110 }
111 starts.push(excluded.len() as u32);
112 let mut tris = Vec::new();
113 let mut tri_prims = Vec::new();
114 for prim in 0..geom.num_prims() {
115 let pts = geom.prim_points(prim);
116 for i in 1..pts.len().saturating_sub(1) {
117 tris.push([pts[0], pts[i], pts[i + 1]]);
118 tri_prims.push(prim as u32);
119 }
120 }
121 let mut sides: Vec<[u32; 2]> = Vec::new();
122 let mut side_of: std::collections::HashMap<[u32; 2], u32> = std::collections::HashMap::new();
123 let tri_sides = tris
124 .iter()
125 .map(|t| {
126 [[t[0], t[1]], [t[1], t[2]], [t[2], t[0]]].map(|[a, b]| {
127 let ends = [a.min(b), a.max(b)];
128 *side_of.entry(ends).or_insert_with(|| {
129 sides.push(ends);
130 sides.len() as u32 - 1
131 })
132 })
133 })
134 .collect();
135 let tri_sides: Vec<[u32; 3]> = tri_sides;
136 let (tri_starts, at_tris) = by_point(n, tris.iter().map(|t| &t[..]));
137 let (side_starts, at_sides) = by_point(n, sides.iter().map(|e| &e[..]));
138 Topo { key, rings, edges: geom.edges().to_vec(), tris, tri_prims, sides, tri_sides, tri_starts, at_tris, side_starts, at_sides, starts, excluded }
139 }
140
141 fn own(&self, p: usize) -> &[u32] {
142 &self.excluded[self.starts[p] as usize..self.starts[p + 1] as usize]
143 }
144
145 fn tris_at(&self, p: usize) -> &[u32] {
146 &self.at_tris[self.tri_starts[p] as usize..self.tri_starts[p + 1] as usize]
147 }
148
149 fn sides_at(&self, p: usize) -> &[u32] {
150 &self.at_sides[self.side_starts[p] as usize..self.side_starts[p + 1] as usize]
151 }
152
153 /// Whether `q`, or a point a side away from it, is stirred: the other
154 /// end of a pair reaches one ring past the rings.
155 fn neighbours_stirred(&self, q: u32, stirred: &[bool]) -> bool {
156 stirred[q as usize] || self.sides_at(q as usize).iter().any(|&j| self.sides[j as usize].iter().any(|&r| stirred[r as usize]))
157 }
158
159 fn excludes(&self, p: usize, q: u32) -> bool {
160 let (a, b) = (self.starts[p] as usize, self.starts[p + 1] as usize);
161 self.excluded[a..b].binary_search(&q).is_ok()
162 }
163 }
164
165 /// Which of `items` each point is part of, flat: point `p`'s are
166 /// `ids[starts[p]..starts[p + 1]]`, ascending.
167 fn by_point<'a>(n: usize, items: impl Iterator<Item = &'a [u32]> + Clone) -> (Vec<u32>, Vec<u32>) {
168 let mut starts = vec![0u32; n + 1];
169 for item in items.clone() {
170 for &p in item {
171 starts[p as usize + 1] += 1;
172 }
173 }
174 for p in 0..n {
175 starts[p + 1] += starts[p];
176 }
177 let mut next = starts.clone();
178 let mut ids = vec![0u32; starts[n] as usize];
179 for (i, item) in items.enumerate() {
180 for &p in item {
181 ids[next[p as usize] as usize] = i as u32;
182 next[p as usize] += 1;
183 }
184 }
185 (starts, ids)
186 }
187
188 /// How many topologies are kept: a project has a few detangles at most, and
189 /// each is asked for in turn as a frame's graph is walked.
190 const KEPT: usize = 4;
191
192 thread_local! {
193 static TOPOS: RefCell<Vec<Rc<Topo>>> = const { RefCell::new(Vec::new()) };
194 }
195
196 /// The connectivity's identity: the point count and every primitive's
197 /// points, in order. Positions are not in it.
198 fn topology_key(geom: &Detail) -> u64 {
199 // FNV-1a over the indices: a few thousand words a step, and nothing
200 // here needs a keyed hash.
201 let mut h: u64 = 0xcbf29ce484222325;
202 let mut eat = |v: u32| {
203 for b in v.to_le_bytes() {
204 h ^= b as u64;
205 h = h.wrapping_mul(0x100000001b3);
206 }
207 };
208 eat(geom.num_points() as u32);
209 eat(geom.num_prims() as u32);
210 for prim in 0..geom.num_prims() {
211 let points = geom.prim_points(prim);
212 eat(points.len() as u32);
213 for &p in points {
214 eat(p);
215 }
216 }
217 h
218 }
219
220 fn topo_for(geom: &Detail, rings: usize) -> Rc<Topo> {
221 let key = topology_key(geom);
222 TOPOS.with(|kept| {
223 let mut kept = kept.borrow_mut();
224 if let Some(i) = kept.iter().position(|t| t.key == key && t.rings == rings && t.starts.len() == geom.num_points() + 1) {
225 // Most recent last, so the one dropped is the one longest unused.
226 let hit = kept.remove(i);
227 kept.push(hit.clone());
228 return hit;
229 }
230 let built = Rc::new(Topo::build(geom, key, rings));
231 if kept.len() >= KEPT {
232 kept.remove(0);
233 }
234 kept.push(built.clone());
235 built
236 })
237 }
238
239 /// How many topologies are kept right now — for the tests.
240 #[cfg(test)]
241 pub(crate) fn kept_topologies() -> usize {
242 TOPOS.with(|k| k.borrow().len())
243 }
244
245 /// Points filed by cell, flat: `ids[starts[c]..starts[c + 1]]` are the
246 /// points the grid filed in cell `c`, ascending.
247 struct FlatGrid {
248 min: Vec3,
249 cell: f32,
250 dims: [i32; 3],
251 starts: Vec<u32>,
252 ids: Vec<u32>,
253 /// Where each point was when it was filed.
254 filed: Vec<Vec3>,
255 }
256
257 impl FlatGrid {
258 fn build(points: &[Vec3], cell: f32) -> FlatGrid {
259 let (min, max) = points.iter().fold(
260 (Vec3::splat(f32::MAX), Vec3::splat(f32::MIN)),
261 |(lo, hi), &p| (lo.min(p), hi.max(p)),
262 );
263 let (min, max) = if points.is_empty() { (Vec3::ZERO, Vec3::ZERO) } else { (min, max) };
264 // The shape `spatial::Grid` gives itself: cells about `cell` across,
265 // capped so a pathological request cannot ask for a billion of them.
266 let span = (max - min).max(Vec3::splat(1e-6));
267 let cell = cell.max(span.max_element() / 128.0).max(1e-6);
268 let dims = [
269 ((span.x / cell).ceil() as i32 + 1).clamp(1, 256),
270 ((span.y / cell).ceil() as i32 + 1).clamp(1, 256),
271 ((span.z / cell).ceil() as i32 + 1).clamp(1, 256),
272 ];
273 let mut grid = FlatGrid { min, cell, dims, starts: Vec::new(), ids: Vec::new(), filed: points.to_vec() };
274 let cells = (dims[0] * dims[1] * dims[2]) as usize;
275 // A counting sort: count, prefix-sum, place. Placing in point order
276 // leaves each cell's ids ascending.
277 let of: Vec<u32> = points.iter().map(|&p| grid.index(grid.coord(p)) as u32).collect();
278 let mut starts = vec![0u32; cells + 1];
279 for &c in &of {
280 starts[c as usize + 1] += 1;
281 }
282 for c in 0..cells {
283 starts[c + 1] += starts[c];
284 }
285 let mut next = starts.clone();
286 let mut ids = vec![0u32; points.len()];
287 for (i, &c) in of.iter().enumerate() {
288 ids[next[c as usize] as usize] = i as u32;
289 next[c as usize] += 1;
290 }
291 grid.starts = starts;
292 grid.ids = ids;
293 grid
294 }
295
296 fn coord(&self, p: Vec3) -> [i32; 3] {
297 let rel = (p - self.min) / self.cell;
298 [
299 (rel.x.floor() as i32).clamp(0, self.dims[0] - 1),
300 (rel.y.floor() as i32).clamp(0, self.dims[1] - 1),
301 (rel.z.floor() as i32).clamp(0, self.dims[2] - 1),
302 ]
303 }
304
305 fn index(&self, c: [i32; 3]) -> usize {
306 ((c[2] * self.dims[1] + c[1]) * self.dims[0] + c[0]) as usize
307 }
308
309 /// Every point filed in a cell the box about `p` touches, ascending.
310 fn gather(&self, p: Vec3, reach: f32, out: &mut Vec<u32>) {
311 out.clear();
312 let (a, b) = (self.coord(p - Vec3::splat(reach)), self.coord(p + Vec3::splat(reach)));
313 for z in a[2]..=b[2] {
314 for y in a[1]..=b[1] {
315 for x in a[0]..=b[0] {
316 let c = self.index([x, y, z]);
317 out.extend_from_slice(&self.ids[self.starts[c] as usize..self.starts[c + 1] as usize]);
318 }
319 }
320 }
321 // Cells are disjoint, so there is nothing to deduplicate; the order
322 // is what the sum over a point's pairs is taken in.
323 out.sort_unstable();
324 }
325
326 /// How far the furthest point has come from where it was filed.
327 fn drift(&self, points: &[Vec3]) -> f32 {
328 points.iter().zip(&self.filed).map(|(p, f)| (*p - *f).length_squared()).fold(0.0f32, f32::max).sqrt()
329 }
330 }
331
332 /// How far points may drift from where the grid filed them, in cells,
333 /// before it is built again. The search reaches a drift further than the
334 /// thickness, so a larger allowance trades rebuilds for wider searches.
335 const DRIFT_CELLS: f32 = 0.5;
336
337 /// What a solve did, for the tests and the timing.
338 #[derive(Debug, Default, Clone, Copy, PartialEq)]
339 pub struct Work {
340 pub passes: usize,
341 pub grids: usize,
342 pub searched: usize,
343 /// Point-triangle contacts the Surface method resolved, over every pass.
344 pub contacts: usize,
345 /// Edges tested against the edges near them, over every pass: the
346 /// ones with an end near something that is not their own neighbourhood.
347 pub edges_searched: usize,
348 /// Of those contacts, the ones between two EDGES.
349 pub edge_contacts: usize,
350 /// Of those contacts, the ones resolved as having gone THROUGH since
351 /// the step began — a point through a triangle, an edge through an
352 /// edge — and put back on the side they came from.
353 pub crossed: usize,
354 /// Pairs inside each other's rings found gone through each other when
355 /// the solve began: folds.
356 pub folds: usize,
357 /// Points put back where the step began, being still through a
358 /// triangle when the passes were done, or a corner of one.
359 pub held: usize,
360 /// Points whose move since the step began was cut to the Step Limit.
361 pub limited: usize,
362 /// Edges passing through a triangle when the solve was done — counted
363 /// only when the node names a Tangled Group to write them to.
364 pub crossings: usize,
365 }
366
367 pub fn apply(geom: &mut Detail, target: &FsNode) -> Work {
368 apply_from(geom, None, target)
369 }
370
371 /// [`apply`], told where the points were when the step began: inside a
372 /// simnet, the state the substep consumed. It is what the Surface method
373 /// knows a point's SIDE from, and what the Step Limit is measured against.
374 /// A `before` that is not this mesh — another point count, other
375 /// primitives — is no memory of it and is not used.
376 pub fn apply_from(geom: &mut Detail, before: Option<&Detail>, target: &FsNode) -> Work {
377 let mut work = Work::default();
378 let n = geom.num_points();
379 if n == 0 || geom.num_prims() == 0 {
380 return work;
381 }
382 let rings = node_param_f32(target, "rings", 2.0).clamp(0.0, 6.0) as usize;
383 let topo = topo_for(geom, rings);
384 if topo.edges.is_empty() {
385 return work;
386 }
387 // A node without the row is one from before it, and solves as it did.
388 if node_param_str(target, "method", "Points").trim().eq_ignore_ascii_case("Surface") {
389 let before: Option<Vec<Vec3>> = before
390 .filter(|b| b.num_points() == n && b.num_prims() == geom.num_prims() && topology_key(b) == topo.key)
391 .map(|b| (0..n).map(|p| b.pos(p)).collect());
392 solve_surface(geom, before.as_deref(), target, &topo, &mut work);
393 } else {
394 solve_points(geom, target, &topo, &mut work);
395 }
396 // What is STILL crossed once the solve is done, which is the part worth
397 // looking at. Only when asked for: it is a second search of the mesh.
398 let mark = node_param_str(target, "tangled_group", "");
399 let mark = mark.trim();
400 if !mark.is_empty() {
401 let found = intersections_of(geom, &topo, false);
402 work.crossings = found.crossings;
403 geom.points_mut().create_group(mark);
404 for p in found.points {
405 geom.points_mut().add_to_group(mark, p as usize);
406 }
407 }
408 work
409 }
410
411 /// The node's thickness as a length: the setting is in EDGE LENGTHS, so it
412 /// means the same thing before and after a remesh.
413 fn thickness_of(geom: &Detail, target: &FsNode, topo: &Topo) -> f32 {
414 let mean_edge = mean_edge(geom, topo);
415 node_param_f32(target, "thickness", 1.0).max(0.0) * mean_edge
416 }
417
418 fn mean_edge(geom: &Detail, topo: &Topo) -> f32 {
419 topo.edges
420 .iter()
421 .map(|e| (geom.pos(e[1] as usize) - geom.pos(e[0] as usize)).length())
422 .sum::<f32>()
423 / topo.edges.len() as f32
424 }
425
426 /// The Points method: the first version's solve.
427 fn solve_points(geom: &mut Detail, target: &FsNode, topo: &Topo, work: &mut Work) {
428 let n = geom.num_points();
429 let thickness = thickness_of(geom, target, topo);
430 if thickness <= 0.0 {
431 return;
432 }
433 let iterations = node_param_f32(target, "iterations", 4.0).clamp(1.0, 32.0) as usize;
434 let group = node_param_str(target, "group", "");
435 let group = group.trim().to_string();
436 let movable: Vec<bool> = (0..n).map(|p| group.is_empty() || geom.points().in_group(&group, p)).collect();
437
438 let mut pos: Vec<Vec3> = (0..n).map(|p| geom.pos(p)).collect();
439 let mut grid = FlatGrid::build(&pos, thickness);
440 work.grids += 1;
441 let mut drift = 0.0f32;
442 let mut near = Vec::new();
443 let mut push = vec![Vec3::ZERO; n];
444 let mut moved_at_all = false;
445 for _ in 0..iterations {
446 work.passes += 1;
447 if drift > DRIFT_CELLS * grid.cell {
448 grid = FlatGrid::build(&pos, thickness);
449 work.grids += 1;
450 drift = 0.0;
451 }
452 // Gathered against the positions at the START of the pass and
453 // applied at the end, so the result does not depend on the order
454 // points are visited in.
455 let mut any = false;
456 for p in 0..n {
457 push[p] = Vec3::ZERO;
458 if !movable[p] {
459 continue;
460 }
461 work.searched += 1;
462 // Anything within a thickness of here now was filed within a
463 // thickness and a drift of here.
464 grid.gather(pos[p], thickness + drift, &mut near);
465 for &q in &near {
466 let qi = q as usize;
467 if qi == p || topo.excludes(p, q) {
468 continue;
469 }
470 let d = pos[p] - pos[qi];
471 let len = d.length();
472 if len >= thickness {
473 continue;
474 }
475 // Two coincident points have no direction to separate along;
476 // nudging along an arbitrary axis at least breaks the tie.
477 let dir = if len < 1e-9 {
478 Vec3::new((p % 7) as f32 - 3.0, (p % 5) as f32 - 2.0, 1.0).normalize_or_zero()
479 } else {
480 d / len
481 };
482 push[p] += dir * ((thickness - len) * 0.5);
483 any = true;
484 }
485 }
486 if !any {
487 // Nothing is within a thickness of anything it may push: the
488 // next pass would start from these positions and find the same.
489 break;
490 }
491 for p in 0..n {
492 if movable[p] {
493 pos[p] += push[p];
494 }
495 }
496 moved_at_all = true;
497 drift = grid.drift(&pos);
498 }
499 if moved_at_all {
500 for (p, v) in pos.iter().enumerate() {
501 geom.set_pos(p, *v);
502 }
503 }
504 }
505
506 /// Triangles filed by cell, flat, as [`FlatGrid`] files points: a triangle
507 /// is in every cell its bounding box touched when it was filed.
508 struct TriCells {
509 min: Vec3,
510 cell: f32,
511 dims: [i32; 3],
512 starts: Vec<u32>,
513 ids: Vec<u32>,
514 }
515
516 impl TriCells {
517 fn build(points: &[Vec3], tris: &[[u32; 3]], cell: f32) -> TriCells {
518 let (min, max) = points.iter().fold(
519 (Vec3::splat(f32::MAX), Vec3::splat(f32::MIN)),
520 |(lo, hi), &p| (lo.min(p), hi.max(p)),
521 );
522 let (min, max) = if points.is_empty() { (Vec3::ZERO, Vec3::ZERO) } else { (min, max) };
523 let span = (max - min).max(Vec3::splat(1e-6));
524 let cell = cell.max(span.max_element() / 128.0).max(1e-6);
525 let dims = [
526 ((span.x / cell).ceil() as i32 + 1).clamp(1, 256),
527 ((span.y / cell).ceil() as i32 + 1).clamp(1, 256),
528 ((span.z / cell).ceil() as i32 + 1).clamp(1, 256),
529 ];
530 let mut grid = TriCells { min, cell, dims, starts: Vec::new(), ids: Vec::new() };
531 let cells = (dims[0] * dims[1] * dims[2]) as usize;
532 let boxes: Vec<([i32; 3], [i32; 3])> = tris
533 .iter()
534 .map(|t| {
535 let [a, b, c] = t.map(|i| points[i as usize]);
536 (grid.coord(a.min(b).min(c)), grid.coord(a.max(b).max(c)))
537 })
538 .collect();
539 // The counting sort again, a triangle counted once per cell it is
540 // in. Placing in triangle order leaves each cell's ids ascending.
541 let mut starts = vec![0u32; cells + 1];
542 for (a, b) in &boxes {
543 for z in a[2]..=b[2] {
544 for y in a[1]..=b[1] {
545 for x in a[0]..=b[0] {
546 starts[grid.index([x, y, z]) + 1] += 1;
547 }
548 }
549 }
550 }
551 for c in 0..cells {
552 starts[c + 1] += starts[c];
553 }
554 let mut next = starts.clone();
555 let mut ids = vec![0u32; starts[cells] as usize];
556 for (i, (a, b)) in boxes.iter().enumerate() {
557 for z in a[2]..=b[2] {
558 for y in a[1]..=b[1] {
559 for x in a[0]..=b[0] {
560 let c = grid.index([x, y, z]);
561 ids[next[c] as usize] = i as u32;
562 next[c] += 1;
563 }
564 }
565 }
566 }
567 grid.starts = starts;
568 grid.ids = ids;
569 grid
570 }
571
572 fn coord(&self, p: Vec3) -> [i32; 3] {
573 let rel = (p - self.min) / self.cell;
574 [
575 (rel.x.floor() as i32).clamp(0, self.dims[0] - 1),
576 (rel.y.floor() as i32).clamp(0, self.dims[1] - 1),
577 (rel.z.floor() as i32).clamp(0, self.dims[2] - 1),
578 ]
579 }
580
581 fn index(&self, c: [i32; 3]) -> usize {
582 ((c[2] * self.dims[1] + c[1]) * self.dims[0] + c[0]) as usize
583 }
584
585 /// Every triangle filed in a cell the box touches, once, in the order
586 /// the cells are walked. `seen` is one mark per triangle and `stamp`
587 /// this gather's, which is cheaper than sorting what came back to find
588 /// the triangles that came back twice.
589 fn gather(&self, lo: Vec3, hi: Vec3, seen: &mut [u32], stamp: u32, out: &mut Vec<u32>) {
590 out.clear();
591 let (a, b) = (self.coord(lo), self.coord(hi));
592 for z in a[2]..=b[2] {
593 for y in a[1]..=b[1] {
594 for x in a[0]..=b[0] {
595 let c = self.index([x, y, z]);
596 for &t in &self.ids[self.starts[c] as usize..self.starts[c + 1] as usize] {
597 if seen[t as usize] != stamp {
598 seen[t as usize] = stamp;
599 out.push(t);
600 }
601 }
602 }
603 }
604 }
605 }
606 }
607
608 /// The Surface method: each point against the triangles near it.
609 ///
610 /// A contact is a point closer than the thickness to a triangle none of
611 /// whose corners is in the point's excluded rings. It is resolved along the
612 /// line from the closest point on the triangle to the point — the triangle's
613 /// own normal where the point lies ON it, which is a direction, where two
614 /// coincident points have none — and the move is SHARED: the point takes
615 /// its part one way and the triangle's corners theirs the other, each corner
616 /// by how much of the closest point it is. What cannot move (outside the
617 /// Group) takes none and the rest take all of it, so a contact with a fixed
618 /// triangle is resolved whole, where Points resolves half of it.
619 ///
620 /// A pass is gathered against the positions at its start and applied at its
621 /// end, as Points is. What a point receives from several contacts is their
622 /// AVERAGE, weighted by how deep each is: a point over a shared edge is in
623 /// contact with both triangles and must move once, not twice, and a sum is
624 /// what makes a dense contact overshoot and ring.
625 ///
626 /// **With `before`, a point has a side.** Distance alone cannot tell a
627 /// point that is near a triangle from one that has gone through it, and
628 /// pushes the second further through. Where the point was on one side of a
629 /// triangle when the step began and is on the other now, having passed
630 /// through the triangle's own extent ([`went_through`]), the contact is
631 /// resolved along the triangle's normal back to the side it came from, to a
632 /// thickness clear of it. A point with such a contact takes no other in
633 /// that pass: the triangles beside the one it went through see it near and
634 /// on the wrong side, and would push it on.
635 ///
636 /// The memory is one step long. A point the passes did not bring back is,
637 /// to the next step, a point that began on that side.
638 ///
639 /// **Edges meet edges.** Two edges can pass through each other with no
640 /// point of either going through any triangle, and then every test above
641 /// reports nothing while the measure counts a crossing. With Edge Contact
642 /// on, each side of each triangle is tested against the sides near it
643 /// that share no neighbourhood with it: where the two are nearest at a
644 /// place INSIDE both — an end is a point, and a point near an edge is near
645 /// the edge's triangle, which is the test above — they are parted along
646 /// the line between those places, the four ends sharing the move by how
647 /// near each is to it. Told where the step began, two edges that have
648 /// passed through each other ([`edges_went_through`]) are put back, and
649 /// held if the passes leave them through.
650 ///
651 /// **The Step Limit** cuts each point's move since the step began to that
652 /// many thicknesses before anything is resolved, so what arrives here is
653 /// close to what left and a contact is met while it is still a contact.
654 fn solve_surface(geom: &mut Detail, before: Option<&[Vec3]>, target: &FsNode, topo: &Topo, work: &mut Work) {
655 let n = geom.num_points();
656 let thickness = thickness_of(geom, target, topo);
657 if thickness <= 0.0 || topo.tris.is_empty() {
658 return;
659 }
660 let iterations = node_param_f32(target, "iterations", 4.0).clamp(1.0, 32.0) as usize;
661 // A node without the row is one from before it: points and triangles.
662 let edges_too = crate::geometry::node_param_bool(target, "edge_contact", false);
663 let group = node_param_str(target, "group", "");
664 let group = group.trim().to_string();
665 let movable: Vec<bool> = (0..n).map(|p| group.is_empty() || geom.points().in_group(&group, p)).collect();
666 let free = |p: usize| if movable[p] { 1.0f32 } else { 0.0 };
667
668 let mut pos: Vec<Vec3> = (0..n).map(|p| geom.pos(p)).collect();
669 let mut moved_at_all = false;
670 // A node without the row is one from before it, and is not limited.
671 let limit = node_param_f32(target, "step_limit", 0.0).max(0.0) * thickness;
672 if let (Some(before), true) = (before, limit > 0.0) {
673 for p in (0..n).filter(|&p| movable[p]) {
674 let d = pos[p] - before[p];
675 let len = d.length();
676 if len > limit {
677 pos[p] = before[p] + d * (limit / len);
678 work.limited += 1;
679 moved_at_all = true;
680 }
681 }
682 }
683
684 let looking = Looking { thickness, cell: thickness.max(mean_edge(geom, topo)), edges_too };
685 let mut near = Near::find(topo, before, &pos, looking);
686 work.grids += 1;
687 work.edges_searched += near.sides_looked_at;
688 // What has folded through its own neighbourhood since the step began.
689 // A node without the row is one from before it.
690 let folds_too = before.is_some() && crate::geometry::node_param_bool(target, "fold_contact", false);
691 let mut folds: Vec<Fold> = Vec::new();
692 if let (Some(was), true) = (before, folds_too) {
693 folded(topo, was, &pos, edges_too, 0.0, None, &mut folds);
694 work.folds = folds.len();
695 }
696 let mut push = vec![Vec3::ZERO; n];
697 let mut weight = vec![0.0f32; n];
698 let mut found: Vec<Contact> = Vec::new();
699 // Which points have gone through something this pass.
700 let mut through = vec![false; n];
701 for _ in 0..iterations {
702 work.passes += 1;
703 if near.is_stale(&pos) {
704 near = Near::find(topo, before, &pos, looking);
705 work.grids += 1;
706 work.edges_searched += near.sides_looked_at;
707 }
708 found.clear();
709 through.iter_mut().for_each(|t| *t = false);
710 // What folded through is put back as far over its neighbour as it
711 // began, which is nearer than a thickness: that is what a
712 // neighbour is.
713 if let Some(was) = before {
714 for fold in &folds {
715 let (who, then, now) = fold.points(topo, was, &pos);
716 if fold.sides {
717 let ([e0, e1], [f0, f1]) = ([now[0], now[1]], [now[2], now[3]]);
718 if let Some((side, at, height)) = edges_went_through([then[0], then[1]], [then[2], then[3]], [e0, e1], [f0, f1], 0.0) {
719 let (s, t) = (at[0].clamp(0.0, 1.0), at[1].clamp(0.0, 1.0));
720 let below = ((e0 + (e1 - e0) * s) - (f0 + (f1 - f0) * t)).dot(side);
721 found.push(Contact { who, share: [1.0 - s, s, t - 1.0, -t], dir: side, deep: height.min(thickness) - below, through: true, edges: true });
722 who.iter().for_each(|&p| through[p] = true);
723 }
724 } else if let Some((side, height)) = went_through(then[0], [then[1], then[2], then[3]], now[0], [now[1], now[2], now[3]], 0.0) {
725 let w = crate::spatial::closest_weights_on_triangle(now[0], now[1], now[2], now[3]);
726 let below = (now[0] - now[1]).dot(side);
727 found.push(Contact { who, share: [1.0, -w[0], -w[1], -w[2]], dir: side, deep: height.min(thickness) - below, through: true, edges: false });
728 through[who[0]] = true;
729 }
730 }
731 }
732 let (touching, positions) = (&near.touching, &pos);
733 let by_point = in_pieces(touching.len(), 4096, |pairs| {
734 let (pos, mut found) = (positions, Vec::new());
735 for &[p, t] in &touching[pairs] {
736 let p = p as usize;
737 let [ia, ib, ic] = topo.tris[t as usize].map(|c| c as usize);
738 let (a, b, c) = (pos[ia], pos[ib], pos[ic]);
739 let who = [p, ia, ib, ic];
740 if let Some((side, _)) = before.and_then(|was| went_through(was[p], [was[ia], was[ib], was[ic]], pos[p], [a, b, c], 0.0)) {
741 // `side` is the triangle's normal, turned to the side
742 // the point came from; it is below the plane by that
743 // much and belongs a thickness above it.
744 let w = crate::spatial::closest_weights_on_triangle(pos[p], a, b, c);
745 let below = (pos[p] - a).dot(side);
746 found.push(Contact { who, share: [1.0, -w[0], -w[1], -w[2]], dir: side, deep: thickness - below, through: true, edges: false });
747 continue;
748 }
749 // What is listed is what was near when it was listed, and
750 // most of it is not near enough: a triangle whose box is a
751 // thickness away on any axis is further than that.
752 let (tlo, thi) = (a.min(b).min(c) - thickness, a.max(b).max(c) + thickness);
753 if pos[p].cmplt(tlo).any() || pos[p].cmpgt(thi).any() {
754 continue;
755 }
756 let w = crate::spatial::closest_weights_on_triangle(pos[p], a, b, c);
757 let d = pos[p] - (a * w[0] + b * w[1] + c * w[2]);
758 let len = d.length();
759 if len >= thickness {
760 continue;
761 }
762 let dir = if len < 1e-9 {
763 let normal = (b - a).cross(c - a).normalize_or_zero();
764 if normal == Vec3::ZERO {
765 continue;
766 }
767 normal
768 } else {
769 d / len
770 };
771 found.push(Contact { who, share: [1.0, -w[0], -w[1], -w[2]], dir, deep: thickness - len, through: false, edges: false });
772 }
773 found
774 });
775 found.extend(by_point.into_iter().flatten());
776 work.searched += n;
777 let meeting = &near.meeting;
778 let by_side = in_pieces(meeting.len(), 4096, |pairs| {
779 let (pos, mut found) = (positions, Vec::new());
780 for &[i, j] in &meeting[pairs] {
781 let (e, f) = (topo.sides[i as usize], topo.sides[j as usize]);
782 let ([e0, e1], [f0, f1]) = (e.map(|p| pos[p as usize]), f.map(|p| pos[p as usize]));
783 let who = [e[0] as usize, e[1] as usize, f[0] as usize, f[1] as usize];
784 let was = before.map(|was| (e.map(|p| was[p as usize]), f.map(|p| was[p as usize])));
785 if let Some((side, at, _)) = was.and_then(|(e_was, f_was)| edges_went_through(e_was, f_was, [e0, e1], [f0, f1], 0.0)) {
786 let (s, t) = (at[0].clamp(0.0, 1.0), at[1].clamp(0.0, 1.0));
787 let below = ((e0 + (e1 - e0) * s) - (f0 + (f1 - f0) * t)).dot(side);
788 found.push(Contact { who, share: [1.0 - s, s, t - 1.0, -t], dir: side, deep: thickness - below, through: true, edges: true });
789 continue;
790 }
791 let (lo, hi) = (e0.min(e1), e0.max(e1));
792 let (flo, fhi) = (f0.min(f1) - thickness, f0.max(f1) + thickness);
793 if hi.cmplt(flo).any() || lo.cmpgt(fhi).any() {
794 continue;
795 }
796 let (s, t) = nearest_on_segments([e0, e1], [f0, f1]);
797 // An end is a point, and the point test has it.
798 if !(s > ENDS && s < 1.0 - ENDS && t > ENDS && t < 1.0 - ENDS) {
799 continue;
800 }
801 let d = (e0 + (e1 - e0) * s) - (f0 + (f1 - f0) * t);
802 let len = d.length();
803 if len >= thickness {
804 continue;
805 }
806 let dir = if len < 1e-9 {
807 let across = (e1 - e0).cross(f1 - f0).normalize_or_zero();
808 if across == Vec3::ZERO {
809 continue;
810 }
811 across
812 } else {
813 d / len
814 };
815 found.push(Contact { who, share: [1.0 - s, s, t - 1.0, -t], dir, deep: thickness - len, through: false, edges: true });
816 }
817 found
818 });
819 found.extend(by_side.into_iter().flatten());
820 // What has gone through something, this pass.
821 for contact in found.iter().filter(|c| c.through) {
822 let subjects = if contact.edges { &contact.who[..] } else { &contact.who[..1] };
823 subjects.iter().for_each(|&p| through[p] = true);
824 }
825 push.iter_mut().for_each(|v| *v = Vec3::ZERO);
826 weight.iter_mut().for_each(|w| *w = 0.0);
827 let mut any = false;
828 for contact in &found {
829 let Contact { who, share, dir, deep, .. } = *contact;
830 // What has gone through something is put back before it is
831 // parted from anything: whatever is beside the thing it went
832 // through sees it near, on the wrong side, and would push it on.
833 let subjects = if contact.edges { &who[..] } else { &who[..1] };
834 if !contact.through && subjects.iter().any(|&p| through[p]) {
835 continue;
836 }
837 // Inverse masses of one or none, each by the square of its
838 // share of the contact.
839 let give: f32 = (0..4).map(|i| free(who[i]) * share[i] * share[i]).sum();
840 if give <= 0.0 {
841 continue;
842 }
843 let step = dir * (deep / give);
844 for i in 0..4 {
845 // Each moves by its share; its say in the average is its
846 // share too, so one that is barely part of a contact does
847 // not water down another it carries.
848 let say = free(who[i]) * share[i].abs() * deep;
849 push[who[i]] += step * (share[i] * say);
850 weight[who[i]] += say;
851 }
852 work.contacts += 1;
853 work.edge_contacts += contact.edges as usize;
854 work.crossed += contact.through as usize;
855 any = true;
856 }
857 if !any {
858 break;
859 }
860 for p in 0..n {
861 if weight[p] > 0.0 {
862 pos[p] += push[p] / weight[p];
863 }
864 }
865 moved_at_all = true;
866 }
867 // The hold. The passes share a move out and average what they are
868 // given, and under a push that does not let up they can run out before
869 // a point is back on its side — and a point left through a triangle is,
870 // to the next step, a point that began there. So whatever is still
871 // through goes back to where the step began, and what it is through
872 // with it: the one arrangement of them known not to cross. It costs
873 // them the step's movement and nothing else.
874 if let Some(was) = before {
875 let mut back: Vec<usize> = Vec::new();
876 // What the last look put back, and so what this one has to look
877 // at: a pair none of whose points has moved since it was last
878 // looked at is as it was. The first look is at everything.
879 let mut stirred = vec![true; n];
880 for _ in 0..HOLD_ROUNDS {
881 if near.is_stale(&pos) {
882 near = Near::find(topo, before, &pos, looking);
883 work.grids += 1;
884 work.edges_searched += near.sides_looked_at;
885 }
886 back.clear();
887 let (touching, meeting, positions, stirred_now) = (&near.touching, &near.meeting, &pos, &stirred);
888 let by_point = in_pieces(touching.len(), 8192, |pairs| {
889 let (pos, stirred, mut back) = (positions, stirred_now, Vec::new());
890 for &[p, t] in &touching[pairs] {
891 let p = p as usize;
892 let [ia, ib, ic] = topo.tris[t as usize].map(|c| c as usize);
893 if !(stirred[p] || stirred[ia] || stirred[ib] || stirred[ic]) {
894 continue;
895 }
896 if went_through(was[p], [was[ia], was[ib], was[ic]], pos[p], [pos[ia], pos[ib], pos[ic]], HOLD_MARGIN).is_some() {
897 back.extend([p, ia, ib, ic]);
898 }
899 }
900 back
901 });
902 let by_side = in_pieces(meeting.len(), 8192, |pairs| {
903 let (pos, stirred, mut back) = (positions, stirred_now, Vec::new());
904 for &[i, j] in &meeting[pairs] {
905 let (e, f) = (topo.sides[i as usize], topo.sides[j as usize]);
906 if !e.iter().chain(f.iter()).any(|&p| stirred[p as usize]) {
907 continue;
908 }
909 let (e_now, e_was) = (e.map(|p| pos[p as usize]), e.map(|p| was[p as usize]));
910 let (f_now, f_was) = (f.map(|p| pos[p as usize]), f.map(|p| was[p as usize]));
911 if edges_went_through(e_was, f_was, e_now, f_now, HOLD_MARGIN).is_some() {
912 back.extend(e.iter().chain(f.iter()).map(|&p| p as usize));
913 }
914 }
915 back
916 });
917 back.extend(by_point.into_iter().chain(by_side).flatten());
918 if folds_too {
919 folded(topo, was, &pos, edges_too, HOLD_MARGIN, Some(&stirred), &mut folds);
920 for fold in &folds {
921 back.extend(fold.points(topo, was, &pos).0);
922 }
923 }
924 stirred.iter_mut().for_each(|s| *s = false);
925 let mut put = 0;
926 for &i in &back {
927 if movable[i] && pos[i] != was[i] {
928 pos[i] = was[i];
929 stirred[i] = true;
930 put += 1;
931 }
932 }
933 if put == 0 {
934 break;
935 }
936 work.held += put;
937 moved_at_all = true;
938 }
939 }
940 if moved_at_all {
941 for (p, v) in pos.iter().enumerate() {
942 geom.set_pos(p, *v);
943 }
944 }
945 }
946
947 /// `run` over `0..count` in pieces, on as many threads as the machine has
948 /// and the work is worth — no piece smaller than `least` — and what each
949 /// piece made, in order. The order is what makes this a way of RUNNING the
950 /// work and not a change to it: put end to end, the pieces are what one
951 /// thread would have made.
952 ///
953 /// Threads and not a pool, since the crate has none: a thread costs tens
954 /// of microseconds to start, which is what `least` is for.
955 pub(crate) fn in_pieces<R: Send>(count: usize, least: usize, run: impl Fn(std::ops::Range<usize>) -> R + Sync) -> Vec<R> {
956 let threads = std::thread::available_parallelism().map_or(1, |n| n.get()).min(count / least.max(1)).max(1);
957 if threads == 1 {
958 return vec![run(0..count)];
959 }
960 let size = count.div_ceil(threads);
961 std::thread::scope(|scope| {
962 let run = &run;
963 let pieces: Vec<_> = (0..threads).map(|k| scope.spawn(move || run((k * size).min(count)..((k + 1) * size).min(count)))).collect();
964 pieces.into_iter().map(|piece| piece.join().expect("a piece of the detangle solve panicked")).collect()
965 })
966 }
967
968 /// What a search is for.
969 #[derive(Clone, Copy)]
970 struct Looking {
971 thickness: f32,
972 cell: f32,
973 edges_too: bool,
974 }
975
976 /// How much further than a thickness the search looks, as a part of one,
977 /// so that what it finds is still everything near once the passes have
978 /// moved the points: until any has moved half of this.
979 const SLACK: f32 = 0.5;
980
981 /// What is near what: the pairs a pass has to look at, found ONCE and kept
982 /// while the points stay near where they were when it was.
983 ///
984 /// The passes of a solve, and the looks of the hold after them, ask the
985 /// same question of nearly the same positions, and until 2026-09-29 each
986 /// answered it from the grid: a gather of cells per point and per edge, a
987 /// search of the rings per candidate, four or five times over. Measured
988 /// on the sphere test, that — and not the contacts — was what the Surface
989 /// method cost. The pairs are found with [`SLACK`] to spare, and found
990 /// again only when a point has moved half of it.
991 struct Near {
992 /// Where the points were when this was found.
993 filed: Vec<Vec3>,
994 slack: f32,
995 /// A point and a triangle none of whose corners is in its rings.
996 touching: Vec<[u32; 2]>,
997 /// Two sides neither of whose ends is in the rings of the other's.
998 meeting: Vec<[u32; 2]>,
999 /// How many sides were searched for sides near them.
1000 sides_looked_at: usize,
1001 }
1002
1003 impl Near {
1004 fn is_stale(&self, pos: &[Vec3]) -> bool {
1005 let moved = pos.iter().zip(&self.filed).map(|(p, f)| (*p - *f).length_squared()).fold(0.0f32, f32::max);
1006 moved > (self.slack * 0.5) * (self.slack * 0.5)
1007 }
1008
1009 fn find(topo: &Topo, before: Option<&[Vec3]>, pos: &[Vec3], looking: Looking) -> Near {
1010 let Looking { thickness, cell, edges_too } = looking;
1011 let n = pos.len();
1012 let slack = thickness * SLACK;
1013 let grid = TriCells::build(pos, &topo.tris, cell);
1014 // How far anything has come since the step began: what a point or
1015 // an edge went through may be that far from where either is now.
1016 let travelled = before.map_or(0.0, |b| pos.iter().zip(b).map(|(p, q)| (*p - *q).length_squared()).fold(0.0f32, f32::max).sqrt());
1017 // Which points are near a triangle with a side that is none of
1018 // their own neighbourhood: what says which edges are worth testing
1019 // against others. Two edges nearer than `close` somewhere along
1020 // them have an end within that and half the edge's length of the
1021 // other edge, which is a side of a triangle — so an edge with
1022 // neither end that near anything is near nothing, and most of a
1023 // mesh is. `close` is the thickness, or as far as two edges that
1024 // have passed through each other this step can have come apart
1025 // since.
1026 let mut near_something = vec![false; n];
1027 let close = thickness.max(2.0 * travelled) + slack;
1028 // Half the longest side at each point.
1029 let mut half = vec![0.0f32; n];
1030 if edges_too {
1031 for e in &topo.sides {
1032 let [a, b] = e.map(|p| p as usize);
1033 let len = (pos[b] - pos[a]).length() * 0.5;
1034 (half[a], half[b]) = (half[a].max(len), half[b].max(len));
1035 }
1036 }
1037 // What may touch is what is within a thickness, and what may have
1038 // gone through since the step began is what is within twice what
1039 // anything has travelled: each has come no further than that from
1040 // where they met. Boxes turn most of a cell away; the distance
1041 // itself turns away most of what is left, which on one sheet is
1042 // the sides a few edges off — near enough for their boxes, and
1043 // further than any thickness.
1044 let reach = thickness.max(2.0 * travelled) + slack;
1045 let boxed = |points: &[u32]| points.iter().fold((Vec3::splat(f32::MAX), Vec3::splat(f32::MIN)), |at, &p| (at.0.min(pos[p as usize]), at.1.max(pos[p as usize])));
1046 let by_point = in_pieces(n, 64, |points| {
1047 let mut search = Search::new(topo);
1048 let (mut touching, mut near_something) = (Vec::new(), Vec::new());
1049 for p in points {
1050 // How far this point looks: for what it may touch, and
1051 // further for what its edges may.
1052 let look = if edges_too { close + half[p] } else { reach };
1053 let mut near = false;
1054 search.triangles(&grid, pos[p] - look, pos[p] + look);
1055 for &t in &search.near {
1056 let corners = topo.tris[t as usize];
1057 let (tlo, thi) = boxed(&corners);
1058 if pos[p].cmplt(tlo - look).any() || pos[p].cmpgt(thi + look).any() {
1059 continue;
1060 }
1061 let [a, b, c] = corners.map(|c| pos[c as usize]);
1062 let away = (pos[p] - crate::spatial::closest_point_on_triangle(pos[p], a, b, c)).length_squared();
1063 if away > look * look {
1064 continue;
1065 }
1066 // A triangle with two corners that are none of the
1067 // point's own has a side its edges may meet; one with
1068 // three is a triangle it may touch.
1069 let own = corners.iter().filter(|&&c| topo.excludes(p, c)).count();
1070 if own > 1 || (own == 1 && !edges_too) {
1071 continue;
1072 }
1073 near = true;
1074 if own == 0 && away <= reach * reach {
1075 touching.push([p as u32, t]);
1076 }
1077 }
1078 if near {
1079 near_something.push(p);
1080 }
1081 }
1082 (touching, near_something)
1083 });
1084 let mut touching = Vec::new();
1085 for (pairs, near) in by_point {
1086 touching.extend(pairs);
1087 near.into_iter().for_each(|p| near_something[p] = true);
1088 }
1089 let near_something = &near_something;
1090 let mut meeting = Vec::new();
1091 let mut sides_looked_at = 0;
1092 if edges_too {
1093 let by_side = in_pieces(topo.sides.len(), 64, |sides| {
1094 let mut search = Search::new(topo);
1095 let (mut meeting, mut looked_at) = (Vec::new(), 0);
1096 for i in sides {
1097 let e = topo.sides[i];
1098 if !e.iter().any(|&p| near_something[p as usize]) {
1099 continue;
1100 }
1101 looked_at += 1;
1102 let (lo, hi) = boxed(&e);
1103 let [e0, e1] = e.map(|p| pos[p as usize]);
1104 search.sides(&grid, topo, i, lo - reach, hi + reach);
1105 for &j in &search.near {
1106 let f = topo.sides[j as usize];
1107 let (flo, fhi) = boxed(&f);
1108 if hi.cmplt(flo - reach).any() || lo.cmpgt(fhi + reach).any() {
1109 continue;
1110 }
1111 let [f0, f1] = f.map(|p| pos[p as usize]);
1112 let (s, t) = nearest_on_segments([e0, e1], [f0, f1]);
1113 if ((e0 + (e1 - e0) * s) - (f0 + (f1 - f0) * t)).length_squared() > reach * reach {
1114 continue;
1115 }
1116 if e.iter().any(|&p| f.iter().any(|&q| topo.excludes(p as usize, q))) {
1117 continue;
1118 }
1119 meeting.push([i as u32, j]);
1120 }
1121 }
1122 (meeting, looked_at)
1123 });
1124 for (pairs, looked_at) in by_side {
1125 meeting.extend(pairs);
1126 sides_looked_at += looked_at;
1127 }
1128 }
1129 Near { filed: pos.to_vec(), slack, touching, meeting, sides_looked_at }
1130 }
1131 }
1132
1133 /// A point and a triangle of its own neighbourhood, or two sides that are
1134 /// each other's, one of which went through the other.
1135 #[derive(Clone, Copy)]
1136 struct Fold {
1137 sides: bool,
1138 /// The point and the triangle, or the two sides.
1139 pair: [u32; 2],
1140 }
1141
1142 impl Fold {
1143 /// The four points of it, where they were and where they are.
1144 fn points(&self, topo: &Topo, was: &[Vec3], pos: &[Vec3]) -> ([usize; 4], [Vec3; 4], [Vec3; 4]) {
1145 let who = if self.sides {
1146 let ([a, b], [c, d]) = (topo.sides[self.pair[0] as usize], topo.sides[self.pair[1] as usize]);
1147 [a, b, c, d].map(|p| p as usize)
1148 } else {
1149 let [a, b, c] = topo.tris[self.pair[1] as usize];
1150 [self.pair[0], a, b, c].map(|p| p as usize)
1151 };
1152 (who, who.map(|p| was[p]), who.map(|p| pos[p]))
1153 }
1154 }
1155
1156 /// Everything that has gone through its own NEIGHBOURHOOD since the step
1157 /// began: a point through a triangle with a corner inside the point's
1158 /// rings, a side through a side with an end inside the other's.
1159 ///
1160 /// The rings are excluded from contact because a neighbour is nearer than
1161 /// a thickness by construction, and no distance says whether it is too
1162 /// near. Going THROUGH is not a distance. A point that was on one side of
1163 /// its neighbour's triangle and is on the other has folded the surface
1164 /// through itself, whatever the thickness, and the only pairs with nothing
1165 /// to say are the ones that share a point — which meet there.
1166 ///
1167 /// Found through the mesh and not through the grid: what is in a point's
1168 /// rings is the triangles at the points of its rings, however far apart
1169 /// the fold has left them.
1170 fn folded(topo: &Topo, was: &[Vec3], pos: &[Vec3], sides_too: bool, margin: f32, stirred: Option<&[bool]>, out: &mut Vec<Fold>) {
1171 out.clear();
1172 // With `stirred`, only the pairs with a point that is: what has a
1173 // stirred point in its rings, or is one, is warm, and of a warm
1174 // point's pairs the ones with no stirred point are as they were.
1175 let is = |p: u32| stirred.is_none_or(|s| s[p as usize]);
1176 let warm: Option<Vec<bool>> = stirred.map(|s| (0..pos.len()).map(|p| topo.own(p).iter().any(|&q| topo.neighbours_stirred(q, s))).collect());
1177 let is_warm = |p: u32| warm.as_ref().is_none_or(|w| w[p as usize]);
1178 let by_point = in_pieces(pos.len(), 256, |points| {
1179 let mut search = Search::new(topo);
1180 let mut out = Vec::new();
1181 for p in points {
1182 if !is_warm(p as u32) {
1183 continue;
1184 }
1185 search.next();
1186 for &q in topo.own(p) {
1187 for &t in topo.tris_at(q as usize) {
1188 if search.seen[t as usize] == search.stamp {
1189 continue;
1190 }
1191 search.seen[t as usize] = search.stamp;
1192 let corners = topo.tris[t as usize];
1193 if corners.contains(&(p as u32)) || !(is(p as u32) || corners.iter().any(|&c| is(c))) {
1194 continue;
1195 }
1196 let [a, b, c] = corners.map(|c| c as usize);
1197 if went_through(was[p], [was[a], was[b], was[c]], pos[p], [pos[a], pos[b], pos[c]], margin).is_some() {
1198 out.push(Fold { sides: false, pair: [p as u32, t] });
1199 }
1200 }
1201 }
1202 }
1203 out
1204 });
1205 out.extend(by_point.into_iter().flatten());
1206 if !sides_too {
1207 return;
1208 }
1209 let by_side = in_pieces(topo.sides.len(), 256, |sides| {
1210 let mut search = Search::new(topo);
1211 let mut out = Vec::new();
1212 for i in sides {
1213 let e = topo.sides[i];
1214 if !e.iter().any(|&p| is_warm(p)) {
1215 continue;
1216 }
1217 search.next();
1218 let (e_was, e_now) = (e.map(|p| was[p as usize]), e.map(|p| pos[p as usize]));
1219 for &end in &e {
1220 for &q in topo.own(end as usize) {
1221 for &j in topo.sides_at(q as usize) {
1222 if j as usize <= i || search.met[j as usize] == search.stamp {
1223 continue;
1224 }
1225 search.met[j as usize] = search.stamp;
1226 let f = topo.sides[j as usize];
1227 if f.iter().any(|q| e.contains(q)) || !e.iter().chain(f.iter()).any(|&p| is(p)) {
1228 continue;
1229 }
1230 let (f_was, f_now) = (f.map(|p| was[p as usize]), f.map(|p| pos[p as usize]));
1231 if edges_went_through(e_was, f_was, e_now, f_now, margin).is_some() {
1232 out.push(Fold { sides: true, pair: [i as u32, j] });
1233 }
1234 }
1235 }
1236 }
1237 }
1238 out
1239 });
1240 out.extend(by_side.into_iter().flatten());
1241 }
1242
1243 /// A contact as a pass found it: a point and a triangle's three corners,
1244 /// or two edges' four ends.
1245 #[derive(Clone, Copy)]
1246 struct Contact {
1247 who: [usize; 4],
1248 /// How much of the move each takes, and which way: the point one, the
1249 /// corners against it by how much of the closest point each is; an
1250 /// edge's ends by how near each is to where the edges are nearest, and
1251 /// the other edge's against them.
1252 share: [f32; 4],
1253 /// The way the first of them is to move.
1254 dir: Vec3,
1255 /// How far they are to part.
1256 deep: f32,
1257 through: bool,
1258 edges: bool,
1259 }
1260
1261 /// How near its end, as a part of its length, the nearest place on an edge
1262 /// may be and still be the edge's and not the end's.
1263 const ENDS: f32 = 1e-3;
1264
1265 /// What a search of the grid keeps between one query and the next.
1266 struct Search {
1267 near: Vec<u32>,
1268 seen: Vec<u32>,
1269 stamp: u32,
1270 /// One mark per side, as `seen` is one per triangle.
1271 met: Vec<u32>,
1272 /// The triangles a search for sides went through.
1273 held: Vec<u32>,
1274 }
1275
1276 impl Search {
1277 fn new(topo: &Topo) -> Search {
1278 Search { near: Vec::new(), seen: vec![u32::MAX; topo.tris.len()], stamp: 0, met: vec![u32::MAX; topo.sides.len()], held: Vec::new() }
1279 }
1280
1281 fn next(&mut self) {
1282 self.stamp = self.stamp.wrapping_add(1);
1283 if self.stamp == u32::MAX {
1284 self.seen.iter_mut().for_each(|m| *m = u32::MAX);
1285 self.met.iter_mut().for_each(|m| *m = u32::MAX);
1286 self.stamp = 0;
1287 }
1288 }
1289
1290 /// The triangles filed in the cells the box touches, into `near`.
1291 fn triangles(&mut self, grid: &TriCells, lo: Vec3, hi: Vec3) {
1292 self.next();
1293 grid.gather(lo, hi, &mut self.seen, self.stamp, &mut self.near);
1294 }
1295
1296 /// The sides of those triangles that come AFTER side `i`, each once,
1297 /// into `near`: a pair of sides is met from the earlier of the two.
1298 fn sides(&mut self, grid: &TriCells, topo: &Topo, i: usize, lo: Vec3, hi: Vec3) {
1299 self.triangles(grid, lo, hi);
1300 std::mem::swap(&mut self.near, &mut self.held);
1301 self.near.clear();
1302 for &t in &self.held {
1303 for &j in &topo.tri_sides[t as usize] {
1304 if j as usize > i && self.met[j as usize] != self.stamp {
1305 self.met[j as usize] = self.stamp;
1306 self.near.push(j);
1307 }
1308 }
1309 }
1310 }
1311 }
1312
1313 /// Where two segments are nearest each other, as a part of each one's
1314 /// length. Ericson's *Real-Time Collision Detection* §5.1.9.
1315 fn nearest_on_segments([p1, q1]: [Vec3; 2], [p2, q2]: [Vec3; 2]) -> (f32, f32) {
1316 let (d1, d2, r) = (q1 - p1, q2 - p2, p1 - p2);
1317 let (a, e, f) = (d1.length_squared(), d2.length_squared(), d2.dot(r));
1318 if a <= 1e-30 && e <= 1e-30 {
1319 return (0.0, 0.0);
1320 }
1321 if a <= 1e-30 {
1322 return (0.0, (f / e).clamp(0.0, 1.0));
1323 }
1324 let c = d1.dot(r);
1325 if e <= 1e-30 {
1326 return ((-c / a).clamp(0.0, 1.0), 0.0);
1327 }
1328 let b = d1.dot(d2);
1329 let denom = a * e - b * b;
1330 let mut s = if denom > 1e-12 * a * e { ((b * f - c * e) / denom).clamp(0.0, 1.0) } else { 0.0 };
1331 let mut t = (b * s + f) / e;
1332 if t < 0.0 {
1333 t = 0.0;
1334 s = (-c / a).clamp(0.0, 1.0);
1335 } else if t > 1.0 {
1336 t = 1.0;
1337 s = ((b - c) / a).clamp(0.0, 1.0);
1338 }
1339 (s, t)
1340 }
1341
1342 /// One edge over another, as lines: how far the first is from the second
1343 /// along the direction across both, where on each the two are nearest (as
1344 /// a part of its length, outside nought to one beyond its ends), and that
1345 /// direction. `None` for edges that run the same way, which have none.
1346 fn over_edge([e0, e1]: [Vec3; 2], [f0, f1]: [Vec3; 2]) -> Option<(f32, [f32; 2], Vec3)> {
1347 let (d1, d2, r) = (e1 - e0, f1 - f0, e0 - f0);
1348 let across = d1.cross(d2);
1349 let (a, e) = (d1.length_squared(), d2.length_squared());
1350 let denom = across.length_squared();
1351 if denom <= 1e-8 * a * e {
1352 return None;
1353 }
1354 let (b, c, f) = (d1.dot(d2), d1.dot(r), d2.dot(r));
1355 let s = (b * f - c * e) / denom;
1356 let t = (a * f - b * c) / denom;
1357 let across = across / denom.sqrt();
1358 Some((r.dot(across), [s, t], across))
1359 }
1360
1361 /// When, between nought and one, something that is `at(0)` on one side of
1362 /// nothing and `at(1)` on the other is nothing: found by halving, since
1363 /// what is asked of is a cubic in the time and its ends are all that is
1364 /// known of it.
1365 fn crossing_time(at: impl Fn(f32) -> f32, began: f32) -> f32 {
1366 let (mut lo, mut hi) = (0.0f32, 1.0f32);
1367 for _ in 0..20 {
1368 let mid = (lo + hi) * 0.5;
1369 if at(mid) * began > 0.0 {
1370 lo = mid;
1371 } else {
1372 hi = mid;
1373 }
1374 }
1375 (lo + hi) * 0.5
1376 }
1377
1378 /// Whether two edges passed through each other between then and now, and
1379 /// if so the direction across them as they are now, turned to the side the
1380 /// first came from, where on each they are nearest, and how far apart
1381 /// they began.
1382 ///
1383 /// [`went_through`]'s question, of two edges, each point taken to have
1384 /// gone straight from where it was to where it is: the volume the four
1385 /// span changed sign — they were in one plane at some moment between — and
1386 /// at that moment the lines met within both edges. A volume is also
1387 /// nothing when the two run the same way, which is no meeting, and then
1388 /// there is no place on either where they are nearest.
1389 fn edges_went_through(e_then: [Vec3; 2], f_then: [Vec3; 2], e_now: [Vec3; 2], f_now: [Vec3; 2], margin: f32) -> Option<(Vec3, [f32; 2], f32)> {
1390 let volume = |e: [Vec3; 2], f: [Vec3; 2]| (e[0] - f[0]).dot((e[1] - e[0]).cross(f[1] - f[0]));
1391 let (v0, v1) = (volume(e_then, f_then), volume(e_now, f_now));
1392 if v0 == 0.0 || v0 * v1 >= 0.0 {
1393 return None;
1394 }
1395 let between = |when: f32| ([0, 1].map(|i| e_then[i].lerp(e_now[i], when)), [0, 1].map(|i| f_then[i].lerp(f_now[i], when)));
1396 let when = crossing_time(|t| { let (e, f) = between(t); volume(e, f) }, v0);
1397 let (e, f) = between(when);
1398 let (_, at, _) = over_edge(e, f)?;
1399 if !at.iter().all(|&a| a >= -margin && a <= 1.0 + margin) {
1400 return None;
1401 }
1402 let (began, _, _) = over_edge(e_then, f_then)?;
1403 let (_, at_now, across) = over_edge(e_now, f_now)?;
1404 Some((across * v0.signum(), at_now, began.abs()))
1405 }
1406
1407 /// A point's height over a triangle's plane, along the normal its winding
1408 /// gives, and where its foot is in the triangle's own terms: three weights
1409 /// summing to one, any of them negative outside it. `None` for a triangle
1410 /// with no area.
1411 fn over_triangle(p: Vec3, [a, b, c]: [Vec3; 3]) -> Option<(f32, [f32; 3], Vec3)> {
1412 let (e1, e2, v) = (b - a, c - a, p - a);
1413 let n = e1.cross(e2);
1414 let nn = n.length_squared();
1415 if nn < 1e-30 {
1416 return None;
1417 }
1418 let (w1, w2) = (v.cross(e2).dot(n) / nn, e1.cross(v).dot(n) / nn);
1419 let normal = n / nn.sqrt();
1420 Some((v.dot(normal), [1.0 - w1 - w2, w1, w2], normal))
1421 }
1422
1423 /// How far outside a triangle, in its own weights, a passage still counts
1424 /// as through it when the question is whether to HOLD the point. A point
1425 /// going through the edge two triangles share is, after rounding, a little
1426 /// outside both, and holding one that did not quite go through costs a
1427 /// step's movement there and nothing else. The passes take no margin: a
1428 /// push is along the triangle's normal, and measured on the sphere test a
1429 /// tenth of a margin pushed points off triangles they had gone around,
1430 /// leaving more crossed than no memory at all.
1431 const HOLD_MARGIN: f32 = 0.05;
1432
1433 /// How many times the hold looks again: putting points back can leave
1434 /// others through what was put back.
1435 const HOLD_ROUNDS: usize = 8;
1436
1437 /// Whether a point went through a triangle between then and now, and if so
1438 /// the triangle's normal as it is now, turned to the side the point came
1439 /// from, and how far over the triangle it began.
1440 ///
1441 /// Both move, each point taken to have gone straight from where it was to
1442 /// where it is. It went through if the volume the four span changed sign —
1443 /// the point was in the triangle's plane at some moment between — and at
1444 /// that moment its foot was inside the triangle. Until 2026-09-29 the
1445 /// moment and the foot were read off a straight line between the two ends'
1446 /// heights and weights, which is right for a small step and wrong for a
1447 /// long one: a point carried across several triangles was said to have
1448 /// gone around the one it went through.
1449 fn went_through(p_then: Vec3, tri_then: [Vec3; 3], p_now: Vec3, tri_now: [Vec3; 3], margin: f32) -> Option<(Vec3, f32)> {
1450 let volume = |p: Vec3, [a, b, c]: [Vec3; 3]| (p - a).dot((b - a).cross(c - a));
1451 let (v0, v1) = (volume(p_then, tri_then), volume(p_now, tri_now));
1452 if v0 == 0.0 || v0 * v1 >= 0.0 {
1453 return None;
1454 }
1455 let between = |when: f32| (p_then.lerp(p_now, when), [0, 1, 2].map(|i| tri_then[i].lerp(tri_now[i], when)));
1456 let when = crossing_time(|t| { let (p, tri) = between(t); volume(p, tri) }, v0);
1457 let (p, tri) = between(when);
1458 let (_, w, _) = over_triangle(p, tri)?;
1459 if !w.iter().all(|&w| w >= -margin) {
1460 return None;
1461 }
1462 let (began, _, _) = over_triangle(p_then, tri_then)?;
1463 let (_, _, normal) = over_triangle(p_now, tri_now)?;
1464 Some((normal * v0.signum(), began.abs()))
1465 }
1466
1467 /// Where a surface passes through itself.
1468 #[derive(Debug, Default, Clone, PartialEq)]
1469 pub struct Tangles {
1470 /// Edge-triangle pairs where the edge passes through the triangle.
1471 pub crossings: usize,
1472 /// The points of those edges and triangles, ascending, each once.
1473 pub points: Vec<u32>,
1474 }
1475
1476 /// Every edge of `geom` that passes through one of its triangles.
1477 ///
1478 /// This is the MEASURE, and it is geometric: no thickness and no rings. An
1479 /// edge is not tested against a triangle it shares a point with — they meet
1480 /// there by construction — nor against one fanned from a primitive the edge
1481 /// is a side of.
1482 pub fn self_intersections(geom: &Detail) -> Tangles {
1483 if geom.num_points() == 0 || geom.num_prims() == 0 {
1484 return Tangles::default();
1485 }
1486 let topo = topo_for(geom, 0);
1487 if topo.edges.is_empty() {
1488 return Tangles::default();
1489 }
1490 intersections_of(geom, &topo, false)
1491 }
1492
1493 /// [`self_intersections`] counting only what the solve is MEANT to see at
1494 /// this ring count: an edge through a triangle none of whose corners is
1495 /// within `rings` of either end of it. The rest are folds inside the
1496 /// excluded neighbourhood, which the node leaves alone by design, and
1497 /// telling the two apart is what says whether a crossing is the method's
1498 /// miss or the setting's.
1499 pub fn crossings_beyond(geom: &Detail, rings: usize) -> usize {
1500 if geom.num_points() == 0 || geom.num_prims() == 0 {
1501 return 0;
1502 }
1503 let topo = topo_for(geom, rings);
1504 if topo.edges.is_empty() {
1505 return 0;
1506 }
1507 intersections_of(geom, &topo, true).crossings
1508 }
1509
1510 fn intersections_of(geom: &Detail, topo: &Topo, beyond_rings: bool) -> Tangles {
1511 let pos: Vec<Vec3> = (0..geom.num_points()).map(|p| geom.pos(p)).collect();
1512 let grid = TriCells::build(&pos, &topo.tris, mean_edge(geom, topo));
1513 let mut found = Tangles::default();
1514 let mut marked = vec![false; pos.len()];
1515 let mut near = Vec::new();
1516 let mut seen = vec![u32::MAX; topo.tris.len()];
1517 for (stamp, e) in topo.edges.iter().enumerate() {
1518 let (a, b) = (pos[e[0] as usize], pos[e[1] as usize]);
1519 grid.gather(a.min(b), a.max(b), &mut seen, stamp as u32, &mut near);
1520 for &t in &near {
1521 let tri = topo.tris[t as usize];
1522 if tri.contains(&e[0]) || tri.contains(&e[1]) {
1523 continue;
1524 }
1525 if beyond_rings && tri.iter().any(|&c| topo.excludes(e[0] as usize, c) || topo.excludes(e[1] as usize, c)) {
1526 continue;
1527 }
1528 let of = geom.prim_points(topo.tri_prims[t as usize] as usize);
1529 if of.contains(&e[0]) && of.contains(&e[1]) {
1530 continue;
1531 }
1532 let [v0, v1, v2] = tri.map(|i| pos[i as usize]);
1533 if crate::spatial::segment_crosses_triangle(a, b, v0, v1, v2) {
1534 found.crossings += 1;
1535 for i in e.iter().chain(tri.iter()) {
1536 marked[*i as usize] = true;
1537 }
1538 }
1539 }
1540 }
1541 found.points = (0..pos.len() as u32).filter(|&p| marked[p as usize]).collect();
1542 found
1543 }