From 4b3375ac446a8aeef5ea317de3e7c042a4b07cb4 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 01:11:47 -0700 Subject: [PATCH] feat(editor): export NPC/M2 model textures as PNG with the zone TextureExporter::collectUsedTextures only picked up terrain textures, so exported zones were missing every texture referenced by NPC creature models and placed M2 doodads. Added collectM2Textures() and unified the export collection to include terrain + all referenced M2 paths, so the rendered zone is fully self-contained in the PNG/WOM open formats. --- tools/editor/editor_app.cpp | 31 +++++++++++++++++++++++++------ tools/editor/texture_exporter.cpp | 25 +++++++++++++++++++++++++ tools/editor/texture_exporter.hpp | 5 +++++ 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/tools/editor/editor_app.cpp b/tools/editor/editor_app.cpp index dc88a37d..3e037cc7 100644 --- a/tools/editor/editor_app.cpp +++ b/tools/editor/editor_app.cpp @@ -1080,12 +1080,31 @@ void EditorApp::exportZone(const std::string& outputDir) { LOG_INFO("Converted ", convertedWMOs.size(), " WMO buildings to WOB"); } - // Export used textures as PNG (open format replacement for BLP) - auto usedTextures = TextureExporter::collectUsedTextures(terrain_); - if (!usedTextures.empty()) { - int exported = TextureExporter::exportTexturesAsPng( - assetManager_.get(), usedTextures, base + "/textures"); - LOG_INFO("Exported ", exported, " textures as PNG"); + // Export used textures as PNG (open format replacement for BLP). + // Includes terrain textures plus textures referenced by every placed M2/NPC model + // so the exported zone has every texture it needs to render without game data. + std::vector usedTextures; + { + std::unordered_set uniq; + for (auto& t : TextureExporter::collectUsedTextures(terrain_)) uniq.insert(std::move(t)); + std::unordered_set visitedM2; + auto addM2Tex = [&](const std::string& m2Path) { + if (m2Path.empty() || !visitedM2.insert(m2Path).second) return; + for (auto& t : TextureExporter::collectM2Textures(assetManager_.get(), m2Path)) + uniq.insert(std::move(t)); + }; + for (const auto& obj : objectPlacer_.getObjects()) { + if (obj.type == PlaceableType::M2) addM2Tex(obj.path); + } + for (const auto& npc : npcSpawner_.getSpawns()) addM2Tex(npc.modelPath); + + usedTextures.assign(uniq.begin(), uniq.end()); + std::sort(usedTextures.begin(), usedTextures.end()); + if (!usedTextures.empty()) { + int exported = TextureExporter::exportTexturesAsPng( + assetManager_.get(), usedTextures, base + "/textures"); + LOG_INFO("Exported ", exported, " textures as PNG (terrain + ", visitedM2.size(), " M2s)"); + } } // Export zone-relevant DBCs as JSON (open format replacement for DBC) diff --git a/tools/editor/texture_exporter.cpp b/tools/editor/texture_exporter.cpp index fd9c7257..d4fa6a2a 100644 --- a/tools/editor/texture_exporter.cpp +++ b/tools/editor/texture_exporter.cpp @@ -1,6 +1,7 @@ #include "texture_exporter.hpp" #include "pipeline/asset_manager.hpp" #include "pipeline/blp_loader.hpp" +#include "pipeline/m2_loader.hpp" #include "core/logger.hpp" #include #include @@ -20,6 +21,30 @@ std::vector TextureExporter::collectUsedTextures(const pipeline::AD return result; } +std::vector TextureExporter::collectM2Textures(pipeline::AssetManager* am, + const std::string& m2Path) { + std::vector out; + if (!am || m2Path.empty()) return out; + + auto data = am->readFile(m2Path); + if (data.empty()) return out; + auto m2 = pipeline::M2Loader::load(data); + // Skin file holds geometry but textures live in the M2 header itself. + // Even if isValid() is false (no skin loaded), the texture list is populated. + + std::unordered_set unique; + for (const auto& tex : m2.textures) { + if (tex.filename.empty()) continue; + std::string p = tex.filename; + std::transform(p.begin(), p.end(), p.begin(), + [](unsigned char c) { return std::tolower(c); }); + unique.insert(p); + } + out.assign(unique.begin(), unique.end()); + std::sort(out.begin(), out.end()); + return out; +} + int TextureExporter::exportTexturesAsPng(pipeline::AssetManager* am, const std::vector& texturePaths, const std::string& outputDir) { diff --git a/tools/editor/texture_exporter.hpp b/tools/editor/texture_exporter.hpp index f483be3a..d8e44cc8 100644 --- a/tools/editor/texture_exporter.hpp +++ b/tools/editor/texture_exporter.hpp @@ -15,6 +15,11 @@ public: // Collect all texture paths referenced by the terrain static std::vector collectUsedTextures(const pipeline::ADTTerrain& terrain); + // Collect all texture paths referenced by an M2 model (loads the M2 from `am`). + // Returns lowercased game paths (e.g. "creature\\foo\\foo.blp"). Empty if M2 not found. + static std::vector collectM2Textures(pipeline::AssetManager* am, + const std::string& m2Path); + // Export all used textures as PNG to an output directory // Returns count of successfully exported textures static int exportTexturesAsPng(pipeline::AssetManager* am,