2023-02-12 18:13:09 -05:00
|
|
|
/*
|
|
|
|
* 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 <world/chunk/storage.h>
|
|
|
|
#include <render/gl.h>
|
|
|
|
#include <unordered_map>
|
2023-03-05 15:21:35 -05:00
|
|
|
#include "blt/profiling/profiler.h"
|
2023-02-12 18:13:09 -05:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2023-02-13 23:37:18 -05:00
|
|
|
|
2023-02-13 16:30:01 -05:00
|
|
|
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)};
|
|
|
|
}
|
2023-02-12 18:13:09 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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);
|
|
|
|
}
|
|
|
|
|
2023-02-13 23:37:18 -05:00
|
|
|
static inline chunk_pos world_to_chunk(const block_pos& pos) {
|
2023-02-13 16:30:01 -05:00
|
|
|
return {world_to_chunk(pos.x), world_to_chunk(pos.y), world_to_chunk(pos.z)};
|
|
|
|
}
|
2023-02-12 18:13:09 -05:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
struct chunk {
|
2023-03-05 15:21:35 -05:00
|
|
|
private:
|
2023-02-12 18:13:09 -05:00
|
|
|
block_storage* storage;
|
|
|
|
mesh_storage* mesh = nullptr;
|
|
|
|
VAO* chunk_vao;
|
2023-02-13 16:30:01 -05:00
|
|
|
chunk_pos pos;
|
2023-02-13 23:37:18 -05:00
|
|
|
|
|
|
|
chunk_mesh_status dirtiness = OKAY;
|
|
|
|
chunk_update_status status = NONE;
|
2023-02-12 18:13:09 -05:00
|
|
|
unsigned long render_size = 0;
|
|
|
|
public:
|
2023-02-13 16:30:01 -05:00
|
|
|
explicit chunk(chunk_pos pos): pos(pos) {
|
2023-02-12 18:13:09 -05:00
|
|
|
storage = new block_storage();
|
|
|
|
chunk_vao = new VAO();
|
2023-03-04 23:24:36 -05:00
|
|
|
auto vbo = new VBO(ARRAY_BUFFER, nullptr, 0);
|
2023-03-05 15:21:35 -05:00
|
|
|
auto data_size = 3 * sizeof(float) + 3 * sizeof(float);
|
2023-03-04 23:24:36 -05:00
|
|
|
chunk_vao->bindVBO(vbo, 0, 3, GL_FLOAT, (int)data_size, 0);
|
2023-03-05 15:21:35 -05:00
|
|
|
chunk_vao->bindVBO(vbo, 1, 3, GL_FLOAT, (int)data_size, 3 * sizeof(float), true);
|
2023-02-14 18:19:39 -05:00
|
|
|
chunk_vao->bindElementVBO(new VBO(ELEMENT_BUFFER, nullptr, 0));
|
2023-02-12 18:13:09 -05:00
|
|
|
}
|
|
|
|
|
2023-03-05 15:21:35 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-02-12 18:13:09 -05:00
|
|
|
~chunk() {
|
|
|
|
delete storage;
|
|
|
|
delete chunk_vao;
|
2023-02-13 16:30:01 -05:00
|
|
|
delete mesh;
|
2023-02-12 18:13:09 -05:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-02-13 23:37:18 -05:00
|
|
|
struct chunk_neighbours {
|
|
|
|
fp::chunk* neighbours[6];
|
|
|
|
|
|
|
|
inline chunk*& operator[](int i) {
|
|
|
|
return neighbours[i];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-02-12 18:13:09 -05:00
|
|
|
class world {
|
|
|
|
private:
|
2023-02-13 16:30:01 -05:00
|
|
|
std::unordered_map<chunk_pos, chunk*, _static::chunk_pos_hash, _static::chunk_pos_equality> chunk_storage;
|
2023-02-12 18:13:09 -05:00
|
|
|
protected:
|
2023-02-15 00:49:27 -05:00
|
|
|
static void generateFullMesh(mesh_storage* mesh, chunk* chunk);
|
2023-02-13 23:37:18 -05:00
|
|
|
|
|
|
|
void generateEdgeMesh(mesh_storage* mesh, chunk* chunk);
|
|
|
|
|
2023-02-13 16:30:01 -05:00
|
|
|
void generateChunkMesh(chunk* chunk);
|
|
|
|
|
2023-03-05 15:21:35 -05:00
|
|
|
static chunk* generateChunk(const chunk_pos& pos);
|
|
|
|
|
2023-02-13 23:37:18 -05:00
|
|
|
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) {
|
2023-03-05 15:21:35 -05:00
|
|
|
chunk_storage.insert({chunk->getPos(), chunk});
|
2023-02-13 23:37:18 -05:00
|
|
|
|
|
|
|
chunk_neighbours chunkNeighbours{};
|
2023-03-05 15:21:35 -05:00
|
|
|
getNeighbours(chunk->getPos(), chunkNeighbours);
|
2023-02-13 23:37:18 -05:00
|
|
|
|
|
|
|
for (auto* p : chunkNeighbours.neighbours){
|
|
|
|
if (p)
|
2023-03-05 15:21:35 -05:00
|
|
|
p->getStatus() = NEIGHBOUR_CREATE;
|
2023-02-13 23:37:18 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2023-02-13 16:30:01 -05:00
|
|
|
}
|
2023-02-12 18:13:09 -05:00
|
|
|
|
2023-02-13 16:30:01 -05:00
|
|
|
inline chunk* getChunk(const block_pos& pos) {
|
2023-03-05 15:21:35 -05:00
|
|
|
return chunk_storage[_static::world_to_chunk(pos)];
|
2023-02-12 18:13:09 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public:
|
2023-03-05 15:21:35 -05:00
|
|
|
world() = default;
|
2023-02-12 18:13:09 -05:00
|
|
|
|
|
|
|
void update();
|
|
|
|
|
|
|
|
void render(fp::shader& shader);
|
|
|
|
|
2023-02-13 23:37:18 -05:00
|
|
|
inline bool setBlock(const block_pos& pos, block_type blockID) {
|
2023-02-13 16:30:01 -05:00
|
|
|
auto c = getChunk(pos);
|
2023-02-13 23:37:18 -05:00
|
|
|
if (!c)
|
|
|
|
return false;
|
2023-02-12 18:13:09 -05:00
|
|
|
// mark the chunk for a mesh update
|
2023-03-05 15:21:35 -05:00
|
|
|
c->markDirty();
|
|
|
|
c->getBlockStorage()->set(_static::world_to_internal(pos), blockID);
|
2023-02-13 23:37:18 -05:00
|
|
|
return true;
|
2023-02-12 18:13:09 -05:00
|
|
|
}
|
|
|
|
|
2023-02-13 23:37:18 -05:00
|
|
|
inline block_type getBlock(const block_pos& pos) {
|
2023-02-13 16:30:01 -05:00
|
|
|
auto c = getChunk(pos);
|
2023-02-13 23:37:18 -05:00
|
|
|
if (!c)
|
|
|
|
return fp::registry::AIR;
|
2023-03-05 15:21:35 -05:00
|
|
|
return c->getBlockStorage()->get(_static::world_to_internal(pos));
|
2023-02-12 18:13:09 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
~world() {
|
2023-03-05 15:21:35 -05:00
|
|
|
BLT_PRINT_PROFILE("Chunk Mesh", blt::logging::TRACE, true);
|
|
|
|
BLT_PRINT_PROFILE("Chunk Generate", blt::logging::TRACE, true);
|
2023-02-12 18:13:09 -05:00
|
|
|
for (auto& chunk : chunk_storage)
|
|
|
|
delete (chunk.second);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif //FINALPROJECT_WORLD_H
|