1use std::f32::consts::{FRAC_PI_3, PI};
2
3use super::{Circle, Measured2d, Measured3d, Primitive2d, Primitive3d};
4use crate::{Dir3, InvalidDirectionError, 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};
10
11#[derive(Clone, Copy, Debug, PartialEq)]
13#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
14#[cfg_attr(
15 feature = "bevy_reflect",
16 derive(Reflect),
17 reflect(Debug, PartialEq, Default)
18)]
19#[cfg_attr(
20 all(feature = "serialize", feature = "bevy_reflect"),
21 reflect(Serialize, Deserialize)
22)]
23pub struct Sphere {
24 pub radius: f32,
26}
27impl Primitive3d for Sphere {}
28
29impl Default for Sphere {
30 fn default() -> Self {
32 Self { radius: 0.5 }
33 }
34}
35
36impl Sphere {
37 #[inline(always)]
39 pub const fn new(radius: f32) -> Self {
40 Self { radius }
41 }
42
43 #[inline(always)]
45 pub fn diameter(&self) -> f32 {
46 2.0 * self.radius
47 }
48
49 #[inline(always)]
54 pub fn closest_point(&self, point: Vec3) -> Vec3 {
55 let distance_squared = point.length_squared();
56
57 if distance_squared <= self.radius.powi(2) {
58 point
60 } else {
61 let dir_to_point = point / distance_squared.sqrt();
64 self.radius * dir_to_point
65 }
66 }
67}
68
69impl Measured3d for Sphere {
70 #[inline(always)]
72 fn area(&self) -> f32 {
73 4.0 * PI * self.radius.powi(2)
74 }
75
76 #[inline(always)]
78 fn volume(&self) -> f32 {
79 4.0 * FRAC_PI_3 * self.radius.powi(3)
80 }
81}
82
83#[derive(Clone, Copy, Debug, PartialEq)]
85#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
86#[cfg_attr(
87 feature = "bevy_reflect",
88 derive(Reflect),
89 reflect(Debug, PartialEq, Default)
90)]
91#[cfg_attr(
92 all(feature = "serialize", feature = "bevy_reflect"),
93 reflect(Serialize, Deserialize)
94)]
95pub struct Plane3d {
96 pub normal: Dir3,
98 pub half_size: Vec2,
100}
101impl Primitive3d for Plane3d {}
102
103impl Default for Plane3d {
104 fn default() -> Self {
106 Self {
107 normal: Dir3::Y,
108 half_size: Vec2::splat(0.5),
109 }
110 }
111}
112
113impl Plane3d {
114 #[inline(always)]
120 pub fn new(normal: Vec3, half_size: Vec2) -> Self {
121 Self {
122 normal: Dir3::new(normal).expect("normal must be nonzero and finite"),
123 half_size,
124 }
125 }
126
127 #[inline(always)]
138 pub fn from_points(a: Vec3, b: Vec3, c: Vec3) -> (Self, Vec3) {
139 let normal = Dir3::new((b - a).cross(c - a)).expect(
140 "finite plane must be defined by three finite points that don't lie on the same line",
141 );
142 let translation = (a + b + c) / 3.0;
143
144 (
145 Self {
146 normal,
147 ..Default::default()
148 },
149 translation,
150 )
151 }
152}
153
154#[derive(Clone, Copy, Debug, PartialEq)]
157#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
158#[cfg_attr(
159 feature = "bevy_reflect",
160 derive(Reflect),
161 reflect(Debug, PartialEq, Default)
162)]
163#[cfg_attr(
164 all(feature = "serialize", feature = "bevy_reflect"),
165 reflect(Serialize, Deserialize)
166)]
167pub struct InfinitePlane3d {
168 pub normal: Dir3,
170}
171impl Primitive3d for InfinitePlane3d {}
172
173impl Default for InfinitePlane3d {
174 fn default() -> Self {
176 Self { normal: Dir3::Y }
177 }
178}
179
180impl InfinitePlane3d {
181 #[inline(always)]
187 pub fn new<T: TryInto<Dir3>>(normal: T) -> Self
188 where
189 <T as TryInto<Dir3>>::Error: std::fmt::Debug,
190 {
191 Self {
192 normal: normal
193 .try_into()
194 .expect("normal must be nonzero and finite"),
195 }
196 }
197
198 #[inline(always)]
209 pub fn from_points(a: Vec3, b: Vec3, c: Vec3) -> (Self, Vec3) {
210 let normal = Dir3::new((b - a).cross(c - a)).expect(
211 "infinite plane must be defined by three finite points that don't lie on the same line",
212 );
213 let translation = (a + b + c) / 3.0;
214
215 (Self { normal }, translation)
216 }
217}
218
219#[derive(Clone, Copy, Debug, PartialEq)]
223#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
224#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
225#[cfg_attr(
226 all(feature = "serialize", feature = "bevy_reflect"),
227 reflect(Serialize, Deserialize)
228)]
229pub struct Line3d {
230 pub direction: Dir3,
232}
233impl Primitive3d for Line3d {}
234
235#[doc(alias = "LineSegment3d")]
237#[derive(Clone, Copy, Debug, PartialEq)]
238#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
239#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
240#[cfg_attr(
241 all(feature = "serialize", feature = "bevy_reflect"),
242 reflect(Serialize, Deserialize)
243)]
244pub struct Segment3d {
245 pub direction: Dir3,
247 pub half_length: f32,
250}
251impl Primitive3d for Segment3d {}
252
253impl Segment3d {
254 #[inline(always)]
256 pub fn new(direction: Dir3, length: f32) -> Self {
257 Self {
258 direction,
259 half_length: length / 2.0,
260 }
261 }
262
263 #[inline(always)]
269 pub fn from_points(point1: Vec3, point2: Vec3) -> (Self, Vec3) {
270 let diff = point2 - point1;
271 let length = diff.length();
272
273 (
274 Self::new(Dir3::new_unchecked(diff / length), length),
276 (point1 + point2) / 2.,
277 )
278 }
279
280 #[inline(always)]
282 pub fn point1(&self) -> Vec3 {
283 *self.direction * -self.half_length
284 }
285
286 #[inline(always)]
288 pub fn point2(&self) -> Vec3 {
289 *self.direction * self.half_length
290 }
291}
292
293#[derive(Clone, Debug, PartialEq)]
297#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
298#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
299#[cfg_attr(
300 all(feature = "serialize", feature = "bevy_reflect"),
301 reflect(Serialize, Deserialize)
302)]
303pub struct Polyline3d<const N: usize> {
304 #[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))]
306 pub vertices: [Vec3; N],
307}
308impl<const N: usize> Primitive3d for Polyline3d<N> {}
309
310impl<const N: usize> FromIterator<Vec3> for Polyline3d<N> {
311 fn from_iter<I: IntoIterator<Item = Vec3>>(iter: I) -> Self {
312 let mut vertices: [Vec3; N] = [Vec3::ZERO; N];
313
314 for (index, i) in iter.into_iter().take(N).enumerate() {
315 vertices[index] = i;
316 }
317 Self { vertices }
318 }
319}
320
321impl<const N: usize> Polyline3d<N> {
322 pub fn new(vertices: impl IntoIterator<Item = Vec3>) -> Self {
324 Self::from_iter(vertices)
325 }
326}
327
328#[derive(Clone, Debug, PartialEq)]
333#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
334pub struct BoxedPolyline3d {
335 pub vertices: Box<[Vec3]>,
337}
338impl Primitive3d for BoxedPolyline3d {}
339
340impl FromIterator<Vec3> for BoxedPolyline3d {
341 fn from_iter<I: IntoIterator<Item = Vec3>>(iter: I) -> Self {
342 let vertices: Vec<Vec3> = iter.into_iter().collect();
343 Self {
344 vertices: vertices.into_boxed_slice(),
345 }
346 }
347}
348
349impl BoxedPolyline3d {
350 pub fn new(vertices: impl IntoIterator<Item = Vec3>) -> Self {
352 Self::from_iter(vertices)
353 }
354}
355
356#[derive(Clone, Copy, Debug, PartialEq)]
358#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
359#[cfg_attr(
360 feature = "bevy_reflect",
361 derive(Reflect),
362 reflect(Debug, PartialEq, Default)
363)]
364#[cfg_attr(
365 all(feature = "serialize", feature = "bevy_reflect"),
366 reflect(Serialize, Deserialize)
367)]
368pub struct Cuboid {
369 pub half_size: Vec3,
371}
372impl Primitive3d for Cuboid {}
373
374impl Default for Cuboid {
375 fn default() -> Self {
377 Self {
378 half_size: Vec3::splat(0.5),
379 }
380 }
381}
382
383impl Cuboid {
384 #[inline(always)]
386 pub fn new(x_length: f32, y_length: f32, z_length: f32) -> Self {
387 Self::from_size(Vec3::new(x_length, y_length, z_length))
388 }
389
390 #[inline(always)]
392 pub fn from_size(size: Vec3) -> Self {
393 Self {
394 half_size: size / 2.0,
395 }
396 }
397
398 #[inline(always)]
400 pub fn from_corners(point1: Vec3, point2: Vec3) -> Self {
401 Self {
402 half_size: (point2 - point1).abs() / 2.0,
403 }
404 }
405
406 #[inline(always)]
409 pub fn from_length(length: f32) -> Self {
410 Self {
411 half_size: Vec3::splat(length / 2.0),
412 }
413 }
414
415 #[inline(always)]
417 pub fn size(&self) -> Vec3 {
418 2.0 * self.half_size
419 }
420
421 #[inline(always)]
426 pub fn closest_point(&self, point: Vec3) -> Vec3 {
427 point.clamp(-self.half_size, self.half_size)
429 }
430}
431
432impl Measured3d for Cuboid {
433 #[inline(always)]
435 fn area(&self) -> f32 {
436 8.0 * (self.half_size.x * self.half_size.y
437 + self.half_size.y * self.half_size.z
438 + self.half_size.x * self.half_size.z)
439 }
440
441 #[inline(always)]
443 fn volume(&self) -> f32 {
444 8.0 * self.half_size.x * self.half_size.y * self.half_size.z
445 }
446}
447
448#[derive(Clone, Copy, Debug, PartialEq)]
450#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
451#[cfg_attr(
452 feature = "bevy_reflect",
453 derive(Reflect),
454 reflect(Debug, PartialEq, Default)
455)]
456#[cfg_attr(
457 all(feature = "serialize", feature = "bevy_reflect"),
458 reflect(Serialize, Deserialize)
459)]
460pub struct Cylinder {
461 pub radius: f32,
463 pub half_height: f32,
465}
466impl Primitive3d for Cylinder {}
467
468impl Default for Cylinder {
469 fn default() -> Self {
471 Self {
472 radius: 0.5,
473 half_height: 0.5,
474 }
475 }
476}
477
478impl Cylinder {
479 #[inline(always)]
481 pub fn new(radius: f32, height: f32) -> Self {
482 Self {
483 radius,
484 half_height: height / 2.0,
485 }
486 }
487
488 #[inline(always)]
490 pub fn base(&self) -> Circle {
491 Circle {
492 radius: self.radius,
493 }
494 }
495
496 #[inline(always)]
499 #[doc(alias = "side_area")]
500 pub fn lateral_area(&self) -> f32 {
501 4.0 * PI * self.radius * self.half_height
502 }
503
504 #[inline(always)]
506 pub fn base_area(&self) -> f32 {
507 PI * self.radius.powi(2)
508 }
509}
510
511impl Measured3d for Cylinder {
512 #[inline(always)]
514 fn area(&self) -> f32 {
515 2.0 * PI * self.radius * (self.radius + 2.0 * self.half_height)
516 }
517
518 #[inline(always)]
520 fn volume(&self) -> f32 {
521 self.base_area() * 2.0 * self.half_height
522 }
523}
524
525#[derive(Clone, Copy, Debug, PartialEq)]
528#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
529#[cfg_attr(
530 feature = "bevy_reflect",
531 derive(Reflect),
532 reflect(Debug, PartialEq, Default)
533)]
534#[cfg_attr(
535 all(feature = "serialize", feature = "bevy_reflect"),
536 reflect(Serialize, Deserialize)
537)]
538pub struct Capsule3d {
539 pub radius: f32,
541 pub half_length: f32,
543}
544impl Primitive3d for Capsule3d {}
545
546impl Default for Capsule3d {
547 fn default() -> Self {
550 Self {
551 radius: 0.5,
552 half_length: 0.5,
553 }
554 }
555}
556
557impl Capsule3d {
558 pub fn new(radius: f32, length: f32) -> Self {
560 Self {
561 radius,
562 half_length: length / 2.0,
563 }
564 }
565
566 #[inline(always)]
569 pub fn to_cylinder(&self) -> Cylinder {
570 Cylinder {
571 radius: self.radius,
572 half_height: self.half_length,
573 }
574 }
575}
576
577impl Measured3d for Capsule3d {
578 #[inline(always)]
580 fn area(&self) -> f32 {
581 4.0 * PI * self.radius * (self.radius + self.half_length)
583 }
584
585 #[inline(always)]
587 fn volume(&self) -> f32 {
588 let diameter = self.radius * 2.0;
590 PI * self.radius * diameter * (diameter / 3.0 + self.half_length)
591 }
592}
593
594#[derive(Clone, Copy, Debug, PartialEq)]
598#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
599#[cfg_attr(
600 feature = "bevy_reflect",
601 derive(Reflect),
602 reflect(Debug, PartialEq, Default)
603)]
604#[cfg_attr(
605 all(feature = "serialize", feature = "bevy_reflect"),
606 reflect(Serialize, Deserialize)
607)]
608pub struct Cone {
609 pub radius: f32,
611 pub height: f32,
613}
614impl Primitive3d for Cone {}
615
616impl Default for Cone {
617 fn default() -> Self {
619 Self {
620 radius: 0.5,
621 height: 1.0,
622 }
623 }
624}
625
626impl Cone {
627 #[inline(always)]
629 pub fn base(&self) -> Circle {
630 Circle {
631 radius: self.radius,
632 }
633 }
634
635 #[inline(always)]
638 #[doc(alias = "side_length")]
639 pub fn slant_height(&self) -> f32 {
640 self.radius.hypot(self.height)
641 }
642
643 #[inline(always)]
646 #[doc(alias = "side_area")]
647 pub fn lateral_area(&self) -> f32 {
648 PI * self.radius * self.slant_height()
649 }
650
651 #[inline(always)]
653 pub fn base_area(&self) -> f32 {
654 PI * self.radius.powi(2)
655 }
656}
657
658impl Measured3d for Cone {
659 #[inline(always)]
661 fn area(&self) -> f32 {
662 self.base_area() + self.lateral_area()
663 }
664
665 #[inline(always)]
667 fn volume(&self) -> f32 {
668 (self.base_area() * self.height) / 3.0
669 }
670}
671
672#[derive(Clone, Copy, Debug, PartialEq)]
676#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
677#[cfg_attr(
678 feature = "bevy_reflect",
679 derive(Reflect),
680 reflect(Debug, PartialEq, Default)
681)]
682#[cfg_attr(
683 all(feature = "serialize", feature = "bevy_reflect"),
684 reflect(Serialize, Deserialize)
685)]
686pub struct ConicalFrustum {
687 pub radius_top: f32,
689 pub radius_bottom: f32,
691 pub height: f32,
693}
694impl Primitive3d for ConicalFrustum {}
695
696impl Default for ConicalFrustum {
697 fn default() -> Self {
699 Self {
700 radius_top: 0.25,
701 radius_bottom: 0.5,
702 height: 0.5,
703 }
704 }
705}
706
707#[derive(Clone, Copy, Debug, PartialEq, Eq)]
709pub enum TorusKind {
710 Ring,
713 Horn,
716 Spindle,
719 Invalid,
723}
724
725#[derive(Clone, Copy, Debug, PartialEq)]
727#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
728#[cfg_attr(
729 feature = "bevy_reflect",
730 derive(Reflect),
731 reflect(Debug, PartialEq, Default)
732)]
733#[cfg_attr(
734 all(feature = "serialize", feature = "bevy_reflect"),
735 reflect(Serialize, Deserialize)
736)]
737pub struct Torus {
738 #[doc(
740 alias = "ring_radius",
741 alias = "tube_radius",
742 alias = "cross_section_radius"
743 )]
744 pub minor_radius: f32,
745 #[doc(alias = "radius_of_revolution")]
747 pub major_radius: f32,
748}
749impl Primitive3d for Torus {}
750
751impl Default for Torus {
752 fn default() -> Self {
754 Self {
755 minor_radius: 0.25,
756 major_radius: 0.75,
757 }
758 }
759}
760
761impl Torus {
762 #[inline(always)]
767 pub fn new(inner_radius: f32, outer_radius: f32) -> Self {
768 let minor_radius = (outer_radius - inner_radius) / 2.0;
769 let major_radius = outer_radius - minor_radius;
770
771 Self {
772 minor_radius,
773 major_radius,
774 }
775 }
776
777 #[inline(always)]
781 pub fn inner_radius(&self) -> f32 {
782 self.major_radius - self.minor_radius
783 }
784
785 #[inline(always)]
789 pub fn outer_radius(&self) -> f32 {
790 self.major_radius + self.minor_radius
791 }
792
793 #[inline(always)]
802 pub fn kind(&self) -> TorusKind {
803 if self.minor_radius <= 0.0
805 || !self.minor_radius.is_finite()
806 || self.major_radius <= 0.0
807 || !self.major_radius.is_finite()
808 {
809 return TorusKind::Invalid;
810 }
811
812 match self.major_radius.partial_cmp(&self.minor_radius).unwrap() {
813 std::cmp::Ordering::Greater => TorusKind::Ring,
814 std::cmp::Ordering::Equal => TorusKind::Horn,
815 std::cmp::Ordering::Less => TorusKind::Spindle,
816 }
817 }
818}
819
820impl Measured3d for Torus {
821 #[inline(always)]
824 fn area(&self) -> f32 {
825 4.0 * PI.powi(2) * self.major_radius * self.minor_radius
826 }
827
828 #[inline(always)]
831 fn volume(&self) -> f32 {
832 2.0 * PI.powi(2) * self.major_radius * self.minor_radius.powi(2)
833 }
834}
835
836#[derive(Clone, Copy, Debug, PartialEq)]
838#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
839#[cfg_attr(
840 feature = "bevy_reflect",
841 derive(Reflect),
842 reflect(Debug, PartialEq, Default)
843)]
844#[cfg_attr(
845 all(feature = "serialize", feature = "bevy_reflect"),
846 reflect(Serialize, Deserialize)
847)]
848pub struct Triangle3d {
849 pub vertices: [Vec3; 3],
851}
852
853impl Primitive3d for Triangle3d {}
854
855impl Default for Triangle3d {
856 fn default() -> Self {
858 Self {
859 vertices: [
860 Vec3::new(0.0, 0.5, 0.0),
861 Vec3::new(-0.5, -0.5, 0.0),
862 Vec3::new(0.5, -0.5, 0.0),
863 ],
864 }
865 }
866}
867
868impl Triangle3d {
869 #[inline(always)]
871 pub fn new(a: Vec3, b: Vec3, c: Vec3) -> Self {
872 Self {
873 vertices: [a, b, c],
874 }
875 }
876
877 #[inline(always)]
887 pub fn normal(&self) -> Result<Dir3, InvalidDirectionError> {
888 let [a, b, c] = self.vertices;
889 let ab = b - a;
890 let ac = c - a;
891 Dir3::new(ab.cross(ac))
892 }
893
894 #[inline(always)]
899 pub fn is_degenerate(&self) -> bool {
900 let [a, b, c] = self.vertices;
901 let ab = b - a;
902 let ac = c - a;
903 ab.cross(ac).length() < 10e-7
904 }
905
906 #[inline(always)]
908 pub fn is_acute(&self) -> bool {
909 let [a, b, c] = self.vertices;
910 let ab = b - a;
911 let bc = c - b;
912 let ca = a - c;
913
914 let mut side_lengths = [
916 ab.length_squared(),
917 bc.length_squared(),
918 ca.length_squared(),
919 ];
920 side_lengths.sort_by(|a, b| a.partial_cmp(b).unwrap());
921 side_lengths[0] + side_lengths[1] > side_lengths[2]
922 }
923
924 #[inline(always)]
926 pub fn is_obtuse(&self) -> bool {
927 let [a, b, c] = self.vertices;
928 let ab = b - a;
929 let bc = c - b;
930 let ca = a - c;
931
932 let mut side_lengths = [
934 ab.length_squared(),
935 bc.length_squared(),
936 ca.length_squared(),
937 ];
938 side_lengths.sort_by(|a, b| a.partial_cmp(b).unwrap());
939 side_lengths[0] + side_lengths[1] < side_lengths[2]
940 }
941
942 #[inline(always)]
944 pub fn reverse(&mut self) {
945 self.vertices.swap(0, 2);
946 }
947
948 #[inline(always)]
950 #[must_use]
951 pub fn reversed(mut self) -> Triangle3d {
952 self.reverse();
953 self
954 }
955
956 #[doc(alias("center", "barycenter", "baricenter"))]
961 #[inline(always)]
962 pub fn centroid(&self) -> Vec3 {
963 (self.vertices[0] + self.vertices[1] + self.vertices[2]) / 3.0
964 }
965
966 #[inline(always)]
970 pub fn largest_side(&self) -> (Vec3, Vec3) {
971 let [a, b, c] = self.vertices;
972 let ab = b - a;
973 let bc = c - b;
974 let ca = a - c;
975
976 let mut largest_side_points = (a, b);
977 let mut largest_side_length = ab.length();
978
979 if bc.length() > largest_side_length {
980 largest_side_points = (b, c);
981 largest_side_length = bc.length();
982 }
983
984 if ca.length() > largest_side_length {
985 largest_side_points = (a, c);
986 }
987
988 largest_side_points
989 }
990
991 #[inline(always)]
993 pub fn circumcenter(&self) -> Vec3 {
994 if self.is_degenerate() {
995 let (p1, p2) = self.largest_side();
997 return (p1 + p2) / 2.0;
998 }
999
1000 let [a, b, c] = self.vertices;
1001 let ab = b - a;
1002 let ac = c - a;
1003 let n = ab.cross(ac);
1004
1005 a + ((ac.length_squared() * n.cross(ab) + ab.length_squared() * ac.cross(ab).cross(ac))
1007 / (2.0 * n.length_squared()))
1008 }
1009}
1010
1011impl Measured2d for Triangle3d {
1012 #[inline(always)]
1014 fn area(&self) -> f32 {
1015 let [a, b, c] = self.vertices;
1016 let ab = b - a;
1017 let ac = c - a;
1018 ab.cross(ac).length() / 2.0
1019 }
1020
1021 #[inline(always)]
1023 fn perimeter(&self) -> f32 {
1024 let [a, b, c] = self.vertices;
1025 a.distance(b) + b.distance(c) + c.distance(a)
1026 }
1027}
1028
1029#[derive(Clone, Copy, Debug, PartialEq)]
1031#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1032#[cfg_attr(
1033 feature = "bevy_reflect",
1034 derive(Reflect),
1035 reflect(Debug, PartialEq, Default)
1036)]
1037#[cfg_attr(
1038 all(feature = "serialize", feature = "bevy_reflect"),
1039 reflect(Serialize, Deserialize)
1040)]
1041pub struct Tetrahedron {
1042 pub vertices: [Vec3; 4],
1044}
1045impl Primitive3d for Tetrahedron {}
1046
1047impl Default for Tetrahedron {
1048 fn default() -> Self {
1051 Self {
1052 vertices: [
1053 Vec3::new(0.5, 0.5, 0.5),
1054 Vec3::new(-0.5, 0.5, -0.5),
1055 Vec3::new(-0.5, -0.5, 0.5),
1056 Vec3::new(0.5, -0.5, -0.5),
1057 ],
1058 }
1059 }
1060}
1061
1062impl Tetrahedron {
1063 #[inline(always)]
1065 pub fn new(a: Vec3, b: Vec3, c: Vec3, d: Vec3) -> Self {
1066 Self {
1067 vertices: [a, b, c, d],
1068 }
1069 }
1070
1071 #[inline(always)]
1077 pub fn signed_volume(&self) -> f32 {
1078 let [a, b, c, d] = self.vertices;
1079 let ab = b - a;
1080 let ac = c - a;
1081 let ad = d - a;
1082 Mat3::from_cols(ab, ac, ad).determinant() / 6.0
1083 }
1084
1085 #[doc(alias("center", "barycenter", "baricenter"))]
1090 #[inline(always)]
1091 pub fn centroid(&self) -> Vec3 {
1092 (self.vertices[0] + self.vertices[1] + self.vertices[2] + self.vertices[3]) / 4.0
1093 }
1094
1095 #[inline(always)]
1101 pub fn faces(&self) -> [Triangle3d; 4] {
1102 let [a, b, c, d] = self.vertices;
1103 [
1104 Triangle3d::new(b, c, d),
1105 Triangle3d::new(a, c, d).reversed(),
1106 Triangle3d::new(a, b, d),
1107 Triangle3d::new(a, b, c).reversed(),
1108 ]
1109 }
1110}
1111
1112impl Measured3d for Tetrahedron {
1113 #[inline(always)]
1115 fn area(&self) -> f32 {
1116 let [a, b, c, d] = self.vertices;
1117 let ab = b - a;
1118 let ac = c - a;
1119 let ad = d - a;
1120 let bc = c - b;
1121 let bd = d - b;
1122 (ab.cross(ac).length()
1123 + ab.cross(ad).length()
1124 + ac.cross(ad).length()
1125 + bc.cross(bd).length())
1126 / 2.0
1127 }
1128
1129 #[inline(always)]
1131 fn volume(&self) -> f32 {
1132 self.signed_volume().abs()
1133 }
1134}
1135
1136#[doc(alias = "Prism")]
1144#[derive(Clone, Copy, Debug, PartialEq)]
1145#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1146pub struct Extrusion<T: Primitive2d> {
1147 pub base_shape: T,
1149 pub half_depth: f32,
1151}
1152impl<T: Primitive2d> Primitive3d for Extrusion<T> {}
1153
1154impl<T: Primitive2d> Extrusion<T> {
1155 pub fn new(base_shape: T, depth: f32) -> Self {
1157 Self {
1158 base_shape,
1159 half_depth: depth / 2.,
1160 }
1161 }
1162}
1163
1164impl<T: Primitive2d + Measured2d> Measured3d for Extrusion<T> {
1165 fn area(&self) -> f32 {
1167 2. * (self.base_shape.area() + self.half_depth * self.base_shape.perimeter())
1168 }
1169
1170 fn volume(&self) -> f32 {
1172 2. * self.base_shape.area() * self.half_depth
1173 }
1174}
1175
1176#[cfg(test)]
1177mod tests {
1178 use super::*;
1181 use crate::{InvalidDirectionError, Quat};
1182 use approx::assert_relative_eq;
1183
1184 #[test]
1185 fn direction_creation() {
1186 assert_eq!(Dir3::new(Vec3::X * 12.5), Ok(Dir3::X));
1187 assert_eq!(
1188 Dir3::new(Vec3::new(0.0, 0.0, 0.0)),
1189 Err(InvalidDirectionError::Zero)
1190 );
1191 assert_eq!(
1192 Dir3::new(Vec3::new(f32::INFINITY, 0.0, 0.0)),
1193 Err(InvalidDirectionError::Infinite)
1194 );
1195 assert_eq!(
1196 Dir3::new(Vec3::new(f32::NEG_INFINITY, 0.0, 0.0)),
1197 Err(InvalidDirectionError::Infinite)
1198 );
1199 assert_eq!(
1200 Dir3::new(Vec3::new(f32::NAN, 0.0, 0.0)),
1201 Err(InvalidDirectionError::NaN)
1202 );
1203 assert_eq!(Dir3::new_and_length(Vec3::X * 6.5), Ok((Dir3::X, 6.5)));
1204
1205 assert!(
1207 (Quat::from_rotation_z(std::f32::consts::FRAC_PI_2) * Dir3::X)
1208 .abs_diff_eq(Vec3::Y, 10e-6)
1209 );
1210 }
1211
1212 #[test]
1213 fn cuboid_closest_point() {
1214 let cuboid = Cuboid::new(2.0, 2.0, 2.0);
1215 assert_eq!(cuboid.closest_point(Vec3::X * 10.0), Vec3::X);
1216 assert_eq!(cuboid.closest_point(Vec3::NEG_ONE * 10.0), Vec3::NEG_ONE);
1217 assert_eq!(
1218 cuboid.closest_point(Vec3::new(0.25, 0.1, 0.3)),
1219 Vec3::new(0.25, 0.1, 0.3)
1220 );
1221 }
1222
1223 #[test]
1224 fn sphere_closest_point() {
1225 let sphere = Sphere { radius: 1.0 };
1226 assert_eq!(sphere.closest_point(Vec3::X * 10.0), Vec3::X);
1227 assert_eq!(
1228 sphere.closest_point(Vec3::NEG_ONE * 10.0),
1229 Vec3::NEG_ONE.normalize()
1230 );
1231 assert_eq!(
1232 sphere.closest_point(Vec3::new(0.25, 0.1, 0.3)),
1233 Vec3::new(0.25, 0.1, 0.3)
1234 );
1235 }
1236
1237 #[test]
1238 fn sphere_math() {
1239 let sphere = Sphere { radius: 4.0 };
1240 assert_eq!(sphere.diameter(), 8.0, "incorrect diameter");
1241 assert_eq!(sphere.area(), 201.06193, "incorrect area");
1242 assert_eq!(sphere.volume(), 268.08257, "incorrect volume");
1243 }
1244
1245 #[test]
1246 fn plane_from_points() {
1247 let (plane, translation) = Plane3d::from_points(Vec3::X, Vec3::Z, Vec3::NEG_X);
1248 assert_eq!(*plane.normal, Vec3::NEG_Y, "incorrect normal");
1249 assert_eq!(plane.half_size, Vec2::new(0.5, 0.5), "incorrect half size");
1250 assert_eq!(translation, Vec3::Z * 0.33333334, "incorrect translation");
1251 }
1252
1253 #[test]
1254 fn infinite_plane_from_points() {
1255 let (plane, translation) = InfinitePlane3d::from_points(Vec3::X, Vec3::Z, Vec3::NEG_X);
1256 assert_eq!(*plane.normal, Vec3::NEG_Y, "incorrect normal");
1257 assert_eq!(translation, Vec3::Z * 0.33333334, "incorrect translation");
1258 }
1259
1260 #[test]
1261 fn cuboid_math() {
1262 let cuboid = Cuboid::new(3.0, 7.0, 2.0);
1263 assert_eq!(
1264 cuboid,
1265 Cuboid::from_corners(Vec3::new(-1.5, -3.5, -1.0), Vec3::new(1.5, 3.5, 1.0)),
1266 "incorrect dimensions when created from corners"
1267 );
1268 assert_eq!(cuboid.area(), 82.0, "incorrect area");
1269 assert_eq!(cuboid.volume(), 42.0, "incorrect volume");
1270 }
1271
1272 #[test]
1273 fn cylinder_math() {
1274 let cylinder = Cylinder::new(2.0, 9.0);
1275 assert_eq!(
1276 cylinder.base(),
1277 Circle { radius: 2.0 },
1278 "base produces incorrect circle"
1279 );
1280 assert_eq!(
1281 cylinder.lateral_area(),
1282 113.097336,
1283 "incorrect lateral area"
1284 );
1285 assert_eq!(cylinder.base_area(), 12.566371, "incorrect base area");
1286 assert_relative_eq!(cylinder.area(), 138.23007);
1287 assert_eq!(cylinder.volume(), 113.097336, "incorrect volume");
1288 }
1289
1290 #[test]
1291 fn capsule_math() {
1292 let capsule = Capsule3d::new(2.0, 9.0);
1293 assert_eq!(
1294 capsule.to_cylinder(),
1295 Cylinder::new(2.0, 9.0),
1296 "cylinder wasn't created correctly from a capsule"
1297 );
1298 assert_eq!(capsule.area(), 163.36282, "incorrect area");
1299 assert_relative_eq!(capsule.volume(), 146.60765);
1300 }
1301
1302 #[test]
1303 fn cone_math() {
1304 let cone = Cone {
1305 radius: 2.0,
1306 height: 9.0,
1307 };
1308 assert_eq!(
1309 cone.base(),
1310 Circle { radius: 2.0 },
1311 "base produces incorrect circle"
1312 );
1313 assert_eq!(cone.slant_height(), 9.219544, "incorrect slant height");
1314 assert_eq!(cone.lateral_area(), 57.92811, "incorrect lateral area");
1315 assert_eq!(cone.base_area(), 12.566371, "incorrect base area");
1316 assert_relative_eq!(cone.area(), 70.49447);
1317 assert_eq!(cone.volume(), 37.699111, "incorrect volume");
1318 }
1319
1320 #[test]
1321 fn torus_math() {
1322 let torus = Torus {
1323 minor_radius: 0.3,
1324 major_radius: 2.8,
1325 };
1326 assert_eq!(torus.inner_radius(), 2.5, "incorrect inner radius");
1327 assert_eq!(torus.outer_radius(), 3.1, "incorrect outer radius");
1328 assert_eq!(torus.kind(), TorusKind::Ring, "incorrect torus kind");
1329 assert_eq!(
1330 Torus::new(0.0, 1.0).kind(),
1331 TorusKind::Horn,
1332 "incorrect torus kind"
1333 );
1334 assert_eq!(
1335 Torus::new(-0.5, 1.0).kind(),
1336 TorusKind::Spindle,
1337 "incorrect torus kind"
1338 );
1339 assert_eq!(
1340 Torus::new(1.5, 1.0).kind(),
1341 TorusKind::Invalid,
1342 "torus should be invalid"
1343 );
1344 assert_relative_eq!(torus.area(), 33.16187);
1345 assert_relative_eq!(torus.volume(), 4.97428, epsilon = 0.00001);
1346 }
1347
1348 #[test]
1349 fn tetrahedron_math() {
1350 let tetrahedron = Tetrahedron {
1351 vertices: [
1352 Vec3::new(0.3, 1.0, 1.7),
1353 Vec3::new(-2.0, -1.0, 0.0),
1354 Vec3::new(1.8, 0.5, 1.0),
1355 Vec3::new(-1.0, -2.0, 3.5),
1356 ],
1357 };
1358 assert_eq!(tetrahedron.area(), 19.251068, "incorrect area");
1359 assert_eq!(tetrahedron.volume(), 3.2058334, "incorrect volume");
1360 assert_eq!(
1361 tetrahedron.signed_volume(),
1362 3.2058334,
1363 "incorrect signed volume"
1364 );
1365 assert_relative_eq!(tetrahedron.centroid(), Vec3::new(-0.225, -0.375, 1.55));
1366
1367 assert_eq!(Tetrahedron::default().area(), 3.4641016, "incorrect area");
1368 assert_eq!(
1369 Tetrahedron::default().volume(),
1370 0.33333334,
1371 "incorrect volume"
1372 );
1373 assert_eq!(
1374 Tetrahedron::default().signed_volume(),
1375 -0.33333334,
1376 "incorrect signed volume"
1377 );
1378 assert_relative_eq!(Tetrahedron::default().centroid(), Vec3::ZERO);
1379 }
1380
1381 #[test]
1382 fn extrusion_math() {
1383 let circle = Circle::new(0.75);
1384 let cylinder = Extrusion::new(circle, 2.5);
1385 assert_eq!(cylinder.area(), 15.315264, "incorrect surface area");
1386 assert_eq!(cylinder.volume(), 4.417865, "incorrect volume");
1387
1388 let annulus = crate::primitives::Annulus::new(0.25, 1.375);
1389 let tube = Extrusion::new(annulus, 0.333);
1390 assert_eq!(tube.area(), 14.886437, "incorrect surface area");
1391 assert_eq!(tube.volume(), 1.9124937, "incorrect volume");
1392
1393 let polygon = crate::primitives::RegularPolygon::new(3.8, 7);
1394 let regular_prism = Extrusion::new(polygon, 1.25);
1395 assert_eq!(regular_prism.area(), 107.8808, "incorrect surface area");
1396 assert_eq!(regular_prism.volume(), 49.392204, "incorrect volume");
1397 }
1398
1399 #[test]
1400 fn triangle_math() {
1401 let mut default_triangle = Triangle3d::default();
1403 let reverse_default_triangle = Triangle3d::new(
1404 Vec3::new(0.5, -0.5, 0.0),
1405 Vec3::new(-0.5, -0.5, 0.0),
1406 Vec3::new(0.0, 0.5, 0.0),
1407 );
1408 assert_eq!(default_triangle.area(), 0.5, "incorrect area");
1409 assert_relative_eq!(
1410 default_triangle.perimeter(),
1411 1.0 + 2.0 * 1.25_f32.sqrt(),
1412 epsilon = 10e-9
1413 );
1414 assert_eq!(default_triangle.normal(), Ok(Dir3::Z), "incorrect normal");
1415 assert!(
1416 !default_triangle.is_degenerate(),
1417 "incorrect degenerate check"
1418 );
1419 assert_eq!(
1420 default_triangle.centroid(),
1421 Vec3::new(0.0, -0.16666667, 0.0),
1422 "incorrect centroid"
1423 );
1424 assert_eq!(
1425 default_triangle.largest_side(),
1426 (Vec3::new(0.0, 0.5, 0.0), Vec3::new(-0.5, -0.5, 0.0))
1427 );
1428 default_triangle.reverse();
1429 assert_eq!(
1430 default_triangle, reverse_default_triangle,
1431 "incorrect reverse"
1432 );
1433 assert_eq!(
1434 default_triangle.circumcenter(),
1435 Vec3::new(0.0, -0.125, 0.0),
1436 "incorrect circumcenter"
1437 );
1438
1439 let right_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::Y);
1441 let obtuse_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::X, Vec3::new(0.0, 0.1, 0.0));
1442 let acute_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::new(0.5, 5.0, 0.0));
1443
1444 assert_eq!(
1445 right_triangle.circumcenter(),
1446 Vec3::new(0.5, 0.5, 0.0),
1447 "incorrect circumcenter"
1448 );
1449 assert_eq!(
1450 obtuse_triangle.circumcenter(),
1451 Vec3::new(0.0, -4.95, 0.0),
1452 "incorrect circumcenter"
1453 );
1454 assert_eq!(
1455 acute_triangle.circumcenter(),
1456 Vec3::new(0.5, 2.475, 0.0),
1457 "incorrect circumcenter"
1458 );
1459
1460 assert!(acute_triangle.is_acute());
1461 assert!(!acute_triangle.is_obtuse());
1462 assert!(!obtuse_triangle.is_acute());
1463 assert!(obtuse_triangle.is_obtuse());
1464
1465 let [a, b, c] = [Vec3::ZERO, Vec3::new(1., 1., 0.5), Vec3::new(-3., 2.5, 1.)];
1467 let triangle = Triangle3d::new(a, b, c);
1468
1469 assert!(!triangle.is_degenerate(), "incorrectly found degenerate");
1470 assert_eq!(triangle.area(), 3.0233467, "incorrect area");
1471 assert_eq!(triangle.perimeter(), 9.832292, "incorrect perimeter");
1472 assert_eq!(
1473 triangle.circumcenter(),
1474 Vec3::new(-1., 1.75, 0.75),
1475 "incorrect circumcenter"
1476 );
1477 assert_eq!(
1478 triangle.normal(),
1479 Ok(Dir3::new_unchecked(Vec3::new(
1480 -0.04134491,
1481 -0.4134491,
1482 0.90958804
1483 ))),
1484 "incorrect normal"
1485 );
1486
1487 let zero_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::ZERO, Vec3::ZERO);
1489 assert!(
1490 zero_degenerate_triangle.is_degenerate(),
1491 "incorrect degenerate check"
1492 );
1493 assert_eq!(
1494 zero_degenerate_triangle.normal(),
1495 Err(InvalidDirectionError::Zero),
1496 "incorrect normal"
1497 );
1498 assert_eq!(
1499 zero_degenerate_triangle.largest_side(),
1500 (Vec3::ZERO, Vec3::ZERO),
1501 "incorrect largest side"
1502 );
1503
1504 let dup_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::X);
1505 assert!(
1506 dup_degenerate_triangle.is_degenerate(),
1507 "incorrect degenerate check"
1508 );
1509 assert_eq!(
1510 dup_degenerate_triangle.normal(),
1511 Err(InvalidDirectionError::Zero),
1512 "incorrect normal"
1513 );
1514 assert_eq!(
1515 dup_degenerate_triangle.largest_side(),
1516 (Vec3::ZERO, Vec3::X),
1517 "incorrect largest side"
1518 );
1519
1520 let collinear_degenerate_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::ZERO, Vec3::X);
1521 assert!(
1522 collinear_degenerate_triangle.is_degenerate(),
1523 "incorrect degenerate check"
1524 );
1525 assert_eq!(
1526 collinear_degenerate_triangle.normal(),
1527 Err(InvalidDirectionError::Zero),
1528 "incorrect normal"
1529 );
1530 assert_eq!(
1531 collinear_degenerate_triangle.largest_side(),
1532 (Vec3::NEG_X, Vec3::X),
1533 "incorrect largest side"
1534 );
1535 }
1536}