// 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 CVarRestrictLod(TEXT("p.AnimDynamicsRestrictLOD"), -1, TEXT("Forces anim dynamics to be enabled for only a specified LOD, -1 to enable on all LODs.")); TAutoConsoleVariable 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 CVarEnableDynamics(TEXT("p.AnimDynamics"), 1, TEXT("Enables/Disables anim dynamics node updates."), ECVF_Scalability); TAutoConsoleVariable 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 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 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& OutChainBoneNames) { OutChainBoneNames.Add(BeginBoneName); if (!EndBoneName.IsNone()) { 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 CVarShowDebug(TEXT("p.animdynamics.showdebug"), 0, TEXT("Enable/disable the drawing of animdynamics data.")); TAutoConsoleVariable 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& 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 { TArray 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& 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& 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(Bodies.Num() <= PhysicsBodyJointOffsets.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 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); if (ReferenceSkeleton.IsValidIndex(BoneIndex)) { BoneIndex = ReferenceSkeleton.GetParentIndex(BoneIndex); } if (ReferenceSkeleton.IsValidIndex(BoneIndex)) { return ReferenceSkeleton.GetBoneName(BoneIndex); } return FName(NAME_None); }; TArray ChainBoneNames; FindChainBones(BoundBone.BoneName, ChainEnd.BoneName, GetParentBoneNameFn, ChainBoneNames); UpdateChainPhysicsBodyDefinitions(ChainBoneNames); } void FAnimNode_AnimDynamics::UpdateChainPhysicsBodyDefinitions(const TArray& 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 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; }