use cvkg_core::Rect;
pub struct Quadtree {
bounds: Rect,
rects: Vec<Rect>,
children: Option<Box<[Quadtree; 4]>>,
max_rects: usize,
max_depth: usize,
depth: usize,
}
impl Quadtree {
pub fn new(bounds: Rect) -> Self {
Self {
bounds,
rects: Vec::new(),
children: None,
max_rects: 10,
max_depth: 5,
depth: 0,
}
}
fn new_with_depth(bounds: Rect, depth: usize) -> Self {
Self {
bounds,
rects: Vec::new(),
children: None,
max_rects: 10,
max_depth: 5,
depth,
}
}
pub fn insert(&mut self, rect: Rect) {
if !self.intersects(self.bounds, rect) {
return;
}
if let Some(ref mut children) = self.children {
for child in children.iter_mut() {
child.insert(rect);
}
return;
}
self.rects.push(rect);
if self.rects.len() > self.max_rects && self.depth < self.max_depth {
self.subdivide();
}
}
fn subdivide(&mut self) {
let hw = self.bounds.width / 2.0;
let hh = self.bounds.height / 2.0;
let x = self.bounds.x;
let y = self.bounds.y;
let d = self.depth + 1;
let mut children = Box::new([
Quadtree::new_with_depth(
Rect {
x,
y,
width: hw,
height: hh,
},
d,
),
Quadtree::new_with_depth(
Rect {
x: x + hw,
y,
width: hw,
height: hh,
},
d,
),
Quadtree::new_with_depth(
Rect {
x,
y: y + hh,
width: hw,
height: hh,
},
d,
),
Quadtree::new_with_depth(
Rect {
x: x + hw,
y: y + hh,
width: hw,
height: hh,
},
d,
),
]);
for rect in self.rects.drain(..) {
for child in children.iter_mut() {
child.insert(rect);
}
}
self.children = Some(children);
}
fn intersects(&self, a: Rect, b: Rect) -> bool {
a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y
}
pub fn retrieve(&self, rect: Rect, out: &mut Vec<Rect>) {
if !self.intersects(self.bounds, rect) {
return;
}
if let Some(ref children) = self.children {
for child in children.iter() {
child.retrieve(rect, out);
}
} else {
for r in &self.rects {
out.push(*r);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use cvkg_core::Rect;
fn rect(x: f32, y: f32, w: f32, h: f32) -> Rect {
Rect {
x,
y,
width: w,
height: h,
}
}
#[test]
fn test_insert_and_retrieve_basic() {
let bounds = rect(0.0, 0.0, 100.0, 100.0);
let mut qt = Quadtree::new(bounds);
qt.insert(rect(10.0, 10.0, 20.0, 20.0));
qt.insert(rect(60.0, 60.0, 20.0, 20.0));
let mut out = Vec::new();
qt.retrieve(rect(5.0, 5.0, 30.0, 30.0), &mut out);
assert!(!out.is_empty(), "Should find at least one candidate");
}
#[test]
fn test_out_of_bounds_insert_is_dropped() {
let bounds = rect(0.0, 0.0, 100.0, 100.0);
let mut qt = Quadtree::new(bounds);
qt.insert(rect(200.0, 200.0, 10.0, 10.0));
let mut out = Vec::new();
qt.retrieve(rect(0.0, 0.0, 100.0, 100.0), &mut out);
assert!(out.is_empty(), "Out-of-bounds rect should be dropped");
}
#[test]
fn test_subdivide_triggers_under_load() {
let bounds = rect(0.0, 0.0, 1000.0, 1000.0);
let mut qt = Quadtree::new(bounds);
for i in 0..15_u32 {
let offset = (i * 60) as f32;
qt.insert(rect(offset % 900.0, offset % 900.0, 30.0, 30.0));
}
let mut out = Vec::new();
qt.retrieve(rect(0.0, 0.0, 1000.0, 1000.0), &mut out);
assert!(!out.is_empty(), "Should retrieve rects after subdivision");
}
}