From 9a55f34dd6a7850d2c5b5d268d667d682a6b06b2 Mon Sep 17 00:00:00 2001 From: OusmBlueNinja <89956790+OusmBlueNinja@users.noreply.github.com> Date: Sat, 5 Apr 2025 21:03:23 -0500 Subject: [PATCH] starting... --- .../.vscode/c_cpp_properties.json | 21 + cpp-voxel-engine/GreedyMesher.cpp | 30 + cpp-voxel-engine/GreedyMesher.h | 23 + cpp-voxel-engine/Makefile | 20 + cpp-voxel-engine/VoxelGame.cpp | 130 + cpp-voxel-engine/VoxelGame.h | 36 + cpp-voxel-engine/dirt.jpg | Bin 0 -> 7732 bytes cpp-voxel-engine/grass.jpg | Bin 0 -> 3883 bytes cpp-voxel-engine/imgui-docking/LICENSE.txt | 21 + cpp-voxel-engine/imgui-docking/imconfig.h | 141 + cpp-voxel-engine/imgui-docking/imgui.cpp | 22948 ++++++++++++++++ cpp-voxel-engine/imgui-docking/imgui.h | 4033 +++ cpp-voxel-engine/imgui-docking/imgui_demo.cpp | 10984 ++++++++ cpp-voxel-engine/imgui-docking/imgui_draw.cpp | 4857 ++++ .../imgui-docking/imgui_impl_glfw.cpp | 1460 + .../imgui-docking/imgui_impl_glfw.h | 69 + .../imgui-docking/imgui_impl_opengl3.cpp | 1023 + .../imgui-docking/imgui_impl_opengl3.h | 67 + .../imgui-docking/imgui_impl_opengl3_loader.h | 916 + .../imgui-docking/imgui_impl_win32.h | 54 + .../imgui-docking/imgui_internal.h | 3941 +++ .../imgui-docking/imgui_tables.cpp | 4525 +++ .../imgui-docking/imgui_widgets.cpp | 10577 +++++++ .../imgui-docking/imstb_rectpack.h | 627 + .../imgui-docking/imstb_textedit.h | 1469 + .../imgui-docking/imstb_truetype.h | 5085 ++++ cpp-voxel-engine/main.cpp | 97 + cpp-voxel-engine/stb_image.h | 7991 ++++++ cpp-voxel-engine/wood.png | Bin 0 -> 83918 bytes 29 files changed, 81145 insertions(+) create mode 100644 cpp-voxel-engine/.vscode/c_cpp_properties.json create mode 100644 cpp-voxel-engine/GreedyMesher.cpp create mode 100644 cpp-voxel-engine/GreedyMesher.h create mode 100644 cpp-voxel-engine/Makefile create mode 100644 cpp-voxel-engine/VoxelGame.cpp create mode 100644 cpp-voxel-engine/VoxelGame.h create mode 100644 cpp-voxel-engine/dirt.jpg create mode 100644 cpp-voxel-engine/grass.jpg create mode 100644 cpp-voxel-engine/imgui-docking/LICENSE.txt create mode 100644 cpp-voxel-engine/imgui-docking/imconfig.h create mode 100644 cpp-voxel-engine/imgui-docking/imgui.cpp create mode 100644 cpp-voxel-engine/imgui-docking/imgui.h create mode 100644 cpp-voxel-engine/imgui-docking/imgui_demo.cpp create mode 100644 cpp-voxel-engine/imgui-docking/imgui_draw.cpp create mode 100644 cpp-voxel-engine/imgui-docking/imgui_impl_glfw.cpp create mode 100644 cpp-voxel-engine/imgui-docking/imgui_impl_glfw.h create mode 100644 cpp-voxel-engine/imgui-docking/imgui_impl_opengl3.cpp create mode 100644 cpp-voxel-engine/imgui-docking/imgui_impl_opengl3.h create mode 100644 cpp-voxel-engine/imgui-docking/imgui_impl_opengl3_loader.h create mode 100644 cpp-voxel-engine/imgui-docking/imgui_impl_win32.h create mode 100644 cpp-voxel-engine/imgui-docking/imgui_internal.h create mode 100644 cpp-voxel-engine/imgui-docking/imgui_tables.cpp create mode 100644 cpp-voxel-engine/imgui-docking/imgui_widgets.cpp create mode 100644 cpp-voxel-engine/imgui-docking/imstb_rectpack.h create mode 100644 cpp-voxel-engine/imgui-docking/imstb_textedit.h create mode 100644 cpp-voxel-engine/imgui-docking/imstb_truetype.h create mode 100644 cpp-voxel-engine/main.cpp create mode 100644 cpp-voxel-engine/stb_image.h create mode 100644 cpp-voxel-engine/wood.png diff --git a/cpp-voxel-engine/.vscode/c_cpp_properties.json b/cpp-voxel-engine/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..04cf75a --- /dev/null +++ b/cpp-voxel-engine/.vscode/c_cpp_properties.json @@ -0,0 +1,21 @@ +{ + "configurations": [ + { + "name": "Win32", + "includePath": [ + "${default}" + ], + "defines": [ + "_DEBUG", + "UNICODE", + "_UNICODE" + ], + "windowsSdkVersion": "10.0.22621.0", + "compilerPath": "cl.exe", + "cStandard": "c17", + "cppStandard": "c++17", + "intelliSenseMode": "windows-msvc-x64" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/cpp-voxel-engine/GreedyMesher.cpp b/cpp-voxel-engine/GreedyMesher.cpp new file mode 100644 index 0000000..49af5a7 --- /dev/null +++ b/cpp-voxel-engine/GreedyMesher.cpp @@ -0,0 +1,30 @@ +#include "GreedyMesher.h" + +std::vector GreedyMesher::mesh(const std::vector>>& voxelData) { + std::vector quads; + + 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); + } + } + } + } + return quads; +} diff --git a/cpp-voxel-engine/GreedyMesher.h b/cpp-voxel-engine/GreedyMesher.h new file mode 100644 index 0000000..c1baa5a --- /dev/null +++ b/cpp-voxel-engine/GreedyMesher.h @@ -0,0 +1,23 @@ +#ifndef GREEDYMESHER_H +#define GREEDYMESHER_H + +#include + +// Simple structure to hold a quad (a single face of a voxel block) +struct Quad { + // Starting position of the quad in world space + 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) + int textureID; +}; + +class GreedyMesher { +public: + // Very basic greedy meshing algorithm: + // For demonstration, each non-zero voxel produces a quad. + static std::vector mesh(const std::vector>>& voxelData); +}; + +#endif // GREEDYMESHER_H diff --git a/cpp-voxel-engine/Makefile b/cpp-voxel-engine/Makefile new file mode 100644 index 0000000..5e43bea --- /dev/null +++ b/cpp-voxel-engine/Makefile @@ -0,0 +1,20 @@ +# Compiler settings +CXX = g++ +CXXFLAGS = -std=c++11 -I. -I/path/to/glad/include -I/path/to/imgui -I. +LDFLAGS = -lglfw -ldl -lGL + +# List source files +SRCS = main.cpp VoxelGame.cpp GreedyMesher.cpp +OBJS = $(SRCS:.cpp=.o) +TARGET = voxelgame + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +clean: + rm -f $(OBJS) $(TARGET) diff --git a/cpp-voxel-engine/VoxelGame.cpp b/cpp-voxel-engine/VoxelGame.cpp new file mode 100644 index 0000000..43e80cd --- /dev/null +++ b/cpp-voxel-engine/VoxelGame.cpp @@ -0,0 +1,130 @@ +#include "VoxelGame.h" +#include +#include + +// Include stb_image implementation (ensure stb_image.h is in your include path) +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + +VoxelGame::VoxelGame() + : textureGrass(0), textureDirt(0), textureWood(0) +{ + // Initialize a 16x16x16 voxel grid with zeros + voxelData.resize(16, std::vector>(16, std::vector(16, 0))); +} + +VoxelGame::~VoxelGame() { + glDeleteTextures(1, &textureGrass); + glDeleteTextures(1, &textureDirt); + glDeleteTextures(1, &textureWood); +} + +bool VoxelGame::init() { + if (!loadTextures()) + return false; + generateVoxelData(); + generateMesh(); + return true; +} + +bool VoxelGame::loadTextures() { + int width, height, nrChannels; + unsigned char *data; + + // Load grass texture + data = stbi_load("grass.png", &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.png", &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; + } + 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 + } + } + // Add some wood blocks as an example + voxelData[5][2][5] = 3; + voxelData[6][2][5] = 3; +} + +void VoxelGame::generateMesh() { + // Use the GreedyMesher to create mesh quads from voxel data. + meshQuads = GreedyMesher::mesh(voxelData); +} + +void VoxelGame::update(float deltaTime) { + // Update game logic (animations, input, physics, etc.) + // (This is left minimal for demo purposes) +} + +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::debugUI() { + // Display a debug window using ImGui + ImGui::Begin("Debug"); + ImGui::Text("Voxel Game Debug Window"); + ImGui::Text("Mesh quads count: %d", (int)meshQuads.size()); + ImGui::End(); +} diff --git a/cpp-voxel-engine/VoxelGame.h b/cpp-voxel-engine/VoxelGame.h new file mode 100644 index 0000000..5756ea2 --- /dev/null +++ b/cpp-voxel-engine/VoxelGame.h @@ -0,0 +1,36 @@ +#ifndef VOXELGAME_H +#define VOXELGAME_H + +#include +#include "GreedyMesher.h" + +class VoxelGame { +public: + VoxelGame(); + ~VoxelGame(); + + // Initialize textures, voxel data, and mesher + bool init(); + + // Game loop functions + void update(float deltaTime); + void render(); + void debugUI(); + +private: + // 3D voxel data (0 = empty, 1 = grass, 2 = dirt, 3 = wood) + std::vector>> voxelData; + // Mesh quads produced by the greedy mesher + std::vector meshQuads; + + // OpenGL texture IDs for each block type + unsigned int textureGrass; + unsigned int textureDirt; + unsigned int textureWood; + + bool loadTextures(); + void generateVoxelData(); + void generateMesh(); +}; + +#endif // VOXELGAME_H diff --git a/cpp-voxel-engine/dirt.jpg b/cpp-voxel-engine/dirt.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5d96d4722644717db53c5fd66686bc8745eb2ff1 GIT binary patch literal 7732 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 VeJL