use bevy::light::{PointLight, SpotLight};
use bevy::prelude::*;
use bevy::time::Time;
use crate::LightsSystems;
use crate::chain_sets;
use crate::model::{FLICKER_DEPTH, KINDLE_SECONDS, flicker_depth, flicker_mean, flicker_swing, 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>()
.register_type::<crate::FireKind>()
.add_systems(Update, flicker_lights.in_set(LightsSystems::Flicker));
}
}
#[derive(Component, Clone, Copy, Debug, PartialEq, Reflect)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
#[reflect(Component)]
pub struct Flicker {
pub base: f32,
pub phase: f32,
pub diameter: Option<f32>,
pub depth: f32,
}
impl Default for Flicker {
fn default() -> Self {
Self { base: 0.0, phase: 0.0, diameter: None, depth: FLICKER_DEPTH }
}
}
impl Flicker {
pub fn at_mean(mean: f32, depth: f32) -> Self {
let depth = flicker_depth(depth);
Self { base: mean / flicker_mean(depth), depth, ..Self::default() }
}
pub fn mean(&self) -> f32 {
self.base * flicker_mean(self.depth)
}
pub fn set_mean(&mut self, mean: f32) {
self.base = mean / flicker_mean(self.depth);
}
pub fn set_depth(&mut self, depth: f32) {
let mean = self.mean();
self.depth = flicker_depth(depth);
self.set_mean(mean);
}
pub fn intensity_at(&self, t: f32, kindle: Option<f32>) -> f32 {
let gain = kindle.map_or(1.0, kindle_gain);
self.base * flicker_swing(self.diameter, self.depth, t, self.phase) * gain
}
}
impl Flicker {
pub fn problems(&self) -> Vec<crate::Problem> {
use crate::model::problem::{finite, non_negative, positive, within};
let mut out = Vec::new();
non_negative(&mut out, "base", self.base);
finite(&mut out, "phase", self.phase);
if let Some(d) = self.diameter {
positive(&mut out, "diameter", d);
}
within(&mut out, "depth", self.depth, 0.0, crate::MAX_FLICKER_DEPTH);
out
}
}
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Reflect)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
#[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 elapsed = kindle.map(|mut kindle| {
lit = kindle.elapsed == 0.0;
if kindle.elapsed < KINDLE_SECONDS {
kindle.elapsed = (kindle.elapsed + dt).max(f32::MIN_POSITIVE);
}
kindle.elapsed
});
if !hidden {
*light.intensity() = flicker.intensity_at(t, elapsed);
}
lit
}