You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#lockdown Nick.Penwarden #rb none ============================ MAJOR FEATURES & CHANGES ============================ Change 3624599 by Thomas.Sarkanen Added the ability to rename shapes in the Physics Asset Editor Added "CanRenameItem" to skeleton tree item API so we are not limited to hard-coded bones/sockets Tweaked physics shape item widget to use editable text in the same vein as virtual bones etc. #jira UEAP-341 - Ability to name collision shapes Change 3624765 by Benn.Gallagher Fixed bad blend profile references #jira UE-46227 Change 3624773 by Danny.Bouimad Content fix for #Jira UE-49191 Change 3625007 by Thomas.Sarkanen Fixed monolithinc game builds Moved new Name member to WITH_EDITORONLY_DATA, as the generated code still picks it up using WITH_EDITOR Change 3625659 by Ori.Cohen Make sure that components being unwelded are always unwelded even if they are about to be deleted. This is needed for fixing dangling pointers. Change 3625850 by Thomas.Sarkanen Fix for crash in physics asset editor after garbage collection Move bone proxies from rooting to FGCObject Change 3625966 by Lina.Halper Instead of PinShownByDefault, changed to PinHiddenByDefault https://github.com/EpicGames/UnrealEngine/pull/3964 #3964 #jira: UE-49168 Change 3626020 by Martin.Wilson Protect against checkSlow when using post process instance without a main instance #jira UE-49275 Change 3627178 by Aaron.McLeran #jira UE-49322 Fixing background muting and preview sound Change 3627179 by Aaron.McLeran Optimizing active sound by not processing active sounds if they are out of range. Allowing virtualized sounds to be exempt. Licensee says they saw a 6x improvement on active sound calculations in audio thread with this change. Change 3627187 by Aaron.McLeran Allowing overriding the sample rate of synth components in C++. Useful for cases where synth component is being used to output media auido or VOIP. Change 3627563 by Thomas.Sarkanen Tweaked tooltip so it isnt the same as other menus #jira UE-47817 - Two Viewport tooltips are the same in Physics Asset Editor Change 3627580 by James.Golding PR #3974: UE-49200: Fixed typo in Physics Handle (Contributed by carloshellin) #jira UE-49264 Change 3627581 by James.Golding Reduce output verbosity during cooking #jira UE-47126 Change 3627584 by James.Golding PR #3954: Upgrade to V-HACD version 2.3 (Contributed by jratcliff63367) Auto-convex generation now exposes more useful 'max hulls' instead of 'accuracy' Auto-generation of convex collision is now done async in StaticMesh Editor #jira UE-49027 Change 3627599 by Martin.Wilson Make sure raw data debug bone rendering in the animation editors actually shows raw data in the case of additive track layers (used to show source instead) Change 3627605 by James.Golding Forgot to remove Box2D from TargetRules.cs (see CL 3555437) Change 3627627 by Martin.Wilson Change raw data evaluation so that virtual bone positions are built before interpolation is carried out #jira UE-42659 Change 3627663 by Martin.Wilson Fix typo Change 3627730 by Martin.Wilson Allow notifies to be trigger on follower animations in a sync group #jira UE-46770 Change 3627852 by Thomas.Sarkanen Add warning to "Use Async Scene" property when shown in the physics asset editor, if the project doesn't currently use an async scene. #jira UE-47964 User is not told to Enable Async Scene in Project Settings when enabling it on a physics asset Change 3627864 by Lina.Halper Fix issue where "reset to default" on search box for bone doesn't work #jira: UE-48874 Change 3627946 by Thomas.Sarkanen Prevent undo/redo breaking when moving both a constraint and a body at the same time #jira UE-49344 - Physics Asset Editor: Moving both a body and a constraint causes undo.redo to break for the whole editor Change 3628091 by Thomas.Sarkanen Fix dangling lines, poor search focus and graph not refreshing when making new constraints Found by Nick D in Main. #jira UE-47812 - Physics Asset Graph wires sometimes get stuck to the window not attached to a node Change 3628107 by Lina.Halper Fixed issue where Blendspace 1D can't scale due to the property not exposed https://udn.unrealengine.com/questions/389958/input-interpolationaxis-to-scale-in-1d-blendspace.html Change 3628108 by Arciel.Rekman Update Linux VHACD. - Also removed arm 32-bit version (the library is editor-only). Change 3628437 by Michael.Trepka Updated Mac VHACD libraries and Xcode project Change 3628667 by Lina.Halper - Fixed issue of showing combo box multiple times - Fixed issue of inconsistent combo box width - Fixed text of pick bone to "select" for more general instruction - Fixed issue with struct displaying children when pin is enabled #jira: UE-49295, UE-46496, UE-47427 Change 3629744 by Aaron.McLeran #jira UE-49383 Fix for source bus loading in sound waves and playing without audio mixer Change 3629846 by Aaron.McLeran #jira UE-49390 Required API change to spatialization interface for google Change 3630322 by Thomas.Sarkanen Fix right-click not displaying context menu for constraints correctly Selection logic was lightly broken #jira UE-49399 - Physics Asset Editor: Right-clicking constraints in the viewport does not bring up the context menu Change 3630463 by Martin.Wilson Remove accidently submitted debug code Change 3630523 by Jurre.deBaare Paint threshold and fill value and can be set to negative numbers #fix Added metadata and default values for cloth fill tool #jira UE-48352 Change 3632009 by Aaron.McLeran #jira UE-49470 Fix for iOS master volume not getting set Recent changes to master volume resulted in platforms which don't have a headroom value defined will not get their master volume updated. IOS doesn't have a headroom value set so the master volume is never set and the fade in is never triggered. Change 3632699 by Thomas.Sarkanen Fix crash undo-ing primitive regeneration while simullation is in progress & stopping simulation #jira UE-49283 - Editor crashes if you regenerate and manipulate a phys body, simulate, undo and then exit simulation Change 3633336 by James.Golding PR #3978: effect is the noun. affect is the verb (Contributed by cdietschrun) #jira UE-49324 Change 3634665 by Aaron.McLeran #jira UE-49538 Fixing param interpolation Change3634922by James.Golding Static analysis fix (PhysXCookHelper.cpp) Change 3634926 by James.Golding Fix HTML5 build (which builds with PhysX, but without APEX) Change 3636005 by Thomas.Sarkanen Constraint setup shortcuts are now undo-able Also fixed body-body collision as you couldnt undo this either. Added transaction and calls to Modify(). #jira UE-49484 - Shortcut for Swing1Motion (2, and 3) do not change physics asset state dirty. Change 3636018 by Thomas.Sarkanen Added back constraint shortcut to PhAT toolbar #jira UE-48859 - Constraint quick set buttons are missing in the new Physics Asset tool Change 3636086 by Martin.Wilson Fix for enabling Live Link plugin in Orion Change 3638367 by Thomas.Sarkanen Connection reporting is now more user-freindly in the physics asset editor graph view Expanded UEdGraphSchema API to allow for more specific feedback when dragging over a graph. Implemented node & pin feedback for physics asset graphs. Also fixed alignment of icon for drag feedback as it stretches with multi-line text. #jira UE-47984 - No node created when dragging off of Constraint node in Physics Asset Graph Change3640144by Aaron.McLeran #jira UE-49409 Attenuation focus audio tests on TM-AnimPhys on Cooked mac doesn't play any audio Fixing the recent optimization to not play active sounds in range. Code attempts to check if there's any possibility for a sound to have it's distance affected before trying to prune by max distance. Change 3640276 by Aaron.McLeran #jira UE-49606 Project does not cook with actors containing ModularSynth component Change 3640313 by Aaron.McLeran #jira UE-49675 Fixing shutdown of audio mixer - Final queued commands aren't getting pumped during audio mixer shutdown, added a new interface to get a final shutdown callback back to audio mixer device. We can do any cleanup or final shutdown tasks in this callback. Added a call to pump the source manager one last time. For cases of audio mixer running without audio plugins, this won't have much of an effect, but is a good thing to do anyway. For the case of audio plugins, who are depending on paired init and release calls, this is valueable to avoid memory leaks between subsequent PIE sessions. Change 3640941 by Martin.Wilson Add editor only animation loading debug data in the hope of diagnosing rare loading crash #jira UE-49335 Change 3641976 by Ethan.Geller #jira UE-49675 ensure that we pump both command queues Change 3642613 by James.Golding Add NoPhysX sample, for CIS testing compilation without PhysX Change 3644001 by Aaron.McLeran #jira UE-49805 looping sounds are, in rare cases, extremely loud Change 3644124 by Aaron.McLeran #jira UE-49787 [CrashReport] Mac crash - UE4Editor-AudioEditor.dylib!FSoundCueEditor::DeleteInput() Adding ensure on returned ptr to avoid crash but keep getting some logging. Change 3644157 by Aaron.McLeran Fixing build error Change 3644163 by Aaron.McLeran Fixing build error (for real) Change 3650331 by Aaron.McLeran #jira UE-49994 SoundMix Fade Time not fading audio properly Making sure we properly set passive mix modifier states. Change 3652648 by Aaron.McLeran #jira UE-49994 SoundMix Fade Time not fading audio properly Change 3652995 by Aaron.McLeran #jira UE-50053 Reduce log level of audio mixer debug category Turning down the log spam level of the underrun category by switching to debug category and reducing level of the debug category. Change 3653461 by James.Golding V-HACD updates from JohnR @ NVIDIA (adding new functions for future use) Change 3654056 by Aaron.McLeran Fixing an issue with caching node states for editor builds and adding optimization to cache if we should apply interior volumes. Change 3654579 by Aaron.McLeran Allow sound submixes and sound classes to be a blueprint type Made all properties of sound classes BlueprintReadOnly. Change 3662519 by James.Golding Merge CL 3575543 from //Fortnite/Main to Dev-AnimPhys Don't call into UpdateKinematicBones if there are no physx bodies Change 3664976 by Aaron.McLeran #jira UE-50175 New Tap Delay Submix Pan parameter does not work in Surround Sound Change 3665751 by Aaron.McLeran Adding a simple panner effect Change 3665851 by Aaron.McLeran Fixing naming convention for new panner source effect Change 3666894 by Thomas.Sarkanen Bone modifications via transform type-in can now be undone Added RF_Transactional & called Modify() #jira UE-47862 - Undoing Bone transformations in Physics Asset Editor does not work Change 3666919 by Lina.Halper Fixed equal operator for bonereference to work when not initialized Change 3668850 by Thomas.Sarkanen Skeleton tree now no longer allows selection of filtered items This fixes an issue where filtered-out constraints were being deselected after a select all operation because the tree thought it had no selection (all constraints were filtered). #jira UE-50200 - Constraint Details do not populate in the Details Panel if the Skeleton tree does not include Constraints Change 3669028 by James.Golding Fix CIS error after merge-down Change 3669053 by James.Golding Fix bad merge in SynthComponent.cpp Change 3669273 by Lina.Halper - delete all tracks option - allow to opt out on bone track importing - fixed pose preview for fullbody to select weights that has pose from asset. Change 3671396 by James.Golding Fix FSkelMeshComponentLODInfo cleaning up all override resources when it should only have been cleaning up one of them Change 3671701 by Martin.Wilson Maya Live Link plugin - Added UI to Maya - Display currently streamed subjects - Allow add and removal of streamed subjects - Display connection status to editor - Stream active camera as EditorActiveCamera - Refactored entire plugin so that streaming has a manager and streaming objects / interfaces - Reworked editor update hook so that streaming is more robust and facial rigs / leaf bones now correctly update. Change 3672170 by Lina.Halper Remove track support for Animation Blueprint Library Change 3675921 by Ethan.Geller Rollback invalidated check from copy down Change 3677606 by Martin.Wilson Add live link driven component - allows an actor to take its rotation and translation from a live link subject Change 3678594 by Lina.Halper Changed API name for clarification Change 3680913 by Ethan.Geller #jira UE-50750 fix stuttering on AudioMixer on MacOS Change 3681127 by Ethan.Geller #jira UE-50720 Fix invalidated audio clock time when audio device is unplugged on legacy audio engine Change 3682729 by Ethan.Geller #jira UE-50832 Fix for null concurrency settings when removing active sounds from a concurrency group. [Dev-AnimPhys] Change 3633185 by James.Golding Fix engine not compiling when WITH_PHYSX == 0 PR #3691: 4.16_WITH_PHYSX_optional (Contributed by JacobNelsonGames) PR #3695: 4.16_PhysXVehicles_WITH_PHYSX_optional (Contributed by JacobNelsonGames) Change 3637031 by Ethan.Geller #jira UE-49605 Platform Headroom fix for non-float devices. Change 3642598 by James.Golding Change bCompileNvCloth to use same pattern as bCompileAPEX (on by default, disabled on some platforms). This allows game projects to disable it. Change 3645224 by Martin.Wilson Fix for rare notify crash. For speed purposes Notify Queue caches a pointer to the notify, this is memory that is owned by the animation and if it gets garbage collected we have a pointer to invalid memory. This change caches a pointer to the owner of the notify memory to so we can track its validity. #jira UE-44869 Change 3668926 by James.Golding Merging //UE4/Dev-Main to Dev-AnimPhys (//UE4/Dev-AnimPhys) @ 3668712 Change 3674824 by James.Golding Merging //UE4/Dev-Main to Dev-AnimPhys (//UE4/Dev-AnimPhys) @ 3674368 [CL 3683447 by Thomas Sarkanen in Main branch]
1908 lines
63 KiB
C++
1908 lines
63 KiB
C++
// Copyright 1998-2017 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<FName>& 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<FVector>& 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<FQuat>& 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<FVector>& 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<FVector>& PositionKeys, TArray<FQuat>& RotationKeys, TArray<FVector>& 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<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)
|
|
{
|
|
bool bIsEnabled = false;
|
|
|
|
if (AnimationSequence)
|
|
{
|
|
bIsEnabled = AnimationSequence->bEnableRootMotion;
|
|
}
|
|
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);
|
|
|
|
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<FAnimNotifyEvent>& 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<FName>& 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<UAnimNotifyState> 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<UObject>(AnimationSequence, NotifyClass, 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(Duration);
|
|
NewEvent.EndLink.Link(AnimationSequence, NewEvent.EndLink.GetTime());
|
|
}
|
|
else
|
|
{
|
|
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<UAnimNotifyState>(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"));
|
|
}
|
|
|
|
}
|
|
|
|
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<FName>& 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];
|
|
}
|
|
|
|
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 UAnimSequence* AnimationSequence, FName NotifyTrackName, TArray<FAnimNotifyEvent>& 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<float> TimeArray;
|
|
TArray<FTransform> TransformArray;
|
|
|
|
TimeArray.Add(Time);
|
|
TransformArray.Add(Transform);
|
|
|
|
AddCurveKeysInternal<FTransform, FTransformCurve>(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<float>& Times, const TArray<FTransform>& Transforms)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
if (Times.Num() == Transforms.Num())
|
|
{
|
|
AddCurveKeysInternal<FTransform, FTransformCurve>(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<float> TimeArray;
|
|
TArray<float> ValueArray;
|
|
|
|
TimeArray.Add(Time);
|
|
ValueArray.Add(Value);
|
|
|
|
AddCurveKeysInternal<float, FFloatCurve>(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<float>& Times, const TArray<float>& Values)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
if (Times.Num() == Values.Num())
|
|
{
|
|
AddCurveKeysInternal<float, FFloatCurve>(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<float> TimeArray;
|
|
TArray<FVector> VectorArray;
|
|
|
|
TimeArray.Add(Time);
|
|
VectorArray.Add(Vector);
|
|
|
|
AddCurveKeysInternal<FVector, FVectorCurve>(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<float>& Times, const TArray<FVector>& Vectors)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
if (Times.Num() == Vectors.Num())
|
|
{
|
|
AddCurveKeysInternal<FVector, FVectorCurve>(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 <typename DataType, typename CurveClass>
|
|
void UAnimationBlueprintLibrary::AddCurveKeysInternal(UAnimSequence* AnimationSequence, FName CurveName, const TArray<float>& Times, const TArray<DataType>& 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<CurveClass*>(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<float>& Times, TArray<float>& Values)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
GetCurveKeysInternal<float, FFloatCurve>(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<float>& Times, TArray<FVector>& Values)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
GetCurveKeysInternal<FVector, FVectorCurve>(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<float>& Times, TArray<FTransform>& Values)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
GetCurveKeysInternal<FTransform, FTransformCurve>(AnimationSequence, CurveName, Times, Values, ERawCurveTrackTypes::RCT_Transform);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid Animation Sequence for GetTransformationKeys"));
|
|
}
|
|
}
|
|
|
|
template <typename DataType, typename CurveClass>
|
|
void UAnimationBlueprintLibrary::GetCurveKeysInternal(UAnimSequence* AnimationSequence, FName CurveName, TArray<float>& Times, TArray<DataType>& 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<CurveClass*>(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<UAnimMetaData> MetaDataClass, UAnimMetaData*& MetaDataInstance)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
MetaDataInstance = NewObject<UAnimMetaData>(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<UAnimMetaData> MetaDataClass)
|
|
{
|
|
if (AnimationSequence)
|
|
{
|
|
TArray<UAnimMetaData*> 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<UAnimMetaData*>& 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<UAnimMetaData> MetaDataClass, TArray<UAnimMetaData*>& MetaDataOfClass)
|
|
{
|
|
MetaDataOfClass.Empty();
|
|
if (AnimationSequence)
|
|
{
|
|
const TArray<UAnimMetaData*>& 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<UAnimMetaData> MetaDataClass)
|
|
{
|
|
bool bContainsMetaData = false;
|
|
if (AnimationSequence)
|
|
{
|
|
TArray<UAnimMetaData*> 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<FName> BoneNameArray;
|
|
TArray<FTransform> 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<FName> BoneNames, float Time, bool bExtractRootMotion, TArray<FTransform>& Poses)
|
|
{
|
|
Poses.Empty(BoneNames.Num());
|
|
if (AnimationSequence)
|
|
{
|
|
Poses.AddDefaulted(BoneNames.Num());
|
|
|
|
// Need this for FCompactPose
|
|
FMemMark Mark(FMemStack::Get());
|
|
|
|
if (IsValidTimeInternal(AnimationSequence, Time))
|
|
{
|
|
TArray<FBoneIndexType> RequiredBones;
|
|
TArray<int32> FoundBoneIndices;
|
|
|
|
FoundBoneIndices.AddZeroed(BoneNames.Num());
|
|
|
|
for (int32 BoneNameIndex = 0; BoneNameIndex < BoneNames.Num(); ++BoneNameIndex)
|
|
{
|
|
const FName& BoneName = BoneNames[BoneNameIndex];
|
|
const int32 BoneIndex = AnimationSequence->GetSkeleton()->GetReferenceSkeleton().FindRawBoneIndex(BoneName);
|
|
|
|
FoundBoneIndices[BoneNameIndex] = INDEX_NONE;
|
|
if (BoneIndex != INDEX_NONE)
|
|
{
|
|
FoundBoneIndices[BoneNameIndex] = RequiredBones.Add(BoneIndex);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAnimationBlueprintLibrary, Warning, TEXT("Invalid bone name %s for Animation Sequence %s in GetBonePosesForTime"), *BoneName.ToString(), *AnimationSequence->GetName());
|
|
}
|
|
}
|
|
|
|
if (RequiredBones.Num())
|
|
{
|
|
FBoneContainer BoneContainer(RequiredBones, FCurveEvaluationOption(true), *AnimationSequence->GetSkeleton());
|
|
BoneContainer.SetUseSourceData(true);
|
|
BoneContainer.SetDisableRetargeting(true);
|
|
FCompactPose Pose;
|
|
Pose.SetBoneContainer(&BoneContainer);
|
|
|
|
FBlendedCurve Curve;
|
|
FAnimExtractContext Context;
|
|
Context.bExtractRootMotion = bExtractRootMotion;
|
|
Context.CurrentTime = Time;
|
|
const bool bForceUseRawData = true;
|
|
Curve.InitFrom(BoneContainer);
|
|
|
|
AnimationSequence->GetBonePose(Pose, Curve, Context, bForceUseRawData);
|
|
|
|
for (int32 BoneNameIndex = 0; BoneNameIndex < BoneNames.Num(); ++BoneNameIndex)
|
|
{
|
|
const int32 BoneContainerIndex = FoundBoneIndices[BoneNameIndex];
|
|
Poses[BoneNameIndex] = BoneContainerIndex != INDEX_NONE ? Pose.GetBones()[BoneContainerIndex] : 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<FName> BoneNames, int32 Frame, bool bExtractRootMotion, TArray<FTransform>& Poses)
|
|
{
|
|
Poses.Empty(BoneNames.Num());
|
|
if (AnimationSequence)
|
|
{
|
|
GetBonePosesForTime(AnimationSequence, BoneNames, GetTimeAtFrameInternal(AnimationSequence, Frame), bExtractRootMotion, Poses);
|
|
}
|
|
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 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<FName>& 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<FName>& TrackNames = AnimationSequence->GetAnimationTrackNames();
|
|
const int32 TrackIndex = TrackNames.Find(BoneName);
|
|
if (TrackIndex != INDEX_NONE)
|
|
{
|
|
TArray<int32> 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<float, FFloatCurve>(UAnimSequence* AnimationSequence, FName CurveName, const TArray<float>& Times, const TArray<float>& KeyData, ERawCurveTrackTypes CurveType);
|
|
template void UAnimationBlueprintLibrary::AddCurveKeysInternal<FVector, FVectorCurve>(UAnimSequence* AnimationSequence, FName CurveName, const TArray<float>& Times, const TArray<FVector>& KeyData, ERawCurveTrackTypes CurveType);
|
|
template void UAnimationBlueprintLibrary::AddCurveKeysInternal<FTransform, FTransformCurve>(UAnimSequence* AnimationSequence, FName CurveName, const TArray<float>& Times, const TArray<FTransform>& KeyData, ERawCurveTrackTypes CurveType);
|
|
|
|
template void UAnimationBlueprintLibrary::GetCurveKeysInternal<float, FFloatCurve>(UAnimSequence* AnimationSequence, FName CurveName, TArray<float>& Times, TArray<float>& KeyData, ERawCurveTrackTypes CurveType);
|
|
template void UAnimationBlueprintLibrary::GetCurveKeysInternal<FVector, FVectorCurve>(UAnimSequence* AnimationSequence, FName CurveName, TArray<float>& Times, TArray<FVector>& KeyData, ERawCurveTrackTypes CurveType);
|
|
template void UAnimationBlueprintLibrary::GetCurveKeysInternal<FTransform, FTransformCurve>(UAnimSequence* AnimationSequence, FName CurveName, TArray<float>& Times, TArray<FTransform>& KeyData, ERawCurveTrackTypes CurveType);
|
|
|