Skip to main content

oxihuman_core/
bsp_tree_2d.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4//! Binary Space Partitioning (BSP) tree for 2D polygon splitting.
5
6#![allow(dead_code)]
7
8/// A 2D line used as a BSP splitting plane: ax + by + c = 0.
9#[allow(dead_code)]
10#[derive(Debug, Clone, Copy)]
11pub struct BspLine {
12    pub a: f32,
13    pub b: f32,
14    pub c: f32,
15}
16
17/// Evaluate which side of the line a point is on.
18/// Returns positive if in front, negative if behind, ~0 if on line.
19#[allow(dead_code)]
20pub fn bsp_line_side(line: &BspLine, p: [f32; 2]) -> f32 {
21    line.a * p[0] + line.b * p[1] + line.c
22}
23
24/// A polygon in the BSP tree.
25#[allow(dead_code)]
26#[derive(Debug, Clone)]
27pub struct BspPolygon {
28    pub vertices: Vec<[f32; 2]>,
29    pub label: String,
30}
31
32/// A node in the BSP tree.
33#[allow(dead_code)]
34#[derive(Debug, Clone)]
35pub enum BspNode {
36    Leaf(Vec<BspPolygon>),
37    Branch {
38        splitter: BspLine,
39        front: Box<BspNode>,
40        back: Box<BspNode>,
41    },
42}
43
44/// A 2D BSP tree.
45#[allow(dead_code)]
46#[derive(Debug, Clone, Default)]
47pub struct BspTree2D {
48    root: Option<Box<BspNode>>,
49}
50
51/// Create a new empty BSP tree.
52#[allow(dead_code)]
53pub fn new_bsp_tree() -> BspTree2D {
54    BspTree2D::default()
55}
56
57/// Split a polygon by a line into front and back parts.
58/// Returns (front_vertices, back_vertices).
59#[allow(dead_code)]
60pub fn bsp_split_polygon(poly: &[[f32; 2]], line: &BspLine) -> (Vec<[f32; 2]>, Vec<[f32; 2]>) {
61    let mut front = Vec::new();
62    let mut back = Vec::new();
63    let n = poly.len();
64    if n == 0 {
65        return (front, back);
66    }
67    for i in 0..n {
68        let curr = poly[i];
69        let next = poly[(i + 1) % n];
70        let d_curr = bsp_line_side(line, curr);
71        let d_next = bsp_line_side(line, next);
72        if d_curr >= 0.0 {
73            front.push(curr);
74        } else {
75            back.push(curr);
76        }
77        // Check for crossing
78        if (d_curr > 0.0 && d_next < 0.0) || (d_curr < 0.0 && d_next > 0.0) {
79            let t = d_curr / (d_curr - d_next);
80            let ix = curr[0] + t * (next[0] - curr[0]);
81            let iy = curr[1] + t * (next[1] - curr[1]);
82            front.push([ix, iy]);
83            back.push([ix, iy]);
84        }
85    }
86    (front, back)
87}
88
89/// Build a BSP tree from a list of polygons using the first polygon's edge as the splitter.
90#[allow(dead_code)]
91pub fn bsp_build(polygons: Vec<BspPolygon>) -> BspNode {
92    if polygons.is_empty() {
93        return BspNode::Leaf(Vec::new());
94    }
95    if polygons.len() == 1 {
96        return BspNode::Leaf(polygons);
97    }
98    // Use first polygon's first edge as splitter.
99    let splitter = {
100        let verts = &polygons[0].vertices;
101        if verts.len() < 2 {
102            return BspNode::Leaf(polygons);
103        }
104        let a = verts[0];
105        let b = verts[1];
106        let dx = b[0] - a[0];
107        let dy = b[1] - a[1];
108        let len = (dx * dx + dy * dy).sqrt().max(1e-10);
109        BspLine {
110            a: -dy / len,
111            b: dx / len,
112            c: (dy * a[0] - dx * a[1]) / len,
113        }
114    };
115
116    let mut front_polys = Vec::new();
117    let mut back_polys = Vec::new();
118    let input_count = polygons.len();
119
120    for poly in polygons {
121        let (f, b) = bsp_split_polygon(&poly.vertices, &splitter);
122        let f_ok = f.len() >= 3;
123        let b_ok = b.len() >= 3;
124        if f_ok {
125            front_polys.push(BspPolygon {
126                vertices: f,
127                label: poly.label.clone(),
128            });
129        }
130        if b_ok {
131            back_polys.push(BspPolygon {
132                vertices: b,
133                label: poly.label.clone(),
134            });
135        }
136        if !f_ok && !b_ok {
137            front_polys.push(poly);
138        }
139    }
140
141    // Guard against infinite recursion: if no actual splitting occurred,
142    // return a leaf with all polygons.
143    if front_polys.len() == input_count && back_polys.is_empty() {
144        return BspNode::Leaf(front_polys);
145    }
146    if back_polys.len() == input_count && front_polys.is_empty() {
147        return BspNode::Leaf(back_polys);
148    }
149
150    BspNode::Branch {
151        splitter,
152        front: Box::new(bsp_build(front_polys)),
153        back: Box::new(bsp_build(back_polys)),
154    }
155}
156
157/// Count the total number of polygons in a BSP tree.
158#[allow(dead_code)]
159pub fn bsp_polygon_count(node: &BspNode) -> usize {
160    match node {
161        BspNode::Leaf(polys) => polys.len(),
162        BspNode::Branch { front, back, .. } => bsp_polygon_count(front) + bsp_polygon_count(back),
163    }
164}
165
166/// Count the depth of the BSP tree.
167#[allow(dead_code)]
168pub fn bsp_depth(node: &BspNode) -> usize {
169    match node {
170        BspNode::Leaf(_) => 0,
171        BspNode::Branch { front, back, .. } => 1 + bsp_depth(front).max(bsp_depth(back)),
172    }
173}
174
175/// Collect all polygons from a BSP tree in front-to-back order.
176#[allow(dead_code)]
177pub fn bsp_collect_polygons(node: &BspNode) -> Vec<&BspPolygon> {
178    let mut result = Vec::new();
179    bsp_collect_recursive(node, &mut result);
180    result
181}
182
183fn bsp_collect_recursive<'a>(node: &'a BspNode, out: &mut Vec<&'a BspPolygon>) {
184    match node {
185        BspNode::Leaf(polys) => {
186            for p in polys {
187                out.push(p);
188            }
189        }
190        BspNode::Branch { front, back, .. } => {
191            bsp_collect_recursive(front, out);
192            bsp_collect_recursive(back, out);
193        }
194    }
195}
196
197/// Check if a BSP tree is a leaf.
198#[allow(dead_code)]
199pub fn bsp_is_leaf(node: &BspNode) -> bool {
200    matches!(node, BspNode::Leaf(_))
201}
202
203/// Set the BSP root.
204#[allow(dead_code)]
205pub fn bsp_set_root(tree: &mut BspTree2D, node: BspNode) {
206    tree.root = Some(Box::new(node));
207}
208
209/// Get BSP root reference.
210#[allow(dead_code)]
211pub fn bsp_get_root(tree: &BspTree2D) -> Option<&BspNode> {
212    tree.root.as_deref()
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218
219    fn make_quad(x: f32, y: f32, w: f32, h: f32, label: &str) -> BspPolygon {
220        BspPolygon {
221            vertices: vec![[x, y], [x + w, y], [x + w, y + h], [x, y + h]],
222            label: label.to_string(),
223        }
224    }
225
226    #[test]
227    fn test_new_tree_empty() {
228        let tree = new_bsp_tree();
229        assert!(bsp_get_root(&tree).is_none());
230    }
231
232    #[test]
233    fn test_build_single_polygon() {
234        let poly = make_quad(0.0, 0.0, 1.0, 1.0, "a");
235        let node = bsp_build(vec![poly]);
236        assert!(bsp_is_leaf(&node));
237    }
238
239    #[test]
240    fn test_build_two_polygons() {
241        let p1 = make_quad(-1.0, -1.0, 2.0, 2.0, "a");
242        let p2 = make_quad(-1.0, 1.0, 2.0, 2.0, "b");
243        let node = bsp_build(vec![p1, p2]);
244        // May be leaf or branch depending on splitter; just check it builds
245        let count = bsp_polygon_count(&node);
246        assert!(count >= 1);
247    }
248
249    #[test]
250    fn test_bsp_line_side() {
251        let line = BspLine {
252            a: 1.0,
253            b: 0.0,
254            c: 0.0,
255        };
256        assert!(bsp_line_side(&line, [1.0, 0.0]) > 0.0);
257        assert!(bsp_line_side(&line, [-1.0, 0.0]) < 0.0);
258    }
259
260    #[test]
261    fn test_split_polygon_horizontal() {
262        let line = BspLine {
263            a: 0.0,
264            b: 1.0,
265            c: -0.5,
266        };
267        let poly = vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
268        let (f, b) = bsp_split_polygon(&poly, &line);
269        assert!(!f.is_empty());
270        assert!(!b.is_empty());
271    }
272
273    #[test]
274    fn test_collect_polygons() {
275        let p1 = make_quad(0.0, 0.0, 1.0, 1.0, "a");
276        let p2 = make_quad(2.0, 0.0, 1.0, 1.0, "b");
277        let node = bsp_build(vec![p1, p2]);
278        let collected = bsp_collect_polygons(&node);
279        assert!(!collected.is_empty());
280    }
281
282    #[test]
283    fn test_bsp_depth_single() {
284        let poly = make_quad(0.0, 0.0, 1.0, 1.0, "a");
285        let node = bsp_build(vec![poly]);
286        assert_eq!(bsp_depth(&node), 0);
287    }
288
289    #[test]
290    fn test_bsp_set_and_get_root() {
291        let mut tree = new_bsp_tree();
292        let poly = make_quad(0.0, 0.0, 1.0, 1.0, "a");
293        let node = bsp_build(vec![poly]);
294        bsp_set_root(&mut tree, node);
295        assert!(bsp_get_root(&tree).is_some());
296    }
297
298    #[test]
299    fn test_empty_build() {
300        let node = bsp_build(vec![]);
301        assert!(bsp_is_leaf(&node));
302        assert_eq!(bsp_polygon_count(&node), 0);
303    }
304}