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