You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
258 lines
10 KiB
C++
258 lines
10 KiB
C++
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "AnimGraphRuntimePrivatePCH.h"
|
|
#include "BoneControllers/AnimNode_Fabrik.h"
|
|
|
|
/////////////////////////////////////////////////////
|
|
// AnimNode_Fabrik
|
|
// Implementation of the FABRIK IK Algorithm
|
|
// Please see http://andreasaristidou.com/publications/FABRIK.pdf for more details
|
|
|
|
FAnimNode_Fabrik::FAnimNode_Fabrik()
|
|
: EffectorTransform(FTransform::Identity)
|
|
, EffectorTransformSpace(BCS_ComponentSpace)
|
|
, EffectorRotationSource(BRS_KeepLocalSpaceRotation)
|
|
, Precision(1.f)
|
|
, MaxIterations(10)
|
|
, bEnableDebugDraw(false)
|
|
{
|
|
}
|
|
|
|
FVector FAnimNode_Fabrik::GetCurrentLocation(FCSPose<FCompactPose>& MeshBases, const FCompactPoseBoneIndex& BoneIndex)
|
|
{
|
|
return MeshBases.GetComponentSpaceTransform(BoneIndex).GetLocation();
|
|
}
|
|
|
|
void FAnimNode_Fabrik::EvaluateBoneTransforms(USkeletalMeshComponent* SkelComp, FCSPose<FCompactPose>& MeshBases, TArray<FBoneTransform>& OutBoneTransforms)
|
|
{
|
|
const FBoneContainer& BoneContainer = MeshBases.GetPose().GetBoneContainer();
|
|
|
|
// Update EffectorLocation if it is based off a bone position
|
|
FTransform CSEffectorTransform = EffectorTransform;
|
|
FAnimationRuntime::ConvertBoneSpaceTransformToCS(SkelComp, MeshBases, CSEffectorTransform, EffectorTransformBone.GetCompactPoseIndex(BoneContainer), EffectorTransformSpace);
|
|
|
|
FVector const CSEffectorLocation = CSEffectorTransform.GetLocation();
|
|
|
|
// @fixme - we need better to draw widgets and debug information in editor.
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
if (bEnableDebugDraw)
|
|
{
|
|
// Show end effector position.
|
|
DrawDebugBox(SkelComp->GetWorld(), CSEffectorLocation, FVector(Precision), FColor::Green, true, 0.1f);
|
|
DrawDebugCoordinateSystem(SkelComp->GetWorld(), CSEffectorLocation, CSEffectorTransform.GetRotation().Rotator(), 5.f, true, 0.1f);
|
|
}
|
|
#endif
|
|
|
|
// 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 = MeshBases.GetPose().GetParentBoneIndex(BoneIndex);
|
|
} while (BoneIndex != RootIndex);
|
|
BoneIndices.Insert(BoneIndex, 0);
|
|
}
|
|
|
|
// Maximum length of skeleton segment at full extension
|
|
float MaximumReach = 0;
|
|
|
|
// Gather transforms
|
|
int32 const NumTransforms = BoneIndices.Num();
|
|
OutBoneTransforms.AddUninitialized(NumTransforms);
|
|
|
|
// Gather chain links. These are non zero length bones.
|
|
TArray<FABRIKChainLink> Chain;
|
|
Chain.Reserve(NumTransforms);
|
|
|
|
// Start with Root Bone
|
|
{
|
|
const FCompactPoseBoneIndex& RootBoneIndex = BoneIndices[0];
|
|
const FTransform& BoneCSTransform = MeshBases.GetComponentSpaceTransform(RootBoneIndex);
|
|
|
|
OutBoneTransforms[0] = FBoneTransform(RootBoneIndex, BoneCSTransform);
|
|
Chain.Add(FABRIKChainLink(BoneCSTransform.GetLocation(), 0.f, RootBoneIndex, 0));
|
|
}
|
|
|
|
// Go through remaining transforms
|
|
for (int32 TransformIndex = 1; TransformIndex < NumTransforms; TransformIndex++)
|
|
{
|
|
const FCompactPoseBoneIndex& BoneIndex = BoneIndices[TransformIndex];
|
|
|
|
const FTransform& BoneCSTransform = MeshBases.GetComponentSpaceTransform(BoneIndex);
|
|
FVector const BoneCSPosition = BoneCSTransform.GetLocation();
|
|
|
|
OutBoneTransforms[TransformIndex] = FBoneTransform(BoneIndex, BoneCSTransform);
|
|
|
|
// Calculate the combined length of this segment of skeleton
|
|
float const BoneLength = FVector::Dist(BoneCSPosition, OutBoneTransforms[TransformIndex-1].Transform.GetLocation());
|
|
|
|
if (!FMath::IsNearlyZero(BoneLength))
|
|
{
|
|
Chain.Add(FABRIKChainLink(BoneCSPosition, BoneLength, BoneIndex, TransformIndex));
|
|
MaximumReach += BoneLength;
|
|
}
|
|
else
|
|
{
|
|
// Mark this transform as a zero length child of the last link.
|
|
// It will inherit position and delta rotation from parent link.
|
|
FABRIKChainLink & ParentLink = Chain[Chain.Num()-1];
|
|
ParentLink.ChildZeroLengthTransformIndices.Add(TransformIndex);
|
|
}
|
|
}
|
|
|
|
bool bBoneLocationUpdated = false;
|
|
float const RootToTargetDistSq = FVector::DistSquared(Chain[0].Position, CSEffectorLocation);
|
|
int32 const NumChainLinks = Chain.Num();
|
|
|
|
// FABRIK algorithm - bone translation calculation
|
|
// If the effector is further away than the distance from root to tip, simply move all bones in a line from root to effector location
|
|
if (RootToTargetDistSq > FMath::Square(MaximumReach))
|
|
{
|
|
for (int32 LinkIndex = 1; LinkIndex < NumChainLinks; LinkIndex++)
|
|
{
|
|
FABRIKChainLink const & ParentLink = Chain[LinkIndex - 1];
|
|
FABRIKChainLink & CurrentLink = Chain[LinkIndex];
|
|
CurrentLink.Position = ParentLink.Position + (CSEffectorLocation - ParentLink.Position).GetUnsafeNormal() * CurrentLink.Length;
|
|
}
|
|
bBoneLocationUpdated = true;
|
|
}
|
|
else // Effector is within reach, calculate bone translations to position tip at effector location
|
|
{
|
|
int32 const TipBoneLinkIndex = NumChainLinks - 1;
|
|
|
|
// Check distance between tip location and effector location
|
|
float Slop = FVector::Dist(Chain[TipBoneLinkIndex].Position, CSEffectorLocation);
|
|
if (Slop > Precision)
|
|
{
|
|
// Set tip bone at end effector location.
|
|
Chain[TipBoneLinkIndex].Position = CSEffectorLocation;
|
|
|
|
int32 IterationCount = 0;
|
|
while ((Slop > Precision) && (IterationCount++ < MaxIterations))
|
|
{
|
|
// "Forward Reaching" stage - adjust bones from end effector.
|
|
for (int32 LinkIndex = TipBoneLinkIndex - 1; LinkIndex > 0; LinkIndex--)
|
|
{
|
|
FABRIKChainLink & CurrentLink = Chain[LinkIndex];
|
|
FABRIKChainLink const & ChildLink = Chain[LinkIndex + 1];
|
|
|
|
CurrentLink.Position = ChildLink.Position + (CurrentLink.Position - ChildLink.Position).GetUnsafeNormal() * ChildLink.Length;
|
|
}
|
|
|
|
// "Backward Reaching" stage - adjust bones from root.
|
|
for (int32 LinkIndex = 1; LinkIndex < TipBoneLinkIndex; LinkIndex++)
|
|
{
|
|
FABRIKChainLink const & ParentLink = Chain[LinkIndex - 1];
|
|
FABRIKChainLink & CurrentLink = Chain[LinkIndex];
|
|
|
|
CurrentLink.Position = ParentLink.Position + (CurrentLink.Position - ParentLink.Position).GetUnsafeNormal() * CurrentLink.Length;
|
|
}
|
|
|
|
// Re-check distance between tip location and effector location
|
|
// Since we're keeping tip on top of effector location, check with its parent bone.
|
|
Slop = FMath::Abs(Chain[TipBoneLinkIndex].Length - FVector::Dist(Chain[TipBoneLinkIndex - 1].Position, CSEffectorLocation));
|
|
}
|
|
|
|
// Place tip bone based on how close we got to target.
|
|
{
|
|
FABRIKChainLink const & ParentLink = Chain[TipBoneLinkIndex - 1];
|
|
FABRIKChainLink & CurrentLink = Chain[TipBoneLinkIndex];
|
|
|
|
CurrentLink.Position = ParentLink.Position + (CurrentLink.Position - ParentLink.Position).GetUnsafeNormal() * CurrentLink.Length;
|
|
}
|
|
|
|
bBoneLocationUpdated = true;
|
|
}
|
|
}
|
|
|
|
// If we moved some bones, update bone transforms.
|
|
if (bBoneLocationUpdated)
|
|
{
|
|
// First step: update bone transform positions from chain links.
|
|
for (int32 LinkIndex = 0; LinkIndex < NumChainLinks; LinkIndex++)
|
|
{
|
|
FABRIKChainLink const & ChainLink = Chain[LinkIndex];
|
|
OutBoneTransforms[ChainLink.TransformIndex].Transform.SetTranslation(ChainLink.Position);
|
|
|
|
// 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.SetTranslation(ChainLink.Position);
|
|
}
|
|
}
|
|
|
|
// FABRIK algorithm - re-orientation of bone local axes after translation calculation
|
|
for (int32 LinkIndex = 0; LinkIndex < NumChainLinks - 1; LinkIndex++)
|
|
{
|
|
FABRIKChainLink const & CurrentLink = Chain[LinkIndex];
|
|
FABRIKChainLink const & ChildLink = Chain[LinkIndex + 1];
|
|
|
|
// Calculate pre-translation vector between this bone and child
|
|
FVector const OldDir = (GetCurrentLocation(MeshBases, ChildLink.BoneIndex) - GetCurrentLocation(MeshBases, CurrentLink.BoneIndex)).GetUnsafeNormal();
|
|
|
|
// Get vector from the post-translation bone to it's child
|
|
FVector const NewDir = (ChildLink.Position - CurrentLink.Position).GetUnsafeNormal();
|
|
|
|
// Calculate axis of rotation from pre-translation vector to post-translation vector
|
|
FVector const RotationAxis = FVector::CrossProduct(OldDir, NewDir).GetSafeNormal();
|
|
float const RotationAngle = FMath::Acos(FVector::DotProduct(OldDir, NewDir));
|
|
FQuat const DeltaRotation = FQuat(RotationAxis, RotationAngle);
|
|
// We're going to multiply it, in order to not have to re-normalize the final quaternion, it has to be a unit quaternion.
|
|
checkSlow(DeltaRotation.IsNormalized());
|
|
|
|
// Calculate absolute rotation and set it
|
|
FTransform& CurrentBoneTransform = OutBoneTransforms[CurrentLink.TransformIndex].Transform;
|
|
CurrentBoneTransform.SetRotation(DeltaRotation * CurrentBoneTransform.GetRotation());
|
|
|
|
// Update zero length children if any
|
|
int32 const NumChildren = CurrentLink.ChildZeroLengthTransformIndices.Num();
|
|
for (int32 ChildIndex = 0; ChildIndex < NumChildren; ChildIndex++)
|
|
{
|
|
FTransform& ChildBoneTransform = OutBoneTransforms[CurrentLink.ChildZeroLengthTransformIndices[ChildIndex]].Transform;
|
|
ChildBoneTransform.SetRotation(DeltaRotation * ChildBoneTransform.GetRotation());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Special handling for tip bone's rotation.
|
|
int32 const TipBoneTransformIndex = OutBoneTransforms.Num() - 1;
|
|
switch (EffectorRotationSource)
|
|
{
|
|
case BRS_KeepLocalSpaceRotation:
|
|
OutBoneTransforms[TipBoneTransformIndex].Transform = MeshBases.GetLocalSpaceTransform(BoneIndices[TipBoneTransformIndex]) * OutBoneTransforms[TipBoneTransformIndex - 1].Transform;
|
|
break;
|
|
case BRS_CopyFromTarget:
|
|
OutBoneTransforms[TipBoneTransformIndex].Transform.SetRotation(CSEffectorTransform.GetRotation());
|
|
break;
|
|
case BRS_KeepComponentSpaceRotation:
|
|
// Don't change the orientation at all
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool FAnimNode_Fabrik::IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones)
|
|
{
|
|
// Allow evaluation if all parameters are initialized and TipBone is child of RootBone
|
|
return
|
|
(
|
|
TipBone.IsValid(RequiredBones)
|
|
&& RootBone.IsValid(RequiredBones)
|
|
&& Precision > 0
|
|
&& RequiredBones.BoneIsChildOf(TipBone.BoneIndex, RootBone.BoneIndex)
|
|
);
|
|
}
|
|
|
|
void FAnimNode_Fabrik::InitializeBoneReferences(const FBoneContainer& RequiredBones)
|
|
{
|
|
TipBone.Initialize(RequiredBones);
|
|
RootBone.Initialize(RequiredBones);
|
|
EffectorTransformBone.Initialize(RequiredBones);
|
|
}
|