bsp_tree/bsp/
tree.rs

1//! BSP tree container and construction.
2
3use nalgebra::Point3;
4
5use crate::{Classification, Cuttable, Polygon};
6
7use super::node::{faces_same_direction, BspNode};
8use super::selector::PlaneSelector;
9use super::visitor::BspVisitor;
10
11/// A Binary Space Partitioning tree for 3D polygons.
12///
13/// BSP trees recursively partition space using planes, enabling efficient
14/// spatial queries and ordered traversal. Each node contains polygons that
15/// are coplanar with its splitting plane, while non-coplanar polygons are
16/// stored in front or back subtrees.
17///
18/// # Construction
19///
20/// Trees are built from a collection of polygons using a [`PlaneSelector`]
21/// to choose splitting planes:
22///
23/// ```ignore
24/// use bsp_tree::{BspTree, Polygon, FirstPolygon};
25///
26/// let polygons: Vec<Polygon> = /* ... */;
27/// let tree = BspTree::build(polygons, &FirstPolygon);
28/// ```
29///
30/// # Traversal
31///
32/// The tree supports front-to-back and back-to-front traversal relative to
33/// a viewpoint, useful for painter's algorithm rendering:
34///
35/// ```ignore
36/// tree.traverse_front_to_back(eye_position, &mut visitor);
37/// ```
38///
39/// # Future: Insertion
40///
41/// The data structure supports insertion of polygons into an existing tree.
42/// This operation will be implemented in a future version.
43#[derive(Debug, Clone, Default)]
44pub struct BspTree {
45    root: Option<BspNode>,
46}
47
48impl BspTree {
49    /// Creates an empty BSP tree.
50    pub fn new() -> Self {
51        Self { root: None }
52    }
53
54    /// Builds a BSP tree from a collection of polygons.
55    ///
56    /// Uses the provided [`PlaneSelector`] to choose splitting planes during
57    /// construction. Polygons that span a splitting plane are automatically
58    /// split using the [`Cuttable`] trait.
59    ///
60    /// Returns an empty tree if the input is empty.
61    pub fn build<S: PlaneSelector>(polygons: Vec<Polygon>, selector: &S) -> Self {
62        Self {
63            root: build_node(polygons, selector),
64        }
65    }
66
67    /// Builds a BSP tree using the default plane selector ([`FirstPolygon`]).
68    pub fn from_polygons(polygons: Vec<Polygon>) -> Self {
69        use super::selector::FirstPolygon;
70        Self::build(polygons, &FirstPolygon)
71    }
72
73    /// Returns `true` if the tree contains no polygons.
74    #[inline]
75    pub fn is_empty(&self) -> bool {
76        self.root.is_none()
77    }
78
79    /// Returns a reference to the root node, if any.
80    #[inline]
81    pub fn root(&self) -> Option<&BspNode> {
82        self.root.as_ref()
83    }
84
85    /// Returns a mutable reference to the root node, if any.
86    ///
87    /// This is primarily for future insert operations.
88    #[inline]
89    pub fn root_mut(&mut self) -> Option<&mut BspNode> {
90        self.root.as_mut()
91    }
92
93    /// Returns the total number of polygons in the tree.
94    pub fn polygon_count(&self) -> usize {
95        self.root.as_ref().map_or(0, |n| n.polygon_count())
96    }
97
98    /// Returns the maximum depth of the tree (0 for empty tree).
99    pub fn depth(&self) -> usize {
100        self.root.as_ref().map_or(0, |n| n.depth())
101    }
102
103    /// Traverses the tree front-to-back relative to the given viewpoint.
104    ///
105    /// Useful for early-Z occlusion culling in modern renderers with depth
106    /// buffers, where drawing front objects first allows the GPU to reject
107    /// occluded fragments.
108    ///
109    /// The visitor's `visit` method is called for each group of coplanar
110    /// polygons, in front-to-back order (nearest first).
111    pub fn traverse_front_to_back<V: BspVisitor>(&self, eye: Point3<f32>, visitor: &mut V) {
112        if let Some(ref root) = self.root {
113            traverse_front_to_back_node(root, eye, visitor);
114        }
115    }
116
117    /// Traverses the tree back-to-front relative to the given viewpoint.
118    ///
119    /// This is the classic painter's algorithm ordering: far polygons are
120    /// visited first, then closer polygons, so they can be drawn on top.
121    /// Also useful for correct alpha blending of transparent surfaces.
122    pub fn traverse_back_to_front<V: BspVisitor>(&self, eye: Point3<f32>, visitor: &mut V) {
123        if let Some(ref root) = self.root {
124            traverse_back_to_front_node(root, eye, visitor);
125        }
126    }
127
128    /// Collects all polygons in the tree into a vector.
129    ///
130    /// The order of polygons is not guaranteed.
131    pub fn collect_polygons(&self) -> Vec<Polygon> {
132        let mut result = Vec::with_capacity(self.polygon_count());
133        collect_polygons_recursive(self.root.as_ref(), &mut result);
134        result
135    }
136
137    // TODO: Future insert operation
138    // pub fn insert(&mut self, polygon: Polygon) { ... }
139}
140
141/// Recursively builds a BSP node from a list of polygons.
142fn build_node<S: PlaneSelector>(mut polygons: Vec<Polygon>, selector: &S) -> Option<BspNode> {
143    if polygons.is_empty() {
144        return None;
145    }
146
147    // Select the splitting polygon and derive the plane
148    let splitter_idx = polygons
149        .iter()
150        .position(|p| Some(p) == selector.select(&polygons))?;
151
152    let splitter = polygons.swap_remove(splitter_idx);
153    let plane = splitter.plane();
154
155    // Initialize lists
156    let mut coplanar_front = Vec::new();
157    let mut coplanar_back = Vec::new();
158    let mut front_list = Vec::new();
159    let mut back_list = Vec::new();
160
161    // The splitter itself is coplanar - determine its facing
162    if faces_same_direction(&splitter, &plane) {
163        coplanar_front.push(splitter);
164    } else {
165        coplanar_back.push(splitter);
166    }
167
168    // Classify and partition remaining polygons
169    for polygon in polygons {
170        match polygon.classify(&plane) {
171            Classification::Front => {
172                front_list.push(polygon);
173            }
174            Classification::Back => {
175                back_list.push(polygon);
176            }
177            Classification::Coplanar => {
178                if faces_same_direction(&polygon, &plane) {
179                    coplanar_front.push(polygon);
180                } else {
181                    coplanar_back.push(polygon);
182                }
183            }
184            Classification::Spanning => {
185                let (front_part, back_part) = polygon.cut(&plane);
186                if let Some(f) = front_part {
187                    front_list.push(f);
188                }
189                if let Some(b) = back_part {
190                    back_list.push(b);
191                }
192            }
193        }
194    }
195
196    // Build the node with children
197    let mut node = BspNode::with_coplanar(plane, coplanar_front, coplanar_back);
198    node.set_front(build_node(front_list, selector));
199    node.set_back(build_node(back_list, selector));
200
201    Some(node)
202}
203
204/// Traverses a node subtree front-to-back.
205fn traverse_front_to_back_node<V: BspVisitor>(node: &BspNode, eye: Point3<f32>, visitor: &mut V) {
206    let side = node.plane().classify_point(eye);
207
208    // Collect coplanar polygons for visiting
209    let coplanar: Vec<Polygon> = node.all_coplanar().cloned().collect();
210
211    match side {
212        crate::PlaneSide::Front | crate::PlaneSide::OnPlane => {
213            // Eye is in front: front subtree is closer
214            if let Some(front) = node.front() {
215                traverse_front_to_back_node(front, eye, visitor);
216            }
217            if !coplanar.is_empty() {
218                visitor.visit(&coplanar);
219            }
220            if let Some(back) = node.back() {
221                traverse_front_to_back_node(back, eye, visitor);
222            }
223        }
224        crate::PlaneSide::Back => {
225            // Eye is behind: back subtree is closer
226            if let Some(back) = node.back() {
227                traverse_front_to_back_node(back, eye, visitor);
228            }
229            if !coplanar.is_empty() {
230                visitor.visit(&coplanar);
231            }
232            if let Some(front) = node.front() {
233                traverse_front_to_back_node(front, eye, visitor);
234            }
235        }
236    }
237}
238
239/// Traverses a node subtree back-to-front.
240fn traverse_back_to_front_node<V: BspVisitor>(node: &BspNode, eye: Point3<f32>, visitor: &mut V) {
241    let side = node.plane().classify_point(eye);
242
243    let coplanar: Vec<Polygon> = node.all_coplanar().cloned().collect();
244
245    match side {
246        crate::PlaneSide::Front | crate::PlaneSide::OnPlane => {
247            // Eye is in front: back subtree is farther
248            if let Some(back) = node.back() {
249                traverse_back_to_front_node(back, eye, visitor);
250            }
251            if !coplanar.is_empty() {
252                visitor.visit(&coplanar);
253            }
254            if let Some(front) = node.front() {
255                traverse_back_to_front_node(front, eye, visitor);
256            }
257        }
258        crate::PlaneSide::Back => {
259            // Eye is behind: front subtree is farther
260            if let Some(front) = node.front() {
261                traverse_back_to_front_node(front, eye, visitor);
262            }
263            if !coplanar.is_empty() {
264                visitor.visit(&coplanar);
265            }
266            if let Some(back) = node.back() {
267                traverse_back_to_front_node(back, eye, visitor);
268            }
269        }
270    }
271}
272
273/// Recursively collects all polygons from a node subtree.
274fn collect_polygons_recursive(node: Option<&BspNode>, result: &mut Vec<Polygon>) {
275    if let Some(n) = node {
276        result.extend(n.all_coplanar().cloned());
277        collect_polygons_recursive(n.front(), result);
278        collect_polygons_recursive(n.back(), result);
279    }
280}
281
282#[cfg(test)]
283mod tests {
284    use super::*;
285    use crate::bsp::visitor::CollectingVisitor;
286    use nalgebra::Point3;
287
288    fn make_triangle(a: [f32; 3], b: [f32; 3], c: [f32; 3]) -> Polygon {
289        Polygon::new(vec![
290            Point3::new(a[0], a[1], a[2]),
291            Point3::new(b[0], b[1], b[2]),
292            Point3::new(c[0], c[1], c[2]),
293        ])
294    }
295
296    #[test]
297    fn empty_tree() {
298        let tree = BspTree::new();
299        assert!(tree.is_empty());
300        assert_eq!(tree.polygon_count(), 0);
301        assert_eq!(tree.depth(), 0);
302    }
303
304    #[test]
305    fn build_empty() {
306        let tree = BspTree::from_polygons(vec![]);
307        assert!(tree.is_empty());
308    }
309
310    #[test]
311    fn build_single_polygon() {
312        let poly = make_triangle([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
313        let tree = BspTree::from_polygons(vec![poly]);
314
315        assert!(!tree.is_empty());
316        assert_eq!(tree.polygon_count(), 1);
317        assert_eq!(tree.depth(), 1);
318    }
319
320    #[test]
321    fn build_two_parallel_polygons() {
322        // Two triangles on parallel planes (not coplanar)
323        let poly1 = make_triangle([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
324        let poly2 = make_triangle([0.0, 0.0, 1.0], [1.0, 0.0, 1.0], [0.0, 1.0, 1.0]);
325
326        let tree = BspTree::from_polygons(vec![poly1, poly2]);
327
328        assert_eq!(tree.polygon_count(), 2);
329        // One should be in front or back of the other
330        assert!(tree.depth() >= 1);
331    }
332
333    #[test]
334    fn build_coplanar_same_facing() {
335        // Two triangles on the same plane, same winding
336        let poly1 = make_triangle([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
337        let poly2 = make_triangle([1.0, 0.0, 0.0], [2.0, 0.0, 0.0], [1.0, 1.0, 0.0]);
338
339        let tree = BspTree::from_polygons(vec![poly1, poly2]);
340
341        assert_eq!(tree.polygon_count(), 2);
342        // Both coplanar, should be in same node
343        assert_eq!(tree.depth(), 1);
344
345        let root = tree.root().unwrap();
346        assert_eq!(root.coplanar_count(), 2);
347    }
348
349    #[test]
350    fn build_spanning_polygon_gets_split() {
351        // First polygon on Y=0 plane
352        let splitter = make_triangle([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]);
353
354        // Second polygon spans the Y=0 plane
355        let spanning = make_triangle([-0.5, -1.0, 0.5], [0.5, 1.0, 0.5], [0.5, -1.0, 0.5]);
356
357        let tree = BspTree::from_polygons(vec![splitter, spanning]);
358
359        // Original was 2 polygons, but spanning got split into 2
360        // So we should have 3 total
361        assert_eq!(tree.polygon_count(), 3);
362    }
363
364    #[test]
365    fn traverse_front_to_back_single() {
366        let poly = make_triangle([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
367        let tree = BspTree::from_polygons(vec![poly.clone()]);
368
369        let mut visitor = CollectingVisitor::new();
370        tree.traverse_front_to_back(Point3::new(0.0, 0.0, 10.0), &mut visitor);
371
372        assert_eq!(visitor.polygons().len(), 1);
373    }
374
375    #[test]
376    fn traverse_front_to_back_ordering() {
377        // Create two polygons at different Z depths
378        // poly_near at z=1, poly_far at z=-1
379        let poly_near = make_triangle([0.0, 0.0, 1.0], [1.0, 0.0, 1.0], [0.0, 1.0, 1.0]);
380        let poly_far = make_triangle([0.0, 0.0, -1.0], [1.0, 0.0, -1.0], [0.0, 1.0, -1.0]);
381
382        let tree = BspTree::from_polygons(vec![poly_far.clone(), poly_near.clone()]);
383
384        // Eye at z=10 looking toward origin
385        let mut visitor = CollectingVisitor::new();
386        tree.traverse_front_to_back(Point3::new(0.5, 0.5, 10.0), &mut visitor);
387
388        let collected = visitor.into_polygons();
389        assert_eq!(collected.len(), 2);
390
391        // Front-to-back from z=10: poly_near (z=1) should come first
392        // (it's in front from the eye's perspective)
393        let first_z = collected[0].centroid().z;
394        let second_z = collected[1].centroid().z;
395        assert!(
396            first_z > second_z,
397            "Expected front-to-back order: first_z={} should be > second_z={}",
398            first_z,
399            second_z
400        );
401    }
402
403    #[test]
404    fn traverse_back_to_front_ordering() {
405        let poly_near = make_triangle([0.0, 0.0, 1.0], [1.0, 0.0, 1.0], [0.0, 1.0, 1.0]);
406        let poly_far = make_triangle([0.0, 0.0, -1.0], [1.0, 0.0, -1.0], [0.0, 1.0, -1.0]);
407
408        let tree = BspTree::from_polygons(vec![poly_far.clone(), poly_near.clone()]);
409
410        let mut visitor = CollectingVisitor::new();
411        tree.traverse_back_to_front(Point3::new(0.5, 0.5, 10.0), &mut visitor);
412
413        let collected = visitor.into_polygons();
414        assert_eq!(collected.len(), 2);
415
416        // Back-to-front: poly_far (z=-1) should come first
417        let first_z = collected[0].centroid().z;
418        let second_z = collected[1].centroid().z;
419        assert!(
420            first_z < second_z,
421            "Expected back-to-front order: first_z={} should be < second_z={}",
422            first_z,
423            second_z
424        );
425    }
426
427    #[test]
428    fn collect_polygons() {
429        let poly1 = make_triangle([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
430        let poly2 = make_triangle([0.0, 0.0, 1.0], [1.0, 0.0, 1.0], [0.0, 1.0, 1.0]);
431        let poly3 = make_triangle([0.0, 0.0, 2.0], [1.0, 0.0, 2.0], [0.0, 1.0, 2.0]);
432
433        let tree = BspTree::from_polygons(vec![poly1, poly2, poly3]);
434        let collected = tree.collect_polygons();
435
436        assert_eq!(collected.len(), 3);
437    }
438}