diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/AnimNotifies/AnimNotifyState_KawaiiPhysics.cpp b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/AnimNotifies/AnimNotifyState_KawaiiPhysics.cpp new file mode 100644 index 0000000..fc3b0c9 --- /dev/null +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/AnimNotifies/AnimNotifyState_KawaiiPhysics.cpp @@ -0,0 +1,41 @@ +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License + +#include "AnimNotifyState_KawaiiPhysics.h" +#include "KawaiiPhysicsLibrary.h" +#include "KawaiiPhysicsExternalForce.h" +#include "Misc/UObjectToken.h" + +#define LOCTEXT_NAMESPACE "KawaiiPhysics_AnimNotifyState" + +UAnimNotifyState_KawaiiPhysicsAddExternalForce::UAnimNotifyState_KawaiiPhysicsAddExternalForce( + const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +#if WITH_EDITORONLY_DATA + NotifyColor = FColor(255, 170, 0, 255); +#endif // WITH_EDITORONLY_DATA +} + +FString UAnimNotifyState_KawaiiPhysicsAddExternalForce::GetNotifyName_Implementation() const +{ + return FString(TEXT("KP: Add ExternalForce")); +} + +void UAnimNotifyState_KawaiiPhysicsAddExternalForce::NotifyBegin(USkeletalMeshComponent* MeshComp, + UAnimSequenceBase* Animation, + float TotalDuration) +{ + UKawaiiPhysicsLibrary::AddExternalForcesToComponent(MeshComp, AdditionalExternalForces, this, + FilterTags, bFilterExactMatch); + Super::NotifyBegin(MeshComp, Animation, TotalDuration); +} + +void UAnimNotifyState_KawaiiPhysicsAddExternalForce::NotifyEnd(USkeletalMeshComponent* MeshComp, + UAnimSequenceBase* Animation) +{ + UKawaiiPhysicsLibrary::RemoveExternalForcesFromComponent(MeshComp, this, FilterTags, bFilterExactMatch); + + Super::NotifyEnd(MeshComp, Animation); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/AnimNotifies/AnimNotifyState_KawaiiPhysics.h b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/AnimNotifies/AnimNotifyState_KawaiiPhysics.h new file mode 100644 index 0000000..c533507 --- /dev/null +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/AnimNotifies/AnimNotifyState_KawaiiPhysics.h @@ -0,0 +1,76 @@ +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License + +#pragma once + +#include "GameplayTagContainer.h" +#include "Animation/AnimNotifies/AnimNotifyState.h" + +#include "AnimNotifyState_KawaiiPhysics.generated.h" + +class UKawaiiPhysics_ExternalForce; + +/** + * UAnimNotifyState_KawaiiPhysicsAddExternalForce + * + * This class represents an animation notify state that adds external forces to a skeletal mesh component + * during an animation sequence. It inherits from UAnimNotifyState and provides functionality to add and remove + * external forces at the beginning and end of the animation notify state. + */ +UCLASS(Blueprintable, meta = (DisplayName = "KawaiiPhyiscs: Add ExternalForce")) +class KAWAIIPHYSICS_API UAnimNotifyState_KawaiiPhysicsAddExternalForce : public UAnimNotifyState +{ + GENERATED_BODY() + +public: + /** + * Constructor for UAnimNotifyState_KawaiiPhysicsAddExternalForce. + * + * @param ObjectInitializer - The object initializer for this class. + */ + UAnimNotifyState_KawaiiPhysicsAddExternalForce(const FObjectInitializer& ObjectInitializer); + + /** + * Gets the name of the notify state. + * + * @return The name of the notify state as a string. + */ + virtual FString GetNotifyName_Implementation() const override; + + /** + * Called when the animation notify state begins. + * + * @param MeshComp - The skeletal mesh component. + * @param Animation - The animation sequence. + * @param TotalDuration - The total duration of the notify state. + */ + virtual void NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration) override; + + /** + * Called when the animation notify state ends. + * + * @param MeshComp - The skeletal mesh component. + * @param Animation - The animation sequence. + */ + virtual void NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override; + + /** + * Additional external forces to be applied to the skeletal mesh component. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Instanced, Category = "ExternalForce", + meta = (BaseStruct = "/Script/KawaiiPhysics.KawaiiPhysics_ExternalForce", ExcludeBaseStruct)) + TArray AdditionalExternalForces; + + /** + * Tags used to filter which external forces are applied. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ExternalForce") + FGameplayTagContainer FilterTags; + + /** + * Whether to filter tags to exact matches (if False, parent tags will also be included). + * Tagのフィルタリングにて完全一致にするか否か(Falseの場合は親Tagも含めます) + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ExternalForce") + bool bFilterExactMatch; + +}; diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/AnimNotifies/AnimNotify_KawaiiPhysics.cpp b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/AnimNotifies/AnimNotify_KawaiiPhysics.cpp new file mode 100644 index 0000000..c6738a5 --- /dev/null +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/AnimNotifies/AnimNotify_KawaiiPhysics.cpp @@ -0,0 +1,67 @@ +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License + +#include "AnimNotify_KawaiiPhysics.h" +#include "KawaiiPhysicsLibrary.h" +#include "KawaiiPhysicsExternalForce.h" +#include "Misc/UObjectToken.h" +#include "Logging/MessageLog.h" + +#define LOCTEXT_NAMESPACE "KawaiiPhysics_AnimNotify" + +UAnimNotify_KawaiiPhysicsAddExternalForce::UAnimNotify_KawaiiPhysicsAddExternalForce( + const FObjectInitializer& ObjectInitializer) +{ +#if WITH_EDITORONLY_DATA + NotifyColor = FColor(255, 170, 0, 255); +#endif // WITH_EDITORONLY_DATA +} + +FString UAnimNotify_KawaiiPhysicsAddExternalForce::GetNotifyName_Implementation() const +{ + return FString(TEXT("KP: Add ExternalForce")); +} + +void UAnimNotify_KawaiiPhysicsAddExternalForce::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) +{ + UKawaiiPhysicsLibrary::AddExternalForcesToComponent(MeshComp, AdditionalExternalForces, this, + FilterTags, bFilterExactMatch, true); + + Super::Notify(MeshComp, Animation); +} + +#if WITH_EDITOR +void UAnimNotify_KawaiiPhysicsAddExternalForce::ValidateAssociatedAssets() +{ + static const FName NAME_AssetCheck("AssetCheck"); + + if (const UAnimSequenceBase* ContainingAsset = Cast(GetContainingAsset())) + { + for (auto& ForceInstancedStruct : AdditionalExternalForces) + { + if (ForceInstancedStruct == nullptr) + { + FMessageLog AssetCheckLog(NAME_AssetCheck); + + const FText MessageLooping = FText::Format( + NSLOCTEXT("AnimNotify", "ExternalForce_ShouldSet", + " AnimNotify(KawaiiPhysics_AddExternalForce) doesn't have a valid ExternalForce in {0}"), + FText::AsCultureInvariant(ContainingAsset->GetPathName())); + + AssetCheckLog.Warning() + ->AddToken(FUObjectToken::Create(ContainingAsset)) + ->AddToken(FTextToken::Create(MessageLooping)); + + if (GIsEditor) + { + constexpr bool bForce = true; + AssetCheckLog.Notify(MessageLooping, EMessageSeverity::Warning, bForce); + } + } + + //const auto& ExternalForce = ForceInstancedStruct.Get(); + } + } +} +#endif + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/AnimNotifies/AnimNotify_KawaiiPhysics.h b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/AnimNotifies/AnimNotify_KawaiiPhysics.h new file mode 100644 index 0000000..25c4414 --- /dev/null +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/AnimNotifies/AnimNotify_KawaiiPhysics.h @@ -0,0 +1,74 @@ +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License + +#pragma once + +#include "GameplayTagContainer.h" +#include "Animation/AnimNotifies/AnimNotify.h" + +#include "AnimNotify_KawaiiPhysics.generated.h" + +class UKawaiiPhysics_ExternalForce; + +/** + * UAnimNotify_KawaiiPhysicsAddExternalForce + * + * This class represents an animation notify that adds external forces to a skeletal mesh component + * during an animation sequence. It inherits from UAnimNotify and provides functionality to add and remove + * external forces when the notify is triggered. + */ +UCLASS(Blueprintable, meta = (DisplayName = "KawaiiPhyiscs: Add ExternalForce")) +class KAWAIIPHYSICS_API UAnimNotify_KawaiiPhysicsAddExternalForce : public UAnimNotify +{ + GENERATED_BODY() + +public: + /** + * Constructor for UAnimNotify_KawaiiPhysicsAddExternalForce. + * + * @param ObjectInitializer - The object initializer for this class. + */ + UAnimNotify_KawaiiPhysicsAddExternalForce(const FObjectInitializer& ObjectInitializer); + + /** + * Gets the name of the notify. + * + * @return The name of the notify as a string. + */ + virtual FString GetNotifyName_Implementation() const override; + + /** + * Called when the animation notify is triggered. + * + * @param MeshComp - The skeletal mesh component. + * @param Animation - The animation sequence. + */ + virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override; + +public: + /** + * Additional external forces to be applied to the skeletal mesh component. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Instanced, Category = "ExternalForce", + meta = (BaseStruct = "/Script/KawaiiPhysics.KawaiiPhysics_ExternalForce", ExcludeBaseStruct)) + TArray AdditionalExternalForces; + + /** + * Tags used to filter which external forces are applied. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ExternalForce") + FGameplayTagContainer FilterTags; + + /** + * Whether to filter tags to exact matches (if False, parent tags will also be included). + * Tagのフィルタリングにて完全一致にするか否か(Falseの場合は親Tagも含めます) + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ExternalForce") + bool bFilterExactMatch; + +#if WITH_EDITOR + /** + * Validates the associated assets in the editor. + */ + virtual void ValidateAssociatedAssets() override; +#endif +}; diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/KawaiiPhysics.Build.cs b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/KawaiiPhysics.Build.cs index 9f067a1..9f6a095 100644 --- a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/KawaiiPhysics.Build.cs +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/KawaiiPhysics.Build.cs @@ -1,4 +1,4 @@ -// Copyright 2019-2026 pafuhana1213. All Rights Reserved. +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License using UnrealBuildTool; @@ -13,6 +13,7 @@ public KawaiiPhysics(ReadOnlyTargetRules Target) : base(Target) { "Core", "AnimGraphRuntime", + "GameplayTags" } ); @@ -28,8 +29,7 @@ public KawaiiPhysics(ReadOnlyTargetRules Target) : base(Target) "CoreUObject", "Engine", "Slate", - "SlateCore", - "GameplayTags" + "SlateCore" } ); diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/AnimNode_KawaiiPhysics.cpp b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/AnimNode_KawaiiPhysics.cpp index d384bba..7684c5d 100644 --- a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/AnimNode_KawaiiPhysics.cpp +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/AnimNode_KawaiiPhysics.cpp @@ -1,59 +1,43 @@ -// Copyright 2019-2026 pafuhana1213. All Rights Reserved. - -#include "AnimNode_KawaiiPhysics.h" +#include "AnimNode_KawaiiPhysics.h" #include "AnimationRuntime.h" +#include "KawaiiPhysics.h" #include "KawaiiPhysicsBoneConstraintsDataAsset.h" #include "KawaiiPhysicsCustomExternalForce.h" -#include "ExternalForces/KawaiiPhysicsExternalForce.h" +#include "KawaiiPhysicsExternalForce.h" #include "KawaiiPhysicsLimitsDataAsset.h" #include "Animation/AnimInstanceProxy.h" #include "Curves/CurveFloat.h" #include "Runtime/Launch/Resources/Version.h" #include "SceneInterface.h" #include "PhysicsEngine/PhysicsAsset.h" -#include "Engine/World.h" -#include "PhysicsEngine/PhysicsSettings.h" #if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 #include "PhysicsEngine/SkeletalBodySetup.h" #endif -#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 6 -#include "Animation/AnimInstance.h" -#endif - #if WITH_EDITOR #include "UnrealEdGlobals.h" #include "Editor/UnrealEdEngine.h" #endif -#include "KawaiiPhysics.h" - -#include UE_INLINE_GENERATED_CPP_BY_NAME(AnimNode_KawaiiPhysics) #if ENABLE_ANIM_DEBUG TAutoConsoleVariable CVarAnimNodeKawaiiPhysicsEnable( TEXT("a.AnimNode.KawaiiPhysics.Enable"), true, TEXT("Enable/Disable KawaiiPhysics")); TAutoConsoleVariable CVarAnimNodeKawaiiPhysicsDebug( TEXT("a.AnimNode.KawaiiPhysics.Debug"), false, TEXT("Turn on visualization debugging for KawaiiPhysics")); -TAutoConsoleVariable CVarAnimNodeKawaiiPhysicsDebugDrawThickness( - TEXT("a.AnimNode.KawaiiPhysics.DebugDrawThickness"), 1.0f, - TEXT("Override debug draw thickness used by KawaiiPhysics (<=0 uses per-call thickness).")); +TAutoConsoleVariable CVarAnimNodeKawaiiPhysicsDebugLengthRate( + TEXT("a.AnimNode.KawaiiPhysics.Debug.LengthRate"), false, + TEXT("Turn on visualization debugging for KawaiiPhysics Bone's LengthRate")); #endif -TAutoConsoleVariable CVarAnimNodeKawaiiPhysicsUseBoneContainerRefSkeletonWhenInit( - TEXT("a.AnimNode.KawaiiPhysics.UseBoneContainerRefSkeletonWhenInit"), true, TEXT( - "flag to revert the behavior of RefSkeleton in InitModifyBones to its previous implementation.")); - DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_InitModifyBones"), STAT_KawaiiPhysics_InitModifyBones, STATGROUP_Anim); DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_Eval"), STAT_KawaiiPhysics_Eval, STATGROUP_Anim); -DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_SimulateModifyBones"), STAT_KawaiiPhysics_SimulateModifyBones, STATGROUP_Anim); +DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_SimulatemodifyBones"), STAT_KawaiiPhysics_SimulatemodifyBones, STATGROUP_Anim); DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_Simulate"), STAT_KawaiiPhysics_Simulate, STATGROUP_Anim); DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_GetWindVelocity"), STAT_KawaiiPhysics_GetWindVelocity, STATGROUP_Anim); DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_WorldCollision"), STAT_KawaiiPhysics_WorldCollision, STATGROUP_Anim); -DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_InitSyncBone"), STAT_KawaiiPhysics_InitSyncBone, STATGROUP_Anim); -DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_ApplySyncBone"), STAT_KawaiiPhysics_ApplySyncBone, STATGROUP_Anim); DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_AdjustByCollision"), STAT_KawaiiPhysics_AdjustByCollision, STATGROUP_Anim); DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_AdjustByBoneConstraint"), STAT_KawaiiPhysics_AdjustByBoneConstraint, STATGROUP_Anim); @@ -63,19 +47,11 @@ DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_WarmUp"), STAT_KawaiiPhysics_WarmUp, STAT DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_UpdatePhysicsSetting"), STAT_KawaiiPhysics_UpdatePhysicsSetting, STATGROUP_Anim); DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_UpdateCapsuleLimit"), STAT_KawaiiPhysics_UpdateCapsuleLimit, STATGROUP_Anim); DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_UpdateBoxLimit"), STAT_KawaiiPhysics_UpdateBoxLimit, STATGROUP_Anim); -DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_UpdateModifyBonesPoseTransform"), - STAT_KawaiiPhysics_UpdateModifyBonesPoseTransform, STATGROUP_Anim); -DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_ApplySimulateResult"), STAT_KawaiiPhysics_ApplySimulateResult, STATGROUP_Anim); -DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_ConvertSimulationSpaceTransform"), - STAT_KawaiiPhysics_ConvertSimulationSpaceTransform, STATGROUP_Anim); -DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_ConvertSimulationSpaceVector"), STAT_KawaiiPhysics_ConvertSimulationSpaceVector, - STATGROUP_Anim); -DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_ConvertSimulationSpaceLocation"), - STAT_KawaiiPhysics_ConvertSimulationSpaceLocation, STATGROUP_Anim); -DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_ConvertSimulationSpaceRotation"), - STAT_KawaiiPhysics_ConvertSimulationSpaceRotation, STATGROUP_Anim); -DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_ConvertSimulationSpace"), STAT_KawaiiPhysics_ConvertSimulationSpace, - STATGROUP_Anim); +DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_ConvertSimulationSpaceTransform"), STAT_KawaiiPhysics_ConvertSimulationSpaceTransform, STATGROUP_Anim); +DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_ConvertSimulationSpaceVector"), STAT_KawaiiPhysics_ConvertSimulationSpaceVector, STATGROUP_Anim); +DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_ConvertSimulationSpaceLocation"), STAT_KawaiiPhysics_ConvertSimulationSpaceLocation, STATGROUP_Anim); +DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_ConvertSimulationSpaceRotation"), STAT_KawaiiPhysics_ConvertSimulationSpaceRotation, STATGROUP_Anim); +DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_ConvertSimulationSpace"), STAT_KawaiiPhysics_ConvertSimulationSpace, STATGROUP_Anim); FAnimNode_KawaiiPhysics::FAnimNode_KawaiiPhysics() { @@ -99,24 +75,13 @@ void FAnimNode_KawaiiPhysics::Initialize_AnyThread(const FAnimationInitializeCon // For Avoiding Zero Divide in the first frame DeltaTimeOld = 1.0f / TargetFramerate; + TimeAccumulator = 0.0f; for (int i = 0; i < ExternalForces.Num(); ++i) { - if (ExternalForces[i].IsValid()) - { - auto& Force = ExternalForces[i].GetMutable(); - Force.Initialize(Context); - } - } - - if (SimulationSpace == EKawaiiPhysicsSimulationSpace::BaseBoneSpace) - { - if (SimulationBaseBone.Initialize(RequiredBones)) + if (ExternalForces[i] != nullptr) { - PrevBaseBoneSpace2ComponentSpace = - FAnimationRuntime::GetComponentSpaceTransformRefPose(RequiredBones.GetReferenceSkeleton(), - SimulationBaseBone.BoneIndex); - CurrentEvalSimSpaceCache.TargetSpaceToComponent = PrevBaseBoneSpace2ComponentSpace; + ExternalForces[i]->Initialize(Context); } } @@ -157,15 +122,14 @@ void FAnimNode_KawaiiPhysics::GatherDebugData(FNodeDebugData& DebugData) #if ENABLE_ANIM_DEBUG void FAnimNode_KawaiiPhysics::AnimDrawDebug(FComponentSpacePoseContext& Output) { - if (const UWorld* World = Output.AnimInstanceProxy->GetSkelMeshComponent()->GetWorld(); !World->IsPreviewWorld()) + const UWorld* World = Output.AnimInstanceProxy->GetSkelMeshComponent()->GetWorld(); + if (World && !World->IsPreviewWorld()) { if (Output.AnimInstanceProxy->GetSkelMeshComponent()->bRecentlyRendered) { if (CVarAnimNodeKawaiiPhysicsDebug.GetValueOnAnyThread()) { const auto AnimInstanceProxy = Output.AnimInstanceProxy; - const float LineThickness = FMath::Max( - 0.0f, CVarAnimNodeKawaiiPhysicsDebugDrawThickness.GetValueOnAnyThread()); // Modify Bones for (const auto& ModifyBone : ModifyBones) @@ -173,155 +137,52 @@ void FAnimNode_KawaiiPhysics::AnimDrawDebug(FComponentSpacePoseContext& Output) const FVector LocationWS = ConvertSimulationSpaceLocation(Output, SimulationSpace, EKawaiiPhysicsSimulationSpace::WorldSpace, ModifyBone.Location); - + auto Color = ModifyBone.bDummy ? FColor::Red : FColor::Yellow; AnimInstanceProxy->AnimDrawDebugSphere(LocationWS, ModifyBone.PhysicsSettings.Radius, 8, - Color, false, -1, LineThickness, SDPG_Foreground); - - AnimInstanceProxy->AnimDrawDebugInWorldMessage( - FString::Printf(TEXT("%.2f"), ModifyBone.LengthRateFromRoot), - ModifyBone.Location, FColor::White, 1.0f); - + Color, false, -1, 0); } // Sphere limit for (const auto& SphericalLimit : SphericalLimits) { const FVector LocationWS = ConvertSimulationSpaceLocation(Output, SimulationSpace, - EKawaiiPhysicsSimulationSpace::WorldSpace, - SphericalLimit.Location); - + EKawaiiPhysicsSimulationSpace::WorldSpace, SphericalLimit.Location); + AnimInstanceProxy->AnimDrawDebugSphere(LocationWS, SphericalLimit.Radius, 8, FColor::Orange, - false, -1, LineThickness, SDPG_Foreground); + false, -1, 0); } for (const auto& SphericalLimit : SphericalLimitsData) { const FVector LocationWS = ConvertSimulationSpaceLocation(Output, SimulationSpace, - EKawaiiPhysicsSimulationSpace::WorldSpace, - SphericalLimit.Location); + EKawaiiPhysicsSimulationSpace::WorldSpace, SphericalLimit.Location); AnimInstanceProxy->AnimDrawDebugSphere(LocationWS, SphericalLimit.Radius, 8, FColor::Blue, - false, -1, LineThickness, SDPG_Foreground); + false, -1, 0); } // Box limit for (const auto& BoxLimit : BoxLimits) { - this->AnimDrawDebugBox(Output, BoxLimit.Location, BoxLimit.Rotation, BoxLimit.Extent, - FColor::Orange, LineThickness); - } - for (const auto& BoxLimit : BoxLimitsData) - { - this->AnimDrawDebugBox(Output, BoxLimit.Location, BoxLimit.Rotation, BoxLimit.Extent, - FColor::Blue, LineThickness); - } + const FVector LocationWS = + ConvertSimulationSpaceLocation(Output, SimulationSpace, + EKawaiiPhysicsSimulationSpace::WorldSpace, BoxLimit.Location); - // Planar limit - for (const auto& PlanarLimit : PlanarLimits) - { - FTransform TransformWS = - ConvertSimulationSpaceTransform(Output, SimulationSpace, - EKawaiiPhysicsSimulationSpace::WorldSpace, - FTransform(PlanarLimit.Rotation, PlanarLimit.Location)); - AnimInstanceProxy->AnimDrawDebugPlane(TransformWS, 50.0f, - FColor::Orange, false, -1, LineThickness, SDPG_Foreground); + // TODO } - for (const auto& PlanarLimit : PlanarLimitsData) + for (const auto& BoxLimit : BoxLimitsData) { - FTransform TransformWS = - ConvertSimulationSpaceTransform(Output, SimulationSpace, - EKawaiiPhysicsSimulationSpace::WorldSpace, - FTransform(PlanarLimit.Rotation, PlanarLimit.Location)); - AnimInstanceProxy->AnimDrawDebugPlane(TransformWS, 50.0f, - FColor::Blue, false, -1, LineThickness, SDPG_Foreground); - } + const FVector LocationWS = + ConvertSimulationSpaceLocation(Output, SimulationSpace, + EKawaiiPhysicsSimulationSpace::WorldSpace, BoxLimit.Location); -#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 6 - // Capsule limit - for (const auto& CapsuleLimit : CapsuleLimits) - { - FTransform TransformWS = - ConvertSimulationSpaceTransform(Output, SimulationSpace, - EKawaiiPhysicsSimulationSpace::WorldSpace, - FTransform(CapsuleLimit.Rotation, CapsuleLimit.Location)); - - AnimInstanceProxy->AnimDrawDebugCapsule(TransformWS.GetTranslation(), CapsuleLimit.Length * 0.5f, - CapsuleLimit.Radius, TransformWS.GetRotation().Rotator(), - FColor::Orange, false, -1, LineThickness, SDPG_Foreground); + // TODO } - for (const auto& CapsuleLimit : CapsuleLimitsData) - { - FTransform TransformWS = - ConvertSimulationSpaceTransform(Output, SimulationSpace, - EKawaiiPhysicsSimulationSpace::WorldSpace, - FTransform(CapsuleLimit.Rotation, CapsuleLimit.Location)); - - AnimInstanceProxy->AnimDrawDebugCapsule(TransformWS.GetTranslation(), CapsuleLimit.Length * 0.5f, - CapsuleLimit.Radius, TransformWS.GetRotation().Rotator(), - FColor::Blue, false, -1, LineThickness, SDPG_Foreground); - } -#endif } } } } -void FAnimNode_KawaiiPhysics::AnimDrawDebugBox(FComponentSpacePoseContext& Output, const FVector& CenterLocationSim, - const FQuat& RotationSim, const FVector& Extent, - const FColor& Color, float LineThickness) const -{ - const auto AnimInstanceProxy = Output.AnimInstanceProxy; - if (!AnimInstanceProxy) - { - return; - } - - const FVector LocationWS = - ConvertSimulationSpaceLocation(Output, SimulationSpace, - EKawaiiPhysicsSimulationSpace::WorldSpace, CenterLocationSim); - const FQuat RotationWS = - ConvertSimulationSpaceRotation(Output, SimulationSpace, - EKawaiiPhysicsSimulationSpace::WorldSpace, RotationSim); - - const FTransform BoxTransformWS(RotationWS, LocationWS); - const FVector E(FMath::Abs(Extent.X), FMath::Abs(Extent.Y), FMath::Abs(Extent.Z)); - - auto DrawFaceRect = [&](const FVector& FaceCenterLS, const FVector& FaceNormalLS, float HalfWidth, float HalfHeight) - { - const FVector FaceCenterWS = BoxTransformWS.TransformPosition(FaceCenterLS); - const FVector NormalWS = RotationWS.RotateVector(FaceNormalLS).GetSafeNormal(); - - const FVector AnyUpWS = (FMath::Abs(NormalWS.Z) < 0.999f) ? FVector::UpVector : FVector::RightVector; - const FVector XAxisWS = FVector::CrossProduct(AnyUpWS, NormalWS).GetSafeNormal(); - const FVector YAxisWS = FVector::CrossProduct(NormalWS, XAxisWS).GetSafeNormal(); - - const FVector P0 = FaceCenterWS + (XAxisWS * HalfWidth) + (YAxisWS * HalfHeight); - const FVector P1 = FaceCenterWS - (XAxisWS * HalfWidth) + (YAxisWS * HalfHeight); - const FVector P2 = FaceCenterWS - (XAxisWS * HalfWidth) - (YAxisWS * HalfHeight); - const FVector P3 = FaceCenterWS + (XAxisWS * HalfWidth) - (YAxisWS * HalfHeight); - - AnimInstanceProxy->AnimDrawDebugLine(P0, P1, Color, false, -1.0f, - LineThickness, SDPG_Foreground); - AnimInstanceProxy->AnimDrawDebugLine(P1, P2, Color, false, -1.0f, - LineThickness, SDPG_Foreground); - AnimInstanceProxy->AnimDrawDebugLine(P2, P3, Color, false, -1.0f, - LineThickness, SDPG_Foreground); - AnimInstanceProxy->AnimDrawDebugLine(P3, P0, Color, false, -1.0f, - LineThickness, SDPG_Foreground); - }; - - // +X / -X faces: cover YZ - DrawFaceRect(FVector(E.X, 0, 0), FVector(1, 0, 0), E.Y, E.Z); - DrawFaceRect(FVector(-E.X, 0, 0), FVector(-1, 0, 0), E.Y, E.Z); - - // +Y / -Y faces: cover XZ - DrawFaceRect(FVector(0, E.Y, 0), FVector(0, 1, 0), E.X, E.Z); - DrawFaceRect(FVector(0, -E.Y, 0), FVector(0, -1, 0), E.X, E.Z); - - // +Z / -Z faces: cover XY - DrawFaceRect(FVector(0, 0, E.Z), FVector(0, 0, 1), E.X, E.Y); - DrawFaceRect(FVector(0, 0, -E.Z), FVector(0, 0, -1), E.X, E.Y); -} #endif void FAnimNode_KawaiiPhysics::EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, @@ -334,33 +195,13 @@ void FAnimNode_KawaiiPhysics::EvaluateSkeletalControl_AnyThread(FComponentSpaceP const FBoneContainer& BoneContainer = Output.Pose.GetPose().GetBoneContainer(); FTransform ComponentTransform = Output.AnimInstanceProxy->GetComponentTransform(); - // save prev frame BaseBoneSpace2ComponentSpace - if (SimulationSpace == EKawaiiPhysicsSimulationSpace::BaseBoneSpace) - { - if (SimulationBaseBone.IsValidToEvaluate(BoneContainer)) - { - PrevBaseBoneSpace2ComponentSpace = CurrentEvalSimSpaceCache.TargetSpaceToComponent; - } - else - { - PrevBaseBoneSpace2ComponentSpace = FTransform::Identity; - UE_LOG(LogKawaiiPhysics, Warning, TEXT("SimulationBaseBone is invalid. Reverting to Identity transform.")); - } - } - - // Build per-evaluate caches AFTER PrevBaseBoneSpace2ComponentSpace is updated. - CurrentEvalSimSpaceCache = BuildSimulationSpaceCache(Output, SimulationSpace); - bHasCurrentEvalSimSpaceCache = true; - CurrentEvalWorldSpaceCache = BuildSimulationSpaceCache(Output, EKawaiiPhysicsSimulationSpace::WorldSpace); - bHasCurrentEvalWorldSpaceCache = true; - if (TeleportType == ETeleportType::ResetPhysics) { ModifyBones.Empty(ModifyBones.Num()); TeleportType = ETeleportType::None; bInitPhysicsSettings = false; } - + if (SimulationSpace != LastSimulationSpace) { ConvertSimulationSpace(Output, LastSimulationSpace, SimulationSpace); @@ -381,7 +222,7 @@ void FAnimNode_KawaiiPhysics::EvaluateSkeletalControl_AnyThread(FComponentSpaceP } #endif - + if (!RootBone.IsValidToEvaluate(BoneContainer)) { return; @@ -394,15 +235,23 @@ void FAnimNode_KawaiiPhysics::EvaluateSkeletalControl_AnyThread(FComponentSpaceP } } + if (SimulationSpace == EKawaiiPhysicsSimulationSpace::BaseBoneSpace) + { + if (SimulationBaseBone.IsValidToEvaluate(BoneContainer)) + { + BaseBoneSpace2ComponentSpace = + Output.Pose.GetComponentSpaceTransform(SimulationBaseBone.GetCompactPoseIndex(BoneContainer)); + } + } + if (ModifyBones.Num() == 0) { InitModifyBones(Output, BoneContainer); - InitSyncBones(Output); InitBoneConstraints(); PreSkelCompTransform = ComponentTransform; } - // Update each parameter and collision + // Update each parameters and collision if (!bInitPhysicsSettings || bUpdatePhysicsSettingsInGame) { UpdatePhysicsSettingsOfModifyBones(); @@ -426,9 +275,6 @@ void FAnimNode_KawaiiPhysics::EvaluateSkeletalControl_AnyThread(FComponentSpaceP // Update Bone Pose Transform UpdateModifyBonesPoseTransform(Output, BoneContainer); - // Apply Sync Bones - ApplySyncBones(Output, BoneContainer); - // Update SkeletalMeshComponent movement in World Space UpdateSkelCompMove(Output, ComponentTransform); @@ -455,7 +301,7 @@ void FAnimNode_KawaiiPhysics::EvaluateSkeletalControl_AnyThread(FComponentSpaceP { SimulateModifyBones(Output, ComponentTransform); } - + ApplySimulateResult(Output, BoneContainer, OutBoneTransforms); TeleportType = ETeleportType::None; @@ -587,26 +433,14 @@ void FAnimNode_KawaiiPhysics::InitializeBoneReferences(const FBoneContainer& Req { BoneConstraint.InitializeBone(RequiredBones); } - - for (auto& SyncBone : SyncBones) - { - SyncBone.Bone.Initialize(RequiredBones); - for (auto& Target : SyncBone.TargetRoots) - { - Target.Bone.Initialize(RequiredBones); - } - } } void FAnimNode_KawaiiPhysics::InitModifyBones(FComponentSpacePoseContext& Output, const FBoneContainer& BoneContainer) { SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_InitModifyBones); - // https://github.com/pafuhana1213/KawaiiPhysics/issues/174 - const FReferenceSkeleton& RefSkeleton = (CVarAnimNodeKawaiiPhysicsUseBoneContainerRefSkeletonWhenInit. - GetValueOnAnyThread()) - ? BoneContainer.GetReferenceSkeleton() - : BoneContainer.GetSkeletonAsset()->GetReferenceSkeleton(); + const USkeleton* Skeleton = BoneContainer.GetSkeletonAsset(); + auto& RefSkeleton = Skeleton->GetReferenceSkeleton(); auto InitRootBone = [&](const FName& RootBoneName, const TArray& InExcludeBones) { @@ -672,7 +506,7 @@ void FAnimNode_KawaiiPhysics::ApplyLimitsDataAsset(const FBoneContainer& Require RemoveAllSourceDataAssets(BoxLimitsData); RemoveAllSourceDataAssets(PlanarLimitsData); - if (LimitsDataAsset) + if (LimitsDataAsset != nullptr) { SphericalLimitsData.Append(LimitsDataAsset->SphericalLimits); CapsuleLimitsData.Append(LimitsDataAsset->CapsuleLimits); @@ -707,7 +541,7 @@ void FAnimNode_KawaiiPhysics::ApplyPhysicsAsset(const FBoneContainer& RequiredBo RemoveAllSourcePhysicsAssets(CapsuleLimitsData); RemoveAllSourcePhysicsAssets(BoxLimitsData); - if (PhysicsAssetForLimits) + if (PhysicsAssetForLimits != nullptr) { for (const auto& BodySetup : PhysicsAssetForLimits->SkeletalBodySetups) { @@ -760,7 +594,7 @@ void FAnimNode_KawaiiPhysics::ApplyPhysicsAsset(const FBoneContainer& RequiredBo void FAnimNode_KawaiiPhysics::ApplyBoneConstraintDataAsset(const FBoneContainer& RequiredBones) { BoneConstraintsData.Empty(); - if (BoneConstraintsDataAsset) + if (BoneConstraintsDataAsset != nullptr) { BoneConstraintsData = BoneConstraintsDataAsset->GenerateBoneConstraints(); for (auto& BoneConstraint : BoneConstraintsData) @@ -877,18 +711,16 @@ void FAnimNode_KawaiiPhysics::CalcBoneLength(FKawaiiPhysicsModifyBone& Bone, if (Bone.ParentIndex < 0) { Bone.LengthFromRoot = 0.0f; - Bone.BoneLength = 0.0f; } else { if (!Bone.bDummy) { - Bone.BoneLength = RefBonePose[Bone.BoneRef.BoneIndex].GetLocation().Size(); - Bone.LengthFromRoot = InModifyBones[Bone.ParentIndex].LengthFromRoot + Bone.BoneLength; + Bone.LengthFromRoot = InModifyBones[Bone.ParentIndex].LengthFromRoot + + RefBonePose[Bone.BoneRef.BoneIndex].GetLocation().Size(); } else { - Bone.BoneLength = DummyBoneLength; Bone.LengthFromRoot = InModifyBones[Bone.ParentIndex].LengthFromRoot + DummyBoneLength; } @@ -911,34 +743,54 @@ void FAnimNode_KawaiiPhysics::UpdatePhysicsSettingsOfModifyBones() const float LengthRate = Bone.LengthRateFromRoot; // Damping - Bone.PhysicsSettings.Damping = FMath::Clamp( - PhysicsSettings.Damping * DampingCurveData.GetRichCurveConst()->Eval( - LengthRate, 1.0f), 0.0f, 1.0f); - + Bone.PhysicsSettings.Damping = PhysicsSettings.Damping; + if (!DampingCurveData.GetRichCurve()->IsEmpty()) + { + Bone.PhysicsSettings.Damping *= DampingCurveData.GetRichCurve()->Eval(LengthRate); + } + Bone.PhysicsSettings.Damping = FMath::Clamp(Bone.PhysicsSettings.Damping, 0.0f, 1.0f); + // WorldLocationDamping - Bone.PhysicsSettings.WorldDampingLocation = FMath::Clamp( - PhysicsSettings.WorldDampingLocation * WorldDampingLocationCurveData.GetRichCurveConst()->Eval( - LengthRate, 1.0f), 0.0f, 1.0f); - + Bone.PhysicsSettings.WorldDampingLocation = PhysicsSettings.WorldDampingLocation; + if (!WorldDampingLocationCurveData.GetRichCurve()->IsEmpty()) + { + Bone.PhysicsSettings.WorldDampingLocation *= WorldDampingLocationCurveData.GetRichCurve()->Eval(LengthRate); + } + Bone.PhysicsSettings.WorldDampingLocation = FMath::Clamp(Bone.PhysicsSettings.WorldDampingLocation, 0.0f, + 1.0f); + // WorldRotationDamping - Bone.PhysicsSettings.WorldDampingRotation = FMath::Clamp( - PhysicsSettings.WorldDampingRotation * WorldDampingRotationCurveData.GetRichCurveConst()->Eval( - LengthRate, 1.0f), 0.0f, 1.0f); - + Bone.PhysicsSettings.WorldDampingRotation = PhysicsSettings.WorldDampingRotation; + if (!WorldDampingRotationCurveData.GetRichCurve()->IsEmpty()) + { + Bone.PhysicsSettings.WorldDampingRotation *= WorldDampingRotationCurveData.GetRichCurve()->Eval(LengthRate); + } + Bone.PhysicsSettings.WorldDampingRotation = FMath::Clamp(Bone.PhysicsSettings.WorldDampingRotation, 0.0f, + 1.0f); + // Stiffness - Bone.PhysicsSettings.Stiffness = FMath::Clamp( - PhysicsSettings.Stiffness * StiffnessCurveData.GetRichCurveConst()->Eval( - LengthRate, 1.0f), 0.0f, 1.0f); - + Bone.PhysicsSettings.Stiffness = PhysicsSettings.Stiffness; + if (!StiffnessCurveData.GetRichCurve()->IsEmpty()) + { + Bone.PhysicsSettings.Stiffness *= StiffnessCurveData.GetRichCurve()->Eval(LengthRate); + } + Bone.PhysicsSettings.Stiffness = FMath::Clamp(Bone.PhysicsSettings.Stiffness, 0.0f, 1.0f); + // Radius - Bone.PhysicsSettings.Radius = FMath::Max( - PhysicsSettings.Radius * RadiusCurveData.GetRichCurveConst()->Eval( - LengthRate, 1.0f), 0.0f); - + Bone.PhysicsSettings.Radius = PhysicsSettings.Radius; + if (!RadiusCurveData.GetRichCurve()->IsEmpty()) + { + Bone.PhysicsSettings.Radius *= RadiusCurveData.GetRichCurve()->Eval(LengthRate); + } + Bone.PhysicsSettings.Radius = FMath::Max(Bone.PhysicsSettings.Radius, 0.0f); + // LimitAngle - Bone.PhysicsSettings.LimitAngle = FMath::Max( - PhysicsSettings.LimitAngle * LimitAngleCurveData.GetRichCurveConst()->Eval( - LengthRate, 1.0f), 0.0f); + Bone.PhysicsSettings.LimitAngle = PhysicsSettings.LimitAngle; + if (!LimitAngleCurveData.GetRichCurve()->IsEmpty()) + { + Bone.PhysicsSettings.LimitAngle *= LimitAngleCurveData.GetRichCurve()->Eval(LengthRate); + } + Bone.PhysicsSettings.LimitAngle = FMath::Max(Bone.PhysicsSettings.LimitAngle, 0.0f); } } @@ -974,7 +826,13 @@ void FAnimNode_KawaiiPhysics::UpdateSphericalLimits(TArray& Lim } else { - Sphere.bEnable = false; + // Handle case when no DrivingBone is set - use OffsetLocation/OffsetRotation directly + FTransform OffsetTransform(Sphere.OffsetRotation, Sphere.OffsetLocation); + OffsetTransform = ConvertSimulationSpaceTransform(Output, EKawaiiPhysicsSimulationSpace::ComponentSpace, + SimulationSpace, OffsetTransform); + Sphere.Location = OffsetTransform.GetLocation(); + Sphere.Rotation = OffsetTransform.GetRotation(); + Sphere.bEnable = true; } } } @@ -1010,7 +868,13 @@ void FAnimNode_KawaiiPhysics::UpdateCapsuleLimits(TArray& Limits, } else { - Capsule.bEnable = false; + // Handle case when no DrivingBone is set - use OffsetLocation/OffsetRotation directly + FTransform OffsetTransform(Capsule.OffsetRotation, Capsule.OffsetLocation); + OffsetTransform = ConvertSimulationSpaceTransform(Output, EKawaiiPhysicsSimulationSpace::ComponentSpace, + SimulationSpace, OffsetTransform); + Capsule.Location = OffsetTransform.GetLocation(); + Capsule.Rotation = OffsetTransform.GetRotation(); + Capsule.bEnable = true; } } } @@ -1047,7 +911,13 @@ void FAnimNode_KawaiiPhysics::UpdateBoxLimits(TArray& Limits, FCompon } else { - Box.bEnable = false; + // Handle case when no DrivingBone is set - use OffsetLocation/OffsetRotation directly + FTransform OffsetTransform(Box.OffsetRotation, Box.OffsetLocation); + OffsetTransform = ConvertSimulationSpaceTransform(Output, EKawaiiPhysicsSimulationSpace::ComponentSpace, + SimulationSpace, OffsetTransform); + Box.Location = OffsetTransform.GetLocation(); + Box.Rotation = OffsetTransform.GetRotation(); + Box.bEnable = true; } } } @@ -1102,8 +972,6 @@ void FAnimNode_KawaiiPhysics::UpdateModifyBonesPoseTransform(FComponentSpacePose { for (auto& Bone : ModifyBones) { - SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_UpdateModifyBonesPoseTransform); - if (Bone.bDummy) { auto ParentBone = ModifyBones[Bone.ParentIndex]; @@ -1124,10 +992,10 @@ void FAnimNode_KawaiiPhysics::UpdateModifyBonesPoseTransform(FComponentSpacePose Bone.PoseRotation = FQuat::Identity; Bone.PoseScale = FVector::OneVector; } - continue; + return; } - const FTransform BoneTransform = GetBoneTransformInSimSpace(Output, CompactPoseIndex); + const auto BoneTransform = GetBoneTransformInSimSpace(Output, CompactPoseIndex); Bone.PoseLocation = BoneTransform.GetLocation(); Bone.PoseRotation = BoneTransform.GetRotation(); Bone.PoseScale = BoneTransform.GetScale3D(); @@ -1158,13 +1026,37 @@ void FAnimNode_KawaiiPhysics::UpdateSkelCompMove(FComponentSpacePoseContext& Out void FAnimNode_KawaiiPhysics::SimulateModifyBones(FComponentSpacePoseContext& Output, const FTransform& ComponentTransform) { - SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_SimulateModifyBones); + SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_SimulatemodifyBones); if (DeltaTime <= 0.0f) { return; } + // Fixed time step mode: only simulate at TargetFramerate intervals + if (bUseFixedTimeStep) + { + const float FixedDeltaTime = 1.0f / FMath::Max(1, TargetFramerate); + TimeAccumulator += DeltaTime; + + if (TimeAccumulator < FixedDeltaTime) + { + // Not enough time accumulated, skip simulation this frame + // Bones keep their current positions + return; + } + + // Override DeltaTime with fixed value for this simulation step + DeltaTime = FixedDeltaTime; + TimeAccumulator -= FixedDeltaTime; + + // Prevent accumulator from growing too large (handles very low FPS) + if (TimeAccumulator > FixedDeltaTime * 2.0f) + { + TimeAccumulator = FixedDeltaTime; + } + } + const USkeletalMeshComponent* SkelComp = Output.AnimInstanceProxy->GetSkelMeshComponent(); // Save Prev/Pose Info , Check SkipSimulate @@ -1187,39 +1079,10 @@ void FAnimNode_KawaiiPhysics::SimulateModifyBones(FComponentSpacePoseContext& Ou Bone.bSkipSimulate = false; } - // Gravity - GravityInSimSpace = ConvertSimulationSpaceVector(Output, - bUseWorldSpaceGravity - ? EKawaiiPhysicsSimulationSpace::WorldSpace - : EKawaiiPhysicsSimulationSpace::ComponentSpace, - SimulationSpace, Gravity); - if (bUseDefaultGravityZProjectSetting) - { - GravityInSimSpace *= FMath::Abs(UPhysicsSettings::Get()->DefaultGravityZ); - } - - // SimpleExternalForce: compute once in SimulationSpace (avoid per-bone conversions) - if (!SimpleExternalForce.IsNearlyZero()) - { - if (bUseWorldSpaceSimpleExternalForce) - { - SimpleExternalForceInSimSpace = ConvertSimulationSpaceVector( - Output, - EKawaiiPhysicsSimulationSpace::WorldSpace, - SimulationSpace, - SimpleExternalForce); - } - else - { - SimpleExternalForceInSimSpace = SimpleExternalForce; - } - } - else - { - SimpleExternalForceInSimSpace = FVector::ZeroVector; - } - // External Force : PreApply + GravityInSimSpace = ConvertSimulationSpaceVector(Output, EKawaiiPhysicsSimulationSpace::ComponentSpace, + SimulationSpace, Gravity); + // NOTE: if use foreach, you may get issue ( Array has changed during ranged-for iteration ) for (int i = 0; i < CustomExternalForces.Num(); ++i) { @@ -1230,10 +1093,9 @@ void FAnimNode_KawaiiPhysics::SimulateModifyBones(FComponentSpacePoseContext& Ou } for (int i = 0; i < ExternalForces.Num(); ++i) { - if (ExternalForces[i].IsValid()) + if (ExternalForces[i] != nullptr) { - auto& Force = ExternalForces[i].GetMutable(); - Force.PreApply(*this, SkelComp); + ExternalForces[i]->PreApply(*this, SkelComp); } } @@ -1253,10 +1115,9 @@ void FAnimNode_KawaiiPhysics::SimulateModifyBones(FComponentSpacePoseContext& Ou // External Force : PostApply for (int i = 0; i < ExternalForces.Num(); ++i) { - if (ExternalForces[i].IsValid()) + if (ExternalForces[i] != nullptr) { - auto& Force = ExternalForces[i].GetMutable(); - Force.PostApply(*this); + ExternalForces[i]->PostApply(*this); } } @@ -1284,6 +1145,17 @@ void FAnimNode_KawaiiPhysics::SimulateModifyBones(FComponentSpacePoseContext& Ou } } + // Adjust by Quad Collision (Experimental) + if (bEnableQuadCollision && CachedQuads.Num() > 0) + { + for (int32 Iter = 0; Iter < QuadCollisionIterationCount; ++Iter) + { + AdjustByQuadCollision(); + } + // Correct stretched edges after collision pushes + CorrectQuadEdgeStretching(); + } + // Adjust by Bone Constraints After Collision if (BoneConstraintIterationCountAfterCollision > 0) { @@ -1340,28 +1212,8 @@ void FAnimNode_KawaiiPhysics::Simulate(FKawaiiPhysicsModifyBone& Bone, const FSc { Velocity += GetWindVelocity(Output, Scene, Bone) * TargetFramerate; } - - // Gravity (apply just after wind; keep legacy compatibility via separate position term) - if (!bUseLegacyGravity) - { - // AnimDynamics-like: integrate acceleration into velocity - Velocity += GravityInSimSpace * DeltaTime; - } - else - { - // Legacy gravity: add 0.5 * g * dt^2 to position - Bone.Location += 0.5 * GravityInSimSpace * DeltaTime * DeltaTime; - } - - // Integrate position from velocity Bone.Location += Velocity * DeltaTime; - // Simple External Force (cached in SimulateModifyBones) - if (!SimpleExternalForceInSimSpace.IsNearlyZero()) - { - Bone.Location += SimpleExternalForceInSimSpace * DeltaTime; - } - // Follow World Movement if (SimulationSpace != EKawaiiPhysicsSimulationSpace::WorldSpace && TeleportType != ETeleportType::TeleportPhysics) { @@ -1377,17 +1229,15 @@ void FAnimNode_KawaiiPhysics::Simulate(FKawaiiPhysicsModifyBone& Bone, const FSc { Bone.Location += SkelCompMoveVector * (1.0f - Bone.PhysicsSettings.WorldDampingLocation); } - + // Follow Rotation if (SimulationSpace == EKawaiiPhysicsSimulationSpace::BaseBoneSpace) { - const FVector PrevLocationCS = PrevBaseBoneSpace2ComponentSpace.TransformPosition(Bone.PrevLocation); + const FVector PrevLocationCS = BaseBoneSpace2ComponentSpace.TransformPosition(Bone.PrevLocation); const FVector RotatedLocationCS = SkelCompMoveRotation.RotateVector(PrevLocationCS); - const FVector RotatedLocationBase = ConvertSimulationSpaceLocationCached( - FSimulationSpaceCache(), CurrentEvalSimSpaceCache, RotatedLocationCS); + const FVector RotatedLocationBase = BaseBoneSpace2ComponentSpace.InverseTransformPosition(RotatedLocationCS); - Bone.Location += (RotatedLocationBase - Bone.PrevLocation) * (1.0f - Bone.PhysicsSettings. - WorldDampingRotation); + Bone.Location += (RotatedLocationBase - Bone.PrevLocation) * (1.0f - Bone.PhysicsSettings.WorldDampingRotation); } else { @@ -1396,6 +1246,10 @@ void FAnimNode_KawaiiPhysics::Simulate(FKawaiiPhysicsModifyBone& Bone, const FSc } } + // Gravity + // TODO:Migrate if there are more good method (Currently copying AnimDynamics implementation) + Bone.Location += 0.5f * GravityInSimSpace * DeltaTime * DeltaTime; + // External Force // NOTE: if use foreach, you may get issue ( Array has changed during ranged-for iteration ) for (int i = 0; i < CustomExternalForces.Num(); ++i) @@ -1413,17 +1267,17 @@ void FAnimNode_KawaiiPhysics::Simulate(FKawaiiPhysicsModifyBone& Bone, const FSc BoneTM = GetBoneTransformInSimSpace( Output, Bone.BoneRef.GetCompactPoseIndex(Output.Pose.GetPose().GetBoneContainer())); } - + CustomExternalForces[i]->Apply(*this, Bone.Index, SkelComp, BoneTM); } } for (int i = 0; i < ExternalForces.Num(); ++i) { - if (ExternalForces[i].IsValid()) + if (ExternalForces[i] != nullptr) { - if (const auto ExForce = ExternalForces[i].GetMutablePtr(); - ExForce->bIsEnabled) + UKawaiiPhysics_ExternalForce* ExForce = ExternalForces[i]; + if (ExForce->bIsEnabled) { if (ExForce->ExternalForceSpace == EExternalForceSpace::BoneSpace) { @@ -1455,7 +1309,7 @@ void FAnimNode_KawaiiPhysics::Simulate(FKawaiiPhysicsModifyBone& Bone, const FSc (1.0f - FMath::Pow(1.0f - Bone.PhysicsSettings.Stiffness, Exponent)); } -FVector FAnimNode_KawaiiPhysics::GetWindVelocity(FComponentSpacePoseContext& Output, const FSceneInterface* Scene, +FVector FAnimNode_KawaiiPhysics::GetWindVelocity(const FComponentSpacePoseContext& Output, const FSceneInterface* Scene, const FKawaiiPhysicsModifyBone& Bone) const { SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_GetWindVelocity); @@ -1471,8 +1325,7 @@ FVector FAnimNode_KawaiiPhysics::GetWindVelocity(FComponentSpacePoseContext& Out float WindMaxGust = 0.0f; Scene->GetWindParameters_GameThread( - ConvertSimulationSpaceLocation(Output, SimulationSpace, EKawaiiPhysicsSimulationSpace::WorldSpace, - Bone.PoseLocation), + ConvertSimulationSpaceLocation(Output, SimulationSpace, EKawaiiPhysicsSimulationSpace::WorldSpace, Bone.PoseLocation), WindDirection, WindSpeed, WindMinGust, WindMaxGust); WindDirection = @@ -1481,7 +1334,7 @@ FVector FAnimNode_KawaiiPhysics::GetWindVelocity(FComponentSpacePoseContext& Out { WindDirection = FMath::VRandCone(WindDirection, FMath::DegreesToRadians(WindDirectionNoiseAngle)); } - + FVector WindVelocity = WindDirection * WindSpeed * WindScale; // TODO:Migrate if there are more good method (Currently copying AnimDynamics implementation) @@ -1499,16 +1352,16 @@ void FAnimNode_KawaiiPhysics::AdjustByWorldCollision(FComponentSpacePoseContext& { return; } - - + + /** the trace is not done in game thread, so TraceTag does not draw debug traces*/ FCollisionQueryParams Params(SCENE_QUERY_STAT(KawaiiCollision)); - + if (bIgnoreSelfComponent) { Params.AddIgnoredComponent(OwningComp); } - + // Get collision settings from component ECollisionChannel TraceChannel = bOverrideCollisionParams ? CollisionChannelSettings.GetObjectType() @@ -1518,14 +1371,13 @@ void FAnimNode_KawaiiPhysics::AdjustByWorldCollision(FComponentSpacePoseContext& CollisionChannelSettings.GetResponseToChannels()) : FCollisionResponseParams( OwningComp->GetCollisionResponseToChannels()); + FTransform OwingCompTransform = OwningComp->GetComponentTransform(); const UWorld* World = OwningComp->GetWorld(); const FVector TraceStartLocationWS = - ConvertSimulationSpaceLocation(Output, SimulationSpace, EKawaiiPhysicsSimulationSpace::WorldSpace, - Bone.PrevLocation); + ConvertSimulationSpaceLocation(Output, SimulationSpace, EKawaiiPhysicsSimulationSpace::WorldSpace, Bone.PrevLocation); const FVector TraceEndLocationWS = - ConvertSimulationSpaceLocation(Output, SimulationSpace, EKawaiiPhysicsSimulationSpace::WorldSpace, - Bone.Location); + ConvertSimulationSpaceLocation(Output, SimulationSpace, EKawaiiPhysicsSimulationSpace::WorldSpace, Bone.Location); if (bIgnoreSelfComponent) { @@ -1606,15 +1458,13 @@ void FAnimNode_KawaiiPhysics::AdjustByWorldCollision(FComponentSpacePoseContext& if (Result.bStartPenetrating) { Bone.Location = - ConvertSimulationSpaceLocation(Output, EKawaiiPhysicsSimulationSpace::WorldSpace, - SimulationSpace, + ConvertSimulationSpaceLocation(Output, EKawaiiPhysicsSimulationSpace::WorldSpace, SimulationSpace, TraceEndLocationWS + Result.Normal * Result.PenetrationDepth); } else { Bone.Location = - ConvertSimulationSpaceLocation(Output, EKawaiiPhysicsSimulationSpace::WorldSpace, - SimulationSpace, + ConvertSimulationSpaceLocation(Output, EKawaiiPhysicsSimulationSpace::WorldSpace, SimulationSpace, Result.Location); } break; @@ -1632,24 +1482,31 @@ void FAnimNode_KawaiiPhysics::AdjustBySphereCollision(FKawaiiPhysicsModifyBone& continue; } + // Cache delta - avoid calculating (Bone.Location - Sphere.Location) multiple times + const FVector Delta = Bone.Location - Sphere.Location; + const float DistSq = Delta.SizeSquared(); const float LimitDistance = Bone.PhysicsSettings.Radius + Sphere.Radius; + const float LimitDistSq = LimitDistance * LimitDistance; + if (Sphere.LimitType == ESphericalLimitType::Outer) { - if ((Bone.Location - Sphere.Location).SizeSquared() > LimitDistance * LimitDistance) + if (DistSq > LimitDistSq) { continue; } - Bone.Location += (LimitDistance - (Bone.Location - Sphere.Location).Size()) - * (Bone.Location - Sphere.Location).GetSafeNormal(); + // Use InvSqrt to get both length and normalized direction efficiently + const float InvDist = FMath::InvSqrt(DistSq + KINDA_SMALL_NUMBER); + const float Dist = DistSq * InvDist; + Bone.Location += Delta * InvDist * (LimitDistance - Dist); } else { - if ((Bone.Location - Sphere.Location).SizeSquared() < LimitDistance * LimitDistance) + if (DistSq < LimitDistSq) { continue; } - Bone.Location = Sphere.Location + - (Sphere.Radius - Bone.PhysicsSettings.Radius) * (Bone.Location - Sphere.Location).GetSafeNormal(); + const float InvDist = FMath::InvSqrt(DistSq + KINDA_SMALL_NUMBER); + Bone.Location = Sphere.Location + Delta * InvDist * (Sphere.Radius - Bone.PhysicsSettings.Radius); } } } @@ -1663,15 +1520,21 @@ void FAnimNode_KawaiiPhysics::AdjustByCapsuleCollision(FKawaiiPhysicsModifyBone& continue; } - FVector StartPoint = Capsule.Location + Capsule.Rotation.GetAxisZ() * Capsule.Length * 0.5f; - FVector EndPoint = Capsule.Location + Capsule.Rotation.GetAxisZ() * Capsule.Length * -0.5f; - const float DistSquared = FMath::PointDistToSegmentSquared(Bone.Location, StartPoint, EndPoint); + // Cache axis - GetAxisZ() extracts from rotation matrix, avoid calling twice + const FVector CapsuleHalfAxis = Capsule.Rotation.GetAxisZ() * (Capsule.Length * 0.5f); + const FVector StartPoint = Capsule.Location + CapsuleHalfAxis; + const FVector EndPoint = Capsule.Location - CapsuleHalfAxis; const float LimitDistance = Bone.PhysicsSettings.Radius + Capsule.Radius; - if (DistSquared < LimitDistance * LimitDistance) + const float LimitDistSq = LimitDistance * LimitDistance; + const float DistSquared = FMath::PointDistToSegmentSquared(Bone.Location, StartPoint, EndPoint); + + if (DistSquared < LimitDistSq) { - FVector ClosestPoint = FMath::ClosestPointOnSegment(Bone.Location, StartPoint, EndPoint); - Bone.Location = ClosestPoint + (Bone.Location - ClosestPoint).GetSafeNormal() * LimitDistance; + const FVector ClosestPoint = FMath::ClosestPointOnSegment(Bone.Location, StartPoint, EndPoint); + const FVector Delta = Bone.Location - ClosestPoint; + const float InvDist = FMath::InvSqrt(Delta.SizeSquared() + KINDA_SMALL_NUMBER); + Bone.Location = ClosestPoint + Delta * InvDist * LimitDistance; } } } @@ -1782,7 +1645,7 @@ void FAnimNode_KawaiiPhysics::AdjustByPlanarConstraint(FKawaiiPhysicsModifyBone& } } -const TArray XPBDComplianceValues = +static const float XPBDComplianceValues[] = { 0.00000000004f, // 0.04 x 10^(-9) (M^2/N) Concrete 0.00000000016f, // 0.16 x 10^(-9) (M^2/N) Wood @@ -1795,10 +1658,20 @@ const TArray XPBDComplianceValues = void FAnimNode_KawaiiPhysics::AdjustByBoneConstraints() { - for (FModifyBoneConstraint& BoneConstraint : MergedBoneConstraints) + // Move profiler outside loop - reduces overhead when many constraints + SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_AdjustByBoneConstraint); + + if (MergedBoneConstraints.Num() == 0) { - SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_AdjustByBoneConstraint); + return; + } + + // Pre-calculate time factor used by all constraints + const float InvDeltaTimeSq = 1.0f / (DeltaTime * DeltaTime); + const float GlobalComplianceScaled = XPBDComplianceValues[static_cast(BoneConstraintGlobalComplianceType)] * InvDeltaTimeSq; + for (FModifyBoneConstraint& BoneConstraint : MergedBoneConstraints) + { if (!BoneConstraint.IsValid()) { continue; @@ -1806,28 +1679,27 @@ void FAnimNode_KawaiiPhysics::AdjustByBoneConstraints() FKawaiiPhysicsModifyBone& ModifyBone1 = ModifyBones[BoneConstraint.ModifyBoneIndex1]; FKawaiiPhysicsModifyBone& ModifyBone2 = ModifyBones[BoneConstraint.ModifyBoneIndex2]; - EXPBDComplianceType ComplianceType = BoneConstraint.bOverrideCompliance - ? BoneConstraint.ComplianceType - : BoneConstraintGlobalComplianceType; FVector Delta = ModifyBone2.Location - ModifyBone1.Location; - float DeltaLength = Delta.Size(); - if (DeltaLength <= 0.0f) + const float DeltaLengthSq = Delta.SizeSquared(); + if (DeltaLengthSq <= KINDA_SMALL_NUMBER) { continue; } - // PBD - // Delta *= (DeltaLength - BoneConstraint.Length) / DeltaLength * 0.5f; - // ModifyBone1.Location += Delta * Stiffness; - // ModifyBone2.Location -= Delta * Stiffness; + // Use InvSqrt for efficient length calculation + const float InvDeltaLength = FMath::InvSqrt(DeltaLengthSq); + const float DeltaLength = DeltaLengthSq * InvDeltaLength; - // XBPD - float Constraint = DeltaLength - BoneConstraint.Length; - float Compliance = XPBDComplianceValues[static_cast(ComplianceType)]; - Compliance /= DeltaTime * DeltaTime; - float DeltaLambda = (Constraint - Compliance * BoneConstraint.Lambda) / (2 + Compliance); // 2 = SumMass - Delta = (Delta / DeltaLength) * DeltaLambda; + // XPBD - use pre-calculated compliance when possible + const float Constraint = DeltaLength - BoneConstraint.Length; + const float Compliance = BoneConstraint.bOverrideCompliance + ? XPBDComplianceValues[static_cast(BoneConstraint.ComplianceType)] * InvDeltaTimeSq + : GlobalComplianceScaled; + const float DeltaLambda = (Constraint - Compliance * BoneConstraint.Lambda) / (2.0f + Compliance); + + // Normalize and scale in one operation + Delta *= InvDeltaLength * DeltaLambda; ModifyBone1.Location += Delta; ModifyBone2.Location -= Delta; @@ -1835,619 +1707,868 @@ void FAnimNode_KawaiiPhysics::AdjustByBoneConstraints() } } -void FAnimNode_KawaiiPhysics::WarmUp(FComponentSpacePoseContext& Output, const FBoneContainer& BoneContainer, - FTransform& ComponentTransform) +// ============================================================================ +// Quad Collision (Experimental) +// ============================================================================ + +void FAnimNode_KawaiiPhysics::InitQuadCollisionBoneConstraints() { - SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_WarmUp); + MergedQuadCollisionBoneConstraints.Empty(); + QuadCollisionBoneConstraintsData.Empty(); - for (int32 i = 0; i < WarmUpFrames; ++i) + if (!bEnableQuadCollision) { - SimulateModifyBones(Output, ComponentTransform); + return; } -} -void FAnimNode_KawaiiPhysics::InitBoneConstraints() -{ - MergedBoneConstraints = BoneConstraints; - MergedBoneConstraints.Append(BoneConstraintsData); + // Build bone lookup map for O(1) index lookups (instead of O(n) per constraint) + TMap BoneNameToIndex; + BoneNameToIndex.Reserve(ModifyBones.Num()); + for (int32 i = 0; i < ModifyBones.Num(); ++i) + { + BoneNameToIndex.Add(ModifyBones[i].BoneRef.BoneName, i); + } - TArray DummyBoneConstraint; - for (FModifyBoneConstraint& Constraint : MergedBoneConstraints) + // Load from quad collision specific data asset if set + if (QuadCollisionBoneConstraintsDataAsset != nullptr) { - Constraint.ModifyBoneIndex1 = - ModifyBones.IndexOfByPredicate([Constraint](const FKawaiiPhysicsModifyBone& ModifyBone) - { - return ModifyBone.BoneRef == Constraint.Bone1; - }); - if (Constraint.ModifyBoneIndex1 < 0) + QuadCollisionBoneConstraintsData = QuadCollisionBoneConstraintsDataAsset->GenerateBoneConstraints(); + + // Reserve capacity for merged constraints (may add dummy constraints too) + MergedQuadCollisionBoneConstraints.Reserve( + bQuadCollisionIncludeDummyBones + ? QuadCollisionBoneConstraintsData.Num() * 2 + : QuadCollisionBoneConstraintsData.Num()); + + for (int32 ConstraintIdx = 0; ConstraintIdx < QuadCollisionBoneConstraintsData.Num(); ++ConstraintIdx) { - continue; - } + FModifyBoneConstraint Constraint = QuadCollisionBoneConstraintsData[ConstraintIdx]; - Constraint.ModifyBoneIndex2 = - ModifyBones.IndexOfByPredicate([Constraint](const FKawaiiPhysicsModifyBone& ModifyBone) + // O(1) lookup using bone name map + const int32* FoundIndex1 = BoneNameToIndex.Find(Constraint.Bone1.BoneName); + if (!FoundIndex1) { - return ModifyBone.BoneRef == Constraint.Bone2; - }); - if (Constraint.ModifyBoneIndex2 < 0) - { - continue; - } + continue; + } + Constraint.ModifyBoneIndex1 = *FoundIndex1; - Constraint.Length = - (ModifyBones[Constraint.ModifyBoneIndex1].Location - ModifyBones[Constraint.ModifyBoneIndex2].Location). - Size(); + const int32* FoundIndex2 = BoneNameToIndex.Find(Constraint.Bone2.BoneName); + if (!FoundIndex2) + { + continue; + } + Constraint.ModifyBoneIndex2 = *FoundIndex2; - // DummyBone"s constraint - if (bAutoAddChildDummyBoneConstraint) - { - const int32 ChildDummyBoneIndex1 = ModifyBones[Constraint.ModifyBoneIndex1].ChildIndices.IndexOfByPredicate( - [&](int32 Index) + Constraint.Length = + (ModifyBones[Constraint.ModifyBoneIndex1].Location - ModifyBones[Constraint.ModifyBoneIndex2].Location).Size(); + + // Add the real constraint + MergedQuadCollisionBoneConstraints.Add(Constraint); + + // If dummy bones enabled, check if this constraint's bones have dummy children + // If so, insert a dummy constraint immediately after (for proper quad formation) + if (bQuadCollisionIncludeDummyBones) + { + // Find direct child bones that are dummy bones + int32 DummyChildIndex1 = INDEX_NONE; + int32 DummyChildIndex2 = INDEX_NONE; + + for (int32 ChildIdx : ModifyBones[Constraint.ModifyBoneIndex1].ChildIndices) { - return Index >= 0 && ModifyBones[Index].bDummy == true; - }); - const int32 ChildDummyBoneIndex2 = ModifyBones[Constraint.ModifyBoneIndex2].ChildIndices.IndexOfByPredicate( - [&](int32 Index) + if (ChildIdx >= 0 && ChildIdx < ModifyBones.Num() && ModifyBones[ChildIdx].bDummy) + { + DummyChildIndex1 = ChildIdx; + break; + } + } + + for (int32 ChildIdx : ModifyBones[Constraint.ModifyBoneIndex2].ChildIndices) { - return Index >= 0 && ModifyBones[Index].bDummy == true; - }); + if (ChildIdx >= 0 && ChildIdx < ModifyBones.Num() && ModifyBones[ChildIdx].bDummy) + { + DummyChildIndex2 = ChildIdx; + break; + } + } - if (ChildDummyBoneIndex1 >= 0 && ChildDummyBoneIndex2 >= 0) + // Only add dummy constraint if BOTH bones have dummy children + // This typically only happens at the last level of each chain + if (DummyChildIndex1 != INDEX_NONE && DummyChildIndex2 != INDEX_NONE) + { + FModifyBoneConstraint NewDummyBoneConstraint; + NewDummyBoneConstraint.ModifyBoneIndex1 = DummyChildIndex1; + NewDummyBoneConstraint.ModifyBoneIndex2 = DummyChildIndex2; + NewDummyBoneConstraint.Length = + (ModifyBones[DummyChildIndex1].Location - ModifyBones[DummyChildIndex2].Location).Size(); + NewDummyBoneConstraint.bIsDummy = true; + + // Insert dummy constraint immediately after the real constraint + MergedQuadCollisionBoneConstraints.Add(NewDummyBoneConstraint); + } + } + } + } + else + { + // Fall back to using the main bone constraints + // Filter out dummy constraints if dummy bones are disabled for quad collision + if (bQuadCollisionIncludeDummyBones) + { + MergedQuadCollisionBoneConstraints = MergedBoneConstraints; + } + else + { + MergedQuadCollisionBoneConstraints.Reserve(MergedBoneConstraints.Num()); + for (const FModifyBoneConstraint& Constraint : MergedBoneConstraints) { - FModifyBoneConstraint NewDummyBoneConstraint; - NewDummyBoneConstraint.ModifyBoneIndex1 = ModifyBones[Constraint.ModifyBoneIndex1].ChildIndices[ - ChildDummyBoneIndex1]; - NewDummyBoneConstraint.ModifyBoneIndex2 = ModifyBones[Constraint.ModifyBoneIndex2].ChildIndices[ - ChildDummyBoneIndex2]; - NewDummyBoneConstraint.Length = - (ModifyBones[NewDummyBoneConstraint.ModifyBoneIndex1].Location - ModifyBones[NewDummyBoneConstraint. - ModifyBoneIndex2].Location). - Size(); - NewDummyBoneConstraint.bIsDummy = true; - DummyBoneConstraint.Add(NewDummyBoneConstraint); + if (!Constraint.bIsDummy) + { + MergedQuadCollisionBoneConstraints.Add(Constraint); + } } } } +} - MergedBoneConstraints.Append(DummyBoneConstraint); -} - -void FAnimNode_KawaiiPhysics::ApplySimulateResult(FComponentSpacePoseContext& Output, - const FBoneContainer& BoneContainer, - TArray& OutBoneTransforms) +void FAnimNode_KawaiiPhysics::BuildQuadsFromConstraints() { - SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_ApplySimulateResult); + CachedQuads.Empty(); - for (int32 i = 0; i < ModifyBones.Num(); ++i) + if (!bEnableQuadCollision || MergedQuadCollisionBoneConstraints.Num() < 2) { - FTransform PoseTransform = FTransform(ModifyBones[i].PoseRotation, ModifyBones[i].PoseLocation, - ModifyBones[i].PoseScale); - PoseTransform = - ConvertSimulationSpaceTransform(Output, SimulationSpace, EKawaiiPhysicsSimulationSpace::ComponentSpace, - PoseTransform); - OutBoneTransforms.Add(FBoneTransform(ModifyBones[i].BoneRef.GetCompactPoseIndex(BoneContainer), PoseTransform)); + return; } + // Reserve capacity for quads (approximately one quad per consecutive constraint pair) + CachedQuads.Reserve(MergedQuadCollisionBoneConstraints.Num()); - for (int32 i = 0; i < ModifyBones.Num(); ++i) + // Helper lambda to check if two bones are in a parent-child relationship (consecutive vertical levels) + auto AreBonesConsecutiveLevels = [this](int32 BoneIdx1, int32 BoneIdx2) -> bool { - FKawaiiPhysicsModifyBone& Bone = ModifyBones[i]; - if (!Bone.HasParent()) + if (BoneIdx1 < 0 || BoneIdx1 >= ModifyBones.Num() || BoneIdx2 < 0 || BoneIdx2 >= ModifyBones.Num()) + return false; + + const FKawaiiPhysicsModifyBone& Bone1 = ModifyBones[BoneIdx1]; + const FKawaiiPhysicsModifyBone& Bone2 = ModifyBones[BoneIdx2]; + + // Check if Bone2 is a child of Bone1 + if (Bone2.ParentIndex == BoneIdx1) + return true; + + // Check if Bone1 is a child of Bone2 + if (Bone1.ParentIndex == BoneIdx2) + return true; + + return false; + }; + + // First pass: find chain pair boundaries (estimate ~10 chain pairs max) + TArray ChainPairStartIndices; + ChainPairStartIndices.Reserve(16); + ChainPairStartIndices.Add(0); // First chain pair starts at 0 + + for (int32 i = 1; i < MergedQuadCollisionBoneConstraints.Num(); ++i) + { + const FModifyBoneConstraint& Prev = MergedQuadCollisionBoneConstraints[i - 1]; + const FModifyBoneConstraint& Curr = MergedQuadCollisionBoneConstraints[i]; + + bool bBone1Consecutive = AreBonesConsecutiveLevels(Prev.ModifyBoneIndex1, Curr.ModifyBoneIndex1); + bool bBone2Consecutive = AreBonesConsecutiveLevels(Prev.ModifyBoneIndex2, Curr.ModifyBoneIndex2); + + if (!bBone1Consecutive || !bBone2Consecutive) { - continue; + ChainPairStartIndices.Add(i); } + } - FKawaiiPhysicsModifyBone& ParentBone = ModifyBones[Bone.ParentIndex]; + // Detect if the last chain pair closes the loop (connects back to the first chain) + int32 LoopClosingChainPairStart = INDEX_NONE; + if (ChainPairStartIndices.Num() >= 2) + { + int32 LastChainStart = ChainPairStartIndices.Last(); + const FModifyBoneConstraint& LastChainFirstConstraint = MergedQuadCollisionBoneConstraints[LastChainStart]; - if (ParentBone.ChildIndices.Num() <= 1) + // Get the first chain pair's bone1 values + int32 FirstChainStart = ChainPairStartIndices[0]; + int32 FirstChainEnd = ChainPairStartIndices[1] - 1; + + // Check if last chain's bone2 matches any first chain's bone1 (loop closure) + for (int32 i = FirstChainStart; i <= FirstChainEnd && !LastChainFirstConstraint.bIsDummy; ++i) { - if (ParentBone.BoneRef.BoneIndex >= 0) + const FModifyBoneConstraint& FirstChainConstraint = MergedQuadCollisionBoneConstraints[i]; + if (!FirstChainConstraint.bIsDummy && + FirstChainConstraint.ModifyBoneIndex1 == LastChainFirstConstraint.ModifyBoneIndex2) { - FVector PoseVector = Bone.PoseLocation - ParentBone.PoseLocation; - FVector SimulateVector = Bone.Location - ParentBone.Location; - - if (PoseVector.GetSafeNormal() == SimulateVector.GetSafeNormal()) - { - continue; - } + LoopClosingChainPairStart = LastChainStart; + break; + } + } + } - if (BoneForwardAxis == EBoneForwardAxis::X_Negative || BoneForwardAxis == EBoneForwardAxis::Y_Negative - || BoneForwardAxis == EBoneForwardAxis::Z_Negative) - { - PoseVector *= -1; - SimulateVector *= -1; - } + // Build quads from consecutive constraint pairs + for (int32 i = 0; i < MergedQuadCollisionBoneConstraints.Num() - 1; ++i) + { + const FModifyBoneConstraint& C1 = MergedQuadCollisionBoneConstraints[i]; + const FModifyBoneConstraint& C2 = MergedQuadCollisionBoneConstraints[i + 1]; - FQuat SimulateRotation = - FQuat::FindBetweenVectors(PoseVector, SimulateVector) * ParentBone.PoseRotation; - ParentBone.PrevRotation = SimulateRotation; + // Skip invalid constraints + if (!C1.IsBoneReferenceValid() || !C2.IsBoneReferenceValid()) + { + continue; + } - SimulateRotation = - ConvertSimulationSpaceRotation(Output, SimulationSpace, - EKawaiiPhysicsSimulationSpace::ComponentSpace, SimulateRotation); - OutBoneTransforms[Bone.ParentIndex].Transform.SetRotation(SimulateRotation); - } + // Skip if the first constraint is dummy (we only want quads starting from real bones) + // But allow the second constraint to be dummy (creates "end quads" extending to dummy bones) + if (C1.bIsDummy) + { + continue; } - if (Bone.BoneRef.BoneIndex >= 0 && !Bone.bDummy) + // Check if the constraints are at consecutive vertical levels (not crossing chain pair boundaries) + // Both bone pairs must be parent-child related for a valid quad + bool bBone1Consecutive = AreBonesConsecutiveLevels(C1.ModifyBoneIndex1, C2.ModifyBoneIndex1); + bool bBone2Consecutive = AreBonesConsecutiveLevels(C1.ModifyBoneIndex2, C2.ModifyBoneIndex2); + + if (!bBone1Consecutive || !bBone2Consecutive) { - OutBoneTransforms[i].Transform.SetLocation( - ConvertSimulationSpaceLocation(Output, SimulationSpace, EKawaiiPhysicsSimulationSpace::ComponentSpace, - Bone.Location)); + continue; } - } - OutBoneTransforms.RemoveAll([](const FBoneTransform& BoneTransform) - { - return BoneTransform.BoneIndex < 0; - }); + // Skip loop-closing quads if bQuadCollisionCloseLoop is false + if (!bQuadCollisionCloseLoop && LoopClosingChainPairStart != INDEX_NONE && i >= LoopClosingChainPairStart) + { + continue; + } - // for check in FCSPose::LocalBlendCSBoneTransforms - OutBoneTransforms.Sort(FCompareBoneTransformIndex()); -} + FQuadCollisionLimit Quad; + Quad.BoneIndices[0] = C1.ModifyBoneIndex1; + Quad.BoneIndices[1] = C1.ModifyBoneIndex2; + Quad.BoneIndices[2] = C2.ModifyBoneIndex1; + Quad.BoneIndices[3] = C2.ModifyBoneIndex2; + Quad.InitializeEdges(); -FTransform FAnimNode_KawaiiPhysics::GetBoneTransformInSimSpace(FComponentSpacePoseContext& Output, - const FCompactPoseBoneIndex& BoneIndex) const -{ - const FSimulationSpaceCache CacheFrom = GetSimulationSpaceCacheFor( - Output, EKawaiiPhysicsSimulationSpace::ComponentSpace); - const FSimulationSpaceCache CacheTo = GetSimulationSpaceCacheFor(Output, SimulationSpace); - return ConvertSimulationSpaceTransformCached(CacheFrom, CacheTo, Output.Pose.GetComponentSpaceTransform(BoneIndex)); + if (Quad.IsValid()) + { + // Cache rest lengths for stretch correction + for (int32 EdgeIdx = 0; EdgeIdx < 4; ++EdgeIdx) + { + FQuadEdge& Edge = Quad.Edges[EdgeIdx]; + Edge.RestLength = (ModifyBones[Edge.BoneIndex1].Location - ModifyBones[Edge.BoneIndex2].Location).Size(); + } + CachedQuads.Add(Quad); + } + } } -FAnimNode_KawaiiPhysics::FSimulationSpaceCache FAnimNode_KawaiiPhysics::GetSimulationSpaceCacheFor( - FComponentSpacePoseContext& Output, - const EKawaiiPhysicsSimulationSpace Space) const +bool FAnimNode_KawaiiPhysics::AdjustEdgeByCapsuleCollision(const FQuadEdge& Edge, const FCapsuleLimit& Capsule) { - if (Space == EKawaiiPhysicsSimulationSpace::ComponentSpace) + if (!Edge.IsValid() || !Capsule.bEnable || Capsule.Radius <= 0.0f || Capsule.Length <= 0.0f) { - return FSimulationSpaceCache(); + return false; } - if (bHasCurrentEvalSimSpaceCache && Space == SimulationSpace) + + if (Edge.BoneIndex1 >= ModifyBones.Num() || Edge.BoneIndex2 >= ModifyBones.Num()) { - return CurrentEvalSimSpaceCache; + return false; } - if (bHasCurrentEvalWorldSpaceCache && Space == EKawaiiPhysicsSimulationSpace::WorldSpace) + + FKawaiiPhysicsModifyBone& Bone1 = ModifyBones[Edge.BoneIndex1]; + FKawaiiPhysicsModifyBone& Bone2 = ModifyBones[Edge.BoneIndex2]; + + // Skip if either bone should skip simulation + if (Bone1.bSkipSimulate || Bone2.bSkipSimulate) { - return CurrentEvalWorldSpaceCache; + return false; } - const UEnum* EnumPtr = StaticEnum(); - UE_LOG(LogKawaiiPhysics, Warning, TEXT("Building Simulation Space Cache for %s"), - *EnumPtr->GetNameStringByValue(static_cast(Space))); - return BuildSimulationSpaceCache(Output, Space); -} + // Capsule endpoints (along Z axis in local space) + const FVector CapsuleAxis = Capsule.Rotation.GetAxisZ() * Capsule.Length * 0.5f; + const FVector CapsuleStart = Capsule.Location + CapsuleAxis; + const FVector CapsuleEnd = Capsule.Location - CapsuleAxis; -FTransform FAnimNode_KawaiiPhysics::ConvertSimulationSpaceTransformCached( - const FSimulationSpaceCache& CacheFrom, - const FSimulationSpaceCache& CacheTo, - const FTransform& InTransform) const -{ - FTransform ResultTransform = InTransform; - ResultTransform = ResultTransform * CacheFrom.TargetSpaceToComponent; - ResultTransform = ResultTransform * CacheTo.ComponentToTargetSpace; - return ResultTransform; -} + // Find closest points between edge segment and capsule axis segment + FVector ClosestOnEdge, ClosestOnCapsule; + FMath::SegmentDistToSegmentSafe( + Bone1.Location, Bone2.Location, + CapsuleStart, CapsuleEnd, + ClosestOnEdge, ClosestOnCapsule); -FVector FAnimNode_KawaiiPhysics::ConvertSimulationSpaceVectorCached( - const FSimulationSpaceCache& CacheFrom, - const FSimulationSpaceCache& CacheTo, - const FVector& InVector) const -{ - FVector ResultVector = InVector; - ResultVector = CacheFrom.TargetSpaceToComponent.TransformVector(ResultVector); - ResultVector = CacheTo.ComponentToTargetSpace.TransformVector(ResultVector); - return ResultVector; -} - -FVector FAnimNode_KawaiiPhysics::ConvertSimulationSpaceLocationCached( - const FSimulationSpaceCache& CacheFrom, - const FSimulationSpaceCache& CacheTo, - const FVector& InLocation) const -{ - FVector ResultLocation = InLocation; - ResultLocation = CacheFrom.TargetSpaceToComponent.TransformPosition(ResultLocation); - ResultLocation = CacheTo.ComponentToTargetSpace.TransformPosition(ResultLocation); - return ResultLocation; -} + // Calculate distance and check for collision + const FVector Delta = ClosestOnEdge - ClosestOnCapsule; + const float Distance = Delta.Size(); -FQuat FAnimNode_KawaiiPhysics::ConvertSimulationSpaceRotationCached( - const FSimulationSpaceCache& CacheFrom, - const FSimulationSpaceCache& CacheTo, - const FQuat& InRotation) const -{ - FQuat ResultRotation = InRotation; - ResultRotation = CacheFrom.TargetSpaceToComponent.TransformRotation(ResultRotation); - ResultRotation = CacheTo.ComponentToTargetSpace.TransformRotation(ResultRotation); - return ResultRotation; -} + // Combined collision distance: capsule radius + average bone radius + threshold + const float AvgBoneRadius = (Bone1.PhysicsSettings.Radius + Bone2.PhysicsSettings.Radius) * 0.5f; + const float MinDist = Capsule.Radius + AvgBoneRadius + QuadCollisionThreshold; -void FAnimNode_KawaiiPhysics::ConvertSimulationSpaceCached( - const FSimulationSpaceCache& CacheFrom, - const FSimulationSpaceCache& CacheTo, - EKawaiiPhysicsSimulationSpace From, - EKawaiiPhysicsSimulationSpace To) -{ - for (FKawaiiPhysicsModifyBone& Bone : ModifyBones) + if (Distance >= MinDist || Distance < KINDA_SMALL_NUMBER) { - Bone.Location = CacheTo.ComponentToTargetSpace.TransformPosition( - CacheFrom.TargetSpaceToComponent.TransformPosition(Bone.Location)); - Bone.PrevLocation = CacheTo.ComponentToTargetSpace.TransformPosition( - CacheFrom.TargetSpaceToComponent.TransformPosition(Bone.PrevLocation)); - - Bone.PrevRotation = CacheTo.ComponentToTargetSpace.TransformRotation( - CacheFrom.TargetSpaceToComponent.TransformRotation(Bone.PrevRotation)); + return false; // No collision } -} -FTransform FAnimNode_KawaiiPhysics::ConvertSimulationSpaceTransform(FComponentSpacePoseContext& Output, - const EKawaiiPhysicsSimulationSpace From, - const EKawaiiPhysicsSimulationSpace To, - const FTransform& InTransform) const -{ - SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_ConvertSimulationSpaceTransform); - if (From == To) + // Calculate push-out vector + const FVector PushDir = Delta.GetSafeNormal(); + const float PenetrationDepth = MinDist - Distance; + const FVector PushVec = PushDir * PenetrationDepth; + + // Calculate parametric position on edge for weighted distribution + const FVector EdgeDir = Bone2.Location - Bone1.Location; + float T = 0.5f; // Default to middle + if (EdgeDir.SizeSquared() > KINDA_SMALL_NUMBER) { - return InTransform; + T = FMath::Clamp(FVector::DotProduct(ClosestOnEdge - Bone1.Location, EdgeDir) / EdgeDir.SizeSquared(), 0.0f, 1.0f); } - const FSimulationSpaceCache CacheFrom = GetSimulationSpaceCacheFor(Output, From); - const FSimulationSpaceCache CacheTo = GetSimulationSpaceCacheFor(Output, To); - return ConvertSimulationSpaceTransformCached(CacheFrom, CacheTo, InTransform); -} - -FVector FAnimNode_KawaiiPhysics::ConvertSimulationSpaceVector(FComponentSpacePoseContext& Output, - const EKawaiiPhysicsSimulationSpace From, - const EKawaiiPhysicsSimulationSpace To, - const FVector& InVector) const -{ - SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_ConvertSimulationSpaceVector); - if (From == To) + // Apply weighted correction to both endpoints + // T=0 means collision near Bone1, T=1 means near Bone2 + if (Bone1.ParentIndex >= 0) { - return InVector; + Bone1.Location += PushVec * (1.0f - T); + } + if (Bone2.ParentIndex >= 0) + { + Bone2.Location += PushVec * T; } - const FSimulationSpaceCache CacheFrom = GetSimulationSpaceCacheFor(Output, From); - const FSimulationSpaceCache CacheTo = GetSimulationSpaceCacheFor(Output, To); - return ConvertSimulationSpaceVectorCached(CacheFrom, CacheTo, InVector); + return true; } -FVector FAnimNode_KawaiiPhysics::ConvertSimulationSpaceLocation(FComponentSpacePoseContext& Output, - const EKawaiiPhysicsSimulationSpace From, - const EKawaiiPhysicsSimulationSpace To, - const FVector& InLocation) const +bool FAnimNode_KawaiiPhysics::AdjustEdgeByCapsuleCollisionCached(const FQuadEdge& Edge, const FCachedCapsuleData& CachedCapsule) { - SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_ConvertSimulationSpaceLocation); - if (From == To) + if (!Edge.IsValid() || CachedCapsule.Radius <= 0.0f) { - return InLocation; + return false; } - const FSimulationSpaceCache CacheFrom = GetSimulationSpaceCacheFor(Output, From); - const FSimulationSpaceCache CacheTo = GetSimulationSpaceCacheFor(Output, To); - return ConvertSimulationSpaceLocationCached(CacheFrom, CacheTo, InLocation); -} - -FQuat FAnimNode_KawaiiPhysics::ConvertSimulationSpaceRotation(FComponentSpacePoseContext& Output, - const EKawaiiPhysicsSimulationSpace From, - const EKawaiiPhysicsSimulationSpace To, - const FQuat& InRotation) const -{ - SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_ConvertSimulationSpaceRotation); - if (From == To) + if (Edge.BoneIndex1 >= ModifyBones.Num() || Edge.BoneIndex2 >= ModifyBones.Num()) { - return InRotation; + return false; } - const FSimulationSpaceCache CacheFrom = GetSimulationSpaceCacheFor(Output, From); - const FSimulationSpaceCache CacheTo = GetSimulationSpaceCacheFor(Output, To); - return ConvertSimulationSpaceRotationCached(CacheFrom, CacheTo, InRotation); -} + FKawaiiPhysicsModifyBone& Bone1 = ModifyBones[Edge.BoneIndex1]; + FKawaiiPhysicsModifyBone& Bone2 = ModifyBones[Edge.BoneIndex2]; -void FAnimNode_KawaiiPhysics::ConvertSimulationSpace(FComponentSpacePoseContext& Output, - const EKawaiiPhysicsSimulationSpace From, - const EKawaiiPhysicsSimulationSpace To) -{ - SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_ConvertSimulationSpace); - if (From == To) + // Skip if either bone should skip simulation + if (Bone1.bSkipSimulate || Bone2.bSkipSimulate) { - return; + return false; } - const FSimulationSpaceCache CacheFrom = GetSimulationSpaceCacheFor(Output, From); - const FSimulationSpaceCache CacheTo = GetSimulationSpaceCacheFor(Output, To); - ConvertSimulationSpaceCached(CacheFrom, CacheTo, From, To); -} + // Find closest points between edge segment and capsule axis segment (using pre-cached endpoints) + FVector ClosestOnEdge, ClosestOnCapsule; + FMath::SegmentDistToSegmentSafe( + Bone1.Location, Bone2.Location, + CachedCapsule.Start, CachedCapsule.End, + ClosestOnEdge, ClosestOnCapsule); -FAnimNode_KawaiiPhysics::FSimulationSpaceCache FAnimNode_KawaiiPhysics::BuildSimulationSpaceCache( - FComponentSpacePoseContext& Output, - const EKawaiiPhysicsSimulationSpace SimulationSpaceForCache) const -{ - FSimulationSpaceCache Cache; + // Calculate distance and check for collision + const FVector Delta = ClosestOnEdge - ClosestOnCapsule; + const float Distance = Delta.Size(); - switch (SimulationSpaceForCache) + // Combined collision distance: capsule radius + average bone radius + threshold + const float AvgBoneRadius = (Bone1.PhysicsSettings.Radius + Bone2.PhysicsSettings.Radius) * 0.5f; + const float MinDist = CachedCapsule.Radius + AvgBoneRadius + QuadCollisionThreshold; + + if (Distance >= MinDist || Distance < KINDA_SMALL_NUMBER) { - default: - case EKawaiiPhysicsSimulationSpace::ComponentSpace: - Cache.ComponentToTargetSpace = FTransform::Identity; - Cache.TargetSpaceToComponent = FTransform::Identity; - break; + return false; // No collision + } - case EKawaiiPhysicsSimulationSpace::WorldSpace: - { - const FTransform& ComponentToWorld = Output.AnimInstanceProxy->GetComponentTransform(); - Cache.ComponentToTargetSpace = ComponentToWorld; // Component -> World - Cache.TargetSpaceToComponent = ComponentToWorld.Inverse(); // World -> Component - } - break; + // Calculate push-out vector + const FVector PushDir = Delta.GetSafeNormal(); + const float PenetrationDepth = MinDist - Distance; + const FVector PushVec = PushDir * PenetrationDepth; - case EKawaiiPhysicsSimulationSpace::BaseBoneSpace: + // Calculate parametric position on edge for weighted distribution + const FVector EdgeDir = Bone2.Location - Bone1.Location; + float T = 0.5f; // Default to middle + if (EdgeDir.SizeSquared() > KINDA_SMALL_NUMBER) + { + T = FMath::Clamp(FVector::DotProduct(ClosestOnEdge - Bone1.Location, EdgeDir) / EdgeDir.SizeSquared(), 0.0f, 1.0f); + } - if (SimulationBaseBone.IsValidToEvaluate()) - { - const FCompactPoseBoneIndex BaseBoneIndex = - SimulationBaseBone.GetCompactPoseIndex(Output.Pose.GetPose().GetBoneContainer()); - Cache.TargetSpaceToComponent = - Output.Pose.GetComponentSpaceTransform(BaseBoneIndex); // Base -> Component - Cache.ComponentToTargetSpace = Cache.TargetSpaceToComponent.Inverse(); // Component -> Base - } - break; + // Apply weighted correction to both endpoints + // T=0 means collision near Bone1, T=1 means near Bone2 + if (Bone1.ParentIndex >= 0) + { + Bone1.Location += PushVec * (1.0f - T); + } + if (Bone2.ParentIndex >= 0) + { + Bone2.Location += PushVec * T; } - return Cache; + return true; } -void FAnimNode_KawaiiPhysics::InitSyncBones(FComponentSpacePoseContext& Output) +void FAnimNode_KawaiiPhysics::AdjustByQuadCollision() { - SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_InitSyncBone); - - const FBoneContainer& BoneContainer = Output.Pose.GetPose().GetBoneContainer(); - for (FKawaiiPhysicsSyncBone& SyncBone : SyncBones) + if (!bEnableQuadCollision || CachedQuads.Num() == 0) { - InitSyncBone(Output, BoneContainer, SyncBone); + return; } -} -void FAnimNode_KawaiiPhysics::InitSyncBone(FComponentSpacePoseContext& Output, const FBoneContainer& BoneContainer, - FKawaiiPhysicsSyncBone& SyncBone) -{ - SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_InitSyncBone); + TArray CachedCapsules; + const int32 TotalCapsules = CapsuleLimits.Num() + CapsuleLimitsData.Num(); + CachedCapsules.Reserve(TotalCapsules); - SyncBone.Bone.Initialize(BoneContainer); - if (!SyncBone.Bone.IsValidToEvaluate(BoneContainer)) + for (const FCapsuleLimit& Capsule : CapsuleLimits) { - SyncBone.InitialPoseLocation = FVector::ZeroVector; - return; + if (Capsule.bEnable && Capsule.Radius > 0.0f && Capsule.Length > 0.0f) + { + const FVector CapsuleAxis = Capsule.Rotation.GetAxisZ() * Capsule.Length * 0.5f; + CachedCapsules.Emplace( + Capsule.Location + CapsuleAxis, + Capsule.Location - CapsuleAxis, + Capsule.Radius + ); + } } - SyncBone.InitialPoseLocation = - FAnimationRuntime::GetComponentSpaceTransformRefPose(BoneContainer.GetReferenceSkeleton(), - SyncBone.Bone.BoneIndex).GetLocation(); - - // cleanup - SyncBone.TargetRoots.RemoveAll([&](const FKawaiiPhysicsSyncTarget& Target) + + for (const FCapsuleLimit& Capsule : CapsuleLimitsData) { - return !Target.IsValid(BoneContainer); - }); + if (Capsule.bEnable && Capsule.Radius > 0.0f && Capsule.Length > 0.0f) + { + const FVector CapsuleAxis = Capsule.Rotation.GetAxisZ() * Capsule.Length * 0.5f; + CachedCapsules.Emplace( + Capsule.Location + CapsuleAxis, + Capsule.Location - CapsuleAxis, + Capsule.Radius + ); + } + } - for (auto& TargetRoot : SyncBone.TargetRoots) + if (CachedCapsules.Num() == 0) { - TargetRoot.ChildTargets.Empty(); + return; + } - TargetRoot.ModifyBoneIndex = ModifyBones.IndexOfByPredicate( - [&](const FKawaiiPhysicsModifyBone& ModifyBone) { return ModifyBone.BoneRef == TargetRoot.Bone; }); - if (TargetRoot.ModifyBoneIndex == INDEX_NONE || !TargetRoot.bIncludeChildBones) + for (const FQuadCollisionLimit& Quad : CachedQuads) + { + if (!Quad.IsValid()) { continue; } - // For Calculate LengthRateFromSyncTargetRoot - const float StartLength = ModifyBones[TargetRoot.ModifyBoneIndex].LengthFromRoot; - float MaxLength = StartLength; + const FVector& Corner0 = ModifyBones[Quad.BoneIndices[0]].Location; + const FVector& Corner1 = ModifyBones[Quad.BoneIndices[1]].Location; + const FVector& Corner2 = ModifyBones[Quad.BoneIndices[2]].Location; + const FVector& Corner3 = ModifyBones[Quad.BoneIndices[3]].Location; + const FVector QuadCenter = (Corner0 + Corner1 + Corner2 + Corner3) * 0.25f; - // Collect Child Bones - TArray IndicesToProcess = ModifyBones[TargetRoot.ModifyBoneIndex].ChildIndices; - while (!IndicesToProcess.IsEmpty()) - { - const int32 CurrentIndex = IndicesToProcess.Pop(); - if (!ModifyBones.IsValidIndex(CurrentIndex)) - { - continue; - } + const float AvgBoneRadius = ( + ModifyBones[Quad.BoneIndices[0]].PhysicsSettings.Radius + + ModifyBones[Quad.BoneIndices[1]].PhysicsSettings.Radius + + ModifyBones[Quad.BoneIndices[2]].PhysicsSettings.Radius + + ModifyBones[Quad.BoneIndices[3]].PhysicsSettings.Radius) * 0.25f; - TargetRoot.ChildTargets.AddUnique({CurrentIndex}); + float QuadRadius = FMath::Max( + FMath::Max((Corner0 - QuadCenter).Size(), (Corner1 - QuadCenter).Size()), + FMath::Max((Corner2 - QuadCenter).Size(), (Corner3 - QuadCenter).Size()) + ) + AvgBoneRadius + QuadCollisionThreshold; -#if WITH_EDITORONLY_DATA - TargetRoot.ChildTargets.Last().PreviewBone = ModifyBones[CurrentIndex].BoneRef; -#endif - - IndicesToProcess.Append(ModifyBones[CurrentIndex].ChildIndices); - MaxLength = FMath::Max(MaxLength, ModifyBones[CurrentIndex].LengthFromRoot); - } - - // Calculate LengthRateFromSyncTargetRoot - const float LengthRange = MaxLength - StartLength; - TargetRoot.LengthRateFromSyncTargetRoot = 0.0f; - for (auto& Target : TargetRoot.ChildTargets) + for (const FCachedCapsuleData& CachedCapsule : CachedCapsules) { - if (LengthRange > KINDA_SMALL_NUMBER) - { - Target.LengthRateFromSyncTargetRoot = - (ModifyBones[Target.ModifyBoneIndex].LengthFromRoot - StartLength) / LengthRange; - } - else + const float CenterDistance = (QuadCenter - CachedCapsule.BoundingCenter).Size(); + const float CombinedRadius = QuadRadius + CachedCapsule.BoundingRadius; + + if (CenterDistance > CombinedRadius) { - Target.LengthRateFromSyncTargetRoot = 0.0f; + continue; } - } - // Update Alpha by Length Rate & Curve - if (const FRichCurve* ScaleCurve = TargetRoot.ScaleCurveByBoneLengthRate.GetRichCurveConst(); - ScaleCurve && !ScaleCurve->IsEmpty()) - { - TargetRoot.UpdateScaleByLengthRate(ScaleCurve); - for (auto& Target : TargetRoot.ChildTargets) + for (int32 EdgeIdx = 0; EdgeIdx < 4; ++EdgeIdx) { - Target.UpdateScaleByLengthRate(ScaleCurve); + AdjustEdgeByCapsuleCollisionCached(Quad.Edges[EdgeIdx], CachedCapsule); } } } } -void FAnimNode_KawaiiPhysics::ApplySyncBones(FComponentSpacePoseContext& Output, - const FBoneContainer& BoneContainer) +void FAnimNode_KawaiiPhysics::CorrectQuadEdgeStretching() { - if (SyncBones.Num() == 0) + if (!bEnableQuadCollision || QuadCollisionEdgeStiffness <= 0.0f || CachedQuads.Num() == 0) { return; } - for (auto& SyncBone : SyncBones) - { - SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_ApplySyncBone); + // Cache curve pointer for efficiency + const FRichCurve* StiffnessCurve = QuadCollisionEdgeStiffnessCurve.GetRichCurveConst(); + const bool bUseCurve = StiffnessCurve && StiffnessCurve->GetNumKeys() > 0; - if (!SyncBone.Bone.IsValidToEvaluate(BoneContainer)) + for (const FQuadCollisionLimit& Quad : CachedQuads) + { + if (!Quad.IsValid()) { continue; } - // Calculate Delta Movement in Component Space - const FCompactPoseBoneIndex SyncBoneIndex = SyncBone.Bone.GetCompactPoseIndex(BoneContainer); - FVector DeltaMovement = Output.Pose.GetComponentSpaceTransform(SyncBoneIndex).GetLocation() - SyncBone. - InitialPoseLocation; - -#if WITH_EDITORONLY_DATA - SyncBone.DeltaDistance = DeltaMovement; -#endif - - // Apply Curve - if (const FRichCurve* ScaleCurve = SyncBone.ScaleCurveByDeltaDistance.GetRichCurveConst(); - ScaleCurve && !ScaleCurve->IsEmpty()) + for (int32 EdgeIdx = 0; EdgeIdx < 4; ++EdgeIdx) { - DeltaMovement *= ScaleCurve->Eval(DeltaMovement.Length()); - } + const FQuadEdge& Edge = Quad.Edges[EdgeIdx]; + if (!Edge.IsValid() || Edge.RestLength <= 0.0f) + { + continue; + } - // Apply Global Alpha - DeltaMovement *= SyncBone.GlobalScale; + FKawaiiPhysicsModifyBone& Bone1 = ModifyBones[Edge.BoneIndex1]; + FKawaiiPhysicsModifyBone& Bone2 = ModifyBones[Edge.BoneIndex2]; -#if WITH_EDITORONLY_DATA - SyncBone.ScaledDeltaDistance = DeltaMovement; -#endif - - // Filter direction once per SyncBone in Component Space - auto CheckDirection = [](const float Val, const ESyncBoneDirection Dir) - { - return (Dir == ESyncBoneDirection::Both) || - (Dir == ESyncBoneDirection::Positive && Val > 0.0) || - (Dir == ESyncBoneDirection::Negative && Val < 0.0); - }; + if (Bone1.bSkipSimulate || Bone2.bSkipSimulate) + { + continue; + } - FVector FilteredDeltaMovement( - CheckDirection(DeltaMovement.X, SyncBone.ApplyDirectionX) ? DeltaMovement.X : 0.0, - CheckDirection(DeltaMovement.Y, SyncBone.ApplyDirectionY) ? DeltaMovement.Y : 0.0, - CheckDirection(DeltaMovement.Z, SyncBone.ApplyDirectionZ) ? DeltaMovement.Z : 0.0 - ); + const FVector EdgeVec = Bone2.Location - Bone1.Location; + const float CurrentLength = EdgeVec.Size(); - if (FilteredDeltaMovement.IsNearlyZero()) - { - continue; - } - - // Convert to Simulation Space - FilteredDeltaMovement = ConvertSimulationSpaceVector(Output, - EKawaiiPhysicsSimulationSpace::ComponentSpace, - SimulationSpace, FilteredDeltaMovement); + if (CurrentLength <= Edge.RestLength || CurrentLength < KINDA_SMALL_NUMBER) + { + continue; + } - // Cache SyncBone location in Simulation Space for distance attenuation - const FVector SyncBoneLocationInSimulationSpace = ConvertSimulationSpaceLocation( - Output, - EKawaiiPhysicsSimulationSpace::ComponentSpace, - SimulationSpace, - Output.Pose.GetComponentSpaceTransform(SyncBoneIndex).GetLocation() - ); + float Stiffness = QuadCollisionEdgeStiffness; + if (bUseCurve) + { + const float LengthRate = FMath::Max(Bone1.LengthRateFromRoot, Bone2.LengthRateFromRoot); + Stiffness *= StiffnessCurve->Eval(LengthRate); + } - // Helper: compute attenuation alpha for a distance - auto CalcAttenuationAlpha = [&](const float Distance) -> float - { - if (!SyncBone.bEnableDistanceAttenuation) + if (Stiffness <= KINDA_SMALL_NUMBER) { - return 1.0f; + continue; } - const float Inner = SyncBone.AttenuationInnerRadius; - const float Outer = SyncBone.AttenuationOuterRadius; - const float MaxAtten = SyncBone.MaxAttenuationRate; + const FVector EdgeDir = EdgeVec / CurrentLength; + const float Stretch = CurrentLength - Edge.RestLength; + const float Correction = Stretch * FMath::Clamp(Stiffness, 0.0f, 1.0f) * 0.5f; - // Safety: if outer <= inner, treat as step function at inner - const float EffectiveOuter = FMath::Max(Outer, Inner); + const bool bBone1CanMove = Bone1.ParentIndex >= 0; + const bool bBone2CanMove = Bone2.ParentIndex >= 0; - float AttenAmount; - if (Distance <= Inner) + if (bBone1CanMove && bBone2CanMove) { - AttenAmount = 0.0f; + Bone1.Location += EdgeDir * Correction; + Bone2.Location -= EdgeDir * Correction; } - else if (Distance >= EffectiveOuter) + else if (bBone1CanMove) { - AttenAmount = MaxAtten; + Bone1.Location += EdgeDir * (Correction * 2.0f); } - else + else if (bBone2CanMove) { - const float Denom = EffectiveOuter - Inner; - const float T = (Denom > KINDA_SMALL_NUMBER) ? ((Distance - Inner) / Denom) : 1.0f; - AttenAmount = T * MaxAtten; + // Only Bone2 can move, apply full correction to it + Bone2.Location -= EdgeDir * (Correction * 2.0f); } + } + } +} - // Convert attenuation amount to alpha multiplier - return FMath::Max(0.0f, 1.0f - AttenAmount); - }; +void FAnimNode_KawaiiPhysics::WarmUp(FComponentSpacePoseContext& Output, const FBoneContainer& BoneContainer, + FTransform& ComponentTransform) +{ + SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_WarmUp); - // Apply to Targets - for (auto& TargetRoot : SyncBone.TargetRoots) - { - // Update Alpha by Length Rate & Curve - // TODO : Need flag to optimize for skip updating Scale after InitSyncBone - if (const FRichCurve* ScaleCurve = TargetRoot.ScaleCurveByBoneLengthRate.GetRichCurveConst(); - ScaleCurve && !ScaleCurve->IsEmpty()) + for (int32 i = 0; i < WarmUpFrames; ++i) + { + SimulateModifyBones(Output, ComponentTransform); + } +} + +void FAnimNode_KawaiiPhysics::InitBoneConstraints() +{ + MergedBoneConstraints = BoneConstraints; + MergedBoneConstraints.Append(BoneConstraintsData); + + TArray DummyBoneConstraint; + for (FModifyBoneConstraint& Constraint : MergedBoneConstraints) + { + Constraint.ModifyBoneIndex1 = + ModifyBones.IndexOfByPredicate([Constraint](const FKawaiiPhysicsModifyBone& ModifyBone) { - TargetRoot.UpdateScaleByLengthRate(ScaleCurve); - for (auto& Target : TargetRoot.ChildTargets) - { - Target.UpdateScaleByLengthRate(ScaleCurve); - } - } + return ModifyBone.BoneRef == Constraint.Bone1; + }); + if (Constraint.ModifyBoneIndex1 < 0) + { + continue; + } - // Root target + Constraint.ModifyBoneIndex2 = + ModifyBones.IndexOfByPredicate([Constraint](const FKawaiiPhysicsModifyBone& ModifyBone) { - const int32 ModifyBoneIndex = TargetRoot.ModifyBoneIndex; - if (ModifyBones.IsValidIndex(ModifyBoneIndex)) + return ModifyBone.BoneRef == Constraint.Bone2; + }); + if (Constraint.ModifyBoneIndex2 < 0) + { + continue; + } + + Constraint.Length = + (ModifyBones[Constraint.ModifyBoneIndex1].Location - ModifyBones[Constraint.ModifyBoneIndex2].Location). + Size(); + + // DummyBone"s constraint + if (bAutoAddChildDummyBoneConstraint) + { + const int32 ChildDummyBoneIndex1 = ModifyBones[Constraint.ModifyBoneIndex1].ChildIndices.IndexOfByPredicate( + [&](int32 Index) { - const float Dist = FVector::Dist(SyncBoneLocationInSimulationSpace, - ModifyBones[ModifyBoneIndex].Location); - const float AlphaMul = CalcAttenuationAlpha(Dist); - TargetRoot.Apply(ModifyBones, FilteredDeltaMovement * AlphaMul); - } - else + return Index >= 0 && ModifyBones[Index].bDummy == true; + }); + const int32 ChildDummyBoneIndex2 = ModifyBones[Constraint.ModifyBoneIndex2].ChildIndices.IndexOfByPredicate( + [&](int32 Index) { - TargetRoot.Apply(ModifyBones, FilteredDeltaMovement); - } + return Index >= 0 && ModifyBones[Index].bDummy == true; + }); + + if (ChildDummyBoneIndex1 >= 0 && ChildDummyBoneIndex2 >= 0) + { + FModifyBoneConstraint NewDummyBoneConstraint; + NewDummyBoneConstraint.ModifyBoneIndex1 = ModifyBones[Constraint.ModifyBoneIndex1].ChildIndices[ + ChildDummyBoneIndex1]; + NewDummyBoneConstraint.ModifyBoneIndex2 = ModifyBones[Constraint.ModifyBoneIndex2].ChildIndices[ + ChildDummyBoneIndex2]; + NewDummyBoneConstraint.Length = + (ModifyBones[NewDummyBoneConstraint.ModifyBoneIndex1].Location - ModifyBones[NewDummyBoneConstraint. + ModifyBoneIndex2].Location). + Size(); + NewDummyBoneConstraint.bIsDummy = true; + DummyBoneConstraint.Add(NewDummyBoneConstraint); } + } + } + + MergedBoneConstraints.Append(DummyBoneConstraint); + + // Initialize and build quads for quad collision (experimental) + InitQuadCollisionBoneConstraints(); + BuildQuadsFromConstraints(); +} + +void FAnimNode_KawaiiPhysics::ApplySimulateResult(FComponentSpacePoseContext& Output, + const FBoneContainer& BoneContainer, + TArray& OutBoneTransforms) +{ + for (int32 i = 0; i < ModifyBones.Num(); ++i) + { + FTransform PoseTransform = FTransform(ModifyBones[i].PoseRotation, ModifyBones[i].PoseLocation, + ModifyBones[i].PoseScale); + PoseTransform = + ConvertSimulationSpaceTransform(Output, SimulationSpace, EKawaiiPhysicsSimulationSpace::ComponentSpace, PoseTransform); + OutBoneTransforms.Add(FBoneTransform(ModifyBones[i].BoneRef.GetCompactPoseIndex(BoneContainer), PoseTransform)); + } - // Child targets - for (auto& Target : TargetRoot.ChildTargets) + + for (int32 i = 0; i < ModifyBones.Num(); ++i) + { + FKawaiiPhysicsModifyBone& Bone = ModifyBones[i]; + if (!Bone.HasParent()) + { + continue; + } + + FKawaiiPhysicsModifyBone& ParentBone = ModifyBones[Bone.ParentIndex]; + + if (ParentBone.ChildIndices.Num() <= 1) + { + if (ParentBone.BoneRef.BoneIndex >= 0) { - const int32 ModifyBoneIndex = Target.ModifyBoneIndex; - if (ModifyBones.IsValidIndex(ModifyBoneIndex)) + FVector PoseVector = Bone.PoseLocation - ParentBone.PoseLocation; + FVector SimulateVector = Bone.Location - ParentBone.Location; + + if (PoseVector.GetSafeNormal() == SimulateVector.GetSafeNormal()) { - const float Dist = FVector::Dist(SyncBoneLocationInSimulationSpace, - ModifyBones[ModifyBoneIndex].Location); - const float AlphaMul = CalcAttenuationAlpha(Dist); - Target.Apply(ModifyBones, FilteredDeltaMovement * AlphaMul); + continue; } - else + + if (BoneForwardAxis == EBoneForwardAxis::X_Negative || BoneForwardAxis == EBoneForwardAxis::Y_Negative + || BoneForwardAxis == EBoneForwardAxis::Z_Negative) { - Target.Apply(ModifyBones, FilteredDeltaMovement); + PoseVector *= -1; + SimulateVector *= -1; } + + FQuat SimulateRotation = + FQuat::FindBetweenVectors(PoseVector, SimulateVector) * ParentBone.PoseRotation; + ParentBone.PrevRotation = SimulateRotation; + + SimulateRotation = + ConvertSimulationSpaceRotation(Output, SimulationSpace, + EKawaiiPhysicsSimulationSpace::ComponentSpace, SimulateRotation); + OutBoneTransforms[Bone.ParentIndex].Transform.SetRotation(SimulateRotation); } } + + if (Bone.BoneRef.BoneIndex >= 0 && !Bone.bDummy) + { + OutBoneTransforms[i].Transform.SetLocation( + ConvertSimulationSpaceLocation(Output, SimulationSpace, EKawaiiPhysicsSimulationSpace::ComponentSpace, + Bone.Location)); + } + } + + OutBoneTransforms.RemoveAll([](const FBoneTransform& BoneTransform) + { + return BoneTransform.BoneIndex < 0; + }); + + // for check in FCSPose::LocalBlendCSBoneTransforms + OutBoneTransforms.Sort(FCompareBoneTransformIndex()); +} + +FTransform FAnimNode_KawaiiPhysics::GetBoneTransformInSimSpace(FComponentSpacePoseContext& Output, + const FCompactPoseBoneIndex& BoneIndex) const +{ + return ConvertSimulationSpaceTransform(Output, EKawaiiPhysicsSimulationSpace::ComponentSpace, SimulationSpace, + Output.Pose.GetComponentSpaceTransform(BoneIndex)); +} + +FTransform FAnimNode_KawaiiPhysics::ConvertSimulationSpaceTransform(const FComponentSpacePoseContext& Output, + const EKawaiiPhysicsSimulationSpace From, + const EKawaiiPhysicsSimulationSpace To, + const FTransform& InTransform) const +{ + if (From == To) + { + return InTransform; + } + + FTransform ResultTransform = InTransform; + + // From -> ComponentSpace + if (From == EKawaiiPhysicsSimulationSpace::WorldSpace) + { + ResultTransform = ResultTransform.GetRelativeTransform(Output.AnimInstanceProxy->GetComponentTransform()); + } + else if (From == EKawaiiPhysicsSimulationSpace::BaseBoneSpace) + { + ResultTransform = ResultTransform * BaseBoneSpace2ComponentSpace; + } + + // ComponentSpace -> To + if (To == EKawaiiPhysicsSimulationSpace::WorldSpace) + { + ResultTransform = ResultTransform * Output.AnimInstanceProxy->GetComponentTransform(); + } + else if (To == EKawaiiPhysicsSimulationSpace::BaseBoneSpace) + { + ResultTransform = ResultTransform.GetRelativeTransform(BaseBoneSpace2ComponentSpace); + } + + return ResultTransform; +} + +FVector FAnimNode_KawaiiPhysics::ConvertSimulationSpaceVector(const FComponentSpacePoseContext& Output, + const EKawaiiPhysicsSimulationSpace From, + const EKawaiiPhysicsSimulationSpace To, const FVector& InVector) const +{ + SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_ConvertSimulationSpaceVector); + if (From == To) + { + return InVector; + } + + FVector ResultVector = InVector; + + // From -> ComponentSpace + if (From == EKawaiiPhysicsSimulationSpace::WorldSpace) + { + ResultVector = Output.AnimInstanceProxy->GetComponentTransform().InverseTransformVector(ResultVector); + } + else if (From == EKawaiiPhysicsSimulationSpace::BaseBoneSpace) + { + ResultVector = BaseBoneSpace2ComponentSpace.TransformVector(ResultVector); + } + + // ComponentSpace -> To + if (To == EKawaiiPhysicsSimulationSpace::WorldSpace) + { + ResultVector = Output.AnimInstanceProxy->GetComponentTransform().TransformVector(ResultVector); + } + else if (To == EKawaiiPhysicsSimulationSpace::BaseBoneSpace) + { + ResultVector = BaseBoneSpace2ComponentSpace.InverseTransformVector(ResultVector); + } + return ResultVector; +} + +FVector FAnimNode_KawaiiPhysics::ConvertSimulationSpaceLocation(const FComponentSpacePoseContext& Output, + const EKawaiiPhysicsSimulationSpace From, const EKawaiiPhysicsSimulationSpace To, + const FVector& InLocation) const +{ + SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_ConvertSimulationSpaceLocation); + if (From == To) + { + return InLocation; + } + + FVector ResultLocation = InLocation; + + // From -> ComponentSpace + if (From == EKawaiiPhysicsSimulationSpace::WorldSpace) + { + ResultLocation = Output.AnimInstanceProxy->GetComponentTransform().InverseTransformPosition(ResultLocation); + } + else if (From == EKawaiiPhysicsSimulationSpace::BaseBoneSpace) + { + ResultLocation = BaseBoneSpace2ComponentSpace.TransformPosition(ResultLocation); + } + + // ComponentSpace -> To + if (To == EKawaiiPhysicsSimulationSpace::WorldSpace) + { + ResultLocation = Output.AnimInstanceProxy->GetComponentTransform().TransformPosition(ResultLocation); + } + else if (To == EKawaiiPhysicsSimulationSpace::BaseBoneSpace) + { + ResultLocation = BaseBoneSpace2ComponentSpace.InverseTransformPosition(ResultLocation); + } + + return ResultLocation; +} + +FQuat FAnimNode_KawaiiPhysics::ConvertSimulationSpaceRotation(FComponentSpacePoseContext& Output, EKawaiiPhysicsSimulationSpace From, + EKawaiiPhysicsSimulationSpace To, const FQuat& InRotation) const +{ + SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_ConvertSimulationSpaceRotation); + if (From == To) + { + return InRotation; } + + FQuat ResultRotation = InRotation; + + // From -> ComponentSpace + if (From == EKawaiiPhysicsSimulationSpace::WorldSpace) + { + ResultRotation = Output.AnimInstanceProxy->GetComponentTransform().InverseTransformRotation(ResultRotation); + } + else if (From == EKawaiiPhysicsSimulationSpace::BaseBoneSpace) + { + ResultRotation = BaseBoneSpace2ComponentSpace.TransformRotation(ResultRotation); + } + + // ComponentSpace -> To + if (To == EKawaiiPhysicsSimulationSpace::WorldSpace) + { + ResultRotation = Output.AnimInstanceProxy->GetComponentTransform().TransformRotation(ResultRotation); + } + else if (To == EKawaiiPhysicsSimulationSpace::BaseBoneSpace) + { + ResultRotation = BaseBoneSpace2ComponentSpace.InverseTransformRotation(ResultRotation); + } + + return ResultRotation; } +void FAnimNode_KawaiiPhysics::ConvertSimulationSpace(FComponentSpacePoseContext& Output, EKawaiiPhysicsSimulationSpace From, + EKawaiiPhysicsSimulationSpace To) +{ + SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_ConvertSimulationSpace); + for (FKawaiiPhysicsModifyBone& Bone : ModifyBones) + { + Bone.Location = ConvertSimulationSpaceLocation(Output, From, To, Bone.Location); + Bone.PrevLocation = ConvertSimulationSpaceLocation(Output, From, To, Bone.PrevLocation); + Bone.PrevRotation = ConvertSimulationSpaceRotation(Output, From, To, Bone.PrevRotation); + } +} diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/KawaiiPhysics.cpp b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/KawaiiPhysics.cpp index c2d28bf..6b69b65 100644 --- a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/KawaiiPhysics.cpp +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/KawaiiPhysics.cpp @@ -1,4 +1,4 @@ -// Copyright 2019-2026 pafuhana1213. All Rights Reserved. +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License #include "KawaiiPhysics.h" #include "Modules/ModuleManager.h" @@ -17,5 +17,5 @@ void FKawaiiPhysicsModule::ShutdownModule() } #undef LOCTEXT_NAMESPACE - -IMPLEMENT_MODULE(FKawaiiPhysicsModule, KawaiiPhysics) + +IMPLEMENT_MODULE(FKawaiiPhysicsModule, KawaiiPhysics) \ No newline at end of file diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/KawaiiPhysicsBoneConstraintsDataAsset.cpp b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/KawaiiPhysicsBoneConstraintsDataAsset.cpp index f8318e5..401be12 100644 --- a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/KawaiiPhysicsBoneConstraintsDataAsset.cpp +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/KawaiiPhysicsBoneConstraintsDataAsset.cpp @@ -1,4 +1,4 @@ -// Copyright 2019-2026 pafuhana1213. All Rights Reserved. +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License #include "KawaiiPhysicsBoneConstraintsDataAsset.h" @@ -10,7 +10,6 @@ #include "Editor.h" #endif -#include UE_INLINE_GENERATED_CPP_BY_NAME(KawaiiPhysicsBoneConstraintsDataAsset) struct FBoneConstraintDataCustomVersion { @@ -91,8 +90,7 @@ void UKawaiiPhysicsBoneConstraintsDataAsset::PostLoad() } } -USkeleton* UKawaiiPhysicsBoneConstraintsDataAsset::GetSkeleton(bool& bInvalidSkeletonIsError, - const IPropertyHandle* PropertyHandle) +USkeleton* UKawaiiPhysicsBoneConstraintsDataAsset::GetSkeleton(bool& bInvalidSkeletonIsError) { #if WITH_EDITOR return PreviewSkeleton.LoadSynchronous(); diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/KawaiiPhysicsCustomExternalForce.cpp b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/KawaiiPhysicsCustomExternalForce.cpp index ea04f09..0af6497 100644 --- a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/KawaiiPhysicsCustomExternalForce.cpp +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/KawaiiPhysicsCustomExternalForce.cpp @@ -1,5 +1,4 @@ -// Copyright 2019-2026 pafuhana1213. All Rights Reserved. +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License #include "KawaiiPhysicsCustomExternalForce.h" -#include UE_INLINE_GENERATED_CPP_BY_NAME(KawaiiPhysicsCustomExternalForce) diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/KawaiiPhysicsExternalForce.cpp b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/KawaiiPhysicsExternalForce.cpp new file mode 100644 index 0000000..0e929ce --- /dev/null +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/KawaiiPhysicsExternalForce.cpp @@ -0,0 +1,374 @@ +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License + +#include "KawaiiPhysicsExternalForce.h" + +#include "SceneInterface.h" +#include "GameFramework/Character.h" +#include "GameFramework/CharacterMovementComponent.h" + +DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_ExternalForce_Basic_Apply"), STAT_KawaiiPhysics_ExternalForce_Basic_Apply, + STATGROUP_Anim); +DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_ExternalForce_Gravity_Apply"), STAT_KawaiiPhysics_ExternalForce_Gravity_Apply, + STATGROUP_Anim); +DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_ExternalForce_Curve_Apply"), STAT_KawaiiPhysics_ExternalForce_Curve_Apply, + STATGROUP_Anim); +DECLARE_CYCLE_STAT(TEXT("KawaiiPhysics_ExternalForce_Wind_Apply"), STAT_KawaiiPhysics_ExternalForce_Wind_Apply, + STATGROUP_Anim); + +/// +/// Basic +/// +void UKawaiiPhysics_ExternalForce_Basic::PreApply(FAnimNode_KawaiiPhysics& Node, + const USkeletalMeshComponent* SkelComp) +{ + Super::PreApply(Node, SkelComp); + + PrevTime = Time; + Time += Node.DeltaTime; + if (Interval > 0.0f) + { + if (Time > Interval) + { + Force = ForceDir * RandomizedForceScale; + Time = FMath::Fmod(Time, Interval); + } + else + { + Force = FVector::ZeroVector; + } + } + else + { + Force = ForceDir * RandomizedForceScale; + } + + if (ExternalForceSpace == EExternalForceSpace::WorldSpace) + { + Force = ComponentTransform.InverseTransformVector(Force); + } +} + +void UKawaiiPhysics_ExternalForce_Basic::Apply(FKawaiiPhysicsModifyBone& Bone, FAnimNode_KawaiiPhysics& Node, + const FComponentSpacePoseContext& PoseContext, const FTransform& BoneTM) +{ + if (!CanApply(Bone)) + { + return; + } + + SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_ExternalForce_Basic_Apply); + + float ForceRate = 1.0f; + const FRichCurve* CurveBasic = ForceRateByBoneLengthRate.GetRichCurve(); + if (CurveBasic && CurveBasic->GetNumKeys() > 0) + { + ForceRate = CurveBasic->Eval(Bone.LengthRateFromRoot); + } + + if (ExternalForceSpace == EExternalForceSpace::BoneSpace) + { + const FVector BoneForce = BoneTM.TransformVector(Force); + Bone.Location += BoneForce * ForceRate * Node.DeltaTime; + +#if ENABLE_ANIM_DEBUG + if (IsInGameThread()) + { + BoneForceMap.Add(Bone.BoneRef.BoneName, BoneForce); + } +#endif + } + else + { + Bone.Location += Force * ForceRate * Node.DeltaTime; + +#if ENABLE_ANIM_DEBUG + if (IsInGameThread()) + { + BoneForceMap.Add(Bone.BoneRef.BoneName, Force * ForceRate); + } +#endif + } +} + +/// +/// Gravity +/// +void UKawaiiPhysics_ExternalForce_Gravity::PreApply(FAnimNode_KawaiiPhysics& Node, + const USkeletalMeshComponent* SkelComp) +{ + Super::PreApply(Node, SkelComp); + + Force = bUseOverrideGravityDirection ? OverrideGravityDirection : FVector(0, 0, -1.0f); + + // For Character's Custom Gravity Direction + if (const ACharacter* Character = Cast(SkelComp->GetOwner())) + { + + if (bUseCharacterGravityScale) + { + if (const UCharacterMovementComponent* CharacterMovementComponent = Character-> + GetCharacterMovement()) + { + Force *= CharacterMovementComponent->GetGravityZ(); + } + } + } + + Force *= RandomizedForceScale; + Force = ComponentTransform.InverseTransformVector(Force); +} + +void UKawaiiPhysics_ExternalForce_Gravity::Apply(FKawaiiPhysicsModifyBone& Bone, FAnimNode_KawaiiPhysics& Node, + const FComponentSpacePoseContext& PoseContext, + const FTransform& BoneTM) +{ + if (!CanApply(Bone)) + { + return; + } + + SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_ExternalForce_Gravity_Apply); + + float ForceRate = 1.0f; + const FRichCurve* CurveGravity = ForceRateByBoneLengthRate.GetRichCurve(); + if (CurveGravity && CurveGravity->GetNumKeys() > 0) + { + ForceRate = CurveGravity->Eval(Bone.LengthRateFromRoot); + } + + Bone.Location += 0.5f * Force * ForceRate * Node.DeltaTime * Node.DeltaTime; + +#if ENABLE_ANIM_DEBUG + if (IsInGameThread()) + { + BoneForceMap.Add(Bone.BoneRef.BoneName, Force * ForceRate); + AnimDrawDebug(Bone, Node, PoseContext); + } +#endif +} + +/// +/// Curve +/// +FVector UKawaiiPhysics_ExternalForce_Curve::GetForceValue(float InTime) const +{ + FVector Result = FVector::ZeroVector; + + if (const FRichCurve* CurveX = ForceCurveX.GetRichCurveConst()) + { + Result.X = CurveX->Eval(InTime); + } + if (const FRichCurve* CurveY = ForceCurveY.GetRichCurveConst()) + { + Result.Y = CurveY->Eval(InTime); + } + if (const FRichCurve* CurveZ = ForceCurveZ.GetRichCurveConst()) + { + Result.Z = CurveZ->Eval(InTime); + } + + return Result; +} + +void UKawaiiPhysics_ExternalForce_Curve::InitMaxCurveTime() +{ + MaxCurveTime = 0.0f; + + if (const FRichCurve* CurveX = ForceCurveX.GetRichCurveConst()) + { + if (CurveX->GetNumKeys() > 0) + { + MaxCurveTime = FMath::Max(MaxCurveTime, CurveX->GetLastKey().Time); + } + } + if (const FRichCurve* CurveY = ForceCurveY.GetRichCurveConst()) + { + if (CurveY->GetNumKeys() > 0) + { + MaxCurveTime = FMath::Max(MaxCurveTime, CurveY->GetLastKey().Time); + } + } + if (const FRichCurve* CurveZ = ForceCurveZ.GetRichCurveConst()) + { + if (CurveZ->GetNumKeys() > 0) + { + MaxCurveTime = FMath::Max(MaxCurveTime, CurveZ->GetLastKey().Time); + } + } +} + +void UKawaiiPhysics_ExternalForce_Curve::Initialize(const FAnimationInitializeContext& Context) +{ + UKawaiiPhysics_ExternalForce::Initialize(Context); + + InitMaxCurveTime(); +} + +void UKawaiiPhysics_ExternalForce_Curve::PreApply(FAnimNode_KawaiiPhysics& Node, const USkeletalMeshComponent* SkelComp) +{ + Super::PreApply(Node, SkelComp); + +#if WITH_EDITOR + InitMaxCurveTime(); +#endif + + PrevTime = Time; + + if (CurveEvaluateType == EExternalForceCurveEvaluateType::Single) + { + Time += Node.DeltaTime * TimeScale; + if (MaxCurveTime > 0 && Time > MaxCurveTime) + { + Time = FMath::Fmod(Time, MaxCurveTime); + } + Force = GetForceValue(Time) * RandomizedForceScale; + } + else + { + TArray CurveValues; + const float SubStep = Node.DeltaTime * TimeScale / SubstepCount; + + for (int i = 0; i < SubstepCount; ++i) + { + Time += SubStep; + if (MaxCurveTime > 0 && Time > MaxCurveTime) + { + Time = FMath::Fmod(Time, MaxCurveTime); + } + CurveValues.Add(GetForceValue(Time)); + } + + Force = FVector::ZeroVector; + switch (CurveEvaluateType) + { + case EExternalForceCurveEvaluateType::Average: + for (const auto& CurveValue : CurveValues) + { + Force += CurveValue; + } + Force /= static_cast(SubstepCount); + + break; + + case EExternalForceCurveEvaluateType::Max: + Force = FVector(FLT_MIN); + for (const auto& CurveValue : CurveValues) + { + Force.X = FMath::Max(Force.X, CurveValue.X); + Force.Y = FMath::Max(Force.Y, CurveValue.Y); + Force.Z = FMath::Max(Force.Z, CurveValue.Z); + } + break; + case EExternalForceCurveEvaluateType::Min: + Force = FVector(FLT_MAX); + for (const auto& CurveValue : CurveValues) + { + Force.X = FMath::Min(Force.X, CurveValue.X); + Force.Y = FMath::Min(Force.Y, CurveValue.Y); + Force.Z = FMath::Min(Force.Z, CurveValue.Z); + } + break; + default: + break; + } + + Force *= RandomizedForceScale; + } + + if (ExternalForceSpace == EExternalForceSpace::WorldSpace) + { + Force = ComponentTransform.InverseTransformVector(Force); + } +} + +void UKawaiiPhysics_ExternalForce_Curve::Apply(FKawaiiPhysicsModifyBone& Bone, FAnimNode_KawaiiPhysics& Node, + const FComponentSpacePoseContext& PoseContext, const FTransform& BoneTM) +{ + if (!CanApply(Bone)) + { + return; + } + + SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_ExternalForce_Curve_Apply); + + float ForceRate = 1.0f; + const FRichCurve* Curve = ForceRateByBoneLengthRate.GetRichCurve(); + if (Curve && Curve->GetNumKeys() > 0) + { + ForceRate = Curve->Eval(Bone.LengthRateFromRoot); + } + + if (ExternalForceSpace == EExternalForceSpace::BoneSpace) + { + const FVector BoneForce = BoneTM.TransformVector(Force); + Bone.Location += BoneForce * ForceRate * Node.DeltaTime; + +#if ENABLE_ANIM_DEBUG + if (IsInGameThread()) + { + BoneForceMap.Add(Bone.BoneRef.BoneName, BoneForce * ForceRate); + } +#endif + } + else + { + Bone.Location += Force * ForceRate * Node.DeltaTime; + +#if ENABLE_ANIM_DEBUG + if (IsInGameThread()) + { + BoneForceMap.Add(Bone.BoneRef.BoneName, Force * ForceRate); + } +#endif + } + +#if ENABLE_ANIM_DEBUG + if (IsInGameThread()) + { + AnimDrawDebug(Bone, Node, PoseContext); + } +#endif +} + +void UKawaiiPhysics_ExternalForce_Wind::PreApply(FAnimNode_KawaiiPhysics& Node, const USkeletalMeshComponent* SkelComp) +{ + Super::PreApply(Node, SkelComp); + + World = SkelComp ? SkelComp->GetWorld() : nullptr; +} + +void UKawaiiPhysics_ExternalForce_Wind::Apply(FKawaiiPhysicsModifyBone& Bone, FAnimNode_KawaiiPhysics& Node, + const FComponentSpacePoseContext& PoseContext, const FTransform& BoneTM) +{ + const FSceneInterface* Scene = World && World->Scene ? World->Scene : nullptr; + if (!CanApply(Bone) || !Scene) + { + return; + } + + SCOPE_CYCLE_COUNTER(STAT_KawaiiPhysics_ExternalForce_Wind_Apply); + + float ForceRate = 1.0f; + const FRichCurve* CurveWind = ForceRateByBoneLengthRate.GetRichCurve(); + if (CurveWind && CurveWind->GetNumKeys() > 0) + { + ForceRate = CurveWind->Eval(Bone.LengthRateFromRoot); + } + + FVector WindDirection = FVector::ZeroVector; + float WindSpeed, WindMinGust, WindMaxGust = 0.0f; + Scene->GetWindParameters(ComponentTransform.TransformPosition(Bone.PoseLocation), WindDirection, + WindSpeed, WindMinGust, WindMaxGust); + WindDirection = ComponentTransform.InverseTransformVector(WindDirection); + WindDirection *= WindSpeed; + + Bone.Location += WindDirection * ForceRate * RandomizedForceScale * Node.DeltaTime; + +#if ENABLE_ANIM_DEBUG + if (IsInGameThread()) + { + BoneForceMap.Add(Bone.BoneRef.BoneName, WindDirection * ForceRate * RandomizedForceScale); + AnimDrawDebug(Bone, Node, PoseContext); + } +#endif +} diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/KawaiiPhysicsLibrary.cpp b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/KawaiiPhysicsLibrary.cpp index 66d29c3..77e88c8 100644 --- a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/KawaiiPhysicsLibrary.cpp +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/KawaiiPhysicsLibrary.cpp @@ -1,234 +1,96 @@ -// Copyright 2019-2026 pafuhana1213. All Rights Reserved. +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License #include "KawaiiPhysicsLibrary.h" -#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 6 -#include "Animation/AnimInstance.h" -#endif - #include "AnimNode_KawaiiPhysics.h" #include "BlueprintGameplayTagLibrary.h" -#include "ExternalForces/KawaiiPhysicsExternalForce.h" - -#include UE_INLINE_GENERATED_CPP_BY_NAME(KawaiiPhysicsLibrary) +#include "KawaiiPhysicsExternalForce.h" +#include "Animation/AnimClassInterface.h" DEFINE_LOG_CATEGORY_STATIC(LogKawaiiPhysicsLibrary, Verbose, All); -FKawaiiPhysicsReference UKawaiiPhysicsLibrary::ConvertToKawaiiPhysics(const FAnimNodeReference& Node, - EAnimNodeReferenceConversionResult& Result) -{ - return FAnimNodeReference::ConvertToType(Node, Result); -} - -bool UKawaiiPhysicsLibrary::CollectKawaiiPhysicsNodes(TArray& Nodes, +void UKawaiiPhysicsLibrary::CollectKawaiiPhysicsNodes(TArray& OutNodes, UAnimInstance* AnimInstance, - const FGameplayTagContainer& FilterTags, bool bFilterExactMatch) + const FGameplayTagContainer& FilterTags, + bool bFilterExactMatch) { - if (!ensure(AnimInstance && AnimInstance->GetClass())) + if (!AnimInstance || !AnimInstance->GetClass()) { - return false; + return; } - bool bResult = false; if (const IAnimClassInterface* AnimClassInterface = - IAnimClassInterface::GetFromClass((AnimInstance->GetClass()))) + IAnimClassInterface::GetFromClass(AnimInstance->GetClass())) { const TArray& AnimNodeProperties = AnimClassInterface->GetAnimNodeProperties(); for (int i = 0; i < AnimNodeProperties.Num(); ++i) { - if (AnimNodeProperties[i]->Struct-> - IsChildOf(FKawaiiPhysicsReference::FInternalNodeType::StaticStruct())) + if (AnimNodeProperties[i]->Struct->IsChildOf(FAnimNode_KawaiiPhysics::StaticStruct())) { - EAnimNodeReferenceConversionResult Result; - FKawaiiPhysicsReference KawaiiPhysicsReference = ConvertToKawaiiPhysics( - FAnimNodeReference(AnimInstance, i), Result); - - if (Result == EAnimNodeReferenceConversionResult::Succeeded) + FAnimNode_KawaiiPhysics* Node = AnimNodeProperties[i]->ContainerPtrToValuePtr(AnimInstance); + if (Node) { - auto& Tag = KawaiiPhysicsReference.GetAnimNode().KawaiiPhysicsTag; + // Check tag filter if (FilterTags.IsEmpty() || UBlueprintGameplayTagLibrary::MatchesAnyTags( - Tag, FilterTags, bFilterExactMatch)) + Node->KawaiiPhysicsTag, FilterTags, bFilterExactMatch)) { - Nodes.Add(KawaiiPhysicsReference); - bResult = true; + OutNodes.Add(Node); } } } } } - - return bResult; } -bool UKawaiiPhysicsLibrary::CollectKawaiiPhysicsNodes(TArray& Nodes, +void UKawaiiPhysicsLibrary::CollectKawaiiPhysicsNodes(TArray& OutNodes, USkeletalMeshComponent* MeshComp, - const FGameplayTagContainer& FilterTags, bool bFilterExactMatch) + const FGameplayTagContainer& FilterTags, + bool bFilterExactMatch) { - if (!ensure(MeshComp)) + if (!MeshComp) { - return false; + return; } - const int NodeNum = Nodes.Num(); - if (UAnimInstance* AnimInstance = MeshComp->GetAnimInstance()) { - CollectKawaiiPhysicsNodes(Nodes, AnimInstance, FilterTags, - bFilterExactMatch); + CollectKawaiiPhysicsNodes(OutNodes, AnimInstance, FilterTags, bFilterExactMatch); } const TArray& LinkedInstances = const_cast(MeshComp)->GetLinkedAnimInstances(); for (UAnimInstance* LinkedInstance : LinkedInstances) { - CollectKawaiiPhysicsNodes(Nodes, LinkedInstance, FilterTags, - bFilterExactMatch); + CollectKawaiiPhysicsNodes(OutNodes, LinkedInstance, FilterTags, bFilterExactMatch); } if (UAnimInstance* PostProcessAnimInstance = MeshComp->GetPostProcessInstance()) { - CollectKawaiiPhysicsNodes(Nodes, PostProcessAnimInstance, FilterTags, - bFilterExactMatch); - } - - return NodeNum != Nodes.Num(); -} - -FKawaiiPhysicsReference UKawaiiPhysicsLibrary::ResetDynamics(const FKawaiiPhysicsReference& KawaiiPhysics) -{ - KawaiiPhysics.CallAnimNodeFunction( - TEXT("ResetDynamics"), - [](FAnimNode_KawaiiPhysics& InKawaiiPhysics) - { - InKawaiiPhysics.ResetDynamics(ETeleportType::ResetPhysics); - }); - - return KawaiiPhysics; -} - - -FKawaiiPhysicsReference UKawaiiPhysicsLibrary::SetRootBoneName(const FKawaiiPhysicsReference& KawaiiPhysics, - FName& RootBoneName) -{ - KawaiiPhysics.CallAnimNodeFunction( - TEXT("SetRootBoneName"), - [RootBoneName](FAnimNode_KawaiiPhysics& InKawaiiPhysics) - { - InKawaiiPhysics.RootBone = FBoneReference(RootBoneName); - }); - - return KawaiiPhysics; -} - -FName UKawaiiPhysicsLibrary::GetRootBoneName(const FKawaiiPhysicsReference& KawaiiPhysics) -{ - FName RootBoneName; - - KawaiiPhysics.CallAnimNodeFunction( - TEXT("GetRootBoneName"), - [&RootBoneName](FAnimNode_KawaiiPhysics& InKawaiiPhysics) - { - RootBoneName = InKawaiiPhysics.RootBone.BoneName; - }); - - return RootBoneName; -} - -FKawaiiPhysicsReference UKawaiiPhysicsLibrary::SetExcludeBoneNames(const FKawaiiPhysicsReference& KawaiiPhysics, - TArray& ExcludeBoneNames) -{ - KawaiiPhysics.CallAnimNodeFunction( - TEXT("SetExcludeBoneNames"), - [&ExcludeBoneNames](FAnimNode_KawaiiPhysics& InKawaiiPhysics) - { - InKawaiiPhysics.ExcludeBones.Empty(); - for (auto& ExcludeBoneName : ExcludeBoneNames) - { - InKawaiiPhysics.ExcludeBones.Add(FBoneReference(ExcludeBoneName)); - } - }); - - return KawaiiPhysics; -} - -TArray UKawaiiPhysicsLibrary::GetExcludeBoneNames(const FKawaiiPhysicsReference& KawaiiPhysics) -{ - TArray ExcludeBoneNames; - - KawaiiPhysics.CallAnimNodeFunction( - TEXT("GetExcludeBoneNames"), - [&ExcludeBoneNames](FAnimNode_KawaiiPhysics& InKawaiiPhysics) - { - for (auto& ExcludeBone : InKawaiiPhysics.ExcludeBones) - { - ExcludeBoneNames.Add(ExcludeBone.BoneName); - } - }); - - return ExcludeBoneNames; -} - -FKawaiiPhysicsReference UKawaiiPhysicsLibrary::AddExternalForceWithExecResult( - EKawaiiPhysicsAccessExternalForceResult& ExecResult, - const FKawaiiPhysicsReference& KawaiiPhysics, - FInstancedStruct& ExternalForce, UObject* Owner) -{ - ExecResult = EKawaiiPhysicsAccessExternalForceResult::NotValid; - - if (AddExternalForce(KawaiiPhysics, ExternalForce, Owner)) - { - ExecResult = EKawaiiPhysicsAccessExternalForceResult::Valid; + CollectKawaiiPhysicsNodes(OutNodes, PostProcessAnimInstance, FilterTags, bFilterExactMatch); } - - return KawaiiPhysics; -} - -bool UKawaiiPhysicsLibrary::AddExternalForce(const FKawaiiPhysicsReference& KawaiiPhysics, - FInstancedStruct& ExternalForce, UObject* Owner, bool bIsOneShot) -{ - bool bResult = false; - - if (ExternalForce.IsValid()) - { - if (auto* ExternalForcePtr = ExternalForce.GetMutablePtr()) - { - ExternalForcePtr->ExternalOwner = Owner; - ExternalForcePtr->bIsOneShot = bIsOneShot; - - KawaiiPhysics.CallAnimNodeFunction( - TEXT("AddExternalForce"), - [&](FAnimNode_KawaiiPhysics& InKawaiiPhysics) - { - InKawaiiPhysics.ExternalForces.Add(ExternalForce); - }); - - bResult = true; - } - } - - return bResult; } bool UKawaiiPhysicsLibrary::AddExternalForcesToComponent(USkeletalMeshComponent* MeshComp, - TArray& ExternalForces, + TArray& ExternalForces, UObject* Owner, FGameplayTagContainer& FilterTags, bool bFilterExactMatch, bool bIsOneShot) { bool bResult = false; - TArray KawaiiPhysicsReferences; - CollectKawaiiPhysicsNodes(KawaiiPhysicsReferences, MeshComp, FilterTags, bFilterExactMatch); - for (auto& KawaiiPhysicsReference : KawaiiPhysicsReferences) + TArray KawaiiPhysicsNodes; + CollectKawaiiPhysicsNodes(KawaiiPhysicsNodes, MeshComp, FilterTags, bFilterExactMatch); + + for (FAnimNode_KawaiiPhysics* Node : KawaiiPhysicsNodes) { - for (auto& AExternalForce : ExternalForces) + for (UKawaiiPhysics_ExternalForce* ExternalForce : ExternalForces) { - if (AExternalForce.IsValid()) + if (ExternalForce != nullptr) { - if (AddExternalForce(KawaiiPhysicsReference, AExternalForce, Owner, bIsOneShot)) - { - bResult = true; - } + ExternalForce->ExternalOwner = Owner; + ExternalForce->bIsOneShot = bIsOneShot; + Node->ExternalForces.Add(ExternalForce); + bResult = true; } } } @@ -241,159 +103,21 @@ bool UKawaiiPhysicsLibrary::RemoveExternalForcesFromComponent(USkeletalMeshCompo { bool bResult = false; - TArray KawaiiPhysicsReferences; - CollectKawaiiPhysicsNodes(KawaiiPhysicsReferences, MeshComp, FilterTags, bFilterExactMatch); - for (auto& KawaiiPhysicsReference : KawaiiPhysicsReferences) - { - KawaiiPhysicsReference.CallAnimNodeFunction( - TEXT("RemoveExternalForce"), - [&](FAnimNode_KawaiiPhysics& InKawaiiPhysics) - { - const int32 NumRemoved = InKawaiiPhysics.ExternalForces.RemoveAll([&](FInstancedStruct& InstancedStruct) - { - const auto* ExternalForcePtr = InstancedStruct.GetMutablePtr(); - return ExternalForcePtr && ExternalForcePtr->ExternalOwner == Owner; - }); - - if (NumRemoved > 0) - { - bResult = true; - } - }); - } - - return bResult; -} - -bool UKawaiiPhysicsLibrary::SetAlphaToComponent(USkeletalMeshComponent* MeshComp, float Alpha, - FGameplayTagContainer& FilterTags, bool bFilterExactMatch) -{ - bool bResult = false; + TArray KawaiiPhysicsNodes; + CollectKawaiiPhysicsNodes(KawaiiPhysicsNodes, MeshComp, FilterTags, bFilterExactMatch); - TArray KawaiiPhysicsReferences; - CollectKawaiiPhysicsNodes(KawaiiPhysicsReferences, MeshComp, FilterTags, bFilterExactMatch); - for (auto& KawaiiPhysicsReference : KawaiiPhysicsReferences) + for (FAnimNode_KawaiiPhysics* Node : KawaiiPhysicsNodes) { - KawaiiPhysicsReference.CallAnimNodeFunction( - TEXT("SetAlpha"), - [&](FAnimNode_KawaiiPhysics& InKawaiiPhysics) - { - InKawaiiPhysics.Alpha = Alpha; - bResult = true; - }); - } - - return bResult; -} - -bool UKawaiiPhysicsLibrary::GetAlphaFromComponent(USkeletalMeshComponent* MeshComp, float& OutAlpha, - FGameplayTagContainer& FilterTags, bool bFilterExactMatch) -{ - bool bResult = false; - OutAlpha = 0.0f; + const int32 NumRemoved = Node->ExternalForces.RemoveAll([&](UKawaiiPhysics_ExternalForce* ExternalForcePtr) + { + return ExternalForcePtr && ExternalForcePtr->ExternalOwner == Owner; + }); - TArray KawaiiPhysicsReferences; - CollectKawaiiPhysicsNodes(KawaiiPhysicsReferences, MeshComp, FilterTags, bFilterExactMatch); - for (auto& KawaiiPhysicsReference : KawaiiPhysicsReferences) - { - KawaiiPhysicsReference.CallAnimNodeFunction( - TEXT("GetAlpha"), - [&](FAnimNode_KawaiiPhysics& InKawaiiPhysics) - { - OutAlpha = InKawaiiPhysics.Alpha; - bResult = true; - }); - if (bResult) + if (NumRemoved > 0) { - break; + bResult = true; } } return bResult; } - -DEFINE_FUNCTION(UKawaiiPhysicsLibrary::execSetExternalForceWildcardProperty) -{ - P_GET_ENUM_REF(EKawaiiPhysicsAccessExternalForceResult, ExecResult); - P_GET_STRUCT_REF(FKawaiiPhysicsReference, KawaiiPhysics); - P_GET_PROPERTY(FIntProperty, ExternalForceIndex); - P_GET_STRUCT_REF(FName, PropertyName); - - ExecResult = EKawaiiPhysicsAccessExternalForceResult::NotValid; - - // Read wildcard Value input. - Stack.MostRecentPropertyAddress = nullptr; - Stack.MostRecentPropertyContainer = nullptr; - Stack.StepCompiledIn(nullptr); - - const FProperty* ValueProp = CastField(Stack.MostRecentProperty); - void* ValuePtr = Stack.MostRecentPropertyAddress; - - KawaiiPhysics.CallAnimNodeFunction( - TEXT("GetExternalForceWildcardProperty"), - [&ExecResult, &ExternalForceIndex, &PropertyName, &ValuePtr](FAnimNode_KawaiiPhysics& InKawaiiPhysics) - { - if (InKawaiiPhysics.ExternalForces.IsValidIndex(ExternalForceIndex) && - InKawaiiPhysics.ExternalForces[ExternalForceIndex].IsValid()) - { - const auto* ScriptStruct = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetScriptStruct(); - auto& Force = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetMutable< - FKawaiiPhysics_ExternalForce>(); - - if (const FProperty* Property = FindFProperty(ScriptStruct, PropertyName)) - { - Property->CopyCompleteValue(Property->ContainerPtrToValuePtr(&Force), ValuePtr); - ExecResult = EKawaiiPhysicsAccessExternalForceResult::Valid; - } - } - }); - - P_FINISH; -} - -DEFINE_FUNCTION(UKawaiiPhysicsLibrary::execGetExternalForceWildcardProperty) -{ - P_GET_ENUM_REF(EKawaiiPhysicsAccessExternalForceResult, ExecResult); - P_GET_STRUCT_REF(FKawaiiPhysicsReference, KawaiiPhysics); - P_GET_PROPERTY(FIntProperty, ExternalForceIndex); - P_GET_STRUCT_REF(FName, PropertyName); - - ExecResult = EKawaiiPhysicsAccessExternalForceResult::NotValid; - - // Read wildcard Value input. - Stack.MostRecentPropertyAddress = nullptr; - Stack.MostRecentPropertyContainer = nullptr; - Stack.StepCompiledIn(nullptr); - - const FProperty* ValueProp = CastField(Stack.MostRecentProperty); - void* ValuePtr = Stack.MostRecentPropertyAddress; - - void* Result = nullptr; - KawaiiPhysics.CallAnimNodeFunction( - TEXT("GetExternalForceWildcardProperty"), - [&Result, &ExecResult, &ExternalForceIndex, &PropertyName](FAnimNode_KawaiiPhysics& InKawaiiPhysics) - { - if (InKawaiiPhysics.ExternalForces.IsValidIndex(ExternalForceIndex) && - InKawaiiPhysics.ExternalForces[ExternalForceIndex].IsValid()) - { - const auto* ScriptStruct = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetScriptStruct(); - auto& Force = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetMutable< - FKawaiiPhysics_ExternalForce>(); - - if (const FProperty* Property = FindFProperty(ScriptStruct, PropertyName)) - { - Result = Property->ContainerPtrToValuePtr(&Force); - ExecResult = EKawaiiPhysicsAccessExternalForceResult::Valid; - } - } - }); - - P_FINISH; - - if (ValuePtr && Result) - { - P_NATIVE_BEGIN; - ValueProp->CopyCompleteValue(ValuePtr, Result); - P_NATIVE_END; - } -} diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/KawaiiPhysicsLimitsDataAsset.cpp b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/KawaiiPhysicsLimitsDataAsset.cpp index 6f16e47..df33f99 100644 --- a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/KawaiiPhysicsLimitsDataAsset.cpp +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Private/KawaiiPhysicsLimitsDataAsset.cpp @@ -1,11 +1,10 @@ -// Copyright 2019-2026 pafuhana1213. All Rights Reserved. +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License #include "KawaiiPhysicsLimitsDataAsset.h" #include "AnimNode_KawaiiPhysics.h" #include "KawaiiPhysics.h" -#include UE_INLINE_GENERATED_CPP_BY_NAME(KawaiiPhysicsLimitsDataAsset) DEFINE_LOG_CATEGORY(LogKawaiiPhysics); @@ -154,8 +153,7 @@ void UKawaiiPhysicsLimitsDataAsset::Serialize(FStructuredArchiveRecord Record) } #endif -USkeleton* UKawaiiPhysicsLimitsDataAsset::GetSkeleton(bool& bInvalidSkeletonIsError, - const IPropertyHandle* PropertyHandle) +USkeleton* UKawaiiPhysicsLimitsDataAsset::GetSkeleton(bool& bInvalidSkeletonIsError) { #if WITH_EDITORONLY_DATA return Skeleton; diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/AnimNode_KawaiiPhysics.h b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/AnimNode_KawaiiPhysics.h index 72f3830..c6c6a8e 100644 --- a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/AnimNode_KawaiiPhysics.h +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/AnimNode_KawaiiPhysics.h @@ -1,42 +1,28 @@ -// Copyright 2019-2026 pafuhana1213. All Rights Reserved. +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License #pragma once -#include "Misc/EngineVersionComparison.h" #include "BoneContainer.h" #include "BonePose.h" #include "GameplayTagContainer.h" #include "BoneControllers/AnimNode_AnimDynamics.h" +#include "BoneControllers/AnimNode_RigidBody.h" #include "BoneControllers/AnimNode_SkeletalControlBase.h" -#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 -#include "StructUtils/InstancedStruct.h" -#else -#include "InstancedStruct.h" -#endif - -#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 6 -#include "PhysicsEngine/PhysicsAsset.h" -#include "PhysicsEngine/BodyInstance.h" -#endif - -#include "KawaiiPhysicsSyncBone.h" #include "AnimNode_KawaiiPhysics.generated.h" class UKawaiiPhysics_CustomExternalForce; class UKawaiiPhysicsLimitsDataAsset; class UKawaiiPhysicsBoneConstraintsDataAsset; +class UKawaiiPhysics_ExternalForce; #if ENABLE_ANIM_DEBUG extern KAWAIIPHYSICS_API TAutoConsoleVariable CVarAnimNodeKawaiiPhysicsEnable; extern KAWAIIPHYSICS_API TAutoConsoleVariable CVarAnimNodeKawaiiPhysicsDebug; extern KAWAIIPHYSICS_API TAutoConsoleVariable CVarAnimNodeKawaiiPhysicsDebugLengthRate; -extern KAWAIIPHYSICS_API TAutoConsoleVariable CVarAnimNodeKawaiiPhysicsDebugDrawThickness; #endif -extern KAWAIIPHYSICS_API TAutoConsoleVariable CVarAnimNodeKawaiiPhysicsUseBoneContainerRefSkeletonWhenInit; - UENUM(BlueprintType) enum class EKawaiiPhysicsSimulationSpace : uint8 { @@ -162,8 +148,8 @@ struct FCollisionLimitBase Location = Other.Location; Rotation = Other.Rotation; bEnable = Other.bEnable; - SourceType = Other.SourceType; #if WITH_EDITORONLY_DATA + SourceType = Other.SourceType; Guid = Other.Guid; Type = Other.Type; #endif @@ -301,6 +287,105 @@ struct FPlanarLimit : public FCollisionLimitBase } }; +/** + * Edge of a collision quad connecting two bones. + * Used for edge-based quad collision detection against capsules. + */ +USTRUCT() +struct FQuadEdge +{ + GENERATED_BODY() + + /** Index of the first bone endpoint in ModifyBones array */ + UPROPERTY() + int32 BoneIndex1 = -1; + + /** Index of the second bone endpoint in ModifyBones array */ + UPROPERTY() + int32 BoneIndex2 = -1; + + /** Rest length of this edge (cached for stretch correction) */ + float RestLength = 0.0f; + + /** Checks if this edge is valid for collision */ + bool IsValid() const { return BoneIndex1 >= 0 && BoneIndex2 >= 0; } +}; + +/** + * Quad formed from two consecutive bone constraints for edge-based collision. + * Corners: [0]=C1B_i, [1]=C2B_i, [2]=C1B_i+1, [3]=C2B_i+1 + * Used to prevent cloth/skirt penetration through body capsules. + */ +USTRUCT() +struct FQuadCollisionLimit +{ + GENERATED_BODY() + + /** The four bone indices forming the quad corners */ + UPROPERTY() + int32 BoneIndices[4] = {-1, -1, -1, -1}; + + /** The four edges of the quad (Top, Bottom, Left, Right) */ + FQuadEdge Edges[4]; + + /** Checks if this quad is valid for collision */ + bool IsValid() const + { + return BoneIndices[0] >= 0 && BoneIndices[1] >= 0 && + BoneIndices[2] >= 0 && BoneIndices[3] >= 0; + } + + /** Initialize edges from bone indices */ + void InitializeEdges() + { + // Edge 0: Top horizontal (C1B_i -> C2B_i) + Edges[0].BoneIndex1 = BoneIndices[0]; + Edges[0].BoneIndex2 = BoneIndices[1]; + // Edge 1: Bottom horizontal (C1B_i+1 -> C2B_i+1) + Edges[1].BoneIndex1 = BoneIndices[2]; + Edges[1].BoneIndex2 = BoneIndices[3]; + // Edge 2: Left vertical (C1B_i -> C1B_i+1) + Edges[2].BoneIndex1 = BoneIndices[0]; + Edges[2].BoneIndex2 = BoneIndices[2]; + // Edge 3: Right vertical (C2B_i -> C2B_i+1) + Edges[3].BoneIndex1 = BoneIndices[1]; + Edges[3].BoneIndex2 = BoneIndices[3]; + } +}; + +/** + * Cached capsule data with pre-computed endpoints for efficient collision checks. + * Avoids redundant axis/endpoint calculations per edge. + * Includes bounding sphere for broad-phase culling. + */ +struct FCachedCapsuleData +{ + FVector Start; // Top endpoint + FVector End; // Bottom endpoint + float Radius; // Capsule radius + FVector BoundingCenter; // Bounding sphere center (same as capsule center) + float BoundingRadius; // Bounding sphere radius (Radius + HalfLength) + + FCachedCapsuleData() + : Start(FVector::ZeroVector) + , End(FVector::ZeroVector) + , Radius(0.0f) + , BoundingCenter(FVector::ZeroVector) + , BoundingRadius(0.0f) + {} + + FCachedCapsuleData(const FVector& InStart, const FVector& InEnd, float InRadius) + : Start(InStart) + , End(InEnd) + , Radius(InRadius) + { + // Bounding sphere: center is midpoint, radius covers entire capsule + BoundingCenter = (InStart + InEnd) * 0.5f; + const float HalfLength = (InStart - InEnd).Size() * 0.5f; + BoundingRadius = InRadius + HalfLength; + } +}; + /** * Structure representing the root bone settings for KawaiiPhysics. */ @@ -431,10 +516,6 @@ struct KAWAIIPHYSICS_API FKawaiiPhysicsModifyBone UPROPERTY(BlueprintReadOnly, Category = "KawaiiPhysics|ModifyBone") FVector PoseScale = FVector::OneVector; - /** Length of the bone */ - UPROPERTY(BlueprintReadOnly, Category = "KawaiiPhysics|ModifyBone") - float BoneLength = 0.0f; - /** Length from the root bone */ UPROPERTY(BlueprintReadOnly, Category = "KawaiiPhysics|ModifyBone") float LengthFromRoot = 0.0f; @@ -574,7 +655,7 @@ struct KAWAIIPHYSICS_API FAnimNode_KawaiiPhysics : public FAnimNode_SkeletalCont * 指定ボーンとそれ以下のボーンを制御対象に(追加用) * Control the specified bone and the bones below it (For Addition) */ - UPROPERTY(EditAnywhere, Category = "Bones", meta=(TitleProperty="RootBone")) + UPROPERTY(EditAnywhere, Category = "Bones") TArray AdditionalRootBones; /** @@ -624,7 +705,14 @@ struct KAWAIIPHYSICS_API FAnimNode_KawaiiPhysics : public FAnimNode_SkeletalCont UPROPERTY(EditAnywhere, Category = "Physics Settings", meta = (InlineEditConditionToggle)) bool OverrideTargetFramerate = false; - /** + /** + * 固定タイムステップを使用してフレームレートに依存しない物理シミュレーションを行う + * Use fixed time step for frame-rate independent physics simulation + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Physics Settings", meta = (PinHiddenByDefault)) + bool bUseFixedTimeStep = false; + + /** * 物理の空回し回数。物理処理が落ち着いてから開始・表示したい際に使用 * Number of times physics has been idle. Used when you want to start/display after physics processing has settled down */ @@ -670,7 +758,7 @@ struct KAWAIIPHYSICS_API FAnimNode_KawaiiPhysics : public FAnimNode_SkeletalCont */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Physics Settings", meta = (PinHiddenByDefault)) - FVector SkelCompMoveScale = FVector::One(); + FVector SkelCompMoveScale = FVector::OneVector; /** * 各ボーンの物理パラメータを毎フレーム更新するフラグ。 @@ -780,14 +868,14 @@ struct KAWAIIPHYSICS_API FAnimNode_KawaiiPhysics : public FAnimNode_SkeletalCont * Collision settings (DataAsset version). This is recommended if you want to reuse the settings for another AnimNode or ABP. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Limits", meta = (PinHiddenByDefault)) - TObjectPtr LimitsDataAsset = nullptr; + UKawaiiPhysicsLimitsDataAsset* LimitsDataAsset = nullptr; /** * コリジョン設定(PhyiscsAsset版)。別AnimNode・ABPで設定を流用したい場合はこちらを推奨 * Collision settings (PhyiscsAsset版 version). This is recommended if you want to reuse the settings for another AnimNode or ABP. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Limits", meta = (PinHiddenByDefault)) - TObjectPtr PhysicsAssetForLimits = nullptr; + UPhysicsAsset* PhysicsAssetForLimits = nullptr; /** * コリジョン設定(DataAsset版)における球コリジョンのプレビュー @@ -857,7 +945,7 @@ struct KAWAIIPHYSICS_API FAnimNode_KawaiiPhysics : public FAnimNode_SkeletalCont */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Bone Constraint", meta = (PinHiddenByDefault)) - TObjectPtr BoneConstraintsDataAsset; + UKawaiiPhysicsBoneConstraintsDataAsset* BoneConstraintsDataAsset; /** * BoneConstraint処理の対象となるボーンのペアのプレビュー @@ -869,48 +957,108 @@ struct KAWAIIPHYSICS_API FAnimNode_KawaiiPhysics : public FAnimNode_SkeletalCont UPROPERTY() TArray MergedBoneConstraints; + // ============================================================================ + // Quad Collision (Experimental) + // ============================================================================ + /** - * 同期元のボーンの移動・回転を物理制御下のボーンに適用します。スカートが足などを貫通するのを防ぐのに役立ちます - * Applies the movement and rotation of the sync source bone to the bone under physics control. Helps prevent skirts from penetrating legs, etc. + * クアッドコリジョンを有効にする(実験的機能) + * Enable quad collision detection between bone constraint quads and capsule limits. + * Experimental feature: Performs edge-to-capsule collision for cloth-like surfaces. */ - UPROPERTY(EditAnywhere, Category = "Sync Bone", meta=(TitleProperty="{Bone}")) - TArray SyncBones; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Quad Collision (Experimental)", + meta = (PinHiddenByDefault)) + bool bEnableQuadCollision = false; /** - * 重力 - * Gravity + * クアッドコリジョンの反復回数 + * Number of iterations for quad collision adjustment. + * Higher values provide more accurate collision resolution but cost more performance. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ExternalForce", - meta = (PinHiddenByDefault)) - FVector Gravity = FVector::ZeroVector; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Quad Collision (Experimental)", + meta = (PinHiddenByDefault, ClampMin = "1", ClampMax = "10", EditCondition = "bEnableQuadCollision")) + int32 QuadCollisionIterationCount = 1; /** - * Gravityの適用方式(レガシー互換) - * true : 従来互換(位置に 0.5 * Gravity * dt^2 を加算) - * false: AnimDynamics互換(速度に Gravity * dt を加算してから位置更新) - * Gravity application method (legacy compatibility) - * true : Legacy compatibility (add 0.5 * Gravity * dt^2 to position) - * false: AnimDynamics compatibility (add Gravity * dt to velocity before updating position) + * クアッドコリジョンの追加閾値 + * Additional collision threshold added to capsule radius for quad edge detection. + * Increase this for softer, earlier collision response. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ExternalForce", meta = (PinHiddenByDefault)) - bool bUseLegacyGravity = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Quad Collision (Experimental)", + meta = (PinHiddenByDefault, ClampMin = "0.0", EditCondition = "bEnableQuadCollision")) + float QuadCollisionThreshold = 0.0f; /** - * Gravityベクトルにプロジェクト設定の DefaultGravityZ(絶対値)を乗算する処理のフラグ - * Flag to multiply the DefaultGravityZ (absolute value) of the project settings to the Gravity vector + * クアッドエッジの伸び剛性(0=補正なし、1=完全補正) + * Edge stiffness for quad collision stretch correction. + * After collision pushes vertices, this corrects stretched edges back toward rest length. + * 0 = no correction (stretchy), 1 = full correction (rigid edges) */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ExternalForce", meta = (PinHiddenByDefault)) - bool bUseDefaultGravityZProjectSetting = false; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Quad Collision (Experimental)", + meta = (PinHiddenByDefault, ClampMin = "0.0", ClampMax = "1.0", EditCondition = "bEnableQuadCollision")) + float QuadCollisionEdgeStiffness = 0.5f; - // - // 重力をワールド座標系で扱うかどうかのフラグ - // Flag to handle gravity in world coordinate system - // - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ExternalForce", meta = (PinHiddenByDefault)) - bool bUseWorldSpaceGravity = true; + /** + * ボーン長さ比率によるエッジ剛性カーブ(0=ルート、1=先端) + * Edge stiffness multiplier by bone length rate (0 = root, 1 = tip). + * Use to vary stiffness along the chain (e.g., stiffer at waist, softer at hem). + * Multiplies the base QuadCollisionEdgeStiffness value. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Quad Collision (Experimental)", AdvancedDisplay, + meta = (PinHiddenByDefault, DisplayName = "Edge Stiffness by Bone Length Rate", EditCondition = "bEnableQuadCollision")) + FRuntimeFloatCurve QuadCollisionEdgeStiffnessCurve; + + /** + * クアッドコリジョン用のBone Constraint設定(DataAsset版) + * Bone constraints data asset specifically for quad collision. + * If not set, will use the main Bone Constraint settings instead. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Quad Collision (Experimental)", + meta = (PinHiddenByDefault, EditCondition = "bEnableQuadCollision")) + UKawaiiPhysicsBoneConstraintsDataAsset* QuadCollisionBoneConstraintsDataAsset = nullptr; + + /** + * ダミーボーンをクアッドコリジョンに含める + * Whether to include dummy bones when building quads from bone constraints. + * When enabled, adds an extra quad row connecting to the dummy bone endpoints. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Quad Collision (Experimental)", + meta = (PinHiddenByDefault, EditCondition = "bEnableQuadCollision")) + bool bQuadCollisionIncludeDummyBones = true; + + /** + * コリジョンチェーンループを閉じる + * Close the collision chain loop by connecting the last chain pair to the first. + * Enable for closed circular shapes (like skirts), disable for open folded surfaces. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Quad Collision (Experimental)", + meta = (PinHiddenByDefault, EditCondition = "bEnableQuadCollision")) + bool bQuadCollisionCloseLoop = true; + + /** + * クアッドコリジョン用のBone Constraintのプレビュー + * Preview of bone constraint pairs used for quad collision + */ + UPROPERTY(VisibleAnywhere, Category = "Quad Collision (Experimental)", AdvancedDisplay, + meta=(TitleProperty="{Bone1} - {Bone2}")) + TArray QuadCollisionBoneConstraintsData; + + /** Merged bone constraints for quad collision */ + UPROPERTY() + TArray MergedQuadCollisionBoneConstraints; + + /** Cached quads generated from consecutive bone constraints */ + UPROPERTY() + TArray CachedQuads; + + /** + * 外力(重力など) + * External forces (gravity, etc.) + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ExternalForce", + meta = (PinHiddenByDefault, DisplayName="External Force")) + FVector Gravity = FVector::ZeroVector; - // 外力としてWindDirectionalSourceの影響を受けるかどうかのフラグ - // Flag to receive the influence of WindDirectionalSource as an external force UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ExternalForce", meta = (PinHiddenByDefault)) bool bEnableWind = false; @@ -930,25 +1078,13 @@ struct KAWAIIPHYSICS_API FAnimNode_KawaiiPhysics : public FAnimNode_SkeletalCont meta = (EditCondition = "bEnableWind", Units = "Degrees", ClampMin=0, PinHiddenByDefault)) float WindDirectionNoiseAngle = 0.0f; - // 単純な外力ベクトル - // Simple external force vector - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ExternalForce", - meta = (PinHiddenByDefault)) - FVector SimpleExternalForce = FVector::ZeroVector; - - // 単純な外力をワールド座標系で扱うかどうかのフラグ - // Flag to handle simple external forces in world coordinate system - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ExternalForce", - meta = (PinHiddenByDefault)) - bool bUseWorldSpaceSimpleExternalForce = true; - /** * 外力のプリセット。C++で独自のプリセットを追加可能(Instanced Struct) * External force presets. You can add your own presets in C++. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ExternalForce", + UPROPERTY(EditAnywhere, BlueprintReadWrite, Instanced, Category = "ExternalForce", meta = (BaseStruct = "/Script/KawaiiPhysics.KawaiiPhysics_ExternalForce", ExcludeBaseStruct)) - TArray ExternalForces; + TArray ExternalForces; /** * !!! VERY VERY EXPERIMENTAL !!! @@ -959,7 +1095,7 @@ struct KAWAIIPHYSICS_API FAnimNode_KawaiiPhysics : public FAnimNode_SkeletalCont */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Instanced, Category = "ExternalForce", meta=(DisplayName="CustomExternalForces(EXPERIMENTAL)")) - TArray> CustomExternalForces; + TArray CustomExternalForces; /** * レベル上の各コリジョンとの判定を行うフラグ。有効にすると物理処理の負荷が大幅に上がります @@ -1046,25 +1182,27 @@ struct KAWAIIPHYSICS_API FAnimNode_KawaiiPhysics : public FAnimNode_SkeletalCont * Flag indicating whether to reset the dynamics. */ FVector GravityInSimSpace = FVector::ZeroVector; - - // Cached simple external force in current SimulationSpace (computed once per SimulateModifyBones) - FVector SimpleExternalForceInSimSpace = FVector::ZeroVector; /** * The last simulation space used for the physics simulation. */ EKawaiiPhysicsSimulationSpace LastSimulationSpace = EKawaiiPhysicsSimulationSpace::ComponentSpace; - + /** - * Previous frame's Base bone space to component space transform + * Base bone space to component space transform. */ - FTransform PrevBaseBoneSpace2ComponentSpace = FTransform::Identity; + FTransform BaseBoneSpace2ComponentSpace = FTransform::Identity; /** * Stores the delta time from the previous frame. */ float DeltaTimeOld = 0.0f; + /** + * Time accumulator for fixed time step simulation. + */ + float TimeAccumulator = 0.0f; + #if WITH_EDITORONLY_DATA bool bEditing = false; double LastEvaluatedTime = 0.0; @@ -1122,15 +1260,7 @@ struct KAWAIIPHYSICS_API FAnimNode_KawaiiPhysics : public FAnimNode_SkeletalCont /** * Get Transform from BaseBoneSpace to ComponentSpace. */ - FTransform GetBaseBoneSpace2ComponentSpace() const - { - if (SimulationSpace == EKawaiiPhysicsSimulationSpace::BaseBoneSpace) - { - return CurrentEvalSimSpaceCache.TargetSpaceToComponent; - } - - return FTransform::Identity; - } + FTransform GetBaseBoneSpace2ComponentSpace() const { return BaseBoneSpace2ComponentSpace; } protected: /** @@ -1153,17 +1283,6 @@ struct KAWAIIPHYSICS_API FAnimNode_KawaiiPhysics : public FAnimNode_SkeletalCont */ void InitModifyBones(FComponentSpacePoseContext& Output, const FBoneContainer& BoneContainer); - /** - * Initializes the sync rotation bones for the physics simulation. - */ - void InitSyncBones(FComponentSpacePoseContext& Output); - - /** - * Initializes a sync bone for the physics simulation. - */ - void InitSyncBone(FComponentSpacePoseContext& Output, const FBoneContainer& BoneContainer, - FKawaiiPhysicsSyncBone& SyncBone); - /** * Initializes the bone constraints for the physics simulation. */ @@ -1285,14 +1404,6 @@ struct KAWAIIPHYSICS_API FAnimNode_KawaiiPhysics : public FAnimNode_SkeletalCont */ void UpdateModifyBonesPoseTransform(FComponentSpacePoseContext& Output, const FBoneContainer& BoneContainer); - /** - * Applies the movement of SyncBones to target bones. - * @param Output The pose context. - * @param BoneContainer The bone container. - */ - void ApplySyncBones(FComponentSpacePoseContext& Output, const FBoneContainer& BoneContainer); - - /** * Updates the skeletal component movement vector and rotation. * @@ -1315,6 +1426,7 @@ struct KAWAIIPHYSICS_API FAnimNode_KawaiiPhysics : public FAnimNode_SkeletalCont * @param Bone The bone to simulate. * @param Scene The scene interface. * @param ComponentTransform The component transform. + * @param GravityCS The gravity vector in component space. * @param Exponent The exponent for the simulation. * @param SkelComp The skeletal mesh component. * @param Output The pose context. @@ -1387,6 +1499,46 @@ struct KAWAIIPHYSICS_API FAnimNode_KawaiiPhysics : public FAnimNode_SkeletalCont */ void AdjustByBoneConstraints(); + /** + * Initializes the quad collision bone constraints from data asset or main bone constraints. + */ + void InitQuadCollisionBoneConstraints(); + + /** + * Builds quads from consecutive bone constraint pairs for quad collision. + */ + void BuildQuadsFromConstraints(); + + /** + * Adjusts bone positions based on quad edge collisions with capsules. + */ + void AdjustByQuadCollision(); + + /** + * Corrects stretched quad edges after collision resolution. + * Pulls edge endpoints back toward rest length based on QuadCollisionEdgeStiffness. + */ + void CorrectQuadEdgeStretching(); + + /** + * Performs edge-to-capsule collision detection and resolution. + * + * @param Edge The quad edge to test. + * @param Capsule The capsule to test against. + * @return True if collision was detected and resolved. + */ + bool AdjustEdgeByCapsuleCollision(const FQuadEdge& Edge, const FCapsuleLimit& Capsule); + + /** + * Performs edge-to-capsule collision detection and resolution using pre-cached capsule data. + * More efficient for repeated calls as capsule endpoints are pre-computed. + * + * @param Edge The quad edge to test. + * @param CachedCapsule Pre-computed capsule endpoint data. + * @return True if collision was detected and resolved. + */ + bool AdjustEdgeByCapsuleCollisionCached(const FQuadEdge& Edge, const FCachedCapsuleData& CachedCapsule); + /** * Applies the simulation results to the bone transforms. * @@ -1402,118 +1554,46 @@ struct KAWAIIPHYSICS_API FAnimNode_KawaiiPhysics : public FAnimNode_SkeletalCont * * @param Output The pose context. * @param BoneContainer The bone container. - * @param InOutComponentTransform The component transform. + * @param ComponentTransform The component transform. */ void WarmUp(FComponentSpacePoseContext& Output, const FBoneContainer& BoneContainer, - FTransform& InOutComponentTransform); + FTransform& ComponentTransform); /** * Gets the wind velocity for a given bone. * * @param Scene The scene interface. + * @param ComponentTransform The component transform. * @param Bone The bone to get the wind velocity for. * @return The wind velocity vector. */ - FVector GetWindVelocity(FComponentSpacePoseContext& Output, const FSceneInterface* Scene, + FVector GetWindVelocity(const FComponentSpacePoseContext& Output, const FSceneInterface* Scene, const FKawaiiPhysicsModifyBone& Bone) const; #if ENABLE_ANIM_DEBUG void AnimDrawDebug(FComponentSpacePoseContext& Output); - - // Draw debug Box - void AnimDrawDebugBox(FComponentSpacePoseContext& Output, const FVector& CenterLocationSim, - const FQuat& RotationSim, - const FVector& Extent, const FColor& Color, float Thickness) const; #endif private: - // Given a bone index, get the transform in the currently selected simulation space + // Given a bone index, get it's transform in the currently selected simulation space FTransform GetBoneTransformInSimSpace(FComponentSpacePoseContext& Output, const FCompactPoseBoneIndex& BoneIndex) const; - // Convert a transform from one simulation space to another (internal cache-aware) - FTransform ConvertSimulationSpaceTransform(FComponentSpacePoseContext& Output, - EKawaiiPhysicsSimulationSpace From, - EKawaiiPhysicsSimulationSpace To, - const FTransform& InTransform) const; - - // Convert a vector from one simulation space to another (internal cache-aware) - FVector ConvertSimulationSpaceVector(FComponentSpacePoseContext& Output, - EKawaiiPhysicsSimulationSpace From, - EKawaiiPhysicsSimulationSpace To, - const FVector& InVector) const; - - // Convert a location from one simulation space to another (internal cache-aware) - FVector ConvertSimulationSpaceLocation(FComponentSpacePoseContext& Output, - EKawaiiPhysicsSimulationSpace From, - EKawaiiPhysicsSimulationSpace To, - const FVector& InLocation) const; - - // Convert a rotation from one simulation space to another (internal cache-aware) - FQuat ConvertSimulationSpaceRotation(FComponentSpacePoseContext& Output, - EKawaiiPhysicsSimulationSpace From, - EKawaiiPhysicsSimulationSpace To, - const FQuat& InRotation) const; - - void ConvertSimulationSpace(FComponentSpacePoseContext& Output, - EKawaiiPhysicsSimulationSpace From, - EKawaiiPhysicsSimulationSpace To); - -private: - // SimulationSpace conversion cache (per-evaluation) - struct FSimulationSpaceCache - { - FTransform ComponentToTargetSpace = FTransform::Identity; - FTransform TargetSpaceToComponent = FTransform::Identity; - - bool IsIdentity() const - { - return ComponentToTargetSpace.Equals(FTransform::Identity) && - TargetSpaceToComponent.Equals(FTransform::Identity); - } - }; - - FSimulationSpaceCache BuildSimulationSpaceCache(FComponentSpacePoseContext& Output, - const EKawaiiPhysicsSimulationSpace SimulationSpaceForCache) const; - - // Select cache for a given simulation space (Evaluate cache preferred) - FSimulationSpaceCache GetSimulationSpaceCacheFor(FComponentSpacePoseContext& Output, - EKawaiiPhysicsSimulationSpace Space) const; - - // Convert helpers using explicit caches - FTransform ConvertSimulationSpaceTransformCached(const FSimulationSpaceCache& CacheFrom, - const FSimulationSpaceCache& CacheTo, - const FTransform& InTransform) const; - FVector ConvertSimulationSpaceVectorCached(const FSimulationSpaceCache& CacheFrom, - const FSimulationSpaceCache& CacheTo, - const FVector& InVector) const; - FVector ConvertSimulationSpaceLocationCached(const FSimulationSpaceCache& CacheFrom, - const FSimulationSpaceCache& CacheTo, - const FVector& InLocation) const; - FQuat ConvertSimulationSpaceRotationCached(const FSimulationSpaceCache& CacheFrom, - const FSimulationSpaceCache& CacheTo, - const FQuat& InRotation) const; - - void ConvertSimulationSpaceCached(const FSimulationSpaceCache& CacheFrom, - const FSimulationSpaceCache& CacheTo, - EKawaiiPhysicsSimulationSpace From, - EKawaiiPhysicsSimulationSpace To); - -private: - // Evaluate中のみ有効なキャッシュ(SimulationSpace<->Component) - // AnyThread評価なので「フレーム跨ぎで使い回さない」こと - mutable FSimulationSpaceCache CurrentEvalSimSpaceCache; - mutable bool bHasCurrentEvalSimSpaceCache = false; - - // Evaluate中のみ有効なWorldSpaceキャッシュ(World<->Component) - mutable FSimulationSpaceCache CurrentEvalWorldSpaceCache; - mutable bool bHasCurrentEvalWorldSpaceCache = false; -}; - - - - + // Convert a transform from one simulation space to another + FTransform ConvertSimulationSpaceTransform(const FComponentSpacePoseContext& Output, EKawaiiPhysicsSimulationSpace From, + EKawaiiPhysicsSimulationSpace To, const FTransform& InTransform) const; + // Convert a vector from one simulation space to another + FVector ConvertSimulationSpaceVector(const FComponentSpacePoseContext& Output, EKawaiiPhysicsSimulationSpace From, + EKawaiiPhysicsSimulationSpace To, const FVector& InVector) const; + // Convert a location from one simulation space to another + FVector ConvertSimulationSpaceLocation(const FComponentSpacePoseContext& Output, EKawaiiPhysicsSimulationSpace From, + EKawaiiPhysicsSimulationSpace To, const FVector& InLocation) const; + // Convert a rotation from one simulation space to another + FQuat ConvertSimulationSpaceRotation(FComponentSpacePoseContext& Output, EKawaiiPhysicsSimulationSpace From, + EKawaiiPhysicsSimulationSpace To, const FQuat& InRotation) const; + void ConvertSimulationSpace(FComponentSpacePoseContext& Output, EKawaiiPhysicsSimulationSpace From, EKawaiiPhysicsSimulationSpace To); +}; diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/KawaiiPhysics.h b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/KawaiiPhysics.h index ce9b3bf..03ec7cb 100644 --- a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/KawaiiPhysics.h +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/KawaiiPhysics.h @@ -1,9 +1,9 @@ -// Copyright 2019-2026 pafuhana1213. All Rights Reserved. +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License #pragma once +#include "CoreMinimal.h" #include "Modules/ModuleInterface.h" -#include "Logging/LogMacros.h" DECLARE_LOG_CATEGORY_EXTERN(LogKawaiiPhysics, Log, All); diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/KawaiiPhysicsBoneConstraintsDataAsset.h b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/KawaiiPhysicsBoneConstraintsDataAsset.h index b6db70f..a865b31 100644 --- a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/KawaiiPhysicsBoneConstraintsDataAsset.h +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/KawaiiPhysicsBoneConstraintsDataAsset.h @@ -1,4 +1,4 @@ -// Copyright 2019-2026 pafuhana1213. All Rights Reserved. +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License #pragma once @@ -104,7 +104,7 @@ class KAWAIIPHYSICS_API UKawaiiPhysicsBoneConstraintsDataAsset : public UDataAss // End UObject Interface. // IBoneReferenceSkeletonProvider interface - virtual USkeleton* GetSkeleton(bool& bInvalidSkeletonIsError, const IPropertyHandle* PropertyHandle) override; + virtual USkeleton* GetSkeleton(bool& bInvalidSkeletonIsError) override; /** Generates bone constraints based on the current data */ TArray GenerateBoneConstraints(); diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/KawaiiPhysicsCustomExternalForce.h b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/KawaiiPhysicsCustomExternalForce.h index 0f5c0bb..54ffc55 100644 --- a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/KawaiiPhysicsCustomExternalForce.h +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/KawaiiPhysicsCustomExternalForce.h @@ -1,4 +1,4 @@ -// Copyright 2019-2026 pafuhana1213. All Rights Reserved. +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License #pragma once #include "AnimNode_KawaiiPhysics.h" diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/KawaiiPhysicsExternalForce.h b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/KawaiiPhysicsExternalForce.h new file mode 100644 index 0000000..1c68530 --- /dev/null +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/KawaiiPhysicsExternalForce.h @@ -0,0 +1,473 @@ +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License + +#pragma once +#include "AnimNode_KawaiiPhysics.h" +#include "SceneManagement.h" +#include "Curves/CurveVector.h" +#include "KawaiiPhysicsExternalForce.generated.h" + +/** + * Enum representing the space in which external forces are simulated. + */ +UENUM(BlueprintType) +enum class EExternalForceSpace : uint8 +{ + /** Simulate in component space. Moving the entire skeletal mesh will have no affect on velocities */ + ComponentSpace, + /** Simulate in world space. Moving the skeletal mesh will generate velocity changes */ + WorldSpace, + /** Simulate in another bone space. Moving the entire skeletal mesh and individually modifying the base bone will have no affect on velocities */ + BoneSpace, +}; + +/** + * Enum representing the evaluation type for external force curves. + */ +UENUM(BlueprintType) +enum class EExternalForceCurveEvaluateType : uint8 +{ + /** Evaluate the curve at a single point */ + Single, + /** Evaluate the curve by averaging multiple points */ + Average, + /** Evaluate the curve by taking the maximum value from multiple points */ + Max, + /** Evaluate the curve by taking the minimum value from multiple points */ + Min +}; + + +/// +/// Base +/// +UCLASS(Abstract, Blueprintable, EditInlineNew, DefaultToInstanced, CollapseCategories) +class KAWAIIPHYSICS_API UKawaiiPhysics_ExternalForce : public UObject +{ + GENERATED_BODY() + +public: + /** Whether the external force is enabled */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(DisplayPriority=1), Category="KawaiiPhysics|ExternalForce") + bool bIsEnabled = true; + + /** Whether to draw debug information for the external force */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(DisplayPriority=1), Category="KawaiiPhysics|ExternalForce") + bool bDrawDebug = false; + + /** + * 外力を適応するボーンを指定(=指定しなかったボーンには適応しない) + * Specify the bones to which the external force will be applied (= the force will not be applied to bones that are not specified) + */ + UPROPERTY(EditAnywhere, meta=(DisplayPriority=1), Category="KawaiiPhysics|ExternalForce") + TArray ApplyBoneFilter; + + /** + * 外力を適応しないボーンを指定 + * Specify the bones to which the external force will be NOT applied + */ + UPROPERTY(EditAnywhere, meta=(DisplayPriority=1), Category="KawaiiPhysics|ExternalForce") + TArray IgnoreBoneFilter; + + /** The space in which the external force is simulated */ + UPROPERTY(EditAnywhere, meta=(DisplayPriority=1, EditCondition=bCanSelectForceSpace, EditConditionHides), + Category="KawaiiPhysics|ExternalForce") + EExternalForceSpace ExternalForceSpace = EExternalForceSpace::WorldSpace; + + /** Range for randomizing the force scale */ + UPROPERTY(EditAnywhere, meta=(DisplayPriority=1), Category="KawaiiPhysics|ExternalForce") + FFloatInterval RandomForceScaleRange = FFloatInterval(1.0f, 1.0f); + + /** Owner of the external force */ + UPROPERTY() + UObject* ExternalOwner; + + /** Whether the external force is applied only once */ + UPROPERTY() + bool bIsOneShot = false; + +#if ENABLE_ANIM_DEBUG + /** Length of the debug arrow */ + float DebugArrowLength = 5.0f; + + /** Size of the debug arrow */ + float DebugArrowSize = 1.0f; + + /** Offset for the debug arrow */ + FVector DebugArrowOffset = FVector::ZeroVector; + + /** Map of bone names to forces for debugging */ + TMap BoneForceMap; +#endif + +protected: + /** Randomized scale of the force */ + UPROPERTY() + float RandomizedForceScale = 0.0f; + + /** The force vector */ + UPROPERTY() + FVector Force = FVector::ZeroVector; + + /** Transform of the component */ + UPROPERTY() + FTransform ComponentTransform; + + /** Whether the force space can be selected */ + UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="KawaiiPhysics|ExternalForce") + bool bCanSelectForceSpace = true; + +public: + virtual void Initialize(const FAnimationInitializeContext& Context) + { + } + + /** Prepares the external force before applying it */ + virtual void PreApply(FAnimNode_KawaiiPhysics& Node, const USkeletalMeshComponent* SkelComp) + { + ComponentTransform = SkelComp->GetComponentTransform(); + RandomizedForceScale = FMath::RandRange(RandomForceScaleRange.Min, RandomForceScaleRange.Max); + +#if ENABLE_ANIM_DEBUG + if (IsInGameThread()) + { + BoneForceMap.Empty(); + } +#endif + } + + /** Applies the external force to a bone */ + virtual void Apply(FKawaiiPhysicsModifyBone& Bone, FAnimNode_KawaiiPhysics& Node, + const FComponentSpacePoseContext& PoseContext, const FTransform& BoneTM = FTransform::Identity) + { + } + + /** Finalizes the external force after applying it */ + virtual void PostApply(FAnimNode_KawaiiPhysics& Node) + { + if (bIsOneShot) + { + Node.ExternalForces.RemoveAll([&](UKawaiiPhysics_ExternalForce* ExternalForcePtr) + { + return ExternalForcePtr == this; + }); + } + } + + /** Checks if debug information should be drawn */ + virtual bool IsDebugEnabled(bool bInPersona = false) + { + if (bInPersona) + { + return bDrawDebug && bIsEnabled; + } + +#if ENABLE_ANIM_DEBUG + if (CVarAnimNodeKawaiiPhysicsDebug.GetValueOnAnyThread()) + { + return bDrawDebug && bIsEnabled; + } +#endif + + return false; + } + +#if ENABLE_ANIM_DEBUG + /** Draws debug information for the external force */ + virtual void AnimDrawDebug(FKawaiiPhysicsModifyBone& Bone, FAnimNode_KawaiiPhysics& Node, + const FComponentSpacePoseContext& PoseContext) + { + if (IsDebugEnabled() && !Force.IsZero()) + { + const auto AnimInstanceProxy = PoseContext.AnimInstanceProxy; + const FVector ModifyRootBoneLocationWS = AnimInstanceProxy->GetComponentTransform().TransformPosition( + Bone.Location); + + AnimInstanceProxy->AnimDrawDebugDirectionalArrow( + ModifyRootBoneLocationWS + DebugArrowOffset, + ModifyRootBoneLocationWS + DebugArrowOffset + BoneForceMap.Find(Bone.BoneRef.BoneName)->GetSafeNormal() + * + DebugArrowLength, + DebugArrowSize, FColor::Red, false, 0.f, 2); + } + } +#endif + +#if WITH_EDITOR + /** Draws debug information for the external force in edit mode */ + virtual void AnimDrawDebugForEditMode(const FKawaiiPhysicsModifyBone& ModifyBone, + const FAnimNode_KawaiiPhysics& Node, FPrimitiveDrawInterface* PDI) + { + if (IsDebugEnabled(true) && CanApply(ModifyBone) && !Force.IsNearlyZero() && BoneForceMap.Contains( + ModifyBone.BoneRef.BoneName)) + { + const FTransform ArrowTransform = FTransform( + BoneForceMap.Find(ModifyBone.BoneRef.BoneName)->GetSafeNormal().ToOrientationRotator(), + ModifyBone.Location + DebugArrowOffset); + DrawDirectionalArrow(PDI, ArrowTransform.ToMatrixNoScale(), FColor::Red, DebugArrowLength, DebugArrowSize, + SDPG_Foreground, 1.0f); + } + } +#endif + +protected: + /** Checks if the external force can be applied to a bone */ + bool CanApply(const FKawaiiPhysicsModifyBone& Bone) const + { + // Dummy bones don't have valid BoneRef - skip them to avoid crash + if (Bone.bDummy) + { + return false; + } + + if (ApplyBoneFilter.Num() > 0 && !ApplyBoneFilter.Contains(Bone.BoneRef)) + { + return false; + } + + if (IgnoreBoneFilter.Num() > 0 && IgnoreBoneFilter.Contains(Bone.BoneRef)) + { + return false; + } + + return true; + } +}; + +/// +/// Basic +/// +UCLASS(Blueprintable, BlueprintType, EditInlineNew, DefaultToInstanced, meta=(DisplayName = "Basic")) +class KAWAIIPHYSICS_API UKawaiiPhysics_ExternalForce_Basic : public UKawaiiPhysics_ExternalForce +{ + GENERATED_BODY() + +public: + /** Direction of the force */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="KawaiiPhysics|ExternalForce") + FVector ForceDir = FVector::ZeroVector; + + /** + * 各ボーンに適用するForce Rateを補正。 + * 「RootBoneから特定のボーンまでの長さ / RootBoneから末端のボーンまでの長さ」(0.0~1.0)の値におけるカーブの値をForceRateに乗算 + * Corrects the Force Rate applied to each bone. + * Multiplies the ForceRate by the curve value for "Length from RootBone to specific bone / Length from RootBone to end bone" (0.0~1.0) + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="KawaiiPhysics|ExternalForce") + FRuntimeFloatCurve ForceRateByBoneLengthRate; + + /** Interval for applying the force */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="KawaiiPhysics|ExternalForce") + float Interval = 0.0f; + + virtual void PreApply(FAnimNode_KawaiiPhysics& Node, const USkeletalMeshComponent* SkelComp) override; + virtual void Apply(FKawaiiPhysicsModifyBone& Bone, FAnimNode_KawaiiPhysics& Node, + const FComponentSpacePoseContext& PoseContext, + const FTransform& BoneTM = FTransform::Identity) override; + +private: + /** Current time */ + UPROPERTY() + float Time = 0.0f; + + /** Previous time */ + UPROPERTY() + float PrevTime = 0.0f; +}; + +/// +/// Gravity +/// +UCLASS(Blueprintable, BlueprintType, EditInlineNew, DefaultToInstanced, meta=(DisplayName = "Gravity")) +class KAWAIIPHYSICS_API UKawaiiPhysics_ExternalForce_Gravity : public UKawaiiPhysics_ExternalForce +{ + GENERATED_BODY() + +public: + UKawaiiPhysics_ExternalForce_Gravity() + { + bCanSelectForceSpace = false; + ExternalForceSpace = EExternalForceSpace::WorldSpace; + } + + /** + * 各ボーンに適用するForce Rateを補正。 + * 「RootBoneから特定のボーンまでの長さ / RootBoneから末端のボーンまでの長さ」(0.0~1.0)の値におけるカーブの値をForceRateに乗算 + * Corrects the Force Rate applied to each bone. + * Multiplies the ForceRate by the curve value for "Length from RootBone to specific bone / Length from RootBone to end bone" (0.0~1.0) + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="KawaiiPhysics|ExternalForce") + FRuntimeFloatCurve ForceRateByBoneLengthRate; + + /** + * Character側で設定されたCustomGravityDirectionを使用するフラグ(UE5.4以降) + * Flag to use CustomGravityDirection set on the Character side (UE5.4 and later) + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="KawaiiPhysics|ExternalForce") + bool bUseCharacterGravityDirection = false; + + /** + * Character側で設定されたGravityScaleを使用するフラグ + * Flag to use GravityScale set on the Character side + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="KawaiiPhysics|ExternalForce") + bool bUseCharacterGravityScale = false; + + /** + * Direction to override the gravity. + * This direction is used when bUseOverrideGravityDirection is true. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, + meta = (EditCondition = "bUseOverrideGravityDirection"), Category="KawaiiPhysics|ExternalForce") + FVector OverrideGravityDirection = FVector::ZeroVector; + + /** + * Flag to determine whether to use the override gravity direction. + * If true, the gravity direction will be overridden by OverrideGravityDirection. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (InlineEditConditionToggle), + Category="KawaiiPhysics|ExternalForce") + bool bUseOverrideGravityDirection = false; + + virtual void PreApply(FAnimNode_KawaiiPhysics& Node, const USkeletalMeshComponent* SkelComp) override; + virtual void Apply(FKawaiiPhysicsModifyBone& Bone, FAnimNode_KawaiiPhysics& Node, + const FComponentSpacePoseContext& PoseContext, + const FTransform& BoneTM = FTransform::Identity) override; +}; + +/// +/// Curve +/// +UCLASS(Blueprintable, BlueprintType, EditInlineNew, DefaultToInstanced, meta=(DisplayName = "Curve")) +class KAWAIIPHYSICS_API UKawaiiPhysics_ExternalForce_Curve : public UKawaiiPhysics_ExternalForce +{ + GENERATED_BODY() + +public: + /** + * 時間に応じて変化する外力をカーブで設定。X軸:Time Y軸:Force + * Set the external force that changes over time using a curve. X-axis: Time Y-axis: Force + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (XAxisName="Time", YAxisName="Force X"), + Category="KawaiiPhysics|ExternalForce") + FRuntimeFloatCurve ForceCurveX; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (XAxisName="Time", YAxisName="Force Y"), + Category="KawaiiPhysics|ExternalForce") + FRuntimeFloatCurve ForceCurveY; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (XAxisName="Time", YAxisName="Force Z"), + Category="KawaiiPhysics|ExternalForce") + FRuntimeFloatCurve ForceCurveZ; + + /** + * カーブの評価方式。 + * Single以外に設定した場合:前フレームからの経過時間をSubstepCountで分割し、 + * 分割後の各時間におけるカーブの値の平均・最大値・最小値を外力として使用 + * Curve evaluation method + * If set to anything other than Single: The time elapsed from the previous frame is divided by SubstepCount, + * and the Average, Maximum, or Minimum values of the curve at each time point after division are used as external forces. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="KawaiiPhysics|ExternalForce") + EExternalForceCurveEvaluateType CurveEvaluateType = EExternalForceCurveEvaluateType::Single; + + /** + * 経過時間の分割数 + * Number of divisions of elapsed time + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, + meta=(EditCondition="CurveEvaluateType!=EExternalForceCurveEvaluateType::Single"), + Category="KawaiiPhysics|ExternalForce") + int SubstepCount = 10; + + /** + * Scale factor for the time. + * This value is used to scale the time for the external force. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category ="KawaiiPhysics|ExternalForce") + float TimeScale = 1.0f; + + /** + * 各ボーンに適用するForce Rateを補正。 + * 「RootBoneから特定のボーンまでの長さ / RootBoneから末端のボーンまでの長さ」(0.0~1.0)の値におけるカーブの値をForceRateに乗算 + * Corrects the Force Rate applied to each bone. + * Multiplies the ForceRate by the curve value for "Length from RootBone to specific bone / Length from RootBone to end bone" (0.0~1.0) + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="KawaiiPhysics|ExternalForce") + FRuntimeFloatCurve ForceRateByBoneLengthRate; + + FVector GetForceValue(float InTime) const; + +private: + /** + * Current time. + * This value is used to track the current time for the external force. + */ + UPROPERTY() + float Time = 0.0f; + + /** + * Previous time. + * This value is used to track the previous time for the external force. + */ + UPROPERTY() + float PrevTime = 0.0f; + + /** + * Maximum curve time. + * This value is used to track the maximum time for the force curve. + */ + UPROPERTY() + float MaxCurveTime = 0.0f; + +public: + /** + * Initializes the maximum curve time. + * This function calculates the maximum time value from the ForceCurve and sets it to MaxCurveTime. + */ + void InitMaxCurveTime(); + + virtual void Initialize(const FAnimationInitializeContext& Context) override; + virtual void PreApply(FAnimNode_KawaiiPhysics& Node, const USkeletalMeshComponent* SkelComp) override; + virtual void Apply(FKawaiiPhysicsModifyBone& Bone, FAnimNode_KawaiiPhysics& Node, + const FComponentSpacePoseContext& PoseContext, + const FTransform& BoneTM = FTransform::Identity) override; +}; + +/// +/// Wind +/// +UCLASS(Blueprintable, BlueprintType, EditInlineNew, DefaultToInstanced, meta=(DisplayName = "Wind")) +class KAWAIIPHYSICS_API UKawaiiPhysics_ExternalForce_Wind : public UKawaiiPhysics_ExternalForce +{ + GENERATED_BODY() + +public: + UKawaiiPhysics_ExternalForce_Wind() + { + bCanSelectForceSpace = false; + ExternalForceSpace = EExternalForceSpace::WorldSpace; + } + + /** + * 各ボーンに適用するForce Rateを補正。 + * 「RootBoneから特定のボーンまでの長さ / RootBoneから末端のボーンまでの長さ」(0.0~1.0)の値におけるカーブの値をForceRateに乗算 + * Corrects the Force Rate applied to each bone. + * Multiplies the ForceRate by the curve value for "Length from RootBone to specific bone / Length from RootBone to end bone" (0.0~1.0) + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="KawaiiPhysics|ExternalForce") + FRuntimeFloatCurve ForceRateByBoneLengthRate; + +private: + /** + * Pointer to the world. + * This is used to access the world context for the external force. + */ + UPROPERTY() + UWorld* World; + +public: + virtual void PreApply(FAnimNode_KawaiiPhysics& Node, const USkeletalMeshComponent* SkelComp) override; + virtual void Apply(FKawaiiPhysicsModifyBone& Bone, FAnimNode_KawaiiPhysics& Node, + const FComponentSpacePoseContext& PoseContext, + const FTransform& BoneTM = FTransform::Identity) override; +}; diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/KawaiiPhysicsLibrary.h b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/KawaiiPhysicsLibrary.h index 7001b0d..2a7978f 100644 --- a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/KawaiiPhysicsLibrary.h +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/KawaiiPhysicsLibrary.h @@ -1,52 +1,18 @@ -// Copyright 2019-2026 pafuhana1213. All Rights Reserved. +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License #pragma once #include "AnimNode_KawaiiPhysics.h" -#include "ExternalForces/KawaiiPhysicsExternalForce.h" -#include "Animation/AnimNodeReference.h" +#include "KawaiiPhysicsExternalForce.h" #include "Kismet/BlueprintFunctionLibrary.h" #include "KawaiiPhysicsLibrary.generated.h" -UENUM() -enum class EKawaiiPhysicsAccessExternalForceResult : uint8 -{ - Valid, - NotValid, -}; - -#define KAWAIIPHYSICS_VALUE_SETTER(PropertyType, PropertyName) \ -{ \ - KawaiiPhysics.CallAnimNodeFunction( \ - TEXT("Set" #PropertyName), \ - [PropertyName](FAnimNode_KawaiiPhysics& InKawaiiPhysics) { \ - InKawaiiPhysics.PropertyName = PropertyName; \ - }); \ - return KawaiiPhysics; \ -} - -#define KAWAIIPHYSICS_VALUE_GETTER(PropertyType, PropertyName) \ - { \ - PropertyType Value; \ - KawaiiPhysics.CallAnimNodeFunction( \ - TEXT("Get" #PropertyName), \ - [&Value](FAnimNode_KawaiiPhysics& InKawaiiPhysics) { \ - Value = InKawaiiPhysics.PropertyName; \ - }); \ - return Value; \ -} - - -USTRUCT(BlueprintType) -struct FKawaiiPhysicsReference : public FAnimNodeReference -{ - GENERATED_BODY() - - using FInternalNodeType = FAnimNode_KawaiiPhysics; -}; +class UKawaiiPhysics_ExternalForce; /** - * Exposes operations to be performed on a blend space anim node. + * Kawaii Physics Blueprint Library (UE4.27 Compatible Version) + * Note: The FAnimNodeReference-based API is not available in UE4.27. + * This provides direct component-based functions instead. */ UCLASS() class KAWAIIPHYSICS_API UKawaiiPhysicsLibrary : public UBlueprintFunctionLibrary @@ -54,547 +20,26 @@ class KAWAIIPHYSICS_API UKawaiiPhysicsLibrary : public UBlueprintFunctionLibrary GENERATED_BODY() public: - /** Get a KawaiiPhysics from an anim node */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", - meta = (BlueprintThreadSafe, ExpandEnumAsExecs = "Result")) - static FKawaiiPhysicsReference ConvertToKawaiiPhysics(const FAnimNodeReference& Node, - EAnimNodeReferenceConversionResult& Result); - - /** Get a KawaiiPhysics from an anim node (pure). */ - UFUNCTION(BlueprintPure, Category = "Kawaii Physics", - meta = (BlueprintThreadSafe, DisplayName = "Convert to Kawaii Physics (Pure)")) - static void ConvertToKawaiiPhysicsPure(const FAnimNodeReference& Node, FKawaiiPhysicsReference& KawaiiPhysics, - bool& Result) - { - EAnimNodeReferenceConversionResult ConversionResult; - KawaiiPhysics = ConvertToKawaiiPhysics(Node, ConversionResult); - Result = (ConversionResult == EAnimNodeReferenceConversionResult::Succeeded); - } - /** Collect KawaiiPhysics Node References from AnimInstance(ABP) */ - static bool CollectKawaiiPhysicsNodes(TArray& Nodes, - UAnimInstance* AnimInstance, const FGameplayTagContainer& FilterTags, - bool bFilterExactMatch); + static void CollectKawaiiPhysicsNodes(TArray& OutNodes, + UAnimInstance* AnimInstance, const FGameplayTagContainer& FilterTags, + bool bFilterExactMatch); /** Collect KawaiiPhysics Node References from SkeletalMeshComponent */ - static bool CollectKawaiiPhysicsNodes(TArray& Nodes, - USkeletalMeshComponent* MeshComp, const FGameplayTagContainer& FilterTags, - bool bFilterExactMatch); - - /** ResetDynamics */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static FKawaiiPhysicsReference ResetDynamics(const FKawaiiPhysicsReference& KawaiiPhysics); - - /** Set RootBone */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static FKawaiiPhysicsReference SetRootBoneName(const FKawaiiPhysicsReference& KawaiiPhysics, - UPARAM(ref) FName& RootBoneName); - /** Get RootBone */ - UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static FName GetRootBoneName(const FKawaiiPhysicsReference& KawaiiPhysics); - - /** Set ExcludeBones */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static FKawaiiPhysicsReference SetExcludeBoneNames(const FKawaiiPhysicsReference& KawaiiPhysics, - UPARAM(ref) TArray& ExcludeBoneNames); - /** Get ExcludeBones */ - UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static TArray GetExcludeBoneNames(const FKawaiiPhysicsReference& KawaiiPhysics); - - // PhysicsSettings - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static FKawaiiPhysicsReference SetPhysicsSettings(const FKawaiiPhysicsReference& KawaiiPhysics, - UPARAM(ref) FKawaiiPhysicsSettings& PhysicsSettings) - { - KAWAIIPHYSICS_VALUE_SETTER(FKawaiiPhysicsSettings, PhysicsSettings); - } + static void CollectKawaiiPhysicsNodes(TArray& OutNodes, + USkeletalMeshComponent* MeshComp, const FGameplayTagContainer& FilterTags, + bool bFilterExactMatch); - UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static FKawaiiPhysicsSettings GetPhysicsSettings(const FKawaiiPhysicsReference& KawaiiPhysics) - { - KAWAIIPHYSICS_VALUE_GETTER(FKawaiiPhysicsSettings, PhysicsSettings); - } - - // DummyBoneLength - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static FKawaiiPhysicsReference SetDummyBoneLength(const FKawaiiPhysicsReference& KawaiiPhysics, - float DummyBoneLength) - { - KAWAIIPHYSICS_VALUE_SETTER(float, DummyBoneLength); - } - - UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static float GetDummyBoneLength(const FKawaiiPhysicsReference& KawaiiPhysics) - { - KAWAIIPHYSICS_VALUE_GETTER(float, DummyBoneLength); - } - - /** TeleportDistanceThreshold */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static FKawaiiPhysicsReference SetTeleportDistanceThreshold(const FKawaiiPhysicsReference& KawaiiPhysics, - float TeleportDistanceThreshold) - { - KAWAIIPHYSICS_VALUE_SETTER(float, TeleportDistanceThreshold); - } - - UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static float GetTeleportDistanceThreshold(const FKawaiiPhysicsReference& KawaiiPhysics) - { - KAWAIIPHYSICS_VALUE_GETTER(float, TeleportDistanceThreshold); - } - - /** TeleportRotationThreshold */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static FKawaiiPhysicsReference SetTeleportRotationThreshold(const FKawaiiPhysicsReference& KawaiiPhysics, - float TeleportRotationThreshold) - { - KAWAIIPHYSICS_VALUE_SETTER(float, TeleportRotationThreshold); - } - - UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static float GetTeleportRotationThreshold(const FKawaiiPhysicsReference& KawaiiPhysics) - { - KAWAIIPHYSICS_VALUE_GETTER(float, TeleportRotationThreshold); - } - - /** Gravity */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static FKawaiiPhysicsReference SetGravity(const FKawaiiPhysicsReference& KawaiiPhysics, FVector Gravity) - { - KAWAIIPHYSICS_VALUE_SETTER(FVector, Gravity); - } - - UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static FVector GetGravity(const FKawaiiPhysicsReference& KawaiiPhysics) - { - KAWAIIPHYSICS_VALUE_GETTER(FVector, Gravity); - } - - /** EnableWind */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static FKawaiiPhysicsReference SetEnableWind(const FKawaiiPhysicsReference& KawaiiPhysics, bool bEnableWind) - { - KAWAIIPHYSICS_VALUE_SETTER(bool, bEnableWind); - } - - UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static bool GetEnableWind(const FKawaiiPhysicsReference& KawaiiPhysics) - { - KAWAIIPHYSICS_VALUE_GETTER(bool, bEnableWind); - } - - /** WindScale */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static FKawaiiPhysicsReference SetWindScale(const FKawaiiPhysicsReference& KawaiiPhysics, float WindScale) - { - KAWAIIPHYSICS_VALUE_SETTER(float, WindScale); - } - - UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static float GetWindScale(const FKawaiiPhysicsReference& KawaiiPhysics) - { - KAWAIIPHYSICS_VALUE_GETTER(float, WindScale); - } - - /** AllowWorldCollision */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static FKawaiiPhysicsReference SetAllowWorldCollision(const FKawaiiPhysicsReference& KawaiiPhysics, - bool bAllowWorldCollision) - { - KAWAIIPHYSICS_VALUE_SETTER(bool, bAllowWorldCollision); - } - - UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static bool GetAllowWorldCollision(const FKawaiiPhysicsReference& KawaiiPhysics) - { - KAWAIIPHYSICS_VALUE_GETTER(bool, bAllowWorldCollision); - } - - /** NeedWarmUp */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static FKawaiiPhysicsReference SetNeedWarmUp(const FKawaiiPhysicsReference& KawaiiPhysics, bool bNeedWarmUp) - { - KAWAIIPHYSICS_VALUE_SETTER(bool, bNeedWarmUp); - } - - /** NeedWarmUp */ - UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static bool GetNeedWarmUp(const FKawaiiPhysicsReference& KawaiiPhysics) - { - KAWAIIPHYSICS_VALUE_GETTER(bool, bNeedWarmUp); - } - - /** LimitsDataAsset */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static FKawaiiPhysicsReference SetLimitsDataAsset(const FKawaiiPhysicsReference& KawaiiPhysics, - UKawaiiPhysicsLimitsDataAsset* LimitsDataAsset) - { - KAWAIIPHYSICS_VALUE_SETTER(TObjectPtr, LimitsDataAsset); - } - - /** LimitsDataAsset */ - UFUNCTION(BlueprintPure, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static UKawaiiPhysicsLimitsDataAsset* GetLimitsDataAsset(const FKawaiiPhysicsReference& KawaiiPhysics) - { - KAWAIIPHYSICS_VALUE_GETTER(TObjectPtr, LimitsDataAsset); - } - - /** Add ExternalForce With ExecResult */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", - meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult")) - static FKawaiiPhysicsReference AddExternalForceWithExecResult(EKawaiiPhysicsAccessExternalForceResult& ExecResult, - const FKawaiiPhysicsReference& KawaiiPhysics, - FInstancedStruct& ExternalForce, UObject* Owner); - - /** Add ExternalForce */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static bool AddExternalForce(const FKawaiiPhysicsReference& KawaiiPhysics, - FInstancedStruct& ExternalForce, UObject* Owner, bool bIsOneShot = false); - /** Add ExternalForces to SkeletalMeshComponent */ UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) static bool AddExternalForcesToComponent(USkeletalMeshComponent* MeshComp, - UPARAM(ref) TArray& ExternalForces, UObject* Owner, + UPARAM(ref) TArray& ExternalForces, UObject* Owner, UPARAM(ref) FGameplayTagContainer& FilterTags, bool bFilterExactMatch = false, bool bIsOneShot = false); - /** Remove ExternalForces from SkeletalMeshComponent (by Owner) */ UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) static bool RemoveExternalForcesFromComponent(USkeletalMeshComponent* MeshComp, UObject* Owner, UPARAM(ref) FGameplayTagContainer& FilterTags, bool bFilterExactMatch = false); - - /** - * Set alpha (input) to all KawaiiPhysics nodes in the component (and linked/post-process instances). - * This is intended for AnimNotifyState usage. - */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static bool SetAlphaToComponent(USkeletalMeshComponent* MeshComp, float Alpha, - UPARAM(ref) FGameplayTagContainer& FilterTags, - bool bFilterExactMatch = false); - - /** Get current alpha (input) from the first matched KawaiiPhysics node in the component. */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", meta=(BlueprintThreadSafe)) - static bool GetAlphaFromComponent(USkeletalMeshComponent* MeshComp, float& OutAlpha, - UPARAM(ref) FGameplayTagContainer& FilterTags, - bool bFilterExactMatch = false); - - - /** Set ExternalForceParameter template */ - template - static FKawaiiPhysicsReference SetExternalForceProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult, - const FKawaiiPhysicsReference& KawaiiPhysics, - int ExternalForceIndex, FName PropertyName, - ValueType Value); - /** Get ExternalForceParameter template */ - template - static ValueType GetExternalForceProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult, - const FKawaiiPhysicsReference& KawaiiPhysics, int ExternalForceIndex, - FName PropertyName); - - /** Set ExternalForceParameter template struct */ - template - static FKawaiiPhysicsReference SetExternalForceStructProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult, - const FKawaiiPhysicsReference& KawaiiPhysics, - int ExternalForceIndex, FName PropertyName, - ValueType Value); - /** Get ExternalForceParameter template struct */ - template - static ValueType GetExternalForceStructProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult, - const FKawaiiPhysicsReference& KawaiiPhysics, - int ExternalForceIndex, - FName PropertyName); - - /** Set ExternalForceParameter bool */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", - meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult")) - static FKawaiiPhysicsReference SetExternalForceBoolProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult, - const FKawaiiPhysicsReference& KawaiiPhysics, - int ExternalForceIndex, FName PropertyName, - bool Value) - { - return SetExternalForceProperty(ExecResult, KawaiiPhysics, ExternalForceIndex, - PropertyName, Value); - } - - /** Get ExternalForceParameter bool */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", - meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult")) - static bool GetExternalForceBoolProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult, - const FKawaiiPhysicsReference& KawaiiPhysics, int ExternalForceIndex, - FName PropertyName) - { - return GetExternalForceProperty(ExecResult, KawaiiPhysics, ExternalForceIndex, PropertyName); - } - - /** Set ExternalForceParameter int */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", - meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult")) - static FKawaiiPhysicsReference SetExternalForceIntProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult, - const FKawaiiPhysicsReference& KawaiiPhysics, - int ExternalForceIndex, FName PropertyName, - int32 Value) - { - return SetExternalForceProperty(ExecResult, KawaiiPhysics, ExternalForceIndex, - PropertyName, Value); - } - - /** Get ExternalForceParameter int */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", - meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult")) - static int32 GetExternalForceIntProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult, - const FKawaiiPhysicsReference& KawaiiPhysics, int ExternalForceIndex, - FName PropertyName) - { - return GetExternalForceProperty(ExecResult, KawaiiPhysics, ExternalForceIndex, PropertyName); - } - - /** Set ExternalForceParameter float */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", - meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult")) - static FKawaiiPhysicsReference SetExternalForceFloatProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult, - const FKawaiiPhysicsReference& KawaiiPhysics, - int ExternalForceIndex, FName PropertyName, - float Value) - { - return SetExternalForceProperty(ExecResult, KawaiiPhysics, ExternalForceIndex, - PropertyName, Value); - } - - /** Get ExternalForceParameter float */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", - meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult")) - static float GetExternalForceFloatProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult, - const FKawaiiPhysicsReference& KawaiiPhysics, int ExternalForceIndex, - FName PropertyName) - { - return GetExternalForceProperty(ExecResult, KawaiiPhysics, ExternalForceIndex, PropertyName); - } - - /** Get ExternalForceParameter Vector */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", - meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult")) - static FKawaiiPhysicsReference SetExternalForceVectorProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult, - const FKawaiiPhysicsReference& KawaiiPhysics, - int ExternalForceIndex, FName PropertyName, - FVector Value) - { - return SetExternalForceStructProperty(ExecResult, KawaiiPhysics, ExternalForceIndex, - PropertyName, Value); - } - - /** Get ExternalForceParameter Vector */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", - meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult")) - static FVector GetExternalForceVectorProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult, - const FKawaiiPhysicsReference& KawaiiPhysics, int ExternalForceIndex, - FName PropertyName) - { - return GetExternalForceStructProperty(ExecResult, KawaiiPhysics, ExternalForceIndex, PropertyName); - } - - /** Get ExternalForceParameter Rotator */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", - meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult")) - static FKawaiiPhysicsReference SetExternalForceRotatorProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult, - const FKawaiiPhysicsReference& KawaiiPhysics, - int ExternalForceIndex, FName PropertyName, - FRotator Value) - { - return SetExternalForceStructProperty(ExecResult, KawaiiPhysics, ExternalForceIndex, - PropertyName, Value); - } - - /** Get ExternalForceParameter Rotator */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", - meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult")) - static FRotator GetExternalForceRotatorProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult, - const FKawaiiPhysicsReference& KawaiiPhysics, - int ExternalForceIndex, - FName PropertyName) - { - return GetExternalForceStructProperty(ExecResult, KawaiiPhysics, ExternalForceIndex, PropertyName); - } - - /** Get ExternalForceParameter Transform */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", - meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult")) - static FKawaiiPhysicsReference SetExternalForceTransformProperty( - EKawaiiPhysicsAccessExternalForceResult& ExecResult, - const FKawaiiPhysicsReference& KawaiiPhysics, - int ExternalForceIndex, FName PropertyName, - FTransform Value) - { - return SetExternalForceStructProperty(ExecResult, KawaiiPhysics, ExternalForceIndex, - PropertyName, Value); - } - - /** Get ExternalForceParameter Transform */ - UFUNCTION(BlueprintCallable, Category = "Kawaii Physics", - meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult")) - static FTransform GetExternalForceTransformProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult, - const FKawaiiPhysicsReference& KawaiiPhysics, - int ExternalForceIndex, - FName PropertyName) - { - return GetExternalForceStructProperty(ExecResult, KawaiiPhysics, ExternalForceIndex, PropertyName); - } - - /** Set ExternalForceParameter Wildcard */ - UFUNCTION(BlueprintCallable, CustomThunk, Category = "Kawaii Physics", - meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult", CustomStructureParam = "Value")) - static void SetExternalForceWildcardProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult, - const FKawaiiPhysicsReference& KawaiiPhysics, int ExternalForceIndex, - FName PropertyName, const int32& Value) - { - checkNoEntry(); - } - - - /** Get ExternalForceParameter Wildcard */ - UFUNCTION(BlueprintCallable, CustomThunk, Category = "Kawaii Physics", - meta=(BlueprintThreadSafe, ExpandEnumAsExecs = "ExecResult", CustomStructureParam = "Value")) - static void GetExternalForceWildcardProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult, - const FKawaiiPhysicsReference& KawaiiPhysics, int ExternalForceIndex, - FName PropertyName, int32& Value) - { - checkNoEntry(); - } - -private: - DECLARE_FUNCTION(execSetExternalForceWildcardProperty); - DECLARE_FUNCTION(execGetExternalForceWildcardProperty); }; - -template -FKawaiiPhysicsReference UKawaiiPhysicsLibrary::SetExternalForceProperty( - EKawaiiPhysicsAccessExternalForceResult& ExecResult, const FKawaiiPhysicsReference& KawaiiPhysics, - int ExternalForceIndex, FName PropertyName, ValueType Value) -{ - ExecResult = EKawaiiPhysicsAccessExternalForceResult::NotValid; - - KawaiiPhysics.CallAnimNodeFunction( - TEXT("SetExternalForceProperty"), - [&ExecResult, &ExternalForceIndex, &PropertyName, &Value](FAnimNode_KawaiiPhysics& InKawaiiPhysics) - { - if (InKawaiiPhysics.ExternalForces.IsValidIndex(ExternalForceIndex) && - InKawaiiPhysics.ExternalForces[ExternalForceIndex].IsValid()) - { - const auto* ScriptStruct = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetScriptStruct(); - auto& Force = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetMutable< - FKawaiiPhysics_ExternalForce>(); - - if (const PropertyType* Property = FindFProperty(ScriptStruct, PropertyName)) - { - if (void* ValuePtr = Property->template ContainerPtrToValuePtr(&Force)) - { - Property->SetPropertyValue(ValuePtr, Value); - ExecResult = EKawaiiPhysicsAccessExternalForceResult::Valid; - } - } - } - }); - - return KawaiiPhysics; -} - -template -ValueType UKawaiiPhysicsLibrary::GetExternalForceProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult, - const FKawaiiPhysicsReference& KawaiiPhysics, - int ExternalForceIndex, FName PropertyName) -{ - ValueType Result; - ExecResult = EKawaiiPhysicsAccessExternalForceResult::NotValid; - - KawaiiPhysics.CallAnimNodeFunction( - TEXT("GetExternalForceProperty"), - [&Result, &ExecResult, &ExternalForceIndex, &PropertyName](FAnimNode_KawaiiPhysics& InKawaiiPhysics) - { - if (InKawaiiPhysics.ExternalForces.IsValidIndex(ExternalForceIndex) && - InKawaiiPhysics.ExternalForces[ExternalForceIndex].IsValid()) - { - const auto* ScriptStruct = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetScriptStruct(); - const auto& Force = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetMutable< - FKawaiiPhysics_ExternalForce>(); - - if (const FProperty* Property = FindFProperty(ScriptStruct, PropertyName)) - { - Result = *(Property->ContainerPtrToValuePtr(&Force)); - ExecResult = EKawaiiPhysicsAccessExternalForceResult::Valid; - } - } - }); - - return Result; -} - -template -FKawaiiPhysicsReference UKawaiiPhysicsLibrary::SetExternalForceStructProperty( - EKawaiiPhysicsAccessExternalForceResult& ExecResult, const FKawaiiPhysicsReference& KawaiiPhysics, - int ExternalForceIndex, FName PropertyName, ValueType Value) -{ - ExecResult = EKawaiiPhysicsAccessExternalForceResult::NotValid; - - KawaiiPhysics.CallAnimNodeFunction( - TEXT("SetExternalForceStructProperty"), - [&ExecResult, &ExternalForceIndex, &PropertyName, &Value](FAnimNode_KawaiiPhysics& InKawaiiPhysics) - { - if (InKawaiiPhysics.ExternalForces.IsValidIndex(ExternalForceIndex) && - InKawaiiPhysics.ExternalForces[ExternalForceIndex].IsValid()) - { - const auto* ScriptStruct = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetScriptStruct(); - auto& Force = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetMutable< - FKawaiiPhysics_ExternalForce>(); - - if (const FStructProperty* StructProperty = FindFProperty( - ScriptStruct, PropertyName)) - { - if (StructProperty->Struct == TBaseStructure::Get()) - { - if (void* ValuePtr = StructProperty->ContainerPtrToValuePtr(&Force)) - { - StructProperty->CopyCompleteValue(ValuePtr, &Value); - ExecResult = EKawaiiPhysicsAccessExternalForceResult::Valid; - } - } - } - } - }); - - return KawaiiPhysics; -} - -template -ValueType UKawaiiPhysicsLibrary::GetExternalForceStructProperty(EKawaiiPhysicsAccessExternalForceResult& ExecResult, - const FKawaiiPhysicsReference& KawaiiPhysics, - int ExternalForceIndex, FName PropertyName) -{ - ValueType Result; - ExecResult = EKawaiiPhysicsAccessExternalForceResult::NotValid; - - KawaiiPhysics.CallAnimNodeFunction( - TEXT("GetExternalForceStructProperty"), - [&Result, &ExecResult, &ExternalForceIndex, &PropertyName](FAnimNode_KawaiiPhysics& InKawaiiPhysics) - { - if (InKawaiiPhysics.ExternalForces.IsValidIndex(ExternalForceIndex) && - InKawaiiPhysics.ExternalForces[ExternalForceIndex].IsValid()) - { - const auto* ScriptStruct = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetScriptStruct(); - const auto& Force = InKawaiiPhysics.ExternalForces[ExternalForceIndex].GetMutable< - FKawaiiPhysics_ExternalForce>(); - - if (const FStructProperty* StructProperty = FindFProperty( - ScriptStruct, PropertyName)) - { - if (StructProperty->Struct == TBaseStructure::Get()) - { - Result = *(StructProperty->ContainerPtrToValuePtr(&Force)); - ExecResult = EKawaiiPhysicsAccessExternalForceResult::Valid; - } - } - } - }); - - return Result; -} diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/KawaiiPhysicsLimitsDataAsset.h b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/KawaiiPhysicsLimitsDataAsset.h index f69bdc1..861132d 100644 --- a/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/KawaiiPhysicsLimitsDataAsset.h +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysics/Public/KawaiiPhysicsLimitsDataAsset.h @@ -1,4 +1,4 @@ -// Copyright 2019-2026 pafuhana1213. All Rights Reserved. +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License #pragma once @@ -150,7 +150,7 @@ class KAWAIIPHYSICS_API UKawaiiPhysicsLimitsDataAsset : public UDataAsset, publi public: #if WITH_EDITORONLY_DATA UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Skeleton") - TObjectPtr Skeleton; + USkeleton* Skeleton; // Deprecated UPROPERTY(meta=(DeprecatedProperty)) @@ -181,7 +181,7 @@ class KAWAIIPHYSICS_API UKawaiiPhysicsLimitsDataAsset : public UDataAsset, publi // End UObject Interface. // IBoneReferenceSkeletonProvider interface - virtual USkeleton* GetSkeleton(bool& bInvalidSkeletonIsError, const IPropertyHandle* PropertyHandle) override; + virtual USkeleton* GetSkeleton(bool& bInvalidSkeletonIsError) override; #if WITH_EDITOR void UpdateLimit(FCollisionLimitBase* Limit); diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/KawaiiPhysicsEd.Build.cs b/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/KawaiiPhysicsEd.Build.cs index 258dc7c..1785371 100644 --- a/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/KawaiiPhysicsEd.Build.cs +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/KawaiiPhysicsEd.Build.cs @@ -1,4 +1,4 @@ -// Copyright 2019-2026 pafuhana1213. All Rights Reserved. +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License using UnrealBuildTool; @@ -21,7 +21,9 @@ public KawaiiPhysicsEd(ReadOnlyTargetRules Target) : base(Target) "UnrealEd", "AnimGraphRuntime", "Slate", - "SlateCore" + "SlateCore", + "GameplayTags", + "PropertyEditor" }); if(Target.Version.MajorVersion >= 5) diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Private/AnimGraphNode_KawaiiPhysics.cpp b/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Private/AnimGraphNode_KawaiiPhysics.cpp index 9db0fa2..deda1e1 100644 --- a/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Private/AnimGraphNode_KawaiiPhysics.cpp +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Private/AnimGraphNode_KawaiiPhysics.cpp @@ -1,4 +1,4 @@ -// Copyright 2019-2026 pafuhana1213. All Rights Reserved. +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License #include "AnimGraphNode_KawaiiPhysics.h" @@ -11,20 +11,14 @@ #include "KawaiiPhysicsLimitsDataAsset.h" #include "Widgets/Input/SButton.h" #include "Framework/Notifications/NotificationManager.h" -#include "Selection.h" +#include "Engine/Selection.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Notifications/SNotificationList.h" #include "AssetRegistry/AssetRegistryModule.h" #include "Dialogs/DlgPickAssetPath.h" #include "Kismet2/CompilerResultsLog.h" #include "Widgets/Layout/SUniformGridPanel.h" -#include "Widgets/Layout/SSeparator.h" -#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 6 -#include "Animation/AnimInstance.h" -#endif - -#include UE_INLINE_GENERATED_CPP_BY_NAME(AnimGraphNode_KawaiiPhysics) #define LOCTEXT_NAMESPACE "KawaiiPhysics" @@ -171,13 +165,20 @@ void UAnimGraphNode_KawaiiPhysics::CopyNodeDataToPreviewNode(FAnimNode_Base* Ani KawaiiPhysics->BoneConstraints = Node.BoneConstraints; KawaiiPhysics->BoneConstraintsDataAsset = Node.BoneConstraintsDataAsset; + // Quad Collision + KawaiiPhysics->bEnableQuadCollision = Node.bEnableQuadCollision; + KawaiiPhysics->QuadCollisionIterationCount = Node.QuadCollisionIterationCount; + KawaiiPhysics->QuadCollisionThreshold = Node.QuadCollisionThreshold; + KawaiiPhysics->QuadCollisionEdgeStiffness = Node.QuadCollisionEdgeStiffness; + KawaiiPhysics->QuadCollisionEdgeStiffnessCurve = Node.QuadCollisionEdgeStiffnessCurve; + KawaiiPhysics->bQuadCollisionIncludeDummyBones = Node.bQuadCollisionIncludeDummyBones; + KawaiiPhysics->bQuadCollisionCloseLoop = Node.bQuadCollisionCloseLoop; + KawaiiPhysics->QuadCollisionBoneConstraintsDataAsset = Node.QuadCollisionBoneConstraintsDataAsset; + // SimulationSpace KawaiiPhysics->SimulationSpace = Node.SimulationSpace; KawaiiPhysics->SimulationBaseBone = Node.SimulationBaseBone; - // SyncBone - KawaiiPhysics->SyncBones = Node.SyncBones; - // Reset for sync without compile KawaiiPhysics->ModifyBones.Empty(); } @@ -205,7 +206,6 @@ void UAnimGraphNode_KawaiiPhysics::CustomizeDetailTools(IDetailLayoutBuilder& De [ SNew(STextBlock) .Text(FText::FromString(TEXT("Export Limits"))) - .Font(FSlateFontInfo(FCoreStyle::GetDefaultFont(), 9)) ] ] + SUniformGridPanel::Slot(1, 0) @@ -222,7 +222,6 @@ void UAnimGraphNode_KawaiiPhysics::CustomizeDetailTools(IDetailLayoutBuilder& De [ SNew(STextBlock) .Text(FText::FromString(TEXT("Export BoneConstraints"))) - .Font(FSlateFontInfo(FCoreStyle::GetDefaultFont(), 9)) ] ] ]; @@ -234,137 +233,188 @@ void UAnimGraphNode_KawaiiPhysics::CustomizeDetailDebugVisualizations(IDetailLay FDetailWidgetRow& WidgetRow = ViewportCategory.AddCustomRow( LOCTEXT("ToggleDebugVisualizationButtonRow", "DebugVisualization")); - auto CreateDebugButton = [&](const FString& Label, bool& DebugFlag) - { - return SNew(SButton) + WidgetRow + [ + SNew(SUniformGridPanel) + .SlotPadding(FMargin(2, 0, 2, 0)) + // Show/Hide Bones button. + + SUniformGridPanel::Slot(0, 0) + [ + SNew(SButton) .HAlign(HAlign_Center) .VAlign(VAlign_Center) - .OnClicked_Lambda([&]() + .OnClicked_Lambda([this]() { - DebugFlag = !DebugFlag; + this->bEnableDebugDrawBone = !this->bEnableDebugDrawBone; return FReply::Handled(); }) - .ButtonColorAndOpacity_Lambda([&]() - { - return DebugFlag - ? FAppStyle::Get().GetSlateColor("Colors.AccentGreen") - : FAppStyle::Get().GetSlateColor("Colors.AccentRed"); - }) + .ButtonColorAndOpacity_Lambda([this]() -> FLinearColor { return this->bEnableDebugDrawBone ? FLinearColor::Green : FLinearColor::Red; }) .Content() [ SNew(STextBlock) - .Text(FText::FromString(Label)) - .Font(FSlateFontInfo(FCoreStyle::GetDefaultFont(), 9)) - ]; - }; - - auto CreateCategorySeparator = [&](const FString& Label, const int32 FontSize = 9) - { - return SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(0.01f) - .VAlign(VAlign_Center) - [ - SNew(SSeparator) + .Text_Lambda([this]() { return LOCTEXT("ShowBoneText", "Bone"); }) ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(FMargin(2.f, 0.f)) + ] + // Show/Hide LengthRate button. + + SUniformGridPanel::Slot(1, 0) + [ + SNew(SButton) + .HAlign(HAlign_Center) .VAlign(VAlign_Center) + .OnClicked_Lambda([this]() + { + this->bEnableDebugBoneLengthRate = !this->bEnableDebugBoneLengthRate; + return FReply::Handled(); + }) + .ButtonColorAndOpacity_Lambda([this]() -> FLinearColor { return this->bEnableDebugBoneLengthRate ? FLinearColor::Green : FLinearColor::Red; }) + .Content() [ SNew(STextBlock) - .Text(FText::FromString(Label)) - .Font(FSlateFontInfo(FCoreStyle::GetDefaultFont(), FontSize)) + .Text_Lambda([this]() { return LOCTEXT("ShowLengthRateText", "Length Rate"); }) ] - + SHorizontalBox::Slot() - .FillWidth(0.9f) - .VAlign(VAlign_Center) - [ - SNew(SSeparator) - ]; - }; - - WidgetRow - [ - SNew(SVerticalBox) - - // Common - + SVerticalBox::Slot() - .AutoHeight() - .Padding(FMargin(0.f, 2.f)) - [ - CreateCategorySeparator(TEXT("Common")) ] - + SVerticalBox::Slot() - .AutoHeight() + // Show/Hide AngleLimit button. + + SUniformGridPanel::Slot(2, 0) [ - SNew(SUniformGridPanel) - + SUniformGridPanel::Slot(0, 0) - [ - CreateDebugButton(TEXT("Bone"), bEnableDebugDrawBone) - ] - + SUniformGridPanel::Slot(1, 0) - [ - CreateDebugButton(TEXT("Length Rate"), bEnableDebugBoneLengthRate) - ] - + SUniformGridPanel::Slot(2, 0) + SNew(SButton) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .OnClicked_Lambda([this]() + { + this->bEnableDebugDrawLimitAngle = !this->bEnableDebugDrawLimitAngle; + return FReply::Handled(); + }) + .ButtonColorAndOpacity_Lambda([this]() -> FLinearColor { return this->bEnableDebugDrawLimitAngle ? FLinearColor::Green : FLinearColor::Red; }) + .Content() [ - CreateDebugButton(TEXT("Limit Angle") , bEnableDebugDrawLimitAngle) + SNew(STextBlock) + .Text_Lambda([this]() { return LOCTEXT("ShowLimitAngleText", "Limit Angle"); }) ] ] - - // Limits - + SVerticalBox::Slot() - .AutoHeight() - .Padding(FMargin(0.f, 2.f)) - [ - CreateCategorySeparator(TEXT("Collision")) - ] - + SVerticalBox::Slot() - .AutoHeight() + // Show/Hide SphereLimit button. + + SUniformGridPanel::Slot(0, 1) [ - SNew(SUniformGridPanel) - + SUniformGridPanel::Slot(0, 0) - [ - CreateDebugButton(TEXT("Sphere"), bEnableDebugDrawSphereLimit) - ] - + SUniformGridPanel::Slot(1, 0) + SNew(SButton) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .OnClicked_Lambda([this]() + { + this->bEnableDebugDrawSphereLimit = !this->bEnableDebugDrawSphereLimit; + return FReply::Handled(); + }) + .ButtonColorAndOpacity_Lambda([this]() -> FLinearColor { return this->bEnableDebugDrawSphereLimit ? FLinearColor::Green : FLinearColor::Red; }) + .Content() [ - CreateDebugButton(TEXT("Capsule"), bEnableDebugDrawCapsuleLimit) + SNew(STextBlock) + .Text_Lambda([this]() { return LOCTEXT("ShowSphereLimitText", "Sphere Limit"); }) ] - + SUniformGridPanel::Slot(2, 0) + ] + // Show/Hide CapsuleLimit button. + + SUniformGridPanel::Slot(1, 1) + [ + SNew(SButton) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .OnClicked_Lambda([this]() + { + this->bEnableDebugDrawCapsuleLimit = !this->bEnableDebugDrawCapsuleLimit; + return FReply::Handled(); + }) + .ButtonColorAndOpacity_Lambda([this]() -> FLinearColor { return this->bEnableDebugDrawCapsuleLimit ? FLinearColor::Green : FLinearColor::Red; }) + .Content() [ - CreateDebugButton(TEXT("Box"), bEnableDebugDrawBoxLimit) + SNew(STextBlock) + .Text_Lambda([this]() { return LOCTEXT("ShowCapsuleLimitText", "Capsule Limit"); }) ] - + SUniformGridPanel::Slot(0, 1) + ] + // Show/Hide BoxLimit button. + + SUniformGridPanel::Slot(2, 1) + [ + SNew(SButton) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .OnClicked_Lambda([this]() + { + this->bEnableDebugDrawBoxLimit = !this->bEnableDebugDrawBoxLimit; + return FReply::Handled(); + }) + .ButtonColorAndOpacity_Lambda([this]() -> FLinearColor { return this->bEnableDebugDrawBoxLimit ? FLinearColor::Green : FLinearColor::Red; }) + .Content() [ - CreateDebugButton(TEXT("Plane"), bEnableDebugDrawPlanerLimit) + SNew(STextBlock) + .Text_Lambda([this]() { return LOCTEXT("ShowBoxLimitText", "Box Limit"); }) ] ] - - // Advanced - + SVerticalBox::Slot() - .AutoHeight() - .Padding(FMargin(0.f, 2.f)) + // Show/Hide PlanerLimit button. + + SUniformGridPanel::Slot(0, 2) [ - CreateCategorySeparator(TEXT("Advanced")) + SNew(SButton) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .OnClicked_Lambda([this]() + { + this->bEnableDebugDrawPlanerLimit = !this->bEnableDebugDrawPlanerLimit; + return FReply::Handled(); + }) + .ButtonColorAndOpacity_Lambda([this]() -> FLinearColor { return this->bEnableDebugDrawPlanerLimit ? FLinearColor::Green : FLinearColor::Red; }) + .Content() + [ + SNew(STextBlock) + .Text_Lambda([this]() { return LOCTEXT("ShowPlanerLimitText", "Planer Limit"); }) + ] ] - + SVerticalBox::Slot() - .AutoHeight() - .Padding(FMargin(0.f, 2.f)) + // Show/Hide BoneConstraint button. + + SUniformGridPanel::Slot(1, 2) [ - SNew(SUniformGridPanel) - + SUniformGridPanel::Slot(0, 0) + SNew(SButton) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .OnClicked_Lambda([this]() + { + this->bEnableDebugDrawBoneConstraint = !this->bEnableDebugDrawBoneConstraint; + return FReply::Handled(); + }) + .ButtonColorAndOpacity_Lambda([this]() -> FLinearColor { return this->bEnableDebugDrawBoneConstraint ? FLinearColor::Green : FLinearColor::Red; }) + .Content() [ - CreateDebugButton(TEXT("Sync Bone"), bEnableDebugDrawSyncBone) + SNew(STextBlock) + .Text_Lambda([this]() { return LOCTEXT("ShowBoneConstraintText", "Bone Constraint"); }) ] - + SUniformGridPanel::Slot(1, 0) + ] + // Show/Hide QuadCollision button. + + SUniformGridPanel::Slot(2, 2) + [ + SNew(SButton) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .OnClicked_Lambda([this]() + { + this->bEnableDebugDrawQuadCollision = !this->bEnableDebugDrawQuadCollision; + return FReply::Handled(); + }) + .ButtonColorAndOpacity_Lambda([this]() -> FLinearColor { return this->bEnableDebugDrawQuadCollision ? FLinearColor::Green : FLinearColor::Red; }) + .Content() [ - CreateDebugButton(TEXT("Bone Constraint"), bEnableDebugDrawBoneConstraint) + SNew(STextBlock) + .Text_Lambda([this]() { return LOCTEXT("ShowQuadCollisionText", "Quad Collision"); }) ] - + SUniformGridPanel::Slot(2, 0) + ] + // Show/Hide ExternalForce button. + + SUniformGridPanel::Slot(0, 3) + [ + SNew(SButton) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + .OnClicked_Lambda([this]() + { + this->bEnableDebugDrawExternalForce = !this->bEnableDebugDrawExternalForce; + return FReply::Handled(); + }) + .ButtonColorAndOpacity_Lambda([this]() -> FLinearColor { return this->bEnableDebugDrawExternalForce ? FLinearColor::Green : FLinearColor::Red; }) + .Content() [ - CreateDebugButton(TEXT("External Force"), bEnableDebugDrawExternalForce) + SNew(STextBlock) + .Text_Lambda([this]() { return LOCTEXT("ShowExternalForceText", "External Force"); }) ] ] ]; @@ -401,10 +451,9 @@ void UAnimGraphNode_KawaiiPhysics::CustomizeDetails(IDetailLayoutBuilder& Detail // Limits SafeSetOrder(FName("Limits")); - SafeSetOrder(FName("Bone Constraint")); + SafeSetOrder(FName("Bone Constraint (Experimental)")); // Other - SafeSetOrder(FName("Sync Bone")); SafeSetOrder(FName("World Collision")); SafeSetOrder(FName("ExternalForce")); diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Private/KawaiiPhysicsEd.cpp b/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Private/KawaiiPhysicsEd.cpp index 88ecccd..f8e50e2 100644 --- a/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Private/KawaiiPhysicsEd.cpp +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Private/KawaiiPhysicsEd.cpp @@ -1,4 +1,4 @@ -// Copyright 2019-2026 pafuhana1213. All Rights Reserved. +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License #include "KawaiiPhysicsEd.h" #include "Modules/ModuleManager.h" diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Private/KawaiiPhysicsEditMode.cpp b/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Private/KawaiiPhysicsEditMode.cpp index b881e7f..c4236c1 100644 --- a/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Private/KawaiiPhysicsEditMode.cpp +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Private/KawaiiPhysicsEditMode.cpp @@ -1,4 +1,4 @@ -// Copyright 2019-2026 pafuhana1213. All Rights Reserved. +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License #include "KawaiiPhysicsEditMode.h" #include "CanvasItem.h" @@ -7,16 +7,12 @@ #include "EditorViewportClient.h" #include "IPersonaPreviewScene.h" #include "KawaiiPhysics.h" -#include "ExternalForces/KawaiiPhysicsExternalForce.h" +#include "KawaiiPhysicsExternalForce.h" #include "KawaiiPhysicsLimitsDataAsset.h" #include "SceneManagement.h" #include "Animation/DebugSkelMeshComponent.h" #include "Materials/MaterialInstanceDynamic.h" -#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 6 -#include "SceneView.h" -#endif - #define LOCTEXT_NAMESPACE "KawaiiPhysicsEditMode" DEFINE_LOG_CATEGORY(LogKawaiiPhysics); @@ -67,13 +63,13 @@ void FKawaiiPhysicsEditMode::EnterMode(UAnimGraphNode_Base* InEditorNode, FAnimN GraphNode->Node.PlanarLimitsData = RuntimeNode->PlanarLimitsData; GraphNode->Node.BoneConstraintsData = RuntimeNode->BoneConstraintsData; GraphNode->Node.MergedBoneConstraints = RuntimeNode->MergedBoneConstraints; - - // SyncBone - GraphNode->Node.SyncBones = RuntimeNode->SyncBones; + GraphNode->Node.QuadCollisionBoneConstraintsData = RuntimeNode->QuadCollisionBoneConstraintsData; + GraphNode->Node.MergedQuadCollisionBoneConstraints = RuntimeNode->MergedQuadCollisionBoneConstraints; + GraphNode->Node.CachedQuads = RuntimeNode->CachedQuads; NodePropertyDelegateHandle = GraphNode->OnNodePropertyChanged().AddSP( this, &FKawaiiPhysicsEditMode::OnExternalNodePropertyChange); - if (RuntimeNode->LimitsDataAsset) + if (RuntimeNode->LimitsDataAsset != nullptr) { LimitsDataAssetPropertyDelegateHandle = RuntimeNode->LimitsDataAsset->OnLimitsChanged.AddRaw( @@ -87,13 +83,13 @@ void FKawaiiPhysicsEditMode::EnterMode(UAnimGraphNode_Base* InEditorNode, FAnimN BaseElemSelectedMaterial, GetTransientPackage()); PhysicsAssetBodyMaterial->SetScalarParameterValue(TEXT("Opacity"), 0.2f); - FAnimNodeEditMode::EnterMode(InEditorNode, InRuntimeNode); + FKawaiiPhysicsEditModeBase::EnterMode(InEditorNode, InRuntimeNode); } void FKawaiiPhysicsEditMode::ExitMode() { GraphNode->OnNodePropertyChanged().Remove(NodePropertyDelegateHandle); - if (RuntimeNode->LimitsDataAsset) + if (RuntimeNode->LimitsDataAsset != nullptr) { RuntimeNode->LimitsDataAsset->OnLimitsChanged.Remove(LimitsDataAssetPropertyDelegateHandle); } @@ -101,24 +97,24 @@ void FKawaiiPhysicsEditMode::ExitMode() GraphNode = nullptr; RuntimeNode = nullptr; - FAnimNodeEditMode::ExitMode(); + FKawaiiPhysicsEditModeBase::ExitMode(); } void FKawaiiPhysicsEditMode::Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) { const USkeletalMeshComponent* SkelMeshComp = GetAnimPreviewScene().GetPreviewMeshComponent(); - if (SkelMeshComp && SkelMeshComp->GetSkeletalMeshAsset() && SkelMeshComp->GetSkeletalMeshAsset()->GetSkeleton() && - FAnimWeight::IsRelevant(RuntimeNode->GetAlpha() && RuntimeNode->IsRecentlyEvaluated())) + if (SkelMeshComp && SkelMeshComp->SkeletalMesh && SkelMeshComp->SkeletalMesh->GetSkeleton()) { RenderModifyBones(PDI); RenderLimitAngle(PDI); - RenderSyncBone(PDI); RenderSphericalLimits(PDI); RenderCapsuleLimit(PDI); RenderBoxLimit(PDI); RenderPlanerLimit(PDI); RenderBoneConstraint(PDI); + RenderBoneConstraint(PDI); + RenderQuadCollision(PDI); RenderExternalForces(PDI); PDI->SetHitProxy(nullptr); @@ -143,7 +139,7 @@ void FKawaiiPhysicsEditMode::Render(const FSceneView* View, FViewport* Viewport, CollisionLocation = BaseBoneSpace2ComponentSpace.TransformPosition(CollisionLocation); CollisionRotation = BaseBoneSpace2ComponentSpace.TransformRotation(CollisionRotation); } - + PDI->DrawPoint(BoneTransform.GetLocation(), FLinearColor::White, 10.0f, SDPG_Foreground); DrawDashedLine(PDI, CollisionLocation, BoneTransform.GetLocation(), FLinearColor::White, 1, SDPG_Foreground); @@ -153,7 +149,7 @@ void FKawaiiPhysicsEditMode::Render(const FSceneView* View, FViewport* Viewport, } } - FAnimNodeEditMode::Render(View, Viewport, PDI); + FKawaiiPhysicsEditModeBase::Render(View, Viewport, PDI); } void FKawaiiPhysicsEditMode::RenderModifyBones(FPrimitiveDrawInterface* PDI) const @@ -168,7 +164,7 @@ void FKawaiiPhysicsEditMode::RenderModifyBones(FPrimitiveDrawInterface* PDI) con const FTransform& BaseBoneSpace2ComponentSpace = RuntimeNode->GetBaseBoneSpace2ComponentSpace(); BoneLocation = BaseBoneSpace2ComponentSpace.TransformPosition(BoneLocation); } - + PDI->DrawPoint(BoneLocation, FLinearColor::White, 5.0f, SDPG_Foreground); if (Bone.PhysicsSettings.Radius > 0) @@ -185,7 +181,7 @@ void FKawaiiPhysicsEditMode::RenderModifyBones(FPrimitiveDrawInterface* PDI) con const FTransform& BaseBoneSpace2ComponentSpace = RuntimeNode->GetBaseBoneSpace2ComponentSpace(); ChildBoneLocation = BaseBoneSpace2ComponentSpace.TransformPosition(ChildBoneLocation); } - + DrawDashedLine(PDI, BoneLocation, ChildBoneLocation, FLinearColor::White, 1, SDPG_Foreground); } @@ -211,7 +207,7 @@ void FKawaiiPhysicsEditMode::RenderLimitAngle(FPrimitiveDrawInterface* PDI) cons BoneTransform = BoneTransform * BaseBoneSpace2ComponentSpace; ParentBoneTransform = ParentBoneTransform * BaseBoneSpace2ComponentSpace; } - + const float Angle = FMath::DegreesToRadians(Bone.PhysicsSettings.LimitAngle); DrawCone(PDI, FScaleMatrix(5.0f) * FTransform( (BoneTransform.GetLocation() - ParentBoneTransform.GetLocation()).Rotation(), @@ -224,202 +220,89 @@ void FKawaiiPhysicsEditMode::RenderLimitAngle(FPrimitiveDrawInterface* PDI) cons } } -void FKawaiiPhysicsEditMode::RenderSyncBone(FPrimitiveDrawInterface* PDI) const +void FKawaiiPhysicsEditMode::RenderSphericalLimits(FPrimitiveDrawInterface* PDI) const { - if (!GraphNode->bEnableDebugDrawSyncBone) + if (GraphNode->bEnableDebugDrawSphereLimit) { - return; - } - - auto ApplyDirectionFilterAndAlpha = [&](double& Delta, const float& Alpha, - const ESyncBoneDirection Direction) - { - if (Direction != ESyncBoneDirection::None && - (Direction == ESyncBoneDirection::Both || - (Direction == ESyncBoneDirection::Positive && Delta > 0) || - (Direction == ESyncBoneDirection::Negative && Delta < 0))) - { - Delta = FMath::Lerp(0.0f, Delta, Alpha); - } - else + for (int32 i = 0; i < RuntimeNode->SphericalLimits.Num(); i++) { - Delta = 0.0f; - } - }; - - auto DrawForceArrow = [&](const FVector& Force, const FVector& Location) - { - const FRotator Rotation = FRotationMatrix::MakeFromX(Force.GetSafeNormal()).Rotator(); - const FMatrix TransformMatrix = FRotationMatrix(Rotation) * FTranslationMatrix(Location); - DrawDirectionalArrow(PDI, TransformMatrix, FLinearColor::Green, Force.Length(), 2.0f, SDPG_Foreground); - }; - - for (auto& SyncBone : RuntimeNode->SyncBones) - { - // InitialPoseLocation - DrawBox(PDI, FTranslationMatrix(SyncBone.InitialPoseLocation), FVector(1.0f), - GEngine->ConstraintLimitMaterialY->GetRenderProxy(), SDPG_World); - - // Current SyncBone Location - DrawBox(PDI, FTranslationMatrix(SyncBone.InitialPoseLocation + SyncBone.DeltaDistance), FVector(1.0f), - GEngine->ConstraintLimitMaterialY->GetRenderProxy(), SDPG_World); - - // DeltaMovement - DrawDashedLine(PDI, SyncBone.InitialPoseLocation, - SyncBone.InitialPoseLocation + SyncBone.DeltaDistance, - FLinearColor::Green, 0.1f, SDPG_World); - - // Distance attenuation radii - if (SyncBone.bEnableDistanceAttenuation) - { - const FVector Center = SyncBone.InitialPoseLocation + SyncBone.DeltaDistance; - // current location in component space - if (SyncBone.AttenuationInnerRadius > 0.0f) - { - DrawWireSphere(PDI, Center, FLinearColor(0.0f, 0.8f, 1.0f), SyncBone.AttenuationInnerRadius, 24, - SDPG_World); - } - if (SyncBone.AttenuationOuterRadius > 0.0f) + auto& Sphere = RuntimeNode->SphericalLimits[i]; + if (Sphere.Radius > 0) { - DrawWireSphere(PDI, Center, FLinearColor(0.0f, 0.3f, 0.0f), SyncBone.AttenuationOuterRadius, 24, - SDPG_World); + PDI->SetHitProxy(new HKawaiiPhysicsHitProxy(ECollisionLimitType::Spherical, i)); + DrawSphere(PDI, Sphere.Location, FRotator::ZeroRotator, FVector(Sphere.Radius), 24, 6, + GEngine->ConstraintLimitMaterialPrismatic->GetRenderProxy(), SDPG_World); + DrawWireSphere(PDI, Sphere.Location, FLinearColor::Black, Sphere.Radius, 24, SDPG_World); + DrawCoordinateSystem(PDI, Sphere.Location, Sphere.Rotation.Rotator(), Sphere.Radius, SDPG_World + 1); } } - // Force By SyncForce - FVector Force = SyncBone.DeltaDistance; - ApplyDirectionFilterAndAlpha(Force.X, SyncBone.GlobalScale.X, SyncBone.ApplyDirectionX); - ApplyDirectionFilterAndAlpha(Force.Y, SyncBone.GlobalScale.Y, SyncBone.ApplyDirectionY); - ApplyDirectionFilterAndAlpha(Force.Z, SyncBone.GlobalScale.Z, SyncBone.ApplyDirectionZ); - DrawForceArrow(Force, SyncBone.InitialPoseLocation); - - // Target Bone - for (auto& TargetRoot : SyncBone.TargetRoots) + for (int32 i = 0; i < RuntimeNode->SphericalLimitsData.Num(); i++) { - TargetRoot.DebugDraw(PDI, RuntimeNode); - for (auto& ChildTarget : TargetRoot.ChildTargets) + auto& Sphere = RuntimeNode->SphericalLimitsData[i]; + if (Sphere.Radius > 0) { - ChildTarget.DebugDraw(PDI, RuntimeNode); + PDI->SetHitProxy(new HKawaiiPhysicsHitProxy(ECollisionLimitType::Spherical, i, Sphere.SourceType)); + DrawSphere(PDI, Sphere.Location, FRotator::ZeroRotator, FVector(Sphere.Radius), 24, 6, + GEngine->ConstraintLimitMaterialZ->GetRenderProxy(), SDPG_World); + DrawWireSphere(PDI, Sphere.Location, FLinearColor::Black, Sphere.Radius, 24, SDPG_World); + DrawCoordinateSystem(PDI, Sphere.Location, Sphere.Rotation.Rotator(), Sphere.Radius, SDPG_World + 1); } } } } -void FKawaiiPhysicsEditMode::RenderSphericalLimits(FPrimitiveDrawInterface* PDI) const +void FKawaiiPhysicsEditMode::RenderCapsuleLimit(FPrimitiveDrawInterface* PDI) const { - if (!GraphNode->bEnableDebugDrawSphereLimit) - { - return; - } - - auto DrawSphereLimit = [&](const auto& Sphere, int32 Index, const FMaterialRenderProxy* MaterialProxy, bool bUseHit) + if (GraphNode->bEnableDebugDrawCapsuleLimit) { - if (Sphere.bEnable && Sphere.Radius > 0) + for (int32 i = 0; i < RuntimeNode->CapsuleLimits.Num(); i++) { - FVector Location = Sphere.Location; - FQuat Rotation = Sphere.Rotation; - if (RuntimeNode->SimulationSpace == EKawaiiPhysicsSimulationSpace::BaseBoneSpace) + auto& Capsule = RuntimeNode->CapsuleLimits[i]; + if (Capsule.Radius > 0 && Capsule.Length > 0) { - const FTransform& BaseBoneSpace2ComponentSpace = RuntimeNode->GetBaseBoneSpace2ComponentSpace(); - Location = BaseBoneSpace2ComponentSpace.TransformPosition(Location); - Rotation = BaseBoneSpace2ComponentSpace.TransformRotation(Rotation); - } + FVector XAxis = Capsule.Rotation.GetAxisX(); + FVector YAxis = Capsule.Rotation.GetAxisY(); + FVector ZAxis = Capsule.Rotation.GetAxisZ(); - PDI->SetHitProxy(bUseHit - ? new HKawaiiPhysicsHitProxy(ECollisionLimitType::Spherical, Index, Sphere.SourceType) - : nullptr); - DrawSphere(PDI, Location, FRotator::ZeroRotator, FVector(Sphere.Radius), 24, 6, MaterialProxy, - SDPG_World); - DrawWireSphere(PDI, Location, FLinearColor::Black, Sphere.Radius, 24, SDPG_World); - DrawCoordinateSystem(PDI, Location, Rotation.Rotator(), Sphere.Radius, SDPG_World + 1); - PDI->SetHitProxy(nullptr); - } - }; + PDI->SetHitProxy(new HKawaiiPhysicsHitProxy(ECollisionLimitType::Capsule, i)); + DrawCylinder(PDI, Capsule.Location, XAxis, YAxis, ZAxis, Capsule.Radius, 0.5f* Capsule.Length, 25, + GEngine->ConstraintLimitMaterialPrismatic->GetRenderProxy(), SDPG_World); + DrawSphere(PDI, Capsule.Location + ZAxis * Capsule.Length * 0.5f, Capsule.Rotation.Rotator(), FVector(Capsule.Radius), + 24, 6, GEngine->ConstraintLimitMaterialPrismatic->GetRenderProxy(), SDPG_World); + DrawSphere(PDI, Capsule.Location - ZAxis * Capsule.Length * 0.5f, Capsule.Rotation.Rotator(), FVector(Capsule.Radius), + 24, 6, GEngine->ConstraintLimitMaterialPrismatic->GetRenderProxy(), SDPG_World); - for (int32 i = 0; i < RuntimeNode->SphericalLimits.Num(); i++) - { - DrawSphereLimit(RuntimeNode->SphericalLimits[i], i, - GEngine->ConstraintLimitMaterialPrismatic->GetRenderProxy(), true); - } + DrawWireCapsule(PDI, Capsule.Location, XAxis, YAxis, ZAxis, + FLinearColor::Black, Capsule.Radius, 0.5f* Capsule.Length + Capsule.Radius, 25, SDPG_World); + + DrawCoordinateSystem(PDI, Capsule.Location, Capsule.Rotation.Rotator(), Capsule.Radius, SDPG_World + 1); - for (int32 i = 0; i < RuntimeNode->SphericalLimitsData.Num(); i++) - { - if (RuntimeNode->SphericalLimitsData[i].SourceType == ECollisionSourceType::DataAsset) - { - DrawSphereLimit(RuntimeNode->SphericalLimitsData[i], i, - GEngine->ConstraintLimitMaterialZ->GetRenderProxy(), true); - } - else - { - if (PhysicsAssetBodyMaterial->IsValidLowLevel()) - { - DrawSphereLimit(RuntimeNode->SphericalLimitsData[i], i, PhysicsAssetBodyMaterial->GetRenderProxy(), - false); } } - } -} -void FKawaiiPhysicsEditMode::RenderCapsuleLimit(FPrimitiveDrawInterface* PDI) const -{ - if (!GraphNode->bEnableDebugDrawCapsuleLimit) - { - return; - } - - auto DrawCapsule = [&](const auto& Capsule, int32 Index, const FMaterialRenderProxy* MaterialProxy, - bool bUseHit) - { - if (Capsule.bEnable && Capsule.Radius > 0 && Capsule.Length > 0) + for (int32 i = 0; i < RuntimeNode->CapsuleLimitsData.Num(); i++) { - FVector Location = Capsule.Location; - FQuat Rotation = Capsule.Rotation; - if (RuntimeNode->SimulationSpace == EKawaiiPhysicsSimulationSpace::BaseBoneSpace) + auto& Capsule = RuntimeNode->CapsuleLimitsData[i]; + if (Capsule.Radius > 0 && Capsule.Length > 0) { - const FTransform& BaseBoneSpace2ComponentSpace = RuntimeNode->GetBaseBoneSpace2ComponentSpace(); - Location = BaseBoneSpace2ComponentSpace.TransformPosition(Location); - Rotation = BaseBoneSpace2ComponentSpace.TransformRotation(Rotation); - } + FVector XAxis = Capsule.Rotation.GetAxisX(); + FVector YAxis = Capsule.Rotation.GetAxisY(); + FVector ZAxis = Capsule.Rotation.GetAxisZ(); - FVector XAxis = Rotation.GetAxisX(); - FVector YAxis = Rotation.GetAxisY(); - FVector ZAxis = Rotation.GetAxisZ(); - - PDI->SetHitProxy(bUseHit - ? new HKawaiiPhysicsHitProxy(ECollisionLimitType::Capsule, Index, Capsule.SourceType) - : nullptr); + PDI->SetHitProxy(new HKawaiiPhysicsHitProxy(ECollisionLimitType::Capsule, i, Capsule.SourceType)); + DrawCylinder(PDI, Capsule.Location, XAxis, YAxis, ZAxis, Capsule.Radius, 0.5f* Capsule.Length, 25, + GEngine->ConstraintLimitMaterialZ->GetRenderProxy(), SDPG_World); + DrawSphere(PDI, Capsule.Location + ZAxis * Capsule.Length * 0.5f, Capsule.Rotation.Rotator(), FVector(Capsule.Radius), + 24, 6, GEngine->ConstraintLimitMaterialZ->GetRenderProxy(), SDPG_World); + DrawSphere(PDI, Capsule.Location - ZAxis * Capsule.Length * 0.5f, Capsule.Rotation.Rotator(), FVector(Capsule.Radius), + 24, 6, GEngine->ConstraintLimitMaterialZ->GetRenderProxy(), SDPG_World); - DrawCylinder(PDI, Location, XAxis, YAxis, ZAxis, Capsule.Radius, 0.5f * Capsule.Length, 25, - MaterialProxy, SDPG_World); - DrawSphere(PDI, Location + ZAxis * Capsule.Length * 0.5f, Rotation.Rotator(), - FVector(Capsule.Radius), 24, 6, MaterialProxy, SDPG_World); - DrawSphere(PDI, Location - ZAxis * Capsule.Length * 0.5f, Rotation.Rotator(), - FVector(Capsule.Radius), 24, 6, MaterialProxy, SDPG_World); - DrawWireCapsule(PDI, Location, XAxis, YAxis, ZAxis, FLinearColor::Black, Capsule.Radius, - 0.5f * Capsule.Length + Capsule.Radius, 25, SDPG_World); - DrawCoordinateSystem(PDI, Location, Rotation.Rotator(), Capsule.Radius, SDPG_World + 1); - PDI->SetHitProxy(nullptr); - } - }; + DrawWireCapsule(PDI, Capsule.Location, XAxis, YAxis, ZAxis, + FLinearColor::Black, Capsule.Radius, 0.5f * Capsule.Length + Capsule.Radius, 25, SDPG_World); - for (int32 i = 0; i < RuntimeNode->CapsuleLimits.Num(); i++) - { - DrawCapsule(RuntimeNode->CapsuleLimits[i], i, GEngine->ConstraintLimitMaterialPrismatic->GetRenderProxy(), - true); - } + DrawCoordinateSystem(PDI, Capsule.Location, Capsule.Rotation.Rotator(), Capsule.Radius, SDPG_World + 1); - for (int32 i = 0; i < RuntimeNode->CapsuleLimitsData.Num(); i++) - { - if (RuntimeNode->CapsuleLimitsData[i].SourceType == ECollisionSourceType::DataAsset) - { - DrawCapsule(RuntimeNode->CapsuleLimitsData[i], i, - GEngine->ConstraintLimitMaterialZ->GetRenderProxy(), true); - } - else - { - if (PhysicsAssetBodyMaterial->IsValidLowLevel()) - { - DrawCapsule(RuntimeNode->CapsuleLimitsData[i], i, PhysicsAssetBodyMaterial->GetRenderProxy(), false); } } } @@ -427,54 +310,37 @@ void FKawaiiPhysicsEditMode::RenderCapsuleLimit(FPrimitiveDrawInterface* PDI) co void FKawaiiPhysicsEditMode::RenderBoxLimit(FPrimitiveDrawInterface* PDI) const { - if (!GraphNode->bEnableDebugDrawBoxLimit) + if (GraphNode->bEnableDebugDrawBoxLimit) { - return; - } - - auto DrawBoxLimit = [&](const auto& Box, int32 Index, const FMaterialRenderProxy* MaterialProxy, - bool bUseHit = true) - { - if (Box.bEnable && Box.Extent.Size() > 0) + for (int32 i = 0; i < RuntimeNode->BoxLimits.Num(); i++) { - FTransform BoxTransform(Box.Rotation, Box.Location); - if (RuntimeNode->SimulationSpace == EKawaiiPhysicsSimulationSpace::BaseBoneSpace) + auto& Box = RuntimeNode->BoxLimits[i]; + if (Box.Extent.Size() > 0) { - const FTransform& BaseBoneSpace2ComponentSpace = RuntimeNode->GetBaseBoneSpace2ComponentSpace(); - BoxTransform = BoxTransform * BaseBoneSpace2ComponentSpace; + FTransform BoxTransform(Box.Rotation, Box.Location); + + PDI->SetHitProxy(new HKawaiiPhysicsHitProxy(ECollisionLimitType::Box, i)); + DrawBox(PDI, BoxTransform.ToMatrixWithScale(), Box.Extent, + GEngine->ConstraintLimitMaterialPrismatic->GetRenderProxy(), SDPG_World); + DrawWireBox(PDI, BoxTransform.ToMatrixWithScale(), FBox(-Box.Extent, Box.Extent), FLinearColor::Black, + SDPG_World); + DrawCoordinateSystem(PDI, BoxTransform.GetLocation(), BoxTransform.Rotator(), Box.Extent.Size(), SDPG_World + 1); } - - PDI->SetHitProxy(bUseHit - ? new HKawaiiPhysicsHitProxy(ECollisionLimitType::Box, Index, Box.SourceType) - : nullptr); - - DrawBox(PDI, BoxTransform.ToMatrixWithScale(), Box.Extent, MaterialProxy, SDPG_World); - DrawWireBox(PDI, BoxTransform.ToMatrixWithScale(), FBox(-Box.Extent, Box.Extent), FLinearColor::Black, - SDPG_World); - DrawCoordinateSystem(PDI, BoxTransform.GetLocation(), BoxTransform.Rotator(), Box.Extent.Size(), - SDPG_World + 1); - PDI->SetHitProxy(nullptr); } - }; - for (int32 i = 0; i < RuntimeNode->BoxLimits.Num(); i++) - { - DrawBoxLimit(RuntimeNode->BoxLimits[i], i, - GEngine->ConstraintLimitMaterialPrismatic->GetRenderProxy()); - } - - for (int32 i = 0; i < RuntimeNode->BoxLimitsData.Num(); i++) - { - if (RuntimeNode->BoxLimitsData[i].SourceType == ECollisionSourceType::DataAsset) + for (int32 i = 0; i < RuntimeNode->BoxLimitsData.Num(); i++) { - DrawBoxLimit(RuntimeNode->BoxLimitsData[i], i, - GEngine->ConstraintLimitMaterialZ->GetRenderProxy()); - } - else - { - if (PhysicsAssetBodyMaterial->IsValidLowLevel()) + auto& Box = RuntimeNode->BoxLimitsData[i]; + if (Box.Extent.Size() > 0) { - DrawBoxLimit(RuntimeNode->BoxLimitsData[i], i, PhysicsAssetBodyMaterial->GetRenderProxy(), false); + FTransform BoxTransform(Box.Rotation, Box.Location); + + PDI->SetHitProxy(new HKawaiiPhysicsHitProxy(ECollisionLimitType::Box, i, Box.SourceType)); + DrawBox(PDI, BoxTransform.ToMatrixWithScale(), Box.Extent, + GEngine->ConstraintLimitMaterialZ->GetRenderProxy(), SDPG_World); + DrawWireBox(PDI, BoxTransform.ToMatrixWithScale(), FBox(-Box.Extent, Box.Extent), FLinearColor::Black, + SDPG_World); + DrawCoordinateSystem(PDI, BoxTransform.GetLocation(), BoxTransform.Rotator(), Box.Extent.Size(), SDPG_World + 1); } } } @@ -525,7 +391,7 @@ void FKawaiiPhysicsEditMode::RenderBoneConstraint(FPrimitiveDrawInterface* PDI) { for (const FModifyBoneConstraint& BoneConstraint : RuntimeNode->MergedBoneConstraints) { - if (BoneConstraint.IsBoneReferenceValid() && !RuntimeNode->ModifyBones.IsEmpty()) + if (BoneConstraint.IsBoneReferenceValid() && RuntimeNode->ModifyBones.Num() > 0) { FTransform BoneTransform1 = FTransform( RuntimeNode->ModifyBones[BoneConstraint.ModifyBoneIndex1].PrevRotation, @@ -540,7 +406,7 @@ void FKawaiiPhysicsEditMode::RenderBoneConstraint(FPrimitiveDrawInterface* PDI) BoneTransform1 = BoneTransform1 * BaseBoneSpace2ComponentSpace; BoneTransform2 = BoneTransform2 * BaseBoneSpace2ComponentSpace; } - + // 1 -> 2 FVector Dir = (BoneTransform2.GetLocation() - BoneTransform1.GetLocation()).GetSafeNormal(); FRotator LookAt = FRotationMatrix::MakeFromX(Dir).Rotator(); @@ -559,6 +425,70 @@ void FKawaiiPhysicsEditMode::RenderBoneConstraint(FPrimitiveDrawInterface* PDI) } } +void FKawaiiPhysicsEditMode::RenderQuadCollision(FPrimitiveDrawInterface* PDI) const +{ + // Only check the debug draw toggle - don't require the feature to be enabled + // This allows debugging visualization even when the feature is disabled + if (!GraphNode || !RuntimeNode || !GraphNode->bEnableDebugDrawQuadCollision) + { + return; + } + + // Early out if no quads to draw or no bones to reference + if (RuntimeNode->CachedQuads.Num() == 0 || RuntimeNode->ModifyBones.Num() == 0) + { + return; + } + + const FLinearColor QuadColor(0.0f, 1.0f, 1.0f); // Cyan for quad edges + + for (const FQuadCollisionLimit& Quad : RuntimeNode->CachedQuads) + { + if (!Quad.IsValid()) + { + continue; + } + + // Get quad corner locations + FVector Corners[4]; + bool bValidQuad = true; + for (int32 i = 0; i < 4; ++i) + { + if (Quad.BoneIndices[i] >= 0 && Quad.BoneIndices[i] < RuntimeNode->ModifyBones.Num()) + { + Corners[i] = RuntimeNode->ModifyBones[Quad.BoneIndices[i]].Location; + + // Transform if in BaseBoneSpace + if (RuntimeNode->SimulationSpace == EKawaiiPhysicsSimulationSpace::BaseBoneSpace) + { + const FTransform& BaseBoneSpace2ComponentSpace = RuntimeNode->GetBaseBoneSpace2ComponentSpace(); + Corners[i] = BaseBoneSpace2ComponentSpace.TransformPosition(Corners[i]); + } + } + else + { + bValidQuad = false; + break; + } + } + + if (!bValidQuad) + { + continue; + } + + // Draw all 4 edges of the quad + // Top edge (0 -> 1) + PDI->DrawLine(Corners[0], Corners[1], QuadColor, SDPG_Foreground); + // Bottom edge (2 -> 3) + PDI->DrawLine(Corners[2], Corners[3], QuadColor, SDPG_Foreground); + // Left edge (0 -> 2) + PDI->DrawLine(Corners[0], Corners[2], QuadColor, SDPG_Foreground); + // Right edge (1 -> 3) + PDI->DrawLine(Corners[1], Corners[3], QuadColor, SDPG_Foreground); + } +} + void FKawaiiPhysicsEditMode::RenderExternalForces(FPrimitiveDrawInterface* PDI) const { if (GraphNode->bEnableDebugDrawExternalForce) @@ -567,10 +497,9 @@ void FKawaiiPhysicsEditMode::RenderExternalForces(FPrimitiveDrawInterface* PDI) { for (auto& Force : RuntimeNode->ExternalForces) { - if (Force.IsValid()) + if (Force != nullptr) { - Force.GetMutablePtr()->AnimDrawDebugForEditMode( - Bone, *RuntimeNode, PDI); + Force->AnimDrawDebugForEditMode(Bone, *RuntimeNode, PDI); } } } @@ -584,7 +513,8 @@ FVector FKawaiiPhysicsEditMode::GetWidgetLocation(ECollisionLimitType CollisionT return GetAnimPreviewScene().GetPreviewMeshComponent()->GetComponentLocation(); } - if (const FCollisionLimitBase* Collision = GetSelectCollisionLimitRuntime()) + FCollisionLimitBase* Collision = GetSelectCollisionLimitRuntime(); + if (Collision) { return Collision->Location; } @@ -605,7 +535,9 @@ bool FKawaiiPhysicsEditMode::GetCustomDrawingCoordinateSystem(FMatrix& InMatrix, } FQuat Rotation = FQuat::Identity; - if (FCollisionLimitBase* Collision = GetSelectCollisionLimitRuntime()) + + FCollisionLimitBase* Collision = GetSelectCollisionLimitRuntime(); + if (Collision) { Rotation = Collision->Rotation; } @@ -616,7 +548,8 @@ bool FKawaiiPhysicsEditMode::GetCustomDrawingCoordinateSystem(FMatrix& InMatrix, UE_WIDGET::EWidgetMode FKawaiiPhysicsEditMode::GetWidgetMode() const { - if (GetSelectCollisionLimitRuntime()) + FCollisionLimitBase* Collision = GetSelectCollisionLimitRuntime(); + if (Collision) { CurWidgetMode = FindValidWidgetMode(CurWidgetMode); return CurWidgetMode; @@ -628,7 +561,7 @@ UE_WIDGET::EWidgetMode FKawaiiPhysicsEditMode::GetWidgetMode() const UE_WIDGET::EWidgetMode FKawaiiPhysicsEditMode::FindValidWidgetMode(UE_WIDGET::EWidgetMode InWidgetMode) const { if (InWidgetMode == UE_WIDGET::EWidgetMode::WM_None) - { + { return UE_WIDGET::EWidgetMode::WM_Translate; } @@ -646,10 +579,9 @@ UE_WIDGET::EWidgetMode FKawaiiPhysicsEditMode::FindValidWidgetMode(UE_WIDGET::EW return UE_WIDGET::EWidgetMode::WM_None; } -bool FKawaiiPhysicsEditMode::HandleClick(FEditorViewportClient* InViewportClient, HHitProxy* HitProxy, - const FViewportClick& Click) +bool FKawaiiPhysicsEditMode::HandleClick(FEditorViewportClient* InViewportClient, HHitProxy* HitProxy, const FViewportClick& Click) { - bool bResult = FAnimNodeEditMode::HandleClick(InViewportClient, HitProxy, Click); + bool bResult = FKawaiiPhysicsEditModeBase::HandleClick(InViewportClient, HitProxy, Click); if (HitProxy != nullptr && HitProxy->IsA(HKawaiiPhysicsHitProxy::StaticGetType())) { @@ -668,12 +600,11 @@ bool FKawaiiPhysicsEditMode::HandleClick(FEditorViewportClient* InViewportClient return bResult; } -bool FKawaiiPhysicsEditMode::InputKey(FEditorViewportClient* InViewportClient, FViewport* InViewport, FKey InKey, - EInputEvent InEvent) +bool FKawaiiPhysicsEditMode::InputKey(FEditorViewportClient* InViewportClient, FViewport* InViewport, FKey InKey, EInputEvent InEvent) { bool bHandled = false; - if ((InEvent == IE_Pressed) && !IsManipulatingWidget()) + if ((InEvent == IE_Pressed)) //&& !bManipulating) { if (InKey == EKeys::SpaceBar) { @@ -686,8 +617,7 @@ bool FKawaiiPhysicsEditMode::InputKey(FEditorViewportClient* InViewportClient, F const auto CoordSystem = GetModeManager()->GetCoordSystem(); GetModeManager()->SetCoordSystem(CoordSystem == COORD_Local ? COORD_World : COORD_Local); } - else if (InKey == EKeys::Delete && SelectCollisionSourceType != ECollisionSourceType::PhysicsAsset && - IsValidSelectCollision()) + else if (InKey == EKeys::Delete && SelectCollisionSourceType != ECollisionSourceType::PhysicsAsset && IsValidSelectCollision()) { switch (SelectCollisionType) { @@ -765,7 +695,7 @@ void FKawaiiPhysicsEditMode::OnExternalNodePropertyChange(FPropertyChangedEvent& if (InPropertyEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(FAnimNode_KawaiiPhysics, LimitsDataAsset)) { - if (RuntimeNode->LimitsDataAsset) + if (RuntimeNode->LimitsDataAsset != nullptr) { RuntimeNode->LimitsDataAsset->OnLimitsChanged.AddRaw( this, &FKawaiiPhysicsEditMode::OnLimitDataAssetPropertyChange); @@ -917,7 +847,7 @@ void FKawaiiPhysicsEditMode::DoTranslation(FVector& InTranslation) if (SelectCollisionSourceType == ECollisionSourceType::DataAsset) { UE_LOG(LogKawaiiPhysics, Warning, TEXT( "Please try saving the DataAsset (%s) and compile this ABP." ), - *RuntimeNode->LimitsDataAsset.GetName()); + *RuntimeNode->LimitsDataAsset->GetName()); } return; } @@ -957,7 +887,7 @@ void FKawaiiPhysicsEditMode::DoRotation(FRotator& InRotation) if (SelectCollisionSourceType == ECollisionSourceType::DataAsset) { UE_LOG(LogKawaiiPhysics, Warning, TEXT( "Please try saving the DataAsset (%s) and compile this ABP." ), - *RuntimeNode->LimitsDataAsset.GetName()); + *RuntimeNode->LimitsDataAsset->GetName()); } return; } @@ -997,7 +927,7 @@ void FKawaiiPhysicsEditMode::DoScale(FVector& InScale) if (SelectCollisionSourceType == ECollisionSourceType::DataAsset) { UE_LOG(LogKawaiiPhysics, Warning, TEXT( "Please try saving the DataAsset (%s) and compile this ABP." ), - *RuntimeNode->LimitsDataAsset.GetName()); + *RuntimeNode->LimitsDataAsset->GetName()); } return; } @@ -1078,12 +1008,12 @@ void FKawaiiPhysicsEditMode::DrawHUD(FEditorViewportClient* ViewportClient, FVie float DrawPositionY = Viewport->GetSizeXY().Y / Canvas->GetDPIScale() - (3 + FontHeight) - 100 / Canvas-> GetDPIScale(); - if (!FAnimWeight::IsRelevant(RuntimeNode->GetAlpha()) || !RuntimeNode->IsRecentlyEvaluated()) + if (!RuntimeNode->IsRecentlyEvaluated()) { DrawTextItem( LOCTEXT("", "This node does not evaluate recently."), Canvas, XOffset, DrawPositionY, FontHeight); - FAnimNodeEditMode::DrawHUD(ViewportClient, Viewport, View, Canvas); + FKawaiiPhysicsEditModeBase::DrawHUD(ViewportClient, Viewport, View, Canvas); return; } @@ -1136,7 +1066,7 @@ void FKawaiiPhysicsEditMode::DrawHUD(FEditorViewportClient* ViewportClient, FVie const FTransform& BaseBoneSpace2ComponentSpace = RuntimeNode->GetBaseBoneSpace2ComponentSpace(); BoneLocation = BaseBoneSpace2ComponentSpace.TransformPosition(BoneLocation); } - + // Refer to FAnimationViewportClient::ShowBoneNames const FVector BonePos = PreviewMeshComponent->GetComponentTransform().TransformPosition(BoneLocation); Draw3DTextItem(FText::AsNumber(Bone.LengthRateFromRoot), Canvas, View, @@ -1145,19 +1075,7 @@ void FKawaiiPhysicsEditMode::DrawHUD(FEditorViewportClient* ViewportClient, FVie } } - // SyncBone - if (GraphNode->bEnableDebugDrawSyncBone) - { - for (auto& SyncBone : RuntimeNode->SyncBones) - { - FString LenText = FString::Printf(TEXT("%.1f / %.1f"), SyncBone.ScaledDeltaDistance.Length(), - SyncBone.DeltaDistance.Length()); - Draw3DTextItem(FText::FromString(LenText), Canvas, View, - Viewport, PreviewMeshComponent->GetComponentTransform().TransformPosition(SyncBone.InitialPoseLocation)); - } - } - - FAnimNodeEditMode::DrawHUD(ViewportClient, Viewport, View, Canvas); + FKawaiiPhysicsEditModeBase::DrawHUD(ViewportClient, Viewport, View, Canvas); } void FKawaiiPhysicsEditMode::DrawTextItem(const FText& Text, FCanvas* Canvas, float X, float& Y, float FontHeight) diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Private/KawaiiPhysicsEditModeBase.cpp b/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Private/KawaiiPhysicsEditModeBase.cpp new file mode 100644 index 0000000..24552c9 --- /dev/null +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Private/KawaiiPhysicsEditModeBase.cpp @@ -0,0 +1,754 @@ +// In UE4, FAnimNodeEditMode didn't have a module API, so I had to copy-paste the contents of FAnimNodeEditMode to an external module +// I will remove this class in the future when I turn off UE4 support. +#if ENGINE_MAJOR_VERSION == 4 + +/** Copy from FAnimNodeEditMode to override **/ + +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "KawaiiPhysicsEditModeBase.h" +#include "EditorViewportClient.h" +#include "IPersonaPreviewScene.h" +#include "Animation/DebugSkelMeshComponent.h" +#include "BoneControllers/AnimNode_SkeletalControlBase.h" +#include "EngineUtils.h" +#include "AnimGraphNode_SkeletalControlBase.h" +#include "AssetEditorModeManager.h" + +#define LOCTEXT_NAMESPACE "AnimNodeEditMode" + +FKawaiiPhysicsEditModeBase::FKawaiiPhysicsEditModeBase() + : AnimNode(nullptr) + , RuntimeAnimNode(nullptr) + , bManipulating(false) + , bInTransaction(false) +{ + // Disable grid drawing for this mode as the viewport handles this + bDrawGrid = false; +} + +bool FKawaiiPhysicsEditModeBase::GetCameraTarget(FSphere& OutTarget) const +{ + FVector Location(GetWidgetLocation()); + OutTarget.Center = Location; + OutTarget.W = 50.0f; + + return true; +} + +IPersonaPreviewScene& FKawaiiPhysicsEditModeBase::GetAnimPreviewScene() const +{ + return *static_cast(static_cast(Owner)->GetPreviewScene()); +} + +void FKawaiiPhysicsEditModeBase::GetOnScreenDebugInfo(TArray& OutDebugInfo) const +{ + if (AnimNode != nullptr) + { + AnimNode->GetOnScreenDebugInfo(OutDebugInfo, RuntimeAnimNode, GetAnimPreviewScene().GetPreviewMeshComponent()); + } +} + +ECoordSystem FKawaiiPhysicsEditModeBase::GetWidgetCoordinateSystem() const +{ + UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); + if (SkelControl != nullptr) + { + PRAGMA_DISABLE_DEPRECATION_WARNINGS + return (ECoordSystem)SkelControl->GetWidgetCoordinateSystem(GetAnimPreviewScene().GetPreviewMeshComponent()); + PRAGMA_ENABLE_DEPRECATION_WARNINGS + } + + return ECoordSystem::COORD_None; +} + +UE_WIDGET::EWidgetMode FKawaiiPhysicsEditModeBase::GetWidgetMode() const +{ + UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); + if (SkelControl != nullptr) + { + PRAGMA_DISABLE_DEPRECATION_WARNINGS + return (UE_WIDGET::EWidgetMode)SkelControl->GetWidgetMode(GetAnimPreviewScene().GetPreviewMeshComponent()); + PRAGMA_ENABLE_DEPRECATION_WARNINGS + } + + return UE_WIDGET::EWidgetMode::WM_None; +} + +UE_WIDGET::EWidgetMode FKawaiiPhysicsEditModeBase::ChangeToNextWidgetMode(UE_WIDGET::EWidgetMode CurWidgetMode) +{ + UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); + if (SkelControl != nullptr) + { + PRAGMA_DISABLE_DEPRECATION_WARNINGS + return (UE_WIDGET::EWidgetMode)SkelControl->ChangeToNextWidgetMode(GetAnimPreviewScene().GetPreviewMeshComponent(), CurWidgetMode); + PRAGMA_ENABLE_DEPRECATION_WARNINGS + } + + return UE_WIDGET::EWidgetMode::WM_None; +} + +bool FKawaiiPhysicsEditModeBase::SetWidgetMode(UE_WIDGET::EWidgetMode InWidgetMode) +{ + UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); + if (SkelControl != nullptr) + { + PRAGMA_DISABLE_DEPRECATION_WARNINGS + return SkelControl->SetWidgetMode(GetAnimPreviewScene().GetPreviewMeshComponent(), InWidgetMode); + PRAGMA_ENABLE_DEPRECATION_WARNINGS + } + + return false; +} + +FName FKawaiiPhysicsEditModeBase::GetSelectedBone() const +{ + UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); + if (SkelControl != nullptr) + { + PRAGMA_DISABLE_DEPRECATION_WARNINGS + return SkelControl->FindSelectedBone(); + PRAGMA_ENABLE_DEPRECATION_WARNINGS + } + + return NAME_None; +} + +void FKawaiiPhysicsEditModeBase::EnterMode(UAnimGraphNode_Base* InEditorNode, FAnimNode_Base* InRuntimeNode) +{ + AnimNode = InEditorNode; + RuntimeAnimNode = InRuntimeNode; + + UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); + if (SkelControl != nullptr) + { + PRAGMA_DISABLE_DEPRECATION_WARNINGS + SkelControl->MoveSelectActorLocation(GetAnimPreviewScene().GetPreviewMeshComponent(), (FAnimNode_SkeletalControlBase*)RuntimeAnimNode); + SkelControl->CopyNodeDataTo(RuntimeAnimNode); + PRAGMA_ENABLE_DEPRECATION_WARNINGS + } + + GetModeManager()->SetCoordSystem(GetWidgetCoordinateSystem()); + GetModeManager()->SetWidgetMode(GetWidgetMode()); +} + +void FKawaiiPhysicsEditModeBase::ExitMode() +{ + UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); + if (SkelControl != nullptr) + { + PRAGMA_DISABLE_DEPRECATION_WARNINGS + SkelControl->DeselectActor(GetAnimPreviewScene().GetPreviewMeshComponent()); + PRAGMA_ENABLE_DEPRECATION_WARNINGS + } + + AnimNode = nullptr; + RuntimeAnimNode = nullptr; +} + +void FKawaiiPhysicsEditModeBase::Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) +{ + if (AnimNode != nullptr) + { + AnimNode->Draw(PDI, GetAnimPreviewScene().GetPreviewMeshComponent()); + } +} + +void FKawaiiPhysicsEditModeBase::DrawHUD(FEditorViewportClient* ViewportClient, FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) +{ + if (AnimNode != nullptr) + { + AnimNode->DrawCanvas(*Viewport, *const_cast(View), *Canvas, GetAnimPreviewScene().GetPreviewMeshComponent()); + } +} + +bool FKawaiiPhysicsEditModeBase::HandleClick(FEditorViewportClient* InViewportClient, HHitProxy* HitProxy, const FViewportClick& Click) +{ + if (HitProxy != nullptr && HitProxy->IsA(HActor::StaticGetType())) + { + HActor* ActorHitProxy = static_cast(HitProxy); + GetAnimPreviewScene().SetSelectedActor(ActorHitProxy->Actor); + + UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); + if (SkelControl != nullptr) + { + PRAGMA_DISABLE_DEPRECATION_WARNINGS + SkelControl->ProcessActorClick(ActorHitProxy); + PRAGMA_ENABLE_DEPRECATION_WARNINGS + } + return true; + } + + return false; +} + +FVector FKawaiiPhysicsEditModeBase::GetWidgetLocation() const +{ + UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); + if (SkelControl != nullptr) + { + PRAGMA_DISABLE_DEPRECATION_WARNINGS + return SkelControl->GetWidgetLocation(GetAnimPreviewScene().GetPreviewMeshComponent(), (FAnimNode_SkeletalControlBase*)RuntimeAnimNode); + PRAGMA_ENABLE_DEPRECATION_WARNINGS + } + + return FVector::ZeroVector; +} + +bool FKawaiiPhysicsEditModeBase::StartTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) +{ + if (!bInTransaction) + { + GEditor->BeginTransaction(LOCTEXT("EditSkelControlNodeTransaction", "Edit Skeletal Control Node")); + AnimNode->SetFlags(RF_Transactional); + AnimNode->Modify(); + bInTransaction = true; + } + + bManipulating = true; + + return true; +} + +bool FKawaiiPhysicsEditModeBase::EndTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) +{ + if (bManipulating) + { + bManipulating = false; + } + + if (bInTransaction) + { + GEditor->EndTransaction(); + bInTransaction = false; + } + + return true; +} + +bool FKawaiiPhysicsEditModeBase::InputKey(FEditorViewportClient* InViewportClient, FViewport* InViewport, FKey InKey, EInputEvent InEvent) +{ + bool bHandled = false; + + // Handle switching modes - only allowed when not already manipulating + if ((InEvent == IE_Pressed) && (InKey == EKeys::SpaceBar) && !bManipulating) + { + UE_WIDGET::EWidgetMode WidgetMode = (UE_WIDGET::EWidgetMode)ChangeToNextWidgetMode(GetModeManager()->GetWidgetMode()); + GetModeManager()->SetWidgetMode(WidgetMode); + if (WidgetMode == UE_WIDGET::EWidgetMode::WM_Scale) + { + GetModeManager()->SetCoordSystem(COORD_Local); + } + else + { + GetModeManager()->SetCoordSystem(COORD_World); + } + + bHandled = true; + InViewportClient->Invalidate(); + } + + return bHandled; +} + +bool FKawaiiPhysicsEditModeBase::InputDelta(FEditorViewportClient* InViewportClient, FViewport* InViewport, FVector& InDrag, FRotator& InRot, FVector& InScale) +{ + const EAxisList::Type CurrentAxis = InViewportClient->GetCurrentWidgetAxis(); + const UE_WIDGET::EWidgetMode WidgetMode = InViewportClient->GetWidgetMode(); + + bool bHandled = false; + + UDebugSkelMeshComponent* PreviewMeshComponent = GetAnimPreviewScene().GetPreviewMeshComponent(); + + if (bManipulating && CurrentAxis != EAxisList::None) + { + bHandled = true; + + const bool bDoRotation = WidgetMode == UE_WIDGET::EWidgetMode::WM_Rotate || WidgetMode == UE_WIDGET::EWidgetMode::WM_TranslateRotateZ; + const bool bDoTranslation = WidgetMode == UE_WIDGET::EWidgetMode::WM_Translate || WidgetMode == UE_WIDGET::EWidgetMode::WM_TranslateRotateZ; + const bool bDoScale = WidgetMode == UE_WIDGET::EWidgetMode::WM_Scale; + + if (bDoRotation) + { + DoRotation(InRot); + } + + if (bDoTranslation) + { + DoTranslation(InDrag); + } + + if (bDoScale) + { + DoScale(InScale); + } + + InViewport->Invalidate(); + } + + return bHandled; +} + +bool FKawaiiPhysicsEditModeBase::GetCustomDrawingCoordinateSystem(FMatrix& InMatrix, void* InData) +{ + UDebugSkelMeshComponent* PreviewMeshComponent = GetAnimPreviewScene().GetPreviewMeshComponent(); + FName BoneName = GetSelectedBone(); + int32 BoneIndex = PreviewMeshComponent->GetBoneIndex(BoneName); + if (BoneIndex != INDEX_NONE) + { + FTransform BoneMatrix = PreviewMeshComponent->GetBoneTransform(BoneIndex); + InMatrix = BoneMatrix.ToMatrixNoScale().RemoveTranslation(); + return true; + } + + return false; +} + +bool FKawaiiPhysicsEditModeBase::GetCustomInputCoordinateSystem(FMatrix& InMatrix, void* InData) +{ + return GetCustomDrawingCoordinateSystem(InMatrix, InData); +} + +bool FKawaiiPhysicsEditModeBase::ShouldDrawWidget() const +{ + return true; +} + +void FKawaiiPhysicsEditModeBase::DoTranslation(FVector& InTranslation) +{ + UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); + if (SkelControl != nullptr) + { + PRAGMA_DISABLE_DEPRECATION_WARNINGS + SkelControl->DoTranslation(GetAnimPreviewScene().GetPreviewMeshComponent(), InTranslation, (FAnimNode_SkeletalControlBase*)RuntimeAnimNode); + PRAGMA_ENABLE_DEPRECATION_WARNINGS + } +} + +void FKawaiiPhysicsEditModeBase::DoRotation(FRotator& InRotation) +{ + UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); + if (SkelControl != nullptr) + { + PRAGMA_DISABLE_DEPRECATION_WARNINGS + SkelControl->DoRotation(GetAnimPreviewScene().GetPreviewMeshComponent(), InRotation, (FAnimNode_SkeletalControlBase*)RuntimeAnimNode); + PRAGMA_ENABLE_DEPRECATION_WARNINGS + } +} + +void FKawaiiPhysicsEditModeBase::DoScale(FVector& InScale) +{ + UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); + if (SkelControl != nullptr) + { + PRAGMA_DISABLE_DEPRECATION_WARNINGS + SkelControl->DoScale(GetAnimPreviewScene().GetPreviewMeshComponent(), InScale, (FAnimNode_SkeletalControlBase*)RuntimeAnimNode); + PRAGMA_ENABLE_DEPRECATION_WARNINGS + } +} + +void FKawaiiPhysicsEditModeBase::Tick(FEditorViewportClient* ViewportClient, float DeltaTime) +{ + IAnimNodeEditMode::Tick(ViewportClient, DeltaTime); + + // Keep actor location in sync with animation + UAnimGraphNode_SkeletalControlBase* SkelControl = Cast(AnimNode); + if (SkelControl != nullptr) + { + PRAGMA_DISABLE_DEPRECATION_WARNINGS + SkelControl->MoveSelectActorLocation(GetAnimPreviewScene().GetPreviewMeshComponent(), (FAnimNode_SkeletalControlBase*)RuntimeAnimNode); + PRAGMA_ENABLE_DEPRECATION_WARNINGS + } +} + +void FKawaiiPhysicsEditModeBase::ConvertToComponentSpaceTransform(const USkeletalMeshComponent* SkelComp, const FTransform & InTransform, FTransform & OutCSTransform, int32 BoneIndex, EBoneControlSpace Space) +{ + USkeleton* Skeleton = SkelComp->SkeletalMesh->GetSkeleton(); + + switch (Space) + { + case BCS_WorldSpace: + { + OutCSTransform = InTransform; + OutCSTransform.SetToRelativeTransform(SkelComp->GetComponentTransform()); + } + break; + + case BCS_ComponentSpace: + { + // Component Space, no change. + OutCSTransform = InTransform; + } + break; + + case BCS_ParentBoneSpace: + if (BoneIndex != INDEX_NONE) + { + const int32 ParentIndex = Skeleton->GetReferenceSkeleton().GetParentIndex(BoneIndex); + if (ParentIndex != INDEX_NONE) + { + const int32 MeshParentIndex = Skeleton->GetMeshBoneIndexFromSkeletonBoneIndex(SkelComp->SkeletalMesh, ParentIndex); + if (MeshParentIndex != INDEX_NONE) + { + const FTransform ParentTM = SkelComp->GetBoneTransform(MeshParentIndex); + OutCSTransform = InTransform * ParentTM; + } + else + { + OutCSTransform = InTransform; + } + } + } + break; + + case BCS_BoneSpace: + if (BoneIndex != INDEX_NONE) + { + const int32 MeshBoneIndex = Skeleton->GetMeshBoneIndexFromSkeletonBoneIndex(SkelComp->SkeletalMesh, BoneIndex); + if (MeshBoneIndex != INDEX_NONE) + { + const FTransform BoneTM = SkelComp->GetBoneTransform(MeshBoneIndex); + OutCSTransform = InTransform * BoneTM; + } + else + { + OutCSTransform = InTransform; + } + } + break; + + default: + if (SkelComp->SkeletalMesh) + { + UE_LOG(LogAnimation, Warning, TEXT("ConvertToComponentSpaceTransform: Unknown BoneSpace %d for Mesh: %s"), (uint8)Space, *SkelComp->SkeletalMesh->GetFName().ToString()); + } + else + { + UE_LOG(LogAnimation, Warning, TEXT("ConvertToComponentSpaceTransform: Unknown BoneSpace %d for Skeleton: %s"), (uint8)Space, *Skeleton->GetFName().ToString()); + } + break; + } +} + + +void FKawaiiPhysicsEditModeBase::ConvertToBoneSpaceTransform(const USkeletalMeshComponent* SkelComp, const FTransform & InCSTransform, FTransform & OutBSTransform, int32 BoneIndex, EBoneControlSpace Space) +{ + USkeleton* Skeleton = SkelComp->SkeletalMesh->GetSkeleton(); + + switch (Space) + { + case BCS_WorldSpace: + { + OutBSTransform = InCSTransform * SkelComp->GetComponentTransform(); + break; + } + + case BCS_ComponentSpace: + { + // Component Space, no change. + OutBSTransform = InCSTransform; + break; + } + + case BCS_ParentBoneSpace: + { + if (BoneIndex != INDEX_NONE) + { + const int32 ParentIndex = Skeleton->GetReferenceSkeleton().GetParentIndex(BoneIndex); + if (ParentIndex != INDEX_NONE) + { + const int32 MeshParentIndex = Skeleton->GetMeshBoneIndexFromSkeletonBoneIndex(SkelComp->SkeletalMesh, ParentIndex); + if (MeshParentIndex != INDEX_NONE) + { + const FTransform ParentTM = SkelComp->GetBoneTransform(MeshParentIndex); + OutBSTransform = InCSTransform.GetRelativeTransform(ParentTM); + } + else + { + OutBSTransform = InCSTransform; + } + } + } + break; + } + + case BCS_BoneSpace: + { + if (BoneIndex != INDEX_NONE) + { + const int32 MeshBoneIndex = Skeleton->GetMeshBoneIndexFromSkeletonBoneIndex(SkelComp->SkeletalMesh, BoneIndex); + if (MeshBoneIndex != INDEX_NONE) + { + FTransform BoneCSTransform = SkelComp->GetBoneTransform(MeshBoneIndex); + OutBSTransform = InCSTransform.GetRelativeTransform(BoneCSTransform); + } + else + { + OutBSTransform = InCSTransform; + } + } + break; + } + + default: + { + UE_LOG(LogAnimation, Warning, TEXT("ConvertToBoneSpaceTransform: Unknown BoneSpace %d for Mesh: %s"), (int32)Space, *GetNameSafe(SkelComp->SkeletalMesh)); + break; + } + } +} + +FVector FKawaiiPhysicsEditModeBase::ConvertCSVectorToBoneSpace(const USkeletalMeshComponent* SkelComp, FVector& InCSVector, FCSPose& MeshBases, const FBoneSocketTarget& InTarget, const EBoneControlSpace Space) +{ + FVector OutVector = FVector::ZeroVector; + + if (MeshBases.GetPose().IsValid()) + { + const FCompactPoseBoneIndex BoneIndex = InTarget.GetCompactPoseBoneIndex(); + + switch (Space) + { + // World Space, no change in preview window + case BCS_WorldSpace: + case BCS_ComponentSpace: + // Component Space, no change. + OutVector = InCSVector; + break; + + case BCS_ParentBoneSpace: + { + if (BoneIndex != INDEX_NONE) + { + const FCompactPoseBoneIndex ParentIndex = MeshBases.GetPose().GetParentBoneIndex(BoneIndex); + if (ParentIndex != INDEX_NONE) + { + const FTransform& ParentTM = MeshBases.GetComponentSpaceTransform(ParentIndex); + OutVector = ParentTM.InverseTransformVector(InCSVector); + } + } + } + break; + + case BCS_BoneSpace: + { + FTransform BoneTransform = InTarget.GetTargetTransform(FVector::ZeroVector, MeshBases, SkelComp->GetComponentToWorld()); + OutVector = BoneTransform.InverseTransformVector(InCSVector); + } + break; + } + } + + return OutVector; +} + +FVector FKawaiiPhysicsEditModeBase::ConvertCSVectorToBoneSpace(const USkeletalMeshComponent* SkelComp, FVector& InCSVector, FCSPose& MeshBases, const FName& BoneName, const EBoneControlSpace Space) +{ + FVector OutVector = FVector::ZeroVector; + + if (MeshBases.GetPose().IsValid()) + { + const FMeshPoseBoneIndex MeshBoneIndex(SkelComp->GetBoneIndex(BoneName)); + const FCompactPoseBoneIndex BoneIndex = MeshBases.GetPose().GetBoneContainer().MakeCompactPoseIndex(MeshBoneIndex); + + switch (Space) + { + // World Space, no change in preview window + case BCS_WorldSpace: + case BCS_ComponentSpace: + // Component Space, no change. + OutVector = InCSVector; + break; + + case BCS_ParentBoneSpace: + { + const FCompactPoseBoneIndex ParentIndex = MeshBases.GetPose().GetParentBoneIndex(BoneIndex); + if (ParentIndex != INDEX_NONE) + { + const FTransform& ParentTM = MeshBases.GetComponentSpaceTransform(ParentIndex); + OutVector = ParentTM.InverseTransformVector(InCSVector); + } + } + break; + + case BCS_BoneSpace: + { + if (BoneIndex != INDEX_NONE) + { + const FTransform& BoneTM = MeshBases.GetComponentSpaceTransform(BoneIndex); + OutVector = BoneTM.InverseTransformVector(InCSVector); + } + } + break; + } + } + + return OutVector; +} + +FQuat FKawaiiPhysicsEditModeBase::ConvertCSRotationToBoneSpace(const USkeletalMeshComponent* SkelComp, FRotator& InCSRotator, FCSPose& MeshBases, const FName& BoneName, const EBoneControlSpace Space) +{ + FQuat OutQuat = FQuat::Identity; + + if (MeshBases.GetPose().IsValid()) + { + const FMeshPoseBoneIndex MeshBoneIndex(SkelComp->GetBoneIndex(BoneName)); + const FCompactPoseBoneIndex BoneIndex = MeshBases.GetPose().GetBoneContainer().MakeCompactPoseIndex(MeshBoneIndex); + + FVector RotAxis; + float RotAngle; + InCSRotator.Quaternion().ToAxisAndAngle(RotAxis, RotAngle); + + switch (Space) + { + // World Space, no change in preview window + case BCS_WorldSpace: + case BCS_ComponentSpace: + // Component Space, no change. + OutQuat = InCSRotator.Quaternion(); + break; + + case BCS_ParentBoneSpace: + { + const FCompactPoseBoneIndex ParentIndex = MeshBases.GetPose().GetParentBoneIndex(BoneIndex); + if (ParentIndex != INDEX_NONE) + { + const FTransform& ParentTM = MeshBases.GetComponentSpaceTransform(ParentIndex); + FTransform InverseParentTM = ParentTM.Inverse(); + //Calculate the new delta rotation + FVector4 BoneSpaceAxis = InverseParentTM.TransformVector(RotAxis); + FQuat DeltaQuat(BoneSpaceAxis, RotAngle); + DeltaQuat.Normalize(); + OutQuat = DeltaQuat; + } + } + break; + + case BCS_BoneSpace: + { + const FTransform& BoneTM = BoneIndex >= 0 ? MeshBases.GetComponentSpaceTransform(BoneIndex) : FTransform::Identity; + FTransform InverseBoneTM = BoneTM.Inverse(); + FVector4 BoneSpaceAxis = InverseBoneTM.TransformVector(RotAxis); + //Calculate the new delta rotation + FQuat DeltaQuat(BoneSpaceAxis, RotAngle); + DeltaQuat.Normalize(); + OutQuat = DeltaQuat; + } + break; + } + } + + return OutQuat; +} + +FVector FKawaiiPhysicsEditModeBase::ConvertWidgetLocation(const USkeletalMeshComponent* InSkelComp, FCSPose& InMeshBases, const FBoneSocketTarget& Target, const FVector& InLocation, const EBoneControlSpace Space) +{ + FVector WidgetLoc = FVector::ZeroVector; + + switch (Space) + { + // GetComponentTransform() must be Identity in preview window so same as ComponentSpace + case BCS_WorldSpace: + case BCS_ComponentSpace: + { + // Component Space, no change. + WidgetLoc = InLocation; + } + break; + + case BCS_ParentBoneSpace: + { + const FCompactPoseBoneIndex CompactBoneIndex = Target.GetCompactPoseBoneIndex(); + + if (CompactBoneIndex != INDEX_NONE) + { + if (ensure(InMeshBases.GetPose().IsValidIndex(CompactBoneIndex))) + { + const FCompactPoseBoneIndex CompactParentIndex = InMeshBases.GetPose().GetParentBoneIndex(CompactBoneIndex); + if (CompactParentIndex != INDEX_NONE) + { + const FTransform& ParentTM = InMeshBases.GetComponentSpaceTransform(CompactParentIndex); + WidgetLoc = ParentTM.TransformPosition(InLocation); + } + } + else + { + UE_LOG(LogAnimation, Warning, TEXT("Using socket(%d), Socket name(%s), Bone name(%s)"), + Target.bUseSocket, *Target.SocketReference.SocketName.ToString(), *Target.BoneReference.BoneName.ToString()); + } + } + } + break; + + case BCS_BoneSpace: + { + FTransform BoneTM = Target.GetTargetTransform(FVector::ZeroVector, InMeshBases, InSkelComp->GetComponentToWorld()); + WidgetLoc = BoneTM.TransformPosition(InLocation); + } + break; + } + + return WidgetLoc; +} +FVector FKawaiiPhysicsEditModeBase::ConvertWidgetLocation(const USkeletalMeshComponent* SkelComp, FCSPose& MeshBases, const FName& BoneName, const FVector& Location, const EBoneControlSpace Space) +{ + FVector WidgetLoc = FVector::ZeroVector; + + auto GetCompactBoneIndex = [](const USkeletalMeshComponent* InSkelComp, FCSPose& InMeshBases, const FName& InBoneName) + { + if (InMeshBases.GetPose().IsValid()) + { + USkeleton* Skeleton = InSkelComp->SkeletalMesh->GetSkeleton(); + const int32 MeshBoneIndex = InSkelComp->GetBoneIndex(InBoneName); + if (MeshBoneIndex != INDEX_NONE) + { + return InMeshBases.GetPose().GetBoneContainer().MakeCompactPoseIndex(FMeshPoseBoneIndex(MeshBoneIndex)); + } + } + + return FCompactPoseBoneIndex(INDEX_NONE); + }; + + switch (Space) + { + // GetComponentTransform() must be Identity in preview window so same as ComponentSpace + case BCS_WorldSpace: + case BCS_ComponentSpace: + { + // Component Space, no change. + WidgetLoc = Location; + } + break; + + case BCS_ParentBoneSpace: + { + const FCompactPoseBoneIndex CompactBoneIndex = GetCompactBoneIndex(SkelComp, MeshBases, BoneName); + if (CompactBoneIndex != INDEX_NONE) + { + const FCompactPoseBoneIndex CompactParentIndex = MeshBases.GetPose().GetParentBoneIndex(CompactBoneIndex); + if (CompactParentIndex != INDEX_NONE) + { + const FTransform& ParentTM = MeshBases.GetComponentSpaceTransform(CompactParentIndex); + WidgetLoc = ParentTM.TransformPosition(Location); + } + } + } + break; + + case BCS_BoneSpace: + { + const FCompactPoseBoneIndex CompactBoneIndex = GetCompactBoneIndex(SkelComp, MeshBases, BoneName); + if (CompactBoneIndex != INDEX_NONE) + { + const FTransform& BoneTM = MeshBases.GetComponentSpaceTransform(CompactBoneIndex); + WidgetLoc = BoneTM.TransformPosition(Location); + } + } + break; + } + + return WidgetLoc; +} + +#undef LOCTEXT_NAMESPACE + +#endif \ No newline at end of file diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Public/AnimGraphNode_KawaiiPhysics.h b/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Public/AnimGraphNode_KawaiiPhysics.h index 2a1e55f..eca3ad7 100644 --- a/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Public/AnimGraphNode_KawaiiPhysics.h +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Public/AnimGraphNode_KawaiiPhysics.h @@ -1,4 +1,4 @@ -// Copyright 2019-2026 pafuhana1213. All Rights Reserved. +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License #pragma once @@ -76,10 +76,6 @@ class UAnimGraphNode_KawaiiPhysics : public UAnimGraphNode_SkeletalControlBase UPROPERTY() bool bEnableDebugDrawLimitAngle = true; - /** Enables or disables debug drawing for sync bones. */ - UPROPERTY() - bool bEnableDebugDrawSyncBone = true; - /** Enables or disables debug drawing for spherical limits. */ UPROPERTY() bool bEnableDebugDrawSphereLimit = true; @@ -100,6 +96,10 @@ class UAnimGraphNode_KawaiiPhysics : public UAnimGraphNode_SkeletalControlBase UPROPERTY() bool bEnableDebugDrawBoneConstraint = true; + /** Enables or disables debug drawing for quad collision. */ + UPROPERTY() + bool bEnableDebugDrawQuadCollision = true; + /** Enables or disables debug drawing for external forces. */ UPROPERTY() bool bEnableDebugDrawExternalForce = true; diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Public/KawaiiPhysicsEd.h b/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Public/KawaiiPhysicsEd.h index b20180a..64ab55f 100644 --- a/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Public/KawaiiPhysicsEd.h +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Public/KawaiiPhysicsEd.h @@ -1,8 +1,8 @@ -// Copyright 2019-2026 pafuhana1213. All Rights Reserved. +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License #pragma once -#include "Modules/ModuleInterface.h" +#include "CoreMinimal.h" class FKawaiiPhysicsEdModule : public IModuleInterface { diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Public/KawaiiPhysicsEditMode.h b/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Public/KawaiiPhysicsEditMode.h index 7a77f3f..2663ff6 100644 --- a/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Public/KawaiiPhysicsEditMode.h +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Public/KawaiiPhysicsEditMode.h @@ -1,23 +1,22 @@ -// Copyright 2019-2026 pafuhana1213. All Rights Reserved. +// KawaiiPhysics : Copyright (c) 2019-2024 pafuhana1213, MIT License #pragma once -#include "AnimNodeEditMode.h" +#include "KawaiiPhysicsEditModeBase.h" #include "AnimGraphNode_KawaiiPhysics.h" #include "AnimNode_KawaiiPhysics.h" -#define UE_WIDGET UE::Widget - class FEditorViewportClient; class FPrimitiveDrawInterface; class USkeletalMeshComponent; struct FViewportClick; -class FKawaiiPhysicsEditMode : public FAnimNodeEditMode +class FKawaiiPhysicsEditMode : public FKawaiiPhysicsEditModeBase { public: - FKawaiiPhysicsEditMode(); + FKawaiiPhysicsEditMode(); + /** IAnimNodeEditMode interface */ virtual void EnterMode(class UAnimGraphNode_Base* InEditorNode, struct FAnimNode_Base* InRuntimeNode) override; virtual void ExitMode() override; @@ -50,7 +49,6 @@ class FKawaiiPhysicsEditMode : public FAnimNodeEditMode private: void RenderModifyBones(FPrimitiveDrawInterface* PDI) const; void RenderLimitAngle(FPrimitiveDrawInterface* PDI) const; - void RenderSyncBone(FPrimitiveDrawInterface* PDI) const; /** Render each collisions */ void RenderSphericalLimits(FPrimitiveDrawInterface* PDI) const; @@ -59,6 +57,7 @@ class FKawaiiPhysicsEditMode : public FAnimNodeEditMode void RenderPlanerLimit(FPrimitiveDrawInterface* PDI); void RenderBoneConstraint(FPrimitiveDrawInterface* PDI) const; + void RenderQuadCollision(FPrimitiveDrawInterface* PDI) const; void RenderExternalForces(FPrimitiveDrawInterface* PDI) const; /** Helper function for GetWidgetLocation() and joint rendering */ @@ -88,9 +87,9 @@ class FKawaiiPhysicsEditMode : public FAnimNodeEditMode int32 SelectCollisionIndex = -1; ECollisionSourceType SelectCollisionSourceType = ECollisionSourceType::AnimNode; - // storing current widget mode + // storing current widget mode mutable UE_WIDGET::EWidgetMode CurWidgetMode; // physics asset body material - TObjectPtr PhysicsAssetBodyMaterial; + UMaterialInstanceDynamic* PhysicsAssetBodyMaterial; }; diff --git a/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Public/KawaiiPhysicsEditModeBase.h b/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Public/KawaiiPhysicsEditModeBase.h new file mode 100644 index 0000000..e65449b --- /dev/null +++ b/Plugins/KawaiiPhysics/Source/KawaiiPhysicsEd/Public/KawaiiPhysicsEditModeBase.h @@ -0,0 +1,93 @@ + +// In UE4, FAnimNodeEditMode didn't have a module API, so I had to copy-paste the contents of FAnimNodeEditMode to an external module +// I will remove this class in the future when I turn off UE4 support. +#if ENGINE_MAJOR_VERSION == 4 + +/** Copy from FAnimNodeEditMode to override **/ + +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "InputCoreTypes.h" +#include "UnrealWidget.h" +#include "IAnimNodeEditMode.h" +#include "BonePose.h" +#include "Runtime/Launch/Resources/Version.h" + +#define UE_WIDGET FWidget + +class FCanvas; +class FEditorViewportClient; +class FPrimitiveDrawInterface; +class USkeletalMeshComponent; +struct FViewportClick; +struct FBoneSocketTarget; + +/** Base implementation for anim node edit modes */ +class FKawaiiPhysicsEditModeBase : public IAnimNodeEditMode +{ +public: + FKawaiiPhysicsEditModeBase(); + + /** IAnimNodeEditMode interface */ + virtual ECoordSystem GetWidgetCoordinateSystem() const override; + + virtual UE_WIDGET::EWidgetMode GetWidgetMode() const override; + virtual UE_WIDGET::EWidgetMode ChangeToNextWidgetMode(UE_WIDGET::EWidgetMode CurWidgetMode) override; + + virtual bool SetWidgetMode(UE_WIDGET::EWidgetMode InWidgetMode) override; + virtual FName GetSelectedBone() const override; + virtual void DoTranslation(FVector& InTranslation) override; + virtual void DoRotation(FRotator& InRotation) override; + virtual void DoScale(FVector& InScale) override; + virtual void EnterMode(class UAnimGraphNode_Base* InEditorNode, struct FAnimNode_Base* InRuntimeNode) override; + virtual void ExitMode() override; + + /** IPersonaEditMode interface */ + virtual bool GetCameraTarget(FSphere& OutTarget) const override; + virtual class IPersonaPreviewScene& GetAnimPreviewScene() const override; + virtual void GetOnScreenDebugInfo(TArray& OutDebugInfo) const override; + + /** FEdMode interface */ + virtual void Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) override; + virtual void DrawHUD(FEditorViewportClient* ViewportClient, FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) override; + virtual bool HandleClick(FEditorViewportClient* InViewportClient, HHitProxy* HitProxy, const FViewportClick& Click) override; + virtual FVector GetWidgetLocation() const override; + virtual bool StartTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) override; + virtual bool EndTracking(FEditorViewportClient* InViewportClient, FViewport* InViewport) override; + virtual bool InputKey(FEditorViewportClient* InViewportClient, FViewport* InViewport, FKey InKey, EInputEvent InEvent) override; + virtual bool InputDelta(FEditorViewportClient* InViewportClient, FViewport* InViewport, FVector& InDrag, FRotator& InRot, FVector& InScale) override; + virtual bool GetCustomDrawingCoordinateSystem(FMatrix& InMatrix, void* InData) override; + virtual bool GetCustomInputCoordinateSystem(FMatrix& InMatrix, void* InData) override; + virtual bool ShouldDrawWidget() const override; + virtual void Tick(FEditorViewportClient* ViewportClient, float DeltaTime) override; + +protected: + // local conversion functions for drawing + static void ConvertToComponentSpaceTransform(const USkeletalMeshComponent* SkelComp, const FTransform & InTransform, FTransform & OutCSTransform, int32 BoneIndex, EBoneControlSpace Space); + static void ConvertToBoneSpaceTransform(const USkeletalMeshComponent* SkelComp, const FTransform & InCSTransform, FTransform & OutBSTransform, int32 BoneIndex, EBoneControlSpace Space); + // convert drag vector in component space to bone space + static FVector ConvertCSVectorToBoneSpace(const USkeletalMeshComponent* SkelComp, FVector& InCSVector, FCSPose& MeshBases, const FName& BoneName, const EBoneControlSpace Space); + static FVector ConvertCSVectorToBoneSpace(const USkeletalMeshComponent* SkelComp, FVector& InCSVector, FCSPose& MeshBases, const FBoneSocketTarget& InTarget, const EBoneControlSpace Space); + // convert rotator in component space to bone space + static FQuat ConvertCSRotationToBoneSpace(const USkeletalMeshComponent* SkelComp, FRotator& InCSRotator, FCSPose& MeshBases, const FName& BoneName, const EBoneControlSpace Space); + // convert widget location according to bone control space + static FVector ConvertWidgetLocation(const USkeletalMeshComponent* InSkelComp, FCSPose& InMeshBases, const FName& BoneName, const FVector& InLocation, const EBoneControlSpace Space); + static FVector ConvertWidgetLocation(const USkeletalMeshComponent* InSkelComp, FCSPose& InMeshBases, const FBoneSocketTarget& Target, const FVector& InLocation, const EBoneControlSpace Space); + +protected: + /** The node we are operating on */ + class UAnimGraphNode_Base* AnimNode; + + /** The runtime node in the preview scene */ + struct FAnimNode_Base* RuntimeAnimNode; + + bool bManipulating; + +private: + + bool bInTransaction; +}; +#endif \ No newline at end of file