diff --git a/include/pipeline/wowee_model.hpp b/include/pipeline/wowee_model.hpp index 6a050812..8e44870f 100644 --- a/include/pipeline/wowee_model.hpp +++ b/include/pipeline/wowee_model.hpp @@ -9,6 +9,8 @@ namespace wowee { namespace pipeline { +struct M2Model; + // Wowee Open Model format (.wom) — novel format, no Blizzard IP // WOM1: static geometry | WOM2: + bones + animations struct WoweeModel { @@ -68,6 +70,14 @@ public: // Check if a .wom exists static bool exists(const std::string& basePath); + + // Convert WoweeModel to an in-memory M2Model so the M2Renderer can consume it. + // Single batch, single material — sufficient for static and simple animated meshes. + static M2Model toM2(const WoweeModel& wom); + + // Convenience: try loading .wom from the standard editor + // search paths (custom_zones/models/, output/models/). Returns valid model on hit. + static WoweeModel tryLoadByGamePath(const std::string& gamePath); }; } // namespace pipeline diff --git a/src/pipeline/wowee_model.cpp b/src/pipeline/wowee_model.cpp index 1497224f..d444a826 100644 --- a/src/pipeline/wowee_model.cpp +++ b/src/pipeline/wowee_model.cpp @@ -324,5 +324,65 @@ WoweeModel WoweeModelLoader::fromM2(const std::string& m2Path, AssetManager* am) return model; } +M2Model WoweeModelLoader::toM2(const WoweeModel& wom) { + M2Model m; + if (!wom.isValid()) return m; + + m.name = wom.name; + m.boundRadius = wom.boundRadius; + + m.vertices.reserve(wom.vertices.size()); + for (const auto& v : wom.vertices) { + 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); + m.vertices.push_back(mv); + } + + m.indices.reserve(wom.indices.size()); + for (uint32_t idx : wom.indices) + m.indices.push_back(static_cast(idx)); + + for (const auto& tp : wom.texturePaths) { + M2Texture tex; + tex.type = 0; + tex.flags = 0; + tex.filename = tp; + m.textures.push_back(tex); + } + m.textureLookup = {0}; + + M2Batch batch{}; + batch.textureCount = std::min(1u, static_cast(wom.texturePaths.size())); + batch.indexCount = static_cast(m.indices.size()); + batch.vertexCount = static_cast(m.vertices.size()); + m.batches.push_back(batch); + + M2Material mat; + mat.flags = 0; + mat.blendMode = 0; + m.materials.push_back(mat); + + return m; +} + +WoweeModel WoweeModelLoader::tryLoadByGamePath(const std::string& gamePath) { + std::string base = gamePath; + auto dot = base.rfind('.'); + if (dot != std::string::npos) base = base.substr(0, dot); + std::replace(base.begin(), base.end(), '\\', '/'); + for (const char* prefix : {"custom_zones/models/", "output/models/"}) { + std::string full = std::string(prefix) + base; + if (exists(full)) { + auto wom = load(full); + if (wom.isValid()) return wom; + } + } + return {}; +} + } // namespace pipeline } // namespace wowee diff --git a/tools/editor/editor_viewport.cpp b/tools/editor/editor_viewport.cpp index 0bba068f..396ea9ce 100644 --- a/tools/editor/editor_viewport.cpp +++ b/tools/editor/editor_viewport.cpp @@ -134,46 +134,11 @@ void EditorViewport::rebuildObjects(const std::vector& objects, pipeline::M2Model model; bool loaded = false; - // 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; - } - } - } + // Try WOM open format first (replaces proprietary M2 when available) + if (auto wom = pipeline::WoweeModelLoader::tryLoadByGamePath(obj.path); + wom.isValid()) { + model = pipeline::WoweeModelLoader::toM2(wom); + loaded = true; } // Fall back to M2 from game data @@ -277,49 +242,13 @@ void EditorViewport::rebuildObjects(const std::vector& objects, if (it != m2ModelIds.end()) { modelId = it->second; } else { - // Try WOM open format first + // Try WOM open format first (replaces proprietary M2 when available) pipeline::M2Model model; bool loaded = false; - { - std::string womBase = npc.modelPath; - 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; - LOG_DEBUG("NPC loaded from WOM: ", prefix, womBase); - break; - } - } - } + if (auto wom = pipeline::WoweeModelLoader::tryLoadByGamePath(npc.modelPath); + wom.isValid()) { + model = pipeline::WoweeModelLoader::toM2(wom); + loaded = true; } // Fall back to M2 from game data