Skip to main content

Crate bevy_lights

Crate bevy_lights 

Source
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:

  • 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 PoolCandidates 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.
  • Fire — FirePlugin writes 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::shared_value;
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 f32 maths with no bevy in 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.
FirePlugin
Writes the components of every Fire that has arrived or changed: see Fire’s “What the fire owns”.
Flame
The drawn flame: the child mesh FlamePlugin spawns under each FlameSite.
FlameEmbers
Sparks leaving a fire: add it to a FlameSite to give its flame embers, remove it to take them away.
FlameGust
A wind with gusts: add it and FlamePlugin writes FlameWind::velocity every frame, so the flames lean and ease with the air instead of standing at one angle.
FlameLook
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.
FlameMaterial
The flame material: a fire’s colour (.a is the ember’s strength, which scales the flame), the ramp that colour makes, and the animation’s clock and shape.
FlameMotion
How a fire that moves feels its own motion: the wind it leans in is the wind less gain times its velocity.
FlamePlugin
FlamePlugin: draws a flame on every FlameSite and ticks their shared clock.
FlameSite
A fire drawn where no light is: a torch’s head, a sconce’s cage, a brazier whose light is a child entity.
FlameSmoke
A fire’s smoke: add it to a FlameSite to give its flame a plume, remove it to take the plume away.
FlameTongue
An extra tongue of a FlameTongues fire: a Flame that 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.
FlameTongues
A wide fire’s tongues: add it to a FlameSite to draw the fire as count flames over a bed spread wide, remove it to go back to one.
FlameVelocity
A FlameSite’s own velocity on the ground plane, in metres per second: what FlameMotion turns into wind.
FlameWind
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.
FlickerPlugin
FlickerPlugin: modulates every Flicker light and advances every Kindle.
FuelBed
A bed of coals under a fire: add it to a FlameSite to 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_SECONDS instead of switching on.
Kindled
A fire took hold this frame: its Kindle began, so the host can play the sound of it.
LightPool
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.
LightPoolPlugin
LightPoolPlugin: keeps LightPool::size real lights and hands them to the nearest PoolCandidates.
PoolBudget
Which candidates hold a real light right now, which of those lights cast shadows, and how many wanted one: the pool’s lamp schedule.
PoolCandidate
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.
PoolLight
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_SECONDS instead of switching off.
Snuffed
A fire went out this frame: its Snuff ran its course, so the host can play the hiss, spawn the smoke and despawn or hide the prop.
UnknownFireKind
A name that is not a FireKind: FireKind::from_str rejects it.

Enums§

FireKind
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.
FocusRule
How many viewers LightPool serves: one focus, or several that share its lights.
LightsSystems
The order the crate’s systems run in Update.
Rank
How LightPool ranks its candidates — and, since LightPool::hysteresis compares the same quantity, how it decides whether a challenger is worth a hand-off.

Constants§

FIRE_EDGE_LUX
The illuminance, in lux, a Fire with no lumens of its own is sized to put on the ground at the edge of its kind’s reach, 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 sets Fire::lumens (or builds the fire with Fire::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::rate above it draws at it.
MAX_SMOKE_DENSITY
The most density a fire’s smoke can ask for: FlameSmoke::density above it draws at it.