use std::any::TypeId;
use bevy::{
camera::visibility::{
PreviousVisibleEntities, VisibilitySystems, VisibleEntities, check_visibility,
},
math::bounding::{Aabb2d, BoundingVolume, IntersectsVolume},
prelude::*,
};
use crate::{
data::FireflyConfig,
lights::{LightHeight, PointLight2d},
occluders::Occluder2dShape,
prelude::Occluder2d,
};
#[derive(Component)]
pub struct VisibilityTimer(pub Timer);
#[derive(Component, Default)]
pub struct NotVisible;
impl Default for VisibilityTimer {
fn default() -> Self {
Self(Timer::from_seconds(2.0, TimerMode::Once))
}
}
#[derive(Component)]
pub struct OccluderAabb(pub Aabb2d);
impl Default for OccluderAabb {
fn default() -> Self {
Self(Aabb2d::new(default(), default()))
}
}
pub struct VisibilityPlugin;
impl Plugin for VisibilityPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<LightRect>();
app.add_systems(Update, occluder_aabb);
app.add_systems(
PostUpdate,
(mark_visible_lights, mark_visible_occluders)
.chain()
.in_set(VisibilitySystems::CheckVisibility)
.after(check_visibility),
);
}
}
#[derive(Resource, Default)]
struct LightRect(pub Rect);
fn mark_visible_lights(
mut lights: Query<(
Entity,
&GlobalTransform,
&PointLight2d,
&LightHeight,
&mut ViewVisibility,
&mut VisibilityTimer,
)>,
mut camera: Single<(&GlobalTransform, &mut VisibleEntities, &Projection), With<FireflyConfig>>,
mut previous_visible_entities: ResMut<PreviousVisibleEntities>,
mut light_rect: ResMut<LightRect>,
time: Res<Time>,
) {
let Projection::Orthographic(projection) = camera.2 else {
return;
};
let camera_aabb = Aabb2d {
min: projection.area.min + camera.0.translation().truncate(),
max: projection.area.max + camera.0.translation().truncate(),
};
let camera_rect = Rect {
min: projection.area.min + camera.0.translation().truncate(),
max: projection.area.max + camera.0.translation().truncate(),
};
light_rect.0 = Rect::EMPTY;
for (entity, transform, light, height, mut visibility, mut visibility_timer) in &mut lights {
let pos = transform.translation().truncate() - vec2(0.0, height.0) + light.offset.xy();
let light_aabb = Aabb2d {
min: pos - light.range,
max: pos + light.range,
};
if light_aabb.intersects(&camera_aabb) {
if !**visibility {
visibility.set();
let visible_lights = camera.1.get_mut(TypeId::of::<PointLight2d>());
visible_lights.push(entity);
previous_visible_entities.remove(&entity);
*visibility_timer = default();
}
light_rect.0 = light_rect
.0
.union(camera_rect.union_point(pos).intersect(Rect {
min: pos - light.range,
max: pos + light.range,
}));
}
visibility_timer.0.tick(time.delta());
}
}
fn mark_visible_occluders(
mut camera: Single<&mut VisibleEntities, With<FireflyConfig>>,
mut occluders: Query<(
Entity,
&OccluderAabb,
&mut ViewVisibility,
&mut VisibilityTimer,
)>,
mut previous_visible_entities: ResMut<PreviousVisibleEntities>,
light_rect: Res<LightRect>,
time: Res<Time>,
) {
let light_rect_aabb = Aabb2d {
min: light_rect.0.min,
max: light_rect.0.max,
};
for (entity, aabb, mut visibility, mut visibility_timer) in &mut occluders {
if aabb.0.intersects(&light_rect_aabb) {
if !**visibility {
visibility.set();
let visible_occluders = camera.get_mut(TypeId::of::<Occluder2d>());
visible_occluders.push(entity);
previous_visible_entities.remove(&entity);
*visibility_timer = default();
}
}
visibility_timer.0.tick(time.delta());
}
}
fn occluder_aabb(
mut occluders: Query<
(&Occluder2d, &GlobalTransform, &mut OccluderAabb),
Or<(Changed<GlobalTransform>, Changed<Occluder2d>)>,
>,
) {
for (occluder, transform, mut rect) in &mut occluders {
let isometry = Isometry2d {
rotation: Rot2::radians(transform.rotation().to_euler(EulerRot::XYZ).2),
translation: transform.translation().truncate() + occluder.offset.truncate(),
};
rect.0 = match occluder.shape() {
Occluder2dShape::RoundRectangle {
width,
height,
radius,
} => Aabb2d {
min: vec2(-width / 2.0, -height / 2.0) - radius,
max: vec2(width / 2.0, height / 2.0) + radius,
}
.transformed_by(isometry.translation, isometry.rotation),
Occluder2dShape::Polygon { vertices } => Aabb2d::from_point_cloud(isometry, vertices),
Occluder2dShape::Polyline { vertices } => Aabb2d::from_point_cloud(isometry, vertices),
}
}
}