Files
UnrealEngineUWP/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_TwoBoneIK.cpp
Hyojong Shin 93c4e2c807 Bone Controller Preview Feature - Applied to Two Bone IK, ModifyBone and Fabrik nodes for now
#code_review : Michael.Noland, Lina.Halper

[CL 2359834 by Hyojong Shin in Main branch]
2014-11-14 05:00:37 -05:00

475 lines
16 KiB
C++

// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
#include "AnimGraphPrivatePCH.h"
#include "AnimGraphNode_TwoBoneIK.h"
#include "DebugRenderSceneProxy.h"
// for customization details
#include "../../PropertyEditor/Public/PropertyHandle.h"
#include "../../PropertyEditor/Public/DetailLayoutBuilder.h"
#include "../../PropertyEditor/Public/DetailCategoryBuilder.h"
#include "../../PropertyEditor/Public/DetailWidgetRow.h"
#include "../../PropertyEditor/Public/IDetailPropertyRow.h"
#define LOCTEXT_NAMESPACE "A3Nodes"
/////////////////////////////////////////////////////
// FTwoBoneIKDelegate
class FTwoBoneIKDelegate : public TSharedFromThis<FTwoBoneIKDelegate>
{
public:
void UpdateLocationSpace(class IDetailLayoutBuilder* DetailBuilder)
{
if (DetailBuilder)
{
DetailBuilder->ForceRefreshDetails();
}
}
};
TSharedPtr<FTwoBoneIKDelegate> UAnimGraphNode_TwoBoneIK::TwoBoneIKDelegate = NULL;
/////////////////////////////////////////////////////
// UAnimGraphNode_TwoBoneIK
UAnimGraphNode_TwoBoneIK::UAnimGraphNode_TwoBoneIK(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
BoneSelectMode = BSM_EndEffector;
}
FText UAnimGraphNode_TwoBoneIK::GetControllerDescription() const
{
return LOCTEXT("TwoBoneIK", "Two Bone IK");
}
FText UAnimGraphNode_TwoBoneIK::GetTooltipText() const
{
return LOCTEXT("AnimGraphNode_TwoBoneIK_Tooltip", "The Two Bone IK control applies an inverse kinematic (IK) solver to a 3-joint chain, such as the limbs of a character.");
}
FText UAnimGraphNode_TwoBoneIK::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
if ((TitleType == ENodeTitleType::ListView || TitleType == ENodeTitleType::MenuTitle) && (Node.IKBone.BoneName == NAME_None))
{
return GetControllerDescription();
}
// @TODO: the bone can be altered in the property editor, so we have to
// choose to mark this dirty when that happens for this to properly work
else //if (!CachedNodeTitles.IsTitleCached(TitleType))
{
FFormatNamedArguments Args;
Args.Add(TEXT("ControllerDescription"), GetControllerDescription());
Args.Add(TEXT("BoneName"), FText::FromName(Node.IKBone.BoneName));
// FText::Format() is slow, so we cache this to save on performance
if (TitleType == ENodeTitleType::ListView || TitleType == ENodeTitleType::MenuTitle)
{
CachedNodeTitles.SetCachedTitle(TitleType, FText::Format(LOCTEXT("AnimGraphNode_IKBone_ListTitle", "{ControllerDescription} - Bone: {BoneName}"), Args));
}
else
{
CachedNodeTitles.SetCachedTitle(TitleType, FText::Format(LOCTEXT("AnimGraphNode_IKBone_Title", "{ControllerDescription}\nBone: {BoneName}"), Args));
}
}
return CachedNodeTitles[TitleType];
}
void UAnimGraphNode_TwoBoneIK::Draw( FPrimitiveDrawInterface* PDI, USkeletalMeshComponent* SkelMeshComp ) const
{
if (SkelMeshComp && SkelMeshComp->SkeletalMesh && SkelMeshComp->SkeletalMesh->Skeleton)
{
USkeleton * Skeleton = SkelMeshComp->SkeletalMesh->Skeleton;
DrawTargetLocation(PDI, SkelMeshComp, Skeleton, Node.EffectorLocationSpace, Node.EffectorSpaceBoneName, Node.EffectorLocation, FColor(255, 128, 128), FColor(180, 128, 128));
DrawTargetLocation(PDI, SkelMeshComp, Skeleton, Node.JointTargetLocationSpace, Node.JointTargetSpaceBoneName, Node.JointTargetLocation, FColor(128, 255, 128), FColor(128, 180, 128));
}
}
void UAnimGraphNode_TwoBoneIK::DrawTargetLocation(FPrimitiveDrawInterface* PDI, USkeletalMeshComponent* SkelComp, USkeleton * Skeleton, EBoneControlSpace SpaceBase, FName SpaceBoneName, const FVector & TargetLocation, const FColor & TargetColor, const FColor & BoneColor) const
{
const bool bInBoneSpace = (SpaceBase == BCS_ParentBoneSpace) || (SpaceBase == BCS_BoneSpace);
const int32 SpaceBoneIndex = bInBoneSpace ? Skeleton->GetReferenceSkeleton().FindBoneIndex(SpaceBoneName) : INDEX_NONE;
// Transform EffectorLocation from EffectorLocationSpace to ComponentSpace.
FTransform TargetTransform = FTransform (TargetLocation);
FTransform CSTransform;
ConvertToComponentSpaceTransform(SkelComp, TargetTransform, CSTransform, SpaceBoneIndex, SpaceBase);
FTransform WorldTransform = CSTransform * SkelComp->ComponentToWorld;
#if 0 // @TODO : remove this code because this doesn't show correct target location
DrawCoordinateSystem( PDI, WorldTransform.GetLocation(), WorldTransform.GetRotation().Rotator(), 20.f, SDPG_Foreground );
DrawWireDiamond( PDI, WorldTransform.ToMatrixWithScale(), 2.f, TargetColor, SDPG_Foreground );
#endif
if (bInBoneSpace)
{
ConvertToComponentSpaceTransform(SkelComp, FTransform::Identity, CSTransform, SpaceBoneIndex, SpaceBase);
WorldTransform = CSTransform * SkelComp->ComponentToWorld;
DrawCoordinateSystem( PDI, WorldTransform.GetLocation(), WorldTransform.GetRotation().Rotator(), 20.f, SDPG_Foreground );
DrawWireDiamond( PDI, WorldTransform.ToMatrixWithScale(), 2.f, BoneColor, SDPG_Foreground );
}
}
void UAnimGraphNode_TwoBoneIK::CopyNodeDataTo(FAnimNode_Base* AnimNode)
{
FAnimNode_TwoBoneIK* TwoBoneIK = static_cast<FAnimNode_TwoBoneIK*>(AnimNode);
// copies Pin values from the internal node to get data which are not compiled yet
TwoBoneIK->EffectorLocation = Node.EffectorLocation;
TwoBoneIK->JointTargetLocation = Node.JointTargetLocation;
}
void UAnimGraphNode_TwoBoneIK::CopyNodeDataFrom(const FAnimNode_Base* InNewAnimNode)
{
const FAnimNode_TwoBoneIK* TwoBoneIK = static_cast<const FAnimNode_TwoBoneIK*>(InNewAnimNode);
// copies Pin data from updated values
Node.EffectorLocation = TwoBoneIK->EffectorLocation;
Node.JointTargetLocation = TwoBoneIK->JointTargetLocation;
}
FVector UAnimGraphNode_TwoBoneIK::GetWidgetLocation(const USkeletalMeshComponent* SkelComp, FAnimNode_SkeletalControlBase* AnimNode)
{
EBoneControlSpace Space;
FName BoneName;
FVector Location;
if (BoneSelectMode == BSM_EndEffector)
{
Space = Node.EffectorLocationSpace;
Location = Node.EffectorLocation;
BoneName = Node.EffectorSpaceBoneName;
}
else // BSM_JointTarget
{
Space = Node.JointTargetLocationSpace;
if (Space == BCS_WorldSpace || Space == BCS_ComponentSpace)
{
return FVector::ZeroVector;
}
Location = Node.JointTargetLocation;
BoneName = Node.JointTargetSpaceBoneName;
}
return ConvertWidgetLocation(SkelComp, AnimNode->ForwardedPose, BoneName, Location, Space);
}
void UAnimGraphNode_TwoBoneIK::UpdateDefaultValues(const FAnimNode_Base* AnimNode)
{
FString UpdateDefaultValueName;
FVector UpdateValue;
const FAnimNode_TwoBoneIK* TwoBoneIK = static_cast<const FAnimNode_TwoBoneIK*>(AnimNode);
if (TwoBoneIK == NULL)
{
return;
}
if (BoneSelectMode == BSM_EndEffector)
{
UpdateDefaultValueName = FString("EffectorLocation");
UpdateValue = TwoBoneIK->EffectorLocation;
}
else
{
UpdateDefaultValueName = FString("JointTargetLocation");
UpdateValue = TwoBoneIK->JointTargetLocation;
}
SetDefaultValue(UpdateDefaultValueName, UpdateValue);
}
void UAnimGraphNode_TwoBoneIK::UpdateAllDefaultValues(const FAnimNode_Base* AnimNode)
{
FString UpdateDefaultValueName;
FVector UpdateValue;
const FAnimNode_TwoBoneIK* TwoBoneIK = static_cast<const FAnimNode_TwoBoneIK*>(AnimNode);
if (TwoBoneIK == NULL)
{
return;
}
UpdateDefaultValueName = FString("EffectorLocation");
UpdateValue = TwoBoneIK->EffectorLocation;
SetDefaultValue(UpdateDefaultValueName, UpdateValue);
UpdateDefaultValueName = FString("JointTargetLocation");
UpdateValue = TwoBoneIK->JointTargetLocation;
SetDefaultValue(UpdateDefaultValueName, UpdateValue);
}
FWidget::EWidgetMode UAnimGraphNode_TwoBoneIK::GetWidgetMode(const USkeletalMeshComponent* SkelComp)
{
int32 BoneIndex = SkelComp->GetBoneIndex(Node.IKBone.BoneName);
// Two bone IK node uses only Translate
if (BoneIndex != INDEX_NONE)
{
return FWidget::WM_Translate;
}
return FWidget::WM_None;
}
void UAnimGraphNode_TwoBoneIK::MoveSelectActorLocation(const USkeletalMeshComponent* SkelComp, FAnimNode_SkeletalControlBase* AnimNode)
{
USkeleton * Skeleton = SkelComp->SkeletalMesh->Skeleton;
// create a bone select actor
if (!BoneSelectActor.IsValid())
{
if (Node.JointTargetLocationSpace == EBoneControlSpace::BCS_BoneSpace ||
Node.JointTargetLocationSpace == EBoneControlSpace::BCS_ParentBoneSpace)
{
int32 JointTargetIndex = Skeleton->GetReferenceSkeleton().FindBoneIndex(Node.JointTargetSpaceBoneName);
if (JointTargetIndex != INDEX_NONE)
{
UWorld* World = SkelComp->GetWorld();
check(World);
BoneSelectActor = World->SpawnActor<ABoneSelectActor>(FVector(0), FRotator(0, 0, 0));
check(BoneSelectActor.IsValid());
}
}
}
if (!BoneSelectActor.IsValid())
{
return;
}
// move the actor's position
if (BoneSelectMode == BSM_JointTarget)
{
FName BoneName;
int32 EffectorBoneIndex = SkelComp->GetBoneIndex(Node.EffectorSpaceBoneName);
if (EffectorBoneIndex != INDEX_NONE)
{
BoneName = Node.EffectorSpaceBoneName;
}
else
{
BoneName = Node.IKBone.BoneName;
}
FVector ActorLocation = ConvertWidgetLocation(SkelComp, AnimNode->ForwardedPose, BoneName, Node.EffectorLocation, Node.EffectorLocationSpace);
BoneSelectActor->SetActorLocation(ActorLocation + FVector(0, 10, 0));
}
else if (BoneSelectMode == BSM_EndEffector)
{
int32 JointTargetIndex = SkelComp->GetBoneIndex(Node.JointTargetSpaceBoneName);
if (JointTargetIndex != INDEX_NONE)
{
FVector ActorLocation = ConvertWidgetLocation(SkelComp, AnimNode->ForwardedPose, Node.JointTargetSpaceBoneName, Node.JointTargetLocation, Node.JointTargetLocationSpace);
BoneSelectActor->SetActorLocation(ActorLocation + FVector(0, 10, 0));
}
}
}
FName UAnimGraphNode_TwoBoneIK::FindSelectedBone()
{
FName SelectedBone;
// should return mesh bone index
if (BoneSelectMode == BSM_EndEffector)
{
if (Node.EffectorLocationSpace == EBoneControlSpace::BCS_BoneSpace ||
Node.EffectorLocationSpace == EBoneControlSpace::BCS_ParentBoneSpace)
{
SelectedBone = Node.EffectorSpaceBoneName;
}
else
{
SelectedBone = Node.IKBone.BoneName;
}
}
else if (BoneSelectMode == BSM_JointTarget)
{
SelectedBone = Node.JointTargetSpaceBoneName;
}
return SelectedBone;
}
bool UAnimGraphNode_TwoBoneIK::IsActorClicked(HActor* ActorHitProxy)
{
ABoneSelectActor* BoneActor = Cast<ABoneSelectActor>(ActorHitProxy->Actor);
if (BoneActor)
{
return true;
}
return false;
}
void UAnimGraphNode_TwoBoneIK::ProcessActorClick(HActor* ActorHitProxy)
{
// toggle bone selection mode
if (BoneSelectMode == BSM_EndEffector)
{
BoneSelectMode = BSM_JointTarget;
}
else
{
BoneSelectMode = BSM_EndEffector;
}
}
void UAnimGraphNode_TwoBoneIK::DoTranslation(const USkeletalMeshComponent* SkelComp, FVector& Drag, FAnimNode_Base* InOutAnimNode)
{
FAnimNode_TwoBoneIK* TwoBoneIK = static_cast<FAnimNode_TwoBoneIK*>(InOutAnimNode);
if (BoneSelectMode == BSM_EndEffector)
{
FVector Offset = ConvertCSVectorToBoneSpace(SkelComp, Drag, TwoBoneIK->ForwardedPose, FindSelectedBone(), Node.EffectorLocationSpace);
TwoBoneIK->EffectorLocation += Offset;
Node.EffectorLocation = TwoBoneIK->EffectorLocation;
}
else if (BoneSelectMode == BSM_JointTarget)
{
FVector Offset = ConvertCSVectorToBoneSpace(SkelComp, Drag, TwoBoneIK->ForwardedPose, FindSelectedBone(), Node.JointTargetLocationSpace);
TwoBoneIK->JointTargetLocation += Offset;
Node.JointTargetLocation = TwoBoneIK->JointTargetLocation;
}
}
void UAnimGraphNode_TwoBoneIK::DeselectActor(USkeletalMeshComponent* SkelComp)
{
if(BoneSelectActor!=NULL)
{
if (SkelComp->GetWorld()->DestroyActor(BoneSelectActor.Get()))
{
BoneSelectActor = NULL;
}
}
}
void UAnimGraphNode_TwoBoneIK::CustomizeDetails(class IDetailLayoutBuilder& DetailBuilder)
{
// initialize just once
if (!TwoBoneIKDelegate.IsValid())
{
TwoBoneIKDelegate = MakeShareable(new FTwoBoneIKDelegate());
}
EBoneControlSpace Space = Node.EffectorLocationSpace;
const FString TakeRotationPropName = FString::Printf(TEXT("Node.%s"), GET_MEMBER_NAME_STRING_CHECKED(FAnimNode_TwoBoneIK, bTakeRotationFromEffectorSpace));
const FString MaintainEffectorPropName = FString::Printf(TEXT("Node.%s"), GET_MEMBER_NAME_STRING_CHECKED(FAnimNode_TwoBoneIK, bMaintainEffectorRelRot));
const FString EffectorBoneName = FString::Printf(TEXT("Node.%s"), GET_MEMBER_NAME_STRING_CHECKED(FAnimNode_TwoBoneIK, EffectorSpaceBoneName));
const FString EffectorLocationPropName = FString::Printf(TEXT("Node.%s"), GET_MEMBER_NAME_STRING_CHECKED(FAnimNode_TwoBoneIK, EffectorLocation));
if (Space == BCS_BoneSpace || Space == BCS_ParentBoneSpace)
{
IDetailCategoryBuilder& IKCategory = DetailBuilder.EditCategory("IK");
IDetailCategoryBuilder& EffectorCategory = DetailBuilder.EditCategory("EndEffector");
TSharedPtr<IPropertyHandle> PropertyHandle;
PropertyHandle = DetailBuilder.GetProperty(*TakeRotationPropName, GetClass());
EffectorCategory.AddProperty(PropertyHandle);
PropertyHandle = DetailBuilder.GetProperty(*MaintainEffectorPropName, GetClass());
EffectorCategory.AddProperty(PropertyHandle);
PropertyHandle = DetailBuilder.GetProperty(*EffectorBoneName, GetClass());
EffectorCategory.AddProperty(PropertyHandle);
}
else // hide all properties in EndEffector category
{
TSharedPtr<IPropertyHandle> PropertyHandle = DetailBuilder.GetProperty(*EffectorLocationPropName, GetClass());
DetailBuilder.HideProperty(PropertyHandle);
PropertyHandle = DetailBuilder.GetProperty(*TakeRotationPropName, GetClass());
DetailBuilder.HideProperty(PropertyHandle);
PropertyHandle = DetailBuilder.GetProperty(*MaintainEffectorPropName, GetClass());
DetailBuilder.HideProperty(PropertyHandle);
PropertyHandle = DetailBuilder.GetProperty(*EffectorBoneName, GetClass());
DetailBuilder.HideProperty(PropertyHandle);
}
Space = Node.JointTargetLocationSpace;
bool bPinVisibilityChanged = false;
const FString JointTargetSpaceBoneName = FString::Printf(TEXT("Node.%s"), GET_MEMBER_NAME_STRING_CHECKED(FAnimNode_TwoBoneIK, JointTargetSpaceBoneName));
const FString JointTargetLocation = FString::Printf(TEXT("Node.%s"), GET_MEMBER_NAME_STRING_CHECKED(FAnimNode_TwoBoneIK, JointTargetLocation));
if (Space == BCS_BoneSpace || Space == BCS_ParentBoneSpace)
{
IDetailCategoryBuilder& IKCategory = DetailBuilder.EditCategory("IK");
IDetailCategoryBuilder& EffectorCategory = DetailBuilder.EditCategory("JointTarget");
TSharedPtr<IPropertyHandle> PropertyHandle;
PropertyHandle = DetailBuilder.GetProperty(*JointTargetSpaceBoneName, GetClass());
EffectorCategory.AddProperty(PropertyHandle);
bPinVisibilityChanged = SetPinsVisibility(true);
}
else // hide all properties in JointTarget category except for JointTargetLocationSpace
{
TSharedPtr<IPropertyHandle> PropertyHandle = DetailBuilder.GetProperty(*JointTargetLocation, GetClass());
DetailBuilder.HideProperty(PropertyHandle);
PropertyHandle = DetailBuilder.GetProperty(*JointTargetSpaceBoneName, GetClass());
DetailBuilder.HideProperty(PropertyHandle);
bPinVisibilityChanged = SetPinsVisibility(false);
}
const FString EffectorLocationSpace = FString::Printf(TEXT("Node.%s"), GET_MEMBER_NAME_STRING_CHECKED(FAnimNode_TwoBoneIK, EffectorLocationSpace));
const FString JointTargetLocationSpace = FString::Printf(TEXT("Node.%s"), GET_MEMBER_NAME_STRING_CHECKED(FAnimNode_TwoBoneIK, JointTargetLocationSpace));
// refresh UIs when bone space is changed
TSharedRef<IPropertyHandle> EffectorLocHandle = DetailBuilder.GetProperty(*EffectorLocationSpace, GetClass());
if (EffectorLocHandle->IsValidHandle())
{
FSimpleDelegate UpdateEffectorSpaceDelegate = FSimpleDelegate::CreateSP(TwoBoneIKDelegate.Get(), &FTwoBoneIKDelegate::UpdateLocationSpace, &DetailBuilder);
EffectorLocHandle->SetOnPropertyValueChanged(UpdateEffectorSpaceDelegate);
}
TSharedRef<IPropertyHandle> JointTragetLocHandle = DetailBuilder.GetProperty(*JointTargetLocationSpace, GetClass());
if (JointTragetLocHandle->IsValidHandle())
{
FSimpleDelegate UpdateJointSpaceDelegate = FSimpleDelegate::CreateSP(TwoBoneIKDelegate.Get(), &FTwoBoneIKDelegate::UpdateLocationSpace, &DetailBuilder);
JointTragetLocHandle->SetOnPropertyValueChanged(UpdateJointSpaceDelegate);
}
// reconstruct node for showing/hiding Pins
if (bPinVisibilityChanged)
{
ReconstructNode();
}
}
bool UAnimGraphNode_TwoBoneIK::SetPinsVisibility(bool bShow)
{
bool bChanged = false;
for (FOptionalPinFromProperty& Pin : ShowPinForProperties)
{
if (Pin.PropertyName == GET_MEMBER_NAME_CHECKED(FAnimNode_TwoBoneIK, JointTargetLocation))
{
PreEditChange(NULL);
if (Pin.bShowPin != bShow)
{
Pin.bShowPin = bShow;
bChanged = true;
}
break;
}
}
return bChanged;
}
#undef LOCTEXT_NAMESPACE