From b2dccca58ce6e3a1ada11eb25cd8a464f8c3cdbd Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 10 Mar 2026 09:13:31 -0700 Subject: [PATCH] rendering: re-enable WMO camera collision with asymmetric smoothing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously disabled because the per-frame raycast caused erratic zoom snapping at doorway transitions. Re-enable using an asymmetrically- smoothed collision limit: pull-in reacts quickly (τ≈60 ms) to prevent the camera from ever visibly clipping through walls, while recovery is slow (τ≈400 ms) so walking through a doorway zooms back out gradually instead of snapping. Uses wmoRenderer->raycastBoundingBoxes() which already has strict wall filters (|normal.z|<0.20, surface-alignment check, ±0.9 height band) to ignore floors, ramps, and arch geometry. --- include/rendering/camera_controller.hpp | 1 + src/rendering/camera_controller.cpp | 32 +++++++++++++++++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/include/rendering/camera_controller.hpp b/include/rendering/camera_controller.hpp index 34600b47..1ccf7f63 100644 --- a/include/rendering/camera_controller.hpp +++ b/include/rendering/camera_controller.hpp @@ -156,6 +156,7 @@ private: static constexpr float MAX_PITCH = 35.0f; // Limited upward look glm::vec3* followTarget = nullptr; glm::vec3 smoothedCamPos = glm::vec3(0.0f); // For smooth camera movement + float smoothedCollisionDist_ = -1.0f; // Asymmetrically-smoothed WMO collision limit (-1 = uninitialised) // Gravity / grounding float verticalVelocity = 0.0f; diff --git a/src/rendering/camera_controller.cpp b/src/rendering/camera_controller.cpp index 891d53ba..4e0d6ff2 100644 --- a/src/rendering/camera_controller.cpp +++ b/src/rendering/camera_controller.cpp @@ -1316,12 +1316,36 @@ void CameraController::update(float deltaTime) { } } - // ===== Camera collision (sphere sweep approximation) ===== - // Find max safe distance using raycast + sphere radius + // ===== Camera collision (WMO raycast) ===== + // Cast a ray from the pivot toward the camera direction to find the + // nearest WMO wall. Uses asymmetric smoothing: pull-in is fast (so + // the camera never visibly clips through a wall) but recovery is slow + // (so passing through a doorway doesn't cause a zoom-out snap). collisionDistance = currentDistance; - // WMO/M2 camera collision disabled — was pulling camera through - // geometry at doorway transitions and causing erratic zoom behaviour. + if (wmoRenderer && currentDistance > MIN_DISTANCE) { + float rawHitDist = wmoRenderer->raycastBoundingBoxes(pivot, camDir, currentDistance); + // rawHitDist == currentDistance means no hit (function returns maxDistance on miss) + float rawLimit = (rawHitDist < currentDistance) + ? std::max(MIN_DISTANCE, rawHitDist - CAM_SPHERE_RADIUS - CAM_EPSILON) + : currentDistance; + + // Initialise smoothed state on first use. + if (smoothedCollisionDist_ < 0.0f) { + smoothedCollisionDist_ = rawLimit; + } + + // Asymmetric smoothing: + // • Pull-in: τ ≈ 60 ms — react quickly to prevent clipping + // • Recover: τ ≈ 400 ms — zoom out slowly after leaving geometry + const float tau = (rawLimit < smoothedCollisionDist_) ? 0.06f : 0.40f; + float alpha = 1.0f - std::exp(-deltaTime / tau); + smoothedCollisionDist_ += (rawLimit - smoothedCollisionDist_) * alpha; + + collisionDistance = std::min(collisionDistance, smoothedCollisionDist_); + } else { + smoothedCollisionDist_ = -1.0f; // Reset when wmoRenderer unavailable + } // Camera collision: terrain-only floor clamping auto getTerrainFloorAt = [&](float x, float y) -> std::optional {