diff --git a/include/rendering/camera_controller.hpp b/include/rendering/camera_controller.hpp index 14e1e1af..d81c20cb 100644 --- a/include/rendering/camera_controller.hpp +++ b/include/rendering/camera_controller.hpp @@ -260,6 +260,12 @@ private: bool autoUnstuckFired_ = false; AutoUnstuckCallback autoUnstuckCallback_; static constexpr float AUTO_UNSTUCK_FALL_TIME = 5.0f; // 5 seconds of falling + + // Collision query cache (skip expensive checks if position barely changed) + glm::vec3 lastCollisionCheckPos_ = glm::vec3(0.0f); + float cachedFloorHeight_ = 0.0f; + bool hasCachedFloor_ = false; + static constexpr float COLLISION_CACHE_DISTANCE = 0.15f; // Re-check every 15cm }; } // namespace rendering diff --git a/src/rendering/camera_controller.cpp b/src/rendering/camera_controller.cpp index 6def7242..fa62cfbe 100644 --- a/src/rendering/camera_controller.cpp +++ b/src/rendering/camera_controller.cpp @@ -495,16 +495,35 @@ void CameraController::update(float deltaTime) { // to terrain when offset samples miss the WMO floor geometry. std::optional groundH; { - std::optional terrainH; - std::optional wmoH; - if (terrainManager) { - terrainH = terrainManager->getHeightAt(targetPos.x, targetPos.y); + // Collision cache: skip expensive checks if barely moved (15cm threshold) + float distMoved = glm::length(glm::vec2(targetPos.x, targetPos.y) - + glm::vec2(lastCollisionCheckPos_.x, lastCollisionCheckPos_.y)); + bool useCached = hasCachedFloor_ && distMoved < COLLISION_CACHE_DISTANCE; + + if (useCached) { + groundH = cachedFloorHeight_; + } else { + // Full collision check + std::optional terrainH; + std::optional wmoH; + if (terrainManager) { + terrainH = terrainManager->getHeightAt(targetPos.x, targetPos.y); + } + float wmoProbeZ = std::max(targetPos.z, lastGroundZ) + stepUpBudget + 0.5f; + if (wmoRenderer) { + wmoH = wmoRenderer->getFloorHeight(targetPos.x, targetPos.y, wmoProbeZ); + } + groundH = selectReachableFloor(terrainH, wmoH, targetPos.z, stepUpBudget); + + // Update cache + lastCollisionCheckPos_ = targetPos; + if (groundH) { + cachedFloorHeight_ = *groundH; + hasCachedFloor_ = true; + } else { + hasCachedFloor_ = false; + } } - float wmoProbeZ = std::max(targetPos.z, lastGroundZ) + stepUpBudget + 0.5f; - if (wmoRenderer) { - wmoH = wmoRenderer->getFloorHeight(targetPos.x, targetPos.y, wmoProbeZ); - } - groundH = selectReachableFloor(terrainH, wmoH, targetPos.z, stepUpBudget); } // 2. Multi-sample for M2 objects (rugs, planks, bridges, ships) —