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

View file

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

View file

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

View file

@ -19,10 +19,16 @@ pub enum ChunkState {
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 position: IVec2,
pub block_data: Option<ChunkData>,
pub mesh: Option<(bool, VertexBuffer<ChunkVertex>, IndexBuffer<u16>)>,
pub mesh: Option<ChunkMesh>,
pub state: ChunkState,
pub desired: ChunkState,
}

View file

@ -3,7 +3,7 @@ use glium::{Display, VertexBuffer, IndexBuffer, index::PrimitiveType};
use std::{mem, thread::{self, JoinHandle}};
use hashbrown::HashMap;
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 mesh_gen;
@ -14,7 +14,7 @@ pub struct WorldThreading {
//Options are needed here to take ownership,
//None values should never appear here!
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 {
pub fn new() -> Self {
@ -84,11 +84,11 @@ impl WorldThreading {
let handle = mem::take(handle).unwrap();
let (shape, index) = handle.join().unwrap();
let chunk = chunks.get_mut(position).unwrap();
chunk.mesh = Some((
true,
VertexBuffer::immutable(display, &shape).expect("Failed to build VertexBuffer"),
IndexBuffer::immutable(display, PrimitiveType::TrianglesList, &index).expect("Failed to build IndexBuffer")
));
chunk.mesh = Some(ChunkMesh {
is_dirty: false,
vertex_buffer: VertexBuffer::new(display, &shape).expect("Failed to build VertexBuffer"),
index_buffer: IndexBuffer::new(display, PrimitiveType::TrianglesList, &index).expect("Failed to build IndexBuffer")
});
chunk.state = ChunkState::Rendered;
false
});

View file

@ -1,15 +1,126 @@
use glam::IVec2;
use crate::game::world::chunk::ChunkData;
use crate::game::shaders::chunk::Vertex as ChunkVertex;
use glam::{IVec2, IVec3, Vec2, Vec3A, vec3a, vec2, ivec3};
use strum::{EnumIter, IntoEnumIterator};
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>) {
let mut vertex = Vec::new();
let mut index = Vec::new();
vertex.push(ChunkVertex { position: [-0.5, -0.5, 0.], uv: [0., 0.], normal: [0., 1., 0.] });
vertex.push(ChunkVertex { position: [ 0.0, 0.5, 0.], uv: [0., 1.], normal: [0., 1., 0.] });
vertex.push(ChunkVertex { position: [ 0.5, -0.5, 0.], uv: [1., 1.], normal: [0., 1., 0.] });
index.push(0);
index.push(1);
index.push(2);
(vertex, index)
#[repr(usize)]
#[derive(Clone, Copy, Debug, EnumIter)]
pub enum CubeFace {
Top = 0,
Front = 1,
Left = 2,
Right = 3,
Back = 4,
Bottom = 5,
}
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()
}