rendering chunks

This commit is contained in:
griffi-gh 2023-01-17 18:56:15 +01:00
parent 3cfd6448cc
commit 213075c5f9
6 changed files with 204 additions and 66 deletions

View file

@ -1,6 +1,5 @@
use glam::Vec2; use glam::Vec2;
use glium::{Surface, uniform}; use glium::Surface;
use glium::uniforms::{Sampler, MinifySamplerFilter, MagnifySamplerFilter};
use glium::glutin::{ use glium::glutin::{
event::{Event, WindowEvent, DeviceEvent}, event::{Event, WindowEvent, DeviceEvent},
event_loop::{EventLoop, ControlFlow}, event_loop::{EventLoop, ControlFlow},
@ -19,7 +18,7 @@ mod options;
use assets::Assets; use assets::Assets;
use display::init_display; use display::init_display;
use shaders::{Programs, chunk::Vertex as ChunkVertex}; use shaders::Programs;
use camera::Camera; use camera::Camera;
use controller::Controls; use controller::Controls;
use world::World; use world::World;
@ -53,15 +52,15 @@ pub fn run() {
let options = GameOptions::default(); let options = GameOptions::default();
log::info!("init game state"); log::info!("init game state");
let mut state = State::init(); let mut state = State::init();
state.camera.position = [0., 0., -1.]; state.camera.position = [0., 260., -1.];
log::info!("game loaded"); log::info!("game loaded");
//======================= //=======================
let vertex1 = ChunkVertex { position: [-0.5, -0.5, 0.], uv: [0., 0.], normal: [0., 1., 0.] }; // let vertex1 = ChunkVertex { position: [-0.5, -0.5, 0.], uv: [0., 0.], normal: [0., 1., 0.] };
let vertex2 = ChunkVertex { position: [ 0.0, 0.5, 0.], uv: [0., 1.], normal: [0., 1., 0.] }; // let vertex2 = ChunkVertex { position: [ 0.0, 0.5, 0.], uv: [0., 1.], normal: [0., 1., 0.] };
let vertex3 = ChunkVertex { position: [ 0.5, -0.5, 0.], uv: [1., 1.], normal: [0., 1., 0.] }; // let vertex3 = ChunkVertex { position: [ 0.5, -0.5, 0.], uv: [1., 1.], normal: [0., 1., 0.] };
let shape = vec![vertex1, vertex2, vertex3]; // let shape = vec![vertex1, vertex2, vertex3];
let vertex_buffer = glium::VertexBuffer::new(&display, &shape).unwrap(); // let vertex_buffer = glium::VertexBuffer::new(&display, &shape).unwrap();
//======================= //=======================
let mut last_render = Instant::now(); let mut last_render = Instant::now();
@ -122,12 +121,11 @@ pub fn run() {
let target_dimensions = target.get_dimensions(); let target_dimensions = target.get_dimensions();
let perspective = state.camera.perspective_matrix(target_dimensions); let perspective = state.camera.perspective_matrix(target_dimensions);
let view = state.camera.view_matrix(); let view = state.camera.view_matrix();
//Draw example triangle
//Draw chunks //Draw chunks
state.world.render(&mut target, &programs, &assets, perspective, view); state.world.render(&mut target, &programs, &assets, perspective, view);
//Draw example triangle
// target.draw( // target.draw(
// &vertex_buffer, // &vertex_buffer,
// glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList), // glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList),

View file

@ -109,7 +109,7 @@ impl Default for Controls {
fn default() -> Self { fn default() -> Self {
Self { Self {
inputs: Default::default(), inputs: Default::default(),
speed: 1., speed: 10.,
sensitivity: 2., sensitivity: 2.,
} }
} }

View file

@ -1,6 +1,8 @@
use glam::{Vec2, IVec2}; use glam::{Vec2, IVec2};
use glium::{ use glium::{
Display, Frame, Surface, uniform, Display, Frame, Surface,
DrawParameters, Depth,
DepthTest, uniform,
uniforms::{ uniforms::{
Sampler, SamplerBehavior, Sampler, SamplerBehavior,
MinifySamplerFilter, MagnifySamplerFilter, MinifySamplerFilter, MagnifySamplerFilter,
@ -24,6 +26,8 @@ const NEGATIVE_X_NEIGHBOR: usize = 1;
const POSITIVE_Z_NEIGHBOR: usize = 2; const POSITIVE_Z_NEIGHBOR: usize = 2;
const NEGATIVE_Z_NEIGHBOR: usize = 3; const NEGATIVE_Z_NEIGHBOR: usize = 3;
const MAX_TASKS: usize = 8;
pub struct World { pub struct World {
pub chunks: HashMap<IVec2, Chunk>, pub chunks: HashMap<IVec2, Chunk>,
pub thread: WorldThreading, pub thread: WorldThreading,
@ -37,12 +41,14 @@ impl World {
self.chunks.get(&(position - IVec2::new(0, 1))), self.chunks.get(&(position - IVec2::new(0, 1))),
] ]
} }
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
chunks: HashMap::new(), chunks: HashMap::new(),
thread: WorldThreading::new(), thread: WorldThreading::new(),
} }
} }
pub fn render( pub fn render(
&self, &self,
target: &mut Frame, target: &mut Frame,
@ -56,28 +62,39 @@ impl World {
magnify_filter: MagnifySamplerFilter::Nearest, magnify_filter: MagnifySamplerFilter::Nearest,
..Default::default() ..Default::default()
}; };
let draw_parameters = DrawParameters {
depth: Depth {
test: DepthTest::IfLess,
write: true,
..Default::default()
},
//backface_culling: glium::draw_parameters::BackfaceCullingMode::CullClockwise,
..Default::default()
};
for (&position, chunk) in &self.chunks { for (&position, chunk) in &self.chunks {
if let Some((_, vertex, index)) = &chunk.mesh { if let Some(mesh) = &chunk.mesh {
target.draw( target.draw(
vertex, &mesh.vertex_buffer,
index, &mesh.index_buffer,
&programs.chunk, &programs.chunk,
&uniform! { &uniform! {
model: [ model: [
[1., 0., 0., 0.], [1., 0., 0., 0.],
[0., 1., 0., 0.], [0., 1., 0., 0.],
[0., 0., 1., 0.], [0., 0., 1., 0.],
//[0., 0., 0., 1.0_f32]
[(position.x * CHUNK_SIZE as i32) as f32, 0., (position.y * CHUNK_SIZE as i32) as f32, 1.0_f32] [(position.x * CHUNK_SIZE as i32) as f32, 0., (position.y * CHUNK_SIZE as i32) as f32, 1.0_f32]
], ],
view: view, view: view,
persperctive: perspective, perspective: perspective,
tex: Sampler(&assets.textures.block_atlas, sampler) tex: Sampler(&assets.textures.block_atlas, sampler)
}, },
&Default::default() &draw_parameters
).unwrap(); ).unwrap();
} }
} }
} }
pub fn update_loaded_chunks(&mut self, around_position: Vec2, options: &GameOptions, display: &Display) { pub fn update_loaded_chunks(&mut self, around_position: Vec2, options: &GameOptions, display: &Display) {
let render_dist = options.render_distance as i32 + 1; let render_dist = options.render_distance as i32 + 1;
let inside_chunk = (around_position / CHUNK_SIZE as f32).as_ivec2(); let inside_chunk = (around_position / CHUNK_SIZE as f32).as_ivec2();
@ -98,43 +115,49 @@ impl World {
{ {
//we only need mutable reference here: //we only need mutable reference here:
let chunk = self.chunks.get_mut(&position).unwrap(); let chunk = self.chunks.get_mut(&position).unwrap();
if x == 0 || z == 0 || x == render_dist || z == render_dist { if x == -render_dist || z == -render_dist || x == render_dist || z == render_dist {
chunk.desired = ChunkState::Loaded; chunk.desired = ChunkState::Loaded;
} else { } else {
chunk.desired = ChunkState::Rendered; chunk.desired = ChunkState::Rendered;
} }
} }
//borrow chunk immutably
let chunk = self.chunks.get(&position).unwrap(); let chunk = self.chunks.get(&position).unwrap();
if matches!(chunk.state, ChunkState::Nothing) && matches!(chunk.desired, ChunkState::Loaded | ChunkState::Rendered) { if self.thread.task_amount() < MAX_TASKS {
self.thread.queue_load(position); if matches!(chunk.state, ChunkState::Nothing) && matches!(chunk.desired, ChunkState::Loaded | ChunkState::Rendered) {
} else if matches!(chunk.state, ChunkState::Loaded) && matches!(chunk.desired, ChunkState::Rendered) { self.thread.queue_load(position);
fn all_some<'a>(x: [Option<&'a Chunk>; 4]) -> Option<[&'a Chunk; 4]> { self.chunks.get_mut(&position).unwrap().state = ChunkState::Loading;
Some([x[0]?, x[1]?, x[2]?, x[3]?]) } else if matches!(chunk.state, ChunkState::Loaded) && matches!(chunk.desired, ChunkState::Rendered) {
} let mut state_changed = false;
if let Some(neighbors) = all_some(self.chunk_neighbors(chunk.position)) { fn all_some<'a>(x: [Option<&'a Chunk>; 4]) -> Option<[&'a Chunk; 4]> {
if { Some([x[0]?, x[1]?, x[2]?, x[3]?])
neighbors[0].block_data.is_some() && }
neighbors[1].block_data.is_some() && if let Some(neighbors) = all_some(self.chunk_neighbors(chunk.position)) {
neighbors[2].block_data.is_some() && if {
neighbors[3].block_data.is_some() neighbors[0].block_data.is_some() &&
} { neighbors[1].block_data.is_some() &&
self.thread.queue_mesh( neighbors[2].block_data.is_some() &&
position, neighbors[3].block_data.is_some()
chunk.block_data.clone().unwrap(), } {
[ self.thread.queue_mesh(
neighbors[0].block_data.clone().unwrap(), position,
neighbors[1].block_data.clone().unwrap(), chunk.block_data.clone().unwrap(),
neighbors[2].block_data.clone().unwrap(), [
neighbors[3].block_data.clone().unwrap(), neighbors[0].block_data.clone().unwrap(),
] neighbors[1].block_data.clone().unwrap(),
); neighbors[2].block_data.clone().unwrap(),
neighbors[3].block_data.clone().unwrap(),
]
);
state_changed = true;
}
}
if state_changed {
self.chunks.get_mut(&position).unwrap().state = ChunkState::Rendering;
} }
} }
} }
} }
} }
//Unloads and state downgrades //Unloads and state downgrades
self.chunks.retain(|_, chunk| { self.chunks.retain(|_, chunk| {
match chunk.desired { match chunk.desired {

View file

@ -19,10 +19,16 @@ pub enum ChunkState {
pub type ChunkData = Box<[[[Block; CHUNK_SIZE]; CHUNK_HEIGHT]; CHUNK_SIZE]>; pub type ChunkData = Box<[[[Block; CHUNK_SIZE]; CHUNK_HEIGHT]; CHUNK_SIZE]>;
pub struct ChunkMesh {
pub is_dirty: bool,
pub vertex_buffer: VertexBuffer<ChunkVertex>,
pub index_buffer: IndexBuffer<u32>,
}
pub struct Chunk { pub struct Chunk {
pub position: IVec2, pub position: IVec2,
pub block_data: Option<ChunkData>, pub block_data: Option<ChunkData>,
pub mesh: Option<(bool, VertexBuffer<ChunkVertex>, IndexBuffer<u16>)>, pub mesh: Option<ChunkMesh>,
pub state: ChunkState, pub state: ChunkState,
pub desired: ChunkState, pub desired: ChunkState,
} }

View file

@ -3,7 +3,7 @@ use glium::{Display, VertexBuffer, IndexBuffer, index::PrimitiveType};
use std::{mem, thread::{self, JoinHandle}}; use std::{mem, thread::{self, JoinHandle}};
use hashbrown::HashMap; use hashbrown::HashMap;
use super::chunk::{Chunk, ChunkData, ChunkState}; use super::chunk::{Chunk, ChunkData, ChunkState};
use crate::game::shaders::chunk::Vertex as ChunkVertex; use crate::game::{shaders::chunk::Vertex as ChunkVertex, world::chunk::ChunkMesh};
mod world_gen; mod world_gen;
mod mesh_gen; mod mesh_gen;
@ -14,7 +14,7 @@ pub struct WorldThreading {
//Options are needed here to take ownership, //Options are needed here to take ownership,
//None values should never appear here! //None values should never appear here!
pub load_tasks: HashMap<IVec2, Option<JoinHandle<ChunkData>>>, pub load_tasks: HashMap<IVec2, Option<JoinHandle<ChunkData>>>,
pub mesh_tasks: HashMap<IVec2, Option<JoinHandle<(Vec<ChunkVertex>, Vec<u16>)>>>, pub mesh_tasks: HashMap<IVec2, Option<JoinHandle<(Vec<ChunkVertex>, Vec<u32>)>>>,
} }
impl WorldThreading { impl WorldThreading {
pub fn new() -> Self { pub fn new() -> Self {
@ -84,11 +84,11 @@ impl WorldThreading {
let handle = mem::take(handle).unwrap(); let handle = mem::take(handle).unwrap();
let (shape, index) = handle.join().unwrap(); let (shape, index) = handle.join().unwrap();
let chunk = chunks.get_mut(position).unwrap(); let chunk = chunks.get_mut(position).unwrap();
chunk.mesh = Some(( chunk.mesh = Some(ChunkMesh {
true, is_dirty: false,
VertexBuffer::immutable(display, &shape).expect("Failed to build VertexBuffer"), vertex_buffer: VertexBuffer::new(display, &shape).expect("Failed to build VertexBuffer"),
IndexBuffer::immutable(display, PrimitiveType::TrianglesList, &index).expect("Failed to build IndexBuffer") index_buffer: IndexBuffer::new(display, PrimitiveType::TrianglesList, &index).expect("Failed to build IndexBuffer")
)); });
chunk.state = ChunkState::Rendered; chunk.state = ChunkState::Rendered;
false false
}); });

View file

@ -1,15 +1,126 @@
use glam::IVec2; use glam::{IVec2, IVec3, Vec2, Vec3A, vec3a, vec2, ivec3};
use crate::game::world::chunk::ChunkData; use strum::{EnumIter, IntoEnumIterator};
use crate::game::shaders::chunk::Vertex as ChunkVertex; use crate::game::{
world::{
POSITIVE_X_NEIGHBOR,
NEGATIVE_X_NEIGHBOR,
POSITIVE_Z_NEIGHBOR,
NEGATIVE_Z_NEIGHBOR,
chunk::{ChunkData, CHUNK_SIZE, CHUNK_HEIGHT}
},
shaders::chunk::Vertex,
blocks::Block
};
pub fn generate_mesh(position: IVec2, chunk_data: ChunkData, neighbors: [ChunkData; 4]) -> (Vec<ChunkVertex>, Vec<u16>) { #[repr(usize)]
let mut vertex = Vec::new(); #[derive(Clone, Copy, Debug, EnumIter)]
let mut index = Vec::new(); pub enum CubeFace {
vertex.push(ChunkVertex { position: [-0.5, -0.5, 0.], uv: [0., 0.], normal: [0., 1., 0.] }); Top = 0,
vertex.push(ChunkVertex { position: [ 0.0, 0.5, 0.], uv: [0., 1.], normal: [0., 1., 0.] }); Front = 1,
vertex.push(ChunkVertex { position: [ 0.5, -0.5, 0.], uv: [1., 1.], normal: [0., 1., 0.] }); Left = 2,
index.push(0); Right = 3,
index.push(1); Back = 4,
index.push(2); Bottom = 5,
(vertex, index) }
const CUBE_FACE_VERTICES: [[Vec3A; 4]; 6] = [
[vec3a(0., 1., 0.), vec3a(0., 1., 1.), vec3a(1., 1., 0.), vec3a(1., 1., 1.)],
[vec3a(0., 0., 0.), vec3a(0., 1., 0.), vec3a(1., 0., 0.), vec3a(1., 1., 0.)],
[vec3a(0., 0., 1.), vec3a(0., 1., 1.), vec3a(0., 0., 0.), vec3a(0., 1., 0.)],
[vec3a(1., 0., 0.), vec3a(1., 1., 0.), vec3a(1., 0., 1.), vec3a(1., 1., 1.)],
[vec3a(1., 0., 1.), vec3a(1., 1., 1.), vec3a(0., 0., 1.), vec3a(0., 1., 1.)],
[vec3a(0., 0., 1.), vec3a(0., 0., 0.), vec3a(1., 0., 1.), vec3a(1., 0., 0.)]
];
pub const CUBE_FACE_NORMALS: [[f32; 3]; 6] = [
[0., 1., 0.],
[0., 0., -1.],
[-1., 0., 0.],
[1., 0., 0.],
[0., 0., 1.],
[0., -1., 0.]
];
pub const CUBE_FACE_INDICES: [u32; 6] = [0, 1, 2, 2, 1, 3];
#[derive(Default)]
struct MeshBuilder {
vertex_buffer: Vec<Vertex>,
index_buffer: Vec<u32>,
idx_counter: u32,
}
impl MeshBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn add_face(&mut self, face: CubeFace, coord: IVec3, uvs: [Vec2; 4]) {
let coord = coord.as_vec3a();
let face_index = face as usize;
//Push vertexes
let norm = CUBE_FACE_NORMALS[face_index];
let vert = CUBE_FACE_VERTICES[face_index];
self.vertex_buffer.reserve(4);
for i in 0..4 {
self.vertex_buffer.push(Vertex {
position: (coord + vert[i]).to_array(),
normal: norm,
uv: uvs[i].to_array()
});
}
//Push indices
self.index_buffer.extend_from_slice(&CUBE_FACE_INDICES.map(|x| x + self.idx_counter));
self.idx_counter += 4;
}
pub fn finish(self) -> (Vec<Vertex>, Vec<u32>) {
(self.vertex_buffer, self.index_buffer)
}
}
pub fn generate_mesh(position: IVec2, chunk_data: ChunkData, neighbors: [ChunkData; 4]) -> (Vec<Vertex>, Vec<u32>) {
let get_block = |pos: IVec3| -> Block {
if pos.x < 0 {
neighbors[NEGATIVE_X_NEIGHBOR][(CHUNK_SIZE as i32 + pos.x) as usize][pos.y as usize][pos.z as usize]
} else if pos.x >= CHUNK_SIZE as i32 {
neighbors[POSITIVE_X_NEIGHBOR][pos.x as usize - CHUNK_SIZE as usize][pos.y as usize][pos.z as usize]
} else if pos.z < 0 {
neighbors[NEGATIVE_Z_NEIGHBOR][pos.x as usize][pos.y as usize][(CHUNK_SIZE as i32 + pos.z) as usize]
} else if pos.z >= CHUNK_SIZE as i32 {
neighbors[POSITIVE_Z_NEIGHBOR][pos.x as usize][pos.y as usize][pos.z as usize - CHUNK_SIZE as usize]
} else {
chunk_data[pos.x as usize][pos.y as usize][pos.z as usize]
}
};
let mut builer = MeshBuilder::new();
for x in 0..CHUNK_SIZE {
for y in 0..CHUNK_HEIGHT {
for z in 0..CHUNK_SIZE {
let coord = ivec3(x as i32, y as i32, z as i32);
if get_block(coord).descriptor().render.is_none() {
continue
}
for face in CubeFace::iter() {
let facing = Vec3A::from_array(CUBE_FACE_NORMALS[face as usize]).as_ivec3();
let facing_coord = coord + facing;
let show = {
(facing_coord.y < 0) ||
(facing_coord.y >= CHUNK_HEIGHT as i32) ||
get_block(facing_coord).descriptor().render.is_none()
};
if show {
builer.add_face(face, coord, [
vec2(0., 0.),
vec2(0., 1.),
vec2(1., 0.),
vec2(1., 1.),
]);
}
}
}
}
}
builer.finish()
} }