2025-04-06 03:14:06 +00:00
|
|
|
#define STB_IMAGE_IMPLEMENTATION
|
2025-04-06 02:03:23 +00:00
|
|
|
#include "VoxelGame.h"
|
|
|
|
#include <iostream>
|
2025-04-06 02:09:28 +00:00
|
|
|
#include <GL/glew.h>
|
2025-04-06 03:14:06 +00:00
|
|
|
#include <GL/glu.h>
|
2025-04-06 02:03:23 +00:00
|
|
|
#include "stb_image.h"
|
2025-04-06 03:14:06 +00:00
|
|
|
#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];
|
|
|
|
}
|
|
|
|
|
2025-04-06 02:03:23 +00:00
|
|
|
|
|
|
|
VoxelGame::VoxelGame()
|
2025-04-06 03:14:06 +00:00
|
|
|
: textureGrass(0), textureDirt(0), textureWood(0),
|
|
|
|
totalChunksLoaded(0), totalChunksEverLoaded(0)
|
2025-04-06 02:03:23 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
VoxelGame::~VoxelGame() {
|
|
|
|
glDeleteTextures(1, &textureGrass);
|
|
|
|
glDeleteTextures(1, &textureDirt);
|
|
|
|
glDeleteTextures(1, &textureWood);
|
|
|
|
}
|
|
|
|
|
2025-04-06 03:14:06 +00:00
|
|
|
|
2025-04-06 02:03:23 +00:00
|
|
|
bool VoxelGame::init() {
|
2025-04-06 03:14:06 +00:00
|
|
|
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);
|
2025-04-06 02:03:23 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool VoxelGame::loadTextures() {
|
2025-04-06 03:14:06 +00:00
|
|
|
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);
|
2025-04-06 02:03:23 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2025-04-06 03:14:06 +00:00
|
|
|
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);
|
2025-04-06 02:03:23 +00:00
|
|
|
}
|
|
|
|
}
|
2025-04-06 03:14:06 +00:00
|
|
|
return c;
|
2025-04-06 02:03:23 +00:00
|
|
|
}
|
|
|
|
|
2025-04-06 03:14:06 +00:00
|
|
|
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();
|
2025-04-06 02:03:23 +00:00
|
|
|
}
|
|
|
|
|
2025-04-06 03:14:06 +00:00
|
|
|
void VoxelGame::update(float dt,const float cam[3]){
|
|
|
|
updateChunks(cam);
|
2025-04-06 02:03:23 +00:00
|
|
|
}
|
|
|
|
|
2025-04-06 03:14:06 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
2025-04-06 02:03:23 +00:00
|
|
|
}
|
|
|
|
|
2025-04-06 03:14:06 +00:00
|
|
|
void VoxelGame::debugUI(){
|
2025-04-06 02:03:23 +00:00
|
|
|
ImGui::Begin("Debug");
|
2025-04-06 03:14:06 +00:00
|
|
|
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);
|
2025-04-06 02:03:23 +00:00
|
|
|
ImGui::End();
|
|
|
|
}
|