summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/content/entities/entity_bullet_emitter.rs17
-rw-r--r--src/content/mod.rs1
-rw-r--r--src/content/vfx/mod.rs29
-rw-r--r--src/input.rs2
-rw-r--r--src/main.rs14
-rw-r--r--src/renderer/mod.rs222
-rw-r--r--src/shader/instance.wgsl6
-rw-r--r--src/shader/particle.wgsl11
-rw-r--r--src/shader/vfx_line.wgsl76
-rw-r--r--src/sim/sim_manager/mod.rs35
-rw-r--r--src/vfx/mod.rs58
11 files changed, 440 insertions, 31 deletions
diff --git a/src/content/entities/entity_bullet_emitter.rs b/src/content/entities/entity_bullet_emitter.rs
index c014e8c..3788dd9 100644
--- a/src/content/entities/entity_bullet_emitter.rs
+++ b/src/content/entities/entity_bullet_emitter.rs
@@ -2,7 +2,7 @@ use glam::{IVec2, Vec2};
use rapier2d::dynamics::RigidBodyBuilder;
use crate::{
- content::materials::MaterialId,
+ content::{materials::MaterialId, vfx::VfxMaterialId},
input::Input,
sim::{
cell::Cell,
@@ -10,6 +10,7 @@ use crate::{
lib::force::apply_bullet,
sim_manager::SimCtx,
},
+ vfx::VfxLine,
};
struct BulletEmitterEntityBehaviour {
@@ -27,11 +28,21 @@ impl EntityBehaviour for BulletEmitterEntityBehaviour {
if let Some(target) = self.target {
let pos = update_ctx.entity_data.transform(ctx).unwrap().0;
let dir = (target - pos).normalize();
+ let from = pos + (dir * 4.0);
self.shot_timer -= delta_time;
if self.shot_timer <= 0.0 {
- apply_bullet(ctx, pos + (dir * 4.0), target, 70);
- self.shot_timer = 0.1;
+ apply_bullet(ctx, from, target, 70);
+ // 0.1 seconds to travel 200 cells
+ let lifetime = (target - from).length() / 1000.0;
+ ctx.vfx_writer.write_vfx_line(VfxLine::new(
+ from,
+ target,
+ 1.3,
+ VfxMaterialId::Tracer,
+ lifetime,
+ ));
+ self.shot_timer = 0.2;
}
self.life -= delta_time;
diff --git a/src/content/mod.rs b/src/content/mod.rs
index 2044048..ee2f47c 100644
--- a/src/content/mod.rs
+++ b/src/content/mod.rs
@@ -1,2 +1,3 @@
pub mod entities;
pub mod materials;
+pub mod vfx;
diff --git a/src/content/vfx/mod.rs b/src/content/vfx/mod.rs
new file mode 100644
index 0000000..8843bf1
--- /dev/null
+++ b/src/content/vfx/mod.rs
@@ -0,0 +1,29 @@
+#[repr(u8)]
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub enum VfxMaterialId {
+ Tracer = 0,
+}
+
+pub struct VfxMaterialDef {
+ pub name: &'static str,
+ pub color: (u8, u8, u8, u8),
+ pub length: f32,
+}
+
+static MATERIALS: [VfxMaterialDef; 1] = [VfxMaterialDef {
+ name: "Tracer",
+ color: (0xFF, 0xAA, 0xAA, 0xFF),
+ length: 15.0,
+}];
+
+impl VfxMaterialId {
+ pub const ALL: [VfxMaterialId; 1] = [VfxMaterialId::Tracer];
+ #[inline]
+ pub fn def(self) -> &'static VfxMaterialDef {
+ &MATERIALS[self as usize]
+ }
+
+ pub fn from_index(idx: u8) -> VfxMaterialId {
+ VfxMaterialId::ALL[idx as usize]
+ }
+}
diff --git a/src/input.rs b/src/input.rs
index 7fe6c79..09a0436 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -9,7 +9,7 @@ use crate::{camera::Camera, sim::cell_manager::manager::CellManager};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Input {
// movement
- Up,
+ Up = 0,
Left,
Down,
Right,
diff --git a/src/main.rs b/src/main.rs
index ccca2db..6e6c9d9 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -4,6 +4,7 @@ mod content;
mod input;
mod renderer;
mod sim;
+mod vfx;
use futures::executor;
use std::{collections::VecDeque, sync::Arc, time::Instant};
@@ -30,6 +31,7 @@ use crate::{
},
renderer::RendererState,
sim::{rb_manager::DebugRenderMode, sim_manager::SimManager},
+ vfx::VfxManager,
};
pub type Error = Box<dyn std::error::Error>;
@@ -58,6 +60,7 @@ struct App {
// game
input_manager: InputManager,
camera: Option<Camera>,
+ vfx_manager: Option<VfxManager>,
sim_manager: Option<SimManager>,
// renderer
@@ -77,7 +80,9 @@ impl App {
camera.handle_camera_input(&self.input_manager, delta_time)
}
- if let Some(sim) = &mut self.sim_manager {
+ if let Some(sim) = &mut self.sim_manager
+ && let Some(vfx_manager) = &mut self.vfx_manager
+ {
// --TEST SPAWNING--
if self.input_manager.pressed(Input::Action1) {
sim.create_entity(entity_cube_def(
@@ -97,7 +102,7 @@ impl App {
));
}
- sim.update(&self.config, &self.input_manager, delta_time);
+ sim.update(vfx_manager, &self.config, &self.input_manager, delta_time);
self.input_manager.reset_for_frame();
}
}
@@ -113,6 +118,7 @@ impl Default for App {
camera: None,
+ vfx_manager: Some(VfxManager::new()),
sim_manager: Some(SimManager::new()),
last_render: Instant::now(),
@@ -206,15 +212,19 @@ impl ApplicationHandler for App {
if let Some(renderer_state) = &mut self.renderer_state
&& let Some(sim) = &mut self.sim_manager
+ && let Some(vfx) = &mut self.vfx_manager
&& let Some(camera) = &mut self.camera
{
renderer_state.render(
sim,
+ vfx,
camera,
&mut self.config,
&self.diagnostics,
&self.input_manager,
);
+
+ vfx.after_render(delta_time);
}
}
_ => {}
diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs
index f21c794..c1b156e 100644
--- a/src/renderer/mod.rs
+++ b/src/renderer/mod.rs
@@ -9,21 +9,22 @@ use crate::{
Config, Diagnostics, InputManager,
camera::Camera,
config::{CELLS_IN_CHUNK, CHUNK_SIZE},
- content::materials::MaterialId,
+ content::{materials::MaterialId, vfx::VfxMaterialId},
renderer::ui::draw_egui,
sim::{
- cell_manager::manager::CellManager,
- entity::EntityId,
- rb_manager::debug_render::DebugVertex,
- sim_manager::{SimCtx, SimManager},
+ cell_manager::manager::CellManager, entity::EntityId,
+ rb_manager::debug_render::DebugVertex, sim_manager::SimManager,
},
+ vfx::VfxManager,
};
// TODO derive from shared chunk config
const CHUNK_SLOTS: usize = 11 * 200;
-const RB_ENTITY_SLOTS: usize = 64;
-const CELL_SLOTS: usize = CHUNK_SLOTS + RB_ENTITY_SLOTS;
+const ENTITY_SLOTS: usize = 64;
+const CELL_SLOTS: usize = CHUNK_SLOTS + ENTITY_SLOTS;
const PARTICLE_SLOTS: usize = 10_000;
+// TODO make this dynamic
+const VFX_LINE_SLOTS: usize = 10_000;
const INITIAL_DEBUG_VERTEX_CAPACITY: usize = 4096;
@@ -46,6 +47,25 @@ struct RendererParticle {
life: f32,
}
+#[repr(C)]
+#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
+struct RendererVfxMaterial {
+ color: [f32; 4],
+ length: f32,
+ _padding: [u32; 3],
+}
+
+#[repr(C)]
+#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
+struct RendererVfxLine {
+ a: [f32; 2],
+ b: [f32; 2],
+ width: f32,
+ material: u32,
+ alive_for: f32,
+ lifetime: f32,
+}
+
fn srgb_to_linear(channel: u8) -> f32 {
let channel = channel as f32 / 255.0;
if channel <= 0.04045 {
@@ -84,7 +104,7 @@ pub struct RendererState {
// common
camera_uniform_buffer: wgpu::Buffer,
- // world pixels
+ // instances
instance_pipeline: wgpu::RenderPipeline,
instance_buffer: wgpu::Buffer,
cell_buffer: wgpu::Buffer,
@@ -95,6 +115,11 @@ pub struct RendererState {
particle_buffer: wgpu::Buffer,
particle_bind_group: wgpu::BindGroup,
+ // vfx
+ vfx_line_pipeline: wgpu::RenderPipeline,
+ vfx_line_buffer: wgpu::Buffer,
+ vfx_line_bind_group: wgpu::BindGroup,
+
// physics debug lines
debug_pipeline: wgpu::RenderPipeline,
debug_bind_group: wgpu::BindGroup,
@@ -193,6 +218,39 @@ impl RendererState {
mapped_at_creation: false,
});
+ queue.write_buffer(&palette_buffer, 0, bytemuck::cast_slice(&palette));
+
+ // --- VFX materials ---
+ let mut vfx_material_palette = [RendererVfxMaterial {
+ color: [0.0f32; 4],
+ length: 0.0,
+ _padding: [0; 3],
+ }; VfxMaterialId::ALL.len()];
+ for material in VfxMaterialId::ALL {
+ let def = material.def();
+ let i = material as usize;
+ vfx_material_palette[i].color = [
+ srgb_to_linear(def.color.0),
+ srgb_to_linear(def.color.1),
+ srgb_to_linear(def.color.2),
+ def.color.3 as f32 / 255.0,
+ ];
+ vfx_material_palette[i].length = def.length;
+ }
+
+ let vfx_material_palette_buffer = device.create_buffer(&wgpu::BufferDescriptor {
+ label: Some("VFX Material Palette buffer"),
+ size: size_of_val(&vfx_material_palette) as u64,
+ usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
+ mapped_at_creation: false,
+ });
+
+ queue.write_buffer(
+ &vfx_material_palette_buffer,
+ 0,
+ bytemuck::cast_slice(&vfx_material_palette),
+ );
+
// --- INSTANCES ---
let instance_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
@@ -298,8 +356,6 @@ impl RendererState {
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::<RendererInstance>()) as u64,
@@ -450,6 +506,120 @@ impl RendererState {
],
});
+ // --- VFX_LINES ---
+ let vfx_line_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,
+ },
+ // material 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,
+ // don't need vertex here
+ visibility: wgpu::ShaderStages::VERTEX | 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 vfx_line_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
+ label: Some("VFX Line shader"),
+ source: wgpu::ShaderSource::Wgsl(include_str!("../shader/vfx_line.wgsl").into()),
+ });
+
+ let vfx_line_pipeline_layout =
+ device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+ label: None,
+ immediate_size: 0,
+ bind_group_layouts: &[Some(&vfx_line_bind_group_layout)],
+ });
+
+ let vfx_line_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+ label: None,
+ layout: Some(&vfx_line_pipeline_layout),
+ vertex: wgpu::VertexState {
+ module: &vfx_line_shader,
+ entry_point: Some("vs_main"),
+ buffers: &[],
+ compilation_options: wgpu::PipelineCompilationOptions::default(),
+ },
+ fragment: Some(wgpu::FragmentState {
+ module: &vfx_line_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 vfx_line_buffer = device.create_buffer(&wgpu::BufferDescriptor {
+ label: Some("VFX line buffer"),
+ size: (VFX_LINE_SLOTS * size_of::<RendererVfxLine>()) as u64,
+ usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
+ mapped_at_creation: false,
+ });
+
+ let vfx_line_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: None,
+ layout: &vfx_line_bind_group_layout,
+ entries: &[
+ wgpu::BindGroupEntry {
+ binding: 0,
+ resource: camera_uniform_buffer.as_entire_binding(),
+ },
+ wgpu::BindGroupEntry {
+ binding: 1,
+ resource: vfx_material_palette_buffer.as_entire_binding(),
+ },
+ wgpu::BindGroupEntry {
+ binding: 2,
+ resource: vfx_line_buffer.as_entire_binding(),
+ },
+ ],
+ });
+
// --- DEBUG ---
let debug_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
@@ -554,6 +724,10 @@ impl RendererState {
particle_buffer,
particle_bind_group,
+ vfx_line_pipeline,
+ vfx_line_buffer,
+ vfx_line_bind_group,
+
debug_pipeline,
debug_bind_group,
debug_vertex_buffer,
@@ -575,6 +749,7 @@ impl RendererState {
pub fn render(
&mut self,
sim: &mut SimManager,
+ vfx_manager: &VfxManager,
camera: &mut Camera,
config: &mut Config,
diagnostics: &Diagnostics,
@@ -784,6 +959,28 @@ impl RendererState {
.write_buffer(&self.particle_buffer, 0, bytemuck::cast_slice(&particles));
}
+ let mut vfx_lines: Vec<RendererVfxLine> = Vec::new();
+
+ {
+ puffin::profile_scope!("Build VFX lines");
+ // TODO could cull this to visible, maybe it's slower?
+ vfx_lines = vfx_manager
+ .lines
+ .iter()
+ .map(|p| RendererVfxLine {
+ a: p.a.to_array(),
+ b: p.b.to_array(),
+ material: p.material as u32,
+ width: p.width,
+ alive_for: p.alive_for,
+ lifetime: p.lifetime,
+ })
+ .collect();
+
+ self.queue
+ .write_buffer(&self.vfx_line_buffer, 0, bytemuck::cast_slice(&vfx_lines));
+ }
+
let debug_vertex_count = if config.debug_render {
puffin::profile_scope!("Build physics debug lines");
let vertices = sim.rb_manager.debug_render(config.debug_render_mode);
@@ -854,6 +1051,11 @@ impl RendererState {
render_pass.set_bind_group(0, &self.particle_bind_group, &[]);
render_pass.draw(0..4, 0..particles.len() as u32);
+ // vfx
+ render_pass.set_pipeline(&self.vfx_line_pipeline);
+ render_pass.set_bind_group(0, &self.vfx_line_bind_group, &[]);
+ render_pass.draw(0..4, 0..vfx_lines.len() as u32);
+
// debug
if debug_vertex_count > 0 {
render_pass.set_pipeline(&self.debug_pipeline);
diff --git a/src/shader/instance.wgsl b/src/shader/instance.wgsl
index fb1e0b1..a549801 100644
--- a/src/shader/instance.wgsl
+++ b/src/shader/instance.wgsl
@@ -17,8 +17,8 @@ struct Instance {
@group(0) @binding(3) var<storage, read> cells: array<u32>;
struct VertexOutput {
- @builtin(position) clip_position: vec4<f32>,
- @location(0) world: vec2<f32>,
+ @builtin(position) clip_position: vec4f,
+ @location(0) world: vec2f,
@location(1) @interpolate(flat) instance: u32,
};
@@ -46,7 +46,7 @@ fn vs_main(
}
@fragment
-fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
+fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let instance = instances[in.instance];
let d = in.world - instance.centre;
diff --git a/src/shader/particle.wgsl b/src/shader/particle.wgsl
index 3dd61a9..227468b 100644
--- a/src/shader/particle.wgsl
+++ b/src/shader/particle.wgsl
@@ -14,8 +14,8 @@ struct Particle {
@group(0) @binding(2) var<storage, read> particles: array<Particle>;
struct VertexOutput {
- @builtin(position) clip_position: vec4<f32>,
- @location(0) world: vec2<f32>,
+ @builtin(position) clip_position: vec4f,
+ @location(0) world: vec2f,
@location(1) @interpolate(flat) instance: u32,
@location(2) @interpolate(flat) material: u32,
@location(3) @interpolate(flat) life: f32,
@@ -29,7 +29,7 @@ fn vs_main(
let particle = particles[n];
// (0,0),(1,0),(0,1),(1,1)
- let corner = vec2<f32>(f32(i & 1u), f32((i >> 1u) & 1u));
+ let corner = vec2f(f32(i & 1u), f32((i >> 1u) & 1u));
let world = particle.position + corner;
var out: VertexOutput;
@@ -43,9 +43,10 @@ fn vs_main(
}
@fragment
-fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
+fn fs_main(in: VertexOutput) -> @location(0) vec4f {
let particle = particles[in.instance];
// TODO can pack more in here and shift
let c = palette[in.material];
- return vec4<f32>(c.r, c.g, c.b, c.a * smoothstep(0.0, 1, in.life));
+ // if life is less than 1, smoothly reduce alpha to 0
+ return vec4f(c.r, c.g, c.b, c.a * smoothstep(0.0, 1, in.life));
}
diff --git a/src/shader/vfx_line.wgsl b/src/shader/vfx_line.wgsl
new file mode 100644
index 0000000..bef7645
--- /dev/null
+++ b/src/shader/vfx_line.wgsl
@@ -0,0 +1,76 @@
+struct Camera {
+ scale: vec2f,
+ centre: vec2f,
+};
+
+struct MaterialDef {
+ color: vec4f,
+ // the length of the path segment for t
+ length: f32,
+};
+
+struct VfxLine {
+ a: vec2f,
+ b: vec2f,
+ width: f32,
+ material: u32,
+ alive_for: f32,
+ lifetime: f32,
+}
+
+@group(0) @binding(0) var<uniform> camera: Camera;
+@group(0) @binding(1) var<storage, read> materials: array<MaterialDef>;
+@group(0) @binding(2) var<storage, read> vfx_lines: array<VfxLine>;
+
+struct VertexOutput {
+ @builtin(position) clip_position: vec4f,
+ @location(0) @interpolate(flat) color: vec4f,
+ // the length of the path segment for t
+ @location(1) @interpolate(flat) length: f32,
+ @location(2) @interpolate(flat) cur_centre: f32,
+ @location(3) dist: f32,
+};
+
+@vertex
+fn vs_main(
+ @builtin(vertex_index) i: u32,
+ @builtin(instance_index) n: u32,
+) -> VertexOutput {
+ let vfx_line = vfx_lines[n];
+
+ let d = vfx_line.b - vfx_line.a;
+ // (-y, x) is a 90deg ccw rotation
+ let p = normalize(vec2f(-d.y, d.x)) * (vfx_line.width * 0.5);
+ // 0=a,1=b
+ let cv = f32(i & 1u);
+ // direction to offset the orthogonal width
+ let co = f32(i >> 1u) * 2.0 - 1.0;
+ // select a/b, add offset
+ let world = mix(vfx_line.a, vfx_line.b, cv) + p * co;
+ var out: VertexOutput;
+ out.clip_position = vec4f((world - camera.centre) * camera.scale, 0.0, 1.0);
+
+ // material
+ let material = materials[vfx_line.material];
+ out.color = material.color;
+ out.length = material.length;
+
+ // computed
+ let l = length(d);
+ out.cur_centre = (l + material.length) * (vfx_line.alive_for / vfx_line.lifetime) - (material.length / 2);
+
+ // interpolated
+ out.dist = cv * l;
+
+ return out;
+}
+
+@fragment
+fn fs_main(in: VertexOutput) -> @location(0) vec4f {
+ // distance on line of the centre of the current (w.r.t. time) line segment
+ // can do this in vertex
+ // our distance along the line vs the current centre
+ let dist_from_centre = abs(in.dist - in.cur_centre);
+ let c = in.color;
+ return vec4f(c.r, c.g, c.b, c.a * (1 - smoothstep(0.0, in.length / 2, dist_from_centre)));
+}
diff --git a/src/sim/sim_manager/mod.rs b/src/sim/sim_manager/mod.rs
index 9ae66be..8cb5eb3 100644
--- a/src/sim/sim_manager/mod.rs
+++ b/src/sim/sim_manager/mod.rs
@@ -18,6 +18,7 @@ use crate::{
process_entity_update_result, read_back_entities_from_world, write_entities_to_world,
},
},
+ vfx::VfxWriter,
};
mod utils;
@@ -43,6 +44,7 @@ pub struct SimManager {
}
pub struct SimCtx<'a> {
+ pub vfx_writer: &'a mut dyn VfxWriter,
pub input_manager: &'a InputManager,
pub cell_manager: &'a mut CellManager,
pub rb_manager: &'a mut RbManager,
@@ -130,7 +132,13 @@ impl SimManager {
}
}
- fn cell_update(&mut self, config: &Config, input_manager: &InputManager, delta_time: f32) {
+ fn cell_update(
+ &mut self,
+ vfx_writer: &mut dyn VfxWriter,
+ config: &Config,
+ input_manager: &InputManager,
+ delta_time: f32,
+ ) {
// before we tick, write all the entities into the sim world
let written_entities_scope = write_entities_to_world(self);
@@ -187,6 +195,7 @@ impl SimManager {
for id in entity_ids {
let entity = self.entities.get_mut(&id);
let mut ctx = SimCtx {
+ vfx_writer,
input_manager,
cell_manager: &mut self.cell_manager,
rb_manager: &mut self.rb_manager,
@@ -203,11 +212,17 @@ impl SimManager {
read_back_entities_from_world(self, written_entities_scope);
}
- fn physics_update(&mut self, input_manager: &InputManager, delta_time: f32) {
+ fn physics_update(
+ &mut self,
+ vfx_writer: &mut dyn VfxWriter,
+ input_manager: &InputManager,
+ delta_time: f32,
+ ) {
let entity_ids: Vec<EntityId> = self.entities.keys().cloned().collect();
for id in entity_ids {
let entity = self.entities.get_mut(&id);
let mut ctx = SimCtx {
+ vfx_writer,
input_manager,
cell_manager: &mut self.cell_manager,
rb_manager: &mut self.rb_manager,
@@ -243,7 +258,13 @@ impl SimManager {
.tick(&mut self.cell_manager, delta_time);
}
- pub fn update(&mut self, config: &Config, input_manager: &InputManager, delta_time: f32) {
+ pub fn update(
+ &mut self,
+ vfx_writer: &mut dyn VfxWriter,
+ config: &Config,
+ input_manager: &InputManager,
+ delta_time: f32,
+ ) {
// handle sim controls
if input_manager.pressed(Input::Pause) {
self.paused = !self.paused;
@@ -268,14 +289,14 @@ impl SimManager {
self.last_cell_update = now;
if self.paused && self.ignore_pause_next_tick {
- self.cell_update(config, input_manager, delta_time);
+ self.cell_update(vfx_writer, config, input_manager, delta_time);
} else if !self.paused {
self.cell_updates_due +=
secs_since_last_cell_update / expected_secs_since_last_cell_update;
let mut cell_updates_done = 0;
// don't ever update more than 3 times per frame, or else we can get a pseudo deadlock
while self.cell_updates_due >= 1.0 && cell_updates_done < 3 {
- self.cell_update(config, input_manager, delta_time);
+ self.cell_update(vfx_writer, config, input_manager, delta_time);
self.cell_updates_due -= 1.0;
cell_updates_done += 1;
}
@@ -288,14 +309,14 @@ impl SimManager {
self.last_physics_update = now;
if self.paused && self.ignore_pause_next_tick {
- self.physics_update(input_manager, PHYSICS_DELTA_TIME);
+ self.physics_update(vfx_writer, input_manager, PHYSICS_DELTA_TIME);
} else if !self.paused {
self.physics_updates_due +=
secs_since_last_physics_update / expected_secs_since_last_physics_update;
let mut updates_done = 0;
// don't ever update more than 3 times per frame, or else we can get a pseudo deadlock
while self.physics_updates_due >= 1.0 && updates_done < 3 {
- self.physics_update(input_manager, PHYSICS_DELTA_TIME);
+ self.physics_update(vfx_writer, input_manager, PHYSICS_DELTA_TIME);
self.physics_updates_due -= 1.0;
updates_done += 1;
}
diff --git a/src/vfx/mod.rs b/src/vfx/mod.rs
new file mode 100644
index 0000000..3a7d1a5
--- /dev/null
+++ b/src/vfx/mod.rs
@@ -0,0 +1,58 @@
+use glam::Vec2;
+
+use crate::content::vfx::VfxMaterialId;
+
+pub struct VfxLine {
+ pub a: Vec2,
+ pub b: Vec2,
+ pub width: f32,
+ pub material: VfxMaterialId,
+ pub alive_for: f32,
+ pub lifetime: f32,
+}
+
+impl VfxLine {
+ pub fn new(a: Vec2, b: Vec2, width: f32, material: VfxMaterialId, lifetime: f32) -> Self {
+ VfxLine {
+ a,
+ b,
+ width,
+ material,
+ alive_for: 0.0,
+ lifetime,
+ }
+ }
+}
+
+pub trait VfxWriter {
+ fn write_vfx_line(&mut self, line: VfxLine) -> ();
+}
+
+pub struct VfxManager {
+ pub lines: Vec<VfxLine>,
+}
+
+impl VfxManager {
+ pub fn after_render(&mut self, delta_time: f32) {
+ let mut i = 0;
+ while i < self.lines.len() {
+ let l = &mut self.lines[i];
+ l.alive_for += delta_time;
+ if l.alive_for > l.lifetime {
+ self.lines.swap_remove(i);
+ continue;
+ }
+ i += 1;
+ }
+ }
+
+ pub fn new() -> Self {
+ VfxManager { lines: Vec::new() }
+ }
+}
+
+impl VfxWriter for VfxManager {
+ fn write_vfx_line(&mut self, line: VfxLine) -> () {
+ self.lines.push(line);
+ }
+}