From 397641dd33567288b616f35b1e2838a18a19267c Mon Sep 17 00:00:00 2001 From: eldek Date: Mon, 16 Mar 2026 22:41:09 -0300 Subject: [PATCH] Added infinite terrain --- .vscode/tasks.json | 28 ++ main.cpp | 725 ++++++++++++++++++++++----------------------- 2 files changed, 380 insertions(+), 373 deletions(-) create mode 100644 .vscode/tasks.json diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..08d9005 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,28 @@ +{ + "tasks": [ + { + "type": "cppbuild", + "label": "C/C++: gcc build active file", + "command": "/usr/bin/gcc", + "args": [ + "-fdiagnostics-color=always", + "-g", + "${file}", + "-o", + "${fileDirname}/${fileBasenameNoExtension}" + ], + "options": { + "cwd": "${fileDirname}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "Task generated by Debugger." + } + ], + "version": "2.0.0" +} \ No newline at end of file diff --git a/main.cpp b/main.cpp index 817e82e..39baff9 100644 --- a/main.cpp +++ b/main.cpp @@ -7,152 +7,161 @@ #include #include -#include #include #include #include +#include +#include +#include -// Instalation -// On root: -// mkdir build -// cmake . -// make -j$(nproc) -// ./minicraft +// Build: +// mkdir build && cd build && cmake .. && make -j$(nproc) +// ./minicraft -// ─── Constants ─────────────────────────────────────────────────────────────── static const int SCREEN_W = 1280; static const int SCREEN_H = 720; static const int CHUNK_SIZE = 16; -static const int WORLD_H = 16; -static const float MOVE_SPEED = 5.0f; +static const int WORLD_H = 64; +static const int RENDER_DIST = 6; +static const float MOVE_SPEED = 6.0f; static const float MOUSE_SENS = 0.12f; -static const float GRAVITY = -20.0f; -static const float JUMP_VEL = 8.0f; +static const float GRAVITY = -22.0f; +static const float JUMP_VEL = 9.0f; +static const float HW = 0.3f; +static const float PH = 1.8f; -// ─── Block types ───────────────────────────────────────────────────────────── -enum BlockType : uint8_t { AIR=0, GRASS, DIRT, STONE, WOOD, LEAVES, SAND, WATER }; +enum BlockType : uint8_t { AIR=0, GRASS, DIRT, STONE, WOOD, LEAVES, SAND, SNOW, WATER }; struct BlockColor { float r,g,b; }; static const BlockColor BLOCK_COLORS[] = { - {0,0,0}, // AIR - {0.40f,0.72f,0.24f}, // GRASS - {0.55f,0.40f,0.22f}, // DIRT - {0.55f,0.55f,0.55f}, // STONE - {0.45f,0.30f,0.15f}, // WOOD - {0.20f,0.60f,0.15f}, // LEAVES - {0.90f,0.85f,0.55f}, // SAND - {0.20f,0.45f,0.90f}, // WATER + {0,0,0}, + {0.40f,0.72f,0.24f}, + {0.55f,0.40f,0.22f}, + {0.50f,0.50f,0.50f}, + {0.45f,0.30f,0.15f}, + {0.22f,0.55f,0.18f}, + {0.88f,0.83f,0.52f}, + {0.92f,0.95f,0.98f}, + {0.18f,0.42f,0.88f}, }; +static const float FACE_SHADE[] = {1.0f, 0.5f, 0.8f, 0.7f, 0.65f, 0.65f}; -// face shade multipliers (top, bottom, front, back, left, right) -static const float FACE_SHADE[] = {1.0f, 0.5f, 0.8f, 0.8f, 0.65f, 0.65f}; - -// ─── World ─────────────────────────────────────────────────────────────────── -static uint8_t WORLD[CHUNK_SIZE][WORLD_H][CHUNK_SIZE]; - -static int terrainHeight(int x, int z) { - float h = 5.0f - + 3.0f * sinf(x * 0.25f) * cosf(z * 0.20f) - + 2.0f * sinf(x * 0.10f + z * 0.13f) - + 1.0f * cosf(x * 0.40f - z * 0.35f); - return (int)h; +// Perlin noise +static int P[512]; +static void initNoise(int seed){ + int perm[256]; for(int i=0;i<256;i++) perm[i]=i; + unsigned rng=(unsigned)seed; + for(int i=255;i>0;i--){ rng=rng*1664525u+1013904223u; int j=(rng>>16)%(i+1); std::swap(perm[i],perm[j]); } + for(int i=0;i<512;i++) P[i]=perm[i&255]; +} +static float fade(float t){return t*t*t*(t*(t*6-15)+10);} +static float lerp(float a,float b,float t){return a+t*(b-a);} +static float grad(int h,float x,float y){h&=3;float u=(h<2)?x:y,v=(h<2)?y:x;return((h&1)?-u:u)+((h&2)?-v:v);} +static float noise2(float x,float y){ + int xi=(int)floorf(x)&255,yi=(int)floorf(y)&255; + float xf=x-floorf(x),yf=y-floorf(y),u=fade(xf),v=fade(yf); + int aa=P[P[xi]+yi],ab=P[P[xi]+yi+1],ba=P[P[xi+1]+yi],bb=P[P[xi+1]+yi+1]; + return lerp(lerp(grad(aa,xf,yf),grad(ba,xf-1,yf),u),lerp(grad(ab,xf,yf-1),grad(bb,xf-1,yf-1),u),v); +} +static float fbm(float x,float y,int oct=6){ + float val=0,amp=1,freq=1,max=0; + for(int i=0;i= WORLD_H) top = WORLD_H - 1; - for (int y = 0; y < WORLD_H; ++y) { - if (y == 0) WORLD[x][y][z] = STONE; - else if (y < top - 3) WORLD[x][y][z] = STONE; - else if (y < top) WORLD[x][y][z] = DIRT; - else if (y == top) WORLD[x][y][z] = (top <= 3) ? SAND : GRASS; +struct Chunk { + int cx,cz; + uint8_t blocks[CHUNK_SIZE][WORLD_H][CHUNK_SIZE]; + GLuint VAO=0,VBO=0,EBO=0; + int indexCount=0; + bool meshDirty=true,generated=false; + Chunk(int cx,int cz):cx(cx),cz(cz){memset(blocks,AIR,sizeof(blocks));} + ~Chunk(){if(VAO){glDeleteVertexArrays(1,&VAO);glDeleteBuffers(1,&VBO);glDeleteBuffers(1,&EBO);}} +}; + +struct ChunkKey{int x,z;bool operator==(const ChunkKey& o)const{return x==o.x&&z==o.z;}}; +struct ChunkKeyHash{size_t operator()(const ChunkKey& k)const{return std::hash()((long long)k.x<<32|(unsigned)k.z);}}; +using ChunkMap=std::unordered_map,ChunkKeyHash>; +static ChunkMap CHUNKS; + +static void generateChunk(Chunk& c){ + int wx0=c.cx*CHUNK_SIZE,wz0=c.cz*CHUNK_SIZE; + for(int x=0;x38); + for(int y=0;y 3 && top < WORLD_H - 5 && (x + z * 7) % 13 == 0) { - int trunk = top + 1; - for (int t = trunk; t < trunk + 3 && t < WORLD_H; ++t) - WORLD[x][t][z] = WOOD; - for (int dx = -1; dx <= 1; ++dx) - for (int dz = -1; dz <= 1; ++dz) - for (int dy = trunk + 2; dy <= trunk + 4; ++dy) { - int nx = x+dx, nz = z+dz; - if (nx>=0&&nx=0&&nz=16&&top<36&&top+6=0&&nx=0&&nz=WORLD_H) return AIR; + int cx=(int)floorf((float)wx/CHUNK_SIZE),cz=(int)floorf((float)wz/CHUNK_SIZE); + auto it=CHUNKS.find({cx,cz}); + if(it==CHUNKS.end()) return STONE; + int lx=wx-cx*CHUNK_SIZE,lz=wz-cz*CHUNK_SIZE; + return it->second->blocks[lx][wy][lz]; } -static GLuint buildProgram() { - GLuint vs = compileShader(GL_VERTEX_SHADER, VS_SRC); - GLuint fs = compileShader(GL_FRAGMENT_SHADER, FS_SRC); - GLuint p = glCreateProgram(); - glAttachShader(p, vs); glAttachShader(p, fs); - glLinkProgram(p); - glDeleteShader(vs); glDeleteShader(fs); - return p; +static void setBlock(int wx,int wy,int wz,uint8_t val){ + if(wy<0||wy>=WORLD_H) return; + int cx=(int)floorf((float)wx/CHUNK_SIZE),cz=(int)floorf((float)wz/CHUNK_SIZE); + auto it=CHUNKS.find({cx,cz}); + if(it==CHUNKS.end()) return; + int lx=wx-cx*CHUNK_SIZE,lz=wz-cz*CHUNK_SIZE; + it->second->blocks[lx][wy][lz]=val; + it->second->meshDirty=true; + if(lx==0){auto n=CHUNKS.find({cx-1,cz});if(n!=CHUNKS.end())n->second->meshDirty=true;} + if(lx==CHUNK_SIZE-1){auto n=CHUNKS.find({cx+1,cz});if(n!=CHUNKS.end())n->second->meshDirty=true;} + if(lz==0){auto n=CHUNKS.find({cx,cz-1});if(n!=CHUNKS.end())n->second->meshDirty=true;} + if(lz==CHUNK_SIZE-1){auto n=CHUNKS.find({cx,cz+1});if(n!=CHUNKS.end())n->second->meshDirty=true;} } -// ─── Mesh builder ──────────────────────────────────────────────────────────── -struct Vertex { float x,y,z,r,g,b; }; +struct Vertex{float x,y,z,r,g,b;}; -// Checks if we should draw a face against a neighboring block -static bool shouldDrawFace(uint8_t currentBlock, int nx, int ny, int nz) { - if (nx<0 || nx>=CHUNK_SIZE || ny<0 || ny>=WORLD_H || nz<0 || nz>=CHUNK_SIZE) return true; - uint8_t nb = WORLD[nx][ny][nz]; - if (nb == AIR) return true; - // Transparent blocks like water/leaves hide internal faces of the *same* type, - // but allow drawing faces of different adjacent blocks. - if (nb == WATER || nb == LEAVES) { - return currentBlock != nb; - } - return false; // Solid opaque block, hide the face +static bool shouldDrawFace(uint8_t cur,int wx,int wy,int wz){ + uint8_t nb=getBlock(wx,wy,wz); + if(nb==AIR) return true; + if(nb==WATER||nb==LEAVES) return cur!=nb; + return false; } -static void addFace(std::vector& verts, std::vector& idx, - glm::vec3 v0, glm::vec3 v1, glm::vec3 v2, glm::vec3 v3, - float r, float g, float b, float shade) { - GLuint base = (GLuint)verts.size(); - float sr=r*shade, sg=g*shade, sb=b*shade; +static void addFace(std::vector& verts,std::vector& idx, + glm::vec3 v0,glm::vec3 v1,glm::vec3 v2,glm::vec3 v3,float r,float g,float b,float shade){ + GLuint base=(GLuint)verts.size(); + float sr=r*shade,sg=g*shade,sb=b*shade; verts.push_back({v0.x,v0.y,v0.z,sr,sg,sb}); verts.push_back({v1.x,v1.y,v1.z,sr,sg,sb}); verts.push_back({v2.x,v2.y,v2.z,sr,sg,sb}); @@ -160,294 +169,264 @@ static void addFace(std::vector& verts, std::vector& idx, idx.insert(idx.end(),{base,base+1,base+2,base,base+2,base+3}); } -static void buildMesh(std::vector& verts, std::vector& idx) { - verts.clear(); idx.clear(); - for (int x=0;x verts; std::vector idx; + int wx0=c.cx*CHUNK_SIZE,wz0=c.cz*CHUNK_SIZE; + for(int x=0;x= CHUNK_SIZE || z < 0 || z >= CHUNK_SIZE) return true; // invisible walls - if (y >= WORLD_H) return false; - uint8_t b = WORLD[x][y][z]; - return b != AIR && b != WATER; -} - -// FIXED: Now correctly checks all blocks intersecting the player's bounding volume -static bool aabbSolid(float px, float py, float pz) { - int minX = (int)floorf(px - HW); - int maxX = (int)floorf(px + HW); - int minY = (int)floorf(py - PH); - int maxY = (int)floorf(py + 0.1f); - int minZ = (int)floorf(pz - HW); - int maxZ = (int)floorf(pz + HW); - - for (int x = minX; x <= maxX; ++x) { - for (int y = minY; y <= maxY; ++y) { - for (int z = minZ; z <= maxZ; ++z) { - if (solidAt((float)x, (float)y, (float)z)) return true; - } - } - } - return false; -} - -static void moveCamera(Camera& cam, const glm::vec3& move, float dt) { - // ── X axis ── - cam.pos.x += move.x * dt; - if (aabbSolid(cam.pos.x, cam.pos.y, cam.pos.z)) { - cam.pos.x -= move.x * dt; // Undo move if collided - } - - // ── Z axis ── - cam.pos.z += move.z * dt; - if (aabbSolid(cam.pos.x, cam.pos.y, cam.pos.z)) { - cam.pos.z -= move.z * dt; // Undo move if collided - } - - // ── Y axis (gravity + jump) ── - cam.vel.y += GRAVITY * dt; - cam.pos.y += cam.vel.y * dt; - - if (cam.vel.y < 0) { - // Moving downward - if (aabbSolid(cam.pos.x, cam.pos.y, cam.pos.z)) { - // Snap feet to top of the block below - cam.pos.y = floorf(cam.pos.y - PH) + 1.0f + PH + 0.001f; - cam.vel.y = 0.0f; - cam.onGround = true; - } else { - cam.onGround = false; - } - } else { - // Moving upward - if (aabbSolid(cam.pos.x, cam.pos.y, cam.pos.z)) { - // Hit ceiling — snap head downwards - cam.pos.y = floorf(cam.pos.y + 0.1f) - 0.1f - 0.001f; - cam.vel.y = 0.0f; - } - cam.onGround = false; - } -} - -// ─── Ray-cast for block placement/removal ──────────────────────────────────── -static bool raycast(const Camera& cam, glm::ivec3& hit, glm::ivec3& prev) { - glm::vec3 dir = cam.forward(); - glm::vec3 p = cam.pos; - glm::ivec3 last{(int)floorf(p.x), (int)floorf(p.y), (int)floorf(p.z)}; - for (float t=0; t<8.0f; t+=0.05f) { - glm::vec3 rp = p + dir*t; - glm::ivec3 bp{(int)floorf(rp.x),(int)floorf(rp.y),(int)floorf(rp.z)}; - - // Skip out of bounds, but don't break the ray - if (bp.x<0||bp.x>=CHUNK_SIZE||bp.y<0||bp.y>=WORLD_H||bp.z<0||bp.z>=CHUNK_SIZE) { - last = bp; - continue; - } - if (WORLD[bp.x][bp.y][bp.z] != AIR && WORLD[bp.x][bp.y][bp.z] != WATER) { - hit = bp; prev = last; return true; - } - last = bp; - } - return false; -} - -// ─── Main ──────────────────────────────────────────────────────────────────── -int main() { - SDL_Init(SDL_INIT_VIDEO); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); - - SDL_Window* win = SDL_CreateWindow("MiniCraft", - SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, - SCREEN_W, SCREEN_H, - SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN); - SDL_GLContext ctx = SDL_GL_CreateContext(win); - SDL_GL_SetSwapInterval(1); - - glewExperimental = GL_TRUE; - glewInit(); - - glEnable(GL_DEPTH_TEST); - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - glClearColor(0.53f, 0.81f, 0.98f, 1.0f); - - GLuint prog = buildProgram(); - GLuint VAO, VBO, EBO; - glGenVertexArrays(1,&VAO); - glGenBuffers(1,&VBO); - glGenBuffers(1,&EBO); - - generateWorld(); - - std::vector verts; - std::vector idx; - buildMesh(verts, idx); - - glBindVertexArray(VAO); - glBindBuffer(GL_ARRAY_BUFFER, VBO); - glBufferData(GL_ARRAY_BUFFER, verts.size()*sizeof(Vertex), verts.data(), GL_DYNAMIC_DRAW); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx.size()*sizeof(GLuint), idx.data(), GL_DYNAMIC_DRAW); + if(!c.VAO){glGenVertexArrays(1,&c.VAO);glGenBuffers(1,&c.VBO);glGenBuffers(1,&c.EBO);} + glBindVertexArray(c.VAO); + glBindBuffer(GL_ARRAY_BUFFER,c.VBO); + glBufferData(GL_ARRAY_BUFFER,verts.size()*sizeof(Vertex),verts.data(),GL_DYNAMIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,c.EBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER,idx.size()*sizeof(GLuint),idx.data(),GL_DYNAMIC_DRAW); glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,sizeof(Vertex),(void*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,sizeof(Vertex),(void*)(3*sizeof(float))); glEnableVertexAttribArray(1); + c.indexCount=(int)idx.size(); + c.meshDirty=false; +} - glm::mat4 proj = glm::perspective(glm::radians(70.0f),(float)SCREEN_W/SCREEN_H,0.05f,300.0f); - GLint mvpLoc = glGetUniformLocation(prog,"uMVP"); +static const char* VS_SRC=R"( +#version 330 core +layout(location=0) in vec3 aPos; +layout(location=1) in vec3 aColor; +out vec3 vColor; out float vFog; +uniform mat4 uMVP; uniform vec3 uCamPos; +void main(){ + gl_Position=uMVP*vec4(aPos,1.0); + vColor=aColor; + float dist=length(aPos-uCamPos); + vFog=clamp((dist-60.0)/40.0,0.0,1.0); +})"; +static const char* FS_SRC=R"( +#version 330 core +in vec3 vColor; in float vFog; +out vec4 fragColor; +void main(){ + vec3 fogColor=vec3(0.53,0.81,0.98); + fragColor=vec4(mix(vColor,fogColor,vFog),1.0); +})"; - SDL_SetRelativeMouseMode(SDL_TRUE); +static GLuint compileShader(GLenum type,const char* src){ + GLuint s=glCreateShader(type); glShaderSource(s,1,&src,nullptr); glCompileShader(s); + GLint ok; glGetShaderiv(s,GL_COMPILE_STATUS,&ok); + if(!ok){char buf[512];glGetShaderInfoLog(s,512,nullptr,buf);std::cerr<<"Shader: "<=WORLD_H) return false; + uint8_t b=getBlock(x,y,z); + return b!=AIR&&b!=WATER&&b!=LEAVES; +} +static bool aabbSolid(float px,float py,float pz){ + int x0=(int)floorf(px-HW),x1=(int)floorf(px+HW); + int y0=(int)floorf(py-PH),y1=(int)floorf(py+0.05f); + int z0=(int)floorf(pz-HW),z1=(int)floorf(pz+HW); + for(int x=x0;x<=x1;x++) for(int y=y0;y<=y1;y++) for(int z=z0;z<=z1;z++) + if(solidAt((float)x,(float)y,(float)z)) return true; + return false; +} +static void moveCamera(Camera& cam,const glm::vec3& move,float dt){ + cam.pos.x+=move.x*dt; if(aabbSolid(cam.pos.x,cam.pos.y,cam.pos.z)) cam.pos.x-=move.x*dt; + cam.pos.z+=move.z*dt; if(aabbSolid(cam.pos.x,cam.pos.y,cam.pos.z)) cam.pos.z-=move.z*dt; + cam.vel.y+=GRAVITY*dt; cam.pos.y+=cam.vel.y*dt; + if(cam.vel.y<0){ + if(aabbSolid(cam.pos.x,cam.pos.y,cam.pos.z)){ + cam.pos.y=floorf(cam.pos.y-PH)+1.0f+PH+0.001f; + cam.vel.y=0; cam.onGround=true; + } else cam.onGround=false; + } else { + if(aabbSolid(cam.pos.x,cam.pos.y,cam.pos.z)){ + cam.pos.y=floorf(cam.pos.y+0.05f)-0.05f-0.001f; cam.vel.y=0; + } + cam.onGround=false; + } +} + +static bool raycast(const Camera& cam,glm::ivec3& hit,glm::ivec3& prev){ + glm::vec3 dir=cam.forward(),p=cam.pos; + glm::ivec3 last{(int)floorf(p.x),(int)floorf(p.y),(int)floorf(p.z)}; + for(float t=0;t<8.0f;t+=0.05f){ + glm::vec3 rp=p+dir*t; + glm::ivec3 bp{(int)floorf(rp.x),(int)floorf(rp.y),(int)floorf(rp.z)}; + if(bp.y<0||bp.y>=WORLD_H){last=bp;continue;} + uint8_t b=getBlock(bp.x,bp.y,bp.z); + if(b!=AIR&&b!=WATER){hit=bp;prev=last;return true;} + last=bp; + } + return false; +} + +static void updateChunks(int pcx,int pcz){ + for(int dx=-RENDER_DIST;dx<=RENDER_DIST;dx++) + for(int dz=-RENDER_DIST;dz<=RENDER_DIST;dz++){ + ChunkKey k{pcx+dx,pcz+dz}; + if(CHUNKS.find(k)==CHUNKS.end()){ + auto c=std::make_unique(k.x,k.z); + generateChunk(*c); + CHUNKS[k]=std::move(c); + for(auto& [nk,nc]:CHUNKS) + if(abs(nk.x-k.x)+abs(nk.z-k.z)==1) nc->meshDirty=true; + } + } + std::vector toRemove; + for(auto& [k,c]:CHUNKS) + if(abs(k.x-pcx)>RENDER_DIST+1||abs(k.z-pcz)>RENDER_DIST+1) toRemove.push_back(k); + for(auto& k:toRemove) CHUNKS.erase(k); +} + +int main(){ + SDL_Init(SDL_INIT_VIDEO); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION,3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION,3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,24); + + SDL_Window* win=SDL_CreateWindow("MiniCraft - Infinite World", + SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED,SCREEN_W,SCREEN_H,SDL_WINDOW_OPENGL|SDL_WINDOW_SHOWN); + SDL_GLContext ctx=SDL_GL_CreateContext(win); + SDL_GL_SetSwapInterval(1); + glewExperimental=GL_TRUE; glewInit(); + glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); + glClearColor(0.53f,0.81f,0.98f,1.0f); + + GLuint prog=buildProgram(); + GLint mvpLoc=glGetUniformLocation(prog,"uMVP"); + GLint camLoc=glGetUniformLocation(prog,"uCamPos"); + + initNoise(12345); + std::cout<<"Generating spawn chunks...\n"; + updateChunks(0,0); + // build all initial meshes immediately + for(auto& [k,c]:CHUNKS) if(c->generated) buildChunkMesh(*c); Camera cam; - bool running = true; - Uint64 last = SDL_GetPerformanceCounter(); + for(int y=WORLD_H-1;y>=0;y--){ + uint8_t b=getBlock(0,y,0); + if(b!=AIR&&b!=WATER){cam.pos.y=(float)y+1+PH+0.1f;break;} + } + cam.pos.x=0.5f; cam.pos.z=0.5f; - uint8_t selectedBlock = GRASS; - bool meshDirty = false; + glm::mat4 proj=glm::perspective(glm::radians(70.0f),(float)SCREEN_W/SCREEN_H,0.05f,500.0f); + SDL_SetRelativeMouseMode(SDL_TRUE); - std::cout << "=== MiniCraft Controls ===\n" - << "WASD - Move\n" - << "Space - Jump\n" - << "Mouse - Look\n" - << "LMB - Destroy block\n" - << "RMB - Place block\n" - << "1-7 - Select block type\n" - << "ESC - Quit\n"; + uint8_t selectedBlock=GRASS; + bool running=true; + Uint64 last=SDL_GetPerformanceCounter(); + int lastPcx=INT_MAX,lastPcz=INT_MAX; - while (running) { - Uint64 now = SDL_GetPerformanceCounter(); - float dt = (float)(now - last) / SDL_GetPerformanceFrequency(); - if (dt > 0.1f) dt = 0.1f; - last = now; + std::cout<<"=== MiniCraft Infinite World ===\n" + <<"WASD Move\n" + <<"Space Jump\n" + <<"Mouse Look\n" + <<"LMB Destroy block\n" + <<"RMB Place block\n" + <<"1-8 Grass/Dirt/Stone/Wood/Leaves/Sand/Snow/Water\n" + <<"ESC Quit\n"; + + while(running){ + Uint64 now=SDL_GetPerformanceCounter(); + float dt=(float)(now-last)/SDL_GetPerformanceFrequency(); + if(dt>0.1f) dt=0.1f; + last=now; SDL_Event e; - while (SDL_PollEvent(&e)) { - if (e.type == SDL_QUIT) running = false; - if (e.type == SDL_KEYDOWN) { - if (e.key.keysym.sym == SDLK_ESCAPE) running = false; - if (e.key.keysym.sym == SDLK_1) selectedBlock = GRASS; - if (e.key.keysym.sym == SDLK_2) selectedBlock = DIRT; - if (e.key.keysym.sym == SDLK_3) selectedBlock = STONE; - if (e.key.keysym.sym == SDLK_4) selectedBlock = WOOD; - if (e.key.keysym.sym == SDLK_5) selectedBlock = LEAVES; - if (e.key.keysym.sym == SDLK_6) selectedBlock = SAND; - if (e.key.keysym.sym == SDLK_7) selectedBlock = WATER; + while(SDL_PollEvent(&e)){ + if(e.type==SDL_QUIT) running=false; + if(e.type==SDL_KEYDOWN){ + switch(e.key.keysym.sym){ + case SDLK_ESCAPE:running=false;break; + case SDLK_1:selectedBlock=GRASS;break; + case SDLK_2:selectedBlock=DIRT;break; + case SDLK_3:selectedBlock=STONE;break; + case SDLK_4:selectedBlock=WOOD;break; + case SDLK_5:selectedBlock=LEAVES;break; + case SDLK_6:selectedBlock=SAND;break; + case SDLK_7:selectedBlock=SNOW;break; + case SDLK_8:selectedBlock=WATER;break; + } } - if (e.type == SDL_MOUSEMOTION) { - cam.yaw += e.motion.xrel * MOUSE_SENS; - cam.pitch -= e.motion.yrel * MOUSE_SENS; - if (cam.pitch > 89.0f) cam.pitch = 89.0f; - if (cam.pitch < -89.0f) cam.pitch = -89.0f; + if(e.type==SDL_MOUSEMOTION){ + cam.yaw+=e.motion.xrel*MOUSE_SENS; + cam.pitch-=e.motion.yrel*MOUSE_SENS; + if(cam.pitch>89)cam.pitch=89; if(cam.pitch<-89)cam.pitch=-89; } - if (e.type == SDL_MOUSEBUTTONDOWN) { - glm::ivec3 hit, prev; - if (raycast(cam, hit, prev)) { - if (e.button.button == SDL_BUTTON_LEFT) { - WORLD[hit.x][hit.y][hit.z] = AIR; - meshDirty = true; - } else if (e.button.button == SDL_BUTTON_RIGHT) { - if (prev.x>=0&&prev.x=0&&prev.y=0&&prev.z0) fwd=glm::normalize(fwd); + glm::vec3 rgt=cam.right(); rgt.y=0; if(glm::length(rgt)>0) rgt=glm::normalize(rgt); + glm::vec3 move{0,0,0}; + if(keys[SDL_SCANCODE_W]) move+=fwd*MOVE_SPEED; + if(keys[SDL_SCANCODE_S]) move-=fwd*MOVE_SPEED; + if(keys[SDL_SCANCODE_D]) move+=rgt*MOVE_SPEED; + if(keys[SDL_SCANCODE_A]) move-=rgt*MOVE_SPEED; + if(keys[SDL_SCANCODE_SPACE]&&cam.onGround) cam.vel.y=JUMP_VEL; + moveCamera(cam,move,dt); + + int pcx=(int)floorf(cam.pos.x/CHUNK_SIZE); + int pcz=(int)floorf(cam.pos.z/CHUNK_SIZE); + if(pcx!=lastPcx||pcz!=lastPcz){ + updateChunks(pcx,pcz); lastPcx=pcx; lastPcz=pcz; } - // Movement - const Uint8* keys = SDL_GetKeyboardState(nullptr); - glm::vec3 fwd = cam.forward(); fwd.y = 0; if (glm::length(fwd)>0) fwd=glm::normalize(fwd); - glm::vec3 rgt = cam.right(); rgt.y = 0; if (glm::length(rgt)>0) rgt=glm::normalize(rgt); - glm::vec3 move{0,0,0}; - - if (keys[SDL_SCANCODE_W]) move += fwd * MOVE_SPEED; - if (keys[SDL_SCANCODE_S]) move -= fwd * MOVE_SPEED; - if (keys[SDL_SCANCODE_D]) move += rgt * MOVE_SPEED; - if (keys[SDL_SCANCODE_A]) move -= rgt * MOVE_SPEED; - if (keys[SDL_SCANCODE_SPACE] && cam.onGround) cam.vel.y = JUMP_VEL; - - moveCamera(cam, move, dt); + int rebuilt=0; + for(auto& [k,c]:CHUNKS) + if(c->meshDirty&&c->generated){buildChunkMesh(*c);if(++rebuilt>=4)break;} - // Render - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glUseProgram(prog); - glm::mat4 mvp = proj * cam.view(); + glm::mat4 mvp=proj*cam.view(); glUniformMatrix4fv(mvpLoc,1,GL_FALSE,glm::value_ptr(mvp)); - - glBindVertexArray(VAO); - glDrawElements(GL_TRIANGLES,(GLsizei)idx.size(),GL_UNSIGNED_INT,0); + glUniform3fv(camLoc,1,glm::value_ptr(cam.pos)); + for(auto& [k,c]:CHUNKS) + if(c->indexCount>0){glBindVertexArray(c->VAO);glDrawElements(GL_TRIANGLES,c->indexCount,GL_UNSIGNED_INT,0);} SDL_GL_SwapWindow(win); } - glDeleteVertexArrays(1,&VAO); - glDeleteBuffers(1,&VBO); - glDeleteBuffers(1,&EBO); + CHUNKS.clear(); glDeleteProgram(prog); SDL_GL_DeleteContext(ctx); SDL_DestroyWindow(win); SDL_Quit(); return 0; -} \ No newline at end of file +}