234 lines
8.3 KiB
C++
234 lines
8.3 KiB
C++
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
#include "GMS_Math.h"
|
|
#include "GMS_Rotation.generated.h"
|
|
|
|
UCLASS(Meta = (BlueprintThreadSafe))
|
|
class GENERICMOVEMENTSYSTEM_API UGMS_Rotation : public UBlueprintFunctionLibrary
|
|
{
|
|
GENERATED_BODY()
|
|
|
|
public:
|
|
static constexpr auto CounterClockwiseRotationAngleThreshold{5.0f};
|
|
|
|
public:
|
|
template <typename ValueType> requires std::is_floating_point_v<ValueType>
|
|
static constexpr ValueType RemapAngleForCounterClockwiseRotation(ValueType Angle);
|
|
|
|
static VectorRegister4Double RemapRotationForCounterClockwiseRotation(const VectorRegister4Double& Rotation);
|
|
|
|
// Remaps the angle from the [175, 180] range to [-185, -180]. Used to
|
|
// make the character rotate counterclockwise during a 180 degree turn.
|
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Rotation Utility", Meta = (ReturnDisplayName = "Angle"))
|
|
static float RemapAngleForCounterClockwiseRotation(float Angle);
|
|
|
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Rotation Utility", Meta = (ReturnDisplayName = "Angle"))
|
|
static float LerpAngle(float From, float To, float Ratio);
|
|
|
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Rotation Utility", Meta = (AutoCreateRefTerm = "From, To", ReturnDisplayName = "Rotation"))
|
|
static FRotator LerpRotation(const FRotator& From, const FRotator& To, float Ratio);
|
|
|
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Rotation Utility", Meta = (ReturnDisplayName = "Angle"))
|
|
static float InterpolateAngleConstant(float Current, float Target, float DeltaTime, float Speed);
|
|
|
|
/**
|
|
* Smoothly lerp current to target in fame-rate stable way.
|
|
* 以帧率稳定方式平滑地从Current过渡到Target。
|
|
* @param HalfLife How fast to reach target.
|
|
*/
|
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Rotation Utility", Meta = (ReturnDisplayName = "Angle"))
|
|
static float DamperExactAngle(float Current, float Target, float DeltaTime, float HalfLife);
|
|
|
|
// HalfLife is the time it takes for the distance to the target to be reduced by half.
|
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Rotation Utility",
|
|
Meta = (AutoCreateRefTerm = "Current, Target", ReturnDisplayName = "Rotation"))
|
|
static FRotator DamperExactRotation(const FRotator& Current, const FRotator& Target, float DeltaTime, float HalfLife);
|
|
|
|
// Same as FMath::QInterpTo(), but uses FQuat::FastLerp() instead of FQuat::Slerp().
|
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Rotation Utility", Meta = (ReturnDisplayName = "Quaternion"))
|
|
static FQuat InterpolateQuaternionFast(const FQuat& Current, const FQuat& Target, float DeltaTime, float Speed);
|
|
|
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GMS|Rotation Utility", Meta = (AutoCreateRefTerm = "TwistAxis", ReturnDisplayName = "Twist"))
|
|
static FQuat GetTwist(const FQuat& Quaternion, const FVector& TwistAxis = FVector::UpVector);
|
|
};
|
|
|
|
template <typename ValueType> requires std::is_floating_point_v<ValueType>
|
|
constexpr ValueType UGMS_Rotation::RemapAngleForCounterClockwiseRotation(const ValueType Angle)
|
|
{
|
|
if (Angle > 180.0f - CounterClockwiseRotationAngleThreshold)
|
|
{
|
|
return Angle - 360.0f;
|
|
}
|
|
|
|
return Angle;
|
|
}
|
|
|
|
inline VectorRegister4Double UGMS_Rotation::RemapRotationForCounterClockwiseRotation(const VectorRegister4Double& Rotation)
|
|
{
|
|
static constexpr auto RemapThreshold{
|
|
MakeVectorRegisterDoubleConstant(180.0f - CounterClockwiseRotationAngleThreshold, 180.0f - CounterClockwiseRotationAngleThreshold,
|
|
180.0f - CounterClockwiseRotationAngleThreshold, 180.0f - CounterClockwiseRotationAngleThreshold)
|
|
};
|
|
|
|
static constexpr auto RemapAngles{MakeVectorRegisterDoubleConstant(360.0f, 360.0f, 360.0f, 0.0f)};
|
|
|
|
const auto ReverseRotationMask{VectorCompareGE(Rotation, RemapThreshold)};
|
|
|
|
const auto ReversedRotation{VectorSubtract(Rotation, RemapAngles)};
|
|
|
|
return VectorSelect(ReverseRotationMask, ReversedRotation, Rotation);
|
|
}
|
|
|
|
inline float UGMS_Rotation::RemapAngleForCounterClockwiseRotation(const float Angle)
|
|
{
|
|
return RemapAngleForCounterClockwiseRotation<float>(Angle);
|
|
}
|
|
|
|
inline float UGMS_Rotation::LerpAngle(const float From, const float To, const float Ratio)
|
|
{
|
|
auto Delta{FMath::UnwindDegrees(To - From)};
|
|
Delta = RemapAngleForCounterClockwiseRotation(Delta);
|
|
|
|
return FMath::UnwindDegrees(From + Delta * Ratio);
|
|
}
|
|
|
|
inline FRotator UGMS_Rotation::LerpRotation(const FRotator& From, const FRotator& To, const float Ratio)
|
|
{
|
|
#if PLATFORM_ENABLE_VECTORINTRINSICS
|
|
const auto FromRegister{VectorLoadFloat3_W0(&From)};
|
|
const auto ToRegister{VectorLoadFloat3_W0(&To)};
|
|
|
|
auto Delta{VectorSubtract(ToRegister, FromRegister)};
|
|
Delta = VectorNormalizeRotator(Delta);
|
|
|
|
if (!VectorAnyGreaterThan(VectorAbs(Delta), GlobalVectorConstants::DoubleKindaSmallNumber))
|
|
{
|
|
return To;
|
|
}
|
|
|
|
Delta = RemapRotationForCounterClockwiseRotation(Delta);
|
|
|
|
auto ResultRegister{VectorMultiplyAdd(Delta, VectorLoadFloat1(&Ratio), FromRegister)};
|
|
ResultRegister = VectorNormalizeRotator(ResultRegister);
|
|
|
|
FRotator Result;
|
|
VectorStoreFloat3(ResultRegister, &Result);
|
|
|
|
return Result;
|
|
#else
|
|
auto Result{To - From};
|
|
Result.Normalize();
|
|
|
|
Result.Pitch = RemapAngleForCounterClockwiseRotation(Result.Pitch);
|
|
Result.Yaw = RemapAngleForCounterClockwiseRotation(Result.Yaw);
|
|
Result.Roll = RemapAngleForCounterClockwiseRotation(Result.Roll);
|
|
|
|
Result *= Ratio;
|
|
Result += From;
|
|
Result.Normalize();
|
|
|
|
return Result;
|
|
#endif
|
|
}
|
|
|
|
inline float UGMS_Rotation::InterpolateAngleConstant(const float Current, const float Target, const float DeltaTime, const float Speed)
|
|
{
|
|
auto Delta{FMath::UnwindDegrees(Target - Current)};
|
|
const auto MaxDelta{Speed * DeltaTime};
|
|
|
|
if (Speed <= 0.0f || FMath::Abs(Delta) <= MaxDelta)
|
|
{
|
|
return Target;
|
|
}
|
|
|
|
Delta = RemapAngleForCounterClockwiseRotation(Delta);
|
|
return FMath::UnwindDegrees(Current + FMath::Sign(Delta) * MaxDelta);
|
|
}
|
|
|
|
inline float UGMS_Rotation::DamperExactAngle(const float Current, const float Target, const float DeltaTime, const float HalfLife)
|
|
{
|
|
auto Delta{FMath::UnwindDegrees(Target - Current)};
|
|
|
|
if (FMath::IsNearlyZero(Delta, UE_KINDA_SMALL_NUMBER))
|
|
{
|
|
return Target;
|
|
}
|
|
|
|
Delta = RemapAngleForCounterClockwiseRotation(Delta);
|
|
|
|
const auto Alpha{UGMS_Math::DamperExactAlpha(DeltaTime, HalfLife)};
|
|
return FMath::UnwindDegrees(Current + Delta * Alpha);
|
|
}
|
|
|
|
inline FRotator UGMS_Rotation::DamperExactRotation(const FRotator& Current, const FRotator& Target,
|
|
const float DeltaTime, const float HalfLife)
|
|
{
|
|
#if PLATFORM_ENABLE_VECTORINTRINSICS
|
|
const auto CurrentRegister{VectorLoadFloat3_W0(&Current)};
|
|
const auto TargetRegister{VectorLoadFloat3_W0(&Target)};
|
|
|
|
auto Delta{VectorSubtract(TargetRegister, CurrentRegister)};
|
|
Delta = VectorNormalizeRotator(Delta);
|
|
|
|
if (!VectorAnyGreaterThan(VectorAbs(Delta), GlobalVectorConstants::DoubleKindaSmallNumber))
|
|
{
|
|
return Target;
|
|
}
|
|
|
|
Delta = RemapRotationForCounterClockwiseRotation(Delta);
|
|
|
|
const double Alpha{UGMS_Math::DamperExactAlpha(DeltaTime, HalfLife)};
|
|
|
|
auto ResultRegister{VectorMultiplyAdd(Delta, VectorLoadDouble1(&Alpha), CurrentRegister)};
|
|
ResultRegister = VectorNormalizeRotator(ResultRegister);
|
|
|
|
FRotator Result;
|
|
VectorStoreFloat3(ResultRegister, &Result);
|
|
|
|
return Result;
|
|
#else
|
|
auto Result{Target - Current};
|
|
Result.Normalize();
|
|
|
|
if (FMath::IsNearlyZero(Result.Pitch, UE_KINDA_SMALL_NUMBER) &&
|
|
FMath::IsNearlyZero(Result.Yaw, UE_KINDA_SMALL_NUMBER) &&
|
|
FMath::IsNearlyZero(Result.Roll, UE_KINDA_SMALL_NUMBER))
|
|
{
|
|
return Target;
|
|
}
|
|
|
|
Result.Pitch = RemapAngleForCounterClockwiseRotation(Result.Pitch);
|
|
Result.Yaw = RemapAngleForCounterClockwiseRotation(Result.Yaw);
|
|
Result.Roll = RemapAngleForCounterClockwiseRotation(Result.Roll);
|
|
|
|
const auto Alpha{UGMS_Math::DamperExactAlpha(DeltaTime, HalfLife)};
|
|
|
|
Result *= Alpha;
|
|
Result += Current;
|
|
Result.Normalize();
|
|
|
|
return Result;
|
|
#endif
|
|
}
|
|
|
|
inline FQuat UGMS_Rotation::InterpolateQuaternionFast(const FQuat& Current, const FQuat& Target, const float DeltaTime, const float Speed)
|
|
{
|
|
if (Speed <= 0.0f || Current.Equals(Target))
|
|
{
|
|
return Target;
|
|
}
|
|
|
|
return FQuat::FastLerp(Current, Target, UGMS_Math::Clamp01(Speed * DeltaTime)).GetNormalized();
|
|
}
|
|
|
|
inline FQuat UGMS_Rotation::GetTwist(const FQuat& Quaternion, const FVector& TwistAxis)
|
|
{
|
|
// Based on TQuat<T>::ToSwingTwist().
|
|
|
|
const auto Projection{(TwistAxis | FVector{Quaternion.X, Quaternion.Y, Quaternion.Z}) * TwistAxis};
|
|
|
|
return FQuat{Projection.X, Projection.Y, Projection.Z, Quaternion.W}.GetNormalized();
|
|
}
|