use crate::primitives::{AABB2, AABB3};
#[derive(Debug, Clone)]
struct Entry2D {
key: usize,
bounds: AABB2,
}
#[derive(Debug, Clone, Default)]
pub struct SpatialIndex2D {
entries: Vec<Entry2D>,
}
impl SpatialIndex2D {
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
entries: Vec::with_capacity(capacity),
}
}
#[inline]
pub fn len(&self) -> usize {
self.entries.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn insert(&mut self, key: usize, bounds: AABB2) {
self.entries.push(Entry2D { key, bounds });
}
pub fn remove(&mut self, key: usize) {
self.entries.retain(|e| e.key != key);
}
pub fn query(&self, region: &AABB2) -> Vec<usize> {
self.entries
.iter()
.filter(|e| e.bounds.intersects(region))
.map(|e| e.key)
.collect()
}
pub fn query_except(&self, region: &AABB2, exclude: usize) -> Vec<usize> {
self.entries
.iter()
.filter(|e| e.key != exclude && e.bounds.intersects(region))
.map(|e| e.key)
.collect()
}
pub fn has_collision(&self, region: &AABB2) -> bool {
self.entries.iter().any(|e| e.bounds.intersects(region))
}
pub fn has_collision_except(&self, region: &AABB2, exclude: usize) -> bool {
self.entries
.iter()
.any(|e| e.key != exclude && e.bounds.intersects(region))
}
pub fn clear(&mut self) {
self.entries.clear();
}
}
#[derive(Debug, Clone)]
struct Entry3D {
key: usize,
bounds: AABB3,
}
#[derive(Debug, Clone, Default)]
pub struct SpatialIndex3D {
entries: Vec<Entry3D>,
}
impl SpatialIndex3D {
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
entries: Vec::with_capacity(capacity),
}
}
#[inline]
pub fn len(&self) -> usize {
self.entries.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn insert(&mut self, key: usize, bounds: AABB3) {
self.entries.push(Entry3D { key, bounds });
}
pub fn remove(&mut self, key: usize) {
self.entries.retain(|e| e.key != key);
}
pub fn query(&self, region: &AABB3) -> Vec<usize> {
self.entries
.iter()
.filter(|e| e.bounds.intersects(region))
.map(|e| e.key)
.collect()
}
pub fn query_except(&self, region: &AABB3, exclude: usize) -> Vec<usize> {
self.entries
.iter()
.filter(|e| e.key != exclude && e.bounds.intersects(region))
.map(|e| e.key)
.collect()
}
pub fn has_collision(&self, region: &AABB3) -> bool {
self.entries.iter().any(|e| e.bounds.intersects(region))
}
pub fn has_collision_except(&self, region: &AABB3, exclude: usize) -> bool {
self.entries
.iter()
.any(|e| e.key != exclude && e.bounds.intersects(region))
}
pub fn get_bounds(&self, key: usize) -> Option<&AABB3> {
self.entries
.iter()
.find(|e| e.key == key)
.map(|e| &e.bounds)
}
pub fn clear(&mut self) {
self.entries.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_2d_insert_and_query() {
let mut idx = SpatialIndex2D::new();
idx.insert(0, AABB2::new(0.0, 0.0, 10.0, 10.0));
idx.insert(1, AABB2::new(20.0, 20.0, 30.0, 30.0));
idx.insert(2, AABB2::new(5.0, 5.0, 15.0, 15.0));
let hits = idx.query(&AABB2::new(8.0, 8.0, 12.0, 12.0));
assert!(hits.contains(&0)); assert!(hits.contains(&2)); assert!(!hits.contains(&1)); }
#[test]
fn test_2d_query_except() {
let mut idx = SpatialIndex2D::new();
idx.insert(0, AABB2::new(0.0, 0.0, 10.0, 10.0));
idx.insert(1, AABB2::new(5.0, 5.0, 15.0, 15.0));
let hits = idx.query_except(&AABB2::new(8.0, 8.0, 12.0, 12.0), 0);
assert!(!hits.contains(&0));
assert!(hits.contains(&1));
}
#[test]
fn test_2d_has_collision() {
let mut idx = SpatialIndex2D::new();
idx.insert(0, AABB2::new(0.0, 0.0, 10.0, 10.0));
assert!(idx.has_collision(&AABB2::new(5.0, 5.0, 15.0, 15.0)));
assert!(!idx.has_collision(&AABB2::new(20.0, 20.0, 30.0, 30.0)));
}
#[test]
fn test_2d_remove() {
let mut idx = SpatialIndex2D::new();
idx.insert(0, AABB2::new(0.0, 0.0, 10.0, 10.0));
idx.insert(1, AABB2::new(5.0, 5.0, 15.0, 15.0));
assert_eq!(idx.len(), 2);
idx.remove(0);
assert_eq!(idx.len(), 1);
assert!(!idx.has_collision(&AABB2::new(0.0, 0.0, 4.0, 4.0)));
}
#[test]
fn test_2d_clear() {
let mut idx = SpatialIndex2D::new();
idx.insert(0, AABB2::new(0.0, 0.0, 10.0, 10.0));
idx.clear();
assert!(idx.is_empty());
}
#[test]
fn test_2d_empty_query() {
let idx = SpatialIndex2D::new();
assert!(idx.query(&AABB2::new(0.0, 0.0, 10.0, 10.0)).is_empty());
assert!(!idx.has_collision(&AABB2::new(0.0, 0.0, 10.0, 10.0)));
}
fn box3d(x: f64, y: f64, z: f64, w: f64, d: f64, h: f64) -> AABB3 {
AABB3::new(x, y, z, x + w, y + d, z + h)
}
#[test]
fn test_3d_insert_and_query() {
let mut idx = SpatialIndex3D::new();
idx.insert(0, box3d(0.0, 0.0, 0.0, 10.0, 10.0, 10.0));
idx.insert(1, box3d(20.0, 20.0, 20.0, 10.0, 10.0, 10.0));
idx.insert(2, box3d(5.0, 5.0, 5.0, 10.0, 10.0, 10.0));
let hits = idx.query(&box3d(8.0, 8.0, 8.0, 4.0, 4.0, 4.0));
assert!(hits.contains(&0));
assert!(hits.contains(&2));
assert!(!hits.contains(&1));
}
#[test]
fn test_3d_query_except() {
let mut idx = SpatialIndex3D::new();
idx.insert(0, box3d(0.0, 0.0, 0.0, 10.0, 10.0, 10.0));
idx.insert(1, box3d(5.0, 5.0, 5.0, 10.0, 10.0, 10.0));
let hits = idx.query_except(&box3d(8.0, 8.0, 8.0, 4.0, 4.0, 4.0), 0);
assert!(!hits.contains(&0));
assert!(hits.contains(&1));
}
#[test]
fn test_3d_has_collision() {
let mut idx = SpatialIndex3D::new();
idx.insert(0, box3d(0.0, 0.0, 0.0, 10.0, 10.0, 10.0));
assert!(idx.has_collision(&box3d(5.0, 5.0, 5.0, 10.0, 10.0, 10.0)));
assert!(!idx.has_collision(&box3d(20.0, 20.0, 20.0, 10.0, 10.0, 10.0)));
}
#[test]
fn test_3d_has_collision_except() {
let mut idx = SpatialIndex3D::new();
idx.insert(0, box3d(0.0, 0.0, 0.0, 10.0, 10.0, 10.0));
idx.insert(1, box3d(5.0, 5.0, 5.0, 10.0, 10.0, 10.0));
assert!(idx.has_collision_except(&box3d(8.0, 8.0, 8.0, 4.0, 4.0, 4.0), 0));
assert!(!idx.has_collision_except(&box3d(0.0, 0.0, 0.0, 2.0, 2.0, 2.0), 0));
}
#[test]
fn test_3d_remove() {
let mut idx = SpatialIndex3D::new();
idx.insert(0, box3d(0.0, 0.0, 0.0, 10.0, 10.0, 10.0));
idx.insert(1, box3d(5.0, 5.0, 5.0, 10.0, 10.0, 10.0));
assert_eq!(idx.len(), 2);
idx.remove(0);
assert_eq!(idx.len(), 1);
assert!(!idx.has_collision(&box3d(0.0, 0.0, 0.0, 2.0, 2.0, 2.0)));
}
#[test]
fn test_3d_get_bounds() {
let mut idx = SpatialIndex3D::new();
let b = box3d(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
idx.insert(42, b);
let found = idx.get_bounds(42).unwrap();
assert!((found.min.x - 1.0).abs() < 1e-10);
assert!((found.max.z - 9.0).abs() < 1e-10);
assert!(idx.get_bounds(99).is_none());
}
#[test]
fn test_3d_clear() {
let mut idx = SpatialIndex3D::new();
idx.insert(0, box3d(0.0, 0.0, 0.0, 10.0, 10.0, 10.0));
idx.clear();
assert!(idx.is_empty());
}
#[test]
fn test_3d_with_capacity() {
let idx = SpatialIndex3D::with_capacity(100);
assert_eq!(idx.len(), 0);
}
#[test]
fn test_3d_z_axis_separation() {
let mut idx = SpatialIndex3D::new();
idx.insert(0, box3d(0.0, 0.0, 0.0, 10.0, 10.0, 10.0));
idx.insert(1, box3d(0.0, 0.0, 20.0, 10.0, 10.0, 10.0));
let hits = idx.query(&box3d(0.0, 0.0, 12.0, 10.0, 10.0, 5.0));
assert!(hits.is_empty());
let hits = idx.query(&box3d(0.0, 0.0, 18.0, 10.0, 10.0, 5.0));
assert_eq!(hits.len(), 1);
assert!(hits.contains(&1));
}
}