use super::super::input::SegmentIndex;
const LEAF_SIZE: usize = 4;
#[derive(Debug, Clone, Copy)]
struct Node {
min_x: f64,
min_y: f64,
max_x: f64,
max_y: f64,
left: u32,
right: u32,
count: u32,
}
#[derive(Debug, Clone)]
pub struct Bvh {
nodes: Vec<Node>,
order: Vec<u32>,
}
impl Bvh {
pub fn new(segments: &SegmentIndex) -> Self {
let n = segments.len();
if n == 0 {
return Self {
nodes: Vec::new(),
order: Vec::new(),
};
}
let mut order: Vec<u32> = (0..n as u32).collect();
let boxes: Vec<(f64, f64, f64, f64)> = (0..n)
.map(|i| {
(
segments.bbox_minx[i],
segments.bbox_miny[i],
segments.bbox_maxx[i],
segments.bbox_maxy[i],
)
})
.collect();
let mut nodes = Vec::with_capacity(2 * n.div_ceil(LEAF_SIZE) + 1);
build(&mut nodes, &mut order, &boxes, 0, n);
Self { nodes, order }
}
#[inline]
pub fn nearest_distance_sq(
&self,
segments: &SegmentIndex,
x: f64,
y: f64,
) -> Option<(f64, usize)> {
if self.nodes.is_empty() || segments.is_empty() {
return None;
}
let mut best_sq = f64::INFINITY;
let mut best_idx = 0usize;
self.descend(0, segments, x, y, &mut best_sq, &mut best_idx);
Some((best_sq, best_idx))
}
#[inline]
pub fn nearest_distance_sq_above(
&self,
segments: &SegmentIndex,
x: f64,
y: f64,
threshold_sq: f64,
seed: Option<usize>,
) -> Option<(f64, usize)> {
if self.nodes.is_empty() || segments.is_empty() {
return None;
}
let mut best_sq = f64::INFINITY;
let mut best_idx = 0usize;
if let Some(si) = seed {
if si < segments.len() {
let d_sq = segments.point_segment_distance_sq(si, x, y);
if d_sq <= threshold_sq {
return None;
}
if d_sq < best_sq {
best_sq = d_sq;
best_idx = si;
}
}
}
if self.descend_above(0, segments, x, y, &mut best_sq, &mut best_idx, threshold_sq) {
return None;
}
Some((best_sq, best_idx))
}
#[allow(clippy::too_many_arguments)]
fn descend_above(
&self,
node_idx: usize,
segments: &SegmentIndex,
x: f64,
y: f64,
best_sq: &mut f64,
best_idx: &mut usize,
threshold_sq: f64,
) -> bool {
let node = self.nodes[node_idx];
if bbox_dist_sq(&node, x, y) >= *best_sq {
return false;
}
if node.count > 0 {
let start = node.left as usize;
for &seg in &self.order[start..start + node.count as usize] {
let d_sq = segments.point_segment_distance_sq(seg as usize, x, y);
if d_sq <= threshold_sq {
return true;
}
if d_sq < *best_sq {
*best_sq = d_sq;
*best_idx = seg as usize;
}
}
return false;
}
let left = node.left as usize;
let right = node.right as usize;
let dl = bbox_dist_sq(&self.nodes[left], x, y);
let dr = bbox_dist_sq(&self.nodes[right], x, y);
let (first, second) = if dl <= dr {
(left, right)
} else {
(right, left)
};
if self.descend_above(first, segments, x, y, best_sq, best_idx, threshold_sq) {
return true;
}
if bbox_dist_sq(&self.nodes[second], x, y) < *best_sq
&& self.descend_above(second, segments, x, y, best_sq, best_idx, threshold_sq)
{
return true;
}
false
}
pub fn supporting_segments(
&self,
segments: &SegmentIndex,
x: f64,
y: f64,
max_dist_sq: f64,
) -> Vec<usize> {
let mut out = Vec::new();
if self.nodes.is_empty() {
return out;
}
self.collect(0, segments, x, y, max_dist_sq, &mut out);
out
}
fn descend(
&self,
node_idx: usize,
segments: &SegmentIndex,
x: f64,
y: f64,
best_sq: &mut f64,
best_idx: &mut usize,
) {
let node = self.nodes[node_idx];
if bbox_dist_sq(&node, x, y) >= *best_sq {
return;
}
if node.count > 0 {
let start = node.left as usize;
for &seg in &self.order[start..start + node.count as usize] {
let d_sq = segments.point_segment_distance_sq(seg as usize, x, y);
if d_sq < *best_sq {
*best_sq = d_sq;
*best_idx = seg as usize;
}
}
return;
}
let left = node.left as usize;
let right = node.right as usize;
let dl = bbox_dist_sq(&self.nodes[left], x, y);
let dr = bbox_dist_sq(&self.nodes[right], x, y);
let (first, second) = if dl <= dr {
(left, right)
} else {
(right, left)
};
self.descend(first, segments, x, y, best_sq, best_idx);
if bbox_dist_sq(&self.nodes[second], x, y) < *best_sq {
self.descend(second, segments, x, y, best_sq, best_idx);
}
}
fn collect(
&self,
node_idx: usize,
segments: &SegmentIndex,
x: f64,
y: f64,
max_dist_sq: f64,
out: &mut Vec<usize>,
) {
let node = self.nodes[node_idx];
if bbox_dist_sq(&node, x, y) > max_dist_sq {
return;
}
if node.count > 0 {
let start = node.left as usize;
for &seg in &self.order[start..start + node.count as usize] {
let d_sq = segments.point_segment_distance_sq(seg as usize, x, y);
if d_sq <= max_dist_sq {
out.push(seg as usize);
}
}
return;
}
self.collect(node.left as usize, segments, x, y, max_dist_sq, out);
self.collect(node.right as usize, segments, x, y, max_dist_sq, out);
}
}
fn build(
nodes: &mut Vec<Node>,
order: &mut [u32],
boxes: &[(f64, f64, f64, f64)],
start: usize,
end: usize,
) -> usize {
let mut min_x = f64::INFINITY;
let mut min_y = f64::INFINITY;
let mut max_x = f64::NEG_INFINITY;
let mut max_y = f64::NEG_INFINITY;
for i in start..end {
let b = boxes[order[i] as usize];
min_x = min_x.min(b.0);
min_y = min_y.min(b.1);
max_x = max_x.max(b.2);
max_y = max_y.max(b.3);
}
let idx = nodes.len();
nodes.push(Node {
min_x,
min_y,
max_x,
max_y,
left: 0,
right: 0,
count: 0,
});
let count = end - start;
if count <= LEAF_SIZE {
nodes[idx].left = start as u32;
nodes[idx].count = count as u32;
return idx;
}
let (mut cmin_x, mut cmin_y) = (f64::INFINITY, f64::INFINITY);
let (mut cmax_x, mut cmax_y) = (f64::NEG_INFINITY, f64::NEG_INFINITY);
for i in start..end {
let b = boxes[order[i] as usize];
let cx = (b.0 + b.2) * 0.5;
let cy = (b.1 + b.3) * 0.5;
cmin_x = cmin_x.min(cx);
cmin_y = cmin_y.min(cy);
cmax_x = cmax_x.max(cx);
cmax_y = cmax_y.max(cy);
}
let axis = if cmax_x - cmin_x >= cmax_y - cmin_y {
0
} else {
1
};
let mid = start + count / 2;
order[start..end].select_nth_unstable_by(count / 2, |&a, &b| {
let ca = centre(boxes[a as usize], axis);
let cb = centre(boxes[b as usize], axis);
ca.partial_cmp(&cb).unwrap_or(std::cmp::Ordering::Equal)
});
nodes[idx].left = (nodes.len()) as u32;
nodes[idx].count = 0;
build(nodes, order, boxes, start, mid);
let right = build(nodes, order, boxes, mid, end);
nodes[idx].right = right as u32;
idx
}
#[inline]
fn centre(b: (f64, f64, f64, f64), axis: usize) -> f64 {
if axis == 0 {
(b.0 + b.2) * 0.5
} else {
(b.1 + b.3) * 0.5
}
}
#[inline]
fn bbox_dist_sq(node: &Node, x: f64, y: f64) -> f64 {
let dx = if x < node.min_x {
node.min_x - x
} else if x > node.max_x {
x - node.max_x
} else {
0.0
};
let dy = if y < node.min_y {
node.min_y - y
} else if y > node.max_y {
y - node.max_y
} else {
0.0
};
dx * dx + dy * dy
}