bsp_tree/
rectangle.rs

1//! Rectangle (quad) representation for BSP trees.
2
3use nalgebra::{Point3, Vector3};
4
5use crate::{Classification, Plane3D, PlaneSide};
6
7/// A rectangle (quad) in 3D space, defined by a corner and two edge vectors.
8///
9/// The four vertices are:
10/// - `origin`
11/// - `origin + u`
12/// - `origin + u + v`
13/// - `origin + v`
14#[derive(Debug, Clone, PartialEq)]
15pub struct Rectangle {
16    origin: Point3<f32>,
17    u: Vector3<f32>,
18    v: Vector3<f32>,
19}
20
21impl Rectangle {
22    /// Creates a new rectangle from an origin corner and two edge vectors.
23    ///
24    /// The vertices will be: origin, origin+u, origin+u+v, origin+v (counter-clockwise).
25    pub fn new(origin: Point3<f32>, u: Vector3<f32>, v: Vector3<f32>) -> Self {
26        Self { origin, u, v }
27    }
28
29    /// Creates a rectangle from four corner points.
30    ///
31    /// The winding order should be: a -> b -> c -> d (counter-clockwise).
32    ///
33    /// Internally computes u = b - a and v = d - a.
34    ///
35    /// # Panics (debug builds only)
36    /// Panics if the points are not coplanar.
37    pub fn from_corners(
38        a: Point3<f32>,
39        b: Point3<f32>,
40        c: Point3<f32>,
41        d: Point3<f32>,
42    ) -> Self {
43        debug_assert!(
44            {
45                let plane = Plane3D::from_three_points(a, b, d);
46                plane.classify_point(c) == PlaneSide::OnPlane
47            },
48            "Rectangle corners must be coplanar"
49        );
50        let u = b - a;
51        let v = d - a;
52        Self { origin: a, u, v }
53    }
54
55    /// Returns the origin corner of the rectangle.
56    #[inline]
57    pub fn origin(&self) -> Point3<f32> {
58        self.origin
59    }
60
61    /// Returns the first edge vector.
62    #[inline]
63    pub fn u(&self) -> Vector3<f32> {
64        self.u
65    }
66
67    /// Returns the second edge vector.
68    #[inline]
69    pub fn v(&self) -> Vector3<f32> {
70        self.v
71    }
72
73    /// Returns the four vertices of the rectangle.
74    ///
75    /// Order: origin, origin+u, origin+u+v, origin+v (counter-clockwise).
76    pub fn vertices(&self) -> [Point3<f32>; 4] {
77        [
78            self.origin,
79            self.origin + self.u,
80            self.origin + self.u + self.v,
81            self.origin + self.v,
82        ]
83    }
84
85    /// Computes the (unnormalized) normal vector of the rectangle.
86    ///
87    /// The direction follows the right-hand rule: u × v.
88    pub fn normal(&self) -> Vector3<f32> {
89        self.u.cross(&self.v)
90    }
91
92    /// Computes the unit normal vector of the rectangle.
93    ///
94    /// Returns `None` if the rectangle is degenerate (zero area).
95    pub fn unit_normal(&self) -> Option<Vector3<f32>> {
96        let n = self.normal();
97        let len = n.norm();
98        if len > f32::EPSILON {
99            Some(n / len)
100        } else {
101            None
102        }
103    }
104
105    /// Returns the plane that this rectangle lies on.
106    ///
107    /// # Panics
108    /// Panics if the rectangle is degenerate (u and v are parallel).
109    pub fn plane(&self) -> Plane3D {
110        Plane3D::from_point_and_normal(self.origin, self.normal())
111    }
112
113    /// Computes the centroid (center) of the rectangle.
114    pub fn centroid(&self) -> Point3<f32> {
115        self.origin + (self.u + self.v) * 0.5
116    }
117
118    /// Computes the area of the rectangle.
119    pub fn area(&self) -> f32 {
120        self.normal().norm()
121    }
122
123    /// Classifies this rectangle relative to a plane.
124    ///
125    /// Returns:
126    /// - `Front` if all vertices are in front of the plane
127    /// - `Back` if all vertices are behind the plane
128    /// - `Coplanar` if all vertices lie on the plane
129    /// - `Spanning` if vertices are on both sides
130    pub fn classify(&self, plane: &Plane3D) -> Classification {
131        let mut front = 0;
132        let mut back = 0;
133        let mut on_plane = 0;
134
135        for vertex in self.vertices() {
136            match plane.classify_point(vertex) {
137                PlaneSide::Front => front += 1,
138                PlaneSide::Back => back += 1,
139                PlaneSide::OnPlane => on_plane += 1,
140            }
141        }
142
143        if on_plane == 4 {
144            Classification::Coplanar
145        } else if back == 0 {
146            Classification::Front
147        } else if front == 0 {
148            Classification::Back
149        } else {
150            Classification::Spanning
151        }
152    }
153}
154
155impl From<Rectangle> for Plane3D {
156    fn from(rectangle: Rectangle) -> Self {
157        rectangle.plane()
158    }
159}
160
161impl From<&Rectangle> for Plane3D {
162    fn from(rectangle: &Rectangle) -> Self {
163        rectangle.plane()
164    }
165}