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, 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    /// Alias for `intersects` — used by the physics module's broad-phase
679    /// collision check. (`Rect::broad_phase(a, b)` was being referenced by
680    /// upstream code; we expose it here so the engine compiles cleanly.)
681    pub fn broad_phase_alias(a: Rect, b: Rect) -> bool {
682        a.intersects(b)
683    }
684
685    /// Returns the intersection of two rectangles, or `None` if they do not overlap.
686    ///
687    /// # Arguments
688    ///
689    /// - `Rect` - The other rectangle.
690    ///
691    /// # Returns
692    ///
693    /// - `Option<Rect>` - The intersection rectangle, or `None`.
694    pub fn intersection(&self, other: Rect) -> Option<Rect> {
695        if !self.intersects(other) {
696            return None;
697        }
698        let max_x: f64 = self.get_x().max(other.get_x());
699        let max_y: f64 = self.get_y().max(other.get_y());
700        let min_right: f64 =
701            (self.get_x() + self.get_width()).min(other.get_x() + other.get_width());
702        let min_bottom: f64 =
703            (self.get_y() + self.get_height()).min(other.get_y() + other.get_height());
704        Some(Rect::new(
705            max_x,
706            max_y,
707            min_right - max_x,
708            min_bottom - max_y,
709        ))
710    }
711}
712
713/// Implements methods for `Circle`.
714impl Circle {
715    /// Tests whether a point is inside this circle.
716    ///
717    /// # Arguments
718    ///
719    /// - `Vector2D` - The point to test.
720    ///
721    /// # Returns
722    ///
723    /// - `bool` - True if the point is inside.
724    pub fn contains(&self, point: Vector2D) -> bool {
725        self.get_center().distance_squared_to(point) <= self.get_radius() * self.get_radius()
726    }
727
728    /// Tests whether this circle intersects another.
729    ///
730    /// # Arguments
731    ///
732    /// - `Circle` - The other circle.
733    ///
734    /// # Returns
735    ///
736    /// - `bool` - True if they intersect.
737    pub fn intersects(&self, other: Circle) -> bool {
738        let distance_sq: f64 = self.get_center().distance_squared_to(other.get_center());
739        let radius_sum: f64 = self.get_radius() + other.get_radius();
740        distance_sq <= radius_sum * radius_sum
741    }
742
743    /// Returns the circumference of the circle.
744    ///
745    /// # Returns
746    ///
747    /// - `f64` - The circumference.
748    pub fn circumference(&self) -> f64 {
749        TWO_PI * self.get_radius()
750    }
751
752    /// Returns the area of the circle.
753    ///
754    /// # Returns
755    ///
756    /// - `f64` - The area.
757    pub fn area(&self) -> f64 {
758        PI * self.get_radius() * self.get_radius()
759    }
760}
761
762/// Implements methods for `Transform2D`.
763impl Transform2D {
764    /// Creates a new transform at the origin with no rotation and unit scale.
765    ///
766    /// # Returns
767    ///
768    /// - `Transform2D` - The identity transform.
769    pub fn identity() -> Transform2D {
770        Transform2D::new(Vector2D::zero(), 0.0, Vector2D::new(1.0, 1.0))
771    }
772
773    /// Translates the position by the given offset.
774    ///
775    /// # Arguments
776    ///
777    /// - `Vector2D` - The translation offset.
778    pub fn translate(&mut self, offset: Vector2D) {
779        self.set_position(self.get_position() + offset);
780    }
781
782    /// Rotates by the given angle in radians.
783    ///
784    /// # Arguments
785    ///
786    /// - `f64` - The rotation delta in radians.
787    pub fn rotate(&mut self, radians: f64) {
788        self.set_rotation(self.get_rotation() + radians);
789    }
790
791    /// Scales by the given factors.
792    ///
793    /// # Arguments
794    ///
795    /// - `Vector2D` - The scale factors.
796    pub fn scale_by(&mut self, factors: Vector2D) {
797        let mut scale: Vector2D = self.get_scale();
798        scale.set_x(scale.get_x() * factors.get_x());
799        scale.set_y(scale.get_y() * factors.get_y());
800        self.set_scale(scale);
801    }
802
803    /// Applies this transform to a local-space point, returning world-space coordinates.
804    ///
805    /// # Arguments
806    ///
807    /// - `Vector2D` - The local-space point.
808    ///
809    /// # Returns
810    ///
811    /// - `Vector2D` - The transformed world-space point.
812    pub fn apply_to_point(&self, point: Vector2D) -> Vector2D {
813        let scaled: Vector2D = Vector2D::new(
814            point.get_x() * self.get_scale().get_x(),
815            point.get_y() * self.get_scale().get_y(),
816        );
817        scaled.rotated(self.get_rotation()) + self.get_position()
818    }
819}
820
821/// Implements `Default` for `Transform2D` as the identity transform.
822impl Default for Transform2D {
823    fn default() -> Transform2D {
824        Transform2D::identity()
825    }
826}
827
828/// Implements methods for `Color`.
829impl Color {
830    /// Creates a color from RGB hex values (0-255), with full opacity.
831    ///
832    /// # Arguments
833    ///
834    /// - `u8` - The red channel (0-255).
835    /// - `u8` - The green channel (0-255).
836    /// - `u8` - The blue channel (0-255).
837    ///
838    /// # Returns
839    ///
840    /// - `Color` - The new color.
841    pub fn from_rgb(red: u8, green: u8, blue: u8) -> Color {
842        Color::new(
843            red as f64 / 255.0,
844            green as f64 / 255.0,
845            blue as f64 / 255.0,
846            1.0,
847        )
848    }
849
850    /// Converts the color to a CSS `rgba()` string.
851    ///
852    /// # Returns
853    ///
854    /// - `String` - The CSS color string.
855    pub fn to_css_rgba(&self) -> String {
856        let mut buffer: String = String::with_capacity(32);
857        self.write_css_rgba(&mut buffer);
858        buffer
859    }
860
861    /// Writes the CSS `rgba()` representation into the provided buffer.
862    ///
863    /// Reuses the caller's allocation so per-frame color conversions in tight
864    /// render loops avoid the `format!` machinery and repeated allocation.
865    ///
866    /// # Arguments
867    ///
868    /// - `&mut String` - The buffer to append the CSS color string to.
869    pub fn write_css_rgba(&self, buffer: &mut String) {
870        use std::fmt::Write as _;
871        let red: i32 = (self.get_red() * 255.0).round() as i32;
872        let green: i32 = (self.get_green() * 255.0).round() as i32;
873        let blue: i32 = (self.get_blue() * 255.0).round() as i32;
874        let alpha: f64 = self.get_alpha();
875        let _ = write!(buffer, "rgba({red}, {green}, {blue}, {alpha})");
876    }
877
878    /// Returns black (0, 0, 0, 1).
879    ///
880    /// # Returns
881    ///
882    /// - `Color` - The black color.
883    pub fn black() -> Color {
884        Color::new(0.0, 0.0, 0.0, 1.0)
885    }
886
887    /// Returns white (1, 1, 1, 1).
888    ///
889    /// # Returns
890    ///
891    /// - `Color` - The white color.
892    pub fn white() -> Color {
893        Color::new(1.0, 1.0, 1.0, 1.0)
894    }
895
896    /// Returns transparent (0, 0, 0, 0).
897    ///
898    /// # Returns
899    ///
900    /// - `Color` - The transparent color.
901    pub fn transparent() -> Color {
902        Color::new(0.0, 0.0, 0.0, 0.0)
903    }
904}
905
906/// Implements `Default` for `Color` as opaque black.
907impl Default for Color {
908    fn default() -> Color {
909        Color::black()
910    }
911}
912
913/// Implements methods and operator overloading for `Vector3D`.
914impl Vector3D {
915    /// Returns the zero vector (0.0, 0.0, 0.0).
916    ///
917    /// # Returns
918    ///
919    /// - `Vector3D` - The zero vector.
920    pub fn zero() -> Vector3D {
921        Vector3D::new(0.0, 0.0, 0.0)
922    }
923
924    /// Returns the unit vector pointing right (1.0, 0.0, 0.0).
925    ///
926    /// # Returns
927    ///
928    /// - `Vector3D` - The right unit vector.
929    pub fn right() -> Vector3D {
930        Vector3D::new(1.0, 0.0, 0.0)
931    }
932
933    /// Returns the unit vector pointing up (0.0, 1.0, 0.0).
934    ///
935    /// # Returns
936    ///
937    /// - `Vector3D` - The up unit vector.
938    pub fn up() -> Vector3D {
939        Vector3D::new(0.0, 1.0, 0.0)
940    }
941
942    /// Returns the unit vector pointing forward (0.0, 0.0, -1.0).
943    ///
944    /// In a right-handed coordinate system where -z is forward.
945    ///
946    /// # Returns
947    ///
948    /// - `Vector3D` - The forward unit vector.
949    pub fn forward() -> Vector3D {
950        Vector3D::new(0.0, 0.0, -1.0)
951    }
952
953    /// Returns the magnitude (length) of the vector.
954    ///
955    /// # Returns
956    ///
957    /// - `f64` - The magnitude of the vector.
958    pub fn magnitude(&self) -> f64 {
959        (self.get_x() * self.get_x() + self.get_y() * self.get_y() + self.get_z() * self.get_z())
960            .sqrt()
961    }
962
963    /// Returns the squared magnitude of the vector.
964    ///
965    /// Avoids a square root, making it faster for comparison-only use cases.
966    ///
967    /// # Returns
968    ///
969    /// - `f64` - The squared magnitude of the vector.
970    pub fn magnitude_squared(&self) -> f64 {
971        self.get_x() * self.get_x() + self.get_y() * self.get_y() + self.get_z() * self.get_z()
972    }
973
974    /// Returns a normalized (unit length) copy of this vector.
975    ///
976    /// Returns the zero vector if the magnitude is zero.
977    ///
978    /// # Returns
979    ///
980    /// - `Vector3D` - The normalized vector.
981    pub fn normalized(&self) -> Vector3D {
982        let mag: f64 = self.magnitude();
983        if mag < EPSILON {
984            return Vector3D::zero();
985        }
986        Vector3D::new(self.get_x() / mag, self.get_y() / mag, self.get_z() / mag)
987    }
988
989    /// Normalizes this vector in place.
990    pub fn normalize(&mut self) {
991        let mag: f64 = self.magnitude();
992        if mag < EPSILON {
993            self.set_x(0.0);
994            self.set_y(0.0);
995            self.set_z(0.0);
996            return;
997        }
998        self.set_x(self.get_x() / mag);
999        self.set_y(self.get_y() / mag);
1000        self.set_z(self.get_z() / mag);
1001    }
1002
1003    /// Computes the dot product with another vector.
1004    ///
1005    /// # Arguments
1006    ///
1007    /// - `Vector3D` - The other vector.
1008    ///
1009    /// # Returns
1010    ///
1011    /// - `f64` - The dot product.
1012    pub fn dot(&self, other: Vector3D) -> f64 {
1013        self.get_x() * other.get_x() + self.get_y() * other.get_y() + self.get_z() * other.get_z()
1014    }
1015
1016    /// Computes the 3D cross product with another vector.
1017    ///
1018    /// # Arguments
1019    ///
1020    /// - `Vector3D` - The other vector.
1021    ///
1022    /// # Returns
1023    ///
1024    /// - `Vector3D` - The cross product vector.
1025    pub fn cross(&self, other: Vector3D) -> Vector3D {
1026        Vector3D::new(
1027            self.get_y() * other.get_z() - self.get_z() * other.get_y(),
1028            self.get_z() * other.get_x() - self.get_x() * other.get_z(),
1029            self.get_x() * other.get_y() - self.get_y() * other.get_x(),
1030        )
1031    }
1032
1033    /// Returns the distance from this point to another.
1034    ///
1035    /// # Arguments
1036    ///
1037    /// - `Vector3D` - The target point.
1038    ///
1039    /// # Returns
1040    ///
1041    /// - `f64` - The Euclidean distance.
1042    pub fn distance_to(&self, other: Vector3D) -> f64 {
1043        (other - *self).magnitude()
1044    }
1045
1046    /// Returns the squared distance from this point to another.
1047    ///
1048    /// # Arguments
1049    ///
1050    /// - `Vector3D` - The target point.
1051    ///
1052    /// # Returns
1053    ///
1054    /// - `f64` - The squared Euclidean distance.
1055    pub fn distance_squared_to(&self, other: Vector3D) -> f64 {
1056        (other - *self).magnitude_squared()
1057    }
1058
1059    /// Returns a unit vector pointing from this point to another.
1060    ///
1061    /// # Arguments
1062    ///
1063    /// - `Vector3D` - The target point.
1064    ///
1065    /// # Returns
1066    ///
1067    /// - `Vector3D` - The direction unit vector.
1068    pub fn direction_to(&self, other: Vector3D) -> Vector3D {
1069        (other - *self).normalized()
1070    }
1071
1072    /// Returns a linearly interpolated vector between this and another.
1073    ///
1074    /// # Arguments
1075    ///
1076    /// - `Vector3D` - The target vector.
1077    /// - `f64` - The interpolation factor.
1078    ///
1079    /// # Returns
1080    ///
1081    /// - `Vector3D` - The interpolated vector.
1082    pub fn lerp(&self, other: Vector3D, t: f64) -> Vector3D {
1083        Vector3D::new(
1084            self.get_x() + (other.get_x() - self.get_x()) * t,
1085            self.get_y() + (other.get_y() - self.get_y()) * t,
1086            self.get_z() + (other.get_z() - self.get_z()) * t,
1087        )
1088    }
1089
1090    /// Scales this vector by a scalar factor.
1091    ///
1092    /// # Arguments
1093    ///
1094    /// - `f64` - The scalar factor.
1095    pub fn scale(&mut self, scalar: f64) {
1096        self.set_x(self.get_x() * scalar);
1097        self.set_y(self.get_y() * scalar);
1098        self.set_z(self.get_z() * scalar);
1099    }
1100
1101    /// Returns a scaled copy of this vector.
1102    ///
1103    /// # Arguments
1104    ///
1105    /// - `f64` - The scalar factor.
1106    ///
1107    /// # Returns
1108    ///
1109    /// - `Vector3D` - The scaled vector.
1110    pub fn scaled(&self, scalar: f64) -> Vector3D {
1111        Vector3D::new(
1112            self.get_x() * scalar,
1113            self.get_y() * scalar,
1114            self.get_z() * scalar,
1115        )
1116    }
1117
1118    /// Rotates this vector by a quaternion.
1119    ///
1120    /// # Arguments
1121    ///
1122    /// - `Quaternion` - The rotation quaternion.
1123    ///
1124    /// # Returns
1125    ///
1126    /// - `Vector3D` - The rotated vector.
1127    pub fn rotated_by(&self, quaternion: Quaternion) -> Vector3D {
1128        let pure: Quaternion = Quaternion::new(self.get_x(), self.get_y(), self.get_z(), 0.0);
1129        let result: Quaternion = quaternion * pure * quaternion.conjugate();
1130        Vector3D::new(result.get_x(), result.get_y(), result.get_z())
1131    }
1132}
1133
1134/// Implements `Interpolable` for `Vector3D`.
1135impl Interpolable for Vector3D {
1136    fn lerp(&self, other: Vector3D, t: f64) -> Vector3D {
1137        Vector3D::lerp(self, other, t)
1138    }
1139}
1140
1141/// Implements vector addition.
1142impl Add for Vector3D {
1143    type Output = Vector3D;
1144    fn add(self, other: Vector3D) -> Vector3D {
1145        Vector3D::new(
1146            self.get_x() + other.get_x(),
1147            self.get_y() + other.get_y(),
1148            self.get_z() + other.get_z(),
1149        )
1150    }
1151}
1152
1153/// Implements vector subtraction.
1154impl Sub for Vector3D {
1155    type Output = Vector3D;
1156    fn sub(self, other: Vector3D) -> Vector3D {
1157        Vector3D::new(
1158            self.get_x() - other.get_x(),
1159            self.get_y() - other.get_y(),
1160            self.get_z() - other.get_z(),
1161        )
1162    }
1163}
1164
1165/// Implements scalar multiplication.
1166impl Mul<f64> for Vector3D {
1167    type Output = Vector3D;
1168    fn mul(self, scalar: f64) -> Vector3D {
1169        Vector3D::new(
1170            self.get_x() * scalar,
1171            self.get_y() * scalar,
1172            self.get_z() * scalar,
1173        )
1174    }
1175}
1176
1177/// Implements vector negation.
1178impl Neg for Vector3D {
1179    type Output = Vector3D;
1180    fn neg(self) -> Vector3D {
1181        Vector3D::new(-self.get_x(), -self.get_y(), -self.get_z())
1182    }
1183}
1184
1185/// Implements in-place vector addition.
1186impl AddAssign for Vector3D {
1187    fn add_assign(&mut self, other: Vector3D) {
1188        self.set_x(self.get_x() + other.get_x());
1189        self.set_y(self.get_y() + other.get_y());
1190        self.set_z(self.get_z() + other.get_z());
1191    }
1192}
1193
1194/// Implements in-place vector subtraction.
1195impl SubAssign for Vector3D {
1196    fn sub_assign(&mut self, other: Vector3D) {
1197        self.set_x(self.get_x() - other.get_x());
1198        self.set_y(self.get_y() - other.get_y());
1199        self.set_z(self.get_z() - other.get_z());
1200    }
1201}
1202
1203/// Implements in-place scalar multiplication.
1204impl MulAssign<f64> for Vector3D {
1205    fn mul_assign(&mut self, scalar: f64) {
1206        self.set_x(self.get_x() * scalar);
1207        self.set_y(self.get_y() * scalar);
1208        self.set_z(self.get_z() * scalar);
1209    }
1210}
1211
1212/// Implements quaternion operations for `Quaternion`.
1213impl Quaternion {
1214    /// Returns the identity quaternion (0, 0, 0, 1) representing no rotation.
1215    ///
1216    /// # Returns
1217    ///
1218    /// - `Quaternion` - The identity quaternion.
1219    pub fn identity() -> Quaternion {
1220        Quaternion::new(0.0, 0.0, 0.0, 1.0)
1221    }
1222
1223    /// Creates a quaternion from a rotation around an axis.
1224    ///
1225    /// # Arguments
1226    ///
1227    /// - `Vector3D` - The rotation axis (should be normalized).
1228    /// - `f64` - The rotation angle in radians.
1229    ///
1230    /// # Returns
1231    ///
1232    /// - `Quaternion` - The rotation quaternion.
1233    pub fn from_axis_angle(axis: Vector3D, angle: f64) -> Quaternion {
1234        let half: f64 = angle * 0.5;
1235        let sin_half: f64 = half.sin();
1236        let cos_half: f64 = half.cos();
1237        let normalized_axis: Vector3D = axis.normalized();
1238        Quaternion::new(
1239            normalized_axis.get_x() * sin_half,
1240            normalized_axis.get_y() * sin_half,
1241            normalized_axis.get_z() * sin_half,
1242            cos_half,
1243        )
1244    }
1245
1246    /// Creates a quaternion from Euler angles (yaw, pitch, roll) in radians.
1247    ///
1248    /// # Arguments
1249    ///
1250    /// - `f64` - The yaw (rotation around y axis) in radians.
1251    /// - `f64` - The pitch (rotation around x axis) in radians.
1252    /// - `f64` - The roll (rotation around z axis) in radians.
1253    ///
1254    /// # Returns
1255    ///
1256    /// - `Quaternion` - The rotation quaternion.
1257    pub fn from_euler(yaw: f64, pitch: f64, roll: f64) -> Quaternion {
1258        let half_yaw: f64 = yaw * 0.5;
1259        let half_pitch: f64 = pitch * 0.5;
1260        let half_roll: f64 = roll * 0.5;
1261        let cy: f64 = half_yaw.cos();
1262        let sy: f64 = half_yaw.sin();
1263        let cp: f64 = half_pitch.cos();
1264        let sp: f64 = half_pitch.sin();
1265        let cr: f64 = half_roll.cos();
1266        let sr: f64 = half_roll.sin();
1267        Quaternion::new(
1268            sp * cy * cr + cp * sy * sr,
1269            cp * sy * cr - sp * cy * sr,
1270            cp * cy * sr - sp * sy * cr,
1271            sp * sy * sr + cp * cy * cr,
1272        )
1273    }
1274
1275    /// Returns the magnitude of the quaternion.
1276    ///
1277    /// # Returns
1278    ///
1279    /// - `f64` - The magnitude.
1280    pub fn magnitude(&self) -> f64 {
1281        (self.get_x() * self.get_x()
1282            + self.get_y() * self.get_y()
1283            + self.get_z() * self.get_z()
1284            + self.get_w() * self.get_w())
1285        .sqrt()
1286    }
1287
1288    /// Returns a normalized copy of this quaternion.
1289    ///
1290    /// # Returns
1291    ///
1292    /// - `Quaternion` - The normalized quaternion.
1293    pub fn normalized(&self) -> Quaternion {
1294        let mag: f64 = self.magnitude();
1295        if mag < EPSILON {
1296            return Quaternion::identity();
1297        }
1298        let inv: f64 = 1.0 / mag;
1299        Quaternion::new(
1300            self.get_x() * inv,
1301            self.get_y() * inv,
1302            self.get_z() * inv,
1303            self.get_w() * inv,
1304        )
1305    }
1306
1307    /// Returns the conjugate of this quaternion.
1308    ///
1309    /// # Returns
1310    ///
1311    /// - `Quaternion` - The conjugate quaternion.
1312    pub fn conjugate(&self) -> Quaternion {
1313        Quaternion::new(-self.get_x(), -self.get_y(), -self.get_z(), self.get_w())
1314    }
1315
1316    /// Computes the dot product with another quaternion.
1317    ///
1318    /// # Arguments
1319    ///
1320    /// - `Quaternion` - The other quaternion.
1321    ///
1322    /// # Returns
1323    ///
1324    /// - `f64` - The dot product.
1325    pub fn dot(&self, other: Quaternion) -> f64 {
1326        self.get_x() * other.get_x()
1327            + self.get_y() * other.get_y()
1328            + self.get_z() * other.get_z()
1329            + self.get_w() * other.get_w()
1330    }
1331
1332    /// Performs spherical linear interpolation between this and another quaternion.
1333    ///
1334    /// # Arguments
1335    ///
1336    /// - `Quaternion` - The target quaternion.
1337    /// - `f64` - The interpolation factor in the range 0.0 to 1.0.
1338    ///
1339    /// # Returns
1340    ///
1341    /// - `Quaternion` - The interpolated quaternion.
1342    pub fn slerp(&self, other: Quaternion, t: f64) -> Quaternion {
1343        let mut cos_theta: f64 = self.dot(other);
1344        let target: Quaternion = if cos_theta < 0.0 {
1345            cos_theta = -cos_theta;
1346            Quaternion::new(
1347                -other.get_x(),
1348                -other.get_y(),
1349                -other.get_z(),
1350                -other.get_w(),
1351            )
1352        } else {
1353            other
1354        };
1355        if cos_theta > 1.0 - EPSILON {
1356            return Quaternion::new(
1357                self.get_x() + (target.get_x() - self.get_x()) * t,
1358                self.get_y() + (target.get_y() - self.get_y()) * t,
1359                self.get_z() + (target.get_z() - self.get_z()) * t,
1360                self.get_w() + (target.get_w() - self.get_w()) * t,
1361            )
1362            .normalized();
1363        }
1364        let theta: f64 = cos_theta.acos();
1365        let sin_theta: f64 = theta.sin();
1366        let factor_a: f64 = ((1.0 - t) * theta).sin() / sin_theta;
1367        let factor_b: f64 = (t * theta).sin() / sin_theta;
1368        Quaternion::new(
1369            self.get_x() * factor_a + target.get_x() * factor_b,
1370            self.get_y() * factor_a + target.get_y() * factor_b,
1371            self.get_z() * factor_a + target.get_z() * factor_b,
1372            self.get_w() * factor_a + target.get_w() * factor_b,
1373        )
1374    }
1375}
1376
1377/// Implements quaternion multiplication.
1378impl Mul for Quaternion {
1379    type Output = Quaternion;
1380    fn mul(self, other: Quaternion) -> Quaternion {
1381        Quaternion::new(
1382            self.get_w() * other.get_x()
1383                + self.get_x() * other.get_w()
1384                + self.get_y() * other.get_z()
1385                - self.get_z() * other.get_y(),
1386            self.get_w() * other.get_y() - self.get_x() * other.get_z()
1387                + self.get_y() * other.get_w()
1388                + self.get_z() * other.get_x(),
1389            self.get_w() * other.get_z() + self.get_x() * other.get_y()
1390                - self.get_y() * other.get_x()
1391                + self.get_z() * other.get_w(),
1392            self.get_w() * other.get_w()
1393                - self.get_x() * other.get_x()
1394                - self.get_y() * other.get_y()
1395                - self.get_z() * other.get_z(),
1396        )
1397    }
1398}
1399
1400/// Implements matrix operations for `Matrix4x4`.
1401impl Matrix4x4 {
1402    /// Returns the identity matrix.
1403    ///
1404    /// # Returns
1405    ///
1406    /// - `Matrix4x4` - The identity matrix.
1407    pub fn identity() -> Matrix4x4 {
1408        Matrix4x4::new([
1409            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,
1410        ])
1411    }
1412
1413    /// Creates a translation matrix.
1414    ///
1415    /// # Arguments
1416    ///
1417    /// - `Vector3D` - The translation vector.
1418    ///
1419    /// # Returns
1420    ///
1421    /// - `Matrix4x4` - The translation matrix.
1422    pub fn translation(translation: Vector3D) -> Matrix4x4 {
1423        let mut elements: [f64; 16] = Self::identity().get_elements();
1424        elements[12] = translation.get_x();
1425        elements[13] = translation.get_y();
1426        elements[14] = translation.get_z();
1427        Matrix4x4::new(elements)
1428    }
1429
1430    /// Creates a scaling matrix.
1431    ///
1432    /// # Arguments
1433    ///
1434    /// - `Vector3D` - The scale factors.
1435    ///
1436    /// # Returns
1437    ///
1438    /// - `Matrix4x4` - The scaling matrix.
1439    pub fn scaling(scale: Vector3D) -> Matrix4x4 {
1440        Matrix4x4::new([
1441            scale.get_x(),
1442            0.0,
1443            0.0,
1444            0.0,
1445            0.0,
1446            scale.get_y(),
1447            0.0,
1448            0.0,
1449            0.0,
1450            0.0,
1451            scale.get_z(),
1452            0.0,
1453            0.0,
1454            0.0,
1455            0.0,
1456            1.0,
1457        ])
1458    }
1459
1460    /// Creates a rotation matrix from a quaternion.
1461    ///
1462    /// # Arguments
1463    ///
1464    /// - `Quaternion` - The rotation quaternion.
1465    ///
1466    /// # Returns
1467    ///
1468    /// - `Matrix4x4` - The rotation matrix.
1469    pub fn rotation(quaternion: Quaternion) -> Matrix4x4 {
1470        let xx: f64 = quaternion.get_x() * quaternion.get_x();
1471        let yy: f64 = quaternion.get_y() * quaternion.get_y();
1472        let zz: f64 = quaternion.get_z() * quaternion.get_z();
1473        let xy: f64 = quaternion.get_x() * quaternion.get_y();
1474        let xz: f64 = quaternion.get_x() * quaternion.get_z();
1475        let yz: f64 = quaternion.get_y() * quaternion.get_z();
1476        let wx: f64 = quaternion.get_w() * quaternion.get_x();
1477        let wy: f64 = quaternion.get_w() * quaternion.get_y();
1478        let wz: f64 = quaternion.get_w() * quaternion.get_z();
1479        Matrix4x4::new([
1480            1.0 - 2.0 * (yy + zz),
1481            2.0 * (xy + wz),
1482            2.0 * (xz - wy),
1483            0.0,
1484            2.0 * (xy - wz),
1485            1.0 - 2.0 * (xx + zz),
1486            2.0 * (yz + wx),
1487            0.0,
1488            2.0 * (xz + wy),
1489            2.0 * (yz - wx),
1490            1.0 - 2.0 * (xx + yy),
1491            0.0,
1492            0.0,
1493            0.0,
1494            0.0,
1495            1.0,
1496        ])
1497    }
1498
1499    /// Creates a perspective projection matrix.
1500    ///
1501    /// # Arguments
1502    ///
1503    /// - `f64` - The vertical field of view in radians.
1504    /// - `f64` - The aspect ratio (width / height).
1505    /// - `f64` - The near clipping plane distance.
1506    /// - `f64` - The far clipping plane distance.
1507    ///
1508    /// # Returns
1509    ///
1510    /// - `Matrix4x4` - The perspective projection matrix.
1511    pub fn perspective(fov: f64, aspect: f64, near: f64, far: f64) -> Matrix4x4 {
1512        let f: f64 = 1.0 / (fov * 0.5).tan();
1513        let range: f64 = far - near;
1514        Matrix4x4::new([
1515            f / aspect,
1516            0.0,
1517            0.0,
1518            0.0,
1519            0.0,
1520            f,
1521            0.0,
1522            0.0,
1523            0.0,
1524            0.0,
1525            -(far + near) / range,
1526            -1.0,
1527            0.0,
1528            0.0,
1529            -(2.0 * far * near) / range,
1530            0.0,
1531        ])
1532    }
1533
1534    /// Creates an orthographic projection matrix.
1535    ///
1536    /// # Arguments
1537    ///
1538    /// - `f64` - The left boundary.
1539    /// - `f64` - The right boundary.
1540    /// - `f64` - The bottom boundary.
1541    /// - `f64` - The top boundary.
1542    /// - `f64` - The near clipping plane distance.
1543    /// - `f64` - The far clipping plane distance.
1544    ///
1545    /// # Returns
1546    ///
1547    /// - `Matrix4x4` - The orthographic projection matrix.
1548    pub fn orthographic(
1549        left: f64,
1550        right: f64,
1551        bottom: f64,
1552        top: f64,
1553        near: f64,
1554        far: f64,
1555    ) -> Matrix4x4 {
1556        let rml: f64 = right - left;
1557        let tmb: f64 = top - bottom;
1558        let fmn: f64 = far - near;
1559        Matrix4x4::new([
1560            2.0 / rml,
1561            0.0,
1562            0.0,
1563            0.0,
1564            0.0,
1565            2.0 / tmb,
1566            0.0,
1567            0.0,
1568            0.0,
1569            0.0,
1570            -2.0 / fmn,
1571            0.0,
1572            -(right + left) / rml,
1573            -(top + bottom) / tmb,
1574            -(far + near) / fmn,
1575            1.0,
1576        ])
1577    }
1578
1579    /// Creates a view matrix using the "look at" convention.
1580    ///
1581    /// # Arguments
1582    ///
1583    /// - `Vector3D` - The eye position.
1584    /// - `Vector3D` - The target position to look at.
1585    /// - `Vector3D` - The up direction.
1586    ///
1587    /// # Returns
1588    ///
1589    /// - `Matrix4x4` - The view matrix.
1590    pub fn look_at(eye: Vector3D, target: Vector3D, up: Vector3D) -> Matrix4x4 {
1591        let forward: Vector3D = (target - eye).normalized();
1592        let right: Vector3D = forward.cross(up).normalized();
1593        let up_orthogonal: Vector3D = right.cross(forward);
1594        Matrix4x4::new([
1595            right.get_x(),
1596            up_orthogonal.get_x(),
1597            -forward.get_x(),
1598            0.0,
1599            right.get_y(),
1600            up_orthogonal.get_y(),
1601            -forward.get_y(),
1602            0.0,
1603            right.get_z(),
1604            up_orthogonal.get_z(),
1605            -forward.get_z(),
1606            0.0,
1607            -right.dot(eye),
1608            -up_orthogonal.dot(eye),
1609            forward.dot(eye),
1610            1.0,
1611        ])
1612    }
1613
1614    /// Multiplies this matrix by another using fully unrolled arithmetic.
1615    ///
1616    /// Eliminates all loop overhead and allows the compiler to maximize
1617    /// register allocation and instruction-level parallelism.
1618    ///
1619    /// # Arguments
1620    ///
1621    /// - `Matrix4x4` - The other matrix.
1622    ///
1623    /// # Returns
1624    ///
1625    /// - `Matrix4x4` - The product matrix.
1626    pub fn multiply(&self, other: Matrix4x4) -> Matrix4x4 {
1627        let a: [f64; 16] = self.get_elements();
1628        let b: [f64; 16] = other.get_elements();
1629        Matrix4x4::new([
1630            a[0] * b[0] + a[4] * b[1] + a[8] * b[2] + a[12] * b[3],
1631            a[1] * b[0] + a[5] * b[1] + a[9] * b[2] + a[13] * b[3],
1632            a[2] * b[0] + a[6] * b[1] + a[10] * b[2] + a[14] * b[3],
1633            a[3] * b[0] + a[7] * b[1] + a[11] * b[2] + a[15] * b[3],
1634            a[0] * b[4] + a[4] * b[5] + a[8] * b[6] + a[12] * b[7],
1635            a[1] * b[4] + a[5] * b[5] + a[9] * b[6] + a[13] * b[7],
1636            a[2] * b[4] + a[6] * b[5] + a[10] * b[6] + a[14] * b[7],
1637            a[3] * b[4] + a[7] * b[5] + a[11] * b[6] + a[15] * b[7],
1638            a[0] * b[8] + a[4] * b[9] + a[8] * b[10] + a[12] * b[11],
1639            a[1] * b[8] + a[5] * b[9] + a[9] * b[10] + a[13] * b[11],
1640            a[2] * b[8] + a[6] * b[9] + a[10] * b[10] + a[14] * b[11],
1641            a[3] * b[8] + a[7] * b[9] + a[11] * b[10] + a[15] * b[11],
1642            a[0] * b[12] + a[4] * b[13] + a[8] * b[14] + a[12] * b[15],
1643            a[1] * b[12] + a[5] * b[13] + a[9] * b[14] + a[13] * b[15],
1644            a[2] * b[12] + a[6] * b[13] + a[10] * b[14] + a[14] * b[15],
1645            a[3] * b[12] + a[7] * b[13] + a[11] * b[14] + a[15] * b[15],
1646        ])
1647    }
1648
1649    /// Transforms a 3D point by this matrix, applying the perspective divide.
1650    ///
1651    /// # Arguments
1652    ///
1653    /// - `Vector3D` - The point to transform.
1654    ///
1655    /// # Returns
1656    ///
1657    /// - `Vector3D` - The transformed point.
1658    pub fn transform_point(&self, point: Vector3D) -> Vector3D {
1659        let elements: [f64; 16] = self.get_elements();
1660        let x: f64 = elements[0] * point.get_x()
1661            + elements[4] * point.get_y()
1662            + elements[8] * point.get_z()
1663            + elements[12];
1664        let y: f64 = elements[1] * point.get_x()
1665            + elements[5] * point.get_y()
1666            + elements[9] * point.get_z()
1667            + elements[13];
1668        let z: f64 = elements[2] * point.get_x()
1669            + elements[6] * point.get_y()
1670            + elements[10] * point.get_z()
1671            + elements[14];
1672        let w: f64 = elements[3] * point.get_x()
1673            + elements[7] * point.get_y()
1674            + elements[11] * point.get_z()
1675            + elements[15];
1676        if w.abs() < EPSILON {
1677            return Vector3D::new(x, y, z);
1678        }
1679        Vector3D::new(x / w, y / w, z / w)
1680    }
1681}
1682
1683/// Implements `Default` for `Quaternion` as the identity quaternion.
1684impl Default for Quaternion {
1685    fn default() -> Quaternion {
1686        Quaternion::identity()
1687    }
1688}
1689
1690/// Implements `Default` for `Matrix4x4` as the identity matrix.
1691impl Default for Matrix4x4 {
1692    fn default() -> Matrix4x4 {
1693        Matrix4x4::identity()
1694    }
1695}
1696
1697/// Implements methods for `Transform3D`.
1698impl Transform3D {
1699    /// Creates a new transform at the origin with no rotation and unit scale.
1700    ///
1701    /// # Returns
1702    ///
1703    /// - `Transform3D` - The identity transform.
1704    pub fn identity() -> Transform3D {
1705        Transform3D::new(
1706            Vector3D::zero(),
1707            Quaternion::identity(),
1708            Vector3D::new(1.0, 1.0, 1.0),
1709        )
1710    }
1711
1712    /// Translates the position by the given offset.
1713    ///
1714    /// # Arguments
1715    ///
1716    /// - `Vector3D` - The translation offset.
1717    pub fn translate(&mut self, offset: Vector3D) {
1718        self.set_position(self.get_position() + offset);
1719    }
1720
1721    /// Rotates by the given quaternion (post-multiplies).
1722    ///
1723    /// # Arguments
1724    ///
1725    /// - `Quaternion` - The rotation to apply.
1726    pub fn rotate(&mut self, rotation: Quaternion) {
1727        self.set_rotation(rotation * self.get_rotation());
1728    }
1729
1730    /// Scales by the given factors.
1731    ///
1732    /// # Arguments
1733    ///
1734    /// - `Vector3D` - The scale factors.
1735    pub fn scale_by(&mut self, factors: Vector3D) {
1736        let mut scale: Vector3D = self.get_scale();
1737        scale.set_x(scale.get_x() * factors.get_x());
1738        scale.set_y(scale.get_y() * factors.get_y());
1739        scale.set_z(scale.get_z() * factors.get_z());
1740        self.set_scale(scale);
1741    }
1742
1743    /// Applies this transform to a local-space point, returning world-space coordinates.
1744    ///
1745    /// # Arguments
1746    ///
1747    /// - `Vector3D` - The local-space point.
1748    ///
1749    /// # Returns
1750    ///
1751    /// - `Vector3D` - The transformed world-space point.
1752    pub fn apply_to_point(&self, point: Vector3D) -> Vector3D {
1753        let scale: Vector3D = self.get_scale();
1754        let scaled: Vector3D = Vector3D::new(
1755            point.get_x() * scale.get_x(),
1756            point.get_y() * scale.get_y(),
1757            point.get_z() * scale.get_z(),
1758        );
1759        scaled.rotated_by(self.get_rotation()) + self.get_position()
1760    }
1761
1762    /// Converts this transform to a `Matrix4x4`.
1763    ///
1764    /// # Returns
1765    ///
1766    /// - `Matrix4x4` - The composed transformation matrix.
1767    pub fn to_matrix(&self) -> Matrix4x4 {
1768        let translation: Matrix4x4 = Matrix4x4::translation(self.get_position());
1769        let rotation: Matrix4x4 = Matrix4x4::rotation(self.get_rotation());
1770        let scaling: Matrix4x4 = Matrix4x4::scaling(self.get_scale());
1771        translation.multiply(rotation).multiply(scaling)
1772    }
1773}
1774
1775/// Implements `Default` for `Transform3D` as the identity transform.
1776impl Default for Transform3D {
1777    fn default() -> Transform3D {
1778        Transform3D::identity()
1779    }
1780}
1781
1782/// Implements methods for `AABB3D`.
1783impl AABB3D {
1784    /// Creates an AABB from a center point and dimensions.
1785    ///
1786    /// # Arguments
1787    ///
1788    /// - `Vector3D` - The center point.
1789    /// - `f64` - The width.
1790    /// - `f64` - The height.
1791    /// - `f64` - The depth.
1792    ///
1793    /// # Returns
1794    ///
1795    /// - `AABB3D` - The new bounding box.
1796    pub fn from_center(center: Vector3D, width: f64, height: f64, depth: f64) -> AABB3D {
1797        AABB3D::new(
1798            Vector3D::new(
1799                center.get_x() - width * 0.5,
1800                center.get_y() - height * 0.5,
1801                center.get_z() - depth * 0.5,
1802            ),
1803            Vector3D::new(
1804                center.get_x() + width * 0.5,
1805                center.get_y() + height * 0.5,
1806                center.get_z() + depth * 0.5,
1807            ),
1808        )
1809    }
1810
1811    /// Returns the center point of the bounding box.
1812    ///
1813    /// # Returns
1814    ///
1815    /// - `Vector3D` - The center point.
1816    pub fn center(&self) -> Vector3D {
1817        Vector3D::new(
1818            (self.get_min().get_x() + self.get_max().get_x()) * 0.5,
1819            (self.get_min().get_y() + self.get_max().get_y()) * 0.5,
1820            (self.get_min().get_z() + self.get_max().get_z()) * 0.5,
1821        )
1822    }
1823
1824    /// Returns the dimensions of the bounding box as a vector.
1825    ///
1826    /// # Returns
1827    ///
1828    /// - `Vector3D` - The size vector (width, height, depth).
1829    pub fn size(&self) -> Vector3D {
1830        Vector3D::new(
1831            self.get_max().get_x() - self.get_min().get_x(),
1832            self.get_max().get_y() - self.get_min().get_y(),
1833            self.get_max().get_z() - self.get_min().get_z(),
1834        )
1835    }
1836
1837    /// Tests whether a point is inside this bounding box.
1838    ///
1839    /// # Arguments
1840    ///
1841    /// - `Vector3D` - The point to test.
1842    ///
1843    /// # Returns
1844    ///
1845    /// - `bool` - True if the point is inside.
1846    pub fn contains(&self, point: Vector3D) -> bool {
1847        point.get_x() >= self.get_min().get_x()
1848            && point.get_x() <= self.get_max().get_x()
1849            && point.get_y() >= self.get_min().get_y()
1850            && point.get_y() <= self.get_max().get_y()
1851            && point.get_z() >= self.get_min().get_z()
1852            && point.get_z() <= self.get_max().get_z()
1853    }
1854
1855    /// Tests whether this bounding box intersects another.
1856    ///
1857    /// # Arguments
1858    ///
1859    /// - `AABB3D` - The other bounding box.
1860    ///
1861    /// # Returns
1862    ///
1863    /// - `bool` - True if they intersect.
1864    pub fn intersects(&self, other: AABB3D) -> bool {
1865        self.get_min().get_x() <= other.get_max().get_x()
1866            && self.get_max().get_x() >= other.get_min().get_x()
1867            && self.get_min().get_y() <= other.get_max().get_y()
1868            && self.get_max().get_y() >= other.get_min().get_y()
1869            && self.get_min().get_z() <= other.get_max().get_z()
1870            && self.get_max().get_z() >= other.get_min().get_z()
1871    }
1872}
1873
1874/// Implements methods for `Sphere`.
1875impl Sphere {
1876    /// Tests whether a point is inside this sphere.
1877    ///
1878    /// # Arguments
1879    ///
1880    /// - `Vector3D` - The point to test.
1881    ///
1882    /// # Returns
1883    ///
1884    /// - `bool` - True if the point is inside.
1885    pub fn contains(&self, point: Vector3D) -> bool {
1886        self.get_center().distance_squared_to(point) <= self.get_radius() * self.get_radius()
1887    }
1888
1889    /// Tests whether this sphere intersects another.
1890    ///
1891    /// # Arguments
1892    ///
1893    /// - `Sphere` - The other sphere.
1894    ///
1895    /// # Returns
1896    ///
1897    /// - `bool` - True if they intersect.
1898    pub fn intersects(&self, other: Sphere) -> bool {
1899        let distance_sq: f64 = self.get_center().distance_squared_to(other.get_center());
1900        let radius_sum: f64 = self.get_radius() + other.get_radius();
1901        distance_sq <= radius_sum * radius_sum
1902    }
1903
1904    /// Returns the volume of the sphere.
1905    ///
1906    /// # Returns
1907    ///
1908    /// - `f64` - The volume.
1909    pub fn volume(&self) -> f64 {
1910        (4.0 / 3.0) * PI * self.get_radius() * self.get_radius() * self.get_radius()
1911    }
1912
1913    /// Returns the surface area of the sphere.
1914    ///
1915    /// # Returns
1916    ///
1917    /// - `f64` - The surface area.
1918    pub fn surface_area(&self) -> f64 {
1919        4.0 * PI * self.get_radius() * self.get_radius()
1920    }
1921}
1922
1923/// Implements methods for `Plane`.
1924impl Plane {
1925    /// Creates a plane from a normal and a point on the plane.
1926    ///
1927    /// # Arguments
1928    ///
1929    /// - `Vector3D` - The normal vector.
1930    /// - `Vector3D` - A point on the plane.
1931    ///
1932    /// # Returns
1933    ///
1934    /// - `Plane` - The new plane.
1935    pub fn from_normal_and_point(normal: Vector3D, point: Vector3D) -> Plane {
1936        let normalized_normal: Vector3D = normal.normalized();
1937        Plane::new(normalized_normal, -normalized_normal.dot(point))
1938    }
1939
1940    /// Returns the signed distance from a point to this plane.
1941    ///
1942    /// # Arguments
1943    ///
1944    /// - `Vector3D` - The point to test.
1945    ///
1946    /// # Returns
1947    ///
1948    /// - `f64` - The signed distance (positive on the normal side).
1949    pub fn distance_to_point(&self, point: Vector3D) -> f64 {
1950        self.get_normal().dot(point) + self.get_distance()
1951    }
1952
1953    /// Normalizes the plane normal and adjusts the distance accordingly.
1954    pub fn normalize(&mut self) {
1955        let mut normal: Vector3D = self.get_normal();
1956        let mag: f64 = normal.magnitude();
1957        if mag < EPSILON {
1958            return;
1959        }
1960        normal.set_x(normal.get_x() / mag);
1961        normal.set_y(normal.get_y() / mag);
1962        normal.set_z(normal.get_z() / mag);
1963        self.set_normal(normal);
1964        self.set_distance(self.get_distance() / mag);
1965    }
1966}
1967
1968/// Implements methods for `Ray3D`.
1969impl Ray3D {
1970    /// Returns the point on the ray at the given parameter value.
1971    ///
1972    /// # Arguments
1973    ///
1974    /// - `f64` - The parameter value (distance along the ray).
1975    ///
1976    /// # Returns
1977    ///
1978    /// - `Vector3D` - The point at the given distance.
1979    pub fn point_at(&self, t: f64) -> Vector3D {
1980        self.get_origin() + self.get_direction().scaled(t)
1981    }
1982
1983    /// Tests for intersection with a sphere, returning the nearest distance if hit.
1984    ///
1985    /// # Arguments
1986    ///
1987    /// - `Sphere` - The sphere to test.
1988    ///
1989    /// # Returns
1990    ///
1991    /// - `Option<f64>` - The distance to the intersection, or `None`.
1992    pub fn intersect_sphere(&self, sphere: Sphere) -> Option<f64> {
1993        let oc: Vector3D = self.get_origin() - sphere.get_center();
1994        let direction: Vector3D = self.get_direction();
1995        let a: f64 = direction.dot(direction);
1996        let b: f64 = 2.0 * oc.dot(direction);
1997        let c: f64 = oc.dot(oc) - sphere.get_radius() * sphere.get_radius();
1998        let discriminant: f64 = b * b - 4.0 * a * c;
1999        if discriminant < 0.0 {
2000            return None;
2001        }
2002        let sqrt_d: f64 = discriminant.sqrt();
2003        let t1: f64 = (-b - sqrt_d) / (2.0 * a);
2004        if t1 >= 0.0 {
2005            return Some(t1);
2006        }
2007        let t2: f64 = (-b + sqrt_d) / (2.0 * a);
2008        if t2 >= 0.0 {
2009            return Some(t2);
2010        }
2011        None
2012    }
2013
2014    /// Tests for intersection with a plane, returning the distance if hit.
2015    ///
2016    /// # Arguments
2017    ///
2018    /// - `Plane` - The plane to test.
2019    ///
2020    /// # Returns
2021    ///
2022    /// - `Option<f64>` - The distance to the intersection, or `None`.
2023    pub fn intersect_plane(&self, plane: Plane) -> Option<f64> {
2024        let direction: Vector3D = self.get_direction();
2025        let normal: Vector3D = plane.get_normal();
2026        let denom: f64 = direction.dot(normal);
2027        if denom.abs() < EPSILON {
2028            return None;
2029        }
2030        let t: f64 = -(normal.dot(self.get_origin()) + plane.get_distance()) / denom;
2031        if t >= 0.0 { Some(t) } else { None }
2032    }
2033
2034    /// Tests for intersection with an AABB, returning the nearest distance if hit.
2035    ///
2036    /// # Arguments
2037    ///
2038    /// - `AABB3D` - The bounding box to test.
2039    ///
2040    /// # Returns
2041    ///
2042    /// - `Option<f64>` - The distance to the intersection, or `None`.
2043    pub fn intersect_aabb(&self, aabb: AABB3D) -> Option<f64> {
2044        let mut t_min: f64 = f64::MIN;
2045        let mut t_max: f64 = f64::MAX;
2046        let direction: Vector3D = self.get_direction();
2047        let origin: Vector3D = self.get_origin();
2048        let aabb_min: Vector3D = aabb.get_min();
2049        let aabb_max: Vector3D = aabb.get_max();
2050        for axis in 0..3usize {
2051            let (dir_component, origin_component, min_component, max_component) = match axis {
2052                0 => (
2053                    direction.get_x(),
2054                    origin.get_x(),
2055                    aabb_min.get_x(),
2056                    aabb_max.get_x(),
2057                ),
2058                1 => (
2059                    direction.get_y(),
2060                    origin.get_y(),
2061                    aabb_min.get_y(),
2062                    aabb_max.get_y(),
2063                ),
2064                _ => (
2065                    direction.get_z(),
2066                    origin.get_z(),
2067                    aabb_min.get_z(),
2068                    aabb_max.get_z(),
2069                ),
2070            };
2071            if dir_component.abs() < EPSILON {
2072                if origin_component < min_component || origin_component > max_component {
2073                    return None;
2074                }
2075            } else {
2076                let inv_dir: f64 = 1.0 / dir_component;
2077                let t1: f64 = (min_component - origin_component) * inv_dir;
2078                let t2: f64 = (max_component - origin_component) * inv_dir;
2079                let t_near: f64 = t1.min(t2);
2080                let t_far: f64 = t1.max(t2);
2081                t_min = t_min.max(t_near);
2082                t_max = t_max.min(t_far);
2083                if t_min > t_max {
2084                    return None;
2085                }
2086            }
2087        }
2088        if t_min >= 0.0 {
2089            Some(t_min)
2090        } else if t_max >= 0.0 {
2091            Some(t_max)
2092        } else {
2093            None
2094        }
2095    }
2096}