Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Emitters

Emitters continuously spawn new particles into the simulation, replacing dead particles. They enable effects like fountains, explosions, rain, and any other continuous particle generation.

How Emitters Work

When you add an emitter, RDPE:

  1. Finds dead particles (those with alive == 0)
  2. Respawns them based on the emitter's rate and position
  3. Sets initial velocity according to the emitter type

Emitters work best with lifecycle rules:

  • Rule::Age - increments particle age each frame
  • Rule::Lifetime(seconds) - kills particles after a duration

Emitter Types

Point

Emits particles from a single point in all directions.

#![allow(unused)]
fn main() {
.with_emitter(Emitter::Point {
    position: Vec3::ZERO,
    rate: 500.0,    // particles per second
    speed: 1.0,     // initial speed (0 = random)
})
}

Burst

One-time explosion of particles. Fires once at simulation start.

#![allow(unused)]
fn main() {
.with_emitter(Emitter::Burst {
    position: Vec3::new(0.0, 0.5, 0.0),
    count: 1000,    // total particles to spawn
    speed: 3.0,     // outward speed
})
}

Cone

Directional emission in a cone shape. Great for fountains, jets, and thrusters.

#![allow(unused)]
fn main() {
.with_emitter(Emitter::Cone {
    position: Vec3::new(0.0, -0.5, 0.0),
    direction: Vec3::Y,     // points up
    speed: 2.5,
    spread: 0.3,            // cone half-angle in radians
    rate: 800.0,
})
}

The spread parameter controls the cone width:

  • 0.0 = laser beam (no spread)
  • 0.3 = tight cone (~17 degrees)
  • PI/4 = 45-degree cone
  • PI/2 = hemisphere

Sphere

Spawns particles on a sphere surface, moving outward (or inward).

#![allow(unused)]
fn main() {
.with_emitter(Emitter::Sphere {
    center: Vec3::ZERO,
    radius: 0.5,
    speed: 1.0,     // positive = outward, negative = inward
    rate: 1000.0,
})
}

Box

Spawns particles at random positions within a box volume.

#![allow(unused)]
fn main() {
.with_emitter(Emitter::Box {
    min: Vec3::new(-1.0, 1.0, -1.0),
    max: Vec3::new(1.0, 1.2, 1.0),
    velocity: Vec3::new(0.0, -2.0, 0.0),  // falling rain
    rate: 2000.0,
})
}

Complete Example

Here's a fountain that continuously emits particles:

use rdpe::prelude::*;

#[derive(Particle, Clone)]
struct Drop {
    position: Vec3,
    velocity: Vec3,
    #[color]
    color: Vec3,
}

fn main() {
    Simulation::<Drop>::new()
        .with_particle_count(10_000)
        .with_bounds(2.0)
        // Start all particles dead - emitter will spawn them
        .with_spawner(|_, _| Drop {
            position: Vec3::ZERO,
            velocity: Vec3::ZERO,
            color: Vec3::new(0.3, 0.6, 1.0),
        })
        // Cone emitter shooting upward
        .with_emitter(Emitter::Cone {
            position: Vec3::new(0.0, -0.8, 0.0),
            direction: Vec3::Y,
            speed: 3.0,
            spread: 0.2,
            rate: 1000.0,
        })
        // Lifecycle management
        .with_rule(Rule::Age)
        .with_rule(Rule::Lifetime(2.0))
        // Physics
        .with_rule(Rule::Gravity(4.0))
        .with_rule(Rule::Drag(0.3))
        .with_rule(Rule::BounceWalls)
        .run();
}

Multiple Emitters

You can add multiple emitters to create complex effects:

#![allow(unused)]
fn main() {
// Twin fountains
.with_emitter(Emitter::Cone {
    position: Vec3::new(-0.5, -0.8, 0.0),
    direction: Vec3::Y,
    speed: 3.0,
    spread: 0.15,
    rate: 500.0,
})
.with_emitter(Emitter::Cone {
    position: Vec3::new(0.5, -0.8, 0.0),
    direction: Vec3::Y,
    speed: 3.0,
    spread: 0.15,
    rate: 500.0,
})
}

Tips

  • Rate tuning: Match your rate to particle count and lifetime. If rate * lifetime > particle_count, you'll run out of dead particles to respawn.
  • Dead start: When using emitters, initialize particles as dead in your spawner (they'll be spawned by the emitter).
  • Burst timing: Burst emitters fire at time < 0.1, so they work immediately on startup.

Sub-Emitters

Sub-emitters spawn child particles when parent particles die. This enables fireworks, explosions, chain reactions, and biological reproduction.

Basic Sub-Emitter

#![allow(unused)]
fn main() {
#[derive(ParticleType)]
enum Firework {
    Rocket,
    Spark,
}

Simulation::<Particle>::new()
    .with_sub_emitter(SubEmitter::new(
        Firework::Rocket.into(),  // Parent type
        Firework::Spark.into(),   // Child type
    )
    .count(30)                    // Children per death
    .speed(1.0..3.0)              // Random speed range
    .spread(std::f32::consts::PI) // Hemisphere
    .inherit_velocity(0.3))       // 30% of parent velocity
    .run();
}

Sub-Emitter Options

MethodDescription
.count(n)Number of children per parent death
.speed(min..max)Random speed range
.spread(radians)0 = laser, PI = hemisphere, TAU = full sphere
.inherit_velocity(factor)0.0 to 1.0, how much parent velocity children get
.child_lifetime(secs)Override lifetime for children
.child_color(Vec3)Override color for children
.spawn_radius(r)Random offset from parent position

Chaining Sub-Emitters

Create multi-stage effects:

#![allow(unused)]
fn main() {
// Rockets → Sparks → Embers
.with_sub_emitter(SubEmitter::new(Rocket.into(), Spark.into()).count(30))
.with_sub_emitter(SubEmitter::new(Spark.into(), Ember.into()).count(5))
}