1use core::f32::consts::{FRAC_PI_3, PI};
2
3use super::{Circle, Measured2d, Measured3d, Primitive2d, Primitive3d};
4use crate::{
5 ops::{self, FloatPow},
6 Dir3, InvalidDirectionError, Isometry3d, Mat3, Ray3d, Vec2, Vec3,
7};
8
9#[cfg(feature = "bevy_reflect")]
10use bevy_reflect::{std_traits::ReflectDefault, Reflect};
11#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
12use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
13use glam::Quat;
14
15#[cfg(feature = "alloc")]
16use alloc::vec::Vec;
17
18#[derive(Clone, Copy, Debug, PartialEq)]
20#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
21#[cfg_attr(
22 feature = "bevy_reflect",
23 derive(Reflect),
24 reflect(Debug, PartialEq, Default, Clone)
25)]
26#[cfg_attr(
27 all(feature = "serialize", feature = "bevy_reflect"),
28 reflect(Serialize, Deserialize)
29)]
30pub struct Sphere {
31 pub radius: f32,
33}
34
35impl Primitive3d for Sphere {}
36
37impl Default for Sphere {
38 fn default() -> Self {
40 Self { radius: 0.5 }
41 }
42}
43
44impl Sphere {
45 #[inline]
47 pub const fn new(radius: f32) -> Self {
48 Self { radius }
49 }
50
51 #[inline]
53 pub const fn diameter(&self) -> f32 {
54 2.0 * self.radius
55 }
56
57 #[inline]
62 pub fn closest_point(&self, point: Vec3) -> Vec3 {
63 let distance_squared = point.length_squared();
64
65 if distance_squared <= self.radius.squared() {
66 point
68 } else {
69 let dir_to_point = point / ops::sqrt(distance_squared);
72 self.radius * dir_to_point
73 }
74 }
75}
76
77impl Measured3d for Sphere {
78 #[inline]
80 fn area(&self) -> f32 {
81 4.0 * PI * self.radius.squared()
82 }
83
84 #[inline]
86 fn volume(&self) -> f32 {
87 4.0 * FRAC_PI_3 * self.radius.cubed()
88 }
89}
90
91#[derive(Clone, Copy, Debug, PartialEq)]
93#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
94#[cfg_attr(
95 feature = "bevy_reflect",
96 derive(Reflect),
97 reflect(Debug, PartialEq, Default, Clone)
98)]
99#[cfg_attr(
100 all(feature = "serialize", feature = "bevy_reflect"),
101 reflect(Serialize, Deserialize)
102)]
103pub struct Plane3d {
104 pub normal: Dir3,
106 pub half_size: Vec2,
108}
109
110impl Primitive3d for Plane3d {}
111
112impl Default for Plane3d {
113 fn default() -> Self {
115 Self {
116 normal: Dir3::Y,
117 half_size: Vec2::splat(0.5),
118 }
119 }
120}
121
122impl Plane3d {
123 #[inline]
129 pub fn new(normal: Vec3, half_size: Vec2) -> Self {
130 Self {
131 normal: Dir3::new(normal).expect("normal must be nonzero and finite"),
132 half_size,
133 }
134 }
135
136 #[inline]
147 pub fn from_points(a: Vec3, b: Vec3, c: Vec3) -> (Self, Vec3) {
148 let normal = Dir3::new((b - a).cross(c - a)).expect(
149 "finite plane must be defined by three finite points that don't lie on the same line",
150 );
151 let translation = (a + b + c) / 3.0;
152
153 (
154 Self {
155 normal,
156 ..Default::default()
157 },
158 translation,
159 )
160 }
161}
162impl Measured2d for Plane3d {
163 #[inline]
164 fn area(&self) -> f32 {
165 self.half_size.element_product() * 4.0
166 }
167
168 #[inline]
169 fn perimeter(&self) -> f32 {
170 self.half_size.element_sum() * 4.0
171 }
172}
173
174#[derive(Clone, Copy, Debug, PartialEq)]
177#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
178#[cfg_attr(
179 feature = "bevy_reflect",
180 derive(Reflect),
181 reflect(Debug, PartialEq, Default, Clone)
182)]
183#[cfg_attr(
184 all(feature = "serialize", feature = "bevy_reflect"),
185 reflect(Serialize, Deserialize)
186)]
187pub struct InfinitePlane3d {
188 pub normal: Dir3,
190}
191
192impl Primitive3d for InfinitePlane3d {}
193
194impl Default for InfinitePlane3d {
195 fn default() -> Self {
197 Self { normal: Dir3::Y }
198 }
199}
200
201impl InfinitePlane3d {
202 #[inline]
208 pub fn new<T: TryInto<Dir3>>(normal: T) -> Self
209 where
210 <T as TryInto<Dir3>>::Error: core::fmt::Debug,
211 {
212 Self {
213 normal: normal
214 .try_into()
215 .expect("normal must be nonzero and finite"),
216 }
217 }
218
219 #[inline]
230 pub fn from_points(a: Vec3, b: Vec3, c: Vec3) -> (Self, Vec3) {
231 let normal = Dir3::new((b - a).cross(c - a)).expect(
232 "infinite plane must be defined by three finite points that don't lie on the same line",
233 );
234 let translation = (a + b + c) / 3.0;
235
236 (Self { normal }, translation)
237 }
238
239 #[inline]
243 pub fn signed_distance(&self, isometry: impl Into<Isometry3d>, point: Vec3) -> f32 {
244 let isometry = isometry.into();
245 self.normal.dot(isometry.inverse() * point)
246 }
247
248 #[inline]
252 pub fn project_point(&self, isometry: impl Into<Isometry3d>, point: Vec3) -> Vec3 {
253 point - self.normal * self.signed_distance(isometry, point)
254 }
255
256 #[inline]
281 pub fn isometry_into_xy(&self, origin: Vec3) -> Isometry3d {
282 let rotation = Quat::from_rotation_arc(self.normal.as_vec3(), Vec3::Z);
283 let transformed_origin = rotation * origin;
284 Isometry3d::new(-Vec3::Z * transformed_origin.z, rotation)
285 }
286
287 #[inline]
312 pub fn isometry_from_xy(&self, origin: Vec3) -> Isometry3d {
313 self.isometry_into_xy(origin).inverse()
314 }
315
316 #[inline]
344 pub fn isometries_xy(&self, origin: Vec3) -> (Isometry3d, Isometry3d) {
345 let projection = self.isometry_into_xy(origin);
346 (projection, projection.inverse())
347 }
348}
349
350#[derive(Clone, Copy, Debug, PartialEq)]
354#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
355#[cfg_attr(
356 feature = "bevy_reflect",
357 derive(Reflect),
358 reflect(Debug, PartialEq, Clone)
359)]
360#[cfg_attr(
361 all(feature = "serialize", feature = "bevy_reflect"),
362 reflect(Serialize, Deserialize)
363)]
364pub struct Line3d {
365 pub direction: Dir3,
367}
368
369impl Primitive3d for Line3d {}
370
371#[derive(Clone, Copy, Debug, PartialEq)]
373#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
374#[cfg_attr(
375 feature = "bevy_reflect",
376 derive(Reflect),
377 reflect(Debug, PartialEq, Clone)
378)]
379#[cfg_attr(
380 all(feature = "serialize", feature = "bevy_reflect"),
381 reflect(Serialize, Deserialize)
382)]
383#[doc(alias = "LineSegment3d")]
384pub struct Segment3d {
385 pub vertices: [Vec3; 2],
387}
388
389impl Primitive3d for Segment3d {}
390
391impl Default for Segment3d {
392 fn default() -> Self {
393 Self {
394 vertices: [Vec3::new(-0.5, 0.0, 0.0), Vec3::new(0.5, 0.0, 0.0)],
395 }
396 }
397}
398
399impl Segment3d {
400 #[inline]
402 pub const fn new(point1: Vec3, point2: Vec3) -> Self {
403 Self {
404 vertices: [point1, point2],
405 }
406 }
407
408 #[inline]
412 pub fn from_direction_and_length(direction: Dir3, length: f32) -> Self {
413 let endpoint = 0.5 * length * direction;
414 Self {
415 vertices: [-endpoint, endpoint],
416 }
417 }
418
419 #[inline]
424 pub fn from_scaled_direction(scaled_direction: Vec3) -> Self {
425 let endpoint = 0.5 * scaled_direction;
426 Self {
427 vertices: [-endpoint, endpoint],
428 }
429 }
430
431 #[inline]
436 pub fn from_ray_and_length(ray: Ray3d, length: f32) -> Self {
437 Self {
438 vertices: [ray.origin, ray.get_point(length)],
439 }
440 }
441
442 #[inline]
444 pub const fn point1(&self) -> Vec3 {
445 self.vertices[0]
446 }
447
448 #[inline]
450 pub const fn point2(&self) -> Vec3 {
451 self.vertices[1]
452 }
453
454 #[inline]
456 #[doc(alias = "midpoint")]
457 pub fn center(&self) -> Vec3 {
458 self.point1().midpoint(self.point2())
459 }
460
461 #[inline]
463 pub fn length(&self) -> f32 {
464 self.point1().distance(self.point2())
465 }
466
467 #[inline]
469 pub fn length_squared(&self) -> f32 {
470 self.point1().distance_squared(self.point2())
471 }
472
473 #[inline]
481 pub fn direction(&self) -> Dir3 {
482 self.try_direction().unwrap_or_else(|err| {
483 panic!("Failed to compute the direction of a line segment: {err}")
484 })
485 }
486
487 #[inline]
492 pub fn try_direction(&self) -> Result<Dir3, InvalidDirectionError> {
493 Dir3::new(self.scaled_direction())
494 }
495
496 #[inline]
498 pub fn scaled_direction(&self) -> Vec3 {
499 self.point2() - self.point1()
500 }
501
502 #[inline]
504 pub fn transformed(&self, isometry: impl Into<Isometry3d>) -> Self {
505 let isometry: Isometry3d = isometry.into();
506 Self::new(
507 isometry.transform_point(self.point1()).into(),
508 isometry.transform_point(self.point2()).into(),
509 )
510 }
511
512 #[inline]
514 pub fn translated(&self, translation: Vec3) -> Segment3d {
515 Self::new(self.point1() + translation, self.point2() + translation)
516 }
517
518 #[inline]
520 pub fn rotated(&self, rotation: Quat) -> Segment3d {
521 Segment3d::new(rotation * self.point1(), rotation * self.point2())
522 }
523
524 #[inline]
526 pub fn rotated_around(&self, rotation: Quat, point: Vec3) -> Segment3d {
527 let offset = self.translated(-point);
529 let rotated = offset.rotated(rotation);
530 rotated.translated(point)
531 }
532
533 #[inline]
535 pub fn rotated_around_center(&self, rotation: Quat) -> Segment3d {
536 self.rotated_around(rotation, self.center())
537 }
538
539 #[inline]
541 pub fn centered(&self) -> Segment3d {
542 let center = self.center();
543 self.translated(-center)
544 }
545
546 #[inline]
548 pub fn resized(&self, length: f32) -> Segment3d {
549 let offset_from_origin = self.center();
550 let centered = self.translated(-offset_from_origin);
551 let ratio = length / self.length();
552 let segment = Segment3d::new(centered.point1() * ratio, centered.point2() * ratio);
553 segment.translated(offset_from_origin)
554 }
555
556 #[inline]
558 pub fn reverse(&mut self) {
559 let [point1, point2] = &mut self.vertices;
560 core::mem::swap(point1, point2);
561 }
562
563 #[inline]
565 #[must_use]
566 pub fn reversed(mut self) -> Self {
567 self.reverse();
568 self
569 }
570
571 #[inline]
573 pub fn closest_point(&self, point: Vec3) -> Vec3 {
574 let segment_vector = self.vertices[1] - self.vertices[0];
583 let offset = point - self.vertices[0];
584 let projection_scaled = segment_vector.dot(offset);
586
587 if projection_scaled <= 0.0 {
589 return self.vertices[0];
590 }
591
592 let length_squared = segment_vector.length_squared();
593 if projection_scaled >= length_squared {
595 return self.vertices[1];
596 }
597
598 let t = projection_scaled / length_squared;
600 self.vertices[0] + t * segment_vector
601 }
602}
603
604impl From<[Vec3; 2]> for Segment3d {
605 #[inline]
606 fn from(vertices: [Vec3; 2]) -> Self {
607 Self { vertices }
608 }
609}
610
611impl From<(Vec3, Vec3)> for Segment3d {
612 #[inline]
613 fn from((point1, point2): (Vec3, Vec3)) -> Self {
614 Self::new(point1, point2)
615 }
616}
617
618#[cfg(feature = "alloc")]
620#[derive(Clone, Debug, PartialEq)]
621#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
622#[cfg_attr(
623 feature = "bevy_reflect",
624 derive(Reflect),
625 reflect(Debug, PartialEq, Clone)
626)]
627#[cfg_attr(
628 all(feature = "serialize", feature = "bevy_reflect"),
629 reflect(Serialize, Deserialize)
630)]
631pub struct Polyline3d {
632 pub vertices: Vec<Vec3>,
634}
635
636#[cfg(feature = "alloc")]
637impl Primitive3d for Polyline3d {}
638
639#[cfg(feature = "alloc")]
640impl FromIterator<Vec3> for Polyline3d {
641 fn from_iter<I: IntoIterator<Item = Vec3>>(iter: I) -> Self {
642 Self {
643 vertices: iter.into_iter().collect(),
644 }
645 }
646}
647
648#[cfg(feature = "alloc")]
649impl Default for Polyline3d {
650 fn default() -> Self {
651 Self::new([Vec3::new(-0.5, 0.0, 0.0), Vec3::new(0.5, 0.0, 0.0)])
652 }
653}
654
655#[cfg(feature = "alloc")]
656impl Polyline3d {
657 pub fn new(vertices: impl IntoIterator<Item = Vec3>) -> Self {
659 Self::from_iter(vertices)
660 }
661
662 pub fn with_subdivisions(start: Vec3, end: Vec3, subdivisions: usize) -> Self {
666 let total_vertices = subdivisions + 2;
667 let mut vertices = Vec::with_capacity(total_vertices);
668
669 let step = (end - start) / (subdivisions + 1) as f32;
670 for i in 0..total_vertices {
671 vertices.push(start + step * i as f32);
672 }
673
674 Self { vertices }
675 }
676}
677
678#[derive(Clone, Copy, Debug, PartialEq)]
681#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
682#[cfg_attr(
683 feature = "bevy_reflect",
684 derive(Reflect),
685 reflect(Debug, PartialEq, Default, Clone)
686)]
687#[cfg_attr(
688 all(feature = "serialize", feature = "bevy_reflect"),
689 reflect(Serialize, Deserialize)
690)]
691pub struct Cuboid {
692 pub half_size: Vec3,
694}
695
696impl Primitive3d for Cuboid {}
697
698impl Default for Cuboid {
699 fn default() -> Self {
701 Self {
702 half_size: Vec3::splat(0.5),
703 }
704 }
705}
706
707impl Cuboid {
708 #[inline]
710 pub const fn new(x_length: f32, y_length: f32, z_length: f32) -> Self {
711 Self::from_size(Vec3::new(x_length, y_length, z_length))
712 }
713
714 #[inline]
716 pub const fn from_size(size: Vec3) -> Self {
717 Self {
718 half_size: Vec3::new(size.x / 2.0, size.y / 2.0, size.z / 2.0),
719 }
720 }
721
722 #[inline]
724 pub fn from_corners(point1: Vec3, point2: Vec3) -> Self {
725 Self {
726 half_size: (point2 - point1).abs() / 2.0,
727 }
728 }
729
730 #[inline]
733 pub const fn from_length(length: f32) -> Self {
734 Self {
735 half_size: Vec3::splat(length / 2.0),
736 }
737 }
738
739 #[inline]
741 pub fn size(&self) -> Vec3 {
742 2.0 * self.half_size
743 }
744
745 #[inline]
750 pub fn closest_point(&self, point: Vec3) -> Vec3 {
751 point.clamp(-self.half_size, self.half_size)
753 }
754}
755
756impl Measured3d for Cuboid {
757 #[inline]
759 fn area(&self) -> f32 {
760 8.0 * (self.half_size.x * self.half_size.y
761 + self.half_size.y * self.half_size.z
762 + self.half_size.x * self.half_size.z)
763 }
764
765 #[inline]
767 fn volume(&self) -> f32 {
768 8.0 * self.half_size.x * self.half_size.y * self.half_size.z
769 }
770}
771
772#[derive(Clone, Copy, Debug, PartialEq)]
774#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
775#[cfg_attr(
776 feature = "bevy_reflect",
777 derive(Reflect),
778 reflect(Debug, PartialEq, Default, Clone)
779)]
780#[cfg_attr(
781 all(feature = "serialize", feature = "bevy_reflect"),
782 reflect(Serialize, Deserialize)
783)]
784pub struct Cylinder {
785 pub radius: f32,
787 pub half_height: f32,
789}
790
791impl Primitive3d for Cylinder {}
792
793impl Default for Cylinder {
794 fn default() -> Self {
796 Self {
797 radius: 0.5,
798 half_height: 0.5,
799 }
800 }
801}
802
803impl Cylinder {
804 #[inline]
806 pub const fn new(radius: f32, height: f32) -> Self {
807 Self {
808 radius,
809 half_height: height / 2.0,
810 }
811 }
812
813 #[inline]
815 pub const fn base(&self) -> Circle {
816 Circle {
817 radius: self.radius,
818 }
819 }
820
821 #[inline]
824 #[doc(alias = "side_area")]
825 pub const fn lateral_area(&self) -> f32 {
826 4.0 * PI * self.radius * self.half_height
827 }
828
829 #[inline]
831 pub fn base_area(&self) -> f32 {
832 PI * self.radius.squared()
833 }
834}
835
836impl Measured3d for Cylinder {
837 #[inline]
839 fn area(&self) -> f32 {
840 2.0 * PI * self.radius * (self.radius + 2.0 * self.half_height)
841 }
842
843 #[inline]
845 fn volume(&self) -> f32 {
846 self.base_area() * 2.0 * self.half_height
847 }
848}
849
850#[derive(Clone, Copy, Debug, PartialEq)]
853#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
854#[cfg_attr(
855 feature = "bevy_reflect",
856 derive(Reflect),
857 reflect(Debug, PartialEq, Default, Clone)
858)]
859#[cfg_attr(
860 all(feature = "serialize", feature = "bevy_reflect"),
861 reflect(Serialize, Deserialize)
862)]
863pub struct Capsule3d {
864 pub radius: f32,
866 pub half_length: f32,
868}
869
870impl Primitive3d for Capsule3d {}
871
872impl Default for Capsule3d {
873 fn default() -> Self {
876 Self {
877 radius: 0.5,
878 half_length: 0.5,
879 }
880 }
881}
882
883impl Capsule3d {
884 pub const fn new(radius: f32, length: f32) -> Self {
886 Self {
887 radius,
888 half_length: length / 2.0,
889 }
890 }
891
892 #[inline]
895 pub const fn to_cylinder(&self) -> Cylinder {
896 Cylinder {
897 radius: self.radius,
898 half_height: self.half_length,
899 }
900 }
901}
902
903impl Measured3d for Capsule3d {
904 #[inline]
906 fn area(&self) -> f32 {
907 4.0 * PI * self.radius * (self.radius + self.half_length)
909 }
910
911 #[inline]
913 fn volume(&self) -> f32 {
914 let diameter = self.radius * 2.0;
916 PI * self.radius * diameter * (diameter / 3.0 + self.half_length)
917 }
918}
919
920#[derive(Clone, Copy, Debug, PartialEq)]
924#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
925#[cfg_attr(
926 feature = "bevy_reflect",
927 derive(Reflect),
928 reflect(Debug, PartialEq, Default, Clone)
929)]
930#[cfg_attr(
931 all(feature = "serialize", feature = "bevy_reflect"),
932 reflect(Serialize, Deserialize)
933)]
934pub struct Cone {
935 pub radius: f32,
937 pub height: f32,
939}
940
941impl Primitive3d for Cone {}
942
943impl Default for Cone {
944 fn default() -> Self {
946 Self {
947 radius: 0.5,
948 height: 1.0,
949 }
950 }
951}
952
953impl Cone {
954 pub const fn new(radius: f32, height: f32) -> Self {
956 Self { radius, height }
957 }
958 #[inline]
960 pub const fn base(&self) -> Circle {
961 Circle {
962 radius: self.radius,
963 }
964 }
965
966 #[inline]
969 #[doc(alias = "side_length")]
970 pub fn slant_height(&self) -> f32 {
971 ops::hypot(self.radius, self.height)
972 }
973
974 #[inline]
977 #[doc(alias = "side_area")]
978 pub fn lateral_area(&self) -> f32 {
979 PI * self.radius * self.slant_height()
980 }
981
982 #[inline]
984 pub fn base_area(&self) -> f32 {
985 PI * self.radius.squared()
986 }
987}
988
989impl Measured3d for Cone {
990 #[inline]
992 fn area(&self) -> f32 {
993 self.base_area() + self.lateral_area()
994 }
995
996 #[inline]
998 fn volume(&self) -> f32 {
999 (self.base_area() * self.height) / 3.0
1000 }
1001}
1002
1003#[derive(Clone, Copy, Debug, PartialEq)]
1007#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1008#[cfg_attr(
1009 feature = "bevy_reflect",
1010 derive(Reflect),
1011 reflect(Debug, PartialEq, Default, Clone)
1012)]
1013#[cfg_attr(
1014 all(feature = "serialize", feature = "bevy_reflect"),
1015 reflect(Serialize, Deserialize)
1016)]
1017pub struct ConicalFrustum {
1018 pub radius_top: f32,
1020 pub radius_bottom: f32,
1022 pub height: f32,
1024}
1025
1026impl Primitive3d for ConicalFrustum {}
1027
1028impl Default for ConicalFrustum {
1029 fn default() -> Self {
1031 Self {
1032 radius_top: 0.25,
1033 radius_bottom: 0.5,
1034 height: 0.5,
1035 }
1036 }
1037}
1038
1039impl ConicalFrustum {
1040 #[inline]
1042 pub const fn bottom_base(&self) -> Circle {
1043 Circle {
1044 radius: self.radius_bottom,
1045 }
1046 }
1047
1048 #[inline]
1050 pub const fn top_base(&self) -> Circle {
1051 Circle {
1052 radius: self.radius_top,
1053 }
1054 }
1055
1056 #[inline]
1059 #[doc(alias = "side_length")]
1060 pub fn slant_height(&self) -> f32 {
1061 ops::hypot(self.radius_bottom - self.radius_top, self.height)
1062 }
1063
1064 #[inline]
1067 #[doc(alias = "side_area")]
1068 pub fn lateral_area(&self) -> f32 {
1069 PI * (self.radius_bottom + self.radius_top) * self.slant_height()
1070 }
1071
1072 #[inline]
1074 pub fn bottom_base_area(&self) -> f32 {
1075 PI * self.radius_bottom.squared()
1076 }
1077
1078 #[inline]
1080 pub fn top_base_area(&self) -> f32 {
1081 PI * self.radius_top.squared()
1082 }
1083}
1084
1085impl Measured3d for ConicalFrustum {
1086 #[inline]
1087 fn volume(&self) -> f32 {
1088 FRAC_PI_3
1089 * self.height
1090 * (self.radius_bottom * self.radius_bottom
1091 + self.radius_top * self.radius_top
1092 + self.radius_top * self.radius_bottom)
1093 }
1094 #[inline]
1095 fn area(&self) -> f32 {
1096 self.bottom_base_area() + self.top_base_area() + self.lateral_area()
1097 }
1098}
1099
1100#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1102pub enum TorusKind {
1103 Ring,
1106 Horn,
1109 Spindle,
1112 Invalid,
1116}
1117
1118#[derive(Clone, Copy, Debug, PartialEq)]
1121#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1122#[cfg_attr(
1123 feature = "bevy_reflect",
1124 derive(Reflect),
1125 reflect(Debug, PartialEq, Default, Clone)
1126)]
1127#[cfg_attr(
1128 all(feature = "serialize", feature = "bevy_reflect"),
1129 reflect(Serialize, Deserialize)
1130)]
1131pub struct Torus {
1132 #[doc(
1134 alias = "ring_radius",
1135 alias = "tube_radius",
1136 alias = "cross_section_radius"
1137 )]
1138 pub minor_radius: f32,
1139 #[doc(alias = "radius_of_revolution")]
1141 pub major_radius: f32,
1142}
1143
1144impl Primitive3d for Torus {}
1145
1146impl Default for Torus {
1147 fn default() -> Self {
1149 Self {
1150 minor_radius: 0.25,
1151 major_radius: 0.75,
1152 }
1153 }
1154}
1155
1156impl Torus {
1157 #[inline]
1162 pub const fn new(inner_radius: f32, outer_radius: f32) -> Self {
1163 let minor_radius = (outer_radius - inner_radius) / 2.0;
1164 let major_radius = outer_radius - minor_radius;
1165
1166 Self {
1167 minor_radius,
1168 major_radius,
1169 }
1170 }
1171
1172 #[inline]
1176 pub const fn inner_radius(&self) -> f32 {
1177 self.major_radius - self.minor_radius
1178 }
1179
1180 #[inline]
1184 pub const fn outer_radius(&self) -> f32 {
1185 self.major_radius + self.minor_radius
1186 }
1187
1188 #[inline]
1197 pub fn kind(&self) -> TorusKind {
1198 if self.minor_radius <= 0.0
1200 || !self.minor_radius.is_finite()
1201 || self.major_radius <= 0.0
1202 || !self.major_radius.is_finite()
1203 {
1204 return TorusKind::Invalid;
1205 }
1206
1207 match self.major_radius.partial_cmp(&self.minor_radius).unwrap() {
1208 core::cmp::Ordering::Greater => TorusKind::Ring,
1209 core::cmp::Ordering::Equal => TorusKind::Horn,
1210 core::cmp::Ordering::Less => TorusKind::Spindle,
1211 }
1212 }
1213}
1214
1215impl Measured3d for Torus {
1216 #[inline]
1219 fn area(&self) -> f32 {
1220 4.0 * PI.squared() * self.major_radius * self.minor_radius
1221 }
1222
1223 #[inline]
1226 fn volume(&self) -> f32 {
1227 2.0 * PI.squared() * self.major_radius * self.minor_radius.squared()
1228 }
1229}
1230
1231#[derive(Clone, Copy, Debug, PartialEq)]
1233#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1234#[cfg_attr(
1235 feature = "bevy_reflect",
1236 derive(Reflect),
1237 reflect(Debug, PartialEq, Default, Clone)
1238)]
1239#[cfg_attr(
1240 all(feature = "serialize", feature = "bevy_reflect"),
1241 reflect(Serialize, Deserialize)
1242)]
1243pub struct Triangle3d {
1244 pub vertices: [Vec3; 3],
1246}
1247
1248impl Primitive3d for Triangle3d {}
1249
1250impl Default for Triangle3d {
1251 fn default() -> Self {
1253 Self {
1254 vertices: [
1255 Vec3::new(0.0, 0.5, 0.0),
1256 Vec3::new(-0.5, -0.5, 0.0),
1257 Vec3::new(0.5, -0.5, 0.0),
1258 ],
1259 }
1260 }
1261}
1262
1263impl Triangle3d {
1264 #[inline]
1266 pub const fn new(a: Vec3, b: Vec3, c: Vec3) -> Self {
1267 Self {
1268 vertices: [a, b, c],
1269 }
1270 }
1271
1272 #[inline]
1282 pub fn normal(&self) -> Result<Dir3, InvalidDirectionError> {
1283 let [a, b, c] = self.vertices;
1284 let ab = b - a;
1285 let ac = c - a;
1286 Dir3::new(ab.cross(ac))
1287 }
1288
1289 #[inline]
1294 pub fn is_degenerate(&self) -> bool {
1295 let [a, b, c] = self.vertices;
1296 let ab = b - a;
1297 let ac = c - a;
1298 ab.cross(ac).length() < 10e-7
1299 }
1300
1301 #[inline]
1303 pub fn is_acute(&self) -> bool {
1304 let [a, b, c] = self.vertices;
1305 let ab = b - a;
1306 let bc = c - b;
1307 let ca = a - c;
1308
1309 let side_lengths = [
1311 ab.length_squared(),
1312 bc.length_squared(),
1313 ca.length_squared(),
1314 ];
1315 let sum = side_lengths[0] + side_lengths[1] + side_lengths[2];
1316 let max = side_lengths[0].max(side_lengths[1]).max(side_lengths[2]);
1317 sum - max > max
1318 }
1319
1320 #[inline]
1322 pub fn is_obtuse(&self) -> bool {
1323 let [a, b, c] = self.vertices;
1324 let ab = b - a;
1325 let bc = c - b;
1326 let ca = a - c;
1327
1328 let side_lengths = [
1330 ab.length_squared(),
1331 bc.length_squared(),
1332 ca.length_squared(),
1333 ];
1334 let sum = side_lengths[0] + side_lengths[1] + side_lengths[2];
1335 let max = side_lengths[0].max(side_lengths[1]).max(side_lengths[2]);
1336 sum - max < max
1337 }
1338
1339 #[inline]
1341 pub fn reverse(&mut self) {
1342 self.vertices.swap(0, 2);
1343 }
1344
1345 #[inline]
1347 #[must_use]
1348 pub fn reversed(mut self) -> Triangle3d {
1349 self.reverse();
1350 self
1351 }
1352
1353 #[doc(alias("center", "barycenter", "baricenter"))]
1358 #[inline]
1359 pub fn centroid(&self) -> Vec3 {
1360 (self.vertices[0] + self.vertices[1] + self.vertices[2]) / 3.0
1361 }
1362
1363 #[inline]
1367 pub fn largest_side(&self) -> (Vec3, Vec3) {
1368 let [a, b, c] = self.vertices;
1369 let ab = b - a;
1370 let bc = c - b;
1371 let ca = a - c;
1372
1373 let mut largest_side_points = (a, b);
1374 let mut largest_side_length = ab.length_squared();
1375
1376 let bc_length = bc.length_squared();
1377 if bc_length > largest_side_length {
1378 largest_side_points = (b, c);
1379 largest_side_length = bc_length;
1380 }
1381
1382 let ca_length = ca.length_squared();
1383 if ca_length > largest_side_length {
1384 largest_side_points = (a, c);
1385 }
1386
1387 largest_side_points
1388 }
1389
1390 #[inline]
1392 pub fn circumcenter(&self) -> Vec3 {
1393 if self.is_degenerate() {
1394 let (p1, p2) = self.largest_side();
1396 return (p1 + p2) / 2.0;
1397 }
1398
1399 let [a, b, c] = self.vertices;
1400 let ab = b - a;
1401 let ac = c - a;
1402 let n = ab.cross(ac);
1403
1404 a + ((ac.length_squared() * n.cross(ab) + ab.length_squared() * ac.cross(ab).cross(ac))
1406 / (2.0 * n.length_squared()))
1407 }
1408}
1409
1410impl Measured2d for Triangle3d {
1411 #[inline]
1413 fn area(&self) -> f32 {
1414 let [a, b, c] = self.vertices;
1415 let ab = b - a;
1416 let ac = c - a;
1417 ab.cross(ac).length() / 2.0
1418 }
1419
1420 #[inline]
1422 fn perimeter(&self) -> f32 {
1423 let [a, b, c] = self.vertices;
1424 a.distance(b) + b.distance(c) + c.distance(a)
1425 }
1426}
1427
1428#[derive(Clone, Copy, Debug, PartialEq)]
1430#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1431#[cfg_attr(
1432 feature = "bevy_reflect",
1433 derive(Reflect),
1434 reflect(Debug, PartialEq, Default, Clone)
1435)]
1436#[cfg_attr(
1437 all(feature = "serialize", feature = "bevy_reflect"),
1438 reflect(Serialize, Deserialize)
1439)]
1440pub struct Tetrahedron {
1441 pub vertices: [Vec3; 4],
1443}
1444
1445impl Primitive3d for Tetrahedron {}
1446
1447impl Default for Tetrahedron {
1448 fn default() -> Self {
1451 Self {
1452 vertices: [
1453 Vec3::new(0.5, 0.5, 0.5),
1454 Vec3::new(-0.5, 0.5, -0.5),
1455 Vec3::new(-0.5, -0.5, 0.5),
1456 Vec3::new(0.5, -0.5, -0.5),
1457 ],
1458 }
1459 }
1460}
1461
1462impl Tetrahedron {
1463 #[inline]
1465 pub const fn new(a: Vec3, b: Vec3, c: Vec3, d: Vec3) -> Self {
1466 Self {
1467 vertices: [a, b, c, d],
1468 }
1469 }
1470
1471 #[inline]
1477 pub fn signed_volume(&self) -> f32 {
1478 let [a, b, c, d] = self.vertices;
1479 let ab = b - a;
1480 let ac = c - a;
1481 let ad = d - a;
1482 Mat3::from_cols(ab, ac, ad).determinant() / 6.0
1483 }
1484
1485 #[doc(alias("center", "barycenter", "baricenter"))]
1490 #[inline]
1491 pub fn centroid(&self) -> Vec3 {
1492 (self.vertices[0] + self.vertices[1] + self.vertices[2] + self.vertices[3]) / 4.0
1493 }
1494
1495 #[inline]
1501 pub fn faces(&self) -> [Triangle3d; 4] {
1502 let [a, b, c, d] = self.vertices;
1503 [
1504 Triangle3d::new(b, c, d),
1505 Triangle3d::new(a, c, d).reversed(),
1506 Triangle3d::new(a, b, d),
1507 Triangle3d::new(a, b, c).reversed(),
1508 ]
1509 }
1510}
1511
1512impl Measured3d for Tetrahedron {
1513 #[inline]
1515 fn area(&self) -> f32 {
1516 let [a, b, c, d] = self.vertices;
1517 let ab = b - a;
1518 let ac = c - a;
1519 let ad = d - a;
1520 let bc = c - b;
1521 let bd = d - b;
1522 (ab.cross(ac).length()
1523 + ab.cross(ad).length()
1524 + ac.cross(ad).length()
1525 + bc.cross(bd).length())
1526 / 2.0
1527 }
1528
1529 #[inline]
1531 fn volume(&self) -> f32 {
1532 ops::abs(self.signed_volume())
1533 }
1534}
1535
1536#[doc(alias = "Prism")]
1544#[derive(Clone, Copy, Debug, PartialEq)]
1545#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1546pub struct Extrusion<T: Primitive2d> {
1547 pub base_shape: T,
1549 pub half_depth: f32,
1551}
1552
1553impl<T: Primitive2d> Primitive3d for Extrusion<T> {}
1554
1555impl<T: Primitive2d> Extrusion<T> {
1556 pub fn new(base_shape: T, depth: f32) -> Self {
1558 Self {
1559 base_shape,
1560 half_depth: depth / 2.,
1561 }
1562 }
1563}
1564
1565impl<T: Primitive2d + Measured2d> Measured3d for Extrusion<T> {
1566 fn area(&self) -> f32 {
1568 2. * (self.base_shape.area() + self.half_depth * self.base_shape.perimeter())
1569 }
1570
1571 fn volume(&self) -> f32 {
1573 2. * self.base_shape.area() * self.half_depth
1574 }
1575}
1576
1577#[cfg(test)]
1578mod tests {
1579 use super::*;
1582 use crate::{InvalidDirectionError, Quat};
1583 use approx::assert_relative_eq;
1584
1585 #[test]
1586 fn direction_creation() {
1587 assert_eq!(Dir3::new(Vec3::X * 12.5), Ok(Dir3::X));
1588 assert_eq!(
1589 Dir3::new(Vec3::new(0.0, 0.0, 0.0)),
1590 Err(InvalidDirectionError::Zero)
1591 );
1592 assert_eq!(
1593 Dir3::new(Vec3::new(f32::INFINITY, 0.0, 0.0)),
1594 Err(InvalidDirectionError::Infinite)
1595 );
1596 assert_eq!(
1597 Dir3::new(Vec3::new(f32::NEG_INFINITY, 0.0, 0.0)),
1598 Err(InvalidDirectionError::Infinite)
1599 );
1600 assert_eq!(
1601 Dir3::new(Vec3::new(f32::NAN, 0.0, 0.0)),
1602 Err(InvalidDirectionError::NaN)
1603 );
1604 assert_eq!(Dir3::new_and_length(Vec3::X * 6.5), Ok((Dir3::X, 6.5)));
1605
1606 assert!(
1608 (Quat::from_rotation_z(core::f32::consts::FRAC_PI_2) * Dir3::X)
1609 .abs_diff_eq(Vec3::Y, 10e-6)
1610 );
1611 }
1612
1613 #[test]
1614 fn cuboid_closest_point() {
1615 let cuboid = Cuboid::new(2.0, 2.0, 2.0);
1616 assert_eq!(cuboid.closest_point(Vec3::X * 10.0), Vec3::X);
1617 assert_eq!(cuboid.closest_point(Vec3::NEG_ONE * 10.0), Vec3::NEG_ONE);
1618 assert_eq!(
1619 cuboid.closest_point(Vec3::new(0.25, 0.1, 0.3)),
1620 Vec3::new(0.25, 0.1, 0.3)
1621 );
1622 }
1623
1624 #[test]
1625 fn sphere_closest_point() {
1626 let sphere = Sphere { radius: 1.0 };
1627 assert_eq!(sphere.closest_point(Vec3::X * 10.0), Vec3::X);
1628 assert_eq!(
1629 sphere.closest_point(Vec3::NEG_ONE * 10.0),
1630 Vec3::NEG_ONE.normalize()
1631 );
1632 assert_eq!(
1633 sphere.closest_point(Vec3::new(0.25, 0.1, 0.3)),
1634 Vec3::new(0.25, 0.1, 0.3)
1635 );
1636 }
1637
1638 #[test]
1639 fn segment_closest_point() {
1640 assert_eq!(
1641 Segment3d::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(3.0, 0.0, 0.0))
1642 .closest_point(Vec3::new(1.0, 6.0, -2.0)),
1643 Vec3::new(1.0, 0.0, 0.0)
1644 );
1645
1646 let segments = [
1647 Segment3d::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(0.0, 0.0, 0.0)),
1648 Segment3d::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0)),
1649 Segment3d::new(Vec3::new(1.0, 0.0, 2.0), Vec3::new(0.0, 1.0, -2.0)),
1650 Segment3d::new(
1651 Vec3::new(1.0, 0.0, 0.0),
1652 Vec3::new(1.0, 5.0 * f32::EPSILON, 0.0),
1653 ),
1654 ];
1655 let points = [
1656 Vec3::new(0.0, 0.0, 0.0),
1657 Vec3::new(1.0, 0.0, 0.0),
1658 Vec3::new(-1.0, 1.0, 2.0),
1659 Vec3::new(1.0, 1.0, 1.0),
1660 Vec3::new(-1.0, 0.0, 0.0),
1661 Vec3::new(5.0, -1.0, 0.5),
1662 Vec3::new(1.0, f32::EPSILON, 0.0),
1663 ];
1664
1665 for point in points.iter() {
1666 for segment in segments.iter() {
1667 let closest = segment.closest_point(*point);
1668 assert!(
1669 point.distance_squared(closest) <= point.distance_squared(segment.point1()),
1670 "Closest point must always be at least as close as either vertex."
1671 );
1672 assert!(
1673 point.distance_squared(closest) <= point.distance_squared(segment.point2()),
1674 "Closest point must always be at least as close as either vertex."
1675 );
1676 assert!(
1677 point.distance_squared(closest) <= point.distance_squared(segment.center()),
1678 "Closest point must always be at least as close as the center."
1679 );
1680 let closest_to_closest = segment.closest_point(closest);
1681 assert_relative_eq!(closest_to_closest, closest);
1683 }
1684 }
1685 }
1686
1687 #[test]
1688 fn sphere_math() {
1689 let sphere = Sphere { radius: 4.0 };
1690 assert_eq!(sphere.diameter(), 8.0, "incorrect diameter");
1691 assert_eq!(sphere.area(), 201.06193, "incorrect area");
1692 assert_eq!(sphere.volume(), 268.08257, "incorrect volume");
1693 }
1694
1695 #[test]
1696 fn plane_from_points() {
1697 let (plane, translation) = Plane3d::from_points(Vec3::X, Vec3::Z, Vec3::NEG_X);
1698 assert_eq!(*plane.normal, Vec3::NEG_Y, "incorrect normal");
1699 assert_eq!(plane.half_size, Vec2::new(0.5, 0.5), "incorrect half size");
1700 assert_eq!(translation, Vec3::Z * 0.33333334, "incorrect translation");
1701 }
1702
1703 #[test]
1704 fn infinite_plane_math() {
1705 let (plane, origin) = InfinitePlane3d::from_points(Vec3::X, Vec3::Z, Vec3::NEG_X);
1706 assert_eq!(*plane.normal, Vec3::NEG_Y, "incorrect normal");
1707 assert_eq!(origin, Vec3::Z * 0.33333334, "incorrect translation");
1708
1709 let point_in_plane = Vec3::X + Vec3::Z;
1710 assert_eq!(
1711 plane.signed_distance(origin, point_in_plane),
1712 0.0,
1713 "incorrect distance"
1714 );
1715 assert_eq!(
1716 plane.project_point(origin, point_in_plane),
1717 point_in_plane,
1718 "incorrect point"
1719 );
1720
1721 let point_outside = Vec3::Y;
1722 assert_eq!(
1723 plane.signed_distance(origin, point_outside),
1724 -1.0,
1725 "incorrect distance"
1726 );
1727 assert_eq!(
1728 plane.project_point(origin, point_outside),
1729 Vec3::ZERO,
1730 "incorrect point"
1731 );
1732
1733 let point_outside = Vec3::NEG_Y;
1734 assert_eq!(
1735 plane.signed_distance(origin, point_outside),
1736 1.0,
1737 "incorrect distance"
1738 );
1739 assert_eq!(
1740 plane.project_point(origin, point_outside),
1741 Vec3::ZERO,
1742 "incorrect point"
1743 );
1744
1745 let area_f = |[a, b, c]: [Vec3; 3]| (a - b).cross(a - c).length() * 0.5;
1746 let (proj, inj) = plane.isometries_xy(origin);
1747
1748 let triangle = [Vec3::X, Vec3::Y, Vec3::ZERO];
1749 assert_eq!(area_f(triangle), 0.5, "incorrect area");
1750
1751 let triangle_proj = triangle.map(|vec3| proj * vec3);
1752 assert_relative_eq!(area_f(triangle_proj), 0.5);
1753
1754 let triangle_proj_inj = triangle_proj.map(|vec3| inj * vec3);
1755 assert_relative_eq!(area_f(triangle_proj_inj), 0.5);
1756 }
1757
1758 #[test]
1759 fn cuboid_math() {
1760 let cuboid = Cuboid::new(3.0, 7.0, 2.0);
1761 assert_eq!(
1762 cuboid,
1763 Cuboid::from_corners(Vec3::new(-1.5, -3.5, -1.0), Vec3::new(1.5, 3.5, 1.0)),
1764 "incorrect dimensions when created from corners"
1765 );
1766 assert_eq!(cuboid.area(), 82.0, "incorrect area");
1767 assert_eq!(cuboid.volume(), 42.0, "incorrect volume");
1768 }
1769
1770 #[test]
1771 fn cylinder_math() {
1772 let cylinder = Cylinder::new(2.0, 9.0);
1773 assert_eq!(
1774 cylinder.base(),
1775 Circle { radius: 2.0 },
1776 "base produces incorrect circle"
1777 );
1778 assert_eq!(
1779 cylinder.lateral_area(),
1780 113.097336,
1781 "incorrect lateral area"
1782 );
1783 assert_eq!(cylinder.base_area(), 12.566371, "incorrect base area");
1784 assert_relative_eq!(cylinder.area(), 138.23007);
1785 assert_eq!(cylinder.volume(), 113.097336, "incorrect volume");
1786 }
1787
1788 #[test]
1789 fn capsule_math() {
1790 let capsule = Capsule3d::new(2.0, 9.0);
1791 assert_eq!(
1792 capsule.to_cylinder(),
1793 Cylinder::new(2.0, 9.0),
1794 "cylinder wasn't created correctly from a capsule"
1795 );
1796 assert_eq!(capsule.area(), 163.36282, "incorrect area");
1797 assert_relative_eq!(capsule.volume(), 146.60765);
1798 }
1799
1800 #[test]
1801 fn cone_math() {
1802 let cone = Cone {
1803 radius: 2.0,
1804 height: 9.0,
1805 };
1806 assert_eq!(
1807 cone.base(),
1808 Circle { radius: 2.0 },
1809 "base produces incorrect circle"
1810 );
1811 assert_eq!(cone.slant_height(), 9.219544, "incorrect slant height");
1812 assert_eq!(cone.lateral_area(), 57.92811, "incorrect lateral area");
1813 assert_eq!(cone.base_area(), 12.566371, "incorrect base area");
1814 assert_relative_eq!(cone.area(), 70.49447);
1815 assert_eq!(cone.volume(), 37.699111, "incorrect volume");
1816 }
1817
1818 #[test]
1819 fn conical_frustum_math() {
1820 let frustum = ConicalFrustum {
1821 height: 9.0,
1822 radius_top: 1.0,
1823 radius_bottom: 2.0,
1824 };
1825 assert_eq!(
1826 frustum.bottom_base(),
1827 Circle { radius: 2.0 },
1828 "bottom base produces incorrect circle"
1829 );
1830 assert_eq!(
1831 frustum.top_base(),
1832 Circle { radius: 1.0 },
1833 "top base produces incorrect circle"
1834 );
1835 assert_eq!(frustum.slant_height(), 9.055386, "incorrect slant height");
1836 assert_eq!(frustum.lateral_area(), 85.345, "incorrect lateral area");
1837 assert_eq!(
1838 frustum.bottom_base_area(),
1839 12.566371,
1840 "incorrect bottom base area"
1841 );
1842 assert_eq!(frustum.top_base_area(), PI, "incorrect top base area");
1843 assert_eq!(frustum.area(), 101.05296, "incorrect surface area");
1844 assert_eq!(frustum.volume(), 65.97345, "incorrect volume");
1845 }
1846
1847 #[test]
1848 fn torus_math() {
1849 let torus = Torus {
1850 minor_radius: 0.3,
1851 major_radius: 2.8,
1852 };
1853 assert_eq!(torus.inner_radius(), 2.5, "incorrect inner radius");
1854 assert_eq!(torus.outer_radius(), 3.1, "incorrect outer radius");
1855 assert_eq!(torus.kind(), TorusKind::Ring, "incorrect torus kind");
1856 assert_eq!(
1857 Torus::new(0.0, 1.0).kind(),
1858 TorusKind::Horn,
1859 "incorrect torus kind"
1860 );
1861 assert_eq!(
1862 Torus::new(-0.5, 1.0).kind(),
1863 TorusKind::Spindle,
1864 "incorrect torus kind"
1865 );
1866 assert_eq!(
1867 Torus::new(1.5, 1.0).kind(),
1868 TorusKind::Invalid,
1869 "torus should be invalid"
1870 );
1871 assert_relative_eq!(torus.area(), 33.16187);
1872 assert_relative_eq!(torus.volume(), 4.97428, epsilon = 0.00001);
1873 }
1874
1875 #[test]
1876 fn tetrahedron_math() {
1877 let tetrahedron = Tetrahedron {
1878 vertices: [
1879 Vec3::new(0.3, 1.0, 1.7),
1880 Vec3::new(-2.0, -1.0, 0.0),
1881 Vec3::new(1.8, 0.5, 1.0),
1882 Vec3::new(-1.0, -2.0, 3.5),
1883 ],
1884 };
1885 assert_eq!(tetrahedron.area(), 19.251068, "incorrect area");
1886 assert_eq!(tetrahedron.volume(), 3.2058334, "incorrect volume");
1887 assert_eq!(
1888 tetrahedron.signed_volume(),
1889 3.2058334,
1890 "incorrect signed volume"
1891 );
1892 assert_relative_eq!(tetrahedron.centroid(), Vec3::new(-0.225, -0.375, 1.55));
1893
1894 assert_eq!(Tetrahedron::default().area(), 3.4641016, "incorrect area");
1895 assert_eq!(
1896 Tetrahedron::default().volume(),
1897 0.33333334,
1898 "incorrect volume"
1899 );
1900 assert_eq!(
1901 Tetrahedron::default().signed_volume(),
1902 -0.33333334,
1903 "incorrect signed volume"
1904 );
1905 assert_relative_eq!(Tetrahedron::default().centroid(), Vec3::ZERO);
1906 }
1907
1908 #[test]
1909 fn extrusion_math() {
1910 let circle = Circle::new(0.75);
1911 let cylinder = Extrusion::new(circle, 2.5);
1912 assert_eq!(cylinder.area(), 15.315264, "incorrect surface area");
1913 assert_eq!(cylinder.volume(), 4.417865, "incorrect volume");
1914
1915 let annulus = crate::primitives::Annulus::new(0.25, 1.375);
1916 let tube = Extrusion::new(annulus, 0.333);
1917 assert_eq!(tube.area(), 14.886437, "incorrect surface area");
1918 assert_eq!(tube.volume(), 1.9124937, "incorrect volume");
1919
1920 let polygon = crate::primitives::RegularPolygon::new(3.8, 7);
1921 let regular_prism = Extrusion::new(polygon, 1.25);
1922 assert_eq!(regular_prism.area(), 107.8808, "incorrect surface area");
1923 assert_eq!(regular_prism.volume(), 49.392204, "incorrect volume");
1924 }
1925
1926 #[test]
1927 fn triangle_math() {
1928 let mut default_triangle = Triangle3d::default();
1930 let reverse_default_triangle = Triangle3d::new(
1931 Vec3::new(0.5, -0.5, 0.0),
1932 Vec3::new(-0.5, -0.5, 0.0),
1933 Vec3::new(0.0, 0.5, 0.0),
1934 );
1935 assert_eq!(default_triangle.area(), 0.5, "incorrect area");
1936 assert_relative_eq!(
1937 default_triangle.perimeter(),
1938 1.0 + 2.0 * ops::sqrt(1.25_f32),
1939 epsilon = 10e-9
1940 );
1941 assert_eq!(default_triangle.normal(), Ok(Dir3::Z), "incorrect normal");
1942 assert!(
1943 !default_triangle.is_degenerate(),
1944 "incorrect degenerate check"
1945 );
1946 assert_eq!(
1947 default_triangle.centroid(),
1948 Vec3::new(0.0, -0.16666667, 0.0),
1949 "incorrect centroid"
1950 );
1951 assert_eq!(
1952 default_triangle.largest_side(),
1953 (Vec3::new(0.0, 0.5, 0.0), Vec3::new(-0.5, -0.5, 0.0))
1954 );
1955 default_triangle.reverse();
1956 assert_eq!(
1957 default_triangle, reverse_default_triangle,
1958 "incorrect reverse"
1959 );
1960 assert_eq!(
1961 default_triangle.circumcenter(),
1962 Vec3::new(0.0, -0.125, 0.0),
1963 "incorrect circumcenter"
1964 );
1965
1966 let right_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::Y);
1968 let obtuse_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::X, Vec3::new(0.0, 0.1, 0.0));
1969 let acute_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::new(0.5, 5.0, 0.0));
1970
1971 assert_eq!(
1972 right_triangle.circumcenter(),
1973 Vec3::new(0.5, 0.5, 0.0),
1974 "incorrect circumcenter"
1975 );
1976 assert_eq!(
1977 obtuse_triangle.circumcenter(),
1978 Vec3::new(0.0, -4.95, 0.0),
1979 "incorrect circumcenter"
1980 );
1981 assert_eq!(
1982 acute_triangle.circumcenter(),
1983 Vec3::new(0.5, 2.475, 0.0),
1984 "incorrect circumcenter"
1985 );
1986
1987 assert!(acute_triangle.is_acute());
1988 assert!(!acute_triangle.is_obtuse());
1989 assert!(!obtuse_triangle.is_acute());
1990 assert!(obtuse_triangle.is_obtuse());
1991
1992 let [a, b, c] = [Vec3::ZERO, Vec3::new(1., 1., 0.5), Vec3::new(-3., 2.5, 1.)];
1994 let triangle = Triangle3d::new(a, b, c);
1995
1996 assert!(!triangle.is_degenerate(), "incorrectly found degenerate");
1997 assert_eq!(triangle.area(), 3.0233467, "incorrect area");
1998 assert_eq!(triangle.perimeter(), 9.832292, "incorrect perimeter");
1999 assert_eq!(
2000 triangle.circumcenter(),
2001 Vec3::new(-1., 1.75, 0.75),
2002 "incorrect circumcenter"
2003 );
2004 assert_eq!(
2005 triangle.normal(),
2006 Ok(Dir3::new_unchecked(Vec3::new(
2007 -0.04134491,
2008 -0.4134491,
2009 0.90958804
2010 ))),
2011 "incorrect normal"
2012 );
2013
2014 let zero_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::ZERO, Vec3::ZERO);
2016 assert!(
2017 zero_degenerate_triangle.is_degenerate(),
2018 "incorrect degenerate check"
2019 );
2020 assert_eq!(
2021 zero_degenerate_triangle.normal(),
2022 Err(InvalidDirectionError::Zero),
2023 "incorrect normal"
2024 );
2025 assert_eq!(
2026 zero_degenerate_triangle.largest_side(),
2027 (Vec3::ZERO, Vec3::ZERO),
2028 "incorrect largest side"
2029 );
2030
2031 let dup_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::X);
2032 assert!(
2033 dup_degenerate_triangle.is_degenerate(),
2034 "incorrect degenerate check"
2035 );
2036 assert_eq!(
2037 dup_degenerate_triangle.normal(),
2038 Err(InvalidDirectionError::Zero),
2039 "incorrect normal"
2040 );
2041 assert_eq!(
2042 dup_degenerate_triangle.largest_side(),
2043 (Vec3::ZERO, Vec3::X),
2044 "incorrect largest side"
2045 );
2046
2047 let collinear_degenerate_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::ZERO, Vec3::X);
2048 assert!(
2049 collinear_degenerate_triangle.is_degenerate(),
2050 "incorrect degenerate check"
2051 );
2052 assert_eq!(
2053 collinear_degenerate_triangle.normal(),
2054 Err(InvalidDirectionError::Zero),
2055 "incorrect normal"
2056 );
2057 assert_eq!(
2058 collinear_degenerate_triangle.largest_side(),
2059 (Vec3::NEG_X, Vec3::X),
2060 "incorrect largest side"
2061 );
2062 }
2063}