use std::collections::{HashMap, HashSet}; use bevy::prelude::*; use bevy_rapier2d::na::UnitComplex; use coord_2d::{Coord, Size}; use derive_more::{Deref, DerefMut}; use shadowcast::{vision_distance, Context, InputGrid}; use crate::{ bevy_rapier2d::prelude::*, core::{Angle, Coordinates, Player, PointLike}, log::Log, map::{ITileType, Map, MapConfig}, utils::target_and_other, }; #[derive(Clone, Copy, Debug, Default, Reflect)] #[reflect(Component)] pub struct DontLogWhenVisible; #[derive(Clone, Debug, Default, Deref, DerefMut, Reflect)] #[reflect(Component)] pub struct RevealedTiles(pub Vec); #[derive(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()) } } #[derive(Clone, Copy, Debug, Default, Reflect)] struct VisibilityCollider; #[derive(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(Clone, Debug, Default, Deref, DerefMut)] pub struct VisibleEntities(HashSet); #[derive(Clone, Debug, Default, Deref, DerefMut, Reflect)] #[reflect(Component)] pub struct VisibleTiles(pub Vec); #[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 }, } #[derive(Clone, Debug, Default, Deref, DerefMut)] struct VisibilityColliderToViewshed(HashMap); #[derive(Clone, Debug, Default, Deref, DerefMut)] struct ViewshedToVisibilityCollider(HashMap); 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, Without)>, map_config: Res, ) { for (entity, map) in query.iter() { let count = map.width * map.height; commands .entity(entity) .insert(VisibleTiles(vec![false; count])); commands .entity(entity) .insert(RevealedTiles(vec![map_config.start_revealed; count])); } } fn viewshed_added( mut commands: Commands, mut collider_to_viewshed: ResMut, mut viewshed_to_collider: ResMut, query: Query>, ) { for entity in query.iter() { let id = commands .spawn_bundle(ColliderBundle { collider_type: ColliderType::Sensor, flags: ColliderFlags { active_collision_types: ActiveCollisionTypes::all(), active_events: ActiveEvents::INTERSECTION_EVENTS, ..Default::default() }, ..Default::default() }) .insert(VisibilityCollider) .id(); viewshed_to_collider.insert(entity, id); collider_to_viewshed.insert(id, entity); } } fn viewshed_changed( mut commands: Commands, viewshed_to_collider: Res, query: Query<(Entity, &Viewshed, &RigidBodyPosition), Changed>, mut collider_data: Query< (Option<&mut ColliderShape>, Option<&mut ColliderPosition>), With, >, ) { for (entity, viewshed, position) in query.iter() { if let Some(collider_entity) = viewshed_to_collider.get(&entity) { if let Ok((collider_shape, collider_position)) = collider_data.get_mut(*collider_entity) { if viewshed.visible_points.len() < 2 { commands.entity(*collider_entity).remove::(); } else { let translation = position.position.translation; let mut points = vec![]; for p in &viewshed.visible_points { points.push(point!( p.x() - translation.x + 0.5, p.y() - translation.y + 0.5 )); } if let Some(shape) = ColliderShape::convex_hull(&points) { if let (Some(mut collider_shape), Some(mut collider_position)) = (collider_shape, collider_position) { *collider_shape = shape; *collider_position = translation.into(); } } } } } } } fn viewshed_removed( mut collider_to_viewshed: ResMut, mut viewshed_to_collider: ResMut, query: RemovedComponents, ) { for entity in query.iter() { if let Some(collider) = viewshed_to_collider.get(&entity) { collider_to_viewshed.remove(&collider); } viewshed_to_collider.remove(&entity); } } pub struct VisibilityGrid<'a, F>(pub &'a Map, pub F); impl<'a, F> InputGrid for VisibilityGrid<'a, F> where F: Fn(Coord) -> u8, { type Grid = VisibilityGrid<'a, 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(coord) } } fn update_viewshed( viewer_entity: &Entity, viewshed: &mut Viewshed, start: &dyn PointLike, query_pipeline: &QueryPipeline, collider_query: &QueryPipelineColliderComponentsQuery, map: &Map, visible: &Query<&Visible>, ) { let mut context: Context = Context::default(); let vision_distance = vision_distance::Circle::new(viewshed.range); let coord = Coord::new(start.x_i32(), start.y_i32()); let shape = Cuboid::new(Vec2::new(0.49, 0.49).into()); let range = viewshed.range as f32; let collider_set = QueryPipelineColliderComponentsSet(&collider_query); let visibility_grid = VisibilityGrid(map, |coord: Coord| { if coord.distance(start) > range { return 255; } let shape_pos = (Vec2::new(coord.x as f32 + 0.5, coord.y as f32 + 0.5), 0.); let mut opacity = 0; query_pipeline.intersections_with_shape( &collider_set, &shape_pos.into(), &shape, InteractionGroups::all(), Some(&|v| v.entity() != *viewer_entity && visible.get(v.entity()).is_ok()), |handle| { if let Ok(visible) = visible.get(handle.entity()) { opacity = **visible; } true }, ); 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 viewshed.visible_points != new_visible { viewshed.visible_points = new_visible; } } fn update_viewshed_for_coordinates( config: Res, visible: Query<(Entity, &Coordinates), (Changed, With)>, mut viewers: Query<(Entity, &mut Viewshed, &Coordinates)>, map: Query<&Map>, query_pipeline: Res, collider_query: QueryPipelineColliderComponentsQuery, visible_query: Query<&Visible>, ) { if !config.query_pipeline_active { return; } for (visible_entity, coordinates) in visible.iter() { for (viewer_entity, mut viewshed, start) in viewers.iter_mut() { if viewer_entity == visible_entity || coordinates.distance(start) > viewshed.range as f32 { continue; } if let Ok(map) = map.single() { update_viewshed( &viewer_entity, &mut viewshed, start, &*query_pipeline, &collider_query, map, &visible_query, ); } } } } fn update_viewshed_for_start( config: Res, mut viewers: Query<(Entity, &mut Viewshed, &Coordinates), Changed>, map: Query<&Map>, query_pipeline: Res, collider_query: QueryPipelineColliderComponentsQuery, visible: Query<&Visible>, ) { if !config.query_pipeline_active { return; } for (viewer_entity, mut viewshed, start) in viewers.iter_mut() { if let Ok(map) = map.single() { update_viewshed( &viewer_entity, &mut viewshed, start, &*query_pipeline, &collider_query, map, &visible, ); } } } fn remove_visible( removed: RemovedComponents, mut viewers: Query<(Entity, &mut Viewshed, &mut VisibleEntities, &Coordinates)>, map: Query<&Map>, query_pipeline: Res, collider_query: QueryPipelineColliderComponentsQuery, visible: Query<&Visible>, ) { 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.single() { update_viewshed( &viewer_entity, &mut viewshed, start, &*query_pipeline, &collider_query, map, &visible, ); } } } } fn update_visible_and_revealed_tiles( mut map: Query<(&Map, &mut RevealedTiles, &mut VisibleTiles)>, viewers: Query<&Viewshed, With>, ) { for (map, mut revealed_tiles, mut visible_tiles) in map.iter_mut() { for viewshed in viewers.iter() { for t in visible_tiles.iter_mut() { *t = false } for v in viewshed.visible_points.iter() { let idx = v.to_index(map.width); if idx >= revealed_tiles.len() || idx >= visible_tiles.len() { continue; } revealed_tiles[idx] = true; visible_tiles[idx] = true; } } } } fn intersection( mut events: EventReader, collider_to_viewshed: Res, colliders: Query>, mut viewers: Query<&mut VisibleEntities>, visible: Query>, mut visibility_changed: EventWriter, ) { for event in events.iter() { if let Some((visibility_collider, other)) = target_and_other(event.collider1.entity(), event.collider2.entity(), |v| { colliders.get(v).is_ok() }) { if visible.get(other).is_ok() { if let Some(viewshed_entity) = collider_to_viewshed.get(&visibility_collider) { if let Ok(mut visible_entities) = viewers.get_mut(*viewshed_entity) { if event.intersecting { visibility_changed.send(VisibilityChanged::Gained { viewer: *viewshed_entity, viewed: other, }); visible_entities.insert(other); } else { visibility_changed.send(VisibilityChanged::Lost { viewer: *viewshed_entity, viewed: other, }); visible_entities.remove(&other); } } } } } } } fn log_visible( time: Res