// Copyright Epic Games, Inc. All Rights Reserved. #include "GeometryCollection/GeometryCollectionComponent.h" #include "Async/ParallelFor.h" #include "Components/BoxComponent.h" #include "ComponentRecreateRenderStateContext.h" #include "GeometryCollection/GeometryCollectionObject.h" #include "GeometryCollection/GeometryCollectionAlgo.h" #include "GeometryCollection/GeometryCollectionComponentPluginPrivate.h" #include "GeometryCollection/GeometryCollectionSceneProxy.h" #include "GeometryCollection/GeometryCollectionSQAccelerator.h" #include "GeometryCollection/GeometryCollectionUtility.h" #include "GeometryCollection/GeometryCollectionClusteringUtility.h" #include "GeometryCollection/GeometryCollectionCache.h" #include "GeometryCollection/GeometryCollectionActor.h" #include "GeometryCollection/GeometryCollectionDebugDrawComponent.h" #include "Physics/Experimental/PhysScene_Chaos.h" #include "Modules/ModuleManager.h" #include "ChaosSolversModule.h" #include "ChaosStats.h" #include "PhysicsProxy/GeometryCollectionPhysicsProxy.h" #include "PhysicsSolver.h" #include "Physics/PhysicsFiltering.h" #include "Chaos/ChaosPhysicalMaterial.h" #include "AI/NavigationSystemHelpers.h" #include "Net/UnrealNetwork.h" #include "Net/Core/PushModel/PushModel.h" #include "PhysicalMaterials/PhysicalMaterial.h" #include "Engine/InstancedStaticMesh.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" #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 #endif 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; } #if WITH_PHYSX && !WITH_CHAOS_NEEDS_TO_BE_FIXED FGeometryCollectionSQAccelerator GlobalGeomCollectionAccelerator; //todo(ocohen): proper lifetime management needed void HackRegisterGeomAccelerator(UGeometryCollectionComponent& Component) { #if TODO_REIMPLEMENT_SCENEQUERY_CROSSENGINE if (UWorld* World = Component.GetWorld()) { if (FPhysScene* PhysScene = World->GetPhysicsScene()) { if (FSQAcceleratorUnion* SQAccelerationUnion = PhysScene->GetSQAcceleratorUnion()) { SQAccelerationUnion->AddSQAccelerator(&GlobalGeomCollectionAccelerator); } } } #endif } #endif 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; int32 NumPoses = Poses.Num(); Ar << NumPoses; if(Ar.IsLoading()) { Poses.SetNum(NumPoses); } for(FGeometryCollectionRepPose& Pose : Poses) { SerializePackedVector<100, 30>(Pose.Position, Ar); SerializePackedVector<100, 30>(Pose.LinearVelocity, Ar); SerializePackedVector<100, 30>(Pose.AngularVelocity, Ar); Pose.Rotation.NetSerialize(Ar, Map, bOutSuccess); Ar << Pose.ParticleIndex; } return true; } static void RecreateGCGlobalRenderState(IConsoleVariable* Var) { FGlobalComponentRecreateRenderStateContext Context; } int32 GGeometryCollectionNanite = 1; FAutoConsoleVariableRef CVarGeometryCollectionNanite( TEXT("r.GeometryCollection.Nanite"), GGeometryCollectionNanite, TEXT("Render geometry collections using Nanite."), FConsoleVariableDelegate::CreateStatic(&RecreateGCGlobalRenderState), 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.")); 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) { } UGeometryCollectionComponent::UGeometryCollectionComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , ChaosSolverActor(nullptr) , Simulating(true) , InitializationState(ESimulationInitializationState::Unintialized) , ObjectType(EObjectStateTypeEnum::Chaos_Object_Dynamic) , EnableClustering(true) , ClusterGroupIndex(0) , MaxClusterLevel(100) , DamageThreshold({250.0}) , bUseSizeSpecificDamageThreshold(false) , 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) , bShowBoneColors(false) , bEnableReplication(false) , bEnableAbandonAfterLevel(false) , ReplicationAbandonClusterLevel(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; WorldBounds = FBoxSphereBounds(FBox(ForceInit)); // 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); EventDispatcher = ObjectInitializer.CreateDefaultSubobject(this, TEXT("GameplayEventDispatcher")); DynamicCollection = nullptr; bHasCustomNavigableGeometry = EHasCustomNavigableGeometry::Yes; bWantsInitializeComponent = true; } Chaos::FPhysicsSolver* GetSolver(const UGeometryCollectionComponent& GeometryCollectionComponent) { #if INCLUDE_CHAOS if(GeometryCollectionComponent.ChaosSolverActor) { return GeometryCollectionComponent.ChaosSolverActor->GetSolver(); } else if(UWorld* CurrentWorld = GeometryCollectionComponent.GetWorld()) { if(FPhysScene* Scene = CurrentWorld->GetPhysicsScene()) { return Scene->GetSolver(); } } #endif return nullptr; } void UGeometryCollectionComponent::BeginPlay() { Super::BeginPlay(); #if WITH_PHYSX && !WITH_CHAOS_NEEDS_TO_BE_FIXED HackRegisterGeomAccelerator(*this); #endif ////////////////////////////////////////////////////////////////////////// // Commenting out these callbacks for now due to the threading model. The callbacks here // expect the rest collection to be mutable which is not the case when running in multiple // threads. Ideally we have some separate animation collection or track that we cache to // without affecting the data we've dispatched to the physics thread ////////////////////////////////////////////////////////////////////////// // ---------- SolverCallbacks->SetResetAnimationCacheFunction([&]() // ---------- { // ---------- FGeometryCollectionEdit Edit = EditRestCollection(); // ---------- Edit.GetRestCollection()->RecordedData.SetNum(0); // ---------- }); // ---------- SolverCallbacks->SetUpdateTransformsFunction([&](const TArrayView&) // ---------- { // ---------- // todo : Move the update to the array passed here... // ---------- }); // ---------- // ---------- SolverCallbacks->SetUpdateRestStateFunction([&](const int32 & CurrentFrame, const TManagedArray & RigidBodyID, const TManagedArray& Hierarchy, const FSolverCallbacks::FParticlesType& Particles) // ---------- { // ---------- FGeometryCollectionEdit Edit = EditRestCollection(); // ---------- UGeometryCollection * RestCollection = Edit.GetRestCollection(); // ---------- check(RestCollection); // ---------- // ---------- if (CurrentFrame >= RestCollection->RecordedData.Num()) // ---------- { // ---------- RestCollection->RecordedData.SetNum(CurrentFrame + 1); // ---------- RestCollection->RecordedData[CurrentFrame].SetNum(RigidBodyID.Num()); // ---------- ParallelFor(RigidBodyID.Num(), [&](int32 i) // ---------- { // ---------- if (!Hierarchy[i].Children.Num()) // ---------- { // ---------- RestCollection->RecordedData[CurrentFrame][i].SetTranslation(Particles.X(RigidBodyID[i])); // ---------- RestCollection->RecordedData[CurrentFrame][i].SetRotation(Particles.R(RigidBodyID[i])); // ---------- } // ---------- else // ---------- { // ---------- RestCollection->RecordedData[CurrentFrame][i].SetTranslation(FVector::ZeroVector); // ---------- RestCollection->RecordedData[CurrentFrame][i].SetRotation(FQuat::Identity); // ---------- } // ---------- }); // ---------- } // ---------- }); ////////////////////////////////////////////////////////////////////////// // default current cache time CurrentCacheTime = MAX_flt; } 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 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); } 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 (!CachePlayback && WorldBounds.GetSphere().W > 1e-5 && NumTransforms > 0) { return WorldBounds; } else if (RestCollection) { const FMatrix LocalToWorldWithScale = LocalToWorldIn.ToMatrixWithScale(); FBox BoundingBox(ForceInit); //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->GetAttribute("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) { return FBoxSphereBounds(ForceInitToZero); } 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 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); #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); } } #endif } return FBoxSphereBounds(BoundingBox); } return FBoxSphereBounds(ForceInitToZero); } void UGeometryCollectionComponent::CreateRenderState_Concurrent(FRegisterComponentContext* Context) { Super::CreateRenderState_Concurrent(Context); } FPrimitiveSceneProxy* UGeometryCollectionComponent::CreateSceneProxy() { FPrimitiveSceneProxy* LocalSceneProxy = nullptr; if (RestCollection) { // TODO: Abstract with a common helper if (UseNanite(GetScene()->GetShaderPlatform()) && RestCollection->EnableNanite && RestCollection->NaniteData != nullptr && GGeometryCollectionNanite != 0) { LocalSceneProxy = new FNaniteGeometryCollectionSceneProxy(this); } 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); } if (FPrimitiveSceneInfo* PrimitiveSceneInfo = GeometryCollectionSceneProxy->GetPrimitiveSceneInfo()) { PrimitiveSceneInfo->RequestGPUSceneUpdate(); } } ); } 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(); } } FBodyInstance* UGeometryCollectionComponent::GetBodyInstance(FName BoneName /*= NAME_None*/, bool bGetWelded /*= true*/) const { return nullptr;// const_cast(&DummyBodyInstance); } void UGeometryCollectionComponent::SetNotifyRigidBodyCollision(bool bNewNotifyRigidBodyCollision) { Super::SetNotifyRigidBodyCollision(bNewNotifyRigidBodyCollision); UpdateRBCollisionEventRegistration(); } void UGeometryCollectionComponent::DispatchBreakEvent(const FChaosBreakEvent& Event) { // native NotifyBreak(Event); // bp if (OnChaosBreakEvent.IsBound()) { OnChaosBreakEvent.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(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 = PhysicalMaterialOverride; if(!PhysMatToUse) { // 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 TManagedArray& ExemplarIndexArray = GetExemplarIndexArray(); const int32 TransformCount = GlobalMatrices.Num(); const int32 ExemplarCount = EmbeddedGeometryComponents.Num(); for (int32 ExemplarIndex = 0; ExemplarIndex < ExemplarCount; ++ExemplarIndex) { 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) { InstanceTransforms.Add(FTransform(GlobalMatrices[Idx])); } } 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); } } } } void UGeometryCollectionComponent::InitializeComponent() { Super::InitializeComponent(); AActor* Owner = GetOwner(); if(!Owner) { return; } const ENetRole LocalRole = Owner->GetLocalRole(); const ENetMode NetMode = Owner->GetNetMode(); // 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 if(GetIsReplicated() && NetMode != NM_Standalone) { if(LocalRole == ENetRole::ROLE_Authority) { // As we're the authority we need to track velocities in the dynamic collection so we // can send them over to the other clients to correctly set their state. Attach this now. // The physics proxy will pick them up and populate them as needed DynamicCollection->AddAttribute("LinearVelocity", FTransformCollection::TransformGroup); DynamicCollection->AddAttribute("AngularVelocity", FTransformCollection::TransformGroup); // We also need to track our control of particles if that control can be shared between server and client if(bEnableAbandonAfterLevel) { TManagedArray& ControlFlags = DynamicCollection->AddAttribute("AuthControl", FTransformCollection::TransformGroup); for(bool& Flag : ControlFlags) { Flag = true; } } } else { // We're a replicated component and we're not in control. Chaos::FPhysicsSolver* CurrSolver = GetSolver(*this); if(CurrSolver) { CurrSolver->RegisterSimOneShotCallback([Prox = PhysicsProxy]() { // 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 const Chaos::FReal MaxStrain = TNumericLimits::Max() - TNumericLimits::Min(); TArray*> Particles = Prox->GetParticles(); for(Chaos::TPBDRigidClusteredParticleHandle * P : Particles) { if(!P) { continue; } P->SetStrain(MaxStrain); } }); } } } } #if WITH_EDITOR void UGeometryCollectionComponent::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) { Super::PostEditChangeChainProperty(PropertyChangedEvent); if (PropertyChangedEvent.Property && PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UGeometryCollectionComponent, bShowBoneColors)) { FScopedColorEdit EditBoneColor = EditBoneSelection(); EditBoneColor.SetShowBoneColors(bShowBoneColors); MarkRenderStateDirty(); MarkRenderDynamicDataDirty(); } } #endif static void DispatchGeometryCollectionBreakEvent(const FChaosBreakEvent& Event) { if (UGeometryCollectionComponent* const GC = Cast(Event.Component)) { GC->DispatchBreakEvent(Event); } } void UGeometryCollectionComponent::DispatchChaosPhysicsCollisionBlueprintEvents(const FChaosPhysicsCollisionInfo& CollisionInfo) { ReceivePhysicsCollision(CollisionInfo); OnChaosPhysicsCollision.Broadcast(CollisionInfo); } // call when first registering void UGeometryCollectionComponent::RegisterForEvents() { if (BodyInstance.bNotifyRigidBodyCollision || bNotifyBreaks || bNotifyCollisions) { if (bNotifyCollisions || BodyInstance.bNotifyRigidBodyCollision) { EventDispatcher->RegisterForCollisionEvents(this, this); #if INCLUDE_CHAOS GetWorld()->GetPhysicsScene()->GetSolver()->SetGenerateCollisionData(true); #endif } if (bNotifyBreaks) { EventDispatcher->RegisterForBreakEvents(this, &DispatchGeometryCollectionBreakEvent); #if INCLUDE_CHAOS GetWorld()->GetPhysicsScene()->GetSolver()->SetGenerateBreakingData(true); #endif } } } 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 ActivateClusters(Chaos::FPBDRigidsEvolution::FRigidClustering& Clustering, Chaos::TPBDRigidClusteredParticleHandle* Cluster) { if(!Cluster) { return; } if(Cluster->ClusterIds().Id) { ActivateClusters(Clustering, Cluster->Parent()); } Clustering.DeactivateClusterParticle(Cluster); } void UGeometryCollectionComponent::OnRep_RepData(const FGeometryCollectionRepData& OldData) { if(!DynamicCollection) { return; } if(AActor* Owner = GetOwner()) { const int32 NumTransforms = DynamicCollection->Transform.Num(); const int32 NumNewPoses = RepData.Poses.Num(); if(NumTransforms < NumNewPoses) { return; } Chaos::FPhysicsSolver* Solver = GetSolver(*this); for(int32 Index = 0; Index < NumNewPoses; ++Index) { const FGeometryCollectionRepPose& SourcePose = RepData.Poses[Index]; const int32 ParticleIndex = SourcePose.ParticleIndex; if(ParticleIndex >= NumTransforms) { // Out of range continue; } Solver->RegisterSimOneShotCallback([SourcePose, Prox = PhysicsProxy]() { Chaos::TPBDRigidClusteredParticleHandle* Particle = Prox->GetParticles()[SourcePose.ParticleIndex]; Chaos::FPhysicsSolver* Solver = Prox->GetSolver(); Chaos::FPBDRigidsEvolution* Evo = Solver->GetEvolution(); check(Evo); Chaos::FPBDRigidsEvolution::FRigidClustering& Clustering = Evo->GetRigidClustering(); // Set X/R/V/W for next sim step from the replicated state Particle->SetX(SourcePose.Position); Particle->SetR(SourcePose.Rotation); Particle->SetV(SourcePose.LinearVelocity); Particle->SetW(SourcePose.AngularVelocity); if(Particle->ClusterIds().Id) { // This particle is clustered but the remote authority has it activated. Fracture the parent cluster ActivateClusters(Clustering, Particle->Parent()); } else if(Particle->Disabled()) { // We might have disabled the particle - need to reactivate if it's active on the remote. Particle->SetDisabled(false); } // Make sure to wake corrected particles Particle->SetSleeping(false); }); } } } void UGeometryCollectionComponent::UpdateRepData() { 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) { // We're inside a replicating actor and we're the authority - update the rep data const int32 NumTransforms = DynamicCollection->Transform.Num(); RepData.Poses.Reset(NumTransforms); TManagedArray* LinearVelocity = DynamicCollection->FindAttributeTyped("LinearVelocity", FTransformCollection::TransformGroup); TManagedArray* AngularVelocity = DynamicCollection->FindAttributeTyped("AngularVelocity", FTransformCollection::TransformGroup); for(int32 Index = 0; Index < NumTransforms; ++Index) { TManagedArray>& GTParticles = PhysicsProxy->GetExternalParticles(); Chaos::FGeometryParticle* Particle = GTParticles[Index].Get(); if(!DynamicCollection->Active[Index] || DynamicCollection->DynamicState[Index] != static_cast(Chaos::EObjectStateType::Dynamic)) { continue; } const int32 ClusterLevel = GetClusterLevel(RestCollection->GetGeometryCollection().Get(), Index); const bool bLevelValid = !EnableClustering || !bEnableAbandonAfterLevel || ClusterLevel <= ReplicationAbandonClusterLevel; if(!bLevelValid) { const int32 ParentTransformIndex = RestCollection->GetGeometryCollection()->Parent[Index]; TManagedArray* ControlFlags = DynamicCollection->FindAttributeTyped("AuthControl", FTransformCollection::TransformGroup); if(ControlFlags && (*ControlFlags)[ParentTransformIndex]) { (*ControlFlags)[ParentTransformIndex] = false; NetAbandonCluster(ParentTransformIndex); } continue; } RepData.Poses.AddDefaulted(); FGeometryCollectionRepPose& Pose = RepData.Poses.Last(); // No scale transfered - shouldn't be a simulated property Pose.ParticleIndex = Index; Pose.Position = Particle->X(); Pose.Rotation = Particle->R(); if(LinearVelocity) { check(AngularVelocity); Pose.LinearVelocity = (*LinearVelocity)[Index]; Pose.AngularVelocity = (*AngularVelocity)[Index]; } else { Pose.LinearVelocity = FVector::ZeroVector; Pose.AngularVelocity = FVector::ZeroVector; } } RepData.Version++; MARK_PROPERTY_DIRTY_FROM_NAME(UGeometryCollectionComponent, RepData, this); } } 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::NetAbandonCluster_Implementation(int32 TransformIndex) { // Called on clients when the server abandons a particle. TransformIndex is the index of the parent // of that particle, should only get called once per cluster but survives multiple calls if(GetOwnerRole() == ENetRole::ROLE_Authority) { // Owner called abandon - takes no action return; } if(!EnableClustering) { // No clustering information to update return; } if(TransformIndex >= 0 && TransformIndex < DynamicCollection->NumElements(FTransformCollection::TransformGroup)) { int32 ClusterLevel = GetClusterLevel(RestCollection->GetGeometryCollection().Get(), TransformIndex); float Strain = DamageThreshold.IsValidIndex(ClusterLevel) ? DamageThreshold[ClusterLevel] : DamageThreshold.Num() > 0 ? DamageThreshold[0] : 0.0f; if(Strain >= 0) { Chaos::FPhysicsSolver* Solver = GetSolver(*this); Solver->RegisterSimOneShotCallback([Prox = PhysicsProxy, Strain, TransformIndex, Solver]() { Chaos::TPBDRigidClustering& Clustering = Solver->GetEvolution()->GetRigidClustering(); Chaos::FPBDRigidClusteredParticleHandle* Parent = Prox->GetParticles()[TransformIndex]; if(!Parent->Disabled()) { SetHierarchyStrain(Parent, Clustering.GetChildrenMap(), Strain); // We know the server must have fractured this cluster, so repeat here Clustering.DeactivateClusterParticle(Parent); } }); } } } void UGeometryCollectionComponent::InitConstantData(FGeometryCollectionConstantData* ConstantData) const { // Constant data should all be moved to the DDC as time permits. 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& UV = Collection->UV; const TManagedArray& Color = Collection->Color; const TManagedArray& BoneColors = Collection->BoneColor; 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->UVs = TArray(UV.GetData(), UV.Num()); ConstantData->Colors = TArray(Color.GetData(), Color.Num()); ConstantData->BoneColors.AddUninitialized(NumPoints); ParallelFor(NumPoints, [&](const int32 InPointIndex) { const int32 BoneIndex = ConstantData->BoneMap[InPointIndex]; ConstantData->BoneColors[InPointIndex] = BoneColors[BoneIndex]; }); 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 const TManagedArray& MaterialIndex = Collection->MaterialIndex; const int32 NumFaceGroupEntries = Collection->NumElements(FGeometryCollection::FacesGroup); for (int FaceIndex = 0; FaceIndex < NumFaceGroupEntries; ++FaceIndex) { NumIndices += static_cast(Visible[FaceIndex]); } ConstantData->Indices.AddUninitialized(NumIndices); for (int IndexIdx = 0, cdx = 0; IndexIdx < NumFaceGroupEntries; ++IndexIdx) { if (Visible[MaterialIndex[IndexIdx]]) { 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 (!Visible[MaterialIndex[TriangleIndex]]) { Section.FirstIndex -= 3; } } for (int32 TriangleIndex = 0; TriangleIndex < Sections[SectionIndex].NumTriangles; TriangleIndex++) { if (!Visible[MaterialIndex[Sections[SectionIndex].FirstIndex / 3 + TriangleIndex]]) { 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 TManagedArray& FaceStart = Collection->FaceStart; const TManagedArray& FaceCount = Collection->FaceCount; 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 (Visible[FaceIndex] && MaterialID[FaceIndex] % 2 == 0) { 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->RestTransforms = MoveTemp(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 (CachePlayback && CacheParameters.TargetCache) { // If we are already on the current cached frame, return if (FMath::IsNearlyEqual(CurrentCacheTime, DesiredCacheTime) && GlobalMatrices.Num() != 0) { return DynamicData; } // #todo(dmp): find a better place to calculate and store this float CacheDt = CacheParameters.TargetCache->GetData()->GetDt(); // If we want the state before the first cached frame, then the collection doesn't need to be dynamic since it'll render the pre-fractured geometry const bool bIsCacheDynamic = DesiredCacheTime > CacheDt; // Bad case here. Sequencer starts at non zero position We cannot correctly reconstruct the current frame, so give a warning const bool bOutOfSync = GlobalMatrices.Num() == 0; // If the input simulation time to playback is the first frame, reset simulation time. if (bOutOfSync || DesiredCacheTime <= CacheDt || FMath::IsNearlyEqual(CurrentCacheTime, FLT_MAX)) { GeometryCollectionAlgo::GlobalMatrices(GetTransformArray(), GetParentArray(), GlobalMatrices); DynamicData = GDynamicDataPool.Allocate(); DynamicData->PrevTransforms = GlobalMatrices; DynamicData->Transforms = GlobalMatrices; DynamicData->ChangedCount = GlobalMatrices.Num(); DynamicData->IsDynamic = bIsCacheDynamic; DynamicData->IsLoading = GetIsObjectLoading(); EventsPlayed.Reset(); EventsPlayed.AddDefaulted(CacheParameters.TargetCache->GetData()->Records.Num()); if (bOutOfSync) { UE_LOG(UGCC_LOG, Warning, TEXT("Cache out of sync - must rewind sequencer to start frame")); } else { CurrentCacheTime = DesiredCacheTime; } } else if (DesiredCacheTime >= CurrentCacheTime) { const TManagedArray& Transform = RestCollection->GetGeometryCollection()->Transform; const TManagedArray>& Children = GetChildrenArray(); const TManagedArray& MassToLocal = RestCollection->GetGeometryCollection()->GetAttribute("MassToLocal", FGeometryCollection::TransformGroup); int32 NumSteps = floor((DesiredCacheTime - CurrentCacheTime) / CacheDt); float LastDt = FMath::Fmod(DesiredCacheTime - CurrentCacheTime, CacheDt); NumSteps += LastDt > SMALL_NUMBER ? 1 : 0; FTransform ActorToWorld = GetComponentTransform(); DynamicData = GDynamicDataPool.Allocate(); DynamicData->ChangedCount = 0; DynamicData->IsDynamic = bIsCacheDynamic; DynamicData->IsLoading = GetIsObjectLoading(); bool HasAnyActiveTransforms = false; // Jump ahead in increments of CacheDt evaluating the cache until we reach our desired time for (int32 Step = 0; Step < NumSteps; ++Step) { float TimeIncrement = Step == NumSteps - 1 ? LastDt : CacheDt; CurrentCacheTime += TimeIncrement; DynamicData->PrevTransforms = GlobalMatrices; const FRecordedFrame* FirstFrame = nullptr; const FRecordedFrame* SecondFrame = nullptr; CacheParameters.TargetCache->GetData()->GetFramesForTime(CurrentCacheTime, FirstFrame, SecondFrame); if (FirstFrame && !SecondFrame) { const TArray& FirstFrameTransforms = FirstFrame->Transforms; const TArray& TransformIndices = FirstFrame->TransformIndices; const int32 NumActives = TransformIndices.Num(); if (NumActives > 0) { HasAnyActiveTransforms = true; } for (int32 TransformIndex = 0; TransformIndex < NumActives; ++TransformIndex) { const int32 InternalIndexTmp = TransformIndices[TransformIndex]; if (InternalIndexTmp >= GlobalMatrices.Num()) { UE_LOG ( UGCC_LOG, Error, TEXT("%s: TargetCache (%s) is out of sync with GeometryCollection. Regenerate the cache."), *RestCollection->GetName(), *CacheParameters.TargetCache->GetName() ); DynamicData->PrevTransforms = GlobalMatrices; DynamicData->Transforms = GlobalMatrices; DynamicData->ChangedCount = GlobalMatrices.Num(); return DynamicData; } // calculate global matrix for current FTransform ParticleToWorld = FirstFrameTransforms[TransformIndex]; FTransform CurrGlobalTransform = MassToLocal[InternalIndexTmp].GetRelativeTransformReverse(ParticleToWorld).GetRelativeTransform(ActorToWorld); CurrGlobalTransform.NormalizeRotation(); GlobalMatrices[InternalIndexTmp] = CurrGlobalTransform.ToMatrixWithScale(); // Traverse from active parent node down to all children and set global transforms GeometryCollectionAlgo::GlobalMatricesFromRoot(InternalIndexTmp, Transform, Children, GlobalMatrices); } } else if (FirstFrame && SecondFrame && CurrentCacheTime > FirstFrame->Timestamp) { const float Alpha = (CurrentCacheTime - FirstFrame->Timestamp) / (SecondFrame->Timestamp - FirstFrame->Timestamp); checkSlow(0 <= Alpha && Alpha <= 1.0f); const int32 NumActives = SecondFrame->TransformIndices.Num(); if (NumActives > 0) { HasAnyActiveTransforms = true; } for (int32 Index = 0; Index < NumActives; ++Index) { const int32 InternalIndexTmp = SecondFrame->TransformIndices[Index]; // Check if transform index is valid if (InternalIndexTmp >= GlobalMatrices.Num()) { UE_LOG ( UGCC_LOG, Error, TEXT("%s: TargetCache (%s) is out of sync with GeometryCollection. Regenerate the cache."), *RestCollection->GetName(), *CacheParameters.TargetCache->GetName() ); DynamicData->PrevTransforms = GlobalMatrices; DynamicData->Transforms = GlobalMatrices; DynamicData->ChangedCount = GlobalMatrices.Num(); return DynamicData; } const int32 PreviousIndexSlot = Index < SecondFrame->PreviousTransformIndices.Num() ? SecondFrame->PreviousTransformIndices[Index] : INDEX_NONE; if (PreviousIndexSlot != INDEX_NONE) { FTransform ParticleToWorld; ParticleToWorld.Blend(FirstFrame->Transforms[PreviousIndexSlot], SecondFrame->Transforms[Index], Alpha); FTransform CurrGlobalTransform = MassToLocal[InternalIndexTmp].GetRelativeTransformReverse(ParticleToWorld).GetRelativeTransform(ActorToWorld); CurrGlobalTransform.NormalizeRotation(); GlobalMatrices[InternalIndexTmp] = CurrGlobalTransform.ToMatrixWithScale(); // Traverse from active parent node down to all children and set global transforms GeometryCollectionAlgo::GlobalMatricesFromRoot(InternalIndexTmp, Transform, Children, GlobalMatrices); } else { FTransform ParticleToWorld = SecondFrame->Transforms[Index]; FTransform CurrGlobalTransform = MassToLocal[InternalIndexTmp].GetRelativeTransformReverse(ParticleToWorld).GetRelativeTransform(ActorToWorld); CurrGlobalTransform.NormalizeRotation(); GlobalMatrices[InternalIndexTmp] = CurrGlobalTransform.ToMatrixWithScale(); // Traverse from active parent node down to all children and set global transforms GeometryCollectionAlgo::GlobalMatricesFromRoot(InternalIndexTmp, Transform, Children, GlobalMatrices); } } } DynamicData->Transforms = GlobalMatrices; } // Check if transforms at start of this tick are the same as what is calculated from the cache if (HasAnyActiveTransforms) { DynamicData->DetermineChanges(); } } } else 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->PrevTransforms = GlobalMatrices; DynamicData->Transforms = GlobalMatrices; DynamicData->ChangedCount = GlobalMatrices.Num(); } else { // Copy existing global matrices into prev transforms DynamicData->PrevTransforms = 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->PrevTransforms = GlobalMatrices; DynamicData->ChangedCount = GlobalMatrices.Num(); bComputeChanges = false; // Optimization to just force all transforms as changed and skip comparison } DynamicData->Transforms = 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) { 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) { 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 WITH_CHAOS if (PhysicsProxy) { PhysicsProxy->SetWorldTransform(GetComponentTransform()); } #endif } 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 WITH_CHAOS //if (bRenderStateDirty && DynamicCollection) //todo: always send for now if (RestCollection) { if(CHAOS_ENSURE(DynamicCollection)) //, TEXT("No dynamic collection available for component %s during tick."), *GetName())) { 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(); 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(); } } } } } #endif } void UGeometryCollectionComponent::OnRegister() { #if WITH_CHAOS //UE_LOG(UGCC_LOG, Log, TEXT("GeometryCollectionComponent[%p]::OnRegister()[%p]"), this,RestCollection ); ResetDynamicCollection(); #if WITH_EDITOR FScopedColorEdit ColorEdit(this); ColorEdit.ResetBoneSelection(); ColorEdit.ResetHighlightedBones(); #endif #endif // WITH_CHAOS SetIsReplicated(bEnableReplication); InitializeEmbeddedGeometry(); Super::OnRegister(); } void UGeometryCollectionComponent::ResetDynamicCollection() { bool bCreateDynamicCollection = true; #if WITH_EDITOR bCreateDynamicCollection = false; if (UWorld* World = GetWorld()) { if(World->IsGameWorld()) { bCreateDynamicCollection = true; } } #endif //UE_LOG(UGCC_LOG, Log, TEXT("GeometryCollectionComponent[%p]::ResetDynamicCollection()"), static_cast(this)); if (bCreateDynamicCollection && RestCollection) { DynamicCollection = MakeUnique(); for (const auto DynamicArray : CopyOnWriteAttributeList) { *DynamicArray = nullptr; } GetTransformArrayCopyOnWrite(); GetParentArrayCopyOnWrite(); GetChildrenArrayCopyOnWrite(); GetSimulationTypeArrayCopyOnWrite(); GetStatusFlagsArrayCopyOnWrite(); SetRenderStateDirty(); } if (RestCollection) { CalculateGlobalMatrices(); CalculateLocalBounds(); } } void UGeometryCollectionComponent::OnCreatePhysicsState() { /*#if WITH_PHYSX DummyBodySetup = NewObject(this, UBodySetup::StaticClass()); DummyBodySetup->AggGeom.BoxElems.Add(FKBoxElem(1.0f)); DummyBodyInstance.InitBody(DummyBodySetup, GetComponentToWorld(), this, nullptr); DummyBodyInstance.bNotifyRigidBodyCollision = BodyInstance.bNotifyRigidBodyCollision; #endif */ // Skip the chain - don't care about body instance setup UActorComponent::OnCreatePhysicsState(); if (!Simulating) IsObjectLoading = false; // just mark as loaded if we are simulating. /*#if WITH_PHYSX DummyBodyInstance.SetCollisionEnabled(ECollisionEnabled::QueryOnly); DummyBodyInstance.SetResponseToAllChannels(ECR_Block); #endif */ #if WITH_CHAOS // 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->EnsureDataIsCooked(); } #endif const bool bValidWorld = GetWorld() && GetWorld()->IsGameWorld(); const bool bValidCollection = DynamicCollection && DynamicCollection->Transform.Num() > 0; if (bValidWorld && bValidCollection) { FPhysxUserData::Set(&PhysicsUserData, this); FSimulationParameters SimulationParameters; { #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) SimulationParameters.Name = GetPathName(); #endif EClusterConnectionTypeEnum ClusterCollectionType = ClusterConnectionType_DEPRECATED; if (RestCollection) { RestCollection->GetSharedSimulationParams(SimulationParameters.Shared); SimulationParameters.RestCollection = RestCollection->GetGeometryCollection().Get(); ClusterCollectionType = RestCollection->ClusterConnectionType; } SimulationParameters.Simulating = Simulating; SimulationParameters.EnableClustering = EnableClustering; SimulationParameters.ClusterGroupIndex = EnableClustering ? ClusterGroupIndex : 0; SimulationParameters.MaxClusterLevel = MaxClusterLevel; SimulationParameters.bUseSizeSpecificDamageThresholds = bUseSizeSpecificDamageThreshold; SimulationParameters.DamageThreshold = DamageThreshold; SimulationParameters.ClusterConnectionMethod = (Chaos::FClusterCreationParameters::EConnectionMethod)ClusterCollectionType; SimulationParameters.CollisionGroup = CollisionGroup; SimulationParameters.CollisionSampleFraction = CollisionSampleFraction; SimulationParameters.InitialVelocityType = InitialVelocityType; SimulationParameters.InitialLinearVelocity = InitialLinearVelocity; SimulationParameters.InitialAngularVelocity = InitialAngularVelocity; SimulationParameters.bClearCache = true; SimulationParameters.ObjectType = ObjectType; SimulationParameters.CacheType = CacheParameters.CacheMode; SimulationParameters.ReverseCacheBeginTime = CacheParameters.ReverseCacheBeginTime; SimulationParameters.CollisionData.SaveCollisionData = CacheParameters.SaveCollisionData; SimulationParameters.CollisionData.DoGenerateCollisionData = CacheParameters.DoGenerateCollisionData; SimulationParameters.CollisionData.CollisionDataSizeMax = CacheParameters.CollisionDataSizeMax; SimulationParameters.CollisionData.DoCollisionDataSpatialHash = CacheParameters.DoCollisionDataSpatialHash; SimulationParameters.CollisionData.CollisionDataSpatialHashRadius = CacheParameters.CollisionDataSpatialHashRadius; SimulationParameters.CollisionData.MaxCollisionPerCell = CacheParameters.MaxCollisionPerCell; SimulationParameters.BreakingData.SaveBreakingData = CacheParameters.SaveBreakingData; SimulationParameters.BreakingData.DoGenerateBreakingData = CacheParameters.DoGenerateBreakingData; SimulationParameters.BreakingData.BreakingDataSizeMax = CacheParameters.BreakingDataSizeMax; SimulationParameters.BreakingData.DoBreakingDataSpatialHash = CacheParameters.DoBreakingDataSpatialHash; SimulationParameters.BreakingData.BreakingDataSpatialHashRadius = CacheParameters.BreakingDataSpatialHashRadius; SimulationParameters.BreakingData.MaxBreakingPerCell = CacheParameters.MaxBreakingPerCell; SimulationParameters.TrailingData.SaveTrailingData = CacheParameters.SaveTrailingData; SimulationParameters.TrailingData.DoGenerateTrailingData = CacheParameters.DoGenerateTrailingData; SimulationParameters.TrailingData.TrailingDataSizeMax = CacheParameters.TrailingDataSizeMax; SimulationParameters.TrailingData.TrailingMinSpeedThreshold = CacheParameters.TrailingMinSpeedThreshold; SimulationParameters.TrailingData.TrailingMinVolumeThreshold = CacheParameters.TrailingMinVolumeThreshold; SimulationParameters.RemoveOnFractureEnabled = SimulationParameters.Shared.RemoveOnFractureIndices.Num() > 0; SimulationParameters.WorldTransform = GetComponentToWorld(); SimulationParameters.UserData = static_cast(&PhysicsUserData); UPhysicalMaterial* EnginePhysicalMaterial = GetPhysicalMaterial(); if(ensure(EnginePhysicalMaterial)) { SimulationParameters.PhysicalMaterialHandle = EnginePhysicalMaterial->GetPhysicsMaterial(); } } // // Called from FGeometryCollectionPhysicsProxy::Initialize() // auto InitFunc = [this](FSimulationParameters& InParams) { #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) InParams.Name = GetPathName(); #endif GetInitializationCommands(InParams.InitializationCommands); UGeometryCollectionCache* Cache = CacheParameters.TargetCache; if(Cache && CacheParameters.CacheMode != EGeometryCollectionCacheType::None) { bool bCacheValid = false; switch(CacheParameters.CacheMode) { case EGeometryCollectionCacheType::Record: case EGeometryCollectionCacheType::RecordAndPlay: bCacheValid = Cache->CompatibleWithForRecord(RestCollection); break; case EGeometryCollectionCacheType::Play: bCacheValid = Cache->CompatibleWithForPlayback(RestCollection); break; default: check(false); break; } if(bCacheValid) { InParams.RecordedTrack = (InParams.IsCachePlaying() && CacheParameters.TargetCache) ? CacheParameters.TargetCache->GetData() : nullptr; } else { // We attempted to utilize a cache that was not compatible with this component. Report and do not use UE_LOG(LogChaos, Error, TEXT("Geometry collection '%s' attempted to use cache '%s' but it is not compatible."), *GetName(), *(CacheParameters.TargetCache->GetName())); InParams.RecordedTrack = nullptr; InParams.CacheType = EGeometryCollectionCacheType::None; CacheParameters.CacheMode = EGeometryCollectionCacheType::None; #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) FMessageLog("PIE").Error() ->AddToken(FTextToken::Create(NSLOCTEXT("GeomCollectionComponent", "BadCache_01", "Geometry collection"))) ->AddToken(FUObjectToken::Create(this)) ->AddToken(FTextToken::Create(NSLOCTEXT("GeomCollectionComponent", "BadCache_02", "attempted to use cache"))) ->AddToken(FUObjectToken::Create(CacheParameters.TargetCache)) ->AddToken(FTextToken::Create(NSLOCTEXT("GeomCollectionComponent", "BadCache_03", "but it is not compatible"))); #endif // !(UE_BUILD_SHIPPING || UE_BUILD_TEST) } } }; auto CacheSyncFunc = [this](const FGeometryCollectionResults& Results) { #if GEOMETRYCOLLECTION_DEBUG_DRAW const bool bHasNumParticlesChanged = (NumParticlesAdded != Results.NumParticlesAdded); // Needs to be evaluated before NumParticlesAdded gets updated #endif // #if GEOMETRYCOLLECTION_DEBUG_DRAW //RigidBodyIds.Init(Results.RigidBodyIds); BaseRigidBodyIndex = Results.BaseIndex; NumParticlesAdded = Results.NumParticlesAdded; DisabledFlags = Results.DisabledStates; if (!IsObjectDynamic && Results.IsObjectDynamic) { IsObjectDynamic = Results.IsObjectDynamic; NotifyGeometryCollectionPhysicsStateChange.Broadcast(this); SwitchRenderModels(GetOwner()); } if (IsObjectLoading && !Results.IsObjectLoading) { IsObjectLoading = Results.IsObjectLoading; NotifyGeometryCollectionPhysicsLoadingStateChange.Broadcast(this); } WorldBounds = Results.WorldBounds; // Update replication data for clients if necessary UpdateRepData(); #if GEOMETRYCOLLECTION_DEBUG_DRAW // Notify debug draw componentUGeometryCollectionDebugDrawComponent of particle changes if (bHasNumParticlesChanged) { if (const AGeometryCollectionActor* const Owner = Cast(GetOwner())) { if (UGeometryCollectionDebugDrawComponent* const GeometryCollectionDebugDrawComponent = Owner->GetGeometryCollectionDebugDrawComponent()) { GeometryCollectionDebugDrawComponent->OnClusterChanged(); } } } #endif // #if GEOMETRYCOLLECTION_DEBUG_DRAW }; auto FinalSyncFunc = [this](const FRecordedTransformTrack& InTrack) { #if WITH_EDITOR && WITH_EDITORONLY_DATA if (CacheParameters.CacheMode == EGeometryCollectionCacheType::Record && InTrack.Records.Num() > 0) { Modify(); if (!CacheParameters.TargetCache) { CacheParameters.TargetCache = UGeometryCollectionCache::CreateCacheForCollection(RestCollection); } if (CacheParameters.TargetCache) { // Queue this up to be dirtied after PIE ends FPhysScene_Chaos* Scene = GetInnerChaosScene(); CacheParameters.TargetCache->PreEditChange(nullptr); CacheParameters.TargetCache->Modify(); CacheParameters.TargetCache->SetFromRawTrack(InTrack); CacheParameters.TargetCache->PostEditChange(); Scene->AddPieModifiedObject(CacheParameters.TargetCache); if (EditorActor) { UGeometryCollectionComponent* EditorComponent = Cast(EditorUtilities::FindMatchingComponentInstance(this, EditorActor)); if (EditorComponent) { EditorComponent->PreEditChange(FindFProperty(EditorComponent->GetClass(), GET_MEMBER_NAME_CHECKED(UGeometryCollectionComponent, CacheParameters))); EditorComponent->Modify(); EditorComponent->CacheParameters.TargetCache = CacheParameters.TargetCache; EditorComponent->PostEditChange(); Scene->AddPieModifiedObject(EditorComponent); Scene->AddPieModifiedObject(EditorActor); } EditorActor = nullptr; } } } #endif }; // If the Component is set to Dynamic, we look to the RestCollection for initial dynamic state override per transform. TManagedArray & DynamicState = DynamicCollection->DynamicState; if (ObjectType != EObjectStateTypeEnum::Chaos_Object_UserDefined) { if (RestCollection && (ObjectType == 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(ObjectType) : InitialDynamicState[i]; } } else { for (int i = 0; i < DynamicState.Num(); i++) { DynamicState[i] = static_cast(ObjectType); } } } TManagedArray & Active = DynamicCollection->Active; { for (int i = 0; i < Active.Num(); i++) { Active[i] = Simulating; } } TManagedArray & CollisionGroupArray = DynamicCollection->CollisionGroup; { for (int i = 0; i < CollisionGroupArray.Num(); i++) { CollisionGroupArray[i] = CollisionGroup; } } // 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; 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; } PhysicsProxy = new FGeometryCollectionPhysicsProxy(this, *DynamicCollection, SimulationParameters, InitialQueryFilter, InitialSimFilter, InitFunc, CacheSyncFunc, FinalSyncFunc); FPhysScene_Chaos* Scene = GetInnerChaosScene(); Scene->AddObject(this, PhysicsProxy); RegisterForEvents(); } } #if WITH_PHYSX && !WITH_CHAOS_NEEDS_TO_BE_FIXED if (PhysicsProxy) { GlobalGeomCollectionAccelerator.AddComponent(this); } #endif #endif // WITH_CHAOS } void UGeometryCollectionComponent::OnDestroyPhysicsState() { UActorComponent::OnDestroyPhysicsState(); #if WITH_CHAOS #if WITH_PHYSX && !WITH_CHAOS_NEEDS_TO_BE_FIXED GlobalGeomCollectionAccelerator.RemoveComponent(this); #endif #if WITH_PHYSX if(DummyBodyInstance.IsValidBodyInstance()) { DummyBodyInstance.TermBody(); } #endif if(PhysicsProxy) { FPhysScene_Chaos* Scene = GetInnerChaosScene(); Scene->RemoveObject(PhysicsProxy); InitializationState = ESimulationInitializationState::Unintialized; // Discard the pointer (cleanup happens through the scene or dedicated thread) PhysicsProxy = nullptr; } #endif } 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::SetRestCollection(const UGeometryCollection* RestCollectionIn) { //UE_LOG(UGCC_LOG, Log, TEXT("GeometryCollectionComponent[%p]::SetRestCollection()"), this); if (RestCollectionIn) { RestCollection = RestCollectionIn; CalculateGlobalMatrices(); CalculateLocalBounds(); if (!IsEmbeddedGeometryValid()) { InitializeEmbeddedGeometry(); } //ResetDynamicCollection(); } } FGeometryCollectionEdit::FGeometryCollectionEdit(UGeometryCollectionComponent* InComponent, GeometryCollection::EEditUpdate InEditUpdate) : Component(InComponent) , EditUpdate(InEditUpdate) { bHadPhysicsState = Component->HasValidPhysicsState(); if (EnumHasAnyFlags(EditUpdate, GeometryCollection::EEditUpdate::Physics) && bHadPhysicsState) { Component->DestroyPhysicsState(); } } FGeometryCollectionEdit::~FGeometryCollectionEdit() { #if WITH_EDITOR if (!!EditUpdate) { if (EnumHasAnyFlags(EditUpdate, GeometryCollection::EEditUpdate::Dynamic)) { Component->ResetDynamicCollection(); } if (EnumHasAnyFlags(EditUpdate, GeometryCollection::EEditUpdate::Rest) && GetRestCollection()) { GetRestCollection()->Modify(); } 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::SetSelectedBones(const TArray& SelectedBonesIn) { bUpdated = true; Component->SelectedBones = SelectedBonesIn; } void FScopedColorEdit::AppendSelectedBones(const TArray& SelectedBonesIn) { bUpdated = true; Component->SelectedBones.Append(SelectedBonesIn); } void FScopedColorEdit::ToggleSelectedBones(const TArray& SelectedBonesIn) { bUpdated = true; for (int32 BoneIndex : SelectedBonesIn) { if (Component->SelectedBones.Contains(BoneIndex)) { Component->SelectedBones.Remove(BoneIndex); } else { Component->SelectedBones.Add(BoneIndex); } } } 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(); } void FScopedColorEdit::ResetBoneSelection() { if (Component->SelectedBones.Num() > 0) { bUpdated = true; } Component->SelectedBones.Empty(); } 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: { TArray Roots; FGeometryCollectionClusteringUtility::GetRootBones(GeometryCollectionPtr.Get(), Roots); ResetBoneSelection(); for (int32 RootElement : Roots) { TArray LeafBones; FGeometryCollectionClusteringUtility::GetLeafBones(GeometryCollectionPtr.Get(), RootElement, true, LeafBones); AppendSelectedBones(LeafBones); } } break; case GeometryCollection::ESelectionMode::InverseGeometry: { TArray Roots; FGeometryCollectionClusteringUtility::GetRootBones(GeometryCollectionPtr.Get(), Roots); TArray NewSelection; for (int32 RootElement : Roots) { TArray LeafBones; FGeometryCollectionClusteringUtility::GetLeafBones(GeometryCollectionPtr.Get(), RootElement, true, LeafBones); for (int32 Element : LeafBones) { if (!IsBoneSelected(Element)) { NewSelection.Push(Element); } } } ResetBoneSelection(); AppendSelectedBones(NewSelection); } break; case GeometryCollection::ESelectionMode::Neighbors: { if (ensureMsgf(GeometryCollectionPtr->HasAttribute("Proximity", FGeometryCollection::GeometryGroup), TEXT("Must build breaking group for neighbor based selection"))) { const TManagedArray& TransformIndex = GeometryCollectionPtr->TransformIndex; const TManagedArray& TransformToGeometryIndex = GeometryCollectionPtr->TransformToGeometryIndex; const TManagedArray>& Proximity = GeometryCollectionPtr->GetAttribute>("Proximity", FGeometryCollection::GeometryGroup); const TArray SelectedBones = GetSelectedBones(); TArray NewSelection; for (int32 Bone : SelectedBones) { NewSelection.AddUnique(Bone); const TSet &Neighbors = Proximity[TransformToGeometryIndex[Bone]]; for (int32 NeighborGeometryIndex : Neighbors) { NewSelection.AddUnique(TransformIndex[NeighborGeometryIndex]); } } ResetBoneSelection(); AppendSelectedBones(NewSelection); } } break; case GeometryCollection::ESelectionMode::Siblings: { const TManagedArray& Parents = GeometryCollectionPtr->Parent; const TManagedArray>& Children = GeometryCollectionPtr->Children; const TArray SelectedBones = GetSelectedBones(); TArray NewSelection; for (int32 Bone : SelectedBones) { int32 ParentBone = Parents[Bone]; if (ParentBone != FGeometryCollection::Invalid) { for (int32 Child : Children[ParentBone]) { NewSelection.AddUnique(Child); } } } ResetBoneSelection(); AppendSelectedBones(NewSelection); } break; case GeometryCollection::ESelectionMode::AllInCluster: { const TManagedArray& Parents = GeometryCollectionPtr->Parent; const TArray SelectedBones = GetSelectedBones(); TArray NewSelection; for (int32 Bone : SelectedBones) { int32 ParentBone = Parents[Bone]; TArray LeafBones; FGeometryCollectionClusteringUtility::GetLeafBones(GeometryCollectionPtr.Get(), ParentBone, true, LeafBones); for (int32 Element : LeafBones) { NewSelection.AddUnique(Element); } } ResetBoneSelection(); AppendSelectedBones(NewSelection); } break; default: check(false); // unexpected selection mode break; } const TArray& SelectedBones = GetSelectedBones(); SetHighlightedBones(SelectedBones); } } bool FScopedColorEdit::IsBoneHighlighted(int BoneIndex) const { return Component->HighlightedBones.Contains(BoneIndex); } void FScopedColorEdit::SetHighlightedBones(const TArray& HighlightedBonesIn) { if (Component->HighlightedBones != HighlightedBonesIn) { bUpdated = true; Component->HighlightedBones = HighlightedBonesIn; } } 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); 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->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; } } // 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) { FName TargetName = GetGeometryCollectionPhysicsTypeName(EGeometryCollectionPhysicsTypeEnum::Chaos_DynamicState); DispatchFieldCommand({ TargetName,new FRadialIntMask(Radius, Position, (int32)Chaos::EObjectStateType::Dynamic, (int32)Chaos::EObjectStateType::Kinematic, ESetMaskConditionType::Field_Set_IFF_NOT_Interior) }); } void UGeometryCollectionComponent::ApplyPhysicsField(bool Enabled, EGeometryCollectionPhysicsTypeEnum Target, UFieldSystemMetaData* MetaData, UFieldNodeBase* Field) { if (Enabled && Field) { FFieldSystemCommand Command = FFieldObjectCommands::CreateFieldCommand(GetGeometryCollectionPhysicsTypeName(Target), Field, MetaData); DispatchFieldCommand(Command); } } void UGeometryCollectionComponent::DispatchFieldCommand(const FFieldSystemCommand& InCommand) { if (PhysicsProxy) { FChaosSolversModule* ChaosModule = FChaosSolversModule::GetModule(); checkSlow(ChaosModule); auto Solver = PhysicsProxy->GetSolver(); Solver->EnqueueCommandImmediate([Solver, PhysicsProxy = this->PhysicsProxy, NewCommand = InCommand]() { // 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) { CombinedCommmands.Add(FieldSystemActor->GetFieldSystemComponent()->ConstructionCommands.BuildFieldCommand(CommandIndex)); } } // Legacy path : only there for old levels. New ones will have the commands directly stored onto the component else if (FieldSystemActor->GetFieldSystemComponent()->GetFieldSystem()) { for (const FFieldSystemCommand& Command : FieldSystemActor->GetFieldSystemComponent()->GetFieldSystem()->Commands) { FFieldSystemCommand NewCommand = { Command.TargetAttribute, Command.RootNode->NewCopy() }; for (auto& Elem : Command.MetaData) { NewCommand.MetaData.Add(Elem.Key, TUniquePtr(Elem.Value->NewCopy())); } CombinedCommmands.Add(NewCommand); } } } } } } FPhysScene_Chaos* UGeometryCollectionComponent::GetInnerChaosScene() const { if (ChaosSolverActor) { return ChaosSolverActor->GetPhysicsScene().Get(); } else { #if INCLUDE_CHAOS if (ensure(GetOwner()) && ensure(GetOwner()->GetWorld())) { return GetOwner()->GetWorld()->GetPhysicsScene(); } check(GWorld); return GWorld->GetPhysicsScene(); #else return nullptr; #endif } } 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(); const TManagedArray& BoundingBoxes = GetBoundingBoxArray(); const TManagedArray& TransformIndices = GetTransformIndexArray(); const int32 NumBoxes = BoundingBoxes.Num(); for (int32 BoxIdx = 0; BoxIdx < NumBoxes; ++BoxIdx) { const int32 TransformIndex = TransformIndices[BoxIdx]; if (GetRestCollection()->GetGeometryCollection()->IsGeometry(TransformIndex)) { LocalBounds += BoundingBoxes[BoxIdx]; } } } 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 { // Have to fully rebuild GeometryCollectionAlgo::GlobalMatrices(GetTransformArray(), GetParentArray(), GlobalMatrices); } #if WITH_EDITOR if (GlobalMatrices.Num() > 0) { if (RestCollection->GetGeometryCollection()->HasAttribute("ExplodedVector", FGeometryCollection::TransformGroup)) { const TManagedArray& ExplodedVectors = RestCollection->GetGeometryCollection()->GetAttribute("ExplodedVector", FGeometryCollection::TransformGroup); check(GlobalMatrices.Num() == ExplodedVectors.Num()); for (int32 tt = 0, nt = GlobalMatrices.Num(); tt < nt; ++tt) { GlobalMatrices[tt] = GlobalMatrices[tt].ConcatTranslation(ExplodedVectors[tt]); } } } #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; } } // #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) { 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); ISMC->RegisterComponent(); EmbeddedGeometryComponents.Add(ISMC); } } } CalculateGlobalMatrices(); RefreshEmbeddedGeometry(); } }