From 0a6e54e8a24ac1f75cae73fb5cc3b48e7cdc741f Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 5 May 2026 08:28:09 -0700 Subject: [PATCH] feat(editor): gradient texture blend for biome transitions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Gradient Blend: smoothly transitions base texture from one biome to another across the entire tile (horizontal or vertical) - Preset buttons: Grass→Sand and Grass→Snow gradients - Creates natural biome transition zones without manual painting - Alpha blending from 0% to 100% across 16 chunks --- tools/editor/editor_ui.cpp | 20 ++++++++++++++++++++ tools/editor/texture_painter.cpp | 32 ++++++++++++++++++++++++++++++++ tools/editor/texture_painter.hpp | 3 +++ 3 files changed, 55 insertions(+) diff --git a/tools/editor/editor_ui.cpp b/tools/editor/editor_ui.cpp index aea9936c..2d834e5c 100644 --- a/tools/editor/editor_ui.cpp +++ b/tools/editor/editor_ui.cpp @@ -897,6 +897,26 @@ void EditorUI::renderTexturePaintPanel(EditorApp& app) { selectedTexture_.c_str()); // Auto-paint by height + if (ImGui::CollapsingHeader("Gradient Blend")) { + static int gradDir = 0; + ImGui::RadioButton("Horizontal##grad", &gradDir, 0); + ImGui::SameLine(); + ImGui::RadioButton("Vertical##grad", &gradDir, 1); + if (ImGui::Button("Grass → Sand Gradient", ImVec2(-1, 0))) { + app.getTexturePainter().gradientBlend( + "Tileset\\Elwynn\\ElwynnGrassBase.blp", + "Tileset\\Tanaris\\TanarisSandBase01.blp", gradDir == 0); + app.showToast("Gradient applied"); + } + if (ImGui::Button("Grass → Snow Gradient", ImVec2(-1, 0))) { + app.getTexturePainter().gradientBlend( + "Tileset\\Elwynn\\ElwynnGrassBase.blp", + "Tileset\\Expansion02\\Dragonblight\\DragonblightFreshSmoothSnowA.blp", gradDir == 0); + app.showToast("Gradient applied"); + } + ImGui::TextColored(ImVec4(0.6f,0.6f,0.6f,1), "Smooth texture transition across tile"); + } + if (ImGui::CollapsingHeader("Auto-Paint by Slope")) { static float slopeThresh = 0.4f; ImGui::SliderFloat("Slope Threshold", &slopeThresh, 0.1f, 0.9f); diff --git a/tools/editor/texture_painter.cpp b/tools/editor/texture_painter.cpp index f3710cff..4d88c7c1 100644 --- a/tools/editor/texture_painter.cpp +++ b/tools/editor/texture_painter.cpp @@ -289,6 +289,38 @@ void TexturePainter::paintAlongPath(const glm::vec3& start, const glm::vec3& end } } +void TexturePainter::gradientBlend(const std::string& tex1, const std::string& tex2, bool horizontal) { + if (!terrain_) return; + uint32_t id1 = ensureTextureInList(tex1); + uint32_t id2 = ensureTextureInList(tex2); + + for (int ci = 0; ci < 256; ci++) { + auto& chunk = terrain_->chunks[ci]; + if (!chunk.hasHeightMap() || chunk.layers.empty()) continue; + int cx = ci % 16, cy = ci / 16; + + // Set base texture to tex1 + chunk.layers[0].textureId = id1; + + // Add tex2 as blended layer + int layerIdx = ensureLayerOnChunk(ci, id2); + if (layerIdx < 0) continue; + size_t off = chunk.layers[layerIdx].offsetMCAL; + if (off + 4096 > chunk.alphaMap.size()) continue; + + for (int ty = 0; ty < 64; ty++) { + for (int tx = 0; tx < 64; tx++) { + float t; + if (horizontal) + t = (cx * 64.0f + tx) / (16.0f * 64.0f); + else + t = (cy * 64.0f + ty) / (16.0f * 64.0f); + chunk.alphaMap[off + ty * 64 + tx] = static_cast(t * 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 029293ed..d5a07f08 100644 --- a/tools/editor/texture_painter.hpp +++ b/tools/editor/texture_painter.hpp @@ -28,6 +28,9 @@ public: void paintAlongPath(const glm::vec3& start, const glm::vec3& end, float width, const std::string& texturePath); + // Gradient blend: transitions base texture from one to another across tile + void gradientBlend(const std::string& tex1, const std::string& tex2, bool horizontal); + // 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);