bevy_lights 0.1.0

Practical lights for Bevy: fires that flicker and flare as they kindle, a budgeted pool of real lights placed over thousands of virtual ones, and billboard shader flames.
Documentation
//! Practical lights for Bevy: the lights a scene's props actually carry.
//!
//! A world can have thousands of torches and only afford a dozen real lights. This crate is the
//! three pieces that make that look right:
//!
//! - [`Flicker`] and [`Kindle`] — [`FlickerPlugin`] modulates a point or spot light around its base
//!   intensity with the doubled sine a fire's light has, and flares a light up over
//!   [`KINDLE_SECONDS`] when it is first lit instead of switching it on. [`Kindled`] is the message
//!   a host hears, for the sound of a lamp catching.
//! - [`LightPool`] — [`LightPoolPlugin`] keeps a fixed number of real lights and hands them to the
//!   [`PoolCandidate`]s nearest the host's focus entity every frame; the rest of the candidates are
//!   virtual. The lights it spawns carry [`PoolLight`], so a host can style them (a fire's palette
//!   recolours them, its flames are not drawn on them).
//! - [`FlameMaterial`], [`FlameSite`] — [`FlamePlugin`] draws a billboarded, additive shader flame
//!   standing on every [`FlameSite`], out of one material and one draw per flame: `src/flame.wesl`.
//!
//! Every plugin is independent; adding some and not others is supported, and
//! [`LightPoolPlugin`] warns (it does not panic) when [`FlickerPlugin`] is missing.
//!
//! ```no_run
//! # use bevy::prelude::*;
//! # use bevy_lights::*;
//! # #[derive(Component)]
//! # struct MyFocus;
//! # fn main() {
//! App::new()
//!     .add_plugins((FlickerPlugin, LightPoolPlugin::<MyFocus>::default(), FlamePlugin))
//!     .add_systems(Startup, |mut commands: Commands| {
//!         commands.spawn((PointLight { intensity: 60_000.0, range: 12.0, ..default() },
//!             Flicker { base: 60_000.0, phase: 0.0 }, Kindle::default(),
//!             FlameSite { height: 0.9, phase: 0.0 }));
//!         commands.spawn((MyFocus, Transform::default()));
//!         commands.spawn((PoolCandidate { offset: Vec3::Y * 1.8, intensity: 8_000.0 },
//!             Transform::from_xyz(4.0, 0.0, 2.0)));
//!     });
//! # }
//! ```
//!
//! The `App` above is not run by the doctest: `FlamePlugin` adds a `MaterialPlugin`, which needs a
//! render app, so running it would need a GPU. `tests/flicker.rs` and `tests/pool.rs` are the
//! behavioural proof for the other two plugins, and `src/flame.rs`'s own tests cover the flame
//! dressing headlessly.
//!
//! # What this crate does not own
//!
//! The sun, the moon, the sky, exposure and weather belong to a sky crate; a gameplay illuminance
//! grid, to a light-grid crate; and which lamp is lit and when, to the game or to a society crate.
//! This crate owns the practicals: their flicker, their kindling, the pool that decides which of
//! them are real, and the flames drawn on them. It never adds a directional light and never meters
//! exposure.
//!
//! # Units
//!
//! Intensities are Bevy's: lumens for a [`PointLight`] and a [`SpotLight`]. Distances are the
//! host's world units. Nothing in this crate converts, so a host in feet is in feet throughout.

#![warn(missing_docs)]

pub mod model;

mod flame;
mod flicker;
mod pool;

pub use flame::{Flame, FlameLook, FlameMaterial, FlamePlugin, FlameSite};
pub use flicker::{Flicker, FlickerPlugin, Kindle, Kindled};
pub use model::{
    KINDLE_SECONDS, RANGE_MARGIN, flicker_factor, kindle_gain, pack_flame_tag, raised_light, unpack_flame_tag,
};
pub use pool::{LightPool, LightPoolPlugin, PoolCandidate, PoolLight};

use bevy::prelude::*;

/// The order the crate's systems run in `Update`.
///
/// [`LightsSystems::Pool`] hands the real lights out before [`LightsSystems::Flicker`] modulates
/// them, so a light handed to a new candidate flickers at its new base in the very same frame; and
/// [`LightsSystems::Flame`] dresses sites after both, so a flame is drawn on the frame its site
/// appears.
#[derive(SystemSet, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LightsSystems {
    /// The light pool: spawning, resizing and assigning the real lights.
    Pool,
    /// `Flicker` and `Kindle`, on every point and spot light that carries them.
    Flicker,
    /// The shader flames: dressing new sites and ticking their shared clock.
    Flame,
}

/// Chains the crate's sets, and does it idempotently, so each plugin may call it.
fn chain_sets(app: &mut App) {
    app.configure_sets(Update, (LightsSystems::Pool, LightsSystems::Flicker, LightsSystems::Flame).chain());
}