Files
UnrealEngine/Engine/Source/Runtime/AnimGraphRuntime/Private/BoneControllers/AnimNode_CCDIK.cpp
2025-05-18 13:04:45 +08:00

210 lines
7.1 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "BoneControllers/AnimNode_CCDIK.h"
#include "Animation/AnimTypes.h"
#include "Animation/AnimStats.h"
#include "AnimationRuntime.h"
#include "DrawDebugHelpers.h"
#include "EngineDefines.h"
#include "Animation/AnimInstanceProxy.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(AnimNode_CCDIK)
/////////////////////////////////////////////////////
// AnimNode_CCDIK
// Implementation of the CCDIK IK Algorithm
FAnimNode_CCDIK::FAnimNode_CCDIK()
: EffectorLocation(FVector::ZeroVector)
, EffectorLocationSpace(BCS_ComponentSpace)
, Precision(1.f)
, MaxIterations(10)
, bStartFromTail(true)
, bEnableRotationLimit(false)
{
}
FVector FAnimNode_CCDIK::GetCurrentLocation(FCSPose<FCompactPose>& MeshBases, const FCompactPoseBoneIndex& BoneIndex)
{
return MeshBases.GetComponentSpaceTransform(BoneIndex).GetLocation();
}
FTransform FAnimNode_CCDIK::GetTargetTransform(const FTransform& InComponentTransform, FCSPose<FCompactPose>& MeshBases, FBoneSocketTarget& InTarget, EBoneControlSpace Space, const FVector& InOffset)
{
FTransform OutTransform;
if (Space == BCS_BoneSpace)
{
OutTransform = InTarget.GetTargetTransform(InOffset, MeshBases, InComponentTransform);
}
else
{
// parent bone space still goes through this way
// if your target is socket, it will try find parents of joint that socket belongs to
OutTransform.SetLocation(InOffset);
FAnimationRuntime::ConvertBoneSpaceTransformToCS(InComponentTransform, MeshBases, OutTransform, InTarget.GetCompactPoseBoneIndex(), Space);
}
return OutTransform;
}
void FAnimNode_CCDIK::EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray<FBoneTransform>& OutBoneTransforms)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(EvaluateSkeletalControl_AnyThread)
ANIM_MT_SCOPE_CYCLE_COUNTER_VERBOSE(CCDIK, !IsInGameThread());
const FBoneContainer& BoneContainer = Output.Pose.GetPose().GetBoneContainer();
// Update EffectorLocation if it is based off a bone position
FTransform CSEffectorTransform = GetTargetTransform(Output.AnimInstanceProxy->GetComponentTransform(), Output.Pose, EffectorTarget, EffectorLocationSpace, EffectorLocation);
FVector const CSEffectorLocation = CSEffectorTransform.GetLocation();
// Gather all bone indices between root and tip.
TArray<FCompactPoseBoneIndex> BoneIndices;
{
const FCompactPoseBoneIndex RootIndex = RootBone.GetCompactPoseIndex(BoneContainer);
FCompactPoseBoneIndex BoneIndex = TipBone.GetCompactPoseIndex(BoneContainer);
do
{
BoneIndices.Insert(BoneIndex, 0);
BoneIndex = Output.Pose.GetPose().GetParentBoneIndex(BoneIndex);
} while (BoneIndex != RootIndex);
BoneIndices.Insert(BoneIndex, 0);
}
// Gather transforms
int32 const NumTransforms = BoneIndices.Num();
OutBoneTransforms.AddUninitialized(NumTransforms);
// Gather chain links. These are non zero length bones.
TArray<FCCDIKChainLink> Chain;
Chain.Reserve(NumTransforms);
// Start with Root Bone
{
const FCompactPoseBoneIndex& RootBoneIndex = BoneIndices[0];
const FTransform& LocalTransform = Output.Pose.GetLocalSpaceTransform(RootBoneIndex);
const FTransform& BoneCSTransform = Output.Pose.GetComponentSpaceTransform(RootBoneIndex);
OutBoneTransforms[0] = FBoneTransform(RootBoneIndex, BoneCSTransform);
Chain.Add(FCCDIKChainLink(BoneCSTransform, LocalTransform, 0));
}
// Go through remaining transforms
for (int32 TransformIndex = 1; TransformIndex < NumTransforms; TransformIndex++)
{
const FCompactPoseBoneIndex& BoneIndex = BoneIndices[TransformIndex];
const FTransform& LocalTransform = Output.Pose.GetLocalSpaceTransform(BoneIndex);
const FTransform& BoneCSTransform = Output.Pose.GetComponentSpaceTransform(BoneIndex);
FVector const BoneCSPosition = BoneCSTransform.GetLocation();
OutBoneTransforms[TransformIndex] = FBoneTransform(BoneIndex, BoneCSTransform);
// Calculate the combined length of this segment of skeleton
double const BoneLength = FVector::Dist(BoneCSPosition, OutBoneTransforms[TransformIndex - 1].Transform.GetLocation());
if (!FMath::IsNearlyZero(BoneLength))
{
Chain.Add(FCCDIKChainLink(BoneCSTransform, LocalTransform, TransformIndex));
}
else
{
// Mark this transform as a zero length child of the last link.
// It will inherit position and delta rotation from parent link.
FCCDIKChainLink & ParentLink = Chain[Chain.Num() - 1];
ParentLink.ChildZeroLengthTransformIndices.Add(TransformIndex);
}
}
// solve
bool bBoneLocationUpdated = AnimationCore::SolveCCDIK(Chain, CSEffectorLocation, Precision, MaxIterations, bStartFromTail, bEnableRotationLimit, RotationLimitPerJoints);
// If we moved some bones, update bone transforms.
if (bBoneLocationUpdated)
{
int32 NumChainLinks = Chain.Num();
// First step: update bone transform positions from chain links.
for (int32 LinkIndex = 0; LinkIndex < NumChainLinks; LinkIndex++)
{
FCCDIKChainLink const & ChainLink = Chain[LinkIndex];
OutBoneTransforms[ChainLink.TransformIndex].Transform = ChainLink.Transform;
// If there are any zero length children, update position of those
int32 const NumChildren = ChainLink.ChildZeroLengthTransformIndices.Num();
for (int32 ChildIndex = 0; ChildIndex < NumChildren; ChildIndex++)
{
OutBoneTransforms[ChainLink.ChildZeroLengthTransformIndices[ChildIndex]].Transform = ChainLink.Transform;
}
}
#if WITH_EDITOR && UE_ENABLE_DEBUG_DRAWING
DebugLines.Reset(OutBoneTransforms.Num());
DebugLines.AddUninitialized(OutBoneTransforms.Num());
for (int32 Index = 0; Index < OutBoneTransforms.Num(); ++Index)
{
DebugLines[Index] = OutBoneTransforms[Index].Transform.GetLocation();
}
#endif // WITH_EDITOR
}
}
bool FAnimNode_CCDIK::IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones)
{
if (EffectorLocationSpace == BCS_ParentBoneSpace || EffectorLocationSpace == BCS_BoneSpace)
{
if (!EffectorTarget.IsValidToEvaluate(RequiredBones))
{
return false;
}
}
// Allow evaluation if all parameters are initialized and TipBone is child of RootBone
return
(
TipBone.IsValidToEvaluate(RequiredBones)
&& RootBone.IsValidToEvaluate(RequiredBones)
&& Precision > 0
&& RequiredBones.BoneIsChildOf(TipBone.BoneIndex, RootBone.BoneIndex)
);
}
#if WITH_EDITOR
void FAnimNode_CCDIK::ResizeRotationLimitPerJoints(int32 NewSize)
{
if (NewSize == 0)
{
RotationLimitPerJoints.Reset();
}
else if (RotationLimitPerJoints.Num() != NewSize)
{
int32 StartIndex = RotationLimitPerJoints.Num();
RotationLimitPerJoints.SetNum(NewSize);
for (int32 Index = StartIndex; Index < RotationLimitPerJoints.Num(); ++Index)
{
RotationLimitPerJoints[Index] = 30.f;
}
}
}
#endif
void FAnimNode_CCDIK::InitializeBoneReferences(const FBoneContainer& RequiredBones)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(InitializeBoneReferences)
TipBone.Initialize(RequiredBones);
RootBone.Initialize(RequiredBones);
EffectorTarget.InitializeBoneReferences(RequiredBones);
}
void FAnimNode_CCDIK::GatherDebugData(FNodeDebugData& DebugData)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(GatherDebugData)
FString DebugLine = DebugData.GetNodeName(this);
DebugData.AddDebugItem(DebugLine);
ComponentPose.GatherDebugData(DebugData);
}