diff --git a/src/game/ChatCommands/Level2.cpp b/src/game/ChatCommands/Level2.cpp index 10dbb9cc..abf5f745 100644 --- a/src/game/ChatCommands/Level2.cpp +++ b/src/game/ChatCommands/Level2.cpp @@ -58,7 +58,7 @@ #include #include #include "Formulas.h" - +#include "G3D/Quat.h" // for turning GO's #include "TargetedMovementGenerator.h" // for HandleNpcUnFollowCommand #include "MoveMap.h" // for mmap manager #include "PathFinder.h" // for mmap commands @@ -947,11 +947,27 @@ bool ChatHandler::HandleGameObjectTurnCommand(char* args) if (!ExtractOptFloat(&args, o, m_session->GetPlayer()->GetOrientation())) { return false; } - Map* map = obj->GetMap(); - map->Remove(obj, false); + // ok, let's rotate the GO around Z axis + // we first get the original rotation quaternion + // then we'll create a rotation quat describing the rotation around Z + G3D::Quat original_rot; + obj->GetQuaternion(original_rot); - obj->Relocate(obj->GetPositionX(), obj->GetPositionY(), obj->GetPositionZ(), o); - obj->UpdateRotationFields(); + // the rotation amount around Z-axis + float deltaO = o - obj->GetOrientationFromQuat(original_rot); + + // multiplying 2 quaternions gives the final rotation + // quaternion multiplication is not commutative! + G3D::Quat final_rot = G3D::Quat(0.0f, 0.0f, sin(deltaO/2), cos(deltaO/2)) * original_rot; + + // quaternion multiplication gives a non-unit quat + final_rot.unitize(); + + Map* map = obj->GetMap(); + map->Remove(obj, false); //mandatory to remove GO model from m_dyn_tree + + obj->SetQuaternion(final_rot); // this will update internal model rotation matrices + obj->Relocate(obj->GetPositionX(), obj->GetPositionY(), obj->GetPositionZ(), obj->GetOrientationFromQuat(final_rot)); map->Add(obj); diff --git a/src/game/Object/GameObject.cpp b/src/game/Object/GameObject.cpp index 6455e379..86c347c0 100644 --- a/src/game/Object/GameObject.cpp +++ b/src/game/Object/GameObject.cpp @@ -23,6 +23,7 @@ */ #include "GameObject.h" +#include "G3D/Quat.h" #include "QuestDef.h" #include "ObjectMgr.h" #include "PoolManager.h" @@ -145,10 +146,32 @@ void GameObject::CleanupsBeforeDelete() WorldObject::CleanupsBeforeDelete(); } -bool GameObject::Create(uint32 guidlow, uint32 name_id, Map* map, float x, float y, float z, float ang, float rotation0, float rotation1, float rotation2, float rotation3, uint32 animprogress, GOState go_state) +bool GameObject::Create(uint32 guidlow, uint32 name_id, Map* map, float x, float y, float z, float ang, float r0, float r1, float r2, float r3, uint32 animprogress, GOState go_state) { - MANGOS_ASSERT(map); - Relocate(x, y, z, ang); + if (!map) + { return false; } + + GameObjectInfo const* goinfo = ObjectMgr::GetGameObjectInfo(name_id); + if (!goinfo) + { + sLog.outErrorDb("Gameobject (GUID: %u) not created: Entry %u does not exist in `gameobject_template`", guidlow, name_id); + return false; + } + + Object::_Create(guidlow, goinfo->id, HIGHGUID_GAMEOBJECT); + + // let's make sure we don't send the client invalid quaternion + if (r0 == 0.0f && r1 == 0.0f && r2 == 0.0f) + { + r2 = sin(ang/2); + r3 = cos(ang/2); + } + + G3D::Quat q(r0, r1, r2, r3); + q.unitize(); + + float o = GetOrientationFromQuat(q); + Relocate(x, y, z, o); SetMap(map); if (!IsPositionValid()) @@ -157,14 +180,7 @@ bool GameObject::Create(uint32 guidlow, uint32 name_id, Map* map, float x, float return false; } - GameObjectInfo const* goinfo = ObjectMgr::GetGameObjectInfo(name_id); - if (!goinfo) - { - sLog.outErrorDb("Gameobject (GUID: %u) not created: Entry %u does not exist in `gameobject_template`. Map: %u (X: %f Y: %f Z: %f) ang: %f rotation0: %f rotation1: %f rotation2: %f rotation3: %f", guidlow, name_id, map->GetId(), x, y, z, ang, rotation0, rotation1, rotation2, rotation3); - return false; - } - - Object::_Create(guidlow, goinfo->id, HIGHGUID_GAMEOBJECT); + SetQuaternion(q); m_goInfo = goinfo; @@ -180,11 +196,6 @@ bool GameObject::Create(uint32 guidlow, uint32 name_id, Map* map, float x, float SetFloatValue(GAMEOBJECT_POS_Y, y); SetFloatValue(GAMEOBJECT_POS_Z, z); - SetFloatValue(GAMEOBJECT_ROTATION + 0, rotation0); - SetFloatValue(GAMEOBJECT_ROTATION + 1, rotation1); - - UpdateRotationFields(rotation2, rotation3); // GAMEOBJECT_FACING, GAMEOBJECT_ROTATION+2/3 - SetUInt32Value(GAMEOBJECT_FACTION, goinfo->faction); SetUInt32Value(GAMEOBJECT_FLAGS, goinfo->flags); @@ -1673,18 +1684,30 @@ const char* GameObject::GetNameForLocaleIdx(int32 loc_idx) const return GetName(); } -void GameObject::UpdateRotationFields(float rotation2 /*=0.0f*/, float rotation3 /*=0.0f*/) +void GameObject::SetQuaternion(G3D::Quat const& q) { - SetFloatValue(GAMEOBJECT_FACING, GetOrientation()); + SetFloatValue(GAMEOBJECT_ROTATION + 0, q.x); + SetFloatValue(GAMEOBJECT_ROTATION + 1, q.y); + SetFloatValue(GAMEOBJECT_ROTATION + 2, q.z); + SetFloatValue(GAMEOBJECT_ROTATION + 3, q.w); - if (rotation2 == 0.0f && rotation3 == 0.0f) - { - rotation2 = sin(GetOrientation() / 2); - rotation3 = cos(GetOrientation() / 2); - } + if (m_model) + { m_model->UpdateRotation(q); } +} - SetFloatValue(GAMEOBJECT_ROTATION + 2, rotation2); - SetFloatValue(GAMEOBJECT_ROTATION + 3, rotation3); +void GameObject::GetQuaternion(G3D::Quat& q) const +{ + q.x = GetFloatValue(GAMEOBJECT_ROTATION + 0); + q.y = GetFloatValue(GAMEOBJECT_ROTATION + 1); + q.z = GetFloatValue(GAMEOBJECT_ROTATION + 2); + q.w = GetFloatValue(GAMEOBJECT_ROTATION + 3); +} + +float GameObject::GetOrientationFromQuat(G3D::Quat const& q) +{ + double t1 = +2.0f * (q.w * q.z + q.x * q.y); + double t2 = +1.0f - 2.0f * (q.y * q.y + q.z * q.z); + return MapManager::NormalizeOrientation(std::atan2(t1, t2)); } bool GameObject::IsHostileTo(Unit const* unit) const @@ -2295,4 +2318,4 @@ float GameObject::GetInteractionDistance() const break; } return maxdist; -} +} \ No newline at end of file diff --git a/src/game/Object/GameObject.h b/src/game/Object/GameObject.h index c337afe1..6bd35541 100644 --- a/src/game/Object/GameObject.h +++ b/src/game/Object/GameObject.h @@ -491,16 +491,16 @@ enum GOState // from `gameobject` struct GameObjectData { - uint32 id; // entry in gamobject_template + uint32 id; // entry in gameobject_template uint32 mapid; float posX; float posY; float posZ; float orientation; - float rotation0; - float rotation1; - float rotation2; - float rotation3; + float rotation0; // i component of rotation quaternion + float rotation1; // j + float rotation2; // k + float rotation3; // w int32 spawntimesecs; uint32 animprogress; GOState go_state; @@ -538,6 +538,12 @@ enum CapturePointSliderValue class Unit; class GameObjectModel; + +namespace G3D +{ + class Quat; +}; + struct GameObjectDisplayInfoEntry; // 5 sec for bobber catch @@ -565,7 +571,12 @@ class GameObject : public WorldObject bool HasStaticDBSpawnData() const; // listed in `gameobject` table and have fixed in DB guid - void UpdateRotationFields(float rotation2 = 0.0f, float rotation3 = 0.0f); + // rotation methods + void GetQuaternion(G3D::Quat& q) const; + void SetQuaternion(G3D::Quat const& q); + float GetOrientationFromQuat(G3D::Quat const& q); + + void SetDisplayId(uint32 model_id); // overwrite WorldObject function for proper name localization const char* GetNameForLocaleIdx(int32 locale_idx) const override; @@ -636,7 +647,7 @@ class GameObject : public WorldObject uint32 GetGoAnimProgress() const { return GetUInt32Value(GAMEOBJECT_ANIMPROGRESS); } void SetGoAnimProgress(uint32 animprogress) { SetUInt32Value(GAMEOBJECT_ANIMPROGRESS, animprogress); } uint32 GetDisplayId() const { return GetUInt32Value(GAMEOBJECT_DISPLAYID); } - void SetDisplayId(uint32 modelId); + void SetDisplayIdx(uint32 modelId); float GetObjectBoundingRadius() const override; // overwrite WorldObject version diff --git a/src/game/vmap/GameObjectModel.cpp b/src/game/vmap/GameObjectModel.cpp index bd2adf65..00757a1c 100644 --- a/src/game/vmap/GameObjectModel.cpp +++ b/src/game/vmap/GameObjectModel.cpp @@ -32,6 +32,7 @@ #include "GameObjectModel.h" #include "DBCStores.h" #include "Creature.h" +#include "G3D/Quat.h" struct GameobjectModelData { @@ -107,9 +108,9 @@ bool GameObjectModel::initialize(const GameObject* const pGo, const GameObjectDi if (it == model_list.end()) { return false; } - G3D::AABox mdl_box(it->second.bound); + iModelBound = it->second.bound; // ignore models with no bounds - if (mdl_box == G3D::AABox::zero()) + if (iModelBound == G3D::AABox::zero()) { sLog.outDebug("Model %s has zero bounds, loading skipped", it->second.name.c_str()); return false; @@ -126,18 +127,30 @@ bool GameObjectModel::initialize(const GameObject* const pGo, const GameObjectDi iScale = pGo->GetObjectScale(); iInvScale = 1.f / iScale; - iRot = G3D::Matrix3::fromEulerAnglesZYX(pGo->GetOrientation(), 0, 0); + G3D::Quat q; + pGo->GetQuaternion(q); + UpdateRotation(q); + + return true; +} + +void GameObjectModel::UpdateRotation(G3D::Quat const& q) +{ + q.toRotationMatrix(iRot); + iInvRot = iRot.inverse(); + G3D::AABox mdl_box(iModelBound); + // transform bounding box: mdl_box = AABox(mdl_box.low() * iScale, mdl_box.high() * iScale); - AABox rotated_bounds; + + G3D::AABox rotated_bounds; + for (int i = 0; i < 8; ++i) { rotated_bounds.merge(iRot * mdl_box.corner(i)); } iBound = rotated_bounds + iPos; - - return true; } GameObjectModel* GameObjectModel::Create(const GameObject* const pGo) diff --git a/src/game/vmap/GameObjectModel.h b/src/game/vmap/GameObjectModel.h index e3db75df..492cbc85 100644 --- a/src/game/vmap/GameObjectModel.h +++ b/src/game/vmap/GameObjectModel.h @@ -40,6 +40,11 @@ namespace VMAP class WorldModel; } +namespace G3D +{ + class Quat; +} + /** * @brief * @@ -51,6 +56,7 @@ class GameObjectModel std::string iName; G3D::AABox iBound; + G3D::AABox iModelBound; G3D::Vector3 iPos; G3D::Matrix3 iRot; float iScale; @@ -71,6 +77,7 @@ class GameObjectModel ~GameObjectModel(); const G3D::Vector3& GetPosition() const { return iPos;} + void UpdateRotation(G3D::Quat const& q); const GameObject* GetOwner() const { return iOwner; } void SetCollidable(bool enabled) { isCollidable = enabled; }