feat(editor): export WMO textures with the zone

Placed WMO buildings reference textures (walls, floors, decorations) that
were not being exported alongside the WOB files. Added collectWMOTextures()
which loads the root WMO + all group files and gathers every texture path,
then folds these into the same PNG export pass that handles terrain and M2
textures. Exported zones now have every texture they need across all model
types.
This commit is contained in:
Kelsi 2026-05-06 01:40:05 -07:00
parent a711a92875
commit f1d332825e
3 changed files with 47 additions and 1 deletions

View file

@ -1152,13 +1152,20 @@ void EditorApp::exportZone(const std::string& outputDir) {
std::unordered_set<std::string> uniq; std::unordered_set<std::string> uniq;
for (auto& t : TextureExporter::collectUsedTextures(terrain_)) uniq.insert(std::move(t)); for (auto& t : TextureExporter::collectUsedTextures(terrain_)) uniq.insert(std::move(t));
std::unordered_set<std::string> visitedM2; std::unordered_set<std::string> visitedM2;
std::unordered_set<std::string> visitedWMO;
auto addM2Tex = [&](const std::string& m2Path) { auto addM2Tex = [&](const std::string& m2Path) {
if (m2Path.empty() || !visitedM2.insert(m2Path).second) return; if (m2Path.empty() || !visitedM2.insert(m2Path).second) return;
for (auto& t : TextureExporter::collectM2Textures(assetManager_.get(), m2Path)) for (auto& t : TextureExporter::collectM2Textures(assetManager_.get(), m2Path))
uniq.insert(std::move(t)); uniq.insert(std::move(t));
}; };
auto addWMOTex = [&](const std::string& wmoPath) {
if (wmoPath.empty() || !visitedWMO.insert(wmoPath).second) return;
for (auto& t : TextureExporter::collectWMOTextures(assetManager_.get(), wmoPath))
uniq.insert(std::move(t));
};
for (const auto& obj : objectPlacer_.getObjects()) { for (const auto& obj : objectPlacer_.getObjects()) {
if (obj.type == PlaceableType::M2) addM2Tex(obj.path); if (obj.type == PlaceableType::M2) addM2Tex(obj.path);
else if (obj.type == PlaceableType::WMO) addWMOTex(obj.path);
} }
for (const auto& npc : npcSpawner_.getSpawns()) addM2Tex(npc.modelPath); for (const auto& npc : npcSpawner_.getSpawns()) addM2Tex(npc.modelPath);
@ -1167,7 +1174,8 @@ void EditorApp::exportZone(const std::string& outputDir) {
if (!usedTextures.empty()) { if (!usedTextures.empty()) {
int exported = TextureExporter::exportTexturesAsPng( int exported = TextureExporter::exportTexturesAsPng(
assetManager_.get(), usedTextures, base + "/textures"); assetManager_.get(), usedTextures, base + "/textures");
LOG_INFO("Exported ", exported, " textures as PNG (terrain + ", visitedM2.size(), " M2s)"); LOG_INFO("Exported ", exported, " textures as PNG (terrain + ",
visitedM2.size(), " M2s + ", visitedWMO.size(), " WMOs)");
} }
} }

View file

@ -2,6 +2,7 @@
#include "pipeline/asset_manager.hpp" #include "pipeline/asset_manager.hpp"
#include "pipeline/blp_loader.hpp" #include "pipeline/blp_loader.hpp"
#include "pipeline/m2_loader.hpp" #include "pipeline/m2_loader.hpp"
#include "pipeline/wmo_loader.hpp"
#include "core/logger.hpp" #include "core/logger.hpp"
#include <filesystem> #include <filesystem>
#include <algorithm> #include <algorithm>
@ -45,6 +46,38 @@ std::vector<std::string> TextureExporter::collectM2Textures(pipeline::AssetManag
return out; return out;
} }
std::vector<std::string> TextureExporter::collectWMOTextures(pipeline::AssetManager* am,
const std::string& wmoPath) {
std::vector<std::string> out;
if (!am || wmoPath.empty()) return out;
auto rootData = am->readFile(wmoPath);
if (rootData.empty()) return out;
auto wmo = pipeline::WMOLoader::load(rootData);
// Load group files so any group-only texture references are populated too.
std::string base = wmoPath;
if (base.size() > 4) base = base.substr(0, base.size() - 4);
for (uint32_t gi = 0; gi < wmo.nGroups; gi++) {
char suffix[16];
std::snprintf(suffix, sizeof(suffix), "_%03u.wmo", gi);
auto gd = am->readFile(base + suffix);
if (!gd.empty()) pipeline::WMOLoader::loadGroup(gd, wmo, gi);
}
std::unordered_set<std::string> unique;
for (const auto& tex : wmo.textures) {
if (tex.empty()) continue;
std::string p = tex;
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, int TextureExporter::exportTexturesAsPng(pipeline::AssetManager* am,
const std::vector<std::string>& texturePaths, const std::vector<std::string>& texturePaths,
const std::string& outputDir) { const std::string& outputDir) {

View file

@ -20,6 +20,11 @@ public:
static std::vector<std::string> collectM2Textures(pipeline::AssetManager* am, static std::vector<std::string> collectM2Textures(pipeline::AssetManager* am,
const std::string& m2Path); const std::string& m2Path);
// Collect all texture paths referenced by a WMO root file.
// Includes textures used by every group (loads root + group files via `am`).
static std::vector<std::string> collectWMOTextures(pipeline::AssetManager* am,
const std::string& wmoPath);
// Export all used textures as PNG to an output directory // Export all used textures as PNG to an output directory
// Returns count of successfully exported textures // Returns count of successfully exported textures
static int exportTexturesAsPng(pipeline::AssetManager* am, static int exportTexturesAsPng(pipeline::AssetManager* am,