Refactor visibility to be collider and event-based.
This commit is contained in:
parent
8f5b1b8f08
commit
42912e59a8
|
@ -95,7 +95,7 @@ where
|
|||
let (x, y) = **coordinates;
|
||||
let x = x as i32;
|
||||
let y = y as i32;
|
||||
if viewshed.visible.contains(&(x, y)) {
|
||||
if viewshed.is_point_visible(&(x, y)) {
|
||||
types.push(*t);
|
||||
}
|
||||
}
|
||||
|
@ -176,7 +176,7 @@ where
|
|||
let (x, y) = ***coordinates;
|
||||
let x = x as i32;
|
||||
let y = y as i32;
|
||||
viewshed.visible.contains(&(x, y))
|
||||
viewshed.is_point_visible(&(x, y))
|
||||
})
|
||||
.collect::<Vec<(&Coordinates, &ExplorationType)>>();
|
||||
features.sort_by(|(c1, _), (c2, _)| c1.partial_cmp(c2).unwrap());
|
||||
|
|
25
src/sound.rs
25
src/sound.rs
|
@ -8,7 +8,7 @@ use rand::random;
|
|||
use crate::{
|
||||
core::{Coordinates, CoreConfig, Player, PointLike},
|
||||
exploration::ExplorationFocused,
|
||||
visibility::Viewshed,
|
||||
visibility::{Viewshed, VisibleEntities},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Reflect)]
|
||||
|
@ -151,14 +151,14 @@ fn sound_icon<S>(
|
|||
config: Res<SoundConfig<S>>,
|
||||
state: Res<State<S>>,
|
||||
time: Res<Time>,
|
||||
viewers: Query<&Viewshed, With<Player>>,
|
||||
viewers: Query<&VisibleEntities, With<Player>>,
|
||||
mut icons: Query<(
|
||||
Entity,
|
||||
&mut SoundIcon,
|
||||
Option<&Coordinates>,
|
||||
Option<&Parent>,
|
||||
&mut Sound,
|
||||
)>,
|
||||
coordinates_storage: Query<&Coordinates>,
|
||||
buffers: Res<Assets<Buffer>>,
|
||||
) where
|
||||
S: Component + Clone + Debug + Eq + Hash,
|
||||
|
@ -168,18 +168,14 @@ fn sound_icon<S>(
|
|||
{
|
||||
return;
|
||||
}
|
||||
for viewer in viewers.iter() {
|
||||
for (mut icon, coordinates, parent, mut sound) in icons.iter_mut() {
|
||||
let coords = if let Some(coordinates) = coordinates {
|
||||
*coordinates
|
||||
} else if let Some(parent) = parent {
|
||||
*coordinates_storage
|
||||
.get(**parent)
|
||||
.expect("If `SoundIcon` is a child, its parent must have `Coordinates`")
|
||||
for visible in viewers.iter() {
|
||||
for (icon_entity, mut icon, coordinates, parent, mut sound) in icons.iter_mut() {
|
||||
let entity = if coordinates.is_some() {
|
||||
icon_entity
|
||||
} else {
|
||||
panic!("No `Coordinates` on `SoundIcon` or parent");
|
||||
**parent.unwrap()
|
||||
};
|
||||
if viewer.is_visible(&coords) {
|
||||
if visible.contains(&entity) {
|
||||
let looping = sound.looping;
|
||||
if looping {
|
||||
sound.state = SoundState::Playing;
|
||||
|
@ -193,7 +189,6 @@ fn sound_icon<S>(
|
|||
let buffer = buffers.get_handle(icon.sound);
|
||||
sound.looping = icon.interval.is_none();
|
||||
if sound.buffer != buffer {
|
||||
sound.stop();
|
||||
sound.buffer = buffer;
|
||||
}
|
||||
sound.gain = icon.gain;
|
||||
|
@ -201,6 +196,8 @@ fn sound_icon<S>(
|
|||
sound.reference_distance = icon.reference_distance;
|
||||
sound.max_distance = icon.max_distance;
|
||||
sound.rolloff_factor = icon.rolloff_factor;
|
||||
} else {
|
||||
sound.state = SoundState::Stopped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_rapier2d::na::UnitComplex;
|
||||
use coord_2d::{Coord, Size};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use shadowcast::{vision_distance, Context, InputGrid};
|
||||
|
@ -10,6 +11,7 @@ use crate::{
|
|||
core::{Angle, Coordinates, Player, PointLike},
|
||||
log::Log,
|
||||
map::{ITileType, Map, MapConfig},
|
||||
utils::target_and_other,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deref, DerefMut, Reflect)]
|
||||
|
@ -21,6 +23,7 @@ impl Default for BlocksVisibility {
|
|||
Self(u8::MAX)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct DontLogWhenVisible;
|
||||
|
@ -31,29 +34,58 @@ pub struct RevealedTiles(pub Vec<bool>);
|
|||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Viewshed {
|
||||
pub visible: HashSet<(i32, i32)>,
|
||||
pub range: u32,
|
||||
pub visible_points: HashSet<(i32, i32)>,
|
||||
}
|
||||
|
||||
impl Default for Viewshed {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
range: 15,
|
||||
visible: HashSet::new(),
|
||||
visible_points: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Viewshed {
|
||||
pub fn is_visible(&self, point: &dyn PointLike) -> bool {
|
||||
self.visible.contains(&point.into())
|
||||
pub fn is_point_visible(&self, point: &dyn PointLike) -> bool {
|
||||
self.visible_points.contains(&point.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Reflect)]
|
||||
struct VisibilityCollider;
|
||||
|
||||
#[derive(Clone, Debug, Default, Deref, DerefMut)]
|
||||
pub struct VisibleEntities(HashSet<Entity>);
|
||||
|
||||
#[derive(Clone, Debug, Default, Deref, DerefMut, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct VisibleTiles(pub Vec<bool>);
|
||||
|
||||
#[derive(Bundle, Default)]
|
||||
pub struct ViewshedBundle {
|
||||
pub viewshed: Viewshed,
|
||||
pub visible_entities: VisibleEntities,
|
||||
}
|
||||
|
||||
impl ViewshedBundle {
|
||||
pub fn new(range: u32) -> Self {
|
||||
Self {
|
||||
viewshed: Viewshed {
|
||||
range,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum VisibilityChanged {
|
||||
Gained { viewer: Entity, viewed: Entity },
|
||||
Lost { viewer: Entity, viewed: Entity },
|
||||
}
|
||||
|
||||
impl PointLike for Coord {
|
||||
fn x(&self) -> f32 {
|
||||
self.x as f32
|
||||
|
@ -80,6 +112,90 @@ fn add_visibility_indices(
|
|||
}
|
||||
}
|
||||
|
||||
fn viewshed_added(mut commands: Commands, query: Query<Entity, Added<Viewshed>>) {
|
||||
for entity in query.iter() {
|
||||
let id = commands.spawn().insert(VisibilityCollider).id();
|
||||
commands.entity(entity).push_children(&[id]);
|
||||
}
|
||||
}
|
||||
|
||||
fn viewshed_changed(
|
||||
mut commands: Commands,
|
||||
query: Query<(Entity, &Viewshed, &RigidBodyPosition), Changed<Viewshed>>,
|
||||
mut collider_data: Query<
|
||||
(&Parent, Entity, Option<&mut ColliderShape>),
|
||||
With<VisibilityCollider>,
|
||||
>,
|
||||
) {
|
||||
for (entity, viewshed, position) in query.iter() {
|
||||
for (parent, child_entity, collider_shape) in collider_data.iter_mut() {
|
||||
if **parent != entity {
|
||||
continue;
|
||||
}
|
||||
if viewshed.visible_points.len() < 2 {
|
||||
commands.entity(child_entity).remove::<ColliderShape>();
|
||||
} else {
|
||||
let position = position.position;
|
||||
let mut points = vec![];
|
||||
for p in &viewshed.visible_points {
|
||||
points.push(point!(
|
||||
position.translation.x - p.x(),
|
||||
position.translation.y - p.y()
|
||||
));
|
||||
}
|
||||
if let Some(shape) = ColliderShape::convex_hull(&points) {
|
||||
//println!("Shape for {:?}", points);
|
||||
if let Some(mut collider_shape) = collider_shape {
|
||||
*collider_shape = shape;
|
||||
} else {
|
||||
//println!("Creating collider at {:?}", coordinates.i32());
|
||||
commands.entity(child_entity).insert_bundle(ColliderBundle {
|
||||
collider_type: ColliderType::Sensor,
|
||||
shape,
|
||||
flags: ActiveEvents::INTERSECTION_EVENTS.into(),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lock_rotation(
|
||||
mut query: Query<(&Parent, &mut ColliderPosition), With<VisibilityCollider>>,
|
||||
positions: Query<&RigidBodyPosition>,
|
||||
) {
|
||||
for (parent, mut position) in query.iter_mut() {
|
||||
if let Ok(parent_position) = positions.get(**parent) {
|
||||
let delta = parent_position
|
||||
.position
|
||||
.rotation
|
||||
.angle_to(&UnitComplex::new(0.));
|
||||
println!("Syncing: {:?}", delta);
|
||||
position.rotation = UnitComplex::new(delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn viewshed_removed(
|
||||
mut commands: Commands,
|
||||
query: RemovedComponents<Viewshed>,
|
||||
children: Query<&Children>,
|
||||
collider_shapes: Query<Entity, With<VisibilityCollider>>,
|
||||
) {
|
||||
for entity in query.iter() {
|
||||
if let Ok(children) = children.get(entity) {
|
||||
for child in children.iter() {
|
||||
if collider_shapes.get(*child).is_ok() {
|
||||
commands.entity(*child).despawn_recursive();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VisibilityGrid<'a, F>(pub &'a Map, pub F);
|
||||
|
||||
impl<'a, F> InputGrid for VisibilityGrid<'a, F>
|
||||
|
@ -128,7 +244,6 @@ fn update_viewshed(
|
|||
Some(&|v| v.entity() != *viewer_entity && blocks_visibility.get(v.entity()).is_ok()),
|
||||
|handle| {
|
||||
if let Ok(blocks_visibility) = blocks_visibility.get(handle.entity()) {
|
||||
// println!("Visibility blocked at {:?}", coord);
|
||||
opacity = **blocks_visibility;
|
||||
false
|
||||
} else {
|
||||
|
@ -149,8 +264,8 @@ fn update_viewshed(
|
|||
new_visible.insert((coord.x, coord.y));
|
||||
},
|
||||
);
|
||||
if viewshed.visible != new_visible {
|
||||
viewshed.visible = new_visible;
|
||||
if viewshed.visible_points != new_visible {
|
||||
viewshed.visible_points = new_visible;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,14 +323,18 @@ fn update_viewshed_for_start(
|
|||
|
||||
fn remove_blocks_visibility(
|
||||
removed: RemovedComponents<BlocksVisibility>,
|
||||
mut viewers: Query<(Entity, &mut Viewshed, &Coordinates)>,
|
||||
mut viewers: Query<(Entity, &mut Viewshed, &mut VisibleEntities, &Coordinates)>,
|
||||
map: Query<&Map>,
|
||||
query_pipeline: Res<QueryPipeline>,
|
||||
collider_query: QueryPipelineColliderComponentsQuery,
|
||||
blocks_visibility: Query<&BlocksVisibility>,
|
||||
) {
|
||||
for _ in removed.iter() {
|
||||
for (viewer_entity, mut viewshed, start) in viewers.iter_mut() {
|
||||
for removed in removed.iter() {
|
||||
for (viewer_entity, mut viewshed, mut visible_entities, start) in viewers.iter_mut() {
|
||||
if !visible_entities.contains(&removed) {
|
||||
continue;
|
||||
}
|
||||
visible_entities.remove(&removed);
|
||||
if let Ok(map) = map.single() {
|
||||
update_viewshed(
|
||||
&viewer_entity,
|
||||
|
@ -240,7 +359,7 @@ fn update_visible_and_revealed_tiles(
|
|||
for t in visible_tiles.iter_mut() {
|
||||
*t = false
|
||||
}
|
||||
for v in viewshed.visible.iter() {
|
||||
for v in viewshed.visible_points.iter() {
|
||||
let idx = v.to_index(map.width);
|
||||
if idx >= revealed_tiles.len() || idx >= visible_tiles.len() {
|
||||
continue;
|
||||
|
@ -252,73 +371,111 @@ fn update_visible_and_revealed_tiles(
|
|||
}
|
||||
}
|
||||
|
||||
fn intersection(
|
||||
mut events: EventReader<IntersectionEvent>,
|
||||
colliders: Query<&Parent, With<VisibilityCollider>>,
|
||||
mut viewers: Query<&mut VisibleEntities>,
|
||||
names: Query<&Name>,
|
||||
parents: Query<&Parent>,
|
||||
mut visibility_changed: EventWriter<VisibilityChanged>,
|
||||
) {
|
||||
for event in events.iter() {
|
||||
println!("{:?}", event);
|
||||
if let Some((visibility_collider, other)) =
|
||||
target_and_other(event.collider1.entity(), event.collider2.entity(), |v| {
|
||||
colliders.get(v).is_ok()
|
||||
})
|
||||
{
|
||||
if let Ok(parent) = colliders.get(visibility_collider) {
|
||||
if let Ok(mut visible_entities) = viewers.get_mut(**parent) {
|
||||
if event.intersecting {
|
||||
println!("{:?} is visible: {:?}", other, names.get(other));
|
||||
if let Ok(p) = parents.get(other) {
|
||||
println!("{:?}", names.get(**p));
|
||||
}
|
||||
visibility_changed.send(VisibilityChanged::Gained {
|
||||
viewer: **parent,
|
||||
viewed: other,
|
||||
});
|
||||
visible_entities.insert(other);
|
||||
} else {
|
||||
println!("{:?} is not visible", other);
|
||||
visibility_changed.send(VisibilityChanged::Lost {
|
||||
viewer: **parent,
|
||||
viewed: other,
|
||||
});
|
||||
visible_entities.remove(&other);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn log_visible(
|
||||
time: Res<Time>,
|
||||
mut seen: Local<HashSet<Entity>>,
|
||||
mut recently_lost: Local<HashMap<Entity, Timer>>,
|
||||
query_pipeline: Res<QueryPipeline>,
|
||||
collider_query: QueryPipelineColliderComponentsQuery,
|
||||
mut events: EventReader<IntersectionEvent>,
|
||||
mut log: Query<&mut Log>,
|
||||
viewers: Query<(Entity, &Viewshed, &Coordinates, &Transform), With<Player>>,
|
||||
names: Query<&Name>,
|
||||
dont_log_when_visible: Query<&DontLogWhenVisible>,
|
||||
viewer_colliders: Query<&Parent, With<VisibilityCollider>>,
|
||||
viewers: Query<(Entity, &Coordinates, &Transform), (With<Player>, With<Viewshed>)>,
|
||||
viewed: Query<
|
||||
(
|
||||
Entity,
|
||||
&Name,
|
||||
Option<&RigidBodyPosition>,
|
||||
Option<&ColliderPosition>,
|
||||
),
|
||||
Without<DontLogWhenVisible>,
|
||||
>,
|
||||
) {
|
||||
for timer in recently_lost.values_mut() {
|
||||
timer.tick(time.delta());
|
||||
}
|
||||
recently_lost.retain(|_, v| !v.finished());
|
||||
let mut new_seen = HashSet::new();
|
||||
if let Ok(mut log) = log.single_mut() {
|
||||
for (viewer_entity, viewshed, coordinates, transform) in viewers.iter() {
|
||||
let collider_set = QueryPipelineColliderComponentsSet(&collider_query);
|
||||
let shape = Cuboid::new(Vec2::new(0.49, 0.49).into());
|
||||
for viewed_coordinates in &viewshed.visible {
|
||||
if coordinates.distance(viewed_coordinates) >= viewshed.range as f32 - 0.1 {
|
||||
continue;
|
||||
}
|
||||
let shape_pos = (
|
||||
Vec2::new(viewed_coordinates.x() + 0.5, viewed_coordinates.y() + 0.5),
|
||||
0.0,
|
||||
)
|
||||
.into();
|
||||
query_pipeline.intersections_with_shape(
|
||||
&collider_set,
|
||||
&shape_pos,
|
||||
&shape,
|
||||
InteractionGroups::all(),
|
||||
Some(&|v| v.entity() != viewer_entity),
|
||||
|handle| {
|
||||
let entity = handle.entity();
|
||||
if recently_lost.contains_key(&entity) {
|
||||
new_seen.insert(entity);
|
||||
recently_lost.remove(&entity);
|
||||
return true;
|
||||
}
|
||||
if let Ok(name) = names.get(entity) {
|
||||
if !seen.contains(&entity)
|
||||
&& !new_seen.contains(&entity)
|
||||
&& dont_log_when_visible.get(entity).is_err()
|
||||
recently_lost.retain(|_entity, timer| !timer.finished());
|
||||
for event in events.iter() {
|
||||
if let Some((viewer_collider, other)) =
|
||||
target_and_other(event.collider1.entity(), event.collider2.entity(), |v| {
|
||||
viewer_colliders.get(v).is_ok()
|
||||
})
|
||||
{
|
||||
if let Ok((viewed_entity, name, rigid_body_position, collider_position)) =
|
||||
viewed.get(other)
|
||||
{
|
||||
if event.intersecting {
|
||||
if recently_lost.contains_key(&viewed_entity) {
|
||||
recently_lost.remove(&viewed_entity);
|
||||
} else {
|
||||
if let Ok(parent) = viewer_colliders.get(viewer_collider) {
|
||||
if let Ok((viewer_entity, coordinates, transform)) =
|
||||
viewers.get(**parent)
|
||||
{
|
||||
let name = name.to_string();
|
||||
let forward = transform.local_x();
|
||||
let yaw = Angle::Radians(forward.y.atan2(forward.x));
|
||||
let location = coordinates
|
||||
.direction_and_distance(viewed_coordinates, Some(yaw));
|
||||
log.push(format!("{}: {}", name, location));
|
||||
if viewer_entity == viewed_entity {
|
||||
continue;
|
||||
}
|
||||
if let Ok(mut log) = log.single_mut() {
|
||||
let viewed_coordinates = if let Some(p) = rigid_body_position {
|
||||
(p.position.translation.x, p.position.translation.y)
|
||||
} else if let Some(p) = collider_position {
|
||||
(p.translation.x, p.translation.y)
|
||||
} else {
|
||||
(0., 0.)
|
||||
};
|
||||
let forward = transform.local_x();
|
||||
let yaw = Angle::Radians(forward.y.atan2(forward.x));
|
||||
let location = coordinates
|
||||
.direction_and_distance(&viewed_coordinates, Some(yaw));
|
||||
log.push(format!("{}: {}", **name, location));
|
||||
}
|
||||
}
|
||||
new_seen.insert(entity);
|
||||
}
|
||||
true
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
recently_lost.insert(viewed_entity, Timer::from_seconds(2., false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let recently_lost_entities = seen.difference(&new_seen);
|
||||
for entity in recently_lost_entities {
|
||||
recently_lost.insert(*entity, Timer::from_seconds(2., false));
|
||||
}
|
||||
*seen = new_seen;
|
||||
}
|
||||
|
||||
pub const LOG_VISIBLE_LABEL: &str = "LOG_VISIBLE";
|
||||
|
@ -327,17 +484,20 @@ pub struct VisibilityPlugin;
|
|||
|
||||
impl Plugin for VisibilityPlugin {
|
||||
fn build(&self, app: &mut AppBuilder) {
|
||||
app.add_system(add_visibility_indices.system())
|
||||
.add_system_to_stage(
|
||||
CoreStage::PostUpdate,
|
||||
update_viewshed_for_coordinates.system(),
|
||||
)
|
||||
.add_system_to_stage(CoreStage::PostUpdate, update_viewshed_for_start.system())
|
||||
app.add_event::<VisibilityChanged>()
|
||||
.add_system(add_visibility_indices.system())
|
||||
.add_system(viewshed_added.system())
|
||||
.add_system(viewshed_changed.system())
|
||||
.add_system(lock_rotation.system())
|
||||
.add_system_to_stage(CoreStage::PostUpdate, viewshed_removed.system())
|
||||
.add_system(update_viewshed_for_coordinates.system())
|
||||
.add_system(update_viewshed_for_start.system())
|
||||
.add_system_to_stage(CoreStage::PostUpdate, remove_blocks_visibility.system())
|
||||
.add_system_to_stage(
|
||||
CoreStage::PostUpdate,
|
||||
update_visible_and_revealed_tiles.system(),
|
||||
)
|
||||
.add_system(intersection.system())
|
||||
.add_system_to_stage(CoreStage::PostUpdate, log_visible.system());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user