From aae233895030d4c9e23e7872c0ca321f18d2dcd1 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Mon, 25 May 2020 15:55:02 -0700 Subject: [PATCH] Frustum culling for chunk generation and rendering --- client/src/graphics/draw.rs | 2 +- client/src/graphics/frustum.rs | 81 +++++++++++++++++++++++++++++++ client/src/graphics/voxels/mod.rs | 15 +++++- common/src/dodeca.rs | 12 +++++ 4 files changed, 107 insertions(+), 3 deletions(-) diff --git a/client/src/graphics/draw.rs b/client/src/graphics/draw.rs index 64266fb8..e79e7127 100644 --- a/client/src/graphics/draw.rs +++ b/client/src/graphics/draw.rs @@ -341,7 +341,7 @@ impl Draw { ); if let Some(ref mut voxels) = self.voxels { - voxels.prepare(device, state.voxels.as_mut().unwrap(), sim, cmd); + voxels.prepare(device, state.voxels.as_mut().unwrap(), sim, cmd, frustum); } // Ensure reads of just-transferred memory wait until it's ready diff --git a/client/src/graphics/frustum.rs b/client/src/graphics/frustum.rs index f249abe4..2b4d9304 100644 --- a/client/src/graphics/frustum.rs +++ b/client/src/graphics/frustum.rs @@ -1,3 +1,5 @@ +use common::Plane; + #[derive(Debug, Copy, Clone)] pub struct Frustum { pub left: f32, @@ -44,4 +46,83 @@ impl Frustum { 0.0, 0.0, za, zb, 0.0, 0.0, -1.0, 0.0)) } + + pub fn planes(&self) -> FrustumPlanes { + FrustumPlanes { + left: Plane::from( + na::UnitQuaternion::from_axis_angle(&na::Vector3::y_axis(), self.left) + * -na::Vector3::x_axis(), + ), + right: Plane::from( + na::UnitQuaternion::from_axis_angle(&na::Vector3::y_axis(), self.right) + * na::Vector3::x_axis(), + ), + down: Plane::from( + na::UnitQuaternion::from_axis_angle(&na::Vector3::x_axis(), self.down) + * na::Vector3::y_axis(), + ), + up: Plane::from( + na::UnitQuaternion::from_axis_angle(&na::Vector3::x_axis(), self.up) + * -na::Vector3::y_axis(), + ), + } + } +} + +#[derive(Debug, Copy, Clone)] +pub struct FrustumPlanes { + left: Plane, + right: Plane, + down: Plane, + up: Plane, +} + +impl FrustumPlanes { + pub fn contain(&self, point: &na::Vector4, radius: f32) -> bool { + for &plane in &[&self.left, &self.right, &self.down, &self.up] { + if plane.distance_to(point) < -radius { + return false; + } + } + true + } +} + +#[cfg(test)] +mod tests { + use super::*; + use common::math::{origin, translate_along}; + use std::f32; + + #[test] + fn planes_sanity() { + // 90 degree square + let planes = Frustum::from_vfov(f32::consts::FRAC_PI_4, 1.0).planes(); + assert!(planes.contain(&origin(), 0.1)); + assert!(planes.contain( + &(translate_along(&na::Vector3::z_axis(), -1.0) * origin()), + 0.0 + )); + assert!(!planes.contain( + &(translate_along(&na::Vector3::z_axis(), 1.0) * origin()), + 0.0 + )); + + assert!(!planes.contain( + &(translate_along(&na::Vector3::x_axis(), 1.0) * origin()), + 0.0 + )); + assert!(!planes.contain( + &(translate_along(&na::Vector3::y_axis(), 1.0) * origin()), + 0.0 + )); + assert!(!planes.contain( + &(translate_along(&na::Vector3::x_axis(), -1.0) * origin()), + 0.0 + )); + assert!(!planes.contain( + &(translate_along(&na::Vector3::y_axis(), -1.0) * origin()), + 0.0 + )); + } } diff --git a/client/src/graphics/voxels/mod.rs b/client/src/graphics/voxels/mod.rs index 8108ccf7..0a120811 100644 --- a/client/src/graphics/voxels/mod.rs +++ b/client/src/graphics/voxels/mod.rs @@ -11,12 +11,12 @@ use metrics::timing; use tracing::warn; use crate::{ - graphics::Base, + graphics::{Base, Frustum}, loader::{Cleanup, LoadCtx, LoadFuture, Loadable, WorkQueue}, sim::VoxelData, worldgen, Config, Loader, Sim, }; -use common::{dodeca::Vertex, graph::NodeId, lru_slab::SlotId, math, LruSlab}; +use common::{dodeca, dodeca::Vertex, graph::NodeId, lru_slab::SlotId, math, LruSlab}; use surface::Surface; use surface_extraction::{DrawBuffer, ScratchBuffer, SurfaceExtraction}; @@ -79,6 +79,7 @@ impl Voxels { frame: &mut Frame, sim: &mut Sim, cmd: vk::CommandBuffer, + frustum: &Frustum, ) { // Clean up after previous frame for i in frame.extracted.drain(..) { @@ -119,7 +120,17 @@ impl Voxels { .unwrap_or(std::cmp::Ordering::Less) }); let node_scan_started = Instant::now(); + let frustum_planes = frustum.planes(); + let local_to_view = view.local.try_inverse().unwrap(); for &(node, ref node_transform) in &nodes { + let node_to_view = local_to_view * node_transform; + let origin = node_to_view * math::origin(); + if !frustum_planes.contain(&origin, dodeca::BOUNDING_SPHERE_RADIUS as f32) { + // Don't bother generating or drawing chunks from nodes that are wholly outside the + // frustum. + continue; + } + use Chunk::*; for chunk in Vertex::iter() { // Fetch existing chunk, or extract surface of new chunk diff --git a/common/src/dodeca.rs b/common/src/dodeca.rs index 227786f7..ce2c4227 100644 --- a/common/src/dodeca.rs +++ b/common/src/dodeca.rs @@ -145,6 +145,8 @@ impl Vertex { pub const VERTEX_COUNT: usize = 20; pub const SIDE_COUNT: usize = 12; +#[allow(clippy::unreadable_literal)] +pub const BOUNDING_SPHERE_RADIUS: f64 = 1.2264568712514068; lazy_static! { /// Whether two sides share an edge @@ -251,6 +253,7 @@ lazy_static! { #[cfg(test)] mod tests { use super::*; + use approx::*; #[test] fn vertex_sides() { @@ -287,4 +290,13 @@ mod tests { assert!(side.faces(&(side.reflection() * math::origin()))); } } + + #[test] + fn radius() { + let corner = Vertex::A.chunk_to_node() * na::Vector4::repeat(1.0); + assert_abs_diff_eq!( + BOUNDING_SPHERE_RADIUS, + math::distance(&corner, &math::origin()) + ); + } }