Skip to content

Commit 1658c43

Browse files
authored
de_index: Prep for faster indexing alternative (#772)
Currently, only collider based spatial indexing is implemented. It allows reasonably fast spatial queries, for example ray casting or AABB search applied on each entity collider. It is the plan to extend `de_index` with point based indexing (as opposed to collider based indexing). Point based spatial queries have to potential to be much faster and yet sufficient for some important use-cases (i.e. energy grid graph construction). This PR reorganizes `de_index` so that we can add the alternative indexing alongside it. Relates to #472.
1 parent 4b89fc3 commit 1658c43

File tree

9 files changed

+65
-63
lines changed

9 files changed

+65
-63
lines changed

crates/index/src/lib.rs

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,14 @@
11
#![allow(rustdoc::private_intra_doc_links)]
2-
//! This module implements 2D object partitioning for fast geometric lookup,
3-
//! for example ray casting.
4-
//!
5-
//! The core structure is a square tile grid which points to Bevy ECS entities.
6-
//! Newly spawned entities are automatically added, despawned entities removed
7-
//! and moved entities updated by systems added by
8-
//! [`self::IndexPlugin`].
9-
mod aabb;
10-
mod collider;
11-
mod grid;
12-
mod index;
13-
mod range;
14-
mod segment;
15-
mod systems;
2+
//! This crate implements spatial indexing and various spatial queries of game
3+
//! entities.
164
17-
use bevy::{app::PluginGroupBuilder, prelude::PluginGroup};
18-
use systems::IndexPlugin;
5+
mod precise;
196

20-
pub use self::{
21-
collider::{ColliderWithCache, LocalCollider, QueryCollider},
22-
index::{EntityIndex, RayEntityIntersection, SpatialQuery},
23-
systems::IndexSet,
7+
use bevy::{app::PluginGroupBuilder, prelude::PluginGroup};
8+
use precise::PreciseIndexPlugin;
9+
pub use precise::{
10+
ColliderWithCache, EntityIndex, LocalCollider, PreciseIndexSet, QueryCollider,
11+
RayEntityIntersection, SpatialQuery,
2412
};
2513

2614
/// Size (in world-space) of a single square tile where entities are kept.
@@ -30,6 +18,6 @@ pub struct IndexPluginGroup;
3018

3119
impl PluginGroup for IndexPluginGroup {
3220
fn build(self) -> PluginGroupBuilder {
33-
PluginGroupBuilder::start::<Self>().add(IndexPlugin)
21+
PluginGroupBuilder::start::<Self>().add(PreciseIndexPlugin)
3422
}
3523
}

crates/index/src/aabb.rs renamed to crates/index/src/precise/aabb.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ use ahash::AHashSet;
22
use bevy::prelude::Entity;
33
use parry3d::bounding_volume::Aabb;
44

5-
use crate::{grid::TileGrid, range::TileRange};
5+
use super::{grid::TileGrid, range::TileRange};
66

77
/// An iterator over unique entity IDs withing a box.
8-
pub(crate) struct AabbCandidates<'a> {
8+
pub(super) struct AabbCandidates<'a> {
99
grid: &'a TileGrid,
1010
tiles: TileRange,
1111
row: Option<i32>,
@@ -16,7 +16,7 @@ pub(crate) struct AabbCandidates<'a> {
1616
impl<'a> AabbCandidates<'a> {
1717
/// Creates a new iterator of entities potentially colliding with a given
1818
/// AABB.
19-
pub(crate) fn new(grid: &'a TileGrid, aabb: &Aabb) -> Self {
19+
pub(super) fn new(grid: &'a TileGrid, aabb: &Aabb) -> Self {
2020
Self {
2121
grid,
2222
tiles: TileRange::from_aabb(aabb),

crates/index/src/collider.rs renamed to crates/index/src/precise/collider.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,20 @@ impl LocalCollider {
4343
}
4444

4545
/// Updates position of cached world-space AABB of the collider.
46-
pub(crate) fn update_position(&mut self, position: Isometry<f32>) {
46+
pub(super) fn update_position(&mut self, position: Isometry<f32>) {
4747
self.world_aabb = self.local_aabb.transform_by(&position);
4848
self.position = position;
4949
}
5050

51-
pub(crate) fn cast_ray(&self, ray: &Ray, max_toi: f32) -> Option<f32> {
51+
pub(super) fn cast_ray(&self, ray: &Ray, max_toi: f32) -> Option<f32> {
5252
if self.world_aabb.intersects_local_ray(ray, max_toi) {
5353
self.object_collider.cast_ray(&self.position, ray, max_toi)
5454
} else {
5555
None
5656
}
5757
}
5858

59-
pub(crate) fn intersects(&self, rhs: &impl ColliderWithCache) -> bool {
59+
pub(super) fn intersects(&self, rhs: &impl ColliderWithCache) -> bool {
6060
if self.query_aabb(rhs.world_aabb()) {
6161
self.object_collider
6262
.intersects(&self.position, rhs.inner(), rhs.position())
@@ -67,7 +67,7 @@ impl LocalCollider {
6767

6868
/// Returns true if world-space axis-aligned bounding boxes of the two
6969
/// colliders intersect.
70-
pub(crate) fn query_aabb(&self, aabb: &Aabb) -> bool {
70+
pub(super) fn query_aabb(&self, aabb: &Aabb) -> bool {
7171
self.world_aabb.intersects(aabb)
7272
}
7373
}

crates/index/src/grid.rs renamed to crates/index/src/precise/grid.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,20 @@ use bevy::prelude::Entity;
66
use glam::IVec2;
77
use parry3d::bounding_volume::Aabb;
88

9-
use crate::range::TileRange;
9+
use super::range::TileRange;
1010

1111
/// Rectangular (2D) grid of sets of Bevy ECS entities.
1212
///
1313
/// Only non-empty sets are kept (a hash map mapping 2D tile coordinates to
1414
/// Entity sets is used under the hood). Each set contains entities whose
1515
/// absolute AABB intersects with the tile.
16-
pub(crate) struct TileGrid {
16+
pub(super) struct TileGrid {
1717
tiles: AHashMap<IVec2, AHashSet<Entity>>,
1818
}
1919

2020
impl TileGrid {
2121
/// Creates a new empty grid.
22-
pub(crate) fn new() -> Self {
22+
pub(super) fn new() -> Self {
2323
Self {
2424
tiles: AHashMap::new(),
2525
}
@@ -36,7 +36,7 @@ impl TileGrid {
3636
/// # Panics
3737
///
3838
/// Might panic if the entity is already present in the grid.
39-
pub(crate) fn insert(&mut self, entity: Entity, aabb: &Aabb) {
39+
pub(super) fn insert(&mut self, entity: Entity, aabb: &Aabb) {
4040
for tile in TileRange::from_aabb(aabb) {
4141
self.insert_to_tile(entity, tile);
4242
}
@@ -56,7 +56,7 @@ impl TileGrid {
5656
///
5757
/// Might panic if the entity is not stored in the grid or if the last used
5858
/// update / insertion AABB differs from the one passed as an argument.
59-
pub(crate) fn remove(&mut self, entity: Entity, aabb: &Aabb) {
59+
pub(super) fn remove(&mut self, entity: Entity, aabb: &Aabb) {
6060
for tile in TileRange::from_aabb(aabb) {
6161
self.remove_from_tile(entity, tile);
6262
}
@@ -77,7 +77,7 @@ impl TileGrid {
7777
///
7878
/// Might panic if the entity is not present in the grid or if `old_aabb`
7979
/// differs from the last used update / insert AABB.
80-
pub(crate) fn update(&mut self, entity: Entity, old_aabb: &Aabb, new_aabb: &Aabb) {
80+
pub(super) fn update(&mut self, entity: Entity, old_aabb: &Aabb, new_aabb: &Aabb) {
8181
let old_tiles = TileRange::from_aabb(old_aabb);
8282
let new_tiles = TileRange::from_aabb(new_aabb);
8383

@@ -107,7 +107,7 @@ impl TileGrid {
107107
/// # Arguments
108108
///
109109
/// `tile_coords` - coordinates of the tile.
110-
pub(crate) fn get_tile_entities(&self, tile_coords: IVec2) -> Option<&AHashSet<Entity>> {
110+
pub(super) fn get_tile_entities(&self, tile_coords: IVec2) -> Option<&AHashSet<Entity>> {
111111
self.tiles.get(&tile_coords)
112112
}
113113

crates/index/src/index.rs renamed to crates/index/src/precise/index.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ use parry3d::{
1818
shape::Segment,
1919
};
2020

21-
use super::{collider::LocalCollider, grid::TileGrid, segment::SegmentCandidates};
22-
use crate::{aabb::AabbCandidates, collider::ColliderWithCache};
21+
use super::{
22+
aabb::AabbCandidates, collider::ColliderWithCache, collider::LocalCollider, grid::TileGrid,
23+
segment::SegmentCandidates,
24+
};
2325

2426
/// 2D rectangular grid based spatial index of entities.
2527
#[derive(Resource)]
@@ -47,15 +49,15 @@ impl EntityIndex {
4749
self.colliders.insert(entity, collider);
4850
}
4951

50-
pub(crate) fn remove(&mut self, entity: Entity) {
52+
pub(super) fn remove(&mut self, entity: Entity) {
5153
let collider = self
5254
.colliders
5355
.remove(&entity)
5456
.expect("Tried to remove non-existent entity.");
5557
self.grid.remove(entity, collider.world_aabb());
5658
}
5759

58-
pub(crate) fn update(&mut self, entity: Entity, position: Isometry<f32>) {
60+
pub(super) fn update(&mut self, entity: Entity, position: Isometry<f32>) {
5961
let collider = self
6062
.colliders
6163
.get_mut(&entity)
@@ -107,7 +109,7 @@ impl Default for EntityIndex {
107109
/// System parameter implementing various spatial queries.
108110
///
109111
/// Only entities automatically indexed by systems from
110-
/// [`super::systems::IndexPlugin`] could be retrieved.
112+
/// [`super::PreciseIndexPlugin`] could be retrieved.
111113
#[derive(SystemParam)]
112114
pub struct SpatialQuery<'w, 's, Q, F = ()>
113115
where
@@ -124,7 +126,7 @@ where
124126
F: ReadOnlyWorldQuery + Sync + Send + 'static,
125127
{
126128
/// Returns closest entity whose shape, as indexed by systems registered by
127-
/// [`super::systems::IndexPlugin`], intersects a given ray.
129+
/// [`super::PreciseIndexPlugin`], intersects a given ray.
128130
///
129131
/// # Arguments
130132
///
@@ -173,7 +175,7 @@ where
173175
}
174176

175177
/// Returns true if queried solid object on the map, as indexed by
176-
/// [`super::systems::IndexPlugin`], intersects with the given collider.
178+
/// [`super::PreciseIndexPlugin`], intersects with the given collider.
177179
pub fn collides(&self, collider: &impl ColliderWithCache) -> bool {
178180
let candidate_sets = self.index.query_aabb(collider.world_aabb());
179181
candidate_sets.flatten().any(|candidate| {

crates/index/src/systems.rs renamed to crates/index/src/precise/mod.rs

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
//! Module with systems and a Bevy plugin for automatic entity indexing of
2-
//! solid entities.
3-
1+
//! This module implements collider based spatial indexing of game entities and
2+
//! various geometry based lookup (for example ray casting).
3+
//!
4+
//! The core structure is a square tile grid which points to Bevy ECS entities.
5+
//! Newly spawned entities are automatically added, despawned entities removed
6+
//! and moved entities updated by systems added by [`PreciseIndexPlugin`].
47
use bevy::prelude::*;
58
use de_core::{
69
gamestate::GameState,
@@ -11,8 +14,17 @@ use de_core::{
1114
use de_objects::SolidObjects;
1215
use parry3d::math::Isometry;
1316

14-
use super::index::EntityIndex;
15-
use crate::collider::LocalCollider;
17+
pub use self::{
18+
collider::{ColliderWithCache, LocalCollider, QueryCollider},
19+
index::{EntityIndex, RayEntityIntersection, SpatialQuery},
20+
};
21+
22+
mod aabb;
23+
mod collider;
24+
mod grid;
25+
mod index;
26+
mod range;
27+
mod segment;
1628

1729
type SolidEntityQuery<'w, 's> = Query<
1830
'w,
@@ -38,29 +50,29 @@ type MovedQuery<'w, 's> =
3850
/// insert newly spawned solid entities to the index, update their position
3951
/// when [`bevy::prelude::Transform`] is changed and remove the entities from
4052
/// the index when they are de-spawned.
41-
pub(crate) struct IndexPlugin;
53+
pub(super) struct PreciseIndexPlugin;
4254

43-
impl Plugin for IndexPlugin {
55+
impl Plugin for PreciseIndexPlugin {
4456
fn build(&self, app: &mut App) {
4557
app.add_systems(OnEnter(AppState::InGame), setup)
4658
.add_systems(OnExit(AppState::InGame), cleanup)
4759
.add_systems(
4860
PostUpdate,
4961
(insert, remove)
5062
.run_if(in_state(GameState::Playing))
51-
.in_set(IndexSet::Index),
63+
.in_set(PreciseIndexSet::Index),
5264
)
5365
.add_systems(
5466
PostMovement,
5567
update
5668
.run_if(in_state(GameState::Playing))
57-
.in_set(IndexSet::Index),
69+
.in_set(PreciseIndexSet::Index),
5870
);
5971
}
6072
}
6173

6274
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, SystemSet)]
63-
pub enum IndexSet {
75+
pub enum PreciseIndexSet {
6476
Index,
6577
}
6678

crates/index/src/range.rs renamed to crates/index/src/precise/range.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::TILE_SIZE;
88
///
99
/// The tiles are iterated row-by-row, for example: (1, 1) -> (2, 1) -> (1, 2)
1010
/// -> (2, 2).
11-
pub(crate) struct TileRange {
11+
pub(super) struct TileRange {
1212
a: IVec2,
1313
b: IVec2,
1414
x: i32,
@@ -21,7 +21,7 @@ impl TileRange {
2121
///
2222
/// Tiles are assumed to be topologically closed. In other words, both
2323
/// touching and intersecting tiles are included in the range.
24-
pub(crate) fn from_aabb(aabb: &Aabb) -> Self {
24+
pub(super) fn from_aabb(aabb: &Aabb) -> Self {
2525
let aabb = aabb.to_flat();
2626
let min_flat: Vec2 = aabb.mins.into();
2727
let max_flat: Vec2 = aabb.maxs.into();
@@ -35,7 +35,7 @@ impl TileRange {
3535
/// * `a` - inclusive range start.
3636
///
3737
/// * `b` - inclusive range end.
38-
pub(crate) fn new(a: IVec2, b: IVec2) -> Self {
38+
pub(super) fn new(a: IVec2, b: IVec2) -> Self {
3939
Self {
4040
a,
4141
b,
@@ -46,12 +46,12 @@ impl TileRange {
4646
}
4747

4848
/// Returns true if the given point is not contained in the tile range.
49-
pub(crate) fn excludes(&self, point: IVec2) -> bool {
49+
pub(super) fn excludes(&self, point: IVec2) -> bool {
5050
self.a.cmpgt(point).any() || self.b.cmplt(point).any()
5151
}
5252

5353
/// Returns intersecting tile range. The result might be empty.
54-
pub(crate) fn intersection(&self, other: &TileRange) -> TileRange {
54+
pub(super) fn intersection(&self, other: &TileRange) -> TileRange {
5555
Self::new(self.a.max(other.a), self.b.min(other.b))
5656
}
5757
}

crates/index/src/segment.rs renamed to crates/index/src/precise/segment.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ use de_types::projection::ToFlat;
66
use glam::{IVec2, Vec2};
77
use parry3d::shape::Segment;
88

9-
use super::{grid::TileGrid, TILE_SIZE};
9+
use super::grid::TileGrid;
10+
use crate::TILE_SIZE;
1011

1112
/// An iterator over sets of entities from tiles intersecting a given line
1213
/// segment.
@@ -18,14 +19,14 @@ use super::{grid::TileGrid, TILE_SIZE};
1819
/// The tiles (and thus the yielded sets) are iterated by increasing distance
1920
/// between point `a` of the given line segment and the intersection of the
2021
/// tile with the line segment.
21-
pub(crate) struct SegmentCandidates<'a> {
22+
pub(super) struct SegmentCandidates<'a> {
2223
grid: &'a TileGrid,
2324
tiles: TileIterator,
2425
encountered: Option<&'a AHashSet<Entity>>,
2526
}
2627

2728
impl<'a> SegmentCandidates<'a> {
28-
pub(crate) fn new(grid: &'a TileGrid, segment: Segment) -> Self {
29+
pub(super) fn new(grid: &'a TileGrid, segment: Segment) -> Self {
2930
Self {
3031
grid,
3132
tiles: TileIterator::new(segment),
@@ -172,7 +173,6 @@ mod tests {
172173
use parry3d::{bounding_volume::Aabb, math::Point, shape::Segment};
173174

174175
use super::*;
175-
use crate::grid::TileGrid;
176176

177177
#[test]
178178
fn test_segment_candidates() {

crates/spawner/src/draft.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use de_core::{
1212
objects::{MovableSolid, ObjectTypeComponent, StaticSolid},
1313
state::AppState,
1414
};
15-
use de_index::{ColliderWithCache, IndexSet, QueryCollider, SpatialQuery};
15+
use de_index::{ColliderWithCache, PreciseIndexSet, QueryCollider, SpatialQuery};
1616
use de_map::size::MapBounds;
1717
use de_objects::{AssetCollection, SceneType, Scenes, SolidObjects, EXCLUSION_OFFSET};
1818
use de_types::{
@@ -42,7 +42,7 @@ impl Plugin for DraftPlugin {
4242
PostUpdate,
4343
(update_draft, check_draft_loaded, update_draft_colour)
4444
.run_if(in_state(GameState::Playing))
45-
.after(IndexSet::Index),
45+
.after(PreciseIndexSet::Index),
4646
);
4747
}
4848
}

0 commit comments

Comments
 (0)