diff --git a/cpp-voxel-engine/.vscode/settings.json b/cpp-voxel-engine/.vscode/settings.json new file mode 100644 index 0000000..c56e9f1 --- /dev/null +++ b/cpp-voxel-engine/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "files.associations": { + "*.pyx": "python", + "*.js": "javascript", + "*.c": "c", + "*.scene": "yaml", + "vector": "cpp" + } +} \ No newline at end of file diff --git a/cpp-voxel-engine/GreedyMesher.cpp b/cpp-voxel-engine/GreedyMesher.cpp index 49af5a7..24b6ec4 100644 --- a/cpp-voxel-engine/GreedyMesher.cpp +++ b/cpp-voxel-engine/GreedyMesher.cpp @@ -1,27 +1,117 @@ #include "GreedyMesher.h" +#include -std::vector GreedyMesher::mesh(const std::vector>>& voxelData) { +// Helper to index into voxels with bounds checking +inline int voxelAt(const std::vector>>& v, int x, int y, int z) { + if (x<0||y<0||z<0) return 0; + if (x>= (int)v.size() || y>= (int)v[0].size() || z>= (int)v[0][0].size()) return 0; + return v[x][y][z]; +} + +std::vector GreedyMesher::mesh(const std::vector>>& voxels) { std::vector quads; + int sizeX = voxels.size(); + int sizeY = voxels[0].size(); + int sizeZ = voxels[0][0].size(); - int sizeX = voxelData.size(); - if (sizeX == 0) return quads; - int sizeY = voxelData[0].size(); - int sizeZ = voxelData[0][0].size(); - - // For simplicity, we create one quad per non-empty voxel. - // A complete greedy mesher would merge adjacent quads. - for (int x = 0; x < sizeX; ++x) { - for (int y = 0; y < sizeY; ++y) { - for (int z = 0; z < sizeZ; ++z) { - if (voxelData[x][y][z] != 0) { - Quad q; - q.x = (float)x; - q.y = (float)y; - q.z = (float)z; - q.width = 1.0f; - q.height = 1.0f; - q.textureID = voxelData[x][y][z]; - quads.push_back(q); + // directions: {dx,dy,dz}, and their “du” and “dv” axes + static const int dirs[6][3] = { + {-1,0,0},{1,0,0},{0,-1,0},{0,1,0},{0,0,-1},{0,0,1} + }; + static const int axisU[6] = {2,2,0,0,0,0}; // u-axis index per face + static const int axisV[6] = {1,1,1,1,2,2}; // v-axis index per face + + // For each face direction + for (int d = 0; d < 6; d++) { + int dx = dirs[d][0], dy = dirs[d][1], dz = dirs[d][2]; + int u = axisU[d], v = axisV[d]; + + // dims: dimensions along u,v, and w (the face-normal axis) + int dimU = (u==0?sizeX:(u==1?sizeY:sizeZ)); + int dimV = (v==0?sizeX:(v==1?sizeY:sizeZ)); + int dimW = (dx!=0?sizeX:(dy!=0?sizeY:sizeZ)); + + // Allocate mask[dimU][dimV] + std::vector mask(dimU * dimV, 0); + + // Sweep along the w axis + for (int w = 0; w <= dimW; w++) { + // build mask + for (int i = 0; i < dimU; i++) { + for (int j = 0; j < dimV; j++) { + int x,y,z; + // map (i,j,w) to (x,y,z) + int coord[3]; + coord[u] = i; + coord[v] = j; + coord[ (dx!=0?0:(dy!=0?1:2)) ] = w; + x = coord[0]; y = coord[1]; z = coord[2]; + int a = voxelAt(voxels, x, y, z); + coord[ (dx!=0?0:(dy!=0?1:2)) ] = w-1; + int b = voxelAt(voxels, coord[0], coord[1], coord[2]); + // if face between a and b should be drawn + if ((a!=0) != (b!=0)) { + // a is solid, b is empty → draw face on b side + mask[i + j*dimU] = (a!=0 ? a : -b); + } else { + mask[i + j*dimU] = 0; + } + } + } + // Greedy merge mask + for (int j = 0; j < dimV; j++) { + for (int i = 0; i < dimU; ) { + int c = mask[i + j*dimU]; + if (c != 0) { + // determine width + int wdt = 1; + while (i+wdt < dimU && mask[i+wdt + j*dimU] == c) wdt++; + // determine height + int hgt = 1; + bool done = false; + while (j+hgt < dimV && !done) { + for (int k = 0; k < wdt; k++) { + if (mask[i+k + (j+hgt)*dimU] != c) { + done = true; + break; + } + } + if (!done) hgt++; + } + // emit quad + Quad q; + // base position + float pos[3] = {0,0,0}; + pos[0] = pos[1] = pos[2] = 0; + // set w axis coordinate + pos[(dx!=0?0:(dy!=0?1:2))] = w; + // but if this is a “back” face (a was empty, b solid), shift pos back + if (c < 0) pos[(dx!=0?0:(dy!=0?1:2))] = w-1; + // then set u,v + pos[u] = i; + pos[v] = j; + q.x = pos[0]; + q.y = pos[1]; + q.z = pos[2]; + // set du and dv vectors + q.du[0] = q.du[1] = q.du[2] = 0; + q.dv[0] = q.dv[1] = q.dv[2] = 0; + q.du[u] = wdt; + q.dv[v] = hgt; + // normal + q.normal = d; + // textureID + q.textureID = abs(c); + quads.push_back(q); + // zero out mask + for (int jj = 0; jj < hgt; jj++) + for (int ii = 0; ii < wdt; ii++) + mask[i+ii + (j+jj)*dimU] = 0; + // advance + i += wdt; + } else { + i++; + } } } } diff --git a/cpp-voxel-engine/GreedyMesher.h b/cpp-voxel-engine/GreedyMesher.h index c1baa5a..6c7f7f5 100644 --- a/cpp-voxel-engine/GreedyMesher.h +++ b/cpp-voxel-engine/GreedyMesher.h @@ -3,21 +3,25 @@ #include -// Simple structure to hold a quad (a single face of a voxel block) +// A single quad face in world space. struct Quad { - // Starting position of the quad in world space + // Position of the minimal corner of the quad float x, y, z; - // Dimensions of the quad (for this demo we assume unit quads) - float width, height; - // Texture ID (1 = grass, 2 = dirt, 3 = wood) + // Dimensions of the quad along its two axes + float du[3], dv[3]; + // Offset of the second corner: (x+du[0]+dv[0], y+du[1]+dv[1], z+du[2]+dv[2]) + // Normal direction (0..5) indicates which face this is: + // 0 = -X, 1 = +X, 2 = -Y, 3 = +Y, 4 = -Z, 5 = +Z + int normal; + // Texture ID for this face (block type) int textureID; }; -class GreedyMesher { -public: - // Very basic greedy meshing algorithm: - // For demonstration, each non-zero voxel produces a quad. - static std::vector mesh(const std::vector>>& voxelData); -}; +// GreedyMesher namespace +namespace GreedyMesher { + // Given a 3D voxel grid [x][y][z] with integer block IDs (0 = empty), + // returns a list of merged Quad faces. + std::vector mesh(const std::vector>>& voxels); +} #endif // GREEDYMESHER_H diff --git a/cpp-voxel-engine/GreedyMesher.o b/cpp-voxel-engine/GreedyMesher.o new file mode 100644 index 0000000..d70ba69 Binary files /dev/null and b/cpp-voxel-engine/GreedyMesher.o differ diff --git a/cpp-voxel-engine/Makefile b/cpp-voxel-engine/Makefile index 61e62d9..00b0547 100644 --- a/cpp-voxel-engine/Makefile +++ b/cpp-voxel-engine/Makefile @@ -1,7 +1,7 @@ # Compiler settings CXX = g++ -CXXFLAGS = -std=c++11 -I. -Iimgui-docking -IC:/msys64/mingw64/include -LDFLAGS = -LC:/msys64/mingw64/lib -lglfw3 -lopengl32 -lgdi32 +CXXFLAGS = -std=c++20 -I. -Iimgui-docking -IC:/msys64/mingw64/include -DGLEW_STATIC -g +LDFLAGS = -LC:/msys64/mingw64/lib -lglfw3 -lopengl32 -lgdi32 -lglew32 -lglu32 # List ImGui source files IMGUISRCS = imgui-docking/imgui.cpp \ diff --git a/cpp-voxel-engine/VoxelGame.cpp b/cpp-voxel-engine/VoxelGame.cpp index 3cd942a..03ae775 100644 --- a/cpp-voxel-engine/VoxelGame.cpp +++ b/cpp-voxel-engine/VoxelGame.cpp @@ -1,16 +1,81 @@ +#define STB_IMAGE_IMPLEMENTATION #include "VoxelGame.h" #include #include - -// Include stb_image implementation (ensure stb_image.h is in your include path) -#define STB_IMAGE_IMPLEMENTATION +#include #include "stb_image.h" +#include +#include +#include "imgui.h" +#include "GreedyMesher.h" + +namespace { + // --- Perlin Noise Helpers --- + float fade_(float t) { + return t * t * t * (t * (t * 6 - 15) + 10); + } + float lerp_(float a, float b, float t) { + return a + t * (b - a); + } + float grad_(int hash, float x, float y) { + int h = hash & 7; + float u = h < 4 ? x : y; + float v = h < 4 ? y : x; + return ((h & 1) ? -u : u) + ((h & 2) ? -v : v); + } + float perlin(float x, float y) { + static int p[512]; + static bool init = false; + if (!init) { + int perm[256] = { + 151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225, + 140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148, + 247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32, + 57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175, + 74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122, + 60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54, + 65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169, + 200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64, + 52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212, + 207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213, + 119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9, + 129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104, + 218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241, + 81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157, + 184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93, + 222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 + }; + for (int i = 0; i < 256; i++) { + p[i] = perm[i]; + p[i + 256] = perm[i]; + } + init = true; + } + int X = int(floor(x)) & 255; + int Y = int(floor(y)) & 255; + x -= floor(x); + y -= floor(y); + float u = fade_(x); + float v = fade_(y); + int A = p[X] + Y, B = p[X + 1] + Y; + return lerp_( v, + lerp_( u, grad_(p[A], x, y), grad_(p[A+1], x-1, y) ), + lerp_( u, grad_(p[B], x, y-1), grad_(p[B+1], x-1, y-1) ) + ); + } +} + +// Helper to safely query voxel (returns 0 if out of bounds) +inline int voxelAt(const Chunk& c, int x, int y, int z) { + if (x<0||y<0||z<0||x>=16||y>=16||z>=16) return 0; + return c.voxels[x][y][z]; +} + VoxelGame::VoxelGame() - : textureGrass(0), textureDirt(0), textureWood(0) + : textureGrass(0), textureDirt(0), textureWood(0), + totalChunksLoaded(0), totalChunksEverLoaded(0) { - // Initialize a 16x16x16 voxel grid with zeros - voxelData.resize(16, std::vector>(16, std::vector(16, 0))); } VoxelGame::~VoxelGame() { @@ -19,112 +84,125 @@ VoxelGame::~VoxelGame() { glDeleteTextures(1, &textureWood); } + bool VoxelGame::init() { - if (!loadTextures()) - return false; - generateVoxelData(); - generateMesh(); + if (!loadTextures()) return false; + // Initial camera position + float cam[3] = {32,32,80}; + updateChunks(cam); + // Build mesh for each chunk + for (auto &p : chunks) { + p.second.mesh = GreedyMesher::mesh(p.second.voxels); + } + glEnable(GL_DEPTH_TEST); + glEnable(GL_TEXTURE_2D); return true; } bool VoxelGame::loadTextures() { - int width, height, nrChannels; - unsigned char *data; - - // Load grass texture - data = stbi_load("grass.jpg", &width, &height, &nrChannels, 0); - if (data) { - glGenTextures(1, &textureGrass); - glBindTexture(GL_TEXTURE_2D, textureGrass); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, - nrChannels == 4 ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, data); - glGenerateMipmap(GL_TEXTURE_2D); - stbi_image_free(data); - } else { - std::cerr << "Failed to load grass texture\n"; - return false; - } - - // Load dirt texture - data = stbi_load("dirt.jpg", &width, &height, &nrChannels, 0); - if (data) { - glGenTextures(1, &textureDirt); - glBindTexture(GL_TEXTURE_2D, textureDirt); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, - nrChannels == 4 ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, data); - glGenerateMipmap(GL_TEXTURE_2D); - stbi_image_free(data); - } else { - std::cerr << "Failed to load dirt texture\n"; - return false; - } - - // Load wood texture - data = stbi_load("wood.png", &width, &height, &nrChannels, 0); - if (data) { - glGenTextures(1, &textureWood); - glBindTexture(GL_TEXTURE_2D, textureWood); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, - nrChannels == 4 ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, data); - glGenerateMipmap(GL_TEXTURE_2D); - stbi_image_free(data); - } else { - std::cerr << "Failed to load wood texture\n"; - return false; - } + int w,h,n; + unsigned char* data; + data = stbi_load("grass.jpg",&w,&h,&n,4); + if (!data) { std::cerr<<"grass.jpg load fail\n"; return false; } + glGenTextures(1,&textureGrass); glBindTexture(GL_TEXTURE_2D,textureGrass); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,w,h,0,GL_RGBA,GL_UNSIGNED_BYTE,data); + glGenerateMipmap(GL_TEXTURE_2D); + stbi_image_free(data); + data = stbi_load("dirt.jpg",&w,&h,&n,4); + if (!data) { std::cerr<<"dirt.jpg load fail\n"; return false; } + glGenTextures(1,&textureDirt); glBindTexture(GL_TEXTURE_2D,textureDirt); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,w,h,0,GL_RGBA,GL_UNSIGNED_BYTE,data); + glGenerateMipmap(GL_TEXTURE_2D); + stbi_image_free(data); + data = stbi_load("wood.png",&w,&h,&n,4); + if (!data) { std::cerr<<"wood.png load fail\n"; return false; } + glGenTextures(1,&textureWood); glBindTexture(GL_TEXTURE_2D,textureWood); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,w,h,0,GL_RGBA,GL_UNSIGNED_BYTE,data); + glGenerateMipmap(GL_TEXTURE_2D); + stbi_image_free(data); return true; } -void VoxelGame::generateVoxelData() { - // For demo purposes: - // - Fill the bottom layer with grass (value 1) - // - Fill the next layer with dirt (value 2) - // - Add a couple of wood blocks (value 3) - for (int x = 0; x < 16; ++x) { - for (int z = 0; z < 16; ++z) { - voxelData[x][0][z] = 1; // grass layer - voxelData[x][1][z] = 2; // dirt layer +Chunk VoxelGame::generateChunk(int cx,int cz) { + Chunk c; c.x=cx; c.z=cz; + c.voxels.assign(16, std::vector>(16, std::vector(16,0))); + float scale=0.1f; + for(int x=0;x<16;x++)for(int z=0;z<16;z++){ + float wx = cx*16 + x, wz = cz*16 + z; + float n = perlin(wx*scale, wz*scale); + int h = int(((n+1)*0.5f)*16); + h = std::clamp(h,0,16); + for(int y=0;y keep; + for(int dx=-r;dx<=r;dx++)for(int dz=-r;dz<=r;dz++){ + ChunkKey key{cx+dx,cz+dz}; + keep.insert(key); + if(chunks.find(key)==chunks.end()){ + Chunk nc = generateChunk(key.x,key.z); + nc.mesh = GreedyMesher::mesh(nc.voxels); + chunks[key]=std::move(nc); + totalChunksEverLoaded++; + } + } + for(auto it=chunks.begin();it!=chunks.end();){ + if(!keep.count(it->first)) it=chunks.erase(it); + else ++it; + } + totalChunksLoaded = chunks.size(); } -void VoxelGame::update(float deltaTime) { - // Update game logic (animations, input, physics, etc.) - // (This is left minimal for demo purposes) +void VoxelGame::update(float dt,const float cam[3]){ + updateChunks(cam); } -void VoxelGame::render() { - // Here you would typically bind a shader, set uniforms, bind textures, - // and render the mesh (e.g. drawing quads as two triangles each). - // For this demo, we simply output the number of quads. - std::cout << "Rendering " << meshQuads.size() << " quads." << std::endl; - // You can extend this function to perform real OpenGL drawing. +void VoxelGame::render(){ + for(auto &p:chunks){ + float ox = p.first.x*16, oz = p.first.z*16; + for(auto &q:p.second.mesh){ + // shift by chunk offset + float v0[3]={q.x+ox,q.y,q.z+oz}, + v1[3]={q.x+q.du[0]+ox,q.y+q.du[1],q.z+q.du[2]+oz}, + v2[3]={q.x+q.du[0]+q.dv[0]+ox,q.y+q.du[1]+q.dv[1],q.z+q.du[2]+q.dv[2]+oz}, + v3[3]={q.x+q.dv[0]+ox,q.y+q.dv[1],q.z+q.dv[2]+oz}; + unsigned int tex = (q.textureID==1?textureGrass:(q.textureID==2?textureDirt:textureWood)); + glBindTexture(GL_TEXTURE_2D,tex); + glBegin(GL_QUADS); + glTexCoord2f(0,0); glVertex3fv(v0); + glTexCoord2f(1,0); glVertex3fv(v1); + glTexCoord2f(1,1); glVertex3fv(v2); + glTexCoord2f(0,1); glVertex3fv(v3); + glEnd(); + } + } } -void VoxelGame::debugUI() { - +void VoxelGame::debugUI(){ ImGui::Begin("Debug"); - ImGui::Text("Voxel Game Debug Window"); - ImGui::Text("Mesh quads count: %d", (int)meshQuads.size()); + ImGui::Text("Chunks Loaded: %d", totalChunksLoaded); + ImGui::Text("Chunks Ever Loaded: %d", totalChunksEverLoaded); + static bool wf=false; + if(ImGui::Checkbox("Wireframe",&wf)) + glPolygonMode(GL_FRONT_AND_BACK, wf?GL_LINE:GL_FILL); ImGui::End(); } diff --git a/cpp-voxel-engine/VoxelGame.h b/cpp-voxel-engine/VoxelGame.h index f0de537..de42dd6 100644 --- a/cpp-voxel-engine/VoxelGame.h +++ b/cpp-voxel-engine/VoxelGame.h @@ -2,40 +2,57 @@ #define VOXELGAME_H #include + +#include +#include #include "GreedyMesher.h" +// Structure representing a chunk (16×16×16 voxels) +struct Chunk { + int x, z; // Chunk grid coordinates + // 3D voxel data: dimensions [16][16][16] + std::vector>> voxels; + // **Merged mesh faces** produced by the greedy mesher + std::vector mesh; +}; -#include "imgui.h" -#include "imgui_impl_glfw.h" -#include "imgui_impl_opengl3.h" +// A key for identifying a chunk by its grid coordinates. +struct ChunkKey { + int x, z; + bool operator==(const ChunkKey &other) const { + return x == other.x && z == other.z; + } +}; + +struct ChunkKeyHash { + std::size_t operator()(const ChunkKey &key) const { + return std::hash()(key.x) ^ (std::hash()(key.z) << 1); + } +}; class VoxelGame { public: VoxelGame(); ~VoxelGame(); - - // Initialize textures, voxel data, and mesher + bool init(); - - // Game loop functions - void update(float deltaTime); + void update(float deltaTime, const float cameraPos[3]); void render(); void debugUI(); - + private: - // 3D voxel data (0 = empty, 1 = grass, 2 = dirt, 3 = wood) - std::vector>> voxelData; - // Mesh quads produced by the greedy mesher - std::vector meshQuads; - - // OpenGL texture IDs for each block type + bool loadTextures(); + Chunk generateChunk(int cx, int cz); + void updateChunks(const float cameraPos[3]); + void drawCube(float x, float y, float z, int voxelType); + + std::unordered_map chunks; + int totalChunksLoaded; + int totalChunksEverLoaded; + unsigned int textureGrass; unsigned int textureDirt; unsigned int textureWood; - - bool loadTextures(); - void generateVoxelData(); - void generateMesh(); }; #endif // VOXELGAME_H diff --git a/cpp-voxel-engine/VoxelGame.o b/cpp-voxel-engine/VoxelGame.o new file mode 100644 index 0000000..e5c5726 Binary files /dev/null and b/cpp-voxel-engine/VoxelGame.o differ diff --git a/cpp-voxel-engine/imgui-docking/imgui.o b/cpp-voxel-engine/imgui-docking/imgui.o new file mode 100644 index 0000000..19e1bd0 Binary files /dev/null and b/cpp-voxel-engine/imgui-docking/imgui.o differ diff --git a/cpp-voxel-engine/imgui-docking/imgui_demo.o b/cpp-voxel-engine/imgui-docking/imgui_demo.o new file mode 100644 index 0000000..52b8593 Binary files /dev/null and b/cpp-voxel-engine/imgui-docking/imgui_demo.o differ diff --git a/cpp-voxel-engine/imgui-docking/imgui_draw.o b/cpp-voxel-engine/imgui-docking/imgui_draw.o new file mode 100644 index 0000000..d293012 Binary files /dev/null and b/cpp-voxel-engine/imgui-docking/imgui_draw.o differ diff --git a/cpp-voxel-engine/imgui-docking/imgui_impl_glfw.o b/cpp-voxel-engine/imgui-docking/imgui_impl_glfw.o new file mode 100644 index 0000000..f6c385b Binary files /dev/null and b/cpp-voxel-engine/imgui-docking/imgui_impl_glfw.o differ diff --git a/cpp-voxel-engine/imgui-docking/imgui_impl_opengl3.o b/cpp-voxel-engine/imgui-docking/imgui_impl_opengl3.o new file mode 100644 index 0000000..eeb46c8 Binary files /dev/null and b/cpp-voxel-engine/imgui-docking/imgui_impl_opengl3.o differ diff --git a/cpp-voxel-engine/imgui-docking/imgui_tables.o b/cpp-voxel-engine/imgui-docking/imgui_tables.o new file mode 100644 index 0000000..cc408d3 Binary files /dev/null and b/cpp-voxel-engine/imgui-docking/imgui_tables.o differ diff --git a/cpp-voxel-engine/imgui-docking/imgui_widgets.o b/cpp-voxel-engine/imgui-docking/imgui_widgets.o new file mode 100644 index 0000000..15be105 Binary files /dev/null and b/cpp-voxel-engine/imgui-docking/imgui_widgets.o differ diff --git a/cpp-voxel-engine/imgui.ini b/cpp-voxel-engine/imgui.ini new file mode 100644 index 0000000..af1c9ee --- /dev/null +++ b/cpp-voxel-engine/imgui.ini @@ -0,0 +1,10 @@ +[Window][Debug##Default] +Pos=60,60 +Size=400,400 +Collapsed=0 + +[Window][Debug] +Pos=492,48 +Size=214,119 +Collapsed=0 + diff --git a/cpp-voxel-engine/main.cpp b/cpp-voxel-engine/main.cpp index bb16b84..26341c7 100644 --- a/cpp-voxel-engine/main.cpp +++ b/cpp-voxel-engine/main.cpp @@ -1,24 +1,67 @@ #include +#include #include #include -#include "VoxelGame.h" - -// ImGui headers (make sure these files are available in your include path) +#include #include "imgui.h" +#include "imgui_internal.h" // For Im_PI #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" +#include "VoxelGame.h" + +// Global camera variables. +float yaw = -90.0f; +float pitch = 0.0f; +float lastX = 400.0f, lastY = 300.0f; +bool firstMouse = true; +float cameraPos[3] = {32.0f, 32.0f, 80.0f}; +float cameraFront[3] = {0.0f, 0.0f, -1.0f}; +float cameraUp[3] = {0.0f, 1.0f, 0.0f}; + +void mouse_callback(GLFWwindow* window, double xpos, double ypos) { + if (glfwGetInputMode(window, GLFW_CURSOR) == GLFW_CURSOR_NORMAL) + return; + if (firstMouse) { + lastX = static_cast(xpos); + lastY = static_cast(ypos); + firstMouse = false; + } + float xoffset = static_cast(xpos) - lastX; + float yoffset = lastY - static_cast(ypos); + lastX = static_cast(xpos); + lastY = static_cast(ypos); + + float sensitivity = 0.1f; + xoffset *= sensitivity; + yoffset *= sensitivity; + + yaw += xoffset; + pitch += yoffset; + if (pitch > 89.0f) pitch = 89.0f; + if (pitch < -89.0f) pitch = -89.0f; + + float radYaw = yaw * static_cast(IM_PI) / 180.0f; + float radPitch = pitch * static_cast(IM_PI) / 180.0f; + + cameraFront[0] = cos(radYaw) * cos(radPitch); + cameraFront[1] = sin(radPitch); + cameraFront[2] = sin(radYaw) * cos(radPitch); + float len = std::sqrt(cameraFront[0]*cameraFront[0] + + cameraFront[1]*cameraFront[1] + + cameraFront[2]*cameraFront[2]); + cameraFront[0] /= len; + cameraFront[1] /= len; + cameraFront[2] /= len; +} -// Initialize GLFW and create a window GLFWwindow* initWindow(int width, int height, const char* title) { if (!glfwInit()) { std::cerr << "Failed to initialize GLFW\n"; return nullptr; } - // Request OpenGL 3.3 Core Profile glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE); GLFWwindow* window = glfwCreateWindow(width, height, title, nullptr, nullptr); if (!window) { std::cerr << "Failed to create GLFW window\n"; @@ -26,8 +69,14 @@ GLFWwindow* initWindow(int width, int height, const char* title) { return nullptr; } glfwMakeContextCurrent(window); - + glfwSetCursorPosCallback(window, mouse_callback); + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + glewExperimental = GL_TRUE; + if (glewInit() != GLEW_OK) { + std::cerr << "GLEW Error\n"; + return nullptr; + } return window; } @@ -35,59 +84,110 @@ int main() { GLFWwindow* window = initWindow(800, 600, "Voxel Game"); if (!window) return -1; - - // Setup ImGui context + IMGUI_CHECKVERSION(); ImGui::CreateContext(); - ImGuiIO &io = ImGui::GetIO(); - (void)io; + ImGuiIO &io = ImGui::GetIO(); (void)io; ImGui::StyleColorsDark(); ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplOpenGL3_Init("#version 330"); - - // Create game instance and initialize it + VoxelGame game; if (!game.init()) { std::cerr << "Failed to initialize game\n"; return -1; } - - // Main loop + + float deltaTime = 0.0f, lastFrame = 0.0f; + static bool cursorEnabled = false; + static bool f1Pressed = false; + while (!glfwWindowShouldClose(window)) { + float currentFrame = static_cast(glfwGetTime()); + deltaTime = currentFrame - lastFrame; + lastFrame = currentFrame; glfwPollEvents(); - - // Update game logic (assume fixed deltaTime for demo) - game.update(0.016f); - - // Start new ImGui frame + + // Toggle cursor lock with F1 (if ImGui isn’t capturing the mouse). + if (glfwGetKey(window, GLFW_KEY_F1) == GLFW_PRESS && !ImGui::GetIO().WantCaptureMouse) { + if (!f1Pressed) { + cursorEnabled = !cursorEnabled; + glfwSetInputMode(window, GLFW_CURSOR, cursorEnabled ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED); + f1Pressed = true; + firstMouse = true; + } + } else if (glfwGetKey(window, GLFW_KEY_F1) == GLFW_RELEASE) { + f1Pressed = false; + } + + float cameraSpeed = 5.0f * deltaTime; + if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) { + cameraPos[0] += cameraFront[0] * cameraSpeed; + cameraPos[1] += cameraFront[1] * cameraSpeed; + cameraPos[2] += cameraFront[2] * cameraSpeed; + } + if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) { + cameraPos[0] -= cameraFront[0] * cameraSpeed; + cameraPos[1] -= cameraFront[1] * cameraSpeed; + cameraPos[2] -= cameraFront[2] * cameraSpeed; + } + if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) { + float right[3] = { + cameraFront[1]*cameraUp[2] - cameraFront[2]*cameraUp[1], + cameraFront[2]*cameraUp[0] - cameraFront[0]*cameraUp[2], + cameraFront[0]*cameraUp[1] - cameraFront[1]*cameraUp[0] + }; + float rLen = std::sqrt(right[0]*right[0] + right[1]*right[1] + right[2]*right[2]); + right[0] /= rLen; right[1] /= rLen; right[2] /= rLen; + cameraPos[0] -= right[0] * cameraSpeed; + cameraPos[1] -= right[1] * cameraSpeed; + cameraPos[2] -= right[2] * cameraSpeed; + } + if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) { + float right[3] = { + cameraFront[1]*cameraUp[2] - cameraFront[2]*cameraUp[1], + cameraFront[2]*cameraUp[0] - cameraFront[0]*cameraUp[2], + cameraFront[0]*cameraUp[1] - cameraFront[1]*cameraUp[0] + }; + float rLen = std::sqrt(right[0]*right[0] + right[1]*right[1] + right[2]*right[2]); + right[0] /= rLen; right[1] /= rLen; right[2] /= rLen; + cameraPos[0] += right[0] * cameraSpeed; + cameraPos[1] += right[1] * cameraSpeed; + cameraPos[2] += right[2] * cameraSpeed; + } + ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); - - // Draw debug UI from game - game.debugUI(); - - ImGui::Render(); - - // Clear the screen - glClearColor(0.2f, 0.3f, 0.3f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - - // Render the voxel game (mesh drawing, etc.) + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(45.0, 800.0/600.0, 0.1, 200.0); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + float centerX = cameraPos[0] + cameraFront[0]; + float centerY = cameraPos[1] + cameraFront[1]; + float centerZ = cameraPos[2] + cameraFront[2]; + gluLookAt(cameraPos[0], cameraPos[1], cameraPos[2], + centerX, centerY, centerZ, + cameraUp[0], cameraUp[1], cameraUp[2]); + + game.update(deltaTime, cameraPos); game.render(); - - // Render ImGui on top + game.debugUI(); + + ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - glfwSwapBuffers(window); } - - // Cleanup ImGui and GLFW + ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); glfwDestroyWindow(window); glfwTerminate(); - return 0; } diff --git a/cpp-voxel-engine/main.o b/cpp-voxel-engine/main.o new file mode 100644 index 0000000..e9d5dfd Binary files /dev/null and b/cpp-voxel-engine/main.o differ diff --git a/cpp-voxel-engine/voxelgame.exe b/cpp-voxel-engine/voxelgame.exe new file mode 100644 index 0000000..5f40676 Binary files /dev/null and b/cpp-voxel-engine/voxelgame.exe differ