diff options
| author | Kai Stevenson <kai@kaistevenson.com> | 2026-08-20 23:20:21 -0700 |
|---|---|---|
| committer | Kai Stevenson <kai@kaistevenson.com> | 2026-08-20 23:20:21 -0700 |
| commit | 533da47f2cc03a8b60a7c6c0bb5a938a7e523a03 (patch) | |
| tree | e4709c9d56954c0a9b2bd3e21f64c036490ed070 /src | |
| parent | 29575e8592f5d6d566abb0ce9beaf794fb98a4e4 (diff) | |
particles
Diffstat (limited to 'src')
| -rw-r--r-- | src/config.rs | 2 | ||||
| -rw-r--r-- | src/main.rs | 46 | ||||
| -rw-r--r-- | src/renderer/mod.rs | 248 | ||||
| -rw-r--r-- | src/shader/instance.wgsl (renamed from src/shader/shader.wgsl) | 0 | ||||
| -rw-r--r-- | src/shader/particle.wgsl | 46 | ||||
| -rw-r--r-- | src/sim/cell_sim/chunk.rs | 5 | ||||
| -rw-r--r-- | src/sim/mod.rs | 6 | ||||
| -rw-r--r-- | src/sim/particle_sim/mod.rs | 101 | ||||
| -rw-r--r-- | src/sim/particle_sim/particle.rs | 21 |
9 files changed, 419 insertions, 56 deletions
diff --git a/src/config.rs b/src/config.rs index eeb4f7e..7506c44 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,7 +3,7 @@ pub const WINDOW_TITLE: &str = "pxs"; pub const CHUNK_SIZE: i32 = 128; pub const CELLS_IN_CHUNK: usize = (CHUNK_SIZE * CHUNK_SIZE) as usize; -pub const CAMERA_MOVEMENT_SPEED: f32 = 40.0; +pub const CAMERA_MOVEMENT_SPEED: f32 = 2400.0; pub const SIM_FPS: u32 = 120; // pub const SIM_DELTA_TIME: f32 = 1.0 / SIM_FPS as f32; diff --git a/src/main.rs b/src/main.rs index e7f2084..08bc61c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ mod renderer; mod sim; use futures::executor; +use glam::Vec2; use rand::random_range; use std::{collections::VecDeque, sync::Arc, time::Instant}; use winit::{ @@ -24,6 +25,7 @@ use crate::{ sim::{ cell::{cell::Cell, materials::MaterialId}, cell_sim::{sim::sim_tick, world::World}, + particle_sim::{ParticleManager, particle::Particle}, rb_sim::{DebugRenderMode, RbSimManager, debug_ops::DebugOperator}, write_rb_entity_to_world, }, @@ -51,6 +53,7 @@ struct Input { is_rmb_pressed: bool, trigger_test_1: bool, trigger_test_2: bool, + trigger_test_3: bool, // keybindings is_up_pressed: bool, @@ -77,6 +80,7 @@ struct App { // physics rb_sim_manager: Option<RbSimManager>, + particle_manager: Option<ParticleManager>, // sim state // the last/current (not yet completed) seqno @@ -122,6 +126,24 @@ impl App { rbsm.test_spawn_ball(lm.0, lm.1, self.config.dropper_material); } + if self.input.trigger_test_3 + && let Some(lm) = self.input.last_mouse_world_pos + && let Some(pm) = &mut self.particle_manager + { + self.input.trigger_test_3 = false; + for i in 0..100 { + pm.particles.push(Particle { + position: Vec2::new( + lm.0 + random_range(-25.0..25.0), + lm.1 + random_range(-25.0..25.0), + ), + velocity: Vec2::ZERO, + material: MaterialId::Sand, + life: 2.0, + }) + } + } + // --TEST DRAWING-- if (self.input.is_lmb_pressed || self.input.is_rmb_pressed) && let Some(lm) = self.input.last_mouse_world_pos @@ -227,6 +249,12 @@ impl App { } physics_manager.rb_tick(physics_delta_time); } + // move the particles + if let Some(particle_manager) = &mut self.particle_manager + && let Some(world) = &mut self.world + { + particle_manager.particle_tick(world, physics_delta_time); + } } } @@ -244,6 +272,7 @@ impl Default for App { trigger_test_1: false, trigger_test_2: false, + trigger_test_3: false, is_lmb_pressed: false, is_rmb_pressed: false, is_up_pressed: false, @@ -257,6 +286,7 @@ impl Default for App { world: Some(World::from_default_size()), rb_sim_manager: Some(RbSimManager::new()), + particle_manager: Some(ParticleManager::new()), sim_seqno: 0, sim_paused: false, @@ -341,6 +371,11 @@ impl ApplicationHandler for App { KeyCode::KeyS => self.input.is_down_pressed = pressed, KeyCode::KeyD => self.input.is_right_pressed = pressed, KeyCode::KeyC => self.world = Some(World::from_default_size()), + KeyCode::KeyP => { + self.particle_manager + .as_mut() + .map(|pm| pm.particles = Vec::new()); + } KeyCode::KeyV => { if let Some(rbsm) = self.rb_sim_manager.as_mut() { let entity_ids: Vec<u32> = rbsm.rb_entities.keys().copied().collect(); @@ -370,6 +405,7 @@ impl ApplicationHandler for App { KeyCode::KeyX if pressed => self.ignore_pause_next_tick = true, KeyCode::Digit1 if pressed && !repeat => self.input.trigger_test_1 = true, KeyCode::Digit2 if pressed && !repeat => self.input.trigger_test_2 = true, + KeyCode::Digit3 if pressed && !repeat => self.input.trigger_test_3 = true, _ => {} } } @@ -412,14 +448,10 @@ impl ApplicationHandler for App { puffin::profile_scope!("redraw_requested"); // compute FPS diagnostics let now = Instant::now(); - let secs_since_last_frame = (now - self.last_render).as_secs_f32(); + let delta_time = (now - self.last_render).as_secs_f32(); self.last_render = now; - let delta_time = secs_since_last_frame / (1.0 / 60.0); - - self.diagnostics - .frame_times - .push_back(secs_since_last_frame); + self.diagnostics.frame_times.push_back(delta_time); if self.diagnostics.frame_times.len() > 30 { self.diagnostics.frame_times.pop_front(); @@ -475,11 +507,13 @@ impl ApplicationHandler for App { if let Some(renderer_state) = &mut self.renderer_state && let Some(world) = &mut self.world && let Some(rb_sim_manager) = &mut self.rb_sim_manager + && let Some(particle_manager) = &mut self.particle_manager && let Some(camera) = &mut self.camera { renderer_state.render( world, rb_sim_manager, + particle_manager, camera, &mut self.config, &self.diagnostics, diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index ff503c8..2aabd48 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -13,6 +13,7 @@ use crate::{ sim::{ cell::materials::MaterialId, cell_sim::world::World, + particle_sim::{ParticleManager, particle::Particle}, rb_sim::{RbSimManager, debug_render::DebugVertex, rb_entity::RbEntity}, }, }; @@ -21,12 +22,13 @@ use crate::{ const CHUNK_SLOTS: usize = 11 * 200; const RB_ENTITY_SLOTS: usize = 64; const CELL_SLOTS: usize = CHUNK_SLOTS + RB_ENTITY_SLOTS; +const PARTICLE_SLOTS: usize = 10_000; const INITIAL_DEBUG_VERTEX_CAPACITY: usize = 4096; #[repr(C)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] -struct Instance { +struct RendererInstance { centre: [f32; 2], cos_sin: [f32; 2], half_size: [f32; 2], @@ -35,6 +37,14 @@ struct Instance { _padding: u32, } +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +struct RendererParticle { + position: [f32; 2], + data: u32, + _padding: u32, +} + fn srgb_to_linear(channel: u8) -> f32 { let channel = channel as f32 / 255.0; if channel <= 0.04045 { @@ -70,12 +80,19 @@ pub struct RendererState { pub egui_state: egui_winit::State, egui_renderer: egui_wgpu::Renderer, - // world pixels - pixels_pipeline: wgpu::RenderPipeline, + // common camera_uniform_buffer: wgpu::Buffer, + + // world pixels + instance_pipeline: wgpu::RenderPipeline, instance_buffer: wgpu::Buffer, cell_buffer: wgpu::Buffer, - pixels_bind_group: wgpu::BindGroup, + instance_bind_group: wgpu::BindGroup, + + // particles + particle_pipeline: wgpu::RenderPipeline, + particle_buffer: wgpu::Buffer, + particle_bind_group: wgpu::BindGroup, // physics debug lines debug_pipeline: wgpu::RenderPipeline, @@ -157,7 +174,26 @@ impl RendererState { } }); - let pixels_bind_group_layout = + // --- PALETTE --- + let mut palette = [0.0f32; MaterialId::ALL.len() * 4]; + for material in MaterialId::ALL { + let def = material.def(); + let i = material as usize * 4; + palette[i] = srgb_to_linear(def.color.0); + palette[i + 1] = srgb_to_linear(def.color.1); + palette[i + 2] = srgb_to_linear(def.color.2); + palette[i + 3] = def.color.3 as f32 / 255.0; + } + + let palette_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Palette buffer"), + size: size_of_val(&palette) as u64, + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + // --- INSTANCES --- + let instance_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[ @@ -208,29 +244,29 @@ impl RendererState { ], }); - let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Shader"), - source: wgpu::ShaderSource::Wgsl(include_str!("../shader/shader.wgsl").into()), + let instance_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Instance shader"), + source: wgpu::ShaderSource::Wgsl(include_str!("../shader/instance.wgsl").into()), }); - let pixels_pipeline_layout = + let instance_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, immediate_size: 0, - bind_group_layouts: &[Some(&pixels_bind_group_layout)], + bind_group_layouts: &[Some(&instance_bind_group_layout)], }); - let pixels_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + let instance_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, - layout: Some(&pixels_pipeline_layout), + layout: Some(&instance_pipeline_layout), vertex: wgpu::VertexState { - module: &shader, + module: &instance_shader, entry_point: Some("vs_main"), buffers: &[], compilation_options: wgpu::PipelineCompilationOptions::default(), }, fragment: Some(wgpu::FragmentState { - module: &shader, + module: &instance_shader, entry_point: Some("fs_main"), targets: &[Some(wgpu::ColorTargetState { format: config.format, @@ -261,28 +297,11 @@ impl RendererState { mapped_at_creation: false, }); - let mut palette = [0.0f32; MaterialId::ALL.len() * 4]; - for material in MaterialId::ALL { - let def = material.def(); - let i = material as usize * 4; - palette[i] = srgb_to_linear(def.color.0); - palette[i + 1] = srgb_to_linear(def.color.1); - palette[i + 2] = srgb_to_linear(def.color.2); - palette[i + 3] = def.color.3 as f32 / 255.0; - } - - let palette_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("Palette buffer"), - size: size_of_val(&palette) as u64, - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - queue.write_buffer(&palette_buffer, 0, bytemuck::cast_slice(&palette)); let instance_buffer = device.create_buffer(&wgpu::BufferDescriptor { label: Some("Instance buffer"), - size: (CELL_SLOTS * size_of::<Instance>()) as u64, + size: (CELL_SLOTS * size_of::<RendererInstance>()) as u64, usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); @@ -294,9 +313,9 @@ impl RendererState { mapped_at_creation: false, }); - let pixels_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + let instance_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, - layout: &pixels_bind_group_layout, + layout: &instance_bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, @@ -317,6 +336,120 @@ impl RendererState { ], }); + // --- PARTICLES --- + let particle_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[ + // camera uniform + wgpu::BindGroupLayoutEntry { + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + binding: 0, + count: None, + visibility: wgpu::ShaderStages::VERTEX, + }, + // palette + wgpu::BindGroupLayoutEntry { + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + binding: 1, + count: None, + visibility: wgpu::ShaderStages::FRAGMENT, + }, + // instance buffer + wgpu::BindGroupLayoutEntry { + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: true }, + has_dynamic_offset: false, + min_binding_size: None, + }, + binding: 2, + count: None, + visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, + }, + ], + }); + + let particle_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Particle shader"), + source: wgpu::ShaderSource::Wgsl(include_str!("../shader/particle.wgsl").into()), + }); + + let particle_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + immediate_size: 0, + bind_group_layouts: &[Some(&particle_bind_group_layout)], + }); + + let particle_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&particle_pipeline_layout), + vertex: wgpu::VertexState { + module: &particle_shader, + entry_point: Some("vs_main"), + buffers: &[], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &particle_shader, + entry_point: Some("fs_main"), + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState::ALPHA_BLENDING), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleStrip, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: None, + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview_mask: None, + cache: None, + }); + + let particle_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Particle buffer"), + size: (PARTICLE_SLOTS * size_of::<RendererParticle>()) as u64, + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + let particle_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &particle_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: camera_uniform_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: palette_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: particle_buffer.as_entire_binding(), + }, + ], + }); + + // --- DEBUG --- let debug_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("Debug bind group layout"), @@ -409,11 +542,16 @@ impl RendererState { egui_state, egui_renderer, - pixels_pipeline, camera_uniform_buffer, + + instance_pipeline, instance_buffer, cell_buffer, - pixels_bind_group, + instance_bind_group, + + particle_pipeline, + particle_buffer, + particle_bind_group, debug_pipeline, debug_bind_group, @@ -437,6 +575,7 @@ impl RendererState { &mut self, world: &mut World, rb_sim_manager: &mut RbSimManager, + particle_manager: &mut ParticleManager, camera: &mut Camera, config: &mut Config, diagnostics: &Diagnostics, @@ -528,7 +667,7 @@ impl RendererState { ); let mut cell_buffer: [u8; CELLS_IN_CHUNK] = [0; CELLS_IN_CHUNK]; - let mut instances: Vec<Instance> = Vec::new(); + let mut instances: Vec<RendererInstance> = Vec::new(); { puffin::profile_scope!("Upload chunk cells"); @@ -587,7 +726,7 @@ impl RendererState { for cx in cxl..=cxu { for cy in cyl..=cyu { if let Some(idx) = world.chunk_position_to_chunk_idx.get(&(cx, cy)) { - instances.push(Instance { + instances.push(RendererInstance { centre: [ (cx * CHUNK_SIZE) as f32 + half_size[0], (cy * CHUNK_SIZE) as f32 + half_size[1], @@ -606,7 +745,7 @@ impl RendererState { if let Some(&RbEntity { width, height, .. }) = rb_sim_manager.rb_entities.get(id) && let Some((x, y, cos, sin)) = rb_sim_manager.get_rb_entity_transform(*id) { - instances.push(Instance { + instances.push(RendererInstance { centre: [x, y], cos_sin: [cos, sin], half_size: [width as f32 / 2.0, height as f32 / 2.0], @@ -621,6 +760,25 @@ impl RendererState { .write_buffer(&self.instance_buffer, 0, bytemuck::cast_slice(&instances)); } + let mut particles: Vec<RendererParticle> = Vec::new(); + + { + puffin::profile_scope!("Build particles"); + // TODO could cull this to visible, maybe it's slower? + particles = particle_manager + .particles + .iter() + .map(|p| RendererParticle { + position: [p.position.x, p.position.y], + data: p.material as u32, + _padding: 0, + }) + .collect(); + + self.queue + .write_buffer(&self.particle_buffer, 0, bytemuck::cast_slice(&particles)); + } + let debug_vertex_count = if config.debug_render { puffin::profile_scope!("Build physics debug lines"); let vertices = rb_sim_manager.debug_render(config.debug_render_mode); @@ -671,6 +829,7 @@ impl RendererState { multiview_mask: None, }); + // this is kind of a hack let instances_start = u32::min( if config.cells_render { 0 @@ -680,10 +839,17 @@ impl RendererState { instances.len() as u32 - 1, ); - render_pass.set_pipeline(&self.pixels_pipeline); - render_pass.set_bind_group(0, &self.pixels_bind_group, &[]); + // instances + render_pass.set_pipeline(&self.instance_pipeline); + render_pass.set_bind_group(0, &self.instance_bind_group, &[]); render_pass.draw(0..4, instances_start..instances.len() as u32); + // particles + render_pass.set_pipeline(&self.particle_pipeline); + render_pass.set_bind_group(0, &self.particle_bind_group, &[]); + render_pass.draw(0..4, 0..particles.len() as u32); + + // debug if debug_vertex_count > 0 { render_pass.set_pipeline(&self.debug_pipeline); render_pass.set_bind_group(0, &self.debug_bind_group, &[]); diff --git a/src/shader/shader.wgsl b/src/shader/instance.wgsl index fb1e0b1..fb1e0b1 100644 --- a/src/shader/shader.wgsl +++ b/src/shader/instance.wgsl diff --git a/src/shader/particle.wgsl b/src/shader/particle.wgsl new file mode 100644 index 0000000..704cfbe --- /dev/null +++ b/src/shader/particle.wgsl @@ -0,0 +1,46 @@ +struct Camera { + scale: vec2f, + centre: vec2f, +}; + +struct Particle { + position: vec2f, + data: u32, +}; + +@group(0) @binding(0) var<uniform> camera: Camera; +@group(0) @binding(1) var<storage, read> palette: array<vec4f>; +@group(0) @binding(2) var<storage, read> particles: array<Particle>; + +struct VertexOutput { + @builtin(position) clip_position: vec4<f32>, + @location(0) world: vec2<f32>, + @location(1) @interpolate(flat) instance: u32, +}; + +@vertex +fn vs_main( + @builtin(vertex_index) i: u32, + @builtin(instance_index) n: u32, +) -> VertexOutput { + let particle = particles[n]; + + // (0,0),(1,0),(0,1),(1,1) + let corner = vec2<f32>(f32(i & 1u), f32((i >> 1u) & 1u)); + let world = particle.position + corner; + + var out: VertexOutput; + out.clip_position = vec4f((world - camera.centre) * camera.scale, 0.0, 1.0); + out.world = world; + out.instance = n; + + return out; +} + +@fragment +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; +} diff --git a/src/sim/cell_sim/chunk.rs b/src/sim/cell_sim/chunk.rs index 85d2c09..3fe5056 100644 --- a/src/sim/cell_sim/chunk.rs +++ b/src/sim/cell_sim/chunk.rs @@ -1,10 +1,7 @@ use crate::{ config::{CELLS_IN_CHUNK, CHUNK_SIZE}, sim::{ - cell::{ - cell::Cell, - materials::{MaterialForm, MaterialId}, - }, + cell::{cell::Cell, materials::MaterialForm}, lib::marching_squares::Marchable, }, }; diff --git a/src/sim/mod.rs b/src/sim/mod.rs index c73a373..d9e358b 100644 --- a/src/sim/mod.rs +++ b/src/sim/mod.rs @@ -1,11 +1,9 @@ -use crate::{ - config::CHUNK_SIZE, - sim::{cell::materials::MaterialId, cell_sim::world::World, rb_sim::RbSimManager}, -}; +use crate::sim::{cell::materials::MaterialId, cell_sim::world::World, rb_sim::RbSimManager}; pub mod cell; pub mod cell_sim; pub mod lib; +pub mod particle_sim; pub mod rb_sim; pub fn write_rb_entity_to_world( 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(), + } + } +} diff --git a/src/sim/particle_sim/particle.rs b/src/sim/particle_sim/particle.rs new file mode 100644 index 0000000..cc7dd28 --- /dev/null +++ b/src/sim/particle_sim/particle.rs @@ -0,0 +1,21 @@ +use glam::Vec2; + +use crate::sim::cell::materials::MaterialId; + +pub struct Particle { + pub position: Vec2, + pub velocity: Vec2, + pub material: MaterialId, + pub life: f32, +} + +impl Particle { + pub fn new(position: Vec2, velocity: Vec2, material: MaterialId, life: f32) -> Self { + Particle { + position, + velocity, + material, + life, + } + } +} |
