mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 17:43:52 +00:00
Fix taxi mount orientation and eliminate tile loading hitches
Fixes two critical taxi flight issues: 1. Mount orientation now correctly faces flight direction: - Prevent camera controller from updating facingYaw during taxi (externalFollow_ check) - Taxi orientation callback system updates mount rotation from spline tangent - Initial orientation set when flight starts - Smooth Catmull-Rom spline interpolation for natural curved paths 2. Eliminate frame hitches from tile loading during flight: - New taxiFlightStartCallback uploads ALL precached tiles to GPU before flight begins - Previously tiles loaded async during 3s mount delay but uploaded 1/frame during flight - Now processAllReadyTiles() blocks briefly after mount delay to batch upload everything - Combined with 2.0s terrain update interval and aggressive culling for smooth flight Additional optimizations: - Aggressive taxi culling: skip models <15 units, all foliage/trees, underwater objects - Max render distance reduced to 150 units during taxi - Movement heartbeat packets disabled during taxi (server controls position) - Reduced taxi speed from 32 to 18 units/sec to prevent streaming overload
This commit is contained in:
parent
536b3cea48
commit
2e0a7e0039
7 changed files with 119 additions and 21 deletions
|
|
@ -501,6 +501,14 @@ public:
|
||||||
using TaxiPrecacheCallback = std::function<void(const std::vector<glm::vec3>&)>;
|
using TaxiPrecacheCallback = std::function<void(const std::vector<glm::vec3>&)>;
|
||||||
void setTaxiPrecacheCallback(TaxiPrecacheCallback cb) { taxiPrecacheCallback_ = std::move(cb); }
|
void setTaxiPrecacheCallback(TaxiPrecacheCallback cb) { taxiPrecacheCallback_ = std::move(cb); }
|
||||||
|
|
||||||
|
// Taxi orientation callback (for mount rotation)
|
||||||
|
using TaxiOrientationCallback = std::function<void(float orientationRadians)>;
|
||||||
|
void setTaxiOrientationCallback(TaxiOrientationCallback cb) { taxiOrientationCallback_ = std::move(cb); }
|
||||||
|
|
||||||
|
// Callback for when taxi flight is about to start (after mounting delay, before movement begins)
|
||||||
|
using TaxiFlightStartCallback = std::function<void()>;
|
||||||
|
void setTaxiFlightStartCallback(TaxiFlightStartCallback cb) { taxiFlightStartCallback_ = std::move(cb); }
|
||||||
|
|
||||||
bool isMounted() const { return currentMountDisplayId_ != 0; }
|
bool isMounted() const { return currentMountDisplayId_ != 0; }
|
||||||
bool isHostileAttacker(uint64_t guid) const { return hostileAttackers_.count(guid) > 0; }
|
bool isHostileAttacker(uint64_t guid) const { return hostileAttackers_.count(guid) > 0; }
|
||||||
float getServerRunSpeed() const { return serverRunSpeed_; }
|
float getServerRunSpeed() const { return serverRunSpeed_; }
|
||||||
|
|
@ -959,7 +967,7 @@ private:
|
||||||
bool taxiClientActive_ = false;
|
bool taxiClientActive_ = false;
|
||||||
size_t taxiClientIndex_ = 0;
|
size_t taxiClientIndex_ = 0;
|
||||||
std::vector<glm::vec3> taxiClientPath_;
|
std::vector<glm::vec3> taxiClientPath_;
|
||||||
float taxiClientSpeed_ = 32.0f;
|
float taxiClientSpeed_ = 18.0f; // Reduced from 32 to prevent loading hitches
|
||||||
float taxiClientSegmentProgress_ = 0.0f;
|
float taxiClientSegmentProgress_ = 0.0f;
|
||||||
bool taxiMountingDelay_ = false; // Delay before flight starts (terrain precache time)
|
bool taxiMountingDelay_ = false; // Delay before flight starts (terrain precache time)
|
||||||
float taxiMountingTimer_ = 0.0f;
|
float taxiMountingTimer_ = 0.0f;
|
||||||
|
|
@ -1023,6 +1031,8 @@ private:
|
||||||
NpcSwingCallback npcSwingCallback_;
|
NpcSwingCallback npcSwingCallback_;
|
||||||
MountCallback mountCallback_;
|
MountCallback mountCallback_;
|
||||||
TaxiPrecacheCallback taxiPrecacheCallback_;
|
TaxiPrecacheCallback taxiPrecacheCallback_;
|
||||||
|
TaxiOrientationCallback taxiOrientationCallback_;
|
||||||
|
TaxiFlightStartCallback taxiFlightStartCallback_;
|
||||||
uint32_t currentMountDisplayId_ = 0;
|
uint32_t currentMountDisplayId_ = 0;
|
||||||
float serverRunSpeed_ = 7.0f;
|
float serverRunSpeed_ = 7.0f;
|
||||||
bool playerDead_ = false;
|
bool playerDead_ = false;
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@ public:
|
||||||
void setMountHeightOffset(float offset) { mountHeightOffset_ = offset; }
|
void setMountHeightOffset(float offset) { mountHeightOffset_ = offset; }
|
||||||
void setExternalFollow(bool enabled) { externalFollow_ = enabled; }
|
void setExternalFollow(bool enabled) { externalFollow_ = enabled; }
|
||||||
void setExternalMoving(bool moving) { externalMoving_ = moving; }
|
void setExternalMoving(bool moving) { externalMoving_ = moving; }
|
||||||
|
void setFacingYaw(float yaw) { facingYaw = yaw; } // For taxi/scripted movement
|
||||||
void clearMovementInputs();
|
void clearMovementInputs();
|
||||||
|
|
||||||
// For first-person player hiding
|
// For first-person player hiding
|
||||||
|
|
|
||||||
|
|
@ -420,10 +420,9 @@ void Application::update(float deltaTime) {
|
||||||
}
|
}
|
||||||
if (renderer && renderer->getTerrainManager()) {
|
if (renderer && renderer->getTerrainManager()) {
|
||||||
renderer->getTerrainManager()->setStreamingEnabled(true);
|
renderer->getTerrainManager()->setStreamingEnabled(true);
|
||||||
// With 8GB tile cache, keep streaming active during taxi at moderate rate.
|
// With 8GB tile cache and precaching, minimize streaming during taxi
|
||||||
// Increase load radius to pre-cache tiles ahead of flight path.
|
|
||||||
if (onTaxi) {
|
if (onTaxi) {
|
||||||
renderer->getTerrainManager()->setUpdateInterval(0.3f);
|
renderer->getTerrainManager()->setUpdateInterval(2.0f); // Very infrequent updates - already precached
|
||||||
renderer->getTerrainManager()->setLoadRadius(2); // 5x5 grid for taxi (each tile ~533 yards)
|
renderer->getTerrainManager()->setLoadRadius(2); // 5x5 grid for taxi (each tile ~533 yards)
|
||||||
} else {
|
} else {
|
||||||
// Ramp streaming back in after taxi to avoid end-of-flight hitches.
|
// Ramp streaming back in after taxi to avoid end-of-flight hitches.
|
||||||
|
|
@ -466,7 +465,8 @@ void Application::update(float deltaTime) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send movement heartbeat every 500ms (keeps server position in sync)
|
// Send movement heartbeat every 500ms (keeps server position in sync)
|
||||||
if (gameHandler && renderer) {
|
// Skip during taxi flights - server controls position
|
||||||
|
if (gameHandler && renderer && !onTaxi) {
|
||||||
movementHeartbeatTimer += deltaTime;
|
movementHeartbeatTimer += deltaTime;
|
||||||
if (movementHeartbeatTimer >= 0.5f) {
|
if (movementHeartbeatTimer >= 0.5f) {
|
||||||
movementHeartbeatTimer = 0.0f;
|
movementHeartbeatTimer = 0.0f;
|
||||||
|
|
@ -718,6 +718,23 @@ void Application::setupUICallbacks() {
|
||||||
renderer->getTerrainManager()->precacheTiles(tilesToLoad);
|
renderer->getTerrainManager()->precacheTiles(tilesToLoad);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Taxi orientation callback - update mount rotation during flight
|
||||||
|
gameHandler->setTaxiOrientationCallback([this](float orientationRadians) {
|
||||||
|
if (renderer && renderer->getCameraController()) {
|
||||||
|
// Convert radians to degrees for camera controller
|
||||||
|
float yawDegrees = glm::degrees(orientationRadians);
|
||||||
|
renderer->getCameraController()->setFacingYaw(yawDegrees);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Taxi flight start callback - upload all precached tiles to GPU before flight begins
|
||||||
|
gameHandler->setTaxiFlightStartCallback([this]() {
|
||||||
|
if (renderer && renderer->getTerrainManager()) {
|
||||||
|
LOG_INFO("Uploading all precached tiles to GPU before taxi flight...");
|
||||||
|
renderer->getTerrainManager()->processAllReadyTiles();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Creature move callback (online mode) - update creature positions
|
// Creature move callback (online mode) - update creature positions
|
||||||
gameHandler->setCreatureMoveCallback([this](uint64_t guid, float x, float y, float z, uint32_t durationMs) {
|
gameHandler->setCreatureMoveCallback([this](uint64_t guid, float x, float y, float z, uint32_t durationMs) {
|
||||||
auto it = creatureInstances_.find(guid);
|
auto it = creatureInstances_.find(guid);
|
||||||
|
|
|
||||||
|
|
@ -269,6 +269,10 @@ void GameHandler::update(float deltaTime) {
|
||||||
if (taxiMountingTimer_ >= 3.0f) {
|
if (taxiMountingTimer_ >= 3.0f) {
|
||||||
taxiMountingDelay_ = false;
|
taxiMountingDelay_ = false;
|
||||||
taxiMountingTimer_ = 0.0f;
|
taxiMountingTimer_ = 0.0f;
|
||||||
|
// Upload all precached tiles to GPU before flight starts
|
||||||
|
if (taxiFlightStartCallback_) {
|
||||||
|
taxiFlightStartCallback_();
|
||||||
|
}
|
||||||
if (!taxiPendingPath_.empty()) {
|
if (!taxiPendingPath_.empty()) {
|
||||||
startClientTaxiPath(taxiPendingPath_);
|
startClientTaxiPath(taxiPendingPath_);
|
||||||
taxiPendingPath_.clear();
|
taxiPendingPath_.clear();
|
||||||
|
|
@ -5182,6 +5186,24 @@ void GameHandler::startClientTaxiPath(const std::vector<uint32_t>& pathNodes) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set initial orientation to face the first flight segment
|
||||||
|
if (!entityManager.hasEntity(playerGuid)) return;
|
||||||
|
auto playerEntity = entityManager.getEntity(playerGuid);
|
||||||
|
if (playerEntity) {
|
||||||
|
glm::vec3 start = taxiClientPath_[0];
|
||||||
|
glm::vec3 end = taxiClientPath_[1];
|
||||||
|
glm::vec3 dir = end - start;
|
||||||
|
float initialOrientation = std::atan2(dir.y, dir.x) - 1.57079632679f;
|
||||||
|
|
||||||
|
playerEntity->setPosition(start.x, start.y, start.z, initialOrientation);
|
||||||
|
movementInfo.orientation = initialOrientation;
|
||||||
|
|
||||||
|
// Update mount rotation immediately
|
||||||
|
if (taxiOrientationCallback_) {
|
||||||
|
taxiOrientationCallback_(initialOrientation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LOG_INFO("Taxi flight started with ", taxiClientPath_.size(), " spline waypoints");
|
LOG_INFO("Taxi flight started with ", taxiClientPath_.size(), " spline waypoints");
|
||||||
taxiClientActive_ = true;
|
taxiClientActive_ = true;
|
||||||
}
|
}
|
||||||
|
|
@ -5234,20 +5256,52 @@ void GameHandler::updateClientTaxi(float deltaTime) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec3 dirNorm = dir / segmentLen;
|
// Use Catmull-Rom spline for smooth interpolation between waypoints
|
||||||
glm::vec3 nextPos = start + dirNorm * (t * segmentLen);
|
// Get surrounding points for spline curve
|
||||||
|
glm::vec3 p0 = (taxiClientIndex_ > 0) ? taxiClientPath_[taxiClientIndex_ - 1] : start;
|
||||||
|
glm::vec3 p1 = start;
|
||||||
|
glm::vec3 p2 = end;
|
||||||
|
glm::vec3 p3 = (taxiClientIndex_ + 2 < taxiClientPath_.size()) ?
|
||||||
|
taxiClientPath_[taxiClientIndex_ + 2] : end;
|
||||||
|
|
||||||
// Add a flight arc to avoid terrain collisions.
|
// Catmull-Rom spline formula for smooth curves
|
||||||
float arcHeight = std::clamp(segmentLen * 0.15f, 20.0f, 120.0f);
|
float t2 = t * t;
|
||||||
float arc = 4.0f * t * (1.0f - t);
|
float t3 = t2 * t;
|
||||||
nextPos.z = glm::mix(start.z, end.z, t) + arcHeight * arc;
|
glm::vec3 nextPos = 0.5f * (
|
||||||
|
(2.0f * p1) +
|
||||||
|
(-p0 + p2) * t +
|
||||||
|
(2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * t2 +
|
||||||
|
(-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3
|
||||||
|
);
|
||||||
|
|
||||||
float orientation = std::atan2(dir.y, dir.x) - 1.57079632679f;
|
// Calculate smooth direction for orientation (tangent to spline)
|
||||||
playerEntity->setPosition(nextPos.x, nextPos.y, nextPos.z, orientation);
|
glm::vec3 tangent = 0.5f * (
|
||||||
|
(-p0 + p2) +
|
||||||
|
2.0f * (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * t +
|
||||||
|
3.0f * (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t2
|
||||||
|
);
|
||||||
|
|
||||||
|
// Smooth orientation based on spline tangent
|
||||||
|
float targetOrientation = std::atan2(tangent.y, tangent.x) - 1.57079632679f;
|
||||||
|
|
||||||
|
// Smooth rotation transition (lerp towards target)
|
||||||
|
float currentOrientation = movementInfo.orientation;
|
||||||
|
float orientDiff = targetOrientation - currentOrientation;
|
||||||
|
// Normalize angle difference to [-PI, PI]
|
||||||
|
while (orientDiff > 3.14159265f) orientDiff -= 6.28318530f;
|
||||||
|
while (orientDiff < -3.14159265f) orientDiff += 6.28318530f;
|
||||||
|
float smoothOrientation = currentOrientation + orientDiff * std::min(1.0f, deltaTime * 3.0f);
|
||||||
|
|
||||||
|
playerEntity->setPosition(nextPos.x, nextPos.y, nextPos.z, smoothOrientation);
|
||||||
movementInfo.x = nextPos.x;
|
movementInfo.x = nextPos.x;
|
||||||
movementInfo.y = nextPos.y;
|
movementInfo.y = nextPos.y;
|
||||||
movementInfo.z = nextPos.z;
|
movementInfo.z = nextPos.z;
|
||||||
movementInfo.orientation = orientation;
|
movementInfo.orientation = smoothOrientation;
|
||||||
|
|
||||||
|
// Update mount rotation to face flight direction
|
||||||
|
if (taxiOrientationCallback_) {
|
||||||
|
taxiOrientationCallback_(smoothOrientation);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameHandler::handleActivateTaxiReply(network::Packet& packet) {
|
void GameHandler::handleActivateTaxiReply(network::Packet& packet) {
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,8 @@ void CameraController::update(float deltaTime) {
|
||||||
// Get camera axes — project forward onto XY plane for walking
|
// Get camera axes — project forward onto XY plane for walking
|
||||||
glm::vec3 forward3D = camera->getForward();
|
glm::vec3 forward3D = camera->getForward();
|
||||||
bool cameraDrivesFacing = rightMouseDown || mouseAutorun;
|
bool cameraDrivesFacing = rightMouseDown || mouseAutorun;
|
||||||
if (cameraDrivesFacing) {
|
// During taxi flights, orientation is controlled by the flight path, not player input
|
||||||
|
if (cameraDrivesFacing && !externalFollow_) {
|
||||||
facingYaw = yaw;
|
facingYaw = yaw;
|
||||||
}
|
}
|
||||||
float moveYaw = cameraDrivesFacing ? yaw : facingYaw;
|
float moveYaw = cameraDrivesFacing ? yaw : facingYaw;
|
||||||
|
|
|
||||||
|
|
@ -1644,7 +1644,8 @@ void M2Renderer::render(const Camera& camera, const glm::mat4& view, const glm::
|
||||||
lastDrawCallCount = 0;
|
lastDrawCallCount = 0;
|
||||||
|
|
||||||
// Adaptive render distance: keep longer tree/foliage visibility to reduce pop-in.
|
// Adaptive render distance: keep longer tree/foliage visibility to reduce pop-in.
|
||||||
const float maxRenderDistance = (instances.size() > 600) ? 320.0f : 2800.0f;
|
// During taxi, use very short render distance to prevent loading hitches
|
||||||
|
const float maxRenderDistance = onTaxi_ ? 150.0f : (instances.size() > 600) ? 320.0f : 2800.0f;
|
||||||
const float maxRenderDistanceSq = maxRenderDistance * maxRenderDistance;
|
const float maxRenderDistanceSq = maxRenderDistance * maxRenderDistance;
|
||||||
const float fadeStartFraction = 0.75f;
|
const float fadeStartFraction = 0.75f;
|
||||||
const glm::vec3 camPos = camera.getPosition();
|
const glm::vec3 camPos = camera.getPosition();
|
||||||
|
|
@ -1713,10 +1714,20 @@ void M2Renderer::render(const Camera& camera, const glm::mat4& view, const glm::
|
||||||
|
|
||||||
const M2ModelGPU& model = *currentModel;
|
const M2ModelGPU& model = *currentModel;
|
||||||
|
|
||||||
// Skip small models when on taxi (performance optimization)
|
// Aggressive culling during taxi for smooth flight
|
||||||
// Small props/foliage aren't visible from flight altitude anyway
|
if (onTaxi_) {
|
||||||
if (onTaxi_ && model.boundRadius < 3.0f) {
|
// Skip all small/medium models (props, foliage, decorations)
|
||||||
continue;
|
if (model.boundRadius < 15.0f) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Skip all foliage and trees (even large ones cause hitching during load)
|
||||||
|
if (model.collisionNoBlock || model.collisionTreeTrunk) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Skip underwater objects (water is opaque from altitude)
|
||||||
|
if (instance.position.z < -5.0f) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Distance-based fade alpha for smooth pop-in (squared-distance, no sqrt)
|
// Distance-based fade alpha for smooth pop-in (squared-distance, no sqrt)
|
||||||
|
|
|
||||||
|
|
@ -1119,7 +1119,11 @@ void Renderer::update(float deltaTime) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Movement-facing comes from camera controller and is decoupled from LMB orbit.
|
// Movement-facing comes from camera controller and is decoupled from LMB orbit.
|
||||||
if (cameraController->isMoving() || cameraController->isRightMouseHeld()) {
|
// During taxi flights, orientation is controlled by the flight path (not player input)
|
||||||
|
if (taxiFlight_) {
|
||||||
|
// Taxi flight: use orientation from flight path
|
||||||
|
characterYaw = cameraController->getFacingYaw();
|
||||||
|
} else if (cameraController->isMoving() || cameraController->isRightMouseHeld()) {
|
||||||
characterYaw = cameraController->getFacingYaw();
|
characterYaw = cameraController->getFacingYaw();
|
||||||
} else if (inCombat_ && targetPosition && !emoteActive && !isMounted()) {
|
} else if (inCombat_ && targetPosition && !emoteActive && !isMounted()) {
|
||||||
// Face target when in combat and idle
|
// Face target when in combat and idle
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue