diff --git a/main.cpp b/main.cpp index 39baff9..102707f 100644 --- a/main.cpp +++ b/main.cpp @@ -13,11 +13,13 @@ #include #include #include +#include // 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; @@ -29,24 +31,30 @@ 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; +static const int HOTBAR_SIZE = 8; +// ─── Block types ───────────────────────────────────────────────────────────── enum BlockType : uint8_t { AIR=0, GRASS, DIRT, STONE, WOOD, LEAVES, SAND, SNOW, WATER }; +static const int NUM_BLOCK_TYPES = 9; 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}, + {0.40f,0.72f,0.24f}, // GRASS + {0.55f,0.40f,0.22f}, // DIRT + {0.50f,0.50f,0.50f}, // STONE + {0.45f,0.30f,0.15f}, // WOOD + {0.22f,0.55f,0.18f}, // LEAVES + {0.88f,0.83f,0.52f}, // SAND + {0.92f,0.95f,0.98f}, // SNOW + {0.18f,0.42f,0.88f}, // WATER +}; +static const char* BLOCK_NAMES[] = { + "Air","Grass","Dirt","Stone","Wood","Leaves","Sand","Snow","Water" }; static const float FACE_SHADE[] = {1.0f, 0.5f, 0.8f, 0.7f, 0.65f, 0.65f}; -// Perlin noise +// ─── Perlin Noise ───────────────────────────────────────────────────────────── static int P[512]; static void initNoise(int seed){ int perm[256]; for(int i=0;i<256;i++) perm[i]=i; @@ -69,6 +77,7 @@ static float fbm(float x,float y,int oct=6){ return val/max; } +// ─── World chunk ───────────────────────────────────────────────────────────── struct Chunk { int cx,cz; uint8_t blocks[CHUNK_SIZE][WORLD_H][CHUNK_SIZE]; @@ -78,7 +87,6 @@ struct Chunk { 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>; @@ -93,28 +101,22 @@ static void generateChunk(Chunk& c){ float detail=fbm(wx*2.0f+5.3f,wz*2.0f+1.7f,5); float ridge=1.0f-fabsf(fbm(wx*0.8f+3.1f,wz*0.8f+8.9f,4)); float h=12.0f+cont*18.0f+detail*6.0f+ridge*14.0f*std::max(0.0f,cont); - int top=(int)h; - top=std::max(2,std::min(WORLD_H-2,top)); + int top=(int)h; top=std::max(2,std::min(WORLD_H-2,top)); bool isSand=(top<16),isSnow=(top>38); for(int y=0;y=16&&top<36&&top+6=0&&nx=0&&nz=WORLD_H) return AIR; + if(wy<0) return STONE; if(wy>=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]; + auto it=CHUNKS.find({cx,cz}); if(it==CHUNKS.end()) return STONE; + return it->second->blocks[wx-cx*CHUNK_SIZE][wy][wz-cz*CHUNK_SIZE]; } - 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; + 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; + 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;} } +// ─── World mesh ────────────────────────────────────────────────────────────── 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}); + 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 + {0x02,0x01,0x51,0x09,0x06}, // ? + {0x32,0x49,0x79,0x41,0x3E}, // @ + {0x7E,0x11,0x11,0x11,0x7E}, // A + {0x7F,0x49,0x49,0x49,0x36}, // B + {0x3E,0x41,0x41,0x41,0x22}, // C + {0x7F,0x41,0x41,0x22,0x1C}, // D + {0x7F,0x49,0x49,0x49,0x41}, // E + {0x7F,0x09,0x09,0x09,0x01}, // F + {0x3E,0x41,0x49,0x49,0x7A}, // G + {0x7F,0x08,0x08,0x08,0x7F}, // H + {0x00,0x41,0x7F,0x41,0x00}, // I + {0x20,0x40,0x41,0x3F,0x01}, // J + {0x7F,0x08,0x14,0x22,0x41}, // K + {0x7F,0x40,0x40,0x40,0x40}, // L + {0x7F,0x02,0x04,0x02,0x7F}, // M + {0x7F,0x04,0x08,0x10,0x7F}, // N + {0x3E,0x41,0x41,0x41,0x3E}, // O + {0x7F,0x09,0x09,0x09,0x06}, // P + {0x3E,0x41,0x51,0x21,0x5E}, // Q + {0x7F,0x09,0x19,0x29,0x46}, // R + {0x46,0x49,0x49,0x49,0x31}, // S + {0x01,0x01,0x7F,0x01,0x01}, // T + {0x3F,0x40,0x40,0x40,0x3F}, // U + {0x1F,0x20,0x40,0x20,0x1F}, // V + {0x3F,0x40,0x38,0x40,0x3F}, // W + {0x63,0x14,0x08,0x14,0x63}, // X + {0x07,0x08,0x70,0x08,0x07}, // Y + {0x61,0x51,0x49,0x45,0x43}, // Z + {0x00,0x7F,0x41,0x41,0x00}, // [ + {0x02,0x04,0x08,0x10,0x20}, // backslash + {0x00,0x41,0x41,0x7F,0x00}, // ] + {0x04,0x02,0x01,0x02,0x04}, // ^ + {0x40,0x40,0x40,0x40,0x40}, // _ + {0x00,0x01,0x02,0x04,0x00}, // ` + {0x20,0x54,0x54,0x54,0x78}, // a + {0x7F,0x48,0x44,0x44,0x38}, // b + {0x38,0x44,0x44,0x44,0x20}, // c + {0x38,0x44,0x44,0x48,0x7F}, // d + {0x38,0x54,0x54,0x54,0x18}, // e + {0x08,0x7E,0x09,0x01,0x02}, // f + {0x0C,0x52,0x52,0x52,0x3E}, // g + {0x7F,0x08,0x04,0x04,0x78}, // h + {0x00,0x44,0x7D,0x40,0x00}, // i + {0x20,0x40,0x44,0x3D,0x00}, // j + {0x7F,0x10,0x28,0x44,0x00}, // k + {0x00,0x41,0x7F,0x40,0x00}, // l + {0x7C,0x04,0x18,0x04,0x78}, // m + {0x7C,0x08,0x04,0x04,0x78}, // n + {0x38,0x44,0x44,0x44,0x38}, // o + {0x7C,0x14,0x14,0x14,0x08}, // p + {0x08,0x14,0x14,0x18,0x7C}, // q + {0x7C,0x08,0x04,0x04,0x08}, // r + {0x48,0x54,0x54,0x54,0x20}, // s + {0x04,0x3F,0x44,0x40,0x20}, // t + {0x3C,0x40,0x40,0x20,0x7C}, // u + {0x1C,0x20,0x40,0x20,0x1C}, // v + {0x3C,0x40,0x30,0x40,0x3C}, // w + {0x44,0x28,0x10,0x28,0x44}, // x + {0x0C,0x50,0x50,0x50,0x3C}, // y + {0x44,0x64,0x54,0x4C,0x44}, // z + {0x00,0x08,0x36,0x41,0x00}, // { + {0x00,0x00,0x7F,0x00,0x00}, // | + {0x00,0x41,0x36,0x08,0x00}, // } + {0x10,0x08,0x08,0x10,0x08}, // ~ +}; + +static void drawText(const char* text,float x,float y,float scale, + float r,float g,float b,float a=1.0f){ + float cx=x; + for(int i=0;text[i];i++){ + int c=text[i]-32; + if(c<0||c>=(int)(sizeof(FONT5x7)/sizeof(FONT5x7[0]))){cx+=(6*scale);continue;} + for(int col=0;col<5;col++){ + uint8_t bits=FONT5x7[c][col]; + for(int row=0;row<7;row++){ + if(bits&(1<=WORLD_H) return false; - uint8_t b=getBlock(x,y,z); - return b!=AIR&&b!=WATER&&b!=LEAVES; + if(y<0) return true; if(y>=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); @@ -273,6 +555,77 @@ static void moveCamera(Camera& cam,const glm::vec3& move,float dt){ } } +// ─── Player model (3rd person) ─────────────────────────────────────────────── +struct PlayerModel { + GLuint VAO=0,VBO=0,EBO=0; + std::vector verts; + std::vector idx; + int indexCount=0; + + void addBox(float x0,float y0,float z0,float x1,float y1,float z1, + float r,float g,float b){ + auto face=[&](glm::vec3 v0,glm::vec3 v1,glm::vec3 v2,glm::vec3 v3,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}); + }; + face({x0,y1,z0},{x1,y1,z0},{x1,y1,z1},{x0,y1,z1},1.0f); // Top + face({x0,y0,z1},{x1,y0,z1},{x1,y0,z0},{x0,y0,z0},0.5f); // Bottom + face({x0,y0,z1},{x1,y0,z1},{x1,y1,z1},{x0,y1,z1},0.8f); // Front + face({x1,y0,z0},{x0,y0,z0},{x0,y1,z0},{x1,y1,z0},0.7f); // Back + face({x0,y0,z0},{x0,y0,z1},{x0,y1,z1},{x0,y1,z0},0.65f); // Left + face({x1,y0,z1},{x1,y0,z0},{x1,y1,z0},{x1,y1,z1},0.65f); // Right + } + + void build(){ + verts.clear(); idx.clear(); + float sk=0.90f,sg_=0.72f,sb_=0.55f; + float tr=0.25f,tg=0.45f,tb=0.75f; + float lr=0.20f,lg=0.20f,lb=0.60f; + float hr=0.30f,hg=0.18f,hb=0.08f; + + addBox(-0.3f,1.55f,-0.3f, 0.3f,2.15f,0.3f, sk,sg_,sb_); // Head + addBox(-0.32f,1.9f,-0.32f, 0.32f,2.17f,0.32f, hr,hg,hb); // Hair + addBox(-0.3f,0.7f,-0.2f, 0.3f,1.5f,0.2f, tr,tg,tb); // Body + addBox(-0.55f,0.7f,-0.15f, -0.32f,1.45f,0.15f, sk,sg_,sb_);// Left arm + addBox( 0.32f,0.7f,-0.15f, 0.55f,1.45f,0.15f, sk,sg_,sb_);// Right arm + addBox(-0.28f,0.0f,-0.15f, -0.05f,0.7f,0.15f, lr,lg,lb); // Left leg + addBox( 0.05f,0.0f,-0.15f, 0.28f,0.7f,0.15f, lr,lg,lb); // Right leg + + indexCount=(int)idx.size(); + + if(!VAO){glGenVertexArrays(1,&VAO);glGenBuffers(1,&VBO);glGenBuffers(1,&EBO);} + glBindVertexArray(VAO); + glBindBuffer(GL_ARRAY_BUFFER,VBO); + glBufferData(GL_ARRAY_BUFFER,verts.size()*sizeof(Vertex),verts.data(),GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,EBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER,idx.size()*sizeof(GLuint),idx.data(),GL_STATIC_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); + glBindVertexArray(0); + } + + void draw(GLuint prog,GLint mvpLoc,GLint camLoc, + const glm::mat4& proj,const glm::mat4& view, + const Camera& cam) const { + glm::vec3 feet=cam.pos-glm::vec3(0,PH,0); + glm::mat4 model=glm::translate(glm::mat4(1),feet); + model=glm::rotate(model,glm::radians(cam.yaw+180.0f),glm::vec3(0,1,0)); + glm::mat4 mvp=proj*view*model; + glUseProgram(prog); + glUniformMatrix4fv(mvpLoc,1,GL_FALSE,glm::value_ptr(mvp)); + glUniform3fv(camLoc,1,glm::value_ptr(cam.pos)); + glBindVertexArray(VAO); + glDrawElements(GL_TRIANGLES,(GLsizei)indexCount,GL_UNSIGNED_INT,0); + glBindVertexArray(0); + } +}; + +static PlayerModel PLAYER_MODEL; + +// ─── Raycast ───────────────────────────────────────────────────────────────── 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)}; @@ -287,24 +640,23 @@ static bool raycast(const Camera& cam,glm::ivec3& hit,glm::ivec3& prev){ return false; } +// ─── Chunk streaming ───────────────────────────────────────────────────────── 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); + 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; + 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,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); } +// ─── Main ──────────────────────────────────────────────────────────────────── int main(){ SDL_Init(SDL_INIT_VIDEO); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION,3); @@ -313,22 +665,25 @@ int main(){ 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_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(); - GLint mvpLoc=glGetUniformLocation(prog,"uMVP"); - GLint camLoc=glGetUniformLocation(prog,"uCamPos"); + GLuint prog3d=buildProg(VS_SRC,FS_SRC); + GLint mvpLoc=glGetUniformLocation(prog3d,"uMVP"); + GLint camLoc=glGetUniformLocation(prog3d,"uCamPos"); + UI.init(); initNoise(12345); - std::cout<<"Generating spawn chunks...\n"; + + std::cout<<"Generating world...\n"; updateChunks(0,0); - // build all initial meshes immediately for(auto& [k,c]:CHUNKS) if(c->generated) buildChunkMesh(*c); Camera cam; @@ -338,21 +693,26 @@ int main(){ } cam.pos.x=0.5f; cam.pos.z=0.5f; + PLAYER_MODEL.build(); + 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; + Inventory inv; + bool thirdPerson=false; bool running=true; Uint64 last=SDL_GetPerformanceCounter(); int lastPcx=INT_MAX,lastPcz=INT_MAX; - std::cout<<"=== MiniCraft Infinite World ===\n" + std::cout<<"=== MiniCraft ===\n" <<"WASD Move\n" <<"Space Jump\n" <<"Mouse Look\n" - <<"LMB Destroy block\n" + <<"LMB Break block\n" <<"RMB Place block\n" - <<"1-8 Grass/Dirt/Stone/Wood/Leaves/Sand/Snow/Water\n" + <<"1-8 Hotbar slot\n" + <<"E Open/close inventory\n" + <<"F5 Toggle 3rd person\n" <<"ESC Quit\n"; while(running){ @@ -366,67 +726,110 @@ int main(){ 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; + case SDLK_ESCAPE: running=false; break; + case SDLK_e: + inv.open=!inv.open; + SDL_SetRelativeMouseMode(inv.open?SDL_FALSE:SDL_TRUE); + break; + case SDLK_F5: thirdPerson=!thirdPerson; break; + case SDLK_1: inv.selected=0; break; + case SDLK_2: inv.selected=1; break; + case SDLK_3: inv.selected=2; break; + case SDLK_4: inv.selected=3; break; + case SDLK_5: inv.selected=4; break; + case SDLK_6: inv.selected=5; break; + case SDLK_7: inv.selected=6; break; + case SDLK_8: inv.selected=7; 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_MOUSEWHEEL){ + inv.selected=(inv.selected-e.wheel.y+HOTBAR_SIZE)%HOTBAR_SIZE; } - 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); + if(!inv.open){ + 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,inv.selectedBlock()); + } + } + } else { + if(e.type==SDL_MOUSEBUTTONDOWN&&e.button.button==SDL_BUTTON_LEFT){ + const int COLS=4; const float SLOT=52.0f,GAP=6.0f; + float iw=COLS*(SLOT+GAP)-GAP+32.0f; + float ih=((NUM_BLOCK_TYPES-1+COLS-1)/COLS)*(SLOT+GAP)-GAP+60.0f; + float ix=(SCREEN_W-iw)*0.5f,iy=(SCREEN_H-ih)*0.5f; + int mx=e.button.x,my=e.button.y; + for(int bi=1;bi=sx&&mx<=sx+SLOT&&my>=sy&&my<=sy+SLOT){ + inv.hotbar[inv.selected]=(uint8_t)bi; + } + } } } } - 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); + if(!inv.open){ + 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; - } - + 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;} + // ── Render 3D world ── glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); - glUseProgram(prog); - glm::mat4 mvp=proj*cam.view(); + glm::mat4 view=thirdPerson?cam.thirdPersonView():cam.firstPersonView(); + glm::mat4 mvp=proj*view; + + glUseProgram(prog3d); 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);} + // ── Player model (3rd person only) ── + if(thirdPerson) + PLAYER_MODEL.draw(prog3d,mvpLoc,camLoc,proj,view,cam); + + // ── 2D UI ── + UI.begin(); + drawHUD(cam.pos,inv.selectedBlock(),thirdPerson); + drawInventory(inv); + UI.end(); + SDL_GL_SwapWindow(win); } CHUNKS.clear(); - glDeleteProgram(prog); + glDeleteProgram(prog3d); + glDeleteProgram(UI.prog); + glDeleteVertexArrays(1,&UI.VAO); glDeleteBuffers(1,&UI.VBO); + glDeleteVertexArrays(1,&PLAYER_MODEL.VAO); + glDeleteBuffers(1,&PLAYER_MODEL.VBO); glDeleteBuffers(1,&PLAYER_MODEL.EBO); SDL_GL_DeleteContext(ctx); SDL_DestroyWindow(win); SDL_Quit(); return 0; -} +} \ No newline at end of file