Skip to main content

euv_engine/math/
impl.rs

1use super::*;
2
3/// Implements static math utility methods on the `Numeric` namespace struct.
4impl Numeric {
5    /// Clamps a value between a minimum and maximum bound.
6    ///
7    /// # Arguments
8    ///
9    /// - `f64` - The value to clamp.
10    /// - `f64` - The minimum allowed value.
11    /// - `f64` - The maximum allowed value.
12    ///
13    /// # Returns
14    ///
15    /// - `f64` - The clamped value.
16    pub fn clamp(value: f64, min: f64, max: f64) -> f64 {
17        value.max(min).min(max)
18    }
19
20    /// Performs linear interpolation between two values.
21    ///
22    /// # Arguments
23    ///
24    /// - `f64` - The start value.
25    /// - `f64` - The end value.
26    /// - `f64` - The interpolation factor, typically in the range 0.0 to 1.0.
27    ///
28    /// # Returns
29    ///
30    /// - `f64` - The interpolated value.
31    pub fn lerp(start: f64, end: f64, factor: f64) -> f64 {
32        start + (end - start) * factor
33    }
34
35    /// Converts an angle from degrees to radians.
36    ///
37    /// # Arguments
38    ///
39    /// - `f64` - The angle in degrees.
40    ///
41    /// # Returns
42    ///
43    /// - `f64` - The angle in radians.
44    pub fn deg_to_rad(degrees: f64) -> f64 {
45        degrees * DEG_TO_RAD
46    }
47
48    /// Converts an angle from radians to degrees.
49    ///
50    /// # Arguments
51    ///
52    /// - `f64` - The angle in radians.
53    ///
54    /// # Returns
55    ///
56    /// - `f64` - The angle in degrees.
57    pub fn rad_to_deg(radians: f64) -> f64 {
58        radians * RAD_TO_DEG
59    }
60
61    /// Normalizes an angle to the range -PI to PI.
62    ///
63    /// # Arguments
64    ///
65    /// - `f64` - The angle in radians.
66    ///
67    /// # Returns
68    ///
69    /// - `f64` - The normalized angle in the range -PI to PI.
70    pub fn normalize_angle(radians: f64) -> f64 {
71        let mut angle: f64 = radians % TWO_PI;
72        if angle < -PI {
73            angle += TWO_PI;
74        }
75        if angle > PI {
76            angle -= TWO_PI;
77        }
78        angle
79    }
80
81    /// Computes the shortest angular difference between two angles.
82    ///
83    /// # Arguments
84    ///
85    /// - `f64` - The source angle in radians.
86    /// - `f64` - The target angle in radians.
87    ///
88    /// # Returns
89    ///
90    /// - `f64` - The signed angular delta in the range -PI to PI.
91    pub fn angle_delta(from: f64, to: f64) -> f64 {
92        Self::normalize_angle(to - from)
93    }
94
95    /// Performs angular interpolation taking the shortest path around the circle.
96    ///
97    /// # Arguments
98    ///
99    /// - `f64` - The source angle in radians.
100    /// - `f64` - The target angle in radians.
101    /// - `f64` - The interpolation factor, typically in the range 0.0 to 1.0.
102    ///
103    /// # Returns
104    ///
105    /// - `f64` - The interpolated angle in radians.
106    pub fn lerp_angle(from: f64, to: f64, factor: f64) -> f64 {
107        from + Self::angle_delta(from, to) * factor
108    }
109
110    /// Computes the Euclidean distance between two 2D points.
111    ///
112    /// # Arguments
113    ///
114    /// - `Vector2D` - The first point.
115    /// - `Vector2D` - The second point.
116    ///
117    /// # Returns
118    ///
119    /// - `f64` - The distance between the two points.
120    pub fn distance(a: Vector2D, b: Vector2D) -> f64 {
121        (b - a).magnitude()
122    }
123
124    /// Computes the squared Euclidean distance between two 2D points.
125    ///
126    /// Avoids a square root, making it faster for comparison-only use cases.
127    ///
128    /// # Arguments
129    ///
130    /// - `Vector2D` - The first point.
131    /// - `Vector2D` - The second point.
132    ///
133    /// # Returns
134    ///
135    /// - `f64` - The squared distance between the two points.
136    pub fn distance_squared(a: Vector2D, b: Vector2D) -> f64 {
137        (b - a).magnitude_squared()
138    }
139
140    /// Computes a smoothstep interpolation factor using a cubic Hermite polynomial.
141    ///
142    /// # Arguments
143    ///
144    /// - `f64` - The edge minimum.
145    /// - `f64` - The edge maximum.
146    /// - `f64` - The input value.
147    ///
148    /// # Returns
149    ///
150    /// - `f64` - The smoothstep result in the range 0.0 to 1.0.
151    pub fn smoothstep(edge_min: f64, edge_max: f64, value: f64) -> f64 {
152        let clamped: f64 = Self::clamp((value - edge_min) / (edge_max - edge_min), 0.0, 1.0);
153        clamped * clamped * (3.0 - 2.0 * clamped)
154    }
155
156    /// Moves `current` towards `target` by at most `max_delta`.
157    ///
158    /// # Arguments
159    ///
160    /// - `f64` - The current value.
161    /// - `f64` - The target value.
162    /// - `f64` - The maximum allowed change.
163    ///
164    /// # Returns
165    ///
166    /// - `f64` - The new value moved towards target.
167    pub fn approach(current: f64, target: f64, max_delta: f64) -> f64 {
168        if (target - current).abs() <= max_delta {
169            return target;
170        }
171        current + max_delta.signum() * max_delta
172    }
173
174    /// Returns the sign of a value as -1.0, 0.0, or 1.0.
175    ///
176    /// # Arguments
177    ///
178    /// - `f64` - The input value.
179    ///
180    /// # Returns
181    ///
182    /// - `f64` - -1.0 if negative, 0.0 if zero, 1.0 if positive.
183    pub fn sign(value: f64) -> f64 {
184        if value > 0.0 {
185            1.0
186        } else if value < 0.0 {
187            -1.0
188        } else {
189            0.0
190        }
191    }
192
193    /// Wraps a value into the range 0.0 to `max`.
194    ///
195    /// # Arguments
196    ///
197    /// - `f64` - The value to wrap.
198    /// - `f64` - The upper bound of the range.
199    ///
200    /// # Returns
201    ///
202    /// - `f64` - The wrapped value in the range 0.0 to `max`.
203    pub fn wrap(value: f64, max: f64) -> f64 {
204        let result: f64 = value % max;
205        if result < 0.0 { result + max } else { result }
206    }
207
208    /// Returns 1.0 if the value is positive, -1.0 otherwise.
209    ///
210    /// # Arguments
211    ///
212    /// - `f64` - The input value.
213    ///
214    /// # Returns
215    ///
216    /// - `f64` - 1.0 if the value is non-negative, -1.0 otherwise.
217    pub fn sign_or_positive(value: f64) -> f64 {
218        if value < 0.0 { -1.0 } else { 1.0 }
219    }
220
221    /// Computes the Euclidean distance between two 3D points.
222    ///
223    /// # Arguments
224    ///
225    /// - `Vector3D` - The first point.
226    /// - `Vector3D` - The second point.
227    ///
228    /// # Returns
229    ///
230    /// - `f64` - The distance between the two points.
231    pub fn distance_3d(a: Vector3D, b: Vector3D) -> f64 {
232        (b - a).magnitude()
233    }
234
235    /// Computes the squared Euclidean distance between two 3D points.
236    ///
237    /// Avoids a square root, making it faster for comparison-only use cases.
238    ///
239    /// # Arguments
240    ///
241    /// - `Vector3D` - The first point.
242    /// - `Vector3D` - The second point.
243    ///
244    /// # Returns
245    ///
246    /// - `f64` - The squared distance between the two points.
247    pub fn distance_squared_3d(a: Vector3D, b: Vector3D) -> f64 {
248        (b - a).magnitude_squared()
249    }
250}
251
252/// Implements the `Interpolable` trait for `f64`.
253impl Interpolable for f64 {
254    fn lerp(&self, other: f64, factor: f64) -> f64 {
255        *self + (other - *self) * factor
256    }
257}
258
259/// Implements the [`Vector`] trait for `Vector2D`, forwarding every method to
260/// the inherent implementation on the struct.
261///
262/// `Vector2D` also offers 2D-specific operations that are not part of the
263/// trait surface: `perp`, `cross` (returning `f64`), `from_angle`, `angle`,
264/// `angle_to`, `rotated`, `rotate`, `distance_to`, `distance_squared_to`,
265/// `direction_to`, `scale`, and `normalize`. These remain inherent.
266impl Vector for Vector2D {
267    fn zero() -> Vector2D {
268        Vector2D::zero()
269    }
270
271    fn dot(&self, other: Vector2D) -> f64 {
272        Vector2D::dot(self, other)
273    }
274
275    fn magnitude(&self) -> f64 {
276        Vector2D::magnitude(self)
277    }
278
279    fn magnitude_squared(&self) -> f64 {
280        Vector2D::magnitude_squared(self)
281    }
282
283    fn normalized(&self) -> Vector2D {
284        Vector2D::normalized(self)
285    }
286
287    fn scaled(&self, scalar: f64) -> Vector2D {
288        Vector2D::scaled(self, scalar)
289    }
290
291    fn lerp(&self, other: Vector2D, factor: f64) -> Vector2D {
292        Vector2D::lerp(self, other, factor)
293    }
294}
295
296/// Implements methods and operator overloading for `Vector2D`.
297impl Vector2D {
298    /// Returns the zero vector (0.0, 0.0).
299    ///
300    /// # Returns
301    ///
302    /// - `Vector2D` - The zero vector.
303    pub fn zero() -> Vector2D {
304        Vector2D::new(0.0, 0.0)
305    }
306
307    /// Returns the unit vector pointing right (1.0, 0.0).
308    ///
309    /// # Returns
310    ///
311    /// - `Vector2D` - The right unit vector.
312    pub fn right() -> Vector2D {
313        Vector2D::new(1.0, 0.0)
314    }
315
316    /// Returns the unit vector pointing up (0.0, -1.0).
317    ///
318    /// In screen coordinates where y increases downward.
319    ///
320    /// # Returns
321    ///
322    /// - `Vector2D` - The up unit vector.
323    pub fn up() -> Vector2D {
324        Vector2D::new(0.0, -1.0)
325    }
326
327    /// Creates a unit vector from an angle in radians.
328    ///
329    /// # Arguments
330    ///
331    /// - `f64` - The angle in radians.
332    ///
333    /// # Returns
334    ///
335    /// - `Vector2D` - The unit vector pointing in the given direction.
336    pub fn from_angle(radians: f64) -> Vector2D {
337        Vector2D::new(radians.cos(), radians.sin())
338    }
339
340    /// Returns the magnitude (length) of the vector.
341    ///
342    /// # Returns
343    ///
344    /// - `f64` - The magnitude of the vector.
345    pub fn magnitude(&self) -> f64 {
346        (self.get_x() * self.get_x() + self.get_y() * self.get_y()).sqrt()
347    }
348
349    /// Returns the squared magnitude of the vector.
350    ///
351    /// Avoids a square root, making it faster for comparison-only use cases.
352    ///
353    /// # Returns
354    ///
355    /// - `f64` - The squared magnitude of the vector.
356    pub fn magnitude_squared(&self) -> f64 {
357        self.get_x() * self.get_x() + self.get_y() * self.get_y()
358    }
359
360    /// Returns a normalized (unit length) copy of this vector.
361    ///
362    /// Returns the zero vector if the magnitude is zero.
363    ///
364    /// # Returns
365    ///
366    /// - `Vector2D` - The normalized vector.
367    pub fn normalized(&self) -> Vector2D {
368        let mag: f64 = self.magnitude();
369        if mag < EPSILON {
370            return Vector2D::zero();
371        }
372        Vector2D::new(self.get_x() / mag, self.get_y() / mag)
373    }
374
375    /// Normalizes this vector in place.
376    pub fn normalize(&mut self) {
377        let mag: f64 = self.magnitude();
378        if mag < EPSILON {
379            self.set_x(0.0);
380            self.set_y(0.0);
381            return;
382        }
383        self.set_x(self.get_x() / mag);
384        self.set_y(self.get_y() / mag);
385    }
386
387    /// Computes the dot product with another vector.
388    ///
389    /// # Arguments
390    ///
391    /// - `Vector2D` - The other vector.
392    ///
393    /// # Returns
394    ///
395    /// - `f64` - The dot product.
396    pub fn dot(&self, other: Vector2D) -> f64 {
397        self.get_x() * other.get_x() + self.get_y() * other.get_y()
398    }
399
400    /// Computes the 2D cross product (scalar) with another vector.
401    ///
402    /// # Arguments
403    ///
404    /// - `Vector2D` - The other vector.
405    ///
406    /// # Returns
407    ///
408    /// - `f64` - The cross product scalar.
409    pub fn cross(&self, other: Vector2D) -> f64 {
410        self.get_x() * other.get_y() - self.get_y() * other.get_x()
411    }
412
413    /// Returns the perpendicular vector (rotated 90 degrees counter-clockwise).
414    ///
415    /// # Returns
416    ///
417    /// - `Vector2D` - The perpendicular vector.
418    pub fn perp(&self) -> Vector2D {
419        Vector2D::new(-self.get_y(), self.get_x())
420    }
421
422    /// Returns the angle of this vector in radians.
423    ///
424    /// # Returns
425    ///
426    /// - `f64` - The angle in radians.
427    pub fn angle(&self) -> f64 {
428        self.get_y().atan2(self.get_x())
429    }
430
431    /// Returns the angle from this vector to another.
432    ///
433    /// # Arguments
434    ///
435    /// - `Vector2D` - The target vector.
436    ///
437    /// # Returns
438    ///
439    /// - `f64` - The signed angle in radians.
440    pub fn angle_to(&self, other: Vector2D) -> f64 {
441        (other - *self).angle()
442    }
443
444    /// Returns a rotated copy of this vector.
445    ///
446    /// # Arguments
447    ///
448    /// - `f64` - The rotation angle in radians.
449    ///
450    /// # Returns
451    ///
452    /// - `Vector2D` - The rotated vector.
453    pub fn rotated(&self, radians: f64) -> Vector2D {
454        let cos: f64 = radians.cos();
455        let sin: f64 = radians.sin();
456        Vector2D::new(
457            self.get_x() * cos - self.get_y() * sin,
458            self.get_x() * sin + self.get_y() * cos,
459        )
460    }
461
462    /// Rotates this vector in place.
463    ///
464    /// # Arguments
465    ///
466    /// - `f64` - The rotation angle in radians.
467    pub fn rotate(&mut self, radians: f64) {
468        let cos: f64 = radians.cos();
469        let sin: f64 = radians.sin();
470        let new_x: f64 = self.get_x() * cos - self.get_y() * sin;
471        let new_y: f64 = self.get_x() * sin + self.get_y() * cos;
472        self.set_x(new_x);
473        self.set_y(new_y);
474    }
475
476    /// Returns the distance from this point to another.
477    ///
478    /// # Arguments
479    ///
480    /// - `Vector2D` - The target point.
481    ///
482    /// # Returns
483    ///
484    /// - `f64` - The Euclidean distance.
485    pub fn distance_to(&self, other: Vector2D) -> f64 {
486        (other - *self).magnitude()
487    }
488
489    /// Returns the squared distance from this point to another.
490    ///
491    /// # Arguments
492    ///
493    /// - `Vector2D` - The target point.
494    ///
495    /// # Returns
496    ///
497    /// - `f64` - The squared Euclidean distance.
498    pub fn distance_squared_to(&self, other: Vector2D) -> f64 {
499        (other - *self).magnitude_squared()
500    }
501
502    /// Returns a unit vector pointing from this point to another.
503    ///
504    /// # Arguments
505    ///
506    /// - `Vector2D` - The target point.
507    ///
508    /// # Returns
509    ///
510    /// - `Vector2D` - The direction unit vector.
511    pub fn direction_to(&self, other: Vector2D) -> Vector2D {
512        (other - *self).normalized()
513    }
514
515    /// Returns a linearly interpolated vector between this and another.
516    ///
517    /// # Arguments
518    ///
519    /// - `Vector2D` - The target vector.
520    /// - `f64` - The interpolation factor.
521    ///
522    /// # Returns
523    ///
524    /// - `Vector2D` - The interpolated vector.
525    pub fn lerp(&self, other: Vector2D, factor: f64) -> Vector2D {
526        Vector2D::new(
527            self.get_x() + (other.get_x() - self.get_x()) * factor,
528            self.get_y() + (other.get_y() - self.get_y()) * factor,
529        )
530    }
531
532    /// Scales this vector by a scalar factor.
533    ///
534    /// # Arguments
535    ///
536    /// - `f64` - The scalar factor.
537    pub fn scale(&mut self, scalar: f64) {
538        self.set_x(self.get_x() * scalar);
539        self.set_y(self.get_y() * scalar);
540    }
541
542    /// Returns a scaled copy of this vector.
543    ///
544    /// # Arguments
545    ///
546    /// - `f64` - The scalar factor.
547    ///
548    /// # Returns
549    ///
550    /// - `Vector2D` - The scaled vector.
551    pub fn scaled(&self, scalar: f64) -> Vector2D {
552        Vector2D::new(self.get_x() * scalar, self.get_y() * scalar)
553    }
554}
555
556/// Implements `Interpolable` for `Vector2D`.
557impl Interpolable for Vector2D {
558    fn lerp(&self, other: Vector2D, factor: f64) -> Vector2D {
559        Vector2D::lerp(self, other, factor)
560    }
561}
562
563/// Implements vector addition.
564impl Add for Vector2D {
565    type Output = Vector2D;
566    fn add(self, other: Vector2D) -> Vector2D {
567        Vector2D::new(self.get_x() + other.get_x(), self.get_y() + other.get_y())
568    }
569}
570
571/// Implements vector subtraction.
572impl Sub for Vector2D {
573    type Output = Vector2D;
574    fn sub(self, other: Vector2D) -> Vector2D {
575        Vector2D::new(self.get_x() - other.get_x(), self.get_y() - other.get_y())
576    }
577}
578
579/// Implements scalar multiplication.
580impl Mul<f64> for Vector2D {
581    type Output = Vector2D;
582    fn mul(self, scalar: f64) -> Vector2D {
583        Vector2D::new(self.get_x() * scalar, self.get_y() * scalar)
584    }
585}
586
587/// Implements vector negation.
588impl Neg for Vector2D {
589    type Output = Vector2D;
590    fn neg(self) -> Vector2D {
591        Vector2D::new(-self.get_x(), -self.get_y())
592    }
593}
594
595/// Implements in-place vector addition.
596impl AddAssign for Vector2D {
597    fn add_assign(&mut self, other: Vector2D) {
598        self.set_x(self.get_x() + other.get_x());
599        self.set_y(self.get_y() + other.get_y());
600    }
601}
602
603/// Implements in-place vector subtraction.
604impl SubAssign for Vector2D {
605    fn sub_assign(&mut self, other: Vector2D) {
606        self.set_x(self.get_x() - other.get_x());
607        self.set_y(self.get_y() - other.get_y());
608    }
609}
610
611/// Implements in-place scalar multiplication.
612impl MulAssign<f64> for Vector2D {
613    fn mul_assign(&mut self, scalar: f64) {
614        self.set_x(self.get_x() * scalar);
615        self.set_y(self.get_y() * scalar);
616    }
617}
618
619/// Implements methods for `Rect`.
620impl Rect {
621    /// Creates a rectangle from a center point and dimensions.
622    ///
623    /// # Arguments
624    ///
625    /// - `Vector2D` - The center point.
626    /// - `f64` - The width.
627    /// - `f64` - The height.
628    ///
629    /// # Returns
630    ///
631    /// - `Rect` - The new rectangle.
632    pub fn from_center(center: Vector2D, width: f64, height: f64) -> Rect {
633        Rect::new(
634            center.get_x() - width * 0.5,
635            center.get_y() - height * 0.5,
636            width,
637            height,
638        )
639    }
640
641    /// Returns the center point of the rectangle.
642    ///
643    /// # Returns
644    ///
645    /// - `Vector2D` - The center point.
646    pub fn center(&self) -> Vector2D {
647        Vector2D::new(
648            self.get_x() + self.get_width() * 0.5,
649            self.get_y() + self.get_height() * 0.5,
650        )
651    }
652
653    /// Returns the minimum corner (top-left).
654    ///
655    /// # Returns
656    ///
657    /// - `Vector2D` - The minimum corner.
658    pub fn min(&self) -> Vector2D {
659        Vector2D::new(self.get_x(), self.get_y())
660    }
661
662    /// Returns the maximum corner (bottom-right).
663    ///
664    /// # Returns
665    ///
666    /// - `Vector2D` - The maximum corner.
667    pub fn max(&self) -> Vector2D {
668        Vector2D::new(
669            self.get_x() + self.get_width(),
670            self.get_y() + self.get_height(),
671        )
672    }
673
674    /// Returns the size as a vector.
675    ///
676    /// # Returns
677    ///
678    /// - `Vector2D` - The size vector.
679    pub fn size(&self) -> Vector2D {
680        Vector2D::new(self.get_width(), self.get_height())
681    }
682
683    /// Tests whether a point is inside this rectangle.
684    ///
685    /// # Arguments
686    ///
687    /// - `Vector2D` - The point to test.
688    ///
689    /// # Returns
690    ///
691    /// - `bool` - True if the point is inside.
692    pub fn contains(&self, point: Vector2D) -> bool {
693        point.get_x() >= self.get_x()
694            && point.get_x() <= self.get_x() + self.get_width()
695            && point.get_y() >= self.get_y()
696            && point.get_y() <= self.get_y() + self.get_height()
697    }
698
699    /// Tests whether this rectangle intersects another.
700    ///
701    /// # Arguments
702    ///
703    /// - `Rect` - The other rectangle.
704    ///
705    /// # Returns
706    ///
707    /// - `bool` - True if they intersect.
708    pub fn intersects(&self, other: Rect) -> bool {
709        self.get_x() < other.get_x() + other.get_width()
710            && self.get_x() + self.get_width() > other.get_x()
711            && self.get_y() < other.get_y() + other.get_height()
712            && self.get_y() + self.get_height() > other.get_y()
713    }
714
715    /// Alias for `intersects` — used by the physics module's broad-phase
716    /// collision check. (`Rect::broad_phase(a, b)` was being referenced by
717    /// upstream code; we expose it here so the engine compiles cleanly.)
718    pub fn broad_phase_alias(a: Rect, b: Rect) -> bool {
719        a.intersects(b)
720    }
721
722    /// Returns the intersection of two rectangles, or `None` if they do not overlap.
723    ///
724    /// # Arguments
725    ///
726    /// - `Rect` - The other rectangle.
727    ///
728    /// # Returns
729    ///
730    /// - `Option<Rect>` - The intersection rectangle, or `None`.
731    pub fn intersection(&self, other: Rect) -> Option<Rect> {
732        if !self.intersects(other) {
733            return None;
734        }
735        let max_x: f64 = self.get_x().max(other.get_x());
736        let max_y: f64 = self.get_y().max(other.get_y());
737        let min_right: f64 =
738            (self.get_x() + self.get_width()).min(other.get_x() + other.get_width());
739        let min_bottom: f64 =
740            (self.get_y() + self.get_height()).min(other.get_y() + other.get_height());
741        Some(Rect::new(
742            max_x,
743            max_y,
744            min_right - max_x,
745            min_bottom - max_y,
746        ))
747    }
748}
749
750/// Implements methods for `Circle`.
751impl Circle {
752    /// Tests whether a point is inside this circle.
753    ///
754    /// # Arguments
755    ///
756    /// - `Vector2D` - The point to test.
757    ///
758    /// # Returns
759    ///
760    /// - `bool` - True if the point is inside.
761    pub fn contains(&self, point: Vector2D) -> bool {
762        self.get_center().distance_squared_to(point) <= self.get_radius() * self.get_radius()
763    }
764
765    /// Tests whether this circle intersects another.
766    ///
767    /// # Arguments
768    ///
769    /// - `Circle` - The other circle.
770    ///
771    /// # Returns
772    ///
773    /// - `bool` - True if they intersect.
774    pub fn intersects(&self, other: Circle) -> bool {
775        let distance_sq: f64 = self.get_center().distance_squared_to(other.get_center());
776        let radius_sum: f64 = self.get_radius() + other.get_radius();
777        distance_sq <= radius_sum * radius_sum
778    }
779
780    /// Returns the circumference of the circle.
781    ///
782    /// # Returns
783    ///
784    /// - `f64` - The circumference.
785    pub fn circumference(&self) -> f64 {
786        TWO_PI * self.get_radius()
787    }
788
789    /// Returns the area of the circle.
790    ///
791    /// # Returns
792    ///
793    /// - `f64` - The area.
794    pub fn area(&self) -> f64 {
795        PI * self.get_radius() * self.get_radius()
796    }
797}
798
799/// Implements methods for `Transform2D`.
800impl Transform2D {
801    /// Creates a new transform at the origin with no rotation and unit scale.
802    ///
803    /// # Returns
804    ///
805    /// - `Transform2D` - The identity transform.
806    pub fn identity() -> Transform2D {
807        Transform2D::new(Vector2D::zero(), 0.0, Vector2D::new(1.0, 1.0))
808    }
809
810    /// Translates the position by the given offset.
811    ///
812    /// # Arguments
813    ///
814    /// - `Vector2D` - The translation offset.
815    pub fn translate(&mut self, offset: Vector2D) {
816        self.set_position(self.get_position() + offset);
817    }
818
819    /// Rotates by the given angle in radians.
820    ///
821    /// # Arguments
822    ///
823    /// - `f64` - The rotation delta in radians.
824    pub fn rotate(&mut self, radians: f64) {
825        self.set_rotation(self.get_rotation() + radians);
826    }
827
828    /// Scales by the given factors.
829    ///
830    /// # Arguments
831    ///
832    /// - `Vector2D` - The scale factors.
833    pub fn scale_by(&mut self, factors: Vector2D) {
834        let mut scale: Vector2D = self.get_scale();
835        scale.set_x(scale.get_x() * factors.get_x());
836        scale.set_y(scale.get_y() * factors.get_y());
837        self.set_scale(scale);
838    }
839
840    /// Applies this transform to a local-space point, returning world-space coordinates.
841    ///
842    /// # Arguments
843    ///
844    /// - `Vector2D` - The local-space point.
845    ///
846    /// # Returns
847    ///
848    /// - `Vector2D` - The transformed world-space point.
849    pub fn apply_to_point(&self, point: Vector2D) -> Vector2D {
850        let scaled: Vector2D = Vector2D::new(
851            point.get_x() * self.get_scale().get_x(),
852            point.get_y() * self.get_scale().get_y(),
853        );
854        scaled.rotated(self.get_rotation()) + self.get_position()
855    }
856}
857
858/// Implements `Default` for `Transform2D` as the identity transform.
859impl Default for Transform2D {
860    fn default() -> Transform2D {
861        Transform2D::identity()
862    }
863}
864
865/// Implements methods for `Color`.
866impl Color {
867    /// Creates a color from RGB hex values (0-255), with full opacity.
868    ///
869    /// # Arguments
870    ///
871    /// - `u8` - The red channel (0-255).
872    /// - `u8` - The green channel (0-255).
873    /// - `u8` - The blue channel (0-255).
874    ///
875    /// # Returns
876    ///
877    /// - `Color` - The new color.
878    pub fn from_rgb(red: u8, green: u8, blue: u8) -> Color {
879        Color::new(
880            red as f64 / 255.0,
881            green as f64 / 255.0,
882            blue as f64 / 255.0,
883            1.0,
884        )
885    }
886
887    /// Converts the color to a CSS `rgba()` string.
888    ///
889    /// # Returns
890    ///
891    /// - `String` - The CSS color string.
892    pub fn to_css_rgba(&self) -> String {
893        let mut buffer: String = String::with_capacity(32);
894        self.write_css_rgba(&mut buffer);
895        buffer
896    }
897
898    /// Writes the CSS `rgba()` representation into the provided buffer.
899    ///
900    /// Reuses the caller's allocation so per-frame color conversions in tight
901    /// render loops avoid the `format!` machinery and repeated allocation.
902    ///
903    /// # Arguments
904    ///
905    /// - `&mut String` - The buffer to append the CSS color string to.
906    pub fn write_css_rgba(&self, buffer: &mut String) {
907        use std::fmt::Write as _;
908        let red: i32 = (self.get_red() * 255.0).round() as i32;
909        let green: i32 = (self.get_green() * 255.0).round() as i32;
910        let blue: i32 = (self.get_blue() * 255.0).round() as i32;
911        let alpha: f64 = self.get_alpha();
912        let _: std::fmt::Result = write!(buffer, "rgba({red}, {green}, {blue}, {alpha})");
913    }
914
915    /// Returns black (0, 0, 0, 1).
916    ///
917    /// # Returns
918    ///
919    /// - `Color` - The black color.
920    pub fn black() -> Color {
921        Color::new(0.0, 0.0, 0.0, 1.0)
922    }
923
924    /// Returns white (1, 1, 1, 1).
925    ///
926    /// # Returns
927    ///
928    /// - `Color` - The white color.
929    pub fn white() -> Color {
930        Color::new(1.0, 1.0, 1.0, 1.0)
931    }
932
933    /// Returns transparent (0, 0, 0, 0).
934    ///
935    /// # Returns
936    ///
937    /// - `Color` - The transparent color.
938    pub fn transparent() -> Color {
939        Color::new(0.0, 0.0, 0.0, 0.0)
940    }
941
942    /// Performs linear interpolation between this color and `other` by `t`,
943    /// interpolating each channel (red, green, blue, alpha) independently.
944    ///
945    /// # Arguments
946    ///
947    /// - `Color` - The target color.
948    /// - `f64` - The interpolation factor, typically in the range 0.0 to 1.0.
949    ///
950    /// # Returns
951    ///
952    /// - `Color` - The interpolated color.
953    pub fn lerp(&self, other: Color, factor: f64) -> Color {
954        Color::new(
955            self.get_red().lerp(other.get_red(), factor),
956            self.get_green().lerp(other.get_green(), factor),
957            self.get_blue().lerp(other.get_blue(), factor),
958            self.get_alpha().lerp(other.get_alpha(), factor),
959        )
960    }
961}
962
963/// Implements `Interpolable` for `Color`.
964impl Interpolable for Color {
965    fn lerp(&self, other: Color, factor: f64) -> Color {
966        Color::lerp(self, other, factor)
967    }
968}
969
970/// Implements `Default` for `Color` as opaque black.
971impl Default for Color {
972    fn default() -> Color {
973        Color::black()
974    }
975}
976
977/// Implements the [`Vector`] trait for `Vector3D`, forwarding every method to
978/// the inherent implementation on the struct.
979///
980/// `Vector3D` also offers 3D-specific operations that are not part of the
981/// trait surface: `cross` (returning `Vector3D`), `direction_to`,
982/// `distance_to`, `scale`, and `normalize`. These remain inherent.
983impl Vector for Vector3D {
984    fn zero() -> Vector3D {
985        Vector3D::zero()
986    }
987
988    fn dot(&self, other: Vector3D) -> f64 {
989        Vector3D::dot(self, other)
990    }
991
992    fn magnitude(&self) -> f64 {
993        Vector3D::magnitude(self)
994    }
995
996    fn magnitude_squared(&self) -> f64 {
997        Vector3D::magnitude_squared(self)
998    }
999
1000    fn normalized(&self) -> Vector3D {
1001        Vector3D::normalized(self)
1002    }
1003
1004    fn scaled(&self, scalar: f64) -> Vector3D {
1005        Vector3D::scaled(self, scalar)
1006    }
1007
1008    fn lerp(&self, other: Vector3D, factor: f64) -> Vector3D {
1009        Vector3D::lerp(self, other, factor)
1010    }
1011}
1012
1013/// Implements methods and operator overloading for `Vector3D`.
1014impl Vector3D {
1015    /// Returns the zero vector (0.0, 0.0, 0.0).
1016    ///
1017    /// # Returns
1018    ///
1019    /// - `Vector3D` - The zero vector.
1020    pub fn zero() -> Vector3D {
1021        Vector3D::new(0.0, 0.0, 0.0)
1022    }
1023
1024    /// Returns the unit vector pointing right (1.0, 0.0, 0.0).
1025    ///
1026    /// # Returns
1027    ///
1028    /// - `Vector3D` - The right unit vector.
1029    pub fn right() -> Vector3D {
1030        Vector3D::new(1.0, 0.0, 0.0)
1031    }
1032
1033    /// Returns the unit vector pointing up (0.0, 1.0, 0.0).
1034    ///
1035    /// # Returns
1036    ///
1037    /// - `Vector3D` - The up unit vector.
1038    pub fn up() -> Vector3D {
1039        Vector3D::new(0.0, 1.0, 0.0)
1040    }
1041
1042    /// Returns the unit vector pointing forward (0.0, 0.0, -1.0).
1043    ///
1044    /// In a right-handed coordinate system where -z is forward.
1045    ///
1046    /// # Returns
1047    ///
1048    /// - `Vector3D` - The forward unit vector.
1049    pub fn forward() -> Vector3D {
1050        Vector3D::new(0.0, 0.0, -1.0)
1051    }
1052
1053    /// Returns the magnitude (length) of the vector.
1054    ///
1055    /// # Returns
1056    ///
1057    /// - `f64` - The magnitude of the vector.
1058    pub fn magnitude(&self) -> f64 {
1059        (self.get_x() * self.get_x() + self.get_y() * self.get_y() + self.get_z() * self.get_z())
1060            .sqrt()
1061    }
1062
1063    /// Returns the squared magnitude of the vector.
1064    ///
1065    /// Avoids a square root, making it faster for comparison-only use cases.
1066    ///
1067    /// # Returns
1068    ///
1069    /// - `f64` - The squared magnitude of the vector.
1070    pub fn magnitude_squared(&self) -> f64 {
1071        self.get_x() * self.get_x() + self.get_y() * self.get_y() + self.get_z() * self.get_z()
1072    }
1073
1074    /// Returns a normalized (unit length) copy of this vector.
1075    ///
1076    /// Returns the zero vector if the magnitude is zero.
1077    ///
1078    /// # Returns
1079    ///
1080    /// - `Vector3D` - The normalized vector.
1081    pub fn normalized(&self) -> Vector3D {
1082        let mag: f64 = self.magnitude();
1083        if mag < EPSILON {
1084            return Vector3D::zero();
1085        }
1086        Vector3D::new(self.get_x() / mag, self.get_y() / mag, self.get_z() / mag)
1087    }
1088
1089    /// Normalizes this vector in place.
1090    pub fn normalize(&mut self) {
1091        let mag: f64 = self.magnitude();
1092        if mag < EPSILON {
1093            self.set_x(0.0);
1094            self.set_y(0.0);
1095            self.set_z(0.0);
1096            return;
1097        }
1098        self.set_x(self.get_x() / mag);
1099        self.set_y(self.get_y() / mag);
1100        self.set_z(self.get_z() / mag);
1101    }
1102
1103    /// Computes the dot product with another vector.
1104    ///
1105    /// # Arguments
1106    ///
1107    /// - `Vector3D` - The other vector.
1108    ///
1109    /// # Returns
1110    ///
1111    /// - `f64` - The dot product.
1112    pub fn dot(&self, other: Vector3D) -> f64 {
1113        self.get_x() * other.get_x() + self.get_y() * other.get_y() + self.get_z() * other.get_z()
1114    }
1115
1116    /// Computes the 3D cross product with another vector.
1117    ///
1118    /// # Arguments
1119    ///
1120    /// - `Vector3D` - The other vector.
1121    ///
1122    /// # Returns
1123    ///
1124    /// - `Vector3D` - The cross product vector.
1125    pub fn cross(&self, other: Vector3D) -> Vector3D {
1126        Vector3D::new(
1127            self.get_y() * other.get_z() - self.get_z() * other.get_y(),
1128            self.get_z() * other.get_x() - self.get_x() * other.get_z(),
1129            self.get_x() * other.get_y() - self.get_y() * other.get_x(),
1130        )
1131    }
1132
1133    /// Returns the distance from this point to another.
1134    ///
1135    /// # Arguments
1136    ///
1137    /// - `Vector3D` - The target point.
1138    ///
1139    /// # Returns
1140    ///
1141    /// - `f64` - The Euclidean distance.
1142    pub fn distance_to(&self, other: Vector3D) -> f64 {
1143        (other - *self).magnitude()
1144    }
1145
1146    /// Returns the squared distance from this point to another.
1147    ///
1148    /// # Arguments
1149    ///
1150    /// - `Vector3D` - The target point.
1151    ///
1152    /// # Returns
1153    ///
1154    /// - `f64` - The squared Euclidean distance.
1155    pub fn distance_squared_to(&self, other: Vector3D) -> f64 {
1156        (other - *self).magnitude_squared()
1157    }
1158
1159    /// Returns a unit vector pointing from this point to another.
1160    ///
1161    /// # Arguments
1162    ///
1163    /// - `Vector3D` - The target point.
1164    ///
1165    /// # Returns
1166    ///
1167    /// - `Vector3D` - The direction unit vector.
1168    pub fn direction_to(&self, other: Vector3D) -> Vector3D {
1169        (other - *self).normalized()
1170    }
1171
1172    /// Returns a linearly interpolated vector between this and another.
1173    ///
1174    /// # Arguments
1175    ///
1176    /// - `Vector3D` - The target vector.
1177    /// - `f64` - The interpolation factor.
1178    ///
1179    /// # Returns
1180    ///
1181    /// - `Vector3D` - The interpolated vector.
1182    pub fn lerp(&self, other: Vector3D, factor: f64) -> Vector3D {
1183        Vector3D::new(
1184            self.get_x() + (other.get_x() - self.get_x()) * factor,
1185            self.get_y() + (other.get_y() - self.get_y()) * factor,
1186            self.get_z() + (other.get_z() - self.get_z()) * factor,
1187        )
1188    }
1189
1190    /// Scales this vector by a scalar factor.
1191    ///
1192    /// # Arguments
1193    ///
1194    /// - `f64` - The scalar factor.
1195    pub fn scale(&mut self, scalar: f64) {
1196        self.set_x(self.get_x() * scalar);
1197        self.set_y(self.get_y() * scalar);
1198        self.set_z(self.get_z() * scalar);
1199    }
1200
1201    /// Returns a scaled copy of this vector.
1202    ///
1203    /// # Arguments
1204    ///
1205    /// - `f64` - The scalar factor.
1206    ///
1207    /// # Returns
1208    ///
1209    /// - `Vector3D` - The scaled vector.
1210    pub fn scaled(&self, scalar: f64) -> Vector3D {
1211        Vector3D::new(
1212            self.get_x() * scalar,
1213            self.get_y() * scalar,
1214            self.get_z() * scalar,
1215        )
1216    }
1217
1218    /// Rotates this vector by a quaternion.
1219    ///
1220    /// # Arguments
1221    ///
1222    /// - `Quaternion` - The rotation quaternion.
1223    ///
1224    /// # Returns
1225    ///
1226    /// - `Vector3D` - The rotated vector.
1227    pub fn rotated_by(&self, quaternion: Quaternion) -> Vector3D {
1228        let pure: Quaternion = Quaternion::new(self.get_x(), self.get_y(), self.get_z(), 0.0);
1229        let result: Quaternion = quaternion * pure * quaternion.conjugate();
1230        Vector3D::new(result.get_x(), result.get_y(), result.get_z())
1231    }
1232}
1233
1234/// Implements `Interpolable` for `Vector3D`.
1235impl Interpolable for Vector3D {
1236    fn lerp(&self, other: Vector3D, factor: f64) -> Vector3D {
1237        Vector3D::lerp(self, other, factor)
1238    }
1239}
1240
1241/// Implements vector addition.
1242impl Add for Vector3D {
1243    type Output = Vector3D;
1244    fn add(self, other: Vector3D) -> Vector3D {
1245        Vector3D::new(
1246            self.get_x() + other.get_x(),
1247            self.get_y() + other.get_y(),
1248            self.get_z() + other.get_z(),
1249        )
1250    }
1251}
1252
1253/// Implements vector subtraction.
1254impl Sub for Vector3D {
1255    type Output = Vector3D;
1256    fn sub(self, other: Vector3D) -> Vector3D {
1257        Vector3D::new(
1258            self.get_x() - other.get_x(),
1259            self.get_y() - other.get_y(),
1260            self.get_z() - other.get_z(),
1261        )
1262    }
1263}
1264
1265/// Implements scalar multiplication.
1266impl Mul<f64> for Vector3D {
1267    type Output = Vector3D;
1268    fn mul(self, scalar: f64) -> Vector3D {
1269        Vector3D::new(
1270            self.get_x() * scalar,
1271            self.get_y() * scalar,
1272            self.get_z() * scalar,
1273        )
1274    }
1275}
1276
1277/// Implements vector negation.
1278impl Neg for Vector3D {
1279    type Output = Vector3D;
1280    fn neg(self) -> Vector3D {
1281        Vector3D::new(-self.get_x(), -self.get_y(), -self.get_z())
1282    }
1283}
1284
1285/// Implements in-place vector addition.
1286impl AddAssign for Vector3D {
1287    fn add_assign(&mut self, other: Vector3D) {
1288        self.set_x(self.get_x() + other.get_x());
1289        self.set_y(self.get_y() + other.get_y());
1290        self.set_z(self.get_z() + other.get_z());
1291    }
1292}
1293
1294/// Implements in-place vector subtraction.
1295impl SubAssign for Vector3D {
1296    fn sub_assign(&mut self, other: Vector3D) {
1297        self.set_x(self.get_x() - other.get_x());
1298        self.set_y(self.get_y() - other.get_y());
1299        self.set_z(self.get_z() - other.get_z());
1300    }
1301}
1302
1303/// Implements in-place scalar multiplication.
1304impl MulAssign<f64> for Vector3D {
1305    fn mul_assign(&mut self, scalar: f64) {
1306        self.set_x(self.get_x() * scalar);
1307        self.set_y(self.get_y() * scalar);
1308        self.set_z(self.get_z() * scalar);
1309    }
1310}
1311
1312/// Implements quaternion operations for `Quaternion`.
1313impl Quaternion {
1314    /// Returns the identity quaternion (0, 0, 0, 1) representing no rotation.
1315    ///
1316    /// # Returns
1317    ///
1318    /// - `Quaternion` - The identity quaternion.
1319    pub fn identity() -> Quaternion {
1320        Quaternion::new(0.0, 0.0, 0.0, 1.0)
1321    }
1322
1323    /// Creates a quaternion from a rotation around an axis.
1324    ///
1325    /// # Arguments
1326    ///
1327    /// - `Vector3D` - The rotation axis (should be normalized).
1328    /// - `f64` - The rotation angle in radians.
1329    ///
1330    /// # Returns
1331    ///
1332    /// - `Quaternion` - The rotation quaternion.
1333    pub fn from_axis_angle(axis: Vector3D, angle: f64) -> Quaternion {
1334        let half: f64 = angle * 0.5;
1335        let sin_half: f64 = half.sin();
1336        let cos_half: f64 = half.cos();
1337        let normalized_axis: Vector3D = axis.normalized();
1338        Quaternion::new(
1339            normalized_axis.get_x() * sin_half,
1340            normalized_axis.get_y() * sin_half,
1341            normalized_axis.get_z() * sin_half,
1342            cos_half,
1343        )
1344    }
1345
1346    /// Creates a quaternion from Euler angles (yaw, pitch, roll) in radians.
1347    ///
1348    /// # Arguments
1349    ///
1350    /// - `f64` - The yaw (rotation around y axis) in radians.
1351    /// - `f64` - The pitch (rotation around x axis) in radians.
1352    /// - `f64` - The roll (rotation around z axis) in radians.
1353    ///
1354    /// # Returns
1355    ///
1356    /// - `Quaternion` - The rotation quaternion.
1357    pub fn from_euler(yaw: f64, pitch: f64, roll: f64) -> Quaternion {
1358        let half_yaw: f64 = yaw * 0.5;
1359        let half_pitch: f64 = pitch * 0.5;
1360        let half_roll: f64 = roll * 0.5;
1361        let cy: f64 = half_yaw.cos();
1362        let sy: f64 = half_yaw.sin();
1363        let cp: f64 = half_pitch.cos();
1364        let sp: f64 = half_pitch.sin();
1365        let cr: f64 = half_roll.cos();
1366        let sr: f64 = half_roll.sin();
1367        Quaternion::new(
1368            sp * cy * cr + cp * sy * sr,
1369            cp * sy * cr - sp * cy * sr,
1370            cp * cy * sr - sp * sy * cr,
1371            sp * sy * sr + cp * cy * cr,
1372        )
1373    }
1374
1375    /// Returns the magnitude of the quaternion.
1376    ///
1377    /// # Returns
1378    ///
1379    /// - `f64` - The magnitude.
1380    pub fn magnitude(&self) -> f64 {
1381        (self.get_x() * self.get_x()
1382            + self.get_y() * self.get_y()
1383            + self.get_z() * self.get_z()
1384            + self.get_w() * self.get_w())
1385        .sqrt()
1386    }
1387
1388    /// Returns a normalized copy of this quaternion.
1389    ///
1390    /// # Returns
1391    ///
1392    /// - `Quaternion` - The normalized quaternion.
1393    pub fn normalized(&self) -> Quaternion {
1394        let mag: f64 = self.magnitude();
1395        if mag < EPSILON {
1396            return Quaternion::identity();
1397        }
1398        let inv: f64 = 1.0 / mag;
1399        Quaternion::new(
1400            self.get_x() * inv,
1401            self.get_y() * inv,
1402            self.get_z() * inv,
1403            self.get_w() * inv,
1404        )
1405    }
1406
1407    /// Returns the conjugate of this quaternion.
1408    ///
1409    /// # Returns
1410    ///
1411    /// - `Quaternion` - The conjugate quaternion.
1412    pub fn conjugate(&self) -> Quaternion {
1413        Quaternion::new(-self.get_x(), -self.get_y(), -self.get_z(), self.get_w())
1414    }
1415
1416    /// Computes the dot product with another quaternion.
1417    ///
1418    /// # Arguments
1419    ///
1420    /// - `Quaternion` - The other quaternion.
1421    ///
1422    /// # Returns
1423    ///
1424    /// - `f64` - The dot product.
1425    pub fn dot(&self, other: Quaternion) -> f64 {
1426        self.get_x() * other.get_x()
1427            + self.get_y() * other.get_y()
1428            + self.get_z() * other.get_z()
1429            + self.get_w() * other.get_w()
1430    }
1431
1432    /// Performs spherical linear interpolation between this and another quaternion.
1433    ///
1434    /// # Arguments
1435    ///
1436    /// - `Quaternion` - The target quaternion.
1437    /// - `f64` - The interpolation factor in the range 0.0 to 1.0.
1438    ///
1439    /// # Returns
1440    ///
1441    /// - `Quaternion` - The interpolated quaternion.
1442    pub fn slerp(&self, other: Quaternion, factor: f64) -> Quaternion {
1443        let mut cos_theta: f64 = self.dot(other);
1444        let target: Quaternion = if cos_theta < 0.0 {
1445            cos_theta = -cos_theta;
1446            Quaternion::new(
1447                -other.get_x(),
1448                -other.get_y(),
1449                -other.get_z(),
1450                -other.get_w(),
1451            )
1452        } else {
1453            other
1454        };
1455        if cos_theta > 1.0 - EPSILON {
1456            return Quaternion::new(
1457                self.get_x() + (target.get_x() - self.get_x()) * factor,
1458                self.get_y() + (target.get_y() - self.get_y()) * factor,
1459                self.get_z() + (target.get_z() - self.get_z()) * factor,
1460                self.get_w() + (target.get_w() - self.get_w()) * factor,
1461            )
1462            .normalized();
1463        }
1464        let theta: f64 = cos_theta.acos();
1465        let sin_theta: f64 = theta.sin();
1466        let factor_a: f64 = ((1.0 - factor) * theta).sin() / sin_theta;
1467        let factor_b: f64 = (factor * theta).sin() / sin_theta;
1468        Quaternion::new(
1469            self.get_x() * factor_a + target.get_x() * factor_b,
1470            self.get_y() * factor_a + target.get_y() * factor_b,
1471            self.get_z() * factor_a + target.get_z() * factor_b,
1472            self.get_w() * factor_a + target.get_w() * factor_b,
1473        )
1474    }
1475}
1476
1477/// Implements quaternion multiplication.
1478impl Mul for Quaternion {
1479    type Output = Quaternion;
1480    fn mul(self, other: Quaternion) -> Quaternion {
1481        Quaternion::new(
1482            self.get_w() * other.get_x()
1483                + self.get_x() * other.get_w()
1484                + self.get_y() * other.get_z()
1485                - self.get_z() * other.get_y(),
1486            self.get_w() * other.get_y() - self.get_x() * other.get_z()
1487                + self.get_y() * other.get_w()
1488                + self.get_z() * other.get_x(),
1489            self.get_w() * other.get_z() + self.get_x() * other.get_y()
1490                - self.get_y() * other.get_x()
1491                + self.get_z() * other.get_w(),
1492            self.get_w() * other.get_w()
1493                - self.get_x() * other.get_x()
1494                - self.get_y() * other.get_y()
1495                - self.get_z() * other.get_z(),
1496        )
1497    }
1498}
1499
1500/// Implements matrix operations for `Matrix4x4`.
1501impl Matrix4x4 {
1502    /// Returns the identity matrix.
1503    ///
1504    /// # Returns
1505    ///
1506    /// - `Matrix4x4` - The identity matrix.
1507    pub fn identity() -> Matrix4x4 {
1508        Matrix4x4::new([
1509            1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
1510        ])
1511    }
1512
1513    /// Creates a translation matrix.
1514    ///
1515    /// # Arguments
1516    ///
1517    /// - `Vector3D` - The translation vector.
1518    ///
1519    /// # Returns
1520    ///
1521    /// - `Matrix4x4` - The translation matrix.
1522    pub fn translation(translation: Vector3D) -> Matrix4x4 {
1523        let mut elements: [f64; 16] = Self::identity().get_elements();
1524        elements[12] = translation.get_x();
1525        elements[13] = translation.get_y();
1526        elements[14] = translation.get_z();
1527        Matrix4x4::new(elements)
1528    }
1529
1530    /// Creates a scaling matrix.
1531    ///
1532    /// # Arguments
1533    ///
1534    /// - `Vector3D` - The scale factors.
1535    ///
1536    /// # Returns
1537    ///
1538    /// - `Matrix4x4` - The scaling matrix.
1539    pub fn scaling(scale: Vector3D) -> Matrix4x4 {
1540        Matrix4x4::new([
1541            scale.get_x(),
1542            0.0,
1543            0.0,
1544            0.0,
1545            0.0,
1546            scale.get_y(),
1547            0.0,
1548            0.0,
1549            0.0,
1550            0.0,
1551            scale.get_z(),
1552            0.0,
1553            0.0,
1554            0.0,
1555            0.0,
1556            1.0,
1557        ])
1558    }
1559
1560    /// Creates a rotation matrix from a quaternion.
1561    ///
1562    /// # Arguments
1563    ///
1564    /// - `Quaternion` - The rotation quaternion.
1565    ///
1566    /// # Returns
1567    ///
1568    /// - `Matrix4x4` - The rotation matrix.
1569    pub fn rotation(quaternion: Quaternion) -> Matrix4x4 {
1570        let xx: f64 = quaternion.get_x() * quaternion.get_x();
1571        let yy: f64 = quaternion.get_y() * quaternion.get_y();
1572        let zz: f64 = quaternion.get_z() * quaternion.get_z();
1573        let xy: f64 = quaternion.get_x() * quaternion.get_y();
1574        let xz: f64 = quaternion.get_x() * quaternion.get_z();
1575        let yz: f64 = quaternion.get_y() * quaternion.get_z();
1576        let wx: f64 = quaternion.get_w() * quaternion.get_x();
1577        let wy: f64 = quaternion.get_w() * quaternion.get_y();
1578        let wz: f64 = quaternion.get_w() * quaternion.get_z();
1579        Matrix4x4::new([
1580            1.0 - 2.0 * (yy + zz),
1581            2.0 * (xy + wz),
1582            2.0 * (xz - wy),
1583            0.0,
1584            2.0 * (xy - wz),
1585            1.0 - 2.0 * (xx + zz),
1586            2.0 * (yz + wx),
1587            0.0,
1588            2.0 * (xz + wy),
1589            2.0 * (yz - wx),
1590            1.0 - 2.0 * (xx + yy),
1591            0.0,
1592            0.0,
1593            0.0,
1594            0.0,
1595            1.0,
1596        ])
1597    }
1598
1599    /// Creates a perspective projection matrix.
1600    ///
1601    /// # Arguments
1602    ///
1603    /// - `f64` - The vertical field of view in radians.
1604    /// - `f64` - The aspect ratio (width / height).
1605    /// - `f64` - The near clipping plane distance.
1606    /// - `f64` - The far clipping plane distance.
1607    ///
1608    /// # Returns
1609    ///
1610    /// - `Matrix4x4` - The perspective projection matrix.
1611    pub fn perspective(fov: f64, aspect: f64, near: f64, far: f64) -> Matrix4x4 {
1612        let f: f64 = 1.0 / (fov * 0.5).tan();
1613        let range: f64 = far - near;
1614        Matrix4x4::new([
1615            f / aspect,
1616            0.0,
1617            0.0,
1618            0.0,
1619            0.0,
1620            f,
1621            0.0,
1622            0.0,
1623            0.0,
1624            0.0,
1625            -(far + near) / range,
1626            -1.0,
1627            0.0,
1628            0.0,
1629            -(2.0 * far * near) / range,
1630            0.0,
1631        ])
1632    }
1633
1634    /// Creates an orthographic projection matrix.
1635    ///
1636    /// # Arguments
1637    ///
1638    /// - `f64` - The left boundary.
1639    /// - `f64` - The right boundary.
1640    /// - `f64` - The bottom boundary.
1641    /// - `f64` - The top boundary.
1642    /// - `f64` - The near clipping plane distance.
1643    /// - `f64` - The far clipping plane distance.
1644    ///
1645    /// # Returns
1646    ///
1647    /// - `Matrix4x4` - The orthographic projection matrix.
1648    pub fn orthographic(
1649        left: f64,
1650        right: f64,
1651        bottom: f64,
1652        top: f64,
1653        near: f64,
1654        far: f64,
1655    ) -> Matrix4x4 {
1656        let rml: f64 = right - left;
1657        let tmb: f64 = top - bottom;
1658        let fmn: f64 = far - near;
1659        Matrix4x4::new([
1660            2.0 / rml,
1661            0.0,
1662            0.0,
1663            0.0,
1664            0.0,
1665            2.0 / tmb,
1666            0.0,
1667            0.0,
1668            0.0,
1669            0.0,
1670            -2.0 / fmn,
1671            0.0,
1672            -(right + left) / rml,
1673            -(top + bottom) / tmb,
1674            -(far + near) / fmn,
1675            1.0,
1676        ])
1677    }
1678
1679    /// Creates a view matrix using the "look at" convention.
1680    ///
1681    /// # Arguments
1682    ///
1683    /// - `Vector3D` - The eye position.
1684    /// - `Vector3D` - The target position to look at.
1685    /// - `Vector3D` - The up direction.
1686    ///
1687    /// # Returns
1688    ///
1689    /// - `Matrix4x4` - The view matrix.
1690    pub fn look_at(eye: Vector3D, target: Vector3D, up: Vector3D) -> Matrix4x4 {
1691        let forward: Vector3D = (target - eye).normalized();
1692        let right: Vector3D = forward.cross(up).normalized();
1693        let up_orthogonal: Vector3D = right.cross(forward);
1694        Matrix4x4::new([
1695            right.get_x(),
1696            up_orthogonal.get_x(),
1697            -forward.get_x(),
1698            0.0,
1699            right.get_y(),
1700            up_orthogonal.get_y(),
1701            -forward.get_y(),
1702            0.0,
1703            right.get_z(),
1704            up_orthogonal.get_z(),
1705            -forward.get_z(),
1706            0.0,
1707            -right.dot(eye),
1708            -up_orthogonal.dot(eye),
1709            forward.dot(eye),
1710            1.0,
1711        ])
1712    }
1713
1714    /// Multiplies this matrix by another using fully unrolled arithmetic.
1715    ///
1716    /// Eliminates all loop overhead and allows the compiler to maximize
1717    /// register allocation and instruction-level parallelism.
1718    ///
1719    /// # Arguments
1720    ///
1721    /// - `Matrix4x4` - The other matrix.
1722    ///
1723    /// # Returns
1724    ///
1725    /// - `Matrix4x4` - The product matrix.
1726    pub fn multiply(&self, other: Matrix4x4) -> Matrix4x4 {
1727        let a: [f64; 16] = self.get_elements();
1728        let b: [f64; 16] = other.get_elements();
1729        Matrix4x4::new([
1730            a[0] * b[0] + a[4] * b[1] + a[8] * b[2] + a[12] * b[3],
1731            a[1] * b[0] + a[5] * b[1] + a[9] * b[2] + a[13] * b[3],
1732            a[2] * b[0] + a[6] * b[1] + a[10] * b[2] + a[14] * b[3],
1733            a[3] * b[0] + a[7] * b[1] + a[11] * b[2] + a[15] * b[3],
1734            a[0] * b[4] + a[4] * b[5] + a[8] * b[6] + a[12] * b[7],
1735            a[1] * b[4] + a[5] * b[5] + a[9] * b[6] + a[13] * b[7],
1736            a[2] * b[4] + a[6] * b[5] + a[10] * b[6] + a[14] * b[7],
1737            a[3] * b[4] + a[7] * b[5] + a[11] * b[6] + a[15] * b[7],
1738            a[0] * b[8] + a[4] * b[9] + a[8] * b[10] + a[12] * b[11],
1739            a[1] * b[8] + a[5] * b[9] + a[9] * b[10] + a[13] * b[11],
1740            a[2] * b[8] + a[6] * b[9] + a[10] * b[10] + a[14] * b[11],
1741            a[3] * b[8] + a[7] * b[9] + a[11] * b[10] + a[15] * b[11],
1742            a[0] * b[12] + a[4] * b[13] + a[8] * b[14] + a[12] * b[15],
1743            a[1] * b[12] + a[5] * b[13] + a[9] * b[14] + a[13] * b[15],
1744            a[2] * b[12] + a[6] * b[13] + a[10] * b[14] + a[14] * b[15],
1745            a[3] * b[12] + a[7] * b[13] + a[11] * b[14] + a[15] * b[15],
1746        ])
1747    }
1748
1749    /// Transforms a 3D point by this matrix, applying the perspective divide.
1750    ///
1751    /// # Arguments
1752    ///
1753    /// - `Vector3D` - The point to transform.
1754    ///
1755    /// # Returns
1756    ///
1757    /// - `Vector3D` - The transformed point.
1758    pub fn transform_point(&self, point: Vector3D) -> Vector3D {
1759        let elements: [f64; 16] = self.get_elements();
1760        let x: f64 = elements[0] * point.get_x()
1761            + elements[4] * point.get_y()
1762            + elements[8] * point.get_z()
1763            + elements[12];
1764        let y: f64 = elements[1] * point.get_x()
1765            + elements[5] * point.get_y()
1766            + elements[9] * point.get_z()
1767            + elements[13];
1768        let z: f64 = elements[2] * point.get_x()
1769            + elements[6] * point.get_y()
1770            + elements[10] * point.get_z()
1771            + elements[14];
1772        let w: f64 = elements[3] * point.get_x()
1773            + elements[7] * point.get_y()
1774            + elements[11] * point.get_z()
1775            + elements[15];
1776        if w.abs() < EPSILON {
1777            return Vector3D::new(x, y, z);
1778        }
1779        Vector3D::new(x / w, y / w, z / w)
1780    }
1781}
1782
1783/// Implements `Default` for `Quaternion` as the identity quaternion.
1784impl Default for Quaternion {
1785    fn default() -> Quaternion {
1786        Quaternion::identity()
1787    }
1788}
1789
1790/// Implements `Default` for `Matrix4x4` as the identity matrix.
1791impl Default for Matrix4x4 {
1792    fn default() -> Matrix4x4 {
1793        Matrix4x4::identity()
1794    }
1795}
1796
1797/// Implements methods for `Transform3D`.
1798impl Transform3D {
1799    /// Creates a new transform at the origin with no rotation and unit scale.
1800    ///
1801    /// # Returns
1802    ///
1803    /// - `Transform3D` - The identity transform.
1804    pub fn identity() -> Transform3D {
1805        Transform3D::new(
1806            Vector3D::zero(),
1807            Quaternion::identity(),
1808            Vector3D::new(1.0, 1.0, 1.0),
1809        )
1810    }
1811
1812    /// Translates the position by the given offset.
1813    ///
1814    /// # Arguments
1815    ///
1816    /// - `Vector3D` - The translation offset.
1817    pub fn translate(&mut self, offset: Vector3D) {
1818        self.set_position(self.get_position() + offset);
1819    }
1820
1821    /// Rotates by the given quaternion (post-multiplies).
1822    ///
1823    /// # Arguments
1824    ///
1825    /// - `Quaternion` - The rotation to apply.
1826    pub fn rotate(&mut self, rotation: Quaternion) {
1827        self.set_rotation(rotation * self.get_rotation());
1828    }
1829
1830    /// Scales by the given factors.
1831    ///
1832    /// # Arguments
1833    ///
1834    /// - `Vector3D` - The scale factors.
1835    pub fn scale_by(&mut self, factors: Vector3D) {
1836        let mut scale: Vector3D = self.get_scale();
1837        scale.set_x(scale.get_x() * factors.get_x());
1838        scale.set_y(scale.get_y() * factors.get_y());
1839        scale.set_z(scale.get_z() * factors.get_z());
1840        self.set_scale(scale);
1841    }
1842
1843    /// Applies this transform to a local-space point, returning world-space coordinates.
1844    ///
1845    /// # Arguments
1846    ///
1847    /// - `Vector3D` - The local-space point.
1848    ///
1849    /// # Returns
1850    ///
1851    /// - `Vector3D` - The transformed world-space point.
1852    pub fn apply_to_point(&self, point: Vector3D) -> Vector3D {
1853        let scale: Vector3D = self.get_scale();
1854        let scaled: Vector3D = Vector3D::new(
1855            point.get_x() * scale.get_x(),
1856            point.get_y() * scale.get_y(),
1857            point.get_z() * scale.get_z(),
1858        );
1859        scaled.rotated_by(self.get_rotation()) + self.get_position()
1860    }
1861
1862    /// Converts this transform to a `Matrix4x4`.
1863    ///
1864    /// # Returns
1865    ///
1866    /// - `Matrix4x4` - The composed transformation matrix.
1867    pub fn to_matrix(&self) -> Matrix4x4 {
1868        let translation: Matrix4x4 = Matrix4x4::translation(self.get_position());
1869        let rotation: Matrix4x4 = Matrix4x4::rotation(self.get_rotation());
1870        let scaling: Matrix4x4 = Matrix4x4::scaling(self.get_scale());
1871        translation.multiply(rotation).multiply(scaling)
1872    }
1873}
1874
1875/// Implements `Default` for `Transform3D` as the identity transform.
1876impl Default for Transform3D {
1877    fn default() -> Transform3D {
1878        Transform3D::identity()
1879    }
1880}
1881
1882/// Implements methods for `AABB3D`.
1883impl AABB3D {
1884    /// Creates an AABB from a center point and dimensions.
1885    ///
1886    /// # Arguments
1887    ///
1888    /// - `Vector3D` - The center point.
1889    /// - `f64` - The width.
1890    /// - `f64` - The height.
1891    /// - `f64` - The depth.
1892    ///
1893    /// # Returns
1894    ///
1895    /// - `AABB3D` - The new bounding box.
1896    pub fn from_center(center: Vector3D, width: f64, height: f64, depth: f64) -> AABB3D {
1897        AABB3D::new(
1898            Vector3D::new(
1899                center.get_x() - width * 0.5,
1900                center.get_y() - height * 0.5,
1901                center.get_z() - depth * 0.5,
1902            ),
1903            Vector3D::new(
1904                center.get_x() + width * 0.5,
1905                center.get_y() + height * 0.5,
1906                center.get_z() + depth * 0.5,
1907            ),
1908        )
1909    }
1910
1911    /// Returns the center point of the bounding box.
1912    ///
1913    /// # Returns
1914    ///
1915    /// - `Vector3D` - The center point.
1916    pub fn center(&self) -> Vector3D {
1917        Vector3D::new(
1918            (self.get_min().get_x() + self.get_max().get_x()) * 0.5,
1919            (self.get_min().get_y() + self.get_max().get_y()) * 0.5,
1920            (self.get_min().get_z() + self.get_max().get_z()) * 0.5,
1921        )
1922    }
1923
1924    /// Returns the dimensions of the bounding box as a vector.
1925    ///
1926    /// # Returns
1927    ///
1928    /// - `Vector3D` - The size vector (width, height, depth).
1929    pub fn size(&self) -> Vector3D {
1930        Vector3D::new(
1931            self.get_max().get_x() - self.get_min().get_x(),
1932            self.get_max().get_y() - self.get_min().get_y(),
1933            self.get_max().get_z() - self.get_min().get_z(),
1934        )
1935    }
1936
1937    /// Tests whether a point is inside this bounding box.
1938    ///
1939    /// # Arguments
1940    ///
1941    /// - `Vector3D` - The point to test.
1942    ///
1943    /// # Returns
1944    ///
1945    /// - `bool` - True if the point is inside.
1946    pub fn contains(&self, point: Vector3D) -> bool {
1947        point.get_x() >= self.get_min().get_x()
1948            && point.get_x() <= self.get_max().get_x()
1949            && point.get_y() >= self.get_min().get_y()
1950            && point.get_y() <= self.get_max().get_y()
1951            && point.get_z() >= self.get_min().get_z()
1952            && point.get_z() <= self.get_max().get_z()
1953    }
1954
1955    /// Tests whether this bounding box intersects another.
1956    ///
1957    /// # Arguments
1958    ///
1959    /// - `AABB3D` - The other bounding box.
1960    ///
1961    /// # Returns
1962    ///
1963    /// - `bool` - True if they intersect.
1964    pub fn intersects(&self, other: AABB3D) -> bool {
1965        self.get_min().get_x() <= other.get_max().get_x()
1966            && self.get_max().get_x() >= other.get_min().get_x()
1967            && self.get_min().get_y() <= other.get_max().get_y()
1968            && self.get_max().get_y() >= other.get_min().get_y()
1969            && self.get_min().get_z() <= other.get_max().get_z()
1970            && self.get_max().get_z() >= other.get_min().get_z()
1971    }
1972}
1973
1974/// Implements methods for `Sphere`.
1975impl Sphere {
1976    /// Tests whether a point is inside this sphere.
1977    ///
1978    /// # Arguments
1979    ///
1980    /// - `Vector3D` - The point to test.
1981    ///
1982    /// # Returns
1983    ///
1984    /// - `bool` - True if the point is inside.
1985    pub fn contains(&self, point: Vector3D) -> bool {
1986        self.get_center().distance_squared_to(point) <= self.get_radius() * self.get_radius()
1987    }
1988
1989    /// Tests whether this sphere intersects another.
1990    ///
1991    /// # Arguments
1992    ///
1993    /// - `Sphere` - The other sphere.
1994    ///
1995    /// # Returns
1996    ///
1997    /// - `bool` - True if they intersect.
1998    pub fn intersects(&self, other: Sphere) -> bool {
1999        let distance_sq: f64 = self.get_center().distance_squared_to(other.get_center());
2000        let radius_sum: f64 = self.get_radius() + other.get_radius();
2001        distance_sq <= radius_sum * radius_sum
2002    }
2003
2004    /// Returns the volume of the sphere.
2005    ///
2006    /// # Returns
2007    ///
2008    /// - `f64` - The volume.
2009    pub fn volume(&self) -> f64 {
2010        (4.0 / 3.0) * PI * self.get_radius() * self.get_radius() * self.get_radius()
2011    }
2012
2013    /// Returns the surface area of the sphere.
2014    ///
2015    /// # Returns
2016    ///
2017    /// - `f64` - The surface area.
2018    pub fn surface_area(&self) -> f64 {
2019        4.0 * PI * self.get_radius() * self.get_radius()
2020    }
2021}
2022
2023/// Implements methods for `Plane`.
2024impl Plane {
2025    /// Creates a plane from a normal and a point on the plane.
2026    ///
2027    /// # Arguments
2028    ///
2029    /// - `Vector3D` - The normal vector.
2030    /// - `Vector3D` - A point on the plane.
2031    ///
2032    /// # Returns
2033    ///
2034    /// - `Plane` - The new plane.
2035    pub fn from_normal_and_point(normal: Vector3D, point: Vector3D) -> Plane {
2036        let normalized_normal: Vector3D = normal.normalized();
2037        Plane::new(normalized_normal, -normalized_normal.dot(point))
2038    }
2039
2040    /// Returns the signed distance from a point to this plane.
2041    ///
2042    /// # Arguments
2043    ///
2044    /// - `Vector3D` - The point to test.
2045    ///
2046    /// # Returns
2047    ///
2048    /// - `f64` - The signed distance (positive on the normal side).
2049    pub fn distance_to_point(&self, point: Vector3D) -> f64 {
2050        self.get_normal().dot(point) + self.get_distance()
2051    }
2052
2053    /// Normalizes the plane normal and adjusts the distance accordingly.
2054    pub fn normalize(&mut self) {
2055        let mut normal: Vector3D = self.get_normal();
2056        let mag: f64 = normal.magnitude();
2057        if mag < EPSILON {
2058            return;
2059        }
2060        normal.set_x(normal.get_x() / mag);
2061        normal.set_y(normal.get_y() / mag);
2062        normal.set_z(normal.get_z() / mag);
2063        self.set_normal(normal);
2064        self.set_distance(self.get_distance() / mag);
2065    }
2066}
2067
2068/// Implements methods for `Ray3D`.
2069impl Ray3D {
2070    /// Returns the point on the ray at the given parameter value.
2071    ///
2072    /// # Arguments
2073    ///
2074    /// - `f64` - The parameter value (distance along the ray).
2075    ///
2076    /// # Returns
2077    ///
2078    /// - `Vector3D` - The point at the given distance.
2079    pub fn point_at(&self, t: f64) -> Vector3D {
2080        self.get_origin() + self.get_direction().scaled(t)
2081    }
2082
2083    /// Tests for intersection with a sphere, returning the nearest distance if hit.
2084    ///
2085    /// # Arguments
2086    ///
2087    /// - `Sphere` - The sphere to test.
2088    ///
2089    /// # Returns
2090    ///
2091    /// - `Option<f64>` - The distance to the intersection, or `None`.
2092    pub fn intersect_sphere(&self, sphere: Sphere) -> Option<f64> {
2093        let oc: Vector3D = self.get_origin() - sphere.get_center();
2094        let direction: Vector3D = self.get_direction();
2095        let a: f64 = direction.dot(direction);
2096        let b: f64 = 2.0 * oc.dot(direction);
2097        let c: f64 = oc.dot(oc) - sphere.get_radius() * sphere.get_radius();
2098        let discriminant: f64 = b * b - 4.0 * a * c;
2099        if discriminant < 0.0 {
2100            return None;
2101        }
2102        let sqrt_d: f64 = discriminant.sqrt();
2103        let t1: f64 = (-b - sqrt_d) / (2.0 * a);
2104        if t1 >= 0.0 {
2105            return Some(t1);
2106        }
2107        let t2: f64 = (-b + sqrt_d) / (2.0 * a);
2108        if t2 >= 0.0 {
2109            return Some(t2);
2110        }
2111        None
2112    }
2113
2114    /// Tests for intersection with a plane, returning the distance if hit.
2115    ///
2116    /// # Arguments
2117    ///
2118    /// - `Plane` - The plane to test.
2119    ///
2120    /// # Returns
2121    ///
2122    /// - `Option<f64>` - The distance to the intersection, or `None`.
2123    pub fn intersect_plane(&self, plane: Plane) -> Option<f64> {
2124        let direction: Vector3D = self.get_direction();
2125        let normal: Vector3D = plane.get_normal();
2126        let denom: f64 = direction.dot(normal);
2127        if denom.abs() < EPSILON {
2128            return None;
2129        }
2130        let t: f64 = -(normal.dot(self.get_origin()) + plane.get_distance()) / denom;
2131        if t >= 0.0 { Some(t) } else { None }
2132    }
2133
2134    /// Tests for intersection with an AABB, returning the nearest distance if hit.
2135    ///
2136    /// # Arguments
2137    ///
2138    /// - `AABB3D` - The bounding box to test.
2139    ///
2140    /// # Returns
2141    ///
2142    /// - `Option<f64>` - The distance to the intersection, or `None`.
2143    pub fn intersect_aabb(&self, aabb: AABB3D) -> Option<f64> {
2144        let mut t_min: f64 = f64::MIN;
2145        let mut t_max: f64 = f64::MAX;
2146        let direction: Vector3D = self.get_direction();
2147        let origin: Vector3D = self.get_origin();
2148        let aabb_min: Vector3D = aabb.get_min();
2149        let aabb_max: Vector3D = aabb.get_max();
2150        for axis in 0..3usize {
2151            let (dir_component, origin_component, min_component, max_component) = match axis {
2152                0 => (
2153                    direction.get_x(),
2154                    origin.get_x(),
2155                    aabb_min.get_x(),
2156                    aabb_max.get_x(),
2157                ),
2158                1 => (
2159                    direction.get_y(),
2160                    origin.get_y(),
2161                    aabb_min.get_y(),
2162                    aabb_max.get_y(),
2163                ),
2164                _ => (
2165                    direction.get_z(),
2166                    origin.get_z(),
2167                    aabb_min.get_z(),
2168                    aabb_max.get_z(),
2169                ),
2170            };
2171            if dir_component.abs() < EPSILON {
2172                if origin_component < min_component || origin_component > max_component {
2173                    return None;
2174                }
2175            } else {
2176                let inv_dir: f64 = 1.0 / dir_component;
2177                let t1: f64 = (min_component - origin_component) * inv_dir;
2178                let t2: f64 = (max_component - origin_component) * inv_dir;
2179                let t_near: f64 = t1.min(t2);
2180                let t_far: f64 = t1.max(t2);
2181                t_min = t_min.max(t_near);
2182                t_max = t_max.min(t_far);
2183                if t_min > t_max {
2184                    return None;
2185                }
2186            }
2187        }
2188        if t_min >= 0.0 {
2189            Some(t_min)
2190        } else if t_max >= 0.0 {
2191            Some(t_max)
2192        } else {
2193            None
2194        }
2195    }
2196}