// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "AnimationBlueprintLibrary.h" #include "Animation/AnimSequence.h" #include "Animation/AnimationAsset.h" #include "Animation/AnimMetaData.h" #include "Animation/AnimNotifies/AnimNotifyState.h" #include "Animation/Skeleton.h" #include "Animation/AnimNotifies/AnimNotify.h" #include "BonePose.h" #include "AnimationRuntime.h" DEFINE_LOG_CATEGORY_STATIC(LogAnimationBlueprintLibrary, Verbose, All); const FName UAnimationBlueprintLibrary::SmartContainerNames[(int32)ESmartNameContainerType::SNCT_MAX] = { USkeleton::AnimCurveMappingName, USkeleton::AnimTrackCurveMappingName }; void UAnimationBlueprintLibrary::GetNumFrames(const UAnimSequence* AnimationSequence, int32& NumFrames) { NumFrames = 0; if (AnimationSequence) { NumFrames = AnimationSequence->NumFrames; } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetNumFrames")); } } void UAnimationBlueprintLibrary::GetAnimationTrackNames(const UAnimSequence* AnimationSequence, TArray& TrackNames) { TrackNames.Empty(); if (AnimationSequence) { TrackNames.Append(AnimationSequence->AnimationTrackNames); } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetAnimationTrackNames")); } } void UAnimationBlueprintLibrary::GetRawTrackPositionData(const UAnimSequence* AnimationSequence, const FName TrackName, TArray& PositionData) { PositionData.Empty(); if (IsValidRawAnimationTrackName(AnimationSequence, TrackName)) { const FRawAnimSequenceTrack& RawTrack = GetRawAnimationTrackByName(AnimationSequence, TrackName); PositionData.Append(RawTrack.PosKeys); } else { const FString AnimSequenceName = (AnimationSequence != nullptr) ? AnimationSequence->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 UAnimSequence* AnimationSequence, const FName TrackName, TArray& RotationData) { RotationData.Empty(); if (IsValidRawAnimationTrackName(AnimationSequence, TrackName)) { const FRawAnimSequenceTrack& RawTrack = GetRawAnimationTrackByName(AnimationSequence, TrackName); RotationData.Append(RawTrack.RotKeys); } else { const FString AnimSequenceName = (AnimationSequence != nullptr) ? AnimationSequence->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 UAnimSequence* AnimationSequence, const FName TrackName, TArray& ScaleData) { ScaleData.Empty(); if (IsValidRawAnimationTrackName(AnimationSequence, TrackName)) { const FRawAnimSequenceTrack& RawTrack = GetRawAnimationTrackByName(AnimationSequence, TrackName); ScaleData.Append(RawTrack.ScaleKeys); } else { const FString AnimSequenceName = (AnimationSequence != nullptr) ? AnimationSequence->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 UAnimSequence* AnimationSequence, const FName TrackName, TArray& PositionKeys, TArray& RotationKeys, TArray& ScalingKeys) { PositionKeys.Empty(); RotationKeys.Empty(); ScalingKeys.Empty(); if (IsValidRawAnimationTrackName(AnimationSequence, TrackName)) { const FRawAnimSequenceTrack& RawTrack = GetRawAnimationTrackByName(AnimationSequence, TrackName); PositionKeys.Append(RawTrack.PosKeys); RotationKeys.Append(RawTrack.RotKeys); ScalingKeys.Append(RawTrack.ScaleKeys); } else { const FString AnimSequenceName = (AnimationSequence != nullptr) ? AnimationSequence->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 UAnimSequence* AnimationSequence, const FName TrackName) { bool bValidName = false; if (AnimationSequence) { const int32 TrackIndex = AnimationSequence->AnimationTrackNames.IndexOfByKey(TrackName); bValidName = (TrackIndex != INDEX_NONE) && AnimationSequence->RawAnimationData.IsValidIndex(TrackIndex); } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for IsValidRawAnimationTrackName")); } return bValidName; } const FRawAnimSequenceTrack& UAnimationBlueprintLibrary::GetRawAnimationTrackByName(const UAnimSequence* AnimationSequence, const FName TrackName) { checkf(AnimationSequence, TEXT("Invalid Animation Sequence supplied for GetRawAnimationTrackByName")); const int32 TrackIndex = AnimationSequence->AnimationTrackNames.IndexOfByKey(TrackName); checkf(TrackIndex != INDEX_NONE, TEXT("Raw Animation Track %s does not exist in Animation Sequence %s"), *TrackName.ToString(), *AnimationSequence->GetName()); return AnimationSequence->GetRawAnimationTrack(TrackIndex); } void UAnimationBlueprintLibrary::GetCompressionScheme(const UAnimSequence* AnimationSequence, UAnimCompress*& CompressionScheme) { if (AnimationSequence) { CompressionScheme = AnimationSequence->CompressionScheme; } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetCompressionScheme")); } } void UAnimationBlueprintLibrary::SetCompressionScheme(UAnimSequence* AnimationSequence, UAnimCompress* CompressionScheme) { if (AnimationSequence) { AnimationSequence->CompressionScheme = CompressionScheme; } { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for SetCompressionScheme")); } } void UAnimationBlueprintLibrary::GetAdditiveAnimationType(const UAnimSequence* AnimationSequence, TEnumAsByte& 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 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& 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 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& LockType) { if (AnimationSequence) { LockType = AnimationSequence->RootMotionRootLock; } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for GetRootMotionLockType")); } } void UAnimationBlueprintLibrary::SetRootMotionLockType(UAnimSequence* AnimationSequence, TEnumAsByte 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& 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& 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); 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 UAnimSequence* AnimationSequence, TArray& NotifyEvents) { NotifyEvents.Empty(); if (AnimationSequence) { NotifyEvents = AnimationSequence->Notifies; } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for GetAnimationNotifyEvents")); } } void UAnimationBlueprintLibrary::GetAnimationNotifyEventNames(const UAnimSequence* AnimationSequence, TArray& EventNames) { EventNames.Empty(); if (AnimationSequence) { for (const FAnimNotifyEvent& Event : AnimationSequence->Notifies) { EventNames.AddUnique(Event.NotifyName); } } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for GetAnimationNotifyEventNames")); } } UAnimNotify* UAnimationBlueprintLibrary::AddAnimationNotifyEvent(UAnimSequence* AnimationSequence, FName NotifyTrackName, float StartTime, float Duration, TSubclassOf NotifyClass) { UAnimNotify* Notify = nullptr; if (AnimationSequence) { const bool bIsValidTrackName = IsValidAnimNotifyTrackName(AnimationSequence, NotifyTrackName); const bool bIsValidTime = IsValidTimeInternal(AnimationSequence, StartTime); if (bIsValidTrackName && bIsValidTime) { AnimationSequence->Notifies.AddDefaulted(); FAnimNotifyEvent& NewEvent = AnimationSequence->Notifies.Last(); NewEvent.NotifyName = NAME_None; NewEvent.Link(AnimationSequence, StartTime); NewEvent.TriggerTimeOffset = GetTriggerTimeOffsetForType(AnimationSequence->CalculateOffsetForNotify(StartTime)); NewEvent.TrackIndex = GetTrackIndexForAnimationNotifyTrackName(AnimationSequence, NotifyTrackName); if (NotifyClass) { class UObject* AnimNotifyClass = NewObject(AnimationSequence, NotifyClass, NAME_None, RF_Transactional); NewEvent.NotifyStateClass = Cast(AnimNotifyClass); NewEvent.Notify = Cast(AnimNotifyClass); // Setup name and duration for new event if (NewEvent.NotifyStateClass) { NewEvent.NotifyName = FName(*NewEvent.NotifyStateClass->GetNotifyName()); NewEvent.SetDuration(Duration); NewEvent.EndLink.Link(AnimationSequence, NewEvent.EndLink.GetTime()); } else if(NewEvent.Notify) { NewEvent.NotifyName = FName(*NewEvent.Notify->GetNotifyName()); } } else { NewEvent.Notify = NULL; NewEvent.NotifyStateClass = NULL; } // Refresh all cached data AnimationSequence->RefreshCacheData(); Notify = NewEvent.Notify; } else { if (!bIsValidTrackName) { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Animation Notify Track %s does not exist on Animation Sequence %s"), *NotifyTrackName.ToString(), *AnimationSequence->GetName()); } if (!bIsValidTime) { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("%f is side of Animation Sequence %s range"), StartTime, *AnimationSequence->GetName()); } } } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for AddAnimationNotifyEvent")); } return Notify; } void UAnimationBlueprintLibrary::AddAnimationNotifyEventObject(UAnimSequence* AnimationSequence, float StartTime, UAnimNotify* Notify, FName NotifyTrackName) { if (AnimationSequence) { const bool bValidNotify = Notify != nullptr; const bool bValidOuter = bValidNotify && Notify->GetOuter() == AnimationSequence; const bool bIsValidTrackName = IsValidAnimNotifyTrackName(AnimationSequence, NotifyTrackName); const bool bIsValidTime = IsValidTimeInternal(AnimationSequence, StartTime); if (bValidNotify && bValidOuter && bIsValidTrackName && bIsValidTime) { AnimationSequence->Notifies.AddDefaulted(); FAnimNotifyEvent& NewEvent = AnimationSequence->Notifies.Last(); NewEvent.NotifyName = NAME_None; NewEvent.Link(AnimationSequence, StartTime); NewEvent.TriggerTimeOffset = GetTriggerTimeOffsetForType(AnimationSequence->CalculateOffsetForNotify(StartTime)); NewEvent.TrackIndex = GetTrackIndexForAnimationNotifyTrackName(AnimationSequence, NotifyTrackName); NewEvent.NotifyStateClass = Cast(Notify); NewEvent.Notify = Notify; // Refresh all cached data AnimationSequence->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, *AnimationSequence->GetName()); } if (!bIsValidTrackName) { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Animation Notify Track %s does not exist on Animation Sequence %s"), *NotifyTrackName.ToString(), *AnimationSequence->GetName()); } if (!bIsValidTime) { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("%f is side of Animation Sequence %s range"), StartTime, *AnimationSequence->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); UObject* AnimNotifyClass = NewObject(AnimationSequence, NewNotifyClass, NAME_None, RF_Transactional); NewEvent.NotifyStateClass = Cast(AnimNotifyClass); NewEvent.Notify = Cast(AnimNotifyClass); // Setup name and duration for new event if (NewEvent.NotifyStateClass) { NewEvent.NotifyName = FName(*NewEvent.NotifyStateClass->GetNotifyName()); 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* AnimationSequence, TSubclassOf OldNotifyClass, TSubclassOf NewNotifyClass, FOnNotifyStateReplaced OnNotifyStateReplaced) { ReplaceAnimNotifies_Helper(AnimationSequence, OldNotifyClass.Get(), NewNotifyClass.Get(), FOnNotifyReplaced(), OnNotifyStateReplaced); } void UAnimationBlueprintLibrary::ReplaceAnimNotifies(UAnimSequenceBase* AnimationSequence, TSubclassOf OldNotifyClass, TSubclassOf NewNotifyClass, FOnNotifyReplaced OnNotifyReplaced) { ReplaceAnimNotifies_Helper(AnimationSequence, OldNotifyClass.Get(), NewNotifyClass.Get(), OnNotifyReplaced, FOnNotifyStateReplaced()); } void UAnimationBlueprintLibrary::CopyAnimNotifiesFromSequence(UAnimSequence* SrcAnimSequence, UAnimSequence* DestAnimSequence) { if (SrcAnimSequence == nullptr) { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Source Animation Sequence for CopyAnimNotifiesFromSequence")); } else if (DestAnimSequence == nullptr) { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Destination Animation Sequence for CopyAnimNotifiesFromSequence")); } else if (SrcAnimSequence == DestAnimSequence) { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Source and Destination Animation Sequence are the same for CopyAnimNotifiesFromSequence")); } else { const bool bShowDialogs = false; UAnimSequence::CopyNotifies(SrcAnimSequence, DestAnimSequence, bShowDialogs); } } int32 UAnimationBlueprintLibrary::RemoveAnimationNotifyEventsByName(UAnimSequence* AnimationSequence, FName NotifyName) { int32 NumRemovedEvents = 0; if (AnimationSequence) { NumRemovedEvents = AnimationSequence->Notifies.RemoveAll( [&](const FAnimNotifyEvent& Event) { return Event.NotifyName == NotifyName; }); // Refresh all cached data AnimationSequence->RefreshCacheData(); } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for RemoveAnimationNotifyEventsByName")); } return NumRemovedEvents; } int32 UAnimationBlueprintLibrary::RemoveAnimationNotifyEventsByTrack(UAnimSequence* AnimationSequence, FName NotifyTrackName) { int32 NumRemovedEvents = 0; if (AnimationSequence) { const bool bIsValidTrackName = IsValidAnimNotifyTrackName(AnimationSequence, NotifyTrackName); if (bIsValidTrackName) { const int32 TrackIndex = GetTrackIndexForAnimationNotifyTrackName(AnimationSequence, NotifyTrackName); NumRemovedEvents = AnimationSequence->Notifies.RemoveAll( [&](const FAnimNotifyEvent& Event) { return Event.TrackIndex == TrackIndex; }); // 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 RemoveAnimationNotifyEventsByTrack")); } return NumRemovedEvents; } void UAnimationBlueprintLibrary::GetAnimationNotifyTrackNames(const UAnimSequence* AnimationSequence, TArray& TrackNames) { TrackNames.Empty(); if (AnimationSequence) { for (const FAnimNotifyTrack& Track : AnimationSequence->AnimNotifyTracks) { TrackNames.AddUnique(Track.TrackName); } } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for GetAnimationNotifyTrackNames")); } } void UAnimationBlueprintLibrary::AddAnimationNotifyTrack(UAnimSequence* AnimationSequence, FName NotifyTrackName, FLinearColor TrackColor /*= FLinearColor::White*/) { if (AnimationSequence) { const bool bExistingTrackName = IsValidAnimNotifyTrackName(AnimationSequence, NotifyTrackName); if (!bExistingTrackName) { FAnimNotifyTrack NewTrack; NewTrack.TrackName = NotifyTrackName; NewTrack.TrackColor = TrackColor; AnimationSequence->AnimNotifyTracks.Add(NewTrack); // Refresh all cached data AnimationSequence->RefreshCacheData(); } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Animation Notify Track %s already exists on Animation Sequence %s"), *NotifyTrackName.ToString(), *AnimationSequence->GetName()); } } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for AddAnimationNotifyTrack")); } } void UAnimationBlueprintLibrary::RemoveAnimationNotifyTrack(UAnimSequence* AnimationSequence, FName NotifyTrackName) { if (AnimationSequence) { const int32 TrackIndexToDelete = GetTrackIndexForAnimationNotifyTrackName(AnimationSequence, NotifyTrackName); if (TrackIndexToDelete != INDEX_NONE) { // Remove all notifies and sync markers on the to-delete-track AnimationSequence->Notifies.RemoveAll([&](const FAnimNotifyEvent& Notify) { return Notify.TrackIndex == TrackIndexToDelete; }); AnimationSequence->AuthoredSyncMarkers.RemoveAll([&](const FAnimSyncMarker& Marker) { return Marker.TrackIndex == TrackIndexToDelete; }); // Before track removal, make sure everything behind is fixed for (FAnimNotifyEvent& Notify : AnimationSequence->Notifies) { if (Notify.TrackIndex > TrackIndexToDelete) { Notify.TrackIndex = Notify.TrackIndex - 1; } } for (FAnimSyncMarker& SyncMarker : AnimationSequence->AuthoredSyncMarkers) { if (SyncMarker.TrackIndex > TrackIndexToDelete) { SyncMarker.TrackIndex = SyncMarker.TrackIndex - 1; } } // Delete the track itself AnimationSequence->AnimNotifyTracks.RemoveAt(TrackIndexToDelete); // Refresh all cached data AnimationSequence->RefreshCacheData(); } } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for RemoveAnimationNotifyTrack")); } } void UAnimationBlueprintLibrary::RemoveAllAnimationNotifyTracks(UAnimSequence* AnimationSequence) { if (AnimationSequence) { AnimationSequence->Notifies.Empty(); AnimationSequence->AuthoredSyncMarkers.Empty(); // Remove all but one notify tracks AnimationSequence->AnimNotifyTracks.SetNum(1); // Also remove all stale notifies and sync markers from only track AnimationSequence->AnimNotifyTracks[0].Notifies.Empty(); AnimationSequence->AnimNotifyTracks[0].SyncMarkers.Empty(); // Refresh all cached data AnimationSequence->RefreshCacheData(); } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for RemoveAllAnimationNotifyTracks")); } } bool UAnimationBlueprintLibrary::IsValidAnimNotifyTrackName(const UAnimSequence* AnimationSequence, FName NotifyTrackName) { bool bIsValid = false; if (AnimationSequence) { bIsValid = GetTrackIndexForAnimationNotifyTrackName(AnimationSequence, NotifyTrackName) != INDEX_NONE; } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for IsValidAnimNotifyTrackName")); } return bIsValid; } int32 UAnimationBlueprintLibrary::GetTrackIndexForAnimationNotifyTrackName(const UAnimSequence* AnimationSequence, FName NotifyTrackName) { return AnimationSequence->AnimNotifyTracks.IndexOfByPredicate( [&](const FAnimNotifyTrack& Track) { return Track.TrackName == NotifyTrackName; }); } const FAnimNotifyTrack& UAnimationBlueprintLibrary::GetNotifyTrackByName(const UAnimSequence* AnimationSequence, FName NotifyTrackName) { const int32 TrackIndex = GetTrackIndexForAnimationNotifyTrackName(AnimationSequence, NotifyTrackName); checkf(TrackIndex != INDEX_NONE, TEXT("Notify Track %s does not exist on %s"), *NotifyTrackName.ToString(), *AnimationSequence->GetName()); return AnimationSequence->AnimNotifyTracks[TrackIndex]; } float UAnimationBlueprintLibrary::GetAnimNotifyEventTriggerTime(const FAnimNotifyEvent& NotifyEvent) { return NotifyEvent.GetTriggerTime(); } void UAnimationBlueprintLibrary::GetAnimationSyncMarkersForTrack(const UAnimSequence* AnimationSequence, FName NotifyTrackName, TArray& 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 UAnimSequence* AnimationSequence, FName NotifyTrackName, TArray& Events) { Events.Empty(); if (AnimationSequence) { const bool bIsValidTrackName = IsValidAnimNotifyTrackName(AnimationSequence, NotifyTrackName); if (bIsValidTrackName) { const FAnimNotifyTrack& Track = GetNotifyTrackByName(AnimationSequence, 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(), *AnimationSequence->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 bool bCurveAdded = AddCurveInternal(AnimationSequence, CurveName, SmartContainerNames[(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) { AnimationSequence->RawCurveData.DeleteAllCurveData(ERawCurveTrackTypes::RCT_Float); AnimationSequence->RawCurveData.DeleteAllCurveData(ERawCurveTrackTypes::RCT_Vector); AnimationSequence->RawCurveData.DeleteAllCurveData(ERawCurveTrackTypes::RCT_Transform); AnimationSequence->bNeedsRebake = true; } 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 TimeArray; TArray TransformArray; TimeArray.Add(Time); TransformArray.Add(Transform); AddCurveKeysInternal(AnimationSequence, CurveName, TimeArray, TransformArray, ERawCurveTrackTypes::RCT_Transform); AnimationSequence->bNeedsRebake = true; } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for AddTransformationCurveKey")); } } void UAnimationBlueprintLibrary::AddTransformationCurveKeys(UAnimSequence* AnimationSequence, FName CurveName, const TArray& Times, const TArray& Transforms) { if (AnimationSequence) { if (Times.Num() == Transforms.Num()) { AddCurveKeysInternal(AnimationSequence, CurveName, Times, Transforms, ERawCurveTrackTypes::RCT_Transform); AnimationSequence->bNeedsRebake = true; } 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 TimeArray; TArray ValueArray; TimeArray.Add(Time); ValueArray.Add(Value); AddCurveKeysInternal(AnimationSequence, CurveName, TimeArray, ValueArray, ERawCurveTrackTypes::RCT_Float); } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for AddFloatCurveKey")); } } void UAnimationBlueprintLibrary::AddFloatCurveKeys(UAnimSequence* AnimationSequence, FName CurveName, const TArray& Times, const TArray& Values) { if (AnimationSequence) { if (Times.Num() == Values.Num()) { AddCurveKeysInternal(AnimationSequence, CurveName, Times, Values, ERawCurveTrackTypes::RCT_Float); } 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 TimeArray; TArray VectorArray; TimeArray.Add(Time); VectorArray.Add(Vector); AddCurveKeysInternal(AnimationSequence, CurveName, TimeArray, VectorArray, ERawCurveTrackTypes::RCT_Vector); } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for AddVectorCurveKey")); } } void UAnimationBlueprintLibrary::AddVectorCurveKeys(UAnimSequence* AnimationSequence, FName CurveName, const TArray& Times, const TArray& Vectors) { if (AnimationSequence) { if (Times.Num() == Vectors.Num()) { AddCurveKeysInternal(AnimationSequence, CurveName, Times, Vectors, ERawCurveTrackTypes::RCT_Vector); } 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")); } } template void UAnimationBlueprintLibrary::AddCurveKeysInternal(UAnimSequence* AnimationSequence, FName CurveName, const TArray& Times, const TArray& KeyData, ERawCurveTrackTypes CurveType) { 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 CurveClass* Curve = static_cast(AnimationSequence->RawCurveData.GetCurveData(CurveSmartName.UID, CurveType)); if (Curve) { const int32 NumKeys = KeyData.Num(); for (int32 KeyIndex = 0; KeyIndex < NumKeys; ++KeyIndex) { Curve->UpdateOrAddKey(KeyData[KeyIndex], Times[KeyIndex]); } AnimationSequence->BakeTrackCurvesToRawAnimation(); } } } 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); bool bCurveAdded = false; if (AnimationSequence->RawCurveData.GetCurveData(SmartCurveName.UID) == nullptr) { bCurveAdded = AnimationSequence->RawCurveData.AddCurveData(SmartCurveName, CurveFlags, SupportedCurveType); } 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)) { if (ContainerName == USkeleton::AnimTrackCurveMappingName) { bRemoved = AnimationSequence->RawCurveData.DeleteCurveData(SmartCurveName, ERawCurveTrackTypes::RCT_Transform); AnimationSequence->bNeedsRebake = true; } else { bRemoved = AnimationSequence->RawCurveData.DeleteCurveData(SmartCurveName, ERawCurveTrackTypes::RCT_Float); bRemoved |= AnimationSequence->RawCurveData.DeleteCurveData(SmartCurveName, ERawCurveTrackTypes::RCT_Vector); } 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 = DoesBoneNameExistInternal(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->GetUIDByName(USkeleton::AnimTrackCurveMappingName, BoneName) != SmartName::MaxUID; } void UAnimationBlueprintLibrary::GetFloatKeys(UAnimSequence* AnimationSequence, FName CurveName, TArray& Times, TArray& Values) { if (AnimationSequence) { GetCurveKeysInternal(AnimationSequence, CurveName, Times, Values, ERawCurveTrackTypes::RCT_Float); } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for GetFloatKeys")); } } void UAnimationBlueprintLibrary::GetVectorKeys(UAnimSequence* AnimationSequence, FName CurveName, TArray& Times, TArray& Values) { if (AnimationSequence) { GetCurveKeysInternal(AnimationSequence, CurveName, Times, Values, ERawCurveTrackTypes::RCT_Vector); } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for GetVectorKeys")); } } void UAnimationBlueprintLibrary::GetTransformationKeys(UAnimSequence* AnimationSequence, FName CurveName, TArray& Times, TArray& Values) { if (AnimationSequence) { GetCurveKeysInternal(AnimationSequence, CurveName, Times, Values, ERawCurveTrackTypes::RCT_Transform); } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for GetTransformationKeys")); } } template void UAnimationBlueprintLibrary::GetCurveKeysInternal(UAnimSequence* AnimationSequence, FName CurveName, TArray& Times, TArray& KeyData, ERawCurveTrackTypes CurveType) { 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 CurveClass* Curve = static_cast(AnimationSequence->RawCurveData.GetCurveData(CurveSmartName.UID, CurveType)); 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)) { FAnimCurveBase* Curve = AnimationSequence->RawCurveData.GetCurveData(SmartName.UID, CurveType); bExistingCurve = Curve != nullptr; } if (RetrieveSmartNameForCurve(AnimationSequence, CurveName, USkeleton::AnimCurveMappingName, SmartName)) { FAnimCurveBase* Curve = AnimationSequence->RawCurveData.GetCurveData(SmartName.UID, CurveType); 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")); for (int32 Index = 0; Index < (int32)ESmartNameContainerType::SNCT_MAX; ++Index) { const FSmartNameMapping* CurveMapping = AnimationSequence->GetSkeleton()->GetSmartNameContainer(SmartContainerNames[Index]); if (CurveMapping->Exists(CurveName)) { return SmartContainerNames[Index]; } } return NAME_None; } void UAnimationBlueprintLibrary::AddMetaData(UAnimSequence* AnimationSequence, TSubclassOf MetaDataClass, UAnimMetaData*& MetaDataInstance) { if (AnimationSequence) { MetaDataInstance = NewObject(AnimationSequence, MetaDataClass, NAME_None, RF_Transactional); if (MetaDataInstance) { AnimationSequence->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(UAnimSequence* AnimationSequence, UAnimMetaData* MetaDataObject) { if (AnimationSequence && MetaDataObject) { if (MetaDataObject->GetOuter() == AnimationSequence) { AnimationSequence->AddMetaData(MetaDataObject); } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Outer for MetaData Instance %s is not Animation Sequence %s"), *MetaDataObject->GetName(), *AnimationSequence->GetName()); } } else { if (!AnimationSequence) { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for AddMetaDataObject")); } if (!MetaDataObject) { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid MetaDataObject for AddMetaDataObject")); } } } void UAnimationBlueprintLibrary::RemoveAllMetaData(UAnimSequence* AnimationSequence) { if (AnimationSequence) { AnimationSequence->EmptyMetaData(); } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for RemoveAllMetaData")); } } void UAnimationBlueprintLibrary::RemoveMetaData(UAnimSequence* AnimationSequence, UAnimMetaData* MetaDataObject) { if (AnimationSequence) { AnimationSequence->RemoveMetaData(MetaDataObject); } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for RemoveMetaData")); } } void UAnimationBlueprintLibrary::RemoveMetaDataOfClass(UAnimSequence* AnimationSequence, TSubclassOf MetaDataClass) { if (AnimationSequence) { TArray MetaDataOfClass; GetMetaDataOfClass(AnimationSequence, MetaDataClass, MetaDataOfClass); AnimationSequence->RemoveMetaData(MetaDataOfClass); } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for RemoveMetaDataOfClass")); } } void UAnimationBlueprintLibrary::GetMetaData(const UAnimSequence* AnimationSequence, TArray& MetaData) { MetaData.Empty(); if (AnimationSequence) { MetaData = AnimationSequence->GetMetaData(); } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for GetMetaData")); } } void UAnimationBlueprintLibrary::GetMetaDataOfClass(const UAnimSequence* AnimationSequence, TSubclassOf MetaDataClass, TArray& MetaDataOfClass) { MetaDataOfClass.Empty(); if (AnimationSequence) { const TArray& MetaData = AnimationSequence->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 UAnimSequence* AnimationSequence, TSubclassOf MetaDataClass) { bool bContainsMetaData = false; if (AnimationSequence) { TArray MetaData; GetMetaData(AnimationSequence, 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 UAnimSequence* AnimationSequence, FName BoneName, float Time, bool bExtractRootMotion, FTransform& Pose) { Pose.SetIdentity(); if (AnimationSequence) { TArray BoneNameArray; TArray PoseArray; BoneNameArray.Add(BoneName); GetBonePosesForTime(AnimationSequence, BoneNameArray, Time, bExtractRootMotion, PoseArray); Pose = PoseArray[0]; } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetBonePoseForTime")); } } void UAnimationBlueprintLibrary::GetBonePoseForFrame(const UAnimSequence* AnimationSequence, FName BoneName, int32 Frame, bool bExtractRootMotion, FTransform& Pose) { Pose.SetIdentity(); if (AnimationSequence) { GetBonePoseForTime(AnimationSequence, BoneName, GetTimeAtFrameInternal(AnimationSequence, Frame), bExtractRootMotion, Pose); } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetBonePoseForFrame")); } } void UAnimationBlueprintLibrary::GetBonePosesForTime(const UAnimSequence* AnimationSequence, TArray BoneNames, float Time, bool bExtractRootMotion, TArray& Poses, const USkeletalMesh* PreviewMesh /*= nullptr*/) { Poses.Empty(BoneNames.Num()); if (AnimationSequence && AnimationSequence->GetSkeleton()) { Poses.AddDefaulted(BoneNames.Num()); // Need this for FCompactPose FMemMark Mark(FMemStack::Get()); const FReferenceSkeleton& RefSkeleton = (PreviewMesh)? PreviewMesh->RefSkeleton : AnimationSequence->GetSkeleton()->GetReferenceSkeleton(); if (IsValidTimeInternal(AnimationSequence, Time)) { if (BoneNames.Num()) { for (int32 BoneNameIndex = 0; BoneNameIndex < BoneNames.Num(); ++BoneNameIndex) { const FName& BoneName = BoneNames[BoneNameIndex]; FTransform& Transform = Poses[BoneNameIndex]; if (IsValidRawAnimationTrackName(AnimationSequence, BoneName)) { AnimationSequence->ExtractBoneTransform(GetRawAnimationTrackByName(AnimationSequence, BoneName), Transform, Time); } 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(), *AnimationSequence->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"), *AnimationSequence->GetName()); } } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid time value %f for Animation Sequence %s supplied for GetBonePosesForTime"), Time, *AnimationSequence->GetName()); } } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetBonePosesForTime")); } } void UAnimationBlueprintLibrary::GetBonePosesForFrame(const UAnimSequence* AnimationSequence, TArray BoneNames, int32 Frame, bool bExtractRootMotion, TArray& Poses, const USkeletalMesh* PreviewMesh /*= nullptr*/) { Poses.Empty(BoneNames.Num()); if (AnimationSequence) { GetBonePosesForTime(AnimationSequence, BoneNames, GetTimeAtFrameInternal(AnimationSequence, 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 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 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 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 UAnimSequence* AnimationSequence, float& Length) { Length = 0.0f; if (AnimationSequence) { Length = AnimationSequence->SequenceLength; } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetSequenceLength")); } } void UAnimationBlueprintLibrary::GetRateScale(const UAnimSequence* AnimationSequence, float& RateScale) { RateScale = 0.0f; if (AnimationSequence) { RateScale = AnimationSequence->RateScale; } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetRateScale")); } } void UAnimationBlueprintLibrary::SetRateScale(UAnimSequence* AnimationSequence, float RateScale) { if (AnimationSequence) { AnimationSequence->RateScale = RateScale; } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for SetRateScale")); } } void UAnimationBlueprintLibrary::GetFrameAtTime(const UAnimSequence* AnimationSequence, const float Time, int32& Frame) { Frame = 0; if (AnimationSequence) { Frame = AnimationSequence->GetFrameAtTime(Time); } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetFrameAtTime")); } } void UAnimationBlueprintLibrary::GetTimeAtFrame(const UAnimSequence* AnimationSequence, const int32 Frame, float& Time) { Time = 0.0f; if (AnimationSequence) { Time = GetTimeAtFrameInternal(AnimationSequence, Frame); } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for GetTimeAtFrame")); } } float UAnimationBlueprintLibrary::GetTimeAtFrameInternal(const UAnimSequence* AnimationSequence, const int32 Frame) { return AnimationSequence->GetTimeAtFrame(Frame); } void UAnimationBlueprintLibrary::IsValidTime(const UAnimSequence* AnimationSequence, const float Time, bool& IsValid) { IsValid = false; if (AnimationSequence) { IsValid = IsValidTimeInternal(AnimationSequence, Time); } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence supplied for IsValidTime")); } } bool UAnimationBlueprintLibrary::IsValidTimeInternal(const UAnimSequence* AnimationSequence, const float Time) { return FMath::IsWithinInclusive(Time, 0.0f, AnimationSequence->SequenceLength); } void UAnimationBlueprintLibrary::FindBonePathToRoot(const UAnimSequence* AnimationSequence, FName BoneName, TArray& BonePath) { BonePath.Empty(); if (AnimationSequence) { BonePath.Add(BoneName); int32 BoneIndex = AnimationSequence->GetSkeleton()->GetReferenceSkeleton().FindRawBoneIndex(BoneName); if (BoneIndex != INDEX_NONE) { while (BoneIndex != INDEX_NONE) { const int32 ParentBoneIndex = AnimationSequence->GetSkeleton()->GetReferenceSkeleton().GetRawParentIndex(BoneIndex); if (ParentBoneIndex != INDEX_NONE) { BonePath.Add(AnimationSequence->GetSkeleton()->GetReferenceSkeleton().GetBoneName(ParentBoneIndex)); } BoneIndex = ParentBoneIndex; } } else { UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Bone name %s not found in Skeleton %s"), *BoneName.ToString(), *AnimationSequence->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) { const TArray& TrackNames = AnimationSequence->GetAnimationTrackNames(); const int32 TrackIndex = TrackNames.Find(BoneName); if (TrackIndex != INDEX_NONE) { TArray TracksToRemove; TracksToRemove.Add(TrackIndex); // 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 (int32 ChildTrackIndex = 0; ChildTrackIndex < TrackNames.Num(); ++ChildTrackIndex) { if (TrackIndex != ChildTrackIndex) { const int32 ChildBoneIndex = RefSkeleton.FindBoneIndex(TrackNames[ChildTrackIndex]); if (RefSkeleton.BoneIsChildOf(ChildBoneIndex, ParentBoneIndex)) { TracksToRemove.Add(ChildTrackIndex); } } } } } TracksToRemove.Sort([](const int32& A, const int32& B) { return A < B; }); // go reverse since we're removing by index for (int32 Index = TracksToRemove.Num() - 1; Index >= 0; --Index) { AnimationSequence->RemoveTrack(TracksToRemove[Index]); } if (bFinalize) { AnimationSequence->PostProcessSequence(); } } else { // print warning with track index UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Bone Name for the animation.")); } } } void UAnimationBlueprintLibrary::RemoveAllBoneAnimation(UAnimSequence* AnimationSequence) { if (AnimationSequence) { AnimationSequence->RemoveAllTracks(); } } void UAnimationBlueprintLibrary::FinalizeBoneAnimation(UAnimSequence* AnimationSequence) { if (AnimationSequence) { AnimationSequence->PostProcessSequence(); } } template void UAnimationBlueprintLibrary::AddCurveKeysInternal(UAnimSequence* AnimationSequence, FName CurveName, const TArray& Times, const TArray& KeyData, ERawCurveTrackTypes CurveType); template void UAnimationBlueprintLibrary::AddCurveKeysInternal(UAnimSequence* AnimationSequence, FName CurveName, const TArray& Times, const TArray& KeyData, ERawCurveTrackTypes CurveType); template void UAnimationBlueprintLibrary::AddCurveKeysInternal(UAnimSequence* AnimationSequence, FName CurveName, const TArray& Times, const TArray& KeyData, ERawCurveTrackTypes CurveType); template void UAnimationBlueprintLibrary::GetCurveKeysInternal(UAnimSequence* AnimationSequence, FName CurveName, TArray& Times, TArray& KeyData, ERawCurveTrackTypes CurveType); template void UAnimationBlueprintLibrary::GetCurveKeysInternal(UAnimSequence* AnimationSequence, FName CurveName, TArray& Times, TArray& KeyData, ERawCurveTrackTypes CurveType); template void UAnimationBlueprintLibrary::GetCurveKeysInternal(UAnimSequence* AnimationSequence, FName CurveName, TArray& Times, TArray& KeyData, ERawCurveTrackTypes CurveType);