1use anput::{
2 entity::Entity,
3 query::{DynamicQueryFilter, DynamicQueryItem, TypedLookupFetch},
4 scheduler::GraphSchedulerPlugin,
5 systems::SystemContext,
6 universe::Res,
7 world::World,
8};
9use rstar::{Envelope, PointDistance, RTree, RTreeObject, primitives::GeomWithData};
10use std::error::Error;
11
12pub mod third_party {
13 pub use rstar;
14}
15
16pub fn make_plugin<const LOCKING: bool, Extractor: SpatialExtractor>()
17-> GraphSchedulerPlugin<LOCKING> {
18 GraphSchedulerPlugin::<LOCKING>::default()
19 .resource(SpatialPartitioning::<Extractor>::default())
20 .system_setup(spatial_partitioning::<LOCKING, Extractor>, |system| {
21 system.name(format!(
22 "spatial_partitioning:{}",
23 std::any::type_name::<Extractor>()
24 ))
25 })
26}
27
28pub struct SpatialPartitioning<Extractor: SpatialExtractor> {
29 tree: RTree<GeomWithData<Extractor::SpatialObject, Entity>>,
30}
31
32impl<Extractor: SpatialExtractor> Default for SpatialPartitioning<Extractor> {
33 fn default() -> Self {
34 Self {
35 tree: RTree::default(),
36 }
37 }
38}
39
40impl<Extractor: SpatialExtractor> SpatialPartitioning<Extractor> {
41 pub fn rebuild<const LOCKING: bool>(&mut self, world: &World) {
42 self.tree = RTree::bulk_load(
43 Extractor::extract::<LOCKING>(world)
44 .map(|(entity, object)| GeomWithData::new(object, entity))
45 .collect::<Vec<_>>(),
46 );
47 }
48
49 pub fn tree(&self) -> &RTree<GeomWithData<Extractor::SpatialObject, Entity>> {
50 &self.tree
51 }
52
53 pub fn iter(&self) -> impl Iterator<Item = &GeomWithData<Extractor::SpatialObject, Entity>> {
54 self.tree.iter()
55 }
56
57 pub fn nearest_entities(
58 &self,
59 point: &<<Extractor::SpatialObject as RTreeObject>::Envelope as Envelope>::Point,
60 ) -> impl Iterator<Item = Entity> + '_ {
61 self.tree.nearest_neighbor_iter(point).map(|geom| geom.data)
62 }
63
64 pub fn locate_contained_entities(
65 &self,
66 envelope: &<Extractor::SpatialObject as RTreeObject>::Envelope,
67 ) -> impl Iterator<Item = Entity> + '_ {
68 self.tree.locate_in_envelope(envelope).map(|geom| geom.data)
69 }
70
71 pub fn locate_intersecting_entities(
72 &self,
73 envelope: &<Extractor::SpatialObject as RTreeObject>::Envelope,
74 ) -> impl Iterator<Item = Entity> + '_ {
75 self.tree
76 .locate_in_envelope_intersecting(envelope)
77 .map(|geom| geom.data)
78 }
79
80 pub fn nearest_query<'a, const LOCKING: bool, Fetch: TypedLookupFetch<'a, LOCKING>>(
81 &'a self,
82 world: &'a World,
83 point: &<<Extractor::SpatialObject as RTreeObject>::Envelope as Envelope>::Point,
84 ) -> impl Iterator<Item = Fetch::Value> {
85 world.lookup::<LOCKING, Fetch>(self.nearest_entities(point))
86 }
87
88 pub fn nearest_dynamic_query<'a, const LOCKING: bool>(
89 &'a self,
90 world: &'a World,
91 point: &<<Extractor::SpatialObject as RTreeObject>::Envelope as Envelope>::Point,
92 filter: &DynamicQueryFilter,
93 ) -> impl Iterator<Item = DynamicQueryItem<'a>> {
94 world.dynamic_lookup::<LOCKING>(filter, self.nearest_entities(point))
95 }
96
97 pub fn locate_contained_query<'a, const LOCKING: bool, Fetch: TypedLookupFetch<'a, LOCKING>>(
98 &'a self,
99 world: &'a World,
100 envelope: &<Extractor::SpatialObject as RTreeObject>::Envelope,
101 ) -> impl Iterator<Item = Fetch::Value> {
102 world.lookup::<LOCKING, Fetch>(self.locate_contained_entities(envelope))
103 }
104
105 pub fn locate_contained_dynamic_query<'a, const LOCKING: bool>(
106 &'a self,
107 world: &'a World,
108 envelope: &<Extractor::SpatialObject as RTreeObject>::Envelope,
109 filter: &DynamicQueryFilter,
110 ) -> impl Iterator<Item = DynamicQueryItem<'a>> {
111 world.dynamic_lookup::<LOCKING>(filter, self.locate_contained_entities(envelope))
112 }
113
114 pub fn locate_intersecting_query<
115 'a,
116 const LOCKING: bool,
117 Fetch: TypedLookupFetch<'a, LOCKING>,
118 >(
119 &'a self,
120 world: &'a World,
121 envelope: &<Extractor::SpatialObject as RTreeObject>::Envelope,
122 ) -> impl Iterator<Item = Fetch::Value> {
123 world.lookup::<LOCKING, Fetch>(self.locate_intersecting_entities(envelope))
124 }
125
126 pub fn locate_intersecting_dynamic_query<'a, const LOCKING: bool>(
127 &'a self,
128 world: &'a World,
129 envelope: &<Extractor::SpatialObject as RTreeObject>::Envelope,
130 filter: &DynamicQueryFilter,
131 ) -> impl Iterator<Item = DynamicQueryItem<'a>> {
132 world.dynamic_lookup::<LOCKING>(filter, self.locate_intersecting_entities(envelope))
133 }
134}
135
136pub fn spatial_partitioning<const LOCKING: bool, Extractor: SpatialExtractor>(
137 context: SystemContext,
138) -> Result<(), Box<dyn Error>> {
139 let (world, mut partitioning) =
140 context.fetch::<(&World, Res<LOCKING, &mut SpatialPartitioning<Extractor>>)>()?;
141
142 partitioning.rebuild::<LOCKING>(world);
143
144 Ok(())
145}
146
147pub trait SpatialExtractor: 'static
148where
149 <<Self as SpatialExtractor>::SpatialObject as RTreeObject>::Envelope: Send + Sync,
150{
151 type SpatialObject: RTreeObject + PointDistance + Send + Sync;
152
153 fn extract<const LOCKING: bool>(
154 world: &World,
155 ) -> impl Iterator<Item = (Entity, Self::SpatialObject)>;
156}