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