Skip to main content

rapier3d/dynamics/soft_body/collision_mesh/
collision_mesh_binding.rs

1//! Binding a collision mesh to a soft body's cells: the requested binding modes, the errors, and
2//! the barycentric bindings themselves.
3
4use crate::alloc_prelude::*;
5use crate::math::{DIM, Real, Vector};
6#[cfg(not(feature = "std"))]
7#[allow(unused_imports)]
8use simba::scalar::{ComplexField as _, RealField as _};
9
10use super::{SoftBody, SoftBodyCell, SoftMeshCellBinding};
11
12/// The barycentric coordinates of `point` in the cell `[a, b, c(, d)]`, extrapolating outside it.
13///
14/// `None` for a degenerate cell, which has no frame to express the point in.
15fn barycentric(point: Vector, cell: [Vector; DIM + 1]) -> Option<[Real; DIM + 1]> {
16    let frame = SoftBody::cell_edge_matrix(cell);
17    if SoftBody::cell_volume(cell).abs() <= Real::EPSILON {
18        return None;
19    }
20
21    let coords = frame.inverse() * (point - cell[0]);
22    let mut weights = [0.0; DIM + 1];
23    weights[0] = 1.0;
24
25    for k in 0..DIM {
26        weights[k + 1] = coords[k];
27        weights[0] -= coords[k];
28    }
29
30    Some(weights)
31}
32
33/// The squared distance from `point` to the cell, zero inside it.
34fn distance_squared_to_cell(point: Vector, cell: [Vector; DIM + 1]) -> Real {
35    use parry::query::PointQuery;
36
37    #[cfg(feature = "dim2")]
38    {
39        let triangle = parry::shape::Triangle::new(cell[0], cell[1], cell[2]);
40        (triangle.project_local_point(point, true).point - point).length_squared()
41    }
42    #[cfg(feature = "dim3")]
43    {
44        // The distance to the closest of the four faces, zero when the point is behind all of
45        // them.
46        // TODO: refactor this into `Tetrahedron`.
47        let faces = [
48            [cell[0], cell[2], cell[1]],
49            [cell[0], cell[1], cell[3]],
50            [cell[0], cell[3], cell[2]],
51            [cell[1], cell[2], cell[3]],
52        ];
53        let mut inside = true;
54        let mut closest = Real::MAX;
55
56        for face in faces {
57            let triangle = parry::shape::Triangle::new(face[0], face[1], face[2]);
58            let normal = (face[1] - face[0]).cross(face[2] - face[0]);
59            inside &= (point - face[0]).dot(normal) <= 0.0;
60            closest = closest
61                .min((triangle.project_local_point(point, true).point - point).length_squared());
62        }
63
64        if inside { 0.0 } else { closest }
65    }
66}
67
68/// Binds a mesh to a soft body's cells: for every vertex, the closest cell and the vertex's
69/// barycentric coordinates in it.
70pub(crate) fn bind_to_cells(
71    vertices: &[Vector],
72    indices: &[[u32; DIM]],
73    cells: &[SoftBodyCell],
74    positions: &[Vector],
75) -> Option<Vec<SoftMeshCellBinding>> {
76    use parry::bounding_volume::Aabb;
77    use parry::partitioning::{Bvh, BvhBuildStrategy};
78    use parry::query::PointQuery;
79
80    if vertices.is_empty() || indices.is_empty() || cells.is_empty() {
81        return None;
82    }
83
84    let cell_points = |cell: &SoftBodyCell| cell.vertices.map(|vid| positions[vid as usize]);
85    let leaves: Vec<Aabb> = cells
86        .iter()
87        .map(|cell| Aabb::from_points(cell_points(cell).iter().copied()))
88        .collect();
89    let bvh = Bvh::from_leaves(BvhBuildStrategy::Binned, &leaves);
90
91    let mut bindings = Vec::with_capacity(vertices.len());
92
93    for vertex in vertices {
94        // The closest cell: exact at the leaves, bounded by the node Aabbs on the way down.
95        let closest = bvh.find_best(
96            Real::MAX,
97            |node, _| node.aabb().distance_to_local_point(*vertex, true),
98            |cell, _| {
99                let points = cell_points(&cells[cell as usize]);
100                Some(distance_squared_to_cell(*vertex, points).sqrt())
101            },
102        );
103
104        let (cell, _) = closest?;
105        let binding = match barycentric(*vertex, cell_points(&cells[cell as usize])) {
106            Some(weights) => SoftMeshCellBinding { cell, weights },
107            // The closest cell can be a sliver too flat to invert: the closest cell that is not
108            // holds the vertex instead (this checks all the cells so it's slow, bet it should
109            // almost never happen unless the input mesh is malformed).
110            None => {
111                let mut by_distance: Vec<(Real, u32)> = cells
112                    .iter()
113                    .enumerate()
114                    .map(|(id, cell)| {
115                        (
116                            distance_squared_to_cell(*vertex, cell_points(cell)),
117                            id as u32,
118                        )
119                    })
120                    .collect();
121                by_distance.sort_unstable_by(|a, b| a.0.total_cmp(&b.0));
122                by_distance.iter().find_map(|(_, cell)| {
123                    let weights = barycentric(*vertex, cell_points(&cells[*cell as usize]))?;
124                    Some(SoftMeshCellBinding {
125                        cell: *cell,
126                        weights,
127                    })
128                })?
129            }
130        };
131        bindings.push(binding);
132    }
133
134    Some(bindings)
135}
136
137/// How a collision mesh should be bound to its cluster, requested at insertion
138/// ([`crate::geometry::ColliderSet::insert_deformable`]).
139#[derive(Clone, Debug)]
140pub enum SoftMeshBindingMode {
141    /// Vertex `i` of the mesh is the particle `particles[i]` of the cluster.
142    Direct {
143        /// The particle backing each vertex.
144        particles: Vec<u32>,
145    },
146    /// Each vertex is the particle of the cluster closest to it, within `eps`: the common case,
147    /// where the collision mesh was authored from the same vertices as the computational mesh.
148    DirectByPosition {
149        /// How far a vertex may sit from its particle.
150        eps: Real,
151    },
152    /// Each vertex rides the cell of the cluster holding it (cage simulation): the cells only
153    /// have to contain the mesh, not resolve it.
154    Skinned,
155}
156
157/// How a deformable collider follows its soft body, and the collision settings that are not the
158/// collider's own.
159#[derive(Clone, Debug)]
160pub struct SoftMeshBinding {
161    pub(crate) mode: SoftMeshBindingMode,
162    pub(crate) self_contacts: bool,
163}
164
165impl SoftMeshBinding {
166    /// Binds vertex `i` of the mesh to `particles[i]`, which must belong to the cluster.
167    pub fn direct(particles: Vec<u32>) -> Self {
168        Self::with_mode(SoftMeshBindingMode::Direct { particles })
169    }
170
171    /// Binds every vertex to the particle of the cluster closest to it, within `eps`.
172    pub fn direct_by_position(eps: Real) -> Self {
173        Self::with_mode(SoftMeshBindingMode::DirectByPosition { eps })
174    }
175
176    /// Binds every vertex to the cell of the cluster holding it (cage simulation).
177    pub fn skinned() -> Self {
178        Self::with_mode(SoftMeshBindingMode::Skinned)
179    }
180
181    fn with_mode(mode: SoftMeshBindingMode) -> Self {
182        Self {
183            mode,
184            self_contacts: false,
185        }
186    }
187
188    /// Enables collisions of this mesh with itself.
189    pub fn self_contacts(mut self, enabled: bool) -> Self {
190        self.self_contacts = enabled;
191        self
192    }
193}
194
195/// Why a mesh could not be bound to a cluster
196/// ([`crate::geometry::ColliderSet::insert_deformable`]).
197#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
198pub enum SoftBindingError {
199    /// The parent is not a live cluster proxy ([`crate::dynamics::SoftBodyCluster::proxy`]).
200    #[error("the parent body is not a soft-body cluster proxy")]
201    NotAClusterProxy,
202    /// The collider's shape is not a polyline (2D) or triangle mesh (3D).
203    #[error("a deformable collider must be a polyline or a trimesh")]
204    UnsupportedShape,
205    /// The collider's shape does not have the deformable flag (`TriMeshFlags::DEFORMABLE`,
206    /// `PolylineFlags::DEFORMABLE`), so its vertices cannot be moved in place.
207    #[error("a deformable collider's shape must have the deformable flag")]
208    NotDeformable,
209    /// The mesh has no vertex, or no element.
210    #[error("the mesh has no vertex or no element")]
211    DegenerateMesh,
212    /// A direct binding names a particle that does not belong to the cluster.
213    #[error("vertex {vertex} is bound to particle {particle}, which is not in the cluster")]
214    VertexOutsideCluster {
215        /// The mesh vertex.
216        vertex: u32,
217        /// The particle it names.
218        particle: u32,
219    },
220    /// A direct binding by position found no particle of the cluster close enough to a vertex.
221    #[error("no particle of the cluster is close enough to vertex {vertex}")]
222    UnmatchedVertex {
223        /// The mesh vertex.
224        vertex: u32,
225    },
226    /// A skinned binding found no cell of the cluster to hold a vertex.
227    #[error("no cell of the cluster can hold vertex {vertex}")]
228    UnboundVertex {
229        /// The mesh vertex.
230        vertex: u32,
231    },
232}
233
234/// The binding of `position` to the closest cell of `body` accepted by `allowed` (`None` without
235/// any such cell).
236// TODO: optimize neighrest neighbor search with the BVH?
237pub(super) fn closest_cell_binding(
238    body: &SoftBody,
239    position: Vector,
240    allowed: impl Fn(u32) -> bool,
241) -> Option<SoftMeshCellBinding> {
242    let cell_points =
243        |cell: &SoftBodyCell| cell.vertices.map(|v| body.particles[v as usize].position);
244    let mut by_distance: Vec<(Real, u32)> = body
245        .cells
246        .iter()
247        .enumerate()
248        .filter(|(id, _)| allowed(*id as u32))
249        .map(|(id, cell)| {
250            (
251                distance_squared_to_cell(position, cell_points(cell)),
252                id as u32,
253            )
254        })
255        .collect();
256    by_distance.sort_unstable_by(|a, b| a.0.total_cmp(&b.0));
257    by_distance.iter().find_map(|(_, cell)| {
258        let weights = barycentric(position, cell_points(&body.cells[*cell as usize]))?;
259        Some(SoftMeshCellBinding {
260            cell: *cell,
261            weights,
262        })
263    })
264}