Fix WMO collision clipping in Stormwind

Relax swept collision hit threshold, expand spatial grid query range,
enable WMO floor detection in first person, raise ramp rejection
threshold, and snap Z after wall collision XY adjustment.
This commit is contained in:
Kelsi 2026-02-08 13:24:56 -08:00
parent f6eaa2cf70
commit 387cc5ddf4
2 changed files with 12 additions and 9 deletions

View file

@ -27,7 +27,7 @@ std::optional<float> selectReachableFloor(const std::optional<float>& terrainH,
if (wmoH && *wmoH <= refZ + maxStepUp) reachWmo = wmoH;
// Avoid snapping up to higher WMO floors when entering buildings.
if (reachTerrain && reachWmo && *reachWmo > refZ + 2.0f) {
if (reachTerrain && reachWmo && *reachWmo > refZ + 3.5f) {
return reachTerrain;
}
@ -479,6 +479,11 @@ void CameraController::update(float deltaTime) {
if (!walkable) {
candidate.x = adjusted.x;
candidate.y = adjusted.y;
// Snap Z to floor at adjusted position to prevent fall-through
auto adjFloor = wmoRenderer->getFloorHeight(adjusted.x, adjusted.y, feetZ + 2.5f);
if (adjFloor && *adjFloor >= feetZ - 1.0f && *adjFloor <= feetZ + 1.6f) {
candidate.z = *adjFloor;
}
} else if (floorH && *floorH > candidate.z) {
// Snap Z to ramp surface so subsequent sweep
// steps measure feetZ from the ramp, not the
@ -611,8 +616,6 @@ void CameraController::update(float deltaTime) {
// Skip entirely while swimming — the swim floor clamp handles vertical bounds.
if (!swimming) {
float stepUpBudget = grounded ? 1.6f : 1.2f;
bool firstPerson = (!thirdPerson) || (currentDistance < 0.6f);
// 1. Center-only sample for terrain/WMO floor selection.
// Using only the center prevents tunnel entrances from snapping
// to terrain when offset samples miss the WMO floor geometry.
@ -624,7 +627,7 @@ void CameraController::update(float deltaTime) {
terrainH = terrainManager->getHeightAt(targetPos.x, targetPos.y);
}
float wmoProbeZ = std::max(targetPos.z, lastGroundZ) + stepUpBudget + 0.5f;
if (wmoRenderer && !firstPerson) {
if (wmoRenderer) {
wmoH = wmoRenderer->getFloorHeight(targetPos.x, targetPos.y, wmoProbeZ);
}
groundH = selectReachableFloor(terrainH, wmoH, targetPos.z, stepUpBudget);

View file

@ -1859,10 +1859,10 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to,
const auto& indices = group.collisionIndices;
// Use spatial grid: query range covering the movement segment + player radius
float rangeMinX = std::min(localFrom.x, localTo.x) - PLAYER_RADIUS - 1.0f;
float rangeMinY = std::min(localFrom.y, localTo.y) - PLAYER_RADIUS - 1.0f;
float rangeMaxX = std::max(localFrom.x, localTo.x) + PLAYER_RADIUS + 1.0f;
float rangeMaxY = std::max(localFrom.y, localTo.y) + PLAYER_RADIUS + 1.0f;
float rangeMinX = std::min(localFrom.x, localTo.x) - PLAYER_RADIUS - 2.5f;
float rangeMinY = std::min(localFrom.y, localTo.y) - PLAYER_RADIUS - 2.5f;
float rangeMaxX = std::max(localFrom.x, localTo.x) + PLAYER_RADIUS + 2.5f;
float rangeMaxY = std::max(localFrom.y, localTo.y) + PLAYER_RADIUS + 2.5f;
group.getTrianglesInRange(rangeMinX, rangeMinY, rangeMaxX, rangeMaxY, wallTriScratch);
for (uint32_t triStart : wallTriScratch) {
@ -1913,7 +1913,7 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to,
glm::vec3 hitPoint = localFrom + (localTo - localFrom) * tHit;
glm::vec3 hitClosest = closestPointOnTriangle(hitPoint, v0, v1, v2);
float hitErrSq = glm::dot(hitClosest - hitPoint, hitClosest - hitPoint);
if (hitErrSq <= 0.04f * 0.04f) {
if (hitErrSq <= 0.5f * 0.5f) {
float side = fromDist > 0.0f ? 1.0f : -1.0f;
glm::vec3 safeLocal = hitPoint + normal * side * (PLAYER_RADIUS + 0.05f);
glm::vec3 safeWorld = glm::vec3(instance.modelMatrix * glm::vec4(safeLocal, 1.0f));