From defc5f0712734f5decf5b7e174598d256cd6feb3 Mon Sep 17 00:00:00 2001 From: Kai Stevenson Date: Fri, 21 Aug 2026 23:45:22 -0700 Subject: better explosions --- src/main.rs | 45 ++++++------------------- src/renderer/mod.rs | 6 ++-- src/shader/particle.wgsl | 9 +++-- src/sim/cell/materials/fire.rs | 3 +- src/sim/lib/force.rs | 76 ++++++++++++++++++++++++++++++++++++++++++ src/sim/lib/mod.rs | 1 + 6 files changed, 99 insertions(+), 41 deletions(-) create mode 100644 src/sim/lib/force.rs diff --git a/src/main.rs b/src/main.rs index c947f05..e066e56 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,6 +28,7 @@ use crate::{ materials::{MaterialId, fire::FireCellView}, }, cell_sim::{sim::sim_tick, world::World}, + lib::force::apply_explosion, particle_sim::{ParticleManager, particle::Particle}, rb_sim::{DebugRenderMode, RbSimManager, debug_ops::DebugOperator}, write_rb_entity_to_world, @@ -157,40 +158,16 @@ impl App { { let mouse = Vec2::new(lm.0, lm.1); self.input.trigger_test_4 = false; - let r: i32 = 30; - for x in -r..r { - for y in -r..r { - if x.pow(2) + y.pow(2) < r.pow(2) { - // if there's a cell, convert it to a particle - let pos = Vec2::new(mouse.x + x as f32, mouse.y + y as f32); - let ipos = pos.round().as_ivec2(); - if let Some(cell) = world.get_cell_from_game_position(ipos.x, ipos.y) { - let mut fire = Cell::from_material(MaterialId::Fire); - fire.set_ticks_lived(300); - fire.match_parity(self.sim_seqno + 1); - if cell.material == MaterialId::Void && random_range(0.0..1.0) > 0.5 { - world.set_cell_from_game_position(ipos.x, ipos.y, fire, false); - } else { - world.set_cell_from_game_position(ipos.x, ipos.y, fire, false); - if random_range(0.0..1.0) > 0.6 { - let d = pos.distance(mouse); - if d < 0.001 { - continue; - } // skip the center cell - let dir = (pos - mouse) / d; // normalize, reusing d - let falloff = 1.0 - (d / r as f32); // 1 at center, 0 at edge - let vel = (Vec2::new(0.0, -0.6) + dir) - * 300.0 - * random_range(0.5..1.5) - * falloff; - pm.particles - .push(Particle::new(pos, vel, cell.material, 5.0)); - } - } - } - } - } - } + apply_explosion( + mouse, + 30, + Vec2::new(0.0, -0.6), + 300.0, + self.sim_seqno, + pm, + world, + rbsm, + ); } // --TEST DRAWING-- diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 2aabd48..233d66b 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -13,7 +13,7 @@ use crate::{ sim::{ cell::materials::MaterialId, cell_sim::world::World, - particle_sim::{ParticleManager, particle::Particle}, + particle_sim::ParticleManager, rb_sim::{RbSimManager, debug_render::DebugVertex, rb_entity::RbEntity}, }, }; @@ -42,7 +42,7 @@ struct RendererInstance { struct RendererParticle { position: [f32; 2], data: u32, - _padding: u32, + life: f32, } fn srgb_to_linear(channel: u8) -> f32 { @@ -771,7 +771,7 @@ impl RendererState { .map(|p| RendererParticle { position: [p.position.x, p.position.y], data: p.material as u32, - _padding: 0, + life: p.life, }) .collect(); diff --git a/src/shader/particle.wgsl b/src/shader/particle.wgsl index 704cfbe..3dd61a9 100644 --- a/src/shader/particle.wgsl +++ b/src/shader/particle.wgsl @@ -6,6 +6,7 @@ struct Camera { struct Particle { position: vec2f, data: u32, + life: f32, }; @group(0) @binding(0) var camera: Camera; @@ -16,6 +17,8 @@ struct VertexOutput { @builtin(position) clip_position: vec4, @location(0) world: vec2, @location(1) @interpolate(flat) instance: u32, + @location(2) @interpolate(flat) material: u32, + @location(3) @interpolate(flat) life: f32, }; @vertex @@ -33,6 +36,8 @@ fn vs_main( out.clip_position = vec4f((world - camera.centre) * camera.scale, 0.0, 1.0); out.world = world; out.instance = n; + out.material = particle.data & 0xFF; + out.life = particle.life; return out; } @@ -41,6 +46,6 @@ fn vs_main( fn fs_main(in: VertexOutput) -> @location(0) vec4 { let particle = particles[in.instance]; // TODO can pack more in here and shift - let c = palette[particle.data]; - return c; + let c = palette[in.material]; + return vec4(c.r, c.g, c.b, c.a * smoothstep(0.0, 1, in.life)); } diff --git a/src/sim/cell/materials/fire.rs b/src/sim/cell/materials/fire.rs index f582a0c..2a1225e 100644 --- a/src/sim/cell/materials/fire.rs +++ b/src/sim/cell/materials/fire.rs @@ -34,11 +34,11 @@ pub fn sim_update(ctx: &mut UpdateCtx) { // we have a chance to live longer--roughly ?% chance of living an extra second if ctx.rng.random_range(0.0..1.0) > 0.9 { // kill ourselves, with a chance to turn into ash - ctx.do_rewrite = false; if ctx.rng.random_range(0.0..1.0) > 0.8 { // TODO ash material // ctx.set_cell(0, 0, Cell::from_material(MaterialId::Sand)); } else { + ctx.do_rewrite = false; ctx.set_cell(0, 0, Cell::void()); } return; @@ -77,5 +77,4 @@ pub fn sim_update(ctx: &mut UpdateCtx) { } ctx.cell.set_ticks_lived(ticks_lived + 1); - ctx.set_cell(0, 0, *ctx.cell); } diff --git a/src/sim/lib/force.rs b/src/sim/lib/force.rs new file mode 100644 index 0000000..50b5c70 --- /dev/null +++ b/src/sim/lib/force.rs @@ -0,0 +1,76 @@ +use glam::Vec2; +use rand::random_range; + +use crate::sim::{ + cell::{ + cell::Cell, + materials::{MaterialId, fire::FireCellView}, + }, + cell_sim::world::World, + particle_sim::{ParticleManager, particle::Particle}, + rb_sim::RbSimManager, +}; + +pub fn apply_explosion( + centre: Vec2, + radius: i32, + force_offset: Vec2, + force_multiplier: f32, + next_seqno: u64, + particle_manager: &mut ParticleManager, + world: &mut World, + rbsm: &mut RbSimManager, +) { + let get_vel = |pos: Vec2| { + let d = pos.distance(centre); + if d < 0.001 { + return Vec2::ZERO; + } + let dir = (pos - centre) / d; + let falloff = 1.0 - (d / radius as f32); + return (force_offset + dir) * force_multiplier * random_range(0.5..1.5) * falloff; + }; + + // cell grid + for x in -radius..radius { + for y in -radius..radius { + if x.pow(2) + y.pow(2) < radius.pow(2) { + // if there's a cell, convert it to a particle + let pos = Vec2::new(centre.x + x as f32, centre.y + y as f32); + let ipos = pos.round().as_ivec2(); + if let Some(cell) = world.get_cell_from_game_position(ipos.x, ipos.y) { + let mut fire = Cell::from_material(MaterialId::Fire); + fire.set_ticks_lived(300); + fire.match_parity(next_seqno); + + if cell.material == MaterialId::Void && random_range(0.0..1.0) > 0.5 { + if random_range(0.0..1.0) > 0.1 { + world.set_cell_from_game_position(ipos.x, ipos.y, fire, false); + } else { + let vel = get_vel(pos); + particle_manager.particles.push(Particle::new( + pos, + vel, + MaterialId::Fire, + 1.0, + )); + } + } else { + world.set_cell_from_game_position(ipos.x, ipos.y, fire, false); + if random_range(0.0..1.0) > 0.6 { + let vel = get_vel(pos); + particle_manager.particles.push(Particle::new( + pos, + vel, + cell.material, + 5.0, + )); + } + } + } + } + } + } + + // rigidbodies +} diff --git a/src/sim/lib/mod.rs b/src/sim/lib/mod.rs index 1d802dc..a00d7e6 100644 --- a/src/sim/lib/mod.rs +++ b/src/sim/lib/mod.rs @@ -1 +1,2 @@ +pub mod force; pub mod marching_squares; -- cgit v1.3.1