Added inventroy, f5 and cross

This commit is contained in:
2026-03-16 23:06:48 -03:00
parent 397641dd33
commit 15f8eb4bd5

615
main.cpp
View File

@@ -13,11 +13,13 @@
#include <memory> #include <memory>
#include <algorithm> #include <algorithm>
#include <climits> #include <climits>
#include <string>
// Build: // Build:
// mkdir build && cd build && cmake .. && make -j$(nproc) // mkdir build && cd build && cmake .. && make -j$(nproc)
// ./minicraft // ./minicraft
// ─── Constants ───────────────────────────────────────────────────────────────
static const int SCREEN_W = 1280; static const int SCREEN_W = 1280;
static const int SCREEN_H = 720; static const int SCREEN_H = 720;
static const int CHUNK_SIZE = 16; 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 JUMP_VEL = 9.0f;
static const float HW = 0.3f; static const float HW = 0.3f;
static const float PH = 1.8f; 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 }; 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; }; struct BlockColor { float r,g,b; };
static const BlockColor BLOCK_COLORS[] = { static const BlockColor BLOCK_COLORS[] = {
{0,0,0}, {0,0,0},
{0.40f,0.72f,0.24f}, {0.40f,0.72f,0.24f}, // GRASS
{0.55f,0.40f,0.22f}, {0.55f,0.40f,0.22f}, // DIRT
{0.50f,0.50f,0.50f}, {0.50f,0.50f,0.50f}, // STONE
{0.45f,0.30f,0.15f}, {0.45f,0.30f,0.15f}, // WOOD
{0.22f,0.55f,0.18f}, {0.22f,0.55f,0.18f}, // LEAVES
{0.88f,0.83f,0.52f}, {0.88f,0.83f,0.52f}, // SAND
{0.92f,0.95f,0.98f}, {0.92f,0.95f,0.98f}, // SNOW
{0.18f,0.42f,0.88f}, {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}; 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 int P[512];
static void initNoise(int seed){ static void initNoise(int seed){
int perm[256]; for(int i=0;i<256;i++) perm[i]=i; 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; return val/max;
} }
// ─── World chunk ─────────────────────────────────────────────────────────────
struct Chunk { struct Chunk {
int cx,cz; int cx,cz;
uint8_t blocks[CHUNK_SIZE][WORLD_H][CHUNK_SIZE]; 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(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);}} ~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 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>()((long long)k.x<<32|(unsigned)k.z);}}; struct ChunkKeyHash{size_t operator()(const ChunkKey& k)const{return std::hash<long long>()((long long)k.x<<32|(unsigned)k.z);}};
using ChunkMap=std::unordered_map<ChunkKey,std::unique_ptr<Chunk>,ChunkKeyHash>; using ChunkMap=std::unordered_map<ChunkKey,std::unique_ptr<Chunk>,ChunkKeyHash>;
@@ -93,18 +101,14 @@ static void generateChunk(Chunk& c){
float detail=fbm(wx*2.0f+5.3f,wz*2.0f+1.7f,5); 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 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); float h=12.0f+cont*18.0f+detail*6.0f+ridge*14.0f*std::max(0.0f,cont);
int top=(int)h; int top=(int)h; top=std::max(2,std::min(WORLD_H-2,top));
top=std::max(2,std::min(WORLD_H-2,top));
bool isSand=(top<16),isSnow=(top>38); bool isSand=(top<16),isSnow=(top>38);
for(int y=0;y<WORLD_H;y++){ for(int y=0;y<WORLD_H;y++){
if(y==0) c.blocks[x][y][z]=STONE; if(y==0) c.blocks[x][y][z]=STONE;
else if(y<top-4) c.blocks[x][y][z]=STONE; else if(y<top-4) c.blocks[x][y][z]=STONE;
else if(y<top) c.blocks[x][y][z]=isSand?SAND:DIRT; else if(y<top) c.blocks[x][y][z]=isSand?SAND:DIRT;
else if(y==top){ else if(y==top){ c.blocks[x][y][z]=isSand?SAND:(isSnow?SNOW:GRASS); }
if(isSand) c.blocks[x][y][z]=SAND; else if(y<=14) c.blocks[x][y][z]=WATER;
else if(isSnow) c.blocks[x][y][z]=SNOW;
else c.blocks[x][y][z]=GRASS;
} else if(y<=14) c.blocks[x][y][z]=WATER;
else c.blocks[x][y][z]=AIR; else c.blocks[x][y][z]=AIR;
} }
if(!isSand&&!isSnow&&top>=16&&top<36&&top+6<WORLD_H){ if(!isSand&&!isSnow&&top>=16&&top<36&&top+6<WORLD_H){
@@ -112,9 +116,7 @@ static void generateChunk(Chunk& c){
if((col&0xFF)<18){ if((col&0xFF)<18){
int trunk=top+1; int trunk=top+1;
for(int t=trunk;t<trunk+4&&t<WORLD_H;t++) c.blocks[x][t][z]=WOOD; for(int t=trunk;t<trunk+4&&t<WORLD_H;t++) c.blocks[x][t][z]=WOOD;
for(int dx=-2;dx<=2;dx++) for(int dx=-2;dx<=2;dx++) for(int dz=-2;dz<=2;dz++) for(int dy=trunk+2;dy<=trunk+5;dy++){
for(int dz=-2;dz<=2;dz++)
for(int dy=trunk+2;dy<=trunk+5;dy++){
int nx=x+dx,nz=z+dz; int nx=x+dx,nz=z+dz;
if(nx>=0&&nx<CHUNK_SIZE&&nz>=0&&nz<CHUNK_SIZE&&dy<WORLD_H) if(nx>=0&&nx<CHUNK_SIZE&&nz>=0&&nz<CHUNK_SIZE&&dy<WORLD_H)
if(c.blocks[nx][dy][nz]==AIR) c.blocks[nx][dy][nz]=LEAVES; if(c.blocks[nx][dy][nz]==AIR) c.blocks[nx][dy][nz]=LEAVES;
@@ -126,59 +128,45 @@ static void generateChunk(Chunk& c){
} }
static uint8_t getBlock(int wx,int wy,int wz){ static uint8_t getBlock(int wx,int wy,int wz){
if(wy<0) return STONE; if(wy<0) return STONE; if(wy>=WORLD_H) return AIR;
if(wy>=WORLD_H) return AIR;
int cx=(int)floorf((float)wx/CHUNK_SIZE),cz=(int)floorf((float)wz/CHUNK_SIZE); int cx=(int)floorf((float)wx/CHUNK_SIZE),cz=(int)floorf((float)wz/CHUNK_SIZE);
auto it=CHUNKS.find({cx,cz}); auto it=CHUNKS.find({cx,cz}); if(it==CHUNKS.end()) return STONE;
if(it==CHUNKS.end()) return STONE; return it->second->blocks[wx-cx*CHUNK_SIZE][wy][wz-cz*CHUNK_SIZE];
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){ static void setBlock(int wx,int wy,int wz,uint8_t val){
if(wy<0||wy>=WORLD_H) return; if(wy<0||wy>=WORLD_H) return;
int cx=(int)floorf((float)wx/CHUNK_SIZE),cz=(int)floorf((float)wz/CHUNK_SIZE); int cx=(int)floorf((float)wx/CHUNK_SIZE),cz=(int)floorf((float)wz/CHUNK_SIZE);
auto it=CHUNKS.find({cx,cz}); auto it=CHUNKS.find({cx,cz}); if(it==CHUNKS.end()) return;
if(it==CHUNKS.end()) return;
int lx=wx-cx*CHUNK_SIZE,lz=wz-cz*CHUNK_SIZE; int lx=wx-cx*CHUNK_SIZE,lz=wz-cz*CHUNK_SIZE;
it->second->blocks[lx][wy][lz]=val; it->second->blocks[lx][wy][lz]=val; it->second->meshDirty=true;
it->second->meshDirty=true;
if(lx==0){auto n=CHUNKS.find({cx-1,cz});if(n!=CHUNKS.end())n->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(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==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;} 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;}; struct Vertex{float x,y,z,r,g,b;};
static bool shouldDrawFace(uint8_t cur,int wx,int wy,int wz){ static bool shouldDrawFace(uint8_t cur,int wx,int wy,int wz){
uint8_t nb=getBlock(wx,wy,wz); uint8_t nb=getBlock(wx,wy,wz);
if(nb==AIR) return true; if(nb==AIR) return true;
if(nb==WATER||nb==LEAVES) return cur!=nb; if(nb==WATER||nb==LEAVES) return cur!=nb;
return false; return false;
} }
static void addFace(std::vector<Vertex>& verts,std::vector<GLuint>& idx, static void addFace(std::vector<Vertex>& verts,std::vector<GLuint>& idx,
glm::vec3 v0,glm::vec3 v1,glm::vec3 v2,glm::vec3 v3,float r,float g,float b,float shade){ 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(); GLuint base=(GLuint)verts.size(); float sr=r*shade,sg=g*shade,sb=b*shade;
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({v0.x,v0.y,v0.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});
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}); idx.insert(idx.end(),{base,base+1,base+2,base,base+2,base+3});
} }
static void buildChunkMesh(Chunk& c){ static void buildChunkMesh(Chunk& c){
std::vector<Vertex> verts; std::vector<GLuint> idx; std::vector<Vertex> verts; std::vector<GLuint> idx;
int wx0=c.cx*CHUNK_SIZE,wz0=c.cz*CHUNK_SIZE; int wx0=c.cx*CHUNK_SIZE,wz0=c.cz*CHUNK_SIZE;
for(int x=0;x<CHUNK_SIZE;x++) for(int x=0;x<CHUNK_SIZE;x++) for(int y=0;y<WORLD_H;y++) for(int z=0;z<CHUNK_SIZE;z++){
for(int y=0;y<WORLD_H;y++)
for(int z=0;z<CHUNK_SIZE;z++){
uint8_t b=c.blocks[x][y][z]; if(b==AIR) continue; uint8_t b=c.blocks[x][y][z]; if(b==AIR) continue;
float r=BLOCK_COLORS[b].r,g=BLOCK_COLORS[b].g,bl=BLOCK_COLORS[b].b; float r=BLOCK_COLORS[b].r,g=BLOCK_COLORS[b].g,bl=BLOCK_COLORS[b].b;
float bx=wx0+x,by=y,bz=wz0+z; float bx=wx0+x,by=y,bz=wz0+z; int wx=wx0+x,wz=wz0+z;
int wx=wx0+x,wz=wz0+z;
if(shouldDrawFace(b,wx,y+1,wz)) addFace(verts,idx,{bx,by+1,bz+1},{bx+1,by+1,bz+1},{bx+1,by+1,bz},{bx,by+1,bz},r,g,bl,FACE_SHADE[0]); if(shouldDrawFace(b,wx,y+1,wz)) addFace(verts,idx,{bx,by+1,bz+1},{bx+1,by+1,bz+1},{bx+1,by+1,bz},{bx,by+1,bz},r,g,bl,FACE_SHADE[0]);
if(shouldDrawFace(b,wx,y-1,wz)) addFace(verts,idx,{bx,by,bz},{bx+1,by,bz},{bx+1,by,bz+1},{bx,by,bz+1},r,g,bl,FACE_SHADE[1]); if(shouldDrawFace(b,wx,y-1,wz)) addFace(verts,idx,{bx,by,bz},{bx+1,by,bz},{bx+1,by,bz+1},{bx,by,bz+1},r,g,bl,FACE_SHADE[1]);
if(shouldDrawFace(b,wx,y,wz+1)) addFace(verts,idx,{bx,by,bz+1},{bx+1,by,bz+1},{bx+1,by+1,bz+1},{bx,by+1,bz+1},r,g,bl,FACE_SHADE[2]); if(shouldDrawFace(b,wx,y,wz+1)) addFace(verts,idx,{bx,by,bz+1},{bx+1,by,bz+1},{bx+1,by+1,bz+1},{bx,by+1,bz+1},r,g,bl,FACE_SHADE[2]);
@@ -188,18 +176,14 @@ static void buildChunkMesh(Chunk& c){
} }
if(!c.VAO){glGenVertexArrays(1,&c.VAO);glGenBuffers(1,&c.VBO);glGenBuffers(1,&c.EBO);} if(!c.VAO){glGenVertexArrays(1,&c.VAO);glGenBuffers(1,&c.VBO);glGenBuffers(1,&c.EBO);}
glBindVertexArray(c.VAO); glBindVertexArray(c.VAO);
glBindBuffer(GL_ARRAY_BUFFER,c.VBO); glBindBuffer(GL_ARRAY_BUFFER,c.VBO); glBufferData(GL_ARRAY_BUFFER,verts.size()*sizeof(Vertex),verts.data(),GL_DYNAMIC_DRAW);
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);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,c.EBO); glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,sizeof(Vertex),(void*)0); glEnableVertexAttribArray(0);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,idx.size()*sizeof(GLuint),idx.data(),GL_DYNAMIC_DRAW); glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,sizeof(Vertex),(void*)(3*sizeof(float))); glEnableVertexAttribArray(1);
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,sizeof(Vertex),(void*)0); c.indexCount=(int)idx.size(); c.meshDirty=false;
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;
} }
// ─── Shaders ─────────────────────────────────────────────────────────────────
static const char* VS_SRC=R"( static const char* VS_SRC=R"(
#version 330 core #version 330 core
layout(location=0) in vec3 aPos; layout(location=0) in vec3 aPos;
@@ -207,46 +191,344 @@ layout(location=1) in vec3 aColor;
out vec3 vColor; out float vFog; out vec3 vColor; out float vFog;
uniform mat4 uMVP; uniform vec3 uCamPos; uniform mat4 uMVP; uniform vec3 uCamPos;
void main(){ void main(){
gl_Position=uMVP*vec4(aPos,1.0); gl_Position=uMVP*vec4(aPos,1.0); vColor=aColor;
vColor=aColor;
float dist=length(aPos-uCamPos); float dist=length(aPos-uCamPos);
vFog=clamp((dist-60.0)/40.0,0.0,1.0); vFog=clamp((dist-60.0)/40.0,0.0,1.0);
})"; })";
static const char* FS_SRC=R"( static const char* FS_SRC=R"(
#version 330 core #version 330 core
in vec3 vColor; in float vFog; in vec3 vColor; in float vFog; out vec4 fragColor;
out vec4 fragColor;
void main(){ void main(){
vec3 fogColor=vec3(0.53,0.81,0.98); vec3 fogColor=vec3(0.53,0.81,0.98);
fragColor=vec4(mix(vColor,fogColor,vFog),1.0); fragColor=vec4(mix(vColor,fogColor,vFog),1.0);
})"; })";
static const char* VS_UI=R"(
#version 330 core
layout(location=0) in vec2 aPos;
layout(location=1) in vec3 aColor;
out vec3 vColor;
uniform mat4 uProj;
void main(){ gl_Position=uProj*vec4(aPos,0.0,1.0); vColor=aColor; })";
static const char* FS_UI=R"(
#version 330 core
in vec3 vColor; out vec4 fragColor;
uniform float uAlpha;
void main(){ fragColor=vec4(vColor,uAlpha); })";
static GLuint compileShader(GLenum type,const char* src){ static GLuint compileShader(GLenum type,const char* src){
GLuint s=glCreateShader(type); glShaderSource(s,1,&src,nullptr); glCompileShader(s); GLuint s=glCreateShader(type); glShaderSource(s,1,&src,nullptr); glCompileShader(s);
GLint ok; glGetShaderiv(s,GL_COMPILE_STATUS,&ok); GLint ok; glGetShaderiv(s,GL_COMPILE_STATUS,&ok);
if(!ok){char buf[512];glGetShaderInfoLog(s,512,nullptr,buf);std::cerr<<"Shader: "<<buf<<"\n";} if(!ok){char buf[512];glGetShaderInfoLog(s,512,nullptr,buf);std::cerr<<"Shader err: "<<buf<<"\n";}
return s; return s;
} }
static GLuint buildProgram(){ static GLuint buildProg(const char* vs,const char* fs){
GLuint vs=compileShader(GL_VERTEX_SHADER,VS_SRC),fs=compileShader(GL_FRAGMENT_SHADER,FS_SRC); GLuint v=compileShader(GL_VERTEX_SHADER,vs),f=compileShader(GL_FRAGMENT_SHADER,fs);
GLuint p=glCreateProgram(); glAttachShader(p,vs); glAttachShader(p,fs); glLinkProgram(p); GLuint p=glCreateProgram(); glAttachShader(p,v); glAttachShader(p,f); glLinkProgram(p);
glDeleteShader(vs); glDeleteShader(fs); return p; glDeleteShader(v); glDeleteShader(f); return p;
} }
// ─── 2D UI Renderer ──────────────────────────────────────────────────────────
struct UIRenderer {
GLuint prog=0,VAO=0,VBO=0;
GLint projLoc=-1,alphaLoc=-1;
glm::mat4 proj{1.0f};
void init(){
prog=buildProg(VS_UI,FS_UI);
projLoc=glGetUniformLocation(prog,"uProj");
alphaLoc=glGetUniformLocation(prog,"uAlpha");
proj=glm::ortho(0.0f,(float)SCREEN_W,(float)SCREEN_H,0.0f);
glGenVertexArrays(1,&VAO);
glGenBuffers(1,&VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER,VBO);
glBufferData(GL_ARRAY_BUFFER,1024*sizeof(float),nullptr,GL_DYNAMIC_DRAW);
glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,5*sizeof(float),(void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,5*sizeof(float),(void*)(2*sizeof(float)));
glEnableVertexAttribArray(1);
glBindVertexArray(0);
}
void begin(){
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
glUseProgram(prog);
glUniformMatrix4fv(projLoc,1,GL_FALSE,glm::value_ptr(proj));
glBindVertexArray(VAO);
}
void end(){
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glBindVertexArray(0);
}
void rect(float x,float y,float w,float h,float r,float g,float b,float a=1.0f){
float verts[]={
x, y, r,g,b,
x+w, y, r,g,b,
x+w, y+h, r,g,b,
x, y, r,g,b,
x+w, y+h, r,g,b,
x, y+h, r,g,b
};
glUniform1f(alphaLoc,a);
glBindBuffer(GL_ARRAY_BUFFER,VBO);
glBufferSubData(GL_ARRAY_BUFFER,0,sizeof(verts),verts);
glDrawArrays(GL_TRIANGLES,0,6);
}
void outline(float x,float y,float w,float h,float r,float g,float b,float t=2.0f){
rect(x,y,w,t,r,g,b);
rect(x,y+h-t,w,t,r,g,b);
rect(x,y,t,h,r,g,b);
rect(x+w-t,y,t,h,r,g,b);
}
};
// ─── Global UI + PlayerModel forward declarations ────────────────────────────
// Declared here so drawText / drawHUD / drawInventory can use them.
static UIRenderer UI;
// ─── Tiny bitmap font (5×7 per glyph, ASCII 32126) ─────────────────────────
static const uint8_t FONT5x7[][5] = {
{0x00,0x00,0x00,0x00,0x00}, // space
{0x00,0x00,0x5F,0x00,0x00}, // !
{0x00,0x07,0x00,0x07,0x00}, // "
{0x14,0x7F,0x14,0x7F,0x14}, // #
{0x24,0x2A,0x7F,0x2A,0x12}, // $
{0x23,0x13,0x08,0x64,0x62}, // %
{0x36,0x49,0x55,0x22,0x50}, // &
{0x00,0x05,0x03,0x00,0x00}, // '
{0x00,0x1C,0x22,0x41,0x00}, // (
{0x00,0x41,0x22,0x1C,0x00}, // )
{0x14,0x08,0x3E,0x08,0x14}, // *
{0x08,0x08,0x3E,0x08,0x08}, // +
{0x00,0x50,0x30,0x00,0x00}, // ,
{0x08,0x08,0x08,0x08,0x08}, // -
{0x00,0x60,0x60,0x00,0x00}, // .
{0x20,0x10,0x08,0x04,0x02}, // /
{0x3E,0x51,0x49,0x45,0x3E}, // 0
{0x00,0x42,0x7F,0x40,0x00}, // 1
{0x42,0x61,0x51,0x49,0x46}, // 2
{0x21,0x41,0x45,0x4B,0x31}, // 3
{0x18,0x14,0x12,0x7F,0x10}, // 4
{0x27,0x45,0x45,0x45,0x39}, // 5
{0x3C,0x4A,0x49,0x49,0x30}, // 6
{0x01,0x71,0x09,0x05,0x03}, // 7
{0x36,0x49,0x49,0x49,0x36}, // 8
{0x06,0x49,0x49,0x29,0x1E}, // 9
{0x00,0x36,0x36,0x00,0x00}, // :
{0x00,0x56,0x36,0x00,0x00}, // ;
{0x08,0x14,0x22,0x41,0x00}, // <
{0x14,0x14,0x14,0x14,0x14}, // =
{0x00,0x41,0x22,0x14,0x08}, // >
{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<<row))
UI.rect(cx+col*scale,y+row*scale,scale,scale,r,g,b,a);
}
}
cx+=6*scale;
}
}
// ─── Inventory ───────────────────────────────────────────────────────────────
struct Inventory {
uint8_t hotbar[HOTBAR_SIZE]={GRASS,DIRT,STONE,WOOD,LEAVES,SAND,SNOW,WATER};
int selected=0;
bool open=false;
uint8_t selectedBlock()const{return hotbar[selected];}
};
static void drawInventory(Inventory& inv){
const float SLOT=52.0f;
const float GAP=6.0f;
const float BORDER=4.0f;
int n=HOTBAR_SIZE;
float totalW=n*(SLOT+GAP)-GAP;
float hx=(SCREEN_W-totalW)*0.5f;
float hy=SCREEN_H-SLOT-16.0f;
// Background bar
UI.rect(hx-8,hy-8,totalW+16,SLOT+16,0.15f,0.15f,0.15f,0.75f);
UI.outline(hx-8,hy-8,totalW+16,SLOT+16,0.0f,0.0f,0.0f);
for(int i=0;i<n;i++){
float sx=hx+i*(SLOT+GAP),sy=hy;
bool sel=(i==inv.selected);
UI.rect(sx,sy,SLOT,SLOT,sel?0.30f:0.20f,sel?0.30f:0.20f,sel?0.30f:0.20f,0.9f);
uint8_t bt=inv.hotbar[i];
if(bt!=AIR){
auto& bc=BLOCK_COLORS[bt];
UI.rect(sx+BORDER,sy+BORDER,SLOT-2*BORDER,SLOT-2*BORDER-10,bc.r,bc.g,bc.b,1.0f);
UI.rect(sx+BORDER,sy+SLOT-BORDER-10-4,SLOT-2*BORDER,4,bc.r*0.65f,bc.g*0.65f,bc.b*0.65f,1.0f);
}
if(sel) UI.outline(sx,sy,SLOT,SLOT,1.0f,1.0f,1.0f,BORDER*0.5f);
else UI.outline(sx,sy,SLOT,SLOT,0.0f,0.0f,0.0f,2.0f);
char num[3]; snprintf(num,3,"%d",i+1);
drawText(num,sx+4,sy+SLOT-12,1.5f,0.8f,0.8f,0.8f);
}
{
uint8_t bt=inv.selectedBlock();
const char* name=BLOCK_NAMES[bt];
float tw=strlen(name)*6*2.0f;
drawText(name,(SCREEN_W-tw)*0.5f,hy-26,2.0f,1.0f,1.0f,1.0f,0.9f);
}
if(!inv.open) return;
const int COLS=4;
const int ROWS=(NUM_BLOCK_TYPES-1+COLS-1)/COLS;
float iw=COLS*(SLOT+GAP)-GAP+32.0f;
float ih=ROWS*(SLOT+GAP)-GAP+60.0f;
float ix=(SCREEN_W-iw)*0.5f;
float iy=(SCREEN_H-ih)*0.5f;
UI.rect(0,0,(float)SCREEN_W,(float)SCREEN_H,0,0,0,0.45f);
UI.rect(ix,iy,iw,ih,0.18f,0.18f,0.18f,0.95f);
UI.outline(ix,iy,iw,ih,0.6f,0.6f,0.6f,2.0f);
drawText("INVENTORY",ix+10,iy+10,2.0f,1.0f,1.0f,1.0f);
for(int bi=1;bi<NUM_BLOCK_TYPES;bi++){
int row=(bi-1)/COLS,col=(bi-1)%COLS;
float sx=ix+16+col*(SLOT+GAP);
float sy=iy+40+row*(SLOT+GAP);
bool inHotbar=false;
for(int h=0;h<HOTBAR_SIZE;h++) if(inv.hotbar[h]==bi) inHotbar=true;
auto& bc=BLOCK_COLORS[bi];
UI.rect(sx,sy,SLOT,SLOT,0.25f,0.25f,0.25f,0.9f);
UI.rect(sx+BORDER,sy+BORDER,SLOT-2*BORDER,SLOT-2*BORDER-8,bc.r,bc.g,bc.b,1.0f);
UI.rect(sx+BORDER,sy+SLOT-BORDER-8-4,SLOT-2*BORDER,4,bc.r*0.65f,bc.g*0.65f,bc.b*0.65f,1.0f);
UI.outline(sx,sy,SLOT,SLOT,inHotbar?1.0f:0.4f,inHotbar?1.0f:0.4f,inHotbar?0.0f:0.4f,2.0f);
float scale=1.5f;
float tw=strlen(BLOCK_NAMES[bi])*6*scale;
float tx=sx+(SLOT-tw)*0.5f;
drawText(BLOCK_NAMES[bi],tx,sy+SLOT-10,scale,0.9f,0.9f,0.9f);
}
drawText("Click a slot (1-8) to select. E to close.",ix+10,iy+ih-18,1.5f,0.7f,0.7f,0.7f);
}
// ─── HUD ─────────────────────────────────────────────────────────────────────
static void drawHUD(const glm::vec3& pos,uint8_t /*selBlock*/,bool thirdPerson){
float cx=(float)SCREEN_W/2,cy=(float)SCREEN_H/2;
UI.rect(cx-1,cy-10,2,20,1,1,1,0.85f);
UI.rect(cx-10,cy-1,20,2,1,1,1,0.85f);
UI.rect(cx-1,cy-1,2,2,0,0,0,0.85f);
char buf[64];
snprintf(buf,64,"X:%.0f Y:%.0f Z:%.0f",pos.x,pos.y-PH,pos.z);
UI.rect(6,6,strlen(buf)*6*1.5f+6,18,0,0,0,0.5f);
drawText(buf,9,9,1.5f,1.0f,1.0f,1.0f);
if(thirdPerson)
drawText("F5: 3rd Person",9,28,1.5f,1.0f,1.0f,0.6f);
}
// ─── Camera / Player ─────────────────────────────────────────────────────────
struct Camera { struct Camera {
glm::vec3 pos{0,40,0}; float yaw=0,pitch=0; glm::vec3 pos{0,40,0};
glm::vec3 vel{0,0,0}; bool onGround=false; float yaw=0,pitch=0;
glm::vec3 forward()const{float y=glm::radians(yaw),p=glm::radians(pitch);return glm::normalize(glm::vec3(cosf(p)*cosf(y),sinf(p),cosf(p)*sinf(y)));} glm::vec3 vel{0,0,0};
bool onGround=false;
glm::vec3 forward()const{
float y=glm::radians(yaw),p=glm::radians(pitch);
return glm::normalize(glm::vec3(cosf(p)*cosf(y),sinf(p),cosf(p)*sinf(y)));
}
glm::vec3 right()const{return glm::normalize(glm::cross(forward(),{0,1,0}));} glm::vec3 right()const{return glm::normalize(glm::cross(forward(),{0,1,0}));}
glm::mat4 view()const{return glm::lookAt(pos,pos+forward(),{0,1,0});} glm::mat4 firstPersonView()const{return glm::lookAt(pos,pos+forward(),{0,1,0});}
glm::mat4 thirdPersonView()const{
glm::vec3 fwd=forward();
glm::vec3 eye=pos-fwd*6.0f+glm::vec3(0,2,0);
return glm::lookAt(eye,pos,{0,1,0});
}
}; };
static bool solidAt(float fx,float fy,float fz){ static bool solidAt(float fx,float fy,float fz){
int x=(int)floorf(fx),y=(int)floorf(fy),z=(int)floorf(fz); int x=(int)floorf(fx),y=(int)floorf(fy),z=(int)floorf(fz);
if(y<0) return true; if(y<0) return true; if(y>=WORLD_H) return false;
if(y>=WORLD_H) return false; uint8_t b=getBlock(x,y,z); return b!=AIR&&b!=WATER&&b!=LEAVES;
uint8_t b=getBlock(x,y,z);
return b!=AIR&&b!=WATER&&b!=LEAVES;
} }
static bool aabbSolid(float px,float py,float pz){ static bool aabbSolid(float px,float py,float pz){
int x0=(int)floorf(px-HW),x1=(int)floorf(px+HW); 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<Vertex> verts;
std::vector<GLuint> 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){ static bool raycast(const Camera& cam,glm::ivec3& hit,glm::ivec3& prev){
glm::vec3 dir=cam.forward(),p=cam.pos; glm::vec3 dir=cam.forward(),p=cam.pos;
glm::ivec3 last{(int)floorf(p.x),(int)floorf(p.y),(int)floorf(p.z)}; 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; return false;
} }
// ─── Chunk streaming ─────────────────────────────────────────────────────────
static void updateChunks(int pcx,int pcz){ static void updateChunks(int pcx,int pcz){
for(int dx=-RENDER_DIST;dx<=RENDER_DIST;dx++) for(int dx=-RENDER_DIST;dx<=RENDER_DIST;dx++)
for(int dz=-RENDER_DIST;dz<=RENDER_DIST;dz++){ for(int dz=-RENDER_DIST;dz<=RENDER_DIST;dz++){
ChunkKey k{pcx+dx,pcz+dz}; ChunkKey k{pcx+dx,pcz+dz};
if(CHUNKS.find(k)==CHUNKS.end()){ if(CHUNKS.find(k)==CHUNKS.end()){
auto c=std::make_unique<Chunk>(k.x,k.z); auto c=std::make_unique<Chunk>(k.x,k.z); generateChunk(*c);
generateChunk(*c);
CHUNKS[k]=std::move(c); CHUNKS[k]=std::move(c);
for(auto& [nk,nc]:CHUNKS) for(auto& [nk,nc]:CHUNKS) if(abs(nk.x-k.x)+abs(nk.z-k.z)==1) nc->meshDirty=true;
if(abs(nk.x-k.x)+abs(nk.z-k.z)==1) nc->meshDirty=true;
} }
} }
std::vector<ChunkKey> toRemove; std::vector<ChunkKey> toRemove;
for(auto& [k,c]:CHUNKS) for(auto& [k,c]:CHUNKS) if(abs(k.x-pcx)>RENDER_DIST+1||abs(k.z-pcz)>RENDER_DIST+1) toRemove.push_back(k);
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); for(auto& k:toRemove) CHUNKS.erase(k);
} }
// ─── Main ────────────────────────────────────────────────────────────────────
int main(){ int main(){
SDL_Init(SDL_INIT_VIDEO); SDL_Init(SDL_INIT_VIDEO);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION,3); 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_DOUBLEBUFFER,1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,24); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,24);
SDL_Window* win=SDL_CreateWindow("MiniCraft - Infinite World", SDL_Window* win=SDL_CreateWindow("MiniCraft",
SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED,SCREEN_W,SCREEN_H,SDL_WINDOW_OPENGL|SDL_WINDOW_SHOWN); SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED,SCREEN_W,SCREEN_H,
SDL_WINDOW_OPENGL|SDL_WINDOW_SHOWN);
SDL_GLContext ctx=SDL_GL_CreateContext(win); SDL_GLContext ctx=SDL_GL_CreateContext(win);
SDL_GL_SetSwapInterval(1); SDL_GL_SetSwapInterval(1);
glewExperimental=GL_TRUE; glewInit(); glewExperimental=GL_TRUE; glewInit();
glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); glCullFace(GL_BACK);
glClearColor(0.53f,0.81f,0.98f,1.0f); glClearColor(0.53f,0.81f,0.98f,1.0f);
GLuint prog=buildProgram(); GLuint prog3d=buildProg(VS_SRC,FS_SRC);
GLint mvpLoc=glGetUniformLocation(prog,"uMVP"); GLint mvpLoc=glGetUniformLocation(prog3d,"uMVP");
GLint camLoc=glGetUniformLocation(prog,"uCamPos"); GLint camLoc=glGetUniformLocation(prog3d,"uCamPos");
UI.init();
initNoise(12345); initNoise(12345);
std::cout<<"Generating spawn chunks...\n";
std::cout<<"Generating world...\n";
updateChunks(0,0); updateChunks(0,0);
// build all initial meshes immediately
for(auto& [k,c]:CHUNKS) if(c->generated) buildChunkMesh(*c); for(auto& [k,c]:CHUNKS) if(c->generated) buildChunkMesh(*c);
Camera cam; Camera cam;
@@ -338,21 +693,26 @@ int main(){
} }
cam.pos.x=0.5f; cam.pos.z=0.5f; 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); glm::mat4 proj=glm::perspective(glm::radians(70.0f),(float)SCREEN_W/SCREEN_H,0.05f,500.0f);
SDL_SetRelativeMouseMode(SDL_TRUE); SDL_SetRelativeMouseMode(SDL_TRUE);
uint8_t selectedBlock=GRASS; Inventory inv;
bool thirdPerson=false;
bool running=true; bool running=true;
Uint64 last=SDL_GetPerformanceCounter(); Uint64 last=SDL_GetPerformanceCounter();
int lastPcx=INT_MAX,lastPcz=INT_MAX; int lastPcx=INT_MAX,lastPcz=INT_MAX;
std::cout<<"=== MiniCraft Infinite World ===\n" std::cout<<"=== MiniCraft ===\n"
<<"WASD Move\n" <<"WASD Move\n"
<<"Space Jump\n" <<"Space Jump\n"
<<"Mouse Look\n" <<"Mouse Look\n"
<<"LMB Destroy block\n" <<"LMB Break block\n"
<<"RMB Place 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"; <<"ESC Quit\n";
while(running){ while(running){
@@ -367,16 +727,25 @@ int main(){
if(e.type==SDL_KEYDOWN){ if(e.type==SDL_KEYDOWN){
switch(e.key.keysym.sym){ switch(e.key.keysym.sym){
case SDLK_ESCAPE: running=false; break; case SDLK_ESCAPE: running=false; break;
case SDLK_1:selectedBlock=GRASS;break; case SDLK_e:
case SDLK_2:selectedBlock=DIRT;break; inv.open=!inv.open;
case SDLK_3:selectedBlock=STONE;break; SDL_SetRelativeMouseMode(inv.open?SDL_FALSE:SDL_TRUE);
case SDLK_4:selectedBlock=WOOD;break; break;
case SDLK_5:selectedBlock=LEAVES;break; case SDLK_F5: thirdPerson=!thirdPerson; break;
case SDLK_6:selectedBlock=SAND;break; case SDLK_1: inv.selected=0; break;
case SDLK_7:selectedBlock=SNOW;break; case SDLK_2: inv.selected=1; break;
case SDLK_8:selectedBlock=WATER;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_MOUSEWHEEL){
inv.selected=(inv.selected-e.wheel.y+HOTBAR_SIZE)%HOTBAR_SIZE;
}
if(!inv.open){
if(e.type==SDL_MOUSEMOTION){ if(e.type==SDL_MOUSEMOTION){
cam.yaw+=e.motion.xrel*MOUSE_SENS; cam.yaw+=e.motion.xrel*MOUSE_SENS;
cam.pitch-=e.motion.yrel*MOUSE_SENS; cam.pitch-=e.motion.yrel*MOUSE_SENS;
@@ -385,12 +754,31 @@ int main(){
if(e.type==SDL_MOUSEBUTTONDOWN){ if(e.type==SDL_MOUSEBUTTONDOWN){
glm::ivec3 hit,prev; glm::ivec3 hit,prev;
if(raycast(cam,hit,prev)){ if(raycast(cam,hit,prev)){
if(e.button.button==SDL_BUTTON_LEFT) setBlock(hit.x,hit.y,hit.z,AIR); if(e.button.button==SDL_BUTTON_LEFT)
else if(e.button.button==SDL_BUTTON_RIGHT) setBlock(prev.x,prev.y,prev.z,selectedBlock); 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<NUM_BLOCK_TYPES;bi++){
int row=(bi-1)/COLS,col=(bi-1)%COLS;
float sx=ix+16+col*(SLOT+GAP),sy=iy+40+row*(SLOT+GAP);
if(mx>=sx&&mx<=sx+SLOT&&my>=sy&&my<=sy+SLOT){
inv.hotbar[inv.selected]=(uint8_t)bi;
}
}
} }
} }
} }
if(!inv.open){
const Uint8* keys=SDL_GetKeyboardState(nullptr); 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 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 rgt=cam.right(); rgt.y=0; if(glm::length(rgt)>0) rgt=glm::normalize(rgt);
@@ -401,30 +789,45 @@ int main(){
if(keys[SDL_SCANCODE_A]) 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; if(keys[SDL_SCANCODE_SPACE]&&cam.onGround) cam.vel.y=JUMP_VEL;
moveCamera(cam,move,dt); moveCamera(cam,move,dt);
}
int pcx=(int)floorf(cam.pos.x/CHUNK_SIZE); int pcx=(int)floorf(cam.pos.x/CHUNK_SIZE);
int pcz=(int)floorf(cam.pos.z/CHUNK_SIZE); int pcz=(int)floorf(cam.pos.z/CHUNK_SIZE);
if(pcx!=lastPcx||pcz!=lastPcz){ if(pcx!=lastPcx||pcz!=lastPcz){ updateChunks(pcx,pcz); lastPcx=pcx; lastPcz=pcz; }
updateChunks(pcx,pcz); lastPcx=pcx; lastPcz=pcz;
}
int rebuilt=0; int rebuilt=0;
for(auto& [k,c]:CHUNKS) for(auto& [k,c]:CHUNKS)
if(c->meshDirty&&c->generated){buildChunkMesh(*c);if(++rebuilt>=4)break;} if(c->meshDirty&&c->generated){buildChunkMesh(*c);if(++rebuilt>=4)break;}
// ── Render 3D world ──
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glUseProgram(prog); glm::mat4 view=thirdPerson?cam.thirdPersonView():cam.firstPersonView();
glm::mat4 mvp=proj*cam.view(); glm::mat4 mvp=proj*view;
glUseProgram(prog3d);
glUniformMatrix4fv(mvpLoc,1,GL_FALSE,glm::value_ptr(mvp)); glUniformMatrix4fv(mvpLoc,1,GL_FALSE,glm::value_ptr(mvp));
glUniform3fv(camLoc,1,glm::value_ptr(cam.pos)); glUniform3fv(camLoc,1,glm::value_ptr(cam.pos));
for(auto& [k,c]:CHUNKS) for(auto& [k,c]:CHUNKS)
if(c->indexCount>0){glBindVertexArray(c->VAO);glDrawElements(GL_TRIANGLES,c->indexCount,GL_UNSIGNED_INT,0);} 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); SDL_GL_SwapWindow(win);
} }
CHUNKS.clear(); 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_GL_DeleteContext(ctx);
SDL_DestroyWindow(win); SDL_DestroyWindow(win);
SDL_Quit(); SDL_Quit();