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! // 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);
} }
_ => {} _ => {}
} }

View File

@ -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),
); );
} }

View File

@ -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)
); );