mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Optimize collision further: skip when stationary, cache floor height, fix drop bug
- Skip wall collision sweep entirely when player isn't moving (saves all collision calls when standing still) - Reduce max sweep steps from 4 to 2 with 1.0f step size (all paths: follow, free-fly, swimming) - Cache floor height between frames, reuse when position changes <0.5 units - Fix floor height not updating after walking off tall objects (fountain etc) by always smoothing toward detected ground instead of ignoring drops >2 units - Reduce free-fly ground probes from 5 to 1 - Disable WMO camera collision (raycast + floor probes) for performance - Add spatial grid to raycastBoundingBoxes for when camera collision is re-enabled
This commit is contained in:
parent
974384c725
commit
6516fd777d
3 changed files with 110 additions and 118 deletions
|
|
@ -4,6 +4,7 @@
|
|||
#include "core/input.hpp"
|
||||
#include <SDL2/SDL.h>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
namespace wowee {
|
||||
namespace rendering {
|
||||
|
|
@ -133,6 +134,10 @@ private:
|
|||
static constexpr float JUMP_BUFFER_TIME = 0.15f; // 150ms input buffer
|
||||
static constexpr float COYOTE_TIME = 0.10f; // 100ms grace after leaving ground
|
||||
|
||||
// Cached floor height between frames (skip expensive probes when barely moving)
|
||||
std::optional<float> cachedFloorHeight;
|
||||
glm::vec2 cachedFloorPos = glm::vec2(0.0f);
|
||||
|
||||
// Cached isInsideWMO result (throttled to avoid per-frame cost)
|
||||
bool cachedInsideWMO = false;
|
||||
int insideWMOCheckCounter = 0;
|
||||
|
|
|
|||
|
|
@ -344,36 +344,38 @@ void CameraController::update(float deltaTime) {
|
|||
}
|
||||
}
|
||||
|
||||
// Enforce collision while swimming too (horizontal only), so we don't
|
||||
// pass through walls/props when underwater or at waterline.
|
||||
// Enforce collision while swimming too (horizontal only), skip when stationary.
|
||||
{
|
||||
glm::vec3 swimFrom = *followTarget;
|
||||
glm::vec3 swimTo = targetPos;
|
||||
float swimMoveDist = glm::length(swimTo - swimFrom);
|
||||
int swimSteps = std::max(1, std::min(4, static_cast<int>(std::ceil(swimMoveDist / 0.5f))));
|
||||
glm::vec3 stepPos = swimFrom;
|
||||
glm::vec3 stepDelta = (swimTo - swimFrom) / static_cast<float>(swimSteps);
|
||||
|
||||
for (int i = 0; i < swimSteps; i++) {
|
||||
glm::vec3 candidate = stepPos + stepDelta;
|
||||
if (swimMoveDist > 0.01f) {
|
||||
int swimSteps = std::max(1, std::min(2, static_cast<int>(std::ceil(swimMoveDist / 1.0f))));
|
||||
glm::vec3 stepDelta = (swimTo - swimFrom) / static_cast<float>(swimSteps);
|
||||
|
||||
if (wmoRenderer) {
|
||||
glm::vec3 adjusted;
|
||||
if (wmoRenderer->checkWallCollision(stepPos, candidate, adjusted)) {
|
||||
candidate.x = adjusted.x;
|
||||
candidate.y = adjusted.y;
|
||||
for (int i = 0; i < swimSteps; i++) {
|
||||
glm::vec3 candidate = stepPos + stepDelta;
|
||||
|
||||
if (wmoRenderer) {
|
||||
glm::vec3 adjusted;
|
||||
if (wmoRenderer->checkWallCollision(stepPos, candidate, adjusted)) {
|
||||
candidate.x = adjusted.x;
|
||||
candidate.y = adjusted.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m2Renderer) {
|
||||
glm::vec3 adjusted;
|
||||
if (m2Renderer->checkCollision(stepPos, candidate, adjusted)) {
|
||||
candidate.x = adjusted.x;
|
||||
candidate.y = adjusted.y;
|
||||
if (m2Renderer) {
|
||||
glm::vec3 adjusted;
|
||||
if (m2Renderer->checkCollision(stepPos, candidate, adjusted)) {
|
||||
candidate.x = adjusted.x;
|
||||
candidate.y = adjusted.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stepPos = candidate;
|
||||
stepPos = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
targetPos.x = stepPos.x;
|
||||
|
|
@ -411,39 +413,42 @@ void CameraController::update(float deltaTime) {
|
|||
}
|
||||
|
||||
// Sweep collisions in small steps to reduce tunneling through thin walls/floors.
|
||||
// Skip entirely when stationary to avoid wasting collision calls.
|
||||
{
|
||||
glm::vec3 startPos = *followTarget;
|
||||
glm::vec3 desiredPos = targetPos;
|
||||
float moveDist = glm::length(desiredPos - startPos);
|
||||
// Adaptive CCD: larger step size to reduce collision call count.
|
||||
int sweepSteps = std::max(1, std::min(4, static_cast<int>(std::ceil(moveDist / 0.50f))));
|
||||
glm::vec3 stepPos = startPos;
|
||||
glm::vec3 stepDelta = (desiredPos - startPos) / static_cast<float>(sweepSteps);
|
||||
|
||||
for (int i = 0; i < sweepSteps; i++) {
|
||||
glm::vec3 candidate = stepPos + stepDelta;
|
||||
if (moveDist > 0.01f) {
|
||||
// Adaptive CCD: 1.0f step size, max 2 steps.
|
||||
int sweepSteps = std::max(1, std::min(2, static_cast<int>(std::ceil(moveDist / 1.0f))));
|
||||
glm::vec3 stepPos = startPos;
|
||||
glm::vec3 stepDelta = (desiredPos - startPos) / static_cast<float>(sweepSteps);
|
||||
|
||||
if (wmoRenderer) {
|
||||
glm::vec3 adjusted;
|
||||
if (wmoRenderer->checkWallCollision(stepPos, candidate, adjusted)) {
|
||||
// Keep vertical motion from physics/grounding; only block horizontal wall penetration.
|
||||
candidate.x = adjusted.x;
|
||||
candidate.y = adjusted.y;
|
||||
for (int i = 0; i < sweepSteps; i++) {
|
||||
glm::vec3 candidate = stepPos + stepDelta;
|
||||
|
||||
if (wmoRenderer) {
|
||||
glm::vec3 adjusted;
|
||||
if (wmoRenderer->checkWallCollision(stepPos, candidate, adjusted)) {
|
||||
candidate.x = adjusted.x;
|
||||
candidate.y = adjusted.y;
|
||||
}
|
||||
}
|
||||
|
||||
if (m2Renderer) {
|
||||
glm::vec3 adjusted;
|
||||
if (m2Renderer->checkCollision(stepPos, candidate, adjusted)) {
|
||||
candidate.x = adjusted.x;
|
||||
candidate.y = adjusted.y;
|
||||
}
|
||||
}
|
||||
|
||||
stepPos = candidate;
|
||||
}
|
||||
|
||||
if (m2Renderer) {
|
||||
glm::vec3 adjusted;
|
||||
if (m2Renderer->checkCollision(stepPos, candidate, adjusted)) {
|
||||
candidate.x = adjusted.x;
|
||||
candidate.y = adjusted.y;
|
||||
}
|
||||
}
|
||||
|
||||
stepPos = candidate;
|
||||
targetPos = stepPos;
|
||||
}
|
||||
|
||||
targetPos = stepPos;
|
||||
}
|
||||
|
||||
// WoW-style slope limiting (50 degrees, with sliding)
|
||||
|
|
@ -574,21 +579,29 @@ void CameraController::update(float deltaTime) {
|
|||
return base;
|
||||
};
|
||||
|
||||
// Single center probe — extra probes are too expensive in WMO-heavy areas.
|
||||
std::optional<float> groundH = sampleGround(targetPos.x, targetPos.y);
|
||||
// Use cached floor height if player hasn't moved much horizontally.
|
||||
float floorPosDist = glm::length(glm::vec2(targetPos.x, targetPos.y) - cachedFloorPos);
|
||||
std::optional<float> groundH;
|
||||
if (cachedFloorHeight && floorPosDist < 0.5f) {
|
||||
groundH = cachedFloorHeight;
|
||||
} else {
|
||||
groundH = sampleGround(targetPos.x, targetPos.y);
|
||||
cachedFloorHeight = groundH;
|
||||
cachedFloorPos = glm::vec2(targetPos.x, targetPos.y);
|
||||
}
|
||||
|
||||
if (groundH) {
|
||||
float groundDiff = *groundH - lastGroundZ;
|
||||
if (groundDiff > 2.0f) {
|
||||
// Landing on a higher ledge - snap up
|
||||
lastGroundZ = *groundH;
|
||||
} else if (groundDiff > -2.0f) {
|
||||
// Small height difference - smooth it
|
||||
lastGroundZ += groundDiff * std::min(1.0f, deltaTime * 15.0f);
|
||||
} else {
|
||||
// Smooth toward detected ground. Use a slower rate for large
|
||||
// drops so multi-story buildings don't snap to the wrong floor,
|
||||
// but always converge so walking off a fountain works.
|
||||
float rate = (groundDiff > -2.0f) ? 15.0f : 6.0f;
|
||||
lastGroundZ += groundDiff * std::min(1.0f, deltaTime * rate);
|
||||
}
|
||||
// If groundDiff < -2.0f (floor much lower), ignore it - we're likely
|
||||
// on an upper floor and detecting ground floor through a gap.
|
||||
// Let gravity handle actual falls.
|
||||
|
||||
if (targetPos.z <= lastGroundZ + 0.1f && verticalVelocity <= 0.0f) {
|
||||
targetPos.z = lastGroundZ;
|
||||
|
|
@ -638,41 +651,15 @@ void CameraController::update(float deltaTime) {
|
|||
// Find max safe distance using raycast + sphere radius
|
||||
collisionDistance = currentDistance;
|
||||
|
||||
// Helper to get floor height for camera collision.
|
||||
// Use the player's ground level as reference to avoid locking the camera
|
||||
// to upper floors in multi-story buildings.
|
||||
auto getFloorAt = [&](float x, float y, float /*z*/) -> std::optional<float> {
|
||||
std::optional<float> terrainH;
|
||||
std::optional<float> wmoH;
|
||||
// Camera collision: terrain-only floor clamping (skip expensive WMO raycasts).
|
||||
// The camera may clip through WMO walls but won't go underground.
|
||||
auto getTerrainFloorAt = [&](float x, float y) -> std::optional<float> {
|
||||
if (terrainManager) {
|
||||
terrainH = terrainManager->getHeightAt(x, y);
|
||||
return terrainManager->getHeightAt(x, y);
|
||||
}
|
||||
if (wmoRenderer) {
|
||||
wmoH = wmoRenderer->getFloorHeight(x, y, lastGroundZ + 2.5f);
|
||||
}
|
||||
return selectReachableFloor(terrainH, wmoH, lastGroundZ, 2.0f);
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
// Raycast against WMO bounding boxes
|
||||
if (wmoRenderer && collisionDistance > MIN_DISTANCE) {
|
||||
float wmoHit = wmoRenderer->raycastBoundingBoxes(pivot, camDir, collisionDistance);
|
||||
if (wmoHit < collisionDistance) {
|
||||
collisionDistance = std::max(MIN_DISTANCE, wmoHit - CAM_SPHERE_RADIUS - CAM_EPSILON);
|
||||
}
|
||||
}
|
||||
|
||||
// Intentionally ignore M2 doodads for camera collision to match WoW feel.
|
||||
|
||||
// Check floor collision at the camera's target position
|
||||
{
|
||||
glm::vec3 testPos = pivot + camDir * collisionDistance;
|
||||
auto floorH = getFloorAt(testPos.x, testPos.y, testPos.z);
|
||||
|
||||
if (floorH && testPos.z < *floorH + CAM_SPHERE_RADIUS + CAM_EPSILON) {
|
||||
collisionDistance = std::max(MIN_DISTANCE, collisionDistance - CAM_SPHERE_RADIUS);
|
||||
}
|
||||
}
|
||||
|
||||
// Use collision distance (don't exceed user target)
|
||||
float actualDist = std::min(currentDistance, collisionDistance);
|
||||
|
||||
|
|
@ -692,9 +679,9 @@ void CameraController::update(float deltaTime) {
|
|||
float camLerp = 1.0f - std::exp(-CAM_SMOOTH_SPEED * deltaTime);
|
||||
smoothedCamPos += (actualCam - smoothedCamPos) * camLerp;
|
||||
|
||||
// ===== Final floor clearance check =====
|
||||
// ===== Final floor clearance check (terrain only) =====
|
||||
constexpr float MIN_FLOOR_CLEARANCE = 0.35f;
|
||||
auto finalFloorH = getFloorAt(smoothedCamPos.x, smoothedCamPos.y, smoothedCamPos.z);
|
||||
auto finalFloorH = getTerrainFloorAt(smoothedCamPos.x, smoothedCamPos.y);
|
||||
if (finalFloorH && smoothedCamPos.z < *finalFloorH + MIN_FLOOR_CLEARANCE) {
|
||||
smoothedCamPos.z = *finalFloorH + MIN_FLOOR_CLEARANCE;
|
||||
}
|
||||
|
|
@ -818,26 +805,28 @@ void CameraController::update(float deltaTime) {
|
|||
newPos.z += verticalVelocity * deltaTime;
|
||||
}
|
||||
|
||||
// Wall sweep collision before grounding (reduces tunneling at low FPS/high speed).
|
||||
// Wall sweep collision before grounding (skip when stationary).
|
||||
if (wmoRenderer) {
|
||||
glm::vec3 startFeet = camera->getPosition() - glm::vec3(0, 0, eyeHeight);
|
||||
glm::vec3 desiredFeet = newPos - glm::vec3(0, 0, eyeHeight);
|
||||
float moveDist = glm::length(desiredFeet - startFeet);
|
||||
// Adaptive CCD: larger step size to reduce collision call count.
|
||||
int sweepSteps = std::max(1, std::min(4, static_cast<int>(std::ceil(moveDist / 0.50f))));
|
||||
glm::vec3 stepPos = startFeet;
|
||||
glm::vec3 stepDelta = (desiredFeet - startFeet) / static_cast<float>(sweepSteps);
|
||||
|
||||
for (int i = 0; i < sweepSteps; i++) {
|
||||
glm::vec3 candidate = stepPos + stepDelta;
|
||||
glm::vec3 adjusted;
|
||||
if (wmoRenderer->checkWallCollision(stepPos, candidate, adjusted)) {
|
||||
candidate = adjusted;
|
||||
if (moveDist > 0.01f) {
|
||||
int sweepSteps = std::max(1, std::min(2, static_cast<int>(std::ceil(moveDist / 1.0f))));
|
||||
glm::vec3 stepPos = startFeet;
|
||||
glm::vec3 stepDelta = (desiredFeet - startFeet) / static_cast<float>(sweepSteps);
|
||||
|
||||
for (int i = 0; i < sweepSteps; i++) {
|
||||
glm::vec3 candidate = stepPos + stepDelta;
|
||||
glm::vec3 adjusted;
|
||||
if (wmoRenderer->checkWallCollision(stepPos, candidate, adjusted)) {
|
||||
candidate = adjusted;
|
||||
}
|
||||
stepPos = candidate;
|
||||
}
|
||||
stepPos = candidate;
|
||||
}
|
||||
|
||||
newPos = stepPos + glm::vec3(0, 0, eyeHeight);
|
||||
newPos = stepPos + glm::vec3(0, 0, eyeHeight);
|
||||
}
|
||||
}
|
||||
|
||||
// Ground to terrain or WMO floor
|
||||
|
|
@ -865,29 +854,17 @@ void CameraController::update(float deltaTime) {
|
|||
return base;
|
||||
};
|
||||
|
||||
std::optional<float> groundH;
|
||||
constexpr float FOOTPRINT = 0.4f; // Larger footprint for better floor detection
|
||||
const glm::vec2 offsets[] = {
|
||||
{0.0f, 0.0f}, {FOOTPRINT, 0.0f}, {-FOOTPRINT, 0.0f}, {0.0f, FOOTPRINT}, {0.0f, -FOOTPRINT}
|
||||
};
|
||||
for (const auto& o : offsets) {
|
||||
auto h = sampleGround(newPos.x + o.x, newPos.y + o.y);
|
||||
if (h && (!groundH || *h > *groundH)) {
|
||||
groundH = h;
|
||||
}
|
||||
}
|
||||
// Single center probe.
|
||||
std::optional<float> groundH = sampleGround(newPos.x, newPos.y);
|
||||
|
||||
if (groundH) {
|
||||
float groundDiff = *groundH - lastGroundZ;
|
||||
if (groundDiff > 2.0f) {
|
||||
// Landing on a higher ledge - snap up
|
||||
lastGroundZ = *groundH;
|
||||
} else if (groundDiff > -2.0f) {
|
||||
// Small difference - accept it
|
||||
lastGroundZ = *groundH;
|
||||
} else {
|
||||
float rate = (groundDiff > -2.0f) ? 15.0f : 6.0f;
|
||||
lastGroundZ += groundDiff * std::min(1.0f, deltaTime * rate);
|
||||
}
|
||||
// If groundDiff < -2.0f (floor much lower), ignore it - we're likely
|
||||
// on an upper floor and detecting ground floor through a gap.
|
||||
|
||||
float groundZ = lastGroundZ + eyeHeight;
|
||||
if (newPos.z <= groundZ) {
|
||||
|
|
|
|||
|
|
@ -2019,13 +2019,23 @@ float WMORenderer::raycastBoundingBoxes(const glm::vec3& origin, const glm::vec3
|
|||
continue;
|
||||
}
|
||||
|
||||
// Narrow-phase: triangle raycast for accurate camera collision.
|
||||
// Narrow-phase: triangle raycast using spatial grid.
|
||||
const auto& verts = group.collisionVertices;
|
||||
const auto& indices = group.collisionIndices;
|
||||
for (size_t i = 0; i + 2 < indices.size(); i += 3) {
|
||||
const glm::vec3& v0 = verts[indices[i]];
|
||||
const glm::vec3& v1 = verts[indices[i + 1]];
|
||||
const glm::vec3& v2 = verts[indices[i + 2]];
|
||||
|
||||
// Compute local-space ray endpoint and query grid for XY range
|
||||
glm::vec3 localEnd = localOrigin + localDir * (closestHit / glm::length(
|
||||
glm::vec3(instance.modelMatrix * glm::vec4(localDir, 0.0f))));
|
||||
float rMinX = std::min(localOrigin.x, localEnd.x) - 1.0f;
|
||||
float rMinY = std::min(localOrigin.y, localEnd.y) - 1.0f;
|
||||
float rMaxX = std::max(localOrigin.x, localEnd.x) + 1.0f;
|
||||
float rMaxY = std::max(localOrigin.y, localEnd.y) + 1.0f;
|
||||
group.getTrianglesInRange(rMinX, rMinY, rMaxX, rMaxY, wallTriScratch);
|
||||
|
||||
for (uint32_t triStart : wallTriScratch) {
|
||||
const glm::vec3& v0 = verts[indices[triStart]];
|
||||
const glm::vec3& v1 = verts[indices[triStart + 1]];
|
||||
const glm::vec3& v2 = verts[indices[triStart + 2]];
|
||||
glm::vec3 triNormal = glm::cross(v1 - v0, v2 - v0);
|
||||
float normalLenSq = glm::dot(triNormal, triNormal);
|
||||
if (normalLenSq < 1e-8f) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue