diff --git a/.attachments/voxel-types.png b/.attachments/voxel-types.png new file mode 100644 index 0000000..003ef10 Binary files /dev/null and b/.attachments/voxel-types.png differ diff --git a/README.md b/README.md index b863199..a64cf02 100644 --- a/README.md +++ b/README.md @@ -23,3 +23,9 @@ Instead of making the _entire_ world a single mesh, now the world is made up of Chunks now generate around the player in a square based on a render distance. This allows the flat plane to be infinite! ![infinite flat cube world](.attachments/chunk-generation.png) + +## 0.5.0 : Voxel Types - 11/12/25 + +Different voxel types are now supported in the data structures and passed to the shaders. + +![a world of dirt with some stone blocks](.attachments/voxel-types.png) diff --git a/shaders/cube_frag.glsl b/shaders/cube_frag.glsl index 4efa73e..3ce0b89 100644 --- a/shaders/cube_frag.glsl +++ b/shaders/cube_frag.glsl @@ -2,11 +2,23 @@ out vec4 FragColor; +flat in float v_VoxelKind; + // [NEW] This variable will be set from your C++ code uniform vec3 u_Color; void main() { // Use the color from C++ - FragColor = vec4(u_Color, 1.0); + // You can now use v_VoxelKind to modify the color + // Example: Different colors for different voxel types + vec3 color = u_Color; + if (v_VoxelKind == 0.0) { + // Dirt - brownish + color *= vec3(0.6, 0.4, 0.2); + } else if (v_VoxelKind == 1.0) { + // Stone - grayish (keep as is) + } + + FragColor = vec4(color, 1.0); } \ No newline at end of file diff --git a/shaders/cube_vert.glsl b/shaders/cube_vert.glsl index c34ce1b..42c6c01 100644 --- a/shaders/cube_vert.glsl +++ b/shaders/cube_vert.glsl @@ -4,12 +4,14 @@ // 'layout (location = 0)' matches the first attribute you enable. layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; +layout (location = 2) in float aVoxelKind; // A "uniform" is a global variable you set from your C++/Java/etc. code. // This matrix combines your model, view, and projection matrices. uniform mat4 u_mvp; flat out vec3 v_Normal; +flat out float v_VoxelKind; void main() { @@ -18,4 +20,5 @@ void main() gl_Position = u_mvp * vec4(aPos, 1.0); v_Normal = aNormal; + v_VoxelKind = aVoxelKind; } diff --git a/src/Chunk.cpp b/src/Chunk.cpp index e98269c..b2c35c8 100644 --- a/src/Chunk.cpp +++ b/src/Chunk.cpp @@ -2,7 +2,7 @@ #include #include -const int VERTEX_SIZE = 6; +const int VERTEX_SIZE = 7; // x, y, z, nx, ny, nz, voxelKind const float topFace[24] = { // x, y, z, nx, ny, nz @@ -42,16 +42,17 @@ const float backFace[24] = { -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f }; -void addFace(glm::ivec3 pos, const float *faceVertices, std::vector &vbo, std::vector &ebo) { +void addFace(glm::ivec3 pos, const float *faceVertices, float voxelKind, std::vector &vbo, std::vector &ebo) { unsigned int vIndex = vbo.size() / VERTEX_SIZE; for (int i = 0; i < 4; i++) { - const float* v = &faceVertices[i * VERTEX_SIZE]; + const float* v = &faceVertices[i * 6]; // Face data is still 6 floats per vertex vbo.push_back(v[0] + pos.x); vbo.push_back(v[1] + pos.y); vbo.push_back(v[2] + pos.z); vbo.push_back(v[3]); vbo.push_back(v[4]); vbo.push_back(v[5]); + vbo.push_back(voxelKind); // Add voxel kind as 7th component } ebo.push_back(vIndex + 0); ebo.push_back(vIndex + 1); @@ -65,7 +66,7 @@ Chunk::Chunk() { // mesh is initially null (nullptr) } -Chunk::Chunk(std::unordered_set voxels) { +Chunk::Chunk(std::unordered_map voxels) { this->voxels = voxels; } @@ -83,24 +84,25 @@ void Chunk::generateMesh() { // std::cout << "Generating Chunk mesh..." << std::endl; - for (const glm::ivec3 pos : voxels) { - if (voxels.count(pos + glm::ivec3(0, 1, 0)) == 0) { - addFace(pos, topFace, vboData, eboData); - } - if (voxels.count(pos + glm::ivec3(0, -1, 0)) == 0) { - addFace(pos, bottomFace, vboData, eboData); - } - if (voxels.count(pos + glm::ivec3(1, 0, 0)) == 0) { - addFace(pos, rightFace, vboData, eboData); - } - if (voxels.count(pos + glm::ivec3(-1, 0, 0)) == 0) { - addFace(pos, leftFace, vboData, eboData); - } - if (voxels.count(pos + glm::ivec3(0, 0, 1)) == 0) { - addFace(pos, frontFace, vboData, eboData); - } - if (voxels.count(pos + glm::ivec3(0, 0, -1)) == 0) { - addFace(pos, backFace, vboData, eboData); + const std::pair neighbors[6] = { + std::pair(glm::ivec3(0, 1, 0), topFace), // top + std::pair(glm::ivec3(0, -1, 0), bottomFace), // bottom + std::pair(glm::ivec3(1, 0, 0), rightFace), // right + std::pair(glm::ivec3(-1, 0, 0), leftFace), // left + std::pair(glm::ivec3(0, 0, 1), frontFace), // front + std::pair(glm::ivec3(0, 0, -1), backFace) // back + }; + + for (const std::pair& pair : voxels) { + const glm::ivec3& pos = pair.first; + const VoxelKind& kind = pair.second; + + for (const std::pair& neighborPair : neighbors) { + glm::ivec3 dir = neighborPair.first; + const float* face = neighborPair.second; + if (voxels.count(pos + dir) == 0) { + addFace(pos, face, static_cast(kind), vboData, eboData); + } } } @@ -113,3 +115,7 @@ void Chunk::generateMesh() { // Now, upload this data to our mesh object mesh->uploadData(vboData, eboData); } + +void Chunk::regenerateMesh() { + this->generateMesh(); +} diff --git a/src/Chunk.h b/src/Chunk.h index cdce79b..925b453 100644 --- a/src/Chunk.h +++ b/src/Chunk.h @@ -1,13 +1,18 @@ #ifndef CHUNK_H #define CHUNK_H -#include +#include #include #include #include "Mesh.h" +enum VoxelKind { + Dirt, + Stone +}; + const int CHUNK_SIZE = 32; // Hash function for glm::ivec3 to use with unordered_set @@ -28,14 +33,15 @@ namespace std { class Chunk { public: - std::unordered_set voxels; + std::unordered_map voxels; Chunk(); - Chunk(std::unordered_set voxels); + Chunk(std::unordered_map voxels); Mesh* getMesh(); // Returns pointer, can be null if not generated yet void generateMesh(); + void regenerateMesh(); private: std::unique_ptr mesh; // Nullable mesh diff --git a/src/Mesh.cpp b/src/Mesh.cpp index bc16f36..625bc5b 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -24,11 +24,14 @@ void Mesh::init() { // 2. Set up attributes (this layout is now saved in the VAO) // Position Attribute - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); // Normal Attribute - glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); + // VoxelKind Attribute + glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (void*)(6 * sizeof(float))); + glEnableVertexAttribArray(2); // 3. Bind EBO glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_EBO); diff --git a/src/World.cpp b/src/World.cpp index 1465a2e..d68a724 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -2,27 +2,38 @@ World::World() {} -void World::flip_voxel(glm::ivec3 position) { +void World::set_voxel(glm::ivec3 position, std::optional kind) { glm::ivec3 chunk_position = position / CHUNK_SIZE; glm::ivec3 local_position = position % CHUNK_SIZE; Chunk * chunk = get_or_create_chunk(chunk_position); - if (chunk->voxels.count(local_position) != 0) { + if (!kind.has_value()) { chunk->voxels.erase(chunk->voxels.find(local_position)); } else { - chunk->voxels.insert(local_position); + chunk->voxels[local_position] = kind.value(); } + + chunk->regenerateMesh(); } Chunk* World::get_or_create_chunk(glm::ivec3 chunk_position) { if (this->chunks.count(chunk_position) == 0) { // Fill in new chunk with a flat plane of voxels at y=0 - std::unordered_set voxels; + std::unordered_map voxels; if (chunk_position.y == 0) { - for (int z = 0; z <= 32; z++) { - for (int x = 0; x <= 32; x++) { - voxels.insert(glm::ivec3(x, 0, z)); + for (int z = 0; z <= CHUNK_SIZE; z++) { + for (int y = 0; y <= CHUNK_SIZE; y++) { + for (int x = 0; x <= CHUNK_SIZE; x++) { + glm::ivec3 offset = glm::ivec3(x, y, z); + glm::ivec3 world_voxel_position = chunk_position * 32 + offset; + + if (world_voxel_position.y == 0) { + voxels[offset] = VoxelKind::Dirt; + } else if (world_voxel_position.y < 0) { + voxels[offset] = VoxelKind::Stone; + } + } } } } diff --git a/src/World.h b/src/World.h index 927ac8f..82413fd 100644 --- a/src/World.h +++ b/src/World.h @@ -2,6 +2,7 @@ #define WORLD_H #include +#include #include @@ -14,7 +15,7 @@ class World World(); - void flip_voxel(glm::ivec3 position); + void set_voxel(glm::ivec3 position, std::optional kind); Chunk* get_or_create_chunk(glm::ivec3 chunk_position); }; diff --git a/src/main.cpp b/src/main.cpp index 6d0f431..29e821a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -54,6 +54,8 @@ bool first_mouse = true; bool mouse_lock = true; float last_x = 400, last_y = 300; +World world; + // Mouse Look void mouse_callback(GLFWwindow * window, double xpos, double ypos) { @@ -84,6 +86,10 @@ void mouse_callback(GLFWwindow * window, double xpos, double ypos) direction.y = -sin(glm::radians(pitch)); direction.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch)); camera.target = glm::normalize(direction); + + if(glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2) == GLFW_PRESS) { + world.set_voxel(glm::ivec3(camera.position), VoxelKind::Stone); + } } // process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly @@ -183,7 +189,7 @@ int main() { glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 2. --- Create a Window --- - GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "Voxel Engine 0.4.0", NULL, NULL); + GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "Voxel Engine 0.5.0", NULL, NULL); if (window == NULL) { std::cerr << "Failed to create GLFW window" << std::endl; glfwTerminate(); @@ -206,7 +212,6 @@ int main() { // 5. --- Set up Vertex Data and Buffers --- // Load Cube OBJ - World world; // Object3D cube = Object3D("objs/cube.obj"); // std::cout << "World created with " << world.chunks.size() << " chunks" << std::endl;