diff --git a/tools/editor/editor_app.cpp b/tools/editor/editor_app.cpp index fadfefa8..ad4f61c8 100644 --- a/tools/editor/editor_app.cpp +++ b/tools/editor/editor_app.cpp @@ -401,7 +401,18 @@ void EditorApp::processEvents() { giz.setMode(TransformMode::None); } else if (event.type == SDL_MOUSEBUTTONDOWN) { // Path point capture (river/road tool) - if (ui_.getPathCapture() != EditorUI::PathCapture::None) { + // Alt+click eyedropper in paint mode + if (mode_ == EditorMode::Paint && (SDL_GetModState() & KMOD_ALT)) { + if (terrainEditor_.brush().isActive()) { + std::string picked = texturePainter_.pickTextureAt( + terrainEditor_.brush().getPosition()); + if (!picked.empty()) { + texturePainter_.setActiveTexture(picked); + showToast("Picked: " + picked.substr(picked.rfind('\\') + 1)); + } + } + } + else if (ui_.getPathCapture() != EditorUI::PathCapture::None) { auto ext = window_->getVkContext()->getSwapchainExtent(); rendering::Ray ray = camera_.getCamera().screenToWorldRay( static_cast(event.button.x), @@ -620,23 +631,37 @@ void EditorApp::refreshDirtyChunks() { } void EditorApp::loadADT(const std::string& mapName, int tileX, int tileY) { - std::ostringstream path; - path << "World\\Maps\\" << mapName << "\\" << mapName - << "_" << tileX << "_" << tileY << ".adt"; - - LOG_INFO("Loading ADT: ", path.str()); - - auto adtData = assetManager_->readFile(path.str()); - if (adtData.empty()) { - LOG_ERROR("ADT file not found: ", path.str()); - return; + // Prefer open format (WOT/WHM) if available + for (const char* dir : {"custom_zones", "output"}) { + std::string wotBase = std::string(dir) + "/" + mapName + "/" + mapName + "_" + + std::to_string(tileX) + "_" + std::to_string(tileY); + if (WoweeTerrain::importOpen(wotBase, terrain_) && terrain_.isLoaded()) { + LOG_INFO("Loaded open format terrain: ", wotBase); + showToast("Loaded WOT/WHM: " + mapName); + goto terrainReady; + } } - terrain_ = pipeline::ADTLoader::load(adtData); - if (!terrain_.isLoaded()) { - LOG_ERROR("Failed to parse ADT: ", path.str()); - return; + { + std::ostringstream path; + path << "World\\Maps\\" << mapName << "\\" << mapName + << "_" << tileX << "_" << tileY << ".adt"; + + LOG_INFO("Loading ADT: ", path.str()); + + auto adtData = assetManager_->readFile(path.str()); + if (adtData.empty()) { + LOG_ERROR("ADT file not found: ", path.str()); + return; + } + + terrain_ = pipeline::ADTLoader::load(adtData); + if (!terrain_.isLoaded()) { + LOG_ERROR("Failed to parse ADT: ", path.str()); + return; + } } + terrainReady: // Override internal coords with what we know from the filename // (instanced maps have arbitrary internal coord values) diff --git a/tools/editor/editor_ui.cpp b/tools/editor/editor_ui.cpp index 88535f62..3fcf9e55 100644 --- a/tools/editor/editor_ui.cpp +++ b/tools/editor/editor_ui.cpp @@ -477,6 +477,7 @@ void EditorUI::renderMenuBar(EditorApp& app) { ImGui::Text("Quick Actions:"); ImGui::BulletText("Ctrl+N — new terrain"); ImGui::BulletText("Ctrl+O — load map tile"); + ImGui::BulletText("Alt+Click — eyedropper (paint mode)"); ImGui::BulletText("Middle-drag — orbit camera"); ImGui::Separator(); ImGui::Text("View:"); @@ -1108,6 +1109,17 @@ void EditorUI::renderTexturePaintPanel(EditorApp& app) { if (ImGui::Combo("Paint Mode", &pm, paintModes, 3)) paintMode_ = static_cast(pm); + if (ImGui::Button("Eyedropper (Alt+Click)")) { + auto& brush = app.getTerrainEditor().brush(); + if (brush.isActive()) { + std::string picked = app.getTexturePainter().pickTextureAt(brush.getPosition()); + if (!picked.empty()) { + app.getTexturePainter().setActiveTexture(picked); + app.showToast("Picked: " + picked.substr(picked.rfind('\\') + 1)); + } + } + } + ImGui::Separator(); // Directory filter diff --git a/tools/editor/texture_painter.cpp b/tools/editor/texture_painter.cpp index 4f2e22f3..eb80f55d 100644 --- a/tools/editor/texture_painter.cpp +++ b/tools/editor/texture_painter.cpp @@ -325,7 +325,7 @@ void TexturePainter::gradientBlend(const std::string& tex1, const std::string& t void TexturePainter::scatterPatches(const std::string& texturePath, int count, float minRadius, float maxRadius, uint32_t seed) { if (!terrain_ || texturePath.empty()) return; - uint32_t texId = ensureTextureInList(texturePath); + ensureTextureInList(texturePath); float tileNW_X = (32.0f - static_cast(terrain_->coord.y)) * 533.33333f; float tileNW_Y = (32.0f - static_cast(terrain_->coord.x)) * 533.33333f; @@ -371,5 +371,48 @@ std::vector TexturePainter::erase(const glm::vec3& center, float radius, return modified; } +std::string TexturePainter::pickTextureAt(const glm::vec3& worldPos) const { + if (!terrain_) return ""; + + for (int ci = 0; ci < 256; ci++) { + const auto& chunk = terrain_->chunks[ci]; + if (!chunk.hasHeightMap() || chunk.layers.empty()) continue; + + glm::vec2 uv = worldToChunkUV(ci, worldPos); + if (uv.x < 0 || uv.x > 1 || uv.y < 0 || uv.y > 1) continue; + + if (chunk.layers.size() == 1) { + if (chunk.layers[0].textureId < terrain_->textures.size()) + return terrain_->textures[chunk.layers[0].textureId]; + continue; + } + + int px = std::clamp(static_cast(uv.x * 64.0f), 0, 63); + int py = std::clamp(static_cast(uv.y * 64.0f), 0, 63); + int pixelIdx = py * 64 + px; + + uint32_t bestLayer = 0; + uint8_t bestAlpha = 0; + for (size_t li = 1; li < chunk.layers.size(); li++) { + size_t alphaOffset = (li - 1) * 4096 + pixelIdx; + if (alphaOffset < chunk.alphaMap.size()) { + uint8_t alpha = chunk.alphaMap[alphaOffset]; + if (alpha > bestAlpha) { + bestAlpha = alpha; + bestLayer = static_cast(li); + } + } + } + + if (bestAlpha < 128) bestLayer = 0; + + if (bestLayer < chunk.layers.size() && + chunk.layers[bestLayer].textureId < terrain_->textures.size()) { + return terrain_->textures[chunk.layers[bestLayer].textureId]; + } + } + return ""; +} + } // namespace editor } // namespace wowee diff --git a/tools/editor/texture_painter.hpp b/tools/editor/texture_painter.hpp index 236e2457..e71b6d7e 100644 --- a/tools/editor/texture_painter.hpp +++ b/tools/editor/texture_painter.hpp @@ -42,6 +42,9 @@ public: // Erase a texture layer at the given position std::vector erase(const glm::vec3& center, float radius, float strength, float falloff); + // Pick the dominant texture at a world position (eyedropper) + std::string pickTextureAt(const glm::vec3& worldPos) const; + private: uint32_t ensureTextureInList(const std::string& path); int ensureLayerOnChunk(int chunkIdx, uint32_t textureId);