From d7a0a9f6758c284b4c4b07a035f3695c855f0b94 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 14 Mar 2026 05:57:27 -0700 Subject: [PATCH] fix(wmo): disable portal traversal for outdoor camera groups --- src/rendering/wmo_renderer.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/rendering/wmo_renderer.cpp b/src/rendering/wmo_renderer.cpp index 7210d1be..523bf818 100644 --- a/src/rendering/wmo_renderer.cpp +++ b/src/rendering/wmo_renderer.cpp @@ -2054,6 +2054,9 @@ void WMORenderer::getVisibleGroupsViaPortals(const ModelData& model, const Frustum& frustum, const glm::mat4& modelMatrix, std::unordered_set& outVisibleGroups) const { + constexpr uint32_t WMO_GROUP_FLAG_OUTDOOR = 0x8; + constexpr uint32_t WMO_GROUP_FLAG_INDOOR = 0x2000; + // Find camera's containing group int cameraGroup = findContainingGroup(model, cameraLocalPos); @@ -2067,6 +2070,21 @@ void WMORenderer::getVisibleGroupsViaPortals(const ModelData& model, return; } + // Outdoor city WMOs (e.g. Stormwind) often have portal graphs that are valid for + // indoor visibility but too aggressive outdoors, causing direction-dependent popout. + // Only trust portal traversal when the camera is in an interior-only group. + if (cameraGroup < static_cast(model.groups.size())) { + const uint32_t gFlags = model.groups[cameraGroup].groupFlags; + const bool isIndoor = (gFlags & WMO_GROUP_FLAG_INDOOR) != 0; + const bool isOutdoor = (gFlags & WMO_GROUP_FLAG_OUTDOOR) != 0; + if (!isIndoor || isOutdoor) { + for (size_t gi = 0; gi < model.groups.size(); gi++) { + outVisibleGroups.insert(static_cast(gi)); + } + return; + } + } + // If the camera group has no portal refs, it's a dead-end group (utility/transition group). // Fall back to showing all groups to avoid the rest of the WMO going invisible. if (cameraGroup < static_cast(model.groupPortalRefs.size())) {