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
This commit is contained in:
Kelsi 2026-05-05 07:43:10 -07:00
parent f3846919a4
commit 9be32a6634
3 changed files with 45 additions and 0 deletions

View file

@ -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;

View file

@ -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;

View file

@ -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);