small-projects/cpp-voxel-engine/VoxelGame.cpp

570 lines
20 KiB
C++
Raw Normal View History

2025-04-06 03:14:06 +00:00
#define STB_IMAGE_IMPLEMENTATION
2025-04-06 02:03:23 +00:00
#include "VoxelGame.h"
2025-04-06 04:20:54 +00:00
2025-04-06 02:03:23 +00:00
#include <iostream>
2025-04-06 03:14:06 +00:00
#include <cmath>
2025-04-06 04:20:54 +00:00
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "stb_image.h"
2025-04-06 03:14:06 +00:00
#include "imgui.h"
2025-04-06 04:20:54 +00:00
#include <GLFW/glfw3.h>
//----------------------------------------------------------------------
// Perlin noise
2025-04-06 03:14:06 +00:00
2025-04-06 04:20:54 +00:00
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;
2025-04-06 03:14:06 +00:00
return c.voxels[x][y][z];
}
2025-04-06 04:20:54 +00:00
//----------------------------------------------------------------------
// Greedy mesh with normals
2025-04-06 02:03:23 +00:00
2025-04-06 04:20:54 +00:00
void VoxelGame::greedyMesh(const Chunk &chunk,
std::vector<Vertex> &vertices,
std::vector<uint32_t> &indices)
2025-04-06 02:03:23 +00:00
{
2025-04-06 04:20:54 +00:00
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;
}
}
}
}
2025-04-06 02:03:23 +00:00
}
2025-04-06 04:20:54 +00:00
//----------------------------------------------------------------------
// Constructor / Destructor
2025-04-06 02:03:23 +00:00
2025-04-06 04:20:54 +00:00
VoxelGame::VoxelGame(): cameraPos(32.0f,32.0f,80.0f) {
for(int i=0;i<4;i++)
workers.emplace_back(&VoxelGame::workerLoop,this);
}
2025-04-06 03:14:06 +00:00
2025-04-06 04:20:54 +00:00
VoxelGame::~VoxelGame(){
{
std::lock_guard<std::mutex> lk(queueMutex);
stopWorkers=true;
2025-04-06 03:14:06 +00:00
}
2025-04-06 04:20:54 +00:00
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();
2025-04-06 03:14:06 +00:00
glEnable(GL_DEPTH_TEST);
2025-04-06 04:20:54 +00:00
glEnable(GL_CULL_FACE);
2025-04-06 02:03:23 +00:00
return true;
}
2025-04-06 04:20:54 +00:00
//----------------------------------------------------------------------
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);
2025-04-06 02:03:23 +00:00
return true;
}
2025-04-06 04:20:54 +00:00
//----------------------------------------------------------------------
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) {
// worldspace 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);
}
2025-04-06 02:03:23 +00:00
}
}
2025-04-06 03:14:06 +00:00
return c;
2025-04-06 02:03:23 +00:00
}
2025-04-06 04:20:54 +00:00
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();
}
2025-04-06 03:14:06 +00:00
}
}
2025-04-06 04:20:54 +00:00
// 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: CPUside greedy meshing
RawMesh rm;
rm.key = key;
greedyMesh(chunkCopy, rm.verts, rm.indices);
// Enqueue for mainthread GPU upload
{
std::lock_guard<std::mutex> lk(queueMutex);
readyToUpload.push(std::move(rm));
}
2025-04-06 03:14:06 +00:00
}
2025-04-06 04:20:54 +00:00
}
//----------------------------------------------------------------------
// 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);
2025-04-06 03:14:06 +00:00
}
2025-04-06 04:20:54 +00:00
// 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;
2025-04-06 03:14:06 +00:00
}
2025-04-06 02:03:23 +00:00
}
2025-04-06 04:20:54 +00:00
//----------------------------------------------------------------------
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);
}
//----------------------------------------------------------------------
2025-04-06 03:14:06 +00:00
void VoxelGame::debugUI(){
2025-04-06 02:03:23 +00:00
ImGui::Begin("Debug");
2025-04-06 04:20:54 +00:00
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();
2025-04-06 03:14:06 +00:00
static bool wf=false;
if(ImGui::Checkbox("Wireframe",&wf))
2025-04-06 04:20:54 +00:00
glPolygonMode(GL_FRONT_AND_BACK,wf?GL_LINE:GL_FILL);
2025-04-06 02:03:23 +00:00
ImGui::End();
}
2025-04-06 04:20:54 +00:00
void VoxelGame::destroyMesh(ChunkMesh* m){
glDeleteBuffers(1,&m->vbo);
glDeleteBuffers(1,&m->ibo);
glDeleteVertexArrays(1,&m->vao);
}