From 59c6dab2b33613884baa83f563094b1fae05f0c5 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 5 May 2026 07:19:05 -0700 Subject: [PATCH] feat(editor): auto-paint by slope for natural cliff texturing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Auto-Paint by Slope: paints rock texture on steep terrain surfaces with configurable slope threshold (0.1 - 0.9) - Uses per-vertex normal Z component to detect steepness - Alpha blending based on slope gradient for smooth transitions - Workflow: sculpt terrain → recalc normals → auto-paint slope → rock appears naturally on cliffs while flat areas keep their biome texture --- tools/editor/editor_ui.cpp | 11 +++++++++ tools/editor/texture_painter.cpp | 39 ++++++++++++++++++++++++++++++++ tools/editor/texture_painter.hpp | 3 +++ 3 files changed, 53 insertions(+) diff --git a/tools/editor/editor_ui.cpp b/tools/editor/editor_ui.cpp index cc33b186..aa9862fd 100644 --- a/tools/editor/editor_ui.cpp +++ b/tools/editor/editor_ui.cpp @@ -756,6 +756,17 @@ void EditorUI::renderTexturePaintPanel(EditorApp& app) { selectedTexture_.c_str()); // Auto-paint by height + if (ImGui::CollapsingHeader("Auto-Paint by Slope")) { + static float slopeThresh = 0.4f; + ImGui::SliderFloat("Slope Threshold", &slopeThresh, 0.1f, 0.9f); + ImGui::TextColored(ImVec4(0.6f,0.6f,0.6f,1), "Paints rock on steep slopes"); + if (ImGui::Button("Apply Slope Paint", ImVec2(-1, 0))) { + app.getTexturePainter().autoPaintBySlope(slopeThresh, + "Tileset\\Barrens\\BarrensRock01.blp"); + app.showToast("Slope paint applied"); + } + } + if (ImGui::CollapsingHeader("Auto-Paint by Height")) { ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1), "Sets base texture per chunk based on average height"); diff --git a/tools/editor/texture_painter.cpp b/tools/editor/texture_painter.cpp index 8579d874..7e4ed0cd 100644 --- a/tools/editor/texture_painter.cpp +++ b/tools/editor/texture_painter.cpp @@ -194,6 +194,45 @@ void TexturePainter::autoPaintByHeight(const std::vector& bands) { } } +void TexturePainter::autoPaintBySlope(float slopeThreshold, const std::string& steepTexture) { + if (!terrain_) return; + uint32_t steepTexId = ensureTextureInList(steepTexture); + + for (int ci = 0; ci < 256; ci++) { + auto& chunk = terrain_->chunks[ci]; + if (!chunk.hasHeightMap() || chunk.layers.empty()) continue; + + // Compute average slope from normals + float maxSlope = 0.0f; + for (int v = 0; v < 145; v++) { + float nz = static_cast(chunk.normals[v * 3 + 2]) / 127.0f; + float slope = 1.0f - std::abs(nz); + maxSlope = std::max(maxSlope, slope); + } + + if (maxSlope > slopeThreshold) { + // Add steep texture as a layer + int layerIdx = ensureLayerOnChunk(ci, steepTexId); + if (layerIdx > 0) { + size_t off = chunk.layers[layerIdx].offsetMCAL; + if (off + 4096 <= chunk.alphaMap.size()) { + for (int ty = 0; ty < 64; ty++) { + for (int tx = 0; tx < 64; tx++) { + // Sample slope at this texel + int vi = (ty / 8) * 17 + (tx / 8); + if (vi >= 145) vi = 144; + float nz = static_cast(chunk.normals[vi * 3 + 2]) / 127.0f; + float slope = 1.0f - std::abs(nz); + float alpha = std::clamp((slope - slopeThreshold * 0.5f) / (1.0f - slopeThreshold * 0.5f), 0.0f, 1.0f); + chunk.alphaMap[off + ty * 64 + tx] = static_cast(alpha * 255.0f); + } + } + } + } + } + } +} + std::vector TexturePainter::erase(const glm::vec3& center, float radius, float strength, float falloff) { if (!terrain_ || activeTexture_.empty()) return {}; diff --git a/tools/editor/texture_painter.hpp b/tools/editor/texture_painter.hpp index 0ea47290..42eadd06 100644 --- a/tools/editor/texture_painter.hpp +++ b/tools/editor/texture_painter.hpp @@ -21,6 +21,9 @@ public: struct HeightBand { float maxHeight; std::string texturePath; }; void autoPaintByHeight(const std::vector& bands); + // Auto-paint steep slopes with rock texture + void autoPaintBySlope(float slopeThreshold, const std::string& steepTexture); + // Paint the active texture at the given world position // Returns list of modified chunk indices std::vector paint(const glm::vec3& center, float radius, float strength, float falloff);