From a0ca6a9f55762615a975bb83102792b7a1562484 Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Wed, 20 Jul 2022 08:24:38 -0500 Subject: [PATCH 1/2] Massive visibility cleanup/optimization. --- src/visibility.rs | 254 +++++++++++++++++++++------------------------- 1 file changed, 118 insertions(+), 136 deletions(-) diff --git a/src/visibility.rs b/src/visibility.rs index 3e2865a..47a73ef 100644 --- a/src/visibility.rs +++ b/src/visibility.rs @@ -4,14 +4,14 @@ use std::{ marker::PhantomData, }; -use bevy::{core::FixedTimestep, prelude::*}; +use bevy::prelude::*; +use bevy_rapier2d::na::{Isometry2, Point2, Vector2}; use coord_2d::{Coord, Size}; use shadowcast::{vision_distance, Context, InputGrid}; use crate::{ bevy_rapier2d::prelude::*, - commands::RunIfExistsExt, core::{GlobalTransformExt, Player, PointLike}, log::Log, map::{Map, MapConfig, MapObstruction}, @@ -53,63 +53,93 @@ impl Viewshed { &mut self, viewer_entity: &Entity, visible_entities: &mut VisibleEntities, - start: &dyn PointLike, + start: &GlobalTransform, rapier_context: &RapierContext, map: &Map, - visible_query: &Query<&Visible>, + visible_query: &Query<(&Visible, &Collider, &GlobalTransform)>, obstructions_query: &Query<&MapObstruction>, events: &mut EventWriter, - cache: &mut HashMap)>, + cache: &mut HashMap<(i32, i32), (u8, HashSet)>, ) { + // println!("Start"); + let mut boxes = vec![]; + let shape = Collider::cuboid(self.range as f32, self.range as f32); + rapier_context.intersections_with_shape( + start.translation.truncate(), + 0., + &shape, + QueryFilter::new().predicate(&|e| visible_query.get(e).is_ok()), + |entity| { + if let Ok((_, collider, transform)) = visible_query.get(entity) { + let position = Isometry2::new( + Vector2::new(transform.translation.x, transform.translation.y), + 0., + ); + // println!("Pushing {:?}", 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 - f32::EPSILON, 0.5 - f32::EPSILON); + let shape = Collider::cuboid(0.5, 0.5); let mut new_visible_entities = HashSet::new(); let size = (map.width as u32, map.height 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 {:?}", shape_pos); if start.distance(&shape_pos) > self.range as f32 + 1. { + // println!("Out of range"); return u8::MAX; } - if let Some((opacity, entities)) = cache.get(&coord) { - for e in entities { + let point = Point2::new(shape_pos.x, shape_pos.y); + let mut hit = false; + for b in &boxes { + if b.contains_local_point(&point) { + // println!("Hit at {:?}", b); + hit = true; + break; + } + } + if hit { + // println!("Hit test"); + 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.get(v).is_ok()), + |entity| { + // println!("{:?}", entity); + let obstruction = obstructions_query.get(entity).is_ok(); + if obstruction { + // println!("Obstruction"); + coord_entities.clear(); + } + coord_entities.insert(entity); + if let Ok((visible, _, _)) = visible_query.get(entity) { + opacity = opacity.max(**visible); + } + !obstruction + }, + ); + for e in &coord_entities { new_visible_entities.insert(*e); } - if entities.contains(viewer_entity) { - return 0; + if coord_entities.contains(viewer_entity) { + // println!("Self hit, 0"); + 0 } else { - return *opacity; + // println!("{}", opacity); + opacity } - } - let mut opacity = 0; - let mut entities = HashSet::new(); - rapier_context.intersections_with_shape( - shape_pos, - 0., - &shape, - QueryFilter::new().predicate(&|v| visible_query.get(v).is_ok()), - |entity| { - let obstruction = obstructions_query.get(entity).is_ok(); - if obstruction { - entities.clear(); - } - entities.insert(entity); - if let Ok(visible) = visible_query.get(entity) { - opacity = opacity.max(**visible); - } - !obstruction - }, - ); - for e in &entities { - new_visible_entities.insert(*e); - } - cache.insert(coord, (opacity, entities.clone())); - if entities.contains(viewer_entity) { - 0 } else { - opacity + // println!("No hits, 0"); + 0 } })), ); @@ -248,15 +278,15 @@ where } fn update_viewshed( - mut commands: Commands, config: Res, - positions: Query< - (Entity, &Transform, &Collider, Option<&LastCoordinates>), - Or<(Changed, Changed)>, - >, - visible: Query<&Visible>, + visible: Query<(&Visible, &Collider, &GlobalTransform)>, obstructions: Query<&MapObstruction>, - mut viewers: Query<(Entity, &mut Viewshed, &mut VisibleEntities, &Transform)>, + mut viewers: Query<( + Entity, + &mut Viewshed, + &mut VisibleEntities, + &GlobalTransform, + )>, map: Query<&Map>, rapier_context: Res, mut changed: EventWriter, @@ -264,95 +294,61 @@ fn update_viewshed( if !config.query_pipeline_active { return; } - let mut to_update = HashSet::new(); - for (entity, transform, collider, last_coordinates) in positions.iter() { - if to_update.contains(&entity) { - continue; - } - let coordinates = (transform.translation.x, transform.translation.y); - let coordinates_i32 = coordinates.i32(); - if viewers.get_mut(entity).is_ok() { - commands.run_if_exists(entity, move |mut entity| { - entity.insert(LastCoordinates(coordinates_i32)); - }); - if let Some(last_coordinates) = last_coordinates { - if **last_coordinates != coordinates.i32() { - to_update.insert(entity); - } - } else { - to_update.insert(entity); - } - } - if visible.get(entity).is_err() { - continue; - } - for (viewer_entity, viewshed, visible_entities, viewer_coordinates) in viewers.iter_mut() { - if viewer_entity != entity { - let forward = transform.local_x(); - let rotation = forward.y.atan2(forward.x); - let distance = collider.distance_to_point( - transform.translation.truncate(), - rotation, - viewer_coordinates.translation.truncate(), - true, - ); - if distance <= viewshed.range as f32 || visible_entities.contains(&entity) { - to_update.insert(viewer_entity); - to_update.insert(entity); - } - } - } - } - let mut cache = HashMap::new(); - for entity in to_update.iter() { - if let Ok((viewer_entity, mut viewshed, mut visible_entities, viewer_coordinates)) = - viewers.get_mut(*entity) + if let Ok(map) = map.get_single() { + let mut cache = HashMap::new(); + for (viewer_entity, mut viewshed, mut visible_entities, viewer_transform) in + viewers.iter_mut() { - if let Ok(map) = map.get_single() { - viewshed.update( - &viewer_entity, - &mut visible_entities, - viewer_coordinates, - &*rapier_context, - map, - &visible, - &obstructions, - &mut changed, - &mut cache, - ); - } + viewshed.update( + &viewer_entity, + &mut visible_entities, + viewer_transform, + &rapier_context, + map, + &visible, + &obstructions, + &mut changed, + &mut cache, + ); } } } fn remove_visible( removed: RemovedComponents, - mut viewers: Query<(Entity, &mut Viewshed, &mut VisibleEntities, &Transform)>, + mut viewers: Query<( + Entity, + &mut Viewshed, + &mut VisibleEntities, + &GlobalTransform, + )>, map: Query<&Map>, rapier_context: Res, - visible: Query<&Visible>, + visible: Query<(&Visible, &Collider, &GlobalTransform)>, obstructions: Query<&MapObstruction>, mut changed: EventWriter, ) { - for removed in removed.iter() { + if removed.iter().len() != 0 { let mut cache = HashMap::new(); - 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.get_single() { - viewshed.update( - &viewer_entity, - &mut visible_entities, - start, - &*rapier_context, - map, - &visible, - &obstructions, - &mut changed, - &mut cache, - ); + 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.get_single() { + viewshed.update( + &viewer_entity, + &mut visible_entities, + start, + &*rapier_context, + map, + &visible, + &obstructions, + &mut changed, + &mut cache, + ); + } } } } @@ -431,25 +427,11 @@ impl Default for VisibilityPlugin } } -pub const UPDATE_VIEWSHED_LABEL: &str = "UPDATE_VIEWSHED"; - impl Plugin for VisibilityPlugin { fn build(&self, app: &mut App) { - const UPDATE_VIEWSHED_STAGE: &str = "UPDATE_VIEWSHED_STAGE"; app.add_event::() - .add_stage_after( - CoreStage::PreUpdate, - UPDATE_VIEWSHED_STAGE, - SystemStage::parallel(), - ) .add_system_to_stage(CoreStage::PreUpdate, add_visibility_indices::) - .add_system_set_to_stage( - UPDATE_VIEWSHED_STAGE, - SystemSet::new() - .with_run_criteria(FixedTimestep::step(0.05)) - .with_system(update_viewshed::) - .label(UPDATE_VIEWSHED_LABEL), - ) + .add_system_to_stage(CoreStage::PreUpdate, update_viewshed::) .add_system_to_stage(CoreStage::PostUpdate, viewshed_removed) .add_system_to_stage(CoreStage::PostUpdate, remove_visible::) .add_system_to_stage(CoreStage::PreUpdate, update_revealed_tiles::) From d19048c4a8fb9b7505ad8bf329643fd2e34f2728 Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Wed, 20 Jul 2022 10:12:14 -0500 Subject: [PATCH 2/2] Use AABB intersection rather than point containment. --- src/visibility.rs | 100 ++++++++++++++++++++++++++++------------------ 1 file changed, 62 insertions(+), 38 deletions(-) diff --git a/src/visibility.rs b/src/visibility.rs index 47a73ef..bdd7403 100644 --- a/src/visibility.rs +++ b/src/visibility.rs @@ -6,7 +6,10 @@ use std::{ use bevy::prelude::*; -use bevy_rapier2d::na::{Isometry2, Point2, Vector2}; +use bevy_rapier2d::{ + na::{Isometry2, Vector2}, + parry::bounding_volume::BoundingVolume, +}; use coord_2d::{Coord, Size}; use shadowcast::{vision_distance, Context, InputGrid}; @@ -21,10 +24,6 @@ use crate::{ #[reflect(Component)] pub struct DontLogWhenVisible; -#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect)] -#[reflect(Component)] -struct LastCoordinates((i32, i32)); - #[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect)] #[reflect(Component)] pub struct RevealedTiles(pub Vec); @@ -75,7 +74,11 @@ impl Viewshed { Vector2::new(transform.translation.x, transform.translation.y), 0., ); - // println!("Pushing {:?}", collider.raw.compute_aabb(&position)); + // println!( + // "Hit {:?}, pushing {:?}", + // entity, + // collider.raw.compute_aabb(&position) + // ); boxes.push(collider.raw.compute_aabb(&position)); } true @@ -95,38 +98,53 @@ impl Viewshed { // println!("Out of range"); return u8::MAX; } - let point = Point2::new(shape_pos.x, shape_pos.y); - let mut hit = false; - for b in &boxes { - if b.contains_local_point(&point) { - // println!("Hit at {:?}", b); - hit = true; - break; + let result = if let Some((opacity, coord_entities)) = cache.get(&(coord.x, coord.y)) + { + Some((*opacity, coord_entities.clone())) + } else { + let position = Isometry2::new(Vector2::new(shape_pos.x, shape_pos.y), 0.); + let aabb = shape.raw.compute_aabb(&position); + 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 mut opacity = 0; - let mut coord_entities = HashSet::new(); - rapier_context.intersections_with_shape( - shape_pos, - 0., - &shape, - QueryFilter::new().predicate(&|v| visible_query.get(v).is_ok()), - |entity| { - // println!("{:?}", entity); - let obstruction = obstructions_query.get(entity).is_ok(); - if obstruction { - // println!("Obstruction"); - coord_entities.clear(); - } - coord_entities.insert(entity); - if let Ok((visible, _, _)) = visible_query.get(entity) { - opacity = opacity.max(**visible); - } - !obstruction - }, - ); + if hit { + // println!("Hit test"); + 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.get(v).is_ok()), + |entity| { + // println!("{:?}", entity); + let obstruction = obstructions_query.get(entity).is_ok(); + if obstruction { + // println!("Obstruction"); + coord_entities.clear(); + } + coord_entities.insert(entity); + if let Ok((visible, _, _)) = visible_query.get(entity) { + opacity = opacity.max(**visible); + } + !obstruction + }, + ); + cache.insert((coord.x, coord.y), (opacity, coord_entities.clone())); + Some((opacity, coord_entities)) + } else { + // println!("No hits, 0"); + let coord_entities = HashSet::new(); + cache.insert((coord.x, coord.y), (0, coord_entities.clone())); + Some((0, coord_entities)) + } + }; + if let Some((opacity, coord_entities)) = result { for e in &coord_entities { new_visible_entities.insert(*e); } @@ -138,7 +156,6 @@ impl Viewshed { opacity } } else { - // println!("No hits, 0"); 0 } })), @@ -159,12 +176,19 @@ impl Viewshed { } if new_visible_entities != **visible_entities { for lost in visible_entities.difference(&new_visible_entities) { + if let Ok((_, _, transform)) = visible_query.get(*lost) { + // println!( + // "transition: {:?} loses {:?} at {:?}", + // viewer_entity, lost, transform.translation + // ); + } events.send(VisibilityChanged::Lost { viewer: *viewer_entity, viewed: *lost, }); } for gained in new_visible_entities.difference(visible_entities) { + // println!("transition: {:?} gains {:?}", viewer_entity, gained); events.send(VisibilityChanged::Gained { viewer: *viewer_entity, viewed: *gained,