Move update_chunk_geometry into Chunk
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
1b91fa94c0
commit
67f275b2fc
4 changed files with 395 additions and 401 deletions
|
@ -18,7 +18,7 @@ use crate::{
|
||||||
time::Time,
|
time::Time,
|
||||||
vertex::{BlockVertex, Vertex},
|
vertex::{BlockVertex, Vertex},
|
||||||
view::View,
|
view::View,
|
||||||
world::world::World,
|
world::World,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct WorldState {
|
pub struct WorldState {
|
||||||
|
|
|
@ -3,6 +3,7 @@ use std::collections::VecDeque;
|
||||||
use crate::{
|
use crate::{
|
||||||
aabb::Aabb,
|
aabb::Aabb,
|
||||||
geometry::{Geometry, GeometryBuffers},
|
geometry::{Geometry, GeometryBuffers},
|
||||||
|
render_context::RenderContext,
|
||||||
vertex::BlockVertex,
|
vertex::BlockVertex,
|
||||||
view::View,
|
view::View,
|
||||||
world::{
|
world::{
|
||||||
|
@ -20,6 +21,7 @@ use serde::{
|
||||||
ser::SerializeSeq,
|
ser::SerializeSeq,
|
||||||
Deserialize, Serialize, Serializer,
|
Deserialize, Serialize, Serializer,
|
||||||
};
|
};
|
||||||
|
use wgpu::BufferUsage;
|
||||||
|
|
||||||
pub const CHUNK_SIZE: usize = 32;
|
pub const CHUNK_SIZE: usize = 32;
|
||||||
pub const CHUNK_ISIZE: isize = CHUNK_SIZE as isize;
|
pub const CHUNK_ISIZE: isize = CHUNK_SIZE as isize;
|
||||||
|
@ -168,6 +170,22 @@ impl Chunk {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn block_coords_to_local(
|
||||||
|
chunk_coords: Point3<isize>,
|
||||||
|
block_coords: Point3<isize>,
|
||||||
|
) -> Option<Vector3<usize>> {
|
||||||
|
let chunk_position = chunk_coords * CHUNK_ISIZE;
|
||||||
|
let position = block_coords - chunk_position;
|
||||||
|
if (0..CHUNK_ISIZE).contains(&position.x)
|
||||||
|
&& (0..CHUNK_ISIZE).contains(&position.y)
|
||||||
|
&& (0..CHUNK_ISIZE).contains(&position.z)
|
||||||
|
{
|
||||||
|
Some(position.cast().unwrap())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
fn check_visible_faces(&self, x: usize, y: usize, z: usize) -> FaceFlags {
|
fn check_visible_faces(&self, x: usize, y: usize, z: usize) -> FaceFlags {
|
||||||
let mut visible_faces = FACE_NONE;
|
let mut visible_faces = FACE_NONE;
|
||||||
|
@ -244,7 +262,7 @@ impl Chunk {
|
||||||
offset: Point3<isize>,
|
offset: Point3<isize>,
|
||||||
culled: AHashMap<(usize, usize), (BlockType, FaceFlags)>,
|
culled: AHashMap<(usize, usize), (BlockType, FaceFlags)>,
|
||||||
queue: &mut VecDeque<(usize, usize)>,
|
queue: &mut VecDeque<(usize, usize)>,
|
||||||
highlighted: Option<&(Point3<usize>, Vector3<i32>)>,
|
highlighted: Option<(Vector3<usize>, Vector3<i32>)>,
|
||||||
) -> Vec<Quad> {
|
) -> Vec<Quad> {
|
||||||
let mut quads: Vec<Quad> = Vec::new();
|
let mut quads: Vec<Quad> = Vec::new();
|
||||||
let mut visited = AHashSet::new();
|
let mut visited = AHashSet::new();
|
||||||
|
@ -260,7 +278,7 @@ impl Chunk {
|
||||||
if let Some(&(block_type, visible_faces)) = &culled.get(&(x, z)) {
|
if let Some(&(block_type, visible_faces)) = &culled.get(&(x, z)) {
|
||||||
let mut quad_faces = visible_faces;
|
let mut quad_faces = visible_faces;
|
||||||
|
|
||||||
if hl == Some(Point3::new(x, y, z)) {
|
if hl == Some(Vector3::new(x, y, z)) {
|
||||||
let mut quad = Quad::new(position, 1, 1);
|
let mut quad = Quad::new(position, 1, 1);
|
||||||
quad.highlighted_normal = highlighted.unwrap().1;
|
quad.highlighted_normal = highlighted.unwrap().1;
|
||||||
quad.visible_faces = quad_faces;
|
quad.visible_faces = quad_faces;
|
||||||
|
@ -282,7 +300,7 @@ impl Chunk {
|
||||||
for x_ in x..CHUNK_SIZE {
|
for x_ in x..CHUNK_SIZE {
|
||||||
xmax = x_ + 1;
|
xmax = x_ + 1;
|
||||||
|
|
||||||
if visited.contains(&(xmax, z)) || hl == Some(Point3::new(xmax, y, z)) {
|
if visited.contains(&(xmax, z)) || hl == Some(Vector3::new(xmax, y, z)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,7 +322,7 @@ impl Chunk {
|
||||||
zmax = z_ + 1;
|
zmax = z_ + 1;
|
||||||
|
|
||||||
for x_ in x..xmax {
|
for x_ in x..xmax {
|
||||||
if visited.contains(&(x_, zmax)) || hl == Some(Point3::new(x_, y, zmax)) {
|
if visited.contains(&(x_, zmax)) || hl == Some(Vector3::new(x_, y, zmax)) {
|
||||||
break 'z;
|
break 'z;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,20 +359,30 @@ impl Chunk {
|
||||||
geometry
|
geometry
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_geometry(
|
pub fn update_geometry(
|
||||||
&self,
|
&mut self,
|
||||||
position: Point3<isize>,
|
render_context: &RenderContext,
|
||||||
highlighted: Option<&(Point3<usize>, Vector3<i32>)>,
|
chunk_coords: Point3<isize>,
|
||||||
) -> Geometry<BlockVertex, u16> {
|
highlighted: Option<(Point3<isize>, Vector3<i32>)>,
|
||||||
|
) {
|
||||||
|
let highlighted = highlighted.and_then(|(position, normal)| {
|
||||||
|
Self::block_coords_to_local(chunk_coords, position).map(|x| (x, normal))
|
||||||
|
});
|
||||||
|
|
||||||
|
let offset = chunk_coords * CHUNK_ISIZE;
|
||||||
let quads: Vec<Quad> = (0..CHUNK_SIZE)
|
let quads: Vec<Quad> = (0..CHUNK_SIZE)
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.flat_map(|y| {
|
.flat_map(|y| {
|
||||||
let (culled, mut queue) = self.cull_layer(y);
|
let (culled, mut queue) = self.cull_layer(y);
|
||||||
self.layer_to_quads(y, position, culled, &mut queue, highlighted)
|
self.layer_to_quads(y, offset, culled, &mut queue, highlighted)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Self::quads_to_geometry(quads)
|
self.buffers = Some(GeometryBuffers::from_geometry(
|
||||||
|
render_context,
|
||||||
|
&Self::quads_to_geometry(quads),
|
||||||
|
BufferUsage::empty(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save(&self, position: Point3<isize>, store: &sled::Db) -> anyhow::Result<()> {
|
pub fn save(&self, position: Point3<isize>, store: &sled::Db) -> anyhow::Result<()> {
|
||||||
|
|
356
src/world/mod.rs
356
src/world/mod.rs
|
@ -2,4 +2,358 @@ pub mod block;
|
||||||
pub mod chunk;
|
pub mod chunk;
|
||||||
pub mod face_flags;
|
pub mod face_flags;
|
||||||
pub mod quad;
|
pub mod quad;
|
||||||
pub mod world;
|
|
||||||
|
use std::{collections::VecDeque, time::Duration};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
camera::Camera,
|
||||||
|
npc::Npc,
|
||||||
|
render_context::RenderContext,
|
||||||
|
renderable::Renderable,
|
||||||
|
view::View,
|
||||||
|
world::{
|
||||||
|
block::{Block, BlockType},
|
||||||
|
chunk::{Chunk, CHUNK_ISIZE},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use ahash::AHashMap;
|
||||||
|
use cgmath::{EuclideanSpace, InnerSpace, Point3, Vector3};
|
||||||
|
use wgpu::RenderPass;
|
||||||
|
|
||||||
|
pub struct World {
|
||||||
|
pub npc: Npc,
|
||||||
|
|
||||||
|
pub chunks: AHashMap<Point3<isize>, Chunk>,
|
||||||
|
pub chunk_database: sled::Db,
|
||||||
|
pub chunk_save_queue: VecDeque<(Point3<isize>, bool)>,
|
||||||
|
pub chunk_load_queue: VecDeque<Point3<isize>>,
|
||||||
|
pub chunk_generate_queue: VecDeque<Point3<isize>>,
|
||||||
|
|
||||||
|
pub highlighted: Option<(Point3<isize>, Vector3<i32>)>,
|
||||||
|
|
||||||
|
pub unload_timer: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const RENDER_DISTANCE: isize = 8;
|
||||||
|
pub const WORLD_HEIGHT: isize = 16 * 16 / CHUNK_ISIZE;
|
||||||
|
|
||||||
|
const DEBUG_IO: bool = false;
|
||||||
|
|
||||||
|
impl Renderable for World {
|
||||||
|
fn update(&mut self, render_context: &RenderContext, dt: Duration, 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) => {
|
||||||
|
chunk.update_geometry(render_context, position, self.highlighted);
|
||||||
|
self.enqueue_chunk_save(position, false);
|
||||||
|
if DEBUG_IO {
|
||||||
|
println!("Generated chunk {:?}", position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(false) => {
|
||||||
|
chunk.update_geometry(render_context, position, self.highlighted);
|
||||||
|
if DEBUG_IO {
|
||||||
|
println!("Loaded chunk {:?}", position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some((position, unload)) = self.chunk_save_queue.pop_front() {
|
||||||
|
if let Some(chunk) = self.chunks.get(&position) {
|
||||||
|
if let Err(err) = chunk.save(position, &self.chunk_database) {
|
||||||
|
eprintln!("Failed to save chunk {:?}: {:?}", position, err);
|
||||||
|
} else if unload {
|
||||||
|
self.chunks.remove(&position);
|
||||||
|
|
||||||
|
if DEBUG_IO {
|
||||||
|
println!("Saved and unloaded chunk {:?}", position);
|
||||||
|
}
|
||||||
|
} else if DEBUG_IO {
|
||||||
|
println!("Saved chunk {:?}", position);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!("Tried to save unloaded chunk {:?}", position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.update_highlight(render_context, camera);
|
||||||
|
|
||||||
|
// Queue up new chunks for loading, 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);
|
||||||
|
|
||||||
|
// Unload chunks that are far away
|
||||||
|
self.unload_timer += dt;
|
||||||
|
if self.unload_timer.as_secs() >= 10 {
|
||||||
|
self.unload_timer = Duration::new(0, 0);
|
||||||
|
|
||||||
|
let camera_pos = camera.position.to_vec();
|
||||||
|
let unload_distance = (RENDER_DISTANCE * CHUNK_ISIZE) as f32 * 1.5;
|
||||||
|
|
||||||
|
let mut unload_chunks = Vec::new();
|
||||||
|
for point in self.chunks.keys() {
|
||||||
|
let pos: Point3<f32> = (point * CHUNK_ISIZE).cast().unwrap();
|
||||||
|
if (pos.x - camera_pos.x).abs() > unload_distance
|
||||||
|
|| (pos.z - camera_pos.z).abs() > unload_distance
|
||||||
|
{
|
||||||
|
unload_chunks.push(*point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for point in unload_chunks {
|
||||||
|
self.enqueue_chunk_save(point, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render<'a>(&'a self, render_pass: &mut RenderPass<'a>, view: &View) -> usize {
|
||||||
|
let mut triangle_count = 0;
|
||||||
|
|
||||||
|
for (position, chunk) in &self.chunks {
|
||||||
|
if !chunk.is_visible(position * CHUNK_ISIZE, &view) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(buffers) = chunk.buffers.as_ref() {
|
||||||
|
buffers.apply_buffers(render_pass);
|
||||||
|
triangle_count += buffers.draw_indexed(render_pass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let buffers = self.npc.geometry_buffers.as_ref().unwrap();
|
||||||
|
buffers.set_buffers(render_pass);
|
||||||
|
triangle_count += buffers.draw_indexed(render_pass);
|
||||||
|
}
|
||||||
|
|
||||||
|
triangle_count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl World {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let chunks = AHashMap::new();
|
||||||
|
let npc = Npc::load();
|
||||||
|
|
||||||
|
let chunk_database = sled::Config::new()
|
||||||
|
.path("chunks")
|
||||||
|
.mode(sled::Mode::HighThroughput)
|
||||||
|
.use_compression(true)
|
||||||
|
.open()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
chunks,
|
||||||
|
npc,
|
||||||
|
|
||||||
|
chunk_database,
|
||||||
|
chunk_load_queue: VecDeque::new(),
|
||||||
|
chunk_save_queue: VecDeque::new(),
|
||||||
|
chunk_generate_queue: VecDeque::new(),
|
||||||
|
|
||||||
|
highlighted: None,
|
||||||
|
|
||||||
|
unload_timer: Duration::new(0, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enqueue_chunk_save(&mut self, position: Point3<isize>, unload: bool) {
|
||||||
|
if let Some((_, unload_)) = self
|
||||||
|
.chunk_save_queue
|
||||||
|
.iter_mut()
|
||||||
|
.find(|(pos, _)| pos == &position)
|
||||||
|
{
|
||||||
|
*unload_ = *unload_ || unload;
|
||||||
|
} else {
|
||||||
|
self.chunk_save_queue.push_back((position, unload));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_chunk_geometry(
|
||||||
|
&mut self,
|
||||||
|
render_context: &RenderContext,
|
||||||
|
chunk_position: Point3<isize>,
|
||||||
|
) {
|
||||||
|
let chunk = self.chunks.get_mut(&chunk_position).unwrap();
|
||||||
|
chunk.update_geometry(render_context, chunk_position, self.highlighted);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_highlight(&mut self, render_context: &RenderContext, camera: &Camera) {
|
||||||
|
let old = self.highlighted;
|
||||||
|
let new = self.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;
|
||||||
|
|
||||||
|
if let Some(old_chunk_) = old_chunk {
|
||||||
|
self.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_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn break_at_crosshair(&mut self, render_context: &RenderContext, camera: &Camera) {
|
||||||
|
if let Some((pos, _)) = self.raycast(camera.position, camera.direction()) {
|
||||||
|
self.set_block(pos.x as isize, pos.y as isize, pos.z as isize, None);
|
||||||
|
self.update_chunk_geometry(render_context, pos / CHUNK_ISIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn place_at_crosshair(&mut self, render_context: &RenderContext, camera: &Camera) {
|
||||||
|
if let Some((pos, face_normal)) = self.raycast(camera.position, camera.direction()) {
|
||||||
|
let new_pos = pos.cast().unwrap() + face_normal;
|
||||||
|
|
||||||
|
self.set_block(
|
||||||
|
new_pos.x as isize,
|
||||||
|
new_pos.y as isize,
|
||||||
|
new_pos.z as isize,
|
||||||
|
Some(Block {
|
||||||
|
block_type: BlockType::Cobblestone,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.update_chunk_geometry(render_context, pos / CHUNK_ISIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_block(&self, x: isize, y: isize, z: isize) -> Option<&Block> {
|
||||||
|
let chunk = match self.chunks.get(&Point3::new(
|
||||||
|
x.div_euclid(CHUNK_ISIZE),
|
||||||
|
y.div_euclid(CHUNK_ISIZE),
|
||||||
|
z.div_euclid(CHUNK_ISIZE),
|
||||||
|
)) {
|
||||||
|
Some(chunk) => chunk,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
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].as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_block(&mut self, x: isize, y: isize, z: isize, block: Option<Block>) {
|
||||||
|
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, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calc_scale(vector: Vector3<f32>, scalar: f32) -> f32 {
|
||||||
|
if scalar == 0.0 {
|
||||||
|
f32::INFINITY
|
||||||
|
} else {
|
||||||
|
(vector / scalar).magnitude()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn raycast(
|
||||||
|
&self,
|
||||||
|
origin: Point3<f32>,
|
||||||
|
direction: Vector3<f32>,
|
||||||
|
) -> Option<(Point3<isize>, Vector3<i32>)> {
|
||||||
|
let direction = direction.normalize();
|
||||||
|
let scale = Vector3::new(
|
||||||
|
Self::calc_scale(direction, direction.x),
|
||||||
|
Self::calc_scale(direction, direction.y),
|
||||||
|
Self::calc_scale(direction, direction.z),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut position: Point3<i32> = origin.map(|x| x.floor() as i32);
|
||||||
|
let step = direction.map(|x| x.signum() as i32);
|
||||||
|
|
||||||
|
// Truncate the origin
|
||||||
|
let mut lengths = Vector3 {
|
||||||
|
x: if direction.x < 0.0 {
|
||||||
|
(origin.x - position.x as f32) * scale.x
|
||||||
|
} else {
|
||||||
|
(position.x as f32 + 1.0 - origin.x) * scale.x
|
||||||
|
},
|
||||||
|
y: if direction.y < 0.0 {
|
||||||
|
(origin.y - position.y as f32) * scale.y
|
||||||
|
} else {
|
||||||
|
(position.y as f32 + 1.0 - origin.y) * scale.y
|
||||||
|
},
|
||||||
|
z: if direction.z < 0.0 {
|
||||||
|
(origin.z - position.z as f32) * scale.z
|
||||||
|
} else {
|
||||||
|
(position.z as f32 + 1.0 - origin.z) * scale.z
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut face;
|
||||||
|
|
||||||
|
while lengths.magnitude() < 100.0 {
|
||||||
|
if lengths.x < lengths.y && lengths.x < lengths.z {
|
||||||
|
lengths.x += scale.x;
|
||||||
|
position.x += step.x;
|
||||||
|
face = Vector3::unit_x() * -step.x;
|
||||||
|
} else if lengths.y < lengths.x && lengths.y < lengths.z {
|
||||||
|
lengths.y += scale.y;
|
||||||
|
position.y += step.y;
|
||||||
|
face = Vector3::unit_y() * -step.y;
|
||||||
|
} else if lengths.z < lengths.x && lengths.z < lengths.y {
|
||||||
|
lengths.z += scale.z;
|
||||||
|
position.z += step.z;
|
||||||
|
face = Vector3::unit_z() * -step.z;
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self
|
||||||
|
.get_block(
|
||||||
|
position.x as isize,
|
||||||
|
position.y as isize,
|
||||||
|
position.z as isize,
|
||||||
|
)
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
// Intersection occurred
|
||||||
|
return Some((position.cast().unwrap(), face));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,388 +0,0 @@
|
||||||
use std::{collections::VecDeque, time::Duration};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
camera::Camera,
|
|
||||||
geometry::GeometryBuffers,
|
|
||||||
npc::Npc,
|
|
||||||
render_context::RenderContext,
|
|
||||||
renderable::Renderable,
|
|
||||||
view::View,
|
|
||||||
world::{
|
|
||||||
block::{Block, BlockType},
|
|
||||||
chunk::{Chunk, CHUNK_ISIZE},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use ahash::AHashMap;
|
|
||||||
use cgmath::{EuclideanSpace, InnerSpace, Point3, Vector3};
|
|
||||||
use wgpu::{BufferUsage, RenderPass};
|
|
||||||
|
|
||||||
pub struct World {
|
|
||||||
pub npc: Npc,
|
|
||||||
|
|
||||||
pub chunks: AHashMap<Point3<isize>, Chunk>,
|
|
||||||
pub chunk_database: sled::Db,
|
|
||||||
pub chunk_save_queue: VecDeque<(Point3<isize>, bool)>,
|
|
||||||
pub chunk_load_queue: VecDeque<Point3<isize>>,
|
|
||||||
pub chunk_generate_queue: VecDeque<Point3<isize>>,
|
|
||||||
|
|
||||||
pub highlighted: Option<(Point3<isize>, Vector3<i32>)>,
|
|
||||||
|
|
||||||
pub unload_timer: Duration,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const RENDER_DISTANCE: isize = 8;
|
|
||||||
pub const WORLD_HEIGHT: isize = 16 * 16 / CHUNK_ISIZE;
|
|
||||||
|
|
||||||
const DEBUG_IO: bool = false;
|
|
||||||
|
|
||||||
impl Renderable for World {
|
|
||||||
fn update(&mut self, render_context: &RenderContext, dt: Duration, 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.enqueue_chunk_save(position, false);
|
|
||||||
if DEBUG_IO {
|
|
||||||
println!("Generated chunk {:?}", position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(false) => {
|
|
||||||
self.update_chunk_geometry(render_context, position);
|
|
||||||
if DEBUG_IO {
|
|
||||||
println!("Loaded chunk {:?}", position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if let Some((position, unload)) = self.chunk_save_queue.pop_front() {
|
|
||||||
if let Some(chunk) = self.chunks.get(&position) {
|
|
||||||
if let Err(err) = chunk.save(position, &self.chunk_database) {
|
|
||||||
eprintln!("Failed to save chunk {:?}: {:?}", position, err);
|
|
||||||
} else if unload {
|
|
||||||
self.chunks.remove(&position);
|
|
||||||
|
|
||||||
if DEBUG_IO {
|
|
||||||
println!("Saved and unloaded chunk {:?}", position);
|
|
||||||
}
|
|
||||||
} else if DEBUG_IO {
|
|
||||||
println!("Saved chunk {:?}", position);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eprintln!("Tried to save unloaded chunk {:?}", position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.update_highlight(render_context, camera);
|
|
||||||
|
|
||||||
// Queue up new chunks for loading, 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);
|
|
||||||
|
|
||||||
// Unload chunks that are far away
|
|
||||||
self.unload_timer += dt;
|
|
||||||
if self.unload_timer.as_secs() >= 10 {
|
|
||||||
self.unload_timer = Duration::new(0, 0);
|
|
||||||
|
|
||||||
let camera_pos = camera.position.to_vec();
|
|
||||||
let unload_distance = (RENDER_DISTANCE * CHUNK_ISIZE) as f32 * 1.5;
|
|
||||||
|
|
||||||
let mut unload_chunks = Vec::new();
|
|
||||||
for point in self.chunks.keys() {
|
|
||||||
let pos: Point3<f32> = (point * CHUNK_ISIZE).cast().unwrap();
|
|
||||||
if (pos.x - camera_pos.x).abs() > unload_distance
|
|
||||||
|| (pos.z - camera_pos.z).abs() > unload_distance
|
|
||||||
{
|
|
||||||
unload_chunks.push(*point);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for point in unload_chunks {
|
|
||||||
self.enqueue_chunk_save(point, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render<'a>(&'a self, render_pass: &mut RenderPass<'a>, view: &View) -> usize {
|
|
||||||
let mut triangle_count = 0;
|
|
||||||
|
|
||||||
for (position, chunk) in &self.chunks {
|
|
||||||
if !chunk.is_visible(position * CHUNK_ISIZE, &view) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(buffers) = chunk.buffers.as_ref() {
|
|
||||||
buffers.apply_buffers(render_pass);
|
|
||||||
triangle_count += buffers.draw_indexed(render_pass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let buffers = self.npc.geometry_buffers.as_ref().unwrap();
|
|
||||||
buffers.set_buffers(render_pass);
|
|
||||||
triangle_count += buffers.draw_indexed(render_pass);
|
|
||||||
}
|
|
||||||
|
|
||||||
triangle_count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl World {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let chunks = AHashMap::new();
|
|
||||||
let npc = Npc::load();
|
|
||||||
|
|
||||||
let chunk_database = sled::Config::new()
|
|
||||||
.path("chunks")
|
|
||||||
.mode(sled::Mode::HighThroughput)
|
|
||||||
.use_compression(true)
|
|
||||||
.open()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
chunks,
|
|
||||||
npc,
|
|
||||||
|
|
||||||
chunk_database,
|
|
||||||
chunk_load_queue: VecDeque::new(),
|
|
||||||
chunk_save_queue: VecDeque::new(),
|
|
||||||
chunk_generate_queue: VecDeque::new(),
|
|
||||||
|
|
||||||
highlighted: None,
|
|
||||||
|
|
||||||
unload_timer: Duration::new(0, 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn enqueue_chunk_save(&mut self, position: Point3<isize>, unload: bool) {
|
|
||||||
if let Some((_, unload_)) = self
|
|
||||||
.chunk_save_queue
|
|
||||||
.iter_mut()
|
|
||||||
.find(|(pos, _)| pos == &position)
|
|
||||||
{
|
|
||||||
*unload_ = *unload_ || unload;
|
|
||||||
} else {
|
|
||||||
self.chunk_save_queue.push_back((position, unload));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_chunk_geometry(
|
|
||||||
&mut self,
|
|
||||||
render_context: &RenderContext,
|
|
||||||
chunk_position: Point3<isize>,
|
|
||||||
) {
|
|
||||||
let chunk = self.chunks.get_mut(&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(),
|
|
||||||
);
|
|
||||||
|
|
||||||
chunk.buffers = Some(GeometryBuffers::from_geometry(
|
|
||||||
render_context,
|
|
||||||
&geometry,
|
|
||||||
BufferUsage::empty(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_highlight(&mut self, render_context: &RenderContext, camera: &Camera) {
|
|
||||||
let old = self.highlighted;
|
|
||||||
let new = self.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;
|
|
||||||
|
|
||||||
if let Some(old_chunk_) = old_chunk {
|
|
||||||
self.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_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn break_at_crosshair(&mut self, render_context: &RenderContext, camera: &Camera) {
|
|
||||||
if let Some((pos, _)) = self.raycast(camera.position, camera.direction()) {
|
|
||||||
self.set_block(pos.x as isize, pos.y as isize, pos.z as isize, None);
|
|
||||||
self.update_chunk_geometry(render_context, pos / CHUNK_ISIZE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn place_at_crosshair(&mut self, render_context: &RenderContext, camera: &Camera) {
|
|
||||||
if let Some((pos, face_normal)) = self.raycast(camera.position, camera.direction()) {
|
|
||||||
let new_pos = pos.cast().unwrap() + face_normal;
|
|
||||||
|
|
||||||
self.set_block(
|
|
||||||
new_pos.x as isize,
|
|
||||||
new_pos.y as isize,
|
|
||||||
new_pos.z as isize,
|
|
||||||
Some(Block {
|
|
||||||
block_type: BlockType::Cobblestone,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
self.update_chunk_geometry(render_context, pos / CHUNK_ISIZE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn highlighted_for_chunk(
|
|
||||||
highlighted: Option<(Point3<isize>, Vector3<i32>)>,
|
|
||||||
chunk_position: &Point3<isize>,
|
|
||||||
) -> Option<(Point3<usize>, Vector3<i32>)> {
|
|
||||||
let position = chunk_position * CHUNK_ISIZE;
|
|
||||||
if let Some((pos, face)) = highlighted {
|
|
||||||
if pos.x >= position.x
|
|
||||||
&& pos.x < position.x + CHUNK_ISIZE
|
|
||||||
&& pos.y >= position.y
|
|
||||||
&& pos.y < position.y + CHUNK_ISIZE
|
|
||||||
&& pos.z >= position.z
|
|
||||||
&& pos.z < position.z + CHUNK_ISIZE
|
|
||||||
{
|
|
||||||
let point: Point3<isize> = EuclideanSpace::from_vec(pos - position);
|
|
||||||
Some((point.cast().unwrap(), face))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_block(&self, x: isize, y: isize, z: isize) -> Option<&Block> {
|
|
||||||
let chunk = match self.chunks.get(&Point3::new(
|
|
||||||
x.div_euclid(CHUNK_ISIZE),
|
|
||||||
y.div_euclid(CHUNK_ISIZE),
|
|
||||||
z.div_euclid(CHUNK_ISIZE),
|
|
||||||
)) {
|
|
||||||
Some(chunk) => chunk,
|
|
||||||
None => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
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].as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_block(&mut self, x: isize, y: isize, z: isize, block: Option<Block>) {
|
|
||||||
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, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calc_scale(vector: Vector3<f32>, scalar: f32) -> f32 {
|
|
||||||
if scalar == 0.0 {
|
|
||||||
f32::INFINITY
|
|
||||||
} else {
|
|
||||||
(vector / scalar).magnitude()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn raycast(
|
|
||||||
&self,
|
|
||||||
origin: Point3<f32>,
|
|
||||||
direction: Vector3<f32>,
|
|
||||||
) -> Option<(Point3<isize>, Vector3<i32>)> {
|
|
||||||
let direction = direction.normalize();
|
|
||||||
let scale = Vector3::new(
|
|
||||||
Self::calc_scale(direction, direction.x),
|
|
||||||
Self::calc_scale(direction, direction.y),
|
|
||||||
Self::calc_scale(direction, direction.z),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut position: Point3<i32> = origin.map(|x| x.floor() as i32);
|
|
||||||
let step = direction.map(|x| x.signum() as i32);
|
|
||||||
|
|
||||||
// Truncate the origin
|
|
||||||
let mut lengths = Vector3 {
|
|
||||||
x: if direction.x < 0.0 {
|
|
||||||
(origin.x - position.x as f32) * scale.x
|
|
||||||
} else {
|
|
||||||
(position.x as f32 + 1.0 - origin.x) * scale.x
|
|
||||||
},
|
|
||||||
y: if direction.y < 0.0 {
|
|
||||||
(origin.y - position.y as f32) * scale.y
|
|
||||||
} else {
|
|
||||||
(position.y as f32 + 1.0 - origin.y) * scale.y
|
|
||||||
},
|
|
||||||
z: if direction.z < 0.0 {
|
|
||||||
(origin.z - position.z as f32) * scale.z
|
|
||||||
} else {
|
|
||||||
(position.z as f32 + 1.0 - origin.z) * scale.z
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut face;
|
|
||||||
|
|
||||||
while lengths.magnitude() < 100.0 {
|
|
||||||
if lengths.x < lengths.y && lengths.x < lengths.z {
|
|
||||||
lengths.x += scale.x;
|
|
||||||
position.x += step.x;
|
|
||||||
face = Vector3::unit_x() * -step.x;
|
|
||||||
} else if lengths.y < lengths.x && lengths.y < lengths.z {
|
|
||||||
lengths.y += scale.y;
|
|
||||||
position.y += step.y;
|
|
||||||
face = Vector3::unit_y() * -step.y;
|
|
||||||
} else if lengths.z < lengths.x && lengths.z < lengths.y {
|
|
||||||
lengths.z += scale.z;
|
|
||||||
position.z += step.z;
|
|
||||||
face = Vector3::unit_z() * -step.z;
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self
|
|
||||||
.get_block(
|
|
||||||
position.x as isize,
|
|
||||||
position.y as isize,
|
|
||||||
position.z as isize,
|
|
||||||
)
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
// Intersection occurred
|
|
||||||
return Some((position.cast().unwrap(), face));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue