diff --git a/src/chunk_builder.rs b/src/chunk_builder.rs index 153e6e5..bafd727 100644 --- a/src/chunk_builder.rs +++ b/src/chunk_builder.rs @@ -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)>, built_recv: mpsc::Receiver<(usize, BuildReply)>, - sections: Vec<(i32, i32, i32)>, - next_collection: f64, - models: Arc>, resources: Arc>, 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::>(); + 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, solid_count: usize, + cull_info: CullInfo, } fn build_func(id: usize, models: Arc>, work_recv: mpsc::Receiver, built_send: mpsc::Sender<(usize, BuildReply)>) { @@ -173,14 +150,106 @@ fn build_func(id: usize, models: Arc>, 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], diff --git a/src/render/mod.rs b/src/render/mod.rs index b89fcfd..958b923 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -70,6 +70,8 @@ pub struct Renderer { pub frustum: collision::Frustum, pub view_vector: cgmath::Vector3, + pub frame_id: u32, + trans: Option, 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) { diff --git a/src/world/mod.rs b/src/world/mod.rs index 5f5df0b..71c870f 100644 --- a/src/world/mod.rs +++ b/src/world/mod.rs @@ -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
; 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,