From 9511d051e2e501a92ef4bd05345a5ca7c2086065 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Thu, 5 Feb 2026 18:12:27 -0800 Subject: [PATCH] Simplify wall collision and add intro camera pan - Remove complex ramp/edge filtering that was skipping building walls - Simpler wall detection: any vertical geometry above step height - Add intro camera pan on game start --- include/game/game_handler.hpp | 1 - include/rendering/camera_controller.hpp | 8 +- src/core/application.cpp | 7 +- src/game/game_handler.cpp | 17 +-- src/rendering/camera_controller.cpp | 18 ++- src/rendering/wmo_renderer.cpp | 140 +++++------------------- 6 files changed, 54 insertions(+), 137 deletions(-) diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index cc671e1d..f5c178d0 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -183,7 +183,6 @@ public: int sfxVolume = 100; float mouseSensitivity = 0.2f; bool invertMouse = false; - bool introSeen = false; }; bool getSinglePlayerSettings(SinglePlayerSettings& out) const; void setSinglePlayerSettings(const SinglePlayerSettings& settings); diff --git a/include/rendering/camera_controller.hpp b/include/rendering/camera_controller.hpp index 40281864..562373ad 100644 --- a/include/rendering/camera_controller.hpp +++ b/include/rendering/camera_controller.hpp @@ -41,7 +41,7 @@ public: } void reset(); - void startIntroPan(float durationSec = 2.5f, float orbitDegrees = 120.0f); + void startIntroPan(float durationSec = 2.8f, float orbitDegrees = 140.0f); bool isIntroActive() const { return introActive; } float getMovementSpeed() const { return movementSpeed; } @@ -186,8 +186,12 @@ private: float introTimer = 0.0f; float introDuration = 0.0f; float introStartYaw = 0.0f; + float introEndYaw = 0.0f; float introOrbitDegrees = 0.0f; - float introPitch = -10.0f; + float introStartPitch = -15.0f; + float introEndPitch = -5.0f; + float introStartDistance = 12.0f; + float introEndDistance = 10.0f; }; } // namespace rendering diff --git a/src/core/application.cpp b/src/core/application.cpp index 08cefd3e..0b456c1b 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -1172,7 +1172,6 @@ void Application::startSinglePlayer() { settings.mouseSensitivity = cameraController->getMouseSensitivity(); settings.invertMouse = cameraController->isInvertMouse(); } - settings.introSeen = false; gameHandler->setSinglePlayerSettings(settings); hasSettings = true; } @@ -1196,11 +1195,7 @@ void Application::startSinglePlayer() { if (auto* cameraController = renderer->getCameraController()) { cameraController->setMouseSensitivity(settings.mouseSensitivity); cameraController->setInvertMouse(settings.invertMouse); - if (!settings.introSeen) { - cameraController->startIntroPan(2.8f, 140.0f); - settings.introSeen = true; - gameHandler->setSinglePlayerSettings(settings); - } + cameraController->startIntroPan(2.8f, 140.0f); } } } diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 88766a14..b70bf0db 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -204,12 +204,9 @@ struct SinglePlayerSqlite { " music_volume INTEGER," " sfx_volume INTEGER," " mouse_sensitivity REAL," - " invert_mouse INTEGER," - " intro_seen INTEGER" + " invert_mouse INTEGER" ");"; - bool ok = exec(kSchema); - exec("ALTER TABLE character_settings ADD COLUMN intro_seen INTEGER DEFAULT 0;"); - return ok; + return exec(kSchema); } }; @@ -1639,7 +1636,7 @@ bool GameHandler::loadSinglePlayerCharacterState(uint64_t guid) { spLastDirtyOrientation_ = movementInfo.orientation; const char* sqlSettings = - "SELECT fullscreen, vsync, shadows, res_w, res_h, music_volume, sfx_volume, mouse_sensitivity, invert_mouse, intro_seen " + "SELECT fullscreen, vsync, shadows, res_w, res_h, music_volume, sfx_volume, mouse_sensitivity, invert_mouse " "FROM character_settings WHERE guid=?;"; if (sqlite3_prepare_v2(sp.db, sqlSettings, -1, &stmt, nullptr) == SQLITE_OK) { sqlite3_bind_int64(stmt, 1, static_cast(guid)); @@ -1653,7 +1650,6 @@ bool GameHandler::loadSinglePlayerCharacterState(uint64_t guid) { spSettings_.sfxVolume = sqlite3_column_int(stmt, 6); spSettings_.mouseSensitivity = static_cast(sqlite3_column_double(stmt, 7)); spSettings_.invertMouse = sqlite3_column_int(stmt, 8) != 0; - spSettings_.introSeen = sqlite3_column_int(stmt, 9) != 0; spSettingsLoaded_ = true; } sqlite3_finalize(stmt); @@ -1814,13 +1810,13 @@ void GameHandler::saveSinglePlayerCharacterState(bool force) { if (spSettingsLoaded_ && (force || (spDirtyFlags_ & SP_DIRTY_SETTINGS))) { const char* upsertSettings = "INSERT INTO character_settings " - "(guid, fullscreen, vsync, shadows, res_w, res_h, music_volume, sfx_volume, mouse_sensitivity, invert_mouse, intro_seen) " - "VALUES (?,?,?,?,?,?,?,?,?,?,?) " + "(guid, fullscreen, vsync, shadows, res_w, res_h, music_volume, sfx_volume, mouse_sensitivity, invert_mouse) " + "VALUES (?,?,?,?,?,?,?,?,?,?) " "ON CONFLICT(guid) DO UPDATE SET " "fullscreen=excluded.fullscreen, vsync=excluded.vsync, shadows=excluded.shadows, " "res_w=excluded.res_w, res_h=excluded.res_h, music_volume=excluded.music_volume, " "sfx_volume=excluded.sfx_volume, mouse_sensitivity=excluded.mouse_sensitivity, " - "invert_mouse=excluded.invert_mouse, intro_seen=excluded.intro_seen;"; + "invert_mouse=excluded.invert_mouse;"; if (sqlite3_prepare_v2(sp.db, upsertSettings, -1, &stmt, nullptr) == SQLITE_OK) { sqlite3_bind_int64(stmt, 1, static_cast(activeCharacterGuid_)); sqlite3_bind_int(stmt, 2, spSettings_.fullscreen ? 1 : 0); @@ -1832,7 +1828,6 @@ void GameHandler::saveSinglePlayerCharacterState(bool force) { sqlite3_bind_int(stmt, 8, spSettings_.sfxVolume); sqlite3_bind_double(stmt, 9, spSettings_.mouseSensitivity); sqlite3_bind_int(stmt, 10, spSettings_.invertMouse ? 1 : 0); - sqlite3_bind_int(stmt, 11, spSettings_.introSeen ? 1 : 0); sqlite3_step(stmt); sqlite3_finalize(stmt); } diff --git a/src/rendering/camera_controller.cpp b/src/rendering/camera_controller.cpp index abe07d62..e97b9708 100644 --- a/src/rendering/camera_controller.cpp +++ b/src/rendering/camera_controller.cpp @@ -61,9 +61,17 @@ void CameraController::startIntroPan(float durationSec, float orbitDegrees) { introActive = true; introTimer = 0.0f; introDuration = std::max(0.5f, durationSec); - introStartYaw = yaw; + introStartYaw = facingYaw + orbitDegrees; + introEndYaw = facingYaw; introOrbitDegrees = orbitDegrees; - introPitch = pitch; + introStartPitch = -32.0f; + introEndPitch = -10.0f; + introStartDistance = 18.0f; + introEndDistance = 10.0f; + yaw = introStartYaw; + pitch = introStartPitch; + currentDistance = introStartDistance; + userTargetDistance = introEndDistance; thirdPerson = true; } @@ -94,8 +102,10 @@ void CameraController::update(float deltaTime) { } else { introTimer += deltaTime; float t = (introDuration > 0.0f) ? std::min(introTimer / introDuration, 1.0f) : 1.0f; - yaw = introStartYaw + introOrbitDegrees * t; - pitch = introPitch; + yaw = introStartYaw + (introEndYaw - introStartYaw) * t; + pitch = introStartPitch + (introEndPitch - introStartPitch) * t; + currentDistance = introStartDistance + (introEndDistance - introStartDistance) * t; + userTargetDistance = introEndDistance; camera->setRotation(yaw, pitch); facingYaw = yaw; if (t >= 1.0f) { diff --git a/src/rendering/wmo_renderer.cpp b/src/rendering/wmo_renderer.cpp index 2d3b44a1..fa16b289 100644 --- a/src/rendering/wmo_renderer.cpp +++ b/src/rendering/wmo_renderer.cpp @@ -1598,97 +1598,28 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to, if (triMaxZ < localFeetZ + 0.3f) continue; if (triMinZ > localFeetZ + PLAYER_HEIGHT) continue; - // Lower parts of ramps should be stepable from the side. - // Allow a larger step-up budget for ramp-like triangles. - // Allow running off/onto lower ramp side geometry without invisible wall blocks. - if (normal.z > 0.30f && triMaxZ <= localFeetZ + 0.95f) continue; - // Ignore short near-vertical side strips around ramps/edges. - // These commonly act like invisible side guard rails. + // Simplified wall detection: any vertical-ish triangle above step height is a wall. float triHeight = triMaxZ - triMinZ; - bool likelyRealWall = - (std::abs(normal.z) < 0.20f) && - (triHeight > 2.4f || triMaxZ > localFeetZ + 2.6f); - bool structuralWall = - (triHeight > 1.6f) && - (triMaxZ > localFeetZ + 1.8f); - if (std::abs(normal.z) < 0.25f && - triHeight < 1.8f && - triMaxZ <= localFeetZ + 1.4f) { - continue; - } - // Motion-aware permissive ramp side strips: - // keeps side entry/exit from behaving like invisible rails. - bool rampSideStrip = false; - if (steppingUp) { - rampSideStrip = - (std::abs(normal.z) > 0.02f && std::abs(normal.z) < 0.45f) && - triMinZ <= localFeetZ + 0.30f && - triHeight < 3.6f && - triMaxZ <= localFeetZ + 2.8f; - } else if (steppingDown) { - rampSideStrip = - (std::abs(normal.z) > 0.02f && std::abs(normal.z) < 0.65f) && - triMinZ <= localFeetZ + 0.45f && - triHeight < 4.5f && - triMaxZ <= localFeetZ + 3.8f; - } - // High on ramps, side triangles can span very tall strips and - // still behave like side rails. If we're stepping down and - // moving away from the wall, don't let them trap movement. - if (!rampSideStrip && - steppingDown && - awayFromWallMotion && - std::abs(normal.z) > 0.02f && std::abs(normal.z) < 0.70f && - triMinZ <= localFeetZ + 0.60f && - triMaxZ <= localFeetZ + 4.5f && - localFeetZ >= triMinZ + 0.80f) { - rampSideStrip = true; - } - if (rampSideStrip && !likelyRealWall && !structuralWall) { - continue; - } - // Let players run off ramp sides: ignore lower side-wall strips - // that sit around foot height and are not true tall building walls. - if (std::abs(normal.z) < 0.45f && - std::abs(normal.z) > 0.05f && - triMinZ <= localFeetZ + 0.20f && - triHeight < 4.0f && - triMaxZ <= localFeetZ + 4.0f && - !likelyRealWall && !structuralWall) { - continue; - } - float stepHeightLimit = MAX_STEP_HEIGHT; - if (triMaxZ <= localFeetZ + stepHeightLimit) continue; // Treat as step-up, not hard wall + // Skip low geometry that can be stepped over + if (triMaxZ <= localFeetZ + MAX_STEP_HEIGHT) continue; + + // Skip ramp surfaces (facing mostly upward) that are low + if (normal.z > 0.50f && triMaxZ <= localFeetZ + 1.2f) continue; // Swept test: prevent tunneling when crossing a wall between frames. - bool shortRampEdgeStrip = - (steppingUp || steppingDown) && - (std::abs(normal.z) > 0.01f && std::abs(normal.z) < (steppingDown ? 0.50f : 0.30f)) && - triMinZ <= localFeetZ + (steppingDown ? 0.45f : 0.35f) && - triHeight < (steppingDown ? 4.2f : 3.0f) && - triMaxZ <= localFeetZ + (steppingDown ? 3.8f : 3.2f); if ((fromDist > PLAYER_RADIUS && toDist < -PLAYER_RADIUS) || (fromDist < -PLAYER_RADIUS && toDist > PLAYER_RADIUS)) { - // For true wall-like faces, always block segment crossing. - // Motion-direction heuristics are only for ramp-side stickiness. - if (!towardWallMotion && !likelyRealWall && !structuralWall) { - continue; - } - if (shortRampEdgeStrip && !likelyRealWall && !structuralWall) { - continue; - } float denom = (fromDist - toDist); if (std::abs(denom) > 1e-6f) { - float tHit = fromDist / denom; // Segment param [0,1] + float tHit = fromDist / denom; if (tHit >= 0.0f && tHit <= 1.0f) { glm::vec3 hitPoint = localFrom + (localTo - localFrom) * tHit; glm::vec3 hitClosest = closestPointOnTriangle(hitPoint, v0, v1, v2); float hitErrSq = glm::dot(hitClosest - hitPoint, hitClosest - hitPoint); - bool insideHit = (hitErrSq <= 0.04f * 0.04f); - if (insideHit) { + if (hitErrSq <= 0.04f * 0.04f) { float side = fromDist > 0.0f ? 1.0f : -1.0f; - glm::vec3 safeLocal = hitPoint + normal * side * (PLAYER_RADIUS + 0.03f); + glm::vec3 safeLocal = hitPoint + normal * side * (PLAYER_RADIUS + 0.05f); glm::vec3 safeWorld = glm::vec3(instance.modelMatrix * glm::vec4(safeLocal, 1.0f)); adjustedPos.x = safeWorld.x; adjustedPos.y = safeWorld.y; @@ -1699,47 +1630,30 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to, } } + // Distance-based collision: push player out of walls glm::vec3 closest = closestPointOnTriangle(localTo, v0, v1, v2); glm::vec3 delta = localTo - closest; float horizDist = glm::length(glm::vec2(delta.x, delta.y)); if (horizDist <= PLAYER_RADIUS) { wallsHit++; - // Push player away from wall (horizontal only, from closest point). - float pushDist = PLAYER_RADIUS - horizDist; - if (pushDist > 0.0f) { - glm::vec2 pushDir2; - if (horizDist > 1e-4f) { - pushDir2 = glm::normalize(glm::vec2(delta.x, delta.y)); - } else { - glm::vec2 n2(normal.x, normal.y); - if (glm::length(n2) < 1e-4f) continue; - pushDir2 = glm::normalize(n2); - } - - // Softer push when stepping up near ramp side edges. - bool rampEdgeLike = (std::abs(normal.z) < 0.45f && triHeight < 4.0f); - if (!towardWallMotion && !likelyRealWall && !structuralWall) continue; - if (shortRampEdgeStrip && - !likelyRealWall && !structuralWall && - std::abs(toDist) >= std::abs(fromDist) - PLAYER_RADIUS * 0.25f) continue; - float pushScale = 0.35f; - float pushCap = 0.06f; - if (rampEdgeLike && (steppingUp || steppingDown)) { - pushScale = steppingDown ? 0.08f : 0.12f; - pushCap = steppingDown ? 0.015f : 0.022f; - } - pushDist = std::min(pushCap, pushDist * pushScale); - if (pushDist <= 0.0f) continue; - glm::vec3 pushLocal(pushDir2.x * pushDist, pushDir2.y * pushDist, 0.0f); - - // Transform push vector back to world space - glm::vec3 pushWorld = glm::vec3(instance.modelMatrix * glm::vec4(pushLocal, 0.0f)); - - // Only horizontal push - adjustedPos.x += pushWorld.x; - adjustedPos.y += pushWorld.y; - blocked = true; + float pushDist = PLAYER_RADIUS - horizDist + 0.02f; + glm::vec2 pushDir2; + if (horizDist > 1e-4f) { + pushDir2 = glm::normalize(glm::vec2(delta.x, delta.y)); + } else { + glm::vec2 n2(normal.x, normal.y); + if (glm::length(n2) < 1e-4f) continue; + pushDir2 = glm::normalize(n2); } + glm::vec3 pushLocal(pushDir2.x * pushDist, pushDir2.y * pushDist, 0.0f); + + // Transform push vector back to world space + glm::vec3 pushWorld = glm::vec3(instance.modelMatrix * glm::vec4(pushLocal, 0.0f)); + + // Only horizontal push + adjustedPos.x += pushWorld.x; + adjustedPos.y += pushWorld.y; + blocked = true; } } }