1
+ use crate :: {
2
+ components:: BoundingBox ,
3
+ { Point , Arc } ,
4
+ algorithms:: { Bounded } ,
5
+ } ;
6
+ use specs:: { Entity , world:: Index } ;
7
+ use aabb_quadtree:: { QuadTree , Spatial , ItemId } ;
8
+ use quadtree_euclid:: { TypedRect , TypedPoint2D , TypedSize2D } ;
9
+ use std:: collections:: HashMap ;
10
+ use euclid:: Angle ;
11
+
12
+ pub ( crate ) type SpatialTree = QuadTree < SpatialEntity , f64 , [ ( ItemId , TypedRect < f32 , f64 > ) ; 0 ] > ;
13
+
14
+ /// A intermediate struct that maps an [`Entity`] to its [`BoundingBox`]
15
+ ///
16
+ /// This is used to populate an efficient spatial lookup structure like a `QuadTree`
17
+ #[ derive( Debug , Copy , Clone ) ]
18
+ pub struct SpatialEntity {
19
+ pub bounds : BoundingBox ,
20
+ pub entity : Entity
21
+ }
22
+
23
+ impl Spatial < f64 > for SpatialEntity {
24
+ fn aabb ( & self ) -> TypedRect < f32 , f64 > {
25
+ let bb = self . bounds ;
26
+ TypedRect :: < f32 , f64 > :: new (
27
+ // TypedRects have their origin at the bottom left corner (this is undocumented!)
28
+ TypedPoint2D :: new ( bb. bottom_left ( ) . x as f32 , bb. bottom_left ( ) . y as f32 ) ,
29
+ TypedSize2D :: new ( bb. width ( ) . 0 as f32 , bb. height ( ) . 0 as f32 ) )
30
+ }
31
+ }
32
+
33
+ impl SpatialEntity {
34
+ pub fn new ( bounds : BoundingBox , entity : Entity ) -> SpatialEntity {
35
+ SpatialEntity {
36
+ bounds,
37
+ entity
38
+ }
39
+ }
40
+ }
41
+
42
+ /// A global [`Resource`] for looking up which [`Entity`]s inhabit
43
+ /// a given spatial point or region
44
+ #[ derive( Debug ) ]
45
+ pub struct Space {
46
+ quadtree : SpatialTree ,
47
+ ids : HashMap < Entity , ItemId >
48
+ }
49
+
50
+ impl Default for Space {
51
+ fn default ( ) -> Self {
52
+ Space {
53
+ quadtree : Self :: default_tree ( ) ,
54
+ ids : HashMap :: new ( )
55
+ }
56
+ }
57
+ }
58
+
59
+ impl Space {
60
+ // FIXME: Hard-code is bad-bad
61
+ pub const WORLD_RADIUS : f64 = 1_000_000.0 ;
62
+ const TREE_ALLOW_DUPLICATES : bool = true ;
63
+ const TREE_MIN_CHILDREN : usize = 4 ;
64
+ const TREE_MAX_CHILDREN : usize = 16 ;
65
+ const TREE_MAX_DEPTH : usize = 8 ;
66
+ const TREE_SIZE_HINT : usize = 4 ;
67
+
68
+ fn default_tree ( ) -> SpatialTree {
69
+ // Initialize quadtree
70
+ let size = BoundingBox :: new (
71
+ Point :: new ( -Self :: WORLD_RADIUS , -Self :: WORLD_RADIUS ) ,
72
+ Point :: new ( Self :: WORLD_RADIUS , Self :: WORLD_RADIUS )
73
+ ) . aabb ( ) ;
74
+ let quadtree: SpatialTree = QuadTree :: new (
75
+ size,
76
+ Self :: TREE_ALLOW_DUPLICATES ,
77
+ Self :: TREE_MIN_CHILDREN ,
78
+ Self :: TREE_MAX_CHILDREN ,
79
+ Self :: TREE_MAX_DEPTH ,
80
+ Self :: TREE_SIZE_HINT ,
81
+ ) ;
82
+
83
+ quadtree
84
+ }
85
+
86
+ fn tree_with_world_size ( size : impl Spatial < f64 > ) -> SpatialTree {
87
+ let quadtree: SpatialTree = QuadTree :: new (
88
+ size. aabb ( ) ,
89
+ Self :: TREE_ALLOW_DUPLICATES ,
90
+ Self :: TREE_MIN_CHILDREN ,
91
+ Self :: TREE_MAX_CHILDREN ,
92
+ Self :: TREE_MAX_DEPTH ,
93
+ Self :: TREE_SIZE_HINT ,
94
+ ) ;
95
+
96
+ quadtree
97
+ }
98
+
99
+ /// Modifies the spatial position of the given [`SpatialEntity`] inside of [`Space`]
100
+ /// If the [`SpatialEntity`] is not already inside of [`Space`] it will be inserted.
101
+ pub fn modify ( & mut self , spatial : SpatialEntity ) {
102
+ if !self . quadtree . bounding_box ( ) . contains_rect ( & spatial. bounds . aabb ( ) ) {
103
+ self . resize ( spatial. bounds ) ;
104
+ }
105
+ let id = if self . ids . contains_key ( & spatial. entity ) {
106
+ self . modify_entity ( spatial)
107
+ }
108
+ else {
109
+ self . insert_entity ( spatial)
110
+ } ;
111
+ // Update hashmap
112
+ self . ids . entry ( spatial. entity ) . or_insert ( id) ;
113
+ }
114
+
115
+ fn insert_entity ( & mut self , spatial : SpatialEntity ) -> ItemId {
116
+ if let Some ( id) = self . quadtree . insert ( spatial) {
117
+ id
118
+ }
119
+ else {
120
+ panic ! ( "ERROR: Failed to insert {:?} into Space!" , self )
121
+ }
122
+ }
123
+
124
+ fn modify_entity ( & mut self , spatial : SpatialEntity ) -> ItemId {
125
+ let item_id = self . ids [ & spatial. entity ] ;
126
+ // remove old item
127
+ self . quadtree . remove ( item_id) ;
128
+
129
+ // Add modified
130
+ self . insert_entity ( spatial)
131
+ }
132
+
133
+ /// Removes the given [`Entity`] from this [`Space`]
134
+ pub fn remove ( & mut self , entity : Entity ) {
135
+ if self . ids . contains_key ( & entity) {
136
+ let item_id = self . ids [ & entity] ;
137
+
138
+ // remove old item
139
+ self . quadtree . remove ( item_id) ;
140
+ self . ids . remove ( & entity) ;
141
+ }
142
+ }
143
+
144
+ /// Removes an [`Entity`] from this [`Space`] given its [`Index`]
145
+ pub fn remove_by_id ( & mut self , id : Index ) {
146
+ let filter = move |( ent, _item_id) : ( & Entity , & ItemId ) | {
147
+ if ent. id ( ) == id {
148
+ Some ( * ent)
149
+ } else {
150
+ None
151
+ }
152
+ } ;
153
+
154
+ if let Some ( ent) = self . ids . iter ( ) . filter_map ( filter) . next ( ) {
155
+ self . remove ( ent) ;
156
+ }
157
+ }
158
+
159
+ /// Returns an iterator over all [`SpatialEntity`] in this [`Space`]
160
+ pub fn iter < ' this > (
161
+ & ' this self ,
162
+ ) -> impl Iterator < Item = SpatialEntity > + ' this {
163
+ self . quadtree . iter ( ) . map ( |( _, ( ent, _) ) | * ent)
164
+ }
165
+
166
+ pub fn len ( & self ) -> usize {
167
+ self . quadtree . len ( )
168
+ }
169
+
170
+ pub fn is_empty ( & self ) -> bool {
171
+ self . quadtree . is_empty ( )
172
+ }
173
+
174
+ // FIXME: radius in CanvasSpace in method signature
175
+ /// Performs a spatial query in an radius around a given [`Point`]
176
+ /// Returns an iterator with all [`SpatialEntity`] inhabiting the [`Space`]
177
+ /// close to the given point
178
+ /// The returned iterator can be empty
179
+ pub fn query_point < ' this > (
180
+ & ' this self , point : Point , radius : f64
181
+ ) -> impl Iterator < Item = SpatialEntity > + ' this {
182
+ let cursor_circle = Arc :: from_centre_radius (
183
+ point,
184
+ radius,
185
+ Angle :: radians ( 0.0 ) ,
186
+ Angle :: radians ( 2.0 * std:: f64:: consts:: PI )
187
+ ) ;
188
+ self . query_region ( cursor_circle. bounding_box ( ) )
189
+ }
190
+
191
+ /// Performs a spatial query for a given [`BoundingBox`]
192
+ /// Returns an iterator with all [`SpatialEntity`] inhabiting the [`Space`]
193
+ /// of the given BoundingBox
194
+ /// The returned iterator can be empty
195
+ pub fn query_region < ' this > (
196
+ & ' this self , region : BoundingBox
197
+ ) -> impl Iterator < Item = SpatialEntity > + ' this {
198
+ self . quadtree . query ( region. aabb ( ) ) . into_iter ( ) . map ( |q| * q. 0 )
199
+ }
200
+
201
+ /// Clears the [`Space`] of all [`SpatialEntity`]
202
+ pub fn clear ( & mut self ) {
203
+ // Re-use old size
204
+ let size = self . quadtree . bounding_box ( ) ;
205
+ self . quadtree = Self :: tree_with_world_size ( size) ;
206
+ self . ids . clear ( ) ;
207
+ }
208
+
209
+ /// Resizes the inner quadtree to the given **bigger** size
210
+ ///
211
+ /// # Panics
212
+ /// Panics if the size given is not bigger then the initial bounding_box of the [`Space`]
213
+ pub fn resize ( & mut self , size : impl Spatial < f64 > ) {
214
+ if self . quadtree . bounding_box ( ) . contains_rect ( & size. aabb ( ) ) {
215
+ panic ! ( "Space.resize() ERROR: Size to resize to is smaller then the tree!" )
216
+ }
217
+ let spatial_entities: Vec < _ > = self . iter ( ) . collect ( ) ;
218
+
219
+ self . clear ( ) ;
220
+
221
+ self . quadtree = Self :: tree_with_world_size ( size) ;
222
+ for spatial_entity in spatial_entities {
223
+ let item_id = self . insert_entity ( spatial_entity) ;
224
+ self . ids . insert ( spatial_entity. entity , item_id) ;
225
+ }
226
+ }
227
+ }
228
+
229
+ #[ cfg( test) ]
230
+ mod tests {
231
+ use crate :: {
232
+ components:: { BoundingBox , Space } ,
233
+ Point ,
234
+ } ;
235
+
236
+ #[ test]
237
+ fn space_should_resize ( ) {
238
+ let mut space = Space :: default ( ) ;
239
+ assert_eq ! ( space. quadtree. bounding_box( ) . max_x( ) as f64 , Space :: WORLD_RADIUS ) ;
240
+ let new_radius = 2_000_000.0 ;
241
+ let new_size = BoundingBox :: new (
242
+ Point :: new ( -new_radius, -new_radius) ,
243
+ Point :: new ( new_radius, new_radius) ) ;
244
+ space. resize ( new_size) ;
245
+ assert_eq ! ( space. quadtree. bounding_box( ) . max_x( ) as f64 , new_radius) ;
246
+ }
247
+ }
0 commit comments