Files
UnrealEngine/Engine/Source/Runtime/ClothingSystemRuntimeCommon/Private/ClothingSimulation.cpp
2025-05-18 13:04:45 +08:00

243 lines
9.2 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ClothingSimulation.h"
#include "Components/SkeletalMeshComponent.h"
#include "Engine/SkeletalMesh.h"
#include "PhysicsEngine/PhysicsSettings.h"
//==============================================================================
// FClothingSimulationContextCommon
//==============================================================================
DEFINE_STAT(STAT_ClothComputeNormals);
DEFINE_STAT(STAT_ClothInternalSolve);
DEFINE_STAT(STAT_ClothUpdateCollisions);
DEFINE_STAT(STAT_ClothSkinPhysMesh);
DEFINE_STAT(STAT_ClothFillContext);
static TAutoConsoleVariable<float> GClothMaxDeltaTimeTeleportMultiplier(
TEXT("p.Cloth.MaxDeltaTimeTeleportMultiplier"),
1.5f,
TEXT("A multiplier of the MaxPhysicsDelta time at which we will automatically just teleport cloth to its new location\n")
TEXT(" default: 1.5"));
static TAutoConsoleVariable<float> GClothMaxVelocityScale(
TEXT("p.Cloth.MaxVelocityScale"),
1.f,
TEXT("The maximum amount of the component induced velocity allowed on all cloths.\n")
TEXT("Use 1.0 for fully induced velocity(default), or use 0.0 for no induced velocity, and any other values in between for a reduced induced velocity.\n")
TEXT("When set to 0.0, it also provides a way to force the clothing to simulate in local space.\n")
TEXT(" default: 1.0"));
PRAGMA_DISABLE_DEPRECATION_WARNINGS // CachedPositions, CachedVelocities
FClothingSimulationContextCommon::FClothingSimulationContextCommon()
: ComponentToWorld(FTransform::Identity)
, WorldGravity(FVector::ZeroVector)
, WindVelocity(FVector::ZeroVector)
, WindAdaption(0.f)
, DeltaSeconds(0.f)
, VelocityScale(1.f)
, TeleportMode(EClothingTeleportMode::None)
, MaxDistanceScale(1.f)
, PredictedLod(INDEX_NONE)
{}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
PRAGMA_DISABLE_DEPRECATION_WARNINGS // CachedPositions, CachedVelocities
FClothingSimulationContextCommon::~FClothingSimulationContextCommon()
{}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
void FClothingSimulationContextCommon::Fill(const USkeletalMeshComponent* InComponent, float InDeltaSeconds, float InMaxPhysicsDelta, bool bIsInitialization)
{
SCOPE_CYCLE_COUNTER(STAT_ClothFillContext);
LLM_SCOPE(ELLMTag::SkeletalMesh);
check(InComponent);
FillBoneTransforms(InComponent);
FillRefToLocals(InComponent, bIsInitialization);
FillComponentToWorld(InComponent);
FillWorldGravity(InComponent);
FillWindVelocity(InComponent);
FillDeltaSeconds(InDeltaSeconds, InMaxPhysicsDelta);
FillTeleportMode(InComponent, InDeltaSeconds, InMaxPhysicsDelta);
FillMaxDistanceScale(InComponent);
FillSolverGeometryScale(InComponent);
PredictedLod = InComponent->GetPredictedLODLevel();
}
void FClothingSimulationContextCommon::FillBoneTransforms(const USkeletalMeshComponent* InComponent)
{
const USkeletalMesh* const SkeletalMesh = InComponent->GetSkeletalMeshAsset();
if (USkinnedMeshComponent* const LeaderComponent = InComponent->LeaderPoseComponent.Get())
{
const TArray<int32>& LeaderBoneMap = InComponent->GetLeaderBoneMap();
int32 NumBones = LeaderBoneMap.Num();
if (NumBones == 0)
{
if (SkeletalMesh)
{
// This case indicates an invalid leader pose component (e.g. no skeletal mesh)
NumBones = SkeletalMesh->GetRefSkeleton().GetNum();
BoneTransforms.Empty(NumBones);
BoneTransforms.AddDefaulted(NumBones);
}
}
else
{
BoneTransforms.Reset(NumBones);
BoneTransforms.AddDefaulted(NumBones);
const TArray<FTransform>& LeaderTransforms = LeaderComponent->GetComponentSpaceTransforms();
for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex)
{
bool bFoundLeader = false;
if (LeaderBoneMap.IsValidIndex(BoneIndex))
{
const int32 LeaderIndex = LeaderBoneMap[BoneIndex];
if (LeaderIndex != INDEX_NONE && LeaderIndex < LeaderTransforms.Num())
{
BoneTransforms[BoneIndex] = LeaderTransforms[LeaderIndex];
bFoundLeader = true;
}
}
if (!bFoundLeader && SkeletalMesh)
{
const int32 ParentIndex = SkeletalMesh->GetRefSkeleton().GetParentIndex(BoneIndex);
BoneTransforms[BoneIndex] =
BoneTransforms.IsValidIndex(ParentIndex) && ParentIndex < BoneIndex ?
BoneTransforms[ParentIndex] * SkeletalMesh->GetRefSkeleton().GetRefBonePose()[BoneIndex] :
SkeletalMesh->GetRefSkeleton().GetRefBonePose()[BoneIndex];
}
}
}
}
else
{
BoneTransforms = InComponent->GetComponentSpaceTransforms();
}
}
void FClothingSimulationContextCommon::FillRefToLocals(const USkeletalMeshComponent* InComponent, bool bIsInitialization)
{
RefToLocals.Reset();
// Constraints are initialized using bone distances upon initialization, so fill out reference pose
if (bIsInitialization)
{
const USkeletalMesh* const SkeletalMesh = InComponent->GetSkeletalMeshAsset();
if (SkeletalMesh)
{
const int32 NumBones = SkeletalMesh->GetRefSkeleton().GetNum();
RefToLocals.AddUninitialized(NumBones);
for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex)
{
RefToLocals[BoneIndex] = FMatrix44f::Identity;
}
}
return;
}
InComponent->GetCurrentRefToLocalMatrices(RefToLocals, InComponent->GetPredictedLODLevel());
}
void FClothingSimulationContextCommon::FillComponentToWorld(const USkeletalMeshComponent* InComponent)
{
ComponentToWorld = InComponent->GetComponentTransform();
}
void FClothingSimulationContextCommon::FillWorldGravity(const USkeletalMeshComponent* InComponent)
{
const UWorld* const ComponentWorld = InComponent->GetWorld();
check(ComponentWorld);
WorldGravity = FVector(0.f, 0.f, ComponentWorld->GetGravityZ());
}
void FClothingSimulationContextCommon::FillWindVelocity(const USkeletalMeshComponent* InComponent)
{
InComponent->GetWindForCloth_GameThread(WindVelocity, WindAdaption);
}
void FClothingSimulationContextCommon::FillDeltaSeconds(float InDeltaSeconds, float InMaxPhysicsDelta)
{
DeltaSeconds = FMath::Min(InDeltaSeconds, InMaxPhysicsDelta);
}
void FClothingSimulationContextCommon::FillTeleportMode(const USkeletalMeshComponent* InComponent, float InDeltaSeconds, float InMaxPhysicsDelta)
{
TeleportMode = (InComponent->ClothTeleportMode < EClothingTeleportMode::Teleport && (InDeltaSeconds > InMaxPhysicsDelta * GClothMaxDeltaTimeTeleportMultiplier.GetValueOnGameThread())) ?
EClothingTeleportMode::Teleport :
InComponent->ClothTeleportMode;
const float MaxVelocityScale = FMath::Clamp(GClothMaxVelocityScale.GetValueOnGameThread(), 0.f, 1.f);
const float ComponentVelocityScale = InComponent->ClothVelocityScale;
VelocityScale = (TeleportMode == EClothingTeleportMode::None && InDeltaSeconds > 0.f) ?
FMath::Clamp(ComponentVelocityScale, 0.f, MaxVelocityScale) * FMath::Min(InDeltaSeconds, InMaxPhysicsDelta) / InDeltaSeconds : 0.f;
}
void FClothingSimulationContextCommon::FillMaxDistanceScale(const USkeletalMeshComponent* InComponent)
{
MaxDistanceScale = InComponent->GetClothMaxDistanceScale();
}
void FClothingSimulationContextCommon::FillSolverGeometryScale(const USkeletalMeshComponent* InComponent)
{
SolverGeometryScale = InComponent->ClothGeometryScale;
}
//==============================================================================
// FClothingSimulationCommon
//==============================================================================
FClothingSimulationCommon::FClothingSimulationCommon()
{
MaxPhysicsDelta = UPhysicsSettings::Get()->MaxPhysicsDeltaTime;
}
FClothingSimulationCommon::~FClothingSimulationCommon()
{}
void FClothingSimulationCommon::FillContext(USkeletalMeshComponent* InComponent, float InDeltaTime, IClothingSimulationContext* InOutContext)
{
// Deprecated version always fills RefToLocals with current animation results instead of using reference pose on initialization
const bool bIsInitialization = false;
FillContext(InComponent, InDeltaTime, InOutContext, bIsInitialization);
}
void FClothingSimulationCommon::FillContext(USkeletalMeshComponent* InComponent, float InDeltaTime, IClothingSimulationContext* InOutContext, bool bIsInitialization)
{
check(InOutContext);
FClothingSimulationContextCommon* const Context = static_cast<FClothingSimulationContextCommon*>(InOutContext);
Context->Fill(InComponent, InDeltaTime, MaxPhysicsDelta, bIsInitialization);
// Checking the component here to track rare issue leading to invalid contexts
if (!IsValid(InComponent))
{
const AActor* const CompOwner = InComponent->GetOwner();
UE_LOG(LogSkeletalMesh, Warning,
TEXT("Attempting to fill a clothing simulation context for a PendingKill skeletal mesh component (Comp: %s, Actor: %s). "
"Pending kill skeletal mesh components should be unregistered before marked pending kill."),
*InComponent->GetName(), CompOwner ? *CompOwner->GetName() : TEXT("None"));
// Make sure we clear this out to skip any attempted simulations
Context->BoneTransforms.Reset();
}
if (Context->BoneTransforms.Num() == 0)
{
const AActor* const CompOwner = InComponent->GetOwner();
const USkinnedMeshComponent* const Leader = InComponent->LeaderPoseComponent.Get();
UE_LOG(LogSkeletalMesh, Warning, TEXT("Attempting to fill a clothing simulation context for a skeletal mesh component that has zero bones (Comp: %s, Leader: %s, Actor: %s)."), *InComponent->GetName(), Leader ? *Leader->GetName() : TEXT("None"), CompOwner ? *CompOwner->GetName() : TEXT("None"));
// Make sure we clear this out to skip any attempted simulations
Context->BoneTransforms.Reset();
}
}