You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
A specially designated set of attribute curves can be authored on a bone
to encode timecode metadata for an animation sequence. Each attribute
encodes a different component of a complete timecode and subframe value
("TCHour", "TCMinute", "TCSecond", "TCFrame", and "TCSubframe"). The
convention is to author these attribute curves on the root bone of the animation.
This adds a function for evaluating those attributes at a particular time, and the result
is returned as a qualified frame time. If the anim sequence has an import file frame rate
specified, then that rate is used for the returned qualified frame time to more closely
match the original source of the animation. Otherwise, the sampling frame rate of the
anim sequence is used. If the root bone does not have any timecode attributes or they
otherwise cannot be evaluated (for example, because they have no keys), the function
returns false, and the qualified frame time passed by reference is unmodified.
Finally, this new function is used for the frame time hint of skeletal animation tracks in
Sequencer. Anim sequences that have these attributes authored will show the authored
timecode/frame values in the frame time hint when they are selected. This enables more
easily correlating a point in time in the anim sequence with where it came from in an
external DCC.
#rb benoit.deschenes, max.chen
#preflight 619d6f48aa4521f9e7a4e183
#ROBOMERGE-AUTHOR: matt.johnson
#ROBOMERGE-SOURCE: CL 18316998 in //UE5/Release-5.0/... via CL 18317391
#ROBOMERGE-BOT: STARSHIP (Release-Engine-Staging -> Release-Engine-Test) (v895-18170469)
[CL 18317981 by matt johnson in ue5-release-engine-test branch]
2462 lines
84 KiB
C++
2462 lines
84 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "AnimationBlueprintLibrary.h"
|
|
|
|
#include "Animation/AnimSequence.h"
|
|
#include "Animation/AnimationAsset.h"
|
|
#include "Animation/AnimBoneCompressionSettings.h"
|
|
#include "Animation/AnimCurveCompressionSettings.h"
|
|
#include "Animation/AnimMetaData.h"
|
|
#include "Animation/AnimNotifies/AnimNotifyState.h"
|
|
#include "Animation/BuiltInAttributeTypes.h"
|
|
#include "Animation/Skeleton.h"
|
|
#include "Animation/AnimNotifies/AnimNotify.h"
|
|
#include "Animation/AnimCurveTypes.h"
|
|
#include "Animation/AnimationSettings.h"
|
|
#include "BonePose.h"
|
|
#include "Algo/Transform.h"
|
|
#include "Misc/FrameRate.h"
|
|
#include "Misc/FrameTime.h"
|
|
#include "Misc/QualifiedFrameTime.h"
|
|
#include "Misc/Timecode.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "Modules/ModuleInterface.h"
|
|
|
|
#include "AnimationRuntime.h"
|
|
#include "Animation/AnimData/AnimDataModel.h"
|
|
#include "Animation/AnimSequenceHelpers.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "AnimationBlueprintLibrary"
|
|
|
|
|
|
IMPLEMENT_MODULE(IModuleInterface, AnimationBlueprintLibrary);
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogAnimationBlueprintLibrary, Verbose, All);
|
|
|
|
void UAnimationBlueprintLibrary::GetNumFrames(const UAnimSequenceBase* AnimationSequenceBase, int32& NumFrames)
|
|
{
|
|
NumFrames = 0;
|
|
if (AnimationSequenceBase)
|
|
{
|
|
NumFrames = AnimationSequenceBase->GetNumberOfSampledKeys() - 1;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetNumFrames"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetNumKeys(const UAnimSequenceBase* AnimationSequenceBase, int32& NumKeys)
|
|
{
|
|
NumKeys = 0;
|
|
if (AnimationSequenceBase)
|
|
{
|
|
NumKeys = AnimationSequenceBase->GetNumberOfSampledKeys();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetNumKeys"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetAnimationTrackNames(const UAnimSequenceBase* AnimationSequenceBase, TArray<FName>& TrackNames)
|
|
{
|
|
TrackNames.Empty();
|
|
if (AnimationSequenceBase)
|
|
{
|
|
AnimationSequenceBase->GetDataModel()->GetBoneTrackNames(TrackNames);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetBoneTrackNames"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetAnimationCurveNames(const UAnimSequence* AnimationSequence, ERawCurveTrackTypes CurveType, TArray<FName>& CurveNames)
|
|
{
|
|
CurveNames.Empty();
|
|
if (AnimationSequence)
|
|
{
|
|
auto GetCurveName = [](const auto& Curve) -> FName
|
|
{
|
|
return Curve.Name.DisplayName;
|
|
};
|
|
|
|
switch (CurveType)
|
|
{
|
|
case ERawCurveTrackTypes::RCT_Float:
|
|
{
|
|
Algo::Transform(AnimationSequence->GetDataModel()->GetFloatCurves(), CurveNames, GetCurveName);
|
|
break;
|
|
}
|
|
|
|
case ERawCurveTrackTypes::RCT_Transform:
|
|
{
|
|
Algo::Transform(AnimationSequence->GetDataModel()->GetTransformCurves(), CurveNames, GetCurveName);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid CurveType supplied for GetCurveNames"));
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetCurveNames"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetRawTrackPositionData(const UAnimSequenceBase* AnimationSequenceBase, const FName TrackName, TArray<FVector>& PositionData)
|
|
{
|
|
PositionData.Empty();
|
|
if (IsValidRawAnimationTrackName(AnimationSequenceBase, TrackName))
|
|
{
|
|
const FRawAnimSequenceTrack& RawTrack = GetRawAnimationTrackByName(AnimationSequenceBase, TrackName);
|
|
PositionData.Append(RawTrack.PosKeys);
|
|
}
|
|
else
|
|
{
|
|
const FString AnimSequenceName = (AnimationSequenceBase != nullptr) ? AnimationSequenceBase->GetName() : "Invalid Animation sequence";
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Raw Animation Track name %s does not exist in Animation Sequence %s"), *TrackName.ToString(), *AnimSequenceName );
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetRawTrackRotationData(const UAnimSequenceBase* AnimationSequenceBase, const FName TrackName, TArray<FQuat>& RotationData)
|
|
{
|
|
RotationData.Empty();
|
|
if (IsValidRawAnimationTrackName(AnimationSequenceBase, TrackName))
|
|
{
|
|
const FRawAnimSequenceTrack& RawTrack = GetRawAnimationTrackByName(AnimationSequenceBase, TrackName);
|
|
RotationData.Append(RawTrack.RotKeys);
|
|
}
|
|
else
|
|
{
|
|
const FString AnimSequenceName = (AnimationSequenceBase != nullptr) ? AnimationSequenceBase->GetName() : "Invalid Animation sequence";
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Raw Animation Track name %s does not exist in Animation Sequence %s"), *TrackName.ToString(), *AnimSequenceName);
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetRawTrackScaleData(const UAnimSequenceBase* AnimationSequenceBase, const FName TrackName, TArray<FVector>& ScaleData)
|
|
{
|
|
ScaleData.Empty();
|
|
if (IsValidRawAnimationTrackName(AnimationSequenceBase, TrackName))
|
|
{
|
|
const FRawAnimSequenceTrack& RawTrack = GetRawAnimationTrackByName(AnimationSequenceBase, TrackName);
|
|
ScaleData.Append(RawTrack.ScaleKeys);
|
|
}
|
|
else
|
|
{
|
|
const FString AnimSequenceName = (AnimationSequenceBase != nullptr) ? AnimationSequenceBase->GetName() : "Invalid Animation sequence";
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Raw Animation Track name %s does not exist in Animation Sequence %s"), *TrackName.ToString(), *AnimSequenceName);
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetRawTrackData(const UAnimSequenceBase* AnimationSequenceBase, const FName TrackName, TArray<FVector>& PositionKeys, TArray<FQuat>& RotationKeys, TArray<FVector>& ScalingKeys)
|
|
{
|
|
PositionKeys.Empty();
|
|
RotationKeys.Empty();
|
|
ScalingKeys.Empty();
|
|
if (IsValidRawAnimationTrackName(AnimationSequenceBase, TrackName))
|
|
{
|
|
const FRawAnimSequenceTrack& RawTrack = GetRawAnimationTrackByName(AnimationSequenceBase, TrackName);
|
|
PositionKeys.Append(RawTrack.PosKeys);
|
|
RotationKeys.Append(RawTrack.RotKeys);
|
|
ScalingKeys.Append(RawTrack.ScaleKeys);
|
|
}
|
|
else
|
|
{
|
|
const FString AnimSequenceName = (AnimationSequenceBase != nullptr) ? AnimationSequenceBase->GetName() : "Invalid Animation sequence";
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Raw Animation Track name %s does not exist in Animation Sequence %s"), *TrackName.ToString(), *AnimSequenceName);
|
|
}
|
|
}
|
|
|
|
bool UAnimationBlueprintLibrary::IsValidRawAnimationTrackName(const UAnimSequenceBase* AnimationSequenceBase, const FName TrackName)
|
|
{
|
|
bool bValidName = false;
|
|
|
|
if (AnimationSequenceBase)
|
|
{
|
|
const int32 TrackIndex = AnimationSequenceBase->GetDataModel()->GetBoneTrackIndexByName(TrackName);
|
|
bValidName = (TrackIndex != INDEX_NONE);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for IsValidRawAnimationTrackName"));
|
|
}
|
|
|
|
return bValidName;
|
|
}
|
|
|
|
const FRawAnimSequenceTrack& UAnimationBlueprintLibrary::GetRawAnimationTrackByName(const UAnimSequenceBase* AnimationSequenceBase, const FName TrackName)
|
|
{
|
|
checkf(AnimationSequenceBase, TEXT("Invalid Animation Sequence supplied for GetRawAnimationTrackByName"));
|
|
|
|
const FBoneAnimationTrack& AnimationTrack = AnimationSequenceBase->GetDataModel()->GetBoneTrackByName(TrackName);
|
|
return AnimationTrack.InternalTrackData;
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetBoneCompressionSettings(const UAnimSequence* AnimationSequence, UAnimBoneCompressionSettings*& CompressionSettings)
|
|
{
|
|
if (AnimationSequence == nullptr)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetBoneCompressionSettings"));
|
|
return;
|
|
}
|
|
|
|
CompressionSettings = AnimationSequence->BoneCompressionSettings;
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::SetBoneCompressionSettings(UAnimSequence* AnimationSequence, UAnimBoneCompressionSettings* CompressionSettings)
|
|
{
|
|
if (AnimationSequence == nullptr)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for SetBoneCompressionSettings"));
|
|
return;
|
|
}
|
|
|
|
if (CompressionSettings == nullptr || !CompressionSettings->AreSettingsValid())
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Bone Compression Settings supplied for SetBoneCompressionSettings"));
|
|
return;
|
|
}
|
|
|
|
AnimationSequence->BoneCompressionSettings = CompressionSettings;
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetCurveCompressionSettings(const UAnimSequence* AnimationSequence, UAnimCurveCompressionSettings*& CompressionSettings)
|
|
{
|
|
if (AnimationSequence == nullptr)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetCurveCompressionSettings"));
|
|
return;
|
|
}
|
|
|
|
CompressionSettings = AnimationSequence->CurveCompressionSettings;
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::SetCurveCompressionSettings(UAnimSequence* AnimationSequence, UAnimCurveCompressionSettings* CompressionSettings)
|
|
{
|
|
if (AnimationSequence == nullptr)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for SetCurveCompressionSettings"));
|
|
return;
|
|
}
|
|
|
|
if (CompressionSettings == nullptr || !CompressionSettings->AreSettingsValid())
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Bone Compression Settings supplied for SetCurveCompressionSettings"));
|
|
return;
|
|
}
|
|
|
|
AnimationSequence->CurveCompressionSettings = CompressionSettings;
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetAdditiveAnimationType(const UAnimSequence* AnimationSequence, TEnumAsByte<enum EAdditiveAnimationType>& AdditiveAnimationType)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
AdditiveAnimationType = AnimationSequence->AdditiveAnimType;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetAdditiveAnimationType"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::SetAdditiveAnimationType(UAnimSequence* AnimationSequence, const TEnumAsByte<enum EAdditiveAnimationType> AdditiveAnimationType)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
AnimationSequence->AdditiveAnimType = AdditiveAnimationType;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for SetAdditiveAnimationType"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetAdditiveBasePoseType(const UAnimSequence* AnimationSequence, TEnumAsByte<enum EAdditiveBasePoseType>& AdditiveBasePoseType)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
AdditiveBasePoseType = AnimationSequence->RefPoseType;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetAdditiveBasePoseType"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::SetAdditiveBasePoseType(UAnimSequence* AnimationSequence, const TEnumAsByte<enum EAdditiveBasePoseType> AdditiveBasePoseType)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
AnimationSequence->RefPoseType = AdditiveBasePoseType;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for SetAdditiveBasePoseType"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetAnimationInterpolationType(const UAnimSequence* AnimationSequence, EAnimInterpolationType& InterpolationType)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
InterpolationType = AnimationSequence->Interpolation;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetAnimationInterpolationType"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::SetAnimationInterpolationType(UAnimSequence* AnimationSequence, EAnimInterpolationType Type)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
AnimationSequence->Interpolation = Type;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for SetAnimationInterpolationType"));
|
|
}
|
|
}
|
|
|
|
bool UAnimationBlueprintLibrary::IsRootMotionEnabled(const UAnimSequence* AnimationSequence)
|
|
{
|
|
bool bEnabled = false;
|
|
if (AnimationSequence)
|
|
{
|
|
bEnabled = AnimationSequence->bEnableRootMotion;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for IsRootMotionEnabled"));
|
|
}
|
|
|
|
return bEnabled;
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::SetRootMotionEnabled(UAnimSequence* AnimationSequence, bool bEnabled)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
AnimationSequence->bEnableRootMotion = bEnabled;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for SetRootMotionEnabled"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetRootMotionLockType(const UAnimSequence* AnimationSequence, TEnumAsByte<ERootMotionRootLock::Type>& LockType)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
LockType = AnimationSequence->RootMotionRootLock;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for GetRootMotionLockType"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::SetRootMotionLockType(UAnimSequence* AnimationSequence, TEnumAsByte<ERootMotionRootLock::Type> RootMotionLockType)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
AnimationSequence->RootMotionRootLock = RootMotionLockType;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for SetRootMotionLockType"));
|
|
}
|
|
}
|
|
|
|
bool UAnimationBlueprintLibrary::IsRootMotionLockForced(const UAnimSequence* AnimationSequence)
|
|
{
|
|
bool bIsLocked = false;
|
|
if (AnimationSequence)
|
|
{
|
|
bIsLocked = AnimationSequence->bForceRootLock;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for IsRootMotionLockForced"));
|
|
}
|
|
|
|
return bIsLocked;
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::SetIsRootMotionLockForced(UAnimSequence* AnimationSequence, bool bForced)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
AnimationSequence->bForceRootLock = bForced;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for SetIsRootMotionLockForced"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetAnimationSyncMarkers(const UAnimSequence* AnimationSequence, TArray<FAnimSyncMarker>& Markers)
|
|
{
|
|
Markers.Empty();
|
|
if (AnimationSequence)
|
|
{
|
|
Markers = AnimationSequence->AuthoredSyncMarkers;;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for GetAnimationSyncMarkers"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetUniqueMarkerNames(const UAnimSequence* AnimationSequence, TArray<FName>& MarkerNames)
|
|
{
|
|
MarkerNames.Empty();
|
|
if (AnimationSequence)
|
|
{
|
|
MarkerNames = AnimationSequence->UniqueMarkerNames;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for GetUniqueMarkerNames"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::AddAnimationSyncMarker(UAnimSequence* AnimationSequence, FName MarkerName, float Time, FName TrackName)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
const bool bIsValidTrackName = IsValidAnimNotifyTrackName(AnimationSequence, TrackName);
|
|
const bool bIsValidTime = IsValidTimeInternal(AnimationSequence, Time);
|
|
|
|
if (bIsValidTrackName && bIsValidTime)
|
|
{
|
|
FAnimSyncMarker NewMarker;
|
|
NewMarker.MarkerName = MarkerName;
|
|
NewMarker.Time = Time;
|
|
NewMarker.TrackIndex = GetTrackIndexForAnimationNotifyTrackName(AnimationSequence, TrackName);
|
|
NewMarker.Guid = FGuid::NewGuid();
|
|
|
|
AnimationSequence->AuthoredSyncMarkers.Add(NewMarker);
|
|
AnimationSequence->AnimNotifyTracks[NewMarker.TrackIndex].SyncMarkers.Add(&AnimationSequence->AuthoredSyncMarkers.Last());
|
|
|
|
AnimationSequence->RefreshSyncMarkerDataFromAuthored();
|
|
|
|
// Refresh all cached data
|
|
AnimationSequence->RefreshCacheData();
|
|
}
|
|
else
|
|
{
|
|
if (!bIsValidTrackName)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Animation Notify Track %s does not exist in Animation Sequence %s"), *TrackName.ToString(), *AnimationSequence->GetName());
|
|
}
|
|
|
|
if (!bIsValidTime)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("%f is side of Animation Sequence %s range"), Time, *AnimationSequence->GetName());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for AddAnimationSyncMarker"));
|
|
}
|
|
|
|
}
|
|
|
|
bool UAnimationBlueprintLibrary::IsValidAnimationSyncMarkerName(const UAnimSequence* AnimationSequence, FName MarkerName)
|
|
{
|
|
bool bIsValid = false;
|
|
if (AnimationSequence)
|
|
{
|
|
bIsValid = AnimationSequence->UniqueMarkerNames.Contains(MarkerName);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for IsValidAnimationSyncMarkerName"));
|
|
}
|
|
|
|
return bIsValid;
|
|
}
|
|
|
|
int32 UAnimationBlueprintLibrary::RemoveAnimationSyncMarkersByName(UAnimSequence* AnimationSequence, FName MarkerName)
|
|
{
|
|
int32 NumRemovedMarkers = 0;
|
|
if (AnimationSequence)
|
|
{
|
|
NumRemovedMarkers = AnimationSequence->AuthoredSyncMarkers.RemoveAll(
|
|
[&](const FAnimSyncMarker& Marker)
|
|
{
|
|
return Marker.MarkerName == MarkerName;
|
|
});
|
|
|
|
AnimationSequence->RefreshSyncMarkerDataFromAuthored();
|
|
|
|
// Refresh all cached data
|
|
AnimationSequence->RefreshCacheData();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for RemoveAnimationSyncMarkersByName"));
|
|
}
|
|
|
|
return NumRemovedMarkers;
|
|
}
|
|
|
|
int32 UAnimationBlueprintLibrary::RemoveAnimationSyncMarkersByTrack(UAnimSequence* AnimationSequence, FName NotifyTrackName)
|
|
{
|
|
int32 NumRemovedMarkers = 0;
|
|
if (AnimationSequence)
|
|
{
|
|
const int32 TrackIndex = GetTrackIndexForAnimationNotifyTrackName(AnimationSequence, NotifyTrackName);
|
|
if (TrackIndex != INDEX_NONE)
|
|
{
|
|
NumRemovedMarkers = AnimationSequence->AuthoredSyncMarkers.RemoveAll(
|
|
[&](const FAnimSyncMarker& Marker)
|
|
{
|
|
return Marker.TrackIndex == TrackIndex;
|
|
});
|
|
|
|
AnimationSequence->RefreshSyncMarkerDataFromAuthored();
|
|
|
|
// Refresh all cached data
|
|
AnimationSequence->RefreshCacheData();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Animation Notify Track %s does not exist on Animation Sequence %s"), *NotifyTrackName.ToString(), *AnimationSequence->GetName());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for RemoveAnimationSyncMarkersByTrack"));
|
|
}
|
|
|
|
return NumRemovedMarkers;
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::RemoveAllAnimationSyncMarkers(UAnimSequence* AnimationSequence)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
AnimationSequence->AuthoredSyncMarkers.Empty();
|
|
AnimationSequence->RefreshSyncMarkerDataFromAuthored();
|
|
|
|
// Refresh all cached data
|
|
AnimationSequence->RefreshCacheData();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for RemoveAllAnimationSyncMarkers"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetAnimationNotifyEvents(const UAnimSequenceBase* AnimationSequenceBase, TArray<FAnimNotifyEvent>& NotifyEvents)
|
|
{
|
|
NotifyEvents.Empty();
|
|
if (AnimationSequenceBase)
|
|
{
|
|
NotifyEvents = AnimationSequenceBase->Notifies;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for GetAnimationNotifyEvents"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetAnimationNotifyEventNames(const UAnimSequenceBase* AnimationSequenceBase, TArray<FName>& EventNames)
|
|
{
|
|
EventNames.Empty();
|
|
if (AnimationSequenceBase)
|
|
{
|
|
for (const FAnimNotifyEvent& Event : AnimationSequenceBase->Notifies)
|
|
{
|
|
EventNames.AddUnique(Event.NotifyName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for GetAnimationNotifyEventNames"));
|
|
}
|
|
}
|
|
|
|
UAnimNotify* UAnimationBlueprintLibrary::AddAnimationNotifyEvent(UAnimSequenceBase* AnimationSequenceBase, FName NotifyTrackName, float StartTime, TSubclassOf<UAnimNotify> NotifyClass)
|
|
{
|
|
UAnimNotify* Notify = nullptr;
|
|
if (AnimationSequenceBase)
|
|
{
|
|
const bool bIsValidTrackName = IsValidAnimNotifyTrackName(AnimationSequenceBase, NotifyTrackName);
|
|
const bool bIsValidTime = IsValidTimeInternal(AnimationSequenceBase, StartTime);
|
|
|
|
if (bIsValidTrackName && bIsValidTime)
|
|
{
|
|
FAnimNotifyEvent& NewEvent = AnimationSequenceBase->Notifies.AddDefaulted_GetRef();
|
|
|
|
NewEvent.NotifyName = NAME_None;
|
|
NewEvent.Link(AnimationSequenceBase, StartTime);
|
|
NewEvent.TriggerTimeOffset = GetTriggerTimeOffsetForType(AnimationSequenceBase->CalculateOffsetForNotify(StartTime));
|
|
NewEvent.TrackIndex = GetTrackIndexForAnimationNotifyTrackName(AnimationSequenceBase, NotifyTrackName);
|
|
NewEvent.NotifyStateClass = nullptr;
|
|
NewEvent.Guid = FGuid::NewGuid();
|
|
|
|
if (NotifyClass)
|
|
{
|
|
Notify = NewObject<UAnimNotify>(AnimationSequenceBase, NotifyClass, NAME_None, RF_Transactional);
|
|
NewEvent.Notify = Notify;
|
|
|
|
// Setup name for new event
|
|
if(NewEvent.Notify)
|
|
{
|
|
NewEvent.NotifyName = FName(*NewEvent.Notify->GetNotifyName());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NewEvent.Notify = nullptr;
|
|
}
|
|
|
|
// Refresh all cached data
|
|
AnimationSequenceBase->RefreshCacheData();
|
|
}
|
|
else
|
|
{
|
|
if (!bIsValidTrackName)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Animation Notify Track %s does not exist on Animation Sequence %s"), *NotifyTrackName.ToString(), *AnimationSequenceBase->GetName());
|
|
}
|
|
|
|
if (!bIsValidTime)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("%f is side of Animation Sequence %s range"), StartTime, *AnimationSequenceBase->GetName());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for AddAnimationNotifyEvent"));
|
|
}
|
|
|
|
return Notify;
|
|
}
|
|
|
|
UAnimNotifyState* UAnimationBlueprintLibrary::AddAnimationNotifyStateEvent(UAnimSequenceBase* AnimationSequenceBase, FName NotifyTrackName, float StartTime, float Duration, TSubclassOf<UAnimNotifyState> NotifyStateClass)
|
|
{
|
|
UAnimNotifyState* NotifyState = nullptr;
|
|
if (AnimationSequenceBase)
|
|
{
|
|
const bool bIsValidTrackName = IsValidAnimNotifyTrackName(AnimationSequenceBase, NotifyTrackName);
|
|
const bool bIsValidTime = IsValidTimeInternal(AnimationSequenceBase, StartTime);
|
|
|
|
if (bIsValidTrackName && bIsValidTime)
|
|
{
|
|
FAnimNotifyEvent& NewEvent = AnimationSequenceBase->Notifies.AddDefaulted_GetRef();
|
|
|
|
NewEvent.NotifyName = NAME_None;
|
|
NewEvent.Link(AnimationSequenceBase, StartTime);
|
|
NewEvent.TriggerTimeOffset = GetTriggerTimeOffsetForType(AnimationSequenceBase->CalculateOffsetForNotify(StartTime));
|
|
NewEvent.TrackIndex = GetTrackIndexForAnimationNotifyTrackName(AnimationSequenceBase, NotifyTrackName);
|
|
NewEvent.Notify = nullptr;
|
|
NewEvent.Guid = FGuid::NewGuid();
|
|
|
|
if (NotifyStateClass)
|
|
{
|
|
NotifyState = NewObject<UAnimNotifyState>(AnimationSequenceBase, NotifyStateClass, NAME_None, RF_Transactional);
|
|
NewEvent.NotifyStateClass = NotifyState;
|
|
|
|
// Setup name and duration for new event
|
|
if (NewEvent.NotifyStateClass)
|
|
{
|
|
NewEvent.NotifyName = FName(*NewEvent.NotifyStateClass->GetNotifyName());
|
|
NewEvent.SetDuration(Duration);
|
|
NewEvent.EndLink.Link(AnimationSequenceBase, NewEvent.EndLink.GetTime());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NewEvent.NotifyStateClass = nullptr;
|
|
}
|
|
|
|
// Refresh all cached data
|
|
AnimationSequenceBase->RefreshCacheData();
|
|
}
|
|
else
|
|
{
|
|
if (!bIsValidTrackName)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Animation Notify Track %s does not exist on Animation Sequence %s"), *NotifyTrackName.ToString(), *AnimationSequenceBase->GetName());
|
|
}
|
|
|
|
if (!bIsValidTime)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("%f is side of Animation Sequence %s range"), StartTime, *AnimationSequenceBase->GetName());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for AddAnimationNotifyStateEvent"));
|
|
}
|
|
|
|
return NotifyState;
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::AddAnimationNotifyEventObject(UAnimSequenceBase* AnimationSequenceBase, float StartTime, UAnimNotify* Notify, FName NotifyTrackName)
|
|
{
|
|
if (AnimationSequenceBase)
|
|
{
|
|
const bool bValidNotify = Notify != nullptr;
|
|
const bool bValidOuter = bValidNotify && Notify->GetOuter() == AnimationSequenceBase;
|
|
const bool bIsValidTrackName = IsValidAnimNotifyTrackName(AnimationSequenceBase, NotifyTrackName);
|
|
const bool bIsValidTime = IsValidTimeInternal(AnimationSequenceBase, StartTime);
|
|
|
|
if (bValidNotify && bValidOuter && bIsValidTrackName && bIsValidTime)
|
|
{
|
|
FAnimNotifyEvent& NewEvent = AnimationSequenceBase->Notifies.AddDefaulted_GetRef();
|
|
|
|
NewEvent.NotifyName = NAME_None;
|
|
NewEvent.Link(AnimationSequenceBase, StartTime);
|
|
NewEvent.TriggerTimeOffset = GetTriggerTimeOffsetForType(AnimationSequenceBase->CalculateOffsetForNotify(StartTime));
|
|
NewEvent.TrackIndex = GetTrackIndexForAnimationNotifyTrackName(AnimationSequenceBase, NotifyTrackName);
|
|
NewEvent.NotifyStateClass = nullptr;
|
|
NewEvent.Notify = Notify;
|
|
NewEvent.Guid = FGuid::NewGuid();
|
|
|
|
// Refresh all cached data
|
|
AnimationSequenceBase->RefreshCacheData();
|
|
}
|
|
else
|
|
{
|
|
if (!bValidNotify)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Notify in AddAnimationNotifyEventObject"));
|
|
}
|
|
|
|
if (!bValidOuter)
|
|
{
|
|
const FString NotifyName = bValidNotify ? Notify->GetName() : "Invalid Notify";
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Animation Notify %s Outer is not %s"), *NotifyName, *AnimationSequenceBase->GetName());
|
|
}
|
|
|
|
if (!bIsValidTrackName)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Animation Notify Track %s does not exist on Animation Sequence %s"), *NotifyTrackName.ToString(), *AnimationSequenceBase->GetName());
|
|
}
|
|
|
|
if (!bIsValidTime)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("%f is side of Animation Sequence %s range"), StartTime, *AnimationSequenceBase->GetName());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for AddAnimationNotifyEventObject"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::AddAnimationNotifyStateEventObject(UAnimSequenceBase* AnimationSequenceBase, float StartTime, float Duration, UAnimNotifyState* NotifyState, FName NotifyTrackName)
|
|
{
|
|
if (AnimationSequenceBase)
|
|
{
|
|
const bool bValidNotifyState = NotifyState != nullptr;
|
|
const bool bValidOuter = bValidNotifyState && NotifyState->GetOuter() == AnimationSequenceBase;
|
|
const bool bIsValidTrackName = IsValidAnimNotifyTrackName(AnimationSequenceBase, NotifyTrackName);
|
|
const bool bIsValidTime = IsValidTimeInternal(AnimationSequenceBase, StartTime);
|
|
|
|
if (bValidNotifyState && bValidOuter && bIsValidTrackName && bIsValidTime)
|
|
{
|
|
FAnimNotifyEvent& NewEvent = AnimationSequenceBase->Notifies.AddDefaulted_GetRef();
|
|
|
|
NewEvent.NotifyName = NAME_None;
|
|
NewEvent.Link(AnimationSequenceBase, StartTime);
|
|
NewEvent.TriggerTimeOffset = GetTriggerTimeOffsetForType(AnimationSequenceBase->CalculateOffsetForNotify(StartTime));
|
|
NewEvent.TrackIndex = GetTrackIndexForAnimationNotifyTrackName(AnimationSequenceBase, NotifyTrackName);
|
|
NewEvent.NotifyStateClass = NotifyState;
|
|
NewEvent.Notify = nullptr;
|
|
NewEvent.SetDuration(Duration);
|
|
NewEvent.EndLink.Link(AnimationSequenceBase, NewEvent.EndLink.GetTime());
|
|
NewEvent.Guid = FGuid::NewGuid();
|
|
|
|
// Refresh all cached data
|
|
AnimationSequenceBase->RefreshCacheData();
|
|
}
|
|
else
|
|
{
|
|
if (!bValidNotifyState)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Notify in AddAnimationNotifyEventObject"));
|
|
}
|
|
|
|
if (!bValidOuter)
|
|
{
|
|
const FString NotifyName = bValidNotifyState ? NotifyState->GetName() : "Invalid Notify";
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Animation Notify %s Outer is not %s"), *NotifyName, *AnimationSequenceBase->GetName());
|
|
}
|
|
|
|
if (!bIsValidTrackName)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Animation Notify Track %s does not exist on Animation Sequence %s"), *NotifyTrackName.ToString(), *AnimationSequenceBase->GetName());
|
|
}
|
|
|
|
if (!bIsValidTime)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("%f is side of Animation Sequence %s range"), StartTime, *AnimationSequenceBase->GetName());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for AddAnimationNotifyEventObject"));
|
|
}
|
|
}
|
|
|
|
static void ReplaceAnimNotifies_Helper(UAnimSequenceBase* AnimationSequence, UClass* OldNotifyClass, UClass* NewNotifyClass, FOnNotifyReplaced OnNotifyReplaced, FOnNotifyStateReplaced OnNotifyStateReplaced)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
if (OldNotifyClass != nullptr && NewNotifyClass != nullptr)
|
|
{
|
|
bool bModified = false;
|
|
for(int32 NotifyIndex = 0; NotifyIndex < AnimationSequence->Notifies.Num(); ++NotifyIndex)
|
|
{
|
|
FAnimNotifyEvent& NotifyEvent = AnimationSequence->Notifies[NotifyIndex];
|
|
|
|
if ((NotifyEvent.Notify && NotifyEvent.Notify->GetClass() == OldNotifyClass) ||
|
|
(NotifyEvent.NotifyStateClass && NotifyEvent.NotifyStateClass->GetClass() == OldNotifyClass))
|
|
{
|
|
|
|
bModified = true;
|
|
|
|
// Copy relevant data from the old notify
|
|
float StartTime = NotifyEvent.GetTime();
|
|
float Length = NotifyEvent.GetDuration();
|
|
int32 TargetTrackIndex = NotifyEvent.TrackIndex;
|
|
float TriggerTimeOffset = NotifyEvent.TriggerTimeOffset;
|
|
float EndTriggerTimeOffset = NotifyEvent.EndTriggerTimeOffset;
|
|
int32 SlotIndex = NotifyEvent.GetSlotIndex();
|
|
int32 EndSlotIndex = NotifyEvent.EndLink.GetSlotIndex();
|
|
int32 SegmentIndex = NotifyEvent.GetSegmentIndex();
|
|
int32 EndSegmentIndex = NotifyEvent.GetSegmentIndex();
|
|
EAnimLinkMethod::Type LinkMethod = NotifyEvent.GetLinkMethod();
|
|
EAnimLinkMethod::Type EndLinkMethod = NotifyEvent.EndLink.GetLinkMethod();
|
|
UAnimNotify* OldNotify = NotifyEvent.Notify;
|
|
UAnimNotifyState* OldNotifyState = NotifyEvent.NotifyStateClass;
|
|
|
|
// Remove old notify
|
|
AnimationSequence->Notifies.RemoveAt(NotifyIndex, 1, false);
|
|
|
|
// Add new notify in old notifies place
|
|
AnimationSequence->Notifies.InsertDefaulted(NotifyIndex);
|
|
FAnimNotifyEvent& NewEvent = AnimationSequence->Notifies[NotifyIndex];
|
|
|
|
// Setup new notify
|
|
NewEvent.NotifyName = NAME_None;
|
|
NewEvent.Link(AnimationSequence, StartTime);
|
|
NewEvent.TriggerTimeOffset = TriggerTimeOffset;
|
|
NewEvent.TrackIndex = TargetTrackIndex;
|
|
NewEvent.ChangeSlotIndex(SlotIndex);
|
|
NewEvent.SetSegmentIndex(SegmentIndex);
|
|
NewEvent.ChangeLinkMethod(LinkMethod);
|
|
NewEvent.Guid = FGuid::NewGuid();
|
|
|
|
UObject* AnimNotifyClass = NewObject<UObject>(AnimationSequence, NewNotifyClass, NAME_None, RF_Transactional);
|
|
NewEvent.NotifyStateClass = Cast<UAnimNotifyState>(AnimNotifyClass);
|
|
NewEvent.Notify = Cast<UAnimNotify>(AnimNotifyClass);
|
|
|
|
// Setup name and duration for new event
|
|
if (NewEvent.NotifyStateClass)
|
|
{
|
|
NewEvent.NotifyName = FName(*NewEvent.NotifyStateClass->GetNotifyName());
|
|
NewEvent.SetDuration(Length);
|
|
NewEvent.EndTriggerTimeOffset = EndTriggerTimeOffset;
|
|
NewEvent.EndLink.ChangeSlotIndex(EndSlotIndex);
|
|
NewEvent.EndLink.SetSegmentIndex(EndSegmentIndex);
|
|
NewEvent.EndLink.ChangeLinkMethod(EndLinkMethod);
|
|
|
|
OnNotifyStateReplaced.ExecuteIfBound(OldNotifyState, NewEvent.NotifyStateClass);
|
|
}
|
|
else if(NewEvent.Notify)
|
|
{
|
|
NewEvent.NotifyName = FName(*NewEvent.Notify->GetNotifyName());
|
|
|
|
OnNotifyReplaced.ExecuteIfBound(OldNotify, NewEvent.Notify);
|
|
}
|
|
|
|
NewEvent.Update();
|
|
}
|
|
}
|
|
|
|
if(bModified)
|
|
{
|
|
// Refresh all cached data
|
|
AnimationSequence->MarkPackageDirty();
|
|
AnimationSequence->RefreshCacheData();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Notify Class for ReplaceAnimNotifies"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for ReplaceAnimNotifies"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::ReplaceAnimNotifyStates(UAnimSequenceBase* AnimationSequenceBase, TSubclassOf<UAnimNotifyState> OldNotifyClass, TSubclassOf<UAnimNotifyState> NewNotifyClass, FOnNotifyStateReplaced OnNotifyStateReplaced)
|
|
{
|
|
ReplaceAnimNotifies_Helper(AnimationSequenceBase, OldNotifyClass.Get(), NewNotifyClass.Get(), FOnNotifyReplaced(), OnNotifyStateReplaced);
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::ReplaceAnimNotifies(UAnimSequenceBase* AnimationSequenceBase, TSubclassOf<UAnimNotify> OldNotifyClass, TSubclassOf<UAnimNotify> NewNotifyClass, FOnNotifyReplaced OnNotifyReplaced)
|
|
{
|
|
ReplaceAnimNotifies_Helper(AnimationSequenceBase, OldNotifyClass.Get(), NewNotifyClass.Get(), OnNotifyReplaced, FOnNotifyStateReplaced());
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::CopyAnimNotifiesFromSequence(UAnimSequenceBase* SourceAnimationSequenceBase, UAnimSequenceBase* DestinationAnimSequenceBase, bool bDeleteExistingNotifies)
|
|
{
|
|
if (SourceAnimationSequenceBase == nullptr)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Source Animation Sequence for CopyAnimNotifiesFromSequence"));
|
|
}
|
|
else if (DestinationAnimSequenceBase == nullptr)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Destination Animation Sequence for CopyAnimNotifiesFromSequence"));
|
|
}
|
|
else if (SourceAnimationSequenceBase == DestinationAnimSequenceBase)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Source and Destination Animation Sequence are the same for CopyAnimNotifiesFromSequence"));
|
|
}
|
|
else
|
|
{
|
|
const bool bShowDialogs = false;
|
|
UE::Anim::CopyNotifies(SourceAnimationSequenceBase, DestinationAnimSequenceBase, bShowDialogs, bDeleteExistingNotifies);
|
|
}
|
|
}
|
|
|
|
int32 UAnimationBlueprintLibrary::RemoveAnimationNotifyEventsByName(UAnimSequenceBase* AnimationSequenceBase, FName NotifyName)
|
|
{
|
|
int32 NumRemovedEvents = 0;
|
|
if (AnimationSequenceBase)
|
|
{
|
|
NumRemovedEvents = AnimationSequenceBase->Notifies.RemoveAll(
|
|
[&](const FAnimNotifyEvent& Event)
|
|
{
|
|
return Event.NotifyName == NotifyName;
|
|
});
|
|
|
|
// Refresh all cached data
|
|
AnimationSequenceBase->RefreshCacheData();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for RemoveAnimationNotifyEventsByName"));
|
|
}
|
|
|
|
return NumRemovedEvents;
|
|
}
|
|
|
|
int32 UAnimationBlueprintLibrary::RemoveAnimationNotifyEventsByTrack(UAnimSequenceBase* AnimationSequenceBase, FName NotifyTrackName)
|
|
{
|
|
int32 NumRemovedEvents = 0;
|
|
if (AnimationSequenceBase)
|
|
{
|
|
const bool bIsValidTrackName = IsValidAnimNotifyTrackName(AnimationSequenceBase, NotifyTrackName);
|
|
if (bIsValidTrackName)
|
|
{
|
|
const int32 TrackIndex = GetTrackIndexForAnimationNotifyTrackName(AnimationSequenceBase, NotifyTrackName);
|
|
NumRemovedEvents = AnimationSequenceBase->Notifies.RemoveAll(
|
|
[&](const FAnimNotifyEvent& Event)
|
|
{
|
|
return Event.TrackIndex == TrackIndex;
|
|
});
|
|
|
|
// Refresh all cached data
|
|
AnimationSequenceBase->RefreshCacheData();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Animation Notify Track %s does not exist on Animation Sequence %s"), *NotifyTrackName.ToString(), *AnimationSequenceBase->GetName());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for RemoveAnimationNotifyEventsByTrack"));
|
|
}
|
|
|
|
|
|
return NumRemovedEvents;
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetAnimationNotifyTrackNames(const UAnimSequenceBase* AnimationSequenceBase, TArray<FName>& TrackNames)
|
|
{
|
|
TrackNames.Empty();
|
|
if (AnimationSequenceBase)
|
|
{
|
|
for (const FAnimNotifyTrack& Track : AnimationSequenceBase->AnimNotifyTracks)
|
|
{
|
|
TrackNames.AddUnique(Track.TrackName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for GetAnimationNotifyTrackNames"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::AddAnimationNotifyTrack(UAnimSequenceBase* AnimationSequenceBase, FName NotifyTrackName, FLinearColor TrackColor /*= FLinearColor::White*/)
|
|
{
|
|
if (AnimationSequenceBase)
|
|
{
|
|
const bool bExistingTrackName = IsValidAnimNotifyTrackName(AnimationSequenceBase, NotifyTrackName);
|
|
if (!bExistingTrackName)
|
|
{
|
|
FAnimNotifyTrack NewTrack;
|
|
NewTrack.TrackName = NotifyTrackName;
|
|
NewTrack.TrackColor = TrackColor;
|
|
AnimationSequenceBase->AnimNotifyTracks.Add(NewTrack);
|
|
|
|
// Refresh all cached data
|
|
AnimationSequenceBase->RefreshCacheData();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Animation Notify Track %s already exists on Animation Sequence %s"), *NotifyTrackName.ToString(), *AnimationSequenceBase->GetName());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for AddAnimationNotifyTrack"));
|
|
}
|
|
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::RemoveAnimationNotifyTrack(UAnimSequenceBase* AnimationSequenceBase, FName NotifyTrackName)
|
|
{
|
|
if (AnimationSequenceBase)
|
|
{
|
|
const int32 TrackIndexToDelete = GetTrackIndexForAnimationNotifyTrackName(AnimationSequenceBase, NotifyTrackName);
|
|
if (TrackIndexToDelete != INDEX_NONE)
|
|
{
|
|
// Remove all notifies and sync markers on the to-delete-track
|
|
AnimationSequenceBase->Notifies.RemoveAll([&](const FAnimNotifyEvent& Notify) { return Notify.TrackIndex == TrackIndexToDelete; });
|
|
|
|
// Before track removal, make sure everything behind is fixed
|
|
for (FAnimNotifyEvent& Notify : AnimationSequenceBase->Notifies)
|
|
{
|
|
if (Notify.TrackIndex > TrackIndexToDelete)
|
|
{
|
|
Notify.TrackIndex = Notify.TrackIndex - 1;
|
|
}
|
|
}
|
|
|
|
if (UAnimSequence* AnimationSequence = Cast<UAnimSequence>(AnimationSequenceBase))
|
|
{
|
|
AnimationSequence->AuthoredSyncMarkers.RemoveAll([&](const FAnimSyncMarker& Marker) { return Marker.TrackIndex == TrackIndexToDelete; });
|
|
for (FAnimSyncMarker& SyncMarker : AnimationSequence->AuthoredSyncMarkers)
|
|
{
|
|
if (SyncMarker.TrackIndex > TrackIndexToDelete)
|
|
{
|
|
SyncMarker.TrackIndex = SyncMarker.TrackIndex - 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Delete the track itself
|
|
AnimationSequenceBase->AnimNotifyTracks.RemoveAt(TrackIndexToDelete);
|
|
|
|
// Refresh all cached data
|
|
AnimationSequenceBase->RefreshCacheData();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for RemoveAnimationNotifyTrack"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::RemoveAllAnimationNotifyTracks(UAnimSequenceBase* AnimationSequenceBase)
|
|
{
|
|
if (AnimationSequenceBase)
|
|
{
|
|
AnimationSequenceBase->Notifies.Empty();
|
|
if (UAnimSequence* AnimationSequence = Cast<UAnimSequence>(AnimationSequenceBase))
|
|
{
|
|
AnimationSequence->AuthoredSyncMarkers.Empty();
|
|
}
|
|
|
|
// Remove all but one notify tracks
|
|
AnimationSequenceBase->AnimNotifyTracks.SetNum(1);
|
|
|
|
// Also remove all stale notifies and sync markers from only track
|
|
AnimationSequenceBase->AnimNotifyTracks[0].Notifies.Empty();
|
|
AnimationSequenceBase->AnimNotifyTracks[0].SyncMarkers.Empty();
|
|
|
|
// Refresh all cached data
|
|
AnimationSequenceBase->RefreshCacheData();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for RemoveAllAnimationNotifyTracks"));
|
|
}
|
|
}
|
|
|
|
bool UAnimationBlueprintLibrary::IsValidAnimNotifyTrackName(const UAnimSequenceBase* AnimationSequenceBase, FName NotifyTrackName)
|
|
{
|
|
bool bIsValid = false;
|
|
if (AnimationSequenceBase)
|
|
{
|
|
bIsValid = GetTrackIndexForAnimationNotifyTrackName(AnimationSequenceBase, NotifyTrackName) != INDEX_NONE;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for IsValidAnimNotifyTrackName"));
|
|
}
|
|
|
|
return bIsValid;
|
|
}
|
|
|
|
int32 UAnimationBlueprintLibrary::GetTrackIndexForAnimationNotifyTrackName(const UAnimSequenceBase* AnimationSequenceBase, FName NotifyTrackName)
|
|
{
|
|
return AnimationSequenceBase->AnimNotifyTracks.IndexOfByPredicate(
|
|
[&](const FAnimNotifyTrack& Track)
|
|
{
|
|
return Track.TrackName == NotifyTrackName;
|
|
});
|
|
}
|
|
|
|
const FAnimNotifyTrack& UAnimationBlueprintLibrary::GetNotifyTrackByName(const UAnimSequenceBase* AnimationSequenceBase, FName NotifyTrackName)
|
|
{
|
|
const int32 TrackIndex = GetTrackIndexForAnimationNotifyTrackName(AnimationSequenceBase, NotifyTrackName);
|
|
checkf(TrackIndex != INDEX_NONE, TEXT("Notify Track %s does not exist on %s"), *NotifyTrackName.ToString(), *AnimationSequenceBase->GetName());
|
|
return AnimationSequenceBase->AnimNotifyTracks[TrackIndex];
|
|
}
|
|
|
|
float UAnimationBlueprintLibrary::GetAnimNotifyEventTriggerTime(const FAnimNotifyEvent& NotifyEvent)
|
|
{
|
|
return NotifyEvent.GetTriggerTime();
|
|
}
|
|
|
|
float UAnimationBlueprintLibrary::GetAnimNotifyEventDuration(const FAnimNotifyEvent& NotifyEvent)
|
|
{
|
|
return NotifyEvent.Duration;
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetAnimationSyncMarkersForTrack(const UAnimSequence* AnimationSequence, FName NotifyTrackName, TArray<FAnimSyncMarker>& Markers)
|
|
{
|
|
Markers.Empty();
|
|
if (AnimationSequence)
|
|
{
|
|
const bool bIsValidTrackName = IsValidAnimNotifyTrackName(AnimationSequence, NotifyTrackName);
|
|
|
|
if (bIsValidTrackName)
|
|
{
|
|
const FAnimNotifyTrack& Track = GetNotifyTrackByName(AnimationSequence, NotifyTrackName);
|
|
Markers.Empty(Track.SyncMarkers.Num());
|
|
for (FAnimSyncMarker* Marker : Track.SyncMarkers)
|
|
{
|
|
Markers.Add(*Marker);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Animation Notify Track %s does not exist on Animation Sequence %s"), *NotifyTrackName.ToString(), *AnimationSequence->GetName());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for AddVectorCurveKey"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetAnimationNotifyEventsForTrack(const UAnimSequenceBase* AnimationSequenceBase, FName NotifyTrackName, TArray<FAnimNotifyEvent>& Events)
|
|
{
|
|
Events.Empty();
|
|
if (AnimationSequenceBase)
|
|
{
|
|
const bool bIsValidTrackName = IsValidAnimNotifyTrackName(AnimationSequenceBase, NotifyTrackName);
|
|
|
|
if (bIsValidTrackName)
|
|
{
|
|
const FAnimNotifyTrack& Track = GetNotifyTrackByName(AnimationSequenceBase, NotifyTrackName);
|
|
Events.Empty(Track.Notifies.Num());
|
|
for (FAnimNotifyEvent* Event : Track.Notifies)
|
|
{
|
|
Events.Add(*Event);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Animation Notify Track %s does not exist on Animation Sequence %s"), *NotifyTrackName.ToString(), *AnimationSequenceBase->GetName());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for AddVectorCurveKey"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::AddCurve(UAnimSequence* AnimationSequence, FName CurveName, ERawCurveTrackTypes CurveType /*= RCT_Float*/, bool bMetaDataCurve /*= false*/)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
static const ESmartNameContainerType ContainerForCurveType[(int32)ERawCurveTrackTypes::RCT_MAX] = { ESmartNameContainerType::SNCT_CurveMapping, ESmartNameContainerType::SNCT_CurveMapping, ESmartNameContainerType::SNCT_TrackCurveMapping };
|
|
const ESmartNameContainerType CurveContainer = ContainerForCurveType[(int32)CurveType];
|
|
const int32 CurveFlags = bMetaDataCurve ? AACF_Metadata : AACF_DefaultCurve;
|
|
|
|
// Validate combination of curve types
|
|
|
|
// Only Float metadata curves are valid
|
|
const bool bValidMetaData = !bMetaDataCurve || (bMetaDataCurve && CurveType == ERawCurveTrackTypes::RCT_Float);
|
|
// Transform curves can only be added if the curve name exists as a bone on the skeleton
|
|
const bool bValidTransformCurveData = CurveType != ERawCurveTrackTypes::RCT_Transform || (AnimationSequence->GetSkeleton() && DoesBoneNameExistInternal(AnimationSequence->GetSkeleton(), CurveName));
|
|
|
|
if (bValidMetaData && bValidTransformCurveData )
|
|
{
|
|
// Add or retrieve the smartname
|
|
const FName Names[(int32)ESmartNameContainerType::SNCT_MAX] = { USkeleton::AnimCurveMappingName, USkeleton::AnimTrackCurveMappingName };
|
|
const bool bCurveAdded = AddCurveInternal(AnimationSequence, CurveName, Names[(int32)CurveContainer], CurveFlags, CurveType);
|
|
|
|
if (!bCurveAdded)
|
|
{
|
|
// Curve already existed
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Curve %s already exists on the Skeleton %s."), *CurveName.ToString(), *AnimationSequence->GetSkeleton()->GetName());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!bValidMetaData)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Curve type to be create as metadata, currently only float curves are supported as metadata."));
|
|
}
|
|
|
|
if (!bValidTransformCurveData)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Transform Curve name, the supplied name %s does not exist on the Skeleton %s."), *CurveName.ToString(), AnimationSequence->GetSkeleton() ? *AnimationSequence->GetSkeleton()->GetName() : TEXT("Invalid Skeleton"));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for AddCurve"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::RemoveCurve(UAnimSequence* AnimationSequence, FName CurveName, bool bRemoveNameFromSkeleton /*= false*/)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
const FName ContainerName = RetrieveContainerNameForCurve(AnimationSequence, CurveName);
|
|
if (ContainerName != NAME_None)
|
|
{
|
|
const bool bCurveRemoved = RemoveCurveInternal(AnimationSequence, CurveName, ContainerName, bRemoveNameFromSkeleton);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Could not find SmartName Container for Curve Name %s while trying to remove the curve"), *CurveName.ToString());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for RemoveCurve"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::RemoveAllCurveData(UAnimSequence* AnimationSequence)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
IAnimationDataController& Controller = AnimationSequence->GetController();
|
|
|
|
Controller.RemoveAllCurvesOfType(ERawCurveTrackTypes::RCT_Float);
|
|
Controller.RemoveAllCurvesOfType(ERawCurveTrackTypes::RCT_Transform);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for RemoveAllCurveData"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::AddTransformationCurveKey(UAnimSequence* AnimationSequence, FName CurveName, const float Time, const FTransform& Transform)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
TArray<float> TimeArray;
|
|
TArray<FTransform> TransformArray;
|
|
|
|
TimeArray.Add(Time);
|
|
TransformArray.Add(Transform);
|
|
|
|
AddCurveKeysInternal<FTransform, FTransformCurve, ERawCurveTrackTypes::RCT_Transform>(AnimationSequence, CurveName, TimeArray, TransformArray);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for AddTransformationCurveKey"));
|
|
}
|
|
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::AddTransformationCurveKeys(UAnimSequence* AnimationSequence, FName CurveName, const TArray<float>& Times, const TArray<FTransform>& Transforms)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
if (Times.Num() == Transforms.Num())
|
|
{
|
|
AddCurveKeysInternal<FTransform, FTransformCurve, ERawCurveTrackTypes::RCT_Transform>(AnimationSequence, CurveName, Times, Transforms);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Number of Time values %i does not match the number of Transforms %i in AddTransformationCurveKeys"), Times.Num(), Transforms.Num());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for AddTransformationCurveKeys"));
|
|
}
|
|
}
|
|
|
|
|
|
void UAnimationBlueprintLibrary::AddFloatCurveKey(UAnimSequence* AnimationSequence, FName CurveName, const float Time, const float Value)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
TArray<float> TimeArray;
|
|
TArray<float> ValueArray;
|
|
|
|
TimeArray.Add(Time);
|
|
ValueArray.Add(Value);
|
|
|
|
AddCurveKeysInternal<float, FFloatCurve, ERawCurveTrackTypes::RCT_Float>(AnimationSequence, CurveName, TimeArray, ValueArray);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for AddFloatCurveKey"));
|
|
}
|
|
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::AddFloatCurveKeys(UAnimSequence* AnimationSequence, FName CurveName, const TArray<float>& Times, const TArray<float>& Values)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
if (Times.Num() == Values.Num())
|
|
{
|
|
AddCurveKeysInternal<float, FFloatCurve, ERawCurveTrackTypes::RCT_Float>(AnimationSequence, CurveName, Times, Values);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Number of Time values %i does not match the number of Values %i in AddFloatCurveKeys"), Times.Num(), Values.Num());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for AddFloatCurveKeys"));
|
|
}
|
|
|
|
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::AddVectorCurveKey(UAnimSequence* AnimationSequence, FName CurveName, const float Time, const FVector Vector)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
TArray<float> TimeArray;
|
|
TArray<FVector> VectorArray;
|
|
|
|
TimeArray.Add(Time);
|
|
VectorArray.Add(Vector);
|
|
|
|
AddCurveKeysInternal<FVector, FVectorCurve, ERawCurveTrackTypes::RCT_Vector>(AnimationSequence, CurveName, TimeArray, VectorArray);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for AddVectorCurveKey"));
|
|
}
|
|
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::AddVectorCurveKeys(UAnimSequence* AnimationSequence, FName CurveName, const TArray<float>& Times, const TArray<FVector>& Vectors)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
if (Times.Num() == Vectors.Num())
|
|
{
|
|
AddCurveKeysInternal<FVector, FVectorCurve, ERawCurveTrackTypes::RCT_Vector>(AnimationSequence, CurveName, Times, Vectors);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Number of Time values %i does not match the number of Vectors %i in AddVectorCurveKeys"), Times.Num(), Vectors.Num());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for AddVectorCurveKeys"));
|
|
}
|
|
}
|
|
|
|
static void SetControllerCurveKeys(IAnimationDataController& Controller, FSmartName SmartName, const TArray<float>& Times, const TArray<float>& Values)
|
|
{
|
|
const int32 NumKeys = Values.Num();
|
|
const FAnimationCurveIdentifier CurveId(SmartName, ERawCurveTrackTypes::RCT_Float);
|
|
for (int32 KeyIndex = 0; KeyIndex < NumKeys; ++KeyIndex)
|
|
{
|
|
Controller.SetCurveKey(CurveId, { Times[KeyIndex], Values[KeyIndex] });
|
|
}
|
|
}
|
|
|
|
static void SetControllerCurveKeys(IAnimationDataController& Controller, FSmartName SmartName, const TArray<float>& Times, const TArray<FTransform>& Values)
|
|
{
|
|
const int32 NumKeys = Values.Num();
|
|
const FAnimationCurveIdentifier CurveId(SmartName, ERawCurveTrackTypes::RCT_Transform);
|
|
for (int32 KeyIndex = 0; KeyIndex < NumKeys; ++KeyIndex)
|
|
{
|
|
Controller.SetTransformCurveKey(CurveId, Times[KeyIndex], Values[KeyIndex]);
|
|
}
|
|
}
|
|
|
|
static void SetControllerCurveKeys(IAnimationDataController& Controller, FSmartName CurveId, const TArray<float>& Times, const TArray<FVector>& Values)
|
|
{
|
|
ensure(false);
|
|
}
|
|
|
|
template <typename DataType, typename CurveClass, ERawCurveTrackTypes CurveType>
|
|
void UAnimationBlueprintLibrary::AddCurveKeysInternal(UAnimSequence* AnimationSequence, FName CurveName, const TArray<float>& Times, const TArray<DataType>& KeyData)
|
|
{
|
|
checkf(Times.Num() == KeyData.Num(), TEXT("Not enough key data supplied"));
|
|
|
|
const FName ContainerName = RetrieveContainerNameForCurve(AnimationSequence, CurveName);
|
|
|
|
if (ContainerName != NAME_None)
|
|
{
|
|
// Retrieve smart name for curve
|
|
const FSmartName CurveSmartName = RetrieveSmartNameForCurve(AnimationSequence, CurveName, ContainerName);
|
|
|
|
// Retrieve the curve by name
|
|
const FAnimationCurveIdentifier CurveId(CurveSmartName, CurveType);
|
|
const CurveClass* Curve = static_cast<const CurveClass*>(AnimationSequence->GetDataModel()->FindCurve(CurveId));
|
|
if (Curve)
|
|
{
|
|
IAnimationDataController& Controller = AnimationSequence->GetController();
|
|
SetControllerCurveKeys(Controller, CurveSmartName, Times, KeyData);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UAnimationBlueprintLibrary::AddCurveInternal(UAnimSequence* AnimationSequence, FName CurveName, FName ContainerName, int32 CurveFlags, ERawCurveTrackTypes SupportedCurveType)
|
|
{
|
|
// Add or retrieve the smart name
|
|
FSmartName SmartCurveName;
|
|
AnimationSequence->GetSkeleton()->AddSmartNameAndModify(ContainerName, CurveName, SmartCurveName);
|
|
|
|
FAnimationCurveIdentifier CurveId(SmartCurveName, SupportedCurveType);
|
|
|
|
bool bCurveAdded = false;
|
|
|
|
const FAnimCurveBase* ExistingCurve = AnimationSequence->GetDataModel()->FindCurve(CurveId);
|
|
if (ExistingCurve == nullptr)
|
|
{
|
|
IAnimationDataController& Controller = AnimationSequence->GetController();
|
|
Controller.AddCurve(CurveId, CurveFlags);
|
|
bCurveAdded = AnimationSequence->GetDataModel()->FindCurve(CurveId) != nullptr;
|
|
}
|
|
else
|
|
{
|
|
// Curve already exists
|
|
}
|
|
|
|
return bCurveAdded;
|
|
}
|
|
|
|
bool UAnimationBlueprintLibrary::RemoveCurveInternal(UAnimSequence* AnimationSequence, FName CurveName, FName ContainerName, bool bRemoveNameFromSkeleton)
|
|
{
|
|
checkf(AnimationSequence != nullptr, TEXT("Invalid Animation Sequence ptr"));
|
|
bool bRemoved = false;
|
|
SmartName::UID_Type UID = AnimationSequence->GetSkeleton()->GetUIDByName(ContainerName, CurveName);
|
|
if (UID != SmartName::MaxUID)
|
|
{
|
|
FSmartName SmartCurveName;
|
|
USkeleton* Skeleton = AnimationSequence->GetSkeleton();
|
|
checkf(Skeleton != nullptr, TEXT("Invalid Skeleton ptr"));
|
|
if (Skeleton->GetSmartNameByUID(ContainerName, UID, SmartCurveName))
|
|
{
|
|
IAnimationDataController& Controller = AnimationSequence->GetController();
|
|
|
|
const FAnimationCurveIdentifier CurveId(SmartCurveName, ContainerName == USkeleton::AnimTrackCurveMappingName ? ERawCurveTrackTypes::RCT_Transform : ERawCurveTrackTypes::RCT_Float);
|
|
bRemoved = Controller.RemoveCurve(CurveId);
|
|
|
|
if (bRemoveNameFromSkeleton)
|
|
{
|
|
// Ensure we are eligible to do this
|
|
bool bValidToRemove = true;
|
|
|
|
if (ContainerName == USkeleton::AnimTrackCurveMappingName)
|
|
{
|
|
// Make sure we do not remove bone names
|
|
bValidToRemove = DoesBoneCurveNameExistInternal(Skeleton, CurveName);
|
|
}
|
|
|
|
if (bValidToRemove)
|
|
{
|
|
Skeleton->RemoveSmartnameAndModify(ContainerName, UID);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Cannot remove Curve Name %s from Skeleton %s"), *CurveName.ToString(), *AnimationSequence->GetSkeleton()->GetName());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Could not retrieve Smart Name for Curve Name %s from Skeleton %s"), *CurveName.ToString(), *AnimationSequence->GetSkeleton()->GetName());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Name does not exist on skeleton
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Could find for Curve Name %s in Skeleton %s"), *CurveName.ToString(), *AnimationSequence->GetSkeleton()->GetName());
|
|
}
|
|
|
|
return bRemoved;
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::DoesBoneNameExist(UAnimSequence* AnimationSequence, FName BoneName, bool& bExists)
|
|
{
|
|
bExists = false;
|
|
if (AnimationSequence)
|
|
{
|
|
USkeleton* Skeleton = AnimationSequence->GetSkeleton();
|
|
if (Skeleton)
|
|
{
|
|
bExists = DoesBoneNameExistInternal(Skeleton, BoneName);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("No Skeleton found for Animation Sequence %s"), *AnimationSequence->GetName());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for DoesBoneNameExist"));
|
|
}
|
|
}
|
|
|
|
bool UAnimationBlueprintLibrary::DoesBoneNameExistInternal(USkeleton* Skeleton, FName BoneName)
|
|
{
|
|
checkf(Skeleton != nullptr, TEXT("Invalid Skeleton ptr"));
|
|
return Skeleton->GetReferenceSkeleton().FindBoneIndex(BoneName) != INDEX_NONE;
|
|
}
|
|
|
|
bool UAnimationBlueprintLibrary::DoesBoneCurveNameExistInternal(USkeleton* Skeleton, FName BoneName)
|
|
{
|
|
checkf(Skeleton != nullptr, TEXT("Invalid Skeleton ptr"));
|
|
return Skeleton->GetUIDByName(USkeleton::AnimTrackCurveMappingName, BoneName) != SmartName::MaxUID;
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetFloatKeys(UAnimSequence* AnimationSequence, FName CurveName, TArray<float>& Times, TArray<float>& Values)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
GetCurveKeysInternal<float, FFloatCurve, ERawCurveTrackTypes::RCT_Float>(AnimationSequence, CurveName, Times, Values);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for GetFloatKeys"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetVectorKeys(UAnimSequence* AnimationSequence, FName CurveName, TArray<float>& Times, TArray<FVector>& Values)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
GetCurveKeysInternal<FVector, FVectorCurve, ERawCurveTrackTypes::RCT_Vector>(AnimationSequence, CurveName, Times, Values);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for GetVectorKeys"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetTransformationKeys(UAnimSequence* AnimationSequence, FName CurveName, TArray<float>& Times, TArray<FTransform>& Values)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
GetCurveKeysInternal<FTransform, FTransformCurve, ERawCurveTrackTypes::RCT_Transform>(AnimationSequence, CurveName, Times, Values);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for GetTransformationKeys"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::CopyAnimationCurveNamesToSkeleton(USkeleton* OldSkeleton, USkeleton* NewSkeleton, UAnimSequenceBase* SequenceBase, ERawCurveTrackTypes CurveType)
|
|
{
|
|
IAnimationDataController& Controller = SequenceBase->GetController();
|
|
Controller.UpdateCurveNamesFromSkeleton(OldSkeleton, CurveType);
|
|
Controller.FindOrAddCurveNamesOnSkeleton(NewSkeleton, CurveType);
|
|
}
|
|
|
|
template <typename DataType, typename CurveClass, ERawCurveTrackTypes CurveType>
|
|
void UAnimationBlueprintLibrary::GetCurveKeysInternal(UAnimSequence* AnimationSequence, FName CurveName, TArray<float>& Times, TArray<DataType>& KeyData)
|
|
{
|
|
checkf(AnimationSequence != nullptr, TEXT("Invalid Animation Sequence ptr"));
|
|
const FName ContainerName = RetrieveContainerNameForCurve(AnimationSequence, CurveName);
|
|
|
|
if (ContainerName != NAME_None)
|
|
{
|
|
// Retrieve smart name for curve
|
|
const FSmartName CurveSmartName = RetrieveSmartNameForCurve(AnimationSequence, CurveName, ContainerName);
|
|
|
|
// Retrieve the curve by name
|
|
const FAnimationCurveIdentifier CurveId(CurveSmartName, CurveType);
|
|
const CurveClass* Curve = static_cast<const CurveClass*>(AnimationSequence->GetDataModel()->FindCurve(CurveId));
|
|
|
|
if (Curve)
|
|
{
|
|
Curve->GetKeys(Times, KeyData);
|
|
checkf(Times.Num() == KeyData.Num(), TEXT("Invalid key data retrieved from curve"));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UAnimationBlueprintLibrary::DoesCurveExist(UAnimSequence* AnimationSequence, FName CurveName, ERawCurveTrackTypes CurveType)
|
|
{
|
|
bool bExistingCurve = false;
|
|
|
|
if (AnimationSequence)
|
|
{
|
|
FSmartName SmartName;
|
|
if (RetrieveSmartNameForCurve(AnimationSequence, CurveName, USkeleton::AnimTrackCurveMappingName, SmartName))
|
|
{
|
|
FAnimationCurveIdentifier CurveId(SmartName, CurveType);
|
|
const FAnimCurveBase* Curve = AnimationSequence->GetDataModel()->FindCurve(CurveId);
|
|
bExistingCurve = Curve != nullptr;
|
|
}
|
|
|
|
if (RetrieveSmartNameForCurve(AnimationSequence, CurveName, USkeleton::AnimCurveMappingName, SmartName))
|
|
{
|
|
FAnimationCurveIdentifier CurveId(SmartName, CurveType);
|
|
const FAnimCurveBase* Curve = AnimationSequence->GetDataModel()->FindCurve(CurveId);
|
|
bExistingCurve |= Curve != nullptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for DoesCurveExist"));
|
|
}
|
|
|
|
return bExistingCurve;
|
|
}
|
|
|
|
bool UAnimationBlueprintLibrary::DoesSmartNameExist(UAnimSequence* AnimationSequence, FName Name)
|
|
{
|
|
checkf(AnimationSequence != nullptr, TEXT("Invalid Animation Sequence ptr"));
|
|
FSmartName SmartName;
|
|
return AnimationSequence->GetSkeleton()->GetSmartNameByName(USkeleton::AnimTrackCurveMappingName, Name, SmartName) ||
|
|
AnimationSequence->GetSkeleton()->GetSmartNameByName(USkeleton::AnimCurveMappingName, Name, SmartName);
|
|
}
|
|
|
|
bool UAnimationBlueprintLibrary::RetrieveSmartNameForCurve(const UAnimSequence* AnimationSequence, FName CurveName, FName ContainerName, FSmartName& SmartName)
|
|
{
|
|
checkf(AnimationSequence != nullptr, TEXT("Invalid Animation Sequence ptr"));
|
|
return AnimationSequence->GetSkeleton()->GetSmartNameByName(ContainerName, CurveName, SmartName);
|
|
}
|
|
|
|
FSmartName UAnimationBlueprintLibrary::RetrieveSmartNameForCurve(const UAnimSequence* AnimationSequence, FName CurveName, FName ContainerName)
|
|
{
|
|
checkf(AnimationSequence != nullptr, TEXT("Invalid Animation Sequence ptr"));
|
|
FSmartName SmartCurveName;
|
|
AnimationSequence->GetSkeleton()->GetSmartNameByName(ContainerName, CurveName, SmartCurveName);
|
|
return SmartCurveName;
|
|
}
|
|
|
|
FName UAnimationBlueprintLibrary::RetrieveContainerNameForCurve(const UAnimSequence* AnimationSequence, FName CurveName)
|
|
{
|
|
checkf(AnimationSequence != nullptr, TEXT("Invalid Animation Sequence ptr"));
|
|
const FName Names[(int32)ESmartNameContainerType::SNCT_MAX] = { USkeleton::AnimCurveMappingName, USkeleton::AnimTrackCurveMappingName };
|
|
for (int32 Index = 0; Index < (int32)ESmartNameContainerType::SNCT_MAX; ++Index)
|
|
{
|
|
const FSmartNameMapping* CurveMapping = AnimationSequence->GetSkeleton()->GetSmartNameContainer(Names[Index]);
|
|
if (CurveMapping && CurveMapping->Exists(CurveName))
|
|
{
|
|
return Names[Index];
|
|
}
|
|
}
|
|
|
|
return NAME_None;
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::AddMetaData(UAnimationAsset* AnimationAsset, TSubclassOf<UAnimMetaData> MetaDataClass, UAnimMetaData*& MetaDataInstance)
|
|
{
|
|
if (AnimationAsset)
|
|
{
|
|
MetaDataInstance = NewObject<UAnimMetaData>(AnimationAsset, MetaDataClass, NAME_None, RF_Transactional);
|
|
if (MetaDataInstance)
|
|
{
|
|
AnimationAsset->AddMetaData(MetaDataInstance);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Failed to create instance for %s"), *MetaDataClass->GetName());
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for AddMetaData"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::AddMetaDataObject(UAnimationAsset* AnimationAsset, UAnimMetaData* MetaDataObject)
|
|
{
|
|
if (AnimationAsset && MetaDataObject)
|
|
{
|
|
if (MetaDataObject->GetOuter() == AnimationAsset)
|
|
{
|
|
AnimationAsset->AddMetaData(MetaDataObject);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Outer for MetaData Instance %s is not Animation Sequence %s"), *MetaDataObject->GetName(), *AnimationAsset->GetName());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!AnimationAsset)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for AddMetaDataObject"));
|
|
}
|
|
|
|
if (!MetaDataObject)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid MetaDataObject for AddMetaDataObject"));
|
|
}
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::RemoveAllMetaData(UAnimationAsset* AnimationAsset)
|
|
{
|
|
if (AnimationAsset)
|
|
{
|
|
AnimationAsset->EmptyMetaData();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for RemoveAllMetaData"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::RemoveMetaData(UAnimationAsset* AnimationAsset, UAnimMetaData* MetaDataObject)
|
|
{
|
|
if (AnimationAsset)
|
|
{
|
|
AnimationAsset->RemoveMetaData(MetaDataObject);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for RemoveMetaData"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::RemoveMetaDataOfClass(UAnimationAsset* AnimationAsset, TSubclassOf<UAnimMetaData> MetaDataClass)
|
|
{
|
|
if (AnimationAsset)
|
|
{
|
|
TArray<UAnimMetaData*> MetaDataOfClass;
|
|
GetMetaDataOfClass(AnimationAsset, MetaDataClass, MetaDataOfClass);
|
|
AnimationAsset->RemoveMetaData(MetaDataOfClass);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for RemoveMetaDataOfClass"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetMetaData(const UAnimationAsset* AnimationAsset, TArray<UAnimMetaData*>& MetaData)
|
|
{
|
|
MetaData.Empty();
|
|
if (AnimationAsset)
|
|
{
|
|
MetaData = AnimationAsset->GetMetaData();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for GetMetaData"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetMetaDataOfClass(const UAnimationAsset* AnimationAsset, TSubclassOf<UAnimMetaData> MetaDataClass, TArray<UAnimMetaData*>& MetaDataOfClass)
|
|
{
|
|
MetaDataOfClass.Empty();
|
|
if (AnimationAsset)
|
|
{
|
|
const TArray<UAnimMetaData*>& MetaData = AnimationAsset->GetMetaData();
|
|
for (UAnimMetaData* MetaDataInstance : MetaData)
|
|
{
|
|
if (MetaDataInstance->GetClass() == *MetaDataClass)
|
|
{
|
|
MetaDataOfClass.Add(MetaDataInstance);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for GetMetaDataOfClass"));
|
|
}
|
|
}
|
|
|
|
bool UAnimationBlueprintLibrary::ContainsMetaDataOfClass(const UAnimationAsset* AnimationAsset, TSubclassOf<UAnimMetaData> MetaDataClass)
|
|
{
|
|
bool bContainsMetaData = false;
|
|
if (AnimationAsset)
|
|
{
|
|
TArray<UAnimMetaData*> MetaData;
|
|
GetMetaData(AnimationAsset, MetaData);
|
|
bContainsMetaData = MetaData.FindByPredicate(
|
|
[&](UAnimMetaData* MetaDataObject)
|
|
{
|
|
return (MetaDataObject->GetClass() == *MetaDataClass);
|
|
}) != nullptr;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for ContainsMetaDataOfClass"));
|
|
}
|
|
|
|
return bContainsMetaData;
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetBonePoseForTime(const UAnimSequenceBase* AnimationSequenceBase, FName BoneName, float Time, bool bExtractRootMotion, FTransform& Pose)
|
|
{
|
|
Pose.SetIdentity();
|
|
if (AnimationSequenceBase)
|
|
{
|
|
TArray<FName> BoneNameArray;
|
|
TArray<FTransform> PoseArray;
|
|
BoneNameArray.Add(BoneName);
|
|
GetBonePosesForTime(AnimationSequenceBase, BoneNameArray, Time, bExtractRootMotion, PoseArray);
|
|
Pose = PoseArray[0];
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetBonePoseForTime"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetBonePoseForFrame(const UAnimSequenceBase* AnimationSequenceBase, FName BoneName, int32 Frame, bool bExtractRootMotion, FTransform& Pose)
|
|
{
|
|
Pose.SetIdentity();
|
|
if (AnimationSequenceBase)
|
|
{
|
|
GetBonePoseForTime(AnimationSequenceBase, BoneName, GetTimeAtFrameInternal(AnimationSequenceBase, Frame), bExtractRootMotion, Pose);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetBonePoseForFrame"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetBonePosesForTime(const UAnimSequenceBase* AnimationSequenceBase, TArray<FName> BoneNames, float Time, bool bExtractRootMotion, TArray<FTransform>& Poses, const USkeletalMesh* PreviewMesh /*= nullptr*/)
|
|
{
|
|
Poses.Empty(BoneNames.Num());
|
|
if (AnimationSequenceBase && AnimationSequenceBase->GetSkeleton())
|
|
{
|
|
Poses.AddDefaulted(BoneNames.Num());
|
|
|
|
// Need this for FCompactPose
|
|
FMemMark Mark(FMemStack::Get());
|
|
|
|
const FReferenceSkeleton& RefSkeleton = (PreviewMesh)? PreviewMesh->GetRefSkeleton() : AnimationSequenceBase->GetSkeleton()->GetReferenceSkeleton();
|
|
|
|
if (IsValidTimeInternal(AnimationSequenceBase, Time))
|
|
{
|
|
if (BoneNames.Num())
|
|
{
|
|
for (int32 BoneNameIndex = 0; BoneNameIndex < BoneNames.Num(); ++BoneNameIndex)
|
|
{
|
|
const FName& BoneName = BoneNames[BoneNameIndex];
|
|
|
|
FTransform& Transform = Poses[BoneNameIndex];
|
|
if (IsValidRawAnimationTrackName(AnimationSequenceBase, BoneName))
|
|
{
|
|
const EAnimInterpolationType InterpolationType = [AnimationSequenceBase]() -> EAnimInterpolationType
|
|
{
|
|
if (const UAnimSequence* AnimationSequence = Cast<const UAnimSequence>(AnimationSequenceBase))
|
|
{
|
|
return AnimationSequence->Interpolation;
|
|
}
|
|
|
|
return EAnimInterpolationType::Linear;
|
|
}();
|
|
UE::Anim::GetBoneTransformFromModel(AnimationSequenceBase->GetDataModel(), Transform, AnimationSequenceBase->GetDataModel()->GetBoneTrackIndexByName(BoneName), Time, InterpolationType);
|
|
}
|
|
else
|
|
{
|
|
|
|
// otherwise, get ref pose if exists
|
|
const int32 BoneIndex = RefSkeleton.FindBoneIndex(BoneName);
|
|
if (BoneIndex != INDEX_NONE)
|
|
{
|
|
Transform = RefSkeleton.GetRefBonePose()[BoneIndex];
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid bone name %s for Animation Sequence %s supplied for GetBonePosesForTime"), *BoneName.ToString(), *AnimationSequenceBase->GetName());
|
|
Transform = FTransform::Identity;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Error, TEXT("Invalid or no bone names specified to retrieve poses given Animation Sequence %s in GetBonePosesForTime"), *AnimationSequenceBase->GetName());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid time value %f for Animation Sequence %s supplied for GetBonePosesForTime"), Time, *AnimationSequenceBase->GetName());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetBonePosesForTime"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetBonePosesForFrame(const UAnimSequenceBase* AnimationSequenceBase, TArray<FName> BoneNames, int32 Frame, bool bExtractRootMotion, TArray<FTransform>& Poses, const USkeletalMesh* PreviewMesh /*= nullptr*/)
|
|
{
|
|
Poses.Empty(BoneNames.Num());
|
|
if (AnimationSequenceBase)
|
|
{
|
|
GetBonePosesForTime(AnimationSequenceBase, BoneNames, GetTimeAtFrameInternal(AnimationSequenceBase, Frame), bExtractRootMotion, Poses, PreviewMesh);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetBonePosesForFrame"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::AddVirtualBone(const UAnimSequence* AnimationSequence, FName SourceBoneName, FName TargetBoneName, FName& VirtualBoneName)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
USkeleton* Skeleton = AnimationSequence->GetSkeleton();
|
|
if (Skeleton)
|
|
{
|
|
const bool bSourceBoneExists = DoesBoneNameExistInternal(Skeleton, SourceBoneName);
|
|
const bool bTargetBoneExists = DoesBoneNameExistInternal(Skeleton, TargetBoneName);
|
|
const bool bVirtualBoneDoesNotExist = !DoesVirtualBoneNameExistInternal(Skeleton, VirtualBoneName);
|
|
|
|
if (bSourceBoneExists && bTargetBoneExists && bVirtualBoneDoesNotExist)
|
|
{
|
|
const bool bAdded = Skeleton->AddNewVirtualBone(SourceBoneName, TargetBoneName, VirtualBoneName);
|
|
if (bAdded)
|
|
{
|
|
Skeleton->HandleSkeletonHierarchyChange();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Virtual bone between %s and %s already exists on Skeleton %s"), *SourceBoneName.ToString(), *TargetBoneName.ToString(), *Skeleton->GetName());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!bSourceBoneExists)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Bone Name %s does not exist on Skeleton %s"), *SourceBoneName.ToString(), *Skeleton->GetName());
|
|
}
|
|
|
|
if (!bTargetBoneExists)
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Bone Name %s does not exist on Skeleton %s"), *TargetBoneName.ToString(), *Skeleton->GetName());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("No Skeleton found for Animation Sequence %s"), *AnimationSequence->GetName());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for AddVirtualBone"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::RemoveVirtualBone(const UAnimSequence* AnimationSequence, FName VirtualBoneName)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
USkeleton* Skeleton = AnimationSequence->GetSkeleton();
|
|
if (Skeleton)
|
|
{
|
|
if (DoesVirtualBoneNameExistInternal(Skeleton, VirtualBoneName))
|
|
{
|
|
TArray<FName> BoneNameArray;
|
|
BoneNameArray.Add(VirtualBoneName);
|
|
Skeleton->RemoveVirtualBones(BoneNameArray);
|
|
Skeleton->HandleSkeletonHierarchyChange();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Virtual Bone Name %s already exists on Skeleton %s"), *VirtualBoneName.ToString(), *Skeleton->GetName());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("No Skeleton found for Animation Sequence %s"), *AnimationSequence->GetName());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for RemoveVirtualBone"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::RemoveVirtualBones(const UAnimSequence* AnimationSequence, TArray<FName> VirtualBoneNames)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
USkeleton* Skeleton = AnimationSequence->GetSkeleton();
|
|
if (Skeleton)
|
|
{
|
|
for (FName& VirtualBoneName : VirtualBoneNames)
|
|
{
|
|
if (!DoesVirtualBoneNameExistInternal(Skeleton, VirtualBoneName))
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Virtual Bone Name %s already exists on Skeleton %s"), *VirtualBoneName.ToString(), *Skeleton->GetName());
|
|
}
|
|
}
|
|
|
|
Skeleton->RemoveVirtualBones(VirtualBoneNames);
|
|
Skeleton->HandleSkeletonHierarchyChange();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("No Skeleton found for Animation Sequence %s"), *AnimationSequence->GetName());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for RemoveVirtualBones"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::RemoveAllVirtualBones(const UAnimSequence* AnimationSequence)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
USkeleton* Skeleton = AnimationSequence->GetSkeleton();
|
|
if (Skeleton)
|
|
{
|
|
TArray<FName> VirtualBoneNames;
|
|
for (const FVirtualBone& VirtualBone : Skeleton->VirtualBones)
|
|
{
|
|
VirtualBoneNames.Add(VirtualBone.VirtualBoneName);
|
|
}
|
|
|
|
RemoveVirtualBones(AnimationSequence, VirtualBoneNames);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("No Skeleton found for Animation Sequence %s"), *AnimationSequence->GetName());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for RemoveAllVirtualBones"));
|
|
}
|
|
}
|
|
|
|
bool UAnimationBlueprintLibrary::DoesVirtualBoneNameExistInternal(USkeleton* Skeleton, FName BoneName)
|
|
{
|
|
checkf(Skeleton != nullptr, TEXT("Invalid Skeleton ptr"));
|
|
return Skeleton->VirtualBones.IndexOfByPredicate([&](const FVirtualBone& VirtualBone) { return VirtualBone.VirtualBoneName == BoneName; }) != INDEX_NONE;
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetSequenceLength(const UAnimSequenceBase* AnimationSequenceBase, float& Length)
|
|
{
|
|
Length = 0.0f;
|
|
if (AnimationSequenceBase)
|
|
{
|
|
Length = AnimationSequenceBase->GetPlayLength();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetSequenceLength"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetRateScale(const UAnimSequenceBase* AnimationSequenceBase, float& RateScale)
|
|
{
|
|
RateScale = 0.0f;
|
|
if (AnimationSequenceBase)
|
|
{
|
|
RateScale = AnimationSequenceBase->RateScale;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetRateScale"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::SetRateScale(UAnimSequenceBase* AnimationSequenceBase, float RateScale)
|
|
{
|
|
if (AnimationSequenceBase)
|
|
{
|
|
AnimationSequenceBase->RateScale = RateScale;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for SetRateScale"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetFrameAtTime(const UAnimSequenceBase* AnimationSequenceBase, const float Time, int32& Frame)
|
|
{
|
|
Frame = 0;
|
|
if (AnimationSequenceBase)
|
|
{
|
|
Frame = AnimationSequenceBase->GetFrameAtTime(Time);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetFrameAtTime"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetTimeAtFrame(const UAnimSequenceBase* AnimationSequenceBase, const int32 Frame, float& Time)
|
|
{
|
|
Time = 0.0f;
|
|
if (AnimationSequenceBase)
|
|
{
|
|
Time = GetTimeAtFrameInternal(AnimationSequenceBase, Frame);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetTimeAtFrame"));
|
|
}
|
|
}
|
|
|
|
float UAnimationBlueprintLibrary::GetTimeAtFrameInternal(const UAnimSequenceBase* AnimationSequenceBase, const int32 Frame)
|
|
{
|
|
return AnimationSequenceBase->GetTimeAtFrame(Frame);
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::IsValidTime(const UAnimSequenceBase* AnimationSequenceBase, const float Time, bool& IsValid)
|
|
{
|
|
IsValid = false;
|
|
if (AnimationSequenceBase)
|
|
{
|
|
IsValid = IsValidTimeInternal(AnimationSequenceBase, Time);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for IsValidTime"));
|
|
}
|
|
}
|
|
|
|
bool UAnimationBlueprintLibrary::IsValidTimeInternal(const UAnimSequenceBase* AnimationSequenceBase, const float Time)
|
|
{
|
|
return FMath::IsWithinInclusive(Time, 0.0f, AnimationSequenceBase->GetPlayLength());
|
|
}
|
|
|
|
bool UAnimationBlueprintLibrary::EvaluateRootBoneTimecodeAttributesAtTime(const UAnimSequenceBase* AnimationSequenceBase, const float EvalTime, FQualifiedFrameTime& OutQualifiedFrameTime)
|
|
{
|
|
if (!AnimationSequenceBase)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const UAnimDataModel* AnimDataModel = AnimationSequenceBase->GetDataModel();
|
|
if (!AnimDataModel)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const int32 RootBoneTrackIndex = 0;
|
|
const FBoneAnimationTrack* RootBoneTrack = AnimDataModel->FindBoneTrackByIndex(RootBoneTrackIndex);
|
|
if (!RootBoneTrack)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const FName& RootBoneName = RootBoneTrack->Name;
|
|
|
|
TArray<const FAnimatedBoneAttribute*> RootBoneAttributes;
|
|
AnimDataModel->GetAttributesForBone(RootBoneName, RootBoneAttributes);
|
|
|
|
FName TCHourAttrName(TEXT("TCHour"));
|
|
FName TCMinuteAttrName(TEXT("TCMinute"));
|
|
FName TCSecondAttrName(TEXT("TCSecond"));
|
|
FName TCFrameAttrName(TEXT("TCFrame"));
|
|
FName TCSubframeAttrName(TEXT("TCSubframe"));
|
|
|
|
if (const UAnimationSettings* AnimationSettings = UAnimationSettings::Get())
|
|
{
|
|
TCHourAttrName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.HourAttributeName;
|
|
TCMinuteAttrName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.MinuteAttributeName;
|
|
TCSecondAttrName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.SecondAttributeName;
|
|
TCFrameAttrName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.FrameAttributeName;
|
|
TCSubframeAttrName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.SubframeAttributeName;
|
|
}
|
|
|
|
const TArray<FName> TimecodeBoneAttributeNames = { TCHourAttrName, TCMinuteAttrName, TCSecondAttrName, TCFrameAttrName, TCSubframeAttrName };
|
|
|
|
bool bHasTimecodeBoneAttributes = false;
|
|
|
|
FTimecode Timecode;
|
|
float SubFrame = 0.0f;
|
|
|
|
for (const FAnimatedBoneAttribute* RootBoneAttribute : RootBoneAttributes)
|
|
{
|
|
if (!RootBoneAttribute)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FName& BoneAttributeName = RootBoneAttribute->Identifier.GetName();
|
|
|
|
// Avoid evaluating non-timecode bone attributes.
|
|
if (!TimecodeBoneAttributeNames.Contains(BoneAttributeName))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!RootBoneAttribute->Curve.CanEvaluate())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
float FloatValue = 0.0f;
|
|
int32 IntValue = 0;
|
|
|
|
// Support timecode attribute curves that are either float-typed or integer-typed.
|
|
if (RootBoneAttribute->Curve.GetScriptStruct() == FFloatAnimationAttribute::StaticStruct())
|
|
{
|
|
const FFloatAnimationAttribute EvaluatedAttribute = RootBoneAttribute->Curve.Evaluate<FFloatAnimationAttribute>(EvalTime);
|
|
FloatValue = EvaluatedAttribute.Value;
|
|
IntValue = static_cast<int32>(FloatValue);
|
|
}
|
|
else if (RootBoneAttribute->Curve.GetScriptStruct() == FIntegerAnimationAttribute::StaticStruct())
|
|
{
|
|
const FIntegerAnimationAttribute EvaluatedAttribute = RootBoneAttribute->Curve.Evaluate<FIntegerAnimationAttribute>(EvalTime);
|
|
IntValue = EvaluatedAttribute.Value;
|
|
FloatValue = static_cast<float>(IntValue);
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (BoneAttributeName.IsEqual(TCHourAttrName))
|
|
{
|
|
Timecode.Hours = IntValue;
|
|
bHasTimecodeBoneAttributes = true;
|
|
}
|
|
else if (BoneAttributeName.IsEqual(TCMinuteAttrName))
|
|
{
|
|
Timecode.Minutes = IntValue;
|
|
bHasTimecodeBoneAttributes = true;
|
|
}
|
|
else if (BoneAttributeName.IsEqual(TCSecondAttrName))
|
|
{
|
|
Timecode.Seconds = IntValue;
|
|
bHasTimecodeBoneAttributes = true;
|
|
}
|
|
else if (BoneAttributeName.IsEqual(TCFrameAttrName))
|
|
{
|
|
Timecode.Frames = IntValue;
|
|
bHasTimecodeBoneAttributes = true;
|
|
}
|
|
else if (BoneAttributeName.IsEqual(TCSubframeAttrName))
|
|
{
|
|
SubFrame = FloatValue;
|
|
bHasTimecodeBoneAttributes = true;
|
|
}
|
|
}
|
|
|
|
if (!bHasTimecodeBoneAttributes)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// We'll fall back on the sampling frame rate if we can't determine the
|
|
// original source frame rate.
|
|
FFrameRate FrameRate = AnimationSequenceBase->GetSamplingFrameRate();
|
|
|
|
if (const UAnimSequence* AnimSequence = AnimDataModel->GetAnimationSequence())
|
|
{
|
|
if (AnimSequence->ImportFileFramerate > 0.0f)
|
|
{
|
|
FrameRate = FFrameRate(static_cast<int32>(AnimSequence->ImportFileFramerate), 1);
|
|
}
|
|
}
|
|
|
|
OutQualifiedFrameTime = FQualifiedFrameTime(
|
|
FFrameTime(Timecode.ToFrameNumber(FrameRate), SubFrame),
|
|
FrameRate);
|
|
|
|
return true;
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::FindBonePathToRoot(const UAnimSequenceBase* AnimationSequenceBase, FName BoneName, TArray<FName>& BonePath)
|
|
{
|
|
BonePath.Empty();
|
|
if (AnimationSequenceBase)
|
|
{
|
|
BonePath.Add(BoneName);
|
|
int32 BoneIndex = AnimationSequenceBase->GetSkeleton()->GetReferenceSkeleton().FindRawBoneIndex(BoneName);
|
|
if (BoneIndex != INDEX_NONE)
|
|
{
|
|
while (BoneIndex != INDEX_NONE)
|
|
{
|
|
const int32 ParentBoneIndex = AnimationSequenceBase->GetSkeleton()->GetReferenceSkeleton().GetRawParentIndex(BoneIndex);
|
|
if (ParentBoneIndex != INDEX_NONE)
|
|
{
|
|
BonePath.Add(AnimationSequenceBase->GetSkeleton()->GetReferenceSkeleton().GetBoneName(ParentBoneIndex));
|
|
}
|
|
|
|
BoneIndex = ParentBoneIndex;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Bone name %s not found in Skeleton %s"), *BoneName.ToString(), *AnimationSequenceBase->GetSkeleton()->GetName());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for FindBonePathToRoot"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::RemoveBoneAnimation(UAnimSequence* AnimationSequence, FName BoneName, bool bIncludeChildren /*= true*/, bool bFinalize /*= true*/)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
TArray<FName> TrackNames;
|
|
AnimationSequence->GetDataModel()->GetBoneTrackNames(TrackNames);
|
|
|
|
if (TrackNames.Contains(BoneName))
|
|
{
|
|
TArray<FName> TracksToRemove;
|
|
TracksToRemove.Add(BoneName);
|
|
|
|
// remove all children if required
|
|
if (bIncludeChildren)
|
|
{
|
|
USkeleton* Skeleton = AnimationSequence->GetSkeleton();
|
|
if (Skeleton)
|
|
{
|
|
const FReferenceSkeleton& RefSkeleton = Skeleton->GetReferenceSkeleton();
|
|
const int32 ParentBoneIndex = RefSkeleton.FindBoneIndex(BoneName);
|
|
|
|
// slow
|
|
for (const FName& TrackName : TrackNames)
|
|
{
|
|
if (TrackName != BoneName)
|
|
{
|
|
const int32 ChildBoneIndex = RefSkeleton.FindBoneIndex(TrackName);
|
|
if (RefSkeleton.BoneIsChildOf(ChildBoneIndex, ParentBoneIndex))
|
|
{
|
|
TracksToRemove.Add(TrackName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
IAnimationDataController& Controller = AnimationSequence->GetController();
|
|
|
|
IAnimationDataController::FScopedBracket ScopedBracket(Controller, LOCTEXT("RemoveBoneAnimation_Bracket", "Removing Bone Animation Track"));
|
|
for (const FName& TrackName : TracksToRemove)
|
|
{
|
|
Controller.RemoveBoneTrack(TrackName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// print warning with track index
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Bone Name for the animation."));
|
|
}
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::RemoveAllBoneAnimation(UAnimSequence* AnimationSequence)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
IAnimationDataController& Controller = AnimationSequence->GetController();
|
|
IAnimationDataController::FScopedBracket ScopedBracket(Controller, LOCTEXT("RemoveAllBoneAnimation_Bracket", "Removing all Bone Animation and Transform Curve Tracks"));
|
|
Controller.RemoveAllBoneTracks();
|
|
Controller.RemoveAllCurvesOfType(ERawCurveTrackTypes::RCT_Transform);
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::FinalizeBoneAnimation(UAnimSequence* AnimationSequence)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
AnimationSequence->PostProcessSequence();
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
}
|
|
}
|
|
|
|
static void RecursiveRetrieveAnimationGraphs(UEdGraph* EdGraph, TArray<UAnimationGraph*>& OutAnimationGraphs)
|
|
{
|
|
if (UAnimationGraph* AnimGraph = Cast<UAnimationGraph>(EdGraph))
|
|
{
|
|
OutAnimationGraphs.Add(AnimGraph);
|
|
}
|
|
|
|
for (TObjectPtr<class UEdGraph>& SubGraph : EdGraph->SubGraphs)
|
|
{
|
|
RecursiveRetrieveAnimationGraphs(SubGraph, OutAnimationGraphs);
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetAnimationGraphs(UAnimBlueprint* AnimationBlueprint, TArray<UAnimationGraph*>& AnimationGraphs)
|
|
{
|
|
if (AnimationBlueprint)
|
|
{
|
|
for (UEdGraph* EdGraph : AnimationBlueprint->FunctionGraphs)
|
|
{
|
|
RecursiveRetrieveAnimationGraphs(EdGraph, AnimationGraphs);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Blueprint"));
|
|
}
|
|
}
|
|
|
|
void UAnimationBlueprintLibrary::GetNodesOfClass(UAnimBlueprint* AnimationBlueprint, TSubclassOf<UAnimGraphNode_Base> NodeClass, TArray<UAnimGraphNode_Base*>& GraphNodes, bool bIncludeChildClasses /*= true*/)
|
|
{
|
|
TArray<UAnimationGraph*> AnimationGraphs;
|
|
GetAnimationGraphs(AnimationBlueprint, AnimationGraphs);
|
|
for (UAnimationGraph* AnimGraph : AnimationGraphs)
|
|
{
|
|
AnimGraph->GetGraphNodesOfClass(NodeClass, GraphNodes, bIncludeChildClasses);
|
|
}
|
|
}
|
|
|
|
template void UAnimationBlueprintLibrary::AddCurveKeysInternal<float, FFloatCurve, ERawCurveTrackTypes::RCT_Float>(UAnimSequence* AnimationSequence, FName CurveName, const TArray<float>& Times, const TArray<float>& KeyData);
|
|
template void UAnimationBlueprintLibrary::AddCurveKeysInternal<FVector, FVectorCurve, ERawCurveTrackTypes::RCT_Vector>(UAnimSequence* AnimationSequence, FName CurveName, const TArray<float>& Times, const TArray<FVector>& KeyData);
|
|
template void UAnimationBlueprintLibrary::AddCurveKeysInternal<FTransform, FTransformCurve, ERawCurveTrackTypes::RCT_Transform>(UAnimSequence* AnimationSequence, FName CurveName, const TArray<float>& Times, const TArray<FTransform>& KeyData);
|
|
|
|
template void UAnimationBlueprintLibrary::GetCurveKeysInternal<float, FFloatCurve, ERawCurveTrackTypes::RCT_Float>(UAnimSequence* AnimationSequence, FName CurveName, TArray<float>& Times, TArray<float>& KeyData);
|
|
template void UAnimationBlueprintLibrary::GetCurveKeysInternal<FVector, FVectorCurve, ERawCurveTrackTypes::RCT_Vector>(UAnimSequence* AnimationSequence, FName CurveName, TArray<float>& Times, TArray<FVector>& KeyData);
|
|
template void UAnimationBlueprintLibrary::GetCurveKeysInternal<FTransform, FTransformCurve, ERawCurveTrackTypes::RCT_Transform>(UAnimSequence* AnimationSequence, FName CurveName, TArray<float>& Times, TArray<FTransform>& KeyData);
|
|
|
|
#undef LOCTEXT_NAMESPACE // "AnimationBlueprintLibrary"
|