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