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        let mut enter = 0.0f64;
101        let mut exit = 1.0f64;
102        for index in 0..3 {
103            let origin = axis(start, index);
104            let direction = axis(delta, index);
105            let minimum = axis(self.minimum, index) - tolerance;
106            let maximum = axis(self.maximum, index) + tolerance;
107            if direction.abs() < 1e-300 {
108                if origin < minimum || origin > maximum {
109                    return false;
110                }
111                continue;
112            }
113            let inverse = 1.0 / direction;
114            let mut near = (minimum - origin) * inverse;
115            let mut far = (maximum - origin) * inverse;
116            if near > far {
117                std::mem::swap(&mut near, &mut far);
118            }
119            enter = enter.max(near);
120            exit = exit.min(far);
121            if enter > exit {
122                return false;
123            }
124        }
125        true
126    }
127}
128
129const LEAF_SIZE: usize = 4;
130
131struct Node {
132    bounds: Aabb,
133    /// Leaf: start index into `order`. Internal: index of the left child
134    /// node (the right child immediately follows the left subtree).
135    left: u32,
136    /// Leaf: end index (exclusive) into `order`. Internal: index of the
137    /// right child node.
138    right: u32,
139    leaf: bool,
140}
141
142/// Static BVH over per-item boxes; queries return indices into the slice
143/// the tree was built from.
144pub struct Bvh {
145    nodes: Vec<Node>,
146    order: Vec<u32>,
147    boxes: Vec<Aabb>,
148}
149
150impl Bvh {
151    pub fn build(boxes: &[Aabb]) -> Self {
152        let mut order: Vec<u32> = (0..boxes.len() as u32).collect();
153        let mut nodes = Vec::new();
154        if !boxes.is_empty() {
155            let count = order.len();
156            build_node(boxes, &mut order, 0, count, &mut nodes);
157        }
158        Self {
159            nodes,
160            order,
161            boxes: boxes.to_vec(),
162        }
163    }
164
165    /// Collect indices whose box overlaps `query` expanded by `tolerance`.
166    pub fn overlapping(&self, query: Aabb, tolerance: f64, out: &mut Vec<usize>) {
167        self.visit(|bounds| bounds.intersects(query, tolerance), out);
168    }
169
170    /// Collect indices whose box (expanded by `tolerance`) intersects the
171    /// segment from `start` to `end`.
172    pub fn intersecting_segment(
173        &self,
174        start: Vec3,
175        end: Vec3,
176        tolerance: f64,
177        out: &mut Vec<usize>,
178    ) {
179        let delta = end.sub(start);
180        self.visit(
181            |bounds| bounds.intersects_segment(start, delta, tolerance),
182            out,
183        );
184    }
185
186    /// Collect indices whose box (expanded by `tolerance`) contains `point`.
187    pub fn containing_point(&self, point: Vec3, tolerance: f64, out: &mut Vec<usize>) {
188        self.visit(|bounds| bounds.expanded(tolerance).contains(point), out);
189    }
190
191    fn visit(&self, hit: impl Fn(Aabb) -> bool, out: &mut Vec<usize>) {
192        if self.nodes.is_empty() {
193            return;
194        }
195        let mut stack = vec![0usize];
196        while let Some(index) = stack.pop() {
197            let node = &self.nodes[index];
198            if !hit(node.bounds) {
199                continue;
200            }
201            if node.leaf {
202                for &item in &self.order[node.left as usize..node.right as usize] {
203                    if hit(self.boxes[item as usize]) {
204                        out.push(item as usize);
205                    }
206                }
207            } else {
208                stack.push(node.left as usize);
209                stack.push(node.right as usize);
210            }
211        }
212    }
213}
214
215fn build_node(
216    boxes: &[Aabb],
217    order: &mut [u32],
218    start: usize,
219    end: usize,
220    nodes: &mut Vec<Node>,
221) -> usize {
222    let mut bounds = Aabb::empty();
223    for &item in &order[start..end] {
224        bounds.include(boxes[item as usize]);
225    }
226    let index = nodes.len();
227    if end - start <= LEAF_SIZE {
228        nodes.push(Node {
229            bounds,
230            left: start as u32,
231            right: end as u32,
232            leaf: true,
233        });
234        return index;
235    }
236    let extent = bounds.maximum.sub(bounds.minimum);
237    let split_axis = if extent.x >= extent.y && extent.x >= extent.z {
238        0
239    } else if extent.y >= extent.z {
240        1
241    } else {
242        2
243    };
244    let middle = start + (end - start) / 2;
245    order[start..end].select_nth_unstable_by(middle - start, |&a, &b| {
246        boxes[a as usize]
247            .center_along(split_axis)
248            .total_cmp(&boxes[b as usize].center_along(split_axis))
249    });
250    nodes.push(Node {
251        bounds,
252        left: 0,
253        right: 0,
254        leaf: false,
255    });
256    let left = build_node(boxes, order, start, middle, nodes);
257    let right = build_node(boxes, order, middle, end, nodes);
258    nodes[index].left = left as u32;
259    nodes[index].right = right as u32;
260    index
261}
262
263// BREP private tests: 3d5ab0aeba79cfa2