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::{boxed::Box, 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}
34impl Primitive3d for Sphere {}
35
36impl Default for Sphere {
37 fn default() -> Self {
39 Self { radius: 0.5 }
40 }
41}
42
43impl Sphere {
44 #[inline(always)]
46 pub const fn new(radius: f32) -> Self {
47 Self { radius }
48 }
49
50 #[inline(always)]
52 pub fn diameter(&self) -> f32 {
53 2.0 * self.radius
54 }
55
56 #[inline(always)]
61 pub fn closest_point(&self, point: Vec3) -> Vec3 {
62 let distance_squared = point.length_squared();
63
64 if distance_squared <= self.radius.squared() {
65 point
67 } else {
68 let dir_to_point = point / ops::sqrt(distance_squared);
71 self.radius * dir_to_point
72 }
73 }
74}
75
76impl Measured3d for Sphere {
77 #[inline(always)]
79 fn area(&self) -> f32 {
80 4.0 * PI * self.radius.squared()
81 }
82
83 #[inline(always)]
85 fn volume(&self) -> f32 {
86 4.0 * FRAC_PI_3 * self.radius.cubed()
87 }
88}
89
90#[derive(Clone, Copy, Debug, PartialEq)]
92#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
93#[cfg_attr(
94 feature = "bevy_reflect",
95 derive(Reflect),
96 reflect(Debug, PartialEq, Default, Clone)
97)]
98#[cfg_attr(
99 all(feature = "serialize", feature = "bevy_reflect"),
100 reflect(Serialize, Deserialize)
101)]
102pub struct Plane3d {
103 pub normal: Dir3,
105 pub half_size: Vec2,
107}
108impl Primitive3d for Plane3d {}
109
110impl Default for Plane3d {
111 fn default() -> Self {
113 Self {
114 normal: Dir3::Y,
115 half_size: Vec2::splat(0.5),
116 }
117 }
118}
119
120impl Plane3d {
121 #[inline(always)]
127 pub fn new(normal: Vec3, half_size: Vec2) -> Self {
128 Self {
129 normal: Dir3::new(normal).expect("normal must be nonzero and finite"),
130 half_size,
131 }
132 }
133
134 #[inline(always)]
145 pub fn from_points(a: Vec3, b: Vec3, c: Vec3) -> (Self, Vec3) {
146 let normal = Dir3::new((b - a).cross(c - a)).expect(
147 "finite plane must be defined by three finite points that don't lie on the same line",
148 );
149 let translation = (a + b + c) / 3.0;
150
151 (
152 Self {
153 normal,
154 ..Default::default()
155 },
156 translation,
157 )
158 }
159}
160
161#[derive(Clone, Copy, Debug, PartialEq)]
164#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
165#[cfg_attr(
166 feature = "bevy_reflect",
167 derive(Reflect),
168 reflect(Debug, PartialEq, Default, Clone)
169)]
170#[cfg_attr(
171 all(feature = "serialize", feature = "bevy_reflect"),
172 reflect(Serialize, Deserialize)
173)]
174pub struct InfinitePlane3d {
175 pub normal: Dir3,
177}
178impl Primitive3d for InfinitePlane3d {}
179
180impl Default for InfinitePlane3d {
181 fn default() -> Self {
183 Self { normal: Dir3::Y }
184 }
185}
186
187impl InfinitePlane3d {
188 #[inline(always)]
194 pub fn new<T: TryInto<Dir3>>(normal: T) -> Self
195 where
196 <T as TryInto<Dir3>>::Error: core::fmt::Debug,
197 {
198 Self {
199 normal: normal
200 .try_into()
201 .expect("normal must be nonzero and finite"),
202 }
203 }
204
205 #[inline(always)]
216 pub fn from_points(a: Vec3, b: Vec3, c: Vec3) -> (Self, Vec3) {
217 let normal = Dir3::new((b - a).cross(c - a)).expect(
218 "infinite plane must be defined by three finite points that don't lie on the same line",
219 );
220 let translation = (a + b + c) / 3.0;
221
222 (Self { normal }, translation)
223 }
224
225 #[inline]
229 pub fn signed_distance(&self, isometry: impl Into<Isometry3d>, point: Vec3) -> f32 {
230 let isometry = isometry.into();
231 self.normal.dot(isometry.inverse() * point)
232 }
233
234 #[inline]
238 pub fn project_point(&self, isometry: impl Into<Isometry3d>, point: Vec3) -> Vec3 {
239 point - self.normal * self.signed_distance(isometry, point)
240 }
241
242 #[inline]
267 pub fn isometry_into_xy(&self, origin: Vec3) -> Isometry3d {
268 let rotation = Quat::from_rotation_arc(self.normal.as_vec3(), Vec3::Z);
269 let transformed_origin = rotation * origin;
270 Isometry3d::new(-Vec3::Z * transformed_origin.z, rotation)
271 }
272
273 #[inline]
298 pub fn isometry_from_xy(&self, origin: Vec3) -> Isometry3d {
299 self.isometry_into_xy(origin).inverse()
300 }
301
302 #[inline]
330 pub fn isometries_xy(&self, origin: Vec3) -> (Isometry3d, Isometry3d) {
331 let projection = self.isometry_into_xy(origin);
332 (projection, projection.inverse())
333 }
334}
335
336#[derive(Clone, Copy, Debug, PartialEq)]
340#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
341#[cfg_attr(
342 feature = "bevy_reflect",
343 derive(Reflect),
344 reflect(Debug, PartialEq, Clone)
345)]
346#[cfg_attr(
347 all(feature = "serialize", feature = "bevy_reflect"),
348 reflect(Serialize, Deserialize)
349)]
350pub struct Line3d {
351 pub direction: Dir3,
353}
354impl Primitive3d for Line3d {}
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, Clone)
363)]
364#[cfg_attr(
365 all(feature = "serialize", feature = "bevy_reflect"),
366 reflect(Serialize, Deserialize)
367)]
368#[doc(alias = "LineSegment3d")]
369pub struct Segment3d {
370 pub vertices: [Vec3; 2],
372}
373impl Primitive3d for Segment3d {}
374
375impl Segment3d {
376 #[inline(always)]
378 pub const fn new(point1: Vec3, point2: Vec3) -> Self {
379 Self {
380 vertices: [point1, point2],
381 }
382 }
383
384 #[inline(always)]
386 #[deprecated(since = "0.16.0", note = "Use the `new` constructor instead")]
387 pub fn from_points(point1: Vec3, point2: Vec3) -> (Self, Vec3) {
388 (Self::new(point1, point2), (point1 + point2) / 2.)
389 }
390
391 #[inline(always)]
395 pub fn from_direction_and_length(direction: Dir3, length: f32) -> Self {
396 let endpoint = 0.5 * length * direction;
397 Self {
398 vertices: [-endpoint, endpoint],
399 }
400 }
401
402 #[inline(always)]
407 pub fn from_scaled_direction(scaled_direction: Vec3) -> Self {
408 let endpoint = 0.5 * scaled_direction;
409 Self {
410 vertices: [-endpoint, endpoint],
411 }
412 }
413
414 #[inline(always)]
419 pub fn from_ray_and_length(ray: Ray3d, length: f32) -> Self {
420 Self {
421 vertices: [ray.origin, ray.get_point(length)],
422 }
423 }
424
425 #[inline(always)]
427 pub fn point1(&self) -> Vec3 {
428 self.vertices[0]
429 }
430
431 #[inline(always)]
433 pub fn point2(&self) -> Vec3 {
434 self.vertices[1]
435 }
436
437 #[inline(always)]
439 #[doc(alias = "midpoint")]
440 pub fn center(&self) -> Vec3 {
441 self.point1().midpoint(self.point2())
442 }
443
444 #[inline(always)]
446 pub fn length(&self) -> f32 {
447 self.point1().distance(self.point2())
448 }
449
450 #[inline(always)]
452 pub fn length_squared(&self) -> f32 {
453 self.point1().distance_squared(self.point2())
454 }
455
456 #[inline(always)]
464 pub fn direction(&self) -> Dir3 {
465 self.try_direction().unwrap_or_else(|err| {
466 panic!("Failed to compute the direction of a line segment: {err}")
467 })
468 }
469
470 #[inline(always)]
475 pub fn try_direction(&self) -> Result<Dir3, InvalidDirectionError> {
476 Dir3::new(self.scaled_direction())
477 }
478
479 #[inline(always)]
481 pub fn scaled_direction(&self) -> Vec3 {
482 self.point2() - self.point1()
483 }
484
485 #[inline(always)]
487 pub fn transformed(&self, isometry: impl Into<Isometry3d>) -> Self {
488 let isometry: Isometry3d = isometry.into();
489 Self::new(
490 isometry.transform_point(self.point1()).into(),
491 isometry.transform_point(self.point2()).into(),
492 )
493 }
494
495 #[inline(always)]
497 pub fn translated(&self, translation: Vec3) -> Segment3d {
498 Self::new(self.point1() + translation, self.point2() + translation)
499 }
500
501 #[inline(always)]
503 pub fn rotated(&self, rotation: Quat) -> Segment3d {
504 Segment3d::new(rotation * self.point1(), rotation * self.point2())
505 }
506
507 #[inline(always)]
509 pub fn rotated_around(&self, rotation: Quat, point: Vec3) -> Segment3d {
510 let offset = self.translated(-point);
512 let rotated = offset.rotated(rotation);
513 rotated.translated(point)
514 }
515
516 #[inline(always)]
518 pub fn rotated_around_center(&self, rotation: Quat) -> Segment3d {
519 self.rotated_around(rotation, self.center())
520 }
521
522 #[inline(always)]
524 pub fn centered(&self) -> Segment3d {
525 let center = self.center();
526 self.translated(-center)
527 }
528
529 #[inline(always)]
531 pub fn resized(&self, length: f32) -> Segment3d {
532 let offset_from_origin = self.center();
533 let centered = self.translated(-offset_from_origin);
534 let ratio = length / self.length();
535 let segment = Segment3d::new(centered.point1() * ratio, centered.point2() * ratio);
536 segment.translated(offset_from_origin)
537 }
538
539 #[inline(always)]
541 pub fn reverse(&mut self) {
542 let [point1, point2] = &mut self.vertices;
543 core::mem::swap(point1, point2);
544 }
545
546 #[inline(always)]
548 #[must_use]
549 pub fn reversed(mut self) -> Self {
550 self.reverse();
551 self
552 }
553}
554
555impl From<[Vec3; 2]> for Segment3d {
556 #[inline(always)]
557 fn from(vertices: [Vec3; 2]) -> Self {
558 Self { vertices }
559 }
560}
561
562impl From<(Vec3, Vec3)> for Segment3d {
563 #[inline(always)]
564 fn from((point1, point2): (Vec3, Vec3)) -> Self {
565 Self::new(point1, point2)
566 }
567}
568
569#[derive(Clone, Debug, PartialEq)]
573#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
574#[cfg_attr(
575 feature = "bevy_reflect",
576 derive(Reflect),
577 reflect(Debug, PartialEq, Clone)
578)]
579#[cfg_attr(
580 all(feature = "serialize", feature = "bevy_reflect"),
581 reflect(Serialize, Deserialize)
582)]
583pub struct Polyline3d<const N: usize> {
584 #[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))]
586 pub vertices: [Vec3; N],
587}
588impl<const N: usize> Primitive3d for Polyline3d<N> {}
589
590impl<const N: usize> FromIterator<Vec3> for Polyline3d<N> {
591 fn from_iter<I: IntoIterator<Item = Vec3>>(iter: I) -> Self {
592 let mut vertices: [Vec3; N] = [Vec3::ZERO; N];
593
594 for (index, i) in iter.into_iter().take(N).enumerate() {
595 vertices[index] = i;
596 }
597 Self { vertices }
598 }
599}
600
601impl<const N: usize> Polyline3d<N> {
602 pub fn new(vertices: impl IntoIterator<Item = Vec3>) -> Self {
604 Self::from_iter(vertices)
605 }
606}
607
608#[cfg(feature = "alloc")]
613#[derive(Clone, Debug, PartialEq)]
614#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
615pub struct BoxedPolyline3d {
616 pub vertices: Box<[Vec3]>,
618}
619
620#[cfg(feature = "alloc")]
621impl Primitive3d for BoxedPolyline3d {}
622
623#[cfg(feature = "alloc")]
624impl FromIterator<Vec3> for BoxedPolyline3d {
625 fn from_iter<I: IntoIterator<Item = Vec3>>(iter: I) -> Self {
626 let vertices: Vec<Vec3> = iter.into_iter().collect();
627 Self {
628 vertices: vertices.into_boxed_slice(),
629 }
630 }
631}
632
633#[cfg(feature = "alloc")]
634impl BoxedPolyline3d {
635 pub fn new(vertices: impl IntoIterator<Item = Vec3>) -> Self {
637 Self::from_iter(vertices)
638 }
639}
640
641#[derive(Clone, Copy, Debug, PartialEq)]
644#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
645#[cfg_attr(
646 feature = "bevy_reflect",
647 derive(Reflect),
648 reflect(Debug, PartialEq, Default, Clone)
649)]
650#[cfg_attr(
651 all(feature = "serialize", feature = "bevy_reflect"),
652 reflect(Serialize, Deserialize)
653)]
654pub struct Cuboid {
655 pub half_size: Vec3,
657}
658impl Primitive3d for Cuboid {}
659
660impl Default for Cuboid {
661 fn default() -> Self {
663 Self {
664 half_size: Vec3::splat(0.5),
665 }
666 }
667}
668
669impl Cuboid {
670 #[inline(always)]
672 pub fn new(x_length: f32, y_length: f32, z_length: f32) -> Self {
673 Self::from_size(Vec3::new(x_length, y_length, z_length))
674 }
675
676 #[inline(always)]
678 pub fn from_size(size: Vec3) -> Self {
679 Self {
680 half_size: size / 2.0,
681 }
682 }
683
684 #[inline(always)]
686 pub fn from_corners(point1: Vec3, point2: Vec3) -> Self {
687 Self {
688 half_size: (point2 - point1).abs() / 2.0,
689 }
690 }
691
692 #[inline(always)]
695 pub fn from_length(length: f32) -> Self {
696 Self {
697 half_size: Vec3::splat(length / 2.0),
698 }
699 }
700
701 #[inline(always)]
703 pub fn size(&self) -> Vec3 {
704 2.0 * self.half_size
705 }
706
707 #[inline(always)]
712 pub fn closest_point(&self, point: Vec3) -> Vec3 {
713 point.clamp(-self.half_size, self.half_size)
715 }
716}
717
718impl Measured3d for Cuboid {
719 #[inline(always)]
721 fn area(&self) -> f32 {
722 8.0 * (self.half_size.x * self.half_size.y
723 + self.half_size.y * self.half_size.z
724 + self.half_size.x * self.half_size.z)
725 }
726
727 #[inline(always)]
729 fn volume(&self) -> f32 {
730 8.0 * self.half_size.x * self.half_size.y * self.half_size.z
731 }
732}
733
734#[derive(Clone, Copy, Debug, PartialEq)]
736#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
737#[cfg_attr(
738 feature = "bevy_reflect",
739 derive(Reflect),
740 reflect(Debug, PartialEq, Default, Clone)
741)]
742#[cfg_attr(
743 all(feature = "serialize", feature = "bevy_reflect"),
744 reflect(Serialize, Deserialize)
745)]
746pub struct Cylinder {
747 pub radius: f32,
749 pub half_height: f32,
751}
752impl Primitive3d for Cylinder {}
753
754impl Default for Cylinder {
755 fn default() -> Self {
757 Self {
758 radius: 0.5,
759 half_height: 0.5,
760 }
761 }
762}
763
764impl Cylinder {
765 #[inline(always)]
767 pub fn new(radius: f32, height: f32) -> Self {
768 Self {
769 radius,
770 half_height: height / 2.0,
771 }
772 }
773
774 #[inline(always)]
776 pub fn base(&self) -> Circle {
777 Circle {
778 radius: self.radius,
779 }
780 }
781
782 #[inline(always)]
785 #[doc(alias = "side_area")]
786 pub fn lateral_area(&self) -> f32 {
787 4.0 * PI * self.radius * self.half_height
788 }
789
790 #[inline(always)]
792 pub fn base_area(&self) -> f32 {
793 PI * self.radius.squared()
794 }
795}
796
797impl Measured3d for Cylinder {
798 #[inline(always)]
800 fn area(&self) -> f32 {
801 2.0 * PI * self.radius * (self.radius + 2.0 * self.half_height)
802 }
803
804 #[inline(always)]
806 fn volume(&self) -> f32 {
807 self.base_area() * 2.0 * self.half_height
808 }
809}
810
811#[derive(Clone, Copy, Debug, PartialEq)]
814#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
815#[cfg_attr(
816 feature = "bevy_reflect",
817 derive(Reflect),
818 reflect(Debug, PartialEq, Default, Clone)
819)]
820#[cfg_attr(
821 all(feature = "serialize", feature = "bevy_reflect"),
822 reflect(Serialize, Deserialize)
823)]
824pub struct Capsule3d {
825 pub radius: f32,
827 pub half_length: f32,
829}
830impl Primitive3d for Capsule3d {}
831
832impl Default for Capsule3d {
833 fn default() -> Self {
836 Self {
837 radius: 0.5,
838 half_length: 0.5,
839 }
840 }
841}
842
843impl Capsule3d {
844 pub fn new(radius: f32, length: f32) -> Self {
846 Self {
847 radius,
848 half_length: length / 2.0,
849 }
850 }
851
852 #[inline(always)]
855 pub fn to_cylinder(&self) -> Cylinder {
856 Cylinder {
857 radius: self.radius,
858 half_height: self.half_length,
859 }
860 }
861}
862
863impl Measured3d for Capsule3d {
864 #[inline(always)]
866 fn area(&self) -> f32 {
867 4.0 * PI * self.radius * (self.radius + self.half_length)
869 }
870
871 #[inline(always)]
873 fn volume(&self) -> f32 {
874 let diameter = self.radius * 2.0;
876 PI * self.radius * diameter * (diameter / 3.0 + self.half_length)
877 }
878}
879
880#[derive(Clone, Copy, Debug, PartialEq)]
884#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
885#[cfg_attr(
886 feature = "bevy_reflect",
887 derive(Reflect),
888 reflect(Debug, PartialEq, Default, Clone)
889)]
890#[cfg_attr(
891 all(feature = "serialize", feature = "bevy_reflect"),
892 reflect(Serialize, Deserialize)
893)]
894pub struct Cone {
895 pub radius: f32,
897 pub height: f32,
899}
900impl Primitive3d for Cone {}
901
902impl Default for Cone {
903 fn default() -> Self {
905 Self {
906 radius: 0.5,
907 height: 1.0,
908 }
909 }
910}
911
912impl Cone {
913 pub fn new(radius: f32, height: f32) -> Self {
915 Self { radius, height }
916 }
917 #[inline(always)]
919 pub fn base(&self) -> Circle {
920 Circle {
921 radius: self.radius,
922 }
923 }
924
925 #[inline(always)]
928 #[doc(alias = "side_length")]
929 pub fn slant_height(&self) -> f32 {
930 ops::hypot(self.radius, self.height)
931 }
932
933 #[inline(always)]
936 #[doc(alias = "side_area")]
937 pub fn lateral_area(&self) -> f32 {
938 PI * self.radius * self.slant_height()
939 }
940
941 #[inline(always)]
943 pub fn base_area(&self) -> f32 {
944 PI * self.radius.squared()
945 }
946}
947
948impl Measured3d for Cone {
949 #[inline(always)]
951 fn area(&self) -> f32 {
952 self.base_area() + self.lateral_area()
953 }
954
955 #[inline(always)]
957 fn volume(&self) -> f32 {
958 (self.base_area() * self.height) / 3.0
959 }
960}
961
962#[derive(Clone, Copy, Debug, PartialEq)]
966#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
967#[cfg_attr(
968 feature = "bevy_reflect",
969 derive(Reflect),
970 reflect(Debug, PartialEq, Default, Clone)
971)]
972#[cfg_attr(
973 all(feature = "serialize", feature = "bevy_reflect"),
974 reflect(Serialize, Deserialize)
975)]
976pub struct ConicalFrustum {
977 pub radius_top: f32,
979 pub radius_bottom: f32,
981 pub height: f32,
983}
984impl Primitive3d for ConicalFrustum {}
985
986impl Default for ConicalFrustum {
987 fn default() -> Self {
989 Self {
990 radius_top: 0.25,
991 radius_bottom: 0.5,
992 height: 0.5,
993 }
994 }
995}
996
997#[derive(Clone, Copy, Debug, PartialEq, Eq)]
999pub enum TorusKind {
1000 Ring,
1003 Horn,
1006 Spindle,
1009 Invalid,
1013}
1014
1015#[derive(Clone, Copy, Debug, PartialEq)]
1018#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1019#[cfg_attr(
1020 feature = "bevy_reflect",
1021 derive(Reflect),
1022 reflect(Debug, PartialEq, Default, Clone)
1023)]
1024#[cfg_attr(
1025 all(feature = "serialize", feature = "bevy_reflect"),
1026 reflect(Serialize, Deserialize)
1027)]
1028pub struct Torus {
1029 #[doc(
1031 alias = "ring_radius",
1032 alias = "tube_radius",
1033 alias = "cross_section_radius"
1034 )]
1035 pub minor_radius: f32,
1036 #[doc(alias = "radius_of_revolution")]
1038 pub major_radius: f32,
1039}
1040impl Primitive3d for Torus {}
1041
1042impl Default for Torus {
1043 fn default() -> Self {
1045 Self {
1046 minor_radius: 0.25,
1047 major_radius: 0.75,
1048 }
1049 }
1050}
1051
1052impl Torus {
1053 #[inline(always)]
1058 pub fn new(inner_radius: f32, outer_radius: f32) -> Self {
1059 let minor_radius = (outer_radius - inner_radius) / 2.0;
1060 let major_radius = outer_radius - minor_radius;
1061
1062 Self {
1063 minor_radius,
1064 major_radius,
1065 }
1066 }
1067
1068 #[inline(always)]
1072 pub fn inner_radius(&self) -> f32 {
1073 self.major_radius - self.minor_radius
1074 }
1075
1076 #[inline(always)]
1080 pub fn outer_radius(&self) -> f32 {
1081 self.major_radius + self.minor_radius
1082 }
1083
1084 #[inline(always)]
1093 pub fn kind(&self) -> TorusKind {
1094 if self.minor_radius <= 0.0
1096 || !self.minor_radius.is_finite()
1097 || self.major_radius <= 0.0
1098 || !self.major_radius.is_finite()
1099 {
1100 return TorusKind::Invalid;
1101 }
1102
1103 match self.major_radius.partial_cmp(&self.minor_radius).unwrap() {
1104 core::cmp::Ordering::Greater => TorusKind::Ring,
1105 core::cmp::Ordering::Equal => TorusKind::Horn,
1106 core::cmp::Ordering::Less => TorusKind::Spindle,
1107 }
1108 }
1109}
1110
1111impl Measured3d for Torus {
1112 #[inline(always)]
1115 fn area(&self) -> f32 {
1116 4.0 * PI.squared() * self.major_radius * self.minor_radius
1117 }
1118
1119 #[inline(always)]
1122 fn volume(&self) -> f32 {
1123 2.0 * PI.squared() * self.major_radius * self.minor_radius.squared()
1124 }
1125}
1126
1127#[derive(Clone, Copy, Debug, PartialEq)]
1129#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1130#[cfg_attr(
1131 feature = "bevy_reflect",
1132 derive(Reflect),
1133 reflect(Debug, PartialEq, Default, Clone)
1134)]
1135#[cfg_attr(
1136 all(feature = "serialize", feature = "bevy_reflect"),
1137 reflect(Serialize, Deserialize)
1138)]
1139pub struct Triangle3d {
1140 pub vertices: [Vec3; 3],
1142}
1143
1144impl Primitive3d for Triangle3d {}
1145
1146impl Default for Triangle3d {
1147 fn default() -> Self {
1149 Self {
1150 vertices: [
1151 Vec3::new(0.0, 0.5, 0.0),
1152 Vec3::new(-0.5, -0.5, 0.0),
1153 Vec3::new(0.5, -0.5, 0.0),
1154 ],
1155 }
1156 }
1157}
1158
1159impl Triangle3d {
1160 #[inline(always)]
1162 pub fn new(a: Vec3, b: Vec3, c: Vec3) -> Self {
1163 Self {
1164 vertices: [a, b, c],
1165 }
1166 }
1167
1168 #[inline(always)]
1178 pub fn normal(&self) -> Result<Dir3, InvalidDirectionError> {
1179 let [a, b, c] = self.vertices;
1180 let ab = b - a;
1181 let ac = c - a;
1182 Dir3::new(ab.cross(ac))
1183 }
1184
1185 #[inline(always)]
1190 pub fn is_degenerate(&self) -> bool {
1191 let [a, b, c] = self.vertices;
1192 let ab = b - a;
1193 let ac = c - a;
1194 ab.cross(ac).length() < 10e-7
1195 }
1196
1197 #[inline(always)]
1199 pub fn is_acute(&self) -> bool {
1200 let [a, b, c] = self.vertices;
1201 let ab = b - a;
1202 let bc = c - b;
1203 let ca = a - c;
1204
1205 let mut side_lengths = [
1207 ab.length_squared(),
1208 bc.length_squared(),
1209 ca.length_squared(),
1210 ];
1211 side_lengths.sort_by(|a, b| a.partial_cmp(b).unwrap());
1212 side_lengths[0] + side_lengths[1] > side_lengths[2]
1213 }
1214
1215 #[inline(always)]
1217 pub fn is_obtuse(&self) -> bool {
1218 let [a, b, c] = self.vertices;
1219 let ab = b - a;
1220 let bc = c - b;
1221 let ca = a - c;
1222
1223 let mut side_lengths = [
1225 ab.length_squared(),
1226 bc.length_squared(),
1227 ca.length_squared(),
1228 ];
1229 side_lengths.sort_by(|a, b| a.partial_cmp(b).unwrap());
1230 side_lengths[0] + side_lengths[1] < side_lengths[2]
1231 }
1232
1233 #[inline(always)]
1235 pub fn reverse(&mut self) {
1236 self.vertices.swap(0, 2);
1237 }
1238
1239 #[inline(always)]
1241 #[must_use]
1242 pub fn reversed(mut self) -> Triangle3d {
1243 self.reverse();
1244 self
1245 }
1246
1247 #[doc(alias("center", "barycenter", "baricenter"))]
1252 #[inline(always)]
1253 pub fn centroid(&self) -> Vec3 {
1254 (self.vertices[0] + self.vertices[1] + self.vertices[2]) / 3.0
1255 }
1256
1257 #[inline(always)]
1261 pub fn largest_side(&self) -> (Vec3, Vec3) {
1262 let [a, b, c] = self.vertices;
1263 let ab = b - a;
1264 let bc = c - b;
1265 let ca = a - c;
1266
1267 let mut largest_side_points = (a, b);
1268 let mut largest_side_length = ab.length();
1269
1270 if bc.length() > largest_side_length {
1271 largest_side_points = (b, c);
1272 largest_side_length = bc.length();
1273 }
1274
1275 if ca.length() > largest_side_length {
1276 largest_side_points = (a, c);
1277 }
1278
1279 largest_side_points
1280 }
1281
1282 #[inline(always)]
1284 pub fn circumcenter(&self) -> Vec3 {
1285 if self.is_degenerate() {
1286 let (p1, p2) = self.largest_side();
1288 return (p1 + p2) / 2.0;
1289 }
1290
1291 let [a, b, c] = self.vertices;
1292 let ab = b - a;
1293 let ac = c - a;
1294 let n = ab.cross(ac);
1295
1296 a + ((ac.length_squared() * n.cross(ab) + ab.length_squared() * ac.cross(ab).cross(ac))
1298 / (2.0 * n.length_squared()))
1299 }
1300}
1301
1302impl Measured2d for Triangle3d {
1303 #[inline(always)]
1305 fn area(&self) -> f32 {
1306 let [a, b, c] = self.vertices;
1307 let ab = b - a;
1308 let ac = c - a;
1309 ab.cross(ac).length() / 2.0
1310 }
1311
1312 #[inline(always)]
1314 fn perimeter(&self) -> f32 {
1315 let [a, b, c] = self.vertices;
1316 a.distance(b) + b.distance(c) + c.distance(a)
1317 }
1318}
1319
1320#[derive(Clone, Copy, Debug, PartialEq)]
1322#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1323#[cfg_attr(
1324 feature = "bevy_reflect",
1325 derive(Reflect),
1326 reflect(Debug, PartialEq, Default, Clone)
1327)]
1328#[cfg_attr(
1329 all(feature = "serialize", feature = "bevy_reflect"),
1330 reflect(Serialize, Deserialize)
1331)]
1332pub struct Tetrahedron {
1333 pub vertices: [Vec3; 4],
1335}
1336impl Primitive3d for Tetrahedron {}
1337
1338impl Default for Tetrahedron {
1339 fn default() -> Self {
1342 Self {
1343 vertices: [
1344 Vec3::new(0.5, 0.5, 0.5),
1345 Vec3::new(-0.5, 0.5, -0.5),
1346 Vec3::new(-0.5, -0.5, 0.5),
1347 Vec3::new(0.5, -0.5, -0.5),
1348 ],
1349 }
1350 }
1351}
1352
1353impl Tetrahedron {
1354 #[inline(always)]
1356 pub fn new(a: Vec3, b: Vec3, c: Vec3, d: Vec3) -> Self {
1357 Self {
1358 vertices: [a, b, c, d],
1359 }
1360 }
1361
1362 #[inline(always)]
1368 pub fn signed_volume(&self) -> f32 {
1369 let [a, b, c, d] = self.vertices;
1370 let ab = b - a;
1371 let ac = c - a;
1372 let ad = d - a;
1373 Mat3::from_cols(ab, ac, ad).determinant() / 6.0
1374 }
1375
1376 #[doc(alias("center", "barycenter", "baricenter"))]
1381 #[inline(always)]
1382 pub fn centroid(&self) -> Vec3 {
1383 (self.vertices[0] + self.vertices[1] + self.vertices[2] + self.vertices[3]) / 4.0
1384 }
1385
1386 #[inline(always)]
1392 pub fn faces(&self) -> [Triangle3d; 4] {
1393 let [a, b, c, d] = self.vertices;
1394 [
1395 Triangle3d::new(b, c, d),
1396 Triangle3d::new(a, c, d).reversed(),
1397 Triangle3d::new(a, b, d),
1398 Triangle3d::new(a, b, c).reversed(),
1399 ]
1400 }
1401}
1402
1403impl Measured3d for Tetrahedron {
1404 #[inline(always)]
1406 fn area(&self) -> f32 {
1407 let [a, b, c, d] = self.vertices;
1408 let ab = b - a;
1409 let ac = c - a;
1410 let ad = d - a;
1411 let bc = c - b;
1412 let bd = d - b;
1413 (ab.cross(ac).length()
1414 + ab.cross(ad).length()
1415 + ac.cross(ad).length()
1416 + bc.cross(bd).length())
1417 / 2.0
1418 }
1419
1420 #[inline(always)]
1422 fn volume(&self) -> f32 {
1423 ops::abs(self.signed_volume())
1424 }
1425}
1426
1427#[doc(alias = "Prism")]
1435#[derive(Clone, Copy, Debug, PartialEq)]
1436#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1437pub struct Extrusion<T: Primitive2d> {
1438 pub base_shape: T,
1440 pub half_depth: f32,
1442}
1443impl<T: Primitive2d> Primitive3d for Extrusion<T> {}
1444
1445impl<T: Primitive2d> Extrusion<T> {
1446 pub fn new(base_shape: T, depth: f32) -> Self {
1448 Self {
1449 base_shape,
1450 half_depth: depth / 2.,
1451 }
1452 }
1453}
1454
1455impl<T: Primitive2d + Measured2d> Measured3d for Extrusion<T> {
1456 fn area(&self) -> f32 {
1458 2. * (self.base_shape.area() + self.half_depth * self.base_shape.perimeter())
1459 }
1460
1461 fn volume(&self) -> f32 {
1463 2. * self.base_shape.area() * self.half_depth
1464 }
1465}
1466
1467#[cfg(test)]
1468mod tests {
1469 use super::*;
1472 use crate::{InvalidDirectionError, Quat};
1473 use approx::assert_relative_eq;
1474
1475 #[test]
1476 fn direction_creation() {
1477 assert_eq!(Dir3::new(Vec3::X * 12.5), Ok(Dir3::X));
1478 assert_eq!(
1479 Dir3::new(Vec3::new(0.0, 0.0, 0.0)),
1480 Err(InvalidDirectionError::Zero)
1481 );
1482 assert_eq!(
1483 Dir3::new(Vec3::new(f32::INFINITY, 0.0, 0.0)),
1484 Err(InvalidDirectionError::Infinite)
1485 );
1486 assert_eq!(
1487 Dir3::new(Vec3::new(f32::NEG_INFINITY, 0.0, 0.0)),
1488 Err(InvalidDirectionError::Infinite)
1489 );
1490 assert_eq!(
1491 Dir3::new(Vec3::new(f32::NAN, 0.0, 0.0)),
1492 Err(InvalidDirectionError::NaN)
1493 );
1494 assert_eq!(Dir3::new_and_length(Vec3::X * 6.5), Ok((Dir3::X, 6.5)));
1495
1496 assert!(
1498 (Quat::from_rotation_z(core::f32::consts::FRAC_PI_2) * Dir3::X)
1499 .abs_diff_eq(Vec3::Y, 10e-6)
1500 );
1501 }
1502
1503 #[test]
1504 fn cuboid_closest_point() {
1505 let cuboid = Cuboid::new(2.0, 2.0, 2.0);
1506 assert_eq!(cuboid.closest_point(Vec3::X * 10.0), Vec3::X);
1507 assert_eq!(cuboid.closest_point(Vec3::NEG_ONE * 10.0), Vec3::NEG_ONE);
1508 assert_eq!(
1509 cuboid.closest_point(Vec3::new(0.25, 0.1, 0.3)),
1510 Vec3::new(0.25, 0.1, 0.3)
1511 );
1512 }
1513
1514 #[test]
1515 fn sphere_closest_point() {
1516 let sphere = Sphere { radius: 1.0 };
1517 assert_eq!(sphere.closest_point(Vec3::X * 10.0), Vec3::X);
1518 assert_eq!(
1519 sphere.closest_point(Vec3::NEG_ONE * 10.0),
1520 Vec3::NEG_ONE.normalize()
1521 );
1522 assert_eq!(
1523 sphere.closest_point(Vec3::new(0.25, 0.1, 0.3)),
1524 Vec3::new(0.25, 0.1, 0.3)
1525 );
1526 }
1527
1528 #[test]
1529 fn sphere_math() {
1530 let sphere = Sphere { radius: 4.0 };
1531 assert_eq!(sphere.diameter(), 8.0, "incorrect diameter");
1532 assert_eq!(sphere.area(), 201.06193, "incorrect area");
1533 assert_eq!(sphere.volume(), 268.08257, "incorrect volume");
1534 }
1535
1536 #[test]
1537 fn plane_from_points() {
1538 let (plane, translation) = Plane3d::from_points(Vec3::X, Vec3::Z, Vec3::NEG_X);
1539 assert_eq!(*plane.normal, Vec3::NEG_Y, "incorrect normal");
1540 assert_eq!(plane.half_size, Vec2::new(0.5, 0.5), "incorrect half size");
1541 assert_eq!(translation, Vec3::Z * 0.33333334, "incorrect translation");
1542 }
1543
1544 #[test]
1545 fn infinite_plane_math() {
1546 let (plane, origin) = InfinitePlane3d::from_points(Vec3::X, Vec3::Z, Vec3::NEG_X);
1547 assert_eq!(*plane.normal, Vec3::NEG_Y, "incorrect normal");
1548 assert_eq!(origin, Vec3::Z * 0.33333334, "incorrect translation");
1549
1550 let point_in_plane = Vec3::X + Vec3::Z;
1551 assert_eq!(
1552 plane.signed_distance(origin, point_in_plane),
1553 0.0,
1554 "incorrect distance"
1555 );
1556 assert_eq!(
1557 plane.project_point(origin, point_in_plane),
1558 point_in_plane,
1559 "incorrect point"
1560 );
1561
1562 let point_outside = Vec3::Y;
1563 assert_eq!(
1564 plane.signed_distance(origin, point_outside),
1565 -1.0,
1566 "incorrect distance"
1567 );
1568 assert_eq!(
1569 plane.project_point(origin, point_outside),
1570 Vec3::ZERO,
1571 "incorrect point"
1572 );
1573
1574 let point_outside = Vec3::NEG_Y;
1575 assert_eq!(
1576 plane.signed_distance(origin, point_outside),
1577 1.0,
1578 "incorrect distance"
1579 );
1580 assert_eq!(
1581 plane.project_point(origin, point_outside),
1582 Vec3::ZERO,
1583 "incorrect point"
1584 );
1585
1586 let area_f = |[a, b, c]: [Vec3; 3]| (a - b).cross(a - c).length() * 0.5;
1587 let (proj, inj) = plane.isometries_xy(origin);
1588
1589 let triangle = [Vec3::X, Vec3::Y, Vec3::ZERO];
1590 assert_eq!(area_f(triangle), 0.5, "incorrect area");
1591
1592 let triangle_proj = triangle.map(|vec3| proj * vec3);
1593 assert_relative_eq!(area_f(triangle_proj), 0.5);
1594
1595 let triangle_proj_inj = triangle_proj.map(|vec3| inj * vec3);
1596 assert_relative_eq!(area_f(triangle_proj_inj), 0.5);
1597 }
1598
1599 #[test]
1600 fn cuboid_math() {
1601 let cuboid = Cuboid::new(3.0, 7.0, 2.0);
1602 assert_eq!(
1603 cuboid,
1604 Cuboid::from_corners(Vec3::new(-1.5, -3.5, -1.0), Vec3::new(1.5, 3.5, 1.0)),
1605 "incorrect dimensions when created from corners"
1606 );
1607 assert_eq!(cuboid.area(), 82.0, "incorrect area");
1608 assert_eq!(cuboid.volume(), 42.0, "incorrect volume");
1609 }
1610
1611 #[test]
1612 fn cylinder_math() {
1613 let cylinder = Cylinder::new(2.0, 9.0);
1614 assert_eq!(
1615 cylinder.base(),
1616 Circle { radius: 2.0 },
1617 "base produces incorrect circle"
1618 );
1619 assert_eq!(
1620 cylinder.lateral_area(),
1621 113.097336,
1622 "incorrect lateral area"
1623 );
1624 assert_eq!(cylinder.base_area(), 12.566371, "incorrect base area");
1625 assert_relative_eq!(cylinder.area(), 138.23007);
1626 assert_eq!(cylinder.volume(), 113.097336, "incorrect volume");
1627 }
1628
1629 #[test]
1630 fn capsule_math() {
1631 let capsule = Capsule3d::new(2.0, 9.0);
1632 assert_eq!(
1633 capsule.to_cylinder(),
1634 Cylinder::new(2.0, 9.0),
1635 "cylinder wasn't created correctly from a capsule"
1636 );
1637 assert_eq!(capsule.area(), 163.36282, "incorrect area");
1638 assert_relative_eq!(capsule.volume(), 146.60765);
1639 }
1640
1641 #[test]
1642 fn cone_math() {
1643 let cone = Cone {
1644 radius: 2.0,
1645 height: 9.0,
1646 };
1647 assert_eq!(
1648 cone.base(),
1649 Circle { radius: 2.0 },
1650 "base produces incorrect circle"
1651 );
1652 assert_eq!(cone.slant_height(), 9.219544, "incorrect slant height");
1653 assert_eq!(cone.lateral_area(), 57.92811, "incorrect lateral area");
1654 assert_eq!(cone.base_area(), 12.566371, "incorrect base area");
1655 assert_relative_eq!(cone.area(), 70.49447);
1656 assert_eq!(cone.volume(), 37.699111, "incorrect volume");
1657 }
1658
1659 #[test]
1660 fn torus_math() {
1661 let torus = Torus {
1662 minor_radius: 0.3,
1663 major_radius: 2.8,
1664 };
1665 assert_eq!(torus.inner_radius(), 2.5, "incorrect inner radius");
1666 assert_eq!(torus.outer_radius(), 3.1, "incorrect outer radius");
1667 assert_eq!(torus.kind(), TorusKind::Ring, "incorrect torus kind");
1668 assert_eq!(
1669 Torus::new(0.0, 1.0).kind(),
1670 TorusKind::Horn,
1671 "incorrect torus kind"
1672 );
1673 assert_eq!(
1674 Torus::new(-0.5, 1.0).kind(),
1675 TorusKind::Spindle,
1676 "incorrect torus kind"
1677 );
1678 assert_eq!(
1679 Torus::new(1.5, 1.0).kind(),
1680 TorusKind::Invalid,
1681 "torus should be invalid"
1682 );
1683 assert_relative_eq!(torus.area(), 33.16187);
1684 assert_relative_eq!(torus.volume(), 4.97428, epsilon = 0.00001);
1685 }
1686
1687 #[test]
1688 fn tetrahedron_math() {
1689 let tetrahedron = Tetrahedron {
1690 vertices: [
1691 Vec3::new(0.3, 1.0, 1.7),
1692 Vec3::new(-2.0, -1.0, 0.0),
1693 Vec3::new(1.8, 0.5, 1.0),
1694 Vec3::new(-1.0, -2.0, 3.5),
1695 ],
1696 };
1697 assert_eq!(tetrahedron.area(), 19.251068, "incorrect area");
1698 assert_eq!(tetrahedron.volume(), 3.2058334, "incorrect volume");
1699 assert_eq!(
1700 tetrahedron.signed_volume(),
1701 3.2058334,
1702 "incorrect signed volume"
1703 );
1704 assert_relative_eq!(tetrahedron.centroid(), Vec3::new(-0.225, -0.375, 1.55));
1705
1706 assert_eq!(Tetrahedron::default().area(), 3.4641016, "incorrect area");
1707 assert_eq!(
1708 Tetrahedron::default().volume(),
1709 0.33333334,
1710 "incorrect volume"
1711 );
1712 assert_eq!(
1713 Tetrahedron::default().signed_volume(),
1714 -0.33333334,
1715 "incorrect signed volume"
1716 );
1717 assert_relative_eq!(Tetrahedron::default().centroid(), Vec3::ZERO);
1718 }
1719
1720 #[test]
1721 fn extrusion_math() {
1722 let circle = Circle::new(0.75);
1723 let cylinder = Extrusion::new(circle, 2.5);
1724 assert_eq!(cylinder.area(), 15.315264, "incorrect surface area");
1725 assert_eq!(cylinder.volume(), 4.417865, "incorrect volume");
1726
1727 let annulus = crate::primitives::Annulus::new(0.25, 1.375);
1728 let tube = Extrusion::new(annulus, 0.333);
1729 assert_eq!(tube.area(), 14.886437, "incorrect surface area");
1730 assert_eq!(tube.volume(), 1.9124937, "incorrect volume");
1731
1732 let polygon = crate::primitives::RegularPolygon::new(3.8, 7);
1733 let regular_prism = Extrusion::new(polygon, 1.25);
1734 assert_eq!(regular_prism.area(), 107.8808, "incorrect surface area");
1735 assert_eq!(regular_prism.volume(), 49.392204, "incorrect volume");
1736 }
1737
1738 #[test]
1739 fn triangle_math() {
1740 let mut default_triangle = Triangle3d::default();
1742 let reverse_default_triangle = Triangle3d::new(
1743 Vec3::new(0.5, -0.5, 0.0),
1744 Vec3::new(-0.5, -0.5, 0.0),
1745 Vec3::new(0.0, 0.5, 0.0),
1746 );
1747 assert_eq!(default_triangle.area(), 0.5, "incorrect area");
1748 assert_relative_eq!(
1749 default_triangle.perimeter(),
1750 1.0 + 2.0 * ops::sqrt(1.25_f32),
1751 epsilon = 10e-9
1752 );
1753 assert_eq!(default_triangle.normal(), Ok(Dir3::Z), "incorrect normal");
1754 assert!(
1755 !default_triangle.is_degenerate(),
1756 "incorrect degenerate check"
1757 );
1758 assert_eq!(
1759 default_triangle.centroid(),
1760 Vec3::new(0.0, -0.16666667, 0.0),
1761 "incorrect centroid"
1762 );
1763 assert_eq!(
1764 default_triangle.largest_side(),
1765 (Vec3::new(0.0, 0.5, 0.0), Vec3::new(-0.5, -0.5, 0.0))
1766 );
1767 default_triangle.reverse();
1768 assert_eq!(
1769 default_triangle, reverse_default_triangle,
1770 "incorrect reverse"
1771 );
1772 assert_eq!(
1773 default_triangle.circumcenter(),
1774 Vec3::new(0.0, -0.125, 0.0),
1775 "incorrect circumcenter"
1776 );
1777
1778 let right_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::Y);
1780 let obtuse_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::X, Vec3::new(0.0, 0.1, 0.0));
1781 let acute_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::new(0.5, 5.0, 0.0));
1782
1783 assert_eq!(
1784 right_triangle.circumcenter(),
1785 Vec3::new(0.5, 0.5, 0.0),
1786 "incorrect circumcenter"
1787 );
1788 assert_eq!(
1789 obtuse_triangle.circumcenter(),
1790 Vec3::new(0.0, -4.95, 0.0),
1791 "incorrect circumcenter"
1792 );
1793 assert_eq!(
1794 acute_triangle.circumcenter(),
1795 Vec3::new(0.5, 2.475, 0.0),
1796 "incorrect circumcenter"
1797 );
1798
1799 assert!(acute_triangle.is_acute());
1800 assert!(!acute_triangle.is_obtuse());
1801 assert!(!obtuse_triangle.is_acute());
1802 assert!(obtuse_triangle.is_obtuse());
1803
1804 let [a, b, c] = [Vec3::ZERO, Vec3::new(1., 1., 0.5), Vec3::new(-3., 2.5, 1.)];
1806 let triangle = Triangle3d::new(a, b, c);
1807
1808 assert!(!triangle.is_degenerate(), "incorrectly found degenerate");
1809 assert_eq!(triangle.area(), 3.0233467, "incorrect area");
1810 assert_eq!(triangle.perimeter(), 9.832292, "incorrect perimeter");
1811 assert_eq!(
1812 triangle.circumcenter(),
1813 Vec3::new(-1., 1.75, 0.75),
1814 "incorrect circumcenter"
1815 );
1816 assert_eq!(
1817 triangle.normal(),
1818 Ok(Dir3::new_unchecked(Vec3::new(
1819 -0.04134491,
1820 -0.4134491,
1821 0.90958804
1822 ))),
1823 "incorrect normal"
1824 );
1825
1826 let zero_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::ZERO, Vec3::ZERO);
1828 assert!(
1829 zero_degenerate_triangle.is_degenerate(),
1830 "incorrect degenerate check"
1831 );
1832 assert_eq!(
1833 zero_degenerate_triangle.normal(),
1834 Err(InvalidDirectionError::Zero),
1835 "incorrect normal"
1836 );
1837 assert_eq!(
1838 zero_degenerate_triangle.largest_side(),
1839 (Vec3::ZERO, Vec3::ZERO),
1840 "incorrect largest side"
1841 );
1842
1843 let dup_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::X);
1844 assert!(
1845 dup_degenerate_triangle.is_degenerate(),
1846 "incorrect degenerate check"
1847 );
1848 assert_eq!(
1849 dup_degenerate_triangle.normal(),
1850 Err(InvalidDirectionError::Zero),
1851 "incorrect normal"
1852 );
1853 assert_eq!(
1854 dup_degenerate_triangle.largest_side(),
1855 (Vec3::ZERO, Vec3::X),
1856 "incorrect largest side"
1857 );
1858
1859 let collinear_degenerate_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::ZERO, Vec3::X);
1860 assert!(
1861 collinear_degenerate_triangle.is_degenerate(),
1862 "incorrect degenerate check"
1863 );
1864 assert_eq!(
1865 collinear_degenerate_triangle.normal(),
1866 Err(InvalidDirectionError::Zero),
1867 "incorrect normal"
1868 );
1869 assert_eq!(
1870 collinear_degenerate_triangle.largest_side(),
1871 (Vec3::NEG_X, Vec3::X),
1872 "incorrect largest side"
1873 );
1874 }
1875}