Files
UnrealEngine/Engine/Source/Runtime/MovieSceneTracks/Private/Systems/MovieSceneQuaternionBlenderSystem.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

349 lines
15 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Systems/MovieSceneQuaternionBlenderSystem.h"
#include "EntitySystem/BuiltInComponentTypes.h"
#include "EntitySystem/EntityAllocationIterator.h"
#include "EntitySystem/MovieSceneEntityManager.h"
#include "EntitySystem/MovieSceneEntitySystemLinker.h"
#include "EntitySystem/MovieSceneEntitySystemTask.h"
#include "Systems/FloatChannelEvaluatorSystem.h"
#include "Systems/DoubleChannelEvaluatorSystem.h"
#include "Systems/MovieSceneBaseValueEvaluatorSystem.h"
#include "Systems/MovieSceneQuaternionInterpolationRotationSystem.h"
#include "Systems/WeightAndEasingEvaluatorSystem.h"
#include "MovieSceneTracksComponentTypes.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(MovieSceneQuaternionBlenderSystem)
namespace UE::MovieScene
{
struct FResetBuffers
{
FQuaternionBlenderAccumulationBuffers* Buffers;
void Run(FEntityAllocationWriteContext WriteContext) const
{
Buffers->AbsoluteWeights.Reset();
for (int32 Index = 0; Index < Buffers->Absolutes.Num(); ++Index)
{
Buffers->Absolutes[Index] = FQuatTransform{
FVector::ZeroVector,
FQuat::Identity,
FVector::ZeroVector
};
}
for (int32 Index = 0; Index < Buffers->Additives.Num(); ++Index)
{
Buffers->Additives[Index] = FQuatTransform{
FVector::ZeroVector,
FQuat::Identity,
FVector::ZeroVector
};
}
}
};
struct FAccumulateAbsoluteWeights
{
FQuaternionBlenderAccumulationBuffers* Buffers;
FAccumulateAbsoluteWeights(FQuaternionBlenderAccumulationBuffers* InBuffers)
: Buffers(InBuffers)
{}
void ForEachEntity(FMovieSceneBlendChannelID BlendChannel, double WeightResult) const
{
if (!Buffers->AbsoluteWeights.IsValidIndex(BlendChannel.ChannelID))
{
Buffers->AbsoluteWeights.Insert(BlendChannel.ChannelID, WeightResult);
}
else
{
Buffers->AbsoluteWeights[BlendChannel.ChannelID] += WeightResult;
}
}
};
/** Task for accumulating all weighted blend inputs into arrays based on BlendID. Will be run for Absolute, Additive and Relative blend modes*/
struct FAbsoluteAccumulationTask
{
FQuaternionBlenderAccumulationBuffers* Buffers;
FAbsoluteAccumulationTask(FQuaternionBlenderAccumulationBuffers* InBuffers)
: Buffers(InBuffers)
{}
void ForEachEntity(
FMovieSceneBlendChannelID BlendChannel,
double LocationX, double LocationY, double LocationZ,
double RotationRoll, double RotationPitch, double RotationYaw,
double ScaleX, double ScaleY, double ScaleZ,
const double* WeightResult
) const
{
double Weight = WeightResult ? *WeightResult : 1.0;
if (Weight == 0.0)
{
return;
}
const double TotalWeight = Buffers->AbsoluteWeights.IsValidIndex(BlendChannel.ChannelID)
? Buffers->AbsoluteWeights[BlendChannel.ChannelID]
: 1.0;
FQuatTransform& Transform = Buffers->Absolutes[BlendChannel.ChannelID];
FQuat ThisRotation = FRotator(RotationPitch, RotationYaw, RotationRoll).Quaternion();
FVector ThisTranslation(LocationX, LocationY, LocationZ);
FVector ThisScale(ScaleX, ScaleY, ScaleZ);
const double NormalizedWeight = Weight / TotalWeight;
Transform.Translation += ThisTranslation * NormalizedWeight;
Transform.Rotation *= FQuat::Slerp(FQuat::Identity, ThisRotation, NormalizedWeight);
Transform.Scale += ThisScale * NormalizedWeight;
}
};
struct FAdditiveAccumulationTask
{
FQuaternionBlenderAccumulationBuffers* Buffers;
FAdditiveAccumulationTask(FQuaternionBlenderAccumulationBuffers* InBuffers)
: Buffers(InBuffers)
{}
void ForEachEntity(
FMovieSceneBlendChannelID BlendChannel,
double LocationX, double LocationY, double LocationZ,
double RotationRoll, double RotationPitch, double RotationYaw,
double ScaleX, double ScaleY, double ScaleZ,
const double* WeightResult
) const
{
FQuatTransform& Transform = Buffers->Additives[BlendChannel.ChannelID];
const double Weight = WeightResult ? *WeightResult : 1.0;
Transform.Translation += FVector(LocationX, LocationY, LocationZ) * Weight;
Transform.Rotation *= FQuat::Slerp(FQuat::Identity, FRotator(RotationPitch, RotationYaw, RotationRoll).Quaternion(), Weight);
Transform.Scale += FVector(ScaleX, ScaleY, ScaleZ) * Weight;
}
};
/** Task that combines all accumulated blends for any tracked property type that has blend inputs/outputs */
struct FCombineTask
{
FQuaternionBlenderAccumulationBuffers* Buffers;
FCombineTask(FQuaternionBlenderAccumulationBuffers* InBuffers)
: Buffers(InBuffers)
{}
void ForEachEntity(
FMovieSceneBlendChannelID BlendChannel,
double& LocationX, double& LocationY, double& LocationZ,
double& RotationRoll, double& RotationPitch, double& RotationYaw,
double& ScaleX, double& ScaleY, double& ScaleZ,
const FIntermediate3DTransform* InitialValue
) const
{
const uint16 ChannelID = BlendChannel.ChannelID;
const double TotalWeight = Buffers->AbsoluteWeights.IsValidIndex(ChannelID) ? Buffers->AbsoluteWeights[ChannelID] : 1.0;
FVector ResultTranslation = Buffers->Absolutes[ChannelID].Translation;
FQuat ResultRotation = Buffers->Absolutes[ChannelID].Rotation;
FVector ResultScale = Buffers->Absolutes[ChannelID].Scale;
if (TotalWeight >= 0.0 && TotalWeight < 1.0)
{
if (ensure(InitialValue))
{
ResultTranslation = FMath::Lerp(InitialValue->GetTranslation(), ResultTranslation, TotalWeight);
ResultRotation = FQuat::Slerp(InitialValue->GetRotation().Quaternion(), ResultRotation, TotalWeight);
ResultScale = FMath::Lerp(InitialValue->GetScale(), ResultScale, TotalWeight);
}
}
ResultTranslation += Buffers->Additives[ChannelID].Translation;
ResultRotation *= Buffers->Additives[ChannelID].Rotation;
ResultScale += Buffers->Additives[ChannelID].Scale;
FRotator RotatorResult(ResultRotation);
LocationX = ResultTranslation.X;
LocationY = ResultTranslation.Y;
LocationZ = ResultTranslation.Z;
RotationRoll = RotatorResult.Roll;
RotationPitch = RotatorResult.Pitch;
RotationYaw = RotatorResult.Yaw;
ScaleX = ResultScale.X;
ScaleY = ResultScale.Y;
ScaleZ = ResultScale.Z;
}
};
} // namespace UE::MovieScene
UMovieSceneQuaternionBlenderSystem::UMovieSceneQuaternionBlenderSystem(const FObjectInitializer& ObjInit)
: Super(ObjInit)
{
using namespace UE::MovieScene;
SelectionPriority = 1000;
Phase = ESystemPhase::Scheduling;
if (HasAnyFlags(RF_ClassDefaultObject))
{
DefineImplicitPrerequisite(UFloatChannelEvaluatorSystem::StaticClass(), GetClass());
DefineImplicitPrerequisite(UDoubleChannelEvaluatorSystem::StaticClass(), GetClass());
DefineImplicitPrerequisite(UMovieSceneBaseValueEvaluatorSystem::StaticClass(), GetClass());
DefineImplicitPrerequisite(UMovieSceneQuaternionInterpolationRotationSystem::StaticClass(), GetClass());
DefineImplicitPrerequisite(UWeightAndEasingEvaluatorSystem::StaticClass(), GetClass());
}
}
void UMovieSceneQuaternionBlenderSystem::OnSchedulePersistentTasks(UE::MovieScene::IEntitySystemScheduler* TaskScheduler)
{
using namespace UE::MovieScene;
FBuiltInComponentTypes* BuiltInComponents = FBuiltInComponentTypes::Get();
FMovieSceneTracksComponentTypes* TracksComponents = FMovieSceneTracksComponentTypes::Get();
Buffers.Absolutes.SetNumZeroed(AllocatedBlendChannels.Num());
Buffers.Additives.SetNumZeroed(AllocatedBlendChannels.Num());
FTaskID ResetBufferTask = TaskScheduler->AddTask<FResetBuffers>(FTaskParams(TEXT("Reset Quaternion Blender Buffers")), &Buffers);
// Kick off additives first because they don't need normalized weighting
// Not handling additive from base yet because ideally we could do those in the AdditivesTask without caring about the base
FTaskID AdditivesTask = FEntityTaskBuilder()
.Read(BuiltInComponents->BlendChannelInput)
.ReadAllOf(BuiltInComponents->DoubleResult[0], BuiltInComponents->DoubleResult[1], BuiltInComponents->DoubleResult[2]) // Translation components
.ReadAllOf(BuiltInComponents->DoubleResult[3], BuiltInComponents->DoubleResult[4], BuiltInComponents->DoubleResult[5]) // Rotation components
.ReadAllOf(BuiltInComponents->DoubleResult[6], BuiltInComponents->DoubleResult[7], BuiltInComponents->DoubleResult[8]) // Scale components
.ReadOptional(BuiltInComponents->WeightAndEasingResult)
.FilterAny({ BuiltInComponents->Tags.AdditiveBlend, BuiltInComponents->Tags.AdditiveFromBaseBlend })
.FilterAll({ GetBlenderTypeTag() })
.FilterNone({ BuiltInComponents->Tags.Ignored })
.Schedule_PerEntity<FAdditiveAccumulationTask>(&Linker->EntityManager, TaskScheduler, &Buffers);
TaskScheduler->AddPrerequisite(ResetBufferTask, AdditivesTask);
// Accumulate all weights so we can do a normalized lerp.
// Everything downstream depends on this task
FTaskID CollectWeightsTask = FEntityTaskBuilder()
.Read(BuiltInComponents->BlendChannelInput)
.Read(BuiltInComponents->WeightAndEasingResult)
.FilterAll({ BuiltInComponents->Tags.AbsoluteBlend, GetBlenderTypeTag() })
.FilterNone({ BuiltInComponents->Tags.Ignored })
.Schedule_PerEntity<FAccumulateAbsoluteWeights>(&Linker->EntityManager, TaskScheduler, &Buffers);
TaskScheduler->AddPrerequisite(ResetBufferTask, CollectWeightsTask);
// Next we accumulate normalized transforms
FTaskID AbsolutesTask = FEntityTaskBuilder()
.Read(BuiltInComponents->BlendChannelInput)
.ReadAllOf(BuiltInComponents->DoubleResult[0], BuiltInComponents->DoubleResult[1], BuiltInComponents->DoubleResult[2]) // Translation components
.ReadAllOf(BuiltInComponents->DoubleResult[3], BuiltInComponents->DoubleResult[4], BuiltInComponents->DoubleResult[5]) // Rotation components
.ReadAllOf(BuiltInComponents->DoubleResult[6], BuiltInComponents->DoubleResult[7], BuiltInComponents->DoubleResult[8]) // Scale components
.ReadOptional(BuiltInComponents->WeightAndEasingResult)
.FilterAll({ BuiltInComponents->Tags.AbsoluteBlend, GetBlenderTypeTag() })
.FilterNone({ BuiltInComponents->Tags.Ignored })
.Schedule_PerEntity<FAbsoluteAccumulationTask>(&Linker->EntityManager, TaskScheduler, &Buffers);
TaskScheduler->AddPrerequisite(CollectWeightsTask, AbsolutesTask);
// Now blend the results
FTaskID BlendTask = FEntityTaskBuilder()
.Read(BuiltInComponents->BlendChannelOutput)
.WriteAllOf(BuiltInComponents->DoubleResult[0], BuiltInComponents->DoubleResult[1], BuiltInComponents->DoubleResult[2]) // Translation components
.WriteAllOf(BuiltInComponents->DoubleResult[3], BuiltInComponents->DoubleResult[4], BuiltInComponents->DoubleResult[5]) // Rotation components
.WriteAllOf(BuiltInComponents->DoubleResult[6], BuiltInComponents->DoubleResult[7], BuiltInComponents->DoubleResult[8]) // Scale components
.ReadOptional(TracksComponents->ComponentTransform.InitialValue)
.FilterAll({ GetBlenderTypeTag() })
.Fork_PerEntity<FCombineTask>(&Linker->EntityManager, TaskScheduler, &Buffers);
TaskScheduler->AddPrerequisite(AdditivesTask, BlendTask);
TaskScheduler->AddPrerequisite(CollectWeightsTask, BlendTask);
TaskScheduler->AddPrerequisite(AbsolutesTask, BlendTask);
}
void UMovieSceneQuaternionBlenderSystem::OnRun(FSystemTaskPrerequisites& InPrerequisites, FSystemSubsequentTasks& Subsequents)
{
using namespace UE::MovieScene;
FBuiltInComponentTypes* BuiltInComponents = FBuiltInComponentTypes::Get();
FMovieSceneTracksComponentTypes* TracksComponents = FMovieSceneTracksComponentTypes::Get();
Buffers.Absolutes.SetNumZeroed(AllocatedBlendChannels.Num());
Buffers.Additives.SetNumZeroed(AllocatedBlendChannels.Num());
FSystemTaskPrerequisites Prereqs = InPrerequisites;
// Kick off additives first because they don't need normalized weighting
// Not handling additive from base yet because ideally we could do those in the AdditivesTask without caring about the base
FGraphEventRef AdditivesTask = FEntityTaskBuilder()
.Read(BuiltInComponents->BlendChannelInput)
.ReadAllOf(BuiltInComponents->DoubleResult[0], BuiltInComponents->DoubleResult[1], BuiltInComponents->DoubleResult[2]) // Translation components
.ReadAllOf(BuiltInComponents->DoubleResult[3], BuiltInComponents->DoubleResult[4], BuiltInComponents->DoubleResult[5]) // Rotation components
.ReadAllOf(BuiltInComponents->DoubleResult[6], BuiltInComponents->DoubleResult[7], BuiltInComponents->DoubleResult[8]) // Scale components
.ReadOptional(BuiltInComponents->WeightAndEasingResult)
.FilterAny({ BuiltInComponents->Tags.AdditiveBlend, BuiltInComponents->Tags.AdditiveFromBaseBlend })
.FilterAll({ GetBlenderTypeTag() })
.FilterNone({ BuiltInComponents->Tags.Ignored })
.template Dispatch_PerEntity<FAdditiveAccumulationTask>(&Linker->EntityManager, InPrerequisites, nullptr, &Buffers);
// Accumulate all weights so we can do a normalized lerp.
// Everything downstream depends on this task
FGraphEventRef CollectWeightsTask = FEntityTaskBuilder()
.Read(BuiltInComponents->BlendChannelInput)
.Read(BuiltInComponents->WeightAndEasingResult)
.FilterAll({ BuiltInComponents->Tags.AbsoluteBlend, GetBlenderTypeTag() })
.FilterNone({ BuiltInComponents->Tags.Ignored })
.template Dispatch_PerEntity<FAccumulateAbsoluteWeights>(&Linker->EntityManager, InPrerequisites, nullptr, &Buffers);
if (CollectWeightsTask)
{
InPrerequisites.AddRootTask(CollectWeightsTask);
}
// Next we accumulate normalized transforms
FGraphEventRef AbsolutesTask = FEntityTaskBuilder()
.Read(BuiltInComponents->BlendChannelInput)
.ReadAllOf(BuiltInComponents->DoubleResult[0], BuiltInComponents->DoubleResult[1], BuiltInComponents->DoubleResult[2]) // Translation components
.ReadAllOf(BuiltInComponents->DoubleResult[3], BuiltInComponents->DoubleResult[4], BuiltInComponents->DoubleResult[5]) // Rotation components
.ReadAllOf(BuiltInComponents->DoubleResult[6], BuiltInComponents->DoubleResult[7], BuiltInComponents->DoubleResult[8]) // Scale components
.ReadOptional(BuiltInComponents->WeightAndEasingResult)
.FilterAll({ BuiltInComponents->Tags.AbsoluteBlend, GetBlenderTypeTag() })
.FilterNone({ BuiltInComponents->Tags.Ignored })
.template Dispatch_PerEntity<FAbsoluteAccumulationTask>(&Linker->EntityManager, InPrerequisites, nullptr, &Buffers);
if (AbsolutesTask)
{
InPrerequisites.AddRootTask(AbsolutesTask);
}
if (AdditivesTask)
{
InPrerequisites.AddRootTask(AdditivesTask);
}
// Now blend the results
FEntityTaskBuilder()
.Read(BuiltInComponents->BlendChannelOutput)
.WriteAllOf(BuiltInComponents->DoubleResult[0], BuiltInComponents->DoubleResult[1], BuiltInComponents->DoubleResult[2]) // Translation components
.WriteAllOf(BuiltInComponents->DoubleResult[3], BuiltInComponents->DoubleResult[4], BuiltInComponents->DoubleResult[5]) // Rotation components
.WriteAllOf(BuiltInComponents->DoubleResult[6], BuiltInComponents->DoubleResult[7], BuiltInComponents->DoubleResult[8]) // Scale components
.ReadOptional(TracksComponents->ComponentTransform.InitialValue)
.FilterAll({ GetBlenderTypeTag() })
.template Dispatch_PerEntity<FCombineTask>(&Linker->EntityManager, InPrerequisites, &Subsequents, &Buffers);
}
FGraphEventRef UMovieSceneQuaternionBlenderSystem::DispatchDecomposeTask(const UE::MovieScene::FValueDecompositionParams& Params, UE::MovieScene::FAlignedDecomposedValue* Output)
{
return nullptr;
}