use bevy::light::{PointLight, SpotLight};
use bevy::prelude::*;
use bevy::time::Time;
use crate::LightsSystems;
use crate::chain_sets;
use crate::model::{KINDLE_SECONDS, flicker_factor, kindle_gain};
pub struct FlickerPlugin;
impl Plugin for FlickerPlugin {
fn build(&self, app: &mut App) {
chain_sets(app);
app.add_message::<Kindled>()
.register_type::<Flicker>()
.register_type::<Kindle>()
.add_systems(Update, flicker_lights.in_set(LightsSystems::Flicker));
}
}
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Reflect)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[reflect(Component)]
pub struct Flicker {
pub base: f32,
pub phase: f32,
}
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Reflect)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[reflect(Component)]
pub struct Kindle {
pub elapsed: f32,
}
#[derive(Message, Clone, Copy, Debug, PartialEq, Eq)]
pub struct Kindled {
pub entity: Entity,
}
trait Emits: Component {
fn intensity(&mut self) -> &mut f32;
}
impl Emits for PointLight {
fn intensity(&mut self) -> &mut f32 {
&mut self.intensity
}
}
impl Emits for SpotLight {
fn intensity(&mut self) -> &mut f32 {
&mut self.intensity
}
}
type PointLightFrame<'w> = (
Entity,
&'w mut PointLight,
&'w Flicker,
Option<&'w mut Kindle>,
Option<&'w ViewVisibility>,
Option<&'w Visibility>,
);
type SpotLightFrame<'w> = (
Entity,
&'w mut SpotLight,
&'w Flicker,
Option<&'w mut Kindle>,
Option<&'w ViewVisibility>,
Option<&'w Visibility>,
);
fn flicker_lights(
time: Res<Time>,
mut points: Query<PointLightFrame, Without<SpotLight>>,
mut spots: Query<SpotLightFrame, Without<PointLight>>,
cameras: Query<(), With<Camera>>,
mut kindled: MessageWriter<Kindled>,
) {
let t = time.elapsed_secs();
let dt = time.delta_secs();
let watched = !cameras.is_empty();
for (entity, mut light, flicker, kindle, seen, visibility) in &mut points {
let hidden = is_hidden(visibility, seen, watched);
if modulate(&mut *light, *flicker, kindle, hidden, t, dt) {
kindled.write(Kindled { entity });
}
}
for (entity, mut light, flicker, kindle, seen, visibility) in &mut spots {
let hidden = is_hidden(visibility, seen, watched);
if modulate(&mut *light, *flicker, kindle, hidden, t, dt) {
kindled.write(Kindled { entity });
}
}
}
fn is_hidden(visibility: Option<&Visibility>, seen: Option<&ViewVisibility>, watched: bool) -> bool {
visibility.is_some_and(|visibility| *visibility == Visibility::Hidden)
|| (watched && seen.is_some_and(|seen| !seen.get()))
}
fn modulate<L: Emits>(
light: &mut L,
flicker: Flicker,
kindle: Option<Mut<Kindle>>,
hidden: bool,
t: f32,
dt: f32,
) -> bool {
let mut lit = false;
let gain = match kindle {
Some(mut kindle) => {
lit = kindle.elapsed == 0.0;
if kindle.elapsed < KINDLE_SECONDS {
kindle.elapsed = (kindle.elapsed + dt).max(f32::MIN_POSITIVE);
}
kindle_gain(kindle.elapsed)
}
None => 1.0,
};
if !hidden {
*light.intensity() = flicker.base * flicker_factor(t, flicker.phase) * gain;
}
lit
}