Improved sprite routines
parent
6e59d95752
commit
4abbcca1d4
|
@ -0,0 +1,76 @@
|
|||
use bytemuck;
|
||||
use std::mem;
|
||||
use wgpu;
|
||||
|
||||
pub struct GlobalData {
|
||||
pub buffer: wgpu::Buffer,
|
||||
pub bind_group: wgpu::BindGroup,
|
||||
pub bind_group_layout: wgpu::BindGroupLayout,
|
||||
pub content: GlobalDataContent,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Default, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct GlobalDataContent {
|
||||
/// Camera position, in game units
|
||||
pub camera_position: [f32; 2],
|
||||
|
||||
/// Camera zoom value, in game units
|
||||
pub camera_zoom: f32,
|
||||
|
||||
/// Aspect ratio of window (width / height)
|
||||
pub window_aspect: f32,
|
||||
|
||||
/// Texture index of starfield sprites
|
||||
pub starfield_texture: u32,
|
||||
|
||||
// Size of (square) starfield tile, in game units
|
||||
pub starfield_tile_size: f32,
|
||||
|
||||
pub padding: [f32; 3],
|
||||
}
|
||||
|
||||
impl GlobalDataContent {
|
||||
const SIZE: u64 = mem::size_of::<Self>() as wgpu::BufferAddress;
|
||||
}
|
||||
|
||||
impl GlobalData {
|
||||
pub fn new(device: &wgpu::Device) -> Self {
|
||||
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: None,
|
||||
size: GlobalDataContent::SIZE,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
label: Some("camera_bind_group_layout"),
|
||||
});
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &bind_group_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: buffer.as_entire_binding(),
|
||||
}],
|
||||
label: Some("camera_bind_group"),
|
||||
});
|
||||
|
||||
return Self {
|
||||
buffer,
|
||||
bind_group,
|
||||
bind_group_layout,
|
||||
content: GlobalDataContent::default(),
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use bytemuck;
|
||||
use cgmath::{EuclideanSpace, Point2, Vector2};
|
||||
use cgmath::{EuclideanSpace, Matrix2, Point2};
|
||||
use std::{iter, rc::Rc};
|
||||
use wgpu;
|
||||
use winit::{self, dpi::PhysicalSize, window::Window};
|
||||
|
@ -8,12 +8,12 @@ use winit::{self, dpi::PhysicalSize, window::Window};
|
|||
use crate::Game;
|
||||
|
||||
use super::{
|
||||
globaldata::{GlobalData, GlobalDataContent},
|
||||
pipeline::PipelineBuilder,
|
||||
texturearray::TextureArray,
|
||||
util::Transform,
|
||||
vertexbuffer::{
|
||||
data::{SPRITE_INDICES, SPRITE_VERTICES},
|
||||
types::{PlainVertex, SpriteInstance, StarfieldInstance, TexturedVertex},
|
||||
types::{SpriteInstance, StarfieldInstance, TexturedVertex},
|
||||
VertexBuffer,
|
||||
},
|
||||
};
|
||||
|
@ -32,6 +32,7 @@ pub struct GPUState {
|
|||
starfield_pipeline: wgpu::RenderPipeline,
|
||||
|
||||
texture_array: TextureArray,
|
||||
global_data: GlobalData,
|
||||
vertex_buffers: VertexBuffers,
|
||||
}
|
||||
|
||||
|
@ -44,7 +45,7 @@ impl GPUState {
|
|||
// We can draw at most this many sprites on the screen.
|
||||
// TODO: compile-time option
|
||||
pub const SPRITE_INSTANCE_LIMIT: u64 = 100;
|
||||
pub const STARFIELD_INSTANCE_LIMIT: u64 = 100;
|
||||
pub const STARFIELD_INSTANCE_LIMIT: u64 = 501 * 9;
|
||||
|
||||
pub async fn new(window: Window) -> Result<Self> {
|
||||
let window_size = window.inner_size();
|
||||
|
@ -118,18 +119,25 @@ impl GPUState {
|
|||
Self::SPRITE_INSTANCE_LIMIT,
|
||||
)),
|
||||
|
||||
starfield: Rc::new(VertexBuffer::new::<PlainVertex, StarfieldInstance>(
|
||||
starfield: Rc::new(VertexBuffer::new::<TexturedVertex, StarfieldInstance>(
|
||||
"starfield",
|
||||
&device,
|
||||
None,
|
||||
None,
|
||||
Some(SPRITE_VERTICES),
|
||||
Some(SPRITE_INDICES),
|
||||
Self::STARFIELD_INSTANCE_LIMIT,
|
||||
)),
|
||||
};
|
||||
|
||||
// Load textures
|
||||
// Load uniforms
|
||||
let global_data = GlobalData::new(&device);
|
||||
let texture_array = TextureArray::new(&device, &queue)?;
|
||||
|
||||
// Make sure these match the indices in each shader
|
||||
let bind_group_layouts = &[
|
||||
&texture_array.bind_group_layout,
|
||||
&global_data.bind_group_layout,
|
||||
];
|
||||
|
||||
// Create render pipelines
|
||||
let sprite_pipeline = PipelineBuilder::new("sprite", &device)
|
||||
.set_shader(include_str!(concat!(
|
||||
|
@ -140,7 +148,7 @@ impl GPUState {
|
|||
.set_format(config.format)
|
||||
.set_triangle(true)
|
||||
.set_vertex_buffer(&vertex_buffers.sprite)
|
||||
.set_bind_group_layouts(&[&texture_array.bind_group_layout])
|
||||
.set_bind_group_layouts(bind_group_layouts)
|
||||
.build();
|
||||
|
||||
let starfield_pipeline = PipelineBuilder::new("starfield", &device)
|
||||
|
@ -150,8 +158,9 @@ impl GPUState {
|
|||
"starfield.wgsl"
|
||||
)))
|
||||
.set_format(config.format)
|
||||
.set_triangle(false)
|
||||
.set_triangle(true)
|
||||
.set_vertex_buffer(&vertex_buffers.starfield)
|
||||
.set_bind_group_layouts(bind_group_layouts)
|
||||
.build();
|
||||
|
||||
return Ok(Self {
|
||||
|
@ -168,6 +177,7 @@ impl GPUState {
|
|||
starfield_pipeline,
|
||||
|
||||
texture_array,
|
||||
global_data,
|
||||
vertex_buffers,
|
||||
});
|
||||
}
|
||||
|
@ -229,19 +239,12 @@ impl GPUState {
|
|||
//
|
||||
// We can't use screen_pos to exclude off-screen sprites because
|
||||
// it can't account for height and width.
|
||||
let scale = height / game.camera.zoom;
|
||||
let screen_pos: Point2<f32> = pos / game.camera.zoom;
|
||||
|
||||
instances.push(SpriteInstance {
|
||||
transform: Transform {
|
||||
pos: screen_pos,
|
||||
sprite_aspect: texture.aspect,
|
||||
window_aspect: self.window_aspect,
|
||||
rotate: s.angle,
|
||||
scale,
|
||||
}
|
||||
.to_matrix()
|
||||
.into(),
|
||||
position: pos.into(),
|
||||
aspect: texture.aspect,
|
||||
rotation: Matrix2::from_angle(s.angle).into(),
|
||||
height,
|
||||
texture_index: texture.index,
|
||||
})
|
||||
}
|
||||
|
@ -259,22 +262,25 @@ impl GPUState {
|
|||
/// Will panic if STARFIELD_INSTANCE_LIMIT is exceeded.
|
||||
///
|
||||
/// This is only called inside self.render()
|
||||
fn make_starfield_instances(&self, _game: &Game) -> Vec<StarfieldInstance> {
|
||||
let instances: Vec<StarfieldInstance> = (-10..10)
|
||||
.map(|x| StarfieldInstance {
|
||||
position: Vector2 {
|
||||
x: x as f32 / 10.0,
|
||||
y: 0.0,
|
||||
fn make_starfield_instances(&self, game: &Game) -> Vec<StarfieldInstance> {
|
||||
let mut instances = Vec::new();
|
||||
for s in &game.system.starfield {
|
||||
// TODO: minimize operations here
|
||||
//let pos = Sprite::post_parallax(s.parallax, s.pos * 500.0, game.camera)
|
||||
// - game.camera.pos.to_vec();
|
||||
instances.push(StarfieldInstance {
|
||||
position: s.pos.into(),
|
||||
parallax: s.parallax,
|
||||
height: 2.0,
|
||||
});
|
||||
}
|
||||
.into(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Enforce starfield limit
|
||||
if instances.len() as u64 > Self::STARFIELD_INSTANCE_LIMIT {
|
||||
// TODO: no panic, handle this better.
|
||||
unreachable!("Starfield limit exceeded!")
|
||||
}
|
||||
|
||||
return instances;
|
||||
}
|
||||
|
||||
|
@ -311,6 +317,20 @@ impl GPUState {
|
|||
timestamp_writes: None,
|
||||
});
|
||||
|
||||
// Update global values
|
||||
self.queue.write_buffer(
|
||||
&self.global_data.buffer,
|
||||
0,
|
||||
bytemuck::cast_slice(&[GlobalDataContent {
|
||||
camera_position: game.camera.pos.into(),
|
||||
camera_zoom: game.camera.zoom,
|
||||
window_aspect: self.window_aspect,
|
||||
starfield_texture: 1,
|
||||
starfield_tile_size: 1000.0,
|
||||
padding: Default::default(),
|
||||
}]),
|
||||
);
|
||||
|
||||
// Create sprite instances
|
||||
let sprite_instances = self.make_sprite_instances(game);
|
||||
self.queue.write_buffer(
|
||||
|
@ -327,12 +347,21 @@ impl GPUState {
|
|||
bytemuck::cast_slice(&starfield_instances),
|
||||
);
|
||||
|
||||
// These should match the indices in each shader,
|
||||
// and should each have a corresponding bind group layout.
|
||||
render_pass.set_bind_group(0, &self.texture_array.bind_group, &[]);
|
||||
render_pass.set_bind_group(1, &self.global_data.bind_group, &[]);
|
||||
|
||||
// Starfield pipeline
|
||||
self.vertex_buffers.starfield.set_in_pass(&mut render_pass);
|
||||
render_pass.set_pipeline(&self.starfield_pipeline);
|
||||
|
||||
render_pass.draw(0..1, 0..starfield_instances.len() as _);
|
||||
render_pass.draw_indexed(
|
||||
0..SPRITE_INDICES.len() as u32,
|
||||
0,
|
||||
0..starfield_instances.len() as _,
|
||||
);
|
||||
|
||||
// Sprite pipeline
|
||||
self.vertex_buffers.sprite.set_in_pass(&mut render_pass);
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
mod globaldata;
|
||||
mod gpustate;
|
||||
mod pipeline;
|
||||
mod rawtexture;
|
||||
mod texturearray;
|
||||
mod util;
|
||||
mod vertexbuffer;
|
||||
|
||||
pub use gpustate::GPUState;
|
||||
pub use texturearray::Texture;
|
||||
|
||||
use cgmath::Matrix4;
|
||||
|
||||
/// API correction matrix.
|
||||
/// cgmath uses OpenGL's matrix format, which
|
||||
/// needs to be converted to wgpu's matrix format.
|
||||
// API correction matrix.
|
||||
// cgmath uses OpenGL's matrix format, which
|
||||
// needs to be converted to wgpu's matrix format.
|
||||
/*
|
||||
#[rustfmt::skip]
|
||||
const OPENGL_TO_WGPU_MATRIX: Matrix4<f32> = Matrix4::new(
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
|
@ -20,3 +19,4 @@ const OPENGL_TO_WGPU_MATRIX: Matrix4<f32> = Matrix4::new(
|
|||
0.0, 0.0, 0.5, 0.5,
|
||||
0.0, 0.0, 0.0, 1.0,
|
||||
);
|
||||
*/
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
// Vertex shader
|
||||
struct InstanceInput {
|
||||
@location(2) transform_matrix_0: vec4<f32>,
|
||||
@location(3) transform_matrix_1: vec4<f32>,
|
||||
@location(4) transform_matrix_2: vec4<f32>,
|
||||
@location(5) transform_matrix_3: vec4<f32>,
|
||||
@location(6) texture_idx: u32,
|
||||
@location(2) rotation_matrix_0: vec2<f32>,
|
||||
@location(3) rotation_matrix_1: vec2<f32>,
|
||||
@location(4) position: vec2<f32>,
|
||||
@location(5) height: f32,
|
||||
@location(6) aspect: f32,
|
||||
@location(7) texture_idx: u32,
|
||||
};
|
||||
|
||||
struct VertexInput {
|
||||
|
@ -13,26 +14,57 @@ struct VertexInput {
|
|||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) texture_coords: vec2<f32>,
|
||||
@location(1) index: u32,
|
||||
}
|
||||
|
||||
|
||||
@group(1) @binding(0)
|
||||
var<uniform> global: GlobalUniform;
|
||||
struct GlobalUniform {
|
||||
camera_position: vec2<f32>,
|
||||
camera_zoom: f32,
|
||||
window_aspect: f32,
|
||||
starfield_texture: u32,
|
||||
starfield_tile_size: f32
|
||||
};
|
||||
|
||||
|
||||
@vertex
|
||||
fn vertex_shader_main(
|
||||
model: VertexInput,
|
||||
vertex: VertexInput,
|
||||
instance: InstanceInput,
|
||||
) -> VertexOutput {
|
||||
let transform_matrix = mat4x4<f32>(
|
||||
instance.transform_matrix_0,
|
||||
instance.transform_matrix_1,
|
||||
instance.transform_matrix_2,
|
||||
instance.transform_matrix_3,
|
||||
|
||||
// Apply sprite aspect ratio & scale factor
|
||||
// This must be done *before* rotation.
|
||||
let scale = instance.height / global.camera_zoom;
|
||||
var pos: vec2<f32> = vec2<f32>(
|
||||
vertex.position.x * instance.aspect * scale,
|
||||
vertex.position.y * scale
|
||||
);
|
||||
|
||||
// Rotate
|
||||
pos = mat2x2<f32>(
|
||||
instance.rotation_matrix_0,
|
||||
instance.rotation_matrix_1,
|
||||
) * pos;
|
||||
|
||||
// Apply screen aspect ratio, again preserving height.
|
||||
// This must be done AFTER rotation... think about it!
|
||||
pos = pos / vec2<f32>(global.window_aspect, 1.0);
|
||||
|
||||
// Translate
|
||||
pos = pos + (
|
||||
// Don't forget to correct distance for screen aspect ratio too!
|
||||
(instance.position / global.camera_zoom)
|
||||
/ vec2<f32>(global.window_aspect, 1.0)
|
||||
);
|
||||
|
||||
var out: VertexOutput;
|
||||
out.texture_coords = model.texture_coords;
|
||||
out.clip_position = transform_matrix * vec4<f32>(model.position, 1.0);
|
||||
out.position = vec4<f32>(pos, 0.0, 1.0);
|
||||
out.texture_coords = vertex.texture_coords;
|
||||
out.index = instance.texture_idx;
|
||||
return out;
|
||||
}
|
||||
|
|
|
@ -1,25 +1,94 @@
|
|||
// Vertex shader
|
||||
struct InstanceInput {
|
||||
@location(2) position: vec2<f32>,
|
||||
@location(3) parallax: f32,
|
||||
@location(4) height: f32,
|
||||
};
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec3<f32>,
|
||||
@location(1) texture_coords: vec2<f32>,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) texture_coords: vec2<f32>,
|
||||
}
|
||||
|
||||
@group(1) @binding(0)
|
||||
var<uniform> global: GlobalUniform;
|
||||
struct GlobalUniform {
|
||||
camera_position: vec2<f32>,
|
||||
camera_zoom: f32,
|
||||
window_aspect: f32,
|
||||
starfield_texture: u32,
|
||||
starfield_tile_size: f32
|
||||
};
|
||||
|
||||
|
||||
fn fmod(x: vec2<f32>, m: f32) -> vec2<f32> {
|
||||
return x - floor(x * (1.0 / m)) * m;
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vertex_shader_main(
|
||||
vertex: VertexInput,
|
||||
instance: InstanceInput,
|
||||
) -> VertexOutput {
|
||||
// Stars consist of only one vertex, so we don't need to pass a buffer into this shader.
|
||||
// We need one instance per star, and that's it!
|
||||
|
||||
// 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
|
||||
- (
|
||||
fmod(
|
||||
global.camera_position + global.starfield_tile_size/2.0,
|
||||
global.starfield_tile_size
|
||||
) - global.starfield_tile_size/2.0
|
||||
)
|
||||
);
|
||||
|
||||
// Apply sprite aspect ratio & scale factor
|
||||
// also applies screen aspect ratio
|
||||
let scale = instance.height / global.camera_zoom;
|
||||
var pos: vec2<f32> = vec2<f32>(
|
||||
vertex.position.x * scale / global.window_aspect,
|
||||
vertex.position.y * scale
|
||||
);
|
||||
|
||||
// World position relative to camera
|
||||
// (Note that instance position is in a different
|
||||
// coordinate system than usual)
|
||||
let camera_pos = (instance.position + tile_center) - global.camera_position;
|
||||
let parallax_vector = -camera_pos * instance.parallax * 0.1;
|
||||
|
||||
// Translate
|
||||
pos = pos + (
|
||||
// Don't forget to correct distance for screen aspect ratio too!
|
||||
// The minus in "minus parallax_vector" is important.
|
||||
((camera_pos + parallax_vector) / global.camera_zoom)
|
||||
/ vec2<f32>(global.window_aspect, 1.0)
|
||||
);
|
||||
|
||||
var out: VertexOutput;
|
||||
out.clip_position = vec4<f32>(instance.position, 0.0, 1.0);
|
||||
out.position = vec4<f32>(pos, 0.0, 1.0);
|
||||
out.texture_coords = vertex.texture_coords;
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@group(0) @binding(0)
|
||||
var texture_array: binding_array<texture_2d<f32>>;
|
||||
@group(0) @binding(1)
|
||||
var sampler_array: binding_array<sampler>;
|
||||
|
||||
// Fragment shader
|
||||
@fragment
|
||||
fn fragment_shader_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
|
||||
return textureSampleLevel(
|
||||
texture_array[1],
|
||||
sampler_array[1],
|
||||
in.texture_coords,
|
||||
0.0
|
||||
).rgba * vec4<f32>(0.5, 0.5, 0.5, 1.0);
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
use cgmath::{Deg, Matrix4, Point2, Vector3};
|
||||
|
||||
use super::OPENGL_TO_WGPU_MATRIX;
|
||||
|
||||
// Represents a sprite tranformation.
|
||||
//
|
||||
// This produces a single matrix we can apply to a brand-new sprite instance
|
||||
// to put it in the right place, at the right angle, with the right scale.
|
||||
pub struct Transform {
|
||||
pub pos: Point2<f32>, // position on screen
|
||||
pub window_aspect: f32, // width / height. Screen aspect ratio.
|
||||
pub sprite_aspect: f32, // width / height. Sprite aspect ratio.
|
||||
pub scale: f32, // if scale = 1, this sprite will be as tall as the screen.
|
||||
pub rotate: Deg<f32>, // Around this object's center, in degrees measured ccw from vertical
|
||||
}
|
||||
|
||||
impl Transform {
|
||||
/// Build the matrix that corresponds to this transformation.
|
||||
pub fn to_matrix(&self) -> Matrix4<f32> {
|
||||
// Note that our mesh starts centered at (0, 0).
|
||||
// This is essential---we do not want scale and rotation
|
||||
// changing our sprite's position!
|
||||
|
||||
// Apply sprite aspect ratio, preserving height.
|
||||
// This must be done *before* rotation.
|
||||
//
|
||||
// We apply the provided scale here as well as a minor optimization
|
||||
let sprite_aspect_and_scale =
|
||||
Matrix4::from_nonuniform_scale(self.sprite_aspect * self.scale, self.scale, 1.0);
|
||||
|
||||
// Apply rotation
|
||||
let rotate = Matrix4::from_angle_z(self.rotate);
|
||||
|
||||
// Apply screen aspect ratio, again preserving height.
|
||||
// This must be done AFTER rotation... think about it!
|
||||
let screen_aspect = Matrix4::from_nonuniform_scale(1.0 / self.window_aspect, 1.0, 1.0);
|
||||
|
||||
// After finishing all op, translate.
|
||||
// This must be done last, all other operations
|
||||
// require us to be at (0, 0).
|
||||
let translate = Matrix4::from_translation(Vector3 {
|
||||
x: self.pos.x,
|
||||
y: self.pos.y,
|
||||
z: 0.0,
|
||||
});
|
||||
|
||||
// Order matters!
|
||||
// The rightmost matrix is applied first.
|
||||
return OPENGL_TO_WGPU_MATRIX
|
||||
* translate * screen_aspect
|
||||
* rotate * sprite_aspect_and_scale;
|
||||
}
|
||||
}
|
|
@ -32,34 +32,20 @@ impl BufferObject for TexturedVertex {
|
|||
}
|
||||
}
|
||||
|
||||
// Represents a plain vertex in WGSL
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct PlainVertex {
|
||||
pub position: [f32; 3],
|
||||
}
|
||||
|
||||
impl BufferObject for PlainVertex {
|
||||
fn layout() -> wgpu::VertexBufferLayout<'static> {
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: Self::SIZE,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
attributes: &[wgpu::VertexAttribute {
|
||||
offset: 0,
|
||||
shader_location: 0,
|
||||
format: wgpu::VertexFormat::Float32x3,
|
||||
}],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Represents a point in the starfield instance in WGSL
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct StarfieldInstance {
|
||||
// All transformations we need to put this dot in its place
|
||||
// TODO: we don't need that much data
|
||||
/// Position in origin field tile.
|
||||
/// note that this is DIFFERENT from
|
||||
/// the way we provide sprite positions!
|
||||
pub position: [f32; 2],
|
||||
|
||||
/// Parallax factor (same unit as usual)
|
||||
pub parallax: f32,
|
||||
|
||||
/// Height of (unrotated) sprite in world units
|
||||
pub height: f32,
|
||||
}
|
||||
|
||||
impl BufferObject for StarfieldInstance {
|
||||
|
@ -67,11 +53,26 @@ impl BufferObject for StarfieldInstance {
|
|||
wgpu::VertexBufferLayout {
|
||||
array_stride: Self::SIZE,
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: &[wgpu::VertexAttribute {
|
||||
attributes: &[
|
||||
// Position
|
||||
wgpu::VertexAttribute {
|
||||
offset: 0,
|
||||
shader_location: 2,
|
||||
format: wgpu::VertexFormat::Float32x2,
|
||||
}],
|
||||
},
|
||||
// Parallax
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
|
||||
shader_location: 3,
|
||||
format: wgpu::VertexFormat::Float32,
|
||||
},
|
||||
// Height
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
|
||||
shader_location: 4,
|
||||
format: wgpu::VertexFormat::Float32,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -80,11 +81,19 @@ impl BufferObject for StarfieldInstance {
|
|||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct SpriteInstance {
|
||||
// All transformations we need to put this sprite in its place
|
||||
pub transform: [[f32; 4]; 4],
|
||||
/// Rotation matrix for this sprite
|
||||
pub rotation: [[f32; 2]; 2],
|
||||
|
||||
// Which texture we should use for this sprite
|
||||
// (see TextureArray)
|
||||
/// World position, relative to camera
|
||||
pub position: [f32; 2],
|
||||
|
||||
/// Height of (unrotated) sprite in world units
|
||||
pub height: f32,
|
||||
|
||||
// Sprite aspect ratio (width / height)
|
||||
pub aspect: f32,
|
||||
|
||||
// What texture to use for this sprite
|
||||
pub texture_index: u32,
|
||||
}
|
||||
|
||||
|
@ -97,29 +106,39 @@ impl BufferObject for SpriteInstance {
|
|||
// instance when the shader starts processing a new instance
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: &[
|
||||
// 2 arrays = 1 2x2 matrix
|
||||
wgpu::VertexAttribute {
|
||||
offset: 0,
|
||||
shader_location: 2,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
format: wgpu::VertexFormat::Float32x2,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
|
||||
shader_location: 3,
|
||||
format: wgpu::VertexFormat::Float32x2,
|
||||
},
|
||||
// Position
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
|
||||
shader_location: 3,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
shader_location: 4,
|
||||
format: wgpu::VertexFormat::Float32x2,
|
||||
},
|
||||
// Height
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 6]>() as wgpu::BufferAddress,
|
||||
shader_location: 5,
|
||||
format: wgpu::VertexFormat::Float32,
|
||||
},
|
||||
// Aspect
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 7]>() as wgpu::BufferAddress,
|
||||
shader_location: 6,
|
||||
format: wgpu::VertexFormat::Float32,
|
||||
},
|
||||
// Texture
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
|
||||
shader_location: 4,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress,
|
||||
shader_location: 5,
|
||||
format: wgpu::VertexFormat::Float32x4,
|
||||
},
|
||||
wgpu::VertexAttribute {
|
||||
offset: mem::size_of::<[f32; 16]>() as wgpu::BufferAddress,
|
||||
shader_location: 6,
|
||||
shader_location: 7,
|
||||
format: wgpu::VertexFormat::Uint32,
|
||||
},
|
||||
],
|
||||
|
|
|
@ -1,13 +1,37 @@
|
|||
use crate::{physics::Polar, Doodad, Sprite, Spriteable};
|
||||
use cgmath::Deg;
|
||||
use crate::{
|
||||
physics::{Pfloat, Polar},
|
||||
Doodad, Sprite, Spriteable,
|
||||
};
|
||||
use cgmath::{Deg, Point2};
|
||||
use rand::{self, Rng};
|
||||
|
||||
pub struct StarfieldStar {
|
||||
// Star coordinates, in world space.
|
||||
// These are relative to the center of a starfield tile.
|
||||
pub pos: Point2<Pfloat>,
|
||||
pub parallax: Pfloat,
|
||||
}
|
||||
|
||||
pub struct System {
|
||||
bodies: Vec<Doodad>,
|
||||
pub starfield: Vec<StarfieldStar>,
|
||||
}
|
||||
|
||||
impl System {
|
||||
pub fn new() -> Self {
|
||||
let mut s = System { bodies: Vec::new() };
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut s = System {
|
||||
bodies: Vec::new(),
|
||||
starfield: (0..500)
|
||||
.map(|_| StarfieldStar {
|
||||
pos: Point2 {
|
||||
x: rng.gen_range(-500.0..500.0),
|
||||
y: rng.gen_range(-500.0..500.0),
|
||||
},
|
||||
parallax: rng.gen_range(10.0..11.0),
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
|
||||
s.bodies.push(Doodad {
|
||||
pos: (0.0, 0.0).into(),
|
||||
|
|
Loading…
Reference in New Issue