Files
UnrealEngineUWP/Engine/Source/Editor/Sequencer/Private/GroupedKeyArea.cpp
Max Chen dfad80bd9e Copying //UE4/Dev-Sequencer to Dev-Main (//UE4/Dev-Main)
==========================
MAJOR FEATURES + CHANGES
==========================

Change 2800717 on 2015/12/11 by Max.Chen

	Sequencer: Sort the key times for drawing to fix path trajectory.
	#jira UE-24331

Change 2803299 on 2015/12/15 by Max.Chen

	Sequencer: Fix property names so that they're the display names. For example, "DepthOfFieldFStop" now reads as "Aperture F Stop"

Change 2804586 on 2015/12/15 by Max.Chen

	Sequencer: Add zoom in/out with shortcuts underscore and equals.

Change 2811823 on 2015/12/23 by Max.Preussner

	Editor: Added UI action for creating new content browser folders; code cleanup; removed dead code

	Based on GitHub PR #1809 by artemavrin (https://github.com/EpicGames/UnrealEngine/pull/1809)

	#github: 1809

Change 2811839 on 2015/12/23 by Max.Preussner

	StereoPanorama: Code cleanup pass

	Based on GitHub PR# 1756 by ETayrienHBO (https://github.com/EpicGames/UnrealEngine/pull/1756)

	Also:
	- NULL to nullptr
	- namespaced enums to enum classes
	- consistent whitespace, line breaks and parentheses

	#github: 1756

Change 2819172 on 2016/01/07 by Andrew.Rodham

	Sequencer: Marquee and move modes are now automatically activated based on sequencer hotspot

Change 2819176 on 2016/01/07 by Andrew.Rodham

	Sequencer: Various cosmetic fixes

	  - Added icons to tracks
	  - Removed SAnimationOutlinerTreeNode dependency from FSequencerDisplayNode (to enable future customization of shot/event track etc)
	  - Added spacer nodes between top level display nodes
	  - Various hover states and highlights

Change 2819445 on 2016/01/07 by Andrew.Rodham

	Sequencer: Rendering out a capture from the composition graph now renders at the correct size even if r.ScreenPercentage is not 100.
	#jira UE-24920

Change 2820747 on 2016/01/08 by Andrew.Rodham

	Sequencer: Added option to close the editor when capturing starts
	#jira UE-21932

Change 2827701 on 2016/01/13 by Max.Preussner

	Media: Updating audio track specs each frame to better support streaming media and variable streams.

Change 2828465 on 2016/01/14 by Max.Preussner

	Media: Better visualization of unknown media durations

Change 2828469 on 2016/01/14 by Max.Preussner

	Media: Checking URL scheme on URLs that didn't pass the file extension filter

Change 2834888 on 2016/01/19 by Max.Preussner

	Core: TQueue modernization pass

Change 2834934 on 2016/01/19 by Max.Preussner

	Core: Implemented TTripleBuffer for triple buffers.

Change 2834950 on 2016/01/19 by Max.Preussner

	Core: Added unit tests for TTripleBuffer dirty flag

Change 2835488 on 2016/01/20 by Max.Preussner

	Core: More descriptive method names, initialization constructor, unit tests for TTripleBuffer

Change 2837515 on 2016/01/20 by Max.Chen

	Sequencer: Command line options for custom passes.

Change 2837517 on 2016/01/20 by Max.Chen

	Sequencer: Fix crash in visibility track instance on PIE.

Change 2837518 on 2016/01/20 by Max.Chen

	Sequencer: Add option to lock to frame rate while playing.
	#jira UETOOL-475

Change 2837523 on 2016/01/20 by Max.Chen

	Sequencer: Capture thumbnail on level sequence asset save.

Change 2837527 on 2016/01/20 by Max.Chen

	Sequencer: Added preroll for subsequences. Refactor instance update to combine data in EMovieSceneUpdateData.
	#jira UE-25380

Change 2837537 on 2016/01/20 by Max.Chen

	Sequencer: Add sequencer transport controls back into viewports.
	#jira UE-25460

Change 2837561 on 2016/01/20 by Max.Chen

	Sequencer: Added ability to convert a possessable to a spawnable

	  - This option is available for any root-level possessable object bindings
	  - It will currently delete the existing possessable (we could make this behaviour optional in future)
	  - There is currently no check to sett if the actor is possessed by subsequent sub-sequences. If this is the case, using a possessable, or externally owned spawnable would be a better bet.

Change 2837565 on 2016/01/20 by Max.Chen

[CL 2858958 by Max Chen in Main branch]
2016-02-08 13:35:28 -05:00

510 lines
13 KiB
C++

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "SequencerPrivatePCH.h"
#include "GroupedKeyArea.h"
#include "MovieSceneSection.h"
FIndexKey::FIndexKey(FString InNodePath, UMovieSceneSection* InSection)
: NodePath(MoveTemp(InNodePath))
, Section(InSection)
, CachedHash(HashCombine(GetTypeHash(NodePath), GetTypeHash(InSection)))
{}
namespace
{
/** Structure that defines the index for a particular section */
struct FIndexEntry
{
TMap<FKeyHandle, int32> HandleToGroup;
TArray<FKeyHandle> GroupHandles;
TArray<float> RepresentativeTimes;
};
/** A persistent index is required to ensure that generated key handles are maintained for the lifetime of specific display nodes */
TMap<FIndexKey, FIndexEntry> GlobalIndex;
}
FGroupedKeyArea::FGroupedKeyArea(FSequencerDisplayNode& InNode, int32 InSectionIndex)
: Section(nullptr)
, IndexKey(FString(), nullptr)
{
TArray<TSharedRef<FSequencerSectionKeyAreaNode>> AllKeyAreaNodes;
AllKeyAreaNodes.Reserve(36);
InNode.GetChildKeyAreaNodesRecursively(AllKeyAreaNodes);
for (const auto& Node : AllKeyAreaNodes)
{
TSharedRef<IKeyArea> KeyArea = Node->GetKeyArea(InSectionIndex);
const int32 KeyAreaIndex = KeyAreas.Num();
KeyAreas.Add(KeyArea);
{
auto* OwningSection = KeyArea->GetOwningSection();
// Ensure they all belong to the same section
ensure(!Section || Section == OwningSection);
Section = OwningSection;
}
for (const FKeyHandle& KeyHandle : KeyArea->GetUnsortedKeyHandles())
{
Groups.Emplace(KeyArea->GetKeyTime(KeyHandle), KeyAreaIndex, KeyHandle);
}
}
Groups.Sort([](const FKeyGrouping& A, const FKeyGrouping& B){
return A.RepresentativeTime < B.RepresentativeTime;
});
// Remove duplicates
for (int32 Index = 0; Index < Groups.Num(); ++Index)
{
const float CurrentTime = Groups[Index].RepresentativeTime;
int32 NumToMerge = 0;
for (int32 DuplIndex = Index + 1; DuplIndex < Groups.Num(); ++DuplIndex)
{
if (!FMath::IsNearlyEqual(CurrentTime, Groups[DuplIndex].RepresentativeTime))
{
break;
}
++NumToMerge;
}
if (NumToMerge)
{
const int32 Start = Index + 1, End = Start + NumToMerge;
for (int32 MergeIndex = Start; MergeIndex < End; ++MergeIndex)
{
Groups[Index].Keys.Append(MoveTemp(Groups[MergeIndex].Keys));
}
Groups.RemoveAt(Start, NumToMerge, false);
}
}
if (Section)
{
IndexKey = FIndexKey(InNode.GetPathName(), Section);
UpdateIndex();
}
}
void FGroupedKeyArea::UpdateIndex() const
{
auto& IndexEntry = GlobalIndex.FindOrAdd(IndexKey);
TArray<FKeyHandle> NewKeyHandles;
TArray<float> NewRepresentativeTimes;
IndexEntry.HandleToGroup.Reset();
for (int32 GroupIndex = 0; GroupIndex < Groups.Num(); ++GroupIndex)
{
float RepresentativeTime = Groups[GroupIndex].RepresentativeTime;
// Find a key handle we can recycle
int32 RecycledIndex = IndexEntry.RepresentativeTimes.IndexOfByPredicate([&](float Time){
// Must be an *exact* match to recycle
return Time == RepresentativeTime;
});
if (RecycledIndex != INDEX_NONE)
{
NewKeyHandles.Add(IndexEntry.GroupHandles[RecycledIndex]);
NewRepresentativeTimes.Add(IndexEntry.RepresentativeTimes[RecycledIndex]);
IndexEntry.GroupHandles.RemoveAt(RecycledIndex, 1, false);
IndexEntry.RepresentativeTimes.RemoveAt(RecycledIndex, 1, false);
}
else
{
NewKeyHandles.Add(FKeyHandle());
NewRepresentativeTimes.Add(RepresentativeTime);
}
IndexEntry.HandleToGroup.Add(NewKeyHandles.Last(), GroupIndex);
}
IndexEntry.GroupHandles = MoveTemp(NewKeyHandles);
IndexEntry.RepresentativeTimes = MoveTemp(NewRepresentativeTimes);
}
const FKeyGrouping* FGroupedKeyArea::FindGroup(FKeyHandle InHandle) const
{
if (auto* IndexEntry = GlobalIndex.Find(IndexKey))
{
if (int32* GroupIndex = IndexEntry->HandleToGroup.Find(InHandle))
{
return Groups.IsValidIndex(*GroupIndex) ? &Groups[*GroupIndex] : nullptr;
}
}
return nullptr;
}
FKeyGrouping* FGroupedKeyArea::FindGroup(FKeyHandle InHandle)
{
if (auto* IndexEntry = GlobalIndex.Find(IndexKey))
{
if (int32* GroupIndex = IndexEntry->HandleToGroup.Find(InHandle))
{
return Groups.IsValidIndex(*GroupIndex) ? &Groups[*GroupIndex] : nullptr;
}
}
return nullptr;
}
FLinearColor FGroupedKeyArea::GetKeyTint(FKeyHandle InHandle) const
{
// Everything is untinted for now
return FLinearColor::White;
}
const FSlateBrush* FGroupedKeyArea::GetBrush(FKeyHandle InHandle) const
{
static const FSlateBrush* PartialKeyBrush = FEditorStyle::GetBrush("Sequencer.PartialKey");
const auto* Group = FindGroup(InHandle);
// Ensure that each key area is represented at least once for it to be considered a 'complete key'
for (int32 AreaIndex = 0; Group && AreaIndex < KeyAreas.Num(); ++AreaIndex)
{
if (!Group->Keys.ContainsByPredicate([&](const FKeyGrouping::FKeyIndex& Key){ return Key.AreaIndex == AreaIndex; }))
{
return PartialKeyBrush;
}
}
return nullptr;
}
TArray<FKeyHandle> FGroupedKeyArea::GetUnsortedKeyHandles() const
{
TArray<FKeyHandle> Array;
if (auto* IndexEntry = GlobalIndex.Find(IndexKey))
{
IndexEntry->HandleToGroup.GenerateKeyArray(Array);
}
return Array;
}
void FGroupedKeyArea::SetKeyTime(FKeyHandle KeyHandle, float NewKeyTime) const
{
auto* Group = FindGroup(KeyHandle);
for (auto& Key : Group->Keys)
{
KeyAreas[Key.AreaIndex]->SetKeyTime(Key.KeyHandle, NewKeyTime);
}
}
float FGroupedKeyArea::GetKeyTime(FKeyHandle KeyHandle) const
{
auto* Group = FindGroup(KeyHandle);
return Group ? Group->RepresentativeTime : 0.f;
}
FKeyHandle FGroupedKeyArea::MoveKey(FKeyHandle KeyHandle, float DeltaPosition)
{
int32* GroupIndex = nullptr;
auto* IndexEntry = GlobalIndex.Find(IndexKey);
if (IndexEntry)
{
GroupIndex = IndexEntry->HandleToGroup.Find(KeyHandle);
}
if (!GroupIndex || !Groups.IsValidIndex(*GroupIndex))
{
return KeyHandle;
}
bool bIsTimeUpdated = false;
FKeyGrouping& Group = Groups[*GroupIndex];
// Move all the keys
for (auto& Key : Group.Keys)
{
Key.KeyHandle = KeyAreas[Key.AreaIndex]->MoveKey(Key.KeyHandle, DeltaPosition);
const float KeyTime = KeyAreas[Key.AreaIndex]->GetKeyTime(Key.KeyHandle);
Group.RepresentativeTime = bIsTimeUpdated ? FMath::Min(Group.RepresentativeTime, KeyTime) : KeyTime;
bIsTimeUpdated = true;
}
// Update the representative time to the smallest of all the keys (so it will deterministically get the same key handle on regeneration)
IndexEntry->RepresentativeTimes[*GroupIndex] = Group.RepresentativeTime;
return KeyHandle;
}
void FGroupedKeyArea::DeleteKey(FKeyHandle KeyHandle)
{
if (auto* Group = FindGroup(KeyHandle))
{
for (auto& Key : Group->Keys)
{
KeyAreas[Key.AreaIndex]->DeleteKey(Key.KeyHandle);
}
}
}
void
FGroupedKeyArea::SetKeyInterpMode(FKeyHandle KeyHandle, ERichCurveInterpMode InterpMode)
{
if (auto* Group = FindGroup(KeyHandle))
{
for (auto& Key : Group->Keys)
{
KeyAreas[Key.AreaIndex]->SetKeyInterpMode(Key.KeyHandle, InterpMode);
}
}
}
ERichCurveInterpMode
FGroupedKeyArea::GetKeyInterpMode(FKeyHandle KeyHandle) const
{
// Return RCIM_None if the keys don't all have the same interp mode
ERichCurveInterpMode InterpMode = RCIM_None;
if (auto* Group = FindGroup(KeyHandle))
{
for (auto& Key : Group->Keys)
{
if (InterpMode == RCIM_None)
{
InterpMode = KeyAreas[Key.AreaIndex]->GetKeyInterpMode(Key.KeyHandle);
}
else if (InterpMode != KeyAreas[Key.AreaIndex]->GetKeyInterpMode(Key.KeyHandle))
{
return RCIM_None;
}
}
}
return InterpMode;
}
void
FGroupedKeyArea::SetKeyTangentMode(FKeyHandle KeyHandle, ERichCurveTangentMode TangentMode)
{
if (auto* Group = FindGroup(KeyHandle))
{
for (auto& Key : Group->Keys)
{
KeyAreas[Key.AreaIndex]->SetKeyTangentMode(Key.KeyHandle, TangentMode);
}
}
}
ERichCurveTangentMode
FGroupedKeyArea::GetKeyTangentMode(FKeyHandle KeyHandle) const
{
// Return RCTM_None if the keys don't all have the same tangent mode
ERichCurveTangentMode TangentMode = RCTM_None;
if (auto* Group = FindGroup(KeyHandle))
{
for (auto& Key : Group->Keys)
{
if (TangentMode == RCTM_None)
{
TangentMode = KeyAreas[Key.AreaIndex]->GetKeyTangentMode(Key.KeyHandle);
}
else if (TangentMode != KeyAreas[Key.AreaIndex]->GetKeyTangentMode(Key.KeyHandle))
{
return RCTM_None;
}
}
}
return TangentMode;
}
void FGroupedKeyArea::SetExtrapolationMode(ERichCurveExtrapolation ExtrapMode, bool bPreInfinity)
{
for (auto& Area : KeyAreas)
{
Area->SetExtrapolationMode(ExtrapMode, bPreInfinity);
}
}
ERichCurveExtrapolation FGroupedKeyArea::GetExtrapolationMode(bool bPreInfinity) const
{
ERichCurveExtrapolation ExtrapMode = RCCE_None;
for (auto& Area : KeyAreas)
{
if (ExtrapMode == RCCE_None)
{
ExtrapMode = Area->GetExtrapolationMode(bPreInfinity);
}
else if (Area->GetExtrapolationMode(bPreInfinity) != ExtrapMode)
{
return RCCE_None;
}
}
return ExtrapMode;
}
bool FGroupedKeyArea::CanSetExtrapolationMode() const
{
for (auto& Area : KeyAreas)
{
if (Area->CanSetExtrapolationMode())
{
return true;
}
}
return false;
}
TArray<FKeyHandle> FGroupedKeyArea::AddKeyUnique(float Time, EMovieSceneKeyInterpolation InKeyInterpolation, float TimeToCopyFrom)
{
TArray<FKeyHandle> AddedKeyHandles;
for (auto& Area : KeyAreas)
{
// If TimeToCopyFrom is valid, add a key only if there is a key to copy from
if (TimeToCopyFrom != FLT_MAX)
{
if (FRichCurve* Curve = Area->GetRichCurve())
{
if (!Curve->IsKeyHandleValid(Curve->FindKey(TimeToCopyFrom)))
{
continue;
}
}
}
TArray<FKeyHandle> AddedGroupKeyHandles = Area->AddKeyUnique(Time, InKeyInterpolation, TimeToCopyFrom);
AddedKeyHandles.Append(AddedGroupKeyHandles);
}
return AddedKeyHandles;
}
TOptional<FKeyHandle> FGroupedKeyArea::DuplicateKey(FKeyHandle KeyToDuplicate)
{
FKeyGrouping* Group = FindGroup(KeyToDuplicate);
if (!Group)
{
return TOptional<FKeyHandle>();
}
const float Time = Group->RepresentativeTime;
const int32 NewGroupIndex = Groups.Num();
Groups.Emplace(Time);
for (const FKeyGrouping::FKeyIndex& Key : Group->Keys)
{
TOptional<FKeyHandle> NewKeyHandle = KeyAreas[Key.AreaIndex]->DuplicateKey(Key.KeyHandle);
if (NewKeyHandle.IsSet())
{
Groups[NewGroupIndex].Keys.Emplace(Key.AreaIndex, NewKeyHandle.GetValue());
}
}
// Update the global index with our new key
FIndexEntry* IndexEntry = GlobalIndex.Find(IndexKey);
if (IndexEntry)
{
FKeyHandle ThisGroupKeyHandle;
IndexEntry->GroupHandles.Add(ThisGroupKeyHandle);
IndexEntry->HandleToGroup.Add(ThisGroupKeyHandle, NewGroupIndex);
IndexEntry->RepresentativeTimes.Add(Time);
return ThisGroupKeyHandle;
}
return TOptional<FKeyHandle>();
}
FRichCurve* FGroupedKeyArea::GetRichCurve()
{
return nullptr;
}
UMovieSceneSection* FGroupedKeyArea::GetOwningSection()
{
return Section;
}
bool FGroupedKeyArea::CanCreateKeyEditor()
{
return false;
}
void FGroupedKeyArea::CopyKeys(FMovieSceneClipboardBuilder& ClipboardBuilder, const TFunctionRef<bool(FKeyHandle, const IKeyArea&)>& KeyMask) const
{
const FIndexEntry* IndexEntry = GlobalIndex.Find(IndexKey);
if (!IndexEntry)
{
return;
}
// Since we are a group of nested key areas, we test the key mask for our key handles, and forward on the results to each key area
// Using ptr as map key is fine here as we know they will not change
TMap<const IKeyArea*, TSet<FKeyHandle>> AllValidHandles;
for (auto& Pair : IndexEntry->HandleToGroup)
{
if (!KeyMask(Pair.Key, *this) || !Groups.IsValidIndex(Pair.Value))
{
continue;
}
const FKeyGrouping& Group = Groups[Pair.Value];
for (const FKeyGrouping::FKeyIndex& KeyIndex : Group.Keys)
{
const IKeyArea& KeyArea = KeyAreas[KeyIndex.AreaIndex].Get();
AllValidHandles.FindOrAdd(&KeyArea).Add(KeyIndex.KeyHandle);
}
}
for (auto& Pair : AllValidHandles)
{
Pair.Key->CopyKeys(ClipboardBuilder, [&](FKeyHandle Handle, const IKeyArea&){
return Pair.Value.Contains(Handle);
});
}
}
TSharedRef<SWidget> FGroupedKeyArea::CreateKeyEditor(ISequencer* Sequencer)
{
return SNullWidget::NullWidget;
}
FLinearColor FGroupedKeyArea::GetColor()
{
return FLinearColor(0.1f, 0.1f, 0.1f, 0.7f);
}
TSharedPtr<FStructOnScope> FGroupedKeyArea::GetKeyStruct(FKeyHandle KeyHandle)
{
FKeyGrouping* Group = FindGroup(KeyHandle);
if (Group == nullptr)
{
return nullptr;
}
TArray<FKeyHandle> KeyHandles;
for (auto& Key : Group->Keys)
{
KeyHandles.Add(Key.KeyHandle);
}
return Section->GetKeyStruct(KeyHandles);
}
void FGroupedKeyArea::PasteKeys(const FMovieSceneClipboardKeyTrack& KeyTrack, const FMovieSceneClipboardEnvironment& SrcEnvironment, const FSequencerPasteEnvironment& DstEnvironment)
{
checkf(false, TEXT("Pasting into grouped key areas is not supported, and should not be used. Iterate child tracks instead."));
}