mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Optimize city performance and harden WMO grounding
This commit is contained in:
parent
dd4b72e046
commit
2219ccde51
8 changed files with 196 additions and 55 deletions
|
|
@ -171,6 +171,8 @@ private:
|
|||
// Cached isInsideWMO result (throttled to avoid per-frame cost)
|
||||
bool cachedInsideWMO = false;
|
||||
bool cachedInsideInteriorWMO = false;
|
||||
int insideStateCheckCounter_ = 0;
|
||||
glm::vec3 lastInsideStateCheckPos_ = glm::vec3(0.0f);
|
||||
int insideWMOCheckCounter = 0;
|
||||
glm::vec3 lastInsideWMOCheckPos = glm::vec3(0.0f);
|
||||
|
||||
|
|
|
|||
|
|
@ -238,6 +238,7 @@ private:
|
|||
glm::vec3 shadowCenter = glm::vec3(0.0f);
|
||||
bool shadowCenterInitialized = false;
|
||||
bool shadowsEnabled = true;
|
||||
uint32_t shadowFrameCounter_ = 0;
|
||||
|
||||
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -296,7 +296,8 @@ void AmbientSoundManager::updatePositionalEmitters(float deltaTime, const glm::v
|
|||
const int MAX_ACTIVE_WATER = 3; // Max 3 water sounds at once
|
||||
|
||||
for (auto& emitter : emitters_) {
|
||||
float distance = glm::distance(emitter.position, cameraPos);
|
||||
const glm::vec3 delta = emitter.position - cameraPos;
|
||||
const float distSq = glm::dot(delta, delta);
|
||||
|
||||
// Determine max distance based on type
|
||||
float maxDist = MAX_AMBIENT_DISTANCE;
|
||||
|
|
@ -317,7 +318,8 @@ void AmbientSoundManager::updatePositionalEmitters(float deltaTime, const glm::v
|
|||
}
|
||||
|
||||
// Update active state based on distance AND limits
|
||||
bool withinRange = (distance < maxDist);
|
||||
const float maxDistSq = maxDist * maxDist;
|
||||
const bool withinRange = (distSq < maxDistSq);
|
||||
|
||||
if (isFire && withinRange && activeFireCount < MAX_ACTIVE_FIRE) {
|
||||
emitter.active = true;
|
||||
|
|
@ -336,6 +338,9 @@ void AmbientSoundManager::updatePositionalEmitters(float deltaTime, const glm::v
|
|||
// Update play timer
|
||||
emitter.lastPlayTime += deltaTime;
|
||||
|
||||
// We only need the true distance for volume attenuation once the emitter is active.
|
||||
const float distance = std::sqrt(distSq);
|
||||
|
||||
// Handle different emitter types
|
||||
switch (emitter.type) {
|
||||
case AmbientType::FIREPLACE_SMALL:
|
||||
|
|
|
|||
|
|
@ -126,6 +126,8 @@ void CameraController::update(float deltaTime) {
|
|||
if (!enabled || !camera) {
|
||||
return;
|
||||
}
|
||||
// Keep physics integration stable during render hitches to avoid floor tunneling.
|
||||
const float physicsDeltaTime = std::min(deltaTime, 1.0f / 30.0f);
|
||||
|
||||
// During taxi flights, skip movement logic but keep camera orbit/zoom controls.
|
||||
if (externalFollow_) {
|
||||
|
|
@ -442,7 +444,7 @@ void CameraController::update(float deltaTime) {
|
|||
|
||||
if (glm::length(swimMove) > 0.001f) {
|
||||
swimMove = glm::normalize(swimMove);
|
||||
targetPos += swimMove * swimSpeed * deltaTime;
|
||||
targetPos += swimMove * swimSpeed * physicsDeltaTime;
|
||||
}
|
||||
|
||||
// Spacebar = swim up (continuous, not a jump)
|
||||
|
|
@ -451,7 +453,7 @@ void CameraController::update(float deltaTime) {
|
|||
verticalVelocity = SWIM_BUOYANCY;
|
||||
} else {
|
||||
// Gentle sink when not pressing space
|
||||
verticalVelocity += SWIM_GRAVITY * deltaTime;
|
||||
verticalVelocity += SWIM_GRAVITY * physicsDeltaTime;
|
||||
if (verticalVelocity < SWIM_SINK_SPEED) {
|
||||
verticalVelocity = SWIM_SINK_SPEED;
|
||||
}
|
||||
|
|
@ -459,15 +461,15 @@ void CameraController::update(float deltaTime) {
|
|||
// you afloat unless you're intentionally diving.
|
||||
if (!diveIntent) {
|
||||
float surfaceErr = (waterSurfaceZ - targetPos.z);
|
||||
verticalVelocity += surfaceErr * 7.0f * deltaTime;
|
||||
verticalVelocity *= std::max(0.0f, 1.0f - 3.2f * deltaTime);
|
||||
verticalVelocity += surfaceErr * 7.0f * physicsDeltaTime;
|
||||
verticalVelocity *= std::max(0.0f, 1.0f - 3.2f * physicsDeltaTime);
|
||||
if (std::abs(surfaceErr) < 0.06f && std::abs(verticalVelocity) < 0.35f) {
|
||||
verticalVelocity = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
targetPos.z += verticalVelocity * deltaTime;
|
||||
targetPos.z += verticalVelocity * physicsDeltaTime;
|
||||
|
||||
// Don't rise above water surface
|
||||
if (waterH && targetPos.z > *waterH - WATER_SURFACE_OFFSET) {
|
||||
|
|
@ -557,7 +559,7 @@ void CameraController::update(float deltaTime) {
|
|||
|
||||
if (glm::length(movement) > 0.001f) {
|
||||
movement = glm::normalize(movement);
|
||||
targetPos += movement * speed * deltaTime;
|
||||
targetPos += movement * speed * physicsDeltaTime;
|
||||
}
|
||||
|
||||
// Jump with input buffering and coyote time
|
||||
|
|
@ -572,12 +574,12 @@ void CameraController::update(float deltaTime) {
|
|||
coyoteTimer = 0.0f;
|
||||
}
|
||||
|
||||
jumpBufferTimer -= deltaTime;
|
||||
coyoteTimer -= deltaTime;
|
||||
jumpBufferTimer -= physicsDeltaTime;
|
||||
coyoteTimer -= physicsDeltaTime;
|
||||
|
||||
// Apply gravity
|
||||
verticalVelocity += gravity * deltaTime;
|
||||
targetPos.z += verticalVelocity * deltaTime;
|
||||
verticalVelocity += gravity * physicsDeltaTime;
|
||||
targetPos.z += verticalVelocity * physicsDeltaTime;
|
||||
}
|
||||
} else {
|
||||
// External follow (e.g., taxi): trust server position without grounding.
|
||||
|
|
@ -589,14 +591,21 @@ void CameraController::update(float deltaTime) {
|
|||
// Refresh inside-WMO state before collision/grounding so we don't use stale
|
||||
// terrain-first caches while entering enclosed tunnel/building spaces.
|
||||
if (wmoRenderer && !externalFollow_) {
|
||||
bool prevInside = cachedInsideWMO;
|
||||
bool prevInsideInterior = cachedInsideInteriorWMO;
|
||||
cachedInsideWMO = wmoRenderer->isInsideWMO(targetPos.x, targetPos.y, targetPos.z + 1.0f, nullptr);
|
||||
cachedInsideInteriorWMO = wmoRenderer->isInsideInteriorWMO(targetPos.x, targetPos.y, targetPos.z + 1.0f);
|
||||
if (cachedInsideWMO != prevInside || cachedInsideInteriorWMO != prevInsideInterior) {
|
||||
hasCachedFloor_ = false;
|
||||
hasCachedCamFloor = false;
|
||||
cachedPivotLift_ = 0.0f;
|
||||
const float insideDist = glm::length(targetPos - lastInsideStateCheckPos_);
|
||||
if (++insideStateCheckCounter_ >= 2 || insideDist > 0.35f) {
|
||||
insideStateCheckCounter_ = 0;
|
||||
lastInsideStateCheckPos_ = targetPos;
|
||||
|
||||
bool prevInside = cachedInsideWMO;
|
||||
bool prevInsideInterior = cachedInsideInteriorWMO;
|
||||
cachedInsideWMO = wmoRenderer->isInsideWMO(targetPos.x, targetPos.y, targetPos.z + 1.0f, nullptr);
|
||||
cachedInsideInteriorWMO = cachedInsideWMO &&
|
||||
wmoRenderer->isInsideInteriorWMO(targetPos.x, targetPos.y, targetPos.z + 1.0f);
|
||||
if (cachedInsideWMO != prevInside || cachedInsideInteriorWMO != prevInsideInterior) {
|
||||
hasCachedFloor_ = false;
|
||||
hasCachedCamFloor = false;
|
||||
cachedPivotLift_ = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -662,7 +671,7 @@ void CameraController::update(float deltaTime) {
|
|||
// Collision cache: skip expensive checks if barely moved (15cm threshold)
|
||||
float distMoved = glm::length(glm::vec2(targetPos.x, targetPos.y) -
|
||||
glm::vec2(lastCollisionCheckPos_.x, lastCollisionCheckPos_.y));
|
||||
bool useCached = hasCachedFloor_ && distMoved < COLLISION_CACHE_DISTANCE;
|
||||
bool useCached = grounded && hasCachedFloor_ && distMoved < COLLISION_CACHE_DISTANCE;
|
||||
if (useCached) {
|
||||
// Never trust cached ground while actively descending or when
|
||||
// vertical drift from cached floor is meaningful.
|
||||
|
|
@ -759,13 +768,14 @@ void CameraController::update(float deltaTime) {
|
|||
// Transition safety: if no reachable floor was selected, choose the higher
|
||||
// of terrain/WMO center surfaces when it is still near the player.
|
||||
// This avoids dropping into void gaps at terrain<->WMO seams.
|
||||
const bool nearWmoSpace = cachedInsideWMO || centerWmoH.has_value();
|
||||
if (!groundH) {
|
||||
auto highestCenter = selectHighestFloor(centerTerrainH, centerWmoH, std::nullopt);
|
||||
if (highestCenter) {
|
||||
float dz = targetPos.z - *highestCenter;
|
||||
// Keep this fallback narrow: only for WMO seam cases, or very short
|
||||
// transient misses while still almost touching the last floor.
|
||||
bool allowFallback = cachedInsideWMO || (noGroundTimer_ < 0.10f && dz < 0.6f);
|
||||
bool allowFallback = nearWmoSpace || (noGroundTimer_ < 0.10f && dz < 0.6f);
|
||||
if (allowFallback && dz >= -0.5f && dz < 2.0f) {
|
||||
groundH = highestCenter;
|
||||
}
|
||||
|
|
@ -774,7 +784,7 @@ void CameraController::update(float deltaTime) {
|
|||
|
||||
// Continuity guard only for WMO seam overlap: avoid instantly switching to a
|
||||
// much lower floor sample at tunnel mouths (bad WMO ramp chains into void).
|
||||
if (groundH && hasRealGround_ && cachedInsideWMO && !cachedInsideInteriorWMO) {
|
||||
if (groundH && hasRealGround_ && nearWmoSpace && !cachedInsideInteriorWMO) {
|
||||
float dropFromLast = lastGroundZ - *groundH;
|
||||
if (dropFromLast > 1.5f) {
|
||||
if (centerTerrainH && *centerTerrainH > *groundH + 1.5f) {
|
||||
|
|
@ -785,7 +795,7 @@ void CameraController::update(float deltaTime) {
|
|||
|
||||
// Seam stability: while overlapping WMO shells, cap how fast floor height can
|
||||
// step downward in a single frame to avoid following bad ramp samples into void.
|
||||
if (groundH && cachedInsideWMO && !cachedInsideInteriorWMO && lastGroundZ > 1.0f) {
|
||||
if (groundH && nearWmoSpace && !cachedInsideInteriorWMO && lastGroundZ > 1.0f) {
|
||||
float maxDropPerFrame = (verticalVelocity < -8.0f) ? 2.0f : 0.60f;
|
||||
float minAllowed = lastGroundZ - maxDropPerFrame;
|
||||
// Extra seam guard: outside interior groups, avoid accepting floors that
|
||||
|
|
@ -804,7 +814,7 @@ void CameraController::update(float deltaTime) {
|
|||
|
||||
// 1b. Multi-sample WMO floors when in/near WMO space to avoid
|
||||
// falling through narrow board/plank gaps where center ray misses.
|
||||
if (wmoRenderer && cachedInsideWMO) {
|
||||
if (wmoRenderer && nearWmoSpace) {
|
||||
constexpr float WMO_FOOTPRINT = 0.35f;
|
||||
const glm::vec2 wmoOffsets[] = {
|
||||
{0.0f, 0.0f},
|
||||
|
|
@ -835,6 +845,37 @@ void CameraController::update(float deltaTime) {
|
|||
}
|
||||
}
|
||||
|
||||
// WMO recovery probe: when no floor is found while descending, do a wider
|
||||
// footprint sample around the player to catch narrow plank/stair misses.
|
||||
if (!groundH && wmoRenderer && hasRealGround_ && verticalVelocity <= 0.0f) {
|
||||
constexpr float RESCUE_FOOTPRINT = 0.55f;
|
||||
const glm::vec2 rescueOffsets[] = {
|
||||
{0.0f, 0.0f},
|
||||
{ RESCUE_FOOTPRINT, 0.0f}, {-RESCUE_FOOTPRINT, 0.0f},
|
||||
{0.0f, RESCUE_FOOTPRINT}, {0.0f, -RESCUE_FOOTPRINT},
|
||||
{ RESCUE_FOOTPRINT, RESCUE_FOOTPRINT},
|
||||
{ RESCUE_FOOTPRINT, -RESCUE_FOOTPRINT},
|
||||
{-RESCUE_FOOTPRINT, RESCUE_FOOTPRINT},
|
||||
{-RESCUE_FOOTPRINT, -RESCUE_FOOTPRINT}
|
||||
};
|
||||
float rescueProbeZ = std::max(lastGroundZ, targetPos.z) + stepUpBudget + 1.2f;
|
||||
std::optional<float> rescueFloor;
|
||||
for (const auto& o : rescueOffsets) {
|
||||
float nz = 1.0f;
|
||||
auto wh = wmoRenderer->getFloorHeight(targetPos.x + o.x, targetPos.y + o.y, rescueProbeZ, &nz);
|
||||
if (!wh) continue;
|
||||
if (nz < MIN_WALKABLE_NORMAL_WMO) continue;
|
||||
if (*wh > lastGroundZ + stepUpBudget + 0.75f) continue;
|
||||
if (*wh < targetPos.z - 4.0f) continue;
|
||||
if (!rescueFloor || *wh > *rescueFloor) {
|
||||
rescueFloor = wh;
|
||||
}
|
||||
}
|
||||
if (rescueFloor) {
|
||||
groundH = rescueFloor;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Multi-sample for M2 objects (rugs, planks, bridges, ships) —
|
||||
// these are narrow and need offset probes to detect reliably.
|
||||
if (m2Renderer && !externalFollow_) {
|
||||
|
|
@ -895,14 +936,15 @@ void CameraController::update(float deltaTime) {
|
|||
}
|
||||
} else {
|
||||
hasRealGround_ = false;
|
||||
noGroundTimer_ += deltaTime;
|
||||
noGroundTimer_ += physicsDeltaTime;
|
||||
|
||||
float dropFromLastGround = lastGroundZ - targetPos.z;
|
||||
bool seamSizedGap = dropFromLastGround <= 0.35f;
|
||||
bool seamSizedGap = dropFromLastGround <= (nearWmoSpace ? 1.0f : 0.35f);
|
||||
if (noGroundTimer_ < NO_GROUND_GRACE && seamSizedGap) {
|
||||
// Micro-gap grace only: keep continuity for tiny seam misses,
|
||||
// but never convert air into persistent ground.
|
||||
targetPos.z = std::max(targetPos.z, lastGroundZ - 0.10f);
|
||||
float maxSlip = nearWmoSpace ? 0.25f : 0.10f;
|
||||
targetPos.z = std::max(targetPos.z, lastGroundZ - maxSlip);
|
||||
grounded = false;
|
||||
} else {
|
||||
grounded = false;
|
||||
|
|
@ -918,7 +960,7 @@ void CameraController::update(float deltaTime) {
|
|||
// Player is safely on real geometry — save periodically
|
||||
continuousFallTime_ = 0.0f;
|
||||
autoUnstuckFired_ = false;
|
||||
safePosSaveTimer_ += deltaTime;
|
||||
safePosSaveTimer_ += physicsDeltaTime;
|
||||
if (safePosSaveTimer_ >= SAFE_POS_SAVE_INTERVAL) {
|
||||
safePosSaveTimer_ = 0.0f;
|
||||
lastSafePos_ = targetPos;
|
||||
|
|
@ -926,7 +968,7 @@ void CameraController::update(float deltaTime) {
|
|||
}
|
||||
} else if (!grounded && !swimming && !externalFollow_) {
|
||||
// Falling (or standing on nothing past grace period) — accumulate fall time
|
||||
continuousFallTime_ += deltaTime;
|
||||
continuousFallTime_ += physicsDeltaTime;
|
||||
if (continuousFallTime_ >= AUTO_UNSTUCK_FALL_TIME && !autoUnstuckFired_) {
|
||||
autoUnstuckFired_ = true;
|
||||
if (autoUnstuckCallback_) {
|
||||
|
|
@ -1179,27 +1221,27 @@ void CameraController::update(float deltaTime) {
|
|||
|
||||
if (glm::length(movement) > 0.001f) {
|
||||
movement = glm::normalize(movement);
|
||||
newPos += movement * swimSpeed * deltaTime;
|
||||
newPos += movement * swimSpeed * physicsDeltaTime;
|
||||
}
|
||||
|
||||
if (nowJump) {
|
||||
verticalVelocity = SWIM_BUOYANCY;
|
||||
} else {
|
||||
verticalVelocity += SWIM_GRAVITY * deltaTime;
|
||||
verticalVelocity += SWIM_GRAVITY * physicsDeltaTime;
|
||||
if (verticalVelocity < SWIM_SINK_SPEED) {
|
||||
verticalVelocity = SWIM_SINK_SPEED;
|
||||
}
|
||||
if (!diveIntent) {
|
||||
float surfaceErr = (waterSurfaceCamZ - newPos.z);
|
||||
verticalVelocity += surfaceErr * 7.0f * deltaTime;
|
||||
verticalVelocity *= std::max(0.0f, 1.0f - 3.2f * deltaTime);
|
||||
verticalVelocity += surfaceErr * 7.0f * physicsDeltaTime;
|
||||
verticalVelocity *= std::max(0.0f, 1.0f - 3.2f * physicsDeltaTime);
|
||||
if (std::abs(surfaceErr) < 0.06f && std::abs(verticalVelocity) < 0.35f) {
|
||||
verticalVelocity = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newPos.z += verticalVelocity * deltaTime;
|
||||
newPos.z += verticalVelocity * physicsDeltaTime;
|
||||
|
||||
// Don't rise above water surface (feet at water level)
|
||||
if (waterH && (newPos.z - eyeHeight) > *waterH - WATER_SURFACE_OFFSET) {
|
||||
|
|
@ -1213,7 +1255,7 @@ void CameraController::update(float deltaTime) {
|
|||
|
||||
if (glm::length(movement) > 0.001f) {
|
||||
movement = glm::normalize(movement);
|
||||
newPos += movement * speed * deltaTime;
|
||||
newPos += movement * speed * physicsDeltaTime;
|
||||
}
|
||||
|
||||
// Jump with input buffering and coyote time
|
||||
|
|
@ -1227,12 +1269,12 @@ void CameraController::update(float deltaTime) {
|
|||
coyoteTimer = 0.0f;
|
||||
}
|
||||
|
||||
jumpBufferTimer -= deltaTime;
|
||||
coyoteTimer -= deltaTime;
|
||||
jumpBufferTimer -= physicsDeltaTime;
|
||||
coyoteTimer -= physicsDeltaTime;
|
||||
|
||||
// Apply gravity
|
||||
verticalVelocity += gravity * deltaTime;
|
||||
newPos.z += verticalVelocity * deltaTime;
|
||||
verticalVelocity += gravity * physicsDeltaTime;
|
||||
newPos.z += verticalVelocity * physicsDeltaTime;
|
||||
}
|
||||
|
||||
// Wall sweep collision before grounding (skip when stationary).
|
||||
|
|
|
|||
|
|
@ -1310,8 +1310,9 @@ void CharacterRenderer::playAnimation(uint32_t instanceId, uint32_t animationId,
|
|||
}
|
||||
|
||||
void CharacterRenderer::update(float deltaTime, const glm::vec3& cameraPos) {
|
||||
// Distance culling for animation updates (150 unit radius)
|
||||
const float animUpdateRadiusSq = 150.0f * 150.0f;
|
||||
// Distance culling for animation updates in dense areas.
|
||||
const float animUpdateRadius = static_cast<float>(envSizeOrDefault("WOWEE_CHAR_ANIM_RADIUS", 120));
|
||||
const float animUpdateRadiusSq = animUpdateRadius * animUpdateRadius;
|
||||
|
||||
// Update fade-in opacity
|
||||
for (auto& [id, inst] : instances) {
|
||||
|
|
@ -1404,6 +1405,7 @@ void CharacterRenderer::update(float deltaTime, const glm::vec3& cameraPos) {
|
|||
for (auto& pair : instances) {
|
||||
auto& instance = pair.second;
|
||||
if (instance.weaponAttachments.empty()) continue;
|
||||
if (glm::distance2(instance.position, cameraPos) > animUpdateRadiusSq) continue;
|
||||
|
||||
glm::mat4 charModelMat = instance.hasOverrideModelMatrix
|
||||
? instance.overrideModelMatrix
|
||||
|
|
@ -1614,6 +1616,12 @@ void CharacterRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet,
|
|||
if (instances.empty() || !opaquePipeline_) {
|
||||
return;
|
||||
}
|
||||
const float renderRadius = static_cast<float>(envSizeOrDefault("WOWEE_CHAR_RENDER_RADIUS", 130));
|
||||
const float renderRadiusSq = renderRadius * renderRadius;
|
||||
const float nearNoConeCullSq = 16.0f * 16.0f;
|
||||
const float backfaceDotCull = -0.30f;
|
||||
const glm::vec3 camPos = camera.getPosition();
|
||||
const glm::vec3 camForward = camera.getForward();
|
||||
|
||||
uint32_t frameIndex = vkCtx_->getCurrentFrame();
|
||||
uint32_t frameSlot = frameIndex % 2u;
|
||||
|
|
@ -1647,6 +1655,18 @@ void CharacterRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet,
|
|||
|
||||
// Skip invisible instances (e.g., player in first-person mode)
|
||||
if (!instance.visible) continue;
|
||||
// Character instance culling: avoid drawing far-away / strongly behind-camera
|
||||
// actors in dense city scenes.
|
||||
if (!instance.hasOverrideModelMatrix) {
|
||||
glm::vec3 toInst = instance.position - camPos;
|
||||
float distSq = glm::dot(toInst, toInst);
|
||||
if (distSq > renderRadiusSq) continue;
|
||||
if (distSq > nearNoConeCullSq) {
|
||||
float invDist = 1.0f / std::sqrt(distSq);
|
||||
float facingDot = glm::dot(toInst, camForward) * invDist;
|
||||
if (facingDot < backfaceDotCull) continue;
|
||||
}
|
||||
}
|
||||
|
||||
auto modelIt = models.find(instance.modelId);
|
||||
if (modelIt == models.end()) continue;
|
||||
|
|
|
|||
|
|
@ -363,6 +363,8 @@ void QuestMarkerRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSe
|
|||
constexpr float MIN_DIST = 4.0f; // Near clamp
|
||||
constexpr float MAX_DIST = 90.0f; // Far fade-out start
|
||||
constexpr float FADE_RANGE = 25.0f; // Fade-out range
|
||||
constexpr float CULL_DIST = MAX_DIST + FADE_RANGE;
|
||||
constexpr float CULL_DIST_SQ = CULL_DIST * CULL_DIST;
|
||||
|
||||
// Get time for bob animation
|
||||
float timeSeconds = SDL_GetTicks() / 1000.0f;
|
||||
|
|
@ -373,6 +375,7 @@ void QuestMarkerRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSe
|
|||
// Get camera right and up vectors for billboarding
|
||||
glm::vec3 cameraRight = glm::vec3(view[0][0], view[1][0], view[2][0]);
|
||||
glm::vec3 cameraUp = glm::vec3(view[0][1], view[1][1], view[2][1]);
|
||||
const glm::vec3 cameraForward = glm::cross(cameraRight, cameraUp);
|
||||
|
||||
// Bind pipeline
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_);
|
||||
|
|
@ -391,7 +394,9 @@ void QuestMarkerRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSe
|
|||
|
||||
// Calculate distance for LOD and culling
|
||||
glm::vec3 toCamera = cameraPos - marker.position;
|
||||
float dist = glm::length(toCamera);
|
||||
float distSq = glm::dot(toCamera, toCamera);
|
||||
if (distSq > CULL_DIST_SQ) continue;
|
||||
float dist = std::sqrt(distSq);
|
||||
|
||||
// Calculate fade alpha
|
||||
float fadeAlpha = 1.0f;
|
||||
|
|
@ -425,7 +430,7 @@ void QuestMarkerRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSe
|
|||
// Billboard: align quad to face camera
|
||||
model[0] = glm::vec4(cameraRight * size, 0.0f);
|
||||
model[1] = glm::vec4(cameraUp * size, 0.0f);
|
||||
model[2] = glm::vec4(glm::cross(cameraRight, cameraUp), 0.0f);
|
||||
model[2] = glm::vec4(cameraForward, 0.0f);
|
||||
|
||||
// Bind material descriptor set (set 1) for this marker's texture
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout_,
|
||||
|
|
|
|||
|
|
@ -99,6 +99,15 @@ static bool envFlagEnabled(const char* key, bool defaultValue) {
|
|||
return !(v == "0" || v == "false" || v == "off" || v == "no");
|
||||
}
|
||||
|
||||
static int envIntOrDefault(const char* key, int defaultValue) {
|
||||
const char* raw = std::getenv(key);
|
||||
if (!raw || !*raw) return defaultValue;
|
||||
char* end = nullptr;
|
||||
long n = std::strtol(raw, &end, 10);
|
||||
if (end == raw) return defaultValue;
|
||||
return static_cast<int>(n);
|
||||
}
|
||||
|
||||
static std::vector<std::string> parseEmoteCommands(const std::string& raw) {
|
||||
std::vector<std::string> out;
|
||||
std::string cur;
|
||||
|
|
@ -2678,15 +2687,19 @@ void Renderer::update(float deltaTime) {
|
|||
}
|
||||
}
|
||||
|
||||
const bool canQueryWmo = (camera && wmoRenderer);
|
||||
const glm::vec3 camPos = camera ? camera->getPosition() : glm::vec3(0.0f);
|
||||
uint32_t insideWmoId = 0;
|
||||
const bool insideWmo = canQueryWmo &&
|
||||
wmoRenderer->isInsideWMO(camPos.x, camPos.y, camPos.z, &insideWmoId);
|
||||
|
||||
// Ambient environmental sounds: fireplaces, water, birds, etc.
|
||||
if (ambientSoundManager && camera && wmoRenderer && cameraController) {
|
||||
glm::vec3 camPos = camera->getPosition();
|
||||
uint32_t wmoId = 0;
|
||||
bool isIndoor = wmoRenderer->isInsideWMO(camPos.x, camPos.y, camPos.z, &wmoId);
|
||||
bool isIndoor = insideWmo;
|
||||
bool isSwimming = cameraController->isSwimming();
|
||||
|
||||
// Check if inside blacksmith (96048 = Goldshire blacksmith)
|
||||
bool isBlacksmith = (wmoId == 96048);
|
||||
bool isBlacksmith = (insideWmoId == 96048);
|
||||
|
||||
// Sync weather audio with visual weather system
|
||||
if (weather) {
|
||||
|
|
@ -2747,9 +2760,8 @@ void Renderer::update(float deltaTime) {
|
|||
|
||||
// Override with WMO-based detection (e.g., inside Stormwind, taverns, blacksmiths)
|
||||
if (wmoRenderer) {
|
||||
glm::vec3 camPos = camera->getPosition();
|
||||
uint32_t wmoModelId = 0;
|
||||
if (wmoRenderer->isInsideWMO(camPos.x, camPos.y, camPos.z, &wmoModelId)) {
|
||||
uint32_t wmoModelId = insideWmoId;
|
||||
if (insideWmo) {
|
||||
// Check if inside Stormwind WMO (model ID 10047)
|
||||
if (wmoModelId == 10047) {
|
||||
zoneId = 1519; // Stormwind City
|
||||
|
|
@ -3839,6 +3851,19 @@ void Renderer::renderShadowPass() {
|
|||
if (!shadowsEnabled || shadowDepthImage == VK_NULL_HANDLE) return;
|
||||
if (currentCmd == VK_NULL_HANDLE) return;
|
||||
|
||||
const int baseInterval = std::max(1, envIntOrDefault("WOWEE_SHADOW_INTERVAL", 1));
|
||||
const int denseInterval = std::max(baseInterval, envIntOrDefault("WOWEE_SHADOW_INTERVAL_DENSE", 3));
|
||||
const uint32_t denseCharThreshold = static_cast<uint32_t>(std::max(1, envIntOrDefault("WOWEE_DENSE_CHAR_THRESHOLD", 120)));
|
||||
const uint32_t denseM2Threshold = static_cast<uint32_t>(std::max(1, envIntOrDefault("WOWEE_DENSE_M2_THRESHOLD", 900)));
|
||||
const bool denseScene =
|
||||
(characterRenderer && characterRenderer->getInstanceCount() >= denseCharThreshold) ||
|
||||
(m2Renderer && m2Renderer->getInstanceCount() >= denseM2Threshold);
|
||||
const int shadowInterval = denseScene ? denseInterval : baseInterval;
|
||||
if (++shadowFrameCounter_ < static_cast<uint32_t>(shadowInterval)) {
|
||||
return;
|
||||
}
|
||||
shadowFrameCounter_ = 0;
|
||||
|
||||
// Compute and store light space matrix; write to per-frame UBO
|
||||
lightSpaceMatrix = computeLightSpaceMatrix();
|
||||
// Zero matrix means character position isn't set yet — skip shadow pass entirely.
|
||||
|
|
@ -3890,15 +3915,17 @@ void Renderer::renderShadowPass() {
|
|||
vkCmdSetScissor(currentCmd, 0, 1, &sc);
|
||||
|
||||
// Phase 7/8: render shadow casters
|
||||
constexpr float kShadowCullRadius = 180.0f; // match kShadowHalfExtent
|
||||
const float baseShadowCullRadius = static_cast<float>(std::max(40, envIntOrDefault("WOWEE_SHADOW_CULL_RADIUS", 180)));
|
||||
const float denseShadowCullRadius = static_cast<float>(std::max(30, envIntOrDefault("WOWEE_SHADOW_CULL_RADIUS_DENSE", 90)));
|
||||
const float shadowCullRadius = denseScene ? std::min(baseShadowCullRadius, denseShadowCullRadius) : baseShadowCullRadius;
|
||||
if (wmoRenderer) {
|
||||
wmoRenderer->renderShadow(currentCmd, lightSpaceMatrix, shadowCenter, kShadowCullRadius);
|
||||
wmoRenderer->renderShadow(currentCmd, lightSpaceMatrix, shadowCenter, shadowCullRadius);
|
||||
}
|
||||
if (m2Renderer) {
|
||||
m2Renderer->renderShadow(currentCmd, lightSpaceMatrix, globalTime, shadowCenter, kShadowCullRadius);
|
||||
m2Renderer->renderShadow(currentCmd, lightSpaceMatrix, globalTime, shadowCenter, shadowCullRadius);
|
||||
}
|
||||
if (characterRenderer) {
|
||||
characterRenderer->renderShadow(currentCmd, lightSpaceMatrix, shadowCenter, kShadowCullRadius);
|
||||
characterRenderer->renderShadow(currentCmd, lightSpaceMatrix, shadowCenter, shadowCullRadius);
|
||||
}
|
||||
|
||||
vkCmdEndRenderPass(currentCmd);
|
||||
|
|
|
|||
|
|
@ -2699,6 +2699,42 @@ std::optional<float> WMORenderer::getFloorHeight(float glX, float glY, float glZ
|
|||
}
|
||||
};
|
||||
|
||||
// Fast path: current active interior group and its neighbors are usually
|
||||
// the right answer for player-floor queries while moving in cities/buildings.
|
||||
if (activeGroup_.isValid() && activeGroup_.instanceIdx < instances.size()) {
|
||||
const auto& instance = instances[activeGroup_.instanceIdx];
|
||||
auto it = loadedModels.find(instance.modelId);
|
||||
if (it != loadedModels.end() && instance.modelId == activeGroup_.modelId) {
|
||||
const ModelData& model = it->second;
|
||||
glm::vec3 localOrigin = glm::vec3(instance.invModelMatrix * glm::vec4(worldOrigin, 1.0f));
|
||||
glm::vec3 localDir = glm::normalize(glm::vec3(instance.invModelMatrix * glm::vec4(worldDir, 0.0f)));
|
||||
|
||||
auto testGroupIdx = [&](uint32_t gi) {
|
||||
if (gi >= model.groups.size()) return;
|
||||
if (gi < instance.worldGroupBounds.size()) {
|
||||
const auto& [gMin, gMax] = instance.worldGroupBounds[gi];
|
||||
if (glX < gMin.x || glX > gMax.x ||
|
||||
glY < gMin.y || glY > gMax.y ||
|
||||
glZ - 4.0f > gMax.z) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const auto& group = model.groups[gi];
|
||||
if (!rayIntersectsAABB(localOrigin, localDir, group.boundingBoxMin, group.boundingBoxMax)) {
|
||||
return;
|
||||
}
|
||||
testGroupFloor(instance, model, group, localOrigin, localDir);
|
||||
};
|
||||
|
||||
if (activeGroup_.groupIdx >= 0) {
|
||||
testGroupIdx(static_cast<uint32_t>(activeGroup_.groupIdx));
|
||||
}
|
||||
for (uint32_t ngi : activeGroup_.neighborGroups) {
|
||||
testGroupIdx(ngi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Full scan: test all instances (active group fast path removed to fix
|
||||
// bridge clipping where early-return missed other WMO instances)
|
||||
glm::vec3 queryMin(glX - 2.0f, glY - 2.0f, glZ - 8.0f);
|
||||
|
|
@ -2720,6 +2756,9 @@ std::optional<float> WMORenderer::getFloorHeight(float glX, float glY, float glZ
|
|||
float zMarginUp = model.isLowPlatform ? 20.0f : 4.0f;
|
||||
|
||||
// Broad-phase reject in world space to avoid expensive matrix transforms.
|
||||
if (bestFloor && instance.worldBoundsMax.z <= (*bestFloor + 0.05f)) {
|
||||
continue;
|
||||
}
|
||||
if (glX < instance.worldBoundsMin.x || glX > instance.worldBoundsMax.x ||
|
||||
glY < instance.worldBoundsMin.y || glY > instance.worldBoundsMax.y ||
|
||||
glZ < instance.worldBoundsMin.z - zMarginDown || glZ > instance.worldBoundsMax.z + zMarginUp) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue