From 9be32a66345e0ff65970112f55776a9c654a97e6 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 5 May 2026 07:43:10 -0700 Subject: [PATCH] feat(editor): terrain terrace/step generator for layered landscapes - Terrace tool: quantizes terrain heights into N flat shelves (like rice paddies, cliff shelves, or stepped pyramids) - Configurable step count (2-20) - Finds actual height range and divides evenly - Auto-stitches chunk edges after terracing - Useful for creating tiered arenas, agricultural zones, or stylized Meso-American terrain --- tools/editor/editor_ui.cpp | 10 ++++++++++ tools/editor/terrain_editor.cpp | 32 ++++++++++++++++++++++++++++++++ tools/editor/terrain_editor.hpp | 3 +++ 3 files changed, 45 insertions(+) diff --git a/tools/editor/editor_ui.cpp b/tools/editor/editor_ui.cpp index 0c88cf27..58646c91 100644 --- a/tools/editor/editor_ui.cpp +++ b/tools/editor/editor_ui.cpp @@ -603,6 +603,16 @@ void EditorUI::renderBrushPanel(EditorApp& app) { ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1), "No stamp copied"); } + if (ImGui::CollapsingHeader("Terrace / Steps")) { + static int terraceSteps = 6; + ImGui::SliderInt("Steps##terrace", &terraceSteps, 2, 20); + if (ImGui::Button("Apply Terracing", ImVec2(-1, 0))) { + app.getTerrainEditor().terraceHeights(terraceSteps); + app.showToast("Terrain terraced"); + } + ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1), "Quantizes heights into flat shelves"); + } + if (ImGui::CollapsingHeader("Canyon Generator")) { static float canyonWidth = 15.0f, canyonDepth = 20.0f; static int canyonSeed = 1; diff --git a/tools/editor/terrain_editor.cpp b/tools/editor/terrain_editor.cpp index f165e910..a949458f 100644 --- a/tools/editor/terrain_editor.cpp +++ b/tools/editor/terrain_editor.cpp @@ -860,6 +860,38 @@ void TerrainEditor::createHill(const glm::vec3& center, float radius, float heig dirty_ = true; } +void TerrainEditor::terraceHeights(int steps) { + if (!terrain_ || steps < 2) return; + + // Find height range + float minH = 1e30f, maxH = -1e30f; + for (int ci = 0; ci < 256; ci++) { + auto& chunk = terrain_->chunks[ci]; + if (!chunk.hasHeightMap()) continue; + for (int v = 0; v < 145; v++) { + float h = chunk.position[2] + chunk.heightMap.heights[v]; + minH = std::min(minH, h); + maxH = std::max(maxH, h); + } + } + float range = maxH - minH; + if (range < 1.0f) return; + float stepSize = range / steps; + + for (int ci = 0; ci < 256; ci++) { + auto& chunk = terrain_->chunks[ci]; + if (!chunk.hasHeightMap()) continue; + for (int v = 0; v < 145; v++) { + float absH = chunk.position[2] + chunk.heightMap.heights[v]; + float quantized = std::floor((absH - minH) / stepSize) * stepSize + minH; + chunk.heightMap.heights[v] = quantized - chunk.position[2]; + } + dirtyChunks_.push_back(ci); + } + for (int ci = 0; ci < 256; ci++) stitchEdges(ci); + dirty_ = true; +} + void TerrainEditor::createCanyon(float width, float depth, uint32_t seed) { if (!terrain_) return; diff --git a/tools/editor/terrain_editor.hpp b/tools/editor/terrain_editor.hpp index 78086b23..04925140 100644 --- a/tools/editor/terrain_editor.hpp +++ b/tools/editor/terrain_editor.hpp @@ -99,6 +99,9 @@ public: // Create a winding canyon across the tile void createCanyon(float width, float depth, uint32_t seed); + // Terrace/quantize heights into N steps + void terraceHeights(int steps); + // Import/export heightmap (raw 16-bit grayscale, 129x129) bool importHeightmap(const std::string& path, float heightScale); bool exportHeightmap(const std::string& path, float heightScale);