Expand description
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:
FlickerandKindle—FlickerPluginmodulates a point or spot light around its base intensity with the doubled sine a fire’s light has, and flares a light up overKINDLE_SECONDSwhen it is first lit instead of switching it on.Kindledis the message a host hears, for the sound of a lamp catching.LightPool—LightPoolPluginkeeps a fixed number of real lights and hands them to thePoolCandidates nearest the host’s focus entity every frame; the rest of the candidates are virtual. The lights it spawns carryPoolLight, so a host can style them (a fire’s palette recolours them, its flames are not drawn on them).FlameMaterial,FlameSite—FlamePlugindraws a billboarded, additive shader flame standing on everyFlameSite, out of one material and one draw per flame:src/flame.wesl.
Under those three is the cinematographer’s half, model::cinematography: key_to_fill and
stops for a scene’s key against its fill, fill_for for the sky that makes a night,
fire_lumens for the size of a pool, and blackbody_colour and white_balanced for a
light’s colour from the temperature it burns at. They touch no light: the same numbers a director
of photography sets before a lamp is hung, for a host to size and colour its practicals with.
Every plugin is independent; adding some and not others is supported, and
LightPoolPlugin warns (it does not panic) when FlickerPlugin is missing.
App::new()
.add_plugins((FlickerPlugin, LightPoolPlugin::<MyFocus>::default(), FlamePlugin))
.add_systems(Startup, |mut commands: Commands| {
// A brazier: `diameter` is its fuel bed, which sizes the flicker through the puffing law
// (`f = 1.5/√D` Hz, so 0.34 m is 2.6 Hz). Leave it `None` for the un-sized rate.
commands.spawn((PointLight { intensity: 60_000.0, range: 12.0, ..default() },
Flicker { base: 60_000.0, phase: 0.0, diameter: Some(0.34), ..default() },
Kindle::default(),
// `diameter` matches the `Flicker`'s, so the drawn flame breathes with the light.
FlameSite { height: 0.9, phase: 0.0, kindle: 0.0, diameter: Some(0.34),
depth: FLICKER_DEPTH, depth_fade: false, fire: None }));
commands.spawn((MyFocus, Transform::default()));
// A carried fire: one entity holds the `FlameSite` and the `PoolCandidate`, so the light it
// is handed sits in the flame the host can see. `offset` is the flame's own centre, measured
// from the site's origin, and the lumens are sized for the height the light will sit at.
let height = 0.35;
commands.spawn((PoolCandidate {
offset: Vec3::from(flame_light_offset(height)),
intensity: 8_000.0,
..default()
},
FlameSite { height, phase: 9.0, kindle: 0.0, diameter: None, depth: FLICKER_DEPTH,
depth_fade: false, fire: None },
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.
Re-exports§
pub use model::CALM_FLICKER_DEPTH;pub use model::EMBER_AT;pub use model::FADE_SECONDS;pub use model::FLICKER_DEPTH;pub use model::HIGH_KEY;pub use model::HYSTERESIS;pub use model::KINDLE_SECONDS;pub use model::LOW_KEY;pub use model::MAX_FLICKER_DEPTH;pub use model::MOON_KEY;pub use model::RANGE_MARGIN;pub use model::WHIPPING_FLICKER_DEPTH;pub use model::blackbody_colour;pub use model::blackbody_luminance;pub use model::char_field;pub use model::char_radiance;pub use model::coal_radiance;pub use model::fade_factor;pub use model::fill_for;pub use model::fire_lumens;pub use model::flame_light_offset;pub use model::flicker_depth;pub use model::flicker_factor;pub use model::flicker_mean;pub use model::flicker_swing;pub use model::hand_off;pub use model::key_to_fill;pub use model::kindle_gain;pub use model::pack_flame_tag;pub use model::preferred;pub use model::raised_light;pub use model::stops;pub use model::unpack_flame_tag;pub use model::white_balanced;
Modules§
- model
- The engine-free half of the crate: plain
f32maths with nobevyin it.
Structs§
- Flame
- The drawn flame: the child mesh
FlamePluginspawns under eachFlameSite. - Flame
Look - How the scene’s flames look: the fire colour every flame is tinted from, how strongly it adds,
and the height scale a
FlameSite’s height is measured against. - Flame
Material - The flame material: the host’s fire colour (
.ais the ember’s strength, which scales the flame) and the animation’s clock and shape. - Flame
Plugin FlamePlugin: draws a flame on everyFlameSiteand ticks their shared clock.- Flame
Site - A fire drawn where no light is: a torch’s head, a sconce’s cage, a brazier whose light is a child entity.
- Flicker
- A light’s mean intensity and the clock phase its flicker runs at.
- Flicker
Plugin FlickerPlugin: modulates everyFlickerlight and advances everyKindle.- Kindle
- A light that is taking hold: its intensity flares up over
KINDLE_SECONDSinstead of switching on. - Kindled
- A fire took hold this frame: its
Kindlebegan, so the host can play the sound of it. - Light
Pool - The pool’s budget and the light it spawns: how many real lights exist, how far a candidate may be from the focus to get one, how brightly the pool burns, and the light every slot starts from.
- Light
Pool Plugin LightPoolPlugin: keepsLightPool::sizereal lights and hands them to the nearestPoolCandidates.- Pool
Budget - Which candidates hold a real light right now, and how many wanted one: the pool’s lamp schedule.
- Pool
Candidate - A prop that would like one of the pool’s real lights: how bright it burns, and where its light sits relative to the prop’s origin.
- Pool
Light - One of the crate’s spawned real lights. Marks an entity the pool owns and drives.
Enums§
- Lights
Systems - The order the crate’s systems run in
Update. - Rank
- How
LightPoolranks its candidates — and, sinceLightPool::hysteresiscompares the same quantity, how it decides whether a challenger is worth a hand-off.