Fix yaw conversions for online movement/entities

This commit is contained in:
Kelsi
2026-02-12 15:08:21 -08:00
parent 0ff0f2f995
commit bdee8bca77
3 changed files with 65 additions and 29 deletions

View File

@@ -9,6 +9,8 @@ namespace wowee::core::coords {
inline constexpr float TILE_SIZE = 533.33333f;
inline constexpr float ZEROPOINT = 32.0f * TILE_SIZE;
inline constexpr float PI = 3.14159265358979323846f;
inline constexpr float TWO_PI = 6.28318530717958647692f;
// ---- Canonical WoW world coordinate system (per-map) ----
// +X = North, +Y = West, +Z = Up (height)
@@ -42,6 +44,28 @@ inline glm::vec3 canonicalToServer(const glm::vec3& canonical) {
return glm::vec3(canonical.y, canonical.x, canonical.z);
}
// Normalize angle to [-PI, PI].
inline float normalizeAngleRad(float a) {
while (a > PI) a -= TWO_PI;
while (a < -PI) a += TWO_PI;
return a;
}
// Convert server/wire yaw (radians) → canonical yaw (radians).
//
// Under server<->canonical X/Y swap:
// dir_s = (cos(s), sin(s))
// dir_c = swap(dir_s) = (sin(s), cos(s)) => c = PI/2 - s
inline float serverToCanonicalYaw(float serverYaw) {
return normalizeAngleRad((PI * 0.5f) - serverYaw);
}
// Convert canonical yaw (radians) → server/wire yaw (radians).
// This mapping is its own inverse.
inline float canonicalToServerYaw(float canonicalYaw) {
return normalizeAngleRad((PI * 0.5f) - canonicalYaw);
}
// Convert between canonical WoW and engine rendering coordinates (just swap X/Y).
inline glm::vec3 canonicalToRender(const glm::vec3& wow) {
return glm::vec3(wow.y, wow.x, wow.z);

View File

@@ -685,8 +685,12 @@ void Application::update(float deltaTime) {
// Sync orientation: camera yaw (degrees) → WoW orientation (radians)
float yawDeg = renderer->getCharacterYaw();
float wowOrientation = glm::radians(yawDeg - 90.0f);
gameHandler->setOrientation(wowOrientation);
// Keep all game-side orientation in canonical space.
// We historically sent serverYaw = radians(yawDeg - 90). With the new
// canonical<->server mapping (serverYaw = PI/2 - canonicalYaw), the
// equivalent canonical yaw is radians(180 - yawDeg).
float canonicalYaw = core::coords::normalizeAngleRad(glm::radians(180.0f - yawDeg));
gameHandler->setOrientation(canonicalYaw);
}
}

View File

@@ -1794,7 +1794,7 @@ void GameHandler::handleLoginVerifyWorld(network::Packet& packet) {
movementInfo.x = canonical.x;
movementInfo.y = canonical.y;
movementInfo.z = canonical.z;
movementInfo.orientation = data.orientation;
movementInfo.orientation = core::coords::serverToCanonicalYaw(data.orientation);
movementInfo.flags = 0;
movementInfo.flags2 = 0;
movementInfo.time = 0;
@@ -2322,23 +2322,20 @@ void GameHandler::sendMovement(Opcode opcode) {
movementInfo.transportTime2 = movementInfo.time;
// ONTRANSPORT expects local orientation (player yaw relative to transport yaw).
float transportYaw = 0.0f;
// Keep internal yaw canonical; convert to server yaw on the wire.
float transportYawCanonical = 0.0f;
if (transportManager_) {
if (auto* tr = transportManager_->getTransport(playerTransportGuid_); tr) {
if (tr->hasServerYaw) {
transportYaw = tr->serverYaw;
transportYawCanonical = tr->serverYaw;
} else {
transportYaw = glm::eulerAngles(tr->rotation).z;
transportYawCanonical = glm::eulerAngles(tr->rotation).z;
}
}
}
float localTransportO = movementInfo.orientation - transportYaw;
constexpr float kPi = 3.14159265359f;
constexpr float kTwoPi = 6.28318530718f;
while (localTransportO > kPi) localTransportO -= kTwoPi;
while (localTransportO < -kPi) localTransportO += kTwoPi;
movementInfo.transportO = localTransportO;
movementInfo.transportO =
core::coords::normalizeAngleRad(movementInfo.orientation - transportYawCanonical);
} else {
// Clear transport flag if not on transport
movementInfo.flags &= ~static_cast<uint32_t>(MovementFlags::ONTRANSPORT);
@@ -2357,6 +2354,9 @@ void GameHandler::sendMovement(Opcode opcode) {
wireInfo.y = serverPos.y;
wireInfo.z = serverPos.z;
// Convert canonical → server yaw for the wire
wireInfo.orientation = core::coords::canonicalToServerYaw(wireInfo.orientation);
// Also convert transport local position to server coordinates if on transport
if (isOnTransport()) {
glm::vec3 serverTransportPos = core::coords::canonicalToServer(
@@ -2364,6 +2364,8 @@ void GameHandler::sendMovement(Opcode opcode) {
wireInfo.transportX = serverTransportPos.x;
wireInfo.transportY = serverTransportPos.y;
wireInfo.transportZ = serverTransportPos.z;
// transportO is a local delta; server<->canonical swap negates delta yaw.
wireInfo.transportO = core::coords::normalizeAngleRad(-wireInfo.transportO);
}
// Build and send movement packet
@@ -2520,7 +2522,8 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
// Set position from movement block (server → canonical)
if (block.hasMovement) {
glm::vec3 pos = core::coords::serverToCanonical(glm::vec3(block.x, block.y, block.z));
entity->setPosition(pos.x, pos.y, pos.z, block.orientation);
float oCanonical = core::coords::serverToCanonicalYaw(block.orientation);
entity->setPosition(pos.x, pos.y, pos.z, oCanonical);
LOG_DEBUG(" Position: (", pos.x, ", ", pos.y, ", ", pos.z, ")");
if (block.guid == playerGuid && block.runSpeed > 0.1f && block.runSpeed < 100.0f) {
serverRunSpeed_ = block.runSpeed;
@@ -2534,7 +2537,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
playerTransportOffset_ = core::coords::serverToCanonical(serverOffset);
if (transportManager_ && transportManager_->getTransport(playerTransportGuid_)) {
glm::vec3 composed = transportManager_->getPlayerWorldPosition(playerTransportGuid_, playerTransportOffset_);
entity->setPosition(composed.x, composed.y, composed.z, block.orientation);
entity->setPosition(composed.x, composed.y, composed.z, oCanonical);
movementInfo.x = composed.x;
movementInfo.y = composed.y;
movementInfo.z = composed.z;
@@ -2556,8 +2559,9 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
glm::vec3 localOffset = core::coords::serverToCanonical(
glm::vec3(block.transportX, block.transportY, block.transportZ));
const bool hasLocalOrientation = (block.updateFlags & 0x0020) != 0; // UPDATEFLAG_LIVING
float localOriCanonical = core::coords::normalizeAngleRad(-block.transportO);
setTransportAttachment(block.guid, block.objectType, block.transportGuid,
localOffset, hasLocalOrientation, block.transportO);
localOffset, hasLocalOrientation, localOriCanonical);
if (transportManager_ && transportManager_->getTransport(block.transportGuid)) {
glm::vec3 composed = transportManager_->getPlayerWorldPosition(block.transportGuid, localOffset);
entity->setPosition(composed.x, composed.y, composed.z, entity->getOrientation());
@@ -2870,7 +2874,8 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
if (entity) {
if (block.hasMovement) {
glm::vec3 pos = core::coords::serverToCanonical(glm::vec3(block.x, block.y, block.z));
entity->setPosition(pos.x, pos.y, pos.z, block.orientation);
float oCanonical = core::coords::serverToCanonicalYaw(block.orientation);
entity->setPosition(pos.x, pos.y, pos.z, oCanonical);
if (block.guid != playerGuid &&
(entity->getType() == ObjectType::UNIT || entity->getType() == ObjectType::GAMEOBJECT)) {
@@ -2878,8 +2883,9 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
glm::vec3 localOffset = core::coords::serverToCanonical(
glm::vec3(block.transportX, block.transportY, block.transportZ));
const bool hasLocalOrientation = (block.updateFlags & 0x0020) != 0; // UPDATEFLAG_LIVING
float localOriCanonical = core::coords::normalizeAngleRad(-block.transportO);
setTransportAttachment(block.guid, entity->getType(), block.transportGuid,
localOffset, hasLocalOrientation, block.transportO);
localOffset, hasLocalOrientation, localOriCanonical);
if (transportManager_ && transportManager_->getTransport(block.transportGuid)) {
glm::vec3 composed = transportManager_->getPlayerWorldPosition(block.transportGuid, localOffset);
entity->setPosition(composed.x, composed.y, composed.z, entity->getOrientation());
@@ -3108,7 +3114,8 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
auto entity = entityManager.getEntity(block.guid);
if (entity) {
glm::vec3 pos = core::coords::serverToCanonical(glm::vec3(block.x, block.y, block.z));
entity->setPosition(pos.x, pos.y, pos.z, block.orientation);
float oCanonical = core::coords::serverToCanonicalYaw(block.orientation);
entity->setPosition(pos.x, pos.y, pos.z, oCanonical);
LOG_DEBUG("Updated entity position: 0x", std::hex, block.guid, std::dec);
if (block.guid != playerGuid &&
@@ -3117,8 +3124,9 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
glm::vec3 localOffset = core::coords::serverToCanonical(
glm::vec3(block.transportX, block.transportY, block.transportZ));
const bool hasLocalOrientation = (block.updateFlags & 0x0020) != 0; // UPDATEFLAG_LIVING
float localOriCanonical = core::coords::normalizeAngleRad(-block.transportO);
setTransportAttachment(block.guid, entity->getType(), block.transportGuid,
localOffset, hasLocalOrientation, block.transportO);
localOffset, hasLocalOrientation, localOriCanonical);
if (transportManager_ && transportManager_->getTransport(block.transportGuid)) {
glm::vec3 composed = transportManager_->getPlayerWorldPosition(block.transportGuid, localOffset);
entity->setPosition(composed.x, composed.y, composed.z, entity->getOrientation());
@@ -3129,7 +3137,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
}
if (block.guid == playerGuid) {
movementInfo.orientation = block.orientation;
movementInfo.orientation = oCanonical;
// Track player-on-transport state from MOVEMENT updates
if (block.onTransport) {
@@ -3139,7 +3147,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
playerTransportOffset_ = core::coords::serverToCanonical(serverOffset);
if (transportManager_ && transportManager_->getTransport(playerTransportGuid_)) {
glm::vec3 composed = transportManager_->getPlayerWorldPosition(playerTransportGuid_, playerTransportOffset_);
entity->setPosition(composed.x, composed.y, composed.z, block.orientation);
entity->setPosition(composed.x, composed.y, composed.z, oCanonical);
movementInfo.x = composed.x;
movementInfo.y = composed.y;
movementInfo.z = composed.z;
@@ -3163,7 +3171,7 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
// Fire transport move callback if this is a known transport
if (transportGuids_.count(block.guid) && transportMoveCallback_) {
serverUpdatedTransportGuids_.insert(block.guid);
transportMoveCallback_(block.guid, pos.x, pos.y, pos.z, block.orientation);
transportMoveCallback_(block.guid, pos.x, pos.y, pos.z, oCanonical);
}
// Fire move callback for non-transport gameobjects.
if (entity->getType() == ObjectType::GAMEOBJECT &&
@@ -5081,7 +5089,7 @@ void GameHandler::handleMonsterMove(network::Packet& packet) {
float orientation = entity->getOrientation();
if (data.moveType == 4) {
// FacingAngle - server specifies exact angle
orientation = data.facingAngle;
orientation = core::coords::serverToCanonicalYaw(data.facingAngle);
} else if (data.moveType == 3) {
// FacingTarget - face toward the target entity
auto target = entityManager.getEntity(data.facingTarget);
@@ -5134,7 +5142,7 @@ void GameHandler::handleMonsterMoveTransport(network::Packet& packet) {
uint8_t unk = packet.readUInt8(); // Unknown byte (usually 0)
uint64_t transportGuid = packet.readUInt64();
// Transport-local coordinates
// Transport-local coordinates (server space)
float localX = packet.readFloat();
float localY = packet.readFloat();
float localZ = packet.readFloat();
@@ -5152,9 +5160,9 @@ void GameHandler::handleMonsterMoveTransport(network::Packet& packet) {
if (transportManager_) {
// Use TransportManager to compose world position from local offset
glm::vec3 localPos(localX, localY, localZ);
setTransportAttachment(moverGuid, entity->getType(), transportGuid, localPos, false, 0.0f);
glm::vec3 worldPos = transportManager_->getPlayerWorldPosition(transportGuid, localPos);
glm::vec3 localPosCanonical = core::coords::serverToCanonical(glm::vec3(localX, localY, localZ));
setTransportAttachment(moverGuid, entity->getType(), transportGuid, localPosCanonical, false, 0.0f);
glm::vec3 worldPos = transportManager_->getPlayerWorldPosition(transportGuid, localPosCanonical);
entity->setPosition(worldPos.x, worldPos.y, worldPos.z, entity->getOrientation());
@@ -6601,7 +6609,7 @@ void GameHandler::handleTeleportAck(network::Packet& packet) {
movementInfo.x = canonical.x;
movementInfo.y = canonical.y;
movementInfo.z = canonical.z;
movementInfo.orientation = orientation;
movementInfo.orientation = core::coords::serverToCanonicalYaw(orientation);
movementInfo.flags = 0;
// Send the ack back to the server
@@ -6661,7 +6669,7 @@ void GameHandler::handleNewWorld(network::Packet& packet) {
movementInfo.x = canonical.x;
movementInfo.y = canonical.y;
movementInfo.z = canonical.z;
movementInfo.orientation = orientation;
movementInfo.orientation = core::coords::serverToCanonicalYaw(orientation);
movementInfo.flags = 0;
movementInfo.flags2 = 0;
resurrectPending_ = false;