7
\$\begingroup\$

I have tried going back to C++ after long time of mainly doing C#. I realize that the code is far from perfect, however I would really appreciate if someone could point of what exactly is wrong and how to fix it, so I can learn how things should be done. (just for code design, not asking to fix any logic errors)

What it does, is generating a Mesh from noise data (minecraft like game terrain) and creating chunks on runtime through threading. It has basic lightning and texture arrays implemented.

Files that were not written by me and shouldn't be reviewed:

  • anything that starts with imgui

  • glsl.h/cpp

I will be very thankful on your opinions how to fix this codes design.

https://github.com/AleCie/NGine

Now, I don't know if it is allowed to ask for a review of code on github, but I hope this won't be an issue, since it is asking me to input at least three lines of code here, I will post Chunk.h file to give you some idea what you will be dealing with.

Edit: Including code for review over here as suggested with posting guidelines:

Chunk.h

#pragma once

#include <memory>

#include <glm/glm.hpp>

#include "Shader.h"
#include "Texture.h"
#include "Mesh.h"

#include <thread>
#include <future>

class Camera;

class Chunk
{
public:
    Chunk();
    ~Chunk();

    void Create(glm::vec3 position, std::shared_ptr<Shader> shader);
    void Update(Camera* cam, float dt);
    void Render(Camera *cam);

    //void RebuildMesh();

    glm::vec3 GetPosition();
    glm::mat4 GetWorldMatrix();

    static const int ChunkSize = 16;

    float NoiseTreshold = 0.0f;
    float NoiseScale = 1.0f;

    static int GlobalChunkVertexCount;

    bool ShouldBeDeleted = false;

    int Data[ChunkSize][ChunkSize][ChunkSize];

    std::weak_ptr<Chunk> TopChunk;
    std::weak_ptr<Chunk> BottomChunk;
    std::weak_ptr<Chunk> LeftChunk;
    std::weak_ptr<Chunk> RightChunk;
    std::weak_ptr<Chunk> FrontChunk;
    std::weak_ptr<Chunk> BackChunk;

    bool ShouldRebuild = false;

private:

    void Create();
    void CleanMesh();

    void CreateVoxelData();
    void CreateMesh();
    void CreateOpenGLMesh();
    void CreateChunkThreadFunc(bool& result, std::atomic<bool>& shouldTerminate, std::atomic<bool>& wasTerminated);

    bool ShouldAddTop(int x, int y, int z);
    bool ShouldAddBottom(int x, int y, int z);
    bool ShouldAddLeft(int x, int y, int z);
    bool ShouldAddRight(int x, int y, int z);
    bool ShouldAddFront(int x, int y, int z);
    bool ShouldAddBack(int x, int y, int z);

    void AddTopFace(int x, int y, int z, int& idx);
    void AddBottomFace(int x, int y, int z, int& idx);
    void AddFrontFace(int x, int y, int z, int& idx);
    void AddBackFace(int x, int y, int z, int& idx);
    void AddLeftFace(int x, int y, int z, int& idx);
    void AddRightFace(int x, int y, int z, int& idx);





    std::unique_ptr<Mesh> ChunkMesh;
    std::shared_ptr<Shader> ChunkShader;

    glm::vec3 Position = glm::vec3(0);

    bool IsChunkEmpty = true;
    bool DidThreadFinish = false;
    bool WasMeshCreated = false;

    std::thread ChunkThread;
    std::atomic<bool> ShouldTerminateThread = false;
    std::atomic<bool> WasThreadTerminated = false;
};

Chunk.cpp

#include "Chunk.h"

#include <functional>

#include "Camera.h"
#include "ChunkManager.h"

#include "Filepath.h"

#include <glm/ext/matrix_transform.hpp>
#include "FastNoiseSIMD/FastNoiseSIMD.h"

//#include <pthread.h>

int Chunk::GlobalChunkVertexCount = 0;

Chunk::Chunk()
{

}

Chunk::~Chunk()
{
    //pthread_cancel(ChunkThread);
}

void Chunk::CreateChunkThreadFunc(bool &result, std::atomic<bool>& shouldTerminate, std::atomic<bool>& wasTerminated)
{
    if (ShouldTerminateThread == true)
    {
        wasTerminated = true;
        return;
    }
    CreateVoxelData();
    if (ShouldTerminateThread == true)
    {
        wasTerminated = true;
        return;
    }
    CreateMesh();
    if (ShouldTerminateThread == true)
    {
        wasTerminated = true;
        return;
    }

    wasTerminated = true;
    result = true;
}

void Chunk::Create(glm::vec3 position, std::shared_ptr<Shader> shader)
{
    Position = position;
    ChunkShader = shader;

    /*auto f = [&](bool &b) {
        CreateVoxelData();
        CreateMesh();
        b = true;
    };*/

    //std::thread t1(&Chunk::CreateChunkThreadFunc, std::ref(DidThreadFinish));
    //std::thread t1([this, &DidThreadFinish]() { this->CreateChunkThreadFunc(DidThreadFinish); });
    //std::thread t1(std::mem_fun(&Chunk::CreateChunkThreadFunc), this, std::ref(DidThreadFinish)));

    ChunkThread = std::thread([this]() { this->CreateChunkThreadFunc(this->DidThreadFinish, this->ShouldTerminateThread, this->WasThreadTerminated); });
    ChunkThread.detach();

    //t1.join();



    //ChunkShader = std::unique_ptr<Shader>(new Shader((fp::ShadersFolder + shaderName + fp::ExtVertex).c_str(), (fp::ShadersFolder + shaderName + fp::ExtFragment).c_str()));
}

void Chunk::Update(Camera *cam, float dt)
{
    if (DidThreadFinish == true && WasMeshCreated == false)
    {
        CreateOpenGLMesh();

        //std::cout << "Vertex count: " << Chunk::GlobalChunkVertexCount << std::endl;
        WasMeshCreated = true;
    }

    // get distance
    glm::vec3 distance = Position - cam->GetPosition();
    bool shouldDelete = false;
    if (distance.x > ChunkManager::ChunkGenRadius * Chunk::ChunkSize)
    {
        shouldDelete = true;
    }
    if (distance.y > ChunkManager::ChunkGenRadius * Chunk::ChunkSize)
    {
        shouldDelete = true;
    }
    if (distance.z > ChunkManager::ChunkGenRadius * Chunk::ChunkSize)
    {
        shouldDelete = true;
    }

    // flags for object deletion
    // erase if too big (first terminate thread
    if (shouldDelete)
    {
        //ShouldBeDeleted = true;
        ShouldTerminateThread = true;
        //ChunkThread.join();

    }

    if (WasThreadTerminated == true && shouldDelete)
    {
        ShouldBeDeleted = true;
    }

    //flags for mesh rebuild

    if (ShouldRebuild)
    {
        if (WasThreadTerminated == false || DidThreadFinish == false)
        {
            //ShouldTerminateThread = true;
        }
        else
        {
            CleanMesh();
            Create();

            ShouldRebuild = false;
        }
    }


}

void Chunk::Render(Camera *cam)
{
    if (IsChunkEmpty == false && DidThreadFinish == true && WasMeshCreated == true)
    {
        ChunkMesh->Render(ChunkShader.get(), cam);
    }
}

/*void Chunk::RebuildMesh()
{
    IsChunkEmpty = true;
    ChunkMesh->Vertices.clear();
    ChunkMesh->Indices.clear();
    ChunkMesh->Normals.clear();
    ChunkMesh->UVs.clear();
    ChunkMesh.reset();
    CreateVoxelData();
    CreateMesh();
}*/

glm::vec3 Chunk::GetPosition()
{
    return Position;
}

glm::mat4 Chunk::GetWorldMatrix()
{
    return ChunkMesh->WorldMatrix;
}

void Chunk::Create()
{
    ChunkThread = std::thread([this]() { this->CreateChunkThreadFunc(this->DidThreadFinish, this->ShouldTerminateThread, this->WasThreadTerminated); });
    ChunkThread.detach();
}

void Chunk::CleanMesh()
{
    IsChunkEmpty = true;

    ChunkMesh->Vertices.clear();
    ChunkMesh->Indices.clear();
    ChunkMesh->Normals.clear();
    ChunkMesh->UVs.clear();

    ChunkMesh.reset();
}

void Chunk::CreateVoxelData()
{
    FastNoiseSIMD* myNoise = FastNoiseSIMD::NewFastNoiseSIMD();

    // Get a set of 16 x 16 x 16 Simplex Fractal noise
    float* noiseSet = myNoise->GetSimplexFractalSet(Position.x, Position.y, Position.z, ChunkSize, ChunkSize, ChunkSize, NoiseScale);
    int noiseIdx = 0;

    for (int x = 0; x < ChunkSize; x++)
    {
        for (int y = 0; y < ChunkSize; y++)
        {
            for (int z = 0; z < ChunkSize; z++)
            {

                if (noiseSet[noiseIdx] > NoiseTreshold)
                {
                    Data[x][y][z] = 0;
                }
                else
                {
                    Data[x][y][z] = -1;
                }
                noiseIdx++;
                /*if (y < 8)
                {
                    if (x < 6 && z < 6)
                    {
                        Data[x][y][z] = 0;
                    }
                    else
                    {
                        Data[x][y][z] = 1;
                    }

                } 
                else if ( y > 8)
                {
                    Data[x][y][z] = -1;
                }
                if (y == 9 || y == 10)
                {
                    if (x > 4 && x < 12 && z > 4 && z < 12)
                    {
                        Data[x][y][z] = 1;
                    }
                }*/
            }
        }
    }

    FastNoiseSIMD::FreeNoiseSet(noiseSet);
}

void Chunk::CreateMesh()
{
    ChunkMesh = std::unique_ptr<Mesh>(new Mesh());
    ChunkMesh->IndicesEnabled = true;
    ChunkMesh->UVsEnabled = true;
    ChunkMesh->UVsAttribute = 1;
    ChunkMesh->NormalsEnabled = true;
    ChunkMesh->NormalsAttribute = 2;
    ChunkMesh->TexIDEnabled = true;
    ChunkMesh->TexIDAttribute = 3;

    int idx = 0;
    for (int x = 0; x < ChunkSize; x++)
    {
        for (int y = 0; y < ChunkSize; y++)
        {
            for (int z = 0; z < ChunkSize; z++)
            {
                if (Data[x][y][z] >= 0) // voxel exists
                {
                    if (ShouldAddTop(x, y, z)) AddTopFace(x, y, z, idx);
                    if (ShouldAddBottom(x, y, z)) AddBottomFace(x, y, z, idx);
                    if (ShouldAddFront(x, y, z)) AddFrontFace(x, y, z, idx);
                    if (ShouldAddBack(x, y, z)) AddBackFace(x, y, z, idx);
                    if (ShouldAddLeft(x, y, z)) AddLeftFace(x, y, z, idx);
                    if (ShouldAddRight(x, y, z)) AddRightFace(x, y, z, idx);
                }
                //if (ShouldAddTop(x, y, z)) AddTopFace(x, y, z, idx);

            }
        }
    }


}

void Chunk::CreateOpenGLMesh()
{
    GlobalChunkVertexCount += ChunkMesh->Vertices.size();

    if (ChunkMesh->Vertices.size() > 0)
    {
        ChunkMesh->Create(ChunkShader.get());

        IsChunkEmpty = false;
    }
    else
    {
        IsChunkEmpty = true;
    }

    ChunkMesh->WorldMatrix = glm::translate(ChunkMesh->WorldMatrix, Position);
}

bool Chunk::ShouldAddTop(int x, int y, int z)
{
    if (y + 1 >= ChunkSize)
    {
        // out of bounds, decide if should display quad, or fetch data from child chunk if exists
        if (auto tch = TopChunk.lock()) //top chunk exist, check value from there
        {
            if (tch->Data[x][0][z] >= 0) // voxel in top chunk is full, dont display
            {
                return false;
            }
            else
            {
                return true;
            }
        }
        else
        {
            return true;
        }
    }

    if (Data[x][y + 1][z] >= 0) // if Data is greater than zero, means there is a voxel there, so should not add that face
    {
        return false;
    }
    else
    {
        return true;
    }
}

bool Chunk::ShouldAddBottom(int x, int y, int z)
{
    if (y - 1 < 0)
    {
        // out of bounds, decide if should display quad, or fetch data from child chunk if exists
        if (auto bch = BottomChunk.lock()) //top chunk exist, check value from there
        {
            if (bch->Data[x][Chunk::ChunkSize - 1][z] >= 0) // voxel in top chunk is full, dont display
            {
                return false;
            }
            else
            {
                return true;
            }
        }
        else
        {
            return true;
        }
    }

    if (Data[x][y - 1][z] >= 0) // if Data is greater than zero, means there is a voxel to display
    {
        return false;
    }
    else
    {
        return true;
    }
}

bool Chunk::ShouldAddLeft(int x, int y, int z)
{
    if (x - 1 < 0)
    {
        // out of bounds, decide if should display quad, or fetch data from child chunk if exists
        if (auto lch = LeftChunk.lock()) //top chunk exist, check value from there
        {
            if (lch->Data[Chunk::ChunkSize - 1][y][z] >= 0) // voxel in top chunk is full, dont display
            {
                return false;
            }
            else
            {
                return true;
            }
        }
        else
        {
            return true;
        }
    }

    if (Data[x - 1][y][z] >= 0) // if Data is greater than zero, means there is a voxel to display
    {
        return false;
    }
    else
    {
        return true;
    }
}

bool Chunk::ShouldAddRight(int x, int y, int z)
{
    if (x + 1 >= ChunkSize)
    {
        // out of bounds, decide if should display quad, or fetch data from child chunk if exists
        if (auto rch = RightChunk.lock()) //top chunk exist, check value from there
        {
            if (rch->Data[0][y][z] >= 0) // voxel in top chunk is full, dont display
            {
                return false;
            }
            else
            {
                return true;
            }
        }
        else
        {
            return true;
        }
    }

    if (Data[x + 1][y][z] >= 0) // if Data is greater than zero, means there is a voxel to display
    {
        return false;
    }
    else
    {
        return true;
    }
}

bool Chunk::ShouldAddFront(int x, int y, int z)
{
    if (z + 1 >= ChunkSize)
    {
        // out of bounds, decide if should display quad, or fetch data from child chunk if exists
        if (auto fch = FrontChunk.lock()) //top chunk exist, check value from there
        {
            if (fch->Data[x][y][Chunk::ChunkSize - 1] >= 0) // voxel in top chunk is full, dont display
            {
                return false;
            }
            else
            {
                return true;
            }
        }
        else
        {
            return true;
        }
    }

    if (Data[x][y][z + 1] >= 0) // if Data is greater than zero, means there is a voxel to display
    {
        return false;
    }
    else
    {
        return true;
    }
}

bool Chunk::ShouldAddBack(int x, int y, int z)
{
    if (z - 1 < 0)
    {
        // out of bounds, decide if should display quad, or fetch data from child chunk if exists
        if (auto bch = BackChunk.lock()) //top chunk exist, check value from there
        {
            if (bch->Data[x][y][0] >= 0) // voxel in top chunk is full, dont display
            {
                return false;
            }
            else
            {
                return true;
            }
        }
        else
        {
            return true;
        }
    }

    if (Data[x][y][z - 1] >= 0) // if Data is greater than zero, means there is a voxel to display
    {
        return false;
    }
    else
    {
        return true;
    }
}

void Chunk::AddTopFace(int x, int y, int z, int& idx)
{
    // top face verts

    ChunkMesh->Vertices.push_back(1 + x); ChunkMesh->Vertices.push_back(1 + y); ChunkMesh->Vertices.push_back(1 + z);
    ChunkMesh->Vertices.push_back(1 + x); ChunkMesh->Vertices.push_back(1 + y); ChunkMesh->Vertices.push_back(0 + z);
    ChunkMesh->Vertices.push_back(0 + x); ChunkMesh->Vertices.push_back(1 + y); ChunkMesh->Vertices.push_back(0 + z);
    ChunkMesh->Vertices.push_back(0 + x); ChunkMesh->Vertices.push_back(1 + y); ChunkMesh->Vertices.push_back(1 + z);

    // indices

    ChunkMesh->Indices.push_back(idx + 0); ChunkMesh->Indices.push_back(idx + 1); ChunkMesh->Indices.push_back(idx + 3);
    ChunkMesh->Indices.push_back(idx + 1); ChunkMesh->Indices.push_back(idx + 2); ChunkMesh->Indices.push_back(idx + 3);
    idx += 4;

    // normals

    ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(1); ChunkMesh->Normals.push_back(0);
    ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(1); ChunkMesh->Normals.push_back(0);
    ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(1); ChunkMesh->Normals.push_back(0);
    ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(1); ChunkMesh->Normals.push_back(0);

    // uvs

    ChunkMesh->UVs.push_back(0); ChunkMesh->UVs.push_back(0);
    ChunkMesh->UVs.push_back(1); ChunkMesh->UVs.push_back(0);
    ChunkMesh->UVs.push_back(1); ChunkMesh->UVs.push_back(1);
    ChunkMesh->UVs.push_back(0); ChunkMesh->UVs.push_back(1);

    // texid

    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
}

void Chunk::AddBottomFace(int x, int y, int z, int& idx)
{
    // bottom face
    ChunkMesh->Vertices.push_back(1 + x); ChunkMesh->Vertices.push_back(y); ChunkMesh->Vertices.push_back(1 + z);
    ChunkMesh->Vertices.push_back(1 + x); ChunkMesh->Vertices.push_back(y); ChunkMesh->Vertices.push_back(0 + z);
    ChunkMesh->Vertices.push_back(0 + x); ChunkMesh->Vertices.push_back(y); ChunkMesh->Vertices.push_back(0 + z);
    ChunkMesh->Vertices.push_back(0 + x); ChunkMesh->Vertices.push_back(y); ChunkMesh->Vertices.push_back(1 + z);

    // bottom

    ChunkMesh->Indices.push_back(idx + 3); ChunkMesh->Indices.push_back(idx + 1); ChunkMesh->Indices.push_back(idx + 0);
    ChunkMesh->Indices.push_back(idx + 3); ChunkMesh->Indices.push_back(idx + 2); ChunkMesh->Indices.push_back(idx + 1);
    idx += 4;

    ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(-1); ChunkMesh->Normals.push_back(0);
    ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(-1); ChunkMesh->Normals.push_back(0);
    ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(-1); ChunkMesh->Normals.push_back(0);
    ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(-1); ChunkMesh->Normals.push_back(0);

    // uvs

    ChunkMesh->UVs.push_back(0); ChunkMesh->UVs.push_back(0);
    ChunkMesh->UVs.push_back(1); ChunkMesh->UVs.push_back(0);
    ChunkMesh->UVs.push_back(1); ChunkMesh->UVs.push_back(1);
    ChunkMesh->UVs.push_back(0); ChunkMesh->UVs.push_back(1);

    // texid

    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
}

void Chunk::AddFrontFace(int x, int y, int z, int& idx)
{
    // front face verts
    ChunkMesh->Vertices.push_back(1 + x); ChunkMesh->Vertices.push_back(1 + y); ChunkMesh->Vertices.push_back(z + 1);
    ChunkMesh->Vertices.push_back(1 + x); ChunkMesh->Vertices.push_back(y); ChunkMesh->Vertices.push_back(z + 1);
    ChunkMesh->Vertices.push_back(0 + x); ChunkMesh->Vertices.push_back(y); ChunkMesh->Vertices.push_back(z + 1);
    ChunkMesh->Vertices.push_back(0 + x); ChunkMesh->Vertices.push_back(1 + y); ChunkMesh->Vertices.push_back(z + 1);

    // indices

    ChunkMesh->Indices.push_back(idx + 3); ChunkMesh->Indices.push_back(idx + 1); ChunkMesh->Indices.push_back(idx + 0);
    ChunkMesh->Indices.push_back(idx + 3); ChunkMesh->Indices.push_back(idx + 2); ChunkMesh->Indices.push_back(idx + 1);
    idx += 4;

    // normals

    ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(1);
    ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(1);
    ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(1);
    ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(1);

    // uvs

    ChunkMesh->UVs.push_back(0); ChunkMesh->UVs.push_back(0);
    ChunkMesh->UVs.push_back(1); ChunkMesh->UVs.push_back(0);
    ChunkMesh->UVs.push_back(1); ChunkMesh->UVs.push_back(1);
    ChunkMesh->UVs.push_back(0); ChunkMesh->UVs.push_back(1);

    // texid

    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
}

void Chunk::AddBackFace(int x, int y, int z, int& idx)
{
    // back face
    ChunkMesh->Vertices.push_back(1 + x); ChunkMesh->Vertices.push_back(1 + y); ChunkMesh->Vertices.push_back(z);
    ChunkMesh->Vertices.push_back(1 + x); ChunkMesh->Vertices.push_back(y); ChunkMesh->Vertices.push_back(z);
    ChunkMesh->Vertices.push_back(0 + x); ChunkMesh->Vertices.push_back(y); ChunkMesh->Vertices.push_back(z);
    ChunkMesh->Vertices.push_back(0 + x); ChunkMesh->Vertices.push_back(1 + y); ChunkMesh->Vertices.push_back(z);

    // back

    ChunkMesh->Indices.push_back(idx + 0); ChunkMesh->Indices.push_back(idx + 1); ChunkMesh->Indices.push_back(idx + 3);
    ChunkMesh->Indices.push_back(idx + 1); ChunkMesh->Indices.push_back(idx + 2); ChunkMesh->Indices.push_back(idx + 3);
    idx += 4;

    ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(-1);
    ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(-1);
    ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(-1);
    ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(-1);

    // uvs

    ChunkMesh->UVs.push_back(0); ChunkMesh->UVs.push_back(0);
    ChunkMesh->UVs.push_back(1); ChunkMesh->UVs.push_back(0);
    ChunkMesh->UVs.push_back(1); ChunkMesh->UVs.push_back(1);
    ChunkMesh->UVs.push_back(0); ChunkMesh->UVs.push_back(1);

    // texid

    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
}

void Chunk::AddLeftFace(int x, int y, int z, int& idx)
{
    // left face
    ChunkMesh->Vertices.push_back(x); ChunkMesh->Vertices.push_back(1 + y); ChunkMesh->Vertices.push_back(z + 1);
    ChunkMesh->Vertices.push_back(x); ChunkMesh->Vertices.push_back(y); ChunkMesh->Vertices.push_back(z + 1);
    ChunkMesh->Vertices.push_back(x); ChunkMesh->Vertices.push_back(y); ChunkMesh->Vertices.push_back(z);
    ChunkMesh->Vertices.push_back(x); ChunkMesh->Vertices.push_back(1 + y); ChunkMesh->Vertices.push_back(z);

    // left

    ChunkMesh->Indices.push_back(idx + 3); ChunkMesh->Indices.push_back(idx + 1); ChunkMesh->Indices.push_back(idx + 0);
    ChunkMesh->Indices.push_back(idx + 3); ChunkMesh->Indices.push_back(idx + 2); ChunkMesh->Indices.push_back(idx + 1);
    idx += 4;

    ChunkMesh->Normals.push_back(-1); ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(0);
    ChunkMesh->Normals.push_back(-1); ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(0);
    ChunkMesh->Normals.push_back(-1); ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(0);
    ChunkMesh->Normals.push_back(-1); ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(0);

    // uvs

    ChunkMesh->UVs.push_back(0); ChunkMesh->UVs.push_back(0);
    ChunkMesh->UVs.push_back(1); ChunkMesh->UVs.push_back(0);
    ChunkMesh->UVs.push_back(1); ChunkMesh->UVs.push_back(1);
    ChunkMesh->UVs.push_back(0); ChunkMesh->UVs.push_back(1);

    // texid

    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
}

void Chunk::AddRightFace(int x, int y, int z, int& idx)
{
    // right face
    ChunkMesh->Vertices.push_back(x + 1); ChunkMesh->Vertices.push_back(1 + y); ChunkMesh->Vertices.push_back(z + 1);
    ChunkMesh->Vertices.push_back(x + 1); ChunkMesh->Vertices.push_back(y); ChunkMesh->Vertices.push_back(z + 1);
    ChunkMesh->Vertices.push_back(x + 1); ChunkMesh->Vertices.push_back(y); ChunkMesh->Vertices.push_back(z);
    ChunkMesh->Vertices.push_back(x + 1); ChunkMesh->Vertices.push_back(1 + y); ChunkMesh->Vertices.push_back(z);

    // right

    ChunkMesh->Indices.push_back(idx + 0); ChunkMesh->Indices.push_back(idx + 1); ChunkMesh->Indices.push_back(idx + 3);
    ChunkMesh->Indices.push_back(idx + 1); ChunkMesh->Indices.push_back(idx + 2); ChunkMesh->Indices.push_back(idx + 3);
    idx += 4;

    ChunkMesh->Normals.push_back(1); ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(0);
    ChunkMesh->Normals.push_back(1); ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(0);
    ChunkMesh->Normals.push_back(1); ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(0);
    ChunkMesh->Normals.push_back(1); ChunkMesh->Normals.push_back(0); ChunkMesh->Normals.push_back(0);

    // uvs

    ChunkMesh->UVs.push_back(0); ChunkMesh->UVs.push_back(0);
    ChunkMesh->UVs.push_back(1); ChunkMesh->UVs.push_back(0);
    ChunkMesh->UVs.push_back(1); ChunkMesh->UVs.push_back(1);
    ChunkMesh->UVs.push_back(0); ChunkMesh->UVs.push_back(1);

    // texid

    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
    ChunkMesh->TexIDs.push_back((float)Data[x][y][z]);
}

ChunkManager.h

#pragma once

/*template<typename T> struct matrix
{
    matrix(unsigned m, unsigned n) : m(m), n(n), vs(m* n) {}
T& operator ()(unsigned i, unsigned j) { 
return vs[i + m * j]; 
} 
private: 
    unsigned m; 
    unsigned n; 
    std::vector<T> vs;
};
//column-major/opengl: vs[i + m * j], row-major/c++: vs[n * i + j] 
*/

#include <memory>
#include <unordered_map> 
#include <map>

#include "Chunk.h"
#include <glm/glm.hpp>
#include <glm/gtx/hash.hpp>


class Shader;
class Camera;

class ChunkManager
{
public:
    ChunkManager();
    ~ChunkManager();

    void CreateFixedWorld(int width, int height, int depth, std::shared_ptr<Shader> shader);
    void Update(Camera* camera, float dt);
    void Render(Camera* camera);

    static int ChunkGenRadius;

private:


    int Width = 0, Height = 0, Depth = 0;
    Chunk* Chunks;
    std::shared_ptr<Shader> ChunkShader;

    std::unordered_map<glm::vec3, std::shared_ptr<Chunk>> ChunkMap;

};

ChunkManager.cpp

#include "ChunkManager.h"


#include "Shader.h"
#include "Camera.h"


#include <chrono>
#include <iostream>

int ChunkManager::ChunkGenRadius = 5;

ChunkManager::ChunkManager()
{
}

ChunkManager::~ChunkManager()
{
}

void ChunkManager::CreateFixedWorld(int width, int height, int depth, std::shared_ptr<Shader> shader)
{
    Width = width;
    Height = height;
    Depth = depth;

    ChunkShader = shader;

    // Record start time
    auto start = std::chrono::high_resolution_clock::now();
    Chunks = new Chunk[width * height * depth];

    for (int x = 0; x < width; x++)
    {
        for (int y = 0; y < height; y++)
        {
            for (int z = 0; z < depth; z++)
            {
                Chunks[x + width * (y + depth * z)].Create(glm::vec3(x * Chunk::ChunkSize, y * Chunk::ChunkSize, z * Chunk::ChunkSize), shader);
            }
        }
    }

    auto finish = std::chrono::high_resolution_clock::now();

    std::chrono::duration<double> elapsed = finish - start;
    std::cout << "Elapsed time: " << elapsed.count() << " s\n";

    std::cout << "Vertex count: " << Chunk::GlobalChunkVertexCount << std::endl;
}

void ChunkManager::Update(Camera* camera, float dt)
{
    //figure out chunk where camera is
    glm::vec3 camChunkPos = camera->GetPosition() / glm::vec3(Chunk::ChunkSize, Chunk::ChunkSize, Chunk::ChunkSize);
    camChunkPos = glm::vec3(floorf(camChunkPos.x) * Chunk::ChunkSize, floorf(camChunkPos.y) * Chunk::ChunkSize, floorf(camChunkPos.z) * Chunk::ChunkSize);

    // loop for theoretical chunks around camera
    for (int x = -ChunkGenRadius; x < ChunkGenRadius; x++)
    {
        for (int y = -ChunkGenRadius; y < ChunkGenRadius; y++)
        {
            for (int z = -ChunkGenRadius; z < ChunkGenRadius; z++)
            {
                // check if chunk exists
                glm::vec3 posToCheck = camChunkPos + glm::vec3(x * Chunk::ChunkSize, y * Chunk::ChunkSize, z * Chunk::ChunkSize);


                auto result = ChunkMap.find(posToCheck);

                if (result == ChunkMap.end()) // if doesn't exist (iterator looped through everything)
                {
                    //create chunk
                    std::shared_ptr<Chunk> newChunk = std::shared_ptr<Chunk>(new Chunk());

                    // check surrounding chunks;
                    auto topResult = ChunkMap.find(posToCheck + glm::vec3(0, Chunk::ChunkSize, 0));
                    if (topResult != ChunkMap.end())
                    {
                        newChunk->TopChunk = topResult->second;

                        topResult->second->BottomChunk = newChunk;
                        topResult->second->ShouldRebuild = true;
                    }

                    auto bottomResult = ChunkMap.find(posToCheck + glm::vec3(0, -Chunk::ChunkSize, 0));
                    if (bottomResult != ChunkMap.end())
                    {
                        newChunk->BottomChunk = bottomResult->second;

                        bottomResult->second->TopChunk = newChunk;
                        bottomResult->second->ShouldRebuild = true;
                    }

                    auto leftResult = ChunkMap.find(posToCheck + glm::vec3(-Chunk::ChunkSize, 0, 0));
                    if (leftResult != ChunkMap.end())
                    {
                        newChunk->LeftChunk = leftResult->second;

                        leftResult->second->RightChunk = newChunk;
                        leftResult->second->ShouldRebuild = true;
                    }

                    auto rightResult = ChunkMap.find(posToCheck + glm::vec3(Chunk::ChunkSize, 0, 0));
                    if (rightResult != ChunkMap.end())
                    {
                        newChunk->RightChunk = rightResult->second;

                        rightResult->second->LeftChunk = newChunk;
                        rightResult->second->ShouldRebuild = true;
                    }

                    auto frontResult = ChunkMap.find(posToCheck + glm::vec3(0, 0, Chunk::ChunkSize));
                    if (frontResult != ChunkMap.end())
                    {
                        newChunk->FrontChunk = frontResult->second;

                        frontResult->second->BackChunk = newChunk;
                        frontResult->second->ShouldRebuild = true;
                    }

                    auto backResult = ChunkMap.find(posToCheck + glm::vec3(0, 0, -Chunk::ChunkSize));
                    if (backResult != ChunkMap.end())
                    {
                        newChunk->BackChunk = backResult->second;

                        backResult->second->FrontChunk = newChunk;
                        backResult->second->ShouldRebuild = true;
                    }

                    //create and assign
                    newChunk->Create(posToCheck, ChunkShader);
                    //ChunkMap.insert(std::make_pair<glm::vec3, Chunk>(posToCheck, newChunk));
                    ChunkMap[posToCheck] = std::move(newChunk);
                }
            }
        }
    }

    for (auto it = ChunkMap.begin(); it != ChunkMap.end(); )
    {
        if(it->second != nullptr)
        if (it->second->ShouldBeDeleted)
        {
            it = ChunkMap.erase(it);
        }
        else
        {
            it->second->Update(camera, dt);
            ++it;
        }
    }
}

void ChunkManager::Render(Camera* camera)
{
    for (auto& [key, val] : ChunkMap)
    {
        val->Render(camera);
    }
}

Application.h

#pragma once

// third party
#include <GL/glew.h>
#include <GLFW/glfw3.h>

#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"

// system
#include <memory>

// custom
#include "Color.h"
#include "Camera.h"
#include "Mesh.h"
#include "CoordsGizmo.h"
#include "Texture.h"

#include "Chunk.h"
#include "ChunkManager.h"

struct DestroyGLFWwnd {

    void operator()(GLFWwindow* ptr) {
        glfwDestroyWindow(ptr);
    }

};

typedef std::unique_ptr<GLFWwindow, DestroyGLFWwnd> sptr_GLFWwindow;

class Shader;
class glShaderManager;

class Application
{
public:
    Application(int windowWidth, int windowHeight);
    ~Application();

    void Run();

private:

    void Init();
    void MainLoop();
    void Release();

    void ProcessInput();
    void UpdateLogic();
    void Render();
    void RenderUI();

    void InitGLFW();
    void InitGLEW();
    void InitOGL();
    void InitIMGUI();

    void GLFWFramebufferSizeCallback(GLFWwindow* window, int width, int height);
    void GLFWKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);

    void CalculateDeltaTime();
    void SaveLastDeltaTime();

    void ClearColor();
    void SwapBuffersPollEvents();

    void CloseOnEsc();

    void EnableCursor();
    void DisableCursor();


    sptr_GLFWwindow Window;

    Color BackgroundColor;

    int WindowWidth;
    int WindowHeight;

    float DeltaTime;
    float LastTime;

    //test

    void InitTestCode();
    void RenderTestCode();

    unsigned int VAO;

    std::unique_ptr<Shader> Sh;

    Camera MainCamera;


    bool IsMouseLookEnabled = true;
    bool IsWireframeEnabled = false;

    std::unique_ptr<Shader> ColorShader;
    std::unique_ptr<Shader> TextureArrayShader;
    std::shared_ptr<Shader> TexArrLightShader;

    std::unique_ptr<Texture> TestTexture;
    std::unique_ptr<Texture> TestTexture2;
    //Mesh coordsMesh;

    CoordsGizmo CoordsObj;

    GLuint TextureArray;

    Chunk chunk, chunk2, chunk3, chunk4;

    ChunkManager ChMgr;
};

Application.cpp

#include "Application.h"

#include <iostream>

#include "Shader.h"
#include "glsl.h"
#include <glm/gtx/transform.hpp>
#include <glm/gtc/type_ptr.hpp>

using namespace std;
Application::Application(int windowWidth, int windowHeight)
    : MainCamera(60.0f, 4.0f / 3.0f, 0.1f, 1000.0f)
{
    WindowWidth = windowWidth;
    WindowHeight = windowHeight;

    BackgroundColor = Color(0.25f, 0.5f, 1, 1);

    DeltaTime = 0;
    LastTime = 0;
}

Application::~Application()
{
    glfwTerminate();
}

void Application::Run()
{
    Init();

    MainLoop();

    Release();
}

void Application::Init()
{
    InitGLFW();
    InitGLEW();
    InitOGL();
    InitIMGUI();

    InitTestCode();
}

void Application::MainLoop()
{
    while (!glfwWindowShouldClose(Window.get()))
    {
        CalculateDeltaTime();

        ProcessInput();
        UpdateLogic();

        Render();


        SaveLastDeltaTime();
    }
}

void Application::Release()
{
}

void Application::ProcessInput()
{
    CloseOnEsc();

    MainCamera.HandleInput(Window.get(), DeltaTime, WindowWidth, WindowHeight);
    if (IsMouseLookEnabled)
    {
        MainCamera.MouseLook(Window.get(), DeltaTime, WindowWidth, WindowHeight);
    }



}

void Application::UpdateLogic()
{
    MainCamera.Update(DeltaTime);
}

void Application::Render()
{
    RenderUI();

    ClearColor();

    RenderTestCode();
    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());

    SwapBuffersPollEvents();


}

float cpos[3], tpos[3];

float noiseTreshold = 0;
float noiseScale = 1;

void Application::RenderUI()
{
    // Start the Dear ImGui frame
    ImGui_ImplOpenGL3_NewFrame();
    ImGui_ImplGlfw_NewFrame();
    ImGui::NewFrame();

    /*ImGui::Begin("Terrain Gen");
    ImGui::SliderFloat ("treshold", &noiseTreshold, -1.0f, 1.0f);
    ImGui::SliderFloat("scale", &noiseScale, 0.0f, 100.0f);
    if (ImGui::Button("generate"))                            // Buttons return true when clicked (most widgets return true when edited/activated)
    {
        chunk.NoiseTreshold = noiseTreshold;
        chunk2.NoiseTreshold = noiseTreshold;
        chunk3.NoiseTreshold = noiseTreshold;
        chunk4.NoiseTreshold = noiseTreshold;
        chunk.NoiseScale = noiseScale;
        chunk2.NoiseScale = noiseScale;
        chunk3.NoiseScale = noiseScale;
        chunk4.NoiseScale = noiseScale;
        chunk.RebuildMesh();
        chunk2.RebuildMesh();
        chunk3.RebuildMesh();
        chunk4.RebuildMesh();
    }
    ImGui::End();*/

    // 2. Show a simple window that we create ourselves. We use a Begin/End pair to created a named window.
    {
        static float f = 0.0f;
        static int counter = 0;
        static bool hasTarget = false;

        ImGui::Begin("Camera");                          // Create a window called "Hello, world!" and append into it.

        ImGui::Text("position");

        ImGui::InputFloat3("pos", cpos);


        ImGui::Checkbox("target", &hasTarget);
        if (hasTarget)
        {
            ImGui::InputFloat3("dir", tpos);
        }

        if (ImGui::Button("Move"))                            // Buttons return true when clicked (most widgets return true when edited/activated)
        {
            MainCamera.SetPosition(glm::vec3(cpos[0], cpos[1], cpos[2]));
            if (hasTarget)
            {
                glm::vec3 target = glm::vec3(cpos[0] - tpos[0], cpos[1] - tpos[1], cpos[2] - tpos[2]);

                MainCamera.SetDirection(glm::normalize(target));
            }
        }

        ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
        ImGui::End();
    }

    // Rendering
    ImGui::Render();

}

void Application::InitGLFW()
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // consider switching to 2.1 with extensions for release
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, 1);
    //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X

    Window = sptr_GLFWwindow(glfwCreateWindow(WindowWidth, WindowHeight, "LearnOpenGL", NULL, NULL));
    if (Window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        //return -1;
    }
    glfwMakeContextCurrent(Window.get());


    glfwSwapInterval(1);

    // Ensure we can capture the escape key being pressed below
    glfwSetInputMode(Window.get(), GLFW_STICKY_KEYS, GL_TRUE);
    // Hide the mouse and enable unlimited mouvement
    glfwSetInputMode(Window.get(), GLFW_CURSOR, GLFW_CURSOR_DISABLED);


    //callbacks

    glfwSetWindowUserPointer(Window.get(), this);

    auto framebufferFunc = [](GLFWwindow* w, int width, int height)
    {
        static_cast<Application*>(glfwGetWindowUserPointer(w))->GLFWFramebufferSizeCallback(w, width, height);
    };

    glfwSetFramebufferSizeCallback(Window.get(), framebufferFunc);

    auto keyFunc = [](GLFWwindow* w, int key, int scancode, int action, int mods)
    {
        static_cast<Application*>(glfwGetWindowUserPointer(w))->GLFWKeyCallback(w, key, scancode, action, mods);
    };

    glfwSetKeyCallback(Window.get(), keyFunc);
}

void Application::InitGLEW()
{
    glewExperimental = GL_TRUE;
    //init glew after the context have been made
    glewInit();
    // enable experimental?

    GLint GlewInitResult = glewInit();
    if (GLEW_OK != GlewInitResult)
    {
        std::cout << "GLEW INIT FAILED";
    }
}

void Application::InitOGL()
{
    // Enable depth test
    glEnable(GL_DEPTH_TEST);
    // Accept fragment if it closer to the camera than the former one
    glDepthFunc(GL_LESS);
    // Cull triangles which normal is not towards the camera
    glEnable(GL_CULL_FACE);

    glPolygonMode(GL_FRONT, GL_FILL);
    glPolygonMode(GL_BACK, GL_LINE);

    auto ogldebugfunc = [](GLenum source,
        GLenum type,
        GLuint id,
        GLenum severity,
        GLsizei length,
        const GLchar* message,
        const void* userParam) {

            cout << "---------------------opengl-callback-start------------" << endl;
            cout << "message: " << message << endl;
            cout << "type: ";
            switch (type) {
            case GL_DEBUG_TYPE_ERROR:
                cout << "ERROR";
                break;
            case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
                cout << "DEPRECATED_BEHAVIOR";
                break;
            case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
                cout << "UNDEFINED_BEHAVIOR";
                break;
            case GL_DEBUG_TYPE_PORTABILITY:
                cout << "PORTABILITY";
                break;
            case GL_DEBUG_TYPE_PERFORMANCE:
                cout << "PERFORMANCE";
                break;
            case GL_DEBUG_TYPE_OTHER:
                cout << "OTHER";
                break;
            }
            cout << endl;

            cout << "id: " << id << endl;
            cout << "severity: ";
            switch (severity) {
            case GL_DEBUG_SEVERITY_LOW:
                cout << "LOW";
                break;
            case GL_DEBUG_SEVERITY_MEDIUM:
                cout << "MEDIUM";
                break;
            case GL_DEBUG_SEVERITY_HIGH:
                cout << "HIGH";
                break;
            }
            cout << endl;
            cout << "---------------------opengl-callback-end--------------" << endl;
    };

    glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
    glDebugMessageCallback(ogldebugfunc, nullptr);
    GLuint unusedIds = 0;
    glDebugMessageControl(GL_DONT_CARE,
        GL_DONT_CARE,
        GL_DONT_CARE,
        0,
        &unusedIds,
        true);
}

void Application::InitIMGUI()
{
    // Setup Dear ImGui context
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO(); (void)io;
    //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;     // Enable Keyboard Controls
    //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;      // Enable Gamepad Controls

    // Setup Dear ImGui style
    ImGui::StyleColorsDark();
    //ImGui::StyleColorsClassic();

    // Setup Platform/Renderer bindings
    ImGui_ImplGlfw_InitForOpenGL(Window.get(), true);
    ImGui_ImplOpenGL3_Init("#version 330");
}


void Application::GLFWFramebufferSizeCallback(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and 
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}

void Application::GLFWKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
    if (key == GLFW_KEY_I && action == GLFW_PRESS)
    {
        MainCamera.SetAngles(0, 3.14f);
    }
    if (key == GLFW_KEY_O && action == GLFW_PRESS)
    {
        IsMouseLookEnabled = !IsMouseLookEnabled;

        if (IsMouseLookEnabled)
        {
            DisableCursor();
        }
        else
        {
            EnableCursor();
        }
    }
    if (key == GLFW_KEY_P && action == GLFW_PRESS)
    {
        IsWireframeEnabled = !IsWireframeEnabled;

        if (IsWireframeEnabled)
        {
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
            //glPolygonMode(GL_BACK, GL_LINE);
        }
        else
        {
            glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        }
    }
}

void Application::CalculateDeltaTime()
{
    DeltaTime = float((float)glfwGetTime() - LastTime);
}

void Application::SaveLastDeltaTime()
{
    LastTime = DeltaTime;
}

void Application::ClearColor()
{
    glClearColor(BackgroundColor.GetR(), BackgroundColor.GetG(), BackgroundColor.GetB(), BackgroundColor.GetA());
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

void Application::SwapBuffersPollEvents()
{
    // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
    glfwSwapBuffers(Window.get());
    glfwPollEvents();
}

void Application::CloseOnEsc()
{
    if (glfwGetKey(Window.get(), GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(Window.get(), true);

}

void Application::EnableCursor()
{
    glfwSetInputMode(Window.get(), GLFW_CURSOR, GLFW_CURSOR_NORMAL);
}

void Application::DisableCursor()
{
    glfwSetInputMode(Window.get(), GLFW_CURSOR, GLFW_CURSOR_DISABLED);
}

void Application::InitTestCode()
{

    Sh = std::unique_ptr<Shader>(new Shader("test.v", "test.f"));

    TextureArrayShader = std::unique_ptr<Shader>(new Shader("Data//Shaders//texturearray.v", "Data//Shaders//texturearray.f"));
    TexArrLightShader = std::shared_ptr<Shader>(new Shader("Data//Shaders//texarrlight.v", "Data//Shaders//texarrlight.f"));

    TestTexture = std::unique_ptr<Texture>(new Texture("Data//Textures//dirt.jpg", GL_TEXTURE_2D));
    TestTexture2 = std::unique_ptr<Texture>(new Texture("Data//Textures//stone.png", GL_TEXTURE_2D));

    /*chunk.Create(glm::vec3(0), TexArrLightShader);
    chunk2.Create(glm::vec3(16, 0, 0), TexArrLightShader);
    chunk3.Create(glm::vec3(16, 0, 16), TexArrLightShader);
    chunk4.Create(glm::vec3(0, 0, 16), TexArrLightShader);*/
    int size = 8;
    ChMgr.CreateFixedWorld(size, size, size, TexArrLightShader);


    ColorShader = std::unique_ptr<Shader>(new Shader("Data//Shaders//color.v", "Data//Shaders//color.f"));
    CoordsObj.Create(ColorShader.get());

    MainCamera.SetSpeed(0.1f);
    MainCamera.SetPosition(glm::vec3(0, 0, 5));


    glGenTextures(1, &TextureArray);
    glBindTexture(GL_TEXTURE_2D_ARRAY, TextureArray);
    glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 512, 512, 2);//last three numbers are size of images and array

    int dw, dh, sw, sh;
    unsigned char* image1 = SOIL_load_image("Data//Textures//dirt.jpg", &dw, &dh, NULL, SOIL_LOAD_RGBA);
    unsigned char* image2 = SOIL_load_image("Data//Textures//stone.png", &sw, &sh, NULL, SOIL_LOAD_RGBA);

    glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, dw, dh, 1, GL_RGBA, GL_UNSIGNED_BYTE, image1);
    glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 1, sw, sh, 1, GL_RGBA, GL_UNSIGNED_BYTE, image2);


    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}

void Application::RenderTestCode()
{

    CoordsObj.Render(ColorShader.get(), &MainCamera);

    //TestTexture->bind(0);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D_ARRAY, TextureArray);

    TexArrLightShader->bind(); // this should be done once and somewhere else

    GLint modelLoc = glGetUniformLocation(TexArrLightShader->id(), "M"); //dont do that in main loop
    glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(glm::mat4(1)/*chunk.GetWorldMatrix()*/));

    GLint lightPosLoc = glGetUniformLocation(TexArrLightShader->id(), "lightPos"); //dont do that in main loop

    GLint objectColorLoc = glGetUniformLocation(TexArrLightShader->id(), "objectColor"); //dont do that in main loop
    GLint lightColorLoc = glGetUniformLocation(TexArrLightShader->id(), "lightColor"); //dont do that in main loop

    GLint ambientStrenghtLoc = glGetUniformLocation(TexArrLightShader->id(), "ambientStrenght"); //dont do that in main loop
    GLint camPosLoc = glGetUniformLocation(TexArrLightShader->id(), "camPos"); //dont do that in main loop

    GLint cusDirLightLoc = glGetUniformLocation(TexArrLightShader->id(), "cusDirLight"); //dont do that in main loop


    glUniform3f(lightPosLoc, -5.0f, 5.0f, 5.0f);

    glUniform3f(objectColorLoc, 0.0f, 1.0f, 0.0f); //since it doesnt change could be done outside main loop
    glUniform3f(lightColorLoc, 1.0f, 1.0f, 1.0f); //since it doesnt change could be done outside main loop

    glUniform1f(ambientStrenghtLoc, 0.5f); //since it doesnt change could be done outside main loop

    glUniform3f(camPosLoc, MainCamera.GetPosition().x, MainCamera.GetPosition().y, MainCamera.GetPosition().z);

    glUniform3f(cusDirLightLoc, 0.5f, -1, 0.5f);

    ChMgr.Update(&MainCamera, DeltaTime);
    ChMgr.Render(&MainCamera);
    /*chunk.Render(&MainCamera);
    chunk2.Render(&MainCamera);
    chunk3.Render(&MainCamera);
    chunk4.Render(&MainCamera);*/
}
\$\endgroup\$
10
\$\begingroup\$

Avoid forward declarations

Why is there a forward declaration of class Camera near the top? If class Camera is declared elsewhere, just add #include <Camera.h> instead. The problem with forward declarations is that you are unnecessarily repeating things, and make it easier to introduce errors.

If you really need forward declarations to avoid #include loops, then put those forward declarations in a separate header file, and then #include that header file both in the place where you originally needed the forward declarations, and in the header files that have the full class definitions. This way, if you make a mistake with the forward declarations, the compiler will catch it when compiling the classes.

Make the constructor useful

I see you have a member function names Create(), and a constructor which takes no parameters. This leads me to believe your constructor doesn't fully construct Chunks, and that you need to call Create() to fully create a Chunk. If so, why not let the constructor do everything Create() does?

Why does updating and rendering depend on the camera?

When rendering using OpenGL, the geometry you build from your objects is normally independent of the camera position. The latter only influences the model-view-projection matrix. I would expect these functions to not depend on the camera. If there is some functionality in them that does, could that perhaps be split out into a separate function, or avoided altogether?

Getters should be const

Functions with names that start with Get probably don't change any of the member variables, only read them. Therefore these should be marked const, like so:

glm::vec3 GetPosition() const;

This helps the compiler optimize your code, and will also create helpful error messages if you do accidentily change some member variables within a getter function.

Make as much private as possible

You generally should hide as many member variables as possible. Does other code really need to write or read to ShouldBeDeleted or GlobalChunkVertexCount? If they only need to either read or write, writing a get- or set-function would ensure no accidents happen.

Similarly, Data[][][] should be private, and some functions should be added to read and write to individual voxels. The reason is that in the future, you might want to change how this data is stored, and it is much easier to just update the getter and setter functions than to change all the places that directly manipulate Data.

Avoid repetition

You have a lot of functions that are specialized for top/bottom/left/right/front/back. This adds a lot of repetition, which might be avoidable. How about defining an enum to denote neighbor direction?

enum class Neighbour {
    TOP,
    BOTTOM,
    LEFT,
    RIGHT,
    FRONT,
    BACK,
    COUNT
};

And then use this to make an array out of the pointers to neighboring chunks:

std::weak_ptr<Chunk> Neighbors[std::static_cast<size_t>(Neighbor::COUNT)];

And of some of the member functions:

bool ShouldAddFace(int x, int y, int z, Neighbor neighbor);
void AddFace(int x, int y, int z, int &idx, Neighbor neighbor);

Avoid too many flags

You have lots of flag variables. Some of them, like IsChunkEmpty, I can imagine are a form of optimization. That's fine. But there's a lot of DidThreadFinish, WasThreadTerminated and so on, that sound like they are there to work around issues that might be solved in other ways.

Flags like these sound like they might be checked repeatedly, which is inefficient. Maybe there is something else that can be checked than the flag itself, or maybe you can guarantee some order so that the flag is unnecessary. For example, if you always ensure CreateMesh() is called before Render(), I'm sure you don't need WasMeshCreated.

Similarly, if you always ensure to call ChunkThread.join() before doing things that currently check WasThreadTerminated, then you could get rid of that flag as well.

Using atomics

Atomics are good to have for multi-threaded code. They are no substitute for locks though. Also, changing multiple atomics doesn't mean they all are changed together in an atomic way. Having so many flags raises questions. What if the thread terminates before ShouldTerminateThread is called? Why are there both DidThreadFinish and WasThreadTerminated, and why is the former not an atomic variable?

Don't include unnecessary headers

Why is there an #include <future>, if no futures are used in Chunk.h? Remove unneeded #includes.

Remove commented-out code

You already mentioned you have your code in a git repository. That means git will take care of remembering the history for you. So you shouldn't merely comment out code you no longer use, but remove it entirely. So this line should go:

//void RebuildMesh();

This keeps the code clean. If you ever need it back, git will be able to find it for you.

Use {} for value initialization

For member variables with a more complex type that you want to ensure are initialized to their default value, use {}. This can sometimes avoid repeating their type. For example:

glm::vec3 Position = {};

Don't write this-> unnecessarily

Member functions and variables can be accessed directly from other class members, you almost never need to add this->.

Similarly, in member functions of class Chunk, you don't need to write Chunk:: in front of static member constants.

Don't detach threads

You almost never have to detach a thread. If you detach a thread, you no longer have full control over it. Just keep it attached to your program, and don't forget to call join() to clean it up.

Building meshes asynchronously

You want to build meshes in the background, and have them pop onto the screen when they are finished. You don't want to potentially have one thread per chunk, as there might be a lot of chunks suddenly coming in to view, which would all spawn threads. Instead, you probably want only a few threads dedicated to chunk building, so they don't take all the CPU time necessary for rendering the already built chunks.

The way to do this is to create a work queue: if a chunk becomes visible, but it hasn't been built yet, add a pointer to this chunk to the queue. One or more threads are waiting for items to be added to this queue, and once there is something in it they can dequeue it and start building the chunks refered to. When a thread is finished with a chunk, it can somehow signal that it is done building, and can then check the queue if there is more work.

For this to work, you need to have an atomic queue, so multiple threads can add and remove from it. A simple solution is to use a std::queue guarded by a std::mutex, and a std::condition_variable (that link contains an example of a very simple worker thread) so the worker threads can go to sleep if there is nothing to be done, but can be woken up once another threads adds something to the queue.

Make better use of GLM

In Chunk::update(), you calculate the difference vector between the chunk's position and the camera position, and then look at the x, y and z components separately to determine whether it should be deleted. This can be greatly simplified by writing:

bool shouldDelete = glm::distance(Position, cam->GetPosition()) > ChunkManager::ChunkGenRadius * ChunkSize;

There's a slight difference; the above code will actually delete chunks outside of a sphere instead of a box, but that's probably fine.

In ChunkManager::Update(), you can get the integer chunk position simply using:

glm::vec3 camChunkPos = glm::floor(camera->GetPosition() / ChunkSize);

Make better use of GLM, this results in shorter, more readable code with less errors. Every time you are doing something component-wise, check if there might not already be a function that does what you want for all components of a vector in one go.

Avoid duplicating data unnecessarily

It looks like ChunkManager has a ChunkShader, which is passed to all Chunks created by it, and each Chunk also stores a copy. This seems unnecessary. Why not pass the ChunkShader as a parameter to the Render() functions?

Avoid bare pointers

Whenever possible use a proper container to manage object storage, instead of calling new and storing a bare pointer. STL containers ensure the storage gets deleted properly.

In ChunkManager, use std::vector<Chunk> Chunks to store chunks. In CreateFixedworld(), simply allocate the right amount of space with:

Chunks.resize(width * height * depth);

You never called delete, so this change will fix a memory leak.

Use C++17's if-statements with initializers

Especially when trying to find something in a container, C++17 allows you to simplify getting an interator and doing something only when it didn't point to the end. For example, in ChunkManager::Update(), you can write:

if (auto result = ChunkMap.find(posToCheck); result == ChunkMap.end) {
    ...
}

Use std::make_shared() and std::make_unique()

Instead of writing std::shared_ptr<Chunk>(new Chunk), write std::make_shared<Chunk>(). This avoids some repetition. Also, when assigning to a std::shared_ptr<> variable, there is no need to first cast a new object to a std::shared_ptr<>. So instead of:

std::shared_ptr<Chunk> newChunk = std::shared_ptr<Chunk>(new Chunk());

You could write:

std::shared_ptr<Chunk> newChunk = new Chunk();

However, it's generally best to avoid new and thus to use std::make_shared<>. Note that you can use auto to avoid more repetition:

auto newChunk = std::make_shared<Chunk>();

The same goes for std::make_unique<>.

Note that in ChunkManager::Update(), it's better to make newChunk a std::unique_ptr<>, since it's not being shared at that moment, and then hand over ownership to ChunkMap simply by writing:

ChunkMap[posToCheck] = newChunk;
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I really appreciate your input, thank you for spending your time for this reply. I have also updated my question with more source. Regarding forward declarations, In the past I have ran into include loops, even when using #pragma once, I understood that forward declarations are a good way of avoiding it? \$\endgroup\$ – alecie Dec 28 '19 at 14:30
  • \$\begingroup\$ That's indeed one way of avoiding include loops. However, if you need to resort to something, it is better to create a separate header file that has the forward declarations, and then include it both in the place where you would need it and in the header file that has the full class definitions. That way, if you make a mistake with the forward declarations, the compiler will spot the mistake when it tries to compile those classes. \$\endgroup\$ – G. Sliepen Dec 28 '19 at 16:51
  • \$\begingroup\$ I disagree with avoiding forward declarations. Using forward declarations instead of includes where it is possible reduces compile times and it prevents indirect including of headers that you dont use in that translation unit. The only unnessecary repetition is the class name once instead of an include. \$\endgroup\$ – Eric Jan 5 at 9:55
  • \$\begingroup\$ @Eric: if you do want to reduce compile times, then I'd still suggest putting the forward declarations into a separate header file (just like <iosfwd>), and #include that one in both the .cpp files that uses them and in the header file with the full declarations; the latter to ensure you don't accidentily have a mismatch between the forward declarations and the full ones. \$\endgroup\$ – G. Sliepen Jan 5 at 19:40
6
\$\begingroup\$

Avoid using namespace std;

In Application.cpp the statement using namespace std; is used. If you are coding professionally you probably should get out of the habit of using the using namespace std; statement. The code will more clearly define where cout and other identifiers are coming from (std::cin, std::cout). As you start using namespaces in your code it is better to identify where each function comes from because there may be function name collisions from different namespaces. The identifiercout you may override within your own classes, and you may override the operator << in your own classes as well. This stack overflow question discusses this in more detail.

Use Source Control Properly

There are several places in the source code where code is commented out, this is generally an indication that the code is not ready for review. It seems that the IDE in use is Visual Studio 2019, which means that the IDE supports the use of GIT for source control. Since GIT is readily available code that is commented out can be removed from source code and easily restored if the code is needed after all. This makes the code easier to read and maintain.

Commenting Versus Self-Documenting Code and DRY Code

In Chunk.cpp there are 6 functions (bool ShouldAddTop(), bool ShouldAddBottom(), ...) of similar format that generally have the same comments, in at least 3 of the functions at least one comment is incorrect.

        if (auto bch = BackChunk.lock()) //top chunk exist, check value from there

All of these functions could be simplified, when the inner if statement is changed to :

        return (bch->Data[x][y][0] >= 0);   // Condition is different in each function

versus

        if (bch->Data[x][y][0] >= 0) // voxel in top chunk is full, dont display
        {
            return false;
        }
        else
        {
            return true;
        }

As mentioned in the review by @G.Sliepen it is best to find a way to remove code that repeats itself.

Magic Numbers

There are Magic Numbers in the following code:

Application::Application(int windowWidth, int windowHeight)
    : MainCamera(60.0f, 4.0f / 3.0f, 0.1f, 1000.0f)
{
    WindowWidth = windowWidth;
    WindowHeight = windowHeight;

    BackgroundColor = Color(0.25f, 0.5f, 1, 1);

    DeltaTime = 0;
    LastTime = 0;
}

it might be better to create symbolic constants for them to make the code more readble and easier to maintain. These numbers may be used in many places and being able to change them by editing only one line makes maintainence easier.

constexpr double Left = 60.0f;    // This might be Top rather than left

I couldn't even guess at what the RGB values are for Color().

Numeric constants in code are sometimes referred to as Magic Numbers, because there is no obvious meaning for them. There is a discussion of this on stackoverflow.

| improve this answer | |
\$\endgroup\$
0
\$\begingroup\$

OpenGL is quickly losing relevance, and Apple is not going to be supporting it on their OSes. Prefer a low-level cross platform rendering engine or Vulkan/Metal/DirectX12.


There's many ways to render this faster. The entire construction including noise generation can be done on the GPU and rendering voxels like this is extremely inefficient. Simply drawing a ton of instanced cubes that pull from an instance buffer with no face culling would probably be faster.

Off the top of my head you could try this:

  • Generate/examine the density volume in a compute shader
  • Compact non-empty voxels on the GPU with a parallel reduction into a single ID buffer
  • Map the dense list of voxels to a 0-6 faces depending on neighbors and camera position
  • Compact the faces in another parallel reduction
  • Render the resultant mesh with a single drawIndexedIndirect call, or maybe a few if you want more sophisticated culling or something.

I would estimate being able to render a few million voxels at 60fps on a modern desktop GPU like this, depending on the volume density.


Commented out code hurts readability


Chunks should not know about rendering and definitely not hold shared pointers to shaders. They should be plain data and have a chunk renderer that owns shaders and uses things with a chunk interface. The only reason not to is if the chunks are going to all be heterogenous which would be bizarre and slow.


Using std::futures may be preferrable to threads. I think they definitely would be if there were continuation support in C++, since that's what's functionally being expressed. Testing atomics is brittle and I have yet to see a valid use of them that isn't writing low-level library code.


MainCamera(60.0f, 4.0f / 3.0f, 0.1f, 1000.0f)

This is perfectly readable to me without seeing the camera class. Should probably be width/height instead of 4.0f/3.0f though. I think this is safe because understanding the domain specific semantics of a 3D camera is a reasonable expectation for understanding this code.

Similarly

Color(0.25f, 0.5f, 1, 1);

is also reasonable to expect r, g, b, a values in floating point form. It would only be unreadable if this isn't actually what they represent.


| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.