diff --git a/src/rendering/camera_controller.cpp b/src/rendering/camera_controller.cpp index f21f7943..2c6c5b18 100644 --- a/src/rendering/camera_controller.cpp +++ b/src/rendering/camera_controller.cpp @@ -506,7 +506,7 @@ void CameraController::update(float deltaTime) { groundH = selectReachableFloor(terrainH, wmoH, targetPos.z, stepUpBudget); } - // 2. Multi-sample for M2 objects (rugs, planks, bridges) — + // 2. Multi-sample for M2 objects (rugs, planks, bridges, ships) — // these are narrow and need offset probes to detect reliably. if (m2Renderer) { constexpr float FOOTPRINT = 0.4f; @@ -519,9 +519,13 @@ void CameraController::update(float deltaTime) { for (const auto& o : offsets) { auto m2H = m2Renderer->getFloorHeight( targetPos.x + o.x, targetPos.y + o.y, m2ProbeZ); - if (m2H && *m2H <= targetPos.z + stepUpBudget && - (!groundH || *m2H > *groundH)) { - groundH = m2H; + // Prefer M2 floors (ships, platforms) even if slightly lower than terrain + // to prevent falling through ship decks to water below + if (m2H && *m2H <= targetPos.z + stepUpBudget) { + if (!groundH || *m2H > *groundH || + (*m2H >= targetPos.z - 0.5f && *groundH < targetPos.z - 1.0f)) { + groundH = m2H; + } } } } diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index 9d99cffe..97617c06 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -718,7 +718,7 @@ void M2ModelGPU::CollisionMesh::build() { glm::vec3 normal = glm::cross(v1 - v0, v2 - v0); float normalLen = glm::length(normal); float absNz = (normalLen > 0.001f) ? std::abs(normal.z / normalLen) : 0.0f; - bool isFloor = (absNz >= 0.40f); // ~66° max slope (relaxed for steep stairs) + bool isFloor = (absNz >= 0.35f); // ~70° max slope (relaxed for steep stairs) bool isWall = (absNz < 0.65f); float triMinX = std::min({v0.x, v1.x, v2.x}); @@ -2573,7 +2573,7 @@ std::optional M2Renderer::getFloorHeight(float glX, float glY, float glZ) if (localN.z < 0.0f) localN = -localN; glm::vec3 worldN = glm::normalize( glm::vec3(instance.modelMatrix * glm::vec4(localN, 0.0f))); - if (std::abs(worldN.z) < 0.40f) continue; // too steep (~66° max slope) + if (std::abs(worldN.z) < 0.35f) continue; // too steep (~70° max slope) } if (hitZ <= localPos.z + 3.0f && hitZ > bestHitZ) { diff --git a/src/rendering/wmo_renderer.cpp b/src/rendering/wmo_renderer.cpp index 35355d9e..50684cd7 100644 --- a/src/rendering/wmo_renderer.cpp +++ b/src/rendering/wmo_renderer.cpp @@ -1607,7 +1607,7 @@ void WMORenderer::GroupResources::buildCollisionGrid() { glm::vec3 normal = glm::cross(edge1, edge2); float normalLen = glm::length(normal); float absNz = (normalLen > 0.001f) ? std::abs(normal.z / normalLen) : 0.0f; - bool isFloor = (absNz >= 0.40f); // ~66° max slope (relaxed for steep stairs) + bool isFloor = (absNz >= 0.35f); // ~70° max slope (relaxed for steep stairs) bool isWall = (absNz < 0.65f); // Matches walkable slope threshold int cellMinX = std::max(0, static_cast((triMinX - gridOrigin.x) * invCellW)); @@ -1749,8 +1749,8 @@ std::optional WMORenderer::getFloorHeight(float glX, float glY, float glZ if (gridIt != precomputedFloorGrid.end()) { float cachedHeight = gridIt->second; // Only trust cache if it's basically at foot level. - // Prevent ledges/shoulder ramps from being treated as "floor". - constexpr float CACHE_ABOVE = 0.50f; // tune: 0.25–0.60 (increased to catch borderline floors) + // 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 @@ -2066,7 +2066,7 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to, if (horizDist <= PLAYER_RADIUS) { // Skip floor-like surfaces — grounding handles them, not wall collision float absNz = std::abs(normal.z); - if (absNz >= 0.40f) continue; + if (absNz >= 0.35f) continue; const float SKIN = 0.005f; // small separation so we don't re-collide immediately // Stronger push when inside WMO for more responsive indoor collision