Skip to main content

bsp_tree/
triangle.rs

1//! Triangle representation for BSP trees.
2
3use nalgebra::{Point3, Vector3};
4
5use crate::{Classification, Plane3D, PlaneSide};
6
7/// A triangle in 3D space, defined by three vertices.
8#[derive(Debug, Clone, PartialEq)]
9pub struct Triangle {
10    vertices: [Point3<f32>; 3],
11}
12
13impl Triangle {
14    /// Creates a new triangle from three points.
15    ///
16    /// The winding order determines the normal direction via the right-hand rule:
17    /// normal = (b - a) × (c - a)
18    pub fn new(a: Point3<f32>, b: Point3<f32>, c: Point3<f32>) -> Self {
19        Self {
20            vertices: [a, b, c],
21        }
22    }
23
24    /// Returns the three vertices of the triangle.
25    #[inline]
26    pub fn vertices(&self) -> &[Point3<f32>; 3] {
27        &self.vertices
28    }
29
30    /// Computes the (unnormalized) normal vector of the triangle.
31    ///
32    /// The direction follows the right-hand rule based on vertex winding.
33    pub fn normal(&self) -> Vector3<f32> {
34        let [a, b, c] = &self.vertices;
35        let ab = b - a;
36        let ac = c - a;
37        ab.cross(&ac)
38    }
39
40    /// Computes the unit normal vector of the triangle.
41    ///
42    /// Returns `None` if the triangle is degenerate (zero area).
43    pub fn unit_normal(&self) -> Option<Vector3<f32>> {
44        let n = self.normal();
45        let len = n.norm();
46        if len > f32::EPSILON {
47            Some(n / len)
48        } else {
49            None
50        }
51    }
52
53    /// Returns the plane that this triangle lies on.
54    ///
55    /// # Panics
56    /// Panics if the triangle is degenerate (vertices are collinear).
57    pub fn plane(&self) -> Plane3D {
58        let [a, b, c] = &self.vertices;
59        Plane3D::from_three_points(*a, *b, *c)
60    }
61
62    /// Computes the centroid (center of mass) of the triangle.
63    pub fn centroid(&self) -> Point3<f32> {
64        let [a, b, c] = &self.vertices;
65        Point3::from((a.coords + b.coords + c.coords) / 3.0)
66    }
67
68    /// Classifies this triangle relative to a plane.
69    ///
70    /// Returns:
71    /// - `Front` if all vertices are in front of the plane
72    /// - `Back` if all vertices are behind the plane
73    /// - `Coplanar` if all vertices lie on the plane
74    /// - `Spanning` if vertices are on both sides
75    pub fn classify(&self, plane: &Plane3D) -> Classification {
76        let mut front = 0;
77        let mut back = 0;
78        let mut on_plane = 0;
79
80        for vertex in &self.vertices {
81            match plane.classify_point(*vertex) {
82                PlaneSide::Front => front += 1,
83                PlaneSide::Back => back += 1,
84                PlaneSide::OnPlane => on_plane += 1,
85            }
86        }
87
88        if on_plane == 3 {
89            Classification::Coplanar
90        } else if back == 0 {
91            Classification::Front
92        } else if front == 0 {
93            Classification::Back
94        } else {
95            Classification::Spanning
96        }
97    }
98}
99
100impl From<Triangle> for Plane3D {
101    fn from(triangle: Triangle) -> Self {
102        triangle.plane()
103    }
104}
105
106impl From<&Triangle> for Plane3D {
107    fn from(triangle: &Triangle) -> Self {
108        triangle.plane()
109    }
110}