#define STB_IMAGE_IMPLEMENTATION #include "VoxelGame.h" #include #include #include #include "stb_image.h" #include #include #include "imgui.h" #include "GreedyMesher.h" namespace { // --- Perlin Noise Helpers --- float fade_(float t) { return t * t * t * (t * (t * 6 - 15) + 10); } float lerp_(float a, float b, float t) { return a + t * (b - a); } float grad_(int hash, float x, float y) { int h = hash & 7; float u = h < 4 ? x : y; float v = h < 4 ? y : x; return ((h & 1) ? -u : u) + ((h & 2) ? -v : v); } 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) ) ); } } // Helper to safely query voxel (returns 0 if out of bounds) inline int voxelAt(const Chunk& c, int x, int y, int z) { if (x<0||y<0||z<0||x>=16||y>=16||z>=16) return 0; return c.voxels[x][y][z]; } VoxelGame::VoxelGame() : textureGrass(0), textureDirt(0), textureWood(0), totalChunksLoaded(0), totalChunksEverLoaded(0) { } VoxelGame::~VoxelGame() { glDeleteTextures(1, &textureGrass); glDeleteTextures(1, &textureDirt); glDeleteTextures(1, &textureWood); } bool VoxelGame::init() { if (!loadTextures()) return false; // Initial camera position float cam[3] = {32,32,80}; updateChunks(cam); // Build mesh for each chunk for (auto &p : chunks) { p.second.mesh = GreedyMesher::mesh(p.second.voxels); } glEnable(GL_DEPTH_TEST); glEnable(GL_TEXTURE_2D); return true; } bool VoxelGame::loadTextures() { int w,h,n; unsigned char* data; data = stbi_load("grass.jpg",&w,&h,&n,4); if (!data) { std::cerr<<"grass.jpg load fail\n"; return false; } glGenTextures(1,&textureGrass); glBindTexture(GL_TEXTURE_2D,textureGrass); 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,data); glGenerateMipmap(GL_TEXTURE_2D); stbi_image_free(data); data = stbi_load("dirt.jpg",&w,&h,&n,4); if (!data) { std::cerr<<"dirt.jpg load fail\n"; return false; } glGenTextures(1,&textureDirt); glBindTexture(GL_TEXTURE_2D,textureDirt); 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,data); glGenerateMipmap(GL_TEXTURE_2D); stbi_image_free(data); data = stbi_load("wood.png",&w,&h,&n,4); if (!data) { std::cerr<<"wood.png load fail\n"; return false; } glGenTextures(1,&textureWood); glBindTexture(GL_TEXTURE_2D,textureWood); 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,data); glGenerateMipmap(GL_TEXTURE_2D); stbi_image_free(data); return true; } Chunk VoxelGame::generateChunk(int cx,int cz) { Chunk c; c.x=cx; c.z=cz; c.voxels.assign(16, std::vector>(16, std::vector(16,0))); float scale=0.1f; for(int x=0;x<16;x++)for(int z=0;z<16;z++){ float wx = cx*16 + x, wz = cz*16 + z; float n = perlin(wx*scale, wz*scale); int h = int(((n+1)*0.5f)*16); h = std::clamp(h,0,16); for(int y=0;y keep; for(int dx=-r;dx<=r;dx++)for(int dz=-r;dz<=r;dz++){ ChunkKey key{cx+dx,cz+dz}; keep.insert(key); if(chunks.find(key)==chunks.end()){ Chunk nc = generateChunk(key.x,key.z); nc.mesh = GreedyMesher::mesh(nc.voxels); chunks[key]=std::move(nc); totalChunksEverLoaded++; } } 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); } void VoxelGame::render(){ for(auto &p:chunks){ float ox = p.first.x*16, oz = p.first.z*16; for(auto &q:p.second.mesh){ // shift by chunk offset 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}, 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}, v3[3]={q.x+q.dv[0]+ox,q.y+q.dv[1],q.z+q.dv[2]+oz}; unsigned int tex = (q.textureID==1?textureGrass:(q.textureID==2?textureDirt:textureWood)); glBindTexture(GL_TEXTURE_2D,tex); glBegin(GL_QUADS); glTexCoord2f(0,0); glVertex3fv(v0); glTexCoord2f(1,0); glVertex3fv(v1); glTexCoord2f(1,1); glVertex3fv(v2); glTexCoord2f(0,1); glVertex3fv(v3); glEnd(); } } } void VoxelGame::debugUI(){ ImGui::Begin("Debug"); ImGui::Text("Chunks Loaded: %d", totalChunksLoaded); ImGui::Text("Chunks Ever Loaded: %d", totalChunksEverLoaded); static bool wf=false; if(ImGui::Checkbox("Wireframe",&wf)) glPolygonMode(GL_FRONT_AND_BACK, wf?GL_LINE:GL_FILL); ImGui::End(); }