Improved sprite routines

master
Mark 2023-12-23 23:24:04 -08:00
parent 6e59d95752
commit 4abbcca1d4
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
8 changed files with 354 additions and 158 deletions

76
src/render/globaldata.rs Normal file
View File

@ -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(),
};
}
}

View File

@ -1,6 +1,6 @@
use anyhow::Result; use anyhow::Result;
use bytemuck; use bytemuck;
use cgmath::{EuclideanSpace, Point2, Vector2}; use cgmath::{EuclideanSpace, Matrix2, Point2};
use std::{iter, rc::Rc}; use std::{iter, rc::Rc};
use wgpu; use wgpu;
use winit::{self, dpi::PhysicalSize, window::Window}; use winit::{self, dpi::PhysicalSize, window::Window};
@ -8,12 +8,12 @@ use winit::{self, dpi::PhysicalSize, window::Window};
use crate::Game; use crate::Game;
use super::{ use super::{
globaldata::{GlobalData, GlobalDataContent},
pipeline::PipelineBuilder, pipeline::PipelineBuilder,
texturearray::TextureArray, texturearray::TextureArray,
util::Transform,
vertexbuffer::{ vertexbuffer::{
data::{SPRITE_INDICES, SPRITE_VERTICES}, data::{SPRITE_INDICES, SPRITE_VERTICES},
types::{PlainVertex, SpriteInstance, StarfieldInstance, TexturedVertex}, types::{SpriteInstance, StarfieldInstance, TexturedVertex},
VertexBuffer, VertexBuffer,
}, },
}; };
@ -32,6 +32,7 @@ pub struct GPUState {
starfield_pipeline: wgpu::RenderPipeline, starfield_pipeline: wgpu::RenderPipeline,
texture_array: TextureArray, texture_array: TextureArray,
global_data: GlobalData,
vertex_buffers: VertexBuffers, vertex_buffers: VertexBuffers,
} }
@ -44,7 +45,7 @@ impl GPUState {
// We can draw at most this many sprites on the screen. // We can draw at most this many sprites on the screen.
// TODO: compile-time option // TODO: compile-time option
pub const SPRITE_INSTANCE_LIMIT: u64 = 100; 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> { pub async fn new(window: Window) -> Result<Self> {
let window_size = window.inner_size(); let window_size = window.inner_size();
@ -118,18 +119,25 @@ impl GPUState {
Self::SPRITE_INSTANCE_LIMIT, Self::SPRITE_INSTANCE_LIMIT,
)), )),
starfield: Rc::new(VertexBuffer::new::<PlainVertex, StarfieldInstance>( starfield: Rc::new(VertexBuffer::new::<TexturedVertex, StarfieldInstance>(
"starfield", "starfield",
&device, &device,
None, Some(SPRITE_VERTICES),
None, Some(SPRITE_INDICES),
Self::STARFIELD_INSTANCE_LIMIT, Self::STARFIELD_INSTANCE_LIMIT,
)), )),
}; };
// Load textures // Load uniforms
let global_data = GlobalData::new(&device);
let texture_array = TextureArray::new(&device, &queue)?; 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 // Create render pipelines
let sprite_pipeline = PipelineBuilder::new("sprite", &device) let sprite_pipeline = PipelineBuilder::new("sprite", &device)
.set_shader(include_str!(concat!( .set_shader(include_str!(concat!(
@ -140,7 +148,7 @@ impl GPUState {
.set_format(config.format) .set_format(config.format)
.set_triangle(true) .set_triangle(true)
.set_vertex_buffer(&vertex_buffers.sprite) .set_vertex_buffer(&vertex_buffers.sprite)
.set_bind_group_layouts(&[&texture_array.bind_group_layout]) .set_bind_group_layouts(bind_group_layouts)
.build(); .build();
let starfield_pipeline = PipelineBuilder::new("starfield", &device) let starfield_pipeline = PipelineBuilder::new("starfield", &device)
@ -150,8 +158,9 @@ impl GPUState {
"starfield.wgsl" "starfield.wgsl"
))) )))
.set_format(config.format) .set_format(config.format)
.set_triangle(false) .set_triangle(true)
.set_vertex_buffer(&vertex_buffers.starfield) .set_vertex_buffer(&vertex_buffers.starfield)
.set_bind_group_layouts(bind_group_layouts)
.build(); .build();
return Ok(Self { return Ok(Self {
@ -168,6 +177,7 @@ impl GPUState {
starfield_pipeline, starfield_pipeline,
texture_array, texture_array,
global_data,
vertex_buffers, vertex_buffers,
}); });
} }
@ -229,19 +239,12 @@ impl GPUState {
// //
// We can't use screen_pos to exclude off-screen sprites because // We can't use screen_pos to exclude off-screen sprites because
// it can't account for height and width. // 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 { instances.push(SpriteInstance {
transform: Transform { position: pos.into(),
pos: screen_pos, aspect: texture.aspect,
sprite_aspect: texture.aspect, rotation: Matrix2::from_angle(s.angle).into(),
window_aspect: self.window_aspect, height,
rotate: s.angle,
scale,
}
.to_matrix()
.into(),
texture_index: texture.index, texture_index: texture.index,
}) })
} }
@ -259,22 +262,25 @@ impl GPUState {
/// Will panic if STARFIELD_INSTANCE_LIMIT is exceeded. /// Will panic if STARFIELD_INSTANCE_LIMIT is exceeded.
/// ///
/// This is only called inside self.render() /// This is only called inside self.render()
fn make_starfield_instances(&self, _game: &Game) -> Vec<StarfieldInstance> { fn make_starfield_instances(&self, game: &Game) -> Vec<StarfieldInstance> {
let instances: Vec<StarfieldInstance> = (-10..10) let mut instances = Vec::new();
.map(|x| StarfieldInstance { for s in &game.system.starfield {
position: Vector2 { // TODO: minimize operations here
x: x as f32 / 10.0, //let pos = Sprite::post_parallax(s.parallax, s.pos * 500.0, game.camera)
y: 0.0, // - game.camera.pos.to_vec();
instances.push(StarfieldInstance {
position: s.pos.into(),
parallax: s.parallax,
height: 2.0,
});
} }
.into(),
})
.collect();
// Enforce starfield limit // Enforce starfield limit
if instances.len() as u64 > Self::STARFIELD_INSTANCE_LIMIT { if instances.len() as u64 > Self::STARFIELD_INSTANCE_LIMIT {
// TODO: no panic, handle this better. // TODO: no panic, handle this better.
unreachable!("Starfield limit exceeded!") unreachable!("Starfield limit exceeded!")
} }
return instances; return instances;
} }
@ -311,6 +317,20 @@ impl GPUState {
timestamp_writes: None, 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 // Create sprite instances
let sprite_instances = self.make_sprite_instances(game); let sprite_instances = self.make_sprite_instances(game);
self.queue.write_buffer( self.queue.write_buffer(
@ -327,12 +347,21 @@ impl GPUState {
bytemuck::cast_slice(&starfield_instances), 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(0, &self.texture_array.bind_group, &[]);
render_pass.set_bind_group(1, &self.global_data.bind_group, &[]);
// Starfield pipeline // Starfield pipeline
self.vertex_buffers.starfield.set_in_pass(&mut render_pass); self.vertex_buffers.starfield.set_in_pass(&mut render_pass);
render_pass.set_pipeline(&self.starfield_pipeline); render_pass.set_pipeline(&self.starfield_pipeline);
render_pass.draw(0..1, 0..starfield_instances.len() as _); 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 // Sprite pipeline
self.vertex_buffers.sprite.set_in_pass(&mut render_pass); self.vertex_buffers.sprite.set_in_pass(&mut render_pass);

View File

@ -1,18 +1,17 @@
mod globaldata;
mod gpustate; mod gpustate;
mod pipeline; mod pipeline;
mod rawtexture; mod rawtexture;
mod texturearray; mod texturearray;
mod util;
mod vertexbuffer; mod vertexbuffer;
pub use gpustate::GPUState; pub use gpustate::GPUState;
pub use texturearray::Texture; pub use texturearray::Texture;
use cgmath::Matrix4; // API correction matrix.
// cgmath uses OpenGL's matrix format, which
/// API correction matrix. // needs to be converted to wgpu's matrix format.
/// cgmath uses OpenGL's matrix format, which /*
/// needs to be converted to wgpu's matrix format.
#[rustfmt::skip] #[rustfmt::skip]
const OPENGL_TO_WGPU_MATRIX: Matrix4<f32> = Matrix4::new( const OPENGL_TO_WGPU_MATRIX: Matrix4<f32> = Matrix4::new(
1.0, 0.0, 0.0, 0.0, 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.5, 0.5,
0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0,
); );
*/

View File

@ -1,10 +1,11 @@
// Vertex shader // Vertex shader
struct InstanceInput { struct InstanceInput {
@location(2) transform_matrix_0: vec4<f32>, @location(2) rotation_matrix_0: vec2<f32>,
@location(3) transform_matrix_1: vec4<f32>, @location(3) rotation_matrix_1: vec2<f32>,
@location(4) transform_matrix_2: vec4<f32>, @location(4) position: vec2<f32>,
@location(5) transform_matrix_3: vec4<f32>, @location(5) height: f32,
@location(6) texture_idx: u32, @location(6) aspect: f32,
@location(7) texture_idx: u32,
}; };
struct VertexInput { struct VertexInput {
@ -13,26 +14,57 @@ struct VertexInput {
} }
struct VertexOutput { struct VertexOutput {
@builtin(position) clip_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) 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 @vertex
fn vertex_shader_main( fn vertex_shader_main(
model: VertexInput, vertex: VertexInput,
instance: InstanceInput, instance: InstanceInput,
) -> VertexOutput { ) -> VertexOutput {
let transform_matrix = mat4x4<f32>(
instance.transform_matrix_0, // Apply sprite aspect ratio & scale factor
instance.transform_matrix_1, // This must be done *before* rotation.
instance.transform_matrix_2, let scale = instance.height / global.camera_zoom;
instance.transform_matrix_3, 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; var out: VertexOutput;
out.texture_coords = model.texture_coords; out.position = vec4<f32>(pos, 0.0, 1.0);
out.clip_position = transform_matrix * vec4<f32>(model.position, 1.0); out.texture_coords = vertex.texture_coords;
out.index = instance.texture_idx; out.index = instance.texture_idx;
return out; return out;
} }

View File

@ -1,25 +1,94 @@
// Vertex shader // Vertex shader
struct InstanceInput { struct InstanceInput {
@location(2) position: vec2<f32>, @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 { 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 @vertex
fn vertex_shader_main( fn vertex_shader_main(
vertex: VertexInput,
instance: InstanceInput, instance: InstanceInput,
) -> VertexOutput { ) -> 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; 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; 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 shader
@fragment @fragment
fn fragment_shader_main(in: VertexOutput) -> @location(0) vec4<f32> { 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);
} }

View File

@ -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;
}
}

View File

@ -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 // Represents a point in the starfield instance in WGSL
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct StarfieldInstance { pub struct StarfieldInstance {
// All transformations we need to put this dot in its place /// Position in origin field tile.
// TODO: we don't need that much data /// note that this is DIFFERENT from
/// the way we provide sprite positions!
pub position: [f32; 2], 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 { impl BufferObject for StarfieldInstance {
@ -67,11 +53,26 @@ impl BufferObject for StarfieldInstance {
wgpu::VertexBufferLayout { wgpu::VertexBufferLayout {
array_stride: Self::SIZE, array_stride: Self::SIZE,
step_mode: wgpu::VertexStepMode::Instance, step_mode: wgpu::VertexStepMode::Instance,
attributes: &[wgpu::VertexAttribute { attributes: &[
// Position
wgpu::VertexAttribute {
offset: 0, offset: 0,
shader_location: 2, shader_location: 2,
format: wgpu::VertexFormat::Float32x2, 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)] #[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct SpriteInstance { pub struct SpriteInstance {
// All transformations we need to put this sprite in its place /// Rotation matrix for this sprite
pub transform: [[f32; 4]; 4], pub rotation: [[f32; 2]; 2],
// Which texture we should use for this sprite /// World position, relative to camera
// (see TextureArray) 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, pub texture_index: u32,
} }
@ -97,29 +106,39 @@ impl BufferObject for SpriteInstance {
// instance when the shader starts processing a new instance // instance when the shader starts processing a new instance
step_mode: wgpu::VertexStepMode::Instance, step_mode: wgpu::VertexStepMode::Instance,
attributes: &[ attributes: &[
// 2 arrays = 1 2x2 matrix
wgpu::VertexAttribute { wgpu::VertexAttribute {
offset: 0, offset: 0,
shader_location: 2, 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 { wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress, offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
shader_location: 3, shader_location: 4,
format: wgpu::VertexFormat::Float32x4, 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 { wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress, offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
shader_location: 4, shader_location: 7,
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,
format: wgpu::VertexFormat::Uint32, format: wgpu::VertexFormat::Uint32,
}, },
], ],

View File

@ -1,13 +1,37 @@
use crate::{physics::Polar, Doodad, Sprite, Spriteable}; use crate::{
use cgmath::Deg; 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 { pub struct System {
bodies: Vec<Doodad>, bodies: Vec<Doodad>,
pub starfield: Vec<StarfieldStar>,
} }
impl System { impl System {
pub fn new() -> Self { 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 { s.bodies.push(Doodad {
pos: (0.0, 0.0).into(), pos: (0.0, 0.0).into(),