diff --git a/MirrorAnimationSystem/MirrorAnimationSystem.uplugin b/MirrorAnimationSystem/MirrorAnimationSystem.uplugin new file mode 100644 index 0000000..dddc8f3 --- /dev/null +++ b/MirrorAnimationSystem/MirrorAnimationSystem.uplugin @@ -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" ] + } + ] +} \ No newline at end of file diff --git a/MirrorAnimationSystem/Resources/CreateMirrorTable_Icon.png b/MirrorAnimationSystem/Resources/CreateMirrorTable_Icon.png new file mode 100644 index 0000000..c231a93 Binary files /dev/null and b/MirrorAnimationSystem/Resources/CreateMirrorTable_Icon.png differ diff --git a/MirrorAnimationSystem/Resources/Icon128.png b/MirrorAnimationSystem/Resources/Icon128.png new file mode 100644 index 0000000..1ac3054 Binary files /dev/null and b/MirrorAnimationSystem/Resources/Icon128.png differ diff --git a/MirrorAnimationSystem/Resources/MirrorAnimAsset_Icon.png b/MirrorAnimationSystem/Resources/MirrorAnimAsset_Icon.png new file mode 100644 index 0000000..bcf5a07 Binary files /dev/null and b/MirrorAnimationSystem/Resources/MirrorAnimAsset_Icon.png differ diff --git a/MirrorAnimationSystem/Resources/MirrorTable_ClassIcon.png b/MirrorAnimationSystem/Resources/MirrorTable_ClassIcon.png new file mode 100644 index 0000000..bb2f063 Binary files /dev/null and b/MirrorAnimationSystem/Resources/MirrorTable_ClassIcon.png differ diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/MirrorAnimationSystem.Build.cs b/MirrorAnimationSystem/Source/MirrorAnimationSystem/MirrorAnimationSystem.Build.cs new file mode 100644 index 0000000..2c7c3d2 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/MirrorAnimationSystem.Build.cs @@ -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 ... + } + ); + } +} diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/AnimNode_Mirror.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/AnimNode_Mirror.cpp new file mode 100644 index 0000000..c68338f --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/AnimNode_Mirror.cpp @@ -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); +} \ No newline at end of file diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/AnimNode_MirrorCS.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/AnimNode_MirrorCS.cpp new file mode 100644 index 0000000..2f82737 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/AnimNode_MirrorCS.cpp @@ -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& OutBoneTransforms) +{ + check(OutBoneTransforms.Num() == 0); + + if(NonTwinIDs.Num()) + { + const bool DeltaStep = !CompletlySymmetrical; + + TArray 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 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); + + } + } +} diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/ExtCharacter.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/ExtCharacter.cpp new file mode 100644 index 0000000..bb3b784 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/ExtCharacter.cpp @@ -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(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(AExtCharacter::GetCharacterMovement()); + if (ChMovComp != NULL) + { + ChMovComp->MirrorRootMotion = Mirror; + } +} + +bool AExtCharacter::GetMirrorRootMotion() +{ + UExtCharacterMovementComponent * ChMovComp = Cast(AExtCharacter::GetCharacterMovement()); + if (ChMovComp != NULL) + { + return ChMovComp->MirrorRootMotion; + } + return false; +} + +void AExtCharacter::SetRootMotionMirrorAndFlipAxis(TEnumAsByte MirrorAxis, TEnumAsByte FlipAxis) +{ + UExtCharacterMovementComponent * ChMovComp = Cast(AExtCharacter::GetCharacterMovement()); + if (ChMovComp != NULL) + { + ChMovComp->MirrorAxis = MirrorAxis; + ChMovComp->FlipAxis = FlipAxis; + return; + } +} + +void AExtCharacter::GetRootMotionMirrorAndFlipAxis(TEnumAsByte& MirrorAxis, TEnumAsByte& FlipAxis) +{ + UExtCharacterMovementComponent * ChMovComp = Cast(AExtCharacter::GetCharacterMovement()); + if (ChMovComp != NULL) + { + MirrorAxis = ChMovComp->MirrorAxis; + FlipAxis = ChMovComp->FlipAxis; + return; + } +} + diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/ExtCharacterMovementComponent.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/ExtCharacterMovementComponent.cpp new file mode 100644 index 0000000..591f538 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/ExtCharacterMovementComponent.cpp @@ -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; +} diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/MASUtils.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/MASUtils.cpp new file mode 100644 index 0000000..011e452 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/MASUtils.cpp @@ -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& TwinPairs, TArray& NonTwinIDs, TArray& NonTwinFlipAxis) +{ + TwinPairs.Empty(); + NonTwinIDs.Empty(); + NonTwinFlipAxis.Empty(); + + const int32 NumBones = RefSkeleton.GetNum(); + TArray 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); + + } + } +} diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/MirrorAnimationSystem.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/MirrorAnimationSystem.cpp new file mode 100644 index 0000000..2b53d81 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/MirrorAnimationSystem.cpp @@ -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) \ No newline at end of file diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/MirrorTable.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/MirrorTable.cpp new file mode 100644 index 0000000..d0ed2cc --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Private/MirrorTable.cpp @@ -0,0 +1,8 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. +#include "MirrorTable.h" +#include "MirrorAnimationSystem.h" + + + + + diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/AnimNode_Mirror.h b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/AnimNode_Mirror.h new file mode 100644 index 0000000..6804300 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/AnimNode_Mirror.h @@ -0,0 +1,34 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. + +#pragma once +#include "Animation/AnimNodeBase.h" +#include "Animation/AnimInstanceProxy.h" +#include "MirrorTable.h" +#include "AnimNode_Mirror.generated.h" +/** +* +*/ +/*Runtime code for the AnimGraph Node Mirror Pose, +takes in a pose in local space and Mirrors each bone according to a Mirror Table*/ +USTRUCT(BlueprintInternalUseOnly) +struct MIRRORANIMATIONSYSTEM_API FAnimNode_Mirror : public FAnimNode_Base +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Links) + FPoseLink BasePose; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MirrorTable, meta = (PinShownByDefault)) + UMirrorTable* MirrorTable; +public: + + FAnimNode_Mirror(); + + // FAnimNode_Base interface + virtual void Initialize_AnyThread(const FAnimationInitializeContext& Context) override; + virtual void CacheBones_AnyThread(const FAnimationCacheBonesContext& Context) override; + virtual void Update_AnyThread(const FAnimationUpdateContext& Context) override; + virtual void Evaluate_AnyThread(FPoseContext & Output) override; + virtual void GatherDebugData(FNodeDebugData& DebugData) override; + // End of FAnimNode_Base interface +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/AnimNode_MirrorCS.h b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/AnimNode_MirrorCS.h new file mode 100644 index 0000000..960d3d6 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/AnimNode_MirrorCS.h @@ -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 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& 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 TwinPairs; + TArray NonTwinIDs; + TArray NonTwinFlipAxis; + int32 LastBoneNum = 0; +}; + diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/ExtCharacter.h b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/ExtCharacter.h new file mode 100644 index 0000000..da390dc --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/ExtCharacter.h @@ -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 MirrorAxis, TEnumAsByte FlipAxis); + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Mirror Animation") + virtual void GetRootMotionMirrorAndFlipAxis(TEnumAsByte & MirrorAxis, TEnumAsByte & FlipAxis); +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/ExtCharacterMovementComponent.h b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/ExtCharacterMovementComponent.h new file mode 100644 index 0000000..aab73ad --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/ExtCharacterMovementComponent.h @@ -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 MirrorAxis = EAxis::None; + + UPROPERTY(Category = "Character Movement: Root Motion", EditAnywhere, BlueprintReadWrite) + TEnumAsByte FlipAxis = EAxis::None; +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/MASUtils.h b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/MASUtils.h new file mode 100644 index 0000000..0d3934a --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/MASUtils.h @@ -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& TwinPairs, TArray& NonTwinIDs, TArray& NonTwinFlipAxis); +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/MirrorAnimationSystem.h b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/MirrorAnimationSystem.h new file mode 100644 index 0000000..877c712 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/MirrorAnimationSystem.h @@ -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; +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/MirrorTable.h b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/MirrorTable.h new file mode 100644 index 0000000..5725d4e --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystem/Public/MirrorTable.h @@ -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 MirrorAxis; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MirrorAnimation") + TEnumAsByte 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 MirrorBones; + +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/MirrorAnimationSystemDev.Build.cs b/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/MirrorAnimationSystemDev.Build.cs new file mode 100644 index 0000000..2143f52 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/MirrorAnimationSystemDev.Build.cs @@ -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 ... + } + ); + } +} diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Private/MASFunctionLibrary.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Private/MASFunctionLibrary.cpp new file mode 100644 index 0000000..5866750 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Private/MASFunctionLibrary.cpp @@ -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 SourceAnims, const UMirrorTable* MirrorTable, TArray & OutNewAnims) +{ + if (SourceAnims.Num() == 0) return; + if (MirrorTable == NULL) return; + +#if WITH_EDITOR + FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("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(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({ MirrorAnimSequence })); }); + Info.Hyperlink = FSimpleDelegate::CreateLambda([=]() { GEditor->GetEditorSubsystem()->OpenEditorForAssets(TArray({ MirrorAnimSequence })); }); + + Info.HyperlinkText = FText::Format(LOCTEXT("OpenNewAnimationHyperlink", "Open {0}"), FText::FromString(MirrorAnimSequence->GetName())); + TSharedPtr Notification = FSlateNotificationManager::Get().AddNotification(Info); + if (Notification.IsValid()) + { + Notification->SetCompletionState(SNotificationItem::CS_Success); + } + } + } + } +#endif +} + +MIRRORANIMATIONSYSTEMDEV_API void UMASFunctionLibrary::BulkMirror_CS_EditorOnly( + const TArray SourceAnims, + const TEnumAsByte MirrorAxis, const FString Substring_A, const FString Substring_B, const bool Symmetrical, TArray& OutNewAnims) +{ + if (SourceAnims.Num() == 0) return; + if (MirrorAxis == EAxis::None) return; + +#if WITH_EDITOR + FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("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(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({ MirrorAnimSequence })); }); + Info.Hyperlink = FSimpleDelegate::CreateLambda([=]() { GEditor->GetEditorSubsystem()->OpenEditorForAssets(TArray({ MirrorAnimSequence })); }); + + Info.HyperlinkText = FText::Format(LOCTEXT("OpenNewAnimationHyperlink", "Open {0}"), FText::FromString(MirrorAnimSequence->GetName())); + TSharedPtr 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 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 MirrorPosKeys; + TArray MirrorRotKeys; + TArray MirrorScaleKeys; + + TArray TwinMirrorPosKeys; + TArray TwinMirrorRotKeys; + TArray 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 MirrorPosKeys; + TArray MirrorRotKeys; + TArray 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 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 Already; Already.SetNumZeroed(Skeleton->GetBoneTree().Num()); + + TArray TwinPairs; + TArray NonTwinIDs; + TArray 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 BoneTracks; + + for (int32 i = 0; i < RefSkeleton.GetNum(); i++) + { + BoneTracks.Add(i, FRawAnimSequenceTrack()); + } + + for (int32 j = 0; j < NumFrames; j++) + { + TArray 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 \ No newline at end of file diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Private/MirrorAnimationSystemDev.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Private/MirrorAnimationSystemDev.cpp new file mode 100644 index 0000000..92f6299 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Private/MirrorAnimationSystemDev.cpp @@ -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) \ No newline at end of file diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Public/MASFunctionLibrary.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Public/MASFunctionLibrary.h new file mode 100644 index 0000000..8306487 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Public/MASFunctionLibrary.h @@ -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 Anims, const UMirrorTable* MirrorTable, TArray & OutNewAnims); + UFUNCTION(BlueprintCallable, Category = "Mirror Animation", meta = (DevelopmentOnly)) + static MIRRORANIMATIONSYSTEMDEV_API void BulkMirror_CS_EditorOnly(const TArray Anims, + const TEnumAsByte MirrorAxis, const FString Substring_A, const FString Substring_B, const bool Symmetrical, TArray & OutNewAnims); +#if WITH_EDITOR + static MIRRORANIMATIONSYSTEMDEV_API void CreateMirrorSequenceFromAnimSequence(UAnimSequence* MirrorSequence, const UMirrorTable* MirrorTable); + static MIRRORANIMATIONSYSTEMDEV_API void CreateMirrorSequenceFromAnimSequence_CS(UAnimSequence* MirrorSequence, + const TEnumAsByte MirrorAxis, const FString Substring_A, const FString Substring_B, const bool Symmetrical); +#endif +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Public/MirrorAnimationSystemDev.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Public/MirrorAnimationSystemDev.h new file mode 100644 index 0000000..2a5fb52 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemDev/Public/MirrorAnimationSystemDev.h @@ -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; +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/MirrorAnimationSystemEditor.Build.cs b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/MirrorAnimationSystemEditor.Build.cs new file mode 100644 index 0000000..137841b --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/MirrorAnimationSystemEditor.Build.cs @@ -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 ... + } + ); + } +} diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/AnimGraphNode_Mirror.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/AnimGraphNode_Mirror.cpp new file mode 100644 index 0000000..a2690af --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/AnimGraphNode_Mirror.cpp @@ -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 diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/AnimGraphNode_MirrorCS.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/AnimGraphNode_MirrorCS.cpp new file mode 100644 index 0000000..1457082 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/AnimGraphNode_MirrorCS.cpp @@ -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 diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/AssetTypeActions_MirrorTable.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/AssetTypeActions_MirrorTable.cpp new file mode 100644 index 0000000..c0c1ebe --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/AssetTypeActions_MirrorTable.cpp @@ -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& InObjects, TSharedPtr EditWithinLevelEditor) +{ + const EToolkitMode::Type Mode = EditWithinLevelEditor.IsValid() ? EToolkitMode::WorldCentric : EToolkitMode::Standalone; + + for (auto ObjIt = InObjects.CreateConstIterator(); ObjIt; ++ObjIt) + { + if (UMirrorTable* MirrorTable = Cast(*ObjIt)) + { + FSimpleAssetEditor::CreateEditor(Mode, Mode == EToolkitMode::WorldCentric ? EditWithinLevelEditor : TSharedPtr(), MirrorTable); + /* + TSharedRef NewFlipbookEditor(new FFlipbookEditor()); + NewFlipbookEditor->InitFlipbookEditor(Mode, EditWithinLevelEditor, Flipbook); + */ + } + } +} + +uint32 FAssetTypeActions_MirrorTable::GetCategories() +{ + return EAssetTypeCategories::Animation | MyAssetCategory; +} + +////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/ContentBrowserTools.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/ContentBrowserTools.cpp new file mode 100644 index 0000000..dcd4cb7 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/ContentBrowserTools.cpp @@ -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 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& Animations) + { + FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); + FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); + + TArray 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 Animations; + for (auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt) + { + const FAssetData& AssetData = *AssetIt; + if (UAnimSequence* Animation = Cast(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& Skeletons) + { + FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); + FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); + + TArray 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 Skeletons; + for (auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt) + { + const FAssetData& AssetData = *AssetIt; + if (USkeleton* Skeleton = Cast(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 SelectedAssetFunctor) + { + SelectedAssetFunctor->Execute(); + } + + static void CreateMirrorButton(FMenuBuilder& MenuBuilder, TArray SelectedAssets) + { + TSharedPtr AnimationMirrorFunctor = MakeShareable(new FMirrorAnimAssetExtension()); + AnimationMirrorFunctor->SelectedAssets = SelectedAssets; + + FUIAction Action_MirrorAnimAsset( + FExecuteAction::CreateStatic(&FContentBrowserToolExtensions_Impl::ExecuteSelectedContentFunctor, StaticCastSharedPtr(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 SelectedAssets) + { + TSharedPtr CreateMirrorTableFunctor = MakeShareable(new FCreateMirrorTableExtension()); + CreateMirrorTableFunctor->SelectedAssets = SelectedAssets; + + FUIAction Action_CreateMirrorTable( + FExecuteAction::CreateStatic(&FContentBrowserToolExtensions_Impl::ExecuteSelectedContentFunctor, StaticCastSharedPtr(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 OnExtendContentBrowserAssetSelectionMenu(const TArray& SelectedAssets) + { + TSharedRef 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& GetExtenderDelegates() + { + FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked(TEXT("ContentBrowser")); + return ContentBrowserModule.GetAllAssetViewContextMenuExtenders(); + } +}; + +////////////////////////////////////////////////////////////////////////// + + +void FContentBrowserTools::InstallHooks() +{ + ContentBrowserExtenderDelegate = FContentBrowserMenuExtender_SelectedAssets::CreateStatic(&FContentBrowserToolExtensions_Impl::OnExtendContentBrowserAssetSelectionMenu); + + TArray& CBMenuExtenderDelegates = FContentBrowserToolExtensions_Impl::GetExtenderDelegates(); + CBMenuExtenderDelegates.Add(ContentBrowserExtenderDelegate); + ContentBrowserExtenderDelegateHandle = CBMenuExtenderDelegates.Last().GetHandle(); +} + +void FContentBrowserTools::RemoveHooks() +{ + TArray& CBMenuExtenderDelegates = FContentBrowserToolExtensions_Impl::GetExtenderDelegates(); + CBMenuExtenderDelegates.RemoveAll([](const FContentBrowserMenuExtender_SelectedAssets& Delegate) { return Delegate.GetHandle() == ContentBrowserExtenderDelegateHandle; }); +} + +////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimAssetDialog.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimAssetDialog.cpp new file mode 100644 index 0000000..ba127b3 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimAssetDialog.cpp @@ -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(); + MirrorAnimAssetSettings->AddToRoot(); + + FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked("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 MirrorAnimSequenceWindow = SNew(SWindow) + .Title(TitleText) + .SizingRule(ESizingRule::UserSized) + .ClientSize(FVector2D(720.f, 720.f)) + .AutoCenter(EAutoCenter::PreferredWorkArea) + .SupportsMinimize(false); + + TSharedRef MirrorAnimSequenceDialog = SNew(SMirrorAnimAssetDialog, SourceAnimSequence); + + MirrorAnimSequenceWindow->SetContent(MirrorAnimSequenceDialog); + TSharedPtr 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 ContainingWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); + if (ContainingWindow.IsValid()) + { + ContainingWindow->RequestDestroyWindow(); + } +} + +void SMirrorAnimAssetDialog::CreateMirroredAnimSequences() +{ + + FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); + FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); + + TArray 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(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 \ No newline at end of file diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimAssetSettings.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimAssetSettings.cpp new file mode 100644 index 0000000..b15432e --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimAssetSettings.cpp @@ -0,0 +1,9 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. +#include "MirrorAnimAssetSettings.h" +#include "MirrorAnimationSystemEditor.h" + + +UMirrorAnimAssetSettings::UMirrorAnimAssetSettings(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} \ No newline at end of file diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimationSystemEditor.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimationSystemEditor.cpp new file mode 100644 index 0000000..3ab8286 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimationSystemEditor.cpp @@ -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("AssetTools").Get(); + RegisterAssetTypeAction(AssetTools, MakeShareable(new FAssetTypeActions_MirrorTable(MirrorTableAssetCategoryBit))); + + FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("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("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("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 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(PropertyEditor); + PropertyModule.RegisterCustomClassLayout(ClassName, DetailLayoutDelegate); +} + + + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FMirrorAnimationSystemEditorModule, MirrorAnimationSystemEditor) \ No newline at end of file diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimationSystemStyle.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimationSystemStyle.cpp new file mode 100644 index 0000000..df7550e --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorAnimationSystemStyle.cpp @@ -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 diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorTableDetails.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorTableDetails.cpp new file mode 100644 index 0000000..aa8d20b --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorTableDetails.cpp @@ -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& 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 ContextPropertyHandle = ContextMappingPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, MirrorAxis)); + if (ContextPropertyHandle->IsValidHandle())//if (BoneNameHandle->IsValidHandle()) + { + const TSharedPtr FlipAxisPropertyHandle = ContextPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, FlipAxis)); + const TSharedPtr RotationOffsetPropertyHandle = ContextPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, RotationOffset)); + const TSharedPtr BoneNamePropertyHandle = ContextPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, BoneName)); + const TSharedPtr ParentHandle = ContextMappingPropertyHandle->GetParentHandle(); + const TSharedPtr ParentArrayHandle = ParentHandle->AsArray(); + + + uint32 ContextCount; + ParentArrayHandle->GetNumElements(ContextCount); + + TSharedRef 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 MirrorAxisPropertyHandle = ContextMappingPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, MirrorAxis)); + ChildrenBuilder.AddProperty(MirrorAxisPropertyHandle.ToSharedRef()); + + const TSharedPtr FlipAxisPropertyHandle = ContextMappingPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, FlipAxis)); + ChildrenBuilder.AddProperty(FlipAxisPropertyHandle.ToSharedRef()); + + const TSharedPtr RotationOffsetPropertyHandle = ContextMappingPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMirrorBone, RotationOffset)); + ChildrenBuilder.AddProperty(RotationOffsetPropertyHandle.ToSharedRef()); + + ChildrenBuilder.AddProperty(IsTwinBoneHandle.ToSharedRef()); + + ChildrenBuilder.AddProperty(TwinBoneNameHandle.ToSharedRef()); + + const TSharedPtr 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 ParentHandle = ContextMappingPropertyHandle->GetParentHandle(); + const TSharedPtr ParentArrayHandle = ParentHandle->AsArray(); + + uint32 ContextCount; + ParentArrayHandle->GetNumElements(ContextCount); + if (ContextCount != 1) // Mustn't remove the only context. + { + ParentArrayHandle->DeleteItem(ContextMappingPropertyHandle->GetIndexInArray()); + } + } +} + +TSharedRef 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 MirrorBonesPropertyHandle = DetailLayoutBuilder->GetProperty(GET_MEMBER_NAME_CHECKED(UMirrorTable, MirrorBones), UMirrorTable::StaticClass()); + MirrorBonesPropertyHandle->MarkHiddenByCustomization(); + + const TSharedPtr 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 ChildContextMappingPropertyHandle = MirrorBonesPropertyArrayHandle->GetElement(j); + + const TSharedRef DialogueContextMapping = MakeShareable(new FMirrorTableNodeBuilder(DetailLayoutBuilder, ChildContextMappingPropertyHandle)); + MirrorBonesDetailCategoryBuilder.AddCustomBuilder(DialogueContextMapping); + } +} + +void FMirrorTableDetails::RebuildChildren() +{ + const TSharedPtr MirrorBonesPropertyHandle = DetailLayoutBuilder->GetProperty(GET_MEMBER_NAME_CHECKED(UMirrorTable, MirrorBones), UMirrorTable::StaticClass()); + const TSharedPtr MirrorBonesPropertyArrayHandle = MirrorBonesPropertyHandle->AsArray(); + + DetailLayoutBuilder->ForceRefreshDetails(); +} + + + +FReply FMirrorTableDetails::AddMirrorTableMapping_OnClicked() +{ + const TSharedPtr MirrorBonesPropertyHandle = DetailLayoutBuilder->GetProperty(GET_MEMBER_NAME_CHECKED(UMirrorTable, MirrorBones), UMirrorTable::StaticClass()); + const TSharedPtr MirrorBonesPropertyArrayHandle = MirrorBonesPropertyHandle->AsArray(); + MirrorBonesPropertyArrayHandle->AddItem(); + + return FReply::Handled(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorTableFromSkeletonDialog.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorTableFromSkeletonDialog.cpp new file mode 100644 index 0000000..9ebcec4 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorTableFromSkeletonDialog.cpp @@ -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(); + + MirrorTableSettings->AddToRoot(); + + FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked("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 MirrorAnimSequenceWindow = SNew(SWindow) + .Title(TitleText) + .SizingRule(ESizingRule::UserSized) + .ClientSize(FVector2D(550.f, 375.f)) + .AutoCenter(EAutoCenter::PreferredWorkArea) + .SupportsMinimize(false); + + TSharedRef MirrorAnimSequenceDialog = SNew(SMirrorTableFromSkeletonDialog, SourceSkeleton); + + MirrorAnimSequenceWindow->SetContent(MirrorAnimSequenceDialog); + TSharedPtr 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 ContainingWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); + if (ContainingWindow.IsValid()) + { + ContainingWindow->RequestDestroyWindow(); + } +} + +void SMirrorTableFromSkeletonDialog::CreateMirrorTableFromSkeletonFunction() +{ + + FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); + FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); + + TArray ObjectsToSync; + + UDataAssetFactory* DataFactory = NewObject(); + + 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(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 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 \ No newline at end of file diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorTableFromSkeletonSettings.cpp b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorTableFromSkeletonSettings.cpp new file mode 100644 index 0000000..8507231 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Private/MirrorTableFromSkeletonSettings.cpp @@ -0,0 +1,9 @@ +// Copyright 2017-2021 Rexocrates. All Rights Reserved. +#include "MirrorTableFromSkeletonSettings.h" +#include "MirrorAnimationSystemEditor.h" + + +UMirrorTableFromSkeletonSettings::UMirrorTableFromSkeletonSettings(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/AnimGraphNode_Mirror.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/AnimGraphNode_Mirror.h new file mode 100644 index 0000000..ab7061a --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/AnimGraphNode_Mirror.h @@ -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); +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/AnimGraphNode_MirrorCS.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/AnimGraphNode_MirrorCS.h new file mode 100644 index 0000000..c532137 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/AnimGraphNode_MirrorCS.h @@ -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 +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/AssetTypeActions_MirrorTable.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/AssetTypeActions_MirrorTable.h new file mode 100644 index 0000000..f690c61 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/AssetTypeActions_MirrorTable.h @@ -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& InObjects, TSharedPtr EditWithinLevelEditor = TSharedPtr()) override; + virtual uint32 GetCategories() override; + // End of IAssetTypeActions interface + +private: + EAssetTypeCategories::Type MyAssetCategory; +}; \ No newline at end of file diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/ContentBrowserTools.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/ContentBrowserTools.h new file mode 100644 index 0000000..e6ad1fe --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/ContentBrowserTools.h @@ -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(); +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimAssetDialog.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimAssetDialog.h new file mode 100644 index 0000000..a789793 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimAssetDialog.h @@ -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 MainPropertyView; +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimAssetSettings.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimAssetSettings.h new file mode 100644 index 0000000..6a85619 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimAssetSettings.h @@ -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 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); +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimationSystemEditor.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimationSystemEditor.h new file mode 100644 index 0000000..7ad0ca1 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimationSystemEditor.h @@ -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 Action); + void RegisterCustomClassLayout(FName ClassName, FOnGetDetailCustomizationInstance DetailLayoutDelegate); + + + TSet< FName > RegisteredClassNames; + EAssetTypeCategories::Type MirrorTableAssetCategoryBit; + TArray< TSharedPtr > CreatedAssetTypeActions; + +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimationSystemStyle.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimationSystemStyle.h new file mode 100644 index 0000000..1fc4df6 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorAnimationSystemStyle.h @@ -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; +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorTableDetails.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorTableDetails.h new file mode 100644 index 0000000..23d0aa6 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorTableDetails.h @@ -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 +{ +public: + FMirrorTableNodeBuilder(IDetailLayoutBuilder* InDetailLayoutBuilder, const TSharedPtr& 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 ContextMappingPropertyHandle; + + /** Property handle to the localization key format property within this context mapping */ + TSharedPtr BoneNameHandle; + + + /** Property handle to the localization key format property within this context mapping */ + TSharedPtr TwinBoneNameHandle; + + /** Property handle to the localization key format property within this context mapping */ + TSharedPtr IsTwinBoneHandle; +}; + +class FMirrorTableDetails : public IDetailCustomization +{ +public: + /** Makes a new instance of this detail layout class for a specific detail view requesting it */ + static TSharedRef MakeInstance(); + + /** ILayoutDetails interface */ + virtual void CustomizeDetails(class IDetailLayoutBuilder& DetailBuilder) override; + + void RebuildChildren(); +private: + FReply AddMirrorTableMapping_OnClicked(); + + +private: + /** Associated detail layout builder */ + IDetailLayoutBuilder* DetailLayoutBuilder; +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorTableFromSkeletonDialog.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorTableFromSkeletonDialog.h new file mode 100644 index 0000000..1e35976 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorTableFromSkeletonDialog.h @@ -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 MainPropertyView; +}; diff --git a/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorTableFromSkeletonSettings.h b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorTableFromSkeletonSettings.h new file mode 100644 index 0000000..46e9e12 --- /dev/null +++ b/MirrorAnimationSystem/Source/MirrorAnimationSystemEditor/Public/MirrorTableFromSkeletonSettings.h @@ -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 DefaultMirrorAxis = EAxis::None; + UPROPERTY(Category = NonTwinBonesSettings, EditAnywhere, meta = (HideAlphaChannel)) + TEnumAsByte DefaultFlipAxis = EAxis::None; + UPROPERTY(Category = NonTwinBonesSettings, EditAnywhere, meta = (HideAlphaChannel)) + FRotator DefaultRotationOffset = FRotator(0, 0, 0); + + UPROPERTY(Category = TwinBonesSettings, EditAnywhere, meta = (HideAlphaChannel)) + TEnumAsByte DefaultTwinMirrorAxis = EAxis::None; + UPROPERTY(Category = TwinBonesSettings, EditAnywhere, meta = (HideAlphaChannel)) + TEnumAsByte 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); +};