Added particle foundation
parent
e5a96621a4
commit
0e38fdb21e
|
@ -50,5 +50,8 @@ pub const OBJECT_SPRITE_INSTANCE_LIMIT: u64 = 500;
|
||||||
/// We can draw at most this many ui sprites on the screen.
|
/// We can draw at most this many ui sprites on the screen.
|
||||||
pub const UI_SPRITE_INSTANCE_LIMIT: u64 = 100;
|
pub const UI_SPRITE_INSTANCE_LIMIT: u64 = 100;
|
||||||
|
|
||||||
|
/// The size of our circular particle buffer. When we create particles, the oldest ones are replaced.
|
||||||
|
pub const PARTICLE_SPRITE_INSTANCE_LIMIT: u64 = 1000;
|
||||||
|
|
||||||
/// Must be small enough to fit in an i32
|
/// Must be small enough to fit in an i32
|
||||||
pub const STARFIELD_SPRITE_INSTANCE_LIMIT: u64 = STARFIELD_COUNT * 24;
|
pub const STARFIELD_SPRITE_INSTANCE_LIMIT: u64 = STARFIELD_COUNT * 24;
|
||||||
|
|
|
@ -56,7 +56,6 @@ impl crate::Build for Texture {
|
||||||
index: ct.textures.len(),
|
index: ct.textures.len(),
|
||||||
aspect: dim.0 as f32 / dim.1 as f32,
|
aspect: dim.0 as f32 / dim.1 as f32,
|
||||||
};
|
};
|
||||||
ct.texture_index.insert(texture_name.clone(), h);
|
|
||||||
|
|
||||||
if texture_name == ct.starfield_texture_name {
|
if texture_name == ct.starfield_texture_name {
|
||||||
if ct.starfield_handle.is_none() {
|
if ct.starfield_handle.is_none() {
|
||||||
|
@ -67,9 +66,10 @@ impl crate::Build for Texture {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ct.texture_index.insert(texture_name.clone(), h);
|
||||||
ct.textures.push(Self {
|
ct.textures.push(Self {
|
||||||
name: texture_name,
|
name: texture_name,
|
||||||
path: path,
|
path,
|
||||||
handle: h,
|
handle: h,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ struct InstanceInput {
|
||||||
@location(3) transform_matrix_1: vec4<f32>,
|
@location(3) transform_matrix_1: vec4<f32>,
|
||||||
@location(4) transform_matrix_2: vec4<f32>,
|
@location(4) transform_matrix_2: vec4<f32>,
|
||||||
@location(5) transform_matrix_3: vec4<f32>,
|
@location(5) transform_matrix_3: vec4<f32>,
|
||||||
@location(6) texture_idx: u32,
|
@location(6) texture_index: u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VertexInput {
|
struct VertexInput {
|
||||||
|
@ -14,7 +14,7 @@ struct VertexInput {
|
||||||
struct VertexOutput {
|
struct VertexOutput {
|
||||||
@builtin(position) position: vec4<f32>,
|
@builtin(position) position: vec4<f32>,
|
||||||
@location(0) texture_coords: vec2<f32>,
|
@location(0) texture_coords: vec2<f32>,
|
||||||
@location(1) index: u32,
|
@location(1) texture_index: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ struct GlobalUniform {
|
||||||
starfield_texture: vec2<u32>,
|
starfield_texture: vec2<u32>,
|
||||||
starfield_tile_size: vec2<f32>,
|
starfield_tile_size: vec2<f32>,
|
||||||
starfield_size_limits: vec2<f32>,
|
starfield_size_limits: vec2<f32>,
|
||||||
|
current_time: vec2<f32>,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,7 +57,7 @@ fn vertex_main(
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
out.position = transform * vec4<f32>(vertex.position, 1.0);
|
out.position = transform * vec4<f32>(vertex.position, 1.0);
|
||||||
out.texture_coords = vertex.texture_coords;
|
out.texture_coords = vertex.texture_coords;
|
||||||
out.index = instance.texture_idx;
|
out.texture_index = instance.texture_index;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +65,7 @@ fn vertex_main(
|
||||||
@fragment
|
@fragment
|
||||||
fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
return textureSampleLevel(
|
return textureSampleLevel(
|
||||||
texture_array[in.index],
|
texture_array[in.texture_index],
|
||||||
sampler_array[0],
|
sampler_array[0],
|
||||||
in.texture_coords,
|
in.texture_coords,
|
||||||
0.0
|
0.0
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
struct InstanceInput {
|
||||||
|
@location(2) position: vec3<f32>,
|
||||||
|
@location(3) size: f32,
|
||||||
|
@location(4) expires: f32,
|
||||||
|
@location(5) texture_index: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VertexInput {
|
||||||
|
@location(0) position: vec3<f32>,
|
||||||
|
@location(1) texture_coords: vec2<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
@builtin(position) position: vec4<f32>,
|
||||||
|
@location(0) texture_coords: vec2<f32>,
|
||||||
|
@location(1) texture_index: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@group(1) @binding(0)
|
||||||
|
var<uniform> global: GlobalUniform;
|
||||||
|
struct GlobalUniform {
|
||||||
|
camera_position: vec2<f32>,
|
||||||
|
camera_zoom: vec2<f32>,
|
||||||
|
camera_zoom_limits: vec2<f32>,
|
||||||
|
window_size: vec2<f32>,
|
||||||
|
window_aspect: vec2<f32>,
|
||||||
|
starfield_texture: vec2<u32>,
|
||||||
|
starfield_tile_size: vec2<f32>,
|
||||||
|
starfield_size_limits: vec2<f32>,
|
||||||
|
current_time: vec2<f32>,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@group(0) @binding(0)
|
||||||
|
var texture_array: binding_array<texture_2d<f32>>;
|
||||||
|
@group(0) @binding(1)
|
||||||
|
var sampler_array: binding_array<sampler>;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vertex_main(
|
||||||
|
vertex: VertexInput,
|
||||||
|
instance: InstanceInput,
|
||||||
|
) -> VertexOutput {
|
||||||
|
|
||||||
|
var out: VertexOutput;
|
||||||
|
out.texture_coords = vertex.texture_coords;
|
||||||
|
out.texture_index = instance.texture_index;
|
||||||
|
|
||||||
|
if instance.expires < global.current_time.x {
|
||||||
|
out.position = vec4<f32>(2.0, 2.0, 0.0, 1.0);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
var scale: f32 = instance.size / global.camera_zoom.x;
|
||||||
|
var pos: vec2<f32> = vec2(vertex.position.x, vertex.position.y);
|
||||||
|
|
||||||
|
pos = pos * vec2<f32>(
|
||||||
|
1.0 * scale / global.window_aspect.x,
|
||||||
|
scale
|
||||||
|
);
|
||||||
|
|
||||||
|
var ipos: vec2<f32> = vec2(instance.position.x, instance.position.y) - global.camera_position;
|
||||||
|
pos = pos + vec2<f32>(
|
||||||
|
ipos.x / (global.camera_zoom.x/2.0) / global.window_aspect.x,
|
||||||
|
ipos.y / (global.camera_zoom.x/2.0)
|
||||||
|
);
|
||||||
|
|
||||||
|
out.position = vec4<f32>(pos, 0.0, 1.0) * instance.position.z;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
return textureSampleLevel(
|
||||||
|
texture_array[in.texture_index],
|
||||||
|
sampler_array[0],
|
||||||
|
in.texture_coords,
|
||||||
|
0.0
|
||||||
|
).rgba;
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ struct GlobalUniform {
|
||||||
starfield_texture: vec2<u32>,
|
starfield_texture: vec2<u32>,
|
||||||
starfield_tile_size: vec2<f32>,
|
starfield_tile_size: vec2<f32>,
|
||||||
starfield_size_limits: vec2<f32>,
|
starfield_size_limits: vec2<f32>,
|
||||||
|
current_time: vec2<f32>,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ struct InstanceInput {
|
||||||
@location(4) transform_matrix_2: vec4<f32>,
|
@location(4) transform_matrix_2: vec4<f32>,
|
||||||
@location(5) transform_matrix_3: vec4<f32>,
|
@location(5) transform_matrix_3: vec4<f32>,
|
||||||
@location(6) color_transform: vec4<f32>,
|
@location(6) color_transform: vec4<f32>,
|
||||||
@location(7) texture_idx: u32,
|
@location(7) texture_index: u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VertexInput {
|
struct VertexInput {
|
||||||
|
@ -15,7 +15,7 @@ struct VertexInput {
|
||||||
struct VertexOutput {
|
struct VertexOutput {
|
||||||
@builtin(position) position: vec4<f32>,
|
@builtin(position) position: vec4<f32>,
|
||||||
@location(0) texture_coords: vec2<f32>,
|
@location(0) texture_coords: vec2<f32>,
|
||||||
@location(1) index: u32,
|
@location(1) texture_index: u32,
|
||||||
@location(2) color_transform: vec4<f32>,
|
@location(2) color_transform: vec4<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ struct GlobalUniform {
|
||||||
starfield_texture: vec2<u32>,
|
starfield_texture: vec2<u32>,
|
||||||
starfield_tile_size: vec2<f32>,
|
starfield_tile_size: vec2<f32>,
|
||||||
starfield_size_limits: vec2<f32>,
|
starfield_size_limits: vec2<f32>,
|
||||||
|
current_time: vec2<f32>,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -58,7 +59,7 @@ fn vertex_main(
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
out.position = transform * vec4<f32>(vertex.position, 1.0);
|
out.position = transform * vec4<f32>(vertex.position, 1.0);
|
||||||
out.texture_coords = vertex.texture_coords;
|
out.texture_coords = vertex.texture_coords;
|
||||||
out.index = instance.texture_idx;
|
out.texture_index = instance.texture_index;
|
||||||
out.color_transform = instance.color_transform;
|
out.color_transform = instance.color_transform;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
@ -67,7 +68,7 @@ fn vertex_main(
|
||||||
@fragment
|
@fragment
|
||||||
fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
return textureSampleLevel(
|
return textureSampleLevel(
|
||||||
texture_array[in.index],
|
texture_array[in.texture_index],
|
||||||
sampler_array[0],
|
sampler_array[0],
|
||||||
in.texture_coords,
|
in.texture_coords,
|
||||||
0.0
|
0.0
|
||||||
|
|
|
@ -30,7 +30,7 @@ pub struct GlobalDataContent {
|
||||||
/// Size ratio of window, in physical pixels
|
/// Size ratio of window, in physical pixels
|
||||||
pub window_size: [f32; 2],
|
pub window_size: [f32; 2],
|
||||||
|
|
||||||
// Aspect ration of window
|
/// Aspect ratio of window
|
||||||
/// Second component is ignored.
|
/// Second component is ignored.
|
||||||
pub window_aspect: [f32; 2],
|
pub window_aspect: [f32; 2],
|
||||||
|
|
||||||
|
@ -42,8 +42,12 @@ pub struct GlobalDataContent {
|
||||||
/// Second component is ignored.
|
/// Second component is ignored.
|
||||||
pub starfield_tile_size: [f32; 2],
|
pub starfield_tile_size: [f32; 2],
|
||||||
|
|
||||||
// Min and max starfield star size, in game units
|
/// Min and max starfield star size, in game units
|
||||||
pub starfield_size_limits: [f32; 2],
|
pub starfield_size_limits: [f32; 2],
|
||||||
|
|
||||||
|
/// Current game time, in seconds.
|
||||||
|
/// Second component is ignored.
|
||||||
|
pub current_time: [f32; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlobalDataContent {
|
impl GlobalDataContent {
|
||||||
|
@ -70,7 +74,7 @@ impl GlobalData {
|
||||||
},
|
},
|
||||||
count: None,
|
count: None,
|
||||||
}],
|
}],
|
||||||
label: Some("camera_bind_group_layout"),
|
label: Some("globaldata bind group layout"),
|
||||||
});
|
});
|
||||||
|
|
||||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
@ -79,7 +83,7 @@ impl GlobalData {
|
||||||
binding: 0,
|
binding: 0,
|
||||||
resource: buffer.as_entire_binding(),
|
resource: buffer.as_entire_binding(),
|
||||||
}],
|
}],
|
||||||
label: Some("camera_bind_group"),
|
label: Some("globaldata bind group"),
|
||||||
});
|
});
|
||||||
|
|
||||||
return Self {
|
return Self {
|
||||||
|
|
|
@ -2,7 +2,7 @@ use anyhow::Result;
|
||||||
use bytemuck;
|
use bytemuck;
|
||||||
use cgmath::{Deg, EuclideanSpace, Matrix4, Point2, Vector3};
|
use cgmath::{Deg, EuclideanSpace, Matrix4, Point2, Vector3};
|
||||||
use galactica_constants;
|
use galactica_constants;
|
||||||
use std::{iter, rc::Rc};
|
use std::{iter, rc::Rc, time::Instant};
|
||||||
use wgpu;
|
use wgpu;
|
||||||
use winit::{self, dpi::LogicalSize, window::Window};
|
use winit::{self, dpi::LogicalSize, window::Window};
|
||||||
|
|
||||||
|
@ -10,13 +10,13 @@ use crate::{
|
||||||
content,
|
content,
|
||||||
globaldata::{GlobalData, GlobalDataContent},
|
globaldata::{GlobalData, GlobalDataContent},
|
||||||
pipeline::PipelineBuilder,
|
pipeline::PipelineBuilder,
|
||||||
sprite::ObjectSubSprite,
|
sprite::{ObjectSubSprite, ParticleBuilder},
|
||||||
starfield::Starfield,
|
starfield::Starfield,
|
||||||
texturearray::TextureArray,
|
texturearray::TextureArray,
|
||||||
vertexbuffer::{
|
vertexbuffer::{
|
||||||
consts::{SPRITE_INDICES, SPRITE_VERTICES},
|
consts::{SPRITE_INDICES, SPRITE_VERTICES},
|
||||||
types::{ObjectInstance, StarfieldInstance, TexturedVertex, UiInstance},
|
types::{ObjectInstance, ParticleInstance, StarfieldInstance, TexturedVertex, UiInstance},
|
||||||
VertexBuffer,
|
BufferObject, VertexBuffer,
|
||||||
},
|
},
|
||||||
ObjectSprite, UiSprite, OPENGL_TO_WGPU_MATRIX,
|
ObjectSprite, UiSprite, OPENGL_TO_WGPU_MATRIX,
|
||||||
};
|
};
|
||||||
|
@ -39,6 +39,7 @@ pub struct GPUState {
|
||||||
|
|
||||||
object_pipeline: wgpu::RenderPipeline,
|
object_pipeline: wgpu::RenderPipeline,
|
||||||
starfield_pipeline: wgpu::RenderPipeline,
|
starfield_pipeline: wgpu::RenderPipeline,
|
||||||
|
particle_pipeline: wgpu::RenderPipeline,
|
||||||
ui_pipeline: wgpu::RenderPipeline,
|
ui_pipeline: wgpu::RenderPipeline,
|
||||||
|
|
||||||
starfield: Starfield,
|
starfield: Starfield,
|
||||||
|
@ -51,6 +52,12 @@ struct VertexBuffers {
|
||||||
object: Rc<VertexBuffer>,
|
object: Rc<VertexBuffer>,
|
||||||
starfield: Rc<VertexBuffer>,
|
starfield: Rc<VertexBuffer>,
|
||||||
ui: Rc<VertexBuffer>,
|
ui: Rc<VertexBuffer>,
|
||||||
|
|
||||||
|
/// The index of the next particle slot we'll write to.
|
||||||
|
/// This must cycle to 0 whenever it exceeds the size
|
||||||
|
/// of the particle instance array.
|
||||||
|
particle_array_head: u64,
|
||||||
|
particle: Rc<VertexBuffer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GPUState {
|
impl GPUState {
|
||||||
|
@ -120,7 +127,7 @@ impl GPUState {
|
||||||
|
|
||||||
let vertex_buffers = VertexBuffers {
|
let vertex_buffers = VertexBuffers {
|
||||||
object: Rc::new(VertexBuffer::new::<TexturedVertex, ObjectInstance>(
|
object: Rc::new(VertexBuffer::new::<TexturedVertex, ObjectInstance>(
|
||||||
"objecte",
|
"object",
|
||||||
&device,
|
&device,
|
||||||
Some(SPRITE_VERTICES),
|
Some(SPRITE_VERTICES),
|
||||||
Some(SPRITE_INDICES),
|
Some(SPRITE_INDICES),
|
||||||
|
@ -142,6 +149,15 @@ impl GPUState {
|
||||||
Some(SPRITE_INDICES),
|
Some(SPRITE_INDICES),
|
||||||
galactica_constants::UI_SPRITE_INSTANCE_LIMIT,
|
galactica_constants::UI_SPRITE_INSTANCE_LIMIT,
|
||||||
)),
|
)),
|
||||||
|
|
||||||
|
particle_array_head: 0,
|
||||||
|
particle: Rc::new(VertexBuffer::new::<TexturedVertex, ParticleInstance>(
|
||||||
|
"particle",
|
||||||
|
&device,
|
||||||
|
Some(SPRITE_VERTICES),
|
||||||
|
Some(SPRITE_INDICES),
|
||||||
|
galactica_constants::PARTICLE_SPRITE_INSTANCE_LIMIT,
|
||||||
|
)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Load uniforms
|
// Load uniforms
|
||||||
|
@ -191,6 +207,18 @@ impl GPUState {
|
||||||
.set_bind_group_layouts(bind_group_layouts)
|
.set_bind_group_layouts(bind_group_layouts)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
let particle_pipeline = PipelineBuilder::new("particle", &device)
|
||||||
|
.set_shader(include_str!(concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/shaders/",
|
||||||
|
"particle.wgsl"
|
||||||
|
)))
|
||||||
|
.set_format(config.format)
|
||||||
|
.set_triangle(true)
|
||||||
|
.set_vertex_buffer(&vertex_buffers.particle)
|
||||||
|
.set_bind_group_layouts(bind_group_layouts)
|
||||||
|
.build();
|
||||||
|
|
||||||
let mut starfield = Starfield::new();
|
let mut starfield = Starfield::new();
|
||||||
starfield.regenerate();
|
starfield.regenerate();
|
||||||
|
|
||||||
|
@ -207,6 +235,7 @@ impl GPUState {
|
||||||
object_pipeline,
|
object_pipeline,
|
||||||
starfield_pipeline,
|
starfield_pipeline,
|
||||||
ui_pipeline,
|
ui_pipeline,
|
||||||
|
particle_pipeline,
|
||||||
|
|
||||||
starfield,
|
starfield,
|
||||||
texture_array,
|
texture_array,
|
||||||
|
@ -500,8 +529,12 @@ impl GPUState {
|
||||||
&mut self,
|
&mut self,
|
||||||
camera_pos: Point2<f32>,
|
camera_pos: Point2<f32>,
|
||||||
camera_zoom: f32,
|
camera_zoom: f32,
|
||||||
|
|
||||||
|
// TODO: clean this up, pass one struct
|
||||||
object_sprites: &Vec<ObjectSprite>,
|
object_sprites: &Vec<ObjectSprite>,
|
||||||
ui_sprites: &Vec<UiSprite>,
|
ui_sprites: &Vec<UiSprite>,
|
||||||
|
new_particles: &mut Vec<ParticleBuilder>,
|
||||||
|
start_instant: Instant,
|
||||||
) -> Result<(), wgpu::SurfaceError> {
|
) -> Result<(), wgpu::SurfaceError> {
|
||||||
let output = self.surface.get_current_texture()?;
|
let output = self.surface.get_current_texture()?;
|
||||||
let view = output
|
let view = output
|
||||||
|
@ -535,6 +568,9 @@ impl GPUState {
|
||||||
timestamp_writes: None,
|
timestamp_writes: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: handle overflow
|
||||||
|
let time_now = start_instant.elapsed().as_secs_f32();
|
||||||
|
|
||||||
// Update global values
|
// Update global values
|
||||||
self.queue.write_buffer(
|
self.queue.write_buffer(
|
||||||
&self.global_data.buffer,
|
&self.global_data.buffer,
|
||||||
|
@ -554,9 +590,31 @@ impl GPUState {
|
||||||
galactica_constants::STARFIELD_SIZE_MIN,
|
galactica_constants::STARFIELD_SIZE_MIN,
|
||||||
galactica_constants::STARFIELD_SIZE_MAX,
|
galactica_constants::STARFIELD_SIZE_MAX,
|
||||||
],
|
],
|
||||||
|
current_time: [time_now, 0.0],
|
||||||
}]),
|
}]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
for i in new_particles.iter() {
|
||||||
|
let texture = self.texture_array.get_texture(i.texture);
|
||||||
|
self.queue.write_buffer(
|
||||||
|
&self.vertex_buffers.particle.instances,
|
||||||
|
ParticleInstance::SIZE * self.vertex_buffers.particle_array_head,
|
||||||
|
bytemuck::cast_slice(&[ParticleInstance {
|
||||||
|
position: [i.pos.x, i.pos.y, 1.0],
|
||||||
|
size: i.size,
|
||||||
|
texture_index: texture.index,
|
||||||
|
expires: time_now + i.lifetime,
|
||||||
|
}]),
|
||||||
|
);
|
||||||
|
self.vertex_buffers.particle_array_head += 1;
|
||||||
|
if self.vertex_buffers.particle_array_head
|
||||||
|
== galactica_constants::PARTICLE_SPRITE_INSTANCE_LIMIT
|
||||||
|
{
|
||||||
|
self.vertex_buffers.particle_array_head = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new_particles.clear();
|
||||||
|
|
||||||
// Create sprite instances
|
// Create sprite instances
|
||||||
let (n_object, n_ui) =
|
let (n_object, n_ui) =
|
||||||
self.update_sprite_instances(camera_zoom, camera_pos, object_sprites, ui_sprites);
|
self.update_sprite_instances(camera_zoom, camera_pos, object_sprites, ui_sprites);
|
||||||
|
@ -580,6 +638,15 @@ impl GPUState {
|
||||||
render_pass.set_pipeline(&self.object_pipeline);
|
render_pass.set_pipeline(&self.object_pipeline);
|
||||||
render_pass.draw_indexed(0..SPRITE_INDICES.len() as u32, 0, 0..n_object as _);
|
render_pass.draw_indexed(0..SPRITE_INDICES.len() as u32, 0, 0..n_object as _);
|
||||||
|
|
||||||
|
// Particle pipeline
|
||||||
|
self.vertex_buffers.particle.set_in_pass(&mut render_pass);
|
||||||
|
render_pass.set_pipeline(&self.particle_pipeline);
|
||||||
|
render_pass.draw_indexed(
|
||||||
|
0..SPRITE_INDICES.len() as u32,
|
||||||
|
0,
|
||||||
|
0..galactica_constants::PARTICLE_SPRITE_INSTANCE_LIMIT as _,
|
||||||
|
);
|
||||||
|
|
||||||
// Ui pipeline
|
// Ui pipeline
|
||||||
self.vertex_buffers.ui.set_in_pass(&mut render_pass);
|
self.vertex_buffers.ui.set_in_pass(&mut render_pass);
|
||||||
render_pass.set_pipeline(&self.ui_pipeline);
|
render_pass.set_pipeline(&self.ui_pipeline);
|
||||||
|
|
|
@ -17,7 +17,7 @@ mod vertexbuffer;
|
||||||
|
|
||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
pub use gpustate::GPUState;
|
pub use gpustate::GPUState;
|
||||||
pub use sprite::{AnchoredUiPosition, ObjectSprite, ObjectSubSprite, UiSprite};
|
pub use sprite::{AnchoredUiPosition, ObjectSprite, ObjectSubSprite, ParticleBuilder, UiSprite};
|
||||||
|
|
||||||
use cgmath::Matrix4;
|
use cgmath::Matrix4;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,23 @@
|
||||||
use crate::content;
|
use crate::content;
|
||||||
use cgmath::{Deg, Point2, Point3};
|
use cgmath::{Deg, Point2, Point3};
|
||||||
|
|
||||||
|
/// Instructions to create a new particle
|
||||||
|
pub struct ParticleBuilder {
|
||||||
|
/// The texture to use for this particle
|
||||||
|
pub texture: content::TextureHandle,
|
||||||
|
|
||||||
|
// TODO: rotation, velocity
|
||||||
|
/// This object's center, in world coordinates.
|
||||||
|
pub pos: Point3<f32>,
|
||||||
|
|
||||||
|
/// This particle's lifetime, in seconds
|
||||||
|
pub lifetime: f32,
|
||||||
|
|
||||||
|
/// The size of this sprite,
|
||||||
|
/// given as height in world units.
|
||||||
|
pub size: f32,
|
||||||
|
}
|
||||||
|
|
||||||
/// The location of a UI element, in one of a few
|
/// The location of a UI element, in one of a few
|
||||||
/// possible coordinate systems.
|
/// possible coordinate systems.
|
||||||
///
|
///
|
||||||
|
|
|
@ -195,3 +195,60 @@ impl BufferObject for UiInstance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
|
pub struct ParticleInstance {
|
||||||
|
/// World position of this particle
|
||||||
|
pub position: [f32; 3],
|
||||||
|
|
||||||
|
// TODO: docs: particle frames must all have the same size
|
||||||
|
// TODO: is transparency trimmed? That's not ideal for animated particles!
|
||||||
|
/// The height of this particle, in world units
|
||||||
|
pub size: f32,
|
||||||
|
|
||||||
|
// TODO: rotation, velocity vector
|
||||||
|
// TODO: animated sprites
|
||||||
|
// TODO: texture aspect ratio
|
||||||
|
/// The time, in seconds, at which this particle expires.
|
||||||
|
/// Time is kept by a variable in the global uniform.
|
||||||
|
pub expires: f32,
|
||||||
|
|
||||||
|
/// What texture to use for this particle
|
||||||
|
pub texture_index: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BufferObject for ParticleInstance {
|
||||||
|
fn layout() -> wgpu::VertexBufferLayout<'static> {
|
||||||
|
wgpu::VertexBufferLayout {
|
||||||
|
array_stride: Self::SIZE,
|
||||||
|
step_mode: wgpu::VertexStepMode::Instance,
|
||||||
|
attributes: &[
|
||||||
|
// Position
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: 0,
|
||||||
|
shader_location: 2,
|
||||||
|
format: wgpu::VertexFormat::Float32x3,
|
||||||
|
},
|
||||||
|
// Size
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 3,
|
||||||
|
format: wgpu::VertexFormat::Float32,
|
||||||
|
},
|
||||||
|
// Expires
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 4,
|
||||||
|
format: wgpu::VertexFormat::Float32,
|
||||||
|
},
|
||||||
|
// Texture
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 5,
|
||||||
|
format: wgpu::VertexFormat::Uint32,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Point2, Rad, Vector2};
|
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Point2, Point3, Rad, Vector2};
|
||||||
use crossbeam::channel::Receiver;
|
use crossbeam::channel::Receiver;
|
||||||
use nalgebra::vector;
|
use nalgebra::vector;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
@ -12,7 +12,7 @@ use std::{collections::HashMap, f32::consts::PI};
|
||||||
use crate::{objects, objects::ProjectileWorldObject, util, wrapper::Wrapper, ShipPhysicsHandle};
|
use crate::{objects, objects::ProjectileWorldObject, util, wrapper::Wrapper, ShipPhysicsHandle};
|
||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
use galactica_gameobject as object;
|
use galactica_gameobject as object;
|
||||||
use galactica_render::ObjectSprite;
|
use galactica_render::{ObjectSprite, ParticleBuilder};
|
||||||
|
|
||||||
/// Keeps track of all objects in the world that we can interact with.
|
/// Keeps track of all objects in the world that we can interact with.
|
||||||
/// Also wraps our physics engine
|
/// Also wraps our physics engine
|
||||||
|
@ -92,7 +92,7 @@ impl<'a> World {
|
||||||
.linvel(vector![vel.x, vel.y])
|
.linvel(vector![vel.x, vel.y])
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let collider = ColliderBuilder::ball(5.0)
|
let collider = ColliderBuilder::ball(1.0)
|
||||||
.sensor(true)
|
.sensor(true)
|
||||||
.active_events(ActiveEvents::COLLISION_EVENTS)
|
.active_events(ActiveEvents::COLLISION_EVENTS)
|
||||||
.build();
|
.build();
|
||||||
|
@ -169,7 +169,7 @@ impl<'a> World {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Step this physics system by `t` seconds
|
/// Step this physics system by `t` seconds
|
||||||
pub fn step(&mut self, t: f32, ct: &content::Content) {
|
pub fn step(&mut self, t: f32, ct: &content::Content, particles: &mut Vec<ParticleBuilder>) {
|
||||||
// Run ship updates
|
// Run ship updates
|
||||||
// TODO: maybe reorganize projectile creation?
|
// TODO: maybe reorganize projectile creation?
|
||||||
let mut projectiles = Vec::new();
|
let mut projectiles = Vec::new();
|
||||||
|
@ -214,6 +214,18 @@ impl<'a> World {
|
||||||
if let Some(s) = self.ships.get_mut(b) {
|
if let Some(s) = self.ships.get_mut(b) {
|
||||||
let hit = s.ship.handle_projectile_collision(ct, &p.projectile);
|
let hit = s.ship.handle_projectile_collision(ct, &p.projectile);
|
||||||
if hit {
|
if hit {
|
||||||
|
let r = self.get_rigid_body(p.rigid_body);
|
||||||
|
let pos = util::rigidbody_position(r);
|
||||||
|
particles.push(ParticleBuilder {
|
||||||
|
texture: ct.get_texture_handle("particle::blaster"),
|
||||||
|
pos: Point3 {
|
||||||
|
x: pos.x,
|
||||||
|
y: pos.y,
|
||||||
|
z: 1.0, // TODO:remove z coordinate
|
||||||
|
},
|
||||||
|
lifetime: 0.1,
|
||||||
|
size: 10.0,
|
||||||
|
});
|
||||||
self.remove_projectile(*a);
|
self.remove_projectile(*a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
32
src/game.rs
32
src/game.rs
|
@ -1,4 +1,4 @@
|
||||||
use cgmath::Point2;
|
use cgmath::{Point2, Point3};
|
||||||
use content::SystemHandle;
|
use content::SystemHandle;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
|
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
|
||||||
|
@ -7,7 +7,7 @@ use crate::camera::Camera;
|
||||||
use crate::{content, inputstatus::InputStatus};
|
use crate::{content, inputstatus::InputStatus};
|
||||||
use galactica_constants;
|
use galactica_constants;
|
||||||
use galactica_gameobject as object;
|
use galactica_gameobject as object;
|
||||||
use galactica_render::{ObjectSprite, UiSprite};
|
use galactica_render::{ObjectSprite, ParticleBuilder, UiSprite};
|
||||||
use galactica_shipbehavior::{behavior, ShipBehavior};
|
use galactica_shipbehavior::{behavior, ShipBehavior};
|
||||||
use galactica_ui as ui;
|
use galactica_ui as ui;
|
||||||
use galactica_world::{util, ShipPhysicsHandle, World};
|
use galactica_world::{util, ShipPhysicsHandle, World};
|
||||||
|
@ -21,10 +21,14 @@ pub struct Game {
|
||||||
paused: bool,
|
paused: bool,
|
||||||
pub time_scale: f32,
|
pub time_scale: f32,
|
||||||
|
|
||||||
physics: World,
|
world: World,
|
||||||
shipbehaviors: Vec<Box<dyn ShipBehavior>>,
|
shipbehaviors: Vec<Box<dyn ShipBehavior>>,
|
||||||
playerbehavior: behavior::Player,
|
playerbehavior: behavior::Player,
|
||||||
content: content::Content,
|
content: content::Content,
|
||||||
|
pub start_instant: Instant,
|
||||||
|
|
||||||
|
// TODO: clean this up
|
||||||
|
pub new_particles: Vec<ParticleBuilder>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Game {
|
impl Game {
|
||||||
|
@ -85,6 +89,7 @@ impl Game {
|
||||||
last_update: Instant::now(),
|
last_update: Instant::now(),
|
||||||
input: InputStatus::new(),
|
input: InputStatus::new(),
|
||||||
player: h1,
|
player: h1,
|
||||||
|
start_instant: Instant::now(),
|
||||||
|
|
||||||
camera: Camera {
|
camera: Camera {
|
||||||
pos: (0.0, 0.0).into(),
|
pos: (0.0, 0.0).into(),
|
||||||
|
@ -95,10 +100,11 @@ impl Game {
|
||||||
|
|
||||||
paused: false,
|
paused: false,
|
||||||
time_scale: 1.0,
|
time_scale: 1.0,
|
||||||
physics,
|
world: physics,
|
||||||
shipbehaviors,
|
shipbehaviors,
|
||||||
content: ct,
|
content: ct,
|
||||||
playerbehavior: behavior::Player::new(h1),
|
playerbehavior: behavior::Player::new(h1),
|
||||||
|
new_particles: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,19 +137,19 @@ impl Game {
|
||||||
self.playerbehavior.key_right = self.input.key_right;
|
self.playerbehavior.key_right = self.input.key_right;
|
||||||
self.playerbehavior.key_left = self.input.key_left;
|
self.playerbehavior.key_left = self.input.key_left;
|
||||||
self.playerbehavior
|
self.playerbehavior
|
||||||
.update_controls(&mut self.physics, &self.content);
|
.update_controls(&mut self.world, &self.content);
|
||||||
|
|
||||||
self.shipbehaviors.retain_mut(|b| {
|
self.shipbehaviors.retain_mut(|b| {
|
||||||
// Remove shipbehaviors of destroyed ships
|
// Remove shipbehaviors of destroyed ships
|
||||||
if self.physics.get_ship_mut(&b.get_handle()).is_none() {
|
if self.world.get_ship_mut(&b.get_handle()).is_none() {
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
b.update_controls(&mut self.physics, &self.content);
|
b.update_controls(&mut self.world, &self.content);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
self.physics.step(t, &self.content);
|
self.world.step(t, &self.content, &mut self.new_particles);
|
||||||
|
|
||||||
if self.input.v_scroll != 0.0 {
|
if self.input.v_scroll != 0.0 {
|
||||||
self.camera.zoom = (self.camera.zoom + self.input.v_scroll)
|
self.camera.zoom = (self.camera.zoom + self.input.v_scroll)
|
||||||
|
@ -153,11 +159,11 @@ impl Game {
|
||||||
|
|
||||||
// TODO: Camera physics
|
// TODO: Camera physics
|
||||||
let r = self
|
let r = self
|
||||||
.physics
|
.world
|
||||||
.get_ship_mut(&self.player)
|
.get_ship_mut(&self.player)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.physics_handle;
|
.physics_handle;
|
||||||
let r = self.physics.get_rigid_body(r.0); // TODO: r.0 shouldn't be public
|
let r = self.world.get_rigid_body(r.0); // TODO: r.0 shouldn't be public
|
||||||
let ship_pos = util::rigidbody_position(r);
|
let ship_pos = util::rigidbody_position(r);
|
||||||
self.camera.pos = ship_pos;
|
self.camera.pos = ship_pos;
|
||||||
self.last_update = Instant::now();
|
self.last_update = Instant::now();
|
||||||
|
@ -167,7 +173,7 @@ impl Game {
|
||||||
let mut sprites: Vec<ObjectSprite> = Vec::new();
|
let mut sprites: Vec<ObjectSprite> = Vec::new();
|
||||||
|
|
||||||
sprites.append(&mut self.system.get_sprites());
|
sprites.append(&mut self.system.get_sprites());
|
||||||
sprites.extend(self.physics.get_ship_sprites(&self.content));
|
sprites.extend(self.world.get_ship_sprites(&self.content));
|
||||||
|
|
||||||
// Make sure sprites are drawn in the correct order
|
// Make sure sprites are drawn in the correct order
|
||||||
// (note the reversed a, b in the comparator)
|
// (note the reversed a, b in the comparator)
|
||||||
|
@ -177,7 +183,7 @@ impl Game {
|
||||||
sprites.sort_by(|a, b| b.pos.z.total_cmp(&a.pos.z));
|
sprites.sort_by(|a, b| b.pos.z.total_cmp(&a.pos.z));
|
||||||
|
|
||||||
// Don't waste time sorting these, they should always be on top.
|
// Don't waste time sorting these, they should always be on top.
|
||||||
sprites.extend(self.physics.get_projectile_sprites());
|
sprites.extend(self.world.get_projectile_sprites());
|
||||||
|
|
||||||
return sprites;
|
return sprites;
|
||||||
}
|
}
|
||||||
|
@ -186,7 +192,7 @@ impl Game {
|
||||||
return ui::build_radar(
|
return ui::build_radar(
|
||||||
&self.content,
|
&self.content,
|
||||||
&self.player,
|
&self.player,
|
||||||
&self.physics,
|
&self.world,
|
||||||
&self.system,
|
&self.system,
|
||||||
self.camera.zoom,
|
self.camera.zoom,
|
||||||
self.camera.aspect,
|
self.camera.aspect,
|
||||||
|
|
|
@ -37,6 +37,11 @@ fn main() -> Result<()> {
|
||||||
game.camera.zoom,
|
game.camera.zoom,
|
||||||
&game.get_object_sprites(),
|
&game.get_object_sprites(),
|
||||||
&game.get_ui_sprites(),
|
&game.get_ui_sprites(),
|
||||||
|
// TODO: clean this up, single game data struct
|
||||||
|
// Game in another crate?
|
||||||
|
// Shipbehavior needs game state too...
|
||||||
|
&mut game.new_particles,
|
||||||
|
game.start_instant,
|
||||||
) {
|
) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(wgpu::SurfaceError::Lost) => gpu.resize(),
|
Err(wgpu::SurfaceError::Lost) => gpu.resize(),
|
||||||
|
|
Loading…
Reference in New Issue