From 6657f0825274deaa83459cce01d064c4bac80496 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 08:15:31 -0700 Subject: [PATCH] fix(wob): sanitize portal vertices/group indices on load and save Three issues addressed: - NaN portal vertices break the WMO portal-frustum cull, defeating the indoor optimization and forcing the whole interior to draw. - Out-of-range portal.groupA/groupB indices walk past wmo.groups during cull; clamp to -1 (invalid) on load. - Symmetric save-side scrub so we don't persist bad in-memory data. --- src/pipeline/wowee_building.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/pipeline/wowee_building.cpp b/src/pipeline/wowee_building.cpp index 8f6fc0bc..36d8c274 100644 --- a/src/pipeline/wowee_building.cpp +++ b/src/pipeline/wowee_building.cpp @@ -149,6 +149,18 @@ WoweeBuilding WoweeBuildingLoader::load(const std::string& basePath) { } portal.vertices.resize(pvCount); f.read(reinterpret_cast(portal.vertices.data()), pvCount * 12); + // Sanitize vertex floats — NaN portal vertices break the WMO + // portal-frustum cull and would draw the whole interior every frame. + for (auto& v : portal.vertices) { + if (!std::isfinite(v.x)) v.x = 0.0f; + if (!std::isfinite(v.y)) v.y = 0.0f; + if (!std::isfinite(v.z)) v.z = 0.0f; + } + // Validate group indices are in range — out-of-range groupA/groupB + // would index past wmo.groups during cull and segfault. + const int32_t maxGroup = static_cast(groupCount); + if (portal.groupA >= maxGroup) portal.groupA = -1; + if (portal.groupB >= maxGroup) portal.groupB = -1; bld.portals.push_back(portal); } @@ -283,7 +295,16 @@ bool WoweeBuildingLoader::save(const WoweeBuilding& bld, const std::string& base f.write(reinterpret_cast(&portal.groupB), 4); uint32_t pvCount = static_cast(portal.vertices.size()); f.write(reinterpret_cast(&pvCount), 4); - f.write(reinterpret_cast(portal.vertices.data()), pvCount * 12); + // Sanitize vertices on the way out — NaN portal vertices break + // the WMO portal-frustum cull and fail-back to drawing the entire + // building, defeating the indoor optimization. + std::vector sanPortal = portal.vertices; + for (auto& v : sanPortal) { + if (!std::isfinite(v.x)) v.x = 0.0f; + if (!std::isfinite(v.y)) v.y = 0.0f; + if (!std::isfinite(v.z)) v.z = 0.0f; + } + f.write(reinterpret_cast(sanPortal.data()), pvCount * 12); } for (const auto& dp : bld.doodads) {