Skip to main content

brep_kernel/geometry/
spatial.rs

1//! Shared axis-aligned bounding boxes and a static bounding-volume
2//! hierarchy over per-face boxes.
3//!
4//! Rational surfaces and curves with positive weights lie inside the convex
5//! hull of their control points, so a control-point box is a conservative
6//! bound for every point on the exact geometry. Queries expand boxes by the
7//! caller's tolerance, which keeps BVH pruning conservative for the same
8//! tolerance the narrow phase uses.
9
10use crate::surface::NurbsSurface;
11use crate::Vec3;
12
13#[derive(Clone, Copy, Debug)]
14pub struct Aabb {
15    pub minimum: Vec3,
16    pub maximum: Vec3,
17}
18
19fn axis(point: Vec3, index: usize) -> f64 {
20    match index {
21        0 => point.x,
22        1 => point.y,
23        _ => point.z,
24    }
25}
26
27impl Aabb {
28    pub(crate) fn separation(self, other: Self) -> f64 {
29        let axis_gap = |a0: f64, a1: f64, b0: f64, b1: f64| {
30            if a1 < b0 {
31                b0 - a1
32            } else if b1 < a0 {
33                a0 - b1
34            } else {
35                0.0
36            }
37        };
38        Vec3::new(
39            axis_gap(self.minimum.x, self.maximum.x, other.minimum.x, other.maximum.x),
40            axis_gap(self.minimum.y, self.maximum.y, other.minimum.y, other.maximum.y),
41            axis_gap(self.minimum.z, self.maximum.z, other.minimum.z, other.maximum.z),
42        )
43        .length()
44    }
45
46    pub(crate) fn pair_scale(self, other: Self) -> f64 {
47        self.maximum
48            .sub(self.minimum)
49            .length()
50            .max(other.maximum.sub(other.minimum).length())
51            .max(1.0)
52    }
53
54
55    pub fn from_curve_controls(curve: &crate::NurbsCurve) -> Result<Self, String> {
56        let mut bounds = Self::empty();
57        for point in &curve.control_points {
58            bounds.include_point(point.point()?);
59        }
60        Ok(bounds)
61    }
62
63    /// Overlap with tolerance added to the upper operands (legacy intersector order).
64    pub(crate) fn intersects_upper(self, other: Self, tolerance: f64) -> bool {
65        self.minimum.x <= other.maximum.x + tolerance
66            && self.maximum.x + tolerance >= other.minimum.x
67            && self.minimum.y <= other.maximum.y + tolerance
68            && self.maximum.y + tolerance >= other.minimum.y
69            && self.minimum.z <= other.maximum.z + tolerance
70            && self.maximum.z + tolerance >= other.minimum.z
71    }
72
73    pub(crate) fn intersects_exact(self, other: Self) -> bool {
74        self.minimum.x <= other.maximum.x
75            && self.maximum.x >= other.minimum.x
76            && self.minimum.y <= other.maximum.y
77            && self.maximum.y >= other.minimum.y
78            && self.minimum.z <= other.maximum.z
79            && self.maximum.z >= other.minimum.z
80    }
81
82    pub(crate) fn from_nonempty_points(points: &[Vec3]) -> Option<Self> {
83        let first = *points.first()?;
84        let mut bounds = Self { minimum: first, maximum: first };
85        for &point in &points[1..] {
86            bounds.include_point(point);
87        }
88        Some(bounds)
89    }
90
91    pub fn empty() -> Self {
92        Self {
93            minimum: Vec3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY),
94            maximum: Vec3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY),
95        }
96    }
97
98    pub(crate) fn from_points(points: impl IntoIterator<Item = Vec3>) -> Self {
99        let mut bounds = Self::empty();
100        for point in points {
101            bounds.include_point(point);
102        }
103        bounds
104    }
105
106    pub fn include_point(&mut self, point: Vec3) {
107        self.minimum.x = self.minimum.x.min(point.x);
108        self.minimum.y = self.minimum.y.min(point.y);
109        self.minimum.z = self.minimum.z.min(point.z);
110        self.maximum.x = self.maximum.x.max(point.x);
111        self.maximum.y = self.maximum.y.max(point.y);
112        self.maximum.z = self.maximum.z.max(point.z);
113    }
114
115    pub fn include(&mut self, other: Self) {
116        self.include_point(other.minimum);
117        self.include_point(other.maximum);
118    }
119
120    pub fn from_surface_controls(surface: &NurbsSurface) -> Result<Self, String> {
121        let mut bounds = Self::empty();
122        for control in surface.control_points.iter().flatten() {
123            bounds.include_point(control.point()?);
124        }
125        Ok(bounds)
126    }
127
128    pub fn expanded(self, amount: f64) -> Self {
129        let delta = Vec3::new(amount, amount, amount);
130        Self {
131            minimum: self.minimum.sub(delta),
132            maximum: self.maximum.add(delta),
133        }
134    }
135
136    pub fn contains(self, point: Vec3) -> bool {
137        point.x >= self.minimum.x
138            && point.x <= self.maximum.x
139            && point.y >= self.minimum.y
140            && point.y <= self.maximum.y
141            && point.z >= self.minimum.z
142            && point.z <= self.maximum.z
143    }
144
145    pub fn intersects(self, other: Self, tolerance: f64) -> bool {
146        self.minimum.x - tolerance <= other.maximum.x
147            && self.maximum.x + tolerance >= other.minimum.x
148            && self.minimum.y - tolerance <= other.maximum.y
149            && self.maximum.y + tolerance >= other.minimum.y
150            && self.minimum.z - tolerance <= other.maximum.z
151            && self.maximum.z + tolerance >= other.minimum.z
152    }
153
154    pub fn diagonal(self) -> f64 {
155        self.maximum.sub(self.minimum).length()
156    }
157
158    fn center_along(self, index: usize) -> f64 {
159        (axis(self.minimum, index) + axis(self.maximum, index)) / 2.0
160    }
161
162    fn intersects_segment(self, start: Vec3, delta: Vec3, tolerance: f64) -> bool {
163        self.intersects_parametric(start, delta, tolerance, 1.0)
164    }
165
166    fn intersects_parametric(self, start: Vec3, delta: Vec3, tolerance: f64, limit: f64) -> bool {
167        let mut enter = 0.0f64;
168        let mut exit = limit;
169        for index in 0..3 {
170            let origin = axis(start, index);
171            let direction = axis(delta, index);
172            let minimum = axis(self.minimum, index) - tolerance;
173            let maximum = axis(self.maximum, index) + tolerance;
174            if direction.abs() < 1e-300 {
175                if origin < minimum || origin > maximum {
176                    return false;
177                }
178                continue;
179            }
180            let inverse = 1.0 / direction;
181            let mut near = (minimum - origin) * inverse;
182            let mut far = (maximum - origin) * inverse;
183            if near > far {
184                std::mem::swap(&mut near, &mut far);
185            }
186            enter = enter.max(near);
187            exit = exit.min(far);
188            if enter > exit {
189                return false;
190            }
191        }
192        true
193    }
194}
195
196const LEAF_SIZE: usize = 4;
197
198#[derive(Debug)]
199struct Node {
200    bounds: Aabb,
201    /// Leaf: start index into `order`. Internal: index of the left child
202    /// node (the right child immediately follows the left subtree).
203    left: u32,
204    /// Leaf: end index (exclusive) into `order`. Internal: index of the
205    /// right child node.
206    right: u32,
207    leaf: bool,
208}
209
210/// Static BVH over per-item boxes; queries return indices into the slice
211/// the tree was built from.
212#[derive(Debug)]
213pub struct Bvh {
214    nodes: Vec<Node>,
215    order: Vec<u32>,
216    boxes: Vec<Aabb>,
217}
218
219impl Bvh {
220    pub fn build(boxes: &[Aabb]) -> Self {
221        let mut order: Vec<u32> = (0..boxes.len() as u32).collect();
222        let mut nodes = Vec::new();
223        if !boxes.is_empty() {
224            let count = order.len();
225            build_node(boxes, &mut order, 0, count, &mut nodes);
226        }
227        Self {
228            nodes,
229            order,
230            boxes: boxes.to_vec(),
231        }
232    }
233
234    /// Collect indices whose box overlaps `query` expanded by `tolerance`.
235    pub fn overlapping(&self, query: Aabb, tolerance: f64, out: &mut Vec<usize>) {
236        self.visit(|bounds| bounds.intersects(query, tolerance), out);
237    }
238
239    /// Collect indices whose box (expanded by `tolerance`) intersects the
240    /// segment from `start` to `end`.
241    pub fn intersecting_segment(
242        &self,
243        start: Vec3,
244        end: Vec3,
245        tolerance: f64,
246        out: &mut Vec<usize>,
247    ) {
248        let delta = end.sub(start);
249        self.visit(
250            |bounds| bounds.intersects_segment(start, delta, tolerance),
251            out,
252        );
253    }
254
255    /// Collect indices whose box intersects the forward ray. Unlike a segment
256    /// query this has no far limit (camera clipping planes need not bound CPU
257    /// picking). `direction` need not be normalized; boxes behind the origin
258    /// are excluded and grazing hits are retained.
259    pub fn intersecting_ray(
260        &self,
261        origin: Vec3,
262        direction: Vec3,
263        tolerance: f64,
264        out: &mut Vec<usize>,
265    ) {
266        self.visit(
267            |bounds| bounds.intersects_parametric(origin, direction, tolerance, f64::INFINITY),
268            out,
269        );
270    }
271
272    /// Collect indices whose box (expanded by `tolerance`) contains `point`.
273    pub fn containing_point(&self, point: Vec3, tolerance: f64, out: &mut Vec<usize>) {
274        self.visit(|bounds| bounds.expanded(tolerance).contains(point), out);
275    }
276
277    fn visit(&self, hit: impl Fn(Aabb) -> bool, out: &mut Vec<usize>) {
278        if self.nodes.is_empty() {
279            return;
280        }
281        let mut stack = vec![0usize];
282        while let Some(index) = stack.pop() {
283            let node = &self.nodes[index];
284            if !hit(node.bounds) {
285                continue;
286            }
287            if node.leaf {
288                for &item in &self.order[node.left as usize..node.right as usize] {
289                    if hit(self.boxes[item as usize]) {
290                        out.push(item as usize);
291                    }
292                }
293            } else {
294                stack.push(node.left as usize);
295                stack.push(node.right as usize);
296            }
297        }
298    }
299}
300
301fn build_node(
302    boxes: &[Aabb],
303    order: &mut [u32],
304    start: usize,
305    end: usize,
306    nodes: &mut Vec<Node>,
307) -> usize {
308    let mut bounds = Aabb::empty();
309    for &item in &order[start..end] {
310        bounds.include(boxes[item as usize]);
311    }
312    let index = nodes.len();
313    if end - start <= LEAF_SIZE {
314        nodes.push(Node {
315            bounds,
316            left: start as u32,
317            right: end as u32,
318            leaf: true,
319        });
320        return index;
321    }
322    let extent = bounds.maximum.sub(bounds.minimum);
323    let split_axis = if extent.x >= extent.y && extent.x >= extent.z {
324        0
325    } else if extent.y >= extent.z {
326        1
327    } else {
328        2
329    };
330    let middle = start + (end - start) / 2;
331    order[start..end].select_nth_unstable_by(middle - start, |&a, &b| {
332        boxes[a as usize]
333            .center_along(split_axis)
334            .total_cmp(&boxes[b as usize].center_along(split_axis))
335    });
336    nodes.push(Node {
337        bounds,
338        left: 0,
339        right: 0,
340        leaf: false,
341    });
342    let left = build_node(boxes, order, start, middle, nodes);
343    let right = build_node(boxes, order, middle, end, nodes);
344    nodes[index].left = left as u32;
345    nodes[index].right = right as u32;
346    index
347}
348