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 <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;
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<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;
// 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++;
}
}
}
}

View File

@ -3,21 +3,25 @@
#include <vector>
// 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<Quad> mesh(const std::vector<std::vector<std::vector<int>>>& 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<Quad> mesh(const std::vector<std::vector<std::vector<int>>>& voxels);
}
#endif // GREEDYMESHER_H

Binary file not shown.

View File

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

View File

@ -1,16 +1,81 @@
#define STB_IMAGE_IMPLEMENTATION
#include "VoxelGame.h"
#include <iostream>
#include <GL/glew.h>
// Include stb_image implementation (ensure stb_image.h is in your include path)
#define STB_IMAGE_IMPLEMENTATION
#include <GL/glu.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()
: 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() {
@ -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<std::vector<int>>(16, std::vector<int>(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<h;y++){
c.voxels[x][y][z] = (y==h-1?1:2);
}
}
// Add some wood blocks as an example
voxelData[5][2][5] = 3;
voxelData[6][2][5] = 3;
return c;
}
void VoxelGame::generateMesh() {
// Use the GreedyMesher to create mesh quads from voxel data.
meshQuads = GreedyMesher::mesh(voxelData);
void VoxelGame::updateChunks(const float cam[3]) {
int cx = int(floor(cam[0]/16)), cz=int(floor(cam[2]/16));
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) {
// 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();
}

View File

@ -2,40 +2,57 @@
#define VOXELGAME_H
#include <vector>
#include <algorithm>
#include <unordered_map>
#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"
#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<int>()(key.x) ^ (std::hash<int>()(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<std::vector<std::vector<int>>> voxelData;
// Mesh quads produced by the greedy mesher
std::vector<Quad> 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<ChunkKey, Chunk, ChunkKeyHash> chunks;
int totalChunksLoaded;
int totalChunksEverLoaded;
unsigned int textureGrass;
unsigned int textureDirt;
unsigned int textureWood;
bool loadTextures();
void generateVoxelData();
void generateMesh();
};
#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 <cmath>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include "VoxelGame.h"
// ImGui headers (make sure these files are available in your include path)
#include <GL/glu.h>
#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<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) {
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<float>(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 isnt 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;
}

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

Binary file not shown.

Binary file not shown.