Implement chunk unloading, frustrum culling and some other minor optimisations
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Sijmen 2021-06-03 16:34:42 +02:00
parent 206f7857fa
commit 4bba01058b
Signed by: vijfhoek
GPG key ID: 82D05C89B28B0DAE
12 changed files with 230 additions and 120 deletions

1
.gitignore vendored
View file

@ -4,5 +4,6 @@
/perf.data* /perf.data*
/chunks/ /chunks/
GPUCache/
profile.txt profile.txt
callgrind.out.* callgrind.out.*

View file

@ -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))
} }
} }

View file

@ -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>,

View file

@ -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)
}
} }

View file

@ -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);
} }

View file

@ -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 => {

View file

@ -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) {

View file

@ -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);

View file

@ -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,

View file

@ -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(

View file

@ -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],
}

View file

@ -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 {