particula-rs 0.1.8

A simple particle library
Documentation
use std::marker::PhantomData;

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// A collection of particles and emitters
pub trait ParticleSystem {
    /// The type of particle that this system will contain
    type ParticleType: Particle;

    /// The type of emitter that this system will contain
    type EmitterType: ParticleEmitter<ParticleType = Self::ParticleType>;

    /// Returns an iterator over all currently alive particles in the system
    fn iter_particles(&self) -> impl Iterator<Item = &Self::ParticleType>;

    /// Returns a mutable iterator over all currently alive particles in the system
    fn iter_particles_mut(&mut self) -> impl Iterator<Item = &mut Self::ParticleType>;

    /// Returns an iterator over all currently alive particle emitters in the system
    fn iter_emitters(&self) -> impl Iterator<Item = &Self::EmitterType>;

    /// Returns a mutable iterator over all currently alive particle emitters in the system
    fn iter_emitters_mut(&mut self) -> impl Iterator<Item = &mut Self::EmitterType>;

    /// Adds a particle to the system
    fn add_particle(&mut self, particle: Self::ParticleType);

    /// Adds an emitter to the system
    fn add_emitter(&mut self, emitter: Self::EmitterType);

    /// Iterates over all currently alive particles in the system and calls their update method
    fn update_particles(&mut self, dt: f64) {
        for particle in self.iter_particles_mut() {
            particle.update(dt);
        }
    }

    /// Iterates over all currently alive particle emitters in the system and calls their update method, returning the vector of new particles to add to the system
    fn update_emitters(&mut self, dt: f64) -> Vec<Self::ParticleType> {
        self.iter_emitters_mut()
            .flat_map(|emitter| emitter.update(dt))
            .collect()
    }

    /// Removes dead particles from the system
    fn clean_particles(&mut self);

    /// Removes dead emitters from the system
    fn clean_emitters(&mut self);

    /// Updates the particle system
    ///
    /// This method is comprised of 3 steps:
    /// 1. Update emitters and add the new particles to the system
    /// 2. Update all particles in the system
    /// 3. Remove dead particles and emitters from the system
    fn update(&mut self, dt: f64) {
        let new_particles = self.update_emitters(dt);

        for new_particle in new_particles {
            self.add_particle(new_particle);
        }

        self.update_particles(dt);

        self.clean_particles();
        self.clean_emitters();
    }
}

/// A base particle system using vectors to store the particles and emitters
///
/// This should suffice for most particle system needs
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct VecParticleSystem<P, E> {
    particles: Vec<P>,
    emitters: Vec<E>,
}

impl<P, E> Default for VecParticleSystem<P, E>
{
    fn default() -> Self {
        Self {
            particles: Vec::default(),
            emitters: Vec::default(),
        }
    }
}

impl<P: Particle, E: ParticleEmitter<ParticleType = P>> ParticleSystem for VecParticleSystem<P, E> {
    /// This system can hold any particle that implements `Particle` with the same `Coordinate` type
    type ParticleType = P;

    /// This system can hold any emitter that emits any particle that implements `Particle` with the same `Coordinate` type
    type EmitterType = E;

    fn iter_particles(&self) -> impl Iterator<Item = &Self::ParticleType> {
        self.particles.iter()
    }

    fn iter_particles_mut(&mut self) -> impl Iterator<Item = &mut Self::ParticleType> {
        self.particles.iter_mut()
    }

    fn iter_emitters(&self) -> impl Iterator<Item = &Self::EmitterType> {
        self.emitters.iter()
    }

    fn iter_emitters_mut(&mut self) -> impl Iterator<Item = &mut Self::EmitterType> {
        self.emitters.iter_mut()
    }

    fn add_particle(&mut self, particle: Self::ParticleType) {
        self.particles.push(particle);
    }

    fn add_emitter(&mut self, emitter: Self::EmitterType) {
        self.emitters.push(emitter);
    }

    fn clean_particles(&mut self) {
        self.particles.retain(|particle| particle.is_alive());
    }

    fn clean_emitters(&mut self) {
        self.emitters.retain(|emitter| emitter.is_alive());
    }
}

pub type BaseParticleSystem<C> = VecParticleSystem<
    Box<dyn Particle<Coordinate = C>>,
    Box<dyn ParticleEmitter<ParticleType = Box<dyn Particle<Coordinate = C>>>>,
>;

/// Creates new particles
pub trait ParticleEmitter {
    /// The type of the particles to be emitted
    type ParticleType: Particle;

    /// Update the state of the emitter and return a vector of particles to add to the system
    fn update(&mut self, dt: f64) -> Vec<Self::ParticleType>;

    /// Returns false if the emitter should be removed from the system
    fn is_alive(&self) -> bool;
}

impl<E: ParticleEmitter + ?Sized> ParticleEmitter for Box<E> {
    type ParticleType = E::ParticleType;

    fn update(&mut self, dt: f64) -> Vec<Self::ParticleType> {
        E::update(self, dt)
    }

    fn is_alive(&self) -> bool {
        E::is_alive(self)
    }
}

/// A particle emitter that never emits particles and is never alive.
/// Useful for when you don't really need emitters in your particle system.
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct NullParticleEmitter<P> {
    phantom: PhantomData<P>,
}

impl<P: Particle> ParticleEmitter for NullParticleEmitter<P> {
    type ParticleType = P;

    fn update(&mut self, _dt: f64) -> Vec<Self::ParticleType> {
        vec![]
    }

    fn is_alive(&self) -> bool {
        false
    }
}

/// A representation of some particle space
pub trait Particle {
    /// The position type of the particle
    type Coordinate;

    /// The position of the particle in space
    fn get_position(&self) -> Self::Coordinate;

    /// Updates the state of the particle
    fn update(&mut self, dt: f64);

    /// Returns false if the particle should be removed from the system
    fn is_alive(&self) -> bool;
}

impl<P: Particle + ?Sized> Particle for Box<P> {
    type Coordinate = P::Coordinate;

    fn get_position(&self) -> Self::Coordinate {
        P::get_position(self)
    }

    fn update(&mut self, dt: f64) {
        P::update(self, dt);
    }

    fn is_alive(&self) -> bool {
        P::is_alive(self)
    }
}

/// Tracks age in a particle
pub trait Aging {
    /// Gets the current age of the particle
    fn get_age(&self) -> f64;

    /// Sets the current age of the particle
    fn set_age(&mut self, age: f64);
}

/// Represents a particle that dies after a set amount of time
pub trait MaxAging: Aging {
    /// Gets the max age of the particle
    fn get_max_age(&self) -> f64;

    /// Gets the age of the particle from 0.0 to 1.0
    fn get_age_percent(&self) -> f64 {
        self.get_age() / self.get_max_age()
    }

    /// Returns false if the particle's age percent is greater than or equal to 1.0
    fn is_alive(&self) -> bool {
        self.get_age_percent() < 1.0
    }
}