diff --git a/Cargo.toml b/Cargo.toml index 9f7123e13..ab004731e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/crates/index/src/lib.rs b/crates/index/src/lib.rs index b82506c0d..e6e9d4757 100644 --- a/crates/index/src/lib.rs +++ b/crates/index/src/lib.rs @@ -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. @@ -30,6 +16,6 @@ pub struct IndexPluginGroup; impl PluginGroup for IndexPluginGroup { fn build(self) -> PluginGroupBuilder { - PluginGroupBuilder::start::().add(IndexPlugin) + PluginGroupBuilder::start::().add(PreciseIndexPlugin) } } diff --git a/crates/index/src/aabb.rs b/crates/index/src/precise/aabb.rs similarity index 95% rename from crates/index/src/aabb.rs rename to crates/index/src/precise/aabb.rs index b489b8d64..e7ed16914 100644 --- a/crates/index/src/aabb.rs +++ b/crates/index/src/precise/aabb.rs @@ -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, @@ -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), diff --git a/crates/index/src/collider.rs b/crates/index/src/precise/collider.rs similarity index 92% rename from crates/index/src/collider.rs rename to crates/index/src/precise/collider.rs index 0af15d6c3..c258a5373 100644 --- a/crates/index/src/collider.rs +++ b/crates/index/src/precise/collider.rs @@ -43,12 +43,12 @@ impl LocalCollider { } /// Updates position of cached world-space AABB of the collider. - pub(crate) fn update_position(&mut self, position: Isometry) { + pub(super) fn update_position(&mut self, position: Isometry) { self.world_aabb = self.local_aabb.transform_by(&position); self.position = position; } - pub(crate) fn cast_ray(&self, ray: &Ray, max_toi: f32) -> Option { + pub(super) fn cast_ray(&self, ray: &Ray, max_toi: f32) -> Option { if self.world_aabb.intersects_local_ray(ray, max_toi) { self.object_collider.cast_ray(&self.position, ray, max_toi) } else { @@ -56,7 +56,7 @@ impl LocalCollider { } } - 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()) @@ -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) } } diff --git a/crates/index/src/grid.rs b/crates/index/src/precise/grid.rs similarity index 96% rename from crates/index/src/grid.rs rename to crates/index/src/precise/grid.rs index 3025ee2d0..5fb946932 100644 --- a/crates/index/src/grid.rs +++ b/crates/index/src/precise/grid.rs @@ -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>, } impl TileGrid { /// Creates a new empty grid. - pub(crate) fn new() -> Self { + pub(super) fn new() -> Self { Self { tiles: AHashMap::new(), } @@ -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); } @@ -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); } @@ -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); @@ -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> { + pub(super) fn get_tile_entities(&self, tile_coords: IVec2) -> Option<&AHashSet> { self.tiles.get(&tile_coords) } diff --git a/crates/index/src/index.rs b/crates/index/src/precise/index.rs similarity index 96% rename from crates/index/src/index.rs rename to crates/index/src/precise/index.rs index 693f13a49..8cec2f815 100644 --- a/crates/index/src/index.rs +++ b/crates/index/src/precise/index.rs @@ -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)] @@ -47,7 +49,7 @@ 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) @@ -55,7 +57,7 @@ impl EntityIndex { self.grid.remove(entity, collider.world_aabb()); } - pub(crate) fn update(&mut self, entity: Entity, position: Isometry) { + pub(super) fn update(&mut self, entity: Entity, position: Isometry) { let collider = self .colliders .get_mut(&entity) @@ -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 @@ -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 /// @@ -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| { diff --git a/crates/index/src/systems.rs b/crates/index/src/precise/mod.rs similarity index 79% rename from crates/index/src/systems.rs rename to crates/index/src/precise/mod.rs index 66f2ab70c..af388e466 100644 --- a/crates/index/src/systems.rs +++ b/crates/index/src/precise/mod.rs @@ -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, @@ -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, @@ -38,9 +50,9 @@ 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) @@ -48,19 +60,19 @@ impl Plugin for IndexPlugin { 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, } diff --git a/crates/index/src/range.rs b/crates/index/src/precise/range.rs similarity index 88% rename from crates/index/src/range.rs rename to crates/index/src/precise/range.rs index a3e206f4a..7dfe2dea1 100644 --- a/crates/index/src/range.rs +++ b/crates/index/src/precise/range.rs @@ -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, @@ -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(); @@ -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, @@ -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)) } } diff --git a/crates/index/src/segment.rs b/crates/index/src/precise/segment.rs similarity index 97% rename from crates/index/src/segment.rs rename to crates/index/src/precise/segment.rs index b72825ad3..f7d2e9120 100644 --- a/crates/index/src/segment.rs +++ b/crates/index/src/precise/segment.rs @@ -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. @@ -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>, } 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), @@ -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() { diff --git a/crates/spawner/src/draft.rs b/crates/spawner/src/draft.rs index 6d57591be..5c9fb356c 100644 --- a/crates/spawner/src/draft.rs +++ b/crates/spawner/src/draft.rs @@ -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::{ @@ -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), ); } }