poggers rotation on the strip

This commit is contained in:
David Markowitz
2026-09-26 19:22:47 -07:00
parent 626f3b8b7d
commit 1c27c4957e
4 changed files with 78 additions and 78 deletions

View File

@@ -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();
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include <array>
#include <cmath>
#include <map>
#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<NiPoint3, 3> 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<NiPoint3, 3> 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;
};

View File

@@ -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<float>(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") {

View File

@@ -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__