1use std::f64::consts::PI;
7
8use crate::MathError;
9use crate::aabb::Aabb3;
10use crate::frame::Frame3;
11use crate::vec::{Point3, Vec3};
12
13fn conic_aabb(center: Point3, (a, u): (f64, Vec3), (b, v): (f64, Vec3)) -> Aabb3 {
16 let reach = |ui: f64, vi: f64| (a * ui).hypot(b * vi);
17 let r = Vec3::new(
18 reach(u.x(), v.x()),
19 reach(u.y(), v.y()),
20 reach(u.z(), v.z()),
21 );
22 Aabb3 {
23 min: center - r,
24 max: center + r,
25 }
26}
27
28fn conic_arc_aabb(
31 center: Point3,
32 (a, u): (f64, Vec3),
33 (b, v): (f64, Vec3),
34 (t0, t1): (f64, f64),
35) -> Aabb3 {
36 use std::f64::consts::TAU;
37 if (t1 - t0).abs() >= TAU {
38 return conic_aabb(center, (a, u), (b, v));
39 }
40 let (lo, hi) = if t1 >= t0 { (t0, t1) } else { (t1, t0) };
41 let at = |t: f64| center + u * (a * t.cos()) + v * (b * t.sin());
42 let mut pts = vec![at(lo), at(hi)];
43 for (ui, vi) in [(u.x(), v.x()), (u.y(), v.y()), (u.z(), v.z())] {
44 let peak = (b * vi).atan2(a * ui);
45 for t in [peak, peak + PI] {
46 let t = lo + (t - lo).rem_euclid(TAU);
47 if t <= hi {
48 pts.push(at(t));
49 }
50 }
51 }
52 Aabb3::from_points(pts)
53}
54
55#[derive(Debug, Clone)]
61pub struct Line3D {
62 origin: Point3,
63 direction: Vec3,
64}
65
66impl Line3D {
67 pub fn new(origin: Point3, direction: Vec3) -> Result<Self, MathError> {
73 let len = direction.length();
74 if len < 1e-15 {
75 return Err(MathError::ZeroVector);
76 }
77 Ok(Self {
78 origin,
79 direction: Vec3::new(
80 direction.x() / len,
81 direction.y() / len,
82 direction.z() / len,
83 ),
84 })
85 }
86
87 #[must_use]
89 pub fn evaluate(&self, t: f64) -> Point3 {
90 self.origin + self.direction * t
91 }
92
93 #[must_use]
95 pub const fn tangent(&self) -> Vec3 {
96 self.direction
97 }
98
99 #[must_use]
101 pub fn project(&self, point: Point3) -> f64 {
102 let v = point - self.origin;
103 self.direction.dot(v)
104 }
105
106 #[must_use]
108 pub fn distance_to_point(&self, point: Point3) -> f64 {
109 let v = point - self.origin;
110 let proj = self.direction * self.direction.dot(v);
111 (v - proj).length()
112 }
113
114 #[must_use]
116 pub const fn origin(&self) -> Point3 {
117 self.origin
118 }
119
120 #[must_use]
122 pub const fn direction(&self) -> Vec3 {
123 self.direction
124 }
125}
126
127#[derive(Debug, Clone)]
135#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
136pub struct Circle3D {
137 center: Point3,
138 normal: Vec3,
139 radius: f64,
140 u_axis: Vec3,
141 v_axis: Vec3,
142}
143
144impl Circle3D {
145 pub fn new(center: Point3, normal: Vec3, radius: f64) -> Result<Self, MathError> {
151 if radius <= 0.0 {
152 return Err(MathError::ParameterOutOfRange {
153 value: radius,
154 min: 0.0,
155 max: f64::INFINITY,
156 });
157 }
158 let f = Frame3::from_normal(center, normal)?;
159 Ok(Self {
160 center,
161 normal: f.z,
162 radius,
163 u_axis: f.x,
164 v_axis: f.y,
165 })
166 }
167
168 pub fn new_with_ref(
180 center: Point3,
181 normal: Vec3,
182 radius: f64,
183 ref_dir: Vec3,
184 ) -> Result<Self, MathError> {
185 if radius <= 0.0 {
186 return Err(MathError::ParameterOutOfRange {
187 value: radius,
188 min: 0.0,
189 max: f64::INFINITY,
190 });
191 }
192 let f = Frame3::from_normal_and_ref(center, normal, ref_dir)?;
193 Ok(Self {
194 center,
195 normal: f.z,
196 radius,
197 u_axis: f.x,
198 v_axis: f.y,
199 })
200 }
201
202 #[must_use]
204 pub fn evaluate(&self, t: f64) -> Point3 {
205 let cos_t = t.cos();
206 let sin_t = t.sin();
207 self.center + self.u_axis * (self.radius * cos_t) + self.v_axis * (self.radius * sin_t)
208 }
209
210 #[must_use]
212 pub fn tangent(&self, t: f64) -> Vec3 {
213 let cos_t = t.cos();
214 let sin_t = t.sin();
215 self.u_axis * (-sin_t) + self.v_axis * cos_t
216 }
217
218 #[must_use]
220 pub fn circumference(&self) -> f64 {
221 2.0 * PI * self.radius
222 }
223
224 #[must_use]
226 pub const fn center(&self) -> Point3 {
227 self.center
228 }
229
230 #[must_use]
232 pub const fn radius(&self) -> f64 {
233 self.radius
234 }
235
236 #[must_use]
238 pub const fn normal(&self) -> Vec3 {
239 self.normal
240 }
241
242 #[must_use]
244 pub fn project(&self, point: Point3) -> f64 {
245 let v = point - self.center;
246 let u_comp = self.u_axis.dot(v);
247 let v_comp = self.v_axis.dot(v);
248 v_comp.atan2(u_comp)
249 }
250
251 #[must_use]
253 pub const fn u_axis(&self) -> Vec3 {
254 self.u_axis
255 }
256
257 #[must_use]
259 pub const fn v_axis(&self) -> Vec3 {
260 self.v_axis
261 }
262
263 #[must_use]
266 pub fn aabb(&self) -> Aabb3 {
267 conic_aabb(
268 self.center,
269 (self.radius, self.u_axis),
270 (self.radius, self.v_axis),
271 )
272 }
273
274 #[must_use]
276 pub fn arc_aabb(&self, t0: f64, t1: f64) -> Aabb3 {
277 conic_arc_aabb(
278 self.center,
279 (self.radius, self.u_axis),
280 (self.radius, self.v_axis),
281 (t0, t1),
282 )
283 }
284
285 pub fn with_axes(
291 center: Point3,
292 normal: Vec3,
293 radius: f64,
294 u_axis: Vec3,
295 v_axis: Vec3,
296 ) -> Result<Self, MathError> {
297 if radius <= 0.0 {
298 return Err(MathError::ParameterOutOfRange {
299 value: radius,
300 min: 0.0,
301 max: f64::INFINITY,
302 });
303 }
304 Ok(Self {
305 center,
306 normal,
307 radius,
308 u_axis,
309 v_axis,
310 })
311 }
312
313 #[must_use]
328 pub fn intersect_segment(
329 &self,
330 seg_start: Point3,
331 seg_end: Point3,
332 tol: f64,
333 ) -> Vec<(Point3, f64)> {
334 let mut out = Vec::new();
335 let d = seg_end - seg_start;
336 let seg_len_sq = d.length_squared();
337 if seg_len_sq < tol * tol {
338 return out;
339 }
340
341 let h0 = (seg_start - self.center).dot(self.normal);
343 let h1 = (seg_end - self.center).dot(self.normal);
344
345 let on_plane = |p: Point3| -> bool {
346 let v = p - self.center;
347 let in_plane = v.dot(self.normal).abs() < tol;
348 let r = v.length();
349 in_plane && (r - self.radius).abs() < tol
350 };
351
352 let mut push_if_unique = |p: Point3| {
355 let v = p - self.center;
356 let mut t = v.dot(self.v_axis).atan2(v.dot(self.u_axis));
358 if t < 0.0 {
359 t += std::f64::consts::TAU;
360 }
361 if out
362 .iter()
363 .any(|(q, _): &(Point3, f64)| (*q - p).length() < tol)
364 {
365 return;
366 }
367 out.push((p, t));
368 };
369
370 if h0.abs() < tol && h1.abs() < tol {
371 let p0_u = (seg_start - self.center).dot(self.u_axis);
374 let p0_v = (seg_start - self.center).dot(self.v_axis);
375 let p1_u = (seg_end - self.center).dot(self.u_axis);
376 let p1_v = (seg_end - self.center).dot(self.v_axis);
377 let du = p1_u - p0_u;
378 let dv = p1_v - p0_v;
379 let a = du * du + dv * dv;
385 let b = p0_u * du + p0_v * dv;
386 let c = p0_u * p0_u + p0_v * p0_v - self.radius * self.radius;
387 let disc = b * b - a * c;
388 if a < tol * tol || disc < -tol * tol * a {
394 return out;
395 }
396 let disc = disc.max(0.0);
397 let s_slack = tol / seg_len_sq.sqrt();
398 let sqrt_disc = disc.sqrt();
408 let roots: &[f64] = if disc <= 2.0 * self.radius * tol * a {
409 &[-b / a]
410 } else {
411 &[(-b - sqrt_disc) / a, (-b + sqrt_disc) / a]
412 };
413 for &s in roots {
414 if s >= -s_slack && s <= 1.0 + s_slack {
415 let s = s.clamp(0.0, 1.0);
416 let p = Point3::new(
417 seg_start.x() + s * d.x(),
418 seg_start.y() + s * d.y(),
419 seg_start.z() + s * d.z(),
420 );
421 push_if_unique(p);
422 }
423 }
424 } else if h0 * h1 <= tol * tol {
425 let denom = h0 - h1;
429 if denom.abs() < tol {
430 return out;
431 }
432 let s = h0 / denom;
433 let s_slack = tol / seg_len_sq.sqrt();
434 if s < -s_slack || s > 1.0 + s_slack {
435 return out;
436 }
437 let s = s.clamp(0.0, 1.0);
438 let p = Point3::new(
439 seg_start.x() + s * d.x(),
440 seg_start.y() + s * d.y(),
441 seg_start.z() + s * d.z(),
442 );
443 if on_plane(p) {
444 push_if_unique(p);
445 }
446 }
447 out
450 }
451
452 #[must_use]
467 pub fn intersect_circle(&self, other: &Self, tol: f64) -> Vec<(Point3, f64)> {
468 let mut out = Vec::new();
469 if self.normal.cross(other.normal).length() > 1e-9 {
470 return self.intersect_skew_circle(other, tol);
471 }
472 let dvec = other.center - self.center;
473 if dvec.dot(self.normal).abs() > tol {
474 return out; }
476 let du = dvec.dot(self.u_axis);
477 let dv = dvec.dot(self.v_axis);
478 let d2 = du * du + dv * dv;
479 let d = d2.sqrt();
480 if d < tol {
481 return out; }
483 let (r1, r2) = (self.radius, other.radius);
484 let a = (d2 + r1 * r1 - r2 * r2) / (2.0 * d);
485 let h2 = r1 * r1 - a * a;
486 let r_eff = r1.min(r2);
487 if h2 < -2.0 * r_eff * tol {
488 return out; }
490 let ux = Vec3::new(
491 (self.u_axis.x() * du + self.v_axis.x() * dv) / d,
492 (self.u_axis.y() * du + self.v_axis.y() * dv) / d,
493 (self.u_axis.z() * du + self.v_axis.z() * dv) / d,
494 );
495 let vx = self.normal.cross(ux);
496 let foot = self.center + ux * a;
497 let mut push = |p: Point3| {
498 let v = p - self.center;
499 let mut t = v.dot(self.v_axis).atan2(v.dot(self.u_axis));
500 if t < 0.0 {
501 t += std::f64::consts::TAU;
502 }
503 if !out
504 .iter()
505 .any(|(q, _): &(Point3, f64)| (*q - p).length() < tol)
506 {
507 out.push((p, t));
508 }
509 };
510 if h2 <= 2.0 * r_eff * tol {
511 push(foot);
512 } else {
513 let h = h2.sqrt();
514 push(foot + vx * h);
515 push(foot - vx * h);
516 }
517 out
518 }
519
520 fn intersect_skew_circle(&self, other: &Self, tol: f64) -> Vec<(Point3, f64)> {
524 let mut out: Vec<(Point3, f64)> = Vec::new();
525 let (n1, n2) = (self.normal, other.normal);
526 let Ok(dir) = n1.cross(n2).normalize() else {
527 return out;
528 };
529 let (h1, h2) = (
532 n1.dot(Vec3::new(self.center.x(), self.center.y(), self.center.z())),
533 n2.dot(Vec3::new(
534 other.center.x(),
535 other.center.y(),
536 other.center.z(),
537 )),
538 );
539 let (a, b, c) = (n1.dot(n1), n2.dot(n2), n1.dot(n2));
540 let det = a.mul_add(b, -(c * c));
541 let base = n1 * ((h1 * b - h2 * c) / det) + n2 * ((h2 * a - h1 * c) / det);
542 let base = Point3::new(base.x(), base.y(), base.z());
543 let off = base - self.center;
544 let half_b = dir.dot(off);
545 let disc = half_b.mul_add(half_b, -(off.dot(off) - self.radius * self.radius));
546 let well = 2.0 * self.radius * tol;
547 if disc < -well {
548 return out;
549 }
550 let roots: Vec<f64> = if disc <= well {
551 vec![-half_b]
552 } else {
553 let root = disc.sqrt();
554 vec![-half_b - root, -half_b + root]
555 };
556 for s in roots {
557 let p = base + dir * s;
558 if ((p - other.center).length() - other.radius).abs() > tol {
559 continue;
560 }
561 let v = p - self.center;
562 let mut t = v.dot(self.v_axis).atan2(v.dot(self.u_axis));
563 if t < 0.0 {
564 t += std::f64::consts::TAU;
565 }
566 if !out.iter().any(|(q, _)| (*q - p).length() < tol) {
567 out.push((p, t));
568 }
569 }
570 out
571 }
572}
573
574#[derive(Debug, Clone)]
580#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
581pub struct Ellipse3D {
582 center: Point3,
583 normal: Vec3,
584 semi_major: f64,
585 semi_minor: f64,
586 u_axis: Vec3,
587 v_axis: Vec3,
588}
589
590impl Ellipse3D {
591 pub fn new(
600 center: Point3,
601 normal: Vec3,
602 semi_major: f64,
603 semi_minor: f64,
604 ) -> Result<Self, MathError> {
605 if semi_major <= 0.0 || semi_minor <= 0.0 {
606 return Err(MathError::ParameterOutOfRange {
607 value: semi_major.min(semi_minor),
608 min: 0.0,
609 max: f64::INFINITY,
610 });
611 }
612 if semi_minor > semi_major {
613 return Err(MathError::ParameterOutOfRange {
614 value: semi_minor,
615 min: 0.0,
616 max: semi_major,
617 });
618 }
619 let f = Frame3::from_normal(center, normal)?;
620 Ok(Self {
621 center,
622 normal: f.z,
623 semi_major,
624 semi_minor,
625 u_axis: f.x,
626 v_axis: f.y,
627 })
628 }
629
630 pub fn new_with_ref(
642 center: Point3,
643 normal: Vec3,
644 semi_major: f64,
645 semi_minor: f64,
646 ref_dir: Vec3,
647 ) -> Result<Self, MathError> {
648 if semi_major <= 0.0 || semi_minor <= 0.0 {
649 return Err(MathError::ParameterOutOfRange {
650 value: semi_major.min(semi_minor),
651 min: 0.0,
652 max: f64::INFINITY,
653 });
654 }
655 if semi_minor > semi_major {
656 return Err(MathError::ParameterOutOfRange {
657 value: semi_minor,
658 min: 0.0,
659 max: semi_major,
660 });
661 }
662 let f = Frame3::from_normal_and_ref(center, normal, ref_dir)?;
663 Ok(Self {
664 center,
665 normal: f.z,
666 semi_major,
667 semi_minor,
668 u_axis: f.x,
669 v_axis: f.y,
670 })
671 }
672
673 #[must_use]
675 pub fn evaluate(&self, t: f64) -> Point3 {
676 let cos_t = t.cos();
677 let sin_t = t.sin();
678 self.center
679 + self.u_axis * (self.semi_major * cos_t)
680 + self.v_axis * (self.semi_minor * sin_t)
681 }
682
683 #[must_use]
685 pub fn tangent(&self, t: f64) -> Vec3 {
686 let cos_t = t.cos();
687 let sin_t = t.sin();
688 self.u_axis * (-self.semi_major * sin_t) + self.v_axis * (self.semi_minor * cos_t)
689 }
690
691 #[must_use]
693 pub const fn center(&self) -> Point3 {
694 self.center
695 }
696
697 #[must_use]
699 pub const fn semi_major(&self) -> f64 {
700 self.semi_major
701 }
702
703 #[must_use]
705 pub const fn semi_minor(&self) -> f64 {
706 self.semi_minor
707 }
708
709 #[must_use]
711 pub const fn normal(&self) -> Vec3 {
712 self.normal
713 }
714
715 #[must_use]
717 pub fn approximate_circumference(&self) -> f64 {
718 let a = self.semi_major;
719 let b = self.semi_minor;
720 let h = (a - b) * (a - b) / ((a + b) * (a + b));
721 PI * (a + b) * (1.0 + 3.0 * h / (10.0 + (3.0f64.mul_add(-h, 4.0)).sqrt()))
722 }
723
724 #[must_use]
726 pub fn project(&self, point: Point3) -> f64 {
727 let v = point - self.center;
728 let u_comp = self.u_axis.dot(v) / self.semi_major;
729 let v_comp = self.v_axis.dot(v) / self.semi_minor;
730 v_comp.atan2(u_comp)
731 }
732
733 #[must_use]
735 pub const fn u_axis(&self) -> Vec3 {
736 self.u_axis
737 }
738
739 #[must_use]
741 pub const fn v_axis(&self) -> Vec3 {
742 self.v_axis
743 }
744
745 #[must_use]
748 pub fn aabb(&self) -> Aabb3 {
749 conic_aabb(
750 self.center,
751 (self.semi_major, self.u_axis),
752 (self.semi_minor, self.v_axis),
753 )
754 }
755
756 #[must_use]
758 pub fn arc_aabb(&self, t0: f64, t1: f64) -> Aabb3 {
759 conic_arc_aabb(
760 self.center,
761 (self.semi_major, self.u_axis),
762 (self.semi_minor, self.v_axis),
763 (t0, t1),
764 )
765 }
766
767 pub fn with_axes(
773 center: Point3,
774 normal: Vec3,
775 semi_major: f64,
776 semi_minor: f64,
777 u_axis: Vec3,
778 v_axis: Vec3,
779 ) -> Result<Self, MathError> {
780 if semi_major <= 0.0 || semi_minor <= 0.0 {
781 return Err(MathError::ParameterOutOfRange {
782 value: semi_major.min(semi_minor),
783 min: 0.0,
784 max: f64::INFINITY,
785 });
786 }
787 Ok(Self {
788 center,
789 normal,
790 semi_major,
791 semi_minor,
792 u_axis,
793 v_axis,
794 })
795 }
796}
797
798#[derive(Debug, Clone)]
806pub struct Parabola3D {
807 vertex: Point3,
808 axis_dir: Vec3,
809 focal_length: f64,
810 u_axis: Vec3,
811}
812
813impl Parabola3D {
814 pub fn new(vertex: Point3, axis_dir: Vec3, focal_length: f64) -> Result<Self, MathError> {
823 if focal_length <= 0.0 {
824 return Err(MathError::ParameterOutOfRange {
825 value: focal_length,
826 min: f64::EPSILON,
827 max: f64::MAX,
828 });
829 }
830 let f = Frame3::from_normal(vertex, axis_dir)?;
831 Ok(Self {
832 vertex,
833 axis_dir: f.z,
834 focal_length,
835 u_axis: f.x,
836 })
837 }
838
839 #[must_use]
843 pub fn evaluate(&self, t: f64) -> Point3 {
844 let along_axis = (t * t) / (4.0 * self.focal_length);
845 self.vertex + self.axis_dir * along_axis + self.u_axis * t
846 }
847
848 #[must_use]
850 pub fn tangent(&self, t: f64) -> Vec3 {
851 let d_axis = t / (2.0 * self.focal_length);
852 self.axis_dir * d_axis + self.u_axis
853 }
854
855 #[must_use]
857 pub fn curvature(&self, t: f64) -> f64 {
858 let two_f = 2.0 * self.focal_length;
859 let ratio = t / two_f;
860 let denom = ratio.mul_add(ratio, 1.0);
861 1.0 / (two_f * denom.powf(1.5))
862 }
863
864 #[must_use]
866 pub const fn vertex(&self) -> Point3 {
867 self.vertex
868 }
869
870 #[must_use]
872 pub const fn focal_length(&self) -> f64 {
873 self.focal_length
874 }
875
876 #[must_use]
878 pub const fn axis_dir(&self) -> Vec3 {
879 self.axis_dir
880 }
881
882 #[must_use]
886 pub const fn u_axis(&self) -> Vec3 {
887 self.u_axis
888 }
889
890 #[must_use]
892 pub fn focus(&self) -> Point3 {
893 self.vertex + self.axis_dir * self.focal_length
894 }
895}
896
897#[derive(Debug, Clone)]
904pub struct Hyperbola3D {
905 center: Point3,
906 normal: Vec3,
907 semi_major: f64,
908 semi_minor: f64,
909 u_axis: Vec3,
910 v_axis: Vec3,
911}
912
913impl Hyperbola3D {
914 pub fn new(
922 center: Point3,
923 normal: Vec3,
924 semi_major: f64,
925 semi_minor: f64,
926 ) -> Result<Self, MathError> {
927 if semi_major <= 0.0 || semi_minor <= 0.0 {
928 return Err(MathError::ParameterOutOfRange {
929 value: semi_major.min(semi_minor),
930 min: f64::EPSILON,
931 max: f64::MAX,
932 });
933 }
934 let f = Frame3::from_normal(center, normal)?;
935 Ok(Self {
936 center,
937 normal: f.z,
938 semi_major,
939 semi_minor,
940 u_axis: f.x,
941 v_axis: f.y,
942 })
943 }
944
945 #[must_use]
947 pub fn evaluate(&self, t: f64) -> Point3 {
948 self.center
949 + self.u_axis * (self.semi_major * t.cosh())
950 + self.v_axis * (self.semi_minor * t.sinh())
951 }
952
953 #[must_use]
955 pub fn tangent(&self, t: f64) -> Vec3 {
956 self.u_axis * (self.semi_major * t.sinh()) + self.v_axis * (self.semi_minor * t.cosh())
957 }
958
959 #[must_use]
961 pub const fn center(&self) -> Point3 {
962 self.center
963 }
964
965 #[must_use]
967 pub const fn semi_major(&self) -> f64 {
968 self.semi_major
969 }
970
971 #[must_use]
973 pub const fn semi_minor(&self) -> f64 {
974 self.semi_minor
975 }
976
977 #[must_use]
979 pub const fn normal(&self) -> Vec3 {
980 self.normal
981 }
982
983 #[must_use]
988 pub const fn u_axis(&self) -> Vec3 {
989 self.u_axis
990 }
991
992 #[must_use]
994 pub const fn v_axis(&self) -> Vec3 {
995 self.v_axis
996 }
997
998 #[must_use]
1000 pub fn eccentricity(&self) -> f64 {
1001 let ratio = self.semi_minor / self.semi_major;
1002 ratio.mul_add(ratio, 1.0).sqrt()
1003 }
1004
1005 #[must_use]
1007 pub fn foci(&self) -> (Point3, Point3) {
1008 let c = self.semi_major.hypot(self.semi_minor);
1009 (
1010 self.center + self.u_axis * c,
1011 self.center + self.u_axis * (-c),
1012 )
1013 }
1014}
1015
1016#[cfg(test)]
1017mod tests;