From 356dfaf3a8c51897f99dd95ef3fbfd4742be1734 Mon Sep 17 00:00:00 2001 From: eldek Date: Mon, 16 Mar 2026 22:32:20 -0300 Subject: [PATCH] First working prototype --- .gitignore | 5 + CMakeLists.txt | 22 +++ cmake_install.cmake | 54 ++++++ main.cpp | 453 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 534 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 cmake_install.cmake create mode 100644 main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f2204f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +build/ +CMakeFiles/ +CMakeCache.txt +minicraft +Makefile \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..507c4e5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.16) +project(MiniCraft) + +set(CMAKE_CXX_STANDARD 17) + +find_package(OpenGL REQUIRED) +find_package(GLEW REQUIRED) +find_package(SDL2 REQUIRED) +find_package(glm REQUIRED) + +add_executable(minicraft main.cpp) + +target_include_directories(minicraft PRIVATE + ${SDL2_INCLUDE_DIRS} + ${GLEW_INCLUDE_DIRS} +) + +target_link_libraries(minicraft PRIVATE + OpenGL::GL + GLEW::GLEW + ${SDL2_LIBRARIES} +) diff --git a/cmake_install.cmake b/cmake_install.cmake new file mode 100644 index 0000000..e22ea8e --- /dev/null +++ b/cmake_install.cmake @@ -0,0 +1,54 @@ +# Install script for directory: /home/eduar/Desktop/code/C/Minecraft + +# Set the install prefix +if(NOT DEFINED CMAKE_INSTALL_PREFIX) + set(CMAKE_INSTALL_PREFIX "/usr/local") +endif() +string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") + +# Set the install configuration name. +if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME) + if(BUILD_TYPE) + string(REGEX REPLACE "^[^A-Za-z0-9_]+" "" + CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}") + else() + set(CMAKE_INSTALL_CONFIG_NAME "") + endif() + message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"") +endif() + +# Set the component getting installed. +if(NOT CMAKE_INSTALL_COMPONENT) + if(COMPONENT) + message(STATUS "Install component: \"${COMPONENT}\"") + set(CMAKE_INSTALL_COMPONENT "${COMPONENT}") + else() + set(CMAKE_INSTALL_COMPONENT) + endif() +endif() + +# Install shared libraries without execute permission? +if(NOT DEFINED CMAKE_INSTALL_SO_NO_EXE) + set(CMAKE_INSTALL_SO_NO_EXE "1") +endif() + +# Is this installation the result of a crosscompile? +if(NOT DEFINED CMAKE_CROSSCOMPILING) + set(CMAKE_CROSSCOMPILING "FALSE") +endif() + +# Set default install directory permissions. +if(NOT DEFINED CMAKE_OBJDUMP) + set(CMAKE_OBJDUMP "/usr/bin/objdump") +endif() + +if(CMAKE_INSTALL_COMPONENT) + set(CMAKE_INSTALL_MANIFEST "install_manifest_${CMAKE_INSTALL_COMPONENT}.txt") +else() + set(CMAKE_INSTALL_MANIFEST "install_manifest.txt") +endif() + +string(REPLACE ";" "\n" CMAKE_INSTALL_MANIFEST_CONTENT + "${CMAKE_INSTALL_MANIFEST_FILES}") +file(WRITE "/home/eduar/Desktop/code/C/Minecraft/${CMAKE_INSTALL_MANIFEST}" + "${CMAKE_INSTALL_MANIFEST_CONTENT}") diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..817e82e --- /dev/null +++ b/main.cpp @@ -0,0 +1,453 @@ +#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; +} \ No newline at end of file