Implement chunk unloading, frustrum culling and some other minor optimisations
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
206f7857fa
commit
4bba01058b
12 changed files with 230 additions and 120 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,5 +4,6 @@
|
||||||
/perf.data*
|
/perf.data*
|
||||||
/chunks/
|
/chunks/
|
||||||
|
|
||||||
|
GPUCache/
|
||||||
profile.txt
|
profile.txt
|
||||||
callgrind.out.*
|
callgrind.out.*
|
||||||
|
|
62
src/aabb.rs
62
src/aabb.rs
|
@ -1,49 +1,23 @@
|
||||||
use cgmath::Vector3;
|
use cgmath::Point3;
|
||||||
use std::ops::{Div, Sub};
|
|
||||||
|
|
||||||
pub struct Aabb<T> {
|
pub struct Aabb {
|
||||||
pub min: Vector3<T>,
|
pub min: Point3<f32>,
|
||||||
pub max: Vector3<T>,
|
pub max: Point3<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Ord + Copy + Sub<Output = T> + Div<Output = T>> Aabb<T> {
|
impl Aabb {
|
||||||
pub fn intersects_ray(
|
pub fn intersects(&self, other: &Self) -> bool {
|
||||||
&self,
|
(self.min.x <= other.max.x && self.max.x >= other.min.x)
|
||||||
ray_origin: Vector3<T>,
|
&& (self.min.y <= other.max.y && self.max.y >= other.min.y)
|
||||||
ray_direction: Vector3<T>,
|
&& (self.min.z <= other.max.z && self.max.z >= other.min.z)
|
||||||
) -> Option<(T, T)> {
|
}
|
||||||
let mut t_min = (self.min.x - ray_origin.x) / ray_direction.x;
|
}
|
||||||
let mut t_max = (self.max.x - ray_origin.x) / ray_direction.x;
|
|
||||||
if t_min > t_max {
|
impl Default for Aabb {
|
||||||
std::mem::swap(&mut t_min, &mut t_max);
|
fn default() -> Self {
|
||||||
}
|
Self {
|
||||||
|
min: Point3::new(0.0, 0.0, 0.0),
|
||||||
let mut ty_min = (self.min.y - ray_origin.y) / ray_direction.y;
|
max: Point3::new(0.0, 0.0, 0.0),
|
||||||
let mut ty_max = (self.max.y - ray_origin.y) / ray_direction.y;
|
}
|
||||||
if ty_min > ty_max {
|
|
||||||
std::mem::swap(&mut ty_min, &mut ty_max);
|
|
||||||
}
|
|
||||||
|
|
||||||
if t_min > ty_max || ty_min > t_max {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
t_min = T::max(t_min, ty_min);
|
|
||||||
t_max = T::min(t_min, ty_max);
|
|
||||||
|
|
||||||
let mut tz_min = (self.min.z - ray_origin.z) / ray_direction.z;
|
|
||||||
let mut tz_max = (self.max.z - ray_origin.z) / ray_direction.z;
|
|
||||||
if tz_min > tz_max {
|
|
||||||
std::mem::swap(&mut tz_min, &mut tz_max);
|
|
||||||
}
|
|
||||||
|
|
||||||
if t_min > tz_max || tz_min > t_max {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
t_min = T::max(t_min, tz_min);
|
|
||||||
t_max = T::max(t_max, tz_max);
|
|
||||||
|
|
||||||
Some((t_min, t_max))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
use cgmath::{Matrix4, Point3, Rad, Vector3};
|
use cgmath::{Matrix4, Point3, Rad, Vector3};
|
||||||
|
|
||||||
use crate::aabb::Aabb;
|
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
pub const OPENGL_TO_WGPU_MATRIX: Matrix4<f32> = Matrix4::new(
|
pub const OPENGL_TO_WGPU_MATRIX: Matrix4<f32> = Matrix4::new(
|
||||||
1.0, 0.0, 0.0, 0.0,
|
1.0, 0.0, 0.0, 0.0,
|
||||||
|
@ -10,11 +8,6 @@ pub const OPENGL_TO_WGPU_MATRIX: Matrix4<f32> = Matrix4::new(
|
||||||
0.0, 0.0, 0.5, 1.0,
|
0.0, 0.0, 0.5, 1.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
const AABB: Aabb<f32> = Aabb {
|
|
||||||
min: Vector3::new(-0.3, 1.62, -0.3),
|
|
||||||
max: Vector3::new(0.3, 1.8 - 1.62, 0.3),
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Camera {
|
pub struct Camera {
|
||||||
pub position: Point3<f32>,
|
pub position: Point3<f32>,
|
||||||
pub yaw: Rad<f32>,
|
pub yaw: Rad<f32>,
|
||||||
|
|
32
src/chunk.rs
32
src/chunk.rs
|
@ -1,6 +1,12 @@
|
||||||
use std::{collections::VecDeque, usize};
|
use std::{collections::VecDeque, usize};
|
||||||
|
|
||||||
use crate::{geometry::Geometry, quad::Quad, vertex::BlockVertex};
|
use crate::{
|
||||||
|
aabb::Aabb,
|
||||||
|
geometry::{Geometry, GeometryBuffers},
|
||||||
|
quad::Quad,
|
||||||
|
vertex::BlockVertex,
|
||||||
|
view::View,
|
||||||
|
};
|
||||||
use ahash::{AHashMap, AHashSet};
|
use ahash::{AHashMap, AHashSet};
|
||||||
use cgmath::{Point3, Vector3};
|
use cgmath::{Point3, Vector3};
|
||||||
use noise::utils::{NoiseMapBuilder, PlaneMapBuilder};
|
use noise::utils::{NoiseMapBuilder, PlaneMapBuilder};
|
||||||
|
@ -68,9 +74,18 @@ pub const CHUNK_ISIZE: isize = CHUNK_SIZE as isize;
|
||||||
|
|
||||||
type ChunkBlocks = [[[Option<Block>; CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE];
|
type ChunkBlocks = [[[Option<Block>; CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE];
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
|
||||||
pub struct Chunk {
|
pub struct Chunk {
|
||||||
pub blocks: ChunkBlocks,
|
pub blocks: ChunkBlocks,
|
||||||
|
pub buffers: Option<GeometryBuffers<u16>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Chunk {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
blocks: [[[None; CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE],
|
||||||
|
buffers: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for Chunk {
|
impl Serialize for Chunk {
|
||||||
|
@ -179,7 +194,7 @@ impl Chunk {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if dirt_max >= 0 && dirt_max < CHUNK_ISIZE {
|
if (0..CHUNK_ISIZE).contains(&dirt_max) {
|
||||||
self.blocks[dirt_max as usize][z][x] = Some(Block {
|
self.blocks[dirt_max as usize][z][x] = Some(Block {
|
||||||
block_type: BlockType::Grass,
|
block_type: BlockType::Grass,
|
||||||
});
|
});
|
||||||
|
@ -410,4 +425,15 @@ impl Chunk {
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_visible(&self, position: Point3<isize>, view: &View) -> bool {
|
||||||
|
let aabb = Aabb {
|
||||||
|
min: position.cast().unwrap(),
|
||||||
|
max: (position + Vector3::new(CHUNK_ISIZE, CHUNK_ISIZE, CHUNK_ISIZE))
|
||||||
|
.cast()
|
||||||
|
.unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
aabb.intersects(&view.aabb)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,7 @@ impl<I: bytemuck::Pod> GeometryBuffers<I> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GeometryBuffers<u16> {
|
impl GeometryBuffers<u16> {
|
||||||
pub fn set_buffers<'a>(&'a self, render_pass: &mut RenderPass<'a>) {
|
pub fn apply_buffers<'a>(&'a self, render_pass: &mut RenderPass<'a>) {
|
||||||
render_pass.set_vertex_buffer(0, self.vertices.slice(..));
|
render_pass.set_vertex_buffer(0, self.vertices.slice(..));
|
||||||
render_pass.set_index_buffer(self.indices.slice(..), wgpu::IndexFormat::Uint16);
|
render_pass.set_index_buffer(self.indices.slice(..), wgpu::IndexFormat::Uint16);
|
||||||
}
|
}
|
||||||
|
|
37
src/main.rs
37
src/main.rs
|
@ -2,6 +2,7 @@ mod aabb;
|
||||||
mod camera;
|
mod camera;
|
||||||
mod chunk;
|
mod chunk;
|
||||||
mod geometry;
|
mod geometry;
|
||||||
|
mod npc;
|
||||||
mod quad;
|
mod quad;
|
||||||
mod render_context;
|
mod render_context;
|
||||||
mod state;
|
mod state;
|
||||||
|
@ -11,7 +12,6 @@ mod time;
|
||||||
mod vertex;
|
mod vertex;
|
||||||
mod view;
|
mod view;
|
||||||
mod world;
|
mod world;
|
||||||
mod npc;
|
|
||||||
|
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use wgpu::SwapChainError;
|
use wgpu::SwapChainError;
|
||||||
|
@ -133,9 +133,21 @@ fn main() {
|
||||||
let fps_max = 1_000_000 / frametime_min.as_micros();
|
let fps_max = 1_000_000 / frametime_min.as_micros();
|
||||||
let fps_min = 1_000_000 / frametime_max.as_micros();
|
let fps_min = 1_000_000 / frametime_max.as_micros();
|
||||||
|
|
||||||
|
print!("{:>4} frames | ", frames);
|
||||||
|
print!(
|
||||||
|
"frametime avg={:>5.2}ms min={:>5.2}ms max={:>5.2}ms | ",
|
||||||
|
frametime.as_secs_f32() * 1000.0,
|
||||||
|
frametime_min.as_secs_f32() * 1000.0,
|
||||||
|
frametime_max.as_secs_f32() * 1000.0,
|
||||||
|
);
|
||||||
|
print!(
|
||||||
|
"fps avg={:>5} min={:>5} max={:>5} | ",
|
||||||
|
fps, fps_min, fps_max
|
||||||
|
);
|
||||||
println!(
|
println!(
|
||||||
"{:>4} frames | frametime avg={:>5.2}ms min={:>5.2}ms max={:>5.2}ms | fps avg={:>5} min={:>5} max={:>5} | {:>8} tris",
|
"{:>8} tris | {:>5} chunks",
|
||||||
frames, frametime.as_secs_f32() * 1000.0, frametime_min.as_secs_f32() * 1000.0, frametime_max.as_secs_f32() * 1000.0, fps, fps_min, fps_max, triangle_count,
|
triangle_count,
|
||||||
|
state.world_state.world.chunks.len()
|
||||||
);
|
);
|
||||||
|
|
||||||
elapsed = Duration::from_secs(0);
|
elapsed = Duration::from_secs(0);
|
||||||
|
@ -150,17 +162,18 @@ fn main() {
|
||||||
state.update(dt);
|
state.update(dt);
|
||||||
|
|
||||||
match state.render() {
|
match state.render() {
|
||||||
Err(root_cause) =>
|
Err(root_cause) => match root_cause.downcast_ref::<SwapChainError>() {
|
||||||
match root_cause.downcast_ref::<SwapChainError>() {
|
// Recreate the swap_chain if lost
|
||||||
// Recreate the swap_chain if lost
|
Some(wgpu::SwapChainError::Lost) => state.resize(state.window_size),
|
||||||
Some(wgpu::SwapChainError::Lost) => state.resize(state.window_size),
|
// The system is out of memory, we should probably quit
|
||||||
// The system is out of memory, we should probably quit
|
Some(wgpu::SwapChainError::OutOfMemory) => {
|
||||||
Some(wgpu::SwapChainError::OutOfMemory) => *control_flow = ControlFlow::Exit,
|
*control_flow = ControlFlow::Exit
|
||||||
// All other errors (Outdated, Timeout) should be resolved by the next frame
|
|
||||||
Some(_) | None => eprintln!("{:?}", root_cause),
|
|
||||||
}
|
}
|
||||||
|
// All other errors (Outdated, Timeout) should be resolved by the next frame
|
||||||
|
Some(_) | None => eprintln!("{:?}", root_cause),
|
||||||
|
},
|
||||||
|
|
||||||
Ok(v) => triangle_count = v
|
Ok(v) => triangle_count = v,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::MainEventsCleared => {
|
Event::MainEventsCleared => {
|
||||||
|
|
|
@ -55,13 +55,13 @@ impl Npc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Self {
|
Self {
|
||||||
position,
|
position,
|
||||||
scale,
|
scale,
|
||||||
rotation,
|
rotation,
|
||||||
geometry: Geometry::new(vertices, indices),
|
geometry: Geometry::new(vertices, indices),
|
||||||
geometry_buffers: None,
|
geometry_buffers: None,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_geometry(&mut self, render_context: &RenderContext) {
|
pub fn load_geometry(&mut self, render_context: &RenderContext) {
|
||||||
|
|
|
@ -124,19 +124,19 @@ impl HudState {
|
||||||
render_pass.set_pipeline(&self.render_pipeline);
|
render_pass.set_pipeline(&self.render_pipeline);
|
||||||
|
|
||||||
// Render the HUD elements
|
// Render the HUD elements
|
||||||
self.hud_geometry_buffers.set_buffers(&mut render_pass);
|
self.hud_geometry_buffers.apply_buffers(&mut render_pass);
|
||||||
render_pass.set_bind_group(0, &self.texture_bind_group, &[]);
|
render_pass.set_bind_group(0, &self.texture_bind_group, &[]);
|
||||||
self.hud_geometry_buffers.draw_indexed(&mut render_pass);
|
self.hud_geometry_buffers.draw_indexed(&mut render_pass);
|
||||||
render_pass.draw_indexed(0..self.hud_geometry_buffers.index_count as u32, 0, 0..1);
|
render_pass.draw_indexed(0..self.hud_geometry_buffers.index_count as u32, 0, 0..1);
|
||||||
|
|
||||||
// Render the FPS text
|
// Render the FPS text
|
||||||
self.fps_geometry_buffers.set_buffers(&mut render_pass);
|
self.fps_geometry_buffers.apply_buffers(&mut render_pass);
|
||||||
render_pass.set_bind_group(0, &self.text_renderer.bind_group, &[]);
|
render_pass.set_bind_group(0, &self.text_renderer.bind_group, &[]);
|
||||||
self.fps_geometry_buffers.draw_indexed(&mut render_pass);
|
self.fps_geometry_buffers.draw_indexed(&mut render_pass);
|
||||||
|
|
||||||
// Render the coordinates text
|
// Render the coordinates text
|
||||||
self.coordinates_geometry_buffers
|
self.coordinates_geometry_buffers
|
||||||
.set_buffers(&mut render_pass);
|
.apply_buffers(&mut render_pass);
|
||||||
render_pass.set_bind_group(0, &self.text_renderer.bind_group, &[]);
|
render_pass.set_bind_group(0, &self.text_renderer.bind_group, &[]);
|
||||||
self.coordinates_geometry_buffers
|
self.coordinates_geometry_buffers
|
||||||
.draw_indexed(&mut render_pass);
|
.draw_indexed(&mut render_pass);
|
||||||
|
|
|
@ -28,7 +28,7 @@ pub const PRIMITIVE_STATE: wgpu::PrimitiveState = wgpu::PrimitiveState {
|
||||||
pub struct State {
|
pub struct State {
|
||||||
pub window_size: PhysicalSize<u32>,
|
pub window_size: PhysicalSize<u32>,
|
||||||
render_context: RenderContext,
|
render_context: RenderContext,
|
||||||
world_state: WorldState,
|
pub world_state: WorldState,
|
||||||
hud_state: HudState,
|
hud_state: HudState,
|
||||||
|
|
||||||
pub mouse_grabbed: bool,
|
pub mouse_grabbed: bool,
|
||||||
|
|
|
@ -67,7 +67,7 @@ impl WorldState {
|
||||||
render_context.swap_chain_descriptor.height,
|
render_context.swap_chain_descriptor.height,
|
||||||
cgmath::Deg(45.0),
|
cgmath::Deg(45.0),
|
||||||
0.1,
|
0.1,
|
||||||
5000.0,
|
300.0,
|
||||||
);
|
);
|
||||||
|
|
||||||
(camera, projection)
|
(camera, projection)
|
||||||
|
@ -85,7 +85,7 @@ impl WorldState {
|
||||||
.device
|
.device
|
||||||
.create_buffer_init(&BufferInitDescriptor {
|
.create_buffer_init(&BufferInitDescriptor {
|
||||||
label: Some("view_buffer"),
|
label: Some("view_buffer"),
|
||||||
contents: bytemuck::cast_slice(&[view]),
|
contents: bytemuck::cast_slice(&[view.to_raw()]),
|
||||||
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
|
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -324,7 +324,7 @@ impl WorldState {
|
||||||
render_pass.set_bind_group(1, &self.view_bind_group, &[]);
|
render_pass.set_bind_group(1, &self.view_bind_group, &[]);
|
||||||
render_pass.set_bind_group(2, &self.time_bind_group, &[]);
|
render_pass.set_bind_group(2, &self.time_bind_group, &[]);
|
||||||
|
|
||||||
triangle_count += self.world.render(&mut render_pass, &self.camera);
|
triangle_count += self.world.render(&mut render_pass, &self.view);
|
||||||
|
|
||||||
triangle_count
|
triangle_count
|
||||||
}
|
}
|
||||||
|
@ -428,13 +428,15 @@ impl WorldState {
|
||||||
pub fn update(&mut self, dt: Duration, render_context: &RenderContext) {
|
pub fn update(&mut self, dt: Duration, render_context: &RenderContext) {
|
||||||
self.update_position(dt);
|
self.update_position(dt);
|
||||||
|
|
||||||
self.world.update(render_context, &self.camera);
|
self.world.update(dt, render_context, &self.camera);
|
||||||
|
|
||||||
self.view
|
self.view
|
||||||
.update_view_projection(&self.camera, &self.projection);
|
.update_view_projection(&self.camera, &self.projection);
|
||||||
render_context
|
render_context.queue.write_buffer(
|
||||||
.queue
|
&self.view_buffer,
|
||||||
.write_buffer(&self.view_buffer, 0, bytemuck::cast_slice(&[self.view]));
|
0,
|
||||||
|
bytemuck::cast_slice(&[self.view.to_raw()]),
|
||||||
|
);
|
||||||
|
|
||||||
self.time.time += dt.as_secs_f32();
|
self.time.time += dt.as_secs_f32();
|
||||||
render_context.queue.write_buffer(
|
render_context.queue.write_buffer(
|
||||||
|
|
69
src/view.rs
69
src/view.rs
|
@ -1,24 +1,73 @@
|
||||||
use cgmath::SquareMatrix;
|
use cgmath::{EuclideanSpace, Matrix4, Point3, SquareMatrix, Vector4, Zero};
|
||||||
|
|
||||||
use crate::camera::{Camera, Projection};
|
use crate::{
|
||||||
|
aabb::Aabb,
|
||||||
|
camera::{Camera, Projection, OPENGL_TO_WGPU_MATRIX},
|
||||||
|
};
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
|
||||||
pub struct View {
|
pub struct View {
|
||||||
view_position: [f32; 4],
|
view_position: Vector4<f32>,
|
||||||
view_projection: [[f32; 4]; 4],
|
view_projection: Matrix4<f32>,
|
||||||
|
pub aabb: Aabb,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View {
|
impl View {
|
||||||
|
pub fn to_raw(&self) -> ViewRaw {
|
||||||
|
ViewRaw {
|
||||||
|
view_position: self.view_position.into(),
|
||||||
|
view_projection: self.view_projection.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
view_position: [0.0; 4],
|
view_position: Vector4::zero(),
|
||||||
view_projection: cgmath::Matrix4::identity().into(),
|
view_projection: Matrix4::identity(),
|
||||||
|
aabb: Aabb::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_view_projection(&mut self, camera: &Camera, projection: &Projection) {
|
pub fn update_view_projection(&mut self, camera: &Camera, projection: &Projection) {
|
||||||
self.view_position = camera.position.to_homogeneous().into();
|
self.view_position = camera.position.to_homogeneous();
|
||||||
self.view_projection = (projection.calculate_matrix() * camera.calculate_matrix()).into();
|
self.view_projection = projection.calculate_matrix() * camera.calculate_matrix();
|
||||||
|
self.aabb = self.frustrum_aabb();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frustrum_aabb(&self) -> Aabb {
|
||||||
|
let projection = OPENGL_TO_WGPU_MATRIX.invert().unwrap() * self.view_projection;
|
||||||
|
let inverse_matrix = projection.invert().unwrap();
|
||||||
|
|
||||||
|
let corners = &[
|
||||||
|
Vector4::new(-1.0, -1.0, 1.0, 1.0),
|
||||||
|
Vector4::new(-1.0, -1.0, -1.0, 1.0),
|
||||||
|
Vector4::new(-1.0, 1.0, 1.0, 1.0),
|
||||||
|
Vector4::new(-1.0, 1.0, -1.0, 1.0),
|
||||||
|
Vector4::new(1.0, -1.0, 1.0, 1.0),
|
||||||
|
Vector4::new(1.0, -1.0, -1.0, 1.0),
|
||||||
|
Vector4::new(1.0, 1.0, 1.0, 1.0),
|
||||||
|
Vector4::new(1.0, 1.0, -1.0, 1.0),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut min = Vector4::new(f32::INFINITY, f32::INFINITY, f32::INFINITY, 1.0);
|
||||||
|
let mut max = Vector4::new(0.0, 0.0, 0.0, 1.0);
|
||||||
|
for &corner in corners {
|
||||||
|
let corner = inverse_matrix * corner;
|
||||||
|
let corner = corner / corner.w;
|
||||||
|
|
||||||
|
min = min.zip(corner, f32::min);
|
||||||
|
max = max.zip(corner, f32::max);
|
||||||
|
}
|
||||||
|
|
||||||
|
Aabb {
|
||||||
|
min: Point3::from_vec(min.truncate()),
|
||||||
|
max: Point3::from_vec(max.truncate()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
|
pub struct ViewRaw {
|
||||||
|
view_position: [f32; 4],
|
||||||
|
view_projection: [[f32; 4]; 4],
|
||||||
|
}
|
||||||
|
|
112
src/world.rs
112
src/world.rs
|
@ -1,4 +1,4 @@
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::{collections::VecDeque, time::Duration};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
camera::Camera,
|
camera::Camera,
|
||||||
|
@ -6,30 +6,34 @@ use crate::{
|
||||||
geometry::GeometryBuffers,
|
geometry::GeometryBuffers,
|
||||||
npc::Npc,
|
npc::Npc,
|
||||||
render_context::RenderContext,
|
render_context::RenderContext,
|
||||||
|
view::View,
|
||||||
};
|
};
|
||||||
use ahash::AHashMap;
|
use ahash::AHashMap;
|
||||||
use cgmath::{EuclideanSpace, InnerSpace, Point3, Vector2, Vector3};
|
use cgmath::{EuclideanSpace, InnerSpace, Point3, Vector3};
|
||||||
use wgpu::{BufferUsage, RenderPass};
|
use wgpu::{BufferUsage, RenderPass};
|
||||||
|
|
||||||
pub struct World {
|
pub struct World {
|
||||||
pub chunks: HashMap<Point3<isize>, Chunk>,
|
|
||||||
pub npc: Npc,
|
pub npc: Npc,
|
||||||
|
|
||||||
|
pub chunks: AHashMap<Point3<isize>, Chunk>,
|
||||||
pub chunk_database: sled::Db,
|
pub chunk_database: sled::Db,
|
||||||
pub chunk_save_queue: VecDeque<Point3<isize>>,
|
pub chunk_save_queue: VecDeque<(Point3<isize>, bool)>,
|
||||||
pub chunk_load_queue: VecDeque<Point3<isize>>,
|
pub chunk_load_queue: VecDeque<Point3<isize>>,
|
||||||
pub chunk_generate_queue: VecDeque<Point3<isize>>,
|
pub chunk_generate_queue: VecDeque<Point3<isize>>,
|
||||||
pub chunk_buffers: AHashMap<Point3<isize>, GeometryBuffers<u16>>,
|
|
||||||
|
|
||||||
pub highlighted: Option<(Point3<isize>, Vector3<i32>)>,
|
pub highlighted: Option<(Point3<isize>, Vector3<i32>)>,
|
||||||
|
|
||||||
|
pub unload_timer: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const RENDER_DISTANCE: isize = 8;
|
pub const RENDER_DISTANCE: isize = 8;
|
||||||
pub const WORLD_HEIGHT: isize = 16 * 16 / CHUNK_ISIZE;
|
pub const WORLD_HEIGHT: isize = 16 * 16 / CHUNK_ISIZE;
|
||||||
|
|
||||||
|
const DEBUG_IO: bool = false;
|
||||||
|
|
||||||
impl World {
|
impl World {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let chunks = HashMap::new();
|
let chunks = AHashMap::new();
|
||||||
let npc = Npc::load();
|
let npc = Npc::load();
|
||||||
|
|
||||||
let chunk_database = sled::Config::new()
|
let chunk_database = sled::Config::new()
|
||||||
|
@ -47,13 +51,14 @@ impl World {
|
||||||
chunk_load_queue: VecDeque::new(),
|
chunk_load_queue: VecDeque::new(),
|
||||||
chunk_save_queue: VecDeque::new(),
|
chunk_save_queue: VecDeque::new(),
|
||||||
chunk_generate_queue: VecDeque::new(),
|
chunk_generate_queue: VecDeque::new(),
|
||||||
chunk_buffers: AHashMap::new(),
|
|
||||||
|
|
||||||
highlighted: None,
|
highlighted: None,
|
||||||
|
|
||||||
|
unload_timer: Duration::new(0, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, render_context: &RenderContext, camera: &Camera) {
|
pub fn update(&mut self, dt: Duration, render_context: &RenderContext, camera: &Camera) {
|
||||||
if let Some(position) = self.chunk_load_queue.pop_front() {
|
if let Some(position) = self.chunk_load_queue.pop_front() {
|
||||||
let chunk = self.chunks.entry(position).or_default();
|
let chunk = self.chunks.entry(position).or_default();
|
||||||
match chunk.load(position, &self.chunk_database) {
|
match chunk.load(position, &self.chunk_database) {
|
||||||
|
@ -62,20 +67,33 @@ impl World {
|
||||||
}
|
}
|
||||||
Ok(true) => {
|
Ok(true) => {
|
||||||
self.update_chunk_geometry(render_context, position);
|
self.update_chunk_geometry(render_context, position);
|
||||||
self.chunk_save_queue.push_back(position);
|
self.enqueue_chunk_save(position, false);
|
||||||
// println!("Generated chunk {:?}", position);
|
if DEBUG_IO {
|
||||||
|
println!("Generated chunk {:?}", position);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(false) => {
|
Ok(false) => {
|
||||||
self.update_chunk_geometry(render_context, position);
|
self.update_chunk_geometry(render_context, position);
|
||||||
// println!("Loaded chunk {:?}", position);
|
if DEBUG_IO {
|
||||||
|
println!("Loaded chunk {:?}", position);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let Some(position) = self.chunk_save_queue.pop_front() {
|
} else if let Some((position, unload)) = self.chunk_save_queue.pop_front() {
|
||||||
let chunk = self.chunks.get(&position).unwrap();
|
if let Some(chunk) = self.chunks.get(&position) {
|
||||||
if let Err(err) = chunk.save(position, &self.chunk_database) {
|
if let Err(err) = chunk.save(position, &self.chunk_database) {
|
||||||
eprintln!("Failed to save chunk {:?}: {:?}", position, err);
|
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 {
|
} else {
|
||||||
// println!("Saved chunk {:?}", position);
|
eprintln!("Tried to save unloaded chunk {:?}", position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,22 +120,42 @@ impl World {
|
||||||
});
|
});
|
||||||
|
|
||||||
self.chunk_load_queue.extend(load_queue);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render<'a>(&'a self, render_pass: &mut RenderPass<'a>, camera: &Camera) -> usize {
|
pub fn render<'a>(&'a self, render_pass: &mut RenderPass<'a>, view: &View) -> usize {
|
||||||
let camera_pos = camera.position.to_vec();
|
|
||||||
let camera_pos = Vector2::new(camera_pos.x, camera_pos.z);
|
|
||||||
let mut triangle_count = 0;
|
let mut triangle_count = 0;
|
||||||
|
|
||||||
for (position, buffers) in &self.chunk_buffers {
|
for (position, chunk) in &self.chunks {
|
||||||
let pos = (position * CHUNK_ISIZE).cast().unwrap();
|
if !chunk.is_visible(position * CHUNK_ISIZE, &view) {
|
||||||
let pos = Vector2::new(pos.x, pos.z);
|
|
||||||
if (pos - camera_pos).magnitude() > 300.0 {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffers.set_buffers(render_pass);
|
if let Some(buffers) = chunk.buffers.as_ref() {
|
||||||
triangle_count += buffers.draw_indexed(render_pass);
|
buffers.apply_buffers(render_pass);
|
||||||
|
triangle_count += buffers.draw_indexed(render_pass);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -129,21 +167,35 @@ impl World {
|
||||||
triangle_count
|
triangle_count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
pub fn update_chunk_geometry(
|
||||||
&mut self,
|
&mut self,
|
||||||
render_context: &RenderContext,
|
render_context: &RenderContext,
|
||||||
chunk_position: Point3<isize>,
|
chunk_position: Point3<isize>,
|
||||||
) {
|
) {
|
||||||
let chunk = &mut self.chunks.get(&chunk_position).unwrap();
|
let chunk = self.chunks.get_mut(&chunk_position).unwrap();
|
||||||
let offset = chunk_position * CHUNK_ISIZE;
|
let offset = chunk_position * CHUNK_ISIZE;
|
||||||
let geometry = chunk.to_geometry(
|
let geometry = chunk.to_geometry(
|
||||||
offset,
|
offset,
|
||||||
World::highlighted_for_chunk(self.highlighted, &chunk_position).as_ref(),
|
World::highlighted_for_chunk(self.highlighted, &chunk_position).as_ref(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let buffers =
|
chunk.buffers = Some(GeometryBuffers::from_geometry(
|
||||||
GeometryBuffers::from_geometry(render_context, &geometry, BufferUsage::empty());
|
render_context,
|
||||||
self.chunk_buffers.insert(chunk_position, buffers);
|
&geometry,
|
||||||
|
BufferUsage::empty(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_highlight(&mut self, render_context: &RenderContext, camera: &Camera) {
|
fn update_highlight(&mut self, render_context: &RenderContext, camera: &Camera) {
|
||||||
|
@ -247,7 +299,7 @@ impl World {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.chunk_save_queue
|
self.chunk_save_queue
|
||||||
.push_back(chunk_position / CHUNK_ISIZE);
|
.push_back((chunk_position / CHUNK_ISIZE, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calc_scale(vector: Vector3<f32>, scalar: f32) -> f32 {
|
fn calc_scale(vector: Vector3<f32>, scalar: f32) -> f32 {
|
||||||
|
|
Loading…
Reference in a new issue