use anput::{
entity::Entity,
query::{DynamicQueryFilter, DynamicQueryItem, TypedLookupFetch},
scheduler::GraphSchedulerPlugin,
systems::SystemContext,
universe::Res,
world::World,
};
use rstar::{Envelope, PointDistance, RTree, RTreeObject, primitives::GeomWithData};
use std::error::Error;
pub mod third_party {
pub use rstar;
}
pub fn make_plugin<const LOCKING: bool, Extractor: SpatialExtractor>()
-> GraphSchedulerPlugin<LOCKING> {
GraphSchedulerPlugin::<LOCKING>::default()
.resource(SpatialPartitioning::<Extractor>::default())
.system_setup(spatial_partitioning::<LOCKING, Extractor>, |system| {
system.name(format!(
"spatial_partitioning:{}",
std::any::type_name::<Extractor>()
))
})
}
pub struct SpatialPartitioning<Extractor: SpatialExtractor> {
tree: RTree<GeomWithData<Extractor::SpatialObject, Entity>>,
}
impl<Extractor: SpatialExtractor> Default for SpatialPartitioning<Extractor> {
fn default() -> Self {
Self {
tree: RTree::default(),
}
}
}
impl<Extractor: SpatialExtractor> SpatialPartitioning<Extractor> {
pub fn rebuild<const LOCKING: bool>(&mut self, world: &World) {
self.tree = RTree::bulk_load(
Extractor::extract::<LOCKING>(world)
.map(|(entity, object)| GeomWithData::new(object, entity))
.collect::<Vec<_>>(),
);
}
pub fn tree(&self) -> &RTree<GeomWithData<Extractor::SpatialObject, Entity>> {
&self.tree
}
pub fn iter(&self) -> impl Iterator<Item = &GeomWithData<Extractor::SpatialObject, Entity>> {
self.tree.iter()
}
pub fn nearest_entities(
&self,
point: &<<Extractor::SpatialObject as RTreeObject>::Envelope as Envelope>::Point,
) -> impl Iterator<Item = Entity> + '_ {
self.tree.nearest_neighbor_iter(point).map(|geom| geom.data)
}
pub fn locate_contained_entities(
&self,
envelope: &<Extractor::SpatialObject as RTreeObject>::Envelope,
) -> impl Iterator<Item = Entity> + '_ {
self.tree.locate_in_envelope(envelope).map(|geom| geom.data)
}
pub fn locate_intersecting_entities(
&self,
envelope: &<Extractor::SpatialObject as RTreeObject>::Envelope,
) -> impl Iterator<Item = Entity> + '_ {
self.tree
.locate_in_envelope_intersecting(envelope)
.map(|geom| geom.data)
}
pub fn nearest_query<'a, const LOCKING: bool, Fetch: TypedLookupFetch<'a, LOCKING>>(
&'a self,
world: &'a World,
point: &<<Extractor::SpatialObject as RTreeObject>::Envelope as Envelope>::Point,
) -> impl Iterator<Item = Fetch::Value> {
world.lookup::<LOCKING, Fetch>(self.nearest_entities(point))
}
pub fn nearest_dynamic_query<'a, const LOCKING: bool>(
&'a self,
world: &'a World,
point: &<<Extractor::SpatialObject as RTreeObject>::Envelope as Envelope>::Point,
filter: &DynamicQueryFilter,
) -> impl Iterator<Item = DynamicQueryItem<'a>> {
world.dynamic_lookup::<LOCKING>(filter, self.nearest_entities(point))
}
pub fn locate_contained_query<'a, const LOCKING: bool, Fetch: TypedLookupFetch<'a, LOCKING>>(
&'a self,
world: &'a World,
envelope: &<Extractor::SpatialObject as RTreeObject>::Envelope,
) -> impl Iterator<Item = Fetch::Value> {
world.lookup::<LOCKING, Fetch>(self.locate_contained_entities(envelope))
}
pub fn locate_contained_dynamic_query<'a, const LOCKING: bool>(
&'a self,
world: &'a World,
envelope: &<Extractor::SpatialObject as RTreeObject>::Envelope,
filter: &DynamicQueryFilter,
) -> impl Iterator<Item = DynamicQueryItem<'a>> {
world.dynamic_lookup::<LOCKING>(filter, self.locate_contained_entities(envelope))
}
pub fn locate_intersecting_query<
'a,
const LOCKING: bool,
Fetch: TypedLookupFetch<'a, LOCKING>,
>(
&'a self,
world: &'a World,
envelope: &<Extractor::SpatialObject as RTreeObject>::Envelope,
) -> impl Iterator<Item = Fetch::Value> {
world.lookup::<LOCKING, Fetch>(self.locate_intersecting_entities(envelope))
}
pub fn locate_intersecting_dynamic_query<'a, const LOCKING: bool>(
&'a self,
world: &'a World,
envelope: &<Extractor::SpatialObject as RTreeObject>::Envelope,
filter: &DynamicQueryFilter,
) -> impl Iterator<Item = DynamicQueryItem<'a>> {
world.dynamic_lookup::<LOCKING>(filter, self.locate_intersecting_entities(envelope))
}
}
pub fn spatial_partitioning<const LOCKING: bool, Extractor: SpatialExtractor>(
context: SystemContext,
) -> Result<(), Box<dyn Error>> {
let (world, mut partitioning) =
context.fetch::<(&World, Res<LOCKING, &mut SpatialPartitioning<Extractor>>)>()?;
partitioning.rebuild::<LOCKING>(world);
Ok(())
}
pub trait SpatialExtractor: 'static
where
<<Self as SpatialExtractor>::SpatialObject as RTreeObject>::Envelope: Send + Sync,
{
type SpatialObject: RTreeObject + PointDistance + Send + Sync;
fn extract<const LOCKING: bool>(
world: &World,
) -> impl Iterator<Item = (Entity, Self::SpatialObject)>;
}