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