master
Mark 2023-12-25 11:17:08 -08:00
parent fd94cd6701
commit abd41af202
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
20 changed files with 258 additions and 232 deletions

21
src/consts.rs Normal file
View File

@ -0,0 +1,21 @@
use crate::physics::Pfloat;
pub const ZOOM_MIN: Pfloat = 200.0;
pub const ZOOM_MAX: Pfloat = 2000.0;
// Z-axis range for starfield stars
pub const STARFIELD_PARALLAX_MIN: f32 = 100.0;
pub const STARFIELD_PARALLAX_MAX: f32 = 200.0;
// Size of a square starfield tile, in game units.
// A tile of size PARALLAX_MAX * screen-size-in-game-units
// will completely cover a (square) screen. This depends on zoom!
//
// Use a value smaller than zoom_max for debug.
pub const STARFIELD_SIZE: u64 = STARFIELD_PARALLAX_MAX as u64 * ZOOM_MAX as u64;
// Average number of stars per game unit
pub const STARFIELD_DENSITY: f64 = 0.01;
// Number of stars in one starfield tile
// Must fit inside an i32
pub const STARFIELD_COUNT: u64 = (STARFIELD_SIZE as f64 * STARFIELD_DENSITY) as u64;
pub const CONTENT_ROOT: &'static str = "./content";

13
src/game/camera.rs Normal file
View File

@ -0,0 +1,13 @@
use cgmath::Point2;
use crate::physics::Pfloat;
#[derive(Debug, Clone, Copy)]
pub struct Camera {
/// Camera center
pub pos: Point2<Pfloat>,
/// Camera zoom
/// (How many game units tall is the viewport?)
pub zoom: Pfloat,
}

View File

@ -1,6 +1,6 @@
use cgmath::{Deg, Point2}; use cgmath::{Deg, Point2};
use crate::{physics::Pfloat, render::SpriteTexture, Sprite, Spriteable}; use crate::{physics::Pfloat, render::Sprite, render::SpriteTexture, render::Spriteable};
pub struct Doodad { pub struct Doodad {
pub sprite: SpriteTexture, pub sprite: SpriteTexture,

84
src/game/game.rs Normal file
View File

@ -0,0 +1,84 @@
use cgmath::Deg;
use std::time::Instant;
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
use super::{ship::ShipKind, Camera, InputStatus, Ship, System};
use crate::{consts, content::Content, physics::Pfloat, render::Sprite, render::Spriteable};
pub struct Game {
pub input: InputStatus,
pub last_update: Instant,
pub player: Ship,
pub system: System,
pub camera: Camera,
}
impl Game {
pub fn new(ct: Content) -> Self {
Game {
last_update: Instant::now(),
input: InputStatus::new(),
player: Ship::new(ShipKind::Gypsum, (0.0, 0.0).into()),
camera: Camera {
pos: (0.0, 0.0).into(),
zoom: 500.0,
},
system: System::new(&ct.systems[0]),
}
}
pub fn process_key(&mut self, state: &ElementState, key: &VirtualKeyCode) {
self.input.process_key(state, key)
}
pub fn process_click(&mut self, state: &ElementState, key: &MouseButton) {
self.input.process_click(state, key)
}
pub fn process_scroll(&mut self, delta: &MouseScrollDelta, phase: &TouchPhase) {
self.input.process_scroll(delta, phase)
}
pub fn update(&mut self) {
let t: Pfloat = self.last_update.elapsed().as_secs_f32();
if self.input.key_thrust {
self.player.body.thrust(50.0 * t);
}
if self.input.key_right {
self.player.body.rot(Deg(35.0) * t);
}
if self.input.key_left {
self.player.body.rot(Deg(-35.0) * t);
}
if self.input.v_scroll != 0.0 {
self.camera.zoom =
(self.camera.zoom + self.input.v_scroll).clamp(consts::ZOOM_MIN, consts::ZOOM_MAX);
self.input.v_scroll = 0.0;
}
self.player.body.tick(t);
self.camera.pos = self.player.body.pos;
self.last_update = Instant::now();
}
pub fn get_sprites(&self) -> Vec<Sprite> {
let mut sprites: Vec<Sprite> = Vec::new();
sprites.append(&mut self.system.get_sprites());
sprites.push(self.player.get_sprite());
// Make sure sprites are drawn in the correct order
// (note the reversed a, b in the comparator)
//
// TODO: use a gpu depth buffer with parallax as z-coordinate?
// Might be overkill.
sprites.sort_by(|a, b| b.parallax.total_cmp(&a.parallax));
return sprites;
}
}

13
src/game/mod.rs Normal file
View File

@ -0,0 +1,13 @@
mod camera;
mod doodad;
mod game;
mod inputstatus;
mod ship;
mod system;
pub use camera::Camera;
pub use doodad::Doodad;
pub use game::Game;
pub use inputstatus::InputStatus;
pub use ship::Ship;
pub use system::System;

View File

@ -1,6 +1,8 @@
use cgmath::Point2; use cgmath::Point2;
use crate::{physics::Pfloat, physics::PhysBody, render::SpriteTexture, Sprite, Spriteable}; use crate::{
physics::Pfloat, physics::PhysBody, render::Sprite, render::SpriteTexture, render::Spriteable,
};
pub enum ShipKind { pub enum ShipKind {
Gypsum, Gypsum,

View File

@ -1,10 +1,11 @@
use crate::{
content, physics::Pfloat, render::SpriteTexture, Doodad, Sprite, Spriteable, STARFIELD_COUNT,
STARFIELD_PARALLAX_MAX, STARFIELD_PARALLAX_MIN, STARFIELD_SIZE,
};
use cgmath::{Point2, Vector2}; use cgmath::{Point2, Vector2};
use rand::{self, Rng}; use rand::{self, Rng};
use super::Doodad;
use crate::{
consts, content, physics::Pfloat, render::Sprite, render::SpriteTexture, render::Spriteable,
};
pub struct StarfieldStar { pub struct StarfieldStar {
/// Star coordinates, in world space. /// Star coordinates, in world space.
/// These are relative to the center of a starfield tile. /// These are relative to the center of a starfield tile.
@ -28,17 +29,18 @@ pub struct System {
impl System { impl System {
pub fn new(ct: &content::system::System) -> Self { pub fn new(ct: &content::system::System) -> Self {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let sz = STARFIELD_SIZE as f32 / 2.0; let sz = consts::STARFIELD_SIZE as f32 / 2.0;
let mut s = System { let mut s = System {
name: ct.name.clone(), name: ct.name.clone(),
bodies: Vec::new(), bodies: Vec::new(),
starfield: (0..STARFIELD_COUNT) starfield: (0..consts::STARFIELD_COUNT)
.map(|_| StarfieldStar { .map(|_| StarfieldStar {
pos: Point2 { pos: Point2 {
x: rng.gen_range(-sz..=sz), x: rng.gen_range(-sz..=sz),
y: rng.gen_range(-sz..=sz), y: rng.gen_range(-sz..=sz),
}, },
parallax: rng.gen_range(STARFIELD_PARALLAX_MIN..STARFIELD_PARALLAX_MAX), parallax: rng
.gen_range(consts::STARFIELD_PARALLAX_MIN..consts::STARFIELD_PARALLAX_MAX),
size: rng.gen_range(0.2..0.8), // TODO: configurable size: rng.gen_range(0.2..0.8), // TODO: configurable
tint: Vector2 { tint: Vector2 {
x: rng.gen_range(0.0..=1.0), x: rng.gen_range(0.0..=1.0),

View File

@ -1,174 +1,29 @@
mod consts;
mod content; mod content;
mod doodad; mod game;
mod inputstatus;
mod physics; mod physics;
mod render; mod render;
mod ship;
mod system;
use anyhow::Result; use anyhow::Result;
use cgmath::{Deg, Point2};
use content::Content;
use std::time::Instant;
use winit::{ use winit::{
event::{ event::{Event, KeyboardInput, WindowEvent},
ElementState, Event, KeyboardInput, MouseButton, MouseScrollDelta, TouchPhase,
VirtualKeyCode, WindowEvent,
},
event_loop::{ControlFlow, EventLoop}, event_loop::{ControlFlow, EventLoop},
window::WindowBuilder, window::WindowBuilder,
}; };
use crate::{ fn main() -> Result<()> {
doodad::Doodad, let content = content::load_content_dir(consts::CONTENT_ROOT)?;
inputstatus::InputStatus, let game = game::Game::new(content);
physics::Pfloat,
render::{GPUState, SpriteTexture},
ship::{Ship, ShipKind},
system::System,
};
pub const ZOOM_MIN: Pfloat = 200.0; pollster::block_on(run(game))?;
pub const ZOOM_MAX: Pfloat = 2000.0; return Ok(());
// Z-axis range for starfield stars
pub const STARFIELD_PARALLAX_MIN: f32 = 100.0;
pub const STARFIELD_PARALLAX_MAX: f32 = 200.0;
// Size of a square starfield tile, in game units.
// A tile of size PARALLAX_MAX * screen-size-in-game-units
// will completely cover a (square) screen. This depends on zoom!
//
// Use a value smaller than zoom_max for debug.
pub const STARFIELD_SIZE: u64 = STARFIELD_PARALLAX_MAX as u64 * ZOOM_MAX as u64;
// Average number of stars per game unit
pub const STARFIELD_DENSITY: f64 = 0.01;
// Number of stars in one starfield tile
// Must fit inside an i32
pub const STARFIELD_COUNT: u64 = (STARFIELD_SIZE as f64 * STARFIELD_DENSITY) as u64;
#[derive(Debug, Clone, Copy)]
struct Camera {
/// Camera center
pos: Point2<Pfloat>,
/// Camera zoom
/// (How many game units tall is the viewport?)
zoom: Pfloat,
} }
trait Spriteable { async fn run(mut game: game::Game) -> Result<()> {
fn get_sprite(&self) -> Sprite;
}
struct Sprite {
/// Name of the sprite to draw
texture: SpriteTexture,
/// This object's position, in world coordinates.
pos: Point2<Pfloat>,
/// The size of this sprite,
/// given as height in world units.
size: Pfloat,
/// Scale factor.
/// if this is 1, sprite height is exactly self.size.
scale: Pfloat,
/// This sprite's rotation
/// (relative to north, measured ccw)
angle: Deg<Pfloat>,
/// Parallax factor.
/// More positive => farther away
/// More negative => closer
/// Zero: no parallax.
parallax: Pfloat,
}
struct Game {
input: InputStatus,
last_update: Instant,
player: Ship,
system: System,
camera: Camera,
}
impl Game {
fn new(ct: Content) -> Self {
Game {
last_update: Instant::now(),
input: InputStatus::new(),
player: Ship::new(ShipKind::Gypsum, (0.0, 0.0).into()),
camera: Camera {
pos: (0.0, 0.0).into(),
zoom: 500.0,
},
system: System::new(&ct.systems[0]),
}
}
fn process_key(&mut self, state: &ElementState, key: &VirtualKeyCode) {
self.input.process_key(state, key)
}
fn process_click(&mut self, state: &ElementState, key: &MouseButton) {
self.input.process_click(state, key)
}
fn process_scroll(&mut self, delta: &MouseScrollDelta, phase: &TouchPhase) {
self.input.process_scroll(delta, phase)
}
fn update(&mut self) {
let t: Pfloat = self.last_update.elapsed().as_secs_f32();
if self.input.key_thrust {
self.player.body.thrust(50.0 * t);
}
if self.input.key_right {
self.player.body.rot(Deg(35.0) * t);
}
if self.input.key_left {
self.player.body.rot(Deg(-35.0) * t);
}
if self.input.v_scroll != 0.0 {
self.camera.zoom = (self.camera.zoom + self.input.v_scroll).clamp(ZOOM_MIN, ZOOM_MAX);
self.input.v_scroll = 0.0;
}
println!("{:?}", self.player.body.pos);
self.player.body.tick(t);
self.camera.pos = self.player.body.pos;
self.last_update = Instant::now();
}
fn get_sprites(&self) -> Vec<Sprite> {
let mut sprites: Vec<Sprite> = Vec::new();
sprites.append(&mut self.system.get_sprites());
sprites.push(self.player.get_sprite());
// Make sure sprites are drawn in the correct order
// (note the reversed a, b in the comparator)
//
// TODO: use a gpu depth buffer with parallax as z-coordinate?
// Might be overkill.
sprites.sort_by(|a, b| b.parallax.total_cmp(&a.parallax));
return sprites;
}
}
async fn run(mut game: Game) -> Result<()> {
let event_loop = EventLoop::new(); let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap(); let window = WindowBuilder::new().build(&event_loop).unwrap();
let mut gpu = GPUState::new(window).await?; let mut gpu = render::GPUState::new(window).await?;
gpu.update_starfield_buffer(&game); gpu.update_starfield_buffer(&game);
@ -229,12 +84,3 @@ async fn run(mut game: Game) -> Result<()> {
} }
}); });
} }
fn main() -> Result<()> {
let content = content::load_content_dir("./content")?;
println!("{:?}", content);
let game = Game::new(content);
pollster::block_on(run(game))?;
return Ok(());
}

18
src/render/consts.rs Normal file
View File

@ -0,0 +1,18 @@
use crate::consts;
// We can draw at most this many sprites on the screen.
// TODO: compile-time option
pub const SPRITE_INSTANCE_LIMIT: u64 = 100;
// Must be small enough to fit in an i32
pub const STARFIELD_INSTANCE_LIMIT: u64 = consts::STARFIELD_COUNT * 24;
/// Texture index file name
pub const TEXTURE_INDEX_NAME: &'static str = "_index.toml";
/// Texture index dir
pub const TEXTURE_INDEX_PATH: &'static str = "./assets";
/// Shader entry points
pub const SHADER_MAIN_VERTEX: &'static str = "vertex_main";
pub const SHADER_MAIN_FRAGMENT: &'static str = "fragment_main";

View File

@ -5,18 +5,18 @@ use std::{iter, rc::Rc};
use wgpu; use wgpu;
use winit::{self, dpi::PhysicalSize, window::Window}; use winit::{self, dpi::PhysicalSize, window::Window};
use crate::{Game, STARFIELD_COUNT, STARFIELD_PARALLAX_MIN, STARFIELD_SIZE, ZOOM_MAX, ZOOM_MIN};
use super::{ use super::{
consts::{SPRITE_INSTANCE_LIMIT, STARFIELD_INSTANCE_LIMIT},
globaldata::{GlobalData, GlobalDataContent}, globaldata::{GlobalData, GlobalDataContent},
pipeline::PipelineBuilder, pipeline::PipelineBuilder,
texturearray::TextureArray, texturearray::TextureArray,
vertexbuffer::{ vertexbuffer::{
data::{SPRITE_INDICES, SPRITE_VERTICES}, consts::{SPRITE_INDICES, SPRITE_VERTICES},
types::{SpriteInstance, StarfieldInstance, TexturedVertex}, types::{SpriteInstance, StarfieldInstance, TexturedVertex},
VertexBuffer, VertexBuffer,
}, },
}; };
use crate::{consts, game::Game};
pub struct GPUState { pub struct GPUState {
device: wgpu::Device, device: wgpu::Device,
@ -43,13 +43,6 @@ struct VertexBuffers {
} }
impl GPUState { impl GPUState {
// We can draw at most this many sprites on the screen.
// TODO: compile-time option
pub const SPRITE_INSTANCE_LIMIT: u64 = 100;
// Must be small enough to fit in an i32
pub const STARFIELD_INSTANCE_LIMIT: u64 = STARFIELD_COUNT * 24;
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();
let window_aspect = window_size.width as f32 / window_size.height as f32; let window_aspect = window_size.width as f32 / window_size.height as f32;
@ -119,7 +112,7 @@ impl GPUState {
&device, &device,
Some(SPRITE_VERTICES), Some(SPRITE_VERTICES),
Some(SPRITE_INDICES), Some(SPRITE_INDICES),
Self::SPRITE_INSTANCE_LIMIT, SPRITE_INSTANCE_LIMIT,
)), )),
starfield: Rc::new(VertexBuffer::new::<TexturedVertex, StarfieldInstance>( starfield: Rc::new(VertexBuffer::new::<TexturedVertex, StarfieldInstance>(
@ -127,7 +120,7 @@ impl GPUState {
&device, &device,
Some(SPRITE_VERTICES), Some(SPRITE_VERTICES),
Some(SPRITE_INDICES), Some(SPRITE_INDICES),
Self::STARFIELD_INSTANCE_LIMIT, STARFIELD_INSTANCE_LIMIT,
)), )),
}; };
@ -217,8 +210,8 @@ impl GPUState {
for s in game.get_sprites() { for s in game.get_sprites() {
// Parallax is computed here, so we can check if this sprite is visible. // Parallax is computed here, so we can check if this sprite is visible.
let pos = let pos = (s.pos - game.camera.pos.to_vec())
(s.pos - game.camera.pos.to_vec()) / (s.parallax + game.camera.zoom / ZOOM_MIN); / (s.parallax + game.camera.zoom / consts::ZOOM_MIN);
let texture = self.texture_array.get_sprite_texture(s.texture); let texture = self.texture_array.get_sprite_texture(s.texture);
// Game dimensions of this sprite post-scale. // Game dimensions of this sprite post-scale.
@ -246,7 +239,7 @@ impl GPUState {
} }
// Enforce sprite limit // Enforce sprite limit
if instances.len() as u64 > Self::SPRITE_INSTANCE_LIMIT { if instances.len() as u64 > SPRITE_INSTANCE_LIMIT {
// TODO: no panic, handle this better. // TODO: no panic, handle this better.
unreachable!("Sprite limit exceeded!") unreachable!("Sprite limit exceeded!")
} }
@ -259,17 +252,17 @@ impl GPUState {
/// ///
/// Starfield data rarely changes, so this is called only when it's needed. /// Starfield data rarely changes, so this is called only when it's needed.
pub fn update_starfield_buffer(&mut self, game: &Game) { pub fn update_starfield_buffer(&mut self, game: &Game) {
let sz = STARFIELD_SIZE as f32; let sz = consts::STARFIELD_SIZE as f32;
// Compute window size in starfield tiles // Compute window size in starfield tiles
let mut nw_tile: Point2<i32> = { let mut nw_tile: Point2<i32> = {
// Game coordinates (relative to camera) of nw corner of screen. // Game coordinates (relative to camera) of nw corner of screen.
let clip_nw = Point2::from((self.window_aspect, 1.0)) * ZOOM_MAX; let clip_nw = Point2::from((self.window_aspect, 1.0)) * consts::ZOOM_MAX;
// Parallax correction. // Parallax correction.
// Also, adjust v for mod to work properly // Also, adjust v for mod to work properly
// (v is centered at 0) // (v is centered at 0)
let v: Point2<f32> = clip_nw * STARFIELD_PARALLAX_MIN; let v: Point2<f32> = clip_nw * consts::STARFIELD_PARALLAX_MIN;
let v_adj: Point2<f32> = (v.x + (sz / 2.0), v.y + (sz / 2.0)).into(); let v_adj: Point2<f32> = (v.x + (sz / 2.0), v.y + (sz / 2.0)).into();
#[rustfmt::skip] #[rustfmt::skip]
@ -293,8 +286,8 @@ impl GPUState {
// Truncate tile grid to buffer size // Truncate tile grid to buffer size
// (The window won't be full of stars if our instance limit is too small) // (The window won't be full of stars if our instance limit is too small)
while ((nw_tile.x * 2 + 1) * (nw_tile.y * 2 + 1) * STARFIELD_COUNT as i32) while ((nw_tile.x * 2 + 1) * (nw_tile.y * 2 + 1) * consts::STARFIELD_COUNT as i32)
> Self::STARFIELD_INSTANCE_LIMIT as i32 > STARFIELD_INSTANCE_LIMIT as i32
{ {
nw_tile -= Vector2::from((1, 1)); nw_tile -= Vector2::from((1, 1));
} }
@ -319,7 +312,7 @@ impl GPUState {
} }
// Enforce starfield limit // Enforce starfield limit
if instances.len() as u64 > Self::STARFIELD_INSTANCE_LIMIT { if instances.len() as u64 > STARFIELD_INSTANCE_LIMIT {
unreachable!("Starfield limit exceeded!") unreachable!("Starfield limit exceeded!")
} }
@ -377,7 +370,7 @@ impl GPUState {
], ],
window_aspect: [self.window_aspect, 0.0], window_aspect: [self.window_aspect, 0.0],
starfield_texture: [self.texture_array.get_starfield_texture().index, 0], starfield_texture: [self.texture_array.get_starfield_texture().index, 0],
starfield_tile_size: [STARFIELD_SIZE as f32, 0.0], starfield_tile_size: [consts::STARFIELD_SIZE as f32, 0.0],
}]), }]),
); );

View File

@ -1,19 +1,22 @@
mod consts;
mod globaldata; mod globaldata;
mod gpustate; mod gpustate;
mod pipeline; mod pipeline;
mod sprite;
mod texturearray; mod texturearray;
mod vertexbuffer; mod vertexbuffer;
pub use gpustate::GPUState; pub use gpustate::GPUState;
pub use sprite::{Sprite, Spriteable};
/// A handle to a sprite texture /// A handle to a sprite texture
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SpriteTexture(pub String); pub struct SpriteTexture(pub String);
/*
// API correction matrix. // API correction matrix.
// cgmath uses OpenGL's matrix format, which // cgmath uses OpenGL's matrix format, which
// needs to be converted to wgpu's matrix format. // 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,

View File

@ -1,6 +1,7 @@
use std::rc::Rc; use std::rc::Rc;
use wgpu; use wgpu;
use super::consts::{SHADER_MAIN_FRAGMENT, SHADER_MAIN_VERTEX};
use super::vertexbuffer::VertexBuffer; use super::vertexbuffer::VertexBuffer;
pub struct PipelineBuilder<'a> { pub struct PipelineBuilder<'a> {
@ -19,9 +20,6 @@ pub struct PipelineBuilder<'a> {
} }
impl<'a> PipelineBuilder<'a> { impl<'a> PipelineBuilder<'a> {
const VERTEX_MAIN: &'static str = "vertex_shader_main";
const FRAGMENT_MAIN: &'static str = "fragment_shader_main";
pub fn new(label: &'a str, device: &'a wgpu::Device) -> Self { pub fn new(label: &'a str, device: &'a wgpu::Device) -> Self {
Self { Self {
label, label,
@ -98,13 +96,13 @@ impl<'a> PipelineBuilder<'a> {
vertex: wgpu::VertexState { vertex: wgpu::VertexState {
module: &shader, module: &shader,
entry_point: Self::VERTEX_MAIN, entry_point: SHADER_MAIN_VERTEX,
buffers: &self.vertex_buffer.unwrap().layout, buffers: &self.vertex_buffer.unwrap().layout,
}, },
fragment: Some(wgpu::FragmentState { fragment: Some(wgpu::FragmentState {
module: &shader, module: &shader,
entry_point: Self::FRAGMENT_MAIN, entry_point: SHADER_MAIN_FRAGMENT,
targets: &[Some(wgpu::ColorTargetState { targets: &[Some(wgpu::ColorTargetState {
format: self.format.unwrap(), format: self.format.unwrap(),
blend: Some(wgpu::BlendState::ALPHA_BLENDING), blend: Some(wgpu::BlendState::ALPHA_BLENDING),

View File

@ -1,4 +1,3 @@
// Vertex shader
struct InstanceInput { struct InstanceInput {
@location(2) rotation_matrix_0: vec2<f32>, @location(2) rotation_matrix_0: vec2<f32>,
@location(3) rotation_matrix_1: vec2<f32>, @location(3) rotation_matrix_1: vec2<f32>,
@ -32,8 +31,16 @@ struct GlobalUniform {
}; };
@group(0) @binding(0)
var texture_array: binding_array<texture_2d<f32>>;
@group(0) @binding(1)
var sampler_array: binding_array<sampler>;
@vertex @vertex
fn vertex_shader_main( fn vertex_main(
vertex: VertexInput, vertex: VertexInput,
instance: InstanceInput, instance: InstanceInput,
) -> VertexOutput { ) -> VertexOutput {
@ -71,15 +78,8 @@ fn vertex_shader_main(
} }
// Fragment shader
@group(0) @binding(0)
var texture_array: binding_array<texture_2d<f32>>;
@group(0) @binding(1)
var sampler_array: binding_array<sampler>;
@fragment @fragment
fn fragment_shader_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.index],
sampler_array[in.index], sampler_array[in.index],

View File

@ -1,4 +1,3 @@
// Vertex shader
struct InstanceInput { struct InstanceInput {
@location(2) position: vec2<f32>, @location(2) position: vec2<f32>,
@location(3) parallax: f32, @location(3) parallax: f32,
@ -29,12 +28,20 @@ struct GlobalUniform {
}; };
@group(0) @binding(0)
var texture_array: binding_array<texture_2d<f32>>;
@group(0) @binding(1)
var sampler_array: binding_array<sampler>;
fn fmod(x: vec2<f32>, m: f32) -> vec2<f32> { fn fmod(x: vec2<f32>, m: f32) -> vec2<f32> {
return x - floor(x / m) * m; return x - floor(x / m) * m;
} }
@vertex @vertex
fn vertex_shader_main( fn vertex_main(
vertex: VertexInput, vertex: VertexInput,
instance: InstanceInput, instance: InstanceInput,
) -> VertexOutput { ) -> VertexOutput {
@ -95,15 +102,8 @@ fn vertex_shader_main(
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 @fragment
fn fragment_shader_main(in: VertexOutput) -> @location(0) vec4<f32> { fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> {
let b = 0.8 - (in.tint.x / 1.5); // brightness let b = 0.8 - (in.tint.x / 1.5); // brightness
let c = in.tint.y; // color let c = in.tint.y; // color
// TODO: saturation // TODO: saturation

33
src/render/sprite.rs Normal file
View File

@ -0,0 +1,33 @@
use cgmath::{Deg, Point2};
use super::SpriteTexture;
use crate::physics::Pfloat;
pub struct Sprite {
/// Name of the sprite to draw
pub texture: SpriteTexture,
/// This object's position, in world coordinates.
pub pos: Point2<Pfloat>,
/// The size of this sprite,
/// given as height in world units.
pub size: Pfloat,
/// Scale factor.
/// if this is 1, sprite height is exactly self.size.
pub scale: Pfloat,
/// This sprite's rotation
/// (relative to north, measured ccw)
pub angle: Deg<Pfloat>,
/// Parallax factor.
/// Corresponds to z-distance, and affects
/// position and scale.
pub parallax: Pfloat,
}
pub trait Spriteable {
fn get_sprite(&self) -> Sprite;
}

View File

@ -2,10 +2,11 @@ use anyhow::Result;
use std::{collections::HashMap, num::NonZeroU32, path::PathBuf}; use std::{collections::HashMap, num::NonZeroU32, path::PathBuf};
use wgpu::BindGroupLayout; use wgpu::BindGroupLayout;
use super::{
super::consts::TEXTURE_INDEX_PATH, loader::TextureLoader, Texture, TextureArrayConfig,
};
use crate::render::SpriteTexture; use crate::render::SpriteTexture;
use super::{loader::TextureLoader, Texture, TextureArrayConfig};
pub struct TextureArray { pub struct TextureArray {
pub bind_group: wgpu::BindGroup, pub bind_group: wgpu::BindGroup,
pub bind_group_layout: BindGroupLayout, pub bind_group_layout: BindGroupLayout,
@ -14,8 +15,6 @@ pub struct TextureArray {
} }
impl TextureArray { impl TextureArray {
const INDEX_PATH: &'static str = "assets";
pub fn get_starfield_texture(&self) -> Texture { pub fn get_starfield_texture(&self) -> Texture {
return self.get_texture(&self.config.starfield); return self.get_texture(&self.config.starfield);
} }
@ -33,7 +32,7 @@ impl TextureArray {
pub fn new(device: &wgpu::Device, queue: &wgpu::Queue) -> Result<Self> { pub fn new(device: &wgpu::Device, queue: &wgpu::Queue) -> Result<Self> {
// Load all textures // Load all textures
let loader = TextureLoader::load(device, queue, &PathBuf::from(Self::INDEX_PATH))?; let loader = TextureLoader::load(device, queue, &PathBuf::from(TEXTURE_INDEX_PATH))?;
let sampler = device.create_sampler(&wgpu::SamplerDescriptor { let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_u: wgpu::AddressMode::ClampToEdge,

View File

@ -1,7 +1,9 @@
use anyhow::Result; use anyhow::Result;
use std::{collections::HashMap, fs::File, io::Read, path::Path, path::PathBuf}; use std::{collections::HashMap, fs::File, io::Read, path::Path, path::PathBuf};
use super::{rawtexture::RawTexture, Texture, TextureArrayConfig}; use super::{
super::consts::TEXTURE_INDEX_NAME, rawtexture::RawTexture, Texture, TextureArrayConfig,
};
mod fields { mod fields {
@ -64,12 +66,10 @@ pub struct TextureLoader {
} }
impl TextureLoader { impl TextureLoader {
const INDEX_NAME: &'static str = "_index.toml";
pub fn load(device: &wgpu::Device, queue: &wgpu::Queue, index_path: &Path) -> Result<Self> { pub fn load(device: &wgpu::Device, queue: &wgpu::Queue, index_path: &Path) -> Result<Self> {
let index: fields::Index = { let index: fields::Index = {
let mut texture_index_string = String::new(); let mut texture_index_string = String::new();
let _ = File::open(index_path.join(Self::INDEX_NAME))? let _ = File::open(index_path.join(TEXTURE_INDEX_NAME))?
.read_to_string(&mut texture_index_string); .read_to_string(&mut texture_index_string);
toml::from_str(&texture_index_string)? toml::from_str(&texture_index_string)?
}; };
@ -79,6 +79,7 @@ impl TextureLoader {
Self::inner_load_textures(device, queue, index, index_path, "".to_string())?; Self::inner_load_textures(device, queue, index, index_path, "".to_string())?;
// TODO: validate configuration // TODO: validate configuration
// TODO: handle error states
let mut textures = HashMap::new(); let mut textures = HashMap::new();
let mut texture_data = Vec::new(); let mut texture_data = Vec::new();
@ -157,7 +158,7 @@ impl TextureLoader {
let sub_root = texture_root.join(&path); let sub_root = texture_root.join(&path);
let index: fields::SubIndex = { let index: fields::SubIndex = {
let mut texture_index_string = String::new(); let mut texture_index_string = String::new();
let _ = File::open(sub_root.join(Self::INDEX_NAME))? let _ = File::open(sub_root.join(TEXTURE_INDEX_NAME))?
.read_to_string(&mut texture_index_string); .read_to_string(&mut texture_index_string);
toml::from_str(&texture_index_string)? toml::from_str(&texture_index_string)?
}; };

View File

@ -1,4 +1,4 @@
pub mod data; pub mod consts;
pub mod types; pub mod types;
mod vertexbuffer; mod vertexbuffer;