Files
UnrealEngineUWP/Engine/Source/Editor/AnimationModifiers/Private/AnimationBlueprintLibrary.cpp
Marc Audy 9d110f953e Copying //UE4/Dev-AnimPhys to //UE4/Dev-Main (Source: //UE4/Dev-AnimPhys @ 3806524) (Part2)
#lockdown Nick.Penwarden
#rb none
#rnx

============================
  MAJOR FEATURES & CHANGES
============================

Change 3783110 by Thomas.Sarkanen

	Added support for logical negation when copying to array properties in the fast path

	#jira UE-52043 - Anim fast path correctly doesn't handle copying to arrays of bools with logical negation

Change 3783112 by Thomas.Sarkanen

	Fixed not being able to assign/unassign constraint and physical animation profiles from the context menu

	#jira UE-50205 - Constraint cannot be assigned or unassigned from current profile when using "Unassign" from the context menu in the Skeleton Tree and Graph

Change 3783114 by Thomas.Sarkanen

	Asset picker now only reports 'picks' on user interactions

	In Ocean and other projects with lots of animation assets, the asset picker amortizes its filter queries over a number of frames. This causes the list to temporarily not include the currently selected item, which then gets reported to client code as a 'deselection'. To address this (and to maintain backwards compatibility) I've added a new delegate that gives more context for selections to the SAssetPicker, allowing us to not report selections that are 'Direct'.

	#jira UE-46802 - Searching for an animation from the dropdown in persona causes it to instantly close

Change 3783118 by Thomas.Sarkanen

	Added inline time/frame settings to notify context menu

	#jira UE-45410 - Inline MontageSection/Notify Time Entry

Change 3783122 by Thomas.Sarkanen

	Collision Response dropdown is now a checkbox

	Added details customization to allow the enum to masquerate as a bool property

	#jira UE-47916 - Drop down used for Collision Response enable/disable in Physics Asset Editor

Change 3783183 by Jurre.deBaare

	Follow up fix for toggling post processing in the preview scene

Change 3783186 by Jurre.deBaare

	Material Baking Options has two Mesh Settings sections
	#fix Changed category names for to allow for two distinct categories (would have preferred to merge them, but am limited by the way detailsview handles multiple objects with containing same category)
	#jira UE-52645

Change 3783188 by Jurre.deBaare

	Duplicating:
	"Simplygon Skelmesh "Regenerate" button remains for content that had Lods created in simplygon, even when it's not installed. On regenerating editor crashes - FPersonaMeshDetails::ApplyChanges()
	#fix Ensure the regenerate button is disabled when there is no MeshReduction interface available
	#jira UE-52641

Change 3783205 by Jurre.deBaare

	Bounds are not shown correctly in Persona for imported alembic skeletal mesh
	#fix Ensured that we do not add invalid of (0,0,0) bounds to outgoing mesh bounds
	#jira UE-49338

Change 3783248 by Jurre.deBaare

	Preview Scene Settings window is missing Search bar
	#fix changed search bar flag on Details View
	#jira UE-50063

Change 3783267 by Jurre.deBaare

	Animation Modifiers: GetBonePosesForTime does not create valid data for GetBonePose
	#fix changed the way we retrieve bone transforms, now take it directly from the raw data rather than from a pose
	#jira UE-52057

Change 3783281 by Jurre.deBaare

	Tool Tip issues in Animation Editor Preferences
	#fix corrected typos in comments
	#jira UE-51338

Change 3783373 by Thomas.Sarkanen

	Added error-reporting to the profile name widgets

	This means that the effect of renaming profiles the same as an existing one is clear (i.e. we deny it now).

	#jira UE-48120 - Renaming Physics profiles the same as existing Physics profiles switches profiles and is confusing

Change 3783438 by Jurre.deBaare

	Vertex paint fill tool fills all channels
	#fix Ensure that the current channel fill selection in the Mesh Painter is also used for the fill tool, to allow the user masking out certain channels when using fill
	#jira UE-49256

Change 3783583 by Thomas.Sarkanen

	Correctly return whether a mesh section is shown or not when it has not been edited

	This fixes skeletal->static mesh conversion

Change 3783598 by Thomas.Sarkanen

	Fix multi-convex generastion for skeletal meshes

	Some triangle indices were being missed out of the index buffer, so the mesh that the convex hull was generated from was corrupt. Removing an early-out fixes this.

	#jira UE-52529 - Inaccurate Collision is Generated When Using Multi Convex Hull (PhysicsAsset Editor)

Change 3783615 by Jurre.deBaare

	OpacityMask/Opacity bug in MeshUtilities
	#fix ensured that material baking uses correct texture samples for Opacity Mask property
	#misc deprecated all of this functionality as users should be using the MaterialBaking module
	#jira UE-52382

Change 3783620 by Martin.Wilson

	Fix crash due to oversampling animation during compression

	#Jira UE-52713

Change 3783633 by Jurre.deBaare

	Fix deprecation warnings on CIS

Change 3783636 by Benn.Gallagher

	Fixed non-working tethers in clothing
	Fixed clothing config not applying to active simulation after editing
	Fixed and re-enabled accurate wind mode

	#jira UE-50797, UE-43611

Change 3783637 by Benn.Gallagher

	Github PR: Fix world to actor transform bug in anim dynamics

	Fixed incorrect Actor-space calculations for simulation transforms and world vector transformations inside AnimDynamics highlighted when testing the above PR

	#jira UE-48681
	#3929

Change 3783638 by Benn.Gallagher

	Fixed UBlendProfile properties not being correctly customized on anim nodes - and animation sequence references not being correctly filtered on anim nodes.

Change 3783660 by Danny.Bouimad

	Fixing #UE-40686 Mass TranslatedMass Automated test, instead of decreasing tolrence I changed the content to preserve granularity.

Change 3783974 by Ori.Cohen

	Refactor when sync components is called and how we pass the data into plugins. Simplifies how we handle physx data getting invalidated by UE4 during updates.

	Also fixed crash when scene query returns a destructible component that's been destroyed, but final apex delete flush hasn't happened yet.

	#jira UE-50215

Change 3784112 by Benn.Gallagher

	Fixed subinstance nodes inside states failing to correctly create bridge variables on their skeleton class due to not correctly processing subgraphs in the anim blueprint compiler.

	#jira UE-51000

Change 3784277 by Martin.Wilson

	Fix socket name getting an appended _0

	#jira UE-46625

Change 3785589 by Ori.Cohen

	Fix cis

Change 3786336 by Martin.Wilson

	Pushing skeleton to live link can now take source guid
	-Message bus source pushes guid when sending skeleton

Change 3786778 by Martin.Wilson

	Added ability for worker thread animation blueprint code to report to Message Log + added additive warning when playing an animation on an additive pose

	#jira UE-49780

Change 3786847 by Martin.Wilson

	Initialization and delta time for live link retargeter

	#Jira UE-52112

Change 3786852 by Lina.Halper

	Sequencer blending support

	#jira: UE-52183

Change 3786924 by Lina.Halper

	PR #4210: FIX: Incorrectly passing an unrelated bool rather than the expected pose ind. (Contributed by ruffenman)


Change 3787114 by Jurre.deBaare

	Discrepancy in description of Preview Scene setting and keyboard shortcut
	#fix Changed naming of the settings to match the Advanced Preview Scene panel
	#jira UE-50060

Change 3787115 by Jurre.deBaare

	Animation Editor Preferences do not update the preview scene
	#fix Removed unused preference from PersonaOptions
	#jira UE-51318

Change 3787117 by Jurre.deBaare

	Off-by one error in frame time calculations
	#fix Fixed up UAnimSequenceBase::GetTimeAtFrame and UAnimSequenceBase::GetFrameAtTime to return correct frame indices, similar to FAnimationRuntime::GetKeyIndicesFromTime
	#jira UE-52037

Change 3787412 by Martin.Wilson

	CIS Fix

Change 3787622 by Ethan.Geller

	Include Google Resonance SDK.

Change 3787633 by Ethan.Geller

	Promote AmibsonicsMixerPtr to FAudioDevice

Change 3788026 by Lina.Halper

	Retarget source reference to soft object ptr

	#jira: UE-48570

Change 3788252 by Ethan.Geller

	Add blueprint functions for Resonance Global Reverb

Change 3788750 by Ethan.Geller

	fix single file compile for Resonance plugin

Change 3788763 by Ethan.Geller

	include IModularFeatures.h explicitly for incremental build

Change 3789108 by Martin.Wilson

	Fix animations with scaled root bone generating incorrect root motion

	#jira UE-52088

Change 3789642 by Martin.Wilson

	Fix transition nodes from referencing the wrong state node due to non unique node guids being introduced by copy and paste

	#jira UE-43891

Change 3790165 by Martin.Wilson

	Fix marker sync position not being maintained across instant transitions

	#jira UE-21355

Change 3790182 by Ethan.Geller

	Final Resonance edits pass.

Change 3790184 by Lina.Halper

	Fix issue with crash when montage is streamed out while event is queued.

	https://udn.unrealengine.com/questions/404318/anim-montage-queued-montage-event-crash.html

Change 3790207 by dan.reynolds

	#UE-50774 Updated AEOverviewReverb to not attempt to destroy Audio Component that has already been destroyed.

Change 3790215 by Martin.Wilson

	CIS Fix

Change 3790953 by Ethan.Geller

	#jira UE-53023 bypass filters when at max frequency for LPF, DC for HPF

Change 3791832 by Martin.Wilson

	Don't load animations for preview tooltip in Persona

	#jira UE-52118

Change 3792873 by David.Hill

	Fix CIS.  Remove timer from proxylod code.

Change 3793251 by David.Hill

	ProxyLOD Thirdparty libs  build cs files.

	Update from ModuleRules.WindowsPlatform to ReadOnlyTargetRules.WindowsPlatform
	-- The WindowsPlatform alias is deprecated in 4.18

Change 3793400 by Ethan.Geller

	Update Resonance blueprint library to fit google naming conventions

Change 3794097 by Benn.Gallagher

	Fixed clothing visualizations no longer functioning
	#jira UE-52995

Change 3794250 by Danny.Bouimad

	Regenerated ground truth on LODCurveLinkingTest1 and AnimatedCloth, expected change as a result of Ben fixing a bug.
	Needed to update Owens cloth settings too.
	Should resolve automation test CIS fails

Change 3794352 by David.Hill

	ProxyLOD code:

	Disable openvdb-centric warnings within the openvdb platform.h file.
	C6326: Potential comparison of a constant with another constant

	and add annotations
	C28251: Inconsistent annotation for 'copysign'

	also added a warning suppress for static analysis CA_SUPPRESS(6011) within the proxylod version of the simplifier.

Change 3794786 by Lina.Halper

	Pose asset retarget source bug fix

	#jira: UE-52429

Change 3794841 by Danny.Bouimad

	Hopefully fixes the cloth automation CIS

Change 3795191 by Lina.Halper

	Fix build issue

Change 3795486 by Ethan.Geller

	re-enable android support for Oculus Audio

Change 3796162 by Danny.Bouimad

	Third attempt to fix the cloth CIS error. Hopefuly this will solve it.

Change 3796311 by Martin.Wilson

	Remove recompress animation from curve track actions. Allows smoother interaction on animations with slow recompress time.

	#jira UE-51740

Change 3796321 by Thomas.Sarkanen

	Duplicating CL 3770752 from 4.18:

	Prevent crash when generating convex bodies fails

	Note: speculative fix as the issue cannot be reproduced locally

	#jira UE-52449 - [CrashReport] FPhysicsAssetUtils::CreateFromSkeletalMeshInternal()

Change 3797093 by Danny.Bouimad

	Constrant node AnimBP Automated tests

Change 3797384 by Danny.Bouimad

	Fixing CIS error caused by automated test lighting issue

Change 3800621 by Thomas.Sarkanen

	Fix CIS: Shadowed variable warning

	#jira UE-53253 - FMenuBuilder declaration shadows a local variable warning appears when building the editor on Linux

Change 3800690 by Danny.Bouimad

	Checking in fix for CIS automation for ConstraintNode, set the screenshot tool to use BaseColor. This should fix the issue with the rendering fuzzyness

Change 3800874 by David.Hill

	Clean up static analysis warnings

	#jira: UE-53270

Change 3801227 by David.Hill

	Allow proxylod to fail gracefully if the input mesh is way too big (e.g. sky sphere)

	Added code to automatically compute the correct spatial sampling rate based on the geometry size, also allow the user to override.

	#cl: UE-53155

Change 3801228 by David.Hill

	UI: Mesh Proxy Dialog  re-write.  Make this more like MeshMerging, and share some code.

	#cl UE-53155, UE-52787, UE-53106

Change 3801319 by Danny.Bouimad

	Regenerated all the screen shots for the constraint tests.

Change 3801383 by Ethan.Geller

	#jira UE-53311 fix additional #if PLATFORM_WINDOWS  guards in Oculus Audio

Change 3801697 by Ethan.Geller

	include AudioDevice.h directly to resolve FAudioDevice.

Change 3802180 by David.Hill

	This should fix the Incremental UE4Editor Linux build.

Change 3802643 by David.Hill

	ProxyLOD UI change. Add limits to the target screen size.  They now reflect the values in the old version of the UI and the thirdparty tool.

	#CL: UE-53313

Change 3802986 by Ethan.Geller

	#jira UE-53330 Change vraudio to explicit library path

Change 3803448 by Danny.Bouimad

	disabling constraint tests

Change 3803678 by Danny.Bouimad

	#jira UE-53306 Fix

Change 3804333 by Ethan.Geller

	#jira UE-53330 fix library paths for iOS on Resonance

Change 3804453 by David.Hill

	Fix Shadow warning when compiling UE4Editor on linux:

	FlattenedMaterials.

	#CL: UE-53349

Change 3804510 by Lina.Halper

	CIS warning on shadow vars

	#jira: UE-53348, UE-53345

Change 3805451 by Lina.Halper

	Fix build issue : Renamed variable -

	https://ec-01.epicgames.net/commander/link/jobStepDetails/jobSteps/74095846?stepName=Incremental%20UnrealHeaderTool%20Win64&jobId=8173688&jobName=UE4%20Dev-AnimPhys%20-%20CL%203805429%20-%20Incremental%20Editor%20Win64&tabGroup=diagnosticHeader

Change 3805470 by Lina.Halper

	Fix build issue

Change 3806524 by Martin.Wilson

	Only use previous frame end position if it is valid for this frame

	#jira UE-53414

Change 3792620 by David.Hill

	Copying //UE4/Dev-ProxyLOD to Dev-AnimPhys-Minimal (//UE4/Dev-AnimPhys-Minimal)

	Adding the ProxyLOD code to AnimPhys.

Change 3796059 by Thomas.Sarkanen

	Persona viewport settings are now per-asset editor

	This prevnets bone following (etc) being shared by all Persona asset editors
	FOV & view type is no longer chared by all Persona asset editors

	#jira UE-53005 - Viewport settings like bone following are shared between all animation sub-editors

[CL 3806814 by Marc Audy in Main branch]
2017-12-13 16:02:45 -05:00

1882 lines
62 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))
{
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
{
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<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);