From ab9f4c83d0fe6cbc0af906fa6e56faa97d6863b4 Mon Sep 17 00:00:00 2001 From: Piotrek Date: Wed, 12 May 2021 16:17:38 +0200 Subject: [PATCH] WIP generating chunks at player position --- Cargo.lock | 41 ++---- Cargo.toml | 31 ++-- src/game/mod.rs | 33 +++-- src/game/world/chunk.rs | 13 +- src/game/world/generator.rs | 2 +- src/game/world/mod.rs | 139 +++++++++--------- src/main.rs | 3 +- src/renderer/mod.rs | 14 +- .../{content_view.rs => renderer_view.rs} | 14 +- src/renderer/ui.rs | 43 ++++-- 10 files changed, 186 insertions(+), 147 deletions(-) rename src/renderer/{content_view.rs => renderer_view.rs} (52%) diff --git a/Cargo.lock b/Cargo.lock index c6f757b..360f4ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -506,18 +506,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" -[[package]] -name = "flate2" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" -dependencies = [ - "cfg-if 1.0.0", - "crc32fast", - "libc", - "miniz_oxide 0.4.4", -] - [[package]] name = "fnv" version = "1.0.7" @@ -1809,6 +1797,20 @@ name = "serde" version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "shaderc" @@ -2042,20 +2044,18 @@ dependencies = [ "byteorder", "cgmath", "dirs", - "flate2", "fs_extra", "futures", "glob", - "image", - "linked-hash-map", "lzzzz", "noise", "rand 0.8.3", + "serde", "shaderc", + "toml", "wgpu", "wgpu_glyph", "winit", - "yaml-rust", ] [[package]] @@ -2438,12 +2438,3 @@ name = "xml-rs" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a" - -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] diff --git a/Cargo.toml b/Cargo.toml index c7a4f6f..50f69ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,21 +5,34 @@ authors = ["Piotrek "] edition = "2018" [dependencies] + +# Rendering and window management winit = "0.24" wgpu = "0.7" wgpu_glyph = "0.11" -futures = "0.3" -bytemuck = { version = "1.5", features = [ "derive" ] } -byteorder = "1.4" -image = "0.23.14" + +# Math cgmath = "0.18" rand = "0.8" noise = "0.7" -dirs = "3.0" -flate2 = "1.0" -lzzzz = "0.8" -yaml-rust = "0.4" -linked-hash-map = "0.5" + +# Saving and loading to and from files +dirs = "3.0" # Retrieving common paths (%APPDATA%) +serde = { version = "1.0", features = ["derive"] } # Serialization and deserialization (world.cfg) +toml = "0.5" # Toml file format (used with serde) +lzzzz = "0.8" # Lz4 compression + +# Other +futures = "0.3" +bytemuck = { version = "1.5", features = [ "derive" ] } +byteorder = "1.4" + +# Deprecated +#image = "0.23.14" +#flate2 = "1.0" +#yaml-rust = "0.4" +#linked-hash-map = "0.5" + [build-dependencies] anyhow = "1.0" diff --git a/src/game/mod.rs b/src/game/mod.rs index ba2fc8b..3573ee9 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -1,11 +1,9 @@ mod camera_controller; mod player; pub mod world; -use std::time::Instant; -use crate::renderer::content_view::ContentView; +use crate::renderer::renderer_view::RendererView; use cgmath::Point3; use winit::event::Event; -use crate::renderer::{camera::CameraTransform}; use player::Player; use world::World; @@ -18,19 +16,20 @@ pub enum GameState { pub struct Game { state: GameState, player: Player, + #[allow(unused)] world: World, - prev_pos: Point3 + prev_pos: Option> } impl Game { pub fn new() -> Self { - let mut app = Self { + Self { state: GameState::Paused, player: Player::new(), world: World::new("default".to_string()), - prev_pos: Point3{ x:0, y:0, z:0 } - }; + prev_pos: None + } // // Try to load world from file // let timer = Instant::now(); @@ -56,24 +55,26 @@ impl Game { // app.world.save(); // println!("Saved in {}", timer.elapsed().as_secs_f32()); // } - - - println!("App initialized"); - app } - pub fn update(&mut self, delta: f32, content: &mut impl ContentView) { + pub fn update(&mut self, delta: f32, renderer: &mut impl RendererView) { // Update camera controller - let camera = content.get_camera_transform(); + let camera = renderer.get_camera_transform(); self.player.camera_controller.update(delta, camera); // Update chunks let pos = camera.get_position(); let pos = Point3{x: pos.x as i32, y: pos.y as i32, z: pos.z as i32}; - if pos != self.prev_pos { - self.prev_pos = pos; + if self.prev_pos == None || self.prev_pos != Some(pos) { + self.prev_pos = Some(pos); + renderer.get_ui().set_text("World", 0, format!("Position: {}, {}, {}", pos.x, pos.y, pos.z)); - // Load sphere of chunks around player + // Load sphere of chunks around player (TODO) + let chunk_pos = Point3{x: pos.x / 32, y: pos.y / 32, z: pos.z / 32}; + if self.world.load_chunk(chunk_pos) { + + renderer.write(, block: &WorldBlock) + } } } diff --git a/src/game/world/chunk.rs b/src/game/world/chunk.rs index c98a37a..9290c26 100644 --- a/src/game/world/chunk.rs +++ b/src/game/world/chunk.rs @@ -28,6 +28,7 @@ impl WorldChunk { } } + #[allow(unused)] pub fn from_bytes(bytes: Vec) -> Self { let mut instance = Self::new(); @@ -59,6 +60,7 @@ impl WorldChunk { instance } + #[allow(unused)] pub fn to_bytes(&self) -> Vec { // Header @@ -82,6 +84,7 @@ impl WorldChunk { bytes } + #[allow(unused)] pub fn all_blocks(&self) -> Vec<(Point3, &WorldBlock)> { self.nodes.iter().enumerate() .filter(|(_, n)| **n != 0) @@ -101,7 +104,7 @@ impl WorldChunk { .collect() } - pub fn get_block(&mut self, block_pos: &Point3) -> &mut WorldBlock { + pub fn get_block_mut(&mut self, block_pos: &Point3) -> &mut WorldBlock { // Get block index let index = (block_pos.x + SIZE as u32 * (block_pos.y + SIZE as u32 * block_pos.z)) as usize; let mut value = self.nodes[index]; @@ -114,4 +117,12 @@ impl WorldChunk { // Return block reference &mut self.blocks[(value-1) as usize] } + + pub fn get_block(&self, block_pos: &Point3) -> &WorldBlock { + // Get block index + let index = (block_pos.x + SIZE as u32 * (block_pos.y + SIZE as u32 * block_pos.z)) as usize; + let value = self.nodes[index]; + // Return block reference + &self.blocks[(value-1) as usize] + } } \ No newline at end of file diff --git a/src/game/world/generator.rs b/src/game/world/generator.rs index afae184..46991ff 100644 --- a/src/game/world/generator.rs +++ b/src/game/world/generator.rs @@ -39,7 +39,7 @@ impl WorldGen { /* * Generate block */ - pub fn generate(block_pos: &Point3, block: &mut WorldBlock) { + pub fn generate(block_pos: &Point3, block: &mut WorldBlock) { let mut rng = rand::thread_rng(); let noise = Perlin::new(); diff --git a/src/game/world/mod.rs b/src/game/world/mod.rs index 85b3a6a..e4ce073 100644 --- a/src/game/world/mod.rs +++ b/src/game/world/mod.rs @@ -1,7 +1,6 @@ mod generator; pub mod chunk; pub mod block; -use linked_hash_map::LinkedHashMap; use std::path::PathBuf; use std::io::Write; use std::io::Read; @@ -11,29 +10,37 @@ use std::collections::HashMap; use generator::WorldGen; use chunk::WorldChunk; use block::WorldBlock; -use yaml_rust::{Yaml, YamlEmitter}; +#[derive(serde::Serialize, serde::Deserialize, Debug)] +struct WorldData { + #[serde(default)] + pub name: String +} pub struct World { - name: String, - chunks: HashMap, WorldChunk>, + data: WorldData, + chunks: HashMap, WorldChunk>, +} + +fn point_abs(p: &Point3) -> Point3 { + Point3 {x: p.x.abs() as u32, y: p.y.abs() as u32, z: p.z.abs() as u32 } } impl World { pub fn new(name: String) -> Self { Self { - name, + data: WorldData { name }, chunks: HashMap::new(), } } - pub fn all_blocks(&self) -> Vec<(Point3, &WorldBlock)> { + pub fn all_blocks(&self) -> Vec<(Point3, &WorldBlock)> { self.chunks.iter().map(|(chunk_pos, chunk)| { - let off = chunk_pos * chunk::SIZE as u32; - let result: Vec<(Point3, &WorldBlock)> = chunk.all_blocks().iter().map(|(pos, block)| { + let off = chunk_pos * chunk::SIZE as i32; + let result: Vec<(Point3, &WorldBlock)> = chunk.all_blocks().iter().map(|(pos, block)| { ( - Point3{x: off.x+pos.x, y: off.y+pos.y, z: off.z+pos.z}, + Point3{x: off.x+pos.x as i32, y: off.y+pos.y as i32, z: off.z+pos.z as i32}, *block ) }).collect(); @@ -58,77 +65,69 @@ impl World { } // Save metadata - let mut data: LinkedHashMap = LinkedHashMap::new(); - data.insert(Yaml::String("name".to_string()), Yaml::String(self.name.clone())); - - let mut data_str = String::new(); - let mut emit = YamlEmitter::new(&mut data_str); - emit.dump(&Yaml::Hash(data)).expect("Failed to encode yaml"); - - let data_path: PathBuf = [dir.to_str().unwrap(), "world.yml"].iter().collect(); + let data_str = toml::to_string(&self.data).unwrap(); + let data_path: PathBuf = [dir.to_str().unwrap(), "world.dat"].iter().collect(); let mut data_file = fs::File::create(data_path).unwrap(); - data_file.write_all(data_str.as_bytes()).expect("Failed to write"); + data_file.write_all(data_str.as_bytes()).expect("Failed to write world metadata"); + } + + /* + * Loads chunk and displays it in the world. + * It will try to load the chunk from file, and if that fails it will generate the chunk and save it. + */ + pub fn load_chunk(&mut self, pos: Point3) -> Option<&WorldChunk> { + // Abort if chunk is already loaded + if let Some(_) = self.chunks.get(&pos) { + return None; + } + + // Try loading chunk from file + let filename = format!("chunk_{}_{}_{}.dat", pos.x, pos.y, pos.z); + let filepath = dirs::data_local_dir().unwrap().join("Voxelgame").with_file_name(filename); + if filepath.is_file() { + let mut buff = Vec::new(); + let mut file = fs::File::open(filepath).expect("Failed to open file"); + file.read_to_end(&mut buff).expect("Failed to read file"); + let chunk = WorldChunk::from_bytes(buff); + self.chunks.insert(pos, chunk); + println!("Chunk at {:?} loaded", pos); + return Some(&chunk); + } + + // Try generating chunk + let mut chunk = WorldChunk::new(); + for x in 0..32 { + for z in 0..32 { + let bpos = &Point3::{x,y:0,z}; + let block = chunk.get_block_mut(&bpos); + let wpos = &Point3::{ x: pos.x+bpos.x as i32, y: pos.y+bpos.y as i32, z: pos.z+bpos.z as i32}; + WorldGen::generate(wpos, block); + } + } + self.chunks.insert(pos, chunk); + println!("Chunk at {:?} generated", pos); + Some(&chunk) } pub fn load(&mut self) -> bool { - // Get directory - let mut dir = dirs::data_local_dir().unwrap(); - dir.push("Voxelgame"); - if !dir.is_dir() { - println!("Not exists: {:?}", dir); - return false; - } - - // Load all chunks - let mut count = 0; - for entry in fs::read_dir(dir).unwrap() { - let path: PathBuf = entry.unwrap().path(); - if path.extension().unwrap().to_str() != Some("dat") { - continue; - } - let stem = path.file_stem().unwrap().to_str().unwrap(); - if !stem.starts_with("chunk") { - continue; - } - - let s: Vec<&str> = stem.split("_").collect(); - let pos = Point3{x: s[1].parse::().unwrap(), y: s[2].parse::().unwrap(), z: s[3].parse::().unwrap()}; - - let mut bytes = Vec::new(); - let mut file = fs::File::open(path).expect("Failed to open file"); - file.read_to_end(&mut bytes).expect("Failed to read file"); - let chunk = WorldChunk::from_bytes(bytes); - - println!("Load: Chunk {}, {}, {}", pos.x, pos.y, pos.z); - self.chunks.insert(pos, chunk); - count += 1; - } - // Load metadata - return count > 0; - } - - pub fn gen_block(&mut self, block_wpos: &Point3) -> &WorldBlock { - // Split world space to chunk and block space - let ch_pos = block_wpos / chunk::SIZE as u32; - let bl_pos = block_wpos % chunk::SIZE as u32; - // Create chunk if does not exist - if let None = self.chunks.get(&ch_pos) { - self.chunks.insert(ch_pos.clone(), WorldChunk::new()); - println!("Alloc: Chunk [{}, {}, {}]", ch_pos.x, ch_pos.y, ch_pos.z); + let filepath = dirs::data_local_dir().unwrap().join("Voxelgame").with_file_name("world.dat"); + if let Ok(mut file) = fs::File::open(filepath) { + let mut buf = Vec::new(); + file.read_to_end(&mut buf).unwrap(); + if let Ok(data) = toml::from_slice::(&buf[..]) { + self.data = data; + return true; + } } - // Generate block for chunk - let chunk = self.chunks.get_mut(&ch_pos).unwrap(); - let block = chunk.get_block(&bl_pos); - WorldGen::generate(&block_wpos, block); - block + return false; } #[allow(unused)] - pub fn get_block(&mut self, block_wpos: &Point3) -> Option<&WorldBlock> { + pub fn get_block(&mut self, block_wpos: &Point3) -> Option<&WorldBlock> { // Split world space to chunk and block space - let ch_pos = block_wpos / chunk::SIZE as u32; - let bl_pos = block_wpos % chunk::SIZE as u32; + let ch_pos = block_wpos / chunk::SIZE as i32; + let bl_pos = point_abs(block_wpos) % chunk::SIZE as u32; // Return block if exists if let Some(chunk) = self.chunks.get_mut(&ch_pos) { return Some(chunk.get_block(&bl_pos)); diff --git a/src/main.rs b/src/main.rs index fe2ed42..65eba93 100644 --- a/src/main.rs +++ b/src/main.rs @@ -89,8 +89,7 @@ fn main() { if elapsed >= 0.5 { let mut fps = (fps_counter as f64) / elapsed; fps = (fps*100.0_f64).floor() / 100.0; - renderer.ui.set_text(0, format!("FPS: {}", fps)); - + renderer.ui.set_text("Performance", 0, format!("FPS: {}", fps)); fps_timer = std::time::Instant::now(); fps_counter = 0; } diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 19ec906..7e2f61f 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -6,7 +6,7 @@ mod passes; use passes::{RaytracePass, PostprocessPass}; pub mod camera; use camera::Camera; -pub mod content_view; +pub mod renderer_view; mod ui; use ui::UserInterface; @@ -120,12 +120,12 @@ impl Renderer { // Update content let timer = Instant::now(); self.buffers.content.update(&self.queue); - self.ui.set_text(2, format!("Update content: {}ms", timer.elapsed().as_micros() as f64 / 1000.0)); + self.ui.set_text("Performance", 1, format!("Update content: {}ms", timer.elapsed().as_micros() as f64 / 1000.0)); // Get next frame to render to let timer = Instant::now(); let frame = self.swapchain.get_current_frame()?.output; - self.ui.set_text(3, format!("Swapchain get frame: {}ms", timer.elapsed().as_micros() as f64 / 1000.0)); + self.ui.set_text("Performance", 2, format!("Swapchain get frame: {}ms", timer.elapsed().as_micros() as f64 / 1000.0)); // Create encoder that will build command buffer for us let timer = Instant::now(); @@ -133,18 +133,18 @@ impl Renderer { self.raytrace_pass.render(&mut encoder, &mut self.buffers, &self.texture);//&frame.view); self.postprocess_pass.render(&mut encoder, &frame.view); self.ui.render(&self.device, &mut encoder, &frame.view, 1280, 720); - self.ui.set_text(4, format!("Build renderpass: {}ms", timer.elapsed().as_micros() as f64 / 1000.0)); + self.ui.set_text("Performance", 3, format!("Build renderpass: {}ms", timer.elapsed().as_micros() as f64 / 1000.0)); // Submit encoder (command buffer) let timer = Instant::now(); self.queue.submit(std::iter::once(encoder.finish())); self.ui.recall(); - self.ui.set_text(5, format!("Submit queue: {}ms", timer.elapsed().as_micros() as f64 / 1000.0)); + self.ui.set_text("Performance", 4, format!("Submit queue: {}ms", timer.elapsed().as_micros() as f64 / 1000.0)); // Stats let content_stats = self.buffers.content.stats(); - self.ui.set_text(7, format!("Nodes: {}MB", content_stats.node_tex_size)); - self.ui.set_text(8, format!("Blocks: {} / {} MB ({}/{})", content_stats.block_tex_used, content_stats.block_tex_size, content_stats.block_used, content_stats.block_count)); + self.ui.set_text("Renderer", 0, format!("Nodes: {}MB", content_stats.node_tex_size)); + self.ui.set_text("Renderer", 1, format!("Blocks: {} / {} MB ({}/{})", content_stats.block_tex_used, content_stats.block_tex_size, content_stats.block_used, content_stats.block_count)); // Return ok Ok(()) diff --git a/src/renderer/content_view.rs b/src/renderer/renderer_view.rs similarity index 52% rename from src/renderer/content_view.rs rename to src/renderer/renderer_view.rs index a39224e..26b9874 100644 --- a/src/renderer/content_view.rs +++ b/src/renderer/renderer_view.rs @@ -1,20 +1,26 @@ +use crate::renderer::UserInterface; use crate::renderer::camera::CameraTransform; use crate::Renderer; use crate::game::world::block::WorldBlock; use cgmath::Point3; -pub trait ContentView { +pub trait RendererView { fn write(&mut self, block_pos: &Point3, block: &WorldBlock); - fn get_camera_transform(&mut self) -> &mut CameraTransform; + fn get_camera_transform(&mut self) -> &mut dyn CameraTransform; + fn get_ui(&mut self) -> &mut UserInterface; } -impl ContentView for Renderer { +impl RendererView for Renderer { fn write(&mut self, block_pos: &Point3, block: &WorldBlock) { self.buffers.content.write(&self.queue, block_pos, block) } - fn get_camera_transform(&mut self) -> &mut CameraTransform { + fn get_camera_transform(&mut self) -> &mut dyn CameraTransform { &mut self.camera } + + fn get_ui(&mut self) -> &mut UserInterface { + &mut self.ui + } } \ No newline at end of file diff --git a/src/renderer/ui.rs b/src/renderer/ui.rs index 6665358..2f6a14f 100644 --- a/src/renderer/ui.rs +++ b/src/renderer/ui.rs @@ -5,6 +5,9 @@ use futures::executor::LocalPool; use futures::task::SpawnExt; const WHITE: [f32; 4] = [1.0, 1.0, 1.0, 1.0]; +const BLUE: [f32; 4] = [0.0, 0.2, 1.0, 1.0]; +const GREEN: [f32; 4] = [0.1, 1.0, 0.0, 1.0]; + pub struct UserInterface { // Glyph drawing @@ -12,7 +15,7 @@ pub struct UserInterface { local_pool: LocalPool, brush: GlyphBrush<()>, // Texts - texts: HashMap + texts: HashMap> } impl UserInterface { @@ -27,20 +30,36 @@ impl UserInterface { } } - pub fn set_text(&mut self, index: u32, text: String) { - self.texts.insert(index, text); + fn print(brush: &mut GlyphBrush<()>, line: u32, text: &str, color: [f32; 4]) { + let t = Text::new(text).with_color(color); + brush.queue(Section { + screen_position: (5 as f32, (5 + line*15) as f32), + bounds: (t.scale.x * t.text.len() as f32, t.scale.y), + text: vec![t], + ..Section::default() + }); + } + + pub fn set_text(&mut self, cat: &str, line: usize, text: String) { + // Category + let c = &cat.to_string(); + if !self.texts.contains_key(c) {self.texts.insert(c.clone(), Vec::new());} + // Texts + let v = self.texts.get_mut(c).unwrap(); + while v.len() < line+1 { v.push(String::new()); } + v[line] = text; } pub fn render(&mut self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, width: u32, height: u32) { - for (id, text) in self.texts.iter() { - - let text = Text::new(text).with_color(WHITE); - self.brush.queue(Section { - screen_position: (5 as f32, (5 + id*15) as f32), - bounds: (text.scale.x * text.text.len() as f32, text.scale.y), - text: vec![text], - ..Section::default() - }); + let mut line = 0; + for (cat, texts) in self.texts.iter_mut() { + UserInterface::print(&mut self.brush, line, cat, GREEN); + line += 1; + for text in texts.iter() { + UserInterface::print(&mut self.brush, line, text, WHITE); + line += 1; + } + line += 1; } // Draw