1use core::f32::consts::{FRAC_PI_3, PI};
2
3use super::{Circle, Measured2d, Measured3d, Primitive2d, Primitive3d};
4use crate::{ops, ops::FloatPow, Dir3, InvalidDirectionError, Isometry3d, Mat3, Vec2, Vec3};
5
6#[cfg(feature = "bevy_reflect")]
7use bevy_reflect::{std_traits::ReflectDefault, Reflect};
8#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
9use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
10use glam::Quat;
11
12#[derive(Clone, Copy, Debug, PartialEq)]
14#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
15#[cfg_attr(
16 feature = "bevy_reflect",
17 derive(Reflect),
18 reflect(Debug, PartialEq, Default)
19)]
20#[cfg_attr(
21 all(feature = "serialize", feature = "bevy_reflect"),
22 reflect(Serialize, Deserialize)
23)]
24pub struct Sphere {
25 pub radius: f32,
27}
28impl Primitive3d for Sphere {}
29
30impl Default for Sphere {
31 fn default() -> Self {
33 Self { radius: 0.5 }
34 }
35}
36
37impl Sphere {
38 #[inline(always)]
40 pub const fn new(radius: f32) -> Self {
41 Self { radius }
42 }
43
44 #[inline(always)]
46 pub fn diameter(&self) -> f32 {
47 2.0 * self.radius
48 }
49
50 #[inline(always)]
55 pub fn closest_point(&self, point: Vec3) -> Vec3 {
56 let distance_squared = point.length_squared();
57
58 if distance_squared <= self.radius.squared() {
59 point
61 } else {
62 let dir_to_point = point / distance_squared.sqrt();
65 self.radius * dir_to_point
66 }
67 }
68}
69
70impl Measured3d for Sphere {
71 #[inline(always)]
73 fn area(&self) -> f32 {
74 4.0 * PI * self.radius.squared()
75 }
76
77 #[inline(always)]
79 fn volume(&self) -> f32 {
80 4.0 * FRAC_PI_3 * self.radius.cubed()
81 }
82}
83
84#[derive(Clone, Copy, Debug, PartialEq)]
86#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
87#[cfg_attr(
88 feature = "bevy_reflect",
89 derive(Reflect),
90 reflect(Debug, PartialEq, Default)
91)]
92#[cfg_attr(
93 all(feature = "serialize", feature = "bevy_reflect"),
94 reflect(Serialize, Deserialize)
95)]
96pub struct Plane3d {
97 pub normal: Dir3,
99 pub half_size: Vec2,
101}
102impl Primitive3d for Plane3d {}
103
104impl Default for Plane3d {
105 fn default() -> Self {
107 Self {
108 normal: Dir3::Y,
109 half_size: Vec2::splat(0.5),
110 }
111 }
112}
113
114impl Plane3d {
115 #[inline(always)]
121 pub fn new(normal: Vec3, half_size: Vec2) -> Self {
122 Self {
123 normal: Dir3::new(normal).expect("normal must be nonzero and finite"),
124 half_size,
125 }
126 }
127
128 #[inline(always)]
139 pub fn from_points(a: Vec3, b: Vec3, c: Vec3) -> (Self, Vec3) {
140 let normal = Dir3::new((b - a).cross(c - a)).expect(
141 "finite plane must be defined by three finite points that don't lie on the same line",
142 );
143 let translation = (a + b + c) / 3.0;
144
145 (
146 Self {
147 normal,
148 ..Default::default()
149 },
150 translation,
151 )
152 }
153}
154
155#[derive(Clone, Copy, Debug, PartialEq)]
158#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
159#[cfg_attr(
160 feature = "bevy_reflect",
161 derive(Reflect),
162 reflect(Debug, PartialEq, Default)
163)]
164#[cfg_attr(
165 all(feature = "serialize", feature = "bevy_reflect"),
166 reflect(Serialize, Deserialize)
167)]
168pub struct InfinitePlane3d {
169 pub normal: Dir3,
171}
172impl Primitive3d for InfinitePlane3d {}
173
174impl Default for InfinitePlane3d {
175 fn default() -> Self {
177 Self { normal: Dir3::Y }
178 }
179}
180
181impl InfinitePlane3d {
182 #[inline(always)]
188 pub fn new<T: TryInto<Dir3>>(normal: T) -> Self
189 where
190 <T as TryInto<Dir3>>::Error: core::fmt::Debug,
191 {
192 Self {
193 normal: normal
194 .try_into()
195 .expect("normal must be nonzero and finite"),
196 }
197 }
198
199 #[inline(always)]
210 pub fn from_points(a: Vec3, b: Vec3, c: Vec3) -> (Self, Vec3) {
211 let normal = Dir3::new((b - a).cross(c - a)).expect(
212 "infinite plane must be defined by three finite points that don't lie on the same line",
213 );
214 let translation = (a + b + c) / 3.0;
215
216 (Self { normal }, translation)
217 }
218
219 #[inline]
223 pub fn signed_distance(&self, isometry: impl Into<Isometry3d>, point: Vec3) -> f32 {
224 let isometry = isometry.into();
225 self.normal.dot(isometry.inverse() * point)
226 }
227
228 #[inline]
232 pub fn project_point(&self, isometry: impl Into<Isometry3d>, point: Vec3) -> Vec3 {
233 point - self.normal * self.signed_distance(isometry, point)
234 }
235
236 #[inline]
261 pub fn isometry_into_xy(&self, origin: Vec3) -> Isometry3d {
262 let rotation = Quat::from_rotation_arc(self.normal.as_vec3(), Vec3::Z);
263 let transformed_origin = rotation * origin;
264 Isometry3d::new(-Vec3::Z * transformed_origin.z, rotation)
265 }
266
267 #[inline]
292 pub fn isometry_from_xy(&self, origin: Vec3) -> Isometry3d {
293 self.isometry_into_xy(origin).inverse()
294 }
295
296 #[inline]
324 pub fn isometries_xy(&self, origin: Vec3) -> (Isometry3d, Isometry3d) {
325 let projection = self.isometry_into_xy(origin);
326 (projection, projection.inverse())
327 }
328}
329
330#[derive(Clone, Copy, Debug, PartialEq)]
334#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
335#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
336#[cfg_attr(
337 all(feature = "serialize", feature = "bevy_reflect"),
338 reflect(Serialize, Deserialize)
339)]
340pub struct Line3d {
341 pub direction: Dir3,
343}
344impl Primitive3d for Line3d {}
345
346#[doc(alias = "LineSegment3d")]
348#[derive(Clone, Copy, Debug, PartialEq)]
349#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
350#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
351#[cfg_attr(
352 all(feature = "serialize", feature = "bevy_reflect"),
353 reflect(Serialize, Deserialize)
354)]
355pub struct Segment3d {
356 pub direction: Dir3,
358 pub half_length: f32,
361}
362impl Primitive3d for Segment3d {}
363
364impl Segment3d {
365 #[inline(always)]
367 pub fn new(direction: Dir3, length: f32) -> Self {
368 Self {
369 direction,
370 half_length: length / 2.0,
371 }
372 }
373
374 #[inline(always)]
380 pub fn from_points(point1: Vec3, point2: Vec3) -> (Self, Vec3) {
381 let diff = point2 - point1;
382 let length = diff.length();
383
384 (
385 Self::new(Dir3::new_unchecked(diff / length), length),
387 (point1 + point2) / 2.,
388 )
389 }
390
391 #[inline(always)]
393 pub fn point1(&self) -> Vec3 {
394 *self.direction * -self.half_length
395 }
396
397 #[inline(always)]
399 pub fn point2(&self) -> Vec3 {
400 *self.direction * self.half_length
401 }
402}
403
404#[derive(Clone, Debug, PartialEq)]
408#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
409#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
410#[cfg_attr(
411 all(feature = "serialize", feature = "bevy_reflect"),
412 reflect(Serialize, Deserialize)
413)]
414pub struct Polyline3d<const N: usize> {
415 #[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))]
417 pub vertices: [Vec3; N],
418}
419impl<const N: usize> Primitive3d for Polyline3d<N> {}
420
421impl<const N: usize> FromIterator<Vec3> for Polyline3d<N> {
422 fn from_iter<I: IntoIterator<Item = Vec3>>(iter: I) -> Self {
423 let mut vertices: [Vec3; N] = [Vec3::ZERO; N];
424
425 for (index, i) in iter.into_iter().take(N).enumerate() {
426 vertices[index] = i;
427 }
428 Self { vertices }
429 }
430}
431
432impl<const N: usize> Polyline3d<N> {
433 pub fn new(vertices: impl IntoIterator<Item = Vec3>) -> Self {
435 Self::from_iter(vertices)
436 }
437}
438
439#[derive(Clone, Debug, PartialEq)]
444#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
445pub struct BoxedPolyline3d {
446 pub vertices: Box<[Vec3]>,
448}
449impl Primitive3d for BoxedPolyline3d {}
450
451impl FromIterator<Vec3> for BoxedPolyline3d {
452 fn from_iter<I: IntoIterator<Item = Vec3>>(iter: I) -> Self {
453 let vertices: Vec<Vec3> = iter.into_iter().collect();
454 Self {
455 vertices: vertices.into_boxed_slice(),
456 }
457 }
458}
459
460impl BoxedPolyline3d {
461 pub fn new(vertices: impl IntoIterator<Item = Vec3>) -> Self {
463 Self::from_iter(vertices)
464 }
465}
466
467#[derive(Clone, Copy, Debug, PartialEq)]
470#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
471#[cfg_attr(
472 feature = "bevy_reflect",
473 derive(Reflect),
474 reflect(Debug, PartialEq, Default)
475)]
476#[cfg_attr(
477 all(feature = "serialize", feature = "bevy_reflect"),
478 reflect(Serialize, Deserialize)
479)]
480pub struct Cuboid {
481 pub half_size: Vec3,
483}
484impl Primitive3d for Cuboid {}
485
486impl Default for Cuboid {
487 fn default() -> Self {
489 Self {
490 half_size: Vec3::splat(0.5),
491 }
492 }
493}
494
495impl Cuboid {
496 #[inline(always)]
498 pub fn new(x_length: f32, y_length: f32, z_length: f32) -> Self {
499 Self::from_size(Vec3::new(x_length, y_length, z_length))
500 }
501
502 #[inline(always)]
504 pub fn from_size(size: Vec3) -> Self {
505 Self {
506 half_size: size / 2.0,
507 }
508 }
509
510 #[inline(always)]
512 pub fn from_corners(point1: Vec3, point2: Vec3) -> Self {
513 Self {
514 half_size: (point2 - point1).abs() / 2.0,
515 }
516 }
517
518 #[inline(always)]
521 pub fn from_length(length: f32) -> Self {
522 Self {
523 half_size: Vec3::splat(length / 2.0),
524 }
525 }
526
527 #[inline(always)]
529 pub fn size(&self) -> Vec3 {
530 2.0 * self.half_size
531 }
532
533 #[inline(always)]
538 pub fn closest_point(&self, point: Vec3) -> Vec3 {
539 point.clamp(-self.half_size, self.half_size)
541 }
542}
543
544impl Measured3d for Cuboid {
545 #[inline(always)]
547 fn area(&self) -> f32 {
548 8.0 * (self.half_size.x * self.half_size.y
549 + self.half_size.y * self.half_size.z
550 + self.half_size.x * self.half_size.z)
551 }
552
553 #[inline(always)]
555 fn volume(&self) -> f32 {
556 8.0 * self.half_size.x * self.half_size.y * self.half_size.z
557 }
558}
559
560#[derive(Clone, Copy, Debug, PartialEq)]
562#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
563#[cfg_attr(
564 feature = "bevy_reflect",
565 derive(Reflect),
566 reflect(Debug, PartialEq, Default)
567)]
568#[cfg_attr(
569 all(feature = "serialize", feature = "bevy_reflect"),
570 reflect(Serialize, Deserialize)
571)]
572pub struct Cylinder {
573 pub radius: f32,
575 pub half_height: f32,
577}
578impl Primitive3d for Cylinder {}
579
580impl Default for Cylinder {
581 fn default() -> Self {
583 Self {
584 radius: 0.5,
585 half_height: 0.5,
586 }
587 }
588}
589
590impl Cylinder {
591 #[inline(always)]
593 pub fn new(radius: f32, height: f32) -> Self {
594 Self {
595 radius,
596 half_height: height / 2.0,
597 }
598 }
599
600 #[inline(always)]
602 pub fn base(&self) -> Circle {
603 Circle {
604 radius: self.radius,
605 }
606 }
607
608 #[inline(always)]
611 #[doc(alias = "side_area")]
612 pub fn lateral_area(&self) -> f32 {
613 4.0 * PI * self.radius * self.half_height
614 }
615
616 #[inline(always)]
618 pub fn base_area(&self) -> f32 {
619 PI * self.radius.squared()
620 }
621}
622
623impl Measured3d for Cylinder {
624 #[inline(always)]
626 fn area(&self) -> f32 {
627 2.0 * PI * self.radius * (self.radius + 2.0 * self.half_height)
628 }
629
630 #[inline(always)]
632 fn volume(&self) -> f32 {
633 self.base_area() * 2.0 * self.half_height
634 }
635}
636
637#[derive(Clone, Copy, Debug, PartialEq)]
640#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
641#[cfg_attr(
642 feature = "bevy_reflect",
643 derive(Reflect),
644 reflect(Debug, PartialEq, Default)
645)]
646#[cfg_attr(
647 all(feature = "serialize", feature = "bevy_reflect"),
648 reflect(Serialize, Deserialize)
649)]
650pub struct Capsule3d {
651 pub radius: f32,
653 pub half_length: f32,
655}
656impl Primitive3d for Capsule3d {}
657
658impl Default for Capsule3d {
659 fn default() -> Self {
662 Self {
663 radius: 0.5,
664 half_length: 0.5,
665 }
666 }
667}
668
669impl Capsule3d {
670 pub fn new(radius: f32, length: f32) -> Self {
672 Self {
673 radius,
674 half_length: length / 2.0,
675 }
676 }
677
678 #[inline(always)]
681 pub fn to_cylinder(&self) -> Cylinder {
682 Cylinder {
683 radius: self.radius,
684 half_height: self.half_length,
685 }
686 }
687}
688
689impl Measured3d for Capsule3d {
690 #[inline(always)]
692 fn area(&self) -> f32 {
693 4.0 * PI * self.radius * (self.radius + self.half_length)
695 }
696
697 #[inline(always)]
699 fn volume(&self) -> f32 {
700 let diameter = self.radius * 2.0;
702 PI * self.radius * diameter * (diameter / 3.0 + self.half_length)
703 }
704}
705
706#[derive(Clone, Copy, Debug, PartialEq)]
710#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
711#[cfg_attr(
712 feature = "bevy_reflect",
713 derive(Reflect),
714 reflect(Debug, PartialEq, Default)
715)]
716#[cfg_attr(
717 all(feature = "serialize", feature = "bevy_reflect"),
718 reflect(Serialize, Deserialize)
719)]
720pub struct Cone {
721 pub radius: f32,
723 pub height: f32,
725}
726impl Primitive3d for Cone {}
727
728impl Default for Cone {
729 fn default() -> Self {
731 Self {
732 radius: 0.5,
733 height: 1.0,
734 }
735 }
736}
737
738impl Cone {
739 pub fn new(radius: f32, height: f32) -> Self {
741 Self { radius, height }
742 }
743 #[inline(always)]
745 pub fn base(&self) -> Circle {
746 Circle {
747 radius: self.radius,
748 }
749 }
750
751 #[inline(always)]
754 #[doc(alias = "side_length")]
755 pub fn slant_height(&self) -> f32 {
756 ops::hypot(self.radius, self.height)
757 }
758
759 #[inline(always)]
762 #[doc(alias = "side_area")]
763 pub fn lateral_area(&self) -> f32 {
764 PI * self.radius * self.slant_height()
765 }
766
767 #[inline(always)]
769 pub fn base_area(&self) -> f32 {
770 PI * self.radius.squared()
771 }
772}
773
774impl Measured3d for Cone {
775 #[inline(always)]
777 fn area(&self) -> f32 {
778 self.base_area() + self.lateral_area()
779 }
780
781 #[inline(always)]
783 fn volume(&self) -> f32 {
784 (self.base_area() * self.height) / 3.0
785 }
786}
787
788#[derive(Clone, Copy, Debug, PartialEq)]
792#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
793#[cfg_attr(
794 feature = "bevy_reflect",
795 derive(Reflect),
796 reflect(Debug, PartialEq, Default)
797)]
798#[cfg_attr(
799 all(feature = "serialize", feature = "bevy_reflect"),
800 reflect(Serialize, Deserialize)
801)]
802pub struct ConicalFrustum {
803 pub radius_top: f32,
805 pub radius_bottom: f32,
807 pub height: f32,
809}
810impl Primitive3d for ConicalFrustum {}
811
812impl Default for ConicalFrustum {
813 fn default() -> Self {
815 Self {
816 radius_top: 0.25,
817 radius_bottom: 0.5,
818 height: 0.5,
819 }
820 }
821}
822
823#[derive(Clone, Copy, Debug, PartialEq, Eq)]
825pub enum TorusKind {
826 Ring,
829 Horn,
832 Spindle,
835 Invalid,
839}
840
841#[derive(Clone, Copy, Debug, PartialEq)]
844#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
845#[cfg_attr(
846 feature = "bevy_reflect",
847 derive(Reflect),
848 reflect(Debug, PartialEq, Default)
849)]
850#[cfg_attr(
851 all(feature = "serialize", feature = "bevy_reflect"),
852 reflect(Serialize, Deserialize)
853)]
854pub struct Torus {
855 #[doc(
857 alias = "ring_radius",
858 alias = "tube_radius",
859 alias = "cross_section_radius"
860 )]
861 pub minor_radius: f32,
862 #[doc(alias = "radius_of_revolution")]
864 pub major_radius: f32,
865}
866impl Primitive3d for Torus {}
867
868impl Default for Torus {
869 fn default() -> Self {
871 Self {
872 minor_radius: 0.25,
873 major_radius: 0.75,
874 }
875 }
876}
877
878impl Torus {
879 #[inline(always)]
884 pub fn new(inner_radius: f32, outer_radius: f32) -> Self {
885 let minor_radius = (outer_radius - inner_radius) / 2.0;
886 let major_radius = outer_radius - minor_radius;
887
888 Self {
889 minor_radius,
890 major_radius,
891 }
892 }
893
894 #[inline(always)]
898 pub fn inner_radius(&self) -> f32 {
899 self.major_radius - self.minor_radius
900 }
901
902 #[inline(always)]
906 pub fn outer_radius(&self) -> f32 {
907 self.major_radius + self.minor_radius
908 }
909
910 #[inline(always)]
919 pub fn kind(&self) -> TorusKind {
920 if self.minor_radius <= 0.0
922 || !self.minor_radius.is_finite()
923 || self.major_radius <= 0.0
924 || !self.major_radius.is_finite()
925 {
926 return TorusKind::Invalid;
927 }
928
929 match self.major_radius.partial_cmp(&self.minor_radius).unwrap() {
930 core::cmp::Ordering::Greater => TorusKind::Ring,
931 core::cmp::Ordering::Equal => TorusKind::Horn,
932 core::cmp::Ordering::Less => TorusKind::Spindle,
933 }
934 }
935}
936
937impl Measured3d for Torus {
938 #[inline(always)]
941 fn area(&self) -> f32 {
942 4.0 * PI.squared() * self.major_radius * self.minor_radius
943 }
944
945 #[inline(always)]
948 fn volume(&self) -> f32 {
949 2.0 * PI.squared() * self.major_radius * self.minor_radius.squared()
950 }
951}
952
953#[derive(Clone, Copy, Debug, PartialEq)]
955#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
956#[cfg_attr(
957 feature = "bevy_reflect",
958 derive(Reflect),
959 reflect(Debug, PartialEq, Default)
960)]
961#[cfg_attr(
962 all(feature = "serialize", feature = "bevy_reflect"),
963 reflect(Serialize, Deserialize)
964)]
965pub struct Triangle3d {
966 pub vertices: [Vec3; 3],
968}
969
970impl Primitive3d for Triangle3d {}
971
972impl Default for Triangle3d {
973 fn default() -> Self {
975 Self {
976 vertices: [
977 Vec3::new(0.0, 0.5, 0.0),
978 Vec3::new(-0.5, -0.5, 0.0),
979 Vec3::new(0.5, -0.5, 0.0),
980 ],
981 }
982 }
983}
984
985impl Triangle3d {
986 #[inline(always)]
988 pub fn new(a: Vec3, b: Vec3, c: Vec3) -> Self {
989 Self {
990 vertices: [a, b, c],
991 }
992 }
993
994 #[inline(always)]
1004 pub fn normal(&self) -> Result<Dir3, InvalidDirectionError> {
1005 let [a, b, c] = self.vertices;
1006 let ab = b - a;
1007 let ac = c - a;
1008 Dir3::new(ab.cross(ac))
1009 }
1010
1011 #[inline(always)]
1016 pub fn is_degenerate(&self) -> bool {
1017 let [a, b, c] = self.vertices;
1018 let ab = b - a;
1019 let ac = c - a;
1020 ab.cross(ac).length() < 10e-7
1021 }
1022
1023 #[inline(always)]
1025 pub fn is_acute(&self) -> bool {
1026 let [a, b, c] = self.vertices;
1027 let ab = b - a;
1028 let bc = c - b;
1029 let ca = a - c;
1030
1031 let mut side_lengths = [
1033 ab.length_squared(),
1034 bc.length_squared(),
1035 ca.length_squared(),
1036 ];
1037 side_lengths.sort_by(|a, b| a.partial_cmp(b).unwrap());
1038 side_lengths[0] + side_lengths[1] > side_lengths[2]
1039 }
1040
1041 #[inline(always)]
1043 pub fn is_obtuse(&self) -> bool {
1044 let [a, b, c] = self.vertices;
1045 let ab = b - a;
1046 let bc = c - b;
1047 let ca = a - c;
1048
1049 let mut side_lengths = [
1051 ab.length_squared(),
1052 bc.length_squared(),
1053 ca.length_squared(),
1054 ];
1055 side_lengths.sort_by(|a, b| a.partial_cmp(b).unwrap());
1056 side_lengths[0] + side_lengths[1] < side_lengths[2]
1057 }
1058
1059 #[inline(always)]
1061 pub fn reverse(&mut self) {
1062 self.vertices.swap(0, 2);
1063 }
1064
1065 #[inline(always)]
1067 #[must_use]
1068 pub fn reversed(mut self) -> Triangle3d {
1069 self.reverse();
1070 self
1071 }
1072
1073 #[doc(alias("center", "barycenter", "baricenter"))]
1078 #[inline(always)]
1079 pub fn centroid(&self) -> Vec3 {
1080 (self.vertices[0] + self.vertices[1] + self.vertices[2]) / 3.0
1081 }
1082
1083 #[inline(always)]
1087 pub fn largest_side(&self) -> (Vec3, Vec3) {
1088 let [a, b, c] = self.vertices;
1089 let ab = b - a;
1090 let bc = c - b;
1091 let ca = a - c;
1092
1093 let mut largest_side_points = (a, b);
1094 let mut largest_side_length = ab.length();
1095
1096 if bc.length() > largest_side_length {
1097 largest_side_points = (b, c);
1098 largest_side_length = bc.length();
1099 }
1100
1101 if ca.length() > largest_side_length {
1102 largest_side_points = (a, c);
1103 }
1104
1105 largest_side_points
1106 }
1107
1108 #[inline(always)]
1110 pub fn circumcenter(&self) -> Vec3 {
1111 if self.is_degenerate() {
1112 let (p1, p2) = self.largest_side();
1114 return (p1 + p2) / 2.0;
1115 }
1116
1117 let [a, b, c] = self.vertices;
1118 let ab = b - a;
1119 let ac = c - a;
1120 let n = ab.cross(ac);
1121
1122 a + ((ac.length_squared() * n.cross(ab) + ab.length_squared() * ac.cross(ab).cross(ac))
1124 / (2.0 * n.length_squared()))
1125 }
1126}
1127
1128impl Measured2d for Triangle3d {
1129 #[inline(always)]
1131 fn area(&self) -> f32 {
1132 let [a, b, c] = self.vertices;
1133 let ab = b - a;
1134 let ac = c - a;
1135 ab.cross(ac).length() / 2.0
1136 }
1137
1138 #[inline(always)]
1140 fn perimeter(&self) -> f32 {
1141 let [a, b, c] = self.vertices;
1142 a.distance(b) + b.distance(c) + c.distance(a)
1143 }
1144}
1145
1146#[derive(Clone, Copy, Debug, PartialEq)]
1148#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1149#[cfg_attr(
1150 feature = "bevy_reflect",
1151 derive(Reflect),
1152 reflect(Debug, PartialEq, Default)
1153)]
1154#[cfg_attr(
1155 all(feature = "serialize", feature = "bevy_reflect"),
1156 reflect(Serialize, Deserialize)
1157)]
1158pub struct Tetrahedron {
1159 pub vertices: [Vec3; 4],
1161}
1162impl Primitive3d for Tetrahedron {}
1163
1164impl Default for Tetrahedron {
1165 fn default() -> Self {
1168 Self {
1169 vertices: [
1170 Vec3::new(0.5, 0.5, 0.5),
1171 Vec3::new(-0.5, 0.5, -0.5),
1172 Vec3::new(-0.5, -0.5, 0.5),
1173 Vec3::new(0.5, -0.5, -0.5),
1174 ],
1175 }
1176 }
1177}
1178
1179impl Tetrahedron {
1180 #[inline(always)]
1182 pub fn new(a: Vec3, b: Vec3, c: Vec3, d: Vec3) -> Self {
1183 Self {
1184 vertices: [a, b, c, d],
1185 }
1186 }
1187
1188 #[inline(always)]
1194 pub fn signed_volume(&self) -> f32 {
1195 let [a, b, c, d] = self.vertices;
1196 let ab = b - a;
1197 let ac = c - a;
1198 let ad = d - a;
1199 Mat3::from_cols(ab, ac, ad).determinant() / 6.0
1200 }
1201
1202 #[doc(alias("center", "barycenter", "baricenter"))]
1207 #[inline(always)]
1208 pub fn centroid(&self) -> Vec3 {
1209 (self.vertices[0] + self.vertices[1] + self.vertices[2] + self.vertices[3]) / 4.0
1210 }
1211
1212 #[inline(always)]
1218 pub fn faces(&self) -> [Triangle3d; 4] {
1219 let [a, b, c, d] = self.vertices;
1220 [
1221 Triangle3d::new(b, c, d),
1222 Triangle3d::new(a, c, d).reversed(),
1223 Triangle3d::new(a, b, d),
1224 Triangle3d::new(a, b, c).reversed(),
1225 ]
1226 }
1227}
1228
1229impl Measured3d for Tetrahedron {
1230 #[inline(always)]
1232 fn area(&self) -> f32 {
1233 let [a, b, c, d] = self.vertices;
1234 let ab = b - a;
1235 let ac = c - a;
1236 let ad = d - a;
1237 let bc = c - b;
1238 let bd = d - b;
1239 (ab.cross(ac).length()
1240 + ab.cross(ad).length()
1241 + ac.cross(ad).length()
1242 + bc.cross(bd).length())
1243 / 2.0
1244 }
1245
1246 #[inline(always)]
1248 fn volume(&self) -> f32 {
1249 self.signed_volume().abs()
1250 }
1251}
1252
1253#[doc(alias = "Prism")]
1261#[derive(Clone, Copy, Debug, PartialEq)]
1262#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1263pub struct Extrusion<T: Primitive2d> {
1264 pub base_shape: T,
1266 pub half_depth: f32,
1268}
1269impl<T: Primitive2d> Primitive3d for Extrusion<T> {}
1270
1271impl<T: Primitive2d> Extrusion<T> {
1272 pub fn new(base_shape: T, depth: f32) -> Self {
1274 Self {
1275 base_shape,
1276 half_depth: depth / 2.,
1277 }
1278 }
1279}
1280
1281impl<T: Primitive2d + Measured2d> Measured3d for Extrusion<T> {
1282 fn area(&self) -> f32 {
1284 2. * (self.base_shape.area() + self.half_depth * self.base_shape.perimeter())
1285 }
1286
1287 fn volume(&self) -> f32 {
1289 2. * self.base_shape.area() * self.half_depth
1290 }
1291}
1292
1293#[cfg(test)]
1294mod tests {
1295 use super::*;
1298 use crate::{InvalidDirectionError, Quat};
1299 use approx::assert_relative_eq;
1300
1301 #[test]
1302 fn direction_creation() {
1303 assert_eq!(Dir3::new(Vec3::X * 12.5), Ok(Dir3::X));
1304 assert_eq!(
1305 Dir3::new(Vec3::new(0.0, 0.0, 0.0)),
1306 Err(InvalidDirectionError::Zero)
1307 );
1308 assert_eq!(
1309 Dir3::new(Vec3::new(f32::INFINITY, 0.0, 0.0)),
1310 Err(InvalidDirectionError::Infinite)
1311 );
1312 assert_eq!(
1313 Dir3::new(Vec3::new(f32::NEG_INFINITY, 0.0, 0.0)),
1314 Err(InvalidDirectionError::Infinite)
1315 );
1316 assert_eq!(
1317 Dir3::new(Vec3::new(f32::NAN, 0.0, 0.0)),
1318 Err(InvalidDirectionError::NaN)
1319 );
1320 assert_eq!(Dir3::new_and_length(Vec3::X * 6.5), Ok((Dir3::X, 6.5)));
1321
1322 assert!(
1324 (Quat::from_rotation_z(core::f32::consts::FRAC_PI_2) * Dir3::X)
1325 .abs_diff_eq(Vec3::Y, 10e-6)
1326 );
1327 }
1328
1329 #[test]
1330 fn cuboid_closest_point() {
1331 let cuboid = Cuboid::new(2.0, 2.0, 2.0);
1332 assert_eq!(cuboid.closest_point(Vec3::X * 10.0), Vec3::X);
1333 assert_eq!(cuboid.closest_point(Vec3::NEG_ONE * 10.0), Vec3::NEG_ONE);
1334 assert_eq!(
1335 cuboid.closest_point(Vec3::new(0.25, 0.1, 0.3)),
1336 Vec3::new(0.25, 0.1, 0.3)
1337 );
1338 }
1339
1340 #[test]
1341 fn sphere_closest_point() {
1342 let sphere = Sphere { radius: 1.0 };
1343 assert_eq!(sphere.closest_point(Vec3::X * 10.0), Vec3::X);
1344 assert_eq!(
1345 sphere.closest_point(Vec3::NEG_ONE * 10.0),
1346 Vec3::NEG_ONE.normalize()
1347 );
1348 assert_eq!(
1349 sphere.closest_point(Vec3::new(0.25, 0.1, 0.3)),
1350 Vec3::new(0.25, 0.1, 0.3)
1351 );
1352 }
1353
1354 #[test]
1355 fn sphere_math() {
1356 let sphere = Sphere { radius: 4.0 };
1357 assert_eq!(sphere.diameter(), 8.0, "incorrect diameter");
1358 assert_eq!(sphere.area(), 201.06193, "incorrect area");
1359 assert_eq!(sphere.volume(), 268.08257, "incorrect volume");
1360 }
1361
1362 #[test]
1363 fn plane_from_points() {
1364 let (plane, translation) = Plane3d::from_points(Vec3::X, Vec3::Z, Vec3::NEG_X);
1365 assert_eq!(*plane.normal, Vec3::NEG_Y, "incorrect normal");
1366 assert_eq!(plane.half_size, Vec2::new(0.5, 0.5), "incorrect half size");
1367 assert_eq!(translation, Vec3::Z * 0.33333334, "incorrect translation");
1368 }
1369
1370 #[test]
1371 fn infinite_plane_math() {
1372 let (plane, origin) = InfinitePlane3d::from_points(Vec3::X, Vec3::Z, Vec3::NEG_X);
1373 assert_eq!(*plane.normal, Vec3::NEG_Y, "incorrect normal");
1374 assert_eq!(origin, Vec3::Z * 0.33333334, "incorrect translation");
1375
1376 let point_in_plane = Vec3::X + Vec3::Z;
1377 assert_eq!(
1378 plane.signed_distance(origin, point_in_plane),
1379 0.0,
1380 "incorrect distance"
1381 );
1382 assert_eq!(
1383 plane.project_point(origin, point_in_plane),
1384 point_in_plane,
1385 "incorrect point"
1386 );
1387
1388 let point_outside = Vec3::Y;
1389 assert_eq!(
1390 plane.signed_distance(origin, point_outside),
1391 -1.0,
1392 "incorrect distance"
1393 );
1394 assert_eq!(
1395 plane.project_point(origin, point_outside),
1396 Vec3::ZERO,
1397 "incorrect point"
1398 );
1399
1400 let point_outside = Vec3::NEG_Y;
1401 assert_eq!(
1402 plane.signed_distance(origin, point_outside),
1403 1.0,
1404 "incorrect distance"
1405 );
1406 assert_eq!(
1407 plane.project_point(origin, point_outside),
1408 Vec3::ZERO,
1409 "incorrect point"
1410 );
1411
1412 let area_f = |[a, b, c]: [Vec3; 3]| (a - b).cross(a - c).length() * 0.5;
1413 let (proj, inj) = plane.isometries_xy(origin);
1414
1415 let triangle = [Vec3::X, Vec3::Y, Vec3::ZERO];
1416 assert_eq!(area_f(triangle), 0.5, "incorrect area");
1417
1418 let triangle_proj = triangle.map(|vec3| proj * vec3);
1419 assert_relative_eq!(area_f(triangle_proj), 0.5);
1420
1421 let triangle_proj_inj = triangle_proj.map(|vec3| inj * vec3);
1422 assert_relative_eq!(area_f(triangle_proj_inj), 0.5);
1423 }
1424
1425 #[test]
1426 fn cuboid_math() {
1427 let cuboid = Cuboid::new(3.0, 7.0, 2.0);
1428 assert_eq!(
1429 cuboid,
1430 Cuboid::from_corners(Vec3::new(-1.5, -3.5, -1.0), Vec3::new(1.5, 3.5, 1.0)),
1431 "incorrect dimensions when created from corners"
1432 );
1433 assert_eq!(cuboid.area(), 82.0, "incorrect area");
1434 assert_eq!(cuboid.volume(), 42.0, "incorrect volume");
1435 }
1436
1437 #[test]
1438 fn cylinder_math() {
1439 let cylinder = Cylinder::new(2.0, 9.0);
1440 assert_eq!(
1441 cylinder.base(),
1442 Circle { radius: 2.0 },
1443 "base produces incorrect circle"
1444 );
1445 assert_eq!(
1446 cylinder.lateral_area(),
1447 113.097336,
1448 "incorrect lateral area"
1449 );
1450 assert_eq!(cylinder.base_area(), 12.566371, "incorrect base area");
1451 assert_relative_eq!(cylinder.area(), 138.23007);
1452 assert_eq!(cylinder.volume(), 113.097336, "incorrect volume");
1453 }
1454
1455 #[test]
1456 fn capsule_math() {
1457 let capsule = Capsule3d::new(2.0, 9.0);
1458 assert_eq!(
1459 capsule.to_cylinder(),
1460 Cylinder::new(2.0, 9.0),
1461 "cylinder wasn't created correctly from a capsule"
1462 );
1463 assert_eq!(capsule.area(), 163.36282, "incorrect area");
1464 assert_relative_eq!(capsule.volume(), 146.60765);
1465 }
1466
1467 #[test]
1468 fn cone_math() {
1469 let cone = Cone {
1470 radius: 2.0,
1471 height: 9.0,
1472 };
1473 assert_eq!(
1474 cone.base(),
1475 Circle { radius: 2.0 },
1476 "base produces incorrect circle"
1477 );
1478 assert_eq!(cone.slant_height(), 9.219544, "incorrect slant height");
1479 assert_eq!(cone.lateral_area(), 57.92811, "incorrect lateral area");
1480 assert_eq!(cone.base_area(), 12.566371, "incorrect base area");
1481 assert_relative_eq!(cone.area(), 70.49447);
1482 assert_eq!(cone.volume(), 37.699111, "incorrect volume");
1483 }
1484
1485 #[test]
1486 fn torus_math() {
1487 let torus = Torus {
1488 minor_radius: 0.3,
1489 major_radius: 2.8,
1490 };
1491 assert_eq!(torus.inner_radius(), 2.5, "incorrect inner radius");
1492 assert_eq!(torus.outer_radius(), 3.1, "incorrect outer radius");
1493 assert_eq!(torus.kind(), TorusKind::Ring, "incorrect torus kind");
1494 assert_eq!(
1495 Torus::new(0.0, 1.0).kind(),
1496 TorusKind::Horn,
1497 "incorrect torus kind"
1498 );
1499 assert_eq!(
1500 Torus::new(-0.5, 1.0).kind(),
1501 TorusKind::Spindle,
1502 "incorrect torus kind"
1503 );
1504 assert_eq!(
1505 Torus::new(1.5, 1.0).kind(),
1506 TorusKind::Invalid,
1507 "torus should be invalid"
1508 );
1509 assert_relative_eq!(torus.area(), 33.16187);
1510 assert_relative_eq!(torus.volume(), 4.97428, epsilon = 0.00001);
1511 }
1512
1513 #[test]
1514 fn tetrahedron_math() {
1515 let tetrahedron = Tetrahedron {
1516 vertices: [
1517 Vec3::new(0.3, 1.0, 1.7),
1518 Vec3::new(-2.0, -1.0, 0.0),
1519 Vec3::new(1.8, 0.5, 1.0),
1520 Vec3::new(-1.0, -2.0, 3.5),
1521 ],
1522 };
1523 assert_eq!(tetrahedron.area(), 19.251068, "incorrect area");
1524 assert_eq!(tetrahedron.volume(), 3.2058334, "incorrect volume");
1525 assert_eq!(
1526 tetrahedron.signed_volume(),
1527 3.2058334,
1528 "incorrect signed volume"
1529 );
1530 assert_relative_eq!(tetrahedron.centroid(), Vec3::new(-0.225, -0.375, 1.55));
1531
1532 assert_eq!(Tetrahedron::default().area(), 3.4641016, "incorrect area");
1533 assert_eq!(
1534 Tetrahedron::default().volume(),
1535 0.33333334,
1536 "incorrect volume"
1537 );
1538 assert_eq!(
1539 Tetrahedron::default().signed_volume(),
1540 -0.33333334,
1541 "incorrect signed volume"
1542 );
1543 assert_relative_eq!(Tetrahedron::default().centroid(), Vec3::ZERO);
1544 }
1545
1546 #[test]
1547 fn extrusion_math() {
1548 let circle = Circle::new(0.75);
1549 let cylinder = Extrusion::new(circle, 2.5);
1550 assert_eq!(cylinder.area(), 15.315264, "incorrect surface area");
1551 assert_eq!(cylinder.volume(), 4.417865, "incorrect volume");
1552
1553 let annulus = crate::primitives::Annulus::new(0.25, 1.375);
1554 let tube = Extrusion::new(annulus, 0.333);
1555 assert_eq!(tube.area(), 14.886437, "incorrect surface area");
1556 assert_eq!(tube.volume(), 1.9124937, "incorrect volume");
1557
1558 let polygon = crate::primitives::RegularPolygon::new(3.8, 7);
1559 let regular_prism = Extrusion::new(polygon, 1.25);
1560 assert_eq!(regular_prism.area(), 107.8808, "incorrect surface area");
1561 assert_eq!(regular_prism.volume(), 49.392204, "incorrect volume");
1562 }
1563
1564 #[test]
1565 fn triangle_math() {
1566 let mut default_triangle = Triangle3d::default();
1568 let reverse_default_triangle = Triangle3d::new(
1569 Vec3::new(0.5, -0.5, 0.0),
1570 Vec3::new(-0.5, -0.5, 0.0),
1571 Vec3::new(0.0, 0.5, 0.0),
1572 );
1573 assert_eq!(default_triangle.area(), 0.5, "incorrect area");
1574 assert_relative_eq!(
1575 default_triangle.perimeter(),
1576 1.0 + 2.0 * 1.25_f32.sqrt(),
1577 epsilon = 10e-9
1578 );
1579 assert_eq!(default_triangle.normal(), Ok(Dir3::Z), "incorrect normal");
1580 assert!(
1581 !default_triangle.is_degenerate(),
1582 "incorrect degenerate check"
1583 );
1584 assert_eq!(
1585 default_triangle.centroid(),
1586 Vec3::new(0.0, -0.16666667, 0.0),
1587 "incorrect centroid"
1588 );
1589 assert_eq!(
1590 default_triangle.largest_side(),
1591 (Vec3::new(0.0, 0.5, 0.0), Vec3::new(-0.5, -0.5, 0.0))
1592 );
1593 default_triangle.reverse();
1594 assert_eq!(
1595 default_triangle, reverse_default_triangle,
1596 "incorrect reverse"
1597 );
1598 assert_eq!(
1599 default_triangle.circumcenter(),
1600 Vec3::new(0.0, -0.125, 0.0),
1601 "incorrect circumcenter"
1602 );
1603
1604 let right_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::Y);
1606 let obtuse_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::X, Vec3::new(0.0, 0.1, 0.0));
1607 let acute_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::new(0.5, 5.0, 0.0));
1608
1609 assert_eq!(
1610 right_triangle.circumcenter(),
1611 Vec3::new(0.5, 0.5, 0.0),
1612 "incorrect circumcenter"
1613 );
1614 assert_eq!(
1615 obtuse_triangle.circumcenter(),
1616 Vec3::new(0.0, -4.95, 0.0),
1617 "incorrect circumcenter"
1618 );
1619 assert_eq!(
1620 acute_triangle.circumcenter(),
1621 Vec3::new(0.5, 2.475, 0.0),
1622 "incorrect circumcenter"
1623 );
1624
1625 assert!(acute_triangle.is_acute());
1626 assert!(!acute_triangle.is_obtuse());
1627 assert!(!obtuse_triangle.is_acute());
1628 assert!(obtuse_triangle.is_obtuse());
1629
1630 let [a, b, c] = [Vec3::ZERO, Vec3::new(1., 1., 0.5), Vec3::new(-3., 2.5, 1.)];
1632 let triangle = Triangle3d::new(a, b, c);
1633
1634 assert!(!triangle.is_degenerate(), "incorrectly found degenerate");
1635 assert_eq!(triangle.area(), 3.0233467, "incorrect area");
1636 assert_eq!(triangle.perimeter(), 9.832292, "incorrect perimeter");
1637 assert_eq!(
1638 triangle.circumcenter(),
1639 Vec3::new(-1., 1.75, 0.75),
1640 "incorrect circumcenter"
1641 );
1642 assert_eq!(
1643 triangle.normal(),
1644 Ok(Dir3::new_unchecked(Vec3::new(
1645 -0.04134491,
1646 -0.4134491,
1647 0.90958804
1648 ))),
1649 "incorrect normal"
1650 );
1651
1652 let zero_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::ZERO, Vec3::ZERO);
1654 assert!(
1655 zero_degenerate_triangle.is_degenerate(),
1656 "incorrect degenerate check"
1657 );
1658 assert_eq!(
1659 zero_degenerate_triangle.normal(),
1660 Err(InvalidDirectionError::Zero),
1661 "incorrect normal"
1662 );
1663 assert_eq!(
1664 zero_degenerate_triangle.largest_side(),
1665 (Vec3::ZERO, Vec3::ZERO),
1666 "incorrect largest side"
1667 );
1668
1669 let dup_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::X);
1670 assert!(
1671 dup_degenerate_triangle.is_degenerate(),
1672 "incorrect degenerate check"
1673 );
1674 assert_eq!(
1675 dup_degenerate_triangle.normal(),
1676 Err(InvalidDirectionError::Zero),
1677 "incorrect normal"
1678 );
1679 assert_eq!(
1680 dup_degenerate_triangle.largest_side(),
1681 (Vec3::ZERO, Vec3::X),
1682 "incorrect largest side"
1683 );
1684
1685 let collinear_degenerate_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::ZERO, Vec3::X);
1686 assert!(
1687 collinear_degenerate_triangle.is_degenerate(),
1688 "incorrect degenerate check"
1689 );
1690 assert_eq!(
1691 collinear_degenerate_triangle.normal(),
1692 Err(InvalidDirectionError::Zero),
1693 "incorrect normal"
1694 );
1695 assert_eq!(
1696 collinear_degenerate_triangle.largest_side(),
1697 (Vec3::NEG_X, Vec3::X),
1698 "incorrect largest side"
1699 );
1700 }
1701}