From 2c993995e85f1f666953da8b12409b0143310e6c Mon Sep 17 00:00:00 2001 From: yggdrasil75 Date: Sun, 22 Feb 2026 21:07:20 -0500 Subject: [PATCH] making a proper widget. --- tests/fluidsim.cpp | 2 +- tests/planet.cpp | 212 ++++++++++++++++++++++++++ tests/ptest.cpp | 138 +++++++++++++++++ util/grid/grid3eigen.hpp | 12 +- util/jsonhelper.hpp | 93 ++++++++++++ util/noise/pnoise.cpp | 90 ++++++++++- util/noise/pnoise2.hpp | 1 + util/sim/planet.hpp | 315 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 852 insertions(+), 11 deletions(-) create mode 100644 tests/planet.cpp create mode 100644 tests/ptest.cpp create mode 100644 util/jsonhelper.hpp create mode 100644 util/sim/planet.hpp diff --git a/tests/fluidsim.cpp b/tests/fluidsim.cpp index d381dec..a15574d 100644 --- a/tests/fluidsim.cpp +++ b/tests/fluidsim.cpp @@ -71,7 +71,7 @@ public: void update() { if (isRunning) { if (sim.getParticleCount() < (size_t)totalParticleCap) { - sim.spawnParticles(baseParticle, particlesToSpawnPerFrame); + sim.spawnParticles(baseParticle, particlesToSpawnPerFrame); } sim.applyPhysics(); } diff --git a/tests/planet.cpp b/tests/planet.cpp new file mode 100644 index 0000000..acd17fe --- /dev/null +++ b/tests/planet.cpp @@ -0,0 +1,212 @@ +#ifndef PLANET_CPP +#define PLANET_CPP + +#include "../util/sim/planet.hpp" +#include "../util/grid/camera.hpp" +#include "../util/noise/pnoise2.hpp" +#include "../util/noise/pnoise.cpp" + +class planetSimUI { +private: + planetsim sim; + Camera cam; + bool isRunning = false; + + // Texture Management + GLuint textu = 0; + std::mutex PreviewMutex; + bool updatePreview = false; + bool textureInitialized = false; + frame currentPreviewFrame; + int outWidth = 512; + int outHeight = 512; + float fps = 60; + int rayCount = 3; + int reflectCount = 4; + bool slowRender = false; + float lodDist = 4096.0f; + float lodDropoff = 0.001f; + bool globalIllumination = false; + bool useLod = true; + std::map keyStates; + float deltaTime = 0.16f; + bool orbitEquator = false; + float rotationRadius = 2500; + float angle = 0.0f; + const float ω = (std::pow(M_PI, 2) / 30) / 10; + +public: + planetSimUI() { + cam.origin = v3(4000, 4000, 4000); + cam.direction = (v3(0,0,0) - cam.origin).normalized(); + cam.up = v3(0,1,0); + cam.fov = 60; + cam.rotationSpeed = M_1_PI; + } + + ~planetSimUI() { + if (textu != 0) { + glDeleteTextures(1, &textu); + } + sim.grid.clear(); + } + + void renderUI(GLFWwindow* window) { + ImGui::Begin("Planet Simulation"); + if (orbitEquator) { + angle += cam.rotationSpeed * deltaTime * ω; + + cam.origin[0] = sim.config.center[0] + rotationRadius * cosf(angle); + cam.origin[1] = sim.config.center[1]; + cam.origin[2] = sim.config.center[2] + rotationRadius * sinf(angle); + + v3 target(sim.config.center); + cam.direction = (target - cam.origin).normalized(); + } + glfwPollEvents(); + for (int i = GLFW_KEY_SPACE; i <= GLFW_KEY_LAST; i++) { + keyStates[i] = (glfwGetKey(window, i) == GLFW_PRESS); + } + if (keyStates[GLFW_KEY_W]) cam.moveForward(deltaTime); + if (keyStates[GLFW_KEY_S]) cam.moveBackward(deltaTime); + if (keyStates[GLFW_KEY_A]) cam.moveLeft(deltaTime); + if (keyStates[GLFW_KEY_D]) cam.moveRight(deltaTime); + if (keyStates[GLFW_KEY_Z]) cam.moveUp(deltaTime); + if (keyStates[GLFW_KEY_X]) cam.moveDown(deltaTime); + if (keyStates[GLFW_KEY_Q]) cam.rotateYaw(deltaTime); + if (keyStates[GLFW_KEY_R]) cam.rotateYaw(-deltaTime); + + if (ImGui::CollapsingHeader("Base Configuration", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::DragFloat("Radius", &sim.config.radius, 1.0f, 10.0f, 10000.0f); + ImGui::InputInt("Surface Points", &sim.config.surfacePoints); + ImGui::DragFloat("Voxel Size", &sim.config.voxelSize, 0.1f, 0.1f, 100.0f); + ImGui::ColorEdit3("Base Color", sim.config.color.data()); + + ImGui::Separator(); + + if (ImGui::Button("1. Generate Fib Sphere", ImVec2(-1, 40))) { + sim.generateFibSphere(); + } + ImGui::Text("Current Step: %d", sim.config.currentStep); + ImGui::Text("Nodes: %zu", sim.config.surfaceNodes.size()); + } + if (ImGui::Button("Apply Tectonics")) { + simulateTectonics(); + } + + if (ImGui::CollapsingHeader("Physics Parameters")) { + ImGui::DragFloat("Gravity (G)", &sim.config.G_ATTRACTION, 0.1f); + ImGui::DragFloat("Time Step", &sim.config.TIMESTEP, 0.001f, 0.0001f, 0.1f); + ImGui::DragFloat("Viscosity", &sim.config.dampingFactor, 0.001f, 0.0f, 1.0f); + ImGui::DragFloat("Pressure Stiffness", &sim.config.pressureStiffness, 10.0f); + ImGui::DragInt("Num Plates", &sim.config.numPlates, 1, 1, 50); + } + + if (ImGui::CollapsingHeader("Tectonic Simulation")) { + ImGui::DragInt("Num Plates", &sim.config.numPlates, 1, 1, 100); + ImGui::DragFloat("Plate Randomness", &sim.config.plateRandom, 0.01f, 0.0f, 2.0f); + ImGui::DragInt("Smoothing Passes", &sim.config.smoothingPasses, 1, 0, 10); + ImGui::DragFloat("Mountain Height", &sim.config.mountHeight, 1.0f, 0.0f, 1000.0f); + ImGui::DragFloat("Valley Depth", &sim.config.valleyDepth, 1.0f, -1000.0f, 0.0f); + ImGui::DragFloat("Transform Roughness", &sim.config.transformRough, 1.0f, 0.0f, 500.0f); + ImGui::DragInt("Stress Passes", &sim.config.stressPasses, 1, 0, 20); + + if (ImGui::Button("2. Simulate Tectonics", ImVec2(-1, 40))) { + simulateTectonics(); + } + } + + if (ImGui::CollapsingHeader("Camera Controls")) { + 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::DragFloat("Rotation Speed", &cam.rotationSpeed, M_1_PI, M_1_PI, M_PI); + ImGui::InputFloat("Rotation Distance", &rotationRadius, 10, 100); + + ImGui::Checkbox("Use Slower Render", &slowRender); + + if (ImGui::Button("Focus on Planet")) { + v3 target(sim.config.center); + v3 newDir = (target - cam.origin).normalized(); + cam.direction = newDir; + } + + if (ImGui::Button(orbitEquator ? "Stop Equator" : "Orbit Equator")) orbitEquator = !orbitEquator; + } + + 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::End(); + } + + void applyNoise(const NoisePreviewState& noiseState) { + TIME_FUNCTION; + auto triplanarNoise = [&](const Eigen::Vector3f& pos) -> float { + PNoise2 gen(noiseState.masterSeed); + + Eigen::Vector3f n = pos.normalized(); + Eigen::Vector3f blend = n.cwiseAbs(); + float sum = blend.x() + blend.y() + blend.z(); + blend /= sum; + + Eigen::Vector3f offsetPos = pos + Eigen::Vector3f(noiseState.offset[0], noiseState.offset[1], 0.0f); + float vXY = sim.evaluate2DStack(Eigen::Vector2f(offsetPos.x(), offsetPos.y()), noiseState, gen); + float vXZ = sim.evaluate2DStack(Eigen::Vector2f(offsetPos.x(), offsetPos.z()), noiseState, gen); + float vYZ = sim.evaluate2DStack(Eigen::Vector2f(offsetPos.y(), offsetPos.z()), noiseState, gen); + + // Blend results + return vYZ * blend.x() + vXZ * blend.y() + vXY * blend.z(); + }; + sim._applyNoise(triplanarNoise); + } + + void livePreview() { + std::lock_guard lock(PreviewMutex); + updatePreview = true; + + sim.grid.setLODMinDistance(lodDist); + sim.grid.setLODFalloff(lodDropoff); + + if (slowRender) { + currentPreviewFrame = sim.grid.renderFrame(cam, outWidth, outHeight, frame::colormap::RGB, rayCount, reflectCount, globalIllumination, useLod); + } else { + currentPreviewFrame = sim.grid.fastRenderFrame(cam, outWidth, outHeight, 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 resetView() { + cam.origin = Vector3f(sim.config.gridSizeCube, sim.config.gridSizeCube, sim.config.gridSizeCube); + Vector3f center(sim.config.gridSizeCube / 2.0f, sim.config.gridSizeCube / 2.0f, sim.config.gridSizeCube / 2.0f); + cam.lookAt(center); + } + + void simulateTectonics() { + sim.assignSeeds(); + // sim.growPlatesCellular(); + sim.growPlatesRandom(); + //sim.fixBoundaries(); + sim.extraplateste(); + sim.finalizeApplyResults(); + } +}; + +#endif \ No newline at end of file diff --git a/tests/ptest.cpp b/tests/ptest.cpp new file mode 100644 index 0000000..f698725 --- /dev/null +++ b/tests/ptest.cpp @@ -0,0 +1,138 @@ +#include +#include +#include + +// Include GLAD/GLFW +// Ensure these are in your include path +#include + +// ImGui +#include "imgui.h" +#include "imgui_impl_glfw.h" +#include "imgui_impl_opengl3.h" + +// Include your implementation files +// (Unity build style as requested) +#include "../util/noise/pnoise.cpp" +#include "planet.cpp" +#include "../util/basicdefines.hpp" + +void framebuffer_size_callback(GLFWwindow* window, int width, int height) { + glViewport(0, 0, width, height); +} + +static void glfw_error_callback(int error, const char* description) +{ + fprintf(stderr, "GLFW Error %d: %s\n", error, description); +} + +int main() { + glfwSetErrorCallback(glfw_error_callback); + if (!glfwInit()) + return -1; + + #if defined(IMGUI_IMPL_OPENGL_ES2) + // GL ES 2.0 + GLSL 100 (WebGL 1.0) + const char* glsl_version = "#version 100"; + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); + #elif defined(IMGUI_IMPL_OPENGL_ES3) + // GL ES 3.0 + GLSL 300 es (WebGL 2.0) + const char* glsl_version = "#version 300 es"; + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); + #elif defined(__APPLE__) + // GL 3.2 + GLSL 150 + const char* glsl_version = "#version 150"; + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac + #else + // GL 3.0 + GLSL 130 + const char* glsl_version = "#version 130"; + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + //glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only + //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 3.0+ only + #endif + + bool application_not_closed = true; + GLFWwindow* window = glfwCreateWindow((int)(1280), (int)(800), "StupidSim", nullptr, nullptr); + if (window == nullptr) { + glfwTerminate(); + return 1; + } + glfwMakeContextCurrent(window); + glfwSwapInterval(1); + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + (void)io; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + ImGui::StyleColorsDark(); + ImGuiStyle& style = ImGui::GetStyle(); + ImGui_ImplGlfw_InitForOpenGL(window, true); + + #ifdef __EMSCRIPTEN__ + ImGui_ImplGlfw_InstallEmscriptenCallbacks(window, "#canvas"); + #endif + ImGui_ImplOpenGL3_Init(glsl_version); + + ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + + planetSimUI planetApp; + NoisePreviewState noiseState; + + if (noiseState.layers.empty()) { + NoiseLayer defaultLayer; + strcpy(defaultLayer.name, "Base Terrain"); + defaultLayer.type = NoiseType::Fractal; + noiseState.layers.push_back(defaultLayer); + } + updateNoiseTexture(noiseState); + + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + glClearColor(0.1f, 0.1f, 0.1f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + ImGui::GetMainViewport(); + drawNoiseLab(noiseState); + planetApp.renderUI(window); + + ImGui::Begin("Integration Control"); + ImGui::Text("Bridge: Noise Lab -> Planet Sim"); + ImGui::Separator(); + + ImGui::TextColored(ImVec4(0.5f, 0.8f, 1.0f, 1.0f), "Current Noise Layers: %zu", noiseState.layers.size()); + + if (ImGui::Button("APPLY CURRENT NOISE TO PLANET", ImVec2(-1, 50))) { + planetApp.applyNoise(noiseState); + } + + ImGui::End(); + + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + glfwSwapBuffers(window); + } + + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + + glfwDestroyWindow(window); + glfwTerminate(); + + FunctionTimer::printStats(FunctionTimer::Mode::ENHANCED); + + return 0; +} \ No newline at end of file diff --git a/util/grid/grid3eigen.hpp b/util/grid/grid3eigen.hpp index 3a17890..5e14ee3 100644 --- a/util/grid/grid3eigen.hpp +++ b/util/grid/grid3eigen.hpp @@ -957,9 +957,9 @@ public: } bool set(const T& data, const PointType& pos, bool visible, Eigen::Vector3f color, float size, bool active, - int objectId = -1, bool light = false, float emittance = 0.0f, float refraction = 0.0f, + int objectId = -1, int subId = 0, bool light = false, float emittance = 0.0f, float refraction = 0.0f, float reflection = 0.0f) { - auto pointData = std::make_shared(data, pos, visible, color, size, active, objectId, + auto pointData = std::make_shared(data, pos, visible, color, size, active, objectId, subId, light, emittance, refraction, reflection); if (insertRecursive(root_.get(), pointData, 0)) { this->size++; @@ -1073,14 +1073,10 @@ public: if (!remove(oldPos, tolerance)) return false; - bool res = set(newData, newPos, - newVisible, + bool res = set(newData, newPos, newVisible, newColor != Eigen::Vector3f(1.0f, 1.0f, 1.0f) ? newColor : pointData->color, newSize > 0 ? newSize : pointData->size, - newActive, - targetObjId, - finalSubId, - newLight, + newActive, targetObjId, finalSubId, newLight, newEmittance > 0 ? newEmittance : pointData->emittance, newRefraction >= 0 ? newRefraction : pointData->refraction, newReflection >= 0 ? newReflection : pointData->reflection); diff --git a/util/jsonhelper.hpp b/util/jsonhelper.hpp new file mode 100644 index 0000000..d032159 --- /dev/null +++ b/util/jsonhelper.hpp @@ -0,0 +1,93 @@ +#ifndef JSONHELPER_HPP +#define JSONHELPER_HPP + +#include +#include +#include + +namespace JsonHelper { + + // Helper to get string value between quotes + inline std::string parseString(const std::string& json, const std::string& key) { + size_t pos = json.find("\"" + key + "\""); + if (pos == std::string::npos) return ""; + pos = json.find(":", pos); + if (pos == std::string::npos) return ""; + size_t start = json.find("\"", pos); + if (start == std::string::npos) return ""; + size_t end = json.find("\"", start + 1); + if (end == std::string::npos) return ""; + return json.substr(start + 1, end - start - 1); + } + + // Helper to get raw non-string value (int, float, bool) + inline std::string parseRaw(const std::string& json, const std::string& key) { + size_t pos = json.find("\"" + key + "\""); + if (pos == std::string::npos) return ""; + pos = json.find(":", pos); + if (pos == std::string::npos) return ""; + pos++; // skip ':' + + while (pos < json.length() && std::isspace(json[pos])) pos++; + size_t end = pos; + while (end < json.length() && json[end] != ',' && json[end] != '}' && json[end] != ']' && !std::isspace(json[end])) end++; + + return json.substr(pos, end - pos); + } + + inline int parseInt(const std::string& json, const std::string& key, int defaultVal = 0) { + std::string raw = parseRaw(json, key); + if (raw.empty()) return defaultVal; + try { return std::stoi(raw); } catch(...) { return defaultVal; } + } + + inline float parseFloat(const std::string& json, const std::string& key, float defaultVal = 0.0f) { + std::string raw = parseRaw(json, key); + if (raw.empty()) return defaultVal; + try { return std::stof(raw); } catch(...) { return defaultVal; } + } + + inline bool parseBool(const std::string& json, const std::string& key, bool defaultVal = false) { + std::string raw = parseRaw(json, key); + if (raw.empty()) return defaultVal; + return raw == "true" || raw == "1"; + } + + // Helper to extract JSON objects out of a JSON array + inline std::vector parseArray(const std::string& json, const std::string& key) { + std::vector items; + size_t pos = json.find("\"" + key + "\""); + if (pos == std::string::npos) return items; + pos = json.find(":", pos); + if (pos == std::string::npos) return items; + pos = json.find("[", pos); + if (pos == std::string::npos) return items; + + int depth = 0; + size_t start = 0; + bool inString = false; + + for (size_t i = pos + 1; i < json.length(); ++i) { + if (json[i] == '"' && (i == 0 || json[i-1] != '\\')) { + inString = !inString; + } + if (!inString) { + if (json[i] == '{') { + if (depth == 0) start = i; + depth++; + } else if (json[i] == '}') { + depth--; + if (depth == 0) { + items.push_back(json.substr(start, i - start + 1)); + } + } else if (json[i] == ']') { + if (depth == 0) break; + } + } + } + return items; + } + +} + +#endif \ No newline at end of file diff --git a/util/noise/pnoise.cpp b/util/noise/pnoise.cpp index 97ee63d..92d1dc8 100644 --- a/util/noise/pnoise.cpp +++ b/util/noise/pnoise.cpp @@ -6,8 +6,11 @@ #include #include #include +#include +#include #include "./pnoise2.hpp" +#include "../jsonhelper.hpp" #include "../timing_decorator.hpp" #include "../../imgui/imgui.h" #include @@ -194,11 +197,94 @@ inline void updateNoiseTexture(NoisePreviewState& state) { state.needsUpdate = false; } +inline void saveNoiseState(const NoisePreviewState& state, const std::string& filename) { + std::ofstream out(filename); + if (!out) return; + + out << "{\n"; + out << " \"masterSeed\": " << state.masterSeed << ",\n"; + out << " \"offsetX\": " << state.offset[0] << ",\n"; + out << " \"offsetY\": " << state.offset[1] << ",\n"; + out << " \"layers\": [\n"; + + for (size_t i = 0; i < state.layers.size(); ++i) { + const auto& l = state.layers[i]; + out << " {\n"; + out << " \"enabled\": " << (l.enabled ? "true" : "false") << ",\n"; + out << " \"name\": \"" << l.name << "\",\n"; + out << " \"type\": " << (int)l.type << ",\n"; + out << " \"blend\": " << (int)l.blend << ",\n"; + out << " \"seedOffset\": " << l.seedOffset << ",\n"; + out << " \"scale\": " << l.scale << ",\n"; + out << " \"strength\": " << l.strength << ",\n"; + out << " \"octaves\": " << l.octaves << ",\n"; + out << " \"persistence\": " << l.persistence << ",\n"; + out << " \"lacunarity\": " << l.lacunarity << ",\n"; + out << " \"ridgeOffset\": " << l.ridgeOffset << "\n"; + out << " }" << (i < state.layers.size() - 1 ? "," : "") << "\n"; + } + + out << " ]\n"; + out << "}\n"; +} + +inline void loadNoiseState(NoisePreviewState& state, const std::string& filename) { + std::ifstream in(filename); + if (!in) return; + + std::stringstream buffer; + buffer << in.rdbuf(); + std::string json = buffer.str(); + + state.masterSeed = JsonHelper::parseInt(json, "masterSeed", 1337); + state.offset[0] = JsonHelper::parseFloat(json, "offsetX", 0.0f); + state.offset[1] = JsonHelper::parseFloat(json, "offsetY", 0.0f); + + auto layerStrs = JsonHelper::parseArray(json, "layers"); + state.layers.clear(); + + for (const auto& lStr : layerStrs) { + NoiseLayer l; + l.enabled = JsonHelper::parseBool(lStr, "enabled", true); + std::string name = JsonHelper::parseString(lStr, "name"); + if (!name.empty()) { + std::strncpy(l.name, name.c_str(), 31); + l.name[31] = '\0'; + } + l.type = (NoiseType)JsonHelper::parseInt(lStr, "type", 0); + l.blend = (BlendMode)JsonHelper::parseInt(lStr, "blend", 0); + l.seedOffset = JsonHelper::parseInt(lStr, "seedOffset", 0); + l.scale = JsonHelper::parseFloat(lStr, "scale", 0.02f); + l.strength = JsonHelper::parseFloat(lStr, "strength", 1.0f); + l.octaves = JsonHelper::parseInt(lStr, "octaves", 4); + l.persistence = JsonHelper::parseFloat(lStr, "persistence", 0.5f); + l.lacunarity = JsonHelper::parseFloat(lStr, "lacunarity", 2.0f); + l.ridgeOffset = JsonHelper::parseFloat(lStr, "ridgeOffset", 1.0f); + + state.layers.push_back(l); + } + state.needsUpdate = true; +} + inline void drawNoiseLab(NoisePreviewState& noiseState) { ImGui::Begin("2D Noise Lab"); - // Master Controls bool changed = false; + + static char filenameBuffer[128] = "output/noise_preset.json"; + ImGui::InputText("File", filenameBuffer, sizeof(filenameBuffer)); + ImGui::SameLine(); + if (ImGui::Button("Save JSON")) { + saveNoiseState(noiseState, filenameBuffer); + } + ImGui::SameLine(); + if (ImGui::Button("Load JSON")) { + loadNoiseState(noiseState, filenameBuffer); + changed = true; + } + + ImGui::Separator(); + changed |= ImGui::InputInt("Master Seed", &noiseState.masterSeed); changed |= ImGui::DragFloat2("Pan Offset", noiseState.offset, 1.0f); @@ -254,7 +340,7 @@ inline void drawNoiseLab(NoisePreviewState& noiseState) { } if (open) { - ImGui::Checkbox("##enabled", &layer.enabled); + if (ImGui::Checkbox("##enabled", &layer.enabled)) changed = true; ImGui::SameLine(); ImGui::InputText("##name", layer.name, 32); diff --git a/util/noise/pnoise2.hpp b/util/noise/pnoise2.hpp index 510024d..2489232 100644 --- a/util/noise/pnoise2.hpp +++ b/util/noise/pnoise2.hpp @@ -9,6 +9,7 @@ #include #include "../../eigen/Eigen/Core" #include "../timing_decorator.hpp" +#include "../basicdefines.hpp" class PNoise2 { private: diff --git a/util/sim/planet.hpp b/util/sim/planet.hpp new file mode 100644 index 0000000..c29e84e --- /dev/null +++ b/util/sim/planet.hpp @@ -0,0 +1,315 @@ +#ifndef PLANET_HPP +#define PLANET_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../grid/grid3eigen.hpp" +#include "../timing_decorator.cpp" + +#include "../imgui/imgui.h" +#include "../imgui/backends/imgui_impl_glfw.h" +#include "../imgui/backends/imgui_impl_opengl3.h" +#include +#include "../stb/stb_image.h" + +using v3 = Eigen::Vector3f; +const float Φ = M_PI * (3.0f - std::sqrt(5.0f)); + +enum class PlateType { + CONTINENTAL, + OCEANIC, + MIXED +}; + +struct Particle { + float noiseDisplacement = 0.0f; + int plateID = -1; + Eigen::Vector3f basePos; + Eigen::Vector3f currentPos; + float plateDisplacement = 0.0f; + float temperature = -1; + float water = -1; + Eigen::Vector3f originColor; + bool surface = false; + + //gravity factors: + Eigen::Matrix velocity; + Eigen::Matrix acceleration; + Eigen::Matrix forceAccumulator; + float density = 0.0f; + float pressure = 0.0f; + Eigen::Matrix pressureForce; + float viscosity = 0.5f; + Eigen::Matrix viscosityForce; + float restitution = 5.0f; + float mass; + bool isStatic = false; + float soundSpeed = 100.0f; + + std::unordered_map neighbors; +}; + +struct planetConfig { + Eigen::Vector3f center = Eigen::Vector3f(0,0,0); + float radius = 1024.0f; + Eigen::Vector3f color = Eigen::Vector3f(0, 1, 0); + + float voxelSize = 10.0f; + int surfacePoints = 50000; + + int currentStep = 0; + + float displacementStrength = 200.0f; + std::vector surfaceNodes; + float noiseStrength = 1.0f; + int numPlates = 15; + float plateRandom = 0.6f; + int smoothingPasses = 3; + float mountHeight = 250.0f; + float valleyDepth = -150.0f; + float transformRough = 80.0f; + int stressPasses = 5; + float maxElevationRatio = 0.25f; + float gridSizeCube = 16384; + float SMOOTHING_RADIUS = 1024.0f; + float REST_DENSITY = 0.00005f; + float TIMESTEP = 0.016f; + float G_ATTRACTION = 50.0f; + float gravitySoftening = 10.0f; + float pressureStiffness = 50000.0f; + float coreRepulsionRadius = 1000.0f; + float coreRepulsionStiffness = 100000.0f; + float dampingFactor = 0.98f; +}; + +struct PlateConfig { + int plateId; + Eigen::Vector3f plateEulerPole; + Eigen::Vector3f direction; + float angularVelocity; + float thickness; + float density; + float rigidity; + float temperature; + Eigen::Vector3f debugColor; + PlateType ptype; +}; + +class planetsim { +public: + planetConfig config; + Octree grid; + std::vector plates; + std::mt19937 rng = std::mt19937(42); + + planetsim() { + config = planetConfig(); + grid = Octree({-config.gridSizeCube,-config.gridSizeCube,-config.gridSizeCube,},{config.gridSizeCube,config.gridSizeCube,config.gridSizeCube}); + } + + float evaluate2DStack(const Eigen::Vector2f& point, const NoisePreviewState& state, PNoise2& gen) { + float finalValue = 0.0f; + Eigen::Vector2f p = point; + + for (const auto& layer : state.layers) { + if (!layer.enabled) continue; + + Eigen::Vector2f samplePoint = p * layer.scale; + + samplePoint += Eigen::Vector2f((float)layer.seedOffset * 10.5f, (float)layer.seedOffset * -10.5f); + + if (layer.blend == BlendMode::DomainWarp) { + if (layer.type == NoiseType::CurlNoise) { + Eigen::Vector2f flow = gen.curlNoise(samplePoint); + p += flow * layer.strength * 100.0f; + } else { + float warpX = sampleNoiseLayer(gen, layer.type, samplePoint, layer); + float warpY = sampleNoiseLayer(gen, layer.type, samplePoint + Eigen::Vector2f(5.2f, 1.3f), layer); + p += Eigen::Vector2f(warpX, warpY) * layer.strength * 100.0f; + } + continue; + } + + float nVal = sampleNoiseLayer(gen, layer.type, samplePoint, layer); + + switch (layer.blend) { + case BlendMode::Replace: finalValue = nVal * layer.strength; break; + case BlendMode::Add: finalValue += nVal * layer.strength; break; + case BlendMode::Subtract: finalValue -= nVal * layer.strength; break; + case BlendMode::Multiply: finalValue *= (nVal * layer.strength); break; + case BlendMode::Max: finalValue = std::max(finalValue, nVal * layer.strength); break; + case BlendMode::Min: finalValue = std::min(finalValue, nVal * layer.strength); break; + } + } + + float norm = std::tanh(finalValue); + return norm; + } + + void generateFibSphere() { + TIME_FUNCTION; + grid.clear(); + config.surfaceNodes.clear(); + for (int i = 0; i < config.surfacePoints; i++) { + float y = 1.0f - (i * 2.0f) / (config.surfacePoints - 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 = config.center + dir * config.radius; + Particle pt; + pt.basePos = pos; + pt.currentPos = pos; + pt.originColor = config.color; + pt.noiseDisplacement = 0.0f; + pt.surface = true; + config.surfaceNodes.emplace_back(pt); + grid.set(pt, pt.currentPos, true, pt.originColor, config.voxelSize, true, 1, 0, false, 0.0f, 0.0f, 0.0f); + } + config.currentStep = 1; + std::cout << "Step 1 done. base sphere generated" << std::endl; + buildAdjacencyList(); + grid.save("output/fibSphere"); + } + + inline void _applyNoise(std::function noiseFunc) { + for (auto& p : config.surfaceNodes) { + Eigen::Vector3f oldPos = p.currentPos; + float displacementValue = noiseFunc(p.basePos); + p.noiseDisplacement = displacementValue; + Eigen::Vector3f normal = p.basePos.normalized(); + p.currentPos = p.basePos + (normal * displacementValue * config.displacementStrength); + + grid.update(oldPos, p.currentPos, p, true, p.originColor, config.voxelSize, true, -2, false, 0.0f, 0.0f, 0.0f); + } + } + + void assignSeeds() { + plates.clear(); + plates.resize(config.numPlates); + float sphereSurfaceArea = 4.0f * M_PI * config.radius * config.radius; + float averageAreaPerPlate = sphereSurfaceArea / config.numPlates; + float minDistance = std::sqrt(averageAreaPerPlate) * 0.4f; + std::vector selectedSeedIndices; + std::uniform_int_distribution distNode(0, config.surfaceNodes.size() - 1); + for (int i = 0; i < config.numPlates; ++i) { + int attempts = 1000; + bool foundValidSeed = false; + int seedid = distNode(rng); + plates[i].plateId = i; + + + while (!foundValidSeed && attempts > 0) { + int seedIndex = distNode(rng); + + bool tooClose = false; + for (int selectedIndex : selectedSeedIndices) { + const auto& existingSeed = config.surfaceNodes[selectedIndex]; + const auto& candidateSeed = config.surfaceNodes[seedIndex]; + + float dot = existingSeed.basePos.normalized().dot(candidateSeed.basePos.normalized()); + float angle = std::acos(std::clamp(dot, -1.0f, 1.0f)); + float distanceOnSphere = angle * config.radius; + + if (distanceOnSphere < minDistance) { + tooClose = true; + break; + } + } + + if (!tooClose || selectedSeedIndices.empty()) { + selectedSeedIndices.push_back(seedIndex); + plates[i].plateId = i; + config.surfaceNodes[seedIndex].plateID = i; + float colorVal = static_cast(seedid) / config.surfaceNodes.size(); + plates[i].debugColor = v3(colorVal,colorVal,colorVal); + foundValidSeed = true; + } + + attempts--; + } + if (!foundValidSeed) { + int seedIndex = distNode(rng); + selectedSeedIndices.push_back(seedIndex); + plates[i].plateId = i; + float colorVal = static_cast(seedIndex) / config.surfaceNodes.size(); + plates[i].debugColor = v3(colorVal,colorVal,colorVal); + config.surfaceNodes[seedIndex].plateID = i; + } + } + + } + + + void buildAdjacencyList() { + TIME_FUNCTION; + for (int i = 0; i < config.surfaceNodes.size(); i++) { + Particle in = config.surfaceNodes[i]; + v3 inn = in.basePos.normalized(); + for (int j = 1; j < config.surfaceNodes.size(); j++) { + if (i == j) { + continue; + } + auto ij = config.surfaceNodes[j]; + if (ij.neighbors.contains(i)){ + in.neighbors[j] = ij.neighbors[i]; + } + + v3 ijn = ij.basePos.normalized(); + float cosangle = inn.dot(ijn); + float angle = std::acos(cosangle); + + in.neighbors[j] = angle; + } + } + } + + void growPlatesRandom() { + TIME_FUNCTION; + } + + void growPlatesCellular() { + TIME_FUNCTION; + + } + + void fixBoundaries() { + TIME_FUNCTION; + } + + void extraplateste() { + + } + + void boundaryStress() { + TIME_FUNCTION; + + } + + void finalizeApplyResults() { + TIME_FUNCTION; + float maxAllowedDisp = config.radius * config.maxElevationRatio; + + for (auto& p : config.surfaceNodes) { + Eigen::Vector3f oldPos = p.currentPos; + grid.update(oldPos, p.currentPos, p, true, plates[p.plateID].debugColor, config.voxelSize, true, -2, false, 0.0f, 0.0f, 0.0f); + } + std::cout << "Finalize apply results completed." << std::endl; + } + +}; + +#endif \ No newline at end of file