use std::{ cell::RefCell, collections::{HashMap, HashSet}, marker::PhantomData, }; use bevy::prelude::*; use bevy_rapier2d::{ na::{Isometry2, Vector2}, parry::bounding_volume::BoundingVolume, }; use coord_2d::{Coord, Size}; use shadowcast::{vision_distance, Context, InputGrid}; use crate::{ bevy_rapier2d::prelude::*, core::{GlobalTransformExt, Player, PointLike}, log::Log, map::{Map, MapConfig, MapObstruction}, }; #[derive(Component, Clone, Copy, Debug, Default, Reflect)] #[reflect(Component)] pub struct DontLogWhenVisible; #[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect)] #[reflect(Component)] pub struct RevealedTiles(pub Vec<bool>); #[derive(Component, Clone, Debug, Eq, PartialEq)] pub struct Viewshed { pub range: u32, pub visible_points: HashSet<(i32, i32)>, } impl Default for Viewshed { fn default() -> Self { Self { range: 15, visible_points: HashSet::new(), } } } impl Viewshed { pub fn is_point_visible(&self, point: &dyn PointLike) -> bool { self.visible_points.contains(&point.into()) } fn update( &mut self, viewer_entity: &Entity, visible_entities: &mut VisibleEntities, start: &GlobalTransform, rapier_context: &RapierContext, visible_query: &Query<(&Visible, &Collider, &GlobalTransform)>, obstructions_query: &Query<&MapObstruction>, events: &mut EventWriter<VisibilityChanged>, cache: &mut HashMap<(i32, i32), (u8, HashSet<Entity>)>, ) { // 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!( // "Hit {:?}, pushing {:?}", // entity, // collider.raw.compute_aabb(&position) // ); boxes.push(collider.raw.compute_aabb(&position)); } true }, ); let mut context: Context<u8> = Context::default(); let vision_distance = vision_distance::Circle::new(self.range); let shape = Collider::cuboid(0.49, 0.49); let mut new_visible_entities = HashSet::new(); let size = ( (start.translation().x.abs() + self.range as f32) as u32, (start.translation().y.abs() + self.range as f32) 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 { // println!("Out of range"); return u8::MAX; } 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 }, ); 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); } if coord_entities.contains(viewer_entity) { // println!("Self hit, 0"); 0 } else { // println!("{}", opacity); opacity } } else { 0 } })), ); let mut new_visible = HashSet::new(); context.for_each_visible( Coord::new(start.x_i32(), start.y_i32()), &visibility_grid, &size, vision_distance, u8::MAX, |coord, _directions, _visibility| { new_visible.insert((coord.x, coord.y)); }, ); if self.visible_points != new_visible { self.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) { // println!("transition: {:?} gains {:?}", viewer_entity, gained); events.send(VisibilityChanged::Gained { viewer: *viewer_entity, viewed: *gained, }); } **visible_entities = new_visible_entities; } } } #[derive(Component, Clone, Copy, Debug, Deref, DerefMut, Reflect)] #[reflect(Component)] pub struct Visible(pub u8); impl Default for Visible { fn default() -> Self { Self::opaque() } } impl Visible { pub fn transparent() -> Self { Self(0) } pub fn opaque() -> Self { Self(u8::MAX) } } #[derive(Component, Clone, Debug, Default, Deref, DerefMut)] pub struct VisibleEntities(HashSet<Entity>); #[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() } } } 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 } fn y(&self) -> f32 { self.y as f32 } } fn add_visibility_indices<D: 'static + Clone + Default + Send + Sync>( mut commands: Commands, query: Query<(Entity, &Map<D>), (Added<Map<D>>, Without<RevealedTiles>)>, map_config: Res<MapConfig>, ) { for (entity, map) in query.iter() { let count = map.width * map.height; commands .entity(entity) .insert(RevealedTiles(vec![map_config.start_revealed; count])); } } fn viewshed_removed( query: RemovedComponents<Viewshed>, visible_entities: Query<&VisibleEntities>, mut events: EventWriter<VisibilityChanged>, ) { for entity in query.iter() { if let Ok(visible) = visible_entities.get(entity) { for e in visible.iter() { events.send(VisibilityChanged::Lost { viewer: entity, viewed: *e, }) } } } } pub struct VisibilityGrid<F>(pub (u32, u32), pub RefCell<Box<F>>); impl<F> InputGrid for VisibilityGrid<F> where F: FnMut(Coord) -> u8, { type Grid = (u32, u32); type Opacity = u8; fn size(&self, grid: &Self::Grid) -> Size { Size::new(grid.0, grid.1) } fn get_opacity(&self, _grid: &Self::Grid, coord: Coord) -> Self::Opacity { (self.1.borrow_mut())(coord) } } fn update_viewshed( config: Res<RapierConfiguration>, visible: Query<(&Visible, &Collider, &GlobalTransform)>, obstructions: Query<&MapObstruction>, mut viewers: Query<( Entity, &mut Viewshed, &mut VisibleEntities, &GlobalTransform, )>, rapier_context: Res<RapierContext>, mut changed: EventWriter<VisibilityChanged>, ) { if !config.query_pipeline_active { return; } let mut cache = HashMap::new(); for (viewer_entity, mut viewshed, mut visible_entities, viewer_transform) in viewers.iter_mut() { viewshed.update( &viewer_entity, &mut visible_entities, viewer_transform, &rapier_context, &visible, &obstructions, &mut changed, &mut cache, ); } } fn remove_visible( removed: RemovedComponents<Visible>, mut viewers: Query<( Entity, &mut Viewshed, &mut VisibleEntities, &GlobalTransform, )>, rapier_context: Res<RapierContext>, visible: Query<(&Visible, &Collider, &GlobalTransform)>, obstructions: Query<&MapObstruction>, mut changed: EventWriter<VisibilityChanged>, ) { if removed.iter().len() != 0 { let mut cache = HashMap::new(); 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); viewshed.update( &viewer_entity, &mut visible_entities, start, &*rapier_context, &visible, &obstructions, &mut changed, &mut cache, ); } } } } fn update_revealed_tiles<D: 'static + Clone + Default + Send + Sync>( mut map: Query<(&Map<D>, &mut RevealedTiles)>, viewers: Query<&Viewshed, (With<Player>, Changed<Viewshed>)>, ) { for viewshed in viewers.iter() { for (map, mut revealed_tiles) in map.iter_mut() { for v in viewshed.visible_points.iter() { let idx = v.to_index(map.width); if idx >= revealed_tiles.len() { continue; } revealed_tiles[idx] = true; } } } } fn log_visible( time: Res<Time>, mut recently_lost: Local<HashMap<Entity, Timer>>, mut events: EventReader<VisibilityChanged>, mut log: Query<&mut Log>, viewers: Query<(Entity, &GlobalTransform, Option<&Collider>), (With<Player>, With<Viewshed>)>, visible: Query<(&Name, &GlobalTransform, Option<&Collider>), Without<DontLogWhenVisible>>, ) { for timer in recently_lost.values_mut() { timer.tick(time.delta()); } recently_lost.retain(|_entity, timer| !timer.finished()); for event in events.iter() { let viewer = match event { VisibilityChanged::Gained { viewer, .. } => viewer, VisibilityChanged::Lost { viewer, .. } => viewer, }; if let Ok((viewer_entity, viewer_transform, viewer_collider)) = viewers.get(*viewer) { if let VisibilityChanged::Gained { viewed, .. } = event { if *viewed == viewer_entity || recently_lost.contains_key(viewed) { continue; } if let Ok((name, viewed_transform, viewed_collider)) = visible.get(*viewed) { let location = if let (Some(viewer_collider), Some(viewed_collider)) = (viewer_collider, viewed_collider) { viewer_transform.collider_direction_and_distance( viewer_collider, viewed_transform, viewed_collider, ) } else { let yaw = viewer_transform.yaw(); viewer_transform.direction_and_distance(viewed_transform, Some(yaw)) }; if let Ok(mut log) = log.get_single_mut() { log.push(format!("{}: {location}", name)); } } } else if let VisibilityChanged::Lost { viewed, .. } = event { recently_lost.insert(*viewed, Timer::from_seconds(2., false)); } } } } pub const LOG_VISIBLE_LABEL: &str = "LOG_VISIBLE"; pub struct VisibilityPlugin<D: 'static + Clone + Default + Send + Sync>(PhantomData<D>); impl<D: 'static + Clone + Default + Send + Sync> Default for VisibilityPlugin<D> { fn default() -> Self { Self(Default::default()) } } impl<D: 'static + Clone + Default + Send + Sync> Plugin for VisibilityPlugin<D> { fn build(&self, app: &mut App) { app.add_event::<VisibilityChanged>() .add_system_to_stage(CoreStage::PreUpdate, add_visibility_indices::<D>) .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::<D>) .add_system_to_stage(CoreStage::PreUpdate, log_visible); } }