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