/* * Created by Brett on 11/02/23. * Licensed under GNU General Public License V3.0 * See LICENSE file for license detail */ #ifndef FINALPROJECT_WORLD_H #define FINALPROJECT_WORLD_H #include #include #include #include "blt/profiling/profiler.h" namespace fp { namespace _static { /** * Converts from world coord to chunk-internal coords * @param coord world space coordinate * @return chunk internal coord */ static inline int world_to_internal(int coord) { auto val = coord % CHUNK_SIZE; return val < 0 ? CHUNK_SIZE + val : val; } static inline block_pos world_to_internal(const block_pos& coord) { return {world_to_internal(coord.x), world_to_internal(coord.y), world_to_internal(coord.z)}; } /** * Converts from world coord to chunk pos coords * * consider: (int) (-31 / 32) which equals 0 * but a negative chunk would be stored at -1, not 0 (since that is taken by the positive coord chunk) * an arithmetic right shift would produce the desired -1 (see Java, which performs a signed right bit shift) * however in C++ shifting on a signed type is undefined behaviour. So we must emulate an arithmetic right shift. * * @param coord x,y, or z coordinate to convert * @return a right arithmetic bit shift resulting in a signed division of the coordinate by CHUNK_SIZE */ static inline int world_to_chunk(int coord) { auto ucoord = (unsigned int) coord; ucoord >>= CHUNK_SHIFT; if (coord < 0) { // the mask only has to be generated once since it is never modified at runtime beyond assignment static unsigned int mask = 0; if (mask == 0) { for (int i = 0; i < CHUNK_SHIFT; i++) mask |= (1 << ((sizeof(int) * 8 - 1) - i)); } ucoord |= mask; } return (int) (ucoord); } static inline chunk_pos world_to_chunk(const block_pos& pos) { return {world_to_chunk(pos.x), world_to_chunk(pos.y), world_to_chunk(pos.z)}; } } struct chunk { private: block_storage* storage; mesh_storage* mesh = nullptr; VAO* chunk_vao; chunk_pos pos; chunk_mesh_status dirtiness = OKAY; chunk_update_status status = NONE; unsigned long render_size = 0; public: explicit chunk(chunk_pos pos): pos(pos) { storage = new block_storage(); chunk_vao = new VAO(); auto vbo = new VBO(ARRAY_BUFFER, nullptr, 0); auto data_size = 3 * sizeof(float) + 3 * sizeof(float); chunk_vao->bindVBO(vbo, 0, 3, GL_FLOAT, (int)data_size, 0); chunk_vao->bindVBO(vbo, 1, 3, GL_FLOAT, (int)data_size, 3 * sizeof(float), true); chunk_vao->bindElementVBO(new VBO(ELEMENT_BUFFER, nullptr, 0)); } inline void render(shader& shader){ if (render_size > 0) { blt::mat4x4 translation{}; translation.translate((float) pos.x * CHUNK_SIZE, (float) pos.y * CHUNK_SIZE, (float) pos.z * CHUNK_SIZE ); shader.setMatrix("translation", translation); // bind the chunk's VAO chunk_vao->bind(); // despite binding the element buffer at creation time, this is required. chunk_vao->getVBO(-1)->bind(); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glEnableVertexAttribArray(2); glDrawElements(GL_TRIANGLES, (int) render_size, GL_UNSIGNED_INT, nullptr); glDisableVertexAttribArray(2); glDisableVertexAttribArray(1); glDisableVertexAttribArray(0); } } inline void updateChunkMesh(){ auto& vertices = mesh->getVertices(); auto& indices = mesh->getIndices(); BLT_DEBUG( "Chunk [%d, %d, %d] mesh updated with %d vertices and %d indices taking (%d, %d) bytes!", pos.x, pos.y, pos.z, vertices.size(), indices.size(), vertices.size() * sizeof(vertex), indices.size() * sizeof(unsigned int)); // upload the new vertices to the GPU chunk_vao->getVBO(0)->update(vertices); chunk_vao->getVBO(-1)->update(indices); render_size = indices.size(); // delete the local chunk mesh memory, since we no longer need to store it. delete (mesh); mesh = nullptr; markDone(); } /** * Mark the chunk as completely dirty and in need of a full check refresh */ inline void markDirty(){ dirtiness = FULL_MESH; } /** * Partial mesh update has been completed, we are now waiting on the edge chunks to be * generated before continuing to generate the chunk edge mesh */ inline void markPartialComplete() { dirtiness = PARTIAL_MESH; } /** * Full chunk mesh is now completely generated and waiting on uploading to the GPU */ inline void markComplete(){ dirtiness = REFRESH; } /** * Mesh uploading complete, chunk meshing is now done and inactive */ inline void markDone(){ dirtiness = OKAY; } [[nodiscard]] inline block_storage*& getBlockStorage() { return storage; } [[nodiscard]] inline mesh_storage*& getMeshStorage(){ return mesh; } [[nodiscard]] inline VAO*& getVAO(){ return chunk_vao; } [[nodiscard]] inline chunk_pos getPos() const { return pos; } [[nodiscard]] inline chunk_mesh_status getDirtiness() const { return dirtiness; } [[nodiscard]] inline chunk_update_status& getStatus() { return status; } ~chunk() { delete storage; delete chunk_vao; delete mesh; } }; struct chunk_neighbours { fp::chunk* neighbours[6]; inline chunk*& operator[](int i) { return neighbours[i]; } }; class world { private: std::unordered_map chunk_storage; protected: static void generateFullMesh(mesh_storage* mesh, chunk* chunk); void generateEdgeMesh(mesh_storage* mesh, chunk* chunk); void generateChunkMesh(chunk* chunk); static chunk* generateChunk(const chunk_pos& pos); inline void getNeighbours(const chunk_pos& pos, chunk_neighbours& neighbours) { neighbours[X_POS] = getChunk(chunk_pos{pos.x + 1, pos.y, pos.z}); neighbours[X_NEG] = getChunk(chunk_pos{pos.x - 1, pos.y, pos.z}); neighbours[Y_POS] = getChunk(chunk_pos{pos.x, pos.y + 1, pos.z}); neighbours[Y_NEG] = getChunk(chunk_pos{pos.x, pos.y - 1, pos.z}); neighbours[Z_POS] = getChunk(chunk_pos{pos.x, pos.y, pos.z + 1}); neighbours[Z_NEG] = getChunk(chunk_pos{pos.x, pos.y, pos.z - 1}); } inline void insertChunk(chunk* chunk) { chunk_storage.insert({chunk->getPos(), chunk}); chunk_neighbours chunkNeighbours{}; getNeighbours(chunk->getPos(), chunkNeighbours); for (auto* p : chunkNeighbours.neighbours){ if (p) p->getStatus() = NEIGHBOUR_CREATE; } } inline chunk* getChunk(const chunk_pos& pos){ const auto map_pos = chunk_storage.find(pos); if (map_pos == chunk_storage.end()) return nullptr; return map_pos->second; } inline chunk* getChunk(const block_pos& pos) { return chunk_storage[_static::world_to_chunk(pos)]; } public: world() = default; void update(); void render(fp::shader& shader); inline bool setBlock(const block_pos& pos, block_type blockID) { auto c = getChunk(pos); if (!c) return false; // mark the chunk for a mesh update c->markDirty(); c->getBlockStorage()->set(_static::world_to_internal(pos), blockID); return true; } inline block_type getBlock(const block_pos& pos) { auto c = getChunk(pos); if (!c) return fp::registry::AIR; return c->getBlockStorage()->get(_static::world_to_internal(pos)); } ~world() { BLT_PRINT_PROFILE("Chunk Mesh", blt::logging::TRACE, true); BLT_PRINT_PROFILE("Chunk Generate", blt::logging::TRACE, true); for (auto& chunk : chunk_storage) delete (chunk.second); } }; } #endif //FINALPROJECT_WORLD_H