Updated comments for functions

This commit is contained in:
LongLy 2024-11-27 10:54:20 -07:00
parent a2436e5c65
commit 4f3b97f455
2 changed files with 192 additions and 83 deletions

View File

@ -12,116 +12,156 @@ DEFINE_LOG_CATEGORY_STATIC(LogOLSLocomotionLibrary, Verbose, All);
float UOLSLocomotionBPLibrary::FindPivotTime(const UAnimSequenceBase* animSequence, const float sampleRate) float UOLSLocomotionBPLibrary::FindPivotTime(const UAnimSequenceBase* animSequence, const float sampleRate)
{ {
if (animSequence) if (animSequence)
{ {
const float animLength = animSequence->GetPlayLength(); const float animLength = animSequence->GetPlayLength(); // Get the total duration of the animation sequence.
const float sampleDeltaTime = 1 / sampleRate; const float sampleDeltaTime = 1 / sampleRate; // Calculate the time interval between each sample based on the sample rate.
float currentAnimTime = 0.f;
float lastTime = 0.f;
float nextTime = currentAnimTime + sampleDeltaTime;
FVector currentLocation = animSequence->ExtractRootMotionFromRange(currentAnimTime, nextTime).
GetTranslation().GetSafeNormal2D();
while (nextTime < animLength)
{
const FRotator currentRotation = animSequence->ExtractRootMotionFromRange(0.0f, currentAnimTime).
GetRotation().Rotator();
const FVector lastLocation = currentRotation.RotateVector(
animSequence->ExtractRootMotionFromRange(
currentAnimTime, nextTime).GetTranslation().GetSafeNormal2D());
if ((currentLocation.Dot(lastLocation) < 0 && currentLocation.SquaredLength() > 0) || (
FMath::IsNearlyZero(lastLocation.SquaredLength()) && currentLocation.SquaredLength() > 0))
{
return currentAnimTime;
}
if (FMath::IsNearlyZero(currentLocation.Length())) float currentAnimTime = 0.f; // Initialize the current animation time.
{ float lastTime = 0.f; // Store the last sampled time.
currentLocation = lastLocation; float nextTime = currentAnimTime + sampleDeltaTime; // Calculate the next time point for sampling.
}
lastTime = FMath::Clamp(lastTime + sampleDeltaTime, 0.0f, animLength); // Extract and normalize the initial root motion translation vector.
currentAnimTime = FMath::Clamp(currentAnimTime + sampleDeltaTime, 0.0f, animLength); FVector currentLocation = animSequence->ExtractRootMotionFromRange(currentAnimTime, nextTime)
nextTime = FMath::Clamp(nextTime + sampleDeltaTime, 0.0f, animLength); .GetTranslation().GetSafeNormal2D();
}
}
return 0.f; while (nextTime < animLength) // Loop through the animation until the end.
{
// Extract the current rotation based on the root motion from the start to the current time.
const FRotator currentRotation = animSequence->ExtractRootMotionFromRange(0.0f, currentAnimTime)
.GetRotation().Rotator();
// Apply the current rotation to the translation vector and normalize.
const FVector lastLocation = currentRotation.RotateVector(
animSequence->ExtractRootMotionFromRange(currentAnimTime, nextTime)
.GetTranslation().GetSafeNormal2D());
// Detect a pivot point if the dot product is negative (indicating a direction change).
if ((currentLocation.Dot(lastLocation) < 0 && currentLocation.SquaredLength() > 0) ||
(FMath::IsNearlyZero(lastLocation.SquaredLength()) && currentLocation.SquaredLength() > 0))
{
return currentAnimTime; // Return the detected pivot time.
}
// Handle the case where the current location length is nearly zero.
if (FMath::IsNearlyZero(currentLocation.Length()))
{
currentLocation = lastLocation; // Update the current location for the next iteration.
}
// Advance to the next sample time, clamping to ensure it doesn't exceed the animation length.
lastTime = FMath::Clamp(lastTime + sampleDeltaTime, 0.0f, animLength);
currentAnimTime = FMath::Clamp(currentAnimTime + sampleDeltaTime, 0.0f, animLength);
nextTime = FMath::Clamp(nextTime + sampleDeltaTime, 0.0f, animLength);
}
}
return 0.f; // Return 0 if no pivot is detected or if the input animation sequence is invalid.
} }
float UOLSLocomotionBPLibrary::GetCurveValueAtTime(const UAnimSequenceBase* animSequence, float UOLSLocomotionBPLibrary::GetCurveValueAtTime(const UAnimSequenceBase* animSequence,
const float time, const float time,
const FName& curveName) const FName& curveName)
{ {
// Initialize buffer access for the specified curve in the given animation sequence.
FAnimCurveBufferAccess bufferCurveAccess(animSequence, curveName); FAnimCurveBufferAccess bufferCurveAccess(animSequence, curveName);
// Validate that the curve data is accessible.
if (bufferCurveAccess.IsValid()) if (bufferCurveAccess.IsValid())
{ {
// Clamp the time to ensure it's within the valid range of the animation length.
const float clampedTime = FMath::Clamp(time, 0.f, animSequence->GetPlayLength()); const float clampedTime = FMath::Clamp(time, 0.f, animSequence->GetPlayLength());
// Ensure the animation has at least 3 sampled keys for evaluation (2 keys are needed for interpolation).
if (animSequence->GetNumberOfSampledKeys() > 2) if (animSequence->GetNumberOfSampledKeys() > 2)
{ {
// Evaluate the curve data at the specified time and return the result.
return animSequence->EvaluateCurveData(curveName, clampedTime); return animSequence->EvaluateCurveData(curveName, clampedTime);
} }
} }
// Return 0 if the curve is invalid or the animation has insufficient sampled keys.
return 0.f; return 0.f;
} }
float UOLSLocomotionBPLibrary::GetTimeAtDistance(const UAnimSequenceBase* animSequence, float UOLSLocomotionBPLibrary::GetTimeAtCurveValue(const UAnimSequenceBase* animSequence,
const float& distance, FName curveName) const float& curveValue, FName curveName)
{ {
FAnimCurveBufferAccess bufferCurveAccess(animSequence, curveName); // Initialize buffer access for the specified curve in the given animation sequence.
if (bufferCurveAccess.IsValid()) FAnimCurveBufferAccess bufferCurveAccess(animSequence, curveName);
{
const int32 numKeys = bufferCurveAccess.GetNumSamples();
if (numKeys < 2)
{
return 0.f;
}
int32 first = 1; // Validate the curve data.
int32 last = numKeys - 1; if (bufferCurveAccess.IsValid())
int32 count = last - first; {
const int32 numKeys = bufferCurveAccess.GetNumSamples(); // Retrieve the total number of keyframes/samples.
while (count > 0) // Ensure there are at least two keyframes for interpolation.
{ if (numKeys < 2)
int32 step = count / 2; {
int32 middle = first + step; return 0.f; // Return 0 if not enough data points.
}
if (distance > bufferCurveAccess.GetValue(middle)) // Initialize binary search variables.
{ int32 first = 1; // Start at the second keyframe.
first = middle + 1; int32 last = numKeys - 1; // Index of the last keyframe.
count -= step + 1; int32 count = last - first; // Number of keyframes to search through.
}
else
{
count = step;
}
}
const float keyAValue = bufferCurveAccess.GetValue(first - 1); // Perform a binary search to locate the interval containing the curve value.
const float keyBValue = bufferCurveAccess.GetValue(first); while (count > 0)
const float diff = keyBValue - keyAValue; {
const float alpha = !FMath::IsNearlyZero(diff) ? ((distance - keyAValue) / diff) : 0.f; int32 step = count / 2; // Calculate the midpoint step.
int32 middle = first + step; // Determine the middle keyframe.
const float keyATime = bufferCurveAccess.GetTime(first - 1); // Adjust the search range based on the target curve value.
const float keyBTime = bufferCurveAccess.GetTime(first); if (curveValue > bufferCurveAccess.GetValue(middle))
return FMath::Lerp(keyATime, keyBTime, alpha); {
} first = middle + 1; // Move the search to the right half.
count -= step + 1; // Update the remaining count.
}
else
{
count = step; // Narrow the search to the left half.
}
}
return 0.f; // Retrieve values at the keyframes surrounding the target value.
const float keyAValue = bufferCurveAccess.GetValue(first - 1);
const float keyBValue = bufferCurveAccess.GetValue(first);
const float diff = keyBValue - keyAValue; // Calculate the difference between the values.
// Calculate the interpolation factor (alpha) based on the target value.
const float alpha = !FMath::IsNearlyZero(diff) ? ((curveValue - keyAValue) / diff) : 0.f;
// Retrieve the corresponding times for the surrounding keyframes.
const float keyATime = bufferCurveAccess.GetTime(first - 1);
const float keyBTime = bufferCurveAccess.GetTime(first);
// Linearly interpolate between the keyframe times to estimate the target time.
return FMath::Lerp(keyATime, keyBTime, alpha);
}
return 0.f; // Return 0 if the curve is invalid or the target value is not found.
} }
float UOLSLocomotionBPLibrary::GetDistanceRange(const UAnimSequenceBase* animSequence, const FName& curveName) float UOLSLocomotionBPLibrary::GetCurveValuesRange(const UAnimSequenceBase* animSequence, const FName& curveName)
{ {
// Initialize a buffer to access the curve data within the specified animation sequence.
FAnimCurveBufferAccess bufferCurveAccess(animSequence, curveName); FAnimCurveBufferAccess bufferCurveAccess(animSequence, curveName);
// Check if the curve data is valid and accessible.
if (bufferCurveAccess.IsValid()) if (bufferCurveAccess.IsValid())
{ {
const int32 numSamples = bufferCurveAccess.GetNumSamples(); const int32 numSamples = bufferCurveAccess.GetNumSamples(); // Get the total number of samples in the curve.
// Ensure there are at least two samples to calculate a meaningful range.
if (numSamples >= 2) if (numSamples >= 2)
{ {
// Calculate the range by subtracting the first sample value from the last sample value.
return (bufferCurveAccess.GetValue(numSamples - 1) - bufferCurveAccess.GetValue(0)); return (bufferCurveAccess.GetValue(numSamples - 1) - bufferCurveAccess.GetValue(0));
} }
} }
return 0.f;
return 0.f; // Return 0 if the curve is invalid or does not have enough data points.
} }
float UOLSLocomotionBPLibrary::GetTimeAfterDistanceTraveled(const UAnimSequenceBase* animSequence, float UOLSLocomotionBPLibrary::GetTimeAfterDistanceTraveled(const UAnimSequenceBase* animSequence,
@ -133,7 +173,7 @@ float UOLSLocomotionBPLibrary::GetTimeAfterDistanceTraveled(const UAnimSequenceB
if (animSequence) if (animSequence)
{ {
// Avoid infinite loops if the animation doesn't cover any distance. // Avoid infinite loops if the animation doesn't cover any distance.
if (!FMath::IsNearlyZero(GetDistanceRange(animSequence, curveName))) if (!FMath::IsNearlyZero(GetCurveValuesRange(animSequence, curveName)))
{ {
float accumulatedDistance = 0.f; float accumulatedDistance = 0.f;
@ -285,7 +325,7 @@ FSequenceEvaluatorReference UOLSLocomotionBPLibrary::DistanceMatchToTarget(const
USequenceEvaluatorLibrary::GetAccumulatedTime(sequenceEvaluator), USequenceEvaluatorLibrary::GetAccumulatedTime(sequenceEvaluator),
curveName) > stopDistanceThreshHold && !shouldDistanceMatchStop) curveName) > stopDistanceThreshHold && !shouldDistanceMatchStop)
{ {
const float newTime = GetTimeAtDistance(animSequence, -distanceToTarget, curveName); const float newTime = GetTimeAtCurveValue(animSequence, -distanceToTarget, curveName);
if (!inSequenceEvaluator.SetExplicitTime(newTime)) if (!inSequenceEvaluator.SetExplicitTime(newTime))
{ {
UE_LOG(LogOLSLocomotionLibrary, Warning, UE_LOG(LogOLSLocomotionLibrary, Warning,

View File

@ -16,20 +16,76 @@ class OLSANIMATION_API UOLSLocomotionBPLibrary : public UBlueprintFunctionLibrar
{ {
GENERATED_BODY() GENERATED_BODY()
public: public: // ~ Helpers ~ //
UFUNCTION(BlueprintCallable, Category = "OLS|Function Library", meta=(BlueprintThreadSafe)) /**
* Finds the time within an animation sequence when a character's root motion changes direction, commonly known as a "pivot."
*
* @param animSequence Pointer to the animation sequence being analyzed. The function extracts root motion data from this sequence.
* @param sampleRate The frequency (in Hz) at which the animation is sampled. Higher values increase accuracy but may impact performance.
*
* @return The time (in seconds) within the animation sequence when a pivot occurs. Returns 0 if no pivot is detected or the input is invalid.
*
* @note This function is useful for identifying key moments in animations where directional changes occur,
* such as during character turns or sharp movements, ensuring smooth transitions or special handling.
*/
static float FindPivotTime(const UAnimSequenceBase* animSequence, const float sampleRate); static float FindPivotTime(const UAnimSequenceBase* animSequence, const float sampleRate);
UFUNCTION(BlueprintCallable, Category = "OLS|Function Library", meta=(BlueprintThreadSafe)) /**
* Retrieves the value of a specified curve at a given time within an animation sequence.
*
* @param animSequence Pointer to the animation sequence containing the curve data.
* @param time The time (in seconds) at which to evaluate the curve value.
* @param curveName The name of the curve to evaluate.
*
* @return The curve value at the specified time. Returns 0 if the curve is invalid or the time is out of range.
*
* @note The time is clamped to ensure it falls within the animation's playback range, and the function requires at least 2 sampled keys in the animation for curve evaluation.
*/
static float GetCurveValueAtTime(const UAnimSequenceBase* animSequence, const float time, const FName& curveName); static float GetCurveValueAtTime(const UAnimSequenceBase* animSequence, const float time, const FName& curveName);
UFUNCTION(BlueprintCallable, Category = "OLS|Function Library", meta=(BlueprintThreadSafe)) /**
static float GetTimeAtDistance(const UAnimSequenceBase* animSequence, const float& distance, FName curveName); * Retrieves the time within an animation sequence at which a specified curve reaches a given value.
*
* @param animSequence Pointer to the animation sequence containing the curve data.
* @param curveValue The target value to locate within the curve.
* @param curveName The name of the curve being evaluated.
*
* @return The interpolated time at which the curve reaches the specified value. Returns 0 if the curve is invalid or the value cannot be found.
*
* @note This function uses binary search to efficiently locate the curve value and linearly interpolates between keyframes for precision.
*/
static float GetTimeAtCurveValue(const UAnimSequenceBase* animSequence, const float& curveValue, FName curveName);
UFUNCTION(BlueprintCallable, Category = "OLS|Function Library", meta=(BlueprintThreadSafe)) /**
static float GetDistanceRange(const UAnimSequenceBase* animSequence, const FName& curveName); * Calculates the range of values for a specified animation curve within an animation sequence.
*
* @param animSequence Pointer to the animation sequence containing the curve. This sequence provides the curve data.
* @param curveName The name of the curve whose value range is to be calculated.
*
* @return The difference between the first and last values of the specified curve. Returns 0 if the curve is invalid or has insufficient data.
*
* @note This function is useful for determining the total change in a curve over the duration of an animation,
* which can be critical for understanding motion characteristics or driving procedural animations.
*/
static float GetCurveValuesRange(const UAnimSequenceBase* animSequence, const FName& curveName);
public:
/**
* Advances the animation time from the current position to a new time, ensuring the root motion covers the specified distance traveled.
*
* @param animSequence Pointer to the animation sequence being evaluated. Contains the root motion and curve data.
* @param currentTime The current time within the animation sequence, serving as the starting point for advancement.
* @param distanceTraveled The desired distance to advance within the animation, calculated based on the root motion curve.
* @param curveName The name of the curve representing root motion distance in the animation sequence.
* @param shouldAllowLooping Specifies whether the animation should loop if the new time exceeds the sequence length.
*
* @return The new animation time after advancing the desired distance along the root motion curve.
*
* @note This function ensures that the visual progression of the animation matches the actual distance traveled,
* which is particularly useful for root motion-based locomotion systems.
*/
UFUNCTION(BlueprintCallable, Category = "OLS|Function Library", meta=(BlueprintThreadSafe)) UFUNCTION(BlueprintCallable, Category = "OLS|Function Library", meta=(BlueprintThreadSafe))
static float GetTimeAfterDistanceTraveled(const UAnimSequenceBase* animSequence, static float GetTimeAfterDistanceTraveled(const UAnimSequenceBase* animSequence,
float currentTime, float currentTime,
@ -37,6 +93,19 @@ public:
FName curveName, FName curveName,
const bool shouldAllowLooping); const bool shouldAllowLooping);
/**
* Advances the animation time based on the distance traveled, adjusting the play rate to synchronize the animation with movement.
*
* @param outDesiredPlayRate Output parameter that will contain the calculated play rate needed to match the distance traveled.
* @param updateContext Provides context for the current animation update, including delta time and other relevant information.
* @param sequenceEvaluator Reference to the sequence evaluator managing the current animation sequence.
* @param distanceTraveled The distance covered since the last frame. This value determines how much the animation time should advance.
* @param curveName The name of the curve used for distance matching. This curve defines how the animation corresponds to distance traveled.
* @param playRateClamp Optional parameter defining the minimum and maximum play rates. Clamps the effective play rate to prevent unrealistic values.
* Default value: FVector2D(0.75f, 1.25f).
*
* @return The updated sequence evaluator with the new animation state.
*/
UFUNCTION(BlueprintCallable, Category = "OLS|Function Library", meta=(BlueprintThreadSafe)) UFUNCTION(BlueprintCallable, Category = "OLS|Function Library", meta=(BlueprintThreadSafe))
static FSequenceEvaluatorReference AdvanceTimeByDistanceMatching(float& outDesiredPlayRate, static FSequenceEvaluatorReference AdvanceTimeByDistanceMatching(float& outDesiredPlayRate,
const FAnimUpdateContext& updateContext, const FAnimUpdateContext& updateContext,