mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Tune collision behavior for ramps, props, and structural walls
This commit is contained in:
parent
f00d13bfc0
commit
871172d63e
4 changed files with 186 additions and 24 deletions
|
|
@ -45,6 +45,8 @@ struct M2ModelGPU {
|
||||||
bool collisionSteppedFountain = false;
|
bool collisionSteppedFountain = false;
|
||||||
bool collisionSteppedLowPlatform = false;
|
bool collisionSteppedLowPlatform = false;
|
||||||
bool collisionPlanter = false;
|
bool collisionPlanter = false;
|
||||||
|
bool collisionSmallSolidProp = false;
|
||||||
|
bool collisionNarrowVerticalProp = false;
|
||||||
bool collisionNoBlock = false;
|
bool collisionNoBlock = false;
|
||||||
|
|
||||||
std::string name;
|
std::string name;
|
||||||
|
|
|
||||||
|
|
@ -295,9 +295,10 @@ void CameraController::update(float deltaTime) {
|
||||||
if (m2Renderer) {
|
if (m2Renderer) {
|
||||||
m2H = m2Renderer->getFloorHeight(x, y, targetPos.z);
|
m2H = m2Renderer->getFloorHeight(x, y, targetPos.z);
|
||||||
}
|
}
|
||||||
auto base = selectReachableFloor(terrainH, wmoH, targetPos.z, 1.0f);
|
float stepUpBudget = grounded ? 1.6f : 1.2f;
|
||||||
|
auto base = selectReachableFloor(terrainH, wmoH, targetPos.z, stepUpBudget);
|
||||||
bool fromM2 = false;
|
bool fromM2 = false;
|
||||||
if (m2H && *m2H <= targetPos.z + 1.0f && (!base || *m2H > *base)) {
|
if (m2H && *m2H <= targetPos.z + stepUpBudget && (!base || *m2H > *base)) {
|
||||||
base = m2H;
|
base = m2H;
|
||||||
fromM2 = true;
|
fromM2 = true;
|
||||||
}
|
}
|
||||||
|
|
@ -382,8 +383,9 @@ void CameraController::update(float deltaTime) {
|
||||||
if (m2Renderer) {
|
if (m2Renderer) {
|
||||||
m2H = m2Renderer->getFloorHeight(x, y, targetPos.z);
|
m2H = m2Renderer->getFloorHeight(x, y, targetPos.z);
|
||||||
}
|
}
|
||||||
auto base = selectReachableFloor(terrainH, wmoH, targetPos.z, 1.0f);
|
float stepUpBudget = grounded ? 1.6f : 1.2f;
|
||||||
if (m2H && *m2H <= targetPos.z + 1.0f && (!base || *m2H > *base)) {
|
auto base = selectReachableFloor(terrainH, wmoH, targetPos.z, stepUpBudget);
|
||||||
|
if (m2H && *m2H <= targetPos.z + stepUpBudget && (!base || *m2H > *base)) {
|
||||||
base = m2H;
|
base = m2H;
|
||||||
}
|
}
|
||||||
return base;
|
return base;
|
||||||
|
|
@ -393,7 +395,9 @@ void CameraController::update(float deltaTime) {
|
||||||
std::optional<float> groundH;
|
std::optional<float> groundH;
|
||||||
constexpr float FOOTPRINT = 0.28f;
|
constexpr float FOOTPRINT = 0.28f;
|
||||||
const glm::vec2 offsets[] = {
|
const glm::vec2 offsets[] = {
|
||||||
{0.0f, 0.0f}, {FOOTPRINT, 0.0f}, {0.0f, FOOTPRINT}
|
{0.0f, 0.0f},
|
||||||
|
{FOOTPRINT, 0.0f}, {-FOOTPRINT, 0.0f},
|
||||||
|
{0.0f, FOOTPRINT}, {0.0f, -FOOTPRINT}
|
||||||
};
|
};
|
||||||
for (const auto& o : offsets) {
|
for (const auto& o : offsets) {
|
||||||
auto h = sampleGround(targetPos.x + o.x, targetPos.y + o.y);
|
auto h = sampleGround(targetPos.x + o.x, targetPos.y + o.y);
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,23 @@ void getTightCollisionBounds(const M2ModelGPU& model, glm::vec3& outMin, glm::ve
|
||||||
glm::vec3 half = (model.boundMax - model.boundMin) * 0.5f;
|
glm::vec3 half = (model.boundMax - model.boundMin) * 0.5f;
|
||||||
|
|
||||||
// Per-shape collision fitting:
|
// Per-shape collision fitting:
|
||||||
|
// - small solid props (boxes/crates/chests): tighter than full mesh, but
|
||||||
|
// larger than default to prevent walk-through on narrow objects
|
||||||
// - default: tighter fit (avoid oversized blockers)
|
// - default: tighter fit (avoid oversized blockers)
|
||||||
// - stepped low platforms (tree curbs/planters): wider XY + lower Z
|
// - stepped low platforms (tree curbs/planters): wider XY + lower Z
|
||||||
if (model.collisionSteppedLowPlatform) {
|
if (model.collisionNarrowVerticalProp) {
|
||||||
half.x *= 0.92f;
|
// Tall thin props (lamps/posts): keep passable gaps near walls.
|
||||||
half.y *= 0.92f;
|
half.x *= 0.30f;
|
||||||
|
half.y *= 0.30f;
|
||||||
|
half.z *= 0.96f;
|
||||||
|
} else if (model.collisionSmallSolidProp) {
|
||||||
|
// Keep full tight mesh bounds for small solid props to avoid clip-through.
|
||||||
|
half.x *= 1.00f;
|
||||||
|
half.y *= 1.00f;
|
||||||
|
half.z *= 1.00f;
|
||||||
|
} else if (model.collisionSteppedLowPlatform) {
|
||||||
|
half.x *= 0.98f;
|
||||||
|
half.y *= 0.98f;
|
||||||
half.z *= 0.52f;
|
half.z *= 0.52f;
|
||||||
} else {
|
} else {
|
||||||
half.x *= 0.66f;
|
half.x *= 0.66f;
|
||||||
|
|
@ -352,6 +364,11 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) {
|
||||||
|
|
||||||
bool isPlanter = (lowerName.find("planter") != std::string::npos);
|
bool isPlanter = (lowerName.find("planter") != std::string::npos);
|
||||||
gpuModel.collisionPlanter = isPlanter;
|
gpuModel.collisionPlanter = isPlanter;
|
||||||
|
bool smallSolidPropName =
|
||||||
|
(lowerName.find("crate") != std::string::npos) ||
|
||||||
|
(lowerName.find("box") != std::string::npos) ||
|
||||||
|
(lowerName.find("chest") != std::string::npos) ||
|
||||||
|
(lowerName.find("barrel") != std::string::npos);
|
||||||
bool foliageName =
|
bool foliageName =
|
||||||
(lowerName.find("bush") != std::string::npos) ||
|
(lowerName.find("bush") != std::string::npos) ||
|
||||||
(lowerName.find("grass") != std::string::npos) ||
|
(lowerName.find("grass") != std::string::npos) ||
|
||||||
|
|
@ -373,6 +390,33 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) {
|
||||||
bool smallSoftShape = (horiz < 2.2f && vert < 2.4f);
|
bool smallSoftShape = (horiz < 2.2f && vert < 2.4f);
|
||||||
bool mediumFoliageShape = (horiz < 4.5f && vert < 4.5f);
|
bool mediumFoliageShape = (horiz < 4.5f && vert < 4.5f);
|
||||||
bool forceSolidCurb = gpuModel.collisionSteppedLowPlatform || knownStormwindPlanter || likelyCurbName || gpuModel.collisionPlanter;
|
bool forceSolidCurb = gpuModel.collisionSteppedLowPlatform || knownStormwindPlanter || likelyCurbName || gpuModel.collisionPlanter;
|
||||||
|
bool narrowVerticalName =
|
||||||
|
(lowerName.find("lamp") != std::string::npos) ||
|
||||||
|
(lowerName.find("lantern") != std::string::npos) ||
|
||||||
|
(lowerName.find("post") != std::string::npos) ||
|
||||||
|
(lowerName.find("pole") != std::string::npos);
|
||||||
|
bool narrowVerticalShape =
|
||||||
|
(horiz > 0.12f && horiz < 2.0f && vert > 2.2f && vert > horiz * 1.8f);
|
||||||
|
gpuModel.collisionNarrowVerticalProp =
|
||||||
|
!gpuModel.collisionSteppedFountain &&
|
||||||
|
!gpuModel.collisionSteppedLowPlatform &&
|
||||||
|
(narrowVerticalName || narrowVerticalShape);
|
||||||
|
bool genericSolidPropShape =
|
||||||
|
(horiz > 0.6f && horiz < 6.0f && vert > 0.30f && vert < 4.0f && vert > horiz * 0.16f);
|
||||||
|
bool curbLikeName =
|
||||||
|
(lowerName.find("curb") != std::string::npos) ||
|
||||||
|
(lowerName.find("planter") != std::string::npos) ||
|
||||||
|
(lowerName.find("ring") != std::string::npos) ||
|
||||||
|
(lowerName.find("well") != std::string::npos) ||
|
||||||
|
(lowerName.find("base") != std::string::npos);
|
||||||
|
bool lowPlatformLikeShape = lowWideShape || lowPlatformShape;
|
||||||
|
gpuModel.collisionSmallSolidProp =
|
||||||
|
!gpuModel.collisionSteppedFountain &&
|
||||||
|
!gpuModel.collisionSteppedLowPlatform &&
|
||||||
|
!gpuModel.collisionNarrowVerticalProp &&
|
||||||
|
!curbLikeName &&
|
||||||
|
!lowPlatformLikeShape &&
|
||||||
|
(smallSolidPropName || (genericSolidPropShape && !foliageName && !softTree));
|
||||||
gpuModel.collisionNoBlock = ((((foliageName && smallSoftShape) || (foliageName && mediumFoliageShape)) || softTree) &&
|
gpuModel.collisionNoBlock = ((((foliageName && smallSoftShape) || (foliageName && mediumFoliageShape)) || softTree) &&
|
||||||
!forceSolidCurb);
|
!forceSolidCurb);
|
||||||
}
|
}
|
||||||
|
|
@ -881,8 +925,13 @@ std::optional<float> M2Renderer::getFloorHeight(float glX, float glY, float glZ)
|
||||||
glm::vec3 localPos = glm::vec3(instance.invModelMatrix * glm::vec4(glX, glY, glZ, 1.0f));
|
glm::vec3 localPos = glm::vec3(instance.invModelMatrix * glm::vec4(glX, glY, glZ, 1.0f));
|
||||||
|
|
||||||
// Must be within doodad footprint in local XY.
|
// Must be within doodad footprint in local XY.
|
||||||
if (localPos.x < localMin.x || localPos.x > localMax.x ||
|
// Stepped low platforms get a small pad so walk-up snapping catches edges.
|
||||||
localPos.y < localMin.y || localPos.y > localMax.y) {
|
float footprintPad = 0.0f;
|
||||||
|
if (model.collisionSteppedLowPlatform) {
|
||||||
|
footprintPad = model.collisionPlanter ? 0.22f : 0.16f;
|
||||||
|
}
|
||||||
|
if (localPos.x < localMin.x - footprintPad || localPos.x > localMax.x + footprintPad ||
|
||||||
|
localPos.y < localMin.y - footprintPad || localPos.y > localMax.y + footprintPad) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -892,7 +941,12 @@ std::optional<float> M2Renderer::getFloorHeight(float glX, float glY, float glZ)
|
||||||
glm::vec3 worldTop = glm::vec3(instance.modelMatrix * glm::vec4(localTop, 1.0f));
|
glm::vec3 worldTop = glm::vec3(instance.modelMatrix * glm::vec4(localTop, 1.0f));
|
||||||
|
|
||||||
// Reachability filter: allow a bit more climb for stepped low platforms.
|
// Reachability filter: allow a bit more climb for stepped low platforms.
|
||||||
float maxStepUp = model.collisionSteppedLowPlatform ? (model.collisionPlanter ? 2.2f : 1.8f) : 1.0f;
|
float maxStepUp = 1.0f;
|
||||||
|
if (model.collisionSmallSolidProp) {
|
||||||
|
maxStepUp = 2.0f;
|
||||||
|
} else if (model.collisionSteppedLowPlatform) {
|
||||||
|
maxStepUp = model.collisionPlanter ? 3.0f : 2.4f;
|
||||||
|
}
|
||||||
if (worldTop.z > glZ + maxStepUp) continue;
|
if (worldTop.z > glZ + maxStepUp) continue;
|
||||||
|
|
||||||
if (!bestFloor || worldTop.z > *bestFloor) {
|
if (!bestFloor || worldTop.z > *bestFloor) {
|
||||||
|
|
@ -938,13 +992,17 @@ bool M2Renderer::checkCollision(const glm::vec3& from, const glm::vec3& to,
|
||||||
|
|
||||||
glm::vec3 localFrom = glm::vec3(instance.invModelMatrix * glm::vec4(from, 1.0f));
|
glm::vec3 localFrom = glm::vec3(instance.invModelMatrix * glm::vec4(from, 1.0f));
|
||||||
glm::vec3 localPos = glm::vec3(instance.invModelMatrix * glm::vec4(adjustedPos, 1.0f));
|
glm::vec3 localPos = glm::vec3(instance.invModelMatrix * glm::vec4(adjustedPos, 1.0f));
|
||||||
float localRadius = playerRadius / instance.scale;
|
float radiusScale = model.collisionNarrowVerticalProp ? 0.45f : 1.0f;
|
||||||
|
float localRadius = (playerRadius * radiusScale) / instance.scale;
|
||||||
|
|
||||||
glm::vec3 rawMin, rawMax;
|
glm::vec3 rawMin, rawMax;
|
||||||
getTightCollisionBounds(model, rawMin, rawMax);
|
getTightCollisionBounds(model, rawMin, rawMax);
|
||||||
glm::vec3 localMin = rawMin - glm::vec3(localRadius);
|
glm::vec3 localMin = rawMin - glm::vec3(localRadius);
|
||||||
glm::vec3 localMax = rawMax + glm::vec3(localRadius);
|
glm::vec3 localMax = rawMax + glm::vec3(localRadius);
|
||||||
float effectiveTop = getEffectiveCollisionTopLocal(model, localPos, rawMin, rawMax) + localRadius;
|
float effectiveTop = getEffectiveCollisionTopLocal(model, localPos, rawMin, rawMax) + localRadius;
|
||||||
|
glm::vec2 localCenter((localMin.x + localMax.x) * 0.5f, (localMin.y + localMax.y) * 0.5f);
|
||||||
|
float fromR = glm::length(glm::vec2(localFrom.x, localFrom.y) - localCenter);
|
||||||
|
float toR = glm::length(glm::vec2(localPos.x, localPos.y) - localCenter);
|
||||||
|
|
||||||
// Feet-based vertical overlap test: ignore objects fully above/below us.
|
// Feet-based vertical overlap test: ignore objects fully above/below us.
|
||||||
constexpr float PLAYER_HEIGHT = 2.0f;
|
constexpr float PLAYER_HEIGHT = 2.0f;
|
||||||
|
|
@ -952,15 +1010,38 @@ bool M2Renderer::checkCollision(const glm::vec3& from, const glm::vec3& to,
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool fromInsideXY =
|
||||||
|
(localFrom.x >= localMin.x && localFrom.x <= localMax.x &&
|
||||||
|
localFrom.y >= localMin.y && localFrom.y <= localMax.y);
|
||||||
|
bool fromInsideZ = (localFrom.z + PLAYER_HEIGHT >= localMin.z && localFrom.z <= effectiveTop);
|
||||||
|
bool escapingOverlap = (fromInsideXY && fromInsideZ && (toR > fromR + 1e-4f));
|
||||||
|
bool allowEscapeRelax = escapingOverlap && !model.collisionSmallSolidProp;
|
||||||
|
|
||||||
// Swept hard clamp for taller blockers only.
|
// Swept hard clamp for taller blockers only.
|
||||||
// Low/stepable objects should be climbable and not "shove" the player off.
|
// Low/stepable objects should be climbable and not "shove" the player off.
|
||||||
float maxStepUp = model.collisionSteppedLowPlatform ? (model.collisionPlanter ? 2.8f : 2.4f) : 1.20f;
|
float maxStepUp = 1.20f;
|
||||||
|
if (model.collisionSmallSolidProp) {
|
||||||
|
// Keep box/crate-class props hard-solid to prevent phase-through.
|
||||||
|
maxStepUp = 0.75f;
|
||||||
|
} else if (model.collisionSteppedLowPlatform) {
|
||||||
|
maxStepUp = model.collisionPlanter ? 2.8f : 2.4f;
|
||||||
|
}
|
||||||
bool stepableLowObject = (effectiveTop <= localFrom.z + maxStepUp);
|
bool stepableLowObject = (effectiveTop <= localFrom.z + maxStepUp);
|
||||||
bool climbingAttempt = (localPos.z > localFrom.z + 0.18f);
|
bool climbingAttempt = (localPos.z > localFrom.z + 0.18f);
|
||||||
bool nearTop = (localFrom.z >= effectiveTop - 0.30f);
|
bool nearTop = (localFrom.z >= effectiveTop - 0.30f);
|
||||||
bool climbingTowardTop = climbingAttempt && (localFrom.z + (model.collisionPlanter ? 0.95f : 0.60f) >= effectiveTop);
|
float climbAllowance = model.collisionPlanter ? 0.95f : 0.60f;
|
||||||
bool forceHardLateral = model.collisionSteppedLowPlatform && !model.collisionPlanter && !nearTop && !climbingTowardTop;
|
if (model.collisionSteppedLowPlatform && !model.collisionPlanter) {
|
||||||
if (!stepableLowObject || forceHardLateral) {
|
// Let low curb/planter blocks be stepable without sticky side shoves.
|
||||||
|
climbAllowance = 1.00f;
|
||||||
|
}
|
||||||
|
if (model.collisionSmallSolidProp) {
|
||||||
|
climbAllowance = 1.05f;
|
||||||
|
}
|
||||||
|
bool climbingTowardTop = climbingAttempt && (localFrom.z + climbAllowance >= effectiveTop);
|
||||||
|
bool forceHardLateral =
|
||||||
|
model.collisionSmallSolidProp &&
|
||||||
|
!nearTop && !climbingTowardTop;
|
||||||
|
if ((!stepableLowObject || forceHardLateral) && !allowEscapeRelax) {
|
||||||
float tEnter = 0.0f;
|
float tEnter = 0.0f;
|
||||||
glm::vec3 sweepMax = localMax;
|
glm::vec3 sweepMax = localMax;
|
||||||
sweepMax.z = std::min(sweepMax.z, effectiveTop);
|
sweepMax.z = std::min(sweepMax.z, effectiveTop);
|
||||||
|
|
@ -986,14 +1067,19 @@ bool M2Renderer::checkCollision(const glm::vec3& from, const glm::vec3& to,
|
||||||
float pushFront = localMax.y - localPos.y;
|
float pushFront = localMax.y - localPos.y;
|
||||||
|
|
||||||
float minPush = std::min({pushLeft, pushRight, pushBack, pushFront});
|
float minPush = std::min({pushLeft, pushRight, pushBack, pushFront});
|
||||||
if (model.collisionSteppedLowPlatform && nearTop && stepableLowObject) {
|
if (allowEscapeRelax) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (model.collisionSteppedLowPlatform && stepableLowObject) {
|
||||||
// Already on/near top surface: don't apply lateral push that ejects
|
// Already on/near top surface: don't apply lateral push that ejects
|
||||||
// the player from the curb when landing.
|
// the player from the object when landing.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Gentle fallback push for overlapping cases.
|
// Gentle fallback push for overlapping cases.
|
||||||
float pushAmount;
|
float pushAmount;
|
||||||
if (model.collisionSteppedLowPlatform) {
|
if (model.collisionNarrowVerticalProp) {
|
||||||
|
pushAmount = std::clamp(minPush * 0.10f, 0.001f, 0.010f);
|
||||||
|
} else if (model.collisionSteppedLowPlatform) {
|
||||||
if (model.collisionPlanter && stepableLowObject) {
|
if (model.collisionPlanter && stepableLowObject) {
|
||||||
pushAmount = std::clamp(minPush * 0.06f, 0.001f, 0.006f);
|
pushAmount = std::clamp(minPush * 0.06f, 0.001f, 0.006f);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1067,6 +1067,10 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to,
|
||||||
glm::vec3 localFrom = glm::vec3(instance.invModelMatrix * glm::vec4(from, 1.0f));
|
glm::vec3 localFrom = glm::vec3(instance.invModelMatrix * glm::vec4(from, 1.0f));
|
||||||
glm::vec3 localTo = glm::vec3(instance.invModelMatrix * glm::vec4(to, 1.0f));
|
glm::vec3 localTo = glm::vec3(instance.invModelMatrix * glm::vec4(to, 1.0f));
|
||||||
float localFeetZ = localTo.z;
|
float localFeetZ = localTo.z;
|
||||||
|
// Use a tiny Z threshold so ramp-side logic still triggers with
|
||||||
|
// smoothed ground snapping and small per-step vertical deltas.
|
||||||
|
bool steppingUp = (localTo.z > localFrom.z + 0.005f);
|
||||||
|
bool steppingDown = (localTo.z < localFrom.z - 0.005f);
|
||||||
|
|
||||||
for (const auto& group : model.groups) {
|
for (const auto& group : model.groups) {
|
||||||
// Quick bounding box check
|
// Quick bounding box check
|
||||||
|
|
@ -1100,6 +1104,10 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to,
|
||||||
// Get triangle Z range
|
// Get triangle Z range
|
||||||
float triMinZ = std::min({v0.z, v1.z, v2.z});
|
float triMinZ = std::min({v0.z, v1.z, v2.z});
|
||||||
float triMaxZ = std::max({v0.z, v1.z, v2.z});
|
float triMaxZ = std::max({v0.z, v1.z, v2.z});
|
||||||
|
float fromDist = glm::dot(localFrom - v0, normal);
|
||||||
|
float toDist = glm::dot(localTo - v0, normal);
|
||||||
|
bool towardWallMotion = (std::abs(toDist) + 1e-4f < std::abs(fromDist));
|
||||||
|
bool awayFromWallMotion = !towardWallMotion;
|
||||||
|
|
||||||
// Only collide with walls in player's vertical range
|
// Only collide with walls in player's vertical range
|
||||||
if (triMaxZ < localFeetZ + 0.3f) continue;
|
if (triMaxZ < localFeetZ + 0.3f) continue;
|
||||||
|
|
@ -1112,17 +1120,56 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to,
|
||||||
// Ignore short near-vertical side strips around ramps/edges.
|
// Ignore short near-vertical side strips around ramps/edges.
|
||||||
// These commonly act like invisible side guard rails.
|
// These commonly act like invisible side guard rails.
|
||||||
float triHeight = triMaxZ - triMinZ;
|
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 &&
|
if (std::abs(normal.z) < 0.25f &&
|
||||||
triHeight < 1.8f &&
|
triHeight < 1.8f &&
|
||||||
triMaxZ <= localFeetZ + 1.4f) {
|
triMaxZ <= localFeetZ + 1.4f) {
|
||||||
continue;
|
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
|
// Let players run off ramp sides: ignore lower side-wall strips
|
||||||
// that sit around foot height and are not true tall building walls.
|
// that sit around foot height and are not true tall building walls.
|
||||||
if (std::abs(normal.z) < 0.45f &&
|
if (std::abs(normal.z) < 0.45f &&
|
||||||
|
std::abs(normal.z) > 0.05f &&
|
||||||
triMinZ <= localFeetZ + 0.20f &&
|
triMinZ <= localFeetZ + 0.20f &&
|
||||||
triHeight < 4.0f &&
|
triHeight < 4.0f &&
|
||||||
triMaxZ <= localFeetZ + 4.0f) {
|
triMaxZ <= localFeetZ + 4.0f &&
|
||||||
|
!likelyRealWall && !structuralWall) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1130,10 +1177,22 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to,
|
||||||
if (triMaxZ <= localFeetZ + stepHeightLimit) continue; // Treat as step-up, not hard wall
|
if (triMaxZ <= localFeetZ + stepHeightLimit) continue; // Treat as step-up, not hard wall
|
||||||
|
|
||||||
// Swept test: prevent tunneling when crossing a wall between frames.
|
// Swept test: prevent tunneling when crossing a wall between frames.
|
||||||
float fromDist = glm::dot(localFrom - v0, normal);
|
bool shortRampEdgeStrip =
|
||||||
float toDist = glm::dot(localTo - v0, normal);
|
(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) ||
|
if ((fromDist > PLAYER_RADIUS && toDist < -PLAYER_RADIUS) ||
|
||||||
(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);
|
float denom = (fromDist - toDist);
|
||||||
if (std::abs(denom) > 1e-6f) {
|
if (std::abs(denom) > 1e-6f) {
|
||||||
float tHit = fromDist / denom; // Segment param [0,1]
|
float tHit = fromDist / denom; // Segment param [0,1]
|
||||||
|
|
@ -1172,8 +1231,19 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to,
|
||||||
pushDir2 = glm::normalize(n2);
|
pushDir2 = glm::normalize(n2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Soft pushback avoids hard side-snaps when skimming walls.
|
// Softer push when stepping up near ramp side edges.
|
||||||
pushDist = std::min(0.06f, pushDist * 0.35f);
|
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;
|
if (pushDist <= 0.0f) continue;
|
||||||
glm::vec3 pushLocal(pushDir2.x * pushDist, pushDir2.y * pushDist, 0.0f);
|
glm::vec3 pushLocal(pushDir2.x * pushDist, pushDir2.y * pushDist, 0.0f);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue