Cleanup
parent
fd94cd6701
commit
abd41af202
|
@ -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";
|
|
@ -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,
|
||||||
|
}
|
|
@ -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,
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
@ -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,
|
|
@ -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),
|
174
src/main.rs
174
src/main.rs
|
@ -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(());
|
|
||||||
}
|
|
||||||
|
|
|
@ -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";
|
|
@ -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],
|
||||||
}]),
|
}]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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)?
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
pub mod data;
|
pub mod consts;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
mod vertexbuffer;
|
mod vertexbuffer;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue