use crate::mesh3d::Vec3;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Particle3D {
pub position: Vec3,
pub velocity: Vec3,
pub acceleration: Vec3,
pub color: [u8; 4],
pub size: f32,
pub lifetime: f32,
pub max_lifetime: f32,
pub rotation: f32,
pub rotation_speed: f32,
pub scale: f32,
pub scale_speed: f32,
pub color_shift: [f32; 4],
pub turbulence: f32,
}
impl Particle3D {
pub fn new(position: Vec3, velocity: Vec3, color: [u8; 4], lifetime: f32) -> Self {
Self {
position,
velocity,
acceleration: Vec3::new(0.0, 0.0, 0.0),
color,
size: 0.1,
lifetime,
max_lifetime: lifetime,
rotation: 0.0,
rotation_speed: 0.0,
scale: 1.0,
scale_speed: 0.0,
color_shift: [0.0, 0.0, 0.0, 0.0],
turbulence: 0.0,
}
}
pub fn with_acceleration(mut self, acceleration: Vec3) -> Self {
self.acceleration = acceleration;
self
}
pub fn with_rotation(mut self, rotation: f32, rotation_speed: f32) -> Self {
self.rotation = rotation;
self.rotation_speed = rotation_speed;
self
}
pub fn with_scale(mut self, scale: f32, scale_speed: f32) -> Self {
self.scale = scale;
self.scale_speed = scale_speed;
self
}
pub fn with_color_shift(mut self, shift: [f32; 4]) -> Self {
self.color_shift = shift;
self
}
pub fn with_turbulence(mut self, turbulence: f32) -> Self {
self.turbulence = turbulence;
self
}
pub fn update(&mut self, dt: f32, gravity: Vec3) {
self.velocity.x += (gravity.x + self.acceleration.x) * dt;
self.velocity.y += (gravity.y + self.acceleration.y) * dt;
self.velocity.z += (gravity.z + self.acceleration.z) * dt;
if self.turbulence > 0.0 {
use rand::Rng;
let mut rng = rand::thread_rng();
self.velocity.x += rng.gen_range(-self.turbulence..self.turbulence);
self.velocity.y += rng.gen_range(-self.turbulence..self.turbulence);
self.velocity.z += rng.gen_range(-self.turbulence..self.turbulence);
}
self.position.x += self.velocity.x * dt;
self.position.y += self.velocity.y * dt;
self.position.z += self.velocity.z * dt;
self.rotation += self.rotation_speed * dt;
self.scale += self.scale_speed * dt;
self.scale = self.scale.max(0.0);
let life_ratio = self.lifetime / self.max_lifetime;
for i in 0..3 {
let shift = self.color_shift[i] * (1.0 - life_ratio);
self.color[i] = (self.color[i] as f32 + shift).clamp(0.0, 255.0) as u8;
}
self.lifetime -= dt;
let alpha = (life_ratio * 255.0) as u8;
self.color[3] = alpha;
}
pub fn is_alive(&self) -> bool {
self.lifetime > 0.0
}
}
#[derive(Debug)]
pub struct ParticleSystem3D {
pub particles: Vec<Particle3D>,
pub gravity: Vec3,
pub max_particles: usize,
}
impl ParticleSystem3D {
pub fn new(max_particles: usize) -> Self {
Self {
particles: Vec::new(),
gravity: Vec3::new(0.0, -9.8, 0.0),
max_particles,
}
}
pub fn emit(&mut self, position: Vec3, velocity: Vec3, color: [u8; 4], lifetime: f32) {
if self.particles.len() < self.max_particles {
self.particles.push(Particle3D::new(position, velocity, color, lifetime));
}
}
pub fn emit_burst(&mut self, position: Vec3, count: usize, speed: f32, color: [u8; 4], lifetime: f32) {
use std::f32::consts::PI;
for i in 0..count {
let angle = (i as f32 / count as f32) * 2.0 * PI;
let elevation = ((i as f32 / count as f32) - 0.5) * PI;
let velocity = Vec3::new(
angle.cos() * elevation.cos() * speed,
elevation.sin() * speed,
angle.sin() * elevation.cos() * speed,
);
self.emit(position, velocity, color, lifetime);
}
}
pub fn update(&mut self, dt: f32) {
for particle in &mut self.particles {
particle.update(dt, self.gravity);
}
self.particles.retain(|p| p.is_alive());
}
pub fn clear(&mut self) {
self.particles.clear();
}
}
impl ParticleSystem3D {
pub fn explosion(&mut self, position: Vec3) {
use rand::Rng;
let mut rng = rand::thread_rng();
for _ in 0..50 {
let angle = rng.gen_range(0.0..std::f32::consts::TAU);
let elevation = rng.gen_range(-std::f32::consts::FRAC_PI_2..std::f32::consts::FRAC_PI_2);
let speed = rng.gen_range(3.0..8.0);
let vel = Vec3::new(
angle.cos() * elevation.cos() * speed,
elevation.sin() * speed,
angle.sin() * elevation.cos() * speed,
);
let mut particle = Particle3D::new(position, vel, [255, 150, 50, 255], rng.gen_range(0.5..1.5));
particle.color_shift = [-100.0, -50.0, 0.0, 0.0];
particle.scale_speed = -0.5;
self.particles.push(particle);
}
}
pub fn smoke(&mut self, position: Vec3) {
use rand::Rng;
let mut rng = rand::thread_rng();
for _ in 0..5 {
let vel = Vec3::new(
rng.gen_range(-0.5..0.5),
rng.gen_range(1.0..3.0),
rng.gen_range(-0.5..0.5),
);
let mut particle = Particle3D::new(position, vel, [100, 100, 100, 200], rng.gen_range(2.0..4.0));
particle.scale_speed = 0.3;
particle.turbulence = 0.1;
self.particles.push(particle);
}
}
pub fn sparkles(&mut self, position: Vec3) {
use rand::Rng;
let mut rng = rand::thread_rng();
for _ in 0..10 {
let vel = Vec3::new(
rng.gen_range(-3.0..3.0),
rng.gen_range(0.0..3.0),
rng.gen_range(-3.0..3.0),
);
let mut particle = Particle3D::new(position, vel, [255, 255, 100, 255], rng.gen_range(0.3..0.8));
particle.rotation_speed = rng.gen_range(-10.0..10.0);
self.particles.push(particle);
}
}
pub fn fire(&mut self, position: Vec3) {
use rand::Rng;
let mut rng = rand::thread_rng();
for _ in 0..8 {
let vel = Vec3::new(
rng.gen_range(-0.3..0.3),
rng.gen_range(2.0..4.0),
rng.gen_range(-0.3..0.3),
);
let color = if rng.gen_bool(0.7) {
[255, rng.gen_range(100..200), 0, 255]
} else {
[255, 255, 0, 255]
};
let mut particle = Particle3D::new(position, vel, color, rng.gen_range(0.5..1.5));
particle.color_shift = [-50.0, -100.0, 0.0, 0.0];
particle.scale_speed = -0.2;
particle.turbulence = 0.2;
self.particles.push(particle);
}
}
pub fn magic(&mut self, position: Vec3) {
use rand::Rng;
let mut rng = rand::thread_rng();
for _ in 0..15 {
let angle = rng.gen_range(0.0..std::f32::consts::TAU);
let radius = rng.gen_range(0.5..2.0);
let vel = Vec3::new(
angle.cos() * radius,
rng.gen_range(-1.0..2.0),
angle.sin() * radius,
);
let hue = rng.gen_range(0..360);
let color = Self::hsv_to_rgb(hue as f32, 1.0, 1.0);
let mut particle = Particle3D::new(position, vel, color, rng.gen_range(0.8..2.0));
particle.rotation_speed = rng.gen_range(-15.0..15.0);
particle.turbulence = 0.15;
self.particles.push(particle);
}
}
pub fn trail(&mut self, position: Vec3, direction: Vec3) {
use rand::Rng;
let mut rng = rand::thread_rng();
for _ in 0..3 {
let vel = Vec3::new(
direction.x * 0.5 + rng.gen_range(-0.5..0.5),
direction.y * 0.5 + rng.gen_range(-0.5..0.5),
direction.z * 0.5 + rng.gen_range(-0.5..0.5),
);
let mut particle = Particle3D::new(position, vel, [200, 200, 255, 255], rng.gen_range(0.3..0.8));
particle.scale_speed = -0.5;
self.particles.push(particle);
}
}
fn hsv_to_rgb(h: f32, s: f32, v: f32) -> [u8; 4] {
let c = v * s;
let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
let m = v - c;
let (r, g, b) = if h < 60.0 {
(c, x, 0.0)
} else if h < 120.0 {
(x, c, 0.0)
} else if h < 180.0 {
(0.0, c, x)
} else if h < 240.0 {
(0.0, x, c)
} else if h < 300.0 {
(x, 0.0, c)
} else {
(c, 0.0, x)
};
[
((r + m) * 255.0) as u8,
((g + m) * 255.0) as u8,
((b + m) * 255.0) as u8,
255,
]
}
}