From 7d3eb598935452273e90c8613ea75cd509e0c8b0 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 01:56:47 -0700 Subject: [PATCH] fix(wob): toWMOModel restores materials, textures, portals, group flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously toWMOModel only copied vertex/index data — materials and textures were dropped, so a converted-back WMO would render textureless white walls. Now rebuilds the global texture index from material paths, emits one WMOMaterial per WoB material, copies group bounds + outdoor flag, and reconstructs MOPR portal refs from groupA/groupB so PVS data survives round-trip. --- src/pipeline/wowee_building.cpp | 57 ++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/src/pipeline/wowee_building.cpp b/src/pipeline/wowee_building.cpp index dfdde29b..a0756c8e 100644 --- a/src/pipeline/wowee_building.cpp +++ b/src/pipeline/wowee_building.cpp @@ -197,12 +197,47 @@ bool WoweeBuildingLoader::toWMOModel(const WoweeBuilding& building, WMOModel& ou outModel.nGroups = static_cast(building.groups.size()); outModel.groups.clear(); + outModel.textures.clear(); + outModel.materials.clear(); + outModel.portals.clear(); + outModel.portalVertices.clear(); + outModel.portalRefs.clear(); + + // Build a global texture index from per-material texturePath strings. + // First-group materials become the WMO material list; each unique texture + // gets one entry in outModel.textures. + auto textureIndex = [&](const std::string& path) -> uint32_t { + if (path.empty()) return 0; + for (uint32_t i = 0; i < outModel.textures.size(); i++) { + if (outModel.textures[i] == path) return i; + } + outModel.textures.push_back(path); + return static_cast(outModel.textures.size() - 1); + }; + + if (!building.groups.empty()) { + for (const auto& mat : building.groups[0].materials) { + WMOMaterial wm{}; + wm.flags = mat.flags; + wm.shader = mat.shader; + wm.blendMode = mat.blendMode; + wm.texture1 = textureIndex(mat.texturePath); + wm.color1 = 0; + wm.texture2 = 0; + wm.color2 = 0; + wm.texture3 = 0; + wm.color3 = 0; + outModel.materials.push_back(wm); + } + } for (const auto& grp : building.groups) { WMOGroup wmoGroup; wmoGroup.name = grp.name; + wmoGroup.boundingBoxMin = grp.boundMin; + wmoGroup.boundingBoxMax = grp.boundMax; + if (grp.isOutdoor) wmoGroup.flags |= 0x08; - // Convert vertices wmoGroup.vertices.reserve(grp.vertices.size()); for (const auto& v : grp.vertices) { WMOVertex wv; @@ -213,7 +248,6 @@ bool WoweeBuildingLoader::toWMOModel(const WoweeBuilding& building, WMOModel& ou wmoGroup.vertices.push_back(wv); } - // Convert indices wmoGroup.indices.reserve(grp.indices.size()); for (uint32_t idx : grp.indices) wmoGroup.indices.push_back(static_cast(idx)); @@ -221,8 +255,23 @@ bool WoweeBuildingLoader::toWMOModel(const WoweeBuilding& building, WMOModel& ou outModel.groups.push_back(std::move(wmoGroup)); } - // WMOModel uses isValid() = nGroups > 0 && !groups.empty() - // Both are now set, so isValid() will return true + // Reconstruct portal vertices + refs from WoB's higher-level portal struct. + for (const auto& wp : building.portals) { + WMOPortal portal{}; + portal.startVertex = static_cast(outModel.portalVertices.size()); + portal.vertexCount = static_cast(wp.vertices.size()); + portal.planeIndex = 0; + for (const auto& v : wp.vertices) outModel.portalVertices.push_back(v); + uint16_t portalIdx = static_cast(outModel.portals.size()); + outModel.portals.push_back(portal); + if (wp.groupA >= 0) { + outModel.portalRefs.push_back({portalIdx, static_cast(wp.groupA), -1, 0}); + } + if (wp.groupB >= 0) { + outModel.portalRefs.push_back({portalIdx, static_cast(wp.groupB), 1, 0}); + } + } + return true; }