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 #[must_use]
579 pub fn intersect_ellipse(&self, ellipse: &Ellipse3D, tol: f64) -> Vec<(Point3, f64)> {
580 let mut out: Vec<(Point3, f64)> = Vec::new();
581 let mut push = |p: Point3| {
582 let v = p - self.center;
583 let mut t = v.dot(self.v_axis).atan2(v.dot(self.u_axis));
584 if t < 0.0 {
585 t += std::f64::consts::TAU;
586 }
587 if !out
588 .iter()
589 .any(|(q, _): &(Point3, f64)| (*q - p).length() < tol)
590 {
591 out.push((p, t));
592 }
593 };
594 let n = self.normal;
596 let a = ellipse.semi_major() * ellipse.u_axis().dot(n);
597 let b = ellipse.semi_minor() * ellipse.v_axis().dot(n);
598 let h = (ellipse.center() - self.center).dot(n);
599 let amp = a.hypot(b);
600 if amp <= tol {
601 if h.abs() <= tol {
602 const STEPS: u32 = 256;
605 let g = |s: f64| {
606 let d = ellipse.evaluate(s) - self.center;
607 self.radius.mul_add(-self.radius, d.dot(d))
608 };
609 let mut prev = (0.0, g(0.0));
610 for k in 1..=STEPS {
611 let s = std::f64::consts::TAU * f64::from(k) / f64::from(STEPS);
612 let cur = (s, g(s));
613 if (prev.1 <= 0.0) != (cur.1 <= 0.0) {
614 let (mut lo, mut hi) = (prev, cur);
615 for _ in 0..60 {
616 let mid = f64::midpoint(lo.0, hi.0);
617 let gm = g(mid);
618 if (gm <= 0.0) == (lo.1 <= 0.0) {
619 lo = (mid, gm);
620 } else {
621 hi = (mid, gm);
622 }
623 }
624 push(ellipse.evaluate(f64::midpoint(lo.0, hi.0)));
625 }
626 prev = cur;
627 }
628 }
629 return out;
630 }
631 if h.abs() > amp + tol {
632 return out;
633 }
634 let (phi, spread) = (b.atan2(a), (-h / amp).clamp(-1.0, 1.0).acos());
635 for s in [phi - spread, phi + spread] {
636 let p = ellipse.evaluate(s);
637 if ((p - self.center).length() - self.radius).abs() <= tol {
638 push(p);
639 }
640 }
641 out
642 }
643}
644
645#[derive(Debug, Clone)]
651#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
652pub struct Ellipse3D {
653 center: Point3,
654 normal: Vec3,
655 semi_major: f64,
656 semi_minor: f64,
657 u_axis: Vec3,
658 v_axis: Vec3,
659}
660
661impl Ellipse3D {
662 pub fn new(
671 center: Point3,
672 normal: Vec3,
673 semi_major: f64,
674 semi_minor: f64,
675 ) -> Result<Self, MathError> {
676 if semi_major <= 0.0 || semi_minor <= 0.0 {
677 return Err(MathError::ParameterOutOfRange {
678 value: semi_major.min(semi_minor),
679 min: 0.0,
680 max: f64::INFINITY,
681 });
682 }
683 if semi_minor > semi_major {
684 return Err(MathError::ParameterOutOfRange {
685 value: semi_minor,
686 min: 0.0,
687 max: semi_major,
688 });
689 }
690 let f = Frame3::from_normal(center, normal)?;
691 Ok(Self {
692 center,
693 normal: f.z,
694 semi_major,
695 semi_minor,
696 u_axis: f.x,
697 v_axis: f.y,
698 })
699 }
700
701 pub fn new_with_ref(
713 center: Point3,
714 normal: Vec3,
715 semi_major: f64,
716 semi_minor: f64,
717 ref_dir: Vec3,
718 ) -> Result<Self, MathError> {
719 if semi_major <= 0.0 || semi_minor <= 0.0 {
720 return Err(MathError::ParameterOutOfRange {
721 value: semi_major.min(semi_minor),
722 min: 0.0,
723 max: f64::INFINITY,
724 });
725 }
726 if semi_minor > semi_major {
727 return Err(MathError::ParameterOutOfRange {
728 value: semi_minor,
729 min: 0.0,
730 max: semi_major,
731 });
732 }
733 let f = Frame3::from_normal_and_ref(center, normal, ref_dir)?;
734 Ok(Self {
735 center,
736 normal: f.z,
737 semi_major,
738 semi_minor,
739 u_axis: f.x,
740 v_axis: f.y,
741 })
742 }
743
744 #[must_use]
746 pub fn evaluate(&self, t: f64) -> Point3 {
747 let cos_t = t.cos();
748 let sin_t = t.sin();
749 self.center
750 + self.u_axis * (self.semi_major * cos_t)
751 + self.v_axis * (self.semi_minor * sin_t)
752 }
753
754 #[must_use]
756 pub fn tangent(&self, t: f64) -> Vec3 {
757 let cos_t = t.cos();
758 let sin_t = t.sin();
759 self.u_axis * (-self.semi_major * sin_t) + self.v_axis * (self.semi_minor * cos_t)
760 }
761
762 #[must_use]
764 pub const fn center(&self) -> Point3 {
765 self.center
766 }
767
768 #[must_use]
770 pub const fn semi_major(&self) -> f64 {
771 self.semi_major
772 }
773
774 #[must_use]
776 pub const fn semi_minor(&self) -> f64 {
777 self.semi_minor
778 }
779
780 #[must_use]
782 pub const fn normal(&self) -> Vec3 {
783 self.normal
784 }
785
786 #[must_use]
788 pub fn approximate_circumference(&self) -> f64 {
789 let a = self.semi_major;
790 let b = self.semi_minor;
791 let h = (a - b) * (a - b) / ((a + b) * (a + b));
792 PI * (a + b) * (1.0 + 3.0 * h / (10.0 + (3.0f64.mul_add(-h, 4.0)).sqrt()))
793 }
794
795 #[must_use]
797 pub fn project(&self, point: Point3) -> f64 {
798 let v = point - self.center;
799 let u_comp = self.u_axis.dot(v) / self.semi_major;
800 let v_comp = self.v_axis.dot(v) / self.semi_minor;
801 v_comp.atan2(u_comp)
802 }
803
804 #[must_use]
806 pub const fn u_axis(&self) -> Vec3 {
807 self.u_axis
808 }
809
810 #[must_use]
812 pub const fn v_axis(&self) -> Vec3 {
813 self.v_axis
814 }
815
816 #[must_use]
819 pub fn aabb(&self) -> Aabb3 {
820 conic_aabb(
821 self.center,
822 (self.semi_major, self.u_axis),
823 (self.semi_minor, self.v_axis),
824 )
825 }
826
827 #[must_use]
829 pub fn arc_aabb(&self, t0: f64, t1: f64) -> Aabb3 {
830 conic_arc_aabb(
831 self.center,
832 (self.semi_major, self.u_axis),
833 (self.semi_minor, self.v_axis),
834 (t0, t1),
835 )
836 }
837
838 pub fn with_axes(
844 center: Point3,
845 normal: Vec3,
846 semi_major: f64,
847 semi_minor: f64,
848 u_axis: Vec3,
849 v_axis: Vec3,
850 ) -> Result<Self, MathError> {
851 if semi_major <= 0.0 || semi_minor <= 0.0 {
852 return Err(MathError::ParameterOutOfRange {
853 value: semi_major.min(semi_minor),
854 min: 0.0,
855 max: f64::INFINITY,
856 });
857 }
858 Ok(Self {
859 center,
860 normal,
861 semi_major,
862 semi_minor,
863 u_axis,
864 v_axis,
865 })
866 }
867}
868
869#[derive(Debug, Clone)]
877pub struct Parabola3D {
878 vertex: Point3,
879 axis_dir: Vec3,
880 focal_length: f64,
881 u_axis: Vec3,
882}
883
884impl Parabola3D {
885 pub fn new(vertex: Point3, axis_dir: Vec3, focal_length: f64) -> Result<Self, MathError> {
894 if focal_length <= 0.0 {
895 return Err(MathError::ParameterOutOfRange {
896 value: focal_length,
897 min: f64::EPSILON,
898 max: f64::MAX,
899 });
900 }
901 let f = Frame3::from_normal(vertex, axis_dir)?;
902 Ok(Self {
903 vertex,
904 axis_dir: f.z,
905 focal_length,
906 u_axis: f.x,
907 })
908 }
909
910 #[must_use]
914 pub fn evaluate(&self, t: f64) -> Point3 {
915 let along_axis = (t * t) / (4.0 * self.focal_length);
916 self.vertex + self.axis_dir * along_axis + self.u_axis * t
917 }
918
919 #[must_use]
921 pub fn tangent(&self, t: f64) -> Vec3 {
922 let d_axis = t / (2.0 * self.focal_length);
923 self.axis_dir * d_axis + self.u_axis
924 }
925
926 #[must_use]
928 pub fn curvature(&self, t: f64) -> f64 {
929 let two_f = 2.0 * self.focal_length;
930 let ratio = t / two_f;
931 let denom = ratio.mul_add(ratio, 1.0);
932 1.0 / (two_f * denom.powf(1.5))
933 }
934
935 #[must_use]
937 pub const fn vertex(&self) -> Point3 {
938 self.vertex
939 }
940
941 #[must_use]
943 pub const fn focal_length(&self) -> f64 {
944 self.focal_length
945 }
946
947 #[must_use]
949 pub const fn axis_dir(&self) -> Vec3 {
950 self.axis_dir
951 }
952
953 #[must_use]
957 pub const fn u_axis(&self) -> Vec3 {
958 self.u_axis
959 }
960
961 #[must_use]
963 pub fn focus(&self) -> Point3 {
964 self.vertex + self.axis_dir * self.focal_length
965 }
966}
967
968#[derive(Debug, Clone)]
975pub struct Hyperbola3D {
976 center: Point3,
977 normal: Vec3,
978 semi_major: f64,
979 semi_minor: f64,
980 u_axis: Vec3,
981 v_axis: Vec3,
982}
983
984impl Hyperbola3D {
985 pub fn new(
993 center: Point3,
994 normal: Vec3,
995 semi_major: f64,
996 semi_minor: f64,
997 ) -> Result<Self, MathError> {
998 if semi_major <= 0.0 || semi_minor <= 0.0 {
999 return Err(MathError::ParameterOutOfRange {
1000 value: semi_major.min(semi_minor),
1001 min: f64::EPSILON,
1002 max: f64::MAX,
1003 });
1004 }
1005 let f = Frame3::from_normal(center, normal)?;
1006 Ok(Self {
1007 center,
1008 normal: f.z,
1009 semi_major,
1010 semi_minor,
1011 u_axis: f.x,
1012 v_axis: f.y,
1013 })
1014 }
1015
1016 #[must_use]
1018 pub fn evaluate(&self, t: f64) -> Point3 {
1019 self.center
1020 + self.u_axis * (self.semi_major * t.cosh())
1021 + self.v_axis * (self.semi_minor * t.sinh())
1022 }
1023
1024 #[must_use]
1026 pub fn tangent(&self, t: f64) -> Vec3 {
1027 self.u_axis * (self.semi_major * t.sinh()) + self.v_axis * (self.semi_minor * t.cosh())
1028 }
1029
1030 #[must_use]
1032 pub const fn center(&self) -> Point3 {
1033 self.center
1034 }
1035
1036 #[must_use]
1038 pub const fn semi_major(&self) -> f64 {
1039 self.semi_major
1040 }
1041
1042 #[must_use]
1044 pub const fn semi_minor(&self) -> f64 {
1045 self.semi_minor
1046 }
1047
1048 #[must_use]
1050 pub const fn normal(&self) -> Vec3 {
1051 self.normal
1052 }
1053
1054 #[must_use]
1059 pub const fn u_axis(&self) -> Vec3 {
1060 self.u_axis
1061 }
1062
1063 #[must_use]
1065 pub const fn v_axis(&self) -> Vec3 {
1066 self.v_axis
1067 }
1068
1069 #[must_use]
1071 pub fn eccentricity(&self) -> f64 {
1072 let ratio = self.semi_minor / self.semi_major;
1073 ratio.mul_add(ratio, 1.0).sqrt()
1074 }
1075
1076 #[must_use]
1078 pub fn foci(&self) -> (Point3, Point3) {
1079 let c = self.semi_major.hypot(self.semi_minor);
1080 (
1081 self.center + self.u_axis * c,
1082 self.center + self.u_axis * (-c),
1083 )
1084 }
1085}
1086
1087#[cfg(test)]
1088mod tests;