mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 17:43:52 +00:00
Harden transport updates and fix waterfall particle tint
This commit is contained in:
parent
ef6be2f186
commit
4a9c86b1e6
6 changed files with 137 additions and 100 deletions
|
|
@ -17,6 +17,7 @@
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
namespace wowee::game {
|
namespace wowee::game {
|
||||||
class TransportManager;
|
class TransportManager;
|
||||||
|
|
@ -516,10 +517,18 @@ public:
|
||||||
void setPlayerOnTransport(uint64_t transportGuid, const glm::vec3& localOffset) {
|
void setPlayerOnTransport(uint64_t transportGuid, const glm::vec3& localOffset) {
|
||||||
playerTransportGuid_ = transportGuid;
|
playerTransportGuid_ = transportGuid;
|
||||||
playerTransportOffset_ = localOffset;
|
playerTransportOffset_ = localOffset;
|
||||||
|
playerTransportStickyGuid_ = transportGuid;
|
||||||
|
playerTransportStickyTimer_ = 8.0f;
|
||||||
|
movementInfo.transportGuid = transportGuid;
|
||||||
}
|
}
|
||||||
void clearPlayerTransport() {
|
void clearPlayerTransport() {
|
||||||
|
if (playerTransportGuid_ != 0) {
|
||||||
|
playerTransportStickyGuid_ = playerTransportGuid_;
|
||||||
|
playerTransportStickyTimer_ = std::max(playerTransportStickyTimer_, 1.5f);
|
||||||
|
}
|
||||||
playerTransportGuid_ = 0;
|
playerTransportGuid_ = 0;
|
||||||
playerTransportOffset_ = glm::vec3(0.0f);
|
playerTransportOffset_ = glm::vec3(0.0f);
|
||||||
|
movementInfo.transportGuid = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cooldowns
|
// Cooldowns
|
||||||
|
|
@ -1032,6 +1041,8 @@ private:
|
||||||
std::unordered_set<uint64_t> serverUpdatedTransportGuids_;
|
std::unordered_set<uint64_t> serverUpdatedTransportGuids_;
|
||||||
uint64_t playerTransportGuid_ = 0; // Transport the player is riding (0 = none)
|
uint64_t playerTransportGuid_ = 0; // Transport the player is riding (0 = none)
|
||||||
glm::vec3 playerTransportOffset_ = glm::vec3(0.0f); // Player offset on transport
|
glm::vec3 playerTransportOffset_ = glm::vec3(0.0f); // Player offset on transport
|
||||||
|
uint64_t playerTransportStickyGuid_ = 0; // Last transport player was on (temporary retention)
|
||||||
|
float playerTransportStickyTimer_ = 0.0f; // Seconds to keep sticky transport alive after transient clears
|
||||||
std::unique_ptr<TransportManager> transportManager_; // Transport movement manager
|
std::unique_ptr<TransportManager> transportManager_; // Transport movement manager
|
||||||
std::vector<uint32_t> knownSpells;
|
std::vector<uint32_t> knownSpells;
|
||||||
std::unordered_map<uint32_t, float> spellCooldowns; // spellId -> remaining seconds
|
std::unordered_map<uint32_t, float> spellCooldowns; // spellId -> remaining seconds
|
||||||
|
|
|
||||||
|
|
@ -217,6 +217,13 @@ void GameHandler::update(float deltaTime) {
|
||||||
if (taxiStartGrace_ > 0.0f) {
|
if (taxiStartGrace_ > 0.0f) {
|
||||||
taxiStartGrace_ -= deltaTime;
|
taxiStartGrace_ -= deltaTime;
|
||||||
}
|
}
|
||||||
|
if (playerTransportStickyTimer_ > 0.0f) {
|
||||||
|
playerTransportStickyTimer_ -= deltaTime;
|
||||||
|
if (playerTransportStickyTimer_ <= 0.0f) {
|
||||||
|
playerTransportStickyTimer_ = 0.0f;
|
||||||
|
playerTransportStickyGuid_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Taxi logic timing
|
// Taxi logic timing
|
||||||
auto taxiStart = std::chrono::high_resolution_clock::now();
|
auto taxiStart = std::chrono::high_resolution_clock::now();
|
||||||
|
|
@ -1985,11 +1992,18 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
||||||
if (entityManager.hasEntity(guid)) {
|
if (entityManager.hasEntity(guid)) {
|
||||||
const bool isKnownTransport = transportGuids_.count(guid) > 0;
|
const bool isKnownTransport = transportGuids_.count(guid) > 0;
|
||||||
if (isKnownTransport) {
|
if (isKnownTransport) {
|
||||||
if (playerTransportGuid_ == guid) {
|
// Keep transports alive across out-of-range flapping.
|
||||||
LOG_INFO("Keeping transport in-range while player is aboard: 0x", std::hex, guid, std::dec);
|
// Boats/zeppelins are global movers and removing them here can make
|
||||||
continue;
|
// them disappear until a later movement snapshot happens to recreate them.
|
||||||
}
|
const bool playerAboardNow = (playerTransportGuid_ == guid);
|
||||||
LOG_INFO("Processing out-of-range removal for transport: 0x", std::hex, guid, std::dec);
|
const bool stickyAboard = (playerTransportStickyGuid_ == guid && playerTransportStickyTimer_ > 0.0f);
|
||||||
|
const bool movementSaysAboard = (movementInfo.transportGuid == guid);
|
||||||
|
LOG_INFO("Preserving transport on out-of-range: 0x",
|
||||||
|
std::hex, guid, std::dec,
|
||||||
|
" now=", playerAboardNow,
|
||||||
|
" sticky=", stickyAboard,
|
||||||
|
" movement=", movementSaysAboard);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_DEBUG("Entity went out of range: 0x", std::hex, guid, std::dec);
|
LOG_DEBUG("Entity went out of range: 0x", std::hex, guid, std::dec);
|
||||||
|
|
@ -2006,8 +2020,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
||||||
serverUpdatedTransportGuids_.erase(guid);
|
serverUpdatedTransportGuids_.erase(guid);
|
||||||
clearTransportAttachment(guid);
|
clearTransportAttachment(guid);
|
||||||
if (playerTransportGuid_ == guid) {
|
if (playerTransportGuid_ == guid) {
|
||||||
playerTransportGuid_ = 0;
|
clearPlayerTransport();
|
||||||
playerTransportOffset_ = glm::vec3(0.0f);
|
|
||||||
}
|
}
|
||||||
entityManager.removeEntity(guid);
|
entityManager.removeEntity(guid);
|
||||||
}
|
}
|
||||||
|
|
@ -2051,7 +2064,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
||||||
// Track player-on-transport state
|
// Track player-on-transport state
|
||||||
if (block.guid == playerGuid) {
|
if (block.guid == playerGuid) {
|
||||||
if (block.onTransport) {
|
if (block.onTransport) {
|
||||||
playerTransportGuid_ = block.transportGuid;
|
setPlayerOnTransport(block.transportGuid, glm::vec3(0.0f));
|
||||||
// Convert transport offset from server → canonical coordinates
|
// Convert transport offset from server → canonical coordinates
|
||||||
glm::vec3 serverOffset(block.transportX, block.transportY, block.transportZ);
|
glm::vec3 serverOffset(block.transportX, block.transportY, block.transportZ);
|
||||||
playerTransportOffset_ = core::coords::serverToCanonical(serverOffset);
|
playerTransportOffset_ = core::coords::serverToCanonical(serverOffset);
|
||||||
|
|
@ -2063,13 +2076,12 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
||||||
movementInfo.z = composed.z;
|
movementInfo.z = composed.z;
|
||||||
}
|
}
|
||||||
LOG_INFO("Player on transport: 0x", std::hex, playerTransportGuid_, std::dec,
|
LOG_INFO("Player on transport: 0x", std::hex, playerTransportGuid_, std::dec,
|
||||||
" offset=(", playerTransportOffset_.x, ", ", playerTransportOffset_.y, ", ", playerTransportOffset_.z, ")");
|
" offset=(", playerTransportOffset_.x, ", ", playerTransportOffset_.y, ", ", playerTransportOffset_.z, ")");
|
||||||
} else {
|
} else {
|
||||||
if (playerTransportGuid_ != 0) {
|
if (playerTransportGuid_ != 0) {
|
||||||
LOG_INFO("Player left transport");
|
LOG_INFO("Player left transport");
|
||||||
}
|
}
|
||||||
playerTransportGuid_ = 0;
|
clearPlayerTransport();
|
||||||
playerTransportOffset_ = glm::vec3(0.0f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2657,7 +2669,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
||||||
|
|
||||||
// Track player-on-transport state from MOVEMENT updates
|
// Track player-on-transport state from MOVEMENT updates
|
||||||
if (block.onTransport) {
|
if (block.onTransport) {
|
||||||
playerTransportGuid_ = block.transportGuid;
|
setPlayerOnTransport(block.transportGuid, glm::vec3(0.0f));
|
||||||
// Convert transport offset from server → canonical coordinates
|
// Convert transport offset from server → canonical coordinates
|
||||||
glm::vec3 serverOffset(block.transportX, block.transportY, block.transportZ);
|
glm::vec3 serverOffset(block.transportX, block.transportY, block.transportZ);
|
||||||
playerTransportOffset_ = core::coords::serverToCanonical(serverOffset);
|
playerTransportOffset_ = core::coords::serverToCanonical(serverOffset);
|
||||||
|
|
@ -2679,8 +2691,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
||||||
movementInfo.z = pos.z;
|
movementInfo.z = pos.z;
|
||||||
if (playerTransportGuid_ != 0) {
|
if (playerTransportGuid_ != 0) {
|
||||||
LOG_INFO("Player left transport (MOVEMENT)");
|
LOG_INFO("Player left transport (MOVEMENT)");
|
||||||
playerTransportGuid_ = 0;
|
clearPlayerTransport();
|
||||||
playerTransportOffset_ = glm::vec3(0.0f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2772,9 +2783,17 @@ void GameHandler::handleDestroyObject(network::Packet& packet) {
|
||||||
// Remove entity
|
// Remove entity
|
||||||
if (entityManager.hasEntity(data.guid)) {
|
if (entityManager.hasEntity(data.guid)) {
|
||||||
if (transportGuids_.count(data.guid) > 0) {
|
if (transportGuids_.count(data.guid) > 0) {
|
||||||
serverUpdatedTransportGuids_.erase(data.guid);
|
const bool playerAboardNow = (playerTransportGuid_ == data.guid);
|
||||||
LOG_INFO("Ignoring destroy for transport entity: 0x", std::hex, data.guid, std::dec);
|
const bool stickyAboard = (playerTransportStickyGuid_ == data.guid && playerTransportStickyTimer_ > 0.0f);
|
||||||
return;
|
const bool movementSaysAboard = (movementInfo.transportGuid == data.guid);
|
||||||
|
if (playerAboardNow || stickyAboard || movementSaysAboard) {
|
||||||
|
serverUpdatedTransportGuids_.erase(data.guid);
|
||||||
|
LOG_INFO("Preserving in-use transport on destroy: 0x", std::hex, data.guid, std::dec,
|
||||||
|
" now=", playerAboardNow,
|
||||||
|
" sticky=", stickyAboard,
|
||||||
|
" movement=", movementSaysAboard);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Mirror out-of-range handling: invoke render-layer despawn callbacks before entity removal.
|
// Mirror out-of-range handling: invoke render-layer despawn callbacks before entity removal.
|
||||||
auto entity = entityManager.getEntity(data.guid);
|
auto entity = entityManager.getEntity(data.guid);
|
||||||
|
|
@ -2785,6 +2804,13 @@ void GameHandler::handleDestroyObject(network::Packet& packet) {
|
||||||
gameObjectDespawnCallback_(data.guid);
|
gameObjectDespawnCallback_(data.guid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (transportGuids_.count(data.guid) > 0) {
|
||||||
|
transportGuids_.erase(data.guid);
|
||||||
|
serverUpdatedTransportGuids_.erase(data.guid);
|
||||||
|
if (playerTransportGuid_ == data.guid) {
|
||||||
|
clearPlayerTransport();
|
||||||
|
}
|
||||||
|
}
|
||||||
clearTransportAttachment(data.guid);
|
clearTransportAttachment(data.guid);
|
||||||
entityManager.removeEntity(data.guid);
|
entityManager.removeEntity(data.guid);
|
||||||
LOG_INFO("Destroyed entity: 0x", std::hex, data.guid, std::dec,
|
LOG_INFO("Destroyed entity: 0x", std::hex, data.guid, std::dec,
|
||||||
|
|
|
||||||
|
|
@ -57,22 +57,13 @@ void TransportManager::registerTransport(uint64_t guid, uint32_t wmoInstanceId,
|
||||||
transport.basePosition = spawnWorldPos - offset0; // Infer base from spawn
|
transport.basePosition = spawnWorldPos - offset0; // Infer base from spawn
|
||||||
transport.position = spawnWorldPos; // Start at spawn position (base + offset0)
|
transport.position = spawnWorldPos; // Start at spawn position (base + offset0)
|
||||||
|
|
||||||
// Sanity check: firstWaypoint should match spawnWorldPos
|
// 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;
|
glm::vec3 firstWaypoint = path.points[0].pos;
|
||||||
glm::vec3 waypointDiff = spawnWorldPos - firstWaypoint;
|
if (glm::length(firstWaypoint) > 10.0f) {
|
||||||
const float mismatchDist = glm::length(waypointDiff);
|
|
||||||
if (mismatchDist > 1.0f) {
|
|
||||||
LOG_WARNING("Transport 0x", std::hex, guid, std::dec, " path ", pathId,
|
LOG_WARNING("Transport 0x", std::hex, guid, std::dec, " path ", pathId,
|
||||||
": firstWaypoint mismatch! spawnPos=(", spawnWorldPos.x, ",", spawnWorldPos.y, ",", spawnWorldPos.z, ")",
|
": first local waypoint far from origin: (",
|
||||||
" firstWaypoint=(", firstWaypoint.x, ",", firstWaypoint.y, ",", firstWaypoint.z, ")",
|
firstWaypoint.x, ",", firstWaypoint.y, ",", firstWaypoint.z, ")");
|
||||||
" diff=(", waypointDiff.x, ",", waypointDiff.y, ",", waypointDiff.z, ")");
|
|
||||||
}
|
|
||||||
const bool firstWaypointIsOrigin = glm::dot(firstWaypoint, firstWaypoint) < 0.0001f;
|
|
||||||
if (mismatchDist > 500.0f || (firstWaypointIsOrigin && mismatchDist > 50.0f)) {
|
|
||||||
transport.allowBootstrapVelocity = false;
|
|
||||||
LOG_WARNING("Transport 0x", std::hex, guid, std::dec,
|
|
||||||
" disabling DBC bootstrap velocity due to large spawn/path mismatch (dist=",
|
|
||||||
mismatchDist, ", firstIsOrigin=", firstWaypointIsOrigin, ")");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -576,14 +567,17 @@ void TransportManager::updateServerTransport(uint64_t guid, const glm::vec3& pos
|
||||||
if (dt > 0.001f) {
|
if (dt > 0.001f) {
|
||||||
glm::vec3 v = (position - prevPos) / dt;
|
glm::vec3 v = (position - prevPos) / dt;
|
||||||
const float speed = glm::length(v);
|
const float speed = glm::length(v);
|
||||||
|
constexpr float kMinAuthoritativeSpeed = 0.15f;
|
||||||
constexpr float kMaxSpeed = 60.0f;
|
constexpr float kMaxSpeed = 60.0f;
|
||||||
if (speed > kMaxSpeed) {
|
if (speed >= kMinAuthoritativeSpeed) {
|
||||||
v *= (kMaxSpeed / speed);
|
if (speed > kMaxSpeed) {
|
||||||
}
|
v *= (kMaxSpeed / speed);
|
||||||
|
}
|
||||||
|
|
||||||
transport->serverLinearVelocity = v;
|
transport->serverLinearVelocity = v;
|
||||||
transport->serverAngularVelocity = 0.0f;
|
transport->serverAngularVelocity = 0.0f;
|
||||||
transport->hasServerVelocity = true;
|
transport->hasServerVelocity = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Bootstrap velocity from mapped DBC path on first authoritative sample.
|
// Bootstrap velocity from mapped DBC path on first authoritative sample.
|
||||||
|
|
@ -611,55 +605,41 @@ void TransportManager::updateServerTransport(uint64_t guid, const glm::vec3& pos
|
||||||
std::sqrt(bestDistSq), ", path=", transport->pathId, ")");
|
std::sqrt(bestDistSq), ", path=", transport->pathId, ")");
|
||||||
} else {
|
} else {
|
||||||
size_t n = path.points.size();
|
size_t n = path.points.size();
|
||||||
size_t nextIdx = (bestIdx + 1) % n;
|
constexpr float kMinBootstrapSpeed = 0.25f;
|
||||||
uint32_t t0 = path.points[bestIdx].tMs;
|
constexpr float kMaxSpeed = 60.0f;
|
||||||
uint32_t t1 = path.points[nextIdx].tMs;
|
|
||||||
if (nextIdx == 0 && t1 <= t0 && path.durationMs > 0) {
|
auto tryApplySegment = [&](size_t a, size_t b) {
|
||||||
t1 = path.durationMs;
|
uint32_t t0 = path.points[a].tMs;
|
||||||
|
uint32_t t1 = path.points[b].tMs;
|
||||||
|
if (b == 0 && t1 <= t0 && path.durationMs > 0) {
|
||||||
|
t1 = path.durationMs;
|
||||||
|
}
|
||||||
|
if (t1 <= t0) return;
|
||||||
|
glm::vec3 seg = path.points[b].pos - path.points[a].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);
|
||||||
|
}
|
||||||
|
transport->serverLinearVelocity = v;
|
||||||
|
transport->serverAngularVelocity = 0.0f;
|
||||||
|
transport->hasServerVelocity = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prefer nearest forward meaningful segment from bestIdx.
|
||||||
|
for (size_t step = 1; step < n && !transport->hasServerVelocity; ++step) {
|
||||||
|
size_t a = (bestIdx + step - 1) % n;
|
||||||
|
size_t b = (bestIdx + step) % n;
|
||||||
|
tryApplySegment(a, b);
|
||||||
}
|
}
|
||||||
if (t1 <= t0 && n > 2) {
|
// Fallback: nearest backward meaningful segment.
|
||||||
size_t prevIdx = (bestIdx + n - 1) % n;
|
for (size_t step = 1; step < n && !transport->hasServerVelocity; ++step) {
|
||||||
t0 = path.points[prevIdx].tMs;
|
size_t b = (bestIdx + n - step + 1) % n;
|
||||||
t1 = path.points[bestIdx].tMs;
|
size_t a = (bestIdx + n - step) % n;
|
||||||
glm::vec3 seg = path.points[bestIdx].pos - path.points[prevIdx].pos;
|
tryApplySegment(a, b);
|
||||||
float dtSeg = static_cast<float>(t1 - t0) / 1000.0f;
|
|
||||||
if (dtSeg > 0.001f) {
|
|
||||||
glm::vec3 v = seg / dtSeg;
|
|
||||||
float speed = glm::length(v);
|
|
||||||
constexpr float kMinBootstrapSpeed = 0.25f;
|
|
||||||
constexpr float kMaxSpeed = 60.0f;
|
|
||||||
if (speed < kMinBootstrapSpeed) {
|
|
||||||
speed = 0.0f;
|
|
||||||
}
|
|
||||||
if (speed > kMaxSpeed) {
|
|
||||||
v *= (kMaxSpeed / speed);
|
|
||||||
}
|
|
||||||
if (speed >= kMinBootstrapSpeed) {
|
|
||||||
transport->serverLinearVelocity = v;
|
|
||||||
transport->serverAngularVelocity = 0.0f;
|
|
||||||
transport->hasServerVelocity = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
glm::vec3 seg = path.points[nextIdx].pos - path.points[bestIdx].pos;
|
|
||||||
float dtSeg = static_cast<float>(t1 - t0) / 1000.0f;
|
|
||||||
if (dtSeg > 0.001f) {
|
|
||||||
glm::vec3 v = seg / dtSeg;
|
|
||||||
float speed = glm::length(v);
|
|
||||||
constexpr float kMinBootstrapSpeed = 0.25f;
|
|
||||||
constexpr float kMaxSpeed = 60.0f;
|
|
||||||
if (speed < kMinBootstrapSpeed) {
|
|
||||||
speed = 0.0f;
|
|
||||||
}
|
|
||||||
if (speed > kMaxSpeed) {
|
|
||||||
v *= (kMaxSpeed / speed);
|
|
||||||
}
|
|
||||||
if (speed >= kMinBootstrapSpeed) {
|
|
||||||
transport->serverLinearVelocity = v;
|
|
||||||
transport->serverAngularVelocity = 0.0f;
|
|
||||||
transport->hasServerVelocity = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transport->hasServerVelocity) {
|
if (transport->hasServerVelocity) {
|
||||||
|
|
|
||||||
|
|
@ -408,13 +408,15 @@ void parseFBlock(const std::vector<uint8_t>& data, uint32_t offset,
|
||||||
uint32_t ofsKeys = disk.ofsKeys;
|
uint32_t ofsKeys = disk.ofsKeys;
|
||||||
|
|
||||||
if (valueType == 0) {
|
if (valueType == 0) {
|
||||||
// Color: 3 bytes per key {r, g, b}
|
// Color: 3 bytes per key.
|
||||||
|
// WotLK particle FBlock color keys are stored as BGR in practice for many assets
|
||||||
|
// (notably water/waterfall emitters). Decode to RGB explicitly.
|
||||||
if (ofsKeys + nKeys * 3 > data.size()) return;
|
if (ofsKeys + nKeys * 3 > data.size()) return;
|
||||||
fb.vec3Values.reserve(nKeys);
|
fb.vec3Values.reserve(nKeys);
|
||||||
for (uint32_t i = 0; i < nKeys; i++) {
|
for (uint32_t i = 0; i < nKeys; i++) {
|
||||||
uint8_t r = data[ofsKeys + i * 3 + 0];
|
uint8_t b = data[ofsKeys + i * 3 + 0];
|
||||||
uint8_t g = data[ofsKeys + i * 3 + 1];
|
uint8_t g = data[ofsKeys + i * 3 + 1];
|
||||||
uint8_t b = data[ofsKeys + i * 3 + 2];
|
uint8_t r = data[ofsKeys + i * 3 + 2];
|
||||||
fb.vec3Values.emplace_back(r / 255.0f, g / 255.0f, b / 255.0f);
|
fb.vec3Values.emplace_back(r / 255.0f, g / 255.0f, b / 255.0f);
|
||||||
}
|
}
|
||||||
} else if (valueType == 1) {
|
} else if (valueType == 1) {
|
||||||
|
|
|
||||||
|
|
@ -1548,25 +1548,23 @@ void M2Renderer::update(float deltaTime, const glm::vec3& cameraPos, const glm::
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Frustum + distance cull: skip expensive bone computation for off-screen instances
|
// Frustum + distance cull: skip expensive bone computation for off-screen instances.
|
||||||
// Aggressive culling for performance (double frame rate target)
|
// Keep thresholds aligned with render culling so visible distant ambient actors
|
||||||
|
// (fish/seagulls/etc.) continue animating instead of freezing in idle poses.
|
||||||
float worldRadius = model.boundRadius * instance.scale;
|
float worldRadius = model.boundRadius * instance.scale;
|
||||||
float cullRadius = worldRadius;
|
float cullRadius = worldRadius;
|
||||||
|
if (model.disableAnimation) {
|
||||||
|
cullRadius = std::max(cullRadius, 3.0f);
|
||||||
|
}
|
||||||
glm::vec3 toCam = instance.position - cachedCamPos_;
|
glm::vec3 toCam = instance.position - cachedCamPos_;
|
||||||
float distSq = glm::dot(toCam, toCam);
|
float distSq = glm::dot(toCam, toCam);
|
||||||
float effectiveMaxDistSq = cachedMaxRenderDistSq_ * std::max(1.0f, cullRadius / 12.0f);
|
float effectiveMaxDistSq = cachedMaxRenderDistSq_ * std::max(1.0f, cullRadius / 12.0f);
|
||||||
if (!model.disableAnimation) {
|
if (model.disableAnimation) {
|
||||||
// Ultra-aggressive animation culling for 60fps target
|
effectiveMaxDistSq *= 2.6f;
|
||||||
if (worldRadius < 0.8f) {
|
|
||||||
effectiveMaxDistSq = std::min(effectiveMaxDistSq, 25.0f * 25.0f); // Ultra tight for small
|
|
||||||
} else if (worldRadius < 1.5f) {
|
|
||||||
effectiveMaxDistSq = std::min(effectiveMaxDistSq, 50.0f * 50.0f); // Very tight for medium
|
|
||||||
} else if (worldRadius < 3.0f) {
|
|
||||||
effectiveMaxDistSq = std::min(effectiveMaxDistSq, 80.0f * 80.0f); // Tight for large
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (distSq > effectiveMaxDistSq) continue;
|
if (distSq > effectiveMaxDistSq) continue;
|
||||||
if (cullRadius > 0.0f && !updateFrustum.intersectsSphere(instance.position, cullRadius)) continue;
|
float paddedRadius = std::max(cullRadius * 1.5f, cullRadius + 3.0f);
|
||||||
|
if (cullRadius > 0.0f && !updateFrustum.intersectsSphere(instance.position, paddedRadius)) continue;
|
||||||
|
|
||||||
boneWorkIndices_.push_back(idx);
|
boneWorkIndices_.push_back(idx);
|
||||||
}
|
}
|
||||||
|
|
@ -2334,6 +2332,15 @@ void M2Renderer::renderM2Particles(const glm::mat4& view, const glm::mat4& proj)
|
||||||
float alpha = interpFBlockFloat(em.particleAlpha, lifeRatio);
|
float alpha = interpFBlockFloat(em.particleAlpha, lifeRatio);
|
||||||
float scale = interpFBlockFloat(em.particleScale, lifeRatio);
|
float scale = interpFBlockFloat(em.particleScale, lifeRatio);
|
||||||
|
|
||||||
|
// Some waterfall/spray emitters become overly dark after channel-correct decoding.
|
||||||
|
// Apply a small correction only for strongly blue-dominant particle colors.
|
||||||
|
if (color.b > color.r * 1.4f && color.b > color.g * 1.15f) {
|
||||||
|
float luma = color.r * 0.2126f + color.g * 0.7152f + color.b * 0.0722f;
|
||||||
|
color = glm::mix(color, glm::vec3(luma), 0.35f);
|
||||||
|
color *= 1.35f;
|
||||||
|
color = glm::clamp(color, glm::vec3(0.28f, 0.35f, 0.45f), glm::vec3(1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
GLuint tex = whiteTexture;
|
GLuint tex = whiteTexture;
|
||||||
if (p.emitterIndex < static_cast<int>(gpu.particleTextures.size())) {
|
if (p.emitterIndex < static_cast<int>(gpu.particleTextures.size())) {
|
||||||
tex = gpu.particleTextures[p.emitterIndex];
|
tex = gpu.particleTextures[p.emitterIndex];
|
||||||
|
|
|
||||||
|
|
@ -506,6 +506,17 @@ void WaterRenderer::render(const Camera& camera, float time) {
|
||||||
glm::vec4 color = getLiquidColor(surface.liquidType);
|
glm::vec4 color = getLiquidColor(surface.liquidType);
|
||||||
float alpha = getLiquidAlpha(surface.liquidType);
|
float alpha = getLiquidAlpha(surface.liquidType);
|
||||||
|
|
||||||
|
// WMO liquid material IDs are not always 1:1 with terrain LiquidType.dbc semantics.
|
||||||
|
// Avoid accidental magma/slime tint (red/green waterfalls) by forcing WMO liquids
|
||||||
|
// to water-like shading unless they're explicitly ocean.
|
||||||
|
if (surface.wmoId != 0) {
|
||||||
|
const uint8_t basicType = (surface.liquidType == 0) ? 0 : ((surface.liquidType - 1) % 4);
|
||||||
|
if (basicType == 2 || basicType == 3) {
|
||||||
|
color = glm::vec4(0.2f, 0.4f, 0.6f, 1.0f);
|
||||||
|
alpha = 0.45f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// City/canal liquid profile: clearer water + stronger ripples/sun shimmer.
|
// City/canal liquid profile: clearer water + stronger ripples/sun shimmer.
|
||||||
// Stormwind canals typically use LiquidType 5 in this data set.
|
// Stormwind canals typically use LiquidType 5 in this data set.
|
||||||
bool canalProfile = (surface.wmoId != 0) || (surface.liquidType == 5);
|
bool canalProfile = (surface.wmoId != 0) || (surface.liquidType == 5);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue