Compare commits

..

20 Commits

Author SHA1 Message Date
EmaMaker e593696af3 restore seamless chunk borders 2023-10-03 20:47:53 +02:00
EmaMaker df933bebdd refactor message-sending system between main and update thread
restores blockpicking with new multithread system
2023-10-03 18:43:33 +02:00
EmaMaker 950e8c68ab separate input handling from main 2023-10-03 18:16:45 +02:00
EmaMaker 4c46811a25 delete meshes relative to deleted chunks 2023-10-03 18:04:06 +02:00
EmaMaker bc3c5587f8 calculateIndex belongs to Chunk namespace 2023-10-03 15:42:01 +02:00
EmaMaker b95ea49c93 gpu printed in debug window 2023-10-03 15:35:56 +02:00
EmaMaker 90fabf73ee add to queues only if completely free 2023-10-03 15:35:34 +02:00
EmaMaker 96bf3c651d player debug variables updated in main 2023-10-03 15:32:49 +02:00
EmaMaker 170deaccf7 eliminate unused variables+stricter "free" condition 2023-10-03 15:28:21 +02:00
EmaMaker da6ceeb605 set generation/meshing queue flags before pushing to queues 2023-10-03 12:31:02 +02:00
EmaMaker ca44c0f284 initial update/mesh/render thread communication refactor
Quirks to iron out:
- Some stuff is rendered wrong/not rendered (chunks missing)
- Still no way to delete stuff from ChunksToRender
2023-10-03 12:27:01 +02:00
EmaMaker a05d019c69 use concurrent_hash_map and parallel_for for chunk update
memory VIRT is higher, RES is the same. memory usage stabilizes after a while
2023-10-01 19:45:51 +02:00
EmaMaker 49edf2de85 move chunk update back to secondary thread 2023-10-01 18:12:14 +02:00
EmaMaker 8a3c963721 solve some memory leaks regarding thread communication queues
also temporarly move chunk update in primary thread
2023-10-01 18:11:54 +02:00
EmaMaker 1ec529fae5 fallimentary attempt at fixing memory management with onetbb 2023-09-30 15:09:26 +02:00
EmaMaker 00cdd22e10 renderer: try frustum culling only if chunk has vertices 2023-09-22 09:06:48 +02:00
EmaMaker a65fc44069 renderer: correct distance check 2023-09-21 17:13:21 +02:00
EmaMaker 3f02ca91db chunkmgr: proper use of int instead of uint in chunk indices 2023-09-21 16:58:18 +02:00
EmaMaker 01f1f9da16 squashed commits
erase chunk buffers only after marking as to be deleted

update chunks around player with a cube instead of concentric spheres

only generate chunks around player, update all chunks use onetbb::parallel_for
2023-09-21 15:49:50 +02:00
EmaMaker 5f396a9801 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-09-21 15:43:51 +02:00
23 changed files with 445 additions and 435 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++11 -g")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O3")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -g")
set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR})

View File

@ -8,6 +8,9 @@
#include <atomic>
#include "chunk.hpp"
#include "debugwindow.hpp"
class Camera
{
@ -52,6 +55,16 @@ 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)
@ -116,7 +129,7 @@ public:
private:
glm::vec3 cameraPos = glm::vec3(512.0, 80.0f, 512.0f);
glm::vec3 cameraPos = glm::vec3(512.0, 256.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,47 +20,38 @@
#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
{
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;
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;
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(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 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 setBlock(Block b, int x, int y, int z);
void setBlocks(int start, int end, Block b);
@ -69,15 +60,17 @@ 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};
chunk_index_t getIndex(){ return this->index; }
// 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; }
private:
glm::vec3 position{};
IntervalMap<Block> blocks{};
std::atomic<chunk_state_t> state{0};
chunk_index_t index;
int32_t index;
std::atomic_uint16_t state{0};
};
};

View File

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

View File

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

View File

@ -15,15 +15,6 @@
#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);
@ -31,4 +22,3 @@ namespace chunkmesher{
#endif

View File

@ -4,11 +4,15 @@
#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 16
#define RENDER_DISTANCE 8
extr Camera theCamera;
// 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);
//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;
extr bool wireframe;
extr float sines[360];

View File

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

View File

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

143
intervalmap.hpp Normal file
View File

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

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 ${DIVISIBLE_INSTALL_BIN_DIR})
install(TARGETS OpenGLTest DESTINATION)

View File

@ -15,13 +15,12 @@ namespace Chunk
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));
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(chunk_intcoord_t i, chunk_intcoord_t j, chunk_intcoord_t k){
return i | (j << 10) | (k << 20);
int32_t calculateIndex(int16_t x, int16_t y, int16_t z){
return x | (y << 10) | (z << 20);
}
Chunk::Chunk(glm::vec3 pos)
@ -32,10 +31,6 @@ 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 ||
@ -54,7 +49,7 @@ namespace Chunk
this->blocks.insert(start < 0 ? 0 : start, end >= CHUNK_VOLUME ? CHUNK_VOLUME : end, b);
}
void Chunk::setState(chunk_state_t nstate, bool value)
void Chunk::setState(uint16_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 = chunk->getPosition().x * CHUNK_SIZE;
int cy = chunk->getPosition().y * CHUNK_SIZE;
int cz = chunk->getPosition().z * CHUNK_SIZE;
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;
// Precalculate LUTs

View File

@ -1,16 +1,17 @@
#include "chunkmanager.hpp"
#include <oneapi/tbb.h>
#include <atomic>
#include <math.h>
#include <vector>
#include <queue>
#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"
@ -18,7 +19,6 @@
#include "debugwindow.hpp"
#include "globals.hpp"
#include "renderer.hpp"
#include "utils.hpp"
namespace chunkmanager
{
@ -26,13 +26,14 @@ 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<chunk_intcoord_t, 3>, chunks_volume> chunks_indices;
std::array<std::array<int16_t, 3>, chunks_volume> chunks_indices;
/* World Update messaging data structure */
WorldUpdateMsgQueue WorldUpdateQueue;
@ -40,21 +41,26 @@ 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};
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++){
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++){
chunks_indices[index][0]=i;
chunks_indices[index][1]=j;
@ -66,6 +72,8 @@ 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)
@ -73,12 +81,11 @@ namespace chunkmanager
while(should_run){
ChunkPQEntry entry;
if(chunks_to_generate_queue.try_pop(entry)){
Chunk::Chunk* chunk = entry.first;
generateChunk(chunk);
chunk->setState(Chunk::CHUNK_STATE_IN_GENERATION_QUEUE, false);
generateChunk(entry.first);
entry.first->setState(Chunk::CHUNK_STATE_IN_GENERATION_QUEUE, false);
}
}
chunks_to_generate_queue.clear();
//chunks_to_generate_queue.clear();
}
// Method for chunk meshing thread(s)
@ -91,18 +98,12 @@ 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);
@ -121,46 +122,37 @@ namespace chunkmanager
}
/* 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;
// Eventually delete old chunks
int i;
ChunkTable::accessor a;
while(chunks_todelete.try_pop(i)){
const chunk_index_t index = i;
const int index = i;
if(chunks.find(a, index)){
Chunk::Chunk* c = a->second;
// Use the accessor to erase the element
// Using the key doesn't work
if(chunks.erase(a)){
nUnloaded++;
renderer::getDeleteIndexQueue().push(index);
delete c;
} else {
c->setState(Chunk::CHUNK_STATE_IN_DELETING_QUEUE, false);
std::cout << "failed to delete " << index << std::endl;
}
} else std::cout << "failed to delete " << index << std::endl;
} else std::cout << "no such element found to delete\n";
}
/* Create new chunks around the player */
// Eventually create new chunks near 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;
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;
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);
const int32_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 */
// Now 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;
@ -172,7 +164,6 @@ 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(
@ -190,44 +181,38 @@ 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;
// Checking if nearby chunks have been generated allows for seamless
// borders between chunks
if(c->isFree() &&
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
// 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);
c->setState(Chunk::CHUNK_STATE_IN_MESHING_QUEUE, true);
chunks_to_mesh_queue.push(std::make_pair(c, MESHING_PRIORITY_NORMAL));
}
}else mesh++;
}else{
mesh++;
// If generated & meshed, render
}
}
}else{
@ -247,56 +232,43 @@ namespace chunkmanager
}
}
// Update atomic variables only once at the end
nGenerated += gen;
nMeshed += mesh;
nMarkUnload += unload;
}
});
debug::window::set_parameter("update_chunks_total", (int)chunks.size());
debug::window::set_parameter("update_chunks_generated", (int) nGenerated);
debug::window::set_parameter("update_chunks_meshed", (int) nMeshed);
debug::window::set_parameter("update_chunks_freed", (int) nUnloaded);
debug::window::set_parameter("update_chunks_explored", (int) nExplored);
std::cout << "time: " << glfwGetTime() << "\ntotal: " << chunks.size() << "\ngenerated: " << nGenerated << "\nmeshed: "
<< nMeshed << "\nunloaded from prev loop: " << nUnloaded << "\nnew marked for unload: " << nMarkUnload << std::endl;
}
}
std::array<std::array<chunk_intcoord_t, 3>, chunks_volume>& getChunksIndices(){ return chunks_indices; }
std::array<std::array<int16_t, 3>, chunks_volume>& getChunksIndices(){ return chunks_indices; }
void stop() {
should_run=false;
std::cout << "Waiting for secondary threads to shut down" << std::endl;
std::cout << "waiting for secondary threads to finish\n";
update_thread.join();
std::cout << "Update thread has terminated" << std::endl;
std::cout << "update thread terminated\n";
gen_thread.join();
std::cout << "Generation thread has terminated" << std::endl;
std::cout << "generation thread terminated\n";
mesh_thread.join();
std::cout << "Meshing thread has terminated" << std::endl;
std::cout << "meshing thread terminated\n";
}
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;
void blockpick(WorldUpdateMsg& msg){
// cast a ray from the camera in the direction pointed by the camera itself
glm::vec3 origin = startposition;
glm::vec3 pos = origin;
glm::vec3 front = startdir;
glm::vec3 pos = msg.cameraPos;
for(float t = 0.0; t <= 10.0; t += 0.5){
// traverse the ray a block at the time
pos = origin + t*front;
pos = msg.cameraPos + t*msg.cameraFront;
// get which chunk and block the ray is at
int px = ((int)(pos.x))/CHUNK_SIZE;
@ -306,196 +278,85 @@ 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::const_accessor a;
ChunkTable::accessor a;
if(!chunks.find(a, Chunk::calculateIndex(px, py, pz))) continue;
Chunk::Chunk* c = a->second;
if(!c->isFree() || !c->getState(Chunk::CHUNK_STATE_GENERATED)){
a.release();
continue;
}
if(!c->getState(Chunk::CHUNK_STATE_GENERATED) || c->getState(Chunk::CHUNK_STATE_EMPTY)) continue;
Block b = c->getBlock(bx, by, bz);
a.release();
// if the block is non empty
if(b != Block::AIR) return pos;
if(b != Block::AIR){
old_chunk = c;
old_bx = bx;
old_by = by;
old_bz = bz;
old_px = px;
old_py = py;
old_pz = pz;
old_pos = 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;
}
return glm::vec3(-1);
}
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;
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;
// 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);
// 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;
// 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));
// 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;
}
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);
}else{
if(tMaxY < tMaxZ){
y += stepY;
tMaxY += tDeltaY;
}else{
z += stepZ;
tMaxZ += tDeltaZ;
}
// 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);
}
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){
@ -521,4 +382,3 @@ namespace chunkmanager
}
}
};

View File

@ -11,18 +11,15 @@
#include "spacefilling.hpp"
#include "utils.hpp"
#define CHUNK_MESH_DATA_QUANTITY 100
#define CHUNK_MESH_WORLD_LIMIT_BORDERS 0
#define CHUNKMESHER_BORDERS 0
namespace chunkmesher{
ChunkMeshDataQueue MeshDataQueue;
ChunkMeshDataQueue& getMeshDataQueue(){ return MeshDataQueue; }
void init()
{
for(int i = 0; i < CHUNK_MESH_DATA_QUANTITY; i++)
void init(){
for(int i = 0; i < 10; i++)
MeshDataQueue.push(new ChunkMeshData{});
}
@ -52,23 +49,23 @@ void mesh(Chunk::Chunk* chunk)
mesh_data->index = chunk->getIndex();
mesh_data->position = chunk->getPosition();
// convert tree to array since it is easier to work with it
int length{0};
std::unique_ptr<Block[]> blocks;
int length{0};
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
@ -100,6 +97,7 @@ 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;
@ -114,6 +112,7 @@ 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;
@ -130,7 +129,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 CHUNK_MESH_WORLD_LIMIT_BORDERS == 1
#if CHUNKMESHER_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;
@ -192,11 +191,12 @@ void mesh(Chunk::Chunk* chunk)
mesh_data->vertices.push_back(x[1]); //bottomLeft.y
mesh_data->vertices.push_back(x[2]); //bottomLeft.z
// extents, use normals for now
// extents
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,24 +2,20 @@
#include "camera.hpp"
#include "chunkmanager.hpp"
#include "debugwindow.hpp"
#include "globals.hpp"
#include "renderer.hpp"
namespace controls{
/* Block picking */
int block_to_place{2};
WorldUpdateMsgQueue MsgQueue;
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 */
@ -49,7 +45,6 @@ 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);
@ -63,4 +58,6 @@ namespace controls{
glfwSetInputMode(window, GLFW_CURSOR, cursor ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED);
}
}
WorldUpdateMsgQueue& getWorldUpdateQueue(){ return MsgQueue; }
};

View File

@ -1,3 +1,6 @@
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "debugwindow.hpp"
#include <imgui/imgui.h>
@ -62,16 +65,14 @@ 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",
std::any_cast<const GLubyte*>(parameters.at("gpu_vendor")),
std::any_cast<const GLubyte*>(parameters.at("gpu_renderer")));
ImGui::Text("GPU: %s %s", glGetString(GL_VENDOR), glGetString(GL_RENDERER));
//ImGui::PlotLines("Frame Times", arr, IM_ARRAYSIZE(arr);
}
@ -82,11 +83,6 @@ 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");
@ -95,6 +91,11 @@ 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")){
@ -115,6 +116,8 @@ 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",
@ -125,9 +128,9 @@ namespace debug{
std::any_cast<int>(parameters.at("update_chunks_explored")));
}
}catch(const std::bad_any_cast& e){
std::cout << e.what() << std::endl;
std::cout << e.what();
}catch(const std::out_of_range& e){
std::cout << e.what() << std::endl;
std::cout << e.what();
}
ImGui::End();

View File

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

View File

@ -2,6 +2,7 @@
#include <glm/ext.hpp>
#include <glm/gtx/string_cast.hpp>
#include <oneapi/tbb/concurrent_hash_map.h>
#include "chunkmanager.hpp"
@ -13,7 +14,7 @@
#include "stb_image_write.h"
namespace renderer{
typedef oneapi::tbb::concurrent_hash_map<chunk_index_t, RenderInfo*> RenderTable;
typedef oneapi::tbb::concurrent_hash_map<int32_t, RenderInfo*> RenderTable;
RenderTable ChunksToRender;
ChunkMeshDataQueue MeshDataQueue;
@ -22,16 +23,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
@ -141,7 +142,6 @@ namespace renderer{
theShader->use();
theShader->setVec3("viewPos", cameraPos);
/* Process incoming mesh data */
ChunkMeshData* m;
while(MeshDataQueue.try_pop(m)){
RenderTable::accessor a;
@ -151,11 +151,7 @@ namespace renderer{
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);
std::cout << "index collision on " << render_info->index << std::endl;
}else{
render_info = new RenderInfo();
render_info->index = m->index;
@ -163,16 +159,14 @@ 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);
}
/* Process chunks to be removed */
chunk_index_t queue_index;
// TODO: implement removal of chunks from rendering
int32_t queue_index;
while(MeshDataToDelete.try_pop(queue_index)){
RenderTable::accessor a;
@ -184,11 +178,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++;
@ -203,7 +197,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++){
@ -261,35 +255,37 @@ namespace renderer{
void send_chunk_to_gpu(ChunkMeshData* mesh_data, RenderInfo* render_info)
{
if(!render_info->buffers_allocated) render_info->allocateBuffers();
if (render_info->num_vertices > 0)
{
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);
}

34
test2.cpp Normal file
View File

@ -0,0 +1,34 @@
#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();
}