flutter3d
Showcase Changelog 38 packages API reference

Particles that light

since 0.2.0 Physics and particles

A fire is its particles, so the light it casts should come from measuring them rather than from a second, hand-tuned glow running alongside. Mix LightEmitter into whatever you pass as a burst's or a standing emission's source, and the particle system fills in a ParticleGlow for you every step.

Step 1: Something to be lit by #

LightEmitter is a mixin with nothing in it beyond the ParticleGlow it carries. A torch, a muzzle flash, anything whose particles should count as light, mixes it in.

final ParticleEffect flame = ParticleEffect(
  count: 1,
  emitter: const ConeEmitter(speed: Range(0.8, 1.6), halfAngleDegrees: 12),
  lifetime: const Range(0.4, 0.7),
  size: const Range(0.08, 0.14),
  color: Vector4(1.0, 0.6, 0.2, 1.0),
  affectors: <ParticleAffector>[const ParticleFade(startsAt: 0.3)],
);
_particles.emit(
  _torch,
  flame,
  Vector3.zero(),
  perSecond: _rate,
  direction: Vector3(0.0, 1.0, 0.0),
);

Note. count on the effect only matters to a burst. A standing emission like this one spends its rate every sub-step and never reads count at all.

Step 2: A light with nothing to say yet #

The room needs an actual LightNode to be lit by, and it starts at zero intensity: nothing is known about the flame before the first step has run.

_glow = LightNode(name: 'glow', type: LightType.point, intensity: 0.0);

Step 3: Keep the flame stepping #

_particles.advance(dt);

Step 4: Let the light follow the fire #

Every step, the torch's glow is asked how much it amounts to and where it is centred, and the light is moved and dimmed or brightened to match. located stays false until the first particle has actually been measured, which is what keeps the light from sitting at the origin for the one frame before anything is burning.

final ParticleGlow glow = _torch.glow;
if (glow.located) {
  _glow.setPositionFrom(glow.centre);
  _glow.intensity = glow.power * 0.6;
}

Warning. A torch that goes out keeps casting light for a little while: the glow is smoothed, not read raw, so a strobe of one particle a frame does not read as one either.

Step 5: What the page checks #

A torch that has actually been burning for a step should have counted particles and measured a positive amount of light.

if (_torch.glow.count == 0 || _torch.glow.power <= 0.0) {
  throw StateError('the torch should be lit by its own flame');
}