From 1601354f409a85cb105d945c1a2feac62c1db5c6 Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Sun, 1 Dec 2024 16:04:12 -0600 Subject: [PATCH] Newer, faster visibility. --- src/visibility.rs | 276 ++++++++++++++++++++++++++-------------------- 1 file changed, 157 insertions(+), 119 deletions(-) diff --git a/src/visibility.rs b/src/visibility.rs index 46b75d8..122d4c2 100644 --- a/src/visibility.rs +++ b/src/visibility.rs @@ -1,12 +1,11 @@ use std::{ - cell::RefCell, collections::{HashMap, HashSet}, marker::PhantomData, }; use bevy::prelude::*; -use bevy_rapier2d::{na::Isometry2, parry::bounding_volume::BoundingVolume}; +use bevy_rapier2d::na::Isometry2; use coord_2d::{Coord, Size}; use shadowcast::{vision_distance, Context, InputGrid}; @@ -51,100 +50,17 @@ impl Viewshed { viewer_entity: &Entity, visible_entities: &mut VisibleEntities, start: &Vec2, + opacity_map: &OpacityMap, + sensors: &Query<&Sensor>, rapier_context: &RapierContext, - visible_query: &Query<(&Visible, &Collider, &GlobalTransform)>, - 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, 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.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, ); - let visibility_grid = VisibilityGrid( - size, - RefCell::new(Box::new(|coord: Coord| { - let shape_pos = Vec2::new(coord.x as f32 + 0.5, coord.y as f32 + 0.5); - // println!("Checking {coord:?}: {shape_pos:?}"); - if start.distance(&shape_pos) > self.range as f32 { - // println!("Out of range"); - return u8::MAX; - } - 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)) - { - // println!("Cache hit: {opacity:?}: {entities:?}"); - coord_entities = entities.clone(); - *opacity - } else { - 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; - rapier_context.intersections_with_shape( - shape_pos, - 0., - &shape, - default(), - |entity| { - if let Ok((visible, _, _)) = visible_query.get(entity) { - // println!( - // "{entity:?}: {visible:?}: {:?}", - // transform.translation().truncate() - // ); - coord_entities.insert(entity); - opacity = opacity.max(**visible); - } - true - }, - ); - to_insert = Some(((coord.x, coord.y), (opacity, coord_entities.clone()))); - // println!("New opacity: {opacity}"); - opacity - } else { - // println!("No hits, 0"); - to_insert = Some(((coord.x, coord.y), default())); - 0 - } - }; - 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 { - opacity - } - })), - ); + let visibility_grid = VisibilityGrid(size, *viewer_entity, opacity_map.clone()); let mut new_visible = HashSet::new(); let mut new_visible_entities = HashSet::new(); // println!("Start: {viewer_entity}"); @@ -156,9 +72,36 @@ 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)) { + let coord = IVec2::new(coord.x, coord.y); + // println!("Checking {coord:?}"); + if let Some((_, entities)) = opacity_map.get(&coord) { for e in entities { - new_visible_entities.insert(*e); + if entities.len() == 1 || sensors.contains(*e) { + new_visible_entities.insert(*e); + } else { + let mut should_push = true; + let coord = Vec2::new(coord.x as f32 + 0.5, coord.y as f32 + 0.5); + let dir = (coord - *start).normalize(); + // println!("Casting from {coord}"); + rapier_context.intersections_with_ray( + *start, + dir, + dir.distance(*start), + true, + default(), + |entity, _hit| { + if *e != entity && entities.contains(&e) { + // println!("{entities:?} contains {e} and hits at {:?}", hit.point); + // commands.entity(*e).log_components(); + should_push = false; + } + true + }, + ); + if should_push { + new_visible_entities.insert(*e); + } + } } } }, @@ -171,8 +114,6 @@ impl Viewshed { commands.trigger_targets(VisibilityChanged::Lost(*lost), *viewer_entity); } for gained in new_visible_entities.difference(visible_entities) { - // println!("transition: {:?} gains {:?}", viewer_entity, - // gained); commands.trigger_targets(VisibilityChanged::Gained(*gained), *viewer_entity); } **visible_entities = new_visible_entities; @@ -200,6 +141,80 @@ impl Visible { } } +#[derive(Resource, Clone, Deref, DerefMut, Default)] +pub struct OpacityMap(HashMap)>); + +impl OpacityMap { + fn update( + &mut self, + rapier_context: &RapierContext, + coordinates: HashSet, + visible: &Query<(Entity, &GlobalTransform, &Collider, &Visible)>, + ) { + self.retain(|k, _| !coordinates.contains(k)); + let shape = Collider::cuboid(0.49, 0.49); + for coords in &coordinates { + let shape_pos = Vec2::new(coords.x as f32 + 0.5, coords.y as f32 + 0.5); + let mut opacity = 0; + let mut coord_entities = HashSet::new(); + rapier_context.intersections_with_shape(shape_pos, 0., &shape, default(), |entity| { + if let Ok((_, _, _, visible)) = visible.get(entity) { + coord_entities.insert(entity); + opacity = opacity.max(**visible); + } + true + }); + self.insert(*coords, (opacity, coord_entities)); + } + } +} + +fn update_opacity_map( + mut opacity_map: ResMut, + rapier_context: Res, + query: Query< + (Entity, &GlobalTransform, &Collider, &Visible), + Or<(Changed, Changed)>, + >, + visible: Query<(Entity, &GlobalTransform, &Collider, &Visible)>, +) { + let mut to_update = HashSet::new(); + for (entity, transform, collider, _) in &query { + // println!( + // "Updating {entity} at {:?}", + // transform.translation().truncate().as_ivec2() + // ); + let mut prev = HashSet::new(); + for (k, v) in opacity_map.iter() { + if v.1.contains(&entity) { + // println!("Also updating {k} because it contained entity"); + prev.insert(*k); + } + } + let mut current = HashSet::new(); + current.insert(transform.translation().truncate().as_ivec2()); + let position = Isometry2::new( + transform.translation().truncate().into(), + transform.yaw().as_radians(), + ); + let aabb = collider.raw.compute_aabb(&position); + for x in aabb.mins.x as i32..aabb.maxs.x as i32 { + for y in aabb.mins.y as i32..aabb.maxs.y as i32 { + // println!( + // "Also updating coordinates at {:?}", + // IVec2::new(x as i32, y as i32) + // ); + current.insert(IVec2::new(x as i32, y as i32)); + } + } + if prev != current { + to_update.extend(prev); + to_update.extend(current); + } + } + opacity_map.update(&rapier_context, to_update, &visible); +} + #[derive(Component, Clone, Debug, Default, Deref, DerefMut)] pub struct VisibleEntities(HashSet); @@ -261,12 +276,9 @@ fn viewshed_removed( } } -pub struct VisibilityGrid(pub (u32, u32), pub RefCell>); +pub struct VisibilityGrid(pub (u32, u32), pub Entity, pub OpacityMap); -impl InputGrid for VisibilityGrid -where - F: FnMut(Coord) -> u8, -{ +impl InputGrid for VisibilityGrid { type Grid = (u32, u32); type Opacity = u8; @@ -276,35 +288,47 @@ where } fn get_opacity(&self, _grid: &Self::Grid, coord: Coord) -> Self::Opacity { - (self.1.borrow_mut())(coord) + // println!("Checking {coord:?}"); + if let Some((opacity, entities)) = self.2.get(&IVec2::new(coord.x, coord.y)) { + if entities.len() == 1 && entities.contains(&self.1) { + // println!("Hit viewer, 0"); + 0 + } else { + // println!("{opacity:?}"); + *opacity + } + } else { + // println!("Miss, 0"); + 0 + } } } fn update_viewshed( mut commands: Commands, config: Res, - visible: Query<(&Visible, &Collider, &GlobalTransform)>, mut viewers: Query<( Entity, &mut Viewshed, &mut VisibleEntities, &GlobalTransform, )>, + opacity_map: Res, + sensors: Query<&Sensor>, rapier_context: Res, ) { if !config.query_pipeline_active { return; } - 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 cache, + &opacity_map, + &sensors, + &*rapier_context, ); } } @@ -318,11 +342,21 @@ fn remove_visible( &mut VisibleEntities, &GlobalTransform, )>, + sensors: Query<&Sensor>, rapier_context: Res, - visible: Query<(&Visible, &Collider, &GlobalTransform)>, + mut opacity_map: ResMut, + visible: Query<(Entity, &GlobalTransform, &Collider, &Visible)>, ) { if !removed.is_empty() { - let mut cache = default(); + let mut to_update = HashSet::new(); + for e in removed.read() { + for (k, v) in opacity_map.iter() { + if v.1.contains(&e) { + to_update.insert(*k); + } + } + } + opacity_map.update(&rapier_context, to_update, &visible); for removed in removed.read() { for (viewer_entity, mut viewshed, mut visible_entities, start) in &mut viewers { if !visible_entities.contains(&removed) { @@ -333,9 +367,9 @@ fn remove_visible( &viewer_entity, &mut visible_entities, &start.translation().truncate(), - &rapier_context, - &visible, - &mut cache, + &opacity_map, + &sensors, + &*rapier_context, ); } } @@ -371,7 +405,6 @@ fn log_visible( } if let Ok((viewer_transform, viewer_collider)) = viewers.get(trigger.entity()) { if let Ok((name, viewed_transform, viewed_collider)) = visible.get(*entity) { - // println!("Gain {name}: {entity}"); let location = if let (Some(viewer_collider), Some(viewed_collider)) = (viewer_collider, viewed_collider) { @@ -402,15 +435,20 @@ impl Default for VisibilityPlu impl Plugin for VisibilityPlugin { fn build(&self, app: &mut App) { - app.add_systems( - FixedPreUpdate, - ( - add_visibility_indices::, - update_viewshed, - update_revealed_tiles::, - ), - ) - .add_systems(FixedPostUpdate, (viewshed_removed, remove_visible)) - .observe(log_visible); + app.init_resource::() + .add_systems( + FixedPreUpdate, + update_opacity_map.before(update_viewshed), + ) + .add_systems( + FixedPreUpdate, + ( + add_visibility_indices::, + update_viewshed, + update_revealed_tiles::, + ), + ) + .add_systems(FixedPostUpdate, (viewshed_removed, remove_visible)) + .observe(log_visible); } }