Added dynamic starfield tiling
parent
6ec95d17a9
commit
cd6537f971
21
src/main.rs
21
src/main.rs
|
@ -26,6 +26,9 @@ pub const STARFIELD_COUNT: u64 = 100;
|
||||||
// will completely cover a (square) screen. This depends on zoom!
|
// will completely cover a (square) screen. This depends on zoom!
|
||||||
pub const STARFIELD_SIZE: u64 = STARFIELD_PARALLAX_MAX as u64 * 500;
|
pub const STARFIELD_SIZE: u64 = STARFIELD_PARALLAX_MAX as u64 * 500;
|
||||||
|
|
||||||
|
pub const ZOOM_MIN: Pfloat = 200.0;
|
||||||
|
pub const ZOOM_MAX: Pfloat = 2000.0;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
doodad::Doodad,
|
doodad::Doodad,
|
||||||
inputstatus::InputStatus,
|
inputstatus::InputStatus,
|
||||||
|
@ -84,9 +87,6 @@ struct Game {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Game {
|
impl Game {
|
||||||
const ZOOM_MIN: Pfloat = 200.0;
|
|
||||||
const ZOOM_MAX: Pfloat = 2000.0;
|
|
||||||
|
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Game {
|
Game {
|
||||||
last_update: Instant::now(),
|
last_update: Instant::now(),
|
||||||
|
@ -128,8 +128,7 @@ impl Game {
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.input.v_scroll != 0.0 {
|
if self.input.v_scroll != 0.0 {
|
||||||
self.camera.zoom =
|
self.camera.zoom = (self.camera.zoom + self.input.v_scroll).clamp(ZOOM_MIN, ZOOM_MAX);
|
||||||
(self.camera.zoom + self.input.v_scroll).clamp(Self::ZOOM_MIN, Self::ZOOM_MAX);
|
|
||||||
self.input.v_scroll = 0.0;
|
self.input.v_scroll = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,7 +164,7 @@ pub async fn run() -> Result<()> {
|
||||||
let mut gpu = GPUState::new(window).await?;
|
let mut gpu = GPUState::new(window).await?;
|
||||||
let mut game = Game::new();
|
let mut game = Game::new();
|
||||||
|
|
||||||
gpu.on_new_system(&game);
|
gpu.update_starfield_buffer(&game);
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
match event {
|
match event {
|
||||||
|
@ -175,7 +174,7 @@ pub async fn run() -> Result<()> {
|
||||||
match gpu.render(&game) {
|
match gpu.render(&game) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
// Reconfigure the surface if lost
|
// Reconfigure the surface if lost
|
||||||
Err(wgpu::SurfaceError::Lost) => gpu.resize(gpu.window_size),
|
Err(wgpu::SurfaceError::Lost) => gpu.resize(&game, gpu.window_size),
|
||||||
// The system is out of memory, we should probably quit
|
// The system is out of memory, we should probably quit
|
||||||
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
|
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
|
||||||
// All other errors (Outdated, Timeout) should be resolved by the next frame
|
// All other errors (Outdated, Timeout) should be resolved by the next frame
|
||||||
|
@ -205,17 +204,17 @@ pub async fn run() -> Result<()> {
|
||||||
..
|
..
|
||||||
} => game.process_key(state, key),
|
} => game.process_key(state, key),
|
||||||
WindowEvent::MouseInput { state, button, .. } => {
|
WindowEvent::MouseInput { state, button, .. } => {
|
||||||
game.process_click(state, button)
|
game.process_click(state, button);
|
||||||
}
|
}
|
||||||
WindowEvent::MouseWheel { delta, phase, .. } => {
|
WindowEvent::MouseWheel { delta, phase, .. } => {
|
||||||
game.process_scroll(delta, phase)
|
game.process_scroll(delta, phase);
|
||||||
}
|
}
|
||||||
WindowEvent::Resized(physical_size) => {
|
WindowEvent::Resized(physical_size) => {
|
||||||
gpu.resize(*physical_size);
|
gpu.resize(&game, *physical_size);
|
||||||
}
|
}
|
||||||
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
|
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
|
||||||
// new_inner_size is &&mut so we have to dereference it twice
|
// new_inner_size is &&mut so we have to dereference it twice
|
||||||
gpu.resize(**new_inner_size);
|
gpu.resize(&game, **new_inner_size);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ 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_SIZE};
|
use crate::{Game, STARFIELD_COUNT, STARFIELD_PARALLAX_MIN, STARFIELD_SIZE, ZOOM_MAX};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
globaldata::{GlobalData, GlobalDataContent},
|
globaldata::{GlobalData, GlobalDataContent},
|
||||||
|
@ -46,6 +46,8 @@ impl GPUState {
|
||||||
// We can draw at most this many sprites on the screen.
|
// We can draw at most this many sprites on the screen.
|
||||||
// TODO: compile-time option
|
// TODO: compile-time option
|
||||||
pub const SPRITE_INSTANCE_LIMIT: u64 = 100;
|
pub const SPRITE_INSTANCE_LIMIT: u64 = 100;
|
||||||
|
|
||||||
|
// Must be small enough to fit in an i32
|
||||||
pub const STARFIELD_INSTANCE_LIMIT: u64 = STARFIELD_COUNT * 9;
|
pub const STARFIELD_INSTANCE_LIMIT: u64 = STARFIELD_COUNT * 9;
|
||||||
|
|
||||||
pub async fn new(window: Window) -> Result<Self> {
|
pub async fn new(window: Window) -> Result<Self> {
|
||||||
|
@ -188,7 +190,7 @@ impl GPUState {
|
||||||
&self.window
|
&self.window
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize(&mut self, new_size: PhysicalSize<u32>) {
|
pub fn resize(&mut self, game: &Game, new_size: PhysicalSize<u32>) {
|
||||||
if new_size.width > 0 && new_size.height > 0 {
|
if new_size.width > 0 && new_size.height > 0 {
|
||||||
self.window_size = new_size;
|
self.window_size = new_size;
|
||||||
self.window_aspect = new_size.width as f32 / new_size.height as f32;
|
self.window_aspect = new_size.width as f32 / new_size.height as f32;
|
||||||
|
@ -196,6 +198,7 @@ impl GPUState {
|
||||||
self.config.height = new_size.height;
|
self.config.height = new_size.height;
|
||||||
self.surface.configure(&self.device, &self.config);
|
self.surface.configure(&self.device, &self.config);
|
||||||
}
|
}
|
||||||
|
self.update_starfield_buffer(game)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self) {}
|
pub fn update(&mut self) {}
|
||||||
|
@ -253,27 +256,56 @@ impl GPUState {
|
||||||
/// Make a StarfieldInstance for each star that needs to be drawn.
|
/// Make a StarfieldInstance for each star that needs to be drawn.
|
||||||
/// Will panic if STARFIELD_INSTANCE_LIMIT is exceeded.
|
/// Will panic if STARFIELD_INSTANCE_LIMIT is exceeded.
|
||||||
///
|
///
|
||||||
/// This is only called inside self.render()
|
/// Starfield data rarely changes, so this is called only when it's needed.
|
||||||
fn make_starfield_instances(&mut self, game: &Game) -> Vec<StarfieldInstance> {
|
pub fn update_starfield_buffer(&mut self, game: &Game) {
|
||||||
let mut instances = Vec::new();
|
|
||||||
let sz = STARFIELD_SIZE as f32;
|
let sz = STARFIELD_SIZE as f32;
|
||||||
for offset in [
|
|
||||||
Vector2 { x: 0.0, y: 0.0 },
|
// Compute window size in starfield tiles
|
||||||
Vector2 { x: 0.0, y: sz },
|
let mut nw_tile: Point2<i32> = {
|
||||||
Vector2 { x: sz, y: sz },
|
// Game coordinates (relative to camera) of nw corner of screen.
|
||||||
Vector2 { x: sz, y: 0.0 },
|
let clip_nw = Point2::from((self.window_aspect, 1.0)) * ZOOM_MAX / 2.0;
|
||||||
Vector2 { x: sz, y: -sz },
|
|
||||||
Vector2 { x: 0.0, y: -sz },
|
// Adjust for parallax
|
||||||
Vector2 { x: -sz, y: -sz },
|
let v: Point2<f32> = clip_nw * STARFIELD_PARALLAX_MIN;
|
||||||
Vector2 { x: -sz, y: 0.0 },
|
|
||||||
Vector2 { x: -sz, y: sz },
|
// We don't need to adjust coordinates here, since clip_nw is always positive.
|
||||||
] {
|
|
||||||
for s in &game.system.starfield {
|
#[rustfmt::skip]
|
||||||
instances.push(StarfieldInstance {
|
// Compute m = fmod(x, sz)
|
||||||
position: (s.pos + offset).into(),
|
let m: Vector2<f32> = (
|
||||||
parallax: s.parallax,
|
(v.x - (v.x / sz).floor() * sz),
|
||||||
height: s.height,
|
(v.y - (v.y / sz).floor() * sz)
|
||||||
})
|
).into();
|
||||||
|
|
||||||
|
// Now, remainder and convert to "relative tile" coordinates
|
||||||
|
// ( where (0,0) is center tile, (0, 1) is north, etc)
|
||||||
|
let rel = (v - m) / sz;
|
||||||
|
|
||||||
|
// relative coordinates of north-east tile
|
||||||
|
(rel.x.round() as i32, rel.y.round() as i32).into()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Truncate tile grid to buffer size
|
||||||
|
// (The window won't be full of stars if we don't have a large enough instance limit)
|
||||||
|
while (nw_tile.x * 2 + 1) * (nw_tile.y * 2 + 1) > Self::STARFIELD_INSTANCE_LIMIT as i32 {
|
||||||
|
nw_tile -= Vector2::from((1, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all tiles to buffer
|
||||||
|
let mut instances = Vec::new();
|
||||||
|
for x in (-nw_tile.x)..=nw_tile.x {
|
||||||
|
for y in (-nw_tile.y)..=nw_tile.y {
|
||||||
|
let offset = Vector2 {
|
||||||
|
x: sz * x as f32,
|
||||||
|
y: sz * y as f32,
|
||||||
|
};
|
||||||
|
for s in &game.system.starfield {
|
||||||
|
instances.push(StarfieldInstance {
|
||||||
|
position: (s.pos + offset).into(),
|
||||||
|
parallax: s.parallax,
|
||||||
|
height: s.height,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,16 +316,10 @@ impl GPUState {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.starfield_count = instances.len() as u32;
|
self.starfield_count = instances.len() as u32;
|
||||||
return instances;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_new_system(&mut self, game: &Game) {
|
|
||||||
// Create starfield instances
|
|
||||||
let starfield_instances = self.make_starfield_instances(game);
|
|
||||||
self.queue.write_buffer(
|
self.queue.write_buffer(
|
||||||
&self.vertex_buffers.starfield.instances,
|
&self.vertex_buffers.starfield.instances,
|
||||||
0,
|
0,
|
||||||
bytemuck::cast_slice(&starfield_instances),
|
bytemuck::cast_slice(&instances),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ struct GlobalUniform {
|
||||||
|
|
||||||
|
|
||||||
fn fmod(x: vec2<f32>, m: f32) -> vec2<f32> {
|
fn fmod(x: vec2<f32>, m: f32) -> vec2<f32> {
|
||||||
return x - floor(x * (1.0 / m)) * m;
|
return x - floor(x / m) * m;
|
||||||
}
|
}
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
|
@ -64,7 +64,7 @@ fn vertex_shader_main(
|
||||||
// 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 * instance.parallax))
|
(camera_pos / (global.camera_zoom * (instance.parallax)))
|
||||||
/ vec2<f32>(global.window_aspect, 1.0)
|
/ vec2<f32>(global.window_aspect, 1.0)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue