mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 17:43:52 +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 "core/input.hpp"
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
namespace wowee {
|
namespace wowee {
|
||||||
namespace rendering {
|
namespace rendering {
|
||||||
|
|
@ -133,6 +134,10 @@ private:
|
||||||
static constexpr float JUMP_BUFFER_TIME = 0.15f; // 150ms input buffer
|
static constexpr float JUMP_BUFFER_TIME = 0.15f; // 150ms input buffer
|
||||||
static constexpr float COYOTE_TIME = 0.10f; // 100ms grace after leaving ground
|
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)
|
// Cached isInsideWMO result (throttled to avoid per-frame cost)
|
||||||
bool cachedInsideWMO = false;
|
bool cachedInsideWMO = false;
|
||||||
int insideWMOCheckCounter = 0;
|
int insideWMOCheckCounter = 0;
|
||||||
|
|
|
||||||
|
|
@ -344,36 +344,38 @@ void CameraController::update(float deltaTime) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enforce collision while swimming too (horizontal only), so we don't
|
// Enforce collision while swimming too (horizontal only), skip when stationary.
|
||||||
// pass through walls/props when underwater or at waterline.
|
|
||||||
{
|
{
|
||||||
glm::vec3 swimFrom = *followTarget;
|
glm::vec3 swimFrom = *followTarget;
|
||||||
glm::vec3 swimTo = targetPos;
|
glm::vec3 swimTo = targetPos;
|
||||||
float swimMoveDist = glm::length(swimTo - swimFrom);
|
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 stepPos = swimFrom;
|
||||||
glm::vec3 stepDelta = (swimTo - swimFrom) / static_cast<float>(swimSteps);
|
|
||||||
|
|
||||||
for (int i = 0; i < swimSteps; i++) {
|
if (swimMoveDist > 0.01f) {
|
||||||
glm::vec3 candidate = stepPos + stepDelta;
|
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) {
|
for (int i = 0; i < swimSteps; i++) {
|
||||||
glm::vec3 adjusted;
|
glm::vec3 candidate = stepPos + stepDelta;
|
||||||
if (wmoRenderer->checkWallCollision(stepPos, candidate, adjusted)) {
|
|
||||||
candidate.x = adjusted.x;
|
if (wmoRenderer) {
|
||||||
candidate.y = adjusted.y;
|
glm::vec3 adjusted;
|
||||||
|
if (wmoRenderer->checkWallCollision(stepPos, candidate, adjusted)) {
|
||||||
|
candidate.x = adjusted.x;
|
||||||
|
candidate.y = adjusted.y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (m2Renderer) {
|
if (m2Renderer) {
|
||||||
glm::vec3 adjusted;
|
glm::vec3 adjusted;
|
||||||
if (m2Renderer->checkCollision(stepPos, candidate, adjusted)) {
|
if (m2Renderer->checkCollision(stepPos, candidate, adjusted)) {
|
||||||
candidate.x = adjusted.x;
|
candidate.x = adjusted.x;
|
||||||
candidate.y = adjusted.y;
|
candidate.y = adjusted.y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
stepPos = candidate;
|
stepPos = candidate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
targetPos.x = stepPos.x;
|
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.
|
// 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 startPos = *followTarget;
|
||||||
glm::vec3 desiredPos = targetPos;
|
glm::vec3 desiredPos = targetPos;
|
||||||
float moveDist = glm::length(desiredPos - startPos);
|
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++) {
|
if (moveDist > 0.01f) {
|
||||||
glm::vec3 candidate = stepPos + stepDelta;
|
// 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) {
|
for (int i = 0; i < sweepSteps; i++) {
|
||||||
glm::vec3 adjusted;
|
glm::vec3 candidate = stepPos + stepDelta;
|
||||||
if (wmoRenderer->checkWallCollision(stepPos, candidate, adjusted)) {
|
|
||||||
// Keep vertical motion from physics/grounding; only block horizontal wall penetration.
|
if (wmoRenderer) {
|
||||||
candidate.x = adjusted.x;
|
glm::vec3 adjusted;
|
||||||
candidate.y = adjusted.y;
|
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) {
|
targetPos = stepPos;
|
||||||
glm::vec3 adjusted;
|
|
||||||
if (m2Renderer->checkCollision(stepPos, candidate, adjusted)) {
|
|
||||||
candidate.x = adjusted.x;
|
|
||||||
candidate.y = adjusted.y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stepPos = candidate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
targetPos = stepPos;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WoW-style slope limiting (50 degrees, with sliding)
|
// WoW-style slope limiting (50 degrees, with sliding)
|
||||||
|
|
@ -574,21 +579,29 @@ void CameraController::update(float deltaTime) {
|
||||||
return base;
|
return base;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Single center probe — extra probes are too expensive in WMO-heavy areas.
|
// Use cached floor height if player hasn't moved much horizontally.
|
||||||
std::optional<float> groundH = sampleGround(targetPos.x, targetPos.y);
|
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) {
|
if (groundH) {
|
||||||
float groundDiff = *groundH - lastGroundZ;
|
float groundDiff = *groundH - lastGroundZ;
|
||||||
if (groundDiff > 2.0f) {
|
if (groundDiff > 2.0f) {
|
||||||
// Landing on a higher ledge - snap up
|
// Landing on a higher ledge - snap up
|
||||||
lastGroundZ = *groundH;
|
lastGroundZ = *groundH;
|
||||||
} else if (groundDiff > -2.0f) {
|
} else {
|
||||||
// Small height difference - smooth it
|
// Smooth toward detected ground. Use a slower rate for large
|
||||||
lastGroundZ += groundDiff * std::min(1.0f, deltaTime * 15.0f);
|
// 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) {
|
if (targetPos.z <= lastGroundZ + 0.1f && verticalVelocity <= 0.0f) {
|
||||||
targetPos.z = lastGroundZ;
|
targetPos.z = lastGroundZ;
|
||||||
|
|
@ -638,41 +651,15 @@ void CameraController::update(float deltaTime) {
|
||||||
// Find max safe distance using raycast + sphere radius
|
// Find max safe distance using raycast + sphere radius
|
||||||
collisionDistance = currentDistance;
|
collisionDistance = currentDistance;
|
||||||
|
|
||||||
// Helper to get floor height for camera collision.
|
// Camera collision: terrain-only floor clamping (skip expensive WMO raycasts).
|
||||||
// Use the player's ground level as reference to avoid locking the camera
|
// The camera may clip through WMO walls but won't go underground.
|
||||||
// to upper floors in multi-story buildings.
|
auto getTerrainFloorAt = [&](float x, float y) -> std::optional<float> {
|
||||||
auto getFloorAt = [&](float x, float y, float /*z*/) -> std::optional<float> {
|
|
||||||
std::optional<float> terrainH;
|
|
||||||
std::optional<float> wmoH;
|
|
||||||
if (terrainManager) {
|
if (terrainManager) {
|
||||||
terrainH = terrainManager->getHeightAt(x, y);
|
return terrainManager->getHeightAt(x, y);
|
||||||
}
|
}
|
||||||
if (wmoRenderer) {
|
return std::nullopt;
|
||||||
wmoH = wmoRenderer->getFloorHeight(x, y, lastGroundZ + 2.5f);
|
|
||||||
}
|
|
||||||
return selectReachableFloor(terrainH, wmoH, lastGroundZ, 2.0f);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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)
|
// Use collision distance (don't exceed user target)
|
||||||
float actualDist = std::min(currentDistance, collisionDistance);
|
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);
|
float camLerp = 1.0f - std::exp(-CAM_SMOOTH_SPEED * deltaTime);
|
||||||
smoothedCamPos += (actualCam - smoothedCamPos) * camLerp;
|
smoothedCamPos += (actualCam - smoothedCamPos) * camLerp;
|
||||||
|
|
||||||
// ===== Final floor clearance check =====
|
// ===== Final floor clearance check (terrain only) =====
|
||||||
constexpr float MIN_FLOOR_CLEARANCE = 0.35f;
|
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) {
|
if (finalFloorH && smoothedCamPos.z < *finalFloorH + MIN_FLOOR_CLEARANCE) {
|
||||||
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;
|
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) {
|
if (wmoRenderer) {
|
||||||
glm::vec3 startFeet = camera->getPosition() - glm::vec3(0, 0, eyeHeight);
|
glm::vec3 startFeet = camera->getPosition() - glm::vec3(0, 0, eyeHeight);
|
||||||
glm::vec3 desiredFeet = newPos - glm::vec3(0, 0, eyeHeight);
|
glm::vec3 desiredFeet = newPos - glm::vec3(0, 0, eyeHeight);
|
||||||
float moveDist = glm::length(desiredFeet - startFeet);
|
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++) {
|
if (moveDist > 0.01f) {
|
||||||
glm::vec3 candidate = stepPos + stepDelta;
|
int sweepSteps = std::max(1, std::min(2, static_cast<int>(std::ceil(moveDist / 1.0f))));
|
||||||
glm::vec3 adjusted;
|
glm::vec3 stepPos = startFeet;
|
||||||
if (wmoRenderer->checkWallCollision(stepPos, candidate, adjusted)) {
|
glm::vec3 stepDelta = (desiredFeet - startFeet) / static_cast<float>(sweepSteps);
|
||||||
candidate = adjusted;
|
|
||||||
|
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
|
// Ground to terrain or WMO floor
|
||||||
|
|
@ -865,29 +854,17 @@ void CameraController::update(float deltaTime) {
|
||||||
return base;
|
return base;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::optional<float> groundH;
|
// Single center probe.
|
||||||
constexpr float FOOTPRINT = 0.4f; // Larger footprint for better floor detection
|
std::optional<float> groundH = sampleGround(newPos.x, newPos.y);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (groundH) {
|
if (groundH) {
|
||||||
float groundDiff = *groundH - lastGroundZ;
|
float groundDiff = *groundH - lastGroundZ;
|
||||||
if (groundDiff > 2.0f) {
|
if (groundDiff > 2.0f) {
|
||||||
// Landing on a higher ledge - snap up
|
|
||||||
lastGroundZ = *groundH;
|
|
||||||
} else if (groundDiff > -2.0f) {
|
|
||||||
// Small difference - accept it
|
|
||||||
lastGroundZ = *groundH;
|
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;
|
float groundZ = lastGroundZ + eyeHeight;
|
||||||
if (newPos.z <= groundZ) {
|
if (newPos.z <= groundZ) {
|
||||||
|
|
|
||||||
|
|
@ -2019,13 +2019,23 @@ float WMORenderer::raycastBoundingBoxes(const glm::vec3& origin, const glm::vec3
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Narrow-phase: triangle raycast for accurate camera collision.
|
// Narrow-phase: triangle raycast using spatial grid.
|
||||||
const auto& verts = group.collisionVertices;
|
const auto& verts = group.collisionVertices;
|
||||||
const auto& indices = group.collisionIndices;
|
const auto& indices = group.collisionIndices;
|
||||||
for (size_t i = 0; i + 2 < indices.size(); i += 3) {
|
|
||||||
const glm::vec3& v0 = verts[indices[i]];
|
// Compute local-space ray endpoint and query grid for XY range
|
||||||
const glm::vec3& v1 = verts[indices[i + 1]];
|
glm::vec3 localEnd = localOrigin + localDir * (closestHit / glm::length(
|
||||||
const glm::vec3& v2 = verts[indices[i + 2]];
|
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);
|
glm::vec3 triNormal = glm::cross(v1 - v0, v2 - v0);
|
||||||
float normalLenSq = glm::dot(triNormal, triNormal);
|
float normalLenSq = glm::dot(triNormal, triNormal);
|
||||||
if (normalLenSq < 1e-8f) {
|
if (normalLenSq < 1e-8f) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue