Files
UnrealEngineUWP/Engine/Source/Editor/AnimationModifiers/Private/AnimationModifier.cpp
Jurre deBaare f24003f082 Animation data MVC refactor
#jira UE-104234
#rb Thomas.Sarkanen, Martin.Wilson, Alexis.Matte, Michael.Zyracki

+ Introduced UAnimDataModel, this currently represents the source data for bone and curve animation. It contains:
    + Bone animation tracks (FBoneAnimationTrack)
        + Key data (coarse)
        + Name
        + Bone tree index
    + Curve data (FAnimationCurveData)
        + Float Curves
        + Transform Curves
    + Play length
    + Sampling rate
        + Used for deriving the expected number of keys/frames when combined with the PlayLength
    + Transient data for supporting backwards compatibility APIs
    + (Dynamic) delegate which broadcasts the mutation Notifies

+ Introduced UAnimDataController, this has sole authority over mutating data contained by UAnimDataModel
    + API functionality allows to transform the contained data in all ways currently expected in the engine.
        + Any mutation will add an undo/redo-able FChange object into the transaction buffer
            + FChangeTransactor helper object allows for keeping track of (compounded) FChange's and inserting them into GUndo
        + Broadcasts change notifies alongside a payload object containing details about the change
            + Interested systems/objects can register to these changes through UAnimDataModel::OnModelModified event
        + Almost all functionality is exposed to both Blueprint and Python scripting
    + Allows for opening 'Brackets', these define the beginning and end of a chain of mutations. Allowing anything registered to OnModelModified to halt responding to the notifies until the (top-level) bracket is closed
    + Undo/redo actions
        + Each mutation to the UAnimDataModel is covered by a 'Action' which is based of FChange and is used to insert a undo/redo-able operation into the transactions buffer

+ Introduced EAnimDataModelNotifType and per-notify payload types. These are used to update views, and any model derived data

+ Introduced FAnimationCurveIdentifier, exposed to scripting, this is used for referencing a curve by name and type when passed to the controller
    + Allows for targetting a specific RichCurve as part of a TransformCurve

+ Introduced FAnimationDataNotifyCollectorused for keeping track of which notifies of type EAnimDataModelNotifType are broadcasted between top-level EAnimDataModelNotifType::BracketOpened and EAnimDataModelNotifType::BracketClosed notifies

+ Added CanTransact to UEngine, returns whether or not a transaction can be made

+ Added UAnimCompositeBase::SetCompositeLength, used for runtime changing of play length value

+ Animation Sequence helpers, containing 'helper' functionality for both AnimSequence and AnimDataModel

* Animation Sequence Base
    + virtual PopulateModel, called during upgrade path for converting existing (legacy) data to the new UAnimDataModel data
    + virtual OnModelModified, registered to the Model's OnModified event to handle any Notifies and payloads
    + Added UAnimDataModel sub-object (editor only)
    + Added UAnimDataController instance (transient and editor only)
    + FAnimationDataNotifyCollector to keep track of model bracketed notifies
    * Deprecated
        * SetSequenceLength()
        * RefreshCurveData()
        * MarkRawDataAsModified()
        * RegisterOnAnimCurvesChanged()
        * UnregisterOnAnimCurvesChanged()
        * UnregisterOnAnimTrackCurvesChanged()
        * RegisterOnAnimTrackCurvesChanged()
        * Public access to RawCurveData

* Animation Sequence
    + Implements PopulateModel/OnModelModified for AnimationSequence related data
    + Added TargetFrameRate, currently locked to the initial sampling rate, but allows for resampling the UAnimDataModel data according to the set rate
    + Added NumberOfSampledKeys (editor only), populated by resampling process
    + Added NumberOfSampledFrames (editor only), populated by resampling process
    + Added ResampledAnimationTrackData (editor only and transient), populated by resampling process
    + Added bBlockCompressionRequests (editor only), used for blocking compression during automation tests
    * Deprecated
        * SetNumberOfSampledKeys()
        * MarkRawDataAsModified()
        * GetRawAnimationData()
        * GetAnimationTrackNames()
        * AddNewRawTrack()
        * GetRawTrackToSkeletonMapTable()
        * GetRawAnimationTrack()
        * GetRawAnimationTrack()
        * UpdateFrameRate()
        * ExtractBoneTransform()
        * CompressRawAnimData()
        * GetSkeletonIndexFromRawDataTrackIndex()
        * RecycleAnimSequence()
        * CleanAnimSequenceForImport()
        * CopyNotifies()
        * PostProcessSequence()
        * AddLoopingInterpolation()
        * RemoveAllTracks()
        * DoesContainTransformCurves()
        * InsertFramesToRawAnimData()
        * CropRawAnimData()
        * RemoveTrack()
        * InsertTrack()
        * ResizeSequence()
        * Non-editor access to GetUncompressedRawSize()
        * Non-editor access to GetApproxRawSize()
        * Public access to UpdateCompressedCurveName()
        * NumberOfKeys
        * SamplingFrameRate
        * TrackToSkeletonMapTable
        * RawAnimationData
        * AnimationTrackNames

* Animation Streamable
    * Model from source Sequence is duplicated rather than copying animation data

* Deprecated ERawCurveTrackTypes::RCT_Vector (marked as hidden)

+ Added automated test for
    + All script exposed controller functionality
    + Undo/redo actions
* Updated existing AnimSequence tests with deprecation and new expected data

+ Base data for compression is now populated from the 'resampled' version stored on the AnimSequence
+ Data cleanup and validation is now performed during compression
    + Track sanitization is now performed during compression (normalizing Quats and clamping near-zero values to zero)
    + Key reduction
    + Invalid track (missing bone from skeleton) removal
    + Curve name validation against skeleton
* Replaced RawCurves with Float curves
* Updated DDC key
- Removed all Guid generation related functionality (now part of the model)

* AnimSequenceBase model registers to AnimModel's notify event, used for refreshing the tracks
    * Replaces the in-place calling of RefreshTracks()
* All curve tracks now hold a const CurveType* rather than a CurveType*/& for introspection of its data
    + Any mutations now go through the controller API
    + Uses AnimationCurveIdentifier to set Transform's per-channel tracks
* RichCurveEditorModelNamed conformed to model/controller
    + Any modifications are applied to an temporary CurveType which always contains a copy of the const-ptr after any previous mutation
    + Registers to the outer AnimModel's notify event, used for updating the cached curve data
    + Registers to CurveModifiedDelegate, used for copying the temp curve data to the model using SetCurveKeys
        * Not-ideal but point that I got to for V1
        * Would be replaced with either refactoring FRichCurve to be MVC like, or by calling UAnimDataController functionality from an implementation of FRichCurveEditorModel

* Mark FChange overrides as final for swap/command change permutations
* Changed FCompoundChange GetDescription to return concatenation of all sub-changes
* Added GetDescription to FTransaction which returns the ToString() value of any contained FChange entries.
    * Entries in SUndoHistory tab now have a ToolTip showing the GetDescription() value
+ FChangeTransactor helper object allows for keeping track of (compounded) FChange's and inserting them into GUndo

* Deprecation handling, conforming code to new APIs and exposing structure/objects/properties to scripting
* Conforming AnimSequence importing from FBX to Model/Controller changes

[CL 15106211 by Jurre deBaare in ue5-main branch]
2021-01-15 06:41:11 -04:00

261 lines
7.7 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AnimationModifier.h"
#include "Animation/AnimSequence.h"
#include "Animation/Skeleton.h"
#include "ModifierOutputFilter.h"
#include "Editor/Transactor.h"
#include "UObject/UObjectIterator.h"
#include "UObject/ReleaseObjectVersion.h"
#include "Misc/MessageDialog.h"
#include "Editor/Transactor.h"
#include "UObject/UObjectIterator.h"
#include "UObject/AnimObjectVersion.h"
#define LOCTEXT_NAMESPACE "AnimationModifier"
UAnimationModifier::UAnimationModifier()
: PreviouslyAppliedModifier(nullptr)
{
}
void UAnimationModifier::ApplyToAnimationSequence(class UAnimSequence* InAnimationSequence)
{
FEditorScriptExecutionGuard ScriptGuard;
checkf(InAnimationSequence, TEXT("Invalid Animation Sequence supplied"));
CurrentAnimSequence = InAnimationSequence;
CurrentSkeleton = InAnimationSequence->GetSkeleton();
// Filter to check for warnings / errors thrown from animation blueprint library (rudimentary approach for now)
FCategoryLogOutputFilter OutputLog;
OutputLog.SetAutoEmitLineTerminator(true);
OutputLog.AddCategoryName("LogAnimationBlueprintLibrary");
GLog->AddOutputDevice(&OutputLog);
// Transact the modifier to prevent instance variables/data to change during applying
FTransaction ModifierTransaction;
ModifierTransaction.SaveObject(this);
FTransaction AnimationDataTransaction;
AnimationDataTransaction.SaveObject(CurrentAnimSequence);
AnimationDataTransaction.SaveObject(CurrentSkeleton);
/** In case this modifier has been previously applied, revert it using the serialised out version at the time */
if (PreviouslyAppliedModifier)
{
PreviouslyAppliedModifier->Modify();
PreviouslyAppliedModifier->OnRevert(CurrentAnimSequence);
}
UAnimDataController* Controller = CurrentAnimSequence->GetController();
{
UAnimDataController::FScopedBracket ScopedBracket(Controller, LOCTEXT("ApplyModifierBracket", "Applying Animation Modifier"));
/** Reverting and applying, populates the log with possible warnings and or errors to notify the user about */
OnApply(CurrentAnimSequence);
}
// Apply transaction
ModifierTransaction.BeginOperation();
ModifierTransaction.Apply();
ModifierTransaction.EndOperation();
GLog->RemoveOutputDevice(&OutputLog);
// Check if warnings or errors have occurred and show dialog to user to inform her about this
const bool bWarningsOrErrors = OutputLog.ContainsWarnings() || OutputLog.ContainsErrors();
bool bShouldRevert = false;
if (bWarningsOrErrors)
{
static const FText WarningMessageFormat = FText::FromString("Modifier has generated warnings during a test run:\n\n{0}\nAre you sure you want to Apply it?");
static const FText ErrorMessageFormat = FText::FromString("Modifier has generated errors (and warnings) during a test run:\n\n{0}\nResolve the Errors before trying to Apply!");
EAppMsgType::Type MessageType = OutputLog.ContainsErrors() ? EAppMsgType::Ok : EAppMsgType::YesNo;
const FText& MessageFormat = OutputLog.ContainsErrors() ? ErrorMessageFormat : WarningMessageFormat;
const FText MessageTitle = FText::FromString("Modifier has Generated Warnings/Errors");
bShouldRevert = (FMessageDialog::Open(MessageType, FText::FormatOrdered(MessageFormat, FText::FromString(OutputLog)), &MessageTitle) != EAppReturnType::Yes);
}
// Revert changes if necessary, otherwise post edit and refresh animation data
if (bShouldRevert)
{
AnimationDataTransaction.BeginOperation();
AnimationDataTransaction.Apply();
AnimationDataTransaction.EndOperation();
CurrentAnimSequence->RefreshCacheData();
}
else
{
/** Mark the previous modifier pending kill, as it will be replaced with the current modifier state */
if (PreviouslyAppliedModifier)
{
PreviouslyAppliedModifier->MarkPendingKill();
}
PreviouslyAppliedModifier = DuplicateObject(this, GetOuter());
CurrentAnimSequence->PostEditChange();
CurrentSkeleton->PostEditChange();
CurrentAnimSequence->RefreshCacheData();
UpdateStoredRevisions();
}
// Finished
CurrentAnimSequence = nullptr;
CurrentSkeleton = nullptr;
}
void UAnimationModifier::UpdateCompressedAnimationData()
{
if (CurrentAnimSequence->DoesNeedRecompress())
{
CurrentAnimSequence->RequestSyncAnimRecompression(false);
}
}
void UAnimationModifier::RevertFromAnimationSequence(class UAnimSequence* InAnimationSequence)
{
FEditorScriptExecutionGuard ScriptGuard;
/** Can only revert if previously applied, which means there should be a previous modifier */
if (PreviouslyAppliedModifier)
{
checkf(InAnimationSequence, TEXT("Invalid Animation Sequence supplied"));
CurrentAnimSequence = InAnimationSequence;
CurrentSkeleton = InAnimationSequence->GetSkeleton();
// Transact the modifier to prevent instance variables/data to change during reverting
FTransaction Transaction;
Transaction.SaveObject(this);
PreviouslyAppliedModifier->Modify();
UAnimDataController* Controller = CurrentAnimSequence->GetController();
{
UAnimDataController::FScopedBracket ScopedBracket(Controller, LOCTEXT("RevertModifierBracket", "Reverting Animation Modifier"));
PreviouslyAppliedModifier->OnRevert(CurrentAnimSequence);
}
// Apply transaction
Transaction.BeginOperation();
Transaction.Apply();
Transaction.EndOperation();
CurrentAnimSequence->PostEditChange();
CurrentSkeleton->PostEditChange();
CurrentAnimSequence->RefreshCacheData();
ResetStoredRevisions();
// Finished
CurrentAnimSequence = nullptr;
CurrentSkeleton = nullptr;
PreviouslyAppliedModifier->MarkPendingKill();
PreviouslyAppliedModifier = nullptr;
}
}
bool UAnimationModifier::IsLatestRevisionApplied() const
{
return (AppliedGuid == RevisionGuid);
}
void UAnimationModifier::PostInitProperties()
{
Super::PostInitProperties();
UpdateNativeRevisionGuid();
// Ensure we always have a valid guid
if (!RevisionGuid.IsValid())
{
UpdateRevisionGuid(GetClass());
MarkPackageDirty();
}
}
void UAnimationModifier::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
Ar.UsingCustomVersion(FReleaseObjectVersion::GUID);
/** Back-wards compatibility, assume the current modifier as previously applied */
if (Ar.CustomVer(FReleaseObjectVersion::GUID) < FReleaseObjectVersion::SerializeAnimModifierState)
{
PreviouslyAppliedModifier = DuplicateObject(this, GetOuter());
}
}
const USkeleton* UAnimationModifier::GetSkeleton()
{
return CurrentSkeleton;
}
void UAnimationModifier::UpdateRevisionGuid(UClass* ModifierClass)
{
RevisionGuid = FGuid::NewGuid();
// Native classes are more difficult?
for (TObjectIterator<UAnimationModifier> It; It; ++It)
{
if (*It != this && It->GetClass() == ModifierClass)
{
It->SetInstanceRevisionGuid(RevisionGuid);
}
}
}
void UAnimationModifier::UpdateNativeRevisionGuid()
{
UClass* Class = GetClass();
// Check if this is the class default object
if (this == GetDefault<UAnimationModifier>(Class))
{
// If so check whether or not the config stored revision matches the natively defined one
if (StoredNativeRevision != GetNativeClassRevision())
{
// If not update the blueprint revision GUID
UpdateRevisionGuid(Class);
StoredNativeRevision = GetNativeClassRevision();
MarkPackageDirty();
// Save the new native revision to config files
SaveConfig();
UpdateDefaultConfigFile();
}
}
}
int32 UAnimationModifier::GetNativeClassRevision() const
{
// Overriden in derrived classes to perform native revisioning
return 0;
}
const UAnimSequence* UAnimationModifier::GetAnimationSequence()
{
return CurrentAnimSequence;
}
void UAnimationModifier::UpdateStoredRevisions()
{
AppliedGuid = RevisionGuid;
}
void UAnimationModifier::ResetStoredRevisions()
{
AppliedGuid.Invalidate();
}
void UAnimationModifier::SetInstanceRevisionGuid(FGuid Guid)
{
RevisionGuid = Guid;
}
#undef LOCTEXT_NAMESPACE