// Copyright Epic Games, Inc. All Rights Reserved. #include "Channels/ChannelCurveModel.h" #include "Algo/BinarySearch.h" #include "Channels/MovieSceneChannelData.h" #include "Channels/MovieSceneChannelProxy.h" #include "Channels/MovieSceneFloatChannel.h" #include "Channels/MovieSceneIntegerChannel.h" #include "Channels/MovieSceneByteChannel.h" #include "Containers/UnrealString.h" #include "CurveDataAbstraction.h" #include "CurveDrawInfo.h" #include "CurveEditorScreenSpace.h" #include "Curves/RealCurve.h" #include "Delegates/Delegate.h" #include "HAL/PlatformCrt.h" #include "ISequencer.h" #include "Math/NumericLimits.h" #include "Math/UnrealMathUtility.h" #include "Math/Vector2D.h" #include "Misc/AssertionMacros.h" #include "Misc/FrameNumber.h" #include "Misc/FrameRate.h" #include "Misc/FrameTime.h" #include "Misc/Optional.h" #include "MovieScene.h" #include "MovieSceneSection.h" #include "Styling/AppStyle.h" #include "Styling/ISlateStyle.h" #include "Templates/UnrealTemplate.h" class UObject; struct FMovieSceneChannelMetaData; template FChannelCurveModel::FChannelCurveModel(TMovieSceneChannelHandle InChannel, UMovieSceneSection* OwningSection, TWeakPtr InWeakSequencer) { ChannelHandle = InChannel; WeakSection = OwningSection; WeakSequencer = InWeakSequencer; LastSignature = OwningSection->GetSignature(); if (FMovieSceneChannelProxy* ChannelProxy = InChannel.GetChannelProxy()) { OnDestroyHandle = ChannelProxy->OnDestroy.AddRaw(this, &FChannelCurveModel::FixupCurve); } SupportedViews = ECurveEditorViewID::Absolute | ECurveEditorViewID::Normalized | ECurveEditorViewID::Stacked; } template FChannelCurveModel::FChannelCurveModel(TMovieSceneChannelHandle InChannel, UMovieSceneSection* InOwningSection, UObject* InOwningObject, TWeakPtr InWeakSequencer) : FChannelCurveModel(InChannel, InOwningSection, InWeakSequencer) { WeakOwningObject = InOwningObject; } template FChannelCurveModel::~FChannelCurveModel() { if (FMovieSceneChannelProxy* ChannelProxy = ChannelHandle.GetChannelProxy()) { ChannelProxy->OnDestroy.Remove(OnDestroyHandle); } } template FTransform2d FChannelCurveModel::GetCurveTransform() const { FTransform2d Transform; const FMovieSceneChannelMetaData* MetaData = ChannelHandle.GetMetaData(); const UMovieSceneSection* Section = WeakSection.Get(); if (MetaData && Section) { FFrameNumber Offset = MetaData->GetOffsetTime(Section); if (Offset != 0) { FFrameRate TickResolution = Section->GetTypedOuter()->GetTickResolution(); Transform = Concatenate(Transform, FVector2d(-Offset / TickResolution, 0.0)); } } return Transform; } template const void* FChannelCurveModel::GetCurve() const { return ChannelHandle.Get(); } template void FChannelCurveModel::Modify() { if (UObject* Object = GetOwningObject()) { Object->Modify(); } LastSignature.Invalidate(); } template void FChannelCurveModel::DrawCurve(const FCurveEditor& CurveEditor, const FCurveEditorScreenSpace& ScreenSpace, TArray>& OutInterpolatingPoints) const { ChannelType* Channel = ChannelHandle.Get(); UMovieSceneSection* Section = WeakSection.Get(); if (Channel && Section) { FFrameRate TickResolution = Section->GetTypedOuter()->GetTickResolution(); TMovieSceneChannelData ChannelData = Channel->GetData(); TArrayView Times = ChannelData.GetTimes(); TArrayView Values = ChannelData.GetValues(); const double DisplayOffset = GetInputDisplayOffset(); const double StartTimeSeconds = ScreenSpace.GetInputMin() - DisplayOffset; const double EndTimeSeconds = ScreenSpace.GetInputMax() - DisplayOffset; const FFrameNumber StartFrame = (StartTimeSeconds * TickResolution).FloorToFrame(); const FFrameNumber EndFrame = (EndTimeSeconds * TickResolution).CeilToFrame(); const int32 StartingIndex = Algo::UpperBound(Times, StartFrame); const int32 EndingIndex = Algo::LowerBound(Times, EndFrame); // Add the lower bound of the visible space const bool bValidRange = StartingIndex < EndingIndex; //if we aren't just doing the default constant then we need to sample const bool PreNotConstant = (Channel->PreInfinityExtrap != RCCE_None && Channel->PreInfinityExtrap != RCCE_Constant); const bool PostNotConstant = (Channel->PostInfinityExtrap != RCCE_None && Channel->PostInfinityExtrap != RCCE_Constant); if (bValidRange && (PreNotConstant || PostNotConstant)) { const FFrameRate DisplayResolution = Section->GetTypedOuter()->GetDisplayRate(); const FFrameNumber StartTimeInDisplay = FFrameRate::TransformTime(FFrameTime(StartFrame), TickResolution, DisplayResolution).FloorToFrame(); const FFrameNumber EndTimeInDisplay = FFrameRate::TransformTime(FFrameTime(EndFrame), TickResolution, DisplayResolution).CeilToFrame(); double Value = 0.0; TOptional PreviousValue; for (FFrameNumber DisplayFrameNumber = StartTimeInDisplay; DisplayFrameNumber <= EndTimeInDisplay; ++DisplayFrameNumber) { FFrameNumber TickFrameNumber = FFrameRate::TransformTime(FFrameTime(DisplayFrameNumber), DisplayResolution, TickResolution).FrameNumber; Evaluate(TickFrameNumber / TickResolution, Value); if (PreviousValue.IsSet() && PreviousValue.GetValue() != Value) { OutInterpolatingPoints.Add(MakeTuple(TickFrameNumber / TickResolution, PreviousValue.GetValue())); } OutInterpolatingPoints.Add(MakeTuple(TickFrameNumber / TickResolution, Value)); PreviousValue = Value; } } else { if (bValidRange) { OutInterpolatingPoints.Add(MakeTuple(StartFrame / TickResolution, GetKeyValue(Values, StartingIndex))); } TOptional PreviousValue; for (int32 KeyIndex = StartingIndex; KeyIndex < EndingIndex; ++KeyIndex) { double Value = GetKeyValue(Values, KeyIndex); if (PreviousValue.IsSet() && PreviousValue.GetValue() != Value) { OutInterpolatingPoints.Add(MakeTuple(Times[KeyIndex] / TickResolution, PreviousValue.GetValue())); } OutInterpolatingPoints.Add(MakeTuple(Times[KeyIndex] / TickResolution, Value)); PreviousValue = Value; } // Add the upper bound of the visible space if (bValidRange) { OutInterpolatingPoints.Add(MakeTuple(EndFrame / TickResolution, GetKeyValue(Values, EndingIndex - 1))); } } } } template void FChannelCurveModel::GetKeys(double MinTime, double MaxTime, double MinValue, double MaxValue, TArray& OutKeyHandles) const { ChannelType* Channel = ChannelHandle.Get(); UMovieSceneSection* Section = WeakSection.Get(); if (Channel && Section) { FFrameRate TickResolution = Section->GetTypedOuter()->GetTickResolution(); TMovieSceneChannelData ChannelData = Channel->GetData(); TArrayView Times = ChannelData.GetTimes(); TArrayView Values = ChannelData.GetValues(); const FFrameNumber StartFrame = MinTime <= MIN_int32 ? MIN_int32 : (MinTime * TickResolution).CeilToFrame(); const FFrameNumber EndFrame = MaxTime >= MAX_int32 ? MAX_int32 : (MaxTime * TickResolution).FloorToFrame(); const int32 StartingIndex = Algo::LowerBound(Times, StartFrame); const int32 EndingIndex = Algo::UpperBound(Times, EndFrame); for (int32 KeyIndex = StartingIndex; KeyIndex < EndingIndex; ++KeyIndex) { if (GetKeyValue(Values, KeyIndex) >= MinValue && GetKeyValue(Values, KeyIndex) <= MaxValue) { OutKeyHandles.Add(ChannelData.GetHandle(KeyIndex)); } } } } template void FChannelCurveModel::GetKeyDrawInfo(ECurvePointType PointType, const FKeyHandle InKeyHandle, FKeyDrawInfo& OutDrawInfo) const { OutDrawInfo.Brush = FAppStyle::Get().GetBrush("Sequencer.KeyDiamond"); OutDrawInfo.ScreenSize = FVector2D(10, 10); } template void FChannelCurveModel::GetKeyPositions(TArrayView InKeys, TArrayView OutKeyPositions) const { ChannelType* Channel = ChannelHandle.Get(); UMovieSceneSection* Section = WeakSection.Get(); if (Channel && Section) { FFrameRate TickResolution = Section->GetTypedOuter()->GetTickResolution(); TMovieSceneChannelData ChannelData = Channel->GetData(); TArrayView Times = ChannelData.GetTimes(); TArrayView Values = ChannelData.GetValues(); for (int32 Index = 0; Index < InKeys.Num(); ++Index) { int32 KeyIndex = ChannelData.GetIndex(InKeys[Index]); if (KeyIndex != INDEX_NONE) { OutKeyPositions[Index].InputValue = Times[KeyIndex] / TickResolution; OutKeyPositions[Index].OutputValue = GetKeyValue(Values, KeyIndex); } } } } template void FChannelCurveModel::SetKeyPositions(TArrayView InKeys, TArrayView InKeyPositions, EPropertyChangeType::Type ChangeType) { UE::MovieScene::FScopedSignedObjectModifyDefer Defer; ChannelType* Channel = ChannelHandle.Get(); UMovieSceneSection* Section = WeakSection.Get(); const FMovieSceneChannelMetaData* MetaData = ChannelHandle.GetMetaData(); UMovieSceneSignedObject* SignedOwner = Cast(WeakOwningObject.Get()); if (!SignedOwner) { SignedOwner = WeakSection.Get(); } if (Channel && MetaData && Section && !IsReadOnly()) { TMovieSceneChannelData ChannelData = Channel->GetData(); const TArrayView Times = ChannelData.GetTimes(); if (Times.IsEmpty()) { return; } Section->MarkAsChanged(); FFrameRate TickResolution = Section->GetTypedOuter()->GetTickResolution(); FFrameNumber KeyOffset = 0; const int32 LastIndex = Times.Num()-1; const FFrameNumber FirstTime = Times[0]; const FFrameNumber LastTime = Times[LastIndex]; // Expand to frame first, then offset if (InKeyPositions.Num() > 0) { double Min = TNumericLimits::Max(); double Max = TNumericLimits::Lowest(); for (const FKeyPosition& Value : InKeyPositions) { Min = FMath::Min(Min, Value.InputValue); Max = FMath::Max(Max, Value.InputValue); } FFrameNumber Offset = MetaData->GetOffsetTime(Section); FFrameNumber MinTime = (Min * TickResolution).RoundToFrame() + Offset; FFrameNumber MaxTime = (Max * TickResolution).RoundToFrame() + Offset; if (!Section->GetRange().Contains(MinTime)) { Section->ExpandToFrame(MinTime); KeyOffset += MetaData->GetOffsetTime(Section) - Offset; } if (Min != Max && !Section->GetRange().Contains(MaxTime)) { Section->ExpandToFrame(MaxTime); } } for (int32 Index = 0; Index < InKeys.Num(); ++Index) { int32 KeyIndex = ChannelData.GetIndex(InKeys[Index]); if (KeyIndex != INDEX_NONE) { FFrameNumber NewTime = (InKeyPositions[Index].InputValue * TickResolution).RoundToFrame() - KeyOffset; const bool bRemoveDuplicates = ChangeType == EPropertyChangeType::ValueSet; KeyIndex = ChannelData.MoveKey(KeyIndex, NewTime, bRemoveDuplicates); SetKeyValue(KeyIndex, InKeyPositions[Index].OutputValue); } } Channel->PostEditChange(); if(WeakSequencer.IsValid()) { WeakSequencer.Pin()->OnChannelChanged().Broadcast(MetaData, Section); } CurveModifiedDelegate.Broadcast(); SignedOwner->MarkAsChanged(); } } template void FChannelCurveModel::GetCurveAttributes(FCurveAttributes& OutCurveAttributes) const { OutCurveAttributes.SetPreExtrapolation(RCCE_None); OutCurveAttributes.SetPostExtrapolation(RCCE_None); } template void FChannelCurveModel::GetTimeRange(double& MinTime, double& MaxTime) const { ChannelType* Channel = ChannelHandle.Get(); UMovieSceneSection* Section = WeakSection.Get(); if (Channel && Section) { TArrayView Times = Channel->GetData().GetTimes(); if (Times.Num() == 0) { MinTime = 0.f; MaxTime = 0.f; } else { FFrameRate TickResolution = Section->GetTypedOuter()->GetTickResolution(); double ToTime = TickResolution.AsInterval(); MinTime = static_cast(Times[0].Value) * ToTime; MaxTime = static_cast(Times[Times.Num() - 1].Value) * ToTime; } } } template void FChannelCurveModel::GetValueRange(double& MinValue, double& MaxValue) const { ChannelType* Channel = ChannelHandle.Get(); UMovieSceneSection* Section = WeakSection.Get(); if (Channel && Section) { TArrayView Times = Channel->GetData().GetTimes(); TArrayView Values = Channel->GetData().GetValues(); if (Times.Num() == 0) { // If there are no keys we just use the default value for the channel, defaulting to zero if there is no default. MinValue = MaxValue = Channel->GetDefault().Get(0.f); } else { FFrameRate TickResolution = Section->GetTypedOuter()->GetTickResolution(); double ToTime = TickResolution.AsInterval(); int32 LastKeyIndex = Values.Num() - 1; MinValue = MaxValue = GetKeyValue(Values, 0); for (int32 i = 0; i < Values.Num(); i++) { double Key = GetKeyValue(Values, i); MinValue = FMath::Min(MinValue, Key); MaxValue = FMath::Max(MaxValue, Key); } } } } template int32 FChannelCurveModel::GetNumKeys() const { ChannelType* Channel = ChannelHandle.Get(); if (Channel) { TArrayView Times = Channel->GetData().GetTimes(); return Times.Num(); } return 0; } template void FChannelCurveModel::GetNeighboringKeys(const FKeyHandle InKeyHandle, TOptional& OutPreviousKeyHandle, TOptional& OutNextKeyHandle) const { ChannelType* Channel = ChannelHandle.Get(); if (Channel) { TMovieSceneChannelData ChannelData = Channel->GetData(); const int32 KeyIndex = ChannelData.GetIndex(InKeyHandle); if (KeyIndex != INDEX_NONE) { if (KeyIndex - 1 >= 0) { OutPreviousKeyHandle = ChannelData.GetHandle(KeyIndex - 1); } if (KeyIndex + 1 < ChannelData.GetTimes().Num()) { OutNextKeyHandle = ChannelData.GetHandle(KeyIndex + 1); } } } } template bool FChannelCurveModel::Evaluate(double Time, double& OutValue) const { ChannelType* Channel = ChannelHandle.Get(); UMovieSceneSection* Section = WeakSection.Get(); if (Channel && Section) { FFrameRate TickResolution = Section->GetTypedOuter()->GetTickResolution(); KeyType ThisValue = 0.f; if (Channel->Evaluate(Time * TickResolution, ThisValue)) { OutValue = ThisValue; return true; } } return false; } template void FChannelCurveModel::AddKeys(TArrayView InKeyPositions, TArrayView InKeyAttributes, TArrayView>* OutKeyHandles) { check(InKeyPositions.Num() == InKeyAttributes.Num() && (!OutKeyHandles || OutKeyHandles->Num() == InKeyPositions.Num())); ChannelType* Channel = ChannelHandle.Get(); UMovieSceneSection* Section = WeakSection.Get(); const FMovieSceneChannelMetaData* MetaData = ChannelHandle.GetMetaData(); UMovieSceneSignedObject* SignedOwner = Cast(WeakOwningObject.Get()); if (!SignedOwner) { SignedOwner = WeakSection.Get(); } if (Channel && MetaData && Section && !IsReadOnly()) { SignedOwner->Modify(); TMovieSceneChannelData ChannelData = Channel->GetData(); FFrameRate TickResolution = Section->GetTypedOuter()->GetTickResolution(); TArray NewKeyHandles; NewKeyHandles.SetNumUninitialized(InKeyPositions.Num()); FFrameNumber MinFrame = TNumericLimits::Max(); FFrameNumber MaxFrame = TNumericLimits::Min(); for (int32 Index = 0; Index < InKeyPositions.Num(); ++Index) { FKeyPosition Position = InKeyPositions[Index]; FFrameNumber Time = (Position.InputValue * TickResolution).RoundToFrame(); MinFrame = FMath::Min(MinFrame, Time); MaxFrame = FMath::Max(MaxFrame, Time); ChannelValue Value = (ChannelValue)(Position.OutputValue); FKeyHandle NewHandle = ChannelData.UpdateOrAddKey(Time, Value); if (NewHandle != FKeyHandle::Invalid()) { NewKeyHandles[Index] = NewHandle; if (OutKeyHandles) { (*OutKeyHandles)[Index] = NewHandle; } } } if (InKeyPositions.Num() > 0) { FFrameNumber Offset = MetaData->GetOffsetTime(Section); Section->ExpandToFrame(MinFrame + Offset); } // We reuse SetKeyAttributes here as there is complex logic determining which parts of the attributes are valid to set. // For now we need to duplicate the new key handle array due to API mismatch. This will auto calculate tangents if needed. SetKeyAttributes(NewKeyHandles, InKeyAttributes); Channel->PostEditChange(); if (WeakSequencer.IsValid()) { WeakSequencer.Pin()->OnChannelChanged().Broadcast(MetaData, Section); } CurveModifiedDelegate.Broadcast(); } } template void FChannelCurveModel::RemoveKeys(TArrayView InKeys, double InCurrentTime) { ChannelType* Channel = ChannelHandle.Get(); UMovieSceneSection* Section = WeakSection.Get(); UMovieSceneSignedObject* SignedOwner = Cast(WeakOwningObject.Get()); if (!SignedOwner) { SignedOwner = WeakSection.Get(); } if (Channel && Section && !IsReadOnly()) { SignedOwner->Modify(); TMovieSceneChannelData ChannelData = Channel->GetData(); double CurrentValue = 0.0; Evaluate(InCurrentTime, CurrentValue); for (FKeyHandle Handle : InKeys) { int32 KeyIndex = ChannelData.GetIndex(Handle); if (KeyIndex != INDEX_NONE) { ChannelData.RemoveKey(KeyIndex); } } if (Channel->GetNumKeys() == 0) { using CurveValueType = typename ChannelType::CurveValueType; CurveValueType ValueAsCurveValue = (CurveValueType)(CurrentValue); //needed for double to other type conversions Channel->SetDefault(ValueAsCurveValue); } Channel->PostEditChange(); if (WeakSequencer.IsValid()) { const FMovieSceneChannelMetaData* MetaData = ChannelHandle.GetMetaData(); WeakSequencer.Pin()->OnChannelChanged().Broadcast(MetaData, Section); } CurveModifiedDelegate.Broadcast(); } } template bool FChannelCurveModel::IsReadOnly() const { UMovieSceneSection* Section = WeakSection.Get(); if (Section) { return Section->IsReadOnly(); } return false; } template void FChannelCurveModel::FixupCurve() { if (UMovieSceneSection* Section = WeakSection.Get()) { FMovieSceneChannelProxy* NewChannelProxy = &Section->GetChannelProxy(); ChannelHandle = NewChannelProxy->CopyHandle(ChannelHandle); OnDestroyHandle = NewChannelProxy->OnDestroy.AddRaw(this, &FChannelCurveModel::FixupCurve); } } template void FChannelCurveModel::GetCurveColorObjectAndName(UObject** OutObject, FString& OutName) const { if (UMovieSceneSection* Section = WeakSection.Get()) { *OutObject = Section->GetImplicitObjectOwner(); if (const FMovieSceneChannelMetaData* MetaData = ChannelHandle.GetMetaData()) { OutName = FString::Printf(TEXT( "%s.%s" ), *MetaData->Group.ToString(), *MetaData->DisplayText.ToString()); return; } OutName = GetIntentionName(); return; } // Just call base if it doesn't work FCurveModel::GetCurveColorObjectAndName(OutObject, OutName); } // Explicit template instantiation template class FChannelCurveModel; template class FChannelCurveModel; template class FChannelCurveModel; template class FChannelCurveModel; template class FChannelCurveModel;