1use std::f64::consts::PI;
7
8use crate::MathError;
9use crate::frame::Frame3;
10use crate::vec::{Point3, Vec3};
11
12#[derive(Debug, Clone)]
18pub struct Line3D {
19 origin: Point3,
20 direction: Vec3,
21}
22
23impl Line3D {
24 pub fn new(origin: Point3, direction: Vec3) -> Result<Self, MathError> {
30 let len = direction.length();
31 if len < 1e-15 {
32 return Err(MathError::ZeroVector);
33 }
34 Ok(Self {
35 origin,
36 direction: Vec3::new(
37 direction.x() / len,
38 direction.y() / len,
39 direction.z() / len,
40 ),
41 })
42 }
43
44 #[must_use]
46 pub fn evaluate(&self, t: f64) -> Point3 {
47 self.origin + self.direction * t
48 }
49
50 #[must_use]
52 pub const fn tangent(&self) -> Vec3 {
53 self.direction
54 }
55
56 #[must_use]
58 pub fn project(&self, point: Point3) -> f64 {
59 let v = point - self.origin;
60 self.direction.dot(v)
61 }
62
63 #[must_use]
65 pub fn distance_to_point(&self, point: Point3) -> f64 {
66 let v = point - self.origin;
67 let proj = self.direction * self.direction.dot(v);
68 (v - proj).length()
69 }
70
71 #[must_use]
73 pub const fn origin(&self) -> Point3 {
74 self.origin
75 }
76
77 #[must_use]
79 pub const fn direction(&self) -> Vec3 {
80 self.direction
81 }
82}
83
84#[derive(Debug, Clone)]
92#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
93pub struct Circle3D {
94 center: Point3,
95 normal: Vec3,
96 radius: f64,
97 u_axis: Vec3,
98 v_axis: Vec3,
99}
100
101impl Circle3D {
102 pub fn new(center: Point3, normal: Vec3, radius: f64) -> Result<Self, MathError> {
108 if radius <= 0.0 {
109 return Err(MathError::ParameterOutOfRange {
110 value: radius,
111 min: 0.0,
112 max: f64::INFINITY,
113 });
114 }
115 let f = Frame3::from_normal(center, normal)?;
116 Ok(Self {
117 center,
118 normal: f.z,
119 radius,
120 u_axis: f.x,
121 v_axis: f.y,
122 })
123 }
124
125 pub fn new_with_ref(
137 center: Point3,
138 normal: Vec3,
139 radius: f64,
140 ref_dir: Vec3,
141 ) -> Result<Self, MathError> {
142 if radius <= 0.0 {
143 return Err(MathError::ParameterOutOfRange {
144 value: radius,
145 min: 0.0,
146 max: f64::INFINITY,
147 });
148 }
149 let f = Frame3::from_normal_and_ref(center, normal, ref_dir)?;
150 Ok(Self {
151 center,
152 normal: f.z,
153 radius,
154 u_axis: f.x,
155 v_axis: f.y,
156 })
157 }
158
159 #[must_use]
161 pub fn evaluate(&self, t: f64) -> Point3 {
162 let cos_t = t.cos();
163 let sin_t = t.sin();
164 self.center + self.u_axis * (self.radius * cos_t) + self.v_axis * (self.radius * sin_t)
165 }
166
167 #[must_use]
169 pub fn tangent(&self, t: f64) -> Vec3 {
170 let cos_t = t.cos();
171 let sin_t = t.sin();
172 self.u_axis * (-sin_t) + self.v_axis * cos_t
173 }
174
175 #[must_use]
177 pub fn circumference(&self) -> f64 {
178 2.0 * PI * self.radius
179 }
180
181 #[must_use]
183 pub const fn center(&self) -> Point3 {
184 self.center
185 }
186
187 #[must_use]
189 pub const fn radius(&self) -> f64 {
190 self.radius
191 }
192
193 #[must_use]
195 pub const fn normal(&self) -> Vec3 {
196 self.normal
197 }
198
199 #[must_use]
201 pub fn project(&self, point: Point3) -> f64 {
202 let v = point - self.center;
203 let u_comp = self.u_axis.dot(v);
204 let v_comp = self.v_axis.dot(v);
205 v_comp.atan2(u_comp)
206 }
207
208 #[must_use]
210 pub const fn u_axis(&self) -> Vec3 {
211 self.u_axis
212 }
213
214 #[must_use]
216 pub const fn v_axis(&self) -> Vec3 {
217 self.v_axis
218 }
219
220 pub fn with_axes(
226 center: Point3,
227 normal: Vec3,
228 radius: f64,
229 u_axis: Vec3,
230 v_axis: Vec3,
231 ) -> Result<Self, MathError> {
232 if radius <= 0.0 {
233 return Err(MathError::ParameterOutOfRange {
234 value: radius,
235 min: 0.0,
236 max: f64::INFINITY,
237 });
238 }
239 Ok(Self {
240 center,
241 normal,
242 radius,
243 u_axis,
244 v_axis,
245 })
246 }
247
248 #[must_use]
263 pub fn intersect_segment(
264 &self,
265 seg_start: Point3,
266 seg_end: Point3,
267 tol: f64,
268 ) -> Vec<(Point3, f64)> {
269 let mut out = Vec::new();
270 let d = seg_end - seg_start;
271 let seg_len_sq = d.length_squared();
272 if seg_len_sq < tol * tol {
273 return out;
274 }
275
276 let h0 = (seg_start - self.center).dot(self.normal);
278 let h1 = (seg_end - self.center).dot(self.normal);
279
280 let on_plane = |p: Point3| -> bool {
281 let v = p - self.center;
282 let in_plane = v.dot(self.normal).abs() < tol;
283 let r = v.length();
284 in_plane && (r - self.radius).abs() < tol
285 };
286
287 let mut push_if_unique = |p: Point3| {
290 let v = p - self.center;
291 let mut t = v.dot(self.v_axis).atan2(v.dot(self.u_axis));
293 if t < 0.0 {
294 t += std::f64::consts::TAU;
295 }
296 if out
297 .iter()
298 .any(|(q, _): &(Point3, f64)| (*q - p).length() < tol)
299 {
300 return;
301 }
302 out.push((p, t));
303 };
304
305 if h0.abs() < tol && h1.abs() < tol {
306 let p0_u = (seg_start - self.center).dot(self.u_axis);
309 let p0_v = (seg_start - self.center).dot(self.v_axis);
310 let p1_u = (seg_end - self.center).dot(self.u_axis);
311 let p1_v = (seg_end - self.center).dot(self.v_axis);
312 let du = p1_u - p0_u;
313 let dv = p1_v - p0_v;
314 let a = du * du + dv * dv;
320 let b = p0_u * du + p0_v * dv;
321 let c = p0_u * p0_u + p0_v * p0_v - self.radius * self.radius;
322 let disc = b * b - a * c;
323 if a < tol * tol || disc < -tol * tol * a {
329 return out;
330 }
331 let disc = disc.max(0.0);
332 let s_slack = tol / seg_len_sq.sqrt();
333 let sqrt_disc = disc.sqrt();
343 let roots: &[f64] = if disc <= 2.0 * self.radius * tol * a {
344 &[-b / a]
345 } else {
346 &[(-b - sqrt_disc) / a, (-b + sqrt_disc) / a]
347 };
348 for &s in roots {
349 if s >= -s_slack && s <= 1.0 + s_slack {
350 let s = s.clamp(0.0, 1.0);
351 let p = Point3::new(
352 seg_start.x() + s * d.x(),
353 seg_start.y() + s * d.y(),
354 seg_start.z() + s * d.z(),
355 );
356 push_if_unique(p);
357 }
358 }
359 } else if h0 * h1 <= tol * tol {
360 let denom = h0 - h1;
364 if denom.abs() < tol {
365 return out;
366 }
367 let s = h0 / denom;
368 let s_slack = tol / seg_len_sq.sqrt();
369 if s < -s_slack || s > 1.0 + s_slack {
370 return out;
371 }
372 let s = s.clamp(0.0, 1.0);
373 let p = Point3::new(
374 seg_start.x() + s * d.x(),
375 seg_start.y() + s * d.y(),
376 seg_start.z() + s * d.z(),
377 );
378 if on_plane(p) {
379 push_if_unique(p);
380 }
381 }
382 out
385 }
386
387 #[must_use]
402 pub fn intersect_circle(&self, other: &Self, tol: f64) -> Vec<(Point3, f64)> {
403 let mut out = Vec::new();
404 if self.normal.cross(other.normal).length() > 1e-9 {
405 return self.intersect_skew_circle(other, tol);
406 }
407 let dvec = other.center - self.center;
408 if dvec.dot(self.normal).abs() > tol {
409 return out; }
411 let du = dvec.dot(self.u_axis);
412 let dv = dvec.dot(self.v_axis);
413 let d2 = du * du + dv * dv;
414 let d = d2.sqrt();
415 if d < tol {
416 return out; }
418 let (r1, r2) = (self.radius, other.radius);
419 let a = (d2 + r1 * r1 - r2 * r2) / (2.0 * d);
420 let h2 = r1 * r1 - a * a;
421 let r_eff = r1.min(r2);
422 if h2 < -2.0 * r_eff * tol {
423 return out; }
425 let ux = Vec3::new(
426 (self.u_axis.x() * du + self.v_axis.x() * dv) / d,
427 (self.u_axis.y() * du + self.v_axis.y() * dv) / d,
428 (self.u_axis.z() * du + self.v_axis.z() * dv) / d,
429 );
430 let vx = self.normal.cross(ux);
431 let foot = self.center + ux * a;
432 let mut push = |p: Point3| {
433 let v = p - self.center;
434 let mut t = v.dot(self.v_axis).atan2(v.dot(self.u_axis));
435 if t < 0.0 {
436 t += std::f64::consts::TAU;
437 }
438 if !out
439 .iter()
440 .any(|(q, _): &(Point3, f64)| (*q - p).length() < tol)
441 {
442 out.push((p, t));
443 }
444 };
445 if h2 <= 2.0 * r_eff * tol {
446 push(foot);
447 } else {
448 let h = h2.sqrt();
449 push(foot + vx * h);
450 push(foot - vx * h);
451 }
452 out
453 }
454
455 fn intersect_skew_circle(&self, other: &Self, tol: f64) -> Vec<(Point3, f64)> {
459 let mut out: Vec<(Point3, f64)> = Vec::new();
460 let (n1, n2) = (self.normal, other.normal);
461 let Ok(dir) = n1.cross(n2).normalize() else {
462 return out;
463 };
464 let (h1, h2) = (
467 n1.dot(Vec3::new(self.center.x(), self.center.y(), self.center.z())),
468 n2.dot(Vec3::new(
469 other.center.x(),
470 other.center.y(),
471 other.center.z(),
472 )),
473 );
474 let (a, b, c) = (n1.dot(n1), n2.dot(n2), n1.dot(n2));
475 let det = a.mul_add(b, -(c * c));
476 let base = n1 * ((h1 * b - h2 * c) / det) + n2 * ((h2 * a - h1 * c) / det);
477 let base = Point3::new(base.x(), base.y(), base.z());
478 let off = base - self.center;
479 let half_b = dir.dot(off);
480 let disc = half_b.mul_add(half_b, -(off.dot(off) - self.radius * self.radius));
481 let well = 2.0 * self.radius * tol;
482 if disc < -well {
483 return out;
484 }
485 let roots: Vec<f64> = if disc <= well {
486 vec![-half_b]
487 } else {
488 let root = disc.sqrt();
489 vec![-half_b - root, -half_b + root]
490 };
491 for s in roots {
492 let p = base + dir * s;
493 if ((p - other.center).length() - other.radius).abs() > tol {
494 continue;
495 }
496 let v = p - self.center;
497 let mut t = v.dot(self.v_axis).atan2(v.dot(self.u_axis));
498 if t < 0.0 {
499 t += std::f64::consts::TAU;
500 }
501 if !out.iter().any(|(q, _)| (*q - p).length() < tol) {
502 out.push((p, t));
503 }
504 }
505 out
506 }
507}
508
509#[derive(Debug, Clone)]
515#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
516pub struct Ellipse3D {
517 center: Point3,
518 normal: Vec3,
519 semi_major: f64,
520 semi_minor: f64,
521 u_axis: Vec3,
522 v_axis: Vec3,
523}
524
525impl Ellipse3D {
526 pub fn new(
535 center: Point3,
536 normal: Vec3,
537 semi_major: f64,
538 semi_minor: f64,
539 ) -> Result<Self, MathError> {
540 if semi_major <= 0.0 || semi_minor <= 0.0 {
541 return Err(MathError::ParameterOutOfRange {
542 value: semi_major.min(semi_minor),
543 min: 0.0,
544 max: f64::INFINITY,
545 });
546 }
547 if semi_minor > semi_major {
548 return Err(MathError::ParameterOutOfRange {
549 value: semi_minor,
550 min: 0.0,
551 max: semi_major,
552 });
553 }
554 let f = Frame3::from_normal(center, normal)?;
555 Ok(Self {
556 center,
557 normal: f.z,
558 semi_major,
559 semi_minor,
560 u_axis: f.x,
561 v_axis: f.y,
562 })
563 }
564
565 pub fn new_with_ref(
577 center: Point3,
578 normal: Vec3,
579 semi_major: f64,
580 semi_minor: f64,
581 ref_dir: Vec3,
582 ) -> Result<Self, MathError> {
583 if semi_major <= 0.0 || semi_minor <= 0.0 {
584 return Err(MathError::ParameterOutOfRange {
585 value: semi_major.min(semi_minor),
586 min: 0.0,
587 max: f64::INFINITY,
588 });
589 }
590 if semi_minor > semi_major {
591 return Err(MathError::ParameterOutOfRange {
592 value: semi_minor,
593 min: 0.0,
594 max: semi_major,
595 });
596 }
597 let f = Frame3::from_normal_and_ref(center, normal, ref_dir)?;
598 Ok(Self {
599 center,
600 normal: f.z,
601 semi_major,
602 semi_minor,
603 u_axis: f.x,
604 v_axis: f.y,
605 })
606 }
607
608 #[must_use]
610 pub fn evaluate(&self, t: f64) -> Point3 {
611 let cos_t = t.cos();
612 let sin_t = t.sin();
613 self.center
614 + self.u_axis * (self.semi_major * cos_t)
615 + self.v_axis * (self.semi_minor * sin_t)
616 }
617
618 #[must_use]
620 pub fn tangent(&self, t: f64) -> Vec3 {
621 let cos_t = t.cos();
622 let sin_t = t.sin();
623 self.u_axis * (-self.semi_major * sin_t) + self.v_axis * (self.semi_minor * cos_t)
624 }
625
626 #[must_use]
628 pub const fn center(&self) -> Point3 {
629 self.center
630 }
631
632 #[must_use]
634 pub const fn semi_major(&self) -> f64 {
635 self.semi_major
636 }
637
638 #[must_use]
640 pub const fn semi_minor(&self) -> f64 {
641 self.semi_minor
642 }
643
644 #[must_use]
646 pub const fn normal(&self) -> Vec3 {
647 self.normal
648 }
649
650 #[must_use]
652 pub fn approximate_circumference(&self) -> f64 {
653 let a = self.semi_major;
654 let b = self.semi_minor;
655 let h = (a - b) * (a - b) / ((a + b) * (a + b));
656 PI * (a + b) * (1.0 + 3.0 * h / (10.0 + (3.0f64.mul_add(-h, 4.0)).sqrt()))
657 }
658
659 #[must_use]
661 pub fn project(&self, point: Point3) -> f64 {
662 let v = point - self.center;
663 let u_comp = self.u_axis.dot(v) / self.semi_major;
664 let v_comp = self.v_axis.dot(v) / self.semi_minor;
665 v_comp.atan2(u_comp)
666 }
667
668 #[must_use]
670 pub const fn u_axis(&self) -> Vec3 {
671 self.u_axis
672 }
673
674 #[must_use]
676 pub const fn v_axis(&self) -> Vec3 {
677 self.v_axis
678 }
679
680 pub fn with_axes(
686 center: Point3,
687 normal: Vec3,
688 semi_major: f64,
689 semi_minor: f64,
690 u_axis: Vec3,
691 v_axis: Vec3,
692 ) -> Result<Self, MathError> {
693 if semi_major <= 0.0 || semi_minor <= 0.0 {
694 return Err(MathError::ParameterOutOfRange {
695 value: semi_major.min(semi_minor),
696 min: 0.0,
697 max: f64::INFINITY,
698 });
699 }
700 Ok(Self {
701 center,
702 normal,
703 semi_major,
704 semi_minor,
705 u_axis,
706 v_axis,
707 })
708 }
709}
710
711#[derive(Debug, Clone)]
719pub struct Parabola3D {
720 vertex: Point3,
721 axis_dir: Vec3,
722 focal_length: f64,
723 u_axis: Vec3,
724}
725
726impl Parabola3D {
727 pub fn new(vertex: Point3, axis_dir: Vec3, focal_length: f64) -> Result<Self, MathError> {
736 if focal_length <= 0.0 {
737 return Err(MathError::ParameterOutOfRange {
738 value: focal_length,
739 min: f64::EPSILON,
740 max: f64::MAX,
741 });
742 }
743 let f = Frame3::from_normal(vertex, axis_dir)?;
744 Ok(Self {
745 vertex,
746 axis_dir: f.z,
747 focal_length,
748 u_axis: f.x,
749 })
750 }
751
752 #[must_use]
756 pub fn evaluate(&self, t: f64) -> Point3 {
757 let along_axis = (t * t) / (4.0 * self.focal_length);
758 self.vertex + self.axis_dir * along_axis + self.u_axis * t
759 }
760
761 #[must_use]
763 pub fn tangent(&self, t: f64) -> Vec3 {
764 let d_axis = t / (2.0 * self.focal_length);
765 self.axis_dir * d_axis + self.u_axis
766 }
767
768 #[must_use]
770 pub fn curvature(&self, t: f64) -> f64 {
771 let two_f = 2.0 * self.focal_length;
772 let ratio = t / two_f;
773 let denom = ratio.mul_add(ratio, 1.0);
774 1.0 / (two_f * denom.powf(1.5))
775 }
776
777 #[must_use]
779 pub const fn vertex(&self) -> Point3 {
780 self.vertex
781 }
782
783 #[must_use]
785 pub const fn focal_length(&self) -> f64 {
786 self.focal_length
787 }
788
789 #[must_use]
791 pub const fn axis_dir(&self) -> Vec3 {
792 self.axis_dir
793 }
794
795 #[must_use]
799 pub const fn u_axis(&self) -> Vec3 {
800 self.u_axis
801 }
802
803 #[must_use]
805 pub fn focus(&self) -> Point3 {
806 self.vertex + self.axis_dir * self.focal_length
807 }
808}
809
810#[derive(Debug, Clone)]
817pub struct Hyperbola3D {
818 center: Point3,
819 normal: Vec3,
820 semi_major: f64,
821 semi_minor: f64,
822 u_axis: Vec3,
823 v_axis: Vec3,
824}
825
826impl Hyperbola3D {
827 pub fn new(
835 center: Point3,
836 normal: Vec3,
837 semi_major: f64,
838 semi_minor: f64,
839 ) -> Result<Self, MathError> {
840 if semi_major <= 0.0 || semi_minor <= 0.0 {
841 return Err(MathError::ParameterOutOfRange {
842 value: semi_major.min(semi_minor),
843 min: f64::EPSILON,
844 max: f64::MAX,
845 });
846 }
847 let f = Frame3::from_normal(center, normal)?;
848 Ok(Self {
849 center,
850 normal: f.z,
851 semi_major,
852 semi_minor,
853 u_axis: f.x,
854 v_axis: f.y,
855 })
856 }
857
858 #[must_use]
860 pub fn evaluate(&self, t: f64) -> Point3 {
861 self.center
862 + self.u_axis * (self.semi_major * t.cosh())
863 + self.v_axis * (self.semi_minor * t.sinh())
864 }
865
866 #[must_use]
868 pub fn tangent(&self, t: f64) -> Vec3 {
869 self.u_axis * (self.semi_major * t.sinh()) + self.v_axis * (self.semi_minor * t.cosh())
870 }
871
872 #[must_use]
874 pub const fn center(&self) -> Point3 {
875 self.center
876 }
877
878 #[must_use]
880 pub const fn semi_major(&self) -> f64 {
881 self.semi_major
882 }
883
884 #[must_use]
886 pub const fn semi_minor(&self) -> f64 {
887 self.semi_minor
888 }
889
890 #[must_use]
892 pub const fn normal(&self) -> Vec3 {
893 self.normal
894 }
895
896 #[must_use]
901 pub const fn u_axis(&self) -> Vec3 {
902 self.u_axis
903 }
904
905 #[must_use]
907 pub const fn v_axis(&self) -> Vec3 {
908 self.v_axis
909 }
910
911 #[must_use]
913 pub fn eccentricity(&self) -> f64 {
914 let ratio = self.semi_minor / self.semi_major;
915 ratio.mul_add(ratio, 1.0).sqrt()
916 }
917
918 #[must_use]
920 pub fn foci(&self) -> (Point3, Point3) {
921 let c = self.semi_major.hypot(self.semi_minor);
922 (
923 self.center + self.u_axis * c,
924 self.center + self.u_axis * (-c),
925 )
926 }
927}
928
929#[cfg(test)]
930mod tests;