diff --git a/dGame/dComponents/ModelComponent.cpp b/dGame/dComponents/ModelComponent.cpp index 842690f08..2b4aa0231 100644 --- a/dGame/dComponents/ModelComponent.cpp +++ b/dGame/dComponents/ModelComponent.cpp @@ -46,12 +46,10 @@ bool ModelComponent::OnResetModelToDefaults(GameMessages::ResetModelToDefaults& if (reset.bResetPos) m_Parent->SetPosition(m_OriginalPosition); if (reset.bResetRot) m_Parent->SetRotation(m_OriginalRotation); m_Parent->SetVelocity(NiPoint3Constant::ZERO); - m_MoveDirection = NiPoint3Constant::ZERO; - m_WasMoving = false; + m_Move = MoveState{}; SyncLinearVelocity(); ResetRotationState(m_Parent->GetRotation()); - m_Speed = 3.0f; m_NumListeningInteract = 0; m_NumActiveAttack = 0; @@ -295,85 +293,77 @@ void ModelComponent::RemoveUnSmash() { m_NumActiveUnSmash--; } -bool ModelComponent::TryStartMove(const int axis, const float direction) { - if (axis < 0 || axis > 2 || direction == 0.0f) return false; - if (m_MoveDirection[axis] != 0.0f) return false; +bool ModelComponent::TryStartMove(const int axis, const float direction, const float speed) { + if (axis < 0 || axis > 2 || direction == 0.0f || speed <= 0.0f) return false; + if (m_Move.velocity[axis] != 0.0f) return false; - m_MoveDirection[axis] = direction > 0.0f ? 1.0f : -1.0f; + m_Move.velocity[axis] = std::copysign(speed, direction); return true; } void ModelComponent::StopMove(const int axis) { if (axis < 0 || axis > 2) return; - m_MoveDirection[axis] = 0.0f; + m_Move.velocity[axis] = 0.0f; } void ModelComponent::SyncLinearVelocity() { const auto& rotation = m_Parent->GetRotation(); - m_MoveBasis = { QuatUtils::Right(rotation), QuatUtils::Up(rotation), QuatUtils::Forward(rotation) }; + m_Move.basis = { QuatUtils::Right(rotation), QuatUtils::Up(rotation), QuatUtils::Forward(rotation) }; NiPoint3 velocity = NiPoint3Constant::ZERO; - for (int axis = 0; axis < 3; axis++) velocity += m_MoveBasis[axis] * (m_MoveDirection[axis] * m_Speed); + for (int axis = 0; axis < 3; axis++) velocity += m_Move.basis[axis] * m_Move.velocity[axis]; // Leave velocity alone unless a move owns it, e.g. pets are driven elsewhere const bool isMoving = velocity != NiPoint3Constant::ZERO; - if (!isMoving && !m_WasMoving) return; - m_WasMoving = isMoving; + if (!isMoving && !m_Move.wasMoving) return; + m_Move.wasMoving = isMoving; // Setting velocity always marks it dirty for serialization if (velocity != m_Parent->GetVelocity()) m_Parent->SetVelocity(velocity); } -bool ModelComponent::TryStartRotation(const int axis, const float direction) { - if (axis < 0 || axis > 2 || direction == 0.0f) return false; - if (m_RotationDirection[axis] != 0.0f) return false; +bool ModelComponent::TryStartRotation(const int axis, const float direction, const float speed) { + if (axis < 0 || axis > 2 || direction == 0.0f || speed <= 0.0f) return false; + if (m_Rotation.velocity[axis] != 0.0f) return false; // Rebase only when nothing is rotating so simultaneous rotations stay relative to the same base - if (m_RotationDirection == NiPoint3Constant::ZERO) ResetRotationState(m_Parent->GetRotation()); + if (m_Rotation.velocity == NiPoint3Constant::ZERO) ResetRotationState(m_Parent->GetRotation()); - m_RotationDegrees[axis] = std::fmod(m_RotationDegrees[axis], 360.0f); - m_RotationActionStart[axis] = m_RotationDegrees[axis]; - m_RotationDirection[axis] = direction > 0.0f ? 1.0f : -1.0f; + m_Rotation.degrees[axis] = std::fmod(m_Rotation.degrees[axis], 360.0f); + m_Rotation.actionStart[axis] = m_Rotation.degrees[axis]; + m_Rotation.velocity[axis] = std::copysign(BASE_ANGULAR_SPEED * speed, direction); SyncAngularVelocity(); return true; } void ModelComponent::SetRotationProgress(const int axis, const float degrees) { if (axis < 0 || axis > 2) return; - m_RotationDegrees[axis] = m_RotationActionStart[axis] + degrees; + m_Rotation.degrees[axis] = m_Rotation.actionStart[axis] + degrees; // Whole turns wrap to exactly 0 so e.g. 720 degrees yields exactly the base rotation const NiPoint3 radians( - Math::DegToRad(std::fmod(m_RotationDegrees.x, 360.0f)), - Math::DegToRad(std::fmod(m_RotationDegrees.y, 360.0f)), - Math::DegToRad(std::fmod(m_RotationDegrees.z, 360.0f)) + Math::DegToRad(std::fmod(m_Rotation.degrees.x, 360.0f)), + Math::DegToRad(std::fmod(m_Rotation.degrees.y, 360.0f)), + Math::DegToRad(std::fmod(m_Rotation.degrees.z, 360.0f)) ); - m_Parent->SetRotation(QuatUtils::FromEuler(radians) * m_RotationBase); + m_Parent->SetRotation(QuatUtils::FromEuler(radians) * m_Rotation.base); } void ModelComponent::StopRotation(const int axis) { if (axis < 0 || axis > 2) return; - m_RotationDirection[axis] = 0.0f; + m_Rotation.velocity[axis] = 0.0f; SyncAngularVelocity(); } -void ModelComponent::SetSpeed(const float newSpeed) { - m_Speed = newSpeed; - if (m_RotationDirection != NiPoint3Constant::ZERO) SyncAngularVelocity(); -} - void ModelComponent::SyncAngularVelocity() const { GameMessages::SetAngularVelocity setAngVel{}; setAngVel.target = m_Parent->GetObjectID(); - setAngVel.angVelocity = m_RotationDirection * Math::DegToRad(GetAngularSpeed()); + setAngVel.angVelocity = m_Rotation.velocity * Math::DegToRad(1.0f); setAngVel.Send(); } void ModelComponent::ResetRotationState(const NiQuaternion& newBase) { - m_RotationBase = newBase; - m_RotationDegrees = NiPoint3Constant::ZERO; - m_RotationActionStart = NiPoint3Constant::ZERO; - m_RotationDirection = NiPoint3Constant::ZERO; + m_Rotation = RotationState{ .base = newBase }; SyncAngularVelocity(); } diff --git a/dGame/dComponents/ModelComponent.h b/dGame/dComponents/ModelComponent.h index c5dd987e1..7947890e2 100644 --- a/dGame/dComponents/ModelComponent.h +++ b/dGame/dComponents/ModelComponent.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include "dCommonVars.h" @@ -139,19 +140,19 @@ public: void Resume(); - // Attempts to claim a local axis (0 = right, 1 = up, 2 = forward) for movement in direction (+1 or -1). + // Attempts to claim a local axis (0 = right, 1 = up, 2 = forward) for movement in direction (+1 or -1) at speed units per second. // Returns false if the axis is already controlled by a behavior. - bool TryStartMove(const int axis, const float direction); + bool TryStartMove(const int axis, const float direction, const float speed); // Releases the local axis so another behavior can move along it. void StopMove(const int axis); // World space direction of the local axis used for the most recently applied velocity. - const NiPoint3& GetMoveAxis(const int axis) const { return m_MoveBasis[axis]; } + const NiPoint3& GetMoveAxis(const int axis) const { return m_Move.basis[axis]; } - // Attempts to claim a world axis (0 = x, 1 = y, 2 = z) for rotation in direction (+1 or -1). + // Attempts to claim a world axis (0 = x, 1 = y, 2 = z) for rotation in direction (+1 or -1) at the given behavior speed. // Returns false if the axis is already controlled by a behavior. - bool TryStartRotation(const int axis, const float direction); + bool TryStartRotation(const int axis, const float direction, const float speed); // Sets how many signed degrees the active rotation on axis has progressed and updates the entity. void SetRotationProgress(const int axis, const float degrees); @@ -159,16 +160,13 @@ public: // Releases the axis so another behavior can rotate it. void StopRotation(const int axis); - // Degrees per second for rotation actions at the current speed. - float GetAngularSpeed() const noexcept { return BASE_ANGULAR_SPEED * m_Speed; } + // Degrees per second of the active rotation on axis. + float GetAngularSpeed(const int axis) const noexcept { return std::abs(m_Rotation.velocity[axis]); } void OnChatMessageReceived(const std::string& sMessage); void OnHit(); - // Sets the speed of the model - void SetSpeed(const float newSpeed); - // Whether or not to restart at the end of the frame void RestartAtEndOfFrame() { m_RestartAtEndOfFrame = true; } @@ -179,12 +177,35 @@ public: // Decrements the number of strips listening for an attack. // If this is the last strip removing an attack, it will reset the factions to the default of -1. void RemoveAttack(); - - float GetSpeed() const noexcept { return m_Speed; } private: - // Degrees per second per unit of m_Speed + // Degrees per second per unit of behavior speed static constexpr float BASE_ANGULAR_SPEED = 15.0f; + struct RotationState { + // The rotation that degrees is applied on top of + NiQuaternion base = QuatUtils::IDENTITY; + + // Accumulated signed degrees per world axis since base was set + NiPoint3 degrees{}; + + // degrees at the moment the current rotation on each axis started + NiPoint3 actionStart{}; + + // Signed degrees per second per world axis. Non-zero means a behavior owns that axis. + NiPoint3 velocity{}; + }; + + struct MoveState { + // Signed units per second along local right, up and forward. Non-zero means a behavior owns that axis. + NiPoint3 velocity{}; + + // Right, up and forward in world space as of the last velocity update + std::array basis{ NiPoint3Constant::UNIT_X, NiPoint3Constant::UNIT_Y, NiPoint3Constant::UNIT_Z }; + + // Whether the last velocity update came from an active move + bool wasMoving{ false }; + }; + // Sends the client-side angular velocity for the currently active rotation axes. void SyncAngularVelocity() const; @@ -236,30 +257,10 @@ private: */ LWOOBJID m_userModelID; - // The speed at which this model moves - float m_Speed{ 3.0f }; - // Whether or not to restart at the end of the frame. bool m_RestartAtEndOfFrame{ false }; - // The rotation that m_RotationDegrees is applied on top of - NiQuaternion m_RotationBase = QuatUtils::IDENTITY; + RotationState m_Rotation; - // Accumulated signed degrees per world axis since m_RotationBase was set - NiPoint3 m_RotationDegrees{}; - - // m_RotationDegrees at the moment the current rotation on each axis started - NiPoint3 m_RotationActionStart{}; - - // Per axis -1, 0 or 1. Non-zero means a behavior currently owns rotation on that axis. - NiPoint3 m_RotationDirection{}; - - // Per local axis (right, up, forward) -1, 0 or 1. Non-zero means a behavior currently owns movement on that axis. - NiPoint3 m_MoveDirection{}; - - // Right, up and forward in world space as of the last velocity update - std::array m_MoveBasis{ NiPoint3Constant::UNIT_X, NiPoint3Constant::UNIT_Y, NiPoint3Constant::UNIT_Z }; - - // Whether the last velocity update came from an active move - bool m_WasMoving{ false }; + MoveState m_Move; }; diff --git a/dGame/dPropertyBehaviors/Strip.cpp b/dGame/dPropertyBehaviors/Strip.cpp index 1c7549195..bdb6577e6 100644 --- a/dGame/dPropertyBehaviors/Strip.cpp +++ b/dGame/dPropertyBehaviors/Strip.cpp @@ -109,6 +109,7 @@ void Strip::HandleMsg(GameMessages::ResetModelToDefaults& msg) { m_PreviousFramePosition = NiPoint3Constant::ZERO; m_InActionRotation = NiPoint3Constant::ZERO; m_RotationProgress = 0.0f; + m_Speed = DEFAULT_SPEED; } void Strip::OnChatMessageReceived(const std::string& sMessage) { @@ -176,21 +177,21 @@ void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent, Up if (nextActionType == "MoveRight" || nextActionType == "MoveLeft") { // Local right axis const bool isMoveLeft = nextActionType == "MoveLeft"; - if (modelComponent.TryStartMove(0, isMoveLeft ? -1.0f : 1.0f)) { + if (modelComponent.TryStartMove(0, isMoveLeft ? -1.0f : 1.0f, m_Speed)) { m_PreviousFramePosition = entity.GetPosition(); m_InActionTranslation.x = isMoveLeft ? -number : number; } } else if (nextActionType == "FlyUp" || nextActionType == "FlyDown") { // Local up axis const bool isFlyDown = nextActionType == "FlyDown"; - if (modelComponent.TryStartMove(1, isFlyDown ? -1.0f : 1.0f)) { + if (modelComponent.TryStartMove(1, isFlyDown ? -1.0f : 1.0f, m_Speed)) { m_PreviousFramePosition = entity.GetPosition(); m_InActionTranslation.y = isFlyDown ? -number : number; } } else if (nextActionType == "MoveForward" || nextActionType == "MoveBackward") { // Local forward axis const bool isMoveBackward = nextActionType == "MoveBackward"; - if (modelComponent.TryStartMove(2, isMoveBackward ? -1.0f : 1.0f)) { + if (modelComponent.TryStartMove(2, isMoveBackward ? -1.0f : 1.0f, m_Speed)) { m_PreviousFramePosition = entity.GetPosition(); m_InActionTranslation.z = isMoveBackward ? -number : number; } @@ -201,21 +202,21 @@ void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent, Up else if (nextActionType == "Spin" || nextActionType == "SpinNegative") { // Y axis const float direction = nextActionType == "SpinNegative" ? -1.0f : 1.0f; - if (number != 0.0 && modelComponent.TryStartRotation(1, direction)) { + if (number != 0.0 && modelComponent.TryStartRotation(1, direction, m_Speed)) { m_InActionRotation.y = direction * number; m_RotationProgress = 0.0f; } } else if (nextActionType == "Tilt" || nextActionType == "TiltNegative") { // X axis const float direction = nextActionType == "TiltNegative" ? -1.0f : 1.0f; - if (number != 0.0 && modelComponent.TryStartRotation(0, direction)) { + if (number != 0.0 && modelComponent.TryStartRotation(0, direction, m_Speed)) { m_InActionRotation.x = direction * number; m_RotationProgress = 0.0f; } } else if (nextActionType == "Roll" || nextActionType == "RollNegative") { // Z axis const float direction = nextActionType == "RollNegative" ? -1.0f : 1.0f; - if (number != 0.0 && modelComponent.TryStartRotation(2, direction)) { + if (number != 0.0 && modelComponent.TryStartRotation(2, direction, m_Speed)) { m_InActionRotation.z = direction * number; m_RotationProgress = 0.0f; } @@ -224,7 +225,8 @@ void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent, Up /* BEGIN Navigation */ else if (nextActionType == "SetSpeed") { - modelComponent.SetSpeed(number); + // Floored so a move or rotation can never stall forever + m_Speed = std::max(static_cast(number), MIN_SPEED); } /* END Navigation */ @@ -353,7 +355,7 @@ bool Strip::CheckRotation(float deltaTime, ModelComponent& modelComponent) { if (target == 0.0f) continue; // Snapping to the target keeps the final angle exact regardless of speed or frame time - const float step = modelComponent.GetAngularSpeed() * deltaTime; + const float step = modelComponent.GetAngularSpeed(axis) * deltaTime; if (std::abs(target - m_RotationProgress) <= step) m_RotationProgress = target; else m_RotationProgress += std::copysign(step, target); @@ -394,6 +396,7 @@ void Strip::Update(float deltaTime, ModelComponent& modelComponent, UpdateResult // Check for trigger blocks and if not a trigger block proc this blocks action if (m_NextActionIndex == 0) { LOG("Behavior strip started %s", nextAction.GetType().data()); + m_Speed = DEFAULT_SPEED; if (nextAction.GetType() == "OnInteract") { modelComponent.AddInteract(); } else if (nextAction.GetType() == "OnChat") { diff --git a/dGame/dPropertyBehaviors/Strip.h b/dGame/dPropertyBehaviors/Strip.h index 2904a3529..3f8a781ba 100644 --- a/dGame/dPropertyBehaviors/Strip.h +++ b/dGame/dPropertyBehaviors/Strip.h @@ -77,6 +77,12 @@ private: // The signed degrees the current rotation action has progressed so far float m_RotationProgress{ 0.0f }; + + static constexpr float DEFAULT_SPEED = 3.0f; + static constexpr float MIN_SPEED = 0.1f; + + // Speed applied to moves and rotations started by this strip + float m_Speed{ DEFAULT_SPEED }; }; #endif //!__STRIP__H__