diff --git a/src/visibility.rs b/src/visibility.rs index 3a9269e..305277b 100644 --- a/src/visibility.rs +++ b/src/visibility.rs @@ -1,7 +1,10 @@ -use std::collections::{HashMap, HashSet}; +use std::{ + cell::RefCell, + collections::{HashMap, HashSet}, +}; use bevy::prelude::*; -use bevy_rapier2d::na::UnitComplex; +use bevy_rapier2d::{na, na::UnitComplex}; use coord_2d::{Coord, Size}; use derive_more::{Deref, DerefMut}; use shadowcast::{vision_distance, Context, InputGrid}; @@ -43,9 +46,6 @@ impl Viewshed { } } -#[derive(Clone, Copy, Debug, Default, Reflect)] -struct VisibilityCollider; - #[derive(Clone, Copy, Debug, Deref, DerefMut, Reflect)] #[reflect(Component)] pub struct Visible(pub u8); @@ -91,12 +91,6 @@ pub enum VisibilityChanged { Lost { viewer: Entity, viewed: Entity }, } -#[derive(Clone, Debug, Default, Deref, DerefMut)] -struct VisibilityColliderToViewshed(HashMap); - -#[derive(Clone, Debug, Default, Deref, DerefMut)] -struct ViewshedToVisibilityCollider(HashMap); - impl PointLike for Coord { fn x(&self) -> f32 { self.x as f32 @@ -120,86 +114,28 @@ fn add_visibility_indices( } } -fn viewshed_added( - mut commands: Commands, - mut collider_to_viewshed: ResMut, - mut viewshed_to_collider: ResMut, - query: Query>, +fn viewshed_removed( + query: RemovedComponents, + visible_entities: Query<&VisibleEntities>, + mut events: EventWriter, ) { for entity in query.iter() { - let id = commands - .spawn_bundle(ColliderBundle { - collider_type: ColliderType::Sensor, - flags: ColliderFlags { - active_collision_types: ActiveCollisionTypes::all(), - active_events: ActiveEvents::INTERSECTION_EVENTS, - ..Default::default() - }, - ..Default::default() - }) - .insert(VisibilityCollider) - .id(); - viewshed_to_collider.insert(entity, id); - collider_to_viewshed.insert(id, entity); - } -} - -fn viewshed_changed( - mut commands: Commands, - viewshed_to_collider: Res, - query: Query<(Entity, &Viewshed, &RigidBodyPosition), Changed>, - mut collider_data: Query< - (Option<&mut ColliderShape>, Option<&mut ColliderPosition>), - With, - >, -) { - for (entity, viewshed, position) in query.iter() { - if let Some(collider_entity) = viewshed_to_collider.get(&entity) { - if let Ok((collider_shape, collider_position)) = collider_data.get_mut(*collider_entity) - { - if viewshed.visible_points.len() < 2 { - commands.entity(*collider_entity).remove::(); - } else { - let translation = position.position.translation; - let mut points = vec![]; - for p in &viewshed.visible_points { - points.push(point!( - p.x() - translation.x + 0.5, - p.y() - translation.y + 0.5 - )); - } - if let Some(shape) = ColliderShape::convex_hull(&points) { - if let (Some(mut collider_shape), Some(mut collider_position)) = - (collider_shape, collider_position) - { - *collider_shape = shape; - *collider_position = translation.into(); - } - } - } + if let Ok(visible) = visible_entities.get(entity) { + for e in visible.iter() { + events.send(VisibilityChanged::Lost { + viewer: entity, + viewed: *e, + }) } } } } -fn viewshed_removed( - mut collider_to_viewshed: ResMut, - mut viewshed_to_collider: ResMut, - query: RemovedComponents, -) { - for entity in query.iter() { - if let Some(collider) = viewshed_to_collider.get(&entity) { - collider_to_viewshed.remove(&collider); - } - viewshed_to_collider.remove(&entity); - } -} - -pub struct VisibilityGrid<'a, F>(pub &'a Map, pub F); +pub struct VisibilityGrid<'a, F>(pub &'a Map, pub RefCell>); impl<'a, F> InputGrid for VisibilityGrid<'a, F> where - F: Fn(Coord) -> u8, + F: FnMut(Coord) -> u8, { type Grid = VisibilityGrid<'a, F>; @@ -210,46 +146,57 @@ where } fn get_opacity(&self, _grid: &Self::Grid, coord: Coord) -> Self::Opacity { - self.1(coord) + (self.1.borrow_mut())(coord) } } fn update_viewshed( viewer_entity: &Entity, viewshed: &mut Viewshed, + visible_entities: &mut VisibleEntities, start: &dyn PointLike, query_pipeline: &QueryPipeline, collider_query: &QueryPipelineColliderComponentsQuery, map: &Map, - visible: &Query<&Visible>, + visible_query: &Query<&Visible>, + events: &mut EventWriter, ) { let mut context: Context = Context::default(); let vision_distance = vision_distance::Circle::new(viewshed.range); let coord = Coord::new(start.x_i32(), start.y_i32()); - let shape = Cuboid::new(Vec2::new(0.49, 0.49).into()); - let range = viewshed.range as f32; let collider_set = QueryPipelineColliderComponentsSet(&collider_query); - let visibility_grid = VisibilityGrid(map, |coord: Coord| { - if coord.distance(start) > range { - return 255; - } - let shape_pos = (Vec2::new(coord.x as f32 + 0.5, coord.y as f32 + 0.5), 0.); - let mut opacity = 0; - query_pipeline.intersections_with_shape( - &collider_set, - &shape_pos.into(), - &shape, - InteractionGroups::all(), - Some(&|v| v.entity() != *viewer_entity && visible.get(v.entity()).is_ok()), - |handle| { - if let Ok(visible) = visible.get(handle.entity()) { - opacity = **visible; - } - true - }, - ); - opacity - }); + let origin = point!(start.x(), start.y()); + let mut new_visible_entities = HashSet::::new(); + let visibility_grid = VisibilityGrid( + map, + RefCell::new(Box::new(|coord: Coord| { + let dest = point!(coord.x as f32 + 0.5, coord.y as f32 + 0.5); + if na::distance(&origin, &dest) >= viewshed.range as f32 { + return u8::MAX; + } + let direction = (dest - origin).normalize(); + let ray = Ray::new(origin, direction); + let max_toi = na::distance(&origin, &dest); + let mut opacity = 0; + query_pipeline.intersections_with_ray( + &collider_set, + &ray, + max_toi, + true, + InteractionGroups::all(), + Some(&|v| v.entity() != *viewer_entity && visible_query.get(v.entity()).is_ok()), + |handle, _intersection| { + let entity = handle.entity(); + new_visible_entities.insert(entity); + if let Ok(visible) = visible_query.get(entity) { + opacity = **visible; + } + opacity != u8::MAX + }, + ); + opacity + })), + ); let mut new_visible = HashSet::new(); context.for_each_visible( coord, @@ -264,22 +211,38 @@ fn update_viewshed( if viewshed.visible_points != new_visible { viewshed.visible_points = new_visible; } + if new_visible_entities != **visible_entities { + for lost in visible_entities.difference(&new_visible_entities) { + events.send(VisibilityChanged::Lost { + viewer: *viewer_entity, + viewed: *lost, + }); + } + for gained in new_visible_entities.difference(visible_entities) { + events.send(VisibilityChanged::Gained { + viewer: *viewer_entity, + viewed: *gained, + }); + } + **visible_entities = new_visible_entities; + } } fn update_viewshed_for_coordinates( config: Res, visible: Query<(Entity, &Coordinates), (Changed, With)>, - mut viewers: Query<(Entity, &mut Viewshed, &Coordinates)>, + mut viewers: Query<(Entity, &mut Viewshed, &mut VisibleEntities, &Coordinates)>, map: Query<&Map>, query_pipeline: Res, collider_query: QueryPipelineColliderComponentsQuery, visible_query: Query<&Visible>, + mut changed: EventWriter, ) { if !config.query_pipeline_active { return; } for (visible_entity, coordinates) in visible.iter() { - for (viewer_entity, mut viewshed, start) in viewers.iter_mut() { + for (viewer_entity, mut viewshed, mut visible_entities, start) in viewers.iter_mut() { if viewer_entity == visible_entity || coordinates.distance(start) > viewshed.range as f32 { @@ -289,11 +252,13 @@ fn update_viewshed_for_coordinates( update_viewshed( &viewer_entity, &mut viewshed, + &mut visible_entities, start, &*query_pipeline, &collider_query, map, &visible_query, + &mut changed, ); } } @@ -302,25 +267,31 @@ fn update_viewshed_for_coordinates( fn update_viewshed_for_start( config: Res, - mut viewers: Query<(Entity, &mut Viewshed, &Coordinates), Changed>, + mut viewers: Query< + (Entity, &mut Viewshed, &mut VisibleEntities, &Coordinates), + Changed, + >, map: Query<&Map>, query_pipeline: Res, collider_query: QueryPipelineColliderComponentsQuery, visible: Query<&Visible>, + mut changed: EventWriter, ) { if !config.query_pipeline_active { return; } - for (viewer_entity, mut viewshed, start) in viewers.iter_mut() { + for (viewer_entity, mut viewshed, mut visible_entities, start) in viewers.iter_mut() { if let Ok(map) = map.single() { update_viewshed( &viewer_entity, &mut viewshed, + &mut visible_entities, start, &*query_pipeline, &collider_query, map, &visible, + &mut changed, ); } } @@ -333,6 +304,7 @@ fn remove_visible( query_pipeline: Res, collider_query: QueryPipelineColliderComponentsQuery, visible: Query<&Visible>, + mut changed: EventWriter, ) { for removed in removed.iter() { for (viewer_entity, mut viewshed, mut visible_entities, start) in viewers.iter_mut() { @@ -344,18 +316,20 @@ fn remove_visible( update_viewshed( &viewer_entity, &mut viewshed, + &mut visible_entities, start, &*query_pipeline, &collider_query, map, &visible, + &mut changed, ); } } } } -fn update_visible_and_revealed_tiles( +fn update_revealed_tiles( mut map: Query<(&Map, &mut RevealedTiles)>, viewers: Query<&Viewshed, With>, ) { @@ -372,54 +346,6 @@ fn update_visible_and_revealed_tiles( } } -fn intersection( - mut events: EventReader, - collider_to_viewshed: Res, - colliders: Query>, - mut viewers: Query<&mut VisibleEntities>, - visible: Query>, - names: Query<&Name>, - mut visibility_changed: EventWriter, -) { - for event in events.iter() { - if let Some((visibility_collider, other)) = - target_and_other(event.collider1.entity(), event.collider2.entity(), |v| { - colliders.get(v).is_ok() - }) - { - if visible.get(other).is_ok() { - if let Some(viewshed_entity) = collider_to_viewshed.get(&visibility_collider) { - if let Ok(mut visible_entities) = viewers.get_mut(*viewshed_entity) { - if event.intersecting { - println!( - "{:?} is visible, {:?}", - other, - names.get(other).map(|v| v.to_string()) - ); - visibility_changed.send(VisibilityChanged::Gained { - viewer: *viewshed_entity, - viewed: other, - }); - visible_entities.insert(other); - } else { - println!( - "{:?} is no longer visible: {:?}", - other, - names.get(other).map(|v| v.to_string()) - ); - visibility_changed.send(VisibilityChanged::Lost { - viewer: *viewshed_entity, - viewed: other, - }); - visible_entities.remove(&other); - } - } - } - } - } - } -} - fn log_visible( time: Res