diff --git a/CMakeLists.txt b/CMakeLists.txt index e25983f..ad4cd12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,9 @@ add_executable(VoxelEngine src/Object3D.cpp src/Camera.h src/World.h + src/World.cpp + src/Mesh.h + src/Mesh.cpp ) # --- Configure GLAD (from your src/ and include/ folders) --- @@ -38,4 +41,4 @@ target_include_directories(VoxelEngine PUBLIC # 2. Tell CMake to link against the specific GLFW .lib file target_link_libraries(VoxelEngine PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/vendor/lib-vc2022/glfw3.lib -) \ No newline at end of file +) diff --git a/src/Mesh.cpp b/src/Mesh.cpp new file mode 100644 index 0000000..4653497 --- /dev/null +++ b/src/Mesh.cpp @@ -0,0 +1,56 @@ +#include "Mesh.h" + +Mesh::Mesh() : m_VAO(0), m_VBO(0), m_EBO(0), m_indexCount(0) {} + +Mesh::~Mesh() { + glDeleteVertexArrays(1, &m_VAO); + glDeleteBuffers(1, &m_VBO); + glDeleteBuffers(1, &m_EBO); +} + +void Mesh::init() { + // Generate all the OpenGL objects + glGenVertexArrays(1, &m_VAO); + glGenBuffers(1, &m_VBO); + glGenBuffers(1, &m_EBO); + + // Bind the VAO and set up the vertex attributes + glBindVertexArray(m_VAO); + + // 1. Bind VBO + glBindBuffer(GL_ARRAY_BUFFER, m_VBO); + + // 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); + glEnableVertexAttribArray(0); + // Normal Attribute + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); + glEnableVertexAttribArray(1); + + // 3. Bind EBO + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_EBO); + + // 4. Unbind VAO (good practice) + glBindVertexArray(0); +} + +void Mesh::uploadData(const std::vector &vboData, const std::vector &eboData) { + m_indexCount = eboData.size(); + + glBindBuffer(GL_ARRAY_BUFFER, m_VBO); + glBufferData(GL_ARRAY_BUFFER, vboData.size() * sizeof(float), vboData.data(), GL_STATIC_DRAW); + + glBindVertexArray(m_VAO); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_EBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, eboData.size() * sizeof(unsigned int), eboData.data(), GL_STATIC_DRAW); + glBindVertexArray(0); +} + +void Mesh::draw() { + if (m_indexCount == 0) return; + + glBindVertexArray(m_VAO); + glDrawElements(GL_TRIANGLES, m_indexCount, GL_UNSIGNED_INT, nullptr); + glBindVertexArray(0); +} diff --git a/src/Mesh.h b/src/Mesh.h new file mode 100644 index 0000000..837442d --- /dev/null +++ b/src/Mesh.h @@ -0,0 +1,27 @@ +#ifndef MESH_H +#define MESH_H + +#include + +#include + +class Mesh { + public: + Mesh(); + ~Mesh(); // Destructor to clean up GPU memory + + // Initializes the VAO and buffer objects + void init(); + + // Uploads mesh data to the GPU + void uploadData(const std::vector& vboData, const std::vector& eboData); + + // Binds the VAO and draws the mesh + void draw(); + + private: + unsigned int m_VAO, m_VBO, m_EBO; + size_t m_indexCount; +}; + +#endif MESH_H \ No newline at end of file diff --git a/src/World.cpp b/src/World.cpp new file mode 100644 index 0000000..a6f7bdc --- /dev/null +++ b/src/World.cpp @@ -0,0 +1,98 @@ +#include "World.h" +#include + +const int VERTEX_SIZE = 6; + +const float topFace[24] = { + // x, y, z, nx, ny, nz + -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, + 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, + 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, + -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f +}; +const float bottomFace[24] = { + -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, + 0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, + 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, + -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f +}; +const float rightFace[24] = { + 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, + 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, + 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, + 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f +}; +const float leftFace[24] = { + -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, + -0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, + -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, + -0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f +}; +const float frontFace[24] = { + -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, + 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, + 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, + -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f +}; +const float backFace[24] = { + -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, + 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, + 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, + -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) { + unsigned int vIndex = vbo.size() / VERTEX_SIZE; + for (int i = 0; i < 4; i++) { + const float* v = &faceVertices[i * VERTEX_SIZE]; + 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]); + } + ebo.push_back(vIndex + 0); + ebo.push_back(vIndex + 1); + ebo.push_back(vIndex + 2); + ebo.push_back(vIndex + 0); + ebo.push_back(vIndex + 2); + ebo.push_back(vIndex + 3); +} + +World::World() { + m_mesh.init(); +} + +Mesh& World::getMesh() { + return m_mesh; +} + +void World::generateMesh() { + std::vector vboData; + std::vector eboData; + + 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); + } + } + + // Now, upload this data to our mesh object + m_mesh.uploadData(vboData, eboData); +} diff --git a/src/World.h b/src/World.h index dfb4279..3367d1b 100644 --- a/src/World.h +++ b/src/World.h @@ -5,6 +5,8 @@ #include +#include "Mesh.h" + // Hash function for glm::ivec3 to use with unordered_set namespace std { template <> @@ -25,6 +27,15 @@ class World { public: std::unordered_set voxels; + + World(); + + void generateMesh(); + + Mesh& getMesh(); + + private: + Mesh m_mesh; }; #endif WORLD_H diff --git a/src/main.cpp b/src/main.cpp index ce43561..e11c433 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -238,35 +238,9 @@ int main() { } world.voxels.insert(glm::ivec3(7, 0, 13)); - - unsigned int VBO, VAO, EBO; - glGenVertexArrays(1, &VAO); // 1. Create Vertex Array Object (VAO) - glGenBuffers(1, &VBO); // 2. Create Vertex Buffer Object (VBO) - glGenBuffers(1, &EBO); - - glBindVertexArray(VAO); // 3. Bind the VAO - - glBindBuffer(GL_ARRAY_BUFFER, VBO); // 4. Bind the VBO - // 5. Copy vertex data into VBO's memory - glBufferData(GL_ARRAY_BUFFER, cube.VBO_buffer.size() * sizeof(float), &cube.VBO_buffer.front(), GL_STATIC_DRAW); - - // 6. Tell OpenGL how to interpret the vertex data - - // Position Attribute - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); - glEnableVertexAttribArray(0); // Enable the vertex attribute (location 0) - - // Normal Attribute - glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); - glEnableVertexAttribArray(1); - - // 6.5 Setup EBO - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, cube.EBO_buffer.size() * sizeof(unsigned int), &cube.EBO_buffer.front(), GL_STATIC_DRAW); - - // Unbind VBO and VAO (optional, but good practice) - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); + std::cout << "Generating world mesh..." << std::endl; + world.generateMesh(); + std::cout << "Mesh generated!" << std::endl; if (WIREFRAME_MODE) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); @@ -294,27 +268,26 @@ int main() { view = camera.getView(); + shader.use(); + glm::mat4 model = glm::mat4(1.0f); + glm::mat4 mvp = projection * view * model; + shader.setMat4("u_mvp", mvp); + // ---- PASS 1: Draw solid grey cubes ---- glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(1.0, 1.0); // Push solid faces "back" glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + shader.setVec3("u_Color", glm::vec3(0.5, 0.5, 0.5)); - glm::vec3 solidColor = glm::vec3(0.5, 0.5, 0.5); // Grey - for (glm::ivec3 voxel_pos : world.voxels) { - // [UPDATED] Pass the grey color - draw_cube(voxel_pos, shader, VAO, cube.EBO_buffer.size(), view, projection, solidColor); - } + world.getMesh().draw(); glDisable(GL_POLYGON_OFFSET_FILL); // ---- PASS 2: Draw wireframe on top ---- glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // Just the lines + shader.setVec3("u_Color", glm::vec3(1.0, 1.0, 1.0)); - glm::vec3 wireframeColor = glm::vec3(1.0, 1.0, 1.0); // White - for (glm::ivec3 voxel_pos : world.voxels) { - // [UPDATED] Pass the white wireframe color - draw_cube(voxel_pos, shader, VAO, cube.EBO_buffer.size(), view, projection, wireframeColor); - } + world.getMesh().draw(); // --- Reset polygon mode for next frame (good practice) --- glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); @@ -332,10 +305,6 @@ int main() { glfwPollEvents(); } - // 7. --- Cleanup --- - glDeleteVertexArrays(1, &VAO); - glDeleteBuffers(1, &VBO); - glfwTerminate(); // Clean up GLFW return 0; } \ No newline at end of file