This commit is contained in:
OusmBlueNinja 2025-04-05 23:20:54 -05:00
parent 7e5261047e
commit 58962e52fc
9 changed files with 816 additions and 350 deletions

View File

@ -4,6 +4,7 @@
"*.js": "javascript", "*.js": "javascript",
"*.c": "c", "*.c": "c",
"*.scene": "yaml", "*.scene": "yaml",
"vector": "cpp" "vector": "cpp",
"cmath": "cpp"
} }
} }

View File

@ -1,208 +1,569 @@
#define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION
#include "VoxelGame.h" #include "VoxelGame.h"
#include <iostream> #include <iostream>
#include <GL/glew.h>
#include <GL/glu.h>
#include "stb_image.h"
#include <cmath> #include <cmath>
#include <unordered_set> #include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "stb_image.h"
#include "imgui.h" #include "imgui.h"
#include "GreedyMesher.h"
namespace { #include <GLFW/glfw3.h>
// --- Perlin Noise Helpers --- //----------------------------------------------------------------------
float fade_(float t) { // Perlin noise
return t * t * t * (t * (t * 6 - 15) + 10);
} static std::vector<int> perm,p;
float lerp_(float a, float b, float t) { static std::once_flag initFlag;
return a + t * (b - a); static void initPerlin(){
} perm.resize(256);
float grad_(int hash, float x, float y) { std::iota(perm.begin(),perm.end(),0);
int h = hash & 7; std::mt19937 gen(1337);
float u = h < 4 ? x : y; std::shuffle(perm.begin(),perm.end(),gen);
float v = h < 4 ? y : x; p.resize(512);
return ((h & 1) ? -u : u) + ((h & 2) ? -v : v); for(int i=0;i<512;++i) p[i]=perm[i&255];
}
float perlin(float x, float y) {
static int p[512];
static bool init = false;
if (!init) {
int perm[256] = {
151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,
140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,
247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,
57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,
74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,
60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,
65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,
200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,
52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,
207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,
119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,
129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,
218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,
81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,
184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,
222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
};
for (int i = 0; i < 256; i++) {
p[i] = perm[i];
p[i + 256] = perm[i];
}
init = true;
}
int X = int(floor(x)) & 255;
int Y = int(floor(y)) & 255;
x -= floor(x);
y -= floor(y);
float u = fade_(x);
float v = fade_(y);
int A = p[X] + Y, B = p[X + 1] + Y;
return lerp_( v,
lerp_( u, grad_(p[A], x, y), grad_(p[A+1], x-1, y) ),
lerp_( u, grad_(p[B], x, y-1), grad_(p[B+1], x-1, y-1) )
);
}
} }
static inline float fade(float t){ return t*t*t*(t*(t*6-15)+10); }
// Helper to safely query voxel (returns 0 if out of bounds) static inline float lerp_(float a,float b,float t){ return a+t*(b-a); }
inline int voxelAt(const Chunk& c, int x, int y, int z) { static inline float grad(int h,float x,float y,float z){
if (x<0||y<0||z<0||x>=16||y>=16||z>=16) return 0; 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]; return c.voxels[x][y][z];
} }
//----------------------------------------------------------------------
// Greedy mesh with normals
VoxelGame::VoxelGame() void VoxelGame::greedyMesh(const Chunk &chunk,
: textureGrass(0), textureDirt(0), textureWood(0), std::vector<Vertex> &vertices,
totalChunksLoaded(0), totalChunksEverLoaded(0) std::vector<uint32_t> &indices)
{ {
} const int dims[3]={CHUNK_SIZE,CHUNK_HEIGHT,CHUNK_SIZE};
for(int d=0;d<3;d++){
VoxelGame::~VoxelGame() { int u=(d+1)%3, v=(d+2)%3;
glDeleteTextures(1, &textureGrass); int x[3]={0,0,0}, q[3]={0,0,0}; q[d]=1;
glDeleteTextures(1, &textureDirt); for(x[d]=-1;x[d]<dims[d];){
glDeleteTextures(1, &textureWood); 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;
bool VoxelGame::init() { int p0[3]={x[0],x[1],x[2]};
if (!loadTextures()) return false; int p1[3]={x[0]+q[0],x[1]+q[1],x[2]+q[2]};
// Initial camera position if(x[d]>=0) a=voxelAt(chunk,p0[0],p0[1],p0[2]);
float cam[3] = {32,32,80}; if(x[d]<dims[d]-1) b=voxelAt(chunk,p1[0],p1[1],p1[2]);
updateChunks(cam); mask[x[u]+dims[u]*x[v]] = ((a!=0)!=(b!=0)) ? ((a!=0)?a:-b) : 0;
// Build mesh for each chunk
for (auto &p : chunks) {
p.second.mesh = GreedyMesher::mesh(p.second.voxels);
} }
}
++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_DEPTH_TEST);
glEnable(GL_TEXTURE_2D); glEnable(GL_CULL_FACE);
return true; return true;
} }
bool VoxelGame::loadTextures() { //----------------------------------------------------------------------
int w,h,n;
unsigned char* data; bool VoxelGame::loadTextures(){
data = stbi_load("grass.jpg",&w,&h,&n,4); auto load=[&](const char*f,GLuint &t){
if (!data) { std::cerr<<"grass.jpg load fail\n"; return false; } int W,H,N; unsigned char*d=stbi_load(f,&W,&H,&N,4);
glGenTextures(1,&textureGrass); glBindTexture(GL_TEXTURE_2D,textureGrass); 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_S,GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,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_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_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,data); glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,W,H,0,GL_RGBA,GL_UNSIGNED_BYTE,d);
glGenerateMipmap(GL_TEXTURE_2D); glGenerateMipmap(GL_TEXTURE_2D);
stbi_image_free(data); stbi_image_free(d);
data = stbi_load("dirt.jpg",&w,&h,&n,4); return true;
if (!data) { std::cerr<<"dirt.jpg load fail\n"; return false; } };
glGenTextures(1,&textureDirt); glBindTexture(GL_TEXTURE_2D,textureDirt); return load("dirt.jpg",textureDirt)
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT); && load("grass.jpg",textureGrass)
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT); && load("wood.png",textureWood);
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,data); //----------------------------------------------------------------------
glGenerateMipmap(GL_TEXTURE_2D);
stbi_image_free(data); bool VoxelGame::loadShaders(){
data = stbi_load("wood.png",&w,&h,&n,4); auto compile=[&](const char*s,GLenum t){
if (!data) { std::cerr<<"wood.png load fail\n"; return false; } GLuint sh=glCreateShader(t);
glGenTextures(1,&textureWood); glBindTexture(GL_TEXTURE_2D,textureWood); glShaderSource(sh,1,&s,nullptr);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT); glCompileShader(sh);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT); return sh;
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); };
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); // depth shader
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,w,h,0,GL_RGBA,GL_UNSIGNED_BYTE,data); const char* dvsrc = R"(
glGenerateMipmap(GL_TEXTURE_2D); #version 330 core
stbi_image_free(data); 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; return true;
} }
Chunk VoxelGame::generateChunk(int cx,int cz) { //----------------------------------------------------------------------
Chunk c; c.x=cx; c.z=cz;
c.voxels.assign(16, std::vector<std::vector<int>>(16, std::vector<int>(16,0))); void VoxelGame::initShadowMaps(){
float scale=0.1f; glGenFramebuffers(NUM_CASCADES,depthFBO);
for(int x=0;x<16;x++)for(int z=0;z<16;z++){ glGenTextures(NUM_CASCADES,depthTex);
float wx = cx*16 + x, wz = cz*16 + z; for(int i=0;i<NUM_CASCADES;i++){
float n = perlin(wx*scale, wz*scale); glBindTexture(GL_TEXTURE_2D,depthTex[i]);
int h = int(((n+1)*0.5f)*16); glTexImage2D(GL_TEXTURE_2D,0,GL_DEPTH_COMPONENT,
h = std::clamp(h,0,16); SHADOW_MAP_SIZE,SHADOW_MAP_SIZE,
for(int y=0;y<h;y++){ 0,GL_DEPTH_COMPONENT,GL_FLOAT,nullptr);
c.voxels[x][y][z] = (y==h-1?1:2); 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);
}
} }
} }
return c; return c;
} }
void VoxelGame::updateChunks(const float cam[3]) {
int cx = int(floor(cam[0]/16)), cz=int(floor(cam[2]/16)); void VoxelGame::update(float dt, glm::vec3 const& camPos){
int r=2; lastDeltaTime=dt;
std::unordered_set<ChunkKey,ChunkKeyHash> keep; fps=1.0f/dt;
for(int dx=-r;dx<=r;dx++)for(int dz=-r;dz<=r;dz++){ frameTimeSum+=dt; frameCount++;
ChunkKey key{cx+dx,cz+dz};
keep.insert(key); int old=totalChunksEverLoaded;
if(chunks.find(key)==chunks.end()){ double t0=glfwGetTime();
Chunk nc = generateChunk(key.x,key.z); updateChunks(camPos);
nc.mesh = GreedyMesher::mesh(nc.voxels); double t1=glfwGetTime();
chunks[key]=std::move(nc); lastChunkGenTime=t1-t0;
totalChunksEverLoaded++; chunkGenCount=totalChunksEverLoaded-old;
} totalChunkGenTime+=lastChunkGenTime*chunkGenCount;
}
for(auto it=chunks.begin();it!=chunks.end();){
if(!keep.count(it->first)) it=chunks.erase(it);
else ++it;
}
totalChunksLoaded = chunks.size();
} }
void VoxelGame::update(float dt,const float cam[3]){ //----------------------------------------------------------------------------
updateChunks(cam); // 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());
} }
void VoxelGame::render(){ //----------------------------------------------------------------------------
for(auto &p:chunks){ // Worker thread: copies chunk data, runs greedyMesh on CPU, then enqueues RawMesh for GPU upload.
float ox = p.first.x*16, oz = p.first.z*16; void VoxelGame::workerLoop() {
for(auto &q:p.second.mesh){ while (true) {
// shift by chunk offset ChunkKey key;
float v0[3]={q.x+ox,q.y,q.z+oz}, {
v1[3]={q.x+q.du[0]+ox,q.y+q.du[1],q.z+q.du[2]+oz}, std::unique_lock<std::mutex> lk(queueMutex);
v2[3]={q.x+q.du[0]+q.dv[0]+ox,q.y+q.du[1]+q.dv[1],q.z+q.du[2]+q.dv[2]+oz}, queueCV.wait(lk, [&]{ return stopWorkers || !toBuild.empty(); });
v3[3]={q.x+q.dv[0]+ox,q.y+q.dv[1],q.z+q.dv[2]+oz}; if (stopWorkers && toBuild.empty()) return;
unsigned int tex = (q.textureID==1?textureGrass:(q.textureID==2?textureDirt:textureWood)); key = toBuild.front();
glBindTexture(GL_TEXTURE_2D,tex); toBuild.pop();
glBegin(GL_QUADS); }
glTexCoord2f(0,0); glVertex3fv(v0);
glTexCoord2f(1,0); glVertex3fv(v1); // Copy voxel data under lock
glTexCoord2f(1,1); glVertex3fv(v2); Chunk chunkCopy;
glTexCoord2f(0,1); glVertex3fv(v3); {
glEnd(); 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));
} }
} }
} }
//----------------------------------------------------------------------
// 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(){ void VoxelGame::debugUI(){
ImGui::Begin("Debug"); 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 Loaded: %d", totalChunksLoaded);
ImGui::Text("Chunks Ever Loaded: %d", totalChunksEverLoaded); 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; static bool wf=false;
if(ImGui::Checkbox("Wireframe",&wf)) if(ImGui::Checkbox("Wireframe",&wf))
glPolygonMode(GL_FRONT_AND_BACK, wf?GL_LINE:GL_FILL); glPolygonMode(GL_FRONT_AND_BACK,wf?GL_LINE:GL_FILL);
ImGui::End(); ImGui::End();
} }
void VoxelGame::destroyMesh(ChunkMesh* m){
glDeleteBuffers(1,&m->vbo);
glDeleteBuffers(1,&m->ibo);
glDeleteVertexArrays(1,&m->vao);
}

View File

@ -1,33 +1,53 @@
#ifndef VOXELGAME_H #pragma once
#define VOXELGAME_H
#include <vector> #include <vector>
#include <algorithm>
#include <unordered_map> #include <unordered_map>
#include "GreedyMesher.h" #include <memory>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <algorithm>
#include <random>
// Structure representing a chunk (16×16×16 voxels) #include <GL/glew.h>
struct Chunk { #include <glm/glm.hpp>
int x, z; // Chunk grid coordinates
// 3D voxel data: dimensions [16][16][16] // world dimensions
std::vector<std::vector<std::vector<int>>> voxels; static constexpr int CHUNK_SIZE = 16;
// **Merged mesh faces** produced by the greedy mesher static constexpr int CHUNK_HEIGHT = 64;
std::vector<Quad> mesh;
};
// A key for identifying a chunk by its grid coordinates.
struct ChunkKey { struct ChunkKey {
int x, z; int x,z;
bool operator==(const ChunkKey &other) const { bool operator==(ChunkKey const& o) const { return x==o.x && z==o.z; }
return x == other.x && z == other.z; };
struct ChunkKeyHash {
std::size_t operator()(ChunkKey const& k) const noexcept {
return std::hash<long long>()(((long long)k.x<<32)|(unsigned)k.z);
} }
}; };
struct ChunkKeyHash { struct Chunk {
std::size_t operator()(const ChunkKey &key) const { int x,z;
return std::hash<int>()(key.x) ^ (std::hash<int>()(key.z) << 1); // voxels[x][y][z], y ∈ [0,CHUNK_HEIGHT)
} std::vector<std::vector<std::vector<int>>> voxels;
};
struct ChunkMesh {
GLuint vao,vbo,ibo;
GLsizei indexCount;
};
struct Vertex {
glm::vec3 pos;
glm::vec3 normal;
glm::vec2 uv;
};
struct RawMesh {
ChunkKey key;
std::vector<Vertex> verts;
std::vector<uint32_t> indices;
}; };
class VoxelGame { class VoxelGame {
@ -35,24 +55,63 @@ public:
VoxelGame(); VoxelGame();
~VoxelGame(); ~VoxelGame();
bool init(); // initialize with screen size (for cascades)
void update(float deltaTime, const float cameraPos[3]); bool init(int screenW,int screenH);
void render(); void update(float dt, glm::vec3 const& camPos);
void render(glm::mat4 const& view, glm::mat4 const& proj);
void debugUI(); void debugUI();
private: private:
// chunk generation
Chunk generateChunk(int cx,int cz);
void updateChunks(glm::vec3 const& camPos);
// CPU meshing
void workerLoop();
void greedyMesh(Chunk const&, std::vector<Vertex>&, std::vector<uint32_t>&);
void destroyMesh(ChunkMesh*);
std::unordered_map<ChunkKey,Chunk,ChunkKeyHash> chunks;
std::unordered_map<ChunkKey,std::unique_ptr<ChunkMesh>,ChunkKeyHash> meshes;
std::mutex queueMutex;
std::condition_variable queueCV;
std::queue<ChunkKey> toBuild;
std::queue<RawMesh> readyToUpload;
bool stopWorkers=false;
std::vector<std::thread> workers;
// stats
int totalChunksLoaded=0, totalChunksEverLoaded=0;
int chunkGenCount=0;
double totalChunkGenTime=0, lastChunkGenTime=0;
float fps=0, lastDeltaTime=0;
double frameTimeSum=0; int frameCount=0;
int lastFaceCount=0, lastGLCalls=0;
// textures
GLuint textureDirt, textureGrass, textureWood;
// cascaded shadow maps
static constexpr int NUM_CASCADES = 3;
static constexpr int SHADOW_MAP_SIZE = 1024;
GLuint depthFBO[NUM_CASCADES];
GLuint depthTex[NUM_CASCADES];
float cascadeSplits[NUM_CASCADES];
glm::mat4 lightSpaceMat[NUM_CASCADES];
// shaders
GLuint depthProg, mainProg;
// light & camera
glm::vec3 lightDir = glm::normalize(glm::vec3(1.0f, -1.0f, 0.5f));
glm::vec3 cameraPos;
int screenW, screenH;
int renderDistance = 2;
bool loadTextures(); bool loadTextures();
Chunk generateChunk(int cx, int cz); bool loadShaders();
void updateChunks(const float cameraPos[3]); void initShadowMaps();
void drawCube(float x, float y, float z, int voxelType); void renderScene(GLuint prog);
std::unordered_map<ChunkKey, Chunk, ChunkKeyHash> chunks;
int totalChunksLoaded;
int totalChunksEverLoaded;
unsigned int textureGrass;
unsigned int textureDirt;
unsigned int textureWood;
}; };
#endif // VOXELGAME_H

Binary file not shown.

74
cpp-voxel-engine/a Normal file
View File

@ -0,0 +1,74 @@
static void computeLightSpace(glm::mat4 view, glm::mat4 proj,
glm::vec3 lightDir,
glm::mat4 out[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++){
glm::mat4 lightView = glm::lookAt(-lightDir*100.0f,glm::vec3(0),glm::vec3(0,1,0));
glm::vec3 mn(FLT_MAX), mx(-FLT_MAX);
for(auto &c: corners){
glm::vec4 wc = inv * c; wc/=wc.w;
glm::vec4 lc = lightView * wc;
mn = glm::min(mn,glm::vec3(lc));
mx = glm::max(mx,glm::vec3(lc));
}
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);
// 1) depth passes
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);
}

View File

@ -4,7 +4,7 @@ Size=400,400
Collapsed=0 Collapsed=0
[Window][Debug] [Window][Debug]
Pos=492,48 Pos=12,41
Size=214,119 Size=336,392
Collapsed=0 Collapsed=0

View File

@ -1,189 +1,160 @@
#include <iostream> #include <iostream>
#include <cmath> #include <cmath>
#include <GL/glew.h> #include <GL/glew.h>
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#include <GL/glu.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "imgui.h" #include "imgui.h"
#include "imgui_internal.h" // For Im_PI
#include "imgui_impl_glfw.h" #include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h" #include "imgui_impl_opengl3.h"
#include "VoxelGame.h" #include "VoxelGame.h"
// Global camera variables. // camera globals
float yaw = -90.0f; static glm::vec3 cameraPos = {32.0f, 32.0f, 80.0f};
float pitch = 0.0f; static glm::vec3 cameraFront = {0.0f, 0.0f, -1.0f};
float lastX = 400.0f, lastY = 300.0f; static glm::vec3 cameraUp = {0.0f, 1.0f, 0.0f};
bool firstMouse = true; static float yaw = -90.0f, pitch = 0.0f;
float cameraPos[3] = {32.0f, 32.0f, 80.0f}; static bool firstMouse = true;
float cameraFront[3] = {0.0f, 0.0f, -1.0f}; static float lastX, lastY;
float cameraUp[3] = {0.0f, 1.0f, 0.0f};
void mouse_callback(GLFWwindow* window, double xpos, double ypos) { static void mouse_callback(GLFWwindow* window, double xpos, double ypos) {
if (glfwGetInputMode(window, GLFW_CURSOR) == GLFW_CURSOR_NORMAL) if (glfwGetInputMode(window, GLFW_CURSOR) == GLFW_CURSOR_NORMAL)
return; return; // do not rotate if cursor is free
if (firstMouse) { if (firstMouse) {
lastX = static_cast<float>(xpos); lastX = float(xpos);
lastY = static_cast<float>(ypos); lastY = float(ypos);
firstMouse = false; firstMouse = false;
} }
float xoffset = static_cast<float>(xpos) - lastX; float dx = float(xpos) - lastX;
float yoffset = lastY - static_cast<float>(ypos); float dy = lastY - float(ypos);
lastX = static_cast<float>(xpos); lastX = float(xpos);
lastY = static_cast<float>(ypos); lastY = float(ypos);
float sensitivity = 0.1f; const float sensitivity = 0.1f;
xoffset *= sensitivity; dx *= sensitivity;
yoffset *= sensitivity; dy *= sensitivity;
yaw += xoffset; yaw += dx;
pitch += yoffset; pitch += dy;
if (pitch > 89.0f) pitch = 89.0f; pitch = std::clamp(pitch, -89.0f, 89.0f);
if (pitch < -89.0f) pitch = -89.0f;
float radYaw = yaw * static_cast<float>(IM_PI) / 180.0f; float ry = glm::radians(yaw);
float radPitch = pitch * static_cast<float>(IM_PI) / 180.0f; float rp = glm::radians(pitch);
cameraFront = glm::normalize(glm::vec3(
cameraFront[0] = cos(radYaw) * cos(radPitch); cos(ry)*cos(rp),
cameraFront[1] = sin(radPitch); sin(rp),
cameraFront[2] = sin(radYaw) * cos(radPitch); sin(ry)*cos(rp)
float len = std::sqrt(cameraFront[0]*cameraFront[0] + ));
cameraFront[1]*cameraFront[1] +
cameraFront[2]*cameraFront[2]);
cameraFront[0] /= len;
cameraFront[1] /= len;
cameraFront[2] /= len;
} }
GLFWwindow* initWindow(int width, int height, const char* title) { static GLFWwindow* initWindow(int w, int h, const char* title) {
if (!glfwInit()) { if (!glfwInit()) return nullptr;
std::cerr << "Failed to initialize GLFW\n";
return nullptr;
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(width, height, title, nullptr, nullptr); #ifdef __APPLE__
if (!window) { glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
std::cerr << "Failed to create GLFW window\n"; #endif
GLFWwindow* win = glfwCreateWindow(w, h, title, nullptr, nullptr);
if (!win) { glfwTerminate(); return nullptr; }
glfwMakeContextCurrent(win);
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK) {
glfwDestroyWindow(win);
glfwTerminate(); glfwTerminate();
return nullptr; return nullptr;
} }
glfwMakeContextCurrent(window); glViewport(0, 0, w, h);
glfwSetCursorPosCallback(window, mouse_callback); glfwSetCursorPosCallback(win, mouse_callback);
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); // start with cursor disabled
glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glewExperimental = GL_TRUE; lastX = w / 2.0f;
if (glewInit() != GLEW_OK) { lastY = h / 2.0f;
std::cerr << "GLEW Error\n"; return win;
return nullptr;
}
return window;
} }
int main() { int main() {
GLFWwindow* window = initWindow(800, 600, "Voxel Game"); const int SCR_W = 1280, SCR_H = 720;
if (!window) GLFWwindow* window = initWindow(SCR_W, SCR_H, "Voxel CSM");
return -1; if (!window) return -1;
// ImGui
IMGUI_CHECKVERSION(); IMGUI_CHECKVERSION();
ImGui::CreateContext(); ImGui::CreateContext();
ImGuiIO &io = ImGui::GetIO(); (void)io;
ImGui::StyleColorsDark();
ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init("#version 330"); ImGui_ImplOpenGL3_Init("#version 330");
ImGui::StyleColorsDark();
// Our game
VoxelGame game; VoxelGame game;
if (!game.init()) { if (!game.init(SCR_W, SCR_H)) {
std::cerr << "Failed to initialize game\n"; std::cerr << "Failed to init VoxelGame\n";
return -1; return -1;
} }
float deltaTime = 0.0f, lastFrame = 0.0f; float lastFrame = 0.0f;
static bool cursorEnabled = false; bool cursorEnabled = false;
static bool f1Pressed = false; bool f1Pressed = false;
while (!glfwWindowShouldClose(window)) { while (!glfwWindowShouldClose(window)) {
float currentFrame = static_cast<float>(glfwGetTime()); float now = float(glfwGetTime());
deltaTime = currentFrame - lastFrame; float dt = now - lastFrame;
lastFrame = currentFrame; lastFrame = now;
glfwPollEvents(); glfwPollEvents();
// Toggle cursor lock with F1 (if ImGui isnt capturing the mouse). // Toggle cursor with F1 (when ImGui not capturing)
if (glfwGetKey(window, GLFW_KEY_F1) == GLFW_PRESS && !ImGui::GetIO().WantCaptureMouse) { ImGuiIO& io = ImGui::GetIO();
if (glfwGetKey(window, GLFW_KEY_F1) == GLFW_PRESS && !io.WantCaptureMouse) {
if (!f1Pressed) { if (!f1Pressed) {
cursorEnabled = !cursorEnabled; cursorEnabled = !cursorEnabled;
glfwSetInputMode(window, GLFW_CURSOR, cursorEnabled ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED); glfwSetInputMode(window, GLFW_CURSOR,
cursorEnabled ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED);
f1Pressed = true; f1Pressed = true;
firstMouse = true; firstMouse = true; // reset for mouse callback
} }
} else if (glfwGetKey(window, GLFW_KEY_F1) == GLFW_RELEASE) { } else if (glfwGetKey(window, GLFW_KEY_F1) == GLFW_RELEASE) {
f1Pressed = false; f1Pressed = false;
} }
float cameraSpeed = 5.0f * deltaTime; // WASD movement
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) { float speed = 5.0f * dt;
cameraPos[0] += cameraFront[0] * cameraSpeed; if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
cameraPos[1] += cameraFront[1] * cameraSpeed; cameraPos += cameraFront * speed;
cameraPos[2] += cameraFront[2] * cameraSpeed; if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
} cameraPos -= cameraFront * speed;
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) { glm::vec3 right = glm::normalize(glm::cross(cameraFront, cameraUp));
cameraPos[0] -= cameraFront[0] * cameraSpeed; if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
cameraPos[1] -= cameraFront[1] * cameraSpeed; cameraPos -= right * speed;
cameraPos[2] -= cameraFront[2] * cameraSpeed; if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
} cameraPos += right * speed;
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) {
float right[3] = {
cameraFront[1]*cameraUp[2] - cameraFront[2]*cameraUp[1],
cameraFront[2]*cameraUp[0] - cameraFront[0]*cameraUp[2],
cameraFront[0]*cameraUp[1] - cameraFront[1]*cameraUp[0]
};
float rLen = std::sqrt(right[0]*right[0] + right[1]*right[1] + right[2]*right[2]);
right[0] /= rLen; right[1] /= rLen; right[2] /= rLen;
cameraPos[0] -= right[0] * cameraSpeed;
cameraPos[1] -= right[1] * cameraSpeed;
cameraPos[2] -= right[2] * cameraSpeed;
}
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) {
float right[3] = {
cameraFront[1]*cameraUp[2] - cameraFront[2]*cameraUp[1],
cameraFront[2]*cameraUp[0] - cameraFront[0]*cameraUp[2],
cameraFront[0]*cameraUp[1] - cameraFront[1]*cameraUp[0]
};
float rLen = std::sqrt(right[0]*right[0] + right[1]*right[1] + right[2]*right[2]);
right[0] /= rLen; right[1] /= rLen; right[2] /= rLen;
cameraPos[0] += right[0] * cameraSpeed;
cameraPos[1] += right[1] * cameraSpeed;
cameraPos[2] += right[2] * cameraSpeed;
}
// Build view/proj
glm::mat4 view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
glm::mat4 proj = glm::perspective(glm::radians(45.0f),
float(SCR_W)/SCR_H, 0.1f, 200.0f);
// Update & render
game.update(dt, cameraPos);
game.render(view, proj);
// Debug UI
ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame(); ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame(); ImGui::NewFrame();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0, 800.0/600.0, 0.1, 200.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
float centerX = cameraPos[0] + cameraFront[0];
float centerY = cameraPos[1] + cameraFront[1];
float centerZ = cameraPos[2] + cameraFront[2];
gluLookAt(cameraPos[0], cameraPos[1], cameraPos[2],
centerX, centerY, centerZ,
cameraUp[0], cameraUp[1], cameraUp[2]);
game.update(deltaTime, cameraPos);
game.render();
game.debugUI(); game.debugUI();
ImGui::Render(); ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwSwapBuffers(window); glfwSwapBuffers(window);
} }
// Cleanup
ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown(); ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext(); ImGui::DestroyContext();

Binary file not shown.

Binary file not shown.