fix(editor): placed objects also use WOM format + always load skin

Applied same two fixes from NPC renderer to placed object renderer:
1. Check WOM open format before M2 fallback (custom_zones/output dirs)
2. Always load skin file regardless of initial isValid state

Both placed objects (M2 doodads from ADT import or manual placement)
and NPC creatures now have consistent WOM→M2 fallback pipeline with
proper skin file loading.
This commit is contained in:
Kelsi 2026-05-05 22:29:31 -07:00
parent 3f5b030a4a
commit 98223995fc

View file

@ -129,29 +129,70 @@ void EditorViewport::rebuildObjects(const std::vector<PlacedObject>& objects,
if (it != m2ModelIds.end()) {
modelId = it->second;
} else {
auto data = assetManager_->readFile(obj.path);
if (data.empty()) {
LOG_WARNING("M2 file not found in manifest: ", obj.path);
continue;
}
auto model = pipeline::M2Loader::load(data);
pipeline::M2Model model;
bool loaded = false;
// WotLK M2s need a separate .skin file for geometry
if (!model.isValid()) {
std::string skinPath = obj.path;
auto dotPos = skinPath.rfind('.');
if (dotPos != std::string::npos)
skinPath = skinPath.substr(0, dotPos) + "00.skin";
auto skinData = assetManager_->readFile(skinPath);
if (!skinData.empty())
pipeline::M2Loader::loadSkin(skinData, model);
// Try WOM open format first
{
std::string womBase = obj.path;
auto womDot = womBase.rfind('.');
if (womDot != std::string::npos) womBase = womBase.substr(0, womDot);
std::replace(womBase.begin(), womBase.end(), '\\', '/');
for (const char* prefix : {"custom_zones/models/", "output/models/"}) {
if (pipeline::WoweeModelLoader::exists(std::string(prefix) + womBase)) {
auto wom = pipeline::WoweeModelLoader::load(std::string(prefix) + womBase);
if (wom.isValid()) {
model.name = wom.name;
model.boundRadius = wom.boundRadius;
for (const auto& v : wom.vertices) {
pipeline::M2Vertex mv;
mv.position = v.position;
mv.normal = v.normal;
mv.texCoords[0] = v.texCoord;
std::memcpy(mv.boneWeights, v.boneWeights, 4);
std::memcpy(mv.boneIndices, v.boneIndices, 4);
model.vertices.push_back(mv);
}
for (uint32_t idx : wom.indices)
model.indices.push_back(static_cast<uint16_t>(idx));
for (const auto& tp : wom.texturePaths) {
pipeline::M2Texture tex; tex.type = 0; tex.flags = 0; tex.filename = tp;
model.textures.push_back(tex);
}
model.textureLookup = {0};
pipeline::M2Batch batch{};
batch.textureCount = std::min(1u, static_cast<uint32_t>(wom.texturePaths.size()));
batch.indexCount = static_cast<uint32_t>(model.indices.size());
batch.vertexCount = static_cast<uint32_t>(model.vertices.size());
model.batches.push_back(batch);
pipeline::M2Material mat; mat.flags = 0; mat.blendMode = 0;
model.materials.push_back(mat);
loaded = true;
break;
}
}
}
}
if (!model.isValid()) {
LOG_WARNING("M2 failed to parse (", data.size(), " bytes): ", obj.path);
continue;
// Fall back to M2 from game data
if (!loaded) {
auto data = assetManager_->readFile(obj.path);
if (data.empty()) continue;
model = pipeline::M2Loader::load(data);
// Always load skin (WotLK M2s need it for geometry)
{
std::string skinPath = obj.path;
auto dotPos = skinPath.rfind('.');
if (dotPos != std::string::npos)
skinPath = skinPath.substr(0, dotPos) + "00.skin";
auto skinData = assetManager_->readFile(skinPath);
if (!skinData.empty())
pipeline::M2Loader::loadSkin(skinData, model);
}
}
if (!model.isValid()) continue;
if (model.boundRadius < 1.0f) model.boundRadius = 50.0f;
// Validate vertex data to prevent GPU crashes