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 fn empty() -> Self {
29        Self {
30            minimum: Vec3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY),
31            maximum: Vec3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY),
32        }
33    }
34
35    pub(crate) fn from_points(points: impl IntoIterator<Item = Vec3>) -> Self {
36        let mut bounds = Self::empty();
37        for point in points {
38            bounds.include_point(point);
39        }
40        bounds
41    }
42
43    pub fn include_point(&mut self, point: Vec3) {
44        self.minimum.x = self.minimum.x.min(point.x);
45        self.minimum.y = self.minimum.y.min(point.y);
46        self.minimum.z = self.minimum.z.min(point.z);
47        self.maximum.x = self.maximum.x.max(point.x);
48        self.maximum.y = self.maximum.y.max(point.y);
49        self.maximum.z = self.maximum.z.max(point.z);
50    }
51
52    pub fn include(&mut self, other: Self) {
53        self.include_point(other.minimum);
54        self.include_point(other.maximum);
55    }
56
57    pub fn from_surface_controls(surface: &NurbsSurface) -> Result<Self, String> {
58        let mut bounds = Self::empty();
59        for control in surface.control_points.iter().flatten() {
60            bounds.include_point(control.point()?);
61        }
62        Ok(bounds)
63    }
64
65    pub fn expanded(self, amount: f64) -> Self {
66        let delta = Vec3::new(amount, amount, amount);
67        Self {
68            minimum: self.minimum.sub(delta),
69            maximum: self.maximum.add(delta),
70        }
71    }
72
73    pub fn contains(self, point: Vec3) -> bool {
74        point.x >= self.minimum.x
75            && point.x <= self.maximum.x
76            && point.y >= self.minimum.y
77            && point.y <= self.maximum.y
78            && point.z >= self.minimum.z
79            && point.z <= self.maximum.z
80    }
81
82    pub fn intersects(self, other: Self, tolerance: f64) -> bool {
83        self.minimum.x - tolerance <= other.maximum.x
84            && self.maximum.x + tolerance >= other.minimum.x
85            && self.minimum.y - tolerance <= other.maximum.y
86            && self.maximum.y + tolerance >= other.minimum.y
87            && self.minimum.z - tolerance <= other.maximum.z
88            && self.maximum.z + tolerance >= other.minimum.z
89    }
90
91    pub fn diagonal(self) -> f64 {
92        self.maximum.sub(self.minimum).length()
93    }
94
95    fn center_along(self, index: usize) -> f64 {
96        (axis(self.minimum, index) + axis(self.maximum, index)) / 2.0
97    }
98
99    fn intersects_segment(self, start: Vec3, delta: Vec3, tolerance: f64) -> bool {
100        self.intersects_parametric(start, delta, tolerance, 1.0)
101    }
102
103    fn intersects_parametric(self, start: Vec3, delta: Vec3, tolerance: f64, limit: f64) -> bool {
104        let mut enter = 0.0f64;
105        let mut exit = limit;
106        for index in 0..3 {
107            let origin = axis(start, index);
108            let direction = axis(delta, index);
109            let minimum = axis(self.minimum, index) - tolerance;
110            let maximum = axis(self.maximum, index) + tolerance;
111            if direction.abs() < 1e-300 {
112                if origin < minimum || origin > maximum {
113                    return false;
114                }
115                continue;
116            }
117            let inverse = 1.0 / direction;
118            let mut near = (minimum - origin) * inverse;
119            let mut far = (maximum - origin) * inverse;
120            if near > far {
121                std::mem::swap(&mut near, &mut far);
122            }
123            enter = enter.max(near);
124            exit = exit.min(far);
125            if enter > exit {
126                return false;
127            }
128        }
129        true
130    }
131}
132
133const LEAF_SIZE: usize = 4;
134
135#[derive(Debug)]
136struct Node {
137    bounds: Aabb,
138    /// Leaf: start index into `order`. Internal: index of the left child
139    /// node (the right child immediately follows the left subtree).
140    left: u32,
141    /// Leaf: end index (exclusive) into `order`. Internal: index of the
142    /// right child node.
143    right: u32,
144    leaf: bool,
145}
146
147/// Static BVH over per-item boxes; queries return indices into the slice
148/// the tree was built from.
149#[derive(Debug)]
150pub struct Bvh {
151    nodes: Vec<Node>,
152    order: Vec<u32>,
153    boxes: Vec<Aabb>,
154}
155
156impl Bvh {
157    pub fn build(boxes: &[Aabb]) -> Self {
158        let mut order: Vec<u32> = (0..boxes.len() as u32).collect();
159        let mut nodes = Vec::new();
160        if !boxes.is_empty() {
161            let count = order.len();
162            build_node(boxes, &mut order, 0, count, &mut nodes);
163        }
164        Self {
165            nodes,
166            order,
167            boxes: boxes.to_vec(),
168        }
169    }
170
171    /// Collect indices whose box overlaps `query` expanded by `tolerance`.
172    pub fn overlapping(&self, query: Aabb, tolerance: f64, out: &mut Vec<usize>) {
173        self.visit(|bounds| bounds.intersects(query, tolerance), out);
174    }
175
176    /// Collect indices whose box (expanded by `tolerance`) intersects the
177    /// segment from `start` to `end`.
178    pub fn intersecting_segment(
179        &self,
180        start: Vec3,
181        end: Vec3,
182        tolerance: f64,
183        out: &mut Vec<usize>,
184    ) {
185        let delta = end.sub(start);
186        self.visit(
187            |bounds| bounds.intersects_segment(start, delta, tolerance),
188            out,
189        );
190    }
191
192    /// Collect indices whose box intersects the forward ray. Unlike a segment
193    /// query this has no far limit (camera clipping planes need not bound CPU
194    /// picking). `direction` need not be normalized; boxes behind the origin
195    /// are excluded and grazing hits are retained.
196    pub fn intersecting_ray(
197        &self,
198        origin: Vec3,
199        direction: Vec3,
200        tolerance: f64,
201        out: &mut Vec<usize>,
202    ) {
203        self.visit(
204            |bounds| bounds.intersects_parametric(origin, direction, tolerance, f64::INFINITY),
205            out,
206        );
207    }
208
209    /// Collect indices whose box (expanded by `tolerance`) contains `point`.
210    pub fn containing_point(&self, point: Vec3, tolerance: f64, out: &mut Vec<usize>) {
211        self.visit(|bounds| bounds.expanded(tolerance).contains(point), out);
212    }
213
214    fn visit(&self, hit: impl Fn(Aabb) -> bool, out: &mut Vec<usize>) {
215        if self.nodes.is_empty() {
216            return;
217        }
218        let mut stack = vec![0usize];
219        while let Some(index) = stack.pop() {
220            let node = &self.nodes[index];
221            if !hit(node.bounds) {
222                continue;
223            }
224            if node.leaf {
225                for &item in &self.order[node.left as usize..node.right as usize] {
226                    if hit(self.boxes[item as usize]) {
227                        out.push(item as usize);
228                    }
229                }
230            } else {
231                stack.push(node.left as usize);
232                stack.push(node.right as usize);
233            }
234        }
235    }
236}
237
238fn build_node(
239    boxes: &[Aabb],
240    order: &mut [u32],
241    start: usize,
242    end: usize,
243    nodes: &mut Vec<Node>,
244) -> usize {
245    let mut bounds = Aabb::empty();
246    for &item in &order[start..end] {
247        bounds.include(boxes[item as usize]);
248    }
249    let index = nodes.len();
250    if end - start <= LEAF_SIZE {
251        nodes.push(Node {
252            bounds,
253            left: start as u32,
254            right: end as u32,
255            leaf: true,
256        });
257        return index;
258    }
259    let extent = bounds.maximum.sub(bounds.minimum);
260    let split_axis = if extent.x >= extent.y && extent.x >= extent.z {
261        0
262    } else if extent.y >= extent.z {
263        1
264    } else {
265        2
266    };
267    let middle = start + (end - start) / 2;
268    order[start..end].select_nth_unstable_by(middle - start, |&a, &b| {
269        boxes[a as usize]
270            .center_along(split_axis)
271            .total_cmp(&boxes[b as usize].center_along(split_axis))
272    });
273    nodes.push(Node {
274        bounds,
275        left: 0,
276        right: 0,
277        leaf: false,
278    });
279    let left = build_node(boxes, order, start, middle, nodes);
280    let right = build_node(boxes, order, middle, end, nodes);
281    nodes[index].left = left as u32;
282    nodes[index].right = right as u32;
283    index
284}
285