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

3037 lines
130 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "PBDRigidsSolver.h"
#include "Async/AsyncWork.h"
#include "Chaos/ChaosArchive.h"
#include "Chaos/Character/CharacterGroundConstraint.h"
#include "Chaos/PBDCollisionConstraintsUtil.h"
#include "Chaos/Utilities.h"
#include "Chaos/ChaosDebugDraw.h"
#include "Chaos/PBDRigidsEvolution.h"
#include "ChaosStats.h"
#include "ChaosSolversModule.h"
#include "ChaosVisualDebugger/ChaosVisualDebuggerTrace.h"
#include "HAL/FileManager.h"
#include "Misc/ScopeLock.h"
#include "PhysicsProxy/CharacterGroundConstraintProxy.h"
#include "PhysicsProxy/ClusterUnionPhysicsProxy.h"
#include "PhysicsProxy/SingleParticlePhysicsProxy.h"
#include "PhysicsProxy/SkeletalMeshPhysicsProxy.h"
#include "PhysicsProxy/StaticMeshPhysicsProxy.h"
#include "PhysicsProxy/GeometryCollectionPhysicsProxy.h"
#include "PhysicsProxy/PerSolverFieldSystem.h"
#include "EventDefaults.h"
#include "EventsData.h"
#include "RewindData.h"
#include "ChaosSolverConfiguration.h"
#include "Chaos/PullPhysicsDataImp.h"
#include "Chaos/PhysicsSolverBaseImpl.h"
#include "Chaos/ConvexOptimizer.h"
#include "Chaos/AsyncInitBodyHelper.h"
#include "Chaos/DebugDraw/DebugDrawParticle.h"
#include "ChaosDebugDraw/ChaosDDContext.h"
#include "ChaosDebugDraw/ChaosDDScene.h"
#include "ChaosDebugDraw/ChaosDDTimeline.h"
#include "ProfilingDebugging/CountersTrace.h"
#include "ProfilingDebugging/CsvProfiler.h"
#include "ChaosLog.h"
//PRAGMA_DISABLE_OPTIMIZATION
DEFINE_LOG_CATEGORY_STATIC(LogPBDRigidsSolver, Log, All);
// Stat Counters
DECLARE_DWORD_COUNTER_STAT(TEXT("NumDisabledBodies"), STAT_ChaosCounter_NumDisabledBodies, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumBodies"), STAT_ChaosCounter_NumBodies, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumDynamicBodies"), STAT_ChaosCounter_NumDynamicBodies, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumKinematicBodies"), STAT_ChaosCounter_NumKinematicBodies, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumStaticBodies"), STAT_ChaosCounter_NumStaticBodies, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumGeomCollBodies"), STAT_ChaosCounter_NumGeometryCollectionBodies, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumMovingBodies"), STAT_ChaosCounter_NumMovingBodies, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumStaticShapes"), STAT_ChaosCounter_NumStaticShapes, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumKinematicShapes"), STAT_ChaosCounter_NumKinematicShapes, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumDynamicShapes"), STAT_ChaosCounter_NumDynamicShapes, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumIslands"), STAT_ChaosCounter_NumIslands, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumIslandGroups"), STAT_ChaosCounter_NumIslandGroups, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumContacts"), STAT_ChaosCounter_NumContacts, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumValidConstraints"), STAT_ChaosCounter_NumValidConstraints, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumActiveConstraints"), STAT_ChaosCounter_NumActiveConstraints, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumRestoredConstraints"), STAT_ChaosCounter_NumRestoredConstraints, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumManifoldPoints"), STAT_ChaosCounter_NumManifoldPoints, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumActiveManifoldPoints"), STAT_ChaosCounter_NumActiveManifoldPoints, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumRestoredManifoldPoints"), STAT_ChaosCounter_NumRestoredManifoldPoints, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumUpdatedManifoldPoints"), STAT_ChaosCounter_NumUpdatedManifoldPoints, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumJoints"), STAT_ChaosCounter_NumJoints, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumCharacterGroundConstraints"), STAT_ChaosCounter_NumCharacterGroundConstraints, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumBroadPhasePairs"), STAT_ChaosCounter_NumBroadPhasePairs, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumMidPhases"), STAT_ChaosCounter_NumMidPhases, STATGROUP_ChaosCounters);
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumDisabledBodies, TEXT("Chaos/Solver/Bodies/NumDisabled"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumBodies, TEXT("Chaos/Solver/Bodies/Num"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumStaticBodies, TEXT("Chaos/Solver/Bodies/NumStatic"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumKinematicBodies, TEXT("Chaos/Solver/Bodies/NumKinematic"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumDynamicBodies, TEXT("Chaos/Solver/Bodies/NumDynamic"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumGeometryCollectionBodies, TEXT("Chaos/Solver/Bodies/NumGC"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumMovingBodies, TEXT("Chaos/Solver/Bodies/NumMoving"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumStaticShapes, TEXT("Chaos/Solver/Collisions/NumStaticShapes"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumKinematicShapes, TEXT("Chaos/Solver/Collisions/NumKinematicShapes"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumDynamicShapes, TEXT("Chaos/Solver/Collisions/NumDynamicShapes"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumIslands, TEXT("Chaos/Solver/Islands/NumIslands"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumIslandGroups, TEXT("Chaos/Solver/Islands/NumIslandGroups"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumContacts, TEXT("Chaos/Solver/Collisions/NumConstraints"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumValidConstraints, TEXT("Chaos/Solver/Collisions/NumValidConstraints"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumActiveConstraints, TEXT("Chaos/Solver/Collisions/NumActiveConstraints"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumRestoredConstraints, TEXT("Chaos/Solver/Collisions/NumRestoredConstraints"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumManifoldPoints, TEXT("Chaos/Solver/Collisions/NumManifoldPoints"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumActiveManifoldPoints, TEXT("Chaos/Solver/Collisions/NumActiveManifoldPoints"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumRestoredManifoldPoints, TEXT("Chaos/Solver/Collisions/NumRestoredManifoldPoints"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumUpdatedManifoldPoints, TEXT("Chaos/Solver/Collisions/NumUpdatedManifoldPoints"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumJoints, TEXT("Chaos/Solver/Joints/NumConstraints"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumCharacterGroundConstraints, TEXT("Chaos/Solver/Character/NumConstraints"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumBroadPhasePairs, TEXT("Chaos/Solver/Collisions/NumBroadPhasePairs"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumMidPhases, TEXT("Chaos/Solver/Collisions/NumMidPhases"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_MidPhase_NumShapePair, TEXT("Chaos/Solver/MidPhase/NumShapePair"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_MidPhase_NumGeneric, TEXT("Chaos/Solver/MidPhase/NumGeneric"));
// Stat Iteration counters
DECLARE_DWORD_COUNTER_STAT(TEXT("NumPositionIterations"), STAT_ChaosCounter_NumPositionIterations, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumVelocityIterations"), STAT_ChaosCounter_NumVelocityIterations, STATGROUP_ChaosCounters);
DECLARE_DWORD_COUNTER_STAT(TEXT("NumProjectionIterations"), STAT_ChaosCounter_NumProjectionIterations, STATGROUP_ChaosCounters);
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumPositionIterations, TEXT("Chaos/Solver/Iterations/NumPosition"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumVelocityIterations, TEXT("Chaos/Solver/Iterations/NumVelocity"));
TRACE_DECLARE_INT_COUNTER(ChaosTraceCounter_NumProjectionIterations, TEXT("Chaos/Solver/Iterations/NumProjection"));
// DebugDraw CVars
#if CHAOS_DEBUG_DRAW
// Must be 0 when checked in...
#define CHAOS_SOLVER_ENABLE_DEBUG_DRAW 0
namespace Chaos
{
extern CHAOS_API bool bChaosDebugDraw_UseNewQueue;
namespace DebugDraw
{
extern const FChaosDebugDrawColorsByState& GetDefaultShapesColorsPreIntegrate();
extern const FChaosDebugDrawColorsByState& GetDefaultShapesColorsPostIntegrate();
extern const FChaosDebugDrawColorsByState& GetDefaultShapesColorsCollisionDetection();
}
namespace CVars
{
int32 ChaosSolverDebugDrawShapes = CHAOS_SOLVER_ENABLE_DEBUG_DRAW;
int32 ChaosSolverDebugDrawMass = 0;
int32 ChaosSolverDebugDrawDensity = 0;
int32 ChaosSolverDebugDrawBVHs = 0;
int32 ChaosSolverDebugDrawCollisions = CHAOS_SOLVER_ENABLE_DEBUG_DRAW;
int32 ChaosSolverDebugDrawCollidingShapes = 0;
int32 ChaosSolverDebugDrawBounds = 0;
int32 ChaosSolverDrawTransforms = 0;
int32 ChaosSolverDrawIslands = 0;
int32 ChaosSolverDebugDrawIslandSleepState = 0;
int32 ChaosSolverDrawCCDInteractions = 0;
int32 ChaosSolverDrawCCDThresholds = 0;
int32 ChaosSolverDrawShapesShowStatic = 1;
int32 ChaosSolverDrawShapesShowKinematic = 1;
int32 ChaosSolverDrawShapesShowDynamic = 1;
int32 ChaosSolverDrawJoints = 0;
int32 ChaosSolverDrawCharacterGroundConstraints = 0;
int32 ChaosSolverDebugDrawSpatialAccelerationStructure = 0;
int32 ChaosSolverDebugDrawSpatialAccelerationStructureShowLeaves = 0;
int32 ChaosSolverDebugDrawSpatialAccelerationStructureShowNodes = 0;
int32 ChaosSolverDebugDrawSuspensionConstraints = 0;
int32 ChaosSolverDrawClusterConstraints = 0;
int32 ChaosSolverDebugDrawMeshContacts = 0;
int32 ChaosSolverDebugDrawMeshContactDetails = 0;
int32 ChaosSolverDebugDrawMeshBVHOverlaps = 0;
int32 ChaosSolverDebugDrawColorShapeByClientServer = 0;
int32 ChaosSolverDebugDrawShowServer = 1;
int32 ChaosSolverDebugDrawShowClient = 1;
DebugDraw::FChaosDebugDrawJointFeatures ChaosSolverDrawJointFeatures = DebugDraw::FChaosDebugDrawJointFeatures::MakeDefault();
FAutoConsoleVariableRef CVarChaosSolverDrawShapes(TEXT("p.Chaos.Solver.DebugDrawShapes"), ChaosSolverDebugDrawShapes, TEXT("Draw Shapes (0 = never; 1 = end of frame)."));
FAutoConsoleVariableRef CVarChaosSolverDebugDrawMass(TEXT("p.Chaos.Solver.DebugDrawMass"), ChaosSolverDebugDrawMass, TEXT("Draw Mass values in Kg (0 = never; 1 = end of frame)."));
FAutoConsoleVariableRef CVarChaosSolverDebugDrawDensity(TEXT("p.Chaos.Solver.DebugDrawDensity"), ChaosSolverDebugDrawDensity, TEXT("Draw Density values in Kg/cm3 (0 = never; 1 = end of frame)."));
FAutoConsoleVariableRef CVarChaosSolverDrawBVHs(TEXT("p.Chaos.Solver.DebugDrawBVHs"), ChaosSolverDebugDrawBVHs, TEXT("Draw Particle BVHs where applicable (0 = never; 1 = end of frame)."));
FAutoConsoleVariableRef CVarChaosSolverDrawCollisions(TEXT("p.Chaos.Solver.DebugDrawCollisions"), ChaosSolverDebugDrawCollisions, TEXT("Draw Collisions (0 = never; 1 = end of frame)."));
FAutoConsoleVariableRef CVarChaosSolverDrawCollidingShapes(TEXT("p.Chaos.Solver.DebugDrawCollidingShapes"), ChaosSolverDebugDrawCollidingShapes, TEXT("Draw Shapes that have collisions on them (0 = never; 1 = end of frame)."));
FAutoConsoleVariableRef CVarChaosSolverDrawBounds(TEXT("p.Chaos.Solver.DebugDrawBounds"), ChaosSolverDebugDrawBounds, TEXT("Draw bounding volumes inside the broadphase (0 = never; 1 = end of frame)."));
FAutoConsoleVariableRef CVarChaosSolverDrawTransforms(TEXT("p.Chaos.Solver.DebugDrawTransforms"), ChaosSolverDrawTransforms, TEXT("Draw particle transforms (0 = never; 1 = end of frame)."));
FAutoConsoleVariableRef CVarChaosSolverDrawIslands(TEXT("p.Chaos.Solver.DebugDrawIslands"), ChaosSolverDrawIslands, TEXT("Draw solver islands (0 = never; 1 = end of frame)."));
FAutoConsoleVariableRef CVarChaosSolverDrawIslandSleepState(TEXT("p.Chaos.Solver.DebugDrawSleepState"), ChaosSolverDebugDrawIslandSleepState, TEXT("Draw island sleep state."));
FAutoConsoleVariableRef CVarChaosSolverDrawCCD(TEXT("p.Chaos.Solver.DebugDrawCCDInteractions"), ChaosSolverDrawCCDInteractions, TEXT("Draw CCD interactions."));
FAutoConsoleVariableRef CVarChaosSolverDrawCCDThresholds(TEXT("p.Chaos.Solver.DebugDrawCCDThresholds"), ChaosSolverDrawCCDThresholds, TEXT("Draw CCD swept thresholds."));
FAutoConsoleVariableRef CVarChaosSolverDrawShapesShapesStatic(TEXT("p.Chaos.Solver.DebugDraw.ShowStatics"), ChaosSolverDrawShapesShowStatic, TEXT("If DebugDrawShapes is enabled, whether to show static objects"));
FAutoConsoleVariableRef CVarChaosSolverDrawShapesShapesKinematic(TEXT("p.Chaos.Solver.DebugDraw.ShowKinematics"), ChaosSolverDrawShapesShowKinematic, TEXT("If DebugDrawShapes is enabled, whether to show kinematic objects"));
FAutoConsoleVariableRef CVarChaosSolverDrawShapesShapesDynamic(TEXT("p.Chaos.Solver.DebugDraw.ShowDynamics"), ChaosSolverDrawShapesShowDynamic, TEXT("If DebugDrawShapes is enabled, whether to show dynamic objects"));
FAutoConsoleVariableRef CVarChaosSolverDrawJoints(TEXT("p.Chaos.Solver.DebugDrawJoints"), ChaosSolverDrawJoints, TEXT("Draw joints"));
FAutoConsoleVariableRef CVarChaosSolverDrawCharacterGroundConstraints(TEXT("p.Chaos.Solver.DebugDrawCharacterGroundConstraints"), ChaosSolverDrawCharacterGroundConstraints, TEXT("Draw character ground constraints"));
FAutoConsoleVariableRef CVarChaosSolverDebugDrawSpatialAccelerationStructure(TEXT("p.Chaos.Solver.DebugDrawSpatialAccelerationStructure"), ChaosSolverDebugDrawSpatialAccelerationStructure, TEXT("Draw spatial acceleration structure"));
FAutoConsoleVariableRef CVarChaosSolverDebugDrawSpatialAccelerationStructureShowLeaves(TEXT("p.Chaos.Solver.DebugDrawSpatialAccelerationStructure.ShowLeaves"), ChaosSolverDebugDrawSpatialAccelerationStructureShowLeaves, TEXT("Show spatial acceleration structure leaves when its debug draw is enabled"));
FAutoConsoleVariableRef CVarChaosSolverDebugDrawSpatialAccelerationStructureShowNodes(TEXT("p.Chaos.Solver.DebugDrawSpatialAccelerationStructure.ShowNodes"), ChaosSolverDebugDrawSpatialAccelerationStructureShowNodes, TEXT("Show spatial acceleration structure nodes when its debug draw is enabled"));
FAutoConsoleVariableRef CVarChaosSolverDrawJointFeaturesCoMConnector(TEXT("p.Chaos.Solver.DebugDraw.JointFeatures.CoMConnector"), ChaosSolverDrawJointFeatures.bCoMConnector, TEXT("Joint features mask (see FDebugDrawJointFeatures)."));
FAutoConsoleVariableRef CVarChaosSolverDrawJointFeaturesActorConnector(TEXT("p.Chaos.Solver.DebugDraw.JointFeatures.ActorConnector"), ChaosSolverDrawJointFeatures.bActorConnector, TEXT("Joint features mask (see FDebugDrawJointFeatures)."));
FAutoConsoleVariableRef CVarChaosSolverDrawJointFeaturesStretch(TEXT("p.Chaos.Solver.DebugDraw.JointFeatures.Stretch"), ChaosSolverDrawJointFeatures.bStretch, TEXT("Joint features mask (see FDebugDrawJointFeatures)."));
FAutoConsoleVariableRef CVarChaosSolverDrawJointFeaturesAxes(TEXT("p.Chaos.Solver.DebugDraw.JointFeatures.Axes"), ChaosSolverDrawJointFeatures.bAxes, TEXT("Joint features mask (see FDebugDrawJointFeatures)."));
FAutoConsoleVariableRef CVarChaosSolverDrawJointFeaturesLevel(TEXT("p.Chaos.Solver.DebugDraw.JointFeatures.Level"), ChaosSolverDrawJointFeatures.bLevel, TEXT("Joint features mask (see FDebugDrawJointFeatures)."));
FAutoConsoleVariableRef CVarChaosSolverDrawJointFeaturesIndex(TEXT("p.Chaos.Solver.DebugDraw.JointFeatures.Index"), ChaosSolverDrawJointFeatures.bIndex, TEXT("Joint features mask (see FDebugDrawJointFeatures)."));
FAutoConsoleVariableRef CVarChaosSolverDrawJointFeaturesColor(TEXT("p.Chaos.Solver.DebugDraw.JointFeatures.Color"), ChaosSolverDrawJointFeatures.bColor, TEXT("Joint features mask (see FDebugDrawJointFeatures)."));
FAutoConsoleVariableRef CVarChaosSolverDrawJointFeaturesIsland(TEXT("p.Chaos.Solver.DebugDraw.JointFeatures.Island"), ChaosSolverDrawJointFeatures.bIsland, TEXT("Joint features mask (see FDebugDrawJointFeatures)."));
FAutoConsoleVariableRef CVarChaosSolverDrawSuspensionConstraints(TEXT("p.Chaos.Solver.DebugDrawSuspension"), ChaosSolverDebugDrawSuspensionConstraints, TEXT("Draw Suspension (0 = never; 1 = end of frame)."));
FAutoConsoleVariableRef CVarChaosSolverDrawClusterConstraints(TEXT("p.Chaos.Solver.DebugDraw.Cluster.Constraints"), ChaosSolverDrawClusterConstraints, TEXT("Draw Active Cluster Constraints (0 = never; 1 = end of frame)."));
FAutoConsoleVariableRef CVarChaosSolverDrawMeshContacts(TEXT("p.Chaos.Solver.DebugDrawMeshContacts"), ChaosSolverDebugDrawMeshContacts, TEXT("Draw Mesh contacts"));
FAutoConsoleVariableRef CVarChaosSolverDrawMeshContactDetails(TEXT("p.Chaos.Solver.DebugDrawMeshContactDetails"), ChaosSolverDebugDrawMeshContactDetails, TEXT("Draw Mesh contacts"));
FAutoConsoleVariableRef CVarChaosSolverDrawMeshBVHOverlaps(TEXT("p.Chaos.Solver.DebugDrawMeshBVHOverlaps"), ChaosSolverDebugDrawMeshBVHOverlaps, TEXT("Draw BVH of objects overlapping meshes"));
FAutoConsoleVariableRef CVarChaosSolverDebugDrawColorShapeByClientServer(TEXT("p.Chaos.Solver.DebugDraw.ColorShapeByClientServer"), ChaosSolverDebugDrawColorShapeByClientServer, TEXT("Color shape according to client and server: red = server / blue = client "));
FAutoConsoleVariableRef CVarChaosSolverDebugDrawShowServer(TEXT("p.Chaos.Solver.DebugDraw.ShowServer"), ChaosSolverDebugDrawShowServer, TEXT("Draw server related debug data"));
FAutoConsoleVariableRef CVarChaosSolverDebugDrawShowClient(TEXT("p.Chaos.Solver.DebugDraw.ShowClient"), ChaosSolverDebugDrawShowClient, TEXT("Draw client related debug data"));
int32 ChaosSolverDebugDrawPreIntegrationShapes = 0;
int32 ChaosSolverDebugDrawPreIntegrationCollisions = 0;
FAutoConsoleVariableRef CVarChaosSolverDrawPreIntegrationShapes(TEXT("p.Chaos.Solver.DebugDrawPreIntegrationShapes"), ChaosSolverDebugDrawPreIntegrationShapes, TEXT("Draw Shapes prior to integrate."));
FAutoConsoleVariableRef CVarChaosSolverDrawPreIntegrationCollisions(TEXT("p.Chaos.Solver.DebugDrawPreIntegrationCollisions"), ChaosSolverDebugDrawPreIntegrationCollisions, TEXT("Draw Collisions prior to integrate."));
int32 ChaosSolverDebugDrawPostIntegrationShapes = 0;
int32 ChaosSolverDebugDrawPostIntegrationCollisions = 0;
FAutoConsoleVariableRef CVarChaosSolverDrawPostIntegrationShapes(TEXT("p.Chaos.Solver.DebugDrawPostIntegrationShapes"), ChaosSolverDebugDrawPostIntegrationShapes, TEXT("Draw Shapes prior to constraint solve phase."));
FAutoConsoleVariableRef CVarChaosSolverDrawPostIntegrationCollisions(TEXT("p.Chaos.Solver.DebugDrawPostIntegrationCollisions"), ChaosSolverDebugDrawPostIntegrationCollisions, TEXT("Draw Collisions prior to constraint solve phase."));
DebugDraw::FChaosDebugDrawSettings ChaosSolverDebugDebugDrawSettings(
/* ArrowSize = */ 10.0f,
/* BodyAxisLen = */ 30.0f,
/* ContactLen = */ 30.0f,
/* ContactWidth = */ 6.0f,
/* ContactInfoWidth */ 6.0f,
/* ContactOwnerWidth = */ 0.0f,
/* ConstraintAxisLen = */ 30.0f,
/* JointComSize = */ 2.0f,
/* LineThickness = */ 1.0f,
/* DrawScale = */ 1.0f,
/* FontHeight = */ 10.0f,
/* FontScale = */ 1.5f,
/* ShapeThicknesScale = */ 1.0f,
/* PointSize = */ 5.0f,
/* VelScale = */ 0.0f,
/* AngVelScale = */ 0.0f,
/* ImpulseScale = */ 0.0f,
/* PushOutScale = */ 0.0f,
/* InertiaScale = */ 1.0f,
/* DrawPriority = */ 10,
/* bShowSimple = */ true,
/* bShowComplex = */ false,
/* bInShowLevelSetCollision = */ true,
/* InShapesColorsPerState = */ DebugDraw::GetDefaultShapesColorsByState(),
/* InShapesColorsPerShaepType= */ DebugDraw::GetDefaultShapesColorsByShapeType(),
/* InBoundsColorsPerState = */ DebugDraw::GetDefaultBoundsColorsByState(),
/* InBoundsColorsPerShapeType= */ DebugDraw::GetDefaultBoundsColorsByShapeType()
);
extern DebugDraw::FChaosDebugDrawColorsByState GetSolverShapesColorsByState_Server();
extern DebugDraw::FChaosDebugDrawColorsByState GetSolverShapesColorsByState_Client();
DebugDraw::FChaosDebugDrawColorsByState GetSolverShapesColorsByState_Server()
{
static DebugDraw::FChaosDebugDrawColorsByState SolverShapesColorsByState_Server
{
/* InDynamicColor = */ FColor(255, 0, 0),
/* InSleepingColor = */ FColor(128, 0, 0),
/* InKinematicColor = */ FColor(255, 0, 0),
/* InStaticColor = */ FColor(255, 0, 0),
/* InDebrisColor = */ FColor(255, 0, 0),
};
return SolverShapesColorsByState_Server;
}
DebugDraw::FChaosDebugDrawColorsByState GetSolverShapesColorsByState_Client()
{
static DebugDraw::FChaosDebugDrawColorsByState SolverShapesColorsByState_Client
{
/* InDynamicColor = */ FColor(0, 0, 255),
/* InSleepingColor = */ FColor(0, 0, 128),
/* InKinematicColor = */ FColor(0, 0, 255),
/* InStaticColor = */ FColor(0, 0, 255),
/* InDebrisColor = */ FColor(0, 0, 255),
};
return SolverShapesColorsByState_Client;
}
FAutoConsoleVariableRef CVarChaosSolverArrowSize(TEXT("p.Chaos.Solver.DebugDraw.ArrowSize"), ChaosSolverDebugDebugDrawSettings.ArrowSize, TEXT("ArrowSize."));
FAutoConsoleVariableRef CVarChaosSolverBodyAxisLen(TEXT("p.Chaos.Solver.DebugDraw.BodyAxisLen"), ChaosSolverDebugDebugDrawSettings.BodyAxisLen, TEXT("BodyAxisLen."));
FAutoConsoleVariableRef CVarChaosSolverContactLen(TEXT("p.Chaos.Solver.DebugDraw.ContactLen"), ChaosSolverDebugDebugDrawSettings.ContactLen, TEXT("ContactLen."));
FAutoConsoleVariableRef CVarChaosSolverContactWidth(TEXT("p.Chaos.Solver.DebugDraw.ContactWidth"), ChaosSolverDebugDebugDrawSettings.ContactWidth, TEXT("ContactWidth."));
FAutoConsoleVariableRef CVarChaosSolverContactInfoWidth(TEXT("p.Chaos.Solver.DebugDraw.ContactInfoWidth"), ChaosSolverDebugDebugDrawSettings.ContactInfoWidth, TEXT("ContactInfoWidth."));
FAutoConsoleVariableRef CVarChaosSolverContactOwnerWidth(TEXT("p.Chaos.Solver.DebugDraw.ContactOwnerWidth"), ChaosSolverDebugDebugDrawSettings.ContactOwnerWidth, TEXT("ContactOwnerWidth."));
FAutoConsoleVariableRef CVarChaosSolverConstraintAxisLen(TEXT("p.Chaos.Solver.DebugDraw.ConstraintAxisLen"), ChaosSolverDebugDebugDrawSettings.ConstraintAxisLen, TEXT("ConstraintAxisLen."));
FAutoConsoleVariableRef CVarChaosSolverLineThickness(TEXT("p.Chaos.Solver.DebugDraw.LineThickness"), ChaosSolverDebugDebugDrawSettings.LineThickness, TEXT("LineThickness."));
FAutoConsoleVariableRef CVarChaosSolverLineShapeThickness(TEXT("p.Chaos.Solver.DebugDraw.ShapeLineThicknessScale"), ChaosSolverDebugDebugDrawSettings.ShapeThicknesScale, TEXT("Shape lineThickness multiplier."));
FAutoConsoleVariableRef CVarChaosSolverPointSize(TEXT("p.Chaos.Solver.DebugDraw.PointSize"), ChaosSolverDebugDebugDrawSettings.PointSize, TEXT("Point size."));
FAutoConsoleVariableRef CVarChaosSolverVelScale(TEXT("p.Chaos.Solver.DebugDraw.VelScale"), ChaosSolverDebugDebugDrawSettings.VelScale, TEXT("If >0 show velocity when drawing particle transforms."));
FAutoConsoleVariableRef CVarChaosSolverAngVelScale(TEXT("p.Chaos.Solver.DebugDraw.AngVelScale"), ChaosSolverDebugDebugDrawSettings.AngVelScale, TEXT("If >0 show angular velocity when drawing particle transforms."));
FAutoConsoleVariableRef CVarChaosSolverImpulseScale(TEXT("p.Chaos.Solver.DebugDraw.ImpulseScale"), ChaosSolverDebugDebugDrawSettings.ImpulseScale, TEXT("If >0 show impulses when drawing collisions."));
FAutoConsoleVariableRef CVarChaosSolverPushOutScale(TEXT("p.Chaos.Solver.DebugDraw.PushOutScale"), ChaosSolverDebugDebugDrawSettings.PushOutScale, TEXT("If >0 show pushouts when drawing collisions."));
FAutoConsoleVariableRef CVarChaosSolverInertiaScale(TEXT("p.Chaos.Solver.DebugDraw.InertiaScale"), ChaosSolverDebugDebugDrawSettings.InertiaScale, TEXT("When DebugDrawTransforms is enabled, show the mass-normalized inertia matrix scaled by this amount."));
FAutoConsoleVariableRef CVarChaosSolverDrawPriority(TEXT("p.Chaos.Solver.DebugDraw.DrawPriority"), ChaosSolverDebugDebugDrawSettings.DrawPriority, TEXT("Draw Priority for debug draw shapes (0 draw at actual Z, +ve is closer to the screen)."));
FAutoConsoleVariableRef CVarChaosSolverScale(TEXT("p.Chaos.Solver.DebugDraw.Scale"), ChaosSolverDebugDebugDrawSettings.DrawScale, TEXT("Scale applied to all Chaos Debug Draw line lengths etc."));
FAutoConsoleVariableRef CVarChaosSolverShowSimple(TEXT("p.Chaos.Solver.DebugDraw.ShowSimple"), ChaosSolverDebugDebugDrawSettings.bShowSimpleCollision, TEXT("Whether to show simple collision is shape drawing is enabled"));
FAutoConsoleVariableRef CVarChaosSolverShowComplex(TEXT("p.Chaos.Solver.DebugDraw.ShowComplex"), ChaosSolverDebugDebugDrawSettings.bShowComplexCollision, TEXT("Whether to show complex collision is shape drawing is enabled"));
FAutoConsoleVariableRef CVarChaosSolverShowLevelSet(TEXT("p.Chaos.Solver.DebugDraw.ShowLevelSet"), ChaosSolverDebugDebugDrawSettings.bShowLevelSetCollision, TEXT("Whether to show levelset collision is shape drawing is enabled"));
}
}
#endif
namespace Chaos
{
namespace CVars
{
bool ChaosSolverUseParticlePool = true;
FAutoConsoleVariableRef CVarChaosSolverUseParticlePool(TEXT("p.Chaos.Solver.UseParticlePool"), ChaosSolverUseParticlePool, TEXT("Whether or not to use dirty particle pool (Optim)"));
int32 ChaosSolverParticlePoolNumFrameUntilShrink = 30;
FAutoConsoleVariableRef CVarChaosSolverParticlePoolNumFrameUntilShrink(TEXT("p.Chaos.Solver.ParticlePoolNumFrameUntilShrink"), ChaosSolverParticlePoolNumFrameUntilShrink, TEXT("Num Frame until we can potentially shrink the pool"));
// Enable/Disable collisions
bool bChaosSolverCollisionEnabled = true;
FAutoConsoleVariableRef CVarChaosSolverCollisionDisable(TEXT("p.Chaos.Solver.Collision.Enabled"), bChaosSolverCollisionEnabled, TEXT("Enable/Disable collisions in the main scene."));
// Shrink particle arrays every frame to recove rmemory when a scene changes significantly
bool bChaosSolverShrinkArrays = false;
float ChaosArrayCollectionMaxSlackFraction = 0.5f;
int32 ChaosArrayCollectionMinSlack = 100;
FAutoConsoleVariableRef CVarChaosSolverShrinkArrays(TEXT("p.Chaos.Solver.ShrinkArrays"), bChaosSolverShrinkArrays, TEXT("Enable/Disable particle array shrinking in the main scene"));
FAutoConsoleVariableRef CVarChaosSolverArrayCollectionMaxSlackFraction(TEXT("p.Chaos.ArrayCollection.MaxSlackFraction"), ChaosArrayCollectionMaxSlackFraction, TEXT("Shrink particle arrays if the number of slack elements exceeds the number of elements by this fraction"));
FAutoConsoleVariableRef CVarChaosSolverArrayCollectionMinSlack(TEXT("p.Chaos.ArrayCollection.MinSlack"), ChaosArrayCollectionMinSlack, TEXT("Do not reduce the size of particle arrays if it would leave less slack than this"));
// Iteration count cvars
// These override the engine config if >= 0
int32 ChaosSolverCollisionPositionFrictionIterations = -1;
int32 ChaosSolverCollisionVelocityFrictionIterations = -1;
int32 ChaosSolverCollisionPositionShockPropagationIterations = -1;
int32 ChaosSolverCollisionVelocityShockPropagationIterations = -1;
FAutoConsoleVariableRef CVarChaosSolverCollisionPositionFrictionIterations(TEXT("p.Chaos.Solver.Collision.PositionFrictionIterations"), ChaosSolverCollisionPositionFrictionIterations, TEXT("Override number of position iterations where friction is applied (if >= 0)"));
FAutoConsoleVariableRef CVarChaosSolverCollisionVelocityFrictionIterations(TEXT("p.Chaos.Solver.Collision.VelocityFrictionIterations"), ChaosSolverCollisionVelocityFrictionIterations, TEXT("Override number of velocity iterations where friction is applied (if >= 0)"));
FAutoConsoleVariableRef CVarChaosSolverCollisionPositionShockPropagationIterations(TEXT("p.Chaos.Solver.Collision.PositionShockPropagationIterations"), ChaosSolverCollisionPositionShockPropagationIterations, TEXT("Override number of position iterations where shock propagation is applied (if >= 0)"));
FAutoConsoleVariableRef CVarChaosSolverCollisionVelocityShockPropagationIterations(TEXT("p.Chaos.Solver.Collision.VelocityShockPropagationIterations"), ChaosSolverCollisionVelocityShockPropagationIterations, TEXT("Override number of velocity iterations where shock propagation is applied (if >= 0)"));
int32 ChaosSolverPositionIterations = -1;
FAutoConsoleVariableRef CVarChaosSolverIterations(TEXT("p.Chaos.Solver.Iterations.Position"), ChaosSolverPositionIterations, TEXT("Override number of solver position iterations (-1 to use config)"));
int32 ChaosSolverVelocityIterations = -1;
FAutoConsoleVariableRef CVarChaosSolverPushOutIterations(TEXT("p.Chaos.Solver.Iterations.Velocity"), ChaosSolverVelocityIterations, TEXT("Override number of solver velocity iterations (-1 to use config)"));
int32 ChaosSolverProjectionIterations = -1;
FAutoConsoleVariableRef CVarChaosSolverProjectionIterations(TEXT("p.Chaos.Solver.Iterations.Projection"), ChaosSolverProjectionIterations, TEXT("Override number of solver projection iterations (-1 to use config)"));
int32 ChaosSolverDeterministic = -1;
FAutoConsoleVariableRef CVarChaosSolverDeterministic(TEXT("p.Chaos.Solver.Deterministic"), ChaosSolverDeterministic, TEXT("Override determinism. 0: disabled; 1: enabled; -1: use config"));
// Copied from RBAN
FRealSingle ChaosSolverJointPositionTolerance = 0.025f;
FAutoConsoleVariableRef CVarChaosSolverJointPositionTolerance(TEXT("p.Chaos.Solver.Joint.PositionTolerance"), ChaosSolverJointPositionTolerance, TEXT("PositionTolerance."));
FRealSingle ChaosSolverJointAngleTolerance = 0.001f;
FAutoConsoleVariableRef CVarChaosSolverJointAngleTolerance(TEXT("p.Chaos.Solver.Joint.AngleTolerance"), ChaosSolverJointAngleTolerance, TEXT("AngleTolerance."));
FRealSingle ChaosSolverJointMinParentMassRatio = 0.2f;
FAutoConsoleVariableRef CVarChaosSolverJointMinParentMassRatio(TEXT("p.Chaos.Solver.Joint.MinParentMassRatio"), ChaosSolverJointMinParentMassRatio, TEXT("6Dof joint MinParentMassRatio (if > 0)"));
FRealSingle ChaosSolverJointMaxInertiaRatio = 5.0f;
FAutoConsoleVariableRef CVarChaosSolverJointMaxInertiaRatio(TEXT("p.Chaos.Solver.Joint.MaxInertiaRatio"), ChaosSolverJointMaxInertiaRatio, TEXT("6Dof joint MaxInertiaRatio (if > 0)"));
// Collision detection cvars
// Utility to support runtime changes to some high-level collision configuration that requires we update all existing collisions (actually we just destroy them)
// This is only intended for testing and debugging and handling the change is not fast.
bool bChaosCollisionConfigChanged = false;
FConsoleVariableDelegate OnCollisionConfigCVarChanged = FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* CVar) -> void { bChaosCollisionConfigChanged = true; });
// These override the engine config if >= 0
FRealSingle ChaosSolverCullDistance = -1.0f;
FAutoConsoleVariableRef CVarChaosSolverCullDistance(TEXT("p.Chaos.Solver.Collision.CullDistance"), ChaosSolverCullDistance, TEXT("Override cull distance (if >= 0)"), OnCollisionConfigCVarChanged);
// @todo(chaos): move to physics project settings and set these to -1 when we are settled on values...
FRealSingle ChaosSolverVelocityBoundsMultiplier = 1.0f;
FRealSingle ChaosSolverMaxVelocityBoundsExpansion = 3.0f; // This should probably be a fraction of object size (see FParticlePairMidPhase::GenerateCollisions)
FRealSingle ChaosSolverVelocityBoundsMultiplierMACD = 1.0f;
FRealSingle ChaosSolverMaxVelocityBoundsExpansionMACD = 1000.0f; // For use when Movement-Aware Collision Detection (MACD) is enabled
FAutoConsoleVariableRef CVarChaosSolverVelocityBoundsMultiplier(TEXT("p.Chaos.Solver.Collision.VelocityBoundsMultiplier"), ChaosSolverVelocityBoundsMultiplier, TEXT("Override velocity bounds multiplier (if >= 0)"), OnCollisionConfigCVarChanged);
FAutoConsoleVariableRef CVarChaosSolverMaxVelocityBoundsExpansion(TEXT("p.Chaos.Solver.Collision.MaxVelocityBoundsExpansion"), ChaosSolverMaxVelocityBoundsExpansion, TEXT("Override max velocity bounds expansion (if >= 0)"), OnCollisionConfigCVarChanged);
FAutoConsoleVariableRef CVarChaosSolverVelocityBoundsMultiplierMACD(TEXT("p.Chaos.Solver.Collision.VelocityBoundsMultiplierMACD"), ChaosSolverVelocityBoundsMultiplierMACD, TEXT("Override velocity bounds multiplier for MACD (if >= 0)"), OnCollisionConfigCVarChanged);
FAutoConsoleVariableRef CVarChaosSolverMaxVelocityBoundsExpansionMACD(TEXT("p.Chaos.Solver.Collision.MaxVelocityBoundsExpansionMACD"), ChaosSolverMaxVelocityBoundsExpansionMACD, TEXT("Override max velocity bounds expansion for MACD (if >= 0)"), OnCollisionConfigCVarChanged);
FRealSingle ChaosSolverMaxPushOutVelocity = -1.0f;
FAutoConsoleVariableRef CVarChaosSolverMaxPushOutVelocity(TEXT("p.Chaos.Solver.Collision.MaxPushOutVelocity"), ChaosSolverMaxPushOutVelocity, TEXT("Override max pushout velocity (if >= 0)"), OnCollisionConfigCVarChanged);
FRealSingle ChaosSolverDepenetrationVelocity = -1.0f;
FAutoConsoleVariableRef CVarChaosSolverInitialOverlapDepentrationVelocity(TEXT("p.Chaos.Solver.Collision.DepenetrationVelocity"), ChaosSolverDepenetrationVelocity, TEXT("Override initial overlap depenetration velocity (if >= 0)"), OnCollisionConfigCVarChanged);
int32 ChaosSolverCleanupCommandsOnDestruction = 1;
FAutoConsoleVariableRef CVarChaosSolverCleanupCommandsOnDestruction(TEXT("p.Chaos.Solver.CleanupCommandsOnDestruction"), ChaosSolverCleanupCommandsOnDestruction, TEXT("Whether or not to run internal command queue cleanup on solver destruction (0 = no cleanup, >0 = cleanup all commands)"));
int32 ChaosSolverCollisionDeferNarrowPhase = 0;
FAutoConsoleVariableRef CVarChaosSolverCollisionDeferNarrowPhase(TEXT("p.Chaos.Solver.Collision.DeferNarrowPhase"), ChaosSolverCollisionDeferNarrowPhase, TEXT("Create contacts for all broadphase pairs, perform NarrowPhase later."));
// Allow one-shot or incremental manifolds where supported (which depends on shape pair types)
int32 ChaosSolverCollisionUseManifolds = 1;
FAutoConsoleVariableRef CVarChaosSolverCollisionUseManifolds(TEXT("p.Chaos.Solver.Collision.UseManifolds"), ChaosSolverCollisionUseManifolds, TEXT("Enable/Disable use of manifolds in collision."));
// Allow manifolds to be reused between ticks
int32 ChaosSolverCollisionAllowManifoldUpdate = 1;
FAutoConsoleVariableRef CVarChaosSolverCollisionAllowManifoldUpdate(TEXT("p.Chaos.Solver.Collision.AllowManifoldUpdate"), ChaosSolverCollisionAllowManifoldUpdate, TEXT("Enable/Disable reuse of manifolds between ticks (for small movement)."));
// Enable/Disable CCD. Set to false to disable the system, regardless of particle settings
bool bChaosUseCCD = true;
FAutoConsoleVariableRef CVarChaosUseCCD(TEXT("p.Chaos.Solver.UseCCD"), bChaosUseCCD, TEXT("Global flag to turn CCD on or off. Default is true (on)"), OnCollisionConfigCVarChanged);
// Enable/Disable MACD (Motion-Aware Collision Detection). Set to false to disable the system, regardless of particle settings
bool bChaosUseMACD = true;
FAutoConsoleVariableRef CVarChaos_Collision_UseMACD(TEXT("p.Chaos.Solver.UseMACD"), bChaosUseMACD, TEXT("Global flag to turn Movement-Aware Collision Detection (MACD) on or off. Default is true (on)"), OnCollisionConfigCVarChanged);
// Use to force all collisions to use MACD for testing (must also have bChaosUseMACD enabled)
bool bChaosForceMACD = false;
FAutoConsoleVariableRef CVarChaos_Collision_ForceMACD(TEXT("p.Chaos.Solver.bChaosForceMACD"), bChaosForceMACD, TEXT("Force all collisions to use MACD for testing"), OnCollisionConfigCVarChanged);
// Joint cvars
float ChaosSolverJointMinSolverStiffness = 1.0f;
float ChaosSolverJointMaxSolverStiffness = 1.0f;
int32 ChaosSolverJointNumIterationsAtMaxSolverStiffness = 1;
bool bChaosSolverJointSolvePositionLast = true;
bool bChaosSolverJointUsePositionBasedDrives = true;
int32 ChaosSolverJointNumShockProagationIterations = 0;
FRealSingle ChaosSolverJointShockPropagation = -1.0f;
FAutoConsoleVariableRef CVarChaosSolverJointMinSolverStiffness(TEXT("p.Chaos.Solver.Joint.MinSolverStiffness"), ChaosSolverJointMinSolverStiffness, TEXT("Solver stiffness on first iteration, increases each iteration toward MaxSolverStiffness."));
FAutoConsoleVariableRef CVarChaosSolverJointMaxSolverStiffness(TEXT("p.Chaos.Solver.Joint.MaxSolverStiffness"), ChaosSolverJointMaxSolverStiffness, TEXT("Solver stiffness on last iteration, increases each iteration from MinSolverStiffness."));
FAutoConsoleVariableRef CVarChaosSolverJointNumIterationsAtMaxSolverStiffness(TEXT("p.Chaos.Solver.Joint.NumIterationsAtMaxSolverStiffness"), ChaosSolverJointNumIterationsAtMaxSolverStiffness, TEXT("How many iterations we want at MaxSolverStiffness."));
FAutoConsoleVariableRef CVarChaosSolverJointSolvePositionFirst(TEXT("p.Chaos.Solver.Joint.SolvePositionLast"), bChaosSolverJointSolvePositionLast, TEXT("Should we solve joints in position-then-rotation order (false) or rotation-then-position order (true, default)"));
FAutoConsoleVariableRef CVarChaosSolverJointUsePBDVelocityDrives(TEXT("p.Chaos.Solver.Joint.UsePBDDrives"), bChaosSolverJointUsePositionBasedDrives, TEXT("Whether to solve drives in the position or velocity phase of the solver (default true"));
FAutoConsoleVariableRef CVarChaosSolverJointNumShockPropagationIterations(TEXT("p.Chaos.Solver.Joint.NumShockPropagationIterations"), ChaosSolverJointNumShockProagationIterations, TEXT("How many iterations to enable SHockProagation for."));
FAutoConsoleVariableRef CVarChaosSolverJointShockPropagation(TEXT("p.Chaos.Solver.Joint.ShockPropagation"), ChaosSolverJointShockPropagation, TEXT("6Dof joint shock propagation override (if >= 0)."));
int32 ChaosVisualDebuggerEnable = 1;
FAutoConsoleVariableRef CVarChaosVisualDebuggerEnable(TEXT("p.Chaos.VisualDebuggerEnable"), ChaosVisualDebuggerEnable, TEXT("Enable/Disable pushing/saving data to the visual debugger"));
bool bChaosEnableOverrideSingleParticleTypeToCluster = false;
FAutoConsoleVariableRef CVarChaosEnableOverrideSingleParticleTypeToCluster(TEXT("p.Chaos.EnableOverrideSingleParticleTypeToCluster"), bChaosEnableOverrideSingleParticleTypeToCluster, TEXT("When enabled particles are promoted to cluster type so they may be used with cluster unions"));
// Enable a couple bug fixes with temporary roll-back just in case
bool bRemoveParticleFromMovingKinematicsOnDisable = true;
FAutoConsoleVariableRef CVarChaosRemoveParticleFromMovingKinematicsOnDisable(TEXT("p.Chaos.RemoveParticleFromMovingKinematicsOnDisable"), bRemoveParticleFromMovingKinematicsOnDisable, TEXT(""));
}
}
namespace PhysicsReplicationCVars
{
namespace ResimulationCVars
{
bool bApplyTargetsWhileResimulating = false;
static FAutoConsoleVariableRef CVarResimApplyTargetsWhileResimulating(TEXT("np2.Resim.ApplyTargetsWhileResimulating"), bApplyTargetsWhileResimulating, TEXT("If false, target states from the server are only applied on rewind. If true, target states from the server are applied during resimulation if there are any available."));
}
}
namespace Chaos
{
using namespace CVars;
CHAOS_API extern int32 SyncKinematicOnGameThread;
class AdvanceOneTimeStepTask : public FNonAbandonableTask
{
friend class FAutoDeleteAsyncTask<AdvanceOneTimeStepTask>;
public:
AdvanceOneTimeStepTask(
FPBDRigidsSolver* Scene
, const FReal DeltaTime
, const FSubStepInfo& SubStepInfo)
: MSolver(Scene)
, MDeltaTime(DeltaTime)
, MSubStepInfo(SubStepInfo)
{
UE_LOG(LogPBDRigidsSolver, Verbose, TEXT("AdvanceOneTimeStepTask::AdvanceOneTimeStepTask()"));
}
void DoWork()
{
LLM_SCOPE(ELLMTag::ChaosUpdate);
UE_LOG(LogPBDRigidsSolver, Verbose, TEXT("AdvanceOneTimeStepTask::DoWork()"));
MSolver->ResetStatCounters();
if (FRewindData* RewindData = MSolver->GetRewindData())
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
RewindData->ApplyInputs(MSolver->GetCurrentFrame(), MSolver->GetEvolution()->IsResetting());
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
MSolver->ApplyCallbacks_Internal();
{
SCOPE_CYCLE_COUNTER(STAT_UpdateParams);
FPBDPositionConstraints PositionTarget; // Dummy for now
TMap<int32, int32> TargetedParticles;
{
MSolver->FieldParameterUpdateCallback(PositionTarget, TargetedParticles);
}
for (FGeometryCollectionPhysicsProxy* GeoclObj : MSolver->GetGeometryCollectionPhysicsProxiesField_Internal())
{
GeoclObj->FieldParameterUpdateCallback(MSolver);
}
MSolver->GetGeometryCollectionPhysicsProxiesField_Internal().Reset();
MSolver->GetEvolution()->GetBroadPhase().GetIgnoreCollisionManager().ProcessPendingQueues(*MSolver);
}
{
//SCOPE_CYCLE_COUNTER(STAT_BeginFrame);
//MSolver->StartFrameCallback(MDeltaTime, MSolver->GetSolverTime());
}
if(FRewindData* RewindData = MSolver->GetRewindData())
{
RewindData->AdvanceFrame(MDeltaTime,[Evolution = MSolver->GetEvolution()]()
{
return Evolution->CreateExternalResimCache();
});
}
{
SCOPE_CYCLE_COUNTER(STAT_EvolutionAndKinematicUpdate);
// This needs to be before BeginFrame since there's a possibility that we'll need to invalidate particles when updating cluster unions.
// Invalidating particles will cause us to iterate through and remove active collisions. If this is after BeginFrame but before we run
// the next broadphase, we'll pass the epoch check but have an invalid index into a now-empty active constraints array.
MSolver->GetEvolution()->GetRigidClustering().UnionClusterGroups();
// Process any sleep/wake requests that came from the game thread
// NOTE: Must be before GetCollisionConstraints().BeginFrame() (because its behaviour depends on sleep state)
MSolver->GetEvolution()->GetIslandManager().UpdateExplicitSleep();
// clear out the collision constraints as they will be stale from last frame if AdvanceOneTimeStep never gets called due to TimeRemaining being less than MinDeltaTime
// @todo(chaos): maybe we can pull data at a better time instead to avoid collision-specific code here for event dispatch
MSolver->GetEvolution()->GetCollisionConstraints().BeginFrame();
// This outer loop can potentially cause the system to lose energy over integration
// in a couple of different cases.
//
// * If we have a timestep that's smaller than MinDeltaTime, then we just won't step.
// Yes, we'll lose some teeny amount of energy, but we'll avoid 1/dt issues.
//
// * If we have used all of our substeps but still have time remaining, then some
// energy will be lost.
const FReal MinDeltaTime = MSolver->GetMinDeltaTime_External();
const FReal MaxDeltaTime = MSolver->GetMaxDeltaTime_External();
int32 StepsRemaining = MSubStepInfo.bSolverSubstepped ? 1 : MSolver->GetMaxSubSteps_External();
FReal TimeRemaining = MDeltaTime;
bool bFirstStep = true;
while (StepsRemaining > 0 && TimeRemaining > MinDeltaTime)
{
--StepsRemaining;
const FReal DeltaTime = MaxDeltaTime > 0.f ? FMath::Min(TimeRemaining, MaxDeltaTime) : TimeRemaining;
TimeRemaining -= DeltaTime;
{
MSolver->FieldForcesUpdateCallback();
}
for (FGeometryCollectionPhysicsProxy* GeoCollectionObj : MSolver->GetGeometryCollectionPhysicsProxiesField_Internal())
{
GeoCollectionObj->FieldForcesUpdateCallback(MSolver);
}
MSolver->GetGeometryCollectionPhysicsProxiesField_Internal().Reset();
if(FRewindData* RewindData = MSolver->GetRewindData())
{
//todo: make this work with sub-stepping
MSolver->GetEvolution()->SetCurrentStepResimCache(bFirstStep ? RewindData->GetCurrentStepResimCache() : nullptr);
}
MSolver->GetEvolution()->AdvanceOneTimeStep(DeltaTime, MSubStepInfo);
bFirstStep = false;
}
// Editor will tick with 0 DT, this will guarantee acceleration structure is still processing even if we don't advance evolution.
if (MDeltaTime < MinDeltaTime)
{
MSolver->GetEvolution()->ComputeIntermediateSpatialAcceleration();
}
#if CHAOS_CHECKED
// If time remains, then log why we have lost energy over the timestep.
if (TimeRemaining > 0.f)
{
if (StepsRemaining == 0)
{
UE_LOG(LogPBDRigidsSolver, Warning, TEXT("AdvanceOneTimeStepTask::DoWork() - Energy lost over %fs due to too many substeps over large timestep"), TimeRemaining);
}
else
{
UE_LOG(LogPBDRigidsSolver, Warning, TEXT("AdvanceOneTimeStepTask::DoWork() - Energy lost over %fs due to small timestep remainder"), TimeRemaining);
}
}
#endif
}
{
SCOPE_CYCLE_COUNTER(STAT_EventDataGathering);
{
SCOPE_CYCLE_COUNTER(STAT_FillProducerData);
// The Game Thread is now in charge to reset the producer buffer
constexpr bool bResetProducerData = false;
MSolver->GetEventManager()->FillProducerData(MSolver, bResetProducerData);
MSolver->GetEvolution()->ResetAllRemovals();
}
}
{
SCOPE_CYCLE_COUNTER(STAT_EndFrame);
MSolver->GetEvolution()->EndFrame(MDeltaTime);
}
{
SCOPE_CYCLE_COUNTER(STAT_FinalizeCallbacks);
MSolver->FinalizeCallbackData_Internal();
}
MSolver->SetSolverTime(MSolver->GetSolverTime() + MDeltaTime );
MSolver->GetCurrentFrame()++;
MSolver->PostTickDebugDraw(MDeltaTime);
MSolver->UpdateStatCounters();
//Editor ticks with 0 dt. We don't want to buffer any dirty data from this since it won't be consumed
//TODO: handle this more gracefully
if(MDeltaTime > 0)
{
MSolver->CompleteSceneSimulation();
}
{
SCOPE_CYCLE_COUNTER(STAT_ResetClusteringEvents);
// reset all clustering events needs to be after CompleteSceneSimulation to make sure the cache recording gets them before they get removed
// they cannot be right after the presolve callback ( as they were before ) because they will cause geometry collection replicated clients to miss them
// Todo(chaos) we should probably move all of the solver event reset here in the future
MSolver->GetEvolution()->GetRigidClustering().ResetAllEvents();
}
// Recover unused memory from particle arrays, based on the array shrink policy
if (bChaosSolverShrinkArrays)
{
QUICK_SCOPE_CYCLE_COUNTER(ShrinkParticleArrays);
MSolver->GetParticles().ShrinkArrays(CVars::ChaosArrayCollectionMaxSlackFraction, CVars::ChaosArrayCollectionMinSlack);
}
if (FRewindData* RewindData = MSolver->GetRewindData())
{
SCOPE_CYCLE_COUNTER(STAT_RewindFinishFrame);
RewindData->FinishFrame();
}
}
protected:
TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(AdvanceOneTimeStepTask, STATGROUP_ThreadPoolAsyncTasks);
}
FPBDRigidsSolver* MSolver;
FReal MDeltaTime;
FSubStepInfo MSubStepInfo;
TSharedPtr<FCriticalSection> PrevLock, CurrentLock;
TSharedPtr<FEvent> PrevEvent, CurrentEvent;
};
FPBDRigidsSolver::FPBDRigidsSolver(const EMultiBufferMode BufferingModeIn, UObject* InOwner, FReal InAsyncDt)
: Super(BufferingModeIn, BufferingModeIn == EMultiBufferMode::Single ? EThreadingModeTemp::SingleThread : EThreadingModeTemp::TaskGraph, InOwner, InAsyncDt)
, PhysSceneHack(nullptr)
, CurrentFrame(0)
, bHasFloor(true)
, bIsFloorAnalytic(false)
, FloorHeight(0.f)
, bIsDeterministic(false)
, Particles(UniqueIndices)
, MEvolution(new FPBDRigidsEvolution(Particles, SimMaterials, &MidPhaseModifiers, &CCDModifiers, &StrainModifiers, &ContactModifiers, BufferingModeIn == EMultiBufferMode::Single))
, MEventManager(new FEventManager(BufferingModeIn))
, MSolverEventFilters(new FSolverEventFilters())
, MDirtyParticlesBuffer(new FDirtyParticlesBuffer(BufferingModeIn, BufferingModeIn == EMultiBufferMode::Single))
, MCurrentLock(new FCriticalSection())
, PerSolverField(nullptr)
, Serializer(this)
{
UE_LOG(LogPBDRigidsSolver, Verbose, TEXT("PBDRigidsSolver::PBDRigidsSolver()"));
Reset();
MEvolution->SetInternalParticleInitilizationFunction(
[this](const FGeometryParticleHandle* OldParticle, FGeometryParticleHandle* NewParticle)
{
IPhysicsProxyBase* Proxy = const_cast<IPhysicsProxyBase*>(OldParticle->PhysicsProxy());
if (FPBDRigidClusteredParticleHandle* NewClusteredParticle = NewParticle->CastToClustered())
{
NewClusteredParticle->AddPhysicsProxy(Proxy);
}
NewParticle->SetPhysicsProxy(Proxy);
});
MEvolution->SetPreIntegrateCallback(
[this](FReal Dt)
{
OnEvolutionPreIntegrate(Dt);
});
MEvolution->SetPostIntegrateCallback(
[this](FReal Dt)
{
OnEvolutionPostIntegrate(Dt);
});
MEvolution->SetPreSolveCallback(
[this](FReal Dt)
{
for (ISimCallbackObject* Callback : SimCallbackObjects)
{
if (Callback->HasOption(ESimCallbackOptions::PreSolve))
{
FScopedTraceSolverCallback TraceCallback(Callback);
Callback->PreSolve_Internal();
}
}
PreSolveDebugDraw(Dt);
});
MEvolution->SetPostSolveCallback(
[this](FReal Dt)
{
for (ISimCallbackObject* Callback : SimCallbackObjects)
{
if (Callback->HasOption(ESimCallbackOptions::PostSolve))
{
FScopedTraceSolverCallback TraceCallback(Callback);
Callback->PostSolve_Internal();
}
}
});
}
void FPBDRigidsSolver::OnEvolutionPreIntegrate(FReal Dt)
{
for (ISimCallbackObject* Callback : SimCallbackObjects)
{
if (Callback->HasOption(ESimCallbackOptions::PreIntegrate))
{
FScopedTraceSolverCallback TraceCallback(Callback);
Callback->PreIntegrate_Internal();
}
}
PreIntegrateDebugDraw(Dt);
}
void FPBDRigidsSolver::OnEvolutionPostIntegrate(FReal Dt)
{
for (ISimCallbackObject* Callback : SimCallbackObjects)
{
if (Callback->HasOption(ESimCallbackOptions::PostIntegrate))
{
FScopedTraceSolverCallback TraceCallback(Callback);
Callback->PostIntegrate_Internal();
}
}
}
#if CHAOS_DEBUG_NAME
void FPBDRigidsSolver::OnDebugNameChanged()
{
MEvolution->SetName(GetDebugName().ToString());
}
#endif
FRealSingle MaxBoundsForTree = (FRealSingle)10000;
FAutoConsoleVariableRef CVarMaxBoundsForTree(
TEXT("p.MaxBoundsForTree"),
MaxBoundsForTree,
TEXT("The max bounds before moving object into a large objects structure. Only applies on object registration")
TEXT(""),
ECVF_Default);
void FPBDRigidsSolver::RegisterObject(FSingleParticlePhysicsProxy* Proxy)
{
UE_CHAOS_ASYNC_INITBODY_WRITESCOPELOCK(GetExternalDataLock_External());
LLM_SCOPE(ELLMTag::ChaosBody);
UE_LOG(LogPBDRigidsSolver, Verbose, TEXT("FPBDRigidsSolver::RegisterObject()"));
auto& RigidBody_External = Proxy->GetGameThreadAPI();
if (BroadPhaseConfig.BroadphaseType < FBroadPhaseConfig::TreeAndGrid)
{
FSpatialAccelerationIdx Idx = RigidBody_External.SpatialIdx();
Idx.Bucket = 0;
RigidBody_External.SetSpatialIdx(Idx);
}
else if (RigidBody_External.GetGeometry() && RigidBody_External.GetGeometry()->HasBoundingBox() && RigidBody_External.GetGeometry()->BoundingBox().Extents().Max() >= MaxBoundsForTree)
{
RigidBody_External.SetSpatialIdx(FSpatialAccelerationIdx{ 1,0 });
}
if (!ensure(Proxy->GetParticle_LowLevel()->IsParticleValid()))
{
return;
}
// NOTE: Do we really need these lists of proxies if we can just
// access them through the GTParticles list?
// Evolution should be valid at this point, adding this check to avoid a server crash
if (!ensure(GetEvolution()))
{
UE_LOG(LogPBDRigidsSolver, Warning, TEXT("FPBDRigidsSolver::RegisterObject: Regiester a proxy while Evolution is still null."));
return;
}
RigidBody_External.SetUniqueIdx(GetEvolution()->GenerateUniqueIdx());
TrackGTParticle_External(*Proxy->GetParticle_LowLevel()); //todo: remove this
//FParticlePropertiesData& RemoteParticleData = *DirtyPropertiesManager->AccessProducerBuffer()->NewRemoteParticleProperties();
//FShapeRemoteDataContainer& RemoteShapeContainer = *DirtyPropertiesManager->AccessProducerBuffer()->NewRemoteShapeContainer();
Proxy->SetSolver(this);
Proxy->GetParticle_LowLevel()->SetProxy(Proxy);
AddDirtyProxy(Proxy);
UpdateParticleInAccelerationStructure_External(Proxy->GetParticle_LowLevel(), EPendingSpatialDataOperation::Add);
}
int32 LogCorruptMap = 0;
FAutoConsoleVariableRef CVarLogCorruptMap(TEXT("p.LogCorruptMap"), LogCorruptMap, TEXT(""));
void FPBDRigidsSolver::ApplyCallbacks_Internal()
{
Super::ApplyCallbacks_Internal();
if (MRewindCallback) // Note: Don't use ShouldApplyRewindCallbacks() here since we want this called even if we don't have RewindData enabled.
{
if (bGameThreadFrozen)
{
MRewindCallback->ApplyCallbacks_Internal(GetCurrentFrame(), SimCallbackObjects);
}
}
}
FSolverSerializer& FPBDRigidsSolver::GetSerializer()
{
return Serializer;
}
bool bResimDelayDestroyObject = true;
FAutoConsoleVariableRef CVarResimDelayDestroyObject(TEXT("p.Resim.DelayDestroyObject"), bResimDelayDestroyObject, TEXT("Keep physics objects and physics proxy available until after they go out of rewind history. Disabled by default due to not fully implemented."));
void FPBDRigidsSolver::UnregisterObject(FSingleParticlePhysicsProxy* Proxy)
{
UE_CHAOS_ASYNC_INITBODY_WRITESCOPELOCK(GetExternalDataLock_External());
UE_LOG(LogPBDRigidsSolver, Verbose, TEXT("FPBDRigidsSolver::UnregisterObject()"));
PullResultsManager->RemoveProxy_External(Proxy);
ClearGTParticle_External(*Proxy->GetParticle_LowLevel()); //todo: remove this
UpdateParticleInAccelerationStructure_External(Proxy->GetParticle_LowLevel(), EPendingSpatialDataOperation::Delete);
// remove the proxy from the invalidation list
RemoveDirtyProxy(Proxy);
// mark proxy timestamp so we avoid trying to pull from sim after deletion
Proxy->MarkDeleted();
// Null out the particle's proxy pointer
Proxy->GetParticle_LowLevel()->SetProxy(nullptr); //todo: use TUniquePtr for better ownership
// Remove the proxy from the GT proxy map
FUniqueIdx UniqueIdx = Proxy->GetGameThreadAPI().UniqueIdx();
FIgnoreCollisionManager& CollisionManager = GetEvolution()->GetBroadPhase().GetIgnoreCollisionManager();
{
int32 ExternalTimestamp = GetMarshallingManager().GetExternalTimestamp_External();
FIgnoreCollisionManager::FDeactivationSet& PendingMap = CollisionManager.GetPendingDeactivationsForGameThread(ExternalTimestamp);
PendingMap.Add(UniqueIdx);
}
// Enqueue a command to remove the particle and delete the proxy
EnqueueCommandImmediate([Proxy, UniqueIdx, this]()
{
UE_LOG(LogPBDRigidsSolver, Verbose, TEXT("FPBDRigidsSolver::UnregisterObject() ~ Dequeue"));
// Generally need to remove stale events for particles that no longer exist
GetEventManager()->template ClearEvents<FCollisionEventData>(EEventType::Collision, [Proxy]
(FCollisionEventData& EventDataInOut)
{
FCollisionDataArray const& CollisionData = EventDataInOut.CollisionData.AllCollisionsArray;
if (CollisionData.Num() > 0)
{
check(Proxy);
TArray<int32> const* const CollisionIndices = EventDataInOut.PhysicsProxyToCollisionIndices.PhysicsProxyToIndicesMap.Find(Proxy);
if (CollisionIndices)
{
EventDataInOut.PhysicsProxyToCollisionIndices.PhysicsProxyToIndicesMap.Remove(Proxy);
}
}
});
FGeometryParticleHandle* Handle = Proxy->GetHandle_LowLevel();
if (MRewindData)
{
MRewindData->RemoveObject(Handle);
}
Proxy->SetHandle(nullptr);
const int32 OffsetForRewind = (bResimDelayDestroyObject && MRewindData) ? MRewindData->Capacity() : 0;
PendingDestroyPhysicsProxy.Add(FPendingDestroyInfo{Proxy, GetCurrentFrame() + OffsetForRewind, Handle, UniqueIdx});
//If particle was created and destroyed before commands were enqueued just skip. I suspect we can skip entire lambda, but too much code to verify right now
if(Handle)
{
//Disable until particle is finally destroyed
GetEvolution()->DisableParticle(Handle);
}
// Remove the proxy for this particle
if (SingleParticlePhysicsProxies_PT.IsValidIndex(UniqueIdx.Idx))
{
SingleParticlePhysicsProxies_PT.RemoveAt(UniqueIdx.Idx);
}
});
}
void FPBDRigidsSolver::RegisterObject(FGeometryCollectionPhysicsProxy* InProxy)
{
LLM_SCOPE(ELLMTag::ChaosBody);
UE_LOG(LogPBDRigidsSolver, Verbose, TEXT("FPBDRigidsSolver::RegisterObject(FGeometryCollectionPhysicsProxy*)"));
InProxy->SetSolver(this);
InProxy->Initialize(GetEvolution());
InProxy->NewData(); // Buffers data on the proxy.
// Need to immediately add to the SQ to make sure that the GC is in the external SQ even after the SQ's flip.
for (const TUniquePtr<FPBDRigidParticle>& Particle : InProxy->GetUnorderedParticles_External())
{
if (Particle && !Particle->Disabled())
{
UpdateParticleInAccelerationStructure_External(Particle.Get(), EPendingSpatialDataOperation::Add);
}
}
// There used to be an EnqueueCommandImmediate here to push the initialization of the GC
// onto the physics thread. We should use AddDirtyProxy here instead to better match up with
// the initialization order done in the ProcessSinglePushedData_Internal. Using EnqueueCommandImmediate
// will cause the GC to be initialized after constraints which would preclude it from being
// used with joint constraints that get spawned on level load with the GC.
AddDirtyProxy(InProxy);
}
void FPBDRigidsSolver::UnregisterObject(FGeometryCollectionPhysicsProxy* InProxy)
{
check(InProxy);
// mark proxy timestamp so we avoid trying to pull from sim after deletion
InProxy->MarkDeleted();
RemoveDirtyProxy(InProxy);
InProxy->OnUnregisteredFromSolver();
// Particles are removed from acceleration structure in FPhysScene_Chaos::RemoveObject.
EnqueueCommandImmediate([InProxy, this]()
{
GeometryCollectionPhysicsProxies_Internal.RemoveSingle(InProxy);
InProxy->SyncBeforeDestroy();
PendingDestroyGeometryCollectionPhysicsProxy.Add(InProxy);
});
}
void FPBDRigidsSolver::RegisterObject(FClusterUnionPhysicsProxy* Proxy)
{
if (!Proxy)
{
return;
}
if (FPBDRigidParticle* Particle = Proxy->GetParticle_External())
{
if (AccelerationStructureSplitStaticAndDynamic == 1)
{
// It needs to be {0, 1}. Things will crash otherwise. I don't know why.
// Probably similarly to the geometry collection physics proxy.
Particle->SetSpatialIdx(FSpatialAccelerationIdx{ 0, 1 });
}
else
{
Particle->SetSpatialIdx(FSpatialAccelerationIdx{ 0, 0 });
}
Particle->SetUniqueIdx(GetEvolution()->GenerateUniqueIdx());
UpdateParticleInAccelerationStructure_External(Particle, EPendingSpatialDataOperation::Add);
}
Proxy->SetSolver(this);
AddDirtyProxy(Proxy);
}
void FPBDRigidsSolver::UnregisterObject(FClusterUnionPhysicsProxy* Proxy)
{
if (!Proxy)
{
return;
}
Proxy->MarkDeleted();
RemoveDirtyProxy(Proxy);
if (FPBDRigidParticle* GTParticle = Proxy->GetParticle_External())
{
GTParticle->SetProxy(nullptr);
}
EnqueueCommandImmediate(
[Proxy, this]()
{
if (Proxy->GetParticle_Internal())
{
MEvolution->DisableParticle(Proxy->GetParticle_Internal());
}
ClusterUnionPhysicsProxies_Internal.RemoveSingle(Proxy);
Proxy->SyncBeforeDestroy();
PendingDestroyClusterUnionProxy.Add(Proxy);
}
);
}
void FPBDRigidsSolver::RegisterObject(FJointConstraint* GTConstraint)
{
LLM_SCOPE(ELLMTag::ChaosConstraint);
FJointConstraintPhysicsProxy* JointProxy = new FJointConstraintPhysicsProxy(GTConstraint, nullptr);
JointProxy->SetSolver(this);
AddDirtyProxy(JointProxy);
}
void FPBDRigidsSolver::UnregisterObject(FJointConstraint* GTConstraint)
{
FJointConstraintPhysicsProxy* JointProxy = GTConstraint->GetProxy<FJointConstraintPhysicsProxy>();
check(JointProxy);
RemoveDirtyProxy(JointProxy);
// mark proxy timestamp so we avoid trying to pull from sim after deletion
GTConstraint->GetProxy()->MarkDeleted();
GTConstraint->SetProxy(static_cast<FJointConstraintPhysicsProxy*>(nullptr));
GTConstraint->ReleaseKinematicEndPoint(this);
JointProxy->DestroyOnGameThread(); //destroy the game thread portion of the proxy
// Finish de-registration on the physics thread...
EnqueueCommandImmediate([JointProxy, this]()
{
//TODO: consider deferring this so that rewind can resim with joint until moment of destruction
//For now we assume this always comes from server update
if(FRewindData* RewindData = GetRewindData())
{
RewindData->RemoveObject(JointProxy->GetHandle());
}
JointProxy->DestroyOnPhysicsThread(this);
JointConstraintPhysicsProxies_Internal.RemoveSingle(JointProxy);
delete JointProxy;
});
}
void FPBDRigidsSolver::RegisterObject(FCharacterGroundConstraint* GTConstraint)
{
LLM_SCOPE(ELLMTag::ChaosConstraint);
FCharacterGroundConstraintProxy* ConstraintProxy = new FCharacterGroundConstraintProxy(GTConstraint);
ConstraintProxy->SetSolver(this);
AddDirtyProxy(ConstraintProxy);
}
void FPBDRigidsSolver::UnregisterObject(FCharacterGroundConstraint* GTConstraint)
{
FCharacterGroundConstraintProxy* ConstraintProxy = GTConstraint->GetProxy<FCharacterGroundConstraintProxy>();
check(ConstraintProxy);
RemoveDirtyProxy(ConstraintProxy);
// mark proxy timestamp so we avoid trying to pull from sim after deletion
GTConstraint->GetProxy()->MarkDeleted();
GTConstraint->SetProxy(static_cast<FCharacterGroundConstraintProxy*>(nullptr));
ConstraintProxy->DestroyOnGameThread(); //destroy the game thread portion of the proxy
// Finish de-registration on the physics thread...
EnqueueCommandImmediate([ConstraintProxy, this]()
{
// TODO: Add character ground constraint to rewind data
//if (FRewindData* RewindData = GetRewindData())
//{
// RewindData->RemoveObject(ConstraintProxy->GetPhysicsThreadAPI());
//}
ConstraintProxy->DestroyOnPhysicsThread(this);
CharacterGroundConstraintProxies_Internal.RemoveSingle(ConstraintProxy);
delete ConstraintProxy;
});
}
void FPBDRigidsSolver::RegisterObject(FSuspensionConstraint* GTConstraint)
{
LLM_SCOPE(ELLMTag::ChaosConstraint);
FSuspensionConstraintPhysicsProxy* SuspensionProxy = new FSuspensionConstraintPhysicsProxy(GTConstraint, nullptr);
SuspensionProxy->SetSolver(this);
AddDirtyProxy(SuspensionProxy);
}
void FPBDRigidsSolver::UnregisterObject(FSuspensionConstraint* GTConstraint)
{
FSuspensionConstraintPhysicsProxy* SuspensionProxy = GTConstraint->GetProxy<FSuspensionConstraintPhysicsProxy>();
check(SuspensionProxy);
// mark proxy timestamp so we avoid trying to pull from sim after deletion
SuspensionProxy->MarkDeleted();
RemoveDirtyProxy(SuspensionProxy);
GTConstraint->SetProxy(static_cast<FSuspensionConstraintPhysicsProxy*>(nullptr));
FParticlesType* InParticles = &GetParticles();
SuspensionProxy->DestroyOnGameThread(); //destroy the game thread portion of the proxy
// Finish registration on the physics thread...
EnqueueCommandImmediate([InParticles, SuspensionProxy, this]()
{
SuspensionProxy->DestroyOnPhysicsThread(this);
delete SuspensionProxy;
});
}
void FPBDRigidsSolver::SetSuspensionTarget(FSuspensionConstraint* GTConstraint, const FVector& TargetPos, const FVector& Normal, bool Enabled)
{
EnsureIsInPhysicsThreadContext();
FSuspensionConstraintPhysicsProxy* SuspensionProxy = GTConstraint->GetProxy<FSuspensionConstraintPhysicsProxy>();
check(SuspensionProxy);
SuspensionProxy->UpdateTargetOnPhysicsThread(this, TargetPos, Normal, Enabled);
}
void FPBDRigidsSolver::EnableRewindCapture(int32 NumFrames, bool InUseCollisionResimCache, TUniquePtr<IRewindCallback>&& RewindCallback)
{
SetRewindCallback(MoveTemp(RewindCallback));
EnableRewindCapture(NumFrames, InUseCollisionResimCache);
}
void FPBDRigidsSolver::EnableRewindCapture(int32 NumFrames, bool InUseCollisionResimCache)
{
SetUseCollisionResimCache(InUseCollisionResimCache);
EnableRewindCapture(NumFrames);
}
void FPBDRigidsSolver::EnableRewindCapture(int32 NumFrames)
{
//TODO: this function calls both internal and external - sort of assumed during initialization. Should decide what thread it's called on and mark it as either external or internal
if (MRewindData.IsValid())
{
MRewindData->Init(((FPBDRigidsSolver*)this), NumFrames, GetCurrentFrame());
}
else
{
MRewindData = MakeUnique<FRewindData>(((FPBDRigidsSolver*)this), NumFrames, GetCurrentFrame());
}
const int32 NumFramesSet = GetRewindData() != nullptr ? GetRewindData()->Capacity() : NumFrames;
MarshallingManager.SetHistoryLength_Internal(NumFramesSet);
MEvolution->SetRewindData(GetRewindData());
if (MRewindCallback)
{
MRewindCallback->RewindData = GetRewindData();
}
UpdateIsDeterministic();
UE_LOG(LogChaos, Log, TEXT("PBDRigidsSolver::EnableRewindCapture - Starting physics data history caching for rewind / resimulation. History Size: %d. Supported Latency: %f. TickRate: %d. "), NumFramesSet, FPBDRigidsSolver::GetPhysicsHistoryTimeLength(), FMath::RoundToInt32(1.0f / GetAsyncDeltaTime()));
}
void FPBDRigidsSolver::EnableRewindCapture()
{
int32 NumFrames = FMath::Max<int32>(1, FMath::CeilToInt32((0.001f * FPBDRigidsSolver::GetPhysicsHistoryTimeLength()) / GetAsyncDeltaTime()));
EnableRewindCapture(NumFrames);
}
void FPBDRigidsSolver::Reset()
{
UE_LOG(LogPBDRigidsSolver, Verbose, TEXT("PBDRigidsSolver::Reset()"));
MTime = 0;
MLastDt = 0.0f;
CurrentFrame = 0;
SetMaxDeltaTime_External(1.0f);
SetMinDeltaTime_External(UE_SMALL_NUMBER);
SetMaxSubSteps_External(1);
MEvolution = TUniquePtr<FPBDRigidsEvolution>(new FPBDRigidsEvolution(Particles, SimMaterials, &MidPhaseModifiers, &CCDModifiers, &StrainModifiers, &ContactModifiers, BufferMode == EMultiBufferMode::Single));
PerSolverField = MakeUnique<FPerSolverFieldSystem>();
//todo: do we need this?
//MarshallingManager.Reset();
if (bUseCollisionResimCache)
{
EnableRewindCapture(true);
}
MEvolution->SetCaptureRewindDataFunction([this](const TParticleView<TPBDRigidParticles<FReal,3>>& ActiveParticles)
{
FinalizeRewindData(ActiveParticles);
});
FEventDefaults::RegisterSystemEvents(*GetEventManager());
}
void FPBDRigidsSolver::ChangeBufferMode(EMultiBufferMode InBufferMode)
{
// This seems unused inside the solver? #BH
BufferMode = InBufferMode;
SetThreadingMode_External(BufferMode == EMultiBufferMode::Single ? EThreadingModeTemp::SingleThread : EThreadingModeTemp::TaskGraph);
}
void FPBDRigidsSolver::StartingSceneSimulation()
{
LLM_SCOPE(ELLMTag::Chaos);
QUICK_SCOPE_CYCLE_COUNTER(STAT_StartedSceneSimulation);
GetEvolution()->GetBroadPhase().GetIgnoreCollisionManager().PopStorageData_Internal(GetEvolution()->LatestExternalTimestampConsumed_Internal);
}
void FPBDRigidsSolver::DestroyPendingProxies_Internal()
{
MarshallingManager.GetCurrentPullData_Internal()->DirtyClusterUnions.Reset();
for (FClusterUnionPhysicsProxy* Proxy : PendingDestroyClusterUnionProxy)
{
if (!Proxy)
{
continue;
}
// Callback for Unregister PhysicsObject
if (PhysicsObjectUnregistrationWatchers.Num() > 0)
{
for (ISimCallbackObject* Callback : PhysicsObjectUnregistrationWatchers)
{
Callback->OnPhysicsObjectUnregistered_Internal(Proxy->GetPhysicsObjectHandle());
}
}
if (FPBDRigidsEvolutionGBF* Evolution = GetEvolution())
{
// Remove the cluster union this proxy manages which will in turn also the destroy the necessary particles.
Evolution->GetRigidClustering().GetClusterUnionManager().DestroyClusterUnion(Proxy->GetClusterUnionIndex());
}
delete Proxy;
}
PendingDestroyClusterUnionProxy.Reset();
// If we have any callback objects watching for particle deregistrations,
// send an array of proxies that are about to be deleted.
if (UnregistrationWatchers.Num() > 0)
{
TArray<TTuple<FUniqueIdx, FSingleParticlePhysicsProxy*>> Proxies;
Proxies.Reserve(PendingDestroyPhysicsProxy.Num());
for (FPendingDestroyInfo& Info : PendingDestroyPhysicsProxy)
{
Proxies.Add({ Info.UniqueIdx, Info.Proxy });
}
for (ISimCallbackObject* Callback : UnregistrationWatchers)
{
Callback->OnParticleUnregistered_Internal(Proxies);
}
}
// Do the actual destruction
for(int32 Idx = PendingDestroyPhysicsProxy.Num() - 1; Idx >= 0; --Idx)
{
FPendingDestroyInfo& Info = PendingDestroyPhysicsProxy[Idx];
if(Info.DestroyOnStep <= (GetCurrentFrame() - 1) || IsShuttingDown()) // Note: GetCurrentFrame() - 1 because this happens at the end of the physics frame after frame number has been incremented already
{
// Callback for Unregister PhysicsObject
if (PhysicsObjectUnregistrationWatchers.Num() > 0)
{
for (ISimCallbackObject* Callback : PhysicsObjectUnregistrationWatchers)
{
Callback->OnPhysicsObjectUnregistered_Internal(Info.Proxy->GetPhysicsObject());
}
}
// finally let's release the unique index
GetEvolution()->ReleaseUniqueIdx(Info.UniqueIdx);
if (Info.Handle)
{
// Use the handle to destroy the particle data
GetEvolution()->DestroyParticle(Info.Handle);
}
ensure(Info.Proxy->GetHandle_LowLevel() == nullptr); //should have already cleared this out
delete Info.Proxy;
PendingDestroyPhysicsProxy.RemoveAtSwap(Idx, EAllowShrinking::No);
}
}
MarshallingManager.GetCurrentPullData_Internal()->DirtyGeometryCollections.Reset();
if (!PendingDestroyGeometryCollectionPhysicsProxy.IsEmpty())
{
GetEvolution()->GetRigidClustering().CleanupInternalClustersForProxies(TArrayView<IPhysicsProxyBase*>{reinterpret_cast<IPhysicsProxyBase**>(&PendingDestroyGeometryCollectionPhysicsProxy[0]), PendingDestroyGeometryCollectionPhysicsProxy.Num() });
for (auto Proxy : PendingDestroyGeometryCollectionPhysicsProxy)
{
// Callback for Unregister PhysicsObject
if (PhysicsObjectUnregistrationWatchers.Num() > 0)
{
TArray<FPhysicsObjectHandle> PhysicsObjects = Proxy->GetAllPhysicsObjects();
for (FConstPhysicsObjectHandle PhysicsObject : PhysicsObjects)
{
for (ISimCallbackObject* Callback : PhysicsObjectUnregistrationWatchers)
{
Callback->OnPhysicsObjectUnregistered_Internal(PhysicsObject);
}
}
}
// Removing the geometry collection from the solver a bit delayed. This lets the cluster union do its cleanup first before
// the geometry collection if they're all being destroyed at the same time.
Proxy->OnRemoveFromSolver(this);
delete Proxy;
}
PendingDestroyGeometryCollectionPhysicsProxy.Reset();
}
}
void FPBDRigidsSolver::SetVelocityBoundsExpansion(const FReal BoundsVelocityMultiplier, const FReal MaxBoundsVelocityExpansion)
{
GetEvolution()->GetCollisionConstraints().SetVelocityBoundsExpansion(BoundsVelocityMultiplier, MaxBoundsVelocityExpansion);
}
void FPBDRigidsSolver::SetVelocityBoundsExpansionMACD(const FReal BoundsVelocityMultiplier, const FReal MaxBoundsVelocityExpansion)
{
GetEvolution()->GetCollisionConstraints().SetVelocityBoundsExpansionMACD(BoundsVelocityMultiplier, MaxBoundsVelocityExpansion);
}
void FPBDRigidsSolver::PrepareAdvanceBy(const FReal DeltaTime)
{
// Handle runtime cvar changes for debugging
ApplyCVars();
UE_LOG(LogPBDRigidsSolver, Verbose, TEXT("PBDRigidsSolver::Tick(%3.5f)"), DeltaTime);
MLastDt = DeltaTime;
EventPreSolve.Broadcast(DeltaTime);
StartingSceneSimulation();
}
void FPBDRigidsSolver::AdvanceSolverBy(const FSubStepInfo& SubStepInfo)
{
const FReal StartSimTime = GetSolverTime();
#if CHAOS_DEBUG_DRAW
ChaosDD::Private::FChaosDDTimelineContext DDContext;
bool bEnableDebugDrawCapture = !GetEvolution()->IsResimming();
if (bEnableDebugDrawCapture)
{
DDContext.BeginFrame(CDDFrameTimeline, StartSimTime, GetLastDt());
}
#endif
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if(IsNetworkPhysicsPredictionEnabled() && CanDebugNetworkPhysicsPrediction())
{
UE_LOG(LogChaos, Log, TEXT("-> Simulating Frame = %d"), CurrentFrame);
}
#endif
AdvanceOneTimeStepTask(this, MLastDt, SubStepInfo).DoWork();
if (MLastDt > 0)
{
SCOPE_CYCLE_COUNTER(STAT_FinalizePullData);
//pass information back to external thread
//we skip dt=0 case because sync data should be identical if dt = 0
MarshallingManager.FinalizePullData_Internal(MEvolution->LatestExternalTimestampConsumed_Internal, StartSimTime, MLastDt);
}
if(SubStepInfo.Step == SubStepInfo.NumSteps - 1)
{
SCOPE_CYCLE_COUNTER(STAT_DestroyPendingProxies);
//final step so we can destroy proxies
DestroyPendingProxies_Internal();
}
#if CHAOS_DEBUG_DRAW
if (bEnableDebugDrawCapture)
{
DDContext.EndFrame();
}
#endif
}
#if CHAOS_DEBUG_DRAW
void FPBDRigidsSolver::SetDebugDrawScene(const ChaosDD::Private::FChaosDDScenePtr& InCDDScene)
{
CDDScene = InCDDScene;
CDDFrameTimeline.Reset();
if (CDDScene.IsValid())
{
CDDFrameTimeline = CDDScene->CreateTimeline(FString::Format(TEXT("{0} {1}"), { CDDScene->GetName(), "Physics Frame" }));
}
GetEvolution()->SetDebugDrawScene(InCDDScene);
}
#endif
void FPBDRigidsSolver::SetExternalTimestampConsumed_Internal(const int32 Timestamp)
{
MEvolution->LatestExternalTimestampConsumed_Internal = Timestamp;
}
void FPBDRigidsSolver::SyncEvents_GameThread()
{
GetEventManager()->DispatchEvents();
}
int32 LogDirtyParticles = 0;
FAutoConsoleVariableRef CVarLogDirtyParticles(TEXT("p.LogDirtyParticles"), LogDirtyParticles, TEXT("Logs out which particles are dirty every frame"));
void FPBDRigidsSolver::PushPhysicsState(const FReal DeltaTime, const int32 NumSteps, const int32 NumExternalSteps)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_PushPhysicsState);
ensure(NumSteps > 0);
ensure(NumExternalSteps > 0);
//TODO: interpolate some data based on num steps
UE_CHAOS_ASYNC_INITBODY_WRITESCOPELOCK(MarshallingManager.GetMarshallingManagerLock());
FPushPhysicsData* PushData = MarshallingManager.GetProducerData_External();
const FReal DynamicsWeight = FReal(1) / FReal(NumExternalSteps);
FDirtySet* DirtyProxiesData = &PushData->DirtyProxiesDataBuffer;
FDirtyPropertiesManager* Manager = &PushData->DirtyPropertiesManager;
Manager->PrepareBuckets(DirtyProxiesData->GetDirtyProxyBucketInfo());
Manager->SetNumShapes(DirtyProxiesData->NumDirtyShapes());
FShapeDirtyData* ShapeDirtyData = DirtyProxiesData->GetShapesDirtyData();
DirtyProxiesData->ParallelForEachProxy([this, DynamicsWeight, Manager, ShapeDirtyData](int32 DataIdx, FDirtyProxy& Dirty)
{
switch(Dirty.Proxy->GetType())
{
case EPhysicsProxyType::SingleParticleProxy:
{
auto Proxy = static_cast<FSingleParticlePhysicsProxy*>(Dirty.Proxy);
auto Particle = Proxy->GetParticle_LowLevel();
if(auto Rigid = Particle->CastToRigidParticle())
{
Rigid->ApplyDynamicsWeight(DynamicsWeight);
}
Particle->PrepareBVH();
Particle->LockGeometry();
Particle->SyncRemoteData(*Manager, DataIdx, Dirty.PropertyData, Dirty.ShapeDataIndices, ShapeDirtyData);
Proxy->ClearAccumulatedData();
Proxy->ResetDirtyIdx();
break;
}
case EPhysicsProxyType::GeometryCollectionType:
{
auto Proxy = static_cast<FGeometryCollectionPhysicsProxy*>(Dirty.Proxy);
Proxy->PushStateOnGameThread(this);
Proxy->ResetDirtyIdx();
break;
}
case EPhysicsProxyType::ClusterUnionProxy:
{
FClusterUnionPhysicsProxy* Proxy = static_cast<FClusterUnionPhysicsProxy*>(Dirty.Proxy);
FClusterUnionPhysicsProxy::FExternalParticle* Particle = Proxy->GetParticle_External();
Particle->LockGeometry();
Proxy->SyncRemoteData(*Manager, DataIdx, Dirty.PropertyData);
Proxy->ClearAccumulatedData();
Proxy->ResetDirtyIdx();
break;
}
case EPhysicsProxyType::JointConstraintType:
{
auto Proxy = static_cast<FJointConstraintPhysicsProxy*>(Dirty.Proxy);
Proxy->PushStateOnGameThread(*Manager, DataIdx, Dirty.PropertyData);
Proxy->ResetDirtyIdx();
break;
}
case EPhysicsProxyType::SuspensionConstraintType:
{
auto Proxy = static_cast<FSuspensionConstraintPhysicsProxy*>(Dirty.Proxy);
Proxy->PushStateOnGameThread(*Manager, DataIdx, Dirty.PropertyData);
Proxy->ResetDirtyIdx();
break;
}
case EPhysicsProxyType::CharacterGroundConstraintType:
{
auto Proxy = static_cast<FCharacterGroundConstraintProxy*>(Dirty.Proxy);
Proxy->PushStateOnGameThread(*Manager, DataIdx, Dirty.PropertyData);
Proxy->ResetDirtyIdx();
break;
}
default:
ensure(0 && TEXT("Unknown proxy type in physics solver."));
}
});
if(!!LogDirtyParticles)
{
int32 NumJoints = 0;
int32 NumSuspension = 0;
int32 NumCharacterGroundConstraints = 0;
int32 NumParticles = 0;
UE_LOG(LogChaos, Warning, TEXT("LogDirtyParticles:"));
DirtyProxiesData->ForEachProxy([&NumJoints, &NumSuspension, &NumCharacterGroundConstraints, &NumParticles](int32 DataIdx, FDirtyProxy& Dirty)
{
switch (Dirty.Proxy->GetType())
{
case EPhysicsProxyType::SingleParticleProxy:
{
auto Proxy = static_cast<FSingleParticlePhysicsProxy*>(Dirty.Proxy);
#if CHAOS_DEBUG_NAME
UE_LOG(LogChaos, Warning, TEXT("\t%s"), **Proxy->GetParticle_LowLevel()->DebugName());
#endif
++NumParticles;
break;
}
case EPhysicsProxyType::JointConstraintType:
{
auto Proxy = static_cast<FJointConstraintPhysicsProxy*>(Dirty.Proxy);
++NumJoints;
break;
}
case EPhysicsProxyType::SuspensionConstraintType:
{
++NumSuspension;
break;
}
case EPhysicsProxyType::CharacterGroundConstraintType:
{
++NumCharacterGroundConstraints;
break;
}
default: break;
}
});
UE_LOG(LogChaos, Warning, TEXT("Num Particles:%d Num Shapes:%d Num Joints:%d Num Suspensions:%d Num CharGround:%d"), NumParticles, DirtyProxiesData->NumDirtyShapes(), NumJoints, NumSuspension, NumCharacterGroundConstraints);
}
GetEvolution()->GetBroadPhase().GetIgnoreCollisionManager().PushProducerStorageData_External(MarshallingManager.GetExternalTimestamp_External());
if (MRewindCallback && !IsShuttingDown())
{
MRewindCallback->InjectInputs_External(MarshallingManager.GetInternalStep_External(), NumSteps);
}
MarshallingManager.Step_External(DeltaTime, NumSteps, GetSolverSubstep_External());
}
void FPBDRigidsSolver::ProcessSinglePushedData_Internal(FPushPhysicsData& PushData)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_ProcessSinglePushedData_Internal);
FRewindData* RewindData = GetRewindData();
FDirtySet* DirtyProxiesData = &PushData.DirtyProxiesDataBuffer;
FDirtyPropertiesManager* Manager = &PushData.DirtyPropertiesManager;
FShapeDirtyData* ShapeDirtyData = DirtyProxiesData->GetShapesDirtyData();
FReal ExternalDt = PushData.ExternalDt;
TArray<FSingleParticlePhysicsProxy*> RegisteredProxies;
auto ProcessProxyPT = [Manager, ShapeDirtyData, RewindData, ExternalDt, &RegisteredProxies, this](auto& Proxy,int32 DataIdx,FDirtyProxy& Dirty,const auto& CreateHandleFunc)
{
const bool bIsNew = !Proxy->IsInitialized();
if(bIsNew)
{
const auto* NonFrequentData = Dirty.PropertyData.FindNonFrequentData(*Manager,DataIdx);
const FUniqueIdx* UniqueIdx = NonFrequentData ? &NonFrequentData->UniqueIdx() : nullptr;
Proxy->SetHandle(CreateHandleFunc(UniqueIdx));
auto Handle = Proxy->GetHandle_LowLevel();
Handle->GTGeometryParticle() = Proxy->GetParticle_LowLevel();
// If anybody's listening for proxy creations, build a list of proxies that have been
// added in this step.
if (RegistrationWatchers.Num() > 0)
{
RegisteredProxies.Add(Proxy);
}
// Track proxies
if (UniqueIdx)
{
SingleParticlePhysicsProxies_PT.EmplaceAt(UniqueIdx->Idx, Proxy);
}
}
if(Proxy->GetHandle_LowLevel())
{
if (RewindData)
{
RewindData->PushGTDirtyData(*Manager, DataIdx, Dirty, ShapeDirtyData);
}
Proxy->PushToPhysicsState(*Manager, DataIdx, Dirty, ShapeDirtyData, ExternalDt);
}
else
{
//The only valid time for a handle to not exist is during a resim, when the proxy was already deleted
//Another way would be to sanitize pending push data, but this would be expensive
ensure(RewindData && RewindData->IsResim());
}
if(bIsNew)
{
auto Handle = Proxy->GetHandle_LowLevel();
if(auto Rigid = Handle->CastToRigidParticle())
{
Rigid->SetPreObjectStateLowLevel(Rigid->ObjectState()); //created this frame so pre is the initial value
}
Handle->SetPhysicsProxy(Proxy);
GetEvolution()->RegisterParticle(Handle);
Proxy->SetInitialized(GetCurrentFrame());
}
};
//need to create new particle handles
int32 NumInitializedGCProxies = 0;
DirtyProxiesData->ForEachProxy([this, &ProcessProxyPT, Manager, &NumInitializedGCProxies](int32 DataIdx,FDirtyProxy& Dirty)
{
if(Dirty.Proxy->GetIgnoreDataOnStep_Internal() != CurrentFrame)
{
switch(Dirty.Proxy->GetType())
{
case EPhysicsProxyType::SingleParticleProxy:
{
auto Proxy = static_cast<FSingleParticlePhysicsProxy*>(Dirty.Proxy);
ProcessProxyPT(Proxy, DataIdx, Dirty, [this, &Dirty](const FUniqueIdx* UniqueIdx) -> TGeometryParticleHandle<FReal,3>*
{
switch (Dirty.PropertyData.GetParticleBufferType())
{
case EParticleType::Static: return Particles.CreateStaticParticles(1, UniqueIdx)[0];
case EParticleType::Kinematic: return Particles.CreateKinematicParticles(1, UniqueIdx)[0];
case EParticleType::Rigid:
{
if (CVars::bChaosEnableOverrideSingleParticleTypeToCluster)
{
return Particles.CreateClusteredParticles(1, UniqueIdx)[0];
}
else
{
return Particles.CreateDynamicParticles(1, UniqueIdx)[0];
}
}
default: check(false); return nullptr;
}
});
break;
}
case EPhysicsProxyType::GeometryCollectionType:
{
auto Proxy = static_cast<FGeometryCollectionPhysicsProxy*>(Dirty.Proxy);
if (!Proxy->IsInitializedOnPhysicsThread())
{
// Finish registration on the physics thread...
Proxy->InitializeBodiesPT(this, GetParticles());
++NumInitializedGCProxies;
GeometryCollectionPhysicsProxies_Internal.Add(Proxy);
}
Proxy->PushToPhysicsState();
break;
}
case EPhysicsProxyType::ClusterUnionProxy:
{
FClusterUnionPhysicsProxy* Proxy = static_cast<FClusterUnionPhysicsProxy*>(Dirty.Proxy);
if (!Proxy->IsInitializedOnPhysicsThread())
{
Proxy->Initialize_Internal(this, GetParticles());
ClusterUnionPhysicsProxies_Internal.Add(Proxy);
}
Proxy->PushToPhysicsState(*Manager, DataIdx, Dirty);
break;
}
case EPhysicsProxyType::JointConstraintType:
case EPhysicsProxyType::SuspensionConstraintType:
case EPhysicsProxyType::CharacterGroundConstraintType:
{
// Pass until after all bodies are created.
break;
}
}
}
});
//need to create new constraint handles
DirtyProxiesData->ForEachProxy([this, Manager, RewindData](int32 DataIdx, FDirtyProxy& Dirty)
{
if (Dirty.Proxy->GetIgnoreDataOnStep_Internal() != CurrentFrame)
{
switch (Dirty.Proxy->GetType())
{
case EPhysicsProxyType::JointConstraintType:
{
auto JointProxy = static_cast<FJointConstraintPhysicsProxy*>(Dirty.Proxy);
const bool bIsNew = !JointProxy->IsInitialized();
if (bIsNew)
{
JointConstraintPhysicsProxies_Internal.Add(JointProxy);
JointProxy->InitializeOnPhysicsThread(this, *Manager, DataIdx, Dirty.PropertyData);
JointProxy->SetInitialized(GetCurrentFrame());
}
//TODO: if we support predicting creation / destruction of joints need to handle null joint case
if (RewindData)
{
RewindData->PushGTDirtyData(*Manager, DataIdx, Dirty, nullptr);
}
JointProxy->PushStateOnPhysicsThread(this, *Manager, DataIdx, Dirty.PropertyData);
#if UE_WITH_REMOTE_OBJECT_HANDLE
if (bIsNew)
{
if (FSerializedDataBufferPtr StateBuffer = Serializer.PopPendingInternalSerializedStateForProxy(JointProxy))
{
Serializer.ApplySerializedStateToJointConstraint(JointProxy->GetHandle(), *StateBuffer);
}
}
#endif
break;
}
case EPhysicsProxyType::SuspensionConstraintType:
{
auto SuspensionProxy = static_cast<FSuspensionConstraintPhysicsProxy*>(Dirty.Proxy);
const bool bIsNew = !SuspensionProxy->IsInitialized();
if (bIsNew)
{
SuspensionProxy->InitializeOnPhysicsThread(this, *Manager, DataIdx, Dirty.PropertyData);
SuspensionProxy->SetInitialized();
}
SuspensionProxy->PushStateOnPhysicsThread(this, *Manager, DataIdx, Dirty.PropertyData);
break;
}
case EPhysicsProxyType::CharacterGroundConstraintType:
{
auto ConstraintProxy = static_cast<FCharacterGroundConstraintProxy*>(Dirty.Proxy);
const bool bIsNew = !ConstraintProxy->IsInitialized();
if (bIsNew)
{
CharacterGroundConstraintProxies_Internal.Add(ConstraintProxy);
ConstraintProxy->InitializeOnPhysicsThread(this, *Manager, DataIdx, Dirty.PropertyData);
ConstraintProxy->SetInitialized(GetCurrentFrame());
}
//TODO: Support rewind for character ground constraints
//if (RewindData)
//{
// RewindData->PushGTDirtyData(*Manager, DataIdx, Dirty, nullptr);
//}
ConstraintProxy->PushStateOnPhysicsThread(this, *Manager, DataIdx, Dirty.PropertyData);
break;
}
}
}
});
// If we have callbacks watching for particle registrations, send them the array
// of newly added proxies
if (RegistrationWatchers.Num() > 0)
{
for (ISimCallbackObject* Callback : RegistrationWatchers)
{
Callback->OnParticlesRegistered_Internal(RegisteredProxies);
}
}
//MarshallingManager.FreeData_Internal(&PushData);
}
template<typename TRigidParticle>
bool ShouldUpdateFromSimulation(const TRigidParticle& InRigidParticle)
{
if (InRigidParticle.ObjectState() == Chaos::EObjectStateType::Kinematic)
{
switch (Chaos::SyncKinematicOnGameThread)
{
case 0:
return false;
case 1:
return true;
default:
return InRigidParticle.UpdateKinematicFromSimulation();
}
}
// We assume that sleeping/static particles etc won't appear repeatedly (over multiple
// frames) in the dirty list, so we can safely return true here without incurring unwanted costs.
return true;
}
void FPBDRigidsSolver::ProcessPushedData_Internal(FPushPhysicsData& PushData)
{
QUICK_SCOPE_CYCLE_COUNTER(ChaosPushData);
ensure(PushData.InternalStep == CurrentFrame); //push data was generated for this specific frame
// Set the current PushData being used on the internal thread
MarshallingManager.SetConsumerData_Internal(&PushData);
//update callbacks
SimCallbackObjects.Reserve(SimCallbackObjects.Num() + PushData.SimCallbackObjectsToAdd.Num());
for(ISimCallbackObject* SimCallbackObject : PushData.SimCallbackObjectsToAdd)
{
if (SimCallbackObject->HasOption(ESimCallbackOptions::Presimulate))
{
SimCallbackObjects.Add(SimCallbackObject);
}
if (SimCallbackObject->HasOption(ESimCallbackOptions::MidPhaseModification))
{
MidPhaseModifiers.Add(SimCallbackObject);
}
if (SimCallbackObject->HasOption(ESimCallbackOptions::CCDModification))
{
CCDModifiers.Add(SimCallbackObject);
}
if (SimCallbackObject->HasOption(ESimCallbackOptions::StrainModification))
{
StrainModifiers.Add(SimCallbackObject);
}
if (SimCallbackObject->HasOption(ESimCallbackOptions::ContactModification))
{
ContactModifiers.Add(SimCallbackObject);
}
if (SimCallbackObject->HasOption(ESimCallbackOptions::ParticleRegister))
{
RegistrationWatchers.Add(SimCallbackObject);
}
if (SimCallbackObject->HasOption(ESimCallbackOptions::ParticleUnregister))
{
UnregistrationWatchers.Add(SimCallbackObject);
}
if (SimCallbackObject->HasOption(ESimCallbackOptions::Rewind))
{
if (MRewindCallback)
{
MRewindCallback->RegisterRewindableSimCallback_Internal(SimCallbackObject);
}
}
if (SimCallbackObject->HasOption(ESimCallbackOptions::PhysicsObjectUnregister))
{
PhysicsObjectUnregistrationWatchers.Add(SimCallbackObject);
}
}
//save any pending data for this particular interval
for (const FSimCallbackInputAndObject& InputAndCallbackObj : PushData.SimCallbackInputs)
{
InputAndCallbackObj.CallbackObject->SetCurrentInput_Internal(InputAndCallbackObj.Input);
}
//remove any callbacks that are unregistered
for (ISimCallbackObject* RemovedCallbackObject : PushData.SimCallbackObjectsToRemove)
{
RemovedCallbackObject->bPendingDelete = true;
if (RemovedCallbackObject->HasOption(ESimCallbackOptions::Rewind))
{
if (MRewindCallback)
{
MRewindCallback->UnregisterRewindableSimCallback_Internal(RemovedCallbackObject);
}
}
}
for (int32 Idx = SimCallbackObjects.Num() - 1; Idx >= 0; --Idx)
{
if (SimCallbackObjects[Idx]->bPendingDelete)
{
SimCallbackObjects.RemoveAtSwap(Idx, EAllowShrinking::No);
}
}
for (int32 Idx = MidPhaseModifiers.Num() - 1; Idx >= 0; --Idx)
{
if (MidPhaseModifiers[Idx]->bPendingDelete)
{
//will also be in SimCallbackObjects so we'll delete it in that loop
MidPhaseModifiers.RemoveAtSwap(Idx, EAllowShrinking::No);
}
}
for (int32 Idx = CCDModifiers.Num() - 1; Idx >= 0; --Idx)
{
if (CCDModifiers[Idx]->bPendingDelete)
{
//will also be in SimCallbackObjects so we'll delete it in that loop
CCDModifiers.RemoveAtSwap(Idx, EAllowShrinking::No);
}
}
for (int32 Idx = StrainModifiers.Num() - 1; Idx >= 0; --Idx)
{
if (StrainModifiers[Idx]->bPendingDelete)
{
//will also be in SimCallbackObjects so we'll delete it in that loop
StrainModifiers.RemoveAtSwap(Idx, EAllowShrinking::No);
}
}
for (int32 Idx = ContactModifiers.Num() - 1; Idx >= 0; --Idx)
{
if (ContactModifiers[Idx]->bPendingDelete)
{
//will also be in SimCallbackObjects so we'll delete it in that loop
ContactModifiers.RemoveAtSwap(Idx, EAllowShrinking::No);
}
}
for (int32 Idx = RegistrationWatchers.Num() - 1; Idx >= 0; --Idx)
{
if (RegistrationWatchers[Idx]->bPendingDelete)
{
//will also be in SimCallbackObjects so we'll delete it in that loop
RegistrationWatchers.RemoveAtSwap(Idx, EAllowShrinking::No);
}
}
for (int32 Idx = UnregistrationWatchers.Num() - 1; Idx >= 0; --Idx)
{
if (UnregistrationWatchers[Idx]->bPendingDelete)
{
//will also be in SimCallbackObjects so we'll delete it in that loop
UnregistrationWatchers.RemoveAtSwap(Idx, EAllowShrinking::No);
}
}
for (int32 Idx = PhysicsObjectUnregistrationWatchers.Num() - 1; Idx >= 0; --Idx)
{
if (PhysicsObjectUnregistrationWatchers[Idx]->bPendingDelete)
{
//will also be in SimCallbackObjects so we'll delete it in that loop
PhysicsObjectUnregistrationWatchers.RemoveAtSwap(Idx, EAllowShrinking::No);
}
}
ProcessSinglePushedData_Internal(PushData);
if (FRewindData* RewindData = GetRewindData())
{
RewindData->CacheCurrentDirtyData(FFrameAndPhase::PostPushData);
}
//run any commands passed in. These don't generate outputs and are a one off so just do them here
//note: commands run before sim callbacks. This is important for sub-stepping since we want each sub-step to have a consistent view
//so for example if the user deletes a floor surface, we want all sub-steps to see that in the same way
//also note, the commands run after data is marshalled over. This is important because data marshalling ensures any GT property changes are seen by command
//for example a particle may not be created until marshalling occurs, and then a command could explicitly modify something like a collision setting
for (FSimCallbackCommandObject* SimCallbackObject : PushData.SimCommands)
{
SimCallbackObject->SetSimAndDeltaTime_Internal(GetSolverTime(), MLastDt);
SimCallbackObject->PreSimulate_Internal();
delete SimCallbackObject;
}
PushData.SimCommands.Reset();
for (ISimCallbackObject* SimCallbackObject : PushData.SimCallbackObjectsToAdd)
{
SimCallbackObject->PostInitialize_Internal();
}
if (MRewindCallback && !IsShuttingDown())
{
MRewindCallback->ProcessInputs_Internal(GetCurrentFrame(), PushData.SimCallbackInputs);
}
}
void FPBDRigidsSolver::ConditionalApplyRewind_Internal()
{
// Note: checking MRewindData->IsResim() can lead to recursion into this function on the last resim frame since the call to AdvanceSolver is what advances RewindData's internal frame
if(!IsShuttingDown() && ShouldApplyRewindCallbacks() && !GetEvolution()->IsResimming())
{
const int32 LastStep = MRewindData->CurrentFrame() - 1;
const int32 ResimStep = MRewindCallback->TriggerRewindIfNeeded_Internal(LastStep);
const int32 NumResimSteps = LastStep - ResimStep + 1;
if (ResimStep < 0)
{
// Clear ResimFrame if no valid resim frame was found
MRewindData->SetResimFrame(INDEX_NONE);
GetEvolution()->GetIslandManager().ResetParticleResimFrame();
return;
}
const bool bEnableNetworkPredictionDebug = IsNetworkPhysicsPredictionEnabled() && CanDebugNetworkPhysicsPrediction();
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (bEnableNetworkPredictionDebug)
{
UE_LOG(LogChaos, Log, TEXT(" -> Trying to Rewind Frame = %d | At Time = %f | Num Steps = %d | Resim Step = %d | Last Step = %d | Manager Size = %d"), CurrentFrame, MTime, NumResimSteps, ResimStep, LastStep, MarshallingManager.GetNumHistory_Internal());
for(auto& Handle : GetParticles().GetNonDisabledDynamicView())
{
if(!Handle.IsSleeping())
{
UE_LOG(LogChaos, Log, TEXT("Particle Dynamic At Position = %s | Velocity = %s | Quaternion = %s | Omega = %s"),
*Handle.GetX().ToString(), *Handle.GetV().ToString(), *Handle.GetR().ToString(), *Handle.GetW().ToString());
}
else
{
UE_LOG(LogChaos, Log, TEXT("Particle Sleeping At Position = %s | Velocity = %s | Quaternion = %s | Omega = %s"),
*Handle.GetX().ToString(), *Handle.GetV().ToString(), *Handle.GetR().ToString(), *Handle.GetW().ToString());
}
}
}
#endif
if ((ResimStep <= LastStep) && NumResimSteps <= MarshallingManager.GetNumHistory_Internal())
{
FResimDebugInfo DebugInfo;
QUICK_SCOPE_CYCLE_COUNTER(ChaosRewindAndResim);
if (MRewindData->RewindToFrame(ResimStep))
{
#if DEBUG_REWIND_DATA
UE_LOG(LogChaos, Warning, TEXT("CLIENT | PT | ConditionalApplyRewind_Internal | PERFORMING RESIMULATION | Resim From Frame = %d | Num Steps = %d | To Current Frame: %d"), ResimStep, NumResimSteps, CurrentFrame);
#endif
SetIsResimming(true);
CurrentFrame = ResimStep;
FPushPhysicsData* ConsumerData = MarshallingManager.GetConsumerData_Internal();
TArray<FPushPhysicsData*> RecordedPushData = MarshallingManager.StealHistory_Internal(NumResimSteps);
bool bFirst = true;
FDurationTimer ResimTimer(DebugInfo.ResimTime);
// Do rollback as necessary
for (int32 Step = ResimStep; Step <= LastStep; ++Step)
{
if ((LastStep - Step) < RecordedPushData.Num())
{
if (!bFirst && !bUseCollisionResimCache)
{
MRewindData->StepNonResimParticles(Step);
}
if (PhysicsReplicationCVars::ResimulationCVars::bApplyTargetsWhileResimulating || bFirst)
{
// Update all the particles having received a target from the server
MRewindData->ApplyTargets(Step, bFirst);
}
FPushPhysicsData* PushData = RecordedPushData[LastStep - Step]; //push data is sorted as latest first
if (bFirst)
{
MTime = PushData->StartTime; //not sure if sub-steps have proper StartTime so just do this once and let solver evolve remaining time
GetEvolution()->ResetCollisions();
}
GetEvolution()->SetReset(bFirst);
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if(bEnableNetworkPredictionDebug)
{
UE_LOG(LogChaos, Log, TEXT(" -> Re-simulating Frame = %d "), Step);
for(auto& Handle : GetParticles().GetNonDisabledDynamicView())
{
if (GetEvolution()->GetIslandManager().GetParticleIsland(Handle.Handle()))
{
if (!Handle.IsSleeping())
{
UE_LOG(LogChaos, Log, TEXT("Particle Dynamic At Position = %s | Velocity = %s | Quaternion = %s | Omega = %s | Resim Frame = %d | Sync State = %d | Needs Resim = %d | Unique Idx = %d"),
*Handle.GetX().ToString(), *Handle.GetV().ToString(), *Handle.GetR().ToString(), *Handle.GetW().ToString(),
GetEvolution()->GetIslandManager().GetParticleIsland(Handle.Handle())->GetResimFrame(), (uint8)Handle.SyncState(),
GetEvolution()->GetIslandManager().GetParticleIsland(Handle.Handle())->NeedsResim(), Handle.UniqueIdx().Idx);
}
else
{
UE_LOG(LogChaos, Log, TEXT("Particle Sleeping At Position = %s | Velocity = %s | Quaternion = %s | Omega = %s | Resim Frame = %d | Sync State = %d | Needs Resim = %d | Unique Idx = %d"),
*Handle.GetX().ToString(), *Handle.GetV().ToString(), *Handle.GetR().ToString(), *Handle.GetW().ToString(),
GetEvolution()->GetIslandManager().GetParticleIsland(Handle.Handle())->GetResimFrame(), (uint8)Handle.SyncState(),
GetEvolution()->GetIslandManager().GetParticleIsland(Handle.Handle())->NeedsResim(), Handle.UniqueIdx().Idx);
}
}
}
}
#endif
MRewindCallback->PreResimStep_Internal(Step, bFirst);
// Run the advance but omit the game thread callbacks as we're executing during the physics tick
FSolverTasksPTOnly ImmediateTask(*this, PushData);
//ensure(bSolverHasFrozenGameThreadCallbacks == false); //We don't support this for resim as it's very expensive and difficult to schedule
ImmediateTask.AdvanceSolver();
MRewindCallback->PostResimStep_Internal(Step);
bFirst = false;
}
}
MarshallingManager.SetConsumerData_Internal(ConsumerData);
GetEvolution()->GetIslandManager().ResetParticleResimFrame();
SetIsResimming(false);
GetEvolution()->SetReset(false);
ResimTimer.Stop();
MRewindCallback->SetResimDebugInfo_Internal(DebugInfo);
}
#if DEBUG_REWIND_DATA
else
{
UE_LOG(LogChaos, Log, TEXT("CLIENT | PT | ConditionalApplyRewind_Internal | Resimulation failed, FRewindData::RewindToFrame returned false | Current Frame = %d | Num Steps = %d | Resim Frame = %d | Last Frame = %d | Rewind History Size = %d"), CurrentFrame, NumResimSteps, ResimStep, LastStep, MarshallingManager.GetNumHistory_Internal());
}
#endif
}
#if DEBUG_REWIND_DATA
else
{
UE_LOG(LogChaos, Log, TEXT("CLIENT | PT | ConditionalApplyRewind_Internal | Resimulation failed, invalid rewind frame data | Current Frame = %d | Num Steps = %d | Resim Frame = %d | Last Frame = %d | Rewind History Size = %d"), CurrentFrame, NumResimSteps, ResimStep, LastStep, MarshallingManager.GetNumHistory_Internal());
}
#endif
// Clear the ResimFrame no matter if resimulation succeeded or failed (if it failed it's not going to succeed next frame either based on the same ResimFrame)
MRewindData->SetResimFrame(INDEX_NONE);
}
}
void FPBDRigidsSolver::SetIsResimming(bool bIsResimming)
{
GetEvolution()->SetResim(bIsResimming);
#if WITH_CHAOS_VISUAL_DEBUGGER
EChaosVDContextAttributes Attributes = static_cast<EChaosVDContextAttributes>(GetChaosVDContextData().Attributes);
if (bIsResimming)
{
EnumAddFlags(Attributes, EChaosVDContextAttributes::Resimulated);
}
else
{
EnumRemoveFlags(Attributes, EChaosVDContextAttributes::Resimulated);
}
GetChaosVDContextData().Attributes = static_cast<int32>(Attributes);
#endif
}
void FPBDRigidsSolver::CompleteSceneSimulation()
{
LLM_SCOPE(ELLMTag::Chaos);
SCOPE_CYCLE_COUNTER(STAT_BufferPhysicsResults);
EventPreBuffer.Broadcast(MLastDt);
GetDirtyParticlesBuffer()->CaptureSolverData(this);
BufferPhysicsResults();
}
void FPBDRigidsSolver::BufferPhysicsResults()
{
//ensure(IsInPhysicsThread());
TArray<FGeometryCollectionPhysicsProxy*> ActiveGC;
ActiveGC.Reserve(GeometryCollectionPhysicsProxies_Internal.Num());
FPullPhysicsData* PullData = MarshallingManager.GetCurrentPullData_Internal();
TParticleView<FPBDRigidParticles>& DirtyParticles = GetParticles().GetDirtyParticlesView();
const int32 NumDirty = DirtyParticles.Num();
// We can have at most NumDirty overall active particles across all dirty proxy lists
// and in each list at most NumProxies. So we take the min, otherwise for example in a world
// with a few million static particles and one dynamic we'll allocate enough storage for the
// millions of particles despite only ever syncing one
TArray<FSingleParticlePhysicsProxy*> ActiveRigid;
ActiveRigid.Reserve(FMath::Min(SingleParticlePhysicsProxies_PT.Num(), NumDirty));
TArray<FClusterUnionPhysicsProxy*> ActiveClusterUnions;
ActiveClusterUnions.Reserve(FMath::Min(ClusterUnionPhysicsProxies_Internal.Num(), NumDirty));
const bool bIsResim = GetEvolution()->IsResimming();
//todo: should be able to go wide just add defaulted etc...
{
ensure(PullData->DirtyRigids.Num() == 0); //we only fill this once per frame
int32 BufferIdx = 0;
for (TPBDRigidParticleHandleImp<FReal, 3, false>& DirtyParticle : DirtyParticles)
{
const EParticleType ParticleType = DirtyParticle.GetParticleType();
if ((ParticleType == EParticleType::Kinematic) || (ParticleType == EParticleType::Static))
{
ensure(false);
}
if(IPhysicsProxyBase* Proxy = DirtyParticle.Handle()->PhysicsProxy())
{
switch (Proxy->GetType())
{
case EPhysicsProxyType::GeometryCollectionType:
case EPhysicsProxyType::ClusterUnionProxy:
{
if (auto ClusterParticle = DirtyParticle.CastToClustered())
{
if (ClusterParticle->InternalCluster())
{
if (Proxy->GetType() == EPhysicsProxyType::ClusterUnionProxy)
{
ActiveClusterUnions.AddUnique((FClusterUnionPhysicsProxy*)(Proxy));
}
const bool bActivateChildren =
ClusterParticle->ObjectState() == EObjectStateType::Dynamic ||
Proxy->GetType() != EPhysicsProxyType::ClusterUnionProxy;
// If the cluster is dynamic, all children will likely need a transform update
// otherwise we should not need a full update (if the cluster is still we only
// need to consider de-clustered objects which should be in other parts of the
// dirty view)
if (bActivateChildren)
{
const TSet<IPhysicsProxyBase*> Proxies = ClusterParticle->PhysicsProxies();
for (IPhysicsProxyBase* ClusterProxy : Proxies)
{
if (!ClusterProxy)
{
continue;
}
switch (ClusterProxy->GetType())
{
case EPhysicsProxyType::SingleParticleProxy:
ActiveRigid.AddUnique((FSingleParticlePhysicsProxy*)ClusterProxy);
break;
case EPhysicsProxyType::GeometryCollectionType:
ActiveGC.AddUnique((FGeometryCollectionPhysicsProxy*)(ClusterProxy));
break;
default:
ensure(false);
break;
}
}
}
}
else
{
switch (Proxy->GetType())
{
case EPhysicsProxyType::SingleParticleProxy:
ActiveRigid.AddUnique((FSingleParticlePhysicsProxy*)Proxy);
break;
case EPhysicsProxyType::GeometryCollectionType:
ActiveGC.AddUnique((FGeometryCollectionPhysicsProxy*)(Proxy));
break;
default:
ensure(false);
break;
}
}
}
else
{
ensure(false);
}
}
break;
case EPhysicsProxyType::SingleParticleProxy:
{
if (!bIsResim || DirtyParticle.SyncState() == ESyncState::HardDesync)
{
// Although per-particle we can control the syncing of target positions (see ShouldUpdateFromSimulation)
// we cannot avoid marking kinematics as dirty (unless the global config Chaos::SyncKinematicOnGameThread
// forces it) because we always need the correct velocities/dynamics to be synced back from the kinematic
// target.
// FSingleParticlePhysicsProxy::PullFromPhysicsState will use the proper checks to see whether it needs
// to sync the particle positions when the dirty particle is processed.
if (!(Chaos::SyncKinematicOnGameThread == 0 && DirtyParticle.ObjectState() == EObjectStateType::Kinematic))
{
ActiveRigid.AddUnique((FSingleParticlePhysicsProxy*)Proxy);
}
}
}
break;
default:
check(false);
break;
}
}
}
}
{
//we only fill this once per frame
ensure(PullData->DirtyRigids.Num() == 0);
PullData->DirtyRigids.Reserve(ActiveRigid.Num());
for (int32 Idx = 0; Idx < ActiveRigid.Num(); ++Idx)
{
PullData->DirtyRigids.AddDefaulted();
ActiveRigid[Idx]->BufferPhysicsResults(PullData->DirtyRigids.Last());
}
}
{
ensure(PullData->DirtyGeometryCollections.Num() == 0); //we only fill this once per frame
PullData->DirtyGeometryCollections.Reserve(ActiveGC.Num());
for (int32 Idx = 0; Idx < ActiveGC.Num(); ++Idx)
{
PullData->DirtyGeometryCollections.AddDefaulted();
ActiveGC[Idx]->BufferPhysicsResults_Internal(this, PullData->DirtyGeometryCollections.Last());
}
}
{
ensure(PullData->DirtyClusterUnions.IsEmpty());
PullData->DirtyClusterUnions.Reserve(ActiveClusterUnions.Num());
for (int32 Idx = 0; Idx < ActiveClusterUnions.Num(); ++Idx)
{
PullData->DirtyClusterUnions.AddDefaulted();
ActiveClusterUnions[Idx]->BufferPhysicsResults_Internal(PullData->DirtyClusterUnions.Last());
}
}
{
ensure(PullData->DirtyJointConstraints.Num() == 0); //we only fill this once per frame
PullData->DirtyJointConstraints.Reserve(JointConstraintPhysicsProxies_Internal.Num());
for(int32 Idx = 0; Idx < JointConstraintPhysicsProxies_Internal.Num(); ++Idx)
{
PullData->DirtyJointConstraints.AddDefaulted();
JointConstraintPhysicsProxies_Internal[Idx]->BufferPhysicsResults(PullData->DirtyJointConstraints.Last());
}
}
{
ensure(PullData->DirtyCharacterGroundConstraints.Num() == 0); //we only fill this once per frame
PullData->DirtyCharacterGroundConstraints.Reserve(CharacterGroundConstraintProxies_Internal.Num());
for (int32 Idx = 0; Idx < CharacterGroundConstraintProxies_Internal.Num(); ++Idx)
{
PullData->DirtyCharacterGroundConstraints.AddDefaulted();
CharacterGroundConstraintProxies_Internal[Idx]->BufferPhysicsResults(PullData->DirtyCharacterGroundConstraints.Last());
}
}
// Calculate and move post-resim error correction data from RewindData to FPullPhysicsData for marshaling
if (FRewindData* RewindData = GetRewindData())
{
RewindData->BufferPhysicsResults(PullData->DirtyRigidErrors);
}
// Now that results have been buffered we have completed a solve step so we can broadcast that event
EventPostSolve.Broadcast(MLastDt);
Particles.ClearTransientDirty();
}
void FPBDRigidsSolver::BeginDestroy()
{
MEvolution->SetCanStartAsyncTasks(false);
}
// This function is not called during normal Engine execution.
// FPhysScene_ChaosInterface::EndFrame() calls
// FPhysScene_ChaosInterface::SyncBodies() instead, and then immediately afterwards
// calls FPBDRigidsSovler::SyncEvents_GameThread(). This function is used by tests,
// however.
void FPBDRigidsSolver::UpdateGameThreadStructures()
{
struct FDispatcher {} Dispatcher;
PullPhysicsStateForEachDirtyProxy_External(Dispatcher);
}
int32 FPBDRigidsSolver::NumJointConstraints() const
{
return MEvolution->GetJointCombinedConstraints().NumConstraints();
}
int32 FPBDRigidsSolver::NumCollisionConstraints() const
{
return GetEvolution()->GetCollisionConstraints().NumConstraints();
}
void FPBDRigidsSolver::ResetStatCounters()
{
TRACE_COUNTER_SET(ChaosTraceCounter_MidPhase_NumShapePair, 0);
TRACE_COUNTER_SET(ChaosTraceCounter_MidPhase_NumGeneric, 0);
}
#ifndef CHAOS_COUNTER_STAT
#define CHAOS_COUNTER_STAT(Name, Value)\
SET_DWORD_STAT(STAT_ChaosCounter_##Name, Value); \
CSV_CUSTOM_STAT(PhysicsCounters, Name, Value, ECsvCustomStatOp::Set); \
TRACE_COUNTER_SET(ChaosTraceCounter_##Name, Value)
#endif
void FPBDRigidsSolver::UpdateStatCounters() const
{
const int32 NumStatic = GetEvolution()->GetParticles().GetActiveStaticParticlesView().Num();
const int32 NumKinematic = GetEvolution()->GetParticles().GetActiveKinematicParticlesView().Num();
const int32 NumDynamic = GetEvolution()->GetParticles().GetNonDisabledDynamicView().Num();
const int32 NumMoving = GetEvolution()->GetParticles().GetActiveDynamicMovingKinematicParticlesView().Num();
// Particle counts
CHAOS_COUNTER_STAT(NumDisabledBodies, GetEvolution()->GetParticles().GetAllParticlesView().Num() - GetEvolution()->GetParticles().GetNonDisabledView().Num());
CHAOS_COUNTER_STAT(NumBodies, GetEvolution()->GetParticles().GetNonDisabledView().Num());
CHAOS_COUNTER_STAT(NumDynamicBodies, NumDynamic);
CHAOS_COUNTER_STAT(NumKinematicBodies, NumKinematic);
CHAOS_COUNTER_STAT(NumStaticBodies, NumStatic);
CHAOS_COUNTER_STAT(NumMovingBodies, NumMoving);
CHAOS_COUNTER_STAT(NumGeometryCollectionBodies, (int32)GetEvolution()->GetParticles().GetGeometryCollectionParticles().Size());
// Constraint counts
CHAOS_COUNTER_STAT(NumIslands, GetEvolution()->GetIslandManager().GetNumIslands());
CHAOS_COUNTER_STAT(NumIslandGroups, GetEvolution()->GetIslandGroupManager().GetNumActiveGroups());
CHAOS_COUNTER_STAT(NumContacts, NumCollisionConstraints());
CHAOS_COUNTER_STAT(NumJoints, NumJointConstraints());
CHAOS_COUNTER_STAT(NumCharacterGroundConstraints, GetEvolution()->GetCharacterGroundConstraints().GetNumConstraints());
#if !UE_BUILD_SHIPPING && !UE_BUILD_TEST
UpdateExpensiveStatCounters();
#endif
// Collision detection info
CHAOS_COUNTER_STAT(NumBroadPhasePairs, GetEvolution()->GetBroadPhase().GetNumBroadPhasePairs());
CHAOS_COUNTER_STAT(NumMidPhases, GetEvolution()->GetBroadPhase().GetNumMidPhases());
// Iterations
CHAOS_COUNTER_STAT(NumPositionIterations, GetEvolution()->GetNumPositionIterations());
CHAOS_COUNTER_STAT(NumVelocityIterations, GetEvolution()->GetNumVelocityIterations());
CHAOS_COUNTER_STAT(NumProjectionIterations, GetEvolution()->GetNumProjectionIterations());
}
void FPBDRigidsSolver::UpdateExpensiveStatCounters() const
{
int32 NumValidCollisions = 0;
int32 NumActiveCollisions = 0;
int32 NumRestoredCollisions = 0;
int32 NumManifoldPoints = 0;
int32 NumActiveManifoldPoints = 0;
int32 NumRestoredManifoldPoints = 0;
int32 NumUpdatedManifoldPoints = 0;
for (const FPBDCollisionConstraintHandle* Collision : GetEvolution()->GetCollisionConstraints().GetConstraints())
{
if (Collision == nullptr)
{
continue;
}
const FPBDCollisionConstraint& Contact = Collision->GetContact();
if (Contact.IsEnabled())
{
if (Contact.GetManifoldPoints().Num() > 0)
{
++NumValidCollisions;
}
if (!Contact.AccumulatedImpulse.IsNearlyZero())
{
++NumActiveCollisions;
}
if (Contact.WasManifoldRestored())
{
++NumRestoredCollisions;
}
for (int32 PointIndex = 0; PointIndex < Contact.NumManifoldPoints(); ++PointIndex)
{
const FManifoldPoint& ManifoldPoint = Contact.GetManifoldPoint(PointIndex);
const FManifoldPointResult& ManifoldPointResult = Contact.GetManifoldPointResult(PointIndex);
++NumManifoldPoints;
if (ManifoldPoint.Flags.bWasRestored || Contact.WasManifoldRestored())
{
++NumRestoredManifoldPoints;
}
if (ManifoldPoint.Flags.bWasReplaced)
{
++NumUpdatedManifoldPoints;
}
if (ManifoldPointResult.bIsValid)
{
if (!ManifoldPointResult.NetPushOut.IsNearlyZero())
{
++NumActiveManifoldPoints;
}
}
}
}
}
int32 NumStaticShapes = 0;
int32 NumKinematicShapes = 0;
int32 NumDynamicShapes = 0;
for (const FTransientPBDRigidParticleHandle& Particle : Particles.GetActiveParticlesView())
{
const FConstGenericParticleHandle P = Particle.Handle();
if (Particle.GetGeometry() != nullptr)
{
int32 NumShapes = 1;
if (const FImplicitObjectUnion* Union = Particle.GetGeometry()->AsA<FImplicitObjectUnion>())
{
NumShapes = Union->GetNumLeafObjects();
}
if(auto* ClusteredParticle = Particle.CastToClustered())
{
if((ClusteredParticle->ConvexOptimizer().Get() != nullptr) && ClusteredParticle->ConvexOptimizer()->IsValid())
{
NumShapes = ClusteredParticle->ConvexOptimizer()->NumCollisionObjects();
}
}
if (P->IsDynamic())
{
NumDynamicShapes += NumShapes;
}
else if (P->IsKinematic())
{
NumKinematicShapes += NumShapes;
}
else
{
NumStaticShapes += NumShapes;
}
}
}
CHAOS_COUNTER_STAT(NumValidConstraints, NumValidCollisions);
CHAOS_COUNTER_STAT(NumActiveConstraints, NumActiveCollisions);
CHAOS_COUNTER_STAT(NumRestoredConstraints, NumRestoredCollisions);
CHAOS_COUNTER_STAT(NumManifoldPoints, NumManifoldPoints);
CHAOS_COUNTER_STAT(NumActiveManifoldPoints, NumActiveManifoldPoints);
CHAOS_COUNTER_STAT(NumRestoredManifoldPoints, NumRestoredManifoldPoints);
CHAOS_COUNTER_STAT(NumUpdatedManifoldPoints, NumUpdatedManifoldPoints);
CHAOS_COUNTER_STAT(NumStaticShapes, NumStaticShapes);
CHAOS_COUNTER_STAT(NumKinematicShapes, NumKinematicShapes);
CHAOS_COUNTER_STAT(NumDynamicShapes, NumDynamicShapes);
}
void FPBDRigidsSolver::DebugDrawShapes(const bool bShowStatic, const bool bShowKinematic, const bool bShowDynamic) const
{
#if CHAOS_DEBUG_DRAW
if (bShowStatic)
{
DebugDraw::DrawParticleShapes(FRigidTransform3(), Particles.GetActiveStaticParticlesView(), 1.0f, &ChaosSolverDebugDebugDrawSettings);
}
if (bShowKinematic)
{
DebugDraw::DrawParticleShapes(FRigidTransform3(), Particles.GetActiveKinematicParticlesView(), 1.0f, &ChaosSolverDebugDebugDrawSettings);
}
if (bShowDynamic)
{
DebugDraw::DrawParticleShapes(FRigidTransform3(), Particles.GetNonDisabledDynamicView(), 1.0f, &ChaosSolverDebugDebugDrawSettings);
}
#endif
}
void FPBDRigidsSolver::PreIntegrateDebugDraw(FReal Dt) const
{
#if CHAOS_DEBUG_DRAW
QUICK_SCOPE_CYCLE_COUNTER(SolverDebugDraw);
if (ChaosSolverDebugDrawPreIntegrationShapes == 1)
{
if (bChaosDebugDraw_UseNewQueue)
{
for (FTransientGeometryParticleHandle& Particle : Particles.GetActiveKinematicParticlesView())
{
FChaosDDParticle::DrawShapes(Particle.Handle(), FColor::Black);
}
for (FTransientGeometryParticleHandle& Particle : Particles.GetNonDisabledDynamicView())
{
FChaosDDParticle::DrawShapes(Particle.Handle(), FColor::Black);
}
}
else
{
ChaosSolverDebugDebugDrawSettings.ShapesColorsPerState = DebugDraw::GetDefaultShapesColorsPreIntegrate();
DebugDrawShapes(false, !!ChaosSolverDrawShapesShowKinematic, !!ChaosSolverDrawShapesShowDynamic);
}
}
if (ChaosSolverDebugDrawPreIntegrationCollisions == 1)
{
DebugDraw::DrawCollisions(FRigidTransform3(), GetEvolution()->GetCollisionConstraints().GetConstraintAllocator(), 1.f, &ChaosSolverDebugDebugDrawSettings);
}
#endif
}
void FPBDRigidsSolver::PreSolveDebugDraw(FReal Dt) const
{
#if CHAOS_DEBUG_DRAW
QUICK_SCOPE_CYCLE_COUNTER(SolverDebugDraw);
if (ChaosSolverDebugDrawPostIntegrationShapes == 1)
{
if (bChaosDebugDraw_UseNewQueue)
{
for (FTransientGeometryParticleHandle& Particle : Particles.GetActiveKinematicParticlesView())
{
FChaosDDParticle::DrawShapes(Particle.Handle(), FColor::Blue);
}
for (FTransientGeometryParticleHandle& Particle : Particles.GetNonDisabledDynamicView())
{
FChaosDDParticle::DrawShapes(Particle.Handle(), FColor::Yellow);
}
}
else
{
ChaosSolverDebugDebugDrawSettings.ShapesColorsPerState = DebugDraw::GetDefaultShapesColorsPostIntegrate();
DebugDrawShapes(!!ChaosSolverDrawShapesShowStatic, !!ChaosSolverDrawShapesShowKinematic, !!ChaosSolverDrawShapesShowDynamic);
}
}
if (ChaosSolverDebugDrawPostIntegrationCollisions == 1)
{
DebugDraw::DrawCollisions(FRigidTransform3(), GetEvolution()->GetCollisionConstraints().GetConstraintAllocator(), 1.f, &ChaosSolverDebugDebugDrawSettings);
}
#endif
}
void FPBDRigidsSolver::PostTickDebugDraw(FReal Dt) const
{
#if CHAOS_DEBUG_DRAW
QUICK_SCOPE_CYCLE_COUNTER(SolverDebugDraw);
if (CDDScene.IsValid())
{
// @todo(chaos): this logic should be in UChaosDebugDrawSubsystem but the CVars are here. Move them and this...
const bool bRenderEnabled = (CDDScene->IsServer() && ChaosSolverDebugDrawShowServer) || (!CDDScene->IsServer() && ChaosSolverDebugDrawShowClient);
CDDScene->SetRenderEnabled(bRenderEnabled);
}
const bool bIsServer = GetDebugName().ToString().StartsWith(TEXT("Server"));
if (bIsServer && !ChaosSolverDebugDrawShowServer)
{
return;
}
if (!bIsServer && !ChaosSolverDebugDrawShowClient)
{
return;
}
if (ChaosSolverDebugDrawShapes == 1)
{
if (bChaosDebugDraw_UseNewQueue)
{
for (FTransientGeometryParticleHandle& Particle : Particles.GetNonDisabledView())
{
FChaosDDParticle::DrawShapes(Particle.Handle());
}
}
else
{
if (ChaosSolverDebugDrawColorShapeByClientServer)
{
if (bIsServer)
{
ChaosSolverDebugDebugDrawSettings.ShapesColorsPerState = GetSolverShapesColorsByState_Server();
}
else
{
ChaosSolverDebugDebugDrawSettings.ShapesColorsPerState = GetSolverShapesColorsByState_Client();
}
}
else
{
ChaosSolverDebugDebugDrawSettings.ShapesColorsPerState = DebugDraw::GetDefaultShapesColorsByState();
}
DebugDrawShapes(!!ChaosSolverDrawShapesShowStatic, !!ChaosSolverDrawShapesShowKinematic, !!ChaosSolverDrawShapesShowDynamic);
}
}
if (ChaosSolverDebugDrawCollisions == 1)
{
DebugDraw::DrawCollisions(FRigidTransform3(), GetEvolution()->GetCollisionConstraints().GetConstraintAllocator(), 1.f, &ChaosSolverDebugDebugDrawSettings);
}
if (ChaosSolverDebugDrawMass == 1)
{
DebugDraw::DrawParticleMass(FRigidTransform3(), Particles.GetActiveKinematicParticlesView(), &ChaosSolverDebugDebugDrawSettings);
DebugDraw::DrawParticleMass(FRigidTransform3(), Particles.GetNonDisabledDynamicView(), &ChaosSolverDebugDebugDrawSettings);
}
if (ChaosSolverDebugDrawDensity == 1)
{
DebugDraw::DrawParticleDensity(FRigidTransform3(), Particles.GetActiveKinematicParticlesView(), &ChaosSolverDebugDebugDrawSettings);
DebugDraw::DrawParticleDensity(FRigidTransform3(), Particles.GetNonDisabledDynamicView(), &ChaosSolverDebugDebugDrawSettings);
}
if (ChaosSolverDebugDrawBVHs == 1)
{
for (const FTransientGeometryParticleHandle& Particle : Particles.GetNonDisabledView())
{
DebugDraw::DrawParticleBVH(FRigidTransform3(), Particle.Handle(), FColor::Silver, &ChaosSolverDebugDebugDrawSettings);
}
}
if (ChaosSolverDebugDrawBounds == 1)
{
DebugDraw::DrawParticleBounds(FRigidTransform3(), Particles.GetActiveStaticParticlesView(), Dt, &ChaosSolverDebugDebugDrawSettings);
DebugDraw::DrawParticleBounds(FRigidTransform3(), Particles.GetActiveKinematicParticlesView(), Dt, &ChaosSolverDebugDebugDrawSettings);
DebugDraw::DrawParticleBounds(FRigidTransform3(), Particles.GetNonDisabledDynamicView(), Dt, &ChaosSolverDebugDebugDrawSettings);
}
if (ChaosSolverDrawTransforms == 1)
{
DebugDraw::DrawParticleTransforms(FRigidTransform3(), Particles.GetAllParticlesView(), &ChaosSolverDebugDebugDrawSettings);
}
if (ChaosSolverDrawIslands == 1)
{
DebugDraw::DrawConstraintGraph(FRigidTransform3(), GetEvolution()->GetIslandManager(), &ChaosSolverDebugDebugDrawSettings);
}
if (ChaosSolverDebugDrawIslandSleepState == 1)
{
GetEvolution()->GetIslandManager().DebugDrawSleepState(&ChaosSolverDebugDebugDrawSettings);
}
if (ChaosSolverDrawClusterConstraints == 1)
{
DebugDraw::DrawConnectionGraph(MEvolution->GetRigidClustering(), &ChaosSolverDebugDebugDrawSettings);
}
if (ChaosSolverDebugDrawCollidingShapes == 1)
{
DebugDraw::DrawCollidingShapes(FRigidTransform3(), GetEvolution()->GetCollisionConstraints(), 1.f, 0.f, &ChaosSolverDebugDebugDrawSettings);
}
if (ChaosSolverDrawJoints == 1)
{
DebugDraw::DrawJointConstraints(FRigidTransform3(), MEvolution->GetJointCombinedConstraints().LinearConstraints, 1.0f, ChaosSolverDrawJointFeatures, &ChaosSolverDebugDebugDrawSettings);
DebugDraw::DrawJointConstraints(FRigidTransform3(), MEvolution->GetJointCombinedConstraints().NonLinearConstraints, 1.0f, ChaosSolverDrawJointFeatures, &ChaosSolverDebugDebugDrawSettings);
}
if (ChaosSolverDrawCharacterGroundConstraints == 1)
{
DebugDraw::DrawCharacterGroundConstraints(FRigidTransform3(), MEvolution->GetCharacterGroundConstraints(), &ChaosSolverDebugDebugDrawSettings);
}
if (ChaosSolverDebugDrawSpatialAccelerationStructure)
{
if (const auto SpatialAccelerationStructure = GetEvolution()->GetSpatialAcceleration())
{
DebugDraw::DrawSpatialAccelerationStructure(*SpatialAccelerationStructure, &ChaosSolverDebugDebugDrawSettings);
}
}
if (ChaosSolverDebugDrawCollidingShapes == 1)
{
DebugDraw::DrawCollidingShapes(FRigidTransform3(), GetEvolution()->GetCollisionConstraints(), 1.f, 0.f, &ChaosSolverDebugDebugDrawSettings);
}
if (ChaosSolverDebugDrawSuspensionConstraints == 1)
{
DebugDraw::DrawSuspensionConstraints(FRigidTransform3(), GetEvolution()->GetSuspensionConstraints(), &ChaosSolverDebugDebugDrawSettings);
}
#endif
}
FSingleParticlePhysicsProxy* FPBDRigidsSolver::GetParticleProxy_PT(const FUniqueIdx& Idx)
{
return SingleParticlePhysicsProxies_PT.IsValidIndex(Idx.Idx) ? SingleParticlePhysicsProxies_PT[Idx.Idx] : nullptr;
}
const FSingleParticlePhysicsProxy* FPBDRigidsSolver::GetParticleProxy_PT(const FUniqueIdx& Idx) const
{
return SingleParticlePhysicsProxies_PT.IsValidIndex(Idx.Idx) ? SingleParticlePhysicsProxies_PT[Idx.Idx] : nullptr;
}
FSingleParticlePhysicsProxy* FPBDRigidsSolver::GetParticleProxy_PT(const FGeometryParticleHandle& Handle)
{
return GetParticleProxy_PT(Handle.UniqueIdx());
}
const FSingleParticlePhysicsProxy* FPBDRigidsSolver::GetParticleProxy_PT(const FGeometryParticleHandle& Handle) const
{
return GetParticleProxy_PT(Handle.UniqueIdx());
}
void FPBDRigidsSolver::UpdateMaterial(FMaterialHandle InHandle, const FChaosPhysicsMaterial& InNewData)
{
TSolverSimMaterialScope<ELockType::Write> Scope(this);
*SimMaterials.Get(InHandle.InnerHandle) = InNewData;
}
void FPBDRigidsSolver::CreateMaterial(FMaterialHandle InHandle, const FChaosPhysicsMaterial& InNewData)
{
TSolverSimMaterialScope<ELockType::Write> Scope(this);
ensure(SimMaterials.Create(InNewData) == InHandle.InnerHandle);
}
void FPBDRigidsSolver::DestroyMaterial(FMaterialHandle InHandle)
{
TSolverSimMaterialScope<ELockType::Write> Scope(this);
SimMaterials.Destroy(InHandle.InnerHandle);
}
void FPBDRigidsSolver::UpdateMaterialMask(FMaterialMaskHandle InHandle, const FChaosPhysicsMaterialMask& InNewData)
{
TSolverSimMaterialScope<ELockType::Write> Scope(this);
*SimMaterialMasks.Get(InHandle.InnerHandle) = InNewData;
}
void FPBDRigidsSolver::CreateMaterialMask(FMaterialMaskHandle InHandle, const FChaosPhysicsMaterialMask& InNewData)
{
TSolverSimMaterialScope<ELockType::Write> Scope(this);
ensure(SimMaterialMasks.Create(InNewData) == InHandle.InnerHandle);
}
void FPBDRigidsSolver::DestroyMaterialMask(FMaterialMaskHandle InHandle)
{
TSolverSimMaterialScope<ELockType::Write> Scope(this);
SimMaterialMasks.Destroy(InHandle.InnerHandle);
}
void FPBDRigidsSolver::SyncQueryMaterials_External()
{
// Using lock on sim material is an imprefect workaround, we may block while physics thread is updating sim materials in callbacks.
// QueryMaterials may be slightly stale. Need to rethink lifetime + ownership of materials for async case.
//acquire external data lock
FPhysicsSceneGuardScopedWrite ScopedWrite(GetExternalDataLock_External());
TSolverSimMaterialScope<ELockType::Read> SimMatLock(this);
QueryMaterials_External = SimMaterials;
QueryMaterialMasks_External = SimMaterialMasks;
}
void FPBDRigidsSolver::FinalizeRewindData(const TParticleView<TPBDRigidParticles<FReal,3>>& DirtyParticles)
{
using namespace Chaos;
//Simulated objects must have their properties captured for rewind
if(MRewindData && DirtyParticles.Num())
{
QUICK_SCOPE_CYCLE_COUNTER(RecordRewindData);
int32 DataIdx = 0;
for(TPBDRigidParticleHandleImp<FReal,3,false>& DirtyParticle : DirtyParticles)
{
MRewindData->PushPTDirtyData(*DirtyParticle.Handle(), DataIdx++);
}
}
}
void FPBDRigidsSolver::UpdateExternalAccelerationStructure_External(ISpatialAccelerationCollection<FAccelerationStructureHandle,FReal,3>*& ExternalStructure)
{
GetEvolution()->UpdateExternalAccelerationStructure_External(ExternalStructure,*PendingSpatialOperations_External);
}
bool FPBDRigidsSolver::IsDetemerministic() const
{
return bIsDeterministic || (MRewindData != nullptr) || (ChaosSolverDeterministic >= 1) || FPhysicsSolverBase::IsNetworkPhysicsPredictionEnabled();
}
void FPBDRigidsSolver::SetIsDeterministic(const bool bInIsDeterministic)
{
if (bIsDeterministic != bInIsDeterministic)
{
bIsDeterministic = bInIsDeterministic;
UpdateIsDeterministic();
}
}
void FPBDRigidsSolver::UpdateIsDeterministic()
{
GetEvolution()->SetIsDeterministic(IsDetemerministic());
}
void FPBDRigidsSolver::SetParticleDynamicMisc(FPBDRigidParticleHandle* Rigid, const FParticleDynamicMisc& DynamicMisc)
{
if (Rigid == nullptr)
{
return;
}
// Enable or disable the particle
if (Rigid->Disabled() != DynamicMisc.Disabled())
{
if (DynamicMisc.Disabled())
{
GetEvolution()->DisableParticle(Rigid);
}
else
{
GetEvolution()->EnableParticle(Rigid);
}
}
// If we changed kinematics we need to rebuild the inertia conditioning
const bool bDirtyInertiaConditioning = (Rigid->ObjectState() != DynamicMisc.ObjectState());
if (bDirtyInertiaConditioning)
{
Rigid->SetInertiaConditioningDirty();
}
// A negative depenetration velocity means use the solver setting
FRealSingle DepenetrationVelocity = DynamicMisc.InitialOverlapDepenetrationVelocity();
if (DepenetrationVelocity < 0)
{
DepenetrationVelocity = GetEvolution()->GetCollisionConstraints().GetSolverSettings().DepenetrationVelocity;
}
Rigid->SetLinearEtherDrag(DynamicMisc.LinearEtherDrag());
Rigid->SetAngularEtherDrag(DynamicMisc.AngularEtherDrag());
Rigid->SetMaxLinearSpeedSq(DynamicMisc.MaxLinearSpeedSq());
Rigid->SetMaxAngularSpeedSq(DynamicMisc.MaxAngularSpeedSq());
Rigid->SetInitialOverlapDepenetrationVelocity(DepenetrationVelocity);
Rigid->SetSleepThresholdMultiplier(DynamicMisc.SleepThresholdMultiplier());
Rigid->SetCollisionGroup(DynamicMisc.CollisionGroup());
Rigid->SetDisabled(DynamicMisc.Disabled());
Rigid->SetCollisionConstraintFlags(DynamicMisc.CollisionConstraintFlags());
Rigid->SetControlFlags(DynamicMisc.ControlFlags());
Rigid->SetIterationSettings(DynamicMisc.IterationSettings());
GetEvolution()->SetParticleObjectState(Rigid, DynamicMisc.ObjectState());
GetEvolution()->SetParticleSleepType(Rigid, DynamicMisc.SleepType());
}
void FPBDRigidsSolver::ApplyConfig(const FChaosSolverConfiguration& InConfig)
{
GetEvolution()->GetRigidClustering().ApplySettings(InConfig);
SetPositionIterations(InConfig.PositionIterations);
SetVelocityIterations(InConfig.VelocityIterations);
SetProjectionIterations(InConfig.ProjectionIterations);
SetCollisionCullDistance(InConfig.CollisionCullDistance);
SetCollisionMaxPushOutVelocity(InConfig.CollisionMaxPushOutVelocity);
SetCollisionDepenetrationVelocity(InConfig.CollisionInitialOverlapDepenetrationVelocity);
SetGenerateCollisionData(InConfig.bGenerateCollisionData);
SetGenerateBreakingData(InConfig.bGenerateBreakData);
SetGenerateTrailingData(InConfig.bGenerateTrailingData);
SetCollisionFilterSettings(InConfig.CollisionFilterSettings);
SetBreakingFilterSettings(InConfig.BreakingFilterSettings);
SetTrailingFilterSettings(InConfig.TrailingFilterSettings);
ApplyCVars();
}
void FPBDRigidsSolver::ApplyCVars()
{
MEvolution->GetCollisionConstraints().SetCollisionsEnabled(bChaosSolverCollisionEnabled);
FCollisionDetectorSettings CollisionDetectorSettings = MEvolution->GetCollisionConstraints().GetDetectorSettings();
CollisionDetectorSettings.bAllowManifoldReuse = (ChaosSolverCollisionAllowManifoldUpdate != 0);
CollisionDetectorSettings.bDeferNarrowPhase = (ChaosSolverCollisionDeferNarrowPhase != 0);
CollisionDetectorSettings.bAllowManifolds = (ChaosSolverCollisionUseManifolds != 0);
CollisionDetectorSettings.bAllowCCD = bChaosUseCCD;
CollisionDetectorSettings.bAllowMACD = bChaosUseMACD;
MEvolution->GetCollisionConstraints().SetDetectorSettings(CollisionDetectorSettings);
FPBDJointSolverSettings JointsSettings = MEvolution->GetJointCombinedConstraints().LinearConstraints.GetSettings();
JointsSettings.MinSolverStiffness = ChaosSolverJointMinSolverStiffness;
JointsSettings.MaxSolverStiffness = ChaosSolverJointMaxSolverStiffness;
JointsSettings.NumIterationsAtMaxSolverStiffness = ChaosSolverJointNumIterationsAtMaxSolverStiffness;
JointsSettings.PositionTolerance = ChaosSolverJointPositionTolerance;
JointsSettings.AngleTolerance = ChaosSolverJointAngleTolerance;
JointsSettings.MinParentMassRatio = ChaosSolverJointMinParentMassRatio;
JointsSettings.MaxInertiaRatio = ChaosSolverJointMaxInertiaRatio;
JointsSettings.bSolvePositionLast = bChaosSolverJointSolvePositionLast;
JointsSettings.bUsePositionBasedDrives = bChaosSolverJointUsePositionBasedDrives;
JointsSettings.NumShockPropagationIterations = ChaosSolverJointNumShockProagationIterations;
JointsSettings.ShockPropagationOverride = ChaosSolverJointShockPropagation;
JointsSettings.bSortEnabled = false;
MEvolution->GetJointCombinedConstraints().LinearConstraints.SetSettings(JointsSettings);
MEvolution->GetJointCombinedConstraints().NonLinearConstraints.SetSettings(JointsSettings);
// Apply CVAR overrides if set
{
// To enable runtime support for switching collision features on/off we need to update existing constraints when config changes.
if (bChaosCollisionConfigChanged)
{
// For now destroy the collisions. This is a bit over the top and causes problems for sleeping islands, but it's only for debugging/testing.
GetEvolution()->DestroyTransientConstraints();
bChaosCollisionConfigChanged = false;
}
if (ChaosSolverCollisionPositionFrictionIterations >= 0)
{
MEvolution->GetCollisionConstraints().SetPositionFrictionIterations(ChaosSolverCollisionPositionFrictionIterations);
}
if (ChaosSolverCollisionVelocityFrictionIterations >= 0)
{
MEvolution->GetCollisionConstraints().SetVelocityFrictionIterations(ChaosSolverCollisionVelocityFrictionIterations);
}
{
MEvolution->SetShockPropagationIterations(ChaosSolverCollisionPositionShockPropagationIterations, ChaosSolverCollisionVelocityShockPropagationIterations);
}
if (ChaosSolverPositionIterations >= 0)
{
SetPositionIterations(ChaosSolverPositionIterations);
}
if (ChaosSolverVelocityIterations >= 0)
{
SetVelocityIterations(ChaosSolverVelocityIterations);
}
if (ChaosSolverProjectionIterations >= 0)
{
SetProjectionIterations(ChaosSolverProjectionIterations);
}
if (ChaosSolverCullDistance >= 0.0f)
{
SetCollisionCullDistance(ChaosSolverCullDistance);
}
if ((ChaosSolverVelocityBoundsMultiplier >= 0.0f) && (ChaosSolverMaxVelocityBoundsExpansion >= 0.0f))
{
SetVelocityBoundsExpansion(ChaosSolverVelocityBoundsMultiplier, ChaosSolverMaxVelocityBoundsExpansion);
}
if ((ChaosSolverVelocityBoundsMultiplierMACD >= 0.0f) && (ChaosSolverMaxVelocityBoundsExpansionMACD >= 0.0f))
{
SetVelocityBoundsExpansionMACD(ChaosSolverVelocityBoundsMultiplierMACD, ChaosSolverMaxVelocityBoundsExpansionMACD);
}
if (ChaosSolverMaxPushOutVelocity >= 0.0f)
{
SetCollisionMaxPushOutVelocity(ChaosSolverMaxPushOutVelocity);
}
if (ChaosSolverDepenetrationVelocity >= 0.0f)
{
SetCollisionDepenetrationVelocity(ChaosSolverDepenetrationVelocity);
}
if (ChaosSolverDeterministic >= 0)
{
UpdateIsDeterministic();
}
}
}
FPBDRigidsSolver::~FPBDRigidsSolver()
{
EventTeardown.Broadcast();
}
void FPBDRigidsSolver::FieldParameterUpdateCallback(
FPBDPositionConstraints& PositionTarget,
TMap<int32, int32>& TargetedParticles)
{
GetPerSolverField().FieldParameterUpdateCallback(this, PositionTarget, TargetedParticles);
}
void FPBDRigidsSolver::FieldForcesUpdateCallback()
{
GetPerSolverField().FieldForcesUpdateCallback(this);
}
}; // namespace Chaos