Broken Greedy Mesher

This commit is contained in:
OusmBlueNinja 2025-04-05 22:14:06 -05:00
parent 4dcf88380b
commit 7e5261047e
19 changed files with 492 additions and 184 deletions

View File

@ -0,0 +1,9 @@
{
"files.associations": {
"*.pyx": "python",
"*.js": "javascript",
"*.c": "c",
"*.scene": "yaml",
"vector": "cpp"
}
}

View File

@ -1,27 +1,117 @@
#include "GreedyMesher.h" #include "GreedyMesher.h"
#include <algorithm>
std::vector<Quad> GreedyMesher::mesh(const std::vector<std::vector<std::vector<int>>>& voxelData) { // Helper to index into voxels with bounds checking
inline int voxelAt(const std::vector<std::vector<std::vector<int>>>& 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<Quad> GreedyMesher::mesh(const std::vector<std::vector<std::vector<int>>>& voxels) {
std::vector<Quad> quads; std::vector<Quad> quads;
int sizeX = voxels.size();
int sizeY = voxels[0].size();
int sizeZ = voxels[0][0].size();
int sizeX = voxelData.size(); // directions: {dx,dy,dz}, and their “du” and “dv” axes
if (sizeX == 0) return quads; static const int dirs[6][3] = {
int sizeY = voxelData[0].size(); {-1,0,0},{1,0,0},{0,-1,0},{0,1,0},{0,0,-1},{0,0,1}
int sizeZ = voxelData[0][0].size(); };
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 simplicity, we create one quad per non-empty voxel. // For each face direction
// A complete greedy mesher would merge adjacent quads. for (int d = 0; d < 6; d++) {
for (int x = 0; x < sizeX; ++x) { int dx = dirs[d][0], dy = dirs[d][1], dz = dirs[d][2];
for (int y = 0; y < sizeY; ++y) { int u = axisU[d], v = axisV[d];
for (int z = 0; z < sizeZ; ++z) {
if (voxelData[x][y][z] != 0) { // 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<int> 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; Quad q;
q.x = (float)x; // base position
q.y = (float)y; float pos[3] = {0,0,0};
q.z = (float)z; pos[0] = pos[1] = pos[2] = 0;
q.width = 1.0f; // set w axis coordinate
q.height = 1.0f; pos[(dx!=0?0:(dy!=0?1:2))] = w;
q.textureID = voxelData[x][y][z]; // 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); 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++;
}
} }
} }
} }

View File

@ -3,21 +3,25 @@
#include <vector> #include <vector>
// Simple structure to hold a quad (a single face of a voxel block) // A single quad face in world space.
struct Quad { struct Quad {
// Starting position of the quad in world space // Position of the minimal corner of the quad
float x, y, z; float x, y, z;
// Dimensions of the quad (for this demo we assume unit quads) // Dimensions of the quad along its two axes
float width, height; float du[3], dv[3];
// Texture ID (1 = grass, 2 = dirt, 3 = wood) // 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; int textureID;
}; };
class GreedyMesher { // GreedyMesher namespace
public: namespace GreedyMesher {
// Very basic greedy meshing algorithm: // Given a 3D voxel grid [x][y][z] with integer block IDs (0 = empty),
// For demonstration, each non-zero voxel produces a quad. // returns a list of merged Quad faces.
static std::vector<Quad> mesh(const std::vector<std::vector<std::vector<int>>>& voxelData); std::vector<Quad> mesh(const std::vector<std::vector<std::vector<int>>>& voxels);
}; }
#endif // GREEDYMESHER_H #endif // GREEDYMESHER_H

Binary file not shown.

View File

@ -1,7 +1,7 @@
# Compiler settings # Compiler settings
CXX = g++ CXX = g++
CXXFLAGS = -std=c++11 -I. -Iimgui-docking -IC:/msys64/mingw64/include CXXFLAGS = -std=c++20 -I. -Iimgui-docking -IC:/msys64/mingw64/include -DGLEW_STATIC -g
LDFLAGS = -LC:/msys64/mingw64/lib -lglfw3 -lopengl32 -lgdi32 LDFLAGS = -LC:/msys64/mingw64/lib -lglfw3 -lopengl32 -lgdi32 -lglew32 -lglu32
# List ImGui source files # List ImGui source files
IMGUISRCS = imgui-docking/imgui.cpp \ IMGUISRCS = imgui-docking/imgui.cpp \

View File

@ -1,16 +1,81 @@
#define STB_IMAGE_IMPLEMENTATION
#include "VoxelGame.h" #include "VoxelGame.h"
#include <iostream> #include <iostream>
#include <GL/glew.h> #include <GL/glew.h>
#include <GL/glu.h>
// Include stb_image implementation (ensure stb_image.h is in your include path)
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h" #include "stb_image.h"
#include <cmath>
#include <unordered_set>
#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() 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<std::vector<int>>(16, std::vector<int>(16, 0)));
} }
VoxelGame::~VoxelGame() { VoxelGame::~VoxelGame() {
@ -19,112 +84,125 @@ VoxelGame::~VoxelGame() {
glDeleteTextures(1, &textureWood); glDeleteTextures(1, &textureWood);
} }
bool VoxelGame::init() { bool VoxelGame::init() {
if (!loadTextures()) if (!loadTextures()) return false;
return false; // Initial camera position
generateVoxelData(); float cam[3] = {32,32,80};
generateMesh(); 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; return true;
} }
bool VoxelGame::loadTextures() { bool VoxelGame::loadTextures() {
int width, height, nrChannels; int w,h,n;
unsigned char* data; unsigned char* data;
data = stbi_load("grass.jpg",&w,&h,&n,4);
// Load grass texture if (!data) { std::cerr<<"grass.jpg load fail\n"; return false; }
data = stbi_load("grass.jpg", &width, &height, &nrChannels, 0); glGenTextures(1,&textureGrass); glBindTexture(GL_TEXTURE_2D,textureGrass);
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_S,GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,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_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,w,h,0,GL_RGBA,GL_UNSIGNED_BYTE,data);
nrChannels == 4 ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D); glGenerateMipmap(GL_TEXTURE_2D);
stbi_image_free(data); stbi_image_free(data);
} else { data = stbi_load("dirt.jpg",&w,&h,&n,4);
std::cerr << "Failed to load grass texture\n"; if (!data) { std::cerr<<"dirt.jpg load fail\n"; return false; }
return false; glGenTextures(1,&textureDirt); glBindTexture(GL_TEXTURE_2D,textureDirt);
}
// 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_S,GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,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_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,w,h,0,GL_RGBA,GL_UNSIGNED_BYTE,data);
nrChannels == 4 ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D); glGenerateMipmap(GL_TEXTURE_2D);
stbi_image_free(data); stbi_image_free(data);
} else { data = stbi_load("wood.png",&w,&h,&n,4);
std::cerr << "Failed to load dirt texture\n"; if (!data) { std::cerr<<"wood.png load fail\n"; return false; }
return false; glGenTextures(1,&textureWood); glBindTexture(GL_TEXTURE_2D,textureWood);
}
// 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_S,GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,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_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,w,h,0,GL_RGBA,GL_UNSIGNED_BYTE,data);
nrChannels == 4 ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D); glGenerateMipmap(GL_TEXTURE_2D);
stbi_image_free(data); stbi_image_free(data);
} else {
std::cerr << "Failed to load wood texture\n";
return false;
}
return true; return true;
} }
void VoxelGame::generateVoxelData() { Chunk VoxelGame::generateChunk(int cx,int cz) {
// For demo purposes: Chunk c; c.x=cx; c.z=cz;
// - Fill the bottom layer with grass (value 1) c.voxels.assign(16, std::vector<std::vector<int>>(16, std::vector<int>(16,0)));
// - Fill the next layer with dirt (value 2) float scale=0.1f;
// - Add a couple of wood blocks (value 3) for(int x=0;x<16;x++)for(int z=0;z<16;z++){
for (int x = 0; x < 16; ++x) { float wx = cx*16 + x, wz = cz*16 + z;
for (int z = 0; z < 16; ++z) { float n = perlin(wx*scale, wz*scale);
voxelData[x][0][z] = 1; // grass layer int h = int(((n+1)*0.5f)*16);
voxelData[x][1][z] = 2; // dirt layer h = std::clamp(h,0,16);
for(int y=0;y<h;y++){
c.voxels[x][y][z] = (y==h-1?1:2);
} }
} }
// Add some wood blocks as an example return c;
voxelData[5][2][5] = 3;
voxelData[6][2][5] = 3;
} }
void VoxelGame::generateMesh() { void VoxelGame::updateChunks(const float cam[3]) {
// Use the GreedyMesher to create mesh quads from voxel data. int cx = int(floor(cam[0]/16)), cz=int(floor(cam[2]/16));
meshQuads = GreedyMesher::mesh(voxelData); int r=2;
std::unordered_set<ChunkKey,ChunkKeyHash> 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) { void VoxelGame::update(float dt,const float cam[3]){
// Update game logic (animations, input, physics, etc.) updateChunks(cam);
// (This is left minimal for demo purposes)
} }
void VoxelGame::render(){ void VoxelGame::render(){
// Here you would typically bind a shader, set uniforms, bind textures, for(auto &p:chunks){
// and render the mesh (e.g. drawing quads as two triangles each). float ox = p.first.x*16, oz = p.first.z*16;
// For this demo, we simply output the number of quads. for(auto &q:p.second.mesh){
std::cout << "Rendering " << meshQuads.size() << " quads." << std::endl; // shift by chunk offset
// You can extend this function to perform real OpenGL drawing. 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::Begin("Debug");
ImGui::Text("Voxel Game Debug Window"); ImGui::Text("Chunks Loaded: %d", totalChunksLoaded);
ImGui::Text("Mesh quads count: %d", (int)meshQuads.size()); 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(); ImGui::End();
} }

View File

@ -2,40 +2,57 @@
#define VOXELGAME_H #define VOXELGAME_H
#include <vector> #include <vector>
#include <algorithm>
#include <unordered_map>
#include "GreedyMesher.h" #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<std::vector<std::vector<int>>> voxels;
// **Merged mesh faces** produced by the greedy mesher
std::vector<Quad> mesh;
};
#include "imgui.h" // A key for identifying a chunk by its grid coordinates.
#include "imgui_impl_glfw.h" struct ChunkKey {
#include "imgui_impl_opengl3.h" 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<int>()(key.x) ^ (std::hash<int>()(key.z) << 1);
}
};
class VoxelGame { class VoxelGame {
public: public:
VoxelGame(); VoxelGame();
~VoxelGame(); ~VoxelGame();
// Initialize textures, voxel data, and mesher
bool init(); bool init();
void update(float deltaTime, const float cameraPos[3]);
// Game loop functions
void update(float deltaTime);
void render(); void render();
void debugUI(); void debugUI();
private: private:
// 3D voxel data (0 = empty, 1 = grass, 2 = dirt, 3 = wood) bool loadTextures();
std::vector<std::vector<std::vector<int>>> voxelData; Chunk generateChunk(int cx, int cz);
// Mesh quads produced by the greedy mesher void updateChunks(const float cameraPos[3]);
std::vector<Quad> meshQuads; void drawCube(float x, float y, float z, int voxelType);
std::unordered_map<ChunkKey, Chunk, ChunkKeyHash> chunks;
int totalChunksLoaded;
int totalChunksEverLoaded;
// OpenGL texture IDs for each block type
unsigned int textureGrass; unsigned int textureGrass;
unsigned int textureDirt; unsigned int textureDirt;
unsigned int textureWood; unsigned int textureWood;
bool loadTextures();
void generateVoxelData();
void generateMesh();
}; };
#endif // VOXELGAME_H #endif // VOXELGAME_H

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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

View File

@ -1,24 +1,67 @@
#include <iostream> #include <iostream>
#include <cmath>
#include <GL/glew.h> #include <GL/glew.h>
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#include "VoxelGame.h" #include <GL/glu.h>
// ImGui headers (make sure these files are available in your include path)
#include "imgui.h" #include "imgui.h"
#include "imgui_internal.h" // For Im_PI
#include "imgui_impl_glfw.h" #include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.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<float>(xpos);
lastY = static_cast<float>(ypos);
firstMouse = false;
}
float xoffset = static_cast<float>(xpos) - lastX;
float yoffset = lastY - static_cast<float>(ypos);
lastX = static_cast<float>(xpos);
lastY = static_cast<float>(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<float>(IM_PI) / 180.0f;
float radPitch = pitch * static_cast<float>(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) { GLFWwindow* initWindow(int width, int height, const char* title) {
if (!glfwInit()) { if (!glfwInit()) {
std::cerr << "Failed to initialize GLFW\n"; std::cerr << "Failed to initialize GLFW\n";
return nullptr; return nullptr;
} }
// Request OpenGL 3.3 Core Profile
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 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); GLFWwindow* window = glfwCreateWindow(width, height, title, nullptr, nullptr);
if (!window) { if (!window) {
std::cerr << "Failed to create GLFW window\n"; std::cerr << "Failed to create GLFW window\n";
@ -26,8 +69,14 @@ GLFWwindow* initWindow(int width, int height, const char* title) {
return nullptr; return nullptr;
} }
glfwMakeContextCurrent(window); 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; return window;
} }
@ -36,58 +85,109 @@ int main() {
if (!window) if (!window)
return -1; return -1;
// Setup ImGui context
IMGUI_CHECKVERSION(); IMGUI_CHECKVERSION();
ImGui::CreateContext(); ImGui::CreateContext();
ImGuiIO &io = ImGui::GetIO(); ImGuiIO &io = ImGui::GetIO(); (void)io;
(void)io;
ImGui::StyleColorsDark(); ImGui::StyleColorsDark();
ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init("#version 330"); ImGui_ImplOpenGL3_Init("#version 330");
// Create game instance and initialize it
VoxelGame game; VoxelGame game;
if (!game.init()) { if (!game.init()) {
std::cerr << "Failed to initialize game\n"; std::cerr << "Failed to initialize game\n";
return -1; return -1;
} }
// Main loop float deltaTime = 0.0f, lastFrame = 0.0f;
static bool cursorEnabled = false;
static bool f1Pressed = false;
while (!glfwWindowShouldClose(window)) { while (!glfwWindowShouldClose(window)) {
float currentFrame = static_cast<float>(glfwGetTime());
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
glfwPollEvents(); glfwPollEvents();
// Update game logic (assume fixed deltaTime for demo) // Toggle cursor lock with F1 (if ImGui isnt capturing the mouse).
game.update(0.016f); 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;
}
// Start new ImGui frame
ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame(); ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame(); ImGui::NewFrame();
// Draw debug UI from game 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();
game.debugUI(); game.debugUI();
ImGui::Render(); 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.)
game.render();
// Render ImGui on top
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwSwapBuffers(window); glfwSwapBuffers(window);
} }
// Cleanup ImGui and GLFW
ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown(); ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext(); ImGui::DestroyContext();
glfwDestroyWindow(window); glfwDestroyWindow(window);
glfwTerminate(); glfwTerminate();
return 0; return 0;
} }

BIN
cpp-voxel-engine/main.o Normal file

Binary file not shown.

Binary file not shown.