From 6de6da766d2d41509711def025e10add601e9f03 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 5 May 2026 08:16:40 -0700 Subject: [PATCH] feat(editor): auto-texture roads with cobblestone on flatten - paintAlongPath(): paints a texture along a line segment with configurable width and quadratic edge falloff - Road mode now automatically applies Elwynn cobblestone texture when flattening a road path (flatten + texture in one operation) - Quick distance check skips chunks far from the path for performance - Alpha blending uses max() so overlapping paths don't wash out --- tools/editor/editor_ui.cpp | 10 ++++-- tools/editor/texture_painter.cpp | 56 ++++++++++++++++++++++++++++++++ tools/editor/texture_painter.hpp | 4 +++ 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/tools/editor/editor_ui.cpp b/tools/editor/editor_ui.cpp index 0bb54305..8895f7e5 100644 --- a/tools/editor/editor_ui.cpp +++ b/tools/editor/editor_ui.cpp @@ -607,11 +607,15 @@ void EditorUI::renderBrushPanel(EditorApp& app) { ImGui::SameLine(); if (ImGui::Button("Set End + Apply##path", ImVec2(140, 0)) && brush4.isActive() && pathStartSet) { pathEnd = brush4.getPosition(); - if (pathMode == 0) + if (pathMode == 0) { app.getTerrainEditor().carveRiver(pathStart, pathEnd, pathWidth, pathDepth); - else + app.showToast("River carved"); + } else { app.getTerrainEditor().flattenRoad(pathStart, pathEnd, pathWidth); - app.showToast(pathMode == 0 ? "River carved" : "Road flattened"); + app.getTexturePainter().paintAlongPath(pathStart, pathEnd, pathWidth, + "Tileset\\Elwynn\\ElwynnCobblestoneBase.blp"); + app.showToast("Road flattened + textured"); + } pathStartSet = false; } if (pathStartSet) diff --git a/tools/editor/texture_painter.cpp b/tools/editor/texture_painter.cpp index 7e4ed0cd..f3710cff 100644 --- a/tools/editor/texture_painter.cpp +++ b/tools/editor/texture_painter.cpp @@ -233,6 +233,62 @@ void TexturePainter::autoPaintBySlope(float slopeThreshold, const std::string& s } } +void TexturePainter::paintAlongPath(const glm::vec3& start, const glm::vec3& end, + float width, const std::string& texturePath) { + if (!terrain_ || texturePath.empty()) return; + uint32_t texId = ensureTextureInList(texturePath); + glm::vec2 lineStart(start.x, start.y); + glm::vec2 lineEnd(end.x, end.y); + glm::vec2 lineDir = glm::normalize(lineEnd - lineStart); + float lineLen = glm::length(lineEnd - lineStart); + + 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; + float tileNW_X = (32.0f - static_cast(terrain_->coord.y)) * 533.33333f; + float tileNW_Y = (32.0f - static_cast(terrain_->coord.x)) * 533.33333f; + float chunkCenterX = tileNW_X - (cy + 0.5f) * (533.33333f / 16.0f); + float chunkCenterY = tileNW_Y - (cx + 0.5f) * (533.33333f / 16.0f); + + // Quick distance check + glm::vec2 cc(chunkCenterX, chunkCenterY); + glm::vec2 toCC = cc - lineStart; + float proj = glm::dot(toCC, lineDir); + proj = std::clamp(proj, 0.0f, lineLen); + glm::vec2 closest = lineStart + lineDir * proj; + if (glm::length(cc - closest) > width + 40.0f) continue; + + int layerIdx = ensureLayerOnChunk(ci, texId); + if (layerIdx < 0) continue; + + size_t alphaOffset = chunk.layers[layerIdx].offsetMCAL; + if (alphaOffset + 4096 > chunk.alphaMap.size()) continue; + + float texelSize = (533.33333f / 16.0f) / 64.0f; + for (int ty = 0; ty < 64; ty++) { + for (int tx = 0; tx < 64; tx++) { + float wx = tileNW_X - cy * (533.33333f / 16.0f) - (ty + 0.5f) * texelSize; + float wy = tileNW_Y - cx * (533.33333f / 16.0f) - (tx + 0.5f) * texelSize; + glm::vec2 p(wx, wy); + glm::vec2 toP = p - lineStart; + float t = glm::dot(toP, lineDir); + t = std::clamp(t, 0.0f, lineLen); + glm::vec2 near = lineStart + lineDir * t; + float dist = glm::length(p - near); + if (dist < width) { + float falloff = 1.0f - (dist / width); + falloff = falloff * falloff; + uint8_t alpha = static_cast(std::min(255.0f, falloff * 255.0f)); + chunk.alphaMap[alphaOffset + ty * 64 + tx] = std::max( + chunk.alphaMap[alphaOffset + ty * 64 + tx], alpha); + } + } + } + } +} + 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 42eadd06..029293ed 100644 --- a/tools/editor/texture_painter.hpp +++ b/tools/editor/texture_painter.hpp @@ -24,6 +24,10 @@ public: // Auto-paint steep slopes with rock texture void autoPaintBySlope(float slopeThreshold, const std::string& steepTexture); + // Paint a texture along a line (for roads/paths after flattening) + void paintAlongPath(const glm::vec3& start, const glm::vec3& end, + float width, const std::string& texturePath); + // 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);