Skip to main content

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