diff --git a/src/visibility.rs b/src/visibility.rs index aed2a7a..46b75d8 100644 --- a/src/visibility.rs +++ b/src/visibility.rs @@ -6,10 +6,7 @@ use std::{ use bevy::prelude::*; -use bevy_rapier2d::{ - na::{Isometry2, Vector2}, - parry::bounding_volume::BoundingVolume, -}; +use bevy_rapier2d::{na::Isometry2, parry::bounding_volume::BoundingVolume}; use coord_2d::{Coord, Size}; use shadowcast::{vision_distance, Context, InputGrid}; @@ -50,42 +47,33 @@ impl Viewshed { fn update( &mut self, + commands: &mut Commands, viewer_entity: &Entity, visible_entities: &mut VisibleEntities, start: &Vec2, rapier_context: &RapierContext, visible_query: &Query<(&Visible, &Collider, &GlobalTransform)>, - events: &mut EventWriter, - cache: &mut HashMap<(i32, i32), (u8, HashSet)>, + cache: &mut RefCell)>>, ) { // println!("Start"); let mut boxes = vec![]; let shape = Collider::cuboid(self.range as f32, self.range as f32); - rapier_context.intersections_with_shape( - *start, - 0., - &shape, - QueryFilter::new().predicate(&|e| visible_query.contains(e)), - |entity| { - if let Ok((_, collider, transform)) = visible_query.get(entity) { - let position = Isometry2::new( - Vector2::new(transform.translation().x, transform.translation().y), - 0., - ); - // println!( - // "Hit {:?}, pushing {:?}", - // entity, - // collider.raw.compute_aabb(&position) - // ); - boxes.push(collider.raw.compute_aabb(&position)); - } - true - }, - ); + rapier_context.intersections_with_shape(*start, 0., &shape, default(), |entity| { + if let Ok((_, collider, transform)) = visible_query.get(entity) { + let position = + Isometry2::translation(transform.translation().x, transform.translation().y); + // println!( + // "Hit {:?}, pushing {:?}", + // entity, + // collider.raw.compute_aabb(&position) + // ); + boxes.push(collider.raw.compute_aabb(&position)); + } + true + }); let mut context: Context = Context::default(); let vision_distance = vision_distance::Circle::new(self.range); - let shape = Collider::cuboid(0.5, 0.5); - let mut new_visible_entities = HashSet::new(); + let shape = Collider::cuboid(0.49, 0.49); let size = ( (start.x.abs() + self.range as f32 * 2.) as u32, (start.y.abs() + self.range as f32 * 2.) as u32, @@ -93,96 +81,72 @@ impl Viewshed { let visibility_grid = VisibilityGrid( size, RefCell::new(Box::new(|coord: Coord| { - // println!("Checking {coord:?}"); let shape_pos = Vec2::new(coord.x as f32 + 0.5, coord.y as f32 + 0.5); - // println!("Checking {:?}", shape_pos); + // println!("Checking {coord:?}: {shape_pos:?}"); if start.distance(&shape_pos) > self.range as f32 { // println!("Out of range"); return u8::MAX; } - let result = if let Some((opacity, coord_entities)) = cache.get(&(coord.x, coord.y)) + let mut coord_entities = HashSet::new(); + let mut to_insert = None; + let opacity = if let Some((opacity, entities)) = + cache.borrow().get(&(coord.x, coord.y)) { - Some((*opacity, coord_entities.clone())) + // println!("Cache hit: {opacity:?}: {entities:?}"); + coord_entities = entities.clone(); + *opacity } else { - let position = Isometry2::new(Vector2::new(shape_pos.x, shape_pos.y), 0.); - let aabb = shape.raw.compute_aabb(&position).tightened(0.01); - let mut hit = false; - for b in &boxes { - if b.intersects(&aabb) { - // println!("Hit at {:?}", b); - hit = true; - break; - } - } - if hit { - // println!("Hit test"); + let position = Isometry2::translation(shape_pos.x, shape_pos.y); + let aabb = shape.raw.compute_aabb(&position); + if boxes.iter().any(|b| b.intersects(&aabb)) { + // println!("Hit"); let mut opacity = 0; - let mut coord_entities = HashSet::new(); rapier_context.intersections_with_shape( shape_pos, 0., &shape, - QueryFilter::new().predicate(&|v| visible_query.contains(v)), + default(), |entity| { - // println!("{:?}", entity); if let Ok((visible, _, _)) = visible_query.get(entity) { + // println!( + // "{entity:?}: {visible:?}: {:?}", + // transform.translation().truncate() + // ); coord_entities.insert(entity); opacity = opacity.max(**visible); } true }, ); - cache.insert((coord.x, coord.y), (opacity, coord_entities.clone())); - Some((opacity, coord_entities)) + to_insert = Some(((coord.x, coord.y), (opacity, coord_entities.clone()))); + // println!("New opacity: {opacity}"); + opacity } else { // println!("No hits, 0"); - let coord_entities = HashSet::new(); - cache.insert((coord.x, coord.y), (0, coord_entities.clone())); - Some((0, coord_entities)) + to_insert = Some(((coord.x, coord.y), default())); + 0 } }; - if let Some((mut opacity, coord_entities)) = result { - // println!("Opacity: {opacity}, coord_entities: {coord_entities:?}"); - for e in &coord_entities { - let mut should_insert = true; - if e == viewer_entity { - opacity = 0; - } else { - let dest = Vec2::new(coord.x as f32, coord.y as f32); - let dir = dest - *start; - // println!( - // "Checking visibility of {e} by casting from {start} to {dest}, {dir}" - // ); - rapier_context.intersections_with_ray( - *start, - dir, - 1., - true, - QueryFilter::new() - .exclude_sensors() - .exclude_collider(*e) - .predicate(&|e| visible_query.contains(e)), - |entity, hit| { - if entity != *viewer_entity { - // println!("Hit {entity} at {hit:?}"); - should_insert = false; - } - true - }, - ); - } - if should_insert { - new_visible_entities.insert(*e); - } - } - // println!("New opacity: {opacity}"); + if let Some((k, v)) = to_insert { + cache.borrow_mut().insert(k, v); + } + if coord_entities.contains(&viewer_entity) { + let mut coord_entities = coord_entities.clone(); + coord_entities.retain(|e| e != viewer_entity); + let opacity = coord_entities + .iter() + .map(|v| visible_query.get(*v).unwrap().0 .0) + .max() + .unwrap_or(0); + // println!("Viewer hit, removing viewer opacity: {opacity}"); opacity } else { - 0 + opacity } })), ); let mut new_visible = HashSet::new(); + let mut new_visible_entities = HashSet::new(); // println!("Start: {viewer_entity}"); context.for_each_visible( Coord::new(start.x_i32(), start.y_i32()), @@ -192,6 +156,11 @@ impl Viewshed { u8::MAX, |coord, _directions, _visibility| { new_visible.insert((coord.x, coord.y)); + if let Some((_, entities)) = cache.borrow().get(&(coord.x, coord.y)) { + for e in entities { + new_visible_entities.insert(*e); + } + } }, ); if self.visible_points != new_visible { @@ -199,17 +168,12 @@ impl Viewshed { } if new_visible_entities != **visible_entities { for lost in visible_entities.difference(&new_visible_entities) { - events.send(VisibilityChanged::Lost { - viewer: *viewer_entity, - viewed: *lost, - }); + commands.trigger_targets(VisibilityChanged::Lost(*lost), *viewer_entity); } for gained in new_visible_entities.difference(visible_entities) { - // println!("transition: {:?} gains {:?}", viewer_entity, gained); - events.send(VisibilityChanged::Gained { - viewer: *viewer_entity, - viewed: *gained, - }); + // println!("transition: {:?} gains {:?}", viewer_entity, + // gained); + commands.trigger_targets(VisibilityChanged::Gained(*gained), *viewer_entity); } **visible_entities = new_visible_entities; } @@ -256,8 +220,8 @@ impl ViewshedBundle { #[derive(Event)] pub enum VisibilityChanged { - Gained { viewer: Entity, viewed: Entity }, - Lost { viewer: Entity, viewed: Entity }, + Gained(Entity), + Lost(Entity), } impl PointLike for Coord { @@ -284,17 +248,14 @@ fn add_visibility_indices( } fn viewshed_removed( + mut commands: Commands, mut query: RemovedComponents, visible_entities: Query<&VisibleEntities>, - mut events: EventWriter, ) { for entity in query.read() { if let Ok(visible) = visible_entities.get(entity) { for e in visible.iter() { - events.send(VisibilityChanged::Lost { - viewer: entity, - viewed: *e, - }); + commands.trigger_targets(VisibilityChanged::Lost(*e), entity); } } } @@ -320,6 +281,7 @@ where } fn update_viewshed( + mut commands: Commands, config: Res, visible: Query<(&Visible, &Collider, &GlobalTransform)>, mut viewers: Query<( @@ -329,26 +291,26 @@ fn update_viewshed( &GlobalTransform, )>, rapier_context: Res, - mut changed: EventWriter, ) { if !config.query_pipeline_active { return; } - let mut cache = HashMap::new(); + let mut cache = default(); for (viewer_entity, mut viewshed, mut visible_entities, viewer_transform) in &mut viewers { viewshed.update( + &mut commands, &viewer_entity, &mut visible_entities, &viewer_transform.translation().truncate(), &rapier_context, &visible, - &mut changed, &mut cache, ); } } fn remove_visible( + mut commands: Commands, mut removed: RemovedComponents, mut viewers: Query<( Entity, @@ -358,23 +320,21 @@ fn remove_visible( )>, rapier_context: Res, visible: Query<(&Visible, &Collider, &GlobalTransform)>, - mut changed: EventWriter, ) { if !removed.is_empty() { - let mut cache = HashMap::new(); + let mut cache = default(); for removed in removed.read() { for (viewer_entity, mut viewshed, mut visible_entities, start) in &mut viewers { if !visible_entities.contains(&removed) { continue; } - visible_entities.remove(&removed); viewshed.update( + &mut commands, &viewer_entity, &mut visible_entities, &start.translation().truncate(), &rapier_context, &visible, - &mut changed, &mut cache, ); } @@ -400,53 +360,38 @@ fn update_revealed_tiles( } fn log_visible( - time: Res