parent
abd41af202
commit
a5b3932e9d
|
@ -4,9 +4,8 @@ name = "12 Autumn above"
|
||||||
|
|
||||||
[object.star]
|
[object.star]
|
||||||
sprite = "star::star"
|
sprite = "star::star"
|
||||||
position = [0.0, 0.0]
|
position = [0.0, 0.0, 20.0]
|
||||||
size = 1000
|
size = 1000
|
||||||
parallax = 20.0
|
|
||||||
|
|
||||||
|
|
||||||
[object.earth]
|
[object.earth]
|
||||||
|
@ -14,8 +13,8 @@ sprite = "planet::earth"
|
||||||
position.center = "star"
|
position.center = "star"
|
||||||
position.radius = 4000
|
position.radius = 4000
|
||||||
position.angle = 0
|
position.angle = 0
|
||||||
|
position.z = 10.0
|
||||||
size = 1000
|
size = 1000
|
||||||
parallax = 10.0
|
|
||||||
|
|
||||||
|
|
||||||
[object.luna]
|
[object.luna]
|
||||||
|
@ -23,6 +22,6 @@ sprite = "planet::luna"
|
||||||
position.center = "earth"
|
position.center = "earth"
|
||||||
position.radius = 1600
|
position.radius = 1600
|
||||||
position.angle = 135
|
position.angle = 135
|
||||||
|
position.z = 7.8
|
||||||
size = 500
|
size = 500
|
||||||
angle = -45
|
angle = -45
|
||||||
parallax = 7.8
|
|
||||||
|
|
|
@ -3,19 +3,29 @@ use crate::physics::Pfloat;
|
||||||
pub const ZOOM_MIN: Pfloat = 200.0;
|
pub const ZOOM_MIN: Pfloat = 200.0;
|
||||||
pub const ZOOM_MAX: Pfloat = 2000.0;
|
pub const ZOOM_MAX: Pfloat = 2000.0;
|
||||||
|
|
||||||
// Z-axis range for starfield stars
|
/// Z-axis range for starfield stars
|
||||||
pub const STARFIELD_PARALLAX_MIN: f32 = 100.0;
|
/// This does not affect scale.
|
||||||
pub const STARFIELD_PARALLAX_MAX: f32 = 200.0;
|
pub const STARFIELD_Z_MIN: f32 = 100.0;
|
||||||
// Size of a square starfield tile, in game units.
|
pub const STARFIELD_Z_MAX: f32 = 200.0;
|
||||||
// A tile of size PARALLAX_MAX * screen-size-in-game-units
|
|
||||||
// will completely cover a (square) screen. This depends on zoom!
|
/// Size range for starfield stars, in game units.
|
||||||
//
|
/// This is scaled for zoom, but NOT for distance.
|
||||||
// Use a value smaller than zoom_max for debug.
|
pub const STARFIELD_SIZE_MIN: f32 = 0.2;
|
||||||
pub const STARFIELD_SIZE: u64 = STARFIELD_PARALLAX_MAX as u64 * ZOOM_MAX as u64;
|
pub const STARFIELD_SIZE_MAX: f32 = 1.8;
|
||||||
// Average number of stars per game unit
|
|
||||||
|
/// Size of a square starfield tile, in game units.
|
||||||
|
/// A tile of size STARFIELD_Z_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_Z_MAX as u64 * ZOOM_MAX as u64;
|
||||||
|
|
||||||
|
/// Average number of stars per game unit
|
||||||
pub const STARFIELD_DENSITY: f64 = 0.01;
|
pub const STARFIELD_DENSITY: f64 = 0.01;
|
||||||
// Number of stars in one starfield tile
|
|
||||||
// Must fit inside an i32
|
/// 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 STARFIELD_COUNT: u64 = (STARFIELD_SIZE as f64 * STARFIELD_DENSITY) as u64;
|
||||||
|
|
||||||
|
/// Root directory of game content
|
||||||
pub const CONTENT_ROOT: &'static str = "./content";
|
pub const CONTENT_ROOT: &'static str = "./content";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use cgmath::{Deg, Point2};
|
use cgmath::{Deg, Point3};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use crate::physics::{Pfloat, Polar};
|
use crate::physics::{Pfloat, Polar};
|
||||||
|
@ -27,7 +27,6 @@ pub(in crate::content) mod toml {
|
||||||
pub position: Position,
|
pub position: Position,
|
||||||
|
|
||||||
pub size: Pfloat,
|
pub size: Pfloat,
|
||||||
pub parallax: Pfloat,
|
|
||||||
|
|
||||||
pub radius: Option<Pfloat>,
|
pub radius: Option<Pfloat>,
|
||||||
pub angle: Option<Pfloat>,
|
pub angle: Option<Pfloat>,
|
||||||
|
@ -37,24 +36,25 @@ pub(in crate::content) mod toml {
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum Position {
|
pub enum Position {
|
||||||
Polar(PolarCoords),
|
Polar(PolarCoords),
|
||||||
Cartesian(Coordinates),
|
Cartesian(CoordinatesThree),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct PolarCoords {
|
pub struct PolarCoords {
|
||||||
pub center: Coordinates,
|
pub center: CoordinatesTwo,
|
||||||
pub radius: Pfloat,
|
pub radius: Pfloat,
|
||||||
pub angle: Pfloat,
|
pub angle: Pfloat,
|
||||||
|
pub z: Pfloat,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum Coordinates {
|
pub enum CoordinatesTwo {
|
||||||
Label(String),
|
Label(String),
|
||||||
Coords([Pfloat; 2]),
|
Coords([Pfloat; 2]),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToString for Coordinates {
|
impl ToString for CoordinatesTwo {
|
||||||
fn to_string(&self) -> String {
|
fn to_string(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Self::Label(s) => s.to_owned(),
|
Self::Label(s) => s.to_owned(),
|
||||||
|
@ -62,6 +62,44 @@ pub(in crate::content) mod toml {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CoordinatesTwo {
|
||||||
|
/// Transform a CoordinatesThree into a CoordinatesTwo by adding a NaN z component.
|
||||||
|
/// Labels are not changed.
|
||||||
|
pub fn to_three(&self) -> CoordinatesThree {
|
||||||
|
match self {
|
||||||
|
Self::Label(s) => CoordinatesThree::Label(s.clone()),
|
||||||
|
Self::Coords(v) => CoordinatesThree::Coords([v[0], v[1], f32::NAN]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum CoordinatesThree {
|
||||||
|
Label(String),
|
||||||
|
Coords([Pfloat; 3]),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for CoordinatesThree {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Label(s) => s.to_owned(),
|
||||||
|
Self::Coords(v) => format!("{:?}", v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CoordinatesThree {
|
||||||
|
/// Transform a CoordinatesThree into a CoordinatesTwo by deleting z component.
|
||||||
|
/// Labels are not changed.
|
||||||
|
pub fn to_two(&self) -> CoordinatesTwo {
|
||||||
|
match self {
|
||||||
|
Self::Label(s) => CoordinatesTwo::Label(s.clone()),
|
||||||
|
Self::Coords(v) => CoordinatesTwo::Coords([v[0], v[1]]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -73,20 +111,20 @@ pub struct System {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Object {
|
pub struct Object {
|
||||||
pub sprite: String,
|
pub sprite: String,
|
||||||
pub position: Point2<f32>,
|
pub position: Point3<f32>,
|
||||||
pub size: Pfloat,
|
pub size: Pfloat,
|
||||||
pub parallax: Pfloat,
|
|
||||||
pub angle: Deg<Pfloat>,
|
pub angle: Deg<Pfloat>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function for resolve_position, never called on its own.
|
||||||
fn resolve_coordinates(
|
fn resolve_coordinates(
|
||||||
objects: &HashMap<String, toml::Object>,
|
objects: &HashMap<String, toml::Object>,
|
||||||
cor: &toml::Coordinates,
|
cor: &toml::CoordinatesThree,
|
||||||
mut cycle_detector: HashSet<String>,
|
mut cycle_detector: HashSet<String>,
|
||||||
) -> Result<Point2<f32>> {
|
) -> Result<Point3<f32>> {
|
||||||
match cor {
|
match cor {
|
||||||
toml::Coordinates::Coords(c) => Ok((*c).into()),
|
toml::CoordinatesThree::Coords(c) => Ok((*c).into()),
|
||||||
toml::Coordinates::Label(l) => {
|
toml::CoordinatesThree::Label(l) => {
|
||||||
if cycle_detector.contains(l) {
|
if cycle_detector.contains(l) {
|
||||||
bail!(
|
bail!(
|
||||||
"Found coordinate cycle: `{}`",
|
"Found coordinate cycle: `{}`",
|
||||||
|
@ -111,19 +149,28 @@ fn resolve_coordinates(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Given an object, resolve it's position as a Point3.
|
||||||
fn resolve_position(
|
fn resolve_position(
|
||||||
objects: &HashMap<String, toml::Object>,
|
objects: &HashMap<String, toml::Object>,
|
||||||
obj: &toml::Object,
|
obj: &toml::Object,
|
||||||
cycle_detector: HashSet<String>,
|
cycle_detector: HashSet<String>,
|
||||||
) -> Result<Point2<f32>> {
|
) -> Result<Point3<f32>> {
|
||||||
match &obj.position {
|
match &obj.position {
|
||||||
toml::Position::Cartesian(c) => Ok(resolve_coordinates(objects, c, cycle_detector)?),
|
toml::Position::Cartesian(c) => Ok(resolve_coordinates(objects, &c, cycle_detector)?),
|
||||||
toml::Position::Polar(p) => Ok(Polar {
|
toml::Position::Polar(p) => {
|
||||||
center: resolve_coordinates(&objects, &p.center, cycle_detector)?,
|
let r = resolve_coordinates(&objects, &p.center.to_three(), cycle_detector)?;
|
||||||
|
let plane = Polar {
|
||||||
|
center: (r.x, r.y).into(),
|
||||||
radius: p.radius,
|
radius: p.radius,
|
||||||
angle: Deg(p.angle),
|
angle: Deg(p.angle),
|
||||||
}
|
}
|
||||||
.to_cartesian()),
|
.to_cartesian();
|
||||||
|
Ok(Point3 {
|
||||||
|
x: plane.x,
|
||||||
|
y: plane.y,
|
||||||
|
z: p.z,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,7 +187,6 @@ impl System {
|
||||||
position: resolve_position(&value.object, &obj, cycle_detector)
|
position: resolve_position(&value.object, &obj, cycle_detector)
|
||||||
.with_context(|| format!("In object {:#?}", label))?,
|
.with_context(|| format!("In object {:#?}", label))?,
|
||||||
size: obj.size,
|
size: obj.size,
|
||||||
parallax: obj.parallax,
|
|
||||||
angle: Deg(obj.angle.unwrap_or(0.0)),
|
angle: Deg(obj.angle.unwrap_or(0.0)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use cgmath::{Deg, Point2};
|
use cgmath::{Deg, Point3};
|
||||||
|
|
||||||
use crate::{physics::Pfloat, render::Sprite, render::SpriteTexture, render::Spriteable};
|
use crate::{physics::Pfloat, render::Sprite, render::SpriteTexture, render::Spriteable};
|
||||||
|
|
||||||
pub struct Doodad {
|
pub struct Doodad {
|
||||||
pub sprite: SpriteTexture,
|
pub sprite: SpriteTexture,
|
||||||
pub pos: Point2<Pfloat>,
|
pub pos: Point3<Pfloat>,
|
||||||
pub parallax: Pfloat,
|
|
||||||
pub size: Pfloat,
|
pub size: Pfloat,
|
||||||
pub angle: Deg<Pfloat>,
|
pub angle: Deg<Pfloat>,
|
||||||
}
|
}
|
||||||
|
@ -18,7 +17,6 @@ impl Spriteable for Doodad {
|
||||||
pos: self.pos,
|
pos: self.pos,
|
||||||
angle: self.angle,
|
angle: self.angle,
|
||||||
size: self.size,
|
size: self.size,
|
||||||
parallax: self.parallax,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,9 +75,8 @@ impl Game {
|
||||||
// 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 with parallax as z-coordinate?
|
// TODO: use a gpu depth buffer instead.
|
||||||
// Might be overkill.
|
sprites.sort_by(|a, b| b.pos.z.total_cmp(&a.pos.z));
|
||||||
sprites.sort_by(|a, b| b.parallax.total_cmp(&a.parallax));
|
|
||||||
|
|
||||||
return sprites;
|
return sprites;
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,11 +41,10 @@ impl Ship {
|
||||||
impl Spriteable for Ship {
|
impl Spriteable for Ship {
|
||||||
fn get_sprite(&self) -> Sprite {
|
fn get_sprite(&self) -> Sprite {
|
||||||
return Sprite {
|
return Sprite {
|
||||||
pos: self.body.pos,
|
pos: (self.body.pos.x, self.body.pos.y, 1.0).into(),
|
||||||
texture: self.kind.sprite(),
|
texture: self.kind.sprite(),
|
||||||
angle: self.body.angle,
|
angle: self.body.angle,
|
||||||
scale: 1.0,
|
scale: 1.0,
|
||||||
parallax: 1.0,
|
|
||||||
size: self.kind.size(),
|
size: self.kind.size(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use cgmath::{Point2, Vector2};
|
use cgmath::{Point3, Vector2};
|
||||||
use rand::{self, Rng};
|
use rand::{self, Rng};
|
||||||
|
|
||||||
use super::Doodad;
|
use super::Doodad;
|
||||||
|
@ -9,14 +9,14 @@ use crate::{
|
||||||
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.
|
||||||
pub pos: Point2<Pfloat>,
|
pub pos: Point3<Pfloat>,
|
||||||
|
|
||||||
// TODO: z-coordinate?
|
/// Height in game units.
|
||||||
pub parallax: Pfloat,
|
/// Will be scaled for zoom, but not for distance.
|
||||||
pub size: Pfloat,
|
pub size: Pfloat,
|
||||||
|
|
||||||
/// Color/brightness variation.
|
/// Color/brightness variation. Random between 0 and 1.
|
||||||
/// See shader.
|
/// Used in starfield shader.
|
||||||
pub tint: Vector2<Pfloat>,
|
pub tint: Vector2<Pfloat>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,13 +35,12 @@ impl System {
|
||||||
bodies: Vec::new(),
|
bodies: Vec::new(),
|
||||||
starfield: (0..consts::STARFIELD_COUNT)
|
starfield: (0..consts::STARFIELD_COUNT)
|
||||||
.map(|_| StarfieldStar {
|
.map(|_| StarfieldStar {
|
||||||
pos: Point2 {
|
pos: Point3 {
|
||||||
x: rng.gen_range(-sz..=sz),
|
x: rng.gen_range(-sz..=sz),
|
||||||
y: rng.gen_range(-sz..=sz),
|
y: rng.gen_range(-sz..=sz),
|
||||||
|
z: rng.gen_range(consts::STARFIELD_Z_MIN..consts::STARFIELD_Z_MAX),
|
||||||
},
|
},
|
||||||
parallax: rng
|
size: rng.gen_range(consts::STARFIELD_SIZE_MIN..consts::STARFIELD_SIZE_MAX),
|
||||||
.gen_range(consts::STARFIELD_PARALLAX_MIN..consts::STARFIELD_PARALLAX_MAX),
|
|
||||||
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),
|
||||||
y: rng.gen_range(0.0..=1.0),
|
y: rng.gen_range(0.0..=1.0),
|
||||||
|
@ -55,7 +54,6 @@ impl System {
|
||||||
pos: o.position,
|
pos: o.position,
|
||||||
sprite: SpriteTexture(o.sprite.to_owned()),
|
sprite: SpriteTexture(o.sprite.to_owned()),
|
||||||
size: o.size,
|
size: o.size,
|
||||||
parallax: o.parallax,
|
|
||||||
angle: o.angle,
|
angle: o.angle,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,23 +21,29 @@ pub struct GlobalDataContent {
|
||||||
pub camera_position: [f32; 2],
|
pub camera_position: [f32; 2],
|
||||||
|
|
||||||
/// Camera zoom value, in game units.
|
/// Camera zoom value, in game units.
|
||||||
/// Only first component has meaning.
|
/// Second component is ignored.
|
||||||
pub camera_zoom: [f32; 2],
|
pub camera_zoom: [f32; 2],
|
||||||
|
|
||||||
|
/// Camera zoom min and max.
|
||||||
|
pub camera_zoom_limits: [f32; 2],
|
||||||
|
|
||||||
/// Size ratio of window, in physical pixels
|
/// Size ratio of window, in physical pixels
|
||||||
pub window_size: [f32; 2],
|
pub window_size: [f32; 2],
|
||||||
|
|
||||||
// Aspect ration of window
|
// Aspect ration of window
|
||||||
/// Only first component has meaning.
|
/// Second component is ignored.
|
||||||
pub window_aspect: [f32; 2],
|
pub window_aspect: [f32; 2],
|
||||||
|
|
||||||
/// Texture index of starfield sprites
|
/// Texture index of starfield sprites
|
||||||
/// Only first component has meaning.
|
/// Second component is ignored.
|
||||||
pub starfield_texture: [u32; 2],
|
pub starfield_texture: [u32; 2],
|
||||||
|
|
||||||
// Size of (square) starfield tiles, in game units
|
// Size of (square) starfield tiles, in game units
|
||||||
/// Only first component has meaning.
|
/// Second component is ignored.
|
||||||
pub starfield_tile_size: [f32; 2],
|
pub starfield_tile_size: [f32; 2],
|
||||||
|
|
||||||
|
// Min and max starfield star size, in game units
|
||||||
|
pub starfield_size_limits: [f32; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlobalDataContent {
|
impl GlobalDataContent {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bytemuck;
|
use bytemuck;
|
||||||
use cgmath::{EuclideanSpace, Matrix2, Point2, Vector2};
|
use cgmath::{EuclideanSpace, Matrix2, 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};
|
||||||
|
@ -16,7 +16,7 @@ use super::{
|
||||||
VertexBuffer,
|
VertexBuffer,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use crate::{consts, game::Game};
|
use crate::{consts, game::Game, physics::Pfloat};
|
||||||
|
|
||||||
pub struct GPUState {
|
pub struct GPUState {
|
||||||
device: wgpu::Device,
|
device: wgpu::Device,
|
||||||
|
@ -209,14 +209,20 @@ 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() {
|
||||||
// Parallax is computed here, so we can check if this sprite is visible.
|
// Compute post-parallax position and distance-adjusted scale.
|
||||||
let pos = (s.pos - game.camera.pos.to_vec())
|
// We do this here so we can check if a sprite is on the screen.
|
||||||
/ (s.parallax + game.camera.zoom / consts::ZOOM_MIN);
|
let pos: Point2<Pfloat> = {
|
||||||
|
(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);
|
let texture = self.texture_array.get_sprite_texture(s.texture);
|
||||||
|
|
||||||
// Game dimensions of this sprite post-scale.
|
// Game dimensions of this sprite post-scale.
|
||||||
// Don't divide by 2, we use this later.
|
// Don't divide by 2, we use this later.
|
||||||
let height = s.size * s.scale / s.parallax;
|
let height = s.size * s.scale / s.pos.z;
|
||||||
let width = height * texture.aspect;
|
let width = height * texture.aspect;
|
||||||
|
|
||||||
// Don't draw (or compute matrices for)
|
// Don't draw (or compute matrices for)
|
||||||
|
@ -233,7 +239,7 @@ impl GPUState {
|
||||||
position: pos.into(),
|
position: pos.into(),
|
||||||
aspect: texture.aspect,
|
aspect: texture.aspect,
|
||||||
rotation: Matrix2::from_angle(s.angle).into(),
|
rotation: Matrix2::from_angle(s.angle).into(),
|
||||||
height,
|
size: height,
|
||||||
texture_index: texture.index,
|
texture_index: texture.index,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -262,7 +268,7 @@ impl GPUState {
|
||||||
// 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 * consts::STARFIELD_PARALLAX_MIN;
|
let v: Point2<f32> = clip_nw * consts::STARFIELD_Z_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]
|
||||||
|
@ -296,14 +302,14 @@ impl GPUState {
|
||||||
let mut instances = Vec::new();
|
let mut instances = Vec::new();
|
||||||
for x in (-nw_tile.x)..=nw_tile.x {
|
for x in (-nw_tile.x)..=nw_tile.x {
|
||||||
for y in (-nw_tile.y)..=nw_tile.y {
|
for y in (-nw_tile.y)..=nw_tile.y {
|
||||||
let offset = Vector2 {
|
let offset = Vector3 {
|
||||||
x: sz * x as f32,
|
x: sz * x as f32,
|
||||||
y: sz * y as f32,
|
y: sz * y as f32,
|
||||||
|
z: 0.0,
|
||||||
};
|
};
|
||||||
for s in &game.system.starfield {
|
for s in &game.system.starfield {
|
||||||
instances.push(StarfieldInstance {
|
instances.push(StarfieldInstance {
|
||||||
position: (s.pos + offset).into(),
|
position: (s.pos + offset).into(),
|
||||||
parallax: s.parallax,
|
|
||||||
size: s.size,
|
size: s.size,
|
||||||
tint: s.tint.into(),
|
tint: s.tint.into(),
|
||||||
})
|
})
|
||||||
|
@ -364,6 +370,7 @@ impl GPUState {
|
||||||
bytemuck::cast_slice(&[GlobalDataContent {
|
bytemuck::cast_slice(&[GlobalDataContent {
|
||||||
camera_position: game.camera.pos.into(),
|
camera_position: game.camera.pos.into(),
|
||||||
camera_zoom: [game.camera.zoom, 0.0],
|
camera_zoom: [game.camera.zoom, 0.0],
|
||||||
|
camera_zoom_limits: [consts::ZOOM_MIN, consts::ZOOM_MAX],
|
||||||
window_size: [
|
window_size: [
|
||||||
self.window_size.width as f32,
|
self.window_size.width as f32,
|
||||||
self.window_size.height as f32,
|
self.window_size.height as f32,
|
||||||
|
@ -371,6 +378,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: [consts::STARFIELD_SIZE as f32, 0.0],
|
starfield_tile_size: [consts::STARFIELD_SIZE as f32, 0.0],
|
||||||
|
starfield_size_limits: [consts::STARFIELD_SIZE_MIN, consts::STARFIELD_SIZE_MAX],
|
||||||
}]),
|
}]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ 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>,
|
||||||
@location(4) position: vec2<f32>,
|
@location(4) position: vec2<f32>,
|
||||||
@location(5) height: f32,
|
@location(5) size: f32,
|
||||||
@location(6) aspect: f32,
|
@location(6) aspect: f32,
|
||||||
@location(7) texture_idx: u32,
|
@location(7) texture_idx: u32,
|
||||||
};
|
};
|
||||||
|
@ -24,10 +24,12 @@ var<uniform> global: GlobalUniform;
|
||||||
struct GlobalUniform {
|
struct GlobalUniform {
|
||||||
camera_position: vec2<f32>,
|
camera_position: vec2<f32>,
|
||||||
camera_zoom: vec2<f32>,
|
camera_zoom: vec2<f32>,
|
||||||
|
camera_zoom_limits: vec2<f32>,
|
||||||
window_size: vec2<f32>,
|
window_size: vec2<f32>,
|
||||||
window_aspect: vec2<f32>,
|
window_aspect: vec2<f32>,
|
||||||
starfield_texture: vec2<u32>,
|
starfield_texture: vec2<u32>,
|
||||||
starfield_tile_size: vec2<f32>,
|
starfield_tile_size: vec2<f32>,
|
||||||
|
starfield_size_limits: vec2<f32>,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -47,7 +49,7 @@ fn vertex_main(
|
||||||
|
|
||||||
// Apply sprite aspect ratio & scale factor
|
// Apply sprite aspect ratio & scale factor
|
||||||
// This must be done *before* rotation.
|
// This must be done *before* rotation.
|
||||||
let scale = instance.height / global.camera_zoom.x;
|
let scale = instance.size / global.camera_zoom.x;
|
||||||
var pos: vec2<f32> = vec2<f32>(
|
var pos: vec2<f32> = vec2<f32>(
|
||||||
vertex.position.x * instance.aspect * scale,
|
vertex.position.x * instance.aspect * scale,
|
||||||
vertex.position.y * scale
|
vertex.position.y * scale
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
struct InstanceInput {
|
struct InstanceInput {
|
||||||
@location(2) position: vec2<f32>,
|
@location(2) position: vec3<f32>,
|
||||||
@location(3) parallax: f32,
|
@location(3) size: f32,
|
||||||
@location(4) size: f32,
|
@location(4) tint: vec2<f32>,
|
||||||
@location(5) tint: vec2<f32>,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VertexInput {
|
struct VertexInput {
|
||||||
|
@ -21,10 +20,12 @@ var<uniform> global: GlobalUniform;
|
||||||
struct GlobalUniform {
|
struct GlobalUniform {
|
||||||
camera_position: vec2<f32>,
|
camera_position: vec2<f32>,
|
||||||
camera_zoom: vec2<f32>,
|
camera_zoom: vec2<f32>,
|
||||||
|
camera_zoom_limits: vec2<f32>,
|
||||||
window_size: vec2<f32>,
|
window_size: vec2<f32>,
|
||||||
window_aspect: vec2<f32>,
|
window_aspect: vec2<f32>,
|
||||||
starfield_texture: vec2<u32>,
|
starfield_texture: vec2<u32>,
|
||||||
starfield_tile_size: vec2<f32>,
|
starfield_tile_size: vec2<f32>,
|
||||||
|
starfield_size_limits: vec2<f32>,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,19 +63,37 @@ fn vertex_main(
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
let zoom_min_times = (
|
||||||
|
global.camera_zoom.x / global.camera_zoom_limits.x
|
||||||
|
);
|
||||||
|
|
||||||
|
// Hide n% of the smallest stars
|
||||||
|
// If we wanted a constant number of stars on the screen, we would do
|
||||||
|
// `let hide_fraction = 1.0 - 1.0 / (zoom_min_times * zoom_min_times);`
|
||||||
|
// We, however, don't want this: a bigger screen should have more stars,
|
||||||
|
// but not *too* many. We thus scale linearly.
|
||||||
|
let hide_fraction = 1.0 - 1.0 / (zoom_min_times * 0.8);
|
||||||
|
|
||||||
|
// Hide some stars at large zoom levels.
|
||||||
|
if (
|
||||||
|
instance.size < (
|
||||||
|
hide_fraction * (global.starfield_size_limits.y - global.starfield_size_limits.x)
|
||||||
|
+ (global.starfield_size_limits.x)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
out.position = vec4<f32>(2.0, 2.0, 0.0, 1.0);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Apply sprite aspect ratio & scale factor
|
// Apply sprite aspect ratio & scale factor
|
||||||
// also applies screen aspect ratio
|
// also applies screen aspect ratio
|
||||||
|
// Note that we do NOT scale for distance here---this is intentional.
|
||||||
var scale: f32 = instance.size / global.camera_zoom.x;
|
var scale: f32 = instance.size / global.camera_zoom.x;
|
||||||
|
|
||||||
// Minimum scale to prevent flicker at large zoom levels
|
// Minimum scale to prevent flicker at large zoom levels
|
||||||
var real_size = scale * global.window_size.xy;
|
var real_size = scale * global.window_size.xy;
|
||||||
// TODO: configurable.
|
|
||||||
// Uniform distribution!
|
|
||||||
if (real_size.x < 0.5 || real_size.y < 0.5) {
|
|
||||||
// If this star is too small, don't even show it
|
|
||||||
out.position = vec4<f32>(2.0, 2.0, 0.0, 1.0);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
if (real_size.x < 2.0 || real_size.y < 2.0) {
|
if (real_size.x < 2.0 || real_size.y < 2.0) {
|
||||||
// Otherwise, clamp to a minimum scale
|
// Otherwise, clamp to a minimum scale
|
||||||
scale = 2.0 / max(global.window_size.x, global.window_size.y);
|
scale = 2.0 / max(global.window_size.x, global.window_size.y);
|
||||||
|
@ -89,16 +108,16 @@ fn vertex_main(
|
||||||
// World position relative to camera
|
// World position relative to camera
|
||||||
// (Note that instance position is in a different
|
// (Note that instance position is in a different
|
||||||
// coordinate system than usual)
|
// coordinate system than usual)
|
||||||
let camera_pos = (instance.position + tile_center) - global.camera_position.xy;
|
let camera_pos = (instance.position.xy + tile_center) - global.camera_position.xy;
|
||||||
|
|
||||||
// Translate
|
// Translate
|
||||||
pos = pos + (
|
pos = pos + (
|
||||||
// Don't forget to correct distance for screen aspect ratio too!
|
// Don't forget to correct distance for screen aspect ratio too!
|
||||||
(camera_pos / (global.camera_zoom.x * (instance.parallax)))
|
(camera_pos / (global.camera_zoom.x * (instance.position.z)))
|
||||||
/ vec2<f32>(global.window_aspect.x, 1.0)
|
/ vec2<f32>(global.window_aspect.x, 1.0)
|
||||||
);
|
);
|
||||||
|
|
||||||
out.position = vec4<f32>(pos, 0.0, 1.0) * instance.parallax;
|
out.position = vec4<f32>(pos, 0.0, 1.0) * instance.position.z;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use cgmath::{Deg, Point2};
|
use cgmath::{Deg, Point3};
|
||||||
|
|
||||||
use super::SpriteTexture;
|
use super::SpriteTexture;
|
||||||
use crate::physics::Pfloat;
|
use crate::physics::Pfloat;
|
||||||
|
@ -8,7 +8,7 @@ pub struct Sprite {
|
||||||
pub texture: SpriteTexture,
|
pub texture: SpriteTexture,
|
||||||
|
|
||||||
/// This object's position, in world coordinates.
|
/// This object's position, in world coordinates.
|
||||||
pub pos: Point2<Pfloat>,
|
pub pos: Point3<Pfloat>,
|
||||||
|
|
||||||
/// The size of this sprite,
|
/// The size of this sprite,
|
||||||
/// given as height in world units.
|
/// given as height in world units.
|
||||||
|
@ -21,11 +21,6 @@ pub struct Sprite {
|
||||||
/// This sprite's rotation
|
/// This sprite's rotation
|
||||||
/// (relative to north, measured ccw)
|
/// (relative to north, measured ccw)
|
||||||
pub angle: Deg<Pfloat>,
|
pub angle: Deg<Pfloat>,
|
||||||
|
|
||||||
/// Parallax factor.
|
|
||||||
/// Corresponds to z-distance, and affects
|
|
||||||
/// position and scale.
|
|
||||||
pub parallax: Pfloat,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Spriteable {
|
pub trait Spriteable {
|
||||||
|
|
|
@ -36,15 +36,20 @@ impl BufferObject for TexturedVertex {
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
pub struct StarfieldInstance {
|
pub struct StarfieldInstance {
|
||||||
/// Position in origin field tile.
|
/// Position in the starfield.
|
||||||
/// note that this is DIFFERENT from
|
///
|
||||||
/// the way we provide sprite positions!
|
/// This is NOT world position, i.e, different from sprite positioning!
|
||||||
pub position: [f32; 2],
|
/// The x and y coordinates here represent position relative to the center
|
||||||
|
/// of a starfield tile in world units, which is converted to world position
|
||||||
/// Parallax factor (same unit as usual)
|
/// by the starfield vertex shader.
|
||||||
pub parallax: f32,
|
pub position: [f32; 3],
|
||||||
|
|
||||||
|
/// Star size, in world units. This does NOT scale with distance,
|
||||||
|
/// unlike sprite size.
|
||||||
pub size: f32,
|
pub size: f32,
|
||||||
|
|
||||||
|
/// Parameters for this star's color variation,
|
||||||
|
/// see the starfield fragment shader.
|
||||||
pub tint: [f32; 2],
|
pub tint: [f32; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,24 +63,18 @@ impl BufferObject for StarfieldInstance {
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: 0,
|
offset: 0,
|
||||||
shader_location: 2,
|
shader_location: 2,
|
||||||
format: wgpu::VertexFormat::Float32x2,
|
format: wgpu::VertexFormat::Float32x3,
|
||||||
},
|
|
||||||
// Parallax
|
|
||||||
wgpu::VertexAttribute {
|
|
||||||
offset: mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
|
|
||||||
shader_location: 3,
|
|
||||||
format: wgpu::VertexFormat::Float32,
|
|
||||||
},
|
},
|
||||||
// Size
|
// Size
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
|
||||||
shader_location: 4,
|
shader_location: 3,
|
||||||
format: wgpu::VertexFormat::Float32,
|
format: wgpu::VertexFormat::Float32,
|
||||||
},
|
},
|
||||||
// Tint
|
// Tint
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
|
||||||
shader_location: 5,
|
shader_location: 4,
|
||||||
format: wgpu::VertexFormat::Float32x2,
|
format: wgpu::VertexFormat::Float32x2,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -91,10 +90,13 @@ pub struct SpriteInstance {
|
||||||
pub rotation: [[f32; 2]; 2],
|
pub rotation: [[f32; 2]; 2],
|
||||||
|
|
||||||
/// World position, relative to camera
|
/// 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],
|
pub position: [f32; 2],
|
||||||
|
|
||||||
/// Height of (unrotated) sprite in world units
|
/// Height of (unrotated) sprite in world units
|
||||||
pub height: f32,
|
pub size: f32,
|
||||||
|
|
||||||
// Sprite aspect ratio (width / height)
|
// Sprite aspect ratio (width / height)
|
||||||
pub aspect: f32,
|
pub aspect: f32,
|
||||||
|
@ -129,7 +131,7 @@ impl BufferObject for SpriteInstance {
|
||||||
shader_location: 4,
|
shader_location: 4,
|
||||||
format: wgpu::VertexFormat::Float32x2,
|
format: wgpu::VertexFormat::Float32x2,
|
||||||
},
|
},
|
||||||
// Height
|
// Size
|
||||||
wgpu::VertexAttribute {
|
wgpu::VertexAttribute {
|
||||||
offset: mem::size_of::<[f32; 6]>() as wgpu::BufferAddress,
|
offset: mem::size_of::<[f32; 6]>() as wgpu::BufferAddress,
|
||||||
shader_location: 5,
|
shader_location: 5,
|
||||||
|
|
Loading…
Reference in New Issue