summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKai Stevenson <kai@kaistevenson.com>2026-08-21 23:45:22 -0700
committerKai Stevenson <kai@kaistevenson.com>2026-08-22 00:07:01 -0700
commitdefc5f0712734f5decf5b7e174598d256cd6feb3 (patch)
treeddf0cfb03f0be4a3c941ab4b0f7ebd289ff4326e /src
parenta216e61a00bde792a49a92593012f9baecf86f3d (diff)
better explosions
Diffstat (limited to 'src')
-rw-r--r--src/main.rs45
-rw-r--r--src/renderer/mod.rs6
-rw-r--r--src/shader/particle.wgsl9
-rw-r--r--src/sim/cell/materials/fire.rs3
-rw-r--r--src/sim/lib/force.rs76
-rw-r--r--src/sim/lib/mod.rs1
6 files changed, 99 insertions, 41 deletions
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<uniform> camera: Camera;
@@ -16,6 +17,8 @@ struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) world: vec2<f32>,
@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<f32> {
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<f32>(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;