1use crate::alloc_prelude::*;
4
5use crate::dynamics::{IntegrationParameters, RigidBodySet, SoftBody, SoftCollisionMesh};
6use crate::geometry::Collider;
7use crate::math::{DIM, Real, Vector};
8use crate::utils::{DotProduct, OrthonormalBasis};
9use parry::utils::hashmap::HashMap;
10#[cfg(not(feature = "std"))]
11#[allow(unused_imports)]
12use simba::scalar::{ComplexField as _, RealField as _};
13
14use super::soft_contacts_classify::{
15 classify_inside, classify_skin, classify_skin_rigid, ring_depths,
16};
17use super::{Side, SoftDetectionCtx, SoftRigidPatch, SoftVertexPass, SoftVolumePatch, body_frozen};
18
19pub(super) fn volume_split(params: &IntegrationParameters) -> u32 {
21 if params.soft_bodies.recovery.overlap_multi_volume {
22 params.soft_bodies.recovery.overlap_split.max(1)
23 } else {
24 1
25 }
26}
27
28pub(super) fn fill_depths(
31 mesh: &SoftCollisionMesh,
32 body: &SoftBody,
33 inside: &[bool],
34 co: &Collider,
35 depth: &mut Vec<Real>,
36) -> bool {
37 depth.clear();
38 depth.resize(mesh.vertex_count(), Real::NEG_INFINITY);
39 let mut any = false;
40 for v in 0..mesh.vertex_count() {
41 if inside[v] {
42 let p = mesh.vertex(body, v);
43 let proj = co.shape().project_point(co.position(), p, false);
44 depth[v] = (proj.point - p).length();
45 any = true;
46 }
47 }
48 any
49}
50
51fn self_tangled(m: &SoftCollisionMesh) -> bool {
54 m.orientation_unreliable || m.crossed_partners.contains(&m.collider())
55}
56
57pub(super) fn detect_volume_patch(
61 pass: &SoftVertexPass,
62 (eb, eb_mesh, eb_handle, eb_co): Side<'_>,
63 (vb, vb_mesh, _, vb_co): Side<'_>,
64 ctx: &SoftDetectionCtx,
65) -> Option<SoftVolumePatch> {
66 let params = ctx.params;
67 let recovery = ¶ms.soft_bodies.recovery;
68 let closed_pair = vb_mesh.is_solid() && eb_mesh.is_solid();
69 let crossed = !pass.cross_pairs.is_empty() && !pass.cross_tangled_vb_elements.is_empty();
70 if !recovery.overlap_constraints
71 || !closed_pair
72 || !crossed
73 || (recovery.overlap_skip_self_tangled && (self_tangled(vb_mesh) || self_tangled(eb_mesh)))
74 {
75 return None;
76 }
77 let skins = vb_co.contact_skin() + eb_co.contact_skin();
78 let mut depth_vb = Vec::new();
79 let mut depth_eb = Vec::new();
80 let band = params.prediction_distance() + ctx.motion_margin(eb_co) + ctx.motion_margin(vb_co);
81 let any = if recovery.overlap_skin_volume {
82 let rest = (1.0 - recovery.overlap_kept_depth.clamp(0.0, 1.0)) * skins;
83 let reach = skins + band;
84 let (a1, _) = classify_skin(
85 vb_mesh,
86 vb,
87 &pass.cross_tangled_vb_elements,
88 (eb_mesh, eb, eb_co),
89 reach,
90 rest,
91 &mut depth_vb,
92 );
93 let (a2, _) = classify_skin(
94 eb_mesh,
95 eb,
96 &pass.cross_tangled_elements,
97 (vb_mesh, vb, vb_co),
98 reach,
99 rest,
100 &mut depth_eb,
101 );
102 a1 || a2
103 } else {
104 let mut inside_vb = Vec::new();
107 let mut inside_eb = Vec::new();
108 classify_inside(
109 vb_mesh,
110 vb,
111 &pass.cross_tangled_vb_elements,
112 (eb_mesh, eb),
113 &mut inside_vb,
114 );
115 classify_inside(
116 eb_mesh,
117 eb,
118 &pass.cross_tangled_elements,
119 (vb_mesh, vb),
120 &mut inside_eb,
121 );
122 let a1 = fill_depths(vb_mesh, vb, &inside_vb, eb_co, &mut depth_vb);
123 let a2 = fill_depths(eb_mesh, eb, &inside_eb, vb_co, &mut depth_eb);
124 if a1 {
125 ring_depths(vb_mesh, vb, eb_co, 0.0, &mut depth_vb);
126 }
127 if a2 {
128 ring_depths(eb_mesh, eb, vb_co, 0.0, &mut depth_eb);
129 }
130 a1 || a2
131 };
132 if !any {
133 return None;
134 }
135 let band = if recovery.overlap_skin_volume {
136 band
137 } else {
138 0.0
139 };
140 let own = VolumeSide {
141 body: eb,
142 mesh: eb_mesh,
143 vertices: patch_vertices(eb_mesh, eb, &depth_eb, band),
144 };
145 let other = VolumeSide {
146 body: vb,
147 mesh: vb_mesh,
148 vertices: patch_vertices(vb_mesh, vb, &depth_vb, band),
149 };
150 Some(SoftVolumePatch {
151 own: eb_handle,
152 bins: volume_bins(&own, Some(&other), volume_split(params)),
153 slot_offset: u32::MAX,
154 })
155}
156
157pub(super) fn detect_rigid_patch(
161 (sb, mesh, handle, surface_co): Side<'_>,
162 other_co: &Collider,
163 bodies: &RigidBodySet,
164 ctx: &SoftDetectionCtx,
165) -> Option<SoftRigidPatch> {
166 let params = ctx.params;
167 let recovery = ¶ms.soft_bodies.recovery;
168 if !recovery.overlap_constraints
169 || !recovery.overlap_rigid
170 || body_frozen(sb)
171 || !mesh.collision_enabled()
172 || other_co.is_sensor()
173 {
174 return None;
175 }
176 if !mesh.is_solid()
179 || (recovery.overlap_skip_self_tangled
180 && (mesh.orientation_unreliable || mesh.crossed_partners.contains(&handle)))
181 {
182 return None;
183 }
184 let skin = recovery.overlap_skin_volume;
185 let other_rb = other_co.parent().and_then(|h| bodies.get(h));
186 let skins = surface_co.contact_skin() + other_co.contact_skin();
187 let rest = if skin {
188 (1.0 - recovery.overlap_kept_depth.clamp(0.0, 1.0)) * skins
189 } else {
190 0.0
191 };
192 let band = params.prediction_distance() + ctx.motion_margin(surface_co);
193 let reach = skin.then_some(skins + band);
194 let mut depth = Vec::new();
195 let (any, _) = classify_skin_rigid(mesh, sb, other_co, reach, rest, &mut depth);
196 if !any {
197 return None;
198 }
199 let own = VolumeSide {
200 body: sb,
201 mesh,
202 vertices: patch_vertices(mesh, sb, &depth, if skin { band } else { 0.0 }),
203 };
204 let bins = volume_bins(&own, None, volume_split(params));
205 Some(SoftRigidPatch {
206 bins,
207 depth: if recovery.overlap_patch_constraints != crate::dynamics::SoftPatchConstraints::Keep
208 {
209 depth
210 } else {
211 Vec::new()
212 },
213 com: other_rb.map_or(other_co.translation(), |rb| rb.center_of_mass()),
214 rot: other_co.position().rotation,
215 })
216}
217
218fn partial_volume_gradients(
222 mesh: &SoftCollisionMesh,
223 body: &SoftBody,
224 sign: Real,
225 mut keep: impl FnMut(usize) -> Option<Real>,
226 grads: &mut [Vector],
227) {
228 for (f, el) in mesh.indices().iter().enumerate() {
229 let Some(w) = keep(f) else {
230 continue;
231 };
232 #[cfg(feature = "dim2")]
233 {
234 let (a, b) = (
235 mesh.vertex(body, el[0] as usize),
236 mesh.vertex(body, el[1] as usize),
237 );
238 let d = b - a;
239 let g = Vector::new(d.y, -d.x) * (0.5 * w * sign);
240 grads[el[0] as usize] += g;
241 grads[el[1] as usize] += g;
242 }
243 #[cfg(feature = "dim3")]
244 {
245 let (a, b, c) = (
246 mesh.vertex(body, el[0] as usize),
247 mesh.vertex(body, el[1] as usize),
248 mesh.vertex(body, el[2] as usize),
249 );
250 let n = (b - a).cross(c - a);
251 let g = n * (w * sign / 6.0);
252 for &v in el {
253 grads[v as usize] += g;
254 }
255 }
256 }
257}
258
259#[derive(Copy, Clone)]
263pub(super) struct PatchVertex {
264 vertex: u32,
265 grad: Vector,
266 depth: Real,
267}
268
269pub(super) struct VolumeSide<'a> {
271 pub(super) body: &'a SoftBody,
272 pub(super) mesh: &'a SoftCollisionMesh,
273 pub(super) vertices: Vec<PatchVertex>,
274}
275
276#[derive(Clone, Debug)]
280pub struct VolumeBin {
281 pub own: Vec<(u32, Vector)>,
283 pub other: Vec<(u32, Vector)>,
285 pub volume: Real,
287 pub normal: Vector,
289 pub center: Vector,
291 pub enabled: bool,
293 pub impulse: Real,
296}
297
298fn clipped_fraction(s: &[Real]) -> Real {
302 let inside: Vec<usize> = (0..s.len()).filter(|&i| s[i] >= 0.0).collect();
303 if inside.len() == s.len() {
304 return 1.0;
305 }
306 if inside.is_empty() {
307 return 0.0;
308 }
309 let share = |a: usize, b: usize| -> Real {
312 if s[b] == Real::NEG_INFINITY {
313 0.5
314 } else {
315 (s[a] / (s[a] - s[b])).clamp(0.0, 1.0)
316 }
317 };
318 #[cfg(feature = "dim2")]
319 {
320 let a = inside[0];
321 share(a, 1 - a)
322 }
323 #[cfg(feature = "dim3")]
324 {
325 if inside.len() == 1 {
326 let a = inside[0];
327 let (b, c) = ((a + 1) % 3, (a + 2) % 3);
328 share(a, b) * share(a, c)
329 } else {
330 let o = (0..3).find(|i| !inside.contains(i)).unwrap();
331 let (b, c) = ((o + 1) % 3, (o + 2) % 3);
332 let out_share = |i: usize| {
333 if s[o] == Real::NEG_INFINITY {
334 0.5
335 } else {
336 (s[i] / (s[i] - s[o])).clamp(0.0, 1.0)
337 }
338 };
339 1.0 - (1.0 - out_share(b)) * (1.0 - out_share(c))
340 }
341 }
342}
343
344pub(super) fn patch_vertices(
348 mesh: &SoftCollisionMesh,
349 body: &SoftBody,
350 depth: &[Real],
351 band: Real,
352) -> Vec<PatchVertex> {
353 let member = |v: usize| depth[v] > Real::NEG_INFINITY;
354 let mut grads = vec![Vector::ZERO; mesh.vertex_count()];
355 partial_volume_gradients(
359 mesh,
360 body,
361 mesh.winding_sign(),
362 |f| {
363 let el = mesh.element(f);
364 let s: [Real; DIM] = core::array::from_fn(|k| {
365 el.get(k).map_or(Real::NEG_INFINITY, |&v| {
366 let d = depth[v as usize];
367 if d == Real::NEG_INFINITY { d } else { d + band }
368 })
369 });
370 let w = clipped_fraction(&s[..el.len().min(DIM)]);
371 (w > 0.0).then_some(w)
372 },
373 &mut grads,
374 );
375 grads
378 .iter()
379 .enumerate()
380 .filter(|(_, g)| **g != Vector::ZERO)
381 .map(|(v, g)| PatchVertex {
382 vertex: v as u32,
383 grad: *g,
384 depth: if member(v) && depth[v] >= -band {
385 depth[v]
386 } else {
387 0.0
388 },
389 })
390 .collect()
391}
392
393fn side_entries(side: &VolumeSide, picked: &[usize]) -> Vec<(u32, Vector)> {
396 let mut by_particle: HashMap<u32, Vector> = HashMap::default();
397 for &i in picked {
398 let pv = side.vertices[i];
399 let (anchors, weights) = side.mesh.vertex_anchors(side.body, pv.vertex as usize);
400 for (p, w) in anchors.iter().zip(&weights) {
401 if *p != u32::MAX && *w != 0.0 {
402 *by_particle.entry(*p).or_insert(Vector::ZERO) += pv.grad * *w;
403 }
404 }
405 }
406 let mut entries: Vec<(u32, Vector)> = by_particle.into_iter().collect();
407 entries.sort_by_key(|e| e.0);
408 entries
409}
410
411pub(super) fn volume_bins(
415 own: &VolumeSide,
416 other: Option<&VolumeSide>,
417 split: u32,
418) -> Vec<VolumeBin> {
419 let n_own = own.vertices.len();
420 let n_other = other.map_or(0, |o| o.vertices.len());
421 if n_own + n_other == 0 {
422 return Vec::new();
423 }
424 let pos = |i: usize| -> Vector {
427 if i < n_own {
428 own.mesh.vertex(own.body, own.vertices[i].vertex as usize)
429 } else {
430 let o = other.unwrap();
431 o.mesh.vertex(o.body, o.vertices[i - n_own].vertex as usize)
432 }
433 };
434 let pv = |i: usize| -> PatchVertex {
435 if i < n_own {
436 own.vertices[i]
437 } else {
438 other.unwrap().vertices[i - n_own]
439 }
440 };
441 let total = n_own + n_other;
442 let mut normal_sum = Vector::ZERO;
443 for i in 0..total {
444 normal_sum += if i < n_own { pv(i).grad } else { -pv(i).grad };
445 }
446 let normal = normal_sum.try_normalize().unwrap_or(Vector::ZERO);
447 let mut cells: Vec<Vec<usize>> = Vec::new();
449 let split = split.max(1) as usize;
450 if split > 1 && normal != Vector::ZERO && total >= 2 {
451 let tangents = normal.orthonormal_basis();
452 let mut center = Vector::ZERO;
453 for i in 0..total {
454 center += pos(i);
455 }
456 center /= total as Real;
457 let coords: Vec<[Real; DIM - 1]> = (0..total)
458 .map(|i| core::array::from_fn(|j| (pos(i) - center).gdot(tangents[j])))
459 .collect();
460 let mut lo = [Real::MAX; DIM - 1];
461 let mut hi = [-Real::MAX; DIM - 1];
462 for c in &coords {
463 for j in 0..DIM - 1 {
464 lo[j] = lo[j].min(c[j]);
465 hi[j] = hi[j].max(c[j]);
466 }
467 }
468 let cell_of = |c: &[Real; DIM - 1]| -> usize {
469 let mut idx = 0;
470 for j in 0..DIM - 1 {
471 let span = hi[j] - lo[j];
472 let k = if span > 0.0 {
473 (((c[j] - lo[j]) / span) * split as Real) as usize
474 } else {
475 0
476 };
477 idx = idx * split + k.min(split - 1);
478 }
479 idx
480 };
481 let n_cells = split.pow((DIM - 1) as u32);
482 cells.resize(n_cells, Vec::new());
483 for (i, c) in coords.iter().enumerate() {
484 cells[cell_of(c)].push(i);
485 }
486 let two_sided = |cell: &Vec<usize>| {
487 other.is_none() || (cell.iter().any(|&i| i < n_own) && cell.iter().any(|&i| i >= n_own))
488 };
489 let cell_center = |k: usize| -> [Real; DIM - 1] {
490 let mut c = [0.0; DIM - 1];
491 let mut idx = k;
492 for j in (0..DIM - 1).rev() {
493 c[j] = (idx % split) as Real;
494 idx /= split;
495 }
496 c
497 };
498 let keep: Vec<bool> = cells
499 .iter()
500 .map(|c| !c.is_empty() && two_sided(c))
501 .collect();
502 if keep.iter().any(|&k| k) {
503 for k in 0..n_cells {
504 if keep[k] || cells[k].is_empty() {
505 continue;
506 }
507 let ck = cell_center(k);
508 let mut best = (Real::MAX, usize::MAX);
509 for t in 0..n_cells {
510 if !keep[t] {
511 continue;
512 }
513 let ct = cell_center(t);
514 let d2: Real = (0..DIM - 1).map(|j| (ck[j] - ct[j]).powi(2)).sum();
515 if d2 < best.0 {
516 best = (d2, t);
517 }
518 }
519 let moved = core::mem::take(&mut cells[k]);
520 cells[best.1].extend(moved);
521 }
522 cells.retain(|c| !c.is_empty());
523 } else {
524 cells.clear();
525 }
526 }
527 if cells.is_empty() {
528 cells.push((0..total).collect());
529 }
530 cells
531 .iter()
532 .map(|cell| {
533 let own_idx: Vec<usize> = cell.iter().copied().filter(|&i| i < n_own).collect();
534 let other_idx: Vec<usize> = cell
535 .iter()
536 .filter(|&&i| i >= n_own)
537 .map(|&i| i - n_own)
538 .collect();
539 let mut volume = 0.0;
540 let mut normal_sum = Vector::ZERO;
541 let mut center = Vector::ZERO;
542 let mut weight = 0.0;
543 for &i in cell {
544 let v = pv(i);
545 let a = v.grad.length();
546 volume += v.depth * a;
547 normal_sum += if i < n_own { v.grad } else { -v.grad };
548 center += pos(i) * a;
549 weight += a;
550 }
551 VolumeBin {
552 own: side_entries(own, &own_idx),
553 other: other.map_or_else(Vec::new, |o| side_entries(o, &other_idx)),
554 volume,
555 normal: normal_sum.try_normalize().unwrap_or(Vector::ZERO),
556 center: if weight > 0.0 {
557 center / weight
558 } else {
559 Vector::ZERO
560 },
561 enabled: true,
562 impulse: 0.0,
563 }
564 })
565 .collect()
566}