#define STB_IMAGE_IMPLEMENTATION
#include "VoxelGame.h"

#include <iostream>
#include <cmath>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "stb_image.h"
#include "imgui.h"

#include <GLFW/glfw3.h>
//----------------------------------------------------------------------
// Perlin noise

static std::vector<int> perm,p;
static std::once_flag initFlag;
static void initPerlin(){
    perm.resize(256);
    std::iota(perm.begin(),perm.end(),0);
    std::mt19937 gen(1337);
    std::shuffle(perm.begin(),perm.end(),gen);
    p.resize(512);
    for(int i=0;i<512;++i) p[i]=perm[i&255];
}
static inline float fade(float t){ return t*t*t*(t*(t*6-15)+10); }
static inline float lerp_(float a,float b,float t){ return a+t*(b-a); }
static inline float grad(int h,float x,float y,float z){
    h&=15; float u=h<8?x:y; float v=h<4?y:(h==12||h==14?x:z);
    return ((h&1)?-u:u)+((h&2)?-v:v);
}
static float perlin(float x,float y,float z){
    std::call_once(initFlag,initPerlin);
    int X=int(floor(x))&255, Y=int(floor(y))&255, Z=int(floor(z))&255;
    x-=floor(x); y-=floor(y); z-=floor(z);
    float u=fade(x), v=fade(y), w=fade(z);
    int A=p[X]+Y, AA=p[A]+Z, AB=p[A+1]+Z;
    int B=p[X+1]+Y, BA=p[B]+Z, BB=p[B+1]+Z;
    return lerp_(
        lerp_( lerp_(grad(p[AA],x,y,z),   grad(p[BA],x-1,y,z), u),
               lerp_(grad(p[AB],x,y-1,z), grad(p[BB],x-1,y-1,z), u), v),
        lerp_( lerp_(grad(p[AA+1],x,y,z-1),   grad(p[BA+1],x-1,y,z-1), u),
               lerp_(grad(p[AB+1],x,y-1,z-1), grad(p[BB+1],x-1,y-1,z-1), u), v),
        w
    );
}
static inline int voxelAt(const Chunk &c,int x,int y,int z){
    if(x<0||y<0||z<0||x>=CHUNK_SIZE||y>=CHUNK_HEIGHT||z>=CHUNK_SIZE) return 0;
    return c.voxels[x][y][z];
}

//----------------------------------------------------------------------
// Greedy mesh with normals

void VoxelGame::greedyMesh(const Chunk &chunk,
                           std::vector<Vertex> &vertices,
                           std::vector<uint32_t> &indices)
{
    const int dims[3]={CHUNK_SIZE,CHUNK_HEIGHT,CHUNK_SIZE};
    for(int d=0;d<3;d++){
        int u=(d+1)%3, v=(d+2)%3;
        int x[3]={0,0,0}, q[3]={0,0,0}; q[d]=1;
        for(x[d]=-1;x[d]<dims[d];){
            std::vector<int> mask(dims[u]*dims[v],0);
            for(x[v]=0;x[v]<dims[v];++x[v]){
                for(x[u]=0;x[u]<dims[u];++x[u]){
                    int a=0,b=0;
                    int p0[3]={x[0],x[1],x[2]};
                    int p1[3]={x[0]+q[0],x[1]+q[1],x[2]+q[2]};
                    if(x[d]>=0)            a=voxelAt(chunk,p0[0],p0[1],p0[2]);
                    if(x[d]<dims[d]-1)     b=voxelAt(chunk,p1[0],p1[1],p1[2]);
                    mask[x[u]+dims[u]*x[v]] = ((a!=0)!=(b!=0)) ? ((a!=0)?a:-b) : 0;
                }
            }
            ++x[d];
            for(int j=0;j<dims[v];j++){
                for(int i=0;i<dims[u];){
                    int c=mask[i+dims[u]*j];
                    if(!c){ ++i; continue; }
                    int w=1; while(i+w<dims[u] && mask[i+w+dims[u]*j]==c) ++w;
                    int h=1; bool done=false;
                    while(j+h<dims[v]){
                        for(int k=0;k<w;k++)
                            if(mask[i+k+dims[u]*(j+h)]!=c){ done=true; break; }
                        if(done) break;
                        ++h;
                    }
                    int du[3]={0,0,0}, dv[3]={0,0,0};
                    du[u]=w; dv[v]=h;
                    int pos[3]={0,0,0}; pos[d]=x[d]; pos[u]=i; pos[v]=j;
                    float fx=pos[0], fy=pos[1], fz=pos[2];
                    float px[4]={fx, fx+du[0], fx+du[0]+dv[0], fx+dv[0]};
                    float py[4]={fy, fy+du[1], fy+du[1]+dv[1], fy+dv[1]};
                    float pz[4]={fz, fz+du[2], fz+du[2]+dv[2], fz+dv[2]};
                    glm::vec3 U={px[1]-px[0],py[1]-py[0],pz[1]-pz[0]};
                    glm::vec3 V={px[3]-px[0],py[3]-py[0],pz[3]-pz[0]};
                    glm::vec3 N=glm::normalize(glm::cross(U,V));
                    float duv=float(w), dvv=float(h);
                    Vertex v0{{px[0],py[0],pz[0]},N,{0,0}};
                    Vertex v1{{px[1],py[1],pz[1]},N,{duv,0}};
                    Vertex v2{{px[2],py[2],pz[2]},N,{duv,dvv}};
                    Vertex v3{{px[3],py[3],pz[3]},N,{0,dvv}};
                    if(c<0){ std::swap(v0,v1); std::swap(v2,v3); }
                    uint32_t base=vertices.size();
                    vertices.insert(vertices.end(),{v0,v1,v2,v3});
                    indices.insert(indices.end(),{base,base+1,base+2,base,base+2,base+3});
                    lastFaceCount++;
                    for(int l=0;l<h;++l)
                        for(int k=0;k<w;++k)
                            mask[i+k+dims[u]*(j+l)] = 0;
                    i+=w;
                }
            }
        }
    }
}

//----------------------------------------------------------------------
// Constructor / Destructor

VoxelGame::VoxelGame(): cameraPos(32.0f,32.0f,80.0f) {
    for(int i=0;i<4;i++)
        workers.emplace_back(&VoxelGame::workerLoop,this);
}

VoxelGame::~VoxelGame(){
    {
        std::lock_guard<std::mutex> lk(queueMutex);
        stopWorkers=true;
    }
    queueCV.notify_all();
    for(auto &t:workers) t.join();

    glDeleteTextures(1,&textureDirt);
    glDeleteTextures(1,&textureGrass);
    glDeleteTextures(1,&textureWood);
    glDeleteFramebuffers(NUM_CASCADES,depthFBO);
    glDeleteTextures(NUM_CASCADES,depthTex);
    glDeleteProgram(depthProg);
    glDeleteProgram(mainProg);
    for(auto &pr:meshes) destroyMesh(pr.second.get());
}

//----------------------------------------------------------------------

bool VoxelGame::init(int w,int h){
    screenW=w; screenH=h;
    if(!loadTextures()||!loadShaders()) return false;
    initShadowMaps();
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);
    return true;
}

//----------------------------------------------------------------------

bool VoxelGame::loadTextures(){
    auto load=[&](const char*f,GLuint &t){
        int W,H,N; unsigned char*d=stbi_load(f,&W,&H,&N,4);
        if(!d) return false;
        glGenTextures(1,&t); glBindTexture(GL_TEXTURE_2D,t);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,W,H,0,GL_RGBA,GL_UNSIGNED_BYTE,d);
        glGenerateMipmap(GL_TEXTURE_2D);
        stbi_image_free(d);
        return true;
    };
    return load("dirt.jpg",textureDirt)
        && load("grass.jpg",textureGrass)
        && load("wood.png",textureWood);
}

//----------------------------------------------------------------------

bool VoxelGame::loadShaders(){
    auto compile=[&](const char*s,GLenum t){
        GLuint sh=glCreateShader(t);
        glShaderSource(sh,1,&s,nullptr);
        glCompileShader(sh);
        return sh;
    };
    // depth shader
    const char* dvsrc = R"(
        #version 330 core
        layout(location=0) in vec3 aPos;
        uniform mat4 uLightSpace,uModel;
        void main(){ gl_Position=uLightSpace*uModel*vec4(aPos,1); }
    )", *dfsrc = R"(
        #version 330 core
        void main(){}
    )";
    GLuint dv=compile(dvsrc,GL_VERTEX_SHADER),
           df=compile(dfsrc,GL_FRAGMENT_SHADER);
    depthProg=glCreateProgram();
    glAttachShader(depthProg,dv); glAttachShader(depthProg,df);
    glLinkProgram(depthProg);
    glDeleteShader(dv); glDeleteShader(df);

    // scene shader
    const char* svsrc = R"(
        #version 330 core
        layout(location=0) in vec3 aPos;
        layout(location=1) in vec3 aNorm;
        layout(location=2) in vec2 aUV;
        uniform mat4 uModel,uView,uProj,uLightSpace[3];
        out vec3 FragPos,Normal;
        out vec2 UV;
        out vec4 FragPosLight[3];
        void main(){
            FragPos = vec3(uModel*vec4(aPos,1));
            Normal  = mat3(transpose(inverse(uModel)))*aNorm;
            UV      = aUV;
            for(int i=0;i<3;i++)
                FragPosLight[i] = uLightSpace[i]*vec4(FragPos,1);
            gl_Position = uProj*uView*vec4(FragPos,1);
        }
    )", *sfsrc = R"(
        #version 330 core
        in vec3 FragPos,Normal;
        in vec2 UV;
        in vec4 FragPosLight[3];
        uniform sampler2D uTexDirt,uTexGrass;
        uniform sampler2DShadow uShadowMap[3];
        uniform float cascadeSplit[3];
        uniform vec3 lightDir,viewPos;
        float CalcShadow(int idx){
            vec3 proj = FragPosLight[idx].xyz/FragPosLight[idx].w;
            proj = proj*0.5+0.5;
            float bias=0.005,sum=0;
            for(int x=-1;x<=1;x++)for(int y=-1;y<=1;y++)
                sum += texture(uShadowMap[idx], proj.xy+vec2(x,y)*0.001, proj.z-bias);
            sum/=9; return proj.z>1?1:sum;
        }
        out vec4 FragColor;
        void main(){
            float ndl = max(dot(normalize(Normal),-lightDir),0);
            vec3 base = Normal.y>0.9?texture(uTexGrass,UV).rgb
                                            :texture(uTexDirt,UV).rgb;
            float d = length(viewPos-FragPos);
            int idx = d>cascadeSplit[1]?2:d>cascadeSplit[0]?1:0;
            float sh=CalcShadow(idx);
            vec3 amb=0.2*base, dif=(1-sh)*ndl*base;
            FragColor=vec4(amb+dif,1);
        }
    )";
    GLuint sv=compile(svsrc,GL_VERTEX_SHADER),
           sf=compile(sfsrc,GL_FRAGMENT_SHADER);
    mainProg=glCreateProgram();
    glAttachShader(mainProg,sv); glAttachShader(mainProg,sf);
    glLinkProgram(mainProg);
    glDeleteShader(sv); glDeleteShader(sf);

    return true;
}

//----------------------------------------------------------------------

void VoxelGame::initShadowMaps(){
    glGenFramebuffers(NUM_CASCADES,depthFBO);
    glGenTextures(NUM_CASCADES,depthTex);
    for(int i=0;i<NUM_CASCADES;i++){
        glBindTexture(GL_TEXTURE_2D,depthTex[i]);
        glTexImage2D(GL_TEXTURE_2D,0,GL_DEPTH_COMPONENT,
                     SHADOW_MAP_SIZE,SHADOW_MAP_SIZE,
                     0,GL_DEPTH_COMPONENT,GL_FLOAT,nullptr);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_BORDER);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_BORDER);
        float bc[4]={1,1,1,1};
        glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_BORDER_COLOR,bc);

        glBindFramebuffer(GL_FRAMEBUFFER,depthFBO[i]);
        glFramebufferTexture2D(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,
                               GL_TEXTURE_2D,depthTex[i],0);
        glDrawBuffer(GL_NONE);
        glReadBuffer(GL_NONE);
    }
    glBindFramebuffer(GL_FRAMEBUFFER,0);

    float near=1.0f, far=200.0f, lambda=0.5f;
    for(int i=0;i<NUM_CASCADES;i++){
        float si=(i+1)/(float)NUM_CASCADES;
        float log = near*std::pow(far/near,si);
        float uni = near + (far-near)*si;
        cascadeSplits[i] = lambda*log + (1-lambda)*uni;
    }
}


Chunk VoxelGame::generateChunk(int cx, int cz) {
    Chunk c;
    c.x = cx;
    c.z = cz;
    c.voxels.assign(CHUNK_SIZE,
        std::vector<std::vector<int>>(CHUNK_HEIGHT,
            std::vector<int>(CHUNK_SIZE, 0)));

    const float scale = 0.05f;  // controls feature size
    for (int x = 0; x < CHUNK_SIZE; ++x) {
        for (int z = 0; z < CHUNK_SIZE; ++z) {
            // world‐space coords
            float wx = (cx * CHUNK_SIZE + x) * scale;
            float wz = (cz * CHUNK_SIZE + z) * scale;
            // perlin → [-1,1] → [0,1]
            float n = (perlin(wx, 0.0f, wz) + 1.0f) * 0.5f;
            // height [1,CHUNK_HEIGHT]
            int h = 1 + int(n * (CHUNK_HEIGHT - 1));
            h = std::clamp(h, 1, CHUNK_HEIGHT);
            // fill from y=0 up to y<h
            for (int y = 0; y < h; ++y) {
                // grass at top, dirt below
                c.voxels[x][y][z] = (y == h - 1 ? 1 : 2);
            }
        }
    }
    return c;
}


void VoxelGame::update(float dt, glm::vec3 const& camPos){
    lastDeltaTime=dt;
    fps=1.0f/dt;
    frameTimeSum+=dt; frameCount++;

    int old=totalChunksEverLoaded;
    double t0=glfwGetTime();
    updateChunks(camPos);
    double t1=glfwGetTime();
    lastChunkGenTime=t1-t0;
    chunkGenCount=totalChunksEverLoaded-old;
    totalChunkGenTime+=lastChunkGenTime*chunkGenCount;
}

//----------------------------------------------------------------------------
// Called each frame to load/unload chunks around the camera and enqueue new ones for meshing.
void VoxelGame::updateChunks(glm::vec3 const& camPos) {
    int cx = int(std::floor(camPos.x / CHUNK_SIZE));
    int cz = int(std::floor(camPos.z / CHUNK_SIZE));
    int r  = renderDistance;

    // Track which chunks to keep
    std::unordered_map<ChunkKey,bool,ChunkKeyHash> keep;
    for (int dx = -r; dx <= r; ++dx) {
        for (int dz = -r; dz <= r; ++dz) {
            ChunkKey k{cx + dx, cz + dz};
            keep[k] = true;
            // If not already generated, create and enqueue for meshing
            if (!chunks.count(k)) {
                chunks[k] = generateChunk(k.x, k.z);
                ++totalChunksEverLoaded;
                {
                    std::lock_guard<std::mutex> lk(queueMutex);
                    toBuild.push(k);
                }
                queueCV.notify_one();
            }
        }
    }

    // Unload chunks that are out of range
    for (auto it = chunks.begin(); it != chunks.end(); ) {
        if (!keep.count(it->first)) {
            // Also destroy its GPU mesh if present
            if (meshes.count(it->first)) {
                destroyMesh(meshes[it->first].get());
                meshes.erase(it->first);
            }
            it = chunks.erase(it);
        } else {
            ++it;
        }
    }

    totalChunksLoaded = int(chunks.size());
}

//----------------------------------------------------------------------------
// Worker thread: copies chunk data, runs greedyMesh on CPU, then enqueues RawMesh for GPU upload.
void VoxelGame::workerLoop() {
    while (true) {
        ChunkKey key;
        {
            std::unique_lock<std::mutex> lk(queueMutex);
            queueCV.wait(lk, [&]{ return stopWorkers || !toBuild.empty(); });
            if (stopWorkers && toBuild.empty()) return;
            key = toBuild.front();
            toBuild.pop();
        }

        // Copy voxel data under lock
        Chunk chunkCopy;
        {
            std::lock_guard<std::mutex> lk(queueMutex);
            auto it = chunks.find(key);
            if (it == chunks.end()) continue;
            chunkCopy = it->second;
        }

        // Phase 1: CPU‐side greedy meshing
        RawMesh rm;
        rm.key = key;
        greedyMesh(chunkCopy, rm.verts, rm.indices);

        // Enqueue for main‐thread GPU upload
        {
            std::lock_guard<std::mutex> lk(queueMutex);
            readyToUpload.push(std::move(rm));
        }
    }
}


//----------------------------------------------------------------------
// computeLightSpace: full frustum corners approach

static void computeLightSpace(glm::mat4 const& view, glm::mat4 const& proj,
                              glm::vec3 const& lightDir,
                              glm::mat4 out[3],
                              float splits[3])
{
    glm::mat4 inv = glm::inverse(proj * view);
    glm::vec4 corners[8] = {
        {-1,-1,-1,1},{ 1,-1,-1,1},{ 1, 1,-1,1},{-1, 1,-1,1},
        {-1,-1, 1,1},{ 1,-1, 1,1},{ 1, 1, 1,1},{-1, 1, 1,1}
    };
    for(int i=0;i<3;i++){
        float near = (i==0? 1.0f : splits[i-1]);
        float far  = splits[i];
        std::vector<glm::vec4> frustCorners;
        for(int c=0;c<8;c++){
            glm::vec4 pt = inv * corners[c];
            pt /= pt.w;
            frustCorners.push_back(pt);
        }
        // light view
        glm::mat4 lightView = glm::lookAt(-lightDir*100.0f, glm::vec3(0), glm::vec3(0,1,0));
        // compute AABB
        glm::vec3 mn(FLT_MAX), mx(-FLT_MAX);
        for(auto &pt: frustCorners){
            glm::vec4 lp = lightView * pt;
            mn = glm::min(mn, glm::vec3(lp));
            mx = glm::max(mx, glm::vec3(lp));
        }
        out[i] = glm::ortho(mn.x,mx.x,mn.y,mx.y,-mx.z-50.0f,-mn.z+50.0f) * lightView;
    }
}

//----------------------------------------------------------------------

void VoxelGame::render(glm::mat4 const& view, glm::mat4 const& proj){
    computeLightSpace(view,proj,lightDir,lightSpaceMat,cascadeSplits);

    // 1) Depth passes
    glEnable(GL_DEPTH_TEST);
    for(int i=0;i<NUM_CASCADES;i++){
        glViewport(0,0,SHADOW_MAP_SIZE,SHADOW_MAP_SIZE);
        glBindFramebuffer(GL_FRAMEBUFFER,depthFBO[i]);
        glClear(GL_DEPTH_BUFFER_BIT);
        glUseProgram(depthProg);
        glUniformMatrix4fv(glGetUniformLocation(depthProg,"uLightSpace"),
                           1,GL_FALSE,glm::value_ptr(lightSpaceMat[i]));
        renderScene(depthProg);
    }
    glBindFramebuffer(GL_FRAMEBUFFER,0);

    // 2) Main pass
    glViewport(0,0,screenW,screenH);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    glUseProgram(mainProg);
    glUniformMatrix4fv(glGetUniformLocation(mainProg,"uView"),
                       1,GL_FALSE,glm::value_ptr(view));
    glUniformMatrix4fv(glGetUniformLocation(mainProg,"uProj"),
                       1,GL_FALSE,glm::value_ptr(proj));
    for(int i=0;i<NUM_CASCADES;i++){
        char buf[32]; sprintf(buf,"uLightSpace[%d]",i);
        glUniformMatrix4fv(glGetUniformLocation(mainProg,buf),
                           1,GL_FALSE,glm::value_ptr(lightSpaceMat[i]));
    }
    glUniform1fv(glGetUniformLocation(mainProg,"cascadeSplit"),
                 NUM_CASCADES,cascadeSplits);
    glUniform3fv(glGetUniformLocation(mainProg,"lightDir"),
                 1,glm::value_ptr(lightDir));
    glUniform3fv(glGetUniformLocation(mainProg,"viewPos"),
                 1,glm::value_ptr(cameraPos));

    // bind textures
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D,textureDirt);
    glUniform1i(glGetUniformLocation(mainProg,"uTexDirt"),0);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D,textureGrass);
    glUniform1i(glGetUniformLocation(mainProg,"uTexGrass"),1);
    for(int i=0;i<NUM_CASCADES;i++){
        glActiveTexture(GL_TEXTURE2+i);
        glBindTexture(GL_TEXTURE_2D,depthTex[i]);
        char buf[32]; sprintf(buf,"uShadowMap[%d]",i);
        glUniform1i(glGetUniformLocation(mainProg,buf),2+i);
    }

    renderScene(mainProg);
}

//----------------------------------------------------------------------

void VoxelGame::renderScene(GLuint prog){
    for(auto &pr:meshes){
        glm::mat4 model = glm::translate(glm::mat4(1.0f),
                                         glm::vec3(pr.first.x*16.0f,0,pr.first.z*16.0f));
        glUniformMatrix4fv(glGetUniformLocation(prog,"uModel"),
                           1,GL_FALSE,glm::value_ptr(model));
        ChunkMesh*m=pr.second.get();
        glBindVertexArray(m->vao);
        glDrawElements(GL_TRIANGLES,m->indexCount,GL_UNSIGNED_INT,0);
    }
    glBindVertexArray(0);
}

//----------------------------------------------------------------------

void VoxelGame::debugUI(){
    ImGui::Begin("Debug");

    ImGui::Text("FPS:             %.1f", fps);
    ImGui::Text("Delta Time:      %.3f ms", lastDeltaTime*1000.0f);
    ImGui::Text("Avg Frame:       %.3f ms", (frameTimeSum/frameCount)*1000.0);
    ImGui::Separator();

    ImGui::Text("Last Chunk Gen:  %.3f ms", lastChunkGenTime*1000.0);
    ImGui::Text("Chunks this gen: %d", chunkGenCount);
    ImGui::Text("Avg Chunk Gen:   %.3f ms",
                (chunkGenCount?(totalChunkGenTime/chunkGenCount)*1000.0:0.0));
    ImGui::Separator();

    ImGui::Text("Chunks Loaded:   %d", totalChunksLoaded);
    ImGui::Text("Chunks Ever:     %d", totalChunksEverLoaded);
    ImGui::Separator();

    ImGui::Text("Faces Drawn:     %d", lastFaceCount);
    ImGui::Text("GL Draw Calls:   %d", lastGLCalls);
    ImGui::Separator();

    ImGui::SliderInt("Render Distance",&renderDistance,1,64);

    ImGui::Separator();
    ImGui::Text("Shadow Maps:");
    for(int i=0;i<NUM_CASCADES;i++){
        ImGui::Text(" Cascade %d", i);
        ImGui::Image((intptr_t)depthTex[i],
                     ImVec2(128,128),
                     ImVec2(0,1),ImVec2(1,0));
    }

    ImGui::Separator();
    static bool wf=false;
    if(ImGui::Checkbox("Wireframe",&wf))
        glPolygonMode(GL_FRONT_AND_BACK,wf?GL_LINE:GL_FILL);

    ImGui::End();
}


void VoxelGame::destroyMesh(ChunkMesh* m){
    glDeleteBuffers(1,&m->vbo);
    glDeleteBuffers(1,&m->ibo);
    glDeleteVertexArrays(1,&m->vao);
}