#[cfg(feature = "multi-quadtree")]
mod multi_plugin;
use crate::collision::{AsDynCollision, UpdateCollision};
use crate::system::{update_collision, update_quadtree};
use crate::tree::QuadTree;
use bevy_app::prelude::*;
use bevy_ecs::component::Mutable;
use bevy_ecs::prelude::*;
use bevy_ecs::schedule::ScheduleConfigs;
use bevy_ecs::system::ScheduleSystem;
#[cfg(feature = "sprite")]
use bevy_sprite::Sprite;
use bevy_transform::prelude::*;
#[cfg(feature = "multi-quadtree")]
pub use multi_plugin::{
AsQuadTreePluginConfig, MultiQuadTreePlugin, QTConfig, QuadTreePluginConfig,
};
#[derive(Debug)]
pub struct QuadTreePlugin<
P,
const N: usize,
const D: usize,
const W: usize,
const H: usize,
const X: usize = 0,
const Y: usize = 0,
const K: usize = 20,
const ID: usize = 0,
> where
P: TrackingPair,
{
_marker: std::marker::PhantomData<P>,
}
impl<
P,
const D: usize,
const N: usize,
const W: usize,
const H: usize,
const X: usize,
const Y: usize,
const K: usize,
const ID: usize,
> Default for QuadTreePlugin<P, N, D, W, H, X, Y, K, ID>
where
P: TrackingPair,
{
fn default() -> Self {
Self {
_marker: std::marker::PhantomData,
}
}
}
impl<
P,
const N: usize,
const D: usize,
const W: usize,
const H: usize,
const X: usize,
const Y: usize,
const K: usize,
const ID: usize,
> Plugin for QuadTreePlugin<P, N, D, W, H, X, Y, K, ID>
where
P: TrackingPair,
{
fn build(&self, app: &mut App) {
assert!(N > 0, "N should > 0");
assert!(D > 0, "D should > 0");
assert!(W > 0, "W should > 0");
assert!(H > 0, "H should > 0");
assert!(K >= 10, "K should >= 10");
app.insert_resource(QuadTree::<ID>::new(N, D, W, H, X, Y, K))
.add_systems(PreUpdate, P::update_collision())
.add_systems(Update, P::update_quadtree::<ID>());
#[cfg(feature = "gizmos")]
app.add_systems(PostUpdate, P::show_boundary::<ID>());
}
}
pub trait TrackingPair: Send + Sync + 'static {
fn update_collision() -> ScheduleConfigs<ScheduleSystem>;
fn update_quadtree<const ID: usize>() -> ScheduleConfigs<ScheduleSystem>;
#[cfg(feature = "gizmos")]
fn show_boundary<const ID: usize>() -> ScheduleConfigs<ScheduleSystem>;
#[cfg(debug_assertions)]
fn shape_id() -> std::any::TypeId;
}
macro_rules! impl_tracking_pair {
($c: ty) => {
impl<S> TrackingPair for (S, $c)
where
S: Component<Mutability = Mutable> + AsDynCollision + UpdateCollision<$c> + Clone,
{
fn update_collision() -> ScheduleConfigs<ScheduleSystem> {
update_collision::<S, $c>.ambiguous_with_all()
}
fn update_quadtree<const ID: usize>() -> ScheduleConfigs<ScheduleSystem> {
update_quadtree::<S, ID>.ambiguous_with_all()
}
#[cfg(feature = "gizmos")]
fn show_boundary<const ID: usize>() -> ScheduleConfigs<ScheduleSystem> {
use crate::system::show_boundary;
show_boundary::<S, ID>.ambiguous_with_all()
}
#[cfg(debug_assertions)]
fn shape_id() -> std::any::TypeId {
std::any::TypeId::of::<S>()
}
}
};
}
impl_tracking_pair!(GlobalTransform);
#[cfg(feature = "sprite")]
impl_tracking_pair!(Sprite);
macro_rules! impl_tracking_pair_tuple {
($($c: ty),+) => {
impl<S> TrackingPair for (S, ($($c),+,))
where
S: Component<Mutability = Mutable> + AsDynCollision + $(UpdateCollision<$c>+)+ Clone,
{
fn update_collision() -> ScheduleConfigs<ScheduleSystem> {
($(update_collision::<S, $c>),+,).chain()
}
fn update_quadtree<const ID: usize>(
) -> ScheduleConfigs<ScheduleSystem> {
update_quadtree::<S, ID>.ambiguous_with_all()
}
#[cfg(feature = "gizmos")]
fn show_boundary<const ID: usize>(
) -> ScheduleConfigs<ScheduleSystem> {
use crate::system::show_boundary;
show_boundary::<S, ID>.ambiguous_with_all()
}
#[cfg(debug_assertions)]
fn shape_id() -> std::any::TypeId {
std::any::TypeId::of::<S>()
}
}
};
}
impl_tracking_pair_tuple!(GlobalTransform);
#[cfg(feature = "sprite")]
impl_tracking_pair_tuple!(Sprite);
#[cfg(feature = "sprite")]
impl_tracking_pair_tuple!(Sprite, GlobalTransform);
#[cfg(feature = "sprite")]
impl_tracking_pair_tuple!(GlobalTransform, Sprite);
macro_rules! impl_tracking_pairs {
($($i: literal),+) => {
paste::paste! {
impl<$([<P $i>]),+> TrackingPair for ($([<P $i>]),+,)
where
$([<P $i>]: TrackingPair),+
{
fn update_collision() -> ScheduleConfigs<ScheduleSystem> {
($([<P $i>]::update_collision()),+,).ambiguous_with_all()
}
fn update_quadtree<const ID: usize>(
) -> ScheduleConfigs<ScheduleSystem> {
#[cfg(debug_assertions)]
{
let mut set = std::collections::HashMap::new();
$(
if let Some(dup) = set.insert([<P $i>]::shape_id(), std::any::type_name::<[<P $i>]>()) {
panic!("Duplicated quadtree updating system added:\n<{}>\n<{}>\nThey have the same collision shape, merge them into one or use `ID` type parameter of shape.", dup, std::any::type_name::<[<P $i>]>());
}
);+
}
($([<P $i>]::update_quadtree::<ID>()),+,).ambiguous_with_all()
}
#[cfg(feature = "gizmos")]
fn show_boundary<const ID: usize>(
) -> ScheduleConfigs<ScheduleSystem> {
#[cfg(debug_assertions)]
{
let mut set = std::collections::HashMap::new();
$(
if let Some(dup) = set.insert([<P $i>]::shape_id(), std::any::type_name::<[<P $i>]>()) {
panic!("Duplicated gizmos box updating system added:\n<{}>\n<{}>\nThey have the same collision shape, merge them into one or use `ID` type parameter of shape.", dup, std::any::type_name::<[<P $i>]>());
}
);+
}
($([<P $i>]::show_boundary::<ID>()),+,).ambiguous_with_all()
}
#[cfg(debug_assertions)]
fn shape_id() -> std::any::TypeId {
unreachable!("only (S, C) and ((S1, C), (S2, C), ...) are allowed")
}
}
}
};
}
impl_tracking_pairs!(0);
impl_tracking_pairs!(0, 1);
impl_tracking_pairs!(0, 1, 2);
impl_tracking_pairs!(0, 1, 2, 3);
impl_tracking_pairs!(0, 1, 2, 3, 4);
impl_tracking_pairs!(0, 1, 2, 3, 4, 5);
impl_tracking_pairs!(0, 1, 2, 3, 4, 5, 6);
impl_tracking_pairs!(0, 1, 2, 3, 4, 5, 6, 7);
impl_tracking_pairs!(0, 1, 2, 3, 4, 5, 6, 7, 8);
#[cfg(test)]
mod tests {
use super::*;
#[allow(unused_imports)]
use crate::{CollisionCircle, CollisionRect, CollisionRotatedRect};
#[test]
#[should_panic(expected = "Duplicated quadtree updating system added")]
fn duplicate_shape1() {
App::new().add_plugins(QuadTreePlugin::<
(
(CollisionCircle, GlobalTransform),
(CollisionCircle, GlobalTransform),
),
40,
4,
100,
100,
0,
0,
20,
>::default());
}
#[cfg(feature = "sprite")]
#[test]
#[should_panic(expected = "Duplicated quadtree updating system added")]
fn duplicate_shape2() {
App::new().add_plugins(QuadTreePlugin::<
((CollisionRect, GlobalTransform), (CollisionRect, Sprite)),
40,
4,
100,
100,
0,
0,
20,
>::default());
}
#[cfg(feature = "sprite")]
#[test]
#[should_panic(expected = "Duplicated quadtree updating system added")]
fn duplicate_shape3() {
App::new().add_plugins(QuadTreePlugin::<
(
(CollisionCircle, GlobalTransform),
(CollisionRect, Sprite),
(CollisionRotatedRect, Sprite),
(CollisionRotatedRect, (GlobalTransform, Sprite)),
),
40,
4,
100,
100,
0,
0,
20,
>::default());
}
#[cfg(all(feature = "sprite", feature = "gizmos"))]
#[test]
fn plugin_test() {
App::new().add_plugins(QuadTreePlugin::<
(
(CollisionCircle, GlobalTransform),
(CollisionRotatedRect, Sprite),
(CollisionRect, (GlobalTransform, Sprite)),
),
40,
4,
100,
100,
0,
0,
20,
>::default());
}
#[test]
fn plugin_test_with_id() {
App::new().add_plugins(QuadTreePlugin::<
(
(CollisionCircle<0>, GlobalTransform),
(CollisionCircle<1>, GlobalTransform),
),
40,
4,
100,
100,
0,
0,
20,
>::default());
}
#[cfg(feature = "sprite")]
#[test]
fn plugin_test_tuple() {
App::new().add_plugins(QuadTreePlugin::<
(
(CollisionRect<0>, (GlobalTransform, Sprite)),
(CollisionRect<1>, GlobalTransform),
),
40,
4,
100,
100,
0,
0,
20,
>::default());
}
}