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