Skip to main content

euv_engine/math/
impl.rs

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