Compare commits

..

1 Commits

Author SHA1 Message Date
EmaMaker 80d3297213 chunkmesher: keep a table of vertices to avoid duplicates 2023-04-08 17:17:47 +02:00
39 changed files with 740 additions and 3606 deletions

2
.gitignore vendored
View File

@ -9,5 +9,3 @@ gmon.out*
cscope* cscope*
test.cpp test.cpp
a.out a.out
*screenshot*
imgui.ini

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "lib/imgui"]
path = lib/imgui
url = https://github.com/ocornut/imgui/

View File

@ -2,9 +2,8 @@ cmake_minimum_required(VERSION 3.2)
project(cmake-project-template) project(cmake-project-template)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O3") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -O3")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g")
set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}) set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR})

View File

@ -6,9 +6,7 @@ enum class Block{
AIR, AIR,
STONE, STONE,
DIRT, DIRT,
GRASS, GRASS
WOOD,
LEAVES
}; };
#endif #endif

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 <atomic> #include <iostream>
class Camera class Camera
{ {
@ -17,11 +17,7 @@ public:
view = glm::mat4(1.0f); view = glm::mat4(1.0f);
// This matrix needs to be also updated in viewPortCallback whenever it is changed // This matrix needs to be also updated in viewPortCallback whenever it is changed
projection = glm::perspective(glm::radians(90.0f), 800.0f / 600.0f, 0.1f, 1200.0f); projection = glm::perspective(glm::radians(90.0f), 800.0f / 600.0f, 0.1f, 200.0f);
posX = cameraPos.x;
posY = cameraPos.y;
posZ = cameraPos.z;
} }
void update(GLFWwindow *window, float deltaTime) void update(GLFWwindow *window, float deltaTime)
@ -42,9 +38,6 @@ 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));
@ -56,7 +49,7 @@ public:
void viewPortCallBack(GLFWwindow *window, int width, int height) void viewPortCallBack(GLFWwindow *window, int width, int height)
{ {
projection = glm::perspective(glm::radians(80.0f), (float)width / (float)height, 0.1f, 1200.0f); projection = glm::perspective(glm::radians(80.0f), (float)width / (float)height, 0.1f, 350.0f);
} }
void mouseCallback(GLFWwindow *window, double xpos, double ypos) void mouseCallback(GLFWwindow *window, double xpos, double ypos)
@ -85,10 +78,6 @@ 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)
@ -116,7 +105,7 @@ public:
private: private:
glm::vec3 cameraPos = glm::vec3(512.0, 80.0f, 512.0f); glm::vec3 cameraPos = glm::vec3(0.0, 80.0f, 0.0f);
glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f); glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f); glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);
glm::vec3 direction = glm::vec3(0.0f); glm::vec3 direction = glm::vec3(0.0f);
@ -125,8 +114,6 @@ 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

@ -1,14 +1,16 @@
#ifndef CHUNK_H #ifndef CHUNK_H
#define CHUNK_H #define CHUNK_H
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/hash.hpp>
#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 <glm/gtc/type_ptr.hpp>
#include <atomic>
#include <array> #include <array>
#include <bitset> #include <bitset>
#include <mutex> #include <mutex>
#include <unordered_map>
#include <vector> #include <vector>
#include "block.hpp" #include "block.hpp"
@ -20,27 +22,14 @@
#define CHUNK_VOLUME (CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE) #define CHUNK_VOLUME (CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE)
#define CHUNK_MAX_INDEX (CHUNK_VOLUME - 1) #define CHUNK_MAX_INDEX (CHUNK_VOLUME - 1)
// int32_t is fine, since i'm limiting the coordinate to only use up to ten bits (1023). There's actually two spare bits
typedef int32_t chunk_index_t;
typedef int16_t chunk_intcoord_t;
typedef uint16_t chunk_state_t;
namespace Chunk namespace Chunk
{ {
chunk_index_t calculateIndex(chunk_intcoord_t i, chunk_intcoord_t j, chunk_intcoord_t k); constexpr uint8_t CHUNK_STATE_GENERATED = 1;
chunk_index_t calculateIndex(glm::vec3 pos); constexpr uint8_t CHUNK_STATE_MESHED = 2;
constexpr uint8_t CHUNK_STATE_MESH_LOADED = 3;
constexpr chunk_state_t CHUNK_STATE_GENERATED = 1; constexpr uint8_t CHUNK_STATE_LOADED = 4;
constexpr chunk_state_t CHUNK_STATE_MESHED = 2; constexpr uint8_t CHUNK_STATE_EMPTY = 7;
constexpr chunk_state_t CHUNK_STATE_MESH_LOADED = 4;
constexpr chunk_state_t CHUNK_STATE_LOADED = 8;
constexpr chunk_state_t CHUNK_STATE_OUTOFVISION = 16;
constexpr chunk_state_t CHUNK_STATE_UNLOADED = 32;
constexpr chunk_state_t CHUNK_STATE_EMPTY = 64;
constexpr chunk_state_t CHUNK_STATE_IN_GENERATION_QUEUE = 128;
constexpr chunk_state_t CHUNK_STATE_IN_MESHING_QUEUE = 256;
constexpr chunk_state_t CHUNK_STATE_IN_DELETING_QUEUE = 512;
int coord3DTo1D(int x, int y, int z); int coord3DTo1D(int x, int y, int z);
@ -53,14 +42,9 @@ namespace Chunk
public: public:
glm::vec3 getPosition() { return this->position; } glm::vec3 getPosition() { return this->position; }
void setState(chunk_state_t nstate, bool value); std::bitset<8> getTotalState() { return this->state; }
bool getState(chunk_state_t n) { return (this->state & n) == n; } bool getState(uint8_t n) { return this->state.test(n); }
bool isFree(){ return !( void setState(uint8_t nstate, bool value);
this->getState(CHUNK_STATE_IN_GENERATION_QUEUE) ||
this->getState(CHUNK_STATE_IN_MESHING_QUEUE) ||
this->getState(CHUNK_STATE_IN_DELETING_QUEUE)
); }
chunk_state_t getTotalState() { return this->state; }
void setBlock(Block b, int x, int y, int z); void setBlock(Block b, int x, int y, int z);
void setBlocks(int start, int end, Block b); void setBlocks(int start, int end, Block b);
@ -69,15 +53,23 @@ namespace Chunk
std::unique_ptr<Block[]> getBlocksArray(int* len) { return (this->blocks.toArray(len)); } std::unique_ptr<Block[]> getBlocksArray(int* len) { return (this->blocks.toArray(len)); }
public: public:
std::atomic<float> unload_timer{0}; GLuint VAO{0}, VBO{0}, EBO{0}, colorBuffer{0}, nIndices{0};
chunk_index_t getIndex(){ return this->index; }
std::mutex mutex_state;
std::vector<GLfloat> vertices;
std::vector<GLfloat> colors;
std::vector<GLuint> indices;
std::unordered_map<glm::vec3, std::tuple<GLuint, glm::vec3, glm::vec3> > vertices_map; // index,
// normal,
std::vector<glm::vec3> index_to_vertex;
private: private:
glm::vec3 position{}; glm::vec3 position{};
IntervalMap<Block> blocks{}; IntervalMap<Block> blocks{};
std::atomic<chunk_state_t> state{0}; std::bitset<8> state{0};
chunk_index_t index;
}; };
}; };

View File

@ -1,41 +1,28 @@
#ifndef CHUNKMANAGER_H #ifndef CHUNKMANAGER_H
#define CHUNKMANAGER_H #define CHUNKMANAGER_H
#include <oneapi/tbb/concurrent_hash_map.h> // Second to be passed outside of render distance for a chunk to be destroyed
#include <oneapi/tbb/concurrent_queue.h>
#include <oneapi/tbb/concurrent_priority_queue.h>
#include <thread>
#include "chunk.hpp"
#include "globals.hpp"
#include "worldupdatemessage.h"
// Seconds to be passed outside of render distance for a chunk to be destroyed
#define UNLOAD_TIMEOUT 10 #define UNLOAD_TIMEOUT 10
#define MESHING_PRIORITY_NORMAL 0 #include <thread>
#define MESHING_PRIORITY_PLAYER_EDIT 10
#define GENERATION_PRIORITY_NORMAL 0
namespace chunkmanager namespace chunkmanager
{ {
typedef oneapi::tbb::concurrent_hash_map<chunk_index_t, Chunk::Chunk*> ChunkTable; std::thread initGenThread();
typedef std::pair<Chunk::Chunk*, uint8_t> ChunkPQEntry; std::thread initMeshThread();
// The comparing function to use void stopGenThread();
struct compare_f { void stopMeshThread();
bool operator()(const ChunkPQEntry& u, const ChunkPQEntry& v) const {
return u.second > v.second; void mesh();
} void generate();
};
typedef oneapi::tbb::concurrent_priority_queue<ChunkPQEntry, compare_f> ChunkPriorityQueue;
void init(); void init();
void update(); void blockpick(bool place);
void stop(); uint32_t calculateIndex(uint16_t i, uint16_t j, uint16_t k);
void destroy(); void destroy();
WorldUpdateMsgQueue& getWorldUpdateQueue(); void update(float deltaTime);
std::array<std::array<chunk_intcoord_t, 3>, chunks_volume>& getChunksIndices(); void updateChunk(uint32_t, uint16_t, uint16_t, uint16_t);
Block getBlockAtPos(int x, int y, int z);
} }
#endif #endif

View File

@ -1,36 +0,0 @@
#ifndef CHUNK_MESH_DATA_H
#define CHUNK_MESH_DATA_H
#include <oneapi/tbb/concurrent_queue.h>
#include "chunk.hpp"
enum class ChunkMeshDataType{
MESH_UPDATE
};
typedef struct ChunkMeshData{
chunk_index_t index;
glm::vec3 position;
int num_vertices = 0;
std::vector<GLfloat> vertices;
std::vector<GLfloat> extents;
std::vector<GLfloat> texinfo;
ChunkMeshDataType message_type;
void clear(){
vertices.clear();
texinfo.clear();
extents.clear();
index = 0;
position = glm::vec3(0);
num_vertices = 0;
}
}ChunkMeshData;
typedef oneapi::tbb::concurrent_queue<ChunkMeshData*> ChunkMeshDataQueue;
#endif

View File

@ -1,34 +1,28 @@
#ifndef CHUNKMESH_H #ifndef CHUNKMESH_H
#define CHUNKMESH_H #define CHUNKMESH_H
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <string> #include <string>
#include <vector> #include <vector>
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <GLFW/glfw3.h>
#include <oneapi/tbb/concurrent_queue.h>
#include "chunk.hpp" #include "chunk.hpp"
#include "chunkmeshdata.hpp"
#include "globals.hpp" #include "globals.hpp"
#include "shader.hpp" #include "shader.hpp"
namespace chunkmesher{ namespace chunkmesher{
struct MeshData{
Chunk::Chunk* chunk;
GLuint numVertices{0};
std::vector<GLfloat> vertices;
std::vector<GLfloat> extents;
std::vector<GLfloat> texinfo;
};
ChunkMeshDataQueue& getMeshDataQueue();
void init();
void mesh(Chunk::Chunk* chunk); void mesh(Chunk::Chunk* chunk);
void sendtogpu(Chunk::Chunk* chunk);
void draw(Chunk::Chunk* chunk, glm::mat4 model);
void quad(Chunk::Chunk* chunk, glm::vec3 bottomLeft, glm::vec3 topLeft, glm::vec3 topRight,
glm::vec3 bottomRight, glm::vec3 normal, Block block, bool backFace);
} }
#endif #endif

View File

@ -1,14 +0,0 @@
#ifndef CONTROLS_H
#define CONTROLS_H
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#define BLOCKPICK_TIMEOUT 0.1f
namespace controls{
void init();
void update(GLFWwindow* window);
};
#endif

View File

@ -1,19 +0,0 @@
#ifndef DEBUG_WINDOW_H
#define DEBUG_WINDOW_H
#include <any>
#include <string>
#include <GLFW/glfw3.h>
namespace debug{
namespace window {
void init(GLFWwindow* window);
void prerender();
void render();
void destroy();
void set_parameter(std::string key, std::any value);
}
}
#endif

View File

@ -13,12 +13,7 @@
#define RENDER_DISTANCE 16 #define RENDER_DISTANCE 16
extr Camera theCamera; extr Camera theCamera;
// the cube spans in both directions, to each axis has to be multiplied by 2. 2^3=8 extr Shader* theShader;
constexpr int chunks_volume = 8*(RENDER_DISTANCE*RENDER_DISTANCE*RENDER_DISTANCE);
extr bool wireframe;
extr float sines[360];
extr float cosines[360];
extr uint32_t MORTON_XYZ_ENCODE[CHUNK_SIZE][CHUNK_SIZE][CHUNK_SIZE]; extr uint32_t MORTON_XYZ_ENCODE[CHUNK_SIZE][CHUNK_SIZE][CHUNK_SIZE];
extr uint32_t MORTON_XYZ_DECODE[CHUNK_VOLUME][3]; extr uint32_t MORTON_XYZ_DECODE[CHUNK_VOLUME][3];

View File

@ -38,9 +38,8 @@ public:
if(end_next_entry->first != end) if(end_next_entry->first != end)
treemap[end] = end_prev_entry->second; treemap[end] = end_prev_entry->second;
auto e = treemap.upper_bound(end);
// A little optimization: delete next key if it is of the same value of the end key // A little optimization: delete next key if it is of the same value of the end key
if(e != treemap.end() && e->second == treemap[end]) treemap.erase(end_next_entry); if(end_next_entry->second == treemap[end]) treemap.erase(end_next_entry);
} }
// insert the start key. Replaces whatever value is already there. Do not place if the element before is of the same value // insert the start key. Replaces whatever value is already there. Do not place if the element before is of the same value

View File

@ -1,10 +1,8 @@
#ifndef MAIN_H #ifndef MAIN_H
#define MAIN_H #define MAIN_H
#include <glad/glad.h>
#include <GLFW/glfw3.h>
void framebuffer_size_callback(GLFWwindow *, int, int); void framebuffer_size_callback(GLFWwindow *, int, int);
void mouse_callback(GLFWwindow *window, double xpos, double ypos); void mouse_callback(GLFWwindow *window, double xpos, double ypos);
void processInput(GLFWwindow *);
#endif #endif

View File

@ -1,61 +0,0 @@
#ifndef RENDERER_H
#define RENDERER_H
#include <oneapi/tbb/concurrent_unordered_set.h>
#include <oneapi/tbb/concurrent_queue.h>
#include "chunk.hpp"
#include "chunkmesher.hpp"
#include "chunkmeshdata.hpp"
#include "shader.hpp"
namespace renderer{
typedef struct RenderInfo {
chunk_index_t index;
int num_vertices;
glm::vec3 position;
bool buffers_allocated=false;
GLuint VAO, VBO, extentsBuffer, texinfoBuffer;
void allocateBuffers(){
// Allocate buffers
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &extentsBuffer);
glGenBuffers(1, &texinfoBuffer);
buffers_allocated=true;
}
void deallocateBuffers(){
// Allocate buffers
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &extentsBuffer);
glDeleteBuffers(1, &texinfoBuffer);
glDeleteVertexArrays(1, &VAO);
buffers_allocated=false;
}
} RenderInfo;
typedef oneapi::tbb::concurrent_queue<int32_t> IndexQueue;
void init(GLFWwindow* window);
void send_chunk_to_gpu(ChunkMeshData* mesh_data, RenderInfo* render_info);
void render();
void resize_framebuffer(int width, int height);
void framebuffer_size_callback(GLFWwindow *window, int width, int height);
void destroy();
void saveScreenshot(bool forceFullHD=false);
Shader* getRenderShader();
ChunkMeshDataQueue& getMeshDataQueue();
IndexQueue& getDeleteIndexQueue();
};
#endif

View File

@ -19,32 +19,22 @@ public:
unsigned int ID; unsigned int ID;
// constructor generates the shader on the fly // constructor generates the shader on the fly
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
Shader(const char* geometryPath, const char *vertexPath, const char *fragmentPath) Shader(const char *vertexPath, const char *fragmentPath)
{ {
// 1. retrieve the vertex/fragment source code from filePath // 1. retrieve the vertex/fragment source code from filePath
std::string vertexCode; std::string vertexCode;
std::string fragmentCode; std::string fragmentCode;
std::string geometryCode;
std::ifstream vShaderFile; std::ifstream vShaderFile;
std::ifstream fShaderFile; std::ifstream fShaderFile;
std::ifstream gShaderFile;
// ensure ifstream objects can throw exceptions: // ensure ifstream objects can throw exceptions:
vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit); vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit); fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
gShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try try
{ {
std::stringstream vShaderStream, fShaderStream, gShaderStream;
if(geometryPath){
gShaderFile.open(geometryPath);
gShaderStream << gShaderFile.rdbuf();
gShaderFile.close();
geometryCode = gShaderStream.str();
}
// open files // open files
vShaderFile.open(vertexPath); vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath); fShaderFile.open(fragmentPath);
std::stringstream vShaderStream, fShaderStream;
// read file's buffer contents into streams // read file's buffer contents into streams
vShaderStream << vShaderFile.rdbuf(); vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf(); fShaderStream << fShaderFile.rdbuf();
@ -59,45 +49,29 @@ public:
{ {
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ: " << e.what() << std::endl; std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ: " << e.what() << std::endl;
} }
// shader Program
ID = glCreateProgram();
unsigned int vertex, fragment, geometry;
// geometry shader
if(geometryPath){
const char *gShaderCode = geometryCode.c_str();
geometry = glCreateShader(GL_GEOMETRY_SHADER);
glShaderSource(geometry, 1, &gShaderCode, NULL);
glCompileShader(geometry);
checkCompileErrors(geometry, "GEOMETRY");
glAttachShader(ID, geometry);
}
// vertex shader
const char *vShaderCode = vertexCode.c_str(); const char *vShaderCode = vertexCode.c_str();
const char *fShaderCode = fragmentCode.c_str();
// 2. compile shaders
unsigned int vertex, fragment;
// vertex shader
vertex = glCreateShader(GL_VERTEX_SHADER); vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL); glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex); glCompileShader(vertex);
checkCompileErrors(vertex, "VERTEX"); checkCompileErrors(vertex, "VERTEX");
glAttachShader(ID, vertex);
// fragment Shader // fragment Shader
const char *fShaderCode = fragmentCode.c_str();
fragment = glCreateShader(GL_FRAGMENT_SHADER); fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fShaderCode, NULL); glShaderSource(fragment, 1, &fShaderCode, NULL);
glCompileShader(fragment); glCompileShader(fragment);
checkCompileErrors(fragment, "FRAGMENT"); checkCompileErrors(fragment, "FRAGMENT");
// shader Program
ID = glCreateProgram();
glAttachShader(ID, vertex);
glAttachShader(ID, fragment); glAttachShader(ID, fragment);
// Constructu the program
glLinkProgram(ID); glLinkProgram(ID);
checkCompileErrors(ID, "PROGRAM"); checkCompileErrors(ID, "PROGRAM");
// delete the shaders as they're linked into our program now and no longer necessary // delete the shaders as they're linked into our program now and no longer necessary
glDeleteShader(vertex); glDeleteShader(vertex);
glDeleteShader(fragment); glDeleteShader(fragment);
if(geometryPath) glDeleteShader(geometry);
} }
// activate the shader // activate the shader
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +0,0 @@
#ifndef WORLD_UPDATE_MSG_H
#define WORLD_UPDATE_MSG_H
#include <glm/glm.hpp>
#include <oneapi/tbb/concurrent_queue.h>
#include "block.hpp"
enum class WorldUpdateMsgType{
BLOCKPICK_PLACE,
BLOCKPICK_BREAK
};
typedef struct WorldUpdateMsg{
WorldUpdateMsgType msg_type;
glm::vec3 cameraPos;
glm::vec3 cameraFront;
float time;
Block block;
} WorldUpdateMsg;
typedef oneapi::tbb::concurrent_queue<WorldUpdateMsg> WorldUpdateMsgQueue;
#endif

View File

@ -1,3 +1,2 @@
add_subdirectory(glad) add_subdirectory(glad)
add_subdirectory(glm) add_subdirectory(glm)
add_subdirectory(imgui)

@ -1 +0,0 @@
Subproject commit 6addf28c4b5d8fd109a6db73bed6436952b230b2

View File

@ -1,28 +0,0 @@
#version 330 core
in vec2 TexCoord;
out vec4 FragColor;
uniform sampler2D renderTex;
uniform int screenWidth;
uniform int screenHeight;
uniform int crosshairType;
void main(){
float crosshair_alpha = 0.8;
float dist = length(gl_FragCoord.xy-vec2(screenWidth/2, screenHeight/2));
FragColor = texture(renderTex, TexCoord);
/*float crosshair_color = (FragColor.x + FragColor.y + FragColor.z) / 3;
/*if(crosshair_color <= 0.5) crosshair_color = 1.0;
/*else crosshair_color = 0.0;*/
float crosshair_color = 1.0;
if(dist <= 7){
if( (crosshairType == 0 && dist >= 5) ||
(crosshairType == 1 && ( int(gl_FragCoord.x) == int(screenWidth / 2) ||
int(gl_FragCoord.y) == int(screenHeight / 2)) )
) FragColor = vec4(vec3(crosshair_color), crosshair_alpha);
}
}

View File

@ -1,12 +0,0 @@
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec2 TexCoord;
void main()
{
TexCoord = aTexCoord;
gl_Position = vec4(aPos, 1.0);
}

View File

@ -1,47 +0,0 @@
#version 330 core
out vec4 FragColor;
in vec3 TexCoord;
in vec3 Normal;
in vec3 FragPos;
vec3 lightColor = vec3(1.0);
vec3 lightDir = -normalize(vec3(0.0, 100.0, 0.0) - vec3(32.0));
float ambientStrength = 0.1;
float diffuseStrength = 0.8;
float specularStrength = 0.1;
uniform vec3 viewPos;
uniform float u_time;
uniform sampler2DArray textureArray;
float gamma = 2.2;
void main(){
// Load the texture
// anti-gamma-correction of the texture. Without this it would be gamma corrected twice!
vec3 vColor = pow(texture(textureArray, TexCoord).rgb, vec3(gamma));
if(TexCoord.z == 4) vColor = vColor * normalize(vec3(10, 250, 10));
vec3 normal = normalize(Normal);
/* Start of Blinn-Phong lighting */
// Ambient
vec3 ambient = lightColor*vColor;
// Diffuse
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = vColor * diff;
// Blinn Specular
vec3 viewDir = normalize(viewPos - FragPos);
vec3 halfwayDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(normal, halfwayDir), 0.0), 32.0);
vec3 specular = lightColor * vColor * spec;
// Final color
vec3 color = ambient * ambientStrength + diffuse * diffuseStrength + specular * specularStrength;
FragColor.rgb = pow(color, vec3(1.0/gamma));
}

View File

@ -1,81 +0,0 @@
#version 330 core
layout (points) in;
layout (triangle_strip, max_vertices = 4) out;
in VS_OUT{
vec3 Extents;
vec3 Normal;
float BlockType;
} gs_in[];
out vec3 TexCoord;
out vec3 Normal;
out vec3 FragPos;
uniform mat4 view;
uniform mat4 projection;
void main(){
Normal = gs_in[0].Normal;
TexCoord = vec3(0.0, 0.0, gs_in[0].BlockType);
gl_Position = gl_in[0].gl_Position;
FragPos = vec3(gl_Position);
gl_Position = projection * view * gl_Position;
EmitVertex();
if(gs_in[0].Extents.x == 0){
TexCoord = vec3(gs_in[0].Extents.z, 0.0, gs_in[0].BlockType);
gl_Position = gl_in[0].gl_Position + vec4(0.0, 0.0, gs_in[0].Extents.z, 0.0);
FragPos = vec3(gl_Position);
gl_Position = projection * view * gl_Position;
EmitVertex();
TexCoord = vec3(0.0, gs_in[0].Extents.y, gs_in[0].BlockType);
gl_Position = gl_in[0].gl_Position + vec4(0.0, gs_in[0].Extents.y, 0.0, 0.0);
FragPos = vec3(gl_Position);
gl_Position = projection * view * gl_Position;
EmitVertex();
TexCoord = vec3(gs_in[0].Extents.z, gs_in[0].Extents.y, gs_in[0].BlockType);
}
else if(gs_in[0].Extents.y == 0){
TexCoord = vec3(gs_in[0].Extents.x, 0.0, gs_in[0].BlockType);
gl_Position = gl_in[0].gl_Position + vec4(gs_in[0].Extents.x, 0.0, 0.0, 0.0);
FragPos = vec3(gl_Position);
gl_Position = projection * view * gl_Position;
EmitVertex();
TexCoord = vec3(0.0, gs_in[0].Extents.z, gs_in[0].BlockType);
gl_Position = gl_in[0].gl_Position + vec4(0.0, 0.0, gs_in[0].Extents.z, 0.0);
FragPos = vec3(gl_Position);
gl_Position = projection * view * gl_Position;
EmitVertex();
TexCoord = vec3(gs_in[0].Extents.x, gs_in[0].Extents.z, gs_in[0].BlockType);
}
else{
TexCoord = vec3(gs_in[0].Extents.x, 0.0, gs_in[0].BlockType);
gl_Position = gl_in[0].gl_Position + vec4(gs_in[0].Extents.x, 0.0, 0.0, 0.0);
FragPos = vec3(gl_Position);
gl_Position = projection * view * gl_Position;
EmitVertex();
TexCoord = vec3(0.0, gs_in[0].Extents.y, gs_in[0].BlockType);
gl_Position = gl_in[0].gl_Position + vec4(0.0, gs_in[0].Extents.y, 0.0, 0.0);
FragPos = vec3(gl_Position);
gl_Position = projection * view * gl_Position;
EmitVertex();
TexCoord = vec3(gs_in[0].Extents.x, gs_in[0].Extents.y, gs_in[0].BlockType);
}
gl_Position = gl_in[0].gl_Position + vec4(gs_in[0].Extents, 0.0);
FragPos = vec3(gl_Position);
gl_Position = projection * view * gl_Position;
EmitVertex();
EndPrimitive();
}

View File

@ -1,27 +0,0 @@
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aExtents;
layout (location = 2) in vec2 aInfo;
uniform mat4 model;
out VS_OUT {
vec3 Extents;
vec3 Normal;
float BlockType;
} vs_out;
void main()
{
//vNormal = mat3(transpose(inverse(model))) * aNormal;
vs_out.Extents = aExtents;
vs_out.BlockType = aInfo.y;
if(aExtents.x == 0) vs_out.Normal = vec3(1.0 - 2*aInfo.x, 0.0, 0.0);
else if(aExtents.y == 0) vs_out.Normal = vec3(0.0, 1.0 - 2*aInfo.x, 0.0);
else vs_out.Normal = vec3(0.0, 0.0, 1.0 - 2*aInfo.x);
vs_out.Normal = mat3(transpose(inverse(model))) * vs_out.Normal;
gl_Position = model * vec4(aPos, 1.0);
}

View File

@ -1,10 +1,9 @@
cmake_minimum_required(VERSION 3.2) cmake_minimum_required(VERSION 3.2)
project(OpenGLTest) project(OpenGLTest)
set(SOURCE_FILES main.cpp controls.cpp chunk.cpp chunkmanager.cpp chunkmesher.cpp chunkgenerator.cpp set(SOURCE_FILES main.cpp chunk.cpp chunkmanager.cpp chunkmesher.cpp chunkgenerator.cpp spacefilling.cpp stb_image.cpp utils.cpp OpenSimplexNoise.cpp)
debugwindow.cpp renderer.cpp spacefilling.cpp stb_image.cpp utils.cpp OpenSimplexNoise.cpp)
add_executable(OpenGLTest ${SOURCE_FILES}) add_executable(OpenGLTest ${SOURCE_FILES})
target_link_libraries(OpenGLTest glfw tbb glad glm imgui) target_link_libraries(OpenGLTest glfw glad glm)
install(TARGETS OpenGLTest DESTINATION ${DIVISIBLE_INSTALL_BIN_DIR}) install(TARGETS OpenGLTest DESTINATION ${DIVISIBLE_INSTALL_BIN_DIR})

View File

@ -15,31 +15,37 @@ namespace Chunk
return utils::coord3DTo1D(x, y, z, CHUNK_SIZE, CHUNK_SIZE, CHUNK_SIZE); return utils::coord3DTo1D(x, y, z, CHUNK_SIZE, CHUNK_SIZE, CHUNK_SIZE);
} }
chunk_index_t calculateIndex(glm::vec3 pos){
return calculateIndex(static_cast<chunk_intcoord_t>(pos.x), static_cast<chunk_intcoord_t>(pos.y),
static_cast<chunk_intcoord_t>(pos.z));
}
chunk_index_t calculateIndex(chunk_intcoord_t i, chunk_intcoord_t j, chunk_intcoord_t k){
return i | (j << 10) | (k << 20);
}
Chunk::Chunk(glm::vec3 pos) Chunk::Chunk(glm::vec3 pos)
{ {
this->position = pos; this->position = pos;
this->setState(CHUNK_STATE_EMPTY, true); this->setState(CHUNK_STATE_EMPTY, true);
this->setBlocks(0, CHUNK_MAX_INDEX, Block::AIR);
this->index = calculateIndex(pos); glGenVertexArrays(1, &(this->VAO));
glGenBuffers(1, &(this->colorBuffer));
glGenBuffers(1, &(this->VBO));
glGenBuffers(1, &(this->EBO));
} }
Chunk ::~Chunk() Chunk ::~Chunk()
{ {
vertices_map.clear();
index_to_vertex.clear();
vertices.clear();
indices.clear();
colors.clear();
glDeleteBuffers(1, &(this->colorBuffer));
glDeleteBuffers(1, &(this->VBO));
glDeleteBuffers(1, &(this->EBO));
glDeleteVertexArrays(1, &(this->VAO));
mutex_state.unlock();
} }
Block Chunk::getBlock(int x, int y, int z) Block Chunk::getBlock(int x, int y, int z)
{ {
if(x < 0 || y < 0 || z < 0 || x > CHUNK_SIZE -1 || y > CHUNK_SIZE -1 || z > CHUNK_SIZE-1 ||
!getState(CHUNK_STATE_GENERATED)) return Block::AIR;
return blocks.at(HILBERT_XYZ_ENCODE[x][y][z]); return blocks.at(HILBERT_XYZ_ENCODE[x][y][z]);
} }
@ -54,11 +60,11 @@ namespace Chunk
this->blocks.insert(start < 0 ? 0 : start, end >= CHUNK_VOLUME ? CHUNK_VOLUME : end, b); this->blocks.insert(start < 0 ? 0 : start, end >= CHUNK_VOLUME ? CHUNK_VOLUME : end, b);
} }
void Chunk::setState(chunk_state_t nstate, bool value) void Chunk::setState(uint8_t nstate, bool value)
{ {
if (value) if (value)
this->state.fetch_or(nstate); this->state.set((size_t)nstate);
else else
this->state.fetch_and(~nstate); this->state.reset((size_t)nstate);
} }
} }

View File

@ -9,229 +9,42 @@
#include "utils.hpp" #include "utils.hpp"
#define GRASS_OFFSET 40 #define GRASS_OFFSET 40
#define NOISE_GRASS_MULT 30 #define NOISE_GRASS_MULT 20
#define NOISE_DIRT_MULT 3 #define NOISE_DIRT_MULT 3
#define NOISE_DIRT_MIN 3 #define NOISE_DIRT_MIN 2
#define NOISE_DIRT_X_MULT 0.001f #define NOISE_DIRT_X_MULT 0.001f
#define NOISE_DIRT_Z_MULT 0.001f #define NOISE_DIRT_Z_MULT 0.001f
#define NOISE_GRASS_X_MULT 0.018f #define NOISE_GRASS_X_MULT 0.035f
#define NOISE_GRASS_Z_MULT 0.018f #define NOISE_GRASS_Z_MULT 0.035f
#define NOISE_TREE_X_MULT 0.01f
#define NOISE_TREE_Z_MULT 0.01f
#define LEAVES_RADIUS 3
#define WOOD_CELL_SIZE 13
#define WOOD_CELL_CENTER 7
#define TREE_STANDARD_HEIGHT 7
#define TREE_HEIGHT_VARIATION 2
#define WOOD_CELL_BORDER (LEAVES_RADIUS-1)
#define WOOD_MAX_OFFSET (WOOD_CELL_SIZE-WOOD_CELL_CENTER-WOOD_CELL_BORDER)
void generatePyramid(Chunk::Chunk *chunk);
void generateNoise(Chunk::Chunk *chunk); void generateNoise(Chunk::Chunk *chunk);
void generateNoise3D(Chunk::Chunk *chunk); void generateNoise3D(Chunk::Chunk *chunk);
double evaluateNoise(OpenSimplexNoise::Noise noiseGen, double x, double y, double amplitude, double
frequency, double persistence, double lacunarity, int octaves);
struct TreeCellInfo evaluateTreeCell(int wcx, int wcz);
std::random_device dev;
std::mt19937 mt(dev());
OpenSimplexNoise::Noise noiseGen1(mt());
OpenSimplexNoise::Noise noiseGen2(mt());
OpenSimplexNoise::Noise noiseGenWood(mt());
// Trees are generated by virtually dividing the world into cells. Each cell can contain exactly one
// tree, with some offset in the position. Having a border in the cell ensures that no trees are generated in
// adjacent blocks
// cover CHUNK_SIZE with WOOD_CELLS + 2 cells before and after the chunk
constexpr int TREE_LUT_SIZE = std::ceil(static_cast<float>(CHUNK_SIZE)/static_cast<float>(WOOD_CELL_SIZE)) + 2;
// Info on the tree cell to generate
struct TreeCellInfo{
// Cell coordinates (in "tree cell space")
int wcx, wcz;
// trunk offset from 0,0 in the cell
int trunk_x_offset, trunk_z_offset;
// Global x,z position of the trunk
int trunk_x, trunk_z;
// Y of the center of the leaves sphere
int leaves_y_pos;
};
// Lookup tables for generation
std::array<int, CHUNK_SIZE * CHUNK_SIZE> grassNoiseLUT;
std::array<int, CHUNK_SIZE * CHUNK_SIZE> dirtNoiseLUT;
std::array<TreeCellInfo, TREE_LUT_SIZE*TREE_LUT_SIZE> treeLUT;
void generateNoise(Chunk::Chunk *chunk)
{
int cx = chunk->getPosition().x * CHUNK_SIZE;
int cy = chunk->getPosition().y * CHUNK_SIZE;
int cz = chunk->getPosition().z * CHUNK_SIZE;
// Precalculate LUTs
// Terrain LUTs
// Noise value at a given (x,z), position represents:
// Grass Noise LUT: Height of the terrain: when the grass is placed and the player will stand
// Dirt Noise LUT: How many blocks of dirt to place before there is stone
// Anything below (grass-level - dirt_height) will be stone
for (int i = 0; i < grassNoiseLUT.size(); i++)
{
int bx = i / CHUNK_SIZE;
int bz = i % CHUNK_SIZE;
grassNoiseLUT[i] = GRASS_OFFSET + evaluateNoise(noiseGen1, cx+bx, cz+bz, NOISE_GRASS_MULT, 0.01, 0.35, 2.1, 5);
dirtNoiseLUT[i] = NOISE_DIRT_MIN + (int)((1 + noiseGen2.eval(cx+bx * NOISE_DIRT_X_MULT,
cz+bz * NOISE_DIRT_Z_MULT)) * NOISE_DIRT_MULT);
}
// Tree LUT
int tree_lut_x_offset = cx / WOOD_CELL_SIZE - 1;
int tree_lut_z_offset = cz / WOOD_CELL_SIZE - 1;
for(int i = 0; i < TREE_LUT_SIZE; i++)
for(int k = 0; k < TREE_LUT_SIZE; k++){
int wcx = (tree_lut_x_offset + i);
int wcz = (tree_lut_z_offset + k);
treeLUT[i * TREE_LUT_SIZE + k] = evaluateTreeCell(wcx, wcz);
}
// Generation of terrain
// March along the space-filling curve, calculate information about the block at every position
// A space-filling curve is continuous, so there is no particular order
// Take advantage of the interval-map structure by only inserting contigous runs of blocks
Block block_prev{Block::AIR}, block;
int block_prev_start{0};
for (int s = 0; s < CHUNK_VOLUME; s++)
{
int bx = HILBERT_XYZ_DECODE[s][0];
int by = HILBERT_XYZ_DECODE[s][1];
int bz = HILBERT_XYZ_DECODE[s][2];
int x = bx + cx;
int y = by + cy;
int z = bz + cz;
int lut_index = bx * CHUNK_SIZE + bz;
int grassNoise = grassNoiseLUT[lut_index];
int dirtNoise = dirtNoiseLUT[lut_index];
int stoneLevel = grassNoise - dirtNoise;
if (y < stoneLevel)
block = Block::STONE;
else if (y >= stoneLevel && y < grassNoise)
block = Block::DIRT;
else if (y == grassNoise)
block = Block::GRASS;
else
block = Block::AIR;
// Divide the world into cells, each with exactly one tree, so that no two trees will be adjacent of each other
struct TreeCellInfo info;
int wcx = (int)(x / WOOD_CELL_SIZE) - tree_lut_x_offset; // wood cell x
int wcz = (int)(z / WOOD_CELL_SIZE) - tree_lut_z_offset; // wood cell z
// Retrieve info on the cell from LUT
info = treeLUT[wcx * TREE_LUT_SIZE + wcz];
// A tree is to be placed in this position if the coordinates are those of the tree of the current cell
int wood_height = TREE_STANDARD_HEIGHT;
bool wood = x == info.trunk_x && z == info.trunk_z && y > grassNoiseLUT[lut_index] && y <= info.leaves_y_pos;
bool leaf{false};
// Check placing of leaves
if(wood) leaf = y > info.leaves_y_pos && y < info.leaves_y_pos+LEAVES_RADIUS;
else{
if(!leaf) leaf = utils::withinDistance(x,y,z, info.trunk_x, info.leaves_y_pos, info.trunk_z, LEAVES_RADIUS);
// Eventually search neighboring cells
if(!leaf && wcx+1 < TREE_LUT_SIZE){
info = treeLUT[(wcx+1) * TREE_LUT_SIZE + wcz];
leaf = utils::withinDistance(x,y,z, info.trunk_x, info.leaves_y_pos, info.trunk_z, LEAVES_RADIUS);
}
if(!leaf && wcx-1 >= 0){
info = treeLUT[(wcx-1) * TREE_LUT_SIZE + wcz];
leaf = utils::withinDistance(x,y,z, info.trunk_x, info.leaves_y_pos, info.trunk_z, LEAVES_RADIUS);
}
if(!leaf && wcz-1 >= 0){
info = treeLUT[wcx * TREE_LUT_SIZE + (wcz-1)];
leaf = utils::withinDistance(x,y,z, info.trunk_x, info.leaves_y_pos, info.trunk_z, LEAVES_RADIUS);
}
if(!leaf && wcz+1 < TREE_LUT_SIZE){
info = treeLUT[wcx * TREE_LUT_SIZE + (wcz+1)];
leaf = utils::withinDistance(x,y,z, info.trunk_x, info.leaves_y_pos, info.trunk_z, LEAVES_RADIUS);
}
}
if(wood) block = Block::WOOD;
if(leaf) block = Block::LEAVES;
// Use the interval-map structure of the chunk to compress the world: insert "runs" of
// equal blocks using indices in the hilbert curve
if (block != block_prev)
{
chunk->setBlocks(block_prev_start, s, block_prev);
block_prev_start = s;
}
block_prev = block;
}
// Insert the last run of blocks
chunk->setBlocks(block_prev_start, CHUNK_VOLUME, block_prev);
// Mark the chunk as generated, is needed to trigger the next steps
chunk->setState(Chunk::CHUNK_STATE_GENERATED, true);
}
// Noise evaluation with Fractal Brownian Motion
double evaluateNoise(OpenSimplexNoise::Noise noiseGen, double x, double y, double amplitude, double
frequency, double persistence, double lacunarity, int octaves){
double sum = 0;
for(int i = 0; i < octaves; i++){
sum += amplitude * noiseGen.eval(x*frequency, y*frequency);
amplitude *= persistence;
frequency *= lacunarity;
}
return sum;
}
// Tree cell Info
const int TREE_MASTER_SEED_X = mt();
const int TREE_MASTER_SEED_Z = mt();
struct TreeCellInfo evaluateTreeCell(int wcx, int wcz){
int anglex = TREE_MASTER_SEED_X*wcx+TREE_MASTER_SEED_Z*wcz;
int anglez = TREE_MASTER_SEED_Z*wcz+TREE_MASTER_SEED_X*wcx;
// Start at the center of the cell, with a bit of random offset
int wcx_off = WOOD_CELL_CENTER + WOOD_MAX_OFFSET * sines[anglex % 360];
int wcz_off = WOOD_CELL_CENTER + WOOD_MAX_OFFSET * cosines[anglez % 360];
struct TreeCellInfo result{};
// Cell to world coordinates
result.trunk_x = wcx * WOOD_CELL_SIZE + wcx_off;
result.trunk_z = wcz * WOOD_CELL_SIZE + wcz_off;
result.trunk_x_offset = wcx_off;
result.trunk_z_offset = wcz_off;
result.leaves_y_pos = 1 + TREE_STANDARD_HEIGHT + GRASS_OFFSET + evaluateNoise(noiseGen1,
result.trunk_x, result.trunk_z, NOISE_GRASS_MULT, 0.01, 0.35, 2.1, 5);
return result;
}
void generateChunk(Chunk::Chunk *chunk) void generateChunk(Chunk::Chunk *chunk)
{ {
generateNoise(chunk); generateNoise(chunk);
} }
/* EXPERIMENTAL STUFF */ Block block;
void generateNoise3D(Chunk::Chunk *chunk) {
Block block_prev{Block::AIR}, block; std::random_device dev;
std::mt19937 mt(dev());
OpenSimplexNoise::Noise noiseGen1(mt());
OpenSimplexNoise::Noise noiseGen2(mt());
std::array<int, CHUNK_SIZE * CHUNK_SIZE> grassNoiseLUT;
std::array<int, CHUNK_SIZE * CHUNK_SIZE> dirtNoiseLUT;
void generateNoise(Chunk::Chunk *chunk)
{
for (int i = 0; i < grassNoiseLUT.size(); i++)
{
grassNoiseLUT[i] = -1;
dirtNoiseLUT[i] = -1;
}
Block block_prev{Block::AIR};
int block_prev_start{0}; int block_prev_start{0};
// A space filling curve is continuous, so there is no particular order // A space filling curve is continuous, so there is no particular order
@ -243,13 +56,20 @@ void generateNoise3D(Chunk::Chunk *chunk) {
int z = HILBERT_XYZ_DECODE[s][2] + CHUNK_SIZE * chunk->getPosition().z; int z = HILBERT_XYZ_DECODE[s][2] + CHUNK_SIZE * chunk->getPosition().z;
int d2 = HILBERT_XYZ_DECODE[s][0] * CHUNK_SIZE + HILBERT_XYZ_DECODE[s][2]; int d2 = HILBERT_XYZ_DECODE[s][0] * CHUNK_SIZE + HILBERT_XYZ_DECODE[s][2];
double noise = noiseGen1.eval(x * 0.025, y*0.025, z * 0.025); if (grassNoiseLUT[d2] == -1)
grassNoiseLUT[d2] = GRASS_OFFSET + (int)((0.5 + noiseGen1.eval(x * NOISE_GRASS_X_MULT, z * NOISE_GRASS_Z_MULT) * NOISE_GRASS_MULT));
if (dirtNoiseLUT[d2] == -1)
dirtNoiseLUT[d2] = NOISE_DIRT_MIN + (int)((0.5 + noiseGen2.eval(x * NOISE_DIRT_X_MULT, z * NOISE_DIRT_Z_MULT) * NOISE_DIRT_MULT));
if (noise < -0.1) int grassNoise = grassNoiseLUT[d2];
int dirtNoise = dirtNoiseLUT[d2];
int stoneLevel = grassNoise - dirtNoise;
if (y < stoneLevel)
block = Block::STONE; block = Block::STONE;
else if (noise >= -0.1 && noise < 0) else if (y >= stoneLevel && y < grassNoise)
block = Block::DIRT; block = Block::DIRT;
else if (noise >= 0 && noise < 0.08) else if (y == grassNoise)
block = Block::GRASS; block = Block::GRASS;
else else
block = Block::AIR; block = Block::AIR;
@ -265,3 +85,47 @@ void generateNoise3D(Chunk::Chunk *chunk) {
chunk->setBlocks(block_prev_start, CHUNK_VOLUME, block_prev); chunk->setBlocks(block_prev_start, CHUNK_VOLUME, block_prev);
} }
void generateNoise3D(Chunk::Chunk *chunk) {
Block block_prev{Block::AIR};
int block_prev_start{0};
// A space filling curve is continuous, so there is no particular order
for (int s = 0; s < CHUNK_VOLUME; s++)
{
int x = HILBERT_XYZ_DECODE[s][0] + CHUNK_SIZE * chunk->getPosition().x;
int y = HILBERT_XYZ_DECODE[s][1] + CHUNK_SIZE * chunk->getPosition().y;
int z = HILBERT_XYZ_DECODE[s][2] + CHUNK_SIZE * chunk->getPosition().z;
int d2 = HILBERT_XYZ_DECODE[s][0] * CHUNK_SIZE + HILBERT_XYZ_DECODE[s][2];
double noise = noiseGen1.eval(x * 0.025, y*0.025, z * 0.025);
if (noise < 0)
block = Block::STONE;
else if (noise >= 0 && noise < 0.1)
block = Block::DIRT;
else if (noise >= 0.1 && noise < 0.2)
block = Block::GRASS;
else
block = Block::AIR;
if (block != block_prev)
{
chunk->setBlocks(block_prev_start, s, block_prev);
block_prev_start = s;
}
block_prev = block;
}
chunk->setBlocks(block_prev_start, CHUNK_VOLUME, block_prev);
}
void generatePyramid(Chunk::Chunk *chunk)
{
for (int i = 0; i < CHUNK_SIZE; i++)
for (int j = 0; j < CHUNK_SIZE; j++)
for (int k = 0; k < CHUNK_SIZE; k++)
chunk->setBlock(i >= j && i < CHUNK_SIZE - j && k >= j && k < CHUNK_SIZE - j ? (j & 1) == 0 ? Block::GRASS : Block::STONE : Block::AIR, i, j, k);
}

View File

@ -1,303 +1,282 @@
#include "chunkmanager.hpp"
#include <atomic>
#include <math.h>
#include <vector>
#include <thread>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtx/string_cast.hpp>
#include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <oneapi/tbb/parallel_for.h>
#include "block.hpp"
#include "chunk.hpp" #include "chunk.hpp"
#include "chunkgenerator.hpp" #include "chunkgenerator.hpp"
#include "chunkmanager.hpp"
#include "chunkmesher.hpp" #include "chunkmesher.hpp"
#include "debugwindow.hpp"
#include "globals.hpp" #include "globals.hpp"
#include "renderer.hpp"
#include "utils.hpp" #include <atomic>
#include <iostream>
#include <math.h>
#include <mutex>
#include <set>
#include <string>
#include <unordered_map>
#include <thread>
std::unordered_map<std::uint32_t, Chunk::Chunk *> chunks;
constexpr int chunks_volume = static_cast<int>(1.333333333333*M_PI*(RENDER_DISTANCE*RENDER_DISTANCE*RENDER_DISTANCE));
std::array<std::array<int, 3>, chunks_volume> chunks_indices;
namespace chunkmanager namespace chunkmanager
{ {
void blockpick(WorldUpdateMsg& msg); // There's no need of passing by value again (check // thread management
// controls.cpp) std::mutex mutex_queue_generate;
void generate(); std::mutex mutex_queue_mesh;
void mesh(); std::set<Chunk::Chunk *> to_generate;
void send_to_chunk_meshing_thread(Chunk::Chunk* c, int priority); std::set<Chunk::Chunk *> to_mesh;
std::atomic_bool generate_should_run;
std::atomic_bool mesh_should_run;
/* Chunk holding data structures */ // update variables
// Concurrent hash table of chunks uint8_t f = 0;
ChunkTable chunks; int rr{RENDER_DISTANCE * RENDER_DISTANCE};
// Chunk indices. Centered at (0,0,0), going in concentric sphere outwards glm::vec4 frustumPlanes[6];
std::array<std::array<chunk_intcoord_t, 3>, chunks_volume> chunks_indices; glm::vec3 cameraPos;
int chunkX, chunkY, chunkZ;
int total{0}, toGpu{0};
/* World Update messaging data structure */ // disposal
WorldUpdateMsgQueue WorldUpdateQueue; std::unordered_map<uint32_t, float> to_delete;
std::set<uint32_t> to_delete_delete;
/* Multithreading */ void mesh()
std::atomic_bool should_run; {
std::thread gen_thread, mesh_thread, update_thread; 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();
}
}
// Queue of chunks to be generated void generate()
ChunkPriorityQueue chunks_to_generate_queue; {
// Queue of chunks to be meshed while (generate_should_run)
ChunkPriorityQueue chunks_to_mesh_queue; 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;
}
WorldUpdateMsgQueue& getWorldUpdateQueue(){ return WorldUpdateQueue; }
// Init chunkmanager. Chunk indices and start threads
void init(){ void init(){
int index{0}; int index{0};
for(chunk_intcoord_t i = -RENDER_DISTANCE; i < RENDER_DISTANCE; i++) int xp{0}, x{0};
for(chunk_intcoord_t j = -RENDER_DISTANCE; j < RENDER_DISTANCE; j++) bool b = true;
for(chunk_intcoord_t k = -RENDER_DISTANCE; k < RENDER_DISTANCE; k++){
chunks_indices[index][0]=i; // Iterate over all chunks, in concentric spheres starting fron the player and going
chunks_indices[index][1]=j; // outwards. Alternate left and right
chunks_indices[index][2]=k; // Eq. of the sphere (x - a)² + (y - b)² + (z - c)² = r²
while (xp <= RENDER_DISTANCE)
{
// Alternate between left and right
if (b) x = +xp;
else x = -xp;
// Step 1. At current x, get the corresponding y values (2nd degree equation, up to 2
// possible results)
int y1 = static_cast<int>(sqrt((rr) - x*x));
for (int y = -y1 + 1 ; y <= y1; y++)
{
// Step 2. At both y's, get the corresponding z values
int z1 = static_cast<int>(sqrt( rr - x*x - y*y));
for (int z = -z1 + 1; z <= z1; z++){
chunks_indices[index][0] = x;
chunks_indices[index][1] = y;
chunks_indices[index][2] = z;
index++; index++;
} }
}
should_run = true; if (!b)
update_thread = std::thread(update); {
gen_thread = std::thread(generate); xp++;
mesh_thread = std::thread(mesh); b = true;
} }
else b = false;
// Method for world generation thread(s)
void generate(){
while(should_run){
ChunkPQEntry entry;
if(chunks_to_generate_queue.try_pop(entry)){
Chunk::Chunk* chunk = entry.first;
generateChunk(chunk);
chunk->setState(Chunk::CHUNK_STATE_IN_GENERATION_QUEUE, false);
}
}
chunks_to_generate_queue.clear();
}
// Method for chunk meshing thread(s)
void mesh(){
while(should_run){
ChunkPQEntry entry;
if(chunks_to_mesh_queue.try_pop(entry)){
Chunk::Chunk* chunk = entry.first;
chunkmesher::mesh(chunk);
chunk->setState(Chunk::CHUNK_STATE_IN_MESHING_QUEUE, false);
}
}
chunks_to_mesh_queue.clear();
}
void send_to_chunk_meshing_thread(Chunk::Chunk* c, int priority){
c->setState(Chunk::CHUNK_STATE_IN_MESHING_QUEUE, true);
chunks_to_mesh_queue.push(std::make_pair(c, MESHING_PRIORITY_NORMAL));
}
oneapi::tbb::concurrent_queue<chunk_index_t> chunks_todelete;
void update(){
while(should_run) {
/* Setup variables for the whole loop */
// Atomic is needed by parallel_for
std::atomic_int nUnloaded{0}, nMarkUnload{0}, nExplored{0}, nMeshed{0}, nGenerated{0};
std::atomic_int chunkX=static_cast<int>(theCamera.getAtomicPosX() / CHUNK_SIZE);
std::atomic_int chunkY=static_cast<int>(theCamera.getAtomicPosY() / CHUNK_SIZE);
std::atomic_int chunkZ=static_cast<int>(theCamera.getAtomicPosZ() / CHUNK_SIZE);
/* Process update messages before anything happens */
WorldUpdateMsg msg;
while(WorldUpdateQueue.try_pop(msg)){
switch(msg.msg_type){
case WorldUpdateMsgType::BLOCKPICK_BREAK:
case WorldUpdateMsgType::BLOCKPICK_PLACE:
blockpick(msg);
break;
}
}
/* Delete old chunks */
// In my head it makes sense to first delete old chunks, then create new ones
// I think it's easier for memory allocator to re-use the memory that was freed just
// before, but this isn't backed be any evidence and I might be wrong. Anyway this way
// works fine so I'm gonna keep it.
chunk_index_t i;
ChunkTable::accessor a;
while(chunks_todelete.try_pop(i)){
const chunk_index_t index = i;
if(chunks.find(a, index)){
Chunk::Chunk* c = a->second;
// Use the accessor to erase the element
// Using the key doesn't work
if(chunks.erase(a)){
nUnloaded++;
renderer::getDeleteIndexQueue().push(index);
delete c;
} else {
c->setState(Chunk::CHUNK_STATE_IN_DELETING_QUEUE, false);
std::cout << "failed to delete " << index << std::endl;
}
} else std::cout << "no such element found to delete\n";
}
/* Create new chunks around the player */
for(int i = 0; i < chunks_volume; i++) {
const chunk_intcoord_t x = chunks_indices[i][0] + chunkX;
const chunk_intcoord_t y = chunks_indices[i][1] + chunkY;
const chunk_intcoord_t z = chunks_indices[i][2] + chunkZ;
if(x < 0 || y < 0 || z < 0 || x > 1023 || y > 1023 || z > 1023) continue;
nExplored++;
const chunk_index_t index = Chunk::calculateIndex(x, y, z);
ChunkTable::accessor a;
if(!chunks.find(a, index)) chunks.emplace(a, std::make_pair(index, new
Chunk::Chunk(glm::vec3(x,y,z))));
}
/* Update all the chunks */
oneapi::tbb::parallel_for(chunks.range(), [&](ChunkTable::range_type &r){
for(ChunkTable::iterator a = r.begin(); a != r.end(); a++){
Chunk::Chunk* c = a->second;
int x = c->getPosition().x;
int y = c->getPosition().y;
int z = c->getPosition().z;
int distx = x - chunkX;
int disty = y - chunkY;
int distz = z - chunkZ;
// Local variables avoid continously having to call atomic variables
int gen{0}, mesh{0}, unload{0};
if(
distx >= -RENDER_DISTANCE && distx < RENDER_DISTANCE &&
disty >= -RENDER_DISTANCE && disty < RENDER_DISTANCE &&
distz >= -RENDER_DISTANCE && distz < RENDER_DISTANCE
){
// If within distance
// Reset out-of-view flags
c->setState(Chunk::CHUNK_STATE_OUTOFVISION, false);
c->setState(Chunk::CHUNK_STATE_UNLOADED, false);
// If not yet generated
if(!c->getState(Chunk::CHUNK_STATE_GENERATED)){
if(c->isFree()){
// Generate
// Mark as present in the queue before sending to avoid strange
// a chunk being marked as in the queue after it was already
// processed
c->setState(Chunk::CHUNK_STATE_IN_GENERATION_QUEUE, true);
chunks_to_generate_queue.push(std::make_pair(c, GENERATION_PRIORITY_NORMAL));
}
}else{
gen++;
// If generated but not yet meshed
if(!c->getState(Chunk::CHUNK_STATE_MESHED)){
ChunkTable::accessor a1;
// Checking if nearby chunks have been generated allows for seamless
// borders between chunks
if(c->isFree() &&
(distx+1 >= RENDER_DISTANCE || x + 1 > 1023 || (chunks.find(a1, Chunk::calculateIndex(x+1, y, z)) &&
a1->second->getState(Chunk::CHUNK_STATE_GENERATED))) &&
(distx-1 < -RENDER_DISTANCE || x - 1 < 0 || (chunks.find(a1, Chunk::calculateIndex(x-1, y, z)) &&
a1->second->getState(Chunk::CHUNK_STATE_GENERATED))) &&
(disty+1 >= RENDER_DISTANCE || y + 1 > 1023 || (chunks.find(a1, Chunk::calculateIndex(x, y+1, z)) &&
a1->second->getState(Chunk::CHUNK_STATE_GENERATED))) &&
(disty-1 < -RENDER_DISTANCE || y - 1 < 0|| (chunks.find(a1, Chunk::calculateIndex(x, y-1, z)) &&
a1->second->getState(Chunk::CHUNK_STATE_GENERATED))) &&
(distz+1 >= RENDER_DISTANCE || z + 1 > 1023 || (chunks.find(a1, Chunk::calculateIndex(x, y, z+1)) &&
a1->second->getState(Chunk::CHUNK_STATE_GENERATED))) &&
(distz-1 < -RENDER_DISTANCE || z - 1 < 0|| (chunks.find(a1, Chunk::calculateIndex(x, y, z-1)) &&
a1->second->getState(Chunk::CHUNK_STATE_GENERATED)))
)
{
// Mesh
// Mark as present in the queue before sending to avoid strange
// a chunk being marked as in the queue after it was already
// processed
send_to_chunk_meshing_thread(c, MESHING_PRIORITY_NORMAL);
}
}else mesh++;
}
}else{
// If not within distance
if(c->getState(Chunk::CHUNK_STATE_OUTOFVISION)){
// If enough time has passed, set to be deleted
if(c->isFree() && glfwGetTime() - c->unload_timer >= UNLOAD_TIMEOUT){
c->setState(Chunk::CHUNK_STATE_IN_DELETING_QUEUE, true);
chunks_todelete.push(c->getIndex());
unload++;
}
}else{
// Mark as out of view, and start waiting time
c->setState(Chunk::CHUNK_STATE_OUTOFVISION, true);
c->setState(Chunk::CHUNK_STATE_UNLOADED, false);
c->unload_timer = glfwGetTime();
}
}
// Update atomic variables only once at the end
nGenerated += gen;
nMeshed += mesh;
nMarkUnload += unload;
}
});
debug::window::set_parameter("update_chunks_total", (int)chunks.size());
debug::window::set_parameter("update_chunks_generated", (int) nGenerated);
debug::window::set_parameter("update_chunks_meshed", (int) nMeshed);
debug::window::set_parameter("update_chunks_freed", (int) nUnloaded);
debug::window::set_parameter("update_chunks_explored", (int) nExplored);
} }
} }
std::array<std::array<chunk_intcoord_t, 3>, chunks_volume>& getChunksIndices(){ return chunks_indices; } void update(float deltaTime)
{
// Try to lock resources
f = 0;
f |= mutex_queue_generate.try_lock();
f |= mutex_queue_mesh.try_lock() << 1;
void stop() { cameraPos = theCamera.getPos();
should_run=false; theCamera.getFrustumPlanes(frustumPlanes, true);
chunkX=static_cast<int>(cameraPos.x) / CHUNK_SIZE;
chunkY=static_cast<int>(cameraPos.y) / CHUNK_SIZE;
chunkZ=static_cast<int>(cameraPos.z) / CHUNK_SIZE;
std::cout << "Waiting for secondary threads to shut down" << std::endl; // Use time in float to be consistent with glfw
update_thread.join(); float currentTime = glfwGetTime();
std::cout << "Update thread has terminated" << std::endl;
gen_thread.join();
std::cout << "Generation thread has terminated" << std::endl;
mesh_thread.join();
std::cout << "Meshing thread has terminated" << std::endl;
}
void destroy(){ // Check for far chunks that need to be cleaned up from memory
int nUnloaded{0};
for(const auto& n : chunks){ for(const auto& n : chunks){
delete n.second; 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));
} }
chunks.clear(); 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
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++)
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);
//std::cout << "Chunks to mesh: " << to_mesh.size() << "\n";
//std::cout << "Chunks to generate: " << to_generate.size() << "\n";
//std::cout << "Total chunks to draw: " << total << ". Sent to GPU: " << toGpu << "\n";
//total = 0;
//toGpu = 0;
// 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);
glm::vec3 ray_intersect(glm::vec3 startposition, glm::vec3 startdir){ if (!(c->mutex_state.try_lock()))
int old_bx{0}, old_by{0}, old_bz{0}; return;
int old_px{0}, old_py{0}, old_pz{0};
Chunk::Chunk* old_chunk{nullptr};
glm::vec3 old_pos;
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);
// Frustum Culling of chunk
total++;
glm::vec3 chunk = c->getPosition();
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);
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
// TODO (?) implement frustum culling as per (Inigo Quilez)[https://iquilezles.org/articles/frustumcorrect/], and check each
// 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)
{
toGpu++;
chunkmesher::draw(c, model);
}
}
}
c->mutex_state.unlock();
}
}
void blockpick(bool place){
// cast a ray from the camera in the direction pointed by the camera itself // cast a ray from the camera in the direction pointed by the camera itself
glm::vec3 origin = startposition; glm::vec3 pos = cameraPos;
glm::vec3 pos = origin;
glm::vec3 front = startdir;
for(float t = 0.0; t <= 10.0; t += 0.5){ for(float t = 0.0; t <= 10.0; t += 0.5){
// traverse the ray a block at the time // traverse the ray a block at the time
pos = origin + t*front; pos = theCamera.getPos() + t * theCamera.getFront();
// get which chunk and block the ray is at // get which chunk and block the ray is at
int px = ((int)(pos.x))/CHUNK_SIZE; int px = ((int)(pos.x))/CHUNK_SIZE;
int py = ((int)(pos.y))/CHUNK_SIZE; int py = ((int)(pos.y))/CHUNK_SIZE;
@ -306,219 +285,75 @@ namespace chunkmanager
int by = pos.y - py*CHUNK_SIZE; int by = pos.y - py*CHUNK_SIZE;
int bz = pos.z - pz*CHUNK_SIZE; int bz = pos.z - pz*CHUNK_SIZE;
if(bx == old_bx && by == old_by && bz == old_bz) continue;
// exit early if the position is invalid or the chunk does not exist // exit early if the position is invalid or the chunk does not exist
if(px < 0 || py < 0 || pz < 0 || px >= 1024 || py >= 1024 || pz >= 1024) continue; if(px < 0 || py < 0 || pz < 0) return;
if(chunks.find(calculateIndex(px, py, pz)) == chunks.end()) return;
ChunkTable::const_accessor a;
if(!chunks.find(a, Chunk::calculateIndex(px, py, pz))) continue;
Chunk::Chunk* c = a->second;
if(!c->isFree() || !c->getState(Chunk::CHUNK_STATE_GENERATED)){
a.release();
continue;
}
Chunk::Chunk* c = chunks.at(calculateIndex(px, py, pz));
Block b = c->getBlock(bx, by, bz); Block b = c->getBlock(bx, by, bz);
a.release();
// if the block is non empty // if the block is non empty
if(b != Block::AIR) return pos; if(b != Block::AIR){
old_chunk = c; // if placing a new block
old_bx = bx; if(place){
old_by = by; // Go half a block backwards on the ray, to check the block where the ray was
old_bz = bz; // coming from
old_px = px; // Doing this and not using normal adds the unexpected (and unwanted) ability to
old_py = py; // place blocks diagonally, without faces colliding with the block that has
old_pz = pz; // been clicked
old_pos = pos; 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
return glm::vec3(-1); if(px1 < 0 || py1 < 0 || pz1 < 0) return;
} if(chunks.find(calculateIndex(px1, py1, pz1)) == chunks.end()) return;
void blockpick(WorldUpdateMsg& msg){ Chunk::Chunk* c1 = chunks.at(calculateIndex(px1, py1, pz1));
//std::cout << glm::to_string(ray_intersect(msg.cameraPos, msg.cameraFront)) << std::endl; // place the new block (only stone for now)
glm::vec3 ray_pos = ray_intersect(msg.cameraPos, msg.cameraFront); c1->setBlock( Block::STONE, bx1, by1, bz1);
if(ray_pos == glm::vec3(-1)) return;
// Chunk in which the blockpick is happening // update the mesh of the chunk
int chunkx = (int)(ray_pos.x) / CHUNK_SIZE; chunkmesher::mesh(c1);
int chunky = (int)(ray_pos.y) / CHUNK_SIZE; // mark the mesh of the chunk the be updated on the gpu
int chunkz = (int)(ray_pos.z) / CHUNK_SIZE; c1->setState(Chunk::CHUNK_STATE_MESH_LOADED, false);
// Block (chunk coord) in which the blockpick is happening
int blockx = ray_pos.x - chunkx*CHUNK_SIZE;
int blocky = ray_pos.y - chunky*CHUNK_SIZE;
int blockz = ray_pos.z - chunkz*CHUNK_SIZE;
// The chunk must exist, otherwise ray_intersect would have returned an error
// Also, the block must be different from AIR
ChunkTable::accessor a;
if(!chunks.find(a, Chunk::calculateIndex(chunkx, chunky, chunkz))) return;
Chunk::Chunk* c = a->second;
if(!(c->isFree() && c->getState(Chunk::CHUNK_STATE_GENERATED))) return;
if(msg.msg_type == WorldUpdateMsgType::BLOCKPICK_BREAK){
c->setBlock(Block::AIR, blockx, blocky, blockz);
send_to_chunk_meshing_thread(c, MESHING_PRIORITY_PLAYER_EDIT);
}else{
// Traverse voxel using Amanatides&Woo traversal algorithm
// http://www.cse.yorku.ca/~amana/research/grid.pdf
glm::vec3 pos = msg.cameraPos;
glm::vec3 front = glm::normalize(pos - ray_pos);
// Original chunk in which the blockpick started
const int ochunkX=chunkx, ochunkY = chunky, ochunkZ = chunkz;
// The ray has equation pos + t*front
// Initialize phase
// Origin integer voxel coordinates
// Avoid floating point accuracy errors: work as close to 0 as possible, translate
// everything later
int basex = std::floor(ray_pos.x);
int basey = std::floor(ray_pos.y);
int basez = std::floor(ray_pos.z);
double x = ray_pos.x - basex;
double y = ray_pos.y - basey;
double z = ray_pos.z - basez;
auto sign = [=](double f){ return f > 0 ? 1 : f < 0 ? -1 : 0; };
auto tmax = [=](double p, double dir){
int s = sign(dir);
if(s > 0)
return (1 - p) / dir;
else if(s < 0)
return -(p) / dir;
return 0.0;
};
// Step
int stepX = sign(front.x);
int stepY = sign(front.y);
int stepZ = sign(front.z);
// tMax: the value of t at which the ray crosses the first voxel boundary
double tMaxX = tmax(x, front.x);
double tMaxY = tmax(y, front.y);
double tMaxZ = tmax(z, front.z);
// tDelta: how far along the ray along they ray (in units of t) for the _ component of such
// a movement to equal the width of a voxel
double tDeltaX = stepX / front.x;
double tDeltaY = stepY / front.y;
double tDeltaZ = stepZ / front.z;
for(int i = 0; i < 10; i++){
if(tMaxX < tMaxY){
if(tMaxX < tMaxZ) {
x += stepX;
tMaxX += tDeltaX;
}else{
z += stepZ;
tMaxZ += tDeltaZ;
}
}else{ }else{
if(tMaxY < tMaxZ){ // replace the current block with air to remove it
y += stepY; c->setBlock( Block::AIR, bx, by, bz);
tMaxY += tDeltaY;
}else{ // update the mesh of the chunk
z += stepZ; chunkmesher::mesh(c);
tMaxZ += tDeltaZ; // mark the mesh of the chunk the be updated on the gpu
} c->setState(Chunk::CHUNK_STATE_MESH_LOADED, false);
} }
int realx = basex + x;
int realy = basey + y;
int realz = basez + z;
chunkx = realx / CHUNK_SIZE;
chunky = realy / CHUNK_SIZE;
chunkz = realz / CHUNK_SIZE;
if(chunkx < 0 || chunky < 0 || chunkz < 0 || chunkx > 1023 || chunky > 1023 ||
chunkz > 1023) continue;
blockx = realx - chunkx*CHUNK_SIZE;
blocky = realy - chunky*CHUNK_SIZE;
blockz = realz - chunkz*CHUNK_SIZE;
Chunk::Chunk* chunk;
ChunkTable::accessor b;
if(chunkx != ochunkX || chunky != ochunkY || chunkz != ochunkZ){
if(!chunks.find(b, Chunk::calculateIndex(chunkx, chunky, chunkz)))
continue;
chunk = b->second;
if(!(chunk->isFree() && chunk->getState(Chunk::CHUNK_STATE_GENERATED)))
continue;
}else{
chunk = c;
}
if(chunk->getBlock(blockx, blocky, blockz) != Block::AIR) continue;
chunk->setBlock(msg.block, blockx, blocky, blockz);
send_to_chunk_meshing_thread(chunk, MESHING_PRIORITY_PLAYER_EDIT);
break; break;
} }
} }
// Release the chunk in which the blockpick started to avoid locks
a.release();
// When necessary, also mesh nearby chunks
ChunkTable::accessor a1, a2, b1, b2, c1, c2;
if(blockx == 0 && chunkx - 1 >= 0 && chunks.find(a1, Chunk::calculateIndex(chunkx - 1, chunky, chunkz)))
send_to_chunk_meshing_thread(a1->second, MESHING_PRIORITY_PLAYER_EDIT);
if(blocky == 0 && chunky - 1 >= 0 && chunks.find(b1, Chunk::calculateIndex(chunkx, chunky - 1, chunkz)))
send_to_chunk_meshing_thread(b1->second, MESHING_PRIORITY_PLAYER_EDIT);
if(blockz == 0 && chunkz - 1 >= 0 && chunks.find(c1, Chunk::calculateIndex(chunkx, chunky, chunkz - 1)))
send_to_chunk_meshing_thread(c1->second, MESHING_PRIORITY_PLAYER_EDIT);
if(blockx == CHUNK_SIZE - 1 && chunkx +1 < 1024 && chunks.find(a2, Chunk::calculateIndex(chunkx +1, chunky, chunkz)))
send_to_chunk_meshing_thread(a2->second, MESHING_PRIORITY_PLAYER_EDIT);
if(blocky == CHUNK_SIZE - 1 && chunky +1 < 1024 && chunks.find(b2, Chunk::calculateIndex(chunkx, chunky +1, chunkz)))
send_to_chunk_meshing_thread(b2->second, MESHING_PRIORITY_PLAYER_EDIT);
if(blockz == CHUNK_SIZE - 1 && chunkz +1 < 1024 && chunks.find(c2, Chunk::calculateIndex(chunkx, chunky, chunkz +1)))
send_to_chunk_meshing_thread(c2->second, MESHING_PRIORITY_PLAYER_EDIT);
// Update debugging information
debug::window::set_parameter("block_last_action", msg.msg_type ==
WorldUpdateMsgType::BLOCKPICK_PLACE);
debug::window::set_parameter("block_last_action_block_type", (int)(msg.msg_type ==
WorldUpdateMsgType::BLOCKPICK_PLACE ? msg.block : Block::AIR));
debug::window::set_parameter("block_last_action_x", chunkx*CHUNK_SIZE+blockx);
debug::window::set_parameter("block_last_action_y", chunky*CHUNK_SIZE+blocky);
debug::window::set_parameter("block_last_action_z", chunkz*CHUNK_SIZE+blockz);
} }
Block getBlockAtPos(int x, int y, int z){ // uint32_t is fine, since i'm limiting the coordinate to only use up to ten bits (1024). There's actually two spare bits
if(x < 0 || y < 0 || z < 0) return Block::NULLBLK; uint32_t calculateIndex(uint16_t i, uint16_t j, uint16_t k){
return i | (j << 10) | (k << 20);
int cx = static_cast<int>(x / CHUNK_SIZE);
int cy = static_cast<int>(y / CHUNK_SIZE);
int cz = static_cast<int>(z / CHUNK_SIZE);
if(cx < 0 || cy < 0 || cz < 0 || cx > 1023 || cy > 1023 || cz > 1023) return Block::NULLBLK;
//std::cout << "Block at " << x << ", " << y << ", " << z << " is in chunk " << cx << "," << cy << "," << cz << "\n";
ChunkTable::accessor a;
if(!chunks.find(a, Chunk::calculateIndex(cx, cy, cz))) return Block::NULLBLK;
else {
int bx = x % CHUNK_SIZE;
int by = y % CHUNK_SIZE;
int bz = z % CHUNK_SIZE;
Block b = a->second->getBlock(bx, by, bz);
//std::cout << "Block is at " << bx << "," << by << "," << bz << "(" << (int)b << ")\n";
return b;
}
} }
void destroy()
{
for (auto &n : chunks)
delete n.second;
}
void stopGenThread(){
generate_should_run = false;
}
void stopMeshThread(){
mesh_should_run = false;
}
}; };

View File

@ -1,35 +1,21 @@
#include "chunkmesher.hpp"
#include <array>
#include <memory>
#include "block.hpp" #include "block.hpp"
#include "chunk.hpp" #include "chunk.hpp"
#include "chunkmanager.hpp" #include "chunkmesher.hpp"
#include "globals.hpp" #include "globals.hpp"
#include "renderer.hpp"
#include "spacefilling.hpp" #include "spacefilling.hpp"
#include "utils.hpp" #include "utils.hpp"
#define CHUNK_MESH_DATA_QUANTITY 100 #include <unordered_map>
#define CHUNK_MESH_WORLD_LIMIT_BORDERS 0 #include <memory>
#include <tuple>
namespace chunkmesher{ namespace chunkmesher{
ChunkMeshDataQueue MeshDataQueue;
ChunkMeshDataQueue& getMeshDataQueue(){ return MeshDataQueue; }
void init()
{
for(int i = 0; i < CHUNK_MESH_DATA_QUANTITY; i++)
MeshDataQueue.push(new ChunkMeshData{});
}
int indexCount{0};
void mesh(Chunk::Chunk* chunk) void mesh(Chunk::Chunk* chunk)
{ {
ChunkMeshData* mesh_data;
if(!MeshDataQueue.try_pop(mesh_data)) return;
/* /*
* Taking inspiration from 0fps and the jme3 porting at * Taking inspiration from 0fps and the jme3 porting at
@ -47,27 +33,30 @@ void mesh(Chunk::Chunk* chunk)
*/ */
// Cleanup previous data // Cleanup previous data
mesh_data->clear(); chunk->vertices_map.clear();
mesh_data->message_type = ChunkMeshDataType::MESH_UPDATE; chunk->index_to_vertex.clear();
mesh_data->index = chunk->getIndex(); indexCount = 0;
mesh_data->position = chunk->getPosition(); chunk->vertices.clear();
chunk->indices.clear();
chunk->colors.clear();
chunk->nIndices = 0;
// Abort if chunk is empty
if(chunk->getState(Chunk::CHUNK_STATE_EMPTY)) 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; std::unique_ptr<Block[]> blocks = chunk->getBlocksArray(&length);
if(length == 0) {
return;
}
int k, l, u, v, w, h, n, j, i; int k, l, u, v, w, h, n, j, i;
int x[]{0, 0, 0}; int x[]{0, 0, 0};
int q[]{0, 0, 0}; int q[]{0, 0, 0};
int du[]{0, 0, 0}; int du[]{0, 0, 0};
int dv[]{0, 0, 0}; int dv[]{0, 0, 0};
// Abort if chunk is empty
if(chunk->getState(Chunk::CHUNK_STATE_EMPTY)) goto end;
blocks = chunk->getBlocksArray(&length);
if(length == 0) goto end;
std::array<Block, CHUNK_SIZE * CHUNK_SIZE> mask; std::array<Block, CHUNK_SIZE * CHUNK_SIZE> mask;
for (bool backFace = true, b = false; b != backFace; backFace = backFace && b, b = !b) for (bool backFace = true, b = false; b != backFace; backFace = backFace && b, b = !b)
{ {
@ -97,48 +86,25 @@ void mesh(Chunk::Chunk* chunk)
{ {
for (x[u] = 0; x[u] < CHUNK_SIZE; x[u]++) for (x[u] = 0; x[u] < CHUNK_SIZE; x[u]++)
{ {
Block b1, b2; Block b1 = (x[dim] >= 0) ? blocks[HILBERT_XYZ_ENCODE[x[0]][x[1]][x[2]]] : Block::NULLBLK;
if(x[dim] >= 0) b1 = blocks[HILBERT_XYZ_ENCODE[x[0]][x[1]][x[2]]]; Block b2 = (x[dim] < CHUNK_SIZE - 1)
else{ ? blocks[HILBERT_XYZ_ENCODE[x[0] + q[0]][x[1] + q[1]][x[2] + q[2]]]
int cx = chunk->getPosition().x*CHUNK_SIZE; : Block::NULLBLK;
int cy = chunk->getPosition().y*CHUNK_SIZE;
int cz = chunk->getPosition().z*CHUNK_SIZE;
int bx = cx+x[0]; // This is the original line taken from rob's code, readapted (replace voxelFace
int by = cy+x[1]; // with b1 and b2).
int bz = cz+x[2]; // mask[n++] = ((voxelFace != Block::NULLBLK && voxelFace1 != Block::NULLBLK &&
// voxelFace.equals(voxelFace1))) ? Block::NULLBLK : backFace ? voxelFace1 : voxelFace;
b1 = chunkmanager::getBlockAtPos(bx, by, bz); // Additionally checking whether b1 and b2 are AIR or Block::NULLBLK allows face culling,
} // thus not rendering faces that cannot be seen
// Removing the control for Block::NULLBLK disables chunk borders, which is
if(x[dim] < CHUNK_SIZE - 1) b2 = blocks[HILBERT_XYZ_ENCODE[x[0] + q[0]][x[1] // not always wanted and needs further checking
+ q[1]][x[2] + q[2]]]; // This can be surely refactored in something that isn't such a big one-liner
else{ mask[n++] = b1 != Block::NULLBLK && b2 != Block::NULLBLK && b1 == b2 ? Block::NULLBLK
int cx = chunk->getPosition().x*CHUNK_SIZE; : backFace ? b1 == Block::AIR || b1 == Block::NULLBLK ? b2 : Block::NULLBLK
int cy = chunk->getPosition().y*CHUNK_SIZE; : b2 == Block::AIR || b2 == Block::NULLBLK ? b1
int cz = chunk->getPosition().z*CHUNK_SIZE; : Block::NULLBLK;
int bx = cx+x[0] + q[0];
int by = cy+x[1] + q[1];
int bz = cz+x[2] + q[2];
b2 = chunkmanager::getBlockAtPos(bx, by, bz);
}
// Compute the mask
// Checking if b1==b2 is needed to generate a single quad
// The else case provides face culling for adjacent solid faces
// Checking for NULLBLK avoids creating empty faces if nearby chunk was not
// yet generated
#if CHUNK_MESH_WORLD_LIMIT_BORDERS == 1
mask[n++] = b1 == b2 ? Block::NULLBLK
: backFace ? b1 == Block::NULLBLK || b1 == Block::AIR ? b2 : Block::NULLBLK
: b2 == Block::NULLBLK || b2 == Block::AIR ? b1 : Block::NULLBLK;
#else
mask[n++] = b1 == b2 ? Block::NULLBLK
: backFace ? b1 == Block::AIR ? b2 : Block::NULLBLK
: b2 == Block::AIR ? b1 : Block::NULLBLK;
#endif
} }
} }
@ -187,19 +153,13 @@ void mesh(Chunk::Chunk* chunk)
dv[2] = 0; dv[2] = 0;
dv[v] = h; dv[v] = h;
// bottom left quad(chunk, glm::vec3(x[0], x[1], x[2]),
mesh_data->vertices.push_back(x[0]); //bottomLeft.x glm::vec3(x[0] + du[0], x[1] + du[1], x[2] + du[2]),
mesh_data->vertices.push_back(x[1]); //bottomLeft.y glm::vec3(x[0] + du[0] + dv[0], x[1] + du[1] + dv[1],
mesh_data->vertices.push_back(x[2]); //bottomLeft.z x[2] + du[2] + dv[2]),
glm::vec3(x[0] + dv[0], x[1] + dv[1], x[2] + dv[2]),
// extents, use normals for now glm::vec3(backFace ? q[0] : -q[0], backFace ? q[1] : -q[1], backFace ? q[2] : -q[2] ),
mesh_data->extents.push_back(du[0] + dv[0]); mask[n], backFace);
mesh_data->extents.push_back(du[1] + dv[1]);
mesh_data->extents.push_back(du[2] + dv[2]);
mesh_data->texinfo.push_back(backFace ? 0.0 : 1.0);
mesh_data->texinfo.push_back((int)(mask[n]) - 2);
mesh_data->num_vertices++;
} }
for (l = 0; l < h; ++l) for (l = 0; l < h; ++l)
@ -227,9 +187,174 @@ void mesh(Chunk::Chunk* chunk)
} }
} }
} }
end:
chunk->setState(Chunk::CHUNK_STATE_MESHED, true);
renderer::getMeshDataQueue().push(mesh_data);
} }
void sendtogpu(Chunk::Chunk* chunk)
{
if (chunk->indices.size() > 0)
{
for(int i = 0; i < chunk->index_to_vertex.size(); i++){
glm::vec3 v = chunk->index_to_vertex[i];
auto t = chunk->vertices_map.at(v);;
glm::vec3 n = std::get<1>(t);
glm::vec3 c = std::get<2>(t);
chunk->vertices.push_back(v.x);
chunk->vertices.push_back(v.y);
chunk->vertices.push_back(v.z);
chunk->vertices.push_back(n.x);
chunk->vertices.push_back(n.y);
chunk->vertices.push_back(n.z);
chunk->colors.push_back(c.x);
chunk->colors.push_back(c.y);
chunk->colors.push_back(c.z);
}
// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
glBindVertexArray(chunk->VAO);
glBindBuffer(GL_ARRAY_BUFFER, chunk->VBO);
glBufferData(GL_ARRAY_BUFFER, chunk->vertices.size() * sizeof(GLfloat), &(chunk->vertices[0]), GL_STATIC_DRAW);
// position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void *)0);
glEnableVertexAttribArray(0);
// normal attribute
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void *)(3*
sizeof(float)));
glEnableVertexAttribArray(1);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, chunk->EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, chunk->indices.size() * sizeof(GLuint), &(chunk->indices[0]), GL_STATIC_DRAW);
// color attribute
glBindBuffer(GL_ARRAY_BUFFER, chunk->colorBuffer);
glBufferData(GL_ARRAY_BUFFER, chunk->colors.size() * sizeof(GLfloat), &(chunk->colors[0]), GL_STATIC_DRAW);
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0);
glBindVertexArray(0);
// save the number of indices of the mesh, it is needed later for drawing
chunk->nIndices = (GLuint)(chunk->indices.size());
// once data has been sent to the GPU, it can be cleared from system RAM
chunk->vertices_map.clear();
chunk->index_to_vertex.clear();
chunk->vertices.clear();
chunk->indices.clear();
chunk->colors.clear();
}
// mark the chunk mesh has loaded on GPU
chunk->setState(Chunk::CHUNK_STATE_MESH_LOADED, true);
}
void draw(Chunk::Chunk* chunk, glm::mat4 model)
{
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // wireframe mode
if(chunk->getState(Chunk::CHUNK_STATE_MESH_LOADED))
{
theShader->use();
theShader->setMat4("model", model);
theShader->setMat4("view", theCamera.getView());
theShader->setMat4("projection", theCamera.getProjection());
glBindVertexArray(chunk->VAO);
glDrawElements(GL_TRIANGLES, chunk->nIndices , GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
}
void quad(Chunk::Chunk* chunk, glm::vec3 bottomLeft, glm::vec3 topLeft, glm::vec3 topRight,
glm::vec3 bottomRight, glm::vec3 normal, Block block, bool backFace)
{
// ugly switch case for colors
glm::vec3 color = glm::vec3(0.0f);
switch (block)
{
case Block::STONE:
color = glm::vec3(0.588f);
break;
case Block::GRASS:
color = glm::vec3(0.05f, 0.725f, 0.0f);
break;
case Block::DIRT:
color = glm::vec3(0.152f, 0.056f, 0.056f);
break;
}
int ibottomLeft, ibottomRight, itopLeft, itopRight;
if(chunk->vertices_map.find(bottomLeft) == chunk->vertices_map.end()){
chunk->index_to_vertex.push_back(bottomLeft);
ibottomLeft = indexCount++;
chunk->vertices_map[bottomLeft] = std::make_tuple(ibottomLeft, normal, color);
}else{
auto vm = chunk->vertices_map[bottomLeft];
ibottomLeft = std::get<0>(vm);
chunk->vertices_map[bottomLeft] = std::make_tuple(std::get<0>(vm), std::get<1>(vm) + normal,
std::get<2>(vm) + color);
}
if(chunk->vertices_map.find(bottomRight) == chunk->vertices_map.end()){
chunk->index_to_vertex.push_back(bottomRight);
ibottomRight = indexCount++;
chunk->vertices_map[bottomRight] = std::make_tuple(ibottomRight, normal, color);
}else{
auto vm = chunk->vertices_map[bottomRight];
ibottomRight = std::get<0>(vm);
chunk->vertices_map[bottomRight] = std::make_tuple(std::get<0>(vm), std::get<1>(vm) + normal,
std::get<2>(vm) + color);
}
if(chunk->vertices_map.find(topLeft) == chunk->vertices_map.end()){
chunk->index_to_vertex.push_back(topLeft);
itopLeft = indexCount++;
chunk->vertices_map[topLeft] = std::make_tuple(itopLeft, normal, color);
}else{
auto vm = chunk->vertices_map[topLeft];
itopLeft = std::get<0>(vm);
chunk->vertices_map[topLeft] = std::make_tuple(std::get<0>(vm), std::get<1>(vm) + normal,
std::get<2>(vm) + color);
}
if(chunk->vertices_map.find(topRight) == chunk->vertices_map.end()){
chunk->index_to_vertex.push_back(topRight);
itopRight = indexCount++;
chunk->vertices_map[topRight] = std::make_tuple(itopRight, normal, color);
}else{
auto vm = chunk->vertices_map[topRight];
itopRight = std::get<0>(vm);
chunk->vertices_map[topRight] = std::make_tuple(std::get<0>(vm), std::get<1>(vm) + normal,
std::get<2>(vm) + color);
}
// bottomLeft, bottomRight, topLeft, topRight
if (backFace)
{
chunk->indices.push_back(itopLeft);
chunk->indices.push_back(ibottomLeft);
chunk->indices.push_back(ibottomRight);
chunk->indices.push_back(ibottomRight);
chunk->indices.push_back(itopRight);
chunk->indices.push_back(itopLeft);
}
else
{
chunk->indices.push_back(itopLeft);
chunk->indices.push_back(itopRight);
chunk->indices.push_back(ibottomRight);
chunk->indices.push_back(ibottomRight);
chunk->indices.push_back(ibottomLeft);
chunk->indices.push_back(itopLeft);
}
}
}; };

View File

@ -1,66 +0,0 @@
#include "controls.hpp"
#include "camera.hpp"
#include "chunkmanager.hpp"
#include "debugwindow.hpp"
#include "globals.hpp"
#include "renderer.hpp"
namespace controls{
/* Block picking */
int block_to_place{2};
float lastBlockPick=0.0;
bool blockpick = false;
/* Cursor */
bool cursor = false;
void init(){
debug::window::set_parameter("block_type_return", &block_to_place);
}
void update(GLFWwindow* window){
float current_time = glfwGetTime();
/* BlockPicking */
// Reset blockpicking if enough time has passed
if(current_time - lastBlockPick > BLOCKPICK_TIMEOUT) blockpick = false;
// Reset blockpicking if both mouse buttons are released
if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_RELEASE && glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2) == GLFW_RELEASE) blockpick = false;
// Process block picking if a mouse button is pressed
if((glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS ||
glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2 == GLFW_PRESS)) && !blockpick){
// Start timeout for next block pick action
blockpick = true;
lastBlockPick = current_time;
// Construct the message to send to chunkmanager
// WorldUpdateMsg is allocated on the stack
// unlike ChunkMeshData, the fields of WorldUpdateMsg are few and light, so there's no
// problem in passing them by value each time.
// It also has the advantage of having less memory to manage, since I'm not allocating
// anything on the heap
WorldUpdateMsg msg{};
msg.cameraPos = theCamera.getPos();
msg.cameraFront = theCamera.getFront();
msg.time = current_time;
msg.msg_type = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS ?
WorldUpdateMsgType::BLOCKPICK_PLACE : WorldUpdateMsgType::BLOCKPICK_BREAK;
msg.block = (Block)(block_to_place);
// Send to chunk manager
chunkmanager::getWorldUpdateQueue().push(msg);
}
/* SCREENSHOTS */
if(glfwGetKey(window, GLFW_KEY_F2) == GLFW_PRESS) renderer::saveScreenshot();
if(glfwGetKey(window, GLFW_KEY_F3) == GLFW_PRESS) renderer::saveScreenshot(true);
if(glfwGetKey(window, GLFW_KEY_M) == GLFW_PRESS) {
cursor = !cursor;
glfwSetInputMode(window, GLFW_CURSOR, cursor ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED);
}
}
};

View File

@ -1,136 +0,0 @@
#include "debugwindow.hpp"
#include <imgui/imgui.h>
#include <imgui/imgui_impl_opengl3.h>
#include <imgui/imgui_impl_glfw.h>
#include <imgui_stdlib.h>
#include <iostream>
#include <string>
#include <unordered_map>
namespace debug{
namespace window{
void show_debug_window();
constexpr int frametimes_array_size = 20;
float frametimes_array[frametimes_array_size]{};
std::unordered_map<std::string, std::any> parameters;
void init(GLFWwindow* window){
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
// Setup Platform/Renderer backends
ImGui_ImplGlfw_InitForOpenGL(window, true); // Second param install_callback=true will install GLFW callbacks and chain to existing ones.
ImGui_ImplOpenGL3_Init();
}
void prerender(){
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
//ImGui::ShowDemoWindow(); // Show demo window! :)
show_debug_window();
}
void render(){
// (Your code clears your framebuffer, renders your other stuff etc.)
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
// (Your code calls glfwSwapBuffers() etc.)
}
void destroy(){
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
}
void set_parameter(std::string key, std::any value){
parameters[key] = value;
}
void show_debug_window(){
ImGui::Begin("Debug Window");
ImGui::PushItemWidth(ImGui::GetFontSize() * -12);
try{
if (ImGui::CollapsingHeader("Frametimes")){
ImGui::Text("FPS: %d", std::any_cast<int>(parameters.at("fps")));
ImGui::Text("Frametime (ms): %f",
std::any_cast<float>(parameters.at("frametime"))*1000);
ImGui::Text("GPU: (%s) %s",
std::any_cast<const GLubyte*>(parameters.at("gpu_vendor")),
std::any_cast<const GLubyte*>(parameters.at("gpu_renderer")));
//ImGui::PlotLines("Frame Times", arr, IM_ARRAYSIZE(arr);
}
if(ImGui::CollapsingHeader("Player")){
ImGui::Text("X: %f, Y: %f, Z: %f",
std::any_cast<float>(parameters.at("px")),std::any_cast<float>(parameters.at("py")),std::any_cast<float>(parameters.at("pz")) );
ImGui::Text("X: %d, Y: %d, Z: %d (chunk)", std::any_cast<int>(parameters.at("cx")),std::any_cast<int>(parameters.at("cy")),std::any_cast<int>(parameters.at("cz")) );
ImGui::Text("Pointing in direction: %f, %f, %f",
std::any_cast<float>(parameters.at("lx")),std::any_cast<float>(parameters.at("ly")),std::any_cast<float>(parameters.at("lz")) );
ImGui::SliderInt("Crosshair type",
std::any_cast<int*>(parameters.at("crosshair_type_return")), 0, 1);
ImGui::SliderInt("Block to place",
std::any_cast<int*>(parameters.at("block_type_return")), 2, 6);
if(parameters.find("block_last_action") != parameters.end()){
ImGui::Text("Last Block action: %s",
std::any_cast<bool>(parameters.at("block_last_action")) ? "place" : "destroy");
ImGui::Text("Last Block action block type: %d",
std::any_cast<int>(parameters.at("block_last_action_block_type")));
ImGui::Text("Last Block action position: X: %d, Y: %d, Z: %d",
std::any_cast<int>(parameters.at("block_last_action_x")),std::any_cast<int>(parameters.at("block_last_action_y")),std::any_cast<int>(parameters.at("block_last_action_z")) );
}
}
if(ImGui::CollapsingHeader("Mesh")){
ImGui::Text("Total chunk meshed: %d",
std::any_cast<int>(parameters.at("render_chunks_total")));
ImGui::Text("Of which renderable (not empty): %d",
std::any_cast<int>(parameters.at("render_chunks_renderable")));
ImGui::Text("Chunks rendered: %d",
std::any_cast<int>(parameters.at("render_chunks_rendered")));
ImGui::Text("Frustum culled: %d",
std::any_cast<int>(parameters.at("render_chunks_culled")));
ImGui::Text("Total vertices in the scene: %d",
std::any_cast<int>(parameters.at("render_chunks_vertices")));
ImGui::Checkbox("Wireframe",
std::any_cast<bool*>(parameters.at("wireframe_return")));
}
if(ImGui::CollapsingHeader("Chunks")){
ImGui::Text("Total chunks present: %d",
std::any_cast<int>(parameters.at("update_chunks_total")));
ImGui::Text("Chunks generated: %d",
std::any_cast<int>(parameters.at("update_chunks_generated")));
ImGui::Text("Chunks meshed: %d",
std::any_cast<int>(parameters.at("update_chunks_meshed")));
ImGui::Text("Chunks actually freed from memory: %d",
std::any_cast<int>(parameters.at("update_chunks_freed")));
ImGui::Text("Chunks explored: %d",
std::any_cast<int>(parameters.at("update_chunks_explored")));
}
}catch(const std::bad_any_cast& e){
std::cout << e.what() << std::endl;
}catch(const std::out_of_range& e){
std::cout << e.what() << std::endl;
}
ImGui::End();
}
}
}

View File

@ -1,23 +1,25 @@
#include "main.hpp" #include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream> #include <iostream>
#include <thread> #include <thread>
#include "chunkmanager.hpp"
#include "main.hpp"
#include "spacefilling.hpp"
#include "shader.hpp"
#define GLOBALS_DEFINER #define GLOBALS_DEFINER
#include "globals.hpp" #include "globals.hpp"
#undef GLOBALS_DEFINER
#include "chunkmanager.hpp"
#include "controls.hpp"
#include "debugwindow.hpp"
#include "renderer.hpp"
#include "shader.hpp"
#include "spacefilling.hpp"
float deltaTime = 0.0f; // Time between current frame and last frame float deltaTime = 0.0f; // Time between current frame and last frame
float lastFrame = 0.0f; // Time of last frame float lastFrame = 0.0f; // Time of last frame
float lastFPSFrame = 0.0f; float lastFPSFrame = 0.0f;
int frames = 0; int frames = 0;
float lastBlockPick=0.0;
bool blockpick = false;
int main() int main()
{ {
@ -40,7 +42,6 @@ int main()
return -1; return -1;
} }
glfwMakeContextCurrent(window); glfwMakeContextCurrent(window);
glfwSwapInterval(0);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{ {
@ -52,23 +53,17 @@ int main()
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glfwSetCursorPosCallback(window, mouse_callback); glfwSetCursorPosCallback(window, mouse_callback);
//glEnable(GL_FRAMEBUFFER_SRGB); //gamma correction done in fragment shader glEnable(GL_DEPTH_TEST);
//glEnable(GL_CULL_FACE); //GL_BACK GL_CCW by default glEnable(GL_CULL_FACE); //GL_BACK GL_CCW by default
debug::window::set_parameter("gpu_vendor", glGetString(GL_VENDOR)); std::cout << "Using GPU: " << glGetString(GL_VENDOR) << " " << glGetString(GL_RENDERER) << "\n";
debug::window::set_parameter("gpu_renderer", glGetString(GL_RENDERER));
for(int i = 0; i < 360; i++){
sines[i] = sin(3.14 / 180 * i);
cosines[i] = cos(3.14 / 180 * i);
}
SpaceFilling::initLUT(); SpaceFilling::initLUT();
controls::init();
chunkmanager::init(); chunkmanager::init();
chunkmesher::init(); std::thread genThread = chunkmanager::initGenThread();
debug::window::init(window); std::thread meshThread = chunkmanager::initMeshThread();
renderer::init(window);
theShader = new Shader{"shaders/shader.vs", "shaders/shader.fs"};
while (!glfwWindowShouldClose(window)) while (!glfwWindowShouldClose(window))
{ {
@ -77,42 +72,30 @@ int main()
deltaTime = currentFrame - lastFrame; deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame; lastFrame = currentFrame;
debug::window::set_parameter("frametime", deltaTime);
// FPS Counter // FPS Counter
frames++; frames++;
if(currentFrame - lastFPSFrame >= 1.0f){ if(currentFrame - lastFPSFrame >= 1.0f){
//std::cout << "FPS: " << frames << " Frametime: " << deltaTime << std::endl; std::cout << "FPS: " << frames << " Frametime: " << deltaTime << std::endl;
debug::window::set_parameter("fps", frames);
frames = 0; frames = 0;
lastFPSFrame = currentFrame; lastFPSFrame = currentFrame;
} }
glClearColor(0.431f, 0.694f, 1.0f, 1.0f); glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Input handling
// Only close event is handles by main
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
// the rest of input processing is handled by controls.cpp
// Input processing // Input processing
controls::update(window); processInput(window);
// Camera // Camera
theCamera.update(window, deltaTime); theCamera.update(window, deltaTime);
debug::window::set_parameter("px", theCamera.getPos().x); theShader->setFloat("u_time", currentFrame);
debug::window::set_parameter("py", theCamera.getPos().y); theShader->setVec3("viewPos", theCamera.getPos());
debug::window::set_parameter("pz", theCamera.getPos().z);
debug::window::set_parameter("cx", (int)(theCamera.getPos().x / CHUNK_SIZE)); // Reset blockping timeout if 200ms have passed
debug::window::set_parameter("cy", (int)(theCamera.getPos().y / CHUNK_SIZE)); if(glfwGetTime() - lastBlockPick > 0.1) blockpick = false;
debug::window::set_parameter("cz", (int)(theCamera.getPos().z / CHUNK_SIZE));
debug::window::set_parameter("lx", theCamera.getFront().x);
debug::window::set_parameter("ly", theCamera.getFront().y);
debug::window::set_parameter("lz", theCamera.getFront().z);
// Render pass // ChunkManager
renderer::render(); chunkmanager::update(deltaTime);
// Swap buffers to avoid tearing // Swap buffers to avoid tearing
glfwSwapBuffers(window); glfwSwapBuffers(window);
@ -120,11 +103,15 @@ int main()
} }
// Stop threads and wait for them to finish // Stop threads and wait for them to finish
chunkmanager::stop(); chunkmanager::stopGenThread();
chunkmanager::stopMeshThread();
genThread.join();
meshThread.join();
// Cleanup allocated memory // Cleanup allocated memory
chunkmanager::destroy(); chunkmanager::destroy();
renderer::destroy(); delete theShader;
glfwTerminate(); glfwTerminate();
return 0; return 0;
@ -134,10 +121,31 @@ void framebuffer_size_callback(GLFWwindow *window, int width, int height)
{ {
glViewport(0, 0, width, height); glViewport(0, 0, width, height);
theCamera.viewPortCallBack(window, width, height); theCamera.viewPortCallBack(window, width, height);
renderer::framebuffer_size_callback(window, width, height);
} }
void mouse_callback(GLFWwindow *window, double xpos, double ypos) void mouse_callback(GLFWwindow *window, double xpos, double ypos)
{ {
theCamera.mouseCallback(window, xpos, ypos); theCamera.mouseCallback(window, xpos, ypos);
} }
void processInput(GLFWwindow *window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2) == GLFW_PRESS && !blockpick){
chunkmanager::blockpick(false);
blockpick=true;
lastBlockPick=glfwGetTime();
}
if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS && !blockpick){
chunkmanager::blockpick(true);
blockpick=true;
lastBlockPick=glfwGetTime();
}
// Reset blockpicking if enough time has passed
if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_RELEASE && glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2) == GLFW_RELEASE) blockpick = false;
}

View File

@ -1,344 +0,0 @@
#include "renderer.hpp"
#include <glm/ext.hpp>
#include <glm/gtx/string_cast.hpp>
#include <oneapi/tbb/concurrent_hash_map.h>
#include "chunkmanager.hpp"
#include "chunkmesher.hpp"
#include "debugwindow.hpp"
#include "globals.hpp"
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
namespace renderer{
typedef oneapi::tbb::concurrent_hash_map<chunk_index_t, RenderInfo*> RenderTable;
RenderTable ChunksToRender;
ChunkMeshDataQueue MeshDataQueue;
IndexQueue MeshDataToDelete;
Shader* theShader, *quadShader;
GLuint chunkTexture;
Shader* getRenderShader() { return theShader; }
ChunkMeshDataQueue& getMeshDataQueue(){ return MeshDataQueue; }
IndexQueue& getDeleteIndexQueue(){ return MeshDataToDelete; }
GLuint renderTexFrameBuffer, renderTex, renderTexDepthBuffer, quadVAO, quadVBO;
int screenWidth, screenHeight;
int crosshair_type{0};
bool wireframe{false};
void init(GLFWwindow* window){
// Setup rendering
// We will render the image to a texture, then display the texture on a quad that fills the
// entire screen.
// This makes it easy to capture screenshots or apply filters to the final image (e.g.
// over-impress HUD elements like a crosshair)
glfwGetWindowSize(window, &screenWidth, &screenHeight);
glGenFramebuffers(1, &renderTexFrameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, renderTexFrameBuffer);
// Depth buffer
glGenRenderbuffers(1, &renderTexDepthBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, renderTexDepthBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, screenWidth, screenHeight); //Support up to
//full-hd for now
// Attach it to the frame buffer
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
renderTexDepthBuffer);
// Create texture to render to
// The texture we're going to render to
glGenTextures(1, &renderTex);
glBindTexture(GL_TEXTURE_2D, renderTex);
glTexImage2D(GL_TEXTURE_2D, 0,GL_RGB, screenWidth, screenHeight, 0,GL_RGB, GL_UNSIGNED_BYTE, 0); // Support
// up to
// full-hd
// for now
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// Set the texture as a render attachment for the framebuffer
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, renderTex, 0);
// Create the quad to render the texture to
float vertices[] = {
-1.0f, -1.0f, 0.0f, 0.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
1.0f, 1.0f, 0.0f, 1.0f, 1.0f
};
glGenBuffers(1, &quadVBO);
glGenVertexArrays(1, &quadVAO);
glBindVertexArray(quadVAO);
glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void*)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void*)(3*sizeof(float)));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindVertexArray(0);
// Rendering of the world
// Create Shader
theShader = new Shader{"shaders/shader-texture.gs", "shaders/shader-texture.vs", "shaders/shader-texture.fs"};
quadShader = new Shader{nullptr, "shaders/shader-quad.vs", "shaders/shader-quad.fs"};
// Block textures
// Create 3d array texture
constexpr int layerCount = 5;
glGenTextures(1, &chunkTexture);
glBindTexture(GL_TEXTURE_2D_ARRAY, chunkTexture);
int width, height, nrChannels;
unsigned char *texels = stbi_load("textures/cobblestone.png", &width, &height, &nrChannels, 0);
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, width, height, layerCount, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, texels);
unsigned char *texels1 = stbi_load("textures/dirt.png", &width, &height, &nrChannels, 0);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 1, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, texels1);
unsigned char *texels2 = stbi_load("textures/grass_top.png", &width, &height, &nrChannels, 0);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 2, width, height, 1, GL_RGB, GL_UNSIGNED_BYTE, texels2);
unsigned char *texels3 = stbi_load("textures/wood.png", &width, &height, &nrChannels, 0);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 3, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, texels3);
unsigned char *texels4 = stbi_load("textures/leaves.png", &width, &height, &nrChannels, 0);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 4, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, texels4);
glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_WRAP_S,GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_WRAP_T,GL_REPEAT);
debug::window::set_parameter("crosshair_type_return", &crosshair_type);
debug::window::set_parameter("wireframe_return", &wireframe);
}
void render(){
// Bind the frame buffer to render to the texture
glBindFramebuffer(GL_FRAMEBUFFER, renderTexFrameBuffer);
glViewport(0, 0, screenWidth, screenHeight);
glEnable(GL_DEPTH_TEST);
if(wireframe) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
else glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
// Clear the screen
glClearColor(0.431f, 0.694f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/* UPDATE IMGUI */
debug::window::prerender();
/* RENDER THE WORLD TO TEXTURE */
int total{0}, toGpu{0}, oof{0}, vertices{0};
glm::vec4 frustumPlanes[6];
theCamera.getFrustumPlanes(frustumPlanes, true);
glm::vec3 cameraPos = theCamera.getPos();
glm::vec3 cameraChunkPos = cameraPos / static_cast<float>(CHUNK_SIZE);
theShader->use();
theShader->setVec3("viewPos", cameraPos);
/* Process incoming mesh data */
ChunkMeshData* m;
while(MeshDataQueue.try_pop(m)){
RenderTable::accessor a;
RenderInfo* render_info;
if(ChunksToRender.find(a, m->index)){
render_info = a->second;
render_info->position = m->position;
render_info->num_vertices = m->num_vertices;
// Always updated the mesh, even if it's empty
// This should solve the problem of having floating quads when destroying a block
// near chunk borders
send_chunk_to_gpu(m, render_info);
}else{
render_info = new RenderInfo();
render_info->index = m->index;
render_info->position = m->position;
render_info->num_vertices = m->num_vertices;
ChunksToRender.emplace(a, std::make_pair(render_info->index, render_info));
// Only send the mesh to the GPU if it's not empty
if(render_info->num_vertices > 0) send_chunk_to_gpu(m, render_info);
}
chunkmesher::getMeshDataQueue().push(m);
}
/* Process chunks to be removed */
chunk_index_t queue_index;
while(MeshDataToDelete.try_pop(queue_index)){
RenderTable::accessor a;
if(ChunksToRender.find(a, queue_index)){
RenderInfo* render_info = a->second;
render_info->deallocateBuffers();
delete render_info;
ChunksToRender.erase(a);
}
}
/* Render the chunks */
// parallel_for cannot be used since all the rendering needs to happen in a single thread
for(RenderTable::iterator i = ChunksToRender.begin(); i != ChunksToRender.end(); i++){
RenderInfo* render_info = i->second;
if(render_info->num_vertices > 0)
{
total++;
// Increase total vertex count
vertices += render_info->num_vertices;
// Perform frustum culling and eventually render
glm::vec3 chunk = render_info->position;
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);
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
// TODO (?) implement frustum culling as per (Inigo Quilez)[https://iquilezles.org/articles/frustumcorrect/], and check each
// 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)
{
theShader->setMat4("model", model);
theShader->setMat4("view", theCamera.getView());
theShader->setMat4("projection", theCamera.getProjection());
glBindVertexArray(render_info->VAO);
glDrawArrays(GL_POINTS, 0, render_info->num_vertices);
glBindVertexArray(0);
toGpu++;
}
}
}
debug::window::set_parameter("render_chunks_total", (int)(ChunksToRender.size()));
debug::window::set_parameter("render_chunks_rendered", toGpu);
debug::window::set_parameter("render_chunks_renderable", total);
debug::window::set_parameter("render_chunks_culled", total-toGpu);
debug::window::set_parameter("render_chunks_vertices", vertices);
/* DISPLAY TEXTURE ON A QUAD THAT FILLS THE SCREEN */
// Now to render the quad, with the texture on top
// Switch to the default frame buffer
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClearColor(0.431f, 0.694f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glBindVertexArray(quadVAO);
glDisable(GL_DEPTH_TEST);
glBindTexture(GL_TEXTURE_2D, renderTex);
quadShader->use();
quadShader->setInt("screenWidth", screenWidth);
quadShader->setInt("screenHeight", screenHeight);
quadShader->setInt("crosshairType", crosshair_type);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
debug::window::render();
}
void send_chunk_to_gpu(ChunkMeshData* mesh_data, RenderInfo* render_info)
{
if(!render_info->buffers_allocated) render_info->allocateBuffers();
// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
glBindVertexArray(render_info->VAO);
// TODO: change GL_STATIC_DRAW to the one that means "few redraws and further in between"
// position attribute
glBindBuffer(GL_ARRAY_BUFFER, render_info->VBO);
glBufferData(GL_ARRAY_BUFFER, mesh_data->vertices.size() * sizeof(GLfloat), &(mesh_data->vertices[0]), GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0);
glEnableVertexAttribArray(0);
// normal attribute
glBindBuffer(GL_ARRAY_BUFFER, render_info->extentsBuffer);
glBufferData(GL_ARRAY_BUFFER, mesh_data->extents.size() * sizeof(GLfloat), &(mesh_data->extents[0]), GL_STATIC_DRAW);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)(0));
glEnableVertexAttribArray(1);
// texcoords attribute
glBindBuffer(GL_ARRAY_BUFFER, render_info->texinfoBuffer);
glBufferData(GL_ARRAY_BUFFER, mesh_data->texinfo.size() * sizeof(GLfloat), &(mesh_data->texinfo[0]), GL_STATIC_DRAW);
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)0);
glBindVertexArray(0);
}
void framebuffer_size_callback(GLFWwindow *window, int width, int height){
resize_framebuffer(width, height);
}
void resize_framebuffer(int width, int height){
screenWidth = width;
screenHeight = height;
theCamera.viewPortCallBack(nullptr, width, height);
glBindFramebuffer(GL_FRAMEBUFFER, renderTexFrameBuffer);
glBindTexture(GL_TEXTURE_2D, renderTex);
glTexImage2D(GL_TEXTURE_2D, 0,GL_RGB, width, height, 0,GL_RGB, GL_UNSIGNED_BYTE, 0); // Support
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, renderTex, 0);
glBindRenderbuffer(GL_RENDERBUFFER, renderTexDepthBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height); //Support up to
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
renderTexDepthBuffer);
}
void saveScreenshot(bool forceFullHD){
int old_screenWidth = screenWidth;
int old_screenHeight = screenHeight;
if(forceFullHD){
resize_framebuffer(1920, 1080);
// Do a render pass
render();
}
// Bind the render frame buffer
glBindFramebuffer(GL_FRAMEBUFFER, renderTexFrameBuffer);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
// Save the framebuffer in a byte array
GLubyte data[screenWidth*screenHeight*3];
glReadPixels(0, 0, screenWidth, screenHeight, GL_RGB, GL_UNSIGNED_BYTE, data);
// Save the byte array onto a texture
stbi_flip_vertically_on_write(1);
stbi_write_png(forceFullHD ? "screenshot_fullhd.png" : "screenshot.png", screenWidth,
screenHeight, 3, data, screenWidth*3);
if(forceFullHD) resize_framebuffer(old_screenWidth, old_screenHeight);
}
void destroy(){
delete theShader;
delete quadShader;
}
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 633 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 B