diff --git a/tests/planet.cpp b/tests/planet.cpp index b8af6b2..ffdb410 100644 --- a/tests/planet.cpp +++ b/tests/planet.cpp @@ -32,6 +32,7 @@ private: std::map keyStates; float deltaTime = 0.16f; bool orbitEquator = false; + bool collapsed; float rotationRadius = 2500; float angle = 0.0f; const float ω = (std::pow(M_PI, 2) / 30) / 10; @@ -84,7 +85,7 @@ public: void renderUI(GLFWwindow* window) { handleCameraControls(window); - ImGui::Begin("Planet Simulation"); + collapsed = ImGui::Begin("Planet Simulation"); if (ImGui::BeginTable("MainLayout", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter)) { ImGui::TableSetupColumn("Controls", ImGuiTableColumnFlags_WidthStretch, 0.3f); ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, 0.7f); @@ -289,8 +290,9 @@ public: void renderPreviewPanel() { ImGui::BeginChild("PreviewChild", ImVec2(0, 0), true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); - - livePreview(); + if (!collapsed) { + livePreview(); + } if (textureInitialized) { float aspect = (float)currentPreviewFrame.getWidth() / (float)currentPreviewFrame.getHeight(); float availWidth = ImGui::GetContentRegionAvail().x; diff --git a/tests/ptest.cpp b/tests/ptest.cpp index ce0f716..bceba3a 100644 --- a/tests/ptest.cpp +++ b/tests/ptest.cpp @@ -10,6 +10,7 @@ #include "../util/noise/pnoise.cpp" #include "planet.cpp" +#include "worldbox.cpp" #include "../util/basicdefines.hpp" void framebuffer_size_callback(GLFWwindow* window, int width, int height) { @@ -80,6 +81,7 @@ int main() { planetSimUI planetApp; NoisePreviewState noiseState; + worldboxSimUI worldBox; if (noiseState.layers.empty()) { NoiseLayer defaultLayer; @@ -101,6 +103,7 @@ int main() { ImGui::GetMainViewport(); drawNoiseLab(noiseState); planetApp.renderUI(window); + worldBox.renderUI(window); ImGui::Begin("Integration Control"); ImGui::Text("Bridge: Noise Lab -> Planet Sim"); diff --git a/tests/worldbox.cpp b/tests/worldbox.cpp new file mode 100644 index 0000000..fe7e61d --- /dev/null +++ b/tests/worldbox.cpp @@ -0,0 +1,307 @@ +#ifndef WORLDBOX_CPP +#define WORLDBOX_CPP + +#include "../util/sim/worldbox.hpp" +#include "../util/grid/camera.hpp" + +#include "../imgui/imgui.h" +#include "../imgui/backends/imgui_impl_glfw.h" +#include "../imgui/backends/imgui_impl_opengl3.h" +#include + +class worldboxSimUI { +private: + worldboxsim sim; + Camera cam; + + // UI and Render State + GLuint textu = 0; + std::mutex PreviewMutex; + bool updatePreview = false; + bool textureInitialized = false; + frame currentPreviewFrame; + + // Rendering Settings + int outWidth = 1024; + int outHeight = 1024; + int reflectCount = 2; + bool slowRender = false; + float lodDist = 1024.0f; + float lodDropoff = 0.05f; + float maxViewDistance = 4096.0f; + bool globalIllumination = false; + bool useLod = true; + float framerate = 60.0f; + + // Input/Time + std::map keyStates; + std::chrono::steady_clock::time_point lastFrameTime; + float deltaTime = 0.016f; + + // Stats tracking + std::chrono::steady_clock::time_point lastStatsUpdate; + std::string cachedStats; + bool statsNeedUpdate = true; + + enum class DebugColorMode { + BASE, + NUTRIENTS, + MOISTURE + }; + DebugColorMode currentColorMode = DebugColorMode::BASE; + +public: + worldboxSimUI() { + // Position camera to look at the center of the world slightly from above + cam.origin = v3(0, 50, 80); + v3 target = v3(0, 0, 0); + cam.direction = (target - cam.origin).normalized(); + cam.up = v3(0, 1, 0); + cam.fov = 60; + cam.rotationSpeed = M_1_PI; + cam.movementSpeed = 50.0f; + lastFrameTime = std::chrono::steady_clock::now(); + } + + ~worldboxSimUI() { + if (textu != 0) { + glDeleteTextures(1, &textu); + } + sim.grid.clear(); + } + + void renderUI(GLFWwindow* window) { + // Compute delta time for consistent movement and sim stepping + auto now = std::chrono::steady_clock::now(); + deltaTime = std::chrono::duration(now - lastFrameTime).count(); + lastFrameTime = now; + + handleCameraControls(window); + + // Update simulation objects like the Star + sim.updateStar(deltaTime); + + ImGui::Begin("WorldBox Simulation"); + if (ImGui::BeginTable("MainLayout", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter)) { + ImGui::TableSetupColumn("Controls", ImGuiTableColumnFlags_WidthStretch, 0.3f); + ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, 0.7f); + ImGui::TableNextColumn(); + renderControlsPanel(); + ImGui::TableNextColumn(); + renderPreviewPanel(); + ImGui::EndTable(); + } + ImGui::End(); + } + + void handleCameraControls(GLFWwindow* window) { + glfwPollEvents(); + for (int i = GLFW_KEY_SPACE; i <= GLFW_KEY_LAST; i++) { + keyStates[i] = (glfwGetKey(window, i) == GLFW_PRESS); + } + + float currentSpeed = cam.movementSpeed * deltaTime; + if (keyStates[GLFW_KEY_LEFT_SHIFT]) currentSpeed *= 3.0f; // Sprint + + if (keyStates[GLFW_KEY_W]) cam.moveForward(currentSpeed); + if (keyStates[GLFW_KEY_S]) cam.moveBackward(currentSpeed); + if (keyStates[GLFW_KEY_A]) cam.moveLeft(currentSpeed); + if (keyStates[GLFW_KEY_D]) cam.moveRight(currentSpeed); + if (keyStates[GLFW_KEY_E]) cam.moveUp(currentSpeed); + if (keyStates[GLFW_KEY_Q]) cam.moveDown(currentSpeed); + + if (keyStates[GLFW_KEY_LEFT]) cam.rotateYaw(cam.rotationSpeed * deltaTime); + if (keyStates[GLFW_KEY_RIGHT]) cam.rotateYaw(-cam.rotationSpeed * deltaTime); + if (keyStates[GLFW_KEY_UP]) cam.rotatePitch(cam.rotationSpeed * deltaTime); + if (keyStates[GLFW_KEY_DOWN]) cam.rotatePitch(-cam.rotationSpeed * deltaTime); + } + + void renderControlsPanel() { + ImGui::BeginChild("ControlsScroll", ImVec2(0, 0), true); + + if (ImGui::CollapsingHeader("World Generation", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::DragFloat("Width (X)", &sim.config.worldSizeX, 1.0f, 10.0f, 2000.0f); + ImGui::DragFloat("Length (Z)", &sim.config.worldSizeZ, 1.0f, 10.0f, 2000.0f); + ImGui::DragFloat("Depth (Y)", &sim.config.worldDepth, 1.0f, 1.0f, 500.0f); + ImGui::DragFloat("Voxel Size", &sim.config.voxelSize, 0.1f, 0.1f, 10.0f); + + ImGui::ColorEdit3("Dirt Base Color", sim.config.baseDirtColor.data()); + + ImGui::Separator(); + + if (ImGui::Button("Generate Flat World", ImVec2(-1, 40))) { + sim.generateFlatWorld(); + // applyDebugColorMode(); + statsNeedUpdate = true; + } + if (ImGui::Button("Clear World", ImVec2(-1, 30))) { + sim.clearWorld(); + statsNeedUpdate = true; + } + } + + if (ImGui::CollapsingHeader("Environment & Celestial", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Text("Star Settings"); + ImGui::Checkbox("Enable Star Rotation", &sim.config.enableStarRotation); + ImGui::DragFloat("Orbit Radius", &sim.config.starOrbitRadius, 10.0f, 100.0f, 5000.0f); + ImGui::DragFloat("Star Speed", &sim.config.starSpeed, 0.01f, 0.0f, 5.0f); + ImGui::DragFloat("Panel Size", &sim.config.starPanelSize, 5.0f, 10.0f, 1000.0f); + ImGui::ColorEdit3("Star Color", sim.config.starColor.data()); + + ImGui::Separator(); + ImGui::Text("Grass Settings"); + ImGui::SliderFloat("Grass Density", &sim.config.grassDensity, 0.0f, 1.0f); + ImGui::ColorEdit3("Grass Color Base", sim.config.grassColorBase.data()); + + if (ImGui::Button("Generate Grass", ImVec2(-1, 40))) { + sim.generateGrass(); + applyDebugColorMode(); + statsNeedUpdate = true; + } + } + + if (ImGui::CollapsingHeader("Debug Views")) { + ImGui::Text("Render Data Mode:"); + bool colorChanged = false; + if (ImGui::RadioButton("Base Color", currentColorMode == DebugColorMode::BASE)) { + currentColorMode = DebugColorMode::BASE; + colorChanged = true; + } + if (ImGui::RadioButton("Nutrients", currentColorMode == DebugColorMode::NUTRIENTS)) { + currentColorMode = DebugColorMode::NUTRIENTS; + colorChanged = true; + } + if (ImGui::RadioButton("Moisture", currentColorMode == DebugColorMode::MOISTURE)) { + currentColorMode = DebugColorMode::MOISTURE; + colorChanged = true; + } + + if (colorChanged) { + applyDebugColorMode(); + } + } + + if (ImGui::CollapsingHeader("Camera & Render Settings", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::DragFloat3("Origin", cam.origin.data()); + ImGui::DragFloat3("Direction", cam.direction.data(), 0.0001f, -1.0f, 1.0f); + ImGui::DragFloat("Movement Speed", &cam.movementSpeed, 0.1f, 1.0f, 500.0f); + ImGui::InputFloat("Max Framerate", &framerate, 1, 10); + + ImGui::Checkbox("Use PBR/Raytracing (Slow)", &slowRender); + if(slowRender) { + ImGui::DragInt("Bounces", &reflectCount, 1, 0, 10); + ImGui::Checkbox("Global Illumination", &globalIllumination); + } + + ImGui::Checkbox("Use LODs", &useLod); + + if (ImGui::Button("Reset Camera")) { + cam.origin = v3(0, sim.config.worldDepth * 2.0f, std::max(sim.config.worldSizeX, sim.config.worldSizeZ)); + cam.direction = (v3(0, 0, 0) - cam.origin).normalized(); + } + } + + updateStatsCache(); + ImGui::TextUnformatted(cachedStats.c_str()); + + ImGui::EndChild(); + } + + void renderPreviewPanel() { + ImGui::BeginChild("PreviewChild", ImVec2(0, 0), true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + livePreview(); + if (textureInitialized) { + float aspect = (float)currentPreviewFrame.getWidth() / (float)currentPreviewFrame.getHeight(); + float availWidth = ImGui::GetContentRegionAvail().x; + ImGui::Image((void*)(intptr_t)textu, ImVec2(availWidth, availWidth / aspect)); + } + + ImGui::EndChild(); + } + + void applyDebugColorMode() { + if (sim.grid.empty()) return; + + v3 boundsHalfExtent = v3(sim.config.worldSizeX, sim.config.worldDepth, sim.config.worldSizeZ); + float searchRadius = boundsHalfExtent.norm() * 2.0f; + + auto allNodes = sim.grid.findInRadius(v3(0,0,0), searchRadius); + + for (auto& p : allNodes) { + if (!p || !p->active) continue; + + v3 color = sim.config.baseDirtColor; + + switch (currentColorMode) { + case DebugColorMode::NUTRIENTS: { + float t = std::clamp(p->data.nutrients, 0.0f, 1.0f); + color = v3(1.0f - t, t, 0.0f); + break; + } + case DebugColorMode::MOISTURE: { + float t = std::clamp(p->data.moisture, 0.0f, 1.0f); + color = v3(1.0f - t, 1.0f - t, 1.0f); + break; + } + case DebugColorMode::BASE: + default: + if (p->data.type == 1) color = sim.config.baseRockColor; + else if (p->data.type == 2) color = sim.config.grassColorBase; + else if (p->data.type == 3) color = sim.config.starColor; + else color = sim.config.baseDirtColor; + break; + } + + sim.grid.setColor(p->position, color); + } + } + + void livePreview() { + std::lock_guard lock(PreviewMutex); + updatePreview = true; + + float invFrameRate = 1.0f / framerate; + if (!useLod) { + sim.grid.setLODFalloff(0.01); + sim.grid.setLODMinDistance(1000.0f); + } else { + sim.grid.setLODMinDistance(lodDist); + sim.grid.setLODFalloff(lodDropoff); + } + sim.grid.setMaxDistance(maxViewDistance); + + if (slowRender) { + currentPreviewFrame = sim.grid.renderFrameTimed(cam, outHeight, outWidth, frame::colormap::RGB, invFrameRate, reflectCount, globalIllumination, useLod); + } else { + currentPreviewFrame = sim.grid.fastRenderFrame(cam, outHeight, outWidth, frame::colormap::RGB); + } + + if (textu == 0) { + glGenTextures(1, &textu); + } + glBindTexture(GL_TEXTURE_2D, textu); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, currentPreviewFrame.getWidth(), currentPreviewFrame.getHeight(), + 0, GL_RGB, GL_UNSIGNED_BYTE, currentPreviewFrame.getData().data()); + + updatePreview = false; + textureInitialized = true; + } + + void updateStatsCache() { + auto now = std::chrono::steady_clock::now(); + if (statsNeedUpdate || std::chrono::duration_cast(now - lastStatsUpdate).count() >= 2) { + std::stringstream gridstats; + sim.grid.printStats(gridstats); + cachedStats = gridstats.str(); + lastStatsUpdate = now; + statsNeedUpdate = false; + } + } +}; + +#endif \ No newline at end of file diff --git a/util/grid/grid3eigen.hpp b/util/grid/grid3eigen.hpp index 99c0155..324b53a 100644 --- a/util/grid/grid3eigen.hpp +++ b/util/grid/grid3eigen.hpp @@ -29,7 +29,7 @@ constexpr int Dim = 3; -template +template class Octree { public: using PointType = Eigen::Matrix; @@ -235,7 +235,7 @@ private: float lodFalloffRate_ = 0.1f; // Lower = better, higher = worse. 0-1 float lodMinDistance_ = 100.0f; - float maxDistance_ = size * size; + float maxDistance_ = 100000; struct Ray { PointType origin; diff --git a/util/noise/pnoise.cpp b/util/noise/pnoise.cpp index 92d1dc8..f88e23c 100644 --- a/util/noise/pnoise.cpp +++ b/util/noise/pnoise.cpp @@ -189,8 +189,7 @@ inline void updateNoiseTexture(NoisePreviewState& state) { glBindTexture(GL_TEXTURE_2D, state.textureId); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, state.width, state.height, - 0, GL_RGB, GL_UNSIGNED_BYTE, state.pixelBuffer.data()); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, state.width, state.height, 0, GL_RGB, GL_UNSIGNED_BYTE, state.pixelBuffer.data()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); diff --git a/util/sim/planet.hpp b/util/sim/planet.hpp index 3858a53..0dc532b 100644 --- a/util/sim/planet.hpp +++ b/util/sim/planet.hpp @@ -837,6 +837,57 @@ public: void addMoon() { ///TODO: using planetConfig, add moon(s). + TIME_FUNCTION; + + const float realEarthRadiusKm = 6371.0f; + const float realMoonRadiusKm = 1737.4f; + const float realOrbitKm = 384400.0f; + float simScale = config.radius / realEarthRadiusKm; + + float moonRadius = realMoonRadiusKm * simScale; + float orbitDistance = realOrbitKm * simScale; + + std::cout << "--- MOON GENERATION ---" << std::endl; + std::cout << "Sim Scale: " << simScale << " units/km" << std::endl; + std::cout << "Moon Radius: " << moonRadius << " units" << std::endl; + std::cout << "Orbit Distance: " << orbitDistance << " units" << std::endl; + + // Place the moon on the Z-axis to keep it distinct from the star (which is on the X-axis) + v3 moonCenter = config.center + v3(0.0f, 0.0f, orbitDistance); + v3 moonColor = v3(0.7f, 0.7f, 0.72f); // Pale gray color + + // Surface area ratio Moon/Earth is ~0.074. We scale the points to maintain node density. + int moonPoints = std::max(1000, static_cast(config.surfacePoints * 0.075f)); + + for (int i = 0; i < moonPoints; i++) { + float y = 1.0f - (i * 2.0f) / (moonPoints - 1); + float radiusY = std::sqrt(1.0f - y * y); + float Θ = Φ * i; + float x = std::cos(Θ) * radiusY; + float z = std::sin(Θ) * radiusY; + + v3 dir(x, y, z); + v3 pos = moonCenter + dir * moonRadius; + Particle pt; + + pt.altPos = std::make_unique(); + pt.altPos->originalPos = pos.cast(); + pt.altPos->noisePos = pos.cast(); + pt.altPos->tectonicPos = pos.cast(); + + pt.currentPos = pos; + pt.originColor = moonColor.cast(); + pt.noiseDisplacement = 0.0f; + pt.surface = true; + + config.surfaceNodes.emplace_back(pt); + + grid.set(pt, pt.currentPos, true, pt.originColor.cast(), config.voxelSize, true, 3, 0, 1.0, 0.0f, 0.0f, 1.0f); + } + + grid.optimize(); + std::cout << "Moon generation complete. Placed " << moonPoints << " nodes." << std::endl; + starAdded = true; } void stretchPlanet() { diff --git a/util/sim/worldbox.hpp b/util/sim/worldbox.hpp new file mode 100644 index 0000000..d0a2f0f --- /dev/null +++ b/util/sim/worldbox.hpp @@ -0,0 +1,232 @@ +#ifndef WORLDBOX_HPP +#define WORLDBOX_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include "../grid/grid3eigen.hpp" +#include "../timing_decorator.cpp" + +using v3 = Eigen::Vector3f; + +struct WorldVoxel { + float nutrients = 1.0f; + float moisture = 0.5f; + int type = 0; + + WorldVoxel() = default; + + WorldVoxel(float nut, float mois, int t) : nutrients(nut), moisture(mois), type(t) {} +}; + +struct WorldBoxConfig { + v3 center = v3(0, 0, 0); + float worldSizeX = 1000.0f; + float worldSizeZ = 1000.0f; + float worldDepth = 20.0f; + + float voxelSize = 2.0f; + v3 baseDirtColor = v3(0.36f, 0.25f, 0.14f); + v3 baseRockColor = v3(0.45f, 0.45f, 0.45f); + + float gridSizeCubeMin = 1024.0f; + + // Grass Config + float grassDensity = 0.05f; + v3 grassColorBase = v3(0.2f, 0.6f, 0.15f); + + // Star Config + bool enableStarRotation = false; // Off by default + float starOrbitRadius = 800.0f; + float starPanelSize = 100.0f; + float starVoxelSize = 10.0f; + v3 starColor = v3(1.0f, 0.95f, 0.8f); + float starSpeed = 0.2f; // Radians per second + float starAngle = 0.0f; +}; + +class worldboxsim { +public: + WorldBoxConfig config; + Octree grid; + std::mt19937 rng; + std::vector starVoxelPositions; + + worldboxsim() : rng(42) { + config = WorldBoxConfig(); + grid = Octree(v3(-config.gridSizeCubeMin, -config.gridSizeCubeMin, -config.gridSizeCubeMin),v3(config.gridSizeCubeMin, config.gridSizeCubeMin, config.gridSizeCubeMin), 16, 32); + grid.setBackgroundColor(v3(0.53f, 0.81f, 0.92f)); + } + + void updateStar(float dt) { + // If turned off, despawn current star if it exists + if (!config.enableStarRotation) { + if (!starVoxelPositions.empty()) { + WorldVoxel emptyVoxel; + for(const auto& pos : starVoxelPositions) { + grid.set(emptyVoxel, pos, false, v3(0,0,0), config.starVoxelSize, false, 0, 0); + } + starVoxelPositions.clear(); + } + return; + } + + // Calculate rotation + config.starAngle += dt * config.starSpeed; + if (config.starAngle > 2 * M_PI) config.starAngle -= 2 * M_PI; + + // 1. Erase the old star voxels + WorldVoxel emptyVoxel; + for(const auto& pos : starVoxelPositions) { + grid.set(emptyVoxel, pos, false, v3(0,0,0), config.starVoxelSize, false, 0, 0); + } + starVoxelPositions.clear(); + + // 2. Calculate new center of star (orbiting on the X/Y plane) + v3 starCenter(cos(config.starAngle) * config.starOrbitRadius, sin(config.starAngle) * config.starOrbitRadius, 0.0f); + + // 3. Create a flat panel facing the origin + v3 n = -starCenter.normalized(); + v3 worldUp(0, 1, 0); + if (std::abs(n.dot(worldUp)) > 0.99f) worldUp = v3(0, 0, 1); + v3 right = worldUp.cross(n).normalized(); + v3 up = n.cross(right).normalized(); + + int halfGrid = std::max(1, static_cast((config.starPanelSize / config.starVoxelSize) / 2.0f)); + WorldVoxel starVoxel(0.0f, 0.0f, 3); // Type 3 = Star + + for (int i = -halfGrid; i <= halfGrid; ++i) { + for (int j = -halfGrid; j <= halfGrid; ++j) { + v3 pos = starCenter + (right * (i * config.starVoxelSize)) + (up * (j * config.starVoxelSize)); + // Add star voxel with high material/emission flag + grid.set(starVoxel, pos, true, config.starColor, config.starVoxelSize, true, 1, 1); + starVoxelPositions.push_back(pos); + } + } + } + + void generateGrass() { + TIME_FUNCTION; + float halfX = config.worldSizeX / 2.0f; + float halfZ = config.worldSizeZ / 2.0f; + float surfaceY = 0.0f; + + int stepsX = static_cast(std::round(config.worldSizeX / config.voxelSize)) + 1; + int stepsZ = static_cast(std::round(config.worldSizeZ / config.voxelSize)) + 1; + + int grassCount = 0; + + #pragma omp parallel + { + std::random_device rd; + std::mt19937 local_rng(rd() ^ std::hash()(std::this_thread::get_id())); + std::uniform_real_distribution probDist(0.0f, 1.0f); + std::uniform_int_distribution grassHeightDist(1, 8); + + #pragma omp for schedule(static) collapse(2) + for (int i = 0; i < stepsX; ++i) { + for (int j = 0; j < stepsZ; ++j) { + + float x = -halfX + i * config.voxelSize; + float z = -halfZ + j * config.voxelSize; + + if (x > halfX || z > halfZ) continue; + + if (probDist(local_rng) < config.grassDensity) { + int gHeight = grassHeightDist(local_rng); + float gSize = config.voxelSize / 25.0f; + + std::uniform_real_distribution offDist(-config.voxelSize/2.0f + gSize/2.0f, config.voxelSize/2.0f - gSize/2.0f); + float offsetX = offDist(local_rng); + float offsetZ = offDist(local_rng); + + WorldVoxel gVox(1.0f, 0.8f, 2); // Type 2 = Grass + float baseY = surfaceY + (config.voxelSize / 2.0f) + (gSize / 2.0f); + + #pragma omp critical + { + for (int g = 0; g < gHeight; ++g) { + v3 gPos(x + offsetX, baseY + g * gSize, z + offsetZ); + grid.set(gVox, gPos, true, config.grassColorBase, gSize, true, 1, 0); + grassCount++; + } + } + } + } + } + } + std::cout << "Grass generation complete. Placed " << grassCount << " grass voxels." << std::endl; + } + + void generateFlatWorld() { + TIME_FUNCTION; + grid.clear(); + + float halfX = config.worldSizeX / 2.0f; + float halfZ = config.worldSizeZ / 2.0f; + float surfaceY = 0.0f; + + // 1. Calculate integer bounds to satisfy OpenMP + int stepsX = static_cast(std::round(config.worldSizeX / config.voxelSize)) + 1; + int stepsZ = static_cast(std::round(config.worldSizeZ / config.voxelSize)) + 1; + int stepsY = static_cast(std::round(config.worldDepth / config.voxelSize)) + 1; + size_t maxSteps = stepsX * stepsZ * stepsY; + + int nodeCount = 0; + + std::random_device rd; + std::mt19937 local_rng(rd()); + std::uniform_real_distribution colorVar(-0.03f, 0.03f); + std::uniform_real_distribution nutrientVar(0.8f, 1.2f); + + #pragma omp parallel for schedule(static) collapse(3) + for (int i = 0; i < stepsX; ++i) { + for (int j = 0; j < stepsZ; ++j) { + for (int k = 0; k < stepsY; ++k) { + + float x = -halfX + i * config.voxelSize; + float z = -halfZ + j * config.voxelSize; + float y = surfaceY - k * config.voxelSize; + + if (x > halfX || z > halfZ || y < surfaceY - config.worldDepth) { + continue; + } + + WorldVoxel voxel; + v3 color; + + float depthRatio = std::abs(y - surfaceY) / config.worldDepth; + + if (depthRatio > 0.8f) { + voxel.type = 1; + voxel.nutrients = 0.1f; + color = config.baseRockColor; + } else { + voxel.type = 0; + voxel.nutrients = 1.0f - depthRatio; + color = config.baseDirtColor; + } + + v3 pos(x, y, z); + + #pragma omp critical + grid.set(voxel, pos, true, color, config.voxelSize, true, 1, 0); + // nodeCount++; + } + } + } + + std::cout << "World generation complete. Placed " << nodeCount << " voxels." << std::endl; + } + + void clearWorld() { + grid.clear(); + } +}; + +#endif \ No newline at end of file