WebGPU Voxel Game

Various fixes/refactors

+128 -79
+1 -1
src/app.rs
··· 4 4 world::map::new, 5 5 world::World, 6 6 }; 7 - use glam::{dvec2, vec2}; 7 + use glam::dvec2; 8 8 use std::sync::Arc; 9 9 use winit::{ 10 10 application::ApplicationHandler,
+89 -34
src/gfx.rs
··· 4 4 mod resources; 5 5 mod texture; 6 6 7 - use std::borrow::Borrow; 8 - use std::f32::consts::{FRAC_PI_2, FRAC_PI_3}; 9 - use egui::text_selection::text_cursor_state::slice_char_range; 10 - use egui::Key::A; 11 - use egui_wgpu::ScreenDescriptor; 12 - use glam::{uvec2, vec2, vec3, IVec3, Mat4, Quat, Vec3, Vec4, Vec4Swizzles}; 13 - use light::LightUniform; 14 - use std::ops::Deref; 15 - use std::{f32::consts::PI, path::Path, sync::Arc}; 16 - use wgpu::util::DeviceExt; 17 - use wgpu::BindingResource; 18 - use winit::dpi::PhysicalPosition; 19 - use winit::keyboard::Key; 20 - use winit::{ 21 - dpi::PhysicalSize, 22 - event::{ElementState, KeyEvent, WindowEvent}, 23 - event_loop::EventLoopProxy, 24 - keyboard::{KeyCode, PhysicalKey}, 25 - window::Window, 26 - }; 27 7 use crate::gfx::camera::CameraUniform; 28 8 use crate::gfx::model::DrawLight; 29 - use crate::world::chunk::{sl3get, CHUNK_SIZE}; 9 + use crate::world::chunk::{sl3get, sl3get_opt, CHUNK_SIZE}; 30 10 use crate::{ 31 11 app::WASM_WIN_SIZE, 32 12 gfx::model::Vertex, 33 13 gui::EguiRenderer, 34 - world::map::{Block, WorldMap}, 14 + world::map::{BlockKind, WorldMap}, 35 15 world::World, 36 16 Instance, InstanceRaw, 37 17 }; 18 + use egui_wgpu::ScreenDescriptor; 19 + use glam::{uvec2, vec3, IVec3, Quat, Vec3}; 20 + use std::f32::consts::{FRAC_PI_2, FRAC_PI_3}; 21 + use std::sync::Arc; 22 + use wgpu::util::DeviceExt; 23 + use wgpu::BindingResource; 24 + use winit::{ 25 + dpi::PhysicalSize, 26 + event::{KeyEvent, WindowEvent}, 27 + event_loop::EventLoopProxy, 28 + keyboard::PhysicalKey, 29 + window::Window, 30 + }; 38 31 39 32 pub struct CameraState { 40 33 pub object: camera::Camera, ··· 325 318 label: Some("texture_bind_group_layout"), 326 319 }); 327 320 328 - let camera = camera::Camera::new(vec3(50., 20., 50.), -std::f32::consts::FRAC_PI_2, std::f32::consts::FRAC_PI_3); 321 + let camera = camera::Camera::new( 322 + vec3(50., 20., 50.), 323 + -std::f32::consts::FRAC_PI_2, 324 + std::f32::consts::FRAC_PI_3, 325 + ); 329 326 let projection = camera::Projection::new( 330 327 uvec2(surface_config.width, surface_config.height).as_vec2(), 331 328 FRAC_PI_2, ··· 378 375 379 376 let mut light_uniform = camera::CameraUniform::new(); 380 377 light_uniform.update_view_proj(&light, &light_projection); 381 - 382 - 383 378 384 379 let shadow_map = texture::Texture::create_depth_texture( 385 380 &device, ··· 670 665 671 666 let mut i = _3diter 672 667 .filter_map(|(x, y, z)| { 673 - if let Block::Air = sl3get(&chunk.blocks, x, y, z) { 668 + if let BlockKind::Air = sl3get(&chunk.blocks, x, y, z) { 669 + return None; 670 + } 671 + 672 + // lookup if node is surrounded by nodes 673 + let mut do_not_occlude = false; 674 + let mut sum = vec![]; 675 + 676 + for (x_, y_, z_) in [ 677 + (-1_i32, 0_i32, 0_i32), 678 + (1, 0, 0), 679 + (0, -1, 0), 680 + (0, 1, 0), 681 + (0, 0, -1), 682 + (0, 0, 1), 683 + ] { 684 + if x == 0 685 + || y == 0 686 + || z == 0 687 + || x == CHUNK_SIZE.0 - 1 688 + || y == CHUNK_SIZE.2 - 1 689 + || z == CHUNK_SIZE.1 - 1 690 + { 691 + do_not_occlude = true; 692 + break; 693 + } 694 + 695 + let (x, y, z) = (x as i32 + x_, y as i32 + y_, z as i32 + z_); 696 + if x < 0 || y < 0 || z < 0 697 + // || x == CHUNK_SIZE.0 as i32 - 1 698 + // || y == CHUNK_SIZE.2 as i32 - 1 699 + // || z == CHUNK_SIZE.1 as i32 - 1 700 + { 701 + continue; 702 + } 703 + 704 + if let Some(block) = 705 + sl3get_opt(&chunk.blocks, x as usize, y as usize, z as usize) 706 + { 707 + sum.push(block) 708 + } 709 + } 710 + 711 + if !do_not_occlude && sum.iter().all(|b| *b == BlockKind::Brick) { 674 712 return None; 675 713 } 676 714 ··· 680 718 let mapping = |n| SPACE_BETWEEN * (n as f32 - CHUNK_SIZE.0 as f32 / 2.0); 681 719 let position = vec3( 682 720 mapping(x) + chunk_offset.x, 683 - -(mapping(y) + chunk_offset.y), 721 + (mapping(y) + chunk_offset.y), 684 722 mapping(z) + chunk_offset.z, 685 723 ); 686 724 ··· 692 730 693 731 instances.append(&mut i); 694 732 } 733 + 734 + // WGPU Crashes with an empty instance buffer, add a small item out of camera vfar 735 + if instances.is_empty() { 736 + instances.push(Instance { 737 + position: Vec3::splat(9999.), 738 + rotation: Quat::from_axis_angle(Vec3::Y, 0.0), 739 + }) 740 + } 695 741 instances 696 742 } 697 743 ··· 716 762 egui: &mut Option<EguiRenderer>, 717 763 window: Arc<Window>, 718 764 world: &mut World, 719 - dt: instant::Duration 765 + dt: instant::Duration, 720 766 ) -> Result<(), wgpu::SurfaceError> { 721 767 let output = self.surface.get_current_texture()?; 722 768 ··· 863 909 864 910 pub fn update(&mut self, world: &mut World, dt: instant::Duration) { 865 911 // Camera update 912 + self.camera.controller.update_camera( 913 + &mut self.camera.object, 914 + dt, 915 + world, 916 + &mut self.object.remake, 917 + ); 866 918 self.camera 867 - .controller 868 - .update_camera(&mut self.camera.object, dt, world, &mut self.object.remake); 869 - self.camera.uniform.update_view_proj(&self.camera.object, &self.camera.projection); 919 + .uniform 920 + .update_view_proj(&self.camera.object, &self.camera.projection); 870 921 871 922 self.queue.write_buffer( 872 923 &self.forward_pass.uniform_bufs[0], ··· 875 926 ); 876 927 877 928 // Light update 878 - self.light.object.position = Quat::from_axis_angle(vec3(0.0, 0.0, 1.0), self.interact.sun_speed * dt.as_secs_f32()) 879 - * self.light.object.position; 880 - self.light.uniform.update_view_proj(&self.light.object, &self.light.projection); 929 + self.light.object.position = Quat::from_axis_angle( 930 + vec3(0.0, 0.0, 1.0), 931 + self.interact.sun_speed * dt.as_secs_f32(), 932 + ) * self.light.object.position; 933 + self.light 934 + .uniform 935 + .update_view_proj(&self.light.object, &self.light.projection); 881 936 882 937 self.queue.write_buffer( 883 938 &self.forward_pass.uniform_bufs[1],
+5 -9
src/gfx/camera.rs
··· 1 - use glam::{ivec3, vec3, vec4, IVec3, Mat4, Vec2, Vec3, Vec4, Vec4Swizzles}; 1 + use glam::{ivec3, vec3, IVec3, Mat4, Vec2, Vec3, Vec4}; 2 2 use instant::Duration; 3 3 use itertools::Itertools; 4 - use rollgrid::math::Convert; 5 - use std::f32::consts::FRAC_2_PI; 6 4 use winit::{ 7 5 dpi::PhysicalPosition, 8 - event::{ElementState, KeyEvent, MouseScrollDelta, WindowEvent}, 6 + event::{ElementState, MouseScrollDelta}, 9 7 keyboard::KeyCode, 10 - keyboard::PhysicalKey, 11 8 }; 12 9 13 - use crate::world::{chunk::Chunk, World}; 10 + use crate::world::{chunk::Chunk, map::RENDER_GRID_SIZE, World}; 14 11 15 - use super::Gfx; 16 12 17 13 const MAX_CAMERA_PITCH: f32 = (3.0 / std::f32::consts::PI) - 0.0001; 18 14 ··· 194 190 const BLOCK_UNIT_SIZE: i32 = 32; 195 191 let chunk_relative = IVec3::from( 196 192 (camera.position.x as i32 / BLOCK_UNIT_SIZE, 197 - -(camera.position.y as i32 / BLOCK_UNIT_SIZE), 193 + camera.position.y as i32 / BLOCK_UNIT_SIZE, 198 194 camera.position.z as i32 / BLOCK_UNIT_SIZE, 199 - )) + IVec3::splat(-2); 195 + )) + IVec3::splat(-(RENDER_GRID_SIZE as i32/2)); 200 196 if chunk_relative != world.map.chunks.offset().into() { 201 197 world 202 198 .map
+1 -1
src/gfx/light.rs
··· 1 1 use std::f32::consts::PI; 2 - use glam::{vec3, Mat4, Vec3, Vec4}; 2 + use glam::{vec3, Mat4, Vec3}; 3 3 4 4 #[repr(C)] 5 5 #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
-1
src/gfx/model.rs
··· 1 1 use super::texture; 2 2 use std::ops::Range; 3 - use wasm_bindgen::__rt::__wbindgen_exn_store; 4 3 5 4 pub trait Vertex { 6 5 fn desc() -> wgpu::VertexBufferLayout<'static>;
+2 -2
src/gui.rs
··· 1 - use egui::{FontFamily, FontId, RichText}; 1 + use egui::{FontId, RichText}; 2 2 use egui_winit::EventResponse; 3 - use glam::{ivec2, ivec3, IVec2}; 3 + use glam::ivec3; 4 4 use winit::window::Window; 5 5 6 6 use crate::{
+1 -2
src/lib.rs
··· 36 36 37 37 impl InstanceRaw { 38 38 fn desc() -> wgpu::VertexBufferLayout<'static> { 39 - use std::mem; 39 + 40 40 41 41 wgpu::VertexBufferLayout { 42 42 array_stride: size_of::<InstanceRaw>() as wgpu::BufferAddress, ··· 102 102 let event_loop = EventLoop::with_user_event().build().unwrap_throw(); 103 103 104 104 let mut app = app::Application::new(&event_loop, "BL0CK"); 105 - // let mut app = DebugWinitApp::new(); 106 105 event_loop.run_app(&mut app).unwrap(); 107 106 }
-1
src/world.rs
··· 1 1 pub(crate) mod map; 2 2 pub(crate) mod encoded; 3 3 pub(crate) mod chunk; 4 - use std::fs::File; 5 4 6 5 use bincode::{Decode, Encode}; 7 6
+15 -12
src/world/chunk.rs
··· 1 - use super::map::Block; 2 - use base64::prelude::BASE64_STANDARD; 3 - use base64::Engine; 1 + use super::map::BlockKind; 4 2 use bincode::{Decode, Encode}; 5 3 use glam::{ivec3, IVec3}; 6 4 use itertools::Itertools; ··· 21 19 pub(crate) const CHUNK_SIZE: (usize, usize, usize) = (16, 16, 16); 22 20 23 21 // A [Block; X*Y*Z] would be a much more efficient datatype, but, well... 24 - pub type Slice3 = [Block; CHUNK_SIZE.0 * CHUNK_SIZE.1 * CHUNK_SIZE.2]; 22 + pub type Slice3 = [BlockKind; CHUNK_SIZE.0 * CHUNK_SIZE.1 * CHUNK_SIZE.2]; 25 23 26 - pub fn sl3get(sl3: &Slice3, x: usize, y: usize, z: usize) -> Block { 24 + pub fn sl3get(sl3: &Slice3, x: usize, y: usize, z: usize) -> BlockKind { 27 25 sl3[y + CHUNK_SIZE.2 * (z + CHUNK_SIZE.1 * x)] 28 26 } 29 - pub fn sl3set(sl3: &mut Slice3, x: usize, y: usize, z: usize, new: Block) { 27 + pub fn sl3get_opt(sl3: &Slice3, x: usize, y: usize, z: usize) -> Option<BlockKind> { 28 + sl3.get(y + CHUNK_SIZE.2 * (z + CHUNK_SIZE.1 * x)).copied() 29 + } 30 + pub fn sl3set(sl3: &mut Slice3, x: usize, y: usize, z: usize, new: BlockKind) { 30 31 sl3[y + CHUNK_SIZE.2 * (z + CHUNK_SIZE.1 * x)] = new; 31 32 } 32 33 ··· 53 54 54 55 // Pretty arbitrary numbers! Just trying to get something interesting 55 56 let n = (((sines / 4. + 0.5) * CHUNK_SIZE.2 as f32).round() as i32) 56 - <= tile_pos_worldspace.y as _; 57 + <= -tile_pos_worldspace.y as _; 57 58 58 59 if n { 59 - Block::Brick 60 + BlockKind::Brick 60 61 } else { 61 - Block::Air 62 + BlockKind::Air 62 63 } 63 64 }) 64 65 .collect_array() ··· 76 77 .blocks 77 78 .iter() 78 79 .map(|b| match b { 79 - Block::Air => Block::Brick, 80 - Block::Brick => Block::Air, 80 + BlockKind::Air => BlockKind::Brick, 81 + BlockKind::Brick => BlockKind::Air, 81 82 }) 82 83 .collect_array() 83 84 .unwrap(), ··· 115 116 // We are going to use LocalStorage for web. I don't like it either. 116 117 #[cfg(target_arch = "wasm32")] 117 118 { 119 + use base64::prelude::{BASE64_STANDARD, Engine}; 118 120 let encoded = bincode::encode_to_vec(self, config)?; 119 121 let encoded = BASE64_STANDARD.encode(encoded); 120 122 ··· 145 147 } 146 148 #[cfg(target_arch = "wasm32")] 147 149 { 150 + use base64::prelude::{BASE64_STANDARD, Engine}; 148 151 let store = web_sys::window().unwrap().local_storage().unwrap().unwrap(); 149 152 if let Ok(Some(s)) = store.get(&file_name) { 150 153 let s = BASE64_STANDARD.decode(s)?; ··· 200 203 #[cfg(not(target_arch = "wasm32"))] 201 204 { 202 205 // range 203 - let r: i32 = 8; 206 + let r: i32 = 8; // normally 8 or so 204 207 let _3diter = itertools::iproduct!(-r..r, -r..r, -r..r); 205 208 206 209 for (x, y, z) in _3diter {
+14 -16
src/world/map.rs
··· 1 + use super::chunk::Chunk; 1 2 use bincode::{Decode, Encode}; 2 3 use glam::ivec3; 3 4 #[cfg(not(target_arch = "wasm32"))] ··· 6 7 Rng, 7 8 }; 8 9 use rollgrid::rollgrid3d::RollGrid3D; 9 - use super::chunk::Chunk; 10 10 11 - #[derive(Copy, Clone, Default, Encode, Decode)] 11 + #[derive(Copy, Clone, Default, Encode, Decode, PartialEq)] 12 12 #[repr(u32)] 13 - pub enum Block { 13 + pub enum BlockKind { 14 14 #[default] 15 15 Air = 0, 16 16 Brick, 17 17 } 18 + pub struct Block { 19 + kind: BlockKind, 20 + } 18 21 19 22 #[cfg(not(target_arch = "wasm32"))] 20 - impl Distribution<Block> for StandardUniform { 21 - fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Block { 23 + impl Distribution<BlockKind> for StandardUniform { 24 + fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> BlockKind { 22 25 match rng.random_range(0..=1) { 23 - 0 => Block::Air, 24 - _ => Block::Brick, 26 + 0 => BlockKind::Air, 27 + _ => BlockKind::Brick, 25 28 } 26 29 } 27 30 } ··· 29 32 pub struct WorldMap { 30 33 pub chunks: RollGrid3D<Chunk>, 31 34 } 35 + pub(crate) const RENDER_GRID_SIZE: usize = 15; 32 36 pub fn new() -> WorldMap { 33 - const INITIAL_GENERATION_SIZE: usize = 5; 34 - 35 37 WorldMap { 36 38 chunks: RollGrid3D::new( 37 39 ( 38 - INITIAL_GENERATION_SIZE as _, 39 - INITIAL_GENERATION_SIZE as _, 40 - INITIAL_GENERATION_SIZE as _, 40 + RENDER_GRID_SIZE as _, 41 + RENDER_GRID_SIZE as _, 42 + RENDER_GRID_SIZE as _, 41 43 ), 42 44 (0, 0, 0), 43 45 |(x, y, z)| Chunk::load(ivec3(x, y, z)).unwrap(), 44 46 ), 45 47 } 46 48 } 47 - 48 - 49 - 50 -