Skip to content

Commit

Permalink
de_index: Prep for faster indexing alternative
Browse files Browse the repository at this point in the history
  • Loading branch information
Indy2222 committed Oct 17, 2023
1 parent 4b89fc3 commit 2c91e19
Show file tree
Hide file tree
Showing 10 changed files with 64 additions and 63 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ glam = "0.24"
gltf = "1.0"
itertools = "0.11.0"
iyes_progress = "0.9.0"
kiddo = "2.1.2"
log = "0.4.17"
nalgebra = { version = "0.32.3", features = ["convert-glam024"] }
nix = "0.26.2"
Expand Down
28 changes: 7 additions & 21 deletions crates/index/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,12 @@
#![allow(rustdoc::private_intra_doc_links)]
//! This module implements 2D object partitioning for fast geometric lookup,
//! for example ray casting.
//!
//! The core structure is a square tile grid which points to Bevy ECS entities.
//! Newly spawned entities are automatically added, despawned entities removed
//! and moved entities updated by systems added by
//! [`self::IndexPlugin`].
mod aabb;
mod collider;
mod grid;
mod index;
mod range;
mod segment;
mod systems;

use bevy::{app::PluginGroupBuilder, prelude::PluginGroup};
use systems::IndexPlugin;
mod precise;

pub use self::{
collider::{ColliderWithCache, LocalCollider, QueryCollider},
index::{EntityIndex, RayEntityIntersection, SpatialQuery},
systems::IndexSet,
use bevy::{app::PluginGroupBuilder, prelude::PluginGroup};
use precise::PreciseIndexPlugin;
pub use precise::{
ColliderWithCache, EntityIndex, PreciseIndexSet, LocalCollider, QueryCollider, RayEntityIntersection,
SpatialQuery,
};

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

impl PluginGroup for IndexPluginGroup {
fn build(self) -> PluginGroupBuilder {
PluginGroupBuilder::start::<Self>().add(IndexPlugin)
PluginGroupBuilder::start::<Self>().add(PreciseIndexPlugin)
}
}
6 changes: 3 additions & 3 deletions crates/index/src/aabb.rs → crates/index/src/precise/aabb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ use ahash::AHashSet;
use bevy::prelude::Entity;
use parry3d::bounding_volume::Aabb;

use crate::{grid::TileGrid, range::TileRange};
use super::{grid::TileGrid, range::TileRange};

/// An iterator over unique entity IDs withing a box.
pub(crate) struct AabbCandidates<'a> {
pub(super) struct AabbCandidates<'a> {
grid: &'a TileGrid,
tiles: TileRange,
row: Option<i32>,
Expand All @@ -16,7 +16,7 @@ pub(crate) struct AabbCandidates<'a> {
impl<'a> AabbCandidates<'a> {
/// Creates a new iterator of entities potentially colliding with a given
/// AABB.
pub(crate) fn new(grid: &'a TileGrid, aabb: &Aabb) -> Self {
pub(super) fn new(grid: &'a TileGrid, aabb: &Aabb) -> Self {
Self {
grid,
tiles: TileRange::from_aabb(aabb),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,20 @@ impl LocalCollider {
}

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

pub(crate) fn cast_ray(&self, ray: &Ray, max_toi: f32) -> Option<f32> {
pub(super) fn cast_ray(&self, ray: &Ray, max_toi: f32) -> Option<f32> {
if self.world_aabb.intersects_local_ray(ray, max_toi) {
self.object_collider.cast_ray(&self.position, ray, max_toi)
} else {
None
}
}

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

/// Returns true if world-space axis-aligned bounding boxes of the two
/// colliders intersect.
pub(crate) fn query_aabb(&self, aabb: &Aabb) -> bool {
pub(super) fn query_aabb(&self, aabb: &Aabb) -> bool {
self.world_aabb.intersects(aabb)
}
}
Expand Down
14 changes: 7 additions & 7 deletions crates/index/src/grid.rs → crates/index/src/precise/grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ use bevy::prelude::Entity;
use glam::IVec2;
use parry3d::bounding_volume::Aabb;

use crate::range::TileRange;
use super::range::TileRange;

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

impl TileGrid {
/// Creates a new empty grid.
pub(crate) fn new() -> Self {
pub(super) fn new() -> Self {
Self {
tiles: AHashMap::new(),
}
Expand All @@ -36,7 +36,7 @@ impl TileGrid {
/// # Panics
///
/// Might panic if the entity is already present in the grid.
pub(crate) fn insert(&mut self, entity: Entity, aabb: &Aabb) {
pub(super) fn insert(&mut self, entity: Entity, aabb: &Aabb) {
for tile in TileRange::from_aabb(aabb) {
self.insert_to_tile(entity, tile);
}
Expand All @@ -56,7 +56,7 @@ impl TileGrid {
///
/// Might panic if the entity is not stored in the grid or if the last used
/// update / insertion AABB differs from the one passed as an argument.
pub(crate) fn remove(&mut self, entity: Entity, aabb: &Aabb) {
pub(super) fn remove(&mut self, entity: Entity, aabb: &Aabb) {
for tile in TileRange::from_aabb(aabb) {
self.remove_from_tile(entity, tile);
}
Expand All @@ -77,7 +77,7 @@ impl TileGrid {
///
/// Might panic if the entity is not present in the grid or if `old_aabb`
/// differs from the last used update / insert AABB.
pub(crate) fn update(&mut self, entity: Entity, old_aabb: &Aabb, new_aabb: &Aabb) {
pub(super) fn update(&mut self, entity: Entity, old_aabb: &Aabb, new_aabb: &Aabb) {
let old_tiles = TileRange::from_aabb(old_aabb);
let new_tiles = TileRange::from_aabb(new_aabb);

Expand Down Expand Up @@ -107,7 +107,7 @@ impl TileGrid {
/// # Arguments
///
/// `tile_coords` - coordinates of the tile.
pub(crate) fn get_tile_entities(&self, tile_coords: IVec2) -> Option<&AHashSet<Entity>> {
pub(super) fn get_tile_entities(&self, tile_coords: IVec2) -> Option<&AHashSet<Entity>> {
self.tiles.get(&tile_coords)
}

Expand Down
16 changes: 9 additions & 7 deletions crates/index/src/index.rs → crates/index/src/precise/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ use parry3d::{
shape::Segment,
};

use super::{collider::LocalCollider, grid::TileGrid, segment::SegmentCandidates};
use crate::{aabb::AabbCandidates, collider::ColliderWithCache};
use super::{
aabb::AabbCandidates, collider::ColliderWithCache, collider::LocalCollider, grid::TileGrid,
segment::SegmentCandidates,
};

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

pub(crate) fn remove(&mut self, entity: Entity) {
pub(super) fn remove(&mut self, entity: Entity) {
let collider = self
.colliders
.remove(&entity)
.expect("Tried to remove non-existent entity.");
self.grid.remove(entity, collider.world_aabb());
}

pub(crate) fn update(&mut self, entity: Entity, position: Isometry<f32>) {
pub(super) fn update(&mut self, entity: Entity, position: Isometry<f32>) {
let collider = self
.colliders
.get_mut(&entity)
Expand Down Expand Up @@ -107,7 +109,7 @@ impl Default for EntityIndex {
/// System parameter implementing various spatial queries.
///
/// Only entities automatically indexed by systems from
/// [`super::systems::IndexPlugin`] could be retrieved.
/// [`super::PreciseIndexPlugin`] could be retrieved.
#[derive(SystemParam)]
pub struct SpatialQuery<'w, 's, Q, F = ()>
where
Expand All @@ -124,7 +126,7 @@ where
F: ReadOnlyWorldQuery + Sync + Send + 'static,
{
/// Returns closest entity whose shape, as indexed by systems registered by
/// [`super::systems::IndexPlugin`], intersects a given ray.
/// [`super::PreciseIndexPlugin`], intersects a given ray.
///
/// # Arguments
///
Expand Down Expand Up @@ -173,7 +175,7 @@ where
}

/// Returns true if queried solid object on the map, as indexed by
/// [`super::systems::IndexPlugin`], intersects with the given collider.
/// [`super::PreciseIndexPlugin`], intersects with the given collider.
pub fn collides(&self, collider: &impl ColliderWithCache) -> bool {
let candidate_sets = self.index.query_aabb(collider.world_aabb());
candidate_sets.flatten().any(|candidate| {
Expand Down
32 changes: 22 additions & 10 deletions crates/index/src/systems.rs → crates/index/src/precise/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! Module with systems and a Bevy plugin for automatic entity indexing of
//! solid entities.

//! This module implements 2D object partitioning for fast but precise object
//! collider geometry lookup, for example ray casting.
//!
//! The core structure is a square tile grid which points to Bevy ECS entities.
//! Newly spawned entities are automatically added, despawned entities removed
//! and moved entities updated by systems added by [`PreciseIndexPlugin`].
use bevy::prelude::*;
use de_core::{
gamestate::GameState,
Expand All @@ -11,8 +14,17 @@ use de_core::{
use de_objects::SolidObjects;
use parry3d::math::Isometry;

use super::index::EntityIndex;
use crate::collider::LocalCollider;
pub use self::{
collider::{ColliderWithCache, LocalCollider, QueryCollider},
index::{EntityIndex, RayEntityIntersection, SpatialQuery},
};

mod aabb;
mod collider;
mod grid;
mod index;
mod range;
mod segment;

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

impl Plugin for IndexPlugin {
impl Plugin for PreciseIndexPlugin {
fn build(&self, app: &mut App) {
app.add_systems(OnEnter(AppState::InGame), setup)
.add_systems(OnExit(AppState::InGame), cleanup)
.add_systems(
PostUpdate,
(insert, remove)
.run_if(in_state(GameState::Playing))
.in_set(IndexSet::Index),
.in_set(PreciseIndexSet::Index),
)
.add_systems(
PostMovement,
update
.run_if(in_state(GameState::Playing))
.in_set(IndexSet::Index),
.in_set(PreciseIndexSet::Index),
);
}
}

#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, SystemSet)]
pub enum IndexSet {
pub enum PreciseIndexSet {
Index,
}

Expand Down
10 changes: 5 additions & 5 deletions crates/index/src/range.rs → crates/index/src/precise/range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::TILE_SIZE;
///
/// The tiles are iterated row-by-row, for example: (1, 1) -> (2, 1) -> (1, 2)
/// -> (2, 2).
pub(crate) struct TileRange {
pub(super) struct TileRange {
a: IVec2,
b: IVec2,
x: i32,
Expand All @@ -21,7 +21,7 @@ impl TileRange {
///
/// Tiles are assumed to be topologically closed. In other words, both
/// touching and intersecting tiles are included in the range.
pub(crate) fn from_aabb(aabb: &Aabb) -> Self {
pub(super) fn from_aabb(aabb: &Aabb) -> Self {
let aabb = aabb.to_flat();
let min_flat: Vec2 = aabb.mins.into();
let max_flat: Vec2 = aabb.maxs.into();
Expand All @@ -35,7 +35,7 @@ impl TileRange {
/// * `a` - inclusive range start.
///
/// * `b` - inclusive range end.
pub(crate) fn new(a: IVec2, b: IVec2) -> Self {
pub(super) fn new(a: IVec2, b: IVec2) -> Self {
Self {
a,
b,
Expand All @@ -46,12 +46,12 @@ impl TileRange {
}

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

/// Returns intersecting tile range. The result might be empty.
pub(crate) fn intersection(&self, other: &TileRange) -> TileRange {
pub(super) fn intersection(&self, other: &TileRange) -> TileRange {
Self::new(self.a.max(other.a), self.b.min(other.b))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use de_types::projection::ToFlat;
use glam::{IVec2, Vec2};
use parry3d::shape::Segment;

use super::{grid::TileGrid, TILE_SIZE};
use super::grid::TileGrid;
use crate::TILE_SIZE;

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

impl<'a> SegmentCandidates<'a> {
pub(crate) fn new(grid: &'a TileGrid, segment: Segment) -> Self {
pub(super) fn new(grid: &'a TileGrid, segment: Segment) -> Self {
Self {
grid,
tiles: TileIterator::new(segment),
Expand Down Expand Up @@ -172,7 +173,6 @@ mod tests {
use parry3d::{bounding_volume::Aabb, math::Point, shape::Segment};

use super::*;
use crate::grid::TileGrid;

#[test]
fn test_segment_candidates() {
Expand Down
4 changes: 2 additions & 2 deletions crates/spawner/src/draft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use de_core::{
objects::{MovableSolid, ObjectTypeComponent, StaticSolid},
state::AppState,
};
use de_index::{ColliderWithCache, IndexSet, QueryCollider, SpatialQuery};
use de_index::{ColliderWithCache, PreciseIndexSet, QueryCollider, SpatialQuery};
use de_map::size::MapBounds;
use de_objects::{AssetCollection, SceneType, Scenes, SolidObjects, EXCLUSION_OFFSET};
use de_types::{
Expand Down Expand Up @@ -42,7 +42,7 @@ impl Plugin for DraftPlugin {
PostUpdate,
(update_draft, check_draft_loaded, update_draft_colour)
.run_if(in_state(GameState::Playing))
.after(IndexSet::Index),
.after(PreciseIndexSet::Index),
);
}
}
Expand Down

0 comments on commit 2c91e19

Please sign in to comment.