Compare commits
2 Commits
renderingb
...
needsfixes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c2fbd43ac | ||
|
|
6336d45075 |
@@ -32,6 +32,7 @@ private:
|
|||||||
std::map<int, bool> keyStates;
|
std::map<int, bool> keyStates;
|
||||||
float deltaTime = 0.16f;
|
float deltaTime = 0.16f;
|
||||||
bool orbitEquator = false;
|
bool orbitEquator = false;
|
||||||
|
bool collapsed;
|
||||||
float rotationRadius = 2500;
|
float rotationRadius = 2500;
|
||||||
float angle = 0.0f;
|
float angle = 0.0f;
|
||||||
const float ω = (std::pow(M_PI, 2) / 30) / 10;
|
const float ω = (std::pow(M_PI, 2) / 30) / 10;
|
||||||
@@ -84,7 +85,7 @@ public:
|
|||||||
|
|
||||||
void renderUI(GLFWwindow* window) {
|
void renderUI(GLFWwindow* window) {
|
||||||
handleCameraControls(window);
|
handleCameraControls(window);
|
||||||
ImGui::Begin("Planet Simulation");
|
collapsed = ImGui::Begin("Planet Simulation");
|
||||||
if (ImGui::BeginTable("MainLayout", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter)) {
|
if (ImGui::BeginTable("MainLayout", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter)) {
|
||||||
ImGui::TableSetupColumn("Controls", ImGuiTableColumnFlags_WidthStretch, 0.3f);
|
ImGui::TableSetupColumn("Controls", ImGuiTableColumnFlags_WidthStretch, 0.3f);
|
||||||
ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, 0.7f);
|
ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, 0.7f);
|
||||||
@@ -289,8 +290,9 @@ public:
|
|||||||
|
|
||||||
void renderPreviewPanel() {
|
void renderPreviewPanel() {
|
||||||
ImGui::BeginChild("PreviewChild", ImVec2(0, 0), true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
|
ImGui::BeginChild("PreviewChild", ImVec2(0, 0), true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
|
||||||
|
if (!collapsed) {
|
||||||
livePreview();
|
livePreview();
|
||||||
|
}
|
||||||
if (textureInitialized) {
|
if (textureInitialized) {
|
||||||
float aspect = (float)currentPreviewFrame.getWidth() / (float)currentPreviewFrame.getHeight();
|
float aspect = (float)currentPreviewFrame.getWidth() / (float)currentPreviewFrame.getHeight();
|
||||||
float availWidth = ImGui::GetContentRegionAvail().x;
|
float availWidth = ImGui::GetContentRegionAvail().x;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include "../util/noise/pnoise.cpp"
|
#include "../util/noise/pnoise.cpp"
|
||||||
#include "planet.cpp"
|
#include "planet.cpp"
|
||||||
|
#include "worldbox.cpp"
|
||||||
#include "../util/basicdefines.hpp"
|
#include "../util/basicdefines.hpp"
|
||||||
|
|
||||||
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
|
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
|
||||||
@@ -80,6 +81,7 @@ int main() {
|
|||||||
|
|
||||||
planetSimUI planetApp;
|
planetSimUI planetApp;
|
||||||
NoisePreviewState noiseState;
|
NoisePreviewState noiseState;
|
||||||
|
worldboxSimUI worldBox;
|
||||||
|
|
||||||
if (noiseState.layers.empty()) {
|
if (noiseState.layers.empty()) {
|
||||||
NoiseLayer defaultLayer;
|
NoiseLayer defaultLayer;
|
||||||
@@ -101,6 +103,7 @@ int main() {
|
|||||||
ImGui::GetMainViewport();
|
ImGui::GetMainViewport();
|
||||||
drawNoiseLab(noiseState);
|
drawNoiseLab(noiseState);
|
||||||
planetApp.renderUI(window);
|
planetApp.renderUI(window);
|
||||||
|
worldBox.renderUI(window);
|
||||||
|
|
||||||
ImGui::Begin("Integration Control");
|
ImGui::Begin("Integration Control");
|
||||||
ImGui::Text("Bridge: Noise Lab -> Planet Sim");
|
ImGui::Text("Bridge: Noise Lab -> Planet Sim");
|
||||||
|
|||||||
347
tests/worldbox.cpp
Normal file
347
tests/worldbox.cpp
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
#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 <GLFW/glfw3.h>
|
||||||
|
|
||||||
|
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<int, bool> 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<float>(now - lastFrameTime).count();
|
||||||
|
lastFrameTime = now;
|
||||||
|
|
||||||
|
handleCameraControls(window);
|
||||||
|
|
||||||
|
// Update simulation objects like the Star
|
||||||
|
sim.updateStar(deltaTime);
|
||||||
|
sim.updateWeatherAndPhysics(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("Weather & Physics", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||||
|
ImGui::Checkbox("Enable Gravity (Terrain)", &sim.config.enableGravity);
|
||||||
|
ImGui::DragFloat3("Gravity", sim.config.gravity.data());
|
||||||
|
ImGui::DragFloat3("Wind", sim.config.wind.data());
|
||||||
|
ImGui::DragFloat("Physics Step (sec)", &sim.config.physicsStep, 0.01f, 0.01f, 1.0f);
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Text("Clouds & Rain");
|
||||||
|
ImGui::DragInt("Cloud Count", &sim.config.cloudCount, 1, 0, 100);
|
||||||
|
ImGui::DragFloat("Cloud Height", &sim.config.cloudHeight, 5.0f, 10.0f, 1000.0f);
|
||||||
|
ImGui::DragFloat("Rain Spawn Rate", &sim.config.rainSpawnRate, 0.1f, 0.0f, 50.0f);
|
||||||
|
ImGui::ColorEdit3("Cloud Color", sim.config.cloudColor.data());
|
||||||
|
ImGui::ColorEdit3("Rain Color", sim.config.rainColor.data());
|
||||||
|
|
||||||
|
if (ImGui::Button("Generate Clouds", ImVec2(-1, 40))) {
|
||||||
|
sim.generateClouds();
|
||||||
|
applyDebugColorMode();
|
||||||
|
statsNeedUpdate = true;
|
||||||
|
}
|
||||||
|
if (ImGui::Button("Clear Weather", ImVec2(-1, 30))) {
|
||||||
|
for (auto& c : sim.clouds) sim.grid.remove(c.pos);
|
||||||
|
for (auto& r : sim.rainDrops) sim.grid.remove(r.pos);
|
||||||
|
sim.clouds.clear();
|
||||||
|
sim.rainDrops.clear();
|
||||||
|
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 == 0) {
|
||||||
|
v3 darkDirt = sim.config.baseDirtColor * 0.4f;
|
||||||
|
color = sim.config.baseDirtColor * (1.0f - p->data.moisture) + darkDirt * p->data.moisture;
|
||||||
|
} else if (p->data.type == 1) {
|
||||||
|
color = sim.config.baseRockColor;
|
||||||
|
} else if (p->data.type == 2) {
|
||||||
|
v3 lushGrass = sim.config.grassColorBase * 1.5f;
|
||||||
|
color = sim.config.grassColorBase * (1.0f - p->data.moisture) + lushGrass * p->data.moisture;
|
||||||
|
} else if (p->data.type == 3) {
|
||||||
|
color = sim.config.starColor;
|
||||||
|
} else if (p->data.type == 4) {
|
||||||
|
color = sim.config.cloudColor;
|
||||||
|
} else if (p->data.type == 5) {
|
||||||
|
color = sim.config.rainColor;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sim.grid.setColor(p->position, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void livePreview() {
|
||||||
|
std::lock_guard<std::mutex> 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<std::chrono::seconds>(now - lastStatsUpdate).count() >= 2) {
|
||||||
|
std::stringstream gridstats;
|
||||||
|
sim.grid.printStats(gridstats);
|
||||||
|
cachedStats = gridstats.str();
|
||||||
|
lastStatsUpdate = now;
|
||||||
|
statsNeedUpdate = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
constexpr int Dim = 3;
|
constexpr int Dim = 3;
|
||||||
|
|
||||||
template<typename T, typename IndexType = uint16_t>
|
template<typename T, typename IndexType = uint32_t>
|
||||||
class Octree {
|
class Octree {
|
||||||
public:
|
public:
|
||||||
using PointType = Eigen::Matrix<float, Dim, 1>;
|
using PointType = Eigen::Matrix<float, Dim, 1>;
|
||||||
@@ -89,6 +89,13 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct RaycastHit {
|
||||||
|
std::shared_ptr<NodeData> node;
|
||||||
|
float distance;
|
||||||
|
PointType normal;
|
||||||
|
PointType hitPoint;
|
||||||
|
};
|
||||||
|
|
||||||
struct OctreeNode {
|
struct OctreeNode {
|
||||||
BoundingBox bounds;
|
BoundingBox bounds;
|
||||||
std::vector<std::shared_ptr<NodeData>> points;
|
std::vector<std::shared_ptr<NodeData>> points;
|
||||||
@@ -235,7 +242,7 @@ private:
|
|||||||
|
|
||||||
float lodFalloffRate_ = 0.1f; // Lower = better, higher = worse. 0-1
|
float lodFalloffRate_ = 0.1f; // Lower = better, higher = worse. 0-1
|
||||||
float lodMinDistance_ = 100.0f;
|
float lodMinDistance_ = 100.0f;
|
||||||
float maxDistance_ = size * size;
|
float maxDistance_ = 100000;
|
||||||
|
|
||||||
struct Ray {
|
struct Ray {
|
||||||
PointType origin;
|
PointType origin;
|
||||||
@@ -634,6 +641,89 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void insertHit(std::vector<RaycastHit>& hits, size_t maxHits, const std::shared_ptr<NodeData>& node,
|
||||||
|
float t, const PointType& normal, const PointType& hitPoint, float& maxDist) const {
|
||||||
|
for (const auto& h : hits) {
|
||||||
|
if (h.node == node) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = std::lower_bound(hits.begin(), hits.end(), t,
|
||||||
|
[](const RaycastHit& a, float val) {
|
||||||
|
return a.distance < val;
|
||||||
|
});
|
||||||
|
|
||||||
|
hits.insert(it, {node, t, normal, hitPoint});
|
||||||
|
|
||||||
|
if (hits.size() > maxHits) {
|
||||||
|
hits.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hits.size() == maxHits) {
|
||||||
|
maxDist = std::min(maxDist, hits.back().distance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void voxelTraverseMultipleRecursive(OctreeNode* node, float tMin, float tMax, float& maxDist, bool enableLOD,
|
||||||
|
const Ray& ray, std::vector<RaycastHit>& hits, size_t maxHits, float invLodf) const {
|
||||||
|
if (enableLOD && !node->isLeaf) {
|
||||||
|
float dist = (node->center - ray.origin).norm();
|
||||||
|
float ratio = dist / node->nodeSize;
|
||||||
|
|
||||||
|
if (dist > lodMinDistance_ && ratio > invLodf && node->lodData) {
|
||||||
|
float t;
|
||||||
|
PointType n;
|
||||||
|
PointType h;
|
||||||
|
if (rayCubeIntersect(ray, node->lodData.get(), t, n, h)) {
|
||||||
|
if (t >= 0 && t <= maxDist) {
|
||||||
|
insertHit(hits, maxHits, node->lodData, t, n, h, maxDist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& pointData : node->points) {
|
||||||
|
if (!pointData->active) continue;
|
||||||
|
|
||||||
|
float t;
|
||||||
|
PointType normal, hitPoint;
|
||||||
|
if (rayCubeIntersect(ray, pointData.get(), t, normal, hitPoint)) {
|
||||||
|
if (t >= 0 && t <= maxDist && t <= tMax + 0.001f) {
|
||||||
|
insertHit(hits, maxHits, pointData, t, normal, hitPoint, maxDist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node->isLeaf) return;
|
||||||
|
|
||||||
|
// DDA Traversal
|
||||||
|
PointType center = node->center;
|
||||||
|
Eigen::Vector3f ttt = (center - ray.origin).cwiseProduct(ray.invDir);
|
||||||
|
|
||||||
|
int currIdx = 0;
|
||||||
|
currIdx = ((tMin >= ttt.x()) ? 1 : 0) | ((tMin >= ttt.y()) ? 2 : 0) | ((tMin >= ttt.z()) ? 4 : 0);
|
||||||
|
|
||||||
|
float tNext;
|
||||||
|
|
||||||
|
while(tMin < tMax && tMin <= maxDist) {
|
||||||
|
Eigen::Vector3f next_t;
|
||||||
|
next_t[0] = (currIdx & 1) ? tMax : ttt[0];
|
||||||
|
next_t[1] = (currIdx & 2) ? tMax : ttt[1];
|
||||||
|
next_t[2] = (currIdx & 4) ? tMax : ttt[2];
|
||||||
|
|
||||||
|
tNext = next_t.minCoeff();
|
||||||
|
|
||||||
|
int physIdx = currIdx ^ ray.signMask;
|
||||||
|
|
||||||
|
if (node->children[physIdx]) {
|
||||||
|
voxelTraverseMultipleRecursive(node->children[physIdx].get(), tMin, tNext, maxDist, enableLOD, ray, hits, maxHits, invLodf);
|
||||||
|
}
|
||||||
|
|
||||||
|
tMin = tNext;
|
||||||
|
currIdx |= ((next_t[0] <= tNext) ? 1 : 0) | ((next_t[1] <= tNext) ? 2 : 0) | ((next_t[2] <= tNext) ? 4 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PointType sampleGGX(const PointType& n, float roughness, uint32_t& state) const {
|
PointType sampleGGX(const PointType& n, float roughness, uint32_t& state) const {
|
||||||
float alpha = std::max(EPSILON, roughness * roughness);
|
float alpha = std::max(EPSILON, roughness * roughness);
|
||||||
float r1 = float(rand_r(&state)) / float(RAND_MAX);
|
float r1 = float(rand_r(&state)) / float(RAND_MAX);
|
||||||
@@ -2280,6 +2370,11 @@ public:
|
|||||||
|
|
||||||
size = 0;
|
size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void collectNodesByObjectId(int id, std::vector<std::shared_ptr<NodeData>>& results) const {
|
||||||
|
std::unordered_set<std::shared_ptr<NodeData>> seen;
|
||||||
|
collectNodesByObjectIdRecursive(root_.get(), id, results, seen);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -189,8 +189,7 @@ inline void updateNoiseTexture(NoisePreviewState& state) {
|
|||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, state.textureId);
|
glBindTexture(GL_TEXTURE_2D, state.textureId);
|
||||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, state.width, state.height,
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, state.width, state.height, 0, GL_RGB, GL_UNSIGNED_BYTE, state.pixelBuffer.data());
|
||||||
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_MIN_FILTER, GL_LINEAR);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||||
|
|
||||||
|
|||||||
@@ -837,6 +837,57 @@ public:
|
|||||||
|
|
||||||
void addMoon() {
|
void addMoon() {
|
||||||
///TODO: using planetConfig, add moon(s).
|
///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<int>(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<AltPositions>();
|
||||||
|
pt.altPos->originalPos = pos.cast<Eigen::half>();
|
||||||
|
pt.altPos->noisePos = pos.cast<Eigen::half>();
|
||||||
|
pt.altPos->tectonicPos = pos.cast<Eigen::half>();
|
||||||
|
|
||||||
|
pt.currentPos = pos;
|
||||||
|
pt.originColor = moonColor.cast<Eigen::half>();
|
||||||
|
pt.noiseDisplacement = 0.0f;
|
||||||
|
pt.surface = true;
|
||||||
|
|
||||||
|
config.surfaceNodes.emplace_back(pt);
|
||||||
|
|
||||||
|
grid.set(pt, pt.currentPos, true, pt.originColor.cast<float>(), 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() {
|
void stretchPlanet() {
|
||||||
|
|||||||
421
util/sim/worldbox.hpp
Normal file
421
util/sim/worldbox.hpp
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
#ifndef WORLDBOX_HPP
|
||||||
|
#define WORLDBOX_HPP
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
#include <cmath>
|
||||||
|
#include <random>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#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; // 0=Dirt, 1=Rock, 2=Grass, 3=Star, 4=Cloud, 5=Rain
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Weather Config
|
||||||
|
int cloudCount = 15;
|
||||||
|
float cloudHeight = 150.0f;
|
||||||
|
v3 cloudColor = v3(0.9f, 0.9f, 0.95f);
|
||||||
|
float cloudBaseSize = 6.0f;
|
||||||
|
|
||||||
|
v3 rainColor = v3(0.2f, 0.4f, 0.9f);
|
||||||
|
float rainDropSize = 0.5f;
|
||||||
|
float rainSpawnRate = 1.0f;
|
||||||
|
|
||||||
|
// Physics Config
|
||||||
|
bool enableGravity = true;
|
||||||
|
v3 gravity = v3(0.0f, -60.0f, 0.0f);
|
||||||
|
v3 wind = v3(20.0f, 0.0f, 10.0f);
|
||||||
|
float physicsStep = 0.1f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CloudVoxel {
|
||||||
|
v3 pos;
|
||||||
|
float size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RainDrop {
|
||||||
|
v3 pos;
|
||||||
|
v3 vel;
|
||||||
|
};
|
||||||
|
|
||||||
|
class worldboxsim {
|
||||||
|
public:
|
||||||
|
WorldBoxConfig config;
|
||||||
|
Octree<WorldVoxel> grid;
|
||||||
|
std::mt19937 rng;
|
||||||
|
std::vector<v3> starVoxelPositions;
|
||||||
|
|
||||||
|
std::vector<CloudVoxel> clouds;
|
||||||
|
std::vector<RainDrop> rainDrops;
|
||||||
|
float physicsTimer = 0.0f;
|
||||||
|
|
||||||
|
worldboxsim() : rng(42) {
|
||||||
|
config = WorldBoxConfig();
|
||||||
|
grid = Octree<WorldVoxel>(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 (!config.enableStarRotation) {
|
||||||
|
if (!starVoxelPositions.empty()) {
|
||||||
|
for(const auto& pos : starVoxelPositions) {
|
||||||
|
grid.remove(pos);
|
||||||
|
}
|
||||||
|
starVoxelPositions.clear();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate rotation
|
||||||
|
config.starAngle += dt * config.starSpeed;
|
||||||
|
if (config.starAngle > 2 * M_PI) config.starAngle -= 2 * M_PI;
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// 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<int>((config.starPanelSize / config.starVoxelSize) / 2.0f));
|
||||||
|
WorldVoxel starVoxel(0.0f, 0.0f, 3); // Type 3 = Star
|
||||||
|
|
||||||
|
// Calculate the new ideal positions for this frame
|
||||||
|
std::vector<v3> newPositions;
|
||||||
|
newPositions.reserve((2 * halfGrid + 1) * (2 * halfGrid + 1));
|
||||||
|
|
||||||
|
for (int i = -halfGrid; i <= halfGrid; ++i) {
|
||||||
|
for (int j = -halfGrid; j <= halfGrid; ++j) {
|
||||||
|
newPositions.push_back(starCenter + (right * (i * config.starVoxelSize)) + (up * (j * config.starVoxelSize)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply grid changes
|
||||||
|
if (starVoxelPositions.empty()) {
|
||||||
|
// Creation: Spawn voxels into the grid for the first time
|
||||||
|
for (const auto& pos : newPositions) {
|
||||||
|
// Injecting a high emittance factor (15.0f) to make it a bright emissive light source
|
||||||
|
grid.set(starVoxel, pos, true, config.starColor, config.starVoxelSize, true, 1, 1, 15.0f);
|
||||||
|
}
|
||||||
|
starVoxelPositions = newPositions;
|
||||||
|
} else if (starVoxelPositions.size() == newPositions.size()) {
|
||||||
|
// Moving: Using grid.move() to smoothly transfer nodes in the Octree
|
||||||
|
for (size_t i = 0; i < starVoxelPositions.size(); ++i) {
|
||||||
|
grid.move(starVoxelPositions[i], newPositions[i]);
|
||||||
|
}
|
||||||
|
starVoxelPositions = newPositions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void generateClouds() {
|
||||||
|
std::uniform_real_distribution<float> randX(-config.worldSizeX/2, config.worldSizeX/2);
|
||||||
|
std::uniform_real_distribution<float> randZ(-config.worldSizeZ/2, config.worldSizeZ/2);
|
||||||
|
std::uniform_real_distribution<float> randY(config.cloudHeight - 10.0f, config.cloudHeight + 10.0f);
|
||||||
|
|
||||||
|
for (int i=0; i<config.cloudCount; ++i) {
|
||||||
|
v3 center(randX(rng), randY(rng), randZ(rng));
|
||||||
|
int numVoxels = 10 + (rng() % 20);
|
||||||
|
|
||||||
|
for (int j=0; j<numVoxels; ++j) {
|
||||||
|
v3 offset(
|
||||||
|
(rng() % 40) - 20,
|
||||||
|
(rng() % 10) - 5,
|
||||||
|
(rng() % 40) - 20
|
||||||
|
);
|
||||||
|
v3 pos = center + offset;
|
||||||
|
float size = config.cloudBaseSize + (rng() % 6);
|
||||||
|
|
||||||
|
WorldVoxel vox(0.0f, 1.0f, 4); // Type 4 = Cloud
|
||||||
|
// Adding to grid with transmission=0.4f (makes it partially transparent for RTX)
|
||||||
|
grid.set(vox, pos, true, config.cloudColor, size, true, 4, 0, 0.0f, 1.0f, 0.0f, 0.4f);
|
||||||
|
clouds.push_back({pos, size});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateWeatherAndPhysics(float dt) {
|
||||||
|
float halfX = config.worldSizeX / 2.0f;
|
||||||
|
float halfZ = config.worldSizeZ / 2.0f;
|
||||||
|
|
||||||
|
// 1. Clouds Update
|
||||||
|
std::vector<CloudVoxel> nextClouds;
|
||||||
|
for (auto& c : clouds) {
|
||||||
|
v3 nextPos = c.pos + config.wind * dt;
|
||||||
|
|
||||||
|
// Screen wrap logic for wind drift
|
||||||
|
if (nextPos.x() > halfX) nextPos.x() -= config.worldSizeX;
|
||||||
|
if (nextPos.x() < -halfX) nextPos.x() += config.worldSizeX;
|
||||||
|
if (nextPos.z() > halfZ) nextPos.z() -= config.worldSizeZ;
|
||||||
|
if (nextPos.z() < -halfZ) nextPos.z() += config.worldSizeZ;
|
||||||
|
|
||||||
|
if (grid.move(c.pos, nextPos)) {
|
||||||
|
c.pos = nextPos;
|
||||||
|
} else {
|
||||||
|
WorldVoxel vox(0.0f, 1.0f, 4);
|
||||||
|
grid.set(vox, nextPos, true, config.cloudColor, c.size, true, 4, 0, 0.0f, 1.0f, 0.0f, 0.4f);
|
||||||
|
c.pos = nextPos;
|
||||||
|
}
|
||||||
|
nextClouds.push_back(c);
|
||||||
|
|
||||||
|
// Spawn Rain
|
||||||
|
std::uniform_real_distribution<float> dist(0, 1);
|
||||||
|
if (dist(rng) < (config.rainSpawnRate * dt * 0.1f)) {
|
||||||
|
RainDrop r = {c.pos - v3(0, c.size, 0), config.wind};
|
||||||
|
rainDrops.push_back(r);
|
||||||
|
WorldVoxel rv(0.0f, 1.0f, 5); // Type 5 = Rain
|
||||||
|
grid.set(rv, r.pos, true, config.rainColor, config.rainDropSize, true, 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clouds = nextClouds;
|
||||||
|
|
||||||
|
// 2. Rain Update
|
||||||
|
std::vector<RainDrop> nextRain;
|
||||||
|
for (auto& r : rainDrops) {
|
||||||
|
r.vel += config.gravity * dt;
|
||||||
|
v3 nextPos = r.pos + r.vel * dt;
|
||||||
|
|
||||||
|
v3 dir = (nextPos - r.pos);
|
||||||
|
float distMag = dir.norm();
|
||||||
|
|
||||||
|
if (distMag > 0) {
|
||||||
|
dir.normalize();
|
||||||
|
auto hit = grid.voxelTraverse(r.pos, dir, distMag, false);
|
||||||
|
|
||||||
|
// If it hits solid terrain
|
||||||
|
if (hit && hit->data.type != 4 && hit->data.type != 5) {
|
||||||
|
if (hit->data.type == 0) { // Hit Dirt
|
||||||
|
hit->data.moisture = std::min(1.0f, hit->data.moisture + 0.15f);
|
||||||
|
v3 darkDirt = config.baseDirtColor * 0.4f;
|
||||||
|
v3 wetColor = config.baseDirtColor * (1.0f - hit->data.moisture) + darkDirt * hit->data.moisture;
|
||||||
|
grid.setColor(hit->position, wetColor);
|
||||||
|
} else if (hit->data.type == 2) { // Hit Grass
|
||||||
|
hit->data.moisture = std::min(1.0f, hit->data.moisture + 0.15f);
|
||||||
|
v3 lushGrass = config.grassColorBase * 1.5f;
|
||||||
|
v3 wetColor = config.grassColorBase * (1.0f - hit->data.moisture) + lushGrass * hit->data.moisture;
|
||||||
|
grid.setColor(hit->position, wetColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.remove(r.pos);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete if falls out of bounds
|
||||||
|
if (nextPos.y() < -config.worldDepth - 20.0f) {
|
||||||
|
grid.remove(r.pos);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (grid.move(r.pos, nextPos)) {
|
||||||
|
r.pos = nextPos;
|
||||||
|
nextRain.push_back(r);
|
||||||
|
} else {
|
||||||
|
WorldVoxel rv(0.0f, 1.0f, 5);
|
||||||
|
grid.set(rv, nextPos, true, config.rainColor, config.rainDropSize, true, 5);
|
||||||
|
r.pos = nextPos;
|
||||||
|
nextRain.push_back(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rainDrops = nextRain;
|
||||||
|
|
||||||
|
// 3. Apply Block Gravity
|
||||||
|
if (config.enableGravity) {
|
||||||
|
physicsTimer += dt;
|
||||||
|
if (physicsTimer >= config.physicsStep) {
|
||||||
|
applyTerrainGravity();
|
||||||
|
physicsTimer = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyTerrainGravity() {
|
||||||
|
std::vector<std::shared_ptr<Octree<WorldVoxel>::NodeData>> nodes;
|
||||||
|
grid.collectNodesByObjectId( -1, nodes);
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<Octree<WorldVoxel>::NodeData>> terrain;
|
||||||
|
terrain.reserve(nodes.size());
|
||||||
|
for (auto& n : nodes) {
|
||||||
|
// Include Dirt, Rock, and Grass in gravity sweep
|
||||||
|
if (n->data.type == 0 || n->data.type == 1 || n->data.type == 2) {
|
||||||
|
terrain.push_back(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process Bottom-Up
|
||||||
|
std::sort(terrain.begin(), terrain.end(), [](const auto& a, const auto& b) {
|
||||||
|
return a->position.y() < b->position.y();
|
||||||
|
});
|
||||||
|
|
||||||
|
for (auto& n : terrain) {
|
||||||
|
v3 belowPos = n->position + v3(0, -config.voxelSize, 0);
|
||||||
|
|
||||||
|
// Bounds check so voxels don't fall infinitely
|
||||||
|
if (belowPos.y() < -config.worldDepth) continue;
|
||||||
|
|
||||||
|
auto hit = grid.find(belowPos, config.voxelSize * 0.1f);
|
||||||
|
if (!hit) {
|
||||||
|
grid.move(n->position, belowPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void generateGrass() {
|
||||||
|
TIME_FUNCTION;
|
||||||
|
float halfX = config.worldSizeX / 2.0f;
|
||||||
|
float halfZ = config.worldSizeZ / 2.0f;
|
||||||
|
float surfaceY = 0.0f;
|
||||||
|
|
||||||
|
int stepsX = static_cast<int>(std::round(config.worldSizeX / config.voxelSize)) + 1;
|
||||||
|
int stepsZ = static_cast<int>(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::thread::id>()(std::this_thread::get_id()));
|
||||||
|
std::uniform_real_distribution<float> probDist(0.0f, 1.0f);
|
||||||
|
std::uniform_int_distribution<int> 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<float> 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<int>(std::round(config.worldSizeX / config.voxelSize)) + 1;
|
||||||
|
int stepsZ = static_cast<int>(std::round(config.worldSizeZ / config.voxelSize)) + 1;
|
||||||
|
int stepsY = static_cast<int>(std::round(config.worldDepth / config.voxelSize)) + 1;
|
||||||
|
size_t maxSteps = stepsX * stepsZ * stepsY;
|
||||||
|
|
||||||
|
int nodeCount = 0;
|
||||||
|
|
||||||
|
#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();
|
||||||
|
clouds.clear();
|
||||||
|
rainDrops.clear();
|
||||||
|
starVoxelPositions.clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user