Switch agent target search to use a spatial grid, add convience method for querying the aabr of a circle

This commit is contained in:
Imbris 2021-03-29 00:57:42 -04:00
parent a76fdbc325
commit e750c9d570
4 changed files with 64 additions and 49 deletions

View File

@ -52,8 +52,6 @@ impl SpatialGrid {
/// provided axis aligned bounding region
/// NOTE: for best optimization of the iterator use `for_each` rather than a
/// for loop
// TODO: a circle would be tighter (how efficient would it be to query the cells
// intersecting a circle?)
pub fn in_aabr<'a>(&'a self, aabr: Aabr<i32>) -> impl Iterator<Item = specs::Entity> + 'a {
let iter = |max_entity_radius, grid: &'a hashbrown::HashMap<_, _>, lg2_cell_size| {
// Add buffer for other entity radius
@ -76,6 +74,32 @@ impl SpatialGrid {
))
}
/// Get an iterator over the entities overlapping the
/// axis aligned bounding region that contains the provided circle
/// NOTE: for best optimization of the iterator use `for_each` rather than a
/// for loop
// TODO: using the circle directly would be tighter (how efficient would it be
// to query the cells intersecting a circle?) (note: if doing this rename
// the function)
pub fn in_circle_aabr<'a>(
&'a self,
center: Vec2<f32>,
radius: f32,
) -> impl Iterator<Item = specs::Entity> + 'a {
let center = center.map(|e| e as i32);
let radius = radius.ceil() as i32;
// From conversion of center above
const CENTER_TRUNCATION_ERROR: i32 = 1;
let max_dist = radius + CENTER_TRUNCATION_ERROR;
let aabr = Aabr {
min: center - max_dist,
max: center + max_dist,
};
self.in_aabr(aabr)
}
pub fn clear(&mut self) {
self.grid.clear();
self.large_grid.clear();

View File

@ -206,6 +206,7 @@ impl State {
ecs.insert(EventBus::<LocalEvent>::default());
ecs.insert(game_mode);
ecs.insert(Vec::<common::outcome::Outcome>::new());
ecs.insert(common::CachedSpatialGrid::default());
let slow_limit = thread_pool.current_num_threads().max(2) as u64;
let slow_limit = slow_limit / 2 + slow_limit / 4;

View File

@ -144,7 +144,7 @@ pub struct PhysicsRead<'a> {
#[derive(SystemData)]
pub struct PhysicsWrite<'a> {
physics_metrics: WriteExpect<'a, PhysicsMetrics>,
cached_spatial_grid: WriteExpect<'a, common::CachedSpatialGrid>,
cached_spatial_grid: Write<'a, common::CachedSpatialGrid>,
physics_states: WriteStorage<'a, PhysicsState>,
positions: WriteStorage<'a, Pos>,
velocities: WriteStorage<'a, Vel>,
@ -343,27 +343,17 @@ impl<'a> PhysicsData<'a> {
let mut entity_entity_collision_checks = 0;
let mut entity_entity_collisions = 0;
let aabr = {
let center = previous_cache.center.xy().map(|e| e as i32);
let radius = previous_cache.collision_boundary.ceil() as i32;
// From conversion of center above
const CENTER_TRUNCATION_ERROR: i32 = 1;
let max_dist = radius + CENTER_TRUNCATION_ERROR;
Aabr {
min: center - max_dist,
max: center + max_dist,
}
};
let query_center = previous_cache.center.xy();
let query_radius = previous_cache.collision_boundary;
spatial_grid
.in_aabr(aabr)
.in_circle_aabr(query_center, query_radius)
.filter_map(|entity| {
read.uids
.get(entity)
.zip(positions.get(entity))
.zip(previous_phys_cache.get(entity))
.zip(read.masses.get(entity))
.and_then(|l| positions.get(entity).map(|r| (l, r)))
.and_then(|l| previous_phys_cache.get(entity).map(|r| (l, r)))
.and_then(|l| read.masses.get(entity).map(|r| (l, r)))
.map(|(((uid, pos), previous_cache), mass)| {
(
entity,
@ -886,27 +876,17 @@ impl<'a> PhysicsData<'a> {
}
};
// Collide with terrain-like entities
let aabr = {
let center = path_sphere.center.xy().map(|e| e as i32);
let radius = path_sphere.radius.ceil() as i32;
// From conversion of center above
const CENTER_TRUNCATION_ERROR: i32 = 1;
let max_dist = radius + CENTER_TRUNCATION_ERROR;
Aabr {
min: center - max_dist,
max: center + max_dist,
}
};
let query_center = path_sphere.center.xy();
let query_radius = path_sphere.radius;
voxel_collider_spatial_grid
.in_aabr(aabr)
.in_circle_aabr(query_center, query_radius)
.filter_map(|entity| {
positions
.get(entity)
.zip(velocities.get(entity))
.zip(previous_phys_cache.get(entity))
.zip(read.colliders.get(entity))
.zip(orientations.get(entity))
.and_then(|l| velocities.get(entity).map(|r| (l, r)))
.and_then(|l| previous_phys_cache.get(entity).map(|r| (l, r)))
.and_then(|l| read.colliders.get(entity).map(|r| (l, r)))
.and_then(|l| orientations.get(entity).map(|r| (l, r)))
.map(|((((pos, vel), previous_cache), collider), ori)| {
(entity, pos, vel, previous_cache, collider, ori)
})

View File

@ -68,6 +68,7 @@ struct AgentData<'a> {
is_gliding: bool,
health: Option<&'a Health>,
char_state: &'a CharacterState,
cached_spatial_grid: &'a common::CachedSpatialGrid,
}
#[derive(SystemData)]
@ -76,6 +77,7 @@ pub struct ReadData<'a> {
uid_allocator: Read<'a, UidAllocator>,
dt: Read<'a, DeltaTime>,
time: Read<'a, Time>,
cached_spatial_grid: Read<'a, common::CachedSpatialGrid>,
group_manager: Read<'a, group::GroupManager>,
energies: ReadStorage<'a, Energy>,
positions: ReadStorage<'a, Pos>,
@ -284,6 +286,7 @@ impl<'a> System<'a> for Sys {
is_gliding,
health: read_data.healths.get(entity),
char_state,
cached_spatial_grid: &read_data.cached_spatial_grid,
};
///////////////////////////////////////////////////////////
@ -1374,10 +1377,19 @@ impl<'a> AgentData<'a> {
) {
agent.action_timer = 0.0;
// Search for new targets (this looks expensive, but it's only run occasionally)
// TODO: Replace this with a better system that doesn't consider *all* entities
let target = (&read_data.entities, &read_data.positions, &read_data.healths, &read_data.stats, &read_data.inventories, read_data.alignments.maybe(), read_data.char_states.maybe())
.join()
// Search area
let target = self.cached_spatial_grid.0
.in_circle_aabr(self.pos.0.xy(), SEARCH_DIST)
.filter_map(|entity| {
read_data.positions
.get(entity)
.and_then(|l| read_data.healths.get(entity).map(|r| (l, r)))
.and_then(|l| read_data.stats.get(entity).map(|r| (l, r)))
.and_then(|l| read_data.inventories.get(entity).map(|r| (l, r)))
.map(|(((pos, health), stats), inventory)| {
(entity, pos, health, stats, inventory, read_data.alignments.get(entity), read_data.char_states.get(entity))
})
})
.filter(|(e, e_pos, e_health, e_stats, e_inventory, e_alignment, char_state)| {
let mut search_dist = SEARCH_DIST;
let mut listen_dist = LISTEN_DIST;
@ -1436,6 +1448,7 @@ impl<'a> AgentData<'a> {
})
// Can we even see them?
// TODO: limit ray cast distance to the amount needed to tell if we can see the entity
.filter(|(_, e_pos, _, _, _, _, _)| read_data.terrain
.ray(self.pos.0 + Vec3::unit_z(), e_pos.0 + Vec3::unit_z())
.until(Block::is_opaque)
@ -1443,15 +1456,12 @@ impl<'a> AgentData<'a> {
.0 >= e_pos.0.distance(self.pos.0))
.min_by_key(|(_, e_pos, _, _, _, _, _)| (e_pos.0.distance_squared(self.pos.0) * 100.0) as i32) // TODO choose target by more than just distance
.map(|(e, _, _, _, _, _, _)| e);
if let Some(target) = target {
agent.target = Some(Target {
target,
hostile: true,
selected_at: read_data.time.0,
})
} else {
agent.target = None;
}
agent.target = target.map(|target| Target {
target,
hostile: true,
selected_at: read_data.time.0,
});
}
fn jump_if(&self, controller: &mut Controller, condition: bool) {