Implement better chunk culling
This commit is contained in:
parent
7692c54cf7
commit
de673f1ee1
|
@ -8,6 +8,7 @@ use world;
|
|||
use render;
|
||||
use resources;
|
||||
use model;
|
||||
use types::bit::Set;
|
||||
|
||||
const NUM_WORKERS: usize = 8;
|
||||
|
||||
|
@ -16,9 +17,6 @@ pub struct ChunkBuilder {
|
|||
free_builders: Vec<(usize, Vec<u8>)>,
|
||||
built_recv: mpsc::Receiver<(usize, BuildReply)>,
|
||||
|
||||
sections: Vec<(i32, i32, i32)>,
|
||||
next_collection: f64,
|
||||
|
||||
models: Arc<RwLock<model::Factory>>,
|
||||
resources: Arc<RwLock<resources::Manager>>,
|
||||
resource_version: usize,
|
||||
|
@ -43,17 +41,13 @@ impl ChunkBuilder {
|
|||
threads: threads,
|
||||
free_builders: free,
|
||||
built_recv: built_recv,
|
||||
sections: vec![],
|
||||
next_collection: 0.0,
|
||||
models: models,
|
||||
resources: resources.clone(),
|
||||
resource_version: 0xFFFF,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tick(&mut self, world: &mut world::World, renderer: &mut render::Renderer, delta: f64) {
|
||||
use std::cmp::Ordering;
|
||||
|
||||
pub fn tick(&mut self, world: &mut world::World, renderer: &mut render::Renderer, _delta: f64) {
|
||||
{
|
||||
let rm = self.resources.read().unwrap();
|
||||
if rm.version() != self.resource_version {
|
||||
|
@ -65,8 +59,9 @@ impl ChunkBuilder {
|
|||
while let Ok((id, mut val)) = self.built_recv.try_recv() {
|
||||
world.reset_building_flag(val.position);
|
||||
|
||||
if let Some(render_buffer) = world.get_section_buffer(val.position.0, val.position.1, val.position.2) {
|
||||
renderer.update_chunk_solid(render_buffer, &val.solid_buffer, val.solid_count);
|
||||
if let Some(sec) = world.get_section_mut(val.position.0, val.position.1, val.position.2) {
|
||||
sec.cull_info = val.cull_info;
|
||||
renderer.update_chunk_solid(&mut sec.render_buffer, &val.solid_buffer, val.solid_count);
|
||||
}
|
||||
|
||||
val.solid_buffer.clear();
|
||||
|
@ -75,30 +70,11 @@ impl ChunkBuilder {
|
|||
if self.free_builders.is_empty() {
|
||||
return;
|
||||
}
|
||||
self.next_collection -= delta;
|
||||
if self.next_collection <= 0.0 {
|
||||
let mut sections = world.get_dirty_chunk_sections();
|
||||
sections.sort_by(|a, b| {
|
||||
let xx = ((a.0<<4)+8) as f64 - renderer.camera.pos.x;
|
||||
let yy = ((a.1<<4)+8) as f64 - renderer.camera.pos.y;
|
||||
let zz = ((a.2<<4)+8) as f64 - renderer.camera.pos.z;
|
||||
let a_dist = xx*xx + yy*yy + zz*zz;
|
||||
let xx = ((b.0<<4)+8) as f64 - renderer.camera.pos.x;
|
||||
let yy = ((b.1<<4)+8) as f64 - renderer.camera.pos.y;
|
||||
let zz = ((b.2<<4)+8) as f64 - renderer.camera.pos.z;
|
||||
let b_dist = xx*xx + yy*yy + zz*zz;
|
||||
if a_dist == b_dist {
|
||||
Ordering::Equal
|
||||
} else if a_dist > b_dist {
|
||||
Ordering::Less
|
||||
} else {
|
||||
Ordering::Greater
|
||||
}
|
||||
});
|
||||
self.sections = sections;
|
||||
self.next_collection = 60.0;
|
||||
}
|
||||
while let Some((x, y, z)) = self.sections.pop() {
|
||||
let dirty_sections = world.get_render_list().iter()
|
||||
.map(|v| v.0)
|
||||
.filter(|v| world.is_section_dirty(*v))
|
||||
.collect::<Vec<_>>();
|
||||
for (x,y, z) in dirty_sections {
|
||||
let t_id = self.free_builders.pop().unwrap();
|
||||
world.set_building_flag((x, y, z));
|
||||
let (cx, cy, cz) = (x << 4, y << 4, z << 4);
|
||||
|
@ -126,6 +102,7 @@ struct BuildReply {
|
|||
position: (i32, i32, i32),
|
||||
solid_buffer: Vec<u8>,
|
||||
solid_count: usize,
|
||||
cull_info: CullInfo,
|
||||
}
|
||||
|
||||
fn build_func(id: usize, models: Arc<RwLock<model::Factory>>, work_recv: mpsc::Receiver<BuildReq>, built_send: mpsc::Sender<(usize, BuildReply)>) {
|
||||
|
@ -173,14 +150,106 @@ fn build_func(id: usize, models: Arc<RwLock<model::Factory>>, work_recv: mpsc::R
|
|||
}
|
||||
}
|
||||
|
||||
let cull_info = build_cull_info(&snapshot);
|
||||
|
||||
built_send.send((id, BuildReply {
|
||||
position: position,
|
||||
solid_buffer: solid_buffer,
|
||||
solid_count: solid_count,
|
||||
cull_info: cull_info,
|
||||
})).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn build_cull_info(snapshot: &world::Snapshot) -> CullInfo {
|
||||
let mut visited = Set::new(16 * 16 * 16);
|
||||
let mut info = CullInfo::new();
|
||||
|
||||
for y in 0 .. 16 {
|
||||
for z in 0 .. 16 {
|
||||
for x in 0 .. 16 {
|
||||
if visited.get(x | (z << 4) | (y << 8)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let touched = flood_fill(snapshot, &mut visited, x as i32, y as i32, z as i32);
|
||||
if touched == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
for d1 in Direction::all() {
|
||||
if (touched & (1 << d1.index())) != 0 {
|
||||
for d2 in Direction::all() {
|
||||
if (touched & (1 << d2.index())) != 0 {
|
||||
info.set_visible(d1, d2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info
|
||||
}
|
||||
|
||||
fn flood_fill(snapshot: &world::Snapshot, visited: &mut Set, x: i32, y: i32, z: i32) -> u8 {
|
||||
let idx = (x | (z << 4) | (y << 8)) as usize;
|
||||
if x < 0 || x > 15 || y < 0 || y > 15 || z < 0 || z > 15 || visited.get(idx) {
|
||||
return 0;
|
||||
}
|
||||
visited.set(idx, true);
|
||||
|
||||
if snapshot.get_block(x, y, z).get_material().should_cull_against {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let mut touched = 0;
|
||||
|
||||
if x == 0 {
|
||||
touched |= 1 << Direction::West.index();
|
||||
} else if x == 15 {
|
||||
touched |= 1 << Direction::East.index();
|
||||
}
|
||||
if y == 0 {
|
||||
touched |= 1 << Direction::Down.index();
|
||||
} else if y == 15 {
|
||||
touched |= 1 << Direction::Up.index();
|
||||
}
|
||||
if z == 0 {
|
||||
touched |= 1 << Direction::North.index();
|
||||
} else if z == 15 {
|
||||
touched |= 1 << Direction::South.index();
|
||||
}
|
||||
|
||||
for d in Direction::all() {
|
||||
let (ox, oy, oz) = d.get_offset();
|
||||
touched |= flood_fill(snapshot, visited, x+ox, y+oy, z+oz);
|
||||
}
|
||||
touched
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct CullInfo(u64);
|
||||
|
||||
impl CullInfo {
|
||||
pub fn new() -> CullInfo {
|
||||
CullInfo(0)
|
||||
}
|
||||
|
||||
pub fn all_vis() -> CullInfo {
|
||||
CullInfo(0xFFFFFFFFFFFFFFFF)
|
||||
}
|
||||
|
||||
pub fn is_visible(&self, from: Direction, to: Direction) -> bool {
|
||||
(self.0 & (1 << (from.index() * 6 + to.index()))) != 0
|
||||
}
|
||||
|
||||
pub fn set_visible(&mut self, from: Direction, to: Direction) {
|
||||
self.0 |= 1 << (from.index() * 6 + to.index());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Direction {
|
||||
Invalid,
|
||||
|
@ -213,6 +282,18 @@ impl Direction {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn opposite(&self) -> Direction {
|
||||
match *self {
|
||||
Direction::Up => Direction::Down,
|
||||
Direction::Down => Direction::Up,
|
||||
Direction::North => Direction::South,
|
||||
Direction::South => Direction::North,
|
||||
Direction::West => Direction::East,
|
||||
Direction::East => Direction::West,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_verts(&self) -> &'static [BlockVertex; 4] {
|
||||
match *self {
|
||||
Direction::Up => PRECOMPUTED_VERTS[0],
|
||||
|
|
|
@ -70,6 +70,8 @@ pub struct Renderer {
|
|||
pub frustum: collision::Frustum<f32>,
|
||||
pub view_vector: cgmath::Vector3<f32>,
|
||||
|
||||
pub frame_id: u32,
|
||||
|
||||
trans: Option<TransInfo>,
|
||||
|
||||
last_width: u32,
|
||||
|
@ -214,6 +216,8 @@ impl Renderer {
|
|||
frustum: collision::Frustum::from_matrix4(cgmath::Matrix4::identity()).unwrap(),
|
||||
view_vector: cgmath::Vector3::zero(),
|
||||
|
||||
frame_id: 1,
|
||||
|
||||
trans: None,
|
||||
}
|
||||
}
|
||||
|
@ -287,9 +291,11 @@ impl Renderer {
|
|||
|
||||
for (pos, info) in world.get_render_list() {
|
||||
if let Some(solid) = info.solid.as_ref() {
|
||||
self.chunk_shader.offset.set_int3(pos.0, pos.1 * 4096, pos.2);
|
||||
solid.array.bind();
|
||||
gl::draw_elements(gl::TRIANGLES, solid.count, self.element_buffer_type, 0);
|
||||
if solid.count > 0 {
|
||||
self.chunk_shader.offset.set_int3(pos.0, pos.1 * 4096, pos.2);
|
||||
solid.array.bind();
|
||||
gl::draw_elements(gl::TRIANGLES, solid.count, self.element_buffer_type, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -337,6 +343,8 @@ impl Renderer {
|
|||
gl::disable(gl::MULTISAMPLE);
|
||||
|
||||
self.ui.tick(width, height);
|
||||
|
||||
self.frame_id = self.frame_id.wrapping_add(1);
|
||||
}
|
||||
|
||||
fn ensure_element_buffer(&mut self, size: usize) {
|
||||
|
|
|
@ -23,6 +23,7 @@ use protocol;
|
|||
use render;
|
||||
use collision;
|
||||
use cgmath;
|
||||
use chunk_builder;
|
||||
|
||||
pub mod biome;
|
||||
|
||||
|
@ -61,15 +62,57 @@ impl World {
|
|||
}
|
||||
|
||||
pub fn compute_render_list(&mut self, renderer: &mut render::Renderer) {
|
||||
use chunk_builder::{self, Direction};
|
||||
use cgmath::Vector;
|
||||
use std::collections::VecDeque;
|
||||
self.render_list.clear();
|
||||
for (pos, chunk) in &self.chunks {
|
||||
for (y, sec) in chunk.sections.iter().enumerate() {
|
||||
if let Some(sec) = sec.as_ref() {
|
||||
let pos = (pos.0, y as i32, pos.1);
|
||||
let min = cgmath::Point3::new(pos.0 as f32 * 16.0, -pos.1 as f32 * 16.0, pos.2 as f32 * 16.0);
|
||||
let bounds = collision::Aabb3::new(min, min + cgmath::Vector3::new(16.0, -16.0, 16.0));
|
||||
if renderer.frustum.contains(bounds) != collision::Relation::Out {
|
||||
self.render_list.push(pos);
|
||||
|
||||
let mut valid_dirs = [false; 6];
|
||||
for dir in Direction::all() {
|
||||
let (ox, oy, oz) = dir.get_offset();
|
||||
let dir_vec = cgmath::Vector3::new(ox as f32, oy as f32, oz as f32);
|
||||
valid_dirs[dir.index()] = renderer.view_vector.dot(dir_vec) > -0.8;
|
||||
}
|
||||
|
||||
let start = (
|
||||
((renderer.camera.pos.x as i32) >> 4),
|
||||
((renderer.camera.pos.y as i32) >> 4),
|
||||
((renderer.camera.pos.z as i32) >> 4)
|
||||
);
|
||||
|
||||
let mut process_queue = VecDeque::with_capacity(self.chunks.len() * 16);
|
||||
process_queue.push_front((Direction::Invalid, start));
|
||||
|
||||
while let Some((from, pos)) = process_queue.pop_front() {
|
||||
let (exists, cull) = if let Some((sec, rendered_on)) = self.get_render_section_mut(pos.0, pos.1, pos.2) {
|
||||
if *rendered_on == renderer.frame_id {
|
||||
continue;
|
||||
}
|
||||
*rendered_on = renderer.frame_id;
|
||||
|
||||
let min = cgmath::Point3::new(pos.0 as f32 * 16.0, -pos.1 as f32 * 16.0, pos.2 as f32 * 16.0);
|
||||
let bounds = collision::Aabb3::new(min, min + cgmath::Vector3::new(16.0, -16.0, 16.0));
|
||||
if renderer.frustum.contains(bounds) == collision::Relation::Out {
|
||||
continue;
|
||||
}
|
||||
(sec.is_some(), sec.map(|v| v.cull_info).unwrap_or(chunk_builder::CullInfo::all_vis()))
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if exists {
|
||||
self.render_list.push(pos);
|
||||
}
|
||||
|
||||
for dir in Direction::all() {
|
||||
let (ox, oy, oz) = dir.get_offset();
|
||||
let opos = (pos.0 + ox, pos.1 + oy, pos.2 + oz);
|
||||
if let Some((_, rendered_on)) = self.get_render_section_mut(opos.0, opos.1, opos.2) {
|
||||
if *rendered_on == renderer.frame_id {
|
||||
continue;
|
||||
}
|
||||
if from == Direction::Invalid || (valid_dirs[dir.index()] && cull.is_visible(from, dir)) {
|
||||
process_queue.push_back((dir.opposite(), opos));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,15 +127,29 @@ impl World {
|
|||
}).collect()
|
||||
}
|
||||
|
||||
pub fn get_section_buffer(&mut self, x: i32, y: i32, z: i32) -> Option<&mut render::ChunkBuffer> {
|
||||
pub fn get_section_mut(&mut self, x: i32, y: i32, z: i32) -> Option<&mut Section> {
|
||||
if let Some(chunk) = self.chunks.get_mut(&CPos(x, z)) {
|
||||
if let Some(sec) = chunk.sections[y as usize].as_mut() {
|
||||
return Some(&mut sec.render_buffer);
|
||||
return Some(sec);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn get_render_section_mut(&mut self, x: i32, y: i32, z: i32) -> Option<(Option<&mut Section>, &mut u32)> {
|
||||
if y < 0 || y > 15 {
|
||||
return None;
|
||||
}
|
||||
if let Some(chunk) = self.chunks.get_mut(&CPos(x, z)) {
|
||||
let rendered = &mut chunk.sections_rendered_on[y as usize];
|
||||
if let Some(sec) = chunk.sections[y as usize].as_mut() {
|
||||
return Some((Some(sec), rendered));
|
||||
}
|
||||
return Some((None, rendered));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_dirty_chunk_sections(&mut self) -> Vec<(i32, i32, i32)> {
|
||||
let mut out = vec![];
|
||||
for (_, chunk) in &mut self.chunks {
|
||||
|
@ -107,6 +164,15 @@ impl World {
|
|||
out
|
||||
}
|
||||
|
||||
pub fn is_section_dirty(&self, pos: (i32, i32, i32)) -> bool {
|
||||
if let Some(chunk) = self.chunks.get(&CPos(pos.0, pos.2)) {
|
||||
if let Some(sec) = chunk.sections[pos.1 as usize].as_ref() {
|
||||
return sec.dirty && !sec.building;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn set_building_flag(&mut self, pos: (i32, i32, i32)) {
|
||||
if let Some(chunk) = self.chunks.get_mut(&CPos(pos.0, pos.2)) {
|
||||
if let Some(sec) = chunk.sections[pos.1 as usize].as_mut() {
|
||||
|
@ -375,6 +441,7 @@ pub struct Chunk {
|
|||
position: CPos,
|
||||
|
||||
sections: [Option<Section>; 16],
|
||||
sections_rendered_on: [u32; 16],
|
||||
biomes: [u8; 16 * 16],
|
||||
}
|
||||
|
||||
|
@ -388,6 +455,7 @@ impl Chunk {
|
|||
None,None,None,None,
|
||||
None,None,None,None,
|
||||
],
|
||||
sections_rendered_on: [0; 16],
|
||||
biomes: [0; 16 * 16],
|
||||
}
|
||||
}
|
||||
|
@ -428,8 +496,10 @@ pub struct SectionKey {
|
|||
pos: (i32, u8, i32),
|
||||
}
|
||||
|
||||
struct Section {
|
||||
pub struct Section {
|
||||
pub cull_info: chunk_builder::CullInfo,
|
||||
pub render_buffer: render::ChunkBuffer,
|
||||
|
||||
y: u8,
|
||||
|
||||
blocks: bit::Map,
|
||||
|
@ -446,6 +516,7 @@ struct Section {
|
|||
impl Section {
|
||||
fn new(x: i32, y: u8, z: i32) -> Section {
|
||||
let mut section = Section {
|
||||
cull_info: chunk_builder::CullInfo::all_vis(),
|
||||
render_buffer: render::ChunkBuffer::new(),
|
||||
y: y,
|
||||
|
||||
|
|
Loading…
Reference in New Issue