Kelsidavis-WoWee/tools/editor/texture_exporter.cpp
Kelsi f1d332825e 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.
2026-05-06 01:40:05 -07:00

122 lines
4.4 KiB
C++

#include "texture_exporter.hpp"
#include "pipeline/asset_manager.hpp"
#include "pipeline/blp_loader.hpp"
#include "pipeline/m2_loader.hpp"
#include "pipeline/wmo_loader.hpp"
#include "core/logger.hpp"
#include <filesystem>
#include <algorithm>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
namespace wowee {
namespace editor {
std::vector<std::string> TextureExporter::collectUsedTextures(const pipeline::ADTTerrain& terrain) {
std::unordered_set<std::string> unique;
for (const auto& tex : terrain.textures)
unique.insert(tex);
std::vector<std::string> result(unique.begin(), unique.end());
std::sort(result.begin(), result.end());
return result;
}
std::vector<std::string> TextureExporter::collectM2Textures(pipeline::AssetManager* am,
const std::string& m2Path) {
std::vector<std::string> 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<std::string> 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;
}
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,
const std::vector<std::string>& texturePaths,
const std::string& outputDir) {
namespace fs = std::filesystem;
int exported = 0;
for (const auto& texPath : texturePaths) {
auto blpImage = am->loadTexture(texPath);
if (!blpImage.isValid()) {
LOG_WARNING("Texture not found or invalid: ", texPath);
continue;
}
// Build output path: replace backslashes, change .blp to .png
std::string outPath = texPath;
std::replace(outPath.begin(), outPath.end(), '\\', '/');
// Lowercase
std::transform(outPath.begin(), outPath.end(), outPath.begin(),
[](unsigned char c) { return std::tolower(c); });
// Change extension
auto dotPos = outPath.rfind('.');
if (dotPos != std::string::npos)
outPath = outPath.substr(0, dotPos) + ".png";
std::string fullPath = outputDir + "/" + outPath;
fs::create_directories(fs::path(fullPath).parent_path());
// Write RGBA data as PNG
if (stbi_write_png(fullPath.c_str(), blpImage.width, blpImage.height, 4,
blpImage.data.data(), blpImage.width * 4)) {
exported++;
} else {
LOG_WARNING("Failed to write PNG: ", fullPath);
}
}
LOG_INFO("Exported ", exported, "/", texturePaths.size(), " textures as PNG to ", outputDir);
return exported;
}
} // namespace editor
} // namespace wowee