Broken Greedy Mesher
This commit is contained in:
parent
4dcf88380b
commit
7e5261047e
9
cpp-voxel-engine/.vscode/settings.json
vendored
Normal file
9
cpp-voxel-engine/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"*.pyx": "python",
|
||||
"*.js": "javascript",
|
||||
"*.c": "c",
|
||||
"*.scene": "yaml",
|
||||
"vector": "cpp"
|
||||
}
|
||||
}
|
@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
BIN
cpp-voxel-engine/GreedyMesher.o
Normal file
BIN
cpp-voxel-engine/GreedyMesher.o
Normal file
Binary file not shown.
@ -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 \
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
|
BIN
cpp-voxel-engine/VoxelGame.o
Normal file
BIN
cpp-voxel-engine/VoxelGame.o
Normal file
Binary file not shown.
BIN
cpp-voxel-engine/imgui-docking/imgui.o
Normal file
BIN
cpp-voxel-engine/imgui-docking/imgui.o
Normal file
Binary file not shown.
BIN
cpp-voxel-engine/imgui-docking/imgui_demo.o
Normal file
BIN
cpp-voxel-engine/imgui-docking/imgui_demo.o
Normal file
Binary file not shown.
BIN
cpp-voxel-engine/imgui-docking/imgui_draw.o
Normal file
BIN
cpp-voxel-engine/imgui-docking/imgui_draw.o
Normal file
Binary file not shown.
BIN
cpp-voxel-engine/imgui-docking/imgui_impl_glfw.o
Normal file
BIN
cpp-voxel-engine/imgui-docking/imgui_impl_glfw.o
Normal file
Binary file not shown.
BIN
cpp-voxel-engine/imgui-docking/imgui_impl_opengl3.o
Normal file
BIN
cpp-voxel-engine/imgui-docking/imgui_impl_opengl3.o
Normal file
Binary file not shown.
BIN
cpp-voxel-engine/imgui-docking/imgui_tables.o
Normal file
BIN
cpp-voxel-engine/imgui-docking/imgui_tables.o
Normal file
Binary file not shown.
BIN
cpp-voxel-engine/imgui-docking/imgui_widgets.o
Normal file
BIN
cpp-voxel-engine/imgui-docking/imgui_widgets.o
Normal file
Binary file not shown.
10
cpp-voxel-engine/imgui.ini
Normal file
10
cpp-voxel-engine/imgui.ini
Normal 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
|
||||
|
@ -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 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;
|
||||
}
|
||||
|
BIN
cpp-voxel-engine/main.o
Normal file
BIN
cpp-voxel-engine/main.o
Normal file
Binary file not shown.
BIN
cpp-voxel-engine/voxelgame.exe
Normal file
BIN
cpp-voxel-engine/voxelgame.exe
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user