pub(crate) use super::tree_impl::{NodeID, Tree};
use super::{tree_impl::Change, QRelation};
use crate::collision::{AsDynCollision, CollisionQuery};
use bevy_ecs::{
entity::{EntityHashMap, EntityHashSet},
prelude::*,
};
use core::fmt;
use parking_lot::RwLock;
use std::any::type_name;
#[derive(Resource)]
pub struct QuadTree<const ID: usize = 0> {
pub(crate) tree: Tree,
pub(crate) entities: RwLock<EntityHashMap<NodeID>>,
}
impl<const ID: usize> fmt::Debug for QuadTree<ID> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct(type_name::<Self>())
.field("tree", &self.tree)
.field("len", &self.len())
.finish()
}
}
impl<const ID: usize> QuadTree<ID> {
pub(crate) fn new(
n: usize,
d: usize,
w: usize,
h: usize,
x: usize,
y: usize,
k: usize,
) -> Self {
Self {
tree: Tree::new(n, d, w as f32, h as f32, x as f32, y as f32, k as f32 / 10.),
entities: RwLock::new(EntityHashMap::default()),
}
}
fn len(&self) -> usize {
self.entities.read().len()
}
pub(crate) fn insert<S>(&self, entity: Entity, shape: S)
where
S: AsDynCollision + 'static,
{
let shape = shape.as_dyn_collision();
let mut changed = vec![];
match self.entities.read().get(&entity) {
Some(id) => self.tree.update(*id, entity, shape, &mut changed),
None => self.tree.insert(0, entity, shape, &mut changed, vec![]),
};
let mut entities = self.entities.write();
for c in changed {
match c {
Change::Move(entity, id) => entities.insert(entity, id),
Change::Leave(entity) => entities.remove(&entity),
};
}
}
pub(crate) fn remove(&self, entity: &Entity) {
if let Some(id) = self.entities.write().remove(entity) {
self.tree.remove(id, entity);
self.tree.merge_up(id);
}
}
pub fn query<Q>(&self, boundary: &dyn CollisionQuery) -> EntityHashSet
where
Q: QRelation,
{
Q::filter(&self.tree.query_tree(), boundary)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{CollisionCircle, CollisionRect, Contain, Contained, Disjoint, Overlap, QNot, QOr};
use bevy_math::prelude::*;
use rand::prelude::*;
use rand_chacha::ChaCha8Rng;
#[test]
fn non_loose() {
let qtree: QuadTree<0> = QuadTree::new(2, 4, 4, 4, 0, 0, 10);
let mut rng = ChaCha8Rng::seed_from_u64(0);
assert_eq!(qtree.len(), 0);
qtree.insert(Entity::PLACEHOLDER, CollisionCircle::new(Vec2::ZERO, 1.));
assert_eq!(qtree.len(), 1);
qtree.insert(
Entity::PLACEHOLDER,
CollisionRect::from(Rect::from_center_size(Vec2::ZERO, Vec2::ONE)),
);
assert_eq!(qtree.len(), 1);
qtree.insert(
Entity::from_raw(rng.random()),
CollisionRect::from(Rect::from_center_size(Vec2::ZERO, Vec2::ONE)),
);
assert_eq!(qtree.len(), 2);
qtree.insert(
Entity::from_raw(rng.random()),
CollisionRect::from(Rect::from_center_size(Vec2::splat(1.), Vec2::ONE)),
);
{
assert_eq!(qtree.tree.total(1), 1);
}
qtree.insert(
Entity::from_raw(rng.random()),
CollisionRect::from(Rect::from_center_size(Vec2::splat(1.), Vec2::ONE)),
);
assert_eq!(qtree.len(), 4);
{
assert_eq!(qtree.tree.total(1), 2);
}
qtree.insert(
Entity::from_raw(rng.random()),
CollisionRect::from(Rect::from_center_size(Vec2::splat(0.5), Vec2::splat(0.2))),
);
assert_eq!(qtree.len(), 5);
{
assert_eq!(qtree.tree[1].len(), 2);
assert_eq!(qtree.tree.total(1), 3);
assert_eq!(qtree.tree.total(7), 1);
}
qtree.insert(
Entity::PLACEHOLDER,
CollisionRect::from(Rect::from_center_size(Vec2::new(1., 0.), Vec2::ONE)),
);
assert_eq!(qtree.len(), 5);
assert_eq!(qtree.tree.total(0), 5);
qtree.insert(
Entity::PLACEHOLDER,
CollisionRect::from(Rect::from_center_size(
Vec2::splat(0.5),
Vec2::new(0.2, 0.3),
)),
);
assert_eq!(qtree.len(), 5);
{
assert_eq!(qtree.tree[0].len(), 1);
assert_eq!(qtree.tree.total(0), 5);
assert_eq!(qtree.tree[1].len(), 2);
assert_eq!(qtree.tree.total(1), 4);
assert_eq!(qtree.tree[7].len(), 2);
assert_eq!(qtree.tree.total(7), 2);
}
qtree.insert(
Entity::PLACEHOLDER,
CollisionRect::from(Rect::from_center_size(Vec2::new(1., -1.), Vec2::splat(1.))),
);
assert_eq!(qtree.len(), 5);
{
assert_eq!(qtree.tree[0].len(), 1);
assert_eq!(qtree.tree.total(0), 5);
assert_eq!(qtree.tree[1].len(), 2);
assert_eq!(qtree.tree.total(1), 3);
assert_eq!(qtree.tree[4].len(), 1);
}
qtree.remove(&Entity::PLACEHOLDER);
assert_eq!(qtree.len(), 4);
{
assert_eq!(qtree.tree[0].len(), 1);
assert_eq!(qtree.tree.total(0), 4);
assert_eq!(qtree.tree[1].len(), 2);
assert_eq!(qtree.tree.total(1), 3);
assert_eq!(qtree.tree[4].len(), 0);
}
qtree.insert(
Entity::from_raw(rng.random()),
CollisionRect::from(Rect::from_center_size(
Vec2::splat(-1.),
Vec2::new(0.2, 0.3),
)),
);
qtree.insert(
Entity::from_raw(rng.random()),
CollisionRect::from(Rect::from_center_size(
Vec2::splat(-1.),
Vec2::new(0.2, 0.3),
)),
);
qtree.insert(
Entity::PLACEHOLDER,
CollisionRect::from(Rect::from_center_size(
Vec2::splat(-0.5),
Vec2::new(0.2, 0.3),
)),
);
qtree.remove(&Entity::PLACEHOLDER);
assert_eq!(qtree.len(), 6);
{
assert_eq!(qtree.tree[0].len(), 1);
assert_eq!(qtree.tree.total(0), 6);
assert_eq!(qtree.tree[3].len(), 2);
assert_eq!(qtree.tree.total(3), 2);
assert!(qtree.tree[3].is_leaf());
}
let q = qtree.query::<Overlap>(&CollisionRect::from(Rect::from_center_size(
Vec2::ZERO,
Vec2::ONE,
)));
assert_eq!(q.len(), 4);
let q = qtree.query::<QOr<(Overlap, Contain)>>(&CollisionCircle::new(Vec2::ZERO, 1.));
assert_eq!(q.len(), 4);
let q = qtree.query::<QNot<Contain>>(&CollisionCircle::new(Vec2::ZERO, 1.));
assert_eq!(q.len(), 4);
let q = qtree.query::<Disjoint>(&CollisionCircle::new(Vec2::splat(0.5), 1.));
assert_eq!(q.len(), 2);
let q = qtree.query::<Contain>(&CollisionCircle::new(Vec2::splat(0.5), 1.));
assert_eq!(q.len(), 1);
let q = qtree.query::<Contained>(&CollisionCircle::new(Vec2::ONE, 0.4));
assert_eq!(q.len(), 2);
}
}