initial update and render with concurrent DS

pull/1/head
EmaMaker 2023-04-29 14:54:43 +02:00
parent 1d3132cf3c
commit 78e3bc11e6
10 changed files with 168 additions and 337 deletions

View File

@ -6,7 +6,7 @@
#include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp> #include <glm/gtc/type_ptr.hpp>
#include <iostream> #include <atomic>
class Camera class Camera
{ {
@ -38,6 +38,9 @@ public:
if (glfwGetKey(window, GLFW_KEY_Z) == GLFW_PRESS) if (glfwGetKey(window, GLFW_KEY_Z) == GLFW_PRESS)
this->cameraPos -= cameraSpeed * cameraUp; this->cameraPos -= cameraSpeed * cameraUp;
posX = cameraPos.x;
posY = cameraPos.y;
posZ = cameraPos.z;
direction.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch)); direction.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
direction.y = sin(glm::radians(pitch)); direction.y = sin(glm::radians(pitch));
@ -78,6 +81,10 @@ public:
glm::mat4 getView() { return view; } glm::mat4 getView() { return view; }
glm::mat4 getProjection() { return projection; } glm::mat4 getProjection() { return projection; }
float getAtomicPosX() { return posX; }
float getAtomicPosY() { return posY; }
float getAtomicPosZ() { return posZ; }
// Plane extraction as per Gribb&Hartmann // Plane extraction as per Gribb&Hartmann
// 6 planes, each with 4 components (a,b,c,d) // 6 planes, each with 4 components (a,b,c,d)
void getFrustumPlanes(glm::vec4 planes[6], bool normalize) void getFrustumPlanes(glm::vec4 planes[6], bool normalize)
@ -114,6 +121,8 @@ private:
float lastX = 400, lastY = 300; float lastX = 400, lastY = 300;
float yaw, pitch; float yaw, pitch;
std::atomic<float> posX, posY, posZ;
}; };
#endif #endif

View File

@ -27,6 +27,8 @@ namespace Chunk
constexpr uint8_t CHUNK_STATE_MESHED = 2; constexpr uint8_t CHUNK_STATE_MESHED = 2;
constexpr uint8_t CHUNK_STATE_MESH_LOADED = 4; constexpr uint8_t CHUNK_STATE_MESH_LOADED = 4;
constexpr uint8_t CHUNK_STATE_LOADED = 8; constexpr uint8_t CHUNK_STATE_LOADED = 8;
constexpr uint8_t CHUNK_STATE_OUTOFVISION = 16;
constexpr uint8_t CHUNK_STATE_UNLOADED = 32;
constexpr uint8_t CHUNK_STATE_EMPTY = 64; constexpr uint8_t CHUNK_STATE_EMPTY = 64;
int coord3DTo1D(int x, int y, int z); int coord3DTo1D(int x, int y, int z);
@ -39,6 +41,9 @@ namespace Chunk
~Chunk(); ~Chunk();
public: public:
void createBuffers();
void deleteBuffers();
glm::vec3 getPosition() { return this->position; } glm::vec3 getPosition() { return this->position; }
uint8_t getTotalState() { return this->state; } uint8_t getTotalState() { return this->state; }
bool getState(uint8_t n) { return (this->state & n) == n; } bool getState(uint8_t n) { return (this->state & n) == n; }
@ -52,8 +57,7 @@ namespace Chunk
public: public:
GLuint VAO{0}, VBO{0}, EBO{0}, colorBuffer{0}, vIndex{0}; GLuint VAO{0}, VBO{0}, EBO{0}, colorBuffer{0}, vIndex{0};
std::atomic<float> unload_timer{0};
std::mutex mutex_state;
std::vector<GLfloat> vertices; std::vector<GLfloat> vertices;
std::vector<GLfloat> colors; std::vector<GLfloat> colors;

View File

@ -1,33 +1,24 @@
#ifndef CHUNKMANAGER_H #ifndef CHUNKMANAGER_H
#define CHUNKMANAGER_H #define CHUNKMANAGER_H
// Second to be passed outside of render distance for a chunk to be destroyed // Seconds to be passed outside of render distance for a chunk to be destroyed
#define UNLOAD_TIMEOUT 10 #define UNLOAD_TIMEOUT 10
#include <unordered_map>
#include <thread> #include <thread>
#include "chunk.hpp" #include "chunk.hpp"
#include "globals.hpp" #include "globals.hpp"
namespace chunkmanager namespace chunkmanager
{ {
std::thread initGenThread(); std::thread init();
std::thread initMeshThread();
void stopGenThread();
void stopMeshThread();
void mesh();
void generate();
void init();
void blockpick(bool place); void blockpick(bool place);
uint32_t calculateIndex(uint16_t i, uint16_t j, uint16_t k); uint32_t calculateIndex(uint16_t i, uint16_t j, uint16_t k);
void stop();
void destroy(); void destroy();
std::unordered_map<std::uint32_t, Chunk::Chunk*>& getChunks();
std::array<std::array<int, 3>, chunks_volume>& getChunksIndices(); std::array<std::array<int, 3>, chunks_volume>& getChunksIndices();
void update(float deltaTime); void update();
void updateChunk(uint32_t, uint16_t, uint16_t, uint16_t);
} }
#endif #endif

View File

@ -1,6 +1,9 @@
#ifndef RENDERER_H #ifndef RENDERER_H
#define RENDERER_H #define RENDERER_H
#include <oneapi/tbb/concurrent_unordered_set.h>
#include "chunk.hpp"
#include "shader.hpp" #include "shader.hpp"
namespace renderer{ namespace renderer{
@ -8,6 +11,7 @@ namespace renderer{
void render(); void render();
void destroy(); void destroy();
Shader* getRenderShader(); Shader* getRenderShader();
oneapi::tbb::concurrent_unordered_set<Chunk::Chunk*>& getChunksToRender();
}; };

View File

@ -19,27 +19,30 @@ namespace Chunk
{ {
this->position = pos; this->position = pos;
this->setState(CHUNK_STATE_EMPTY, true); this->setState(CHUNK_STATE_EMPTY, true);
}
Chunk ::~Chunk()
{
vertices.clear();
indices.clear();
colors.clear();
this->deleteBuffers();
}
void Chunk::createBuffers(){
glGenVertexArrays(1, &(this->VAO)); glGenVertexArrays(1, &(this->VAO));
glGenBuffers(1, &(this->colorBuffer)); glGenBuffers(1, &(this->colorBuffer));
glGenBuffers(1, &(this->VBO)); glGenBuffers(1, &(this->VBO));
glGenBuffers(1, &(this->EBO)); glGenBuffers(1, &(this->EBO));
mutex_state.unlock();
} }
Chunk ::~Chunk() void Chunk::deleteBuffers(){
{
glDeleteBuffers(1, &(this->colorBuffer)); glDeleteBuffers(1, &(this->colorBuffer));
glDeleteBuffers(1, &(this->VBO)); glDeleteBuffers(1, &(this->VBO));
glDeleteBuffers(1, &(this->EBO)); glDeleteBuffers(1, &(this->EBO));
glDeleteVertexArrays(1, &(this->VAO)); glDeleteVertexArrays(1, &(this->VAO));
vertices.clear();
indices.clear();
colors.clear();
mutex_state.unlock();
} }
Block Chunk::getBlock(int x, int y, int z) Block Chunk::getBlock(int x, int y, int z)

View File

@ -84,6 +84,7 @@ void generateNoise(Chunk::Chunk *chunk)
} }
chunk->setBlocks(block_prev_start, CHUNK_VOLUME, block_prev); chunk->setBlocks(block_prev_start, CHUNK_VOLUME, block_prev);
chunk->setState(Chunk::CHUNK_STATE_GENERATED, true);
} }
void generateNoise3D(Chunk::Chunk *chunk) { void generateNoise3D(Chunk::Chunk *chunk) {

View File

@ -1,105 +1,36 @@
#include "chunkmanager.hpp"
#include <atomic>
#include <math.h>
#include <thread>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp> #include <oneapi/tbb/concurrent_hash_map.h>
#include "chunk.hpp" #include "chunk.hpp"
#include "chunkgenerator.hpp" #include "chunkgenerator.hpp"
#include "chunkmanager.hpp"
#include "chunkmesher.hpp" #include "chunkmesher.hpp"
#include "globals.hpp" #include "globals.hpp"
#include "renderer.hpp"
#include <atomic>
#include <iostream>
#include <math.h>
#include <mutex>
#include <set>
#include <string>
#include <unordered_map>
#include <thread>
namespace chunkmanager namespace chunkmanager
{ {
std::unordered_map<std::uint32_t, Chunk::Chunk *> chunks; typedef oneapi::tbb::concurrent_hash_map<uint32_t, Chunk::Chunk*> ChunkTable;
ChunkTable chunks;
//std::unordered_map<std::uint32_t, Chunk::Chunk *> chunks;
std::array<std::array<int, 3>, chunks_volume> chunks_indices; std::array<std::array<int, 3>, chunks_volume> chunks_indices;
// thread management std::atomic_bool should_run;
std::mutex mutex_queue_generate; std::thread init(){
std::mutex mutex_queue_mesh;
std::set<Chunk::Chunk *> to_generate;
std::set<Chunk::Chunk *> to_mesh;
std::atomic_bool generate_should_run;
std::atomic_bool mesh_should_run;
// update variables
uint8_t f = 0;
int rr{RENDER_DISTANCE * RENDER_DISTANCE};
glm::vec3 cameraPos;
int chunkX, chunkY, chunkZ;
// disposal
std::unordered_map<uint32_t, float> to_delete;
std::set<uint32_t> to_delete_delete;
void mesh()
{
while (mesh_should_run)
if (mutex_queue_mesh.try_lock())
{
for (const auto &c : to_mesh)
{
if (c->mutex_state.try_lock())
{
chunkmesher::mesh(c);
c->setState(Chunk::CHUNK_STATE_MESHED, true);
c->mutex_state.unlock();
}
}
to_mesh.clear();
mutex_queue_mesh.unlock();
}
}
void generate()
{
while (generate_should_run)
if (mutex_queue_generate.try_lock())
{
for (const auto &c : to_generate)
{
if (c->mutex_state.try_lock())
{
generateChunk(c);
c->setState(Chunk::CHUNK_STATE_GENERATED, true);
c->mutex_state.unlock();
}
}
to_generate.clear();
mutex_queue_generate.unlock();
}
}
std::thread initMeshThread()
{
mesh_should_run = true;
std::thread mesh_thread(mesh);
return mesh_thread;
}
std::thread initGenThread()
{
generate_should_run = true;
std::thread gen_thread(generate);
return gen_thread;
}
void init(){
int index{0}; int index{0};
int rr{RENDER_DISTANCE * RENDER_DISTANCE};
int xp{0}, x{0}; int xp{0}, x{0};
bool b = true; bool b = true;
// Iterate over all chunks, in concentric spheres starting fron the player and going // Iterate over all chunks, in concentric spheres starting fron the player and going outwards. Alternate left and right
// outwards. Alternate left and right
// Eq. of the sphere (x - a)² + (y - b)² + (z - c)² = r² // Eq. of the sphere (x - a)² + (y - b)² + (z - c)² = r²
while (xp <= RENDER_DISTANCE) while (xp <= RENDER_DISTANCE)
{ {
@ -131,168 +62,36 @@ namespace chunkmanager
} }
else b = false; else b = false;
} }
should_run = true;
std::thread update_thread (update);
return update_thread;
} }
void update(float deltaTime) std::vector<uint32_t> chunks_todelete;
{ void update(){
// Try to lock resources while(should_run) {
f = 0; //cameraPos = theCamera.getPos();
f |= mutex_queue_generate.try_lock(); int chunkX=static_cast<int>(theCamera.getAtomicPosX() / CHUNK_SIZE);
f |= mutex_queue_mesh.try_lock() << 1; int chunkY=static_cast<int>(theCamera.getAtomicPosY() / CHUNK_SIZE);
int chunkZ=static_cast<int>(theCamera.getAtomicPosZ() / CHUNK_SIZE);
cameraPos = theCamera.getPos(); // Update other chunks
chunkX=static_cast<int>(cameraPos.x) / CHUNK_SIZE; for(int i = 0; i < chunks_volume; i++) {
chunkY=static_cast<int>(cameraPos.y) / CHUNK_SIZE; const uint16_t x = chunks_indices[i][0] + chunkX;
chunkZ=static_cast<int>(cameraPos.z) / CHUNK_SIZE; const uint16_t y = chunks_indices[i][1] + chunkY;
const uint16_t z = chunks_indices[i][2] + chunkZ;
const uint32_t index = calculateIndex(x, y, z);
// Use time in float to be consistent with glfw ChunkTable::accessor a;
float currentTime = glfwGetTime(); if(!chunks.find(a, index)) chunks.emplace(a, std::make_pair(index, new Chunk::Chunk(glm::vec3(x,y,z))));
// Check for far chunks that need to be cleaned up from memory if(! (a->second->getState(Chunk::CHUNK_STATE_GENERATED))) generateChunk(a->second);
int nUnloaded{0}; if(! (a->second->getState(Chunk::CHUNK_STATE_MESHED))) chunkmesher::mesh(a->second);
for(const auto& n : chunks){
Chunk::Chunk* c = n.second;
int x{(int)(c->getPosition().x)};
int y{(int)(c->getPosition().y)};
int z{(int)(c->getPosition().z)};
if( (chunkX-x)*(chunkX-x) + (chunkY-y)*(chunkY-y) + (chunkZ-z)*(chunkZ-z) >=
(int)(RENDER_DISTANCE*1.5)*(int)(RENDER_DISTANCE*1.5))
if(to_delete.find(n.first) == to_delete.end())
to_delete.insert(std::make_pair(n.first, currentTime));
}
for(const auto& n :to_delete){
if( currentTime>=n.second + UNLOAD_TIMEOUT) {
delete chunks.at(n.first);
chunks.erase(n.first);
nUnloaded++;
// Delete afterwards to avoid exception due to invalid iterators renderer::getChunksToRender().insert(a->second);
to_delete_delete.insert(n.first);
}
}
for(uint32_t i : to_delete_delete) to_delete.erase(i);
to_delete_delete.clear();
if(nUnloaded) std::cout << "Unloaded " << nUnloaded << " chunks\n";
for(int i = 0; i < chunks_volume; i++) a.release();
updateChunk(calculateIndex(chunks_indices[i][0] + chunkX,
chunks_indices[i][1] + chunkY,
chunks_indices[i][2] + chunkZ),
chunks_indices[i][0] + chunkX,
chunks_indices[i][1] + chunkY,
chunks_indices[i][2] + chunkZ);
// Unlock mutexes if previously locked. Unlocking a mutex not locked by the current thread
// or already locked is undefined behaviour, so checking has to be done
if ((f & 1))
mutex_queue_generate.unlock();
if ((f & 2))
mutex_queue_mesh.unlock();
}
// Generation and meshing happen in two separate threads from the main one
// Chunk states are used to decide which actions need to be done on the chunk and sets+mutexes
// to pass the chunks to be operated on between the threads.
// Uploading data to GPU still needs to be done in the main thread, or another OpenGL context
// needs to be opened, which further complicates stuff.
void updateChunk(uint32_t index, uint16_t i, uint16_t j, uint16_t k)
{
if (chunks.find(index) == chunks.end())
{
Chunk::Chunk *c = new Chunk::Chunk(glm::vec3(i, j, k));
chunks.insert(std::make_pair(index, c));
}
else
{
Chunk::Chunk *c = chunks.at(index);
if (!(c->mutex_state.try_lock()))
return;
if (!c->getState(Chunk::CHUNK_STATE_GENERATED))
{
if (f & 1)
to_generate.insert(c);
}
else
{
if (!c->getState(Chunk::CHUNK_STATE_MESHED))
{
if (f & 2)
to_mesh.insert(c);
}
else
{
if (!c->getState(Chunk::CHUNK_STATE_MESH_LOADED)) chunkmesher::sendtogpu(c);
}
}
c->mutex_state.unlock();
}
}
void blockpick(bool place){
// cast a ray from the camera in the direction pointed by the camera itself
glm::vec3 pos = cameraPos;
for(float t = 0.0; t <= 10.0; t += 0.5){
// traverse the ray a block at the time
pos = theCamera.getPos() + t * theCamera.getFront();
// get which chunk and block the ray is at
int px = ((int)(pos.x))/CHUNK_SIZE;
int py = ((int)(pos.y))/CHUNK_SIZE;
int pz = ((int)(pos.z))/CHUNK_SIZE;
int bx = pos.x - px*CHUNK_SIZE;
int by = pos.y - py*CHUNK_SIZE;
int bz = pos.z - pz*CHUNK_SIZE;
// exit early if the position is invalid or the chunk does not exist
if(px < 0 || py < 0 || pz < 0) return;
if(chunks.find(calculateIndex(px, py, pz)) == chunks.end()) return;
Chunk::Chunk* c = chunks.at(calculateIndex(px, py, pz));
Block b = c->getBlock(bx, by, bz);
// if the block is non empty
if(b != Block::AIR){
// if placing a new block
if(place){
// Go half a block backwards on the ray, to check the block where the ray was
// coming from
// Doing this and not using normal adds the unexpected (and unwanted) ability to
// place blocks diagonally, without faces colliding with the block that has
// been clicked
pos -= theCamera.getFront()*0.5f;
int px1 = ((int)(pos.x))/CHUNK_SIZE;
int py1 = ((int)(pos.y))/CHUNK_SIZE;
int pz1 = ((int)(pos.z))/CHUNK_SIZE;
int bx1 = pos.x - px1*CHUNK_SIZE;
int by1 = pos.y - py1*CHUNK_SIZE;
int bz1 = pos.z - pz1*CHUNK_SIZE;
// exit early if the position is invalid or the chunk does not exist
if(px1 < 0 || py1 < 0 || pz1 < 0) return;
if(chunks.find(calculateIndex(px1, py1, pz1)) == chunks.end()) return;
Chunk::Chunk* c1 = chunks.at(calculateIndex(px1, py1, pz1));
// place the new block (only stone for now)
c1->setBlock( Block::STONE, bx1, by1, bz1);
// update the mesh of the chunk
chunkmesher::mesh(c1);
// mark the mesh of the chunk the be updated on the gpu
c1->setState(Chunk::CHUNK_STATE_MESH_LOADED, false);
}else{
// replace the current block with air to remove it
c->setBlock( Block::AIR, bx, by, bz);
// update the mesh of the chunk
chunkmesher::mesh(c);
// mark the mesh of the chunk the be updated on the gpu
c->setState(Chunk::CHUNK_STATE_MESH_LOADED, false);
}
break;
} }
} }
} }
@ -302,21 +101,13 @@ namespace chunkmanager
return i | (j << 10) | (k << 20); return i | (j << 10) | (k << 20);
} }
std::unordered_map<std::uint32_t, Chunk::Chunk*>& getChunks(){ return chunks; }
std::array<std::array<int, 3>, chunks_volume>& getChunksIndices(){ return chunks_indices; } std::array<std::array<int, 3>, chunks_volume>& getChunksIndices(){ return chunks_indices; }
void destroy() void stop() { should_run=false; }
{ void destroy(){
for (auto &n : chunks) /*for(const auto& n : chunks){
delete n.second; delete n.second;
} }*/
void stopGenThread(){
generate_should_run = false;
}
void stopMeshThread(){
mesh_should_run = false;
} }
}; };

View File

@ -35,12 +35,16 @@ void mesh(Chunk::Chunk* chunk)
chunk->vIndex = 0; chunk->vIndex = 0;
// Abort if chunk is empty // Abort if chunk is empty
if(chunk->getState(Chunk::CHUNK_STATE_EMPTY)) return; if(chunk->getState(Chunk::CHUNK_STATE_EMPTY)){
chunk->setState(Chunk::CHUNK_STATE_MESHED, true);
return;
}
// convert tree to array since it is easier to work with it // convert tree to array since it is easier to work with it
int length{0}; int length{0};
std::unique_ptr<Block[]> blocks = chunk->getBlocksArray(&length); std::unique_ptr<Block[]> blocks = chunk->getBlocksArray(&length);
if(length == 0) { if(length == 0) {
chunk->setState(Chunk::CHUNK_STATE_MESHED, true);
return; return;
} }
@ -180,13 +184,14 @@ void mesh(Chunk::Chunk* chunk)
} }
} }
} }
chunk->setState(Chunk::CHUNK_STATE_MESHED, true);
} }
void sendtogpu(Chunk::Chunk* chunk) void sendtogpu(Chunk::Chunk* chunk)
{ {
if (chunk->vIndex > 0) if (chunk->vIndex > 0)
{ {
// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
glBindVertexArray(chunk->VAO); glBindVertexArray(chunk->VAO);

View File

@ -63,9 +63,7 @@ int main()
SpaceFilling::initLUT(); SpaceFilling::initLUT();
renderer::init(); renderer::init();
chunkmanager::init(); std::thread chunkmanager_thread = chunkmanager::init();
std::thread genThread = chunkmanager::initGenThread();
std::thread meshThread = chunkmanager::initMeshThread();
while (!glfwWindowShouldClose(window)) while (!glfwWindowShouldClose(window))
{ {
@ -94,9 +92,6 @@ int main()
// Reset blockping timeout if 200ms have passed // Reset blockping timeout if 200ms have passed
if(glfwGetTime() - lastBlockPick > 0.1) blockpick = false; if(glfwGetTime() - lastBlockPick > 0.1) blockpick = false;
// ChunkManager
chunkmanager::update(deltaTime);
// Render pass // Render pass
renderer::render(); renderer::render();
@ -106,10 +101,8 @@ int main()
} }
// Stop threads and wait for them to finish // Stop threads and wait for them to finish
chunkmanager::stopGenThread(); chunkmanager::stop();
chunkmanager::stopMeshThread(); chunkmanager_thread.join();
genThread.join();
meshThread.join();
// Cleanup allocated memory // Cleanup allocated memory
chunkmanager::destroy(); chunkmanager::destroy();
@ -136,13 +129,13 @@ void processInput(GLFWwindow *window)
glfwSetWindowShouldClose(window, true); glfwSetWindowShouldClose(window, true);
if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2) == GLFW_PRESS && !blockpick){ if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2) == GLFW_PRESS && !blockpick){
chunkmanager::blockpick(false); //chunkmanager::blockpick(false);
blockpick=true; blockpick=true;
lastBlockPick=glfwGetTime(); lastBlockPick=glfwGetTime();
} }
if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS && !blockpick){ if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS && !blockpick){
chunkmanager::blockpick(true); //chunkmanager::blockpick(true);
blockpick=true; blockpick=true;
lastBlockPick=glfwGetTime(); lastBlockPick=glfwGetTime();
} }

View File

@ -3,14 +3,18 @@
#include "chunkmanager.hpp" #include "chunkmanager.hpp"
#include "chunkmesher.hpp" #include "chunkmesher.hpp"
#include "globals.hpp" #include "globals.hpp"
#include "stb_image.h" #include "stb_image.h"
namespace renderer{ namespace renderer{
oneapi::tbb::concurrent_unordered_set<Chunk::Chunk*> chunks_torender;
oneapi::tbb::concurrent_unordered_set<Chunk::Chunk*> render_todelete;
Shader* theShader; Shader* theShader;
GLuint chunkTexture; GLuint chunkTexture;
Shader* getRenderShader() { return theShader; } Shader* getRenderShader() { return theShader; }
oneapi::tbb::concurrent_unordered_set<Chunk::Chunk*>& getChunksToRender(){ return chunks_torender; }
void init(){ void init(){
// Create Shader // Create Shader
@ -40,63 +44,89 @@ namespace renderer{
int total{0}, toGpu{0}; int total{0}, toGpu{0};
glm::vec4 frustumPlanes[6]; glm::vec4 frustumPlanes[6];
theCamera.getFrustumPlanes(frustumPlanes, true); theCamera.getFrustumPlanes(frustumPlanes, true);
glm::vec3 cameraPos = theCamera.getPos(); glm::vec3 cameraPos = theCamera.getPos();
int chunkX=static_cast<int>(cameraPos.x) / CHUNK_SIZE; glm::vec3 cameraChunkPos = cameraPos / static_cast<float>(CHUNK_SIZE);
int chunkY=static_cast<int>(cameraPos.y) / CHUNK_SIZE;
int chunkZ=static_cast<int>(cameraPos.z) / CHUNK_SIZE;
theShader->use(); theShader->use();
theShader->setVec3("viewPos", cameraPos); theShader->setVec3("viewPos", cameraPos);
for(int i = 0; i < chunks_volume; i++) {
Chunk::Chunk* c = chunkmanager::getChunks().at(chunkmanager::calculateIndex(chunkmanager::getChunksIndices()[i][0] +
chunkX, chunkmanager::getChunksIndices()[i][1] + chunkY, chunkmanager::getChunksIndices()[i][2] + chunkZ));
// Frustum Culling of chunk
total++;
glm::vec3 chunk = c->getPosition(); for(Chunk::Chunk* c : chunks_torender){
glm::vec4 chunkW = glm::vec4(chunk.x*static_cast<float>(CHUNK_SIZE), chunk.y*static_cast<float>(CHUNK_SIZE), chunk.z*static_cast<float>(CHUNK_SIZE),1.0); if(! (c->getState(Chunk::CHUNK_STATE_MESHED))) continue;
glm::mat4 model = glm::translate(glm::mat4(1.0), ((float)CHUNK_SIZE) * chunk);
// Check if all the corners of the chunk are outside any of the planes // If the mesh is ready send it to the gpu
// TODO (?) implement frustum culling as per (Inigo Quilez)[https://iquilezles.org/articles/frustumcorrect/], and check each if(! (c->getState(Chunk::CHUNK_STATE_MESH_LOADED))){
// plane against each corner of the chunk if(c->VAO == 0) c->createBuffers();
bool out=false; chunkmesher::sendtogpu(c);
int a{0}; c->setState(Chunk::CHUNK_STATE_MESH_LOADED, true);
for(int p = 0; p < 6; p++){
a = 0;
for(int i = 0; i < 8; i++) a += glm::dot(frustumPlanes[p], glm::vec4(chunkW.x + ((float)(i & 1))*CHUNK_SIZE, chunkW.y
+ ((float)((i & 2) >> 1))*CHUNK_SIZE, chunkW.z + ((float)((i & 4) >> 2))*CHUNK_SIZE, 1.0)) < 0.0;
if(a==8){
out=true;
break;
}
} }
if (!out) if(glm::distance(c->getPosition(), cameraChunkPos) < static_cast<float>(RENDER_DISTANCE)){
{ // if chunk can be seen
toGpu++; // reset out-of-vision and unload flags
c->setState(Chunk::CHUNK_STATE_OUTOFVISION, false);
c->setState(Chunk::CHUNK_STATE_UNLOADED, false);
if(c->getState(Chunk::CHUNK_STATE_MESH_LOADED) && c->vIndex > 0) // Perform frustum culling and eventually render
{ glm::vec3 chunk = c->getPosition();
// glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // wireframe mode glm::vec4 chunkW = glm::vec4(chunk.x*static_cast<float>(CHUNK_SIZE), chunk.y*static_cast<float>(CHUNK_SIZE), chunk.z*static_cast<float>(CHUNK_SIZE),1.0);
theShader->setMat4("model", model); glm::mat4 model = glm::translate(glm::mat4(1.0), ((float)CHUNK_SIZE) * chunk);
theShader->setMat4("view", theCamera.getView());
theShader->setMat4("projection", theCamera.getProjection());
glBindVertexArray(c->VAO); // Check if all the corners of the chunk are outside any of the planes
glDrawElements(GL_TRIANGLES, c->vIndex , GL_UNSIGNED_INT, 0); // TODO (?) implement frustum culling as per (Inigo Quilez)[https://iquilezles.org/articles/frustumcorrect/], and check each
glBindVertexArray(0); // plane against each corner of the chunk
bool out=false;
int a{0};
for(int p = 0; p < 6; p++){
a = 0;
for(int i = 0; i < 8; i++) a += glm::dot(frustumPlanes[p], glm::vec4(chunkW.x + ((float)(i & 1))*CHUNK_SIZE, chunkW.y
+ ((float)((i & 2) >> 1))*CHUNK_SIZE, chunkW.z + ((float)((i & 4) >> 2))*CHUNK_SIZE, 1.0)) < 0.0;
if(a==8){
out=true;
break;
}
} }
if (!out)
{
if(c->vIndex > 0)
{
// glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // wireframe mode
theShader->setMat4("model", model);
theShader->setMat4("view", theCamera.getView());
theShader->setMat4("projection", theCamera.getProjection());
glBindVertexArray(c->VAO);
glDrawElements(GL_TRIANGLES, c->vIndex , GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
}
}else{
// When the chunk is outside render distance
if(c->getState(Chunk::CHUNK_STATE_OUTOFVISION) && glfwGetTime() -
c->unload_timer > UNLOAD_TIMEOUT){
// If chunk was already out and enough time has passed
// Mark the chunk to be unloaded
c->setState(Chunk::CHUNK_STATE_UNLOADED, true);
// And delete it from the render set
render_todelete.insert(c);
} else{
// Mark has out of vision and annotate when it started
c->setState(Chunk::CHUNK_STATE_OUTOFVISION, true);
c->setState(Chunk::CHUNK_STATE_UNLOADED, false);
c->unload_timer = glfwGetTime();
}
} }
} }
//std::cout << "Chunks to mesh: " << to_mesh.size() << "\n"; for(Chunk::Chunk* i : render_todelete){
//std::cout << "Chunks to generate: " << to_generate.size() << "\n"; chunks_torender.unsafe_erase(i);
//std::cout << "Total chunks to draw: " << total << ". Sent to GPU: " << toGpu << "\n"; }
render_todelete.clear();
} }
void destroy(){ void destroy(){
delete theShader; delete theShader;
} }