1use nalgebra::{Point3, Vector3};
4
5use crate::{Classification, Plane3D, PlaneSide, Rectangle, Triangle};
6
7#[derive(Debug, Clone, PartialEq)]
12pub struct Polygon {
13 vertices: Vec<Point3<f32>>,
14}
15
16impl Polygon {
17 pub fn new(vertices: Vec<Point3<f32>>) -> Self {
23 debug_assert!(
24 vertices.len() >= 3,
25 "Polygon must have at least 3 vertices"
26 );
27 debug_assert!(
28 Self::are_coplanar(&vertices),
29 "Polygon vertices must be coplanar"
30 );
31 Self { vertices }
32 }
33
34 fn are_coplanar(vertices: &[Point3<f32>]) -> bool {
36 if vertices.len() <= 3 {
37 return true;
38 }
39
40 let plane = Plane3D::from_three_points(vertices[0], vertices[1], vertices[2]);
41 vertices[3..]
42 .iter()
43 .all(|v| plane.classify_point(*v) == PlaneSide::OnPlane)
44 }
45
46 #[inline]
48 pub fn vertices(&self) -> &[Point3<f32>] {
49 &self.vertices
50 }
51
52 #[inline]
54 pub fn len(&self) -> usize {
55 self.vertices.len()
56 }
57
58 #[inline]
60 pub fn is_empty(&self) -> bool {
61 self.vertices.is_empty()
62 }
63
64 pub fn normal(&self) -> Vector3<f32> {
69 let a = &self.vertices[0];
70 let b = &self.vertices[1];
71 let c = &self.vertices[2];
72 let ab = b - a;
73 let ac = c - a;
74 ab.cross(&ac)
75 }
76
77 pub fn unit_normal(&self) -> Option<Vector3<f32>> {
81 let n = self.normal();
82 let len = n.norm();
83 if len > f32::EPSILON {
84 Some(n / len)
85 } else {
86 None
87 }
88 }
89
90 pub fn plane(&self) -> Plane3D {
95 Plane3D::from_three_points(self.vertices[0], self.vertices[1], self.vertices[2])
96 }
97
98 pub fn centroid(&self) -> Point3<f32> {
100 let sum: Vector3<f32> = self.vertices.iter().map(|p| p.coords).sum();
101 Point3::from(sum / self.vertices.len() as f32)
102 }
103
104 pub fn classify(&self, plane: &Plane3D) -> Classification {
112 let mut front = 0;
113 let mut back = 0;
114 let mut on_plane = 0;
115
116 for vertex in &self.vertices {
117 match plane.classify_point(*vertex) {
118 PlaneSide::Front => front += 1,
119 PlaneSide::Back => back += 1,
120 PlaneSide::OnPlane => on_plane += 1,
121 }
122 }
123
124 if on_plane == self.vertices.len() {
125 Classification::Coplanar
126 } else if back == 0 {
127 Classification::Front
128 } else if front == 0 {
129 Classification::Back
130 } else {
131 Classification::Spanning
132 }
133 }
134}
135
136impl From<Triangle> for Polygon {
137 fn from(triangle: Triangle) -> Self {
138 Self {
139 vertices: triangle.vertices().to_vec(),
140 }
141 }
142}
143
144impl From<&Triangle> for Polygon {
145 fn from(triangle: &Triangle) -> Self {
146 Self {
147 vertices: triangle.vertices().to_vec(),
148 }
149 }
150}
151
152impl From<Rectangle> for Polygon {
153 fn from(rectangle: Rectangle) -> Self {
154 Self {
155 vertices: rectangle.vertices().to_vec(),
156 }
157 }
158}
159
160impl From<&Rectangle> for Polygon {
161 fn from(rectangle: &Rectangle) -> Self {
162 Self {
163 vertices: rectangle.vertices().to_vec(),
164 }
165 }
166}
167
168impl From<Polygon> for Plane3D {
169 fn from(polygon: Polygon) -> Self {
170 polygon.plane()
171 }
172}
173
174impl From<&Polygon> for Plane3D {
175 fn from(polygon: &Polygon) -> Self {
176 polygon.plane()
177 }
178}