Skip to main content

rscad_csg_bsp/
bsp.rs

1use crate::*;
2pub struct BspNode<T: Number> {
3    plane: Option<Plane<T, 3>>,
4    front: Option<Box<BspNode<T>>>,
5    back: Option<Box<BspNode<T>>>,
6    polygons: Vec<Polygon<T, 3>>,
7}
8
9impl<T: Number> Default for BspNode<T> {
10    fn default() -> Self {
11        Self {
12            plane: None,
13            front: None,
14            back: None,
15            polygons: Vec::new(),
16        }
17    }
18}
19
20impl<T: Number> BspNode<T> {
21    fn pick_splitting_plane(polygons: &[Polygon<T, 3>]) -> (usize, Vec<PolygonClassification>) {
22        let len = polygons.len();
23        if len <= 1 {
24            let classes = polygons
25                .iter()
26                .map(|_| PolygonClassification::Coplanar)
27                .collect();
28            return (0, classes);
29        }
30
31        // Evaluate candidate splitting planes. For small sets test every
32        // polygon; for larger sets, sample sqrt(n) evenly-spaced candidates
33        // (clamped to 16..64). Each candidate is scored against ALL polygons
34        // — the O(candidates × n) cost is negligible compared to the downstream
35        // polygon splitting, and accuracy here directly reduces cascading splits.
36        let num_candidates = if len <= 48 {
37            len
38        } else {
39            ((len as f64).sqrt().ceil() as usize).clamp(16, 64)
40        };
41        let cand_step = if num_candidates >= len {
42            1
43        } else {
44            len / num_candidates
45        };
46
47        let mut best_idx = 0;
48        let mut best_score = i64::MAX;
49        // Reuse a single buffer for classifications; swap into best on improvement.
50        let mut classes = Vec::with_capacity(len);
51        let mut best_classes = Vec::with_capacity(len);
52
53        for c in 0..num_candidates {
54            let idx = c * cand_step;
55            let plane = polygons[idx].plane();
56            let mut front = 0i64;
57            let mut back = 0i64;
58            let mut spanning = 0i64;
59            let mut coplanar = 0i64;
60
61            classes.clear();
62            classes.extend(polygons.iter().map(|p| {
63                let cl = plane.classify_polygon(p);
64                match cl {
65                    PolygonClassification::Front => front += 1,
66                    PolygonClassification::Back => back += 1,
67                    PolygonClassification::Spanning => spanning += 1,
68                    PolygonClassification::Coplanar => coplanar += 1,
69                }
70                cl
71            }));
72
73            // Each spanning polygon becomes two fragments that cascade through
74            // the tree — penalize heavily (×16). Lightly penalize imbalance to
75            // prefer shallower trees. Reward coplanar polygons: they stay at
76            // this node and shrink both subtrees.
77            let imbalance = if front > back {
78                front - back
79            } else {
80                back - front
81            };
82            let score = spanning * 16 + imbalance - coplanar * 2;
83
84            if score < best_score {
85                best_score = score;
86                best_idx = idx;
87                core::mem::swap(&mut classes, &mut best_classes);
88            }
89        }
90
91        (best_idx, best_classes)
92    }
93
94    pub fn build(polygons: Vec<Polygon<T, 3>>) -> Self {
95        if polygons.is_empty() {
96            return Self::default();
97        }
98
99        let (split_idx, classes) = Self::pick_splitting_plane(&polygons);
100        let plane = polygons[split_idx].plane();
101
102        let mut node = Self {
103            plane: Some(plane),
104            front: None,
105            back: None,
106            polygons: Vec::new(),
107        };
108
109        let mut front_polygons = Vec::new();
110        let mut back_polygons = Vec::new();
111
112        for (polygon, class) in polygons.into_iter().zip(classes) {
113            match class {
114                PolygonClassification::Coplanar => node.polygons.push(polygon),
115                PolygonClassification::Front => front_polygons.push(polygon),
116                PolygonClassification::Back => back_polygons.push(polygon),
117                PolygonClassification::Spanning => {
118                    let (front, back) = plane.split_polygon(polygon);
119                    front_polygons.push(front);
120                    back_polygons.push(back);
121                }
122            }
123        }
124
125        if !front_polygons.is_empty() {
126            node.front = Some(Box::new(Self::build(front_polygons)));
127        }
128        if !back_polygons.is_empty() {
129            node.back = Some(Box::new(Self::build(back_polygons)));
130        }
131
132        node
133    }
134
135    pub fn clip_polygons(&self, polygons: Vec<Polygon<T, 3>>) -> Vec<Polygon<T, 3>> {
136        let mut out = Vec::new();
137        self.clip_polygons_into(polygons, &mut out);
138        out
139    }
140
141    fn clip_polygons_into(&self, polygons: Vec<Polygon<T, 3>>, out: &mut Vec<Polygon<T, 3>>) {
142        let Some(plane) = self.plane else {
143            out.extend(polygons);
144            return;
145        };
146
147        let mut front = Vec::new();
148        let mut back = Vec::new();
149
150        for polygon in polygons {
151            match plane.classify_polygon(&polygon) {
152                PolygonClassification::Front => front.push(polygon),
153                PolygonClassification::Back => back.push(polygon),
154                PolygonClassification::Coplanar => {
155                    let dot = plane.normal().dot(&polygon.plane().normal());
156                    if dot > T::zero() {
157                        front.push(polygon);
158                    } else {
159                        back.push(polygon);
160                    }
161                }
162                PolygonClassification::Spanning => {
163                    let (f, b) = plane.split_polygon(polygon);
164                    front.push(f);
165                    back.push(b);
166                }
167            }
168        }
169
170        match &self.front {
171            Some(f) => f.clip_polygons_into(front, out),
172            None => out.extend(front),
173        };
174
175        if let Some(b) = &self.back {
176            b.clip_polygons_into(back, out);
177        }
178        // else: back polygons clipped away
179    }
180
181    pub fn clip_to(&mut self, other: &BspNode<T>) {
182        let taken = core::mem::take(&mut self.polygons);
183        self.polygons = other.clip_polygons(taken);
184        if let Some(ref mut front) = self.front {
185            front.clip_to(other);
186        }
187        if let Some(ref mut back) = self.back {
188            back.clip_to(other);
189        }
190    }
191
192    pub fn invert(&mut self) {
193        for polygon in &mut self.polygons {
194            polygon.flip_in_place();
195        }
196        if let Some(ref mut plane) = self.plane {
197            *plane = plane.flip();
198        }
199        core::mem::swap(&mut self.front, &mut self.back);
200        if let Some(ref mut front) = self.front {
201            front.invert();
202        }
203        if let Some(ref mut back) = self.back {
204            back.invert();
205        }
206    }
207
208    pub fn polygons(&self) -> Vec<Polygon<T, 3>> {
209        let mut result = Vec::new();
210        self.collect_polygons_into(&mut result);
211        result
212    }
213
214    fn collect_polygons_into(&self, out: &mut Vec<Polygon<T, 3>>) {
215        out.extend(self.polygons.iter().cloned());
216        if let Some(ref front) = self.front {
217            front.collect_polygons_into(out);
218        }
219        if let Some(ref back) = self.back {
220            back.collect_polygons_into(out);
221        }
222    }
223
224    pub fn build_into(&mut self, polygons: Vec<Polygon<T, 3>>) {
225        let mut front_polygons = Vec::new();
226        let mut back_polygons = Vec::new();
227
228        for polygon in polygons {
229            let Some(plane) = self.plane else {
230                self.plane = Some(polygon.plane());
231                self.polygons.push(polygon);
232                continue;
233            };
234            match plane.classify_polygon(&polygon) {
235                PolygonClassification::Coplanar => self.polygons.push(polygon),
236                PolygonClassification::Front => front_polygons.push(polygon),
237                PolygonClassification::Back => back_polygons.push(polygon),
238                PolygonClassification::Spanning => {
239                    let (front, back) = plane.split_polygon(polygon);
240                    front_polygons.push(front);
241                    back_polygons.push(back);
242                }
243            }
244        }
245
246        if !front_polygons.is_empty() {
247            match &mut self.front {
248                Some(f) => f.build_into(front_polygons),
249                None => self.front = Some(Box::new(Self::build(front_polygons))),
250            }
251        }
252        if !back_polygons.is_empty() {
253            match &mut self.back {
254                Some(b) => b.build_into(back_polygons),
255                None => self.back = Some(Box::new(Self::build(back_polygons))),
256            }
257        }
258    }
259}