// Copyright Epic Games, Inc. All Rights Reserved. #include "GeometryCollection/GeometryCollectionComponent.h" #include "AI/NavigationSystemHelpers.h" #include "Async/ParallelFor.h" #include "Chaos/ChaosPhysicalMaterial.h" #include "ChaosSolversModule.h" #include "ChaosStats.h" #include "ComponentRecreateRenderStateContext.h" #include "Components/BoxComponent.h" #include "Engine/Engine.h" #include "Engine/InstancedStaticMesh.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/GeometryCollectionObject.h" #include "GeometryCollection/GeometryCollectionProximityUtility.h" #include "GeometryCollection/GeometryCollectionSQAccelerator.h" #include "GeometryCollection/GeometryCollectionSceneProxy.h" #include "GeometryCollection/GeometryCollectionUtility.h" #include "GeometryCollection/GeometryCollectionISMPoolActor.h" #include "GeometryCollection/GeometryCollectionISMPoolComponent.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/PhysicsFiltering.h" #include "PhysicsField/PhysicsFieldComponent.h" #include "PhysicsProxy/GeometryCollectionPhysicsProxy.h" #include "PhysicsSolver.h" #include "Algo/RemoveIf.h" #if WITH_EDITOR #include "AssetToolsModule.h" #include "Editor.h" #endif #include "PhysicsEngine/BodySetup.h" #include "PhysicsEngine/BodyInstance.h" #include "Chaos/ChaosGameplayEventDispatcher.h" #include "Rendering/NaniteResources.h" #include "PrimitiveSceneInfo.h" #include "GeometryCollection/GeometryCollectionEngineRemoval.h" #include "GeometryCollection/Facades/CollectionAnchoringFacade.h" #include "GeometryCollection/Facades/CollectionRemoveOnBreakFacade.h" #include "GeometryCollection/Facades/CollectionInstancedMeshFacade.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 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_CacheComponentSpaceBounds = true; FAutoConsoleVariableRef CVarChaosGCCacheComponentSpaceBounds(TEXT("p.Chaos.GC.CacheComponentSpaceBounds"), bChaos_GC_CacheComponentSpaceBounds, TEXT("Cache component space bounds for performance")); bool bChaos_GC_UseISMPool = true; FAutoConsoleVariableRef CVarChaosGCUseISMPool(TEXT("p.Chaos.GC.UseISMPool"), bChaos_GC_UseISMPool, TEXT("When enabled, use the ISM pool if specified")); bool bChaos_GC_UseISMPoolForNonFracturedParts = true; FAutoConsoleVariableRef CVarChaosGCUseISMPoolForNonFracturedParts(TEXT("p.Chaos.GC.UseISMPoolForNonFracturedParts"), bChaos_GC_UseISMPoolForNonFracturedParts, TEXT("When enabled, non fractured part will use the ISM pool 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 ")); DEFINE_LOG_CATEGORY_STATIC(UGCC_LOG, Error, All); extern FGeometryCollectionDynamicDataPool GDynamicDataPool; 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::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) { bOutSuccess = true; Ar << Version; Ar << OneOffActivated; Ar << ServerFrame; int32 NumClusters = Clusters.Num(); Ar << NumClusters; if(Ar.IsLoading()) { Clusters.SetNum(NumClusters); } 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; } 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]")); 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& UGeometryCollectionComponent::Get##Name##Array() const \ { \ return Indirect##Name##Array ? \ *Indirect##Name##Array : RestCollection->GetGeometryCollection()->Name; \ } \ TManagedArray& UGeometryCollectionComponent::Get##Name##ArrayCopyOnWrite() \ { \ if(!Indirect##Name##Array) \ { \ static FName StaticName(#Name); \ DynamicCollection->AddAttribute(StaticName, Group); \ DynamicCollection->CopyAttribute( \ *RestCollection->GetGeometryCollection(), StaticName, Group); \ Indirect##Name##Array = \ &DynamicCollection->ModifyAttribute(StaticName, Group); \ CopyOnWriteAttributeList.Add( \ reinterpret_cast(&Indirect##Name##Array)); \ } \ return *Indirect##Name##Array; \ } \ void UGeometryCollectionComponent::Reset##Name##ArrayDynamic() \ { \ Indirect##Name##Array = NULL; \ } \ const TManagedArray& UGeometryCollectionComponent::Get##Name##ArrayRest() const \ { \ return RestCollection->GetGeometryCollection()->Name; \ } \ // Define the methods COPY_ON_WRITE_ATTRIBUTES UGeometryCollectionComponent::UGeometryCollectionComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , ChaosSolverActor(nullptr) , InitializationState(ESimulationInitializationState::Unintialized) , ObjectType(EObjectStateTypeEnum::Chaos_Object_Dynamic) , bForceMotionBlur() , EnableClustering(true) , ClusterGroupIndex(0) , MaxClusterLevel(100) , DamageThreshold({ 500000.f, 50000.f, 5000.f }) , bUseSizeSpecificDamageThreshold(false) , 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) , bStoreVelocities(false) , bShowBoneColors(false) #if WITH_EDITORONLY_DATA , bEnableRunTimeDataCollection(false) , RunTimeDataCollectionGuid(FGuid::NewGuid()) #endif , bEnableReplication(false) , bEnableAbandonAfterLevel(true) , ReplicationAbandonClusterLevel_DEPRECATED(0) , ReplicationAbandonAfterLevel(0) , bRenderStateDirty(true) , bEnableBoneSelection(false) , ViewLevel(-1) , NavmeshInvalidationTimeSliceIndex(0) , IsObjectDynamic(false) , IsObjectLoading(true) , PhysicsProxy(nullptr) #if WITH_EDITOR && WITH_EDITORONLY_DATA , EditorActor(nullptr) #endif #if GEOMETRYCOLLECTION_EDITOR_SELECTION , bIsTransformSelectionModeEnabled(false) #endif // #if GEOMETRYCOLLECTION_EDITOR_SELECTION , bIsMoving(false) { PrimaryComponentTick.bCanEverTick = true; bTickInEditor = true; bAutoActivate = 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; EventDispatcher = ObjectInitializer.CreateDefaultSubobject(this, TEXT("GameplayEventDispatcher")); DynamicCollection = nullptr; bHasCustomNavigableGeometry = EHasCustomNavigableGeometry::Yes; bWantsInitializeComponent = true; // make sure older asset are using the default behaviour DamagePropagationData.bEnabled = false; } 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; // we only enable ISM if we are playing ( not in editing mode because of various side effect like selection ) RegisterToISMPool(); } 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()); #endif UnregisterFromISMPool(); Super::EndPlay(ReasonEnd); CurrentCacheTime = MAX_flt; } void UGeometryCollectionComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); /* FDoRepLifetimeParams Params; Params.bIsPushBased = true; Params.RepNotifyCondition = REPNOTIFY_OnChanged; DOREPLIFETIME_WITH_PARAMS_FAST(UGeometryCollectionComponent, RepData, Params);*/ DOREPLIFETIME(UGeometryCollectionComponent, RepData); } namespace { void UpdateGlobalMatricesWithExplodedVectors(TArray& GlobalMatricesIn, FGeometryCollection& GeometryCollection) { int32 NumMatrices = GlobalMatricesIn.Num(); if (GlobalMatricesIn.Num() > 0) { if (GeometryCollection.HasAttribute("ExplodedVector", FGeometryCollection::TransformGroup)) { const TManagedArray& ExplodedVectors = GeometryCollection.GetAttribute("ExplodedVector", FGeometryCollection::TransformGroup); check(NumMatrices == ExplodedVectors.Num()); for (int32 tt = 0, nt = NumMatrices; tt < nt; ++tt) { GlobalMatricesIn[tt] = GlobalMatricesIn[tt].ConcatTranslation((FVector)ExplodedVectors[tt]); } } } } } FBox UGeometryCollectionComponent::ComputeBounds(const FMatrix& LocalToWorldWithScale) const { FBox BoundingBox(ForceInit); if (RestCollection) { //Hold on to reference so it doesn't get GC'ed auto HackGeometryCollectionPtr = RestCollection->GetGeometryCollection(); const TManagedArray& BoundingBoxes = GetBoundingBoxArray(); const TManagedArray& TransformIndices = GetTransformIndexArray(); const TManagedArray& ParentIndices = GetParentArray(); const TManagedArray& TransformToGeometryIndex = GetTransformToGeometryIndexArray(); const TManagedArray& Transforms = GetTransformArray(); const int32 NumBoxes = BoundingBoxes.Num(); int32 NumElements = HackGeometryCollectionPtr->NumElements(FGeometryCollection::TransformGroup); if (RestCollection->EnableNanite && HackGeometryCollectionPtr->HasAttribute("BoundingBox", FGeometryCollection::TransformGroup) && NumElements) { TArray TmpGlobalMatrices; GeometryCollectionAlgo::GlobalMatrices(Transforms, ParentIndices, TmpGlobalMatrices); TManagedArray& TransformBounds = HackGeometryCollectionPtr->ModifyAttribute("BoundingBox", "Transform"); for (int32 TransformIndex = 0; TransformIndex < HackGeometryCollectionPtr->NumElements(FGeometryCollection::TransformGroup); TransformIndex++) { BoundingBox += TransformBounds[TransformIndex].TransformBy(TmpGlobalMatrices[TransformIndex] * LocalToWorldWithScale); } } else if (NumElements == 0 || GlobalMatrices.Num() != RestCollection->NumElements(FGeometryCollection::TransformGroup)) { // #todo(dmp): we could do the bbox transform in parallel with a bit of reformulating // #todo(dmp): there are some cases where the calcbounds function is called before the component // has set the global matrices cache while in the editor. This is a somewhat weak guard against this // to default to just calculating tmp global matrices. This should be removed or modified somehow // such that we always cache the global matrices and this method always does the correct behavior TArray TmpGlobalMatrices; GeometryCollectionAlgo::GlobalMatrices(Transforms, ParentIndices, TmpGlobalMatrices); if (TmpGlobalMatrices.Num() == 0) { BoundingBox = FBox(ForceInitToZero); } else { UpdateGlobalMatricesWithExplodedVectors(TmpGlobalMatrices, *(RestCollection->GetGeometryCollection())); for (int32 BoxIdx = 0; BoxIdx < NumBoxes; ++BoxIdx) { const int32 TransformIndex = TransformIndices[BoxIdx]; if(RestCollection->GetGeometryCollection()->IsGeometry(TransformIndex)) { BoundingBox += BoundingBoxes[BoxIdx].TransformBy(TmpGlobalMatrices[TransformIndex] * LocalToWorldWithScale); } } } } else if (bGeometryCollectionSingleThreadedBoundsCalculation) { CHAOS_ENSURE(false); // this is slower and only enabled through a pvar debugging, disable bGeometryCollectionSingleThreadedBoundsCalculation in a production environment. for (int32 BoxIdx = 0; BoxIdx < NumBoxes; ++BoxIdx) { const int32 TransformIndex = TransformIndices[BoxIdx]; if (RestCollection->GetGeometryCollection()->IsGeometry(TransformIndex)) { BoundingBox += BoundingBoxes[BoxIdx].TransformBy(GlobalMatrices[TransformIndex] * LocalToWorldWithScale); } } } else { if (bChaos_BoxCalcBounds_ISPC_Enabled) { #if INTEL_ISPC ispc::BoxCalcBounds( (int32*)&TransformToGeometryIndex[0], (int32*)&TransformIndices[0], (ispc::FMatrix*)&GlobalMatrices[0], (ispc::FBox*)&BoundingBoxes[0], (ispc::FMatrix&)LocalToWorldWithScale, (ispc::FBox&)BoundingBox, NumBoxes); #endif } else { for (int32 BoxIdx = 0; BoxIdx < NumBoxes; ++BoxIdx) { const int32 TransformIndex = TransformIndices[BoxIdx]; if (RestCollection->GetGeometryCollection()->IsGeometry(TransformIndex)) { BoundingBox += BoundingBoxes[BoxIdx].TransformBy(GlobalMatrices[TransformIndex] * LocalToWorldWithScale); } } } } } return BoundingBox; } FBoxSphereBounds UGeometryCollectionComponent::CalcBounds(const FTransform& LocalToWorldIn) const { SCOPE_CYCLE_COUNTER(STAT_GCCUpdateBounds); // #todo(dmp): hack to make bounds calculation work when we don't have valid physics proxy data. This will // force bounds calculation. const FGeometryCollectionResults* Results = PhysicsProxy ? PhysicsProxy->GetConsumerResultsGT() : nullptr; const int32 NumTransforms = Results ? Results->GlobalTransforms.Num() : 0; if (bChaos_GC_CacheComponentSpaceBounds) { bool NeedBoundsUpdate = false; NeedBoundsUpdate |= (ComponentSpaceBounds.GetSphere().W < 1e-5); NeedBoundsUpdate |= CachePlayback; NeedBoundsUpdate |= (NumTransforms > 0); NeedBoundsUpdate |= (DynamicCollection && DynamicCollection->IsDirty()); if (NeedBoundsUpdate) { ComponentSpaceBounds = ComputeBounds(FMatrix::Identity); } else { NeedBoundsUpdate = false; } return ComponentSpaceBounds.TransformBy(LocalToWorldIn); } const FMatrix LocalToWorldWithScale = LocalToWorldIn.ToMatrixWithScale(); return FBoxSphereBounds(ComputeBounds(LocalToWorldWithScale)); } 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::UpdateCachedBounds() { ComponentSpaceBounds = ComputeBounds(FMatrix::Identity); CalculateLocalBounds(); UpdateBounds(); } 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) { if (UseNanite(GetScene()->GetShaderPlatform()) && RestCollection->EnableNanite && RestCollection->NaniteData != nullptr && GGeometryCollectionNanite != 0) { LocalSceneProxy = new FNaniteGeometryCollectionSceneProxy(this); // ForceMotionBlur means we maintain bIsMoving, regardless of actual state. if (bForceMotionBlur) { bIsMoving = true; if (LocalSceneProxy) { FNaniteGeometryCollectionSceneProxy* NaniteProxy = static_cast(LocalSceneProxy); ENQUEUE_RENDER_COMMAND(NaniteProxyOnMotionEnd)( [NaniteProxy](FRHICommandListImmediate& RHICmdList) { NaniteProxy->OnMotionBegin(); } ); } } } // If we didn't get a proxy, but Nanite was enabled on the asset when it was built, evaluate proxy creation else if (RestCollection->EnableNanite && NaniteProxyRenderMode != 0) { // Do not render Nanite proxy return nullptr; } else { LocalSceneProxy = new FGeometryCollectionSceneProxy(this); } if (RestCollection->HasVisibleGeometry()) { FGeometryCollectionConstantData* const ConstantData = ::new FGeometryCollectionConstantData; InitConstantData(ConstantData); FGeometryCollectionDynamicData* const DynamicData = InitDynamicData(true /* initialization */); if (LocalSceneProxy->IsNaniteMesh()) { FNaniteGeometryCollectionSceneProxy* const GeometryCollectionSceneProxy = static_cast(LocalSceneProxy); // ... #if GEOMETRYCOLLECTION_EDITOR_SELECTION if (bIsTransformSelectionModeEnabled) { // ... } #endif ENQUEUE_RENDER_COMMAND(CreateRenderState)( [GeometryCollectionSceneProxy, ConstantData, DynamicData](FRHICommandListImmediate& RHICmdList) { GeometryCollectionSceneProxy->SetConstantData_RenderThread(ConstantData); if (DynamicData) { GeometryCollectionSceneProxy->SetDynamicData_RenderThread(DynamicData); } bool bValidUpdate = false; if (FPrimitiveSceneInfo* PrimitiveSceneInfo = GeometryCollectionSceneProxy->GetPrimitiveSceneInfo()) { bValidUpdate = PrimitiveSceneInfo->RequestGPUSceneUpdate(); } // Deferred the GPU Scene update if the primitive scene info is not yet initialized with a valid index. GeometryCollectionSceneProxy->SetRequiresGPUSceneUpdate_RenderThread(!bValidUpdate); } ); } else { FGeometryCollectionSceneProxy* const GeometryCollectionSceneProxy = static_cast(LocalSceneProxy); #if GEOMETRYCOLLECTION_EDITOR_SELECTION // Re-init subsections if (bIsTransformSelectionModeEnabled) { GeometryCollectionSceneProxy->UseSubSections(true, false); // Do not force reinit now, it'll be done in SetConstantData_RenderThread } #endif ENQUEUE_RENDER_COMMAND(CreateRenderState)( [GeometryCollectionSceneProxy, ConstantData, DynamicData](FRHICommandListImmediate& RHICmdList) { GeometryCollectionSceneProxy->SetConstantData_RenderThread(ConstantData); if (DynamicData) { GeometryCollectionSceneProxy->SetDynamicData_RenderThread(DynamicData); } } ); } } } 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) { bNotifyBreaks = bNewNotifyBreaks; UpdateBreakEventRegistration(); } } void UGeometryCollectionComponent::SetNotifyRemovals(bool bNewNotifyRemovals) { if (bNotifyRemovals != bNewNotifyRemovals) { bNotifyRemovals = bNewNotifyRemovals; UpdateRemovalEventRegistration(); } } void UGeometryCollectionComponent::SetNotifyCrumblings(bool bNewNotifyCrumblings) { if (bNotifyCrumblings != bNewNotifyCrumblings) { bNotifyCrumblings = bNewNotifyCrumblings; UpdateBreakEventRegistration(); } } FBodyInstance* UGeometryCollectionComponent::GetBodyInstance(FName BoneName /*= NAME_None*/, bool bGetWelded /*= true*/, int32 Index /*=INDEX_NONE*/) const { return nullptr;// const_cast(&DummyBodyInstance); } void UGeometryCollectionComponent::SetNotifyRigidBodyCollision(bool bNewNotifyRigidBodyCollision) { Super::SetNotifyRigidBodyCollision(bNewNotifyRigidBodyCollision); UpdateRBCollisionEventRegistration(); } bool UGeometryCollectionComponent::CanEditSimulatePhysics() { return true; } 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 PreviousOwnerCOmponent = BodyInstance.OwnerComponent; { BodyInstance.OwnerComponent = nullptr; Super::SetSimulatePhysics(bEnabled); BodyInstance.OwnerComponent = PreviousOwnerCOmponent; } if (bEnabled && !PhysicsProxy) { RegisterAndInitializePhysicsProxy(); } } 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 MakeRadialField(const FVector& Origin, float Radius, float Strength, ERadialImpulseFalloff Falloff) { TUniquePtr 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 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 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); } 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; } TArray OutVertexBuffer; TArray 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 GeometryIndexBuffer; TArray TransformIndexBuffer; int32 NumGeometry = Collection->NumElements(FGeometryCollection::GeometryGroup); const TManagedArray& BoundingBox = Collection->BoundingBox; const TManagedArray& TransformIndexArray = Collection->TransformIndex; const TManagedArray& VertexCountArray = Collection->VertexCount; const TManagedArray& FaceCountArray = Collection->FaceCount; const TManagedArray& VertexStartArray = Collection->VertexStart; const TManagedArray& 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) TArray GeomToComponent; GeometryCollectionAlgo::GlobalMatrices(GetTransformArray(), GetParentArray(), TransformIndexBuffer, GeomToComponent); 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, [&](int32 PointIdx) { //extract vertex from source int32 SourceGeometryVertexIndex = SourceGeometryVertexStart + PointIdx; FVector const VertexInWorldSpace = GeomToComponent[SubsetIndex].TransformPosition((FVector)Vertex[SourceGeometryVertexIndex]); int32 DestVertexIndex = DestVertex + PointIdx; OutVertexBuffer[DestVertexIndex].X = VertexInWorldSpace.X; OutVertexBuffer[DestVertexIndex].Y = VertexInWorldSpace.Y; OutVertexBuffer[DestVertexIndex].Z = VertexInWorldSpace.Z; }); DestVertex += SourceGeometryVertexCount; } //gather data needed for indices const TManagedArray& FaceStartArray = Collection->FaceStart; const TManagedArray& Indices = Collection->Indices; const TManagedArray& Visible = GetVisibleArray(); const TManagedArray& 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& ExemplarIndexArray = GetExemplarIndexArray(); const int32 TransformCount = GlobalMatrices.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* HideArray = nullptr; if (RestCollection->GetGeometryCollection()->HasAttribute("Hide", FGeometryCollection::TransformGroup)) { HideArray = &RestCollection->GetGeometryCollection()->GetAttribute("Hide", FGeometryCollection::TransformGroup); } #if WITH_EDITOR EmbeddedInstanceIndex.Init(INDEX_NONE, RestCollection->GetGeometryCollection()->NumElements(FGeometryCollection::TransformGroup)); #endif for (int32 ExemplarIndex = 0; ExemplarIndex < ExemplarCount; ++ExemplarIndex) { #if WITH_EDITOR EmbeddedBoneMaps[ExemplarIndex].Empty(TransformCount); EmbeddedBoneMaps[ExemplarIndex].Reserve(TransformCount); // Allocate for worst case #endif TArray 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(GlobalMatrices[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 EmbeddedGeometryComponent : EmbeddedGeometryComponents) { if (EmbeddedGeometryComponent) { EmbeddedGeometryComponent->bSelectable = bSelectable; EmbeddedGeometryComponent->bHasPerInstanceHitProxies = bSelectable; } } } 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::SetRestState(TArray&& InRestTransforms) { RestTransforms = InRestTransforms; if (DynamicCollection) { SetInitialTransforms(RestTransforms); } FGeometryCollectionDynamicData* DynamicData = GDynamicDataPool.Allocate(); DynamicData->SetPrevTransforms(GlobalMatrices); CalculateGlobalMatrices(); DynamicData->SetTransforms(GlobalMatrices); DynamicData->IsDynamic = true; if (SceneProxy) { #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 if (SceneProxy->IsNaniteMesh()) { FNaniteGeometryCollectionSceneProxy* GeometryCollectionSceneProxy = static_cast(SceneProxy); ENQUEUE_RENDER_COMMAND(SendRenderDynamicData)( [GeometryCollectionSceneProxy, DynamicData](FRHICommandListImmediate& RHICmdList) { GeometryCollectionSceneProxy->SetDynamicData_RenderThread(DynamicData); } ); } else { FGeometryCollectionSceneProxy* GeometryCollectionSceneProxy = static_cast(SceneProxy); ENQUEUE_RENDER_COMMAND(SendRenderDynamicData)( [GeometryCollectionSceneProxy, DynamicData](FRHICommandListImmediate& RHICmdList) { GeometryCollectionSceneProxy->SetDynamicData_RenderThread(DynamicData); } ); } } RefreshEmbeddedGeometry(); } void UGeometryCollectionComponent::InitializeComponent() { Super::InitializeComponent(); if (DynamicCollection) { if (bStoreVelocities || bNotifyTrailing) { if (!DynamicCollection->FindAttributeTyped("LinearVelocity", FTransformCollection::TransformGroup)) { DynamicCollection->AddAttribute("LinearVelocity", FTransformCollection::TransformGroup); } if (!DynamicCollection->FindAttributeTyped("AngularVelocity", FTransformCollection::TransformGroup)) { DynamicCollection->AddAttribute("AngularVelocity", FTransformCollection::TransformGroup); } } DynamicCollection->AddAttribute("InternalClusterParentTypeArray", FTransformCollection::TransformGroup); } } #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(); } } #endif static void DispatchGeometryCollectionBreakEvent(const FChaosBreakEvent& Event) { if (UGeometryCollectionComponent* const GC = Cast(Event.Component)) { GC->DispatchBreakEvent(Event); } } static void DispatchGeometryCollectionRemovalEvent(const FChaosRemovalEvent& Event) { if (UGeometryCollectionComponent* const GC = Cast(Event.Component)) { GC->DispatchRemovalEvent(Event); } } static void DispatchGeometryCollectionCrumblingEvent(const FChaosCrumblingEvent& Event) { if (UGeometryCollectionComponent* const GC = Cast(Event.Component)) { GC->DispatchCrumblingEvent(Event); } } 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() { if (BodyInstance.bNotifyRigidBodyCollision || bNotifyBreaks || bNotifyCollisions || bNotifyRemovals || bNotifyCrumblings) { Chaos::FPhysicsSolver* Solver = GetWorld()->GetPhysicsScene()->GetSolver(); if (Solver) { 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); }); } } } } void UGeometryCollectionComponent::UpdateRBCollisionEventRegistration() { if (bNotifyCollisions || BodyInstance.bNotifyRigidBodyCollision) { EventDispatcher->RegisterForCollisionEvents(this, this); } else { EventDispatcher->UnRegisterForCollisionEvents(this, this); } } void UGeometryCollectionComponent::UpdateBreakEventRegistration() { if (bNotifyBreaks) { EventDispatcher->RegisterForBreakEvents(this, &DispatchGeometryCollectionBreakEvent); } else { EventDispatcher->UnRegisterForBreakEvents(this); } } void UGeometryCollectionComponent::UpdateRemovalEventRegistration() { if (bNotifyRemovals) { EventDispatcher->RegisterForRemovalEvents(this, &DispatchGeometryCollectionRemovalEvent); } else { EventDispatcher->UnRegisterForRemovalEvents(this); } } void UGeometryCollectionComponent::UpdateCrumblingEventRegistration() { if (bNotifyCrumblings) { EventDispatcher->RegisterForCrumblingEvents(this, &DispatchGeometryCollectionCrumblingEvent); } else { EventDispatcher->UnRegisterForCrumblingEvents(this); } } void ActivateClusters(Chaos::FRigidClustering& Clustering, Chaos::TPBDRigidClusteredParticleHandle* Cluster) { if(!Cluster) { return; } if(Cluster->ClusterIds().Id) { ActivateClusters(Clustering, Cluster->Parent()); } Clustering.DeactivateClusterParticle(Cluster); } void UGeometryCollectionComponent::ResetRepData() { ClustersToRep.Reset(); RepData.Reset(); OneOffActivatedProcessed = 0; VersionProcessed = INDEX_NONE; LastHardsnapTimeInMs = 0; } void UGeometryCollectionComponent::UpdateRepData() { using namespace Chaos; if(!bEnableReplication) { 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) { 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>(); 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 bool bClustersChanged = false; const FPBDRigidsSolver* Solver = PhysicsProxy->GetSolver(); const FRigidClustering& RigidClustering = Solver->GetEvolution()->GetRigidClustering(); const TManagedArray* InitialLevels = PhysicsProxy->GetPhysicsCollection().FindAttribute("InitialLevel", FGeometryCollection::TransformGroup); const TManagedArray>& InitialChildren = PhysicsProxy->GetPhysicsCollection().Children; //see if we have any new clusters that are enabled TSet Processed; for (FPBDRigidClusteredParticleHandle* Particle : PhysicsProxy->GetParticles()) { // 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); } } if (bProcess && Root->Disabled() == false && ClustersToRep->Find(Root) == nullptr) { 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::Max(), TEXT("Trying to replicate GC with more than 65k pieces. We assumed uint16 would suffice")); Level = InitialLevels && InitialLevels->Num() > 0 ? (*InitialLevels)[TransformGroupIdx] : INDEX_NONE; } else { // Use internal cluster child's index to compute level. const TArray& Children = RigidClustering.GetChildrenMap()[Root]; const int32 ChildTransformGroupIdx = PhysicsProxy->GetTransformGroupIndexFromHandle(Children[0]); Level = InitialLevels && InitialLevels->Num() > 0 ? ((*InitialLevels)[ChildTransformGroupIdx] - 1) : INDEX_NONE; } if (!bEnableAbandonAfterLevel || Level <= ReplicationAbandonAfterLevel) { // not already replicated and not abandoned level, start replicating cluster ClustersToRep->Add(Root); bClustersChanged = true; } if(Root->InternalCluster() == false && bFirstUpdate == false) //if bFirstUpdate it must be that these are the initial roots of the GC. These did not break off so no need to replicate { //a one off so record it ensureMsgf(TransformGroupIdx >= 0, TEXT("Non-internal cluster should always have a group index")); ensureMsgf(TransformGroupIdx < TNumericLimits::Max(), TEXT("Trying to replicate GC with more than 65k pieces. We assumed uint16 would suffice")); // 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->V(), Root->W()); if(!RepData.OneOffActivated.Contains(OneOffActivated)) { bClustersChanged = true; RepData.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); } } } } } INC_DWORD_STAT_BY(STAT_GCReplicatedFractures, RepData.OneOffActivated.Num()); //build up clusters to replicate and compare with previous frame TArray Clusters; //remove disabled clusters and update rep data if needed for (auto Itr = ClustersToRep->CreateIterator(); Itr; ++Itr) { FPBDRigidClusteredParticleHandle* Cluster = *Itr; if (Cluster->Disabled()) { Itr.RemoveCurrent(); } else { Clusters.AddDefaulted(); FGeometryCollectionClusterRep& ClusterRep = Clusters.Last(); ClusterRep.Position = Cluster->X(); ClusterRep.Rotation = Cluster->R(); ClusterRep.LinearVelocity = Cluster->V(); ClusterRep.AngularVelocity = Cluster->W(); ClusterRep.ClusterState.SetObjectState(Cluster->ObjectState()); ClusterRep.ClusterState.SetInternalCluster(Cluster->InternalCluster()); int32 TransformGroupIdx; if(Cluster->InternalCluster()) { const TArray& Children = RigidClustering.GetChildrenMap()[Cluster]; ensureMsgf(Children.Num(), TEXT("Internal cluster yet we have no children?")); 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::Max(), TEXT("Trying to replicate GC with more than 65k pieces. We assumed uint16 would suffice")); ClusterRep.ClusterIdx = TransformGroupIdx; if(!bClustersChanged) { //compare to previous frame data // this could be more efficient by having a way to find back the data from the idx auto Predicate = [TransformGroupIdx](const FGeometryCollectionClusterRep& Entry) { return Entry.ClusterIdx == TransformGroupIdx; }; if (const FGeometryCollectionClusterRep* PrevClusterData = RepData.Clusters.FindByPredicate(Predicate)) { if (ClusterRep.ClusterChanged(*PrevClusterData)) { bClustersChanged = true; } } } } } if (bClustersChanged) { RepData.Clusters = MoveTemp(Clusters); if (Owner->GetWorld() && Owner->GetWorld()->GetPhysicsScene()) { RepData.ServerFrame = Owner->GetWorld()->GetPhysicsScene()->ReplicationCache.ServerFrame; } INC_DWORD_STAT_BY(STAT_GCReplicatedClusters, RepData.Clusters.Num()); MARK_PROPERTY_DIRTY_FROM_NAME(UGeometryCollectionComponent, RepData, this); ++RepData.Version; 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 //TODO: set back to dormant when sim goes to sleep Owner->SetNetDormancy(DORM_Awake); } } } } 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() { using namespace Chaos; if(!PhysicsProxy || !PhysicsProxy->IsInitializedOnPhysicsThread() || VersionProcessed == RepData.Version || PhysicsProxy->GetReplicationMode() != FGeometryCollectionPhysicsProxy::EReplicationMode::Client) { return; } bool bHardSnap = false; const int64 CurrentTimeInMs = FPlatformTime::ToMilliseconds64(FPlatformTime::Cycles64()); if(VersionProcessed < RepData.Version) { //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 |= (RepData.Version - VersionProcessed) > GeometryCollectionHardMissingUpdatesSnapThreshold; bHardSnap |= (CurrentTimeInMs - LastHardsnapTimeInMs) > GeometryCollectionHardsnapThresholdMs; } else { //rollover so just treat as hard snap - this case is extremely rare and a one off bHardSnap = true; } if (bHardSnap) { LastHardsnapTimeInMs = CurrentTimeInMs; } FPBDRigidsSolver* Solver = PhysicsProxy->GetSolver(); 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]; FPBDRigidParticleHandle* OneOff = PhysicsProxy->GetParticles()[ActivatedCluster.ActivatedIndex]; check(OneOff); // 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); } RigidClustering.ReleaseClusterParticles(TArray{ OneOff }, /* bTriggerBreakEvents */ true); } if(bHardSnap) { for (const FGeometryCollectionClusterRep& RepCluster : RepData.Clusters) { if (FPBDRigidParticleHandle* Cluster = PhysicsProxy->GetParticles()[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) { Cluster->SetX(RepCluster.Position); Cluster->SetR(RepCluster.Rotation); Cluster->SetV(RepCluster.LinearVelocity); Cluster->SetW(RepCluster.AngularVelocity); // TODO: snap object state too once we fix interpolation // Solver->GetEvolution()->SetParticleObjectState(Cluster, static_cast(RepCluster.ObjectState)); } } } } VersionProcessed = RepData.Version; } void UGeometryCollectionComponent::SetDynamicState(const Chaos::EObjectStateType& NewDynamicState) { if (DynamicCollection) { TManagedArray& DynamicState = DynamicCollection->DynamicState; for (int i = 0; i < DynamicState.Num(); i++) { DynamicState[i] = static_cast(NewDynamicState); } } } void UGeometryCollectionComponent::SetInitialTransforms(const TArray& InitialTransforms) { if (DynamicCollection) { TManagedArray& Transform = DynamicCollection->Transform; int32 MaxIdx = FMath::Min(Transform.Num(), InitialTransforms.Num()); for (int32 Idx = 0; Idx < MaxIdx; ++Idx) { Transform[Idx] = InitialTransforms[Idx]; } } } void UGeometryCollectionComponent::SetInitialClusterBreaks(const TArray& ReleaseIndices) { if (DynamicCollection) { TManagedArray& Parent = DynamicCollection->Parent; TManagedArray >& Children = DynamicCollection->Children; const int32 NumTransforms = Parent.Num(); for (int32 ReleaseIndex : ReleaseIndices) { if (ReleaseIndex < NumTransforms) { if (Parent[ReleaseIndex] > INDEX_NONE) { Children[Parent[ReleaseIndex]].Remove(ReleaseIndex); Parent[ReleaseIndex] = INDEX_NONE; } } } } } void SetHierarchyStrain(Chaos::TPBDRigidClusteredParticleHandle* P, TMap*, TArray*>>& Map, float Strain) { TArray*>* Children = Map.Find(P); if(Children) { for(Chaos::TPBDRigidParticleHandle * ChildP : (*Children)) { SetHierarchyStrain(ChildP->CastToClustered(), Map, Strain); } } if(P) { P->SetStrain(Strain); } } void UGeometryCollectionComponent::InitConstantData(FGeometryCollectionConstantData* ConstantData) const { // Constant data should all be moved to the DDC as time permits. // todo : this should be computed once per asset not per component or at least the part that does not depend on the component properties check(ConstantData); check(RestCollection); const FGeometryCollection* Collection = RestCollection->GetGeometryCollection().Get(); check(Collection); if (!RestCollection->EnableNanite) { const int32 NumPoints = Collection->NumElements(FGeometryCollection::VerticesGroup); const TManagedArray& Vertex = Collection->Vertex; const TManagedArray& BoneMap = Collection->BoneMap; const TManagedArray& TangentU = Collection->TangentU; const TManagedArray& TangentV = Collection->TangentV; const TManagedArray& Normal = Collection->Normal; const TManagedArray>& UVs = Collection->UVs; const TManagedArray& Color = Collection->Color; const TManagedArray& BoneColors = Collection->BoneColor; const int32 NumGeom = Collection->NumElements(FGeometryCollection::GeometryGroup); const TManagedArray& TransformIndex = Collection->TransformIndex; const TManagedArray& FaceStart = Collection->FaceStart; const TManagedArray& FaceCount = Collection->FaceCount; ConstantData->Vertices = TArray(Vertex.GetData(), Vertex.Num()); ConstantData->BoneMap = TArray(BoneMap.GetData(), BoneMap.Num()); ConstantData->TangentU = TArray(TangentU.GetData(), TangentU.Num()); ConstantData->TangentV = TArray(TangentV.GetData(), TangentV.Num()); ConstantData->Normals = TArray(Normal.GetData(), Normal.Num()); ConstantData->Colors = TArray(Color.GetData(), Color.Num()); // find the maximum number of channels int32 NumUVChannels = 0; for (const TArray& VertexUVs : Collection->UVs.GetConstArray()) { NumUVChannels = FMath::Max(NumUVChannels, VertexUVs.Num()); } ConstantData->UVChannels.SetNum(NumUVChannels); for (int32 UVChannelIndex = 0; UVChannelIndex < NumUVChannels; UVChannelIndex++) { ConstantData->UVChannels[UVChannelIndex].AddUninitialized(NumPoints); } ConstantData->BoneColors.AddUninitialized(NumPoints); if (bChaos_GC_InitConstantDataUseParallelFor) { ParallelFor(TEXT("GC:InitConstantData"), NumPoints, bChaos_GC_InitConstantDataParallelForBatchSize, [&](const int32 InPointIndex) { const int32 BoneIndex = ConstantData->BoneMap[InPointIndex]; ConstantData->BoneColors[InPointIndex] = BoneColors[BoneIndex]; const TArray& VertexUVs = Collection->UVs[InPointIndex]; for (int32 UVChannelIndex = 0; UVChannelIndex < VertexUVs.Num(); UVChannelIndex++) { ConstantData->UVChannels[UVChannelIndex][InPointIndex] = Collection->UVs[InPointIndex][UVChannelIndex]; } for (int32 UVChannelIndex = VertexUVs.Num(); UVChannelIndex < NumUVChannels; UVChannelIndex++) { ConstantData->UVChannels[UVChannelIndex][InPointIndex] = FVector2f::ZeroVector; } }); } else { for (int32 PointIndex = 0; PointIndex < NumPoints; PointIndex++) { const int32 BoneIndex = ConstantData->BoneMap[PointIndex]; ConstantData->BoneColors[PointIndex] = BoneColors[BoneIndex]; } for (int32 UVChannelIndex = 0; UVChannelIndex < NumUVChannels; UVChannelIndex++) { for (int32 PointIndex = 0; PointIndex < NumPoints; PointIndex++) { ConstantData->UVChannels[UVChannelIndex][PointIndex] = Collection->UVs[PointIndex][UVChannelIndex]; } } } int32 NumIndices = 0; const TManagedArray& Indices = Collection->Indices; const TManagedArray& MaterialID = Collection->MaterialID; const TManagedArray& Visible = GetVisibleArray(); // Use copy on write attribute. The rest collection visible array can be overriden for the convenience of debug drawing the collision volumes #if WITH_EDITOR // We will override visibility with the Hide array (if available). TArray VisibleOverride; VisibleOverride.SetNumUninitialized(Visible.Num()); for (int32 FaceIdx = 0; FaceIdx < Visible.Num(); FaceIdx++) { VisibleOverride[FaceIdx] = Visible[FaceIdx]; } bool bUsingHideArray = false; if (Collection->HasAttribute("Hide", FGeometryCollection::TransformGroup)) { bUsingHideArray = true; bool bAllHidden = true; const TManagedArray& Hide = Collection->GetAttribute("Hide", FGeometryCollection::TransformGroup); for (int32 GeomIdx = 0; GeomIdx < NumGeom; ++GeomIdx) { int32 TransformIdx = TransformIndex[GeomIdx]; if (Hide[TransformIdx]) { // (Temporarily) hide faces of this hidden geometry for (int32 FaceIdxOffset = 0; FaceIdxOffset < FaceCount[GeomIdx]; ++FaceIdxOffset) { VisibleOverride[FaceStart[GeomIdx]+FaceIdxOffset] = false; } } else if (bAllHidden && Collection->IsVisible(TransformIdx)) { bAllHidden = false; } } if (!ensure(!bAllHidden)) // if they're all hidden, rendering would crash -- reset to default visibility instead { for (int32 FaceIdx = 0; FaceIdx < VisibleOverride.Num(); ++FaceIdx) { VisibleOverride[FaceIdx] = Visible[FaceIdx]; } } } #endif const TManagedArray& MaterialIndex = Collection->MaterialIndex; const int32 NumFaceGroupEntries = Collection->NumElements(FGeometryCollection::FacesGroup); for (int FaceIndex = 0; FaceIndex < NumFaceGroupEntries; ++FaceIndex) { #if WITH_EDITOR NumIndices += bUsingHideArray ? static_cast(VisibleOverride[FaceIndex]) : static_cast(Visible[FaceIndex]); #else NumIndices += static_cast(Visible[FaceIndex]); #endif } ConstantData->Indices.AddUninitialized(NumIndices); for (int IndexIdx = 0, cdx = 0; IndexIdx < NumFaceGroupEntries; ++IndexIdx) { #if WITH_EDITOR const bool bUseVisible = bUsingHideArray ? VisibleOverride[MaterialIndex[IndexIdx]] : Visible[MaterialIndex[IndexIdx]]; if (bUseVisible) #else if (Visible[MaterialIndex[IndexIdx]]) #endif { ConstantData->Indices[cdx++] = Indices[MaterialIndex[IndexIdx]]; } } // We need to correct the section index start point & number of triangles since only the visible ones have been copied across in the code above const int32 NumMaterialSections = Collection->NumElements(FGeometryCollection::MaterialGroup); ConstantData->Sections.AddUninitialized(NumMaterialSections); const TManagedArray& Sections = Collection->Sections; for (int SectionIndex = 0; SectionIndex < NumMaterialSections; ++SectionIndex) { FGeometryCollectionSection Section = Sections[SectionIndex]; // deliberate copy for (int32 TriangleIndex = 0; TriangleIndex < Sections[SectionIndex].FirstIndex / 3; TriangleIndex++) { #if WITH_EDITOR const bool bUseVisible = bUsingHideArray ? VisibleOverride[MaterialIndex[TriangleIndex]] : Visible[MaterialIndex[TriangleIndex]]; if (!bUseVisible) #else if (!Visible[MaterialIndex[TriangleIndex]]) #endif { Section.FirstIndex -= 3; } } for (int32 TriangleIndex = 0; TriangleIndex < Sections[SectionIndex].NumTriangles; TriangleIndex++) { int32 FaceIdx = MaterialIndex[Sections[SectionIndex].FirstIndex / 3 + TriangleIndex]; #if WITH_EDITOR const bool bUseVisible = bUsingHideArray ? VisibleOverride[FaceIdx] : Visible[FaceIdx]; if (!bUseVisible) #else if (!Visible[FaceIdx]) #endif { Section.NumTriangles--; } } ConstantData->Sections[SectionIndex] = MoveTemp(Section); } ConstantData->NumTransforms = Collection->NumElements(FGeometryCollection::TransformGroup); ConstantData->LocalBounds = LocalBounds; // store the index buffer and render sections for the base unfractured mesh const TManagedArray& TransformToGeometryIndex = Collection->TransformToGeometryIndex; const int32 NumFaces = Collection->NumElements(FGeometryCollection::FacesGroup); TArray BaseMeshIndices; TArray BaseMeshOriginalFaceIndices; BaseMeshIndices.Reserve(NumFaces); BaseMeshOriginalFaceIndices.Reserve(NumFaces); // add all visible external faces to the original geometry index array // #note: This is a stopgap because the original geometry array is broken for (int FaceIndex = 0; FaceIndex < NumFaces; ++FaceIndex) { // only add visible external faces. MaterialID that is even is an external material #if WITH_EDITOR const bool bUseVisible = bUsingHideArray ? VisibleOverride[FaceIndex] : Visible[FaceIndex]; if (bUseVisible && (MaterialID[FaceIndex] % 2 == 0 || bUsingHideArray)) #else if (Visible[FaceIndex] && MaterialID[FaceIndex] % 2 == 0) #endif { BaseMeshIndices.Add(Indices[FaceIndex]); BaseMeshOriginalFaceIndices.Add(FaceIndex); } } // We should always have external faces of a geometry collection ensure(BaseMeshIndices.Num() > 0); ConstantData->OriginalMeshSections = Collection->BuildMeshSections(BaseMeshIndices, BaseMeshOriginalFaceIndices, ConstantData->OriginalMeshIndices); } TArray RestMatrices; GeometryCollectionAlgo::GlobalMatrices(RestCollection->GetGeometryCollection()->Transform, RestCollection->GetGeometryCollection()->Parent, RestMatrices); ConstantData->SetRestTransforms(RestMatrices); } FGeometryCollectionDynamicData* UGeometryCollectionComponent::InitDynamicData(bool bInitialization) { SCOPE_CYCLE_COUNTER(STAT_GCInitDynamicData); FGeometryCollectionDynamicData* DynamicData = nullptr; const bool bEditorMode = bShowBoneColors || bEnableBoneSelection; const bool bIsDynamic = GetIsObjectDynamic() || bEditorMode || bInitialization; if (bIsDynamic) { DynamicData = GDynamicDataPool.Allocate(); DynamicData->IsDynamic = true; DynamicData->IsLoading = GetIsObjectLoading(); // If we have no transforms stored in the dynamic data, then assign both prev and current to the same global matrices if (GlobalMatrices.Num() == 0) { // Copy global matrices over to DynamicData CalculateGlobalMatrices(); DynamicData->SetAllTransforms(GlobalMatrices); } else { // Copy existing global matrices into prev transforms DynamicData->SetPrevTransforms(GlobalMatrices); // Copy global matrices over to DynamicData CalculateGlobalMatrices(); bool bComputeChanges = true; // if the number of matrices has changed between frames, then sync previous to current if (GlobalMatrices.Num() != DynamicData->PrevTransforms.Num()) { DynamicData->SetPrevTransforms(GlobalMatrices); DynamicData->ChangedCount = GlobalMatrices.Num(); bComputeChanges = false; // Optimization to just force all transforms as changed and skip comparison } DynamicData->SetTransforms(GlobalMatrices); // The number of transforms for current and previous should match now check(DynamicData->PrevTransforms.Num() == DynamicData->Transforms.Num()); if (bComputeChanges) { DynamicData->DetermineChanges(); } } } if (!bEditorMode && !bInitialization) { if (DynamicData && DynamicData->ChangedCount == 0) { GDynamicDataPool.Release(DynamicData); DynamicData = nullptr; // Change of state? if (bIsMoving && !bForceMotionBlur) { bIsMoving = false; if (SceneProxy && SceneProxy->IsNaniteMesh()) { FNaniteGeometryCollectionSceneProxy* NaniteProxy = static_cast(SceneProxy); ENQUEUE_RENDER_COMMAND(NaniteProxyOnMotionEnd)( [NaniteProxy](FRHICommandListImmediate& RHICmdList) { NaniteProxy->OnMotionEnd(); } ); } } } else { // Change of state? if (!bIsMoving && !bForceMotionBlur) { bIsMoving = true; if (SceneProxy && SceneProxy->IsNaniteMesh()) { FNaniteGeometryCollectionSceneProxy* NaniteProxy = static_cast(SceneProxy); ENQUEUE_RENDER_COMMAND(NaniteProxyOnMotionBegin)( [NaniteProxy](FRHICommandListImmediate& RHICmdList) { NaniteProxy->OnMotionBegin(); } ); } } } } return DynamicData; } void UGeometryCollectionComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) { Super::OnUpdateTransform(UpdateTransformFlags, Teleport); if (PhysicsProxy) { PhysicsProxy->SetWorldTransform_External(GetComponentTransform()); } } bool UGeometryCollectionComponent::HasAnySockets() const { if (RestCollection && RestCollection->GetGeometryCollection()) { return (RestCollection->GetGeometryCollection()->BoneName.Num() > 0); } return false; } bool UGeometryCollectionComponent::DoesSocketExist(FName InSocketName) const { if (RestCollection && RestCollection->GetGeometryCollection()) { return RestCollection->GetGeometryCollection()->BoneName.Contains(InSocketName.ToString()); } return false; } FTransform UGeometryCollectionComponent::GetSocketTransform(FName InSocketName, ERelativeTransformSpace TransformSpace) const { if (RestCollection && RestCollection->GetGeometryCollection()) { const TSharedPtr Collection = RestCollection->GetGeometryCollection(); const int32 TransformIndex = Collection->BoneName.Find(InSocketName.ToString()); if (TransformIndex != INDEX_NONE) { if (GlobalMatrices.IsValidIndex(TransformIndex)) { const FTransform BoneComponentSpaceTransform = FTransform(GlobalMatrices[TransformIndex]); switch (TransformSpace) { case RTS_World: return BoneComponentSpaceTransform * GetComponentTransform(); case RTS_Actor: { if (const AActor* Actor = GetOwner()) { const FTransform SocketWorldSpaceTransform = BoneComponentSpaceTransform * GetComponentTransform(); return SocketWorldSpaceTransform.GetRelativeTransform(Actor->GetTransform()); } break; } case RTS_Component: return BoneComponentSpaceTransform; case RTS_ParentBoneSpace: { const int32 ParentTransformIndex = Collection->Parent[TransformIndex]; FTransform ParentComponentSpaceTransform{ FTransform::Identity }; if (GlobalMatrices.IsValidIndex(ParentTransformIndex)) { ParentComponentSpaceTransform = FTransform(GlobalMatrices[ParentTransformIndex]); } return BoneComponentSpaceTransform.GetRelativeTransform(ParentComponentSpaceTransform); } default: check(false); } } } } return Super::GetSocketTransform(InSocketName, TransformSpace); } void UGeometryCollectionComponent::QuerySupportedSockets(TArray& OutSockets) const { if (RestCollection && RestCollection->GetGeometryCollection()) { for (const FString& BoneName: RestCollection->GetGeometryCollection()->BoneName) { FComponentSocketDescription& Desc = OutSockets.AddZeroed_GetRef(); Desc.Name = *BoneName; Desc.Type = EComponentSocketType::Type::Bone; } } } 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& AttachedChild: this->GetAttachChildren()) { if (AttachedChild) { AttachedChild->UpdateComponentToWorld(); } } } } 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 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 (bRenderStateDirty && DynamicCollection) //todo: always send for now if (RestCollection) { // In editor mode we have no DynamicCollection so this test is necessary if(DynamicCollection) //, TEXT("No dynamic collection available for component %s during tick."), *GetName())) { if (IsRootBroken()) { IncrementSleepTimer(DeltaTime); IncrementBreakTimer(DeltaTime); } // 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& AttachedChild: this->GetAttachChildren()) // { // AttachedChild->UpdateComponentToWorld(); // } // } if (RestCollection->HasVisibleGeometry() || DynamicCollection->IsDirty()) { // #todo review: When we've made changes to ISMC, we need to move this function call to SetRenderDynamicData_Concurrent RefreshEmbeddedGeometry(); // we may want to call this when the geometry collection updates ( notified by the proxy buffer updates ) // otherwise we are getting a frame delay RefreshISMPoolInstances(); if (SceneProxy && SceneProxy->IsNaniteMesh()) { FNaniteGeometryCollectionSceneProxy* NaniteProxy = static_cast(SceneProxy); NaniteProxy->FlushGPUSceneUpdate_GameThread(); } MarkRenderTransformDirty(); MarkRenderDynamicDataDirty(); bRenderStateDirty = false; const UWorld* MyWorld = GetWorld(); 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::AsyncPhysicsTickComponent(float DeltaTime, float SimTime) { Super::AsyncPhysicsTickComponent(DeltaTime, SimTime); // using net mode for now as using local role seemed to cause other issues at initialization time // we may nee dto to also use local role in the future if the authority is likely to change at runtime if (GetNetMode() != ENetMode::NM_Client) { UpdateRepData(); } else { ProcessRepData(); } } 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(); Super::OnRegister(); } void UGeometryCollectionComponent::ResetDynamicCollection() { bool bCreateDynamicCollection = true; #if WITH_EDITOR bCreateDynamicCollection = false; if (UWorld* World = GetWorld()) { if(World->IsGameWorld() || World->IsPreviewWorld()) { bCreateDynamicCollection = true; } } #endif if (bCreateDynamicCollection && RestCollection && RestCollection->GetGeometryCollection()) { DynamicCollection = MakeUnique(); for (const auto DynamicArray : CopyOnWriteAttributeList) { *DynamicArray = nullptr; } GetTransformArrayCopyOnWrite(); GetParentArrayCopyOnWrite(); GetChildrenArrayCopyOnWrite(); GetSimulationTypeArrayCopyOnWrite(); GetStatusFlagsArrayCopyOnWrite(); 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); } SetRenderStateDirty(); } if (RestTransforms.Num() > 0) { SetInitialTransforms(RestTransforms); } if (RestCollection) { CalculateGlobalMatrices(); CalculateLocalBounds(); } } 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) { #if WITH_EDITOR && WITH_EDITORONLY_DATA EditorActor = nullptr; if (RestCollection) { //hack: find a better place for this UGeometryCollection* RestCollectionMutable = const_cast(ToRawPtr(RestCollection)); RestCollectionMutable->CreateSimulationDataIfNeeded(); } #endif const bool bValidWorld = GetWorld() && (GetWorld()->IsGameWorld() || GetWorld()->IsPreviewWorld()); const bool bValidCollection = DynamicCollection && DynamicCollection->Transform.Num() > 0; if (bValidWorld && bValidCollection) { FPhysxUserData::Set(&PhysicsUserData, this); // If the Component is set to Dynamic, we look to the RestCollection for initial dynamic state override per transform. TManagedArray & 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& InitialDynamicState = RestCollection->GetGeometryCollection()->InitialDynamicState; for (int i = 0; i < DynamicState.Num(); i++) { DynamicState[i] = (InitialDynamicState[i] == static_cast(Chaos::EObjectStateType::Uninitialized)) ? static_cast(LocalObjectType) : InitialDynamicState[i]; } } else { for (int i = 0; i < DynamicState.Num(); i++) { DynamicState[i] = static_cast(LocalObjectType); } } } TManagedArray& Active = DynamicCollection->Active; if (RestCollection->GetGeometryCollection()->HasAttribute(FGeometryCollection::SimulatableParticlesAttribute, FTransformCollection::TransformGroup)) { TManagedArray* SimulatableParticles = RestCollection->GetGeometryCollection()->FindAttribute(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); } } TManagedArray & CollisionGroupArray = DynamicCollection->CollisionGroup; { for (int i = 0; i < CollisionGroupArray.Num(); i++) { CollisionGroupArray[i] = CollisionGroup; } } // 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& Levels = RestCollection->GetGeometryCollection()->AddAttribute("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(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(); } } } } 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() { FSimulationParameters SimulationParameters; { #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) SimulationParameters.Name = GetPathName(); #endif EClusterConnectionTypeEnum ClusterCollectionType = ClusterConnectionType_DEPRECATED; float ConnectionGraphBoundsFilteringMargin = 0; if (RestCollection) { RestCollection->GetSharedSimulationParams(SimulationParameters.Shared); SimulationParameters.RestCollection = RestCollection->GetGeometryCollection().Get(); ClusterCollectionType = RestCollection->ClusterConnectionType; ConnectionGraphBoundsFilteringMargin = RestCollection->ConnectionGraphBoundsFilteringMargin; } SimulationParameters.Simulating = BodyInstance.bSimulatePhysics; SimulationParameters.EnableClustering = EnableClustering; SimulationParameters.ClusterGroupIndex = EnableClustering ? ClusterGroupIndex : 0; SimulationParameters.MaxClusterLevel = MaxClusterLevel; SimulationParameters.bUseSizeSpecificDamageThresholds = bUseSizeSpecificDamageThreshold; SimulationParameters.DamageThreshold = DamageThreshold; SimulationParameters.bUsePerClusterOnlyDamageThreshold = RestCollection? RestCollection->PerClusterOnlyDamageThreshold: false; SimulationParameters.ClusterConnectionMethod = (Chaos::FClusterCreationParameters::EConnectionMethod)ClusterCollectionType; SimulationParameters.ConnectionGraphBoundsFilteringMargin = ConnectionGraphBoundsFilteringMargin; SimulationParameters.CollisionGroup = CollisionGroup; SimulationParameters.CollisionSampleFraction = CollisionSampleFraction; SimulationParameters.InitialVelocityType = InitialVelocityType; SimulationParameters.InitialLinearVelocity = InitialLinearVelocity; SimulationParameters.InitialAngularVelocity = InitialAngularVelocity; SimulationParameters.bClearCache = true; SimulationParameters.ObjectType = ObjectType; SimulationParameters.StartAwake = BodyInstance.bStartAwake; SimulationParameters.CacheType = CacheParameters.CacheMode; SimulationParameters.ReverseCacheBeginTime = CacheParameters.ReverseCacheBeginTime; SimulationParameters.bGenerateBreakingData = bNotifyBreaks; SimulationParameters.bGenerateCollisionData = bNotifyCollisions; SimulationParameters.bGenerateTrailingData = bNotifyTrailing; SimulationParameters.bGenerateRemovalsData = bNotifyRemovals; SimulationParameters.bGenerateCrumblingData = bNotifyCrumblings; SimulationParameters.bGenerateCrumblingChildrenData = bCrumblingEventIncludesChildren; SimulationParameters.EnableGravity = BodyInstance.bEnableGravity; SimulationParameters.UseInertiaConditioning = BodyInstance.IsInertiaConditioningEnabled(); SimulationParameters.UseCCD = BodyInstance.bUseCCD; SimulationParameters.LinearDamping = BodyInstance.LinearDamping; SimulationParameters.AngularDamping = BodyInstance.AngularDamping; SimulationParameters.bUseDamagePropagation = DamagePropagationData.bEnabled; SimulationParameters.BreakDamagePropagationFactor = DamagePropagationData.BreakDamagePropagationFactor; SimulationParameters.ShockDamagePropagationFactor = DamagePropagationData.ShockDamagePropagationFactor; SimulationParameters.WorldTransform = GetComponentToWorld(); SimulationParameters.UserData = static_cast(&PhysicsUserData); UPhysicalMaterial* EnginePhysicalMaterial = GetPhysicalMaterial(); if (ensure(EnginePhysicalMaterial)) { SimulationParameters.PhysicalMaterialHandle = EnginePhysicalMaterial->GetPhysicsMaterial(); } 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, SimulationParameters, InitialSimFilter, InitialQueryFilter, CollectorGuid); PhysicsProxy->SetPostPhysicsSyncCallback([this]() { UpdateAttachedChildrenTransform(); }); 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); } } FPhysScene_Chaos* Scene = GetInnerChaosScene(); Scene->AddObject(this, PhysicsProxy); // If we're replicating we need some extra setup - check netmode as we don't need this for standalone runtimes where we aren't going to network the component // IMPORTANT this need to happen after the object is registered so this will garantee that the particles are properly created by the time the callback below gets called if (GetIsReplicated()) { // Client side : geometry collection children of parents below the rep level need to be infintely strong so that client cannot break it if (Chaos::FPhysicsSolver* CurrSolver = GetSolver(*this)) { CurrSolver->EnqueueCommandImmediate([Proxy = PhysicsProxy, AbandonAfterLevel = ReplicationAbandonAfterLevel, EnableAbandonAfterLevel = bEnableAbandonAfterLevel]() { if (Proxy->GetReplicationMode() == FGeometryCollectionPhysicsProxy::EReplicationMode::Client) { // 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 constexpr Chaos::FReal MaxStrain = TNumericLimits::Max() - TNumericLimits::Min(); for (Chaos::FPBDRigidClusteredParticleHandle* ParticleHandle : Proxy->GetParticles()) { if (ParticleHandle) { const int32 Level = EnableAbandonAfterLevel ? ComputeParticleLevel(ParticleHandle) : -1; if (Level <= AbandonAfterLevel + 1) //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->SetStrain(MaxStrain); } } } } }); } } RegisterForEvents(); SetAsyncPhysicsTickEnabled(GetIsReplicated()); } #if WITH_EDITORONLY_DATA const FDamageCollector* UGeometryCollectionComponent::GetRunTimeDataCollector() const { return FRuntimeDataCollector::GetInstance().Find(RunTimeDataCollectionGuid); } #endif void UGeometryCollectionComponent::OnDestroyPhysicsState() { UActorComponent::OnDestroyPhysicsState(); if(DummyBodyInstance.IsValidBodyInstance()) { DummyBodyInstance.TermBody(); } if(PhysicsProxy) { FPhysScene_Chaos* Scene = GetInnerChaosScene(); Scene->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; } } void UGeometryCollectionComponent::SendRenderDynamicData_Concurrent() { //UE_LOG(UGCC_LOG, Log, TEXT("GeometryCollectionComponent[%p]::SendRenderDynamicData_Concurrent()"), this); Super::SendRenderDynamicData_Concurrent(); // Only update the dynamic data if the dynamic collection is dirty if (SceneProxy && ((DynamicCollection && DynamicCollection->IsDirty()) || CachePlayback)) { FGeometryCollectionDynamicData* DynamicData = InitDynamicData(false /* initialization */); if (DynamicData || SceneProxy->IsNaniteMesh()) { INC_DWORD_STAT_BY(STAT_GCTotalTransforms, DynamicData ? DynamicData->Transforms.Num() : 0); INC_DWORD_STAT_BY(STAT_GCChangedTransforms, DynamicData ? DynamicData->ChangedCount : 0); // #todo (bmiller) Once ISMC changes have been complete, this is the best place to call this method // but we can't currently because it's an inappropriate place to call MarkRenderStateDirty on the ISMC. // RefreshEmbeddedGeometry(); // Enqueue command to send to render thread if (SceneProxy->IsNaniteMesh()) { FNaniteGeometryCollectionSceneProxy* GeometryCollectionSceneProxy = static_cast(SceneProxy); ENQUEUE_RENDER_COMMAND(SendRenderDynamicData)( [GeometryCollectionSceneProxy, DynamicData](FRHICommandListImmediate& RHICmdList) { if (DynamicData) { GeometryCollectionSceneProxy->SetDynamicData_RenderThread(DynamicData); } else { // No longer dynamic, make sure previous transforms are reset GeometryCollectionSceneProxy->ResetPreviousTransforms_RenderThread(); } } ); } else { FGeometryCollectionSceneProxy* GeometryCollectionSceneProxy = static_cast(SceneProxy); ENQUEUE_RENDER_COMMAND(SendRenderDynamicData)( [GeometryCollectionSceneProxy, DynamicData](FRHICommandListImmediate& RHICmdList) { if (GeometryCollectionSceneProxy) { GeometryCollectionSceneProxy->SetDynamicData_RenderThread(DynamicData); } } ); } } // mark collection clean now that we have rendered if (DynamicCollection) { DynamicCollection->MakeClean(); } } } 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) { InitialQueryFilter.Word3 |= EPDF_ContactNotify; InitialSimFilter.Word3 |= EPDF_ContactNotify; } } void UGeometryCollectionComponent::SetRestCollection(const UGeometryCollection* RestCollectionIn) { //UE_LOG(UGCC_LOG, Log, TEXT("GeometryCollectionComponent[%p]::SetRestCollection()"), this); if (RestCollectionIn) { RestCollection = RestCollectionIn; const int32 NumTransforms = RestCollection->GetGeometryCollection()->NumElements(FGeometryCollection::TransformGroup); RestTransforms.SetNum(NumTransforms); for (int32 Idx = 0; Idx < NumTransforms; ++Idx) { RestTransforms[Idx] = RestCollection->GetGeometryCollection()->Transform[Idx]; } CalculateGlobalMatrices(); CalculateLocalBounds(); if (!IsEmbeddedGeometryValid()) { InitializeEmbeddedGeometry(); } // initialize the component per level damage threshold from the asset defaults DamageThreshold = RestCollection->DamageThreshold; bUseSizeSpecificDamageThreshold = RestCollection->bUseSizeSpecificDamageThreshold; // initialize the component damage progataion data from the asset defaults DamagePropagationData = RestCollection->DamagePropagationData; //ResetDynamicCollection(); } } 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) : Component(InComponent) , EditUpdate(InEditUpdate) , bShapeIsUnchanged(bShapeIsUnchanged) { bHadPhysicsState = Component->HasValidPhysicsState(); if (EnumHasAnyFlags(EditUpdate, GeometryCollection::EEditUpdate::Physics) && bHadPhysicsState) { Component->DestroyPhysicsState(); } if (EnumHasAnyFlags(EditUpdate, GeometryCollection::EEditUpdate::Rest) && GetRestCollection()) { Component->Modify(); GetRestCollection()->Modify(); } } FGeometryCollectionEdit::~FGeometryCollectionEdit() { #if WITH_EDITOR if (!!EditUpdate) { if (EnumHasAnyFlags(EditUpdate, GeometryCollection::EEditUpdate::Dynamic)) { Component->ResetDynamicCollection(); } if (EnumHasAnyFlags(EditUpdate, GeometryCollection::EEditUpdate::Rest) && GetRestCollection()) { if (!bShapeIsUnchanged) { GetRestCollection()->UpdateGeometryDependentProperties(); } GetRestCollection()->InvalidateCollection(); } if (EnumHasAnyFlags(EditUpdate, GeometryCollection::EEditUpdate::Physics) && bHadPhysicsState) { Component->RecreatePhysicsState(); } } #endif } UGeometryCollection* FGeometryCollectionEdit::GetRestCollection() { if (Component) { return const_cast(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 FScopedColorEdit::RandomColors; FScopedColorEdit::FScopedColorEdit(UGeometryCollectionComponent* InComponent, bool bForceUpdate) : bUpdated(bForceUpdate), Component(InComponent) { if (RandomColors.Num() == 0) { FMath::RandInit(2019); for (int i = 0; i < 100; i++) { const FColor Color(FMath::Rand() % 100 + 5, FMath::Rand() % 100 + 5, FMath::Rand() % 100 + 5, 255); RandomColors.Push(FLinearColor(Color)); } } } 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 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& SelectedBonesIn) { bUpdated = true; Component->SelectedBones = SelectedBonesIn; Component->SelectEmbeddedGeometry(); } void FScopedColorEdit::AppendSelectedBones(const TArray& SelectedBonesIn) { bUpdated = true; Component->SelectedBones.Append(SelectedBonesIn); } void FScopedColorEdit::ToggleSelectedBones(const TArray& SelectedBonesIn, bool bAdd, bool bSnapToLevel) { bUpdated = true; const UGeometryCollection* GeometryCollection = Component->GetRestCollection(); if (GeometryCollection) { TSharedPtr 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& 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 GeometryCollectionPtr = GeometryCollection->GetGeometryCollection(); const TManagedArray& Levels = GeometryCollectionPtr->GetAttribute("Level", FGeometryCollection::TransformGroup); const TManagedArray& 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 GeometryCollectionPtr = GeometryCollection->GetGeometryCollection(); const TManagedArray& Levels = GeometryCollectionPtr->GetAttribute("Level", FGeometryCollection::TransformGroup); const TManagedArray& 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 GeometryCollectionPtr = GeometryCollection->GetGeometryCollection(); const TManagedArray& Levels = GeometryCollectionPtr->GetAttribute("Level", FGeometryCollection::TransformGroup); const TManagedArray& SimTypes = GeometryCollectionPtr->SimulationType; TArray 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 GeometryCollectionPtr = GeometryCollection->GetGeometryCollection(); switch (SelectionMode) { case GeometryCollection::ESelectionMode::None: ResetBoneSelection(); break; case GeometryCollection::ESelectionMode::AllGeometry: { ResetBoneSelection(); TArray BonesToSelect; FGeometryCollectionClusteringUtility::GetBonesToLevel(GeometryCollectionPtr.Get(), GetViewLevel(), BonesToSelect, true, true); AppendSelectedBones(BonesToSelect); } break; case GeometryCollection::ESelectionMode::Leaves: { ResetBoneSelection(); int32 ViewLevel = GetViewLevel(); TArray BonesToSelect; FGeometryCollectionClusteringUtility::GetBonesToLevel(GeometryCollectionPtr.Get(), GetViewLevel(), BonesToSelect, true, true); const TManagedArray& SimType = GeometryCollectionPtr->SimulationType; const TManagedArray* Levels = GeometryCollectionPtr->FindAttributeTyped("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 BonesToSelect; FGeometryCollectionClusteringUtility::GetBonesToLevel(GeometryCollectionPtr.Get(), ViewLevel, BonesToSelect, true, true); const TManagedArray& SimType = GeometryCollectionPtr->SimulationType; const TManagedArray* Levels = GeometryCollectionPtr->FindAttributeTyped("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 Roots; FGeometryCollectionClusteringUtility::GetRootBones(GeometryCollectionPtr.Get(), Roots); TArray NewSelection; for (int32 RootElement : Roots) { if (GetViewLevel() == -1) { TArray LeafBones; FGeometryCollectionClusteringUtility::GetLeafBones(GeometryCollectionPtr.Get(), RootElement, true, LeafBones); for (int32 Element : LeafBones) { if (!IsBoneSelected(Element)) { NewSelection.Push(Element); } } } else { TArray 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& TransformIndex = GeometryCollectionPtr->TransformIndex; const TManagedArray& TransformToGeometryIndex = GeometryCollectionPtr->TransformToGeometryIndex; const TManagedArray>& Proximity = GeometryCollectionPtr->GetAttribute>("Proximity", FGeometryCollection::GeometryGroup); const TArray SelectedBones = GetSelectedBones(); TSet NewSelection; for (int32 Bone : SelectedBones) { NewSelection.Add(Bone); int32 GeometryIdx = TransformToGeometryIndex[Bone]; if (GeometryIdx != INDEX_NONE) { const TSet& Neighbors = Proximity[GeometryIdx]; for (int32 NeighborGeometryIndex : Neighbors) { NewSelection.Add(TransformIndex[NeighborGeometryIndex]); } } } ResetBoneSelection(); AppendSelectedBones(NewSelection.Array()); } break; case GeometryCollection::ESelectionMode::Parent: { const TManagedArray& Parents = GeometryCollectionPtr->Parent; const TArray SelectedBones = GetSelectedBones(); TSet 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>& Children = GeometryCollectionPtr->Children; const TArray SelectedBones = GetSelectedBones(); TSet 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& Parents = GeometryCollectionPtr->Parent; const TManagedArray>& Children = GeometryCollectionPtr->Children; const TArray SelectedBones = GetSelectedBones(); TSet 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& Levels = GeometryCollectionPtr->GetAttribute("Level", FTransformCollection::TransformGroup); const TArray SelectedBones = GetSelectedBones(); TSet 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& SelectedBones = GetSelectedBones(); TArray 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& HighlightedBonesIn, bool bHighlightChildren) { if (Component->HighlightedBones != HighlightedBonesIn) { const UGeometryCollection* GeometryCollection = Component->GetRestCollection(); if (bHighlightChildren && GeometryCollection) { Component->HighlightedBones.Reset(); TSharedPtr 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& 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) { FGeometryCollection* Collection = GeometryCollection->GetGeometryCollection().Get(); FLinearColor BlankColor(FColor(80, 80, 80, 50)); const TManagedArray& Parents = Collection->Parent; bool HasLevelAttribute = Collection->HasAttribute("Level", FTransformCollection::TransformGroup); const TManagedArray* Levels = nullptr; if (HasLevelAttribute) { Levels = &Collection->GetAttribute("Level", FTransformCollection::TransformGroup); } TManagedArray& 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.LinearRGBToHSV(); BoneColor.B *= .5; BoneColor.HSVToLinearRGB(); } else { BoneColor = 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(); const FName Name = GetOwner() ? *GetOwner()->GetName() : TEXT(""); FFieldSystemCommand LocalCommand = InCommand; LocalCommand.InitFieldNodes(Solver->GetSolverTime(), Name); Solver->EnqueueCommandImmediate([Solver, PhysicsProxy = this->PhysicsProxy, NewCommand = LocalCommand]() { // Pass through nullptr here as geom component commands can never affect other solvers PhysicsProxy->BufferCommand(Solver, NewCommand); }); } } void UGeometryCollectionComponent::GetInitializationCommands(TArray& 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); } } } // 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>& Elem : Command.MetaData) { NewCommand.MetaData.Add(Elem.Key, TUniquePtr(Elem.Value->NewCopy())); } CombinedCommmands.Emplace(NewCommand); } } } } } } } bool UGeometryCollectionComponent::GetSuppressSelectionMaterial() const { return RestCollection->GetGeometryCollection()->HasAttribute("Hide", FGeometryCollection::TransformGroup); } const int UGeometryCollectionComponent::GetBoneSelectedMaterialID() const { return RestCollection->GetBoneSelectedMaterialIndex(); } FPhysScene_Chaos* UGeometryCollectionComponent::GetInnerChaosScene() const { if (ChaosSolverActor) { return ChaosSolverActor->GetPhysicsScene().Get(); } else { if (ensure(GetOwner()) && ensure(GetOwner()->GetWorld())) { return GetOwner()->GetWorld()->GetPhysicsScene(); } check(GWorld); return GWorld->GetPhysicsScene(); } } AChaosSolverActor* UGeometryCollectionComponent::GetPhysicsSolverActor() const { if (ChaosSolverActor) { return ChaosSolverActor; } else { FPhysScene_Chaos const* const Scene = GetInnerChaosScene(); return Scene ? Cast(Scene->GetSolverActor()) : nullptr; } return nullptr; } void UGeometryCollectionComponent::CalculateLocalBounds() { LocalBounds.Init(); LocalBounds = ComputeBounds(FMatrix::Identity); } void UGeometryCollectionComponent::CalculateGlobalMatrices() { SCOPE_CYCLE_COUNTER(STAT_GCCUGlobalMatrices); const FGeometryCollectionResults* Results = PhysicsProxy ? PhysicsProxy->GetConsumerResultsGT() : nullptr; const int32 NumTransforms = Results ? Results->GlobalTransforms.Num() : 0; if(NumTransforms > 0) { // Just calc from results GlobalMatrices.Reset(); GlobalMatrices.Append(Results->GlobalTransforms); } else { // If hierarchy topology has changed, the RestTransforms is invalidated. if (RestTransforms.Num() != GetTransformArray().Num()) { RestTransforms.Empty(); } if (!DynamicCollection && RestTransforms.Num() > 0) { GeometryCollectionAlgo::GlobalMatrices(RestTransforms, GetParentArray(), GlobalMatrices); } else { // Have to fully rebuild if (DynamicCollection && DynamicCollection->HasAttribute("UniformScale", FGeometryCollection::TransformGroup) && DynamicCollection->HasAttribute("Decay", FGeometryCollection::TransformGroup)) { const TManagedArray& Decay = DynamicCollection->GetAttribute("Decay", FGeometryCollection::TransformGroup); TManagedArray& UniformScale = DynamicCollection->ModifyAttribute("UniformScale", FGeometryCollection::TransformGroup); const FTransform InverseComponentTransform = GetComponentTransform().Inverse(); const FTransform ZeroScaleTransform(FQuat::Identity, FVector::Zero(), FVector(0, 0, 0)); for (int32 Idx = 0; Idx < GetTransformArray().Num(); ++Idx) { // only update values if the decay has changed if (Decay[Idx] > 0.f && Decay[Idx] <= 1.f) { const float Scale = 1.0 - Decay[Idx]; if (Scale < UE_SMALL_NUMBER) { UniformScale[Idx] = ZeroScaleTransform; } else { float ShrinkRadius = 0.0f; UE::Math::TSphere AccumulatedSphere; // todo(chaos) : find a faster way to do that ( precompute the data ? ) if (CalculateInnerSphere(Idx, AccumulatedSphere)) { ShrinkRadius = -AccumulatedSphere.W; } const FQuat LocalRotation = (InverseComponentTransform * FTransform(GlobalMatrices[Idx]).Inverse()).GetRotation(); const FVector LocalDown = LocalRotation.RotateVector(FVector(0.f, 0.f, ShrinkRadius)); const FVector CenterOfMass = DynamicCollection->MassToLocal[Idx].GetTranslation(); const FVector ScaleCenter = LocalDown + CenterOfMass; UniformScale[Idx] = FTransform(FQuat::Identity, ScaleCenter * (FVector::FReal)(1.f - Scale), FVector(Scale)); } } } GeometryCollectionAlgo::GlobalMatrices(GetTransformArray(), GetParentArray(), UniformScale, GlobalMatrices); } else { GeometryCollectionAlgo::GlobalMatrices(GetTransformArray(), GetParentArray(), GlobalMatrices); } } } #if WITH_EDITOR UpdateGlobalMatricesWithExplodedVectors(GlobalMatrices, *(RestCollection->GetGeometryCollection())); #endif } // #todo(dmp): for backwards compatibility with existing maps, we need to have a default of 3 materials. Otherwise // some existing test scenes will crash int32 UGeometryCollectionComponent::GetNumMaterials() const { return !RestCollection || RestCollection->Materials.Num() == 0 ? 3 : 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; } } #if WITH_EDITOR void UGeometryCollectionComponent::SelectEmbeddedGeometry() { // First reset the selections for (TObjectPtr EmbeddedGeometryComponent : EmbeddedGeometryComponents) { EmbeddedGeometryComponent->ClearInstanceSelection(); } const TManagedArray& ExemplarIndex = GetExemplarIndexArray(); 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 PrimitiveComponents; Actor->GetComponents(PrimitiveComponents); for (UPrimitiveComponent* PrimitiveComponent : PrimitiveComponents) { bool ValidComponent = false; if (UStaticMeshComponent* StaticMeshComp = Cast(PrimitiveComponent)) { // unhacked. //StaticMeshComp->SetVisibility(false); } else if (UGeometryCollectionComponent* GeometryCollectionComponent = Cast(PrimitiveComponent)) { if (!GeometryCollectionComponent->IsVisible()) { continue; } GeometryCollectionComponent->SetVisibility(true); } } TInlineComponentArray 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? if (SceneProxy && !SceneProxy->IsNaniteMesh() && RestCollection && RestCollection->HasVisibleGeometry()) { static_cast(SceneProxy)->UseSubSections(bEnable, true); } bIsTransformSelectionModeEnabled = bEnable; } #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(RestCollection->EmbeddedGeometryExemplar[Idx].StaticMeshExemplar.TryLoad()); if (!ExemplarStaticMesh) { return false; } if (ExemplarStaticMesh != EmbeddedGeometryComponents[Idx]->GetStaticMesh()) { return false; } } return true; } void UGeometryCollectionComponent::ClearEmbeddedGeometry() { AActor* OwningActor = GetOwner(); TArray TargetComponents; OwningActor->GetComponents(TargetComponents, false); for (UActorComponent* TargetComponent : TargetComponents) { if ((TargetComponent->GetOuter() == this) || !IsValidChecked(TargetComponent->GetOuter())) { if (UInstancedStaticMeshComponent* ISMComponent = Cast(TargetComponent)) { ISMComponent->ClearInstances(); ISMComponent->DestroyComponent(); } } } EmbeddedGeometryComponents.Empty(); } void UGeometryCollectionComponent::InitializeEmbeddedGeometry() { if (RestCollection) { ClearEmbeddedGeometry(); AActor* ActorOwner = GetOwner(); check(ActorOwner); // Construct an InstancedStaticMeshComponent for each exemplar for (const FGeometryCollectionEmbeddedExemplar& Exemplar : RestCollection->EmbeddedGeometryExemplar) { if (UStaticMesh* ExemplarStaticMesh = Cast(Exemplar.StaticMeshExemplar.TryLoad())) { if (UInstancedStaticMeshComponent* ISMC = NewObject(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 } } bool UGeometryCollectionComponent::CanUseISMPool() const { return bChaos_GC_UseISMPool && ISMPool; } void UGeometryCollectionComponent::RegisterToISMPool() { UnregisterFromISMPool(); if (CanUseISMPool()) { if (UGeometryCollectionISMPoolComponent* ISMPoolComp = ISMPool->GetISMPoolComp()) { bool bCanRenderComponent = true; if (RestCollection) { ISMPoolMeshGroupIndex = ISMPoolComp->CreateMeshGroup(); if (bChaos_GC_UseISMPoolForNonFracturedParts) { if (RestCollection->GetGeometryCollection()) { // if we use ISM pool for the hierarchy we must hide the component for rendering bCanRenderComponent = false; // fisrt count the instance per mesh TArray InstanceCounts; InstanceCounts.AddZeroed(RestCollection->AutoInstanceMeshes.Num()); const TManagedArray>& Children = RestCollection->GetGeometryCollection()->Children; const GeometryCollection::Facades::FCollectionInstancedMeshFacade InstancedMeshFacade(*RestCollection->GetGeometryCollection()); if (InstancedMeshFacade.IsValid()) { for (int32 TransformIndex = 0; TransformIndex < InstancedMeshFacade.GetNumIndices(); TransformIndex++) { const int32 AutoInstanceMeshIndex = InstancedMeshFacade.GetIndex(TransformIndex); if (Children[TransformIndex].Num() == 0) { InstanceCounts[AutoInstanceMeshIndex]++; } } } // now register each mesh for (int32 MeshIndex = 0; MeshIndex < RestCollection->AutoInstanceMeshes.Num(); MeshIndex++) { const FGeometryCollectionAutoInstanceMesh& AutoInstanceMesh = RestCollection->AutoInstanceMeshes[MeshIndex]; if (UStaticMesh* StaticMesh = Cast(AutoInstanceMesh.StaticMesh.TryLoad())) { bool bMaterialOverride = false; for (int32 MatIndex = 0; MatIndex < AutoInstanceMesh.Materials.Num(); MatIndex++) { const UMaterialInterface* OriginalMaterial = StaticMesh->GetMaterial(MatIndex); if (OriginalMaterial != AutoInstanceMesh.Materials[MatIndex]) { bMaterialOverride = true; break; } } FGeometryCollectionStaticMeshInstance StaticMeshInstance; StaticMeshInstance.StaticMesh = StaticMesh; if (bMaterialOverride) { StaticMeshInstance.MaterialsOverrides = AutoInstanceMesh.Materials; } ISMPoolComp->AddMeshToGroup(ISMPoolMeshGroupIndex, StaticMeshInstance, InstanceCounts[MeshIndex]); } } } } // root proxy if available // TODO : if ISM pool is not available : uses a standard static mesh component if (UObject* RootMeshProxyObject = RestCollection->RootProxy.ResolveObject()) { if (UStaticMesh* RootMeshProxy = Cast(RootMeshProxyObject)) { // if we use a mesh proxy hide the component for rendering bCanRenderComponent = false; FGeometryCollectionStaticMeshInstance StaticMeshInstance; StaticMeshInstance.StaticMesh = RootMeshProxy; ISMPoolRootProxyMeshId = ISMPoolComp->AddMeshToGroup(ISMPoolMeshGroupIndex, StaticMeshInstance, 1); } } } SetVisibility(bCanRenderComponent); RefreshISMPoolInstances(); } } } void UGeometryCollectionComponent::UnregisterFromISMPool() { if (ISMPool) { if (UGeometryCollectionISMPoolComponent* ISMPoolComp = ISMPool->GetISMPoolComp()) { ISMPoolComp->DestroyMeshGroup(ISMPoolMeshGroupIndex); ISMPoolMeshGroupIndex = INDEX_NONE; ISMPoolRootProxyMeshId = INDEX_NONE; } } SetVisibility(true); } void UGeometryCollectionComponent::RefreshISMPoolInstances() { if (CanUseISMPool()) { if (UGeometryCollectionISMPoolComponent* ISMPoolComp = ISMPool->GetISMPoolComp()) { if (RestCollection) { // default to true for editor purposes? //const bool bCollectionIsDirty = DynamicCollection ? DynamicCollection->IsDirty() : true; if (bChaos_GC_UseISMPoolForNonFracturedParts /*&& bCollectionIsDirty*/) { if (RestCollection->GetGeometryCollection()) { const TManagedArray>& Children = RestCollection->GetGeometryCollection()->Children; const GeometryCollection::Facades::FCollectionInstancedMeshFacade InstancedMeshFacade(*RestCollection->GetGeometryCollection()); if (InstancedMeshFacade.IsValid()) { const int32 NumTransforms = RestCollection->NumElements(FGeometryCollection::TransformAttribute); CalculateGlobalMatrices(); const FTransform& ComponentTransform = GetComponentTransform(); constexpr bool bWorlSpace = true; constexpr bool bMarkRenderStateDirty = true; constexpr bool bTeleport = true; const int32 RootIndex = GetRootIndex(); //const bool bIsBroken = DynamicCollection ? (DynamicCollection->Children[RootIndex].Num() != Children[RootIndex].Num()) : false; const bool bIsBroken = DynamicCollection ? !DynamicCollection->Active[RootIndex] : false; const bool bHasRootProxyMesh = (ISMPoolRootProxyMeshId != INDEX_NONE); if (bHasRootProxyMesh && !bIsBroken) { if (GlobalMatrices.IsValidIndex(RootIndex)) { FTransform RootTransform = FTransform(GlobalMatrices[RootIndex]) * ComponentTransform; ISMPoolComp->BatchUpdateInstancesTransforms(ISMPoolMeshGroupIndex, ISMPoolRootProxyMeshId, 0, { RootTransform }, bWorlSpace, bMarkRenderStateDirty, bTeleport); } } else if (bChaos_GC_UseISMPoolForNonFracturedParts) { // make sure this mesh is invisible // todo : should be event based instead of doing it every frame if (bHasRootProxyMesh && GlobalMatrices.IsValidIndex(RootIndex)) { FTransform RootTransformZeroScale; RootTransformZeroScale.SetIdentityZeroScale(); ISMPoolComp->BatchUpdateInstancesTransforms(ISMPoolMeshGroupIndex, ISMPoolRootProxyMeshId, 0, { RootTransformZeroScale }, bWorlSpace, bMarkRenderStateDirty, bTeleport); } TArray InstanceTransforms; for (int32 MeshIndex = 0; MeshIndex < RestCollection->AutoInstanceMeshes.Num(); MeshIndex++) { InstanceTransforms.Reset(NumTransforms); // Allocate for worst case for (int32 TransformIndex = 0; TransformIndex < NumTransforms; TransformIndex++) { const int32 AutoInstanceMeshIndex = InstancedMeshFacade.GetIndex(TransformIndex); if (AutoInstanceMeshIndex == MeshIndex && Children[TransformIndex].Num() == 0) { InstanceTransforms.Add(FTransform(GlobalMatrices[TransformIndex]) * ComponentTransform); } } ISMPoolComp->BatchUpdateInstancesTransforms(ISMPoolMeshGroupIndex, MeshIndex, 0, InstanceTransforms, bWorlSpace, bMarkRenderStateDirty, bTeleport); } } } } } } } } } bool UGeometryCollectionComponent::IsRootBroken() const { if (DynamicCollection && DynamicCollection->Active.Num() > 0) { const int32 RootIndex = GetRootIndex(); if (RootIndex != INDEX_NONE) { return !DynamicCollection->Active[RootIndex]; } } return false; } struct FGeometryCollectionDecayContext { FGeometryCollectionDecayContext(FGeometryCollectionPhysicsProxy& PhysicsProxyIn, FGeometryCollectionDecayDynamicFacade& DecayFacadeIn) : PhysicsProxy(PhysicsProxyIn) , DecayFacade(DecayFacadeIn) , DirtyDynamicCollection(false) {} FGeometryCollectionPhysicsProxy& PhysicsProxy; FGeometryCollectionDecayDynamicFacade& DecayFacade; bool DirtyDynamicCollection; TArray ToDisable; TArray 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) { TManagedArray& Decay = ContextInOut.DecayFacade.DecayAttribute.Modify(); if (UpdatedDecay > Decay[TransformIdx]) { ContextInOut.DirtyDynamicCollection = true; Decay[TransformIdx] = UpdatedDecay; if (bUseClusterCrumbling) { if (bHasDynamicInternalClusterParent) { FGeometryCollectionItemIndex InternalClusterItemindex = ContextInOut.PhysicsProxy.GetInternalClusterParentItemIndex_External(TransformIdx); if (InternalClusterItemindex.IsValid()) { ContextInOut.ToCrumble.AddUnique(InternalClusterItemindex); Decay[TransformIdx] = 0.0f; } } else { ContextInOut.ToCrumble.AddUnique(FGeometryCollectionItemIndex::CreateTransformItemIndex(TransformIdx)); Decay[TransformIdx] = 0.0f; } } else if (Decay[TransformIdx] >= 1.0f) { // Disable the particle if it has decayed the requisite time Decay[TransformIdx] = 1.0f; ContextInOut.ToDisable.Add(TransformIdx); } } } void UGeometryCollectionComponent::IncrementSleepTimer(float DeltaTime) { if (DeltaTime <= 0 || !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& OriginalParents = RestCollection->GetGeometryCollection()->Parent; TManagedArray& Decay = DecayFacade.DecayAttribute.Modify(); for (int32 TransformIdx = 0; TransformIdx < Decay.Num(); ++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 = true; // with sleep removal : internal clusters always crumble - this will change when we merge the removal feature together 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 = (Decay[TransformIdx] > 0); if (!ShouldUpdateTimer && RestCollection->bSlowMovingAsSleeping) { const FVector CurrentPosition = DynamicCollection->Transform[TransformIdx].GetTranslation(); ShouldUpdateTimer |= RemoveOnSleepFacade.ComputeSlowMovingState(TransformIdx, 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& OriginalParents = RestCollection->GetGeometryCollection()->Parent; const TManagedArray* InitialLevels = PhysicsProxy->GetPhysicsCollection().FindAttribute("InitialLevel", FGeometryCollection::TransformGroup); TManagedArray& Decay = DecayFacade.DecayAttribute.Modify(); for (int32 TransformIdx = 0; TransformIdx < Decay.Num(); ++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 && (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 && (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) { SetAnchoredByTransformedBox(WorldSpaceBox, FTransform::Identity, bAnchored); } void UGeometryCollectionComponent::SetAnchoredByTransformedBox(FBox Box, FTransform Transform, bool bAnchored) { if (PhysicsProxy) { PhysicsProxy->SetAnchoredByTransformedBox_External(Box, Transform, bAnchored); } } 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->ApplyBreakingLinearVelocity_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; int32 Level = INDEX_NONE; if (RestCollection && RestCollection->GetGeometryCollection()) { const TManagedArray& Parent = RestCollection->GetGeometryCollection()->Parent; int32 TransformIndex = INDEX_NONE; FGeometryCollectionItemIndex GCItemIndex = FGeometryCollectionItemIndex::CreateFromExistingItemIndex(ItemIndex); if (GCItemIndex.IsInternalCluster()) { if (const TArray* 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 && RestCollection->GetGeometryCollection()) { Chaos::Facades::FCollectionHierarchyFacade HierarchyFacade(*RestCollection->GetGeometryCollection()); return HierarchyFacade.GetRootIndex(); } return INDEX_NONE; } void UGeometryCollectionComponent::GetMassAndExtents(int32 ItemIndex, float& OutMass, FBox& OutExtents) { using FGeometryCollectionPtr = const TSharedPtr; OutMass = 0.0f; OutExtents = FBox(EForceInit::ForceInitToZero); int32 Level = INDEX_NONE; if (RestCollection && RestCollection->GetGeometryCollection()) { const FGeometryCollection& Collection = *RestCollection->GetGeometryCollection(); if (const TManagedArray* CollectionMass = Collection.FindAttribute(TEXT("Mass"), FTransformCollection::TransformGroup)) { const TManagedArray* TransformBoundingBoxes = Collection.FindAttribute(TEXT("BoundingBox"), FTransformCollection::TransformGroup); const TManagedArray* GeoBoundingBoxes = Collection.FindAttribute(TEXT("BoundingBox"), FGeometryCollection::GeometryGroup); int32 TransformIndex = INDEX_NONE; FGeometryCollectionItemIndex GCItemIndex = FGeometryCollectionItemIndex::CreateFromExistingItemIndex(ItemIndex); if (GCItemIndex.IsInternalCluster()) { const TArray* Children = PhysicsProxy->FindInternalClusterChildrenTransformIndices_External(GCItemIndex); if (Children) { for (const int32 ChildTramsformIndex : *Children) { OutMass += (*CollectionMass)[ChildTramsformIndex]; if (TransformBoundingBoxes) { OutExtents += (*TransformBoundingBoxes)[ChildTramsformIndex]; } else if (GeoBoundingBoxes) { OutExtents += (*GeoBoundingBoxes)[Collection.TransformToGeometryIndex[ChildTramsformIndex]]; } } } } else { TransformIndex = GCItemIndex.GetTransformIndex(); OutMass = (*CollectionMass)[TransformIndex]; if (TransformBoundingBoxes) { OutExtents = (*TransformBoundingBoxes)[TransformIndex]; } else { OutExtents = (*GeoBoundingBoxes)[Collection.TransformToGeometryIndex[TransformIndex]]; } } } } } bool UGeometryCollectionComponent::CalculateInnerSphere(int32 TransformIndex, UE::Math::TSphere& 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& TransformToGeometryIndex = RestCollection->GetGeometryCollection()->TransformToGeometryIndex; const TManagedArray& InnerRadius = RestCollection->GetGeometryCollection()->InnerRadius; const TManagedArray>& Children = RestCollection->GetGeometryCollection()->Children; const TManagedArray& MassToLocal = RestCollection->GetGeometryCollection()->GetAttribute("MassToLocal", FGeometryCollection::TransformGroup); if (RestCollection->GetGeometryCollection()->IsRigid(TransformIndex)) { // Sphere in component space, centered on body's COM. FVector COM = MassToLocal[TransformIndex].GetLocation(); SphereOut = UE::Math::TSphere(COM, InnerRadius[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 LocalSphere; if (CalculateInnerSphere(ChildIndex, LocalSphere)) { if (!bSphereFound) { bSphereFound = true; SphereOut = LocalSphere; } else { SphereOut += LocalSphere; } } } return bSphereFound; } else { // Likely an embedded geometry, which doesn't count towards volume. return false; } } void UGeometryCollectionComponent::PostLoad() { Super::PostLoad(); // // 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; } } Chaos::FPhysicsObject* UGeometryCollectionComponent::GetPhysicsObjectById(int32 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 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 UGeometryCollectionComponent::GetAllPhysicsObjects() const { if (!PhysicsProxy) { return {}; } TArray Objects; Objects.Reserve(PhysicsProxy->GetNumParticles()); for (int32 Index = 0; Index < PhysicsProxy->GetNumParticles(); ++Index) { Objects.Add(GetPhysicsObjectById(Index)); } return Objects; }