diff --git a/include/game/warden_emulator.hpp b/include/game/warden_emulator.hpp index 30a0759f..3c6cddbf 100644 --- a/include/game/warden_emulator.hpp +++ b/include/game/warden_emulator.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include // Forward declare unicorn types (will include in .cpp) @@ -148,7 +149,7 @@ private: uint32_t apiStubBase_; // API stub base address // API hooks: DLL name -> Function name -> stub address - std::map> apiAddresses_; + std::unordered_map> apiAddresses_; // API stub dispatch: stub address -> {argCount, handler} struct ApiHookEntry { diff --git a/include/game/world_packets.hpp b/include/game/world_packets.hpp index e315b213..2fae62e7 100644 --- a/include/game/world_packets.hpp +++ b/include/game/world_packets.hpp @@ -1502,9 +1502,10 @@ struct CreatureQueryResponseData { std::string subName; std::string iconName; uint32_t typeFlags = 0; - uint32_t creatureType = 0; + uint32_t creatureType = 0; // 1=Beast, 2=Dragonkin, 3=Demon, 4=Elemental, 5=Giant, 6=Undead, 7=Humanoid, ... uint32_t family = 0; uint32_t rank = 0; // 0=Normal, 1=Elite, 2=Rare Elite, 3=Boss, 4=Rare + uint32_t displayId[4] = {}; // Up to 4 random display models (0 = unused) bool isValid() const { return entry != 0 && !name.empty(); } }; diff --git a/include/pipeline/asset_manager.hpp b/include/pipeline/asset_manager.hpp index 869b87a3..ca6a44c1 100644 --- a/include/pipeline/asset_manager.hpp +++ b/include/pipeline/asset_manager.hpp @@ -8,7 +8,9 @@ #include #include #include +#include #include +#include namespace wowee { namespace pipeline { @@ -164,15 +166,15 @@ private: */ std::string resolveFile(const std::string& normalizedPath) const; - mutable std::mutex cacheMutex; - std::map> dbcCache; + mutable std::shared_mutex cacheMutex; + std::unordered_map> dbcCache; // File cache (LRU, dynamic budget based on system RAM) struct CachedFile { std::vector data; uint64_t lastAccessTime; }; - mutable std::map fileCache; + mutable std::unordered_map fileCache; mutable size_t fileCacheTotalBytes = 0; mutable uint64_t fileCacheAccessCounter = 0; mutable size_t fileCacheHits = 0; diff --git a/include/pipeline/dbc_loader.hpp b/include/pipeline/dbc_loader.hpp index 1e4d9d5c..6e14d5da 100644 --- a/include/pipeline/dbc_loader.hpp +++ b/include/pipeline/dbc_loader.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -92,6 +93,11 @@ public: */ std::string getString(uint32_t recordIndex, uint32_t fieldIndex) const; + /** + * Get a string field as a view (no allocation; valid while this DBCFile lives) + */ + std::string_view getStringView(uint32_t recordIndex, uint32_t fieldIndex) const; + /** * Get string by offset in string block * @param offset Offset into string block @@ -99,6 +105,11 @@ public: */ std::string getStringByOffset(uint32_t offset) const; + /** + * Get string by offset as a view (no allocation; valid while this DBCFile lives) + */ + std::string_view getStringViewByOffset(uint32_t offset) const; + /** * Find a record by ID (assumes first field is ID) * @param id Record ID to find diff --git a/include/pipeline/mpq_manager.hpp b/include/pipeline/mpq_manager.hpp index 7de542ff..2169fee4 100644 --- a/include/pipeline/mpq_manager.hpp +++ b/include/pipeline/mpq_manager.hpp @@ -8,6 +8,7 @@ #include #include #include +#include // Forward declare StormLib handle typedef void* HANDLE; @@ -115,7 +116,7 @@ private: // // Important: caching misses can blow up memory if the game probes many unique non-existent filenames. // Miss caching is disabled by default and must be explicitly enabled. - mutable std::mutex fileArchiveCacheMutex_; + mutable std::shared_mutex fileArchiveCacheMutex_; mutable std::unordered_map fileArchiveCache_; size_t fileArchiveCacheMaxEntries_ = 500000; bool fileArchiveCacheMisses_ = false; diff --git a/include/rendering/character_renderer.hpp b/include/rendering/character_renderer.hpp index 6129940f..69b8f5df 100644 --- a/include/rendering/character_renderer.hpp +++ b/include/rendering/character_renderer.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -325,6 +326,7 @@ private: }; // Completed results ready for GPU upload (populated by background threads) std::mutex normalMapResultsMutex_; + std::condition_variable normalMapDoneCV_; // signaled when pendingNormalMapCount_ reaches 0 std::deque completedNormalMaps_; std::atomic pendingNormalMapCount_{0}; // in-flight background tasks diff --git a/src/core/application.cpp b/src/core/application.cpp index 42122e24..69c75430 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -1600,8 +1600,9 @@ void Application::update(float deltaTime) { // Keep facing toward target and emit charge effect glm::vec3 dir = chargeEndPos_ - chargeStartPos_; - if (glm::length(dir) > 0.01f) { - dir = glm::normalize(dir); + float dirLenSq = glm::dot(dir, dir); + if (dirLenSq > 1e-4f) { + dir *= glm::inversesqrt(dirLenSq); float yawDeg = glm::degrees(std::atan2(dir.x, dir.y)); renderer->setCharacterYaw(yawDeg); renderer->emitChargeEffect(renderPos, dir); @@ -1634,10 +1635,10 @@ void Application::update(float deltaTime) { glm::vec3 targetCanonical(targetEntity->getX(), targetEntity->getY(), targetEntity->getZ()); glm::vec3 targetRender = core::coords::canonicalToRender(targetCanonical); glm::vec3 toTarget = targetRender - renderPos; - float d = glm::length(toTarget); - if (d > 1.5f) { + float dSq = glm::dot(toTarget, toTarget); + if (dSq > 2.25f) { // Place us 1.5 units from target (well within 8-unit melee range) - glm::vec3 snapPos = targetRender - glm::normalize(toTarget) * 1.5f; + glm::vec3 snapPos = targetRender - toTarget * (1.5f * glm::inversesqrt(dSq)); renderer->getCharacterPosition() = snapPos; glm::vec3 snapCanonical = core::coords::renderToCanonical(snapPos); gameHandler->setPosition(snapCanonical.x, snapCanonical.y, snapCanonical.z); @@ -1925,13 +1926,14 @@ void Application::update(float deltaTime) { creatureRenderPosCache_[guid] = renderPos; } else { const glm::vec3 prevPos = posIt->second; - const glm::vec2 delta2(renderPos.x - prevPos.x, renderPos.y - prevPos.y); - float planarDist = glm::length(delta2); + float ddx2 = renderPos.x - prevPos.x; + float ddy2 = renderPos.y - prevPos.y; + float planarDistSq = ddx2 * ddx2 + ddy2 * ddy2; float dz = std::abs(renderPos.z - prevPos.z); auto unitPtr = std::static_pointer_cast(entity); const bool deadOrCorpse = unitPtr->getHealth() == 0; - const bool largeCorrection = (planarDist > 6.0f) || (dz > 3.0f); + const bool largeCorrection = (planarDistSq > 36.0f) || (dz > 3.0f); // isEntityMoving() reflects server-authoritative move state set by // startMoveTo() in handleMonsterMove, regardless of distance-cull. // This correctly detects movement for distant creatures (> 150u) @@ -1941,11 +1943,13 @@ void Application::update(float deltaTime) { // destination, rather than persisting through the dead- // reckoning overrun window. const bool entityIsMoving = entity->isActivelyMoving(); - const bool isMovingNow = !deadOrCorpse && (entityIsMoving || planarDist > 0.03f || dz > 0.08f); + constexpr float kMoveThreshSq = 0.03f * 0.03f; + const bool isMovingNow = !deadOrCorpse && (entityIsMoving || planarDistSq > kMoveThreshSq || dz > 0.08f); if (deadOrCorpse || largeCorrection) { charRenderer->setInstancePosition(instanceId, renderPos); - } else if (planarDist > 0.03f || dz > 0.08f) { + } else if (planarDistSq > kMoveThreshSq || dz > 0.08f) { // Position changed in entity coords → drive renderer toward it. + float planarDist = std::sqrt(planarDistSq); float duration = std::clamp(planarDist / 5.5f, 0.05f, 0.22f); charRenderer->moveInstanceTo(instanceId, renderPos, duration); } @@ -2045,19 +2049,22 @@ void Application::update(float deltaTime) { creatureRenderPosCache_[guid] = renderPos; } else { const glm::vec3 prevPos = posIt->second; - const glm::vec2 delta2(renderPos.x - prevPos.x, renderPos.y - prevPos.y); - float planarDist = glm::length(delta2); + float ddx2 = renderPos.x - prevPos.x; + float ddy2 = renderPos.y - prevPos.y; + float planarDistSq = ddx2 * ddx2 + ddy2 * ddy2; float dz = std::abs(renderPos.z - prevPos.z); auto unitPtr = std::static_pointer_cast(entity); const bool deadOrCorpse = unitPtr->getHealth() == 0; - const bool largeCorrection = (planarDist > 6.0f) || (dz > 3.0f); + const bool largeCorrection = (planarDistSq > 36.0f) || (dz > 3.0f); const bool entityIsMoving = entity->isActivelyMoving(); - const bool isMovingNow = !deadOrCorpse && (entityIsMoving || planarDist > 0.03f || dz > 0.08f); + constexpr float kMoveThreshSq2 = 0.03f * 0.03f; + const bool isMovingNow = !deadOrCorpse && (entityIsMoving || planarDistSq > kMoveThreshSq2 || dz > 0.08f); if (deadOrCorpse || largeCorrection) { charRenderer->setInstancePosition(instanceId, renderPos); - } else if (planarDist > 0.03f || dz > 0.08f) { + } else if (planarDistSq > kMoveThreshSq2 || dz > 0.08f) { + float planarDist = std::sqrt(planarDistSq); float duration = std::clamp(planarDist / 5.5f, 0.05f, 0.22f); charRenderer->moveInstanceTo(instanceId, renderPos, duration); } @@ -2810,9 +2817,10 @@ void Application::setupUICallbacks() { // Compute direction and stop 2.0 units short (melee reach) glm::vec3 dir = targetRender - startRender; - float dist = glm::length(dir); - if (dist < 3.0f) return; // Too close, nothing to do - glm::vec3 dirNorm = dir / dist; + float distSq = glm::dot(dir, dir); + if (distSq < 9.0f) return; // Too close, nothing to do + float invDist = glm::inversesqrt(distSq); + glm::vec3 dirNorm = dir * invDist; glm::vec3 endRender = targetRender - dirNorm * 2.0f; // Face toward target BEFORE starting charge @@ -2827,7 +2835,7 @@ void Application::setupUICallbacks() { // Set charge state chargeActive_ = true; chargeTimer_ = 0.0f; - chargeDuration_ = std::max(dist / 25.0f, 0.3f); // ~25 units/sec + chargeDuration_ = std::max(std::sqrt(distSq) / 25.0f, 0.3f); // ~25 units/sec chargeStartPos_ = startRender; chargeEndPos_ = endRender; chargeTargetGuid_ = targetGuid; @@ -8859,7 +8867,7 @@ void Application::processPendingTransportRegistrations() { pendingTransportMoves_.erase(moveIt); } - if (glm::length(canonicalSpawnPos) < 1.0f) { + if (glm::dot(canonicalSpawnPos, canonicalSpawnPos) < 1.0f) { auto goData = gameHandler->getCachedGameObjectInfo(pending.entry); if (goData && goData->type == 15 && goData->hasData && goData->data[0] != 0) { uint32_t taxiPathId = goData->data[0]; diff --git a/src/core/logger.cpp b/src/core/logger.cpp index 0a85d6df..27162dfd 100644 --- a/src/core/logger.cpp +++ b/src/core/logger.cpp @@ -124,10 +124,11 @@ void Logger::log(LogLevel level, const std::string& message) { return; } + // Capture timestamp before acquiring lock to minimize critical section + auto nowSteady = std::chrono::steady_clock::now(); + std::lock_guard lock(mutex); ensureFile(); - - auto nowSteady = std::chrono::steady_clock::now(); if (dedupeEnabled_ && !lastMessage_.empty() && level == lastLevel_ && message == lastMessage_) { auto elapsedMs = std::chrono::duration_cast(nowSteady - lastMessageTime_).count(); diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 539006a7..17b6a98e 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -11264,8 +11264,7 @@ void GameHandler::applyUpdateObjectBlock(const UpdateBlock& block, bool& newItem if (sock1EnchIt != block.fields.end()) info.socketEnchantIds[0] = sock1EnchIt->second; if (sock2EnchIt != block.fields.end()) info.socketEnchantIds[1] = sock2EnchIt->second; if (sock3EnchIt != block.fields.end()) info.socketEnchantIds[2] = sock3EnchIt->second; - bool isNew = (onlineItems_.find(block.guid) == onlineItems_.end()); - onlineItems_[block.guid] = info; + auto [itemIt, isNew] = onlineItems_.insert_or_assign(block.guid, info); if (isNew) newItemCreated = true; queryItemInfo(info.entry, block.guid); } @@ -22953,11 +22952,11 @@ void GameHandler::startClientTaxiPath(const std::vector& pathNodes) { // Set initial orientation to face the first non-degenerate flight segment. glm::vec3 start = taxiClientPath_[0]; glm::vec3 dir(0.0f); - float dirLen = 0.0f; + float dirLenSq = 0.0f; for (size_t i = 1; i < taxiClientPath_.size(); i++) { dir = taxiClientPath_[i] - start; - dirLen = glm::length(dir); - if (dirLen >= 0.001f) { + dirLenSq = glm::dot(dir, dir); + if (dirLenSq >= 1e-6f) { break; } } @@ -22966,11 +22965,11 @@ void GameHandler::startClientTaxiPath(const std::vector& pathNodes) { float initialRenderYaw = movementInfo.orientation; float initialPitch = 0.0f; float initialRoll = 0.0f; - if (dirLen >= 0.001f) { + if (dirLenSq >= 1e-6f) { initialOrientation = std::atan2(dir.y, dir.x); glm::vec3 renderDir = core::coords::canonicalToRender(dir); initialRenderYaw = std::atan2(renderDir.y, renderDir.x); - glm::vec3 dirNorm = dir / dirLen; + glm::vec3 dirNorm = dir * glm::inversesqrt(dirLenSq); initialPitch = std::asin(std::clamp(dirNorm.z, -1.0f, 1.0f)); } @@ -23056,12 +23055,13 @@ void GameHandler::updateClientTaxi(float deltaTime) { start = taxiClientPath_[taxiClientIndex_]; end = taxiClientPath_[taxiClientIndex_ + 1]; dir = end - start; - segmentLen = glm::length(dir); + float segLenSq = glm::dot(dir, dir); - if (segmentLen < 0.01f) { + if (segLenSq < 1e-4f) { taxiClientIndex_++; continue; } + segmentLen = std::sqrt(segLenSq); if (remainingDistance >= segmentLen) { remainingDistance -= segmentLen; @@ -23099,13 +23099,13 @@ void GameHandler::updateClientTaxi(float deltaTime) { 2.0f * (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * t + 3.0f * (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t2 ); - float tangentLen = glm::length(tangent); - if (tangentLen < 0.0001f) { + float tangentLenSq = glm::dot(tangent, tangent); + if (tangentLenSq < 1e-8f) { tangent = dir; - tangentLen = glm::length(tangent); - if (tangentLen < 0.0001f) { + tangentLenSq = glm::dot(tangent, tangent); + if (tangentLenSq < 1e-8f) { tangent = glm::vec3(std::cos(movementInfo.orientation), std::sin(movementInfo.orientation), 0.0f); - tangentLen = glm::length(tangent); + tangentLenSq = 1.0f; // unit vector } } @@ -23113,7 +23113,7 @@ void GameHandler::updateClientTaxi(float deltaTime) { float targetOrientation = std::atan2(tangent.y, tangent.x); // Calculate pitch from vertical component (altitude change) - glm::vec3 tangentNorm = tangent / std::max(tangentLen, 0.0001f); + glm::vec3 tangentNorm = tangent * glm::inversesqrt(std::max(tangentLenSq, 1e-8f)); float pitch = std::asin(std::clamp(tangentNorm.z, -1.0f, 1.0f)); // Calculate roll (banking) from rate of yaw change diff --git a/src/game/transport_manager.cpp b/src/game/transport_manager.cpp index 58cb6a79..fd91c1fd 100644 --- a/src/game/transport_manager.cpp +++ b/src/game/transport_manager.cpp @@ -66,7 +66,7 @@ void TransportManager::registerTransport(uint64_t guid, uint32_t wmoInstanceId, // TransportAnimation paths are local offsets; first waypoint is expected near origin. // Warn only if the local path itself looks suspicious. glm::vec3 firstWaypoint = path.points[0].pos; - if (glm::length(firstWaypoint) > 10.0f) { + if (glm::dot(firstWaypoint, firstWaypoint) > 100.0f) { LOG_WARNING("Transport 0x", std::hex, guid, std::dec, " path ", pathId, ": first local waypoint far from origin: (", firstWaypoint.x, ",", firstWaypoint.y, ",", firstWaypoint.z, ")"); @@ -492,18 +492,18 @@ glm::quat TransportManager::orientationFromTangent(const TransportPath& path, ui ); // Normalize tangent - float tangentLength = glm::length(tangent); - if (tangentLength < 0.001f) { + float tangentLenSq = glm::dot(tangent, tangent); + if (tangentLenSq < 1e-6f) { // Fallback to simple direction tangent = p2 - p1; - tangentLength = glm::length(tangent); + tangentLenSq = glm::dot(tangent, tangent); } - if (tangentLength < 0.001f) { + if (tangentLenSq < 1e-6f) { return glm::quat(1.0f, 0.0f, 0.0f, 0.0f); // Identity } - tangent /= tangentLength; + tangent *= glm::inversesqrt(tangentLenSq); // Calculate rotation from forward direction glm::vec3 forward = tangent; @@ -565,7 +565,7 @@ void TransportManager::updateServerTransport(uint64_t guid, const glm::vec3& pos const bool isWorldCoordPath = (hasPath && pathIt->second.worldCoords && pathIt->second.durationMs > 0); // Don't let (0,0,0) server updates override a TaxiPathNode world-coordinate path - if (isWorldCoordPath && glm::length(position) < 1.0f) { + if (isWorldCoordPath && glm::dot(position, position) < 1.0f) { transport->serverUpdateCount++; transport->lastServerUpdate = elapsedTime_; transport->serverYaw = orientation; @@ -583,12 +583,13 @@ void TransportManager::updateServerTransport(uint64_t guid, const glm::vec3& pos if (isZOnlyPath || isWorldCoordPath) { transport->useClientAnimation = true; } else if (transport->useClientAnimation && hasPath && pathIt->second.fromDBC) { - float posDelta = glm::length(position - transport->position); - if (posDelta > 1.0f) { + glm::vec3 pd = position - transport->position; + float posDeltaSq = glm::dot(pd, pd); + if (posDeltaSq > 1.0f) { // Server sent a meaningfully different position — it's actively driving this transport transport->useClientAnimation = false; LOG_INFO("Transport 0x", std::hex, guid, std::dec, - " switching to server-driven (posDelta=", posDelta, ")"); + " switching to server-driven (posDeltaSq=", posDeltaSq, ")"); } // Otherwise keep client animation (server just echoed spawn pos or sent small jitter) } else if (!hasPath || !pathIt->second.fromDBC) { @@ -632,16 +633,16 @@ void TransportManager::updateServerTransport(uint64_t guid, const glm::vec3& pos const float dt = elapsedTime_ - prevUpdateTime; if (dt > 0.001f) { glm::vec3 v = (position - prevPos) / dt; - const float speed = glm::length(v); + float speedSq = glm::dot(v, v); constexpr float kMinAuthoritativeSpeed = 0.15f; constexpr float kMaxSpeed = 60.0f; - if (speed >= kMinAuthoritativeSpeed) { + if (speedSq >= kMinAuthoritativeSpeed * kMinAuthoritativeSpeed) { // Auto-detect 180-degree yaw mismatch by comparing heading to movement direction. // Some transports appear to report yaw opposite their actual travel direction. glm::vec2 horizontalV(v.x, v.y); - float hLen = glm::length(horizontalV); - if (hLen > 0.2f) { - horizontalV /= hLen; + float hLenSq = glm::dot(horizontalV, horizontalV); + if (hLenSq > 0.04f) { + horizontalV *= glm::inversesqrt(hLenSq); glm::vec2 heading(std::cos(transport->serverYaw), std::sin(transport->serverYaw)); float alignDot = glm::dot(heading, horizontalV); @@ -665,8 +666,8 @@ void TransportManager::updateServerTransport(uint64_t guid, const glm::vec3& pos } } - if (speed > kMaxSpeed) { - v *= (kMaxSpeed / speed); + if (speedSq > kMaxSpeed * kMaxSpeed) { + v *= (kMaxSpeed * glm::inversesqrt(speedSq)); } transport->serverLinearVelocity = v; @@ -738,10 +739,10 @@ void TransportManager::updateServerTransport(uint64_t guid, const glm::vec3& pos float dtSeg = static_cast(t1 - t0) / 1000.0f; if (dtSeg <= 0.001f) return; glm::vec3 v = seg / dtSeg; - float speed = glm::length(v); - if (speed < kMinBootstrapSpeed) return; - if (speed > kMaxSpeed) { - v *= (kMaxSpeed / speed); + float speedSq = glm::dot(v, v); + if (speedSq < kMinBootstrapSpeed * kMinBootstrapSpeed) return; + if (speedSq > kMaxSpeed * kMaxSpeed) { + v *= (kMaxSpeed * glm::inversesqrt(speedSq)); } transport->serverLinearVelocity = v; transport->serverAngularVelocity = 0.0f; @@ -1136,7 +1137,7 @@ bool TransportManager::assignTaxiPathToTransport(uint32_t entry, uint32_t taxiPa // Find transport(s) with matching entry that are at (0,0,0) for (auto& [guid, transport] : transports_) { if (transport.entry != entry) continue; - if (glm::length(transport.position) > 1.0f) continue; // Already has real position + if (glm::dot(transport.position, transport.position) > 1.0f) continue; // Already has real position // Copy the taxi path into the main paths_ map (indexed by entry for this transport) TransportPath path = taxiIt->second; diff --git a/src/game/world_packets.cpp b/src/game/world_packets.cpp index 016d89bb..90bd292d 100644 --- a/src/game/world_packets.cpp +++ b/src/game/world_packets.cpp @@ -2724,11 +2724,26 @@ bool CreatureQueryResponseParser::parse(network::Packet& packet, CreatureQueryRe data.family = packet.readUInt32(); data.rank = packet.readUInt32(); - // Skip remaining fields (kill credits, display IDs, modifiers, quest items, etc.) - // We've got what we need for display purposes + // killCredit[2] + displayId[4] = 6 × 4 = 24 bytes + if (!packet.hasRemaining(24)) { + LOG_WARNING("SMSG_CREATURE_QUERY_RESPONSE: truncated before displayIds (entry=", data.entry, ")"); + LOG_DEBUG("Creature query response: ", data.name, " (type=", data.creatureType, + " rank=", data.rank, ")"); + return true; + } + + packet.readUInt32(); // killCredit[0] + packet.readUInt32(); // killCredit[1] + data.displayId[0] = packet.readUInt32(); + data.displayId[1] = packet.readUInt32(); + data.displayId[2] = packet.readUInt32(); + data.displayId[3] = packet.readUInt32(); + + // Skip remaining fields (healthMultiplier, powerMultiplier, racialLeader, questItems, movementId) LOG_DEBUG("Creature query response: ", data.name, " (type=", data.creatureType, - " rank=", data.rank, ")"); + " rank=", data.rank, " displayIds=[", data.displayId[0], ",", + data.displayId[1], ",", data.displayId[2], ",", data.displayId[3], "])"); return true; } diff --git a/src/pipeline/asset_manager.cpp b/src/pipeline/asset_manager.cpp index 771bce9b..d5edce76 100644 --- a/src/pipeline/asset_manager.cpp +++ b/src/pipeline/asset_manager.cpp @@ -396,14 +396,15 @@ std::vector AssetManager::readFile(const std::string& path) const { std::string normalized = normalizePath(path); - // Check cache first + // Check cache first (shared lock allows concurrent reads) { - std::lock_guard cacheLock(cacheMutex); + std::shared_lock cacheLock(cacheMutex); auto it = fileCache.find(normalized); if (it != fileCache.end()) { - it->second.lastAccessTime = ++fileCacheAccessCounter; + auto data = it->second.data; + cacheLock.unlock(); fileCacheHits++; - return it->second.data; + return data; } } @@ -422,7 +423,7 @@ std::vector AssetManager::readFile(const std::string& path) const { // Add to cache if within budget size_t fileSize = data.size(); if (fileSize > 0 && fileSize < fileCacheBudget / 2) { - std::lock_guard cacheLock(cacheMutex); + std::lock_guard cacheLock(cacheMutex); // Evict old entries if needed (LRU) while (fileCacheTotalBytes + fileSize > fileCacheBudget && !fileCache.empty()) { auto lru = fileCache.begin(); @@ -456,13 +457,13 @@ std::vector AssetManager::readFileOptional(const std::string& path) con } void AssetManager::clearDBCCache() { - std::lock_guard lock(cacheMutex); + std::lock_guard lock(cacheMutex); dbcCache.clear(); LOG_INFO("Cleared DBC cache"); } void AssetManager::clearCache() { - std::lock_guard lock(cacheMutex); + std::lock_guard lock(cacheMutex); dbcCache.clear(); fileCache.clear(); fileCacheTotalBytes = 0; diff --git a/src/pipeline/dbc_loader.cpp b/src/pipeline/dbc_loader.cpp index 4adb9bfa..71415f1e 100644 --- a/src/pipeline/dbc_loader.cpp +++ b/src/pipeline/dbc_loader.cpp @@ -137,26 +137,32 @@ float DBCFile::getFloat(uint32_t recordIndex, uint32_t fieldIndex) const { } std::string DBCFile::getString(uint32_t recordIndex, uint32_t fieldIndex) const { + return std::string(getStringView(recordIndex, fieldIndex)); +} + +std::string_view DBCFile::getStringView(uint32_t recordIndex, uint32_t fieldIndex) const { uint32_t offset = getUInt32(recordIndex, fieldIndex); - return getStringByOffset(offset); + return getStringViewByOffset(offset); } std::string DBCFile::getStringByOffset(uint32_t offset) const { + return std::string(getStringViewByOffset(offset)); +} + +std::string_view DBCFile::getStringViewByOffset(uint32_t offset) const { if (!loaded || offset >= stringBlockSize) { - return ""; + return {}; } - // Find null terminator const char* str = reinterpret_cast(stringBlock.data() + offset); const char* end = reinterpret_cast(stringBlock.data() + stringBlockSize); - // Find string length (up to null terminator or end of block) size_t length = 0; while (str + length < end && str[length] != '\0') { length++; } - return std::string(str, length); + return std::string_view(str, length); } int32_t DBCFile::findRecordById(uint32_t id) const { diff --git a/src/pipeline/mpq_manager.cpp b/src/pipeline/mpq_manager.cpp index 98a984b1..c6ab2264 100644 --- a/src/pipeline/mpq_manager.cpp +++ b/src/pipeline/mpq_manager.cpp @@ -169,7 +169,7 @@ void MPQManager::shutdown() { archives.clear(); archiveNames.clear(); { - std::lock_guard lock(fileArchiveCacheMutex_); + std::lock_guard lock(fileArchiveCacheMutex_); fileArchiveCache_.clear(); } { @@ -214,7 +214,7 @@ bool MPQManager::loadArchive(const std::string& path, int priority) { // Archive set/priority changed, so cached filename -> archive mappings may be stale. { - std::lock_guard lock(fileArchiveCacheMutex_); + std::lock_guard lock(fileArchiveCacheMutex_); fileArchiveCache_.clear(); } @@ -383,7 +383,7 @@ HANDLE MPQManager::findFileArchive(const std::string& filename) const { #ifdef HAVE_STORMLIB std::string cacheKey = normalizeVirtualFilenameForLookup(filename); { - std::lock_guard lock(fileArchiveCacheMutex_); + std::shared_lock lock(fileArchiveCacheMutex_); auto it = fileArchiveCache_.find(cacheKey); if (it != fileArchiveCache_.end()) { return it->second; @@ -416,7 +416,7 @@ HANDLE MPQManager::findFileArchive(const std::string& filename) const { } { - std::lock_guard lock(fileArchiveCacheMutex_); + std::lock_guard lock(fileArchiveCacheMutex_); if (fileArchiveCache_.size() >= fileArchiveCacheMaxEntries_) { // Simple safety valve: clear the cache rather than allowing an unbounded growth. LOG_WARNING("MPQ archive lookup cache cleared (size=", fileArchiveCache_.size(), diff --git a/src/rendering/camera_controller.cpp b/src/rendering/camera_controller.cpp index fe089da4..feec51f7 100644 --- a/src/rendering/camera_controller.cpp +++ b/src/rendering/camera_controller.cpp @@ -111,9 +111,11 @@ std::optional CameraController::getCachedFloorHeight(float x, float y, fl // Check cache validity (position within threshold and frame count) glm::vec2 queryPos(x, y); glm::vec2 cachedPos(lastFloorQueryPos.x, lastFloorQueryPos.y); - float dist = glm::length(queryPos - cachedPos); + glm::vec2 dq = queryPos - cachedPos; + float distSq = glm::dot(dq, dq); + constexpr float kFloorThresholdSq = FLOOR_QUERY_DISTANCE_THRESHOLD * FLOOR_QUERY_DISTANCE_THRESHOLD; - if (dist < FLOOR_QUERY_DISTANCE_THRESHOLD && floorQueryFrameCounter < FLOOR_QUERY_FRAME_INTERVAL) { + if (distSq < kFloorThresholdSq && floorQueryFrameCounter < FLOOR_QUERY_FRAME_INTERVAL) { floorQueryFrameCounter++; return cachedFloorHeight; } @@ -194,7 +196,7 @@ void CameraController::update(float deltaTime) { } // Smooth camera position - if (glm::length(smoothedCamPos) < 0.01f) { + if (glm::dot(smoothedCamPos, smoothedCamPos) < 1e-4f) { smoothedCamPos = actualCam; } float camLerp = 1.0f - std::exp(-CAM_SMOOTH_SPEED * deltaTime); @@ -516,9 +518,9 @@ void CameraController::update(float deltaTime) { }; glm::vec2 fwd2(forward.x, forward.y); - float fwdLen = glm::length(fwd2); - if (fwdLen > 1e-4f) { - fwd2 /= fwdLen; + float fwdLenSq = glm::dot(fwd2, fwd2); + if (fwdLenSq > 1e-8f) { + fwd2 *= glm::inversesqrt(fwdLenSq); std::optional aheadFloor; const float probeZ = targetPos.z + 2.0f; const float dists[] = {0.45f, 0.90f, 1.25f}; @@ -566,7 +568,7 @@ void CameraController::update(float deltaTime) { } else { // Manual control: use camera's 3D direction (swim where you look) swimForward = glm::normalize(forward3D); - if (glm::length(swimForward) < 1e-4f) { + if (glm::dot(swimForward, swimForward) < 1e-8f) { swimForward = forward; } } @@ -583,8 +585,9 @@ void CameraController::update(float deltaTime) { if (nowStrafeLeft) swimMove += swimRight; if (nowStrafeRight) swimMove -= swimRight; - if (glm::length(swimMove) > 0.001f) { - swimMove = glm::normalize(swimMove); + float swimMoveLenSq = glm::dot(swimMove, swimMove); + if (swimMoveLenSq > 1e-6f) { + swimMove *= glm::inversesqrt(swimMoveLenSq); // Use backward swim speed when moving backwards only (not when combining with strafe) float applySpeed = (nowBackward && !nowForward) ? swimBackSpeed : swimSpeed; targetPos += swimMove * applySpeed * physicsDeltaTime; @@ -623,10 +626,12 @@ void CameraController::update(float deltaTime) { // Prevent sinking/clipping through world floor while swimming. // Cache floor queries (update every 3 frames or 1 unit movement) std::optional floorH; - float dist2D = glm::length(glm::vec2(targetPos.x - lastFloorQueryPos.x, - targetPos.y - lastFloorQueryPos.y)); + float dx2D = targetPos.x - lastFloorQueryPos.x; + float dy2D = targetPos.y - lastFloorQueryPos.y; + float dist2DSq = dx2D * dx2D + dy2D * dy2D; + constexpr float kFloorDistSq = FLOOR_QUERY_DISTANCE_THRESHOLD * FLOOR_QUERY_DISTANCE_THRESHOLD; bool updateFloorCache = (floorQueryFrameCounter++ >= FLOOR_QUERY_FRAME_INTERVAL) || - (dist2D > FLOOR_QUERY_DISTANCE_THRESHOLD); + (dist2DSq > kFloorDistSq); if (updateFloorCache) { floorQueryFrameCounter = 0; @@ -685,10 +690,12 @@ void CameraController::update(float deltaTime) { { glm::vec3 swimFrom = *followTarget; glm::vec3 swimTo = targetPos; - float swimMoveDist = glm::length(swimTo - swimFrom); + glm::vec3 swimDelta = swimTo - swimFrom; + float swimMoveDistSq = glm::dot(swimDelta, swimDelta); glm::vec3 stepPos = swimFrom; - if (swimMoveDist > 0.01f) { + if (swimMoveDistSq > 1e-4f) { + float swimMoveDist = std::sqrt(swimMoveDistSq); float swimStepSize = cachedInsideWMO ? 0.20f : 0.35f; int swimSteps = std::max(1, std::min(8, static_cast(std::ceil(swimMoveDist / swimStepSize)))); glm::vec3 stepDelta = (swimTo - swimFrom) / static_cast(swimSteps); @@ -746,7 +753,7 @@ void CameraController::update(float deltaTime) { // Forward/back follows camera 3D direction (same as swim) glm::vec3 flyFwd = glm::normalize(forward3D); - if (glm::length(flyFwd) < 1e-4f) flyFwd = forward; + if (glm::dot(flyFwd, flyFwd) < 1e-8f) flyFwd = forward; glm::vec3 flyMove(0.0f); if (nowForward) flyMove += flyFwd; if (nowBackward) flyMove -= flyFwd; @@ -756,8 +763,9 @@ void CameraController::update(float deltaTime) { bool flyDescend = !uiWantsKeyboard && xDown && mounted_; if (nowJump) flyMove.z += 1.0f; if (flyDescend) flyMove.z -= 1.0f; - if (glm::length(flyMove) > 0.001f) { - flyMove = glm::normalize(flyMove); + float flyMoveLenSq = glm::dot(flyMove, flyMove); + if (flyMoveLenSq > 1e-6f) { + flyMove *= glm::inversesqrt(flyMoveLenSq); float flyFwdSpeed = (flightSpeedOverride_ > 0.0f && flightSpeedOverride_ < 200.0f && !std::isnan(flightSpeedOverride_)) ? flightSpeedOverride_ : speed; @@ -771,8 +779,9 @@ void CameraController::update(float deltaTime) { // Skip all ground physics — go straight to collision/WMO sections } else { - if (glm::length(movement) > 0.001f) { - movement = glm::normalize(movement); + float moveLenSq = glm::dot(movement, movement); + if (moveLenSq > 1e-6f) { + movement *= glm::inversesqrt(moveLenSq); targetPos += movement * speed * physicsDeltaTime; } @@ -784,7 +793,7 @@ void CameraController::update(float deltaTime) { float drag = std::exp(-KNOCKBACK_HORIZ_DRAG * physicsDeltaTime); knockbackHorizVel_ *= drag; // Once negligible, clear the flag so collision/grounding work normally. - if (glm::length(knockbackHorizVel_) < 0.05f) { + if (glm::dot(knockbackHorizVel_, knockbackHorizVel_) < 0.0025f) { knockbackActive_ = false; knockbackHorizVel_ = glm::vec2(0.0f); } @@ -829,8 +838,9 @@ 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_) { - const float insideDist = glm::length(targetPos - lastInsideStateCheckPos_); - if (++insideStateCheckCounter_ >= 2 || insideDist > 0.35f) { + glm::vec3 insideDelta = targetPos - lastInsideStateCheckPos_; + float insideDistSq = glm::dot(insideDelta, insideDelta); + if (++insideStateCheckCounter_ >= 2 || insideDistSq > 0.1225f) { insideStateCheckCounter_ = 0; lastInsideStateCheckPos_ = targetPos; @@ -853,9 +863,11 @@ void CameraController::update(float deltaTime) { { glm::vec3 startPos = *followTarget; glm::vec3 desiredPos = targetPos; - float moveDist = glm::length(desiredPos - startPos); + glm::vec3 moveDelta = desiredPos - startPos; + float moveDistSq = glm::dot(moveDelta, moveDelta); - if (moveDist > 0.01f) { + if (moveDistSq > 1e-4f) { + float moveDist = std::sqrt(moveDistSq); // Smaller step size when inside buildings for tighter collision float stepSize = cachedInsideWMO ? 0.20f : 0.35f; int sweepSteps = std::max(1, std::min(8, static_cast(std::ceil(moveDist / stepSize)))); @@ -909,9 +921,11 @@ void CameraController::update(float deltaTime) { std::optional centerM2H; { // 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 = grounded && hasCachedFloor_ && distMoved < COLLISION_CACHE_DISTANCE; + float dmx = targetPos.x - lastCollisionCheckPos_.x; + float dmy = targetPos.y - lastCollisionCheckPos_.y; + float distMovedSq = dmx * dmx + dmy * dmy; + constexpr float kCollisionCacheDistSq = COLLISION_CACHE_DISTANCE * COLLISION_CACHE_DISTANCE; + bool useCached = grounded && hasCachedFloor_ && distMovedSq < kCollisionCacheDistSq; if (useCached) { // Never trust cached ground while actively descending or when // vertical drift from cached floor is meaningful. @@ -1371,11 +1385,13 @@ void CameraController::update(float deltaTime) { float mountedOffset = mounted_ ? mountHeightOffset_ : 0.0f; float pivotLift = 0.0f; if (terrainManager && !externalFollow_ && !cachedInsideInteriorWMO) { - float moved = glm::length(glm::vec2(targetPos.x - lastPivotLiftQueryPos_.x, - targetPos.y - lastPivotLiftQueryPos_.y)); + float plx = targetPos.x - lastPivotLiftQueryPos_.x; + float ply = targetPos.y - lastPivotLiftQueryPos_.y; + float movedSq = plx * plx + ply * ply; + constexpr float kPivotLiftPosSq = PIVOT_LIFT_POS_THRESHOLD * PIVOT_LIFT_POS_THRESHOLD; float distDelta = std::abs(currentDistance - lastPivotLiftDistance_); bool queryLift = (++pivotLiftQueryCounter_ >= PIVOT_LIFT_QUERY_INTERVAL) || - (moved >= PIVOT_LIFT_POS_THRESHOLD) || + (movedSq >= kPivotLiftPosSq) || (distDelta >= PIVOT_LIFT_DIST_THRESHOLD); if (queryLift) { pivotLiftQueryCounter_ = 0; @@ -1421,8 +1437,9 @@ void CameraController::update(float deltaTime) { // Limit max zoom when inside a WMO with a ceiling (building interior) // Throttle: only recheck every 10 frames or when position changes >2 units. if (wmoRenderer) { - float distFromLastCheck = glm::length(targetPos - lastInsideWMOCheckPos); - if (++insideWMOCheckCounter >= 10 || distFromLastCheck > 2.0f) { + glm::vec3 wmoCheckDelta = targetPos - lastInsideWMOCheckPos; + float distFromLastCheckSq = glm::dot(wmoCheckDelta, wmoCheckDelta); + if (++insideWMOCheckCounter >= 10 || distFromLastCheckSq > 4.0f) { wmoRenderer->updateActiveGroup(targetPos.x, targetPos.y, targetPos.z + 1.0f); insideWMOCheckCounter = 0; lastInsideWMOCheckPos = targetPos; @@ -1486,7 +1503,7 @@ void CameraController::update(float deltaTime) { } // Smooth camera position to avoid jitter - if (glm::length(smoothedCamPos) < 0.01f) { + if (glm::dot(smoothedCamPos, smoothedCamPos) < 1e-4f) { smoothedCamPos = actualCam; // Initialize } float camLerp = 1.0f - std::exp(-CAM_SMOOTH_SPEED * deltaTime); @@ -1503,9 +1520,10 @@ void CameraController::update(float deltaTime) { std::optional camWmoH; if (wmoRenderer) { // Skip expensive WMO floor query if camera barely moved - float camDelta = glm::length(glm::vec2(smoothedCamPos.x - lastCamFloorQueryPos.x, - smoothedCamPos.y - lastCamFloorQueryPos.y)); - if (camDelta < 0.3f && hasCachedCamFloor) { + float cdx = smoothedCamPos.x - lastCamFloorQueryPos.x; + float cdy = smoothedCamPos.y - lastCamFloorQueryPos.y; + float camDeltaSq = cdx * cdx + cdy * cdy; + if (camDeltaSq < 0.09f && hasCachedCamFloor) { camWmoH = cachedCamWmoFloor; } else { float camFloorProbeZ = smoothedCamPos.z; @@ -1618,8 +1636,9 @@ void CameraController::update(float deltaTime) { float waterSurfaceCamZ = waterH ? (*waterH - WATER_SURFACE_OFFSET + eyeHeight) : newPos.z; bool diveIntent = nowForward && (forward3D.z < -0.28f); - if (glm::length(movement) > 0.001f) { - movement = glm::normalize(movement); + float movLenSq = glm::dot(movement, movement); + if (movLenSq > 1e-6f) { + movement *= glm::inversesqrt(movLenSq); newPos += movement * swimSpeed * physicsDeltaTime; } @@ -1652,8 +1671,9 @@ void CameraController::update(float deltaTime) { } else { swimming = false; - if (glm::length(movement) > 0.001f) { - movement = glm::normalize(movement); + float movLenSq2 = glm::dot(movement, movement); + if (movLenSq2 > 1e-6f) { + movement *= glm::inversesqrt(movLenSq2); newPos += movement * speed * physicsDeltaTime; } @@ -1680,9 +1700,11 @@ void CameraController::update(float deltaTime) { if (wmoRenderer) { glm::vec3 startFeet = camera->getPosition() - glm::vec3(0, 0, eyeHeight); glm::vec3 desiredFeet = newPos - glm::vec3(0, 0, eyeHeight); - float moveDist = glm::length(desiredFeet - startFeet); + glm::vec3 feetDelta = desiredFeet - startFeet; + float moveDistSq2 = glm::dot(feetDelta, feetDelta); - if (moveDist > 0.01f) { + if (moveDistSq2 > 1e-4f) { + float moveDist = std::sqrt(moveDistSq2); float stepSize = cachedInsideWMO ? 0.20f : 0.35f; int sweepSteps = std::max(1, std::min(8, static_cast(std::ceil(moveDist / stepSize)))); glm::vec3 stepPos = startFeet; diff --git a/src/rendering/character_renderer.cpp b/src/rendering/character_renderer.cpp index 2cc66265..f9e5352a 100644 --- a/src/rendering/character_renderer.cpp +++ b/src/rendering/character_renderer.cpp @@ -319,8 +319,11 @@ void CharacterRenderer::shutdown() { " models=", models.size(), " override=", (void*)renderPassOverride_); // Wait for any in-flight background normal map generation threads - while (pendingNormalMapCount_.load(std::memory_order_relaxed) > 0) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + { + std::unique_lock lock(normalMapResultsMutex_); + normalMapDoneCV_.wait(lock, [this] { + return pendingNormalMapCount_.load(std::memory_order_acquire) == 0; + }); } vkDeviceWaitIdle(vkCtx_->getDevice()); @@ -407,8 +410,11 @@ void CharacterRenderer::clear() { " models=", models.size()); // Wait for any in-flight background normal map generation threads - while (pendingNormalMapCount_.load(std::memory_order_relaxed) > 0) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + { + std::unique_lock lock(normalMapResultsMutex_); + normalMapDoneCV_.wait(lock, [this] { + return pendingNormalMapCount_.load(std::memory_order_acquire) == 0; + }); } // Discard any completed results that haven't been uploaded { @@ -731,7 +737,9 @@ VkTexture* CharacterRenderer::loadTexture(const std::string& path) { std::lock_guard lock(self->normalMapResultsMutex_); self->completedNormalMaps_.push_back(std::move(result)); } - self->pendingNormalMapCount_.fetch_sub(1, std::memory_order_relaxed); + if (self->pendingNormalMapCount_.fetch_sub(1, std::memory_order_release) == 1) { + self->normalMapDoneCV_.notify_one(); + } }).detach(); e.normalMapPending = true; } @@ -2825,11 +2833,12 @@ void CharacterRenderer::moveInstanceTo(uint32_t instanceId, const glm::vec3& des return 0; }; - float planarDist = glm::length(glm::vec2(destination.x - inst.position.x, - destination.y - inst.position.y)); + float pdx = destination.x - inst.position.x; + float pdy = destination.y - inst.position.y; + float planarDistSq = pdx * pdx + pdy * pdy; bool synthesizedDuration = false; if (durationSeconds <= 0.0f) { - if (planarDist < 0.01f) { + if (planarDistSq < 1e-4f) { // Stop at current location. inst.position = destination; inst.isMoving = false; @@ -2840,7 +2849,7 @@ void CharacterRenderer::moveInstanceTo(uint32_t instanceId, const glm::vec3& des } // Some cores send movement-only deltas without spline duration. // Synthesize a tiny duration so movement anim/rotation still updates. - durationSeconds = std::clamp(planarDist / 7.0f, 0.05f, 0.20f); + durationSeconds = std::clamp(std::sqrt(planarDistSq) / 7.0f, 0.05f, 0.20f); synthesizedDuration = true; } @@ -2852,14 +2861,14 @@ void CharacterRenderer::moveInstanceTo(uint32_t instanceId, const glm::vec3& des // Face toward destination (yaw around Z axis since Z is up) glm::vec3 dir = destination - inst.position; - if (glm::length(glm::vec2(dir.x, dir.y)) > 0.001f) { + if (dir.x * dir.x + dir.y * dir.y > 1e-6f) { float angle = std::atan2(dir.y, dir.x); inst.rotation.z = angle; } // Play movement animation while moving. // Prefer run only when speed is clearly above normal walk pace. - float moveSpeed = planarDist / std::max(durationSeconds, 0.001f); + float moveSpeed = std::sqrt(planarDistSq) / std::max(durationSeconds, 0.001f); bool preferRun = (!synthesizedDuration && moveSpeed >= 4.5f); uint32_t moveAnim = pickMoveAnim(preferRun); if (moveAnim != 0 && inst.currentAnimationId != moveAnim) { diff --git a/src/rendering/charge_effect.cpp b/src/rendering/charge_effect.cpp index 32a3b36d..f4282b43 100644 --- a/src/rendering/charge_effect.cpp +++ b/src/rendering/charge_effect.cpp @@ -449,8 +449,9 @@ void ChargeEffect::emit(const glm::vec3& position, const glm::vec3& direction) { } // Only add a new trail point if we've moved enough - float dist = glm::length(position - lastEmitPos_); - if (dist >= TRAIL_SPAWN_DIST || trail_.empty()) { + glm::vec3 emitDelta = position - lastEmitPos_; + float distSq = glm::dot(emitDelta, emitDelta); + if (distSq >= TRAIL_SPAWN_DIST * TRAIL_SPAWN_DIST || trail_.empty()) { // Ribbon is vertical: side vector points straight up glm::vec3 side = glm::vec3(0.0f, 0.0f, 1.0f); @@ -466,9 +467,10 @@ void ChargeEffect::emit(const glm::vec3& position, const glm::vec3& direction) { // Spawn dust puffs at feet glm::vec3 horizDir = glm::vec3(direction.x, direction.y, 0.0f); - float horizLen = glm::length(horizDir); - if (horizLen < 0.001f) return; - glm::vec3 backDir = -horizDir / horizLen; + float horizLenSq = glm::dot(horizDir, horizDir); + if (horizLenSq < 1e-6f) return; + float invHorizLen = glm::inversesqrt(horizLenSq); + glm::vec3 backDir = -horizDir * invHorizLen; glm::vec3 sideDir = glm::vec3(-backDir.y, backDir.x, 0.0f); dustAccum_ += 30.0f * 0.016f; diff --git a/src/rendering/frustum.cpp b/src/rendering/frustum.cpp index 5df490fc..872097cc 100644 --- a/src/rendering/frustum.cpp +++ b/src/rendering/frustum.cpp @@ -63,10 +63,11 @@ void Frustum::extractFromMatrix(const glm::mat4& vp) { } void Frustum::normalizePlane(Plane& plane) { - float length = glm::length(plane.normal); - if (length > 0.0001f) { - plane.normal /= length; - plane.distance /= length; + float lenSq = glm::dot(plane.normal, plane.normal); + if (lenSq > 0.00000001f) { + float invLen = glm::inversesqrt(lenSq); + plane.normal *= invLen; + plane.distance *= invLen; } } diff --git a/src/rendering/lens_flare.cpp b/src/rendering/lens_flare.cpp index e9a9bb04..debddff5 100644 --- a/src/rendering/lens_flare.cpp +++ b/src/rendering/lens_flare.cpp @@ -301,10 +301,11 @@ void LensFlare::render(VkCommandBuffer cmd, const Camera& camera, const glm::vec // Sun billboard rendering is sky-locked (view translation removed), so anchor // flare projection to camera position along sun direction to avoid parallax drift. glm::vec3 sunDir = sunPosition; - if (glm::length(sunDir) < 0.0001f) { + float sunDirLenSq = glm::dot(sunDir, sunDir); + if (sunDirLenSq < 1e-8f) { return; } - sunDir = glm::normalize(sunDir); + sunDir *= glm::inversesqrt(sunDirLenSq); glm::vec3 anchoredSunPos = camera.getPosition() + sunDir * 800.0f; // Calculate sun visibility diff --git a/src/rendering/lighting_manager.cpp b/src/rendering/lighting_manager.cpp index beaef514..e6460b87 100644 --- a/src/rendering/lighting_manager.cpp +++ b/src/rendering/lighting_manager.cpp @@ -305,8 +305,9 @@ void LightingManager::update(const glm::vec3& playerPos, uint32_t mapId, } // Normalize blended direction - if (glm::length(blendedDir) > 0.001f) { - newParams.directionalDir = glm::normalize(blendedDir); + float blendedDirLenSq = glm::dot(blendedDir, blendedDir); + if (blendedDirLenSq > 1e-6f) { + newParams.directionalDir = blendedDir * glm::inversesqrt(blendedDirLenSq); } else { // Fallback if all directions cancelled out newParams.directionalDir = glm::vec3(0.3f, -0.7f, 0.6f); @@ -371,14 +372,16 @@ std::vector LightingManager::findLightVolumes(c for (const auto& volume : volumes) { // Apply coordinate scaling (test with 1.0f, try 36.0f if distances are off) glm::vec3 scaledPos = volume.position * LIGHT_COORD_SCALE; - float dist = glm::length(playerPos - scaledPos); + glm::vec3 toPlayer = playerPos - scaledPos; + float distSq = glm::dot(toPlayer, toPlayer); float weight = 0.0f; - if (dist <= volume.innerRadius) { + if (distSq <= volume.innerRadius * volume.innerRadius) { // Inside inner radius: full weight weight = 1.0f; - } else if (dist < volume.outerRadius) { - // Between inner and outer: fade out with smoothstep + } else if (distSq < volume.outerRadius * volume.outerRadius) { + // Between inner and outer: fade out with smoothstep (sqrt needed for interpolation) + float dist = std::sqrt(distSq); float t = (dist - volume.innerRadius) / (volume.outerRadius - volume.innerRadius); t = glm::clamp(t, 0.0f, 1.0f); weight = 1.0f - (t * t * (3.0f - 2.0f * t)); // Smoothstep @@ -389,7 +392,7 @@ std::vector LightingManager::findLightVolumes(c // Debug logging for first few volumes if (weighted.size() <= 3) { - LOG_INFO("Light volume ", volume.lightId, ": dist=", dist, + LOG_INFO("Light volume ", volume.lightId, ": distSq=", distSq, " inner=", volume.innerRadius, " outer=", volume.outerRadius, " weight=", weight); } @@ -400,15 +403,20 @@ std::vector LightingManager::findLightVolumes(c return {}; } - // Sort by weight descending - std::sort(weighted.begin(), weighted.end(), - [](const WeightedVolume& a, const WeightedVolume& b) { - return a.weight > b.weight; - }); - - // Keep top N volumes + // Keep top N volumes by weight (partial sort is O(n) vs O(n log n) for full sort) if (weighted.size() > MAX_BLEND_VOLUMES) { + std::partial_sort(weighted.begin(), + weighted.begin() + MAX_BLEND_VOLUMES, + weighted.end(), + [](const WeightedVolume& a, const WeightedVolume& b) { + return a.weight > b.weight; + }); weighted.resize(MAX_BLEND_VOLUMES); + } else { + std::sort(weighted.begin(), weighted.end(), + [](const WeightedVolume& a, const WeightedVolume& b) { + return a.weight > b.weight; + }); } // Normalize weights to sum to 1.0 diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index 31f66a1c..d78845c6 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -947,6 +947,9 @@ void M2ModelGPU::CollisionMesh::getFloorTrisInRange( int cxMax = std::clamp(static_cast((maxX - gridOrigin.x) / CELL_SIZE), 0, gridCellsX - 1); int cyMin = std::clamp(static_cast((minY - gridOrigin.y) / CELL_SIZE), 0, gridCellsY - 1); int cyMax = std::clamp(static_cast((maxY - gridOrigin.y) / CELL_SIZE), 0, gridCellsY - 1); + const size_t cellCount = static_cast(cxMax - cxMin + 1) * + static_cast(cyMax - cyMin + 1); + out.reserve(cellCount * 8); for (int cy = cyMin; cy <= cyMax; cy++) { for (int cx = cxMin; cx <= cxMax; cx++) { const auto& cell = cellFloorTris[cy * gridCellsX + cx]; @@ -966,6 +969,9 @@ void M2ModelGPU::CollisionMesh::getWallTrisInRange( int cxMax = std::clamp(static_cast((maxX - gridOrigin.x) / CELL_SIZE), 0, gridCellsX - 1); int cyMin = std::clamp(static_cast((minY - gridOrigin.y) / CELL_SIZE), 0, gridCellsY - 1); int cyMax = std::clamp(static_cast((maxY - gridOrigin.y) / CELL_SIZE), 0, gridCellsY - 1); + const size_t cellCount = static_cast(cxMax - cxMin + 1) * + static_cast(cyMax - cyMin + 1); + out.reserve(cellCount * 8); for (int cy = cyMin; cy <= cyMax; cy++) { for (int cx = cxMin; cx <= cxMax; cx++) { const auto& cell = cellWallTris[cy * gridCellsX + cx]; @@ -3227,8 +3233,8 @@ void M2Renderer::emitParticles(M2Instance& inst, const M2ModelGPU& gpu, float dt dir.x += distN(particleRng_) * hRange; dir.y += distN(particleRng_) * hRange; dir.z += distN(particleRng_) * vRange; - float len = glm::length(dir); - if (len > 0.001f) dir /= len; + float lenSq = glm::dot(dir, dir); + if (lenSq > 0.001f * 0.001f) dir *= glm::inversesqrt(lenSq); // Transform direction by bone + model orientation (rotation only) glm::mat3 rotMat = glm::mat3(inst.modelMatrix * boneXform); @@ -4715,7 +4721,7 @@ float M2Renderer::raycastBoundingBoxes(const glm::vec3& origin, const glm::vec3& getTightCollisionBounds(model, localMin, localMax); // Skip tiny doodads for camera occlusion; they cause jitter and false hits. glm::vec3 extents = (localMax - localMin) * instance.scale; - if (glm::length(extents) < 0.75f) continue; + if (glm::dot(extents, extents) < 0.5625f) continue; glm::vec3 localOrigin = glm::vec3(instance.invModelMatrix * glm::vec4(origin, 1.0f)); glm::vec3 localDir = glm::normalize(glm::vec3(instance.invModelMatrix * glm::vec4(direction, 0.0f))); diff --git a/src/rendering/minimap.cpp b/src/rendering/minimap.cpp index 7cccca2b..e6940d3e 100644 --- a/src/rendering/minimap.cpp +++ b/src/rendering/minimap.cpp @@ -421,10 +421,11 @@ void Minimap::compositePass(VkCommandBuffer cmd, const glm::vec3& centerWorldPos const auto now = std::chrono::steady_clock::now(); bool needsRefresh = !hasCachedFrame; if (!needsRefresh) { - float moved = glm::length(glm::vec2(centerWorldPos.x - lastUpdatePos.x, - centerWorldPos.y - lastUpdatePos.y)); + float mdx = centerWorldPos.x - lastUpdatePos.x; + float mdy = centerWorldPos.y - lastUpdatePos.y; + float movedSq = mdx * mdx + mdy * mdy; float elapsed = std::chrono::duration(now - lastUpdateTime).count(); - needsRefresh = (moved >= updateDistance) || (elapsed >= updateIntervalSec); + needsRefresh = (movedSq >= updateDistance * updateDistance) || (elapsed >= updateIntervalSec); } // Also refresh if player crossed a tile boundary diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index 2f674153..604ab932 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -3242,7 +3242,7 @@ void Renderer::update(float deltaTime) { } else if (inCombat_ && targetPosition && !emoteActive && !isMounted()) { // Face target when in combat and idle glm::vec3 toTarget = *targetPosition - characterPosition; - if (glm::length(glm::vec2(toTarget.x, toTarget.y)) > 0.1f) { + if (toTarget.x * toTarget.x + toTarget.y * toTarget.y > 0.01f) { float targetYaw = glm::degrees(std::atan2(toTarget.y, toTarget.x)); float diff = targetYaw - characterYaw; while (diff > 180.0f) diff -= 360.0f; @@ -6222,8 +6222,9 @@ glm::mat4 Renderer::computeLightSpaceMatrix() { glm::vec3 sunDir = glm::normalize(glm::vec3(-0.3f, -0.7f, -0.6f)); if (lightingManager) { const auto& lighting = lightingManager->getLightingParams(); - if (glm::length(lighting.directionalDir) > 0.001f) { - sunDir = glm::normalize(-lighting.directionalDir); + float ldirLenSq = glm::dot(lighting.directionalDir, lighting.directionalDir); + if (ldirLenSq > 1e-6f) { + sunDir = -lighting.directionalDir * glm::inversesqrt(ldirLenSq); } } // Shadow camera expects light rays pointing downward in render space (Z up). diff --git a/src/rendering/sky_system.cpp b/src/rendering/sky_system.cpp index 98e27621..af2e87e5 100644 --- a/src/rendering/sky_system.cpp +++ b/src/rendering/sky_system.cpp @@ -156,8 +156,9 @@ void SkySystem::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, } glm::vec3 SkySystem::getSunPosition(const SkyParams& params) const { - glm::vec3 dir = glm::normalize(params.directionalDir); - if (glm::length(dir) < 0.0001f) { + float dirLenSq = glm::dot(params.directionalDir, params.directionalDir); + glm::vec3 dir = (dirLenSq > 1e-8f) ? params.directionalDir * glm::inversesqrt(dirLenSq) : glm::vec3(0.0f); + if (dirLenSq < 1e-8f) { dir = glm::vec3(0.0f, 0.0f, -1.0f); } glm::vec3 sunDir = -dir; diff --git a/src/rendering/swim_effects.cpp b/src/rendering/swim_effects.cpp index e439c2dd..e964f736 100644 --- a/src/rendering/swim_effects.cpp +++ b/src/rendering/swim_effects.cpp @@ -542,7 +542,7 @@ void SwimEffects::update(const Camera& camera, const CameraController& cc, // Compute movement direction from camera yaw float yawRad = glm::radians(cc.getYaw()); glm::vec3 moveDir(std::cos(yawRad), std::sin(yawRad), 0.0f); - if (glm::length(glm::vec2(moveDir)) > 0.001f) { + if (moveDir.x * moveDir.x + moveDir.y * moveDir.y > 1e-6f) { moveDir = glm::normalize(moveDir); } @@ -676,6 +676,7 @@ void SwimEffects::update(const Camera& camera, const CameraController& cc, // --- Build vertex data --- rippleVertexData.clear(); + rippleVertexData.reserve(ripples.size() * 5); for (const auto& p : ripples) { rippleVertexData.push_back(p.position.x); rippleVertexData.push_back(p.position.y); @@ -685,6 +686,7 @@ void SwimEffects::update(const Camera& camera, const CameraController& cc, } bubbleVertexData.clear(); + bubbleVertexData.reserve(bubbles.size() * 5); for (const auto& p : bubbles) { bubbleVertexData.push_back(p.position.x); bubbleVertexData.push_back(p.position.y); @@ -694,6 +696,7 @@ void SwimEffects::update(const Camera& camera, const CameraController& cc, } insectVertexData.clear(); + insectVertexData.reserve(insects.size() * 5); for (const auto& p : insects) { insectVertexData.push_back(p.position.x); insectVertexData.push_back(p.position.y); diff --git a/src/rendering/water_renderer.cpp b/src/rendering/water_renderer.cpp index b8d3d33b..88c22879 100644 --- a/src/rendering/water_renderer.cpp +++ b/src/rendering/water_renderer.cpp @@ -922,7 +922,8 @@ void WaterRenderer::loadFromWMO([[maybe_unused]] const pipeline::WMOLiquid& liqu float stepXLen = glm::length(surface.stepX); float stepYLen = glm::length(surface.stepY); glm::vec3 planeN = glm::cross(surface.stepX, surface.stepY); - float nz = (glm::length(planeN) > 1e-4f) ? std::abs(glm::normalize(planeN).z) : 0.0f; + float planeNLenSq = glm::dot(planeN, planeN); + float nz = (planeNLenSq > 1e-8f) ? std::abs(planeN.z * glm::inversesqrt(planeNLenSq)) : 0.0f; float spanX = stepXLen * static_cast(surface.width); float spanY = stepYLen * static_cast(surface.height); if (stepXLen < 0.2f || stepXLen > 12.0f || diff --git a/src/rendering/weather.cpp b/src/rendering/weather.cpp index 6f81aae0..e25ad4e9 100644 --- a/src/rendering/weather.cpp +++ b/src/rendering/weather.cpp @@ -221,6 +221,7 @@ void Weather::update(const Camera& camera, float deltaTime) { // Update position buffer particlePositions.clear(); + particlePositions.reserve(particles.size()); for (const auto& particle : particles) { particlePositions.push_back(particle.position); } @@ -232,9 +233,10 @@ void Weather::updateParticle(Particle& particle, const Camera& camera, float del // Reset if lifetime exceeded or too far from camera glm::vec3 cameraPos = camera.getPosition(); - float distance = glm::length(particle.position - cameraPos); + glm::vec3 toCamera = particle.position - cameraPos; + float distSq = glm::dot(toCamera, toCamera); - if (particle.lifetime >= particle.maxLifetime || distance > SPAWN_VOLUME_SIZE || + if (particle.lifetime >= particle.maxLifetime || distSq > SPAWN_VOLUME_SIZE * SPAWN_VOLUME_SIZE || particle.position.y < cameraPos.y - 20.0f) { // Respawn at top particle.position = getRandomPosition(cameraPos); diff --git a/src/rendering/wmo_renderer.cpp b/src/rendering/wmo_renderer.cpp index c5677d66..fdcfd3df 100644 --- a/src/rendering/wmo_renderer.cpp +++ b/src/rendering/wmo_renderer.cpp @@ -2101,6 +2101,7 @@ void WMORenderer::getVisibleGroupsViaPortals(const ModelData& model, // BFS through portals from camera's group std::vector visited(model.groups.size(), false); std::vector queue; + queue.reserve(model.groups.size()); queue.push_back(static_cast(cameraGroup)); visited[cameraGroup] = true; outVisibleGroups.insert(static_cast(cameraGroup)); @@ -2731,6 +2732,11 @@ void WMORenderer::GroupResources::getTrianglesInRange( if (cellMinX > cellMaxX || cellMinY > cellMaxY) return; + // Reserve estimate: cells queried * ~8 triangles per cell + const size_t cellCount = static_cast(cellMaxX - cellMinX + 1) * + static_cast(cellMaxY - cellMinY + 1); + out.reserve(cellCount * 8); + // Collect unique triangle indices using visited bitset (O(n) dedup) bool multiCell = (cellMinX != cellMaxX || cellMinY != cellMaxY); if (multiCell && !triVisited.empty()) { @@ -2776,6 +2782,10 @@ void WMORenderer::GroupResources::getFloorTrianglesInRange( if (cellMinX > cellMaxX || cellMinY > cellMaxY) return; + const size_t cellCount = static_cast(cellMaxX - cellMinX + 1) * + static_cast(cellMaxY - cellMinY + 1); + out.reserve(cellCount * 8); + bool multiCell = (cellMinX != cellMaxX || cellMinY != cellMaxY); if (multiCell && !triVisited.empty()) { for (int cy = cellMinY; cy <= cellMaxY; ++cy) { @@ -2819,6 +2829,10 @@ void WMORenderer::GroupResources::getWallTrianglesInRange( if (cellMinX > cellMaxX || cellMinY > cellMaxY) return; + const size_t cellCount = static_cast(cellMaxX - cellMinX + 1) * + static_cast(cellMaxY - cellMinY + 1); + out.reserve(cellCount * 8); + bool multiCell = (cellMinX != cellMaxX || cellMinY != cellMaxY); if (multiCell && !triVisited.empty()) { for (int cy = cellMinY; cy <= cellMaxY; ++cy) { @@ -3112,8 +3126,9 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to, bool blocked = false; glm::vec3 moveDir = to - from; - float moveDist = glm::length(moveDir); - if (moveDist < 0.001f) return false; + float moveDistSq = glm::dot(moveDir, moveDir); + if (moveDistSq < 1e-6f) return false; + float moveDist = std::sqrt(moveDistSq); // Player collision parameters — WoW-style horizontal cylinder // Tighter radius when inside for more responsive indoor collision @@ -3246,10 +3261,10 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to, glm::vec3 safeLocal = hitPoint + normal * side * (PLAYER_RADIUS + 0.05f); glm::vec3 pushLocal(safeLocal.x - localTo.x, safeLocal.y - localTo.y, 0.0f); // Cap swept pushback so walls don't shove the player violently - float pushLen = glm::length(glm::vec2(pushLocal.x, pushLocal.y)); + float pushLenSq = pushLocal.x * pushLocal.x + pushLocal.y * pushLocal.y; const float MAX_SWEPT_PUSH = insideWMO ? 0.45f : 0.25f; - if (pushLen > MAX_SWEPT_PUSH) { - float scale = MAX_SWEPT_PUSH / pushLen; + if (pushLenSq > MAX_SWEPT_PUSH * MAX_SWEPT_PUSH) { + float scale = MAX_SWEPT_PUSH * glm::inversesqrt(pushLenSq); pushLocal.x *= scale; pushLocal.y *= scale; } @@ -3268,9 +3283,9 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to, // Horizontal cylinder collision: closest point + horizontal distance glm::vec3 closest = closestPointOnTriangle(localTo, v0, v1, v2); glm::vec3 delta = localTo - closest; - float horizDist = glm::length(glm::vec2(delta.x, delta.y)); + float horizDistSq = delta.x * delta.x + delta.y * delta.y; - if (horizDist <= PLAYER_RADIUS) { + if (horizDistSq <= PLAYER_RADIUS * PLAYER_RADIUS) { // Skip floor-like surfaces — grounding handles them, not wall collision. // Threshold matches MAX_WALK_SLOPE (cos 50° ≈ 0.6428): surfaces steeper // than 50° from horizontal must be tested as walls to prevent clip-through. @@ -3280,16 +3295,17 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to, const float SKIN = 0.005f; // small separation so we don't re-collide immediately // Push must cover full penetration to prevent gradual clip-through const float MAX_PUSH = PLAYER_RADIUS; + float horizDist = std::sqrt(horizDistSq); float penetration = (PLAYER_RADIUS - horizDist); float pushDist = glm::clamp(penetration + SKIN, 0.0f, MAX_PUSH); glm::vec2 pushDir2; - if (horizDist > 1e-4f) { - pushDir2 = glm::normalize(glm::vec2(delta.x, delta.y)); + if (horizDistSq > 1e-8f) { + pushDir2 = glm::vec2(delta.x, delta.y) * (1.0f / horizDist); } else { glm::vec2 n2(normal.x, normal.y); - float n2Len = glm::length(n2); - if (n2Len < 1e-4f) continue; - pushDir2 = n2 / n2Len; + float n2LenSq = glm::dot(n2, n2); + if (n2LenSq < 1e-8f) continue; + pushDir2 = n2 * glm::inversesqrt(n2LenSq); } glm::vec3 pushLocal(pushDir2.x * pushDist, pushDir2.y * pushDist, 0.0f); @@ -3524,8 +3540,12 @@ float WMORenderer::raycastBoundingBoxes(const glm::vec3& origin, const glm::vec3 } glm::vec3 center = (instance.worldBoundsMin + instance.worldBoundsMax) * 0.5f; - float radius = glm::length(instance.worldBoundsMax - center); - if (glm::length(center - origin) > (maxDistance + radius + 1.0f)) { + glm::vec3 halfExtent = instance.worldBoundsMax - center; + float radiusSq = glm::dot(halfExtent, halfExtent); + glm::vec3 toCenter = center - origin; + float distSq = glm::dot(toCenter, toCenter); + float maxR = maxDistance + std::sqrt(radiusSq) + 1.0f; + if (distSq > maxR * maxR) { continue; } diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index d24818d2..bb5692c4 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -2990,10 +2990,11 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) { if (leftClickWasPress_ && input.isMouseButtonJustReleased(SDL_BUTTON_LEFT)) { leftClickWasPress_ = false; glm::vec2 releasePos = input.getMousePosition(); - float dragDist = glm::length(releasePos - leftClickPressPos_); + glm::vec2 dragDelta = releasePos - leftClickPressPos_; + float dragDistSq = glm::dot(dragDelta, dragDelta); constexpr float CLICK_THRESHOLD = 5.0f; // pixels - if (dragDist < CLICK_THRESHOLD) { + if (dragDistSq < CLICK_THRESHOLD * CLICK_THRESHOLD) { auto* renderer = core::Application::getInstance().getRenderer(); auto* camera = renderer ? renderer->getCamera() : nullptr; auto* window = core::Application::getInstance().getWindow(); @@ -11557,9 +11558,10 @@ void GameScreen::renderNameplates(game::GameHandler& gameHandler) { renderPos.z += 2.3f; // Cull distance: target or other players up to 40 units; NPC others up to 20 units - float dist = glm::length(renderPos - camPos); + glm::vec3 nameDelta = renderPos - camPos; + float distSq = glm::dot(nameDelta, nameDelta); float cullDist = (isTarget || isPlayer) ? 40.0f : 20.0f; - if (dist > cullDist) continue; + if (distSq > cullDist * cullDist) continue; // Project to clip space glm::vec4 clipPos = viewProj * glm::vec4(renderPos, 1.0f); @@ -11576,7 +11578,9 @@ void GameScreen::renderNameplates(game::GameHandler& gameHandler) { float sy = (ndc.y * 0.5f + 0.5f) * screenH; // Fade out in the last 5 units of cull range - float alpha = dist < (cullDist - 5.0f) ? 1.0f : 1.0f - (dist - (cullDist - 5.0f)) / 5.0f; + float fadeSq = (cullDist - 5.0f) * (cullDist - 5.0f); + float dist = std::sqrt(distSq); + float alpha = distSq < fadeSq ? 1.0f : 1.0f - (dist - (cullDist - 5.0f)) / 5.0f; auto A = [&](int v) { return static_cast(v * alpha); }; // Bar colour by hostility (grey for corpses)