euv_engine/math/impl.rs
1use super::*;
2
3/// Implements static math utility methods on the `Numeric` namespace struct.
4impl Numeric {
5 /// Clamps a value between a minimum and maximum bound.
6 ///
7 /// # Arguments
8 ///
9 /// - `f64` - The value to clamp.
10 /// - `f64` - The minimum allowed value.
11 /// - `f64` - The maximum allowed value.
12 ///
13 /// # Returns
14 ///
15 /// - `f64` - The clamped value.
16 pub fn clamp(value: f64, min: f64, max: f64) -> f64 {
17 value.max(min).min(max)
18 }
19
20 /// Performs linear interpolation between two values.
21 ///
22 /// # Arguments
23 ///
24 /// - `f64` - The start value.
25 /// - `f64` - The end value.
26 /// - `f64` - The interpolation factor, typically in the range 0.0 to 1.0.
27 ///
28 /// # Returns
29 ///
30 /// - `f64` - The interpolated value.
31 pub fn lerp(start: f64, end: f64, factor: f64) -> f64 {
32 start + (end - start) * factor
33 }
34
35 /// Converts an angle from degrees to radians.
36 ///
37 /// # Arguments
38 ///
39 /// - `f64` - The angle in degrees.
40 ///
41 /// # Returns
42 ///
43 /// - `f64` - The angle in radians.
44 pub fn deg_to_rad(degrees: f64) -> f64 {
45 degrees * DEG_TO_RAD
46 }
47
48 /// Converts an angle from radians to degrees.
49 ///
50 /// # Arguments
51 ///
52 /// - `f64` - The angle in radians.
53 ///
54 /// # Returns
55 ///
56 /// - `f64` - The angle in degrees.
57 pub fn rad_to_deg(radians: f64) -> f64 {
58 radians * RAD_TO_DEG
59 }
60
61 /// Normalizes an angle to the range -PI to PI.
62 ///
63 /// # Arguments
64 ///
65 /// - `f64` - The angle in radians.
66 ///
67 /// # Returns
68 ///
69 /// - `f64` - The normalized angle in the range -PI to PI.
70 pub fn normalize_angle(radians: f64) -> f64 {
71 let mut angle: f64 = radians % TWO_PI;
72 if angle < -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, factor: f64) -> f64 {
107 from + Self::angle_delta(from, to) * factor
108 }
109
110 /// Computes the Euclidean distance between two 2D points.
111 ///
112 /// # Arguments
113 ///
114 /// - `Vector2D` - The first point.
115 /// - `Vector2D` - The second point.
116 ///
117 /// # Returns
118 ///
119 /// - `f64` - The distance between the two points.
120 pub fn distance(a: Vector2D, b: Vector2D) -> f64 {
121 (b - a).magnitude()
122 }
123
124 /// Computes the squared Euclidean distance between two 2D points.
125 ///
126 /// Avoids a square root, making it faster for comparison-only use cases.
127 ///
128 /// # Arguments
129 ///
130 /// - `Vector2D` - The first point.
131 /// - `Vector2D` - The second point.
132 ///
133 /// # Returns
134 ///
135 /// - `f64` - The squared distance between the two points.
136 pub fn distance_squared(a: Vector2D, b: Vector2D) -> f64 {
137 (b - a).magnitude_squared()
138 }
139
140 /// Computes a smoothstep interpolation factor using a cubic Hermite polynomial.
141 ///
142 /// # Arguments
143 ///
144 /// - `f64` - The edge minimum.
145 /// - `f64` - The edge maximum.
146 /// - `f64` - The input value.
147 ///
148 /// # Returns
149 ///
150 /// - `f64` - The smoothstep result in the range 0.0 to 1.0.
151 pub fn smoothstep(edge_min: f64, edge_max: f64, value: f64) -> f64 {
152 let clamped: f64 = Self::clamp((value - edge_min) / (edge_max - edge_min), 0.0, 1.0);
153 clamped * clamped * (3.0 - 2.0 * clamped)
154 }
155
156 /// Moves `current` towards `target` by at most `max_delta`.
157 ///
158 /// # Arguments
159 ///
160 /// - `f64` - The current value.
161 /// - `f64` - The target value.
162 /// - `f64` - The maximum allowed change.
163 ///
164 /// # Returns
165 ///
166 /// - `f64` - The new value moved towards target.
167 pub fn approach(current: f64, target: f64, max_delta: f64) -> f64 {
168 if (target - current).abs() <= max_delta {
169 return target;
170 }
171 current + max_delta.signum() * max_delta
172 }
173
174 /// Returns the sign of a value as -1.0, 0.0, or 1.0.
175 ///
176 /// # Arguments
177 ///
178 /// - `f64` - The input value.
179 ///
180 /// # Returns
181 ///
182 /// - `f64` - -1.0 if negative, 0.0 if zero, 1.0 if positive.
183 pub fn sign(value: f64) -> f64 {
184 if value > 0.0 {
185 1.0
186 } else if value < 0.0 {
187 -1.0
188 } else {
189 0.0
190 }
191 }
192
193 /// Wraps a value into the range 0.0 to `max`.
194 ///
195 /// # Arguments
196 ///
197 /// - `f64` - The value to wrap.
198 /// - `f64` - The upper bound of the range.
199 ///
200 /// # Returns
201 ///
202 /// - `f64` - The wrapped value in the range 0.0 to `max`.
203 pub fn wrap(value: f64, max: f64) -> f64 {
204 let result: f64 = value % max;
205 if result < 0.0 { result + max } else { result }
206 }
207
208 /// Returns 1.0 if the value is positive, -1.0 otherwise.
209 ///
210 /// # Arguments
211 ///
212 /// - `f64` - The input value.
213 ///
214 /// # Returns
215 ///
216 /// - `f64` - 1.0 if the value is non-negative, -1.0 otherwise.
217 pub fn sign_or_positive(value: f64) -> f64 {
218 if value < 0.0 { -1.0 } else { 1.0 }
219 }
220
221 /// Computes the Euclidean distance between two 3D points.
222 ///
223 /// # Arguments
224 ///
225 /// - `Vector3D` - The first point.
226 /// - `Vector3D` - The second point.
227 ///
228 /// # Returns
229 ///
230 /// - `f64` - The distance between the two points.
231 pub fn distance_3d(a: Vector3D, b: Vector3D) -> f64 {
232 (b - a).magnitude()
233 }
234
235 /// Computes the squared Euclidean distance between two 3D points.
236 ///
237 /// Avoids a square root, making it faster for comparison-only use cases.
238 ///
239 /// # Arguments
240 ///
241 /// - `Vector3D` - The first point.
242 /// - `Vector3D` - The second point.
243 ///
244 /// # Returns
245 ///
246 /// - `f64` - The squared distance between the two points.
247 pub fn distance_squared_3d(a: Vector3D, b: Vector3D) -> f64 {
248 (b - a).magnitude_squared()
249 }
250}
251
252/// Implements the `Interpolable` trait for `f64`.
253impl Interpolable for f64 {
254 fn lerp(&self, other: f64, factor: f64) -> f64 {
255 *self + (other - *self) * factor
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, factor: f64) -> Vector2D {
292 Vector2D::lerp(self, other, factor)
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, factor: f64) -> Vector2D {
526 Vector2D::new(
527 self.get_x() + (other.get_x() - self.get_x()) * factor,
528 self.get_y() + (other.get_y() - self.get_y()) * factor,
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, factor: f64) -> Vector2D {
559 Vector2D::lerp(self, other, factor)
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 /// Performs linear interpolation between this color and `other` by `t`,
943 /// interpolating each channel (red, green, blue, alpha) independently.
944 ///
945 /// # Arguments
946 ///
947 /// - `Color` - The target color.
948 /// - `f64` - The interpolation factor, typically in the range 0.0 to 1.0.
949 ///
950 /// # Returns
951 ///
952 /// - `Color` - The interpolated color.
953 pub fn lerp(&self, other: Color, factor: f64) -> Color {
954 Color::new(
955 self.get_red().lerp(other.get_red(), factor),
956 self.get_green().lerp(other.get_green(), factor),
957 self.get_blue().lerp(other.get_blue(), factor),
958 self.get_alpha().lerp(other.get_alpha(), factor),
959 )
960 }
961}
962
963/// Implements `Interpolable` for `Color`.
964impl Interpolable for Color {
965 fn lerp(&self, other: Color, factor: f64) -> Color {
966 Color::lerp(self, other, factor)
967 }
968}
969
970/// Implements `Default` for `Color` as opaque black.
971impl Default for Color {
972 fn default() -> Color {
973 Color::black()
974 }
975}
976
977/// Implements the [`Vector`] trait for `Vector3D`, forwarding every method to
978/// the inherent implementation on the struct.
979///
980/// `Vector3D` also offers 3D-specific operations that are not part of the
981/// trait surface: `cross` (returning `Vector3D`), `direction_to`,
982/// `distance_to`, `scale`, and `normalize`. These remain inherent.
983impl Vector for Vector3D {
984 fn zero() -> Vector3D {
985 Vector3D::zero()
986 }
987
988 fn dot(&self, other: Vector3D) -> f64 {
989 Vector3D::dot(self, other)
990 }
991
992 fn magnitude(&self) -> f64 {
993 Vector3D::magnitude(self)
994 }
995
996 fn magnitude_squared(&self) -> f64 {
997 Vector3D::magnitude_squared(self)
998 }
999
1000 fn normalized(&self) -> Vector3D {
1001 Vector3D::normalized(self)
1002 }
1003
1004 fn scaled(&self, scalar: f64) -> Vector3D {
1005 Vector3D::scaled(self, scalar)
1006 }
1007
1008 fn lerp(&self, other: Vector3D, factor: f64) -> Vector3D {
1009 Vector3D::lerp(self, other, factor)
1010 }
1011}
1012
1013/// Implements methods and operator overloading for `Vector3D`.
1014impl Vector3D {
1015 /// Returns the zero vector (0.0, 0.0, 0.0).
1016 ///
1017 /// # Returns
1018 ///
1019 /// - `Vector3D` - The zero vector.
1020 pub fn zero() -> Vector3D {
1021 Vector3D::new(0.0, 0.0, 0.0)
1022 }
1023
1024 /// Returns the unit vector pointing right (1.0, 0.0, 0.0).
1025 ///
1026 /// # Returns
1027 ///
1028 /// - `Vector3D` - The right unit vector.
1029 pub fn right() -> Vector3D {
1030 Vector3D::new(1.0, 0.0, 0.0)
1031 }
1032
1033 /// Returns the unit vector pointing up (0.0, 1.0, 0.0).
1034 ///
1035 /// # Returns
1036 ///
1037 /// - `Vector3D` - The up unit vector.
1038 pub fn up() -> Vector3D {
1039 Vector3D::new(0.0, 1.0, 0.0)
1040 }
1041
1042 /// Returns the unit vector pointing forward (0.0, 0.0, -1.0).
1043 ///
1044 /// In a right-handed coordinate system where -z is forward.
1045 ///
1046 /// # Returns
1047 ///
1048 /// - `Vector3D` - The forward unit vector.
1049 pub fn forward() -> Vector3D {
1050 Vector3D::new(0.0, 0.0, -1.0)
1051 }
1052
1053 /// Returns the magnitude (length) of the vector.
1054 ///
1055 /// # Returns
1056 ///
1057 /// - `f64` - The magnitude of the vector.
1058 pub fn magnitude(&self) -> f64 {
1059 (self.get_x() * self.get_x() + self.get_y() * self.get_y() + self.get_z() * self.get_z())
1060 .sqrt()
1061 }
1062
1063 /// Returns the squared magnitude of the vector.
1064 ///
1065 /// Avoids a square root, making it faster for comparison-only use cases.
1066 ///
1067 /// # Returns
1068 ///
1069 /// - `f64` - The squared magnitude of the vector.
1070 pub fn magnitude_squared(&self) -> f64 {
1071 self.get_x() * self.get_x() + self.get_y() * self.get_y() + self.get_z() * self.get_z()
1072 }
1073
1074 /// Returns a normalized (unit length) copy of this vector.
1075 ///
1076 /// Returns the zero vector if the magnitude is zero.
1077 ///
1078 /// # Returns
1079 ///
1080 /// - `Vector3D` - The normalized vector.
1081 pub fn normalized(&self) -> Vector3D {
1082 let mag: f64 = self.magnitude();
1083 if mag < EPSILON {
1084 return Vector3D::zero();
1085 }
1086 Vector3D::new(self.get_x() / mag, self.get_y() / mag, self.get_z() / mag)
1087 }
1088
1089 /// Normalizes this vector in place.
1090 pub fn normalize(&mut self) {
1091 let mag: f64 = self.magnitude();
1092 if mag < EPSILON {
1093 self.set_x(0.0);
1094 self.set_y(0.0);
1095 self.set_z(0.0);
1096 return;
1097 }
1098 self.set_x(self.get_x() / mag);
1099 self.set_y(self.get_y() / mag);
1100 self.set_z(self.get_z() / mag);
1101 }
1102
1103 /// Computes the dot product with another vector.
1104 ///
1105 /// # Arguments
1106 ///
1107 /// - `Vector3D` - The other vector.
1108 ///
1109 /// # Returns
1110 ///
1111 /// - `f64` - The dot product.
1112 pub fn dot(&self, other: Vector3D) -> f64 {
1113 self.get_x() * other.get_x() + self.get_y() * other.get_y() + self.get_z() * other.get_z()
1114 }
1115
1116 /// Computes the 3D cross product with another vector.
1117 ///
1118 /// # Arguments
1119 ///
1120 /// - `Vector3D` - The other vector.
1121 ///
1122 /// # Returns
1123 ///
1124 /// - `Vector3D` - The cross product vector.
1125 pub fn cross(&self, other: Vector3D) -> Vector3D {
1126 Vector3D::new(
1127 self.get_y() * other.get_z() - self.get_z() * other.get_y(),
1128 self.get_z() * other.get_x() - self.get_x() * other.get_z(),
1129 self.get_x() * other.get_y() - self.get_y() * other.get_x(),
1130 )
1131 }
1132
1133 /// Returns the distance from this point to another.
1134 ///
1135 /// # Arguments
1136 ///
1137 /// - `Vector3D` - The target point.
1138 ///
1139 /// # Returns
1140 ///
1141 /// - `f64` - The Euclidean distance.
1142 pub fn distance_to(&self, other: Vector3D) -> f64 {
1143 (other - *self).magnitude()
1144 }
1145
1146 /// Returns the squared distance from this point to another.
1147 ///
1148 /// # Arguments
1149 ///
1150 /// - `Vector3D` - The target point.
1151 ///
1152 /// # Returns
1153 ///
1154 /// - `f64` - The squared Euclidean distance.
1155 pub fn distance_squared_to(&self, other: Vector3D) -> f64 {
1156 (other - *self).magnitude_squared()
1157 }
1158
1159 /// Returns a unit vector pointing from this point to another.
1160 ///
1161 /// # Arguments
1162 ///
1163 /// - `Vector3D` - The target point.
1164 ///
1165 /// # Returns
1166 ///
1167 /// - `Vector3D` - The direction unit vector.
1168 pub fn direction_to(&self, other: Vector3D) -> Vector3D {
1169 (other - *self).normalized()
1170 }
1171
1172 /// Returns a linearly interpolated vector between this and another.
1173 ///
1174 /// # Arguments
1175 ///
1176 /// - `Vector3D` - The target vector.
1177 /// - `f64` - The interpolation factor.
1178 ///
1179 /// # Returns
1180 ///
1181 /// - `Vector3D` - The interpolated vector.
1182 pub fn lerp(&self, other: Vector3D, factor: f64) -> Vector3D {
1183 Vector3D::new(
1184 self.get_x() + (other.get_x() - self.get_x()) * factor,
1185 self.get_y() + (other.get_y() - self.get_y()) * factor,
1186 self.get_z() + (other.get_z() - self.get_z()) * factor,
1187 )
1188 }
1189
1190 /// Scales this vector by a scalar factor.
1191 ///
1192 /// # Arguments
1193 ///
1194 /// - `f64` - The scalar factor.
1195 pub fn scale(&mut self, scalar: f64) {
1196 self.set_x(self.get_x() * scalar);
1197 self.set_y(self.get_y() * scalar);
1198 self.set_z(self.get_z() * scalar);
1199 }
1200
1201 /// Returns a scaled copy of this vector.
1202 ///
1203 /// # Arguments
1204 ///
1205 /// - `f64` - The scalar factor.
1206 ///
1207 /// # Returns
1208 ///
1209 /// - `Vector3D` - The scaled vector.
1210 pub fn scaled(&self, scalar: f64) -> Vector3D {
1211 Vector3D::new(
1212 self.get_x() * scalar,
1213 self.get_y() * scalar,
1214 self.get_z() * scalar,
1215 )
1216 }
1217
1218 /// Rotates this vector by a quaternion.
1219 ///
1220 /// # Arguments
1221 ///
1222 /// - `Quaternion` - The rotation quaternion.
1223 ///
1224 /// # Returns
1225 ///
1226 /// - `Vector3D` - The rotated vector.
1227 pub fn rotated_by(&self, quaternion: Quaternion) -> Vector3D {
1228 let pure: Quaternion = Quaternion::new(self.get_x(), self.get_y(), self.get_z(), 0.0);
1229 let result: Quaternion = quaternion * pure * quaternion.conjugate();
1230 Vector3D::new(result.get_x(), result.get_y(), result.get_z())
1231 }
1232}
1233
1234/// Implements `Interpolable` for `Vector3D`.
1235impl Interpolable for Vector3D {
1236 fn lerp(&self, other: Vector3D, factor: f64) -> Vector3D {
1237 Vector3D::lerp(self, other, factor)
1238 }
1239}
1240
1241/// Implements vector addition.
1242impl Add for Vector3D {
1243 type Output = Vector3D;
1244 fn add(self, other: Vector3D) -> Vector3D {
1245 Vector3D::new(
1246 self.get_x() + other.get_x(),
1247 self.get_y() + other.get_y(),
1248 self.get_z() + other.get_z(),
1249 )
1250 }
1251}
1252
1253/// Implements vector subtraction.
1254impl Sub for Vector3D {
1255 type Output = Vector3D;
1256 fn sub(self, other: Vector3D) -> Vector3D {
1257 Vector3D::new(
1258 self.get_x() - other.get_x(),
1259 self.get_y() - other.get_y(),
1260 self.get_z() - other.get_z(),
1261 )
1262 }
1263}
1264
1265/// Implements scalar multiplication.
1266impl Mul<f64> for Vector3D {
1267 type Output = Vector3D;
1268 fn mul(self, scalar: f64) -> Vector3D {
1269 Vector3D::new(
1270 self.get_x() * scalar,
1271 self.get_y() * scalar,
1272 self.get_z() * scalar,
1273 )
1274 }
1275}
1276
1277/// Implements vector negation.
1278impl Neg for Vector3D {
1279 type Output = Vector3D;
1280 fn neg(self) -> Vector3D {
1281 Vector3D::new(-self.get_x(), -self.get_y(), -self.get_z())
1282 }
1283}
1284
1285/// Implements in-place vector addition.
1286impl AddAssign for Vector3D {
1287 fn add_assign(&mut self, other: Vector3D) {
1288 self.set_x(self.get_x() + other.get_x());
1289 self.set_y(self.get_y() + other.get_y());
1290 self.set_z(self.get_z() + other.get_z());
1291 }
1292}
1293
1294/// Implements in-place vector subtraction.
1295impl SubAssign for Vector3D {
1296 fn sub_assign(&mut self, other: Vector3D) {
1297 self.set_x(self.get_x() - other.get_x());
1298 self.set_y(self.get_y() - other.get_y());
1299 self.set_z(self.get_z() - other.get_z());
1300 }
1301}
1302
1303/// Implements in-place scalar multiplication.
1304impl MulAssign<f64> for Vector3D {
1305 fn mul_assign(&mut self, scalar: f64) {
1306 self.set_x(self.get_x() * scalar);
1307 self.set_y(self.get_y() * scalar);
1308 self.set_z(self.get_z() * scalar);
1309 }
1310}
1311
1312/// Implements quaternion operations for `Quaternion`.
1313impl Quaternion {
1314 /// Returns the identity quaternion (0, 0, 0, 1) representing no rotation.
1315 ///
1316 /// # Returns
1317 ///
1318 /// - `Quaternion` - The identity quaternion.
1319 pub fn identity() -> Quaternion {
1320 Quaternion::new(0.0, 0.0, 0.0, 1.0)
1321 }
1322
1323 /// Creates a quaternion from a rotation around an axis.
1324 ///
1325 /// # Arguments
1326 ///
1327 /// - `Vector3D` - The rotation axis (should be normalized).
1328 /// - `f64` - The rotation angle in radians.
1329 ///
1330 /// # Returns
1331 ///
1332 /// - `Quaternion` - The rotation quaternion.
1333 pub fn from_axis_angle(axis: Vector3D, angle: f64) -> Quaternion {
1334 let half: f64 = angle * 0.5;
1335 let sin_half: f64 = half.sin();
1336 let cos_half: f64 = half.cos();
1337 let normalized_axis: Vector3D = axis.normalized();
1338 Quaternion::new(
1339 normalized_axis.get_x() * sin_half,
1340 normalized_axis.get_y() * sin_half,
1341 normalized_axis.get_z() * sin_half,
1342 cos_half,
1343 )
1344 }
1345
1346 /// Creates a quaternion from Euler angles (yaw, pitch, roll) in radians.
1347 ///
1348 /// # Arguments
1349 ///
1350 /// - `f64` - The yaw (rotation around y axis) in radians.
1351 /// - `f64` - The pitch (rotation around x axis) in radians.
1352 /// - `f64` - The roll (rotation around z axis) in radians.
1353 ///
1354 /// # Returns
1355 ///
1356 /// - `Quaternion` - The rotation quaternion.
1357 pub fn from_euler(yaw: f64, pitch: f64, roll: f64) -> Quaternion {
1358 let half_yaw: f64 = yaw * 0.5;
1359 let half_pitch: f64 = pitch * 0.5;
1360 let half_roll: f64 = roll * 0.5;
1361 let cy: f64 = half_yaw.cos();
1362 let sy: f64 = half_yaw.sin();
1363 let cp: f64 = half_pitch.cos();
1364 let sp: f64 = half_pitch.sin();
1365 let cr: f64 = half_roll.cos();
1366 let sr: f64 = half_roll.sin();
1367 Quaternion::new(
1368 sp * cy * cr + cp * sy * sr,
1369 cp * sy * cr - sp * cy * sr,
1370 cp * cy * sr - sp * sy * cr,
1371 sp * sy * sr + cp * cy * cr,
1372 )
1373 }
1374
1375 /// Returns the magnitude of the quaternion.
1376 ///
1377 /// # Returns
1378 ///
1379 /// - `f64` - The magnitude.
1380 pub fn magnitude(&self) -> f64 {
1381 (self.get_x() * self.get_x()
1382 + self.get_y() * self.get_y()
1383 + self.get_z() * self.get_z()
1384 + self.get_w() * self.get_w())
1385 .sqrt()
1386 }
1387
1388 /// Returns a normalized copy of this quaternion.
1389 ///
1390 /// # Returns
1391 ///
1392 /// - `Quaternion` - The normalized quaternion.
1393 pub fn normalized(&self) -> Quaternion {
1394 let mag: f64 = self.magnitude();
1395 if mag < EPSILON {
1396 return Quaternion::identity();
1397 }
1398 let inv: f64 = 1.0 / mag;
1399 Quaternion::new(
1400 self.get_x() * inv,
1401 self.get_y() * inv,
1402 self.get_z() * inv,
1403 self.get_w() * inv,
1404 )
1405 }
1406
1407 /// Returns the conjugate of this quaternion.
1408 ///
1409 /// # Returns
1410 ///
1411 /// - `Quaternion` - The conjugate quaternion.
1412 pub fn conjugate(&self) -> Quaternion {
1413 Quaternion::new(-self.get_x(), -self.get_y(), -self.get_z(), self.get_w())
1414 }
1415
1416 /// Computes the dot product with another quaternion.
1417 ///
1418 /// # Arguments
1419 ///
1420 /// - `Quaternion` - The other quaternion.
1421 ///
1422 /// # Returns
1423 ///
1424 /// - `f64` - The dot product.
1425 pub fn dot(&self, other: Quaternion) -> f64 {
1426 self.get_x() * other.get_x()
1427 + self.get_y() * other.get_y()
1428 + self.get_z() * other.get_z()
1429 + self.get_w() * other.get_w()
1430 }
1431
1432 /// Performs spherical linear interpolation between this and another quaternion.
1433 ///
1434 /// # Arguments
1435 ///
1436 /// - `Quaternion` - The target quaternion.
1437 /// - `f64` - The interpolation factor in the range 0.0 to 1.0.
1438 ///
1439 /// # Returns
1440 ///
1441 /// - `Quaternion` - The interpolated quaternion.
1442 pub fn slerp(&self, other: Quaternion, factor: f64) -> Quaternion {
1443 let mut cos_theta: f64 = self.dot(other);
1444 let target: Quaternion = if cos_theta < 0.0 {
1445 cos_theta = -cos_theta;
1446 Quaternion::new(
1447 -other.get_x(),
1448 -other.get_y(),
1449 -other.get_z(),
1450 -other.get_w(),
1451 )
1452 } else {
1453 other
1454 };
1455 if cos_theta > 1.0 - EPSILON {
1456 return Quaternion::new(
1457 self.get_x() + (target.get_x() - self.get_x()) * factor,
1458 self.get_y() + (target.get_y() - self.get_y()) * factor,
1459 self.get_z() + (target.get_z() - self.get_z()) * factor,
1460 self.get_w() + (target.get_w() - self.get_w()) * factor,
1461 )
1462 .normalized();
1463 }
1464 let theta: f64 = cos_theta.acos();
1465 let sin_theta: f64 = theta.sin();
1466 let factor_a: f64 = ((1.0 - factor) * theta).sin() / sin_theta;
1467 let factor_b: f64 = (factor * theta).sin() / sin_theta;
1468 Quaternion::new(
1469 self.get_x() * factor_a + target.get_x() * factor_b,
1470 self.get_y() * factor_a + target.get_y() * factor_b,
1471 self.get_z() * factor_a + target.get_z() * factor_b,
1472 self.get_w() * factor_a + target.get_w() * factor_b,
1473 )
1474 }
1475}
1476
1477/// Implements quaternion multiplication.
1478impl Mul for Quaternion {
1479 type Output = Quaternion;
1480 fn mul(self, other: Quaternion) -> Quaternion {
1481 Quaternion::new(
1482 self.get_w() * other.get_x()
1483 + self.get_x() * other.get_w()
1484 + self.get_y() * other.get_z()
1485 - self.get_z() * other.get_y(),
1486 self.get_w() * other.get_y() - self.get_x() * other.get_z()
1487 + self.get_y() * other.get_w()
1488 + self.get_z() * other.get_x(),
1489 self.get_w() * other.get_z() + self.get_x() * other.get_y()
1490 - self.get_y() * other.get_x()
1491 + self.get_z() * other.get_w(),
1492 self.get_w() * other.get_w()
1493 - self.get_x() * other.get_x()
1494 - self.get_y() * other.get_y()
1495 - self.get_z() * other.get_z(),
1496 )
1497 }
1498}
1499
1500/// Implements matrix operations for `Matrix4x4`.
1501impl Matrix4x4 {
1502 /// Returns the identity matrix.
1503 ///
1504 /// # Returns
1505 ///
1506 /// - `Matrix4x4` - The identity matrix.
1507 pub fn identity() -> Matrix4x4 {
1508 Matrix4x4::new([
1509 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,
1510 ])
1511 }
1512
1513 /// Creates a translation matrix.
1514 ///
1515 /// # Arguments
1516 ///
1517 /// - `Vector3D` - The translation vector.
1518 ///
1519 /// # Returns
1520 ///
1521 /// - `Matrix4x4` - The translation matrix.
1522 pub fn translation(translation: Vector3D) -> Matrix4x4 {
1523 let mut elements: [f64; 16] = Self::identity().get_elements();
1524 elements[12] = translation.get_x();
1525 elements[13] = translation.get_y();
1526 elements[14] = translation.get_z();
1527 Matrix4x4::new(elements)
1528 }
1529
1530 /// Creates a scaling matrix.
1531 ///
1532 /// # Arguments
1533 ///
1534 /// - `Vector3D` - The scale factors.
1535 ///
1536 /// # Returns
1537 ///
1538 /// - `Matrix4x4` - The scaling matrix.
1539 pub fn scaling(scale: Vector3D) -> Matrix4x4 {
1540 Matrix4x4::new([
1541 scale.get_x(),
1542 0.0,
1543 0.0,
1544 0.0,
1545 0.0,
1546 scale.get_y(),
1547 0.0,
1548 0.0,
1549 0.0,
1550 0.0,
1551 scale.get_z(),
1552 0.0,
1553 0.0,
1554 0.0,
1555 0.0,
1556 1.0,
1557 ])
1558 }
1559
1560 /// Creates a rotation matrix from a quaternion.
1561 ///
1562 /// # Arguments
1563 ///
1564 /// - `Quaternion` - The rotation quaternion.
1565 ///
1566 /// # Returns
1567 ///
1568 /// - `Matrix4x4` - The rotation matrix.
1569 pub fn rotation(quaternion: Quaternion) -> Matrix4x4 {
1570 let xx: f64 = quaternion.get_x() * quaternion.get_x();
1571 let yy: f64 = quaternion.get_y() * quaternion.get_y();
1572 let zz: f64 = quaternion.get_z() * quaternion.get_z();
1573 let xy: f64 = quaternion.get_x() * quaternion.get_y();
1574 let xz: f64 = quaternion.get_x() * quaternion.get_z();
1575 let yz: f64 = quaternion.get_y() * quaternion.get_z();
1576 let wx: f64 = quaternion.get_w() * quaternion.get_x();
1577 let wy: f64 = quaternion.get_w() * quaternion.get_y();
1578 let wz: f64 = quaternion.get_w() * quaternion.get_z();
1579 Matrix4x4::new([
1580 1.0 - 2.0 * (yy + zz),
1581 2.0 * (xy + wz),
1582 2.0 * (xz - wy),
1583 0.0,
1584 2.0 * (xy - wz),
1585 1.0 - 2.0 * (xx + zz),
1586 2.0 * (yz + wx),
1587 0.0,
1588 2.0 * (xz + wy),
1589 2.0 * (yz - wx),
1590 1.0 - 2.0 * (xx + yy),
1591 0.0,
1592 0.0,
1593 0.0,
1594 0.0,
1595 1.0,
1596 ])
1597 }
1598
1599 /// Creates a perspective projection matrix.
1600 ///
1601 /// # Arguments
1602 ///
1603 /// - `f64` - The vertical field of view in radians.
1604 /// - `f64` - The aspect ratio (width / height).
1605 /// - `f64` - The near clipping plane distance.
1606 /// - `f64` - The far clipping plane distance.
1607 ///
1608 /// # Returns
1609 ///
1610 /// - `Matrix4x4` - The perspective projection matrix.
1611 pub fn perspective(fov: f64, aspect: f64, near: f64, far: f64) -> Matrix4x4 {
1612 let f: f64 = 1.0 / (fov * 0.5).tan();
1613 let range: f64 = far - near;
1614 Matrix4x4::new([
1615 f / aspect,
1616 0.0,
1617 0.0,
1618 0.0,
1619 0.0,
1620 f,
1621 0.0,
1622 0.0,
1623 0.0,
1624 0.0,
1625 -(far + near) / range,
1626 -1.0,
1627 0.0,
1628 0.0,
1629 -(2.0 * far * near) / range,
1630 0.0,
1631 ])
1632 }
1633
1634 /// Creates an orthographic projection matrix.
1635 ///
1636 /// # Arguments
1637 ///
1638 /// - `f64` - The left boundary.
1639 /// - `f64` - The right boundary.
1640 /// - `f64` - The bottom boundary.
1641 /// - `f64` - The top boundary.
1642 /// - `f64` - The near clipping plane distance.
1643 /// - `f64` - The far clipping plane distance.
1644 ///
1645 /// # Returns
1646 ///
1647 /// - `Matrix4x4` - The orthographic projection matrix.
1648 pub fn orthographic(
1649 left: f64,
1650 right: f64,
1651 bottom: f64,
1652 top: f64,
1653 near: f64,
1654 far: f64,
1655 ) -> Matrix4x4 {
1656 let rml: f64 = right - left;
1657 let tmb: f64 = top - bottom;
1658 let fmn: f64 = far - near;
1659 Matrix4x4::new([
1660 2.0 / rml,
1661 0.0,
1662 0.0,
1663 0.0,
1664 0.0,
1665 2.0 / tmb,
1666 0.0,
1667 0.0,
1668 0.0,
1669 0.0,
1670 -2.0 / fmn,
1671 0.0,
1672 -(right + left) / rml,
1673 -(top + bottom) / tmb,
1674 -(far + near) / fmn,
1675 1.0,
1676 ])
1677 }
1678
1679 /// Creates a view matrix using the "look at" convention.
1680 ///
1681 /// # Arguments
1682 ///
1683 /// - `Vector3D` - The eye position.
1684 /// - `Vector3D` - The target position to look at.
1685 /// - `Vector3D` - The up direction.
1686 ///
1687 /// # Returns
1688 ///
1689 /// - `Matrix4x4` - The view matrix.
1690 pub fn look_at(eye: Vector3D, target: Vector3D, up: Vector3D) -> Matrix4x4 {
1691 let forward: Vector3D = (target - eye).normalized();
1692 let right: Vector3D = forward.cross(up).normalized();
1693 let up_orthogonal: Vector3D = right.cross(forward);
1694 Matrix4x4::new([
1695 right.get_x(),
1696 up_orthogonal.get_x(),
1697 -forward.get_x(),
1698 0.0,
1699 right.get_y(),
1700 up_orthogonal.get_y(),
1701 -forward.get_y(),
1702 0.0,
1703 right.get_z(),
1704 up_orthogonal.get_z(),
1705 -forward.get_z(),
1706 0.0,
1707 -right.dot(eye),
1708 -up_orthogonal.dot(eye),
1709 forward.dot(eye),
1710 1.0,
1711 ])
1712 }
1713
1714 /// Multiplies this matrix by another using fully unrolled arithmetic.
1715 ///
1716 /// Eliminates all loop overhead and allows the compiler to maximize
1717 /// register allocation and instruction-level parallelism.
1718 ///
1719 /// # Arguments
1720 ///
1721 /// - `Matrix4x4` - The other matrix.
1722 ///
1723 /// # Returns
1724 ///
1725 /// - `Matrix4x4` - The product matrix.
1726 pub fn multiply(&self, other: Matrix4x4) -> Matrix4x4 {
1727 let a: [f64; 16] = self.get_elements();
1728 let b: [f64; 16] = other.get_elements();
1729 Matrix4x4::new([
1730 a[0] * b[0] + a[4] * b[1] + a[8] * b[2] + a[12] * b[3],
1731 a[1] * b[0] + a[5] * b[1] + a[9] * b[2] + a[13] * b[3],
1732 a[2] * b[0] + a[6] * b[1] + a[10] * b[2] + a[14] * b[3],
1733 a[3] * b[0] + a[7] * b[1] + a[11] * b[2] + a[15] * b[3],
1734 a[0] * b[4] + a[4] * b[5] + a[8] * b[6] + a[12] * b[7],
1735 a[1] * b[4] + a[5] * b[5] + a[9] * b[6] + a[13] * b[7],
1736 a[2] * b[4] + a[6] * b[5] + a[10] * b[6] + a[14] * b[7],
1737 a[3] * b[4] + a[7] * b[5] + a[11] * b[6] + a[15] * b[7],
1738 a[0] * b[8] + a[4] * b[9] + a[8] * b[10] + a[12] * b[11],
1739 a[1] * b[8] + a[5] * b[9] + a[9] * b[10] + a[13] * b[11],
1740 a[2] * b[8] + a[6] * b[9] + a[10] * b[10] + a[14] * b[11],
1741 a[3] * b[8] + a[7] * b[9] + a[11] * b[10] + a[15] * b[11],
1742 a[0] * b[12] + a[4] * b[13] + a[8] * b[14] + a[12] * b[15],
1743 a[1] * b[12] + a[5] * b[13] + a[9] * b[14] + a[13] * b[15],
1744 a[2] * b[12] + a[6] * b[13] + a[10] * b[14] + a[14] * b[15],
1745 a[3] * b[12] + a[7] * b[13] + a[11] * b[14] + a[15] * b[15],
1746 ])
1747 }
1748
1749 /// Transforms a 3D point by this matrix, applying the perspective divide.
1750 ///
1751 /// # Arguments
1752 ///
1753 /// - `Vector3D` - The point to transform.
1754 ///
1755 /// # Returns
1756 ///
1757 /// - `Vector3D` - The transformed point.
1758 pub fn transform_point(&self, point: Vector3D) -> Vector3D {
1759 let elements: [f64; 16] = self.get_elements();
1760 let x: f64 = elements[0] * point.get_x()
1761 + elements[4] * point.get_y()
1762 + elements[8] * point.get_z()
1763 + elements[12];
1764 let y: f64 = elements[1] * point.get_x()
1765 + elements[5] * point.get_y()
1766 + elements[9] * point.get_z()
1767 + elements[13];
1768 let z: f64 = elements[2] * point.get_x()
1769 + elements[6] * point.get_y()
1770 + elements[10] * point.get_z()
1771 + elements[14];
1772 let w: f64 = elements[3] * point.get_x()
1773 + elements[7] * point.get_y()
1774 + elements[11] * point.get_z()
1775 + elements[15];
1776 if w.abs() < EPSILON {
1777 return Vector3D::new(x, y, z);
1778 }
1779 Vector3D::new(x / w, y / w, z / w)
1780 }
1781}
1782
1783/// Implements `Default` for `Quaternion` as the identity quaternion.
1784impl Default for Quaternion {
1785 fn default() -> Quaternion {
1786 Quaternion::identity()
1787 }
1788}
1789
1790/// Implements `Default` for `Matrix4x4` as the identity matrix.
1791impl Default for Matrix4x4 {
1792 fn default() -> Matrix4x4 {
1793 Matrix4x4::identity()
1794 }
1795}
1796
1797/// Implements methods for `Transform3D`.
1798impl Transform3D {
1799 /// Creates a new transform at the origin with no rotation and unit scale.
1800 ///
1801 /// # Returns
1802 ///
1803 /// - `Transform3D` - The identity transform.
1804 pub fn identity() -> Transform3D {
1805 Transform3D::new(
1806 Vector3D::zero(),
1807 Quaternion::identity(),
1808 Vector3D::new(1.0, 1.0, 1.0),
1809 )
1810 }
1811
1812 /// Translates the position by the given offset.
1813 ///
1814 /// # Arguments
1815 ///
1816 /// - `Vector3D` - The translation offset.
1817 pub fn translate(&mut self, offset: Vector3D) {
1818 self.set_position(self.get_position() + offset);
1819 }
1820
1821 /// Rotates by the given quaternion (post-multiplies).
1822 ///
1823 /// # Arguments
1824 ///
1825 /// - `Quaternion` - The rotation to apply.
1826 pub fn rotate(&mut self, rotation: Quaternion) {
1827 self.set_rotation(rotation * self.get_rotation());
1828 }
1829
1830 /// Scales by the given factors.
1831 ///
1832 /// # Arguments
1833 ///
1834 /// - `Vector3D` - The scale factors.
1835 pub fn scale_by(&mut self, factors: Vector3D) {
1836 let mut scale: Vector3D = self.get_scale();
1837 scale.set_x(scale.get_x() * factors.get_x());
1838 scale.set_y(scale.get_y() * factors.get_y());
1839 scale.set_z(scale.get_z() * factors.get_z());
1840 self.set_scale(scale);
1841 }
1842
1843 /// Applies this transform to a local-space point, returning world-space coordinates.
1844 ///
1845 /// # Arguments
1846 ///
1847 /// - `Vector3D` - The local-space point.
1848 ///
1849 /// # Returns
1850 ///
1851 /// - `Vector3D` - The transformed world-space point.
1852 pub fn apply_to_point(&self, point: Vector3D) -> Vector3D {
1853 let scale: Vector3D = self.get_scale();
1854 let scaled: Vector3D = Vector3D::new(
1855 point.get_x() * scale.get_x(),
1856 point.get_y() * scale.get_y(),
1857 point.get_z() * scale.get_z(),
1858 );
1859 scaled.rotated_by(self.get_rotation()) + self.get_position()
1860 }
1861
1862 /// Converts this transform to a `Matrix4x4`.
1863 ///
1864 /// # Returns
1865 ///
1866 /// - `Matrix4x4` - The composed transformation matrix.
1867 pub fn to_matrix(&self) -> Matrix4x4 {
1868 let translation: Matrix4x4 = Matrix4x4::translation(self.get_position());
1869 let rotation: Matrix4x4 = Matrix4x4::rotation(self.get_rotation());
1870 let scaling: Matrix4x4 = Matrix4x4::scaling(self.get_scale());
1871 translation.multiply(rotation).multiply(scaling)
1872 }
1873}
1874
1875/// Implements `Default` for `Transform3D` as the identity transform.
1876impl Default for Transform3D {
1877 fn default() -> Transform3D {
1878 Transform3D::identity()
1879 }
1880}
1881
1882/// Implements methods for `AABB3D`.
1883impl AABB3D {
1884 /// Creates an AABB from a center point and dimensions.
1885 ///
1886 /// # Arguments
1887 ///
1888 /// - `Vector3D` - The center point.
1889 /// - `f64` - The width.
1890 /// - `f64` - The height.
1891 /// - `f64` - The depth.
1892 ///
1893 /// # Returns
1894 ///
1895 /// - `AABB3D` - The new bounding box.
1896 pub fn from_center(center: Vector3D, width: f64, height: f64, depth: f64) -> AABB3D {
1897 AABB3D::new(
1898 Vector3D::new(
1899 center.get_x() - width * 0.5,
1900 center.get_y() - height * 0.5,
1901 center.get_z() - depth * 0.5,
1902 ),
1903 Vector3D::new(
1904 center.get_x() + width * 0.5,
1905 center.get_y() + height * 0.5,
1906 center.get_z() + depth * 0.5,
1907 ),
1908 )
1909 }
1910
1911 /// Returns the center point of the bounding box.
1912 ///
1913 /// # Returns
1914 ///
1915 /// - `Vector3D` - The center point.
1916 pub fn center(&self) -> Vector3D {
1917 Vector3D::new(
1918 (self.get_min().get_x() + self.get_max().get_x()) * 0.5,
1919 (self.get_min().get_y() + self.get_max().get_y()) * 0.5,
1920 (self.get_min().get_z() + self.get_max().get_z()) * 0.5,
1921 )
1922 }
1923
1924 /// Returns the dimensions of the bounding box as a vector.
1925 ///
1926 /// # Returns
1927 ///
1928 /// - `Vector3D` - The size vector (width, height, depth).
1929 pub fn size(&self) -> Vector3D {
1930 Vector3D::new(
1931 self.get_max().get_x() - self.get_min().get_x(),
1932 self.get_max().get_y() - self.get_min().get_y(),
1933 self.get_max().get_z() - self.get_min().get_z(),
1934 )
1935 }
1936
1937 /// Tests whether a point is inside this bounding box.
1938 ///
1939 /// # Arguments
1940 ///
1941 /// - `Vector3D` - The point to test.
1942 ///
1943 /// # Returns
1944 ///
1945 /// - `bool` - True if the point is inside.
1946 pub fn contains(&self, point: Vector3D) -> bool {
1947 point.get_x() >= self.get_min().get_x()
1948 && point.get_x() <= self.get_max().get_x()
1949 && point.get_y() >= self.get_min().get_y()
1950 && point.get_y() <= self.get_max().get_y()
1951 && point.get_z() >= self.get_min().get_z()
1952 && point.get_z() <= self.get_max().get_z()
1953 }
1954
1955 /// Tests whether this bounding box intersects another.
1956 ///
1957 /// # Arguments
1958 ///
1959 /// - `AABB3D` - The other bounding box.
1960 ///
1961 /// # Returns
1962 ///
1963 /// - `bool` - True if they intersect.
1964 pub fn intersects(&self, other: AABB3D) -> bool {
1965 self.get_min().get_x() <= other.get_max().get_x()
1966 && self.get_max().get_x() >= other.get_min().get_x()
1967 && self.get_min().get_y() <= other.get_max().get_y()
1968 && self.get_max().get_y() >= other.get_min().get_y()
1969 && self.get_min().get_z() <= other.get_max().get_z()
1970 && self.get_max().get_z() >= other.get_min().get_z()
1971 }
1972}
1973
1974/// Implements methods for `Sphere`.
1975impl Sphere {
1976 /// Tests whether a point is inside this sphere.
1977 ///
1978 /// # Arguments
1979 ///
1980 /// - `Vector3D` - The point to test.
1981 ///
1982 /// # Returns
1983 ///
1984 /// - `bool` - True if the point is inside.
1985 pub fn contains(&self, point: Vector3D) -> bool {
1986 self.get_center().distance_squared_to(point) <= self.get_radius() * self.get_radius()
1987 }
1988
1989 /// Tests whether this sphere intersects another.
1990 ///
1991 /// # Arguments
1992 ///
1993 /// - `Sphere` - The other sphere.
1994 ///
1995 /// # Returns
1996 ///
1997 /// - `bool` - True if they intersect.
1998 pub fn intersects(&self, other: Sphere) -> bool {
1999 let distance_sq: f64 = self.get_center().distance_squared_to(other.get_center());
2000 let radius_sum: f64 = self.get_radius() + other.get_radius();
2001 distance_sq <= radius_sum * radius_sum
2002 }
2003
2004 /// Returns the volume of the sphere.
2005 ///
2006 /// # Returns
2007 ///
2008 /// - `f64` - The volume.
2009 pub fn volume(&self) -> f64 {
2010 (4.0 / 3.0) * PI * self.get_radius() * self.get_radius() * self.get_radius()
2011 }
2012
2013 /// Returns the surface area of the sphere.
2014 ///
2015 /// # Returns
2016 ///
2017 /// - `f64` - The surface area.
2018 pub fn surface_area(&self) -> f64 {
2019 4.0 * PI * self.get_radius() * self.get_radius()
2020 }
2021}
2022
2023/// Implements methods for `Plane`.
2024impl Plane {
2025 /// Creates a plane from a normal and a point on the plane.
2026 ///
2027 /// # Arguments
2028 ///
2029 /// - `Vector3D` - The normal vector.
2030 /// - `Vector3D` - A point on the plane.
2031 ///
2032 /// # Returns
2033 ///
2034 /// - `Plane` - The new plane.
2035 pub fn from_normal_and_point(normal: Vector3D, point: Vector3D) -> Plane {
2036 let normalized_normal: Vector3D = normal.normalized();
2037 Plane::new(normalized_normal, -normalized_normal.dot(point))
2038 }
2039
2040 /// Returns the signed distance from a point to this plane.
2041 ///
2042 /// # Arguments
2043 ///
2044 /// - `Vector3D` - The point to test.
2045 ///
2046 /// # Returns
2047 ///
2048 /// - `f64` - The signed distance (positive on the normal side).
2049 pub fn distance_to_point(&self, point: Vector3D) -> f64 {
2050 self.get_normal().dot(point) + self.get_distance()
2051 }
2052
2053 /// Normalizes the plane normal and adjusts the distance accordingly.
2054 pub fn normalize(&mut self) {
2055 let mut normal: Vector3D = self.get_normal();
2056 let mag: f64 = normal.magnitude();
2057 if mag < EPSILON {
2058 return;
2059 }
2060 normal.set_x(normal.get_x() / mag);
2061 normal.set_y(normal.get_y() / mag);
2062 normal.set_z(normal.get_z() / mag);
2063 self.set_normal(normal);
2064 self.set_distance(self.get_distance() / mag);
2065 }
2066}
2067
2068/// Implements methods for `Ray3D`.
2069impl Ray3D {
2070 /// Returns the point on the ray at the given parameter value.
2071 ///
2072 /// # Arguments
2073 ///
2074 /// - `f64` - The parameter value (distance along the ray).
2075 ///
2076 /// # Returns
2077 ///
2078 /// - `Vector3D` - The point at the given distance.
2079 pub fn point_at(&self, t: f64) -> Vector3D {
2080 self.get_origin() + self.get_direction().scaled(t)
2081 }
2082
2083 /// Tests for intersection with a sphere, returning the nearest distance if hit.
2084 ///
2085 /// # Arguments
2086 ///
2087 /// - `Sphere` - The sphere to test.
2088 ///
2089 /// # Returns
2090 ///
2091 /// - `Option<f64>` - The distance to the intersection, or `None`.
2092 pub fn intersect_sphere(&self, sphere: Sphere) -> Option<f64> {
2093 let oc: Vector3D = self.get_origin() - sphere.get_center();
2094 let direction: Vector3D = self.get_direction();
2095 let a: f64 = direction.dot(direction);
2096 let b: f64 = 2.0 * oc.dot(direction);
2097 let c: f64 = oc.dot(oc) - sphere.get_radius() * sphere.get_radius();
2098 let discriminant: f64 = b * b - 4.0 * a * c;
2099 if discriminant < 0.0 {
2100 return None;
2101 }
2102 let sqrt_d: f64 = discriminant.sqrt();
2103 let t1: f64 = (-b - sqrt_d) / (2.0 * a);
2104 if t1 >= 0.0 {
2105 return Some(t1);
2106 }
2107 let t2: f64 = (-b + sqrt_d) / (2.0 * a);
2108 if t2 >= 0.0 {
2109 return Some(t2);
2110 }
2111 None
2112 }
2113
2114 /// Tests for intersection with a plane, returning the distance if hit.
2115 ///
2116 /// # Arguments
2117 ///
2118 /// - `Plane` - The plane to test.
2119 ///
2120 /// # Returns
2121 ///
2122 /// - `Option<f64>` - The distance to the intersection, or `None`.
2123 pub fn intersect_plane(&self, plane: Plane) -> Option<f64> {
2124 let direction: Vector3D = self.get_direction();
2125 let normal: Vector3D = plane.get_normal();
2126 let denom: f64 = direction.dot(normal);
2127 if denom.abs() < EPSILON {
2128 return None;
2129 }
2130 let t: f64 = -(normal.dot(self.get_origin()) + plane.get_distance()) / denom;
2131 if t >= 0.0 { Some(t) } else { None }
2132 }
2133
2134 /// Tests for intersection with an AABB, returning the nearest distance if hit.
2135 ///
2136 /// # Arguments
2137 ///
2138 /// - `AABB3D` - The bounding box to test.
2139 ///
2140 /// # Returns
2141 ///
2142 /// - `Option<f64>` - The distance to the intersection, or `None`.
2143 pub fn intersect_aabb(&self, aabb: AABB3D) -> Option<f64> {
2144 let mut t_min: f64 = f64::MIN;
2145 let mut t_max: f64 = f64::MAX;
2146 let direction: Vector3D = self.get_direction();
2147 let origin: Vector3D = self.get_origin();
2148 let aabb_min: Vector3D = aabb.get_min();
2149 let aabb_max: Vector3D = aabb.get_max();
2150 for axis in 0..3usize {
2151 let (dir_component, origin_component, min_component, max_component) = match axis {
2152 0 => (
2153 direction.get_x(),
2154 origin.get_x(),
2155 aabb_min.get_x(),
2156 aabb_max.get_x(),
2157 ),
2158 1 => (
2159 direction.get_y(),
2160 origin.get_y(),
2161 aabb_min.get_y(),
2162 aabb_max.get_y(),
2163 ),
2164 _ => (
2165 direction.get_z(),
2166 origin.get_z(),
2167 aabb_min.get_z(),
2168 aabb_max.get_z(),
2169 ),
2170 };
2171 if dir_component.abs() < EPSILON {
2172 if origin_component < min_component || origin_component > max_component {
2173 return None;
2174 }
2175 } else {
2176 let inv_dir: f64 = 1.0 / dir_component;
2177 let t1: f64 = (min_component - origin_component) * inv_dir;
2178 let t2: f64 = (max_component - origin_component) * inv_dir;
2179 let t_near: f64 = t1.min(t2);
2180 let t_far: f64 = t1.max(t2);
2181 t_min = t_min.max(t_near);
2182 t_max = t_max.min(t_far);
2183 if t_min > t_max {
2184 return None;
2185 }
2186 }
2187 }
2188 if t_min >= 0.0 {
2189 Some(t_min)
2190 } else if t_max >= 0.0 {
2191 Some(t_max)
2192 } else {
2193 None
2194 }
2195 }
2196}