Files
UnrealEngine/Engine/Source/Runtime/Experimental/ChaosSolverEngine/Private/Chaos/ChaosSolverActor.cpp
2025-05-18 13:04:45 +08:00

624 lines
19 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Chaos/ChaosSolverActor.h"
#include "UObject/ConstructorHelpers.h"
#include "PhysicsSolver.h"
#include "ChaosModule.h"
#include "Components/BillboardComponent.h"
#include "EngineUtils.h"
#include "ChaosSolversModule.h"
#include "Chaos/ChaosGameplayEventDispatcher.h"
#include "Chaos/Framework/DebugSubstep.h"
#include "Engine/Texture2D.h"
#include "UObject/FortniteMainBranchObjectVersion.h"
#include "PhysicsProxy/SingleParticlePhysicsProxy.h"
#include "Dataflow/DataflowSimulationManager.h"
#include "PhysicsEngine/PhysicsSettings.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(ChaosSolverActor)
//DEFINE_LOG_CATEGORY_STATIC(AFA_Log, NoLogging, All);
#ifndef CHAOS_WITH_PAUSABLE_SOLVER
#define CHAOS_WITH_PAUSABLE_SOLVER 1
#endif
#if CHAOS_DEBUG_SUBSTEP
#include "HAL/IConsoleManager.h"
#include "Chaos/Utilities.h"
class FChaosSolverActorConsoleObjects final
{
public:
FChaosSolverActorConsoleObjects()
: ConsoleCommands()
{
// Register console command
ConsoleCommands.Add(IConsoleManager::Get().RegisterConsoleCommand(
TEXT("p.Chaos.Solver.Pause"),
TEXT("Debug pause the specified solver."),
FConsoleCommandWithArgsDelegate::CreateRaw(this, &FChaosSolverActorConsoleObjects::Pause),
ECVF_Cheat));
ConsoleCommands.Add(IConsoleManager::Get().RegisterConsoleCommand(
TEXT("p.Chaos.Solver.Step"),
TEXT("Debug step the specified solver."),
FConsoleCommandWithArgsDelegate::CreateRaw(this, &FChaosSolverActorConsoleObjects::Step),
ECVF_Cheat));
ConsoleCommands.Add(IConsoleManager::Get().RegisterConsoleCommand(
TEXT("p.Chaos.Solver.Substep"),
TEXT("Debug substep the specified solver."),
FConsoleCommandWithArgsDelegate::CreateRaw(this, &FChaosSolverActorConsoleObjects::Substep),
ECVF_Cheat));
}
~FChaosSolverActorConsoleObjects()
{
for (IConsoleObject* ConsoleCommand: ConsoleCommands)
{
IConsoleManager::Get().UnregisterConsoleObject(ConsoleCommand);
}
}
void AddSolver(const FString& Name, AChaosSolverActor* SolverActor) { SolverActors.Add(Name, SolverActor); }
void RemoveSolver(const FString& Name) { SolverActors.Remove(Name); }
private:
void Pause(const TArray<FString>& Args)
{
AChaosSolverActor* const* SolverActor;
Chaos::FPhysicsSolver* Solver;
switch (Args.Num())
{
default:
break; // Invalid arguments
case 1:
if ((SolverActor = SolverActors.Find(Args[0])) != nullptr &&
(Solver = (*SolverActor)->GetSolver()) != nullptr)
{
UE_LOG(LogChaosDebug, Display, TEXT("%d"), (*SolverActor)->ChaosDebugSubstepControl.bPause);
return;
}
break; // Invalid arguments
case 2:
if ((SolverActor = SolverActors.Find(Args[0])) != nullptr &&
(Solver = (*SolverActor)->GetSolver()) != nullptr)
{
#if TODO_REIMPLEMENT_DEBUG_SUBSTEP
if (Args[1] == TEXT("0"))
{
Solver->GetDebugSubstep().Enable(false);
(*SolverActor)->ChaosDebugSubstepControl.bPause = false;
#if WITH_EDITOR
(*SolverActor)->ChaosDebugSubstepControl.OnPauseChanged.ExecuteIfBound();
#endif
return;
}
else if (Args[1] == TEXT("1"))
{
Solver->GetDebugSubstep().Enable(true);
(*SolverActor)->ChaosDebugSubstepControl.bPause = true;
#if WITH_EDITOR
(*SolverActor)->ChaosDebugSubstepControl.OnPauseChanged.ExecuteIfBound();
#endif
return;
}
#endif
}
break; // Invalid arguments
}
UE_LOG(LogChaosDebug, Display, TEXT("Invalid arguments."));
UE_LOG(LogChaosDebug, Display, TEXT("Usage:"));
UE_LOG(LogChaosDebug, Display, TEXT(" p.Chaos.Solver.Pause [SolverName] [0|1|]"));
UE_LOG(LogChaosDebug, Display, TEXT(" SolverName The Id name of the solver as shown by p.Chaos.Solver.List"));
UE_LOG(LogChaosDebug, Display, TEXT(" 0|1| Use either 0 to unpause, 1 to pause, or nothing to query"));
UE_LOG(LogChaosDebug, Display, TEXT("Example: p.Chaos.Solver.Pause ChaosSolverActor_3 1"));
}
void Step(const TArray<FString>& Args)
{
#if TODO_REIMPLEMENT_DEBUG_SUBSTEP
AChaosSolverActor* const* SolverActor;
Chaos::FPhysicsSolver* Solver;
switch (Args.Num())
{
default:
break; // Invalid arguments
case 1:
if ((SolverActor = SolverActors.Find(Args[0])) != nullptr &&
(Solver = (*SolverActor)->GetSolver()) != nullptr)
{
Solver->GetDebugSubstep().ProgressToStep();
return;
}
break; // Invalid arguments
}
UE_LOG(LogChaosDebug, Display, TEXT("Invalid arguments."));
UE_LOG(LogChaosDebug, Display, TEXT("Usage:"));
UE_LOG(LogChaosDebug, Display, TEXT(" p.Chaos.Solver.Step [SolverName]"));
UE_LOG(LogChaosDebug, Display, TEXT(" SolverName The Id name of the solver as shown by p.Chaos.Solver.List"));
UE_LOG(LogChaosDebug, Display, TEXT("Example: p.Chaos.Solver.Step ChaosSolverActor_3"));
#endif
}
void Substep(const TArray<FString>& Args)
{
#if TODO_REIMPLEMENT_DEBUG_SUBSTEP
AChaosSolverActor* const* SolverActor;
Chaos::FPhysicsSolver* Solver;
switch (Args.Num())
{
default:
break; // Invalid arguments
case 1:
if ((SolverActor = SolverActors.Find(Args[0])) != nullptr &&
(Solver = (*SolverActor)->GetSolver()) != nullptr)
{
Solver->GetDebugSubstep().ProgressToSubstep();
return;
}
break; // Invalid arguments
}
UE_LOG(LogChaosDebug, Display, TEXT("Invalid arguments."));
UE_LOG(LogChaosDebug, Display, TEXT("Usage:"));
UE_LOG(LogChaosDebug, Display, TEXT(" p.Chaos.Solver.Substep [SolverName]"));
UE_LOG(LogChaosDebug, Display, TEXT(" SolverName The Id name of the solver as shown by p.Chaos.Solver.List"));
UE_LOG(LogChaosDebug, Display, TEXT("Example: p.Chaos.Solver.Substep ChaosSolverActor_3"));
#endif
}
private:
TArray<IConsoleObject*> ConsoleCommands;
TMap<FString, AChaosSolverActor*> SolverActors;
};
static TUniquePtr<FChaosSolverActorConsoleObjects> ChaosSolverActorConsoleObjects;
#endif // #if CHAOS_DEBUG_SUBSTEP
void FDataflowRigidSolverProxy::AdvanceSolverDatas(const float DeltaTime)
{
if(Solver != nullptr)
{
for(auto* PushData : PushDatas)
{
Chaos::FSolverTasksPTOnly SolverTask(*Solver, PushData);
SolverTask.AdvanceSolver();
}
}
}
AChaosSolverActor::AChaosSolverActor(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, TimeStepMultiplier_DEPRECATED(1.f)
, CollisionIterations_DEPRECATED(1)
, PushOutIterations_DEPRECATED(3)
, PushOutPairIterations_DEPRECATED(2)
, ClusterConnectionFactor_DEPRECATED(1.0)
, ClusterUnionConnectionType_DEPRECATED(EClusterConnectionTypeEnum::Chaos_DelaunayTriangulation)
, DoGenerateCollisionData_DEPRECATED(true)
, DoGenerateBreakingData_DEPRECATED(true)
, DoGenerateTrailingData_DEPRECATED(true)
, MassScale_DEPRECATED(1.f)
, bHasFloor(true)
, FloorHeight(0.f)
, ChaosDebugSubstepControl()
, PhysScene(nullptr)
, RigidSolverProxy()
, Proxy(nullptr)
{
if(!HasAnyFlags(RF_ClassDefaultObject))
{
// Don't spawn solvers on the CDO
// @question(Benn) : Does this need to be created on the Physics thread using a queued command?
PhysScene = MakeShareable(new FPhysScene_Chaos(this
#if CHAOS_DEBUG_NAME
, TEXT("Solver Actor Physics")
#endif
));
RigidSolverProxy.Solver = PhysScene->GetSolver();
// Ticking setup for collision/breaking notifies
PrimaryActorTick.TickGroup = TG_PostPhysics;
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bStartWithTickEnabled = true;
}
/*
* Display icon in the editor
*/
// Structure to hold one-time initialization
struct FConstructorStatics
{
// A helper class object we use to find target UTexture2D object in resource package
ConstructorHelpers::FObjectFinderOptional<UTexture2D> SolverTextureObject;
// Icon sprite category name
FName ID_Solver;
// Icon sprite display name
FText NAME_Solver;
FConstructorStatics()
// Use helper class object to find the texture
// "/Engine/EditorResources/S_Solver" is resource path
: SolverTextureObject(TEXT("/Engine/EditorResources/S_Solver.S_Solver"))
, ID_Solver(TEXT("Solver"))
, NAME_Solver(NSLOCTEXT("SpriteCategory", "Solver", "Solver"))
{
}
};
static FConstructorStatics ConstructorStatics;
// We need a scene component to attach Icon sprite
USceneComponent* SceneComponent = ObjectInitializer.CreateDefaultSubobject<USceneComponent>(this, TEXT("SceneComp"));
RootComponent = SceneComponent;
RootComponent->Mobility = EComponentMobility::Static;
#if WITH_EDITORONLY_DATA
SpriteComponent = ObjectInitializer.CreateEditorOnlyDefaultSubobject<UBillboardComponent>(this, TEXT("Sprite"));
if (SpriteComponent)
{
SpriteComponent->Sprite = ConstructorStatics.SolverTextureObject.Get(); // Get the sprite texture from helper class object
SpriteComponent->SpriteInfo.Category = ConstructorStatics.ID_Solver; // Assign sprite category name
SpriteComponent->SpriteInfo.DisplayName = ConstructorStatics.NAME_Solver; // Assign sprite display name
SpriteComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);
SpriteComponent->Mobility = EComponentMobility::Static;
}
#endif // WITH_EDITORONLY_DATA
GameplayEventDispatcherComponent = ObjectInitializer.CreateDefaultSubobject<UChaosGameplayEventDispatcher>(this, TEXT("GameplayEventDispatcher"));
}
void AChaosSolverActor::PreInitializeComponents()
{
Super::PreInitializeComponents();
}
void AChaosSolverActor::MigrateSolver() const
{
if(GetSolver())
{
if(FChaosSolversModule* Module = FChaosSolversModule::GetModule())
{
if(SimulationAsset.DataflowAsset)
{
Module->MigrateSolver(GetSolver(), this);
GetSolver()->SetStandaloneSolver(true);
}
else
{
Module->MigrateSolver(GetSolver(), GetWorld());
GetSolver()->SetStandaloneSolver(false);
}
}
}
// Make sure that the owning world is correct
PhysScene->SetOwningWorld(GetWorld());
}
void AChaosSolverActor::BeginPlay()
{
Super::BeginPlay();
if(!RigidSolverProxy.Solver)
{
return;
}
MigrateSolver();
RigidSolverProxy.Solver->EnqueueCommandImmediate(
[InSolver = RigidSolverProxy.Solver, InProps = Properties]()
{
InSolver->ApplyConfig(InProps);
});
MakeFloor();
#if TODO_REIMPLEMENT_DEBUG_SUBSTEP
#if CHAOS_DEBUG_SUBSTEP
if (!ChaosSolverActorConsoleObjects)
{
ChaosSolverActorConsoleObjects = MakeUnique<FChaosSolverActorConsoleObjects>();
}
ChaosSolverActorConsoleObjects->AddSolver(GetName(), this);
#if WITH_EDITOR
if (ChaosDebugSubstepControl.bPause)
{
Solver->GetDebugSubstep().Enable(true);
}
#endif // #if WITH_EDITOR
#endif // #if CHAOS_DEBUG_SUBSTEP
#endif // #if TODO_REIMPLEMENT_DEBUG_SUBSTEP
}
void AChaosSolverActor::EndPlay(const EEndPlayReason::Type ReasonEnd)
{
Super::EndPlay(ReasonEnd);
if(!RigidSolverProxy.Solver)
{
return;
}
if(Proxy)
{
RigidSolverProxy.Solver->UnregisterObject(Proxy);
Proxy = nullptr;
}
RigidSolverProxy.Solver->EnqueueCommandImmediate([InSolver=RigidSolverProxy.Solver]()
{
// #TODO BG - We should really reset the solver here but the current reset function
// is really heavy handed and clears out absolutely everything. Ideally we want to keep
// all of the solver physics proxies and revert to a state before the very first tick
//InSolver->SetEnabled(false);
});
#if TODO_REIMPLEMENT_DEBUG_SUBSTEP
#if CHAOS_DEBUG_SUBSTEP
ChaosSolverActorConsoleObjects->RemoveSolver(GetName());
#endif // #if CHAOS_DEBUG_SUBSTEP
#endif // #if TODO_REIMPLEMENT_DEBUG_SUBSTEP
}
void AChaosSolverActor::PostLoad()
{
Super::PostLoad();
if(GetLinkerCustomVersion(FFortniteMainBranchObjectVersion::GUID) < FFortniteMainBranchObjectVersion::ChaosSolverPropertiesMoved)
{
auto ConvertDeprecatedConnectionType = [](EClusterConnectionTypeEnum LegacyType) -> EClusterUnionMethod
{
switch(LegacyType)
{
case EClusterConnectionTypeEnum::Chaos_PointImplicit:
return EClusterUnionMethod::PointImplicit;
case EClusterConnectionTypeEnum::Chaos_DelaunayTriangulation:
return EClusterUnionMethod::DelaunayTriangulation;
case EClusterConnectionTypeEnum::Chaos_MinimalSpanningSubsetDelaunayTriangulation:
return EClusterUnionMethod::MinimalSpanningSubsetDelaunayTriangulation;
case EClusterConnectionTypeEnum::Chaos_PointImplicitAugmentedWithMinimalDelaunay:
return EClusterUnionMethod::PointImplicitAugmentedWithMinimalDelaunay;
case EClusterConnectionTypeEnum::Chaos_BoundsOverlapFilteredDelaunayTriangulation:
return EClusterUnionMethod::BoundsOverlapFilteredDelaunayTriangulation;
default:
break;
}
return EClusterUnionMethod::None;
};
Properties.PositionIterations = CollisionIterations_DEPRECATED;
Properties.VelocityIterations = PushOutIterations_DEPRECATED;
Properties.ClusterConnectionFactor = ClusterConnectionFactor_DEPRECATED;
Properties.ClusterUnionConnectionType = ConvertDeprecatedConnectionType(ClusterUnionConnectionType_DEPRECATED);
Properties.bGenerateBreakData = DoGenerateBreakingData_DEPRECATED;
Properties.bGenerateCollisionData = DoGenerateCollisionData_DEPRECATED;
Properties.bGenerateTrailingData = DoGenerateTrailingData_DEPRECATED;
Properties.BreakingFilterSettings = BreakingFilterSettings_DEPRECATED;
Properties.CollisionFilterSettings = CollisionFilterSettings_DEPRECATED;
Properties.TrailingFilterSettings = TrailingFilterSettings_DEPRECATED;
}
}
void AChaosSolverActor::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
// Attach custom version
Ar.UsingCustomVersion(FFortniteMainBranchObjectVersion::GUID);
}
void AChaosSolverActor::PostRegisterAllComponents()
{
Super::PostRegisterAllComponents();
MigrateSolver();
UWorld* const W = GetWorld();
if (W && !W->PhysicsScene_Chaos)
{
SetAsCurrentWorldSolver();
}
// Register the dataflow simulation interface
UE::Dataflow::RegisterSimulationInterface(this);
}
void AChaosSolverActor::PostUnregisterAllComponents()
{
Super::PostUnregisterAllComponents();
// Unregister the dataflow simulation interface
UE::Dataflow::UnregisterSimulationInterface(this);
}
void AChaosSolverActor::PostDuplicate(EDuplicateMode::Type DuplicateMode)
{
Super::PostDuplicate(DuplicateMode);
MigrateSolver();
}
void AChaosSolverActor::MakeFloor()
{
if(bHasFloor)
{
TUniquePtr<Chaos::FGeometryParticle> FloorParticle = Chaos::FGeometryParticle::CreateParticle();
// todo(chaos) : Changing the floor to be a box for now because there's few cases where this may fail to collide with geometry collection
// since the box is finite, let's center it at the actor position for better user control
//FloorParticle->SetGeometry(TUniquePtr<Chaos::TPlane<Chaos::FReal, 3>>(new Chaos::TPlane<Chaos::FReal, 3>(FVector(0), FVector(0, 0, 1))));
const FVector SolverLocation = GetActorLocation();
const FVector BoxMin(-100000, -100000, -1000);
const FVector BoxMax(100000, 100000, 0);
FloorParticle->SetGeometry(MakeImplicitObjectPtr<Chaos::TBox<Chaos::FReal, 3>>(BoxMin, BoxMax));
FloorParticle->SetX(Chaos::FVec3(SolverLocation.X, SolverLocation.Y, FloorHeight));
FCollisionFilterData FilterData;
FilterData.Word1 = 0xFFFF;
FilterData.Word3 = 0xFFFF;
FloorParticle->SetShapeSimData(0, FilterData);
Proxy = Chaos::FSingleParticlePhysicsProxy::Create(MoveTemp(FloorParticle));
RigidSolverProxy.Solver->RegisterObject(Proxy);
}
}
void AChaosSolverActor::SetAsCurrentWorldSolver()
{
UWorld* const W = GetWorld();
if (W && (GetSolver() && !GetSolver()->IsStandaloneSolver()))
{
W->PhysicsScene_Chaos = PhysScene;
}
}
void AChaosSolverActor::SetSolverActive(bool bActive)
{
if(RigidSolverProxy.Solver && PhysScene)
{
RigidSolverProxy.Solver->SetIsPaused_External(!bActive);
}
}
void AChaosSolverActor::BuildSimulationProxy()
{
RigidSolverProxy.Solver = PhysScene->GetSolver();
// Ticking setup for collision/breaking notifies
PrimaryActorTick.TickGroup = TG_PostPhysics;
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bStartWithTickEnabled = true;
}
void AChaosSolverActor::ResetSimulationProxy()
{
RigidSolverProxy.Solver = nullptr;
}
void AChaosSolverActor::WriteToSimulation(const float DeltaTime, const bool bAsyncTask)
{
if (RigidSolverProxy.IsValid())
{
if(!bAsyncTask)
{
if(!Proxy && bHasFloor)
{
MakeFloor();
}
// Update gravity in case it changed
const FVector DefaultGravity( 0.f, 0.f, GetWorld()->GetGravityZ() );
PhysScene->SetUpForFrame(&DefaultGravity, DeltaTime, UPhysicsSettings::Get()->MinPhysicsDeltaTime, UPhysicsSettings::Get()->MaxPhysicsDeltaTime,
UPhysicsSettings::Get()->MaxSubstepDeltaTime, UPhysicsSettings::Get()->MaxSubsteps, UPhysicsSettings::Get()->bSubstepping);
PhysScene->StartFrame();
}
else
{
GetSolver()->AdvanceAndDispatch_External(DeltaTime);
}
RigidSolverProxy.PushDatas.Reset();
while(Chaos::FPushPhysicsData* PushData = RigidSolverProxy.Solver->GetMarshallingManager().StepInternalTime_External())
{
RigidSolverProxy.PushDatas.Add(PushData);
}
}
}
void AChaosSolverActor::ReadFromSimulation(const float DeltaTime, const bool bAsyncTask)
{
if(!bAsyncTask)
{
if (RigidSolverProxy.IsValid())
{
PhysScene->EndFrame();
}
}
}
#if WITH_EDITOR
void AChaosSolverActor::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
if(RigidSolverProxy.Solver && PropertyChangedEvent.Property)
{
if(PropertyChangedEvent.MemberProperty->GetFName() == GET_MEMBER_NAME_CHECKED(AChaosSolverActor, Properties))
{
RigidSolverProxy.Solver->EnqueueCommandImmediate([InSolver = RigidSolverProxy.Solver, InConfig = Properties]()
{
InSolver->ApplyConfig(InConfig);
});
}
else if(PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(AChaosSolverActor, bHasFloor))
{
if(Proxy)
{
RigidSolverProxy.Solver->UnregisterObject(Proxy);
Proxy = nullptr;
}
MakeFloor();
}
else if(PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(AChaosSolverActor, FloorHeight))
{
if(Proxy)
{
Proxy->GetGameThreadAPI().SetX(FVector(0.0f, 0.0f, FloorHeight));
}
}
else if(PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(AChaosSolverActor, SimulationAsset))
{
MigrateSolver();
}
}
}
bool AChaosSolverActor::CanEditChange(const FProperty* InProperty) const
{
if (!Super::CanEditChange(InProperty))
{
return false;
}
const FName& Name = InProperty->GetFName();
if (Name == GET_MEMBER_NAME_CHECKED(ThisClass, SimulationAsset))
{
static const auto CVarEnableSimulationDataflow = IConsoleManager::Get().FindConsoleVariable(TEXT("p.Dataflow.EnableSimulation"));
return CVarEnableSimulationDataflow->GetBool();
}
return true;
}
#if TODO_REIMPLEMENT_SERIALIZATION_FOR_PERF_TEST
#if !UE_BUILD_SHIPPING
void SerializeForPerfTest(const TArray< FString >&, UWorld* World, FOutputDevice&)
{
UE_LOG(LogChaos, Log, TEXT("Serializing for perf test:"));
const FString FileName(TEXT("ChaosPerf"));
//todo(mlentine): use this once chaos solver actors are in
for (TActorIterator<AChaosSolverActor> Itr(World); Itr; ++Itr)
{
Chaos::FPhysicsSolver* Solver = Itr->GetSolver();
Solver->EnqueueCommandImmediate([FileName, InSolver=Solver]() { InSolver->SerializeForPerfTest(FileName); });
}
}
FAutoConsoleCommand SerializeForPerfTestCommand(TEXT("p.SerializeForPerfTest"), TEXT(""), FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateStatic(&SerializeForPerfTest));
#endif
#endif
#endif