You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
+ Support for editing most geometric parameters (Joint Offsets, Box Extents, Collision Planes etc) with a TRS Widget. + Allow parameters to be edited for each individual physics body in a chain. + DEPRECATED some physics body properties, these are now held in an array, one instance per physics body. + Changed definition of LocalJointOffset - was joint position relative to physics body, now physics body position relative to joint + Fix for apparent bug in position constraints between chain bodies. + Added a new AnimDynamics Edit Mode to support new widget features. + Changes to details panel layout. #rb [at]Benn.Gallagher, [at]Thomas.Sarkanen [FYI] [at]charles.anderson #preflight 61eec96daa3f15faa57b841f #ROBOMERGE-OWNER: nick.brett #ROBOMERGE-AUTHOR: nick.brett #ROBOMERGE-SOURCE: CL 18721085 via CL 18721089 via CL 18721093 via CL 18724643 via CL 18724991 #ROBOMERGE-BOT: UE5 (Release-Engine-Test -> Main) (v903-18687472) [CL 18725005 by nick brett in ue5-main branch]
1338 lines
48 KiB
C++
1338 lines
48 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "BoneControllers/AnimNode_AnimDynamics.h"
|
|
#include "GameFramework/WorldSettings.h"
|
|
#include "Animation/AnimInstanceProxy.h"
|
|
#include "PhysicsEngine/PhysicsSettings.h"
|
|
#include "CommonAnimationLibrary.h"
|
|
|
|
DEFINE_STAT(STAT_AnimDynamicsOverall);
|
|
DEFINE_STAT(STAT_AnimDynamicsWindData);
|
|
DEFINE_STAT(STAT_AnimDynamicsBoneEval);
|
|
DEFINE_STAT(STAT_AnimDynamicsSubSteps);
|
|
|
|
CSV_DECLARE_CATEGORY_MODULE_EXTERN(ENGINE_API, Animation);
|
|
|
|
TAutoConsoleVariable<int32> CVarRestrictLod(TEXT("p.AnimDynamicsRestrictLOD"), -1, TEXT("Forces anim dynamics to be enabled for only a specified LOD, -1 to enable on all LODs."));
|
|
TAutoConsoleVariable<int32> CVarLODThreshold(TEXT("p.AnimDynamicsLODThreshold"), -1, TEXT("Max LOD that anim dynamics is allowed to run on. Provides a global threshold that overrides per-node the LODThreshold property. -1 means no override."), ECVF_Scalability);
|
|
TAutoConsoleVariable<int32> CVarEnableDynamics(TEXT("p.AnimDynamics"), 1, TEXT("Enables/Disables anim dynamics node updates."), ECVF_Scalability);
|
|
TAutoConsoleVariable<int32> CVarEnableAdaptiveSubstep(TEXT("p.AnimDynamicsAdaptiveSubstep"), 0, TEXT("Enables/disables adaptive substepping. Adaptive substepping will substep the simulation when it is necessary and maintain a debt buffer for time, always trying to utilise as much time as possible."));
|
|
TAutoConsoleVariable<int32> CVarAdaptiveSubstepNumDebtFrames(TEXT("p.AnimDynamicsNumDebtFrames"), 5, TEXT("Number of frames to maintain as time debt when using adaptive substepping, this should be at least 1 or the time debt will never be cleared."));
|
|
TAutoConsoleVariable<int32> CVarEnableWind(TEXT("p.AnimDynamicsWind"), 1, TEXT("Enables/Disables anim dynamics wind forces globally."), ECVF_Scalability);
|
|
|
|
// FindChainBones
|
|
//
|
|
// Returns an ordered list of the names of all the bones in a chain between BeginBoneName and EndBoneName.
|
|
//
|
|
void FindChainBones(const FName BeginBoneName, const FName EndBoneName, TFunctionRef< FName (const FName) > GetParentBoneName, TArray<FName>& OutChainBoneNames)
|
|
{
|
|
OutChainBoneNames.Add(BeginBoneName);
|
|
|
|
const uint32 OutputStartIndex = OutChainBoneNames.Num();
|
|
|
|
// Add the end of the chain. We have to walk from the bottom upwards to find a chain
|
|
// as walking downwards doesn't guarantee a single end point.
|
|
|
|
// Walk up the chain until we either find the top or hit the root bone
|
|
FName ParentBoneName = EndBoneName;
|
|
for (; !ParentBoneName.IsNone() && (ParentBoneName != BeginBoneName); ParentBoneName = GetParentBoneName(ParentBoneName))
|
|
{
|
|
OutChainBoneNames.Insert(ParentBoneName, OutputStartIndex);
|
|
}
|
|
|
|
if (ParentBoneName != BeginBoneName)
|
|
{
|
|
UE_LOG(LogAnimation, Error, TEXT("AnimDynamics: Attempted to find bone chain starting at %s and ending at %s but failed."), *BeginBoneName.ToString(), *EndBoneName.ToString());
|
|
|
|
// Remove any accumulated chain bone names beyond the start bone from output.
|
|
OutChainBoneNames.RemoveAt(OutputStartIndex, OutChainBoneNames.Num() - OutputStartIndex);
|
|
}
|
|
}
|
|
|
|
const float FAnimNode_AnimDynamics::MaxTimeDebt = (1.0f / 60.0f) * 5.0f; // 5 frames max debt
|
|
|
|
#if ENABLE_ANIM_DRAW_DEBUG
|
|
|
|
TAutoConsoleVariable<int32> CVarShowDebug(TEXT("p.animdynamics.showdebug"), 0, TEXT("Enable/disable the drawing of animdynamics data."));
|
|
TAutoConsoleVariable<FString> CVarDebugBone(TEXT("p.animdynamics.debugbone"), FString(), TEXT("Filters p.animdynamics.showdebug to a specific bone by name."));
|
|
|
|
void FAnimNode_AnimDynamics::DrawBodies(FComponentSpacePoseContext& InContext, const TArray<FAnimPhysRigidBody*>& InBodies)
|
|
{
|
|
if(CVarShowDebug.GetValueOnAnyThread() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
auto ToWorldT = [this](FComponentSpacePoseContext& InPoseContext, const FTransform& SimTransform)
|
|
{
|
|
FTransform OutTransform = GetComponentSpaceTransformFromSimSpace(SimulationSpace, InPoseContext, SimTransform);
|
|
OutTransform *= InPoseContext.AnimInstanceProxy->GetComponentTransform();
|
|
return OutTransform;
|
|
};
|
|
|
|
auto ToWorldV = [this](FComponentSpacePoseContext& InPoseContext, const FVector& SimLocation)
|
|
{
|
|
FVector OutLoc = GetComponentSpaceTransformFromSimSpace(SimulationSpace, InPoseContext, FTransform(SimLocation)).GetTranslation();
|
|
OutLoc = InPoseContext.AnimInstanceProxy->GetComponentTransform().TransformPosition(SimLocation);
|
|
return OutLoc;
|
|
};
|
|
|
|
FAnimInstanceProxy* Proxy = InContext.AnimInstanceProxy;
|
|
|
|
check(Proxy);
|
|
|
|
const FString FilteredBoneName = CVarDebugBone.GetValueOnAnyThread();
|
|
const bool bFilterBone = FilteredBoneName.Len() > 0;
|
|
|
|
const int32 NumBodies = Bodies.Num();
|
|
|
|
check(PhysicsBodyDefinitions.Num() == NumBodies);
|
|
|
|
for(int32 BodyIndex = 0 ; BodyIndex < NumBodies ; ++BodyIndex)
|
|
{
|
|
const FAnimPhysRigidBody& Body = Bodies[BodyIndex].RigidBody.PhysBody;
|
|
const FAnimPhysBodyDefinition& PhysicsBodyDef = PhysicsBodyDefinitions[BodyIndex];
|
|
|
|
if(bFilterBone && PhysicsBodyDef.BoundBone.BoneName != FName(*FilteredBoneName))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FTransform Transform(Body.Pose.Orientation, Body.Pose.Position);
|
|
Transform = GetComponentSpaceTransformFromSimSpace(SimulationSpace, InContext, Transform);
|
|
Transform *= Proxy->GetComponentTransform();
|
|
|
|
Proxy->AnimDrawDebugCoordinateSystem(Transform.GetTranslation(), Transform.Rotator(), 2.0f, false, -1.0f, 0.15f);
|
|
|
|
for(const FAnimPhysShape& Shape : Body.Shapes)
|
|
{
|
|
const int32 NumTris = Shape.Triangles.Num();
|
|
for(int32 TriIndex = 0; TriIndex < NumTris; ++TriIndex)
|
|
{
|
|
FIntVector Tri = Shape.Triangles[TriIndex];
|
|
|
|
Proxy->AnimDrawDebugLine(ToWorldV(InContext, Transform.TransformPosition(Shape.Vertices[Tri.X])), ToWorldV(InContext, Transform.TransformPosition(Shape.Vertices[Tri.Y])), FColor::Yellow, false, -1.0f, 0.15f);
|
|
Proxy->AnimDrawDebugLine(ToWorldV(InContext, Transform.TransformPosition(Shape.Vertices[Tri.Y])), ToWorldV(InContext, Transform.TransformPosition(Shape.Vertices[Tri.Z])), FColor::Yellow, false, -1.0f, 0.15f);
|
|
Proxy->AnimDrawDebugLine(ToWorldV(InContext, Transform.TransformPosition(Shape.Vertices[Tri.Z])), ToWorldV(InContext, Transform.TransformPosition(Shape.Vertices[Tri.X])), FColor::Yellow, false, -1.0f, 0.15f);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(SimulationSpace != AnimPhysSimSpaceType::World)
|
|
{
|
|
FTransform Origin;
|
|
|
|
switch(SimulationSpace)
|
|
{
|
|
case AnimPhysSimSpaceType::Actor:
|
|
{
|
|
Origin = Proxy->GetActorTransform();
|
|
}
|
|
break;
|
|
case AnimPhysSimSpaceType::BoneRelative:
|
|
{
|
|
Origin = Proxy->GetComponentTransform() * InContext.Pose.GetComponentSpaceTransform(FCompactPoseBoneIndex(RelativeSpaceBone.BoneIndex));
|
|
}
|
|
break;
|
|
case AnimPhysSimSpaceType::Component:
|
|
{
|
|
Origin = Proxy->GetComponentTransform();
|
|
}
|
|
break;
|
|
case AnimPhysSimSpaceType::RootRelative:
|
|
{
|
|
Origin = Proxy->GetComponentTransform() * InContext.Pose.GetComponentSpaceTransform(FCompactPoseBoneIndex(0));
|
|
}
|
|
break;
|
|
|
|
default: break;
|
|
}
|
|
|
|
Proxy->AnimDrawDebugSphere(Origin.GetTranslation(), 25.0f, 16, FColor::Green, false, -1.0f, 0.15f);
|
|
Proxy->AnimDrawDebugCoordinateSystem(Origin.GetTranslation(), Origin.Rotator(), 3.0f, false, -1.0f, 0.15f);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
FAnimNode_AnimDynamics::FAnimNode_AnimDynamics()
|
|
: LinearDampingOverride(0.0f)
|
|
, AngularDampingOverride(0.0f)
|
|
, PreviousCompWorldSpaceTM(FTransform::Identity)
|
|
, PreviousActorWorldSpaceTM(FTransform::Identity)
|
|
, GravityScale(1.0f)
|
|
, GravityOverride(ForceInitToZero)
|
|
, LinearSpringConstant(0.0f)
|
|
, AngularSpringConstant(0.0f)
|
|
, WindScale(1.0f)
|
|
, AngularBiasOverride(0.0f)
|
|
, NumSolverIterationsPreUpdate(4)
|
|
, NumSolverIterationsPostUpdate(1)
|
|
, ExternalForce(ForceInitToZero)
|
|
, SimulationSpace(AnimPhysSimSpaceType::Component)
|
|
, LastSimSpace(AnimPhysSimSpaceType::Component)
|
|
, InitTeleportType(ETeleportType::None)
|
|
, bUseSphericalLimits(false)
|
|
, bUsePlanarLimit(true)
|
|
, bDoUpdate(true)
|
|
, bDoEval(true)
|
|
, bOverrideLinearDamping(false)
|
|
, bOverrideAngularBias(false)
|
|
, bOverrideAngularDamping(false)
|
|
, bEnableWind(false)
|
|
, bWindWasEnabled(false)
|
|
, bUseGravityOverride(false)
|
|
, bGravityOverrideInSimSpace(false)
|
|
, bLinearSpring(false)
|
|
, bAngularSpring(false)
|
|
, bChain(false)
|
|
, RetargetingSettings(FRotationRetargetingInfo(false /* enabled */))
|
|
#if WITH_EDITORONLY_DATA
|
|
, BoxExtents_DEPRECATED(FVector::ZeroVector)
|
|
, LocalJointOffset_DEPRECATED(FVector::ZeroVector)
|
|
, CollisionType_DEPRECATED(AnimPhysCollisionType::CoM)
|
|
, SphereCollisionRadius_DEPRECATED(0.0f)
|
|
#endif
|
|
#if WITH_EDITOR
|
|
, bDoPhysicsUpdateInEditor(true)
|
|
#endif
|
|
#if ENABLE_ANIM_DRAW_DEBUG
|
|
, FilteredBoneIndex(INDEX_NONE)
|
|
#endif
|
|
{
|
|
ComponentLinearAccScale = FVector::ZeroVector;
|
|
ComponentLinearVelScale = FVector::ZeroVector;
|
|
ComponentAppliedLinearAccClamp = FVector(100000, 100000, 100000);
|
|
|
|
PreviousComponentLinearVelocity = FVector::ZeroVector;
|
|
|
|
PhysicsBodyDefinitions.Add(FAnimPhysBodyDefinition());
|
|
}
|
|
|
|
void FAnimNode_AnimDynamics::Initialize_AnyThread(const FAnimationInitializeContext& Context)
|
|
{
|
|
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Initialize_AnyThread)
|
|
FAnimNode_SkeletalControlBase::Initialize_AnyThread(Context);
|
|
|
|
FBoneContainer& RequiredBones = Context.AnimInstanceProxy->GetRequiredBones();
|
|
|
|
InitializeBoneReferences(RequiredBones);
|
|
|
|
if(BoundBone.IsValidToEvaluate(RequiredBones))
|
|
{
|
|
RequestInitialise(ETeleportType::ResetPhysics);
|
|
}
|
|
|
|
PreviousCompWorldSpaceTM = Context.AnimInstanceProxy->GetComponentTransform();
|
|
PreviousActorWorldSpaceTM = Context.AnimInstanceProxy->GetActorTransform();
|
|
|
|
NextTimeStep = 0.0f;
|
|
TimeDebt = 0.0f;
|
|
}
|
|
|
|
void FAnimNode_AnimDynamics::UpdateInternal(const FAnimationUpdateContext& Context)
|
|
{
|
|
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(UpdateInternal)
|
|
FAnimNode_SkeletalControlBase::UpdateInternal(Context);
|
|
|
|
NextTimeStep = Context.GetDeltaTime();
|
|
}
|
|
|
|
struct FSimBodiesScratch : public TThreadSingleton<FSimBodiesScratch>
|
|
{
|
|
TArray<FAnimPhysRigidBody*> SimBodies;
|
|
};
|
|
|
|
bool FAnimNode_AnimDynamics::IsAnimDynamicsSystemEnabledFor(int32 InLOD)
|
|
{
|
|
int32 RestrictToLOD = CVarRestrictLod.GetValueOnAnyThread();
|
|
bool bEnabledForLod = RestrictToLOD >= 0 ? InLOD == RestrictToLOD : true;
|
|
|
|
// note this doesn't check LODThreshold of global value here. That's checked in
|
|
// GetLODThreshold per node
|
|
return (CVarEnableDynamics.GetValueOnAnyThread() == 1 && bEnabledForLod);
|
|
}
|
|
void FAnimNode_AnimDynamics::EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray<FBoneTransform>& OutBoneTransforms)
|
|
{
|
|
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(EvaluateSkeletalControl_AnyThread)
|
|
SCOPE_CYCLE_COUNTER(STAT_AnimDynamicsOverall);
|
|
CSV_SCOPED_TIMING_STAT(Animation, AnimDynamicsEval);
|
|
|
|
if (IsAnimDynamicsSystemEnabledFor(Output.AnimInstanceProxy->GetLODLevel()))
|
|
{
|
|
if(LastSimSpace != SimulationSpace)
|
|
{
|
|
// Our sim space has been changed since our last update, we need to convert all of our
|
|
// body transforms into the new space.
|
|
ConvertSimulationSpace(Output, LastSimSpace, SimulationSpace);
|
|
}
|
|
|
|
// Pretty nasty - but there isn't really a good way to get clean bone transforms (without the modification from
|
|
// previous runs) so we have to initialize here, checking often so we can restart a simulation in the editor.
|
|
if (InitTeleportType != ETeleportType::None)
|
|
{
|
|
InitPhysics(Output);
|
|
InitTeleportType = ETeleportType::None;
|
|
}
|
|
|
|
const FBoneContainer& RequiredBones = Output.Pose.GetPose().GetBoneContainer();
|
|
while(BodiesToReset.Num() > 0)
|
|
{
|
|
FAnimPhysLinkedBody* BodyToReset = BodiesToReset.Pop(false);
|
|
if(BodyToReset && BodyToReset->RigidBody.BoundBone.IsValidToEvaluate(RequiredBones))
|
|
{
|
|
FTransform BoneTransform = GetBoneTransformInSimSpace(Output, BodyToReset->RigidBody.BoundBone.GetCompactPoseIndex(RequiredBones));
|
|
FAnimPhysRigidBody& PhysBody = BodyToReset->RigidBody.PhysBody;
|
|
|
|
PhysBody.Pose.Position = BoneTransform.GetTranslation();
|
|
PhysBody.Pose.Orientation = BoneTransform.GetRotation();
|
|
PhysBody.LinearMomentum = FVector::ZeroVector;
|
|
PhysBody.AngularMomentum = FVector::ZeroVector;
|
|
}
|
|
}
|
|
|
|
if (ShouldDoPhysicsUpdate() && NextTimeStep > AnimPhysicsMinDeltaTime)
|
|
{
|
|
// Calculate gravity direction
|
|
SimSpaceGravityDirection = TransformWorldVectorToSimSpace(Output, FVector(0.0f, 0.0f, -1.0f));
|
|
|
|
FVector OrientedExternalForce = ExternalForce;
|
|
if(!OrientedExternalForce.IsNearlyZero())
|
|
{
|
|
OrientedExternalForce = TransformWorldVectorToSimSpace(Output, OrientedExternalForce);
|
|
}
|
|
|
|
// We don't send any bodies that don't have valid bones to the simulation
|
|
TArray<FAnimPhysRigidBody*>& SimBodies = FSimBodiesScratch::Get().SimBodies;
|
|
SimBodies.Empty(SimBodies.Num());
|
|
for(int32& ActiveIndex : ActiveBoneIndices)
|
|
{
|
|
if(BaseBodyPtrs.IsValidIndex(ActiveIndex))
|
|
{
|
|
SimBodies.Add(BaseBodyPtrs[ActiveIndex]);
|
|
}
|
|
}
|
|
|
|
FVector ComponentLinearAcc(0.0f);
|
|
|
|
if (SimulationSpace != AnimPhysSimSpaceType::World)
|
|
{
|
|
FTransform CurrentTransform = Output.AnimInstanceProxy->GetComponentTransform();
|
|
|
|
// Transform Gravity Override into simulation space
|
|
if (bUseGravityOverride && !bGravityOverrideInSimSpace)
|
|
{
|
|
const FVector GravityOverrideSimSpace = TransformWorldVectorToSimSpace(Output, GravityOverride);
|
|
|
|
for (FAnimPhysRigidBody* ChainBody : SimBodies)
|
|
{
|
|
ChainBody->GravityOverride = GravityOverrideSimSpace;
|
|
}
|
|
}
|
|
|
|
// Calc linear velocity
|
|
const FVector ComponentDeltaLocation = CurrentTransform.GetTranslation() - PreviousCompWorldSpaceTM.GetTranslation();
|
|
const FVector ComponentLinearVelocity = ComponentDeltaLocation / NextTimeStep;
|
|
// Apply acceleration that opposed velocity (basically 'drag')
|
|
ComponentLinearAcc += TransformWorldVectorToSimSpace(Output, -ComponentLinearVelocity) * ComponentLinearVelScale;
|
|
|
|
// Calc linear acceleration
|
|
const FVector ComponentLinearAcceleration = (ComponentLinearVelocity - PreviousComponentLinearVelocity) / NextTimeStep;
|
|
PreviousComponentLinearVelocity = ComponentLinearVelocity;
|
|
// Apply opposite acceleration to bodies
|
|
ComponentLinearAcc += TransformWorldVectorToSimSpace(Output, -ComponentLinearAcceleration) * ComponentLinearAccScale;
|
|
|
|
// Clamp to desired strength
|
|
ComponentLinearAcc = ComponentLinearAcc.BoundToBox(-ComponentAppliedLinearAccClamp, ComponentAppliedLinearAccClamp);
|
|
}
|
|
|
|
if (CVarEnableAdaptiveSubstep.GetValueOnAnyThread() == 1)
|
|
{
|
|
float CurrentTimeDilation = Output.AnimInstanceProxy->GetTimeDilation();
|
|
float FixedTimeStep = MaxSubstepDeltaTime * CurrentTimeDilation;
|
|
|
|
// Clamp the fixed timestep down to max physics tick time.
|
|
// at high speeds the simulation will not converge as the delta time is too high, this will
|
|
// help to keep constraints together at a cost of physical accuracy
|
|
FixedTimeStep = FMath::Clamp(FixedTimeStep, 0.0f, MaxPhysicsDeltaTime);
|
|
|
|
// Calculate number of substeps we should do.
|
|
int32 NumIters = FMath::TruncToInt((NextTimeStep + (TimeDebt * CurrentTimeDilation)) / FixedTimeStep);
|
|
NumIters = FMath::Clamp(NumIters, 0, MaxSubsteps);
|
|
|
|
SET_DWORD_STAT(STAT_AnimDynamicsSubSteps, NumIters);
|
|
|
|
// Store the remaining time as debt for later frames
|
|
TimeDebt = (NextTimeStep + TimeDebt) - (NumIters * FixedTimeStep);
|
|
TimeDebt = FMath::Clamp(TimeDebt, 0.0f, MaxTimeDebt);
|
|
|
|
NextTimeStep = FixedTimeStep;
|
|
|
|
for (int32 Iter = 0; Iter < NumIters; ++Iter)
|
|
{
|
|
UpdateLimits(Output);
|
|
FAnimPhys::PhysicsUpdate(FixedTimeStep, SimBodies, LinearLimits, AngularLimits, Springs, SimSpaceGravityDirection, OrientedExternalForce, ComponentLinearAcc, NumSolverIterationsPreUpdate, NumSolverIterationsPostUpdate);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Do variable frame-time update
|
|
const float MaxDeltaTime = MaxPhysicsDeltaTime;
|
|
|
|
NextTimeStep = FMath::Min(NextTimeStep, MaxDeltaTime);
|
|
|
|
UpdateLimits(Output);
|
|
FAnimPhys::PhysicsUpdate(NextTimeStep, SimBodies, LinearLimits, AngularLimits, Springs, SimSpaceGravityDirection, OrientedExternalForce, ComponentLinearAcc, NumSolverIterationsPreUpdate, NumSolverIterationsPostUpdate);
|
|
}
|
|
|
|
#if ENABLE_ANIM_DRAW_DEBUG
|
|
DrawBodies(Output, SimBodies);
|
|
#endif
|
|
}
|
|
|
|
if (bDoEval)
|
|
{
|
|
CONDITIONAL_SCOPE_CYCLE_COUNTER(STAT_AnimDynamicsBoneEval, FAnimPhys::bEnableDetailedStats);
|
|
|
|
const FBoneContainer& BoneContainer = Output.Pose.GetPose().GetBoneContainer();
|
|
|
|
check(Bodies.Num() == PhysicsBodyDefinitions.Num());
|
|
check(PhysicsBodyJointOffsets.Num() == PhysicsBodyDefinitions.Num());
|
|
|
|
for (int32 Idx = 0; Idx < Bodies.Num(); ++Idx)
|
|
{
|
|
FBoneReference& CurrentChainBone = PhysicsBodyDefinitions[Idx].BoundBone;
|
|
FAnimPhysRigidBody& CurrentBody = Bodies[Idx].RigidBody.PhysBody;
|
|
|
|
// Skip invalid bones
|
|
if(!CurrentChainBone.IsValidToEvaluate(BoneContainer))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FCompactPoseBoneIndex BoneIndex = CurrentChainBone.GetCompactPoseIndex(BoneContainer);
|
|
|
|
// Calculate target bone transform from physics body.
|
|
FTransform NewBoneTransform(CurrentBody.Pose.Orientation, CurrentBody.Pose.Position - CurrentBody.Pose.Orientation.RotateVector(PhysicsBodyJointOffsets[Idx]));
|
|
|
|
if (RetargetingSettings.bEnabled)
|
|
{
|
|
FTransform ParentTransform = FTransform::Identity;
|
|
FCompactPoseBoneIndex ParentBoneIndex = BoneContainer.GetParentBoneIndex(BoneIndex);
|
|
if (ParentBoneIndex != INDEX_NONE)
|
|
{
|
|
ParentTransform = GetBoneTransformInSimSpace(Output, ParentBoneIndex);
|
|
}
|
|
|
|
FQuat RetargetedRotation = CommonAnimationLibrary::RetargetSingleRotation(
|
|
NewBoneTransform.GetRotation(),
|
|
RetargetingSettings.Source * ParentTransform,
|
|
RetargetingSettings.Target * ParentTransform,
|
|
RetargetingSettings.CustomCurve,
|
|
RetargetingSettings.EasingType,
|
|
RetargetingSettings.bFlipEasing,
|
|
RetargetingSettings.EasingWeight,
|
|
RetargetingSettings.RotationComponent,
|
|
RetargetingSettings.TwistAxis,
|
|
RetargetingSettings.bUseAbsoluteAngle,
|
|
RetargetingSettings.SourceMinimum,
|
|
RetargetingSettings.SourceMaximum,
|
|
RetargetingSettings.TargetMinimum,
|
|
RetargetingSettings.TargetMaximum);
|
|
|
|
NewBoneTransform.SetRotation(RetargetedRotation);
|
|
}
|
|
|
|
NewBoneTransform = GetComponentSpaceTransformFromSimSpace(SimulationSpace, Output, NewBoneTransform);
|
|
|
|
OutBoneTransforms.Add(FBoneTransform(BoneIndex, NewBoneTransform));
|
|
}
|
|
}
|
|
|
|
// Store our sim space in case it changes
|
|
LastSimSpace = SimulationSpace;
|
|
|
|
// Store previous component and actor space transform
|
|
PreviousCompWorldSpaceTM = Output.AnimInstanceProxy->GetComponentTransform();
|
|
PreviousActorWorldSpaceTM = Output.AnimInstanceProxy->GetActorTransform();
|
|
}
|
|
}
|
|
|
|
void FAnimNode_AnimDynamics::UpdateChainPhysicsBodyDefinitions(const USkeletalMeshComponent* const PreviewSkelMeshComp)
|
|
{
|
|
if (PreviewSkelMeshComp)
|
|
{
|
|
TArray<FName> ChainBoneNames;
|
|
FindChainBones(BoundBone.BoneName, ChainEnd.BoneName, [PreviewSkelMeshComp](const FName BoneName) { return PreviewSkelMeshComp->GetParentBone(BoneName); }, ChainBoneNames);
|
|
UpdateChainPhysicsBodyDefinitions(ChainBoneNames);
|
|
}
|
|
}
|
|
|
|
void FAnimNode_AnimDynamics::UpdateChainPhysicsBodyDefinitions(const FReferenceSkeleton& ReferenceSkeleton)
|
|
{
|
|
auto GetParentBoneNameFn = [&ReferenceSkeleton](const FName BoneName)
|
|
{
|
|
int32 BoneIndex = ReferenceSkeleton.FindBoneIndex(BoneName);
|
|
BoneIndex = ReferenceSkeleton.GetParentIndex(BoneIndex);
|
|
return ReferenceSkeleton.GetBoneName(BoneIndex);
|
|
};
|
|
|
|
TArray<FName> ChainBoneNames;
|
|
FindChainBones(BoundBone.BoneName, ChainEnd.BoneName, GetParentBoneNameFn, ChainBoneNames);
|
|
UpdateChainPhysicsBodyDefinitions(ChainBoneNames);
|
|
}
|
|
|
|
void FAnimNode_AnimDynamics::UpdateChainPhysicsBodyDefinitions(const TArray<FName>& ChainBoneNames)
|
|
{
|
|
check(ChainBoneNames.Num() > 0);
|
|
|
|
// Remove any bodies for bones that are not in the chain.
|
|
PhysicsBodyDefinitions.RemoveAll([&ChainBoneNames](const FAnimPhysBodyDefinition& Value) { return ChainBoneNames.Find(Value.BoundBone.BoneName) == INDEX_NONE;});
|
|
PhysicsBodyDefinitions.Reserve(ChainBoneNames.Num());
|
|
|
|
// Create a new Physics Body Def for any new bones in the chain and add them before or after the existing bodies as appropriate to maintain the order of the chain bones.
|
|
{
|
|
// If there was only one physics body then copy its values to all new chain bodies (emulating legacy behaviour), otherwise use values from default construction.
|
|
FAnimPhysBodyDefinition PrototypePhysBodyDef;
|
|
|
|
if (PhysicsBodyDefinitions.Num() == 1)
|
|
{
|
|
PrototypePhysBodyDef = PhysicsBodyDefinitions[0];
|
|
}
|
|
|
|
uint32 PhysicsBodyDefIndex = 0;
|
|
|
|
for (FName BoneName : ChainBoneNames)
|
|
{
|
|
if (!PhysicsBodyDefinitions.FindByPredicate([BoneName](const FAnimPhysBodyDefinition& Value) { return Value.BoundBone.BoneName == BoneName; }))
|
|
{
|
|
// Add the new bone to the chain.
|
|
FAnimPhysBodyDefinition ChainPhysicsBody;
|
|
ChainPhysicsBody.BoundBone.BoneName = BoneName;
|
|
ChainPhysicsBody.BoxExtents = PrototypePhysBodyDef.BoxExtents;
|
|
ChainPhysicsBody.LocalJointOffset = PrototypePhysBodyDef.LocalJointOffset;
|
|
ChainPhysicsBody.SphereCollisionRadius = PrototypePhysBodyDef.SphereCollisionRadius;
|
|
PhysicsBodyDefinitions.Insert(ChainPhysicsBody, PhysicsBodyDefIndex);
|
|
}
|
|
|
|
++PhysicsBodyDefIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FAnimNode_AnimDynamics::InitializeBoneReferences(const FBoneContainer& RequiredBones)
|
|
{
|
|
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(InitializeBoneReferences)
|
|
BoundBone.Initialize(RequiredBones);
|
|
|
|
if (bChain)
|
|
{
|
|
ChainEnd.Initialize(RequiredBones);
|
|
}
|
|
|
|
for (FAnimPhysPlanarLimit& PlanarLimit : PlanarLimits)
|
|
{
|
|
PlanarLimit.DrivingBone.Initialize(RequiredBones);
|
|
}
|
|
|
|
for (FAnimPhysSphericalLimit& SphericalLimit : SphericalLimits)
|
|
{
|
|
SphericalLimit.DrivingBone.Initialize(RequiredBones);
|
|
}
|
|
|
|
if (SimulationSpace == AnimPhysSimSpaceType::BoneRelative)
|
|
{
|
|
RelativeSpaceBone.Initialize(RequiredBones);
|
|
}
|
|
|
|
// If we're currently simulating (LOD change etc.)
|
|
bool bSimulating = ActiveBoneIndices.Num() > 0;
|
|
|
|
const int32 NumRefs = PhysicsBodyDefinitions.Num();
|
|
for(int32 BoneRefIdx = 0; BoneRefIdx < NumRefs; ++BoneRefIdx)
|
|
{
|
|
FBoneReference& BoneRef = PhysicsBodyDefinitions[BoneRefIdx].BoundBone;
|
|
BoneRef.Initialize(RequiredBones);
|
|
|
|
if(bSimulating)
|
|
{
|
|
if(BoneRef.IsValidToEvaluate(RequiredBones) && !ActiveBoneIndices.Contains(BoneRefIdx))
|
|
{
|
|
// This body is inactive and needs to be reset to bone position
|
|
// as it is now required for the current LOD
|
|
BodiesToReset.Add(&Bodies[BoneRefIdx]);
|
|
}
|
|
}
|
|
}
|
|
|
|
ActiveBoneIndices.Empty(ActiveBoneIndices.Num());
|
|
const int32 NumBodies = Bodies.Num();
|
|
for(int32 BodyIdx = 0; BodyIdx < NumBodies; ++BodyIdx)
|
|
{
|
|
FAnimPhysLinkedBody& LinkedBody = Bodies[BodyIdx];
|
|
LinkedBody.RigidBody.BoundBone.Initialize(RequiredBones);
|
|
|
|
// If this bone is active in this LOD, add to the active list.
|
|
if(LinkedBody.RigidBody.BoundBone.IsValidToEvaluate(RequiredBones))
|
|
{
|
|
ActiveBoneIndices.Add(BodyIdx);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FAnimNode_AnimDynamics::GatherDebugData(FNodeDebugData& DebugData)
|
|
{
|
|
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(GatherDebugData)
|
|
const float ActualBiasedAlpha = AlphaScaleBias.ApplyTo(Alpha);
|
|
|
|
FString DebugLine = DebugData.GetNodeName(this);
|
|
DebugLine += FString::Printf(TEXT("(Alpha: %.1f%%)"), ActualBiasedAlpha*100.f);
|
|
|
|
DebugData.AddDebugItem(DebugLine);
|
|
ComponentPose.GatherDebugData(DebugData);
|
|
}
|
|
|
|
bool FAnimNode_AnimDynamics::IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones)
|
|
{
|
|
bool bValid = BoundBone.IsValidToEvaluate(RequiredBones);
|
|
|
|
if (bChain)
|
|
{
|
|
bool bChainEndValid = ChainEnd.IsValidToEvaluate(RequiredBones);
|
|
bool bSubChainValid = false;
|
|
|
|
if(!bChainEndValid)
|
|
{
|
|
// Check for LOD subchain
|
|
int32 NumValidBonesFromRoot = 0;
|
|
for(const FAnimPhysBodyDefinition& PhysicsBodyDef : PhysicsBodyDefinitions)
|
|
{
|
|
if(PhysicsBodyDef.BoundBone.IsValidToEvaluate(RequiredBones))
|
|
{
|
|
bSubChainValid = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bValid = bValid && (bChainEndValid || bSubChainValid);
|
|
}
|
|
|
|
return bValid;
|
|
}
|
|
|
|
int32 FAnimNode_AnimDynamics::GetNumBodies() const
|
|
{
|
|
return Bodies.Num();
|
|
}
|
|
|
|
const FAnimPhysRigidBody& FAnimNode_AnimDynamics::GetPhysBody(int32 BodyIndex) const
|
|
{
|
|
return Bodies[BodyIndex].RigidBody.PhysBody;
|
|
}
|
|
|
|
FTransform FAnimNode_AnimDynamics::GetBodyComponentSpaceTransform(const FAnimPhysRigidBody& Body, const USkeletalMeshComponent* const SkelComp) const
|
|
{
|
|
return GetComponentSpaceTransformFromSimSpace(SimulationSpace, SkelComp, FTransform(Body.Pose.Orientation, Body.Pose.Position));
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
FVector FAnimNode_AnimDynamics::GetBodyLocalJointOffset(const int32 BodyIndex) const
|
|
{
|
|
if (PhysicsBodyDefinitions.IsValidIndex(BodyIndex))
|
|
{
|
|
return PhysicsBodyDefinitions[BodyIndex].LocalJointOffset;
|
|
}
|
|
return FVector::ZeroVector;
|
|
}
|
|
|
|
#endif
|
|
|
|
void FAnimNode_AnimDynamics::RequestInitialise(ETeleportType InTeleportType)
|
|
{
|
|
// Request an initialization. Teleport type can only go higher - i.e. if we have requested a reset, then a teleport will still reset fully
|
|
InitTeleportType = ((InTeleportType > InitTeleportType) ? InTeleportType : InitTeleportType);
|
|
}
|
|
|
|
void FAnimNode_AnimDynamics::InitPhysics(FComponentSpacePoseContext& Output)
|
|
{
|
|
switch (InitTeleportType)
|
|
{
|
|
case ETeleportType::ResetPhysics:
|
|
{
|
|
// Clear up any existing physics data
|
|
TermPhysics();
|
|
|
|
const FBoneContainer& BoneContainer = Output.Pose.GetPose().GetBoneContainer();
|
|
|
|
if (PhysicsBodyDefinitions.Num() && ((PhysicsBodyDefinitions[0].BoundBone.BoneName != BoundBone.BoneName) || (PhysicsBodyDefinitions.Last().BoundBone.BoneName != ChainEnd.BoneName)))
|
|
{
|
|
UpdateChainPhysicsBodyDefinitions(BoneContainer.GetReferenceSkeleton());
|
|
}
|
|
|
|
// Transform GravityOverride to simulation space if necessary.
|
|
const FVector GravityOverrideSimSpace = (bUseGravityOverride && !bGravityOverrideInSimSpace) ? TransformWorldVectorToSimSpace(Output, GravityOverride) : GravityOverride;
|
|
|
|
check(PhysicsBodyDefinitions.Num() > 0);
|
|
if (PhysicsBodyDefinitions.Num() > 0)
|
|
{
|
|
Bodies.Reserve(PhysicsBodyDefinitions.Num());
|
|
PhysicsBodyDefinitions[0].BoundBone = BoundBone;
|
|
}
|
|
|
|
ConstraintOffsets.Reset(PhysicsBodyDefinitions.Num());
|
|
PhysicsBodyJointOffsets.Reset(PhysicsBodyDefinitions.Num());
|
|
|
|
FTransform PreviousBodyTransformSimSpace;
|
|
PreviousBodyTransformSimSpace.SetIdentity();
|
|
|
|
for (FAnimPhysBodyDefinition& PhysicsBodyDef : PhysicsBodyDefinitions)
|
|
{
|
|
TArray<FAnimPhysShape> BodyShapes;
|
|
BodyShapes.Add(FAnimPhysShape::MakeBox(PhysicsBodyDef.BoxExtents));
|
|
|
|
PhysicsBodyDef.BoundBone.Initialize(BoneContainer);
|
|
|
|
FTransform BodyTransform = GetBoneTransformInSimSpace(Output, PhysicsBodyDef.BoundBone.GetCompactPoseIndex(BoneContainer));
|
|
BodyTransform.SetTranslation(BodyTransform.GetTranslation() + BodyTransform.GetRotation().RotateVector(PhysicsBodyDef.LocalJointOffset)); // Transform for physics body in Sim Space.
|
|
|
|
FAnimPhysLinkedBody NewChainBody(BodyShapes, BodyTransform.GetTranslation(), PhysicsBodyDef.BoundBone);
|
|
FAnimPhysRigidBody& PhysicsBody = NewChainBody.RigidBody.PhysBody;
|
|
PhysicsBody.Pose.Orientation = BodyTransform.GetRotation();
|
|
PhysicsBody.PreviousOrientation = PhysicsBody.Pose.Orientation;
|
|
PhysicsBody.NextOrientation = PhysicsBody.Pose.Orientation;
|
|
PhysicsBody.CollisionType = PhysicsBodyDef.CollisionType;
|
|
|
|
switch (PhysicsBody.CollisionType)
|
|
{
|
|
case AnimPhysCollisionType::CustomSphere:
|
|
PhysicsBody.SphereCollisionRadius = PhysicsBodyDef.SphereCollisionRadius;
|
|
break;
|
|
case AnimPhysCollisionType::InnerSphere:
|
|
PhysicsBody.SphereCollisionRadius = PhysicsBodyDef.BoxExtents.GetAbsMin() / 2.0f;
|
|
break;
|
|
case AnimPhysCollisionType::OuterSphere:
|
|
PhysicsBody.SphereCollisionRadius = PhysicsBodyDef.BoxExtents.GetAbsMax() / 2.0f;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (bOverrideLinearDamping)
|
|
{
|
|
PhysicsBody.bLinearDampingOverriden = true;
|
|
PhysicsBody.LinearDamping = LinearDampingOverride;
|
|
}
|
|
|
|
if (bOverrideAngularDamping)
|
|
{
|
|
PhysicsBody.bAngularDampingOverriden = true;
|
|
PhysicsBody.AngularDamping = AngularDampingOverride;
|
|
}
|
|
|
|
PhysicsBody.GravityScale = GravityScale;
|
|
PhysicsBody.bUseGravityOverride = bUseGravityOverride;
|
|
PhysicsBody.GravityOverride = GravityOverrideSimSpace;
|
|
|
|
PhysicsBody.bWindEnabled = bWindWasEnabled;
|
|
|
|
if (Bodies.Num() > 0)
|
|
{
|
|
// Link to parent
|
|
NewChainBody.ParentBody = &Bodies.Last().RigidBody;
|
|
|
|
// Calculate constraint offset positions in the space of each body.
|
|
const FVector ConstaintLocationSimSpace = (BodyTransform.GetTranslation() - PreviousBodyTransformSimSpace.GetTranslation()) * 0.5f;
|
|
ConstraintOffsets.Add(FAnimConstraintOffsetPair(PreviousBodyTransformSimSpace.GetRotation().UnrotateVector(ConstaintLocationSimSpace), BodyTransform.GetRotation().UnrotateVector(-ConstaintLocationSimSpace)));
|
|
}
|
|
else
|
|
{
|
|
// The first physics body is constrained to the location of it's bound bone.
|
|
ConstraintOffsets.Add(FAnimConstraintOffsetPair(FVector::ZeroVector, -PhysicsBodyDef.LocalJointOffset)); // Offset is the vector from the physics body to its associated bone in the bodies local space.
|
|
}
|
|
|
|
// Set up transient constraint data
|
|
const bool bXAxisLocked = PhysicsBodyDef.ConstraintSetup.LinearXLimitType != AnimPhysLinearConstraintType::Free && PhysicsBodyDef.ConstraintSetup.LinearAxesMin.X - PhysicsBodyDef.ConstraintSetup.LinearAxesMax.X == 0.0f;
|
|
const bool bYAxisLocked = PhysicsBodyDef.ConstraintSetup.LinearYLimitType != AnimPhysLinearConstraintType::Free && PhysicsBodyDef.ConstraintSetup.LinearAxesMin.Y - PhysicsBodyDef.ConstraintSetup.LinearAxesMax.Y == 0.0f;
|
|
const bool bZAxisLocked = PhysicsBodyDef.ConstraintSetup.LinearZLimitType != AnimPhysLinearConstraintType::Free && PhysicsBodyDef.ConstraintSetup.LinearAxesMin.Z - PhysicsBodyDef.ConstraintSetup.LinearAxesMax.Z == 0.0f;
|
|
PhysicsBodyDef.ConstraintSetup.bLinearFullyLocked = bXAxisLocked && bYAxisLocked && bZAxisLocked;
|
|
|
|
Bodies.Add(NewChainBody);
|
|
ActiveBoneIndices.Add(Bodies.Num() - 1);
|
|
PhysicsBodyJointOffsets.Add(PhysicsBodyDef.LocalJointOffset);
|
|
|
|
PreviousBodyTransformSimSpace = BodyTransform;
|
|
}
|
|
|
|
BaseBodyPtrs.Reset();
|
|
for (FAnimPhysLinkedBody& Body : Bodies)
|
|
{
|
|
BaseBodyPtrs.Add(&Body.RigidBody.PhysBody);
|
|
}
|
|
|
|
// Cache physics settings to avoid accessing UPhysicsSettings continuously
|
|
if (UPhysicsSettings* Settings = UPhysicsSettings::Get())
|
|
{
|
|
AnimPhysicsMinDeltaTime = Settings->AnimPhysicsMinDeltaTime;
|
|
MaxPhysicsDeltaTime = Settings->MaxPhysicsDeltaTime;
|
|
MaxSubstepDeltaTime = Settings->MaxSubstepDeltaTime;
|
|
MaxSubsteps = Settings->MaxSubsteps;
|
|
}
|
|
else
|
|
{
|
|
AnimPhysicsMinDeltaTime = 0.f;
|
|
MaxPhysicsDeltaTime = (1.0f / 30.0f);
|
|
MaxSubstepDeltaTime = (1.0f / 60.0f);
|
|
MaxSubsteps = 4;
|
|
}
|
|
|
|
SimSpaceGravityDirection = TransformWorldVectorToSimSpace(Output, FVector(0.0f, 0.0f, -1.0f));
|
|
}
|
|
break;
|
|
|
|
case ETeleportType::TeleportPhysics:
|
|
{
|
|
// Clear any external forces
|
|
ExternalForce = FVector::ZeroVector;
|
|
|
|
// Move any active bones
|
|
for (const int32& BodyIndex : ActiveBoneIndices)
|
|
{
|
|
FAnimPhysRigidBody& Body = Bodies[BodyIndex].RigidBody.PhysBody;
|
|
|
|
// Get old comp space transform
|
|
FTransform BodyTransform(Body.Pose.Orientation, Body.Pose.Position - Body.Pose.Orientation.RotateVector(PhysicsBodyDefinitions[BodyIndex].LocalJointOffset));
|
|
BodyTransform = GetComponentSpaceTransformFromSimSpace(SimulationSpace, Output, BodyTransform, PreviousCompWorldSpaceTM, PreviousActorWorldSpaceTM);
|
|
|
|
// move to new space
|
|
BodyTransform = GetSimSpaceTransformFromComponentSpace(SimulationSpace, Output, BodyTransform);
|
|
|
|
Body.Pose.Orientation = BodyTransform.GetRotation();
|
|
Body.PreviousOrientation = Body.Pose.Orientation;
|
|
Body.NextOrientation = Body.Pose.Orientation;
|
|
|
|
Body.Pose.Position = BodyTransform.GetTranslation() + Body.Pose.Orientation.RotateVector(PhysicsBodyDefinitions[BodyIndex].LocalJointOffset);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
InitTeleportType = ETeleportType::None;
|
|
PreviousCompWorldSpaceTM = Output.AnimInstanceProxy->GetComponentTransform();
|
|
PreviousActorWorldSpaceTM = Output.AnimInstanceProxy->GetActorTransform();
|
|
}
|
|
|
|
void FAnimNode_AnimDynamics::TermPhysics()
|
|
{
|
|
Bodies.Reset();
|
|
LinearLimits.Reset();
|
|
AngularLimits.Reset();
|
|
Springs.Reset();
|
|
ActiveBoneIndices.Reset();
|
|
PhysicsBodyJointOffsets.Reset();
|
|
ConstraintOffsets.Reset();
|
|
BodiesToReset.Reset();
|
|
|
|
for (FAnimPhysBodyDefinition& PhysicsBodyDef : PhysicsBodyDefinitions)
|
|
{
|
|
PhysicsBodyDef.BoundBone.BoneIndex = INDEX_NONE;
|
|
}
|
|
}
|
|
|
|
void FAnimNode_AnimDynamics::UpdateLimits(FComponentSpacePoseContext& Output)
|
|
{
|
|
CONDITIONAL_SCOPE_CYCLE_COUNTER(STAT_AnimDynamicsLimitUpdate, FAnimPhys::bEnableDetailedStats);
|
|
|
|
// We're always going to use the same number so don't realloc
|
|
LinearLimits.Empty(LinearLimits.Num());
|
|
AngularLimits.Empty(AngularLimits.Num());
|
|
Springs.Empty(Springs.Num());
|
|
|
|
const FBoneContainer& BoneContainer = Output.Pose.GetPose().GetBoneContainer();
|
|
|
|
for(int32 ActiveIndex : ActiveBoneIndices)
|
|
{
|
|
const FAnimPhysBodyDefinition& PhysicsBodyDef = PhysicsBodyDefinitions[ActiveIndex];
|
|
const FBoneReference& CurrentBoneRef = PhysicsBodyDef.BoundBone;
|
|
|
|
// If our bone isn't valid, move on
|
|
if(!CurrentBoneRef.IsValidToEvaluate(BoneContainer))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FAnimPhysLinkedBody& ChainBody = Bodies[ActiveIndex];
|
|
FAnimPhysRigidBody& RigidBody = Bodies[ActiveIndex].RigidBody.PhysBody;
|
|
|
|
FAnimPhysRigidBody* PrevBody = nullptr;
|
|
if (ChainBody.ParentBody)
|
|
{
|
|
PrevBody = &ChainBody.ParentBody->PhysBody;
|
|
}
|
|
|
|
// Get joint transform
|
|
FCompactPoseBoneIndex BoneIndex = CurrentBoneRef.GetCompactPoseIndex(BoneContainer);
|
|
FTransform BoundBoneTransform = GetBoneTransformInSimSpace(Output, BoneIndex);
|
|
|
|
FTransform ShapeTransform = BoundBoneTransform;
|
|
|
|
if (PrevBody)
|
|
{
|
|
ShapeTransform = FTransform(FQuat::Identity, ConstraintOffsets[ActiveIndex].Body0Offset);
|
|
}
|
|
|
|
// Local offset to joint for Body1
|
|
const FVector Body1JointOffset = ConstraintOffsets[ActiveIndex].Body1Offset;
|
|
|
|
if (PhysicsBodyDef.ConstraintSetup.bLinearFullyLocked)
|
|
{
|
|
// Rather than calculate prismatic limits, just lock the transform (1 limit instead of 6)
|
|
FAnimPhys::ConstrainPositionNailed(NextTimeStep, LinearLimits, PrevBody, ShapeTransform.GetTranslation(), &RigidBody, Body1JointOffset);
|
|
}
|
|
else
|
|
{
|
|
if (PhysicsBodyDef.ConstraintSetup.LinearXLimitType != AnimPhysLinearConstraintType::Free)
|
|
{
|
|
FAnimPhys::ConstrainAlongDirection(NextTimeStep, LinearLimits, PrevBody, ShapeTransform.GetTranslation(), &RigidBody, Body1JointOffset, ShapeTransform.GetRotation().GetAxisX(), FVector2D(PhysicsBodyDef.ConstraintSetup.LinearAxesMin.X, PhysicsBodyDef.ConstraintSetup.LinearAxesMax.X));
|
|
}
|
|
|
|
if (PhysicsBodyDef.ConstraintSetup.LinearYLimitType != AnimPhysLinearConstraintType::Free)
|
|
{
|
|
FAnimPhys::ConstrainAlongDirection(NextTimeStep, LinearLimits, PrevBody, ShapeTransform.GetTranslation(), &RigidBody, Body1JointOffset, ShapeTransform.GetRotation().GetAxisY(), FVector2D(PhysicsBodyDef.ConstraintSetup.LinearAxesMin.Y, PhysicsBodyDef.ConstraintSetup.LinearAxesMax.Y));
|
|
}
|
|
|
|
if (PhysicsBodyDef.ConstraintSetup.LinearZLimitType != AnimPhysLinearConstraintType::Free)
|
|
{
|
|
FAnimPhys::ConstrainAlongDirection(NextTimeStep, LinearLimits, PrevBody, ShapeTransform.GetTranslation(), &RigidBody, Body1JointOffset, ShapeTransform.GetRotation().GetAxisZ(), FVector2D(PhysicsBodyDef.ConstraintSetup.LinearAxesMin.Z, PhysicsBodyDef.ConstraintSetup.LinearAxesMax.Z));
|
|
}
|
|
}
|
|
|
|
if (PhysicsBodyDef.ConstraintSetup.AngularConstraintType == AnimPhysAngularConstraintType::Angular)
|
|
{
|
|
#if WITH_EDITOR
|
|
// Check the ranges are valid when running in the editor, log if something is wrong
|
|
if(PhysicsBodyDef.ConstraintSetup.AngularLimitsMin.X > PhysicsBodyDef.ConstraintSetup.AngularLimitsMax.X ||
|
|
PhysicsBodyDef.ConstraintSetup.AngularLimitsMin.Y > PhysicsBodyDef.ConstraintSetup.AngularLimitsMax.Y ||
|
|
PhysicsBodyDef.ConstraintSetup.AngularLimitsMin.Z > PhysicsBodyDef.ConstraintSetup.AngularLimitsMax.Z)
|
|
{
|
|
UE_LOG(LogAnimation, Warning, TEXT("AnimDynamics: Min/Max angular limits for bone %s incorrect, at least one min axis value is greater than the corresponding max."), *BoundBone.BoneName.ToString());
|
|
}
|
|
#endif
|
|
|
|
// Add angular limits. any limit with 360+ degree range is ignored and left free.
|
|
FAnimPhys::ConstrainAngularRange(NextTimeStep, AngularLimits, PrevBody, &RigidBody, ShapeTransform.GetRotation(), PhysicsBodyDef.ConstraintSetup.TwistAxis, PhysicsBodyDef.ConstraintSetup.AngularLimitsMin, PhysicsBodyDef.ConstraintSetup.AngularLimitsMax, bOverrideAngularBias ? AngularBiasOverride : AnimPhysicsConstants::JointBiasFactor);
|
|
}
|
|
else
|
|
{
|
|
FAnimPhys::ConstrainConeAngle(NextTimeStep, AngularLimits, PrevBody, BoundBoneTransform.GetRotation().GetAxisX(), &RigidBody, FVector(1.0f, 0.0f, 0.0f), PhysicsBodyDef.ConstraintSetup.ConeAngle, bOverrideAngularBias ? AngularBiasOverride : AnimPhysicsConstants::JointBiasFactor);
|
|
}
|
|
|
|
if(PlanarLimits.Num() > 0 && bUsePlanarLimit)
|
|
{
|
|
for(FAnimPhysPlanarLimit& PlanarLimit : PlanarLimits)
|
|
{
|
|
FTransform LimitPlaneTransform = PlanarLimit.PlaneTransform;
|
|
if(PlanarLimit.DrivingBone.IsValidToEvaluate(BoneContainer))
|
|
{
|
|
FCompactPoseBoneIndex DrivingBoneIndex = PlanarLimit.DrivingBone.GetCompactPoseIndex(BoneContainer);
|
|
|
|
FTransform DrivingBoneTransform = GetBoneTransformInSimSpace(Output, DrivingBoneIndex);
|
|
|
|
LimitPlaneTransform *= DrivingBoneTransform;
|
|
}
|
|
|
|
FAnimPhys::ConstrainPlanar(NextTimeStep, LinearLimits, &RigidBody, LimitPlaneTransform);
|
|
}
|
|
}
|
|
|
|
if(SphericalLimits.Num() > 0 && bUseSphericalLimits)
|
|
{
|
|
for(FAnimPhysSphericalLimit& SphericalLimit : SphericalLimits)
|
|
{
|
|
FTransform SphereTransform = FTransform::Identity;
|
|
SphereTransform.SetTranslation(SphericalLimit.SphereLocalOffset);
|
|
|
|
if(SphericalLimit.DrivingBone.IsValidToEvaluate(BoneContainer))
|
|
{
|
|
FCompactPoseBoneIndex DrivingBoneIndex = SphericalLimit.DrivingBone.GetCompactPoseIndex(BoneContainer);
|
|
|
|
FTransform DrivingBoneTransform = GetBoneTransformInSimSpace(Output, DrivingBoneIndex);
|
|
|
|
SphereTransform *= DrivingBoneTransform;
|
|
}
|
|
|
|
switch(SphericalLimit.LimitType)
|
|
{
|
|
case ESphericalLimitType::Inner:
|
|
FAnimPhys::ConstrainSphericalInner(NextTimeStep, LinearLimits, &RigidBody, SphereTransform, SphericalLimit.LimitRadius);
|
|
break;
|
|
case ESphericalLimitType::Outer:
|
|
FAnimPhys::ConstrainSphericalOuter(NextTimeStep, LinearLimits, &RigidBody, SphereTransform, SphericalLimit.LimitRadius);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add spring if we need spring forces
|
|
if (bAngularSpring || bLinearSpring)
|
|
{
|
|
FAnimPhys::CreateSpring(Springs, PrevBody, ShapeTransform.GetTranslation(), &RigidBody, FVector::ZeroVector);
|
|
FAnimPhysSpring& NewSpring = Springs.Last();
|
|
NewSpring.SpringConstantLinear = LinearSpringConstant;
|
|
NewSpring.SpringConstantAngular = AngularSpringConstant;
|
|
NewSpring.AngularTarget = PhysicsBodyDef.ConstraintSetup.AngularTarget.GetSafeNormal();
|
|
NewSpring.AngularTargetAxis = PhysicsBodyDef.ConstraintSetup.AngularTargetAxis;
|
|
NewSpring.TargetOrientationOffset = ShapeTransform.GetRotation();
|
|
NewSpring.bApplyAngular = bAngularSpring;
|
|
NewSpring.bApplyLinear = bLinearSpring;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FAnimNode_AnimDynamics::HasPreUpdate() const
|
|
{
|
|
if(CVarEnableDynamics.GetValueOnGameThread() == 1)
|
|
{
|
|
return (CVarEnableWind.GetValueOnGameThread() == 1 && (bEnableWind || bWindWasEnabled))
|
|
#if ENABLE_ANIM_DRAW_DEBUG
|
|
|| (CVarShowDebug.GetValueOnGameThread() == 1 && !CVarDebugBone.GetValueOnGameThread().IsEmpty())
|
|
#endif
|
|
;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FAnimNode_AnimDynamics::PreUpdate(const UAnimInstance* InAnimInstance)
|
|
{
|
|
// If dynamics are disabled, skip all this work as it'll never get used
|
|
if(CVarEnableDynamics.GetValueOnAnyThread() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(!InAnimInstance)
|
|
{
|
|
// No anim instance, won't be able to find our world.
|
|
return;
|
|
}
|
|
|
|
const USkeletalMeshComponent* SkelComp = InAnimInstance->GetSkelMeshComponent();
|
|
|
|
if(!SkelComp || !SkelComp->GetWorld())
|
|
{
|
|
// Can't find our world.
|
|
return;
|
|
}
|
|
|
|
const UWorld* World = SkelComp->GetWorld();
|
|
|
|
if(CVarEnableWind.GetValueOnAnyThread() == 1 && bEnableWind)
|
|
{
|
|
CONDITIONAL_SCOPE_CYCLE_COUNTER(STAT_AnimDynamicsWindData, FAnimPhys::bEnableDetailedStats);
|
|
|
|
for(FAnimPhysRigidBody* Body : BaseBodyPtrs)
|
|
{
|
|
Body->bWindEnabled = bEnableWind;
|
|
|
|
if(Body->bWindEnabled && World->Scene)
|
|
{
|
|
FSceneInterface* Scene = World->Scene;
|
|
|
|
// Unused by our simulation but needed for the call to GetWindParameters below
|
|
float WindMinGust;
|
|
float WindMaxGust;
|
|
|
|
// Setup wind data
|
|
Body->bWindEnabled = true;
|
|
Scene->GetWindParameters_GameThread(SkelComp->GetComponentTransform().TransformPosition(Body->Pose.Position), Body->WindData.WindDirection, Body->WindData.WindSpeed, WindMinGust, WindMaxGust);
|
|
|
|
Body->WindData.WindDirection = SkelComp->GetComponentTransform().Inverse().TransformVector(Body->WindData.WindDirection);
|
|
Body->WindData.WindAdaption = FMath::FRandRange(0.0f, 2.0f);
|
|
Body->WindData.BodyWindScale = WindScale;
|
|
}
|
|
}
|
|
}
|
|
else if (bWindWasEnabled)
|
|
{
|
|
CONDITIONAL_SCOPE_CYCLE_COUNTER(STAT_AnimDynamicsWindData, FAnimPhys::bEnableDetailedStats);
|
|
|
|
bWindWasEnabled = false;
|
|
for(FAnimPhysRigidBody* Body : BaseBodyPtrs)
|
|
{
|
|
Body->bWindEnabled = false;
|
|
}
|
|
}
|
|
|
|
#if ENABLE_ANIM_DRAW_DEBUG
|
|
FilteredBoneIndex = INDEX_NONE;
|
|
if(SkelComp)
|
|
{
|
|
FString FilteredBoneName = CVarDebugBone.GetValueOnGameThread();
|
|
if(FilteredBoneName.Len() > 0)
|
|
{
|
|
FilteredBoneIndex = SkelComp->GetBoneIndex(FName(*FilteredBoneName));
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int32 FAnimNode_AnimDynamics::GetLODThreshold() const
|
|
{
|
|
if(CVarLODThreshold.GetValueOnAnyThread() != -1)
|
|
{
|
|
if(LODThreshold != -1)
|
|
{
|
|
return FMath::Min(LODThreshold, CVarLODThreshold.GetValueOnAnyThread());
|
|
}
|
|
else
|
|
{
|
|
return CVarLODThreshold.GetValueOnAnyThread();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return LODThreshold;
|
|
}
|
|
}
|
|
|
|
FTransform FAnimNode_AnimDynamics::GetBoneTransformInSimSpace(FComponentSpacePoseContext& Output, const FCompactPoseBoneIndex& BoneIndex) const
|
|
{
|
|
FTransform Transform = Output.Pose.GetComponentSpaceTransform(BoneIndex);
|
|
|
|
return GetSimSpaceTransformFromComponentSpace(SimulationSpace, Output, Transform);
|
|
}
|
|
|
|
FTransform FAnimNode_AnimDynamics::GetComponentSpaceTransformFromSimSpace(AnimPhysSimSpaceType SimSpace, FComponentSpacePoseContext& Output, const FTransform& InSimTransform) const
|
|
{
|
|
return GetComponentSpaceTransformFromSimSpace(SimSpace, Output, InSimTransform, Output.AnimInstanceProxy->GetComponentTransform(), Output.AnimInstanceProxy->GetActorTransform());
|
|
}
|
|
|
|
FTransform FAnimNode_AnimDynamics::GetComponentSpaceTransformFromSimSpace(AnimPhysSimSpaceType SimSpace, FComponentSpacePoseContext& Output, const FTransform& InSimTransform, const FTransform& InCompWorldSpaceTM, const FTransform& InActorWorldSpaceTM) const
|
|
{
|
|
FTransform OutTransform = InSimTransform;
|
|
|
|
switch(SimSpace)
|
|
{
|
|
// Change nothing, already in component space
|
|
case AnimPhysSimSpaceType::Component:
|
|
{
|
|
break;
|
|
}
|
|
|
|
case AnimPhysSimSpaceType::Actor:
|
|
{
|
|
FTransform WorldTransform(OutTransform * InActorWorldSpaceTM);
|
|
WorldTransform.SetToRelativeTransform(InCompWorldSpaceTM);
|
|
OutTransform = WorldTransform;
|
|
|
|
break;
|
|
}
|
|
|
|
case AnimPhysSimSpaceType::RootRelative:
|
|
{
|
|
const FBoneContainer& RequiredBones = Output.Pose.GetPose().GetBoneContainer();
|
|
|
|
FCompactPoseBoneIndex RootBoneCompactIndex(0);
|
|
|
|
FTransform RelativeBoneTransform = Output.Pose.GetComponentSpaceTransform(RootBoneCompactIndex);
|
|
OutTransform = OutTransform * RelativeBoneTransform;
|
|
|
|
break;
|
|
}
|
|
|
|
case AnimPhysSimSpaceType::BoneRelative:
|
|
{
|
|
const FBoneContainer& RequiredBones = Output.Pose.GetPose().GetBoneContainer();
|
|
if(RelativeSpaceBone.IsValidToEvaluate(RequiredBones))
|
|
{
|
|
FTransform RelativeBoneTransform = Output.Pose.GetComponentSpaceTransform(RelativeSpaceBone.GetCompactPoseIndex(RequiredBones));
|
|
OutTransform = OutTransform * RelativeBoneTransform;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case AnimPhysSimSpaceType::World:
|
|
{
|
|
OutTransform *= InCompWorldSpaceTM.Inverse();
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return OutTransform;
|
|
}
|
|
|
|
FTransform FAnimNode_AnimDynamics::GetComponentSpaceTransformFromSimSpace(AnimPhysSimSpaceType SimSpace, const USkeletalMeshComponent* const SkelComp, const FTransform& InSimTransform) const
|
|
{
|
|
FTransform OutTransformCS = InSimTransform;
|
|
|
|
check(SkelComp);
|
|
if (SkelComp)
|
|
{
|
|
if (SimSpace == AnimPhysSimSpaceType::RootRelative)
|
|
{
|
|
const FTransform RelativeBoneTransform = SkelComp->GetBoneTransform(0);
|
|
OutTransformCS = InSimTransform * RelativeBoneTransform;
|
|
}
|
|
else if (SimSpace == AnimPhysSimSpaceType::BoneRelative)
|
|
{
|
|
const FTransform RelativeBoneTransform = SkelComp->GetBoneTransform(SkelComp->GetBoneIndex(RelativeSpaceBone.BoneName));
|
|
OutTransformCS = InSimTransform * RelativeBoneTransform;
|
|
}
|
|
}
|
|
|
|
return OutTransformCS;
|
|
}
|
|
|
|
FTransform FAnimNode_AnimDynamics::GetSimSpaceTransformFromComponentSpace(AnimPhysSimSpaceType SimSpace, FComponentSpacePoseContext& Output, const FTransform& InComponentTransform) const
|
|
{
|
|
FTransform ResultTransform = InComponentTransform;
|
|
|
|
switch(SimSpace)
|
|
{
|
|
// Change nothing, already in component space
|
|
case AnimPhysSimSpaceType::Component:
|
|
{
|
|
break;
|
|
}
|
|
|
|
case AnimPhysSimSpaceType::Actor:
|
|
{
|
|
FTransform WorldTransform = ResultTransform * Output.AnimInstanceProxy->GetComponentTransform();
|
|
WorldTransform.SetToRelativeTransform(Output.AnimInstanceProxy->GetActorTransform());
|
|
ResultTransform = WorldTransform;
|
|
|
|
break;
|
|
}
|
|
|
|
case AnimPhysSimSpaceType::RootRelative:
|
|
{
|
|
const FBoneContainer& RequiredBones = Output.Pose.GetPose().GetBoneContainer();
|
|
|
|
FCompactPoseBoneIndex RootBoneCompactIndex(0);
|
|
|
|
FTransform RelativeBoneTransform = Output.Pose.GetComponentSpaceTransform(RootBoneCompactIndex);
|
|
ResultTransform = ResultTransform.GetRelativeTransform(RelativeBoneTransform);
|
|
|
|
break;
|
|
}
|
|
|
|
case AnimPhysSimSpaceType::BoneRelative:
|
|
{
|
|
const FBoneContainer& RequiredBones = Output.Pose.GetPose().GetBoneContainer();
|
|
if(RelativeSpaceBone.IsValidToEvaluate(RequiredBones))
|
|
{
|
|
FTransform RelativeBoneTransform = Output.Pose.GetComponentSpaceTransform(RelativeSpaceBone.GetCompactPoseIndex(RequiredBones));
|
|
ResultTransform = ResultTransform.GetRelativeTransform(RelativeBoneTransform);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case AnimPhysSimSpaceType::World:
|
|
{
|
|
// Out to world space
|
|
ResultTransform *= Output.AnimInstanceProxy->GetComponentTransform();
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ResultTransform;
|
|
}
|
|
|
|
FVector FAnimNode_AnimDynamics::TransformWorldVectorToSimSpace(FComponentSpacePoseContext& Output, const FVector& InVec) const
|
|
{
|
|
FVector OutVec = InVec;
|
|
|
|
switch(SimulationSpace)
|
|
{
|
|
case AnimPhysSimSpaceType::Component:
|
|
{
|
|
OutVec = Output.AnimInstanceProxy->GetComponentTransform().InverseTransformVectorNoScale(OutVec);
|
|
|
|
break;
|
|
}
|
|
|
|
case AnimPhysSimSpaceType::Actor:
|
|
{
|
|
OutVec = Output.AnimInstanceProxy->GetActorTransform().InverseTransformVectorNoScale(OutVec);
|
|
|
|
break;
|
|
}
|
|
|
|
case AnimPhysSimSpaceType::RootRelative:
|
|
{
|
|
const FBoneContainer& RequiredBones = Output.Pose.GetPose().GetBoneContainer();
|
|
|
|
FCompactPoseBoneIndex RootBoneCompactIndex(0);
|
|
|
|
FTransform RelativeBoneTransform = Output.Pose.GetComponentSpaceTransform(RootBoneCompactIndex);
|
|
RelativeBoneTransform = Output.AnimInstanceProxy->GetComponentTransform() * RelativeBoneTransform;
|
|
OutVec = RelativeBoneTransform.InverseTransformVectorNoScale(OutVec);
|
|
|
|
break;
|
|
}
|
|
|
|
case AnimPhysSimSpaceType::BoneRelative:
|
|
{
|
|
const FBoneContainer& RequiredBones = Output.Pose.GetPose().GetBoneContainer();
|
|
if(RelativeSpaceBone.IsValidToEvaluate(RequiredBones))
|
|
{
|
|
FTransform RelativeBoneTransform = Output.Pose.GetComponentSpaceTransform(RelativeSpaceBone.GetCompactPoseIndex(RequiredBones));
|
|
RelativeBoneTransform = Output.AnimInstanceProxy->GetComponentTransform() * RelativeBoneTransform;
|
|
OutVec = RelativeBoneTransform.InverseTransformVectorNoScale(OutVec);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case AnimPhysSimSpaceType::World:
|
|
{
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return OutVec;
|
|
}
|
|
|
|
void FAnimNode_AnimDynamics::ConvertSimulationSpace(FComponentSpacePoseContext& Output, AnimPhysSimSpaceType From, AnimPhysSimSpaceType To) const
|
|
{
|
|
for(FAnimPhysRigidBody* Body : BaseBodyPtrs)
|
|
{
|
|
if(!Body)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Get transform
|
|
FTransform BodyTransform(Body->Pose.Orientation, Body->Pose.Position);
|
|
// Out to component space
|
|
BodyTransform = GetComponentSpaceTransformFromSimSpace(LastSimSpace, Output, BodyTransform);
|
|
// In to new space
|
|
BodyTransform = GetSimSpaceTransformFromComponentSpace(SimulationSpace, Output, BodyTransform);
|
|
|
|
// Push back to body
|
|
Body->Pose.Orientation = BodyTransform.GetRotation();
|
|
Body->Pose.Position = BodyTransform.GetTranslation();
|
|
}
|
|
}
|
|
|
|
bool FAnimNode_AnimDynamics::ShouldDoPhysicsUpdate() const
|
|
{
|
|
#if WITH_EDITOR
|
|
if (!bDoPhysicsUpdateInEditor)
|
|
{
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
return bDoUpdate;
|
|
}
|