1use core::f32::consts::{FRAC_PI_3, PI};
2
3use super::{Circle, Measured2d, Measured3d, Primitive2d, Primitive3d};
4use crate::{
5 ops::{self, FloatPow},
6 Dir3, InvalidDirectionError, Isometry3d, Mat3, Ray3d, Vec2, Vec3,
7};
8
9#[cfg(feature = "bevy_reflect")]
10use bevy_reflect::{std_traits::ReflectDefault, Reflect};
11#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
12use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
13use glam::Quat;
14
15#[cfg(feature = "alloc")]
16use alloc::vec::Vec;
17
18#[derive(Clone, Copy, Debug, PartialEq)]
20#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
21#[cfg_attr(
22 feature = "bevy_reflect",
23 derive(Reflect),
24 reflect(Debug, PartialEq, Default, Clone)
25)]
26#[cfg_attr(
27 all(feature = "serialize", feature = "bevy_reflect"),
28 reflect(Serialize, Deserialize)
29)]
30pub struct Sphere {
31 pub radius: f32,
33}
34
35impl Primitive3d for Sphere {}
36
37impl Default for Sphere {
38 fn default() -> Self {
40 Self { radius: 0.5 }
41 }
42}
43
44impl Sphere {
45 #[inline(always)]
47 pub const fn new(radius: f32) -> Self {
48 Self { radius }
49 }
50
51 #[inline(always)]
53 pub const fn diameter(&self) -> f32 {
54 2.0 * self.radius
55 }
56
57 #[inline(always)]
62 pub fn closest_point(&self, point: Vec3) -> Vec3 {
63 let distance_squared = point.length_squared();
64
65 if distance_squared <= self.radius.squared() {
66 point
68 } else {
69 let dir_to_point = point / ops::sqrt(distance_squared);
72 self.radius * dir_to_point
73 }
74 }
75}
76
77impl Measured3d for Sphere {
78 #[inline(always)]
80 fn area(&self) -> f32 {
81 4.0 * PI * self.radius.squared()
82 }
83
84 #[inline(always)]
86 fn volume(&self) -> f32 {
87 4.0 * FRAC_PI_3 * self.radius.cubed()
88 }
89}
90
91#[derive(Clone, Copy, Debug, PartialEq)]
93#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
94#[cfg_attr(
95 feature = "bevy_reflect",
96 derive(Reflect),
97 reflect(Debug, PartialEq, Default, Clone)
98)]
99#[cfg_attr(
100 all(feature = "serialize", feature = "bevy_reflect"),
101 reflect(Serialize, Deserialize)
102)]
103pub struct Plane3d {
104 pub normal: Dir3,
106 pub half_size: Vec2,
108}
109
110impl Primitive3d for Plane3d {}
111
112impl Default for Plane3d {
113 fn default() -> Self {
115 Self {
116 normal: Dir3::Y,
117 half_size: Vec2::splat(0.5),
118 }
119 }
120}
121
122impl Plane3d {
123 #[inline(always)]
129 pub fn new(normal: Vec3, half_size: Vec2) -> Self {
130 Self {
131 normal: Dir3::new(normal).expect("normal must be nonzero and finite"),
132 half_size,
133 }
134 }
135
136 #[inline(always)]
147 pub fn from_points(a: Vec3, b: Vec3, c: Vec3) -> (Self, Vec3) {
148 let normal = Dir3::new((b - a).cross(c - a)).expect(
149 "finite plane must be defined by three finite points that don't lie on the same line",
150 );
151 let translation = (a + b + c) / 3.0;
152
153 (
154 Self {
155 normal,
156 ..Default::default()
157 },
158 translation,
159 )
160 }
161}
162
163#[derive(Clone, Copy, Debug, PartialEq)]
166#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
167#[cfg_attr(
168 feature = "bevy_reflect",
169 derive(Reflect),
170 reflect(Debug, PartialEq, Default, Clone)
171)]
172#[cfg_attr(
173 all(feature = "serialize", feature = "bevy_reflect"),
174 reflect(Serialize, Deserialize)
175)]
176pub struct InfinitePlane3d {
177 pub normal: Dir3,
179}
180
181impl Primitive3d for InfinitePlane3d {}
182
183impl Default for InfinitePlane3d {
184 fn default() -> Self {
186 Self { normal: Dir3::Y }
187 }
188}
189
190impl InfinitePlane3d {
191 #[inline(always)]
197 pub fn new<T: TryInto<Dir3>>(normal: T) -> Self
198 where
199 <T as TryInto<Dir3>>::Error: core::fmt::Debug,
200 {
201 Self {
202 normal: normal
203 .try_into()
204 .expect("normal must be nonzero and finite"),
205 }
206 }
207
208 #[inline(always)]
219 pub fn from_points(a: Vec3, b: Vec3, c: Vec3) -> (Self, Vec3) {
220 let normal = Dir3::new((b - a).cross(c - a)).expect(
221 "infinite plane must be defined by three finite points that don't lie on the same line",
222 );
223 let translation = (a + b + c) / 3.0;
224
225 (Self { normal }, translation)
226 }
227
228 #[inline]
232 pub fn signed_distance(&self, isometry: impl Into<Isometry3d>, point: Vec3) -> f32 {
233 let isometry = isometry.into();
234 self.normal.dot(isometry.inverse() * point)
235 }
236
237 #[inline]
241 pub fn project_point(&self, isometry: impl Into<Isometry3d>, point: Vec3) -> Vec3 {
242 point - self.normal * self.signed_distance(isometry, point)
243 }
244
245 #[inline]
270 pub fn isometry_into_xy(&self, origin: Vec3) -> Isometry3d {
271 let rotation = Quat::from_rotation_arc(self.normal.as_vec3(), Vec3::Z);
272 let transformed_origin = rotation * origin;
273 Isometry3d::new(-Vec3::Z * transformed_origin.z, rotation)
274 }
275
276 #[inline]
301 pub fn isometry_from_xy(&self, origin: Vec3) -> Isometry3d {
302 self.isometry_into_xy(origin).inverse()
303 }
304
305 #[inline]
333 pub fn isometries_xy(&self, origin: Vec3) -> (Isometry3d, Isometry3d) {
334 let projection = self.isometry_into_xy(origin);
335 (projection, projection.inverse())
336 }
337}
338
339#[derive(Clone, Copy, Debug, PartialEq)]
343#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
344#[cfg_attr(
345 feature = "bevy_reflect",
346 derive(Reflect),
347 reflect(Debug, PartialEq, Clone)
348)]
349#[cfg_attr(
350 all(feature = "serialize", feature = "bevy_reflect"),
351 reflect(Serialize, Deserialize)
352)]
353pub struct Line3d {
354 pub direction: Dir3,
356}
357
358impl Primitive3d for Line3d {}
359
360#[derive(Clone, Copy, Debug, PartialEq)]
362#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
363#[cfg_attr(
364 feature = "bevy_reflect",
365 derive(Reflect),
366 reflect(Debug, PartialEq, Clone)
367)]
368#[cfg_attr(
369 all(feature = "serialize", feature = "bevy_reflect"),
370 reflect(Serialize, Deserialize)
371)]
372#[doc(alias = "LineSegment3d")]
373pub struct Segment3d {
374 pub vertices: [Vec3; 2],
376}
377
378impl Primitive3d for Segment3d {}
379
380impl Default for Segment3d {
381 fn default() -> Self {
382 Self {
383 vertices: [Vec3::new(-0.5, 0.0, 0.0), Vec3::new(0.5, 0.0, 0.0)],
384 }
385 }
386}
387
388impl Segment3d {
389 #[inline(always)]
391 pub const fn new(point1: Vec3, point2: Vec3) -> Self {
392 Self {
393 vertices: [point1, point2],
394 }
395 }
396
397 #[inline(always)]
401 pub fn from_direction_and_length(direction: Dir3, length: f32) -> Self {
402 let endpoint = 0.5 * length * direction;
403 Self {
404 vertices: [-endpoint, endpoint],
405 }
406 }
407
408 #[inline(always)]
413 pub fn from_scaled_direction(scaled_direction: Vec3) -> Self {
414 let endpoint = 0.5 * scaled_direction;
415 Self {
416 vertices: [-endpoint, endpoint],
417 }
418 }
419
420 #[inline(always)]
425 pub fn from_ray_and_length(ray: Ray3d, length: f32) -> Self {
426 Self {
427 vertices: [ray.origin, ray.get_point(length)],
428 }
429 }
430
431 #[inline(always)]
433 pub const fn point1(&self) -> Vec3 {
434 self.vertices[0]
435 }
436
437 #[inline(always)]
439 pub const fn point2(&self) -> Vec3 {
440 self.vertices[1]
441 }
442
443 #[inline(always)]
445 #[doc(alias = "midpoint")]
446 pub fn center(&self) -> Vec3 {
447 self.point1().midpoint(self.point2())
448 }
449
450 #[inline(always)]
452 pub fn length(&self) -> f32 {
453 self.point1().distance(self.point2())
454 }
455
456 #[inline(always)]
458 pub fn length_squared(&self) -> f32 {
459 self.point1().distance_squared(self.point2())
460 }
461
462 #[inline(always)]
470 pub fn direction(&self) -> Dir3 {
471 self.try_direction().unwrap_or_else(|err| {
472 panic!("Failed to compute the direction of a line segment: {err}")
473 })
474 }
475
476 #[inline(always)]
481 pub fn try_direction(&self) -> Result<Dir3, InvalidDirectionError> {
482 Dir3::new(self.scaled_direction())
483 }
484
485 #[inline(always)]
487 pub fn scaled_direction(&self) -> Vec3 {
488 self.point2() - self.point1()
489 }
490
491 #[inline(always)]
493 pub fn transformed(&self, isometry: impl Into<Isometry3d>) -> Self {
494 let isometry: Isometry3d = isometry.into();
495 Self::new(
496 isometry.transform_point(self.point1()).into(),
497 isometry.transform_point(self.point2()).into(),
498 )
499 }
500
501 #[inline(always)]
503 pub fn translated(&self, translation: Vec3) -> Segment3d {
504 Self::new(self.point1() + translation, self.point2() + translation)
505 }
506
507 #[inline(always)]
509 pub fn rotated(&self, rotation: Quat) -> Segment3d {
510 Segment3d::new(rotation * self.point1(), rotation * self.point2())
511 }
512
513 #[inline(always)]
515 pub fn rotated_around(&self, rotation: Quat, point: Vec3) -> Segment3d {
516 let offset = self.translated(-point);
518 let rotated = offset.rotated(rotation);
519 rotated.translated(point)
520 }
521
522 #[inline(always)]
524 pub fn rotated_around_center(&self, rotation: Quat) -> Segment3d {
525 self.rotated_around(rotation, self.center())
526 }
527
528 #[inline(always)]
530 pub fn centered(&self) -> Segment3d {
531 let center = self.center();
532 self.translated(-center)
533 }
534
535 #[inline(always)]
537 pub fn resized(&self, length: f32) -> Segment3d {
538 let offset_from_origin = self.center();
539 let centered = self.translated(-offset_from_origin);
540 let ratio = length / self.length();
541 let segment = Segment3d::new(centered.point1() * ratio, centered.point2() * ratio);
542 segment.translated(offset_from_origin)
543 }
544
545 #[inline(always)]
547 pub fn reverse(&mut self) {
548 let [point1, point2] = &mut self.vertices;
549 core::mem::swap(point1, point2);
550 }
551
552 #[inline(always)]
554 #[must_use]
555 pub fn reversed(mut self) -> Self {
556 self.reverse();
557 self
558 }
559
560 #[inline(always)]
562 pub fn closest_point(&self, point: Vec3) -> Vec3 {
563 let segment_vector = self.vertices[1] - self.vertices[0];
572 let offset = point - self.vertices[0];
573 let projection_scaled = segment_vector.dot(offset);
575
576 if projection_scaled <= 0.0 {
578 return self.vertices[0];
579 }
580
581 let length_squared = segment_vector.length_squared();
582 if projection_scaled >= length_squared {
584 return self.vertices[1];
585 }
586
587 let t = projection_scaled / length_squared;
589 self.vertices[0] + t * segment_vector
590 }
591}
592
593impl From<[Vec3; 2]> for Segment3d {
594 #[inline(always)]
595 fn from(vertices: [Vec3; 2]) -> Self {
596 Self { vertices }
597 }
598}
599
600impl From<(Vec3, Vec3)> for Segment3d {
601 #[inline(always)]
602 fn from((point1, point2): (Vec3, Vec3)) -> Self {
603 Self::new(point1, point2)
604 }
605}
606
607#[cfg(feature = "alloc")]
609#[derive(Clone, Debug, PartialEq)]
610#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
611#[cfg_attr(
612 feature = "bevy_reflect",
613 derive(Reflect),
614 reflect(Debug, PartialEq, Clone)
615)]
616#[cfg_attr(
617 all(feature = "serialize", feature = "bevy_reflect"),
618 reflect(Serialize, Deserialize)
619)]
620pub struct Polyline3d {
621 pub vertices: Vec<Vec3>,
623}
624
625#[cfg(feature = "alloc")]
626impl Primitive3d for Polyline3d {}
627
628#[cfg(feature = "alloc")]
629impl FromIterator<Vec3> for Polyline3d {
630 fn from_iter<I: IntoIterator<Item = Vec3>>(iter: I) -> Self {
631 Self {
632 vertices: iter.into_iter().collect(),
633 }
634 }
635}
636
637#[cfg(feature = "alloc")]
638impl Default for Polyline3d {
639 fn default() -> Self {
640 Self::new([Vec3::new(-0.5, 0.0, 0.0), Vec3::new(0.5, 0.0, 0.0)])
641 }
642}
643
644#[cfg(feature = "alloc")]
645impl Polyline3d {
646 pub fn new(vertices: impl IntoIterator<Item = Vec3>) -> Self {
648 Self::from_iter(vertices)
649 }
650
651 pub fn with_subdivisions(start: Vec3, end: Vec3, subdivisions: usize) -> Self {
655 let total_vertices = subdivisions + 2;
656 let mut vertices = Vec::with_capacity(total_vertices);
657
658 let step = (end - start) / (subdivisions + 1) as f32;
659 for i in 0..total_vertices {
660 vertices.push(start + step * i as f32);
661 }
662
663 Self { vertices }
664 }
665}
666
667#[derive(Clone, Copy, Debug, PartialEq)]
670#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
671#[cfg_attr(
672 feature = "bevy_reflect",
673 derive(Reflect),
674 reflect(Debug, PartialEq, Default, Clone)
675)]
676#[cfg_attr(
677 all(feature = "serialize", feature = "bevy_reflect"),
678 reflect(Serialize, Deserialize)
679)]
680pub struct Cuboid {
681 pub half_size: Vec3,
683}
684
685impl Primitive3d for Cuboid {}
686
687impl Default for Cuboid {
688 fn default() -> Self {
690 Self {
691 half_size: Vec3::splat(0.5),
692 }
693 }
694}
695
696impl Cuboid {
697 #[inline(always)]
699 pub const fn new(x_length: f32, y_length: f32, z_length: f32) -> Self {
700 Self::from_size(Vec3::new(x_length, y_length, z_length))
701 }
702
703 #[inline(always)]
705 pub const fn from_size(size: Vec3) -> Self {
706 Self {
707 half_size: Vec3::new(size.x / 2.0, size.y / 2.0, size.z / 2.0),
708 }
709 }
710
711 #[inline(always)]
713 pub fn from_corners(point1: Vec3, point2: Vec3) -> Self {
714 Self {
715 half_size: (point2 - point1).abs() / 2.0,
716 }
717 }
718
719 #[inline(always)]
722 pub const fn from_length(length: f32) -> Self {
723 Self {
724 half_size: Vec3::splat(length / 2.0),
725 }
726 }
727
728 #[inline(always)]
730 pub fn size(&self) -> Vec3 {
731 2.0 * self.half_size
732 }
733
734 #[inline(always)]
739 pub fn closest_point(&self, point: Vec3) -> Vec3 {
740 point.clamp(-self.half_size, self.half_size)
742 }
743}
744
745impl Measured3d for Cuboid {
746 #[inline(always)]
748 fn area(&self) -> f32 {
749 8.0 * (self.half_size.x * self.half_size.y
750 + self.half_size.y * self.half_size.z
751 + self.half_size.x * self.half_size.z)
752 }
753
754 #[inline(always)]
756 fn volume(&self) -> f32 {
757 8.0 * self.half_size.x * self.half_size.y * self.half_size.z
758 }
759}
760
761#[derive(Clone, Copy, Debug, PartialEq)]
763#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
764#[cfg_attr(
765 feature = "bevy_reflect",
766 derive(Reflect),
767 reflect(Debug, PartialEq, Default, Clone)
768)]
769#[cfg_attr(
770 all(feature = "serialize", feature = "bevy_reflect"),
771 reflect(Serialize, Deserialize)
772)]
773pub struct Cylinder {
774 pub radius: f32,
776 pub half_height: f32,
778}
779
780impl Primitive3d for Cylinder {}
781
782impl Default for Cylinder {
783 fn default() -> Self {
785 Self {
786 radius: 0.5,
787 half_height: 0.5,
788 }
789 }
790}
791
792impl Cylinder {
793 #[inline(always)]
795 pub const fn new(radius: f32, height: f32) -> Self {
796 Self {
797 radius,
798 half_height: height / 2.0,
799 }
800 }
801
802 #[inline(always)]
804 pub const fn base(&self) -> Circle {
805 Circle {
806 radius: self.radius,
807 }
808 }
809
810 #[inline(always)]
813 #[doc(alias = "side_area")]
814 pub const fn lateral_area(&self) -> f32 {
815 4.0 * PI * self.radius * self.half_height
816 }
817
818 #[inline(always)]
820 pub fn base_area(&self) -> f32 {
821 PI * self.radius.squared()
822 }
823}
824
825impl Measured3d for Cylinder {
826 #[inline(always)]
828 fn area(&self) -> f32 {
829 2.0 * PI * self.radius * (self.radius + 2.0 * self.half_height)
830 }
831
832 #[inline(always)]
834 fn volume(&self) -> f32 {
835 self.base_area() * 2.0 * self.half_height
836 }
837}
838
839#[derive(Clone, Copy, Debug, PartialEq)]
842#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
843#[cfg_attr(
844 feature = "bevy_reflect",
845 derive(Reflect),
846 reflect(Debug, PartialEq, Default, Clone)
847)]
848#[cfg_attr(
849 all(feature = "serialize", feature = "bevy_reflect"),
850 reflect(Serialize, Deserialize)
851)]
852pub struct Capsule3d {
853 pub radius: f32,
855 pub half_length: f32,
857}
858
859impl Primitive3d for Capsule3d {}
860
861impl Default for Capsule3d {
862 fn default() -> Self {
865 Self {
866 radius: 0.5,
867 half_length: 0.5,
868 }
869 }
870}
871
872impl Capsule3d {
873 pub const fn new(radius: f32, length: f32) -> Self {
875 Self {
876 radius,
877 half_length: length / 2.0,
878 }
879 }
880
881 #[inline(always)]
884 pub const fn to_cylinder(&self) -> Cylinder {
885 Cylinder {
886 radius: self.radius,
887 half_height: self.half_length,
888 }
889 }
890}
891
892impl Measured3d for Capsule3d {
893 #[inline(always)]
895 fn area(&self) -> f32 {
896 4.0 * PI * self.radius * (self.radius + self.half_length)
898 }
899
900 #[inline(always)]
902 fn volume(&self) -> f32 {
903 let diameter = self.radius * 2.0;
905 PI * self.radius * diameter * (diameter / 3.0 + self.half_length)
906 }
907}
908
909#[derive(Clone, Copy, Debug, PartialEq)]
913#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
914#[cfg_attr(
915 feature = "bevy_reflect",
916 derive(Reflect),
917 reflect(Debug, PartialEq, Default, Clone)
918)]
919#[cfg_attr(
920 all(feature = "serialize", feature = "bevy_reflect"),
921 reflect(Serialize, Deserialize)
922)]
923pub struct Cone {
924 pub radius: f32,
926 pub height: f32,
928}
929
930impl Primitive3d for Cone {}
931
932impl Default for Cone {
933 fn default() -> Self {
935 Self {
936 radius: 0.5,
937 height: 1.0,
938 }
939 }
940}
941
942impl Cone {
943 pub const fn new(radius: f32, height: f32) -> Self {
945 Self { radius, height }
946 }
947 #[inline(always)]
949 pub const fn base(&self) -> Circle {
950 Circle {
951 radius: self.radius,
952 }
953 }
954
955 #[inline(always)]
958 #[doc(alias = "side_length")]
959 pub fn slant_height(&self) -> f32 {
960 ops::hypot(self.radius, self.height)
961 }
962
963 #[inline(always)]
966 #[doc(alias = "side_area")]
967 pub fn lateral_area(&self) -> f32 {
968 PI * self.radius * self.slant_height()
969 }
970
971 #[inline(always)]
973 pub fn base_area(&self) -> f32 {
974 PI * self.radius.squared()
975 }
976}
977
978impl Measured3d for Cone {
979 #[inline(always)]
981 fn area(&self) -> f32 {
982 self.base_area() + self.lateral_area()
983 }
984
985 #[inline(always)]
987 fn volume(&self) -> f32 {
988 (self.base_area() * self.height) / 3.0
989 }
990}
991
992#[derive(Clone, Copy, Debug, PartialEq)]
996#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
997#[cfg_attr(
998 feature = "bevy_reflect",
999 derive(Reflect),
1000 reflect(Debug, PartialEq, Default, Clone)
1001)]
1002#[cfg_attr(
1003 all(feature = "serialize", feature = "bevy_reflect"),
1004 reflect(Serialize, Deserialize)
1005)]
1006pub struct ConicalFrustum {
1007 pub radius_top: f32,
1009 pub radius_bottom: f32,
1011 pub height: f32,
1013}
1014
1015impl Primitive3d for ConicalFrustum {}
1016
1017impl Default for ConicalFrustum {
1018 fn default() -> Self {
1020 Self {
1021 radius_top: 0.25,
1022 radius_bottom: 0.5,
1023 height: 0.5,
1024 }
1025 }
1026}
1027
1028#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1030pub enum TorusKind {
1031 Ring,
1034 Horn,
1037 Spindle,
1040 Invalid,
1044}
1045
1046#[derive(Clone, Copy, Debug, PartialEq)]
1049#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1050#[cfg_attr(
1051 feature = "bevy_reflect",
1052 derive(Reflect),
1053 reflect(Debug, PartialEq, Default, Clone)
1054)]
1055#[cfg_attr(
1056 all(feature = "serialize", feature = "bevy_reflect"),
1057 reflect(Serialize, Deserialize)
1058)]
1059pub struct Torus {
1060 #[doc(
1062 alias = "ring_radius",
1063 alias = "tube_radius",
1064 alias = "cross_section_radius"
1065 )]
1066 pub minor_radius: f32,
1067 #[doc(alias = "radius_of_revolution")]
1069 pub major_radius: f32,
1070}
1071
1072impl Primitive3d for Torus {}
1073
1074impl Default for Torus {
1075 fn default() -> Self {
1077 Self {
1078 minor_radius: 0.25,
1079 major_radius: 0.75,
1080 }
1081 }
1082}
1083
1084impl Torus {
1085 #[inline(always)]
1090 pub const fn new(inner_radius: f32, outer_radius: f32) -> Self {
1091 let minor_radius = (outer_radius - inner_radius) / 2.0;
1092 let major_radius = outer_radius - minor_radius;
1093
1094 Self {
1095 minor_radius,
1096 major_radius,
1097 }
1098 }
1099
1100 #[inline(always)]
1104 pub const fn inner_radius(&self) -> f32 {
1105 self.major_radius - self.minor_radius
1106 }
1107
1108 #[inline(always)]
1112 pub const fn outer_radius(&self) -> f32 {
1113 self.major_radius + self.minor_radius
1114 }
1115
1116 #[inline(always)]
1125 pub fn kind(&self) -> TorusKind {
1126 if self.minor_radius <= 0.0
1128 || !self.minor_radius.is_finite()
1129 || self.major_radius <= 0.0
1130 || !self.major_radius.is_finite()
1131 {
1132 return TorusKind::Invalid;
1133 }
1134
1135 match self.major_radius.partial_cmp(&self.minor_radius).unwrap() {
1136 core::cmp::Ordering::Greater => TorusKind::Ring,
1137 core::cmp::Ordering::Equal => TorusKind::Horn,
1138 core::cmp::Ordering::Less => TorusKind::Spindle,
1139 }
1140 }
1141}
1142
1143impl Measured3d for Torus {
1144 #[inline(always)]
1147 fn area(&self) -> f32 {
1148 4.0 * PI.squared() * self.major_radius * self.minor_radius
1149 }
1150
1151 #[inline(always)]
1154 fn volume(&self) -> f32 {
1155 2.0 * PI.squared() * self.major_radius * self.minor_radius.squared()
1156 }
1157}
1158
1159#[derive(Clone, Copy, Debug, PartialEq)]
1161#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1162#[cfg_attr(
1163 feature = "bevy_reflect",
1164 derive(Reflect),
1165 reflect(Debug, PartialEq, Default, Clone)
1166)]
1167#[cfg_attr(
1168 all(feature = "serialize", feature = "bevy_reflect"),
1169 reflect(Serialize, Deserialize)
1170)]
1171pub struct Triangle3d {
1172 pub vertices: [Vec3; 3],
1174}
1175
1176impl Primitive3d for Triangle3d {}
1177
1178impl Default for Triangle3d {
1179 fn default() -> Self {
1181 Self {
1182 vertices: [
1183 Vec3::new(0.0, 0.5, 0.0),
1184 Vec3::new(-0.5, -0.5, 0.0),
1185 Vec3::new(0.5, -0.5, 0.0),
1186 ],
1187 }
1188 }
1189}
1190
1191impl Triangle3d {
1192 #[inline(always)]
1194 pub const fn new(a: Vec3, b: Vec3, c: Vec3) -> Self {
1195 Self {
1196 vertices: [a, b, c],
1197 }
1198 }
1199
1200 #[inline(always)]
1210 pub fn normal(&self) -> Result<Dir3, InvalidDirectionError> {
1211 let [a, b, c] = self.vertices;
1212 let ab = b - a;
1213 let ac = c - a;
1214 Dir3::new(ab.cross(ac))
1215 }
1216
1217 #[inline(always)]
1222 pub fn is_degenerate(&self) -> bool {
1223 let [a, b, c] = self.vertices;
1224 let ab = b - a;
1225 let ac = c - a;
1226 ab.cross(ac).length() < 10e-7
1227 }
1228
1229 #[inline(always)]
1231 pub fn is_acute(&self) -> bool {
1232 let [a, b, c] = self.vertices;
1233 let ab = b - a;
1234 let bc = c - b;
1235 let ca = a - c;
1236
1237 let mut side_lengths = [
1239 ab.length_squared(),
1240 bc.length_squared(),
1241 ca.length_squared(),
1242 ];
1243 side_lengths.sort_by(|a, b| a.partial_cmp(b).unwrap());
1244 side_lengths[0] + side_lengths[1] > side_lengths[2]
1245 }
1246
1247 #[inline(always)]
1249 pub fn is_obtuse(&self) -> bool {
1250 let [a, b, c] = self.vertices;
1251 let ab = b - a;
1252 let bc = c - b;
1253 let ca = a - c;
1254
1255 let mut side_lengths = [
1257 ab.length_squared(),
1258 bc.length_squared(),
1259 ca.length_squared(),
1260 ];
1261 side_lengths.sort_by(|a, b| a.partial_cmp(b).unwrap());
1262 side_lengths[0] + side_lengths[1] < side_lengths[2]
1263 }
1264
1265 #[inline(always)]
1267 pub fn reverse(&mut self) {
1268 self.vertices.swap(0, 2);
1269 }
1270
1271 #[inline(always)]
1273 #[must_use]
1274 pub fn reversed(mut self) -> Triangle3d {
1275 self.reverse();
1276 self
1277 }
1278
1279 #[doc(alias("center", "barycenter", "baricenter"))]
1284 #[inline(always)]
1285 pub fn centroid(&self) -> Vec3 {
1286 (self.vertices[0] + self.vertices[1] + self.vertices[2]) / 3.0
1287 }
1288
1289 #[inline(always)]
1293 pub fn largest_side(&self) -> (Vec3, Vec3) {
1294 let [a, b, c] = self.vertices;
1295 let ab = b - a;
1296 let bc = c - b;
1297 let ca = a - c;
1298
1299 let mut largest_side_points = (a, b);
1300 let mut largest_side_length = ab.length_squared();
1301
1302 let bc_length = bc.length_squared();
1303 if bc_length > largest_side_length {
1304 largest_side_points = (b, c);
1305 largest_side_length = bc_length;
1306 }
1307
1308 let ca_length = ca.length_squared();
1309 if ca_length > largest_side_length {
1310 largest_side_points = (a, c);
1311 }
1312
1313 largest_side_points
1314 }
1315
1316 #[inline(always)]
1318 pub fn circumcenter(&self) -> Vec3 {
1319 if self.is_degenerate() {
1320 let (p1, p2) = self.largest_side();
1322 return (p1 + p2) / 2.0;
1323 }
1324
1325 let [a, b, c] = self.vertices;
1326 let ab = b - a;
1327 let ac = c - a;
1328 let n = ab.cross(ac);
1329
1330 a + ((ac.length_squared() * n.cross(ab) + ab.length_squared() * ac.cross(ab).cross(ac))
1332 / (2.0 * n.length_squared()))
1333 }
1334}
1335
1336impl Measured2d for Triangle3d {
1337 #[inline(always)]
1339 fn area(&self) -> f32 {
1340 let [a, b, c] = self.vertices;
1341 let ab = b - a;
1342 let ac = c - a;
1343 ab.cross(ac).length() / 2.0
1344 }
1345
1346 #[inline(always)]
1348 fn perimeter(&self) -> f32 {
1349 let [a, b, c] = self.vertices;
1350 a.distance(b) + b.distance(c) + c.distance(a)
1351 }
1352}
1353
1354#[derive(Clone, Copy, Debug, PartialEq)]
1356#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1357#[cfg_attr(
1358 feature = "bevy_reflect",
1359 derive(Reflect),
1360 reflect(Debug, PartialEq, Default, Clone)
1361)]
1362#[cfg_attr(
1363 all(feature = "serialize", feature = "bevy_reflect"),
1364 reflect(Serialize, Deserialize)
1365)]
1366pub struct Tetrahedron {
1367 pub vertices: [Vec3; 4],
1369}
1370
1371impl Primitive3d for Tetrahedron {}
1372
1373impl Default for Tetrahedron {
1374 fn default() -> Self {
1377 Self {
1378 vertices: [
1379 Vec3::new(0.5, 0.5, 0.5),
1380 Vec3::new(-0.5, 0.5, -0.5),
1381 Vec3::new(-0.5, -0.5, 0.5),
1382 Vec3::new(0.5, -0.5, -0.5),
1383 ],
1384 }
1385 }
1386}
1387
1388impl Tetrahedron {
1389 #[inline(always)]
1391 pub const fn new(a: Vec3, b: Vec3, c: Vec3, d: Vec3) -> Self {
1392 Self {
1393 vertices: [a, b, c, d],
1394 }
1395 }
1396
1397 #[inline(always)]
1403 pub fn signed_volume(&self) -> f32 {
1404 let [a, b, c, d] = self.vertices;
1405 let ab = b - a;
1406 let ac = c - a;
1407 let ad = d - a;
1408 Mat3::from_cols(ab, ac, ad).determinant() / 6.0
1409 }
1410
1411 #[doc(alias("center", "barycenter", "baricenter"))]
1416 #[inline(always)]
1417 pub fn centroid(&self) -> Vec3 {
1418 (self.vertices[0] + self.vertices[1] + self.vertices[2] + self.vertices[3]) / 4.0
1419 }
1420
1421 #[inline(always)]
1427 pub fn faces(&self) -> [Triangle3d; 4] {
1428 let [a, b, c, d] = self.vertices;
1429 [
1430 Triangle3d::new(b, c, d),
1431 Triangle3d::new(a, c, d).reversed(),
1432 Triangle3d::new(a, b, d),
1433 Triangle3d::new(a, b, c).reversed(),
1434 ]
1435 }
1436}
1437
1438impl Measured3d for Tetrahedron {
1439 #[inline(always)]
1441 fn area(&self) -> f32 {
1442 let [a, b, c, d] = self.vertices;
1443 let ab = b - a;
1444 let ac = c - a;
1445 let ad = d - a;
1446 let bc = c - b;
1447 let bd = d - b;
1448 (ab.cross(ac).length()
1449 + ab.cross(ad).length()
1450 + ac.cross(ad).length()
1451 + bc.cross(bd).length())
1452 / 2.0
1453 }
1454
1455 #[inline(always)]
1457 fn volume(&self) -> f32 {
1458 ops::abs(self.signed_volume())
1459 }
1460}
1461
1462#[doc(alias = "Prism")]
1470#[derive(Clone, Copy, Debug, PartialEq)]
1471#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1472pub struct Extrusion<T: Primitive2d> {
1473 pub base_shape: T,
1475 pub half_depth: f32,
1477}
1478
1479impl<T: Primitive2d> Primitive3d for Extrusion<T> {}
1480
1481impl<T: Primitive2d> Extrusion<T> {
1482 pub fn new(base_shape: T, depth: f32) -> Self {
1484 Self {
1485 base_shape,
1486 half_depth: depth / 2.,
1487 }
1488 }
1489}
1490
1491impl<T: Primitive2d + Measured2d> Measured3d for Extrusion<T> {
1492 fn area(&self) -> f32 {
1494 2. * (self.base_shape.area() + self.half_depth * self.base_shape.perimeter())
1495 }
1496
1497 fn volume(&self) -> f32 {
1499 2. * self.base_shape.area() * self.half_depth
1500 }
1501}
1502
1503#[cfg(test)]
1504mod tests {
1505 use super::*;
1508 use crate::{InvalidDirectionError, Quat};
1509 use approx::assert_relative_eq;
1510
1511 #[test]
1512 fn direction_creation() {
1513 assert_eq!(Dir3::new(Vec3::X * 12.5), Ok(Dir3::X));
1514 assert_eq!(
1515 Dir3::new(Vec3::new(0.0, 0.0, 0.0)),
1516 Err(InvalidDirectionError::Zero)
1517 );
1518 assert_eq!(
1519 Dir3::new(Vec3::new(f32::INFINITY, 0.0, 0.0)),
1520 Err(InvalidDirectionError::Infinite)
1521 );
1522 assert_eq!(
1523 Dir3::new(Vec3::new(f32::NEG_INFINITY, 0.0, 0.0)),
1524 Err(InvalidDirectionError::Infinite)
1525 );
1526 assert_eq!(
1527 Dir3::new(Vec3::new(f32::NAN, 0.0, 0.0)),
1528 Err(InvalidDirectionError::NaN)
1529 );
1530 assert_eq!(Dir3::new_and_length(Vec3::X * 6.5), Ok((Dir3::X, 6.5)));
1531
1532 assert!(
1534 (Quat::from_rotation_z(core::f32::consts::FRAC_PI_2) * Dir3::X)
1535 .abs_diff_eq(Vec3::Y, 10e-6)
1536 );
1537 }
1538
1539 #[test]
1540 fn cuboid_closest_point() {
1541 let cuboid = Cuboid::new(2.0, 2.0, 2.0);
1542 assert_eq!(cuboid.closest_point(Vec3::X * 10.0), Vec3::X);
1543 assert_eq!(cuboid.closest_point(Vec3::NEG_ONE * 10.0), Vec3::NEG_ONE);
1544 assert_eq!(
1545 cuboid.closest_point(Vec3::new(0.25, 0.1, 0.3)),
1546 Vec3::new(0.25, 0.1, 0.3)
1547 );
1548 }
1549
1550 #[test]
1551 fn sphere_closest_point() {
1552 let sphere = Sphere { radius: 1.0 };
1553 assert_eq!(sphere.closest_point(Vec3::X * 10.0), Vec3::X);
1554 assert_eq!(
1555 sphere.closest_point(Vec3::NEG_ONE * 10.0),
1556 Vec3::NEG_ONE.normalize()
1557 );
1558 assert_eq!(
1559 sphere.closest_point(Vec3::new(0.25, 0.1, 0.3)),
1560 Vec3::new(0.25, 0.1, 0.3)
1561 );
1562 }
1563
1564 #[test]
1565 fn segment_closest_point() {
1566 assert_eq!(
1567 Segment3d::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(3.0, 0.0, 0.0))
1568 .closest_point(Vec3::new(1.0, 6.0, -2.0)),
1569 Vec3::new(1.0, 0.0, 0.0)
1570 );
1571
1572 let segments = [
1573 Segment3d::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(0.0, 0.0, 0.0)),
1574 Segment3d::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0)),
1575 Segment3d::new(Vec3::new(1.0, 0.0, 2.0), Vec3::new(0.0, 1.0, -2.0)),
1576 Segment3d::new(
1577 Vec3::new(1.0, 0.0, 0.0),
1578 Vec3::new(1.0, 5.0 * f32::EPSILON, 0.0),
1579 ),
1580 ];
1581 let points = [
1582 Vec3::new(0.0, 0.0, 0.0),
1583 Vec3::new(1.0, 0.0, 0.0),
1584 Vec3::new(-1.0, 1.0, 2.0),
1585 Vec3::new(1.0, 1.0, 1.0),
1586 Vec3::new(-1.0, 0.0, 0.0),
1587 Vec3::new(5.0, -1.0, 0.5),
1588 Vec3::new(1.0, f32::EPSILON, 0.0),
1589 ];
1590
1591 for point in points.iter() {
1592 for segment in segments.iter() {
1593 let closest = segment.closest_point(*point);
1594 assert!(
1595 point.distance_squared(closest) <= point.distance_squared(segment.point1()),
1596 "Closest point must always be at least as close as either vertex."
1597 );
1598 assert!(
1599 point.distance_squared(closest) <= point.distance_squared(segment.point2()),
1600 "Closest point must always be at least as close as either vertex."
1601 );
1602 assert!(
1603 point.distance_squared(closest) <= point.distance_squared(segment.center()),
1604 "Closest point must always be at least as close as the center."
1605 );
1606 let closest_to_closest = segment.closest_point(closest);
1607 assert_relative_eq!(closest_to_closest, closest);
1609 }
1610 }
1611 }
1612
1613 #[test]
1614 fn sphere_math() {
1615 let sphere = Sphere { radius: 4.0 };
1616 assert_eq!(sphere.diameter(), 8.0, "incorrect diameter");
1617 assert_eq!(sphere.area(), 201.06193, "incorrect area");
1618 assert_eq!(sphere.volume(), 268.08257, "incorrect volume");
1619 }
1620
1621 #[test]
1622 fn plane_from_points() {
1623 let (plane, translation) = Plane3d::from_points(Vec3::X, Vec3::Z, Vec3::NEG_X);
1624 assert_eq!(*plane.normal, Vec3::NEG_Y, "incorrect normal");
1625 assert_eq!(plane.half_size, Vec2::new(0.5, 0.5), "incorrect half size");
1626 assert_eq!(translation, Vec3::Z * 0.33333334, "incorrect translation");
1627 }
1628
1629 #[test]
1630 fn infinite_plane_math() {
1631 let (plane, origin) = InfinitePlane3d::from_points(Vec3::X, Vec3::Z, Vec3::NEG_X);
1632 assert_eq!(*plane.normal, Vec3::NEG_Y, "incorrect normal");
1633 assert_eq!(origin, Vec3::Z * 0.33333334, "incorrect translation");
1634
1635 let point_in_plane = Vec3::X + Vec3::Z;
1636 assert_eq!(
1637 plane.signed_distance(origin, point_in_plane),
1638 0.0,
1639 "incorrect distance"
1640 );
1641 assert_eq!(
1642 plane.project_point(origin, point_in_plane),
1643 point_in_plane,
1644 "incorrect point"
1645 );
1646
1647 let point_outside = Vec3::Y;
1648 assert_eq!(
1649 plane.signed_distance(origin, point_outside),
1650 -1.0,
1651 "incorrect distance"
1652 );
1653 assert_eq!(
1654 plane.project_point(origin, point_outside),
1655 Vec3::ZERO,
1656 "incorrect point"
1657 );
1658
1659 let point_outside = Vec3::NEG_Y;
1660 assert_eq!(
1661 plane.signed_distance(origin, point_outside),
1662 1.0,
1663 "incorrect distance"
1664 );
1665 assert_eq!(
1666 plane.project_point(origin, point_outside),
1667 Vec3::ZERO,
1668 "incorrect point"
1669 );
1670
1671 let area_f = |[a, b, c]: [Vec3; 3]| (a - b).cross(a - c).length() * 0.5;
1672 let (proj, inj) = plane.isometries_xy(origin);
1673
1674 let triangle = [Vec3::X, Vec3::Y, Vec3::ZERO];
1675 assert_eq!(area_f(triangle), 0.5, "incorrect area");
1676
1677 let triangle_proj = triangle.map(|vec3| proj * vec3);
1678 assert_relative_eq!(area_f(triangle_proj), 0.5);
1679
1680 let triangle_proj_inj = triangle_proj.map(|vec3| inj * vec3);
1681 assert_relative_eq!(area_f(triangle_proj_inj), 0.5);
1682 }
1683
1684 #[test]
1685 fn cuboid_math() {
1686 let cuboid = Cuboid::new(3.0, 7.0, 2.0);
1687 assert_eq!(
1688 cuboid,
1689 Cuboid::from_corners(Vec3::new(-1.5, -3.5, -1.0), Vec3::new(1.5, 3.5, 1.0)),
1690 "incorrect dimensions when created from corners"
1691 );
1692 assert_eq!(cuboid.area(), 82.0, "incorrect area");
1693 assert_eq!(cuboid.volume(), 42.0, "incorrect volume");
1694 }
1695
1696 #[test]
1697 fn cylinder_math() {
1698 let cylinder = Cylinder::new(2.0, 9.0);
1699 assert_eq!(
1700 cylinder.base(),
1701 Circle { radius: 2.0 },
1702 "base produces incorrect circle"
1703 );
1704 assert_eq!(
1705 cylinder.lateral_area(),
1706 113.097336,
1707 "incorrect lateral area"
1708 );
1709 assert_eq!(cylinder.base_area(), 12.566371, "incorrect base area");
1710 assert_relative_eq!(cylinder.area(), 138.23007);
1711 assert_eq!(cylinder.volume(), 113.097336, "incorrect volume");
1712 }
1713
1714 #[test]
1715 fn capsule_math() {
1716 let capsule = Capsule3d::new(2.0, 9.0);
1717 assert_eq!(
1718 capsule.to_cylinder(),
1719 Cylinder::new(2.0, 9.0),
1720 "cylinder wasn't created correctly from a capsule"
1721 );
1722 assert_eq!(capsule.area(), 163.36282, "incorrect area");
1723 assert_relative_eq!(capsule.volume(), 146.60765);
1724 }
1725
1726 #[test]
1727 fn cone_math() {
1728 let cone = Cone {
1729 radius: 2.0,
1730 height: 9.0,
1731 };
1732 assert_eq!(
1733 cone.base(),
1734 Circle { radius: 2.0 },
1735 "base produces incorrect circle"
1736 );
1737 assert_eq!(cone.slant_height(), 9.219544, "incorrect slant height");
1738 assert_eq!(cone.lateral_area(), 57.92811, "incorrect lateral area");
1739 assert_eq!(cone.base_area(), 12.566371, "incorrect base area");
1740 assert_relative_eq!(cone.area(), 70.49447);
1741 assert_eq!(cone.volume(), 37.699111, "incorrect volume");
1742 }
1743
1744 #[test]
1745 fn torus_math() {
1746 let torus = Torus {
1747 minor_radius: 0.3,
1748 major_radius: 2.8,
1749 };
1750 assert_eq!(torus.inner_radius(), 2.5, "incorrect inner radius");
1751 assert_eq!(torus.outer_radius(), 3.1, "incorrect outer radius");
1752 assert_eq!(torus.kind(), TorusKind::Ring, "incorrect torus kind");
1753 assert_eq!(
1754 Torus::new(0.0, 1.0).kind(),
1755 TorusKind::Horn,
1756 "incorrect torus kind"
1757 );
1758 assert_eq!(
1759 Torus::new(-0.5, 1.0).kind(),
1760 TorusKind::Spindle,
1761 "incorrect torus kind"
1762 );
1763 assert_eq!(
1764 Torus::new(1.5, 1.0).kind(),
1765 TorusKind::Invalid,
1766 "torus should be invalid"
1767 );
1768 assert_relative_eq!(torus.area(), 33.16187);
1769 assert_relative_eq!(torus.volume(), 4.97428, epsilon = 0.00001);
1770 }
1771
1772 #[test]
1773 fn tetrahedron_math() {
1774 let tetrahedron = Tetrahedron {
1775 vertices: [
1776 Vec3::new(0.3, 1.0, 1.7),
1777 Vec3::new(-2.0, -1.0, 0.0),
1778 Vec3::new(1.8, 0.5, 1.0),
1779 Vec3::new(-1.0, -2.0, 3.5),
1780 ],
1781 };
1782 assert_eq!(tetrahedron.area(), 19.251068, "incorrect area");
1783 assert_eq!(tetrahedron.volume(), 3.2058334, "incorrect volume");
1784 assert_eq!(
1785 tetrahedron.signed_volume(),
1786 3.2058334,
1787 "incorrect signed volume"
1788 );
1789 assert_relative_eq!(tetrahedron.centroid(), Vec3::new(-0.225, -0.375, 1.55));
1790
1791 assert_eq!(Tetrahedron::default().area(), 3.4641016, "incorrect area");
1792 assert_eq!(
1793 Tetrahedron::default().volume(),
1794 0.33333334,
1795 "incorrect volume"
1796 );
1797 assert_eq!(
1798 Tetrahedron::default().signed_volume(),
1799 -0.33333334,
1800 "incorrect signed volume"
1801 );
1802 assert_relative_eq!(Tetrahedron::default().centroid(), Vec3::ZERO);
1803 }
1804
1805 #[test]
1806 fn extrusion_math() {
1807 let circle = Circle::new(0.75);
1808 let cylinder = Extrusion::new(circle, 2.5);
1809 assert_eq!(cylinder.area(), 15.315264, "incorrect surface area");
1810 assert_eq!(cylinder.volume(), 4.417865, "incorrect volume");
1811
1812 let annulus = crate::primitives::Annulus::new(0.25, 1.375);
1813 let tube = Extrusion::new(annulus, 0.333);
1814 assert_eq!(tube.area(), 14.886437, "incorrect surface area");
1815 assert_eq!(tube.volume(), 1.9124937, "incorrect volume");
1816
1817 let polygon = crate::primitives::RegularPolygon::new(3.8, 7);
1818 let regular_prism = Extrusion::new(polygon, 1.25);
1819 assert_eq!(regular_prism.area(), 107.8808, "incorrect surface area");
1820 assert_eq!(regular_prism.volume(), 49.392204, "incorrect volume");
1821 }
1822
1823 #[test]
1824 fn triangle_math() {
1825 let mut default_triangle = Triangle3d::default();
1827 let reverse_default_triangle = Triangle3d::new(
1828 Vec3::new(0.5, -0.5, 0.0),
1829 Vec3::new(-0.5, -0.5, 0.0),
1830 Vec3::new(0.0, 0.5, 0.0),
1831 );
1832 assert_eq!(default_triangle.area(), 0.5, "incorrect area");
1833 assert_relative_eq!(
1834 default_triangle.perimeter(),
1835 1.0 + 2.0 * ops::sqrt(1.25_f32),
1836 epsilon = 10e-9
1837 );
1838 assert_eq!(default_triangle.normal(), Ok(Dir3::Z), "incorrect normal");
1839 assert!(
1840 !default_triangle.is_degenerate(),
1841 "incorrect degenerate check"
1842 );
1843 assert_eq!(
1844 default_triangle.centroid(),
1845 Vec3::new(0.0, -0.16666667, 0.0),
1846 "incorrect centroid"
1847 );
1848 assert_eq!(
1849 default_triangle.largest_side(),
1850 (Vec3::new(0.0, 0.5, 0.0), Vec3::new(-0.5, -0.5, 0.0))
1851 );
1852 default_triangle.reverse();
1853 assert_eq!(
1854 default_triangle, reverse_default_triangle,
1855 "incorrect reverse"
1856 );
1857 assert_eq!(
1858 default_triangle.circumcenter(),
1859 Vec3::new(0.0, -0.125, 0.0),
1860 "incorrect circumcenter"
1861 );
1862
1863 let right_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::Y);
1865 let obtuse_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::X, Vec3::new(0.0, 0.1, 0.0));
1866 let acute_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::new(0.5, 5.0, 0.0));
1867
1868 assert_eq!(
1869 right_triangle.circumcenter(),
1870 Vec3::new(0.5, 0.5, 0.0),
1871 "incorrect circumcenter"
1872 );
1873 assert_eq!(
1874 obtuse_triangle.circumcenter(),
1875 Vec3::new(0.0, -4.95, 0.0),
1876 "incorrect circumcenter"
1877 );
1878 assert_eq!(
1879 acute_triangle.circumcenter(),
1880 Vec3::new(0.5, 2.475, 0.0),
1881 "incorrect circumcenter"
1882 );
1883
1884 assert!(acute_triangle.is_acute());
1885 assert!(!acute_triangle.is_obtuse());
1886 assert!(!obtuse_triangle.is_acute());
1887 assert!(obtuse_triangle.is_obtuse());
1888
1889 let [a, b, c] = [Vec3::ZERO, Vec3::new(1., 1., 0.5), Vec3::new(-3., 2.5, 1.)];
1891 let triangle = Triangle3d::new(a, b, c);
1892
1893 assert!(!triangle.is_degenerate(), "incorrectly found degenerate");
1894 assert_eq!(triangle.area(), 3.0233467, "incorrect area");
1895 assert_eq!(triangle.perimeter(), 9.832292, "incorrect perimeter");
1896 assert_eq!(
1897 triangle.circumcenter(),
1898 Vec3::new(-1., 1.75, 0.75),
1899 "incorrect circumcenter"
1900 );
1901 assert_eq!(
1902 triangle.normal(),
1903 Ok(Dir3::new_unchecked(Vec3::new(
1904 -0.04134491,
1905 -0.4134491,
1906 0.90958804
1907 ))),
1908 "incorrect normal"
1909 );
1910
1911 let zero_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::ZERO, Vec3::ZERO);
1913 assert!(
1914 zero_degenerate_triangle.is_degenerate(),
1915 "incorrect degenerate check"
1916 );
1917 assert_eq!(
1918 zero_degenerate_triangle.normal(),
1919 Err(InvalidDirectionError::Zero),
1920 "incorrect normal"
1921 );
1922 assert_eq!(
1923 zero_degenerate_triangle.largest_side(),
1924 (Vec3::ZERO, Vec3::ZERO),
1925 "incorrect largest side"
1926 );
1927
1928 let dup_degenerate_triangle = Triangle3d::new(Vec3::ZERO, Vec3::X, Vec3::X);
1929 assert!(
1930 dup_degenerate_triangle.is_degenerate(),
1931 "incorrect degenerate check"
1932 );
1933 assert_eq!(
1934 dup_degenerate_triangle.normal(),
1935 Err(InvalidDirectionError::Zero),
1936 "incorrect normal"
1937 );
1938 assert_eq!(
1939 dup_degenerate_triangle.largest_side(),
1940 (Vec3::ZERO, Vec3::X),
1941 "incorrect largest side"
1942 );
1943
1944 let collinear_degenerate_triangle = Triangle3d::new(Vec3::NEG_X, Vec3::ZERO, Vec3::X);
1945 assert!(
1946 collinear_degenerate_triangle.is_degenerate(),
1947 "incorrect degenerate check"
1948 );
1949 assert_eq!(
1950 collinear_degenerate_triangle.normal(),
1951 Err(InvalidDirectionError::Zero),
1952 "incorrect normal"
1953 );
1954 assert_eq!(
1955 collinear_degenerate_triangle.largest_side(),
1956 (Vec3::NEG_X, Vec3::X),
1957 "incorrect largest side"
1958 );
1959 }
1960}