Compare commits
56 Commits
better-gen
...
main
Author | SHA1 | Date |
---|---|---|
EmaMaker | fa3d36ec58 | |
EmaMaker | ebef608000 | |
EmaMaker | 0b294bb35b | |
EmaMaker | f3d8ffed54 | |
EmaMaker | 0560fd43c6 | |
EmaMaker | 62212a22ab | |
EmaMaker | 357f67aac1 | |
EmaMaker | 1b4cef8958 | |
EmaMaker | e4c6eb50f3 | |
EmaMaker | 9b5939d256 | |
EmaMaker | 8401a3dca7 | |
EmaMaker | 255460892d | |
EmaMaker | afdd622ec2 | |
EmaMaker | 93bc0e7066 | |
EmaMaker | 8544620899 | |
EmaMaker | a4f1e5fc1f | |
EmaMaker | 78b65894b7 | |
EmaMaker | 355da726f6 | |
EmaMaker | ba95db4678 | |
EmaMaker | 7c82a71dd0 | |
EmaMaker | 7786d4f04d | |
EmaMaker | 1822911845 | |
EmaMaker | d0ddf2256f | |
EmaMaker | 88abf21502 | |
EmaMaker | f4947d5f70 | |
EmaMaker | 4e7fadd2b9 | |
EmaMaker | d1b151f92f | |
EmaMaker | 44edf3e53a | |
EmaMaker | 3adb061057 | |
EmaMaker | 1a50d1fb84 | |
EmaMaker | 60bbc85682 | |
EmaMaker | 490f207e39 | |
EmaMaker | c6d00c4200 | |
EmaMaker | 2a57796ed2 | |
EmaMaker | ca043bac68 | |
EmaMaker | f6f4057109 | |
EmaMaker | 353ef37186 | |
EmaMaker | 880c634be0 | |
EmaMaker | 9ad0485a79 | |
EmaMaker | 50bc52a679 | |
EmaMaker | 07068d740a | |
EmaMaker | 52696f2dde | |
EmaMaker | 9d2d0c8772 | |
EmaMaker | f526e9b152 | |
EmaMaker | e7c4b2d56b | |
EmaMaker | a6a419fdff | |
EmaMaker | 3f61a6a753 | |
EmaMaker | 00a4b8e1e2 | |
EmaMaker | 4b723d58fa | |
EmaMaker | 1c0ee1315f | |
EmaMaker | 6670f3b41c | |
EmaMaker | 41275486a6 | |
EmaMaker | 3d0d8b7593 | |
EmaMaker | 8d160c3239 | |
EmaMaker | ca04afcc89 | |
EmaMaker | 8313ee97bc |
|
@ -9,3 +9,5 @@ gmon.out*
|
||||||
cscope*
|
cscope*
|
||||||
test.cpp
|
test.cpp
|
||||||
a.out
|
a.out
|
||||||
|
*screenshot*
|
||||||
|
imgui.ini
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "lib/imgui"]
|
||||||
|
path = lib/imgui
|
||||||
|
url = https://github.com/ocornut/imgui/
|
|
@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 3.2)
|
||||||
|
|
||||||
project(cmake-project-template)
|
project(cmake-project-template)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 11)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -O3")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O3")
|
||||||
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g")
|
#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})
|
||||||
|
|
|
@ -18,6 +18,10 @@ public:
|
||||||
|
|
||||||
// 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, 1200.0f);
|
||||||
|
|
||||||
|
posX = cameraPos.x;
|
||||||
|
posY = cameraPos.y;
|
||||||
|
posZ = cameraPos.z;
|
||||||
}
|
}
|
||||||
|
|
||||||
void update(GLFWwindow *window, float deltaTime)
|
void update(GLFWwindow *window, float deltaTime)
|
||||||
|
@ -112,7 +116,7 @@ public:
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
glm::vec3 cameraPos = glm::vec3(256.0, 80.0f, 256.0f);
|
glm::vec3 cameraPos = glm::vec3(512.0, 80.0f, 512.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);
|
||||||
|
|
|
@ -20,16 +20,27 @@
|
||||||
#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
|
||||||
{
|
{
|
||||||
|
|
||||||
constexpr uint8_t CHUNK_STATE_GENERATED = 1;
|
chunk_index_t calculateIndex(chunk_intcoord_t i, chunk_intcoord_t j, chunk_intcoord_t k);
|
||||||
constexpr uint8_t CHUNK_STATE_MESHED = 2;
|
chunk_index_t calculateIndex(glm::vec3 pos);
|
||||||
constexpr uint8_t CHUNK_STATE_MESH_LOADED = 4;
|
|
||||||
constexpr uint8_t CHUNK_STATE_LOADED = 8;
|
constexpr chunk_state_t CHUNK_STATE_GENERATED = 1;
|
||||||
constexpr uint8_t CHUNK_STATE_OUTOFVISION = 16;
|
constexpr chunk_state_t CHUNK_STATE_MESHED = 2;
|
||||||
constexpr uint8_t CHUNK_STATE_UNLOADED = 32;
|
constexpr chunk_state_t CHUNK_STATE_MESH_LOADED = 4;
|
||||||
constexpr uint8_t CHUNK_STATE_EMPTY = 64;
|
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);
|
||||||
|
|
||||||
|
@ -41,13 +52,15 @@ namespace Chunk
|
||||||
~Chunk();
|
~Chunk();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void createBuffers();
|
|
||||||
void deleteBuffers();
|
|
||||||
|
|
||||||
glm::vec3 getPosition() { return this->position; }
|
glm::vec3 getPosition() { return this->position; }
|
||||||
uint8_t getTotalState() { return this->state; }
|
void setState(chunk_state_t nstate, bool value);
|
||||||
bool getState(uint8_t n) { return (this->state & n) == n; }
|
bool getState(chunk_state_t n) { return (this->state & n) == n; }
|
||||||
void setState(uint8_t nstate, bool value);
|
bool isFree(){ return !(
|
||||||
|
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);
|
||||||
|
@ -56,14 +69,15 @@ 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:
|
||||||
GLuint VAO{0}, VBO{0}, extentsBuffer{0}, texinfoBuffer{0}, numVertices{0};
|
|
||||||
std::atomic<float> unload_timer{0};
|
std::atomic<float> unload_timer{0};
|
||||||
|
chunk_index_t getIndex(){ return this->index; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
glm::vec3 position{};
|
glm::vec3 position{};
|
||||||
IntervalMap<Block> blocks{};
|
IntervalMap<Block> blocks{};
|
||||||
|
|
||||||
std::atomic_uint8_t state{0};
|
std::atomic<chunk_state_t> state{0};
|
||||||
|
chunk_index_t index;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
#ifndef CHUNKMANAGER_H
|
#ifndef CHUNKMANAGER_H
|
||||||
#define CHUNKMANAGER_H
|
#define CHUNKMANAGER_H
|
||||||
|
|
||||||
#include "chunk.hpp"
|
|
||||||
|
|
||||||
#include <oneapi/tbb/concurrent_hash_map.h>
|
#include <oneapi/tbb/concurrent_hash_map.h>
|
||||||
#include <oneapi/tbb/concurrent_queue.h>
|
#include <oneapi/tbb/concurrent_queue.h>
|
||||||
#include <oneapi/tbb/concurrent_priority_queue.h>
|
#include <oneapi/tbb/concurrent_priority_queue.h>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
|
#include "chunk.hpp"
|
||||||
#include "globals.hpp"
|
#include "globals.hpp"
|
||||||
|
#include "worldupdatemessage.h"
|
||||||
|
|
||||||
// Seconds to be passed outside of render distance for a chunk to be destroyed
|
// Seconds to be passed outside of render distance for a chunk to be destroyed
|
||||||
#define UNLOAD_TIMEOUT 10
|
#define UNLOAD_TIMEOUT 10
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
namespace chunkmanager
|
namespace chunkmanager
|
||||||
{
|
{
|
||||||
typedef oneapi::tbb::concurrent_hash_map<uint32_t, Chunk::Chunk*> ChunkTable;
|
typedef oneapi::tbb::concurrent_hash_map<chunk_index_t, Chunk::Chunk*> ChunkTable;
|
||||||
typedef std::pair<Chunk::Chunk*, uint8_t> ChunkPQEntry;
|
typedef std::pair<Chunk::Chunk*, uint8_t> ChunkPQEntry;
|
||||||
// The comparing function to use
|
// The comparing function to use
|
||||||
struct compare_f {
|
struct compare_f {
|
||||||
|
@ -30,15 +30,12 @@ namespace chunkmanager
|
||||||
typedef oneapi::tbb::concurrent_priority_queue<ChunkPQEntry, compare_f> ChunkPriorityQueue;
|
typedef oneapi::tbb::concurrent_priority_queue<ChunkPQEntry, compare_f> ChunkPriorityQueue;
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
void blockpick(bool place);
|
void update();
|
||||||
uint32_t calculateIndex(uint16_t i, uint16_t j, uint16_t k);
|
|
||||||
|
|
||||||
void stop();
|
void stop();
|
||||||
void destroy();
|
void destroy();
|
||||||
oneapi::tbb::concurrent_queue<Chunk::Chunk*>& getDeleteVector();
|
WorldUpdateMsgQueue& getWorldUpdateQueue();
|
||||||
std::array<std::array<int, 3>, chunks_volume>& getChunksIndices();
|
std::array<std::array<chunk_intcoord_t, 3>, chunks_volume>& getChunksIndices();
|
||||||
Block getBlockAtPos(int x, int y, int z);
|
Block getBlockAtPos(int x, int y, int z);
|
||||||
void update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
#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
|
||||||
|
|
|
@ -5,10 +5,12 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
#include <oneapi/tbb/concurrent_queue.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"
|
||||||
|
|
||||||
|
@ -21,13 +23,12 @@ namespace chunkmesher{
|
||||||
std::vector<GLfloat> extents;
|
std::vector<GLfloat> extents;
|
||||||
std::vector<GLfloat> texinfo;
|
std::vector<GLfloat> texinfo;
|
||||||
};
|
};
|
||||||
oneapi::tbb::concurrent_queue<MeshData*>& getMeshDataQueue();
|
|
||||||
|
|
||||||
|
ChunkMeshDataQueue& getMeshDataQueue();
|
||||||
|
void init();
|
||||||
void mesh(Chunk::Chunk* chunk);
|
void mesh(Chunk::Chunk* chunk);
|
||||||
void sendtogpu(MeshData* mesh_data);
|
|
||||||
void quad(MeshData* mesh_data, glm::vec3 bottomLeft, glm::vec3 topLeft, glm::vec3 topRight,
|
|
||||||
glm::vec3 bottomRight, glm::vec3 normal, Block block, int dim, bool backFace);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
#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
|
|
@ -0,0 +1,19 @@
|
||||||
|
#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
|
|
@ -13,7 +13,8 @@
|
||||||
#define RENDER_DISTANCE 16
|
#define RENDER_DISTANCE 16
|
||||||
|
|
||||||
extr Camera theCamera;
|
extr Camera theCamera;
|
||||||
constexpr int chunks_volume = static_cast<int>(1.333333333333*M_PI*(RENDER_DISTANCE*RENDER_DISTANCE*RENDER_DISTANCE));
|
// the cube spans in both directions, to each axis has to be multiplied by 2. 2^3=8
|
||||||
|
constexpr int chunks_volume = 8*(RENDER_DISTANCE*RENDER_DISTANCE*RENDER_DISTANCE);
|
||||||
extr bool wireframe;
|
extr bool wireframe;
|
||||||
|
|
||||||
extr float sines[360];
|
extr float sines[360];
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
#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
|
||||||
|
|
|
@ -6,18 +6,56 @@
|
||||||
|
|
||||||
#include "chunk.hpp"
|
#include "chunk.hpp"
|
||||||
#include "chunkmesher.hpp"
|
#include "chunkmesher.hpp"
|
||||||
|
#include "chunkmeshdata.hpp"
|
||||||
#include "shader.hpp"
|
#include "shader.hpp"
|
||||||
|
|
||||||
namespace renderer{
|
namespace renderer{
|
||||||
typedef oneapi::tbb::concurrent_unordered_set<Chunk::Chunk*> RenderSet;
|
typedef struct RenderInfo {
|
||||||
|
chunk_index_t index;
|
||||||
|
int num_vertices;
|
||||||
|
glm::vec3 position;
|
||||||
|
bool buffers_allocated=false;
|
||||||
|
|
||||||
void init();
|
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 render();
|
||||||
|
void resize_framebuffer(int width, int height);
|
||||||
|
void framebuffer_size_callback(GLFWwindow *window, int width, int height);
|
||||||
void destroy();
|
void destroy();
|
||||||
|
|
||||||
|
void saveScreenshot(bool forceFullHD=false);
|
||||||
|
|
||||||
Shader* getRenderShader();
|
Shader* getRenderShader();
|
||||||
RenderSet& getChunksToRender();
|
ChunkMeshDataQueue& getMeshDataQueue();
|
||||||
oneapi::tbb::concurrent_queue<chunkmesher::MeshData*>& getMeshDataQueue();
|
IndexQueue& getDeleteIndexQueue();
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -34,60 +34,70 @@ public:
|
||||||
gShaderFile.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);
|
||||||
gShaderFile.open(geometryPath);
|
|
||||||
std::stringstream vShaderStream, fShaderStream, gShaderStream;
|
|
||||||
// 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();
|
||||||
gShaderStream << gShaderFile.rdbuf();
|
|
||||||
// close file handlers
|
// close file handlers
|
||||||
vShaderFile.close();
|
vShaderFile.close();
|
||||||
fShaderFile.close();
|
fShaderFile.close();
|
||||||
gShaderFile.close();
|
|
||||||
// convert stream into string
|
// convert stream into string
|
||||||
vertexCode = vShaderStream.str();
|
vertexCode = vShaderStream.str();
|
||||||
fragmentCode = fShaderStream.str();
|
fragmentCode = fShaderStream.str();
|
||||||
geometryCode = gShaderStream.str();
|
|
||||||
}
|
}
|
||||||
catch (std::ifstream::failure &e)
|
catch (std::ifstream::failure &e)
|
||||||
{
|
{
|
||||||
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ: " << e.what() << std::endl;
|
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ: " << e.what() << std::endl;
|
||||||
}
|
}
|
||||||
const char *vShaderCode = vertexCode.c_str();
|
// shader Program
|
||||||
const char *fShaderCode = fragmentCode.c_str();
|
ID = glCreateProgram();
|
||||||
const char *gShaderCode = geometryCode.c_str();
|
|
||||||
// 2. compile shaders
|
|
||||||
unsigned int vertex, fragment, geometry;
|
unsigned int vertex, fragment, geometry;
|
||||||
// vertex shader
|
|
||||||
vertex = glCreateShader(GL_VERTEX_SHADER);
|
|
||||||
glShaderSource(vertex, 1, &vShaderCode, NULL);
|
|
||||||
glCompileShader(vertex);
|
|
||||||
checkCompileErrors(vertex, "VERTEX");
|
|
||||||
// fragment Shader
|
|
||||||
fragment = glCreateShader(GL_FRAGMENT_SHADER);
|
|
||||||
glShaderSource(fragment, 1, &fShaderCode, NULL);
|
|
||||||
glCompileShader(fragment);
|
|
||||||
checkCompileErrors(fragment, "FRAGMENT");
|
|
||||||
// geometry shader
|
// geometry shader
|
||||||
|
if(geometryPath){
|
||||||
|
const char *gShaderCode = geometryCode.c_str();
|
||||||
geometry = glCreateShader(GL_GEOMETRY_SHADER);
|
geometry = glCreateShader(GL_GEOMETRY_SHADER);
|
||||||
glShaderSource(geometry, 1, &gShaderCode, NULL);
|
glShaderSource(geometry, 1, &gShaderCode, NULL);
|
||||||
glCompileShader(geometry);
|
glCompileShader(geometry);
|
||||||
checkCompileErrors(geometry, "GEOMETRY");
|
checkCompileErrors(geometry, "GEOMETRY");
|
||||||
|
|
||||||
// shader Program
|
|
||||||
ID = glCreateProgram();
|
|
||||||
glAttachShader(ID, vertex);
|
|
||||||
glAttachShader(ID, fragment);
|
|
||||||
glAttachShader(ID, geometry);
|
glAttachShader(ID, geometry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// vertex shader
|
||||||
|
const char *vShaderCode = vertexCode.c_str();
|
||||||
|
vertex = glCreateShader(GL_VERTEX_SHADER);
|
||||||
|
glShaderSource(vertex, 1, &vShaderCode, NULL);
|
||||||
|
glCompileShader(vertex);
|
||||||
|
checkCompileErrors(vertex, "VERTEX");
|
||||||
|
glAttachShader(ID, vertex);
|
||||||
|
|
||||||
|
// fragment Shader
|
||||||
|
const char *fShaderCode = fragmentCode.c_str();
|
||||||
|
fragment = glCreateShader(GL_FRAGMENT_SHADER);
|
||||||
|
glShaderSource(fragment, 1, &fShaderCode, NULL);
|
||||||
|
glCompileShader(fragment);
|
||||||
|
checkCompileErrors(fragment, "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);
|
||||||
glDeleteShader(geometry);
|
if(geometryPath) glDeleteShader(geometry);
|
||||||
}
|
}
|
||||||
// activate the shader
|
// activate the shader
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,24 @@
|
||||||
|
#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
|
|
@ -1,2 +1,3 @@
|
||||||
add_subdirectory(glad)
|
add_subdirectory(glad)
|
||||||
add_subdirectory(glm)
|
add_subdirectory(glm)
|
||||||
|
add_subdirectory(imgui)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 6addf28c4b5d8fd109a6db73bed6436952b230b2
|
|
@ -0,0 +1,28 @@
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
#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);
|
||||||
|
}
|
|
@ -26,29 +26,30 @@ void main(){
|
||||||
EmitVertex();
|
EmitVertex();
|
||||||
|
|
||||||
if(gs_in[0].Extents.x == 0){
|
if(gs_in[0].Extents.x == 0){
|
||||||
TexCoord = vec3(0.0, gs_in[0].Extents.z, gs_in[0].BlockType);
|
|
||||||
|
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);
|
gl_Position = gl_in[0].gl_Position + vec4(0.0, 0.0, gs_in[0].Extents.z, 0.0);
|
||||||
FragPos = vec3(gl_Position);
|
FragPos = vec3(gl_Position);
|
||||||
gl_Position = projection * view * gl_Position;
|
gl_Position = projection * view * gl_Position;
|
||||||
EmitVertex();
|
EmitVertex();
|
||||||
|
|
||||||
TexCoord = vec3(gs_in[0].Extents.y, 0.0, gs_in[0].BlockType);
|
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);
|
gl_Position = gl_in[0].gl_Position + vec4(0.0, gs_in[0].Extents.y, 0.0, 0.0);
|
||||||
FragPos = vec3(gl_Position);
|
FragPos = vec3(gl_Position);
|
||||||
gl_Position = projection * view * gl_Position;
|
gl_Position = projection * view * gl_Position;
|
||||||
EmitVertex();
|
EmitVertex();
|
||||||
|
|
||||||
TexCoord = vec3(gs_in[0].Extents.y, gs_in[0].Extents.z, gs_in[0].BlockType);
|
TexCoord = vec3(gs_in[0].Extents.z, gs_in[0].Extents.y, gs_in[0].BlockType);
|
||||||
}
|
}
|
||||||
else if(gs_in[0].Extents.y == 0){
|
else if(gs_in[0].Extents.y == 0){
|
||||||
TexCoord = vec3(0.0, gs_in[0].Extents.z, gs_in[0].BlockType);
|
TexCoord = vec3(gs_in[0].Extents.x, 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);
|
gl_Position = gl_in[0].gl_Position + vec4(gs_in[0].Extents.x, 0.0, 0.0, 0.0);
|
||||||
FragPos = vec3(gl_Position);
|
FragPos = vec3(gl_Position);
|
||||||
gl_Position = projection * view * gl_Position;
|
gl_Position = projection * view * gl_Position;
|
||||||
EmitVertex();
|
EmitVertex();
|
||||||
|
|
||||||
TexCoord = vec3(gs_in[0].Extents.x, 0.0, gs_in[0].BlockType);
|
TexCoord = vec3(0.0, gs_in[0].Extents.z, gs_in[0].BlockType);
|
||||||
gl_Position = gl_in[0].gl_Position + vec4(gs_in[0].Extents.x, 0.0, 0.0, 0.0);
|
gl_Position = gl_in[0].gl_Position + vec4(0.0, 0.0, gs_in[0].Extents.z, 0.0);
|
||||||
FragPos = vec3(gl_Position);
|
FragPos = vec3(gl_Position);
|
||||||
gl_Position = projection * view * gl_Position;
|
gl_Position = projection * view * gl_Position;
|
||||||
EmitVertex();
|
EmitVertex();
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
cmake_minimum_required(VERSION 3.2)
|
cmake_minimum_required(VERSION 3.2)
|
||||||
project(OpenGLTest)
|
project(OpenGLTest)
|
||||||
|
|
||||||
set(SOURCE_FILES main.cpp chunk.cpp chunkmanager.cpp chunkmesher.cpp chunkgenerator.cpp renderer.cpp spacefilling.cpp stb_image.cpp utils.cpp OpenSimplexNoise.cpp)
|
set(SOURCE_FILES main.cpp controls.cpp chunk.cpp chunkmanager.cpp chunkmesher.cpp chunkgenerator.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)
|
target_link_libraries(OpenGLTest glfw tbb glad glm imgui)
|
||||||
install(TARGETS OpenGLTest DESTINATION ${DIVISIBLE_INSTALL_BIN_DIR})
|
install(TARGETS OpenGLTest DESTINATION ${DIVISIBLE_INSTALL_BIN_DIR})
|
||||||
|
|
|
@ -15,32 +15,27 @@ 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->setBlocks(0, CHUNK_MAX_INDEX, Block::AIR);
|
||||||
|
this->index = calculateIndex(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
Chunk ::~Chunk()
|
Chunk ::~Chunk()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void Chunk::createBuffers(){
|
|
||||||
glGenVertexArrays(1, &(this->VAO));
|
|
||||||
glGenBuffers(1, &(this->VBO));
|
|
||||||
glGenBuffers(1, &(this->extentsBuffer));
|
|
||||||
glGenBuffers(1, &(this->texinfoBuffer));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Chunk::deleteBuffers(){
|
|
||||||
glDeleteBuffers(1, &(this->VBO));
|
|
||||||
glDeleteBuffers(1, &(this->extentsBuffer));
|
|
||||||
glDeleteBuffers(1, &(this->texinfoBuffer));
|
|
||||||
glDeleteVertexArrays(1, &(this->VAO));
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ||
|
if(x < 0 || y < 0 || z < 0 || x > CHUNK_SIZE -1 || y > CHUNK_SIZE -1 || z > CHUNK_SIZE-1 ||
|
||||||
|
@ -59,7 +54,7 @@ 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(uint8_t nstate, bool value)
|
void Chunk::setState(chunk_state_t nstate, bool value)
|
||||||
{
|
{
|
||||||
if (value)
|
if (value)
|
||||||
this->state.fetch_or(nstate);
|
this->state.fetch_or(nstate);
|
||||||
|
|
|
@ -6,80 +6,61 @@
|
||||||
#include <thread>
|
#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 <oneapi/tbb/parallel_for.h>
|
||||||
|
|
||||||
#include "block.hpp"
|
#include "block.hpp"
|
||||||
#include "chunk.hpp"
|
#include "chunk.hpp"
|
||||||
#include "chunkgenerator.hpp"
|
#include "chunkgenerator.hpp"
|
||||||
#include "chunkmesher.hpp"
|
#include "chunkmesher.hpp"
|
||||||
|
#include "debugwindow.hpp"
|
||||||
#include "globals.hpp"
|
#include "globals.hpp"
|
||||||
#include "renderer.hpp"
|
#include "renderer.hpp"
|
||||||
|
#include "utils.hpp"
|
||||||
|
|
||||||
namespace chunkmanager
|
namespace chunkmanager
|
||||||
{
|
{
|
||||||
|
void blockpick(WorldUpdateMsg& msg); // There's no need of passing by value again (check
|
||||||
|
// controls.cpp)
|
||||||
void generate();
|
void generate();
|
||||||
void mesh();
|
void mesh();
|
||||||
|
void send_to_chunk_meshing_thread(Chunk::Chunk* c, int priority);
|
||||||
|
|
||||||
|
/* Chunk holding data structures */
|
||||||
// Concurrent hash table of chunks
|
// Concurrent hash table of chunks
|
||||||
ChunkTable chunks;
|
ChunkTable chunks;
|
||||||
// Chunk indices. Centered at (0,0,0), going in concentric sphere outwards
|
// Chunk indices. Centered at (0,0,0), going in concentric sphere outwards
|
||||||
std::array<std::array<int, 3>, chunks_volume> chunks_indices;
|
std::array<std::array<chunk_intcoord_t, 3>, chunks_volume> chunks_indices;
|
||||||
|
|
||||||
|
/* World Update messaging data structure */
|
||||||
|
WorldUpdateMsgQueue WorldUpdateQueue;
|
||||||
|
|
||||||
/* Multithreading */
|
/* Multithreading */
|
||||||
std::atomic_bool should_run;
|
std::atomic_bool should_run;
|
||||||
std::thread gen_thread, mesh_thread, update_thread;
|
std::thread gen_thread, mesh_thread, update_thread;
|
||||||
|
|
||||||
// Queue of chunks to be generated
|
// Queue of chunks to be generated
|
||||||
ChunkPriorityQueue chunks_to_generate_queue;
|
ChunkPriorityQueue chunks_to_generate_queue;
|
||||||
// Queue of chunks to be meshed
|
// Queue of chunks to be meshed
|
||||||
ChunkPriorityQueue chunks_to_mesh_queue;
|
ChunkPriorityQueue chunks_to_mesh_queue;
|
||||||
|
|
||||||
|
WorldUpdateMsgQueue& getWorldUpdateQueue(){ return WorldUpdateQueue; }
|
||||||
|
|
||||||
// Init chunkmanager. Chunk indices and start threads
|
// Init chunkmanager. Chunk indices and start threads
|
||||||
int chunks_volume_real;
|
|
||||||
void init(){
|
void init(){
|
||||||
int index{0};
|
int index{0};
|
||||||
int rr{RENDER_DISTANCE * RENDER_DISTANCE};
|
|
||||||
|
|
||||||
int xp{0}, x{0};
|
for(chunk_intcoord_t i = -RENDER_DISTANCE; i < RENDER_DISTANCE; i++)
|
||||||
bool b = true;
|
for(chunk_intcoord_t j = -RENDER_DISTANCE; j < RENDER_DISTANCE; j++)
|
||||||
|
for(chunk_intcoord_t k = -RENDER_DISTANCE; k < RENDER_DISTANCE; k++){
|
||||||
|
|
||||||
// Iterate over all chunks, in concentric spheres starting fron the player and going outwards. Alternate left and right
|
chunks_indices[index][0]=i;
|
||||||
// Eq. of the sphere (x - a)² + (y - b)² + (z - c)² = r²
|
chunks_indices[index][1]=j;
|
||||||
while (xp <= RENDER_DISTANCE)
|
chunks_indices[index][2]=k;
|
||||||
{
|
|
||||||
// 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++;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!b)
|
|
||||||
{
|
|
||||||
xp++;
|
|
||||||
b = true;
|
|
||||||
}
|
|
||||||
else b = false;
|
|
||||||
}
|
|
||||||
chunks_volume_real = index;
|
|
||||||
|
|
||||||
// Also init mesh data queue
|
|
||||||
for(int i = 0; i < 10; i++)
|
|
||||||
chunkmesher::getMeshDataQueue().push(new chunkmesher::MeshData());
|
|
||||||
|
|
||||||
should_run = true;
|
should_run = true;
|
||||||
update_thread = std::thread(update);
|
update_thread = std::thread(update);
|
||||||
|
@ -91,9 +72,14 @@ namespace chunkmanager
|
||||||
void generate(){
|
void generate(){
|
||||||
while(should_run){
|
while(should_run){
|
||||||
ChunkPQEntry entry;
|
ChunkPQEntry entry;
|
||||||
while(chunks_to_generate_queue.try_pop(entry)) generateChunk(entry.first);
|
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)
|
// Method for chunk meshing thread(s)
|
||||||
void mesh(){
|
void mesh(){
|
||||||
|
@ -101,86 +87,216 @@ namespace chunkmanager
|
||||||
ChunkPQEntry entry;
|
ChunkPQEntry entry;
|
||||||
if(chunks_to_mesh_queue.try_pop(entry)){
|
if(chunks_to_mesh_queue.try_pop(entry)){
|
||||||
Chunk::Chunk* chunk = entry.first;
|
Chunk::Chunk* chunk = entry.first;
|
||||||
if(chunk->getState(Chunk::CHUNK_STATE_GENERATED)){
|
|
||||||
chunkmesher::mesh(chunk);
|
chunkmesher::mesh(chunk);
|
||||||
renderer::getChunksToRender().insert(chunk);
|
chunk->setState(Chunk::CHUNK_STATE_IN_MESHING_QUEUE, false);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
chunks_to_mesh_queue.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
oneapi::tbb::concurrent_queue<Chunk::Chunk*> chunks_todelete;
|
void send_to_chunk_meshing_thread(Chunk::Chunk* c, int priority){
|
||||||
int nUnloaded{0};
|
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(){
|
void update(){
|
||||||
while(should_run) {
|
while(should_run) {
|
||||||
int chunkX=static_cast<int>(theCamera.getAtomicPosX() / CHUNK_SIZE);
|
/* Setup variables for the whole loop */
|
||||||
int chunkY=static_cast<int>(theCamera.getAtomicPosY() / CHUNK_SIZE);
|
// Atomic is needed by parallel_for
|
||||||
int chunkZ=static_cast<int>(theCamera.getAtomicPosZ() / CHUNK_SIZE);
|
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);
|
||||||
|
|
||||||
// Update other chunks
|
/* Process update messages before anything happens */
|
||||||
for(int i = 0; i < chunks_volume_real; i++) {
|
WorldUpdateMsg msg;
|
||||||
const uint16_t x = chunks_indices[i][0] + chunkX;
|
while(WorldUpdateQueue.try_pop(msg)){
|
||||||
const uint16_t y = chunks_indices[i][1] + chunkY;
|
switch(msg.msg_type){
|
||||||
const uint16_t z = chunks_indices[i][2] + chunkZ;
|
case WorldUpdateMsgType::BLOCKPICK_BREAK:
|
||||||
const uint32_t index = calculateIndex(x, y, z);
|
case WorldUpdateMsgType::BLOCKPICK_PLACE:
|
||||||
|
blockpick(msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(x > 1023 || y > 1023 || z > 1023) continue;
|
|
||||||
|
|
||||||
|
/* 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;
|
ChunkTable::accessor a;
|
||||||
if(!chunks.find(a, index)) chunks.emplace(a, std::make_pair(index, new Chunk::Chunk(glm::vec3(x,y,z))));
|
while(chunks_todelete.try_pop(i)){
|
||||||
|
const chunk_index_t index = i;
|
||||||
if(! (a->second->getState(Chunk::CHUNK_STATE_GENERATED))) {
|
if(chunks.find(a, index)){
|
||||||
chunks_to_generate_queue.push(std::make_pair(a->second, GENERATION_PRIORITY_NORMAL));
|
Chunk::Chunk* c = a->second;
|
||||||
}else if(! (a->second->getState(Chunk::CHUNK_STATE_MESHED))){
|
// Use the accessor to erase the element
|
||||||
chunks_to_mesh_queue.push(std::make_pair(a->second, MESHING_PRIORITY_NORMAL));
|
// Using the key doesn't work
|
||||||
}
|
if(chunks.erase(a)){
|
||||||
|
|
||||||
a.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
Chunk::Chunk* n;
|
|
||||||
nUnloaded = 0;
|
|
||||||
while(chunks_todelete.try_pop(n)){
|
|
||||||
int x = static_cast<uint16_t>(n->getPosition().x);
|
|
||||||
int y = static_cast<uint16_t>(n->getPosition().y);
|
|
||||||
int z = static_cast<uint16_t>(n->getPosition().z);
|
|
||||||
if(x > 1023 || y > 1023 || z > 1023) continue;
|
|
||||||
const uint32_t index = calculateIndex(x, y, z);
|
|
||||||
|
|
||||||
chunks.erase(index);
|
|
||||||
//delete n;
|
|
||||||
nUnloaded++;
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// uint32_t is fine, since i'm limiting the coordinate to only use up to ten bits (1023). There's actually two spare bits
|
// Update atomic variables only once at the end
|
||||||
uint32_t calculateIndex(uint16_t i, uint16_t j, uint16_t k){
|
nGenerated += gen;
|
||||||
return i | (j << 10) | (k << 20);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
oneapi::tbb::concurrent_queue<Chunk::Chunk*>& getDeleteVector(){ return chunks_todelete; }
|
std::array<std::array<chunk_intcoord_t, 3>, chunks_volume>& getChunksIndices(){ return chunks_indices; }
|
||||||
std::array<std::array<int, 3>, chunks_volume>& getChunksIndices(){ return chunks_indices; }
|
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
should_run=false;
|
should_run=false;
|
||||||
|
|
||||||
|
std::cout << "Waiting for secondary threads to shut down" << std::endl;
|
||||||
update_thread.join();
|
update_thread.join();
|
||||||
|
std::cout << "Update thread has terminated" << std::endl;
|
||||||
gen_thread.join();
|
gen_thread.join();
|
||||||
|
std::cout << "Generation thread has terminated" << std::endl;
|
||||||
mesh_thread.join();
|
mesh_thread.join();
|
||||||
}
|
std::cout << "Meshing thread has terminated" << std::endl;
|
||||||
void destroy(){
|
|
||||||
/*for(const auto& n : chunks){
|
|
||||||
delete n.second;
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void blockpick(bool place){
|
void destroy(){
|
||||||
|
for(const auto& n : chunks){
|
||||||
|
delete n.second;
|
||||||
|
}
|
||||||
|
chunks.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
glm::vec3 ray_intersect(glm::vec3 startposition, glm::vec3 startdir){
|
||||||
|
int old_bx{0}, old_by{0}, old_bz{0};
|
||||||
|
int old_px{0}, old_py{0}, old_pz{0};
|
||||||
|
Chunk::Chunk* old_chunk{nullptr};
|
||||||
|
glm::vec3 old_pos;
|
||||||
|
|
||||||
// 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 pos = glm::vec3(theCamera.getAtomicPosX(), theCamera.getAtomicPosY(),
|
glm::vec3 origin = startposition;
|
||||||
theCamera.getAtomicPosZ());
|
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 = theCamera.getPos() + t * theCamera.getFront();
|
pos = origin + t*front;
|
||||||
|
|
||||||
// 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;
|
||||||
|
@ -190,56 +306,196 @@ 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 || px >= 1024 || py >= 1024 || pz >= 1024) continue;
|
||||||
|
|
||||||
ChunkTable::accessor a;
|
ChunkTable::const_accessor a;
|
||||||
if(!chunks.find(a, calculateIndex(px, py, pz))) continue;
|
if(!chunks.find(a, Chunk::calculateIndex(px, py, pz))) continue;
|
||||||
Chunk::Chunk* c = a->second;
|
Chunk::Chunk* c = a->second;
|
||||||
if(!c->getState(Chunk::CHUNK_STATE_GENERATED) || c->getState(Chunk::CHUNK_STATE_EMPTY)) continue;
|
if(!c->isFree() || !c->getState(Chunk::CHUNK_STATE_GENERATED)){
|
||||||
|
a.release();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
Block b = c->getBlock(bx, by, bz);
|
Block b = c->getBlock(bx, by, bz);
|
||||||
|
|
||||||
a.release();
|
a.release();
|
||||||
|
|
||||||
// if the block is non empty
|
// if the block is non empty
|
||||||
if(b != Block::AIR){
|
if(b != Block::AIR) return pos;
|
||||||
|
|
||||||
// if placing a new block
|
old_chunk = c;
|
||||||
if(place){
|
old_bx = bx;
|
||||||
// Go half a block backwards on the ray, to check the block where the ray was
|
old_by = by;
|
||||||
// coming from
|
old_bz = bz;
|
||||||
// Doing this and not using normal adds the unexpected (and unwanted) ability to
|
old_px = px;
|
||||||
// place blocks diagonally, without faces colliding with the block that has
|
old_py = py;
|
||||||
// been clicked
|
old_pz = pz;
|
||||||
pos -= theCamera.getFront()*0.5f;
|
old_pos = pos;
|
||||||
|
|
||||||
int px1 = ((int)(pos.x))/CHUNK_SIZE;
|
|
||||||
int py1 = ((int)(pos.y))/CHUNK_SIZE;
|
|
||||||
int pz1 = ((int)(pos.z))/CHUNK_SIZE;
|
|
||||||
int bx1 = pos.x - px1*CHUNK_SIZE;
|
|
||||||
int by1 = pos.y - py1*CHUNK_SIZE;
|
|
||||||
int bz1 = pos.z - pz1*CHUNK_SIZE;
|
|
||||||
|
|
||||||
// exit early if the position is invalid or the chunk does not exist
|
|
||||||
if(px1 < 0 || py1 < 0 || pz1 < 0 || px1 >= 1024 || py1 >= 1024 || pz1 >= 1024) return;
|
|
||||||
ChunkTable::accessor a1;
|
|
||||||
if(!chunks.find(a1, calculateIndex(px1, py1, pz1))) return;
|
|
||||||
Chunk::Chunk* c1 = a1->second;
|
|
||||||
// place the new block (only stone for now)
|
|
||||||
c1->setBlock( Block::STONE, bx1, by1, bz1);
|
|
||||||
|
|
||||||
// mark the mesh of the chunk the be updated
|
|
||||||
chunks_to_mesh_queue.push(std::make_pair(c1, MESHING_PRIORITY_PLAYER_EDIT));
|
|
||||||
chunks_to_mesh_queue.push(std::make_pair(c, MESHING_PRIORITY_PLAYER_EDIT));
|
|
||||||
}else{
|
|
||||||
// replace the current block with air to remove it
|
|
||||||
c->setBlock( Block::AIR, bx, by, bz);
|
|
||||||
|
|
||||||
chunks_to_mesh_queue.push(std::make_pair(c, MESHING_PRIORITY_PLAYER_EDIT));
|
|
||||||
}
|
}
|
||||||
|
return glm::vec3(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void blockpick(WorldUpdateMsg& msg){
|
||||||
|
//std::cout << glm::to_string(ray_intersect(msg.cameraPos, msg.cameraFront)) << std::endl;
|
||||||
|
glm::vec3 ray_pos = ray_intersect(msg.cameraPos, msg.cameraFront);
|
||||||
|
if(ray_pos == glm::vec3(-1)) return;
|
||||||
|
|
||||||
|
// Chunk in which the blockpick is happening
|
||||||
|
int chunkx = (int)(ray_pos.x) / CHUNK_SIZE;
|
||||||
|
int chunky = (int)(ray_pos.y) / CHUNK_SIZE;
|
||||||
|
int chunkz = (int)(ray_pos.z) / CHUNK_SIZE;
|
||||||
|
// 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{
|
||||||
|
if(tMaxY < tMaxZ){
|
||||||
|
y += stepY;
|
||||||
|
tMaxY += tDeltaY;
|
||||||
|
}else{
|
||||||
|
z += stepZ;
|
||||||
|
tMaxZ += tDeltaZ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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){
|
Block getBlockAtPos(int x, int y, int z){
|
||||||
|
@ -253,7 +509,7 @@ namespace chunkmanager
|
||||||
|
|
||||||
//std::cout << "Block at " << x << ", " << y << ", " << z << " is in chunk " << cx << "," << cy << "," << cz << "\n";
|
//std::cout << "Block at " << x << ", " << y << ", " << z << " is in chunk " << cx << "," << cy << "," << cz << "\n";
|
||||||
ChunkTable::accessor a;
|
ChunkTable::accessor a;
|
||||||
if(!chunks.find(a, calculateIndex(cx, cy, cz))) return Block::NULLBLK;
|
if(!chunks.find(a, Chunk::calculateIndex(cx, cy, cz))) return Block::NULLBLK;
|
||||||
else {
|
else {
|
||||||
int bx = x % CHUNK_SIZE;
|
int bx = x % CHUNK_SIZE;
|
||||||
int by = y % CHUNK_SIZE;
|
int by = y % CHUNK_SIZE;
|
||||||
|
@ -265,3 +521,4 @@ namespace chunkmanager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -11,15 +11,24 @@
|
||||||
#include "spacefilling.hpp"
|
#include "spacefilling.hpp"
|
||||||
#include "utils.hpp"
|
#include "utils.hpp"
|
||||||
|
|
||||||
|
#define CHUNK_MESH_DATA_QUANTITY 100
|
||||||
|
#define CHUNK_MESH_WORLD_LIMIT_BORDERS 0
|
||||||
|
|
||||||
namespace chunkmesher{
|
namespace chunkmesher{
|
||||||
|
|
||||||
oneapi::tbb::concurrent_queue<MeshData*> MeshDataQueue;
|
ChunkMeshDataQueue MeshDataQueue;
|
||||||
|
|
||||||
oneapi::tbb::concurrent_queue<MeshData*>& getMeshDataQueue(){ return MeshDataQueue; }
|
ChunkMeshDataQueue& getMeshDataQueue(){ return MeshDataQueue; }
|
||||||
|
|
||||||
|
void init()
|
||||||
|
{
|
||||||
|
for(int i = 0; i < CHUNK_MESH_DATA_QUANTITY; i++)
|
||||||
|
MeshDataQueue.push(new ChunkMeshData{});
|
||||||
|
}
|
||||||
|
|
||||||
void mesh(Chunk::Chunk* chunk)
|
void mesh(Chunk::Chunk* chunk)
|
||||||
{
|
{
|
||||||
MeshData* mesh_data;
|
ChunkMeshData* mesh_data;
|
||||||
if(!MeshDataQueue.try_pop(mesh_data)) return;
|
if(!MeshDataQueue.try_pop(mesh_data)) return;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -38,27 +47,14 @@ void mesh(Chunk::Chunk* chunk)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Cleanup previous data
|
// Cleanup previous data
|
||||||
mesh_data->numVertices = 0;
|
mesh_data->clear();
|
||||||
mesh_data->chunk = chunk;
|
mesh_data->message_type = ChunkMeshDataType::MESH_UPDATE;
|
||||||
mesh_data->vertices.clear();
|
mesh_data->index = chunk->getIndex();
|
||||||
mesh_data->extents.clear();
|
mesh_data->position = chunk->getPosition();
|
||||||
mesh_data->texinfo.clear();
|
|
||||||
|
|
||||||
// Abort if chunk is empty
|
|
||||||
if(chunk->getState(Chunk::CHUNK_STATE_EMPTY)){
|
|
||||||
chunk->setState(Chunk::CHUNK_STATE_MESHED, true);
|
|
||||||
renderer::getMeshDataQueue().push(mesh_data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert tree to array since it is easier to work with it
|
// convert tree to array since it is easier to work with it
|
||||||
int length{0};
|
int length{0};
|
||||||
std::unique_ptr<Block[]> blocks = chunk->getBlocksArray(&length);
|
std::unique_ptr<Block[]> blocks;
|
||||||
if(length == 0) {
|
|
||||||
chunk->setState(Chunk::CHUNK_STATE_MESHED, true);
|
|
||||||
renderer::getMeshDataQueue().push(mesh_data);
|
|
||||||
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};
|
||||||
|
@ -66,6 +62,12 @@ void mesh(Chunk::Chunk* chunk)
|
||||||
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)
|
||||||
{
|
{
|
||||||
|
@ -128,9 +130,15 @@ void mesh(Chunk::Chunk* chunk)
|
||||||
// The else case provides face culling for adjacent solid faces
|
// The else case provides face culling for adjacent solid faces
|
||||||
// Checking for NULLBLK avoids creating empty faces if nearby chunk was not
|
// Checking for NULLBLK avoids creating empty faces if nearby chunk was not
|
||||||
// yet generated
|
// yet generated
|
||||||
|
#if CHUNK_MESH_WORLD_LIMIT_BORDERS == 1
|
||||||
mask[n++] = b1 == b2 ? Block::NULLBLK
|
mask[n++] = b1 == b2 ? Block::NULLBLK
|
||||||
: backFace ? b1 == Block::NULLBLK || b1 == Block::AIR ? b2 : Block::NULLBLK
|
: backFace ? b1 == Block::NULLBLK || b1 == Block::AIR ? b2 : Block::NULLBLK
|
||||||
: b2 == Block::NULLBLK || b2 == Block::AIR ? b1 : 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,7 +199,7 @@ void mesh(Chunk::Chunk* chunk)
|
||||||
|
|
||||||
mesh_data->texinfo.push_back(backFace ? 0.0 : 1.0);
|
mesh_data->texinfo.push_back(backFace ? 0.0 : 1.0);
|
||||||
mesh_data->texinfo.push_back((int)(mask[n]) - 2);
|
mesh_data->texinfo.push_back((int)(mask[n]) - 2);
|
||||||
mesh_data->numVertices++;
|
mesh_data->num_vertices++;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (l = 0; l < h; ++l)
|
for (l = 0; l < h; ++l)
|
||||||
|
@ -220,49 +228,8 @@ void mesh(Chunk::Chunk* chunk)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
chunk->setState(Chunk::CHUNK_STATE_MESHED, true);
|
chunk->setState(Chunk::CHUNK_STATE_MESHED, true);
|
||||||
renderer::getMeshDataQueue().push(mesh_data);
|
renderer::getMeshDataQueue().push(mesh_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendtogpu(MeshData* mesh_data)
|
|
||||||
{
|
|
||||||
if (mesh_data->numVertices > 0)
|
|
||||||
{
|
|
||||||
if(mesh_data->chunk->VAO == 0) mesh_data->chunk->createBuffers();
|
|
||||||
|
|
||||||
// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
|
|
||||||
glBindVertexArray(mesh_data->chunk->VAO);
|
|
||||||
|
|
||||||
// position attribute
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, mesh_data->chunk->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, mesh_data->chunk->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, mesh_data->chunk->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);
|
|
||||||
|
|
||||||
// save the number of indices of the mesh, it is needed later for drawing
|
|
||||||
mesh_data->chunk->numVertices = (GLuint)(mesh_data->numVertices);
|
|
||||||
|
|
||||||
// once data has been sent to the GPU, it can be cleared from system RAM
|
|
||||||
mesh_data->vertices.clear();
|
|
||||||
mesh_data->extents.clear();
|
|
||||||
mesh_data->texinfo.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// mark the chunk mesh has loaded on GPU
|
|
||||||
mesh_data->chunk->setState(Chunk::CHUNK_STATE_MESH_LOADED, true);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,136 @@
|
||||||
|
#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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
src/main.cpp
77
src/main.cpp
|
@ -1,5 +1,4 @@
|
||||||
#include <glad/glad.h>
|
#include "main.hpp"
|
||||||
#include <GLFW/glfw3.h>
|
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
@ -7,22 +6,18 @@
|
||||||
#define GLOBALS_DEFINER
|
#define GLOBALS_DEFINER
|
||||||
#include "globals.hpp"
|
#include "globals.hpp"
|
||||||
#undef GLOBALS_DEFINER
|
#undef GLOBALS_DEFINER
|
||||||
|
|
||||||
#include "chunkmanager.hpp"
|
#include "chunkmanager.hpp"
|
||||||
#include "main.hpp"
|
#include "controls.hpp"
|
||||||
|
#include "debugwindow.hpp"
|
||||||
#include "renderer.hpp"
|
#include "renderer.hpp"
|
||||||
#include "spacefilling.hpp"
|
|
||||||
#include "shader.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;
|
|
||||||
bool canChangeWireframe = true;
|
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -57,20 +52,23 @@ 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_DEPTH_TEST);
|
|
||||||
//glEnable(GL_FRAMEBUFFER_SRGB); //gamma correction done in fragment shader
|
//glEnable(GL_FRAMEBUFFER_SRGB); //gamma correction done in fragment shader
|
||||||
//glEnable(GL_CULL_FACE); //GL_BACK GL_CCW by default
|
//glEnable(GL_CULL_FACE); //GL_BACK GL_CCW by default
|
||||||
|
|
||||||
std::cout << "Using GPU: " << glGetString(GL_VENDOR) << " " << glGetString(GL_RENDERER) << "\n";
|
debug::window::set_parameter("gpu_vendor", glGetString(GL_VENDOR));
|
||||||
|
debug::window::set_parameter("gpu_renderer", glGetString(GL_RENDERER));
|
||||||
|
|
||||||
wireframe = false;
|
|
||||||
for(int i = 0; i < 360; i++){
|
for(int i = 0; i < 360; i++){
|
||||||
sines[i] = sin(3.14 / 180 * i);
|
sines[i] = sin(3.14 / 180 * i);
|
||||||
cosines[i] = cos(3.14 / 180 * i);
|
cosines[i] = cos(3.14 / 180 * i);
|
||||||
}
|
}
|
||||||
|
|
||||||
SpaceFilling::initLUT();
|
SpaceFilling::initLUT();
|
||||||
|
controls::init();
|
||||||
chunkmanager::init();
|
chunkmanager::init();
|
||||||
renderer::init();
|
chunkmesher::init();
|
||||||
|
debug::window::init(window);
|
||||||
|
renderer::init(window);
|
||||||
|
|
||||||
while (!glfwWindowShouldClose(window))
|
while (!glfwWindowShouldClose(window))
|
||||||
{
|
{
|
||||||
|
@ -79,10 +77,12 @@ 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;
|
||||||
}
|
}
|
||||||
|
@ -90,14 +90,26 @@ int main()
|
||||||
glClearColor(0.431f, 0.694f, 1.0f, 1.0f);
|
glClearColor(0.431f, 0.694f, 1.0f, 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
|
||||||
processInput(window);
|
controls::update(window);
|
||||||
|
|
||||||
// Camera
|
// Camera
|
||||||
theCamera.update(window, deltaTime);
|
theCamera.update(window, deltaTime);
|
||||||
|
debug::window::set_parameter("px", theCamera.getPos().x);
|
||||||
// Reset blockping timeout if 200ms have passed
|
debug::window::set_parameter("py", theCamera.getPos().y);
|
||||||
if(glfwGetTime() - lastBlockPick > 0.1) blockpick = false;
|
debug::window::set_parameter("pz", theCamera.getPos().z);
|
||||||
|
debug::window::set_parameter("cx", (int)(theCamera.getPos().x / CHUNK_SIZE));
|
||||||
|
debug::window::set_parameter("cy", (int)(theCamera.getPos().y / CHUNK_SIZE));
|
||||||
|
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
|
// Render pass
|
||||||
renderer::render();
|
renderer::render();
|
||||||
|
@ -122,37 +134,10 @@ 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (glfwGetKey(window, GLFW_KEY_F) == GLFW_PRESS && canChangeWireframe){
|
|
||||||
wireframe = !wireframe;
|
|
||||||
canChangeWireframe = false;
|
|
||||||
}
|
|
||||||
if (glfwGetKey(window, GLFW_KEY_F) == GLFW_RELEASE) canChangeWireframe = true;
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
293
src/renderer.cpp
293
src/renderer.cpp
|
@ -1,30 +1,93 @@
|
||||||
#include "renderer.hpp"
|
#include "renderer.hpp"
|
||||||
|
|
||||||
#include <oneapi/tbb/concurrent_vector.h>
|
#include <glm/ext.hpp>
|
||||||
#include <oneapi/tbb/concurrent_queue.h>
|
#include <glm/gtx/string_cast.hpp>
|
||||||
|
#include <oneapi/tbb/concurrent_hash_map.h>
|
||||||
|
|
||||||
#include "chunkmanager.hpp"
|
#include "chunkmanager.hpp"
|
||||||
#include "chunkmesher.hpp"
|
#include "chunkmesher.hpp"
|
||||||
|
#include "debugwindow.hpp"
|
||||||
#include "globals.hpp"
|
#include "globals.hpp"
|
||||||
#include "stb_image.h"
|
#include "stb_image.h"
|
||||||
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||||
|
#include "stb_image_write.h"
|
||||||
|
|
||||||
namespace renderer{
|
namespace renderer{
|
||||||
RenderSet chunks_torender;
|
typedef oneapi::tbb::concurrent_hash_map<chunk_index_t, RenderInfo*> RenderTable;
|
||||||
oneapi::tbb::concurrent_vector<Chunk::Chunk*> render_todelete;
|
|
||||||
oneapi::tbb::concurrent_queue<chunkmesher::MeshData*> MeshDataQueue;
|
|
||||||
|
|
||||||
Shader* theShader;
|
RenderTable ChunksToRender;
|
||||||
|
ChunkMeshDataQueue MeshDataQueue;
|
||||||
|
IndexQueue MeshDataToDelete;
|
||||||
|
|
||||||
|
Shader* theShader, *quadShader;
|
||||||
GLuint chunkTexture;
|
GLuint chunkTexture;
|
||||||
|
|
||||||
Shader* getRenderShader() { return theShader; }
|
Shader* getRenderShader() { return theShader; }
|
||||||
RenderSet& getChunksToRender(){ return chunks_torender; }
|
ChunkMeshDataQueue& getMeshDataQueue(){ return MeshDataQueue; }
|
||||||
oneapi::tbb::concurrent_queue<chunkmesher::MeshData*>& getMeshDataQueue(){ return MeshDataQueue; }
|
IndexQueue& getDeleteIndexQueue(){ return MeshDataToDelete; }
|
||||||
|
|
||||||
|
GLuint renderTexFrameBuffer, renderTex, renderTexDepthBuffer, quadVAO, quadVBO;
|
||||||
|
int screenWidth, screenHeight;
|
||||||
|
|
||||||
void init(){
|
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
|
// Create Shader
|
||||||
theShader = new Shader{"shaders/shader-texture.gs", "shaders/shader-texture.vs", "shaders/shader-texture.fs"};
|
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
|
// Create 3d array texture
|
||||||
constexpr int layerCount = 5;
|
constexpr int layerCount = 5;
|
||||||
glGenTextures(1, &chunkTexture);
|
glGenTextures(1, &chunkTexture);
|
||||||
|
@ -47,10 +110,29 @@ namespace renderer{
|
||||||
glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_MAG_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_S,GL_REPEAT);
|
||||||
glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_WRAP_T,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(){
|
void render(){
|
||||||
int total{0}, toGpu{0};
|
// 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];
|
glm::vec4 frustumPlanes[6];
|
||||||
theCamera.getFrustumPlanes(frustumPlanes, true);
|
theCamera.getFrustumPlanes(frustumPlanes, true);
|
||||||
glm::vec3 cameraPos = theCamera.getPos();
|
glm::vec3 cameraPos = theCamera.getPos();
|
||||||
|
@ -59,26 +141,63 @@ namespace renderer{
|
||||||
theShader->use();
|
theShader->use();
|
||||||
theShader->setVec3("viewPos", cameraPos);
|
theShader->setVec3("viewPos", cameraPos);
|
||||||
|
|
||||||
chunkmesher::MeshData* m;
|
/* Process incoming mesh data */
|
||||||
|
ChunkMeshData* m;
|
||||||
while(MeshDataQueue.try_pop(m)){
|
while(MeshDataQueue.try_pop(m)){
|
||||||
chunkmesher::sendtogpu(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);
|
chunkmesher::getMeshDataQueue().push(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(wireframe) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
/* Process chunks to be removed */
|
||||||
else glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
chunk_index_t queue_index;
|
||||||
|
while(MeshDataToDelete.try_pop(queue_index)){
|
||||||
|
RenderTable::accessor a;
|
||||||
|
|
||||||
for(auto& c : chunks_torender){
|
if(ChunksToRender.find(a, queue_index)){
|
||||||
float dist = glm::distance(c->getPosition(), cameraChunkPos);
|
RenderInfo* render_info = a->second;
|
||||||
if(dist <= static_cast<float>(RENDER_DISTANCE)){
|
render_info->deallocateBuffers();
|
||||||
if(!c->getState(Chunk::CHUNK_STATE_MESH_LOADED)) continue;
|
delete render_info;
|
||||||
|
ChunksToRender.erase(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// reset out-of-vision and unload flags
|
/* Render the chunks */
|
||||||
c->setState(Chunk::CHUNK_STATE_OUTOFVISION, false);
|
// parallel_for cannot be used since all the rendering needs to happen in a single thread
|
||||||
c->setState(Chunk::CHUNK_STATE_UNLOADED, false);
|
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
|
// Perform frustum culling and eventually render
|
||||||
glm::vec3 chunk = c->getPosition();
|
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::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);
|
glm::mat4 model = glm::translate(glm::mat4(1.0), ((float)CHUNK_SIZE) * chunk);
|
||||||
|
|
||||||
|
@ -99,50 +218,126 @@ namespace renderer{
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!out)
|
if (!out)
|
||||||
{
|
|
||||||
if(c->numVertices > 0)
|
|
||||||
{
|
{
|
||||||
theShader->setMat4("model", model);
|
theShader->setMat4("model", model);
|
||||||
theShader->setMat4("view", theCamera.getView());
|
theShader->setMat4("view", theCamera.getView());
|
||||||
theShader->setMat4("projection", theCamera.getProjection());
|
theShader->setMat4("projection", theCamera.getProjection());
|
||||||
|
|
||||||
glBindVertexArray(c->VAO);
|
glBindVertexArray(render_info->VAO);
|
||||||
glDrawArrays(GL_POINTS, 0, c->numVertices);
|
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);
|
glBindVertexArray(0);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}else{
|
|
||||||
// When the chunk is outside render distance
|
|
||||||
|
|
||||||
if(c->getState(Chunk::CHUNK_STATE_OUTOFVISION)){
|
|
||||||
if(glfwGetTime() - c->unload_timer > UNLOAD_TIMEOUT){
|
void framebuffer_size_callback(GLFWwindow *window, int width, int height){
|
||||||
// If chunk was already out and enough time has passed
|
resize_framebuffer(width, height);
|
||||||
// Mark the chunk to be unloaded
|
|
||||||
// And mark is to be removed from the render set
|
|
||||||
render_todelete.push_back(c);
|
|
||||||
}
|
|
||||||
} else{
|
|
||||||
// Mark has out of vision and annotate when it started
|
|
||||||
c->setState(Chunk::CHUNK_STATE_OUTOFVISION, true);
|
|
||||||
c->setState(Chunk::CHUNK_STATE_UNLOADED, false);
|
|
||||||
c->unload_timer = glfwGetTime();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(auto& c : render_todelete){
|
void saveScreenshot(bool forceFullHD){
|
||||||
// we can get away with unsafe erase as access to the container is only done by this
|
int old_screenWidth = screenWidth;
|
||||||
// thread
|
int old_screenHeight = screenHeight;
|
||||||
c->deleteBuffers();
|
|
||||||
chunks_torender.unsafe_erase(c);
|
if(forceFullHD){
|
||||||
chunkmanager::getDeleteVector().push(c);
|
resize_framebuffer(1920, 1080);
|
||||||
|
// Do a render pass
|
||||||
|
render();
|
||||||
}
|
}
|
||||||
render_todelete.clear();
|
|
||||||
|
// 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(){
|
void destroy(){
|
||||||
delete theShader;
|
delete theShader;
|
||||||
|
delete quadShader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue