use std::{ cell::RefCell, collections::{HashMap, HashSet}, marker::PhantomData, }; use bevy::{ core::{FixedTimestep, FloatOrd}, prelude::*, }; use bevy_rapier2d::na::{self, Point2}; use coord_2d::{Coord, Size}; use shadowcast::{vision_distance, Context, InputGrid}; use crate::{ bevy_rapier2d::prelude::*, commands::RunIfExistsExt, core::{Angle, Coordinates, Player, PointLike}, log::Log, map::{ITileType, Map, MapConfig}, }; #[derive(Component, Clone, Copy, Debug, Default, Reflect)] #[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); #[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: &dyn PointLike, rapier_context: &RapierContext, map: &Map, visible_query: &Query<&Visible>, events: &mut EventWriter, cache: &mut HashMap<(FloatOrd, FloatOrd), (u8, HashSet)>, ) { let mut context: Context = Context::default(); let vision_distance = vision_distance::Circle::new(self.range); let shape = Collider::cuboid(0.5, 0.5); let origin = Point2::new(start.x(), start.y()); let coord = Coord::new(start.x_i32(), start.y_i32()); let mut new_visible_entities = HashSet::new(); let visibility_grid = VisibilityGrid( map, RefCell::new(Box::new(|coord: Coord| { let dest = Point2::new(coord.x as f32 + 0.5, coord.y as f32 + 0.5); if let Some((opacity, entities)) = cache.get(&(FloatOrd(dest.x), FloatOrd(dest.y))) { for e in entities { new_visible_entities.insert(*e); } return *opacity; } let tile = map.at(coord.x as usize, coord.y as usize); if tile.blocks_visibility() { cache.insert( (FloatOrd(dest.x), FloatOrd(dest.y)), (u8::MAX, HashSet::new()), ); return u8::MAX; } if na::distance(&origin, &dest) > self.range as f32 { return u8::MAX; } let mut opacity = 0; let mut entities = HashSet::new(); let shape_pos = Vec2::new(coord.x as f32 + 0.5, coord.y as f32 + 0.5); rapier_context.intersections_with_shape( shape_pos, 0., &shape, InteractionGroups::all(), Some(&|v| v != *viewer_entity && visible_query.get(v).is_ok()), |entity| { entities.insert(entity); if let Ok(visible) = visible_query.get(entity) { opacity = **visible; } cache.insert( (FloatOrd(dest.x), FloatOrd(dest.y)), (opacity, entities.clone()), ); true }, ); for e in &entities { new_visible_entities.insert(*e); } opacity })), ); let mut new_visible = HashSet::new(); context.for_each_visible( coord, &visibility_grid, &visibility_grid, vision_distance, 255, |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) { 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); #[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() }, ..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( mut commands: Commands, query: Query<(Entity, &Map), (Added>, Without)>, map_config: Res, ) { 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, visible_entities: Query<&VisibleEntities>, mut events: EventWriter, ) { 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<'a, D: 'static + Clone + Default + Send + Sync, F>( pub &'a Map, pub RefCell>, ); impl<'a, D, F> InputGrid for VisibilityGrid<'a, D, F> where D: 'static + Clone + Default + Send + Sync, F: FnMut(Coord) -> u8, { type Grid = VisibilityGrid<'a, D, F>; type Opacity = u8; fn size(&self, grid: &Self::Grid) -> Size { Size::new(grid.0.width as u32, grid.0.height as u32) } fn get_opacity(&self, _grid: &Self::Grid, coord: Coord) -> Self::Opacity { (self.1.borrow_mut())(coord) } } fn update_viewshed( mut commands: Commands, config: Res, positions: Query<(Entity, &Transform, Option<&LastCoordinates>), Changed>, visible: Query<&Visible>, mut viewers: Query<( Entity, &mut Viewshed, &mut VisibleEntities, &Coordinates, Option<&LastCoordinates>, )>, map: Query<&Map>, rapier_context: Res, mut changed: EventWriter, ) { if !config.query_pipeline_active { return; } let mut to_update = HashSet::new(); for (entity, transform, 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, viewer_last_coordinates, ) in viewers.iter_mut() { if let (Some(last_coordinates), Some(viewer_last_coordinates)) = (last_coordinates, viewer_last_coordinates) { if viewer_coordinates.i32() == **viewer_last_coordinates && coordinates.i32() == **last_coordinates { continue; } } if viewer_entity != entity && (viewer_coordinates.distance(&coordinates) <= viewshed.range as f32 || visible_entities.contains(&entity)) { to_update.insert(viewer_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() { viewshed.update( &viewer_entity, &mut visible_entities, viewer_coordinates, &*rapier_context, map, &visible, &mut changed, &mut cache, ); } } } } fn remove_visible( removed: RemovedComponents, mut viewers: Query<(Entity, &mut Viewshed, &mut VisibleEntities, &Coordinates)>, map: Query<&Map>, rapier_context: Res, visible: Query<&Visible>, mut changed: EventWriter, ) { for removed in removed.iter() { 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, &mut changed, &mut cache, ); } } } } fn update_revealed_tiles( mut map: Query<(&Map, &mut RevealedTiles)>, viewers: Query<&Viewshed, With>, ) { for (map, mut revealed_tiles) in map.iter_mut() { for viewshed in viewers.iter() { 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