Files
UnrealEngine/Engine/Source/Editor/SkeletonEditor/Private/BoneProxy.cpp
2025-05-18 13:04:45 +08:00

646 lines
18 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "BoneProxy.h"
#include "IPersonaPreviewScene.h"
#include "BoneControllers/AnimNode_ModifyBone.h"
#include "AnimPreviewInstance.h"
#include "Editor.h"
#include "IPersonaToolkit.h"
#include "SAdvancedTransformInputBox.h"
#include "ScopedTransaction.h"
#include "Animation/DebugSkelMeshComponent.h"
#define LOCTEXT_NAMESPACE "BoneProxy"
UBoneProxy::UBoneProxy()
: bLocalLocation(true)
, bLocalRotation(true)
, bLocalScale(true)
, PreviousLocation(FVector::ZeroVector)
, PreviousRotation(FRotator::ZeroRotator)
, PreviousScale(FVector::ZeroVector)
, bManipulating(false)
, bIsTickable(false)
, bIsTransformEditable(true)
{
}
FTransform GetWorldSpaceBoneTransform(const FReferenceSkeleton& ReferenceSkeleton, const int32 BoneIndex)
{
const TArray<FTransform>& BonePoses = ReferenceSkeleton.GetRefBonePose();
if (BonePoses.IsValidIndex(BoneIndex))
{
FTransform WorldSpacePose = BonePoses[BoneIndex];
int32 ParentIndex = ReferenceSkeleton.GetParentIndex(BoneIndex);
while(ParentIndex != INDEX_NONE)
{
WorldSpacePose = WorldSpacePose * BonePoses[ParentIndex];
ParentIndex = ReferenceSkeleton.GetParentIndex(ParentIndex);
}
return WorldSpacePose;
}
return FTransform::Identity;
}
void UBoneProxy::Tick(float DeltaTime)
{
if (!bManipulating)
{
if (UDebugSkelMeshComponent* Component = SkelMeshComponent.Get())
{
if (Component->GetSkeletalMeshAsset() && Component->GetSkeletalMeshAsset()->IsCompiling())
{
//We do not want to tick if the skeletal mesh is inside a compilation
return;
}
if (Component->GetSkeletalMeshAsset())
{
TArray<FTransform> LocalBoneTransforms = Component->GetBoneSpaceTransforms();
const int32 BoneIndex = Component->GetBoneIndex(BoneName);
if (LocalBoneTransforms.IsValidIndex(BoneIndex))
{
const FTransform& LocalTransform = LocalBoneTransforms[BoneIndex];
const FTransform BoneTransform = Component->GetBoneTransform(BoneIndex);
if (bLocalLocation)
{
Location = LocalTransform.GetLocation();
}
else
{
Location = BoneTransform.GetLocation();
}
if (bLocalRotation)
{
Rotation = LocalTransform.GetRotation().Rotator();
}
else
{
Rotation = BoneTransform.GetRotation().Rotator();
}
if(bLocalScale)
{
Scale = LocalTransform.GetScale3D();
}
else
{
Scale = BoneTransform.GetScale3D();
}
const FTransform ReferenceTransform = Component->GetSkeletalMeshAsset()->GetRefSkeleton().GetRefBonePose()[BoneIndex];
ReferenceLocation = ReferenceTransform.GetLocation();
ReferenceRotation = ReferenceTransform.GetRotation().Rotator();
ReferenceScale = ReferenceTransform.GetScale3D();
}
// Show mesh relative transform on the details panel so we have a way to visualize the root transform when processing root motion
// Note that this doesn't always represent the actual transform of the root in the animation at current time but where root motion has taken us so far
// It will not match the root transform at the current time in the animation after lopping multiple times if we are using ProcessRootMotion::Loop
// or if we are visualizing a complex section from a montage, for example
const FTransform MeshRelativeTransform = Component->GetRelativeTransform();
MeshLocation = MeshRelativeTransform.GetLocation();
MeshRotation = MeshRelativeTransform.GetRotation().Rotator();
MeshScale = MeshRelativeTransform.GetScale3D();
}
else if (const TSharedPtr<IPersonaPreviewScene> PreviewScene = WeakPreviewScene.Pin())
{
if (USkeleton* Skeleton = PreviewScene->GetPersonaToolkit()->GetSkeleton())
{
const int32 BoneIndex = Skeleton->GetReferenceSkeleton().FindBoneIndex(BoneName);
if (BoneIndex != INDEX_NONE)
{
const FTransform& ReferenceTransform = Skeleton->GetReferenceSkeleton().GetRefBonePose()[BoneIndex];
const FTransform WorldReferenceTransform = GetWorldSpaceBoneTransform(Skeleton->GetReferenceSkeleton(), BoneIndex);
if (bLocalLocation)
{
Location = ReferenceTransform.GetLocation();
}
else
{
Location = WorldReferenceTransform.GetLocation();
}
if (bLocalRotation)
{
Rotation = ReferenceTransform.GetRotation().Rotator();
}
else
{
Rotation = WorldReferenceTransform.GetRotation().Rotator();
}
if(bLocalScale)
{
Scale = ReferenceTransform.GetScale3D();
}
else
{
Scale = WorldReferenceTransform.GetScale3D();
}
ReferenceLocation = ReferenceTransform.GetLocation();
ReferenceRotation = ReferenceTransform.GetRotation().Rotator();
ReferenceScale = ReferenceTransform.GetScale3D();
}
}
}
}
}
}
bool UBoneProxy::IsTickable() const
{
return bIsTickable;
}
TStatId UBoneProxy::GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(UBoneProxy, STATGROUP_Tickables);
}
TOptional<FVector::FReal> UBoneProxy::GetNumericValue(
ESlateTransformComponent::Type Component,
ESlateRotationRepresentation::Type Representation,
ESlateTransformSubComponent::Type SubComponent,
ETransformType TransformType) const
{
const FVector* LocationPtr = &Location;
const FRotator* RotationPtr = &Rotation;
const FVector* ScalePtr = &Scale;
switch(TransformType)
{
case TransformType_Bone:
// Pointers are already set.
break;
case TransformType_Reference:
{
LocationPtr = &ReferenceLocation;
RotationPtr = &ReferenceRotation;
ScalePtr = &ReferenceScale;
break;
}
case TransformType_Mesh:
{
LocationPtr = &MeshLocation;
RotationPtr = &MeshRotation;
ScalePtr = &MeshScale;
break;
}
}
const FEulerTransform Transform(*RotationPtr, *LocationPtr, *ScalePtr);
return SAdvancedTransformInputBox<FEulerTransform>::GetNumericValueFromTransform(
Transform,
Component,
Representation,
SubComponent
);
}
TOptional<FVector::FReal> UBoneProxy::GetMultiNumericValue(
ESlateTransformComponent::Type Component,
ESlateRotationRepresentation::Type Representation,
ESlateTransformSubComponent::Type SubComponent,
ETransformType TransformType,
TArrayView<UBoneProxy*> BoneProxies)
{
TOptional<FVector::FReal> FirstValue = BoneProxies[0]->GetNumericValue(Component, Representation, SubComponent, TransformType);
if(!FirstValue.IsSet())
{
return FirstValue;
}
for(int32 Index=1;Index<BoneProxies.Num();Index++)
{
TOptional<FVector::FReal> Value = BoneProxies[Index]->GetNumericValue(Component, Representation, SubComponent, TransformType);
if(!Value.IsSet())
{
return Value;
}
if(!FMath::IsNearlyEqual(FirstValue.GetValue(), Value.GetValue()))
{
return TOptional<FVector::FReal>();
}
}
return FirstValue;
}
void UBoneProxy::OnNumericValueChanged(
ESlateTransformComponent::Type Component,
ESlateRotationRepresentation::Type Representation,
ESlateTransformSubComponent::Type SubComponent,
FVector::FReal Value,
ETransformType TransformType)
{
if(TransformType != TransformType_Bone || !bIsTransformEditable)
{
return;
}
switch(Component)
{
case ESlateTransformComponent::Location:
{
OnPreEditChange(GET_MEMBER_NAME_CHECKED(UBoneProxy, Location));
switch(SubComponent)
{
case ESlateTransformSubComponent::X:
{
Location.X = Value;
break;
}
case ESlateTransformSubComponent::Y:
{
Location.Y = Value;
break;
}
case ESlateTransformSubComponent::Z:
{
Location.Z = Value;
break;
}
default:
checkNoEntry();
}
OnPostEditChangeProperty(GET_MEMBER_NAME_CHECKED(UBoneProxy, Location));
break;
}
case ESlateTransformComponent::Rotation:
{
OnPreEditChange(GET_MEMBER_NAME_CHECKED(UBoneProxy, Rotation));
switch(SubComponent)
{
case ESlateTransformSubComponent::Roll:
{
Rotation.Roll = Value;
break;
}
case ESlateTransformSubComponent::Pitch:
{
Rotation.Pitch = Value;
break;
}
case ESlateTransformSubComponent::Yaw:
{
Rotation.Yaw = Value;
break;
}
default:
checkNoEntry();
}
OnPostEditChangeProperty(GET_MEMBER_NAME_CHECKED(UBoneProxy, Rotation));
break;
}
case ESlateTransformComponent::Scale:
{
OnPreEditChange(GET_MEMBER_NAME_CHECKED(UBoneProxy, Scale));
switch(SubComponent)
{
case ESlateTransformSubComponent::X:
{
Scale.X = Value;
break;
}
case ESlateTransformSubComponent::Y:
{
Scale.Y = Value;
break;
}
case ESlateTransformSubComponent::Z:
{
Scale.Z = Value;
break;
}
default:
checkNoEntry();
}
OnPostEditChangeProperty(GET_MEMBER_NAME_CHECKED(UBoneProxy, Scale));
break;
}
default:
checkNoEntry();
}
}
void UBoneProxy::OnSliderMovementStateChanged(
ESlateTransformComponent::Type Component,
ESlateRotationRepresentation::Type Representation,
ESlateTransformSubComponent::Type SubComponent,
FVector::FReal Value,
ESliderMovementState SliderMovementState,
TArrayView<UBoneProxy*> BoneProxies
)
{
// If doing slider movement, we have to start the transaction at slider move begin so that
// we can make a copy of the state before value changes begin. The slider end transition
// occurs after value commit has happened, so that's where we end the transaction.
if (SliderMovementState == ESliderMovementState::Begin)
{
BeginSetValueTransaction(Component, BoneProxies);
}
else // == ESliderMovementState::End
{
EndTransaction();
}
for(UBoneProxy* BoneProxy : BoneProxies)
{
BoneProxy->bManipulating = SliderMovementState == ESliderMovementState::Begin;
}
}
void UBoneProxy::OnMultiNumericValueChanged(
ESlateTransformComponent::Type Component,
ESlateRotationRepresentation::Type Representation,
ESlateTransformSubComponent::Type SubComponent,
FVector::FReal Value,
ETextCommit::Type InCommitType,
bool bInTransactional,
ETransformType TransformType,
TArrayView<UBoneProxy*> BoneProxies)
{
if(TransformType != TransformType_Bone)
{
return;
}
// Are we in a slider movement? In that case, transaction is handled by the OnSliderMovementStateChanged
// callback, since that completely brackets calls to OnMultiNumericValueChanged for change/commit values.
bool bInSliderMovement = false;
for(UBoneProxy* BoneProxy : BoneProxies)
{
if (BoneProxy->bManipulating)
{
bInSliderMovement = true;
break;
}
}
if (bInTransactional && !bInSliderMovement)
{
BeginSetValueTransaction(Component, BoneProxies);
}
for(UBoneProxy* BoneProxy : BoneProxies)
{
BoneProxy->OnNumericValueChanged(Component, Representation, SubComponent, Value, TransformType);
}
if (bInTransactional && !bInSliderMovement)
{
EndTransaction();
}
}
bool UBoneProxy::DiffersFromDefault(ESlateTransformComponent::Type Component, ETransformType TransformType) const
{
if(TransformType == TransformType_Bone && bIsTransformEditable)
{
switch(Component)
{
case ESlateTransformComponent::Location:
{
return !Location.Equals(ReferenceLocation);
}
case ESlateTransformComponent::Rotation:
{
return !Rotation.Equals(ReferenceRotation);
}
case ESlateTransformComponent::Scale:
{
return !Scale.Equals(ReferenceScale);
}
default:
{
return DiffersFromDefault(ESlateTransformComponent::Location, TransformType) ||
DiffersFromDefault(ESlateTransformComponent::Rotation, TransformType) ||
DiffersFromDefault(ESlateTransformComponent::Scale, TransformType);
}
}
}
return false;
}
void UBoneProxy::ResetToDefault(ESlateTransformComponent::Type InComponent, ETransformType TransformType)
{
if(TransformType == TransformType_Bone && bIsTransformEditable)
{
if (const UDebugSkelMeshComponent* Component = SkelMeshComponent.Get())
{
if (Component->PreviewInstance && Component->AnimScriptInstance == Component->PreviewInstance)
{
const int32 BoneIndex = Component->GetBoneIndex(BoneName);
if (BoneIndex != INDEX_NONE && BoneIndex < Component->GetNumComponentSpaceTransforms())
{
Component->PreviewInstance->SetFlags(RF_Transactional);
Component->PreviewInstance->Modify();
FAnimNode_ModifyBone& ModifyBone = Component->PreviewInstance->ModifyBone(BoneName);
switch(InComponent)
{
case ESlateTransformComponent::Location:
{
ModifyBone.Translation = FVector::ZeroVector;
break;
}
case ESlateTransformComponent::Rotation:
{
ModifyBone.Rotation = FRotator::ZeroRotator;
break;
}
case ESlateTransformComponent::Scale:
{
ModifyBone.Scale = FVector::OneVector;
break;
}
default:
{
ModifyBone.Translation = FVector::ZeroVector;
ModifyBone.Rotation = FRotator::ZeroRotator;
ModifyBone.Scale = FVector::OneVector;
break;
}
}
if(ModifyBone.Translation.Equals(FVector::ZeroVector) &&
ModifyBone.Rotation.Equals(FRotator::ZeroRotator) &&
ModifyBone.Scale.Equals(FVector::OneVector))
{
Component->PreviewInstance->RemoveBoneModification(BoneName);
}
}
}
}
}
}
void UBoneProxy::BeginSetValueTransaction(ESlateTransformComponent::Type InComponent, TArrayView<UBoneProxy*> BoneProxies)
{
FText TransactionScopeText;
switch (InComponent)
{
case ESlateTransformComponent::Location:
TransactionScopeText = LOCTEXT("SetLocation", "Set Location");
break;
case ESlateTransformComponent::Rotation:
TransactionScopeText = LOCTEXT("SetRotation", "Set Rotation");
break;
case ESlateTransformComponent::Scale:
TransactionScopeText = LOCTEXT("SetScale", "Set Scale");
break;
default:
checkNoEntry();
}
GEditor->BeginTransaction(TransactionScopeText);
for (UBoneProxy* BoneProxy: BoneProxies)
{
if (UDebugSkelMeshComponent* Component = BoneProxy->SkelMeshComponent.Get())
{
if (Component->PreviewInstance && Component->AnimScriptInstance == Component->PreviewInstance)
{
Component->PreviewInstance->SetFlags(RF_Transactional);
Component->PreviewInstance->Modify();
}
}
}
}
void UBoneProxy::EndTransaction()
{
GEditor->EndTransaction();
}
void UBoneProxy::PreEditChange(FEditPropertyChain& PropertyAboutToChange)
{
if (UDebugSkelMeshComponent* Component = SkelMeshComponent.Get())
{
if (Component->PreviewInstance && Component->AnimScriptInstance == Component->PreviewInstance)
{
if (PropertyAboutToChange.GetActiveMemberNode()->GetValue()->GetFName() == GET_MEMBER_NAME_CHECKED(UBoneProxy, Location))
{
PreviousLocation = Location;
}
else if (PropertyAboutToChange.GetActiveMemberNode()->GetValue()->GetFName() == GET_MEMBER_NAME_CHECKED(UBoneProxy, Rotation))
{
PreviousRotation = Rotation;
}
else if (PropertyAboutToChange.GetActiveMemberNode()->GetValue()->GetFName() == GET_MEMBER_NAME_CHECKED(UBoneProxy, Scale))
{
PreviousScale = Scale;
}
}
}
}
void UBoneProxy::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
if(PropertyChangedEvent.Property != nullptr)
{
if (UDebugSkelMeshComponent* Component = SkelMeshComponent.Get())
{
if (Component->PreviewInstance && Component->AnimScriptInstance == Component->PreviewInstance)
{
int32 BoneIndex = Component->GetBoneIndex(BoneName);
if (BoneIndex != INDEX_NONE && BoneIndex < Component->GetNumComponentSpaceTransforms())
{
FTransform BoneTransform = Component->GetBoneTransform(BoneIndex);
FMatrix BoneLocalCoordSystem = Component->GetBoneTransform(BoneIndex).ToMatrixNoScale().RemoveTranslation();
FAnimNode_ModifyBone& ModifyBone = Component->PreviewInstance->ModifyBone(BoneName);
FTransform ModifyBoneTransform(ModifyBone.Rotation, ModifyBone.Translation, ModifyBone.Scale);
FTransform BaseTransform = BoneTransform.GetRelativeTransformReverse(ModifyBoneTransform);
if (PropertyChangedEvent.MemberProperty->GetFName() == GET_MEMBER_NAME_CHECKED(UBoneProxy, Location))
{
FVector Delta = (Location - PreviousLocation);
if (!Delta.IsNearlyZero())
{
if (bLocalLocation)
{
Delta = BoneLocalCoordSystem.TransformPosition(Delta);
}
FVector BoneSpaceDelta = BaseTransform.TransformVector(Delta);
ModifyBone.Translation += BoneSpaceDelta;
}
}
else if (PropertyChangedEvent.MemberProperty->GetFName() == GET_MEMBER_NAME_CHECKED(UBoneProxy, Rotation))
{
FRotator Delta = (Rotation - PreviousRotation);
if (!Delta.IsNearlyZero())
{
if (bLocalRotation)
{
// get delta in current coord space
Delta = (BoneLocalCoordSystem.Inverse() * FRotationMatrix(Delta) * BoneLocalCoordSystem).Rotator();
}
FVector RotAxis;
float RotAngle;
Delta.Quaternion().ToAxisAndAngle(RotAxis, RotAngle);
FVector4 BoneSpaceAxis = BaseTransform.TransformVectorNoScale(RotAxis);
//Calculate the new delta rotation
FQuat NewDeltaQuat(BoneSpaceAxis, RotAngle);
NewDeltaQuat.Normalize();
FRotator NewRotation = (ModifyBoneTransform * FTransform(NewDeltaQuat)).Rotator();
ModifyBone.Rotation = NewRotation;
}
}
else if (PropertyChangedEvent.MemberProperty->GetFName() == GET_MEMBER_NAME_CHECKED(UBoneProxy, Scale))
{
FVector Delta = (Scale - PreviousScale);
if (!Delta.IsNearlyZero())
{
ModifyBone.Scale += Delta;
}
}
}
}
}
}
}
void UBoneProxy::OnPreEditChange(FName PropertyName)
{
FEditPropertyChain PropertyChain;
FProperty* Property = FindFProperty<FProperty>(GetClass(), PropertyName);
PropertyChain.AddHead(Property);
PropertyChain.SetActiveMemberPropertyNode(Property);
PreEditChange(PropertyChain);
}
void UBoneProxy::OnPostEditChangeProperty(FName PropertyName)
{
FProperty* Property = FindFProperty<FProperty>(GetClass(), PropertyName);
FPropertyChangedEvent ChangedEvent(Property, bManipulating ? EPropertyChangeType::Interactive : EPropertyChangeType::ValueSet);
PostEditChangeProperty(ChangedEvent);
}
#undef LOCTEXT_NAMESPACE