From 6277800773b5e7e32ecf4f11eb6bdad56efb55b5 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 5 May 2026 08:13:04 -0700 Subject: [PATCH] feat(editor): edge ramp tool for seamless multi-tile connections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Edge Ramp: smoothly transitions tile borders to a target height so adjacent tiles can connect seamlessly - Configurable target height and ramp width (how far in from the edge) - Quadratic blend for smooth start from edge → interior - Essential for multi-tile zone creation: ramp each tile's edges to match its neighbor's border height --- tools/editor/editor_ui.cpp | 12 ++++++++++++ tools/editor/terrain_editor.cpp | 34 +++++++++++++++++++++++++++++++++ tools/editor/terrain_editor.hpp | 3 +++ 3 files changed, 49 insertions(+) diff --git a/tools/editor/editor_ui.cpp b/tools/editor/editor_ui.cpp index b6b8baa7..6f4c2030 100644 --- a/tools/editor/editor_ui.cpp +++ b/tools/editor/editor_ui.cpp @@ -645,6 +645,18 @@ void EditorUI::renderBrushPanel(EditorApp& app) { ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1), "No stamp copied"); } + if (ImGui::CollapsingHeader("Edge Ramp (Multi-tile)")) { + static float rampTarget = 100.0f, rampWidth = 20.0f; + ImGui::SliderFloat("Target Height##ramp", &rampTarget, 0.0f, 500.0f); + ImGui::SliderFloat("Ramp Width##ramp", &rampWidth, 5.0f, 60.0f); + if (ImGui::Button("Ramp Tile Edges", ImVec2(-1, 0))) { + app.getTerrainEditor().rampEdges(rampTarget, rampWidth); + app.showToast("Edges ramped"); + } + ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1), + "Smoothly transitions tile borders to target height\nfor seamless multi-tile connections"); + } + if (ImGui::CollapsingHeader("Thermal Erosion")) { static int erosionIters = 10; static float talusAngle = 40.0f; diff --git a/tools/editor/terrain_editor.cpp b/tools/editor/terrain_editor.cpp index f581bb25..34170277 100644 --- a/tools/editor/terrain_editor.cpp +++ b/tools/editor/terrain_editor.cpp @@ -985,6 +985,40 @@ void TerrainEditor::smoothBeaches(float waterHeight, float beachWidth) { dirty_ = true; } +void TerrainEditor::rampEdges(float targetHeight, float rampWidth) { + if (!terrain_) return; + float relTarget = targetHeight - terrain_->chunks[0].position[2]; + + for (int ci = 0; ci < 256; ci++) { + auto& chunk = terrain_->chunks[ci]; + if (!chunk.hasHeightMap()) continue; + int cx = ci % 16, cy = ci / 16; + + for (int v = 0; v < 145; v++) { + int row = v / 17, col = v % 17; + if (col > 8) continue; + + // Distance to nearest tile edge (in chunk units) + float edgeDistX = std::min(static_cast(cx * 8 + col), + static_cast(128 - cx * 8 - col)) / 128.0f; + float edgeDistY = std::min(static_cast(cy * 8 + row), + static_cast(128 - cy * 8 - row)) / 128.0f; + float edgeDist = std::min(edgeDistX, edgeDistY); + float rampNorm = rampWidth / 128.0f; + + if (edgeDist < rampNorm) { + float t = edgeDist / rampNorm; + float blend = t * t; // smooth start + chunk.heightMap.heights[v] = chunk.heightMap.heights[v] * blend + + relTarget * (1.0f - blend); + } + } + dirtyChunks_.push_back(ci); + } + for (int ci = 0; ci < 256; ci++) stitchEdges(ci); + dirty_ = true; +} + void TerrainEditor::thermalErosion(int iterations, float talusAngle) { if (!terrain_) return; float unitSize = CHUNK_SIZE / 8.0f; diff --git a/tools/editor/terrain_editor.hpp b/tools/editor/terrain_editor.hpp index 1b81ad73..7ad3ca5a 100644 --- a/tools/editor/terrain_editor.hpp +++ b/tools/editor/terrain_editor.hpp @@ -120,6 +120,9 @@ public: // Smooth terrain near water level to create natural beaches void smoothBeaches(float waterHeight, float beachWidth); + // Ramp tile edges to a target height for seamless multi-tile joins + void rampEdges(float targetHeight, float rampWidth); + // Import/export heightmap (raw 16-bit grayscale, 129x129) bool importHeightmap(const std::string& path, float heightScale); bool exportHeightmap(const std::string& path, float heightScale);