diff --git a/src/rendering/camera_controller.cpp b/src/rendering/camera_controller.cpp index 2c6c5b18..56301d5a 100644 --- a/src/rendering/camera_controller.cpp +++ b/src/rendering/camera_controller.cpp @@ -641,8 +641,30 @@ void CameraController::update(float deltaTime) { // Find max safe distance using raycast + sphere radius collisionDistance = currentDistance; - // Camera collision: terrain-only floor clamping (skip expensive WMO raycasts). - // The camera may clip through WMO walls but won't go underground. + // WMO raycast collision: zoom in when camera would clip through walls + if (wmoRenderer && cachedInsideWMO && currentDistance > MIN_DISTANCE) { + glm::vec3 camRayOrigin = pivot; + glm::vec3 camRayDir = camDir; + float wmoHitDist = wmoRenderer->raycastBoundingBoxes(camRayOrigin, camRayDir, currentDistance); + if (wmoHitDist < currentDistance) { + // Hit WMO geometry — pull camera in to avoid clipping + constexpr float CAM_RADIUS = 0.3f; + collisionDistance = std::max(MIN_DISTANCE, wmoHitDist - CAM_RADIUS); + } + } + + // M2 raycast collision: zoom in when camera would clip through doodads + if (m2Renderer && currentDistance > MIN_DISTANCE) { + glm::vec3 camRayOrigin = pivot; + glm::vec3 camRayDir = camDir; + float m2HitDist = m2Renderer->raycastBoundingBoxes(camRayOrigin, camRayDir, currentDistance); + if (m2HitDist < collisionDistance) { + constexpr float CAM_RADIUS = 0.3f; + collisionDistance = std::max(MIN_DISTANCE, m2HitDist - CAM_RADIUS); + } + } + + // Camera collision: terrain-only floor clamping auto getTerrainFloorAt = [&](float x, float y) -> std::optional { if (terrainManager) { return terrainManager->getHeightAt(x, y); diff --git a/src/rendering/wmo_renderer.cpp b/src/rendering/wmo_renderer.cpp index 50684cd7..72fc2a76 100644 --- a/src/rendering/wmo_renderer.cpp +++ b/src/rendering/wmo_renderer.cpp @@ -1743,22 +1743,8 @@ std::optional WMORenderer::getFloorHeight(float glX, float glY, float glZ auto frameCached = frameFloorCache_.get(glX, glY, currentFrameId, outNormalZ); if (frameCached) return *frameCached; - // Check persistent grid cache first (computed lazily, never expires) - uint64_t gridKey = floorGridKey(glX, glY); - auto gridIt = precomputedFloorGrid.find(gridKey); - if (gridIt != precomputedFloorGrid.end()) { - float cachedHeight = gridIt->second; - // Only trust cache if it's basically at foot level. - // Reject cache if it's too high above us (prevents stair landing from overriding approach floor) - constexpr float CACHE_ABOVE = 0.25f; // tight above threshold (prevents stair landing cache hit) - constexpr float CACHE_BELOW = 4.0f; // keep generous below - if (cachedHeight <= glZ + CACHE_ABOVE && cachedHeight >= glZ - CACHE_BELOW) { - // Persistent cache doesn't store normal — report as flat - if (outNormalZ) *outNormalZ = 0.8f; // conservative "walkable-ish" - frameFloorCache_.put(glX, glY, cachedHeight, 1.0f, currentFrameId); - return cachedHeight; - } - } + // Persistent grid cache disabled - causes fall-through at stairs where + // one 2-unit cell contains multiple floor heights. Per-frame cache is sufficient. QueryTimer timer(&queryTimeMs, &queryCallCount); std::optional bestFloor;