Move chunk loading/saving logic to World, sort chunk loading based on distance to camera
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
a8936d9046
commit
0ca35638fc
5 changed files with 213 additions and 137 deletions
60
Cargo.lock
generated
60
Cargo.lock
generated
|
@ -518,6 +518,16 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "fs2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.15"
|
||||
|
@ -799,6 +809,12 @@ dependencies = [
|
|||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||
|
||||
[[package]]
|
||||
name = "glow"
|
||||
version = "0.9.0"
|
||||
|
@ -970,6 +986,15 @@ dependencies = [
|
|||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.0"
|
||||
|
@ -1124,16 +1149,16 @@ dependencies = [
|
|||
"futures",
|
||||
"gltf",
|
||||
"image",
|
||||
"itertools",
|
||||
"itertools 0.10.0",
|
||||
"log",
|
||||
"noise",
|
||||
"rayon",
|
||||
"rmp-serde",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"sled",
|
||||
"wgpu",
|
||||
"winit",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1694,6 +1719,23 @@ version = "0.4.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
|
||||
|
||||
[[package]]
|
||||
name = "sled"
|
||||
version = "0.34.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d0132f3e393bcb7390c60bb45769498cf4550bcb7a21d7f95c02b69f6362cdc"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
"fs2",
|
||||
"fxhash",
|
||||
"libc",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slotmap"
|
||||
version = "0.4.0"
|
||||
|
@ -2065,18 +2107,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.8.2+zstd.1.5.0"
|
||||
version = "0.5.4+zstd.1.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c83508bcbbdc9c3abcf77e8e56773d3ffcd2479e0933caab2e7d6b5a9e183aae"
|
||||
checksum = "69996ebdb1ba8b1517f61387a883857818a66c8a295f487b1ffd8fd9d2c82910"
|
||||
dependencies = [
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "4.1.0+zstd.1.5.0"
|
||||
version = "2.0.6+zstd.1.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d30375f78e185ca4c91930f42ea2c0162f9aa29737032501f93b79266d985ae7"
|
||||
checksum = "98aa931fb69ecee256d44589d19754e61851ae4769bf963b385119b1cc37a49e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"zstd-sys",
|
||||
|
@ -2084,10 +2126,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "1.6.0+zstd.1.5.0"
|
||||
version = "1.4.18+zstd.1.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2141bed8922b427761470e6bbfeff255da94fa20b0bbeab0d9297fcaf71e3aa7"
|
||||
checksum = "a1e6e8778706838f43f771d80d37787cb2fe06dafe89dd3aebaf6721b9eaec81"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"glob",
|
||||
"itertools 0.9.0",
|
||||
"libc",
|
||||
]
|
||||
|
|
|
@ -23,7 +23,7 @@ serde_repr = "0.1.7"
|
|||
rmp-serde = "0.15.4"
|
||||
itertools = "0.10.0"
|
||||
serde = { version = "1.0.126", features = ["derive"] }
|
||||
zstd = "0.8.2"
|
||||
sled = { version = "0.34.6", features = ["compression"] }
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
|
62
src/chunk.rs
62
src/chunk.rs
|
@ -1,8 +1,4 @@
|
|||
use std::{
|
||||
collections::VecDeque,
|
||||
io::{Read, Write},
|
||||
usize,
|
||||
};
|
||||
use std::{collections::VecDeque, usize};
|
||||
|
||||
use crate::{geometry::Geometry, quad::Quad, vertex::BlockVertex};
|
||||
use ahash::{AHashMap, AHashSet};
|
||||
|
@ -10,11 +6,10 @@ use cgmath::{Point3, Vector3};
|
|||
use noise::utils::{NoiseMapBuilder, PlaneMapBuilder};
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
|
||||
use serde::Serialize;
|
||||
use serde::{
|
||||
de::{SeqAccess, Visitor},
|
||||
ser::{SerializeSeq, Serializer},
|
||||
Deserialize,
|
||||
Deserialize, Serialize,
|
||||
};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
|
||||
|
@ -131,7 +126,7 @@ impl<'de> Deserialize<'de> for Chunk {
|
|||
}
|
||||
|
||||
impl Chunk {
|
||||
pub fn generate(chunk_x: i32, chunk_y: i32, chunk_z: i32) -> Self {
|
||||
pub fn generate(&mut self, chunk_x: isize, chunk_y: isize, chunk_z: isize) {
|
||||
let fbm = noise::Fbm::new();
|
||||
|
||||
const TERRAIN_NOISE_SCALE: f64 = 0.1 / 16.0 * CHUNK_SIZE as f64;
|
||||
|
@ -162,42 +157,41 @@ impl Chunk {
|
|||
)
|
||||
.build();
|
||||
|
||||
let mut blocks: ChunkBlocks = [[[Default::default(); CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE];
|
||||
for z in 0..CHUNK_SIZE {
|
||||
for x in 0..CHUNK_SIZE {
|
||||
let v = terrain_noise.get_value(x, z) * 20.0 + 128.0;
|
||||
let v = v.round() as i32;
|
||||
let v = v.round() as isize;
|
||||
|
||||
let s = stone_noise.get_value(x, z) * 20.0 + 4.5;
|
||||
let s = (s.round() as i32).min(10).max(3);
|
||||
let s = (s.round() as isize).min(10).max(3);
|
||||
|
||||
let stone_max = (v - s - chunk_y * CHUNK_SIZE as i32).min(CHUNK_SIZE as i32);
|
||||
let stone_max = (v - s - chunk_y * CHUNK_ISIZE).min(CHUNK_ISIZE);
|
||||
for y in 0..stone_max {
|
||||
blocks[y as usize][z][x] = Some(Block {
|
||||
self.blocks[y as usize][z][x] = Some(Block {
|
||||
block_type: BlockType::Stone,
|
||||
});
|
||||
}
|
||||
|
||||
let dirt_max = (v - chunk_y * CHUNK_SIZE as i32).min(CHUNK_SIZE as i32);
|
||||
let dirt_max = (v - chunk_y * CHUNK_ISIZE).min(CHUNK_ISIZE);
|
||||
for y in stone_max.max(0)..dirt_max {
|
||||
blocks[y as usize][z][x] = Some(Block {
|
||||
self.blocks[y as usize][z][x] = Some(Block {
|
||||
block_type: BlockType::Dirt,
|
||||
});
|
||||
}
|
||||
|
||||
if dirt_max >= 0 && dirt_max < CHUNK_SIZE as i32 {
|
||||
blocks[dirt_max as usize][z][x] = Some(Block {
|
||||
if dirt_max >= 0 && dirt_max < CHUNK_ISIZE {
|
||||
self.blocks[dirt_max as usize][z][x] = Some(Block {
|
||||
block_type: BlockType::Grass,
|
||||
});
|
||||
}
|
||||
|
||||
if chunk_y == 0 {
|
||||
blocks[0][z][x] = Some(Block {
|
||||
self.blocks[0][z][x] = Some(Block {
|
||||
block_type: BlockType::Bedrock,
|
||||
});
|
||||
}
|
||||
if chunk_y < 128 / CHUNK_SIZE as i32 {
|
||||
for layer in blocks.iter_mut() {
|
||||
if chunk_y < 128 / CHUNK_ISIZE {
|
||||
for layer in self.blocks.iter_mut() {
|
||||
if layer[z][x].is_none() {
|
||||
layer[z][x] = Some(Block {
|
||||
block_type: BlockType::Water,
|
||||
|
@ -207,8 +201,6 @@ impl Chunk {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self { blocks }
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
|
@ -400,26 +392,22 @@ impl Chunk {
|
|||
Self::quads_to_geometry(quads)
|
||||
}
|
||||
|
||||
pub fn save(&self, position: Point3<isize>) -> anyhow::Result<()> {
|
||||
pub fn save(&self, position: Point3<isize>, store: &sled::Db) -> anyhow::Result<()> {
|
||||
let data = rmp_serde::encode::to_vec_named(self)?;
|
||||
let compressed = zstd::block::compress(&data, 0)?;
|
||||
|
||||
let path = format!("chunks/{}_{}_{}.bin", position.x, position.y, position.z);
|
||||
let mut file = std::fs::File::create(&path)?;
|
||||
file.write(&compressed)?;
|
||||
|
||||
let key = format!("{}_{}_{}", position.x, position.y, position.z);
|
||||
store.insert(key, data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load(&mut self, position: Point3<isize>) -> anyhow::Result<()> {
|
||||
let path = format!("chunks/{}_{}_{}.bin", position.x, position.y, position.z);
|
||||
let mut file = std::fs::File::open(&path)?;
|
||||
|
||||
let mut compressed = Vec::new();
|
||||
file.read_to_end(&mut compressed)?;
|
||||
let data = zstd::block::decompress(&compressed, 1024 * 1024)?;
|
||||
pub fn load(&mut self, position: Point3<isize>, store: &sled::Db) -> anyhow::Result<bool> {
|
||||
let key = format!("{}_{}_{}", position.x, position.y, position.z);
|
||||
|
||||
if let Some(data) = store.get(key)? {
|
||||
*self = rmp_serde::decode::from_slice(&data)?;
|
||||
Ok(())
|
||||
Ok(false)
|
||||
} else {
|
||||
self.generate(position.x, position.y, position.z);
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
use std::{
|
||||
collections::VecDeque,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use ahash::AHashMap;
|
||||
use cgmath::{EuclideanSpace, InnerSpace, Point3, Rad, Vector2, Vector3};
|
||||
use wgpu::{
|
||||
util::{BufferInitDescriptor, DeviceExt},
|
||||
|
@ -38,15 +34,11 @@ pub struct WorldState {
|
|||
pub time_bind_group: wgpu::BindGroup,
|
||||
pub world: World,
|
||||
|
||||
pub chunk_buffers: AHashMap<Point3<isize>, GeometryBuffers>,
|
||||
pub chunk_save_queue: VecDeque<Point3<isize>>,
|
||||
pub chunk_load_queue: VecDeque<Point3<isize>>,
|
||||
time: Time,
|
||||
time_buffer: wgpu::Buffer,
|
||||
wireframe: bool,
|
||||
shader: wgpu::ShaderModule,
|
||||
render_pipeline_layout: wgpu::PipelineLayout,
|
||||
pub highlighted: Option<(Point3<isize>, Vector3<i32>)>,
|
||||
|
||||
pub forward_pressed: bool,
|
||||
pub backward_pressed: bool,
|
||||
|
@ -222,18 +214,19 @@ impl WorldState {
|
|||
})
|
||||
}
|
||||
|
||||
/// TODO Move to World
|
||||
pub fn update_world_geometry(&mut self, render_context: &RenderContext) {
|
||||
let instant = Instant::now();
|
||||
|
||||
let world_geometry = self.world.to_geometry(self.highlighted);
|
||||
self.chunk_buffers.clear();
|
||||
let world_geometry = self.world.to_geometry(self.world.highlighted);
|
||||
self.world.chunk_buffers.clear();
|
||||
for (chunk_position, chunk_geometry) in world_geometry {
|
||||
let buffers = GeometryBuffers::from_geometry(
|
||||
render_context,
|
||||
&chunk_geometry,
|
||||
BufferUsage::empty(),
|
||||
);
|
||||
self.chunk_buffers.insert(chunk_position, buffers);
|
||||
self.world.chunk_buffers.insert(chunk_position, buffers);
|
||||
}
|
||||
|
||||
let elapsed = instant.elapsed();
|
||||
|
@ -258,23 +251,6 @@ impl WorldState {
|
|||
));
|
||||
}
|
||||
|
||||
pub fn update_chunk_geometry(
|
||||
&mut self,
|
||||
render_context: &RenderContext,
|
||||
chunk_position: Point3<isize>,
|
||||
) {
|
||||
let chunk = &mut self.world.chunks.get(&chunk_position).unwrap();
|
||||
let offset = chunk_position * CHUNK_ISIZE;
|
||||
let geometry = chunk.to_geometry(
|
||||
offset,
|
||||
World::highlighted_for_chunk(self.highlighted, &chunk_position).as_ref(),
|
||||
);
|
||||
|
||||
let buffers =
|
||||
GeometryBuffers::from_geometry(render_context, &geometry, BufferUsage::empty());
|
||||
self.chunk_buffers.insert(chunk_position, buffers);
|
||||
}
|
||||
|
||||
pub fn toggle_wireframe(&mut self, render_context: &RenderContext) {
|
||||
self.wireframe = !self.wireframe;
|
||||
self.render_pipeline = Self::create_render_pipeline(
|
||||
|
@ -286,8 +262,6 @@ impl WorldState {
|
|||
}
|
||||
|
||||
pub fn new(render_context: &RenderContext) -> WorldState {
|
||||
let world = World::generate();
|
||||
|
||||
let texture_manager = Self::create_textures(render_context);
|
||||
|
||||
let (camera, projection) = Self::create_camera(render_context);
|
||||
|
@ -297,6 +271,8 @@ impl WorldState {
|
|||
|
||||
let (time, time_buffer, time_layout, time_bind_group) = Self::create_time(render_context);
|
||||
|
||||
let world = World::new();
|
||||
|
||||
let shader = render_context.device.create_shader_module(
|
||||
&(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("shader"),
|
||||
|
@ -317,10 +293,8 @@ impl WorldState {
|
|||
&time_layout,
|
||||
],
|
||||
});
|
||||
|
||||
let render_pipeline =
|
||||
Self::create_render_pipeline(&render_context, &shader, &render_pipeline_layout, false);
|
||||
|
||||
let depth_texture = Texture::create_depth_texture(render_context, "depth_texture");
|
||||
|
||||
let mut world_state = Self {
|
||||
|
@ -340,11 +314,8 @@ impl WorldState {
|
|||
time_bind_group,
|
||||
|
||||
world,
|
||||
chunk_buffers: AHashMap::new(),
|
||||
chunk_load_queue: VecDeque::new(),
|
||||
chunk_save_queue: VecDeque::new(),
|
||||
|
||||
wireframe: false,
|
||||
highlighted: None,
|
||||
|
||||
up_speed: 0.0,
|
||||
sprinting: false,
|
||||
|
@ -352,7 +323,7 @@ impl WorldState {
|
|||
backward_pressed: false,
|
||||
left_pressed: false,
|
||||
right_pressed: false,
|
||||
creative: false,
|
||||
creative: true,
|
||||
};
|
||||
|
||||
world_state.update_world_geometry(render_context);
|
||||
|
@ -399,7 +370,8 @@ impl WorldState {
|
|||
let camera_pos = self.camera.position.to_vec();
|
||||
let camera_pos = Vector2::new(camera_pos.x, camera_pos.z);
|
||||
|
||||
for (position, buffers) in &self.chunk_buffers {
|
||||
// TODO Move to World
|
||||
for (position, buffers) in &self.world.chunk_buffers {
|
||||
let pos = (position * CHUNK_ISIZE).cast().unwrap();
|
||||
let pos = Vector2::new(pos.x, pos.z);
|
||||
if (pos - camera_pos).magnitude() > 300.0 {
|
||||
|
@ -419,6 +391,7 @@ impl WorldState {
|
|||
render_pass
|
||||
.set_index_buffer(index_buffer.unwrap().slice(..), wgpu::IndexFormat::Uint32);
|
||||
render_pass.draw_indexed(0..self.world.npc.indices.len() as u32, 0, 0..1);
|
||||
triangle_count += self.world.npc.indices.len() / 3;
|
||||
}
|
||||
|
||||
triangle_count
|
||||
|
@ -436,31 +409,33 @@ impl WorldState {
|
|||
}
|
||||
}
|
||||
|
||||
/// TODO Move to World
|
||||
fn update_aim(&mut self, render_context: &RenderContext) {
|
||||
let camera = &self.camera;
|
||||
|
||||
let old = self.highlighted;
|
||||
let old = self.world.highlighted;
|
||||
let new = self.world.raycast(camera.position, camera.direction());
|
||||
|
||||
let old_chunk = old.map(|(pos, _)| pos.map(|n| n.div_euclid(CHUNK_ISIZE)));
|
||||
let new_chunk = new.map(|(pos, _)| pos.map(|n| n.div_euclid(CHUNK_ISIZE)));
|
||||
|
||||
if old != new {
|
||||
self.highlighted = new;
|
||||
self.world.highlighted = new;
|
||||
|
||||
if let Some(old_chunk_) = old_chunk {
|
||||
self.update_chunk_geometry(render_context, old_chunk_);
|
||||
self.world.update_chunk_geometry(render_context, old_chunk_);
|
||||
}
|
||||
|
||||
if let Some(new_chunk_) = new_chunk {
|
||||
// Don't update the same chunk twice
|
||||
if old_chunk != new_chunk {
|
||||
self.update_chunk_geometry(render_context, new_chunk_);
|
||||
self.world.update_chunk_geometry(render_context, new_chunk_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO Move to World
|
||||
pub fn input_mouse_button(&mut self, button: &MouseButton, render_context: &RenderContext) {
|
||||
let camera = &self.camera;
|
||||
|
||||
|
@ -468,7 +443,8 @@ impl WorldState {
|
|||
if let Some((pos, face_normal)) = world.raycast(camera.position, camera.direction()) {
|
||||
if button == &MouseButton::Left {
|
||||
world.set_block(pos.x as isize, pos.y as isize, pos.z as isize, None);
|
||||
self.update_chunk_geometry(render_context, pos / CHUNK_ISIZE);
|
||||
self.world
|
||||
.update_chunk_geometry(render_context, pos / CHUNK_ISIZE);
|
||||
} else if button == &MouseButton::Right {
|
||||
let new_pos = pos.cast().unwrap() + face_normal;
|
||||
|
||||
|
@ -481,7 +457,8 @@ impl WorldState {
|
|||
}),
|
||||
);
|
||||
|
||||
self.update_chunk_geometry(render_context, pos / CHUNK_ISIZE);
|
||||
self.world
|
||||
.update_chunk_geometry(render_context, pos / CHUNK_ISIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -494,8 +471,6 @@ impl WorldState {
|
|||
VirtualKeyCode::A => self.left_pressed = pressed,
|
||||
VirtualKeyCode::D => self.right_pressed = pressed,
|
||||
VirtualKeyCode::F2 if pressed => self.creative = !self.creative,
|
||||
VirtualKeyCode::F3 if pressed => self.chunk_save_queue.extend(self.world.chunks.keys()),
|
||||
VirtualKeyCode::F4 if pressed => self.chunk_load_queue.extend(self.world.chunks.keys()),
|
||||
VirtualKeyCode::Space => {
|
||||
self.up_speed = if pressed {
|
||||
if self.creative {
|
||||
|
@ -565,26 +540,11 @@ impl WorldState {
|
|||
}
|
||||
|
||||
pub fn update(&mut self, dt: Duration, render_context: &RenderContext) {
|
||||
if let Some(position) = self.chunk_load_queue.pop_front() {
|
||||
let chunk = self.world.chunks.entry(position).or_default();
|
||||
if let Err(err) = chunk.load(position) {
|
||||
eprintln!("Failed to load chunk {:?}: {:?}", position, err);
|
||||
} else {
|
||||
self.update_chunk_geometry(render_context, position);
|
||||
println!("Loaded chunk {:?}", position);
|
||||
}
|
||||
} else if let Some(position) = self.chunk_save_queue.pop_front() {
|
||||
let chunk = self.world.chunks.get(&position).unwrap();
|
||||
if let Err(err) = chunk.save(position) {
|
||||
eprintln!("Failed to save chunk {:?}: {:?}", position, err);
|
||||
} else {
|
||||
println!("Saved chunk {:?}", position);
|
||||
}
|
||||
}
|
||||
|
||||
self.update_position(dt);
|
||||
self.update_aim(render_context);
|
||||
|
||||
self.world.update(render_context, &self.camera);
|
||||
|
||||
self.view
|
||||
.update_view_projection(&self.camera, &self.projection);
|
||||
render_context
|
||||
|
|
136
src/world.rs
136
src/world.rs
|
@ -1,45 +1,124 @@
|
|||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
use crate::{
|
||||
chunk::{Block, Chunk, CHUNK_ISIZE, CHUNK_SIZE},
|
||||
geometry::Geometry,
|
||||
camera::Camera,
|
||||
chunk::{Block, Chunk, CHUNK_ISIZE},
|
||||
geometry::{Geometry, GeometryBuffers},
|
||||
npc::Npc,
|
||||
render_context::RenderContext,
|
||||
vertex::BlockVertex,
|
||||
};
|
||||
use ahash::AHashMap;
|
||||
use cgmath::{EuclideanSpace, InnerSpace, Point3, Vector3};
|
||||
use rayon::prelude::*;
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
use wgpu::BufferUsage;
|
||||
|
||||
pub struct World {
|
||||
pub chunks: HashMap<Point3<isize>, Chunk>,
|
||||
pub npc: Npc,
|
||||
|
||||
pub chunk_database: sled::Db,
|
||||
pub chunk_save_queue: VecDeque<Point3<isize>>,
|
||||
pub chunk_load_queue: VecDeque<Point3<isize>>,
|
||||
pub chunk_generate_queue: VecDeque<Point3<isize>>,
|
||||
pub chunk_buffers: AHashMap<Point3<isize>, GeometryBuffers>,
|
||||
|
||||
pub highlighted: Option<(Point3<isize>, Vector3<i32>)>,
|
||||
}
|
||||
|
||||
const WORLD_SIZE: Vector3<usize> = Vector3::new(
|
||||
8 * 16 / CHUNK_SIZE,
|
||||
16 * 16 / CHUNK_SIZE,
|
||||
8 * 16 / CHUNK_SIZE,
|
||||
);
|
||||
pub const RENDER_DISTANCE: isize = 8;
|
||||
pub const WORLD_HEIGHT: isize = 16 * 16 / CHUNK_ISIZE;
|
||||
|
||||
impl World {
|
||||
pub fn generate() -> Self {
|
||||
pub fn new() -> Self {
|
||||
let chunks = HashMap::new();
|
||||
let npc = Npc::load();
|
||||
let half: Vector3<isize> = WORLD_SIZE.cast().unwrap() / 2;
|
||||
|
||||
let coords: Vec<_> =
|
||||
itertools::iproduct!(-half.x..half.x, 0..WORLD_SIZE.y as isize, -half.z..half.z)
|
||||
.collect();
|
||||
let chunk_database = sled::Config::new()
|
||||
.path("chunks")
|
||||
.mode(sled::Mode::HighThroughput)
|
||||
.use_compression(true)
|
||||
.open()
|
||||
.unwrap();
|
||||
|
||||
let chunks: HashMap<_, _> = coords
|
||||
.par_iter()
|
||||
.map(|&(x, y, z)| {
|
||||
(
|
||||
Point3::new(x, y, z),
|
||||
Chunk::generate(x as i32, y as i32, z as i32),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
Self {
|
||||
chunks,
|
||||
npc,
|
||||
|
||||
Self { chunks, npc }
|
||||
chunk_database,
|
||||
chunk_load_queue: VecDeque::new(),
|
||||
chunk_save_queue: VecDeque::new(),
|
||||
chunk_generate_queue: VecDeque::new(),
|
||||
chunk_buffers: AHashMap::new(),
|
||||
|
||||
highlighted: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, render_context: &RenderContext, camera: &Camera) {
|
||||
if let Some(position) = self.chunk_load_queue.pop_front() {
|
||||
let chunk = self.chunks.entry(position).or_default();
|
||||
match chunk.load(position, &self.chunk_database) {
|
||||
Err(error) => {
|
||||
eprintln!("Failed to load/generate chunk {:?}: {:?}", position, error)
|
||||
}
|
||||
Ok(true) => {
|
||||
self.update_chunk_geometry(render_context, position);
|
||||
self.chunk_save_queue.push_back(position);
|
||||
// println!("Generated chunk {:?}", position);
|
||||
}
|
||||
Ok(false) => {
|
||||
self.update_chunk_geometry(render_context, position);
|
||||
// println!("Loaded chunk {:?}", position);
|
||||
}
|
||||
}
|
||||
} else if let Some(position) = self.chunk_save_queue.pop_front() {
|
||||
let chunk = self.chunks.get(&position).unwrap();
|
||||
if let Err(err) = chunk.save(position, &self.chunk_database) {
|
||||
eprintln!("Failed to save chunk {:?}: {:?}", position, err);
|
||||
} else {
|
||||
// println!("Saved chunk {:?}", position);
|
||||
}
|
||||
}
|
||||
|
||||
// Load new chunks, if necessary
|
||||
let camera_pos: Point3<isize> = camera.position.cast().unwrap();
|
||||
let camera_chunk: Point3<isize> = camera_pos.map(|n| n.div_euclid(CHUNK_ISIZE));
|
||||
let mut load_queue = Vec::new();
|
||||
for (x, y, z) in itertools::iproduct!(
|
||||
-RENDER_DISTANCE..RENDER_DISTANCE,
|
||||
0..WORLD_HEIGHT,
|
||||
-RENDER_DISTANCE..RENDER_DISTANCE
|
||||
) {
|
||||
let point: Point3<isize> = Point3::new(x + camera_chunk.x, y, z + camera_chunk.z);
|
||||
if !self.chunks.contains_key(&point) && !self.chunk_load_queue.contains(&point) {
|
||||
load_queue.push(point);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Sort based on where camera is looking
|
||||
load_queue.sort_unstable_by_key(|f| {
|
||||
(f.x * CHUNK_ISIZE - camera_pos.x).abs() + (f.y * CHUNK_ISIZE - camera_pos.y).abs()
|
||||
});
|
||||
|
||||
self.chunk_load_queue.extend(load_queue);
|
||||
}
|
||||
|
||||
pub fn update_chunk_geometry(
|
||||
&mut self,
|
||||
render_context: &RenderContext,
|
||||
chunk_position: Point3<isize>,
|
||||
) {
|
||||
let chunk = &mut self.chunks.get(&chunk_position).unwrap();
|
||||
let offset = chunk_position * CHUNK_ISIZE;
|
||||
let geometry = chunk.to_geometry(
|
||||
offset,
|
||||
World::highlighted_for_chunk(self.highlighted, &chunk_position).as_ref(),
|
||||
);
|
||||
|
||||
let buffers =
|
||||
GeometryBuffers::from_geometry(render_context, &geometry, BufferUsage::empty());
|
||||
self.chunk_buffers.insert(chunk_position, buffers);
|
||||
}
|
||||
|
||||
pub fn highlighted_for_chunk(
|
||||
|
@ -105,16 +184,21 @@ impl World {
|
|||
}
|
||||
|
||||
pub fn set_block(&mut self, x: isize, y: isize, z: isize, block: Option<Block>) {
|
||||
if let Some(chunk) = self.chunks.get_mut(&Point3::new(
|
||||
let chunk_position = Point3::new(
|
||||
x.div_euclid(CHUNK_ISIZE),
|
||||
y.div_euclid(CHUNK_ISIZE),
|
||||
z.div_euclid(CHUNK_ISIZE),
|
||||
)) {
|
||||
);
|
||||
|
||||
if let Some(chunk) = self.chunks.get_mut(&chunk_position) {
|
||||
let bx = x.rem_euclid(CHUNK_ISIZE) as usize;
|
||||
let by = y.rem_euclid(CHUNK_ISIZE) as usize;
|
||||
let bz = z.rem_euclid(CHUNK_ISIZE) as usize;
|
||||
chunk.blocks[by][bz][bx] = block;
|
||||
}
|
||||
|
||||
self.chunk_save_queue
|
||||
.push_back(chunk_position / CHUNK_ISIZE);
|
||||
}
|
||||
|
||||
fn calc_scale(vector: Vector3<f32>, scalar: f32) -> f32 {
|
||||
|
|
Loading…
Reference in a new issue