Files
2025-05-18 13:04:45 +08:00

7458 lines
253 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "GeometryCollection/GeometryCollectionComponent.h"
#include "AI/Navigation/NavCollisionBase.h"
#include "AI/NavigationSystemHelpers.h"
#include "Async/ParallelFor.h"
#include "Chaos/ChaosPhysicalMaterial.h"
#include "Chaos/ChaosScene.h"
#include "ChaosSolversModule.h"
#include "ChaosStats.h"
#include "ComponentRecreateRenderStateContext.h"
#include "Components/BoxComponent.h"
#include "Engine/CollisionProfile.h"
#include "Engine/Engine.h"
#include "Engine/InstancedStaticMesh.h"
#include "Engine/StaticMeshSocket.h"
#include "MaterialDomain.h"
#include "Field/FieldSystemComponent.h"
#include "GeometryCollection/Facades/CollectionHierarchyFacade.h"
#include "GeometryCollection/GeometryCollection.h"
#include "GeometryCollection/GeometryCollectionActor.h"
#include "GeometryCollection/GeometryCollectionAlgo.h"
#include "GeometryCollection/GeometryCollectionCache.h"
#include "GeometryCollection/GeometryCollectionClusteringUtility.h"
#include "GeometryCollection/GeometryCollectionComponentPluginPrivate.h"
#include "GeometryCollection/GeometryCollectionDebugDrawComponent.h"
#include "GeometryCollection/GeometryCollectionISMPoolRenderer.h"
#include "GeometryCollection/GeometryCollectionObject.h"
#include "GeometryCollection/GeometryCollectionProximityUtility.h"
#include "GeometryCollection/GeometryCollectionSQAccelerator.h"
#include "GeometryCollection/GeometryCollectionSceneProxy.h"
#include "GeometryCollection/GeometryCollectionUtility.h"
#include "ISMPool/ISMPoolActor.h"
#include "ISMPool/ISMPoolComponent.h"
#include "Math/Sphere.h"
#include "Modules/ModuleManager.h"
#include "Net/Core/PushModel/PushModel.h"
#include "Net/UnrealNetwork.h"
#include "PhysicalMaterials/PhysicalMaterial.h"
#include "Physics/Experimental/PhysScene_Chaos.h"
#include "Physics/Experimental/ChaosInterfaceUtils.h"
#include "Physics/PhysicsFiltering.h"
#include "PhysicsEngine/PhysicsObjectExternalInterface.h"
#include "PhysicsField/PhysicsFieldComponent.h"
#include "PhysicsProxy/GeometryCollectionPhysicsProxy.h"
#include "PhysicsSolver.h"
#include "UObject/FortniteValkyrieBranchObjectVersion.h"
#include "Chaos/PBDRigidClusteringAlgo.h"
#include "Algo/RemoveIf.h"
#if WITH_EDITOR
#include "AssetToolsModule.h"
#include "Editor.h"
#include "UObject/UObjectThreadContext.h"
#endif
#if ENABLE_DRAW_DEBUG
#include "Chaos/DebugDrawQueue.h"
#include "Chaos/ChaosDebugDraw.h"
#endif
#include "PhysicsEngine/BodySetup.h"
#include "PhysicsEngine/BodyInstance.h"
#include "Chaos/ChaosGameplayEventDispatcher.h"
#include "Rendering/NaniteResources.h"
#include "NaniteVertexFactory.h"
#include "PrimitiveSceneInfo.h"
#include "GeometryCollection/GeometryCollectionEngineRemoval.h"
#include "GeometryCollection/Facades/CollectionAnchoringFacade.h"
#include "GeometryCollection/Facades/CollectionRemoveOnBreakFacade.h"
#include "GeometryCollection/Facades/CollectionInstancedMeshFacade.h"
#include "GeometryCollection/GeometryCollectionExternalRenderInterface.h"
#include "Dataflow/DataflowSettings.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GeometryCollectionComponent)
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
#include "Logging/MessageLog.h"
#include "Misc/UObjectToken.h"
#endif //!(UE_BUILD_SHIPPING || UE_BUILD_TEST)
#if UE_WITH_IRIS
#include "Iris/ReplicationState/PropertyNetSerializerInfoRegistry.h"
#endif
#if INTEL_ISPC
#if USING_CODE_ANALYSIS
MSVC_PRAGMA(warning(push))
MSVC_PRAGMA(warning(disable : ALL_CODE_ANALYSIS_WARNINGS))
#endif // USING_CODE_ANALYSIS
#include "GeometryCollectionComponent.ispc.generated.h"
#if USING_CODE_ANALYSIS
MSVC_PRAGMA(warning(pop))
#endif // USING_CODE_ANALYSIS
static_assert(sizeof(ispc::FMatrix) == sizeof(FMatrix), "sizeof(ispc::FMatrix) != sizeof(FMatrix)");
static_assert(sizeof(ispc::FBox) == sizeof(FBox), "sizeof(ispc::FBox) != sizeof(FBox)");
#endif
#if !defined(CHAOS_BOX_CALC_BOUNDS_ISPC_ENABLED_DEFAULT)
#define CHAOS_BOX_CALC_BOUNDS_ISPC_ENABLED_DEFAULT 1
#endif
// Support run-time toggling on supported platforms in non-shipping configurations
#if !INTEL_ISPC || UE_BUILD_SHIPPING
static constexpr bool bChaos_BoxCalcBounds_ISPC_Enabled = INTEL_ISPC && CHAOS_BOX_CALC_BOUNDS_ISPC_ENABLED_DEFAULT;
#else
static bool bChaos_BoxCalcBounds_ISPC_Enabled = CHAOS_BOX_CALC_BOUNDS_ISPC_ENABLED_DEFAULT;
static FAutoConsoleVariableRef CVarChaosBoxCalcBoundsISPCEnabled(TEXT("p.Chaos.BoxCalcBounds.ISPC"), bChaos_BoxCalcBounds_ISPC_Enabled, TEXT("Whether to use ISPC optimizations in calculating box bounds in geometry collections"));
#endif
bool bChaos_GC_UseCustomRenderer = true;
FAutoConsoleVariableRef CVarChaosGCUseCustomRenderer(TEXT("p.Chaos.GC.UseCustomRenderer"), bChaos_GC_UseCustomRenderer, TEXT("When enabled, use a custom renderer if specified"));
bool bChaos_GC_InitConstantDataUseParallelFor = true;
FAutoConsoleVariableRef CVarChaosGCInitConstantDataUseParallelFor(TEXT("p.Chaos.GC.InitConstantDataUseParallelFor"), bChaos_GC_InitConstantDataUseParallelFor, TEXT("When enabled, InitConstant data will use parallelFor for copying some of the data"));
int32 bChaos_GC_InitConstantDataParallelForBatchSize = 5000;
FAutoConsoleVariableRef CVarChaosGCInitConstantDataParallelForBatchSize(TEXT("p.Chaos.GC.InitConstantDataParallelForBatchSize"), bChaos_GC_InitConstantDataParallelForBatchSize, TEXT("When parallelFor is used in InitConstantData, defined the minimium size of a batch of vertex "));
int32 MaxGeometryCollectionAsyncPhysicsTickIdleTimeMs = 30;
FAutoConsoleVariableRef CVarMaxGeometryCollectionAsyncPhysicsTickIdleTimeMs(TEXT("p.Chaos.GC.MaxGeometryCollectionAsyncPhysicsTickIdleTimeMs"), MaxGeometryCollectionAsyncPhysicsTickIdleTimeMs, TEXT("Amount of time in milliseconds before the async tick turns off when it is otherwise not doing anything."));
float GeometryCollectionRemovalMultiplier = 1.0f;
FAutoConsoleVariableRef CVarGeometryCollectionRemovalTimerMultiplier(TEXT("p.Chaos.GC.RemovalTimerMultiplier"), GeometryCollectionRemovalMultiplier, TEXT("Multiplier for the removal time evaluation ( > 1 : faster removal , > 1 slower"));
// temporary cvar , should be removed when the root event is no longer no necessary
bool GeometryCollectionEmitRootBreakingEvent = false;
FAutoConsoleVariableRef CVarGeometryCollectionEmitRootBreakingEvent(TEXT("p.Chaos.GC.EmitRootBreakingEvent"), GeometryCollectionEmitRootBreakingEvent, TEXT("When true send a breaking event when root is breaking"));
bool GeometryCollectionCreatePhysicsStateInEditor = false;
FAutoConsoleVariableRef CVarGeometryCollectionCreatePhysicsStateInEditor(TEXT("p.Chaos.GC.CreatePhysicsStateInEditor"), GeometryCollectionCreatePhysicsStateInEditor, TEXT("when on , physics state for a GC will be create in editor ( non PIE )"));
bool GeometryCollectionUseReplicationV2 = true;
FAutoConsoleVariableRef CVarGeometryCollectionUseReplicationV2(TEXT("p.Chaos.GC.UseReplicationV2"), GeometryCollectionUseReplicationV2, TEXT("When true use new replication data model"));
int32 GeometryCollectionNetAwakeningMode = 1;
FAutoConsoleVariableRef CVarGeometryCollectionNetAwakeningMode(TEXT("p.Chaos.GC.NetAwakeningMode"), GeometryCollectionNetAwakeningMode, TEXT("Changes how GC components ensure that their owner is awake for replication. 0 = ForceDormancyAwake, 1 = Use Flush Net Dormancy"));
bool bGeometryCollectionCustomRendererHiddenActorFix = true;
FAutoConsoleVariableRef CVarGeometryCollectionCustomRendererHiddenActorFix(TEXT("p.Chaos.GC.CustomRendererHiddenActorFix"), bGeometryCollectionCustomRendererHiddenActorFix, TEXT("When true custom renderer will account for the actor hidden flag"));
// this cvar enables by default the fix for the render issue happening when a cluster with children breaks from a remove on break cluster crumbling event
// this should remain true unless an issue is found with it ( note this was introduce right after 5.5 )
bool bGeometryCollectionRemoveOnBreakDecayFix = true;
FAutoConsoleVariableRef CVarGeometryCollectionRemoveOnBreakDecayFix(TEXT("p.Chaos.GC.RemoveOnBreakDecayFix"), bGeometryCollectionRemoveOnBreakDecayFix, TEXT("When true, fix the issue causing a remove on break cluster crumbling with simulating children from disappearing for several frames"));
DEFINE_LOG_CATEGORY_STATIC(UGCC_LOG, Error, All);
DEFINE_LOG_CATEGORY_STATIC(LogGeometryCollectionComponent, Warning, All);
extern FGeometryCollectionDynamicDataPool GDynamicDataPool;
static void GetGeometryCollectionComponentDebugDebugName(const UGeometryCollectionComponent& Comp, FString& DebugName)
{
// Setup names
// Make the debug name for this geometry...
DebugName.Reset();
#if WITH_EDITOR
if (Comp.GetOwner())
{
DebugName += FString::Printf(TEXT("Actor: '%s' "), *Comp.GetOwner()->GetActorLabel(false));
}
#endif
DebugName += FString::Printf(TEXT("Component: '%s' "), *Comp.GetPathName());
}
FString NetModeToString(ENetMode InMode)
{
switch(InMode)
{
case ENetMode::NM_Client:
return FString("Client");
case ENetMode::NM_DedicatedServer:
return FString("DedicatedServer");
case ENetMode::NM_ListenServer:
return FString("ListenServer");
case ENetMode::NM_Standalone:
return FString("Standalone");
default:
break;
}
return FString("INVALID NETMODE");
}
FString RoleToString(ENetRole InRole)
{
switch(InRole)
{
case ROLE_None:
return FString(TEXT("None"));
case ROLE_SimulatedProxy:
return FString(TEXT("SimProxy"));
case ROLE_AutonomousProxy:
return FString(TEXT("AutoProxy"));
case ROLE_Authority:
return FString(TEXT("Auth"));
default:
break;
}
return FString(TEXT("Invalid Role"));
}
int32 GetClusterLevel(const FTransformCollection* Collection, int32 TransformGroupIndex)
{
int32 Level = 0;
while(Collection && Collection->Parent[TransformGroupIndex] != -1)
{
TransformGroupIndex = Collection->Parent[TransformGroupIndex];
Level++;
}
return Level;
}
bool FGeometryCollectionRepData::Identical(const FGeometryCollectionRepData* Other, uint32 PortFlags) const
{
return Other && (Version == Other->Version);
}
bool FGeometryCollectionRepData::HasChanged(const FGeometryCollectionRepData& BaseData) const
{
if((Version == 0) || (bIsRootAnchored != BaseData.bIsRootAnchored))
{
return true;
}
for (const FGeometryCollectionActivatedCluster& Data : OneOffActivated)
{
if (!BaseData.OneOffActivated.Contains(Data))
{
return true;
}
}
for (const FGeometryCollectionClusterRep& Data : Clusters)
{
const FGeometryCollectionClusterRep* ExistingData = BaseData.Clusters.FindByPredicate(
[&TransformIndex = Data.ClusterIdx](const FGeometryCollectionClusterRep& OtherData) ->
bool { return OtherData.ClusterIdx == TransformIndex; });
if(!ExistingData || (ExistingData && Data.ClusterChanged(*ExistingData)))
{
return true;
}
}
return false;
}
#if UE_WITH_IRIS
UE_NET_IMPLEMENT_NAMED_STRUCT_LASTRESORT_NETSERIALIZER_AND_REGISTRY_DELEGATES(GeometryCollectionRepData);
#endif // UE_WITH_IRIS
bool FGeometryCollectionRepData::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess)
{
bOutSuccess = true;
Ar << Version;
Ar << OneOffActivated;
Ar << ServerFrame;
int32 NumClusters = Clusters.Num();
Ar << NumClusters;
Ar << bIsRootAnchored;
if(Ar.IsLoading())
{
Clusters.SetNum(NumClusters);
// Resetting this received time signals that this is the first frame that this
// RepData will be processed.
RepDataReceivedTime.Reset();
}
for(FGeometryCollectionClusterRep& Cluster : Clusters)
{
Ar << Cluster.Position;
Ar << Cluster.LinearVelocity;
Ar << Cluster.AngularVelocity;
Ar << Cluster.Rotation;
Ar << Cluster.ClusterIdx;
Ar << Cluster.ClusterState.Value;
}
return true;
}
bool FGeometryCollectionRepStateData::SetBroken(int32 TransformIndex, int32 NumTransforms, bool bDisabled, const FVector& LinV, const FVector& AngVInRadiansPerSecond)
{
if (BrokenState.Num() != NumTransforms)
{
BrokenState.SetNum(NumTransforms, false);
}
if (!BrokenState[TransformIndex])
{
BrokenState[TransformIndex] = true;
FReleasedData Data;
Data.TransformIndex = TransformIndex;
Data.LinearVelocity = LinV;
Data.AngularVelocityInDegreesPerSecond = FMath::RadiansToDegrees(AngVInRadiansPerSecond);
ReleasedData.Emplace(Data);
return true;
}
// breaking state already recorded, if it's disabled we can remove it from the ReleasedData
// because it certainly went throug the removal stage and clients do not need to care about the initial velocity anymore
if (bDisabled)
{
const int32 FoundDataIndex = ReleasedData.IndexOfByPredicate(
[&TransformIndex](const FReleasedData& Data) -> bool { return (Data.TransformIndex == TransformIndex); });
if (FoundDataIndex != INDEX_NONE)
{
ReleasedData.RemoveAtSwap(FoundDataIndex, EAllowShrinking::No);
}
// this does not have to be reported as a state change to save bandwidth
return false;
}
return false;
}
bool FGeometryCollectionRepStateData::Identical(const FGeometryCollectionRepStateData* Other, uint32 PortFlags) const
{
return Other && (Version == Other->Version);
}
bool FGeometryCollectionRepStateData::HasChanged(const FGeometryCollectionRepStateData& BaseData) const
{
// We are not using the ReleasedData to check if something has changed since this array is fully controlled by the brokenstate one
return (Version == 0) || (BrokenState != BaseData.BrokenState) || (bIsRootAnchored != BaseData.bIsRootAnchored);
}
#if UE_WITH_IRIS
UE_NET_IMPLEMENT_NAMED_STRUCT_LASTRESORT_NETSERIALIZER_AND_REGISTRY_DELEGATES(GeometryCollectionRepStateData);
#endif // UE_WITH_IRIS
bool FGeometryCollectionRepStateData::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess)
{
bOutSuccess = true;
Ar << Version;
Ar << BrokenState;
Ar << bIsRootAnchored;
// we only support up to 2^16 bones for replication ( bandwidth reasons )
ensure(ReleasedData.Num() < TNumericLimits<uint16>::Max());
uint16 NumReleasedData = static_cast<uint16>(ReleasedData.Num());
Ar << NumReleasedData;
if (Ar.IsLoading())
{
ReleasedData.SetNumUninitialized(static_cast<int32>(NumReleasedData));
}
for (FReleasedData& Data : ReleasedData)
{
Ar << Data.TransformIndex;
Ar << Data.LinearVelocity;
Ar << Data.AngularVelocityInDegreesPerSecond;
}
return true;
}
bool FGeometryCollectionRepDynamicData::FClusterData::IsEqualPositionsAndVelocities(const FGeometryCollectionRepDynamicData::FClusterData& Data) const
{
return Position.Equals(Data.Position)
&& EulerRotation.Equals(Data.EulerRotation)
&& LinearVelocity.Equals(Data.LinearVelocity)
&& AngularVelocityInDegreesPerSecond.Equals(Data.AngularVelocityInDegreesPerSecond)
;
}
bool FGeometryCollectionRepDynamicData::SetData(const FGeometryCollectionRepDynamicData::FClusterData& Data)
{
FClusterData* ExistingData = ClusterData.FindByPredicate(
[&TransformIndex = Data.TransformIndex](const FClusterData& Data) -> bool { return Data.TransformIndex == TransformIndex; });
if (ExistingData)
{
const bool bHasChanged = !ExistingData->IsEqualPositionsAndVelocities(Data);
if (bHasChanged)
{
*ExistingData = Data;
ExistingData->LastUpdatedVersion = (Version + 1);
}
return bHasChanged;
}
FClusterData& NewData = ClusterData.Emplace_GetRef(Data);
NewData.LastUpdatedVersion = (Version + 1);
return true;
}
bool FGeometryCollectionRepDynamicData::RemoveOutOfDateClusterData()
{
const int32 NumRemoved = ClusterData.RemoveAllSwap(
[this](const FClusterData& Data) -> bool
{
return (Data.LastUpdatedVersion <= Version);
},
EAllowShrinking::No);
return (NumRemoved > 0);
}
bool FGeometryCollectionRepDynamicData::Identical(const FGeometryCollectionRepDynamicData* Other, uint32 PortFlags) const
{
return Other && (Version == Other->Version);
}
bool FGeometryCollectionRepDynamicData::HasChanged(const FGeometryCollectionRepDynamicData& BaseData) const
{
if (Version == 0)
{
return true;
}
for (const FGeometryCollectionRepDynamicData::FClusterData& Data : ClusterData)
{
const FGeometryCollectionRepDynamicData::FClusterData* ExistingData = BaseData.ClusterData.FindByPredicate(
[&TransformIndex = Data.TransformIndex](const FGeometryCollectionRepDynamicData::FClusterData& OtherData) ->
bool { return OtherData.TransformIndex == TransformIndex; });
if (!ExistingData || (ExistingData && !ExistingData->IsEqualPositionsAndVelocities(Data)))
{
return true;
}
}
return false;
}
#if UE_WITH_IRIS
UE_NET_IMPLEMENT_NAMED_STRUCT_LASTRESORT_NETSERIALIZER_AND_REGISTRY_DELEGATES(GeometryCollectionRepDynamicData);
#endif // UE_WITH_IRIS
bool FGeometryCollectionRepDynamicData::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess)
{
Ar << Version;
// we only support up to 2^16 bones for replication ( bandwidth reasons )
ensure(ClusterData.Num() < TNumericLimits<uint16>::Max());
uint16 NumClusterData = static_cast<uint16>(ClusterData.Num());
Ar << NumClusterData;
if (Ar.IsLoading())
{
ClusterData.SetNumUninitialized(static_cast<int32>(NumClusterData));
}
for (FClusterData& Data : ClusterData)
{
Ar << Data.TransformIndex;
Ar << Data.bIsInternalCluster;
Ar << Data.Position;
Ar << Data.EulerRotation; // as rotator
Ar << Data.LinearVelocity;
Ar << Data.AngularVelocityInDegreesPerSecond;
}
return true;
}
int32 GGeometryCollectionNanite = 1;
FAutoConsoleVariableRef CVarGeometryCollectionNanite(
TEXT("r.GeometryCollection.Nanite"),
GGeometryCollectionNanite,
TEXT("Render geometry collections using Nanite."),
FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable)
{
FGlobalComponentRecreateRenderStateContext Context;
}),
ECVF_RenderThreadSafe
);
// Size in CM used as a threshold for whether a geometry in the collection is collected and exported for
// navigation purposes. Measured as the diagonal of the leaf node bounds.
float GGeometryCollectionNavigationSizeThreshold = 20.0f;
FAutoConsoleVariableRef CVarGeometryCollectionNavigationSizeThreshold(TEXT("p.GeometryCollectionNavigationSizeThreshold"), GGeometryCollectionNavigationSizeThreshold, TEXT("Size in CM used as a threshold for whether a geometry in the collection is collected and exported for navigation purposes. Measured as the diagonal of the leaf node bounds."));
// Single-Threaded Bounds
bool bGeometryCollectionSingleThreadedBoundsCalculation = false;
FAutoConsoleVariableRef CVarGeometryCollectionSingleThreadedBoundsCalculation(TEXT("p.GeometryCollectionSingleThreadedBoundsCalculation"), bGeometryCollectionSingleThreadedBoundsCalculation, TEXT("[Debug Only] Single threaded bounds calculation. [def:false]"));
FName UGeometryCollectionComponent::DefaultCollisionProfileName("InternalGCDefaultCollision");
FGeomComponentCacheParameters::FGeomComponentCacheParameters()
: CacheMode(EGeometryCollectionCacheType::None)
, TargetCache(nullptr)
, ReverseCacheBeginTime(0.0f)
, SaveCollisionData(false)
, DoGenerateCollisionData(false)
, CollisionDataSizeMax(512)
, DoCollisionDataSpatialHash(false)
, CollisionDataSpatialHashRadius(50.f)
, MaxCollisionPerCell(1)
, SaveBreakingData(false)
, DoGenerateBreakingData(false)
, BreakingDataSizeMax(512)
, DoBreakingDataSpatialHash(false)
, BreakingDataSpatialHashRadius(50.f)
, MaxBreakingPerCell(1)
, SaveTrailingData(false)
, DoGenerateTrailingData(false)
, TrailingDataSizeMax(512)
, TrailingMinSpeedThreshold(200.f)
, TrailingMinVolumeThreshold(10000.f)
{
}
#undef COPY_ON_WRITE_ATTRIBUTE
#define COPY_ON_WRITE_ATTRIBUTE(Type, Name, Group) \
const TManagedArray<Type>& UGeometryCollectionComponent::Get##Name##Array() const \
{ \
return RestCollection->GetGeometryCollection()->Name; \
} \
TManagedArray<Type>& UGeometryCollectionComponent::Get##Name##ArrayCopyOnWrite() \
{ \
static FName StaticName(#Name); \
if (!DynamicCollection->HasAttribute(StaticName, Group)) \
{ \
DynamicCollection->AddAttribute<Type>(StaticName, Group); \
DynamicCollection->CopyAttribute( \
*RestCollection->GetGeometryCollection(), StaticName, Group); \
} \
return RestCollection->GetGeometryCollection()->Name; \
} \
void UGeometryCollectionComponent::Reset##Name##ArrayDynamic() \
{ \
} \
const TManagedArray<Type>& UGeometryCollectionComponent::Get##Name##ArrayRest() const \
{ \
return RestCollection->GetGeometryCollection()->Name; \
} \
#if !(UE_BUILD_SHIPPING)
static TAutoConsoleVariable<int> CVarNumToForceBreak(
TEXT("r.GeometryCollection.CustomRenderer.ForceBreak"),
-1,
TEXT("Force the specified number of pieces to render individually, replacing their root proxy mesh.")
);
struct _ForcedBroken
{
public:
void Reset()
{
Components.Reset();
TotalTransforms = 0;
}
bool Break(UGeometryCollectionComponent* t, const TArray<FTransform3f>& CompSpaceTransforms)
{
if (Components.Contains(t))
return true;
if (TotalTransforms + CompSpaceTransforms.Num() > (uint32)CVarNumToForceBreak->GetInt())
return false;
TotalTransforms += CompSpaceTransforms.Num();
Components.Add(t);
return true;
}
private:
TSet<UGeometryCollectionComponent*> Components;
uint32 TotalTransforms = 0;
}static ForcedBroken;
#endif
// Define the methods
COPY_ON_WRITE_ATTRIBUTES
int32 UGeometryCollectionComponent::GetParent(int32 Index) const
{
if (DynamicCollection)
{
return DynamicCollection->GetParent(Index);
}
return RestCollection->GetGeometryCollection()->Parent[Index];
}
const TManagedArray<int32>& UGeometryCollectionComponent::GetParentArrayRest() const
{
return RestCollection->GetGeometryCollection()->Parent;
}
UGeometryCollectionComponent::UGeometryCollectionComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, ChaosSolverActor(nullptr)
, InitializationState(ESimulationInitializationState::Unintialized)
, ObjectType(EObjectStateTypeEnum::Chaos_Object_Dynamic)
, GravityGroupIndex(0)
, OneWayInteractionLevel(INDEX_NONE)
, bDensityFromPhysicsMaterial(false)
, bForceMotionBlur()
, EnableClustering(true)
, ClusterGroupIndex(0)
, MaxClusterLevel(100)
, MaxSimulatedLevel(100)
, DamageThreshold({ 500000.f, 50000.f, 5000.f })
, bUseSizeSpecificDamageThreshold(false)
, bUseMaterialDamageModifiers(false)
, bEnableDamageFromCollision(true)
, bAllowRemovalOnSleep(true)
, bAllowRemovalOnBreak(true)
, ClusterConnectionType_DEPRECATED(EClusterConnectionTypeEnum::Chaos_MinimalSpanningSubsetDelaunayTriangulation)
, CollisionGroup(0)
, CollisionSampleFraction(1.0)
, InitialVelocityType(EInitialVelocityTypeEnum::Chaos_Initial_Velocity_User_Defined)
, InitialLinearVelocity(0.f, 0.f, 0.f)
, InitialAngularVelocity(0.f, 0.f, 0.f)
, BaseRigidBodyIndex(INDEX_NONE)
, NumParticlesAdded(0)
, CachePlayback(false)
, bNotifyBreaks(false)
, bNotifyCollisions(false)
, bNotifyRemovals(false)
, bNotifyCrumblings(false)
, bCrumblingEventIncludesChildren(false)
, bNotifyGlobalBreaks(false)
, bNotifyGlobalCollisions(false)
, bNotifyGlobalRemovals(false)
, bNotifyGlobalCrumblings(false)
, bGlobalCrumblingEventIncludesChildren(false)
, bStoreVelocities(false)
, bShowBoneColors(false)
, bUpdateComponentTransformToRootBone(false)
, bUseRootProxyForNavigation(false)
, bUpdateNavigationInTick(true)
#if WITH_EDITORONLY_DATA
, bEnableRunTimeDataCollection(false)
, RunTimeDataCollectionGuid(FGuid::NewGuid())
#endif
, bForceBrokenForCustomRenderer(false)
, bUpdateCustomRendererOnPostPhysicsSync(true)
, bCustomRendererCanUseNativeFallback(false)
, bCustomRendererShouldUseNativeFallback(false)
, bForceNativeRenderer(false)
, bEnableReplication(false)
, bEnableAbandonAfterLevel(true)
, AbandonedCollisionProfileName(UCollisionProfile::CustomCollisionProfileName)
, ReplicationAbandonClusterLevel_DEPRECATED(0)
, ReplicationAbandonAfterLevel(0)
, ReplicationMaxPositionAndVelocityCorrectionLevel(100)
, bInitializedRemovalDynamicAttribute(false)
, bEnableBoneSelection(false)
, IsObjectDynamic(false)
, IsObjectLoading(true)
, ViewLevel(-1)
, NavmeshInvalidationTimeSliceIndex(0)
, ComponentSpaceTransforms(this)
, PhysicsProxy(nullptr)
#if WITH_EDITOR && WITH_EDITORONLY_DATA
, EditorActor(nullptr)
#endif
#if GEOMETRYCOLLECTION_EDITOR_SELECTION
, bIsTransformSelectionModeEnabled(false)
#endif // #if GEOMETRYCOLLECTION_EDITOR_SELECTION
{
// by default tick is registered but disabled, we only need it when we need to update the removal timers
// tick will be then enabled only when the root is broken from OnPostPhysicsSync callback
PrimaryComponentTick.bCanEverTick = true;
PrimaryComponentTick.bStartWithTickEnabled = false;
bTickInEditor = true;
static uint32 GlobalNavMeshInvalidationCounter = 0;
//space these out over several frames (3 is arbitrary)
GlobalNavMeshInvalidationCounter += 3;
NavmeshInvalidationTimeSliceIndex = GlobalNavMeshInvalidationCounter;
// default current cache time
CurrentCacheTime = MAX_flt;
SetGenerateOverlapEvents(false);
// By default use the destructible object channel unless the user specifies otherwise
BodyInstance.SetObjectType(ECC_Destructible);
// By default, we initialize immediately. If this is set false, we defer initialization.
BodyInstance.bSimulatePhysics = true;
if (!HasAnyFlags(RF_ArchetypeObject | RF_ClassDefaultObject))
{
EventDispatcher = NewObject<UChaosGameplayEventDispatcher>(this, TEXT("GameplayEventDispatcher"), RF_Transient);
}
DynamicCollection = nullptr;
bHasCustomNavigableGeometry = EHasCustomNavigableGeometry::Yes;
bWantsInitializeComponent = true;
// make sure older asset are using the default behaviour
DamagePropagationData.bEnabled = false;
#if !(UE_BUILD_SHIPPING)
static auto ForcedBrokenDelegate = CVarNumToForceBreak->OnChangedDelegate().Add(FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* CVar)
{
ForcedBroken.Reset();
for (TObjectIterator<UGeometryCollectionComponent> It; It; ++It)
{
It->RefreshCustomRenderer();
}
}));
#endif
}
PRAGMA_DISABLE_DEPRECATION_WARNINGS // Disable warnings for implicit destruction of deprecated members.
UGeometryCollectionComponent::~UGeometryCollectionComponent()
{
if (!ensureMsgf(
!PhysicsProxy,
TEXT("Geometry collection %s destroyed while still attached to physics proxy. "
"BeginDestroy() might be overridden without a base class call."),
*GetName()))
{
// Clear callback to avoid use-after-destruction via dangling pointer.
PhysicsProxy->SetPostPhysicsSyncCallback(nullptr);
}
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
Chaos::FPhysicsSolver* UGeometryCollectionComponent::GetSolver(const UGeometryCollectionComponent& GeometryCollectionComponent)
{
if(GeometryCollectionComponent.ChaosSolverActor)
{
return GeometryCollectionComponent.ChaosSolverActor->GetSolver();
}
else if(UWorld* CurrentWorld = GeometryCollectionComponent.GetWorld())
{
if(FPhysScene* Scene = CurrentWorld->GetPhysicsScene())
{
return Scene->GetSolver();
}
}
return nullptr;
}
void UGeometryCollectionComponent::BeginPlay()
{
Super::BeginPlay();
#if WITH_EDITOR
if (RestCollection != nullptr)
{
if (RestCollection->GetGeometryCollection()->HasAttribute("ExplodedVector", FGeometryCollection::TransformGroup))
{
RestCollection->GetGeometryCollection()->RemoveAttribute("ExplodedVector", FGeometryCollection::TransformGroup);
}
}
#endif
// default current cache time
CurrentCacheTime = MAX_flt;
RefreshCustomRenderer();
}
void UGeometryCollectionComponent::EndPlay(const EEndPlayReason::Type ReasonEnd)
{
#if WITH_EDITOR && WITH_EDITORONLY_DATA
// Track our editor component if needed for syncing simulations back from PIE on shutdown
EditorActor = EditorUtilities::GetEditorWorldCounterpartActor(GetTypedOuter<AActor>());
#endif
Super::EndPlay(ReasonEnd);
CurrentCacheTime = MAX_flt;
}
void UGeometryCollectionComponent::OnVisibilityChanged()
{
Super::OnVisibilityChanged();
RefreshCustomRenderer();
}
void UGeometryCollectionComponent::OnActorVisibilityChanged()
{
Super::OnVisibilityChanged();
RefreshCustomRenderer();
}
void UGeometryCollectionComponent::OnHiddenInGameChanged()
{
Super::OnHiddenInGameChanged();
RefreshCustomRenderer();
}
void UGeometryCollectionComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
/*
FDoRepLifetimeParams Params;
Params.bIsPushBased = true;
Params.RepNotifyCondition = REPNOTIFY_OnChanged;
DOREPLIFETIME_WITH_PARAMS_FAST(UGeometryCollectionComponent, RepData, Params);*/
DOREPLIFETIME(UGeometryCollectionComponent, RepData);
FDoRepLifetimeParams Params;
Params.bIsPushBased = true;
Params.RepNotifyCondition = REPNOTIFY_OnChanged;
DOREPLIFETIME_WITH_PARAMS_FAST(UGeometryCollectionComponent, RepStateData, Params);
DOREPLIFETIME_WITH_PARAMS_FAST(UGeometryCollectionComponent, RepDynamicData, Params);
}
namespace
{
template <typename TTransformType>
void UpdateGlobalMatricesWithExplodedVectors(TArray<TTransformType>& GlobalMatricesInOut, FGeometryCollection& GeometryCollection)
{
int32 NumMatrices = GlobalMatricesInOut.Num();
if (GlobalMatricesInOut.Num() > 0)
{
if (GeometryCollection.HasAttribute("ExplodedVector", FGeometryCollection::TransformGroup))
{
const TManagedArray<FVector3f>& ExplodedVectors = GeometryCollection.GetAttribute<FVector3f>("ExplodedVector", FGeometryCollection::TransformGroup);
if (!ensure(NumMatrices == ExplodedVectors.Num()))
{
return;
}
constexpr bool bIsMatrixType = std::is_same<TTransformType, FMatrix>::value;
if constexpr (bIsMatrixType)
{
for (int32 TransformIndex = 0; TransformIndex < ExplodedVectors.Num(); ++TransformIndex)
{
GlobalMatricesInOut[TransformIndex] = GlobalMatricesInOut[TransformIndex].ConcatTranslation((FVector)ExplodedVectors[TransformIndex]);
}
}
else
{
for (int32 TransformIndex = 0; TransformIndex < ExplodedVectors.Num(); ++TransformIndex)
{
GlobalMatricesInOut[TransformIndex].AddToTranslation((FVector3f)ExplodedVectors[TransformIndex]);
}
}
}
}
}
/**
* compute the bounding box from the bounding boxes stored in the geometry group
*/
template <typename TWorldTransformType, typename TLocalTransformType>
inline FBox ComputeBoundsFromGeometryBoundingBoxes(
const TManagedArray<int32>& TransformToGeometryIndex,
const TManagedArray<int32>& TransformIndices,
const TManagedArray<FBox>& BoundingBoxes,
const TArray<TLocalTransformType>& GlobalMatrices,
const TWorldTransformType& LocalToWorldWithScale)
{
FBox BoundingBox(ForceInit);
// todo(chaos ) implement ISPC function using FTransform
constexpr bool bIsMatrixType = std::is_same<TLocalTransformType, FMatrix>::value && std::is_same<TWorldTransformType, FMatrix>::value;
if constexpr (bIsMatrixType)
{
if (bChaos_BoxCalcBounds_ISPC_Enabled && !bGeometryCollectionSingleThreadedBoundsCalculation)
{
ensure(BoundingBoxes.Num() > 0);
ensure(TransformIndices.Num() == BoundingBoxes.Num());
ensure(TransformToGeometryIndex.Num() > 0);
ensure(TransformToGeometryIndex.Num() == GlobalMatrices.Num());
#if INTEL_ISPC
ispc::BoxCalcBoundsFromGeometryGroup(
(int32*)&TransformToGeometryIndex[0],
(int32*)&TransformIndices[0],
(ispc::FMatrix*)&GlobalMatrices[0],
(ispc::FBox*)&BoundingBoxes[0],
(ispc::FMatrix&)LocalToWorldWithScale,
(ispc::FBox&)BoundingBox,
BoundingBoxes.Num());
#endif
return BoundingBox;
}
}
// non-ISPC path
for (int32 BoxIdx = 0; BoxIdx < BoundingBoxes.Num(); ++BoxIdx)
{
const int32 TransformIndex = TransformIndices[BoxIdx];
if (TransformToGeometryIndex[TransformIndex] != INDEX_NONE)
{
BoundingBox += BoundingBoxes[BoxIdx].TransformBy(TWorldTransformType(GlobalMatrices[TransformIndex]) * LocalToWorldWithScale);
}
}
return BoundingBox;
}
/**
* compute the bounding box from the bounding boxes stored in the transform group
* (used for nanite or when the geometry group data has been stripped on cook )
*/
template <typename TWorldTransformType, typename TLocalTransformType>
inline FBox ComputeBoundsFromTransformBoundingBoxes(
const TManagedArray<int32>& TransformToGeometryIndex,
const TManagedArray<FBox>& BoundingBoxes,
const TArray<TLocalTransformType>& GlobalMatrices,
const TWorldTransformType& LocalToWorldWithScale)
{
FBox BoundingBox(ForceInit);
// todo(chaos ) implement ISPC function using FTransform
constexpr bool bIsMatrixType = std::is_same<TWorldTransformType, FMatrix>::value && std::is_same<TLocalTransformType, FMatrix>::value;
if constexpr (bIsMatrixType)
{
if (bChaos_BoxCalcBounds_ISPC_Enabled && !bGeometryCollectionSingleThreadedBoundsCalculation)
{
#if INTEL_ISPC
ispc::BoxCalcBoundsFromTransformGroup(
(int32*)&TransformToGeometryIndex[0],
(ispc::FMatrix*)&GlobalMatrices[0],
(ispc::FBox*)&BoundingBoxes[0],
(ispc::FMatrix&)LocalToWorldWithScale,
(ispc::FBox&)BoundingBox,
BoundingBoxes.Num());
#endif
return BoundingBox;
}
}
// non-ISPC path
for (int32 TransformIndex = 0; TransformIndex < BoundingBoxes.Num(); ++TransformIndex)
{
if (TransformToGeometryIndex[TransformIndex] != INDEX_NONE)
{
BoundingBox += BoundingBoxes[TransformIndex].TransformBy(TWorldTransformType(GlobalMatrices[TransformIndex]) * LocalToWorldWithScale);
}
}
return BoundingBox;
}
template <typename TWorldTransformType, typename TLocalTransformType>
inline FBox ComputeBoundsFromTransforms(const UGeometryCollectionComponent& Component, const TWorldTransformType& LocalToWorldWithScale, const TArray<TLocalTransformType>& GlobalMatricesArray)
{
static FName BoundingBoxAttributeName = "BoundingBox";
auto GeometryCollectionPtr = Component.GetRestCollection()->GetGeometryCollection();
const TManagedArray<FBox>* TransformBoundingBoxes = GeometryCollectionPtr->FindAttribute<FBox>(BoundingBoxAttributeName, FGeometryCollection::TransformGroup);
const TManagedArray<FBox>& GeometryBoundingBoxes = GeometryCollectionPtr->BoundingBox;
const TManagedArray<int32>& TransformToGeometryIndex = GeometryCollectionPtr->TransformToGeometryIndex;
if (TransformBoundingBoxes)
{
return ComputeBoundsFromTransformBoundingBoxes(TransformToGeometryIndex, *TransformBoundingBoxes, GlobalMatricesArray, LocalToWorldWithScale);
}
const TManagedArray<int32>& TransformIndices = GeometryCollectionPtr->TransformIndex;
return ComputeBoundsFromGeometryBoundingBoxes(TransformToGeometryIndex, TransformIndices, GeometryBoundingBoxes, GlobalMatricesArray, LocalToWorldWithScale);
}
}
FBox UGeometryCollectionComponent::ComputeBoundsFromGlobalMatrices(const FMatrix& LocalToWorldWithScale, const TArray<FMatrix>& GlobalMatricesArray) const
{
return ComputeBoundsFromTransforms<FMatrix, FMatrix>(*this, LocalToWorldWithScale, GlobalMatricesArray);
}
FBox UGeometryCollectionComponent::ComputeBoundsFromComponentSpaceTransforms(const FTransform& LocalToWorldWithScale, const TArray<FTransform>& ComponentSpaceTransformsArray) const
{
return ComputeBoundsFromTransforms<FTransform, FTransform>(*this, LocalToWorldWithScale, ComponentSpaceTransformsArray);
}
FBox UGeometryCollectionComponent::ComputeBoundsFromComponentSpaceTransforms(const FTransform& LocalToWorldWithScale, const TArray<FTransform3f>& ComponentSpaceTransformsArray) const
{
return ComputeBoundsFromTransforms<FTransform, FTransform3f>(*this, LocalToWorldWithScale, ComponentSpaceTransformsArray);
}
FBox UGeometryCollectionComponent::ComputeBounds(const FMatrix& LocalToWorldWithScale) const
{
return ComputeBounds(FTransform(LocalToWorldWithScale));
}
FBox UGeometryCollectionComponent::ComputeBounds(const FTransform& LocalToWorldWithScale) const
{
FBox BoundingBox(ForceInit);
if (RestCollection)
{
if (IsRootBroken())
{
// root is broken we need to go through all the leafs and compute bounds from them
const TArray<FTransform3f>& CompSpaceTransforms = ComponentSpaceTransforms.RequestAllTransforms();
if (CompSpaceTransforms.Num() > 0)
{
BoundingBox = ComputeBoundsFromComponentSpaceTransforms(LocalToWorldWithScale, CompSpaceTransforms);
}
}
else
{
// fast path where we use the root space bounds top avoid paying the high cost of recomputing everything from scratch
if (!RootSpaceBounds.IsValid)
{
const TArray<FTransform3f>& CompSpaceTransforms = ComponentSpaceTransforms.RequestAllTransforms();
if (CompSpaceTransforms.Num() > 0)
{
const FTransform InverseRootTransform(ComponentSpaceTransforms.RequestRootTransform().Inverse());
RootSpaceBounds = ComputeBoundsFromComponentSpaceTransforms(InverseRootTransform, CompSpaceTransforms);
}
}
if (RootSpaceBounds.IsValid)
{
const FTransform RootToWorld = (FTransform(ComponentSpaceTransforms.RequestRootTransform()) * LocalToWorldWithScale);
BoundingBox = RootSpaceBounds.TransformBy(RootToWorld);
}
}
}
return BoundingBox;
}
FBoxSphereBounds UGeometryCollectionComponent::CalcBounds(const FTransform& LocalToWorldIn) const
{
SCOPE_CYCLE_COUNTER(STAT_GCCUpdateBounds);
const bool NeedBoundsUpdate = (!ComponentSpaceBounds.IsValid) || CachePlayback;
if (NeedBoundsUpdate)
{
ComponentSpaceBounds = ComputeBounds(FTransform::Identity);
}
return ComponentSpaceBounds.TransformBy(LocalToWorldIn);
}
int32 UGeometryCollectionComponent::GetNumElements(FName Group) const
{
int32 Size = RestCollection->NumElements(Group); //assume rest collection has the group and is connected to dynamic.
return Size > 0 ? Size : DynamicCollection->NumElements(Group); //if not, maybe dynamic has the group
}
void UGeometryCollectionComponent::SetDamageModel(EDamageModelTypeEnum InDamageModel)
{
DamageModel = InDamageModel;
if (PhysicsProxy)
{
PhysicsProxy->SetDamageModel_External(DamageModel);
}
}
void UGeometryCollectionComponent::SetGravityGroupIndex(int32 InGravityGroupIndex)
{
GravityGroupIndex = InGravityGroupIndex;
if (PhysicsProxy)
{
PhysicsProxy->SetGravityGroupIndex_External(GravityGroupIndex);
}
}
void UGeometryCollectionComponent::SetOneWayInteractionLevel(int32 InOneWayInteractionLevel)
{
OneWayInteractionLevel = InOneWayInteractionLevel;
if (PhysicsProxy)
{
PhysicsProxy->SetOneWayInteractionLevel_External(InOneWayInteractionLevel);
}
}
void UGeometryCollectionComponent::SetDamageThreshold(const TArray<float>& InDamageThreshold)
{
// NOTE: Should only call this during construction, not during runtime
DamageThreshold = InDamageThreshold;
if (PhysicsProxy)
{
PhysicsProxy->SetDamageThresholds_External(DamageThreshold);
}
}
void UGeometryCollectionComponent::SetDamagePropagationData(const FGeometryCollectionDamagePropagationData& InDamagePropagationData)
{
DamagePropagationData = InDamagePropagationData;
if (PhysicsProxy)
{
PhysicsProxy->SetDamagePropagationData_External(DamagePropagationData.bEnabled, DamagePropagationData.BreakDamagePropagationFactor, DamagePropagationData.ShockDamagePropagationFactor);
}
}
void UGeometryCollectionComponent::SetUseMaterialDamageModifiers(bool bInUseMaterialDamageModifiers)
{
bUseMaterialDamageModifiers = bInUseMaterialDamageModifiers;
if (PhysicsProxy)
{
PhysicsProxy->SetUseMaterialDamageModifiers_External(bUseMaterialDamageModifiers);
}
}
void UGeometryCollectionComponent::SetDensityFromPhysicsMaterial(bool bInDensityFromPhysicsMaterial)
{
bDensityFromPhysicsMaterial = bInDensityFromPhysicsMaterial;
if (PhysicsProxy)
{
PhysicsProxy->SetMaterialOverrideMassScaleMultiplier_External(ComputeMassScaleRelativeToAsset());
}
}
void UGeometryCollectionComponent::UpdateCachedBounds()
{
ComponentSpaceBounds = ComputeBounds(FTransform::Identity);
UpdateBounds();
}
bool UGeometryCollectionComponent::ShouldCreateRenderState() const
{
// Called once on registration
// Returning true here means that we pay overhead of render thread tasks for maintaining render state.
// Try and return false whenever possible, but ONLY if there is no chance that we will want to create a scene proxy.
return
!IsCustomRendererAvailable() || // Custom renderer replaces need for a scene proxy.
bCustomRendererCanUseNativeFallback || // Except if custom renderer can require native rendering according to state.
!GetWorld()->IsGameWorld(); // Editor worlds need to support fracture editor which forces native rendering.
}
void UGeometryCollectionComponent::CreateRenderState_Concurrent(FRegisterComponentContext* Context)
{
Super::CreateRenderState_Concurrent(Context);
}
FPrimitiveSceneProxy* UGeometryCollectionComponent::CreateSceneProxy()
{
static const auto NaniteProxyRenderModeVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Nanite.ProxyRenderMode"));
const int32 NaniteProxyRenderMode = (NaniteProxyRenderModeVar != nullptr) ? (NaniteProxyRenderModeVar->GetInt() != 0) : 0;
FPrimitiveSceneProxy* LocalSceneProxy = nullptr;
if (RestCollection && !IsUsingCustomRenderer())
{
if (UseNanite(GetScene()->GetShaderPlatform()) &&
RestCollection->EnableNanite &&
RestCollection->HasNaniteData() &&
GGeometryCollectionNanite != 0)
{
LocalSceneProxy = new FNaniteGeometryCollectionSceneProxy(this);
}
else if (RestCollection->HasMeshData())
{
LocalSceneProxy = new FGeometryCollectionSceneProxy(this);
}
}
return LocalSceneProxy;
}
bool UGeometryCollectionComponent::ShouldCreatePhysicsState() const
{
// Geometry collections always create physics state, not relying on the
// underlying implementation that requires the body instance to decide
return true;
}
bool UGeometryCollectionComponent::HasValidPhysicsState() const
{
return PhysicsProxy != nullptr;
}
void UGeometryCollectionComponent::SetNotifyBreaks(bool bNewNotifyBreaks)
{
if (bNotifyBreaks != bNewNotifyBreaks)
{
if (PhysicsProxy)
{
PhysicsProxy->SetNotifyBreakings_External(bNewNotifyBreaks);
}
bNotifyBreaks = bNewNotifyBreaks;
RegisterForEvents();
}
}
void UGeometryCollectionComponent::SetNotifyRemovals(bool bNewNotifyRemovals)
{
if (bNotifyRemovals != bNewNotifyRemovals)
{
if (PhysicsProxy)
{
PhysicsProxy->SetNotifyRemovals_External(bNewNotifyRemovals);
}
bNotifyRemovals = bNewNotifyRemovals;
RegisterForEvents();
}
}
void UGeometryCollectionComponent::SetNotifyCrumblings(bool bNewNotifyCrumblings, bool bNewCrumblingEventIncludesChildren)
{
if (bNotifyCrumblings != bNewNotifyCrumblings ||
bCrumblingEventIncludesChildren != bNewCrumblingEventIncludesChildren)
{
if (PhysicsProxy)
{
PhysicsProxy->SetNotifyCrumblings_External(bNewNotifyCrumblings, bNewCrumblingEventIncludesChildren);
}
bNotifyCrumblings = bNewNotifyCrumblings;
bCrumblingEventIncludesChildren = bNewCrumblingEventIncludesChildren;
RegisterForEvents();
}
}
void UGeometryCollectionComponent::SetNotifyGlobalBreaks(bool bNewNotifyGlobalBreaks)
{
if (bNotifyGlobalBreaks != bNewNotifyGlobalBreaks)
{
if (PhysicsProxy)
{
PhysicsProxy->SetNotifyGlobalBreakings_External(bNewNotifyGlobalBreaks);
}
bNotifyGlobalBreaks = bNewNotifyGlobalBreaks;
}
}
void UGeometryCollectionComponent::SetNotifyGlobalCollision(bool bNewNotifyGlobalCollisions)
{
if (bNotifyGlobalCollisions != bNewNotifyGlobalCollisions)
{
bNotifyGlobalCollisions = bNewNotifyGlobalCollisions;
UpdateGlobalCollisionEventRegistration();
}
}
void UGeometryCollectionComponent::SetNotifyGlobalRemovals(bool bNewNotifyGlobalRemovals)
{
if (bNotifyGlobalRemovals != bNewNotifyGlobalRemovals)
{
if (PhysicsProxy)
{
PhysicsProxy->SetNotifyGlobalRemovals_External(bNewNotifyGlobalRemovals);
}
bNotifyGlobalRemovals = bNewNotifyGlobalRemovals;
UpdateGlobalRemovalEventRegistration();
}
}
void UGeometryCollectionComponent::SetNotifyGlobalCrumblings(bool bNewNotifyGlobalCrumblings, bool bGlobalNewCrumblingEventIncludesChildren)
{
if (bNotifyGlobalCrumblings != bNewNotifyGlobalCrumblings ||
bGlobalCrumblingEventIncludesChildren != bGlobalNewCrumblingEventIncludesChildren)
{
if (PhysicsProxy)
{
PhysicsProxy->SetNotifyGlobalCrumblings_External(bNewNotifyGlobalCrumblings, bGlobalNewCrumblingEventIncludesChildren);
}
bNotifyGlobalCrumblings = bNewNotifyGlobalCrumblings;
bGlobalCrumblingEventIncludesChildren = bGlobalNewCrumblingEventIncludesChildren;
}
}
FBodyInstance* UGeometryCollectionComponent::GetBodyInstance(FName BoneName /*= NAME_None*/, bool bGetWelded /*= true*/, int32 Index /*=INDEX_NONE*/) const
{
return nullptr;
}
void UGeometryCollectionComponent::SetNotifyRigidBodyCollision(bool bNewNotifyRigidBodyCollision)
{
Super::SetNotifyRigidBodyCollision(bNewNotifyRigidBodyCollision);
UpdateRBCollisionEventRegistration();
}
bool UGeometryCollectionComponent::CanEditSimulatePhysics()
{
return (RestCollection != nullptr);
}
void UGeometryCollectionComponent::SetSimulatePhysics(bool bEnabled)
{
// make sure owner component is set to null before calling Super::SetSimulatePhysics
// this will prevent unwanted log warning to trigger in BodyInstance::SetInstanceSimulatePhysics() because
// in geometry collection , body instance never holds a valid physics handle
const TWeakObjectPtr<UPrimitiveComponent> PreviousOwnerCOmponent = BodyInstance.OwnerComponent;
{
BodyInstance.OwnerComponent = nullptr;
Super::SetSimulatePhysics(bEnabled);
BodyInstance.OwnerComponent = PreviousOwnerCOmponent;
}
if (bEnabled && !PhysicsProxy && RestCollection)
{
EnsurePhysicsStateCreated();
if (!PhysicsProxy && !RestCollection->IsEmpty() && DynamicCollection)
{
RegisterAndInitializePhysicsProxy();
}
}
}
void UGeometryCollectionComponent::SetEnableGravity(bool bGravityEnabled)
{
if (bGravityEnabled != BodyInstance.bEnableGravity)
{
BodyInstance.bEnableGravity = bGravityEnabled;
if (PhysicsProxy)
{
PhysicsProxy->SetEnableGravity_External(bGravityEnabled);
}
}
}
void UGeometryCollectionComponent::AddForce(FVector Force, FName BoneName, bool bAccelChange)
{
ensure(bAccelChange == false); // not supported
const FVector Direction = Force.GetSafeNormal();
const FVector::FReal Magnitude = Force.Size();
const FFieldSystemCommand Command = FFieldObjectCommands::CreateFieldCommand(EFieldPhysicsType::Field_LinearForce, new FUniformVector(Magnitude, Direction));
DispatchFieldCommand(Command);
}
void UGeometryCollectionComponent::AddForceAtLocation(FVector Force, FVector WorldLocation, FName BoneName)
{
if (PhysicsProxy)
{
PhysicsProxy->ApplyForceAt_External(Force, WorldLocation);
}
}
void UGeometryCollectionComponent::AddImpulse(FVector Impulse, FName BoneName, bool bVelChange)
{
const FVector Direction = Impulse.GetSafeNormal();
const FVector::FReal Magnitude = Impulse.Size();
const EFieldPhysicsType FieldType = bVelChange? EFieldPhysicsType::Field_LinearVelocity: EFieldPhysicsType::Field_LinearImpulse;
const FFieldSystemCommand Command = FFieldObjectCommands::CreateFieldCommand(FieldType, new FUniformVector(Magnitude, Direction));
DispatchFieldCommand(Command);
}
void UGeometryCollectionComponent::AddImpulseAtLocation(FVector Impulse, FVector WorldLocation, FName BoneName)
{
if (PhysicsProxy)
{
PhysicsProxy->ApplyImpulseAt_External(Impulse, WorldLocation);
}
}
TUniquePtr<FFieldNodeBase> MakeRadialField(const FVector& Origin, float Radius, float Strength, ERadialImpulseFalloff Falloff)
{
TUniquePtr<FFieldNodeBase> Field;
if (Falloff == ERadialImpulseFalloff::RIF_Constant)
{
Field.Reset(new FRadialVector(Strength, Origin));
}
else
{
FRadialFalloff * FalloffField = new FRadialFalloff(Strength,0.f, 1.f, 0.f, Radius, Origin, EFieldFalloffType::Field_Falloff_Linear);
FRadialVector* VectorField = new FRadialVector(1.f, Origin);
Field.Reset(new FSumVector(1.0, FalloffField, VectorField, nullptr, Field_Multiply));
}
return Field;
}
void UGeometryCollectionComponent::AddRadialForce(FVector Origin, float Radius, float Strength, ERadialImpulseFalloff Falloff, bool bAccelChange)
{
ensure(bAccelChange == false); // not supported
if(bIgnoreRadialForce)
{
return;
}
if (TUniquePtr<FFieldNodeBase> Field = MakeRadialField(Origin, Radius, Strength, Falloff))
{
const FFieldSystemCommand Command = FFieldObjectCommands::CreateFieldCommand(EFieldPhysicsType::Field_LinearForce, Field.Release());
DispatchFieldCommand(Command);
}
}
void UGeometryCollectionComponent::AddRadialImpulse(FVector Origin, float Radius, float Strength, enum ERadialImpulseFalloff Falloff, bool bVelChange)
{
if(bIgnoreRadialImpulse)
{
return;
}
if (TUniquePtr<FFieldNodeBase> Field = MakeRadialField(Origin, Radius, Strength, Falloff))
{
const EFieldPhysicsType FieldType = bVelChange? EFieldPhysicsType::Field_LinearVelocity: EFieldPhysicsType::Field_LinearImpulse;
const FFieldSystemCommand Command = FFieldObjectCommands::CreateFieldCommand(FieldType, Field.Release());
DispatchFieldCommand(Command);
}
}
void UGeometryCollectionComponent::AddTorqueInRadians(FVector Torque, FName BoneName, bool bAccelChange)
{
ensure(bAccelChange == false); // not supported
const FVector Direction = Torque.GetSafeNormal();
const FVector::FReal Magnitude = Torque.Size();
const FFieldSystemCommand Command = FFieldObjectCommands::CreateFieldCommand(EFieldPhysicsType::Field_AngularTorque, new FUniformVector(Magnitude, Direction));
DispatchFieldCommand(Command);
}
bool UGeometryCollectionComponent::IsFullyDecayed() const
{
return bAlreadyFullyDecayed;
}
void UGeometryCollectionComponent::DispatchBreakEvent(const FChaosBreakEvent& Event)
{
// native
NotifyBreak(Event);
// bp
if (OnChaosBreakEvent.IsBound())
{
OnChaosBreakEvent.Broadcast(Event);
}
}
void UGeometryCollectionComponent::DispatchRemovalEvent(const FChaosRemovalEvent& Event)
{
// native
NotifyRemoval(Event);
// bp
if (OnChaosRemovalEvent.IsBound())
{
OnChaosRemovalEvent.Broadcast(Event);
}
}
void UGeometryCollectionComponent::DispatchCrumblingEvent(const FChaosCrumblingEvent& Event)
{
// bp
if (OnChaosCrumblingEvent.IsBound())
{
OnChaosCrumblingEvent.Broadcast(Event);
}
}
bool UGeometryCollectionComponent::DoCustomNavigableGeometryExport(FNavigableGeometryExport& GeomExport) const
{
if(!RestCollection)
{
// No geometry data so skip export - geometry collections don't have other geometry sources
// so return false here to skip non-custom export for this component as well.
return false;
}
if (bUseRootProxyForNavigation)
{
const FGeometryCollectionProxyMeshData& ProxyMeshData = RestCollection->RootProxyData;
bool bHasData = false;
for (int32 MeshIndex = 0; MeshIndex < ProxyMeshData.ProxyMeshes.Num(); MeshIndex++)
{
const TObjectPtr<UStaticMesh>& ProxyMesh = ProxyMeshData.ProxyMeshes[MeshIndex];
if (ProxyMesh != nullptr)
{
const int32 RootIndex = GetRootIndex();
const FTransform& CompToWorld = GetComponentToWorld();
const FTransform FinalTransform = (DynamicCollection ? FTransform(DynamicCollection->GetTransform(RootIndex)) : FTransform::Identity) * CompToWorld;
const FVector Scale3D = FinalTransform.GetScale3D();
const FTransform MeshLocalTransform{ ProxyMeshData.GetMeshTransform(MeshIndex) };
if (!Scale3D.IsZero())
{
if (const UNavCollisionBase* NavCollision = ProxyMesh->GetNavCollision())
{
if (NavCollision->IsDynamicObstacle())
{
continue;
}
bHasData = NavCollision->ExportGeometry(MeshLocalTransform * FinalTransform, GeomExport) || bHasData;
}
}
}
}
if (bHasData)
{
// skip default export
return false;
}
return true;
}
if (RestCollection->bStripOnCook)
{
UE_LOG(LogGeometryCollectionComponent, Log,
TEXT("Collection data is set to be stripped on cook so there is nothing left to export. Make sure to use bUseRootProxyForNavigation with bStripOnCook for %s."), *GetFullNameSafe(GetOwner()));
}
TArray<FVector> OutVertexBuffer;
TArray<int32> OutIndexBuffer;
const FGeometryCollection* const Collection = RestCollection->GetGeometryCollection().Get();
check(Collection);
const float SizeThreshold = GGeometryCollectionNavigationSizeThreshold * GGeometryCollectionNavigationSizeThreshold;
// for all geometry. inspect bounding box build int list of transform indices.
int32 VertexCount = 0;
int32 FaceCountEstimate = 0;
TArray<int32> GeometryIndexBuffer;
TArray<int32> TransformIndexBuffer;
int32 NumGeometry = Collection->NumElements(FGeometryCollection::GeometryGroup);
const TManagedArray<FBox>& BoundingBox = Collection->BoundingBox;
const TManagedArray<int32>& TransformIndexArray = Collection->TransformIndex;
const TManagedArray<int32>& VertexCountArray = Collection->VertexCount;
const TManagedArray<int32>& FaceCountArray = Collection->FaceCount;
const TManagedArray<int32>& VertexStartArray = Collection->VertexStart;
const TManagedArray<FVector3f>& Vertex = Collection->Vertex;
for(int32 GeometryGroupIndex = 0; GeometryGroupIndex < NumGeometry; GeometryGroupIndex++)
{
if(BoundingBox[GeometryGroupIndex].GetSize().SizeSquared() > SizeThreshold)
{
TransformIndexBuffer.Add(TransformIndexArray[GeometryGroupIndex]);
GeometryIndexBuffer.Add(GeometryGroupIndex);
VertexCount += VertexCountArray[GeometryGroupIndex];
FaceCountEstimate += FaceCountArray[GeometryGroupIndex];
}
}
// Get all the geometry transforms in component space (they are stored natively in parent-bone space)
const TArray<FTransform3f>& GeomToComponent = ComponentSpaceTransforms.RequestAllTransforms();
OutVertexBuffer.AddUninitialized(VertexCount);
int32 DestVertex = 0;
//for each "subset" we care about
for(int32 SubsetIndex = 0; SubsetIndex < GeometryIndexBuffer.Num(); ++SubsetIndex)
{
//find indices into the collection data
int32 GeometryIndex = GeometryIndexBuffer[SubsetIndex];
int32 TransformIndex = TransformIndexBuffer[SubsetIndex];
int32 SourceGeometryVertexStart = VertexStartArray[GeometryIndex];
int32 SourceGeometryVertexCount = VertexCountArray[GeometryIndex];
ParallelFor(SourceGeometryVertexCount, [&OutVertexBuffer, &GeomToComponent, &Vertex, SourceGeometryVertexStart, TransformIndex, DestVertex](int32 PointIdx)
{
//extract vertex from source
int32 SourceGeometryVertexIndex = SourceGeometryVertexStart + PointIdx;
const FVector3f VertexInComponentSpace = GeomToComponent[TransformIndex].TransformPosition(Vertex[SourceGeometryVertexIndex]);
int32 DestVertexIndex = DestVertex + PointIdx;
OutVertexBuffer[DestVertexIndex].X = VertexInComponentSpace.X;
OutVertexBuffer[DestVertexIndex].Y = VertexInComponentSpace.Y;
OutVertexBuffer[DestVertexIndex].Z = VertexInComponentSpace.Z;
});
DestVertex += SourceGeometryVertexCount;
}
//gather data needed for indices
const TManagedArray<int32>& FaceStartArray = Collection->FaceStart;
const TManagedArray<FIntVector>& Indices = Collection->Indices;
const TManagedArray<bool>& Visible = Collection->Visible;
const TManagedArray<int32>& MaterialIndex = Collection->MaterialIndex;
//pre-allocate enough room (assuming all faces are visible)
OutIndexBuffer.AddUninitialized(3 * FaceCountEstimate);
//reset vertex counter so that we base the indices off the new location rather than the global vertex list
DestVertex = 0;
int32 DestinationIndex = 0;
//leaving index traversal in a different loop to help cache coherency of source data
for(int32 SubsetIndex = 0; SubsetIndex < GeometryIndexBuffer.Num(); ++SubsetIndex)
{
int32 GeometryIndex = GeometryIndexBuffer[SubsetIndex];
//for each index, subtract the starting vertex for that geometry to make it 0-based. Then add the new starting vertex index for this geometry
int32 SourceGeometryVertexStart = VertexStartArray[GeometryIndex];
int32 SourceGeometryVertexCount = VertexCountArray[GeometryIndex];
int32 IndexDelta = DestVertex - SourceGeometryVertexStart;
int32 FaceStart = FaceStartArray[GeometryIndex];
int32 FaceCount = FaceCountArray[GeometryIndex];
//Copy the faces
for(int FaceIdx = FaceStart; FaceIdx < FaceStart + FaceCount; FaceIdx++)
{
if(Visible[FaceIdx])
{
OutIndexBuffer[DestinationIndex++] = Indices[FaceIdx].X + IndexDelta;
OutIndexBuffer[DestinationIndex++] = Indices[FaceIdx].Y + IndexDelta;
OutIndexBuffer[DestinationIndex++] = Indices[FaceIdx].Z + IndexDelta;
}
}
DestVertex += SourceGeometryVertexCount;
}
// Invisible faces make the index buffer smaller
OutIndexBuffer.SetNum(DestinationIndex);
// Push as a custom mesh to navigation system
// #CHAOSTODO This is pretty inefficient as it copies the whole buffer transforming each vert by the component to world
// transform. Investigate a move aware custom mesh for pre-transformed verts to speed this up.
GeomExport.ExportCustomMesh(OutVertexBuffer.GetData(), OutVertexBuffer.Num(), OutIndexBuffer.GetData(), OutIndexBuffer.Num(), GetComponentToWorld());
return true;
}
UPhysicalMaterial* UGeometryCollectionComponent::GetPhysicalMaterial() const
{
// Pull material from first mesh element to grab physical material. Prefer an override if one exists
UPhysicalMaterial* PhysMatToUse = BodyInstance.GetSimplePhysicalMaterial();
if(!PhysMatToUse || PhysMatToUse->GetFName() == "DefaultPhysicalMaterial")
{
// No override, try render materials
const int32 NumMaterials = GetNumMaterials();
if(NumMaterials > 0)
{
UMaterialInterface* FirstMatInterface = GetMaterial(0);
if(FirstMatInterface && FirstMatInterface->GetPhysicalMaterial())
{
PhysMatToUse = FirstMatInterface->GetPhysicalMaterial();
}
}
}
if(!PhysMatToUse)
{
// Still no material, fallback on default
PhysMatToUse = GEngine->DefaultPhysMaterial;
}
// Should definitely have a material at this point.
check(PhysMatToUse);
return PhysMatToUse;
}
void UGeometryCollectionComponent::RefreshEmbeddedGeometry()
{
const int32 ExemplarCount = EmbeddedGeometryComponents.Num();
if (ExemplarCount == 0)
{
return;
}
const TManagedArray<int32>& ExemplarIndexArray = GetRestCollection()->GetGeometryCollection()->ExemplarIndex;
const int32 TransformCount = ComponentSpaceTransforms.Num();
if (!ensureMsgf(TransformCount == ExemplarIndexArray.Num(), TEXT("GlobalMatrices (Num=%d) cached on GeometryCollectionComponent are not in sync with ExemplarIndexArray (Num=%d) on underlying GeometryCollection; likely missed a dynamic data update"), TransformCount, ExemplarIndexArray.Num()))
{
return;
}
const TManagedArray<bool>* HideArray = nullptr;
if (RestCollection->GetGeometryCollection()->HasAttribute("Hide", FGeometryCollection::TransformGroup))
{
HideArray = &RestCollection->GetGeometryCollection()->GetAttribute<bool>("Hide", FGeometryCollection::TransformGroup);
}
#if WITH_EDITOR
EmbeddedInstanceIndex.Init(INDEX_NONE, RestCollection->GetGeometryCollection()->NumElements(FGeometryCollection::TransformGroup));
#endif
const TArray<FTransform3f>& CompSpaceTransforms = ComponentSpaceTransforms.RequestAllTransforms();
for (int32 ExemplarIndex = 0; ExemplarIndex < ExemplarCount; ++ExemplarIndex)
{
#if WITH_EDITOR
EmbeddedBoneMaps[ExemplarIndex].Empty(TransformCount);
EmbeddedBoneMaps[ExemplarIndex].Reserve(TransformCount); // Allocate for worst case
#endif
TArray<FTransform> InstanceTransforms;
InstanceTransforms.Reserve(TransformCount); // Allocate for worst case
// Construct instance transforms for this exemplar
for (int32 Idx = 0; Idx < TransformCount; ++Idx)
{
if (ExemplarIndexArray[Idx] == ExemplarIndex)
{
if (!HideArray || !(*HideArray)[Idx])
{
InstanceTransforms.Add(FTransform(CompSpaceTransforms[Idx]));
#if WITH_EDITOR
int32 InstanceIndex = EmbeddedBoneMaps[ExemplarIndex].Add(Idx);
EmbeddedInstanceIndex[Idx] = InstanceIndex;
#endif
}
}
}
if (EmbeddedGeometryComponents[ExemplarIndex])
{
const int32 InstanceCount = EmbeddedGeometryComponents[ExemplarIndex]->GetInstanceCount();
// If the number of instances has changed, we rebuild the structure.
if (InstanceCount != InstanceTransforms.Num())
{
EmbeddedGeometryComponents[ExemplarIndex]->ClearInstances();
EmbeddedGeometryComponents[ExemplarIndex]->PreAllocateInstancesMemory(InstanceTransforms.Num());
for (const FTransform& InstanceTransform : InstanceTransforms)
{
EmbeddedGeometryComponents[ExemplarIndex]->AddInstance(InstanceTransform);
}
EmbeddedGeometryComponents[ExemplarIndex]->MarkRenderStateDirty();
}
else
{
// #todo (bmiller) When ISMC has been changed to be able to update transforms in place, we need to switch this function call over.
EmbeddedGeometryComponents[ExemplarIndex]->BatchUpdateInstancesTransforms(0, InstanceTransforms, false, true, false);
// EmbeddedGeometryComponents[ExemplarIndex]->UpdateKinematicTransforms(InstanceTransforms);
}
}
}
}
#if WITH_EDITOR
void UGeometryCollectionComponent::SetEmbeddedGeometrySelectable(bool bSelectableIn)
{
for (TObjectPtr<UInstancedStaticMeshComponent>& EmbeddedGeometryComponent : EmbeddedGeometryComponents)
{
if (EmbeddedGeometryComponent)
{
EmbeddedGeometryComponent->bSelectable = bSelectable;
EmbeddedGeometryComponent->bHasPerInstanceHitProxies = bSelectable;
}
}
}
void UGeometryCollectionComponent::ForceNativeRendering(bool bForce)
{
if (bForce != bForceNativeRenderer)
{
bForceNativeRenderer = bForce;
UnregisterCustomRenderer();
RegisterCustomRenderer();
RefreshCustomRenderer();
MarkRenderStateDirty();
}
}
int32 UGeometryCollectionComponent::EmbeddedIndexToTransformIndex(const UInstancedStaticMeshComponent* ISMComponent, int32 InstanceIndex) const
{
for (int32 ISMIdx = 0; ISMIdx < EmbeddedGeometryComponents.Num(); ++ISMIdx)
{
if (EmbeddedGeometryComponents[ISMIdx].Get() == ISMComponent)
{
return (EmbeddedBoneMaps[ISMIdx][InstanceIndex]);
}
}
return INDEX_NONE;
}
#endif
void UGeometryCollectionComponent::ResetRestTransforms()
{
const bool bWasOverriden = (RestTransforms.Num() > 0);
if (bWasOverriden)
{
RestTransforms.Empty();
if (RestCollection && RestCollection->GetGeometryCollection())
{
if (DynamicCollection)
{
DynamicCollection->ResetInitialTransforms();
}
RestTransformsChanged();
}
}
}
void UGeometryCollectionComponent::SetRestState(TArray<FTransform>&& InRestTransforms)
{
RestTransforms = InRestTransforms;
SetInitialTransforms(RestTransforms);
RestTransformsChanged();
}
void UGeometryCollectionComponent::RestTransformsChanged()
{
if (SceneProxy)
{
OnTransformsDirty();
#if WITH_EDITOR
// We need to do this in case we're controlled by Sequencer in editor, which doesn't invoke PostEditChangeProperty
UpdateCachedBounds();
SendRenderTransform_Concurrent();
#endif
SendDynamicDataToSceneProxy();
}
else
{
// only need to mark transform dirty and let whoever needs it to compute it on demand
OnTransformsDirty();
}
RootSpaceBounds.Init();
RefreshEmbeddedGeometry();
RefreshCustomRenderer();
}
void UGeometryCollectionComponent::GetResourceSizeEx(FResourceSizeEx& CumulativeResourceSize)
{
Super::GetResourceSizeEx(CumulativeResourceSize);
int32 SizeBytes =
InitializationFields.GetAllocatedSize()
+ DamageThreshold.GetAllocatedSize()
+ RestTransforms.GetAllocatedSize()
+ DisabledFlags.GetAllocatedSize()
+ CollisionProfilePerLevel.GetAllocatedSize()
+ ComponentSpaceTransforms.GetAllocatedSize()
+ EventsPlayed.GetAllocatedSize()
+ EmbeddedGeometryComponents.GetAllocatedSize()
+ (ClustersToRep ? ClustersToRep->GetAllocatedSize() : 0);
#if WITH_EDITORONLY_DATA
SizeBytes +=
+ SelectedBones.GetAllocatedSize()
+ HighlightedBones.GetAllocatedSize()
+ EmbeddedInstanceIndex.GetAllocatedSize()
+ EmbeddedBoneMaps.GetAllocatedSize();
for (TArray<int32>& BoneMap : EmbeddedBoneMaps)
{
SizeBytes += BoneMap.GetAllocatedSize();
}
#endif
CumulativeResourceSize.AddDedicatedSystemMemoryBytes(SizeBytes);
}
bool UGeometryCollectionComponent::IsNavigationRelevant() const
{
return bIsCurrentlyNavigationRelevant && Super::IsNavigationRelevant();
}
#if WITH_EDITOR
FDelegateHandle UGeometryCollectionComponent::RegisterOnGeometryCollectionPropertyChanged(const FOnGeometryCollectionPropertyChanged& Delegate)
{
return OnGeometryCollectionPropertyChanged.Add(Delegate);
}
void UGeometryCollectionComponent::UnregisterOnGeometryCollectionPropertyChanged(FDelegateHandle Handle)
{
OnGeometryCollectionPropertyChanged.Remove(Handle);
}
void UGeometryCollectionComponent::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent)
{
Super::PostEditChangeChainProperty(PropertyChangedEvent);
if (PropertyChangedEvent.Property && PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UGeometryCollectionComponent, bShowBoneColors))
{
FScopedColorEdit EditBoneColor(this, true /*bForceUpdate*/); // the property has already changed; this will trigger the color update + render state updates
}
}
void UGeometryCollectionComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
if (OnGeometryCollectionPropertyChanged.IsBound())
{
OnGeometryCollectionPropertyChanged.Broadcast();
}
if (PropertyChangedEvent.Property && PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UGeometryCollectionComponent, ChaosSolverActor))
{
// Reset the dynamic collection
ResetDynamicCollection();
// Recreate the dynamic state since the dynamic collection has been reset
RecreatePhysicsState();
}
}
#endif
static void DispatchGeometryCollectionBreakEvent(const FChaosBreakEvent& Event)
{
if (UGeometryCollectionComponent* const GC = Cast<UGeometryCollectionComponent>(Event.Component))
{
GC->DispatchBreakEvent(Event);
}
}
static void DispatchGeometryCollectionRemovalEvent(const FChaosRemovalEvent& Event)
{
if (UGeometryCollectionComponent* const GC = Cast<UGeometryCollectionComponent>(Event.Component))
{
GC->DispatchRemovalEvent(Event);
}
}
static void DispatchGeometryCollectionCrumblingEvent(const FChaosCrumblingEvent& Event)
{
if (UGeometryCollectionComponent* const GC = Cast<UGeometryCollectionComponent>(Event.Component))
{
GC->DispatchCrumblingEvent(Event);
}
}
// DEPRECATED Use GetComponentSpaceTransforms3f instead
TArray<FTransform> UGeometryCollectionComponent::GetComponentSpaceTransforms()
{
const TArray<FTransform3f>& Transforms3f = GetComponentSpaceTransforms3f();
TArray<FTransform> Transforms;
Transforms.Reserve(Transforms3f.Num());
Algo::Transform(Transforms3f, Transforms, [](const FTransform3f& T) { return FTransform(T); });
return Transforms;
}
const TArray<FTransform3f>& UGeometryCollectionComponent::GetComponentSpaceTransforms3f()
{
return ComponentSpaceTransforms.RequestAllTransforms();
}
const FGeometryDynamicCollection* UGeometryCollectionComponent::GetDynamicCollection() const
{
return DynamicCollection.Get();
}
FGeometryDynamicCollection* UGeometryCollectionComponent::GetDynamicCollection()
{
return DynamicCollection.Get();
}
void UGeometryCollectionComponent::DispatchChaosPhysicsCollisionBlueprintEvents(const FChaosPhysicsCollisionInfo& CollisionInfo)
{
ReceivePhysicsCollision(CollisionInfo);
OnChaosPhysicsCollision.Broadcast(CollisionInfo);
}
// call when first registering
void UGeometryCollectionComponent::RegisterForEvents()
{
UWorld* World = GetWorld();
if (!World)
{
return;
}
FPhysScene* Scene = World->GetPhysicsScene();
if (!Scene)
{
return;
}
Chaos::FPhysicsSolver* Solver = Scene->GetSolver();
if (Solver)
{
if (EventDispatcher)
{
// The new interested proxy system relies on us having having a proxy registered with the scene at the time we register Chaos events.
// Hence we need to do a double-take here to force the re-registration of chaos events. This is guaranteed to happen every time we
// re-create the physics proxy (e.g. in the case of the construction script in PIE). In that case, UChaosGameplayEventDispatcher::OnRegister
// will run *before* the new physics proxy is created.
EventDispatcher->UnregisterChaosEvents();
if (BodyInstance.bNotifyRigidBodyCollision || bNotifyBreaks || bNotifyCollisions || bNotifyRemovals || bNotifyCrumblings)
{
if (bNotifyCollisions || BodyInstance.bNotifyRigidBodyCollision)
{
EventDispatcher->RegisterForCollisionEvents(this, this);
Solver->EnqueueCommandImmediate([Solver]()
{
Solver->SetGenerateCollisionData(true);
});
}
if (bNotifyBreaks)
{
EventDispatcher->RegisterForBreakEvents(this, &DispatchGeometryCollectionBreakEvent);
Solver->EnqueueCommandImmediate([Solver]()
{
Solver->SetGenerateBreakingData(true);
});
}
if (bNotifyRemovals)
{
EventDispatcher->RegisterForRemovalEvents(this, &DispatchGeometryCollectionRemovalEvent);
Solver->EnqueueCommandImmediate([Solver]()
{
Solver->SetGenerateRemovalData(true);
});
}
if (bNotifyCrumblings)
{
EventDispatcher->RegisterForCrumblingEvents(this, &DispatchGeometryCollectionCrumblingEvent);
Solver->EnqueueCommandImmediate([Solver]()
{
Solver->SetGenerateBreakingData(true);
});
}
}
// Needs to be after the RegisterForXYZEvents calls on the event dispatcher or else GetInterestedProxyOwnersForXYZEvents will not
// properly allow our registration to go through.
EventDispatcher->RegisterChaosEvents();
}
if (bNotifyGlobalBreaks || bNotifyGlobalCrumblings)
{
Solver->EnqueueCommandImmediate([Solver]()
{
Solver->SetGenerateBreakingData(true);
});
}
if (bNotifyGlobalCollisions)
{
if (FPhysScene_Chaos* PhysicsScene = GetInnerChaosScene())
{
PhysicsScene->RegisterForGlobalCollisionEvents(this);
Solver->EnqueueCommandImmediate([Solver]()
{
Solver->SetGenerateCollisionData(true);
});
}
}
if (bNotifyGlobalRemovals)
{
if (FPhysScene_Chaos* PhysicsScene = GetInnerChaosScene())
{
PhysicsScene->RegisterForGlobalRemovalEvents(this);
Solver->EnqueueCommandImmediate([Solver]()
{
Solver->SetGenerateRemovalData(true);
});
}
}
}
}
void UGeometryCollectionComponent::UpdateRBCollisionEventRegistration()
{
if (EventDispatcher)
{
if (bNotifyCollisions || BodyInstance.bNotifyRigidBodyCollision)
{
EventDispatcher->RegisterForCollisionEvents(this, this);
}
else
{
EventDispatcher->UnRegisterForCollisionEvents(this, this);
}
}
}
void UGeometryCollectionComponent::UpdateGlobalCollisionEventRegistration()
{
if (FPhysScene_Chaos* PhysScene = GetInnerChaosScene())
{
if (bNotifyGlobalCollisions)
{
PhysScene->RegisterForGlobalCollisionEvents(this);
}
else
{
PhysScene->UnRegisterForGlobalCollisionEvents(this);
}
}
}
void UGeometryCollectionComponent::UpdateGlobalRemovalEventRegistration()
{
if (FPhysScene_Chaos* PhysicsScene = GetInnerChaosScene())
{
if (bNotifyGlobalRemovals)
{
PhysicsScene->RegisterForGlobalRemovalEvents(this);
}
else
{
PhysicsScene->UnRegisterForGlobalRemovalEvents(this);
}
}
}
void ActivateClusters(Chaos::FRigidClustering& Clustering, Chaos::TPBDRigidClusteredParticleHandle<Chaos::FReal, 3>* Cluster)
{
if(!Cluster)
{
return;
}
if(Cluster->ClusterIds().Id)
{
ActivateClusters(Clustering, Cluster->Parent());
}
Clustering.DeactivateClusterParticle(Cluster);
}
void UGeometryCollectionComponent::ResetRepData()
{
ClustersToRep.Reset();
RepData.Reset();
RepStateData.Reset();
RepDynamicData.Reset();
// Those following data are initialized here from the Game Thread,
// but they are read and written from the Physics Thread
// This function ResetRepData is only called when the component is destroyed and so the PhysicsProxy become null
// so at this point those data won't be changed on the Physics Thread.
OneOffActivatedProcessed = 0;
VersionProcessed = INDEX_NONE;
DynamicRepDataVersionProcessed = INDEX_NONE;
LastHardsnapTimeInMs = 0;
}
void UGeometryCollectionComponent::UpdateRepData()
{
if (GeometryCollectionUseReplicationV2)
{
return UpdateRepStateAndDynamicData();
}
check(GetNetMode() != ENetMode::NM_Client);
using namespace Chaos;
if(!bEnableReplication)
{
return;
}
if (PhysicsProxy == nullptr)
{
return;
}
AActor* Owner = GetOwner();
// If we have no owner or our netmode means we never require replication then early out
if(!Owner || Owner->GetNetMode() == ENetMode::NM_Standalone)
{
return;
}
if (Owner && GetIsReplicated() && Owner->GetLocalRole() == ROLE_Authority)
{
const bool bReplicateMovement = Owner->IsReplicatingMovement();
bool bFirstUpdate = false;
if(ClustersToRep == nullptr)
{
//we only allocate set if needed because it's pretty big to have per components that don't replicate
ClustersToRep = MakeUnique<TSet<Chaos::FPBDRigidClusteredParticleHandle*>>();
bFirstUpdate = true;
}
//We need to build a snapshot of the GC
//We rely on the fact that clusters always fracture with one off pieces being removed.
//This means we only need to record the one offs that broke and we get the connected components for free
//The cluster properties are replicated with the first child of each connected component. These are always children that are known at author time and have a unique id per component
//If the first child is disabled it means the properties apply to the parent (i.e. the cluster)
//If the first child is enabled it means it's a one off and the cluster IS the first child
//TODO: for now we have to iterate over all particles to find the clusters, would be better if we had the clusters and children already available
//We are relying on the fact that we fracture one level per step. This means we will see all one offs here
FGeometryCollectionRepData LocalRepData;
LocalRepData.Version = RepData.Version;
LocalRepData.RepDataReceivedTime = RepData.RepDataReceivedTime;
LocalRepData.ServerFrame = RepData.ServerFrame;
FPBDRigidsSolver* Solver = PhysicsProxy->GetSolver<Chaos::FPBDRigidsSolver>();
const FRigidClustering& RigidClustering = Solver->GetEvolution()->GetRigidClustering();
const TManagedArrayAccessor<int32> InitialLevels = PhysicsProxy->GetPhysicsCollection().GetInitialLevels();
const TArray<FPBDRigidClusteredParticleHandle*>& UnorderedParticleHandles = PhysicsProxy->GetUnorderedParticles_Internal();
// Replicate the anchored state of the root particle
LocalRepData.bIsRootAnchored = RepData.bIsRootAnchored;
if (FPBDRigidClusteredParticleHandle* RootHandle = PhysicsProxy->GetInitialRootHandle_Internal())
{
LocalRepData.bIsRootAnchored = RootHandle->IsAnchored();
}
//see if we have any new clusters that are enabled
TSet<FPBDRigidClusteredParticleHandle*> Processed;
// It feels weird to start from the previous one. Need to be checked
LocalRepData.OneOffActivated = RepData.OneOffActivated;
for (FPBDRigidClusteredParticleHandle* Particle : UnorderedParticleHandles)
{
// Particle can be null if we have embedded geometry
if (Particle)
{
bool bProcess = true;
Processed.Add(Particle);
FPBDRigidClusteredParticleHandle* Root = Particle;
while (Root->Parent())
{
Root = Root->Parent();
//TODO: set avoids n^2, would be nice if clustered particle cached its root
if(Processed.Contains(Root))
{
bProcess = false;
break;
}
else
{
Processed.Add(Root);
}
}
// The additional physics proxy check is to make sure that we don't try to replicate a cluster union particle.
if (bProcess && Root->Disabled() == false && ClustersToRep->Find(Root) == nullptr && Root->PhysicsProxy() == PhysicsProxy)
{
int32 TransformGroupIdx = INDEX_NONE;
int32 Level = INDEX_NONE;
if (Root->InternalCluster() == false)
{
TransformGroupIdx = PhysicsProxy->GetTransformGroupIndexFromHandle(Root);
ensureMsgf(TransformGroupIdx >= 0, TEXT("Non-internal cluster should always have a group index"));
ensureMsgf(TransformGroupIdx < TNumericLimits<uint16>::Max(), TEXT("Trying to replicate GC with more than 65k pieces. We assumed uint16 would suffice"));
Level = InitialLevels.IsValid() && InitialLevels.Num() > 0 ? InitialLevels[TransformGroupIdx] : INDEX_NONE;
}
else
{
// Use internal cluster child's index to compute level.
const TArray<FPBDRigidParticleHandle*>& Children = RigidClustering.GetChildrenMap()[Root];
const int32 ChildTransformGroupIdx = PhysicsProxy->GetTransformGroupIndexFromHandle(Children[0]);
Level = InitialLevels.IsValid() && InitialLevels.Num() > 0 ? (InitialLevels[ChildTransformGroupIdx] - 1) : INDEX_NONE;
}
if (!bEnableAbandonAfterLevel || Level <= ReplicationAbandonAfterLevel)
{
// not already replicated and not abandoned level, start replicating cluster
if (Level <= ReplicationMaxPositionAndVelocityCorrectionLevel)
{
ClustersToRep->Add(Root);
}
}
if(Root->InternalCluster() == false && Level > 0)
{
//a one off so record it
ensureMsgf(TransformGroupIdx >= 0, TEXT("Non-internal cluster should always have a group index"));
ensureMsgf(TransformGroupIdx < TNumericLimits<uint16>::Max(), TEXT("Trying to replicate GC with more than 65k pieces. We assumed uint16 would suffice"));
ensureMsgf(PhysicsProxy->GetParticle_Internal(TransformGroupIdx) != nullptr, TEXT("Invalid particle index being replicated - Contact physics team"));
// Because we cull ClustersToRep with abandoned level, we must make sure we don't add duplicates to one off activated.
// TODO: avoid search for entry for perf
// TODO: once we support deep fracture we should be able to remove one offs clusters that are now disabled, reducing the amount to be replicated
FGeometryCollectionActivatedCluster OneOffActivated(TransformGroupIdx, Root->GetV(), Root->GetW());
if(!LocalRepData.OneOffActivated.Contains(OneOffActivated))
{
LocalRepData.OneOffActivated.Add(OneOffActivated);
}
}
// if we just hit the abandon level , let's disable all children
if (bEnableAbandonAfterLevel && Level >= (ReplicationAbandonAfterLevel + 1))
{
if (!Root->Disabled())
{
Solver->GetEvolution()->DisableParticle(Root);
Solver->GetParticles().MarkTransientDirtyParticle(Root);
}
}
}
}
}
INC_DWORD_STAT_BY(STAT_GCReplicatedFractures, LocalRepData.OneOffActivated.Num());
//Build up clusters to replicate and compare with previous frame
if (bReplicateMovement)
{
LocalRepData.Clusters.Reserve(ClustersToRep->Num());
//remove disabled clusters and update rep data if needed
for (auto Itr = ClustersToRep->CreateIterator(); Itr; ++Itr)
{
FPBDRigidClusteredParticleHandle* Cluster = *Itr;
const TArray<FPBDRigidParticleHandle*>& Children = RigidClustering.GetChildrenMap().FindRef(Cluster);
const bool bIsInternalCluster = Cluster->InternalCluster();
const bool bChildrenEmpty = Children.IsEmpty();
if (Cluster->Disabled() || (bIsInternalCluster && bChildrenEmpty))
{
Itr.RemoveCurrent();
}
else
{
FGeometryCollectionClusterRep& ClusterRep = LocalRepData.Clusters.AddDefaulted_GetRef();
ClusterRep.Position = Cluster->GetX();
ClusterRep.Rotation = Cluster->GetR();
ClusterRep.LinearVelocity = Cluster->GetV();
ClusterRep.AngularVelocity = Cluster->GetW();
ClusterRep.ClusterState.SetObjectState(Cluster->ObjectState());
ClusterRep.ClusterState.SetInternalCluster(Cluster->InternalCluster());
int32 TransformGroupIdx;
if (Cluster->InternalCluster())
{
ensureMsgf(Children.Num(), TEXT("Internal cluster yet we have no children? [Num %d vs Cached Empty %d]"), Children.Num(), bChildrenEmpty);
TransformGroupIdx = PhysicsProxy->GetTransformGroupIndexFromHandle(Children[0]);
}
else
{
// not internal so we can just use the cluster's ID. On client we'll know based on the parent whether to use this index or the parent
TransformGroupIdx = PhysicsProxy->GetTransformGroupIndexFromHandle(Cluster);
}
ensureMsgf(TransformGroupIdx < TNumericLimits<uint16>::Max(), TEXT("Trying to replicate GC with more than 65k pieces. We assumed uint16 would suffice"));
ClusterRep.ClusterIdx = TransformGroupIdx;
}
}
}
if (LocalRepData.HasChanged(RepData))
{
LocalRepData.ServerFrame = Solver->GetMarshallingManager().GetInternalStep_External();
INC_DWORD_STAT_BY(STAT_GCReplicatedClusters, LocalRepData.Clusters.Num());
FlushNetDormancyIfNeeded();
RepData = MoveTemp(LocalRepData);
MARK_PROPERTY_DIRTY_FROM_NAME(UGeometryCollectionComponent, RepData, this);
++RepData.Version;
const bool bSetDormancyToAwake = Owner->NetDormancy > DORM_Awake && GetDesiredNetAwakeningMode() == ENetAwakeningMode::ForceDormancyAwake;
if (bSetDormancyToAwake)
{
if (Owner->NetDormancy > DORM_Awake)
{
//If net dormancy is Initial it must be for perf reasons, but since a cluster changed we need to replicate down
Owner->SetNetDormancy(DORM_Awake);
}
}
}
}
}
void UGeometryCollectionComponent::UpdateRepStateAndDynamicData()
{
check(GetNetMode() != ENetMode::NM_Client);
using namespace Chaos;
if (!bEnableReplication || PhysicsProxy == nullptr)
{
return;
}
AActor* Owner = GetOwner();
// If we have no owner or our netmode means we never require replication then early out
if (!Owner || Owner->GetNetMode() == ENetMode::NM_Standalone)
{
return;
}
if (GetIsReplicated() && Owner->GetLocalRole() == ROLE_Authority)
{
if (RestCollection && RestCollection->GetGeometryCollection())
{
FPBDRigidsSolver* Solver = PhysicsProxy->GetSolver<Chaos::FPBDRigidsSolver>();
// let go through all the transform and see which one has changed its state
const TArray<FPBDRigidClusteredParticleHandle*>& UnorderedParticleHandles = PhysicsProxy->GetUnorderedParticles_Internal();
const int32 NumParticles = UnorderedParticleHandles.Num();
const TManagedArrayAccessor<int32> InitialLevels = PhysicsProxy->GetPhysicsCollection().GetInitialLevels();
FGeometryCollectionRepStateData LocalRepStateData;
FGeometryCollectionRepDynamicData LocalRepDynamicData;
LocalRepStateData.Version = RepStateData.Version;
LocalRepDynamicData.Version = RepDynamicData.Version;
// root level anchor state
LocalRepStateData.bIsRootAnchored = RepStateData.bIsRootAnchored;
if (FPBDRigidClusteredParticleHandle* RootHandle = PhysicsProxy->GetInitialRootHandle_Internal())
{
LocalRepStateData.bIsRootAnchored = RootHandle->IsAnchored() ? 1 : 0;
}
struct FRootHandle
{
const FPBDRigidClusteredParticleHandle* Handle; // handle of the particle or internal cluster
const int32 TransformIndex; // transform index of the particle or the child of the internal cluster
bool operator==(const FRootHandle& Other) const { return Handle == Other.Handle; }
};
// contains all the dynamic root of the geometry collection
// this includes non-clusterunion internal clusters
// using an array as the number of root should always be small
TArray<FRootHandle> RootsToTrack;
const int32 RootIndex = GetRootIndex();
// go through particles and send replicated data
const int32 NumTransforms = PhysicsProxy->GetNumTransforms();
LocalRepStateData.BrokenState.SetNum(NumTransforms, false);
LocalRepStateData.ReleasedData.Reserve(NumTransforms);
for (int32 ParticleIndex = 0; ParticleIndex < NumParticles; ParticleIndex++)
{
const int32 TransformIndex = PhysicsProxy->GetFromParticleToTransformIndex(ParticleIndex);
if (FPBDRigidClusteredParticleHandle* ParticleHandle = UnorderedParticleHandles[ParticleIndex])
{
const int32 Level = (InitialLevels.IsValid() && InitialLevels.IsValidIndex(TransformIndex)) ? InitialLevels[TransformIndex] : INDEX_NONE;
const FPBDRigidClusteredParticleHandle* ParentHandle = ParticleHandle->Parent();
// no parent and not root means the particle has been broken
const bool bIsRootIndex = (TransformIndex == RootIndex);
const bool bIsBroken = (ParentHandle == nullptr && !bIsRootIndex);
if (bIsBroken)
{
// only record the one at the abandon level and before ( child break of abandon level clusters must be recorded )
if (!bEnableAbandonAfterLevel || Level <= ReplicationAbandonAfterLevel)
{
LocalRepStateData.BrokenState[TransformIndex] = true;
if(!ParticleHandle->Disabled())
{
FGeometryCollectionRepStateData::FReleasedData& Data = LocalRepStateData.ReleasedData.AddDefaulted_GetRef();
Data.TransformIndex = TransformIndex;
Data.LinearVelocity = ParticleHandle->GetV();
Data.AngularVelocityInDegreesPerSecond = FMath::RadiansToDegrees(ParticleHandle->GetW());
}
}
// anything beyond ReplicationAbandonAfterLevel must be disabled to save server CPU as they are now client authoritative
if (bEnableAbandonAfterLevel && Level >= (ReplicationAbandonAfterLevel + 1))
{
if (!ParticleHandle->Disabled())
{
Solver->GetEvolution()->DisableParticle(ParticleHandle);
Solver->GetParticles().MarkTransientDirtyParticle(ParticleHandle);
}
}
}
const bool bIsActiveBrokenOrOriginalRoot = (!ParticleHandle->Disabled()) && (bIsBroken || bIsRootIndex);
const bool bHasInternalClusterParent = ParentHandle && (ParentHandle->InternalCluster()) && (ParentHandle->PhysicsProxy() == ParticleHandle->PhysicsProxy());
const FPBDRigidClusteredParticleHandle* RootParticle = bIsActiveBrokenOrOriginalRoot ? ParticleHandle : (bHasInternalClusterParent ? ParentHandle : nullptr);
const int32 RootParticleLevel = (bHasInternalClusterParent)? (Level - 1): (Level);
const bool bTrackPosition = (RootParticleLevel <= this->ReplicationMaxPositionAndVelocityCorrectionLevel);
if (bTrackPosition && RootParticle)
{
RootsToTrack.AddUnique(FRootHandle{ RootParticle, TransformIndex });
}
}
}
//reclaim unused slack
LocalRepStateData.ReleasedData.Shrink();
// let's update the data for the tracked clusters
LocalRepDynamicData.ClusterData.Reserve(RootsToTrack.Num());
for (const FRootHandle& Root: RootsToTrack)
{
FGeometryCollectionRepDynamicData::FClusterData& Data = LocalRepDynamicData.ClusterData.AddDefaulted_GetRef();
Data.TransformIndex = Root.TransformIndex;
Data.bIsInternalCluster = Root.Handle->InternalCluster();
Data.Position = Root.Handle->GetX();
Data.EulerRotation = Root.Handle->GetR().Rotator().Euler();
Data.LinearVelocity = Root.Handle->GetV();
Data.AngularVelocityInDegreesPerSecond = FMath::RadiansToDegrees(Root.Handle->GetW());
Data.LastUpdatedVersion = LocalRepDynamicData.Version + 1;
}
// Check to see if the replicated state and dynamic Data has changed
const bool bStateChanged = LocalRepStateData.HasChanged(RepStateData);
const bool bDynamicChanged = LocalRepDynamicData.HasChanged(RepDynamicData);
if(bStateChanged || bDynamicChanged)
{
FlushNetDormancyIfNeeded();
}
if (bStateChanged)
{
RepStateData = MoveTemp(LocalRepStateData);
MARK_PROPERTY_DIRTY_FROM_NAME(UGeometryCollectionComponent, RepStateData, this);
++RepStateData.Version;
}
if (bDynamicChanged)
{
RepDynamicData = MoveTemp(LocalRepDynamicData);
MARK_PROPERTY_DIRTY_FROM_NAME(UGeometryCollectionComponent, RepDynamicData, this);
++RepDynamicData.Version;
}
if (bDynamicChanged || bStateChanged)
{
const bool bSetDormancyToAwake = Owner->NetDormancy > DORM_Awake && GetDesiredNetAwakeningMode() == ENetAwakeningMode::ForceDormancyAwake;
if (bSetDormancyToAwake)
{
//If net dormancy is Initial it must be for perf reasons, but since a cluster changed we need to replicate down
Owner->SetNetDormancy(DORM_Awake);
}
}
}
}
}
float GeometryCollectionRepLinearMatchStrength = 50;
FAutoConsoleVariableRef CVarGeometryCollectionRepLinearMatchStrength(TEXT("p.GeometryCollectionRepLinearMatchStrength"), GeometryCollectionRepLinearMatchStrength, TEXT("Units can be interpreted as %/s^2 - acceleration of percent linear correction"));
float GeometryCollectionRepAngularMatchTime = .5f;
FAutoConsoleVariableRef CVarGeometryCollectionRepAngularMatchTime(TEXT("p.GeometryCollectionRepAngularMatchTime"), GeometryCollectionRepAngularMatchTime, TEXT("In seconds, how quickly should the angle match the replicated target angle"));
float GeometryCollectionRepMaxExtrapolationTime = 3.f;
FAutoConsoleVariableRef CVarGeometryCollectionRepMaxExtrapolationTime(TEXT("p.GeometryCollectionRepMaxExtrapolationTime"), GeometryCollectionRepMaxExtrapolationTime, TEXT("Number of seconds that replicated physics data will persist for a GC, extrapolating velocities"));
bool bGeometryCollectionDebugDrawRep = 0;
FAutoConsoleVariableRef CVarGeometryCollectionDebugDrawRep(TEXT("p.Chaos.DebugDraw.GeometryCollectionReplication"), bGeometryCollectionDebugDrawRep,
TEXT("If true debug draw deltas and corrections for geometry collection replication"));
bool bGeometryCollectionRepUseClusterVelocityMatch = true;
FAutoConsoleVariableRef CVarGeometryCollectionRepUseClusterVelocityMatch(TEXT("p.bGeometryCollectionRepUseClusterVelocityMatch"), bGeometryCollectionRepUseClusterVelocityMatch,
TEXT("Use physical velocity to match cluster states"));
int32 GeometryCollectionHardMissingUpdatesSnapThreshold = 20;
FAutoConsoleVariableRef CVarGeometryCollectionHardMissingUpdatesSnapThreshold(TEXT("p.GeometryCollectionHardMissingUpdatesSnapThreshold"), GeometryCollectionHardMissingUpdatesSnapThreshold,
TEXT("Determines how many missing updates before we trigger a hard snap"));
int32 GeometryCollectionHardsnapThresholdMs = 100; // 10 Hz
FAutoConsoleVariableRef CVarGeometryCollectionHardsnapThresholdMs(TEXT("p.GeometryCollectionHardsnapThresholdMs"), GeometryCollectionHardMissingUpdatesSnapThreshold,
TEXT("Determines how many ms since the last hardsnap to trigger a new one"));
void UGeometryCollectionComponent::ProcessRepData()
{
bGeometryCollectionRepUseClusterVelocityMatch = false;
ProcessRepData(0.f, 0.f);
}
namespace
{
bool ReplicationShouldHardSnap(int32 RepDataVersion, int32 VersionProcessed, bool bReplicateMovement, double& LastHardsnapTimeInMs)
{
bool bHardSnap = false;
if (bReplicateMovement)
{
const int64 CurrentTimeInMs = FPlatformTime::ToMilliseconds64(FPlatformTime::Cycles64());
// Always hard snap on the very first version received
if (VersionProcessed == 0)
{
bHardSnap = true;
}
else if (VersionProcessed < RepDataVersion)
{
//TODO: this will not really work if a fracture happens and then immediately goes to sleep without updating client enough times
//A time method would work better here, but is limited to async mode. Maybe we can support both
bHardSnap = (RepDataVersion - VersionProcessed) > GeometryCollectionHardMissingUpdatesSnapThreshold;
if (!bGeometryCollectionRepUseClusterVelocityMatch)
{
// When not doing velocity match for clusters, instead we do periodic hard snapping
bHardSnap |= (CurrentTimeInMs - LastHardsnapTimeInMs) > GeometryCollectionHardsnapThresholdMs;
}
}
else if (VersionProcessed > RepDataVersion)
{
//rollover so just treat as hard snap - this case is extremely rare and a one off
bHardSnap = true;
}
if (bHardSnap)
{
LastHardsnapTimeInMs = CurrentTimeInMs;
}
}
return bHardSnap;
}
void UpdateRootStateFromReplication(const FGeometryCollectionPhysicsProxy& PhysicsProxy, const bool bNewAnchoredState)
{
using namespace Chaos;
const int32 InitialRootIndex = PhysicsProxy.GetSimParameters().InitialRootIndex;
if (FPBDRigidClusteredParticleHandle* RootHandle = PhysicsProxy.GetParticle_Internal(InitialRootIndex))
{
if (RootHandle->Parent() || !RootHandle->Disabled())
{
if (bNewAnchoredState != RootHandle->IsAnchored())
{
RootHandle->SetIsAnchored(bNewAnchoredState);
if (FPBDRigidsSolver* Solver = PhysicsProxy.GetSolver<Chaos::FPBDRigidsSolver>())
{
if (FPBDRigidsEvolutionGBF* Evolution = Solver->GetEvolution())
{
FRigidClustering& RigidClustering = Solver->GetEvolution()->GetRigidClustering();
// NOTE: We have to start out with the particle as kinematic and let UpdateKinematicProperties correct it if this is wrong
Evolution->SetParticleObjectState(RootHandle, EObjectStateType::Kinematic);
UpdateKinematicProperties(RootHandle, RigidClustering.GetChildrenMap(), *Evolution);
}
}
}
}
}
}
// return the awaken state of the cluster
bool UpdateClusterPositionAndVelocitiesFromReplication(
Chaos::FPBDRigidsSolver* Solver,
Chaos::FPBDRigidParticleHandle& Cluster,
const FVector& Position, const FQuat& Rotation,
const FVector& LinearVelocity, const FVector& AngularVelocity,
const bool bHardSnap,
const double RepExtrapTime,
const double DeltaTime)
{
bool bWake = false;
if (bHardSnap)
{
Cluster.SetX(Position);
Cluster.SetR(Rotation);
Cluster.SetV(LinearVelocity);
Cluster.SetW(AngularVelocity);
bWake = true;
}
else if (bGeometryCollectionRepUseClusterVelocityMatch)
{
//
// Match linear velocity
//
const FVector RepVel = LinearVelocity;
const FVector RepExtrapPos = Position + (RepVel * RepExtrapTime);
const Chaos::FVec3 DeltaX = RepExtrapPos - Cluster.GetX();
const float DeltaXMagSq = DeltaX.SizeSquared();
if (DeltaXMagSq > SMALL_NUMBER && GeometryCollectionRepLinearMatchStrength > SMALL_NUMBER)
{
bWake = true;
//
// DeltaX * MatchStrength is an acceleration, m/s^2, which is integrated
// by multiplying by DeltaTime.
//
// It's formulated this way to get a larger correction for a longer time
// step, ie. correction velocities are framerate independent.
//
Cluster.SetV(RepVel + (DeltaX * FMath::Min(1.0f, GeometryCollectionRepLinearMatchStrength * DeltaTime)));
}
//
// Match angular velocity
//
const FVector RepAngVel = AngularVelocity;
const Chaos::FRotation3 RepExtrapAng = Chaos::FRotation3::IntegrateRotationWithAngularVelocity(Rotation, RepAngVel, RepExtrapTime);
const FVector AngVel = Chaos::FRotation3::CalculateAngularVelocity(Cluster.GetR(), RepExtrapAng, GeometryCollectionRepAngularMatchTime);
if (AngVel.SizeSquared() > SMALL_NUMBER)
{
Cluster.SetW(RepAngVel + AngVel);
bWake = true;
}
}
//
// Wake up particle if it's sleeping and there's a delta to correct
//
if (bWake && Cluster.IsSleeping())
{
Solver->GetEvolution()->SetParticleObjectState(&Cluster, Chaos::EObjectStateType::Dynamic);
}
return bWake;
}
bool ProcessRepDataCommon(const float DeltaTime, const float SimTime, const FGeometryCollectionRepData& RepData, FGeometryCollectionPhysicsProxy* PhysicsProxy, int32& VersionProcessed, int32& OneOffActivatedProcessed, double& LastHardsnapTimeInMs, bool bReplicateMovement)
{
using namespace Chaos;
if (!PhysicsProxy || !PhysicsProxy->IsInitializedOnPhysicsThread() || PhysicsProxy->GetReplicationMode() != FGeometryCollectionPhysicsProxy::EReplicationMode::Client)
{
return false;
}
float ReceivedTime = SimTime;
// Track the sim time that this rep data was received on.
if (RepData.RepDataReceivedTime.IsSet())
{
ReceivedTime = *RepData.RepDataReceivedTime;
}
// How far we must extrapolate from when we received the data
const float RepExtrapTime = FMath::Max(0.f, SimTime - ReceivedTime);
// If we've extrapolated past a threshold, then stop tracking
// the last received rep data
if (RepExtrapTime > GeometryCollectionRepMaxExtrapolationTime)
{
return false;
}
// Create a little little function for applying a lambda to each
// corresponding pair of replicated and local clusters.
const auto ForEachClusterPair = [&RepData, &PhysicsProxy](const auto& Lambda)
{
for (const FGeometryCollectionClusterRep& RepCluster : RepData.Clusters)
{
if (Chaos::FPBDRigidParticleHandle* Cluster = PhysicsProxy->GetParticle_Internal(RepCluster.ClusterIdx))
{
if (RepCluster.ClusterState.IsInternalCluster())
{
// internal cluster do not have an index so we rep data send one of the children's
// let's find the parent
Cluster = Cluster->CastToClustered()->Parent();
}
if (Cluster && Cluster->Disabled() == false)
{
Lambda(RepCluster, *Cluster);
}
}
}
};
// If not doing velocity match, don't bother processing the same version twice.
// Do this one after the debug draw so that we can still easily see the diff
// between the position and the target position.
if (VersionProcessed == RepData.Version && !bGeometryCollectionRepUseClusterVelocityMatch)
{
return false;
}
// Update the anchored state of the root particle
UpdateRootStateFromReplication(*PhysicsProxy, RepData.bIsRootAnchored);
const bool bHardSnap = ReplicationShouldHardSnap(RepData.Version, VersionProcessed, bReplicateMovement, LastHardsnapTimeInMs);
FPBDRigidsSolver* Solver = PhysicsProxy->GetSolver<Chaos::FPBDRigidsSolver>();
FRigidClustering& RigidClustering = Solver->GetEvolution()->GetRigidClustering();
//First make sure all one off activations have been applied. This ensures our connectivity graph is the same and we have the same clusters as the server
for (; OneOffActivatedProcessed < RepData.OneOffActivated.Num(); ++OneOffActivatedProcessed)
{
const FGeometryCollectionActivatedCluster& ActivatedCluster = RepData.OneOffActivated[OneOffActivatedProcessed];
#if !UE_BUILD_SHIPPING
if(PhysicsProxy->GetParticle_Internal(ActivatedCluster.ActivatedIndex) == nullptr)
{
ensureMsgf(false, TEXT("Invalid activated cluster index processing replication data."));
continue;
}
#endif
FPBDRigidParticleHandle* OneOff = PhysicsProxy->GetParticle_Internal(ActivatedCluster.ActivatedIndex);
if (ensure(OneOff))
{
if (FPBDRigidClusteredParticleHandle* ClusterParticle = OneOff->CastToClustered())
{
// Set initial velocities if not hard snapping
if (!bHardSnap)
{
// TODO: we should get an update cluster position first so that when particles break off they get the right position
// TODO: should we invalidate?
OneOff->SetV(ActivatedCluster.InitialLinearVelocity);
OneOff->SetW(ActivatedCluster.InitialAngularVelocity);
}
// OneOffActivated is not guaranteed to be sorted by level, so we may need to release particles before their parent get released
// so we make sure the full parent chain is properly released as well
RigidClustering.ForceReleaseChildParticleAndParents(ClusterParticle, /* bTriggerBreakEvents */true);
}
}
}
// Keep track of whether we did some "work" on this frame so we can turn off the async tick after
// multiple frames of not doing anything.
bool bProcessed = false;
if (bReplicateMovement)
{
ForEachClusterPair([Solver, DeltaTime, RepExtrapTime, bHardSnap, &bProcessed](const FGeometryCollectionClusterRep& RepData, Chaos::FPBDRigidParticleHandle& Cluster)
{
bProcessed |= UpdateClusterPositionAndVelocitiesFromReplication(Solver, Cluster, RepData.Position, RepData.Rotation, RepData.LinearVelocity, RepData.AngularVelocity, bHardSnap, RepExtrapTime, DeltaTime);
});
}
VersionProcessed = RepData.Version;
return bProcessed;
}
bool ProcessRepStateDataCommon(FGeometryCollectionPhysicsProxy* PhysicsProxy, const FGeometryCollectionRepStateData& RepStateData, int32& VersionProcessed)
{
using namespace Chaos;
if (!PhysicsProxy || !PhysicsProxy->IsInitializedOnPhysicsThread() || PhysicsProxy->GetReplicationMode() != FGeometryCollectionPhysicsProxy::EReplicationMode::Client)
{
return false;
}
if (VersionProcessed == RepStateData.Version)
{
return false;
}
const TArray<FPBDRigidClusteredParticleHandle*>& UnorderedParticleHandle = PhysicsProxy->GetUnorderedParticles_Internal();
// Update the anchored state of the root particle
UpdateRootStateFromReplication(*PhysicsProxy, (bool)RepStateData.bIsRootAnchored);
// now sync the broken state of the particles
if (RepStateData.BrokenState.Num() == PhysicsProxy->GetNumTransforms())
{
FPBDRigidsSolver* Solver = PhysicsProxy->GetSolver<Chaos::FPBDRigidsSolver>();
FRigidClustering& RigidClustering = Solver->GetEvolution()->GetRigidClustering();
for (int32 ParticleIndex = 0; ParticleIndex < UnorderedParticleHandle.Num(); ParticleIndex++)
{
if (FPBDRigidClusteredParticleHandle* ParticleHandle = UnorderedParticleHandle[ParticleIndex])
{
const int32 TransformIndex = PhysicsProxy->GetFromParticleToTransformIndex(ParticleIndex);
// we only check for when thing goes from unbroken to broken
const bool bWasBroken = (ParticleHandle->Parent() == nullptr);
const bool bIsBroken = RepStateData.BrokenState[TransformIndex];
if (!bWasBroken && bIsBroken)
{
RigidClustering.ForceReleaseChildParticleAndParents(ParticleHandle, /* bTriggerBreakEvents */true);
if (!ParticleHandle->Disabled())
{
// check if we have a release velocity to be applied
const FGeometryCollectionRepStateData::FReleasedData* ReleaseData = RepStateData.ReleasedData.FindByPredicate(
[TransformIndex](const FGeometryCollectionRepStateData::FReleasedData& Data)
{
return Data.TransformIndex == TransformIndex;
});
if (ReleaseData)
{
ParticleHandle->SetV(ReleaseData->LinearVelocity);
ParticleHandle->SetW(FMath::DegreesToRadians(ReleaseData->AngularVelocityInDegreesPerSecond));
}
else
{
// if we are a leaf we can disable this particle right away if there's no release data
// because this means the server has already disabled the particle from a removal
if (ParticleHandle->ClusterIds().NumChildren == 0)
{
// todo we could probably disable all at once later to have only one call
Solver->GetEvolution()->DisableParticle(ParticleHandle);
Solver->GetParticles().MarkTransientDirtyParticle(ParticleHandle);
}
}
}
}
}
}
}
VersionProcessed = RepStateData.Version;
return true;
}
bool ProcessRepDynamicDataCommon(
FGeometryCollectionPhysicsProxy* PhysicsProxy,
const FGeometryCollectionRepDynamicData& RepDynamicData,
int32& VersionProcessed,
bool bReplicateMovement,
double SimTime,
double DeltaTime,
double& LastHardsnapTimeInMs
)
{
using namespace Chaos;
if (!PhysicsProxy || !PhysicsProxy->IsInitializedOnPhysicsThread() || PhysicsProxy->GetReplicationMode() != FGeometryCollectionPhysicsProxy::EReplicationMode::Client)
{
return false;
}
if (VersionProcessed == RepDynamicData.Version)
{
return false;
}
// extrapolation time is 0, because we do have the sim time when we receive the data
// we keep this to be mimic the original implementation that was not running on the physics thread
// todo(chaos) shoul dwe fix this ?
const float RepExtrapTime = 0.f;
// If we've extrapolated past a threshold, then stop tracking
// the last received rep data
if (RepExtrapTime > GeometryCollectionRepMaxExtrapolationTime)
{
return false;
}
const bool bHardSnap = ReplicationShouldHardSnap(RepDynamicData.Version, VersionProcessed, bReplicateMovement, LastHardsnapTimeInMs);
// Keep track of whether we did some "work" on this frame so we can turn off the async tick after
// multiple frames of not doing anything.
bool bProcessed = false;
if (bReplicateMovement)
{
FPBDRigidsSolver* Solver = PhysicsProxy->GetSolver<Chaos::FPBDRigidsSolver>();
for (const FGeometryCollectionRepDynamicData::FClusterData& RepCluster : RepDynamicData.ClusterData)
{
if (Chaos::FPBDRigidParticleHandle* Cluster = PhysicsProxy->GetParticle_Internal(RepCluster.TransformIndex))
{
if (RepCluster.bIsInternalCluster)
{
// internal cluster do not have an index so we rep data send one of the children's, let's find the parent
Cluster = Cluster->CastToClustered()->Parent();
}
if (Cluster && Cluster->Disabled() == false)
{
const FQuat RotationAsQuat = FQuat::MakeFromEuler(RepCluster.EulerRotation);
const FVector AngularVelocityInRadiansPerSecond = FMath::DegreesToRadians(RepCluster.AngularVelocityInDegreesPerSecond);
UpdateClusterPositionAndVelocitiesFromReplication(Solver, *Cluster, RepCluster.Position, RotationAsQuat, RepCluster.LinearVelocity, AngularVelocityInRadiansPerSecond, bHardSnap, RepExtrapTime, DeltaTime);
}
}
};
}
VersionProcessed = RepDynamicData.Version;
return true;
}
}
bool UGeometryCollectionComponent::ProcessRepData(const float DeltaTime, const float SimTime)
{
// Track the sim time that this rep data was received on.
if (!RepData.RepDataReceivedTime.IsSet())
{
RepData.RepDataReceivedTime = SimTime;
}
AActor* Owner = GetOwner();
const bool bReplicateMovement = Owner ? GetOwner()->IsReplicatingMovement() : true;
bool ReturnValue = ::ProcessRepDataCommon(DeltaTime, SimTime, RepData, PhysicsProxy, VersionProcessed, OneOffActivatedProcessed, LastHardsnapTimeInMs, bReplicateMovement);
#if CHAOS_DEBUG_DRAW
if (bGeometryCollectionDebugDrawRep)
{
// Track the sim time that this rep data was received on.
if (!RepData.RepDataReceivedTime.IsSet())
{
RepData.RepDataReceivedTime = SimTime;
}
// How far we must extrapolate from when we received the data
const float RepExtrapTime = FMath::Max(0.f, SimTime - *RepData.RepDataReceivedTime);
// Create a little little function for applying a lambda to each
// corresponding pair of replicated and local clusters.
const auto ForEachClusterPair = [this](const auto& Lambda)
{
for (const FGeometryCollectionClusterRep& RepCluster : RepData.Clusters)
{
if (Chaos::FPBDRigidParticleHandle* Cluster = PhysicsProxy->GetParticle_Internal(RepCluster.ClusterIdx))
{
if (RepCluster.ClusterState.IsInternalCluster())
{
// internal cluster do not have an index so we rep data send one of the children's
// let's find the parent
Cluster = Cluster->CastToClustered()->Parent();
}
if (Cluster && Cluster->Disabled() == false)
{
Lambda(RepCluster, *Cluster);
}
}
}
};
ForEachClusterPair([RepExtrapTime](const FGeometryCollectionClusterRep& RepCluster, Chaos::FPBDRigidParticleHandle& Cluster)
{
// Don't bother debug drawing if the delta is too small
if ((Cluster.GetX() - RepCluster.Position).SizeSquared() < .1f)
{
FVector Axis;
float Angle;
(RepCluster.Rotation.Inverse() * Cluster.GetR()).ToAxisAndAngle(Axis, Angle);
if (FMath::Abs(Angle) < .1f)
{
return;
}
}
Chaos::FDebugDrawQueue& DrawQueue = Chaos::FDebugDrawQueue::GetInstance();
DrawQueue.DrawDebugCoordinateSystem(Cluster.GetX(), FRotator(Cluster.GetR()), 100.f, false, -1, -1, 1.f);
DrawQueue.DrawDebugBox(Cluster.GetX() + Cluster.LocalBounds().Center(), Cluster.LocalBounds().Extents(), Cluster.GetR(), FColor::White, false, -1, -1, 1.f);
DrawQueue.DrawDebugBox(RepCluster.Position + Cluster.LocalBounds().Center(), Cluster.LocalBounds().Extents(), RepCluster.Rotation, FColor::Green, false, -1, -1, 1.f);
if (bGeometryCollectionRepUseClusterVelocityMatch)
{
const FVector RepVel = RepCluster.LinearVelocity;
const FVector RepAngVel = RepCluster.AngularVelocity;
const FVector RepExtrapPos = RepCluster.Position + (RepVel * RepExtrapTime);
const Chaos::FRotation3 RepExtrapAng = Chaos::FRotation3::IntegrateRotationWithAngularVelocity(RepCluster.Rotation, RepAngVel, RepExtrapTime);
DrawQueue.DrawDebugCoordinateSystem(RepExtrapPos, FRotator(RepExtrapAng), 100.f, false, -1, -1, 1.f);
DrawQueue.DrawDebugDirectionalArrow(Cluster.GetX(), RepExtrapPos, 10.f, FColor::White, false, -1, -1, 1.f);
DrawQueue.DrawDebugBox(RepExtrapPos + Cluster.LocalBounds().Center(), Cluster.LocalBounds().Extents(), RepExtrapAng, FColor::Orange, false, -1, -1, 1.f);
}
else
{
DrawQueue.DrawDebugCoordinateSystem(RepCluster.Position, FRotator(RepCluster.Rotation), 100.f, false, -1, -1, 1.f);
}
});
}
#endif
return ReturnValue;
}
void UGeometryCollectionComponent::ProcessRepDataOnPT()
{
using namespace Chaos;
if (Chaos::FPhysicsSolver* CurrSolver = GetSolver(*this))
{
AActor* Owner = GetOwner();
const bool bReplicateMovement = Owner ? GetOwner()->IsReplicatingMovement() : true;
// Capture - VersionProcessed - LastHardsnapTimeInMs - OneOffActivatedProcessed
// which are only used in that function and in the reset function and both are executed on the Physics Thread
CurrSolver->EnqueueCommandImmediate([PhysicsProxy = PhysicsProxy, RepDataCopy = RepData, &VersionProcessed = VersionProcessed, &LastHardsnapTimeInMs = LastHardsnapTimeInMs, &OneOffActivatedProcessed = OneOffActivatedProcessed, bReplicateMovement](Chaos::FReal DeltaTime, Chaos::FReal SimTime)
{
::ProcessRepDataCommon(DeltaTime, SimTime, RepDataCopy, PhysicsProxy, VersionProcessed, OneOffActivatedProcessed, LastHardsnapTimeInMs, bReplicateMovement);
});
}
}
void UGeometryCollectionComponent::ProcessRepStateDataOnPT()
{
ensure(GeometryCollectionUseReplicationV2);
if (Chaos::FPhysicsSolver* CurrSolver = GetSolver(*this))
{
// enqueue a callback to process the replicated state data on the physics thread
CurrSolver->EnqueueCommandImmediate(
[PhysicsProxy = PhysicsProxy, RepStateDataCopy = RepStateData, &VersionProcessed = VersionProcessed]
(Chaos::FReal /*DeltaTime*/, Chaos::FReal /*SimTime*/)
{
::ProcessRepStateDataCommon(PhysicsProxy, RepStateDataCopy, VersionProcessed);
});
}
}
void UGeometryCollectionComponent::ProcessRepDynamicDataOnPT()
{
ensure(GeometryCollectionUseReplicationV2);
if (Chaos::FPhysicsSolver* CurrSolver = GetSolver(*this))
{
AActor* Owner = GetOwner();
const bool bReplicateMovement = Owner ? GetOwner()->IsReplicatingMovement() : true;
// enqueue a callback to process the replicated dynamic data on the physics thread
CurrSolver->EnqueueCommandImmediate(
[PhysicsProxy = PhysicsProxy, RepDynamicDataCopy = RepDynamicData, &VersionProcessed = DynamicRepDataVersionProcessed, bReplicateMovement, &LastHardsnapTimeInMs = LastHardsnapTimeInMs]
(Chaos::FReal DeltaTime, Chaos::FReal SimTime)
{
::ProcessRepDynamicDataCommon(PhysicsProxy, RepDynamicDataCopy, VersionProcessed, bReplicateMovement, SimTime, DeltaTime, LastHardsnapTimeInMs);
});
}
}
void UGeometryCollectionComponent::SetDynamicState(const Chaos::EObjectStateType& NewDynamicState)
{
if (DynamicCollection)
{
TManagedArray<uint8>& DynamicState = DynamicCollection->DynamicState;
for (int i = 0; i < DynamicState.Num(); i++)
{
DynamicState[i] = static_cast<uint8>(NewDynamicState);
}
}
}
void UGeometryCollectionComponent::SetInitialTransforms(const TArray<FTransform>& InitialTransforms)
{
if (DynamicCollection)
{
int32 MaxIdx = FMath::Min(DynamicCollection->GetNumTransforms(), InitialTransforms.Num());
for (int32 Idx = 0; Idx < MaxIdx; ++Idx)
{
DynamicCollection->SetTransform(Idx, FTransform3f(InitialTransforms[Idx]));
}
}
}
void UGeometryCollectionComponent::SetInitialClusterBreaks(const TArray<int32>& ReleaseIndices)
{
if (DynamicCollection)
{
const int32 NumTransforms = DynamicCollection->GetNumTransforms();
for (int32 ReleaseIndex : ReleaseIndices)
{
if (ReleaseIndex < NumTransforms)
{
if (int32 ParentIndex = DynamicCollection->GetParent(ReleaseIndex); ParentIndex > INDEX_NONE)
{
DynamicCollection->SetHasParent(ReleaseIndex, false);
}
}
}
}
}
#if WITH_EDITOR
void UGeometryCollectionComponent::GetBoneColors(TArray<FColor>& OutColors) const
{
const FGeometryCollection* Collection = RestCollection->GetGeometryCollection().Get();
const int32 NumPoints = Collection->NumElements(FGeometryCollection::VerticesGroup);
const TManagedArray<int32>& BoneMap = Collection->BoneMap;
const TManagedArray<FLinearColor>& BoneColors = Collection->BoneColor;
OutColors.SetNumUninitialized(NumPoints);
if (bChaos_GC_InitConstantDataUseParallelFor)
{
ParallelFor(TEXT("GC:InitBoneColors"), NumPoints, bChaos_GC_InitConstantDataParallelForBatchSize,
[&](const int32 InPointIndex)
{
const int32 BoneIndex = BoneMap[InPointIndex];
OutColors[InPointIndex] = BoneColors[BoneIndex].ToFColor(true);
});
}
else
{
for (int32 PointIndex = 0; PointIndex < NumPoints; PointIndex++)
{
const int32 BoneIndex = BoneMap[PointIndex];
OutColors[PointIndex] = BoneColors[BoneIndex].ToFColor(true);
}
}
}
void UGeometryCollectionComponent::GetHiddenTransforms(TArray<bool>& OutHiddenTransforms) const
{
const FGeometryCollection* Collection = RestCollection->GetGeometryCollection().Get();
check(Collection);
OutHiddenTransforms.Reset();
if (Collection->HasAttribute("Hide", FGeometryCollection::TransformGroup))
{
const TManagedArray<bool>& Hide = Collection->GetAttribute<bool>("Hide", FGeometryCollection::TransformGroup);
const TManagedArray<int32>& TransformIndices = Collection->TransformIndex;
const int32 NumGeom = Collection->NumElements(FGeometryCollection::GeometryGroup);
const int32 NumTransforms = Collection->Transform.Num();
OutHiddenTransforms.SetNumZeroed(NumTransforms);
for (int32 GeometryIndex = 0; GeometryIndex < NumGeom; ++GeometryIndex)
{
const int32 TransformIndex = TransformIndices[GeometryIndex];
if (Hide[TransformIndex])
{
OutHiddenTransforms[TransformIndex] = 1;
}
}
}
}
#endif // WITH_EDITOR
void UGeometryCollectionComponent::GetRestTransforms(TArray<FMatrix44f>& OutRestTransforms) const
{
TArray<FMatrix> RestMatrices;
const int32 RestCollectionNum = RestCollection->GetGeometryCollection()->Transform.Num();
if (RestTransforms.Num() == RestCollectionNum)
{
GeometryCollectionAlgo::GlobalMatrices(TManagedArray<FTransform>(RestTransforms), RestCollection->GetGeometryCollection()->Parent, RestMatrices);
}
else
{
GeometryCollectionAlgo::GlobalMatrices(RestCollection->GetGeometryCollection()->Transform, RestCollection->GetGeometryCollection()->Parent, RestMatrices);
}
#if WITH_EDITOR
UpdateGlobalMatricesWithExplodedVectors(RestMatrices, *(RestCollection->GetGeometryCollection()));
#endif
CopyTransformsWithConversionWhenNeeded(OutRestTransforms, RestMatrices);
}
FGeometryCollectionDynamicData* UGeometryCollectionComponent::InitDynamicData(bool bInitialization)
{
SCOPE_CYCLE_COUNTER(STAT_GCInitDynamicData);
FGeometryCollectionDynamicData* DynamicData = GDynamicDataPool.Allocate();
DynamicData->SetTransforms(ComponentSpaceTransforms.RequestAllTransforms());
#if WITH_EDITOR
// zero out transfrom matrices if they are marked to be hidden
if (RestCollection && RestCollection->GetGeometryCollection())
{
static const FName HideAttribute{ "Hide" };
const FGeometryCollection& Collection = *RestCollection->GetGeometryCollection();
if (const TManagedArray<bool>* HideTransforms = Collection.FindAttribute<bool>(HideAttribute, FGeometryCollection::TransformGroup))
{
if (DynamicData->Transforms.Num() == HideTransforms->Num())
{
for (int32 TransformIndex = 0; TransformIndex < HideTransforms->Num(); TransformIndex++)
{
if ((*HideTransforms)[TransformIndex])
{
DynamicData->Transforms[TransformIndex] = FMatrix44f(EForceInit::ForceInitToZero);
}
}
}
}
}
#endif
return DynamicData;
}
bool UGeometryCollectionComponent::MoveComponentImpl(const FVector& Delta, const FQuat& NewRotation, bool bSweep, FHitResult* Hit, EMoveComponentFlags MoveFlags, ETeleportType Teleport)
{
const bool bResult = Super::MoveComponentImpl(Delta, NewRotation, bSweep, Hit, MoveFlags, Teleport);
#if WITH_EDITOR
// Make sure that custom renderer is updated _after_ any move has been applied to the full component hierachy.
if (UWorld* World = GetWorld())
{
if (!World->IsGameWorld())
{
RefreshCustomRenderer();
}
}
#endif
return bResult;
}
void UGeometryCollectionComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport)
{
// Don't allow the primitive component to update physics as that does processing to set a transform for
// UPrimitiveComponent::BodyInstance as it is unecessary for geometry collections. Out update is handled below.
Super::OnUpdateTransform(UpdateTransformFlags | EUpdateTransformFlags::SkipPhysicsUpdate, Teleport);
const bool bSkipPhysicsUpdate = ((UpdateTransformFlags & EUpdateTransformFlags::SkipPhysicsUpdate) == EUpdateTransformFlags::SkipPhysicsUpdate);
if (!bSkipPhysicsUpdate && PhysicsProxy)
{
PhysicsProxy->SetWorldTransform_External(GetComponentTransform());
}
if (SceneProxy && SceneProxy->IsNaniteMesh())
{
// Nanite scene proxy requires an render update because it may fail to detect rotation and scale changes in the transform
MarkRenderTransformDirty();
MarkRenderDynamicDataDirty();
}
if (bForceUpdateActiveTransforms)
{
RebaseDynamicCollectionTransformsOnNewWorldTransform();
}
}
bool UGeometryCollectionComponent::HasAnySockets() const
{
bool bHasAnySocket= false;
if (RestCollection)
{
if (RestCollection->GetGeometryCollection())
{
if (RestCollection->GetGeometryCollection()->BoneName.Num() > 0)
{
bHasAnySocket = true;
}
}
if (!bHasAnySocket)
{
for (const TObjectPtr<UStaticMesh>& ProxyMesh : RestCollection->RootProxyData.ProxyMeshes)
{
if (ProxyMesh && ProxyMesh->Sockets.Num() > 0)
{
bHasAnySocket = true;
break;
}
}
}
}
return bHasAnySocket;
}
bool UGeometryCollectionComponent::DoesSocketExist(FName InSocketName) const
{
bool bFoundSocket = false;
if (RestCollection)
{
if (RestCollection->GetGeometryCollection())
{
bFoundSocket |= RestCollection->GetGeometryCollection()->BoneName.Contains(InSocketName.ToString());
}
if (!bFoundSocket)
{
for (const TObjectPtr<UStaticMesh>& ProxyMesh : RestCollection->RootProxyData.ProxyMeshes)
{
if (ProxyMesh)
{
if (UStaticMeshSocket* MeshSocket = ProxyMesh->FindSocket(InSocketName))
{
bFoundSocket = true;
break;
}
}
}
}
}
return bFoundSocket;
}
FTransform UGeometryCollectionComponent::GetSocketTransform(FName InSocketName, ERelativeTransformSpace TransformSpace) const
{
if (RestCollection)
{
bool bFoundSocket = false;
FTransform SocketComponentSpaceTransform{ FTransform::Identity };
FTransform ParentComponentSpaceTransform{ FTransform::Identity };
if (RestCollection->GetGeometryCollection())
{
const TSharedPtr<FGeometryCollection, ESPMode::ThreadSafe> Collection = RestCollection->GetGeometryCollection();
if (Collection)
{
const int32 TransformIndex = Collection->BoneName.Find(InSocketName.ToString());
if (TransformIndex != INDEX_NONE)
{
const TArray<FTransform3f>& CompSpaceTransforms = ComponentSpaceTransforms.RequestAllTransforms();
if (CompSpaceTransforms.IsValidIndex(TransformIndex))
{
SocketComponentSpaceTransform = FTransform(CompSpaceTransforms[TransformIndex]);
const int32 ParentTransformIndex = Collection->Parent[TransformIndex];
if (CompSpaceTransforms.IsValidIndex(ParentTransformIndex))
{
ParentComponentSpaceTransform = FTransform(CompSpaceTransforms[ParentTransformIndex]);
}
bFoundSocket = true;
}
}
}
}
if (!bFoundSocket)
{
const FGeometryCollectionProxyMeshData& ProxyMeshData = RestCollection->RootProxyData;
for (int32 MeshIdx = 0; MeshIdx < ProxyMeshData.ProxyMeshes.Num(); ++MeshIdx)
{
const TObjectPtr<UStaticMesh>& ProxyMesh = ProxyMeshData.ProxyMeshes[MeshIdx];
if (ProxyMesh)
{
if (UStaticMeshSocket* MeshSocket = ProxyMesh->FindSocket(InSocketName))
{
SocketComponentSpaceTransform = FTransform(MeshSocket->RelativeRotation, MeshSocket->RelativeLocation, MeshSocket->RelativeScale);
SocketComponentSpaceTransform = SocketComponentSpaceTransform * FTransform(ProxyMeshData.GetMeshTransform(MeshIdx));
bFoundSocket = true;
break;
}
}
}
}
if (bFoundSocket)
{
// convert to the right space
switch (TransformSpace)
{
case RTS_World:
return SocketComponentSpaceTransform * GetComponentTransform();
case RTS_Actor:
{
if (const AActor* Actor = GetOwner())
{
const FTransform SocketWorldSpaceTransform = SocketComponentSpaceTransform * GetComponentTransform();
return SocketWorldSpaceTransform.GetRelativeTransform(Actor->GetTransform());
}
break;
}
case RTS_Component:
return SocketComponentSpaceTransform;
case RTS_ParentBoneSpace:
{
return SocketComponentSpaceTransform.GetRelativeTransform(ParentComponentSpaceTransform);
}
default:
check(false);
}
}
}
return Super::GetSocketTransform(InSocketName, TransformSpace);
}
void UGeometryCollectionComponent::QuerySupportedSockets(TArray<FComponentSocketDescription>& OutSockets) const
{
if (RestCollection)
{
if (RestCollection->GetGeometryCollection())
{
for (const FString& BoneName : RestCollection->GetGeometryCollection()->BoneName)
{
FComponentSocketDescription& Desc = OutSockets.AddZeroed_GetRef();
Desc.Name = *BoneName;
Desc.Type = EComponentSocketType::Type::Bone;
}
}
for (const TObjectPtr<UStaticMesh>& ProxyMesh : RestCollection->RootProxyData.ProxyMeshes)
{
if (ProxyMesh)
{
for (const TObjectPtr<class UStaticMeshSocket>& MeshSocket : ProxyMesh->Sockets)
{
if (MeshSocket)
{
FComponentSocketDescription& Desc = OutSockets.AddZeroed_GetRef();
Desc.Name = MeshSocket->SocketName;
Desc.Type = EComponentSocketType::Type::Socket;
}
}
}
}
}
}
void UGeometryCollectionComponent::UpdateAttachedChildrenTransform() const
{
// todo(chaos) : find a way to only update that of transform have changed
// right now this does not work properly because the dirty flags may not be updated at the right time
//if (PhysicsProxy && PhysicsProxy->IsGTCollectionDirty())
{
for (const TObjectPtr<USceneComponent>& AttachedChild: this->GetAttachChildren())
{
if (AttachedChild)
{
AttachedChild->UpdateComponentToWorld();
}
}
}
}
bool UGeometryCollectionComponent::HasVisibleGeometry() const
{
return CustomRenderer != nullptr || RestCollection->HasVisibleGeometry();
}
void UGeometryCollectionComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
{
//UE_LOG(UGCC_LOG, Log, TEXT("GeometryCollectionComponent[%p]::TickComponent()"), this);
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (BrokenAndDecayedStates.HasAnyDecaying())
{
InitializeRemovalDynamicAttributesIfNeeded();
const float AdjustedDeltaTime = FMath::Max(GeometryCollectionRemovalMultiplier, 0.0001f) * DeltaTime;
// todo(chaos) : move removal logic on the physics thread
IncrementSleepTimer(AdjustedDeltaTime);
IncrementBreakTimer(AdjustedDeltaTime);
}
// if there's no more decaying pieces, we can disable tick component
if (!BrokenAndDecayedStates.HasAnyDecaying())
{
PrimaryComponentTick.SetTickFunctionEnable(false);
}
}
void UGeometryCollectionComponent::CheckFullyDecayed()
{
if (!bAlreadyFullyDecayed)
{
if (BrokenAndDecayedStates.HasFullyDecayed())
{
bAlreadyFullyDecayed = true;
if (OnFullyDecayedEvent.IsBound())
{
SCOPE_CYCLE_COUNTER(STAT_GCFullyDecayedBroadcast);
OnFullyDecayedEvent.Broadcast();
}
}
}
}
void UGeometryCollectionComponent::AsyncPhysicsTickComponent(float DeltaTime, float SimTime)
{
QUICK_SCOPE_CYCLE_COUNTER(GeometryCollectionComponent_AsyncPhysicsTick);
if (!PhysicsProxy || !PhysicsProxy->IsInitializedOnPhysicsThread())
{
return;
}
#if WITH_EDITOR
// A bit of a hack because the super async physics tick will crash if this is true.
if (FUObjectThreadContext::Get().IsRoutingPostLoad)
{
return;
}
#endif
check(GetNetMode() != ENetMode::NM_Client);
Super::AsyncPhysicsTickComponent(DeltaTime, SimTime);
UpdateRepData();
}
void UGeometryCollectionComponent::OnRegister()
{
//UE_LOG(UGCC_LOG, Log, TEXT("GeometryCollectionComponent[%p]::OnRegister()[%p]"), this,RestCollection );
ResetDynamicCollection();
bool bIsReplicated = false;
const bool bHasClusterGroup = (ClusterGroupIndex != 0);
if (bEnableReplication)
{
if (ensureMsgf(!bHasClusterGroup, TEXT("Replication with cluster groups is not supported - disabling replication")))
{
bIsReplicated = true;
}
}
SetIsReplicated(bIsReplicated);
InitializeEmbeddedGeometry();
InitializeCustomRenderer();
Super::OnRegister();
}
void UGeometryCollectionComponent::OnUnregister()
{
Super::OnUnregister();
UnregisterCustomRenderer();
}
void UGeometryCollectionComponent::InitializeCustomRenderer()
{
// Create a custom renderer object if a type is set on the component or the GC asset.
UClass* Type = bOverrideCustomRenderer ? CustomRendererType : (RestCollection ? RestCollection->CustomRendererType : nullptr);
if (Type && Type->ImplementsInterface(UGeometryCollectionExternalRenderInterface::StaticClass()))
{
if (!CustomRenderer || CustomRenderer.GetObject()->GetClass() != Type)
{
CustomRenderer = NewObject<UObject>(this, Type, NAME_None, RF_Transient | RF_DuplicateTransient);
}
RegisterCustomRenderer();
#if WITH_EDITOR
// Make sure that custom renderer is updated at least once in editor.
if (GetWorld() && !GetWorld()->IsGameWorld())
{
RefreshCustomRenderer();
}
#endif
}
else
{
CustomRenderer = nullptr;
}
}
void UGeometryCollectionComponent::RegisterCustomRenderer()
{
bCustomRendererCanUseNativeFallback = false;
if (IsCustomRendererAvailable())
{
if (IGeometryCollectionExternalRenderInterface* RendererInterface = CustomRenderer.GetInterface())
{
bCustomRendererCanUseNativeFallback = RendererInterface->CanEverUseNativeFallback();
if (IsUsingCustomRenderer())
{
RendererInterface->OnRegisterGeometryCollection(*this);
}
}
}
}
void UGeometryCollectionComponent::UnregisterCustomRenderer()
{
if (IGeometryCollectionExternalRenderInterface* RendererInterface = CustomRenderer.GetInterface())
{
RendererInterface->OnUnregisterGeometryCollection();
}
}
void UGeometryCollectionComponent::ReregisterAllCustomRenderers()
{
for (TObjectIterator<UGeometryCollectionComponent> It(RF_ClassDefaultObject, true, EInternalObjectFlags::Garbage); It; ++It)
{
It->UnregisterCustomRenderer();
}
for (TObjectIterator<UGeometryCollectionComponent> It(RF_ClassDefaultObject, true, EInternalObjectFlags::Garbage); It; ++It)
{
It->RegisterCustomRenderer();
It->RefreshCustomRenderer();
}
}
void UGeometryCollectionComponent::InitializeRemovalDynamicAttributesIfNeeded()
{
if (bInitializedRemovalDynamicAttribute == false)
{
FGeometryCollectionDecayDynamicFacade DecayDynamicFacade(*DynamicCollection);
// we are not testing for bAllowRemovalOnSleep, so that we can enable it at runtime if necessary
if (RestCollection->bRemoveOnMaxSleep)
{
DecayDynamicFacade.AddAttributes();
FGeometryCollectionRemoveOnSleepDynamicFacade RemoveOnSleepDynamicFacade(*DynamicCollection);
RemoveOnSleepDynamicFacade.DefineSchema();
RemoveOnSleepDynamicFacade.SetAttributeValues(RestCollection->MaximumSleepTime, RestCollection->RemovalDuration);
}
// Remove on break feature related dynamic attribute arrays
// we are not testing for bAllowRemovalOnBreak, so that we can enable it at runtime if necessary
GeometryCollection::Facades::FCollectionRemoveOnBreakFacade RemoveOnBreakFacade(*RestCollection->GetGeometryCollection());
if (RemoveOnBreakFacade.IsValid())
{
DecayDynamicFacade.AddAttributes();
FGeometryCollectionRemoveOnBreakDynamicFacade RemoveOnBreakDynamicFacade(*DynamicCollection);
RemoveOnBreakDynamicFacade.DefineSchema();
RemoveOnBreakDynamicFacade.SetAttributeValues(RemoveOnBreakFacade);
}
bInitializedRemovalDynamicAttribute = true;
}
}
void UGeometryCollectionComponent::ResetDynamicCollection()
{
bool bCreateDynamicCollection = true;
#if WITH_EDITOR
bCreateDynamicCollection = CanRunSimulationInEditor();
#endif
if (bCreateDynamicCollection && RestCollection && RestCollection->GetGeometryCollection())
{
DynamicCollection = MakeShared<FGeometryDynamicCollection>(RestCollection->GetGeometryCollection());
if (bStoreVelocities || bNotifyTrailing)
{
DynamicCollection->AddVelocitiesAttributes();
}
DynamicCollection->MakeDirty();
MarkRenderStateDirty();
MarkRenderDynamicDataDirty();
}
else
{
DynamicCollection.Reset();
}
const int32 NumRestCollectionTransforms = RestCollection ? RestCollection->GetGeometryCollection()->Transform.Num() : 0;
if (RestTransforms.Num() != NumRestCollectionTransforms)
{
RestTransforms.Reset();
}
// if Reset transform have been overridden uses them to initialize the dynamic collection transforms
if (RestTransforms.Num() > 0)
{
SetInitialTransforms(RestTransforms);
}
ComponentSpaceTransforms.Reset(NumRestCollectionTransforms, GetRootIndex());
RootSpaceBounds.Init();
UpdateCachedBounds();
bInitializedRemovalDynamicAttribute = false;
BrokenAndDecayedStates.Reset(NumRestCollectionTransforms);
}
void UGeometryCollectionComponent::OnCreatePhysicsState()
{
// Skip the chain - don't care about body instance setup
UActorComponent::OnCreatePhysicsState();
if (!BodyInstance.bSimulatePhysics) IsObjectLoading = false; // just mark as loaded if we are simulating.
// Static mesh uses an init framework that goes through FBodyInstance. We
// do the same thing, but through the geometry collection proxy and lambdas
// defined below. FBodyInstance doesn't work for geometry collections
// because FBodyInstance manages a single particle, where we have many.
if (!PhysicsProxy && RestCollection)
{
#if WITH_EDITOR && WITH_EDITORONLY_DATA
EditorActor = nullptr;
if (RestCollection)
{
//hack: find a better place for this
UGeometryCollection* RestCollectionMutable = const_cast<UGeometryCollection*>(ToRawPtr(RestCollection));
RestCollectionMutable->CreateSimulationDataIfNeeded();
}
#endif
const bool bValidWorld = CanRunSimulationInEditor() || (GetWorld() && GetWorld()->IsPreviewWorld());
const bool bValidCollection = DynamicCollection && DynamicCollection->GetNumTransforms() > 0;
if (bValidWorld && bValidCollection)
{
FChaosUserData::Set<UPrimitiveComponent>(&PhysicsUserData, this);
// If the Component is set to Dynamic, we look to the RestCollection for initial dynamic state override per transform.
TManagedArray<uint8> & DynamicState = DynamicCollection->DynamicState;
// if this code is changed you may need to account for bStartAwake
EObjectStateTypeEnum LocalObjectType = (ObjectType != EObjectStateTypeEnum::Chaos_Object_Sleeping) ? ObjectType : EObjectStateTypeEnum::Chaos_Object_Dynamic;
if (LocalObjectType != EObjectStateTypeEnum::Chaos_Object_UserDefined)
{
if (RestCollection && (LocalObjectType == EObjectStateTypeEnum::Chaos_Object_Dynamic))
{
TManagedArray<int32>& InitialDynamicState = RestCollection->GetGeometryCollection()->InitialDynamicState;
for (int i = 0; i < DynamicState.Num(); i++)
{
DynamicState[i] = (InitialDynamicState[i] == static_cast<int32>(Chaos::EObjectStateType::Uninitialized)) ? static_cast<uint8>(LocalObjectType) : InitialDynamicState[i];
}
}
else
{
for (int i = 0; i < DynamicState.Num(); i++)
{
DynamicState[i] = static_cast<uint8>(LocalObjectType);
}
}
}
TManagedArray<bool>& Active = DynamicCollection->Active;
if (RestCollection->GetGeometryCollection()->HasAttribute(FGeometryCollection::SimulatableParticlesAttribute, FTransformCollection::TransformGroup))
{
TManagedArray<bool>* SimulatableParticles = RestCollection->GetGeometryCollection()->FindAttribute<bool>(FGeometryCollection::SimulatableParticlesAttribute, FTransformCollection::TransformGroup);
for (int i = 0; i < Active.Num(); i++)
{
Active[i] = (*SimulatableParticles)[i];
}
}
else
{
// If no simulation data is available then default to the simulation of just the rigid geometry.
for (int i = 0; i < Active.Num(); i++)
{
Active[i] = RestCollection->GetGeometryCollection()->IsRigid(i);
}
}
// there's a code path where Level is not serialized and InitializeSharedCollisionStructures is not being called,
// resulting in the attribute missing and causing a crash in CopyAttribute calls later in FGeometryCollectionPhysicsProxy::Initialize
// @todo(chaos) we should better handle computation of dependent attribute like level
// @todo(chaos) We should implement a facade for levels, (parent and child included ? )
if (!RestCollection->GetGeometryCollection()->HasAttribute("Level", FTransformCollection::TransformGroup))
{
TManagedArray<int32>& Levels = RestCollection->GetGeometryCollection()->AddAttribute<int32>("Level", FTransformCollection::TransformGroup);
for (int32 TransformIndex = 0; TransformIndex < Levels.Num(); ++TransformIndex)
{
FGeometryCollectionPhysicsProxy::CalculateAndSetLevel(TransformIndex, RestCollection->GetGeometryCollection()->Parent, Levels);
}
}
// let's copy anchored information if available
const Chaos::Facades::FCollectionAnchoringFacade RestCollectionAnchoringFacade(*RestCollection->GetGeometryCollection());
Chaos::Facades::FCollectionAnchoringFacade DynamicCollectionAnchoringFacade(*DynamicCollection);
DynamicCollectionAnchoringFacade.CopyAnchoredAttribute(RestCollectionAnchoringFacade);
// Set up initial filter data for our particles
// #BGTODO We need a dummy body setup for now to allow the body instance to generate filter information. Change body instance to operate independently.
DummyBodySetup = NewObject<UBodySetup>(this, UBodySetup::StaticClass());
BodyInstance.BodySetup = DummyBodySetup;
BodyInstance.OwnerComponent = this; // Required to make filter data include component/actor ID for ignored actors/components
BuildInitialFilterData();
if (BodyInstance.bSimulatePhysics)
{
RegisterAndInitializePhysicsProxy();
// We're skipping over the primitive component so we need to make sure this event gets fired.
OnComponentPhysicsStateChanged.Broadcast(this, EComponentPhysicsStateChange::Created);
}
}
}
}
static FORCEINLINE_DEBUGGABLE int32 ComputeParticleLevel(Chaos::FPBDRigidClusteredParticleHandle* Particle)
{
int32 Level = 0;
if (Particle)
{
Chaos::FPBDRigidClusteredParticleHandle* Current = Particle;
while (Current->Parent())
{
Current = Current->Parent();
++Level;
}
}
return Level;
};
void UGeometryCollectionComponent::RegisterAndInitializePhysicsProxy()
{
check(DynamicCollection != nullptr);
// CVar defined in BodyInstance but pertinent here as we will need to copy simplicials in the case that this is set.
// Original CVar is read-only so taking a static ptr here is fine as the value cannot be changed
static IConsoleVariable* AnalyticDisableCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("p.IgnoreAnalyticCollisionsOverride"));
static const bool bAnalyticsDisabled = (AnalyticDisableCVar && AnalyticDisableCVar->GetBool());
FSimulationParameters SimulationParameters;
{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
GetGeometryCollectionComponentDebugDebugName(*this, SimulationParameters.Name);
#endif
EClusterConnectionTypeEnum ClusterCollectionType = ClusterConnectionType_DEPRECATED;
float ConnectionGraphBoundsFilteringMargin = 0;
bool bUseSimplicialWhenAvailable = false;
if (RestCollection)
{
RestCollection->GetSharedSimulationParams(SimulationParameters.Shared);
SimulationParameters.RestCollectionShared = RestCollection->GetGeometryCollection();
PRAGMA_DISABLE_DEPRECATION_WARNINGS
// To be removed when RestCollection is removed post-deprecation. Here for back compat
SimulationParameters.RestCollection = SimulationParameters.RestCollectionShared.Get();
PRAGMA_ENABLE_DEPRECATION_WARNINGS
SimulationParameters.InitialRootIndex = RestCollection->GetRootIndex();
ClusterCollectionType = RestCollection->ClusterConnectionType;
ConnectionGraphBoundsFilteringMargin = RestCollection->ConnectionGraphBoundsFilteringMargin;
bUseSimplicialWhenAvailable =
FGeometryCollection::AreCollisionParticlesEnabled()
&& SimulationParameters.RestCollectionShared
&& SimulationParameters.RestCollectionShared->HasAttribute(FGeometryDynamicCollection::SimplicialsAttribute, FTransformCollection::TransformGroup)
&& SimulationParameters.Shared.SizeSpecificData[0].CollisionShapesData.Num()
&& (SimulationParameters.Shared.SizeSpecificData[0].CollisionShapesData[0].CollisionType == ECollisionTypeEnum::Chaos_Surface_Volumetric || bAnalyticsDisabled)
;
}
SimulationParameters.Simulating = BodyInstance.bSimulatePhysics;
SimulationParameters.EnableClustering = EnableClustering;
SimulationParameters.ClusterGroupIndex = EnableClustering ? ClusterGroupIndex : 0;
SimulationParameters.MaxClusterLevel = MaxClusterLevel;
SimulationParameters.MaxSimulatedLevel = MaxSimulatedLevel;
SimulationParameters.DamageModel = DamageModel;
SimulationParameters.DamageEvaluationModel = GetDamageEvaluationModel(DamageModel);
SimulationParameters.bUseSizeSpecificDamageThresholds = bUseSizeSpecificDamageThreshold;
SimulationParameters.bUseMaterialDamageModifiers = bUseMaterialDamageModifiers;
SimulationParameters.DamageThreshold = DamageThreshold;
SimulationParameters.bUsePerClusterOnlyDamageThreshold = RestCollection? RestCollection->PerClusterOnlyDamageThreshold: false;
SimulationParameters.ClusterConnectionMethod = (Chaos::FClusterCreationParameters::EConnectionMethod)ClusterCollectionType;
SimulationParameters.ConnectionGraphBoundsFilteringMargin = ConnectionGraphBoundsFilteringMargin;
SimulationParameters.bForceUpdateActiveTransforms = bForceUpdateActiveTransforms;
SimulationParameters.CollisionGroup = CollisionGroup;
SimulationParameters.CollisionSampleFraction = CollisionSampleFraction;
SimulationParameters.InitialVelocityType = InitialVelocityType;
SimulationParameters.InitialLinearVelocity = FVector3f(InitialLinearVelocity);
SimulationParameters.InitialAngularVelocity = FVector3f(InitialAngularVelocity);
SimulationParameters.ObjectType = ObjectType;
SimulationParameters.StartAwake = BodyInstance.bStartAwake;
SimulationParameters.bGenerateBreakingData = bNotifyBreaks;
SimulationParameters.bGenerateCollisionData = bNotifyCollisions;
SimulationParameters.bGenerateTrailingData = bNotifyTrailing;
SimulationParameters.bGenerateCrumblingData = bNotifyCrumblings;
SimulationParameters.bGenerateCrumblingChildrenData = bCrumblingEventIncludesChildren;
SimulationParameters.bGenerateGlobalBreakingData = bNotifyGlobalBreaks;
SimulationParameters.bGenerateGlobalCollisionData = bNotifyGlobalCollisions;
SimulationParameters.bGenerateGlobalCrumblingData = bNotifyGlobalCrumblings;
SimulationParameters.bGenerateGlobalCrumblingChildrenData = bGlobalCrumblingEventIncludesChildren;
SimulationParameters.EnableGravity = BodyInstance.bEnableGravity;
SimulationParameters.GravityGroupIndex = GravityGroupIndex;
SimulationParameters.OneWayInteractionLevel = OneWayInteractionLevel;
SimulationParameters.UseInertiaConditioning = BodyInstance.IsInertiaConditioningEnabled();
SimulationParameters.UseCCD = BodyInstance.bUseCCD;
SimulationParameters.UseMACD = BodyInstance.GetUseMACD();
SimulationParameters.PositionSolverIterations = BodyInstance.GetPositionSolverIterationCount();
SimulationParameters.VelocitySolverIterations = BodyInstance.GetVelocitySolverIterationCount();
SimulationParameters.ProjectionSolverIterations = BodyInstance.GetProjectionSolverIterationCount();
SimulationParameters.LinearDamping = BodyInstance.LinearDamping;
SimulationParameters.AngularDamping = BodyInstance.AngularDamping;
SimulationParameters.InitialOverlapDepenetrationVelocity = BodyInstance.GetMaxDepenetrationVelocity();
SimulationParameters.SleepThresholdMultiplier = BodyInstance.GetSleepThresholdMultiplier();
SimulationParameters.bUseDamagePropagation = DamagePropagationData.bEnabled;
SimulationParameters.BreakDamagePropagationFactor = DamagePropagationData.BreakDamagePropagationFactor;
SimulationParameters.ShockDamagePropagationFactor = DamagePropagationData.ShockDamagePropagationFactor;
SimulationParameters.WorldTransform = GetComponentToWorld();
SimulationParameters.UserData = static_cast<void*>(&PhysicsUserData);
SimulationParameters.bEnableStrainOnCollision = bEnableDamageFromCollision;
SimulationParameters.bUseStaticMeshCollisionForTraces = bUseStaticMeshCollisionForTraces;
SimulationParameters.bOptimizeConvexes = RestCollection ? RestCollection->bOptimizeConvexes : true;
SimulationParameters.bUseSimplicialsWhenAvailable = bUseSimplicialWhenAvailable;
#if SIMULATIONPARAMETERS_CACHE_PARAMETERS
PRAGMA_DISABLE_DEPRECATION_WARNINGS
SimulationParameters.bClearCache = true;
SimulationParameters.CacheType = CacheParameters.CacheMode;
SimulationParameters.ReverseCacheBeginTime = CacheParameters.ReverseCacheBeginTime;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
#endif
UPhysicalMaterial* EnginePhysicalMaterial = GetPhysicalMaterial();
if (ensure(EnginePhysicalMaterial))
{
SimulationParameters.PhysicalMaterialHandle = EnginePhysicalMaterial->GetPhysicsMaterial();
}
// mass properties are cooked in the GC asset, but component can override physics material with a different density
// if the user wants the density to be set from the physics material we need adjust by scaling mass by the ratio override/cooked defined properties
SimulationParameters.MaterialOverrideMassScaleMultiplier = ComputeMassScaleRelativeToAsset();
GetInitializationCommands(SimulationParameters.InitializationCommands);
}
FGuid CollectorGuid = FGuid::NewGuid();
#if WITH_EDITORONLY_DATA
CollectorGuid = RunTimeDataCollectionGuid;
if (bEnableRunTimeDataCollection && RestCollection)
{
FRuntimeDataCollector::GetInstance().AddCollector(CollectorGuid, RestCollection->NumElements(FGeometryCollection::TransformGroup));
}
else
{
FRuntimeDataCollector::GetInstance().RemoveCollector(CollectorGuid);
}
#endif
PhysicsProxy = new FGeometryCollectionPhysicsProxy(this, DynamicCollection.ToSharedRef(), SimulationParameters, InitialSimFilter, InitialQueryFilter, CollectorGuid);
PhysicsProxy->SetPostPhysicsSyncCallback(
[WeakThis = MakeWeakObjectPtr(this)]()
{
if (UGeometryCollectionComponent* This = WeakThis.Get())
{
This->OnPostPhysicsSync();
}
}
);
PhysicsProxy->SetPostParticlesCreatedCallback(
[WeakThis = MakeWeakObjectPtr(this)]()
{
if (UGeometryCollectionComponent* This = WeakThis.Get())
{
This->OnPostCreateParticles();
}
}
);
if (GetIsReplicated())
{
// using net mode and not local role because at this time in the initialization client and server both have an authority local role
const ENetMode NetMode = GetNetMode();
if (NetMode != NM_Standalone)
{
const FGeometryCollectionPhysicsProxy::EReplicationMode ReplicationMode =
(NetMode == ENetMode::NM_Client)
? FGeometryCollectionPhysicsProxy::EReplicationMode::Client
: FGeometryCollectionPhysicsProxy::EReplicationMode::Server;
PhysicsProxy->SetReplicationMode(ReplicationMode);
}
}
auto CreateTraceCollisionFromStaticMeshGeometry = [this](const FTransform& InToLocal, TArray<Chaos::FImplicitObjectPtr>& OutGeoms, Chaos::FShapesArray& OutShapes) {
const FGeometryCollectionProxyMeshData& ProxyMeshData = RestCollection->RootProxyData;
for (int32 MeshIdx = 0; MeshIdx < ProxyMeshData.ProxyMeshes.Num(); ++MeshIdx)
{
const TObjectPtr<UStaticMesh>& ProxyMesh = RestCollection->RootProxyData.ProxyMeshes[MeshIdx];
if (const UStaticMesh* StaticMesh = ProxyMesh.Get())
{
if (UBodySetup* BodySetup = ProxyMesh->GetBodySetup())
{
FBodyCollisionData BodyCollisionData;
BodyInstance.BuildBodyFilterData(BodyCollisionData.CollisionFilterData);
FBodyInstance::BuildBodyCollisionFlags(BodyCollisionData.CollisionFlags, BodyInstance.GetCollisionEnabled(), BodySetup->GetCollisionTraceFlag() == CTF_UseComplexAsSimple);
const FTransform MeshLocalTransform(ProxyMeshData.GetMeshTransform(MeshIdx));
FGeometryAddParams AddParams;
AddParams.bDoubleSided = BodySetup->bDoubleSidedGeometry;
AddParams.CollisionData = BodyCollisionData;
AddParams.CollisionTraceType = BodySetup->GetCollisionTraceFlag();
AddParams.WorldTransform = PhysicsProxy->GetWorldTransform_External();
AddParams.Scale = AddParams.WorldTransform.GetScale3D();
AddParams.SimpleMaterial = GetPhysicalMaterial();
AddParams.LocalTransform = InToLocal * MeshLocalTransform;
AddParams.Geometry = &BodySetup->AggGeom;
ChaosInterface::CreateGeometry(AddParams, OutGeoms, OutShapes);
}
}
}
};
// todo(chaos): Remove this and move to a cook time approach of the SM data based on the GC property
if (RestCollection != nullptr)
{
for (const TObjectPtr<UStaticMesh>& ProxyMesh : RestCollection->RootProxyData.ProxyMeshes)
{
if (const UStaticMesh* StaticMesh = ProxyMesh.Get())
{
if (UBodySetup* BodySetup = ProxyMesh->GetBodySetup())
{
// We have at least one valid mesh, so set the callback in case we are asked to use SM collision for traces
PhysicsProxy->SetCreateTraceCollisionGeometryCallback(CreateTraceCollisionFromStaticMeshGeometry);
break;
}
}
}
}
FPhysScene_Chaos* PhysicsScene = GetInnerChaosScene();
if (ensure(PhysicsScene))
{
PhysicsScene->AddObject(this, PhysicsProxy);
// If we're replicating we need some extra setup - check netmode as we don't need this for standalone runtime where we aren't going to network the component
// IMPORTANT this need to happen after the object is registered so this will guarantee that the particles are properly created by the time the callback below gets called
if (GetIsReplicated() && PhysicsProxy->GetReplicationMode() == FGeometryCollectionPhysicsProxy::EReplicationMode::Client)
{
// Client side : geometry collection children of parents below the rep level need to be infinitely strong so that client cannot break it
if (Chaos::FPhysicsSolver* CurrSolver = GetSolver(*this))
{
CurrSolver->EnqueueCommandImmediate([Proxy = PhysicsProxy, AbandonAfterLevel = ReplicationAbandonAfterLevel, EnableAbandonAfterLevel = bEnableAbandonAfterLevel]()
{
// As we're not in control we make it so our simulated proxy cannot break clusters
// We have to set the strain to a high value but be below the max for the data type
// so releasing on authority demand works
for (Chaos::FPBDRigidClusteredParticleHandle* ParticleHandle : Proxy->GetUnorderedParticles_Internal())
{
if (ParticleHandle)
{
const int32 Level = EnableAbandonAfterLevel ? ComputeParticleLevel(ParticleHandle) : -1;
if (Level <= AbandonAfterLevel) //we only replicate up until level X, but it means we should replicate the breaking event of level X+1 (but not X+1's positions)
{
ParticleHandle->SetUnbreakable(true);
}
}
}
});
}
}
LoadCollisionProfiles();
// We need to add the geometry collection into the external acceleration structure so that it's immediately available for queries instead of waiting for the sync from the physics thread (which could take awhile).
// Just adding the root particle should be sufficient since that'll be the only particle we'd expect any collisions with right after initialization.
if (Chaos::FPhysicsObjectHandle RootObject = GetPhysicsObjectByName(NAME_None))
{
TArrayView<Chaos::FPhysicsObjectHandle> Handles{ &RootObject, 1 };
FLockedWritePhysicsObjectExternalInterface Interface = FPhysicsObjectExternalInterface::LockWrite(Handles);
Interface->AddToSpatialAcceleration(Handles, PhysicsScene->GetSpacialAcceleration());
}
}
RegisterForEvents();
}
void UGeometryCollectionComponent::UpdateBrokenAndDecayedStates()
{
if (PhysicsProxy && DynamicCollection && DynamicCollection->Active.Num() > 0)
{
const int32 RootIndex = GetRootIndex();
if (RootIndex != INDEX_NONE)
{
const int32 NumTransforms = DynamicCollection->GetNumTransforms();
const bool bIsRootBroken = !DynamicCollection->Active[RootIndex];
BrokenAndDecayedStates.SetRootIsBroken(bIsRootBroken);
// we mark it both broken and decayed
BrokenAndDecayedStates.SetIsBroken(RootIndex);
BrokenAndDecayedStates.SetHasDecayed(RootIndex);
if (BrokenAndDecayedStates.GetIsRootBroken())
{
const FGeometryCollectionDynamicStateFacade DynamicStateFacade(*DynamicCollection);
if (DynamicStateFacade.IsValid())
{
for (int32 TransformIdx = 0; TransformIdx < NumTransforms; ++TransformIdx)
{
if (BrokenAndDecayedStates.GetIsBroken(TransformIdx) || BrokenAndDecayedStates.GetHasDecayed(TransformIdx))
{
// already decayed nothing to do anymore
continue;
}
const Chaos::FPBDRigidParticle* Particle = PhysicsProxy->GetParticleByIndex_External(TransformIdx);
if (!Particle)
{
// when no particle we mark it as a decayed one
BrokenAndDecayedStates.SetHasDecayed(TransformIdx);
continue;
}
if (!BrokenAndDecayedStates.GetIsBroken(TransformIdx))
{
const bool bInternalClusterParentBrokenOff = DynamicStateFacade.HasDynamicInternalClusterParent(TransformIdx) && !DynamicStateFacade.HasClusterUnionParent(TransformIdx);
const bool bSelfBrokenOff = DynamicStateFacade.HasBrokenOff(TransformIdx) && !DynamicStateFacade.HasInternalClusterParent(TransformIdx);
if (bSelfBrokenOff || bInternalClusterParentBrokenOff)
{
BrokenAndDecayedStates.SetIsBroken(TransformIdx);
}
}
if (BrokenAndDecayedStates.GetIsBroken(TransformIdx))
{
if (Particle->Disabled())
{
// Todo(chaos) in the future we shoud probably store a bitArray to know if the bone is a simulated leaf
bool bHasAnyChildWithValidParticle = false;
DynamicCollection->IterateThroughChildren(TransformIdx,
[&bHasAnyChildWithValidParticle, this] (int32 ChildTransformIdx) -> bool
{
if (PhysicsProxy->GetParticleByIndex_External(ChildTransformIdx))
{
bHasAnyChildWithValidParticle = true;
return false; // do no continue iterating
}
return true; // continue iterating
});
if (!bHasAnyChildWithValidParticle)
{
BrokenAndDecayedStates.SetHasDecayed(TransformIdx);
}
}
}
}
}
}
}
}
}
bool UGeometryCollectionComponent::FBrokenAndDecayedStates::GetIsBroken(int32 TransformIndex) const
{
return bIsRootBroken ? IsBroken[TransformIndex] : false;
}
bool UGeometryCollectionComponent::FBrokenAndDecayedStates::GetHasDecayed(int32 TransformIndex) const
{
return bIsRootBroken ? HasDecayed[TransformIndex] : false;
}
double UGeometryCollectionComponent::FBrokenAndDecayedStates::GetRootBrokenEventTimeInMs() const
{
return RootBrokenEventTimeInMs;
}
double UGeometryCollectionComponent::FBrokenAndDecayedStates::GetRootBrokenElapsedTimeInMs() const
{
if (bIsRootBroken)
{
const double CurrentTimeInMs = FPlatformTime::ToMilliseconds64(FPlatformTime::Cycles64());
return (CurrentTimeInMs - RootBrokenEventTimeInMs);
}
return 0;
}
void UGeometryCollectionComponent::FBrokenAndDecayedStates::Reset(int32 InNumTransforms)
{
NumTransforms = InNumTransforms;
bIsRootBroken = false;
NumDecaying = 0;
IsBroken.Empty();
HasDecayed.Empty();
RootBrokenEventTimeInMs = 0;
}
void UGeometryCollectionComponent::FBrokenAndDecayedStates::SetRootIsBroken(bool bIsBroken)
{
if (bIsBroken != bIsRootBroken)
{
bIsRootBroken = bIsBroken;
NumDecaying = 0;
if (bIsBroken)
{
IsBroken.Init(false, NumTransforms);
HasDecayed.Init(false, NumTransforms);
RootBrokenEventTimeInMs = FPlatformTime::ToMilliseconds64(FPlatformTime::Cycles64());
}
else
{
IsBroken.Empty();
HasDecayed.Empty();
RootBrokenEventTimeInMs = 0;
}
}
}
void UGeometryCollectionComponent::FBrokenAndDecayedStates::SetIsBroken(int32 TransformIndex)
{
if (bIsRootBroken && !IsBroken[TransformIndex])
{
IsBroken[TransformIndex] = true;
NumDecaying++;
}
}
void UGeometryCollectionComponent::FBrokenAndDecayedStates::SetHasDecayed(int32 TransformIndex)
{
if (bIsRootBroken && !HasDecayed[TransformIndex])
{
HasDecayed[TransformIndex] = true;
if (IsBroken[TransformIndex])
{
NumDecaying--;
}
}
}
void UGeometryCollectionComponent::FBrokenAndDecayedStates::SetHasDecayedRecursive(int32 TransformIndex, const TArray<TSet<int32>>& Children)
{
if (bIsRootBroken && ensure(Children.Num() == HasDecayed.Num()))
{
if (!HasDecayed[TransformIndex])
{
HasDecayed[TransformIndex] = true;
if (IsBroken[TransformIndex])
{
NumDecaying--;
}
for (const int32 ChildIndex : Children[TransformIndex])
{
SetHasDecayedRecursive(ChildIndex, Children);
}
}
}
}
bool UGeometryCollectionComponent::FBrokenAndDecayedStates::HasAnyDecaying() const
{
return (NumDecaying > 0);
}
bool UGeometryCollectionComponent::FBrokenAndDecayedStates::HasFullyDecayed() const
{
if (HasAnyDecaying())
{
return false;
}
return (HasDecayed.CountSetBits() == HasDecayed.Num());
}
void UGeometryCollectionComponent::OnTransformsDirty()
{
ComponentSpaceTransforms.MarkDirty();
ComponentSpaceBounds.Init();
}
void UGeometryCollectionComponent::OnPostCreateParticles()
{
LoadCollisionProfiles();
}
void UGeometryCollectionComponent::OnPostPhysicsSync()
{
SCOPE_CYCLE_COUNTER(STAT_GCPostPhysicsSync);
// dirty the transform if the collection is
bool bHasRootMoved = false;
if (DynamicCollection && DynamicCollection->IsDirty())
{
// Can't be a const reference - we need to make a copy or else the next RequestRootTransform will change the value under our nose.
const FTransform3f PreviousRootTransform = OnRootMovedEvent.IsBound() ? ComponentSpaceTransforms.RequestRootTransform() : FTransform3f::Identity;
OnTransformsDirty();
if (OnRootMovedEvent.IsBound() || OnRootMovedNativeEvent.IsBound())
{
const FTransform3f& NewRootTransform = ComponentSpaceTransforms.RequestRootTransform();
bHasRootMoved = !PreviousRootTransform.EqualsNoScale(NewRootTransform);
}
}
UpdateAttachedChildrenTransform();
if (GetIsReplicated() && GetNetMode() != ENetMode::NM_Client)
{
// The GameThreadCollection dirty flag doesn't correspond to the "dirtiness" that should trigger replication.
// So as long as the physics sync happens, check for potential replication updates.
RequestUpdateRepData();
}
// Once the GC is broken, removal feature will need the tick to properly update the timers
// even if the physics does not get any updates
UpdateBrokenAndDecayedStates();
if (BrokenAndDecayedStates.GetIsRootBroken())
{
if (GeometryCollectionEmitRootBreakingEvent)
{
if (OnRootBreakEvent.IsBound())
{
FChaosBreakEvent Event;
Event.Index = GetRootIndex();
Event.Component = this;
OnRootBreakEvent.Broadcast(Event);
OnRootBreakEvent.Clear(); // one shot
}
}
if (BrokenAndDecayedStates.HasAnyDecaying())
{
if (!PrimaryComponentTick.IsTickFunctionEnabled())
{
PrimaryComponentTick.SetTickFunctionEnable(true);
}
}
else
{
InitializeRemovalDynamicAttributesIfNeeded();
}
UpdateRemovalIfNeeded();
CheckFullyDecayed();
}
else if (bUpdateComponentTransformToRootBone)
{
MoveComponentToRootTransform();
}
const bool bDynamicDataIsDirty = (DynamicCollection && DynamicCollection->IsDirty() && HasVisibleGeometry());
UpdateRenderSystemsIfNeeded(bDynamicDataIsDirty);
UpdateNavigationDataIfNeeded(bDynamicDataIsDirty);
// at this is called at the end as thos events may change some internal values of the component
// and interfere with transforms required for naviagtion or rendering
if (bHasRootMoved)
{
if (OnRootMovedEvent.IsBound())
{
OnRootMovedEvent.Broadcast();
}
if (OnRootMovedNativeEvent.IsBound())
{
OnRootMovedNativeEvent.Broadcast(this);
}
}
}
void UGeometryCollectionComponent::MoveComponentToRootTransform()
{
if (RestCollection && PhysicsProxy)
{
const TSharedPtr<FGeometryCollection, ESPMode::ThreadSafe> AssetCollection = RestCollection->GetGeometryCollection();
if (AssetCollection && DynamicCollection && DynamicCollection->IsDirty())
{
static FName MassToLocalAttributeName = "MassToLocal";
const TManagedArray<FTransform>& MassToLocalAttribute = RestCollection->GetGeometryCollection()->GetAttribute<FTransform>(MassToLocalAttributeName, FGeometryCollection::TransformGroup);
FGeometryCollectionDynamicStateFacade DynamicStateFacade(*DynamicCollection);
const int32 RootIndex = GetRootIndex();
const bool bIsRootActive = DynamicStateFacade.IsDynamicOrSleeping(RootIndex);
const bool bHasDynamicOPrClusterUnionParent = DynamicStateFacade.HasDynamicInternalClusterParent(RootIndex) || !DynamicStateFacade.HasClusterUnionParent(RootIndex);
if (bIsRootActive || bHasDynamicOPrClusterUnionParent)
{
const FTransform3f& OriginalComponentSpaceRootTransformOffset = AssetCollection->Transform[RootIndex];
DynamicCollection->SetTransform(RootIndex, OriginalComponentSpaceRootTransformOffset);
const Chaos::FPBDRigidParticle* RootParticle = PhysicsProxy->GetParticleByIndex_External(RootIndex);
const FTransform ParticleWorldPosition(RootParticle->R(), RootParticle->X());
const FTransform MassToLocal = MassToLocalAttribute[RootIndex];
const FTransform NewRootWorldPosition = MassToLocal.Inverse() * ParticleWorldPosition;
const FTransform CurrentComponentTransform = GetComponentTransform();
if (!NewRootWorldPosition.EqualsNoScale(CurrentComponentTransform))
{
const FVector NewPosition = NewRootWorldPosition.GetLocation();
const FVector MoveBy = NewPosition - CurrentComponentTransform.GetLocation();
const FRotator NewRotation = NewRootWorldPosition.Rotator();
MoveComponent(MoveBy, NewRotation, /* bSweep */ false,/* Hit */ NULL, MOVECOMP_SkipPhysicsMove);
}
OnTransformsDirty();
}
}
}
}
void UGeometryCollectionComponent::UpdateRenderSystemsIfNeeded(bool bDynamicCollectionDirty)
{
#if WITH_EDITOR
if (IsRegistered() && SceneProxy && RestCollection)
{
const bool bWantNanite = RestCollection->EnableNanite && GGeometryCollectionNanite != 0;
const bool bHaveNanite = SceneProxy->IsNaniteMesh();
bool bRecreateProxy = bWantNanite != bHaveNanite;
if (bRecreateProxy)
{
// Wait until resources are released
FlushRenderingCommands();
FComponentReregisterContext ReregisterContext(this);
UpdateAllPrimitiveSceneInfosForSingleComponent(this);
}
}
#endif
if (bDynamicCollectionDirty)
{
// #todo review: When we've made changes to ISMC, we need to move this function call to SetRenderDynamicData_Concurrent
RefreshEmbeddedGeometry();
if (bUpdateCustomRendererOnPostPhysicsSync)
{
RefreshCustomRenderer();
}
if (SceneProxy != nullptr)
{
if (SceneProxy->IsNaniteMesh())
{
FNaniteGeometryCollectionSceneProxy* NaniteProxy = static_cast<FNaniteGeometryCollectionSceneProxy*>(SceneProxy);
NaniteProxy->FlushGPUSceneUpdate_GameThread();
}
MarkRenderTransformDirty();
MarkRenderDynamicDataDirty();
}
}
}
void UGeometryCollectionComponent::UpdateNavigationDataIfNeeded(bool bDynamicCollectionDirty)
{
if (bUpdateNavigationInTick && bDynamicCollectionDirty)
{
const UWorld* MyWorld = GetWorld();// If not doing velocity match, don't bother processing the same version twice.
if (MyWorld && MyWorld->IsGameWorld())
{
//cycle every 0xff frames
//@todo - Need way of seeing if the collection is actually changing
if (bNavigationRelevant && bRegistered && (((GFrameCounter + NavmeshInvalidationTimeSliceIndex) & 0xff) == 0))
{
UpdateNavigationData();
}
}
}
}
void UGeometryCollectionComponent::UpdateRemovalIfNeeded()
{
static FName MassToLocalAttributeName = "MassToLocal";
// if removal is enabled, update the dynamic collection transform based on the decay
// todo: we could optimize this using a list of transform to update from when we update the decay values
const bool bAllowAnyRemoval = (bAllowRemovalOnBreak || bAllowRemovalOnSleep);
if (DynamicCollection && bAllowAnyRemoval && PhysicsProxy)
{
FGeometryCollectionDecayDynamicFacade DecayFacade(*DynamicCollection);
if (DecayFacade.IsValid())
{
const FTransform ZeroScaleTransform(FQuat::Identity, FVector::Zero(), FVector(0, 0, 0));
const FTransform InverseComponentTransform = (RestCollection->bScaleOnRemoval) ? GetComponentTransform().Inverse() : FTransform::Identity;
const TManagedArray<FTransform>* MassToLocal = nullptr;
const TArray<FTransform3f>* CompSpaceTransform = nullptr;
if (RestCollection->bScaleOnRemoval)
{
MassToLocal = RestCollection->GetGeometryCollection()->FindAttribute<FTransform>(MassToLocalAttributeName, FGeometryCollection::TransformGroup);
CompSpaceTransform = &ComponentSpaceTransforms.RequestAllTransforms();
}
const int32 RootIndex = RestCollection->GetRootIndex();
bool bTransformsChanged = false;
const int32 NumTransforms = DecayFacade.GetDecayAttributeSize();
for (int32 TransformIndex = 0; TransformIndex < NumTransforms; ++TransformIndex)
{
// only update values if the decay has changed
float Decay = DecayFacade.GetDecay(TransformIndex);
// reconcile changes to the decay state with the decay value
if (Decay < 1.f && TransformIndex != RootIndex)
{
if (PhysicsProxy->GetParticleByIndex_External(TransformIndex))
{
if (BrokenAndDecayedStates.GetHasDecayed(TransformIndex))
{
// this logic should only apply to end nodes that do not have simulated children
// otherwise this may cause clusters with children to disappear for several frames
// ( see Cl 30290839 and UpdateBrokenAndDecayedStates() method logic )
bool bHasAnyChildWithValidParticle = false;
if (bGeometryCollectionRemoveOnBreakDecayFix)
{
DynamicCollection->IterateThroughChildren(TransformIndex,
[&bHasAnyChildWithValidParticle, this](int32 ChildTransformIdx) -> bool
{
if (PhysicsProxy->GetParticleByIndex_External(ChildTransformIdx))
{
bHasAnyChildWithValidParticle = true;
return false; // do no continue iterating
}
return true; // continue iterating
});
}
if (!bHasAnyChildWithValidParticle)
{
Decay = 1.0f;
DecayFacade.SetDecay(TransformIndex, Decay);
}
}
}
}
if (Decay > 0.f && Decay <= 1.f)
{
const float Scale = 1.0 - Decay;
if (Scale < UE_SMALL_NUMBER)
{
FTransform3f Transform = DynamicCollection->GetTransform(TransformIndex);
Transform.SetScale3D(FVector3f::ZeroVector);
DynamicCollection->SetTransform(TransformIndex, Transform);
bTransformsChanged = true;
}
// do not try to get this condition out of the loop as this may cause some optimizer related issues
else if (RestCollection->bScaleOnRemoval && MassToLocal && CompSpaceTransform)
{
float ShrinkRadius = 0.0f;
UE::Math::TSphere<double> AccumulatedSphere;
// todo(chaos) : find a faster way to do that ( precompute the data ? )
if (CalculateInnerSphere(TransformIndex, AccumulatedSphere))
{
ShrinkRadius = -AccumulatedSphere.W;
}
const FTransform3f& CurrentTransform = DynamicCollection->GetTransform(TransformIndex);
const float CurrentScale = CurrentTransform.GetScale3D().X; // always uniform scale
const float ScaleToApply = (CurrentScale > SMALL_NUMBER) ? (Scale / CurrentScale): Scale;
// InverseComponentTransform calculation must in double precision for LWC
const FQuat LocalRotation = (InverseComponentTransform * FTransform((*CompSpaceTransform)[TransformIndex].Inverse())).GetRotation();
const FVector LocalDown = LocalRotation.RotateVector(FVector(0.f, 0.f, ShrinkRadius));
const FVector CenterOfMass = (*MassToLocal)[TransformIndex].GetTranslation();
const FVector3f ScaleCenter = FVector3f(LocalDown + CenterOfMass);
const FTransform3f ScaleTransform(FQuat4f::Identity, ScaleCenter * FVector3f::FReal(1.f - ScaleToApply), FVector3f(ScaleToApply));
const FTransform3f FinalTransform = ScaleTransform * CurrentTransform;
DynamicCollection->SetTransform(TransformIndex, FinalTransform);
bTransformsChanged = true;
}
}
}
if (bTransformsChanged)
{
DynamicCollection->MakeDirty();
OnTransformsDirty();
}
}
}
}
void UGeometryCollectionComponent::RequestUpdateRepData()
{
FPhysScene* PhysScene = GetInnerChaosScene();
if (PhysScene && PhysicsProxy)
{
if (Chaos::FPBDRigidsSolver* Solver = PhysicsProxy->GetSolver<Chaos::FPBDRigidsSolver>())
{
const int32 CurrentFrame = Solver->GetCurrentFrame();
PhysScene->EnqueueAsyncPhysicsCommand(CurrentFrame, this,
[this]()
{
if (GetIsReplicated() && GetNetMode() != ENetMode::NM_Client)
{
UpdateRepData();
}
}, false
);
}
}
}
void UGeometryCollectionComponent::OnRep_RepData()
{
// We have new data that was replicated! Turn on the async tick to process instead of just requesting a one-off
// since we may want to keep processing for extra time afterwards.
check(IsInGameThread());
check(GetNetMode() == ENetMode::NM_Client);
ProcessRepDataOnPT();
}
void UGeometryCollectionComponent::OnRep_RepStateData()
{
// We have new data that was replicated! Turn on the async tick to process instead of just requesting a one-off
// since we may want to keep processing for extra time afterwards.
check(IsInGameThread());
check(GetNetMode() == ENetMode::NM_Client);
ProcessRepStateDataOnPT();
}
void UGeometryCollectionComponent::OnRep_RepDynamicData()
{
// We have new dynamic data that was replicated! Turn on the async tick to process instead of just requesting a one-off
// since we may want to keep processing for extra time afterwards.
check(IsInGameThread());
check(GetNetMode() == ENetMode::NM_Client);
ProcessRepDynamicDataOnPT();
}
void UGeometryCollectionComponent::SetAbandonedParticleCollisionProfileName(FName CollisionProfile)
{
if (!bEnableAbandonAfterLevel || !GetIsReplicated())
{
return;
}
AbandonedCollisionProfileName = CollisionProfile;
LoadCollisionProfiles();
}
void UGeometryCollectionComponent::SetPerLevelCollisionProfileNames(const TArray<FName>& ProfileNames)
{
CollisionProfilePerLevel = ProfileNames;
LoadCollisionProfiles();
}
// return true if anything was cahnged
template <typename TIteratableBoneContainerType>
static bool SetPerParticleCollisionProfileNameFromIterable(const UGeometryCollection* RestCollection, const TIteratableBoneContainerType& BoneIds, FName ProfileName, TArray<FName>& CollisionProfilePerParticle)
{
if (!RestCollection)
{
return false;
}
bool bHasChanged = false;
const int32 NumTransforms = RestCollection->GetGeometryCollection()->Transform.Num();
if (CollisionProfilePerParticle.Num() != NumTransforms)
{
bHasChanged = true;
CollisionProfilePerParticle.SetNumZeroed(NumTransforms);
}
for (int32 Id : BoneIds)
{
if (CollisionProfilePerParticle.IsValidIndex(Id))
{
if (CollisionProfilePerParticle[Id] != ProfileName)
{
bHasChanged = true;
CollisionProfilePerParticle[Id] = ProfileName;
}
}
}
return bHasChanged;
}
void UGeometryCollectionComponent::SetPerParticleCollisionProfileName(const TSet<int32>& BoneIds, FName ProfileName)
{
const bool bHasChanged = SetPerParticleCollisionProfileNameFromIterable(RestCollection, BoneIds, ProfileName, CollisionProfilePerParticle);
if (bHasChanged)
{
LoadCollisionProfiles();
}
}
bool UGeometryCollectionComponent::UpdatePerParticleCollisionProfilesNum()
{
if (!RestCollection)
{
return false;
}
const int32 NumTransforms = RestCollection->GetGeometryCollection()->Transform.Num();
const bool bHasChanged = CollisionProfilePerParticle.Num() != NumTransforms;
if (bHasChanged)
{
CollisionProfilePerParticle.SetNumZeroed(NumTransforms);
}
return bHasChanged;
}
void UGeometryCollectionComponent::SetParticleCollisionProfileName(int32 BoneId, FName ProfileName, FGCCollisionProfileScopedTransaction& InProfileNameUpdateTransaction)
{
if (!InProfileNameUpdateTransaction.IsValid())
{
return;
}
if (!CollisionProfilePerParticle.IsValidIndex(BoneId))
{
return;
}
FName& CurrentProfileName = CollisionProfilePerParticle[BoneId];
if (CurrentProfileName != ProfileName)
{
CurrentProfileName = ProfileName;
InProfileNameUpdateTransaction.MarkDirty();
}
}
void UGeometryCollectionComponent::SetPerParticleCollisionProfileName(const TArray<int32>& BoneIds, FName ProfileName)
{
const bool bHasChanged = SetPerParticleCollisionProfileNameFromIterable(RestCollection, BoneIds, ProfileName, CollisionProfilePerParticle);
if (bHasChanged)
{
LoadCollisionProfiles();
}
}
void UGeometryCollectionComponent::LoadCollisionProfiles()
{
if (!PhysicsProxy)
{
return;
}
// Cache the FCollisionResponseTemplate as well as the query/sim collision filter data for a given collision profile name
// so we don't have to recreate it every time.
using FCollisionProfileDataCache = FGeometryCollectionPhysicsProxy::FParticleCollisionFilterData;
TMap<FName, FCollisionProfileDataCache> CachedData;
// Returns nullptr if we can't create or get the data.
auto CreateOrGetCollisionProfileData = [this, &CachedData](const FName& ProfileName) -> const FCollisionProfileDataCache*
{
if (const FCollisionProfileDataCache* Data = CachedData.Find(ProfileName))
{
return Data;
}
FCollisionProfileDataCache Cache;
FCollisionResponseTemplate Template;
if (ProfileName == NAME_None)
{
return nullptr;
}
if (ProfileName == DefaultCollisionProfileName)
{
Cache.QueryFilter = InitialQueryFilter;
Cache.SimFilter = InitialSimFilter;
Cache.bQueryEnabled = true;
Cache.bSimEnabled = true;
}
else if (UCollisionProfile::Get()->GetProfileTemplate(ProfileName, Template))
{
AActor* Owner = GetOwner();
const uint32 ActorID = Owner ? Owner->GetUniqueID() : 0;
const uint32 CompID = GetUniqueID();
Cache.QueryFilter = InitialQueryFilter;
Cache.SimFilter = InitialSimFilter;
CreateShapeFilterData(Template.ObjectType, BodyInstance.GetMaskFilter(), ActorID, Template.ResponseToChannels, CompID, INDEX_NONE, Cache.QueryFilter, Cache.SimFilter, BodyInstance.bUseCCD, bNotifyCollisions || bNotifyGlobalCollisions, false, false);
// Maintain parity with the rest of the geometry collection filters.
Cache.QueryFilter.Word3 |= (EPDF_SimpleCollision | EPDF_ComplexCollision);
Cache.SimFilter.Word3 |= (EPDF_SimpleCollision | EPDF_ComplexCollision);
Cache.bQueryEnabled = CollisionEnabledHasQuery(Template.CollisionEnabled);
Cache.bSimEnabled = CollisionEnabledHasPhysics(Template.CollisionEnabled);
}
else
{
return nullptr;
}
Cache.bIsValid = true;
return &CachedData.Add(ProfileName, Cache);
};
const FCollisionProfileDataCache* AbandonedData = (bEnableAbandonAfterLevel && GetIsReplicated()) ? CreateOrGetCollisionProfileData(AbandonedCollisionProfileName) : nullptr;
Chaos::Facades::FCollectionHierarchyFacade HierarchyFacade(*RestCollection->GetGeometryCollection());
// Use GetAllPhysicsObjectIncludingNulls instead of GetAllPhysicsObjects if you need to use Level data or any data from HierarchyFacade
TArray<Chaos::FPhysicsObjectHandle> PhysicsObjects = PhysicsProxy->GetAllPhysicsObjectIncludingNulls();
FLockedWritePhysicsObjectExternalInterface Interface = FPhysicsObjectExternalInterface::LockWrite(PhysicsObjects);
TArray<FCollisionProfileDataCache> PerParticleData;
PerParticleData.Reserve(PhysicsObjects.Num());
for (int32 ParticleIndex = 0; ParticleIndex < PhysicsObjects.Num(); ++ParticleIndex)
{
if (PhysicsObjects[ParticleIndex] != nullptr)
{
const int32 Level = HierarchyFacade.GetInitialLevel(ParticleIndex);
TArrayView<Chaos::FPhysicsObjectHandle> ParticleView{ &PhysicsObjects[ParticleIndex], 1 };
const FCollisionProfileDataCache* Data = nullptr;
if (!CollisionProfilePerParticle.IsEmpty() && CollisionProfilePerParticle[ParticleIndex] != NAME_None)
{
Data = CreateOrGetCollisionProfileData(CollisionProfilePerParticle[ParticleIndex]);
}
else if (!CollisionProfilePerLevel.IsEmpty())
{
Data = CreateOrGetCollisionProfileData(CollisionProfilePerLevel[FMath::Min(CollisionProfilePerLevel.Num() - 1, Level)]);
}
else if (AbandonedData && Level >= ReplicationAbandonAfterLevel + 1)
{
Data = AbandonedData;
}
if (Data)
{
FCollisionProfileDataCache ParticleData = *Data;
ParticleData.ParticleIndex = ParticleIndex;
PerParticleData.Emplace(MoveTemp(ParticleData));
// Need to update on the GT too (physics proxy will just enqueue to do it on the PT) or else the changes won't take effect for GT SQ queries.
Interface->UpdateShapeCollisionFlags(ParticleView, ParticleData.bSimEnabled, ParticleData.bQueryEnabled);
Interface->UpdateShapeFilterData(ParticleView, ParticleData.QueryFilter, ParticleData.SimFilter);
}
}
}
PhysicsProxy->UpdatePerParticleFilterData_External(PerParticleData);
}
#if WITH_EDITORONLY_DATA
const FDamageCollector* UGeometryCollectionComponent::GetRunTimeDataCollector() const
{
return FRuntimeDataCollector::GetInstance().Find(RunTimeDataCollectionGuid);
}
#endif
void UGeometryCollectionComponent::OnDestroyPhysicsState()
{
UActorComponent::OnDestroyPhysicsState();
// we need to unregister the events because it relies on the proxy internally
if (EventDispatcher)
{
EventDispatcher->UnregisterChaosEvents();
}
if(PhysicsProxy)
{
// Clear physics sync callback
PhysicsProxy->SetPostPhysicsSyncCallback(nullptr);
if (FPhysScene* PhysScene = GetInnerChaosScene())
{
PhysScene->RemoveObject(PhysicsProxy);
}
InitializationState = ESimulationInitializationState::Unintialized;
// clear the clusters to rep as the information hold by it is now invalid
// we can still call this on the game thread because replication runs with the game thread frozen and will not run while the physics state is being torned down
ResetRepData();
// Discard the pointer (cleanup happens through the scene or dedicated thread)
PhysicsProxy = nullptr;
}
// We're skipping over the primitive component so we need to make sure this event gets fired.
OnComponentPhysicsStateChanged.Broadcast(this, EComponentPhysicsStateChange::Destroyed);
}
void UGeometryCollectionComponent::SendDynamicDataToSceneProxy()
{
FGeometryCollectionDynamicData* DynamicData = InitDynamicData();
if (SceneProxy && DynamicData)
{
if (SceneProxy->IsNaniteMesh())
{
INC_DWORD_STAT_BY(STAT_GCTotalTransforms, DynamicData ? DynamicData->Transforms.Num() : 0);
const FMatrix RenderMatrix{ GetRenderMatrix() };
FNaniteGeometryCollectionSceneProxy* GeometryCollectionSceneProxy = static_cast<FNaniteGeometryCollectionSceneProxy*>(SceneProxy);
ENQUEUE_RENDER_COMMAND(SendRenderDynamicData)(
[GeometryCollectionSceneProxy, DynamicData, RenderMatrix](FRHICommandListBase& RHICmdList)
{
GeometryCollectionSceneProxy->SetDynamicData_RenderThread(RHICmdList, DynamicData, RenderMatrix);
}
);
}
else
{
FGeometryCollectionSceneProxy* GeometryCollectionSceneProxy = static_cast<FGeometryCollectionSceneProxy*>(SceneProxy);
ENQUEUE_RENDER_COMMAND(SendRenderDynamicData)(
[GeometryCollectionSceneProxy, DynamicData](FRHICommandListBase& RHICmdList)
{
GeometryCollectionSceneProxy->SetDynamicData_RenderThread(RHICmdList, DynamicData);
}
);
}
}
}
void UGeometryCollectionComponent::SendRenderDynamicData_Concurrent()
{
//UE_LOG(UGCC_LOG, Log, TEXT("GeometryCollectionComponent[%p]::SendRenderDynamicData_Concurrent()"), this);
Super::SendRenderDynamicData_Concurrent();
SendDynamicDataToSceneProxy();
}
void UGeometryCollectionComponent::SetCollisionObjectType(ECollisionChannel Channel)
{
Super::SetCollisionObjectType(Channel);
BuildInitialFilterData();
// Update filters stored on proxy
if (PhysicsProxy)
{
PhysicsProxy->UpdateFilterData_External(InitialSimFilter, InitialQueryFilter);
}
}
void UGeometryCollectionComponent::OnActorEnableCollisionChanged()
{
// Update filters on BI
BodyInstance.UpdatePhysicsFilterData();
// Update InitialSimFilter and InitialQueryFilter
BuildInitialFilterData();
// Update filters stored on proxy
if (PhysicsProxy)
{
PhysicsProxy->UpdateFilterData_External(InitialSimFilter, InitialQueryFilter);
}
}
void UGeometryCollectionComponent::BuildInitialFilterData()
{
FBodyCollisionFilterData FilterData;
FMaskFilter FilterMask = BodyInstance.GetMaskFilter();
BodyInstance.BuildBodyFilterData(FilterData);
InitialSimFilter = FilterData.SimFilter;
InitialQueryFilter = FilterData.QuerySimpleFilter;
// Enable for complex and simple (no dual representation currently like other meshes)
InitialQueryFilter.Word3 |= (EPDF_SimpleCollision | EPDF_ComplexCollision);
InitialSimFilter.Word3 |= (EPDF_SimpleCollision | EPDF_ComplexCollision);
if (bNotifyCollisions || bNotifyGlobalCollisions)
{
InitialQueryFilter.Word3 |= EPDF_ContactNotify;
InitialSimFilter.Word3 |= EPDF_ContactNotify;
}
}
void UGeometryCollectionComponent::ApplyAssetDefaults()
{
if (RestCollection)
{
// initialize the component per level damage threshold from the asset defaults
DamageModel = RestCollection->DamageModel;
DamageThreshold = RestCollection->DamageThreshold;
bUseSizeSpecificDamageThreshold = RestCollection->bUseSizeSpecificDamageThreshold;
bUseMaterialDamageModifiers = RestCollection->bUseMaterialDamageModifiers;
// initialize the component damage progataion data from the asset defaults
DamagePropagationData = RestCollection->DamagePropagationData;
if (RestCollection->PhysicsMaterial)
{
BodyInstance.SetPhysMaterialOverride(RestCollection->PhysicsMaterial);
}
bDensityFromPhysicsMaterial = RestCollection->bDensityFromPhysicsMaterial;
}
}
void UGeometryCollectionComponent::SetRestCollection(const UGeometryCollection* RestCollectionIn, bool bApplyAssetDefaults)
{
if (CustomRenderer)
{
UnregisterCustomRenderer();
}
//UE_LOG(UGCC_LOG, Log, TEXT("GeometryCollectionComponent[%p]::SetRestCollection()"), this);
if (RestCollectionIn)
{
RestCollection = RestCollectionIn;
// if the geometry collection changes we need to clear the rest override rest transforms
// otherwise this may cause mismatch issues and make the geometry collection look wrong
RestTransforms.Reset();
// Reset the dynamic collection
ResetDynamicCollection();
// Rebuild the physics state since the dynamic collection has changed
RecreatePhysicsState();
if (!IsEmbeddedGeometryValid())
{
InitializeEmbeddedGeometry();
}
if (IsRegistered())
{
InitializeCustomRenderer();
}
if (CustomRenderer)
{
RegisterCustomRenderer();
RefreshCustomRenderer();
}
ClearRootProxyLocalTransforms();
if (bApplyAssetDefaults)
{
ApplyAssetDefaults();
}
}
}
FString UGeometryCollectionComponent::GetDebugInfo()
{
// print the game thread side of things
FString DebugInfo;
DebugInfo += FString("RestCollection - ") + (RestCollection? RestCollection->GetName() : FString("None"));
DebugInfo += "\n";
if (RestCollection && RestCollection->GetGeometryCollection())
{
DebugInfo += RestCollection->GetGeometryCollection()->ToString();
}
DebugInfo += FString("DynamicCollection - ") + FString(DynamicCollection ? "Yes": "No");
DebugInfo += "\n";
if (DynamicCollection)
{
DebugInfo += DynamicCollection->ToString();
}
return DebugInfo;
}
FGeometryCollectionEdit::FGeometryCollectionEdit(UGeometryCollectionComponent* InComponent, GeometryCollection::EEditUpdate InEditUpdate, bool bShapeIsUnchanged, bool bPropagateToAllMatchingComponents)
: Component(InComponent)
, EditUpdate(InEditUpdate)
, bShapeIsUnchanged(bShapeIsUnchanged)
, bPropagateToAllMatchingComponents(bPropagateToAllMatchingComponents)
{
if (!Component->RestCollection)
{
return;
}
auto ProcessComponent = [this](UGeometryCollectionComponent* ToProcess)
{
bool bHadPhysicsState = ToProcess->HasValidPhysicsState();
if (EnumHasAnyFlags(EditUpdate, GeometryCollection::EEditUpdate::Physics) && bHadPhysicsState)
{
HadPhysicsState.Add(ToProcess);
ToProcess->DestroyPhysicsState();
}
if (EnumHasAnyFlags(EditUpdate, GeometryCollection::EEditUpdate::Rest) && GetRestCollection())
{
ToProcess->Modify();
GetRestCollection()->Modify(); // Note: If multiple components share the rest collection, its Modify() will be called multiple times, but this should be ok
}
};
if (bPropagateToAllMatchingComponents)
{
for (TObjectIterator<UGeometryCollectionComponent> It(RF_ClassDefaultObject, false, EInternalObjectFlags::Garbage); It; ++It)
{
if (It->RestCollection == Component->RestCollection)
{
ProcessComponent(*It);
}
}
}
else
{
ProcessComponent(Component);
}
}
FGeometryCollectionEdit::~FGeometryCollectionEdit()
{
#if WITH_EDITOR
if (!!EditUpdate)
{
if (EnumHasAnyFlags(EditUpdate, GeometryCollection::EEditUpdate::Rest) && GetRestCollection())
{
if (!bShapeIsUnchanged)
{
GetRestCollection()->UpdateGeometryDependentProperties();
}
GetRestCollection()->InvalidateCollection();
}
auto ProcessComponent = [this](UGeometryCollectionComponent* ToProcess)
{
// Note: Reset dynamic collection for both rest and dynamic updates, since we don't want the dynamic to be out of sync with the rest
if (EnumHasAnyFlags(EditUpdate, GeometryCollection::EEditUpdate::Dynamic | GeometryCollection::EEditUpdate::Rest))
{
ToProcess->ResetDynamicCollection();
}
if (EnumHasAnyFlags(EditUpdate, GeometryCollection::EEditUpdate::Physics) && HadPhysicsState.Contains(ToProcess))
{
ToProcess->RecreatePhysicsState();
}
};
if (bPropagateToAllMatchingComponents)
{
for (TObjectIterator<UGeometryCollectionComponent> It(RF_ClassDefaultObject, false, EInternalObjectFlags::Garbage); It; ++It)
{
if (It->RestCollection == Component->RestCollection)
{
ProcessComponent(*It);
}
}
}
else
{
ProcessComponent(Component);
}
}
#endif
}
UGeometryCollection* FGeometryCollectionEdit::GetRestCollection()
{
if (Component)
{
return const_cast<UGeometryCollection*>(ToRawPtr(Component->RestCollection)); //const cast is ok here since we are explicitly in edit mode. Should all this editor code be in an editor module?
}
return nullptr;
}
#if WITH_EDITOR
TArray<FLinearColor> FScopedColorEdit::RandomColors;
FScopedColorEdit::FScopedColorEdit(UGeometryCollectionComponent* InComponent, bool bForceUpdate) : bUpdated(bForceUpdate), Component(InComponent)
{
if (RandomColors.Num() == 0)
{
// predictable colors based on the component
FRandomStream Random(GetTypeHash(InComponent));
for (int i = 0; i < 100; i++)
{
const uint8 R = static_cast<uint8>(Random.FRandRange(5, 105));
const uint8 G = static_cast<uint8>(Random.FRandRange(5, 105));
const uint8 B = static_cast<uint8>(Random.FRandRange(5, 105));
RandomColors.Push(FLinearColor(FColor(R, G, B, 255)));
}
}
}
FScopedColorEdit::~FScopedColorEdit()
{
if (bUpdated)
{
UpdateBoneColors();
}
}
void FScopedColorEdit::SetShowBoneColors(bool ShowBoneColorsIn)
{
if (Component->bShowBoneColors != ShowBoneColorsIn)
{
bUpdated = true;
Component->bShowBoneColors = ShowBoneColorsIn;
}
}
bool FScopedColorEdit::GetShowBoneColors() const
{
return Component->bShowBoneColors;
}
void FScopedColorEdit::SetEnableBoneSelection(bool ShowSelectedBonesIn)
{
if (Component->bEnableBoneSelection != ShowSelectedBonesIn)
{
bUpdated = true;
Component->bEnableBoneSelection = ShowSelectedBonesIn;
}
}
bool FScopedColorEdit::GetEnableBoneSelection() const
{
return Component->bEnableBoneSelection;
}
bool FScopedColorEdit::IsBoneSelected(int BoneIndex) const
{
return Component->SelectedBones.Contains(BoneIndex);
}
void FScopedColorEdit::Sanitize()
{
const UGeometryCollection* GeometryCollection = Component->GetRestCollection();
if (GeometryCollection)
{
TSharedPtr<FGeometryCollection, ESPMode::ThreadSafe> GeometryCollectionPtr = GeometryCollection->GetGeometryCollection();
if (GeometryCollectionPtr)
{
const int32 NumTransforms = GeometryCollectionPtr->NumElements(FGeometryCollection::TransformGroup);
const int32 NumSelectionRemoved = Component->SelectedBones.RemoveAll([this, NumTransforms](int32 Index) {
return Index < 0 || Index >= NumTransforms;
});
const int32 NumHighlightRemoved = Component->HighlightedBones.RemoveAll([this, NumTransforms](int32 Index) {
return Index < 0 || Index >= NumTransforms;
});
bUpdated = bUpdated || NumSelectionRemoved || NumHighlightRemoved;
}
}
}
void FScopedColorEdit::SetSelectedBones(const TArray<int32>& SelectedBonesIn)
{
bUpdated = true;
Component->SelectedBones = SelectedBonesIn;
Component->SelectEmbeddedGeometry();
}
void FScopedColorEdit::AppendSelectedBones(const TArray<int32>& SelectedBonesIn)
{
bUpdated = true;
Component->SelectedBones.Append(SelectedBonesIn);
}
void FScopedColorEdit::ToggleSelectedBones(const TArray<int32>& SelectedBonesIn, bool bAdd, bool bSnapToLevel)
{
bUpdated = true;
const UGeometryCollection* GeometryCollection = Component->GetRestCollection();
if (GeometryCollection)
{
TSharedPtr<FGeometryCollection, ESPMode::ThreadSafe> GeometryCollectionPtr = GeometryCollection->GetGeometryCollection();
for (int32 BoneIndex : SelectedBonesIn)
{
int32 ContextBoneIndex = (bSnapToLevel && GetViewLevel() > -1) ?
FGeometryCollectionClusteringUtility::GetParentOfBoneAtSpecifiedLevel(GeometryCollectionPtr.Get(), BoneIndex, GetViewLevel(), true /*bSkipFiltered*/)
: BoneIndex;
if (ContextBoneIndex == FGeometryCollection::Invalid)
{
continue;
}
if (bAdd) // shift select
{
Component->SelectedBones.Add(ContextBoneIndex);
}
else // ctrl select (toggle)
{
if (Component->SelectedBones.Contains(ContextBoneIndex))
{
Component->SelectedBones.Remove(ContextBoneIndex);
}
else
{
Component->SelectedBones.Add(ContextBoneIndex);
}
}
}
}
}
void FScopedColorEdit::AddSelectedBone(int32 BoneIndex)
{
if (!Component->SelectedBones.Contains(BoneIndex))
{
bUpdated = true;
Component->SelectedBones.Push(BoneIndex);
}
}
void FScopedColorEdit::ClearSelectedBone(int32 BoneIndex)
{
if (Component->SelectedBones.Contains(BoneIndex))
{
bUpdated = true;
Component->SelectedBones.Remove(BoneIndex);
}
}
const TArray<int32>& FScopedColorEdit::GetSelectedBones() const
{
return Component->GetSelectedBones();
}
int32 FScopedColorEdit::GetMaxSelectedLevel(bool bOnlyRigid) const
{
int32 MaxSelectedLevel = -1;
const UGeometryCollection* GeometryCollection = Component->GetRestCollection();
if (GeometryCollection && GeometryCollection->GetGeometryCollection()->HasAttribute("Level", FGeometryCollection::TransformGroup))
{
TSharedPtr<FGeometryCollection, ESPMode::ThreadSafe> GeometryCollectionPtr = GeometryCollection->GetGeometryCollection();
const TManagedArray<int32>& Levels = GeometryCollectionPtr->GetAttribute<int32>("Level", FGeometryCollection::TransformGroup);
const TManagedArray<int32>& SimTypes = GeometryCollectionPtr->SimulationType;
for (int32 BoneIndex : Component->SelectedBones)
{
if (!bOnlyRigid || SimTypes[BoneIndex] == FGeometryCollection::ESimulationTypes::FST_Rigid)
{
MaxSelectedLevel = FMath::Max(MaxSelectedLevel, Levels[BoneIndex]);
}
}
}
return MaxSelectedLevel;
}
bool FScopedColorEdit::IsSelectionValidAtLevel(int32 TargetLevel) const
{
if (TargetLevel == -1)
{
return true;
}
const UGeometryCollection* GeometryCollection = Component->GetRestCollection();
if (GeometryCollection && GeometryCollection->GetGeometryCollection()->HasAttribute("Level", FGeometryCollection::TransformGroup))
{
TSharedPtr<FGeometryCollection, ESPMode::ThreadSafe> GeometryCollectionPtr = GeometryCollection->GetGeometryCollection();
const TManagedArray<int32>& Levels = GeometryCollectionPtr->GetAttribute<int32>("Level", FGeometryCollection::TransformGroup);
const TManagedArray<int32>& SimTypes = GeometryCollectionPtr->SimulationType;
for (int32 BoneIndex : Component->SelectedBones)
{
if (SimTypes[BoneIndex] != FGeometryCollection::ESimulationTypes::FST_Clustered && // clusters are always shown in outliner
Levels[BoneIndex] != TargetLevel && // nodes at the target level are shown in outliner
// non-cluster parents are shown if they have children that are exact matches (i.e., a rigid parent w/ embedded at the target level)
(GeometryCollectionPtr->Children[BoneIndex].Num() == 0 || Levels[BoneIndex] + 1 != TargetLevel))
{
return false;
}
}
}
return true;
}
void FScopedColorEdit::ResetBoneSelection()
{
if (Component->SelectedBones.Num() > 0)
{
bUpdated = true;
}
Component->SelectedBones.Empty();
}
void FScopedColorEdit::FilterSelectionToLevel(bool bPreferLowestOnly)
{
const UGeometryCollection* GeometryCollection = Component->GetRestCollection();
int32 ViewLevel = GetViewLevel();
bool bNeedsFiltering = ViewLevel >= 0 || bPreferLowestOnly;
if (GeometryCollection && Component->SelectedBones.Num() > 0 && bNeedsFiltering && GeometryCollection->GetGeometryCollection()->HasAttribute("Level", FGeometryCollection::TransformGroup))
{
TSharedPtr<FGeometryCollection, ESPMode::ThreadSafe> GeometryCollectionPtr = GeometryCollection->GetGeometryCollection();
const TManagedArray<int32>& Levels = GeometryCollectionPtr->GetAttribute<int32>("Level", FGeometryCollection::TransformGroup);
const TManagedArray<int32>& SimTypes = GeometryCollectionPtr->SimulationType;
TArray<int32> NewSelection;
NewSelection.Reserve(Component->SelectedBones.Num());
if (ViewLevel >= 0)
{
for (int32 BoneIdx : Component->SelectedBones)
{
bool bIsCluster = SimTypes[BoneIdx] == FGeometryCollection::ESimulationTypes::FST_Clustered;
if (bPreferLowestOnly && bIsCluster && Levels[BoneIdx] < ViewLevel)
{
continue;
}
if (Levels[BoneIdx] == ViewLevel || (bIsCluster && Levels[BoneIdx] <= ViewLevel))
{
NewSelection.Add(BoneIdx);
}
}
}
else // bPreferLowestOnly && ViewLevel == -1
{
// If view level is "all" and we prefer lowest selection, just select any non-cluster nodes
for (int32 BoneIdx : Component->SelectedBones)
{
bool bIsCluster = SimTypes[BoneIdx] == FGeometryCollection::ESimulationTypes::FST_Clustered;
if (!bIsCluster)
{
NewSelection.Add(BoneIdx);
}
}
}
if (NewSelection.Num() != Component->SelectedBones.Num())
{
SetSelectedBones(NewSelection);
SetHighlightedBones(NewSelection, true);
}
}
}
void FScopedColorEdit::SelectBones(GeometryCollection::ESelectionMode SelectionMode)
{
check(Component);
const UGeometryCollection* GeometryCollection = Component->GetRestCollection();
if (GeometryCollection)
{
TSharedPtr<FGeometryCollection, ESPMode::ThreadSafe> GeometryCollectionPtr = GeometryCollection->GetGeometryCollection();
switch (SelectionMode)
{
case GeometryCollection::ESelectionMode::None:
ResetBoneSelection();
break;
case GeometryCollection::ESelectionMode::AllGeometry:
{
ResetBoneSelection();
TArray<int32> BonesToSelect;
FGeometryCollectionClusteringUtility::GetBonesToLevel(GeometryCollectionPtr.Get(), GetViewLevel(), BonesToSelect, true, true);
AppendSelectedBones(BonesToSelect);
}
break;
case GeometryCollection::ESelectionMode::Leaves:
{
ResetBoneSelection();
int32 ViewLevel = GetViewLevel();
TArray<int32> BonesToSelect;
FGeometryCollectionClusteringUtility::GetBonesToLevel(GeometryCollectionPtr.Get(), GetViewLevel(), BonesToSelect, true, true);
const TManagedArray<int32>& SimType = GeometryCollectionPtr->SimulationType;
const TManagedArray<int32>* Levels = GeometryCollectionPtr->FindAttributeTyped<int32>("Level", FGeometryCollection::TransformGroup);
BonesToSelect.SetNum(Algo::RemoveIf(BonesToSelect, [&](int32 BoneIdx)
{
return SimType[BoneIdx] != FGeometryCollection::ESimulationTypes::FST_Rigid
|| (ViewLevel != -1 && Levels && (*Levels)[BoneIdx] != ViewLevel);
}));
AppendSelectedBones(BonesToSelect);
}
break;
case GeometryCollection::ESelectionMode::Clusters:
{
ResetBoneSelection();
int32 ViewLevel = GetViewLevel();
TArray<int32> BonesToSelect;
FGeometryCollectionClusteringUtility::GetBonesToLevel(GeometryCollectionPtr.Get(), ViewLevel, BonesToSelect, true, true);
const TManagedArray<int32>& SimType = GeometryCollectionPtr->SimulationType;
const TManagedArray<int32>* Levels = GeometryCollectionPtr->FindAttributeTyped<int32>("Level", FGeometryCollection::TransformGroup);
BonesToSelect.SetNum(Algo::RemoveIf(BonesToSelect, [&](int32 BoneIdx)
{
return SimType[BoneIdx] != FGeometryCollection::ESimulationTypes::FST_Clustered
|| (ViewLevel != -1 && Levels && (*Levels)[BoneIdx] != ViewLevel);
}));
AppendSelectedBones(BonesToSelect);
}
break;
case GeometryCollection::ESelectionMode::InverseGeometry:
{
TArray<int32> Roots;
FGeometryCollectionClusteringUtility::GetRootBones(GeometryCollectionPtr.Get(), Roots);
TArray<int32> NewSelection;
for (int32 RootElement : Roots)
{
if (GetViewLevel() == -1)
{
TArray<int32> LeafBones;
FGeometryCollectionClusteringUtility::GetLeafBones(GeometryCollectionPtr.Get(), RootElement, true, LeafBones);
for (int32 Element : LeafBones)
{
if (!IsBoneSelected(Element))
{
NewSelection.Push(Element);
}
}
}
else
{
TArray<int32> ViewLevelBones;
FGeometryCollectionClusteringUtility::GetChildBonesAtLevel(GeometryCollectionPtr.Get(), RootElement, GetViewLevel(), ViewLevelBones);
for (int32 ViewLevelBone : ViewLevelBones)
{
if (!IsBoneSelected(ViewLevelBone))
{
NewSelection.Push(ViewLevelBone);
}
}
}
}
ResetBoneSelection();
AppendSelectedBones(NewSelection);
}
break;
case GeometryCollection::ESelectionMode::Neighbors:
{
FGeometryCollectionProximityUtility ProximityUtility(GeometryCollectionPtr.Get());
ProximityUtility.RequireProximity();
const TManagedArray<int32>& TransformIndex = GeometryCollectionPtr->TransformIndex;
const TManagedArray<int32>& TransformToGeometryIndex = GeometryCollectionPtr->TransformToGeometryIndex;
const TManagedArray<TSet<int32>>& Proximity = GeometryCollectionPtr->GetAttribute<TSet<int32>>("Proximity", FGeometryCollection::GeometryGroup);
Chaos::Facades::FCollectionHierarchyFacade HierarchyFacade(*GeometryCollectionPtr);
const TArray<int32> SelectedBones = GetSelectedBones();
TSet<int32> NewSelection;
for (int32 Bone : SelectedBones)
{
NewSelection.Add(Bone);
ProximityUtility.EnumerateNeighbors(HierarchyFacade, Bone, [&NewSelection](int32 NeighborTransformIdx)
{
NewSelection.Add(NeighborTransformIdx);
}, true /*allow neighbors in parent levels*/, false /*bFilterDuplicates, not needed since we add to a set*/);
}
ResetBoneSelection();
AppendSelectedBones(NewSelection.Array());
}
break;
case GeometryCollection::ESelectionMode::Parent:
{
const TManagedArray<int32>& Parents = GeometryCollectionPtr->Parent;
const TArray<int32> SelectedBones = GetSelectedBones();
TSet<int32> NewSelection;
for (int32 Bone : SelectedBones)
{
int32 ParentBone = Parents[Bone];
if (ParentBone != FGeometryCollection::Invalid)
{
NewSelection.Add(ParentBone);
}
}
ResetBoneSelection();
AppendSelectedBones(NewSelection.Array());
}
break;
case GeometryCollection::ESelectionMode::Children:
{
const TManagedArray<TSet<int32>>& Children = GeometryCollectionPtr->Children;
const TArray<int32> SelectedBones = GetSelectedBones();
TSet<int32> NewSelection;
for (int32 Bone : SelectedBones)
{
if (Children[Bone].IsEmpty())
{
NewSelection.Add(Bone);
continue;
}
for (int32 Child : Children[Bone])
{
NewSelection.Add(Child);
}
}
ResetBoneSelection();
AppendSelectedBones(NewSelection.Array());
}
break;
case GeometryCollection::ESelectionMode::Siblings:
{
const TManagedArray<int32>& Parents = GeometryCollectionPtr->Parent;
const TManagedArray<TSet<int32>>& Children = GeometryCollectionPtr->Children;
const TArray<int32> SelectedBones = GetSelectedBones();
TSet<int32> NewSelection;
for (int32 Bone : SelectedBones)
{
int32 ParentBone = Parents[Bone];
if (ParentBone != FGeometryCollection::Invalid)
{
for (int32 Child : Children[ParentBone])
{
NewSelection.Add(Child);
}
}
}
ResetBoneSelection();
AppendSelectedBones(NewSelection.Array());
}
break;
case GeometryCollection::ESelectionMode::Level:
{
if (GeometryCollectionPtr->HasAttribute("Level", FTransformCollection::TransformGroup))
{
const TManagedArray<int32>& Levels = GeometryCollectionPtr->GetAttribute<int32>("Level", FTransformCollection::TransformGroup);
const TArray<int32> SelectedBones = GetSelectedBones();
TSet<int32> NewSelection;
for (int32 Bone : SelectedBones)
{
int32 Level = Levels[Bone];
for (int32 TransformIdx = 0; TransformIdx < GeometryCollectionPtr->NumElements(FTransformCollection::TransformGroup); ++TransformIdx)
{
if (Levels[TransformIdx] == Level)
{
NewSelection.Add(TransformIdx);
}
}
}
ResetBoneSelection();
AppendSelectedBones(NewSelection.Array());
}
}
break;
default:
check(false); // unexpected selection mode
break;
}
const TArray<int32>& SelectedBones = GetSelectedBones();
TArray<int32> HighlightBones;
for (int32 SelectedBone: SelectedBones)
{
FGeometryCollectionClusteringUtility::RecursiveAddAllChildren(GeometryCollectionPtr->Children, SelectedBone, HighlightBones);
}
SetHighlightedBones(HighlightBones);
}
}
bool FScopedColorEdit::IsBoneHighlighted(int BoneIndex) const
{
return Component->HighlightedBones.Contains(BoneIndex);
}
void FScopedColorEdit::SetHighlightedBones(const TArray<int32>& HighlightedBonesIn, bool bHighlightChildren)
{
if (Component->HighlightedBones != HighlightedBonesIn)
{
const UGeometryCollection* GeometryCollection = Component->GetRestCollection();
if (bHighlightChildren && GeometryCollection)
{
Component->HighlightedBones.Reset();
TSharedPtr<FGeometryCollection, ESPMode::ThreadSafe> GeometryCollectionPtr = GeometryCollection->GetGeometryCollection();
for (int32 SelectedBone : HighlightedBonesIn)
{
FGeometryCollectionClusteringUtility::RecursiveAddAllChildren(GeometryCollectionPtr->Children, SelectedBone, Component->HighlightedBones);
}
}
else
{
Component->HighlightedBones = HighlightedBonesIn;
}
bUpdated = true;
}
}
void FScopedColorEdit::AddHighlightedBone(int32 BoneIndex)
{
Component->HighlightedBones.Push(BoneIndex);
}
const TArray<int32>& FScopedColorEdit::GetHighlightedBones() const
{
return Component->GetHighlightedBones();
}
void FScopedColorEdit::ResetHighlightedBones()
{
if (Component->HighlightedBones.Num() > 0)
{
bUpdated = true;
Component->HighlightedBones.Empty();
}
}
void FScopedColorEdit::SetLevelViewMode(int ViewLevelIn)
{
if (Component->ViewLevel != ViewLevelIn)
{
bUpdated = true;
Component->ViewLevel = ViewLevelIn;
}
}
int FScopedColorEdit::GetViewLevel()
{
return Component->ViewLevel;
}
void FScopedColorEdit::UpdateBoneColors()
{
// @todo FractureTools - For large fractures updating colors this way is extremely slow because the render state (and thus all buffers) must be recreated.
// It would be better to push the update to the proxy via a render command and update the existing buffer directly
FGeometryCollectionEdit GeometryCollectionEdit = Component->EditRestCollection(GeometryCollection::EEditUpdate::None);
UGeometryCollection* GeometryCollection = GeometryCollectionEdit.GetRestCollection();
if(GeometryCollection)
{
const UDataflowSettings* DataflowSettings = GetDefault<UDataflowSettings>();
FGeometryCollection* Collection = GeometryCollection->GetGeometryCollection().Get();
const TManagedArray<int>& Parents = Collection->Parent;
bool HasLevelAttribute = Collection->HasAttribute("Level", FTransformCollection::TransformGroup);
const TManagedArray<int>* Levels = nullptr;
if (HasLevelAttribute)
{
Levels = &Collection->GetAttribute<int32>("Level", FTransformCollection::TransformGroup);
}
TManagedArray<FLinearColor>& BoneColors = Collection->BoneColor;
for (int32 BoneIndex = 0, NumBones = Parents.Num() ; BoneIndex < NumBones; ++BoneIndex)
{
FLinearColor BoneColor = FLinearColor(FColor::Black);
if (Component->bShowBoneColors)
{
if (Component->ViewLevel == -1)
{
BoneColor = RandomColors[BoneIndex % RandomColors.Num()];
}
else
{
if (HasLevelAttribute && (*Levels)[BoneIndex] >= Component->ViewLevel)
{
// go up until we find parent at the required ViewLevel
int32 Bone = BoneIndex;
while (Bone != -1 && (*Levels)[Bone] > Component->ViewLevel)
{
Bone = Parents[Bone];
}
int32 ColorIndex = Bone + 1; // parent can be -1 for root, range [-1..n]
BoneColor = RandomColors[ColorIndex % RandomColors.Num()];
BoneColor = BoneColor.LinearRGBToHSV();
BoneColor.B *= .5;
BoneColor = BoneColor.HSVToLinearRGB();
}
else
{
BoneColor = DataflowSettings->TransformLevelColors.BlankColor;
}
}
}
else
{
BoneColor = FLinearColor(FColor::White);
if (Component->ViewLevel != INDEX_NONE && HasLevelAttribute && (*Levels)[BoneIndex] < Component->ViewLevel)
{
BoneColor = FLinearColor(FColor(128U, 128U, 128U, 255U));
}
}
// store the bone selected toggle in alpha so we can use it in the shader
BoneColor.A = IsBoneHighlighted(BoneIndex) ? 1 : 0;
BoneColors[BoneIndex] = BoneColor;
}
Component->MarkRenderStateDirty();
Component->MarkRenderDynamicDataDirty();
}
}
#endif
void UGeometryCollectionComponent::ApplyKinematicField(float Radius, FVector Position)
{
FFieldSystemCommand Command = FFieldObjectCommands::CreateFieldCommand(EFieldPhysicsType::Field_DynamicState, new FRadialIntMask(Radius, Position, (int32)Chaos::EObjectStateType::Dynamic,
(int32)Chaos::EObjectStateType::Kinematic, ESetMaskConditionType::Field_Set_IFF_NOT_Interior));
DispatchFieldCommand(Command);
}
void UGeometryCollectionComponent::ApplyPhysicsField(bool Enabled, EGeometryCollectionPhysicsTypeEnum Target, UFieldSystemMetaData* MetaData, UFieldNodeBase* Field)
{
if (Enabled && Field)
{
FFieldSystemCommand Command = FFieldObjectCommands::CreateFieldCommand(GetGeometryCollectionPhysicsType(Target), Field, MetaData);
DispatchFieldCommand(Command);
}
}
bool UGeometryCollectionComponent::GetIsObjectDynamic() const
{
return PhysicsProxy ? PhysicsProxy->GetIsObjectDynamic() : IsObjectDynamic;
}
void UGeometryCollectionComponent::DispatchFieldCommand(const FFieldSystemCommand& InCommand)
{
if (PhysicsProxy && InCommand.RootNode)
{
FChaosSolversModule* ChaosModule = FChaosSolversModule::GetModule();
checkSlow(ChaosModule);
auto Solver = PhysicsProxy->GetSolver<Chaos::FPBDRigidsSolver>();
const FName Name = GetOwner() ? *GetOwner()->GetName() : TEXT("");
FFieldSystemCommand LocalCommand = InCommand;
LocalCommand.InitFieldNodes(Solver->GetSolverTime(), Name);
PhysicsProxy->BufferFieldCommand_External(MoveTemp(LocalCommand));
}
}
void UGeometryCollectionComponent::GetInitializationCommands(TArray<FFieldSystemCommand>& CombinedCommmands)
{
CombinedCommmands.Reset();
for (const AFieldSystemActor* FieldSystemActor : InitializationFields)
{
if (FieldSystemActor != nullptr)
{
if (FieldSystemActor->GetFieldSystemComponent())
{
const int32 NumCommands = FieldSystemActor->GetFieldSystemComponent()->ConstructionCommands.GetNumCommands();
if (NumCommands > 0)
{
for (int32 CommandIndex = 0; CommandIndex < NumCommands; ++CommandIndex)
{
const FFieldSystemCommand NewCommand = FieldSystemActor->GetFieldSystemComponent()->ConstructionCommands.BuildFieldCommand(CommandIndex);
if (NewCommand.RootNode)
{
CombinedCommmands.Emplace(NewCommand);
UWorld* World = GetWorld();
if (World && World->PhysicsField)
{
const FName Name = GetOwner() ? *GetOwner()->GetName() : TEXT("");
FFieldSystemCommand LocalCommand = NewCommand;
LocalCommand.InitFieldNodes(World->GetTimeSeconds(), Name);
World->PhysicsField->AddConstructionCommand(LocalCommand);
}
}
}
}
// Legacy path : only there for old levels. New ones will have the commands directly stored onto the component
else if (FieldSystemActor->GetFieldSystemComponent()->GetFieldSystem())
{
const FName Name = GetOwner() ? *GetOwner()->GetName() : TEXT("");
for (const FFieldSystemCommand& Command : FieldSystemActor->GetFieldSystemComponent()->GetFieldSystem()->Commands)
{
if (Command.RootNode)
{
FFieldSystemCommand NewCommand = { Command.TargetAttribute, Command.RootNode->NewCopy() };
NewCommand.InitFieldNodes(0.0, Name);
for (const TPair<FFieldSystemMetaData::EMetaType, TUniquePtr<FFieldSystemMetaData>>& Elem : Command.MetaData)
{
NewCommand.MetaData.Add(Elem.Key, TUniquePtr<FFieldSystemMetaData>(Elem.Value->NewCopy()));
}
CombinedCommmands.Emplace(NewCommand);
}
}
}
}
}
}
}
bool UGeometryCollectionComponent::GetSuppressSelectionMaterial() const
{
return RestCollection->GetGeometryCollection()->HasAttribute("Hide", FGeometryCollection::TransformGroup);
}
FPhysScene_Chaos* UGeometryCollectionComponent::GetInnerChaosScene() const
{
if (ChaosSolverActor)
{
return ChaosSolverActor->GetPhysicsScene().Get();
}
else
{
if (GetOwner() && GetOwner()->GetWorld())
{
return GetOwner()->GetWorld()->GetPhysicsScene();
}
if (GWorld)
{
return GWorld->GetPhysicsScene();
}
UE_LOG(LogPhysics, Error, TEXT("Failed to find valid UWorld in UGeometryCollectionComponent::GetInnerChaosScene"));
return nullptr;
}
}
AChaosSolverActor* UGeometryCollectionComponent::GetPhysicsSolverActor() const
{
if (ChaosSolverActor)
{
return ChaosSolverActor;
}
else
{
FPhysScene_Chaos const* const Scene = GetInnerChaosScene();
return Scene ? Cast<AChaosSolverActor>(Scene->GetSolverActor()) : nullptr;
}
}
void UGeometryCollectionComponent::SetSolverActor(AChaosSolverActor* InSolverActor)
{
ChaosSolverActor = InSolverActor;
// Reset the dynamic collection
ResetDynamicCollection();
// Rebuild the physics state since the proxy could now be registered to another actor
RecreatePhysicsState();
}
bool UGeometryCollectionComponent::CanRunSimulationInEditor() const
{
if (UWorld* World = GetWorld())
{
Chaos::FPhysicsSolver* CurrSolver = GetSolver(*this);
return World->IsGameWorld() || GeometryCollectionCreatePhysicsStateInEditor || (CurrSolver && CurrSolver->IsStandaloneSolver());
}
return false;
}
#define GEOMETRY_COLLECTION_CHECK_FOR_NANS_IN_TRANSFORMS 0
#if GEOMETRY_COLLECTION_CHECK_FOR_NANS_IN_TRANSFORMS
static void CheckForNaNs(const TArray<FTransform>& Transforms)
{
for (const FTransform& Transform : Transforms)
{
ensureAlways(false == Transform.ContainsNaN());
}
}
#endif
FTransform3f UGeometryCollectionComponent::GetCurrentTransform(int32 Index) const
{
if (DynamicCollection)
{
return DynamicCollection->GetTransform(Index);
}
const bool bRestTransformsOverriden = (RestTransforms.Num() > 0);
if (bRestTransformsOverriden)
{
return FTransform3f(RestTransforms[Index]);
}
return RestCollection->GetGeometryCollection()->Transform[Index];
}
void UGeometryCollectionComponent::ComputeCurrentGlobalsMatrices(TArray<FTransform3f>& OutTransforms) const
{
if (DynamicCollection)
{
GeometryCollectionAlgo::Private::GlobalMatrices(*DynamicCollection, OutTransforms);
}
else
{
const bool bRestTransformsOverriden = (RestTransforms.Num() > 0);
if (bRestTransformsOverriden)
{
GeometryCollectionAlgo::GlobalMatrices(TManagedArray<FTransform>(RestTransforms), GetParentArrayRest(), OutTransforms);
}
else
{
GeometryCollectionAlgo::GlobalMatrices(RestCollection->GetGeometryCollection()->Transform, GetParentArrayRest(), OutTransforms);
}
}
}
const FTransform3f& UGeometryCollectionComponent::FComponentSpaceTransforms::RequestRootTransform() const
{
if (!Component)
{
ensure(false); // we should not be able to reach here
return FTransform3f::Identity;
}
if (Transforms.IsValidIndex(RootIndex))
{
if (bIsRootDirty)
{
Transforms[RootIndex] = Component->GetCurrentTransform(RootIndex);
bIsRootDirty = false;
}
return Transforms[RootIndex];
}
return FTransform3f::Identity;
}
const TArray<FTransform3f>& UGeometryCollectionComponent::FComponentSpaceTransforms::RequestAllTransforms() const
{
SCOPE_CYCLE_COUNTER(STAT_GCCUGlobalMatrices);
static TArray<FTransform3f> EmptyArray;
if (!Component || Transforms.Num() == 0)
{
return EmptyArray;
}
if (!bIsDirty)
{
return Transforms;
}
int32 CurrentTransformNum;
if (Component->DynamicCollection)
{
CurrentTransformNum = Component->DynamicCollection->GetNumTransforms();
}
else
{
CurrentTransformNum = Component->RestCollection->GetGeometryCollection()->Transform.Num();
}
bool bFastPath = false;
if (Component->RestCollection)
{
const TArray<int32>& BreadthFirstTransformIndices = Component->RestCollection->GetBreadthFirstTransformIndices();
bFastPath = (BreadthFirstTransformIndices.Num() == CurrentTransformNum);
}
if (bFastPath)
{
const TArray<int32>& BreadthFirstTransformIndices = Component->RestCollection->GetBreadthFirstTransformIndices();
for (int32 Index = 0; Index < BreadthFirstTransformIndices.Num(); Index++)
{
const int32 TransformIndex = BreadthFirstTransformIndices[Index];
const int32 ParentTransformIndex = Component->GetParent(TransformIndex);
FTransform3f CurrentTransform = Component->GetCurrentTransform(TransformIndex);
if (ParentTransformIndex == INDEX_NONE)
{
Transforms[TransformIndex] = CurrentTransform;
}
else
{
const FTransform3f& ParentTransform = Transforms[ParentTransformIndex];
Transforms[TransformIndex] = CurrentTransform * ParentTransform;
}
}
}
else
{
Component->ComputeCurrentGlobalsMatrices(Transforms);
}
#if WITH_EDITOR
UpdateGlobalMatricesWithExplodedVectors(Transforms, *(Component->RestCollection->GetGeometryCollection()));
#endif
#if GEOMETRY_COLLECTION_CHECK_FOR_NANS_IN_TRANSFORMS
CheckForNaNs(Transforms);
#endif
bIsDirty = false;
bIsRootDirty = false;
return Transforms;
}
int32 UGeometryCollectionComponent::GetNumMaterials() const
{
return !RestCollection ? 0 : RestCollection->Materials.Num();
}
UMaterialInterface* UGeometryCollectionComponent::GetMaterial(int32 MaterialIndex) const
{
// If we have a base materials array, use that
if (OverrideMaterials.IsValidIndex(MaterialIndex) && OverrideMaterials[MaterialIndex])
{
return OverrideMaterials[MaterialIndex];
}
// Otherwise get from geom collection
else
{
return RestCollection && RestCollection->Materials.IsValidIndex(MaterialIndex) ? RestCollection->Materials[MaterialIndex] : nullptr;
}
}
void UGeometryCollectionComponent::GetUsedMaterials(TArray<UMaterialInterface*>& OutMaterials, bool bGetDebugMaterials) const
{
Super::GetUsedMaterials(OutMaterials, bGetDebugMaterials);
if (GetRestCollection())
{
if (UMaterialInterface* BoneSelectedMaterial = GetRestCollection()->GetBoneSelectedMaterial())
{
OutMaterials.Add(BoneSelectedMaterial);
}
}
}
FMaterialRelevance UGeometryCollectionComponent::GetMaterialRelevance(ERHIFeatureLevel::Type InFeatureLevel) const
{
FMaterialRelevance Result = Super::GetMaterialRelevance(InFeatureLevel);
if (RestCollection)
{
if (UMaterialInterface* BoneSelectedMaterial = GetRestCollection()->GetBoneSelectedMaterial())
{
Result |= BoneSelectedMaterial->GetRelevance_Concurrent(InFeatureLevel);
}
}
return Result;
}
#if WITH_EDITOR
void UGeometryCollectionComponent::SelectEmbeddedGeometry()
{
// First reset the selections
for (TObjectPtr<UInstancedStaticMeshComponent>& EmbeddedGeometryComponent : EmbeddedGeometryComponents)
{
EmbeddedGeometryComponent->ClearInstanceSelection();
}
const TManagedArray<int32>& ExemplarIndex = RestCollection->GetGeometryCollection()->ExemplarIndex;
for (int32 SelectedBone : SelectedBones)
{
if (EmbeddedGeometryComponents.IsValidIndex(ExemplarIndex[SelectedBone]))
{
EmbeddedGeometryComponents[ExemplarIndex[SelectedBone]]->SelectInstance(true, EmbeddedInstanceIndex[SelectedBone], 1);
}
}
}
#endif
// #temp HACK for demo, When fracture happens (physics state changes to dynamic) then switch the visible render meshes in a blueprint/actor from static meshes to geometry collections
void UGeometryCollectionComponent::SwitchRenderModels(const AActor* Actor)
{
// Don't touch visibility if the component is not visible
if (!IsVisible())
{
return;
}
TInlineComponentArray<UPrimitiveComponent*> PrimitiveComponents;
Actor->GetComponents(PrimitiveComponents);
for (UPrimitiveComponent* PrimitiveComponent : PrimitiveComponents)
{
bool ValidComponent = false;
if (UStaticMeshComponent* StaticMeshComp = Cast<UStaticMeshComponent>(PrimitiveComponent))
{
// unhacked.
//StaticMeshComp->SetVisibility(false);
}
else if (UGeometryCollectionComponent* GeometryCollectionComponent = Cast<UGeometryCollectionComponent>(PrimitiveComponent))
{
if (!GeometryCollectionComponent->IsVisible())
{
continue;
}
GeometryCollectionComponent->SetVisibility(true);
}
}
TInlineComponentArray<UChildActorComponent*> ChildActorComponents;
Actor->GetComponents(ChildActorComponents);
for (UChildActorComponent* ChildComponent : ChildActorComponents)
{
AActor* ChildActor = ChildComponent->GetChildActor();
if (ChildActor)
{
SwitchRenderModels(ChildActor);
}
}
}
#if GEOMETRYCOLLECTION_EDITOR_SELECTION
void UGeometryCollectionComponent::EnableTransformSelectionMode(bool bEnable)
{
// TODO: Support for Nanite?
bIsTransformSelectionModeEnabled = bEnable;
MarkRenderStateDirty();
}
#endif // #if GEOMETRYCOLLECTION_EDITOR_SELECTION
bool UGeometryCollectionComponent::IsEmbeddedGeometryValid() const
{
// Check that the array of ISMCs that implement embedded geometry matches RestCollection Exemplar array.
if (!RestCollection)
{
return false;
}
if (RestCollection->EmbeddedGeometryExemplar.Num() != EmbeddedGeometryComponents.Num())
{
return false;
}
for (int32 Idx = 0; Idx < EmbeddedGeometryComponents.Num(); ++Idx)
{
UStaticMesh* ExemplarStaticMesh = Cast<UStaticMesh>(RestCollection->EmbeddedGeometryExemplar[Idx].StaticMeshExemplar.TryLoad());
if (!ExemplarStaticMesh)
{
return false;
}
if (ExemplarStaticMesh != EmbeddedGeometryComponents[Idx]->GetStaticMesh())
{
return false;
}
}
return true;
}
void UGeometryCollectionComponent::ClearEmbeddedGeometry()
{
if (AActor* OwningActor = GetOwner())
{
TArray<UActorComponent*> TargetComponents;
OwningActor->GetComponents(TargetComponents, false);
for (UActorComponent* TargetComponent : TargetComponents)
{
if ((TargetComponent->GetOuter() == this) || !IsValidChecked(TargetComponent->GetOuter()))
{
if (UInstancedStaticMeshComponent* ISMComponent = Cast<UInstancedStaticMeshComponent>(TargetComponent))
{
ISMComponent->ClearInstances();
ISMComponent->DestroyComponent();
}
}
}
}
EmbeddedGeometryComponents.Empty();
}
void UGeometryCollectionComponent::InitializeEmbeddedGeometry()
{
if (RestCollection)
{
ClearEmbeddedGeometry();
if (AActor* ActorOwner = GetOwner())
{
// Construct an InstancedStaticMeshComponent for each exemplar
for (const FGeometryCollectionEmbeddedExemplar& Exemplar : RestCollection->EmbeddedGeometryExemplar)
{
if (UStaticMesh* ExemplarStaticMesh = Cast<UStaticMesh>(Exemplar.StaticMeshExemplar.TryLoad()))
{
if (UInstancedStaticMeshComponent* ISMC = NewObject<UInstancedStaticMeshComponent>(this))
{
ISMC->SetStaticMesh(ExemplarStaticMesh);
ISMC->SetCullDistances(Exemplar.StartCullDistance, Exemplar.EndCullDistance);
ISMC->SetCanEverAffectNavigation(false);
ISMC->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
ISMC->SetCastShadow(false);
ISMC->SetMobility(EComponentMobility::Stationary);
ISMC->SetupAttachment(this);
ActorOwner->AddInstanceComponent(ISMC);
ISMC->RegisterComponent();
EmbeddedGeometryComponents.Add(ISMC);
}
}
}
#if WITH_EDITOR
EmbeddedBoneMaps.SetNum(RestCollection->EmbeddedGeometryExemplar.Num());
EmbeddedInstanceIndex.Init(INDEX_NONE, RestCollection->GetGeometryCollection()->NumElements(FGeometryCollection::TransformGroup));
#endif
}
}
}
void UGeometryCollectionComponent::ForceBrokenForCustomRenderer(bool bForceBroken)
{
bForceBrokenForCustomRenderer = bForceBroken;
RefreshCustomRenderer();
}
void UGeometryCollectionComponent::SetRootProxyComponentSpaceTransform(int32 Index, const FTransform& RootProxyTransform)
{
SetRootProxyLocalTransform(Index, FTransform3f(RootProxyTransform));
RefreshRootProxies();
}
void UGeometryCollectionComponent::SetRootProxyLocalTransform(int32 Index, const FTransform3f& RootProxyTransform)
{
if (RootProxyLocalTransforms.IsEmpty() && RestCollection)
{
const int32 NumProxyMeshes = RestCollection->RootProxyData.ProxyMeshes.Num();
RootProxyLocalTransforms.Reserve(NumProxyMeshes);
RootProxyLocalTransforms.Append(RestCollection->RootProxyData.MeshTransforms);
for (int32 TransformIndex = RootProxyLocalTransforms.Num(); TransformIndex < NumProxyMeshes; ++TransformIndex)
{
RootProxyLocalTransforms.Add(FTransform3f::Identity);
}
}
if (RootProxyLocalTransforms.IsValidIndex(Index))
{
RootProxyLocalTransforms[Index] = RootProxyTransform;
}
}
void UGeometryCollectionComponent::ClearRootProxyLocalTransforms()
{
RootProxyLocalTransforms.Empty();
}
void UGeometryCollectionComponent::RefreshRootProxies()
{
RefreshCustomRenderer();
}
bool UGeometryCollectionComponent::IsCustomRendererAvailable() const
{
return bChaos_GC_UseCustomRenderer && CustomRenderer != nullptr && FApp::CanEverRender();
}
bool UGeometryCollectionComponent::IsUsingCustomRenderer() const
{
return IsCustomRendererAvailable() && !(bCustomRendererCanUseNativeFallback && bCustomRendererShouldUseNativeFallback) && !bForceNativeRenderer;
}
void UGeometryCollectionComponent::RefreshCustomRenderer()
{
if (RestCollection == nullptr || !IsCustomRendererAvailable())
{
return;
}
if (IGeometryCollectionExternalRenderInterface* RendererInterface = CustomRenderer.GetInterface())
{
bool bIsBroken = BrokenAndDecayedStates.GetIsRootBroken();
#if !(UE_BUILD_SHIPPING)
if (CVarNumToForceBreak->GetInt() >= 0)
{
bIsBroken = ForcedBroken.Break(this, ComponentSpaceTransforms.RequestAllTransforms());
}
#endif
const bool bRenderRootProxy = !bForceBrokenForCustomRenderer && !bIsBroken && (RestCollection->RootProxyData.ProxyMeshes.Num() > 0);
const AActor* Owner = GetOwner();
const bool bIsActorHidden = (Owner && bGeometryCollectionCustomRendererHiddenActorFix) ? Owner->IsHidden() : false;
uint32 StateFlags = 0;
StateFlags |= (bHiddenInGame || !IsVisible() || bIsActorHidden) ? 0 : IGeometryCollectionExternalRenderInterface::EState_Visible;
StateFlags |= bRenderRootProxy ? 0 : IGeometryCollectionExternalRenderInterface::EState_Broken;
StateFlags |= bForceBrokenForCustomRenderer ? IGeometryCollectionExternalRenderInterface::EState_ForcedBroken : 0;
// Test if we should change the native rendering fallback state before updating custom renderer.
const bool bShouldUseNativeFallback = bCustomRendererCanUseNativeFallback && RendererInterface->ShouldUseNativeFallback(StateFlags);
if (bCustomRendererShouldUseNativeFallback != bShouldUseNativeFallback)
{
bCustomRendererShouldUseNativeFallback = bShouldUseNativeFallback;
UnregisterCustomRenderer();
RegisterCustomRenderer();
MarkRenderStateDirty();
}
if (IsUsingCustomRenderer())
{
RendererInterface->UpdateState(*RestCollection, GetComponentTransform(), StateFlags);
if (bRenderRootProxy)
{
const FTransform CompSpaceRootTransform(ComponentSpaceTransforms.RequestRootTransform());
if (RootProxyLocalTransforms.IsEmpty())
{
if (RestCollection->RootProxyData.MeshTransforms.IsEmpty())
{
RendererInterface->UpdateRootTransform(*RestCollection, CompSpaceRootTransform);
}
else
{
RendererInterface->UpdateRootTransforms(*RestCollection, CompSpaceRootTransform, MakeArrayView(RestCollection->RootProxyData.MeshTransforms));
}
}
else
{
RendererInterface->UpdateRootTransforms(*RestCollection, CompSpaceRootTransform, MakeArrayView(RootProxyLocalTransforms));
}
}
else
{
const TArray<FTransform3f>& CompSpaceTransforms = ComponentSpaceTransforms.RequestAllTransforms();
RendererInterface->UpdateTransforms(*RestCollection, CompSpaceTransforms);
}
}
}
}
void UGeometryCollectionComponent::SetUseStaticMeshCollisionForTraces(const bool bInUseStaticMeshCollisionForTraces)
{
if (bUseStaticMeshCollisionForTraces != bInUseStaticMeshCollisionForTraces)
{
if (PhysicsProxy != nullptr)
{
PhysicsProxy->SetUseStaticMeshCollisionForTraces_External(bInUseStaticMeshCollisionForTraces);
}
bUseStaticMeshCollisionForTraces = bInUseStaticMeshCollisionForTraces;
}
}
struct FGeometryCollectionDecayContext
{
FGeometryCollectionDecayContext(FGeometryCollectionPhysicsProxy& PhysicsProxyIn, FGeometryCollectionDecayDynamicFacade& DecayFacadeIn)
: PhysicsProxy(PhysicsProxyIn)
, DecayFacade(DecayFacadeIn)
, DirtyDynamicCollection(false)
{}
FGeometryCollectionPhysicsProxy& PhysicsProxy;
FGeometryCollectionDecayDynamicFacade& DecayFacade;
bool DirtyDynamicCollection;
TArray<int32> ToDisable;
TArray<FGeometryCollectionItemIndex> ToCrumble;
void Process(FGeometryDynamicCollection& DynamicCollection)
{
if (DirtyDynamicCollection)
{
DynamicCollection.MakeDirty();
}
if (ToCrumble.Num())
{
PhysicsProxy.BreakClusters_External(MoveTemp(ToCrumble));
}
if (ToDisable.Num())
{
PhysicsProxy.DisableParticles_External(MoveTemp(ToDisable));
}
}
};
void UGeometryCollectionComponent::UpdateDecay(int32 TransformIdx, float UpdatedDecay, bool bUseClusterCrumbling, bool bHasDynamicInternalClusterParent, FGeometryCollectionDecayContext& ContextInOut)
{
float Decay = ContextInOut.DecayFacade.GetDecay(TransformIdx);
if (UpdatedDecay > Decay)
{
ContextInOut.DirtyDynamicCollection = true;
Decay = UpdatedDecay;
if (bUseClusterCrumbling)
{
if (bHasDynamicInternalClusterParent)
{
FGeometryCollectionItemIndex InternalClusterItemindex = ContextInOut.PhysicsProxy.GetInternalClusterParentItemIndex_External(TransformIdx);
if (InternalClusterItemindex.IsValid())
{
ContextInOut.ToCrumble.AddUnique(InternalClusterItemindex);
Decay = 0.0f;
// this is an internal cluster so we do not update the BrokenAndDecayedStates, the children will update their own state
}
}
else
{
ContextInOut.ToCrumble.AddUnique(FGeometryCollectionItemIndex::CreateTransformItemIndex(TransformIdx));
Decay = 0.0f;
BrokenAndDecayedStates.SetHasDecayed(TransformIdx);
}
}
else if (Decay >= 1.0f)
{
// Disable the particle if it has decayed the requisite time
Decay = 1.0f;
ContextInOut.ToDisable.Add(TransformIdx);
if (RestCollection && RestCollection->GetGeometryCollection())
{
BrokenAndDecayedStates.SetHasDecayedRecursive(TransformIdx, RestCollection->GetGeometryCollection()->Children.GetConstArray());
}
}
// push back Decay in the attribute
ContextInOut.DecayFacade.SetDecay(TransformIdx, Decay);
}
}
void UGeometryCollectionComponent::IncrementSleepTimer(float DeltaTime)
{
if (DeltaTime <= 0 || !RestCollection || !RestCollection->bRemoveOnMaxSleep || !bAllowRemovalOnSleep)
{
return;
}
// If a particle is sleeping, increment its sleep timer, otherwise reset it.
if (DynamicCollection && PhysicsProxy)
{
FGeometryCollectionRemoveOnSleepDynamicFacade RemoveOnSleepFacade(*DynamicCollection);
FGeometryCollectionDecayDynamicFacade DecayFacade(*DynamicCollection);
FGeometryCollectionDynamicStateFacade DynamicStateFacade(*DynamicCollection);
if (RemoveOnSleepFacade.IsValid()
&& DecayFacade.IsValid()
&& DynamicStateFacade.IsValid())
{
FGeometryCollectionDecayContext DecayContext(*PhysicsProxy, DecayFacade);
const TManagedArray<int32>& OriginalParents = RestCollection->GetGeometryCollection()->Parent;
const bool bAutomaticCrumblePartialClusters = RestCollection->bAutomaticCrumblePartialClusters;
const int32 NumTransforms = OriginalParents.Num();
for (int32 TransformIdx = 0; TransformIdx < NumTransforms; ++TransformIdx)
{
const bool HasInternalClusterParent = DynamicStateFacade.HasInternalClusterParent(TransformIdx);
if (HasInternalClusterParent)
{
// this children has an dynamic internal cluster parent so it can't be removed but we need tyo process the internal cluster by looking at the original parent properties
const int32 OriginalParentIdx = OriginalParents[TransformIdx];
const bool HasDynamicInternalClusterParent = DynamicStateFacade.HasDynamicInternalClusterParent(TransformIdx);
if (OriginalParentIdx > INDEX_NONE && HasDynamicInternalClusterParent && RemoveOnSleepFacade.IsRemovalActive(OriginalParentIdx))
{
const bool UseClusterCrumbling = bAutomaticCrumblePartialClusters;
const float UpdatedBreakDecay = UE_SMALL_NUMBER; // since we crumble we can only pass a timy number since this will be ignore ( but need to be >0 to ake sure Update Decay works properly )
UpdateDecay(TransformIdx, UpdatedBreakDecay, UseClusterCrumbling, HasDynamicInternalClusterParent, DecayContext);
}
}
else if (RemoveOnSleepFacade.IsRemovalActive(TransformIdx) && DynamicStateFacade.HasBrokenOff(TransformIdx))
{
// root bone should not be affected by remove on sleep
if (OriginalParents[TransformIdx] > INDEX_NONE)
{
// if decay has started we do not need to check slow moving or sleeping state anymore
bool ShouldUpdateTimer = (DecayFacade.GetDecay(TransformIdx) > 0);
if (!ShouldUpdateTimer && RestCollection->bSlowMovingAsSleeping)
{
const FVector3f CurrentPosition = DynamicCollection->GetTransform(TransformIdx).GetTranslation();
ShouldUpdateTimer |= RemoveOnSleepFacade.ComputeSlowMovingState(TransformIdx, FVector(CurrentPosition), DeltaTime, RestCollection->SlowMovingVelocityThreshold);
}
if (ShouldUpdateTimer || DynamicStateFacade.IsSleeping(TransformIdx))
{
RemoveOnSleepFacade.UpdateSleepTimer(TransformIdx, DeltaTime);
}
// update the decay and disable the particle when decay has completed
const float UpdatedDecay = RemoveOnSleepFacade.ComputeDecay(TransformIdx);
UpdateDecay(TransformIdx, UpdatedDecay, DynamicStateFacade.HasChildren(TransformIdx), false, DecayContext);
}
}
}
DecayContext.Process(*DynamicCollection);
}
}
}
void UGeometryCollectionComponent::IncrementBreakTimer(float DeltaTime)
{
if (DeltaTime <= 0 || !bAllowRemovalOnBreak)
{
return;
}
if (RestCollection && DynamicCollection && PhysicsProxy)
{
FGeometryCollectionRemoveOnBreakDynamicFacade RemoveOnBreakFacade(*DynamicCollection);
FGeometryCollectionDecayDynamicFacade DecayFacade(*DynamicCollection);
FGeometryCollectionDynamicStateFacade DynamicStateFacade(*DynamicCollection);
// if replication is on, client may not need to process this at all or only partially ( depending on the abandon cluster level )
const bool bIsReplicatedClient = GetIsReplicated() && PhysicsProxy->GetReplicationMode() == FGeometryCollectionPhysicsProxy::EReplicationMode::Client;
if (RemoveOnBreakFacade.IsValid()
&& DecayFacade.IsValid()
&& DynamicStateFacade.IsValid())
{
FGeometryCollectionDecayContext DecayContext(*PhysicsProxy, DecayFacade);
const TManagedArray<int32>& OriginalParents = RestCollection->GetGeometryCollection()->Parent;
const TManagedArrayAccessor<int32> InitialLevels = PhysicsProxy->GetPhysicsCollection().GetInitialLevels();
const int32 NumTransforms = OriginalParents.Num();
for (int32 TransformIdx = 0; TransformIdx < NumTransforms; ++TransformIdx)
{
const bool HasInternalClusterParent = DynamicStateFacade.HasInternalClusterParent(TransformIdx);
if (HasInternalClusterParent)
{
// this children has an internal cluster parent so it can't be removed but we need tyo process the internal cluster by looking at the original parent properties
const int32 OriginalParentIdx = OriginalParents[TransformIdx];
const bool HasDynamicInternalClusterParent = DynamicStateFacade.HasDynamicInternalClusterParent(TransformIdx);
if (OriginalParentIdx > INDEX_NONE && HasDynamicInternalClusterParent && RemoveOnBreakFacade.IsRemovalActive(OriginalParentIdx))
{
bool bIsAllowedClusterCrumbling = true;
if (bIsReplicatedClient && InitialLevels.IsValid() && (InitialLevels.Num() > 0))
{
if (!bEnableAbandonAfterLevel || InitialLevels[OriginalParentIdx] <= ReplicationAbandonAfterLevel)
{
bIsAllowedClusterCrumbling = false;
}
}
const bool UseClusterCrumbling = RemoveOnBreakFacade.UseClusterCrumbling(OriginalParentIdx);
if (!UseClusterCrumbling || bIsAllowedClusterCrumbling)
{
const float UpdatedBreakDecay = RemoveOnBreakFacade.UpdateBreakTimerAndComputeDecay(TransformIdx, DeltaTime);
UpdateDecay(TransformIdx, UpdatedBreakDecay, UseClusterCrumbling, HasDynamicInternalClusterParent, DecayContext);
}
}
}
else if (RemoveOnBreakFacade.IsRemovalActive(TransformIdx) && DynamicStateFacade.HasBrokenOff(TransformIdx))
{
bool bIsAllowedClusterCrumbling = true;
if (bIsReplicatedClient && InitialLevels.IsValid() && (InitialLevels.Num() > 0))
{
if (!bEnableAbandonAfterLevel || InitialLevels[TransformIdx] <= ReplicationAbandonAfterLevel)
{
bIsAllowedClusterCrumbling = false;
}
}
const bool UseClusterCrumbling = RemoveOnBreakFacade.UseClusterCrumbling(TransformIdx);
if (!UseClusterCrumbling || bIsAllowedClusterCrumbling)
{
const float UpdatedBreakDecay = RemoveOnBreakFacade.UpdateBreakTimerAndComputeDecay(TransformIdx, DeltaTime);
UpdateDecay(TransformIdx, UpdatedBreakDecay, UseClusterCrumbling, false, DecayContext);
}
}
}
DecayContext.Process(*DynamicCollection);
}
}
}
void UGeometryCollectionComponent::ApplyExternalStrain(int32 ItemIndex, const FVector& Location, float Radius, int32 PropagationDepth, float PropagationFactor, float Strain)
{
if (PhysicsProxy)
{
PhysicsProxy->ApplyExternalStrain_External(FGeometryCollectionItemIndex::CreateFromExistingItemIndex(ItemIndex), Location, Radius, PropagationDepth, PropagationFactor, Strain);
}
}
void UGeometryCollectionComponent::ApplyInternalStrain(int32 ItemIndex, const FVector& Location, float Radius, int32 PropagationDepth, float PropagationFactor, float Strain)
{
if (PhysicsProxy)
{
PhysicsProxy->ApplyInternalStrain_External(FGeometryCollectionItemIndex::CreateFromExistingItemIndex(ItemIndex), Location, Radius, PropagationDepth, PropagationFactor, Strain);
}
}
void UGeometryCollectionComponent::CrumbleCluster(int32 ItemIndex)
{
if (PhysicsProxy)
{
PhysicsProxy->BreakClusters_External({FGeometryCollectionItemIndex::CreateFromExistingItemIndex(ItemIndex)});
}
}
void UGeometryCollectionComponent::CrumbleActiveClusters()
{
if (PhysicsProxy)
{
PhysicsProxy->BreakActiveClusters_External();
}
}
void UGeometryCollectionComponent::SetAnchoredByIndex(int32 Index, bool bAnchored)
{
if (PhysicsProxy)
{
PhysicsProxy->SetAnchoredByIndex_External(Index, bAnchored);
}
}
void UGeometryCollectionComponent::SetAnchoredByBox(FBox WorldSpaceBox, bool bAnchored, int32 MaxLevel)
{
SetAnchoredByTransformedBox(WorldSpaceBox, FTransform::Identity, bAnchored, MaxLevel);
}
void UGeometryCollectionComponent::SetAnchoredByTransformedBox(FBox Box, FTransform Transform, bool bAnchored, int32 MaxLevel)
{
if (PhysicsProxy)
{
PhysicsProxy->SetAnchoredByTransformedBox_External(Box, Transform, bAnchored, MaxLevel);
}
}
void UGeometryCollectionComponent::RemoveAllAnchors()
{
if (PhysicsProxy)
{
PhysicsProxy->RemoveAllAnchors_External();
}
}
void UGeometryCollectionComponent::ApplyBreakingLinearVelocity(int32 ItemIndex, const FVector& LinearVelocity)
{
if (PhysicsProxy)
{
PhysicsProxy->ApplyBreakingLinearVelocity_External(FGeometryCollectionItemIndex::CreateFromExistingItemIndex(ItemIndex), LinearVelocity);
}
}
void UGeometryCollectionComponent::ApplyBreakingAngularVelocity(int32 ItemIndex, const FVector& AngularVelocity)
{
if (PhysicsProxy)
{
PhysicsProxy->ApplyBreakingAngularVelocity_External(FGeometryCollectionItemIndex::CreateFromExistingItemIndex(ItemIndex), AngularVelocity);
}
}
void UGeometryCollectionComponent::ApplyLinearVelocity(int32 ItemIndex, const FVector& LinearVelocity)
{
if (PhysicsProxy)
{
PhysicsProxy->ApplyLinearVelocity_External(FGeometryCollectionItemIndex::CreateFromExistingItemIndex(ItemIndex), LinearVelocity);
}
}
void UGeometryCollectionComponent::ApplyAngularVelocity(int32 ItemIndex, const FVector& AngularVelocity)
{
if (PhysicsProxy)
{
PhysicsProxy->ApplyAngularVelocity_External(FGeometryCollectionItemIndex::CreateFromExistingItemIndex(ItemIndex), AngularVelocity);
}
}
int32 UGeometryCollectionComponent::GetInitialLevel(int32 ItemIndex)
{
using FGeometryCollectionPtr = const TSharedPtr<FGeometryCollection, ESPMode::ThreadSafe>;
int32 Level = INDEX_NONE;
if (RestCollection && RestCollection->GetGeometryCollection())
{
const TManagedArray<int32>& Parent = RestCollection->GetGeometryCollection()->Parent;
int32 TransformIndex = INDEX_NONE;
FGeometryCollectionItemIndex GCItemIndex = FGeometryCollectionItemIndex::CreateFromExistingItemIndex(ItemIndex);
if (GCItemIndex.IsInternalCluster())
{
if (const TArray<int32>* Children = PhysicsProxy->FindInternalClusterChildrenTransformIndices_External(GCItemIndex))
{
if (!Children->IsEmpty())
{
// find the original cluster index from first children
TransformIndex = Parent[(*Children)[0]];
}
}
}
else
{
TransformIndex = GCItemIndex.GetTransformIndex();
}
// @todo(chaos) : use "Level" attribute when it will be properly serialized
// for now climb back the hierarchy
if (TransformIndex > INDEX_NONE)
{
Level = 0;
int32 ParentTransformIndex = Parent[TransformIndex];
while (ParentTransformIndex != INDEX_NONE)
{
++Level;
ParentTransformIndex = Parent[ParentTransformIndex];
}
}
}
return Level;
}
int32 UGeometryCollectionComponent::GetRootIndex() const
{
if (RestCollection)
{
return RestCollection->GetRootIndex();
}
return INDEX_NONE;
}
FTransform UGeometryCollectionComponent::GetRootInitialTransform() const
{
FTransform RootInitialTransform{ FTransform::Identity };
if (RestCollection && RestCollection->GetGeometryCollection())
{
const FGeometryCollection& RestGeometryCollection = *RestCollection->GetGeometryCollection();
const int32 RootIndex = RestCollection->GetRootIndex();
if (RestGeometryCollection.Transform.IsValidIndex(RootIndex))
{
const FTransform LocalSpaceTransform = GeometryCollectionAlgo::GlobalMatrix(RestGeometryCollection.Transform, RestGeometryCollection.Parent, RootIndex);
RootInitialTransform = LocalSpaceTransform * GetComponentTransform();
}
}
return RootInitialTransform;
}
FTransform UGeometryCollectionComponent::GetRootCurrentTransform() const
{
FTransform RootInitialTransform{ FTransform::Identity };
if (RestCollection)
{
const FTransform CompSpaceRootTransform(ComponentSpaceTransforms.RequestRootTransform());
RootInitialTransform = CompSpaceRootTransform * GetComponentTransform();
}
return RootInitialTransform;
}
FTransform UGeometryCollectionComponent::GetRootCurrentComponentSpaceTransform() const
{
return (RestCollection) ? FTransform(ComponentSpaceTransforms.RequestRootTransform()) : FTransform::Identity;
}
FTransform UGeometryCollectionComponent::GetRootParticleMassOffset() const
{
FTransform MassOffset{ FTransform::Identity };
if (RestCollection)
{
const int32 RootIndex = RestCollection->GetRootIndex();
if (const FGeometryCollection* RestGeometryCollection = RestCollection->GetGeometryCollection().Get())
{
const TManagedArray<FTransform>* MassToLocal = RestGeometryCollection->FindAttribute<FTransform>("MassToLocal", FGeometryCollection::TransformGroup);
if (MassToLocal && MassToLocal->IsValidIndex(RootIndex))
{
MassOffset = (*MassToLocal)[RootIndex];
}
}
}
return MassOffset;
}
TArray<FTransform> UGeometryCollectionComponent::GetInitialLocalRestTransforms() const
{
TArray<FTransform> InitialLocalTransforms;
if (RestCollection && RestCollection->GetGeometryCollection())
{
const FGeometryCollection& RestGeometryCollection = *RestCollection->GetGeometryCollection();
GeometryCollectionAlgo::GlobalMatrices(RestGeometryCollection.Transform, RestGeometryCollection.Parent, InitialLocalTransforms);
const TManagedArray<FTransform>* MassToLocal = RestGeometryCollection.FindAttribute<FTransform>("MassToLocal", FGeometryCollection::TransformGroup);
if (MassToLocal && InitialLocalTransforms.Num() == MassToLocal->Num())
{
for (int32 TransformIndex = 0; TransformIndex < InitialLocalTransforms.Num(); TransformIndex++)
{
InitialLocalTransforms[TransformIndex] = (*MassToLocal)[TransformIndex] * InitialLocalTransforms[TransformIndex];
}
}
}
return InitialLocalTransforms;
}
TArray<FTransform> UGeometryCollectionComponent::GetLocalRestTransforms(bool bInitialTransforms) const
{
TArray<FTransform> CurrentLocalTransforms;
if (RestCollection && RestCollection->GetGeometryCollection())
{
const FGeometryCollection& RestGeometryCollection = *RestCollection->GetGeometryCollection();
const bool bHasRestTransformOverride = (RestTransforms.Num() > 0) && (RestTransforms.Num() == RestGeometryCollection.Transform.Num());
if (bInitialTransforms || !bHasRestTransformOverride)
{
GeometryCollectionAlgo::GlobalMatrices(RestCollection->GetGeometryCollection()->Transform, RestGeometryCollection.Parent, CurrentLocalTransforms);
}
else
{
GeometryCollectionAlgo::GlobalMatrices(TManagedArray<FTransform>(RestTransforms), RestGeometryCollection.Parent, CurrentLocalTransforms);
}
const TManagedArray<FTransform>* MassToLocal = RestGeometryCollection.FindAttribute<FTransform>("MassToLocal", FGeometryCollection::TransformGroup);
if (MassToLocal && CurrentLocalTransforms.Num() == MassToLocal->Num())
{
for (int32 TransformIndex = 0; TransformIndex < CurrentLocalTransforms.Num(); TransformIndex++)
{
CurrentLocalTransforms[TransformIndex] = (*MassToLocal)[TransformIndex] * CurrentLocalTransforms[TransformIndex];
}
}
}
return CurrentLocalTransforms;
}
void UGeometryCollectionComponent::SetLocalRestTransforms(const TArray<FTransform>& NewTransforms, bool bOnlyLeaves)
{
if (RestCollection && RestCollection->GetGeometryCollection())
{
// get the current transform and will use this array to update
TArray<FTransform> LocalTransforms = GetInitialLocalRestTransforms();
if (LocalTransforms.Num() == NewTransforms.Num() && NewTransforms.Num() > 0)
{
FGeometryCollection& GeometryCollection = *RestCollection->GetGeometryCollection();
const TManagedArray<FTransform>* MassToLocal = GeometryCollection.FindAttribute<FTransform>("MassToLocal", FGeometryCollection::TransformGroup);
const int32 NumTransforms = GeometryCollection.Parent.Num();
TArray<int32> TransformsToProcess;
TransformsToProcess.Reserve(NumTransforms);
TransformsToProcess.Emplace(RestCollection->GetRootIndex());
for (int32 ProcessIndex = 0; ProcessIndex < TransformsToProcess.Num(); ProcessIndex++)
{
const int32 TransformIndex = TransformsToProcess[ProcessIndex];
const FTransform InvMassToLocal = MassToLocal ? (*MassToLocal)[TransformIndex].Inverse() : FTransform::Identity;
const bool bIsLeaf = (GeometryCollection.SimulationType[TransformIndex] == FGeometryCollection::ESimulationTypes::FST_Rigid);
const bool bNeedUpdate = !bOnlyLeaves || bIsLeaf;
if (bNeedUpdate)
{
LocalTransforms[TransformIndex] = InvMassToLocal * NewTransforms[TransformIndex];
// because we update top-down, the parent up-to-date transform is stored in LocalTransforms
const int32 ParentTransformIndex = GeometryCollection.Parent[TransformIndex];
if (ParentTransformIndex != INDEX_NONE)
{
LocalTransforms[TransformIndex] = LocalTransforms[TransformIndex].GetRelativeTransform(LocalTransforms[ParentTransformIndex]);
}
}
else
{
LocalTransforms[TransformIndex] = InvMassToLocal * LocalTransforms[TransformIndex];
}
for (const int32 ChildTransformIndex : GeometryCollection.Children[TransformIndex])
{
TransformsToProcess.Emplace(ChildTransformIndex);
}
}
// send the transforms for update
SetRestState(MoveTemp(LocalTransforms));
}
}
}
TArray<FMatrix> UGeometryCollectionComponent::ComputeGlobalMatricesFromComponentSpaceTransforms() const
{
TArray<FMatrix> ComponentSpaceMatrices;
ComponentSpaceMatrices.SetNumUninitialized(ComponentSpaceTransforms.Num());
const TArray<FTransform3f>& CompSpaceTransforms = ComponentSpaceTransforms.RequestAllTransforms();
for (int32 TransformIndex = 0; TransformIndex < CompSpaceTransforms.Num(); TransformIndex++)
{
ComponentSpaceMatrices[TransformIndex] = FTransform(CompSpaceTransforms[TransformIndex]).ToMatrixWithScale();
}
return ComponentSpaceMatrices;
}
void UGeometryCollectionComponent::GetMassAndExtents(int32 ItemIndex, float& OutMass, FBox& OutExtents)
{
using FGeometryCollectionPtr = const TSharedPtr<FGeometryCollection, ESPMode::ThreadSafe>;
static FName BoundingBoxAttributeName = "BoundingBox";
static FName MassAttributeName = "Mass";
OutMass = 0.0f;
OutExtents = FBox(EForceInit::ForceInitToZero);
if (RestCollection && RestCollection->GetGeometryCollection())
{
const FGeometryCollection& Collection = *RestCollection->GetGeometryCollection();
if (const TManagedArray<float>* CollectionMass = Collection.FindAttribute<float>(MassAttributeName, FTransformCollection::TransformGroup))
{
const TManagedArray<FBox>* TransformBoundingBoxes = Collection.FindAttribute<FBox>(BoundingBoxAttributeName, FTransformCollection::TransformGroup);
const TManagedArray<FBox>* GeoBoundingBoxes = Collection.FindAttribute<FBox>(BoundingBoxAttributeName, FGeometryCollection::GeometryGroup);
const FGeometryCollectionItemIndex GCItemIndex = FGeometryCollectionItemIndex::CreateFromExistingItemIndex(ItemIndex);
if (!ensure(CollectionMass->IsValidIndex(GCItemIndex.GetTransformIndex())))
{
return;
}
if (GCItemIndex.IsInternalCluster())
{
const TArray<int32>* Children = PhysicsProxy->FindInternalClusterChildrenTransformIndices_External(GCItemIndex);
if (Children)
{
for (const int32 ChildTramsformIndex : *Children)
{
OutMass += (*CollectionMass)[ChildTramsformIndex];
if (TransformBoundingBoxes)
{
OutExtents += (*TransformBoundingBoxes)[ChildTramsformIndex];
}
else if (GeoBoundingBoxes)
{
const int32 GeometryIndex = Collection.TransformToGeometryIndex[ChildTramsformIndex];
if (GeoBoundingBoxes->IsValidIndex(GeometryIndex))
{
OutExtents += (*GeoBoundingBoxes)[GeometryIndex];
}
}
}
}
}
else
{
const int32 TransformIndex = GCItemIndex.GetTransformIndex();
OutMass = (*CollectionMass)[TransformIndex];
if (TransformBoundingBoxes)
{
OutExtents = (*TransformBoundingBoxes)[TransformIndex];
}
else if (GeoBoundingBoxes)
{
const int32 GeometryIndex = Collection.TransformToGeometryIndex[TransformIndex];
if (GeoBoundingBoxes->IsValidIndex(GeometryIndex))
{
OutExtents = (*GeoBoundingBoxes)[GeometryIndex];
}
}
}
}
}
}
float UGeometryCollectionComponent::ComputeMassScaleRelativeToAsset() const
{
// note this only acount for material override density , in the future we could certainly take in account the scale of the component
float MassScaleMultiplier = 1.0f;
UPhysicalMaterial* EnginePhysicalMaterial = GetPhysicalMaterial();
if (ensure(EnginePhysicalMaterial))
{
if (RestCollection && bDensityFromPhysicsMaterial)
{
bool bMassAsDensity = false;
const float AssetMassOrDensity = RestCollection->GetMassOrDensity(bMassAsDensity);
if (ensureMsgf(bMassAsDensity, TEXT("Need a density to be able to compute the mass scale multiplier, this may result in incorrect mass properties")))
{
if (ensureMsgf(AssetMassOrDensity > SMALL_NUMBER, TEXT("Asset density is set to a too small number, ignoring it for mass adjustment")))
{
const float OverrideMaterialDensity = Chaos::GCm3ToKgCm3(EnginePhysicalMaterial->Density);
MassScaleMultiplier = OverrideMaterialDensity / AssetMassOrDensity;
}
}
}
}
return MassScaleMultiplier;
}
float UGeometryCollectionComponent::GetMass() const
{
float OutMass{ 0 };
if (RestCollection && RestCollection->GetGeometryCollection())
{
const FGeometryCollection& Collection = *RestCollection->GetGeometryCollection();
if (const TManagedArray<float>* CollectionMass = Collection.FindAttribute<float>(TEXT("Mass"), FTransformCollection::TransformGroup))
{
const int32 RootIndex = RestCollection->GetRootIndex();
if (CollectionMass->IsValidIndex(RootIndex))
{
OutMass = (*CollectionMass)[RootIndex];
}
}
}
// need to multiply by the instance specific mass scale is any
return OutMass * ComputeMassScaleRelativeToAsset();
}
float UGeometryCollectionComponent::CalculateMass(FName BoneName)
{
float OutMass{ 0 };
if (RestCollection && RestCollection->GetGeometryCollection())
{
const FGeometryCollection& Collection = *RestCollection->GetGeometryCollection();
if (const TManagedArray<float>* CollectionMass = Collection.FindAttribute<float>(TEXT("Mass"), FTransformCollection::TransformGroup))
{
const int32 BoneIndex = (BoneName == NAME_None)? RestCollection->GetRootIndex(): Collection.BoneName.Find(BoneName.ToString());
if (CollectionMass->IsValidIndex(BoneIndex))
{
OutMass = (*CollectionMass)[BoneIndex];
}
}
}
// need to multiply by the instance specific mass scale is any
return OutMass * ComputeMassScaleRelativeToAsset();
}
bool UGeometryCollectionComponent::CalculateInnerSphere(int32 TransformIndex, UE::Math::TSphere<double>& SphereOut) const
{
// Approximates the inscribed sphere. Returns false if no such sphere exists, if for instance the index is to an embedded geometry.
const TManagedArray<int32>& TransformToGeometryIndex = RestCollection->GetGeometryCollection()->TransformToGeometryIndex;
const TManagedArray<TSet<int32>>& Children = RestCollection->GetGeometryCollection()->Children;
const TManagedArray<FTransform>& MassToLocal = RestCollection->GetGeometryCollection()->GetAttribute<FTransform>("MassToLocal", FGeometryCollection::TransformGroup);
TManagedArrayAccessor<Chaos::FRealSingle> InnerRadiusAttribute(*RestCollection->GetGeometryCollection(), "InnerRadius", FGeometryCollection::GeometryGroup);
if (InnerRadiusAttribute.IsValid() && InnerRadiusAttribute.IsValidIndex(TransformIndex))
{
if (RestCollection->GetGeometryCollection()->IsRigid(TransformIndex))
{
// Sphere in component space, centered on body's COM.
FVector COM = MassToLocal[TransformIndex].GetLocation();
SphereOut = UE::Math::TSphere<double>(COM, InnerRadiusAttribute[TransformToGeometryIndex[TransformIndex]]);
return true;
}
else if (RestCollection->GetGeometryCollection()->IsClustered(TransformIndex))
{
// Recursively accumulate the cluster's child spheres.
bool bSphereFound = false;
for (int32 ChildIndex : Children[TransformIndex])
{
UE::Math::TSphere<double> LocalSphere;
if (CalculateInnerSphere(ChildIndex, LocalSphere))
{
if (!bSphereFound)
{
bSphereFound = true;
SphereOut = LocalSphere;
}
else
{
SphereOut += LocalSphere;
}
}
}
return bSphereFound;
}
}
// Likely an embedded geometry or missing inner radius attribute , which doesn't count towards volume.
return false;
}
void UGeometryCollectionComponent::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
Ar.UsingCustomVersion(FFortniteValkyrieBranchObjectVersion::GUID);
#if WITH_EDITOR
// Fix rest transforms
// Prior to this change, it is possible for the RestTransforms to be set with the original rest collection transforms or have outdated rest transforms
// this is a waste of memory and performance cost with serialization at runtime since FTransform can not be bulk serialized
// so if they are no longer matching the rest collection let's clear them
if (Ar.IsLoading() && RestTransforms.Num() > 0)
{
if (Ar.CustomVer(FFortniteValkyrieBranchObjectVersion::GUID) < FFortniteValkyrieBranchObjectVersion::FixRestTransformsInGeometryCollectionComponent)
{
bool bNeedToRestRestTransform = false;
bool bIsRestCollectionValid = (RestCollection && RestCollection->GetGeometryCollection());
if (!bIsRestCollectionValid || RestTransforms.Num() != RestCollection->GetGeometryCollection()->Transform.Num())
{
bNeedToRestRestTransform = true;
}
else
{
const TArray<FTransform3f>& RestCollectionTransforms = RestCollection->GetGeometryCollection()->Transform.GetConstArray();
for (int32 Index = 0; Index < RestTransforms.Num(); Index++)
{
// Do a float comparison otherwise, changing from float to double coulld cause transforms to be different
if (!FTransform3f(RestTransforms[Index]).Equals(RestCollectionTransforms[Index]))
{
bNeedToRestRestTransform = true;
break;
}
}
}
if (bNeedToRestRestTransform)
{
RestTransforms.Reset();
}
}
}
#endif
}
void UGeometryCollectionComponent::PostLoad()
{
Super::PostLoad();
// If there is a rest collection and no custom renderer then precache PSOs required to render the mesh
if (RestCollection && !IsCustomRendererAvailable())
{
PrecachePSOs();
}
//
// The UGeometryCollectionComponent::PhysicalMaterial_DEPRECATED needs
// to be transferred to the BodyInstance simple material. Going forward
// the deprecated value will not be saved.
//
if (PhysicalMaterialOverride_DEPRECATED)
{
BodyInstance.SetPhysMaterialOverride(PhysicalMaterialOverride_DEPRECATED.Get());
PhysicalMaterialOverride_DEPRECATED = nullptr;
}
// Convert deprecated ISMPool and bAutoAssignISMPool settings to use the a CustomRendererType.
if ((ISMPool_DEPRECATED || bAutoAssignISMPool_DEPRECATED) && !(bOverrideCustomRenderer || CustomRendererType))
{
bOverrideCustomRenderer = true;
CustomRendererType = UGeometryCollectionISMPoolRenderer::StaticClass();
ISMPool_DEPRECATED = nullptr;
bAutoAssignISMPool_DEPRECATED = false;
}
}
void UGeometryCollectionComponent::CollectPSOPrecacheData(const FPSOPrecacheParams& BasePrecachePSOParams, FMaterialInterfacePSOPrecacheParamsList& OutParams)
{
if (!RestCollection)
{
return;
}
FPSOPrecacheVertexFactoryDataList VFDataList;
const FVertexFactoryType* VFType = nullptr;
if (UseNanite(GMaxRHIShaderPlatform) &&
RestCollection->EnableNanite &&
RestCollection->HasNaniteData() &&
GGeometryCollectionNanite != 0)
{
VFDataList.Add(FPSOPrecacheVertexFactoryData(&FNaniteVertexFactory::StaticType));
}
else if (RestCollection->HasMeshData())
{
VFDataList.Add(FPSOPrecacheVertexFactoryData(&FGeometryCollectionVertexFactory::StaticType));
}
const TManagedArray<FGeometryCollectionSection>& SectionsArray = RestCollection->GetGeometryCollection()->Sections;
for (int32 SectionIndex = 0; SectionIndex < SectionsArray.Num(); ++SectionIndex)
{
const FGeometryCollectionSection& MeshSection = SectionsArray[SectionIndex];
const bool bValidMeshSection = MeshSection.MaterialID != INDEX_NONE;
UMaterialInterface* MaterialInterface = bValidMeshSection ? GetMaterial(MeshSection.MaterialID) : UMaterial::GetDefaultMaterial(MD_Surface);
FMaterialInterfacePSOPrecacheParams& ComponentParams = OutParams[OutParams.AddDefaulted()];
ComponentParams.Priority = EPSOPrecachePriority::Medium;
ComponentParams.MaterialInterface = MaterialInterface;
ComponentParams.VertexFactoryDataList = VFDataList;
ComponentParams.PSOPrecacheParams = BasePrecachePSOParams;
}
}
void UGeometryCollectionComponent::SetPhysMaterialOverride(UPhysicalMaterial* NewPhysMaterial)
{
Super::SetPhysMaterialOverride(NewPhysMaterial);
if (PhysicsProxy)
{
UPhysicalMaterial* EnginePhysicalMaterial = GetPhysicalMaterial();
if (ensure(EnginePhysicalMaterial))
{
PhysicsProxy->SetPhysicsMaterial_External(EnginePhysicalMaterial->GetPhysicsMaterial());
}
}
}
void UGeometryCollectionComponent::FlushNetDormancyIfNeeded() const
{
if (GetDesiredNetAwakeningMode() != ENetAwakeningMode::FlushNetDormancy)
{
return;
}
if (AActor* Owner = GetOwner())
{
Owner->FlushNetDormancy();
}
}
UGeometryCollectionComponent::ENetAwakeningMode UGeometryCollectionComponent::GetDesiredNetAwakeningMode() const
{
constexpr int32 MinNetAwakeningMode = 0;
constexpr int32 MaxNetAwakeningMode = 1;
ENetAwakeningMode Mode;
if (GeometryCollectionNetAwakeningMode < MinNetAwakeningMode || GeometryCollectionNetAwakeningMode > MaxNetAwakeningMode)
{
ensureMsgf(false, TEXT("Invalid NetAwakening mode configured, falling back to the default mode"));
Mode = ENetAwakeningMode::ForceDormancyAwake;
}
else
{
Mode = static_cast<ENetAwakeningMode>(GeometryCollectionNetAwakeningMode);
}
return Mode;
}
Chaos::FPhysicsObject* UGeometryCollectionComponent::GetPhysicsObjectById(Chaos::FPhysicsObjectId Id) const
{
if (!PhysicsProxy)
{
return nullptr;
}
return PhysicsProxy->GetPhysicsObjectByIndex(Id);
}
Chaos::FPhysicsObject* UGeometryCollectionComponent::GetPhysicsObjectByName(const FName& Name) const
{
if (!RestCollection)
{
return nullptr;
}
if (Name == NAME_None)
{
// Special case where it's more convenient for us to return the root bone instead.
TArray<int32> Roots;
FGeometryCollectionClusteringUtility::GetRootBones(RestCollection->GetGeometryCollection().Get(), Roots);
if (Roots.IsEmpty())
{
return nullptr;
}
// More convenient just to assume there's one root for this special case here.
return GetPhysicsObjectById(Roots[0]);
}
const int32 Index = RestCollection->GetGeometryCollection()->BoneName.Find(Name.ToString());
return GetPhysicsObjectById(Index);
}
TArray<Chaos::FPhysicsObject*> UGeometryCollectionComponent::GetAllPhysicsObjects() const
{
if (!PhysicsProxy)
{
return {};
}
TArray<Chaos::FPhysicsObject*> Objects;
Objects.Reserve(PhysicsProxy->GetNumTransforms());
for (int32 Index = 0; Index < PhysicsProxy->GetNumTransforms(); ++Index)
{
Objects.Add(GetPhysicsObjectById(Index));
}
return Objects;
}
Chaos::FPhysicsObjectId UGeometryCollectionComponent::GetIdFromGTParticle(Chaos::FGeometryParticle* Particle) const
{
if (!PhysicsProxy || !Particle)
{
return INDEX_NONE;
}
FGeometryCollectionItemIndex Index = PhysicsProxy->GetItemIndexFromGTParticleNoInternalCluster_External(Particle->CastToRigidParticle());
if (Index.IsValid())
{
return Index.GetTransformIndex();
}
else
{
return INDEX_NONE;
}
}
void UGeometryCollectionComponent::SetEnableDamageFromCollision(bool bValue)
{
bEnableDamageFromCollision = bValue;
if (PhysicsProxy)
{
PhysicsProxy->SetEnableDamageFromCollision_External(bValue);
}
}
void UGeometryCollectionComponent::OnComponentCollisionSettingsChanged(bool bUpdateOverlaps)
{
Super::OnComponentCollisionSettingsChanged(bUpdateOverlaps);
BuildInitialFilterData();
LoadCollisionProfiles();
}
bool UGeometryCollectionComponent::CanBeUsedInPhysicsReplication(const FName BoneName) const
{
return false;
}
const FTransform& UGeometryCollectionComponent::GetPreviousComponentToWorld() const
{
if (!PhysicsProxy)
{
return FTransform::Identity;
}
return PhysicsProxy->GetPreviousWorldTransform_External();
}
#if WITH_EDITOR
bool UGeometryCollectionComponent::IsHLODRelevant() const
{
if (!CanBeHLODRelevant(this))
{
return false;
}
if (RestCollection == nullptr || !IsCustomRendererAvailable())
{
return false;
}
if (!IsVisible())
{
return false;
}
#if WITH_EDITORONLY_DATA
if (!bEnableAutoLODGeneration)
{
return false;
}
#endif
if (GetHLODProxyComponents().IsEmpty())
{
return false;
}
return true;
}
TArray<UActorComponent*> UGeometryCollectionComponent::GetHLODProxyComponents() const
{
TArray<UActorComponent*> HLODProxyComponents;
ForEachObjectWithOuter(this, [&HLODProxyComponents](UObject* InObject)
{
if (UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(InObject))
{
HLODProxyComponents.Add(StaticMeshComponent);
}
});
return HLODProxyComponents;
}
#endif // #if WITH_EDITOR
void UGeometryCollectionComponent::RebaseDynamicCollectionTransformsOnNewWorldTransform()
{
if (!PhysicsProxy)
{
return;
}
PhysicsProxy->RebaseAllGameThreadCollectionTransformsOnNewWorldTransform_External();
ComponentSpaceTransforms.MarkDirty();
}