You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Skeleton compatibility is now bi-directional. Specifying a compatible skeleton A -> B now implies B -> A. Skeleton compatibility is now an editor-only concern. The runtime will attempt to do the 'best it can' via name -> name mappings. Only the editor will prevent assigning incompatible skeletons in (e.g.) asset pickers etc. Skeleton compatibility checks in editor can now be disabled in the editor preferences (and each asset picker now has a checkbox option in its view settings that allows for quick access to this). Moves FSkeletonRemapping to its own file (which is now private). Skeleton remappings are now generated on demand on worker threads just before animation decompression and stored in a registry, guarded by FRWScopeLock for thread-safety. Fixed some anim BP compiler edge cases where asset references on pins were not getting preloaded correctly, causing skeletons to be erroneously reported as missing. Exposed the current asset registry filter in SAssetView so that menu extensions can access it (and use it to provide context) #jira UE-166054 #jira UE-167355 #rb Jurre.deBaare,John.vanderBerg #preflight 635902602e6690262afa86f9 #preflight 6372ad7a0c74adb48b472b38 #p4v-cherrypick 22878911 #preflight 6374b3c51d25fe8b931b8d7b [CL 23152671 by thomas sarkanen in ue5-main branch]
499 lines
15 KiB
C++
499 lines
15 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "AnimNodes/AnimNode_PoseDriver.h"
|
|
#include "AnimationRuntime.h"
|
|
#include "Serialization/CustomVersion.h"
|
|
#include "Animation/AnimInstanceProxy.h"
|
|
#include "RBF/RBFSolver.h"
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(AnimNode_PoseDriver)
|
|
|
|
|
|
FAnimNode_PoseDriver::FAnimNode_PoseDriver()
|
|
: DriveSource(EPoseDriverSource::Rotation)
|
|
, DriveOutput(EPoseDriverOutput::DrivePoses)
|
|
, bOnlyDriveSelectedBones(false)
|
|
, LODThreshold(INDEX_NONE)
|
|
{
|
|
RBFParams.DistanceMethod = ERBFDistanceMethod::SwingAngle;
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
SoloTargetIndex = INDEX_NONE;
|
|
bSoloDrivenOnly = false;
|
|
|
|
RadialScaling_DEPRECATED = 0.25f;
|
|
Type_DEPRECATED = EPoseDriverType::SwingOnly;
|
|
TwistAxis_DEPRECATED = BA_X;
|
|
#endif
|
|
}
|
|
|
|
void FAnimNode_PoseDriver::Initialize_AnyThread(const FAnimationInitializeContext& Context)
|
|
{
|
|
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Initialize_AnyThread)
|
|
FAnimNode_PoseHandler::Initialize_AnyThread(Context);
|
|
|
|
SourcePose.Initialize(Context);
|
|
}
|
|
|
|
void FAnimNode_PoseDriver::RebuildPoseList(const FBoneContainer& InBoneContainer, const UPoseAsset* InPoseAsset)
|
|
{
|
|
// Cache UIDs for driving curves
|
|
PoseExtractContext.PoseCurves.Reset();
|
|
const USkeleton* Skeleton = InPoseAsset->GetSkeleton();
|
|
if (Skeleton)
|
|
{
|
|
const TArray<FSmartName>& PoseNames = InPoseAsset->GetPoseNames();
|
|
for (FPoseDriverTarget& PoseTarget : PoseTargets)
|
|
{
|
|
if (DriveOutput == EPoseDriverOutput::DriveCurves)
|
|
{
|
|
PoseTarget.DrivenUID = Skeleton->GetUIDByName(USkeleton::AnimCurveMappingName, PoseTarget.DrivenName);
|
|
}
|
|
else
|
|
{
|
|
PoseTarget.DrivenUID = SmartName::MaxUID;
|
|
}
|
|
|
|
const int32 PoseIndex = InPoseAsset->GetPoseIndexByName(PoseTarget.DrivenName);
|
|
if (PoseIndex != INDEX_NONE)
|
|
{
|
|
TArray<uint16> const& LUTIndex = InBoneContainer.GetUIDToArrayLookupTable();
|
|
if (LUTIndex.IsValidIndex(PoseNames[PoseIndex].UID) && LUTIndex[PoseNames[PoseIndex].UID] != MAX_uint16)
|
|
{
|
|
// we keep pose index as that is the fastest way to search when extracting pose asset
|
|
PoseTarget.PoseCurveIndex = PoseExtractContext.PoseCurves.Add(FPoseCurve(PoseIndex, PoseNames[PoseIndex].UID, 0.f));
|
|
}
|
|
else
|
|
{
|
|
PoseTarget.PoseCurveIndex = INDEX_NONE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PoseTarget.PoseCurveIndex = INDEX_NONE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FAnimNode_PoseDriver::CacheBones_AnyThread(const FAnimationCacheBonesContext& Context)
|
|
{
|
|
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(CacheBones_AnyThread)
|
|
FAnimNode_PoseHandler::CacheBones_AnyThread(Context);
|
|
|
|
// Init pose input
|
|
SourcePose.CacheBones(Context);
|
|
|
|
const FBoneContainer& BoneContainer = Context.AnimInstanceProxy->GetRequiredBones();
|
|
|
|
// Init bone refs
|
|
for (FBoneReference& SourceBoneRef : SourceBones)
|
|
{
|
|
SourceBoneRef.Initialize(BoneContainer);
|
|
}
|
|
|
|
for (FBoneReference& OnlyDriveBoneRef : OnlyDriveBones)
|
|
{
|
|
OnlyDriveBoneRef.Initialize(BoneContainer);
|
|
}
|
|
|
|
EvalSpaceBone.Initialize(BoneContainer);
|
|
|
|
// Don't want to modify SourceBones, set weight to zero (if weight array is allocated)
|
|
for (FBoneReference& SourceBoneRef : SourceBones)
|
|
{
|
|
const FCompactPoseBoneIndex SourceCompactIndex = SourceBoneRef.GetCompactPoseIndex(BoneContainer);
|
|
if (BoneBlendWeights.IsValidIndex(SourceCompactIndex.GetInt()))
|
|
{
|
|
BoneBlendWeights[SourceCompactIndex.GetInt()] = 0.f;
|
|
}
|
|
}
|
|
|
|
|
|
// If we are filtering for only specific bones, set blend weight to zero for unwanted bones, and remember which bones to filter
|
|
BonesToFilter.Reset();
|
|
if (bOnlyDriveSelectedBones && CurrentPoseAsset.IsValid())
|
|
{
|
|
// Super call above should init BoneBlendWeights to compact pose size if CurrentPoseAsset is valid
|
|
check(BoneBlendWeights.Num() == BoneContainer.GetBoneIndicesArray().Num());
|
|
|
|
const TArray<FName> TrackNames = CurrentPoseAsset.Get()->GetTrackNames();
|
|
for (const auto& TrackName : TrackNames)
|
|
{
|
|
// See if bone is in select list
|
|
if (!IsBoneDriven(TrackName))
|
|
{
|
|
int32 MeshBoneIndex = BoneContainer.GetPoseBoneIndexForBoneName(TrackName);
|
|
FCompactPoseBoneIndex CompactBoneIndex = BoneContainer.MakeCompactPoseIndex(FMeshPoseBoneIndex(MeshBoneIndex));
|
|
if (CompactBoneIndex != INDEX_NONE)
|
|
{
|
|
BoneBlendWeights[CompactBoneIndex.GetInt()] = 0.f; // Set blend weight for non-additive
|
|
BonesToFilter.Add(CompactBoneIndex); // Remember bones to filter out for additive
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PoseExtractContext.BonesRequired.SetNumZeroed(BoneBlendWeights.Num());
|
|
for (int32 BoneIndex = 0; BoneIndex < BoneBlendWeights.Num(); BoneIndex++)
|
|
{
|
|
PoseExtractContext.BonesRequired[BoneIndex] = BoneBlendWeights[BoneIndex] > SMALL_NUMBER;
|
|
}
|
|
}
|
|
|
|
void FAnimNode_PoseDriver::UpdateAssetPlayer(const FAnimationUpdateContext& Context)
|
|
{
|
|
FAnimNode_PoseHandler::UpdateAssetPlayer(Context);
|
|
SourcePose.Update(Context);
|
|
}
|
|
|
|
void FAnimNode_PoseDriver::GatherDebugData(FNodeDebugData& DebugData)
|
|
{
|
|
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(GatherDebugData)
|
|
FAnimNode_PoseHandler::GatherDebugData(DebugData);
|
|
SourcePose.GatherDebugData(DebugData.BranchFlow(1.f));
|
|
}
|
|
|
|
float FAnimNode_PoseDriver::GetRadiusForTarget(const FRBFTarget& Target) const
|
|
{
|
|
return FRBFSolver::GetRadiusForTarget(Target, RBFParams);
|
|
}
|
|
|
|
bool FAnimNode_PoseDriver::IsBoneDriven(FName BoneName) const
|
|
{
|
|
// If not filtering, drive all the bones
|
|
if (!bOnlyDriveSelectedBones)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool bIsDriven = false;
|
|
for (const FBoneReference& BoneRef : OnlyDriveBones)
|
|
{
|
|
if (BoneRef.BoneName == BoneName)
|
|
{
|
|
bIsDriven = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return bIsDriven;
|
|
}
|
|
|
|
|
|
void FAnimNode_PoseDriver::GetRBFTargets(TArray<FRBFTarget>& OutTargets, const FBoneContainer* BoneContainer) const
|
|
{
|
|
OutTargets.Reset();
|
|
OutTargets.AddZeroed(PoseTargets.Num());
|
|
|
|
// Create entry for each target
|
|
for (int32 TargetIdx = 0; TargetIdx < PoseTargets.Num(); TargetIdx++)
|
|
{
|
|
FRBFTarget& RBFTarget = OutTargets[TargetIdx];
|
|
const FPoseDriverTarget& PoseTarget = PoseTargets[TargetIdx];
|
|
|
|
// We want to make sure we always have the right number of Values in our RBFTarget.
|
|
// If bone entries are missing, we fill with zeroes
|
|
for (int32 SourceIdx = 0; SourceIdx < SourceBones.Num(); SourceIdx++)
|
|
{
|
|
if (PoseTarget.BoneTransforms.IsValidIndex(SourceIdx))
|
|
{
|
|
const FPoseDriverTransform& BoneTransform = PoseTarget.BoneTransforms[SourceIdx];
|
|
|
|
// Get Ref Transform
|
|
FTransform RefBoneTransform = FTransform::Identity;
|
|
if (bEvalFromRefPose && BoneContainer)
|
|
{
|
|
const FCompactPoseBoneIndex CompactPoseIndex = SourceBones[SourceIdx].CachedCompactPoseIndex;
|
|
if (CompactPoseIndex < BoneContainer->GetCompactPoseNumBones())
|
|
{
|
|
RefBoneTransform = BoneContainer->GetRefPoseTransform(CompactPoseIndex);
|
|
}
|
|
}
|
|
|
|
// Target Translation
|
|
if (DriveSource == EPoseDriverSource::Translation)
|
|
{
|
|
// Make translation relative to its Ref
|
|
if (bEvalFromRefPose)
|
|
{
|
|
RBFTarget.AddFromVector(RefBoneTransform.Inverse().TransformPosition(BoneTransform.TargetTranslation));
|
|
}
|
|
else
|
|
{
|
|
RBFTarget.AddFromVector(BoneTransform.TargetTranslation);
|
|
}
|
|
}
|
|
|
|
// Target Rotation
|
|
else
|
|
{
|
|
// Make rotation relative to its Ref
|
|
if (bEvalFromRefPose)
|
|
{
|
|
const FQuat TargetRotation = BoneTransform.TargetRotation.Quaternion();
|
|
RBFTarget.AddFromRotator(RefBoneTransform.Inverse().TransformRotation(TargetRotation).Rotator());
|
|
}
|
|
else
|
|
{
|
|
RBFTarget.AddFromRotator(BoneTransform.TargetRotation);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RBFTarget.AddFromVector(FVector::ZeroVector);
|
|
}
|
|
}
|
|
|
|
RBFTarget.ScaleFactor = PoseTarget.TargetScale;
|
|
RBFTarget.bApplyCustomCurve = PoseTarget.bApplyCustomCurve;
|
|
RBFTarget.CustomCurve = PoseTarget.CustomCurve;
|
|
RBFTarget.DistanceMethod = PoseTarget.DistanceMethod;
|
|
RBFTarget.FunctionType = PoseTarget.FunctionType;
|
|
}
|
|
}
|
|
|
|
|
|
void FAnimNode_PoseDriver::Evaluate_AnyThread(FPoseContext& Output)
|
|
{
|
|
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Evaluate_AnyThread)
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_PoseDriver_Eval);
|
|
|
|
if (!IsLODEnabled(Output.AnimInstanceProxy))
|
|
{
|
|
SourcePose.Evaluate(Output);
|
|
return;
|
|
}
|
|
|
|
FPoseContext SourceData(Output);
|
|
SourcePose.Evaluate(SourceData);
|
|
|
|
// Udpate DrivenIDs if needed
|
|
if (bCachedDrivenIDsAreDirty)
|
|
{
|
|
if (CurrentPoseAsset.IsValid())
|
|
{
|
|
RebuildPoseList(Output.AnimInstanceProxy->GetRequiredBones(), CurrentPoseAsset.Get());
|
|
}
|
|
}
|
|
|
|
// Get the index of the source bone
|
|
const FBoneContainer& BoneContainer = SourceData.Pose.GetBoneContainer();
|
|
|
|
RBFInput.Values.Reset();
|
|
|
|
SourceBoneTMs.Reset();
|
|
bool bFoundAnyBone = false;
|
|
for (const FBoneReference& SourceBoneRef : SourceBones)
|
|
{
|
|
FTransform SourceBoneTM = FTransform::Identity;
|
|
|
|
const FCompactPoseBoneIndex SourceCompactIndex = SourceBoneRef.GetCompactPoseIndex(BoneContainer);
|
|
if (SourceCompactIndex.GetInt() != INDEX_NONE)
|
|
{
|
|
// If evaluating in alternative bone space, have to build component space pose
|
|
if (EvalSpaceBone.IsValidToEvaluate(BoneContainer))
|
|
{
|
|
FCSPose<FCompactPose> CSPose;
|
|
CSPose.InitPose(SourceData.Pose);
|
|
|
|
const FCompactPoseBoneIndex EvalSpaceCompactIndex = EvalSpaceBone.GetCompactPoseIndex(BoneContainer);
|
|
FTransform EvalSpaceCompSpace = CSPose.GetComponentSpaceTransform(EvalSpaceCompactIndex);
|
|
FTransform SourceBoneCompSpace = CSPose.GetComponentSpaceTransform(SourceCompactIndex);
|
|
|
|
SourceBoneTM = SourceBoneCompSpace.GetRelativeTransform(EvalSpaceCompSpace);
|
|
}
|
|
// If just evaluating in local space, just grab from local space pose
|
|
else
|
|
{
|
|
// Relative to Ref Pose
|
|
if (bEvalFromRefPose && SourceCompactIndex.GetInt() < BoneContainer.GetCompactPoseNumBones())
|
|
{
|
|
SourceBoneTM = SourceData.Pose[SourceCompactIndex].GetRelativeTransform(BoneContainer.GetRefPoseTransform(SourceCompactIndex));
|
|
}
|
|
else
|
|
{
|
|
SourceBoneTM = SourceData.Pose[SourceCompactIndex];
|
|
}
|
|
}
|
|
|
|
bFoundAnyBone = true;
|
|
}
|
|
|
|
|
|
// Build RBFInput entry
|
|
if (DriveSource == EPoseDriverSource::Translation)
|
|
{
|
|
RBFInput.AddFromVector(SourceBoneTM.GetTranslation());
|
|
}
|
|
else
|
|
{
|
|
RBFInput.AddFromRotator(SourceBoneTM.Rotator());
|
|
}
|
|
|
|
// Record this so we can use it for drawing in edit mode
|
|
SourceBoneTMs.Add(SourceBoneTM);
|
|
}
|
|
|
|
// Do nothing if bone is no bones are found/all LOD-ed out
|
|
if (!bFoundAnyBone)
|
|
{
|
|
Output = SourceData;
|
|
return;
|
|
}
|
|
|
|
RBFParams.TargetDimensions = SourceBones.Num() * 3;
|
|
|
|
OutputWeights.Reset();
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
if (SoloTargetIndex != INDEX_NONE && SoloTargetIndex < PoseTargets.Num())
|
|
{
|
|
OutputWeights.Add(FRBFOutputWeight(SoloTargetIndex, 1.0f));
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
// Get target array as RBF types
|
|
GetRBFTargets(RBFTargets, &BoneContainer);
|
|
|
|
if (!SolverData.IsValid() || !FRBFSolver::IsSolverDataValid(*SolverData, RBFParams, RBFTargets))
|
|
{
|
|
SolverData = FRBFSolver::InitSolver(RBFParams, RBFTargets);
|
|
}
|
|
|
|
// Run RBF solver
|
|
FRBFSolver::Solve(*SolverData, RBFParams, RBFTargets, RBFInput, OutputWeights);
|
|
}
|
|
|
|
// Track if we have filled Output with valid pose
|
|
bool bHaveValidPose = false;
|
|
|
|
// Process active targets (if any)
|
|
if (OutputWeights.Num() > 0)
|
|
{
|
|
// If we want to drive poses, and PoseAsset is assigned and compatible
|
|
const UPoseAsset* CachedPoseAsset = CurrentPoseAsset.Get();
|
|
if (DriveOutput == EPoseDriverOutput::DrivePoses &&
|
|
CachedPoseAsset &&
|
|
CachedPoseAsset->GetSkeleton() != nullptr)
|
|
{
|
|
FPoseContext CurrentPose(Output);
|
|
|
|
// clear the value before setting it.
|
|
for (int32 PoseIndex = 0; PoseIndex < PoseExtractContext.PoseCurves.Num(); ++PoseIndex)
|
|
{
|
|
PoseExtractContext.PoseCurves[PoseIndex].Value = 0.f;
|
|
}
|
|
|
|
// Then fill in weight for any driven poses
|
|
for (const FRBFOutputWeight& Weight : OutputWeights)
|
|
{
|
|
const FPoseDriverTarget& PoseTarget = PoseTargets[Weight.TargetIndex];
|
|
const int32 PoseIndex = PoseTarget.PoseCurveIndex;
|
|
if (PoseIndex != INDEX_NONE)
|
|
{
|
|
PoseExtractContext.PoseCurves[PoseIndex].Value = Weight.TargetWeight;
|
|
}
|
|
}
|
|
|
|
FAnimationPoseData CurrentAnimationPoseData(CurrentPose);
|
|
|
|
if (CurrentPoseAsset.Get()->GetAnimationPose(CurrentAnimationPoseData, PoseExtractContext))
|
|
{
|
|
// blend by weight
|
|
if (CurrentPoseAsset->IsValidAdditive())
|
|
{
|
|
const FTransform AdditiveIdentity(FQuat::Identity, FVector::ZeroVector, FVector::ZeroVector);
|
|
|
|
// Don't want to modify SourceBones, set additive offset to zero (not identity transform, as need zero scale)
|
|
for (const FBoneReference& SourceBoneRef : SourceBones)
|
|
{
|
|
const FCompactPoseBoneIndex SourceCompactIndex = SourceBoneRef.GetCompactPoseIndex(BoneContainer);
|
|
CurrentPose.Pose[SourceCompactIndex] = AdditiveIdentity;
|
|
}
|
|
|
|
// If filtering for specific bones, filter out bones using BonesToFilter array
|
|
if (bOnlyDriveSelectedBones)
|
|
{
|
|
for (FCompactPoseBoneIndex BoneIndex : BonesToFilter)
|
|
{
|
|
CurrentPose.Pose[BoneIndex] = AdditiveIdentity;
|
|
}
|
|
}
|
|
|
|
Output = SourceData;
|
|
|
|
FAnimationPoseData BaseAnimationPoseData(Output);
|
|
const FAnimationPoseData AdditiveAnimationPoseData(CurrentPose);
|
|
FAnimationRuntime::AccumulateAdditivePose(BaseAnimationPoseData, AdditiveAnimationPoseData, 1.f, EAdditiveAnimationType::AAT_LocalSpaceBase);
|
|
}
|
|
else
|
|
{
|
|
FAnimationPoseData BlendedAnimationPoseData(Output);
|
|
const FAnimationPoseData SourceAnimationPoseData(SourceData);
|
|
FAnimationRuntime::BlendTwoPosesTogetherPerBone(SourceAnimationPoseData, CurrentAnimationPoseData, BoneBlendWeights, BlendedAnimationPoseData);
|
|
}
|
|
|
|
bHaveValidPose = true;
|
|
}
|
|
}
|
|
// Drive curves (morphs, materials etc)
|
|
else if (DriveOutput == EPoseDriverOutput::DriveCurves)
|
|
{
|
|
// Start by copying input
|
|
Output = SourceData;
|
|
|
|
// Then set curves based on target weights
|
|
for (const FRBFOutputWeight& Weight : OutputWeights)
|
|
{
|
|
FPoseDriverTarget& PoseTarget = PoseTargets[Weight.TargetIndex];
|
|
if (PoseTarget.DrivenUID != SmartName::MaxUID)
|
|
{
|
|
Output.Curve.Set(PoseTarget.DrivenUID, Weight.TargetWeight);
|
|
}
|
|
}
|
|
|
|
bHaveValidPose = true;
|
|
}
|
|
}
|
|
|
|
// No valid pose, just pass through
|
|
if (!bHaveValidPose)
|
|
{
|
|
Output = SourceData;
|
|
}
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
else if (!bSoloDrivenOnly && SoloTargetIndex != INDEX_NONE && SoloTargetIndex < PoseTargets.Num())
|
|
{
|
|
SourceBoneTMs.Reset();
|
|
const FPoseDriverTarget& PoseTarget = PoseTargets[SoloTargetIndex];
|
|
|
|
for (int32 SourceIdx = 0; SourceIdx < SourceBones.Num(); SourceIdx++)
|
|
{
|
|
const FBoneReference& SourceBoneRef = SourceBones[SourceIdx];
|
|
const FCompactPoseBoneIndex SourceCompactIndex = SourceBoneRef.GetCompactPoseIndex(BoneContainer);
|
|
|
|
if (PoseTarget.BoneTransforms.IsValidIndex(SourceIdx) && SourceCompactIndex.GetInt() != INDEX_NONE)
|
|
{
|
|
FTransform& TargetTransform = Output.Pose[SourceCompactIndex];
|
|
const FPoseDriverTransform& SourceTransform = PoseTarget.BoneTransforms[SourceIdx];
|
|
|
|
if (DriveSource == EPoseDriverSource::Translation)
|
|
{
|
|
TargetTransform.SetTranslation(SourceTransform.TargetTranslation);
|
|
}
|
|
else
|
|
{
|
|
TargetTransform.SetRotation(SourceTransform.TargetRotation.Quaternion());
|
|
}
|
|
SourceBoneTMs.Add(TargetTransform);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|