Refactor visibility to use physics system.

This commit is contained in:
Nolan Darilek 2021-06-09 14:13:09 -05:00
parent eefd3a2209
commit 173630edab
5 changed files with 111 additions and 330 deletions

View File

@ -21,7 +21,7 @@ impl Coordinates {
)) ))
} }
fn to_transform(&self, config: &CoreConfig) -> Transform { pub fn to_transform(&self, config: &CoreConfig) -> Transform {
Transform::from_translation(Vec3::new( Transform::from_translation(Vec3::new(
self.0 .0 * config.pixels_per_unit as f32, self.0 .0 * config.pixels_per_unit as f32,
self.0 .1 * config.pixels_per_unit as f32, self.0 .1 * config.pixels_per_unit as f32,

View File

@ -2,6 +2,7 @@ use std::{error::Error, hash::Hash, marker::PhantomData};
use bevy::prelude::*; use bevy::prelude::*;
use bevy_input_actionmap::InputMap; use bevy_input_actionmap::InputMap;
use bevy_rapier2d::prelude::*;
use bevy_tts::Tts; use bevy_tts::Tts;
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use mapgen::TileType; use mapgen::TileType;
@ -328,13 +329,17 @@ fn exploration_changed_announcement(
names: Query<&Name>, names: Query<&Name>,
types: Query<&ExplorationType>, types: Query<&ExplorationType>,
mappables: Query<&Mappable>, mappables: Query<&Mappable>,
query_pipeline: Res<QueryPipeline>,
collider_query: QueryPipelineColliderComponentsQuery,
) -> Result<(), Box<dyn Error>> { ) -> Result<(), Box<dyn Error>> {
for (coordinates, exploring) in explorers.iter() { for (coordinates, exploring) in explorers.iter() {
let collider_set = QueryPipelineColliderComponentsSet(&collider_query);
let coordinates = **coordinates; let coordinates = **coordinates;
let coordinates = (coordinates.0.floor(), coordinates.1.floor()); let coordinates = (coordinates.0.floor(), coordinates.1.floor());
for (map, revealed_tiles, visible_tiles) in map.iter() { for (map, revealed_tiles, visible_tiles) in map.iter() {
let point = **exploring; let point = **exploring;
let idx = point.to_index(map.width()); let idx = point.to_index(map.width());
let shape = Cuboid::new(Vec2::new(0.5, 0.5).into());
let known = revealed_tiles[idx]; let known = revealed_tiles[idx];
let visible = visible_tiles[idx]; let visible = visible_tiles[idx];
let fog_of_war = known && !visible; let fog_of_war = known && !visible;
@ -343,21 +348,31 @@ fn exploration_changed_announcement(
for (entity, _) in focused.iter() { for (entity, _) in focused.iter() {
commands.entity(entity).remove::<ExplorationFocused>(); commands.entity(entity).remove::<ExplorationFocused>();
} }
for entity in &map.entities[idx] { let shape_pos = (Vec2::new(exploring.x(), exploring.y()), 0.);
query_pipeline.intersections_with_shape(
&collider_set,
&shape_pos.into(),
&shape,
InteractionGroups::all(),
None,
|handle| {
let entity = handle.entity();
commands commands
.entity(*entity) .entity(entity)
.insert(ExplorationFocused::default()); .insert(ExplorationFocused::default());
if visible || mappables.get(*entity).is_ok() { if visible || mappables.get(entity).is_ok() {
if let Ok(name) = names.get(*entity) { if let Ok(name) = names.get(entity) {
tokens.push(name.as_str()); tokens.push(name.as_str());
} }
if tokens.is_empty() { if tokens.is_empty() {
if let Ok(t) = types.get(*entity) { if let Ok(t) = types.get(entity) {
tokens.push((*t).into()); tokens.push((*t).into());
} }
} }
} }
} true
},
);
if tokens.is_empty() { if tokens.is_empty() {
match map.base.tiles[idx] { match map.base.tiles[idx] {
TileType::Floor => "Floor".to_string(), TileType::Floor => "Floor".to_string(),

View File

@ -1,3 +1,4 @@
#![allow(unused_imports)]
#![allow(clippy::too_many_arguments)] #![allow(clippy::too_many_arguments)]
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]

View File

@ -29,16 +29,12 @@ pub struct Portal;
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct Map { pub struct Map {
pub base: MapgenMap, pub base: MapgenMap,
pub entities: Vec<HashSet<Entity>>,
} }
impl Map { impl Map {
pub fn new(base: MapgenMap) -> Self { pub fn new(base: MapgenMap) -> Self {
let count = (base.width * base.height) as usize; let count = (base.width * base.height) as usize;
Self { Self { base }
base,
entities: vec![HashSet::new(); count],
}
} }
pub fn width(&self) -> usize { pub fn width(&self) -> usize {
@ -358,38 +354,6 @@ fn area_description(
} }
} }
fn remove_coordinates(removed: RemovedComponents<Coordinates>, mut map: Query<&mut Map>) {
for removed in removed.iter() {
for mut map in map.iter_mut() {
for entities in map.entities.iter_mut() {
entities.retain(|e| *e != removed);
}
}
}
}
#[derive(Default, Deref, DerefMut)]
struct PreviousIndex(HashMap<Entity, usize>);
fn entity_indexing(
mut map: Query<&mut Map>,
mut previous_index: ResMut<PreviousIndex>,
query: Query<(Entity, &Coordinates), Changed<Coordinates>>,
) {
for (entity, coordinates) in query.iter() {
for mut map in map.iter_mut() {
let idx = coordinates.to_index(map.width());
if let Some(prev_idx) = previous_index.get(&entity) {
if idx != *prev_idx {
map.entities[*prev_idx].retain(|&e| e != entity);
}
}
map.entities[idx].insert(entity);
previous_index.insert(entity, idx);
}
}
}
fn add_areas(mut commands: Commands, query: Query<(Entity, &Map), (Added<Map>, Without<Areas>)>) { fn add_areas(mut commands: Commands, query: Query<(Entity, &Map), (Added<Map>, Without<Areas>)>) {
for (entity, map) in query.iter() { for (entity, map) in query.iter() {
let mut v = vec![]; let mut v = vec![];
@ -403,8 +367,6 @@ fn add_areas(mut commands: Commands, query: Query<(Entity, &Map), (Added<Map>, W
} }
} }
pub const UPDATE_ENTITY_INDEX_LABEL: &str = "UPDATE_ENTITY_INDEX";
pub struct MapPlugin; pub struct MapPlugin;
impl Plugin for MapPlugin { impl Plugin for MapPlugin {
@ -413,27 +375,9 @@ impl Plugin for MapPlugin {
app.insert_resource(MapConfig::default()); app.insert_resource(MapConfig::default());
} }
let config = app.world().get_resource::<MapConfig>().unwrap().clone(); let config = app.world().get_resource::<MapConfig>().unwrap().clone();
const SPAWN_PORTALS: &str = "SPAWN_PORTALS";
app.register_type::<Portal>() app.register_type::<Portal>()
.insert_resource(PreviousIndex::default())
.add_system(add_map_colliders.system()) .add_system(add_map_colliders.system())
.add_system(entity_indexing.system().label(UPDATE_ENTITY_INDEX_LABEL)) .add_system(portal_spawner.system())
.add_system(
portal_spawner
.system()
.label(SPAWN_PORTALS)
.before(UPDATE_ENTITY_INDEX_LABEL),
)
.add_system_to_stage(
CoreStage::PostUpdate,
remove_coordinates
.system()
.before(UPDATE_ENTITY_INDEX_LABEL),
)
.add_system_to_stage(
CoreStage::PostUpdate,
entity_indexing.system().label(UPDATE_ENTITY_INDEX_LABEL),
)
.add_system(add_areas.system()) .add_system(add_areas.system())
.add_system_to_stage(CoreStage::PostUpdate, add_areas.system()); .add_system_to_stage(CoreStage::PostUpdate, add_areas.system());
if config.speak_area_descriptions { if config.speak_area_descriptions {

View File

@ -6,6 +6,7 @@ use derive_more::{Deref, DerefMut};
use shadowcast::{vision_distance, Context, InputGrid}; use shadowcast::{vision_distance, Context, InputGrid};
use crate::{ use crate::{
bevy_rapier2d::prelude::*,
core::{Angle, Coordinates, Player, PointLike}, core::{Angle, Coordinates, Player, PointLike},
log::Log, log::Log,
map::{ITileType, Map, MapConfig}, map::{ITileType, Map, MapConfig},
@ -44,33 +45,16 @@ impl Viewshed {
} }
} }
#[derive(Clone, Debug, Default, Deref, DerefMut, Reflect)]
#[reflect(Component)]
pub struct VisibilityBlocked(pub Vec<bool>);
#[derive(Clone, Debug, Default, Deref, DerefMut, Reflect)] #[derive(Clone, Debug, Default, Deref, DerefMut, Reflect)]
#[reflect(Component)] #[reflect(Component)]
pub struct VisibleTiles(pub Vec<bool>); pub struct VisibleTiles(pub Vec<bool>);
fn add_visibility_indices( fn add_visibility_indices(
mut commands: Commands, mut commands: Commands,
query: Query< query: Query<(Entity, &Map), (Added<Map>, Without<VisibleTiles>, Without<RevealedTiles>)>,
(Entity, &Map),
(
Added<Map>,
Without<VisibilityBlocked>,
Without<VisibleTiles>,
Without<RevealedTiles>,
),
>,
map_config: Res<MapConfig>, map_config: Res<MapConfig>,
) { ) {
for (entity, map) in query.iter() { for (entity, map) in query.iter() {
let mut v = vec![];
for tile in &map.base.tiles {
v.push(tile.blocks_visibility());
}
commands.entity(entity).insert(VisibilityBlocked(v));
let count = map.count(); let count = map.count();
commands commands
.entity(entity) .entity(entity)
@ -81,85 +65,13 @@ fn add_visibility_indices(
} }
} }
pub(crate) fn set_visibility_blocked( struct VisibilityGrid<'a, F>(&'a Map, F);
map: &Map,
index: usize,
visibility_blockers: &Query<&BlocksVisibility>,
visibility_blocked: &mut VisibilityBlocked,
) {
let mut new_visibility_blocked = map.base.tiles[index].blocks_visibility();
if !new_visibility_blocked {
for e in &map.entities[index] {
if visibility_blockers.get(*e).is_ok() {
new_visibility_blocked = true;
break;
}
}
}
visibility_blocked[index] = new_visibility_blocked;
}
#[derive(Default, Deref, DerefMut)] impl<'a, F> InputGrid for VisibilityGrid<'a, F>
struct PrevIndex(HashMap<Entity, usize>); where
F: Fn(Coord) -> u8,
fn map_visibility_indexing( {
mut map: Query<(&Map, &mut VisibilityBlocked)>, type Grid = VisibilityGrid<'a, F>;
mut prev_index: ResMut<PrevIndex>,
query: Query<
(Entity, &Coordinates),
(
With<BlocksVisibility>,
Or<(Changed<Coordinates>, Changed<BlocksVisibility>)>,
),
>,
visibility_blockers: Query<&BlocksVisibility>,
) {
for (entity, coordinates) in query.iter() {
for (map, mut visibility_blocked) in map.iter_mut() {
let idx = coordinates.to_index(map.width());
if let Some(prev_idx) = prev_index.get(&entity) {
if *prev_idx == idx {
continue;
}
set_visibility_blocked(
&map,
*prev_idx,
&visibility_blockers,
&mut visibility_blocked,
);
}
visibility_blocked[idx] = true;
prev_index.insert(entity, idx);
}
}
}
fn remove_blocks_visibility(
mut prev_index: ResMut<PrevIndex>,
mut map: Query<(&Map, &mut VisibilityBlocked)>,
removed: RemovedComponents<BlocksVisibility>,
coordinates: Query<&Coordinates>,
blocks_visibility: Query<&BlocksVisibility>,
) {
for entity in removed.iter() {
for (map, mut visibility_blocked) in map.iter_mut() {
let prev = prev_index.get(&entity).cloned();
if let Some(prev) = prev {
prev_index.remove(&entity);
set_visibility_blocked(&map, prev, &blocks_visibility, &mut visibility_blocked);
}
if let Ok(coordinates) = coordinates.get_component::<Coordinates>(entity) {
let idx = coordinates.to_index(map.width());
set_visibility_blocked(&map, idx, &blocks_visibility, &mut visibility_blocked);
}
}
}
}
struct VisibilityGrid(Map, VisibilityBlocked);
impl InputGrid for VisibilityGrid {
type Grid = VisibilityGrid;
type Opacity = u8; type Opacity = u8;
@ -167,28 +79,42 @@ impl InputGrid for VisibilityGrid {
Size::new(grid.0.width() as u32, grid.0.height() as u32) Size::new(grid.0.width() as u32, grid.0.height() as u32)
} }
fn get_opacity(&self, grid: &Self::Grid, coord: Coord) -> Self::Opacity { fn get_opacity(&self, _grid: &Self::Grid, coord: Coord) -> Self::Opacity {
let point = (coord.x, coord.y); self.1(coord)
let index = point.to_index(grid.0.width());
if grid.1 .0[index] {
255
} else {
0
}
} }
} }
fn update_viewshed( fn update_viewshed<'a>(
viewshed: &mut Viewshed, mut viewers: Query<(&mut Viewshed, &Coordinates), Changed<Coordinates>>,
start: &Coordinates, map: Query<&Map>,
map: &Map, query_pipeline: Res<QueryPipeline>,
visibility_blocked: &VisibilityBlocked, collider_query: QueryPipelineColliderComponentsQuery,
blocks_visibility: Query<&BlocksVisibility>,
) { ) {
for (mut viewshed, start) in viewers.iter_mut() {
for map in map.iter() {
let mut context: Context<u8> = Context::default(); let mut context: Context<u8> = Context::default();
let vision_distance = vision_distance::Circle::new(viewshed.range); let vision_distance = vision_distance::Circle::new(viewshed.range);
let coord = Coord::new(start.x_i32(), start.y_i32()); let coord = Coord::new(start.x_i32(), start.y_i32());
viewshed.visible.clear(); viewshed.visible.clear();
let visibility_grid = VisibilityGrid(map.clone(), visibility_blocked.clone()); let collider_set = QueryPipelineColliderComponentsSet(&collider_query);
let shape = Cuboid::new(Vec2::new(0.5, 0.5).into());
let visibility_grid = VisibilityGrid(map, |coord: Coord| {
let shape_pos = (Vec2::new(coord.x as f32, coord.y as f32), 0.);
let mut opacity = 0;
query_pipeline.intersections_with_shape(
&collider_set,
&shape_pos.into(),
&shape,
InteractionGroups::all(),
Some(&|v| blocks_visibility.get(v.entity()).is_ok()),
|_| {
opacity = 255;
false
},
);
opacity
});
context.for_each_visible( context.for_each_visible(
coord, coord,
&visibility_grid, &visibility_grid,
@ -200,82 +126,11 @@ fn update_viewshed(
}, },
); );
} }
fn update_viewshed_for_coordinates(
mut viewers: Query<(&mut Viewshed, &Coordinates), Changed<Coordinates>>,
map: Query<(&Map, &VisibilityBlocked)>,
) {
for (mut viewshed, start) in viewers.iter_mut() {
for (map, visibility_blocked) in map.iter() {
update_viewshed(&mut viewshed, start, &map, &visibility_blocked);
}
}
}
#[derive(Clone, Debug, Default, Deref, DerefMut)]
struct PrevVisibilityBlocked(Vec<bool>);
fn visibility_blocked_added(
mut prev_visibility_blocked: ResMut<PrevVisibilityBlocked>,
added: Query<&VisibilityBlocked, Added<VisibilityBlocked>>,
) {
for visibility_blocked in added.iter() {
**prev_visibility_blocked = visibility_blocked.to_vec();
}
}
fn visibility_blocked_removed(
mut prev_visibility_blocked: ResMut<PrevVisibilityBlocked>,
removed: RemovedComponents<VisibilityBlocked>,
) {
for _ in removed.iter() {
prev_visibility_blocked.clear();
}
}
fn update_viewshed_for_visibility_blocked(
mut prev_visibility_blocked: ResMut<PrevVisibilityBlocked>,
map: Query<(&Map, &VisibilityBlocked), Changed<VisibilityBlocked>>,
mut queryset: QuerySet<(
Query<(Entity, &Viewshed)>,
Query<(&mut Viewshed, &Coordinates)>,
)>,
) {
for (map, visibility_blocked) in map.iter() {
let mut to_check = vec![];
for (index, new) in visibility_blocked.iter().enumerate() {
let changed = if let Some(prev) = prev_visibility_blocked.get(index) {
prev != new
} else {
true
};
if changed {
let coords = (index % map.width(), index / map.width());
for (entity, viewshed) in queryset.q0().iter() {
if viewshed.is_visible(&coords) {
to_check.push(entity);
}
}
}
}
to_check.dedup();
for entity in to_check {
if let Ok((mut viewshed, coordinates)) = queryset.q1_mut().get_mut(entity) {
update_viewshed(&mut viewshed, coordinates, &map, &visibility_blocked);
}
}
**prev_visibility_blocked = visibility_blocked.to_vec();
} }
} }
fn update_visible_and_revealed_tiles( fn update_visible_and_revealed_tiles(
mut map: Query< mut map: Query<(&Map, &mut RevealedTiles, &mut VisibleTiles)>,
(&Map, &mut RevealedTiles, &mut VisibleTiles),
(
With<VisibilityBlocked>,
Or<(Changed<Map>, Changed<VisibilityBlocked>)>,
),
>,
viewers: Query<&Viewshed, With<Player>>, viewers: Query<&Viewshed, With<Player>>,
) { ) {
for (map, mut revealed_tiles, mut visible_tiles) in map.iter_mut() { for (map, mut revealed_tiles, mut visible_tiles) in map.iter_mut() {
@ -296,9 +151,10 @@ fn log_visible(
time: Res<Time>, time: Res<Time>,
mut seen: Local<HashSet<Entity>>, mut seen: Local<HashSet<Entity>>,
mut recently_lost: Local<HashMap<Entity, Timer>>, mut recently_lost: Local<HashMap<Entity, Timer>>,
query_pipeline: Res<QueryPipeline>,
collider_query: QueryPipelineColliderComponentsQuery,
mut log: Query<&mut Log>, mut log: Query<&mut Log>,
viewers: Query<(&Viewshed, &Coordinates, &Transform), With<Player>>, viewers: Query<(&Viewshed, &Coordinates, &Transform), With<Player>>,
map: Query<&Map>,
names: Query<&Name>, names: Query<&Name>,
players: Query<&Player>, players: Query<&Player>,
dont_log_when_visible: Query<&DontLogWhenVisible>, dont_log_when_visible: Query<&DontLogWhenVisible>,
@ -315,17 +171,29 @@ fn log_visible(
let mut new_seen = HashSet::new(); let mut new_seen = HashSet::new();
if let Ok(mut log) = log.single_mut() { if let Ok(mut log) = log.single_mut() {
for (viewshed, coordinates, transform) in viewers.iter() { for (viewshed, coordinates, transform) in viewers.iter() {
let collider_set = QueryPipelineColliderComponentsSet(&collider_query);
let shape = Cuboid::new(Vec2::new(0.5, 0.5).into());
for viewed_coordinates in &viewshed.visible { for viewed_coordinates in &viewshed.visible {
for map in map.iter() { let shape_pos = (
let index = viewed_coordinates.to_index(map.width()); Vec2::new(viewed_coordinates.x() + 0.5, viewed_coordinates.y() + 0.5),
for entity in &map.entities[index] { 0.0,
)
.into();
query_pipeline.intersections_with_shape(
&collider_set,
&shape_pos,
&shape,
InteractionGroups::all(),
None,
|handle| {
let entity = handle.entity();
if recently_lost.contains_key(&entity) { if recently_lost.contains_key(&entity) {
continue; return true;
} }
if let Ok(name) = names.get(*entity) { if let Ok(name) = names.get(entity) {
if players.get(*entity).is_err() { if players.get(entity).is_err() {
if !seen.contains(&*entity) if !seen.contains(&entity)
&& dont_log_when_visible.get(*entity).is_err() && dont_log_when_visible.get(entity).is_err()
{ {
let name = name.to_string(); let name = name.to_string();
let forward = transform.local_x(); let forward = transform.local_x();
@ -334,11 +202,12 @@ fn log_visible(
.direction_and_distance(viewed_coordinates, Some(yaw)); .direction_and_distance(viewed_coordinates, Some(yaw));
log.push(format!("{}: {}", name, location)); log.push(format!("{}: {}", name, location));
} }
new_seen.insert(*entity); new_seen.insert(entity);
}
}
} }
} }
true
},
);
} }
} }
} }
@ -355,60 +224,12 @@ pub struct VisibilityPlugin;
impl Plugin for VisibilityPlugin { impl Plugin for VisibilityPlugin {
fn build(&self, app: &mut AppBuilder) { fn build(&self, app: &mut AppBuilder) {
const UPDATE_VISIBILITY_INDEX: &str = "UPDATE_VISIBILITY_INDEX"; app.add_system(add_visibility_indices.system())
const UPDATE_VIEWSHED: &str = "UPDATE_VIEWSHED"; .add_system_to_stage(CoreStage::PostUpdate, update_viewshed.system())
const MAP_VISIBILITY: &str = "MAP_VISIBILITY";
app.init_resource::<PrevVisibilityBlocked>()
.init_resource::<PrevIndex>()
.add_system(visibility_blocked_added.system())
.add_system_to_stage(CoreStage::PostUpdate, visibility_blocked_removed.system())
.add_system(add_visibility_indices.system())
.add_system_to_stage( .add_system_to_stage(
CoreStage::PostUpdate, CoreStage::PostUpdate,
add_visibility_indices update_visible_and_revealed_tiles.system(),
.system()
.before(UPDATE_VISIBILITY_INDEX),
) )
.add_system_to_stage( .add_system_to_stage(CoreStage::PostUpdate, log_visible.system());
CoreStage::PostUpdate,
remove_blocks_visibility
.system()
.before(UPDATE_VISIBILITY_INDEX),
)
.add_system_to_stage(
CoreStage::PostUpdate,
map_visibility_indexing
.system()
.label(UPDATE_VISIBILITY_INDEX)
.after(crate::map::UPDATE_ENTITY_INDEX_LABEL),
)
.add_system_to_stage(
CoreStage::PostUpdate,
update_viewshed_for_coordinates
.system()
.label(UPDATE_VIEWSHED)
.after(UPDATE_VISIBILITY_INDEX),
)
.add_system_to_stage(
CoreStage::PostUpdate,
update_viewshed_for_visibility_blocked
.system()
.label(UPDATE_VIEWSHED)
.after(UPDATE_VISIBILITY_INDEX),
)
.add_system_to_stage(
CoreStage::PostUpdate,
update_visible_and_revealed_tiles
.system()
.label(MAP_VISIBILITY)
.after(UPDATE_VIEWSHED),
)
.add_system_to_stage(
CoreStage::PostUpdate,
log_visible
.system()
.label(LOG_VISIBLE_LABEL)
.after(MAP_VISIBILITY),
);
} }
} }