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.Fire—FirePluginwrites a whole fire from one component:Fire::lit(FireKind::Campfire, 250.0, 0.4)is the kind’s flame, the light the pool hands it, and its sparks, smoke, tongues and bed of coals, each fire on a clock of its own. It is the way in; the three above are the parts it writes, for a host that wants every number.
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.
The three plugins are independent of each other; adding some and not others is supported, and
LightPoolPlugin warns (it does not panic) when FlickerPlugin is missing. FirePlugin writes what the
other three run, so it warns when any of them 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, color: 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, color: 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::DEFAULT_BED_COOLING;pub use model::DEFAULT_FIRE_DIAMETER;pub use model::EMBER_AT;pub use model::FADE_SECONDS;pub use model::FIRE_KELVIN;pub use model::FLAME_REACH;pub use model::FLAME_TILT_LIMIT;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::MAX_TONGUES;pub use model::MAX_TRACKED_SPEED;pub use model::MERGE_RATIO;pub use model::MIN_TRACKED_SPEED;pub use model::MOON_KEY;pub use model::MOTION_RESPONSE;pub use model::Problem;pub use model::ProblemKind;pub use model::RANGE_MARGIN;pub use model::SMOKE_TAIL_SECONDS;pub use model::SNUFF_SECONDS;pub use model::Tongue;pub use model::WHIPPING_FLICKER_DEPTH;pub use model::bed_drawn_heat;pub use model::bed_heat_step;pub use model::bed_radiance;pub use model::bed_visible_heat;pub use model::blackbody_colour;pub use model::blackbody_luminance;pub use model::blackbody_radiance;pub use model::char_field;pub use model::char_radiance;pub use model::char_tile;pub use model::coal_radiance;pub use model::ember_cull_extents;pub use model::ember_reach;pub use model::fade_factor;pub use model::fill_for;pub use model::fire_lumens;pub use model::fire_lux;pub use model::fire_phase;pub use model::fire_reach;pub use model::flame_cull_extents;pub use model::flame_light_offset;pub use model::flame_mid;pub use model::flame_tilt;pub use model::flame_tongue;pub use model::flicker_depth;pub use model::flicker_factor;pub use model::flicker_mean;pub use model::flicker_swing;pub use model::focus_quota;pub use model::ground_lux;pub use model::ground_ramp;pub use model::ground_reach;pub use model::hand_off;pub use model::key_to_fill;pub use model::kindle_gain;pub use model::lean_light_shift;pub use model::pack_flame_tag;pub use model::preferred;pub use model::quota_cutoff;pub use model::raised_light;pub use model::relative_air;pub use model::retint;pub use model::shadow_set;pub use model::smooth_velocity;pub use model::snuff_gain;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§
- Fire
- A whole fire: its flame, the light the pool hands it, and its sparks, smoke, tongues and bed of coals, from a kind, a brightness and a colour.
- Fire
Plugin - Writes the components of every
Firethat has arrived or changed: seeFire’s “What the fire owns”. - Flame
- The drawn flame: the child mesh
FlamePluginspawns under eachFlameSite. - Flame
Embers - Sparks leaving a fire: add it to a
FlameSiteto give its flame embers, remove it to take them away. - Flame
Gust - A wind with gusts: add it and
FlamePluginwritesFlameWind::velocityevery frame, so the flames lean and ease with the air instead of standing at one angle. - 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: a fire’s colour (
.ais the ember’s strength, which scales the flame), the ramp that colour makes, and the animation’s clock and shape. - Flame
Motion - How a fire that moves feels its own motion: the wind it leans in is the wind less
gaintimes its velocity. - 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.
- Flame
Smoke - A fire’s smoke: add it to a
FlameSiteto give its flame a plume, remove it to take the plume away. - Flame
Tongue - An extra tongue of a
FlameTonguesfire: aFlamethat is not the site’s own, the one a host counts a fire’s tongues by (With<FlameTongue>) and that the sparks and the smoke skip. The host never spawns one. - Flame
Tongues - A wide fire’s tongues: add it to a
FlameSiteto draw the fire ascountflames over a bedspreadwide, remove it to go back to one. - Flame
Velocity - A
FlameSite’s own velocity on the ground plane, in metres per second: whatFlameMotionturns into wind. - Flame
Wind - The wind the flames lean in: a velocity on the ground plane, in metres per second.
- Flicker
- A light’s mean intensity and the clock phase its flicker runs at.
- Flicker
Plugin FlickerPlugin: modulates everyFlickerlight and advances everyKindle.- FuelBed
- A bed of coals under a fire: add it to a
FlameSiteto give the fire a bed that glows with it, remove it to take the bed away. - 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, which of those lights cast shadows, 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.
- Snuff
- A light going out: its intensity falls to nothing over
SNUFF_SECONDSinstead of switching off. - Snuffed
- A fire went out this frame: its
Snuffran its course, so the host can play the hiss, spawn the smoke and despawn or hide the prop. - Unknown
Fire Kind - A name that is not a
FireKind:FireKind::from_strrejects it.
Enums§
- Fire
Kind - A named kind of fire: its fuel-bed diameter, flicker depth and flame height together, so a picker (or a project file) stores one word instead of three loose numbers.
- Focus
Rule - How many viewers
LightPoolserves: one focus, or several that share its lights. - 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.
Constants§
- FIRE_
EDGE_ LUX - The illuminance, in lux, a
Firewith nolumensof its own is sized to put on the ground at the edge of its kind’sreach, with its light at the flame’s middle and the fire’s foot on the ground: the courtyard’s own figure for a carried fire, the shoulder two stops under a pool’s centre (docs/research/lighting-look.md), in the crate’s reference exposure (EV100 6). A design default, and only a starting point: the lumens that read right depend on the host’s exposure, which nothing here converts, so a host setsFire::lumens(or builds the fire withFire::lit). - MAX_
EMBER_ RATE - The most sparks a second one fire sheds: the shader draws sixteen slots and a spark lives up to 1.8 s, so eight a second is the
rate at which every slot is always busy.
FlameEmbers::rateabove it draws at it. - MAX_
SMOKE_ DENSITY - The most density a fire’s smoke can ask for:
FlameSmoke::densityabove it draws at it.