1use std::collections::HashMap;
11
12pub fn vertex_triangle_distance(
19 p: [f64; 3],
20 a: [f64; 3],
21 b: [f64; 3],
22 c: [f64; 3],
23) -> (f64, [f64; 3]) {
24 let ab = sub3(b, a);
25 let ac = sub3(c, a);
26 let ap = sub3(p, a);
27
28 let d1 = dot3(ab, ap);
29 let d2 = dot3(ac, ap);
30 if d1 <= 0.0 && d2 <= 0.0 {
31 let bary = [1.0, 0.0, 0.0];
32 return (dot3(ap, ap), bary);
33 }
34
35 let bp = sub3(p, b);
36 let d3 = dot3(ab, bp);
37 let d4 = dot3(ac, bp);
38 if d3 >= 0.0 && d4 <= d3 {
39 let bary = [0.0, 1.0, 0.0];
40 let diff = sub3(p, b);
41 return (dot3(diff, diff), bary);
42 }
43
44 let vc = d1 * d4 - d3 * d2;
45 if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 {
46 let v = d1 / (d1 - d3);
47 let closest = add3(a, scale3(ab, v));
48 let diff = sub3(p, closest);
49 let bary = [1.0 - v, v, 0.0];
50 return (dot3(diff, diff), bary);
51 }
52
53 let cp = sub3(p, c);
54 let d5 = dot3(ab, cp);
55 let d6 = dot3(ac, cp);
56 if d6 >= 0.0 && d5 <= d6 {
57 let bary = [0.0, 0.0, 1.0];
58 let diff = sub3(p, c);
59 return (dot3(diff, diff), bary);
60 }
61
62 let vb = d5 * d2 - d1 * d6;
63 if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 {
64 let w = d2 / (d2 - d6);
65 let closest = add3(a, scale3(ac, w));
66 let diff = sub3(p, closest);
67 let bary = [1.0 - w, 0.0, w];
68 return (dot3(diff, diff), bary);
69 }
70
71 let va = d3 * d6 - d5 * d4;
72 if va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0 {
73 let w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
74 let bc = sub3(c, b);
75 let closest = add3(b, scale3(bc, w));
76 let diff = sub3(p, closest);
77 let bary = [0.0, 1.0 - w, w];
78 return (dot3(diff, diff), bary);
79 }
80
81 let denom = 1.0 / (va + vb + vc);
82 let v = vb * denom;
83 let w = vc * denom;
84 let closest = add3(add3(a, scale3(ab, v)), scale3(ac, w));
85 let diff = sub3(p, closest);
86 let bary = [1.0 - v - w, v, w];
87 (dot3(diff, diff), bary)
88}
89
90pub fn edge_edge_distance(p: [f64; 3], d: [f64; 3], q: [f64; 3], e: [f64; 3]) -> (f64, f64, f64) {
94 let r = sub3(p, q);
95 let a = dot3(d, d);
96 let f = dot3(e, e);
97 let c = dot3(d, r);
98
99 let eps = 1e-10;
100 let (s, t) = if a <= eps && f <= eps {
101 (0.0, 0.0)
102 } else if a <= eps {
103 let t = (dot3(e, r) / f).clamp(0.0, 1.0);
104 (0.0, t)
105 } else {
106 let e_dot = dot3(e, r);
107 if f <= eps {
108 let s = (-c / a).clamp(0.0, 1.0);
109 (s, 0.0)
110 } else {
111 let b = dot3(d, e);
112 let denom = a * f - b * b;
113 let s = if denom.abs() > eps {
114 ((b * e_dot - c * f) / denom).clamp(0.0, 1.0)
115 } else {
116 0.0
117 };
118 let t = (b * s + e_dot) / f;
119 if t < 0.0 {
120 let s2 = (-c / a).clamp(0.0, 1.0);
121 (s2, 0.0)
122 } else if t > 1.0 {
123 let s2 = ((b - c) / a).clamp(0.0, 1.0);
124 (s2, 1.0)
125 } else {
126 (s, t)
127 }
128 }
129 };
130
131 let closest_p = add3(p, scale3(d, s));
132 let closest_q = add3(q, scale3(e, t));
133 let diff = sub3(closest_p, closest_q);
134 (dot3(diff, diff), s, t)
135}
136
137pub fn normal_cone_angle(normals: &[[f64; 3]]) -> f64 {
140 if normals.is_empty() {
141 return 0.0;
142 }
143 let avg = normals.iter().fold([0.0f64; 3], |acc, n| add3(acc, *n));
144 let avg = normalize3(avg);
145 normals
146 .iter()
147 .map(|n| dot3(*n, avg).clamp(-1.0, 1.0))
148 .fold(f64::INFINITY, f64::min)
149}
150
151pub fn pairwise_ccd_dt(
155 p0: [f64; 3],
156 p1: [f64; 3],
157 q0: [f64; 3],
158 q1: [f64; 3],
159 radius: f64,
160) -> Option<f64> {
161 let dp = sub3(p1, p0);
162 let dq = sub3(q1, q0);
163 let rel_vel = sub3(dp, dq);
164 let r0 = sub3(p0, q0);
165
166 let a = dot3(rel_vel, rel_vel);
167 let b = 2.0 * dot3(r0, rel_vel);
168 let c = dot3(r0, r0) - radius * radius;
169
170 if a < 1e-14 {
171 if c <= 0.0 {
172 return Some(0.0);
173 }
174 return None;
175 }
176
177 let disc = b * b - 4.0 * a * c;
178 if disc < 0.0 {
179 return None;
180 }
181 let sqrt_disc = disc.sqrt();
182 let t0 = (-b - sqrt_disc) / (2.0 * a);
183 let t1 = (-b + sqrt_disc) / (2.0 * a);
184 if t1 < 0.0 || t0 > 1.0 {
185 return None;
186 }
187 Some(t0.max(0.0))
188}
189
190#[inline]
195fn add3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
196 [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
197}
198
199#[inline]
200fn sub3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
201 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
202}
203
204#[inline]
205fn scale3(a: [f64; 3], s: f64) -> [f64; 3] {
206 [a[0] * s, a[1] * s, a[2] * s]
207}
208
209#[inline]
210fn dot3(a: [f64; 3], b: [f64; 3]) -> f64 {
211 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
212}
213
214#[inline]
215fn cross3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
216 [
217 a[1] * b[2] - a[2] * b[1],
218 a[2] * b[0] - a[0] * b[2],
219 a[0] * b[1] - a[1] * b[0],
220 ]
221}
222
223#[inline]
224fn len3(a: [f64; 3]) -> f64 {
225 dot3(a, a).sqrt()
226}
227
228#[inline]
229fn normalize3(a: [f64; 3]) -> [f64; 3] {
230 let l = len3(a);
231 if l < 1e-14 {
232 [0.0, 0.0, 1.0]
233 } else {
234 scale3(a, 1.0 / l)
235 }
236}
237
238#[derive(Debug, Clone)]
244pub struct TriAabb {
245 pub min: [f64; 3],
247 pub max: [f64; 3],
249 pub tri_index: usize,
251}
252
253impl TriAabb {
254 pub fn from_triangle(a: [f64; 3], b: [f64; 3], c: [f64; 3], idx: usize) -> Self {
256 let min = [
257 a[0].min(b[0]).min(c[0]),
258 a[1].min(b[1]).min(c[1]),
259 a[2].min(b[2]).min(c[2]),
260 ];
261 let max = [
262 a[0].max(b[0]).max(c[0]),
263 a[1].max(b[1]).max(c[1]),
264 a[2].max(b[2]).max(c[2]),
265 ];
266 TriAabb {
267 min,
268 max,
269 tri_index: idx,
270 }
271 }
272
273 pub fn overlaps(&self, other: &TriAabb) -> bool {
275 self.min[0] <= other.max[0]
276 && self.max[0] >= other.min[0]
277 && self.min[1] <= other.max[1]
278 && self.max[1] >= other.min[1]
279 && self.min[2] <= other.max[2]
280 && self.max[2] >= other.min[2]
281 }
282}
283
284#[derive(Debug, Clone)]
287pub struct DeformContact {
288 pub vertex_index: usize,
290 pub tri_index: usize,
292 pub depth: f64,
294 pub normal: [f64; 3],
296 pub point_on_rigid: [f64; 3],
298}
299
300pub struct DeformableVsBvh {
304 pub rigid_triangles: Vec<[[f64; 3]; 3]>,
306 pub aabbs: Vec<TriAabb>,
308 pub contact_threshold: f64,
310}
311
312impl DeformableVsBvh {
313 pub fn new(triangles: Vec<[[f64; 3]; 3]>, contact_threshold: f64) -> Self {
315 let aabbs = triangles
316 .iter()
317 .enumerate()
318 .map(|(i, t)| TriAabb::from_triangle(t[0], t[1], t[2], i))
319 .collect();
320 DeformableVsBvh {
321 rigid_triangles: triangles,
322 aabbs,
323 contact_threshold,
324 }
325 }
326
327 pub fn query(&self, vertices: &[[f64; 3]]) -> Vec<DeformContact> {
330 let mut contacts = Vec::new();
331 for (vi, &v) in vertices.iter().enumerate() {
332 let r = self.contact_threshold;
334 let query_aabb = TriAabb {
335 min: [v[0] - r, v[1] - r, v[2] - r],
336 max: [v[0] + r, v[1] + r, v[2] + r],
337 tri_index: 0,
338 };
339 for aabb in &self.aabbs {
340 if !aabb.overlaps(&query_aabb) {
341 continue;
342 }
343 let tri = &self.rigid_triangles[aabb.tri_index];
344 let (sq_dist, bary) = vertex_triangle_distance(v, tri[0], tri[1], tri[2]);
345 let dist = sq_dist.sqrt();
346 if dist < self.contact_threshold {
347 let closest = add3(
348 add3(scale3(tri[0], bary[0]), scale3(tri[1], bary[1])),
349 scale3(tri[2], bary[2]),
350 );
351 let ab = sub3(tri[1], tri[0]);
352 let ac = sub3(tri[2], tri[0]);
353 let tri_normal = normalize3(cross3(ab, ac));
354 contacts.push(DeformContact {
355 vertex_index: vi,
356 tri_index: aabb.tri_index,
357 depth: self.contact_threshold - dist,
358 normal: tri_normal,
359 point_on_rigid: closest,
360 });
361 }
362 }
363 }
364 contacts
365 }
366}
367
368pub struct NormalConeFilter {
375 pub cos_threshold: f64,
378}
379
380impl NormalConeFilter {
381 pub fn new(half_angle_rad: f64) -> Self {
383 NormalConeFilter {
384 cos_threshold: half_angle_rad.cos(),
385 }
386 }
387
388 pub fn should_test(&self, vertex_normal: [f64; 3], tri_normal: [f64; 3]) -> bool {
391 dot3(vertex_normal, tri_normal) < self.cos_threshold
392 }
393}
394
395#[derive(Debug, Clone)]
401pub struct SelfCollisionPair {
402 pub a: usize,
404 pub b: usize,
406 pub sq_dist: f64,
408}
409
410pub struct SelfCollision {
413 pub cell_size: f64,
415 pub contact_threshold: f64,
417 pub cone_filter: NormalConeFilter,
419 hash: HashMap<(i64, i64, i64), Vec<usize>>,
420}
421
422impl SelfCollision {
423 pub fn new(cell_size: f64, contact_threshold: f64, cone_half_angle: f64) -> Self {
425 SelfCollision {
426 cell_size,
427 contact_threshold,
428 cone_filter: NormalConeFilter::new(cone_half_angle),
429 hash: HashMap::new(),
430 }
431 }
432
433 fn cell_of(&self, p: [f64; 3]) -> (i64, i64, i64) {
434 (
435 (p[0] / self.cell_size).floor() as i64,
436 (p[1] / self.cell_size).floor() as i64,
437 (p[2] / self.cell_size).floor() as i64,
438 )
439 }
440
441 pub fn update(&mut self, vertices: &[[f64; 3]]) {
443 self.hash.clear();
444 for (i, &v) in vertices.iter().enumerate() {
445 let cell = self.cell_of(v);
446 self.hash.entry(cell).or_default().push(i);
447 }
448 }
449
450 pub fn find_pairs(
452 &self,
453 vertices: &[[f64; 3]],
454 normals: Option<&[[f64; 3]]>,
455 ) -> Vec<SelfCollisionPair> {
456 let mut pairs = Vec::new();
457 let threshold_sq = self.contact_threshold * self.contact_threshold;
458
459 for (&cell, indices) in &self.hash {
460 for di in -1i64..=1 {
462 for dj in -1i64..=1 {
463 for dk in -1i64..=1 {
464 let nc = (cell.0 + di, cell.1 + dj, cell.2 + dk);
465 if let Some(nb_indices) = self.hash.get(&nc) {
466 for &a in indices {
467 for &b in nb_indices {
468 if b <= a {
469 continue;
470 }
471 if let Some(norms) = normals
473 && !self.cone_filter.should_test(norms[a], norms[b])
474 {
475 continue;
476 }
477 let diff = sub3(vertices[a], vertices[b]);
478 let sq = dot3(diff, diff);
479 if sq < threshold_sq {
480 pairs.push(SelfCollisionPair { a, b, sq_dist: sq });
481 }
482 }
483 }
484 }
485 }
486 }
487 }
488 }
489 pairs
490 }
491}
492
493#[derive(Debug, Clone)]
499pub struct SdfContact {
500 pub vertex_index: usize,
502 pub sdf_value: f64,
504 pub gradient: [f64; 3],
506}
507
508pub struct ClothSdfContact {
513 pub nx: usize,
515 pub ny: usize,
517 pub nz: usize,
519 pub dx: f64,
521 pub origin: [f64; 3],
523 pub values: Vec<f64>,
525 pub thickness: f64,
527}
528
529impl ClothSdfContact {
530 pub fn new(
532 nx: usize,
533 ny: usize,
534 nz: usize,
535 dx: f64,
536 origin: [f64; 3],
537 values: Vec<f64>,
538 thickness: f64,
539 ) -> Self {
540 ClothSdfContact {
541 nx,
542 ny,
543 nz,
544 dx,
545 origin,
546 values,
547 thickness,
548 }
549 }
550
551 fn index(&self, ix: usize, iy: usize, iz: usize) -> usize {
552 ix + self.nx * (iy + self.ny * iz)
553 }
554
555 fn sample(&self, ix: usize, iy: usize, iz: usize) -> f64 {
556 if ix < self.nx && iy < self.ny && iz < self.nz {
557 self.values[self.index(ix, iy, iz)]
558 } else {
559 f64::MAX
560 }
561 }
562
563 pub fn interpolate(&self, p: [f64; 3]) -> f64 {
565 let lp = sub3(p, self.origin);
566 let fx = lp[0] / self.dx;
567 let fy = lp[1] / self.dx;
568 let fz = lp[2] / self.dx;
569 let ix = fx.floor() as isize;
570 let iy = fy.floor() as isize;
571 let iz = fz.floor() as isize;
572 if ix < 0
573 || iy < 0
574 || iz < 0
575 || ix + 1 >= self.nx as isize
576 || iy + 1 >= self.ny as isize
577 || iz + 1 >= self.nz as isize
578 {
579 return f64::MAX;
580 }
581 let (ix, iy, iz) = (ix as usize, iy as usize, iz as usize);
582 let tx = fx - ix as f64;
583 let ty = fy - iy as f64;
584 let tz = fz - iz as f64;
585 let v000 = self.sample(ix, iy, iz);
586 let v100 = self.sample(ix + 1, iy, iz);
587 let v010 = self.sample(ix, iy + 1, iz);
588 let v110 = self.sample(ix + 1, iy + 1, iz);
589 let v001 = self.sample(ix, iy, iz + 1);
590 let v101 = self.sample(ix + 1, iy, iz + 1);
591 let v011 = self.sample(ix, iy + 1, iz + 1);
592 let v111 = self.sample(ix + 1, iy + 1, iz + 1);
593 let lerp = |a: f64, b: f64, t: f64| a + (b - a) * t;
594 let c00 = lerp(v000, v100, tx);
595 let c10 = lerp(v010, v110, tx);
596 let c01 = lerp(v001, v101, tx);
597 let c11 = lerp(v011, v111, tx);
598 let c0 = lerp(c00, c10, ty);
599 let c1 = lerp(c01, c11, ty);
600 lerp(c0, c1, tz)
601 }
602
603 pub fn gradient(&self, p: [f64; 3]) -> [f64; 3] {
605 let h = self.dx * 0.5;
606 let gx = (self.interpolate([p[0] + h, p[1], p[2]])
607 - self.interpolate([p[0] - h, p[1], p[2]]))
608 / (2.0 * h);
609 let gy = (self.interpolate([p[0], p[1] + h, p[2]])
610 - self.interpolate([p[0], p[1] - h, p[2]]))
611 / (2.0 * h);
612 let gz = (self.interpolate([p[0], p[1], p[2] + h])
613 - self.interpolate([p[0], p[1], p[2] - h]))
614 / (2.0 * h);
615 normalize3([gx, gy, gz])
616 }
617
618 pub fn test_vertices(&self, vertices: &[[f64; 3]]) -> Vec<SdfContact> {
620 vertices
621 .iter()
622 .enumerate()
623 .filter_map(|(i, &v)| {
624 let d = self.interpolate(v);
625 if d < self.thickness {
626 Some(SdfContact {
627 vertex_index: i,
628 sdf_value: d,
629 gradient: self.gradient(v),
630 })
631 } else {
632 None
633 }
634 })
635 .collect()
636 }
637}
638
639#[derive(Debug, Clone)]
645pub struct EdgeEdgeResult {
646 pub edge_a: usize,
648 pub edge_b: usize,
650 pub distance: f64,
652 pub s: f64,
654 pub t: f64,
656 pub normal: [f64; 3],
658}
659
660pub struct EdgeEdgeContact {
664 pub threshold: f64,
666}
667
668impl EdgeEdgeContact {
669 pub fn new(threshold: f64) -> Self {
671 EdgeEdgeContact { threshold }
672 }
673
674 pub fn test_pair(
676 &self,
677 ei_a: usize,
678 p_a: [f64; 3],
679 d_a: [f64; 3],
680 ei_b: usize,
681 p_b: [f64; 3],
682 d_b: [f64; 3],
683 ) -> Option<EdgeEdgeResult> {
684 let (sq_dist, s, t) = edge_edge_distance(p_a, d_a, p_b, d_b);
685 let dist = sq_dist.sqrt();
686 if dist < self.threshold {
687 let ca = add3(p_a, scale3(d_a, s));
688 let cb = add3(p_b, scale3(d_b, t));
689 let diff = sub3(ca, cb);
690 let normal = if dist > 1e-14 {
691 scale3(diff, 1.0 / dist)
692 } else {
693 normalize3(cross3(d_a, d_b))
694 };
695 Some(EdgeEdgeResult {
696 edge_a: ei_a,
697 edge_b: ei_b,
698 distance: dist,
699 s,
700 t,
701 normal,
702 })
703 } else {
704 None
705 }
706 }
707
708 pub fn test_all(
710 &self,
711 edges_a: &[(usize, usize)],
712 verts_a: &[[f64; 3]],
713 edges_b: &[(usize, usize)],
714 verts_b: &[[f64; 3]],
715 ) -> Vec<EdgeEdgeResult> {
716 let mut results = Vec::new();
717 for (ia, &(a0, a1)) in edges_a.iter().enumerate() {
718 let pa = verts_a[a0];
719 let da = sub3(verts_a[a1], pa);
720 for (ib, &(b0, b1)) in edges_b.iter().enumerate() {
721 let pb = verts_b[b0];
722 let db = sub3(verts_b[b1], pb);
723 if let Some(r) = self.test_pair(ia, pa, da, ib, pb, db) {
724 results.push(r);
725 }
726 }
727 }
728 results
729 }
730}
731
732#[derive(Debug, Clone)]
738pub struct CcdDeformableResult {
739 pub vertex_index: usize,
741 pub toi: f64,
743 pub normal: [f64; 3],
745}
746
747pub struct CcdDeformable {
751 pub vertex_radius: f64,
753 pub edge_radius: f64,
755}
756
757impl CcdDeformable {
758 pub fn new(vertex_radius: f64, edge_radius: f64) -> Self {
760 CcdDeformable {
761 vertex_radius,
762 edge_radius,
763 }
764 }
765
766 pub fn vertex_vertex_ccd(
769 &self,
770 verts0_a: &[[f64; 3]],
771 verts1_a: &[[f64; 3]],
772 verts0_b: &[[f64; 3]],
773 verts1_b: &[[f64; 3]],
774 ) -> Vec<CcdDeformableResult> {
775 let mut results = Vec::new();
776 for (ia, (&p0, &p1)) in verts0_a.iter().zip(verts1_a.iter()).enumerate() {
777 for (&q0, &q1) in verts0_b.iter().zip(verts1_b.iter()) {
778 if let Some(toi) = pairwise_ccd_dt(p0, p1, q0, q1, self.vertex_radius) {
779 let pt = add3(p0, scale3(sub3(p1, p0), toi));
780 let qt = add3(q0, scale3(sub3(q1, q0), toi));
781 let diff = sub3(pt, qt);
782 let normal = normalize3(diff);
783 results.push(CcdDeformableResult {
784 vertex_index: ia,
785 toi,
786 normal,
787 });
788 }
789 }
790 }
791 results
792 }
793
794 pub fn edge_edge_ccd(
796 &self,
797 edges_a: &[(usize, usize)],
798 verts0_a: &[[f64; 3]],
799 verts1_a: &[[f64; 3]],
800 edges_b: &[(usize, usize)],
801 verts0_b: &[[f64; 3]],
802 verts1_b: &[[f64; 3]],
803 ) -> Vec<CcdDeformableResult> {
804 let mut results = Vec::new();
805 for (ia, &(a0, a1)) in edges_a.iter().enumerate() {
806 for &(b0, b1) in edges_b {
807 let pa0 = midpoint(verts0_a[a0], verts0_a[a1]);
809 let pa1 = midpoint(verts1_a[a0], verts1_a[a1]);
810 let pb0 = midpoint(verts0_b[b0], verts0_b[b1]);
811 let pb1 = midpoint(verts1_b[b0], verts1_b[b1]);
812 if let Some(toi) = pairwise_ccd_dt(pa0, pa1, pb0, pb1, self.edge_radius) {
813 let pt = add3(pa0, scale3(sub3(pa1, pa0), toi));
814 let qt = add3(pb0, scale3(sub3(pb1, pb0), toi));
815 let normal = normalize3(sub3(pt, qt));
816 results.push(CcdDeformableResult {
817 vertex_index: ia,
818 toi,
819 normal,
820 });
821 }
822 }
823 }
824 results
825 }
826}
827
828fn midpoint(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
829 scale3(add3(a, b), 0.5)
830}
831
832#[derive(Debug, Clone)]
838pub struct PrimitiveDeformContact {
839 pub vertex_index: usize,
841 pub depth: f64,
843 pub normal: [f64; 3],
845 pub point: [f64; 3],
847}
848
849pub struct PrimitiveVsDeformable {
851 pub sphere_center: [f64; 3],
853 pub sphere_radius: f64,
855}
856
857impl PrimitiveVsDeformable {
858 pub fn sphere(center: [f64; 3], radius: f64) -> Self {
860 PrimitiveVsDeformable {
861 sphere_center: center,
862 sphere_radius: radius,
863 }
864 }
865
866 pub fn query_sphere(&self, vertices: &[[f64; 3]]) -> Vec<PrimitiveDeformContact> {
868 vertices
869 .iter()
870 .enumerate()
871 .filter_map(|(i, &v)| {
872 let diff = sub3(v, self.sphere_center);
873 let dist = len3(diff);
874 if dist < self.sphere_radius {
875 let depth = self.sphere_radius - dist;
876 let normal = if dist > 1e-14 {
877 scale3(diff, 1.0 / dist)
878 } else {
879 [0.0, 1.0, 0.0]
880 };
881 let point = add3(self.sphere_center, scale3(normal, self.sphere_radius));
882 Some(PrimitiveDeformContact {
883 vertex_index: i,
884 depth,
885 normal,
886 point,
887 })
888 } else {
889 None
890 }
891 })
892 .collect()
893 }
894
895 pub fn query_box(
897 vertices: &[[f64; 3]],
898 box_min: [f64; 3],
899 box_max: [f64; 3],
900 ) -> Vec<PrimitiveDeformContact> {
901 vertices
902 .iter()
903 .enumerate()
904 .filter_map(|(i, &v)| {
905 let cx = v[0].clamp(box_min[0], box_max[0]);
907 let cy = v[1].clamp(box_min[1], box_max[1]);
908 let cz = v[2].clamp(box_min[2], box_max[2]);
909 let closest = [cx, cy, cz];
910 let diff = sub3(v, closest);
911 let dist = len3(diff);
912 if dist < 1e-6 {
913 let normal = [0.0, 1.0, 0.0];
915 Some(PrimitiveDeformContact {
916 vertex_index: i,
917 depth: 1e-6,
918 normal,
919 point: closest,
920 })
921 } else {
922 None
923 }
924 })
925 .collect()
926 }
927
928 pub fn query_capsule(
930 vertices: &[[f64; 3]],
931 cap_a: [f64; 3],
932 cap_b: [f64; 3],
933 radius: f64,
934 ) -> Vec<PrimitiveDeformContact> {
935 let d = sub3(cap_b, cap_a);
936 let len_sq = dot3(d, d);
937 vertices
938 .iter()
939 .enumerate()
940 .filter_map(|(i, &v)| {
941 let t = if len_sq < 1e-14 {
942 0.0
943 } else {
944 (dot3(sub3(v, cap_a), d) / len_sq).clamp(0.0, 1.0)
945 };
946 let closest = add3(cap_a, scale3(d, t));
947 let diff = sub3(v, closest);
948 let dist = len3(diff);
949 if dist < radius {
950 let depth = radius - dist;
951 let normal = if dist > 1e-14 {
952 scale3(diff, 1.0 / dist)
953 } else {
954 [0.0, 1.0, 0.0]
955 };
956 let point = add3(closest, scale3(normal, radius));
957 Some(PrimitiveDeformContact {
958 vertex_index: i,
959 depth,
960 normal,
961 point,
962 })
963 } else {
964 None
965 }
966 })
967 .collect()
968 }
969}
970
971#[derive(Debug, Clone)]
977pub struct VertexImpulse {
978 pub vertex_index: usize,
980 pub correction: [f64; 3],
982 pub impulse: [f64; 3],
984}
985
986pub struct ContactResponse {
989 pub restitution: f64,
991 pub friction: f64,
993}
994
995impl ContactResponse {
996 pub fn new(restitution: f64, friction: f64) -> Self {
998 ContactResponse {
999 restitution,
1000 friction,
1001 }
1002 }
1003
1004 pub fn solve_deform_rigid(
1006 &self,
1007 contacts: &[DeformContact],
1008 velocities: &[[f64; 3]],
1009 masses: &[f64],
1010 ) -> Vec<VertexImpulse> {
1011 contacts
1012 .iter()
1013 .map(|c| {
1014 let vi = c.vertex_index;
1015 let v = velocities[vi];
1016 let m = masses[vi];
1017 let vn = dot3(v, c.normal);
1018 let correction = scale3(c.normal, c.depth);
1020 let j_n = -(1.0 + self.restitution) * vn * m;
1022 let impulse_n = scale3(c.normal, j_n);
1023 let v_tang = sub3(v, scale3(c.normal, vn));
1025 let v_tang_len = len3(v_tang);
1026 let impulse_t = if v_tang_len > 1e-14 {
1027 scale3(v_tang, -self.friction * j_n.abs() / v_tang_len)
1028 } else {
1029 [0.0; 3]
1030 };
1031 let impulse = add3(impulse_n, impulse_t);
1032 VertexImpulse {
1033 vertex_index: vi,
1034 correction,
1035 impulse,
1036 }
1037 })
1038 .collect()
1039 }
1040
1041 pub fn solve_edge_edge(
1043 &self,
1044 results: &[EdgeEdgeResult],
1045 velocities: &[[f64; 3]],
1046 edges_a: &[(usize, usize)],
1047 masses: &[f64],
1048 ) -> Vec<VertexImpulse> {
1049 let mut impulses = Vec::new();
1050 for r in results {
1051 let (a0, a1) = edges_a[r.edge_a];
1052 for &vi in &[a0, a1] {
1053 let v = velocities[vi];
1054 let m = masses[vi];
1055 let vn = dot3(v, r.normal);
1056 let depth = (self.friction - r.distance).max(0.0);
1057 let correction = scale3(r.normal, depth);
1058 let j = -(1.0 + self.restitution) * vn * m;
1059 let impulse = scale3(r.normal, j);
1060 impulses.push(VertexImpulse {
1061 vertex_index: vi,
1062 correction,
1063 impulse,
1064 });
1065 }
1066 }
1067 impulses
1068 }
1069}
1070
1071pub struct SpatialHashDeformable {
1077 pub cell_size: f64,
1079 cells: HashMap<(i64, i64, i64), Vec<usize>>,
1080}
1081
1082impl SpatialHashDeformable {
1083 pub fn new(cell_size: f64) -> Self {
1085 SpatialHashDeformable {
1086 cell_size,
1087 cells: HashMap::new(),
1088 }
1089 }
1090
1091 fn cell_of(&self, p: [f64; 3]) -> (i64, i64, i64) {
1092 (
1093 (p[0] / self.cell_size).floor() as i64,
1094 (p[1] / self.cell_size).floor() as i64,
1095 (p[2] / self.cell_size).floor() as i64,
1096 )
1097 }
1098
1099 pub fn update(&mut self, positions: &[[f64; 3]]) {
1101 self.cells.clear();
1102 for (i, &p) in positions.iter().enumerate() {
1103 let cell = self.cell_of(p);
1104 self.cells.entry(cell).or_default().push(i);
1105 }
1106 }
1107
1108 pub fn query_radius(&self, point: [f64; 3], radius: f64) -> Vec<usize> {
1110 let r_cells = (radius / self.cell_size).ceil() as i64 + 1;
1111 let center_cell = self.cell_of(point);
1112 let mut result = Vec::new();
1113 for di in -r_cells..=r_cells {
1114 for dj in -r_cells..=r_cells {
1115 for dk in -r_cells..=r_cells {
1116 let nc = (center_cell.0 + di, center_cell.1 + dj, center_cell.2 + dk);
1117 if let Some(indices) = self.cells.get(&nc) {
1118 for &idx in indices {
1119 result.push(idx);
1120 }
1121 }
1122 }
1123 }
1124 }
1125 result
1127 .into_iter()
1128 .filter(|_| true) .collect()
1130 }
1131
1132 pub fn query_radius_with_positions(
1134 &self,
1135 point: [f64; 3],
1136 radius: f64,
1137 positions: &[[f64; 3]],
1138 ) -> Vec<usize> {
1139 let candidates = self.query_radius(point, radius);
1140 let r2 = radius * radius;
1141 candidates
1142 .into_iter()
1143 .filter(|&i| dot3(sub3(positions[i], point), sub3(positions[i], point)) <= r2)
1144 .collect()
1145 }
1146}
1147
1148#[derive(Debug, Clone)]
1154pub struct InflatableContact {
1155 pub tri_index: usize,
1157 pub depth: f64,
1159 pub normal: [f64; 3],
1161 pub point: [f64; 3],
1163 pub stiffness: f64,
1165}
1166
1167pub struct InflatableContactDetector {
1170 pub pressure: f64,
1172 pub thickness: f64,
1174}
1175
1176impl InflatableContactDetector {
1177 pub fn new(pressure: f64, thickness: f64) -> Self {
1179 InflatableContactDetector {
1180 pressure,
1181 thickness,
1182 }
1183 }
1184
1185 pub fn query(
1187 &self,
1188 balloon_tris: &[[[f64; 3]; 3]],
1189 rigid_tris: &[[[f64; 3]; 3]],
1190 ) -> Vec<InflatableContact> {
1191 let mut contacts = Vec::new();
1192 for (bi, btri) in balloon_tris.iter().enumerate() {
1193 let bc = tri_centroid(*btri);
1194 for rtri in rigid_tris {
1195 let (sq_dist, bary) = vertex_triangle_distance(bc, rtri[0], rtri[1], rtri[2]);
1196 let dist = sq_dist.sqrt();
1197 if dist < self.thickness {
1198 let closest = add3(
1199 add3(scale3(rtri[0], bary[0]), scale3(rtri[1], bary[1])),
1200 scale3(rtri[2], bary[2]),
1201 );
1202 let ab = sub3(rtri[1], rtri[0]);
1203 let ac = sub3(rtri[2], rtri[0]);
1204 let normal = normalize3(cross3(ab, ac));
1205 let depth = self.thickness - dist;
1206 let area = tri_area(*btri);
1209 let stiffness = self.pressure * area;
1210 contacts.push(InflatableContact {
1211 tri_index: bi,
1212 depth,
1213 normal,
1214 point: closest,
1215 stiffness,
1216 });
1217 }
1218 }
1219 }
1220 contacts
1221 }
1222}
1223
1224fn tri_centroid(tri: [[f64; 3]; 3]) -> [f64; 3] {
1225 scale3(add3(add3(tri[0], tri[1]), tri[2]), 1.0 / 3.0)
1226}
1227
1228fn tri_area(tri: [[f64; 3]; 3]) -> f64 {
1229 let ab = sub3(tri[1], tri[0]);
1230 let ac = sub3(tri[2], tri[0]);
1231 len3(cross3(ab, ac)) * 0.5
1232}
1233
1234#[cfg(test)]
1239mod tests {
1240 use super::*;
1241
1242 #[test]
1245 fn vtd_point_at_vertex() {
1246 let a = [0.0, 0.0, 0.0];
1247 let b = [1.0, 0.0, 0.0];
1248 let c = [0.0, 1.0, 0.0];
1249 let (sq, bary) = vertex_triangle_distance(a, a, b, c);
1250 assert!(sq < 1e-12, "sq={sq}");
1251 assert!((bary[0] - 1.0).abs() < 1e-9);
1252 }
1253
1254 #[test]
1255 fn vtd_point_above_triangle() {
1256 let a = [0.0, 0.0, 0.0];
1257 let b = [1.0, 0.0, 0.0];
1258 let c = [0.0, 1.0, 0.0];
1259 let p = [0.25, 0.25, 1.0];
1260 let (sq, _) = vertex_triangle_distance(p, a, b, c);
1261 assert!((sq.sqrt() - 1.0).abs() < 1e-9, "dist={}", sq.sqrt());
1262 }
1263
1264 #[test]
1265 fn vtd_point_inside_triangle_projection() {
1266 let a = [0.0, 0.0, 0.0];
1267 let b = [2.0, 0.0, 0.0];
1268 let c = [0.0, 2.0, 0.0];
1269 let p = [0.5, 0.5, 0.0];
1270 let (sq, _) = vertex_triangle_distance(p, a, b, c);
1271 assert!(sq < 1e-12, "sq={sq}");
1272 }
1273
1274 #[test]
1275 fn vtd_point_outside_triangle() {
1276 let a = [0.0, 0.0, 0.0];
1277 let b = [1.0, 0.0, 0.0];
1278 let c = [0.0, 1.0, 0.0];
1279 let p = [2.0, 0.0, 0.0];
1280 let (sq, _) = vertex_triangle_distance(p, a, b, c);
1281 assert!((sq.sqrt() - 1.0).abs() < 1e-9, "dist={}", sq.sqrt());
1282 }
1283
1284 #[test]
1287 fn eed_parallel_edges() {
1288 let p = [0.0, 0.0, 0.0];
1289 let d = [1.0, 0.0, 0.0];
1290 let q = [0.0, 1.0, 0.0];
1291 let e = [1.0, 0.0, 0.0];
1292 let (sq, _, _) = edge_edge_distance(p, d, q, e);
1293 assert!((sq.sqrt() - 1.0).abs() < 1e-9, "dist={}", sq.sqrt());
1294 }
1295
1296 #[test]
1297 fn eed_perpendicular_crossing() {
1298 let p = [0.0, 0.0, 0.0];
1299 let d = [1.0, 0.0, 0.0];
1300 let q = [0.5, -1.0, 0.0];
1301 let e = [0.0, 2.0, 0.0];
1302 let (sq, s, t) = edge_edge_distance(p, d, q, e);
1303 assert!(sq < 1e-12, "sq={sq}");
1304 assert!((s - 0.5).abs() < 1e-9, "s={s}");
1305 assert!((t - 0.5).abs() < 1e-9, "t={t}");
1306 }
1307
1308 #[test]
1309 fn eed_skew_edges() {
1310 let p = [0.0, 0.0, 0.0];
1311 let d = [1.0, 0.0, 0.0];
1312 let q = [0.5, 1.0, 1.0];
1313 let e = [0.0, 0.0, -1.0];
1314 let (sq, _, _) = edge_edge_distance(p, d, q, e);
1315 assert!((sq.sqrt() - 1.0).abs() < 1e-9, "dist={}", sq.sqrt());
1316 }
1317
1318 #[test]
1321 fn nca_identical_normals() {
1322 let n = [0.0, 1.0, 0.0];
1323 let normals = vec![n, n, n];
1324 let cos = normal_cone_angle(&normals);
1325 assert!((cos - 1.0).abs() < 1e-9, "cos={cos}");
1326 }
1327
1328 #[test]
1329 fn nca_empty() {
1330 let cos = normal_cone_angle(&[]);
1331 assert_eq!(cos, 0.0);
1332 }
1333
1334 #[test]
1335 fn nca_perpendicular() {
1336 let normals = vec![[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
1337 let cos = normal_cone_angle(&normals);
1338 assert!(cos < 1.0);
1339 }
1340
1341 #[test]
1344 fn ccd_dt_approaching() {
1345 let p0 = [0.0, 0.0, 0.0];
1346 let p1 = [0.0, 0.0, 0.0];
1347 let q0 = [2.0, 0.0, 0.0];
1348 let q1 = [0.5, 0.0, 0.0];
1349 let r = 0.6;
1350 let toi = pairwise_ccd_dt(p0, p1, q0, q1, r);
1351 assert!(toi.is_some(), "expected impact");
1352 let t = toi.unwrap();
1353 assert!((0.0..=1.0).contains(&t), "t={t}");
1354 }
1355
1356 #[test]
1357 fn ccd_dt_separating() {
1358 let p0 = [0.0, 0.0, 0.0];
1359 let p1 = [0.0, 0.0, 0.0];
1360 let q0 = [1.0, 0.0, 0.0];
1361 let q1 = [5.0, 0.0, 0.0];
1362 let toi = pairwise_ccd_dt(p0, p1, q0, q1, 0.1);
1363 assert!(toi.is_none());
1364 }
1365
1366 #[test]
1367 fn ccd_dt_already_overlapping() {
1368 let p0 = [0.0, 0.0, 0.0];
1369 let p1 = [0.0, 0.0, 0.0];
1370 let q0 = [0.05, 0.0, 0.0];
1371 let q1 = [0.05, 0.0, 0.0];
1372 let toi = pairwise_ccd_dt(p0, p1, q0, q1, 1.0);
1373 assert!(toi.is_some());
1374 assert_eq!(toi.unwrap(), 0.0);
1375 }
1376
1377 #[test]
1380 fn dvb_no_contact() {
1381 let tris = vec![[[0.0f64, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]];
1382 let det = DeformableVsBvh::new(tris, 0.05);
1383 let verts = vec![[0.5f64, 0.5, 5.0]];
1384 let contacts = det.query(&verts);
1385 assert!(contacts.is_empty());
1386 }
1387
1388 #[test]
1389 fn dvb_contact_detected() {
1390 let tris = vec![[[0.0f64, 0.0, 0.0], [2.0, 0.0, 0.0], [0.0, 2.0, 0.0]]];
1391 let det = DeformableVsBvh::new(tris, 0.1);
1392 let verts = vec![[0.5f64, 0.5, 0.05]];
1393 let contacts = det.query(&verts);
1394 assert!(!contacts.is_empty(), "expected contact");
1395 assert_eq!(contacts[0].vertex_index, 0);
1396 assert!(contacts[0].depth > 0.0);
1397 }
1398
1399 #[test]
1402 fn ncf_opposite_normals_always_tested() {
1403 let filter = NormalConeFilter::new(0.1);
1406 assert!(filter.should_test([0.0, 1.0, 0.0], [0.0, -1.0, 0.0]));
1407 }
1408
1409 #[test]
1410 fn ncf_same_normal_culled_small_threshold() {
1411 let filter = NormalConeFilter::new(0.0001);
1414 assert!(!filter.should_test([0.0, 1.0, 0.0], [0.0, 1.0, 0.0]));
1416 }
1417
1418 #[test]
1421 fn self_collision_finds_close_pair() {
1422 let mut sc = SelfCollision::new(1.0, 0.5, std::f64::consts::PI);
1423 let verts = vec![[0.0f64, 0.0, 0.0], [0.2, 0.0, 0.0], [10.0, 0.0, 0.0]];
1424 sc.update(&verts);
1425 let pairs = sc.find_pairs(&verts, None);
1426 assert!(!pairs.is_empty(), "expected close pair");
1427 }
1428
1429 #[test]
1430 fn self_collision_no_pair_far_apart() {
1431 let mut sc = SelfCollision::new(1.0, 0.1, std::f64::consts::PI);
1432 let verts = vec![[0.0f64, 0.0, 0.0], [5.0, 0.0, 0.0]];
1433 sc.update(&verts);
1434 let pairs = sc.find_pairs(&verts, None);
1435 assert!(pairs.is_empty());
1436 }
1437
1438 fn make_sdf_sphere(
1441 center: [f64; 3],
1442 radius: f64,
1443 nx: usize,
1444 ny: usize,
1445 nz: usize,
1446 dx: f64,
1447 origin: [f64; 3],
1448 ) -> ClothSdfContact {
1449 let mut values = Vec::with_capacity(nx * ny * nz);
1450 for iz in 0..nz {
1451 for iy in 0..ny {
1452 for ix in 0..nx {
1453 let p = [
1454 origin[0] + ix as f64 * dx,
1455 origin[1] + iy as f64 * dx,
1456 origin[2] + iz as f64 * dx,
1457 ];
1458 let d = len3(sub3(p, center)) - radius;
1459 values.push(d);
1460 }
1461 }
1462 }
1463 ClothSdfContact::new(nx, ny, nz, dx, origin, values, 0.1)
1464 }
1465
1466 #[test]
1467 fn cloth_sdf_contact_inside_sphere() {
1468 let sdf = make_sdf_sphere([2.5, 2.5, 2.5], 1.0, 6, 6, 6, 1.0, [0.0, 0.0, 0.0]);
1469 let verts = vec![[2.5f64, 2.5, 2.5]]; let contacts = sdf.test_vertices(&verts);
1471 assert!(!contacts.is_empty(), "vertex inside sphere should contact");
1472 }
1473
1474 #[test]
1475 fn cloth_sdf_contact_outside_sphere() {
1476 let sdf = make_sdf_sphere([2.5, 2.5, 2.5], 1.0, 6, 6, 6, 1.0, [0.0, 0.0, 0.0]);
1477 let verts = vec![[2.5f64, 2.5, 10.0]]; let contacts = sdf.test_vertices(&verts);
1479 assert!(
1480 contacts.is_empty(),
1481 "vertex far outside sphere should not contact"
1482 );
1483 }
1484
1485 #[test]
1488 fn eec_crossing_edges() {
1489 let det = EdgeEdgeContact::new(0.1);
1490 let pa = [0.0, 0.0, 0.0];
1491 let da = [2.0, 0.0, 0.0];
1492 let pb = [1.0, -1.0, 0.0];
1493 let db = [0.0, 2.0, 0.0];
1494 let result = det.test_pair(0, pa, da, 1, pb, db);
1495 assert!(result.is_some(), "crossing edges should contact");
1496 }
1497
1498 #[test]
1499 fn eec_distant_edges() {
1500 let det = EdgeEdgeContact::new(0.1);
1501 let pa = [0.0, 0.0, 0.0];
1502 let da = [1.0, 0.0, 0.0];
1503 let pb = [0.0, 5.0, 0.0];
1504 let db = [1.0, 0.0, 0.0];
1505 let result = det.test_pair(0, pa, da, 1, pb, db);
1506 assert!(result.is_none(), "distant edges should not contact");
1507 }
1508
1509 #[test]
1510 fn eec_test_all_returns_multiple() {
1511 let det = EdgeEdgeContact::new(0.2);
1512 let verts_a = vec![[0.0f64, 0.0, 0.0], [1.0, 0.0, 0.0], [2.0, 0.0, 0.0]];
1513 let edges_a = vec![(0, 1), (1, 2)];
1514 let verts_b = vec![[0.5f64, -0.1, 0.0], [0.5, 0.1, 0.0]];
1515 let edges_b = vec![(0, 1)];
1516 let results = det.test_all(&edges_a, &verts_a, &edges_b, &verts_b);
1517 assert!(!results.is_empty());
1518 }
1519
1520 #[test]
1523 fn ccd_deformable_vertex_impact() {
1524 let ccd = CcdDeformable::new(0.1, 0.1);
1525 let verts0_a = vec![[0.0f64, 0.0, 0.0]];
1526 let verts1_a = vec![[0.0f64, 0.0, 0.0]];
1527 let verts0_b = vec![[0.5, 0.0, 0.0]];
1528 let verts1_b = vec![[0.05, 0.0, 0.0]];
1529 let results = ccd.vertex_vertex_ccd(&verts0_a, &verts1_a, &verts0_b, &verts1_b);
1530 assert!(!results.is_empty(), "expected CCD impact");
1531 }
1532
1533 #[test]
1534 fn ccd_deformable_no_impact_separating() {
1535 let ccd = CcdDeformable::new(0.05, 0.05);
1536 let verts0_a = vec![[0.0f64, 0.0, 0.0]];
1537 let verts1_a = vec![[0.0f64, 0.0, 0.0]];
1538 let verts0_b = vec![[1.0, 0.0, 0.0]];
1539 let verts1_b = vec![[5.0, 0.0, 0.0]];
1540 let results = ccd.vertex_vertex_ccd(&verts0_a, &verts1_a, &verts0_b, &verts1_b);
1541 assert!(results.is_empty());
1542 }
1543
1544 #[test]
1547 fn pvd_sphere_contact() {
1548 let det = PrimitiveVsDeformable::sphere([0.0, 0.0, 0.0], 1.0);
1549 let verts = vec![[0.5f64, 0.0, 0.0], [2.0, 0.0, 0.0]];
1550 let contacts = det.query_sphere(&verts);
1551 assert_eq!(contacts.len(), 1);
1552 assert_eq!(contacts[0].vertex_index, 0);
1553 assert!(contacts[0].depth > 0.0);
1554 }
1555
1556 #[test]
1557 fn pvd_sphere_no_contact() {
1558 let det = PrimitiveVsDeformable::sphere([0.0, 0.0, 0.0], 0.5);
1559 let verts = vec![[2.0f64, 0.0, 0.0]];
1560 let contacts = det.query_sphere(&verts);
1561 assert!(contacts.is_empty());
1562 }
1563
1564 #[test]
1565 fn pvd_capsule_contact() {
1566 let verts = vec![[0.5f64, 0.5, 0.0]];
1567 let contacts =
1568 PrimitiveVsDeformable::query_capsule(&verts, [0.0, 0.0, 0.0], [1.0, 0.0, 0.0], 1.0);
1569 assert!(!contacts.is_empty());
1570 }
1571
1572 #[test]
1573 fn pvd_box_no_contact() {
1574 let verts = vec![[5.0f64, 0.0, 0.0]];
1575 let contacts = PrimitiveVsDeformable::query_box(&verts, [0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
1576 assert!(contacts.is_empty());
1577 }
1578
1579 #[test]
1582 fn cr_solve_deform_rigid_positive_depth() {
1583 let cr = ContactResponse::new(0.0, 0.1);
1584 let contact = DeformContact {
1585 vertex_index: 0,
1586 tri_index: 0,
1587 depth: 0.02,
1588 normal: [0.0, 1.0, 0.0],
1589 point_on_rigid: [0.0, 0.0, 0.0],
1590 };
1591 let velocities = vec![[0.0, -1.0, 0.0]];
1592 let masses = vec![1.0];
1593 let impulses = cr.solve_deform_rigid(&[contact], &velocities, &masses);
1594 assert_eq!(impulses.len(), 1);
1595 assert!(impulses[0].correction[1] > 0.0);
1596 }
1597
1598 #[test]
1601 fn shd_query_finds_nearby() {
1602 let mut sh = SpatialHashDeformable::new(1.0);
1603 let positions = vec![[0.0f64, 0.0, 0.0], [0.3, 0.0, 0.0], [10.0, 0.0, 0.0]];
1604 sh.update(&positions);
1605 let nearby = sh.query_radius_with_positions([0.0, 0.0, 0.0], 0.5, &positions);
1606 assert!(nearby.contains(&0));
1607 assert!(nearby.contains(&1));
1608 assert!(!nearby.contains(&2));
1609 }
1610
1611 #[test]
1612 fn shd_empty_after_clear() {
1613 let mut sh = SpatialHashDeformable::new(1.0);
1614 let positions = vec![[0.0f64, 0.0, 0.0]];
1615 sh.update(&positions);
1616 sh.update(&[]);
1617 let nearby = sh.query_radius_with_positions([0.0, 0.0, 0.0], 1.0, &[]);
1618 assert!(nearby.is_empty());
1619 }
1620
1621 #[test]
1624 fn icd_contact_detected() {
1625 let det = InflatableContactDetector::new(1.0, 0.2);
1626 let balloon = vec![[[0.0f64, 0.0, 0.1], [1.0, 0.0, 0.1], [0.0, 1.0, 0.1]]];
1627 let rigid = vec![[[0.0f64, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]];
1628 let contacts = det.query(&balloon, &rigid);
1629 assert!(
1630 !contacts.is_empty(),
1631 "balloon close to rigid should contact"
1632 );
1633 }
1634
1635 #[test]
1636 fn icd_no_contact_far_apart() {
1637 let det = InflatableContactDetector::new(1.0, 0.05);
1638 let balloon = vec![[[0.0f64, 0.0, 5.0], [1.0, 0.0, 5.0], [0.0, 1.0, 5.0]]];
1639 let rigid = vec![[[0.0f64, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]];
1640 let contacts = det.query(&balloon, &rigid);
1641 assert!(contacts.is_empty());
1642 }
1643
1644 #[test]
1647 fn tri_centroid_correct() {
1648 let tri = [[0.0f64, 0.0, 0.0], [3.0, 0.0, 0.0], [0.0, 3.0, 0.0]];
1649 let c = tri_centroid(tri);
1650 assert!((c[0] - 1.0).abs() < 1e-9);
1651 assert!((c[1] - 1.0).abs() < 1e-9);
1652 }
1653
1654 #[test]
1655 fn tri_area_unit() {
1656 let tri = [[0.0f64, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
1657 let a = tri_area(tri);
1658 assert!((a - 0.5).abs() < 1e-9, "area={a}");
1659 }
1660
1661 #[test]
1664 fn vec_helpers_dot_cross() {
1665 let a = [1.0f64, 0.0, 0.0];
1666 let b = [0.0f64, 1.0, 0.0];
1667 assert!((dot3(a, b) - 0.0).abs() < 1e-14);
1668 let c = cross3(a, b);
1669 assert!((c[2] - 1.0).abs() < 1e-14);
1670 }
1671
1672 #[test]
1673 fn vec_normalize() {
1674 let v = [3.0f64, 4.0, 0.0];
1675 let n = normalize3(v);
1676 assert!((len3(n) - 1.0).abs() < 1e-12);
1677 }
1678
1679 #[test]
1680 fn midpoint_test() {
1681 let a = [0.0f64, 0.0, 0.0];
1682 let b = [2.0f64, 0.0, 0.0];
1683 let m = midpoint(a, b);
1684 assert!((m[0] - 1.0).abs() < 1e-14);
1685 }
1686
1687 #[test]
1688 fn sdf_interpolate_outside_grid_returns_max() {
1689 let sdf = ClothSdfContact::new(3, 3, 3, 1.0, [0.0, 0.0, 0.0], vec![1.0; 27], 0.1);
1690 let val = sdf.interpolate([100.0, 100.0, 100.0]);
1691 assert_eq!(val, f64::MAX);
1692 }
1693
1694 #[test]
1695 fn sdf_gradient_nonzero_in_sphere() {
1696 let sdf = make_sdf_sphere([2.5, 2.5, 2.5], 1.0, 6, 6, 6, 1.0, [0.0, 0.0, 0.0]);
1697 let g = sdf.gradient([2.5, 2.5, 1.8]);
1698 let gl = len3(g);
1699 assert!(gl > 0.5, "gradient should be nonzero: len={gl}");
1700 }
1701
1702 #[test]
1703 fn self_collision_update_and_query() {
1704 let mut sc = SelfCollision::new(0.5, 0.3, std::f64::consts::PI);
1705 let verts = vec![[0.0f64, 0.0, 0.0], [0.1, 0.0, 0.0], [0.2, 0.0, 0.0]];
1706 sc.update(&verts);
1707 let pairs = sc.find_pairs(&verts, None);
1708 assert!(!pairs.is_empty());
1709 for p in &pairs {
1710 assert!(p.a < p.b);
1711 }
1712 }
1713
1714 #[test]
1715 fn ccd_deformable_edge_edge() {
1716 let ccd = CcdDeformable::new(0.1, 0.1);
1717 let verts0_a = vec![[0.0f64, 0.0, 0.0], [1.0, 0.0, 0.0]];
1718 let verts1_a = vec![[0.0f64, 0.0, 0.0], [1.0, 0.0, 0.0]];
1719 let verts0_b = vec![[0.5, 1.0, 0.0], [0.5, 0.1, 0.0]];
1720 let verts1_b = vec![[0.5, 0.05, 0.0], [0.5, -0.85, 0.0]];
1721 let edges_a = vec![(0, 1)];
1722 let edges_b = vec![(0, 1)];
1723 let results = ccd.edge_edge_ccd(
1724 &edges_a, &verts0_a, &verts1_a, &edges_b, &verts0_b, &verts1_b,
1725 );
1726 let _ = results;
1728 }
1729
1730 #[test]
1731 fn deformable_vs_bvh_multiple_triangles() {
1732 let tris = vec![
1733 [[0.0f64, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
1734 [[2.0f64, 0.0, 0.0], [3.0, 0.0, 0.0], [2.0, 1.0, 0.0]],
1735 ];
1736 let det = DeformableVsBvh::new(tris, 0.1);
1737 let verts = vec![[0.3f64, 0.3, 0.05], [2.5, 0.3, 0.05]];
1738 let contacts = det.query(&verts);
1739 assert_eq!(contacts.len(), 2, "expected 2 contacts");
1740 }
1741}