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.min(h1) <= tol && h0.max(h1) >= -tol {
425 let denom = h0 - h1;
432 if denom.abs() < tol {
433 return out;
434 }
435 let s = h0 / denom;
436 let s_slack = tol / seg_len_sq.sqrt();
437 if s < -s_slack || s > 1.0 + s_slack {
438 return out;
439 }
440 let s = s.clamp(0.0, 1.0);
441 let p = Point3::new(
442 seg_start.x() + s * d.x(),
443 seg_start.y() + s * d.y(),
444 seg_start.z() + s * d.z(),
445 );
446 if on_plane(p) {
447 push_if_unique(p);
448 }
449 }
450 out
453 }
454
455 #[must_use]
470 pub fn intersect_circle(&self, other: &Self, tol: f64) -> Vec<(Point3, f64)> {
471 let mut out = Vec::new();
472 if self.normal.cross(other.normal).length() > 1e-9 {
473 return self.intersect_skew_circle(other, tol);
474 }
475 let dvec = other.center - self.center;
476 if dvec.dot(self.normal).abs() > tol {
477 return out; }
479 let du = dvec.dot(self.u_axis);
480 let dv = dvec.dot(self.v_axis);
481 let d2 = du * du + dv * dv;
482 let d = d2.sqrt();
483 if d < tol {
484 return out; }
486 let (r1, r2) = (self.radius, other.radius);
487 let a = (d2 + r1 * r1 - r2 * r2) / (2.0 * d);
488 let h2 = r1 * r1 - a * a;
489 let r_eff = r1.min(r2);
490 if h2 < -2.0 * r_eff * tol {
491 return out; }
493 let ux = Vec3::new(
494 (self.u_axis.x() * du + self.v_axis.x() * dv) / d,
495 (self.u_axis.y() * du + self.v_axis.y() * dv) / d,
496 (self.u_axis.z() * du + self.v_axis.z() * dv) / d,
497 );
498 let vx = self.normal.cross(ux);
499 let foot = self.center + ux * a;
500 let mut push = |p: Point3| {
501 let v = p - self.center;
502 let mut t = v.dot(self.v_axis).atan2(v.dot(self.u_axis));
503 if t < 0.0 {
504 t += std::f64::consts::TAU;
505 }
506 if !out
507 .iter()
508 .any(|(q, _): &(Point3, f64)| (*q - p).length() < tol)
509 {
510 out.push((p, t));
511 }
512 };
513 if h2 <= 2.0 * r_eff * tol {
514 push(foot);
515 } else {
516 let h = h2.sqrt();
517 push(foot + vx * h);
518 push(foot - vx * h);
519 }
520 out
521 }
522
523 fn intersect_skew_circle(&self, other: &Self, tol: f64) -> Vec<(Point3, f64)> {
527 let mut out: Vec<(Point3, f64)> = Vec::new();
528 let (n1, n2) = (self.normal, other.normal);
529 let Ok(dir) = n1.cross(n2).normalize() else {
530 return out;
531 };
532 let (h1, h2) = (
535 n1.dot(Vec3::new(self.center.x(), self.center.y(), self.center.z())),
536 n2.dot(Vec3::new(
537 other.center.x(),
538 other.center.y(),
539 other.center.z(),
540 )),
541 );
542 let (a, b, c) = (n1.dot(n1), n2.dot(n2), n1.dot(n2));
543 let det = a.mul_add(b, -(c * c));
544 let base = n1 * ((h1 * b - h2 * c) / det) + n2 * ((h2 * a - h1 * c) / det);
545 let base = Point3::new(base.x(), base.y(), base.z());
546 let off = base - self.center;
547 let half_b = dir.dot(off);
548 let disc = half_b.mul_add(half_b, -(off.dot(off) - self.radius * self.radius));
549 let well = 2.0 * self.radius * tol;
550 if disc < -well {
551 return out;
552 }
553 let roots: Vec<f64> = if disc <= well {
554 vec![-half_b]
555 } else {
556 let root = disc.sqrt();
557 vec![-half_b - root, -half_b + root]
558 };
559 for s in roots {
560 let p = base + dir * s;
561 if ((p - other.center).length() - other.radius).abs() > tol {
562 continue;
563 }
564 let v = p - self.center;
565 let mut t = v.dot(self.v_axis).atan2(v.dot(self.u_axis));
566 if t < 0.0 {
567 t += std::f64::consts::TAU;
568 }
569 if !out.iter().any(|(q, _)| (*q - p).length() < tol) {
570 out.push((p, t));
571 }
572 }
573 out
574 }
575
576 #[must_use]
582 pub fn intersect_ellipse(&self, ellipse: &Ellipse3D, tol: f64) -> Vec<(Point3, f64)> {
583 let mut out: Vec<(Point3, f64)> = Vec::new();
584 let mut push = |p: Point3| {
585 let v = p - self.center;
586 let mut t = v.dot(self.v_axis).atan2(v.dot(self.u_axis));
587 if t < 0.0 {
588 t += std::f64::consts::TAU;
589 }
590 if !out
591 .iter()
592 .any(|(q, _): &(Point3, f64)| (*q - p).length() < tol)
593 {
594 out.push((p, t));
595 }
596 };
597 let n = self.normal;
599 let a = ellipse.semi_major() * ellipse.u_axis().dot(n);
600 let b = ellipse.semi_minor() * ellipse.v_axis().dot(n);
601 let h = (ellipse.center() - self.center).dot(n);
602 let amp = a.hypot(b);
603 if amp <= tol {
604 if h.abs() <= tol {
605 const STEPS: u32 = 256;
608 let g = |s: f64| {
609 let d = ellipse.evaluate(s) - self.center;
610 self.radius.mul_add(-self.radius, d.dot(d))
611 };
612 let mut prev = (0.0, g(0.0));
613 for k in 1..=STEPS {
614 let s = std::f64::consts::TAU * f64::from(k) / f64::from(STEPS);
615 let cur = (s, g(s));
616 if (prev.1 <= 0.0) != (cur.1 <= 0.0) {
617 let (mut lo, mut hi) = (prev, cur);
618 for _ in 0..60 {
619 let mid = f64::midpoint(lo.0, hi.0);
620 let gm = g(mid);
621 if (gm <= 0.0) == (lo.1 <= 0.0) {
622 lo = (mid, gm);
623 } else {
624 hi = (mid, gm);
625 }
626 }
627 push(ellipse.evaluate(f64::midpoint(lo.0, hi.0)));
628 }
629 prev = cur;
630 }
631 }
632 return out;
633 }
634 if h.abs() > amp + tol {
635 return out;
636 }
637 let (phi, spread) = (b.atan2(a), (-h / amp).clamp(-1.0, 1.0).acos());
638 for s in [phi - spread, phi + spread] {
639 let p = ellipse.evaluate(s);
640 if ((p - self.center).length() - self.radius).abs() <= tol {
641 push(p);
642 }
643 }
644 out
645 }
646}
647
648#[derive(Debug, Clone)]
654#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
655pub struct Ellipse3D {
656 center: Point3,
657 normal: Vec3,
658 semi_major: f64,
659 semi_minor: f64,
660 u_axis: Vec3,
661 v_axis: Vec3,
662}
663
664impl Ellipse3D {
665 pub fn new(
674 center: Point3,
675 normal: Vec3,
676 semi_major: f64,
677 semi_minor: f64,
678 ) -> Result<Self, MathError> {
679 if semi_major <= 0.0 || semi_minor <= 0.0 {
680 return Err(MathError::ParameterOutOfRange {
681 value: semi_major.min(semi_minor),
682 min: 0.0,
683 max: f64::INFINITY,
684 });
685 }
686 if semi_minor > semi_major {
687 return Err(MathError::ParameterOutOfRange {
688 value: semi_minor,
689 min: 0.0,
690 max: semi_major,
691 });
692 }
693 let f = Frame3::from_normal(center, normal)?;
694 Ok(Self {
695 center,
696 normal: f.z,
697 semi_major,
698 semi_minor,
699 u_axis: f.x,
700 v_axis: f.y,
701 })
702 }
703
704 pub fn new_with_ref(
716 center: Point3,
717 normal: Vec3,
718 semi_major: f64,
719 semi_minor: f64,
720 ref_dir: Vec3,
721 ) -> Result<Self, MathError> {
722 if semi_major <= 0.0 || semi_minor <= 0.0 {
723 return Err(MathError::ParameterOutOfRange {
724 value: semi_major.min(semi_minor),
725 min: 0.0,
726 max: f64::INFINITY,
727 });
728 }
729 if semi_minor > semi_major {
730 return Err(MathError::ParameterOutOfRange {
731 value: semi_minor,
732 min: 0.0,
733 max: semi_major,
734 });
735 }
736 let f = Frame3::from_normal_and_ref(center, normal, ref_dir)?;
737 Ok(Self {
738 center,
739 normal: f.z,
740 semi_major,
741 semi_minor,
742 u_axis: f.x,
743 v_axis: f.y,
744 })
745 }
746
747 #[must_use]
749 pub fn evaluate(&self, t: f64) -> Point3 {
750 let cos_t = t.cos();
751 let sin_t = t.sin();
752 self.center
753 + self.u_axis * (self.semi_major * cos_t)
754 + self.v_axis * (self.semi_minor * sin_t)
755 }
756
757 #[must_use]
759 pub fn tangent(&self, t: f64) -> Vec3 {
760 let cos_t = t.cos();
761 let sin_t = t.sin();
762 self.u_axis * (-self.semi_major * sin_t) + self.v_axis * (self.semi_minor * cos_t)
763 }
764
765 #[must_use]
767 pub const fn center(&self) -> Point3 {
768 self.center
769 }
770
771 #[must_use]
773 pub const fn semi_major(&self) -> f64 {
774 self.semi_major
775 }
776
777 #[must_use]
779 pub const fn semi_minor(&self) -> f64 {
780 self.semi_minor
781 }
782
783 #[must_use]
785 pub const fn normal(&self) -> Vec3 {
786 self.normal
787 }
788
789 #[must_use]
791 pub fn approximate_circumference(&self) -> f64 {
792 let a = self.semi_major;
793 let b = self.semi_minor;
794 let h = (a - b) * (a - b) / ((a + b) * (a + b));
795 PI * (a + b) * (1.0 + 3.0 * h / (10.0 + (3.0f64.mul_add(-h, 4.0)).sqrt()))
796 }
797
798 #[must_use]
800 pub fn project(&self, point: Point3) -> f64 {
801 let v = point - self.center;
802 let u_comp = self.u_axis.dot(v) / self.semi_major;
803 let v_comp = self.v_axis.dot(v) / self.semi_minor;
804 v_comp.atan2(u_comp)
805 }
806
807 #[must_use]
809 pub const fn u_axis(&self) -> Vec3 {
810 self.u_axis
811 }
812
813 #[must_use]
815 pub const fn v_axis(&self) -> Vec3 {
816 self.v_axis
817 }
818
819 #[must_use]
822 pub fn aabb(&self) -> Aabb3 {
823 conic_aabb(
824 self.center,
825 (self.semi_major, self.u_axis),
826 (self.semi_minor, self.v_axis),
827 )
828 }
829
830 #[must_use]
832 pub fn arc_aabb(&self, t0: f64, t1: f64) -> Aabb3 {
833 conic_arc_aabb(
834 self.center,
835 (self.semi_major, self.u_axis),
836 (self.semi_minor, self.v_axis),
837 (t0, t1),
838 )
839 }
840
841 pub fn with_axes(
847 center: Point3,
848 normal: Vec3,
849 semi_major: f64,
850 semi_minor: f64,
851 u_axis: Vec3,
852 v_axis: Vec3,
853 ) -> Result<Self, MathError> {
854 if semi_major <= 0.0 || semi_minor <= 0.0 {
855 return Err(MathError::ParameterOutOfRange {
856 value: semi_major.min(semi_minor),
857 min: 0.0,
858 max: f64::INFINITY,
859 });
860 }
861 Ok(Self {
862 center,
863 normal,
864 semi_major,
865 semi_minor,
866 u_axis,
867 v_axis,
868 })
869 }
870}
871
872#[derive(Debug, Clone)]
880pub struct Parabola3D {
881 vertex: Point3,
882 axis_dir: Vec3,
883 focal_length: f64,
884 u_axis: Vec3,
885}
886
887impl Parabola3D {
888 pub fn new(vertex: Point3, axis_dir: Vec3, focal_length: f64) -> Result<Self, MathError> {
897 if focal_length <= 0.0 {
898 return Err(MathError::ParameterOutOfRange {
899 value: focal_length,
900 min: f64::EPSILON,
901 max: f64::MAX,
902 });
903 }
904 let f = Frame3::from_normal(vertex, axis_dir)?;
905 Ok(Self {
906 vertex,
907 axis_dir: f.z,
908 focal_length,
909 u_axis: f.x,
910 })
911 }
912
913 #[must_use]
917 pub fn evaluate(&self, t: f64) -> Point3 {
918 let along_axis = (t * t) / (4.0 * self.focal_length);
919 self.vertex + self.axis_dir * along_axis + self.u_axis * t
920 }
921
922 #[must_use]
924 pub fn tangent(&self, t: f64) -> Vec3 {
925 let d_axis = t / (2.0 * self.focal_length);
926 self.axis_dir * d_axis + self.u_axis
927 }
928
929 #[must_use]
931 pub fn curvature(&self, t: f64) -> f64 {
932 let two_f = 2.0 * self.focal_length;
933 let ratio = t / two_f;
934 let denom = ratio.mul_add(ratio, 1.0);
935 1.0 / (two_f * denom.powf(1.5))
936 }
937
938 #[must_use]
940 pub const fn vertex(&self) -> Point3 {
941 self.vertex
942 }
943
944 #[must_use]
946 pub const fn focal_length(&self) -> f64 {
947 self.focal_length
948 }
949
950 #[must_use]
952 pub const fn axis_dir(&self) -> Vec3 {
953 self.axis_dir
954 }
955
956 #[must_use]
960 pub const fn u_axis(&self) -> Vec3 {
961 self.u_axis
962 }
963
964 #[must_use]
966 pub fn focus(&self) -> Point3 {
967 self.vertex + self.axis_dir * self.focal_length
968 }
969}
970
971#[derive(Debug, Clone)]
978pub struct Hyperbola3D {
979 center: Point3,
980 normal: Vec3,
981 semi_major: f64,
982 semi_minor: f64,
983 u_axis: Vec3,
984 v_axis: Vec3,
985}
986
987impl Hyperbola3D {
988 pub fn new(
996 center: Point3,
997 normal: Vec3,
998 semi_major: f64,
999 semi_minor: f64,
1000 ) -> Result<Self, MathError> {
1001 if semi_major <= 0.0 || semi_minor <= 0.0 {
1002 return Err(MathError::ParameterOutOfRange {
1003 value: semi_major.min(semi_minor),
1004 min: f64::EPSILON,
1005 max: f64::MAX,
1006 });
1007 }
1008 let f = Frame3::from_normal(center, normal)?;
1009 Ok(Self {
1010 center,
1011 normal: f.z,
1012 semi_major,
1013 semi_minor,
1014 u_axis: f.x,
1015 v_axis: f.y,
1016 })
1017 }
1018
1019 #[must_use]
1021 pub fn evaluate(&self, t: f64) -> Point3 {
1022 self.center
1023 + self.u_axis * (self.semi_major * t.cosh())
1024 + self.v_axis * (self.semi_minor * t.sinh())
1025 }
1026
1027 #[must_use]
1029 pub fn tangent(&self, t: f64) -> Vec3 {
1030 self.u_axis * (self.semi_major * t.sinh()) + self.v_axis * (self.semi_minor * t.cosh())
1031 }
1032
1033 #[must_use]
1035 pub const fn center(&self) -> Point3 {
1036 self.center
1037 }
1038
1039 #[must_use]
1041 pub const fn semi_major(&self) -> f64 {
1042 self.semi_major
1043 }
1044
1045 #[must_use]
1047 pub const fn semi_minor(&self) -> f64 {
1048 self.semi_minor
1049 }
1050
1051 #[must_use]
1053 pub const fn normal(&self) -> Vec3 {
1054 self.normal
1055 }
1056
1057 #[must_use]
1062 pub const fn u_axis(&self) -> Vec3 {
1063 self.u_axis
1064 }
1065
1066 #[must_use]
1068 pub const fn v_axis(&self) -> Vec3 {
1069 self.v_axis
1070 }
1071
1072 #[must_use]
1074 pub fn eccentricity(&self) -> f64 {
1075 let ratio = self.semi_minor / self.semi_major;
1076 ratio.mul_add(ratio, 1.0).sqrt()
1077 }
1078
1079 #[must_use]
1081 pub fn foci(&self) -> (Point3, Point3) {
1082 let c = self.semi_major.hypot(self.semi_minor);
1083 (
1084 self.center + self.u_axis * c,
1085 self.center + self.u_axis * (-c),
1086 )
1087 }
1088}
1089
1090#[cfg(test)]
1091mod tests;