nightshade 0.8.2

A cross-platform data-oriented game engine.
Documentation
use crate::ecs::sprite::components::SpriteBlendMode;
use nalgebra_glm::Vec2;

#[derive(Debug, Clone, Copy)]
pub struct Particle2D {
    pub position: Vec2,
    pub velocity: Vec2,
    pub age: f32,
    pub lifetime: f32,
    pub rotation: f32,
    pub rotation_speed: f32,
}

#[derive(Debug, Clone, Copy, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub enum EmitterShape2D {
    #[default]
    Point,
    Circle {
        radius: f32,
    },
    Rectangle {
        half_extents: Vec2,
    },
}

#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub struct ColorRange2D {
    pub start: [f32; 4],
    pub end: [f32; 4],
}

impl Default for ColorRange2D {
    fn default() -> Self {
        Self {
            start: [1.0, 1.0, 1.0, 1.0],
            end: [1.0, 1.0, 1.0, 0.0],
        }
    }
}

impl ColorRange2D {
    pub fn new(start: [f32; 4], end: [f32; 4]) -> Self {
        Self { start, end }
    }
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SpriteParticleEmitter {
    pub enabled: bool,
    pub one_shot: bool,
    pub has_fired: bool,
    pub spawn_rate: f32,
    pub burst_count: u32,
    pub accumulated_spawn: f32,
    pub max_particles: u32,
    pub lifetime_min: f32,
    pub lifetime_max: f32,
    pub velocity_min: Vec2,
    pub velocity_max: Vec2,
    pub gravity: Vec2,
    pub drag: f32,
    pub size_start: Vec2,
    pub size_end: Vec2,
    pub color: ColorRange2D,
    pub rotation_min: f32,
    pub rotation_max: f32,
    pub rotation_speed_min: f32,
    pub rotation_speed_max: f32,
    pub blend_mode: SpriteBlendMode,
    pub texture_index: u32,
    pub uv_min: Vec2,
    pub uv_max: Vec2,
    pub depth: f32,
    pub anchor: Vec2,
    pub shape: EmitterShape2D,
    #[serde(skip)]
    pub particles: Vec<Particle2D>,
    #[serde(skip)]
    pub rng_state: u64,
}

impl Default for SpriteParticleEmitter {
    fn default() -> Self {
        Self {
            enabled: true,
            one_shot: false,
            has_fired: false,
            spawn_rate: 50.0,
            burst_count: 0,
            accumulated_spawn: 0.0,
            max_particles: 1000,
            lifetime_min: 0.5,
            lifetime_max: 1.5,
            velocity_min: Vec2::new(-50.0, -50.0),
            velocity_max: Vec2::new(50.0, 50.0),
            gravity: Vec2::new(0.0, -200.0),
            drag: 0.1,
            size_start: Vec2::new(8.0, 8.0),
            size_end: Vec2::new(2.0, 2.0),
            color: ColorRange2D::default(),
            rotation_min: 0.0,
            rotation_max: 0.0,
            rotation_speed_min: 0.0,
            rotation_speed_max: 0.0,
            blend_mode: SpriteBlendMode::Alpha,
            texture_index: 0,
            uv_min: Vec2::new(0.0, 0.0),
            uv_max: Vec2::new(1.0, 1.0),
            depth: 0.0,
            anchor: Vec2::new(0.0, 0.0),
            shape: EmitterShape2D::default(),
            particles: Vec::new(),
            rng_state: 0,
        }
    }
}

static EMITTER_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);

fn next_rng_seed() -> u64 {
    EMITTER_COUNTER
        .fetch_add(1, std::sync::atomic::Ordering::Relaxed)
        .wrapping_mul(6364136223846793005)
        .wrapping_add(1442695040888963407)
}

impl SpriteParticleEmitter {
    pub fn with_blend_mode(mut self, blend_mode: SpriteBlendMode) -> Self {
        self.blend_mode = blend_mode;
        self
    }

    pub fn with_shape(mut self, shape: EmitterShape2D) -> Self {
        self.shape = shape;
        self
    }

    pub fn with_texture(mut self, texture_index: u32) -> Self {
        self.texture_index = texture_index;
        self
    }

    pub fn with_uv(mut self, uv_min: Vec2, uv_max: Vec2) -> Self {
        self.uv_min = uv_min;
        self.uv_max = uv_max;
        self
    }

    pub fn with_color(mut self, color: ColorRange2D) -> Self {
        self.color = color;
        self
    }

    pub fn with_gravity(mut self, gravity: Vec2) -> Self {
        self.gravity = gravity;
        self
    }

    pub fn with_depth(mut self, depth: f32) -> Self {
        self.depth = depth;
        self
    }

    pub fn with_size(mut self, start: Vec2, end: Vec2) -> Self {
        self.size_start = start;
        self.size_end = end;
        self
    }

    pub fn with_velocity(mut self, min: Vec2, max: Vec2) -> Self {
        self.velocity_min = min;
        self.velocity_max = max;
        self
    }

    pub fn with_lifetime(mut self, min: f32, max: f32) -> Self {
        self.lifetime_min = min;
        self.lifetime_max = max;
        self
    }

    pub fn with_rotation(mut self, min: f32, max: f32) -> Self {
        self.rotation_min = min;
        self.rotation_max = max;
        self
    }

    pub fn with_rotation_speed(mut self, min: f32, max: f32) -> Self {
        self.rotation_speed_min = min;
        self.rotation_speed_max = max;
        self
    }

    pub fn with_spawn_rate(mut self, rate: f32) -> Self {
        self.spawn_rate = rate;
        self
    }

    pub fn with_burst(mut self, count: u32) -> Self {
        self.burst_count = count;
        self.one_shot = true;
        self
    }

    pub fn with_max_particles(mut self, max: u32) -> Self {
        self.max_particles = max;
        self
    }

    pub fn explosion(position_x: f32, position_y: f32) -> Self {
        Self {
            enabled: true,
            one_shot: true,
            has_fired: false,
            spawn_rate: 0.0,
            burst_count: 64,
            accumulated_spawn: 0.0,
            max_particles: 256,
            lifetime_min: 0.3,
            lifetime_max: 0.8,
            velocity_min: Vec2::new(-200.0, -200.0),
            velocity_max: Vec2::new(200.0, 200.0),
            gravity: Vec2::new(0.0, -300.0),
            drag: 0.2,
            size_start: Vec2::new(6.0, 6.0),
            size_end: Vec2::new(1.0, 1.0),
            color: ColorRange2D::new([1.0, 0.8, 0.2, 1.0], [1.0, 0.2, 0.0, 0.0]),
            rotation_min: 0.0,
            rotation_max: std::f32::consts::TAU,
            rotation_speed_min: -3.0,
            rotation_speed_max: 3.0,
            blend_mode: SpriteBlendMode::Additive,
            texture_index: 0,
            uv_min: Vec2::new(0.0, 0.0),
            uv_max: Vec2::new(1.0, 1.0),
            depth: 0.0,
            anchor: Vec2::new(position_x, position_y),
            shape: EmitterShape2D::Point,
            particles: Vec::new(),
            rng_state: next_rng_seed(),
        }
    }

    pub fn fire_trail(position_x: f32, position_y: f32) -> Self {
        Self {
            enabled: true,
            one_shot: false,
            has_fired: false,
            spawn_rate: 80.0,
            burst_count: 0,
            accumulated_spawn: 0.0,
            max_particles: 512,
            lifetime_min: 0.3,
            lifetime_max: 0.8,
            velocity_min: Vec2::new(-20.0, 30.0),
            velocity_max: Vec2::new(20.0, 80.0),
            gravity: Vec2::new(0.0, 50.0),
            drag: 0.3,
            size_start: Vec2::new(8.0, 8.0),
            size_end: Vec2::new(2.0, 2.0),
            color: ColorRange2D::new([1.0, 0.6, 0.1, 1.0], [1.0, 0.1, 0.0, 0.0]),
            rotation_min: 0.0,
            rotation_max: std::f32::consts::TAU,
            rotation_speed_min: -2.0,
            rotation_speed_max: 2.0,
            blend_mode: SpriteBlendMode::Additive,
            texture_index: 0,
            uv_min: Vec2::new(0.0, 0.0),
            uv_max: Vec2::new(1.0, 1.0),
            depth: 0.0,
            anchor: Vec2::new(position_x, position_y),
            shape: EmitterShape2D::Circle { radius: 4.0 },
            particles: Vec::new(),
            rng_state: next_rng_seed(),
        }
    }

    pub fn smoke(position_x: f32, position_y: f32) -> Self {
        Self {
            enabled: true,
            one_shot: false,
            has_fired: false,
            spawn_rate: 30.0,
            burst_count: 0,
            accumulated_spawn: 0.0,
            max_particles: 256,
            lifetime_min: 1.0,
            lifetime_max: 2.5,
            velocity_min: Vec2::new(-15.0, 20.0),
            velocity_max: Vec2::new(15.0, 60.0),
            gravity: Vec2::new(0.0, 10.0),
            drag: 0.2,
            size_start: Vec2::new(6.0, 6.0),
            size_end: Vec2::new(20.0, 20.0),
            color: ColorRange2D::new([0.5, 0.5, 0.5, 0.6], [0.3, 0.3, 0.3, 0.0]),
            rotation_min: 0.0,
            rotation_max: std::f32::consts::TAU,
            rotation_speed_min: -0.5,
            rotation_speed_max: 0.5,
            blend_mode: SpriteBlendMode::Alpha,
            texture_index: 0,
            uv_min: Vec2::new(0.0, 0.0),
            uv_max: Vec2::new(1.0, 1.0),
            depth: 0.0,
            anchor: Vec2::new(position_x, position_y),
            shape: EmitterShape2D::Circle { radius: 3.0 },
            particles: Vec::new(),
            rng_state: next_rng_seed(),
        }
    }

    pub fn sparks(position_x: f32, position_y: f32) -> Self {
        Self {
            enabled: true,
            one_shot: true,
            has_fired: false,
            spawn_rate: 0.0,
            burst_count: 32,
            accumulated_spawn: 0.0,
            max_particles: 128,
            lifetime_min: 0.2,
            lifetime_max: 0.5,
            velocity_min: Vec2::new(-150.0, 50.0),
            velocity_max: Vec2::new(150.0, 250.0),
            gravity: Vec2::new(0.0, -400.0),
            drag: 0.05,
            size_start: Vec2::new(3.0, 3.0),
            size_end: Vec2::new(1.0, 1.0),
            color: ColorRange2D::new([1.0, 1.0, 0.8, 1.0], [1.0, 0.5, 0.0, 0.0]),
            rotation_min: 0.0,
            rotation_max: 0.0,
            rotation_speed_min: 0.0,
            rotation_speed_max: 0.0,
            blend_mode: SpriteBlendMode::Additive,
            texture_index: 0,
            uv_min: Vec2::new(0.0, 0.0),
            uv_max: Vec2::new(1.0, 1.0),
            depth: 0.0,
            anchor: Vec2::new(position_x, position_y),
            shape: EmitterShape2D::Point,
            particles: Vec::new(),
            rng_state: next_rng_seed(),
        }
    }
}