general code cleanup

vertex-deduplication
EmaMaker 2023-03-23 21:17:06 +01:00
parent 19cd81b9fb
commit 8584d2e974
16 changed files with 134 additions and 162 deletions

View File

@ -8,8 +8,6 @@
#include <iostream>
#include "chunk.hpp"
class Camera
{
@ -17,13 +15,15 @@ public:
Camera()
{
view = glm::mat4(1.0f);
// note that we're translating the scene in the reverse direction of where we want to move
// This matrix needs to be also updated in viewPortCallback whenever it is changed
projection = glm::perspective(glm::radians(90.0f), 800.0f / 600.0f, 0.1f, 200.0f);
}
void update(GLFWwindow *window, float deltaTime)
{
const float cameraSpeed = 10.0f * deltaTime; // adjust accordingly
const float cameraSpeed = 25.0f * deltaTime; // adjust accordingly
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
this->cameraPos += cameraSpeed * cameraFront;
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
@ -72,34 +72,16 @@ public:
pitch = -89.0f;
}
glm::vec3 getPos()
{
return cameraPos;
}
glm::vec3 getFront()
{
return cameraFront;
}
glm::vec3 getUp()
{
return cameraUp;
}
glm::mat4 getView()
{
return view;
}
glm::mat4 getProjection()
{
return projection;
}
glm::vec3 getPos() { return cameraPos; }
glm::vec3 getFront() { return cameraFront; }
glm::vec3 getUp() { return cameraUp; }
glm::mat4 getView() { return view; }
glm::mat4 getProjection() { return projection; }
// Plane extraction as per Gribb&Hartmann
// 6 planes, each with 4 components (a,b,c,d)
void getFrustumPlanes(glm::vec4 planes[6], bool normalize){
void getFrustumPlanes(glm::vec4 planes[6], bool normalize)
{
glm::mat4 mat = transpose(projection*view);
// This just compressed the code below
@ -122,9 +104,8 @@ public:
}
private:
glm::vec3 cameraPos = glm::vec3(static_cast<float>(CHUNK_SIZE)*24, 40.0f, static_cast<float>(CHUNK_SIZE)*24);
glm::vec3 cameraPos = glm::vec3(0.0, 80.0f, 0.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

@ -15,7 +15,7 @@
#include "intervalmap.hpp"
#include "shader.hpp"
#define CHUNK_SIZE 8
#define CHUNK_SIZE 16
#define CHUNK_VOLUME (CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE)
#define CHUNK_MAX_INDEX (CHUNK_VOLUME - 1)

View File

@ -13,17 +13,15 @@ namespace chunkmanager
void stopGenThread();
void stopMeshThread();
void update(float deltaTime);
void updateChunk(uint32_t, uint16_t, uint16_t, uint16_t);
void destroy();
void mesh();
void generate();
void blockpick(bool place);
uint32_t calculateIndex(uint16_t i, uint16_t j, uint16_t k);
void mesh();
void generate();
void destroy();
void update(float deltaTime);
void updateChunk(uint32_t, uint16_t, uint16_t, uint16_t);
}
#endif

View File

@ -4,20 +4,19 @@
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <string>
#include <vector>
#include "chunk.hpp"
#include "globals.hpp"
#include "shader.hpp"
#include <string>
namespace chunkmesher{
void mesh(Chunk::Chunk* chunk);
void sendtogpu(Chunk::Chunk* chunk);
void draw(Chunk::Chunk* chunk, glm::mat4 model);
void quad(Chunk::Chunk* chunk, glm::vec3 bottomLeft, glm::vec3 topLeft, glm::vec3 topRight, glm::vec3 bottomRight, Block block, bool backFace);
void mesh(Chunk::Chunk* chunk);
void sendtogpu(Chunk::Chunk* chunk);
void draw(Chunk::Chunk* chunk, glm::mat4 model);
void quad(Chunk::Chunk* chunk, glm::vec3 bottomLeft, glm::vec3 topLeft, glm::vec3 topRight, glm::vec3 bottomRight, Block block, bool backFace);
}

View File

@ -10,7 +10,7 @@
#define extr extern
#endif
#define RENDER_DISTANCE 8
#define RENDER_DISTANCE 16
extr Camera theCamera;
extr Shader* theShader;

View File

@ -20,10 +20,7 @@ public:
if (start >= end)
return;
// higherEntry -> upper_bound
// lowerEntry -> find()--
// c++ has no builtin function to find the greater key LESS THAN the supplied key. The solution I've found is to get an iterator to the key with find() and traverse back with std::prev
const auto &tmp = treemap.lower_bound(end);
const auto &end_prev_entry = tmp != treemap.begin() && tmp != treemap.end() ? std::prev(tmp) : tmp; // first element before end
const auto &end_next_entry = tmp; // first element after end
@ -40,7 +37,11 @@ public:
{
if(end_next_entry->first != end)
treemap[end] = end_prev_entry->second;
// A little optimization: delete next key if it is of the same value of the end key
if(end_next_entry->second == treemap[end]) treemap.erase(end_next_entry);
}
// insert the start key. Replaces whatever value is already there. Do not place if the element before is of the same value
treemap[start] = value;
treemap.erase(treemap.upper_bound(start), treemap.lower_bound(end));
@ -70,23 +71,18 @@ public:
{
if (treemap.empty())
{
// std::cout << "List is empty" << std::endl;
*length = 0;
return nullptr;
}
const auto &end = std::prev(treemap.end());
*length = end->first;
if(*length == 0) return nullptr;
// std::cout << "Length: " << *length << "\n";
std::unique_ptr<V[]> arr(new V[*length]);
auto start = treemap.begin();
for (auto i = std::next(treemap.begin()); i != treemap.end(); i++)
{
// std::cout << "creating list from " << start->first << " to " << i->first << " of type " << (int)(start->second) << "\n";
for (int k = start->first; k < i->first; k++)
arr[k] = start->second;
start = i;

View File

@ -5,5 +5,4 @@ void framebuffer_size_callback(GLFWwindow *, int, int);
void mouse_callback(GLFWwindow *window, double xpos, double ypos);
void processInput(GLFWwindow *);
#endif

View File

@ -7,18 +7,11 @@
namespace SpaceFilling
{
uint32_t MortonToHilbert3D(const uint32_t morton, const uint32_t bits);
uint32_t HilbertToMorton3D(const uint32_t hilbert, const uint32_t bits);
uint32_t Morton_3D_Encode_5bit(uint32_t index1, uint32_t index2, uint32_t index3);
void Morton_3D_Decode_5bit(const uint32_t morton,
uint32_t &index1, uint32_t &index2, uint32_t &index3);
void Morton_3D_Decode_5bit(const uint32_t morton, uint32_t &index1, uint32_t &index2, uint32_t &index3);
uint32_t Morton_3D_Encode_10bit(uint32_t index1, uint32_t index2, uint32_t index3);
void Morton_3D_Decode_10bit(const uint32_t morton,
uint32_t &index1, uint32_t &index2, uint32_t &index3);
void Morton_3D_Decode_10bit(const uint32_t morton, uint32_t &index1, uint32_t &index2, uint32_t &index3);
void initLUT();
};

View File

@ -6,8 +6,6 @@
namespace utils
{
bool withinDistance(int startx, int starty, int startz, int x, int y, int z, int dist);
// https://stackoverflow.com/questions/20266201/3d-array-1d-flat-indexing
// flatten 3d coords to 1d array cords
int coord3DTo1D(int x, int y, int z, int maxX, int maxY, int maxZ);
std::array<int, 3> coord1DTo3D(int idx, int maxX, int maxY, int mazZ);

View File

@ -19,7 +19,7 @@ namespace Chunk
{
this->position = pos;
this->setState(CHUNK_STATE_EMPTY, true);
// std::cout << "CHUNK" << std::endl;
glGenVertexArrays(1, &(this->VAO));
glGenBuffers(1, &(this->colorBuffer));
glGenBuffers(1, &(this->VBO));

View File

@ -33,6 +33,7 @@ OpenSimplexNoise::Noise noiseGen2(12345);
std::array<int, CHUNK_SIZE * CHUNK_SIZE> grassNoiseLUT;
std::array<int, CHUNK_SIZE * CHUNK_SIZE> dirtNoiseLUT;
void generateNoise(Chunk::Chunk *chunk)
{
for (int i = 0; i < grassNoiseLUT.size(); i++)
@ -88,6 +89,5 @@ void generatePyramid(Chunk::Chunk *chunk)
for (int i = 0; i < CHUNK_SIZE; i++)
for (int j = 0; j < CHUNK_SIZE; j++)
for (int k = 0; k < CHUNK_SIZE; k++)
// blocks[utils::coord3DTo1D(i, j, k, CHUNK_SIZE, CHUNK_SIZE, CHUNK_SIZE)] = j == 0 ? Block::STONE : Block::AIR;
chunk->setBlock(i >= j && i < CHUNK_SIZE - j && k >= j && k < CHUNK_SIZE - j ? (j & 1) == 0 ? Block::GRASS : Block::STONE : Block::AIR, i, j, k);
}

View File

@ -20,16 +20,25 @@ std::unordered_map<std::uint32_t, Chunk::Chunk *> chunks;
namespace chunkmanager
{
// thread management
std::mutex mutex_queue_generate;
std::set<Chunk::Chunk *> to_generate;
std::set<Chunk::Chunk *> to_generate_shared;
std::mutex mutex_queue_mesh;
std::set<Chunk::Chunk *> to_generate;
std::set<Chunk::Chunk *> to_mesh;
std::set<Chunk::Chunk *> to_mesh_shared;
std::atomic_bool mesh_should_run;
std::atomic_bool generate_should_run;
std::atomic_bool mesh_should_run;
// update variables
uint8_t f = 0;
int rr{RENDER_DISTANCE * RENDER_DISTANCE};
glm::vec4 frustumPlanes[6];
glm::vec3 cameraPos;
int chunkX, chunkY, chunkZ;
int total{0}, toGpu{0};
// disposal
std::unordered_map<uint32_t, float> to_delete;
std::set<uint32_t> to_delete_delete;
void mesh()
{
@ -75,6 +84,7 @@ namespace chunkmanager
std::thread mesh_thread(mesh);
return mesh_thread;
}
std::thread initGenThread()
{
generate_should_run = true;
@ -90,31 +100,25 @@ namespace chunkmanager
mesh_should_run = false;
}
int total{0}, toGpu{0};
int rr{RENDER_DISTANCE * RENDER_DISTANCE};
uint8_t f = 0;
glm::vec4 frustumPlanes[6];
std::unordered_map<uint32_t, std::time_t> to_delete;
std::set<uint32_t> to_delete_delete;
glm::vec3 cameraPos = theCamera.getPos();
int chunkX, chunkY, chunkZ;
void update(float deltaTime)
{
int nUnloaded{0};
// Try to lock resources
f = 0;
f |= mutex_queue_generate.try_lock();
f |= mutex_queue_mesh.try_lock() << 1;
// Iterate over all chunks, in concentric spheres starting fron the player and going outwards
// Eq. of the sphere (x - a)² + (y - b)² + (z - c)² = r²
cameraPos = theCamera.getPos();
theCamera.getFrustumPlanes(frustumPlanes, true);
chunkX=(static_cast<int>(cameraPos.x)) / CHUNK_SIZE; chunkY=(static_cast<int>(cameraPos.y)) / CHUNK_SIZE; chunkZ=(static_cast<int>(cameraPos.z)) / CHUNK_SIZE;
chunkX=static_cast<int>(cameraPos.x) / CHUNK_SIZE;
chunkY=static_cast<int>(cameraPos.y) / CHUNK_SIZE;
chunkZ=static_cast<int>(cameraPos.z) / CHUNK_SIZE;
// Use time in float to be consistent with glfw
float currentTime = glfwGetTime();
std::time_t currentTime = std::time(nullptr);
// Check for far chunks that need to be cleaned up from memory
int nUnloaded{0};
for(const auto& n : chunks){
Chunk::Chunk* c = n.second;
int x{(int)(c->getPosition().x)};
@ -130,6 +134,8 @@ namespace chunkmanager
delete chunks.at(n.first);
chunks.erase(n.first);
nUnloaded++;
// Delete afterwards to avoid exception due to invalid iterators
to_delete_delete.insert(n.first);
}
}
@ -137,6 +143,9 @@ namespace chunkmanager
to_delete_delete.clear();
if(nUnloaded) std::cout << "Unloaded " << nUnloaded << " chunks\n";
// Iterate over all chunks, in concentric spheres starting fron the player and going
// outwards. Alternate left and right
// Eq. of the sphere (x - a)² + (y - b)² + (z - c)² = r²
// Possible change: coordinates everything at the origin, then translate later?
// Step 1. Eq. of a circle. Fix the x coordinate, get the 2 possible y's
@ -144,16 +153,9 @@ namespace chunkmanager
bool b = true;
while (xp <= RENDER_DISTANCE)
{
if (b)
{
x = chunkX + xp;
}
else
{
x = chunkX - xp;
}
// for (int x = chunkX - RENDER_DISTANCE; x < chunkX + RENDER_DISTANCE; x++)
// {
// Alternate between left and right
if (b) x = chunkX + xp;
else x = chunkX - xp;
// Possible optimization: use sqrt lookup
int y1 = sqrt((rr) - (x - chunkX) * (x - chunkX)) + chunkY;
@ -192,7 +194,6 @@ namespace chunkmanager
else
k = z;
// uint32_t is fine, since i'm limiting the coordinate to only use up to ten bits (1024). There's actually two spare bits
uint32_t in = calculateIndex(i, j, k);
chunkmanager::updateChunk(in, i, j, k);
}
@ -203,10 +204,7 @@ namespace chunkmanager
xp++;
b = true;
}
else
{
b = false;
}
else b = false;
}
//std::cout << "Chunks to mesh: " << to_mesh.size() << "\n";
//std::cout << "Chunks to generate: " << to_generate.size() << "\n";
@ -214,6 +212,8 @@ namespace chunkmanager
//total = 0;
//toGpu = 0;
// Unlock mutexes if previously locked. Unlocking a mutex not locked by the current thread
// or already locked is undefined behaviour, so checking has to be done
if ((f & 1))
mutex_queue_generate.unlock();
if ((f & 2))
@ -221,8 +221,10 @@ namespace chunkmanager
}
// Generation and meshing happen in two separate threads from the main one
// Chunk states are used to decide which actions need to be done on the chunk and queues+mutexes to pass the chunks between the threads
// Uploading data to GPU still needs to be done in the main thread, or another OpenGL context needs to be opened, which further complicates stuff
// Chunk states are used to decide which actions need to be done on the chunk and sets+mutexes
// to pass the chunks to be operated on between the threads.
// Uploading data to GPU still needs to be done in the main thread, or another OpenGL context
// needs to be opened, which further complicates stuff.
void updateChunk(uint32_t index, uint16_t i, uint16_t j, uint16_t k)
{
if (chunks.find(index) == chunks.end())
@ -252,6 +254,7 @@ namespace chunkmanager
else
{
if (!c->getState(Chunk::CHUNK_STATE_MESH_LOADED)) chunkmesher::sendtogpu(c);
// Frustum Culling of chunk
total++;
@ -259,18 +262,15 @@ namespace chunkmanager
glm::vec4 chunkW = glm::vec4(chunk.x*static_cast<float>(CHUNK_SIZE), chunk.y*static_cast<float>(CHUNK_SIZE), chunk.z*static_cast<float>(CHUNK_SIZE),1.0);
glm::mat4 model = glm::translate(glm::mat4(1.0), ((float)CHUNK_SIZE) * chunk);
// 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
bool out=false;
// First test, check if all the corners of the chunk are outside any of the
// planes
for(int p = 0; p < 6; p++){
int a{0};
for(int i = 0; i < 8; i++) {
a+=glm::dot(frustumPlanes[p], glm::vec4(chunkW.x + ((float)(i & 1))*CHUNK_SIZE, chunkW.y
+ ((float)((i
& 2) >> 1))*CHUNK_SIZE, chunkW.z + ((float)((i & 4) >>
2))*CHUNK_SIZE, 1.0)) < 0.0;
}
for(int p = 0; p < 6; p++){
a = 0;
for(int i = 0; i < 8; i++) a += glm::dot(frustumPlanes[p], glm::vec4(chunkW.x + ((float)(i & 1))*CHUNK_SIZE, chunkW.y
+ ((float)((i & 2) >> 1))*CHUNK_SIZE, chunkW.z + ((float)((i & 4) >> 2))*CHUNK_SIZE, 1.0)) < 0.0;
if(a==8){
out=true;
@ -292,7 +292,7 @@ namespace chunkmanager
void blockpick(bool place){
// cast a ray from the camera in the direction pointed by the camera itself
glm::vec3 pos = cameraPos;
for(float t = 0.0; t <= 25.0; t += 0.5){
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();
@ -356,6 +356,7 @@ namespace chunkmanager
}
}
// uint32_t is fine, since i'm limiting the coordinate to only use up to ten bits (1024). There's actually two spare bits
uint32_t calculateIndex(uint16_t i, uint16_t j, uint16_t k){
return i | (j << 10) | (k << 20);
}

View File

@ -27,18 +27,14 @@ void mesh(Chunk::Chunk* chunk)
* across different planes everytime I change dimension without having to
* write 3 separate 3-nested-for-loops
*/
/*
* It's not feasible to just create a new mesh everytime a quad needs placing.
* This ends up creating TONS of meshes and the game will just lag out.
* As I did in the past, it's better to create a single mesh for each chunk,
* containing all the quads. In the future, maybe translucent blocks and liquids
* will need a separate mesh, but still on a per-chunk basis
*/
// Cleanup previous data
chunk->vertices.clear();
chunk->indices.clear();
chunk->colors.clear();
chunk->vIndex = 0;
// Abort if chunk is empty
if(chunk->getState(Chunk::CHUNK_STATE_EMPTY)) return;
// convert tree to array since it is easier to work with it
@ -95,7 +91,8 @@ void mesh(Chunk::Chunk* chunk)
// Additionally checking whether b1 and b2 are AIR or Block::NULLBLK allows face culling,
// thus not rendering faces that cannot be seen
// Removing the control for Block::NULLBLK disables chunk borders
// Removing the control for Block::NULLBLK disables chunk borders, which is
// not always wanted and needs further checking
// This can be surely refactored in something that isn't such a big one-liner
mask[n++] = b1 != Block::NULLBLK && b2 != Block::NULLBLK && b1 == b2 ? Block::NULLBLK
: backFace ? b1 == Block::AIR || b1 == Block::NULLBLK ? b2 : Block::NULLBLK
@ -209,12 +206,16 @@ void sendtogpu(Chunk::Chunk* chunk)
glBindVertexArray(0);
// save the number of indices of the mesh, it is needed later for drawing
chunk->vIndex = (GLuint)(chunk->indices.size());
// once data has been sent to the GPU, it can be cleared from system RAM
chunk->vertices.clear();
chunk->indices.clear();
chunk->colors.clear();
}
// mark the chunk mesh has loaded on GPU
chunk->setState(Chunk::CHUNK_STATE_MESH_LOADED, true);
}
@ -274,7 +275,7 @@ void quad(Chunk::Chunk* chunk, glm::vec3 bottomLeft, glm::vec3 topLeft, glm::vec
}
chunk->vIndex += 4;
// ugly switch case
// ugly switch case for colors
GLfloat r, g, b;
switch (block)
{

View File

@ -79,8 +79,6 @@ int main()
lastFPSFrame = currentFrame;
}
if(glfwGetTime() - lastBlockPick > 0.2) blockpick = false;
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
@ -90,6 +88,9 @@ int main()
// Camera
theCamera.update(window, deltaTime);
// Reset blockping timeout if 200ms have passed
if(glfwGetTime() - lastBlockPick > 0.1) blockpick = false;
// ChunkManager
chunkmanager::update(deltaTime);
@ -98,14 +99,14 @@ int main()
glfwPollEvents();
}
// Stop threads and wait for them to finish
chunkmanager::stopGenThread();
chunkmanager::stopMeshThread();
genThread.join();
meshThread.join();
// Cleanup allocated memory
chunkmanager::destroy();
delete theShader;
@ -128,16 +129,20 @@ 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;
}

View File

@ -4,6 +4,7 @@
#include <cstdint>
// http://and-what-happened.blogspot.com/2011/08/fast-2d-and-3d-hilbert-curves-and.html
namespace SpaceFilling
{
uint32_t MortonToHilbert3D(const uint32_t morton, const uint32_t bits)

View File

@ -1,19 +1,19 @@
#include "utils.hpp"
bool utils::withinDistance(int startx, int starty, int startz, int x, int y, int z, int dist){
bool utils::withinDistance(int startx, int starty, int startz, int x, int y, int z, int dist)
{
return (x-startx)*(x-startx) + (y - starty)*(y-starty) + (z-startz)*(z-startz) <= dist*dist;
}
// https://stackoverflow.com/questions/20266201/3d-array-1d-flat-indexing
//flatten 3d coords to 1d array cords
int utils::coord3DTo1D (int x, int y, int z, int maxX, int maxY, int maxZ){
return x + maxX * (y + z * maxY);
}
//flatten 3d coords to 1d array cords
// https://stackoverflow.com/questions/20266201/3d-array-1d-flat-indexing
int utils::coord3DTo1D (int x, int y, int z, int maxX, int maxY, int maxZ) { return x + maxX * (y + z * maxY); }
std::array<int, 3> utils::coord1DTo3D(int idx, int maxX, int maxY, int mazZ){
std::array<int, 3> utils::coord1DTo3D(int idx, int maxX, int maxY, int mazZ)
{
int z = idx / (maxX * maxY);
idx -= (z * maxX* maxY);
int y = idx / maxX;
int x = idx % maxX;
return std::array<int, 3> {x, y, z};
}
}