rapier3d/dynamics/soft_body/collision_mesh/
collision_mesh_binding.rs1use 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
12fn 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
33fn 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 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
68pub(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 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 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#[derive(Clone, Debug)]
140pub enum SoftMeshBindingMode {
141 Direct {
143 particles: Vec<u32>,
145 },
146 DirectByPosition {
149 eps: Real,
151 },
152 Skinned,
155}
156
157#[derive(Clone, Debug)]
160pub struct SoftMeshBinding {
161 pub(crate) mode: SoftMeshBindingMode,
162 pub(crate) self_contacts: bool,
163}
164
165impl SoftMeshBinding {
166 pub fn direct(particles: Vec<u32>) -> Self {
168 Self::with_mode(SoftMeshBindingMode::Direct { particles })
169 }
170
171 pub fn direct_by_position(eps: Real) -> Self {
173 Self::with_mode(SoftMeshBindingMode::DirectByPosition { eps })
174 }
175
176 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 pub fn self_contacts(mut self, enabled: bool) -> Self {
190 self.self_contacts = enabled;
191 self
192 }
193}
194
195#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
198pub enum SoftBindingError {
199 #[error("the parent body is not a soft-body cluster proxy")]
201 NotAClusterProxy,
202 #[error("a deformable collider must be a polyline or a trimesh")]
204 UnsupportedShape,
205 #[error("a deformable collider's shape must have the deformable flag")]
208 NotDeformable,
209 #[error("the mesh has no vertex or no element")]
211 DegenerateMesh,
212 #[error("vertex {vertex} is bound to particle {particle}, which is not in the cluster")]
214 VertexOutsideCluster {
215 vertex: u32,
217 particle: u32,
219 },
220 #[error("no particle of the cluster is close enough to vertex {vertex}")]
222 UnmatchedVertex {
223 vertex: u32,
225 },
226 #[error("no cell of the cluster can hold vertex {vertex}")]
228 UnboundVertex {
229 vertex: u32,
231 },
232}
233
234pub(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}