Skip to main content

rapier3d/dynamics/soft_body/
soft_body_volume.rs

1//! Volume preservation per piece: the pieces of material enclosed by a closed boundary, each with its own volume constraint.
2use super::SoftBody;
3use super::soft_body_builder::surface_is_closed;
4use crate::alloc_prelude::*;
5use crate::math::{DIM, Real, Vector};
6
7/// A piece of a soft body's material enclosed by closed boundary elements (see
8/// [`SoftBody::volume_pieces`]): its volume constraint holds the area/volume it encloses at its
9/// rest one times the body's volume factor.
10#[derive(Clone, Debug, PartialEq)]
11#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
12pub struct SoftVolumePiece {
13    /// The piece's boundary elements (increasing indices into the body's boundary).
14    pub(crate) elements: Vec<u32>,
15    /// The particles of those elements (increasing).
16    pub(crate) particles: Vec<u32>,
17    /// The signed area (2D) or volume (3D) the elements enclose at rest.
18    pub(crate) rest_volume: Real,
19    /// Accumulated volume impulse (warm-start state).
20    pub(crate) impulse: Real,
21    /// Whether the piece is currently turned inside out (its signed volume has the opposite sign
22    /// to the rest one): its pressure target flips with it (a mirrored balloon is the same
23    /// balloon).
24    #[cfg_attr(feature = "serde-serialize", serde(skip))]
25    pub(crate) inverted: bool,
26}
27
28impl SoftVolumePiece {
29    /// The piece's boundary elements, as indices into [`SoftBody::boundary`].
30    pub fn elements(&self) -> &[u32] {
31        &self.elements
32    }
33
34    /// The particles of the piece's boundary elements, in increasing order.
35    pub fn particles(&self) -> &[u32] {
36        &self.particles
37    }
38
39    /// The signed area (2D) or volume (3D) enclosed by the piece at rest.
40    pub fn rest_volume(&self) -> Real {
41        self.rest_volume
42    }
43
44    /// The signed area (2D) or volume (3D) currently enclosed by this piece of `body`.
45    pub fn volume(&self, body: &SoftBody) -> Real {
46        SoftBody::elements_volume(self.boundary_elements(&body.boundary), |i| {
47            body.particles[i as usize].position
48        })
49    }
50
51    /// The piece's elements, read from the body's `boundary`.
52    pub(crate) fn boundary_elements<'a>(
53        &'a self,
54        boundary: &'a [[u32; DIM]],
55    ) -> impl Iterator<Item = &'a [u32; DIM]> + Clone + 'a {
56        self.elements.iter().map(move |&e| &boundary[e as usize])
57    }
58}
59
60impl SoftBody {
61    /// Rebuilds the volume pieces after a topology change (rest volumes from the rest positions)
62    /// and disables volume preservation if none is left. `remap` maps the previous particle
63    /// indices to the current ones (empty: unchanged; `u32::MAX`: removed).
64    pub(crate) fn rebuild_volume_pieces(&mut self, remap: &[u32]) {
65        self.volume_pieces =
66            self.compute_volume_pieces(remap, |i| self.particles[i as usize].rest_position);
67        self.volume_preservation &= !self.volume_pieces.is_empty();
68    }
69
70    /// The volume pieces of the current topology: the particles connected by edges, cells or
71    /// boundary elements, kept when their boundary elements are closed. Each piece takes the
72    /// warm-start impulse and orientation of the previous piece holding its first particle.
73    pub(crate) fn compute_volume_pieces(
74        &self,
75        remap: &[u32],
76        rest_position: impl Fn(u32) -> Vector,
77    ) -> Vec<SoftVolumePiece> {
78        fn find(parent: &mut [u32], mut i: u32) -> u32 {
79            while parent[i as usize] != i {
80                let up = parent[parent[i as usize] as usize];
81                parent[i as usize] = up;
82                i = up;
83            }
84            i
85        }
86        fn link(parent: &mut [u32], vertices: &[u32]) {
87            let n = parent.len();
88            let mut vertices = vertices.iter().copied().filter(|&v| (v as usize) < n);
89            let Some(first) = vertices.next() else {
90                return;
91            };
92            let mut root = find(parent, first);
93            for v in vertices {
94                let other = find(parent, v);
95                if other != root {
96                    let (low, high) = (root.min(other), root.max(other));
97                    parent[high as usize] = low;
98                    root = low;
99                }
100            }
101        }
102
103        let n = self.particles.len();
104        let mut parent: Vec<u32> = (0..n as u32).collect();
105        for edge in &self.edges {
106            link(&mut parent, &edge.vertices);
107        }
108        for cell in &self.cells {
109            link(&mut parent, &cell.vertices);
110        }
111        for element in &self.boundary {
112            link(&mut parent, element);
113        }
114
115        // The boundary elements of each connected piece, in boundary order.
116        let mut group_of_root = vec![u32::MAX; n];
117        let mut groups: Vec<Vec<u32>> = Vec::new();
118        for (e, element) in self.boundary.iter().enumerate() {
119            let Some(&v) = element.iter().find(|&&v| (v as usize) < n) else {
120                continue;
121            };
122            let root = find(&mut parent, v) as usize;
123            if group_of_root[root] == u32::MAX {
124                group_of_root[root] = groups.len() as u32;
125                groups.push(Vec::new());
126            }
127            groups[group_of_root[root] as usize].push(e as u32);
128        }
129
130        // The previous piece of every current particle, for the warm start.
131        let mut previous = vec![u32::MAX; n];
132        for (pi, piece) in self.volume_pieces.iter().enumerate() {
133            for &q in &piece.particles {
134                let p = if remap.is_empty() {
135                    q
136                } else {
137                    remap.get(q as usize).copied().unwrap_or(u32::MAX)
138                };
139                if let Some(slot) = previous.get_mut(p as usize) {
140                    *slot = pi as u32;
141                }
142            }
143        }
144
145        let mut pieces = Vec::new();
146        let mut elements: Vec<[u32; DIM]> = Vec::new();
147        for group in groups {
148            elements.clear();
149            elements.extend(group.iter().map(|&e| self.boundary[e as usize]));
150            if !surface_is_closed(&elements) {
151                continue;
152            }
153            let mut particles: Vec<u32> = elements.iter().flatten().copied().collect();
154            particles.sort_unstable();
155            particles.dedup();
156            let rest_volume = Self::boundary_volume(&elements, &rest_position);
157            let old = self
158                .volume_pieces
159                .get(previous[particles[0] as usize] as usize);
160            pieces.push(SoftVolumePiece {
161                elements: group,
162                particles,
163                rest_volume,
164                impulse: old.map_or(0.0, |piece| piece.impulse),
165                inverted: old.is_some_and(|piece| piece.inverted),
166            });
167        }
168        pieces
169    }
170}