From 18aa8f06b716e056845bb8bf020f072d74015621 Mon Sep 17 00:00:00 2001 From: Yggdrasil75 Date: Thu, 26 Feb 2026 14:03:12 -0500 Subject: [PATCH] bunch of planet sim changes --- tests/planet.cpp | 349 +++++++++++++++++++++++++++++++++++++++++++- util/sim/planet.hpp | 141 +++++++++++++----- 2 files changed, 446 insertions(+), 44 deletions(-) diff --git a/tests/planet.cpp b/tests/planet.cpp index c3b7712..1b537bb 100644 --- a/tests/planet.cpp +++ b/tests/planet.cpp @@ -34,6 +34,29 @@ private: float rotationRadius = 2500; float angle = 0.0f; const float ω = (std::pow(M_PI, 2) / 30) / 10; + bool tectonicGenned = false; + bool doFixPlates = true; + bool platesUseCellular = false; + + enum class DebugColorMode { + BASE, + PLATES, + NOISE, + RESERVED + }; + DebugColorMode currentColorMode = DebugColorMode::BASE; + + enum class DebugMapMode { + NONE, + BASE, + NOISE, + TECTONIC, + TECTONICCOLOR, + CURRENT + }; + DebugMapMode currentMapMode = DebugMapMode::NONE; + GLuint mapTexture = 0; + frame mapFrame; public: planetSimUI() { @@ -48,11 +71,28 @@ public: if (textu != 0) { glDeleteTextures(1, &textu); } + if (mapTexture != 0) { + glDeleteTextures(1, &mapTexture); + } sim.grid.clear(); } void renderUI(GLFWwindow* window) { + handleCameraControls(window); 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); + ImGui::TableNextColumn(); + renderControlsPanel(); + ImGui::TableNextColumn(); + renderPreviewPanel(); + ImGui::EndTable(); + } + ImGui::End(); + } + + void handleCameraControls(GLFWwindow* window) { if (orbitEquator) { angle += cam.rotationSpeed * deltaTime * ω; @@ -75,6 +115,10 @@ public: if (keyStates[GLFW_KEY_X]) cam.moveDown(deltaTime); if (keyStates[GLFW_KEY_Q]) cam.rotateYaw(deltaTime); if (keyStates[GLFW_KEY_R]) cam.rotateYaw(-deltaTime); + } + + void renderControlsPanel() { + ImGui::BeginChild("ControlsScroll", ImVec2(0, 0), true); if (ImGui::CollapsingHeader("Base Configuration", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::DragFloat("Radius", &sim.config.radius, 1.0f, 10.0f, 10000.0f); @@ -86,9 +130,11 @@ public: if (ImGui::Button("1. Generate Fib Sphere", ImVec2(-1, 40))) { sim.generateFibSphere(); + applyDebugColorMode(); } ImGui::Text("Current Step: %d", sim.config.currentStep); ImGui::Text("Nodes: %zu", sim.config.surfaceNodes.size()); + ImGui::InputFloat("Noise strength", &sim.config.noiseStrength, 0.01, 1, "%.4f"); } if (ImGui::CollapsingHeader("Physics Parameters")) { @@ -100,19 +146,115 @@ public: 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); + ImGui::DragFloat("Max Elevation Ratio", &sim.config.maxElevationRatio, 1.0f, 0.0f, 1.0f); + ImGui::Checkbox("Fix Boundaries", &doFixPlates); + ImGui::Checkbox("use Cellular", &platesUseCellular); if (ImGui::Button("2. Simulate Tectonics", ImVec2(-1, 40))) { simulateTectonics(); } } - if (ImGui::CollapsingHeader("Camera Controls")) { + if (ImGui::CollapsingHeader("Celestial Bodies")) { + ///TODO: add controls for moon, star. + } + + if (ImGui::CollapsingHeader("Fillings")) { + if (ImGui::Button("Interpolate surface", ImVec2(-1, 40))) { + interpolateSurface(); + } + if (ImGui::Button("Fill Planet", ImVec2(-1, 40))) { + fillPlanet(); + } + } + + if (ImGui::CollapsingHeader("Debug Views")) { + ImGui::Text("3D Planet Color Mode:"); + bool colorChanged = false; + if (ImGui::RadioButton("Base Color", currentColorMode == DebugColorMode::BASE)) { + currentColorMode = DebugColorMode::BASE; + colorChanged = true; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Plates", currentColorMode == DebugColorMode::PLATES)) { + currentColorMode = DebugColorMode::PLATES; + colorChanged = true; + } + + if (ImGui::RadioButton("Noise", currentColorMode == DebugColorMode::NOISE)) { + currentColorMode = DebugColorMode::NOISE; + colorChanged = true; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Reserved", currentColorMode == DebugColorMode::RESERVED)) { + currentColorMode = DebugColorMode::RESERVED; + colorChanged = true; + } + + if (colorChanged) { + applyDebugColorMode(); + } + + ImGui::Separator(); + ImGui::Text("2D Height Map Mode:"); + bool mapChanged = false; + + if (ImGui::RadioButton("None", currentMapMode == DebugMapMode::NONE)) { + currentMapMode = DebugMapMode::NONE; + mapChanged = true; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Base Pos", currentMapMode == DebugMapMode::BASE)) { + currentMapMode = DebugMapMode::BASE; + mapChanged = true; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Noise Pos", currentMapMode == DebugMapMode::NOISE)) { + currentMapMode = DebugMapMode::NOISE; + mapChanged = true; + } + + if (!tectonicGenned) ImGui::BeginDisabled(); + + ImGui::SameLine(); + if (ImGui::RadioButton("Tectonic Pos", currentMapMode == DebugMapMode::TECTONIC)) { + currentMapMode = DebugMapMode::TECTONIC; + mapChanged = true; + } + + if (ImGui::RadioButton("Tectonic Color", currentMapMode == DebugMapMode::TECTONICCOLOR)) { + currentMapMode = DebugMapMode::TECTONICCOLOR; + mapChanged = true; + } + ImGui::SameLine(); + if (!tectonicGenned) ImGui::EndDisabled(); + + if (ImGui::RadioButton("Current Pos", currentMapMode == DebugMapMode::CURRENT)) { + currentMapMode = DebugMapMode::CURRENT; + mapChanged = true; + } + + if (ImGui::Button("Refresh Map", ImVec2(-1, 24))) { + mapChanged = true; + generateDebugMap(currentMapMode); + } + + if (mapChanged && currentMapMode != DebugMapMode::NONE) { + generateDebugMap(currentMapMode); + } + + if (currentMapMode != DebugMapMode::NONE && mapTexture != 0) { + float availWidth = ImGui::GetContentRegionAvail().x; + ImGui::Image((void*)(intptr_t)mapTexture, ImVec2(availWidth, availWidth * 0.5f)); + } + } + + if (ImGui::CollapsingHeader("Camera Controls", 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); @@ -129,7 +271,12 @@ public: if (ImGui::Button(orbitEquator ? "Stop Equator" : "Orbit Equator")) orbitEquator = !orbitEquator; } + 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(); @@ -137,7 +284,179 @@ public: ImGui::Image((void*)(intptr_t)textu, ImVec2(availWidth, availWidth / aspect)); } - ImGui::End(); + ImGui::EndChild(); + } + + void applyDebugColorMode() { + if (sim.config.surfaceNodes.empty()) return; + + float minNoise = std::numeric_limits::max(); + float maxNoise = std::numeric_limits::lowest(); + int minSub = std::numeric_limits::max(); + int maxSub = std::numeric_limits::lowest(); + + for (const auto& p : sim.config.surfaceNodes) { + if (p.noiseDisplacement < minNoise) minNoise = p.noiseDisplacement; + if (p.noiseDisplacement > maxNoise) maxNoise = p.noiseDisplacement; + } + + for (auto& p : sim.config.surfaceNodes) { + v3 color = p.originColor; + + switch (currentColorMode) { + case DebugColorMode::PLATES: + if (p.plateID != -1 && p.plateID < sim.plates.size()) { + color = sim.plates[p.plateID].debugColor; + } else { + color = v3(0.5f, 0.5f, 0.5f); + } + break; + case DebugColorMode::NOISE: { + float t = 0.5f; + if (maxNoise > minNoise) t = (p.noiseDisplacement - minNoise) / (maxNoise - minNoise); + color = v3(t, t, t); + break; + } + case DebugColorMode::BASE: + default: + color = p.originColor; + break; + } + + sim.grid.update(p.currentPos, p.currentPos, p, true, color, sim.config.voxelSize, true, -2, false, 0.0f, 0.0f, 0.0f); + } + for (auto& p : sim.config.interpolatedNodes) { + v3 color = p.originColor; + + switch (currentColorMode) { + case DebugColorMode::PLATES: + if (p.plateID != -1 && p.plateID < sim.plates.size()) { + color = sim.plates[p.plateID].debugColor; + } else { + color = v3(0.5f, 0.5f, 0.5f); + } + break; + case DebugColorMode::NOISE: { + float t = 0.5f; + if (maxNoise > minNoise) t = (p.noiseDisplacement - minNoise) / (maxNoise - minNoise); + color = v3(t, t, t); + break; + } + case DebugColorMode::BASE: + default: + color = p.originColor; + break; + } + + sim.grid.update(p.currentPos, p.currentPos, p, true, color, sim.config.voxelSize, true, -2, false, 0.0f, 0.0f, 0.0f); + } + } + + void generateDebugMap(DebugMapMode mode) { + if (mode == DebugMapMode::NONE || sim.config.surfaceNodes.empty()) return; + + int w = 512; + int h = 348; + std::vector depths(w * h, -1.0f); + float minD = std::numeric_limits::max(); + float maxD = std::numeric_limits::lowest(); + + for (const auto& p : sim.config.surfaceNodes) { + v3 pos; + switch(mode) { + case DebugMapMode::BASE: + pos = p.originalPos; + break; + case DebugMapMode::NOISE: + pos = p.noisePos; + break; + case DebugMapMode::TECTONIC: + pos = p.tectonicPos; + break; + case DebugMapMode::CURRENT: + default: + pos = p.currentPos; + break; + } + float d = pos.norm(); + if (d < minD) minD = d; + if (d > maxD) maxD = d; + } + + for (const auto& p : sim.config.surfaceNodes) { + v3 pos; + switch(mode) { + case DebugMapMode::BASE: + pos = p.originalPos; + break; + case DebugMapMode::NOISE: + pos = p.noisePos; + break; + case DebugMapMode::TECTONIC: + pos = p.tectonicPos; + break; + case DebugMapMode::TECTONICCOLOR: + pos = sim.plates[p.plateID].debugColor; + break; + case DebugMapMode::CURRENT: + default: + pos = p.currentPos; + break; + } + + float d = pos.norm(); + v3 n = p.originalPos.normalized(); + + float u = 0.5f + std::atan2(n.z(), n.x()) / (2.0f * static_cast(M_PI)); + float v = 0.5f - std::asin(n.y()) / static_cast(M_PI); + + int px = std::clamp(static_cast(u * w), 0, w - 1); + int py = std::clamp(static_cast(v * h), 0, h - 1); + + float normalizedD = (maxD > minD) ? (d - minD) / (maxD - minD) : 0.5f; + + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) { + int nx = px + dx; + int ny = py + dy; + + if (nx < 0) nx += w; + if (nx >= w) nx -= w; + + if (ny >= 0 && ny < h) { + int idx = ny * w + nx; + if (depths[idx] < 0.0f || normalizedD > depths[idx]) { + depths[idx] = normalizedD; + } + } + } + } + } + + for (int i = 0; i < w * h; i++) { + if (depths[i] < 0.0f) depths[i] = 0.0f; + } + + std::vector pixels(w * h * 3); + for (int i = 0; i < w * h; i++) { + uint8_t val = static_cast(depths[i] * 255.0f); + pixels[i * 3 + 0] = val; + pixels[i * 3 + 1] = val; + pixels[i * 3 + 2] = val; + } + + mapFrame = frame(w, h, frame::colormap::RGB); + mapFrame.setData(pixels); + + if (mapTexture == 0) { + glGenTextures(1, &mapTexture); + } + + glBindTexture(GL_TEXTURE_2D, mapTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, mapFrame.getData().data()); } void applyNoise(const NoisePreviewState& noiseState) { @@ -159,6 +478,7 @@ public: return vYZ * blend.x() + vXZ * blend.y() + vXY * blend.z(); }; sim._applyNoise(triplanarNoise); + applyDebugColorMode(); } void livePreview() { @@ -196,13 +516,30 @@ public: } void simulateTectonics() { + + currentColorMode = DebugColorMode::PLATES; + sim.assignSeeds(); sim.buildAdjacencyList(); - // sim.growPlatesCellular(); - sim.growPlatesRandom(); - //sim.fixBoundaries(); + if (platesUseCellular) { + sim.growPlatesCellular(); + } else sim.growPlatesRandom(); + if (doFixPlates) sim.fixBoundaries(); sim.extraplateste(); + sim.boundaryStress(); sim.finalizeApplyResults(); + + applyDebugColorMode(); + tectonicGenned = true; + if(currentMapMode != DebugMapMode::NONE) generateDebugMap(currentMapMode); + } + + void interpolateSurface() { + sim.interpolateSurface(); + } + + void fillPlanet() { + sim.fillPlanet(); } }; diff --git a/util/sim/planet.hpp b/util/sim/planet.hpp index 5454f16..b75cb03 100644 --- a/util/sim/planet.hpp +++ b/util/sim/planet.hpp @@ -35,8 +35,11 @@ enum class PlateType { struct Particle { float noiseDisplacement = 0.0f; int plateID = -1; - Eigen::Vector3f basePos; + Eigen::Vector3f originalPos; + Eigen::Vector3f noisePos; + Eigen::Vector3f tectonicPos; Eigen::Vector3f currentPos; + float plateDisplacement = 0.0f; float temperature = -1; float water = -1; @@ -56,9 +59,17 @@ struct Particle { float mass; bool isStatic = false; float soundSpeed = 100.0f; + float temperature = 0.0f; + float water = 0.0f; + float sandcontent = 0.0f; + float siltcontent = 0.0f; + float claycontent = 0.0f; + float rockcontent = 0.0f; + float metalcontent = 0.0f; + std::unordered_map neighbors; - std::vector nearNeighbors; // Switched to vector to prevent key collision & drops + std::vector nearNeighbors; }; struct planetConfig { @@ -73,16 +84,18 @@ struct planetConfig { float displacementStrength = 200.0f; std::vector surfaceNodes; + std::vector interpolatedNodes; 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 gridSizeCube = 65536; //absolute max size for all nodes + float gridSizeCubeMin = 16384; //max size, if something leaves this, then it probably needs to be purged before it leaves the grid and becomes lost float SMOOTHING_RADIUS = 1024.0f; float REST_DENSITY = 0.00005f; float TIMESTEP = 0.016f; @@ -173,7 +186,9 @@ public: v3 dir(x, y, z); v3 pos = config.center + dir * config.radius; Particle pt; - pt.basePos = pos; + pt.originalPos = pos; + pt.noisePos = pos; + pt.tectonicPos = pos; pt.currentPos = pos; pt.originColor = config.color; pt.noiseDisplacement = 0.0f; @@ -189,10 +204,11 @@ public: inline void _applyNoise(std::function noiseFunc) { for (auto& p : config.surfaceNodes) { Eigen::Vector3f oldPos = p.currentPos; - float displacementValue = noiseFunc(p.basePos); + float displacementValue = noiseFunc(p.originalPos); p.noiseDisplacement = displacementValue; - Eigen::Vector3f normal = p.basePos.normalized(); - p.currentPos = p.basePos + (normal * displacementValue * config.displacementStrength); + Eigen::Vector3f normal = p.originalPos.normalized(); + p.noisePos = p.originalPos + (normal * displacementValue * config.noiseStrength); + p.currentPos = p.noisePos; grid.update(oldPos, p.currentPos, p, true, p.originColor, config.voxelSize, true, -2, false, 0.0f, 0.0f, 0.0f); } @@ -220,7 +236,7 @@ public: const auto& existingSeed = config.surfaceNodes[selectedIndex]; const auto& candidateSeed = config.surfaceNodes[seedIndex]; - float dot = existingSeed.basePos.normalized().dot(candidateSeed.basePos.normalized()); + float dot = existingSeed.originalPos.normalized().dot(candidateSeed.originalPos.normalized()); float angle = std::acos(std::clamp(dot, -1.0f, 1.0f)); float distanceOnSphere = angle * config.radius; @@ -282,7 +298,7 @@ public: std::vector normPos(numNodes); #pragma omp parallel for schedule(static) for (int i = 0; i < numNodes; i++) { - normPos[i] = config.surfaceNodes[i].basePos.normalized(); + normPos[i] = config.surfaceNodes[i].originalPos.normalized(); } #pragma omp parallel for schedule(static) @@ -449,7 +465,7 @@ public: int closestPlate = 0; float minDist = std::numeric_limits::max(); for (int p = 0; p < config.numPlates; p++) { - float d = (config.surfaceNodes[i].basePos - plates[p].plateEulerPole.basePos).norm(); + float d = (config.surfaceNodes[i].originalPos - plates[p].plateEulerPole.originalPos).norm(); if (d < minDist) { minDist = d; closestPlate = p; @@ -519,7 +535,7 @@ public: for (int nIdx : plates[i].assignedNodes) { sumElevation += config.surfaceNodes[nIdx].currentPos.norm(); - centroid += config.surfaceNodes[nIdx].basePos; + centroid += config.surfaceNodes[nIdx].originalPos; } if (!plates[i].assignedNodes.empty()) { @@ -528,18 +544,18 @@ public: float maxSpread = 0.0f; for (int nIdx : plates[i].assignedNodes) { - float d = (config.surfaceNodes[nIdx].basePos - centroid).norm(); + float d = (config.surfaceNodes[nIdx].originalPos - centroid).norm(); if (d > maxSpread) maxSpread = d; } - float distToCentroid = (plates[i].plateEulerPole.basePos - centroid).norm(); + float distToCentroid = (plates[i].plateEulerPole.originalPos - centroid).norm(); if (distToCentroid > maxSpread * 0.6f) { int bestNodeIdx = plates[i].assignedNodes[0]; float minDistToCentroid = std::numeric_limits::max(); for (int nIdx : plates[i].assignedNodes) { - float d = (config.surfaceNodes[nIdx].basePos - centroid).norm(); + float d = (config.surfaceNodes[nIdx].originalPos - centroid).norm(); if (d < minDistToCentroid) { minDistToCentroid = d; bestNodeIdx = nIdx; @@ -555,7 +571,7 @@ public: Eigen::Vector3f randomDir(distFloat(rng) - 0.5f, distFloat(rng) - 0.5f, distFloat(rng) - 0.5f); randomDir.normalize(); - Eigen::Vector3f poleDir = plates[i].plateEulerPole.basePos.normalized(); + Eigen::Vector3f poleDir = plates[i].plateEulerPole.originalPos.normalized(); plates[i].direction = (randomDir - poleDir * randomDir.dot(poleDir)).normalized(); plates[i].angularVelocity = distFloat(rng) * 0.1f + 0.02f; @@ -596,7 +612,7 @@ public: std::vector ω(config.numPlates); for (int i = 0; i < config.numPlates; i++) { - ω[i] = plates[i].plateEulerPole.basePos.normalized().cross(plates[i].direction) * plates[i].angularVelocity; + ω[i] = plates[i].plateEulerPole.originalPos.normalized().cross(plates[i].direction) * plates[i].angularVelocity; } std::uniform_real_distribution dist(-1.0f, 1.0f); @@ -609,7 +625,7 @@ public: int myPlate = config.surfaceNodes[i].plateID; if (myPlate == -1) continue; - Eigen::Vector3f myPos = config.surfaceNodes[i].basePos.normalized(); + Eigen::Vector3f myPos = config.surfaceNodes[i].originalPos.normalized(); Eigen::Vector3f myVel = ω[myPlate].cross(myPos); float localStress = 0.0f; @@ -620,7 +636,7 @@ public: int nPlate = config.surfaceNodes[nIdx].plateID; if (nPlate != -1 && myPlate != nPlate) { boundaryCount++; - Eigen::Vector3f nPos = config.surfaceNodes[nIdx].basePos.normalized(); + Eigen::Vector3f nPos = config.surfaceNodes[nIdx].originalPos.normalized(); Eigen::Vector3f nVel = ω[nPlate].cross(nPos); Eigen::Vector3f relVel = nVel - myVel; @@ -672,8 +688,8 @@ public: float noiseVal = dist(rng) * nodeNoise[i]; - Eigen::Vector3f normal = p.basePos.normalized(); - p.currentPos += normal * (p.plateDisplacement + noiseVal); + Eigen::Vector3f normal = p.originalPos.normalized(); + p.tectonicPos = p.noisePos + (normal * (p.plateDisplacement + noiseVal)); } } @@ -683,23 +699,12 @@ public: 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); + p.currentPos = p.tectonicPos; + grid.update(oldPos, p.currentPos, p, true, p.originColor, config.voxelSize, true, -2, false, 0.0f, 0.0f, 0.0f); } std::cout << "Finalize apply results completed." << std::endl; } - void interpolateSurface() { - TIME_FUNCTION; - ///TODO: go through all surface nodes and fill in gaps between each and their near neighbors until the surface has no holes - //lets keep these separate so they can be removed when redoing any prior steps - } - - void fillPlanet() { - TIME_FUNCTION; - ///TODO: completely fill the planet, interpolating the entire planet. - //same as interpolatesurface, these should be kept separate. but since they will probably be bigger than a vector I dont know how. - } - void addStar() { ///TODO: add a star at roughly earth distance scaled based on planet radius. } @@ -707,6 +712,70 @@ public: void addMoon() { ///TODO: using planetConfig, add moon(s). } + + void stretchPlanet() { + ///TODO: simulate millenia of gravitational stretching by nearby celestial bodies by squeezing the planet slightly at its poles + } + + void interpolateSurface() { + TIME_FUNCTION; + std::unordered_map processedEdges; + size_t counter = 0; + + for (int i = 0; i < config.surfaceNodes.size(); i++) { + Particle& p1 = config.surfaceNodes[i]; + + for (int j : p1.nearNeighbors) { + if (i >= j) continue; + + uint64_t edgeKey = ((uint64_t)i << 32) | (uint32_t)j; + if (processedEdges[edgeKey]) continue; + processedEdges[edgeKey] = true; + + Particle& p2 = config.surfaceNodes[j]; + float dist = (p1.currentPos - p2.currentPos).norm(); + + // If nodes are too far apart, fill the gap + if (dist > config.voxelSize) { + int steps = static_cast(dist / config.voxelSize); + for (int step = 1; step <= steps; step++) { + float t = static_cast(step) / (steps + 1); + + Particle newPt; + newPt.surface = true; + newPt.plateID = (t < 0.5f) ? p1.plateID : p2.plateID; + newPt.originColor = (t < 0.5f) ? p1.originColor : p2.originColor; + + // Spherically interpolate base positions + Eigen::Vector3f baseP1 = p1.originalPos.normalized(); + Eigen::Vector3f baseP2 = p2.originalPos.normalized(); + + // SLERP (Spherical Linear Interpolation) for perfect curves + float dot = std::clamp(baseP1.dot(baseP2), -1.0f, 1.0f); + float theta = std::acos(dot); + Eigen::Vector3f interpBase = ((std::sin((1.0f - t) * theta) / std::sin(theta)) * baseP1 + + (std::sin(t * theta) / std::sin(theta)) * baseP2).normalized(); + + newPt.originalPos = interpBase * config.radius; + newPt.noisePos = p1.noisePos * (1.0f - t) + p2.noisePos * t; + newPt.tectonicPos = p1.tectonicPos * (1.0f - t) + p2.tectonicPos * t; + newPt.currentPos = p1.currentPos * (1.0f - t) + p2.currentPos * t; // Linear for height + + grid.set(newPt, newPt.currentPos, true, newPt.originColor, config.voxelSize, true, 1, 2 /*subid 2 for interpolated*/, false, 0.0f, 0.0f, 0.0f); + config.interpolatedNodes.emplace_back(newPt); + counter++; + } + } + } + } + std::cout << "Interpolated " << counter << " surface gaps." << std::endl; + } + + void fillPlanet() { + TIME_FUNCTION; + ///TODO: completely fill the planet, interpolating the entire planet. + //same as interpolatesurface, these should be kept separate. but since they will probably be bigger than a vector I dont know how. + } void simulateImpacts() { TIME_FUNCTION; @@ -716,10 +785,6 @@ public: // they should be spawned going in random directions that are roughly towards the planet. //the gravity portion should be turned off when this is done. } - - void stretchPlanet() { - ///TODO: simulate millenia of gravitational stretching by nearby celestial bodies by squeezing the planet slightly at its poles - } void erosion() { ///TODO: simulate erosion by spawning many nodes all over the surface one at a time and then pulling them towards the lowest neighboring points. reducing height from source as it flows downhill and increasing at bottom.