From 4a932dd8cd9c0bba48e39fb7cc1f2f7096984b99 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 7 Feb 2026 17:13:09 -0800 Subject: [PATCH] Fix corner and ramp collision clipping - Remove early-out in wall collision so multiple wall faces are resolved per frame, preventing corner clip-through - Update localTo after each wall push so subsequent triangles check against the corrected position - Tighten collision sweep steps from 0.65 to 0.35 units (max 5 steps) for better tunneling prevention on thin walls and corners --- src/rendering/camera_controller.cpp | 2 +- src/rendering/wmo_renderer.cpp | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/rendering/camera_controller.cpp b/src/rendering/camera_controller.cpp index bb662e1d..36c85a46 100644 --- a/src/rendering/camera_controller.cpp +++ b/src/rendering/camera_controller.cpp @@ -430,7 +430,7 @@ void CameraController::update(float deltaTime) { float moveDist = glm::length(desiredPos - startPos); if (moveDist > 0.01f) { - int sweepSteps = std::max(1, std::min(3, static_cast(std::ceil(moveDist / 0.65f)))); + int sweepSteps = std::max(1, std::min(5, static_cast(std::ceil(moveDist / 0.35f)))); glm::vec3 stepPos = startPos; glm::vec3 stepDelta = (desiredPos - startPos) / static_cast(sweepSteps); diff --git a/src/rendering/wmo_renderer.cpp b/src/rendering/wmo_renderer.cpp index b6d0292c..02988e84 100644 --- a/src/rendering/wmo_renderer.cpp +++ b/src/rendering/wmo_renderer.cpp @@ -1754,7 +1754,6 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to, gatherCandidates(queryMin, queryMax, candidateScratch); for (size_t idx : candidateScratch) { - if (blocked) break; // Early-out once a wall is found const auto& instance = instances[idx]; if (collisionFocusEnabled && pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) { @@ -1792,7 +1791,7 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to, glm::vec3 localFrom = glm::vec3(instance.invModelMatrix * glm::vec4(from, 1.0f)); glm::vec3 localTo = glm::vec3(instance.invModelMatrix * glm::vec4(to, 1.0f)); float localFeetZ = localTo.z; - for (size_t gi = 0; gi < model.groups.size() && !blocked; ++gi) { + for (size_t gi = 0; gi < model.groups.size(); ++gi) { // World-space group cull if (gi < instance.worldGroupBounds.size()) { const auto& [gMin, gMax] = instance.worldGroupBounds[gi]; @@ -1823,7 +1822,6 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to, group.getTrianglesInRange(rangeMinX, rangeMinY, rangeMaxX, rangeMaxY, wallTriScratch); for (uint32_t triStart : wallTriScratch) { - if (blocked) break; const glm::vec3& v0 = verts[indices[triStart]]; const glm::vec3& v1 = verts[indices[triStart + 1]]; const glm::vec3& v2 = verts[indices[triStart + 2]]; @@ -1843,8 +1841,6 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to, // Get triangle Z range float triMinZ = std::min({v0.z, v1.z, v2.z}); float triMaxZ = std::max({v0.z, v1.z, v2.z}); - float fromDist = glm::dot(localFrom - v0, normal); - float toDist = glm::dot(localTo - v0, normal); // Only collide with walls in player's vertical range if (triMaxZ < localFeetZ + 0.3f) continue; @@ -1859,6 +1855,10 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to, // Skip very short vertical surfaces (stair risers) if (triHeight < 0.6f && triMaxZ <= localFeetZ + 0.8f) continue; + // Recompute distances with current (possibly pushed) localTo + float fromDist = glm::dot(localFrom - v0, normal); + float toDist = glm::dot(localTo - v0, normal); + // Swept test: prevent tunneling when crossing a wall between frames. if ((fromDist > PLAYER_RADIUS && toDist < -PLAYER_RADIUS) || (fromDist < -PLAYER_RADIUS && toDist > PLAYER_RADIUS)) { @@ -1875,6 +1875,8 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to, glm::vec3 safeWorld = glm::vec3(instance.modelMatrix * glm::vec4(safeLocal, 1.0f)); adjustedPos.x = safeWorld.x; adjustedPos.y = safeWorld.y; + // Update localTo for subsequent triangle checks + localTo = glm::vec3(instance.invModelMatrix * glm::vec4(adjustedPos, 1.0f)); blocked = true; continue; } @@ -1898,8 +1900,10 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to, } glm::vec3 pushLocal(pushDir2.x * pushDist, pushDir2.y * pushDist, 0.0f); + // Update localTo so subsequent triangles use corrected position + localTo.x += pushLocal.x; + localTo.y += pushLocal.y; glm::vec3 pushWorld = glm::vec3(instance.modelMatrix * glm::vec4(pushLocal, 0.0f)); - adjustedPos.x += pushWorld.x; adjustedPos.y += pushWorld.y; blocked = true;