diff --git a/include/chunkmanager.hpp b/include/chunkmanager.hpp index aa1bf3a..e000c86 100644 --- a/include/chunkmanager.hpp +++ b/include/chunkmanager.hpp @@ -1,8 +1,6 @@ #ifndef CHUNKMANAGER_H #define CHUNKMANAGER_H -#include "chunk.hpp" - #include #include #include @@ -10,7 +8,9 @@ #include #include +#include "chunk.hpp" #include "globals.hpp" +#include "worldupdatemessage.h" // Seconds to be passed outside of render distance for a chunk to be destroyed #define UNLOAD_TIMEOUT 5 @@ -35,14 +35,12 @@ namespace chunkmanager typedef oneapi::tbb::concurrent_priority_queue ChunkPriorityQueue; void init(); - void blockpick(bool place); - + void update(); void stop(); void destroy(); + WorldUpdateMsgQueue& getWorldUpdateQueue(); std::array, chunks_volume>& getChunksIndices(); Block getBlockAtPos(int x, int y, int z); - void update(); - void primary_thread_update(); } #endif diff --git a/include/controls.hpp b/include/controls.hpp index 7938c0a..dacd39f 100644 --- a/include/controls.hpp +++ b/include/controls.hpp @@ -6,6 +6,8 @@ #include "worldupdatemessage.h" +#define BLOCKPICK_TIMEOUT 0.1f + namespace controls{ void init(); void update(GLFWwindow* window); diff --git a/include/worldupdatemessage.h b/include/worldupdatemessage.h new file mode 100644 index 0000000..200f8d9 --- /dev/null +++ b/include/worldupdatemessage.h @@ -0,0 +1,21 @@ +#ifndef WORLD_UPDATE_MSG_H +#define WORLD_UPDATE_MSG_H + +#include +#include + +enum class WorldUpdateMsgType{ + BLOCKPICK_PLACE, + BLOCKPICK_BREAK +}; + +typedef struct WorldUpdateMsg{ + WorldUpdateMsgType msg_type; + glm::vec3 cameraPos; + glm::vec3 cameraFront; + float time; +} WorldUpdateMsg; + +typedef oneapi::tbb::concurrent_queue WorldUpdateMsgQueue; + +#endif diff --git a/src/chunkmanager.cpp b/src/chunkmanager.cpp index 3bff859..5250a03 100644 --- a/src/chunkmanager.cpp +++ b/src/chunkmanager.cpp @@ -22,9 +22,12 @@ namespace chunkmanager { + void blockpick(WorldUpdateMsg& msg); // There's no need of passing by value again (check + // controls.cpp) void generate(); void mesh(); + /* Chunk holding data structures */ // Concurrent hash table of chunks ChunkTable chunks; // Concurrent queue for chunks to be deleted @@ -32,6 +35,9 @@ namespace chunkmanager // Chunk indices. Centered at (0,0,0), going in concentric sphere outwards std::array, chunks_volume> chunks_indices; + /* World Update messaging data structure */ + WorldUpdateMsgQueue WorldUpdateQueue; + /* Multithreading */ std::atomic_bool should_run; std::thread gen_thread, mesh_thread, update_thread; @@ -40,7 +46,10 @@ namespace chunkmanager // 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(){ @@ -94,11 +103,25 @@ namespace chunkmanager void update(){ while(should_run) { + // Setup variables for the whole loop + // Atomic is needed by parallel_for std::atomic_int nUnloaded{0}, nMarkUnload{0}, nExplored{0}, nMeshed{0}, nGenerated{0}; std::atomic_int chunkX=static_cast(theCamera.getAtomicPosX() / CHUNK_SIZE); std::atomic_int chunkY=static_cast(theCamera.getAtomicPosY() / CHUNK_SIZE); std::atomic_int chunkZ=static_cast(theCamera.getAtomicPosZ() / CHUNK_SIZE); + /* Process update messages before anything happens */ + WorldUpdateMsg msg; + while(WorldUpdateQueue.try_pop(msg)){ + switch(msg.msg_type){ + case WorldUpdateMsgType::BLOCKPICK_BREAK: + case WorldUpdateMsgType::BLOCKPICK_PLACE: + blockpick(msg); + break; + } + } + + // Eventually delete old chunks int i; ChunkTable::accessor a; @@ -225,14 +248,12 @@ namespace chunkmanager void destroy(){ } - /* - void blockpick(bool place){ + void blockpick(WorldUpdateMsg& msg){ // cast a ray from the camera in the direction pointed by the camera itself - glm::vec3 pos = glm::vec3(theCamera.getAtomicPosX(), theCamera.getAtomicPosY(), - theCamera.getAtomicPosZ()); + 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 = theCamera.getPos() + t * theCamera.getFront(); + pos = msg.cameraPos + t*msg.cameraFront; // get which chunk and block the ray is at int px = ((int)(pos.x))/CHUNK_SIZE; @@ -246,7 +267,7 @@ namespace chunkmanager if(px < 0 || py < 0 || pz < 0 || px >= 1024 || py >= 1024 || pz >= 1024) continue; ChunkTable::accessor a; - if(!chunks.find(a, calculateIndex(px, py, pz))) continue; + if(!chunks.find(a, Chunk::calculateIndex(px, py, pz))) continue; Chunk::Chunk* c = a->second; if(!c->getState(Chunk::CHUNK_STATE_GENERATED) || c->getState(Chunk::CHUNK_STATE_EMPTY)) continue; @@ -257,7 +278,7 @@ namespace chunkmanager if(b != Block::AIR){ // if placing a new block - if(place){ + 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 @@ -275,7 +296,7 @@ namespace chunkmanager // exit early if the position is invalid or the chunk does not exist if(px1 < 0 || py1 < 0 || pz1 < 0 || px1 >= 1024 || py1 >= 1024 || pz1 >= 1024) return; ChunkTable::accessor a1; - if(!chunks.find(a1, calculateIndex(px1, py1, pz1))) return; + 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); @@ -284,8 +305,9 @@ namespace chunkmanager 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)); - debug::window::set_parameter("block_last_action", place); - debug::window::set_parameter("block_last_action_block_type", (int)(Block::STONE)); + 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); @@ -297,20 +319,20 @@ namespace chunkmanager // When necessary, also mesh nearby chunks ChunkTable::accessor a1, a2, b1, b2, c1, c2; - if(bx == 0 && px - 1 >= 0 && chunks.find(a1, calculateIndex(px - 1, py, pz))) - chunkmesher::mesh(a1->second); - if(by == 0 && py - 1 >= 0 && chunks.find(b1, calculateIndex(px, py - 1, pz))) - chunkmesher::mesh(b1->second); - if(bz == 0 && pz - 1 >= 0 && chunks.find(c1, calculateIndex(px, py, pz - 1))) - chunkmesher::mesh(c1->second); - if(bx == CHUNK_SIZE - 1 && px +1 < 1024 && chunks.find(a2, calculateIndex(px +1, py, pz))) - chunkmesher::mesh(a2->second); - if(by == CHUNK_SIZE - 1 && py +1 < 1024 && chunks.find(b2, calculateIndex(px, py +1, pz))) - chunkmesher::mesh(b2->second); - if(bz == CHUNK_SIZE - 1 && pz +1 < 1024 && chunks.find(c2, calculateIndex(px, py, pz +1))) - chunkmesher::mesh(c2->second); + 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", place); + 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); @@ -322,6 +344,7 @@ namespace chunkmanager } } + /* Block getBlockAtPos(int x, int y, int z){ if(x < 0 || y < 0 || z < 0) return Block::NULLBLK; diff --git a/src/controls.cpp b/src/controls.cpp index 87cb06c..ae6a9e3 100644 --- a/src/controls.cpp +++ b/src/controls.cpp @@ -1,4 +1,8 @@ #include "controls.hpp" + +#include "camera.hpp" +#include "chunkmanager.hpp" +#include "globals.hpp" #include "renderer.hpp" namespace controls{ @@ -11,24 +15,42 @@ namespace controls{ } void update(GLFWwindow* window){ - // Reset blockping timeout if 200ms have passed - if(glfwGetTime() - lastBlockPick > 0.1) blockpick = false; - - if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2) == GLFW_PRESS && !blockpick){ - //chunkmanager::blockpick(false); - blockpick=true; - lastBlockPick=glfwGetTime(); - } - - if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS && !blockpick){ - //chunkmanager::blockpick(true); - blockpick=true; - lastBlockPick=glfwGetTime(); - } + // Reset blockping timeout have passed + float current_time = glfwGetTime(); + /* BlockPicking */ // Reset blockpicking if enough time has passed + if(current_time - lastBlockPick > BLOCKPICK_TIMEOUT) blockpick = false; + // Reset blockpicking if both mouse buttons are released if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_RELEASE && glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2) == GLFW_RELEASE) blockpick = false; + // Process block picking if a mouse button is pressed + if((glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS || + glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2 == GLFW_PRESS)) && !blockpick){ + // Start timeout for next block pick action + blockpick = true; + lastBlockPick = current_time; + + // Construct the message to send to chunkmanager + + // WorldUpdateMsg is allocated on the stack + // unlike ChunkMeshData, the fields of WorldUpdateMsg are few and light, so there's no + // problem in passing them by value each time. + // It also has the advantage of having less memory to manage, since I'm not allocating + // anything on the heap + + WorldUpdateMsg msg{}; + msg.cameraPos = theCamera.getPos(); + msg.cameraFront = theCamera.getFront(); + msg.time = current_time; + msg.msg_type = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS ? + WorldUpdateMsgType::BLOCKPICK_PLACE : WorldUpdateMsgType::BLOCKPICK_BREAK; + + // Send to chunk manager + chunkmanager::getWorldUpdateQueue().push(msg); + } + + /* SCREENSHOTS */ if(glfwGetKey(window, GLFW_KEY_F2) == GLFW_PRESS) renderer::saveScreenshot(); if(glfwGetKey(window, GLFW_KEY_F3) == GLFW_PRESS) renderer::saveScreenshot(true); if(glfwGetKey(window, GLFW_KEY_M) == GLFW_PRESS) { diff --git a/src/main.cpp b/src/main.cpp index 98ff15b..8ae028a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,15 +1,16 @@ #include #include +#include "main.hpp" + #include #include #define GLOBALS_DEFINER #include "globals.hpp" #undef GLOBALS_DEFINER - #include "chunkmanager.hpp" -#include "main.hpp" +#include "controls.hpp" #include "debugwindow.hpp" #include "renderer.hpp" #include "spacefilling.hpp" @@ -20,10 +21,6 @@ float lastFrame = 0.0f; // Time of last frame float lastFPSFrame = 0.0f; int frames = 0; -float lastBlockPick=0.0; -bool blockpick = false; -bool cursor = false; - int main() { @@ -68,9 +65,10 @@ int main() } SpaceFilling::initLUT(); - debug::window::init(window); + controls::init(); chunkmanager::init(); chunkmesher::init(); + debug::window::init(window); renderer::init(window); while (!glfwWindowShouldClose(window)) @@ -93,16 +91,15 @@ 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 + if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) + glfwSetWindowShouldClose(window, true); + // Input processing - processInput(window); + controls::update(window); // Camera theCamera.update(window, deltaTime); - - // Reset blockping timeout if 200ms have passed - if(glfwGetTime() - lastBlockPick > 0.1) blockpick = false; - - //chunkmanager::primary_thread_update(); // Render pass renderer::render(); @@ -135,33 +132,3 @@ void mouse_callback(GLFWwindow *window, double xpos, double ypos) { theCamera.mouseCallback(window, xpos, ypos); } - -void processInput(GLFWwindow *window) -{ - if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) - glfwSetWindowShouldClose(window, true); - - if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2) == GLFW_PRESS && !blockpick){ - //chunkmanager::blockpick(false); - blockpick=true; - lastBlockPick=glfwGetTime(); - } - - if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS && !blockpick){ - //chunkmanager::blockpick(true); - blockpick=true; - lastBlockPick=glfwGetTime(); - } - - // Reset blockpicking if enough time has passed - if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_RELEASE && glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2) == GLFW_RELEASE) blockpick = false; - - if(glfwGetKey(window, GLFW_KEY_F2) == GLFW_PRESS) renderer::saveScreenshot(); - if(glfwGetKey(window, GLFW_KEY_F3) == GLFW_PRESS) renderer::saveScreenshot(true); - if(glfwGetKey(window, GLFW_KEY_M) == GLFW_PRESS) { - cursor = !cursor; - glfwSetInputMode(window, GLFW_CURSOR, cursor ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED); - } - - -}