You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#rb jordan.cristiano #ROBOMERGE-AUTHOR: evgenii.babinets #ROBOMERGE-SOURCE: CL 19483684 via CL 19484981 via CL 19486898 via CL 19496784 via CL 19497168 #ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v936-19480137) [CL 19498484 by evgenii babinets in ue5-main branch]
1849 lines
72 KiB
C++
1849 lines
72 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "BoneControllers/AnimNode_RigidBody.h"
|
|
#include "AnimationRuntime.h"
|
|
#include "Animation/AnimInstanceProxy.h"
|
|
#include "HAL/Event.h"
|
|
#include "HAL/LowLevelMemTracker.h"
|
|
#include "PhysicsEngine/BodySetup.h"
|
|
#include "PhysicsEngine/PhysicsAsset.h"
|
|
#include "PhysicsEngine/PhysicsConstraintTemplate.h"
|
|
#include "GameFramework/PawnMovementComponent.h"
|
|
#include "Physics/PhysicsInterfaceCore.h"
|
|
#include "Physics/ImmediatePhysics/ImmediatePhysicsActorHandle.h"
|
|
#include "Physics/ImmediatePhysics/ImmediatePhysicsSimulation.h"
|
|
#include "Physics/ImmediatePhysics/ImmediatePhysicsStats.h"
|
|
#include "PhysicsField/PhysicsFieldComponent.h"
|
|
#include "PhysicsEngine/PhysicsSettings.h"
|
|
#include "Logging/MessageLog.h"
|
|
#include "Logging/LogMacros.h"
|
|
|
|
LLM_DEFINE_TAG(Animation_RigidBody);
|
|
|
|
//PRAGMA_DISABLE_OPTIMIZATION
|
|
|
|
/////////////////////////////////////////////////////
|
|
// FAnimNode_RigidBody
|
|
|
|
#define LOCTEXT_NAMESPACE "ImmediatePhysics"
|
|
|
|
DEFINE_STAT(STAT_RigidBodyNodeInitTime);
|
|
|
|
CSV_DECLARE_CATEGORY_MODULE_EXTERN(ENGINE_API, Animation);
|
|
|
|
#if UE_BUILD_SHIPPING || UE_BUILD_TEST
|
|
DECLARE_LOG_CATEGORY_EXTERN(LogRBAN, Log, Warning);
|
|
#else
|
|
DECLARE_LOG_CATEGORY_EXTERN(LogRBAN, Log, All);
|
|
#endif
|
|
DEFINE_LOG_CATEGORY(LogRBAN);
|
|
|
|
TAutoConsoleVariable<int32> CVarEnableRigidBodyNode(TEXT("p.RigidBodyNode"), 1, TEXT("Enables/disables the whole rigid body node system. When disabled, avoids all allocations and runtime costs. Can be used to disable RB Nodes on low-end platforms."), ECVF_Default);
|
|
TAutoConsoleVariable<int32> CVarEnableRigidBodyNodeSimulation(TEXT("p.RigidBodyNode.EnableSimulation"), 1, TEXT("Runtime Enable/Disable RB Node Simulation for debugging and testing (node is initialized and bodies and constraints are created, even when disabled.)"), ECVF_Default);
|
|
TAutoConsoleVariable<int32> CVarRigidBodyLODThreshold(TEXT("p.RigidBodyLODThreshold"), -1, TEXT("Max LOD that rigid body node is allowed to run on. Provides a global threshold that overrides per-node the LODThreshold property. -1 means no override."), ECVF_Scalability);
|
|
|
|
int32 RBAN_MaxSubSteps = 4;
|
|
bool bRBAN_EnableTimeBasedReset = true;
|
|
bool bRBAN_EnableComponentAcceleration = true;
|
|
int32 RBAN_WorldObjectExpiry = 4;
|
|
FAutoConsoleVariableRef CVarRigidBodyNodeMaxSteps(TEXT("p.RigidBodyNode.MaxSubSteps"), RBAN_MaxSubSteps, TEXT("Set the maximum number of simulation steps in the update loop"), ECVF_Default);
|
|
FAutoConsoleVariableRef CVarRigidBodyNodeEnableTimeBasedReset(TEXT("p.RigidBodyNode.EnableTimeBasedReset"), bRBAN_EnableTimeBasedReset, TEXT("If true, Rigid Body nodes are reset when they have not been updated for a while (default true)"), ECVF_Default);
|
|
FAutoConsoleVariableRef CVarRigidBodyNodeEnableComponentAcceleration(TEXT("p.RigidBodyNode.EnableComponentAcceleration"), bRBAN_EnableComponentAcceleration, TEXT("Enable/Disable the simple acceleration transfer system for component- or bone-space simulation"), ECVF_Default);
|
|
FAutoConsoleVariableRef CVarRigidBodyNodeWorldObjectExpiry(TEXT("p.RigidBodyNode.WorldObjectExpiry"), RBAN_WorldObjectExpiry, TEXT("World objects are removed from the simulation if not detected after this many tests"), ECVF_Default);
|
|
|
|
// FSimSpaceSettings forced overrides for testing
|
|
bool bRBAN_SimSpace_EnableOverride = false;
|
|
FSimSpaceSettings RBAN_SimSpaceOverride;
|
|
FAutoConsoleVariableRef CVarRigidBodyNodeSpaceOverride(TEXT("p.RigidBodyNode.Space.Override"), bRBAN_SimSpace_EnableOverride, TEXT("Force-enable the advanced simulation space movement forces"), ECVF_Default);
|
|
FAutoConsoleVariableRef CVarRigidBodyNodeSpaceMasterAlpha(TEXT("p.RigidBodyNode.Space.MasterAlpha"), RBAN_SimSpaceOverride.MasterAlpha, TEXT("RBAN SimSpaceSettings overrides"), ECVF_Default);
|
|
FAutoConsoleVariableRef CVarRigidBodyNodeSpaceVelScaleZ(TEXT("p.RigidBodyNode.Space.VelocityScaleZ"), RBAN_SimSpaceOverride.VelocityScaleZ, TEXT("RBAN SimSpaceSettings overrides"), ECVF_Default);
|
|
FAutoConsoleVariableRef CVarRigidBodyNodeSpaceMaxCompLinVel(TEXT("p.RigidBodyNode.Space.MaxLinearVelocity"), RBAN_SimSpaceOverride.MaxLinearVelocity, TEXT("RBAN SimSpaceSettings overrides"), ECVF_Default);
|
|
FAutoConsoleVariableRef CVarRigidBodyNodeSpaceMaxCompAngVel(TEXT("p.RigidBodyNode.Space.MaxAngularVelocity"), RBAN_SimSpaceOverride.MaxAngularVelocity, TEXT("RBAN SimSpaceSettings overrides"), ECVF_Default);
|
|
FAutoConsoleVariableRef CVarRigidBodyNodeSpaceMaxCompLinAcc(TEXT("p.RigidBodyNode.Space.MaxLinearAcceleration"), RBAN_SimSpaceOverride.MaxLinearAcceleration, TEXT("RBAN SimSpaceSettings overrides"), ECVF_Default);
|
|
FAutoConsoleVariableRef CVarRigidBodyNodeSpaceMaxCompAngAcc(TEXT("p.RigidBodyNode.Space.MaxAngularAcceleration"), RBAN_SimSpaceOverride.MaxAngularAcceleration, TEXT("RBAN SimSpaceSettings overrides"), ECVF_Default);
|
|
// LWC_TODO: Double support for console variables
|
|
#if 0
|
|
FAutoConsoleVariableRef CVarRigidBodyNodeSpaceExternalLinearDragX(TEXT("p.RigidBodyNode.Space.ExternalLinearDrag.X"), RBAN_SimSpaceOverride.ExternalLinearDragV.X, TEXT("RBAN SimSpaceSettings overrides"), ECVF_Default);
|
|
FAutoConsoleVariableRef CVarRigidBodyNodeSpaceExternalLinearDragY(TEXT("p.RigidBodyNode.Space.ExternalLinearDrag.Y"), RBAN_SimSpaceOverride.ExternalLinearDragV.Y, TEXT("RBAN SimSpaceSettings overrides"), ECVF_Default);
|
|
FAutoConsoleVariableRef CVarRigidBodyNodeSpaceExternalLinearDragZ(TEXT("p.RigidBodyNode.Space.ExternalLinearDrag.Z"), RBAN_SimSpaceOverride.ExternalLinearDragV.Z, TEXT("RBAN SimSpaceSettings overrides"), ECVF_Default);
|
|
FAutoConsoleVariableRef CVarRigidBodyNodeSpaceExternalLinearVelocityX(TEXT("p.RigidBodyNode.Space.ExternalLinearVelocity.X"), RBAN_SimSpaceOverride.ExternalLinearVelocity.X, TEXT("RBAN SimSpaceSettings overrides"), ECVF_Default);
|
|
FAutoConsoleVariableRef CVarRigidBodyNodeSpaceExternalLinearVelocityY(TEXT("p.RigidBodyNode.Space.ExternalLinearVelocity.Y"), RBAN_SimSpaceOverride.ExternalLinearVelocity.Y, TEXT("RBAN SimSpaceSettings overrides"), ECVF_Default);
|
|
FAutoConsoleVariableRef CVarRigidBodyNodeSpaceExternalLinearVelocityZ(TEXT("p.RigidBodyNode.Space.ExternalLinearVelocity.Z"), RBAN_SimSpaceOverride.ExternalLinearVelocity.Z, TEXT("RBAN SimSpaceSettings overrides"), ECVF_Default);
|
|
#endif
|
|
|
|
bool bRBAN_DeferredSimulationDefault = false;
|
|
FAutoConsoleVariableRef CVarRigidBodyNodeDeferredSimulationDefault(
|
|
TEXT("p.RigidBodyNode.DeferredSimulationDefault"),
|
|
bRBAN_DeferredSimulationDefault,
|
|
TEXT("Whether rigid body simulations are deferred one frame for assets that don't opt into a specific simulation timing"),
|
|
ECVF_Default);
|
|
|
|
bool bRBAN_DebugDraw = false;
|
|
FAutoConsoleVariableRef CVarRigidBodyNodeDebugDraw(TEXT("p.RigidBodyNode.DebugDraw"), bRBAN_DebugDraw, TEXT("Whether to debug draw the rigid body simulation state. Requires p.Chaos.DebugDraw.Enabled 1 to function as well."), ECVF_Default);
|
|
|
|
// Array of priorities that can be indexed into with CVars, since task priorities cannot be set from scalability .ini
|
|
static UE::Tasks::ETaskPriority GRigidBodyNodeTaskPriorities[] =
|
|
{
|
|
UE::Tasks::ETaskPriority::High,
|
|
UE::Tasks::ETaskPriority::Normal,
|
|
UE::Tasks::ETaskPriority::BackgroundHigh,
|
|
UE::Tasks::ETaskPriority::BackgroundNormal,
|
|
UE::Tasks::ETaskPriority::BackgroundLow
|
|
};
|
|
|
|
static int32 GRigidBodyNodeSimulationTaskPriority = 0;
|
|
FAutoConsoleVariableRef CVarRigidBodyNodeSimulationTaskPriority(
|
|
TEXT("p.RigidBodyNode.TaskPriority.Simulation"),
|
|
GRigidBodyNodeSimulationTaskPriority,
|
|
TEXT("Task priority for running the rigid body node simulation task (0 = foreground/high, 1 = foreground/normal, 2 = background/high, 3 = background/normal, 4 = background/low)."),
|
|
ECVF_Default
|
|
);
|
|
|
|
FSimSpaceSettings::FSimSpaceSettings()
|
|
: MasterAlpha(0)
|
|
, VelocityScaleZ(1)
|
|
, MaxLinearVelocity(10000)
|
|
, MaxAngularVelocity(10000)
|
|
, MaxLinearAcceleration(10000)
|
|
, MaxAngularAcceleration(10000)
|
|
, ExternalLinearDrag_DEPRECATED(0)
|
|
, ExternalLinearDragV(FVector::ZeroVector)
|
|
, ExternalLinearVelocity(FVector::ZeroVector)
|
|
, ExternalAngularVelocity(FVector::ZeroVector)
|
|
{
|
|
}
|
|
|
|
void FSimSpaceSettings::PostSerialize(const FArchive& Ar)
|
|
{
|
|
if (Ar.IsLoading())
|
|
{
|
|
if (ExternalLinearDrag_DEPRECATED != 0.0f)
|
|
{
|
|
ExternalLinearDragV = FVector(ExternalLinearDrag_DEPRECATED, ExternalLinearDrag_DEPRECATED, ExternalLinearDrag_DEPRECATED);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
FAnimNode_RigidBody::FAnimNode_RigidBody()
|
|
: OverridePhysicsAsset(nullptr)
|
|
, PreviousCompWorldSpaceTM()
|
|
, CurrentTransform()
|
|
, PreviousTransform()
|
|
, UsePhysicsAsset(nullptr)
|
|
, OverrideWorldGravity(0.0f)
|
|
, ExternalForce(0.0f)
|
|
, ComponentLinearAccScale(0.0f)
|
|
, ComponentLinearVelScale(0.0f)
|
|
, ComponentAppliedLinearAccClamp(10000.0f)
|
|
, SimSpaceSettings()
|
|
, CachedBoundsScale(1.2f)
|
|
, BaseBoneRef()
|
|
, OverlapChannel(ECC_WorldStatic)
|
|
, SimulationSpace(ESimulationSpace::ComponentSpace)
|
|
, bForceDisableCollisionBetweenConstraintBodies(false)
|
|
, ResetSimulatedTeleportType(ETeleportType::None)
|
|
, bEnableWorldGeometry(false)
|
|
, bOverrideWorldGravity(false)
|
|
, bTransferBoneVelocities(false)
|
|
, bFreezeIncomingPoseOnStart(false)
|
|
, bClampLinearTranslationLimitToRefPose(false)
|
|
, WorldSpaceMinimumScale(0.01f)
|
|
, EvaluationResetTime(0.01f)
|
|
, bEnabled(false)
|
|
, bSimulationStarted(false)
|
|
, bCheckForBodyTransformInit(false)
|
|
#if WITH_EDITORONLY_DATA
|
|
, bComponentSpaceSimulation_DEPRECATED(true)
|
|
#endif
|
|
, SimulationTiming(ESimulationTiming::Default)
|
|
, WorldTimeSeconds(0.0f)
|
|
, LastEvalTimeSeconds(0.0f)
|
|
, AccumulatedDeltaTime(0.0f)
|
|
, AnimPhysicsMinDeltaTime(0.0f)
|
|
, bSimulateAnimPhysicsAfterReset(false)
|
|
, SkelMeshCompWeakPtr()
|
|
, PhysicsSimulation(nullptr)
|
|
, SolverSettings()
|
|
, SolverIterations()
|
|
, SimulationTask()
|
|
, OutputBoneData()
|
|
, Bodies()
|
|
, SkeletonBoneIndexToBodyIndex()
|
|
, BodyAnimData()
|
|
, Constraints()
|
|
, PendingRadialForces()
|
|
, PerSolverField()
|
|
, ComponentsInSim()
|
|
, ComponentsInSimTick(0)
|
|
, WorldSpaceGravity(0.0f)
|
|
, TotalMass(0.0f)
|
|
, CachedBounds(FVector::ZeroVector, 0.0f)
|
|
, QueryParams(NAME_None, FCollisionQueryParams::GetUnknownStatId())
|
|
, PhysScene(nullptr)
|
|
, UnsafeWorld(nullptr)
|
|
, UnsafeOwner(nullptr)
|
|
, CapturedBoneVelocityBoneContainer()
|
|
, CapturedBoneVelocityPose()
|
|
, CapturedFrozenPose()
|
|
, CapturedFrozenCurves()
|
|
, PreviousComponentLinearVelocity(0.0f)
|
|
, SimSpacePreviousComponentToWorld()
|
|
, SimSpacePreviousBoneToComponent()
|
|
, SimSpacePreviousComponentLinearVelocity(0.0f)
|
|
, SimSpacePreviousComponentAngularVelocity(0.0f)
|
|
, SimSpacePreviousBoneLinearVelocity(0.0f)
|
|
, SimSpacePreviousBoneAngularVelocity(0.0f)
|
|
{
|
|
}
|
|
|
|
FAnimNode_RigidBody::~FAnimNode_RigidBody()
|
|
{
|
|
DestroyPhysicsSimulation();
|
|
}
|
|
|
|
void FAnimNode_RigidBody::GatherDebugData(FNodeDebugData& DebugData)
|
|
{
|
|
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(GatherDebugData)
|
|
FString DebugLine = DebugData.GetNodeName(this);
|
|
|
|
DebugLine += "(";
|
|
AddDebugNodeData(DebugLine);
|
|
DebugLine += ")";
|
|
|
|
DebugData.AddDebugItem(DebugLine);
|
|
|
|
const bool bUsingFrozenPose = bFreezeIncomingPoseOnStart && bSimulationStarted && (CapturedFrozenPose.GetPose().GetNumBones() > 0);
|
|
if (!bUsingFrozenPose)
|
|
{
|
|
ComponentPose.GatherDebugData(DebugData);
|
|
}
|
|
}
|
|
|
|
FTransform SpaceToWorldTransform(ESimulationSpace Space, const FTransform& ComponentToWorld, const FTransform& BaseBoneTM)
|
|
{
|
|
switch (Space)
|
|
{
|
|
case ESimulationSpace::ComponentSpace:
|
|
return ComponentToWorld;
|
|
case ESimulationSpace::WorldSpace:
|
|
return FTransform::Identity;
|
|
case ESimulationSpace::BaseBoneSpace:
|
|
return BaseBoneTM * ComponentToWorld;
|
|
default:
|
|
return FTransform::Identity;
|
|
}
|
|
}
|
|
|
|
FVector WorldVectorToSpaceNoScale(ESimulationSpace Space, const FVector& WorldDir, const FTransform& ComponentToWorld, const FTransform& BaseBoneTM)
|
|
{
|
|
switch(Space)
|
|
{
|
|
case ESimulationSpace::ComponentSpace: return ComponentToWorld.InverseTransformVectorNoScale(WorldDir);
|
|
case ESimulationSpace::WorldSpace: return WorldDir;
|
|
case ESimulationSpace::BaseBoneSpace:
|
|
return BaseBoneTM.InverseTransformVectorNoScale(ComponentToWorld.InverseTransformVectorNoScale(WorldDir));
|
|
default: return FVector::ZeroVector;
|
|
}
|
|
}
|
|
|
|
FVector WorldPositionToSpace(ESimulationSpace Space, const FVector& WorldPoint, const FTransform& ComponentToWorld, const FTransform& BaseBoneTM)
|
|
{
|
|
switch (Space)
|
|
{
|
|
case ESimulationSpace::ComponentSpace: return ComponentToWorld.InverseTransformPosition(WorldPoint);
|
|
case ESimulationSpace::WorldSpace: return WorldPoint;
|
|
case ESimulationSpace::BaseBoneSpace:
|
|
return BaseBoneTM.InverseTransformPosition(ComponentToWorld.InverseTransformPosition(WorldPoint));
|
|
default: return FVector::ZeroVector;
|
|
}
|
|
}
|
|
|
|
FORCEINLINE_DEBUGGABLE FTransform ConvertCSTransformToSimSpace(ESimulationSpace SimulationSpace, const FTransform& InCSTransform, const FTransform& ComponentToWorld, const FTransform& BaseBoneTM)
|
|
{
|
|
switch (SimulationSpace)
|
|
{
|
|
case ESimulationSpace::ComponentSpace: return InCSTransform;
|
|
case ESimulationSpace::WorldSpace: return InCSTransform * ComponentToWorld;
|
|
case ESimulationSpace::BaseBoneSpace: return InCSTransform.GetRelativeTransform(BaseBoneTM); break;
|
|
default: ensureMsgf(false, TEXT("Unsupported Simulation Space")); return InCSTransform;
|
|
}
|
|
}
|
|
|
|
void FAnimNode_RigidBody::UpdateComponentPose_AnyThread(const FAnimationUpdateContext& Context)
|
|
{
|
|
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(UpdateComponentPose_AnyThread)
|
|
// Only freeze update graph after initial update, as we want to get that pose through.
|
|
if (bFreezeIncomingPoseOnStart && bSimulationStarted && ResetSimulatedTeleportType == ETeleportType::None)
|
|
{
|
|
// If we have a Frozen Pose captured,
|
|
// then we don't need to update the rest of the graph.
|
|
if (CapturedFrozenPose.GetPose().GetNumBones() > 0)
|
|
{
|
|
}
|
|
else
|
|
{
|
|
// Create a new context with zero deltatime to freeze time in rest of the graph.
|
|
// This will be used to capture a frozen pose.
|
|
FAnimationUpdateContext FrozenContext = Context.FractionalWeightAndTime(1.f, 0.f);
|
|
|
|
Super::UpdateComponentPose_AnyThread(FrozenContext);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Super::UpdateComponentPose_AnyThread(Context);
|
|
}
|
|
}
|
|
|
|
void FAnimNode_RigidBody::EvaluateComponentPose_AnyThread(FComponentSpacePoseContext& Output)
|
|
{
|
|
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(EvaluateComponentPose_AnyThread)
|
|
if (bFreezeIncomingPoseOnStart && bSimulationStarted)
|
|
{
|
|
// If we have a Frozen Pose captured, use it.
|
|
// Only after our intialize setup. As we need new pose for that.
|
|
if (ResetSimulatedTeleportType == ETeleportType::None && (CapturedFrozenPose.GetPose().GetNumBones() > 0))
|
|
{
|
|
Output.Pose.CopyPose(CapturedFrozenPose);
|
|
Output.Curve.CopyFrom(CapturedFrozenCurves);
|
|
}
|
|
// Otherwise eval graph to capture it.
|
|
else
|
|
{
|
|
Super::EvaluateComponentPose_AnyThread(Output);
|
|
CapturedFrozenPose.CopyPose(Output.Pose);
|
|
CapturedFrozenCurves.CopyFrom(Output.Curve);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Super::EvaluateComponentPose_AnyThread(Output);
|
|
}
|
|
|
|
// Capture incoming pose if 'bTransferBoneVelocities' is set.
|
|
// That is, until simulation starts.
|
|
if (bTransferBoneVelocities && !bSimulationStarted)
|
|
{
|
|
CapturedBoneVelocityPose.CopyPose(Output.Pose);
|
|
CapturedBoneVelocityPose.CopyAndAssignBoneContainer(CapturedBoneVelocityBoneContainer);
|
|
}
|
|
}
|
|
|
|
void FAnimNode_RigidBody::InitializeNewBodyTransformsDuringSimulation(FComponentSpacePoseContext& Output, const FTransform& ComponentTransform, const FTransform& BaseBoneTM)
|
|
{
|
|
for (const FOutputBoneData& OutputData : OutputBoneData)
|
|
{
|
|
const int32 BodyIndex = OutputData.BodyIndex;
|
|
FBodyAnimData& BodyData = BodyAnimData[BodyIndex];
|
|
if (!BodyData.bBodyTransformInitialized)
|
|
{
|
|
BodyData.bBodyTransformInitialized = true;
|
|
|
|
// If we have a parent body, we need to grab relative transforms to it.
|
|
if (OutputData.ParentBodyIndex != INDEX_NONE)
|
|
{
|
|
ensure(BodyAnimData[OutputData.ParentBodyIndex].bBodyTransformInitialized);
|
|
|
|
FTransform BodyRelativeTransform = FTransform::Identity;
|
|
for (const FCompactPoseBoneIndex& CompactBoneIndex : OutputData.BoneIndicesToParentBody)
|
|
{
|
|
const FTransform& LocalSpaceTM = Output.Pose.GetLocalSpaceTransform(CompactBoneIndex);
|
|
BodyRelativeTransform = BodyRelativeTransform * LocalSpaceTM;
|
|
}
|
|
|
|
const FTransform WSBodyTM = BodyRelativeTransform * Bodies[OutputData.ParentBodyIndex]->GetWorldTransform();
|
|
Bodies[BodyIndex]->InitWorldTransform(WSBodyTM);
|
|
BodyAnimData[BodyIndex].RefPoseLength = BodyRelativeTransform.GetLocation().Size();
|
|
}
|
|
// If we don't have a parent body, then we can just grab the incoming pose in component space.
|
|
else
|
|
{
|
|
const FTransform& ComponentSpaceTM = Output.Pose.GetComponentSpaceTransform(OutputData.CompactPoseBoneIndex);
|
|
const FTransform BodyTM = ConvertCSTransformToSimSpace(SimulationSpace, ComponentSpaceTM, ComponentTransform, BaseBoneTM);
|
|
|
|
Bodies[BodyIndex]->InitWorldTransform(BodyTM);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FAnimNode_RigidBody::InitSimulationSpace(
|
|
const FTransform& ComponentToWorld,
|
|
const FTransform& BoneToComponent)
|
|
{
|
|
SimSpacePreviousComponentToWorld = ComponentToWorld;
|
|
SimSpacePreviousBoneToComponent = BoneToComponent;
|
|
SimSpacePreviousComponentLinearVelocity = FVector::ZeroVector;
|
|
SimSpacePreviousComponentAngularVelocity = FVector::ZeroVector;
|
|
SimSpacePreviousBoneLinearVelocity = FVector::ZeroVector;
|
|
SimSpacePreviousBoneAngularVelocity = FVector::ZeroVector;
|
|
}
|
|
|
|
void FAnimNode_RigidBody::CalculateSimulationSpace(
|
|
ESimulationSpace Space,
|
|
const FTransform& ComponentToWorld,
|
|
const FTransform& BoneToComponent,
|
|
const float Dt,
|
|
const FSimSpaceSettings& Settings,
|
|
FTransform& SpaceTransform,
|
|
FVector& SpaceLinearVel,
|
|
FVector& SpaceAngularVel,
|
|
FVector& SpaceLinearAcc,
|
|
FVector& SpaceAngularAcc)
|
|
{
|
|
// World-space transform of the simulation space
|
|
SpaceTransform = SpaceToWorldTransform(Space, ComponentToWorld, BoneToComponent);
|
|
SpaceLinearVel = FVector::ZeroVector;
|
|
SpaceAngularVel = FVector::ZeroVector;
|
|
SpaceLinearAcc = FVector::ZeroVector;
|
|
SpaceAngularAcc = FVector::ZeroVector;
|
|
|
|
// If the system is disabled, nothing else to do
|
|
if ((Settings.MasterAlpha == 0.0f) || (Dt < SMALL_NUMBER))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Space == ESimulationSpace::WorldSpace)
|
|
{
|
|
SpaceLinearVel = Settings.ExternalLinearVelocity;
|
|
SpaceAngularVel = Settings.ExternalAngularVelocity;
|
|
return;
|
|
}
|
|
|
|
// World-space component velocity and acceleration
|
|
FVector CompLinVel = Chaos::FVec3::CalculateVelocity(SimSpacePreviousComponentToWorld.GetTranslation(), ComponentToWorld.GetTranslation(), Dt);
|
|
FVector CompAngVel = Chaos::FRotation3::CalculateAngularVelocity(SimSpacePreviousComponentToWorld.GetRotation(), ComponentToWorld.GetRotation(), Dt);
|
|
FVector CompLinAcc = (CompLinVel - SimSpacePreviousComponentLinearVelocity) / Dt;
|
|
FVector CompAngAcc = (CompAngVel - SimSpacePreviousComponentAngularVelocity) / Dt;
|
|
SimSpacePreviousComponentToWorld = ComponentToWorld;
|
|
SimSpacePreviousComponentLinearVelocity = CompLinVel;
|
|
SimSpacePreviousComponentAngularVelocity = CompAngVel;
|
|
|
|
if (Space == ESimulationSpace::ComponentSpace)
|
|
{
|
|
CompLinVel.Z *= Settings.VelocityScaleZ;
|
|
CompLinAcc.Z *= Settings.VelocityScaleZ;
|
|
|
|
SpaceLinearVel = CompLinVel.GetClampedToMaxSize(Settings.MaxLinearVelocity) + Settings.ExternalLinearVelocity;
|
|
SpaceAngularVel = CompAngVel.GetClampedToMaxSize(Settings.MaxAngularVelocity) + Settings.ExternalAngularVelocity;
|
|
SpaceLinearAcc = CompLinAcc.GetClampedToMaxSize(Settings.MaxLinearAcceleration);
|
|
SpaceAngularAcc = CompAngAcc.GetClampedToMaxSize(Settings.MaxAngularAcceleration);
|
|
return;
|
|
}
|
|
|
|
if (Space == ESimulationSpace::BaseBoneSpace)
|
|
{
|
|
// World-space component-relative bone velocity and acceleration
|
|
FVector BoneLinVel = Chaos::FVec3::CalculateVelocity(SimSpacePreviousBoneToComponent.GetTranslation(), BoneToComponent.GetTranslation(), Dt);
|
|
FVector BoneAngVel = Chaos::FRotation3::CalculateAngularVelocity(SimSpacePreviousBoneToComponent.GetRotation(), BoneToComponent.GetRotation(), Dt);
|
|
BoneLinVel = ComponentToWorld.TransformVector(BoneLinVel);
|
|
BoneAngVel = ComponentToWorld.TransformVector(BoneAngVel);
|
|
FVector BoneLinAcc = (BoneLinVel - SimSpacePreviousBoneLinearVelocity) / Dt;
|
|
FVector BoneAngAcc = (BoneAngVel - SimSpacePreviousBoneAngularVelocity) / Dt;
|
|
SimSpacePreviousBoneToComponent = BoneToComponent;
|
|
SimSpacePreviousBoneLinearVelocity = BoneLinVel;
|
|
SimSpacePreviousBoneAngularVelocity = BoneAngVel;
|
|
|
|
// World-space bone velocity and acceleration
|
|
FVector NetAngVel = CompAngVel + BoneAngVel;
|
|
FVector NetAngAcc = CompAngAcc + BoneAngAcc;
|
|
|
|
// If we limit the angular velocity, we also need to limit the component of linear velocity that comes from (angvel x offset)
|
|
float AngVelScale = 1.0f;
|
|
float NetAngVelLenSq = NetAngVel.SizeSquared();
|
|
if (NetAngVelLenSq > FMath::Square(Settings.MaxAngularVelocity))
|
|
{
|
|
AngVelScale = Settings.MaxAngularVelocity * FMath::InvSqrt(NetAngVelLenSq);
|
|
}
|
|
|
|
// Add the linear velocity and acceleration that comes from rotation of the space about the component
|
|
// NOTE: Component angular velocity constribution is scaled
|
|
FVector SpaceCompOffset = ComponentToWorld.TransformVector(BoneToComponent.GetTranslation());
|
|
FVector NetLinVel = CompLinVel + BoneLinVel + FVector::CrossProduct(AngVelScale * CompAngVel, SpaceCompOffset);
|
|
FVector NetLinAcc = CompLinAcc + BoneLinAcc + FVector::CrossProduct(AngVelScale * CompAngAcc, SpaceCompOffset);
|
|
|
|
NetLinVel.Z *= Settings.VelocityScaleZ;
|
|
NetLinAcc.Z *= Settings.VelocityScaleZ;
|
|
|
|
SpaceLinearVel = NetLinVel.GetClampedToMaxSize(Settings.MaxLinearVelocity) + Settings.ExternalLinearVelocity;
|
|
SpaceAngularVel = NetAngVel.GetClampedToMaxSize(Settings.MaxAngularVelocity) + Settings.ExternalAngularVelocity;
|
|
SpaceLinearAcc = NetLinAcc.GetClampedToMaxSize(Settings.MaxLinearAcceleration);
|
|
SpaceAngularAcc = NetAngAcc.GetClampedToMaxSize(Settings.MaxAngularAcceleration);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
DECLARE_CYCLE_STAT(TEXT("RigidBody_Eval"), STAT_RigidBody_Eval, STATGROUP_Anim);
|
|
DECLARE_CYCLE_STAT(TEXT("RigidBodyNode_Simulation"), STAT_RigidBodyNode_Simulation, STATGROUP_Anim);
|
|
DECLARE_CYCLE_STAT(TEXT("RigidBodyNode_SimulationWait"), STAT_RigidBodyNode_SimulationWait, STATGROUP_Anim);
|
|
DECLARE_CYCLE_STAT(TEXT("FAnimNode_RigidBody::EvaluateSkeletalControl_AnyThread"), STAT_ImmediateEvaluateSkeletalControl, STATGROUP_ImmediatePhysics);
|
|
|
|
void FAnimNode_RigidBody::RunPhysicsSimulation(float DeltaSeconds, const FVector& SimSpaceGravity)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RigidBodyNode_Simulation);
|
|
CSV_SCOPED_TIMING_STAT(Animation, RigidBodyNodeSimulation);
|
|
FScopeCycleCounterUObject AdditionalScope(UsePhysicsAsset, GET_STATID(STAT_RigidBodyNode_Simulation));
|
|
|
|
const int32 MaxSteps = RBAN_MaxSubSteps;
|
|
const float MaxDeltaSeconds = 1.f / 30.f;
|
|
#if !WITH_CHAOS
|
|
const int32 NumSteps = FMath::Clamp(FMath::CeilToInt(DeltaSeconds / MaxDeltaSeconds), 1, MaxSteps);
|
|
const float StepDeltaTime = DeltaSeconds / float(NumSteps);
|
|
for (int32 Step = 1; Step <= NumSteps; Step++)
|
|
{
|
|
// We call the _AssumesLocked version here without a lock as the simulation is local to this node and we know
|
|
// we're not going to alter anything while this is running.
|
|
PhysicsSimulation->Simulate_AssumesLocked(StepDeltaTime, SimSpaceGravity);
|
|
}
|
|
#else
|
|
PhysicsSimulation->Simulate_AssumesLocked(DeltaSeconds, MaxDeltaSeconds, MaxSteps, SimSpaceGravity);
|
|
#endif
|
|
}
|
|
|
|
void FAnimNode_RigidBody::FlushDeferredSimulationTask()
|
|
{
|
|
if (SimulationTask.IsValid() && !SimulationTask.IsCompleted())
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_RigidBodyNode_SimulationWait);
|
|
CSV_SCOPED_TIMING_STAT(Animation, RigidBodyNodeSimulationWait);
|
|
SimulationTask.Wait();
|
|
}
|
|
}
|
|
|
|
void FAnimNode_RigidBody::DestroyPhysicsSimulation()
|
|
{
|
|
FlushDeferredSimulationTask();
|
|
delete PhysicsSimulation;
|
|
PhysicsSimulation = nullptr;
|
|
}
|
|
|
|
void FAnimNode_RigidBody::EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray<FBoneTransform>& OutBoneTransforms)
|
|
{
|
|
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(EvaluateSkeletalControl_AnyThread)
|
|
SCOPE_CYCLE_COUNTER(STAT_RigidBody_Eval);
|
|
CSV_SCOPED_TIMING_STAT(Animation, RigidBodyEval);
|
|
SCOPE_CYCLE_COUNTER(STAT_ImmediateEvaluateSkeletalControl);
|
|
//SCOPED_NAMED_EVENT_TEXT("FAnimNode_RigidBody::EvaluateSkeletalControl_AnyThread", FColor::Magenta);
|
|
|
|
if (CVarEnableRigidBodyNodeSimulation.GetValueOnAnyThread() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const float DeltaSeconds = AccumulatedDeltaTime;
|
|
AccumulatedDeltaTime = 0.f;
|
|
|
|
if (bEnabled && PhysicsSimulation)
|
|
{
|
|
FlushDeferredSimulationTask();
|
|
|
|
const FBoneContainer& BoneContainer = Output.Pose.GetPose().GetBoneContainer();
|
|
const FTransform CompWorldSpaceTM = Output.AnimInstanceProxy->GetComponentTransform();
|
|
|
|
bool bFirstEvalSinceReset = !Output.AnimInstanceProxy->GetEvaluationCounter().HasEverBeenUpdated();
|
|
|
|
// First-frame initialization
|
|
if (bFirstEvalSinceReset)
|
|
{
|
|
PreviousCompWorldSpaceTM = CompWorldSpaceTM;
|
|
ResetSimulatedTeleportType = ETeleportType::ResetPhysics;
|
|
}
|
|
|
|
// See if we need to reset physics because too much time passed since our last update (e.g., because we we off-screen for a while),
|
|
// in which case the current sim state may be too far from the current anim pose. This is mostly a problem with world-space
|
|
// simulation, whereas bone- and component-space sims can be fairly robust against missing updates.
|
|
// Don't do this on first frame or if time-based reset is disabled.
|
|
if ((EvaluationResetTime > 0.0f) && !bFirstEvalSinceReset)
|
|
{
|
|
// NOTE: under normal conditions, when this anim node is being serviced at the usual rate (which may not be every frame
|
|
// if URO is enabled), we expect that WorldTimeSeconds == (LastEvalTimeSeconds + DeltaSeconds). DeltaSeconds is the
|
|
// accumulated time since the last update, including frames dropped by URO, but not frames dropped because of
|
|
// being off-screen or LOD changes.
|
|
if (WorldTimeSeconds - (LastEvalTimeSeconds + DeltaSeconds) > EvaluationResetTime)
|
|
{
|
|
UE_LOG(LogRBAN, Verbose, TEXT("%s Time-Based Reset"), *Output.AnimInstanceProxy->GetAnimInstanceName());
|
|
ResetSimulatedTeleportType = ETeleportType::ResetPhysics;
|
|
}
|
|
}
|
|
|
|
// Update the evaluation time to the current time
|
|
LastEvalTimeSeconds = WorldTimeSeconds;
|
|
|
|
// Disable simulation below minimum scale in world space mode. World space sim doesn't play nice with scale anyway - we do not scale joint offets or collision shapes.
|
|
if ((SimulationSpace == ESimulationSpace::WorldSpace) && (CompWorldSpaceTM.GetScale3D().SizeSquared() < WorldSpaceMinimumScale * WorldSpaceMinimumScale))
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FTransform BaseBoneTM = Output.Pose.GetComponentSpaceTransform(BaseBoneRef.GetCompactPoseIndex(BoneContainer));
|
|
|
|
// Initialize potential new bodies because of LOD change.
|
|
if (ResetSimulatedTeleportType == ETeleportType::None && bCheckForBodyTransformInit)
|
|
{
|
|
bCheckForBodyTransformInit = false;
|
|
InitializeNewBodyTransformsDuringSimulation(Output, CompWorldSpaceTM, BaseBoneTM);
|
|
}
|
|
|
|
// If time advances, update simulation
|
|
// Reset if necessary
|
|
bool bDynamicsReset = (ResetSimulatedTeleportType != ETeleportType::None);
|
|
if (bDynamicsReset)
|
|
{
|
|
// Capture bone velocities if we have captured a bone velocity pose.
|
|
if (bTransferBoneVelocities && (CapturedBoneVelocityPose.GetPose().GetNumBones() > 0))
|
|
{
|
|
for (const FOutputBoneData& OutputData : OutputBoneData)
|
|
{
|
|
const int32 BodyIndex = OutputData.BodyIndex;
|
|
FBodyAnimData& BodyData = BodyAnimData[BodyIndex];
|
|
|
|
if (BodyData.bIsSimulated)
|
|
{
|
|
const FCompactPoseBoneIndex NextCompactPoseBoneIndex = OutputData.CompactPoseBoneIndex;
|
|
// Convert CompactPoseBoneIndex to SkeletonBoneIndex...
|
|
const FSkeletonPoseBoneIndex PoseSkeletonBoneIndex = BoneContainer.GetSkeletonPoseIndexFromCompactPoseIndex(NextCompactPoseBoneIndex);
|
|
// ... So we can convert to the captured pose CompactPoseBoneIndex.
|
|
// In case there was a LOD change, and poses are not compatible anymore.
|
|
const FCompactPoseBoneIndex PrevCompactPoseBoneIndex = CapturedBoneVelocityBoneContainer.GetCompactPoseIndexFromSkeletonPoseIndex(PoseSkeletonBoneIndex);
|
|
|
|
if (PrevCompactPoseBoneIndex.IsValid())
|
|
{
|
|
const FTransform PrevCSTM = CapturedBoneVelocityPose.GetComponentSpaceTransform(PrevCompactPoseBoneIndex);
|
|
const FTransform NextCSTM = Output.Pose.GetComponentSpaceTransform(NextCompactPoseBoneIndex);
|
|
|
|
const FTransform PrevSSTM = ConvertCSTransformToSimSpace(SimulationSpace, PrevCSTM, CompWorldSpaceTM, BaseBoneTM);
|
|
const FTransform NextSSTM = ConvertCSTransformToSimSpace(SimulationSpace, NextCSTM, CompWorldSpaceTM, BaseBoneTM);
|
|
|
|
if(DeltaSeconds > 0.0f)
|
|
{
|
|
// Linear Velocity
|
|
BodyData.TransferedBoneLinearVelocity = ((NextSSTM.GetLocation() - PrevSSTM.GetLocation()) / DeltaSeconds);
|
|
|
|
// Angular Velocity
|
|
const FQuat DeltaRotation = (NextSSTM.GetRotation().Inverse() * PrevSSTM.GetRotation());
|
|
const float RotationAngle = DeltaRotation.GetAngle() / DeltaSeconds;
|
|
BodyData.TransferedBoneAngularVelocity = (FQuat(DeltaRotation.GetRotationAxis(), RotationAngle));
|
|
}
|
|
else
|
|
{
|
|
BodyData.TransferedBoneLinearVelocity = (FVector::ZeroVector);
|
|
BodyData.TransferedBoneAngularVelocity = (FQuat::Identity);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
switch(ResetSimulatedTeleportType)
|
|
{
|
|
case ETeleportType::TeleportPhysics:
|
|
{
|
|
UE_LOG(LogRBAN, Verbose, TEXT("%s TeleportPhysics (Scale: %f %f %f)"), *Output.AnimInstanceProxy->GetAnimInstanceName(), CompWorldSpaceTM.GetScale3D().X, CompWorldSpaceTM.GetScale3D().Y, CompWorldSpaceTM.GetScale3D().Z);
|
|
|
|
// Teleport bodies.
|
|
for (const FOutputBoneData& OutputData : OutputBoneData)
|
|
{
|
|
const int32 BodyIndex = OutputData.BodyIndex;
|
|
BodyAnimData[BodyIndex].bBodyTransformInitialized = true;
|
|
|
|
FTransform BodyTM = Bodies[BodyIndex]->GetWorldTransform();
|
|
FTransform ComponentSpaceTM;
|
|
|
|
switch(SimulationSpace)
|
|
{
|
|
case ESimulationSpace::ComponentSpace: ComponentSpaceTM = BodyTM; break;
|
|
case ESimulationSpace::WorldSpace: ComponentSpaceTM = BodyTM.GetRelativeTransform(PreviousCompWorldSpaceTM); break;
|
|
case ESimulationSpace::BaseBoneSpace: ComponentSpaceTM = BodyTM * BaseBoneTM; break;
|
|
default: ensureMsgf(false, TEXT("Unsupported Simulation Space")); ComponentSpaceTM = BodyTM;
|
|
}
|
|
|
|
BodyTM = ConvertCSTransformToSimSpace(SimulationSpace, ComponentSpaceTM, CompWorldSpaceTM, BaseBoneTM);
|
|
Bodies[BodyIndex]->SetWorldTransform(BodyTM);
|
|
if (OutputData.ParentBodyIndex != INDEX_NONE)
|
|
{
|
|
BodyAnimData[BodyIndex].RefPoseLength = BodyTM.GetRelativeTransform(Bodies[OutputData.ParentBodyIndex]->GetWorldTransform()).GetLocation().Size();
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ETeleportType::ResetPhysics:
|
|
{
|
|
UE_LOG(LogRBAN, Verbose, TEXT("%s ResetPhysics (Scale: %f %f %f)"), *Output.AnimInstanceProxy->GetAnimInstanceName(), CompWorldSpaceTM.GetScale3D().X, CompWorldSpaceTM.GetScale3D().Y, CompWorldSpaceTM.GetScale3D().Z);
|
|
|
|
InitSimulationSpace(CompWorldSpaceTM, BaseBoneTM);
|
|
|
|
// Completely reset bodies.
|
|
for (const FOutputBoneData& OutputData : OutputBoneData)
|
|
{
|
|
const int32 BodyIndex = OutputData.BodyIndex;
|
|
BodyAnimData[BodyIndex].bBodyTransformInitialized = true;
|
|
|
|
const FTransform& ComponentSpaceTM = Output.Pose.GetComponentSpaceTransform(OutputData.CompactPoseBoneIndex);
|
|
const FTransform BodyTM = ConvertCSTransformToSimSpace(SimulationSpace, ComponentSpaceTM, CompWorldSpaceTM, BaseBoneTM);
|
|
Bodies[BodyIndex]->InitWorldTransform(BodyTM);
|
|
if (OutputData.ParentBodyIndex != INDEX_NONE)
|
|
{
|
|
BodyAnimData[BodyIndex].RefPoseLength = BodyTM.GetRelativeTransform(Bodies[OutputData.ParentBodyIndex]->GetWorldTransform()).GetLocation().Size();
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Always reset after a teleport
|
|
PreviousCompWorldSpaceTM = CompWorldSpaceTM;
|
|
ResetSimulatedTeleportType = ETeleportType::None;
|
|
PreviousComponentLinearVelocity = FVector::ZeroVector;
|
|
}
|
|
|
|
// Assets can override config for deferred simulation
|
|
const bool bUseDeferredSimulationTask =
|
|
(SimulationTiming == ESimulationTiming::Deferred) ||
|
|
((SimulationTiming == ESimulationTiming::Default) && bRBAN_DeferredSimulationDefault);
|
|
FVector SimSpaceGravity(0.f);
|
|
|
|
// Only need to tick physics if we didn't reset and we have some time to simulate
|
|
const bool bNeedsSimulationTick = ((bSimulateAnimPhysicsAfterReset || !bDynamicsReset) && DeltaSeconds > AnimPhysicsMinDeltaTime);
|
|
if (bNeedsSimulationTick)
|
|
{
|
|
// Transfer bone velocities previously captured.
|
|
if (bTransferBoneVelocities && (CapturedBoneVelocityPose.GetPose().GetNumBones() > 0))
|
|
{
|
|
for (const FOutputBoneData& OutputData : OutputBoneData)
|
|
{
|
|
const int32 BodyIndex = OutputData.BodyIndex;
|
|
const FBodyAnimData& BodyData = BodyAnimData[BodyIndex];
|
|
|
|
if (BodyData.bIsSimulated)
|
|
{
|
|
ImmediatePhysics::FActorHandle* Body = Bodies[BodyIndex];
|
|
Body->SetLinearVelocity(BodyData.TransferedBoneLinearVelocity);
|
|
|
|
const FQuat AngularVelocity = BodyData.TransferedBoneAngularVelocity;
|
|
Body->SetAngularVelocity(AngularVelocity.GetRotationAxis() * AngularVelocity.GetAngle());
|
|
}
|
|
}
|
|
|
|
// Free up our captured pose after it's been used.
|
|
CapturedBoneVelocityPose.Empty();
|
|
}
|
|
else if ((SimulationSpace != ESimulationSpace::WorldSpace) && bRBAN_EnableComponentAcceleration)
|
|
{
|
|
if (!ComponentLinearVelScale.IsNearlyZero() || !ComponentLinearAccScale.IsNearlyZero())
|
|
{
|
|
// Calc linear velocity
|
|
const FVector ComponentDeltaLocation = CurrentTransform.GetTranslation() - PreviousTransform.GetTranslation();
|
|
const FVector ComponentLinearVelocity = ComponentDeltaLocation / DeltaSeconds;
|
|
// Apply acceleration that opposed velocity (basically 'drag')
|
|
FVector ApplyLinearAcc = WorldVectorToSpaceNoScale(SimulationSpace, -ComponentLinearVelocity, CompWorldSpaceTM, BaseBoneTM) * ComponentLinearVelScale;
|
|
|
|
// Calc linear acceleration
|
|
const FVector ComponentLinearAcceleration = (ComponentLinearVelocity - PreviousComponentLinearVelocity) / DeltaSeconds;
|
|
PreviousComponentLinearVelocity = ComponentLinearVelocity;
|
|
// Apply opposite acceleration to bodies
|
|
ApplyLinearAcc += WorldVectorToSpaceNoScale(SimulationSpace, -ComponentLinearAcceleration, CompWorldSpaceTM, BaseBoneTM) * ComponentLinearAccScale;
|
|
|
|
// Iterate over bodies
|
|
for (const FOutputBoneData& OutputData : OutputBoneData)
|
|
{
|
|
const int32 BodyIndex = OutputData.BodyIndex;
|
|
const FBodyAnimData& BodyData = BodyAnimData[BodyIndex];
|
|
|
|
if (BodyData.bIsSimulated)
|
|
{
|
|
ImmediatePhysics::FActorHandle* Body = Bodies[BodyIndex];
|
|
|
|
// Apply
|
|
const float BodyInvMass = Body->GetInverseMass();
|
|
if (BodyInvMass > 0.f)
|
|
{
|
|
// Final desired acceleration to apply to body
|
|
FVector FinalBodyLinearAcc = ApplyLinearAcc;
|
|
|
|
// Clamp if desired
|
|
if (!ComponentAppliedLinearAccClamp.IsNearlyZero())
|
|
{
|
|
FinalBodyLinearAcc = FinalBodyLinearAcc.BoundToBox(-ComponentAppliedLinearAccClamp, ComponentAppliedLinearAccClamp);
|
|
}
|
|
|
|
// Apply to body
|
|
Body->AddForce(FinalBodyLinearAcc / BodyInvMass);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// @todo(ccaulfield): We should be interpolating kinematic targets for each sub-step below
|
|
for (const FOutputBoneData& OutputData : OutputBoneData)
|
|
{
|
|
const int32 BodyIndex = OutputData.BodyIndex;
|
|
if (!BodyAnimData[BodyIndex].bIsSimulated)
|
|
{
|
|
const FTransform& ComponentSpaceTM = Output.Pose.GetComponentSpaceTransform(OutputData.CompactPoseBoneIndex);
|
|
const FTransform BodyTM = ConvertCSTransformToSimSpace(SimulationSpace, ComponentSpaceTM, CompWorldSpaceTM, BaseBoneTM);
|
|
|
|
Bodies[BodyIndex]->SetKinematicTarget(BodyTM);
|
|
}
|
|
}
|
|
|
|
UpdateWorldForces(CompWorldSpaceTM, BaseBoneTM, DeltaSeconds);
|
|
SimSpaceGravity = WorldVectorToSpaceNoScale(SimulationSpace, WorldSpaceGravity, CompWorldSpaceTM, BaseBoneTM);
|
|
|
|
#if WITH_CHAOS
|
|
FSimSpaceSettings* UseSimSpaceSettings = &SimSpaceSettings;
|
|
if (bRBAN_SimSpace_EnableOverride)
|
|
{
|
|
UseSimSpaceSettings = &RBAN_SimSpaceOverride;
|
|
}
|
|
|
|
FTransform SimulationTransform;
|
|
FVector SimulationLinearVelocity;
|
|
FVector SimulationAngularVelocity;
|
|
FVector SimulationLinearAcceleration;
|
|
FVector SimulationAngularAcceleration;
|
|
CalculateSimulationSpace(
|
|
SimulationSpace,
|
|
CompWorldSpaceTM,
|
|
BaseBoneTM,
|
|
DeltaSeconds,
|
|
*UseSimSpaceSettings,
|
|
SimulationTransform,
|
|
SimulationLinearVelocity,
|
|
SimulationAngularVelocity,
|
|
SimulationLinearAcceleration,
|
|
SimulationAngularAcceleration);
|
|
|
|
UpdateWorldObjects(SimulationTransform);
|
|
|
|
PhysicsSimulation->UpdateSimulationSpace(
|
|
SimulationTransform,
|
|
SimulationLinearVelocity,
|
|
SimulationAngularVelocity,
|
|
SimulationLinearAcceleration,
|
|
SimulationAngularAcceleration);
|
|
|
|
PhysicsSimulation->SetSimulationSpaceSettings(
|
|
UseSimSpaceSettings->MasterAlpha,
|
|
UseSimSpaceSettings->ExternalLinearDragV);
|
|
|
|
PhysicsSimulation->SetSolverSettings(
|
|
SolverSettings.FixedTimeStep,
|
|
SolverSettings.CullDistance,
|
|
SolverSettings.MaxDepenetrationVelocity,
|
|
SolverSettings.PositionIterations,
|
|
SolverSettings.VelocityIterations,
|
|
SolverSettings.ProjectionIterations);
|
|
|
|
PhysicsSimulation->SetLegacySolverSettings(
|
|
SolverIterations.SolverIterations,
|
|
SolverIterations.JointIterations,
|
|
SolverIterations.CollisionIterations,
|
|
SolverIterations.SolverPushOutIterations,
|
|
SolverIterations.JointPushOutIterations,
|
|
SolverIterations.CollisionPushOutIterations);
|
|
#endif
|
|
|
|
if (!bUseDeferredSimulationTask)
|
|
{
|
|
RunPhysicsSimulation(DeltaSeconds, SimSpaceGravity);
|
|
}
|
|
|
|
#if WITH_CHAOS
|
|
// Draw here even if the simulation is deferred since we want the shapes drawn relative to the current transform
|
|
if (bRBAN_DebugDraw)
|
|
{
|
|
PhysicsSimulation->DebugDraw();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//write back to animation system
|
|
const FTransform& SimulationWorldSpaceTM = bUseDeferredSimulationTask ? PreviousCompWorldSpaceTM : CompWorldSpaceTM;
|
|
for (const FOutputBoneData& OutputData : OutputBoneData)
|
|
{
|
|
const int32 BodyIndex = OutputData.BodyIndex;
|
|
if (BodyAnimData[BodyIndex].bIsSimulated)
|
|
{
|
|
FTransform BodyTM = Bodies[BodyIndex]->GetWorldTransform();
|
|
|
|
// if we clamp translation, we only do this when all linear translation are locked
|
|
//
|
|
// @todo(ccaulfield): this shouldn't be required with Chaos - projection should be handling it...
|
|
if (bClampLinearTranslationLimitToRefPose
|
|
&&BodyAnimData[BodyIndex].LinearXMotion == ELinearConstraintMotion::LCM_Locked
|
|
&& BodyAnimData[BodyIndex].LinearYMotion == ELinearConstraintMotion::LCM_Locked
|
|
&& BodyAnimData[BodyIndex].LinearZMotion == ELinearConstraintMotion::LCM_Locked)
|
|
{
|
|
// grab local space of length from ref pose
|
|
// we have linear limit value - see if that works
|
|
// calculate current local space from parent
|
|
// find parent transform
|
|
const int32 ParentBodyIndex = OutputData.ParentBodyIndex;
|
|
FTransform ParentTransform = FTransform::Identity;
|
|
if (ParentBodyIndex != INDEX_NONE)
|
|
{
|
|
ParentTransform = Bodies[ParentBodyIndex]->GetWorldTransform();
|
|
}
|
|
|
|
// get local transform
|
|
FTransform LocalTransform = BodyTM.GetRelativeTransform(ParentTransform);
|
|
const float CurrentLength = LocalTransform.GetTranslation().Size();
|
|
|
|
// this is inconsistent with constraint. The actual linear limit is set by constraint
|
|
if (!FMath::IsNearlyEqual(CurrentLength, BodyAnimData[BodyIndex].RefPoseLength, KINDA_SMALL_NUMBER))
|
|
{
|
|
float RefPoseLength = BodyAnimData[BodyIndex].RefPoseLength;
|
|
if (CurrentLength > RefPoseLength)
|
|
{
|
|
float Scale = (CurrentLength > KINDA_SMALL_NUMBER) ? RefPoseLength / CurrentLength : 0.f;
|
|
// we don't use 1.f here because 1.f can create pops based on float issue.
|
|
// so we only activate clamping when less than 90%
|
|
if (Scale < 0.9f)
|
|
{
|
|
LocalTransform.ScaleTranslation(Scale);
|
|
BodyTM = LocalTransform * ParentTransform;
|
|
Bodies[BodyIndex]->SetWorldTransform(BodyTM);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FTransform ComponentSpaceTM;
|
|
|
|
switch(SimulationSpace)
|
|
{
|
|
case ESimulationSpace::ComponentSpace: ComponentSpaceTM = BodyTM; break;
|
|
case ESimulationSpace::WorldSpace: ComponentSpaceTM = BodyTM.GetRelativeTransform(SimulationWorldSpaceTM); break;
|
|
case ESimulationSpace::BaseBoneSpace: ComponentSpaceTM = BodyTM * BaseBoneTM; break;
|
|
default: ensureMsgf(false, TEXT("Unsupported Simulation Space")); ComponentSpaceTM = BodyTM;
|
|
}
|
|
|
|
OutBoneTransforms.Add(FBoneTransform(OutputData.CompactPoseBoneIndex, ComponentSpaceTM));
|
|
}
|
|
}
|
|
|
|
// Deferred task must be started after we read actor poses to avoid a race
|
|
if (bNeedsSimulationTick && bUseDeferredSimulationTask)
|
|
{
|
|
// FlushDeferredSimulationTask() should have already ensured task is done.
|
|
ensure(SimulationTask.IsCompleted());
|
|
const int32 PriorityIndex = FMath::Clamp<int32>(GRigidBodyNodeSimulationTaskPriority, 0, UE_ARRAY_COUNT(GRigidBodyNodeTaskPriorities) - 1);
|
|
const UE::Tasks::ETaskPriority TaskPriority = GRigidBodyNodeTaskPriorities[PriorityIndex];
|
|
SimulationTask = UE::Tasks::Launch(
|
|
TEXT("RigidBodyNodeSimulationTask"),
|
|
[this, DeltaSeconds, SimSpaceGravity] { RunPhysicsSimulation(DeltaSeconds, SimSpaceGravity); },
|
|
TaskPriority);
|
|
}
|
|
|
|
PreviousCompWorldSpaceTM = CompWorldSpaceTM;
|
|
}
|
|
}
|
|
|
|
void ComputeBodyInsertionOrder(TArray<FBoneIndexType>& InsertionOrder, const USkeletalMeshComponent& SKC)
|
|
{
|
|
//We want to ensure simulated bodies are sorted by LOD so that the first simulated bodies are at the highest LOD.
|
|
//Since LOD2 is a subset of LOD1 which is a subset of LOD0 we can change the number of simulated bodies without any reordering
|
|
//For this to work we must first insert all simulated bodies in the right order. We then insert all the kinematic bodies in the right order
|
|
|
|
InsertionOrder.Reset();
|
|
|
|
if (SKC.SkeletalMesh == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const int32 NumLODs = SKC.GetNumLODs();
|
|
if(NumLODs > 0)
|
|
{
|
|
TArray<FBoneIndexType> RequiredBones0;
|
|
TArray<FBoneIndexType> ComponentSpaceTMs0;
|
|
SKC.ComputeRequiredBones(RequiredBones0, ComponentSpaceTMs0, 0, /*bIgnorePhysicsAsset=*/ true);
|
|
|
|
TArray<bool> InSortedOrder;
|
|
InSortedOrder.AddZeroed(SKC.SkeletalMesh->GetRefSkeleton().GetNum());
|
|
|
|
auto MergeIndices = [&InsertionOrder, &InSortedOrder](const TArray<FBoneIndexType>& RequiredBones) -> void
|
|
{
|
|
for (FBoneIndexType BoneIdx : RequiredBones)
|
|
{
|
|
if (!InSortedOrder[BoneIdx])
|
|
{
|
|
InsertionOrder.Add(BoneIdx);
|
|
}
|
|
|
|
InSortedOrder[BoneIdx] = true;
|
|
}
|
|
};
|
|
|
|
|
|
for(int32 LodIdx = NumLODs - 1; LodIdx > 0; --LodIdx)
|
|
{
|
|
TArray<FBoneIndexType> RequiredBones;
|
|
TArray<FBoneIndexType> ComponentSpaceTMs;
|
|
SKC.ComputeRequiredBones(RequiredBones, ComponentSpaceTMs, LodIdx, /*bIgnorePhysicsAsset=*/ true);
|
|
MergeIndices(RequiredBones);
|
|
}
|
|
|
|
MergeIndices(RequiredBones0);
|
|
}
|
|
}
|
|
|
|
void FAnimNode_RigidBody::InitPhysics(const UAnimInstance* InAnimInstance)
|
|
{
|
|
LLM_SCOPE_BYNAME(TEXT("Animation/RigidBody"));
|
|
|
|
SCOPE_CYCLE_COUNTER(STAT_RigidBodyNodeInitTime);
|
|
|
|
DestroyPhysicsSimulation();
|
|
|
|
const USkeletalMeshComponent* SkeletalMeshComp = InAnimInstance->GetSkelMeshComponent();
|
|
const USkeletalMesh* SkeletalMeshAsset = SkeletalMeshComp->SkeletalMesh;
|
|
USkeleton* SkeletonAsset = InAnimInstance->CurrentSkeleton;
|
|
|
|
if(!SkeletalMeshAsset || !SkeletonAsset)
|
|
{
|
|
// Without both the skeleton and the mesh we can't create a new simulation.
|
|
// The previous simulation has just been cleaned up above so we can return early here and not instantiate a new one
|
|
return;
|
|
}
|
|
|
|
const FReferenceSkeleton& SkelMeshRefSkel = SkeletalMeshAsset->GetRefSkeleton();
|
|
UsePhysicsAsset = OverridePhysicsAsset ? ToRawPtr(OverridePhysicsAsset) : InAnimInstance->GetSkelMeshComponent()->GetPhysicsAsset();
|
|
|
|
ensure(SkeletonAsset == SkeletalMeshAsset->GetSkeleton());
|
|
|
|
const int32 SkelMeshLinkupIndex = SkeletonAsset->GetMeshLinkupIndex(SkeletalMeshAsset);
|
|
ensure(SkelMeshLinkupIndex != INDEX_NONE);
|
|
const FSkeletonToMeshLinkup& SkeletonToMeshLinkupTable = SkeletonAsset->LinkupCache[SkelMeshLinkupIndex];
|
|
const TArray<int32>& MeshToSkeletonBoneIndex = SkeletonToMeshLinkupTable.MeshToSkeletonTable;
|
|
|
|
const int32 NumSkeletonBones = SkeletonAsset->GetReferenceSkeleton().GetNum();
|
|
SkeletonBoneIndexToBodyIndex.Reset(NumSkeletonBones);
|
|
SkeletonBoneIndexToBodyIndex.Init(INDEX_NONE, NumSkeletonBones);
|
|
|
|
PreviousTransform = SkeletalMeshComp->GetComponentToWorld();
|
|
|
|
ComponentsInSim.Reset();
|
|
ComponentsInSimTick = 0;
|
|
|
|
if (UPhysicsSettings* Settings = UPhysicsSettings::Get())
|
|
{
|
|
AnimPhysicsMinDeltaTime = Settings->AnimPhysicsMinDeltaTime;
|
|
bSimulateAnimPhysicsAfterReset = Settings->bSimulateAnimPhysicsAfterReset;
|
|
}
|
|
else
|
|
{
|
|
AnimPhysicsMinDeltaTime = 0.f;
|
|
bSimulateAnimPhysicsAfterReset = false;
|
|
}
|
|
|
|
bEnabled = UsePhysicsAsset && SkeletalMeshComp->GetAllowRigidBodyAnimNode() && CVarEnableRigidBodyNode.GetValueOnAnyThread() != 0;
|
|
if(bEnabled)
|
|
{
|
|
PhysicsSimulation = new ImmediatePhysics::FSimulation();
|
|
const int32 NumBodies = UsePhysicsAsset->SkeletalBodySetups.Num();
|
|
Bodies.Empty(NumBodies);
|
|
BodyAnimData.Reset(NumBodies);
|
|
BodyAnimData.AddDefaulted(NumBodies);
|
|
TotalMass = 0.f;
|
|
|
|
// Instantiate a FBodyInstance/FConstraintInstance set that will be cloned into the Immediate Physics sim.
|
|
// NOTE: We do not have a skeleton at the moment, so we have to use the ref pose
|
|
TArray<FBodyInstance*> HighLevelBodyInstances;
|
|
TArray<FConstraintInstance*> HighLevelConstraintInstances;
|
|
|
|
// Chaos relies on the initial pose to set up constraint positions
|
|
bool bCreateBodiesInRefPose = (WITH_CHAOS != 0);
|
|
SkeletalMeshComp->InstantiatePhysicsAssetRefPose(
|
|
*UsePhysicsAsset,
|
|
SimulationSpace == ESimulationSpace::WorldSpace ? SkeletalMeshComp->GetComponentToWorld().GetScale3D() : FVector(1.f),
|
|
HighLevelBodyInstances,
|
|
HighLevelConstraintInstances,
|
|
nullptr,
|
|
nullptr,
|
|
INDEX_NONE,
|
|
FPhysicsAggregateHandle(),
|
|
bCreateBodiesInRefPose);
|
|
|
|
TMap<FName, ImmediatePhysics::FActorHandle*> NamesToHandles;
|
|
TArray<ImmediatePhysics::FActorHandle*> IgnoreCollisionActors;
|
|
|
|
TArray<FBoneIndexType> InsertionOrder;
|
|
ComputeBodyInsertionOrder(InsertionOrder, *SkeletalMeshComp);
|
|
|
|
// NOTE: NumBonesLOD0 may be less than NumBonesTotal, and it may be middle bones that are missing from LOD0.
|
|
// In this case, LOD0 bone indices may be >= NumBonesLOD0, but always < NumBonesTotal. Arrays indexed by
|
|
// bone index must be size NumBonesTotal.
|
|
const int32 NumBonesLOD0 = InsertionOrder.Num();
|
|
const int32 NumBonesTotal = SkelMeshRefSkel.GetNum();
|
|
|
|
// If our skeleton is not the one that was used to build the PhysicsAsset, some bodies may be missing, or rearranged.
|
|
// We need to map the original indices to the new bodies for use by the CollisionDisableTable.
|
|
// NOTE: This array is indexed by the original BodyInstance body index (BodyInstance->InstanceBodyIndex)
|
|
TArray<ImmediatePhysics::FActorHandle*> BodyIndexToActorHandle;
|
|
BodyIndexToActorHandle.AddZeroed(HighLevelBodyInstances.Num());
|
|
|
|
TArray<FBodyInstance*> BodiesSorted;
|
|
BodiesSorted.AddZeroed(NumBonesTotal);
|
|
|
|
for (FBodyInstance* BI : HighLevelBodyInstances)
|
|
{
|
|
if(BI->IsValidBodyInstance())
|
|
{
|
|
BodiesSorted[BI->InstanceBoneIndex] = BI;
|
|
}
|
|
}
|
|
|
|
// Create the immediate physics bodies
|
|
for (FBoneIndexType InsertBone : InsertionOrder)
|
|
{
|
|
if (FBodyInstance* BodyInstance = BodiesSorted[InsertBone])
|
|
{
|
|
UBodySetup* BodySetup = UsePhysicsAsset->SkeletalBodySetups[BodyInstance->InstanceBodyIndex];
|
|
|
|
bool bSimulated = (BodySetup->PhysicsType == EPhysicsType::PhysType_Simulated);
|
|
ImmediatePhysics::EActorType ActorType = bSimulated ? ImmediatePhysics::EActorType::DynamicActor : ImmediatePhysics::EActorType::KinematicActor;
|
|
ImmediatePhysics::FActorHandle* NewBodyHandle = PhysicsSimulation->CreateActor(ActorType, BodyInstance, BodyInstance->GetUnrealWorldTransform());
|
|
if (NewBodyHandle)
|
|
{
|
|
if (bSimulated)
|
|
{
|
|
const float InvMass = NewBodyHandle->GetInverseMass();
|
|
TotalMass += InvMass > 0.f ? 1.f / InvMass : 0.f;
|
|
}
|
|
const int32 BodyIndex = Bodies.Add(NewBodyHandle);
|
|
const int32 SkeletonBoneIndex = MeshToSkeletonBoneIndex[InsertBone];
|
|
SkeletonBoneIndexToBodyIndex[SkeletonBoneIndex] = BodyIndex;
|
|
BodyAnimData[BodyIndex].bIsSimulated = bSimulated;
|
|
NamesToHandles.Add(BodySetup->BoneName, NewBodyHandle);
|
|
BodyIndexToActorHandle[BodyInstance->InstanceBodyIndex] = NewBodyHandle;
|
|
|
|
if (BodySetup->CollisionReponse == EBodyCollisionResponse::BodyCollision_Disabled)
|
|
{
|
|
IgnoreCollisionActors.Add(NewBodyHandle);
|
|
}
|
|
|
|
#if WITH_CHAOS
|
|
NewBodyHandle->SetName(BodySetup->BoneName);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
//Insert joints so that they coincide body order. That is, if we stop simulating all bodies past some index, we can simply ignore joints past a corresponding index without any re-order
|
|
//For this to work we consider the most last inserted bone in each joint
|
|
TArray<int32> InsertionOrderPerBone;
|
|
InsertionOrderPerBone.AddUninitialized(NumBonesTotal);
|
|
|
|
for(int32 Position = 0; Position < NumBonesLOD0; ++Position)
|
|
{
|
|
InsertionOrderPerBone[InsertionOrder[Position]] = Position;
|
|
}
|
|
|
|
HighLevelConstraintInstances.Sort([&InsertionOrderPerBone, &SkelMeshRefSkel](const FConstraintInstance& LHS, const FConstraintInstance& RHS)
|
|
{
|
|
if(LHS.IsValidConstraintInstance() && RHS.IsValidConstraintInstance())
|
|
{
|
|
const int32 BoneIdxLHS1 = SkelMeshRefSkel.FindBoneIndex(LHS.ConstraintBone1);
|
|
const int32 BoneIdxLHS2 = SkelMeshRefSkel.FindBoneIndex(LHS.ConstraintBone2);
|
|
|
|
const int32 BoneIdxRHS1 = SkelMeshRefSkel.FindBoneIndex(RHS.ConstraintBone1);
|
|
const int32 BoneIdxRHS2 = SkelMeshRefSkel.FindBoneIndex(RHS.ConstraintBone2);
|
|
|
|
const int32 MaxPositionLHS = FMath::Max(InsertionOrderPerBone[BoneIdxLHS1], InsertionOrderPerBone[BoneIdxLHS2]);
|
|
const int32 MaxPositionRHS = FMath::Max(InsertionOrderPerBone[BoneIdxRHS1], InsertionOrderPerBone[BoneIdxRHS2]);
|
|
|
|
return MaxPositionLHS < MaxPositionRHS;
|
|
}
|
|
|
|
return false;
|
|
});
|
|
|
|
|
|
TArray<ImmediatePhysics::FSimulation::FIgnorePair> IgnorePairs;
|
|
if(NamesToHandles.Num() > 0)
|
|
{
|
|
//constraints
|
|
for(int32 ConstraintIdx = 0; ConstraintIdx < HighLevelConstraintInstances.Num(); ++ConstraintIdx)
|
|
{
|
|
FConstraintInstance* CI = HighLevelConstraintInstances[ConstraintIdx];
|
|
ImmediatePhysics::FActorHandle* Body1Handle = NamesToHandles.FindRef(CI->ConstraintBone1);
|
|
ImmediatePhysics::FActorHandle* Body2Handle = NamesToHandles.FindRef(CI->ConstraintBone2);
|
|
|
|
if(Body1Handle && Body2Handle)
|
|
{
|
|
if (Body1Handle->IsSimulated() || Body2Handle->IsSimulated())
|
|
{
|
|
PhysicsSimulation->CreateJoint(CI, Body1Handle, Body2Handle);
|
|
if (bForceDisableCollisionBetweenConstraintBodies)
|
|
{
|
|
int32 BodyIndex1 = UsePhysicsAsset->FindBodyIndex(CI->ConstraintBone1);
|
|
int32 BodyIndex2 = UsePhysicsAsset->FindBodyIndex(CI->ConstraintBone2);
|
|
if (BodyIndex1 != INDEX_NONE && BodyIndex2 != INDEX_NONE)
|
|
{
|
|
UsePhysicsAsset->DisableCollision(BodyIndex1, BodyIndex2);
|
|
}
|
|
}
|
|
|
|
int32 BodyIndex;
|
|
if (Bodies.Find(Body1Handle, BodyIndex))
|
|
{
|
|
BodyAnimData[BodyIndex].LinearXMotion = CI->GetLinearXMotion();
|
|
BodyAnimData[BodyIndex].LinearYMotion = CI->GetLinearYMotion();
|
|
BodyAnimData[BodyIndex].LinearZMotion = CI->GetLinearZMotion();
|
|
BodyAnimData[BodyIndex].LinearLimit = CI->GetLinearLimit();
|
|
|
|
//set limit to ref pose
|
|
FTransform Body1Transform = Body1Handle->GetWorldTransform();
|
|
FTransform Body2Transform = Body2Handle->GetWorldTransform();
|
|
BodyAnimData[BodyIndex].RefPoseLength = Body1Transform.GetRelativeTransform(Body2Transform).GetLocation().Size();
|
|
}
|
|
|
|
if (CI->IsCollisionDisabled())
|
|
{
|
|
ImmediatePhysics::FSimulation::FIgnorePair Pair;
|
|
Pair.A = Body1Handle;
|
|
Pair.B = Body2Handle;
|
|
IgnorePairs.Add(Pair);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ResetSimulatedTeleportType = ETeleportType::ResetPhysics;
|
|
}
|
|
|
|
// Terminate all the constraint instances
|
|
for (FConstraintInstance* CI : HighLevelConstraintInstances)
|
|
{
|
|
CI->TermConstraint();
|
|
delete CI;
|
|
}
|
|
|
|
// Terminate all of the instances, cannot be done during insert or we may break constraint chains
|
|
for(FBodyInstance* Instance : HighLevelBodyInstances)
|
|
{
|
|
if(Instance->IsValidBodyInstance())
|
|
{
|
|
Instance->TermBody(true);
|
|
}
|
|
|
|
delete Instance;
|
|
}
|
|
|
|
HighLevelConstraintInstances.Empty();
|
|
HighLevelBodyInstances.Empty();
|
|
BodiesSorted.Empty();
|
|
|
|
const TMap<FRigidBodyIndexPair, bool>& DisableTable = UsePhysicsAsset->CollisionDisableTable;
|
|
for(auto ConstItr = DisableTable.CreateConstIterator(); ConstItr; ++ConstItr)
|
|
{
|
|
int32 IndexA = ConstItr.Key().Indices[0];
|
|
int32 IndexB = ConstItr.Key().Indices[1];
|
|
if ((IndexA < BodyIndexToActorHandle.Num()) && (IndexB < BodyIndexToActorHandle.Num()))
|
|
{
|
|
if ((BodyIndexToActorHandle[IndexA] != nullptr) && (BodyIndexToActorHandle[IndexB] != nullptr))
|
|
{
|
|
ImmediatePhysics::FSimulation::FIgnorePair Pair;
|
|
Pair.A = BodyIndexToActorHandle[IndexA];
|
|
Pair.B = BodyIndexToActorHandle[IndexB];
|
|
IgnorePairs.Add(Pair);
|
|
}
|
|
}
|
|
}
|
|
|
|
PhysicsSimulation->SetIgnoreCollisionPairTable(IgnorePairs);
|
|
PhysicsSimulation->SetIgnoreCollisionActors(IgnoreCollisionActors);
|
|
|
|
#if WITH_CHAOS
|
|
SolverSettings = UsePhysicsAsset->SolverSettings;
|
|
PhysicsSimulation->SetSolverSettings(
|
|
SolverSettings.FixedTimeStep,
|
|
SolverSettings.CullDistance,
|
|
SolverSettings.MaxDepenetrationVelocity,
|
|
SolverSettings.PositionIterations,
|
|
SolverSettings.VelocityIterations,
|
|
SolverSettings.ProjectionIterations);
|
|
|
|
SolverIterations = UsePhysicsAsset->SolverIterations;
|
|
PhysicsSimulation->SetLegacySolverSettings(
|
|
SolverIterations.SolverIterations,
|
|
SolverIterations.JointIterations,
|
|
SolverIterations.CollisionIterations,
|
|
SolverIterations.SolverPushOutIterations,
|
|
SolverIterations.JointPushOutIterations,
|
|
SolverIterations.CollisionPushOutIterations);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
DECLARE_CYCLE_STAT(TEXT("FAnimNode_RigidBody::UpdateWorldGeometry"), STAT_ImmediateUpdateWorldGeometry, STATGROUP_ImmediatePhysics);
|
|
|
|
void FAnimNode_RigidBody::UpdateWorldGeometry(const UWorld& World, const USkeletalMeshComponent& SKC)
|
|
{
|
|
LLM_SCOPE_BYNAME(TEXT("Animation/RigidBody"));
|
|
|
|
SCOPE_CYCLE_COUNTER(STAT_ImmediateUpdateWorldGeometry);
|
|
QueryParams = FCollisionQueryParams(SCENE_QUERY_STAT(RagdollNodeFindGeometry), /*bTraceComplex=*/false);
|
|
#if WITH_EDITOR
|
|
if(!World.IsGameWorld())
|
|
{
|
|
QueryParams.MobilityType = EQueryMobilityType::Any; //If we're in some preview world trace against everything because things like the preview floor are not static
|
|
QueryParams.AddIgnoredComponent(&SKC);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
QueryParams.MobilityType = EQueryMobilityType::Static; //We only want static actors
|
|
}
|
|
|
|
// Check for deleted world objects and flag for removal (later in anim task)
|
|
ExpireWorldObjects();
|
|
|
|
// If we have moved outside of the bounds we checked for world objects we need to gather new world objects
|
|
FSphere Bounds = SKC.CalcBounds(SKC.GetComponentToWorld()).GetSphere();
|
|
if (!Bounds.IsInside(CachedBounds))
|
|
{
|
|
// Since the cached bounds are no longer valid, update them.
|
|
CachedBounds = Bounds;
|
|
CachedBounds.W *= CachedBoundsScale;
|
|
|
|
// Cache the PhysScene and World for use in UpdateWorldForces and CollectWorldObjects
|
|
// When these are non-null it is an indicator that we need to update the collected world objects list
|
|
PhysScene = World.GetPhysicsScene();
|
|
UnsafeWorld = &World;
|
|
UnsafeOwner = SKC.GetOwner();
|
|
|
|
// A timer to track objects we haven't detected in a while
|
|
++ComponentsInSimTick;
|
|
}
|
|
}
|
|
|
|
DECLARE_CYCLE_STAT(TEXT("FAnimNode_RigidBody::UpdateWorldForces"), STAT_ImmediateUpdateWorldForces, STATGROUP_ImmediatePhysics);
|
|
|
|
void FAnimNode_RigidBody::UpdateWorldForces(const FTransform& ComponentToWorld, const FTransform& BaseBoneTM, const float DeltaSeconds)
|
|
{
|
|
LLM_SCOPE_BYNAME(TEXT("Animation/RigidBody"));
|
|
SCOPE_CYCLE_COUNTER(STAT_ImmediateUpdateWorldForces);
|
|
|
|
if(TotalMass > 0.f)
|
|
{
|
|
for (const USkeletalMeshComponent::FPendingRadialForces& PendingRadialForce : PendingRadialForces)
|
|
{
|
|
const FVector RadialForceOrigin = WorldPositionToSpace(SimulationSpace, PendingRadialForce.Origin, ComponentToWorld, BaseBoneTM);
|
|
for(ImmediatePhysics::FActorHandle* Body : Bodies)
|
|
{
|
|
const float InvMass = Body->GetInverseMass();
|
|
if(InvMass > 0.f)
|
|
{
|
|
const float StrengthPerBody = PendingRadialForce.bIgnoreMass ? PendingRadialForce.Strength : PendingRadialForce.Strength / (TotalMass * InvMass);
|
|
ImmediatePhysics::EForceType ForceType;
|
|
if (PendingRadialForce.Type == USkeletalMeshComponent::FPendingRadialForces::AddImpulse)
|
|
{
|
|
ForceType = PendingRadialForce.bIgnoreMass ? ImmediatePhysics::EForceType::AddVelocity : ImmediatePhysics::EForceType::AddImpulse;
|
|
}
|
|
else
|
|
{
|
|
ForceType = PendingRadialForce.bIgnoreMass ? ImmediatePhysics::EForceType::AddAcceleration : ImmediatePhysics::EForceType::AddForce;
|
|
}
|
|
|
|
Body->AddRadialForce(RadialForceOrigin, StrengthPerBody, PendingRadialForce.Radius, PendingRadialForce.Falloff, ForceType);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!ExternalForce.IsNearlyZero())
|
|
{
|
|
const FVector ExternalForceInSimSpace = WorldVectorToSpaceNoScale(SimulationSpace, ExternalForce, ComponentToWorld, BaseBoneTM);
|
|
for (ImmediatePhysics::FActorHandle* Body : Bodies)
|
|
{
|
|
const float InvMass = Body->GetInverseMass();
|
|
if (InvMass > 0.f)
|
|
{
|
|
Body->AddForce(ExternalForceInSimSpace);
|
|
}
|
|
}
|
|
}
|
|
if(DeltaSeconds != 0.0)
|
|
{
|
|
if(!PerSolverField.IsEmpty())
|
|
{
|
|
TArray<FVector>& SamplePositions = PerSolverField.GetSamplePositions();
|
|
TArray<FFieldContextIndex>& SampleIndices = PerSolverField.GetSampleIndices();
|
|
|
|
SamplePositions.SetNum(Bodies.Num(),false);
|
|
SampleIndices.SetNum(Bodies.Num(), false);
|
|
|
|
int32 Index = 0;
|
|
for (ImmediatePhysics::FActorHandle* Body : Bodies)
|
|
{
|
|
SamplePositions[Index] = (Body->GetWorldTransform() * SpaceToWorldTransform(SimulationSpace, ComponentToWorld, BaseBoneTM)).GetLocation();
|
|
SampleIndices[Index] = FFieldContextIndex(Index, Index);
|
|
++Index;
|
|
}
|
|
PerSolverField.ComputeFieldRigidImpulse(WorldTimeSeconds);
|
|
|
|
const TArray<FVector>& LinearVelocities = PerSolverField.GetOutputResults(EFieldCommandOutputType::LinearVelocity);
|
|
const TArray<FVector>& LinearForces = PerSolverField.GetOutputResults(EFieldCommandOutputType::LinearForce);
|
|
const TArray<FVector>& AngularVelocities = PerSolverField.GetOutputResults(EFieldCommandOutputType::AngularVelocity);
|
|
const TArray<FVector>& AngularTorques = PerSolverField.GetOutputResults(EFieldCommandOutputType::AngularTorque);
|
|
|
|
if (LinearVelocities.Num() == Bodies.Num())
|
|
{
|
|
Index = 0;
|
|
for (ImmediatePhysics::FActorHandle* Body : Bodies)
|
|
{
|
|
const FVector ExternalForceInSimSpace = WorldVectorToSpaceNoScale(SimulationSpace, LinearVelocities[Index++], ComponentToWorld, BaseBoneTM) * Body->GetMass() / DeltaSeconds;
|
|
Body->AddForce(ExternalForceInSimSpace);
|
|
}
|
|
}
|
|
if (LinearForces.Num() == Bodies.Num())
|
|
{
|
|
Index = 0;
|
|
for (ImmediatePhysics::FActorHandle* Body : Bodies)
|
|
{
|
|
const FVector ExternalForceInSimSpace = WorldVectorToSpaceNoScale(SimulationSpace, LinearForces[Index++], ComponentToWorld, BaseBoneTM);
|
|
Body->AddForce(ExternalForceInSimSpace);
|
|
}
|
|
}
|
|
if (AngularVelocities.Num() == Bodies.Num())
|
|
{
|
|
Index = 0;
|
|
for (ImmediatePhysics::FActorHandle* Body : Bodies)
|
|
{
|
|
const FVector ExternalTorqueInSimSpace = WorldVectorToSpaceNoScale(SimulationSpace, AngularVelocities[Index++], ComponentToWorld, BaseBoneTM) * Body->GetInertia() / DeltaSeconds;
|
|
Body->AddTorque(ExternalTorqueInSimSpace);
|
|
}
|
|
}
|
|
if (AngularTorques.Num() == Bodies.Num())
|
|
{
|
|
Index = 0;
|
|
for (ImmediatePhysics::FActorHandle* Body : Bodies)
|
|
{
|
|
const FVector ExternalTorqueInSimSpace = WorldVectorToSpaceNoScale(SimulationSpace, AngularTorques[Index++], ComponentToWorld, BaseBoneTM);
|
|
Body->AddTorque(ExternalTorqueInSimSpace);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FAnimNode_RigidBody::NeedsDynamicReset() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void FAnimNode_RigidBody::ResetDynamics(ETeleportType InTeleportType)
|
|
{
|
|
// This will be picked up next evaluate and reset our simulation.
|
|
// Teleport type can only go higher - i.e. if we have requested a reset, then a teleport will still reset fully
|
|
ResetSimulatedTeleportType = ((InTeleportType > ResetSimulatedTeleportType) ? InTeleportType : ResetSimulatedTeleportType);
|
|
}
|
|
|
|
DECLARE_CYCLE_STAT(TEXT("RigidBody_PreUpdate"), STAT_RigidBody_PreUpdate, STATGROUP_Anim);
|
|
|
|
void FAnimNode_RigidBody::PreUpdate(const UAnimInstance* InAnimInstance)
|
|
{
|
|
// Don't update geometry if RBN is disabled
|
|
if(!bEnabled)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SCOPE_CYCLE_COUNTER(STAT_RigidBody_PreUpdate);
|
|
|
|
USkeletalMeshComponent* SKC = InAnimInstance->GetSkelMeshComponent();
|
|
APawn* PawnOwner = InAnimInstance->TryGetPawnOwner();
|
|
UPawnMovementComponent* MovementComp = PawnOwner ? PawnOwner->GetMovementComponent() : nullptr;
|
|
|
|
#if WITH_EDITOR && WITH_CHAOS
|
|
if (bEnableWorldGeometry && SimulationSpace != ESimulationSpace::WorldSpace && SKC && SKC->GetRelativeScale3D() != FVector(1.f, 1.f, 1.f))
|
|
{
|
|
FMessageLog("PIE").Warning(FText::Format(LOCTEXT("WorldCollisionComponentSpace", "Trying to use world collision without world space simulation for scaled ''{0}''. This is not supported, please change SimulationSpace to WorldSpace"),
|
|
FText::FromString(GetPathNameSafe(SKC))));
|
|
}
|
|
#endif
|
|
|
|
UWorld* World = InAnimInstance->GetWorld();
|
|
if (World)
|
|
{
|
|
WorldSpaceGravity = bOverrideWorldGravity ? OverrideWorldGravity : (MovementComp ? FVector(0.f, 0.f, MovementComp->GetGravityZ()) : FVector(0.f, 0.f, World->GetGravityZ()));
|
|
|
|
if(SKC)
|
|
{
|
|
// Store game time for use in parallel evaluation. This may be the totol time (inc pauses) or the time the game has been unpaused.
|
|
WorldTimeSeconds = SKC->PrimaryComponentTick.bTickEvenWhenPaused ? World->UnpausedTimeSeconds : World->TimeSeconds;
|
|
|
|
if (PhysicsSimulation && bEnableWorldGeometry)
|
|
{
|
|
UpdateWorldGeometry(*World, *SKC);
|
|
}
|
|
|
|
PendingRadialForces = SKC->GetPendingRadialForces();
|
|
|
|
PreviousTransform = CurrentTransform;
|
|
CurrentTransform = SKC->GetComponentToWorld();
|
|
|
|
if (World->PhysicsField)
|
|
{
|
|
const FBox BoundingBox = SKC->CalcBounds(SKC->GetComponentTransform()).GetBox();
|
|
|
|
World->PhysicsField->FillTransientCommands(false, BoundingBox, WorldTimeSeconds, PerSolverField.GetTransientCommands());
|
|
World->PhysicsField->FillPersistentCommands(false, BoundingBox, WorldTimeSeconds, PerSolverField.GetPersistentCommands());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 FAnimNode_RigidBody::GetLODThreshold() const
|
|
{
|
|
if(CVarRigidBodyLODThreshold.GetValueOnAnyThread() != -1)
|
|
{
|
|
if(LODThreshold != -1)
|
|
{
|
|
return FMath::Min(LODThreshold, CVarRigidBodyLODThreshold.GetValueOnAnyThread());
|
|
}
|
|
else
|
|
{
|
|
return CVarRigidBodyLODThreshold.GetValueOnAnyThread();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return LODThreshold;
|
|
}
|
|
}
|
|
|
|
DECLARE_CYCLE_STAT(TEXT("RigidBody_Update"), STAT_RigidBody_Update, STATGROUP_Anim);
|
|
|
|
void FAnimNode_RigidBody::UpdateInternal(const FAnimationUpdateContext& Context)
|
|
{
|
|
LLM_SCOPE_BYNAME(TEXT("Animation/RigidBody"));
|
|
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(UpdateInternal)
|
|
// Avoid this work if RBN is disabled, as the results would be discarded
|
|
if(!bEnabled)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SCOPE_CYCLE_COUNTER(STAT_RigidBody_Update);
|
|
|
|
// Must flush the simulation since we may be making changes to the scene
|
|
FlushDeferredSimulationTask();
|
|
|
|
// Accumulate deltatime elapsed during update. To be used during evaluation.
|
|
AccumulatedDeltaTime += Context.AnimInstanceProxy->GetDeltaSeconds();
|
|
|
|
if (UnsafeWorld != nullptr)
|
|
{
|
|
// Node is valid to evaluate. Simulation is starting.
|
|
bSimulationStarted = true;
|
|
}
|
|
|
|
// Remove expired objects from the sim
|
|
PurgeExpiredWorldObjects();
|
|
|
|
// Find nearby world objects to add to the sim (gated on UnsafeWorld - see UpdateWorldGeometry)
|
|
CollectWorldObjects();
|
|
|
|
// These get set again if our bounds change. Subsequent calls to CollectWorldObjects will early-out until then
|
|
UnsafeWorld = nullptr;
|
|
UnsafeOwner = nullptr;
|
|
PhysScene = nullptr;
|
|
}
|
|
|
|
void FAnimNode_RigidBody::CollectWorldObjects()
|
|
{
|
|
if ((UnsafeWorld != nullptr) && (PhysScene != nullptr))
|
|
{
|
|
// @todo(ccaulfield): should this use CachedBounds?
|
|
TArray<FOverlapResult> Overlaps;
|
|
UnsafeWorld->OverlapMultiByChannel(Overlaps, CachedBounds.Center, FQuat::Identity, OverlapChannel, FCollisionShape::MakeSphere(CachedBounds.W), QueryParams, FCollisionResponseParams(ECR_Overlap));
|
|
|
|
// @todo(ccaulfield): is there an engine-independent way to do this?
|
|
#if WITH_PHYSX && PHYSICS_INTERFACE_PHYSX
|
|
SCOPED_SCENE_READ_LOCK(PhysScene ? PhysScene->GetPxScene() : nullptr); //TODO: expose this part to the anim node
|
|
#endif
|
|
|
|
for (const FOverlapResult& Overlap : Overlaps)
|
|
{
|
|
if (UPrimitiveComponent* OverlapComp = Overlap.GetComponent())
|
|
{
|
|
FWorldObject* WorldObject = ComponentsInSim.Find(OverlapComp);
|
|
if (WorldObject != nullptr)
|
|
{
|
|
// Existing object - reset its age
|
|
WorldObject->LastSeenTick = ComponentsInSimTick;
|
|
}
|
|
else
|
|
{
|
|
#if WITH_PHYSX && PHYSICS_INTERFACE_PHYSX
|
|
ComponentsInSim.Add(OverlapComp);
|
|
// Not sure why this happens, adding check to fix crash in CheckRBN engine test.
|
|
if (OverlapComp->BodyInstance.BodySetup != nullptr)
|
|
{
|
|
PhysicsSimulation->CreateActor(ImmediatePhysics::EActorType::StaticActor, &OverlapComp->BodyInstance, OverlapComp->BodyInstance.GetUnrealWorldTransform());
|
|
}
|
|
#elif WITH_CHAOS
|
|
// New object - add it to the sim
|
|
const bool bIsSelf = (UnsafeOwner == OverlapComp->GetOwner());
|
|
if (!bIsSelf)
|
|
{
|
|
// Create a kinematic actor. Not using Static as world-static objects may move in the simulation's frame of reference
|
|
ImmediatePhysics::FActorHandle* ActorHandle = PhysicsSimulation->CreateActor(ImmediatePhysics::EActorType::KinematicActor, &OverlapComp->BodyInstance, OverlapComp->GetComponentTransform());
|
|
PhysicsSimulation->AddToCollidingPairs(ActorHandle);
|
|
ComponentsInSim.Add(OverlapComp, FWorldObject(ActorHandle, ComponentsInSimTick));
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Flag invalid objects for purging
|
|
void FAnimNode_RigidBody::ExpireWorldObjects()
|
|
{
|
|
#if WITH_CHAOS
|
|
// Invalidate deleted and expired world objects
|
|
TArray<const UPrimitiveComponent*> PrunedEntries;
|
|
for (auto& WorldEntry : ComponentsInSim)
|
|
{
|
|
const UPrimitiveComponent* WorldComp = WorldEntry.Key;
|
|
FWorldObject& WorldObject = WorldEntry.Value;
|
|
|
|
// Do we need to expire this object?
|
|
const int32 ExpireTickCount = RBAN_WorldObjectExpiry;
|
|
bool bIsInvalid =
|
|
((ComponentsInSimTick - WorldObject.LastSeenTick) > ExpireTickCount) // Haven't seen this object for a while
|
|
|| !IsValid(WorldComp)
|
|
|| (WorldComp->GetBodyInstance() == nullptr)
|
|
|| (!WorldComp->GetBodyInstance()->IsValidBodyInstance());
|
|
|
|
// Remove from sim if necessary
|
|
if (bIsInvalid)
|
|
{
|
|
WorldObject.bExpired = true;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FAnimNode_RigidBody::PurgeExpiredWorldObjects()
|
|
{
|
|
#if WITH_CHAOS
|
|
// Destroy expired simulated objects
|
|
TArray<const UPrimitiveComponent*> PurgedEntries;
|
|
for (auto& WorldEntry : ComponentsInSim)
|
|
{
|
|
FWorldObject& WorldObject = WorldEntry.Value;
|
|
|
|
if (WorldObject.bExpired)
|
|
{
|
|
PhysicsSimulation->DestroyActor(WorldObject.ActorHandle);
|
|
WorldObject.ActorHandle = nullptr;
|
|
|
|
PurgedEntries.Add(WorldEntry.Key);
|
|
}
|
|
}
|
|
|
|
// Remove purged map entries
|
|
for (const UPrimitiveComponent* PurgedEntry : PurgedEntries)
|
|
{
|
|
ComponentsInSim.Remove(PurgedEntry);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Update the transforms of the world objects we added to the sim. This is required
|
|
// if we have a component- or bone-space simulation as even world-static objects
|
|
// will be moving in the simulation's frame of reference.
|
|
void FAnimNode_RigidBody::UpdateWorldObjects(const FTransform& SpaceTransform)
|
|
{
|
|
LLM_SCOPE_BYNAME(TEXT("Animation/RigidBody"));
|
|
#if WITH_CHAOS
|
|
if (SimulationSpace != ESimulationSpace::WorldSpace)
|
|
{
|
|
for (const auto& WorldEntry : ComponentsInSim)
|
|
{
|
|
const UPrimitiveComponent* OverlapComp = WorldEntry.Key;
|
|
if (OverlapComp != nullptr)
|
|
{
|
|
ImmediatePhysics::FActorHandle* ActorHandle = WorldEntry.Value.ActorHandle;
|
|
|
|
// Calculate the sim-space transform of this object
|
|
const FTransform CompWorldTransform = OverlapComp->BodyInstance.GetUnrealWorldTransform();
|
|
FTransform CompSpaceTransform;
|
|
CompSpaceTransform.SetTranslation(SpaceTransform.InverseTransformPosition(CompWorldTransform.GetLocation()));
|
|
CompSpaceTransform.SetRotation(SpaceTransform.InverseTransformRotation(CompWorldTransform.GetRotation()));
|
|
CompSpaceTransform.SetScale3D(FVector::OneVector); // TODO - sort out scale for world objects in local sim
|
|
|
|
// Update the sim's copy of the world object
|
|
ActorHandle->SetWorldTransform(CompSpaceTransform);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FAnimNode_RigidBody::InitializeBoneReferences(const FBoneContainer& RequiredBones)
|
|
{
|
|
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(InitializeBoneReferences)
|
|
/** We only need to update simulated bones and children of simulated bones*/
|
|
const int32 NumBodies = Bodies.Num();
|
|
const TArray<FBoneIndexType>& RequiredBoneIndices = RequiredBones.GetBoneIndicesArray();
|
|
const int32 NumRequiredBoneIndices = RequiredBoneIndices.Num();
|
|
const FReferenceSkeleton& RefSkeleton = RequiredBones.GetReferenceSkeleton();
|
|
|
|
OutputBoneData.Empty(NumBodies);
|
|
|
|
int32 NumSimulatedBodies = 0;
|
|
TArray<int32> SimulatedBodyIndices;
|
|
// if no name is entered, use root
|
|
if (BaseBoneRef.BoneName == NAME_None)
|
|
{
|
|
BaseBoneRef.BoneName = RefSkeleton.GetBoneName(0);
|
|
}
|
|
|
|
if (BaseBoneRef.BoneName != NAME_None)
|
|
{
|
|
BaseBoneRef.Initialize(RequiredBones);
|
|
}
|
|
|
|
bool bHasInvalidBoneReference = false;
|
|
for (int32 Index = 0; Index < NumRequiredBoneIndices; ++Index)
|
|
{
|
|
const FCompactPoseBoneIndex CompactPoseBoneIndex(Index);
|
|
const FBoneIndexType SkeletonBoneIndex = RequiredBones.GetSkeletonIndex(CompactPoseBoneIndex);
|
|
const FBoneIndexType IndexToBodyNum = SkeletonBoneIndexToBodyIndex.Num();
|
|
|
|
// If we have a missing bone in our skeleton, we don't want to have an out of bounds access.
|
|
if (SkeletonBoneIndex >= IndexToBodyNum)
|
|
{
|
|
bHasInvalidBoneReference = true;
|
|
break;
|
|
}
|
|
|
|
const int32 BodyIndex = SkeletonBoneIndexToBodyIndex[SkeletonBoneIndex];
|
|
|
|
if (BodyIndex != INDEX_NONE)
|
|
{
|
|
//If we have a body we need to save it for later
|
|
FOutputBoneData* OutputData = new (OutputBoneData) FOutputBoneData();
|
|
OutputData->BodyIndex = BodyIndex;
|
|
OutputData->CompactPoseBoneIndex = CompactPoseBoneIndex;
|
|
|
|
if (BodyAnimData[BodyIndex].bIsSimulated)
|
|
{
|
|
++NumSimulatedBodies;
|
|
SimulatedBodyIndices.AddUnique(BodyIndex);
|
|
}
|
|
|
|
OutputData->BoneIndicesToParentBody.Add(CompactPoseBoneIndex);
|
|
|
|
// Walk up parent chain until we find parent body.
|
|
OutputData->ParentBodyIndex = INDEX_NONE;
|
|
FCompactPoseBoneIndex CompactParentIndex = RequiredBones.GetParentBoneIndex(CompactPoseBoneIndex);
|
|
while (CompactParentIndex != INDEX_NONE)
|
|
{
|
|
const FBoneIndexType SkeletonParentBoneIndex = RequiredBones.GetSkeletonIndex(CompactParentIndex);
|
|
|
|
// Must check our parent as well for a missing bone.
|
|
if (SkeletonParentBoneIndex >= IndexToBodyNum)
|
|
{
|
|
bHasInvalidBoneReference = true;
|
|
break;
|
|
}
|
|
|
|
OutputData->ParentBodyIndex = SkeletonBoneIndexToBodyIndex[SkeletonParentBoneIndex];
|
|
if (OutputData->ParentBodyIndex != INDEX_NONE)
|
|
{
|
|
break;
|
|
}
|
|
|
|
OutputData->BoneIndicesToParentBody.Add(CompactParentIndex);
|
|
CompactParentIndex = RequiredBones.GetParentBoneIndex(CompactParentIndex);
|
|
}
|
|
|
|
if (bHasInvalidBoneReference)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bHasInvalidBoneReference)
|
|
{
|
|
// If a bone was missing, let us know which asset it happened on, and clear our bone container to make the bad asset visible.
|
|
ensureMsgf(false, TEXT("FAnimNode_RigidBody::InitializeBoneReferences: The Skeleton %s, is missing bones that SkeletalMesh %s needs. Skeleton might need to be resaved."),
|
|
*GetNameSafe(RequiredBones.GetSkeletonAsset()), *GetNameSafe(RequiredBones.GetSkeletalMeshAsset()));
|
|
OutputBoneData.Empty();
|
|
}
|
|
else
|
|
{
|
|
// New bodies potentially introduced with new LOD
|
|
// We'll have to initialize their transform.
|
|
bCheckForBodyTransformInit = true;
|
|
|
|
if (PhysicsSimulation)
|
|
{
|
|
#if WITH_CHAOS
|
|
PhysicsSimulation->SetNumActiveBodies(NumSimulatedBodies, SimulatedBodyIndices);
|
|
#else
|
|
PhysicsSimulation->SetNumActiveBodies(NumSimulatedBodies);
|
|
#endif
|
|
}
|
|
|
|
// We're switching to a new LOD, this invalidates our captured poses.
|
|
CapturedFrozenPose.Empty();
|
|
CapturedFrozenCurves.Empty();
|
|
}
|
|
}
|
|
|
|
void FAnimNode_RigidBody::AddImpulseAtLocation(FVector Impulse, FVector Location, FName BoneName)
|
|
{
|
|
#if WITH_CHAOS
|
|
// Find the body. This is currently only used in the editor and will need optimizing if used in game
|
|
for (int32 BodyIndex = 0; BodyIndex < Bodies.Num(); ++BodyIndex)
|
|
{
|
|
ImmediatePhysics::FActorHandle* Body = Bodies[BodyIndex];
|
|
if (Body->GetName() == BoneName)
|
|
{
|
|
Body->AddImpulseAtLocation(Impulse, Location);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FAnimNode_RigidBody::OnInitializeAnimInstance(const FAnimInstanceProxy* InProxy, const UAnimInstance* InAnimInstance)
|
|
{
|
|
InitPhysics(InAnimInstance);
|
|
}
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
void FAnimNode_RigidBody::PostSerialize(const FArchive& Ar)
|
|
{
|
|
if(bComponentSpaceSimulation_DEPRECATED == false)
|
|
{
|
|
//If this is not the default value it means we have old content where we were simulating in world space
|
|
SimulationSpace = ESimulationSpace::WorldSpace;
|
|
bComponentSpaceSimulation_DEPRECATED = true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
bool FAnimNode_RigidBody::IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones)
|
|
{
|
|
return BaseBoneRef.IsValidToEvaluate(RequiredBones);
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|