280 lines
11 KiB
C++
280 lines
11 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "BoneControllers/AnimNode_TwoBoneIK.h"
|
|
#include "Engine/Engine.h"
|
|
#include "AnimationRuntime.h"
|
|
#include "Components/SkeletalMeshComponent.h"
|
|
#include "Materials/Material.h"
|
|
#include "TwoBoneIK.h"
|
|
#include "AnimationCoreLibrary.h"
|
|
#include "Animation/AnimInstanceProxy.h"
|
|
#include "PrimitiveDrawingUtils.h"
|
|
#include "Materials/MaterialInstanceDynamic.h"
|
|
#include "MaterialShared.h"
|
|
#include "Animation/AnimTrace.h"
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(AnimNode_TwoBoneIK)
|
|
|
|
DECLARE_CYCLE_STAT(TEXT("TwoBoneIK Eval"), STAT_TwoBoneIK_Eval, STATGROUP_Anim);
|
|
|
|
|
|
/////////////////////////////////////////////////////
|
|
// FAnimNode_TwoBoneIK
|
|
|
|
FAnimNode_TwoBoneIK::FAnimNode_TwoBoneIK()
|
|
: StartStretchRatio(1.f)
|
|
, MaxStretchScale(1.2f)
|
|
#if WITH_EDITORONLY_DATA
|
|
, StretchLimits_DEPRECATED(FVector2D::ZeroVector)
|
|
, bNoTwist_DEPRECATED(false)
|
|
#endif
|
|
, EffectorLocation(FVector::ZeroVector)
|
|
, CachedUpperLimbIndex(INDEX_NONE)
|
|
, JointTargetLocation(FVector::ZeroVector)
|
|
, CachedLowerLimbIndex(INDEX_NONE)
|
|
, EffectorLocationSpace(BCS_ComponentSpace)
|
|
, JointTargetLocationSpace(BCS_ComponentSpace)
|
|
, bAllowStretching(false)
|
|
, bTakeRotationFromEffectorSpace(false)
|
|
, bMaintainEffectorRelRot(false)
|
|
, bAllowTwist(true)
|
|
{
|
|
}
|
|
|
|
void FAnimNode_TwoBoneIK::GatherDebugData(FNodeDebugData& DebugData)
|
|
{
|
|
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(GatherDebugData)
|
|
FString DebugLine = DebugData.GetNodeName(this);
|
|
|
|
DebugLine += "(";
|
|
AddDebugNodeData(DebugLine);
|
|
DebugLine += FString::Printf(TEXT(" IKBone: %s)"), *IKBone.BoneName.ToString());
|
|
DebugData.AddDebugItem(DebugLine);
|
|
|
|
ComponentPose.GatherDebugData(DebugData);
|
|
}
|
|
|
|
FTransform FAnimNode_TwoBoneIK::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_TwoBoneIK::EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray<FBoneTransform>& OutBoneTransforms)
|
|
{
|
|
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(EvaluateSkeletalControl_AnyThread)
|
|
SCOPE_CYCLE_COUNTER(STAT_TwoBoneIK_Eval);
|
|
|
|
check(OutBoneTransforms.Num() == 0);
|
|
|
|
const FBoneContainer& BoneContainer = Output.Pose.GetPose().GetBoneContainer();
|
|
|
|
// Get indices of the lower and upper limb bones and check validity.
|
|
bool bInvalidLimb = false;
|
|
|
|
FCompactPoseBoneIndex IKBoneCompactPoseIndex = IKBone.GetCompactPoseIndex(BoneContainer);
|
|
|
|
const bool bInBoneSpace = (EffectorLocationSpace == BCS_ParentBoneSpace) || (EffectorLocationSpace == BCS_BoneSpace);
|
|
|
|
// Get Local Space transforms for our bones. We do this first in case they already are local.
|
|
// As right after we get them in component space. (And that does the auto conversion).
|
|
// We might save one transform by doing local first...
|
|
const FTransform EndBoneLocalTransform = Output.Pose.GetLocalSpaceTransform(IKBoneCompactPoseIndex);
|
|
const FTransform LowerLimbLocalTransform = Output.Pose.GetLocalSpaceTransform(CachedLowerLimbIndex);
|
|
const FTransform UpperLimbLocalTransform = Output.Pose.GetLocalSpaceTransform(CachedUpperLimbIndex);
|
|
|
|
// Now get those in component space...
|
|
FTransform LowerLimbCSTransform = Output.Pose.GetComponentSpaceTransform(CachedLowerLimbIndex);
|
|
FTransform UpperLimbCSTransform = Output.Pose.GetComponentSpaceTransform(CachedUpperLimbIndex);
|
|
FTransform EndBoneCSTransform = Output.Pose.GetComponentSpaceTransform(IKBoneCompactPoseIndex);
|
|
|
|
// Get current position of root of limb.
|
|
// All position are in Component space.
|
|
const FVector RootPos = UpperLimbCSTransform.GetTranslation();
|
|
const FVector InitialJointPos = LowerLimbCSTransform.GetTranslation();
|
|
const FVector InitialEndPos = EndBoneCSTransform.GetTranslation();
|
|
|
|
// Transform EffectorLocation from EffectorLocationSpace to ComponentSpace.
|
|
FTransform EffectorTransform = GetTargetTransform(Output.AnimInstanceProxy->GetComponentTransform(), Output.Pose, EffectorTarget, EffectorLocationSpace, EffectorLocation);
|
|
|
|
// Get joint target (used for defining plane that joint should be in).
|
|
FTransform JointTargetTransform = GetTargetTransform(Output.AnimInstanceProxy->GetComponentTransform(), Output.Pose, JointTarget, JointTargetLocationSpace, JointTargetLocation);
|
|
|
|
FVector JointTargetPos = JointTargetTransform.GetTranslation();
|
|
|
|
// This is our reach goal.
|
|
FVector DesiredPos = EffectorTransform.GetTranslation();
|
|
|
|
// IK solver
|
|
UpperLimbCSTransform.SetLocation(RootPos);
|
|
LowerLimbCSTransform.SetLocation(InitialJointPos);
|
|
EndBoneCSTransform.SetLocation(InitialEndPos);
|
|
|
|
AnimationCore::SolveTwoBoneIK(UpperLimbCSTransform, LowerLimbCSTransform, EndBoneCSTransform, JointTargetPos, DesiredPos, bAllowStretching, StartStretchRatio, MaxStretchScale);
|
|
|
|
#if WITH_EDITOR
|
|
CachedJointTargetPos = JointTargetPos;
|
|
CachedJoints[0] = UpperLimbCSTransform.GetLocation();
|
|
CachedJoints[1] = LowerLimbCSTransform.GetLocation();
|
|
CachedJoints[2] = EndBoneCSTransform.GetLocation();
|
|
#endif // WITH_EDITOR
|
|
|
|
// if no twist, we clear twist from each limb
|
|
if (!bAllowTwist)
|
|
{
|
|
auto RemoveTwist = [this](const FTransform& InParentTransform, FTransform& InOutTransform, const FTransform& OriginalLocalTransform, const FVector& InAlignVector)
|
|
{
|
|
FTransform LocalTransform = InOutTransform.GetRelativeTransform(InParentTransform);
|
|
FQuat LocalRotation = LocalTransform.GetRotation();
|
|
FQuat NewTwist, NewSwing;
|
|
LocalRotation.ToSwingTwist(InAlignVector, NewSwing, NewTwist);
|
|
NewSwing.Normalize();
|
|
|
|
// get new twist from old local
|
|
LocalRotation = OriginalLocalTransform.GetRotation();
|
|
FQuat OldTwist, OldSwing;
|
|
LocalRotation.ToSwingTwist(InAlignVector, OldSwing, OldTwist);
|
|
OldTwist.Normalize();
|
|
|
|
InOutTransform.SetRotation(InParentTransform.GetRotation() * NewSwing * OldTwist);
|
|
InOutTransform.NormalizeRotation();
|
|
};
|
|
|
|
const FCompactPoseBoneIndex UpperLimbParentIndex = BoneContainer.GetParentBoneIndex(CachedUpperLimbIndex);
|
|
FVector AlignDir = TwistAxis.GetTransformedAxis(FTransform::Identity);
|
|
if (UpperLimbParentIndex != INDEX_NONE)
|
|
{
|
|
FTransform UpperLimbParentTransform = Output.Pose.GetComponentSpaceTransform(UpperLimbParentIndex);
|
|
RemoveTwist(UpperLimbParentTransform, UpperLimbCSTransform, UpperLimbLocalTransform, AlignDir);
|
|
}
|
|
|
|
RemoveTwist(UpperLimbCSTransform, LowerLimbCSTransform, LowerLimbLocalTransform, AlignDir);
|
|
}
|
|
|
|
// Update transform for upper bone.
|
|
{
|
|
// Order important. First bone is upper limb.
|
|
OutBoneTransforms.Add( FBoneTransform(CachedUpperLimbIndex, UpperLimbCSTransform) );
|
|
}
|
|
|
|
// Update transform for lower bone.
|
|
{
|
|
// Order important. Second bone is lower limb.
|
|
OutBoneTransforms.Add( FBoneTransform(CachedLowerLimbIndex, LowerLimbCSTransform) );
|
|
}
|
|
|
|
// Update transform for end bone.
|
|
{
|
|
// only allow bTakeRotationFromEffectorSpace during bone space
|
|
if (bInBoneSpace && bTakeRotationFromEffectorSpace)
|
|
{
|
|
EndBoneCSTransform.SetRotation(EffectorTransform.GetRotation());
|
|
}
|
|
else if (bMaintainEffectorRelRot)
|
|
{
|
|
EndBoneCSTransform = EndBoneLocalTransform * LowerLimbCSTransform;
|
|
}
|
|
// Order important. Third bone is End Bone.
|
|
OutBoneTransforms.Add(FBoneTransform(IKBoneCompactPoseIndex, EndBoneCSTransform));
|
|
}
|
|
|
|
// Make sure we have correct number of bones
|
|
check(OutBoneTransforms.Num() == 3);
|
|
|
|
TRACE_ANIM_NODE_VALUE(Output, TEXT("IK Bone"), IKBone.BoneName);
|
|
}
|
|
|
|
bool FAnimNode_TwoBoneIK::IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones)
|
|
{
|
|
if (!IKBone.IsValidToEvaluate(RequiredBones))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (CachedUpperLimbIndex == INDEX_NONE || CachedLowerLimbIndex == INDEX_NONE)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// check bone space here
|
|
if (EffectorLocationSpace == BCS_ParentBoneSpace || EffectorLocationSpace == BCS_BoneSpace)
|
|
{
|
|
if (!EffectorTarget.IsValidToEvaluate(RequiredBones))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (JointTargetLocationSpace == BCS_ParentBoneSpace || JointTargetLocationSpace == BCS_BoneSpace)
|
|
{
|
|
if (!JointTarget.IsValidToEvaluate(RequiredBones))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FAnimNode_TwoBoneIK::InitializeBoneReferences(const FBoneContainer& RequiredBones)
|
|
{
|
|
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(InitializeBoneReferences)
|
|
IKBone.Initialize(RequiredBones);
|
|
|
|
EffectorTarget.InitializeBoneReferences(RequiredBones);
|
|
JointTarget.InitializeBoneReferences(RequiredBones);
|
|
|
|
FCompactPoseBoneIndex IKBoneCompactPoseIndex = IKBone.GetCompactPoseIndex(RequiredBones);
|
|
CachedLowerLimbIndex = FCompactPoseBoneIndex(INDEX_NONE);
|
|
CachedUpperLimbIndex = FCompactPoseBoneIndex(INDEX_NONE);
|
|
if (IKBoneCompactPoseIndex != INDEX_NONE)
|
|
{
|
|
CachedLowerLimbIndex = RequiredBones.GetParentBoneIndex(IKBoneCompactPoseIndex);
|
|
if (CachedLowerLimbIndex != INDEX_NONE)
|
|
{
|
|
CachedUpperLimbIndex = RequiredBones.GetParentBoneIndex(CachedLowerLimbIndex);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void FAnimNode_TwoBoneIK::Initialize_AnyThread(const FAnimationInitializeContext& Context)
|
|
{
|
|
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Initialize_AnyThread)
|
|
Super::Initialize_AnyThread(Context);
|
|
EffectorTarget.Initialize(Context.AnimInstanceProxy);
|
|
JointTarget.Initialize(Context.AnimInstanceProxy);
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
// can't use World Draw functions because this is called from Render of viewport, AFTER ticking component,
|
|
// which means LineBatcher already has ticked, so it won't render anymore
|
|
// to use World Draw functions, we have to call this from tick of actor
|
|
void FAnimNode_TwoBoneIK::ConditionalDebugDraw(FPrimitiveDrawInterface* PDI, USkeletalMeshComponent* MeshComp) const
|
|
{
|
|
FTransform LocalToWorld = MeshComp->GetComponentToWorld();
|
|
FVector WorldPosition[3];
|
|
WorldPosition[0] = LocalToWorld.TransformPosition(CachedJoints[0]);
|
|
WorldPosition[1] = LocalToWorld.TransformPosition(CachedJoints[1]);
|
|
WorldPosition[2] = LocalToWorld.TransformPosition(CachedJoints[2]);
|
|
const FVector JointTargetInWorld = LocalToWorld.TransformPosition(CachedJointTargetPos);
|
|
|
|
DrawTriangle(PDI, WorldPosition[0], WorldPosition[1], WorldPosition[2], GEngine->DebugEditorMaterial->GetRenderProxy(), SDPG_World);
|
|
PDI->DrawLine(WorldPosition[0], JointTargetInWorld, FLinearColor::Red, SDPG_Foreground);
|
|
PDI->DrawLine(WorldPosition[1], JointTargetInWorld, FLinearColor::Red, SDPG_Foreground);
|
|
PDI->DrawLine(WorldPosition[2], JointTargetInWorld, FLinearColor::Red, SDPG_Foreground);
|
|
}
|
|
#endif // WITH_EDITOR
|