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}