mirror of
https://github.com/lucastucious/Mirror_Animation_System.git
synced 2025-05-10 14:25:51 +00:00
Commit of code
This commit is contained in:
parent
9d0ffec76a
commit
74407403fe
48 changed files with 4094 additions and 0 deletions
36
MirrorAnimationSystem/MirrorAnimationSystem.uplugin
Normal file
36
MirrorAnimationSystem/MirrorAnimationSystem.uplugin
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"FileVersion": 3,
|
||||||
|
"Version": 1,
|
||||||
|
"VersionName": "2.0",
|
||||||
|
"FriendlyName": "Mirror Animation System",
|
||||||
|
"Description": "Incorporates functionalities that allow the mirroring of animations with their root motion at runtime, as well as inside the editor.",
|
||||||
|
"Category": "Animation",
|
||||||
|
"CreatedBy": "Rexocrates",
|
||||||
|
"CreatedByURL": "https://github.com/Rexocrates",
|
||||||
|
"DocsURL": "https://github.com/Rexocrates/Mirror_Animation_System/blob/main/Mirror%20Animation%20System%20Documentation.pdf",
|
||||||
|
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/content/36b1fe6319794a25ab6dfffb82e1b29b",
|
||||||
|
"SupportURL": "https://github.com/Rexocrates/Mirror_Animation_System/issues",
|
||||||
|
"EngineVersion": "4.26.0",
|
||||||
|
"CanContainContent": false,
|
||||||
|
"Installed": true,
|
||||||
|
"Modules": [
|
||||||
|
{
|
||||||
|
"Name": "MirrorAnimationSystem",
|
||||||
|
"Type": "Runtime",
|
||||||
|
"LoadingPhase": "PreDefault",
|
||||||
|
"WhitelistPlatforms": [ "Win64", "Win32", "PS4", "XboxOne", "Mac" ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "MirrorAnimationSystemDev",
|
||||||
|
"Type": "DeveloperTool",
|
||||||
|
"LoadingPhase": "PreDefault",
|
||||||
|
"WhitelistPlatforms": [ "Win64", "Win32", "PS4", "XboxOne", "Mac" ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "MirrorAnimationSystemEditor",
|
||||||
|
"Type": "Editor",
|
||||||
|
"LoadingPhase": "PreDefault",
|
||||||
|
"WhitelistPlatforms": [ "Win64", "Win32", "PS4", "XboxOne", "Mac" ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
BIN
MirrorAnimationSystem/Resources/CreateMirrorTable_Icon.png
Normal file
BIN
MirrorAnimationSystem/Resources/CreateMirrorTable_Icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
MirrorAnimationSystem/Resources/Icon128.png
Normal file
BIN
MirrorAnimationSystem/Resources/Icon128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
BIN
MirrorAnimationSystem/Resources/MirrorAnimAsset_Icon.png
Normal file
BIN
MirrorAnimationSystem/Resources/MirrorAnimAsset_Icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
MirrorAnimationSystem/Resources/MirrorTable_ClassIcon.png
Normal file
BIN
MirrorAnimationSystem/Resources/MirrorTable_ClassIcon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
|
@ -0,0 +1,54 @@
|
||||||
|
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
using UnrealBuildTool;
|
||||||
|
|
||||||
|
public class MirrorAnimationSystem : ModuleRules
|
||||||
|
{
|
||||||
|
public MirrorAnimationSystem(ReadOnlyTargetRules Target) : base(Target)
|
||||||
|
{
|
||||||
|
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||||
|
|
||||||
|
PublicIncludePaths.AddRange(
|
||||||
|
new string[] {
|
||||||
|
// ... add public include paths required here ...
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
PrivateIncludePaths.AddRange(
|
||||||
|
new string[] {
|
||||||
|
// ... add other private include paths required here ...
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
PublicDependencyModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
"Core",
|
||||||
|
"AnimGraphRuntime",
|
||||||
|
// ... add other public dependencies that you statically link with here ...
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
PrivateDependencyModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
"CoreUObject",
|
||||||
|
"Engine",
|
||||||
|
"Slate",
|
||||||
|
"SlateCore",
|
||||||
|
// ... add private dependencies that you statically link with here ...
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
DynamicallyLoadedModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
// ... add any modules that your module loads dynamically here ...
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
#include "AnimNode_Mirror.h"
|
||||||
|
#include "MirrorAnimationSystem.h"
|
||||||
|
|
||||||
|
//#include "AnimationRuntime.h"
|
||||||
|
|
||||||
|
FAnimNode_Mirror::FAnimNode_Mirror()
|
||||||
|
:
|
||||||
|
MirrorTable(NULL)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void FAnimNode_Mirror::Initialize_AnyThread(const FAnimationInitializeContext & Context)
|
||||||
|
{
|
||||||
|
|
||||||
|
FAnimNode_Base::Initialize_AnyThread(Context);
|
||||||
|
|
||||||
|
BasePose.Initialize(Context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FAnimNode_Mirror::CacheBones_AnyThread(const FAnimationCacheBonesContext & Context)
|
||||||
|
{
|
||||||
|
|
||||||
|
BasePose.CacheBones(Context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FAnimNode_Mirror::Update_AnyThread(const FAnimationUpdateContext & Context)
|
||||||
|
{
|
||||||
|
GetEvaluateGraphExposedInputs().Execute(Context);
|
||||||
|
BasePose.Update(Context);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void FAnimNode_Mirror::Evaluate_AnyThread(FPoseContext & Output)
|
||||||
|
{
|
||||||
|
// Evaluate the input
|
||||||
|
|
||||||
|
BasePose.Evaluate(Output);
|
||||||
|
|
||||||
|
if ((MirrorTable != NULL) || (MirrorTable != nullptr))
|
||||||
|
{
|
||||||
|
int Number = MirrorTable->MirrorBones.Num();
|
||||||
|
if (Number > 0)
|
||||||
|
{
|
||||||
|
USkeletalMeshComponent* SkelComp = Output.AnimInstanceProxy->GetSkelMeshComponent();
|
||||||
|
|
||||||
|
if (SkelComp != NULL)
|
||||||
|
{
|
||||||
|
|
||||||
|
for (int i = 0; i < Number; i++)
|
||||||
|
{
|
||||||
|
|
||||||
|
FMirrorBone CurrentBone = MirrorTable->MirrorBones[i];
|
||||||
|
int BoneIndex = SkelComp->GetBoneIndex(CurrentBone.BoneName);
|
||||||
|
FCompactPoseBoneIndex CmpctBoneIndex(BoneIndex);
|
||||||
|
if (Output.Pose.IsValidIndex(CmpctBoneIndex))
|
||||||
|
{
|
||||||
|
|
||||||
|
FTransform BoneTransform = Output.Pose[CmpctBoneIndex];
|
||||||
|
|
||||||
|
BoneTransform.Mirror(CurrentBone.MirrorAxis, CurrentBone.FlipAxis);
|
||||||
|
|
||||||
|
if (CurrentBone.RotationOffset != FRotator::ZeroRotator)
|
||||||
|
{
|
||||||
|
FRotator BoneNewRotation = BoneTransform.Rotator();
|
||||||
|
|
||||||
|
BoneNewRotation.Yaw += CurrentBone.RotationOffset.Yaw;
|
||||||
|
BoneNewRotation.Roll += CurrentBone.RotationOffset.Roll;
|
||||||
|
BoneNewRotation.Pitch += CurrentBone.RotationOffset.Pitch;
|
||||||
|
|
||||||
|
BoneTransform.SetRotation(FQuat(BoneNewRotation));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CurrentBone.IsTwinBone)
|
||||||
|
{
|
||||||
|
|
||||||
|
int TwinBoneIndex = SkelComp->GetBoneIndex(CurrentBone.TwinBoneName);
|
||||||
|
FCompactPoseBoneIndex CmpctTwinBoneIndex(TwinBoneIndex);
|
||||||
|
|
||||||
|
if (Output.Pose.IsValidIndex(CmpctTwinBoneIndex))
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
FTransform TwinBoneTransform = Output.Pose[CmpctTwinBoneIndex];
|
||||||
|
TwinBoneTransform.Mirror(CurrentBone.MirrorAxis, CurrentBone.FlipAxis);
|
||||||
|
|
||||||
|
if (CurrentBone.RotationOffset != FRotator::ZeroRotator)
|
||||||
|
{
|
||||||
|
FRotator TwinBoneNewRotation = TwinBoneTransform.Rotator();
|
||||||
|
TwinBoneNewRotation.Yaw += CurrentBone.RotationOffset.Yaw;
|
||||||
|
TwinBoneNewRotation.Roll += CurrentBone.RotationOffset.Roll;
|
||||||
|
TwinBoneNewRotation.Pitch += CurrentBone.RotationOffset.Pitch;
|
||||||
|
TwinBoneTransform.SetRotation(FQuat(TwinBoneNewRotation));
|
||||||
|
}
|
||||||
|
|
||||||
|
TwinBoneTransform.SetScale3D(TwinBoneTransform.GetScale3D().GetAbs());
|
||||||
|
BoneTransform.SetScale3D(BoneTransform.GetScale3D().GetAbs());
|
||||||
|
|
||||||
|
if (CurrentBone.MirrorTranslation)
|
||||||
|
{
|
||||||
|
//TwinBoneTransform.SetRotation(TwinBoneTransform.GetRotation());
|
||||||
|
//BoneTransform.SetRotation(FQuat(BoneNewRotation));
|
||||||
|
|
||||||
|
|
||||||
|
Output.Pose[CmpctBoneIndex] = TwinBoneTransform;
|
||||||
|
Output.Pose[CmpctBoneIndex].NormalizeRotation();
|
||||||
|
|
||||||
|
Output.Pose[CmpctTwinBoneIndex] = BoneTransform;
|
||||||
|
Output.Pose[CmpctTwinBoneIndex].NormalizeRotation();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FVector TwinPos = TwinBoneTransform.GetLocation();
|
||||||
|
FVector Pos = BoneTransform.GetLocation();
|
||||||
|
|
||||||
|
TwinBoneTransform.SetLocation(Pos);
|
||||||
|
BoneTransform.SetLocation(TwinPos);
|
||||||
|
|
||||||
|
Output.Pose[CmpctBoneIndex] = TwinBoneTransform;
|
||||||
|
Output.Pose[CmpctBoneIndex].NormalizeRotation();
|
||||||
|
|
||||||
|
Output.Pose[CmpctTwinBoneIndex] = BoneTransform;
|
||||||
|
Output.Pose[CmpctTwinBoneIndex].NormalizeRotation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
BoneTransform.SetScale3D(BoneTransform.GetScale3D().GetAbs());
|
||||||
|
|
||||||
|
Output.Pose[CmpctBoneIndex] = BoneTransform;
|
||||||
|
//Output.Pose[CmpctBoneIndex].NormalizeRotation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void FAnimNode_Mirror::GatherDebugData(FNodeDebugData & DebugData)
|
||||||
|
{
|
||||||
|
FString DebugLine = DebugData.GetNodeName(this);
|
||||||
|
|
||||||
|
DebugData.AddDebugItem(DebugLine);
|
||||||
|
|
||||||
|
BasePose.GatherDebugData(DebugData);
|
||||||
|
}
|
|
@ -0,0 +1,265 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
#include "AnimNode_MirrorCS.h"
|
||||||
|
#include "MirrorAnimationSystem.h"
|
||||||
|
|
||||||
|
#include "AnimationRuntime.h"
|
||||||
|
#include "Animation/AnimInstanceProxy.h"
|
||||||
|
|
||||||
|
#include "MASUtils.h"
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
// FAnimNode_MirrorCS
|
||||||
|
|
||||||
|
FAnimNode_MirrorCS::~FAnimNode_MirrorCS()
|
||||||
|
{
|
||||||
|
TwinPairs.Empty();
|
||||||
|
NonTwinIDs.Empty();
|
||||||
|
NonTwinFlipAxis.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
FAnimNode_MirrorCS::FAnimNode_MirrorCS() :
|
||||||
|
CompletlySymmetrical(false)
|
||||||
|
{
|
||||||
|
TwinPairs.Empty();
|
||||||
|
NonTwinIDs.Empty();
|
||||||
|
NonTwinFlipAxis.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FAnimNode_MirrorCS::GatherDebugData(FNodeDebugData& DebugData)
|
||||||
|
{
|
||||||
|
Super::GatherDebugData(DebugData);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FAnimNode_MirrorCS::EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray<FBoneTransform>& OutBoneTransforms)
|
||||||
|
{
|
||||||
|
check(OutBoneTransforms.Num() == 0);
|
||||||
|
|
||||||
|
if(NonTwinIDs.Num())
|
||||||
|
{
|
||||||
|
const bool DeltaStep = !CompletlySymmetrical;
|
||||||
|
|
||||||
|
TArray <FTransform> NewCSTMs; NewCSTMs.SetNum(Output.Pose.GetPose().GetNumBones());
|
||||||
|
|
||||||
|
const USkeletalMeshComponent* SkelComp = Output.AnimInstanceProxy->GetSkelMeshComponent();
|
||||||
|
const auto& RefSkeleton = SkelComp->SkeletalMesh->RefSkeleton;
|
||||||
|
|
||||||
|
|
||||||
|
FVector TwinMirrorScale = FVector(1.f);
|
||||||
|
FVector TargetAxis = FVector::ZeroVector;
|
||||||
|
if (MirrorAxis != EAxis::None)
|
||||||
|
{
|
||||||
|
TwinMirrorScale[MirrorAxis - 1] = -1.f;
|
||||||
|
TargetAxis[MirrorAxis - 1] = 1.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
FTransform TwinMirrorModTM(FQuat::Identity, FVector::ZeroVector, TwinMirrorScale);
|
||||||
|
|
||||||
|
for (int32 i = 0; i < NonTwinIDs.Num(); i++)
|
||||||
|
{
|
||||||
|
FCompactPoseBoneIndex CmptBoneIndex(NonTwinIDs[i]);
|
||||||
|
|
||||||
|
FTransform TM = Output.Pose.GetComponentSpaceTransform(CmptBoneIndex);
|
||||||
|
TM.Mirror(MirrorAxis, NonTwinFlipAxis[i]);
|
||||||
|
|
||||||
|
OutBoneTransforms.Add(FBoneTransform(CmptBoneIndex, TM));
|
||||||
|
NewCSTMs[NonTwinIDs[i]] = TM;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32 i = 0; i < TwinPairs.Num(); i++)
|
||||||
|
{
|
||||||
|
int32 BoneIndex = TwinPairs[i].X;
|
||||||
|
FCompactPoseBoneIndex CmptBoneIndex(BoneIndex);
|
||||||
|
|
||||||
|
const int32 TwinBoneIndex = TwinPairs[i].Y;
|
||||||
|
|
||||||
|
FCompactPoseBoneIndex TwinCmptBoneIndex(TwinBoneIndex);
|
||||||
|
|
||||||
|
const FTransform RefTM = FAnimationRuntime::GetComponentSpaceTransformRefPose(RefSkeleton, BoneIndex);
|
||||||
|
const FTransform TwinRefTM = FAnimationRuntime::GetComponentSpaceTransformRefPose(RefSkeleton, TwinBoneIndex);
|
||||||
|
|
||||||
|
const FTransform TM = Output.Pose.GetComponentSpaceTransform(CmptBoneIndex);
|
||||||
|
const FTransform TwinTM = Output.Pose.GetComponentSpaceTransform(TwinCmptBoneIndex);
|
||||||
|
|
||||||
|
const int32 ParentIndex = RefSkeleton.GetParentIndex(BoneIndex);
|
||||||
|
const int32 TwinParentIndex = RefSkeleton.GetParentIndex(TwinBoneIndex);
|
||||||
|
|
||||||
|
const bool SameParent = ParentIndex == TwinParentIndex;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// twin 1º
|
||||||
|
{
|
||||||
|
const FTransform MirrRef = RefTM * TwinMirrorModTM;
|
||||||
|
const FTransform Delta = TwinRefTM.GetRelativeTransform(MirrRef);
|
||||||
|
const FQuat DeltaQuat = Delta.GetRotation();
|
||||||
|
|
||||||
|
FTransform MirrTM = TM * TwinMirrorModTM;
|
||||||
|
|
||||||
|
MirrTM.SetRotation(MirrTM.GetRotation() * DeltaQuat);
|
||||||
|
MirrTM.SetScale3D(TwinTM.GetScale3D());
|
||||||
|
|
||||||
|
if(DeltaStep)
|
||||||
|
{
|
||||||
|
if (SameParent)
|
||||||
|
{
|
||||||
|
FTransform RefBS = RefTM;
|
||||||
|
RefBS = RefBS * TwinMirrorModTM;
|
||||||
|
const FVector PosDelta = MirrTM.GetLocation() - RefBS.GetLocation();
|
||||||
|
MirrTM.SetLocation(TwinRefTM.GetLocation() + PosDelta);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const FTransform& ParentTwinTM = NewCSTMs[RefSkeleton.GetParentIndex(TwinBoneIndex)];
|
||||||
|
const FTransform& IParentTM = Output.Pose.GetComponentSpaceTransform(FCompactPoseBoneIndex(ParentIndex));
|
||||||
|
FTransform RefBS = RefSkeleton.GetRefBonePose()[BoneIndex] * IParentTM;
|
||||||
|
RefBS = RefBS * TwinMirrorModTM;
|
||||||
|
RefBS.SetRotation(RefBS.GetRotation() * DeltaQuat);
|
||||||
|
RefBS.SetScale3D(TwinTM.GetScale3D());
|
||||||
|
|
||||||
|
MirrTM = (MirrTM.GetRelativeTransform(RefBS) * RefSkeleton.GetRefBonePose()[TwinBoneIndex]) * ParentTwinTM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
OutBoneTransforms.Add(FBoneTransform(TwinCmptBoneIndex, MirrTM));
|
||||||
|
NewCSTMs[TwinBoneIndex] = MirrTM;
|
||||||
|
}
|
||||||
|
|
||||||
|
// twin 2º
|
||||||
|
{
|
||||||
|
FTransform TwinMirrRef = TwinRefTM * TwinMirrorModTM;
|
||||||
|
const FQuat TwinDeltaQuat = TwinMirrRef.GetRotation().Inverse() * RefTM.GetRotation();
|
||||||
|
|
||||||
|
FTransform TwinMirrTM = TwinTM * TwinMirrorModTM;
|
||||||
|
|
||||||
|
TwinMirrTM.SetRotation(TwinMirrTM.GetRotation() * TwinDeltaQuat);
|
||||||
|
TwinMirrTM.SetScale3D(TM.GetScale3D());
|
||||||
|
|
||||||
|
if (DeltaStep)
|
||||||
|
{
|
||||||
|
if (SameParent)
|
||||||
|
{
|
||||||
|
FTransform TwinRefBS = TwinRefTM;
|
||||||
|
TwinRefBS = TwinRefBS * TwinMirrorModTM;
|
||||||
|
const FVector PosDelta = TwinMirrTM.GetLocation() - TwinRefBS.GetLocation();
|
||||||
|
TwinMirrTM.SetLocation(RefTM.GetLocation() + PosDelta);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const FTransform& ParentTM = NewCSTMs[RefSkeleton.GetParentIndex(BoneIndex)];
|
||||||
|
const FTransform& IParentTwinTM = Output.Pose.GetComponentSpaceTransform(FCompactPoseBoneIndex(TwinParentIndex));
|
||||||
|
FTransform TwinRefBS = RefSkeleton.GetRefBonePose()[TwinBoneIndex] * IParentTwinTM;
|
||||||
|
TwinRefBS = TwinRefBS * TwinMirrorModTM;
|
||||||
|
TwinRefBS.SetRotation(TwinRefBS.GetRotation() * TwinDeltaQuat);
|
||||||
|
TwinRefBS.SetScale3D(TM.GetScale3D());
|
||||||
|
|
||||||
|
TwinMirrTM = (TwinMirrTM.GetRelativeTransform(TwinRefBS) * RefSkeleton.GetRefBonePose()[BoneIndex]) * ParentTM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OutBoneTransforms.Add(FBoneTransform(CmptBoneIndex, TwinMirrTM));
|
||||||
|
NewCSTMs[BoneIndex] = TwinMirrTM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OutBoneTransforms.Num())
|
||||||
|
{
|
||||||
|
OutBoneTransforms.Sort(FCompareBoneTransformIndex());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FAnimNode_MirrorCS::IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FAnimNode_MirrorCS::InitializeBoneReferences(const FBoneContainer& RequiredBones)
|
||||||
|
{
|
||||||
|
Super::InitializeBoneReferences(RequiredBones);
|
||||||
|
|
||||||
|
SetMirrorDataIfDirty(RequiredBones.GetReferenceSkeleton());
|
||||||
|
}
|
||||||
|
|
||||||
|
void FAnimNode_MirrorCS::Initialize_AnyThread(const FAnimationInitializeContext& Context)
|
||||||
|
{
|
||||||
|
Super::Initialize_AnyThread(Context);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void FAnimNode_MirrorCS::SetMirrorDataIfDirty(const FReferenceSkeleton& RefSkeleton)
|
||||||
|
{
|
||||||
|
if (RefSkeleton.GetNum() == LastBoneNum) return;
|
||||||
|
|
||||||
|
const int32 NumBones = RefSkeleton.GetNum();
|
||||||
|
LastBoneNum = NumBones;
|
||||||
|
TArray <bool> Already; Already.SetNumZeroed(NumBones);
|
||||||
|
|
||||||
|
FVector TargetAxis = FVector::ZeroVector;
|
||||||
|
if (MirrorAxis != EAxis::None)
|
||||||
|
{
|
||||||
|
TargetAxis[MirrorAxis - 1] = 1.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32 BoneIndex = 0; BoneIndex < NumBones; BoneIndex++)
|
||||||
|
{
|
||||||
|
if (Already[BoneIndex] == true) continue;
|
||||||
|
|
||||||
|
FName BoneName = RefSkeleton.GetBoneName(BoneIndex);
|
||||||
|
FName TwinBoneName;
|
||||||
|
if (FMASUtils::TwinSubstring(BoneName, Substring_A, Substring_B, TwinBoneName))
|
||||||
|
{
|
||||||
|
const int32 TwinBoneIndex = RefSkeleton.FindBoneIndex(TwinBoneName);
|
||||||
|
if (TwinBoneIndex != INDEX_NONE)
|
||||||
|
{
|
||||||
|
Already[BoneIndex] = true;
|
||||||
|
Already[TwinBoneIndex] = true;
|
||||||
|
TwinPairs.Add(FIntPoint(BoneIndex, TwinBoneIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const FTransform RefTM = FAnimationRuntime::GetComponentSpaceTransformRefPose(RefSkeleton, BoneIndex);
|
||||||
|
|
||||||
|
EAxis::Type WinnerAxis = EAxis::None;
|
||||||
|
float Max = -FLT_MAX;
|
||||||
|
|
||||||
|
for (int32 Axis = 0; Axis < 3; Axis++)
|
||||||
|
{
|
||||||
|
if (Axis == 0)
|
||||||
|
{
|
||||||
|
const float Val = FMath::Abs(RefTM.GetRotation().GetAxisX() | TargetAxis);
|
||||||
|
if (Val > Max)
|
||||||
|
{
|
||||||
|
Max = Val;
|
||||||
|
WinnerAxis = EAxis::X;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Axis == 1)
|
||||||
|
{
|
||||||
|
const float Val = FMath::Abs(RefTM.GetRotation().GetAxisY() | TargetAxis);
|
||||||
|
if (Val > Max)
|
||||||
|
{
|
||||||
|
Max = Val;
|
||||||
|
WinnerAxis = EAxis::Y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Axis == 2)
|
||||||
|
{
|
||||||
|
const float Val = FMath::Abs(RefTM.GetRotation().GetAxisZ() | TargetAxis);
|
||||||
|
if (Val > Max)
|
||||||
|
{
|
||||||
|
Max = Val;
|
||||||
|
WinnerAxis = EAxis::Z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NonTwinIDs.Add(BoneIndex);
|
||||||
|
NonTwinFlipAxis.Add(WinnerAxis);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
#include "ExtCharacter.h"
|
||||||
|
#include "MirrorAnimationSystem.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Sets default values
|
||||||
|
AExtCharacter::AExtCharacter(const FObjectInitializer& ObjectInitializer/* = FObjectInitializer::Get()*/)
|
||||||
|
:
|
||||||
|
Super(ObjectInitializer.SetDefaultSubobjectClass<UExtCharacterMovementComponent>(ACharacter::CharacterMovementComponentName))
|
||||||
|
{
|
||||||
|
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
|
||||||
|
PrimaryActorTick.bCanEverTick = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when the game starts or when spawned
|
||||||
|
void AExtCharacter::BeginPlay()
|
||||||
|
{
|
||||||
|
Super::BeginPlay();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called every frame
|
||||||
|
void AExtCharacter::Tick(float DeltaTime)
|
||||||
|
{
|
||||||
|
Super::Tick(DeltaTime);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called to bind functionality to input
|
||||||
|
void AExtCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
|
||||||
|
{
|
||||||
|
Super::SetupPlayerInputComponent(PlayerInputComponent);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AExtCharacter::SetMirrorRootMotion(bool Mirror)
|
||||||
|
{
|
||||||
|
UExtCharacterMovementComponent * ChMovComp = Cast<UExtCharacterMovementComponent>(AExtCharacter::GetCharacterMovement());
|
||||||
|
if (ChMovComp != NULL)
|
||||||
|
{
|
||||||
|
ChMovComp->MirrorRootMotion = Mirror;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AExtCharacter::GetMirrorRootMotion()
|
||||||
|
{
|
||||||
|
UExtCharacterMovementComponent * ChMovComp = Cast<UExtCharacterMovementComponent>(AExtCharacter::GetCharacterMovement());
|
||||||
|
if (ChMovComp != NULL)
|
||||||
|
{
|
||||||
|
return ChMovComp->MirrorRootMotion;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AExtCharacter::SetRootMotionMirrorAndFlipAxis(TEnumAsByte<EAxis::Type> MirrorAxis, TEnumAsByte<EAxis::Type> FlipAxis)
|
||||||
|
{
|
||||||
|
UExtCharacterMovementComponent * ChMovComp = Cast<UExtCharacterMovementComponent>(AExtCharacter::GetCharacterMovement());
|
||||||
|
if (ChMovComp != NULL)
|
||||||
|
{
|
||||||
|
ChMovComp->MirrorAxis = MirrorAxis;
|
||||||
|
ChMovComp->FlipAxis = FlipAxis;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AExtCharacter::GetRootMotionMirrorAndFlipAxis(TEnumAsByte<EAxis::Type>& MirrorAxis, TEnumAsByte<EAxis::Type>& FlipAxis)
|
||||||
|
{
|
||||||
|
UExtCharacterMovementComponent * ChMovComp = Cast<UExtCharacterMovementComponent>(AExtCharacter::GetCharacterMovement());
|
||||||
|
if (ChMovComp != NULL)
|
||||||
|
{
|
||||||
|
MirrorAxis = ChMovComp->MirrorAxis;
|
||||||
|
FlipAxis = ChMovComp->FlipAxis;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,427 @@
|
||||||
|
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
/*=============================================================================
|
||||||
|
Movement.cpp: Character movement implementation
|
||||||
|
|
||||||
|
=============================================================================*/
|
||||||
|
#include "ExtCharacterMovementComponent.h"
|
||||||
|
|
||||||
|
#include "GameFramework/CharacterMovementComponent.h"
|
||||||
|
#include "EngineStats.h"
|
||||||
|
#include "Components/PrimitiveComponent.h"
|
||||||
|
#include "AI/NavigationSystemBase.h"
|
||||||
|
#include "AI/Navigation/NavigationDataInterface.h"
|
||||||
|
#include "UObject/Package.h"
|
||||||
|
#include "GameFramework/PlayerController.h"
|
||||||
|
#include "GameFramework/PhysicsVolume.h"
|
||||||
|
#include "Components/SkeletalMeshComponent.h"
|
||||||
|
#include "Engine/NetDriver.h"
|
||||||
|
#include "DrawDebugHelpers.h"
|
||||||
|
#include "GameFramework/GameNetworkManager.h"
|
||||||
|
#include "GameFramework/Character.h"
|
||||||
|
#include "Components/CapsuleComponent.h"
|
||||||
|
#include "GameFramework/GameStateBase.h"
|
||||||
|
#include "Engine/Canvas.h"
|
||||||
|
#include "AI/Navigation/PathFollowingAgentInterface.h"
|
||||||
|
#include "AI/Navigation/AvoidanceManager.h"
|
||||||
|
#include "Components/BrushComponent.h"
|
||||||
|
|
||||||
|
#include "Engine/DemoNetDriver.h"
|
||||||
|
#include "Engine/NetworkObjectList.h"
|
||||||
|
|
||||||
|
//#include "Net/PerfCountersHelpers.h"
|
||||||
|
#include "ProfilingDebugging/CsvProfiler.h"
|
||||||
|
|
||||||
|
DECLARE_CYCLE_STAT(TEXT("Char PerformMovement"), STAT_CharacterMovementPerformMovement, STATGROUP_Character);
|
||||||
|
DECLARE_CYCLE_STAT(TEXT("Char RootMotionSource Calculate"), STAT_CharacterMovementRootMotionSourceCalculate, STATGROUP_Character);
|
||||||
|
DECLARE_CYCLE_STAT(TEXT("Char RootMotionSource Apply"), STAT_CharacterMovementRootMotionSourceApply, STATGROUP_Character);
|
||||||
|
|
||||||
|
|
||||||
|
// Defines for build configs
|
||||||
|
#if DO_CHECK && !UE_BUILD_SHIPPING // Disable even if checks in shipping are enabled.
|
||||||
|
#define devCode( Code ) checkCode( Code )
|
||||||
|
#else
|
||||||
|
#define devCode(...)
|
||||||
|
#endif
|
||||||
|
// CVars
|
||||||
|
namespace ExtCharacterMovementCVars
|
||||||
|
{
|
||||||
|
static int32 ExtNetUseClientTimestampForReplicatedTransform = 1;
|
||||||
|
FAutoConsoleVariableRef ExtCVarNetUseClientTimestampForReplicatedTransform(
|
||||||
|
TEXT("p.ExtNetUseClientTimestampForReplicatedTransform"),
|
||||||
|
ExtNetUseClientTimestampForReplicatedTransform,
|
||||||
|
TEXT("If enabled, use client timestamp changes to track the replicated transform timestamp, otherwise uses server tick time as the timestamp.\n")
|
||||||
|
TEXT("Game session usually needs to be restarted if this is changed at runtime.\n")
|
||||||
|
TEXT("0: Disable, 1: Enable"),
|
||||||
|
ECVF_Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UExtCharacterMovementComponent::PerformMovement(float DeltaSeconds)
|
||||||
|
{
|
||||||
|
SCOPE_CYCLE_COUNTER(STAT_CharacterMovementPerformMovement);
|
||||||
|
|
||||||
|
const UWorld* MyWorld = GetWorld();
|
||||||
|
if (!HasValidData() || MyWorld == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no movement if we can't move, or if currently doing physical simulation on UpdatedComponent
|
||||||
|
if (MovementMode == MOVE_None || UpdatedComponent->Mobility != EComponentMobility::Movable || UpdatedComponent->IsSimulatingPhysics())
|
||||||
|
{
|
||||||
|
if (!CharacterOwner->bClientUpdating && !CharacterOwner->bServerMoveIgnoreRootMotion)
|
||||||
|
{
|
||||||
|
// Consume root motion
|
||||||
|
if (CharacterOwner->IsPlayingRootMotion() && CharacterOwner->GetMesh())
|
||||||
|
{
|
||||||
|
TickCharacterPose(DeltaSeconds);
|
||||||
|
RootMotionParams.Clear();
|
||||||
|
}
|
||||||
|
if (CurrentRootMotion.HasActiveRootMotionSources())
|
||||||
|
{
|
||||||
|
CurrentRootMotion.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Clear pending physics forces
|
||||||
|
ClearAccumulatedForces();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force floor update if we've moved outside of CharacterMovement since last update.
|
||||||
|
bForceNextFloorCheck |= (IsMovingOnGround() && UpdatedComponent->GetComponentLocation() != LastUpdateLocation);
|
||||||
|
|
||||||
|
// Update saved LastPreAdditiveVelocity with any external changes to character Velocity that happened since last update.
|
||||||
|
if (CurrentRootMotion.HasAdditiveVelocity())
|
||||||
|
{
|
||||||
|
const FVector Adjustment = (Velocity - LastUpdateVelocity);
|
||||||
|
CurrentRootMotion.LastPreAdditiveVelocity += Adjustment;
|
||||||
|
|
||||||
|
#if ROOT_MOTION_DEBUG
|
||||||
|
if (RootMotionSourceDebug::CVarDebugRootMotionSources.GetValueOnGameThread() == 1)
|
||||||
|
{
|
||||||
|
if (!Adjustment.IsNearlyZero())
|
||||||
|
{
|
||||||
|
FString AdjustedDebugString = FString::Printf(TEXT("PerformMovement HasAdditiveVelocity LastUpdateVelocityAdjustment LastPreAdditiveVelocity(%s) Adjustment(%s)"),
|
||||||
|
*CurrentRootMotion.LastPreAdditiveVelocity.ToCompactString(), *Adjustment.ToCompactString());
|
||||||
|
RootMotionSourceDebug::PrintOnScreen(*CharacterOwner, AdjustedDebugString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector OldVelocity;
|
||||||
|
FVector OldLocation;
|
||||||
|
|
||||||
|
// Scoped updates can improve performance of multiple MoveComponent calls.
|
||||||
|
{
|
||||||
|
FScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates ? EScopedUpdate::DeferredUpdates : EScopedUpdate::ImmediateUpdates);
|
||||||
|
|
||||||
|
MaybeUpdateBasedMovement(DeltaSeconds);
|
||||||
|
|
||||||
|
// Clean up invalid RootMotion Sources.
|
||||||
|
// This includes RootMotion sources that ended naturally.
|
||||||
|
// They might want to perform a clamp on velocity or an override,
|
||||||
|
// so we want this to happen before ApplyAccumulatedForces and HandlePendingLaunch as to not clobber these.
|
||||||
|
const bool bHasRootMotionSources = HasRootMotionSources();
|
||||||
|
if (bHasRootMotionSources && !CharacterOwner->bClientUpdating && !CharacterOwner->bServerMoveIgnoreRootMotion)
|
||||||
|
{
|
||||||
|
SCOPE_CYCLE_COUNTER(STAT_CharacterMovementRootMotionSourceCalculate);
|
||||||
|
|
||||||
|
const FVector VelocityBeforeCleanup = Velocity;
|
||||||
|
CurrentRootMotion.CleanUpInvalidRootMotion(DeltaSeconds, *CharacterOwner, *this);
|
||||||
|
|
||||||
|
#if ROOT_MOTION_DEBUG
|
||||||
|
if (RootMotionSourceDebug::CVarDebugRootMotionSources.GetValueOnGameThread() == 1)
|
||||||
|
{
|
||||||
|
if (Velocity != VelocityBeforeCleanup)
|
||||||
|
{
|
||||||
|
const FVector Adjustment = Velocity - VelocityBeforeCleanup;
|
||||||
|
FString AdjustedDebugString = FString::Printf(TEXT("PerformMovement CleanUpInvalidRootMotion Velocity(%s) VelocityBeforeCleanup(%s) Adjustment(%s)"),
|
||||||
|
*Velocity.ToCompactString(), *VelocityBeforeCleanup.ToCompactString(), *Adjustment.ToCompactString());
|
||||||
|
RootMotionSourceDebug::PrintOnScreen(*CharacterOwner, AdjustedDebugString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
OldVelocity = Velocity;
|
||||||
|
OldLocation = UpdatedComponent->GetComponentLocation();
|
||||||
|
|
||||||
|
ApplyAccumulatedForces(DeltaSeconds);
|
||||||
|
|
||||||
|
// Update the character state before we do our movement
|
||||||
|
UpdateCharacterStateBeforeMovement(DeltaSeconds);
|
||||||
|
|
||||||
|
if (MovementMode == MOVE_NavWalking && bWantsToLeaveNavWalking)
|
||||||
|
{
|
||||||
|
TryToLeaveNavWalking();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Character::LaunchCharacter() has been deferred until now.
|
||||||
|
HandlePendingLaunch();
|
||||||
|
ClearAccumulatedForces();
|
||||||
|
|
||||||
|
#if ROOT_MOTION_DEBUG
|
||||||
|
if (RootMotionSourceDebug::CVarDebugRootMotionSources.GetValueOnGameThread() == 1)
|
||||||
|
{
|
||||||
|
if (OldVelocity != Velocity)
|
||||||
|
{
|
||||||
|
const FVector Adjustment = Velocity - OldVelocity;
|
||||||
|
FString AdjustedDebugString = FString::Printf(TEXT("PerformMovement ApplyAccumulatedForces+HandlePendingLaunch Velocity(%s) OldVelocity(%s) Adjustment(%s)"),
|
||||||
|
*Velocity.ToCompactString(), *OldVelocity.ToCompactString(), *Adjustment.ToCompactString());
|
||||||
|
RootMotionSourceDebug::PrintOnScreen(*CharacterOwner, AdjustedDebugString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Update saved LastPreAdditiveVelocity with any external changes to character Velocity that happened due to ApplyAccumulatedForces/HandlePendingLaunch
|
||||||
|
if (CurrentRootMotion.HasAdditiveVelocity())
|
||||||
|
{
|
||||||
|
const FVector Adjustment = (Velocity - OldVelocity);
|
||||||
|
CurrentRootMotion.LastPreAdditiveVelocity += Adjustment;
|
||||||
|
|
||||||
|
#if ROOT_MOTION_DEBUG
|
||||||
|
if (RootMotionSourceDebug::CVarDebugRootMotionSources.GetValueOnGameThread() == 1)
|
||||||
|
{
|
||||||
|
if (!Adjustment.IsNearlyZero())
|
||||||
|
{
|
||||||
|
FString AdjustedDebugString = FString::Printf(TEXT("PerformMovement HasAdditiveVelocity AccumulatedForces LastPreAdditiveVelocity(%s) Adjustment(%s)"),
|
||||||
|
*CurrentRootMotion.LastPreAdditiveVelocity.ToCompactString(), *Adjustment.ToCompactString());
|
||||||
|
RootMotionSourceDebug::PrintOnScreen(*CharacterOwner, AdjustedDebugString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare Root Motion (generate/accumulate from root motion sources to be used later)
|
||||||
|
if (bHasRootMotionSources && !CharacterOwner->bClientUpdating && !CharacterOwner->bServerMoveIgnoreRootMotion)
|
||||||
|
{
|
||||||
|
// Animation root motion - If using animation RootMotion, tick animations before running physics.
|
||||||
|
if (CharacterOwner->IsPlayingRootMotion() && CharacterOwner->GetMesh())
|
||||||
|
{
|
||||||
|
TickCharacterPose(DeltaSeconds);
|
||||||
|
|
||||||
|
// Make sure animation didn't trigger an event that destroyed us
|
||||||
|
if (!HasValidData())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For local human clients, save off root motion data so it can be used by movement networking code.
|
||||||
|
if (CharacterOwner->IsLocallyControlled() && (CharacterOwner->GetLocalRole() == ROLE_AutonomousProxy) && CharacterOwner->IsPlayingNetworkedRootMotionMontage())
|
||||||
|
{
|
||||||
|
CharacterOwner->ClientRootMotionParams = RootMotionParams;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates root motion to be used this frame from sources other than animation
|
||||||
|
{
|
||||||
|
SCOPE_CYCLE_COUNTER(STAT_CharacterMovementRootMotionSourceCalculate);
|
||||||
|
CurrentRootMotion.PrepareRootMotion(DeltaSeconds, *CharacterOwner, *this, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For local human clients, save off root motion data so it can be used by movement networking code.
|
||||||
|
if (CharacterOwner->IsLocallyControlled() && (CharacterOwner->GetLocalRole() == ROLE_AutonomousProxy))
|
||||||
|
{
|
||||||
|
CharacterOwner->SavedRootMotion = CurrentRootMotion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply Root Motion to Velocity
|
||||||
|
if (CurrentRootMotion.HasOverrideVelocity() || HasAnimRootMotion())
|
||||||
|
{
|
||||||
|
// Animation root motion overrides Velocity and currently doesn't allow any other root motion sources
|
||||||
|
if (HasAnimRootMotion())
|
||||||
|
{
|
||||||
|
// Convert to world space (animation root motion is always local)
|
||||||
|
USkeletalMeshComponent* SkelMeshComp = CharacterOwner->GetMesh();
|
||||||
|
if (SkelMeshComp)
|
||||||
|
{
|
||||||
|
if (MirrorRootMotion)
|
||||||
|
{
|
||||||
|
FTransform TmpRootMootTr = RootMotionParams.GetRootMotionTransform();
|
||||||
|
TmpRootMootTr.Mirror(MirrorAxis, FlipAxis);
|
||||||
|
TmpRootMootTr.SetScale3D(TmpRootMootTr.GetScale3D().GetAbs());
|
||||||
|
RootMotionParams.Set(ConvertLocalRootMotionToWorld(TmpRootMootTr));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Convert Local Space Root Motion to world space. Do it right before used by physics to make sure we use up to date transforms, as translation is relative to rotation.
|
||||||
|
RootMotionParams.Set(ConvertLocalRootMotionToWorld(RootMotionParams.GetRootMotionTransform()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then turn root motion to velocity to be used by various physics modes.
|
||||||
|
if (DeltaSeconds > 0.f)
|
||||||
|
{
|
||||||
|
AnimRootMotionVelocity = CalcAnimRootMotionVelocity(RootMotionParams.GetRootMotionTransform().GetTranslation(), DeltaSeconds, Velocity);
|
||||||
|
Velocity = ConstrainAnimRootMotionVelocity(AnimRootMotionVelocity, Velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogRootMotion, Log, TEXT("PerformMovement WorldSpaceRootMotion Translation: %s, Rotation: %s, Actor Facing: %s, Velocity: %s")
|
||||||
|
, *RootMotionParams.GetRootMotionTransform().GetTranslation().ToCompactString()
|
||||||
|
, *RootMotionParams.GetRootMotionTransform().GetRotation().Rotator().ToCompactString()
|
||||||
|
, *CharacterOwner->GetActorForwardVector().ToCompactString()
|
||||||
|
, *Velocity.ToCompactString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We don't have animation root motion so we apply other sources
|
||||||
|
if (DeltaSeconds > 0.f)
|
||||||
|
{
|
||||||
|
SCOPE_CYCLE_COUNTER(STAT_CharacterMovementRootMotionSourceApply);
|
||||||
|
|
||||||
|
const FVector VelocityBeforeOverride = Velocity;
|
||||||
|
FVector NewVelocity = Velocity;
|
||||||
|
CurrentRootMotion.AccumulateOverrideRootMotionVelocity(DeltaSeconds, *CharacterOwner, *this, NewVelocity);
|
||||||
|
Velocity = NewVelocity;
|
||||||
|
|
||||||
|
#if ROOT_MOTION_DEBUG
|
||||||
|
if (RootMotionSourceDebug::CVarDebugRootMotionSources.GetValueOnGameThread() == 1)
|
||||||
|
{
|
||||||
|
if (VelocityBeforeOverride != Velocity)
|
||||||
|
{
|
||||||
|
FString AdjustedDebugString = FString::Printf(TEXT("PerformMovement AccumulateOverrideRootMotionVelocity Velocity(%s) VelocityBeforeOverride(%s)"),
|
||||||
|
*Velocity.ToCompactString(), *VelocityBeforeOverride.ToCompactString());
|
||||||
|
RootMotionSourceDebug::PrintOnScreen(*CharacterOwner, AdjustedDebugString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ROOT_MOTION_DEBUG
|
||||||
|
if (RootMotionSourceDebug::CVarDebugRootMotionSources.GetValueOnGameThread() == 1)
|
||||||
|
{
|
||||||
|
FString AdjustedDebugString = FString::Printf(TEXT("PerformMovement Velocity(%s) OldVelocity(%s)"),
|
||||||
|
*Velocity.ToCompactString(), *OldVelocity.ToCompactString());
|
||||||
|
RootMotionSourceDebug::PrintOnScreen(*CharacterOwner, AdjustedDebugString);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// NaN tracking
|
||||||
|
devCode(ensureMsgf(!Velocity.ContainsNaN(), TEXT("UCharacterMovementComponent::PerformMovement: Velocity contains NaN (%s)\n%s"), *GetPathNameSafe(this), *Velocity.ToString()));
|
||||||
|
|
||||||
|
// Clear jump input now, to allow movement events to trigger it for next update.
|
||||||
|
CharacterOwner->ClearJumpInput(DeltaSeconds);
|
||||||
|
NumJumpApexAttempts = 0;
|
||||||
|
|
||||||
|
// change position
|
||||||
|
StartNewPhysics(DeltaSeconds, 0);
|
||||||
|
|
||||||
|
if (!HasValidData())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update character state based on change from movement
|
||||||
|
UpdateCharacterStateAfterMovement(DeltaSeconds);
|
||||||
|
|
||||||
|
if ((bAllowPhysicsRotationDuringAnimRootMotion || !HasAnimRootMotion()) && !CharacterOwner->IsMatineeControlled())
|
||||||
|
{
|
||||||
|
PhysicsRotation(DeltaSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply Root Motion rotation after movement is complete.
|
||||||
|
if (HasAnimRootMotion())
|
||||||
|
{
|
||||||
|
const FQuat OldActorRotationQuat = UpdatedComponent->GetComponentQuat();
|
||||||
|
const FQuat RootMotionRotationQuat = RootMotionParams.GetRootMotionTransform().GetRotation();
|
||||||
|
if (!RootMotionRotationQuat.IsIdentity())
|
||||||
|
{
|
||||||
|
const FQuat NewActorRotationQuat = RootMotionRotationQuat * OldActorRotationQuat;
|
||||||
|
MoveUpdatedComponent(FVector::ZeroVector, NewActorRotationQuat, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !(UE_BUILD_SHIPPING)
|
||||||
|
// debug
|
||||||
|
if (false)
|
||||||
|
{
|
||||||
|
const FRotator OldActorRotation = OldActorRotationQuat.Rotator();
|
||||||
|
const FVector ResultingLocation = UpdatedComponent->GetComponentLocation();
|
||||||
|
const FRotator ResultingRotation = UpdatedComponent->GetComponentRotation();
|
||||||
|
|
||||||
|
// Show current position
|
||||||
|
DrawDebugCoordinateSystem(MyWorld, CharacterOwner->GetMesh()->GetComponentLocation() + FVector(0, 0, 1), ResultingRotation, 50.f, false);
|
||||||
|
|
||||||
|
// Show resulting delta move.
|
||||||
|
DrawDebugLine(MyWorld, OldLocation, ResultingLocation, FColor::Red, false, 10.f);
|
||||||
|
|
||||||
|
// Log details.
|
||||||
|
UE_LOG(LogRootMotion, Warning, TEXT("PerformMovement Resulting DeltaMove Translation: %s, Rotation: %s, MovementBase: %s"), //-V595
|
||||||
|
*(ResultingLocation - OldLocation).ToCompactString(), *(ResultingRotation - OldActorRotation).GetNormalized().ToCompactString(), *GetNameSafe(CharacterOwner->GetMovementBase()));
|
||||||
|
|
||||||
|
const FVector RMTranslation = RootMotionParams.GetRootMotionTransform().GetTranslation();
|
||||||
|
const FRotator RMRotation = RootMotionParams.GetRootMotionTransform().GetRotation().Rotator();
|
||||||
|
UE_LOG(LogRootMotion, Warning, TEXT("PerformMovement Resulting DeltaError Translation: %s, Rotation: %s"),
|
||||||
|
*(ResultingLocation - OldLocation - RMTranslation).ToCompactString(), *(ResultingRotation - OldActorRotation - RMRotation).GetNormalized().ToCompactString());
|
||||||
|
}
|
||||||
|
#endif // !(UE_BUILD_SHIPPING)
|
||||||
|
|
||||||
|
// Root Motion has been used, clear
|
||||||
|
RootMotionParams.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// consume path following requested velocity
|
||||||
|
bHasRequestedVelocity = false;
|
||||||
|
|
||||||
|
OnMovementUpdated(DeltaSeconds, OldLocation, OldVelocity);
|
||||||
|
} // End scoped movement update
|
||||||
|
|
||||||
|
// Call external post-movement events. These happen after the scoped movement completes in case the events want to use the current state of overlaps etc.
|
||||||
|
CallMovementUpdateDelegate(DeltaSeconds, OldLocation, OldVelocity);
|
||||||
|
|
||||||
|
SaveBaseLocation();
|
||||||
|
UpdateComponentVelocity();
|
||||||
|
|
||||||
|
const bool bHasAuthority = CharacterOwner && CharacterOwner->HasAuthority();
|
||||||
|
|
||||||
|
// If we move we want to avoid a long delay before replication catches up to notice this change, especially if it's throttling our rate.
|
||||||
|
if (bHasAuthority && UNetDriver::IsAdaptiveNetUpdateFrequencyEnabled() && UpdatedComponent)
|
||||||
|
{
|
||||||
|
UNetDriver* NetDriver = MyWorld->GetNetDriver();
|
||||||
|
if (NetDriver && NetDriver->IsServer())
|
||||||
|
{
|
||||||
|
FNetworkObjectInfo* NetActor = NetDriver->FindOrAddNetworkObjectInfo(CharacterOwner);
|
||||||
|
|
||||||
|
if (NetActor && MyWorld->GetTimeSeconds() <= NetActor->NextUpdateTime && NetDriver->IsNetworkActorUpdateFrequencyThrottled(*NetActor))
|
||||||
|
{
|
||||||
|
if (ShouldCancelAdaptiveReplication())
|
||||||
|
{
|
||||||
|
NetDriver->CancelAdaptiveReplication(*NetActor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const FVector NewLocation = UpdatedComponent ? UpdatedComponent->GetComponentLocation() : FVector::ZeroVector;
|
||||||
|
const FQuat NewRotation = UpdatedComponent ? UpdatedComponent->GetComponentQuat() : FQuat::Identity;
|
||||||
|
|
||||||
|
if (bHasAuthority && UpdatedComponent && !IsNetMode(NM_Client))
|
||||||
|
{
|
||||||
|
const bool bLocationChanged = (NewLocation != LastUpdateLocation);
|
||||||
|
const bool bRotationChanged = (NewRotation != LastUpdateRotation);
|
||||||
|
if (bLocationChanged || bRotationChanged)
|
||||||
|
{
|
||||||
|
// Update ServerLastTransformUpdateTimeStamp. This is used by Linear smoothing on clients to interpolate positions with the correct delta time,
|
||||||
|
// so the timestamp should be based on the client's move delta (ServerAccumulatedClientTimeStamp), not the server time when receiving the RPC.
|
||||||
|
const bool bIsRemotePlayer = (CharacterOwner->GetRemoteRole() == ROLE_AutonomousProxy);
|
||||||
|
const FNetworkPredictionData_Server_Character* ServerData = bIsRemotePlayer ? GetPredictionData_Server_Character() : nullptr;
|
||||||
|
if (bIsRemotePlayer && ServerData && ExtCharacterMovementCVars::ExtNetUseClientTimestampForReplicatedTransform)
|
||||||
|
{
|
||||||
|
ServerLastTransformUpdateTimeStamp = float(ServerData->ServerAccumulatedClientTimeStamp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ServerLastTransformUpdateTimeStamp = MyWorld->GetTimeSeconds();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LastUpdateLocation = NewLocation;
|
||||||
|
LastUpdateRotation = NewRotation;
|
||||||
|
LastUpdateVelocity = Velocity;
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "MASUtils.h"
|
||||||
|
|
||||||
|
bool FMASUtils::TwinSubstring(const FName BoneName, const FString Substring_A, const FString Substring_B, FName& OutTwinBoneName)
|
||||||
|
{
|
||||||
|
FString CurrentBoneString = BoneName.ToString();
|
||||||
|
|
||||||
|
int ResultA = CurrentBoneString.Find(Substring_A, ESearchCase::IgnoreCase, ESearchDir::FromEnd, -1);
|
||||||
|
int ResultB = CurrentBoneString.Find(Substring_B, ESearchCase::IgnoreCase, ESearchDir::FromEnd, -1);
|
||||||
|
|
||||||
|
if ((ResultA != -1) || (ResultB != -1))
|
||||||
|
{
|
||||||
|
FString TwinBoneString;
|
||||||
|
bool ValidSymmetry = true;
|
||||||
|
if (ResultB != -1)
|
||||||
|
{
|
||||||
|
|
||||||
|
TwinBoneString = CurrentBoneString.Mid(0, ResultB);
|
||||||
|
TwinBoneString += Substring_A;
|
||||||
|
TwinBoneString += CurrentBoneString.Mid(ResultB + Substring_B.Len());
|
||||||
|
|
||||||
|
if ((!(abs(TwinBoneString.Len() - Substring_A.Len()) == abs(CurrentBoneString.Len() - Substring_B.Len()))) && ValidSymmetry)
|
||||||
|
{
|
||||||
|
ValidSymmetry = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
TwinBoneString = CurrentBoneString.Mid(0, ResultA);
|
||||||
|
TwinBoneString += Substring_B;
|
||||||
|
TwinBoneString += CurrentBoneString.Mid(ResultA + Substring_A.Len());
|
||||||
|
|
||||||
|
if ((!(abs(TwinBoneString.Len() - Substring_B.Len()) == abs(CurrentBoneString.Len() - Substring_A.Len()))) && ValidSymmetry)
|
||||||
|
{
|
||||||
|
ValidSymmetry = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ValidSymmetry)
|
||||||
|
{
|
||||||
|
OutTwinBoneName = FName(*TwinBoneString);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FMASUtils::CSMirrorSettings(
|
||||||
|
const FReferenceSkeleton& RefSkeleton,
|
||||||
|
const EAxis::Type MirrorAxis, const FString Substring_A, const FString Substring_B,
|
||||||
|
TArray<FIntPoint>& TwinPairs, TArray<int32>& NonTwinIDs, TArray<EAxis::Type>& NonTwinFlipAxis)
|
||||||
|
{
|
||||||
|
TwinPairs.Empty();
|
||||||
|
NonTwinIDs.Empty();
|
||||||
|
NonTwinFlipAxis.Empty();
|
||||||
|
|
||||||
|
const int32 NumBones = RefSkeleton.GetNum();
|
||||||
|
TArray <bool> Already; Already.SetNumZeroed(NumBones);
|
||||||
|
|
||||||
|
FVector TargetAxis = FVector::ZeroVector;
|
||||||
|
if (MirrorAxis != EAxis::None)
|
||||||
|
{
|
||||||
|
TargetAxis[MirrorAxis - 1] = 1.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32 BoneIndex = 0; BoneIndex < NumBones; BoneIndex++)
|
||||||
|
{
|
||||||
|
if (Already[BoneIndex] == true) continue;
|
||||||
|
|
||||||
|
FName BoneName = RefSkeleton.GetBoneName(BoneIndex);
|
||||||
|
FName TwinBoneName;
|
||||||
|
if (FMASUtils::TwinSubstring(BoneName, Substring_A, Substring_B, TwinBoneName))
|
||||||
|
{
|
||||||
|
const int32 TwinBoneIndex = RefSkeleton.FindBoneIndex(TwinBoneName);
|
||||||
|
if (TwinBoneIndex != INDEX_NONE)
|
||||||
|
{
|
||||||
|
Already[BoneIndex] = true;
|
||||||
|
Already[TwinBoneIndex] = true;
|
||||||
|
TwinPairs.Add(FIntPoint(BoneIndex, TwinBoneIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const FTransform RefTM = FAnimationRuntime::GetComponentSpaceTransformRefPose(RefSkeleton, BoneIndex);
|
||||||
|
|
||||||
|
EAxis::Type WinnerAxis = EAxis::None;
|
||||||
|
float Max = -FLT_MAX;
|
||||||
|
|
||||||
|
for (int32 Axis = 0; Axis < 3; Axis++)
|
||||||
|
{
|
||||||
|
if (Axis == 0)
|
||||||
|
{
|
||||||
|
const float Val = FMath::Abs(RefTM.GetRotation().GetAxisX() | TargetAxis);
|
||||||
|
if (Val > Max)
|
||||||
|
{
|
||||||
|
Max = Val;
|
||||||
|
WinnerAxis = EAxis::X;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Axis == 1)
|
||||||
|
{
|
||||||
|
const float Val = FMath::Abs(RefTM.GetRotation().GetAxisY() | TargetAxis);
|
||||||
|
if (Val > Max)
|
||||||
|
{
|
||||||
|
Max = Val;
|
||||||
|
WinnerAxis = EAxis::Y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Axis == 2)
|
||||||
|
{
|
||||||
|
const float Val = FMath::Abs(RefTM.GetRotation().GetAxisZ() | TargetAxis);
|
||||||
|
if (Val > Max)
|
||||||
|
{
|
||||||
|
Max = Val;
|
||||||
|
WinnerAxis = EAxis::Z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NonTwinIDs.Add(BoneIndex);
|
||||||
|
NonTwinFlipAxis.Add(WinnerAxis);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "MirrorAnimationSystem.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "FMirrorAnimationSystemModule"
|
||||||
|
|
||||||
|
void FMirrorAnimationSystemModule::StartupModule()
|
||||||
|
{
|
||||||
|
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
|
||||||
|
#if WITH_EDITOR
|
||||||
|
FModuleManager::Get().LoadModule(TEXT("BlueprintGraph"));
|
||||||
|
//FModuleManager::Get().LoadModule(TEXT("AnimGraph"));
|
||||||
|
FModuleManager::Get().LoadModule(TEXT("MirrorAnimationSystemEditor"));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void FMirrorAnimationSystemModule::ShutdownModule()
|
||||||
|
{
|
||||||
|
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
|
||||||
|
// we call this function before unloading the module.
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
||||||
|
|
||||||
|
IMPLEMENT_MODULE(FMirrorAnimationSystemModule, MirrorAnimationSystem)
|
|
@ -0,0 +1,8 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
#include "MirrorTable.h"
|
||||||
|
#include "MirrorAnimationSystem.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "Animation/AnimNodeBase.h"
|
||||||
|
#include "Animation/AnimInstanceProxy.h"
|
||||||
|
#include "MirrorTable.h"
|
||||||
|
#include "AnimNode_Mirror.generated.h"
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/*Runtime code for the AnimGraph Node Mirror Pose,
|
||||||
|
takes in a pose in local space and Mirrors each bone according to a Mirror Table*/
|
||||||
|
USTRUCT(BlueprintInternalUseOnly)
|
||||||
|
struct MIRRORANIMATIONSYSTEM_API FAnimNode_Mirror : public FAnimNode_Base
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Links)
|
||||||
|
FPoseLink BasePose;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MirrorTable, meta = (PinShownByDefault))
|
||||||
|
UMirrorTable* MirrorTable;
|
||||||
|
public:
|
||||||
|
|
||||||
|
FAnimNode_Mirror();
|
||||||
|
|
||||||
|
// FAnimNode_Base interface
|
||||||
|
virtual void Initialize_AnyThread(const FAnimationInitializeContext& Context) override;
|
||||||
|
virtual void CacheBones_AnyThread(const FAnimationCacheBonesContext& Context) override;
|
||||||
|
virtual void Update_AnyThread(const FAnimationUpdateContext& Context) override;
|
||||||
|
virtual void Evaluate_AnyThread(FPoseContext & Output) override;
|
||||||
|
virtual void GatherDebugData(FNodeDebugData& DebugData) override;
|
||||||
|
// End of FAnimNode_Base interface
|
||||||
|
};
|
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "UObject/ObjectMacros.h"
|
||||||
|
#include "BoneContainer.h"
|
||||||
|
#include "BonePose.h"
|
||||||
|
#include "BoneControllers/AnimNode_SkeletalControlBase.h"
|
||||||
|
|
||||||
|
#include "MirrorTable.h"
|
||||||
|
|
||||||
|
#include "AnimNode_MirrorCS.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
// Component Space version of the Mirror Pose node.
|
||||||
|
USTRUCT(BlueprintInternalUseOnly)
|
||||||
|
struct MIRRORANIMATIONSYSTEM_API FAnimNode_MirrorCS : public FAnimNode_SkeletalControlBase
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MirrorSettings)
|
||||||
|
TEnumAsByte<EAxis::Type> MirrorAxis = EAxis::None;
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MirrorSettings)
|
||||||
|
bool CompletlySymmetrical = false;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MirrorBones)
|
||||||
|
FString Substring_A;
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MirrorBones)
|
||||||
|
FString Substring_B;
|
||||||
|
|
||||||
|
~FAnimNode_MirrorCS();
|
||||||
|
FAnimNode_MirrorCS();
|
||||||
|
|
||||||
|
// FAnimNode_Base interface
|
||||||
|
virtual void GatherDebugData(FNodeDebugData& DebugData) override;
|
||||||
|
// End of FAnimNode_Base interface
|
||||||
|
|
||||||
|
// FAnimNode_SkeletalControlBase interface
|
||||||
|
virtual void EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray<FBoneTransform>& OutBoneTransforms) override;
|
||||||
|
virtual bool IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones) override;
|
||||||
|
virtual void Initialize_AnyThread(const FAnimationInitializeContext& Context) override;
|
||||||
|
// End of FAnimNode_SkeletalControlBase interface
|
||||||
|
|
||||||
|
private:
|
||||||
|
// FAnimNode_SkeletalControlBase interface
|
||||||
|
virtual void InitializeBoneReferences(const FBoneContainer& RequiredBones) override;
|
||||||
|
// End of FAnimNode_SkeletalControlBase interface
|
||||||
|
|
||||||
|
|
||||||
|
void SetMirrorDataIfDirty(const FReferenceSkeleton& RefSkeleton);
|
||||||
|
TArray<FIntPoint> TwinPairs;
|
||||||
|
TArray<int32> NonTwinIDs;
|
||||||
|
TArray<EAxis::Type> NonTwinFlipAxis;
|
||||||
|
int32 LastBoneNum = 0;
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "GameFramework/Character.h"
|
||||||
|
#include "ExtCharacterMovementComponent.h"
|
||||||
|
#include "ExtCharacter.generated.h"
|
||||||
|
/*Character class that merely implements ExtCharacterMovementComponent and some blueprint functions to set the Mirror Root Motion Parameters*/
|
||||||
|
UCLASS()
|
||||||
|
class MIRRORANIMATIONSYSTEM_API AExtCharacter : public ACharacter
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Sets default values for this character's properties
|
||||||
|
AExtCharacter(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Called when the game starts or when spawned
|
||||||
|
virtual void BeginPlay() override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Called every frame
|
||||||
|
virtual void Tick(float DeltaTime) override;
|
||||||
|
|
||||||
|
// Called to bind functionality to input
|
||||||
|
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
|
||||||
|
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Mirror Animation")
|
||||||
|
virtual void SetMirrorRootMotion(bool Mirror);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Mirror Animation")
|
||||||
|
virtual bool GetMirrorRootMotion();
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Mirror Animation")
|
||||||
|
virtual void SetRootMotionMirrorAndFlipAxis(TEnumAsByte<EAxis::Type> MirrorAxis, TEnumAsByte<EAxis::Type> FlipAxis);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Mirror Animation")
|
||||||
|
virtual void GetRootMotionMirrorAndFlipAxis(TEnumAsByte<EAxis::Type> & MirrorAxis, TEnumAsByte<EAxis::Type> & FlipAxis);
|
||||||
|
};
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "GameFramework/CharacterMovementComponent.h"
|
||||||
|
#include "ExtCharacterMovementComponent.generated.h"
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct FRootMotionMovementParams;
|
||||||
|
/*CharacterMovementComponent Class just carries the Mirror Root Motion parameters and applies them when root motion is received from an animation*/
|
||||||
|
UCLASS()
|
||||||
|
class MIRRORANIMATIONSYSTEM_API UExtCharacterMovementComponent : public UCharacterMovementComponent
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
protected:
|
||||||
|
virtual void PerformMovement(float DeltaSeconds) override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(Category = "Character Movement: Root Motion", EditAnywhere, BlueprintReadWrite)
|
||||||
|
bool MirrorRootMotion = false;
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Character Movement: Root Motion", EditAnywhere, BlueprintReadWrite)
|
||||||
|
TEnumAsByte<EAxis::Type> MirrorAxis = EAxis::None;
|
||||||
|
|
||||||
|
UPROPERTY(Category = "Character Movement: Root Motion", EditAnywhere, BlueprintReadWrite)
|
||||||
|
TEnumAsByte<EAxis::Type> FlipAxis = EAxis::None;
|
||||||
|
};
|
|
@ -0,0 +1,17 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class MIRRORANIMATIONSYSTEM_API FMASUtils
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static bool TwinSubstring(const FName BoneName, const FString Substring_A, const FString Substring_B, FName& OutTwinBoneName);
|
||||||
|
static void CSMirrorSettings(const FReferenceSkeleton& RefSkeleton,
|
||||||
|
const EAxis::Type MirrorAxis, const FString Substring_A, const FString Substring_B,
|
||||||
|
TArray<FIntPoint>& TwinPairs, TArray<int32>& NonTwinIDs, TArray<EAxis::Type>& NonTwinFlipAxis);
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
|
||||||
|
class FMirrorAnimationSystemModule : public IModuleInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** IModuleInterface implementation */
|
||||||
|
virtual void StartupModule() override;
|
||||||
|
virtual void ShutdownModule() override;
|
||||||
|
};
|
|
@ -0,0 +1,49 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Engine/DataAsset.h"
|
||||||
|
#include "MirrorTable.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/*Struct that contains the setup data for Mirroring a single bone or a pair of bones*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct MIRRORANIMATIONSYSTEM_API FMirrorBone
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MirrorAnimation")
|
||||||
|
FName BoneName;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MirrorAnimation")
|
||||||
|
TEnumAsByte<EAxis::Type> MirrorAxis;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MirrorAnimation")
|
||||||
|
TEnumAsByte<EAxis::Type> FlipAxis;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MirrorAnimation")
|
||||||
|
FRotator RotationOffset;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MirrorAnimation")
|
||||||
|
bool IsTwinBone;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MirrorAnimation")
|
||||||
|
FName TwinBoneName;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MirrorAnimation")
|
||||||
|
bool MirrorTranslation;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*Data asset class that holds the MirrorBone's parameters for an entire skeleton,
|
||||||
|
this class is used for both the Mirror Pose Animgraph Node and when selecting a Mirror Table inside the Mirror AnimAsset dialog*/
|
||||||
|
UCLASS(BlueprintType)
|
||||||
|
class MIRRORANIMATIONSYSTEM_API UMirrorTable : public UDataAsset
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
public:
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MirrorBoneSettings")
|
||||||
|
TArray <FMirrorBone> MirrorBones;
|
||||||
|
|
||||||
|
};
|
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
using UnrealBuildTool;
|
||||||
|
|
||||||
|
public class MirrorAnimationSystemDev : ModuleRules
|
||||||
|
{
|
||||||
|
public MirrorAnimationSystemDev(ReadOnlyTargetRules Target) : base(Target)
|
||||||
|
{
|
||||||
|
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||||
|
|
||||||
|
PublicIncludePaths.AddRange(
|
||||||
|
new string[] {
|
||||||
|
// ... add public include paths required here ...
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
PrivateIncludePaths.AddRange(
|
||||||
|
new string[] {
|
||||||
|
// ... add other private include paths required here ...
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
PublicDependencyModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
"Core",
|
||||||
|
// ... add other public dependencies that you statically link with here ...
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
PrivateDependencyModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
"CoreUObject",
|
||||||
|
"Engine",
|
||||||
|
"Slate",
|
||||||
|
"SlateCore",
|
||||||
|
"MirrorAnimationSystem",
|
||||||
|
|
||||||
|
// ... add private dependencies that you statically link with here ...
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Target.bBuildEditor == true)
|
||||||
|
{
|
||||||
|
PrivateDependencyModuleNames.Add("UnrealEd");
|
||||||
|
}
|
||||||
|
|
||||||
|
DynamicallyLoadedModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
// ... add any modules that your module loads dynamically here ...
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,686 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
#include "MASFunctionLibrary.h"
|
||||||
|
|
||||||
|
#include "MirrorTable.h"
|
||||||
|
|
||||||
|
#include "Animation/AnimSequence.h"
|
||||||
|
|
||||||
|
#include "Misc/PackageName.h"
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
#include "AssetToolsModule.h"
|
||||||
|
#include "AssetRegistryModule.h"
|
||||||
|
#include "Toolkits/AssetEditorManager.h"
|
||||||
|
|
||||||
|
#include "Framework/Notifications/NotificationManager.h"
|
||||||
|
#include "Widgets/Notifications/SNotificationList.h"
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "MASUtils.h"
|
||||||
|
#include "AnimationRuntime.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "MASLibrary"
|
||||||
|
|
||||||
|
UMASFunctionLibrary::UMASFunctionLibrary(const FObjectInitializer& ObjectInitializer)
|
||||||
|
: Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void UMASFunctionLibrary::BulkMirrorEditorOnly(const TArray <UAnimSequence*> SourceAnims, const UMirrorTable* MirrorTable, TArray <UAnimSequence*>& OutNewAnims)
|
||||||
|
{
|
||||||
|
if (SourceAnims.Num() == 0) return;
|
||||||
|
if (MirrorTable == NULL) return;
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools");
|
||||||
|
|
||||||
|
for (int32 i = 0; i < SourceAnims.Num(); i++)
|
||||||
|
{
|
||||||
|
// Create the asset
|
||||||
|
FString Name;
|
||||||
|
FString PackageName;
|
||||||
|
|
||||||
|
|
||||||
|
FString Suffix = TEXT("_Mirrored");
|
||||||
|
|
||||||
|
AssetToolsModule.Get().CreateUniqueAssetName(SourceAnims[i]->GetOutermost()->GetName(), Suffix, /*out*/ PackageName, /*out*/ Name);
|
||||||
|
const FString PackagePath = FPackageName::GetLongPackagePath(PackageName);
|
||||||
|
|
||||||
|
|
||||||
|
UObject* NewAsset = AssetToolsModule.Get().DuplicateAsset(Name, PackagePath, SourceAnims[i]);
|
||||||
|
|
||||||
|
if (NewAsset != NULL)
|
||||||
|
{
|
||||||
|
UAnimSequence* MirrorAnimSequence = Cast<UAnimSequence>(NewAsset);
|
||||||
|
CreateMirrorSequenceFromAnimSequence(MirrorAnimSequence, MirrorTable);
|
||||||
|
|
||||||
|
OutNewAnims.Add(MirrorAnimSequence);
|
||||||
|
|
||||||
|
// Notify asset registry of new asset
|
||||||
|
FAssetRegistryModule::AssetCreated(MirrorAnimSequence);
|
||||||
|
|
||||||
|
// Display notification so users can quickly access
|
||||||
|
if (GIsEditor)
|
||||||
|
{
|
||||||
|
FNotificationInfo Info(FText::Format(LOCTEXT("AnimationMirrored", "Successfully Mirrored Animation"), FText::FromString(MirrorAnimSequence->GetName())));
|
||||||
|
Info.ExpireDuration = 8.0f;
|
||||||
|
Info.bUseLargeFont = false;
|
||||||
|
//Info.Hyperlink = FSimpleDelegate::CreateLambda([=]() { FAssetEditorManager::Get().OpenEditorForAssets(TArray<UObject*>({ MirrorAnimSequence })); });
|
||||||
|
Info.Hyperlink = FSimpleDelegate::CreateLambda([=]() { GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAssets(TArray<UObject*>({ MirrorAnimSequence })); });
|
||||||
|
|
||||||
|
Info.HyperlinkText = FText::Format(LOCTEXT("OpenNewAnimationHyperlink", "Open {0}"), FText::FromString(MirrorAnimSequence->GetName()));
|
||||||
|
TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification(Info);
|
||||||
|
if (Notification.IsValid())
|
||||||
|
{
|
||||||
|
Notification->SetCompletionState(SNotificationItem::CS_Success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
MIRRORANIMATIONSYSTEMDEV_API void UMASFunctionLibrary::BulkMirror_CS_EditorOnly(
|
||||||
|
const TArray<UAnimSequence*> SourceAnims,
|
||||||
|
const TEnumAsByte<EAxis::Type> MirrorAxis, const FString Substring_A, const FString Substring_B, const bool Symmetrical, TArray<UAnimSequence*>& OutNewAnims)
|
||||||
|
{
|
||||||
|
if (SourceAnims.Num() == 0) return;
|
||||||
|
if (MirrorAxis == EAxis::None) return;
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools");
|
||||||
|
|
||||||
|
for (int32 i = 0; i < SourceAnims.Num(); i++)
|
||||||
|
{
|
||||||
|
// Create the asset
|
||||||
|
FString Name;
|
||||||
|
FString PackageName;
|
||||||
|
|
||||||
|
|
||||||
|
FString Suffix = TEXT("_Mirrored");
|
||||||
|
|
||||||
|
AssetToolsModule.Get().CreateUniqueAssetName(SourceAnims[i]->GetOutermost()->GetName(), Suffix, /*out*/ PackageName, /*out*/ Name);
|
||||||
|
const FString PackagePath = FPackageName::GetLongPackagePath(PackageName);
|
||||||
|
|
||||||
|
|
||||||
|
UObject* NewAsset = AssetToolsModule.Get().DuplicateAsset(Name, PackagePath, SourceAnims[i]);
|
||||||
|
|
||||||
|
if (NewAsset != NULL)
|
||||||
|
{
|
||||||
|
UAnimSequence* MirrorAnimSequence = Cast<UAnimSequence>(NewAsset);
|
||||||
|
CreateMirrorSequenceFromAnimSequence_CS(MirrorAnimSequence, MirrorAxis, Substring_A, Substring_B, Symmetrical);
|
||||||
|
|
||||||
|
OutNewAnims.Add(MirrorAnimSequence);
|
||||||
|
|
||||||
|
// Notify asset registry of new asset
|
||||||
|
FAssetRegistryModule::AssetCreated(MirrorAnimSequence);
|
||||||
|
|
||||||
|
// Display notification so users can quickly access
|
||||||
|
if (GIsEditor)
|
||||||
|
{
|
||||||
|
FNotificationInfo Info(FText::Format(LOCTEXT("AnimationMirrored", "Successfully Mirrored Animation"), FText::FromString(MirrorAnimSequence->GetName())));
|
||||||
|
Info.ExpireDuration = 8.0f;
|
||||||
|
Info.bUseLargeFont = false;
|
||||||
|
//Info.Hyperlink = FSimpleDelegate::CreateLambda([=]() { FAssetEditorManager::Get().OpenEditorForAssets(TArray<UObject*>({ MirrorAnimSequence })); });
|
||||||
|
Info.Hyperlink = FSimpleDelegate::CreateLambda([=]() { GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAssets(TArray<UObject*>({ MirrorAnimSequence })); });
|
||||||
|
|
||||||
|
Info.HyperlinkText = FText::Format(LOCTEXT("OpenNewAnimationHyperlink", "Open {0}"), FText::FromString(MirrorAnimSequence->GetName()));
|
||||||
|
TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification(Info);
|
||||||
|
if (Notification.IsValid())
|
||||||
|
{
|
||||||
|
Notification->SetCompletionState(SNotificationItem::CS_Success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
|
||||||
|
void UMASFunctionLibrary::CreateMirrorSequenceFromAnimSequence(UAnimSequence* MirrorSequence, const UMirrorTable* MirrorTable)
|
||||||
|
{
|
||||||
|
//Check if it's valid
|
||||||
|
if ((MirrorSequence != NULL) && (MirrorTable != NULL) && (MirrorSequence->GetSkeleton() != NULL))
|
||||||
|
{
|
||||||
|
//Make the duplicate that I will edit
|
||||||
|
//UAnimSequence* MirrorSequence = FromAnimSequence;
|
||||||
|
const auto& Skel = MirrorSequence->GetSkeleton()->GetReferenceSkeleton();
|
||||||
|
|
||||||
|
int NumMirrorBones = MirrorTable->MirrorBones.Num();
|
||||||
|
|
||||||
|
int NumFrames = MirrorSequence->GetNumberOfFrames();
|
||||||
|
TArray<FRawAnimSequenceTrack> SourceRawAnimDatas = MirrorSequence->GetRawAnimationData();
|
||||||
|
const auto& TrackNames = MirrorSequence->GetAnimationTrackNames();
|
||||||
|
|
||||||
|
for (int i = 0; i < NumMirrorBones; i++)
|
||||||
|
{
|
||||||
|
FMirrorBone CurrentBone = MirrorTable->MirrorBones[i];
|
||||||
|
|
||||||
|
if (Skel.FindBoneIndex(CurrentBone.BoneName) == INDEX_NONE)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CurrentBone.IsTwinBone)
|
||||||
|
{
|
||||||
|
if (Skel.FindBoneIndex(CurrentBone.TwinBoneName) == INDEX_NONE)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 TrackIndex = TrackNames.IndexOfByKey(CurrentBone.BoneName);
|
||||||
|
int32 TwinTrackIndex = TrackNames.IndexOfByKey(CurrentBone.TwinBoneName);
|
||||||
|
|
||||||
|
if (TrackIndex == INDEX_NONE && TwinTrackIndex == INDEX_NONE)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray <FVector> MirrorPosKeys;
|
||||||
|
TArray <FQuat> MirrorRotKeys;
|
||||||
|
TArray <FVector> MirrorScaleKeys;
|
||||||
|
|
||||||
|
TArray <FVector> TwinMirrorPosKeys;
|
||||||
|
TArray <FQuat> TwinMirrorRotKeys;
|
||||||
|
TArray <FVector> TwinMirrorScaleKeys;
|
||||||
|
|
||||||
|
// Original Bone
|
||||||
|
if (TrackIndex != INDEX_NONE)
|
||||||
|
{
|
||||||
|
auto& MirroredRawTrack = SourceRawAnimDatas[TrackIndex];
|
||||||
|
|
||||||
|
for (int u = 0; u < NumFrames; u++)
|
||||||
|
{
|
||||||
|
FTransform MirrorTM;
|
||||||
|
|
||||||
|
bool bSetPos = false;
|
||||||
|
bool bSetRot = false;
|
||||||
|
bool bSetScale = false;
|
||||||
|
|
||||||
|
if (MirroredRawTrack.PosKeys.IsValidIndex(u))
|
||||||
|
{
|
||||||
|
MirrorTM.SetTranslation(MirroredRawTrack.PosKeys[u]);
|
||||||
|
bSetPos = true;
|
||||||
|
}
|
||||||
|
if (MirroredRawTrack.RotKeys.IsValidIndex(u))
|
||||||
|
{
|
||||||
|
MirrorTM.SetRotation(MirroredRawTrack.RotKeys[u]);
|
||||||
|
bSetRot = true;
|
||||||
|
}
|
||||||
|
if (MirroredRawTrack.ScaleKeys.IsValidIndex(u))
|
||||||
|
{
|
||||||
|
MirrorTM.SetScale3D(MirroredRawTrack.ScaleKeys[u]);
|
||||||
|
bSetScale = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
MirrorTM.Mirror(CurrentBone.MirrorAxis, CurrentBone.FlipAxis);
|
||||||
|
|
||||||
|
FRotator BoneNewRotation = MirrorTM.Rotator();
|
||||||
|
|
||||||
|
BoneNewRotation.Yaw += CurrentBone.RotationOffset.Yaw;
|
||||||
|
BoneNewRotation.Roll += CurrentBone.RotationOffset.Roll;
|
||||||
|
BoneNewRotation.Pitch += CurrentBone.RotationOffset.Pitch;
|
||||||
|
|
||||||
|
MirrorTM.SetRotation(FQuat(BoneNewRotation));
|
||||||
|
MirrorTM.SetScale3D(MirrorTM.GetScale3D().GetAbs());
|
||||||
|
MirrorTM.NormalizeRotation();
|
||||||
|
|
||||||
|
if (bSetPos)
|
||||||
|
{
|
||||||
|
MirrorPosKeys.Add(MirrorTM.GetTranslation());
|
||||||
|
}
|
||||||
|
if (bSetRot)
|
||||||
|
{
|
||||||
|
MirrorRotKeys.Add(MirrorTM.GetRotation());
|
||||||
|
}
|
||||||
|
if (bSetScale)
|
||||||
|
{
|
||||||
|
MirrorScaleKeys.Add(MirrorTM.GetScale3D());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto RefTM = Skel.GetRefBonePose()[Skel.FindBoneIndex(CurrentBone.BoneName)];
|
||||||
|
|
||||||
|
RefTM.Mirror(CurrentBone.MirrorAxis, CurrentBone.FlipAxis);
|
||||||
|
|
||||||
|
FRotator BoneNewRotation = RefTM.Rotator();
|
||||||
|
|
||||||
|
BoneNewRotation.Yaw += CurrentBone.RotationOffset.Yaw;
|
||||||
|
BoneNewRotation.Roll += CurrentBone.RotationOffset.Roll;
|
||||||
|
BoneNewRotation.Pitch += CurrentBone.RotationOffset.Pitch;
|
||||||
|
|
||||||
|
RefTM.SetRotation(FQuat(BoneNewRotation));
|
||||||
|
RefTM.SetScale3D(RefTM.GetScale3D().GetAbs());
|
||||||
|
RefTM.NormalizeRotation();
|
||||||
|
|
||||||
|
MirrorPosKeys.Add(RefTM.GetTranslation());
|
||||||
|
MirrorRotKeys.Add(RefTM.GetRotation());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Twin Bone
|
||||||
|
if (TwinTrackIndex != INDEX_NONE)
|
||||||
|
{
|
||||||
|
auto& TwinMirroredRawTrack = SourceRawAnimDatas[TwinTrackIndex];
|
||||||
|
|
||||||
|
for (int u = 0; u < NumFrames; u++)
|
||||||
|
{
|
||||||
|
FTransform TwinMirrorTM;
|
||||||
|
|
||||||
|
bool TwinbSetPos = false;
|
||||||
|
bool TwinbSetRot = false;
|
||||||
|
bool TwinbSetScale = false;
|
||||||
|
|
||||||
|
if (TwinMirroredRawTrack.PosKeys.IsValidIndex(u))
|
||||||
|
{
|
||||||
|
TwinMirrorTM.SetTranslation(TwinMirroredRawTrack.PosKeys[u]);
|
||||||
|
TwinbSetPos = true;
|
||||||
|
}
|
||||||
|
if (TwinMirroredRawTrack.RotKeys.IsValidIndex(u))
|
||||||
|
{
|
||||||
|
TwinMirrorTM.SetRotation(TwinMirroredRawTrack.RotKeys[u]);
|
||||||
|
TwinbSetRot = true;
|
||||||
|
}
|
||||||
|
if (TwinMirroredRawTrack.ScaleKeys.IsValidIndex(u))
|
||||||
|
{
|
||||||
|
TwinMirrorTM.SetScale3D(TwinMirroredRawTrack.ScaleKeys[u]);
|
||||||
|
TwinbSetScale = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TwinMirrorTM.Mirror(CurrentBone.MirrorAxis, CurrentBone.FlipAxis);
|
||||||
|
|
||||||
|
FRotator TwinBoneNewRotation = TwinMirrorTM.Rotator();
|
||||||
|
|
||||||
|
TwinBoneNewRotation.Yaw += CurrentBone.RotationOffset.Yaw;
|
||||||
|
TwinBoneNewRotation.Roll += CurrentBone.RotationOffset.Roll;
|
||||||
|
TwinBoneNewRotation.Pitch += CurrentBone.RotationOffset.Pitch;
|
||||||
|
|
||||||
|
TwinMirrorTM.SetRotation(FQuat(TwinBoneNewRotation));
|
||||||
|
TwinMirrorTM.SetScale3D(TwinMirrorTM.GetScale3D().GetAbs());
|
||||||
|
TwinMirrorTM.NormalizeRotation();
|
||||||
|
|
||||||
|
if (TwinbSetPos)
|
||||||
|
{
|
||||||
|
TwinMirrorPosKeys.Add(TwinMirrorTM.GetTranslation());
|
||||||
|
}
|
||||||
|
if (TwinbSetRot)
|
||||||
|
{
|
||||||
|
TwinMirrorRotKeys.Add(TwinMirrorTM.GetRotation());
|
||||||
|
}
|
||||||
|
if (TwinbSetScale)
|
||||||
|
{
|
||||||
|
TwinMirrorScaleKeys.Add(TwinMirrorTM.GetScale3D());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto RefTM = Skel.GetRefBonePose()[Skel.FindBoneIndex(CurrentBone.TwinBoneName)];
|
||||||
|
|
||||||
|
RefTM.Mirror(CurrentBone.MirrorAxis, CurrentBone.FlipAxis);
|
||||||
|
|
||||||
|
FRotator TwinBoneNewRotation = RefTM.Rotator();
|
||||||
|
|
||||||
|
TwinBoneNewRotation.Yaw += CurrentBone.RotationOffset.Yaw;
|
||||||
|
TwinBoneNewRotation.Roll += CurrentBone.RotationOffset.Roll;
|
||||||
|
TwinBoneNewRotation.Pitch += CurrentBone.RotationOffset.Pitch;
|
||||||
|
|
||||||
|
RefTM.SetRotation(FQuat(TwinBoneNewRotation));
|
||||||
|
RefTM.SetScale3D(RefTM.GetScale3D().GetAbs());
|
||||||
|
RefTM.NormalizeRotation();
|
||||||
|
|
||||||
|
TwinMirrorPosKeys.Add(RefTM.GetTranslation());
|
||||||
|
TwinMirrorRotKeys.Add(RefTM.GetRotation());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Original Bone -> Twin Bone
|
||||||
|
{
|
||||||
|
FRawAnimSequenceTrack NewTrack;
|
||||||
|
|
||||||
|
NewTrack.PosKeys = CurrentBone.MirrorTranslation ? MirrorPosKeys : TwinMirrorPosKeys;
|
||||||
|
NewTrack.RotKeys = MirrorRotKeys;
|
||||||
|
NewTrack.ScaleKeys = MirrorScaleKeys;
|
||||||
|
|
||||||
|
MirrorSequence->AddNewRawTrack(CurrentBone.TwinBoneName, &NewTrack);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Twin Bone -> Original Bone
|
||||||
|
{
|
||||||
|
FRawAnimSequenceTrack NewTrack;
|
||||||
|
|
||||||
|
NewTrack.PosKeys = CurrentBone.MirrorTranslation ? TwinMirrorPosKeys : MirrorPosKeys;
|
||||||
|
NewTrack.RotKeys = TwinMirrorRotKeys;
|
||||||
|
NewTrack.ScaleKeys = TwinMirrorScaleKeys;
|
||||||
|
|
||||||
|
MirrorSequence->AddNewRawTrack(CurrentBone.BoneName, &NewTrack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int32 TrackIndex = TrackNames.IndexOfByKey(CurrentBone.BoneName);
|
||||||
|
|
||||||
|
if (TrackIndex == INDEX_NONE)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
FRawAnimSequenceTrack MirroredRawTrack = SourceRawAnimDatas[TrackIndex];
|
||||||
|
|
||||||
|
//MirrorAllFrames
|
||||||
|
TArray <FVector> MirrorPosKeys;
|
||||||
|
TArray <FQuat> MirrorRotKeys;
|
||||||
|
TArray <FVector> MirrorScaleKeys;
|
||||||
|
|
||||||
|
for (int u = 0; u < NumFrames; u++)
|
||||||
|
{
|
||||||
|
//Mirror Transform
|
||||||
|
FTransform MirrorTM;
|
||||||
|
|
||||||
|
bool bSetPos = false;
|
||||||
|
bool bSetRot = false;
|
||||||
|
bool bSetScale = false;
|
||||||
|
|
||||||
|
if (MirroredRawTrack.PosKeys.IsValidIndex(u))
|
||||||
|
{
|
||||||
|
MirrorTM.SetTranslation(MirroredRawTrack.PosKeys[u]);
|
||||||
|
bSetPos = true;
|
||||||
|
}
|
||||||
|
if (MirroredRawTrack.RotKeys.IsValidIndex(u))
|
||||||
|
{
|
||||||
|
MirrorTM.SetRotation(MirroredRawTrack.RotKeys[u]);
|
||||||
|
bSetRot = true;
|
||||||
|
}
|
||||||
|
if (MirroredRawTrack.ScaleKeys.IsValidIndex(u))
|
||||||
|
{
|
||||||
|
MirrorTM.SetScale3D(MirroredRawTrack.ScaleKeys[u]);
|
||||||
|
bSetScale = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
MirrorTM.Mirror(CurrentBone.MirrorAxis, CurrentBone.FlipAxis);
|
||||||
|
|
||||||
|
FRotator BoneNewRotation = MirrorTM.Rotator();
|
||||||
|
|
||||||
|
BoneNewRotation.Yaw += CurrentBone.RotationOffset.Yaw;
|
||||||
|
BoneNewRotation.Roll += CurrentBone.RotationOffset.Roll;
|
||||||
|
BoneNewRotation.Pitch += CurrentBone.RotationOffset.Pitch;
|
||||||
|
|
||||||
|
MirrorTM.SetRotation(FQuat(BoneNewRotation));
|
||||||
|
//MirrorTM.NormalizeRotation();
|
||||||
|
MirrorTM.SetScale3D(MirrorTM.GetScale3D().GetAbs());
|
||||||
|
|
||||||
|
MirrorTM.NormalizeRotation();
|
||||||
|
|
||||||
|
//Setting it up Main
|
||||||
|
if (bSetPos)
|
||||||
|
{
|
||||||
|
MirrorPosKeys.Add(MirrorTM.GetTranslation());
|
||||||
|
}
|
||||||
|
if (bSetRot)
|
||||||
|
{
|
||||||
|
MirrorRotKeys.Add(MirrorTM.GetRotation());
|
||||||
|
}
|
||||||
|
if (bSetScale)
|
||||||
|
{
|
||||||
|
MirrorScaleKeys.Add(MirrorTM.GetScale3D());
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////
|
||||||
|
}
|
||||||
|
|
||||||
|
MirroredRawTrack.PosKeys = MirrorPosKeys;
|
||||||
|
MirroredRawTrack.RotKeys = MirrorRotKeys;
|
||||||
|
MirroredRawTrack.ScaleKeys = MirrorScaleKeys;
|
||||||
|
|
||||||
|
//Finally Setting it in the AnimSequence
|
||||||
|
|
||||||
|
MirrorSequence->AddNewRawTrack(CurrentBone.BoneName, &MirroredRawTrack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MirrorSequence->ClearBakedTransformData();
|
||||||
|
MirrorSequence->RawCurveData.TransformCurves.Empty();
|
||||||
|
MirrorSequence->bNeedsRebake = false;
|
||||||
|
MirrorSequence->MarkRawDataAsModified();
|
||||||
|
MirrorSequence->OnRawDataChanged();
|
||||||
|
MirrorSequence->MarkPackageDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static FTransform GetAnimBoneTM(UAnimSequence* AnimSeq, const int32 BoneTreeIndex, const float AnimTime)
|
||||||
|
{
|
||||||
|
USkeleton* Skeleton = AnimSeq->GetSkeleton();
|
||||||
|
//int32 BoneTreeIndex = Skeleton->GetSkeletonBoneIndexFromMeshBoneIndex(SkelMesh, BoneTreeIndex);
|
||||||
|
int32 BoneTrackIndex = Skeleton->GetRawAnimationTrackIndex(BoneTreeIndex, AnimSeq);
|
||||||
|
if (BoneTrackIndex == INDEX_NONE)
|
||||||
|
{
|
||||||
|
return Skeleton->GetReferenceSkeleton().GetRefBonePose()[BoneTreeIndex];
|
||||||
|
}
|
||||||
|
FTransform BoneTM = FTransform::Identity;
|
||||||
|
AnimSeq->GetBoneTransform(BoneTM, BoneTrackIndex, AnimTime, true);
|
||||||
|
return BoneTM;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FTransform GetAnimBoneCSTM(UAnimSequence* AnimSeq, const int32 BoneTreeIndex, const float AnimTime)
|
||||||
|
{
|
||||||
|
USkeleton* Skeleton = AnimSeq->GetSkeleton();
|
||||||
|
const auto& RefSkeleton = Skeleton->GetReferenceSkeleton();
|
||||||
|
FTransform BoneTMWS = GetAnimBoneTM(AnimSeq, BoneTreeIndex, AnimTime);
|
||||||
|
int32 CurrBone = BoneTreeIndex;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
const int32 Parent(RefSkeleton.GetParentIndex(CurrBone));
|
||||||
|
if (Parent < 0) break;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
BoneTMWS = BoneTMWS * GetAnimBoneTM(AnimSeq, Parent, AnimTime);
|
||||||
|
|
||||||
|
CurrBone = Parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return BoneTMWS;
|
||||||
|
}
|
||||||
|
|
||||||
|
MIRRORANIMATIONSYSTEMDEV_API void UMASFunctionLibrary::CreateMirrorSequenceFromAnimSequence_CS(
|
||||||
|
UAnimSequence* MirrorSequence,
|
||||||
|
const TEnumAsByte<EAxis::Type> MirrorAxis,
|
||||||
|
const FString Substring_A,
|
||||||
|
const FString Substring_B,
|
||||||
|
const bool Symmetrical)
|
||||||
|
{
|
||||||
|
const int32 NumFrames = MirrorSequence->GetRawNumberOfFrames();
|
||||||
|
const float DT = MirrorSequence->SequenceLength / NumFrames;
|
||||||
|
|
||||||
|
USkeleton* Skeleton = MirrorSequence->GetSkeleton();
|
||||||
|
const auto& RefSkeleton = Skeleton->GetReferenceSkeleton();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TArray <bool> Already; Already.SetNumZeroed(Skeleton->GetBoneTree().Num());
|
||||||
|
|
||||||
|
TArray<FIntPoint> TwinPairs;
|
||||||
|
TArray<int32> NonTwinIDs;
|
||||||
|
TArray<EAxis::Type> NonTwinFlipAxis;
|
||||||
|
FMASUtils::CSMirrorSettings(RefSkeleton, MirrorAxis, Substring_A, Substring_B, TwinPairs, NonTwinIDs, NonTwinFlipAxis);
|
||||||
|
|
||||||
|
const bool DeltaStep = !Symmetrical;
|
||||||
|
|
||||||
|
FVector TwinMirrorScale = FVector(1.f);
|
||||||
|
FVector TargetAxis = FVector::ZeroVector;
|
||||||
|
|
||||||
|
check(MirrorAxis != EAxis::None);
|
||||||
|
{
|
||||||
|
TwinMirrorScale[MirrorAxis - 1] = -1.f;
|
||||||
|
TargetAxis[MirrorAxis - 1] = 1.f;
|
||||||
|
}
|
||||||
|
FTransform TwinMirrorModTM(FQuat::Identity, FVector::ZeroVector, TwinMirrorScale);
|
||||||
|
|
||||||
|
|
||||||
|
TMap<int32, FRawAnimSequenceTrack> BoneTracks;
|
||||||
|
|
||||||
|
for (int32 i = 0; i < RefSkeleton.GetNum(); i++)
|
||||||
|
{
|
||||||
|
BoneTracks.Add(i, FRawAnimSequenceTrack());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32 j = 0; j < NumFrames; j++)
|
||||||
|
{
|
||||||
|
TArray <FTransform> NewCSTMs; NewCSTMs.SetNum(RefSkeleton.GetNum());
|
||||||
|
|
||||||
|
for (int32 i = 0; i < NonTwinIDs.Num(); i++)
|
||||||
|
{
|
||||||
|
int32 BoneTreeIndex = NonTwinIDs[i];
|
||||||
|
int32 BoneTrackIndex = Skeleton->GetRawAnimationTrackIndex(BoneTreeIndex, MirrorSequence);
|
||||||
|
|
||||||
|
if (BoneTrackIndex == INDEX_NONE)
|
||||||
|
{
|
||||||
|
const int32 ParentIndex = RefSkeleton.GetParentIndex(BoneTreeIndex);
|
||||||
|
if (ParentIndex != INDEX_NONE)
|
||||||
|
{
|
||||||
|
NewCSTMs[BoneTreeIndex] = RefSkeleton.GetRefBonePose()[BoneTreeIndex] * NewCSTMs[ParentIndex];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NewCSTMs[BoneTreeIndex] = RefSkeleton.GetRefBonePose()[BoneTreeIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
FTransform CSTM = GetAnimBoneCSTM(MirrorSequence, BoneTreeIndex, DT * j);
|
||||||
|
CSTM.Mirror(MirrorAxis, NonTwinFlipAxis[i]);
|
||||||
|
|
||||||
|
NewCSTMs[BoneTreeIndex] = CSTM;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (int32 i = 0; i < TwinPairs.Num(); i++)
|
||||||
|
{
|
||||||
|
const int32 BoneIndex = TwinPairs[i].X;
|
||||||
|
const FCompactPoseBoneIndex CmptBoneIndex(BoneIndex);
|
||||||
|
|
||||||
|
const int32 TwinBoneIndex = TwinPairs[i].Y;
|
||||||
|
|
||||||
|
const FCompactPoseBoneIndex TwinCmptBoneIndex(TwinBoneIndex);
|
||||||
|
|
||||||
|
const FTransform RefTM = FAnimationRuntime::GetComponentSpaceTransformRefPose(RefSkeleton, BoneIndex);
|
||||||
|
const FTransform TwinRefTM = FAnimationRuntime::GetComponentSpaceTransformRefPose(RefSkeleton, TwinBoneIndex);
|
||||||
|
|
||||||
|
const FTransform TM = GetAnimBoneCSTM(MirrorSequence, BoneIndex, DT * j);
|
||||||
|
//Output.Pose.GetComponentSpaceTransform(CmptBoneIndex);
|
||||||
|
const FTransform TwinTM = GetAnimBoneCSTM(MirrorSequence, TwinBoneIndex, DT * j);
|
||||||
|
//Output.Pose.GetComponentSpaceTransform(TwinCmptBoneIndex);
|
||||||
|
|
||||||
|
const int32 ParentIndex = RefSkeleton.GetParentIndex(BoneIndex);
|
||||||
|
const int32 TwinParentIndex = RefSkeleton.GetParentIndex(TwinBoneIndex);
|
||||||
|
|
||||||
|
const bool SameParent = ParentIndex == TwinParentIndex;
|
||||||
|
|
||||||
|
// twin 1º
|
||||||
|
{
|
||||||
|
const FTransform MirrRef = RefTM * TwinMirrorModTM;
|
||||||
|
const FTransform Delta = TwinRefTM.GetRelativeTransform(MirrRef);
|
||||||
|
const FQuat DeltaQuat = Delta.GetRotation();
|
||||||
|
|
||||||
|
FTransform MirrTM = TM * TwinMirrorModTM;
|
||||||
|
|
||||||
|
MirrTM.SetRotation(MirrTM.GetRotation() * DeltaQuat);
|
||||||
|
MirrTM.SetScale3D(TwinTM.GetScale3D());
|
||||||
|
|
||||||
|
if (DeltaStep)
|
||||||
|
{
|
||||||
|
if (SameParent)
|
||||||
|
{
|
||||||
|
FTransform RefBS = RefTM;
|
||||||
|
RefBS = RefBS * TwinMirrorModTM;
|
||||||
|
const FVector PosDelta = MirrTM.GetLocation() - RefBS.GetLocation();
|
||||||
|
MirrTM.SetLocation(TwinRefTM.GetLocation() + PosDelta);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const FTransform& ParentTwinTM = NewCSTMs[RefSkeleton.GetParentIndex(TwinBoneIndex)];
|
||||||
|
const FTransform& IParentTM =// Output.Pose.GetComponentSpaceTransform(FCompactPoseBoneIndex(ParentIndex));
|
||||||
|
GetAnimBoneCSTM(MirrorSequence, ParentIndex, DT * j);
|
||||||
|
FTransform RefBS = RefSkeleton.GetRefBonePose()[BoneIndex] * IParentTM;
|
||||||
|
RefBS = RefBS * TwinMirrorModTM;
|
||||||
|
RefBS.SetRotation(RefBS.GetRotation() * DeltaQuat);
|
||||||
|
RefBS.SetScale3D(TwinTM.GetScale3D());
|
||||||
|
|
||||||
|
MirrTM = (MirrTM.GetRelativeTransform(RefBS) * RefSkeleton.GetRefBonePose()[TwinBoneIndex]) * ParentTwinTM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NewCSTMs[TwinBoneIndex] = MirrTM;
|
||||||
|
}
|
||||||
|
|
||||||
|
// twin 2º
|
||||||
|
{
|
||||||
|
FTransform TwinMirrRef = TwinRefTM * TwinMirrorModTM;
|
||||||
|
const FQuat TwinDeltaQuat = TwinMirrRef.GetRotation().Inverse() * RefTM.GetRotation();
|
||||||
|
|
||||||
|
FTransform TwinMirrTM = TwinTM * TwinMirrorModTM;
|
||||||
|
|
||||||
|
TwinMirrTM.SetRotation(TwinMirrTM.GetRotation() * TwinDeltaQuat);
|
||||||
|
TwinMirrTM.SetScale3D(TM.GetScale3D());
|
||||||
|
|
||||||
|
if (DeltaStep)
|
||||||
|
{
|
||||||
|
if (SameParent)
|
||||||
|
{
|
||||||
|
FTransform TwinRefBS = TwinRefTM;
|
||||||
|
TwinRefBS = TwinRefBS * TwinMirrorModTM;
|
||||||
|
const FVector PosDelta = TwinMirrTM.GetLocation() - TwinRefBS.GetLocation();
|
||||||
|
TwinMirrTM.SetLocation(RefTM.GetLocation() + PosDelta);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const FTransform& ParentTM = NewCSTMs[RefSkeleton.GetParentIndex(BoneIndex)];
|
||||||
|
const FTransform& IParentTwinTM = //Output.Pose.GetComponentSpaceTransform(FCompactPoseBoneIndex(TwinParentIndex));
|
||||||
|
GetAnimBoneCSTM(MirrorSequence, TwinParentIndex, DT * j);
|
||||||
|
FTransform TwinRefBS = RefSkeleton.GetRefBonePose()[TwinBoneIndex] * IParentTwinTM;
|
||||||
|
TwinRefBS = TwinRefBS * TwinMirrorModTM;
|
||||||
|
TwinRefBS.SetRotation(TwinRefBS.GetRotation() * TwinDeltaQuat);
|
||||||
|
TwinRefBS.SetScale3D(TM.GetScale3D());
|
||||||
|
|
||||||
|
TwinMirrTM = (TwinMirrTM.GetRelativeTransform(TwinRefBS) * RefSkeleton.GetRefBonePose()[BoneIndex]) * ParentTM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NewCSTMs[BoneIndex] = TwinMirrTM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (int32 i = 0; i < NewCSTMs.Num(); i++)
|
||||||
|
{
|
||||||
|
const int32 ParentIndex = RefSkeleton.GetParentIndex(i);
|
||||||
|
FTransform BSTM;
|
||||||
|
if (ParentIndex != INDEX_NONE) BSTM = NewCSTMs[i].GetRelativeTransform(NewCSTMs[ParentIndex]);
|
||||||
|
else BSTM = NewCSTMs[i];
|
||||||
|
|
||||||
|
auto& BoneTrack = BoneTracks[i];
|
||||||
|
BoneTrack.PosKeys.Add(BSTM.GetLocation());
|
||||||
|
BoneTrack.RotKeys.Add(BSTM.GetRotation());
|
||||||
|
BoneTrack.ScaleKeys.Add(BSTM.GetScale3D());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto Pair : BoneTracks)
|
||||||
|
{
|
||||||
|
const FName TrackName = Skeleton->GetReferenceSkeleton().GetBoneName(Pair.Key);
|
||||||
|
MirrorSequence->AddNewRawTrack(TrackName, &Pair.Value);
|
||||||
|
}
|
||||||
|
// Have to also apply to pelvis and spine_01
|
||||||
|
MirrorSequence->MarkRawDataAsModified();
|
||||||
|
MirrorSequence->OnRawDataChanged();
|
||||||
|
MirrorSequence->MarkPackageDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "MirrorAnimationSystemDev.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "FMirrorAnimationSystemDevModule"
|
||||||
|
|
||||||
|
void FMirrorAnimationSystemDevModule::StartupModule()
|
||||||
|
{
|
||||||
|
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
|
||||||
|
}
|
||||||
|
|
||||||
|
void FMirrorAnimationSystemDevModule::ShutdownModule()
|
||||||
|
{
|
||||||
|
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
|
||||||
|
// we call this function before unloading the module.
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
||||||
|
|
||||||
|
IMPLEMENT_MODULE(FMirrorAnimationSystemDevModule, MirrorAnimationSystemDev)
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||||
|
|
||||||
|
#include "MASFunctionLibrary.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
class UAnimSequence;
|
||||||
|
class UMirrorTable;
|
||||||
|
|
||||||
|
|
||||||
|
UCLASS(MinimalAPI, meta = (ScriptName = "MirrorLibrary"))
|
||||||
|
class UMASFunctionLibrary : public UBlueprintFunctionLibrary
|
||||||
|
{
|
||||||
|
GENERATED_UCLASS_BODY()
|
||||||
|
public:
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Mirror Animation", meta = (DevelopmentOnly))
|
||||||
|
static MIRRORANIMATIONSYSTEMDEV_API void BulkMirrorEditorOnly(const TArray <UAnimSequence*> Anims, const UMirrorTable* MirrorTable, TArray <UAnimSequence*>& OutNewAnims);
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Mirror Animation", meta = (DevelopmentOnly))
|
||||||
|
static MIRRORANIMATIONSYSTEMDEV_API void BulkMirror_CS_EditorOnly(const TArray <UAnimSequence*> Anims,
|
||||||
|
const TEnumAsByte<EAxis::Type> MirrorAxis, const FString Substring_A, const FString Substring_B, const bool Symmetrical, TArray <UAnimSequence*>& OutNewAnims);
|
||||||
|
#if WITH_EDITOR
|
||||||
|
static MIRRORANIMATIONSYSTEMDEV_API void CreateMirrorSequenceFromAnimSequence(UAnimSequence* MirrorSequence, const UMirrorTable* MirrorTable);
|
||||||
|
static MIRRORANIMATIONSYSTEMDEV_API void CreateMirrorSequenceFromAnimSequence_CS(UAnimSequence* MirrorSequence,
|
||||||
|
const TEnumAsByte<EAxis::Type> MirrorAxis, const FString Substring_A, const FString Substring_B, const bool Symmetrical);
|
||||||
|
#endif
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
|
||||||
|
class FMirrorAnimationSystemDevModule : public IModuleInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** IModuleInterface implementation */
|
||||||
|
virtual void StartupModule() override;
|
||||||
|
virtual void ShutdownModule() override;
|
||||||
|
};
|
|
@ -0,0 +1,72 @@
|
||||||
|
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
using UnrealBuildTool;
|
||||||
|
|
||||||
|
public class MirrorAnimationSystemEditor : ModuleRules
|
||||||
|
{
|
||||||
|
public MirrorAnimationSystemEditor(ReadOnlyTargetRules Target) : base(Target)
|
||||||
|
{
|
||||||
|
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||||
|
|
||||||
|
PublicIncludePaths.AddRange(
|
||||||
|
new string[] {
|
||||||
|
// ... add public include paths required here ...
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
PrivateIncludePaths.AddRange(
|
||||||
|
new string[] {
|
||||||
|
// ... add other private include paths required here ...
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
PublicDependencyModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
"Core",
|
||||||
|
// ... add other public dependencies that you statically link with here ...
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
PrivateDependencyModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
"CoreUObject",
|
||||||
|
"Engine",
|
||||||
|
"Slate",
|
||||||
|
"SlateCore",
|
||||||
|
// Start
|
||||||
|
"Engine",
|
||||||
|
"InputCore",
|
||||||
|
"UnrealEd", // for FAssetEditorManager
|
||||||
|
"KismetWidgets",
|
||||||
|
"Kismet", // for FWorkflowCentricApplication
|
||||||
|
"PropertyEditor",
|
||||||
|
"RenderCore",
|
||||||
|
"ContentBrowser",
|
||||||
|
"WorkspaceMenuStructure",
|
||||||
|
"EditorStyle",
|
||||||
|
"MeshPaint",
|
||||||
|
"EditorWidgets",
|
||||||
|
"Projects",
|
||||||
|
"MirrorAnimationSystem",
|
||||||
|
"MirrorAnimationSystemDev",
|
||||||
|
"AnimGraph",
|
||||||
|
"BlueprintGraph",
|
||||||
|
// ... add private dependencies that you statically link with here ...
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
DynamicallyLoadedModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
"AssetTools",
|
||||||
|
// ... add any modules that your module loads dynamically here ...
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
#include "AnimGraphNode_Mirror.h"
|
||||||
|
#include "MirrorAnimationSystemEditor.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "A3Nodes"
|
||||||
|
|
||||||
|
UAnimGraphNode_Mirror::UAnimGraphNode_Mirror(const FObjectInitializer& ObjectInitializer)
|
||||||
|
:Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
FLinearColor UAnimGraphNode_Mirror::GetNodeTitleColor() const
|
||||||
|
{
|
||||||
|
return FLinearColor::Red;
|
||||||
|
}
|
||||||
|
|
||||||
|
FText UAnimGraphNode_Mirror::GetTooltipText() const
|
||||||
|
{
|
||||||
|
return LOCTEXT("Mirrors_the_designated_bones", "Mirrors the pose based on the designated Mirror Table");
|
||||||
|
}
|
||||||
|
|
||||||
|
FText UAnimGraphNode_Mirror::GetNodeTitle(ENodeTitleType::Type TitleType) const
|
||||||
|
{
|
||||||
|
return LOCTEXT("Mirror_Pose", "Mirror Pose");
|
||||||
|
}
|
||||||
|
|
||||||
|
FString UAnimGraphNode_Mirror::GetNodeCategory() const
|
||||||
|
{
|
||||||
|
return TEXT("Tools");
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
|
@ -0,0 +1,53 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
#include "AnimGraphNode_MirrorCS.h"
|
||||||
|
#include "MirrorAnimationSystemEditor.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "A3Nodes"
|
||||||
|
|
||||||
|
|
||||||
|
UAnimGraphNode_MirrorCS::UAnimGraphNode_MirrorCS(const FObjectInitializer& ObjectInitializer)
|
||||||
|
: Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FLinearColor UAnimGraphNode_MirrorCS::GetNodeTitleColor() const
|
||||||
|
{
|
||||||
|
return FLinearColor::Blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
FText UAnimGraphNode_MirrorCS::GetControllerDescription() const
|
||||||
|
{
|
||||||
|
return LOCTEXT("AnimGraphNode_MirrorCS", "Mirror Pose CS");
|
||||||
|
}
|
||||||
|
|
||||||
|
FText UAnimGraphNode_MirrorCS::GetTooltipText() const
|
||||||
|
{
|
||||||
|
return LOCTEXT("AnimGraphNode_MirrorCS_Tooltip", "Mirror the pose in Component Space.");
|
||||||
|
}
|
||||||
|
|
||||||
|
FText UAnimGraphNode_MirrorCS::GetNodeTitle(ENodeTitleType::Type TitleType) const
|
||||||
|
{
|
||||||
|
FText NodeTitle;
|
||||||
|
if (TitleType == ENodeTitleType::ListView || TitleType == ENodeTitleType::MenuTitle)
|
||||||
|
{
|
||||||
|
NodeTitle = GetControllerDescription();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NodeTitle = FText(LOCTEXT("AnimGraphNode_MirrorCS_Title", "Mirror Pose CS"));
|
||||||
|
}
|
||||||
|
return NodeTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UAnimGraphNode_MirrorCS::ValidateAnimNodePostCompile(class FCompilerResultsLog& MessageLog, class UAnimBlueprintGeneratedClass* CompiledClass, int32 CompiledNodeIndex)
|
||||||
|
{
|
||||||
|
Super::ValidateAnimNodePostCompile(MessageLog, CompiledClass, CompiledNodeIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UAnimGraphNode_MirrorCS::IsCompatibleWithGraph(const UEdGraph* TargetGraph) const
|
||||||
|
{
|
||||||
|
return Super::IsCompatibleWithGraph(TargetGraph);
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
#include "AssetTypeActions_MirrorTable.h"
|
||||||
|
#include "MirrorAnimationSystemEditor.h"
|
||||||
|
|
||||||
|
#include "MirrorTable.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "AssetTypeActions"
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// FFlipbookAssetTypeActions
|
||||||
|
|
||||||
|
FAssetTypeActions_MirrorTable::FAssetTypeActions_MirrorTable(EAssetTypeCategories::Type InAssetCategory)
|
||||||
|
: MyAssetCategory(InAssetCategory)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FText FAssetTypeActions_MirrorTable::GetName() const
|
||||||
|
{
|
||||||
|
return LOCTEXT("FMirrorTableAssetTypeActionsName", "Mirror Table");
|
||||||
|
}
|
||||||
|
|
||||||
|
FColor FAssetTypeActions_MirrorTable::GetTypeColor() const
|
||||||
|
{
|
||||||
|
return FColor::Blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
UClass* FAssetTypeActions_MirrorTable::GetSupportedClass() const
|
||||||
|
{
|
||||||
|
return UMirrorTable::StaticClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FAssetTypeActions_MirrorTable::OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor)
|
||||||
|
{
|
||||||
|
const EToolkitMode::Type Mode = EditWithinLevelEditor.IsValid() ? EToolkitMode::WorldCentric : EToolkitMode::Standalone;
|
||||||
|
|
||||||
|
for (auto ObjIt = InObjects.CreateConstIterator(); ObjIt; ++ObjIt)
|
||||||
|
{
|
||||||
|
if (UMirrorTable* MirrorTable = Cast<UMirrorTable>(*ObjIt))
|
||||||
|
{
|
||||||
|
FSimpleAssetEditor::CreateEditor(Mode, Mode == EToolkitMode::WorldCentric ? EditWithinLevelEditor : TSharedPtr<IToolkitHost>(), MirrorTable);
|
||||||
|
/*
|
||||||
|
TSharedRef<FFlipbookEditor> NewFlipbookEditor(new FFlipbookEditor());
|
||||||
|
NewFlipbookEditor->InitFlipbookEditor(Mode, EditWithinLevelEditor, Flipbook);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 FAssetTypeActions_MirrorTable::GetCategories()
|
||||||
|
{
|
||||||
|
return EAssetTypeCategories::Animation | MyAssetCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
|
@ -0,0 +1,254 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
#include "ContentBrowserTools.h"
|
||||||
|
#include "MirrorAnimationSystemEditor.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "Animation/AnimSequence.h"
|
||||||
|
#include "Components/SkeletalMeshComponent.h"
|
||||||
|
#include "ContentBrowserModule.h"
|
||||||
|
#include "Framework/MultiBox/MultiBoxExtender.h"
|
||||||
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
||||||
|
#include "EditorStyleSet.h"
|
||||||
|
#include "IContentBrowserSingleton.h"
|
||||||
|
#include "MirrorAnimationSystemStyle.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "MirrorAnimAssetDialog.h"
|
||||||
|
#include "MirrorTableFromSkeletonDialog.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "IAssetTools.h"
|
||||||
|
#include "AssetToolsModule.h"
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// FContentBrowserSelectedAssetExtensionBase
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "MirrorAnimationSystem"
|
||||||
|
|
||||||
|
//DECLARE_LOG_CATEGORY_EXTERN(LogMirrorCBExtensions, Log, All);
|
||||||
|
//DEFINE_LOG_CATEGORY(LogMirrorCBExtensions);
|
||||||
|
|
||||||
|
FContentBrowserMenuExtender_SelectedAssets ContentBrowserExtenderDelegate;
|
||||||
|
FDelegateHandle ContentBrowserExtenderDelegateHandle;
|
||||||
|
|
||||||
|
//Base struct to be extended and merely hold primordial data
|
||||||
|
struct FContentBrowserSelectedAssetExtensionBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TArray<struct FAssetData> SelectedAssets;
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual void Execute() {}
|
||||||
|
virtual ~FContentBrowserSelectedAssetExtensionBase() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
//Struct that contains the functionality for opening the Dialog of the "Mirror AnimAsset" tool
|
||||||
|
struct FMirrorAnimAssetExtension : public FContentBrowserSelectedAssetExtensionBase
|
||||||
|
{
|
||||||
|
FMirrorAnimAssetExtension()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void MirrorAnimAssets(TArray<UAnimSequence*>& Animations)
|
||||||
|
{
|
||||||
|
FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools");
|
||||||
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
|
||||||
|
|
||||||
|
TArray<UObject*> ObjectsToSync;
|
||||||
|
|
||||||
|
for (auto AnimationIt = Animations.CreateConstIterator(); AnimationIt; ++AnimationIt)
|
||||||
|
{
|
||||||
|
UAnimSequence* Animation = *AnimationIt;
|
||||||
|
|
||||||
|
SMirrorAnimAssetDialog::ShowWindow(Animation);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ObjectsToSync.Num() > 0)
|
||||||
|
{
|
||||||
|
ContentBrowserModule.Get().SyncBrowserToAssets(ObjectsToSync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Execute() override
|
||||||
|
{
|
||||||
|
// Mirror Animations from selected animassets
|
||||||
|
TArray<UAnimSequence*> Animations;
|
||||||
|
for (auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt)
|
||||||
|
{
|
||||||
|
const FAssetData& AssetData = *AssetIt;
|
||||||
|
if (UAnimSequence* Animation = Cast<UAnimSequence>(AssetData.GetAsset()))
|
||||||
|
{
|
||||||
|
Animations.Add(Animation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MirrorAnimAssets(Animations);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
//Struct that contains the functionality for opening the Dialog of the "Mirror Table from Skeleton" tool
|
||||||
|
struct FCreateMirrorTableExtension : public FContentBrowserSelectedAssetExtensionBase
|
||||||
|
{
|
||||||
|
FCreateMirrorTableExtension()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateMirrorTable(TArray<USkeleton*>& Skeletons)
|
||||||
|
{
|
||||||
|
FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools");
|
||||||
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
|
||||||
|
|
||||||
|
TArray<UObject*> ObjectsToSync;
|
||||||
|
|
||||||
|
for (auto SkeletonIt = Skeletons.CreateConstIterator(); SkeletonIt; ++SkeletonIt)
|
||||||
|
{
|
||||||
|
USkeleton* Skeleton = *SkeletonIt;
|
||||||
|
|
||||||
|
SMirrorTableFromSkeletonDialog::ShowWindow(Skeleton);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ObjectsToSync.Num() > 0)
|
||||||
|
{
|
||||||
|
ContentBrowserModule.Get().SyncBrowserToAssets(ObjectsToSync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Execute() override
|
||||||
|
{
|
||||||
|
// Create Mirror Table
|
||||||
|
TArray<USkeleton*> Skeletons;
|
||||||
|
for (auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt)
|
||||||
|
{
|
||||||
|
const FAssetData& AssetData = *AssetIt;
|
||||||
|
if (USkeleton* Skeleton = Cast<USkeleton>(AssetData.GetAsset()))
|
||||||
|
{
|
||||||
|
Skeletons.Add(Skeleton);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CreateMirrorTable(Skeletons);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// FContentBrowserToolExtensions_Impl
|
||||||
|
;
|
||||||
|
/*class that identifies and constructs the tool's buttons adding the functionality for opening
|
||||||
|
the corresponding dialog when clicked*/
|
||||||
|
class FContentBrowserToolExtensions_Impl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static void ExecuteSelectedContentFunctor(TSharedPtr<FContentBrowserSelectedAssetExtensionBase> SelectedAssetFunctor)
|
||||||
|
{
|
||||||
|
SelectedAssetFunctor->Execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CreateMirrorButton(FMenuBuilder& MenuBuilder, TArray<FAssetData> SelectedAssets)
|
||||||
|
{
|
||||||
|
TSharedPtr<FMirrorAnimAssetExtension> AnimationMirrorFunctor = MakeShareable(new FMirrorAnimAssetExtension());
|
||||||
|
AnimationMirrorFunctor->SelectedAssets = SelectedAssets;
|
||||||
|
|
||||||
|
FUIAction Action_MirrorAnimAsset(
|
||||||
|
FExecuteAction::CreateStatic(&FContentBrowserToolExtensions_Impl::ExecuteSelectedContentFunctor, StaticCastSharedPtr<FContentBrowserSelectedAssetExtensionBase>(AnimationMirrorFunctor)));
|
||||||
|
|
||||||
|
const FName MyToolEditorStyleSetName = FMirrorAnimationSystemStyle::GetStyleSetName();
|
||||||
|
|
||||||
|
MenuBuilder.AddMenuEntry(
|
||||||
|
LOCTEXT("Mirror_Animation_Asset", "Mirror Animation Asset"),
|
||||||
|
LOCTEXT("Mirror_This_Animation_Based_On_Mirror_Table", "Mirror this Animation asset based on a Mirror Table"),
|
||||||
|
FSlateIcon(MyToolEditorStyleSetName, "Icon.MirrorAnimAsset"),
|
||||||
|
Action_MirrorAnimAsset,
|
||||||
|
NAME_None,
|
||||||
|
EUserInterfaceActionType::Button);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CreateMirrorTableButton(FMenuBuilder& MenuBuilder, TArray<FAssetData> SelectedAssets)
|
||||||
|
{
|
||||||
|
TSharedPtr<FCreateMirrorTableExtension> CreateMirrorTableFunctor = MakeShareable(new FCreateMirrorTableExtension());
|
||||||
|
CreateMirrorTableFunctor->SelectedAssets = SelectedAssets;
|
||||||
|
|
||||||
|
FUIAction Action_CreateMirrorTable(
|
||||||
|
FExecuteAction::CreateStatic(&FContentBrowserToolExtensions_Impl::ExecuteSelectedContentFunctor, StaticCastSharedPtr<FContentBrowserSelectedAssetExtensionBase>(CreateMirrorTableFunctor)));
|
||||||
|
|
||||||
|
const FName MyToolEditorStyleSetName = FMirrorAnimationSystemStyle::GetStyleSetName();
|
||||||
|
|
||||||
|
MenuBuilder.AddMenuEntry(
|
||||||
|
LOCTEXT("Create_Mirror_Table", "Mirror Table From Skeleton"),
|
||||||
|
LOCTEXT("Create_Mirror_Table_From_Skeleton", "Creates a Mirror Table template based on this Skeleton's Hierarchy"),
|
||||||
|
FSlateIcon(MyToolEditorStyleSetName, "Icon.CreateMirrorTable"),
|
||||||
|
Action_CreateMirrorTable,
|
||||||
|
NAME_None,
|
||||||
|
EUserInterfaceActionType::Button);
|
||||||
|
}
|
||||||
|
|
||||||
|
static TSharedRef<FExtender> OnExtendContentBrowserAssetSelectionMenu(const TArray<FAssetData>& SelectedAssets)
|
||||||
|
{
|
||||||
|
TSharedRef<FExtender> Extender(new FExtender());
|
||||||
|
|
||||||
|
// Run thru the assets to determine if any meet our criteria
|
||||||
|
bool bShowButton = false;
|
||||||
|
bool bIsSkeletonAsset = false;
|
||||||
|
for (auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt)
|
||||||
|
{
|
||||||
|
const FAssetData& Asset = *AssetIt;
|
||||||
|
bShowButton = bShowButton || (Asset.AssetClass == UAnimSequence::StaticClass()->GetFName()) || (Asset.AssetClass == USkeleton::StaticClass()->GetFName());
|
||||||
|
bIsSkeletonAsset = (Asset.AssetClass == USkeleton::StaticClass()->GetFName());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bShowButton)
|
||||||
|
{
|
||||||
|
if (bIsSkeletonAsset)
|
||||||
|
{
|
||||||
|
// Add the Mirror AnimAsset sub-menu extender
|
||||||
|
Extender->AddMenuExtension(
|
||||||
|
"GetAssetActions",
|
||||||
|
EExtensionHook::After,
|
||||||
|
nullptr,
|
||||||
|
FMenuExtensionDelegate::CreateStatic(&FContentBrowserToolExtensions_Impl::CreateMirrorTableButton, SelectedAssets));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Add the Mirror AnimAsset sub-menu extender
|
||||||
|
Extender->AddMenuExtension(
|
||||||
|
"GetAssetActions",
|
||||||
|
EExtensionHook::After,
|
||||||
|
nullptr,
|
||||||
|
FMenuExtensionDelegate::CreateStatic(&FContentBrowserToolExtensions_Impl::CreateMirrorButton, SelectedAssets));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Extender;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TArray<FContentBrowserMenuExtender_SelectedAssets>& GetExtenderDelegates()
|
||||||
|
{
|
||||||
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
|
||||||
|
return ContentBrowserModule.GetAllAssetViewContextMenuExtenders();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
void FContentBrowserTools::InstallHooks()
|
||||||
|
{
|
||||||
|
ContentBrowserExtenderDelegate = FContentBrowserMenuExtender_SelectedAssets::CreateStatic(&FContentBrowserToolExtensions_Impl::OnExtendContentBrowserAssetSelectionMenu);
|
||||||
|
|
||||||
|
TArray<FContentBrowserMenuExtender_SelectedAssets>& CBMenuExtenderDelegates = FContentBrowserToolExtensions_Impl::GetExtenderDelegates();
|
||||||
|
CBMenuExtenderDelegates.Add(ContentBrowserExtenderDelegate);
|
||||||
|
ContentBrowserExtenderDelegateHandle = CBMenuExtenderDelegates.Last().GetHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FContentBrowserTools::RemoveHooks()
|
||||||
|
{
|
||||||
|
TArray<FContentBrowserMenuExtender_SelectedAssets>& CBMenuExtenderDelegates = FContentBrowserToolExtensions_Impl::GetExtenderDelegates();
|
||||||
|
CBMenuExtenderDelegates.RemoveAll([](const FContentBrowserMenuExtender_SelectedAssets& Delegate) { return Delegate.GetHandle() == ContentBrowserExtenderDelegateHandle; });
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
|
@ -0,0 +1,230 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
#include "MirrorAnimAssetDialog.h"
|
||||||
|
#include "MirrorAnimationSystemEditor.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "Widgets/SBoxPanel.h"
|
||||||
|
#include "Widgets/SWindow.h"
|
||||||
|
#include "Widgets/SViewport.h"
|
||||||
|
#include "Misc/FeedbackContext.h"
|
||||||
|
#include "Misc/ScopedSlowTask.h"
|
||||||
|
#include "Misc/MessageDialog.h"
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
#include "Misc/PackageName.h"
|
||||||
|
#include "Layout/WidgetPath.h"
|
||||||
|
#include "Framework/Application/SlateApplication.h"
|
||||||
|
#include "Widgets/Layout/SBorder.h"
|
||||||
|
#include "Widgets/Layout/SUniformGridPanel.h"
|
||||||
|
#include "Widgets/Input/SButton.h"
|
||||||
|
#include "Framework/Docking/TabManager.h"
|
||||||
|
#include "EditorStyleSet.h"
|
||||||
|
#include "CanvasItem.h"
|
||||||
|
|
||||||
|
#include "PropertyEditorModule.h"
|
||||||
|
#include "IContentBrowserSingleton.h"
|
||||||
|
#include "ContentBrowserModule.h"
|
||||||
|
#include "IAssetTools.h"
|
||||||
|
#include "AssetToolsModule.h"
|
||||||
|
#include "CanvasTypes.h"
|
||||||
|
|
||||||
|
#include "Factories/AnimSequenceFactory.h"
|
||||||
|
|
||||||
|
#include "MASFunctionLibrary.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "MirrorAnimationSystemEditor"
|
||||||
|
|
||||||
|
void SMirrorAnimAssetDialog::Construct(const FArguments & InArgs, UAnimSequence * AnimSequence)
|
||||||
|
{
|
||||||
|
SourceAnimSequence = AnimSequence;
|
||||||
|
|
||||||
|
MirrorAnimAssetSettings = NewObject<UMirrorAnimAssetSettings>();
|
||||||
|
MirrorAnimAssetSettings->AddToRoot();
|
||||||
|
|
||||||
|
FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
||||||
|
FDetailsViewArgs DetailsViewArgs(/*bUpdateFromSelection=*/ false, /*bLockable=*/ false, /*bAllowSearch=*/ false, /*InNameAreaSettings=*/ FDetailsViewArgs::HideNameArea, /*bHideSelectionTip=*/ true);
|
||||||
|
MainPropertyView = EditModule.CreateDetailView(DetailsViewArgs);
|
||||||
|
MainPropertyView->SetObject(MirrorAnimAssetSettings);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ChildSlot
|
||||||
|
[
|
||||||
|
SNew(SBorder)
|
||||||
|
.BorderImage(FEditorStyle::GetBrush("DetailsView.CategoryTop"))
|
||||||
|
.Padding(FMargin(1.0f, 1.0f, 1.0f, 0.0f))
|
||||||
|
[
|
||||||
|
SNew(SVerticalBox)
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
.HAlign(HAlign_Fill)
|
||||||
|
.VAlign(VAlign_Fill)
|
||||||
|
[
|
||||||
|
SNew(SVerticalBox)
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
.Padding(1.0f)
|
||||||
|
.AutoHeight()
|
||||||
|
[
|
||||||
|
MainPropertyView.ToSharedRef()
|
||||||
|
]
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
.Padding(1.0f)
|
||||||
|
.HAlign(HAlign_Right)
|
||||||
|
.VAlign(VAlign_Bottom)
|
||||||
|
[
|
||||||
|
SNew(SUniformGridPanel)
|
||||||
|
.SlotPadding(1)
|
||||||
|
+ SUniformGridPanel::Slot(0, 0)
|
||||||
|
[
|
||||||
|
SNew(SButton)
|
||||||
|
.ButtonStyle(FEditorStyle::Get(), "FlatButton.Success")
|
||||||
|
.ForegroundColor(FLinearColor::White)
|
||||||
|
.Text(LOCTEXT("PaperExtractSpritesExtractButton", "Mirror"))
|
||||||
|
.OnClicked(this, &SMirrorAnimAssetDialog::MirrorClicked)
|
||||||
|
]
|
||||||
|
+ SUniformGridPanel::Slot(1, 0)
|
||||||
|
[
|
||||||
|
SNew(SButton)
|
||||||
|
.ButtonStyle(FEditorStyle::Get(), "FlatButton")
|
||||||
|
.ForegroundColor(FLinearColor::White)
|
||||||
|
.Text(LOCTEXT("PaperExtractSpritesCancelButton", "Cancel"))
|
||||||
|
.OnClicked(this, &SMirrorAnimAssetDialog::CancelClicked)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
SMirrorAnimAssetDialog::~SMirrorAnimAssetDialog()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SMirrorAnimAssetDialog::ShowWindow(UAnimSequence * SourceAnimSequence)
|
||||||
|
{
|
||||||
|
|
||||||
|
const FText TitleText = NSLOCTEXT("MirrorAnimationSystem", "MirrorAnimationSystem_MirrorAnimSequence", "Mirror AnimSequence");
|
||||||
|
// Create the window to pick the class
|
||||||
|
TSharedRef<SWindow> MirrorAnimSequenceWindow = SNew(SWindow)
|
||||||
|
.Title(TitleText)
|
||||||
|
.SizingRule(ESizingRule::UserSized)
|
||||||
|
.ClientSize(FVector2D(720.f, 720.f))
|
||||||
|
.AutoCenter(EAutoCenter::PreferredWorkArea)
|
||||||
|
.SupportsMinimize(false);
|
||||||
|
|
||||||
|
TSharedRef<SMirrorAnimAssetDialog> MirrorAnimSequenceDialog = SNew(SMirrorAnimAssetDialog, SourceAnimSequence);
|
||||||
|
|
||||||
|
MirrorAnimSequenceWindow->SetContent(MirrorAnimSequenceDialog);
|
||||||
|
TSharedPtr<SWindow> RootWindow = FGlobalTabmanager::Get()->GetRootWindow();
|
||||||
|
if (RootWindow.IsValid())
|
||||||
|
{
|
||||||
|
FSlateApplication::Get().AddWindowAsNativeChild(MirrorAnimSequenceWindow, RootWindow.ToSharedRef());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FSlateApplication::Get().AddWindow(MirrorAnimSequenceWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SMirrorAnimAssetDialog::ValidateCSMirrorData()
|
||||||
|
{
|
||||||
|
if (MirrorAnimAssetSettings->MirrorTable)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MirrorAnimAssetSettings->MirrorAxis == EAxis::None)
|
||||||
|
{
|
||||||
|
FText DialogText = FText::AsCultureInvariant(FString(TEXT("Mirror Axis Cannot be None")));
|
||||||
|
FMessageDialog::Open(EAppMsgType::Ok, DialogText);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((MirrorAnimAssetSettings->Substring_A == "None")
|
||||||
|
||
|
||||||
|
(MirrorAnimAssetSettings->Substring_B == "None"))
|
||||||
|
{
|
||||||
|
FText DialogText = FText::AsCultureInvariant(FString(TEXT("Invalid Substring value")));
|
||||||
|
FMessageDialog::Open(EAppMsgType::Ok, DialogText);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
FReply SMirrorAnimAssetDialog::MirrorClicked()
|
||||||
|
{
|
||||||
|
if (ValidateCSMirrorData())
|
||||||
|
{
|
||||||
|
CreateMirroredAnimSequences();
|
||||||
|
|
||||||
|
CloseContainingWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
|
||||||
|
FReply SMirrorAnimAssetDialog::CancelClicked()
|
||||||
|
{
|
||||||
|
CloseContainingWindow();
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SMirrorAnimAssetDialog::CloseContainingWindow()
|
||||||
|
{
|
||||||
|
TSharedPtr<SWindow> ContainingWindow = FSlateApplication::Get().FindWidgetWindow(AsShared());
|
||||||
|
if (ContainingWindow.IsValid())
|
||||||
|
{
|
||||||
|
ContainingWindow->RequestDestroyWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SMirrorAnimAssetDialog::CreateMirroredAnimSequences()
|
||||||
|
{
|
||||||
|
|
||||||
|
FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools");
|
||||||
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
|
||||||
|
|
||||||
|
TArray<UObject*> ObjectsToSync;
|
||||||
|
|
||||||
|
// Create the asset
|
||||||
|
FString Name;
|
||||||
|
FString PackageName;
|
||||||
|
|
||||||
|
|
||||||
|
FString Suffix = TEXT("_Mirrored");
|
||||||
|
|
||||||
|
AssetToolsModule.Get().CreateUniqueAssetName(SourceAnimSequence->GetOutermost()->GetName(), Suffix, /*out*/ PackageName, /*out*/ Name);
|
||||||
|
const FString PackagePath = FPackageName::GetLongPackagePath(PackageName);
|
||||||
|
|
||||||
|
|
||||||
|
UObject* NewAsset = AssetToolsModule.Get().DuplicateAsset(Name, PackagePath, SourceAnimSequence);
|
||||||
|
|
||||||
|
if (NewAsset != NULL)
|
||||||
|
{
|
||||||
|
|
||||||
|
UAnimSequence* MirrorAnimSequence = Cast<UAnimSequence>(NewAsset);
|
||||||
|
|
||||||
|
if (MirrorAnimAssetSettings->MirrorTable != NULL)
|
||||||
|
{
|
||||||
|
UMASFunctionLibrary::CreateMirrorSequenceFromAnimSequence(MirrorAnimSequence, MirrorAnimAssetSettings->MirrorTable);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UMASFunctionLibrary::CreateMirrorSequenceFromAnimSequence_CS(MirrorAnimSequence,
|
||||||
|
MirrorAnimAssetSettings->MirrorAxis,
|
||||||
|
MirrorAnimAssetSettings->Substring_A,
|
||||||
|
MirrorAnimAssetSettings->Substring_B,
|
||||||
|
MirrorAnimAssetSettings->CompletlySymmetrical);
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectsToSync.Add(NewAsset);
|
||||||
|
}
|
||||||
|
if (ObjectsToSync.Num() > 0)
|
||||||
|
{
|
||||||
|
ContentBrowserModule.Get().SyncBrowserToAssets(ObjectsToSync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
|
@ -0,0 +1,9 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
#include "MirrorAnimAssetSettings.h"
|
||||||
|
#include "MirrorAnimationSystemEditor.h"
|
||||||
|
|
||||||
|
|
||||||
|
UMirrorAnimAssetSettings::UMirrorAnimAssetSettings(const FObjectInitializer& ObjectInitializer)
|
||||||
|
: Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "MirrorAnimationSystemEditor.h"
|
||||||
|
|
||||||
|
#include "SlateBasics.h"
|
||||||
|
#include "SlateExtras.h"
|
||||||
|
|
||||||
|
#include "LevelEditor.h"
|
||||||
|
#include "ContentBrowserTools.h"
|
||||||
|
#include "MirrorAnimationSystemStyle.h"
|
||||||
|
|
||||||
|
#include "AssetToolsModule.h"
|
||||||
|
#include "MirrorTableDetails.h"
|
||||||
|
#include "AssetTypeActions_MirrorTable.h"
|
||||||
|
|
||||||
|
|
||||||
|
static const FName MirrorAnimationSystemEditorTabName("MirrorAnimationSystemEditor");
|
||||||
|
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "FMirrorAnimationSystemEditorModule"
|
||||||
|
|
||||||
|
void FMirrorAnimationSystemEditorModule::StartupModule()
|
||||||
|
{
|
||||||
|
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
|
||||||
|
FMirrorAnimationSystemStyle::Initialize();
|
||||||
|
|
||||||
|
|
||||||
|
if (!IsRunningCommandlet())
|
||||||
|
{
|
||||||
|
FContentBrowserTools::InstallHooks();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
||||||
|
RegisterAssetTypeAction(AssetTools, MakeShareable(new FAssetTypeActions_MirrorTable(MirrorTableAssetCategoryBit)));
|
||||||
|
|
||||||
|
FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
||||||
|
RegisterCustomClassLayout("MirrorTable", FOnGetDetailCustomizationInstance::CreateStatic(&FMirrorTableDetails::MakeInstance));
|
||||||
|
PropertyModule.NotifyCustomizationModuleChanged();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void FMirrorAnimationSystemEditorModule::ShutdownModule()
|
||||||
|
{
|
||||||
|
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
|
||||||
|
// we call this function before unloading the module.
|
||||||
|
|
||||||
|
if (!IsRunningCommandlet())
|
||||||
|
{
|
||||||
|
FContentBrowserTools::RemoveHooks();
|
||||||
|
}
|
||||||
|
FMirrorAnimationSystemStyle::Shutdown();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (FModuleManager::Get().IsModuleLoaded("AssetTools"))
|
||||||
|
{
|
||||||
|
IAssetTools& AssetTools = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
||||||
|
for (int32 Index = 0; Index < CreatedAssetTypeActions.Num(); ++Index)
|
||||||
|
{
|
||||||
|
AssetTools.UnregisterAssetTypeActions(CreatedAssetTypeActions[Index].ToSharedRef());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CreatedAssetTypeActions.Empty();
|
||||||
|
if (FModuleManager::Get().IsModuleLoaded("PropertyEditor"))
|
||||||
|
{
|
||||||
|
FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
||||||
|
|
||||||
|
// Unregister all classes customized by name
|
||||||
|
for (auto It = RegisteredClassNames.CreateConstIterator(); It; ++It)
|
||||||
|
{
|
||||||
|
if (It->IsValid())
|
||||||
|
{
|
||||||
|
PropertyModule.UnregisterCustomClassLayout(*It);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PropertyModule.NotifyCustomizationModuleChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void FMirrorAnimationSystemEditorModule::RegisterAssetTypeAction(IAssetTools& AssetTools, TSharedRef<IAssetTypeActions> Action)
|
||||||
|
{
|
||||||
|
AssetTools.RegisterAssetTypeActions(Action);
|
||||||
|
CreatedAssetTypeActions.Add(Action);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FMirrorAnimationSystemEditorModule::RegisterCustomClassLayout(FName ClassName, FOnGetDetailCustomizationInstance DetailLayoutDelegate)
|
||||||
|
{
|
||||||
|
check(ClassName != NAME_None);
|
||||||
|
|
||||||
|
RegisteredClassNames.Add(ClassName);
|
||||||
|
|
||||||
|
static FName PropertyEditor("PropertyEditor");
|
||||||
|
FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>(PropertyEditor);
|
||||||
|
PropertyModule.RegisterCustomClassLayout(ClassName, DetailLayoutDelegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
||||||
|
|
||||||
|
IMPLEMENT_MODULE(FMirrorAnimationSystemEditorModule, MirrorAnimationSystemEditor)
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
|
||||||
|
#include "MirrorAnimationSystemStyle.h"
|
||||||
|
#include "MirrorAnimationSystemEditor.h"
|
||||||
|
|
||||||
|
#include "Framework/Application/SlateApplication.h"
|
||||||
|
#include "Styling/SlateStyleRegistry.h"
|
||||||
|
#include "Slate/SlateGameResources.h"
|
||||||
|
#include "Interfaces/IPluginManager.h"
|
||||||
|
|
||||||
|
TSharedPtr< FSlateStyleSet > FMirrorAnimationSystemStyle::StyleInstance = NULL;
|
||||||
|
|
||||||
|
void FMirrorAnimationSystemStyle::Initialize()
|
||||||
|
{
|
||||||
|
if (!StyleInstance.IsValid())
|
||||||
|
{
|
||||||
|
StyleInstance = Create();
|
||||||
|
FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FMirrorAnimationSystemStyle::Shutdown()
|
||||||
|
{
|
||||||
|
FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance);
|
||||||
|
ensure(StyleInstance.IsUnique());
|
||||||
|
StyleInstance.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
FName FMirrorAnimationSystemStyle::GetStyleSetName()
|
||||||
|
{
|
||||||
|
static FName StyleSetName(TEXT("MirrorAnimationSystemStyle"));
|
||||||
|
return StyleSetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ )
|
||||||
|
#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ )
|
||||||
|
#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ )
|
||||||
|
#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ )
|
||||||
|
#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ )
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TSharedRef< FSlateStyleSet > FMirrorAnimationSystemStyle::Create()
|
||||||
|
{
|
||||||
|
const FVector2D Icon16x16(16.0f, 16.0f);
|
||||||
|
const FVector2D Icon20x20(20.0f, 20.0f);
|
||||||
|
const FVector2D Icon40x40(40.0f, 40.0f);
|
||||||
|
const FVector2D Icon64x64(64.0f, 64.0f);
|
||||||
|
|
||||||
|
TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("MirrorAnimationSystemStyle"));
|
||||||
|
Style->SetContentRoot(IPluginManager::Get().FindPlugin("MirrorAnimationSystem")->GetBaseDir() / TEXT("Resources"));
|
||||||
|
|
||||||
|
Style->Set("Icon.MirrorAnimAsset", new IMAGE_BRUSH(TEXT("MirrorAnimAsset_Icon"), Icon16x16));
|
||||||
|
Style->Set("Icon.CreateMirrorTable", new IMAGE_BRUSH(TEXT("CreateMirrorTable_Icon"), Icon16x16));
|
||||||
|
Style->Set("ClassIcon.MirrorTable", new IMAGE_BRUSH(TEXT("MirrorTable_ClassIcon"), Icon20x20));
|
||||||
|
Style->Set("ClassThumbnail.MirrorTable", new IMAGE_BRUSH(TEXT("MirrorTable_ClassIcon"), Icon64x64));
|
||||||
|
|
||||||
|
return Style;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef IMAGE_BRUSH
|
||||||
|
#undef BOX_BRUSH
|
||||||
|
#undef BORDER_BRUSH
|
||||||
|
#undef TTF_FONT
|
||||||
|
#undef OTF_FONT
|
|
@ -0,0 +1,239 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
#include "MirrorTableDetails.h"
|
||||||
|
#include "MirrorAnimationSystemEditor.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "Widgets/SWidget.h"
|
||||||
|
#include "Layout/Margin.h"
|
||||||
|
#include "Widgets/DeclarativeSyntaxSupport.h"
|
||||||
|
#include "Widgets/SBoxPanel.h"
|
||||||
|
#include "EditorStyleSet.h"
|
||||||
|
#include "IDetailChildrenBuilder.h"
|
||||||
|
#include "Framework/Application/SlateApplication.h"
|
||||||
|
#include "Widgets/Layout/SBorder.h"
|
||||||
|
#include "Widgets/Text/STextBlock.h"
|
||||||
|
#include "Widgets/Layout/SBox.h"
|
||||||
|
#include "Widgets/Input/SEditableTextBox.h"
|
||||||
|
#include "Widgets/Input/SButton.h"
|
||||||
|
|
||||||
|
#include "Misc/MessageDialog.h"
|
||||||
|
|
||||||
|
#include "DetailLayoutBuilder.h"
|
||||||
|
#include "DetailCategoryBuilder.h"
|
||||||
|
#include "IDetailsView.h"
|
||||||
|
#include "PropertyCustomizationHelpers.h"
|
||||||
|
|
||||||
|
#include "MirrorTable.h"
|
||||||
|
//#include "DialogueWaveWidgets.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "MirrorTableDetails"
|
||||||
|
|
||||||
|
FMirrorTableNodeBuilder::FMirrorTableNodeBuilder(IDetailLayoutBuilder* InDetailLayoutBuilder, const TSharedPtr<IPropertyHandle>& InPropertyHandle)
|
||||||
|
: DetailLayoutBuilder(InDetailLayoutBuilder)
|
||||||
|
, ContextMappingPropertyHandle(InPropertyHandle)
|
||||||
|
, BoneNameHandle(ContextMappingPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, BoneName)))
|
||||||
|
, TwinBoneNameHandle(ContextMappingPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, TwinBoneName)))
|
||||||
|
, IsTwinBoneHandle(ContextMappingPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, IsTwinBone)))
|
||||||
|
{
|
||||||
|
//check(BoneNameHandle.IsValid());
|
||||||
|
//check(TwinBoneNameHandle.IsValid());
|
||||||
|
// check(IsTwinBoneHandle.IsValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
void FMirrorTableNodeBuilder::GenerateHeaderRowContent(FDetailWidgetRow& NodeRow)
|
||||||
|
{
|
||||||
|
if (ContextMappingPropertyHandle->IsValidHandle())
|
||||||
|
{
|
||||||
|
const TSharedPtr<IPropertyHandle> ContextPropertyHandle = ContextMappingPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, MirrorAxis));
|
||||||
|
if (ContextPropertyHandle->IsValidHandle())//if (BoneNameHandle->IsValidHandle())
|
||||||
|
{
|
||||||
|
const TSharedPtr<IPropertyHandle> FlipAxisPropertyHandle = ContextPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, FlipAxis));
|
||||||
|
const TSharedPtr<IPropertyHandle> RotationOffsetPropertyHandle = ContextPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, RotationOffset));
|
||||||
|
const TSharedPtr<IPropertyHandle> BoneNamePropertyHandle = ContextPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, BoneName));
|
||||||
|
const TSharedPtr<IPropertyHandle> ParentHandle = ContextMappingPropertyHandle->GetParentHandle();
|
||||||
|
const TSharedPtr<IPropertyHandleArray> ParentArrayHandle = ParentHandle->AsArray();
|
||||||
|
|
||||||
|
|
||||||
|
uint32 ContextCount;
|
||||||
|
ParentArrayHandle->GetNumElements(ContextCount);
|
||||||
|
|
||||||
|
TSharedRef<SWidget> ClearButton = PropertyCustomizationHelpers::MakeDeleteButton(FSimpleDelegate::CreateSP(this, &FMirrorTableNodeBuilder::RemoveContextButton_OnClick),
|
||||||
|
ContextCount > 1 ? LOCTEXT("RemoveBoneToolTip", "Remove Bone.") : LOCTEXT("RemoveBoneDisabledToolTip", "Cannot remove Bone - a mirror table must have at least one bone."),
|
||||||
|
ContextCount > 1);
|
||||||
|
|
||||||
|
NodeRow
|
||||||
|
[
|
||||||
|
SNew(SHorizontalBox)
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.FillWidth(1.0f)
|
||||||
|
[
|
||||||
|
SNew(SBorder)
|
||||||
|
.BorderImage(FEditorStyle::GetBrush("DialogueWaveDetails.HeaderBorder"))
|
||||||
|
[
|
||||||
|
//SNew(SDialogueContextHeaderWidget, ContextPropertyHandle.ToSharedRef(), DetailLayoutBuilder->GetThumbnailPool().ToSharedRef())
|
||||||
|
SNew(SVerticalBox)
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
.Padding(2.0f)
|
||||||
|
.HAlign(HAlign_Center)
|
||||||
|
.AutoHeight()
|
||||||
|
[
|
||||||
|
// Voice Description
|
||||||
|
SNew(STextBlock)
|
||||||
|
.Text(this, &FMirrorTableNodeBuilder::GetText)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.Padding(2.0f)
|
||||||
|
.HAlign(HAlign_Center)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
.AutoWidth()
|
||||||
|
[
|
||||||
|
ClearButton
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FMirrorTableNodeBuilder::Tick(float DeltaTime)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void FMirrorTableNodeBuilder::GenerateChildContent(IDetailChildrenBuilder& ChildrenBuilder)
|
||||||
|
{
|
||||||
|
if (ContextMappingPropertyHandle->IsValidHandle())
|
||||||
|
{
|
||||||
|
ChildrenBuilder.AddProperty(BoneNameHandle.ToSharedRef());
|
||||||
|
|
||||||
|
const TSharedPtr<IPropertyHandle> MirrorAxisPropertyHandle = ContextMappingPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, MirrorAxis));
|
||||||
|
ChildrenBuilder.AddProperty(MirrorAxisPropertyHandle.ToSharedRef());
|
||||||
|
|
||||||
|
const TSharedPtr<IPropertyHandle> FlipAxisPropertyHandle = ContextMappingPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, FlipAxis));
|
||||||
|
ChildrenBuilder.AddProperty(FlipAxisPropertyHandle.ToSharedRef());
|
||||||
|
|
||||||
|
const TSharedPtr<IPropertyHandle> RotationOffsetPropertyHandle = ContextMappingPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, RotationOffset));
|
||||||
|
ChildrenBuilder.AddProperty(RotationOffsetPropertyHandle.ToSharedRef());
|
||||||
|
|
||||||
|
ChildrenBuilder.AddProperty(IsTwinBoneHandle.ToSharedRef());
|
||||||
|
|
||||||
|
ChildrenBuilder.AddProperty(TwinBoneNameHandle.ToSharedRef());
|
||||||
|
|
||||||
|
const TSharedPtr<IPropertyHandle> MirrorTranslationPropertyHandle = ContextMappingPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, MirrorTranslation));
|
||||||
|
ChildrenBuilder.AddProperty(MirrorTranslationPropertyHandle.ToSharedRef());
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FText FMirrorTableNodeBuilder::GetText() const
|
||||||
|
{
|
||||||
|
FString ValueStr;
|
||||||
|
FText RowText = FText::FromString(TEXT("Basic"));
|
||||||
|
if (BoneNameHandle->GetValue(ValueStr) == FPropertyAccess::Success)
|
||||||
|
{
|
||||||
|
bool ValueBl;
|
||||||
|
RowText = FText::FromString(TEXT("BoneNameHandle"));
|
||||||
|
if (IsTwinBoneHandle->GetValue(ValueBl) == FPropertyAccess::Success)
|
||||||
|
{
|
||||||
|
RowText = FText::FromString(TEXT("IsTwinBoneHandle"));
|
||||||
|
if (ValueBl)
|
||||||
|
{
|
||||||
|
RowText = FText::FromString(TEXT("ValueBl"));
|
||||||
|
FString AddedStr;
|
||||||
|
if (TwinBoneNameHandle->GetValue(AddedStr) == FPropertyAccess::Success)
|
||||||
|
{
|
||||||
|
ValueStr += (" / " + AddedStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowText = FText::FromString(MoveTemp(ValueStr));
|
||||||
|
}
|
||||||
|
return RowText;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FMirrorTableNodeBuilder::RemoveContextButton_OnClick()
|
||||||
|
{
|
||||||
|
if (ContextMappingPropertyHandle->IsValidHandle())
|
||||||
|
{
|
||||||
|
const TSharedPtr<IPropertyHandle> ParentHandle = ContextMappingPropertyHandle->GetParentHandle();
|
||||||
|
const TSharedPtr<IPropertyHandleArray> ParentArrayHandle = ParentHandle->AsArray();
|
||||||
|
|
||||||
|
uint32 ContextCount;
|
||||||
|
ParentArrayHandle->GetNumElements(ContextCount);
|
||||||
|
if (ContextCount != 1) // Mustn't remove the only context.
|
||||||
|
{
|
||||||
|
ParentArrayHandle->DeleteItem(ContextMappingPropertyHandle->GetIndexInArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedRef<IDetailCustomization> FMirrorTableDetails::MakeInstance()
|
||||||
|
{
|
||||||
|
return MakeShareable(new FMirrorTableDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FMirrorTableDetails::CustomizeDetails(class IDetailLayoutBuilder& DetailBuilder)
|
||||||
|
{
|
||||||
|
DetailLayoutBuilder = &DetailBuilder;
|
||||||
|
|
||||||
|
IDetailCategoryBuilder& MirrorBonesDetailCategoryBuilder = DetailBuilder.EditCategory("MirrorBones", FText::GetEmpty(), ECategoryPriority::Important);
|
||||||
|
|
||||||
|
// Add Context Button
|
||||||
|
MirrorBonesDetailCategoryBuilder.AddCustomRow(LOCTEXT("AddBoneToMirrorTable", "Add Bone to Mirror Table"))
|
||||||
|
[
|
||||||
|
SNew(SBox)
|
||||||
|
.Padding(2.0f)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
.HAlign(HAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(SButton)
|
||||||
|
.Text(LOCTEXT("AddBoneToMirrorTable", "Add Bone to Mirror Table"))
|
||||||
|
.ToolTipText(LOCTEXT("AddMirrorBoneToolTip", "Adds a new Bone to the Mirror table."))
|
||||||
|
.OnClicked(FOnClicked::CreateSP(this, &FMirrorTableDetails::AddMirrorTableMapping_OnClicked))
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
// Individual Context Mappings
|
||||||
|
const TSharedPtr<IPropertyHandle> MirrorBonesPropertyHandle = DetailLayoutBuilder->GetProperty(GET_MEMBER_NAME_CHECKED(UMirrorTable, MirrorBones), UMirrorTable::StaticClass());
|
||||||
|
MirrorBonesPropertyHandle->MarkHiddenByCustomization();
|
||||||
|
|
||||||
|
const TSharedPtr<IPropertyHandleArray> MirrorBonesPropertyArrayHandle = MirrorBonesPropertyHandle->AsArray();
|
||||||
|
|
||||||
|
uint32 DialogueContextMappingCount;
|
||||||
|
MirrorBonesPropertyArrayHandle->GetNumElements(DialogueContextMappingCount);
|
||||||
|
|
||||||
|
FSimpleDelegate RebuildChildrenDelegate = FSimpleDelegate::CreateRaw(this, &FMirrorTableDetails::RebuildChildren);
|
||||||
|
//MirrorBonesPropertyArrayHandle->SetSetOnPropertyValueChanged(RebuildChildrenDelegate);
|
||||||
|
MirrorBonesPropertyArrayHandle->SetOnNumElementsChanged(RebuildChildrenDelegate);
|
||||||
|
|
||||||
|
for (uint32 j = 0; j < DialogueContextMappingCount; ++j)
|
||||||
|
{
|
||||||
|
const TSharedPtr<IPropertyHandle> ChildContextMappingPropertyHandle = MirrorBonesPropertyArrayHandle->GetElement(j);
|
||||||
|
|
||||||
|
const TSharedRef<FMirrorTableNodeBuilder> DialogueContextMapping = MakeShareable(new FMirrorTableNodeBuilder(DetailLayoutBuilder, ChildContextMappingPropertyHandle));
|
||||||
|
MirrorBonesDetailCategoryBuilder.AddCustomBuilder(DialogueContextMapping);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FMirrorTableDetails::RebuildChildren()
|
||||||
|
{
|
||||||
|
const TSharedPtr<IPropertyHandle> MirrorBonesPropertyHandle = DetailLayoutBuilder->GetProperty(GET_MEMBER_NAME_CHECKED(UMirrorTable, MirrorBones), UMirrorTable::StaticClass());
|
||||||
|
const TSharedPtr<IPropertyHandleArray> MirrorBonesPropertyArrayHandle = MirrorBonesPropertyHandle->AsArray();
|
||||||
|
|
||||||
|
DetailLayoutBuilder->ForceRefreshDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
FReply FMirrorTableDetails::AddMirrorTableMapping_OnClicked()
|
||||||
|
{
|
||||||
|
const TSharedPtr<IPropertyHandle> MirrorBonesPropertyHandle = DetailLayoutBuilder->GetProperty(GET_MEMBER_NAME_CHECKED(UMirrorTable, MirrorBones), UMirrorTable::StaticClass());
|
||||||
|
const TSharedPtr<IPropertyHandleArray> MirrorBonesPropertyArrayHandle = MirrorBonesPropertyHandle->AsArray();
|
||||||
|
MirrorBonesPropertyArrayHandle->AddItem();
|
||||||
|
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
|
@ -0,0 +1,288 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
#include "MirrorTableFromSkeletonDialog.h"
|
||||||
|
#include "MirrorAnimationSystemEditor.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#include "Widgets/SBoxPanel.h"
|
||||||
|
#include "Widgets/SWindow.h"
|
||||||
|
#include "Widgets/SViewport.h"
|
||||||
|
#include "Misc/FeedbackContext.h"
|
||||||
|
#include "Misc/ScopedSlowTask.h"
|
||||||
|
#include "Misc/MessageDialog.h"
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
#include "Misc/PackageName.h"
|
||||||
|
#include "Layout/WidgetPath.h"
|
||||||
|
#include "Framework/Application/SlateApplication.h"
|
||||||
|
#include "Widgets/Layout/SBorder.h"
|
||||||
|
#include "Widgets/Layout/SUniformGridPanel.h"
|
||||||
|
#include "Widgets/Input/SButton.h"
|
||||||
|
#include "Framework/Docking/TabManager.h"
|
||||||
|
#include "EditorStyleSet.h"
|
||||||
|
#include "CanvasItem.h"
|
||||||
|
#include "AssetRegistryModule.h"
|
||||||
|
#include "PropertyEditorModule.h"
|
||||||
|
#include "IContentBrowserSingleton.h"
|
||||||
|
#include "ContentBrowserModule.h"
|
||||||
|
#include "IAssetTools.h"
|
||||||
|
#include "AssetToolsModule.h"
|
||||||
|
#include "CanvasTypes.h"
|
||||||
|
|
||||||
|
#include "Factories/DataAssetFactory.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "MirrorAnimationSystemlEditor"
|
||||||
|
|
||||||
|
void SMirrorTableFromSkeletonDialog::Construct(const FArguments & InArgs, USkeleton * Skeleton)
|
||||||
|
{
|
||||||
|
SourceSkeleton = Skeleton;
|
||||||
|
|
||||||
|
MirrorTableSettings = NewObject<UMirrorTableFromSkeletonSettings>();
|
||||||
|
|
||||||
|
MirrorTableSettings->AddToRoot();
|
||||||
|
|
||||||
|
FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
||||||
|
FDetailsViewArgs DetailsViewArgs(/*bUpdateFromSelection=*/ false, /*bLockable=*/ false, /*bAllowSearch=*/ false, /*InNameAreaSettings=*/ FDetailsViewArgs::HideNameArea, /*bHideSelectionTip=*/ true);
|
||||||
|
MainPropertyView = EditModule.CreateDetailView(DetailsViewArgs);
|
||||||
|
MainPropertyView->SetObject(MirrorTableSettings);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ChildSlot
|
||||||
|
[
|
||||||
|
SNew(SBorder)
|
||||||
|
.BorderImage(FEditorStyle::GetBrush("DetailsView.CategoryTop"))
|
||||||
|
.Padding(FMargin(1.0f, 1.0f, 1.0f, 0.0f))
|
||||||
|
[
|
||||||
|
SNew(SVerticalBox)
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
.HAlign(HAlign_Fill)
|
||||||
|
.VAlign(VAlign_Fill)
|
||||||
|
[
|
||||||
|
SNew(SVerticalBox)
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
.Padding(1.0f)
|
||||||
|
.AutoHeight()
|
||||||
|
[
|
||||||
|
MainPropertyView.ToSharedRef()
|
||||||
|
]
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
.Padding(1.0f)
|
||||||
|
.HAlign(HAlign_Right)
|
||||||
|
.VAlign(VAlign_Bottom)
|
||||||
|
[
|
||||||
|
SNew(SUniformGridPanel)
|
||||||
|
.SlotPadding(1)
|
||||||
|
+ SUniformGridPanel::Slot(0, 0)
|
||||||
|
[
|
||||||
|
SNew(SButton)
|
||||||
|
.ButtonStyle(FEditorStyle::Get(), "FlatButton.Success")
|
||||||
|
.ForegroundColor(FLinearColor::White)
|
||||||
|
.Text(LOCTEXT("PaperExtractSpritesExtractButton", "Create"))
|
||||||
|
.OnClicked(this, &SMirrorTableFromSkeletonDialog::CreateClicked)
|
||||||
|
]
|
||||||
|
+ SUniformGridPanel::Slot(1, 0)
|
||||||
|
[
|
||||||
|
SNew(SButton)
|
||||||
|
.ButtonStyle(FEditorStyle::Get(), "FlatButton")
|
||||||
|
.ForegroundColor(FLinearColor::White)
|
||||||
|
.Text(LOCTEXT("PaperExtractSpritesCancelButton", "Cancel"))
|
||||||
|
.OnClicked(this, &SMirrorTableFromSkeletonDialog::CancelClicked)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
SMirrorTableFromSkeletonDialog::~SMirrorTableFromSkeletonDialog()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SMirrorTableFromSkeletonDialog::ShowWindow(USkeleton * SourceSkeleton)
|
||||||
|
{
|
||||||
|
|
||||||
|
const FText TitleText = NSLOCTEXT("MirrorAnimationSystem", "Create_Mirror_Table_From_Skeleton", "Create Mirror Table From Skeleton");
|
||||||
|
// Create the window to pick the class
|
||||||
|
TSharedRef<SWindow> MirrorAnimSequenceWindow = SNew(SWindow)
|
||||||
|
.Title(TitleText)
|
||||||
|
.SizingRule(ESizingRule::UserSized)
|
||||||
|
.ClientSize(FVector2D(550.f, 375.f))
|
||||||
|
.AutoCenter(EAutoCenter::PreferredWorkArea)
|
||||||
|
.SupportsMinimize(false);
|
||||||
|
|
||||||
|
TSharedRef<SMirrorTableFromSkeletonDialog> MirrorAnimSequenceDialog = SNew(SMirrorTableFromSkeletonDialog, SourceSkeleton);
|
||||||
|
|
||||||
|
MirrorAnimSequenceWindow->SetContent(MirrorAnimSequenceDialog);
|
||||||
|
TSharedPtr<SWindow> RootWindow = FGlobalTabmanager::Get()->GetRootWindow();
|
||||||
|
if (RootWindow.IsValid())
|
||||||
|
{
|
||||||
|
FSlateApplication::Get().AddWindowAsNativeChild(MirrorAnimSequenceWindow, RootWindow.ToSharedRef());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FSlateApplication::Get().AddWindow(MirrorAnimSequenceWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FReply SMirrorTableFromSkeletonDialog::CreateClicked()
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
(MirrorTableSettings->Substring_A != "none")
|
||||||
|
&& (!MirrorTableSettings->Substring_A.IsEmpty())
|
||||||
|
&& (MirrorTableSettings->Substring_B != "none")
|
||||||
|
&& (!MirrorTableSettings->Substring_B.IsEmpty())
|
||||||
|
)
|
||||||
|
{
|
||||||
|
CreateMirrorTableFromSkeletonFunction();
|
||||||
|
|
||||||
|
CloseContainingWindow();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FText DialogText = FText::AsCultureInvariant(FString(TEXT("Enter valid Substrings for correctly identifying twin bones")));
|
||||||
|
FMessageDialog::Open(EAppMsgType::Ok, DialogText);
|
||||||
|
}
|
||||||
|
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
|
||||||
|
FReply SMirrorTableFromSkeletonDialog::CancelClicked()
|
||||||
|
{
|
||||||
|
CloseContainingWindow();
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SMirrorTableFromSkeletonDialog::CloseContainingWindow()
|
||||||
|
{
|
||||||
|
TSharedPtr<SWindow> ContainingWindow = FSlateApplication::Get().FindWidgetWindow(AsShared());
|
||||||
|
if (ContainingWindow.IsValid())
|
||||||
|
{
|
||||||
|
ContainingWindow->RequestDestroyWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SMirrorTableFromSkeletonDialog::CreateMirrorTableFromSkeletonFunction()
|
||||||
|
{
|
||||||
|
|
||||||
|
FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools");
|
||||||
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
|
||||||
|
|
||||||
|
TArray<UObject*> ObjectsToSync;
|
||||||
|
|
||||||
|
UDataAssetFactory* DataFactory = NewObject<UDataAssetFactory>();
|
||||||
|
|
||||||
|
FString Name;
|
||||||
|
FString PackageName;
|
||||||
|
|
||||||
|
|
||||||
|
FString Suffix = TEXT("_MirrorTable");
|
||||||
|
|
||||||
|
//////////
|
||||||
|
|
||||||
|
|
||||||
|
AssetToolsModule.Get().CreateUniqueAssetName(SourceSkeleton->GetOutermost()->GetName(), Suffix, /*out*/ PackageName, /*out*/ Name);
|
||||||
|
const FString PackagePath = FPackageName::GetLongPackagePath(PackageName);
|
||||||
|
|
||||||
|
if (UObject* NewAsset = AssetToolsModule.Get().CreateAsset(Name, PackagePath, UMirrorTable::StaticClass(), DataFactory))
|
||||||
|
{
|
||||||
|
UMirrorTable* MirrorTable = Cast<UMirrorTable>(NewAsset);
|
||||||
|
SetMirrorTableFromSettings(MirrorTable);
|
||||||
|
|
||||||
|
ObjectsToSync.Add(NewAsset);
|
||||||
|
|
||||||
|
}
|
||||||
|
if (ObjectsToSync.Num() > 0)
|
||||||
|
{
|
||||||
|
ContentBrowserModule.Get().SyncBrowserToAssets(ObjectsToSync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SMirrorTableFromSkeletonDialog::SetMirrorTableFromSettings(UMirrorTable * MirrorTable)
|
||||||
|
{
|
||||||
|
if (MirrorTable != NULL)
|
||||||
|
{
|
||||||
|
int NumBones = SourceSkeleton->GetReferenceSkeleton().GetNum();
|
||||||
|
TArray <FString> CreatedBoneStrings;
|
||||||
|
for (int i = 0; i < NumBones; i++)
|
||||||
|
{
|
||||||
|
FString CurrentBoneString = SourceSkeleton->GetReferenceSkeleton().GetBoneName(i).ToString();
|
||||||
|
|
||||||
|
int ResultA = CurrentBoneString.Find(MirrorTableSettings->Substring_A, ESearchCase::IgnoreCase, ESearchDir::FromEnd, -1);
|
||||||
|
int ResultB = CurrentBoneString.Find(MirrorTableSettings->Substring_B, ESearchCase::IgnoreCase, ESearchDir::FromEnd, -1);
|
||||||
|
|
||||||
|
if ((ResultA != -1) || (ResultB != -1))
|
||||||
|
{
|
||||||
|
FString TwinBoneString;
|
||||||
|
bool ValidSymmetry = true;
|
||||||
|
if (ResultB != -1)
|
||||||
|
{
|
||||||
|
|
||||||
|
TwinBoneString = CurrentBoneString.Mid(0, ResultB);
|
||||||
|
TwinBoneString += MirrorTableSettings->Substring_A;
|
||||||
|
TwinBoneString += CurrentBoneString.Mid(ResultB + MirrorTableSettings->Substring_B.Len());
|
||||||
|
|
||||||
|
if ((!(abs(TwinBoneString.Len() - MirrorTableSettings->Substring_A.Len()) == abs(CurrentBoneString.Len() - MirrorTableSettings->Substring_B.Len()))) && ValidSymmetry)
|
||||||
|
{
|
||||||
|
ValidSymmetry = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
TwinBoneString = CurrentBoneString.Mid(0, ResultA);
|
||||||
|
TwinBoneString += MirrorTableSettings->Substring_B;
|
||||||
|
TwinBoneString += CurrentBoneString.Mid(ResultA + MirrorTableSettings->Substring_A.Len());
|
||||||
|
|
||||||
|
if ((!(abs(TwinBoneString.Len() - MirrorTableSettings->Substring_B.Len()) == abs(CurrentBoneString.Len() - MirrorTableSettings->Substring_A.Len()))) && ValidSymmetry)
|
||||||
|
{
|
||||||
|
ValidSymmetry = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ValidSymmetry)
|
||||||
|
{
|
||||||
|
|
||||||
|
FName TwinBoneName = FName(*TwinBoneString);
|
||||||
|
int TwinBoneIndex = SourceSkeleton ? SourceSkeleton->GetReferenceSkeleton().FindBoneIndex(FName(*TwinBoneString)) : INDEX_NONE;
|
||||||
|
if (TwinBoneIndex != INDEX_NONE)
|
||||||
|
{
|
||||||
|
if ((!CreatedBoneStrings.Contains(TwinBoneString)) && (!CreatedBoneStrings.Contains(CurrentBoneString)))
|
||||||
|
{
|
||||||
|
FMirrorBone NewMirrorBone = FMirrorBone();
|
||||||
|
NewMirrorBone.BoneName = FName(*CurrentBoneString);
|
||||||
|
NewMirrorBone.MirrorAxis = MirrorTableSettings->DefaultTwinMirrorAxis;
|
||||||
|
NewMirrorBone.FlipAxis = MirrorTableSettings->DefaultTwinFlipAxis;
|
||||||
|
NewMirrorBone.RotationOffset = MirrorTableSettings->DefaultTwinRotationOffset;
|
||||||
|
NewMirrorBone.IsTwinBone = true;
|
||||||
|
NewMirrorBone.TwinBoneName = FName(*TwinBoneString);
|
||||||
|
NewMirrorBone.MirrorTranslation = MirrorTableSettings->DefaultTwinMirrorTranslation;
|
||||||
|
|
||||||
|
MirrorTable->MirrorBones.Add(NewMirrorBone);
|
||||||
|
CreatedBoneStrings.Add(TwinBoneString);
|
||||||
|
CreatedBoneStrings.Add(CurrentBoneString);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!CreatedBoneStrings.Contains(CurrentBoneString))
|
||||||
|
{
|
||||||
|
/////
|
||||||
|
FMirrorBone NewMirrorBone = FMirrorBone();
|
||||||
|
NewMirrorBone.BoneName = FName(*CurrentBoneString);
|
||||||
|
NewMirrorBone.MirrorAxis = MirrorTableSettings->DefaultMirrorAxis;
|
||||||
|
NewMirrorBone.FlipAxis = MirrorTableSettings->DefaultFlipAxis;
|
||||||
|
NewMirrorBone.RotationOffset = MirrorTableSettings->DefaultRotationOffset;
|
||||||
|
NewMirrorBone.IsTwinBone = false;
|
||||||
|
|
||||||
|
MirrorTable->MirrorBones.Add(NewMirrorBone);
|
||||||
|
CreatedBoneStrings.Add(CurrentBoneString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
|
@ -0,0 +1,9 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
#include "MirrorTableFromSkeletonSettings.h"
|
||||||
|
#include "MirrorAnimationSystemEditor.h"
|
||||||
|
|
||||||
|
|
||||||
|
UMirrorTableFromSkeletonSettings::UMirrorTableFromSkeletonSettings(const FObjectInitializer& ObjectInitializer)
|
||||||
|
: Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "AnimGraphNode_Base.h"
|
||||||
|
#include "AnimNode_Mirror.h"
|
||||||
|
#include "AnimGraphNode_Mirror.generated.h"
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/*class that holds Editor version of the AnimGraph Node Mirror Pose, along its tittle, tooltip, Node Color, and the category of the node*/
|
||||||
|
UCLASS()
|
||||||
|
class MIRRORANIMATIONSYSTEMEDITOR_API UAnimGraphNode_Mirror : public UAnimGraphNode_Base
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
UPROPERTY(EditAnywhere, Category = Settings)
|
||||||
|
FAnimNode_Mirror Node;
|
||||||
|
|
||||||
|
//~ Begin UEdGraphNode Interface.
|
||||||
|
virtual FLinearColor GetNodeTitleColor() const override;
|
||||||
|
virtual FText GetTooltipText() const override;
|
||||||
|
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
|
||||||
|
//~ End UEdGraphNode Interface.
|
||||||
|
|
||||||
|
//~ Begin UAnimGraphNode_Base Interface
|
||||||
|
virtual FString GetNodeCategory() const override;
|
||||||
|
//~ End UAnimGraphNode_Base Interface
|
||||||
|
|
||||||
|
UAnimGraphNode_Mirror(const FObjectInitializer& ObjectInitializer);
|
||||||
|
};
|
|
@ -0,0 +1,51 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
//#include "AnimGraphNode_Base.h"
|
||||||
|
#include "AnimGraphNode_SkeletalControlBase.h"
|
||||||
|
#include "AnimNode_MirrorCS.h"
|
||||||
|
#include "AnimGraphNode_MirrorCS.generated.h"
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/*class that holds Editor version of the AnimGraph Node Mirror Pose, along its tittle, tooltip, Node Color, and the category of the node*/
|
||||||
|
UCLASS()
|
||||||
|
class MIRRORANIMATIONSYSTEMEDITOR_API UAnimGraphNode_MirrorCS : public UAnimGraphNode_SkeletalControlBase
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, Category = Settings)
|
||||||
|
FAnimNode_MirrorCS Node;
|
||||||
|
/*
|
||||||
|
//~ Begin UEdGraphNode Interface.
|
||||||
|
virtual FLinearColor GetNodeTitleColor() const override;
|
||||||
|
virtual FText GetTooltipText() const override;
|
||||||
|
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
|
||||||
|
//~ End UEdGraphNode Interface.
|
||||||
|
|
||||||
|
//~ Begin UAnimGraphNode_Base Interface
|
||||||
|
virtual FString GetNodeCategory() const override;
|
||||||
|
//~ End UAnimGraphNode_Base Interface
|
||||||
|
|
||||||
|
UAnimGraphNode_MirrorCS(const FObjectInitializer& ObjectInitializer);
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
UAnimGraphNode_MirrorCS(const FObjectInitializer& ObjectInitializer);
|
||||||
|
|
||||||
|
// UEdGraphNode interface
|
||||||
|
virtual FLinearColor GetNodeTitleColor() const override;
|
||||||
|
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
|
||||||
|
virtual FText GetTooltipText() const override;
|
||||||
|
// validate if this is within VehicleAnimInstance
|
||||||
|
virtual void ValidateAnimNodePostCompile(class FCompilerResultsLog& MessageLog, class UAnimBlueprintGeneratedClass* CompiledClass, int32 CompiledNodeIndex) override;
|
||||||
|
virtual bool IsCompatibleWithGraph(const UEdGraph* TargetGraph) const override;
|
||||||
|
// End of UEdGraphNode interface
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// UAnimGraphNode_SkeletalControlBase interface
|
||||||
|
virtual FText GetControllerDescription() const override;
|
||||||
|
virtual const FAnimNode_SkeletalControlBase* GetNode() const override { return &Node; }
|
||||||
|
// End of UAnimGraphNode_SkeletalControlBase interface
|
||||||
|
};
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Toolkits/IToolkitHost.h"
|
||||||
|
#include "AssetTypeActions_Base.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/*AssetTypeActions class for the Mirror table so it can have a distinctive color and opens the Mirror Table class's custom asset editor*/
|
||||||
|
class FAssetTypeActions_MirrorTable : public FAssetTypeActions_Base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FAssetTypeActions_MirrorTable(EAssetTypeCategories::Type InAssetCategory);
|
||||||
|
|
||||||
|
// IAssetTypeActions interface
|
||||||
|
virtual FText GetName() const override;
|
||||||
|
virtual FColor GetTypeColor() const override;
|
||||||
|
virtual UClass* GetSupportedClass() const override;
|
||||||
|
virtual void OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<class IToolkitHost> EditWithinLevelEditor = TSharedPtr<IToolkitHost>()) override;
|
||||||
|
virtual uint32 GetCategories() override;
|
||||||
|
// End of IAssetTypeActions interface
|
||||||
|
|
||||||
|
private:
|
||||||
|
EAssetTypeCategories::Type MyAssetCategory;
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/*Class that adds the "Mirror AnimAsset" and the "Mirror Table From Skeleton" tool's buttons
|
||||||
|
when an Animation Asset or a Skeleton asset is right clicked*/
|
||||||
|
class FContentBrowserTools
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static void InstallHooks();
|
||||||
|
static void RemoveHooks();
|
||||||
|
};
|
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Input/Reply.h"
|
||||||
|
#include "Widgets/DeclarativeSyntaxSupport.h"
|
||||||
|
#include "Widgets/SCompoundWidget.h"
|
||||||
|
#include "Engine/Texture2D.h"
|
||||||
|
#include "IDetailsView.h"
|
||||||
|
#include "Animation/AnimSequence.h"
|
||||||
|
#include "MirrorAnimAssetSettings.h"
|
||||||
|
|
||||||
|
/*Dialog used to choose the Mirror Table,
|
||||||
|
it then Mirrors the animation according to the data contained in the Mirror Table*/
|
||||||
|
class MIRRORANIMATIONSYSTEMEDITOR_API SMirrorAnimAssetDialog : public SCompoundWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
SLATE_BEGIN_ARGS(SMirrorAnimAssetDialog) {}
|
||||||
|
SLATE_END_ARGS()
|
||||||
|
|
||||||
|
void Construct(const FArguments& InArgs, UAnimSequence* AnimSequence);
|
||||||
|
|
||||||
|
~SMirrorAnimAssetDialog();
|
||||||
|
|
||||||
|
static bool ShowWindow(UAnimSequence* SourceAnimSequence);
|
||||||
|
|
||||||
|
static void CreateMirrorSequenceFromAnimSequence(UAnimSequence* MirrorSequence, UMirrorTable* MirrorTable);
|
||||||
|
private:
|
||||||
|
|
||||||
|
UAnimSequence* SourceAnimSequence;
|
||||||
|
|
||||||
|
class UMirrorAnimAssetSettings* MirrorAnimAssetSettings;
|
||||||
|
|
||||||
|
bool ValidateCSMirrorData();
|
||||||
|
|
||||||
|
FReply MirrorClicked();
|
||||||
|
|
||||||
|
FReply CancelClicked();
|
||||||
|
|
||||||
|
void CloseContainingWindow();
|
||||||
|
|
||||||
|
void CreateMirroredAnimSequences();
|
||||||
|
|
||||||
|
TSharedPtr<class IDetailsView> MainPropertyView;
|
||||||
|
};
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "UObject/ObjectMacros.h"
|
||||||
|
#include "UObject/UObjectGlobals.h"
|
||||||
|
#include "UObject/Object.h"
|
||||||
|
#include "MirrorTable.h"
|
||||||
|
#include "MirrorAnimAssetSettings.generated.h"
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/*class used in Mirror AnimAsset Dialog for choosing the Mirror Table asset in a details view*/
|
||||||
|
UCLASS()
|
||||||
|
class MIRRORANIMATIONSYSTEMEDITOR_API UMirrorAnimAssetSettings : public UObject
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
public:
|
||||||
|
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = ComponentSpaceMirrorSettings)
|
||||||
|
TEnumAsByte<EAxis::Type> MirrorAxis = EAxis::None;
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = ComponentSpaceMirrorSettings)
|
||||||
|
bool CompletlySymmetrical = false;
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = ComponentSpaceMirrorSettings)
|
||||||
|
FString Substring_A = "None";
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = ComponentSpaceMirrorSettings)
|
||||||
|
FString Substring_B = "None";
|
||||||
|
|
||||||
|
UPROPERTY(Category = BoneSpaceMirrorSettings, EditAnywhere, meta = (HideAlphaChannel))
|
||||||
|
UMirrorTable* MirrorTable;
|
||||||
|
|
||||||
|
UMirrorAnimAssetSettings(const FObjectInitializer& ObjectInitializer);
|
||||||
|
};
|
|
@ -0,0 +1,55 @@
|
||||||
|
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "Toolkits/AssetEditorToolkit.h"
|
||||||
|
#include "Modules/ModuleInterface.h"
|
||||||
|
|
||||||
|
#include "AssetTypeCategories.h"
|
||||||
|
#include "Engine/Texture2D.h"
|
||||||
|
#include "Editor.h"
|
||||||
|
#include "EditorModeRegistry.h"
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
#include "UObject/UObjectHash.h"
|
||||||
|
#include "UObject/UObjectIterator.h"
|
||||||
|
#include "ThumbnailRendering/ThumbnailManager.h"
|
||||||
|
|
||||||
|
#include "AssetToolsModule.h"
|
||||||
|
#include "PropertyEditorModule.h"
|
||||||
|
|
||||||
|
#include "IAssetTypeActions.h"
|
||||||
|
|
||||||
|
#include "ISettingsModule.h"
|
||||||
|
|
||||||
|
#include "PropertyEditorDelegates.h"
|
||||||
|
|
||||||
|
class FToolBarBuilder;
|
||||||
|
class FMenuBuilder;
|
||||||
|
|
||||||
|
|
||||||
|
class FMirrorAnimationSystemEditorModule : public IModuleInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FMirrorAnimationSystemEditorModule()
|
||||||
|
:MirrorTableAssetCategoryBit(EAssetTypeCategories::Misc)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
/** IModuleInterface implementation */
|
||||||
|
virtual void StartupModule() override;
|
||||||
|
virtual void ShutdownModule() override;
|
||||||
|
private:
|
||||||
|
|
||||||
|
|
||||||
|
void RegisterAssetTypeAction(IAssetTools& AssetTools, TSharedRef<IAssetTypeActions> Action);
|
||||||
|
void RegisterCustomClassLayout(FName ClassName, FOnGetDetailCustomizationInstance DetailLayoutDelegate);
|
||||||
|
|
||||||
|
|
||||||
|
TSet< FName > RegisteredClassNames;
|
||||||
|
EAssetTypeCategories::Type MirrorTableAssetCategoryBit;
|
||||||
|
TArray< TSharedPtr<IAssetTypeActions> > CreatedAssetTypeActions;
|
||||||
|
|
||||||
|
};
|
|
@ -0,0 +1,29 @@
|
||||||
|
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Styling/SlateStyle.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/*class containing the SlateStyle for the Mirror Animation System,
|
||||||
|
Adds the ability to use the Mirror Animation System's icons stored in the "resources" folder inside the plugin's directory*/
|
||||||
|
|
||||||
|
class FMirrorAnimationSystemStyle
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static void Initialize();
|
||||||
|
|
||||||
|
static void Shutdown();
|
||||||
|
|
||||||
|
static FName GetStyleSetName();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static TSharedRef< class FSlateStyleSet > Create();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
static TSharedPtr< class FSlateStyleSet > StyleInstance;
|
||||||
|
};
|
|
@ -0,0 +1,73 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Input/Reply.h"
|
||||||
|
#include "IDetailCustomization.h"
|
||||||
|
#include "IDetailCustomNodeBuilder.h"
|
||||||
|
|
||||||
|
class FDetailWidgetRow;
|
||||||
|
class IDetailChildrenBuilder;
|
||||||
|
class IDetailLayoutBuilder;
|
||||||
|
class IPropertyHandle;
|
||||||
|
class SEditableTextBox;
|
||||||
|
/*class containing the custom layout for the Mirror Table class.
|
||||||
|
It merely displays the data to setup inside the Mirror Table in a more user friendly way,
|
||||||
|
where it is easy to identify the bone's name and whether it is a twin bone or not*/
|
||||||
|
class FMirrorTableNodeBuilder : public IDetailCustomNodeBuilder, public TSharedFromThis<FMirrorTableNodeBuilder>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FMirrorTableNodeBuilder(IDetailLayoutBuilder* InDetailLayoutBuilder, const TSharedPtr<IPropertyHandle>& InPropertyHandle);
|
||||||
|
|
||||||
|
/** IDetailCustomNodeBuilder interface */
|
||||||
|
virtual void SetOnRebuildChildren(FSimpleDelegate InOnRebuildChildren) override { OnRebuildChildren = InOnRebuildChildren; }
|
||||||
|
virtual bool RequiresTick() const override { return true; }
|
||||||
|
virtual void Tick(float DeltaTime) override;
|
||||||
|
virtual void GenerateHeaderRowContent(FDetailWidgetRow& NodeRow) override;
|
||||||
|
virtual void GenerateChildContent(IDetailChildrenBuilder& ChildrenBuilder) override;
|
||||||
|
virtual bool InitiallyCollapsed() const override { return true; }
|
||||||
|
virtual FName GetName() const override { return NAME_None; }
|
||||||
|
virtual FText GetText() const;
|
||||||
|
private:
|
||||||
|
void RemoveContextButton_OnClick();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/** Called to rebuild the children of the detail tree */
|
||||||
|
FSimpleDelegate OnRebuildChildren;
|
||||||
|
|
||||||
|
/** Associated detail layout builder */
|
||||||
|
IDetailLayoutBuilder* DetailLayoutBuilder;
|
||||||
|
|
||||||
|
/** Property handle to associated context mapping */
|
||||||
|
TSharedPtr<IPropertyHandle> ContextMappingPropertyHandle;
|
||||||
|
|
||||||
|
/** Property handle to the localization key format property within this context mapping */
|
||||||
|
TSharedPtr<IPropertyHandle> BoneNameHandle;
|
||||||
|
|
||||||
|
|
||||||
|
/** Property handle to the localization key format property within this context mapping */
|
||||||
|
TSharedPtr<IPropertyHandle> TwinBoneNameHandle;
|
||||||
|
|
||||||
|
/** Property handle to the localization key format property within this context mapping */
|
||||||
|
TSharedPtr<IPropertyHandle> IsTwinBoneHandle;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FMirrorTableDetails : public IDetailCustomization
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** Makes a new instance of this detail layout class for a specific detail view requesting it */
|
||||||
|
static TSharedRef<IDetailCustomization> MakeInstance();
|
||||||
|
|
||||||
|
/** ILayoutDetails interface */
|
||||||
|
virtual void CustomizeDetails(class IDetailLayoutBuilder& DetailBuilder) override;
|
||||||
|
|
||||||
|
void RebuildChildren();
|
||||||
|
private:
|
||||||
|
FReply AddMirrorTableMapping_OnClicked();
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
/** Associated detail layout builder */
|
||||||
|
IDetailLayoutBuilder* DetailLayoutBuilder;
|
||||||
|
};
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Input/Reply.h"
|
||||||
|
#include "Widgets/DeclarativeSyntaxSupport.h"
|
||||||
|
#include "Widgets/SCompoundWidget.h"
|
||||||
|
#include "Engine/Texture2D.h"
|
||||||
|
#include "IDetailsView.h"
|
||||||
|
#include "Animation/AnimSequence.h"
|
||||||
|
#include "Animation/Skeleton.h"
|
||||||
|
#include "MirrorTable.h"
|
||||||
|
|
||||||
|
#include "MirrorTableFromSkeletonSettings.h"
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class USkeleton;
|
||||||
|
/*SCompound widget class containing the functionality for creating a new Mirror Table asset based on a Skeleton Asset,
|
||||||
|
the dialog serves to input the Prefixes or suffixes used in the skeleton to identify whether a bone is a twin bone and if so find it's corresponding twin.
|
||||||
|
At the same time it allows to setup quick initial values for all the bones that are being added to the new Mirror Table*/
|
||||||
|
class MIRRORANIMATIONSYSTEMEDITOR_API SMirrorTableFromSkeletonDialog : public SCompoundWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
SLATE_BEGIN_ARGS(SMirrorTableFromSkeletonDialog) {}
|
||||||
|
SLATE_END_ARGS()
|
||||||
|
|
||||||
|
void Construct(const FArguments& InArgs, USkeleton* Skeleton);
|
||||||
|
|
||||||
|
~SMirrorTableFromSkeletonDialog();
|
||||||
|
|
||||||
|
static bool ShowWindow(USkeleton* SourceSkeleton);
|
||||||
|
|
||||||
|
void SetMirrorTableFromSettings(UMirrorTable * MirrorTable);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
USkeleton* SourceSkeleton;
|
||||||
|
|
||||||
|
class UMirrorTableFromSkeletonSettings* MirrorTableSettings;
|
||||||
|
|
||||||
|
FReply CreateClicked();
|
||||||
|
|
||||||
|
FReply CancelClicked();
|
||||||
|
|
||||||
|
void CloseContainingWindow();
|
||||||
|
|
||||||
|
void CreateMirrorTableFromSkeletonFunction();
|
||||||
|
|
||||||
|
TSharedPtr<class IDetailsView> MainPropertyView;
|
||||||
|
};
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "UObject/NoExportTypes.h"
|
||||||
|
#include "MirrorTableFromSkeletonSettings.generated.h"
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/*class used in Mirror Table from Skeleton Dialog for creating the details view panel
|
||||||
|
and settting up the Suffix or preffix for twin identification as well as
|
||||||
|
defining the default values for the bones that will be added to the new Mirror Table*/
|
||||||
|
UCLASS()
|
||||||
|
class MIRRORANIMATIONSYSTEMEDITOR_API UMirrorTableFromSkeletonSettings : public UObject
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
UPROPERTY(Category = SearchSettings, EditAnywhere, meta = (HideAlphaChannel))
|
||||||
|
FString Substring_A = "none";
|
||||||
|
UPROPERTY(Category = SearchSettings, EditAnywhere, meta = (HideAlphaChannel))
|
||||||
|
FString Substring_B = "none";
|
||||||
|
|
||||||
|
UPROPERTY(Category = NonTwinBonesSettings, EditAnywhere, meta = (HideAlphaChannel))
|
||||||
|
TEnumAsByte<EAxis::Type> DefaultMirrorAxis = EAxis::None;
|
||||||
|
UPROPERTY(Category = NonTwinBonesSettings, EditAnywhere, meta = (HideAlphaChannel))
|
||||||
|
TEnumAsByte<EAxis::Type> DefaultFlipAxis = EAxis::None;
|
||||||
|
UPROPERTY(Category = NonTwinBonesSettings, EditAnywhere, meta = (HideAlphaChannel))
|
||||||
|
FRotator DefaultRotationOffset = FRotator(0, 0, 0);
|
||||||
|
|
||||||
|
UPROPERTY(Category = TwinBonesSettings, EditAnywhere, meta = (HideAlphaChannel))
|
||||||
|
TEnumAsByte<EAxis::Type> DefaultTwinMirrorAxis = EAxis::None;
|
||||||
|
UPROPERTY(Category = TwinBonesSettings, EditAnywhere, meta = (HideAlphaChannel))
|
||||||
|
TEnumAsByte<EAxis::Type> DefaultTwinFlipAxis = EAxis::None;
|
||||||
|
UPROPERTY(Category = TwinBonesSettings, EditAnywhere, meta = (HideAlphaChannel))
|
||||||
|
FRotator DefaultTwinRotationOffset = FRotator(0, 0, 0);
|
||||||
|
UPROPERTY(Category = TwinBonesSettings, EditAnywhere, meta = (HideAlphaChannel))
|
||||||
|
bool DefaultTwinMirrorTranslation = false;
|
||||||
|
|
||||||
|
|
||||||
|
UMirrorTableFromSkeletonSettings(const FObjectInitializer& ObjectInitializer);
|
||||||
|
};
|
Loading…
Reference in a new issue