Compare commits
4 Commits
10e1ef8107
...
0af265040c
Author | SHA1 | Date |
---|---|---|
Mark | 0af265040c | |
Mark | 370d4c9bc9 | |
Mark | e5d399c3c5 | |
Mark | 1bd78d3cfd |
2
assets
2
assets
|
@ -1 +1 @@
|
||||||
Subproject commit 7b00da4f1971908d389d906fe537bfecd3d03b50
|
Subproject commit 7f9886acae8ab62827821ba4f5271689f9a67d4d
|
|
@ -0,0 +1,7 @@
|
||||||
|
# content type: ship
|
||||||
|
[ship]
|
||||||
|
name = "Gypsum"
|
||||||
|
sprite = "ship::gypsum"
|
||||||
|
size = 100
|
||||||
|
|
||||||
|
engines = [{ x = 0.0, y = -105, size = 50.0 }]
|
|
@ -6,28 +6,29 @@ use super::{syntax, ContentType};
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Content {
|
pub struct Content {
|
||||||
pub systems: Vec<syntax::system::System>,
|
pub systems: Vec<syntax::system::System>,
|
||||||
|
pub ships: Vec<syntax::ship::Ship>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Content {
|
impl Content {
|
||||||
pub fn new(cv: Vec<(PathBuf, ContentType)>) -> Result<Self> {
|
pub fn new(cv: Vec<(PathBuf, ContentType)>) -> Result<Self> {
|
||||||
let mut content = Self {
|
let mut systems = Vec::new();
|
||||||
systems: Vec::new(),
|
let mut ships = Vec::new();
|
||||||
};
|
|
||||||
|
|
||||||
// These methods check intra-file consistency
|
// These methods check intra-file consistency
|
||||||
for (p, c) in cv {
|
for (p, c) in cv {
|
||||||
match c {
|
match c {
|
||||||
ContentType::System(v) => content
|
ContentType::System(v) => systems.push(
|
||||||
.add_system(v)
|
syntax::system::System::parse(v)
|
||||||
.with_context(|| format!("Could not parse {}", p.display()))?,
|
.with_context(|| format!("Could not parse {}", p.display()))?,
|
||||||
};
|
),
|
||||||
|
|
||||||
|
ContentType::Ship(v) => ships.push(
|
||||||
|
syntax::ship::Ship::parse(v)
|
||||||
|
.with_context(|| format!("Could not parse {}", p.display()))?,
|
||||||
|
),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(content);
|
return Ok(Self { systems, ships });
|
||||||
}
|
|
||||||
|
|
||||||
fn add_system(&mut self, toml: syntax::system::toml::SystemRoot) -> Result<()> {
|
|
||||||
self.systems.push(syntax::system::System::parse(toml)?);
|
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ use super::syntax;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ContentType {
|
pub enum ContentType {
|
||||||
System(syntax::system::toml::SystemRoot),
|
System(syntax::system::toml::SystemRoot),
|
||||||
|
Ship(syntax::ship::toml::ShipRoot),
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check content without loading game
|
// TODO: check content without loading game
|
||||||
|
@ -26,6 +27,7 @@ impl ContentType {
|
||||||
|
|
||||||
return Ok(match &type_spec[..] {
|
return Ok(match &type_spec[..] {
|
||||||
"system" => Some(Self::System(toml::from_str(&file_string)?)),
|
"system" => Some(Self::System(toml::from_str(&file_string)?)),
|
||||||
|
"ship" => Some(Self::Ship(toml::from_str(&file_string)?)),
|
||||||
_ => bail!("Invalid content type `{}`", type_spec),
|
_ => bail!("Invalid content type `{}`", type_spec),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ mod syntax;
|
||||||
|
|
||||||
pub use content::Content;
|
pub use content::Content;
|
||||||
pub use contenttype::ContentType;
|
pub use contenttype::ContentType;
|
||||||
|
pub use syntax::ship;
|
||||||
pub use syntax::system;
|
pub use syntax::system;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
pub mod ship;
|
||||||
pub mod system;
|
pub mod system;
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use cgmath::Point2;
|
||||||
|
|
||||||
|
/// Toml file syntax
|
||||||
|
pub(in crate::content) mod toml {
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct ShipRoot {
|
||||||
|
pub ship: Ship,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Ship {
|
||||||
|
pub name: String,
|
||||||
|
pub sprite: String,
|
||||||
|
pub size: f32,
|
||||||
|
pub engines: Vec<Engine>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Engine {
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
pub size: f32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Ship {
|
||||||
|
pub name: String,
|
||||||
|
pub sprite: String,
|
||||||
|
pub size: f32,
|
||||||
|
pub engines: Vec<Engine>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Engine {
|
||||||
|
pub pos: Point2<f32>,
|
||||||
|
pub size: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ship {
|
||||||
|
pub fn parse(value: toml::ShipRoot) -> Result<Self> {
|
||||||
|
return Ok(Self {
|
||||||
|
name: value.ship.name,
|
||||||
|
sprite: value.ship.sprite,
|
||||||
|
size: value.ship.size,
|
||||||
|
engines: value
|
||||||
|
.ship
|
||||||
|
.engines
|
||||||
|
.iter()
|
||||||
|
.map(|e| Engine {
|
||||||
|
pos: Point2 { x: e.x, y: e.y },
|
||||||
|
size: e.size,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,13 +2,14 @@ use cgmath::Deg;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
|
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
|
||||||
|
|
||||||
use super::{ship::ShipKind, Camera, InputStatus, Ship, System};
|
use super::{Camera, InputStatus, Ship, System};
|
||||||
use crate::{consts, content::Content, render::Sprite, render::Spriteable};
|
use crate::{consts, content::Content, render::Sprite, render::Spriteable};
|
||||||
|
|
||||||
pub struct Game {
|
pub struct Game {
|
||||||
pub input: InputStatus,
|
pub input: InputStatus,
|
||||||
pub last_update: Instant,
|
pub last_update: Instant,
|
||||||
pub player: Ship,
|
pub player: Ship,
|
||||||
|
pub test: Ship,
|
||||||
pub system: System,
|
pub system: System,
|
||||||
pub camera: Camera,
|
pub camera: Camera,
|
||||||
paused: bool,
|
paused: bool,
|
||||||
|
@ -20,7 +21,7 @@ impl Game {
|
||||||
Game {
|
Game {
|
||||||
last_update: Instant::now(),
|
last_update: Instant::now(),
|
||||||
input: InputStatus::new(),
|
input: InputStatus::new(),
|
||||||
player: Ship::new(ShipKind::Gypsum, (0.0, 0.0).into()),
|
player: Ship::new(&ct.ships[0], (0.0, 0.0).into()),
|
||||||
camera: Camera {
|
camera: Camera {
|
||||||
pos: (0.0, 0.0).into(),
|
pos: (0.0, 0.0).into(),
|
||||||
zoom: 500.0,
|
zoom: 500.0,
|
||||||
|
@ -28,6 +29,7 @@ impl Game {
|
||||||
system: System::new(&ct.systems[0]),
|
system: System::new(&ct.systems[0]),
|
||||||
paused: false,
|
paused: false,
|
||||||
time_scale: 1.0,
|
time_scale: 1.0,
|
||||||
|
test: Ship::new(&ct.ships[0], (100.0, 100.0).into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +57,7 @@ impl Game {
|
||||||
pub fn update(&mut self) {
|
pub fn update(&mut self) {
|
||||||
let t: f32 = self.last_update.elapsed().as_secs_f32() * self.time_scale;
|
let t: f32 = self.last_update.elapsed().as_secs_f32() * self.time_scale;
|
||||||
|
|
||||||
|
self.player.engines_on = self.input.key_thrust;
|
||||||
if self.input.key_thrust {
|
if self.input.key_thrust {
|
||||||
self.player.physicsbody.thrust(50.0 * t);
|
self.player.physicsbody.thrust(50.0 * t);
|
||||||
}
|
}
|
||||||
|
@ -84,11 +87,13 @@ impl Game {
|
||||||
|
|
||||||
sprites.append(&mut self.system.get_sprites());
|
sprites.append(&mut self.system.get_sprites());
|
||||||
sprites.push(self.player.get_sprite());
|
sprites.push(self.player.get_sprite());
|
||||||
|
sprites.push(self.test.get_sprite());
|
||||||
|
|
||||||
// Make sure sprites are drawn in the correct order
|
// Make sure sprites are drawn in the correct order
|
||||||
// (note the reversed a, b in the comparator)
|
// (note the reversed a, b in the comparator)
|
||||||
//
|
//
|
||||||
// TODO: use a gpu depth buffer instead.
|
// TODO: maybe use a gpu depth buffer instead?
|
||||||
|
// I've tried this, but it doesn't seem to work with transparent textures.
|
||||||
sprites.sort_by(|a, b| b.pos.z.total_cmp(&a.pos.z));
|
sprites.sort_by(|a, b| b.pos.z.total_cmp(&a.pos.z));
|
||||||
|
|
||||||
return sprites;
|
return sprites;
|
||||||
|
|
|
@ -1,49 +1,63 @@
|
||||||
use cgmath::Point2;
|
use cgmath::{Deg, Point2, Point3};
|
||||||
|
|
||||||
use crate::{physics::PhysicsBody, render::Sprite, render::SpriteTexture, render::Spriteable};
|
use crate::{
|
||||||
|
content::{self, ship::Engine},
|
||||||
pub enum ShipKind {
|
physics::PhysicsBody,
|
||||||
Gypsum,
|
render::Sprite,
|
||||||
}
|
render::SpriteTexture,
|
||||||
|
render::Spriteable,
|
||||||
impl ShipKind {
|
};
|
||||||
fn sprite(&self) -> SpriteTexture {
|
|
||||||
let name = match self {
|
|
||||||
Self::Gypsum => "ship::gypsum",
|
|
||||||
};
|
|
||||||
|
|
||||||
return SpriteTexture(name.to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn size(&self) -> f32 {
|
|
||||||
match self {
|
|
||||||
Self::Gypsum => 100.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Ship {
|
pub struct Ship {
|
||||||
pub physicsbody: PhysicsBody,
|
pub physicsbody: PhysicsBody,
|
||||||
kind: ShipKind,
|
pub engines_on: bool,
|
||||||
|
|
||||||
|
sprite: SpriteTexture,
|
||||||
|
size: f32,
|
||||||
|
engines: Vec<Engine>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ship {
|
impl Ship {
|
||||||
pub fn new(kind: ShipKind, pos: Point2<f32>) -> Self {
|
pub fn new(ct: &content::ship::Ship, pos: Point2<f32>) -> Self {
|
||||||
Ship {
|
Ship {
|
||||||
physicsbody: PhysicsBody::new(pos),
|
physicsbody: PhysicsBody::new(pos),
|
||||||
kind,
|
sprite: SpriteTexture(ct.sprite.clone()),
|
||||||
|
size: ct.size,
|
||||||
|
engines: ct.engines.clone(),
|
||||||
|
engines_on: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Spriteable for Ship {
|
impl Spriteable for Ship {
|
||||||
fn get_sprite(&self) -> Sprite {
|
fn get_sprite(&self) -> Sprite {
|
||||||
return Sprite {
|
let engines = if self.engines_on {
|
||||||
pos: (self.physicsbody.pos.x, self.physicsbody.pos.y, 1.0).into(),
|
Some(
|
||||||
texture: self.kind.sprite(),
|
self.engines
|
||||||
angle: self.physicsbody.angle,
|
.iter()
|
||||||
scale: 1.0,
|
.map(|e| Sprite {
|
||||||
size: self.kind.size(),
|
pos: Point3 {
|
||||||
|
x: e.pos.x,
|
||||||
|
y: e.pos.y,
|
||||||
|
z: 1.0,
|
||||||
|
},
|
||||||
|
texture: SpriteTexture("flare::ion".to_owned()),
|
||||||
|
angle: Deg(0.0),
|
||||||
|
size: e.size,
|
||||||
|
children: None,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Sprite {
|
||||||
|
pos: (self.physicsbody.pos.x, self.physicsbody.pos.y, 1.0).into(),
|
||||||
|
texture: self.sprite.clone(), // TODO: sprite texture should be easy to clone
|
||||||
|
angle: self.physicsbody.angle,
|
||||||
|
size: self.size,
|
||||||
|
children: engines,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use cgmath::{Point3, Vector2};
|
||||||
use rand::{self, Rng};
|
use rand::{self, Rng};
|
||||||
|
|
||||||
use super::SystemObject;
|
use super::SystemObject;
|
||||||
use crate::{consts, content, render::Sprite, render::SpriteTexture, render::Spriteable};
|
use crate::{consts, content, render::Sprite, render::SpriteTexture};
|
||||||
|
|
||||||
pub struct StarfieldStar {
|
pub struct StarfieldStar {
|
||||||
/// Star coordinates, in world space.
|
/// Star coordinates, in world space.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use cgmath::{Deg, Point3};
|
use cgmath::{Deg, Point3};
|
||||||
|
|
||||||
use crate::{render::Sprite, render::SpriteTexture, render::Spriteable};
|
use crate::{render::Sprite, render::SpriteTexture};
|
||||||
|
|
||||||
pub struct SystemObject {
|
pub struct SystemObject {
|
||||||
pub sprite: SpriteTexture,
|
pub sprite: SpriteTexture,
|
||||||
|
@ -9,14 +9,14 @@ pub struct SystemObject {
|
||||||
pub angle: Deg<f32>,
|
pub angle: Deg<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Spriteable for SystemObject {
|
impl SystemObject {
|
||||||
fn get_sprite(&self) -> Sprite {
|
pub(super) fn get_sprite(&self) -> Sprite {
|
||||||
return Sprite {
|
return Sprite {
|
||||||
texture: self.sprite.clone(),
|
texture: self.sprite.clone(),
|
||||||
scale: 1.0,
|
|
||||||
pos: self.pos,
|
pos: self.pos,
|
||||||
angle: self.angle,
|
angle: self.angle,
|
||||||
size: self.size,
|
size: self.size,
|
||||||
|
children: None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::consts;
|
use crate::consts;
|
||||||
|
use cgmath::Matrix4;
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -16,3 +17,11 @@ pub const TEXTURE_INDEX_PATH: &'static str = "./assets";
|
||||||
/// Shader entry points
|
/// Shader entry points
|
||||||
pub const SHADER_MAIN_VERTEX: &'static str = "vertex_main";
|
pub const SHADER_MAIN_VERTEX: &'static str = "vertex_main";
|
||||||
pub const SHADER_MAIN_FRAGMENT: &'static str = "fragment_main";
|
pub const SHADER_MAIN_FRAGMENT: &'static str = "fragment_main";
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub const OPENGL_TO_WGPU_MATRIX: Matrix4<f32> = Matrix4::new(
|
||||||
|
1.0, 0.0, 0.0, 0.0,
|
||||||
|
0.0, 1.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.5, 0.5,
|
||||||
|
0.0, 0.0, 0.0, 1.0,
|
||||||
|
);
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bytemuck;
|
use bytemuck;
|
||||||
use cgmath::{EuclideanSpace, Matrix2, Point2, Vector2, Vector3};
|
use cgmath::{Deg, EuclideanSpace, Matrix4, Point2, Vector2, Vector3};
|
||||||
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};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
consts::{SPRITE_INSTANCE_LIMIT, STARFIELD_INSTANCE_LIMIT},
|
consts::{OPENGL_TO_WGPU_MATRIX, SPRITE_INSTANCE_LIMIT, STARFIELD_INSTANCE_LIMIT},
|
||||||
globaldata::{GlobalData, GlobalDataContent},
|
globaldata::{GlobalData, GlobalDataContent},
|
||||||
pipeline::PipelineBuilder,
|
pipeline::PipelineBuilder,
|
||||||
texturearray::TextureArray,
|
texturearray::TextureArray,
|
||||||
|
@ -15,6 +15,7 @@ use super::{
|
||||||
types::{SpriteInstance, StarfieldInstance, TexturedVertex},
|
types::{SpriteInstance, StarfieldInstance, TexturedVertex},
|
||||||
VertexBuffer,
|
VertexBuffer,
|
||||||
},
|
},
|
||||||
|
Sprite,
|
||||||
};
|
};
|
||||||
use crate::{consts, game::Game};
|
use crate::{consts, game::Game};
|
||||||
|
|
||||||
|
@ -194,6 +195,138 @@ impl GPUState {
|
||||||
self.update_starfield_buffer(game)
|
self.update_starfield_buffer(game)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a SpriteInstance for s and add it to instances.
|
||||||
|
/// Also handles child sprites.
|
||||||
|
fn push_sprite(
|
||||||
|
&self,
|
||||||
|
game: &Game,
|
||||||
|
instances: &mut Vec<SpriteInstance>,
|
||||||
|
clip_ne: Point2<f32>,
|
||||||
|
clip_sw: Point2<f32>,
|
||||||
|
s: Sprite,
|
||||||
|
) {
|
||||||
|
// Position adjusted for parallax
|
||||||
|
// TODO: adjust parallax for zoom?
|
||||||
|
let pos: Point2<f32> = {
|
||||||
|
(Point2 {
|
||||||
|
x: s.pos.x,
|
||||||
|
y: s.pos.y,
|
||||||
|
} - game.camera.pos.to_vec())
|
||||||
|
/ s.pos.z
|
||||||
|
};
|
||||||
|
let texture = self.texture_array.get_sprite_texture(s.texture);
|
||||||
|
|
||||||
|
// Game dimensions of this sprite post-scale.
|
||||||
|
// Don't divide by 2, we use this later.
|
||||||
|
let height = s.size / s.pos.z;
|
||||||
|
let width = height * texture.aspect;
|
||||||
|
|
||||||
|
// Don't draw (or compute matrices for)
|
||||||
|
// sprites that are off the screen
|
||||||
|
if pos.x < clip_ne.x - width
|
||||||
|
|| pos.y > clip_ne.y + height
|
||||||
|
|| pos.x > clip_sw.x + width
|
||||||
|
|| pos.y < clip_sw.y - height
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: clean up
|
||||||
|
let scale = height / game.camera.zoom;
|
||||||
|
|
||||||
|
// 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(texture.aspect * scale, scale, 1.0);
|
||||||
|
|
||||||
|
// Apply rotation
|
||||||
|
let rotate = Matrix4::from_angle_z(s.angle);
|
||||||
|
|
||||||
|
// 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 ops, translate.
|
||||||
|
// This must be done last, all other operations
|
||||||
|
// require us to be at (0, 0).
|
||||||
|
let translate = Matrix4::from_translation(Vector3 {
|
||||||
|
x: pos.x / game.camera.zoom / self.window_aspect,
|
||||||
|
y: pos.y / game.camera.zoom,
|
||||||
|
z: 0.0,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Order matters!
|
||||||
|
// The rightmost matrix is applied first.
|
||||||
|
let t =
|
||||||
|
OPENGL_TO_WGPU_MATRIX * translate * screen_aspect * rotate * sprite_aspect_and_scale;
|
||||||
|
|
||||||
|
instances.push(SpriteInstance {
|
||||||
|
transform: t.into(),
|
||||||
|
texture_index: texture.index,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add children
|
||||||
|
if let Some(children) = s.children {
|
||||||
|
for c in children {
|
||||||
|
self.push_subsprite(game, instances, c, pos, s.angle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a sprite's subsprite to instance.
|
||||||
|
/// Only called by push_sprite.
|
||||||
|
fn push_subsprite(
|
||||||
|
&self,
|
||||||
|
game: &Game,
|
||||||
|
instances: &mut Vec<SpriteInstance>,
|
||||||
|
s: Sprite,
|
||||||
|
parent_pos: Point2<f32>,
|
||||||
|
parent_angle: Deg<f32>,
|
||||||
|
) {
|
||||||
|
// TODO: clean up
|
||||||
|
if s.children.is_some() {
|
||||||
|
panic!("Child sprites must not have child sprites!")
|
||||||
|
}
|
||||||
|
|
||||||
|
let texture = self.texture_array.get_sprite_texture(s.texture);
|
||||||
|
let scale = s.size / (s.pos.z * game.camera.zoom);
|
||||||
|
let sprite_aspect_and_scale =
|
||||||
|
Matrix4::from_nonuniform_scale(texture.aspect * scale, scale, 1.0);
|
||||||
|
let rotate = Matrix4::from_angle_z(s.angle);
|
||||||
|
let screen_aspect = Matrix4::from_nonuniform_scale(1.0 / self.window_aspect, 1.0, 1.0);
|
||||||
|
|
||||||
|
let ptranslate = Matrix4::from_translation(Vector3 {
|
||||||
|
x: parent_pos.x / game.camera.zoom / self.window_aspect,
|
||||||
|
y: parent_pos.y / game.camera.zoom,
|
||||||
|
z: 0.0,
|
||||||
|
});
|
||||||
|
let protate = Matrix4::from_angle_z(parent_angle);
|
||||||
|
|
||||||
|
let translate = Matrix4::from_translation(Vector3 {
|
||||||
|
x: s.pos.x / game.camera.zoom / self.window_aspect,
|
||||||
|
y: s.pos.y / game.camera.zoom,
|
||||||
|
z: 0.0,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Order matters!
|
||||||
|
// The rightmost matrix is applied first.
|
||||||
|
let t = OPENGL_TO_WGPU_MATRIX
|
||||||
|
* ptranslate * screen_aspect
|
||||||
|
* protate * translate
|
||||||
|
* rotate * sprite_aspect_and_scale;
|
||||||
|
|
||||||
|
instances.push(SpriteInstance {
|
||||||
|
transform: t.into(),
|
||||||
|
texture_index: texture.index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Make a SpriteInstance for each of the game's visible sprites.
|
/// Make a SpriteInstance for each of the game's visible sprites.
|
||||||
/// Will panic if SPRITE_INSTANCE_LIMIT is exceeded.
|
/// Will panic if SPRITE_INSTANCE_LIMIT is exceeded.
|
||||||
///
|
///
|
||||||
|
@ -207,45 +340,13 @@ impl GPUState {
|
||||||
let clip_sw = Point2::from((self.window_aspect, -1.0)) * game.camera.zoom;
|
let clip_sw = Point2::from((self.window_aspect, -1.0)) * game.camera.zoom;
|
||||||
|
|
||||||
for s in game.get_sprites() {
|
for s in game.get_sprites() {
|
||||||
// Compute post-parallax position and distance-adjusted scale.
|
self.push_sprite(game, &mut instances, clip_ne, clip_sw, s);
|
||||||
// We do this here so we can check if a sprite is on the screen.
|
|
||||||
let pos: Point2<f32> = {
|
|
||||||
(Point2 {
|
|
||||||
x: s.pos.x,
|
|
||||||
y: s.pos.y,
|
|
||||||
} - game.camera.pos.to_vec())
|
|
||||||
/ (s.pos.z + game.camera.zoom / consts::ZOOM_MIN)
|
|
||||||
};
|
|
||||||
let texture = self.texture_array.get_sprite_texture(s.texture);
|
|
||||||
|
|
||||||
// Game dimensions of this sprite post-scale.
|
|
||||||
// Don't divide by 2, we use this later.
|
|
||||||
let height = s.size * s.scale / s.pos.z;
|
|
||||||
let width = height * texture.aspect;
|
|
||||||
|
|
||||||
// Don't draw (or compute matrices for)
|
|
||||||
// sprites that are off the screen
|
|
||||||
if pos.x < clip_ne.x - width
|
|
||||||
|| pos.y > clip_ne.y + height
|
|
||||||
|| pos.x > clip_sw.x + width
|
|
||||||
|| pos.y < clip_sw.y - height
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
instances.push(SpriteInstance {
|
|
||||||
position: pos.into(),
|
|
||||||
aspect: texture.aspect,
|
|
||||||
rotation: Matrix2::from_angle(s.angle).into(),
|
|
||||||
size: height,
|
|
||||||
texture_index: texture.index,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enforce sprite limit
|
// Enforce sprite limit
|
||||||
if instances.len() as u64 > 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!")
|
panic!("Sprite limit exceeded!")
|
||||||
}
|
}
|
||||||
|
|
||||||
return instances;
|
return instances;
|
||||||
|
|
|
@ -12,16 +12,3 @@ 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.
|
|
||||||
// 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,
|
|
||||||
0.0, 1.0, 0.0, 0.0,
|
|
||||||
0.0, 0.0, 0.5, 0.5,
|
|
||||||
0.0, 0.0, 0.0, 1.0,
|
|
||||||
);
|
|
||||||
*/
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
struct InstanceInput {
|
struct InstanceInput {
|
||||||
@location(2) rotation_matrix_0: vec2<f32>,
|
@location(2) transform_matrix_0: vec4<f32>,
|
||||||
@location(3) rotation_matrix_1: vec2<f32>,
|
@location(3) transform_matrix_1: vec4<f32>,
|
||||||
@location(4) position: vec2<f32>,
|
@location(4) transform_matrix_2: vec4<f32>,
|
||||||
@location(5) size: f32,
|
@location(5) transform_matrix_3: vec4<f32>,
|
||||||
@location(6) aspect: f32,
|
@location(6) texture_idx: u32,
|
||||||
@location(7) texture_idx: u32,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VertexInput {
|
struct VertexInput {
|
||||||
|
@ -47,33 +46,15 @@ fn vertex_main(
|
||||||
instance: InstanceInput,
|
instance: InstanceInput,
|
||||||
) -> VertexOutput {
|
) -> VertexOutput {
|
||||||
|
|
||||||
// Apply sprite aspect ratio & scale factor
|
let transform = mat4x4<f32>(
|
||||||
// This must be done *before* rotation.
|
instance.transform_matrix_0,
|
||||||
let scale = instance.size / global.camera_zoom.x;
|
instance.transform_matrix_1,
|
||||||
var pos: vec2<f32> = vec2<f32>(
|
instance.transform_matrix_2,
|
||||||
vertex.position.x * instance.aspect * scale,
|
instance.transform_matrix_3,
|
||||||
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.x, 1.0);
|
|
||||||
|
|
||||||
// Translate
|
|
||||||
pos = pos + (
|
|
||||||
// Don't forget to correct distance for screen aspect ratio too!
|
|
||||||
(instance.position / global.camera_zoom.x)
|
|
||||||
/ vec2<f32>(global.window_aspect.x, 1.0)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
out.position = vec4<f32>(pos, 0.0, 1.0);
|
out.position = transform * vec4<f32>(vertex.position, 1.0);
|
||||||
out.texture_coords = vertex.texture_coords;
|
out.texture_coords = vertex.texture_coords;
|
||||||
out.index = instance.texture_idx;
|
out.index = instance.texture_idx;
|
||||||
return out;
|
return out;
|
||||||
|
|
|
@ -13,13 +13,18 @@ pub struct Sprite {
|
||||||
/// given as height in world units.
|
/// given as height in world units.
|
||||||
pub size: f32,
|
pub size: f32,
|
||||||
|
|
||||||
/// Scale factor.
|
|
||||||
/// if this is 1, sprite height is exactly self.size.
|
|
||||||
pub scale: f32,
|
|
||||||
|
|
||||||
/// This sprite's rotation
|
/// This sprite's rotation
|
||||||
/// (relative to north, measured ccw)
|
/// (relative to north, measured ccw)
|
||||||
pub angle: Deg<f32>,
|
pub angle: Deg<f32>,
|
||||||
|
|
||||||
|
/// Sprites that should be drawn relative to this sprite.
|
||||||
|
/// Coordinates of sprites in this array will be interpreted
|
||||||
|
/// as world units, relative to the center of this sprite,
|
||||||
|
/// before any rotation or scaling.
|
||||||
|
/// Children rotate with their parent sprite.
|
||||||
|
///
|
||||||
|
/// Note that child sprites may NOT have children.
|
||||||
|
pub children: Option<Vec<Sprite>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Spriteable {
|
pub trait Spriteable {
|
||||||
|
|
|
@ -86,20 +86,9 @@ 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 {
|
||||||
/// Rotation matrix for this sprite
|
/// Extra transformations this sprite
|
||||||
pub rotation: [[f32; 2]; 2],
|
/// (rotation, etc)
|
||||||
|
pub transform: [[f32; 4]; 4],
|
||||||
/// World position, relative to camera
|
|
||||||
/// Note that this does NOT contain z-distance,
|
|
||||||
/// since sprite parallax and distance scaling
|
|
||||||
/// is applied beforehand.
|
|
||||||
pub position: [f32; 2],
|
|
||||||
|
|
||||||
/// Height of (unrotated) sprite in world units
|
|
||||||
pub size: f32,
|
|
||||||
|
|
||||||
// Sprite aspect ratio (width / height)
|
|
||||||
pub aspect: f32,
|
|
||||||
|
|
||||||
// What texture to use for this sprite
|
// What texture to use for this sprite
|
||||||
pub texture_index: u32,
|
pub texture_index: u32,
|
||||||
|
@ -114,39 +103,31 @@ 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
|
// 4 arrays = 1 4x4 matrix
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: 0,
|
offset: 0,
|
||||||
shader_location: 2,
|
shader_location: 2,
|
||||||
format: wgpu::VertexFormat::Float32x2,
|
format: wgpu::VertexFormat::Float32x4,
|
||||||
},
|
},
|
||||||
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,
|
||||||
|
format: wgpu::VertexFormat::Float32x4,
|
||||||
|
},
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
|
||||||
shader_location: 4,
|
shader_location: 4,
|
||||||
format: wgpu::VertexFormat::Float32x2,
|
format: wgpu::VertexFormat::Float32x4,
|
||||||
},
|
},
|
||||||
// Size
|
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 6]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress,
|
||||||
shader_location: 5,
|
shader_location: 5,
|
||||||
format: wgpu::VertexFormat::Float32,
|
format: wgpu::VertexFormat::Float32x4,
|
||||||
},
|
|
||||||
// Aspect
|
|
||||||
wgpu::VertexAttribute {
|
|
||||||
offset: mem::size_of::<[f32; 7]>() as wgpu::BufferAddress,
|
|
||||||
shader_location: 6,
|
|
||||||
format: wgpu::VertexFormat::Float32,
|
|
||||||
},
|
},
|
||||||
// Texture
|
// Texture
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 16]>() as wgpu::BufferAddress,
|
||||||
shader_location: 7,
|
shader_location: 6,
|
||||||
format: wgpu::VertexFormat::Uint32,
|
format: wgpu::VertexFormat::Uint32,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in New Issue