use crate::{Position, QuadTreeNode};
#[derive(Debug)]
pub struct QuadTree {
nodes: Vec<QuadTreeNode>,
bounds_min: Position,
bounds_max: Position,
}
impl QuadTree {
pub fn build(positions: &[Position], max_depth: usize) -> Self {
if positions.is_empty() {
return Self {
nodes: vec![QuadTreeNode::default()],
bounds_min: Position::default(),
bounds_max: Position::default(),
};
}
let mut min_x = f32::MAX;
let mut min_y = f32::MAX;
let mut max_x = f32::MIN;
let mut max_y = f32::MIN;
for pos in positions {
min_x = min_x.min(pos.x);
min_y = min_y.min(pos.y);
max_x = max_x.max(pos.x);
max_y = max_y.max(pos.y);
}
let padding = ((max_x - min_x).max(max_y - min_y) * 0.1).max(1.0);
min_x -= padding;
min_y -= padding;
max_x += padding;
max_y += padding;
let width = (max_x - min_x).max(max_y - min_y);
let center_x = (min_x + max_x) / 2.0;
let center_y = (min_y + max_y) / 2.0;
let bounds_min = Position::new(center_x - width / 2.0, center_y - width / 2.0);
let bounds_max = Position::new(center_x + width / 2.0, center_y + width / 2.0);
let mut nodes = Vec::with_capacity(positions.len() * 2);
let mut builder = TreeBuilder {
positions,
nodes: &mut nodes,
max_depth,
};
let indices: Vec<usize> = (0..positions.len()).collect();
builder.build_node(&indices, bounds_min.x, bounds_min.y, width, 0);
Self {
nodes,
bounds_min,
bounds_max,
}
}
pub fn nodes(&self) -> &[QuadTreeNode] {
&self.nodes
}
pub fn bounds(&self) -> (Position, Position) {
(self.bounds_min, self.bounds_max)
}
}
struct TreeBuilder<'a> {
positions: &'a [Position],
nodes: &'a mut Vec<QuadTreeNode>,
max_depth: usize,
}
impl<'a> TreeBuilder<'a> {
fn build_node(&mut self, indices: &[usize], x: f32, y: f32, width: f32, depth: usize) -> i32 {
if indices.is_empty() {
return -1;
}
let node_idx = self.nodes.len() as i32;
self.nodes.push(QuadTreeNode::default());
let mut com_x = 0.0;
let mut com_y = 0.0;
let mass = indices.len() as f32;
for &i in indices {
com_x += self.positions[i].x;
com_y += self.positions[i].y;
}
com_x /= mass;
com_y /= mass;
if indices.len() == 1 || depth >= self.max_depth {
self.nodes[node_idx as usize] = QuadTreeNode {
center_x: com_x,
center_y: com_y,
mass,
width,
child_nw: -1,
child_ne: -1,
child_sw: -1,
child_se: -1,
};
return node_idx;
}
let half_width = width / 2.0;
let mid_x = x + half_width;
let mid_y = y + half_width;
let mut nw_indices = Vec::new();
let mut ne_indices = Vec::new();
let mut sw_indices = Vec::new();
let mut se_indices = Vec::new();
for &i in indices {
let pos = &self.positions[i];
if pos.x < mid_x {
if pos.y < mid_y {
sw_indices.push(i);
} else {
nw_indices.push(i);
}
} else if pos.y < mid_y {
se_indices.push(i);
} else {
ne_indices.push(i);
}
}
let child_nw = self.build_node(&nw_indices, x, mid_y, half_width, depth + 1);
let child_ne = self.build_node(&ne_indices, mid_x, mid_y, half_width, depth + 1);
let child_sw = self.build_node(&sw_indices, x, y, half_width, depth + 1);
let child_se = self.build_node(&se_indices, mid_x, y, half_width, depth + 1);
self.nodes[node_idx as usize] = QuadTreeNode {
center_x: com_x,
center_y: com_y,
mass,
width,
child_nw,
child_ne,
child_sw,
child_se,
};
node_idx
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_tree() {
let tree = QuadTree::build(&[], 10);
assert_eq!(tree.nodes().len(), 1);
}
#[test]
fn test_single_node() {
let positions = vec![Position::new(0.0, 0.0)];
let tree = QuadTree::build(&positions, 10);
assert!(!tree.nodes().is_empty());
assert_eq!(tree.nodes()[0].mass, 1.0);
}
#[test]
fn test_multiple_nodes() {
let positions = vec![
Position::new(0.0, 0.0),
Position::new(100.0, 0.0),
Position::new(0.0, 100.0),
Position::new(100.0, 100.0),
];
let tree = QuadTree::build(&positions, 10);
assert!(tree.nodes().len() > 1);
}
}