1use super::functions::*;
6#[derive(Debug, Clone)]
12pub struct RotationalSweep {
13 pub profile: Vec<[f64; 2]>,
15 pub segments: usize,
17}
18impl RotationalSweep {
19 pub fn new(profile: Vec<[f64; 2]>, segments: usize) -> Self {
21 Self {
22 profile,
23 segments: segments.max(3),
24 }
25 }
26 pub fn aabb(&self) -> ([f64; 3], [f64; 3]) {
28 if self.profile.is_empty() {
29 return ([0.0; 3], [0.0; 3]);
30 }
31 let max_r = self
32 .profile
33 .iter()
34 .map(|p| p[0].abs())
35 .fold(0.0f64, f64::max);
36 let min_y = self
37 .profile
38 .iter()
39 .map(|p| p[1])
40 .fold(f64::INFINITY, f64::min);
41 let max_y = self
42 .profile
43 .iter()
44 .map(|p| p[1])
45 .fold(f64::NEG_INFINITY, f64::max);
46 ([-max_r, min_y, -max_r], [max_r, max_y, max_r])
47 }
48 pub fn volume(&self) -> f64 {
50 if self.profile.len() < 2 {
51 return 0.0;
52 }
53 let mut vol = 0.0f64;
54 for i in 0..self.profile.len() - 1 {
55 let [r0, y0] = self.profile[i];
56 let [r1, y1] = self.profile[i + 1];
57 let dy = (y1 - y0).abs();
58 let avg_area = std::f64::consts::PI * (r0 * r0 + r0 * r1 + r1 * r1) / 3.0;
59 vol += avg_area * dy;
60 }
61 vol
62 }
63 pub fn lateral_surface_area(&self) -> f64 {
65 if self.profile.len() < 2 {
66 return 0.0;
67 }
68 let mut area = 0.0f64;
69 for i in 0..self.profile.len() - 1 {
70 let [r0, y0] = self.profile[i];
71 let [r1, y1] = self.profile[i + 1];
72 let dr = r1 - r0;
73 let dy = y1 - y0;
74 let slant = (dr * dr + dy * dy).sqrt();
75 area += std::f64::consts::PI * (r0 + r1) * slant;
76 }
77 area
78 }
79 pub fn vertices(&self) -> Vec<[f64; 3]> {
83 let segs = self.segments;
84 let mut verts = Vec::with_capacity(self.profile.len() * segs);
85 for &[r, y] in &self.profile {
86 for s in 0..segs {
87 let angle = 2.0 * std::f64::consts::PI * s as f64 / segs as f64;
88 verts.push([r * angle.cos(), y, r * angle.sin()]);
89 }
90 }
91 verts
92 }
93}
94#[derive(Debug, Clone)]
96pub struct LinearCastResult {
97 pub toi: f64,
99 pub contact_point: [f64; 3],
101 pub normal: [f64; 3],
103}
104#[derive(Debug, Clone)]
106pub struct SweptSphere {
107 pub center_start: [f64; 3],
109 pub center_end: [f64; 3],
111 pub radius: f64,
113}
114impl SweptSphere {
115 pub fn new(center_start: [f64; 3], center_end: [f64; 3], radius: f64) -> Self {
117 Self {
118 center_start,
119 center_end,
120 radius,
121 }
122 }
123 pub fn aabb(&self) -> ([f64; 3], [f64; 3]) {
125 let r = self.radius;
126 let min = [
127 self.center_start[0].min(self.center_end[0]) - r,
128 self.center_start[1].min(self.center_end[1]) - r,
129 self.center_start[2].min(self.center_end[2]) - r,
130 ];
131 let max = [
132 self.center_start[0].max(self.center_end[0]) + r,
133 self.center_start[1].max(self.center_end[1]) + r,
134 self.center_start[2].max(self.center_end[2]) + r,
135 ];
136 (min, max)
137 }
138 pub fn sweep_length(&self) -> f64 {
140 len3(sub3(self.center_end, self.center_start))
141 }
142 pub fn center_at(&self, t: f64) -> [f64; 3] {
144 lerp3(self.center_start, self.center_end, t)
145 }
146 pub fn direction(&self) -> [f64; 3] {
148 sub3(self.center_end, self.center_start)
149 }
150 pub fn surface_area(&self) -> f64 {
152 let l = self.sweep_length();
153 2.0 * std::f64::consts::PI * self.radius * l
154 + 4.0 * std::f64::consts::PI * self.radius * self.radius
155 }
156 pub fn volume(&self) -> f64 {
158 let l = self.sweep_length();
159 let r = self.radius;
160 std::f64::consts::PI * r * r * l + (4.0 / 3.0) * std::f64::consts::PI * r * r * r
161 }
162 pub fn ray_intersect(&self, ray_origin: [f64; 3], ray_dir: [f64; 3]) -> Option<f64> {
171 let pa = self.center_start;
172 let pb = self.center_end;
173 let r = self.radius;
174 let d = sub3(pb, pa);
175 let ro = sub3(ray_origin, pa);
176 let dd = dot3(d, d);
177 let rd = dot3(ray_dir, d);
178 let ro_d = dot3(ro, d);
179 let ro_ro = dot3(ro, ro);
180 let rd_rd = dot3(ray_dir, ray_dir);
181 let ro_rd = dot3(ro, ray_dir);
182 let a = rd_rd - rd * rd / dd;
183 let b = 2.0 * (ro_rd - ro_d * rd / dd);
184 let c = ro_ro - ro_d * ro_d / dd - r * r;
185 let mut t_min = f64::INFINITY;
186 let disc = b * b - 4.0 * a * c;
187 if disc >= 0.0 && a.abs() > 1e-14 {
188 let sq = disc.sqrt();
189 for &sign in &[-1.0_f64, 1.0_f64] {
190 let t = (-b + sign * sq) / (2.0 * a);
191 if t >= 0.0 {
192 let proj = (ro_d + t * rd) / dd;
193 if (0.0..=1.0).contains(&proj) && t < t_min {
194 t_min = t;
195 }
196 }
197 }
198 }
199 {
200 let qa = rd_rd;
201 let qb = 2.0 * ro_rd;
202 let qc = ro_ro - r * r;
203 let disc_a = qb * qb - 4.0 * qa * qc;
204 if disc_a >= 0.0 {
205 let sq = disc_a.sqrt();
206 for &sign in &[-1.0_f64, 1.0_f64] {
207 let t = (-qb + sign * sq) / (2.0 * qa);
208 if t >= 0.0 {
209 let proj = (ro_d + t * rd) / dd;
210 if proj <= 0.0 && t < t_min {
211 t_min = t;
212 }
213 }
214 }
215 }
216 }
217 {
218 let ro_b = sub3(ray_origin, pb);
219 let ro_b_ro_b = dot3(ro_b, ro_b);
220 let ro_b_rd = dot3(ro_b, ray_dir);
221 let qa = rd_rd;
222 let qb = 2.0 * ro_b_rd;
223 let qc = ro_b_ro_b - r * r;
224 let disc_b = qb * qb - 4.0 * qa * qc;
225 if disc_b >= 0.0 {
226 let sq = disc_b.sqrt();
227 for &sign in &[-1.0_f64, 1.0_f64] {
228 let t = (-qb + sign * sq) / (2.0 * qa);
229 if t >= 0.0 {
230 let proj = (ro_d + t * rd) / dd;
231 if proj >= 1.0 && t < t_min {
232 t_min = t;
233 }
234 }
235 }
236 }
237 }
238 if t_min.is_finite() { Some(t_min) } else { None }
239 }
240}
241#[derive(Debug, Clone)]
247pub struct SweptObb {
248 pub center_start: [f64; 3],
250 pub axes: [[f64; 3]; 3],
252 pub half_extents: [f64; 3],
254 pub displacement: [f64; 3],
256}
257impl SweptObb {
258 pub fn new(
260 center_start: [f64; 3],
261 axes: [[f64; 3]; 3],
262 half_extents: [f64; 3],
263 displacement: [f64; 3],
264 ) -> Self {
265 Self {
266 center_start,
267 axes,
268 half_extents,
269 displacement,
270 }
271 }
272 pub fn center_end(&self) -> [f64; 3] {
274 add3(self.center_start, self.displacement)
275 }
276 pub fn aabb(&self) -> ([f64; 3], [f64; 3]) {
278 let mut world_min = [f64::INFINITY; 3];
279 let mut world_max = [f64::NEG_INFINITY; 3];
280 for ¢er in &[self.center_start, self.center_end()] {
281 for k in 0..3 {
282 let mut r = 0.0f64;
283 for j in 0..3 {
284 r += self.half_extents[j] * self.axes[j][k].abs();
285 }
286 if center[k] - r < world_min[k] {
287 world_min[k] = center[k] - r;
288 }
289 if center[k] + r > world_max[k] {
290 world_max[k] = center[k] + r;
291 }
292 }
293 }
294 (world_min, world_max)
295 }
296 pub fn volume(&self) -> f64 {
298 8.0 * self.half_extents[0] * self.half_extents[1] * self.half_extents[2]
299 }
300 pub fn support(&self, dir: [f64; 3]) -> [f64; 3] {
302 let mut result = self.center_start;
303 for j in 0..3 {
304 let s = if dot3(self.axes[j], dir) >= 0.0 {
305 1.0
306 } else {
307 -1.0
308 };
309 result = add3(result, scale3(self.axes[j], s * self.half_extents[j]));
310 }
311 result
312 }
313}
314#[derive(Debug, Clone)]
320pub struct SweptCapsule {
321 pub position_start: [f64; 3],
323 pub position_end: [f64; 3],
325 pub radius: f64,
327 pub half_height: f64,
329}
330impl SweptCapsule {
331 pub fn new(
333 position_start: [f64; 3],
334 position_end: [f64; 3],
335 radius: f64,
336 half_height: f64,
337 ) -> Self {
338 Self {
339 position_start,
340 position_end,
341 radius,
342 half_height,
343 }
344 }
345 pub fn aabb(&self) -> ([f64; 3], [f64; 3]) {
347 let r = self.radius;
348 let h = self.half_height;
349 let expand = [r, h + r, r];
350 let mut world_min = [f64::INFINITY; 3];
351 let mut world_max = [f64::NEG_INFINITY; 3];
352 for &pos in &[self.position_start, self.position_end] {
353 for k in 0..3 {
354 let lo = pos[k] - expand[k];
355 let hi = pos[k] + expand[k];
356 if lo < world_min[k] {
357 world_min[k] = lo;
358 }
359 if hi > world_max[k] {
360 world_max[k] = hi;
361 }
362 }
363 }
364 (world_min, world_max)
365 }
366 pub fn position_at(&self, t: f64) -> [f64; 3] {
368 lerp3(self.position_start, self.position_end, t)
369 }
370 pub fn capsule_volume(&self) -> f64 {
372 let r = self.radius;
373 let h = 2.0 * self.half_height;
374 std::f64::consts::PI * r * r * h + (4.0 / 3.0) * std::f64::consts::PI * r * r * r
375 }
376 pub fn toi_vs_sphere(&self, sphere_center: [f64; 3], sphere_radius: f64) -> Option<f64> {
380 let effective_radius = self.radius + self.half_height;
381 let combined = effective_radius + sphere_radius;
382 let vel = sub3(self.position_end, self.position_start);
383 let rel = sub3(self.position_start, sphere_center);
384 let a = dot3(vel, vel);
385 let b = 2.0 * dot3(rel, vel);
386 let c = dot3(rel, rel) - combined * combined;
387 if a < 1e-14 {
388 return if c <= 0.0 { Some(0.0) } else { None };
389 }
390 let disc = b * b - 4.0 * a * c;
391 if disc < 0.0 {
392 return None;
393 }
394 let sq = disc.sqrt();
395 let t1 = (-b - sq) / (2.0 * a);
396 let t2 = (-b + sq) / (2.0 * a);
397 let t = if t1 >= 0.0 { t1 } else { t2 };
398 if (0.0..=1.0).contains(&t) {
399 Some(t)
400 } else {
401 None
402 }
403 }
404}
405#[derive(Debug, Clone)]
409pub struct SweptBox {
410 pub transform_start: [[f64; 4]; 4],
412 pub transform_end: [[f64; 4]; 4],
414 pub half_extents: [f64; 3],
416}
417impl SweptBox {
418 pub fn new(
420 transform_start: [[f64; 4]; 4],
421 transform_end: [[f64; 4]; 4],
422 half_extents: [f64; 3],
423 ) -> Self {
424 Self {
425 transform_start,
426 transform_end,
427 half_extents,
428 }
429 }
430 pub fn aabb(&self) -> ([f64; 3], [f64; 3]) {
435 let hx = self.half_extents[0];
436 let hy = self.half_extents[1];
437 let hz = self.half_extents[2];
438 let corners: [[f64; 3]; 8] = [
439 [-hx, -hy, -hz],
440 [hx, -hy, -hz],
441 [-hx, hy, -hz],
442 [hx, hy, -hz],
443 [-hx, -hy, hz],
444 [hx, -hy, hz],
445 [-hx, hy, hz],
446 [hx, hy, hz],
447 ];
448 let mut world_min = [f64::INFINITY; 3];
449 let mut world_max = [f64::NEG_INFINITY; 3];
450 for &m in &[self.transform_start, self.transform_end] {
451 for &lc in &corners {
452 let wc = transform_point(m, lc);
453 for k in 0..3 {
454 if wc[k] < world_min[k] {
455 world_min[k] = wc[k];
456 }
457 if wc[k] > world_max[k] {
458 world_max[k] = wc[k];
459 }
460 }
461 }
462 }
463 (world_min, world_max)
464 }
465 pub fn aabb_sampled(&self, n_samples: usize) -> ([f64; 3], [f64; 3]) {
467 let hx = self.half_extents[0];
468 let hy = self.half_extents[1];
469 let hz = self.half_extents[2];
470 let corners: [[f64; 3]; 8] = [
471 [-hx, -hy, -hz],
472 [hx, -hy, -hz],
473 [-hx, hy, -hz],
474 [hx, hy, -hz],
475 [-hx, -hy, hz],
476 [hx, -hy, hz],
477 [-hx, hy, hz],
478 [hx, hy, hz],
479 ];
480 let mut world_min = [f64::INFINITY; 3];
481 let mut world_max = [f64::NEG_INFINITY; 3];
482 let steps = n_samples.max(2);
483 for i in 0..steps {
484 let t = i as f64 / (steps - 1) as f64;
485 let m = lerp_matrix(self.transform_start, self.transform_end, t);
486 for &lc in &corners {
487 let wc = transform_point(m, lc);
488 for k in 0..3 {
489 if wc[k] < world_min[k] {
490 world_min[k] = wc[k];
491 }
492 if wc[k] > world_max[k] {
493 world_max[k] = wc[k];
494 }
495 }
496 }
497 }
498 (world_min, world_max)
499 }
500 pub fn box_volume(&self) -> f64 {
502 8.0 * self.half_extents[0] * self.half_extents[1] * self.half_extents[2]
503 }
504 pub fn start_translation(&self) -> [f64; 3] {
506 [
507 self.transform_start[0][3],
508 self.transform_start[1][3],
509 self.transform_start[2][3],
510 ]
511 }
512 pub fn end_translation(&self) -> [f64; 3] {
514 [
515 self.transform_end[0][3],
516 self.transform_end[1][3],
517 self.transform_end[2][3],
518 ]
519 }
520 pub fn displacement(&self) -> [f64; 3] {
522 sub3(self.end_translation(), self.start_translation())
523 }
524}
525#[derive(Debug, Clone)]
530pub struct LinearExtrusion {
531 pub profile: Vec<[f64; 2]>,
533 pub sweep_vec: [f64; 3],
535}
536impl LinearExtrusion {
537 pub fn new(profile: Vec<[f64; 2]>, sweep_vec: [f64; 3]) -> Self {
539 Self { profile, sweep_vec }
540 }
541 pub fn aabb(&self) -> ([f64; 3], [f64; 3]) {
543 if self.profile.is_empty() {
544 return ([0.0; 3], [0.0; 3]);
545 }
546 let mut mn = [f64::INFINITY; 3];
547 let mut mx = [f64::NEG_INFINITY; 3];
548 for &[x, y] in &self.profile {
549 mn[0] = mn[0].min(x);
550 mx[0] = mx[0].max(x);
551 mn[1] = mn[1].min(y);
552 mx[1] = mx[1].max(y);
553 mn[2] = mn[2].min(0.0);
554 mx[2] = mx[2].max(0.0);
555 let sx = x + self.sweep_vec[0];
556 let sy = y + self.sweep_vec[1];
557 let sz = self.sweep_vec[2];
558 mn[0] = mn[0].min(sx);
559 mx[0] = mx[0].max(sx);
560 mn[1] = mn[1].min(sy);
561 mx[1] = mx[1].max(sy);
562 mn[2] = mn[2].min(sz);
563 mx[2] = mx[2].max(sz);
564 }
565 (mn, mx)
566 }
567 pub fn volume(&self) -> f64 {
569 let area = self.profile_area();
570 let len = len3(self.sweep_vec);
571 area * len
572 }
573 pub fn profile_area(&self) -> f64 {
575 let n = self.profile.len();
576 if n < 3 {
577 return 0.0;
578 }
579 let mut signed = 0.0f64;
580 for i in 0..n {
581 let [x0, y0] = self.profile[i];
582 let [x1, y1] = self.profile[(i + 1) % n];
583 signed += x0 * y1 - x1 * y0;
584 }
585 (signed * 0.5).abs()
586 }
587 pub fn profile_perimeter(&self) -> f64 {
589 let n = self.profile.len();
590 if n < 2 {
591 return 0.0;
592 }
593 let mut perim = 0.0f64;
594 for i in 0..n {
595 let [x0, y0] = self.profile[i];
596 let [x1, y1] = self.profile[(i + 1) % n];
597 let dx = x1 - x0;
598 let dy = y1 - y0;
599 perim += (dx * dx + dy * dy).sqrt();
600 }
601 perim
602 }
603 pub fn surface_area(&self) -> f64 {
607 let area = self.profile_area();
608 let perim = self.profile_perimeter();
609 let len = len3(self.sweep_vec);
610 2.0 * area + perim * len
611 }
612}
613#[derive(Debug, Clone)]
619pub struct SweptAabb {
620 pub start_min: [f64; 3],
622 pub start_max: [f64; 3],
624 pub end_min: [f64; 3],
626 pub end_max: [f64; 3],
628}
629impl SweptAabb {
630 pub fn new(
632 start_min: [f64; 3],
633 start_max: [f64; 3],
634 end_min: [f64; 3],
635 end_max: [f64; 3],
636 ) -> Self {
637 Self {
638 start_min,
639 start_max,
640 end_min,
641 end_max,
642 }
643 }
644 pub fn aabb(&self) -> ([f64; 3], [f64; 3]) {
646 let mut mn = [f64::INFINITY; 3];
647 let mut mx = [f64::NEG_INFINITY; 3];
648 for k in 0..3 {
649 mn[k] = self.start_min[k].min(self.end_min[k]);
650 mx[k] = self.start_max[k].max(self.end_max[k]);
651 }
652 (mn, mx)
653 }
654 pub fn contains_point(&self, p: [f64; 3]) -> bool {
656 let (mn, mx) = self.aabb();
657 (0..3).all(|k| p[k] >= mn[k] && p[k] <= mx[k])
658 }
659 pub fn displacement(&self) -> [f64; 3] {
661 let start_center = scale3(add3(self.start_min, self.start_max), 0.5);
662 let end_center = scale3(add3(self.end_min, self.end_max), 0.5);
663 sub3(end_center, start_center)
664 }
665}
666#[derive(Debug, Clone)]
668pub struct CcdResult {
669 pub toi: f64,
671 pub contact_point: [f64; 3],
673}