From 4cb03c38fe6d68dd92a2a5f6c0878279228b42a1 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 7 Mar 2026 22:29:06 -0800 Subject: [PATCH] Parallel animation updates, thread-safe collision, M2 pop-in fix, shadow stabilization - Overlap M2 and character animation updates via std::async (~2-5ms saved) - Thread-local collision scratch buffers for concurrent floor queries - Parallel terrain/WMO/M2 floor queries in camera controller - Seed new M2 instance bones from existing siblings to eliminate pop-in flash - Fix shadow flicker: snap center along stable light axes instead of in view space - Increase shadow distance default to 300 units (slider max 500) --- include/rendering/m2_renderer.hpp | 4 +- include/rendering/renderer.hpp | 4 +- include/rendering/wmo_renderer.hpp | 4 +- include/ui/game_screen.hpp | 2 +- src/rendering/camera_controller.cpp | 45 ++++++++++++++---- src/rendering/m2_renderer.cpp | 72 ++++++++++++++++++++++------- src/rendering/renderer.cpp | 65 ++++++++++++++------------ src/rendering/wmo_renderer.cpp | 45 ++++++++++-------- src/ui/game_screen.cpp | 6 +-- 9 files changed, 160 insertions(+), 87 deletions(-) diff --git a/include/rendering/m2_renderer.hpp b/include/rendering/m2_renderer.hpp index 4b26214f..75a92565 100644 --- a/include/rendering/m2_renderer.hpp +++ b/include/rendering/m2_renderer.hpp @@ -475,9 +475,7 @@ private: static constexpr float SPATIAL_CELL_SIZE = 64.0f; std::unordered_map, GridCellHash> spatialGrid; std::unordered_map instanceIndexById; - mutable std::vector candidateScratch; - mutable std::unordered_set candidateIdScratch; - mutable std::vector collisionTriScratch_; + // Collision scratch buffers are thread_local (see m2_renderer.cpp) for thread-safety. // Collision query profiling (per frame). mutable double queryTimeMs = 0.0; diff --git a/include/rendering/renderer.hpp b/include/rendering/renderer.hpp index c7582eea..cbb9c7e1 100644 --- a/include/rendering/renderer.hpp +++ b/include/rendering/renderer.hpp @@ -246,7 +246,7 @@ private: glm::vec3 shadowCenter = glm::vec3(0.0f); bool shadowCenterInitialized = false; bool shadowsEnabled = true; - float shadowDistance_ = 72.0f; // Shadow frustum half-extent (default: 72 units) + float shadowDistance_ = 300.0f; // Shadow frustum half-extent (default: 300 units) uint32_t shadowFrameCounter_ = 0; @@ -257,7 +257,7 @@ public: void setShadowsEnabled(bool enabled) { shadowsEnabled = enabled; } bool areShadowsEnabled() const { return shadowsEnabled; } - void setShadowDistance(float dist) { shadowDistance_ = glm::clamp(dist, 40.0f, 200.0f); } + void setShadowDistance(float dist) { shadowDistance_ = glm::clamp(dist, 40.0f, 500.0f); } float getShadowDistance() const { return shadowDistance_; } void setMsaaSamples(VkSampleCountFlagBits samples); diff --git a/include/rendering/wmo_renderer.hpp b/include/rendering/wmo_renderer.hpp index b8be9485..4546d41c 100644 --- a/include/rendering/wmo_renderer.hpp +++ b/include/rendering/wmo_renderer.hpp @@ -711,9 +711,7 @@ private: static constexpr float SPATIAL_CELL_SIZE = 64.0f; std::unordered_map, GridCellHash> spatialGrid; std::unordered_map instanceIndexById; - mutable std::vector candidateScratch; - mutable std::vector triScratch_; // Scratch for collision grid queries - mutable std::unordered_set candidateIdScratch; + // Collision scratch buffers are thread_local (see wmo_renderer.cpp) for thread-safety. // Parallel visibility culling uint32_t numCullThreads_ = 1; diff --git a/include/ui/game_screen.hpp b/include/ui/game_screen.hpp index bf7558cd..3bb99628 100644 --- a/include/ui/game_screen.hpp +++ b/include/ui/game_screen.hpp @@ -87,7 +87,7 @@ private: bool pendingVsync = false; int pendingResIndex = 0; bool pendingShadows = true; - float pendingShadowDistance = 72.0f; + float pendingShadowDistance = 300.0f; bool pendingWaterRefraction = false; int pendingMasterVolume = 100; int pendingMusicVolume = 30; diff --git a/src/rendering/camera_controller.cpp b/src/rendering/camera_controller.cpp index 4103cc9f..891d53ba 100644 --- a/src/rendering/camera_controller.cpp +++ b/src/rendering/camera_controller.cpp @@ -1,5 +1,6 @@ #include "rendering/camera_controller.hpp" #include +#include #include #include "rendering/terrain_manager.hpp" #include "rendering/wmo_renderer.hpp" @@ -808,25 +809,53 @@ void CameraController::update(float deltaTime) { if (useCached) { groundH = cachedFloorHeight_; } else { - // Full collision check + // Full collision check — run terrain/WMO/M2 queries in parallel std::optional terrainH; std::optional wmoH; std::optional m2H; - if (terrainManager) { - terrainH = terrainManager->getHeightAt(targetPos.x, targetPos.y); - } // When airborne, anchor probe to last ground level so the // ceiling doesn't rise with the jump and catch roof geometry. float wmoBaseZ = grounded ? std::max(targetPos.z, lastGroundZ) : lastGroundZ; float wmoProbeZ = wmoBaseZ + stepUpBudget + 0.5f; float wmoNormalZ = 1.0f; + + // Launch WMO + M2 floor queries asynchronously while terrain runs on this thread. + // Collision scratch buffers are thread_local so concurrent calls are safe. + using FloorResult = std::pair, float>; + std::future wmoFuture; + std::future m2Future; + bool wmoAsync = false, m2Async = false; + float px = targetPos.x, py = targetPos.y; if (wmoRenderer) { - wmoH = wmoRenderer->getFloorHeight(targetPos.x, targetPos.y, wmoProbeZ, &wmoNormalZ); + wmoAsync = true; + wmoFuture = std::async(std::launch::async, + [this, px, py, wmoProbeZ]() -> FloorResult { + float nz = 1.0f; + auto h = wmoRenderer->getFloorHeight(px, py, wmoProbeZ, &nz); + return {h, nz}; + }); } if (m2Renderer && !externalFollow_) { - float m2NormalZ = 1.0f; - m2H = m2Renderer->getFloorHeight(targetPos.x, targetPos.y, wmoProbeZ, &m2NormalZ); - if (m2H && m2NormalZ < MIN_WALKABLE_NORMAL_M2) { + m2Async = true; + m2Future = std::async(std::launch::async, + [this, px, py, wmoProbeZ]() -> FloorResult { + float nz = 1.0f; + auto h = m2Renderer->getFloorHeight(px, py, wmoProbeZ, &nz); + return {h, nz}; + }); + } + if (terrainManager) { + terrainH = terrainManager->getHeightAt(targetPos.x, targetPos.y); + } + if (wmoAsync) { + auto [h, nz] = wmoFuture.get(); + wmoH = h; + wmoNormalZ = nz; + } + if (m2Async) { + auto [h, nz] = m2Future.get(); + m2H = h; + if (m2H && nz < MIN_WALKABLE_NORMAL_M2) { m2H = std::nullopt; } } diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index 3a097217..0ca3f940 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -282,6 +282,14 @@ glm::vec3 closestPointOnTriangle(const glm::vec3& p, } // namespace +// Thread-local scratch buffers for collision queries (allows concurrent getFloorHeight calls) +static thread_local std::vector tl_m2_candidateScratch; +static thread_local std::unordered_set tl_m2_candidateIdScratch; +static thread_local std::vector tl_m2_collisionTriScratch; + +// Forward declaration (defined after animation helpers) +static void computeBoneMatrices(const M2ModelGPU& model, M2Instance& instance); + void M2Instance::updateModelMatrix() { modelMatrix = glm::mat4(1.0f); modelMatrix = glm::translate(modelMatrix, position); @@ -1673,6 +1681,21 @@ uint32_t M2Renderer::createInstance(uint32_t modelId, const glm::vec3& position, instance.animDuration = static_cast(mdl.sequences[0].duration); instance.animTime = static_cast(rand() % std::max(1u, mdl.sequences[0].duration)); instance.variationTimer = 3000.0f + static_cast(rand() % 8000); + + // Seed bone matrices from an existing instance of the same model so the + // new instance renders immediately instead of being invisible until the + // next update() computes bones (prevents pop-in flash). + for (const auto& existing : instances) { + if (existing.modelId == modelId && !existing.boneMatrices.empty()) { + instance.boneMatrices = existing.boneMatrices; + instance.bonesDirty = true; + break; + } + } + // If no sibling exists yet, compute bones immediately + if (instance.boneMatrices.empty()) { + computeBoneMatrices(mdlRef, instance); + } } // Register in dedup map before pushing (uses original position, not ground-adjusted) @@ -1764,6 +1787,18 @@ uint32_t M2Renderer::createInstanceWithMatrix(uint32_t modelId, const glm::mat4& instance.animDuration = static_cast(mdl2.sequences[0].duration); instance.animTime = static_cast(rand() % std::max(1u, mdl2.sequences[0].duration)); instance.variationTimer = 3000.0f + static_cast(rand() % 8000); + + // Seed bone matrices from an existing sibling so the instance renders immediately + for (const auto& existing : instances) { + if (existing.modelId == modelId && !existing.boneMatrices.empty()) { + instance.boneMatrices = existing.boneMatrices; + instance.bonesDirty = true; + break; + } + } + if (instance.boneMatrices.empty()) { + computeBoneMatrices(mdl2, instance); + } } else { instance.animTime = static_cast(rand()) / RAND_MAX * 10000.0f; } @@ -2380,12 +2415,15 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const } // Upload bone matrices to SSBO if model has skeletal animation. - // Bone buffers are pre-allocated by prepareRender() on the main thread. - // If not yet allocated (race/timing), skip this instance entirely to avoid - // a bind-pose flash — it will render correctly next frame. - bool needsBones = model.hasAnimation && !model.disableAnimation && !instance.boneMatrices.empty(); + // Skip animated instances entirely until bones are computed + buffers allocated + // to prevent bind-pose/T-pose flash on first appearance. + bool modelNeedsAnimation = model.hasAnimation && !model.disableAnimation; + if (modelNeedsAnimation && instance.boneMatrices.empty()) { + continue; // Bones not yet computed — skip to avoid bind-pose flash + } + bool needsBones = modelNeedsAnimation && !instance.boneMatrices.empty(); if (needsBones && (!instance.boneBuffer[frameIndex] || !instance.boneSet[frameIndex])) { - continue; + continue; // Bone buffers not yet allocated — skip to avoid bind-pose flash } bool useBones = needsBones; if (useBones) { @@ -3620,7 +3658,7 @@ void M2Renderer::rebuildSpatialIndex() { void M2Renderer::gatherCandidates(const glm::vec3& queryMin, const glm::vec3& queryMax, std::vector& outIndices) const { outIndices.clear(); - candidateIdScratch.clear(); + tl_m2_candidateIdScratch.clear(); GridCell minCell = toCell(queryMin); GridCell maxCell = toCell(queryMax); @@ -3630,7 +3668,7 @@ void M2Renderer::gatherCandidates(const glm::vec3& queryMin, const glm::vec3& qu auto it = spatialGrid.find(GridCell{x, y, z}); if (it == spatialGrid.end()) continue; for (uint32_t id : it->second) { - if (!candidateIdScratch.insert(id).second) continue; + if (!tl_m2_candidateIdScratch.insert(id).second) continue; auto idxIt = instanceIndexById.find(id); if (idxIt != instanceIndexById.end()) { outIndices.push_back(idxIt->second); @@ -3803,9 +3841,9 @@ std::optional M2Renderer::getFloorHeight(float glX, float glY, float glZ, glm::vec3 queryMin(glX - 2.0f, glY - 2.0f, glZ - 6.0f); glm::vec3 queryMax(glX + 2.0f, glY + 2.0f, glZ + 8.0f); - gatherCandidates(queryMin, queryMax, candidateScratch); + gatherCandidates(queryMin, queryMax, tl_m2_candidateScratch); - for (size_t idx : candidateScratch) { + for (size_t idx : tl_m2_candidateScratch) { const auto& instance = instances[idx]; if (collisionFocusEnabled && pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) { @@ -3827,14 +3865,14 @@ std::optional M2Renderer::getFloorHeight(float glX, float glY, float glZ, model.collision.getFloorTrisInRange( localPos.x - 1.0f, localPos.y - 1.0f, localPos.x + 1.0f, localPos.y + 1.0f, - collisionTriScratch_); + tl_m2_collisionTriScratch); glm::vec3 rayOrigin(localPos.x, localPos.y, localPos.z + 5.0f); glm::vec3 rayDir(0.0f, 0.0f, -1.0f); float bestHitZ = -std::numeric_limits::max(); bool hitAny = false; - for (uint32_t ti : collisionTriScratch_) { + for (uint32_t ti : tl_m2_collisionTriScratch) { if (ti >= model.collision.triCount) continue; if (model.collision.triBounds[ti].maxZ < localPos.z - 10.0f || model.collision.triBounds[ti].minZ > localPos.z + 5.0f) continue; @@ -3949,10 +3987,10 @@ bool M2Renderer::checkCollision(const glm::vec3& from, const glm::vec3& to, glm::vec3 queryMin = glm::min(from, to) - glm::vec3(7.0f, 7.0f, 5.0f); glm::vec3 queryMax = glm::max(from, to) + glm::vec3(7.0f, 7.0f, 5.0f); - gatherCandidates(queryMin, queryMax, candidateScratch); + gatherCandidates(queryMin, queryMax, tl_m2_candidateScratch); // Check against all M2 instances in local space (rotation-aware). - for (size_t idx : candidateScratch) { + for (size_t idx : tl_m2_candidateScratch) { const auto& instance = instances[idx]; if (collisionFocusEnabled && pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) { @@ -3985,14 +4023,14 @@ bool M2Renderer::checkCollision(const glm::vec3& from, const glm::vec3& to, std::min(localFrom.y, localPos.y) - localRadius - 1.0f, std::max(localFrom.x, localPos.x) + localRadius + 1.0f, std::max(localFrom.y, localPos.y) + localRadius + 1.0f, - collisionTriScratch_); + tl_m2_collisionTriScratch); constexpr float PLAYER_HEIGHT = 2.0f; constexpr float MAX_TOTAL_PUSH = 0.02f; // Cap total push per instance bool pushed = false; float totalPushX = 0.0f, totalPushY = 0.0f; - for (uint32_t ti : collisionTriScratch_) { + for (uint32_t ti : tl_m2_collisionTriScratch) { if (ti >= model.collision.triCount) continue; if (localPos.z + PLAYER_HEIGHT < model.collision.triBounds[ti].minZ || localPos.z > model.collision.triBounds[ti].maxZ) continue; @@ -4190,9 +4228,9 @@ float M2Renderer::raycastBoundingBoxes(const glm::vec3& origin, const glm::vec3& glm::vec3 rayEnd = origin + direction * maxDistance; glm::vec3 queryMin = glm::min(origin, rayEnd) - glm::vec3(1.0f); glm::vec3 queryMax = glm::max(origin, rayEnd) + glm::vec3(1.0f); - gatherCandidates(queryMin, queryMax, candidateScratch); + gatherCandidates(queryMin, queryMax, tl_m2_candidateScratch); - for (size_t idx : candidateScratch) { + for (size_t idx : tl_m2_candidateScratch) { const auto& instance = instances[idx]; if (collisionFocusEnabled && pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) { diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index 9f3d65e7..4e2b66f5 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -70,6 +70,7 @@ #include #include #include +#include namespace wowee { namespace rendering { @@ -2678,16 +2679,23 @@ void Renderer::update(float deltaTime) { } - // Update character animations + // Launch M2 doodad animation on background thread (overlaps with character animation + audio) + std::future m2AnimFuture; + bool m2AnimLaunched = false; + if (m2Renderer && camera) { + float m2DeltaTime = deltaTime; + glm::vec3 m2CamPos = camera->getPosition(); + glm::mat4 m2ViewProj = camera->getProjectionMatrix() * camera->getViewMatrix(); + m2AnimFuture = std::async(std::launch::async, + [this, m2DeltaTime, m2CamPos, m2ViewProj]() { + m2Renderer->update(m2DeltaTime, m2CamPos, m2ViewProj); + }); + m2AnimLaunched = true; + } + + // Update character animations (runs in parallel with M2 animation above) if (characterRenderer && camera) { - auto charAnimStart = std::chrono::steady_clock::now(); characterRenderer->update(deltaTime, camera->getPosition()); - float charAnimMs = std::chrono::duration( - std::chrono::steady_clock::now() - charAnimStart).count(); - if (charAnimMs > 5.0f) { - LOG_WARNING("SLOW characterRenderer->update: ", charAnimMs, "ms (", - characterRenderer->getInstanceCount(), " instances)"); - } } // Update AudioEngine (cleanup finished sounds, etc.) @@ -2872,17 +2880,9 @@ void Renderer::update(float deltaTime) { ambientSoundManager->update(deltaTime, camPos, isIndoor, isSwimming, isBlacksmith); } - // Update M2 doodad animations (pass camera for frustum-culling bone computation) - if (m2Renderer && camera) { - auto m2Start = std::chrono::steady_clock::now(); - m2Renderer->update(deltaTime, camera->getPosition(), - camera->getProjectionMatrix() * camera->getViewMatrix()); - float m2Ms = std::chrono::duration( - std::chrono::steady_clock::now() - m2Start).count(); - if (m2Ms > 3.0f) { - LOG_WARNING("SLOW m2Renderer->update: ", m2Ms, "ms (", - m2Renderer->getInstanceCount(), " instances)"); - } + // Wait for M2 doodad animation to finish (was launched earlier in parallel with character anim) + if (m2AnimLaunched) { + m2AnimFuture.get(); } // Helper: play zone music, dispatching local files (file: prefix) vs MPQ paths @@ -4338,27 +4338,32 @@ glm::mat4 Renderer::computeLightSpaceMatrix() { shadowCenter = desiredCenter; glm::vec3 center = shadowCenter; - // Snap to shadow texel grid to keep projection stable while moving. + // Snap shadow frustum to texel grid so the projection is perfectly stable + // while moving. We compute the light's right/up axes from the sun direction + // (these are constant per frame regardless of center) and snap center along + // them before building the view matrix. float halfExtent = kShadowHalfExtent; float texelWorld = (2.0f * halfExtent) / static_cast(SHADOW_MAP_SIZE); - // Build light view to get stable axes + // Stable light-space axes (independent of center position) glm::vec3 up(0.0f, 0.0f, 1.0f); - // If sunDir is nearly parallel to up, pick a different up vector if (std::abs(glm::dot(sunDir, up)) > 0.99f) { up = glm::vec3(0.0f, 1.0f, 0.0f); } - glm::mat4 lightView = glm::lookAt(center - sunDir * kShadowLightDistance, center, up); + glm::vec3 lightRight = glm::normalize(glm::cross(sunDir, up)); + glm::vec3 lightUp = glm::normalize(glm::cross(lightRight, sunDir)); - // Stable texel snapping in light space removes movement shimmer. - glm::vec4 centerLS = lightView * glm::vec4(center, 1.0f); - centerLS.x = std::round(centerLS.x / texelWorld) * texelWorld; - centerLS.y = std::round(centerLS.y / texelWorld) * texelWorld; - glm::vec4 snappedCenter = glm::inverse(lightView) * centerLS; - center = glm::vec3(snappedCenter); + // Snap center along light's right and up axes to align with texel grid. + // This eliminates sub-texel shifts that cause shadow shimmer. + float dotR = glm::dot(center, lightRight); + float dotU = glm::dot(center, lightUp); + dotR = std::floor(dotR / texelWorld) * texelWorld; + dotU = std::floor(dotU / texelWorld) * texelWorld; + float dotD = glm::dot(center, sunDir); // depth axis unchanged + center = lightRight * dotR + lightUp * dotU + sunDir * dotD; shadowCenter = center; - lightView = glm::lookAt(center - sunDir * kShadowLightDistance, center, up); + glm::mat4 lightView = glm::lookAt(center - sunDir * kShadowLightDistance, center, up); glm::mat4 lightProj = glm::ortho(-halfExtent, halfExtent, -halfExtent, halfExtent, kShadowNearPlane, kShadowFarPlane); lightProj[1][1] *= -1.0f; // Vulkan Y-flip for shadow pass diff --git a/src/rendering/wmo_renderer.cpp b/src/rendering/wmo_renderer.cpp index 2e5afcc3..51d8c2a2 100644 --- a/src/rendering/wmo_renderer.cpp +++ b/src/rendering/wmo_renderer.cpp @@ -48,6 +48,11 @@ size_t envSizeOrDefault(const char* name, size_t defValue) { } } // namespace +// Thread-local scratch buffers for collision queries (allows concurrent getFloorHeight/checkWallCollision calls) +static thread_local std::vector tl_candidateScratch; +static thread_local std::vector tl_triScratch; +static thread_local std::unordered_set tl_candidateIdScratch; + static void transformAABB(const glm::mat4& modelMatrix, const glm::vec3& localMin, const glm::vec3& localMax, @@ -1288,7 +1293,7 @@ void WMORenderer::rebuildSpatialIndex() { void WMORenderer::gatherCandidates(const glm::vec3& queryMin, const glm::vec3& queryMax, std::vector& outIndices) const { outIndices.clear(); - candidateIdScratch.clear(); + tl_candidateIdScratch.clear(); GridCell minCell = toCell(queryMin); GridCell maxCell = toCell(queryMax); @@ -1298,7 +1303,7 @@ void WMORenderer::gatherCandidates(const glm::vec3& queryMin, const glm::vec3& q auto it = spatialGrid.find(GridCell{x, y, z}); if (it == spatialGrid.end()) continue; for (uint32_t id : it->second) { - if (!candidateIdScratch.insert(id).second) continue; + if (!tl_candidateIdScratch.insert(id).second) continue; auto idxIt = instanceIndexById.find(id); if (idxIt != instanceIndexById.end()) { outIndices.push_back(idxIt->second); @@ -2830,9 +2835,9 @@ std::optional WMORenderer::getFloorHeight(float glX, float glY, float glZ group.getTrianglesInRange( localOrigin.x - 1.0f, localOrigin.y - 1.0f, localOrigin.x + 1.0f, localOrigin.y + 1.0f, - triScratch_); + tl_triScratch); - for (uint32_t triStart : triScratch_) { + for (uint32_t triStart : tl_triScratch) { const glm::vec3& v0 = verts[indices[triStart]]; const glm::vec3& v1 = verts[indices[triStart + 1]]; const glm::vec3& v2 = verts[indices[triStart + 2]]; @@ -2906,9 +2911,9 @@ std::optional WMORenderer::getFloorHeight(float glX, float glY, float glZ // early-returned because overlapping WMO instances need full coverage). glm::vec3 queryMin(glX - 2.0f, glY - 2.0f, glZ - 8.0f); glm::vec3 queryMax(glX + 2.0f, glY + 2.0f, glZ + 10.0f); - gatherCandidates(queryMin, queryMax, candidateScratch); + gatherCandidates(queryMin, queryMax, tl_candidateScratch); - for (size_t idx : candidateScratch) { + for (size_t idx : tl_candidateScratch) { const auto& instance = instances[idx]; if (collisionFocusEnabled && pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) { @@ -3081,9 +3086,9 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to, glm::vec3 queryMin = glm::min(from, to) - glm::vec3(8.0f, 8.0f, 5.0f); glm::vec3 queryMax = glm::max(from, to) + glm::vec3(8.0f, 8.0f, 5.0f); - gatherCandidates(queryMin, queryMax, candidateScratch); + gatherCandidates(queryMin, queryMax, tl_candidateScratch); - for (size_t idx : candidateScratch) { + for (size_t idx : tl_candidateScratch) { const auto& instance = instances[idx]; if (collisionFocusEnabled && pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) { @@ -3149,9 +3154,9 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to, float rangeMinY = std::min(localFrom.y, localTo.y) - PLAYER_RADIUS - 1.5f; float rangeMaxX = std::max(localFrom.x, localTo.x) + PLAYER_RADIUS + 1.5f; float rangeMaxY = std::max(localFrom.y, localTo.y) + PLAYER_RADIUS + 1.5f; - group.getTrianglesInRange(rangeMinX, rangeMinY, rangeMaxX, rangeMaxY, triScratch_); + group.getTrianglesInRange(rangeMinX, rangeMinY, rangeMaxX, rangeMaxY, tl_triScratch); - for (uint32_t triStart : triScratch_) { + for (uint32_t triStart : tl_triScratch) { // Use pre-computed Z bounds for fast vertical reject const auto& tb = group.triBounds[triStart / 3]; @@ -3319,9 +3324,9 @@ void WMORenderer::updateActiveGroup(float glX, float glY, float glZ) { glm::vec3 queryMin(glX - 0.5f, glY - 0.5f, glZ - 0.5f); glm::vec3 queryMax(glX + 0.5f, glY + 0.5f, glZ + 0.5f); - gatherCandidates(queryMin, queryMax, candidateScratch); + gatherCandidates(queryMin, queryMax, tl_candidateScratch); - for (size_t idx : candidateScratch) { + for (size_t idx : tl_candidateScratch) { const auto& instance = instances[idx]; if (glX < instance.worldBoundsMin.x || glX > instance.worldBoundsMax.x || glY < instance.worldBoundsMin.y || glY > instance.worldBoundsMax.y || @@ -3365,9 +3370,9 @@ bool WMORenderer::isInsideWMO(float glX, float glY, float glZ, uint32_t* outMode QueryTimer timer(&queryTimeMs, &queryCallCount); glm::vec3 queryMin(glX - 0.5f, glY - 0.5f, glZ - 0.5f); glm::vec3 queryMax(glX + 0.5f, glY + 0.5f, glZ + 0.5f); - gatherCandidates(queryMin, queryMax, candidateScratch); + gatherCandidates(queryMin, queryMax, tl_candidateScratch); - for (size_t idx : candidateScratch) { + for (size_t idx : tl_candidateScratch) { const auto& instance = instances[idx]; if (collisionFocusEnabled && pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) { @@ -3414,9 +3419,9 @@ bool WMORenderer::isInsideWMO(float glX, float glY, float glZ, uint32_t* outMode bool WMORenderer::isInsideInteriorWMO(float glX, float glY, float glZ) const { glm::vec3 queryMin(glX - 0.5f, glY - 0.5f, glZ - 0.5f); glm::vec3 queryMax(glX + 0.5f, glY + 0.5f, glZ + 0.5f); - gatherCandidates(queryMin, queryMax, candidateScratch); + gatherCandidates(queryMin, queryMax, tl_candidateScratch); - for (size_t idx : candidateScratch) { + for (size_t idx : tl_candidateScratch) { const auto& instance = instances[idx]; if (collisionFocusEnabled && pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) { @@ -3470,9 +3475,9 @@ float WMORenderer::raycastBoundingBoxes(const glm::vec3& origin, const glm::vec3 glm::vec3 rayEnd = origin + direction * maxDistance; glm::vec3 queryMin = glm::min(origin, rayEnd) - glm::vec3(1.0f); glm::vec3 queryMax = glm::max(origin, rayEnd) + glm::vec3(1.0f); - gatherCandidates(queryMin, queryMax, candidateScratch); + gatherCandidates(queryMin, queryMax, tl_candidateScratch); - for (size_t idx : candidateScratch) { + for (size_t idx : tl_candidateScratch) { const auto& instance = instances[idx]; if (collisionFocusEnabled && pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) { @@ -3526,9 +3531,9 @@ float WMORenderer::raycastBoundingBoxes(const glm::vec3& origin, const glm::vec3 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.getWallTrianglesInRange(rMinX, rMinY, rMaxX, rMaxY, triScratch_); + group.getWallTrianglesInRange(rMinX, rMinY, rMaxX, rMaxY, tl_triScratch); - for (uint32_t triStart : triScratch_) { + for (uint32_t triStart : tl_triScratch) { const glm::vec3& v0 = verts[indices[triStart]]; const glm::vec3& v1 = verts[indices[triStart + 1]]; const glm::vec3& v2 = verts[indices[triStart + 2]]; diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 19db13e9..8b79cd4c 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -6270,7 +6270,7 @@ void GameScreen::renderSettingsWindow() { if (pendingShadows) { ImGui::SameLine(); ImGui::SetNextItemWidth(150.0f); - if (ImGui::SliderFloat("Distance##shadow", &pendingShadowDistance, 40.0f, 200.0f, "%.0f")) { + if (ImGui::SliderFloat("Distance##shadow", &pendingShadowDistance, 40.0f, 500.0f, "%.0f")) { if (renderer) renderer->setShadowDistance(pendingShadowDistance); saveSettings(); } @@ -6387,7 +6387,7 @@ void GameScreen::renderSettingsWindow() { pendingFullscreen = kDefaultFullscreen; pendingVsync = kDefaultVsync; pendingShadows = kDefaultShadows; - pendingShadowDistance = 72.0f; + pendingShadowDistance = 300.0f; pendingGroundClutterDensity = kDefaultGroundClutterDensity; pendingAntiAliasing = 0; pendingNormalMapping = true; @@ -7505,7 +7505,7 @@ void GameScreen::loadSettings() { else if (key == "auto_loot") pendingAutoLoot = (std::stoi(val) != 0); else if (key == "ground_clutter_density") pendingGroundClutterDensity = std::clamp(std::stoi(val), 0, 150); else if (key == "shadows") pendingShadows = (std::stoi(val) != 0); - else if (key == "shadow_distance") pendingShadowDistance = std::clamp(std::stof(val), 40.0f, 200.0f); + else if (key == "shadow_distance") pendingShadowDistance = std::clamp(std::stof(val), 40.0f, 500.0f); else if (key == "water_refraction") pendingWaterRefraction = (std::stoi(val) != 0); else if (key == "antialiasing") pendingAntiAliasing = std::clamp(std::stoi(val), 0, 3); else if (key == "normal_mapping") pendingNormalMapping = (std::stoi(val) != 0);