Added dynamic starfield tiling

master
Mark 2023-12-24 09:34:39 -08:00
parent 6ec95d17a9
commit cd6537f971
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
3 changed files with 67 additions and 42 deletions

View File

@ -26,6 +26,9 @@ pub const STARFIELD_COUNT: u64 = 100;
// will completely cover a (square) screen. This depends on zoom!
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::{
doodad::Doodad,
inputstatus::InputStatus,
@ -84,9 +87,6 @@ struct Game {
}
impl Game {
const ZOOM_MIN: Pfloat = 200.0;
const ZOOM_MAX: Pfloat = 2000.0;
fn new() -> Self {
Game {
last_update: Instant::now(),
@ -128,8 +128,7 @@ impl Game {
}
if self.input.v_scroll != 0.0 {
self.camera.zoom =
(self.camera.zoom + self.input.v_scroll).clamp(Self::ZOOM_MIN, Self::ZOOM_MAX);
self.camera.zoom = (self.camera.zoom + self.input.v_scroll).clamp(ZOOM_MIN, ZOOM_MAX);
self.input.v_scroll = 0.0;
}
@ -165,7 +164,7 @@ pub async fn run() -> Result<()> {
let mut gpu = GPUState::new(window).await?;
let mut game = Game::new();
gpu.on_new_system(&game);
gpu.update_starfield_buffer(&game);
event_loop.run(move |event, _, control_flow| {
match event {
@ -175,7 +174,7 @@ pub async fn run() -> Result<()> {
match gpu.render(&game) {
Ok(_) => {}
// 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
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// 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),
WindowEvent::MouseInput { state, button, .. } => {
game.process_click(state, button)
game.process_click(state, button);
}
WindowEvent::MouseWheel { delta, phase, .. } => {
game.process_scroll(delta, phase)
game.process_scroll(delta, phase);
}
WindowEvent::Resized(physical_size) => {
gpu.resize(*physical_size);
gpu.resize(&game, *physical_size);
}
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
// new_inner_size is &&mut so we have to dereference it twice
gpu.resize(**new_inner_size);
gpu.resize(&game, **new_inner_size);
}
_ => {}
}

View File

@ -5,7 +5,7 @@ use std::{iter, rc::Rc};
use wgpu;
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::{
globaldata::{GlobalData, GlobalDataContent},
@ -46,6 +46,8 @@ 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 * 9;
pub async fn new(window: Window) -> Result<Self> {
@ -188,7 +190,7 @@ impl GPUState {
&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 {
self.window_size = new_size;
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.surface.configure(&self.device, &self.config);
}
self.update_starfield_buffer(game)
}
pub fn update(&mut self) {}
@ -253,21 +256,49 @@ impl GPUState {
/// Make a StarfieldInstance for each star that needs to be drawn.
/// Will panic if STARFIELD_INSTANCE_LIMIT is exceeded.
///
/// This is only called inside self.render()
fn make_starfield_instances(&mut self, game: &Game) -> Vec<StarfieldInstance> {
let mut instances = Vec::new();
/// Starfield data rarely changes, so this is called only when it's needed.
pub fn update_starfield_buffer(&mut self, game: &Game) {
let sz = STARFIELD_SIZE as f32;
for offset in [
Vector2 { x: 0.0, y: 0.0 },
Vector2 { x: 0.0, y: sz },
Vector2 { x: sz, y: sz },
Vector2 { x: sz, y: 0.0 },
Vector2 { x: sz, y: -sz },
Vector2 { x: 0.0, y: -sz },
Vector2 { x: -sz, y: -sz },
Vector2 { x: -sz, y: 0.0 },
Vector2 { x: -sz, y: sz },
] {
// Compute window size in starfield tiles
let mut nw_tile: Point2<i32> = {
// Game coordinates (relative to camera) of nw corner of screen.
let clip_nw = Point2::from((self.window_aspect, 1.0)) * ZOOM_MAX / 2.0;
// Adjust for parallax
let v: Point2<f32> = clip_nw * STARFIELD_PARALLAX_MIN;
// We don't need to adjust coordinates here, since clip_nw is always positive.
#[rustfmt::skip]
// Compute m = fmod(x, sz)
let m: Vector2<f32> = (
(v.x - (v.x / sz).floor() * sz),
(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(),
@ -276,6 +307,7 @@ impl GPUState {
})
}
}
}
// Enforce starfield limit
if instances.len() as u64 > Self::STARFIELD_INSTANCE_LIMIT {
@ -284,16 +316,10 @@ impl GPUState {
}
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.vertex_buffers.starfield.instances,
0,
bytemuck::cast_slice(&starfield_instances),
bytemuck::cast_slice(&instances),
);
}

View File

@ -27,7 +27,7 @@ struct GlobalUniform {
fn fmod(x: vec2<f32>, m: f32) -> vec2<f32> {
return x - floor(x * (1.0 / m)) * m;
return x - floor(x / m) * m;
}
@vertex
@ -64,7 +64,7 @@ fn vertex_shader_main(
// Translate
pos = pos + (
// 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)
);