Moved positioning logic to shaders

master
Mark 2024-01-06 14:02:50 -08:00
parent b28cb03a52
commit a65372f866
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
14 changed files with 286 additions and 200 deletions

View File

@ -1,43 +1,41 @@
// Pick frame of animation from an instance.
//
// This function assumes that the uniform header has been loaded,
// and that `InstanceInput` contains a field `texture_index`
fn animate(instance: InstanceInput, age: f32) -> u32 {
let idx = instance.texture_index;
let len = sprites[idx].frame_count;
let rep = sprites[idx].repeatmode;
let fps = sprites[idx].fps;
var frame: u32 = u32(0);
// Pick a frame of a sprite animation from an instance.
fn animate(sprite_index: u32, age: f32, offset: f32) -> f32 {
let len = global_sprites[sprite_index].frame_count;
let rep = global_sprites[sprite_index].repeatmode;
let fps = global_sprites[sprite_index].fps;
var frame: f32 = 0.0;
// Once
if rep == u32(1) {
frame = u32(min(
age / fps,
frame = min(
age / fps + offset,
f32(len) - 1.0
));
);
// Reverse
} else if rep == u32(2) {
let x = age / fps;
let x = age / fps + offset;
let m = f32(len) * 2.0 - 1.0;
// x fmod m
frame = u32(x - floor(x / m) * m);
frame = x - floor(x / m) * m;
if frame >= len {
frame = len + len - frame - u32(1);
if frame >= f32(len) {
frame = (
f32(len) + f32(len) - 1.0
// Split integer and fractional part so tweening works properly
- floor(frame)
+ fract(frame)
);
}
// Repeat (default)
} else {
let x = age / fps;
let x = age / fps + offset;
let m = f32(len);
// x fmod m
frame = u32(x - floor(x / m) * m);
frame = x - floor(x / m) * m;
}
return frame + sprites[idx].first_frame;
return frame + f32(global_sprites[sprite_index].first_frame);
}

View File

@ -1,7 +1,7 @@
// INCLUDE: global uniform header
struct InstanceInput {
@location(2) texture_index: u32,
@location(2) sprite_index: u32,
@location(3) object_index: u32,
};
@ -12,8 +12,11 @@ struct VertexInput {
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) texture_coords: vec2<f32>,
@location(1) texture_index: u32,
@location(0) tween: f32,
@location(1) texture_index_a: u32,
@location(2) texture_coords_a: vec2<f32>,
@location(3) texture_index_b: u32,
@location(4) texture_coords_b: vec2<f32>,
};
@ -25,15 +28,15 @@ var sampler_array: binding_array<sampler>;
// INCLUDE: animate.wgsl
fn transform_vertex(obj: ObjectLocation, vertex: VertexInput, instance: InstanceInput) -> vec4<f32> {
fn transform_vertex(obj: ObjectData, vertex_position: vec2<f32>, sprite_index: u32) -> vec4<f32> {
// Object scale
let scale = obj.size / (global.camera_zoom.x * obj.zpos);
let scale = obj.size / (global_data.camera_zoom.x * obj.zpos);
// Apply scale and sprite aspect
// Note that our mesh starts centered at (0, 0). This is important!
var pos: vec2<f32> = vec2(
vertex.position.x * scale * sprites[instance.texture_index].aspect,
vertex.position.y * scale
vertex_position.x * scale * global_sprites[sprite_index].aspect,
vertex_position.y * scale
);
// Apply rotation
@ -42,25 +45,59 @@ fn transform_vertex(obj: ObjectLocation, vertex: VertexInput, instance: Instance
vec2(-sin(obj.angle), cos(obj.angle))
) * pos;
// Correct for screen aspect, preserving height
// This must be done AFTER rotation.
// (It's thus done later if this is a child)
if obj.is_child == u32(0) {
pos = vec2(
pos.x / global.window_aspect.x,
pos.x / global_data.window_aspect.x,
pos.y
);
}
// Distance-adjusted world position
let trans = (vec2(obj.xpos, obj.ypos) - global.camera_position) / obj.zpos;
// Finally, translate
// Translate
//
// Note that we divide camera zoom by two.
// The height of the viewport is `zoom` in game units,
// but it's 2 in screen units (since coordinates range from -1 to 1)
if obj.is_child == u32(0) {
let trans = (vec2(obj.xpos, obj.ypos) - global_data.camera_position) / obj.zpos;
pos = pos + vec2(
trans.x / (global.camera_zoom.x / 2.0) / global.window_aspect.x,
trans.y / (global.camera_zoom.x / 2.0)
trans.x / (global_data.camera_zoom.x / 2.0) / global_data.window_aspect.x,
trans.y / (global_data.camera_zoom.x / 2.0)
);
}
if obj.is_child == u32(1) {
// Apply translation relative to parent
// Note that obj.zpos is ignored
pos = pos + vec2(
obj.xpos / (global_data.camera_zoom.x / 2.0) / global_data.window_aspect.x,
obj.ypos / (global_data.camera_zoom.x / 2.0)
);
let parent = objects[obj.parent];
// Apply parent's rotation
pos = mat2x2(
vec2(cos(parent.angle), sin(parent.angle)),
vec2(-sin(parent.angle), cos(parent.angle))
) * pos;
// Correct for screen aspect, preserving height
pos = vec2(
pos.x / global_data.window_aspect.x,
pos.y
);
// Apply parent's translation
let ptrans = (vec2(parent.xpos, parent.ypos) - global_data.camera_position) / parent.zpos;
pos = pos + vec2(
ptrans.x / (global_data.camera_zoom.x / 2.0) / global_data.window_aspect.x,
ptrans.y / (global_data.camera_zoom.x / 2.0)
);
}
return vec4<f32>(pos, 0.0, 1.0);;
}
@ -71,16 +108,36 @@ fn vertex_main(
instance: InstanceInput,
) -> VertexOutput {
var out: VertexOutput;
out.position = transform_vertex(objects[instance.object_index], vertex, instance);
let t = atlas[animate(instance, global.current_time.x)];
out.texture_index = t.atlas_texture;
out.texture_coords = vec2(t.xpos, t.ypos);
out.position = transform_vertex(objects[instance.object_index], vertex.position.xy, instance.sprite_index);
// Compute texture coordinates
let age = global_data.current_time.x;
let frame = animate(instance.sprite_index, age, 0.0);
out.tween = fract(frame);
let t = global_atlas[u32(floor(frame))];
out.texture_index_a = u32(t.atlas_texture);
out.texture_coords_a = vec2(t.xpos, t.ypos);
if vertex.texture_coords.x == 1.0 {
out.texture_coords = vec2(out.texture_coords.x + t.width, out.texture_coords.y);
out.texture_coords_a = out.texture_coords_a + vec2(t.width, 0.0);
}
if vertex.texture_coords.y == 1.0 {
out.texture_coords = vec2(out.texture_coords.x, out.texture_coords.y + t.height);
out.texture_coords_a = out.texture_coords_a + vec2(0.0, t.height);
}
let b = global_atlas[u32(floor(animate(instance.sprite_index, age, 1.0)))];
out.texture_index_b = u32(b.atlas_texture);
out.texture_coords_b = vec2(b.xpos, b.ypos);
if vertex.texture_coords.x == 1.0 {
out.texture_coords_b = out.texture_coords_b + vec2(b.width, 0.0);
}
if vertex.texture_coords.y == 1.0 {
out.texture_coords_b = out.texture_coords_b + vec2(0.0, b.height);
}
return out;
@ -89,10 +146,19 @@ fn vertex_main(
@fragment
fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> {
return textureSampleLevel(
texture_array[in.texture_index],
return mix(
textureSampleLevel(
texture_array[in.texture_index_a],
sampler_array[0],
in.texture_coords,
in.texture_coords_a,
0.0
).rgba;
).rgba,
textureSampleLevel(
texture_array[in.texture_index_b],
sampler_array[0],
in.texture_coords_b,
0.0
).rgba,
in.tween
);
}

View File

@ -3,12 +3,12 @@
struct InstanceInput {
@location(2) position: vec2<f32>,
@location(3) velocity: vec2<f32>,
@location(4) rotation_0: vec2<f32>,
@location(5) rotation_1: vec2<f32>,
@location(4) angle: f32,
@location(5) angvel: f32,
@location(6) size: f32,
@location(7) created: f32,
@location(8) expires: f32,
@location(9) texture_index: u32,
@location(9) sprite_index: u32,
};
struct VertexInput {
@ -18,8 +18,11 @@ struct VertexInput {
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) texture_coords: vec2<f32>,
@location(1) texture_index: u32,
@location(0) tween: f32,
@location(1) texture_index_a: u32,
@location(2) texture_coords_a: vec2<f32>,
@location(3) texture_index_b: u32,
@location(4) texture_coords_b: vec2<f32>,
}
@ -38,24 +41,31 @@ fn vertex_main(
) -> VertexOutput {
var out: VertexOutput;
out.texture_coords = vertex.texture_coords;
// Skip expired particles
if instance.expires < global.current_time.x {
out.texture_index = u32(0);
// Draw off screen
if instance.expires < global_data.current_time.x {
out.tween = 0.0;
out.texture_index_a = u32(0);
out.texture_index_b = u32(0);
out.texture_coords_a = vec2(0.0, 0.0);
out.texture_coords_b = vec2(0.0, 0.0);
out.position = vec4<f32>(2.0, 2.0, 0.0, 1.0);
return out;
}
let age = global.current_time.x - instance.created;
let age = global_data.current_time.x - instance.created;
// Apply transformations
let rotation = mat2x2(instance.rotation_0, instance.rotation_1);
var scale: f32 = instance.size / global.camera_zoom.x;
let angle = instance.angle + age * instance.angvel;
let rotation = mat2x2(
vec2(cos(angle), sin(angle)),
vec2(-sin(angle), cos(angle))
);
var scale: f32 = instance.size / global_data.camera_zoom.x;
var pos: vec2<f32> = vec2(vertex.position.x, vertex.position.y);
pos = pos * vec2<f32>(
sprites[instance.texture_index].aspect * scale / global.window_aspect.x,
global_sprites[instance.sprite_index].aspect * scale / global_data.window_aspect.x,
scale
);
@ -64,38 +74,62 @@ fn vertex_main(
var ipos: vec2<f32> = (
vec2(instance.position.x, instance.position.y)
+ (instance.velocity * age)
- global.camera_position
- global_data.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)
ipos.x / (global_data.camera_zoom.x/2.0) / global_data.window_aspect.x,
ipos.y / (global_data.camera_zoom.x/2.0)
);
out.position = vec4<f32>(pos, 0.0, 1.0);
// Compute texture coordinates
let t = atlas[animate(instance, age)];
out.texture_index = u32(t.atlas_texture);
out.texture_coords = vec2(t.xpos, t.ypos);
let frame = animate(instance.sprite_index, age, 0.0);
out.tween = fract(frame);
let t = global_atlas[u32(floor(frame))];
out.texture_index_a = u32(t.atlas_texture);
out.texture_coords_a = vec2(t.xpos, t.ypos);
if vertex.texture_coords.x == 1.0 {
out.texture_coords = vec2(out.texture_coords.x + t.width, out.texture_coords.y);
out.texture_coords_a = out.texture_coords_a + vec2(t.width, 0.0);
}
if vertex.texture_coords.y == 1.0 {
out.texture_coords = vec2(out.texture_coords.x, out.texture_coords.y + t.height);
out.texture_coords_a = out.texture_coords_a + vec2(0.0, t.height);
}
let b = global_atlas[u32(floor(animate(instance.sprite_index, age, 1.0)))];
out.texture_index_b = u32(b.atlas_texture);
out.texture_coords_b = vec2(b.xpos, b.ypos);
if vertex.texture_coords.x == 1.0 {
out.texture_coords_b = out.texture_coords_b + vec2(b.width, 0.0);
}
if vertex.texture_coords.y == 1.0 {
out.texture_coords_b = out.texture_coords_b + vec2(0.0, b.height);
}
return out;
}
@fragment
fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> {
return textureSampleLevel(
texture_array[in.texture_index],
return mix(
textureSampleLevel(
texture_array[in.texture_index_a],
sampler_array[0],
in.texture_coords,
in.texture_coords_a,
0.0
).rgba;
).rgba,
textureSampleLevel(
texture_array[in.texture_index_b],
sampler_array[0],
in.texture_coords_b,
0.0
).rgba,
in.tween
);
}

View File

@ -41,18 +41,18 @@ fn vertex_main(
// Center of the tile the camera is currently in, in game coordinates.
// x div y = x - (x mod y)
let tile_center = (
global.camera_position.xy
global_data.camera_position.xy
- (
fmod(
global.camera_position.xy + global.starfield_tile_size.x / 2.0,
global.starfield_tile_size.x
) - global.starfield_tile_size.x / 2.0
global_data.camera_position.xy + global_data.starfield_tile_size.x / 2.0,
global_data.starfield_tile_size.x
) - global_data.starfield_tile_size.x / 2.0
)
);
let zoom_min_times = (
global.camera_zoom.x / global.camera_zoom_limits.x
global_data.camera_zoom.x / global_data.camera_zoom_limits.x
);
// Hide n% of the smallest stars
@ -64,8 +64,8 @@ fn vertex_main(
if (
instance.size < (
hide_fraction * (global.starfield_size_limits.y - global.starfield_size_limits.x)
+ (global.starfield_size_limits.x)
hide_fraction * (global_data.starfield_size_limits.y - global_data.starfield_size_limits.x)
+ (global_data.starfield_size_limits.x)
)
) {
out.position = vec4<f32>(2.0, 2.0, 0.0, 1.0);
@ -76,41 +76,41 @@ fn vertex_main(
// Apply sprite aspect ratio & scale factor
// also applies screen aspect ratio
// Note that we do NOT scale for distance here---this is intentional.
var scale: f32 = instance.size / global.camera_zoom.x;
var scale: f32 = instance.size / global_data.camera_zoom.x;
// Minimum scale to prevent flicker at large zoom levels
var real_size = scale * global.window_size.xy;
var real_size = scale * global_data.window_size.xy;
if (real_size.x < 2.0 || real_size.y < 2.0) {
// Otherwise, clamp to a minimum scale
scale = 2.0 / max(global.window_size.x, global.window_size.y);
scale = 2.0 / max(global_data.window_size.x, global_data.window_size.y);
}
// Divide by two, because viewport height is 2 in screen units
// (coordinates go from -1 to 1)
var pos: vec2<f32> = vec2<f32>(
vertex.position.x * (scale/2.0) / global.window_aspect.x,
vertex.position.x * (scale/2.0) / global_data.window_aspect.x,
vertex.position.y * (scale/2.0)
);
// World position relative to camera
// (Note that instance position is in a different
// coordinate system than usual)
let camera_pos = (instance.position.xy + tile_center) - global.camera_position.xy;
let camera_pos = (instance.position.xy + tile_center) - global_data.camera_position.xy;
// Translate
pos = pos + (
// Don't forget to correct distance for screen aspect ratio too!
(camera_pos / (global.camera_zoom.x * instance.position.z))
/ vec2<f32>(global.window_aspect.x, 1.0)
(camera_pos / (global_data.camera_zoom.x * instance.position.z))
/ vec2<f32>(global_data.window_aspect.x, 1.0)
);
out.position = vec4<f32>(pos, 0.0, 1.0) * instance.position.z;
// Starfield sprites may not be animated
let i = sprites[global.starfield_sprite.x].first_frame;
let t = atlas[i];
let i = global_sprites[global_data.starfield_sprite.x].first_frame;
let t = global_atlas[i];
out.texture_index = u32(t.atlas_texture);
out.texture_coords = vec2(t.xpos, t.ypos);
if vertex.texture_coords.x == 1.0 {

View File

@ -6,7 +6,7 @@ struct InstanceInput {
@location(4) transform_matrix_2: vec4<f32>,
@location(5) transform_matrix_3: vec4<f32>,
@location(6) color_transform: vec4<f32>,
@location(7) texture_index: u32,
@location(7) sprite_index: u32,
};
struct VertexInput {
@ -48,7 +48,7 @@ fn vertex_main(
out.color_transform = instance.color_transform;
// Pick texture frame
let t = atlas[animate(instance, global.current_time.x)];
let t = global_atlas[u32(animate(instance.sprite_index, global_data.current_time.x, 0.0))];
out.texture_index = u32(t.atlas_texture);
out.texture_coords = vec2(t.xpos, t.ypos);
if vertex.texture_coords.x == 1.0 {

View File

@ -5,7 +5,7 @@ use wgpu;
#[repr(C)]
#[derive(Debug, Copy, Clone, Pod, Zeroable, Default)]
pub struct ImageLocation {
pub struct AtlasImageLocation {
// Image box, in texture coordinates
pub xpos: f32,
pub ypos: f32,
@ -20,14 +20,14 @@ pub struct ImageLocation {
#[derive(Debug, Copy, Clone)]
pub struct AtlasArray {
pub data: [ImageLocation; IMAGE_LIMIT as usize],
pub data: [AtlasImageLocation; IMAGE_LIMIT as usize],
}
unsafe impl Pod for AtlasArray {}
unsafe impl Zeroable for AtlasArray {
fn zeroed() -> Self {
Self {
data: [ImageLocation::zeroed(); IMAGE_LIMIT as usize],
data: [AtlasImageLocation::zeroed(); IMAGE_LIMIT as usize],
}
}
}

View File

@ -9,7 +9,7 @@ use wgpu;
// all smaller values must be padded.
// also, [f32; 3] are aligned as [f32; 4]
// (since alignments must be powers of two)
pub struct DataContent {
pub struct GlobalDataContent {
/// Camera position, in game units
pub camera_position: [f32; 2],
@ -43,6 +43,6 @@ pub struct DataContent {
pub current_time: [f32; 2],
}
impl DataContent {
impl GlobalDataContent {
pub const SIZE: u64 = mem::size_of::<Self>() as wgpu::BufferAddress;
}

View File

@ -1,7 +1,7 @@
use galactica_constants::{IMAGE_LIMIT, OBJECT_SPRITE_INSTANCE_LIMIT, SPRITE_LIMIT};
use wgpu;
use super::{object::ObjectLocationArray, AtlasArray, DataContent, SpriteDataArray};
use super::{object::ObjectLocationArray, AtlasArray, GlobalDataContent, SpriteDataArray};
pub struct GlobalUniform {
pub data_buffer: wgpu::Buffer,
@ -10,7 +10,7 @@ pub struct GlobalUniform {
pub object_buffer: wgpu::Buffer,
pub bind_group: wgpu::BindGroup,
pub bind_group_layout: wgpu::BindGroupLayout,
pub content: DataContent,
pub content: GlobalDataContent,
}
impl GlobalUniform {
@ -21,8 +21,8 @@ impl GlobalUniform {
out.push_str(&format!("@group({group}) @binding(0)\n"));
out.push_str(
r#"
var<uniform> global: GlobalUniform;
struct GlobalUniform {
var<uniform> global_data: GlobalData;
struct GlobalData {
camera_position: vec2<f32>,
camera_zoom: vec2<f32>,
camera_zoom_limits: vec2<f32>,
@ -41,13 +41,13 @@ impl GlobalUniform {
out.push_str(&format!(
r#"
@group({group}) @binding(1)
var<uniform> atlas: array<ImageLocation, {IMAGE_LIMIT}>;
var<uniform> global_atlas: array<AtlasImageLocation, {IMAGE_LIMIT}>;
"#
));
out.push_str("\n");
out.push_str(
r#"
struct ImageLocation {
struct AtlasImageLocation {
xpos: f32,
ypos: f32,
width: f32,
@ -73,7 +73,7 @@ impl GlobalUniform {
out.push_str(&format!(
r#"
@group({group}) @binding(2)
var<uniform> sprites: array<SpriteData, {SPRITE_LIMIT}>;
var<uniform> global_sprites: array<SpriteData, {SPRITE_LIMIT}>;
"#
));
out.push_str("\n");
@ -98,22 +98,22 @@ impl GlobalUniform {
out.push_str(&format!(
r#"
@group({group}) @binding(3)
var<uniform> objects: array<ObjectLocation, {OBJECT_SPRITE_INSTANCE_LIMIT}>;
var<uniform> objects: array<ObjectData, {OBJECT_SPRITE_INSTANCE_LIMIT}>;
"#
));
out.push_str("\n");
out.push_str(
r#"
struct ObjectLocation {
struct ObjectData {
xpos: f32,
ypos: f32,
zpos: f32,
angle: f32,
size: f32,
parent: u32,
is_child: u32,
padding_a: f32,
padding_b: f32,
padding_c: f32,
};
"#,
);
@ -125,7 +125,7 @@ impl GlobalUniform {
pub fn new(device: &wgpu::Device) -> Self {
let data_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("global uniform data buffer"),
size: DataContent::SIZE,
size: GlobalDataContent::SIZE,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
@ -227,7 +227,7 @@ impl GlobalUniform {
object_buffer,
bind_group,
bind_group_layout,
content: DataContent::default(),
content: GlobalDataContent::default(),
};
}
}

View File

@ -4,8 +4,8 @@ mod globaluniform;
mod object;
mod sprite;
pub use atlas::{AtlasArray, ImageLocation};
pub use data::DataContent;
pub use atlas::{AtlasArray, AtlasImageLocation};
pub use data::GlobalDataContent;
pub use globaluniform::GlobalUniform;
pub use object::ObjectLocation;
pub use object::ObjectData;
pub use sprite::{SpriteData, SpriteDataArray};

View File

@ -5,30 +5,36 @@ use wgpu;
#[repr(C)]
#[derive(Debug, Copy, Clone, Pod, Zeroable, Default)]
pub struct ObjectLocation {
pub struct ObjectData {
pub xpos: f32,
pub ypos: f32,
pub zpos: f32,
pub angle: f32,
pub size: f32,
pub _padding: [f32; 3],
/// Index of parent object
pub parent: u32,
/// 1 if has parent, 0 if not
pub is_child: u32,
pub _padding: [f32; 1],
}
impl ObjectLocation {
impl ObjectData {
pub const SIZE: u64 = mem::size_of::<Self>() as wgpu::BufferAddress;
}
#[derive(Debug, Copy, Clone)]
pub struct ObjectLocationArray {
pub data: [ObjectLocation; OBJECT_SPRITE_INSTANCE_LIMIT as usize],
pub data: [ObjectData; OBJECT_SPRITE_INSTANCE_LIMIT as usize],
}
unsafe impl Pod for ObjectLocationArray {}
unsafe impl Zeroable for ObjectLocationArray {
fn zeroed() -> Self {
Self {
data: [ObjectLocation::zeroed(); OBJECT_SPRITE_INSTANCE_LIMIT as usize],
data: [ObjectData::zeroed(); OBJECT_SPRITE_INSTANCE_LIMIT as usize],
}
}
}

View File

@ -1,6 +1,6 @@
use anyhow::Result;
use bytemuck;
use cgmath::{EuclideanSpace, Matrix2, Matrix4, Point2, Rad, Vector3};
use cgmath::{EuclideanSpace, Matrix4, Point2, Rad, Vector3};
use galactica_constants;
use rand::seq::SliceRandom;
use std::{iter, rc::Rc};
@ -9,9 +9,8 @@ use winit::{self, dpi::LogicalSize, window::Window};
use crate::{
content,
globaluniform::{DataContent, GlobalUniform, ObjectLocation},
globaluniform::{GlobalDataContent, GlobalUniform, ObjectData},
pipeline::PipelineBuilder,
sprite::ObjectSubSprite,
starfield::Starfield,
texturearray::TextureArray,
vertexbuffer::{
@ -302,7 +301,6 @@ impl GPUState {
}
/// Create a ObjectInstance for an object and add it to `instances`.
/// Also handles child sprites.
fn push_object_sprite(
&self,
state: &RenderState,
@ -328,8 +326,7 @@ impl GPUState {
// We take the maximum to account for rotated sprites.
let m = (s.size / s.pos.z) * s.sprite.aspect.max(1.0);
// Don't draw (or compute matrices for)
// sprites that are off the screen
// Don't draw sprites that are off the screen
if pos.x < clip_ne.x - m
|| pos.y > clip_ne.y + m
|| pos.x > clip_sw.x + m
@ -338,16 +335,19 @@ impl GPUState {
return;
}
let idx = instances.len();
// Write this object's location data
self.queue.write_buffer(
&self.global_uniform.object_buffer,
ObjectLocation::SIZE * instances.len() as u64,
bytemuck::cast_slice(&[ObjectLocation {
ObjectData::SIZE * idx as u64,
bytemuck::cast_slice(&[ObjectData {
xpos: s.pos.x,
ypos: s.pos.y,
zpos: s.pos.z,
angle: Rad::from(s.angle).0,
size: s.size,
parent: 0,
is_child: 0,
_padding: Default::default(),
}]),
);
@ -355,62 +355,36 @@ impl GPUState {
// Push this object's instance
instances.push(ObjectInstance {
sprite_index: s.sprite.get_index(),
object_index: instances.len() as u32,
object_index: idx as u32,
});
}
/*
// Add children
if let Some(children) = &s.children {
for c in children {
self.push_object_subsprite(&state, instances, c, pos, s.angle);
}
}
}
/// Add an object sprite's subsprite to `instances`.
/// Only called by `self.push_object_sprite`.
fn push_object_subsprite(
&self,
state: &RenderState,
instances: &mut Vec<ObjectInstance>,
s: &ObjectSubSprite,
parent_pos: Point2<f32>,
parent_angle: Deg<f32>,
) {
let scale = s.size / (s.pos.z * state.camera_zoom);
let sprite_aspect_and_scale =
Matrix4::from_nonuniform_scale(s.sprite.aspect * scale, scale, 1.0);
let rotate = Matrix4::from_angle_z(s.angle);
let screen_aspect = Matrix4::from_nonuniform_scale(1.0 / self.window_aspect, 1.0, 1.0);
let ptranslate = Matrix4::from_translation(Vector3 {
x: parent_pos.x / (state.camera_zoom / 2.0) / self.window_aspect,
y: parent_pos.y / (state.camera_zoom / 2.0),
z: 0.0,
});
let protate = Matrix4::from_angle_z(parent_angle);
let translate = Matrix4::from_translation(Vector3 {
x: s.pos.x / (state.camera_zoom / 2.0) / self.window_aspect,
y: s.pos.y / (state.camera_zoom / 2.0),
z: 0.0,
});
// Order matters!
// The rightmost matrix is applied first.
let t = OPENGL_TO_WGPU_MATRIX
* ptranslate * screen_aspect
* protate * translate
* rotate * sprite_aspect_and_scale;
self.queue.write_buffer(
&self.global_uniform.object_buffer,
ObjectData::SIZE * instances.len() as u64,
bytemuck::cast_slice(&[ObjectData {
xpos: c.pos.x,
ypos: c.pos.y,
zpos: c.pos.z,
angle: Rad::from(c.angle).0,
size: c.size,
parent: idx as u32,
is_child: 1,
_padding: Default::default(),
}]),
);
instances.push(ObjectInstance {
transform: t.into(),
sprite_index: s.sprite.get_index(),
sprite_index: c.sprite.get_index(),
object_index: instances.len() as u32,
});
}
*/
}
}
/// Create a ObjectInstance for a ui sprite and add it to `instances`
/// Create a UiInstance for a ui sprite and add it to `instances`
fn push_ui_sprite(&self, instances: &mut Vec<UiInstance>, s: &UiSprite) {
let logical_size: LogicalSize<f32> =
self.window_size.to_logical(self.window.scale_factor());
@ -578,7 +552,7 @@ impl GPUState {
self.queue.write_buffer(
&self.global_uniform.data_buffer,
0,
bytemuck::cast_slice(&[DataContent {
bytemuck::cast_slice(&[GlobalDataContent {
camera_position: state.camera_pos.into(),
camera_zoom: [state.camera_zoom, 0.0],
camera_zoom_limits: [galactica_constants::ZOOM_MIN, galactica_constants::ZOOM_MAX],
@ -606,7 +580,8 @@ impl GPUState {
bytemuck::cast_slice(&[ParticleInstance {
position: [i.pos.x, i.pos.y],
velocity: i.velocity.into(),
rotation: Matrix2::from_angle(i.angle).into(),
angle: i.angle.0,
angvel: i.angvel.0,
size: i.size,
sprite_index: i.sprite.get_index(),
created: state.current_time,

View File

@ -1,5 +1,5 @@
use crate::content;
use cgmath::{Deg, Point2, Point3, Vector2};
use cgmath::{Deg, Point2, Point3, Rad, Vector2};
/// Instructions to create a new particle
pub struct ParticleBuilder {
@ -12,8 +12,11 @@ pub struct ParticleBuilder {
/// This particle's velocity, in world coordinates
pub velocity: Vector2<f32>,
/// This particle's angle, in world coordinates
pub angle: Deg<f32>,
/// This particle's angle
pub angle: Rad<f32>,
/// This particle's angular velocity (rad/sec)
pub angvel: Rad<f32>,
/// This particle's lifetime, in seconds
pub lifetime: f32,

View File

@ -1,6 +1,6 @@
use crate::{
content,
globaluniform::{AtlasArray, ImageLocation, SpriteData, SpriteDataArray},
globaluniform::{AtlasArray, AtlasImageLocation, SpriteData, SpriteDataArray},
};
use anyhow::Result;
use bytemuck::Zeroable;
@ -129,7 +129,7 @@ impl TextureArray {
// Insert texture location data
for path in &t.frames {
let image = ct.get_image(&path);
image_locations.data[image_counter as usize] = ImageLocation {
image_locations.data[image_counter as usize] = AtlasImageLocation {
xpos: image.x,
ypos: image.y,
width: image.w,

View File

@ -186,11 +186,14 @@ pub struct ParticleInstance {
/// World position of this particle
pub position: [f32; 2],
/// Velocity of this sprite, in world coordinates
/// Velocity of this particle, in world coordinates
pub velocity: [f32; 2],
/// Rotation matrix for this sprite, in world coordinates
pub rotation: [[f32; 2]; 2],
/// Angle of this particle, in radians
pub angle: f32,
/// Angular velocity of this particle, rad/sec
pub angvel: f32,
/// The height of this particle, in world units
pub size: f32,
@ -225,38 +228,39 @@ impl BufferObject for ParticleInstance {
shader_location: 3,
format: wgpu::VertexFormat::Float32x2,
},
// Rotation
// Angle
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
shader_location: 4,
format: wgpu::VertexFormat::Float32x2,
format: wgpu::VertexFormat::Float32,
},
// Angvel
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 6]>() as wgpu::BufferAddress,
offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress,
shader_location: 5,
format: wgpu::VertexFormat::Float32x2,
format: wgpu::VertexFormat::Float32,
},
// Size
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
offset: mem::size_of::<[f32; 6]>() as wgpu::BufferAddress,
shader_location: 6,
format: wgpu::VertexFormat::Float32,
},
// Created
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 9]>() as wgpu::BufferAddress,
offset: mem::size_of::<[f32; 7]>() as wgpu::BufferAddress,
shader_location: 7,
format: wgpu::VertexFormat::Float32,
},
// Expires
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 10]>() as wgpu::BufferAddress,
offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
shader_location: 8,
format: wgpu::VertexFormat::Float32,
},
// Sprite
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 11]>() as wgpu::BufferAddress,
offset: mem::size_of::<[f32; 9]>() as wgpu::BufferAddress,
shader_location: 9,
format: wgpu::VertexFormat::Uint32,
},