154 lines
5.4 KiB
C
154 lines
5.4 KiB
C
|
/*
|
||
|
* 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>
|
||
|
|
||
|
namespace fp {
|
||
|
|
||
|
struct ChunkPos {
|
||
|
int x, y, z;
|
||
|
};
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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);
|
||
|
}
|
||
|
|
||
|
// std::unordered_map requires a type. As a result the functions are encapsulated.
|
||
|
struct ChunkPosHash {
|
||
|
inline size_t operator()(const ChunkPos& pos) const {
|
||
|
size_t p1 = std::hash<int>()(pos.x);
|
||
|
size_t p2 = std::hash<int>()(pos.y);
|
||
|
size_t p3 = std::hash<int>()(pos.z);
|
||
|
return (p1 ^ (p2 << 1)) ^ p3;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
struct ChunkPosEquality {
|
||
|
inline bool operator()(const ChunkPos& p1, const ChunkPos& p2) const {
|
||
|
return p1.x == p2.x && p1.y == p2.y && p1.z == p2.z;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
}
|
||
|
|
||
|
struct chunk {
|
||
|
public:
|
||
|
block_storage* storage;
|
||
|
mesh_storage* mesh = nullptr;
|
||
|
VAO* chunk_vao;
|
||
|
ChunkPos pos;
|
||
|
|
||
|
unsigned char dirtiness = 0;
|
||
|
unsigned long render_size = 0;
|
||
|
public:
|
||
|
explicit chunk(ChunkPos pos): pos(pos) {
|
||
|
storage = new block_storage();
|
||
|
chunk_vao = new VAO();
|
||
|
// using indices uses:
|
||
|
// 12 faces * 4 vertex * 3 float * 4 bytes + 12 faces * 6 indices * 4 bytes = 864 bytes for vertex + index
|
||
|
|
||
|
// using only vertices:
|
||
|
// 12 faces * 6 vertex * 3 floats * 4 bytes = 864 bytes.
|
||
|
|
||
|
// since they both use the same amount of memory we will only store the vertices and draw with drawArrays, since it is less complex.
|
||
|
// set up the VBOs which will be later updated when the mesh is generated.
|
||
|
chunk_vao->bindVBO(new VBO(ARRAY_BUFFER, nullptr, 0), 0, 3);
|
||
|
}
|
||
|
|
||
|
~chunk() {
|
||
|
delete storage;
|
||
|
delete chunk_vao;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
class world {
|
||
|
private:
|
||
|
std::unordered_map<ChunkPos, chunk*, _static::ChunkPosHash, _static::ChunkPosEquality> chunk_storage;
|
||
|
protected:
|
||
|
mesh_storage* generateChunkMesh(chunk* chunk);
|
||
|
|
||
|
chunk* getChunk(int x, int y, int z) {
|
||
|
return chunk_storage.at(ChunkPos{_static::world_to_chunk(x), _static::world_to_chunk(y), _static::world_to_chunk(z)});
|
||
|
}
|
||
|
|
||
|
public:
|
||
|
world() {
|
||
|
chunk_storage.insert({{0, 0, 0}, new chunk({0, 0, 0})});
|
||
|
}
|
||
|
|
||
|
void update();
|
||
|
|
||
|
void render(fp::shader& shader);
|
||
|
|
||
|
inline void setBlock(int x, int y, int z, char blockID) {
|
||
|
auto c = getChunk(x, y, z);
|
||
|
// mark the chunk for a mesh update
|
||
|
c->dirtiness = 2;
|
||
|
c->storage->set(_static::world_to_internal(x), _static::world_to_internal(y), _static::world_to_internal(z), blockID);
|
||
|
}
|
||
|
|
||
|
inline void setBlock(float x, float y, float z, char blockID) { setBlock((int) x, (int) y, (int) z, blockID); }
|
||
|
|
||
|
inline char getBlock(int x, int y, int z) {
|
||
|
auto c = getChunk(x, y, z);
|
||
|
return c->storage->get(x, y, z);
|
||
|
}
|
||
|
|
||
|
inline char getBlock(float x, float y, float z) {
|
||
|
return getBlock((int) x, (int) y, (int) z);
|
||
|
}
|
||
|
|
||
|
~world() {
|
||
|
for (auto& chunk : chunk_storage)
|
||
|
delete (chunk.second);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
}
|
||
|
|
||
|
#endif //FINALPROJECT_WORLD_H
|