Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

de_index: Prep for faster indexing alternative #772

Merged
merged 1 commit into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 9 additions & 21 deletions crates/index/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,14 @@
#![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;
//! This crate implements spatial indexing and various spatial queries of game
//! entities.

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, LocalCollider, PreciseIndexSet, QueryCollider,
RayEntityIntersection, SpatialQuery,
};

/// Size (in world-space) of a single square tile where entities are kept.
Expand All @@ -30,6 +18,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 collider based spatial indexing of game entities and
//! various geometry based 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