Compare commits

..

38 Commits

Author SHA1 Message Date
EmaMaker fa3d36ec58 shaders: fix rotated textures on x axis 2023-10-12 21:47:05 +02:00
EmaMaker ebef608000 blockpick: avoid blocks being placed diagonally 2023-10-12 21:47:00 +02:00
EmaMaker 0b294bb35b blockpick: cleanup blockpick place code 2023-10-09 21:55:31 +02:00
EmaMaker f3d8ffed54 blockpick: skip iteration if block has not changed 2023-10-04 23:37:04 +02:00
EmaMaker 0560fd43c6 print gpu in debug window 2023-10-04 20:58:06 +02:00
EmaMaker 62212a22ab main: do not terminate imgui, leads to crash 2023-10-04 14:59:31 +02:00
EmaMaker 357f67aac1 chunkmgr: debug info for chunkmgr 2023-10-04 14:59:17 +02:00
EmaMaker 1b4cef8958 Merge pull request 'Refactor Main/Update thread communication' (#14) from multithread-refactor into main
Reviewed-on: #14
2023-10-04 14:58:07 +02:00
EmaMaker e4c6eb50f3 Merge branch 'main' into multithread-refactor 2023-10-04 14:55:47 +02:00
EmaMaker 9b5939d256 renderer: do not send empty meshes to the gpu only at creation
I feel never sending empty meshes to the GPU is the cause of the bug causing floating quads near
chunk borders when a block is placed and then destroyed.
When destroying a block in a chunk, if nearby empty chunk meshes are not updated, the old mesh is
kept, which includes a quad at the border
2023-10-04 14:53:05 +02:00
EmaMaker 8401a3dca7 Merge branch 'main' into multithread-refactor 2023-10-04 14:51:56 +02:00
EmaMaker 255460892d blockpick control belongs to control, not chunkmgr 2023-10-04 14:41:44 +02:00
EmaMaker afdd622ec2 refactor message system between main and update threads
restores blockpicking with new multithread system
2023-10-04 14:35:29 +02:00
EmaMaker 93bc0e7066 renderer: do not send empty meshes to the gpu only at creation
I feel never sending empty meshes to the GPU is the cause of the bug causing floating quads near
chunk borders when a block is placed and then destroyed.
When destroying a block in a chunk, if nearby empty chunk meshes are not updated, the old mesh is
kept, which includes a quad at the border
2023-10-04 13:57:12 +02:00
EmaMaker 8544620899 Merge pull request 'Refactor Meshing/Rendering communication' (#13) from multithread-refactor into main
Reviewed-on: #13
2023-10-04 13:52:26 +02:00
EmaMaker a4f1e5fc1f Merge branch 'main' into multithread-refactor 2023-10-04 13:51:56 +02:00
EmaMaker 78b65894b7 chunkmesher: compiler flag to not show borders at world limit 2023-10-04 13:35:50 +02:00
EmaMaker 355da726f6 Merge pull request 'Refactor Secondary Threads' (#12) from multithread-refactor into main
Reviewed-on: #12
2023-10-04 13:32:23 +02:00
EmaMaker ba95db4678 renderer: also delete meshes of old chunks 2023-10-04 13:24:06 +02:00
EmaMaker 7c82a71dd0 chunk: vram buffers are managed by renderer now 2023-10-04 13:24:06 +02:00
EmaMaker 7786d4f04d renderer: use ChunkMeshData from rendering thread
This system decouples the Secondary threads from the Render thread. Once a chunk is meshed, only the mesh data is sent to the rendering thread, which does not use any direct reference to the chunk itself
2023-10-04 13:24:06 +02:00
EmaMaker 1822911845 chunkmesher: use goto for error handling 2023-10-04 13:24:06 +02:00
EmaMaker d0ddf2256f move chunkmeshdata into its own file
And let it be managed by chunkmesher instead of chunkmanager
2023-10-04 13:23:49 +02:00
EmaMaker 88abf21502 multithread: refactor update thread, communication between mesh/gen/upd threads
- Use parallel_for to iterate over all the stored chunks
- Only push a chunk to a queue if it is not already present, using chunk state bitfield (solves
	memory leak, continously adding new elements to a queue)
- Properly delete an element from chunks concurrent_hash_map using accessor, then free the memory
(solves memory leak: not being able to delete old elements and always adding new ones)

Breaks:

- Rendering (will be properly refactored in a future commit)
- Block picking (will be refactored in a future commit)
2023-10-04 13:10:05 +02:00
EmaMaker f4947d5f70 debugwindow: catch exception to avoid crash when missing parameters 2023-10-04 12:58:00 +02:00
EmaMaker 4e7fadd2b9 chunk: use chunk state to mark presence of chunk in thread communication queues 2023-10-04 12:57:57 +02:00
EmaMaker d1b151f92f chunk: typedef for chunk state 2023-10-04 12:57:57 +02:00
EmaMaker 44edf3e53a Merge pull request 'Misc fixes in preparation for multithreading refactor' (#10) from multithread-refactor into main
Reviewed-on: #10
2023-10-03 22:55:11 +02:00
EmaMaker 3adb061057 Merge branch 'main' into multithread-refactor 2023-10-03 22:53:42 +02:00
EmaMaker 1a50d1fb84 chunkmgr: span chunk indices in a cube around the player, not a sphere 2023-10-03 22:45:09 +02:00
EmaMaker 60bbc85682 chunk: store index in chunk itself 2023-10-03 22:45:09 +02:00
EmaMaker 490f207e39 chunk/mgr: calculate index belongs to chunk namespace 2023-10-03 22:45:09 +02:00
EmaMaker c6d00c4200 fix type mismatch in chunk index/coordinates
typedef an appropriate chunk_index_t and chunk_intcoord_t
2023-10-03 22:45:09 +02:00
EmaMaker 2a57796ed2 move input handling from main into dedicated file 2023-10-03 22:45:09 +02:00
EmaMaker ca043bac68 threads: allow for proper shutdown
using `if` instead of `while` avoids the need to wait for the queue to empty to shutdown the thread
2023-10-03 22:44:55 +02:00
EmaMaker f6f4057109 update player debug variables in main instead of chunkmgr 2023-10-03 22:09:03 +02:00
EmaMaker 353ef37186 renderer: perform frustum culling only if chunk has vertices 2023-10-03 22:08:40 +02:00
EmaMaker 880c634be0 camera: set atomic position at startup
this avoids the first few chunk update loops recognizing the camera as being positioned at (0,0,0), which in turns avoids wastefully generating chunks out of view at startup
2023-10-03 22:08:40 +02:00
23 changed files with 435 additions and 445 deletions

View File

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

View File

@ -8,9 +8,6 @@
#include <atomic>
#include "chunk.hpp"
#include "debugwindow.hpp"
class Camera
{
@ -55,16 +52,6 @@ public:
cameraFront = glm::normalize(direction);
view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
debug::window::set_parameter("px", cameraPos.x);
debug::window::set_parameter("py", cameraPos.y);
debug::window::set_parameter("pz", cameraPos.z);
debug::window::set_parameter("cx", (int)(cameraPos.x / CHUNK_SIZE));
debug::window::set_parameter("cy", (int)(cameraPos.y / CHUNK_SIZE));
debug::window::set_parameter("cz", (int)(cameraPos.z / CHUNK_SIZE));
debug::window::set_parameter("lx", cameraFront.x);
debug::window::set_parameter("ly", cameraFront.y);
debug::window::set_parameter("lz", cameraFront.z);
}
void viewPortCallBack(GLFWwindow *window, int width, int height)
@ -129,7 +116,7 @@ public:
private:
glm::vec3 cameraPos = glm::vec3(512.0, 256.0f, 512.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 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);
glm::vec3 direction = glm::vec3(0.0f);

View File

@ -20,38 +20,47 @@
#define CHUNK_VOLUME (CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE)
#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
{
constexpr uint16_t CHUNK_STATE_GENERATED = 1;
constexpr uint16_t CHUNK_STATE_MESHED = 2;
constexpr uint16_t CHUNK_STATE_OUTOFVISION = 4;
constexpr uint16_t CHUNK_STATE_UNLOADED = 8;
constexpr uint16_t CHUNK_STATE_EMPTY = 16;
constexpr uint16_t CHUNK_STATE_IN_GENERATION_QUEUE = 32;
constexpr uint16_t CHUNK_STATE_IN_MESHING_QUEUE = 64;
constexpr uint16_t CHUNK_STATE_IN_DELETING_QUEUE = 128;
chunk_index_t calculateIndex(chunk_intcoord_t i, chunk_intcoord_t j, chunk_intcoord_t k);
chunk_index_t calculateIndex(glm::vec3 pos);
constexpr chunk_state_t CHUNK_STATE_GENERATED = 1;
constexpr chunk_state_t CHUNK_STATE_MESHED = 2;
constexpr chunk_state_t CHUNK_STATE_MESH_LOADED = 4;
constexpr chunk_state_t CHUNK_STATE_LOADED = 8;
constexpr chunk_state_t CHUNK_STATE_OUTOFVISION = 16;
constexpr chunk_state_t CHUNK_STATE_UNLOADED = 32;
constexpr chunk_state_t CHUNK_STATE_EMPTY = 64;
constexpr chunk_state_t CHUNK_STATE_IN_GENERATION_QUEUE = 128;
constexpr chunk_state_t CHUNK_STATE_IN_MESHING_QUEUE = 256;
constexpr chunk_state_t CHUNK_STATE_IN_DELETING_QUEUE = 512;
int coord3DTo1D(int x, int y, int z);
int32_t calculateIndex(int16_t x, int16_t y, int16_t z);
int32_t calculateIndex(glm::vec3 pos);
class Chunk
{
public:
Chunk(glm::vec3 pos = glm::vec3(0.0f)); // a default value for the argument satisfies the need for a default constructor when using the type in an unordered_map (i.e. in chunkmanager)
~Chunk();
public:
void createBuffers();
void deleteBuffers();
glm::vec3 getPosition() { return this->position; }
void setState(uint16_t nstate, bool value);
uint16_t getTotalState() { return this->state; }
bool getState(uint16_t n) { return (this->state & n) == n; }
bool isFree(){ return !(getState(CHUNK_STATE_IN_GENERATION_QUEUE) ||
getState(CHUNK_STATE_IN_MESHING_QUEUE)); }
void setState(chunk_state_t nstate, bool value);
bool getState(chunk_state_t n) { return (this->state & n) == n; }
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 setBlocks(int start, int end, Block b);
@ -60,17 +69,15 @@ namespace Chunk
std::unique_ptr<Block[]> getBlocksArray(int* len) { return (this->blocks.toArray(len)); }
public:
GLuint VAO{0}, VBO{0}, extentsBuffer{0}, texinfoBuffer{0}, numVertices{0};
std::atomic<float> unload_timer{0};
// uint32_t is fine, since i'm limiting the coordinate to only use up to ten bits (1023). There's actually two spare bits
int32_t getIndex(){ return index; }
chunk_index_t getIndex(){ return this->index; }
private:
glm::vec3 position{};
IntervalMap<Block> blocks{};
int32_t index;
std::atomic_uint16_t state{0};
std::atomic<chunk_state_t> state{0};
chunk_index_t index;
};
};

View File

@ -4,8 +4,6 @@
#include <oneapi/tbb/concurrent_hash_map.h>
#include <oneapi/tbb/concurrent_queue.h>
#include <oneapi/tbb/concurrent_priority_queue.h>
#include <unordered_map>
#include <thread>
#include "chunk.hpp"
@ -13,7 +11,7 @@
#include "worldupdatemessage.h"
// Seconds to be passed outside of render distance for a chunk to be destroyed
#define UNLOAD_TIMEOUT 5
#define UNLOAD_TIMEOUT 10
#define MESHING_PRIORITY_NORMAL 0
#define MESHING_PRIORITY_PLAYER_EDIT 10
@ -21,10 +19,7 @@
namespace chunkmanager
{
typedef oneapi::tbb::concurrent_hash_map<int32_t, Chunk::Chunk*> ChunkTable;
typedef oneapi::tbb::concurrent_queue<int> IntQueue;
//typedef std::unordered_map<int32_t, Chunk::Chunk*> ChunkTable;
typedef oneapi::tbb::concurrent_hash_map<chunk_index_t, Chunk::Chunk*> ChunkTable;
typedef std::pair<Chunk::Chunk*, uint8_t> ChunkPQEntry;
// The comparing function to use
struct compare_f {
@ -39,7 +34,7 @@ namespace chunkmanager
void stop();
void destroy();
WorldUpdateMsgQueue& getWorldUpdateQueue();
std::array<std::array<int16_t, 3>, chunks_volume>& getChunksIndices();
std::array<std::array<chunk_intcoord_t, 3>, chunks_volume>& getChunksIndices();
Block getBlockAtPos(int x, int y, int z);
}

View File

@ -1,12 +1,15 @@
#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{
int32_t index;
chunk_index_t index;
glm::vec3 position;
int num_vertices = 0;
@ -30,3 +33,4 @@ typedef oneapi::tbb::concurrent_queue<ChunkMeshData*> ChunkMeshDataQueue;
#endif

View File

@ -15,6 +15,15 @@
#include "shader.hpp"
namespace chunkmesher{
struct MeshData{
Chunk::Chunk* chunk;
GLuint numVertices{0};
std::vector<GLfloat> vertices;
std::vector<GLfloat> extents;
std::vector<GLfloat> texinfo;
};
ChunkMeshDataQueue& getMeshDataQueue();
void init();
void mesh(Chunk::Chunk* chunk);
@ -22,3 +31,4 @@ namespace chunkmesher{
#endif

View File

@ -4,15 +4,11 @@
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "worldupdatemessage.h"
#define BLOCKPICK_TIMEOUT 0.1f
namespace controls{
void init();
void update(GLFWwindow* window);
WorldUpdateMsgQueue& getWorldUpdateQueue();
};
#endif

View File

@ -10,11 +10,11 @@
#define extr extern
#endif
#define RENDER_DISTANCE 8
#define RENDER_DISTANCE 16
extr Camera theCamera;
//constexpr int chunks_volume = static_cast<int>(1.333333333333*M_PI*(RENDER_DISTANCE*RENDER_DISTANCE*RENDER_DISTANCE));
constexpr int chunks_volume = RENDER_DISTANCE*RENDER_DISTANCE*RENDER_DISTANCE*8;
// 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 float sines[360];

View File

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

View File

@ -1,7 +1,8 @@
#ifndef RENDERER_H
#define RENDERER_H
#include <glm/glm.hpp>
#include <oneapi/tbb/concurrent_unordered_set.h>
#include <oneapi/tbb/concurrent_queue.h>
#include "chunk.hpp"
#include "chunkmesher.hpp"
@ -10,7 +11,7 @@
namespace renderer{
typedef struct RenderInfo {
int32_t index;
chunk_index_t index;
int num_vertices;
glm::vec3 position;
bool buffers_allocated=false;
@ -53,6 +54,8 @@ namespace renderer{
ChunkMeshDataQueue& getMeshDataQueue();
IndexQueue& getDeleteIndexQueue();
};
#endif

View File

@ -4,6 +4,8 @@
#include <glm/glm.hpp>
#include <oneapi/tbb/concurrent_queue.h>
#include "block.hpp"
enum class WorldUpdateMsgType{
BLOCKPICK_PLACE,
BLOCKPICK_BREAK
@ -14,6 +16,7 @@ typedef struct WorldUpdateMsg{
glm::vec3 cameraPos;
glm::vec3 cameraFront;
float time;
Block block;
} WorldUpdateMsg;
typedef oneapi::tbb::concurrent_queue<WorldUpdateMsg> WorldUpdateMsgQueue;

View File

@ -1,143 +0,0 @@
#ifndef INTERVALMAP_H
#define INTERVALMAP_H
#include <iostream>
#include <iterator> //std::prev
#include <limits> // std::numeric_limits
#include <memory> //std::shared_ptr
#include <map>
template <typename K, typename V>
class IntervalMap
{
public:
~IntervalMap(){
treemap.clear();
}
void insert(K start, K end, V value)
{
if (start >= end)
return;
// The entry just before the end index
auto tmp = treemap.upper_bound(end);
auto end_prev_entry = tmp == treemap.end() ? tmp : --tmp;
auto added_end = treemap.end();
// If it doesn't exist (empty map)
if(end_prev_entry == treemap.end()){
V v{};
added_end = treemap.insert_or_assign(treemap.begin(), end, v);
}
// Or if it has value different from the insertion
else if(end_prev_entry->second != value)
// Add it back at the end
added_end = treemap.insert_or_assign(end_prev_entry, end, end_prev_entry->second);
// The entry just before the start index
tmp = treemap.upper_bound(start);
auto start_prev_entry = tmp == treemap.end() ? tmp : --tmp;
auto added_start = treemap.end();
// If it has value different from the insertion
if(start_prev_entry == treemap.end() || start_prev_entry->second != value)
// Add the start node of the insertion
added_start = treemap.insert_or_assign(start_prev_entry, start, value);
// Delete everything else inside
// map.erase(start, end) deletes every node with key in the range [start, end)
// The key to start deleting from is the key after the start node we added
// (We don't want to delete a node we just added)
auto del_start = added_start == treemap.end() ? std::next(start_prev_entry) : std::next(added_start);
auto del_end = added_end == treemap.end() ? end_prev_entry : added_end;
auto del_end_next = std::next(del_end);
// If the node after the end is of the same type of the end, delete it
// We cannot just expand del_end (++del_end) otherwise interval limits get messed up
if(del_end != treemap.end() && del_end_next != treemap.end() && del_end->second ==
del_end_next->second) treemap.erase(del_end_next);
// Delete everything in between
if(del_start != treemap.end() && (del_end==treemap.end() || del_start->first <
del_end->first)) treemap.erase(del_start, del_end);
}
void remove(K at)
{
treemap.erase(at);
}
auto at(K index)
{
const auto tmp = treemap.lower_bound(index);
const auto r = tmp != treemap.begin() && tmp->first!=index ? std::prev(tmp) : tmp;
return r;
}
void print()
{
for (auto i = treemap.begin(); i != treemap.end(); i++)
std::cout << i->first << ": " << (int)(i->second) << "\n";
if(!treemap.empty()) std::cout << "end key: " << std::prev(treemap.end())->first << "\n";
}
std::unique_ptr<V[]> toArray(int *length)
{
if (treemap.empty())
{
*length = 0;
return nullptr;
}
const auto &end = std::prev(treemap.end());
*length = end->first;
if(*length == 0) return nullptr;
std::unique_ptr<V[]> arr(new V[*length]);
auto start = treemap.begin();
for (auto i = std::next(treemap.begin()); i != treemap.end(); i++)
{
for (int k = start->first; k < i->first; k++)
arr[k] = start->second;
start = i;
}
return arr;
}
void fromArray(V *arr, int length)
{
treemap.clear();
if (length == 0)
return;
V prev = arr[0];
unsigned int prev_start = 0;
for (unsigned int i = 1; i < length; i++)
{
if (prev != arr[i])
{
insert(prev_start, i, prev);
prev_start = i;
}
prev = arr[i];
}
insert(prev_start, length, prev);
}
auto begin(){
return treemap.begin();
}
auto end(){
return treemap.end();
}
private:
std::map<K, V> treemap{};
};
#endif

View File

@ -26,29 +26,30 @@ void main(){
EmitVertex();
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);
FragPos = vec3(gl_Position);
gl_Position = projection * view * gl_Position;
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);
FragPos = vec3(gl_Position);
gl_Position = projection * view * gl_Position;
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){
TexCoord = vec3(0.0, gs_in[0].Extents.z, gs_in[0].BlockType);
gl_Position = gl_in[0].gl_Position + vec4(0.0, 0.0, gs_in[0].Extents.z, 0.0);
TexCoord = vec3(gs_in[0].Extents.x, 0.0, gs_in[0].BlockType);
gl_Position = gl_in[0].gl_Position + vec4(gs_in[0].Extents.x, 0.0, 0.0, 0.0);
FragPos = vec3(gl_Position);
gl_Position = projection * view * gl_Position;
EmitVertex();
TexCoord = vec3(gs_in[0].Extents.x, 0.0, gs_in[0].BlockType);
gl_Position = gl_in[0].gl_Position + vec4(gs_in[0].Extents.x, 0.0, 0.0, 0.0);
TexCoord = vec3(0.0, gs_in[0].Extents.z, gs_in[0].BlockType);
gl_Position = gl_in[0].gl_Position + vec4(0.0, 0.0, gs_in[0].Extents.z, 0.0);
FragPos = vec3(gl_Position);
gl_Position = projection * view * gl_Position;
EmitVertex();

View File

@ -7,4 +7,4 @@ set(SOURCE_FILES main.cpp controls.cpp chunk.cpp chunkmanager.cpp chunkmesher.cp
add_executable(OpenGLTest ${SOURCE_FILES})
target_link_libraries(OpenGLTest glfw tbb glad glm imgui)
install(TARGETS OpenGLTest DESTINATION)
install(TARGETS OpenGLTest DESTINATION ${DIVISIBLE_INSTALL_BIN_DIR})

View File

@ -15,12 +15,13 @@ namespace Chunk
return utils::coord3DTo1D(x, y, z, CHUNK_SIZE, CHUNK_SIZE, CHUNK_SIZE);
}
int32_t calculateIndex(glm::vec3 pos){
return calculateIndex(static_cast<int16_t>(pos.x), static_cast<int16_t>(pos.y),
static_cast<int16_t>(pos.z));
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));
}
int32_t calculateIndex(int16_t x, int16_t y, int16_t z){
return x | (y << 10) | (z << 20);
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)
@ -31,6 +32,10 @@ namespace Chunk
this->index = calculateIndex(pos);
}
Chunk ::~Chunk()
{
}
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 ||
@ -49,7 +54,7 @@ namespace Chunk
this->blocks.insert(start < 0 ? 0 : start, end >= CHUNK_VOLUME ? CHUNK_VOLUME : end, b);
}
void Chunk::setState(uint16_t nstate, bool value)
void Chunk::setState(chunk_state_t nstate, bool value)
{
if (value)
this->state.fetch_or(nstate);

View File

@ -63,9 +63,9 @@ std::array<TreeCellInfo, TREE_LUT_SIZE*TREE_LUT_SIZE> treeLUT;
void generateNoise(Chunk::Chunk *chunk)
{
int cx = static_cast<int>(chunk->getPosition().x) * CHUNK_SIZE;
int cy = static_cast<int>(chunk->getPosition().y) * CHUNK_SIZE;
int cz = static_cast<int>(chunk->getPosition().z) * CHUNK_SIZE;
int cx = chunk->getPosition().x * CHUNK_SIZE;
int cy = chunk->getPosition().y * CHUNK_SIZE;
int cz = chunk->getPosition().z * CHUNK_SIZE;
// Precalculate LUTs

View File

@ -1,17 +1,16 @@
#include "chunkmanager.hpp"
#include <oneapi/tbb.h>
#include <atomic>
#include <math.h>
#include <queue>
#include <vector>
#include <thread>
#include <glm/glm.hpp>
#include <glm/ext.hpp>
#include <glm/gtx/string_cast.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <oneapi/tbb/parallel_for.h>
#include "block.hpp"
#include "chunk.hpp"
#include "chunkgenerator.hpp"
@ -19,6 +18,7 @@
#include "debugwindow.hpp"
#include "globals.hpp"
#include "renderer.hpp"
#include "utils.hpp"
namespace chunkmanager
{
@ -26,14 +26,13 @@ namespace chunkmanager
// controls.cpp)
void generate();
void mesh();
void send_to_chunk_meshing_thread(Chunk::Chunk* c, int priority);
/* Chunk holding data structures */
// Concurrent hash table of chunks
ChunkTable chunks;
// Concurrent queue for chunks to be deleted
IntQueue chunks_todelete;
// Chunk indices. Centered at (0,0,0), going in concentric sphere outwards
std::array<std::array<int16_t, 3>, chunks_volume> chunks_indices;
std::array<std::array<chunk_intcoord_t, 3>, chunks_volume> chunks_indices;
/* World Update messaging data structure */
WorldUpdateMsgQueue WorldUpdateQueue;
@ -41,26 +40,21 @@ namespace chunkmanager
/* Multithreading */
std::atomic_bool should_run;
std::thread gen_thread, mesh_thread, update_thread;
// Queue of chunks to be generated
ChunkPriorityQueue chunks_to_generate_queue;
// Queue of chunks to be meshed
ChunkPriorityQueue chunks_to_mesh_queue;
/* Block picking */
int block_to_place{2};
WorldUpdateMsgQueue& getWorldUpdateQueue(){ return WorldUpdateQueue; }
// Init chunkmanager. Chunk indices and start threads
void init(){
int index{0};
constexpr int rr{RENDER_DISTANCE * RENDER_DISTANCE};
bool b = true;
for(int16_t i = -RENDER_DISTANCE; i < RENDER_DISTANCE; i++)
for(int16_t j = -RENDER_DISTANCE; j < RENDER_DISTANCE; j++)
for(int16_t k = -RENDER_DISTANCE; k < RENDER_DISTANCE; k++){
for(chunk_intcoord_t i = -RENDER_DISTANCE; i < RENDER_DISTANCE; i++)
for(chunk_intcoord_t j = -RENDER_DISTANCE; j < RENDER_DISTANCE; j++)
for(chunk_intcoord_t k = -RENDER_DISTANCE; k < RENDER_DISTANCE; k++){
chunks_indices[index][0]=i;
chunks_indices[index][1]=j;
@ -72,8 +66,6 @@ namespace chunkmanager
update_thread = std::thread(update);
gen_thread = std::thread(generate);
mesh_thread = std::thread(mesh);
debug::window::set_parameter("block_type_return", &block_to_place);
}
// Method for world generation thread(s)
@ -81,11 +73,12 @@ namespace chunkmanager
while(should_run){
ChunkPQEntry entry;
if(chunks_to_generate_queue.try_pop(entry)){
generateChunk(entry.first);
entry.first->setState(Chunk::CHUNK_STATE_IN_GENERATION_QUEUE, false);
Chunk::Chunk* chunk = entry.first;
generateChunk(chunk);
chunk->setState(Chunk::CHUNK_STATE_IN_GENERATION_QUEUE, false);
}
}
//chunks_to_generate_queue.clear();
chunks_to_generate_queue.clear();
}
// Method for chunk meshing thread(s)
@ -98,12 +91,18 @@ namespace chunkmanager
chunk->setState(Chunk::CHUNK_STATE_IN_MESHING_QUEUE, false);
}
}
//chunks_to_mesh_queue.clear();
chunks_to_mesh_queue.clear();
}
void send_to_chunk_meshing_thread(Chunk::Chunk* c, int priority){
c->setState(Chunk::CHUNK_STATE_IN_MESHING_QUEUE, true);
chunks_to_mesh_queue.push(std::make_pair(c, MESHING_PRIORITY_NORMAL));
}
oneapi::tbb::concurrent_queue<chunk_index_t> chunks_todelete;
void update(){
while(should_run) {
// Setup variables for the whole loop
/* Setup variables for the whole loop */
// Atomic is needed by parallel_for
std::atomic_int nUnloaded{0}, nMarkUnload{0}, nExplored{0}, nMeshed{0}, nGenerated{0};
std::atomic_int chunkX=static_cast<int>(theCamera.getAtomicPosX() / CHUNK_SIZE);
@ -122,37 +121,46 @@ namespace chunkmanager
}
// Eventually delete old chunks
int i;
/* Delete old chunks */
// In my head it makes sense to first delete old chunks, then create new ones
// I think it's easier for memory allocator to re-use the memory that was freed just
// before, but this isn't backed be any evidence and I might be wrong. Anyway this way
// works fine so I'm gonna keep it.
chunk_index_t i;
ChunkTable::accessor a;
while(chunks_todelete.try_pop(i)){
const int index = i;
const chunk_index_t index = i;
if(chunks.find(a, index)){
Chunk::Chunk* c = a->second;
// Use the accessor to erase the element
// Using the key doesn't work
if(chunks.erase(a)){
nUnloaded++;
renderer::getDeleteIndexQueue().push(index);
delete c;
} else std::cout << "failed to delete " << index << std::endl;
} 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";
}
// Eventually create new chunks near the player
/* Create new chunks around the player */
for(int i = 0; i < chunks_volume; i++) {
const int16_t x = chunks_indices[i][0] + chunkX;
const int16_t y = chunks_indices[i][1] + chunkY;
const int16_t z = chunks_indices[i][2] + chunkZ;
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 int32_t index = Chunk::calculateIndex(x, y, z);
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))));
}
// Now update all the chunks
/* 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;
@ -164,6 +172,7 @@ namespace chunkmanager
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(
@ -181,38 +190,44 @@ namespace chunkmanager
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
// TODO: not getting meshed
if(!c->getState(Chunk::CHUNK_STATE_MESHED)){
ChunkTable::accessor a1;
if(c->isFree() &&
// 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))) &&
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))) &&
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))) &&
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))) &&
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))) &&
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
c->setState(Chunk::CHUNK_STATE_IN_MESHING_QUEUE, true);
chunks_to_mesh_queue.push(std::make_pair(c, MESHING_PRIORITY_NORMAL));
// 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++;
// If generated & meshed, render
}
}else mesh++;
}
}else{
@ -232,43 +247,56 @@ namespace chunkmanager
}
}
// Update atomic variables only once at the end
nGenerated += gen;
nMeshed += mesh;
nMarkUnload += unload;
}
});
std::cout << "time: " << glfwGetTime() << "\ntotal: " << chunks.size() << "\ngenerated: " << nGenerated << "\nmeshed: "
<< nMeshed << "\nunloaded from prev loop: " << nUnloaded << "\nnew marked for unload: " << nMarkUnload << std::endl;
debug::window::set_parameter("update_chunks_total", (int)chunks.size());
debug::window::set_parameter("update_chunks_generated", (int) nGenerated);
debug::window::set_parameter("update_chunks_meshed", (int) nMeshed);
debug::window::set_parameter("update_chunks_freed", (int) nUnloaded);
debug::window::set_parameter("update_chunks_explored", (int) nExplored);
}
}
std::array<std::array<int16_t, 3>, chunks_volume>& getChunksIndices(){ return chunks_indices; }
std::array<std::array<chunk_intcoord_t, 3>, chunks_volume>& getChunksIndices(){ return chunks_indices; }
void stop() {
should_run=false;
std::cout << "waiting for secondary threads to finish\n";
std::cout << "Waiting for secondary threads to shut down" << std::endl;
update_thread.join();
std::cout << "update thread terminated\n";
std::cout << "Update thread has terminated" << std::endl;
gen_thread.join();
std::cout << "generation thread terminated\n";
std::cout << "Generation thread has terminated" << std::endl;
mesh_thread.join();
std::cout << "meshing thread terminated\n";
std::cout << "Meshing thread has terminated" << std::endl;
}
void destroy(){
for(const auto& n : chunks){
delete n.second;
}
chunks.clear();
}
void blockpick(WorldUpdateMsg& msg){
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
glm::vec3 pos = msg.cameraPos;
glm::vec3 origin = startposition;
glm::vec3 pos = origin;
glm::vec3 front = startdir;
for(float t = 0.0; t <= 10.0; t += 0.5){
// traverse the ray a block at the time
pos = msg.cameraPos + t*msg.cameraFront;
pos = origin + t*front;
// get which chunk and block the ray is at
int px = ((int)(pos.x))/CHUNK_SIZE;
@ -278,85 +306,196 @@ namespace chunkmanager
int by = pos.y - py*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
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, Chunk::calculateIndex(px, py, pz))) continue;
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);
a.release();
// if the block is non empty
if(b != Block::AIR){
if(b != Block::AIR) return pos;
// if placing a new block
if(msg.msg_type == WorldUpdateMsgType::BLOCKPICK_PLACE){
// Go half a block backwards on the ray, to check the block where the ray was
// coming from
// Doing this and not using normal adds the unexpected (and unwanted) ability to
// place blocks diagonally, without faces colliding with the block that has
// been clicked
pos -= theCamera.getFront()*0.5f;
old_chunk = c;
old_bx = bx;
old_by = by;
old_bz = bz;
old_px = px;
old_py = py;
old_pz = pz;
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;
}
return glm::vec3(-1);
}
// 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, Chunk::calculateIndex(px1, py1, pz1))) return;
Chunk::Chunk* c1 = a1->second;
// place the new block (only stone for now)
c1->setBlock((Block)block_to_place, bx1, by1, bz1);
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;
// 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));
// 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;
debug::window::set_parameter("block_last_action", (int)msg.msg_type);
debug::window::set_parameter("block_last_action_block_type",
(int)(block_to_place));
debug::window::set_parameter("block_last_action_x", px1*CHUNK_SIZE + bx1);
debug::window::set_parameter("block_last_action_y", px1*CHUNK_SIZE + by1);
debug::window::set_parameter("block_last_action_z", px1*CHUNK_SIZE + bz1);
// 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{
// 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));
// When necessary, also mesh nearby chunks
ChunkTable::accessor a1, a2, b1, b2, c1, c2;
if(bx == 0 && px - 1 >= 0 && chunks.find(a1, Chunk::calculateIndex(px - 1, py, pz)))
chunks_to_mesh_queue.push(std::make_pair(a1->second, MESHING_PRIORITY_PLAYER_EDIT));
if(by == 0 && py - 1 >= 0 && chunks.find(b1, Chunk::calculateIndex(px, py - 1, pz)))
chunks_to_mesh_queue.push(std::make_pair(a2->second, MESHING_PRIORITY_PLAYER_EDIT));
if(bz == 0 && pz - 1 >= 0 && chunks.find(c1, Chunk::calculateIndex(px, py, pz - 1)))
chunks_to_mesh_queue.push(std::make_pair(b1->second, MESHING_PRIORITY_PLAYER_EDIT));
if(bx == CHUNK_SIZE - 1 && px +1 < 1024 && chunks.find(a2, Chunk::calculateIndex(px +1, py, pz)))
chunks_to_mesh_queue.push(std::make_pair(b2->second, MESHING_PRIORITY_PLAYER_EDIT));
if(by == CHUNK_SIZE - 1 && py +1 < 1024 && chunks.find(b2, Chunk::calculateIndex(px, py +1, pz)))
chunks_to_mesh_queue.push(std::make_pair(c1->second, MESHING_PRIORITY_PLAYER_EDIT));
if(bz == CHUNK_SIZE - 1 && pz +1 < 1024 && chunks.find(c2, Chunk::calculateIndex(px, py, pz +1)))
chunks_to_mesh_queue.push(std::make_pair(c2->second, MESHING_PRIORITY_PLAYER_EDIT));
debug::window::set_parameter("block_last_action", (int)msg.msg_type);
debug::window::set_parameter("block_last_action_block_type", (int) (Block::AIR));
debug::window::set_parameter("block_last_action_x", px*CHUNK_SIZE + bx);
debug::window::set_parameter("block_last_action_y", py*CHUNK_SIZE + by);
debug::window::set_parameter("block_last_action_z", pz*CHUNK_SIZE + bz);
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;
}
}
// 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){
@ -382,3 +521,4 @@ namespace chunkmanager
}
}
};

View File

@ -11,15 +11,18 @@
#include "spacefilling.hpp"
#include "utils.hpp"
#define CHUNKMESHER_BORDERS 0
#define CHUNK_MESH_DATA_QUANTITY 100
#define CHUNK_MESH_WORLD_LIMIT_BORDERS 0
namespace chunkmesher{
ChunkMeshDataQueue MeshDataQueue;
ChunkMeshDataQueue& getMeshDataQueue(){ return MeshDataQueue; }
void init(){
for(int i = 0; i < 10; i++)
void init()
{
for(int i = 0; i < CHUNK_MESH_DATA_QUANTITY; i++)
MeshDataQueue.push(new ChunkMeshData{});
}
@ -49,23 +52,23 @@ void mesh(Chunk::Chunk* chunk)
mesh_data->index = chunk->getIndex();
mesh_data->position = chunk->getPosition();
std::unique_ptr<Block[]> blocks;
// convert tree to array since it is easier to work with it
int length{0};
std::unique_ptr<Block[]> blocks;
int k, l, u, v, w, h, n, j, i;
int x[]{0, 0, 0};
int q[]{0, 0, 0};
int du[]{0, 0, 0};
int dv[]{0, 0, 0};
std::array<Block, CHUNK_SIZE * CHUNK_SIZE> mask;
// Abort if chunk is empty
if(chunk->getState(Chunk::CHUNK_STATE_EMPTY)) goto end;
// convert tree to array since it is easier to work with it
blocks = chunk->getBlocksArray(&length);
if(length == 0) goto end;
std::array<Block, CHUNK_SIZE * CHUNK_SIZE> mask;
for (bool backFace = true, b = false; b != backFace; backFace = backFace && b, b = !b)
{
// iterate over 3 dimensions
@ -97,7 +100,6 @@ void mesh(Chunk::Chunk* chunk)
Block b1, b2;
if(x[dim] >= 0) b1 = blocks[HILBERT_XYZ_ENCODE[x[0]][x[1]][x[2]]];
else{
// b1 = Block::NULLBLK;
int cx = chunk->getPosition().x*CHUNK_SIZE;
int cy = chunk->getPosition().y*CHUNK_SIZE;
int cz = chunk->getPosition().z*CHUNK_SIZE;
@ -112,7 +114,6 @@ void mesh(Chunk::Chunk* chunk)
if(x[dim] < CHUNK_SIZE - 1) b2 = blocks[HILBERT_XYZ_ENCODE[x[0] + q[0]][x[1]
+ q[1]][x[2] + q[2]]];
else{
//b2 = Block::NULLBLK;
int cx = chunk->getPosition().x*CHUNK_SIZE;
int cy = chunk->getPosition().y*CHUNK_SIZE;
int cz = chunk->getPosition().z*CHUNK_SIZE;
@ -129,7 +130,7 @@ void mesh(Chunk::Chunk* chunk)
// The else case provides face culling for adjacent solid faces
// Checking for NULLBLK avoids creating empty faces if nearby chunk was not
// yet generated
#if CHUNKMESHER_BORDERS == 1
#if CHUNK_MESH_WORLD_LIMIT_BORDERS == 1
mask[n++] = b1 == b2 ? Block::NULLBLK
: backFace ? b1 == Block::NULLBLK || b1 == Block::AIR ? b2 : Block::NULLBLK
: b2 == Block::NULLBLK || b2 == Block::AIR ? b1 : Block::NULLBLK;
@ -191,12 +192,11 @@ void mesh(Chunk::Chunk* chunk)
mesh_data->vertices.push_back(x[1]); //bottomLeft.y
mesh_data->vertices.push_back(x[2]); //bottomLeft.z
// extents
// extents, use normals for now
mesh_data->extents.push_back(du[0] + dv[0]);
mesh_data->extents.push_back(du[1] + dv[1]);
mesh_data->extents.push_back(du[2] + dv[2]);
// texture info (block type, backFace)
mesh_data->texinfo.push_back(backFace ? 0.0 : 1.0);
mesh_data->texinfo.push_back((int)(mask[n]) - 2);
mesh_data->num_vertices++;

View File

@ -2,20 +2,24 @@
#include "camera.hpp"
#include "chunkmanager.hpp"
#include "debugwindow.hpp"
#include "globals.hpp"
#include "renderer.hpp"
namespace controls{
WorldUpdateMsgQueue MsgQueue;
/* 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){
// Reset blockping timeout have passed
float current_time = glfwGetTime();
/* BlockPicking */
@ -45,6 +49,7 @@ namespace controls{
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);
@ -58,6 +63,4 @@ namespace controls{
glfwSetInputMode(window, GLFW_CURSOR, cursor ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED);
}
}
WorldUpdateMsgQueue& getWorldUpdateQueue(){ return MsgQueue; }
};

View File

@ -1,6 +1,3 @@
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "debugwindow.hpp"
#include <imgui/imgui.h>
@ -65,14 +62,16 @@ namespace debug{
void show_debug_window(){
ImGui::Begin("Debug Window");
//ImGui::PushItemWidth(ImGui::GetFontSize() * -12);
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", glGetString(GL_VENDOR), glGetString(GL_RENDERER));
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);
}
@ -83,6 +82,11 @@ namespace debug{
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");
@ -91,11 +95,6 @@ namespace debug{
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")) );
}
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(ImGui::CollapsingHeader("Mesh")){
@ -116,8 +115,6 @@ namespace debug{
if(ImGui::CollapsingHeader("Chunks")){
ImGui::Text("Total chunks present: %d",
std::any_cast<int>(parameters.at("update_chunks_total")));
ImGui::Text("Buckets in hash map: %d",
std::any_cast<int>(parameters.at("update_chunks_buckets")));
ImGui::Text("Chunks generated: %d",
std::any_cast<int>(parameters.at("update_chunks_generated")));
ImGui::Text("Chunks meshed: %d",
@ -128,9 +125,9 @@ namespace debug{
std::any_cast<int>(parameters.at("update_chunks_explored")));
}
}catch(const std::bad_any_cast& e){
std::cout << e.what();
std::cout << e.what() << std::endl;
}catch(const std::out_of_range& e){
std::cout << e.what();
std::cout << e.what() << std::endl;
}
ImGui::End();

View File

@ -1,6 +1,3 @@
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "main.hpp"
#include <iostream>
@ -13,8 +10,8 @@
#include "controls.hpp"
#include "debugwindow.hpp"
#include "renderer.hpp"
#include "spacefilling.hpp"
#include "shader.hpp"
#include "spacefilling.hpp"
float deltaTime = 0.0f; // Time between current frame and last frame
float lastFrame = 0.0f; // Time of last frame
@ -58,7 +55,9 @@ int main()
//glEnable(GL_FRAMEBUFFER_SRGB); //gamma correction done in fragment shader
//glEnable(GL_CULL_FACE); //GL_BACK GL_CCW by default
wireframe = false;
debug::window::set_parameter("gpu_vendor", glGetString(GL_VENDOR));
debug::window::set_parameter("gpu_renderer", glGetString(GL_RENDERER));
for(int i = 0; i < 360; i++){
sines[i] = sin(3.14 / 180 * i);
cosines[i] = cos(3.14 / 180 * i);
@ -91,15 +90,26 @@ int main()
glClearColor(0.431f, 0.694f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Only handle window closing here
// 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
controls::update(window);
// Camera
theCamera.update(window, deltaTime);
debug::window::set_parameter("px", theCamera.getPos().x);
debug::window::set_parameter("py", theCamera.getPos().y);
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
renderer::render();
@ -113,9 +123,8 @@ int main()
chunkmanager::stop();
// Cleanup allocated memory
debug::window::destroy();
renderer::destroy();
chunkmanager::destroy();
renderer::destroy();
glfwTerminate();
return 0;

View File

@ -2,7 +2,6 @@
#include <glm/ext.hpp>
#include <glm/gtx/string_cast.hpp>
#include <oneapi/tbb/concurrent_hash_map.h>
#include "chunkmanager.hpp"
@ -14,7 +13,7 @@
#include "stb_image_write.h"
namespace renderer{
typedef oneapi::tbb::concurrent_hash_map<int32_t, RenderInfo*> RenderTable;
typedef oneapi::tbb::concurrent_hash_map<chunk_index_t, RenderInfo*> RenderTable;
RenderTable ChunksToRender;
ChunkMeshDataQueue MeshDataQueue;
@ -23,16 +22,16 @@ namespace renderer{
Shader* theShader, *quadShader;
GLuint chunkTexture;
Shader* getRenderShader() { return theShader; }
ChunkMeshDataQueue& getMeshDataQueue(){ return MeshDataQueue; }
IndexQueue& getDeleteIndexQueue(){ return MeshDataToDelete; }
GLuint renderTexFrameBuffer, renderTex, renderTexDepthBuffer, quadVAO, quadVBO;
int screenWidth, screenHeight;
int crosshair_type{0};
bool wireframe{false};
Shader* getRenderShader() { return theShader; }
ChunkMeshDataQueue& getMeshDataQueue(){ return MeshDataQueue; }
IndexQueue& getDeleteIndexQueue(){ return MeshDataToDelete; }
void init(GLFWwindow* window){
// Setup rendering
// We will render the image to a texture, then display the texture on a quad that fills the
@ -142,6 +141,7 @@ namespace renderer{
theShader->use();
theShader->setVec3("viewPos", cameraPos);
/* Process incoming mesh data */
ChunkMeshData* m;
while(MeshDataQueue.try_pop(m)){
RenderTable::accessor a;
@ -151,7 +151,11 @@ namespace renderer{
render_info = a->second;
render_info->position = m->position;
render_info->num_vertices = m->num_vertices;
std::cout << "index collision on " << render_info->index << std::endl;
// 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;
@ -159,14 +163,16 @@ namespace renderer{
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);
}
send_chunk_to_gpu(m, render_info);
chunkmesher::getMeshDataQueue().push(m);
}
// TODO: implement removal of chunks from rendering
int32_t queue_index;
/* Process chunks to be removed */
chunk_index_t queue_index;
while(MeshDataToDelete.try_pop(queue_index)){
RenderTable::accessor a;
@ -178,11 +184,11 @@ namespace renderer{
}
}
// Render the chunks
/* Render the chunks */
// parallel_for cannot be used since all the rendering needs to happen in a single thread
for(RenderTable::iterator i = ChunksToRender.begin(); i != ChunksToRender.end(); i++){
RenderInfo* render_info = i->second;
if(render_info->num_vertices > 0)
{
total++;
@ -197,7 +203,7 @@ namespace renderer{
// Check if all the corners of the chunk are outside any of the planes
// TODO (?) implement frustum culling as per (Inigo Quilez)[https://iquilezles.org/articles/frustumcorrect/], and check each
// plane against each corner of the chunk
// plane against each corner of the chunk
bool out=false;
int a{0};
for(int p = 0; p < 6; p++){
@ -255,37 +261,35 @@ namespace renderer{
void send_chunk_to_gpu(ChunkMeshData* mesh_data, RenderInfo* render_info)
{
if (render_info->num_vertices > 0)
{
if(!render_info->buffers_allocated) render_info->allocateBuffers();
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);
// 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"
// 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);
// 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);
// 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);
// 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);
}
void framebuffer_size_callback(GLFWwindow *window, int width, int height){
resize_framebuffer(width, height);
}

View File

@ -1,34 +0,0 @@
#include <iostream>
#include <string>
#include <thread>
#include "oneapi/tbb/concurrent_hash_map.h"
#include "oneapi/tbb/tbb_allocator.h"
typedef oneapi::tbb::concurrent_hash_map<int, int, oneapi::tbb::tbb_allocator<int>> CTable;
CTable table;
void f(){
/*while(table.size() > 0){
std::cout << "---------------\n";
oneapi::tbb::parallel_for(table.range(), [=](Table::range_type &r){
for(Table::const_iterator a = r.begin(); a != r.end(); a++){
std::cout << a->first << ": " << a->second << std::endl;
}
});
}*/
}
int main(){
std::thread t = std::thread(f);
//Table::accessor a;
/*table.emplace(a, std::make_pair(0, "zero"));
table.emplace(a, std::make_pair(1, "one"));
table.emplace(a, std::make_pair(2, "two"));
table.emplace(a, std::make_pair(3, "three"));
table.emplace(a, std::make_pair(4, "four"));
table.emplace(a, std::make_pair(5, "five"));*/
t.join();
}