From 86c544b8410aeee02496c77a1fb13df5bf29aac6 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 08:46:20 -0700 Subject: [PATCH] fix(wmo): degenerate-portal guard during portal plane build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A portal whose first three vertices are coincident or collinear produces a zero cross product and glm::normalize returns NaN. The NaN propagates into the portal-frustum cull (every interior group either always-visible or never-visible depending on plane orientation). Use the same length-check pattern as the editor's spline/path code: zero cross → fall back to (0,0,1) up-axis. --- src/rendering/wmo_renderer.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/rendering/wmo_renderer.cpp b/src/rendering/wmo_renderer.cpp index ab558a8a..fce12661 100644 --- a/src/rendering/wmo_renderer.cpp +++ b/src/rendering/wmo_renderer.cpp @@ -749,8 +749,18 @@ bool WMORenderer::loadModel(const pipeline::WMOModel& model, uint32_t id) { glm::vec3 v0 = model.portalVertices[portal.startVertex]; glm::vec3 v1 = model.portalVertices[portal.startVertex + 1]; glm::vec3 v2 = model.portalVertices[portal.startVertex + 2]; - pd.normal = glm::normalize(glm::cross(v1 - v0, v2 - v0)); - pd.distance = glm::dot(pd.normal, v0); + // Degenerate portal (collinear or coincident verts) → cross is + // zero → normalize returns NaN. Fall back to up-axis instead of + // poisoning the portal-frustum cull. + glm::vec3 cross = glm::cross(v1 - v0, v2 - v0); + float crossLen = glm::length(cross); + if (crossLen > 1e-6f) { + pd.normal = cross / crossLen; + pd.distance = glm::dot(pd.normal, v0); + } else { + pd.normal = glm::vec3(0.0f, 0.0f, 1.0f); + pd.distance = 0.0f; + } } else { pd.normal = glm::vec3(0.0f, 0.0f, 1.0f); pd.distance = 0.0f;