mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 01:23:51 +00:00
perf: eliminate ~70 unnecessary sqrt ops per frame, optimize caches and threading
Squared distance optimizations across 30 files: - Convert glm::length() comparisons to glm::dot() (no sqrt) - Use glm::inversesqrt() for check-then-normalize patterns (1 rsqrt vs 2 sqrt) - Defer sqrt to after early-out checks in collision/movement code - Hottest paths: camera_controller (21), weather particles, WMO collision, transport movement, creature interpolation, nameplate culling Container and algorithm improvements: - std::map<string> → std::unordered_map for asset/DBC/MPQ/warden caches - std::mutex → std::shared_mutex for asset_manager and mpq_manager caches - std::sort → std::partial_sort in lighting_manager (top-2 of N volumes) - Double-lookup find()+operator[] → insert_or_assign in game_handler - Add reserve() for per-frame vectors: weather, swim_effects, WMO/M2 collision Threading and synchronization: - Replace 1ms busy-wait polling with condition_variable in character_renderer - Move timestamp capture before mutex in logger - Use memory_order_acquire/release for normal map completion signaling API additions: - DBC getStringView()/getStringViewByOffset() for zero-copy string access - Parse creature display IDs from SMSG_CREATURE_QUERY_SINGLE_RESPONSE
This commit is contained in:
parent
cf0e2aa240
commit
b0466e9029
29 changed files with 328 additions and 196 deletions
|
|
@ -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<game::Unit>(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<game::Unit>(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];
|
||||
|
|
|
|||
|
|
@ -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<std::mutex> lock(mutex);
|
||||
ensureFile();
|
||||
|
||||
auto nowSteady = std::chrono::steady_clock::now();
|
||||
if (dedupeEnabled_ && !lastMessage_.empty() &&
|
||||
level == lastLevel_ && message == lastMessage_) {
|
||||
auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(nowSteady - lastMessageTime_).count();
|
||||
|
|
|
|||
|
|
@ -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<uint32_t>& 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<uint32_t>& 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
|
||||
|
|
|
|||
|
|
@ -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<float>(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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -396,14 +396,15 @@ std::vector<uint8_t> 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<std::mutex> cacheLock(cacheMutex);
|
||||
std::shared_lock<std::shared_mutex> 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<uint8_t> 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<std::mutex> cacheLock(cacheMutex);
|
||||
std::lock_guard<std::shared_mutex> cacheLock(cacheMutex);
|
||||
// Evict old entries if needed (LRU)
|
||||
while (fileCacheTotalBytes + fileSize > fileCacheBudget && !fileCache.empty()) {
|
||||
auto lru = fileCache.begin();
|
||||
|
|
@ -456,13 +457,13 @@ std::vector<uint8_t> AssetManager::readFileOptional(const std::string& path) con
|
|||
}
|
||||
|
||||
void AssetManager::clearDBCCache() {
|
||||
std::lock_guard<std::mutex> lock(cacheMutex);
|
||||
std::lock_guard<std::shared_mutex> lock(cacheMutex);
|
||||
dbcCache.clear();
|
||||
LOG_INFO("Cleared DBC cache");
|
||||
}
|
||||
|
||||
void AssetManager::clearCache() {
|
||||
std::lock_guard<std::mutex> lock(cacheMutex);
|
||||
std::lock_guard<std::shared_mutex> lock(cacheMutex);
|
||||
dbcCache.clear();
|
||||
fileCache.clear();
|
||||
fileCacheTotalBytes = 0;
|
||||
|
|
|
|||
|
|
@ -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<const char*>(stringBlock.data() + offset);
|
||||
const char* end = reinterpret_cast<const char*>(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 {
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ void MPQManager::shutdown() {
|
|||
archives.clear();
|
||||
archiveNames.clear();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(fileArchiveCacheMutex_);
|
||||
std::lock_guard<std::shared_mutex> 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<std::mutex> lock(fileArchiveCacheMutex_);
|
||||
std::lock_guard<std::shared_mutex> 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<std::mutex> lock(fileArchiveCacheMutex_);
|
||||
std::shared_lock<std::shared_mutex> 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<std::mutex> lock(fileArchiveCacheMutex_);
|
||||
std::lock_guard<std::shared_mutex> 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(),
|
||||
|
|
|
|||
|
|
@ -111,9 +111,11 @@ std::optional<float> 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<float> 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<float> 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<int>(std::ceil(swimMoveDist / swimStepSize))));
|
||||
glm::vec3 stepDelta = (swimTo - swimFrom) / static_cast<float>(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<int>(std::ceil(moveDist / stepSize))));
|
||||
|
|
@ -909,9 +921,11 @@ void CameraController::update(float deltaTime) {
|
|||
std::optional<float> 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<float> 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<int>(std::ceil(moveDist / stepSize))));
|
||||
glm::vec3 stepPos = startFeet;
|
||||
|
|
|
|||
|
|
@ -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<std::mutex> 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<std::mutex> 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<std::mutex> 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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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::WeightedVolume> 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::WeightedVolume> 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::WeightedVolume> 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
|
||||
|
|
|
|||
|
|
@ -947,6 +947,9 @@ void M2ModelGPU::CollisionMesh::getFloorTrisInRange(
|
|||
int cxMax = std::clamp(static_cast<int>((maxX - gridOrigin.x) / CELL_SIZE), 0, gridCellsX - 1);
|
||||
int cyMin = std::clamp(static_cast<int>((minY - gridOrigin.y) / CELL_SIZE), 0, gridCellsY - 1);
|
||||
int cyMax = std::clamp(static_cast<int>((maxY - gridOrigin.y) / CELL_SIZE), 0, gridCellsY - 1);
|
||||
const size_t cellCount = static_cast<size_t>(cxMax - cxMin + 1) *
|
||||
static_cast<size_t>(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<int>((maxX - gridOrigin.x) / CELL_SIZE), 0, gridCellsX - 1);
|
||||
int cyMin = std::clamp(static_cast<int>((minY - gridOrigin.y) / CELL_SIZE), 0, gridCellsY - 1);
|
||||
int cyMax = std::clamp(static_cast<int>((maxY - gridOrigin.y) / CELL_SIZE), 0, gridCellsY - 1);
|
||||
const size_t cellCount = static_cast<size_t>(cxMax - cxMin + 1) *
|
||||
static_cast<size_t>(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)));
|
||||
|
|
|
|||
|
|
@ -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<float>(now - lastUpdateTime).count();
|
||||
needsRefresh = (moved >= updateDistance) || (elapsed >= updateIntervalSec);
|
||||
needsRefresh = (movedSq >= updateDistance * updateDistance) || (elapsed >= updateIntervalSec);
|
||||
}
|
||||
|
||||
// Also refresh if player crossed a tile boundary
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<float>(surface.width);
|
||||
float spanY = stepYLen * static_cast<float>(surface.height);
|
||||
if (stepXLen < 0.2f || stepXLen > 12.0f ||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -2101,6 +2101,7 @@ void WMORenderer::getVisibleGroupsViaPortals(const ModelData& model,
|
|||
// BFS through portals from camera's group
|
||||
std::vector<bool> visited(model.groups.size(), false);
|
||||
std::vector<uint32_t> queue;
|
||||
queue.reserve(model.groups.size());
|
||||
queue.push_back(static_cast<uint32_t>(cameraGroup));
|
||||
visited[cameraGroup] = true;
|
||||
outVisibleGroups.insert(static_cast<uint32_t>(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<size_t>(cellMaxX - cellMinX + 1) *
|
||||
static_cast<size_t>(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<size_t>(cellMaxX - cellMinX + 1) *
|
||||
static_cast<size_t>(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<size_t>(cellMaxX - cellMinX + 1) *
|
||||
static_cast<size_t>(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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<int>(v * alpha); };
|
||||
|
||||
// Bar colour by hostility (grey for corpses)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue