use glam::Vec3;
#[derive(Clone, Debug)]
pub enum Emitter {
Point {
position: Vec3,
rate: f32,
speed: f32,
},
Burst {
position: Vec3,
count: u32,
speed: f32,
},
Cone {
position: Vec3,
direction: Vec3,
speed: f32,
spread: f32,
rate: f32,
},
Sphere {
center: Vec3,
radius: f32,
speed: f32,
rate: f32,
},
Box {
min: Vec3,
max: Vec3,
velocity: Vec3,
rate: f32,
},
}
impl Emitter {
pub fn rate(&self) -> f32 {
match self {
Emitter::Point { rate, .. } => *rate,
Emitter::Burst { count, .. } => *count as f32,
Emitter::Cone { rate, .. } => *rate,
Emitter::Sphere { rate, .. } => *rate,
Emitter::Box { rate, .. } => *rate,
}
}
pub(crate) fn to_wgsl(&self, emitter_index: usize) -> String {
match self {
Emitter::Point { position, rate, speed } => {
let speed_code = if *speed > 0.0 {
format!(
r#"let vel_dir = normalize(vec3<f32>(vx, vy, vz));
p.velocity = vel_dir * {speed};"#
)
} else {
"p.velocity = vec3<f32>(vx, vy, vz) * 0.5;".to_string()
};
format!(
r#" // Point emitter {emitter_index} at ({}, {}, {})
if p.alive == 0u {{
let spawn_hash = (index * 1103515245u + u32(uniforms.time * 10000.0) + {emitter_index}u * 7919u) ^ (index >> 3u);
let spawn_chance = f32(spawn_hash & 0xFFFFu) / 65535.0;
let spawn_rate = {rate} * uniforms.delta_time / f32(num_particles);
if spawn_chance < spawn_rate {{
p.alive = 1u;
p.age = 0.0;
p.scale = 1.0;
p.particle_type = 0u;
p.position = vec3<f32>({}, {}, {});
// Random direction
let vhash = spawn_hash * 0x45d9f3bu;
let vx = f32((vhash >> 0u) & 0xFFu) / 128.0 - 1.0;
let vy = f32((vhash >> 8u) & 0xFFu) / 128.0 - 1.0;
let vz = f32((vhash >> 16u) & 0xFFu) / 128.0 - 1.0;
{speed_code}
}}
}}"#,
position.x, position.y, position.z,
position.x, position.y, position.z,
)
}
Emitter::Burst { position, count, speed } => {
format!(
r#" // Burst emitter {emitter_index} at ({}, {}, {})
// Fires once at time ~0, spawning first {count} particles
if index < {count}u && uniforms.time < 0.1 {{
p.alive = 1u;
p.age = 0.0;
p.scale = 1.0;
p.particle_type = 0u;
p.position = vec3<f32>({}, {}, {});
// Random outward direction (uniform on sphere)
let vhash = index * 2654435761u;
let theta = f32((vhash >> 0u) & 0xFFFFu) / 65535.0 * 6.28318;
let phi = acos(f32((vhash >> 16u) & 0xFFFFu) / 65535.0 * 2.0 - 1.0);
let dir = vec3<f32>(
sin(phi) * cos(theta),
sin(phi) * sin(theta),
cos(phi)
);
p.velocity = dir * {speed};
}}"#,
position.x, position.y, position.z,
position.x, position.y, position.z,
)
}
Emitter::Cone { position, direction, speed, spread, rate } => {
let dir = direction.normalize();
format!(
r#" // Cone emitter {emitter_index} at ({}, {}, {}) dir ({}, {}, {})
if p.alive == 0u {{
let spawn_hash = (index * 1103515245u + u32(uniforms.time * 10000.0) + {emitter_index}u * 7919u) ^ (index >> 3u);
let spawn_chance = f32(spawn_hash & 0xFFFFu) / 65535.0;
let spawn_rate = {rate} * uniforms.delta_time / f32(num_particles);
if spawn_chance < spawn_rate {{
p.alive = 1u;
p.age = 0.0;
p.scale = 1.0;
p.particle_type = 0u;
p.position = vec3<f32>({}, {}, {});
// Cone direction with spread
let base_dir = vec3<f32>({}, {}, {});
let vhash = spawn_hash * 0x45d9f3bu;
// Random angle within spread cone
let rand_angle = f32((vhash >> 0u) & 0xFFFFu) / 65535.0 * 6.28318;
let rand_spread = f32((vhash >> 16u) & 0xFFFFu) / 65535.0 * {spread};
// Create perpendicular vectors for rotation
let up = select(vec3<f32>(0.0, 1.0, 0.0), vec3<f32>(1.0, 0.0, 0.0), abs(base_dir.y) > 0.9);
let right = normalize(cross(up, base_dir));
let forward = cross(base_dir, right);
// Apply spread
let spread_x = sin(rand_spread) * cos(rand_angle);
let spread_y = sin(rand_spread) * sin(rand_angle);
let spread_z = cos(rand_spread);
let dir = normalize(right * spread_x + forward * spread_y + base_dir * spread_z);
p.velocity = dir * {speed};
}}
}}"#,
position.x, position.y, position.z,
dir.x, dir.y, dir.z,
position.x, position.y, position.z,
dir.x, dir.y, dir.z,
)
}
Emitter::Sphere { center, radius, speed, rate } => {
format!(
r#" // Sphere emitter {emitter_index} center ({}, {}, {}) radius {}
if p.alive == 0u {{
let spawn_hash = (index * 1103515245u + u32(uniforms.time * 10000.0) + {emitter_index}u * 7919u) ^ (index >> 3u);
let spawn_chance = f32(spawn_hash & 0xFFFFu) / 65535.0;
let spawn_rate = {rate} * uniforms.delta_time / f32(num_particles);
if spawn_chance < spawn_rate {{
p.alive = 1u;
p.age = 0.0;
p.scale = 1.0;
p.particle_type = 0u;
// Random point on sphere surface
let vhash = spawn_hash * 0x45d9f3bu;
let theta = f32((vhash >> 0u) & 0xFFFFu) / 65535.0 * 6.28318;
let phi = acos(f32((vhash >> 16u) & 0xFFFFu) / 65535.0 * 2.0 - 1.0);
let dir = vec3<f32>(
sin(phi) * cos(theta),
sin(phi) * sin(theta),
cos(phi)
);
p.position = vec3<f32>({}, {}, {}) + dir * {radius};
p.velocity = dir * {speed};
}}
}}"#,
center.x, center.y, center.z, radius,
center.x, center.y, center.z,
)
}
Emitter::Box { min, max, velocity, rate } => {
format!(
r#" // Box emitter {emitter_index} from ({}, {}, {}) to ({}, {}, {})
if p.alive == 0u {{
let spawn_hash = (index * 1103515245u + u32(uniforms.time * 10000.0) + {emitter_index}u * 7919u) ^ (index >> 3u);
let spawn_chance = f32(spawn_hash & 0xFFFFu) / 65535.0;
let spawn_rate = {rate} * uniforms.delta_time / f32(num_particles);
if spawn_chance < spawn_rate {{
p.alive = 1u;
p.age = 0.0;
p.scale = 1.0;
p.particle_type = 0u;
// Random position within box
let vhash = spawn_hash * 0x45d9f3bu;
let rx = f32((vhash >> 0u) & 0xFFu) / 255.0;
let ry = f32((vhash >> 8u) & 0xFFu) / 255.0;
let rz = f32((vhash >> 16u) & 0xFFu) / 255.0;
p.position = vec3<f32>(
mix({}, {}, rx),
mix({}, {}, ry),
mix({}, {}, rz)
);
p.velocity = vec3<f32>({}, {}, {});
}}
}}"#,
min.x, min.y, min.z,
max.x, max.y, max.z,
min.x, max.x,
min.y, max.y,
min.z, max.z,
velocity.x, velocity.y, velocity.z,
)
}
}
}
}