From 98223995fc2ea3b32a94fc2272466210fa11c14d Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 5 May 2026 22:29:31 -0700 Subject: [PATCH] fix(editor): placed objects also use WOM format + always load skin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- tools/editor/editor_viewport.cpp | 77 ++++++++++++++++++++++++-------- 1 file changed, 59 insertions(+), 18 deletions(-) diff --git a/tools/editor/editor_viewport.cpp b/tools/editor/editor_viewport.cpp index 120e5b71..be249f40 100644 --- a/tools/editor/editor_viewport.cpp +++ b/tools/editor/editor_viewport.cpp @@ -129,29 +129,70 @@ void EditorViewport::rebuildObjects(const std::vector& 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(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(wom.texturePaths.size())); + batch.indexCount = static_cast(model.indices.size()); + batch.vertexCount = static_cast(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