WebGPU Voxel Game
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 333 lines 11 kB view raw
1use super::map::BlockKind; 2use crate::{ 3 gfx::model::{Material, Mesh, ModelVertex}, 4 Instance, 5}; 6use block_mesh::{ 7 greedy_quads, visible_block_faces, GreedyQuadsBuffer, MergeVoxel, Voxel, VoxelVisibility, 8 RIGHT_HANDED_Y_UP_CONFIG, 9}; 10use glam::{ivec3, vec3, IVec3, Quat, UVec3, Vec3}; 11use itertools::{iproduct, izip, Itertools}; 12use ndshape::{ConstShape, ConstShape3u32, Shape}; 13use wgpu::util::DeviceExt; 14 15// I have arbitrarily decided that this is (x,z,y) where +y is up. 16pub(crate) const CHUNK_SIZE: (usize, usize, usize) = (16, 16, 16); 17pub(crate) const CHUNK_SIZEEE: usize = 16; 18 19// A [Block; X*Y*Z] would be a much more efficient datatype, but, well... 20pub type Slice3 = [BlockKind; CHUNK_SIZE.0 * CHUNK_SIZE.1 * CHUNK_SIZE.2]; 21 22pub fn sl3get(sl3: &Slice3, x: usize, y: usize, z: usize) -> BlockKind { 23 sl3[z + CHUNK_SIZE.2 * (y + CHUNK_SIZE.1 * x)] 24} 25pub fn sl3get_opt(sl3: &Slice3, x: usize, y: usize, z: usize) -> Option<BlockKind> { 26 sl3.get(z + CHUNK_SIZE.2 * (y + CHUNK_SIZE.1 * x)).copied() 27} 28// pub fn sl3set(sl3: &mut Slice3, x: usize, y: usize, z: usize, new: BlockKind) { 29// sl3[y + CHUNK_SIZE.2 * (z + CHUNK_SIZE.1 * x)] = new; 30// } 31 32pub struct ChunkOf<T: Copy> { 33 inner: [T; CHUNK_SIZE.0 * CHUNK_SIZE.1 * CHUNK_SIZE.2], 34} 35impl<T: Copy> ChunkOf<T> { 36 pub fn get(&self, pos: UVec3) -> Option<T> { 37 self.inner 38 .get((pos.y as usize) + CHUNK_SIZEEE * (pos.z as usize + CHUNK_SIZEEE * pos.x as usize)) 39 .copied() 40 } 41 pub fn map<F, U: Copy>(&self, f: F) -> ChunkOf<U> 42 where 43 F: FnMut(T) -> U, 44 { 45 ChunkOf::<U> { 46 inner: self.inner.map(f), 47 } 48 } 49} 50 51#[allow(unused)] 52pub enum ChunkScramble { 53 Normal, 54 Inverse, 55 Random, 56} 57 58type ChunkShape = ndshape::ConstShape3u32<18, 18, 18>; 59 60#[derive(Clone)] 61pub struct Chunk { 62 pub blocks: Slice3, 63} 64 65#[derive(Clone, Copy, Eq, PartialEq)] 66struct BoolVoxel(bool); 67 68const EMPTY: BoolVoxel = BoolVoxel(false); 69const FULL: BoolVoxel = BoolVoxel(true); 70 71impl Voxel for BoolVoxel { 72 fn get_visibility(&self) -> VoxelVisibility { 73 if *self == EMPTY { 74 VoxelVisibility::Empty 75 } else { 76 VoxelVisibility::Opaque 77 } 78 } 79} 80 81impl MergeVoxel for BoolVoxel { 82 type MergeValue = Self; 83 84 fn merge_value(&self) -> Self::MergeValue { 85 *self 86 } 87} 88 89impl Chunk { 90 pub fn mesh(&self, chunk_pos: IVec3, material: usize, device: &wgpu::Device) -> Mesh { 91 let blocks = ChunkOf::<BlockKind> { inner: self.blocks }; 92 93 let size = CHUNK_SIZEEE as i32; 94 let voxels = iproduct!(-1..size + 1, -1..size + 1, -1..size + 1) 95 .map(|(x, y, z)| { 96 let idx = ivec3(x, y, z).as_uvec3(); 97 let block = blocks.get(idx); 98 match block { 99 Some(BlockKind::Brick) => FULL, 100 _ => EMPTY, 101 } 102 }) 103 .collect_vec(); 104 105 let mut output = GreedyQuadsBuffer::new(voxels.len()); 106 greedy_quads( 107 &voxels[..], 108 &ChunkShape {}, 109 [0; 3], 110 [17; 3], 111 &RIGHT_HANDED_Y_UP_CONFIG.faces, 112 &mut output, 113 ); 114 115 let coordinate_spaces = RIGHT_HANDED_Y_UP_CONFIG; 116 117 let num_indices = output.quads.num_quads() * 6; 118 let num_vertices = output.quads.num_quads() * 4; 119 let mut indices = Vec::with_capacity(num_indices); 120 let mut vertices = Vec::with_capacity(num_vertices); 121 let mut positions = Vec::with_capacity(num_vertices / 3); 122 // let mut normals = Vec::with_capacity(num_vertices); 123 // let mut tex_coords = Vec::with_capacity(num_vertices); 124 for (group, face) in output 125 .quads 126 .groups 127 .into_iter() 128 .zip(coordinate_spaces.faces.into_iter()) 129 { 130 for quad in group.into_iter() { 131 let ps = face.quad_mesh_positions(&quad, 1.0); 132 let ns = face.quad_mesh_normals(); 133 let ts = face.tex_coords(coordinate_spaces.u_flip_face, true, &quad); 134 135 vertices.extend(izip!(ps, ns, ts).map(|(position, normal, tex_coords)| { 136 let Vec3 { x, y, z } = chunk_pos.as_vec3() * Vec3::splat(16.0); 137 let [px, py, pz] = position; 138 ModelVertex { 139 position: [x + px, y + py, z + pz], 140 tex_coords, 141 normal, 142 } 143 })); 144 indices.extend_from_slice(&face.quad_mesh_indices(positions.len() as u32)); 145 146 // hack 147 positions.extend_from_slice(&face.quad_mesh_positions(&quad, 1.0)); 148 } 149 } 150 151 let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { 152 label: Some(&format!("Chunk Vertex Buffer")), 153 contents: bytemuck::cast_slice(&vertices), 154 usage: wgpu::BufferUsages::VERTEX, 155 }); 156 157 let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { 158 label: Some(&format!("Chunk Index Buffer")), 159 contents: bytemuck::cast_slice(&indices), 160 usage: wgpu::BufferUsages::INDEX, 161 }); 162 163 Mesh { 164 name: "Chunk".to_string(), 165 vertex_buffer, 166 index_buffer, 167 num_elements: num_indices as _, 168 material, 169 } 170 } 171 172 fn generate_normal(world_pos: IVec3) -> Chunk { 173 let blocks = itertools::iproduct!(0..CHUNK_SIZE.0, 0..CHUNK_SIZE.1, 0..CHUNK_SIZE.2) 174 .map(|(x, z, y)| { 175 let tile_pos = ivec3(x as _, y as _, z as _); 176 let tile_pos_worldspace = (tile_pos + (world_pos * CHUNK_SIZE.0 as i32)).as_vec3(); 177 178 let sines = 179 f32::sin(tile_pos_worldspace.x * 0.1) + f32::sin(tile_pos_worldspace.z * 0.1); 180 181 // Pretty arbitrary numbers! Just trying to get something interesting 182 let n = (((sines / 4. + 0.5) * CHUNK_SIZE.2 as f32).round() as i32) 183 <= -tile_pos_worldspace.y as _; 184 185 if n { 186 BlockKind::Brick 187 } else { 188 BlockKind::Air 189 } 190 }) 191 .collect_array() 192 .unwrap(); 193 194 Chunk { blocks } 195 } 196 197 fn generate_callback(method: ChunkScramble) -> fn(IVec3) -> Chunk { 198 use ChunkScramble as C; 199 match method { 200 C::Normal => Chunk::generate_normal, 201 C::Inverse => |p| { 202 let blocks = Chunk::generate_normal(p) 203 .blocks 204 .iter() 205 .map(|b| match b { 206 BlockKind::Air => BlockKind::Brick, 207 BlockKind::Brick => BlockKind::Air, 208 }) 209 .collect_array() 210 .unwrap(); 211 Chunk { blocks } 212 }, 213 C::Random => |p| { 214 #[cfg(not(target_arch = "wasm32"))] 215 let blocks = { 216 use rand::Rng; 217 rand::rng().random() 218 }; 219 220 Chunk { blocks } 221 }, 222 } 223 } 224 pub fn generate(map_pos: IVec3, method: ChunkScramble) -> Chunk { 225 Chunk::generate_callback(method)(map_pos) 226 } 227 228 // pub fn save( 229 // &self, 230 // map_pos: IVec3, 231 // conn: &mut ConnectionOnlyOnNative, 232 // ) -> Result<(), Box<dyn std::error::Error>> { 233 // let config = bincode::config::standard(); 234 235 // #[cfg(not(target_arch = "wasm32"))] 236 // { 237 // let encoded = bincode::encode_to_vec(self, config)?; 238 239 // let mut stmt = conn.prepare_cached( 240 // r#" 241 // INSERT INTO chunks (x,y,z,data) 242 // VALUES (?,?,?,?) 243 // "#, 244 // )?; 245 // stmt.insert((map_pos.x, map_pos.y, map_pos.z, encoded))?; 246 // } 247 248 // // We are going to use LocalStorage for web. I don't like it either. 249 // #[cfg(target_arch = "wasm32")] 250 // { 251 // use base64::prelude::{Engine, BASE64_STANDARD}; 252 // let encoded = bincode::encode_to_vec(self, config)?; 253 // let encoded = BASE64_STANDARD.encode(encoded); 254 255 // let store = web_sys::window().unwrap().local_storage().unwrap().unwrap(); 256 // store.set(&file_name, &encoded); 257 // } 258 // Ok(()) 259 // } 260 // fn load_from_file( 261 // map_pos: IVec3, 262 // conn: &mut ConnectionOnlyOnNative, 263 // ) -> Result<Option<Chunk>, Box<dyn std::error::Error>> { 264 // let config = bincode::config::standard(); 265 // let file_hash = calculate_hash(&map_pos); 266 // let file_name = format!("chunk_{}.bl0ck", file_hash); 267 268 // #[cfg(not(target_arch = "wasm32"))] 269 // { 270 // // let file_path = Path::new("./save/chunk/").join(Path::new(&file_name)); 271 // // if file_path.exists() { 272 // // log::warn!("Load Chunk!"); 273 // // let mut file = File::open(file_path).unwrap(); 274 275 // let mut stmt = conn.prepare_cached( 276 // r#" 277 // SELECT (data) from chunks 278 // WHERE (x,y,z) == (?,?,?) 279 // "#, 280 // )?; 281 // let i: Vec<u8> = 282 // match stmt.query_row((map_pos.x, map_pos.y, map_pos.z), |f| f.get("data")) { 283 // Ok(x) => x, 284 // Err(rusqlite::Error::QueryReturnedNoRows) => { 285 // return Ok(None); 286 // } 287 // Err(e) => { 288 // return Err(e.into()); 289 // } 290 // }; 291 292 // let (decoded, _) = bincode::decode_from_slice(i.as_slice(), config)?; 293 294 // Ok(Some(decoded)) 295 // // } else { 296 // // log::warn!("Chunk not loaded!"); 297 // // Ok(None) 298 // // } 299 // } 300 // #[cfg(target_arch = "wasm32")] 301 // { 302 // use base64::prelude::{Engine, BASE64_STANDARD}; 303 // let store = web_sys::window().unwrap().local_storage().unwrap().unwrap(); 304 // if let Ok(Some(s)) = store.get(&file_name) { 305 // let s = BASE64_STANDARD.decode(s)?; 306 // let (decoded, _) = bincode::decode_from_slice(&s[..], config)?; 307 308 // Ok(Some(decoded)) 309 // } else { 310 // Ok(None) 311 // } 312 // } 313 // } 314 315 pub fn load(map_pos: IVec3) -> Result<Chunk, Box<dyn std::error::Error>> { 316 // #[cfg(not(target_arch = "wasm32"))] 317 // let cached = CHUNK_FILE_CACHE.lock().unwrap().contains_key(&map_pos); 318 // #[cfg(target_arch = "wasm32")] 319 // let cached = false; 320 321 let chunk = Chunk::generate(map_pos, ChunkScramble::Normal); 322 Ok(chunk) 323 324 // if cached { 325 // // log::warn!("Cache hit!"); 326 // #[cfg(not(target_arch = "wasm32"))] 327 // //return Ok(CHUNK_FILE_CACHE.lock().unwrap()[&map_pos]); 328 // #[cfg(target_arch = "wasm32")] 329 // return unreachable!(); 330 // } else { 331 // } 332 } 333}