Commit of code

This commit is contained in:
Rexocrates 2021-08-07 11:38:59 +02:00
parent 9d0ffec76a
commit 74407403fe
48 changed files with 4094 additions and 0 deletions

View 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" ]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -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 ...
}
);
}
}

View file

@ -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);
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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);
}
}
}

View file

@ -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)

View file

@ -0,0 +1,8 @@
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
#include "MirrorTable.h"
#include "MirrorAnimationSystem.h"

View file

@ -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
};

View file

@ -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;
};

View file

@ -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);
};

View file

@ -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;
};

View file

@ -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);
};

View file

@ -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;
};

View file

@ -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;
};

View file

@ -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 ...
}
);
}
}

View file

@ -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

View file

@ -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)

View file

@ -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
};

View file

@ -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;
};

View file

@ -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 ...
}
);
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,9 @@
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
#include "MirrorAnimAssetSettings.h"
#include "MirrorAnimationSystemEditor.h"
UMirrorAnimAssetSettings::UMirrorAnimAssetSettings(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,9 @@
// Copyright 2017-2021 Rexocrates. All Rights Reserved.
#include "MirrorTableFromSkeletonSettings.h"
#include "MirrorAnimationSystemEditor.h"
UMirrorTableFromSkeletonSettings::UMirrorTableFromSkeletonSettings(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}

View file

@ -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);
};

View file

@ -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
};

View file

@ -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;
};

View file

@ -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();
};

View file

@ -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;
};

View file

@ -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);
};

View file

@ -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;
};

View file

@ -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;
};

View file

@ -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;
};

View file

@ -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;
};

View file

@ -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);
};