Skip to main content

rapier3d/dynamics/soft_body/tearing/
tearing_particle_split.rs

1//! Duplication of the particles a tear passes through: the measure elements around a particle
2//! (its fan), their split along a plane or into disconnected groups, and the elements that follow
3//! the copies.
4
5use crate::alloc_prelude::*;
6use crate::math::{DIM, Real, Vector};
7use parry::utils::hashmap::HashMap;
8#[cfg(not(feature = "std"))]
9#[allow(unused_imports)]
10use simba::scalar::{ComplexField as _, RealField as _};
11
12use super::super::soft_body_builder::cell_faces;
13use super::super::{SoftBody, SoftBodyEdgeKind, SoftBodyParticle};
14use super::tearing::shared_others;
15
16/// The elements carrying a body's rest measure (see [`SoftBody::rest_measure`]).
17#[derive(Copy, Clone, Debug, PartialEq, Eq)]
18pub(super) enum MeasureKind {
19    /// The cells (triangles in 2D, tetrahedra in 3D).
20    Cells,
21    /// The surface triangles of a 3D body without cells (cloth, shells).
22    Triangles,
23    /// The structural edges (ropes, polylines, polygons, wires, spring networks).
24    Segments,
25}
26
27impl MeasureKind {
28    /// The number of vertices besides `v` two elements have in common when they share a facet
29    /// through `v` (segments always do: their facet is `v` itself).
30    fn facet_others(self) -> usize {
31        match self {
32            MeasureKind::Cells => DIM - 1,
33            MeasureKind::Triangles => 1,
34            MeasureKind::Segments => 0,
35        }
36    }
37}
38
39/// The measure elements containing a particle, and the groups a split of the particle gives them.
40#[derive(Clone, Debug)]
41pub(super) struct Fan {
42    /// The kind of the elements.
43    pub kind: MeasureKind,
44    /// The element indices: into the cells, the boundary or the edges, by `kind`.
45    pub elements: Vec<u32>,
46    /// The vertices of each element, as they were when the fan was taken.
47    pub vertices: Vec<Vec<u32>>,
48    /// The rest measure of each element.
49    pub measures: Vec<Real>,
50    /// The side of the split plane each element lies on (`0` or `1`; `0` without a plane).
51    pub sides: Vec<usize>,
52    /// The group of each element in `0..num_groups`: group 0 keeps the particle, every other
53    /// group gets a copy.
54    pub groups: Vec<usize>,
55    /// The number of groups.
56    pub num_groups: usize,
57}
58
59impl Fan {
60    /// Groups the elements into the connected components of their adjacency at `v` (sharing a
61    /// facet through `v`) within each side, numbered side 0 first, then by first element.
62    pub(super) fn assign_groups(&mut self, v: u32) {
63        let n = self.elements.len();
64        let mut parent: Vec<usize> = (0..n).collect();
65        fn find(parent: &mut [usize], mut i: usize) -> usize {
66            while parent[i] != i {
67                parent[i] = parent[parent[i]];
68                i = parent[i];
69            }
70            i
71        }
72        let needed = self.kind.facet_others();
73        for i in 0..n {
74            for j in i + 1..n {
75                if self.sides[i] == self.sides[j]
76                    && shared_others(&self.vertices[i], &self.vertices[j], v) >= needed
77                {
78                    let (ri, rj) = (find(&mut parent, i), find(&mut parent, j));
79                    if ri != rj {
80                        parent[ri.max(rj)] = ri.min(rj);
81                    }
82                }
83            }
84        }
85        let mut group_of_root = vec![usize::MAX; n];
86        self.num_groups = 0;
87        for side in 0..2 {
88            for i in (0..n).filter(|&i| self.sides[i] == side) {
89                let root = find(&mut parent, i);
90                if group_of_root[root] == usize::MAX {
91                    group_of_root[root] = self.num_groups;
92                    self.num_groups += 1;
93                }
94                self.groups[i] = group_of_root[root];
95            }
96        }
97    }
98}
99
100/// What the particle splits of a tear did.
101#[derive(Clone, Debug, Default)]
102pub(super) struct SplitLog {
103    /// The particle copies, as `(copy, source)`, in creation order.
104    pub split_particles: Vec<(u32, u32)>,
105    /// The particle pairs of the non-measure edges removed for straddling an opened crack.
106    pub removed_edges: Vec<[u32; 2]>,
107    /// The segments a cut inserted particles into, as `[a, b, p, q]`: the segment `(a, b)` became
108    /// `(a, p)` and `(q, b)`.
109    pub inserted: Vec<[u32; 4]>,
110}
111
112/// Replaces every occurrence of `from` in `vertices` by `to`.
113fn replace(vertices: &mut [u32], from: u32, to: u32) {
114    for x in vertices.iter_mut() {
115        if *x == from {
116            *x = to;
117        }
118    }
119}
120
121impl SoftBody {
122    /// The kind of this body's measure elements, by priority: its cells, its surface triangles
123    /// (3D, without cells), its structural edges. `None` when it has none of them.
124    pub(super) fn measure_kind(&self) -> Option<MeasureKind> {
125        if !self.cells.is_empty() {
126            Some(MeasureKind::Cells)
127        } else if DIM == 3 && !self.boundary.is_empty() {
128            Some(MeasureKind::Triangles)
129        } else if self
130            .edges
131            .iter()
132            .any(|e| e.kind == SoftBodyEdgeKind::Structural)
133        {
134            Some(MeasureKind::Segments)
135        } else {
136            None
137        }
138    }
139
140    /// Calls `f` with the index and the vertices of every measure element of the given kind.
141    pub(super) fn for_each_measure_element(
142        &self,
143        kind: MeasureKind,
144        mut f: impl FnMut(u32, &[u32]),
145    ) {
146        match kind {
147            MeasureKind::Cells => {
148                for (i, c) in self.cells.iter().enumerate() {
149                    f(i as u32, &c.vertices);
150                }
151            }
152            MeasureKind::Triangles => {
153                for (i, t) in self.boundary.iter().enumerate() {
154                    f(i as u32, t);
155                }
156            }
157            MeasureKind::Segments => {
158                for (i, e) in self.edges.iter().enumerate() {
159                    if e.kind == SoftBodyEdgeKind::Structural {
160                        f(i as u32, &e.vertices);
161                    }
162                }
163            }
164        }
165    }
166
167    /// The total rest measure of this body's measure elements, by priority: the rest area (2D) or
168    /// volume (3D) of its cells, else (3D) of its surface triangles, else the rest length of its
169    /// structural edges, before plastic flow; `0.0` without any, and no tear or cut changes it.
170    pub fn rest_measure(&self) -> Real {
171        let Some(kind) = self.measure_kind() else {
172            return 0.0;
173        };
174        let mut total = 0.0;
175        self.for_each_measure_element(kind, |i, _| total += self.element_rest_measure(kind, i));
176        total
177    }
178
179    /// The rest measure of the measure elements whose vertices all belong to the given sorted
180    /// particle set (see [`Self::rest_measure`]): the measure of a piece of the body.
181    pub fn rest_measure_of(&self, particles: &[u32]) -> Real {
182        let Some(kind) = self.measure_kind() else {
183            return 0.0;
184        };
185        let mut total = 0.0;
186        self.for_each_measure_element(kind, |i, vertices| {
187            if vertices.iter().all(|v| particles.binary_search(v).is_ok()) {
188                total += self.element_rest_measure(kind, i);
189            }
190        });
191        total
192    }
193
194    /// The rest-shape distance from `point` to the material of the given sorted particle set: the
195    /// closest measure element with all vertices in the set (zero in a cell), else the closest
196    /// particle of the set. What a joint reads to follow a split cluster's piece.
197    pub(crate) fn rest_distance_to(&self, particles: &[u32], point: Vector) -> Real {
198        use parry::query::PointQuery;
199        let rest = |v: u32| self.particles[v as usize].rest_position;
200        let mut best = Real::MAX;
201        if let Some(kind) = self.measure_kind() {
202            self.for_each_measure_element(kind, |_, vertices| {
203                if !vertices.iter().all(|v| particles.binary_search(v).is_ok()) {
204                    return;
205                }
206                let distance = match vertices.len() {
207                    2 => parry::shape::Segment::new(rest(vertices[0]), rest(vertices[1]))
208                        .distance_to_local_point(point, true),
209                    3 => parry::shape::Triangle::new(
210                        rest(vertices[0]),
211                        rest(vertices[1]),
212                        rest(vertices[2]),
213                    )
214                    .distance_to_local_point(point, true),
215                    #[cfg(feature = "dim3")]
216                    4 => parry::shape::Tetrahedron::new(
217                        rest(vertices[0]),
218                        rest(vertices[1]),
219                        rest(vertices[2]),
220                        rest(vertices[3]),
221                    )
222                    .distance_to_local_point(point, true),
223                    _ => return,
224                };
225                best = best.min(distance);
226            });
227        }
228        if best == Real::MAX {
229            for &v in particles {
230                if let Some(p) = self.particles.get(v as usize) {
231                    best = best.min((p.rest_position - point).length());
232                }
233            }
234        }
235        best
236    }
237
238    /// The rest measure of the `i`-th measure element of the given kind: a cell's rest area or
239    /// volume, a triangle's rest area, an edge's rest length before any plastic flow.
240    fn element_rest_measure(&self, kind: MeasureKind, i: u32) -> Real {
241        match kind {
242            MeasureKind::Cells => self.cells[i as usize].rest_volume.abs(),
243            MeasureKind::Triangles => {
244                #[cfg(feature = "dim3")]
245                {
246                    let rest = |v: u32| self.particles[v as usize].initial_rest_position;
247                    let t = self.boundary[i as usize];
248                    (rest(t[1]) - rest(t[0]))
249                        .cross(rest(t[2]) - rest(t[0]))
250                        .length()
251                        * 0.5
252                }
253                #[cfg(feature = "dim2")]
254                {
255                    0.0
256                }
257            }
258            MeasureKind::Segments => self.edges[i as usize].initial_rest_length(),
259        }
260    }
261
262    /// Whether some measure element of this body contains all the given particles.
263    pub(crate) fn measure_element_holds(&self, particles: &[u32]) -> bool {
264        let Some(kind) = self.measure_kind() else {
265            return false;
266        };
267        let mut found = false;
268        self.for_each_measure_element(kind, |_, vertices| {
269            found |= particles.iter().all(|p| vertices.contains(p));
270        });
271        found
272    }
273
274    /// The fan of `v`: the measure elements of the given kind containing it, in one group.
275    pub(super) fn fan(&self, kind: MeasureKind, v: u32) -> Fan {
276        let mut elements = Vec::new();
277        let mut vertices = Vec::new();
278        self.for_each_measure_element(kind, |i, element| {
279            if element.contains(&v) {
280                elements.push(i);
281                vertices.push(element.to_vec());
282            }
283        });
284        let n = elements.len();
285        Fan {
286            kind,
287            measures: elements
288                .iter()
289                .map(|&i| self.element_rest_measure(kind, i))
290                .collect(),
291            elements,
292            vertices,
293            sides: vec![0; n],
294            groups: vec![0; n],
295            num_groups: (n > 0) as usize,
296        }
297    }
298
299    /// Duplicates every candidate particle whose measure elements no longer form one connected
300    /// fan (a crack left the particle joining two pieces alone): one copy per extra group, the
301    /// other elements around the particle following the group they attach to.
302    pub(super) fn split_particles(&mut self, candidates: &[u32], log: &mut SplitLog) {
303        let Some(kind) = self.measure_kind() else {
304            return;
305        };
306        for &v in candidates {
307            let mut fan = self.fan(kind, v);
308            if fan.elements.len() < 2 {
309                continue;
310            }
311            fan.assign_groups(v);
312            if fan.num_groups >= 2 {
313                self.split_fan(v, &fan, log);
314            }
315        }
316    }
317
318    /// The fan of `v` split by the plane through `origin` with normal `normal`, in the rest
319    /// shape: every element goes to the side of its rest centroid, and the groups are the
320    /// connected components within a side. `None` when the fan lies on one side only.
321    pub(super) fn plane_fan(&self, v: u32, origin: Vector, normal: Vector) -> Option<Fan> {
322        // A pinned particle is never split: the crack must open elsewhere.
323        if self.particles[v as usize].inv_mass == 0.0 {
324            return None;
325        }
326        let kind = self.measure_kind()?;
327        let mut fan = self.fan(kind, v);
328        let rest = |i: u32| self.particles[i as usize].rest_position;
329        for (side, vertices) in fan.sides.iter_mut().zip(&fan.vertices) {
330            let offset: Vector = vertices.iter().map(|&w| rest(w) - origin).sum();
331            *side = (offset.dot(normal) > 0.0) as usize;
332        }
333        if !fan.sides.contains(&0) || !fan.sides.contains(&1) {
334            return None;
335        }
336        fan.assign_groups(v);
337        Some(fan)
338    }
339
340    /// The smallest piece, in measure elements of the given kind, a tear may split off this
341    /// body: the material's [`SoftBodyMaterial::min_piece`], else the kind's default (see
342    /// [`default_min_piece`]).
343    pub(super) fn min_piece(&self, kind: MeasureKind) -> usize {
344        self.material
345            .min_piece
346            .map_or(default_min_piece(kind), |n| n.max(1) as usize)
347    }
348
349    /// Whether the split of `v` given by `fan` leaves every group at least [`Self::min_piece`]
350    /// measure elements reachable without passing through `v` (no chips). Tears consult it, cuts
351    /// do not.
352    pub(super) fn opens_without_confetti(&self, v: u32, fan: &Fan) -> bool {
353        let min = self.min_piece(fan.kind);
354        (0..fan.num_groups).all(|g| {
355            let seeds: Vec<u32> = (0..fan.elements.len())
356                .filter(|&i| fan.groups[i] == g)
357                .map(|i| fan.elements[i])
358                .collect();
359            self.reachable_elements(fan.kind, &seeds, v, min) >= min
360        })
361    }
362
363    /// Number of measure elements of the given kind reachable from `seeds` through shared facets
364    /// not containing `pivot`, counting up to `limit`.
365    fn reachable_elements(
366        &self,
367        kind: MeasureKind,
368        seeds: &[u32],
369        pivot: u32,
370        limit: usize,
371    ) -> usize {
372        // Calls `f` with every facet of an element not containing `pivot`: its vertices but one,
373        // sorted and padded with `u32::MAX`.
374        fn facets(vertices: &[u32], pivot: u32, mut f: impl FnMut([u32; 3])) {
375            for skip in 0..vertices.len() {
376                let mut key = [u32::MAX; 3];
377                let mut k = 0;
378                for (i, &w) in vertices.iter().enumerate() {
379                    if i != skip {
380                        key[k] = w;
381                        k += 1;
382                    }
383                }
384                if !key[..k].contains(&pivot) {
385                    key[..k].sort_unstable();
386                    f(key);
387                }
388            }
389        }
390        // Facet -> elements map of the whole body (a tear is a rare event).
391        let mut by_facet: HashMap<[u32; 3], Vec<u32>> = HashMap::default();
392        let mut elements: HashMap<u32, Vec<u32>> = HashMap::default();
393        self.for_each_measure_element(kind, |i, vertices| {
394            elements.insert(i, vertices.to_vec());
395            facets(vertices, pivot, |key| {
396                by_facet.entry(key).or_default().push(i)
397            });
398        });
399        let mut seen: HashMap<u32, ()> = HashMap::default();
400        let mut stack: Vec<u32> = seeds.to_vec();
401        for &s in seeds {
402            seen.insert(s, ());
403        }
404        while let Some(e) = stack.pop() {
405            if seen.len() >= limit {
406                return seen.len();
407            }
408            facets(&elements[&e], pivot, |key| {
409                if let Some(neighbors) = by_facet.get(&key) {
410                    for &n in neighbors {
411                        if !seen.contains_key(&n) {
412                            seen.insert(n, ());
413                            stack.push(n);
414                        }
415                    }
416                }
417            });
418        }
419        seen.len()
420    }
421
422    /// Splits `v` by the groups of its fan (see [`Self::split_vertex`]); returns whether the
423    /// topology changed.
424    pub(super) fn split_fan(&mut self, v: u32, fan: &Fan, log: &mut SplitLog) -> bool {
425        // A pinned (fixed or kinematic) particle is never split: a clamp keeps its material.
426        if fan.num_groups < 2 || self.particles[v as usize].inv_mass == 0.0 {
427            return false;
428        }
429        self.split_vertex(v, fan, log);
430        true
431    }
432
433    /// The group an element around `v` that holds no measure element follows: the lowest group
434    /// of the fan elements it shares a vertex with, or the group whose fan points its way in the
435    /// rest shape.
436    fn group_toward(&self, v: u32, fan: &Fan, vertices: &[u32]) -> usize {
437        let mut best: Option<usize> = None;
438        for (i, fv) in fan.vertices.iter().enumerate() {
439            if fv.iter().any(|x| *x != v && vertices.contains(x)) {
440                best = Some(best.map_or(fan.groups[i], |b| b.min(fan.groups[i])));
441            }
442        }
443        if let Some(g) = best {
444            return g;
445        }
446        let rest = |i: u32| self.particles[i as usize].rest_position;
447        let mut dir = Vector::ZERO;
448        for &w in vertices.iter().filter(|&&w| w != v) {
449            dir += rest(w) - rest(v);
450        }
451        let mut best_score = -Real::MAX;
452        let mut best_group = 0;
453        for (i, fv) in fan.vertices.iter().enumerate() {
454            for &w in fv.iter().filter(|&&w| w != v) {
455                let d = rest(w) - rest(v);
456                let score = d.dot(dir) / (d.length() * dir.length()).max(1.0e-12);
457                if score > best_score {
458                    best_score = score;
459                    best_group = fan.groups[i];
460                }
461            }
462        }
463        best_group
464    }
465
466    /// Whether some measure element of the given kind contains both `x` and `y`.
467    fn share_measure_element(&self, kind: MeasureKind, x: u32, y: u32) -> bool {
468        let mut found = false;
469        self.for_each_measure_element(kind, |_, vertices| {
470            found |= vertices.contains(&x) && vertices.contains(&y);
471        });
472        found
473    }
474
475    /// Splits the particle `v` into one particle per group of its fan: group 0 keeps `v`, every
476    /// other group gets a copy with its fan elements, and the mass is shared by the groups'
477    /// rest measures; the remaining elements at `v` are handled per [`Self::group_toward`].
478    fn split_vertex(&mut self, v: u32, fan: &Fan, log: &mut SplitLog) {
479        let kind = fan.kind;
480        let n = fan.elements.len();
481        let mut weights = vec![0.0; fan.num_groups];
482        for (&g, &measure) in fan.groups.iter().zip(&fan.measures) {
483            weights[g] += measure;
484        }
485        let total: Real = weights.iter().sum();
486        let source = self.particles[v as usize];
487        let mut copies: Vec<u32> = Vec::with_capacity(fan.num_groups);
488        for (g, weight) in weights.iter().enumerate() {
489            let mass = if total > 0.0 {
490                source.mass * weight / total
491            } else {
492                source.mass / fan.num_groups as Real
493            };
494            if g == 0 {
495                let p = &mut self.particles[v as usize];
496                p.mass = mass;
497                if p.inv_mass != 0.0 {
498                    p.inv_mass = crate::utils::inv(mass);
499                }
500                copies.push(v);
501            } else {
502                // A split particle is never pinned, so its copies are free like it.
503                self.particles.push(SoftBodyParticle {
504                    mass,
505                    inv_mass: crate::utils::inv(mass),
506                    next_position: None,
507                    split_root: source.split_root(v),
508                    ..source
509                });
510                let copy = self.particles.len() as u32 - 1;
511                log.split_particles.push((copy, v));
512                copies.push(copy);
513            }
514        }
515
516        // The groups of the fan elements holding each neighbor of `v`, sorted.
517        let mut ring: HashMap<u32, Vec<usize>> = HashMap::default();
518        for (vertices, &g) in fan.vertices.iter().zip(&fan.groups) {
519            for &w in vertices.iter().filter(|&&w| w != v) {
520                let groups = ring.entry(w).or_default();
521                if let Err(pos) = groups.binary_search(&g) {
522                    groups.insert(pos, g);
523                }
524            }
525        }
526        // The facets through `v` shared by cells of different groups: the crack opens there.
527        let mut crack_facets: Vec<(usize, usize)> = Vec::new();
528        if kind == MeasureKind::Cells {
529            for i in 0..n {
530                for j in i + 1..n {
531                    if fan.groups[i] != fan.groups[j]
532                        && shared_others(&fan.vertices[i], &fan.vertices[j], v) == DIM - 1
533                    {
534                        crack_facets.push((i, j));
535                    }
536                }
537            }
538        }
539
540        for (i, &e) in fan.elements.iter().enumerate() {
541            let g = fan.groups[i];
542            if g == 0 {
543                continue;
544            }
545            let target: &mut [u32] = match kind {
546                MeasureKind::Cells => &mut self.cells[e as usize].vertices,
547                MeasureKind::Triangles => &mut self.boundary[e as usize],
548                MeasureKind::Segments => &mut self.edges[e as usize].vertices,
549            };
550            replace(target, v, copies[g]);
551        }
552
553        // The other edges at `v`. One shared by fan elements of several groups (the crack ends
554        // there) gets a copy per group, so no side stretches freely across the crack.
555        let is_measure = |e: &super::super::SoftBodyEdge| {
556            kind == MeasureKind::Segments && e.kind == SoftBodyEdgeKind::Structural
557        };
558        let mut new_edges = Vec::new();
559        for ei in 0..self.edges.len() {
560            let edge = self.edges[ei];
561            if !edge.vertices.contains(&v) || is_measure(&edge) {
562                continue;
563            }
564            let x = if edge.vertices[0] == v {
565                edge.vertices[1]
566            } else {
567                edge.vertices[0]
568            };
569            let groups = ring.get(&x).map(Vec::as_slice).unwrap_or(&[]);
570            let g = match groups.first() {
571                Some(&g) => g,
572                None => self.group_toward(v, fan, &edge.vertices),
573            };
574            replace(&mut self.edges[ei].vertices, v, copies[g]);
575            for &other in groups.iter().skip(1) {
576                let mut copy = self.edges[ei];
577                replace(&mut copy.vertices, copies[g], copies[other]);
578                copy.impulse = 0.0;
579                new_edges.push(copy);
580            }
581        }
582        self.edges.extend(new_edges);
583
584        // Straddlers: a non-measure edge between two neighbors of `v` on disjoint groups, not
585        // held together by a measure element (a bend edge over `v`, a quad diagonal).
586        let mut straddling = vec![false; self.edges.len()];
587        for (ei, e) in self.edges.iter().enumerate() {
588            if is_measure(e) || e.vertices.iter().any(|w| copies.contains(w)) {
589                continue;
590            }
591            let [x, y] = e.vertices;
592            let (Some(gx), Some(gy)) = (ring.get(&x), ring.get(&y)) else {
593                continue;
594            };
595            if gx.iter().all(|g| gy.binary_search(g).is_err())
596                && !self.share_measure_element(kind, x, y)
597            {
598                straddling[ei] = true;
599                log.removed_edges.push(e.vertices);
600            }
601        }
602        if straddling.contains(&true) {
603            let mut keep = straddling.iter().map(|s| !s);
604            self.edges.retain(|_| keep.next().unwrap());
605        }
606
607        #[cfg(feature = "dim3")]
608        {
609            // The fan element holding each triangle of a dihedral, if any.
610            let holder = |t: [u32; 3]| {
611                fan.vertices
612                    .iter()
613                    .position(|fv| t.iter().all(|w| fv.contains(w)))
614            };
615            let mut removed = vec![false; self.dihedrals.len()];
616            for di in 0..self.dihedrals.len() {
617                let d = self.dihedrals[di].vertices;
618                if !d.contains(&v) {
619                    continue;
620                }
621                let g = match (holder([d[0], d[1], d[2]]), holder([d[0], d[1], d[3]])) {
622                    // Bending across the crack.
623                    (Some(i), Some(j)) if fan.groups[i] != fan.groups[j] => {
624                        removed[di] = true;
625                        continue;
626                    }
627                    (Some(i), _) | (None, Some(i)) => fan.groups[i],
628                    (None, None) => self.group_toward(v, fan, &d),
629                };
630                replace(&mut self.dihedrals[di].vertices, v, copies[g]);
631            }
632            if removed.contains(&true) {
633                let mut keep = removed.iter().map(|r| !r);
634                self.dihedrals.retain(|_| keep.next().unwrap());
635            }
636        }
637
638        if kind != MeasureKind::Triangles {
639            // A surface element follows the measure element holding it (its cell, or the segment
640            // it is): the fan of a shared vertex alone would let a face bridge two pieces.
641            for si in 0..self.boundary.len() {
642                let element = self.boundary[si];
643                if !element.contains(&v) {
644                    continue;
645                }
646                let holder = fan
647                    .vertices
648                    .iter()
649                    .position(|fv| element.iter().all(|x| fv.contains(x)));
650                let g = match holder {
651                    Some(i) => fan.groups[i],
652                    None => self.group_toward(v, fan, &element),
653                };
654                replace(&mut self.boundary[si], v, copies[g]);
655            }
656        }
657        for &(i, j) in &crack_facets {
658            for k in [i, j] {
659                let cell = self.cells[fan.elements[k] as usize].vertices;
660                let copy = copies[fan.groups[k]];
661                let on_facet = |w: &u32| {
662                    *w == copy
663                        || (*w != v && fan.vertices[i].contains(w) && fan.vertices[j].contains(w))
664                };
665                if let Some(face) = cell_faces(cell)
666                    .into_iter()
667                    .find(|face| face.iter().all(on_facet))
668                {
669                    self.boundary.push(face);
670                }
671            }
672        }
673    }
674
675    /// Removes the non-measure elements (edges, dihedrals, surface elements of a body of cells or
676    /// segments) joining pieces that the measure elements alone leave disconnected: the catch-all
677    /// for straddlers a split misses. Elements touching a measure-less particle stay.
678    pub(super) fn remove_straddlers_across_pieces(&mut self, log: &mut SplitLog) {
679        let Some(kind) = self.measure_kind() else {
680            return;
681        };
682        let n = self.particles.len();
683        let mut parent: Vec<u32> = (0..n as u32).collect();
684        let mut supported = vec![false; n];
685        fn find(parent: &mut [u32], mut i: u32) -> u32 {
686            while parent[i as usize] != i {
687                let up = parent[parent[i as usize] as usize];
688                parent[i as usize] = up;
689                i = up;
690            }
691            i
692        }
693        self.for_each_measure_element(kind, |_, vertices| {
694            for &w in vertices {
695                supported[w as usize] = true;
696            }
697            for w in vertices.windows(2) {
698                let (a, b) = (find(&mut parent, w[0]), find(&mut parent, w[1]));
699                if a != b {
700                    parent[a.max(b) as usize] = a.min(b);
701                }
702            }
703        });
704        let roots: Vec<u32> = (0..n as u32).map(|i| find(&mut parent, i)).collect();
705        let spans = |vertices: &[u32]| {
706            vertices.iter().all(|&w| supported[w as usize])
707                && vertices
708                    .iter()
709                    .any(|&w| roots[w as usize] != roots[vertices[0] as usize])
710        };
711
712        let removed: Vec<bool> = self
713            .edges
714            .iter()
715            .map(|e| {
716                !(kind == MeasureKind::Segments && e.kind == SoftBodyEdgeKind::Structural)
717                    && spans(&e.vertices)
718            })
719            .collect();
720        if removed.contains(&true) {
721            for (e, _) in self.edges.iter().zip(&removed).filter(|(_, r)| **r) {
722                log.removed_edges.push(e.vertices);
723            }
724            let mut keep = removed.iter().map(|r| !r);
725            self.edges.retain(|_| keep.next().unwrap());
726        }
727        #[cfg(feature = "dim3")]
728        self.dihedrals.retain(|d| !spans(&d.vertices));
729        if kind != MeasureKind::Triangles {
730            self.boundary.retain(|s| !spans(s));
731        }
732    }
733}
734
735/// The smallest piece, in triangles, a tear may split off a 3D cloth or shell.
736pub(super) const MIN_PIECE_TRIANGLES: usize = 10;
737/// The smallest piece, in cells, a tear may split off: more than a grid quad (2D) or a lattice
738/// cube (3D).
739pub(super) const MIN_PIECE_CELLS: usize = if DIM == 2 { 3 } else { 6 };
740/// The smallest piece, in structural edges, a tear may split off a rope, polyline or wire.
741pub(super) const MIN_PIECE_SEGMENTS: usize = 3;
742
743/// The smallest piece, in measure elements of the given kind, a tear may split off by default
744/// (see [`SoftBodyMaterial::min_piece`]).
745pub(super) fn default_min_piece(kind: MeasureKind) -> usize {
746    match kind {
747        MeasureKind::Cells => MIN_PIECE_CELLS,
748        MeasureKind::Triangles => MIN_PIECE_TRIANGLES,
749        MeasureKind::Segments => MIN_PIECE_SEGMENTS,
750    }
751}