use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum ForceField {
Radial {
center: [f64; 3],
strength: f64,
falloff: f64, radius: f64, },
Directional {
force: [f64; 3],
min: [f64; 3], max: [f64; 3], },
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SubEmitter {
pub count: u32,
pub speed: f64,
pub lifetime: f64,
pub radius: f64,
pub gravity_scale: f64,
pub restitution: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ParticleHandle(pub u64);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Particle {
pub handle: ParticleHandle,
pub position: [f64; 3],
pub velocity: [f64; 3],
pub lifetime: f64,
pub radius: f64,
pub drag: f64,
pub restitution: f64,
pub gravity_scale: f64,
pub damping: f64,
#[serde(default)]
pub on_death_emit: Option<SubEmitter>,
}
impl Particle {
pub fn new(position: [f64; 3], velocity: [f64; 3], lifetime: f64) -> Self {
Self {
handle: ParticleHandle(0), position,
velocity,
lifetime,
radius: 0.05,
drag: 0.0,
restitution: 0.3,
gravity_scale: 1.0,
damping: 0.0,
on_death_emit: None,
}
}
pub fn with_radius(mut self, radius: f64) -> Self {
self.radius = radius;
self
}
pub fn with_drag(mut self, drag: f64) -> Self {
self.drag = drag;
self
}
pub fn with_restitution(mut self, restitution: f64) -> Self {
self.restitution = restitution;
self
}
pub fn with_gravity_scale(mut self, scale: f64) -> Self {
self.gravity_scale = scale;
self
}
pub fn with_damping(mut self, damping: f64) -> Self {
self.damping = damping;
self
}
pub fn with_sub_emitter(mut self, sub: SubEmitter) -> Self {
self.on_death_emit = Some(sub);
self
}
#[must_use]
pub fn is_alive(&self) -> bool {
self.lifetime > 0.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct EmitterHandle(pub u64);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ParticleEmitter {
pub handle: EmitterHandle,
pub position: [f64; 3],
pub velocity: [f64; 3],
pub velocity_spread: [f64; 3],
pub rate: f64,
pub particle_lifetime: f64,
pub particle_radius: f64,
pub particle_restitution: f64,
pub particle_gravity_scale: f64,
pub particle_damping: f64,
pub active: bool,
pub(crate) accumulator: f64,
}
impl ParticleEmitter {
pub fn new(position: [f64; 3], velocity: [f64; 3], rate: f64) -> Self {
Self {
handle: EmitterHandle(0), position,
velocity,
velocity_spread: [0.0, 0.0, 0.0],
rate,
particle_lifetime: 2.0,
particle_radius: 0.05,
particle_restitution: 0.3,
particle_gravity_scale: 1.0,
particle_damping: 0.0,
active: true,
accumulator: 0.0,
}
}
pub fn with_spread(mut self, spread: [f64; 3]) -> Self {
self.velocity_spread = spread;
self
}
pub fn with_lifetime(mut self, lifetime: f64) -> Self {
self.particle_lifetime = lifetime;
self
}
pub fn with_radius(mut self, radius: f64) -> Self {
self.particle_radius = radius;
self
}
pub fn with_gravity_scale(mut self, scale: f64) -> Self {
self.particle_gravity_scale = scale;
self
}
pub fn with_damping(mut self, damping: f64) -> Self {
self.particle_damping = damping;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn particle_new() {
let p = Particle::new([1.0, 2.0, 0.0], [3.0, 4.0, 0.0], 5.0);
assert_eq!(p.position, [1.0, 2.0, 0.0]);
assert_eq!(p.velocity, [3.0, 4.0, 0.0]);
assert_eq!(p.lifetime, 5.0);
assert!(p.is_alive());
}
#[test]
fn particle_dead() {
let p = Particle::new([0.0, 0.0, 0.0], [0.0, 0.0, 0.0], 0.0);
assert!(!p.is_alive());
}
#[test]
fn particle_builder() {
let p = Particle::new([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], 3.0)
.with_radius(0.1)
.with_drag(0.5)
.with_restitution(0.8)
.with_gravity_scale(0.5)
.with_damping(0.1);
assert_eq!(p.radius, 0.1);
assert_eq!(p.drag, 0.5);
assert_eq!(p.restitution, 0.8);
assert_eq!(p.gravity_scale, 0.5);
assert_eq!(p.damping, 0.1);
}
#[test]
fn particle_serde() {
let p = Particle::new([1.0, 2.0, 0.0], [3.0, 4.0, 0.0], 5.0);
let json = serde_json::to_string(&p).unwrap();
let back: Particle = serde_json::from_str(&json).unwrap();
assert_eq!(p, back);
}
#[test]
fn emitter_new() {
let e = ParticleEmitter::new([0.0, 0.0, 0.0], [0.0, 10.0, 0.0], 100.0);
assert_eq!(e.rate, 100.0);
assert!(e.active);
assert_eq!(e.particle_lifetime, 2.0);
}
#[test]
fn emitter_builder() {
let e = ParticleEmitter::new([0.0, 0.0, 0.0], [0.0, 5.0, 0.0], 50.0)
.with_spread([1.0, 2.0, 0.0])
.with_lifetime(3.0)
.with_radius(0.2)
.with_gravity_scale(0.0)
.with_damping(0.5);
assert_eq!(e.velocity_spread, [1.0, 2.0, 0.0]);
assert_eq!(e.particle_lifetime, 3.0);
assert_eq!(e.particle_radius, 0.2);
assert_eq!(e.particle_gravity_scale, 0.0);
assert_eq!(e.particle_damping, 0.5);
}
#[test]
fn emitter_serde() {
let e = ParticleEmitter::new([1.0, 2.0, 0.0], [3.0, 4.0, 0.0], 10.0);
let json = serde_json::to_string(&e).unwrap();
let back: ParticleEmitter = serde_json::from_str(&json).unwrap();
assert_eq!(e, back);
}
#[test]
fn particle_handle_eq() {
assert_eq!(ParticleHandle(1), ParticleHandle(1));
assert_ne!(ParticleHandle(1), ParticleHandle(2));
}
}