#include #include #include #include #include #include #include #include #include #include #include #include #include #include // Build: // mkdir build && cd build && cmake .. && make -j$(nproc) // ./minicraft static const int SCREEN_W = 1280; static const int SCREEN_H = 720; static const int CHUNK_SIZE = 16; 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 = -22.0f; static const float JUMP_VEL = 9.0f; static const float HW = 0.3f; static const float PH = 1.8f; 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}, {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}; // 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()((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=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 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;} } struct Vertex{float x,y,z,r,g,b;}; 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; 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}); verts.push_back({v3.x,v3.y,v3.z,sr,sg,sb}); idx.insert(idx.end(),{base,base+1,base+2,base,base+2,base+3}); } static void buildChunkMesh(Chunk& c){ std::vector verts; std::vector idx; int wx0=c.cx*CHUNK_SIZE,wz0=c.cz*CHUNK_SIZE; for(int x=0;x=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; 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; glm::mat4 proj=glm::perspective(glm::radians(70.0f),(float)SCREEN_W/SCREEN_H,0.05f,500.0f); SDL_SetRelativeMouseMode(SDL_TRUE); uint8_t selectedBlock=GRASS; bool running=true; Uint64 last=SDL_GetPerformanceCounter(); int lastPcx=INT_MAX,lastPcz=INT_MAX; 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){ 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)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) setBlock(hit.x,hit.y,hit.z,AIR); else if(e.button.button==SDL_BUTTON_RIGHT) setBlock(prev.x,prev.y,prev.z,selectedBlock); } } } 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 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; } int rebuilt=0; for(auto& [k,c]:CHUNKS) if(c->meshDirty&&c->generated){buildChunkMesh(*c);if(++rebuilt>=4)break;} glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glUseProgram(prog); glm::mat4 mvp=proj*cam.view(); glUniformMatrix4fv(mvpLoc,1,GL_FALSE,glm::value_ptr(mvp)); 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); } CHUNKS.clear(); glDeleteProgram(prog); SDL_GL_DeleteContext(ctx); SDL_DestroyWindow(win); SDL_Quit(); return 0; }