Skip to main content

rapier3d/dynamics/soft_body/
soft_body_contacts.rs

1//! The contact records a soft body keeps between steps (edge, vertex, overlap and volume contacts) and their debug-render accessors.
2use super::{SoftBody, SoftCollisionMesh, SoftMeshRef};
3use crate::alloc_prelude::*;
4use crate::math::{AngVector, DIM, Pose, Real, Vector};
5
6/// An edge-vs-edge contact between two surface edges (segments in 2D, triangle edges in 3D):
7/// the impulses of the last step, matched by edge ids for warm starting.
8#[derive(Copy, Clone, Debug)]
9#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
10pub(crate) struct SoftEdgeContact {
11    /// The other edge's collision mesh (this mesh for a self contact).
12    pub other: SoftMeshRef,
13    /// Edge id in this body (element id in 2D), and in the other body.
14    pub edge: u32,
15    pub other_edge: u32,
16    /// Normal impulse and world-space friction impulse.
17    pub impulse: Real,
18    pub tangent_impulse: Vector,
19}
20
21/// A vertex-vs-surface contact between a surface vertex of another soft body (or of this body:
22/// self contacts) and one of this body's surface elements: the impulses of the last step, matched
23/// by ids for warm starting.
24#[derive(Copy, Clone, Debug)]
25#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
26pub(crate) struct SoftVertexContact {
27    /// The vertex's collision mesh (this mesh for a self contact).
28    pub other: SoftMeshRef,
29    /// The vertex (particle index in `other`) and the element (in this body). `flipped`: the
30    /// vertex is this body's and the element the other's (the other body is fully pinned and
31    /// not simulated, this body assembles both vertex passes).
32    pub vertex: u32,
33    pub element: u32,
34    pub flipped: bool,
35    /// Normal impulse and world-space friction impulse.
36    pub impulse: Real,
37    pub tangent_impulse: Vector,
38}
39
40/// The progress of one pair's overlap correction across steps: a pair whose volume estimate stops
41/// improving stands its positional correction down after `SoftRecoverySettings::overlap_patience`
42/// steps (a static compression) and re-arms when the estimate drops by `overlap_progress_margin`.
43#[derive(Copy, Clone, Debug)]
44#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
45pub(crate) struct SoftOverlapState {
46    /// The other collider of the pair.
47    pub other: crate::geometry::ColliderHandle,
48    /// Lowest volume estimate reached so far.
49    pub best_error: Real,
50    /// Steps since the estimate last improved by the margin.
51    pub steps_stuck: u16,
52    /// Whether the correction has stood down (the patience ran out): re-arming then takes
53    /// a clearly better estimate, not the noise of a pair squeezed against what holds it.
54    pub stalled: bool,
55}
56
57/// The impulses a volume contact accumulated in the last step, per pair and per particle (see
58/// `SoftOverlapConstraint`), plus the rigid side's: the hard constraints warm start from them
59/// before the elastic constraints of the first substep, so resting springs see the contact force.
60#[derive(Clone, Debug)]
61pub(crate) struct SoftOverlapWarm {
62    pub other: crate::geometry::ColliderHandle,
63    /// The owner body's particles and their impulses.
64    pub own: Vec<(u32, Vector)>,
65    /// The other soft body's particles and their impulses.
66    pub other_soft: Vec<(u32, Vector)>,
67    /// The rigid side's linear and angular impulse.
68    pub rigid: (Vector, AngVector),
69}
70
71/// One volume constraint assembled at the last step (a bin of a pair's volume contact, see
72/// `SoftOverlapConstraint`), kept for debug rendering: center, normal (from the owner body into the
73/// other side), signed volume estimate, and every entry's position and volume gradient.
74#[derive(Clone, Debug)]
75pub struct SoftVolumeContact {
76    /// The area-weighted center of the constraint's patch.
77    pub center: Vector,
78    /// The contact normal, from the owner body into the other side.
79    pub normal: Vector,
80    /// The signed volume estimate (negative: speculative slack).
81    pub volume: Real,
82    /// The entries' positions and volume gradients.
83    pub gradients: Vec<(Vector, Vector)>,
84}
85
86impl SoftBody {
87    /// The world position of the `i`-th vertex of `mesh` (which must belong to `sb`), or `None`
88    /// if the mesh does not have that vertex.
89    fn mesh_vertex(sb: &SoftBody, mesh: &SoftCollisionMesh, i: u32) -> Option<Vector> {
90        ((i as usize) < mesh.vertex_count()).then(|| mesh.vertex(sb, i as usize))
91    }
92
93    /// The `i`-th edge of `mesh` (which must belong to `sb`) as a world segment.
94    fn mesh_edge(sb: &SoftBody, mesh: &SoftCollisionMesh, i: u32) -> Option<parry::shape::Segment> {
95        #[cfg(feature = "dim2")]
96        let vertices = *mesh.indices.get(i as usize)?;
97        #[cfg(feature = "dim3")]
98        let vertices = *mesh.edges.get(i as usize)?;
99        Some(parry::shape::Segment::new(
100            Self::mesh_vertex(sb, mesh, vertices[0])?,
101            Self::mesh_vertex(sb, mesh, vertices[1])?,
102        ))
103    }
104
105    /// The edge-vs-edge contacts this soft body's collision meshes owned at the last step (self
106    /// contacts and contacts with other soft bodies' meshes), as world segments joining the two
107    /// edges' closest points, i.e. the contact's witness points (for debug rendering).
108    pub fn edge_contact_segments<'a>(
109        &'a self,
110        soft_bodies: &'a super::SoftBodySet,
111    ) -> impl Iterator<Item = (Vector, Vector)> + 'a {
112        self.meshes()
113            .flat_map(|mesh| mesh.edge_contacts.iter().map(move |c| (mesh, c)))
114            .filter_map(move |(mesh, c)| {
115                // `edge` indexes the mesh owning the contact, `other_edge` the mesh it hit.
116                let other = soft_bodies.get(c.other.body)?;
117                let seg1 = Self::mesh_edge(self, mesh, c.edge)?;
118                let seg2 = Self::mesh_edge(other, other.mesh(c.other.id)?, c.other_edge)?;
119                let (loc1, loc2) =
120                    parry::query::details::closest_points_segment_segment_with_locations(
121                        &Pose::IDENTITY,
122                        &seg1,
123                        &seg2,
124                    );
125                Some((seg1.point_at(&loc1), seg2.point_at(&loc2)))
126            })
127    }
128
129    /// The volume constraints this soft body's collision meshes assembled at the last step (see
130    /// `SoftVolumeContact`; the pair's owner mesh keeps them), for debug rendering.
131    pub fn volume_contacts(&self) -> impl Iterator<Item = &SoftVolumeContact> + '_ {
132        self.meshes().flat_map(|mesh| mesh.volume_contacts.iter())
133    }
134
135    /// The vertex-vs-surface contacts against this soft body's collision meshes at the last step
136    /// (self contacts and other soft bodies' vertices), as world segments joining the vertex to
137    /// its projection on the element, i.e. the contact's witness points (for debug rendering).
138    pub fn vertex_contact_segments<'a>(
139        &'a self,
140        soft_bodies: &'a super::SoftBodySet,
141    ) -> impl Iterator<Item = (Vector, Vector)> + 'a {
142        use parry::query::PointQuery;
143
144        self.meshes()
145            .flat_map(|mesh| mesh.vertex_contacts.iter().map(move |c| (mesh, c)))
146            .filter_map(move |(mesh, c)| {
147                let other = soft_bodies.get(c.other.body)?;
148                let other_mesh = other.mesh(c.other.id)?;
149                // `vertex` indexes the other mesh and `element` the mesh owning the contact,
150                // unless the contact is flipped (see `SoftVertexContact`).
151                let ((vertex_body, vertex_mesh), (element_body, element_mesh)) = if c.flipped {
152                    ((self, mesh), (other, other_mesh))
153                } else {
154                    ((other, other_mesh), (self, mesh))
155                };
156                let point = Self::mesh_vertex(vertex_body, vertex_mesh, c.vertex)?;
157                let element = *element_mesh.indices.get(c.element as usize)?;
158                let mut vtx = [Vector::ZERO; DIM];
159                for (dest, v) in vtx.iter_mut().zip(&element) {
160                    *dest = Self::mesh_vertex(element_body, element_mesh, *v)?;
161                }
162                #[cfg(feature = "dim2")]
163                let projection = parry::shape::Segment::new(vtx[0], vtx[1]);
164                #[cfg(feature = "dim3")]
165                let projection = parry::shape::Triangle::new(vtx[0], vtx[1], vtx[2]);
166                Some((point, projection.project_local_point(point, false).point))
167            })
168    }
169}
170
171#[cfg(all(test, feature = "dim2"))]
172mod test {
173    use crate::alloc_prelude::*;
174    use crate::dynamics::soft_body::SoftVertexContact;
175    use crate::prelude::*;
176
177    /// The closest point of segment `ab` to `p`, derived independently of the code under test.
178    fn clamped_projection(p: Vector, a: Vector, b: Vector) -> Vector {
179        let ab = b - a;
180        let t = ((p - a).dot(ab) / ab.length_squared()).clamp(0.0, 1.0);
181        a + ab * t
182    }
183
184    /// A debug-render contact segment must end at the contact's witness point on the element (its
185    /// closest point to the vertex), not at the element's centroid: on a coarse mesh the two are
186    /// far apart, and the contact is drawn where nothing touches.
187    #[test]
188    fn vertex_contact_segment_ends_at_the_witness_point() {
189        let mut world = PhysicsWorld::new();
190        let mut positions = Vec::new();
191        for i in 0..4u32 {
192            for j in 0..4u32 {
193                positions.push(Vector::new(i as Real * 1.7, 10.0 + j as Real * 1.3));
194            }
195        }
196        let mut cells = Vec::new();
197        for i in 0..3u32 {
198            for j in 0..3u32 {
199                let (a, b, c, d) = (
200                    i * 4 + j,
201                    i * 4 + j + 1,
202                    (i + 1) * 4 + j,
203                    (i + 1) * 4 + j + 1,
204                );
205                cells.push([a, b, c]);
206                cells.push([b, d, c]);
207            }
208        }
209        let handle = world.insert_soft_body(SoftBodyBuilder::new(positions).cells(cells));
210        world.step();
211
212        // The (vertex, element) pair whose witness point is furthest from the element's centroid:
213        // the configuration the two candidate renderings disagree on the most.
214        let sb = &world.soft_bodies[handle];
215        let mesh = sb.meshes().next().expect("the body has a collision mesh");
216        let id = mesh.id();
217        let mut best = (0u32, 0u32, -1.0 as Real);
218        for v in 0..mesh.vertex_count() as u32 {
219            for (e, element) in mesh.indices().iter().enumerate() {
220                if element.contains(&v) {
221                    continue;
222                }
223                let p = mesh.vertex(sb, v as usize);
224                let (a, b) = (
225                    mesh.vertex(sb, element[0] as usize),
226                    mesh.vertex(sb, element[1] as usize),
227                );
228                let spread = (clamped_projection(p, a, b) - (a + b) * 0.5).length();
229                if spread > best.2 {
230                    best = (v, e as u32, spread);
231                }
232            }
233        }
234        let (vertex, element, spread) = best;
235        assert!(spread > 0.5, "no off-center pair in this mesh");
236
237        // Inject that contact, then read back what the debug-renderer would draw for it.
238        let sb = world.soft_bodies.get_mut(handle).unwrap();
239        let mesh = sb.mesh_mut(id).unwrap();
240        mesh.vertex_contacts.clear();
241        mesh.vertex_contacts.push(SoftVertexContact {
242            other: super::SoftMeshRef { body: handle, id },
243            vertex,
244            element,
245            flipped: false,
246            impulse: 0.0,
247            tangent_impulse: Vector::ZERO,
248        });
249
250        let sb = &world.soft_bodies[handle];
251        let mesh = sb.meshes().next().unwrap();
252        let segments: Vec<_> = sb.vertex_contact_segments(&world.soft_bodies).collect();
253        assert_eq!(segments.len(), 1);
254
255        let idx = mesh.indices()[element as usize];
256        let p = mesh.vertex(sb, vertex as usize);
257        let (a, b) = (
258            mesh.vertex(sb, idx[0] as usize),
259            mesh.vertex(sb, idx[1] as usize),
260        );
261        let (drawn_from, drawn_to) = segments[0];
262        assert!((drawn_from - p).length() < 1.0e-5);
263        assert!((drawn_to - clamped_projection(p, a, b)).length() < 1.0e-5);
264        // What the centroid rendering would have drawn instead.
265        assert!((drawn_to - (a + b) * 0.5).length() > 0.5);
266    }
267}