#include #include #include #include #include #include #include #include #include #include #include #include // Instalation // On root: // mkdir 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 float MOUSE_SENS = 0.12f; static const float GRAVITY = -20.0f; static const float JUMP_VEL = 8.0f; // ─── Block types ───────────────────────────────────────────────────────────── enum BlockType : uint8_t { AIR=0, GRASS, DIRT, STONE, WOOD, LEAVES, SAND, 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 }; // 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; } static void generateWorld() { memset(WORLD, AIR, sizeof(WORLD)); for (int x = 0; x < CHUNK_SIZE; ++x) for (int z = 0; z < CHUNK_SIZE; ++z) { int top = terrainHeight(x, z); if (top < 1) top = 1; if (top >= 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; } // Small trees if (top > 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=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 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 buildMesh(std::vector& verts, std::vector& idx) { verts.clear(); idx.clear(); 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); 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); glm::mat4 proj = glm::perspective(glm::radians(70.0f),(float)SCREEN_W/SCREEN_H,0.05f,300.0f); GLint mvpLoc = glGetUniformLocation(prog,"uMVP"); SDL_SetRelativeMouseMode(SDL_TRUE); Camera cam; bool running = true; Uint64 last = SDL_GetPerformanceCounter(); uint8_t selectedBlock = GRASS; bool meshDirty = false; 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"; 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; } 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_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); // Render 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)); glBindVertexArray(VAO); glDrawElements(GL_TRIANGLES,(GLsizei)idx.size(),GL_UNSIGNED_INT,0); SDL_GL_SwapWindow(win); } glDeleteVertexArrays(1,&VAO); glDeleteBuffers(1,&VBO); glDeleteBuffers(1,&EBO); glDeleteProgram(prog); SDL_GL_DeleteContext(ctx); SDL_DestroyWindow(win); SDL_Quit(); return 0; }