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