summaryrefslogtreecommitdiff
path: root/src/sim/particle_sim/mod.rs
diff options
context:
space:
mode:
authorKai Stevenson <kai@kaistevenson.com>2026-08-20 23:20:21 -0700
committerKai Stevenson <kai@kaistevenson.com>2026-08-20 23:20:21 -0700
commit533da47f2cc03a8b60a7c6c0bb5a938a7e523a03 (patch)
treee4709c9d56954c0a9b2bd3e21f64c036490ed070 /src/sim/particle_sim/mod.rs
parent29575e8592f5d6d566abb0ce9beaf794fb98a4e4 (diff)
particles
Diffstat (limited to 'src/sim/particle_sim/mod.rs')
-rw-r--r--src/sim/particle_sim/mod.rs101
1 files changed, 101 insertions, 0 deletions
diff --git a/src/sim/particle_sim/mod.rs b/src/sim/particle_sim/mod.rs
new file mode 100644
index 0000000..8bad61d
--- /dev/null
+++ b/src/sim/particle_sim/mod.rs
@@ -0,0 +1,101 @@
+use glam::{IVec2, Vec2};
+
+use crate::{
+ config::PIXELS_TO_METRES,
+ sim::{
+ cell::{cell::Cell, materials::MaterialId},
+ cell_sim::world::World,
+ particle_sim::particle::Particle,
+ },
+};
+
+pub mod particle;
+
+pub struct ParticleManager {
+ pub particles: Vec<Particle>,
+}
+
+const PARTICLE_GRAVITY: f32 = 9.81 * PIXELS_TO_METRES;
+
+impl ParticleManager {
+ pub fn particle_tick(&mut self, world: &mut World, delta_time: f32) {
+ let mut i = 0;
+ 'outer: while i < self.particles.len() {
+ let p = &mut self.particles[i];
+
+ p.life -= delta_time;
+ if p.life <= 0.0 {
+ self.particles.swap_remove(i);
+ continue 'outer;
+ }
+
+ p.velocity.y += PARTICLE_GRAVITY * delta_time;
+ let dt_velocity = p.velocity * delta_time;
+
+ // Amanatides and Woo's fast Voxel Traversal
+ {
+ let mut cur = IVec2::new(p.position.x.round() as i32, p.position.y.round() as i32);
+ // direction we step for each component on each iteration
+ let step_sign =
+ IVec2::new(dt_velocity.x.signum() as i32, dt_velocity.y.signum() as i32);
+ let delta = Vec2::new(1.0 / dt_velocity.x.abs(), 1.0 / dt_velocity.y.abs());
+
+ let frac = Vec2::new(
+ if step_sign.x > 0 {
+ (cur.x as f32 + 0.5) - p.position.x
+ } else {
+ p.position.x - (cur.x as f32 - 0.5)
+ },
+ if step_sign.y > 0 {
+ (cur.y as f32 + 0.5) - p.position.y
+ } else {
+ p.position.y - (cur.y as f32 - 0.5)
+ },
+ );
+ let mut t_max = frac * delta;
+
+ let mut prev = cur;
+ if let Some(cell) = world.get_cell_from_game_position(prev.x, prev.y)
+ && cell.material != MaterialId::Void
+ {
+ // already occupied, die
+ self.particles.swap_remove(i);
+ continue 'outer;
+ }
+ while t_max.min_element() <= 1.0 {
+ if t_max.x < t_max.y {
+ cur.x += step_sign.x;
+ t_max.x += delta.x;
+ } else {
+ cur.y += step_sign.y;
+ t_max.y += delta.y;
+ }
+ if let Some(cell) = world.get_cell_from_game_position(cur.x, cur.y)
+ && cell.material != MaterialId::Void
+ {
+ // write ourselves to the board
+ world.set_cell_from_game_position(
+ prev.x,
+ prev.y,
+ Cell::from_material(p.material),
+ false,
+ );
+ self.particles.swap_remove(i);
+ continue 'outer;
+ }
+ prev = cur;
+ }
+ }
+
+ // no collision, just move
+ p.position = p.position + dt_velocity;
+ i += 1;
+ }
+ }
+
+ pub fn new() -> Self {
+ ParticleManager {
+ particles: Vec::new(),
+ }
+ }
+}