// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "GeometryCollection/GeometryCollectionComponent.h" #include "Async/ParallelFor.h" #include "Components/BoxComponent.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 "Chaos/DebugDrawQueue.h" #include "DrawDebugHelpers.h" #include "Modules/ModuleManager.h" #include "ChaosSolversModule.h" #include "ChaosStats.h" #include "SolverObjects/GeometryCollectionPhysicsObject.h" #include "PBDRigidsSolver.h" #if WITH_EDITOR #include "AssetToolsModule.h" #include "Editor.h" #endif #include "PhysicsEngine/BodySetup.h" #include "PhysicsEngine/BodyInstance.h" #include "Chaos/ChaosGameplayEventDispatcher.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 #include "GeometryCollectionComponent.ispc.generated.h" #endif DEFINE_LOG_CATEGORY_STATIC(UGCC_LOG, Error, All); #if INCLUDE_CHAOS && WITH_PHYSX && !WITH_CHAOS_NEEDS_TO_BE_FIXED FGeometryCollectionSQAccelerator GlobalGeomCollectionAccelerator; //todo(ocohen): proper lifetime management needed void HackRegisterGeomAccelerator(UGeometryCollectionComponent& Component) { if (UWorld* World = Component.GetWorld()) { if (FPhysScene* PhysScene = World->GetPhysicsScene()) { if (FSQAcceleratorUnion* SQAccelerationUnion = PhysScene->GetSQAcceleratorUnion()) { SQAccelerationUnion->AddSQAccelerator(&GlobalGeomCollectionAccelerator); } } } } #endif 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}) , ClusterConnectionType(EClusterConnectionTypeEnum::Chaos_PointImplicit) , 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) , bRenderStateDirty(true) , bShowBoneColors(false) , bEnableBoneSelection(false) , ViewLevel(-1) , NavmeshInvalidationTimeSliceIndex(0) , IsObjectDynamic(false) , IsObjectLoading(true) , PhysicsObject(nullptr) #if WITH_EDITOR && WITH_EDITORONLY_DATA , EditorActor(nullptr) #endif #if GEOMETRYCOLLECTION_EDITOR_SELECTION , bIsTransformSelectionModeEnabled(false) #endif // #if GEOMETRYCOLLECTION_EDITOR_SELECTION { PrimaryComponentTick.bCanEverTick = true; bTickInEditor = true; bAutoActivate = true; static uint32 GlobalNavMeshInvalidationCounter = 0; //space these out over several frames (3 is arbitrary) GlobalNavMeshInvalidationCounter += 3; NavmeshInvalidationTimeSliceIndex = GlobalNavMeshInvalidationCounter; ChaosMaterial = MakeUnique>(); WorldBounds = FBoxSphereBounds(FBox(ForceInit)); // default current cache time CurrentCacheTime = MAX_flt; // Buffer for rolling cache of past N=3 transforms being equal. TransformsAreEqual.AddDefaulted(3); TransformsAreEqualIndex = 0; SetGenerateOverlapEvents(false); } #if INCLUDE_CHAOS Chaos::FPBDRigidsSolver* GetSolver(const UGeometryCollectionComponent& GeometryCollectionComponent) { return GeometryCollectionComponent.ChaosSolverActor != nullptr ? GeometryCollectionComponent.ChaosSolverActor->GetSolver() : GeometryCollectionComponent.GetOwner()->GetWorld()->PhysicsScene_Chaos->GetSolver(); } #endif void UGeometryCollectionComponent::BeginPlay() { Super::BeginPlay(); #if INCLUDE_CHAOS #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); // ---------- } // ---------- }); // ---------- } // ---------- }); ////////////////////////////////////////////////////////////////////////// FChaosSolversModule* ChaosModule = FModuleManager::Get().GetModulePtr("ChaosSolvers"); if (ChaosModule != nullptr) { Chaos::FPBDRigidsSolver* Solver = GetSolver(*this); if (Solver != nullptr) { if (PhysicsObject != nullptr) { ChaosModule->GetDispatcher()->EnqueueCommand(Solver, [&InPhysicsObject = PhysicsObject](Chaos::FPBDRigidsSolver* InSolver) { if (InPhysicsObject) { InPhysicsObject->ActivateBodies(); } }); } } } InitializationState = ESimulationInitializationState::Activated; #endif // default current cache time CurrentCacheTime = MAX_flt; } void UGeometryCollectionComponent::EndPlay(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; } FBoxSphereBounds UGeometryCollectionComponent::CalcBounds(const FTransform& LocalToWorldIn) const { SCOPE_CYCLE_COUNTER(STAT_GCCUpdateBounds); // Don't use bounds calculated in the physics object if we are doing sequencer cache playback // because we are overriding the transforms in the GC if (!CachePlayback && WorldBounds.GetSphere().W > 1e-5) { return WorldBounds; } else if (RestCollection && RestCollection->HasVisibleGeometry()) { 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 int32 NumBoxes = BoundingBoxes.Num(); // #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 if (GlobalMatrices.Num() != RestCollection->NumElements(FGeometryCollection::TransformGroup)) { TArray TmpGlobalMatrices; GeometryCollectionAlgo::GlobalMatrices(GetTransformArray(), 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() { Super::CreateRenderState_Concurrent(); if (SceneProxy && RestCollection && RestCollection->HasVisibleGeometry()) { FGeometryCollectionSceneProxy* const GeometryCollectionSceneProxy = static_cast(SceneProxy); #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 // #if GEOMETRYCOLLECTION_EDITOR_SELECTION FGeometryCollectionConstantData* const ConstantData = ::new FGeometryCollectionConstantData; InitConstantData(ConstantData); FGeometryCollectionDynamicData* const DynamicData = ::new FGeometryCollectionDynamicData; InitDynamicData(DynamicData); ENQUEUE_RENDER_COMMAND(CreateRenderState)( [GeometryCollectionSceneProxy, ConstantData, DynamicData](FRHICommandListImmediate& RHICmdList) { if (GeometryCollectionSceneProxy) { GeometryCollectionSceneProxy->SetConstantData_RenderThread(ConstantData); GeometryCollectionSceneProxy->SetDynamicData_RenderThread(DynamicData); } } ); } } FPrimitiveSceneProxy* UGeometryCollectionComponent::CreateSceneProxy() { if (RestCollection) { return new FGeometryCollectionSceneProxy(this); } return nullptr; } 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 PhysicsObject != 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); } } 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 INCLUDE_CHAOS if (BodyInstance.bNotifyRigidBodyCollision || bNotifyBreaks || bNotifyCollisions) { if (AChaosSolverActor* const SolverActor = GetPhysicsSolverActor()) { if (UChaosGameplayEventDispatcher* const EventDispatcher = SolverActor->GetGameplayEventDispatcher()) { if (bNotifyCollisions || BodyInstance.bNotifyRigidBodyCollision) { EventDispatcher->RegisterForCollisionEvents(this, this); } if (bNotifyBreaks) { EventDispatcher->RegisterForBreakEvents(this, &DispatchGeometryCollectionBreakEvent); } } } } #endif } void UGeometryCollectionComponent::UpdateRBCollisionEventRegistration() { #if INCLUDE_CHAOS if (AChaosSolverActor* const SolverActor = GetPhysicsSolverActor()) { if (UChaosGameplayEventDispatcher* const EventDispatcher = SolverActor->GetGameplayEventDispatcher()) { if (bNotifyCollisions || BodyInstance.bNotifyRigidBodyCollision) { EventDispatcher->RegisterForCollisionEvents(this, this); } else { EventDispatcher->UnRegisterForCollisionEvents(this, this); } } } #endif } void UGeometryCollectionComponent::UpdateBreakEventRegistration() { #if INCLUDE_CHAOS if (AChaosSolverActor* const SolverActor = GetPhysicsSolverActor()) { if (UChaosGameplayEventDispatcher* const EventDispatcher = SolverActor->GetGameplayEventDispatcher()) { if (bNotifyBreaks) { EventDispatcher->RegisterForBreakEvents(this, &DispatchGeometryCollectionBreakEvent); } else { EventDispatcher->UnRegisterForBreakEvents(this); } } } #endif } 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); 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); // #todo(dmp): we should eventually get this working where we use geometry nodes // that signify original unfractured geometry. For now, this system is broken. /* for (int i = 0; i < Collection->NumElements(FGeometryCollection::TransformGroup); ++i) { const FGeometryCollectionBoneNode &CurrBone = BoneHierarchy[i]; // root node could be parent geo if (CurrBone.Parent == INDEX_NONE) { int32 GeometryIndex = TransformToGeometryIndex[i]; // found geometry associated with base mesh root node if (GeometryIndex != INDEX_NONE) { int32 CurrFaceStart = FaceStart[GeometryIndex]; int32 CurrFaceCount = FaceCount[GeometryIndex]; // add all the faces to the original geometry face array for (int face = CurrFaceStart; face < CurrFaceStart + CurrFaceCount; ++face) { BaseMeshIndices.Add(Indices[face]); BaseMeshOriginalFaceIndices.Add(face); } // build an array of mesh sections ConstantData->HasOriginalMesh = true; } else { // all the direct decedents of the root node with no geometry are original geometry for (int32 CurrChild : CurrBone.Children) { int32 GeometryIndex = TransformToGeometryIndex[CurrChild]; if (GeometryIndex != INDEX_NONE) { // original geo static mesh int32 CurrFaceStart = FaceStart[GeometryIndex]; int32 CurrFaceCount = FaceCount[GeometryIndex]; // add all the faces to the original geometry face array for (int face = CurrFaceStart; face < CurrFaceStart + CurrFaceCount; ++face) { BaseMeshIndices.Add(Indices[face]); BaseMeshOriginalFaceIndices.Add(face); } ConstantData->HasOriginalMesh = true; } } } } } */ ConstantData->OriginalMeshSections = Collection->BuildMeshSections(BaseMeshIndices, BaseMeshOriginalFaceIndices, ConstantData->OriginalMeshIndices); TArray RestMatrices; GeometryCollectionAlgo::GlobalMatrices(RestCollection->GetGeometryCollection()->Transform, RestCollection->GetGeometryCollection()->Parent, RestMatrices); ConstantData->RestTransforms = MoveTemp(RestMatrices); } void UGeometryCollectionComponent::InitDynamicData(FGeometryCollectionDynamicData * DynamicData) { SCOPE_CYCLE_COUNTER(STAT_GCInitDynamicData); check(DynamicData); DynamicData->IsDynamic = this->GetIsObjectDynamic() || bShowBoneColors || bEnableBoneSelection; DynamicData->IsLoading = this->GetIsObjectLoading(); if (CachePlayback && CacheParameters.TargetCache && CacheParameters.TargetCache->GetData()) { const TManagedArray &Parents = GetParentArray(); const TManagedArray> &Children = GetChildrenArray(); const TManagedArray &Transform = RestCollection->GetGeometryCollection()->Transform; const TManagedArray &MassToLocal = RestCollection->GetGeometryCollection()->GetAttribute("MassToLocal", FGeometryCollection::TransformGroup); // #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 DynamicData->IsDynamic = DesiredCacheTime > CacheDt; // if we are already on the current cached frame, return if (FMath::IsNearlyEqual(CurrentCacheTime, DesiredCacheTime) && GlobalMatrices.Num() != 0) { DynamicData->PrevTransforms = GlobalMatrices; DynamicData->Transforms = GlobalMatrices; // maintaining the cache time means we should consider the transforms equal for dynamic data sending purposes TransformsAreEqual[(TransformsAreEqualIndex++) % TransformsAreEqual.Num()] = true; return; } // if the input simulation time to playback is the first frame, reset simulation time if (DesiredCacheTime <= CacheDt || FMath::IsNearlyEqual(CurrentCacheTime, FLT_MAX)) { CurrentCacheTime = DesiredCacheTime; GeometryCollectionAlgo::GlobalMatrices(GetTransformArray(), GetParentArray(), GlobalMatrices); DynamicData->PrevTransforms = GlobalMatrices; DynamicData->Transforms = GlobalMatrices; EventsPlayed.Empty(); EventsPlayed.AddDefaulted(CacheParameters.TargetCache->GetData()->Records.Num()); // reset should send new transforms to the RT TransformsAreEqual[(TransformsAreEqualIndex++) % TransformsAreEqual.Num()] = false; } else if (GlobalMatrices.Num() == 0) { // bad case here. Sequencer starts at non zero position We cannot correctly reconstruct the current frame, so give a warning GeometryCollectionAlgo::GlobalMatrices(GetTransformArray(), GetParentArray(), GlobalMatrices); DynamicData->PrevTransforms = GlobalMatrices; DynamicData->Transforms = GlobalMatrices; EventsPlayed.Empty(); EventsPlayed.AddDefaulted(CacheParameters.TargetCache->GetData()->Records.Num()); // degenerate case causes and reset should send new transforms to the RT TransformsAreEqual[(TransformsAreEqualIndex++) % TransformsAreEqual.Num()] = false; UE_LOG(UGCC_LOG, Warning, TEXT("Cache out of sync - must rewind sequencer to start frame")); } else if (DesiredCacheTime >= CurrentCacheTime) { int NumSteps = floor((DesiredCacheTime - CurrentCacheTime) / CacheDt); float LastDt = FMath::Fmod(DesiredCacheTime - CurrentCacheTime, CacheDt); NumSteps += LastDt > SMALL_NUMBER ? 1 : 0; FTransform ActorToWorld = GetComponentTransform(); bool HasAnyActiveTransforms = false; // Jump ahead in increments of CacheDt evaluating the cache until we reach our desired time for (int st = 0; st < NumSteps; ++st) { float TimeIncrement = st == 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 &xforms = FirstFrame->Transforms; const TArray &TransformIndices = FirstFrame->TransformIndices; const int32 NumActives = FirstFrame->TransformIndices.Num(); if (NumActives > 0) { HasAnyActiveTransforms = true; } for (int i = 0; i < NumActives; ++i) { const int32 InternalIndexTmp = TransformIndices[i]; 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; return; } // calculate global matrix for current FTransform ParticleToWorld = xforms[i]; 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); check(0 <= Alpha && Alpha <= 1.0f); const int32 NumActives = SecondFrame->TransformIndices.Num(); if (NumActives > 0) { HasAnyActiveTransforms = true; } for (int 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; return; } 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; /**********************************************************************************************************************************************************************************/ // Capture all events for the given time if(false) { #if INCLUDE_CHAOS // clear events on the solver Chaos::FPBDRigidsSolver *Solver = GetSolver(*this); if (Solver) { Chaos::FSolverWriteLock ScopedWriteLock(Solver); ////////////////////////////////////////////////////////////////////////// // Temporary workaround for writing on multiple threads. // The following is called wide from the render thread to populate // Niagara data from a cache without invoking a solver directly // // The above write lock guarantees we can safely write to these buffers // // TO BE REFACTORED // #TODO BG ////////////////////////////////////////////////////////////////////////// Chaos::FPBDRigidsSolver::FAllCollisionData *CollisionDataToWriteTo = const_cast(Solver->GetAllCollisions_FromSequencerCache_NEEDSLOCK()); Chaos::FPBDRigidsSolver::FAllBreakingData *BreakingDataToWriteTo = const_cast(Solver->GetAllBreakings_FromSequencerCache_NEEDSLOCK()); Chaos::FPBDRigidsSolver::FAllTrailingData *TrailingDataToWriteTo = const_cast(Solver->GetAllTrailings_FromSequencerCache_NEEDSLOCK()); if (!FMath::IsNearlyEqual(CollisionDataToWriteTo->TimeCreated, DesiredCacheTime)) { CollisionDataToWriteTo->AllCollisionsArray.Empty(); CollisionDataToWriteTo->TimeCreated = DesiredCacheTime; } int32 Index = CacheParameters.TargetCache->GetData()->FindLastKeyBefore(CurrentCacheTime); const FRecordedFrame *RecordedFrame = &CacheParameters.TargetCache->GetData()->Records[Index]; if (RecordedFrame && PhysicsObject && !EventsPlayed[Index]) { EventsPlayed[Index] = true; // Collisions if (RecordedFrame->Collisions.Num() > 0) { for (int32 Idx = 0; Idx < RecordedFrame->Collisions.Num(); ++Idx) { // Check if the particle is still kinematic int32 NewIdx = CollisionDataToWriteTo->AllCollisionsArray.Add(Chaos::TCollisionData()); Chaos::TCollisionData& AllCollisionsDataArrayItem = CollisionDataToWriteTo->AllCollisionsArray[NewIdx]; AllCollisionsDataArrayItem.Location = RecordedFrame->Collisions[Idx].Location; AllCollisionsDataArrayItem.AccumulatedImpulse = RecordedFrame->Collisions[Idx].AccumulatedImpulse; AllCollisionsDataArrayItem.Normal = RecordedFrame->Collisions[Idx].Normal; AllCollisionsDataArrayItem.Velocity1 = RecordedFrame->Collisions[Idx].Velocity1; AllCollisionsDataArrayItem.Velocity2 = RecordedFrame->Collisions[Idx].Velocity2; AllCollisionsDataArrayItem.AngularVelocity1 = RecordedFrame->Collisions[Idx].AngularVelocity1; AllCollisionsDataArrayItem.AngularVelocity2 = RecordedFrame->Collisions[Idx].AngularVelocity2; AllCollisionsDataArrayItem.Mass1 = RecordedFrame->Collisions[Idx].Mass1; AllCollisionsDataArrayItem.Mass2 = RecordedFrame->Collisions[Idx].Mass2; AllCollisionsDataArrayItem.ParticleIndex = RecordedFrame->Collisions[Idx].ParticleIndex; AllCollisionsDataArrayItem.LevelsetIndex = RecordedFrame->Collisions[Idx].LevelsetIndex; AllCollisionsDataArrayItem.ParticleIndexMesh = RecordedFrame->Collisions[Idx].ParticleIndexMesh; AllCollisionsDataArrayItem.LevelsetIndexMesh = RecordedFrame->Collisions[Idx].LevelsetIndexMesh; } } // Breaking if (RecordedFrame->Breakings.Num() > 0) { for (int32 Idx = 0; Idx < RecordedFrame->Breakings.Num(); ++Idx) { // Check if the particle is still kinematic int32 NewIdx = BreakingDataToWriteTo->AllBreakingsArray.Add(Chaos::TBreakingData()); Chaos::TBreakingData& AllBreakingsDataArrayItem = BreakingDataToWriteTo->AllBreakingsArray[NewIdx]; AllBreakingsDataArrayItem.Location = RecordedFrame->Breakings[Idx].Location; AllBreakingsDataArrayItem.Velocity = RecordedFrame->Breakings[Idx].Velocity; AllBreakingsDataArrayItem.AngularVelocity = RecordedFrame->Breakings[Idx].AngularVelocity; AllBreakingsDataArrayItem.Mass = RecordedFrame->Breakings[Idx].Mass; AllBreakingsDataArrayItem.ParticleIndex = RecordedFrame->Breakings[Idx].ParticleIndex; AllBreakingsDataArrayItem.ParticleIndexMesh = RecordedFrame->Breakings[Idx].ParticleIndexMesh; } } // Trailing if (RecordedFrame->Trailings.Num() > 0) { for (FSolverTrailingData Trailing : RecordedFrame->Trailings) { // Check if the particle is still kinematic int32 NewIdx = TrailingDataToWriteTo->AllTrailingsArray.Add(Chaos::TTrailingData()); Chaos::TTrailingData& AllTrailingsDataArrayItem = TrailingDataToWriteTo->AllTrailingsArray[NewIdx]; AllTrailingsDataArrayItem.Location = Trailing.Location; AllTrailingsDataArrayItem.Velocity = Trailing.Velocity; AllTrailingsDataArrayItem.AngularVelocity = Trailing.AngularVelocity; AllTrailingsDataArrayItem.Mass = Trailing.Mass; AllTrailingsDataArrayItem.ParticleIndex = Trailing.ParticleIndex; AllTrailingsDataArrayItem.ParticleIndexMesh = Trailing.ParticleIndexMesh; } } } } #endif } } // check if transforms at start of this tick are the same as what is calculated from the cache TransformsAreEqual[(TransformsAreEqualIndex++) % TransformsAreEqual.Num()] = !HasAnyActiveTransforms; } else { // time is before current cache time so maintain the matrices we have since we can't rewind DynamicData->PrevTransforms = GlobalMatrices; DynamicData->Transforms = GlobalMatrices; // reset event means we don't want to consider transforms as being equal between prev and current frame TransformsAreEqual[(TransformsAreEqualIndex++) % TransformsAreEqual.Num()] = true; } } else if (DynamicData->IsDynamic) { // 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; // reset event means we don't want to consider transforms as being equal between prev and current frame TransformsAreEqual[(TransformsAreEqualIndex++) % TransformsAreEqual.Num()] = false; } else { // Copy existing global matrices into prev transforms DynamicData->PrevTransforms = GlobalMatrices; // Copy global matrices over to DynamicData CalculateGlobalMatrices(); // if the number of matrices has changed between frames, then sync previous to current if (GlobalMatrices.Num() != DynamicData->PrevTransforms.Num()) { DynamicData->PrevTransforms = GlobalMatrices; } DynamicData->Transforms = GlobalMatrices; // check if previous transforms are the same as current TransformsAreEqual[(TransformsAreEqualIndex++) % TransformsAreEqual.Num()] = IsEqual(DynamicData->PrevTransforms, DynamicData->Transforms); } } } #if CHAOS_DEBUG_DRAW void DebugDrawChaos(UWorld* World) { if (!World->IsGameWorld()) { return; } using namespace Chaos; TArray LatenetDrawCommands; FDebugDrawQueue::GetInstance().ExtractAllElements(LatenetDrawCommands); for (const FLatentDrawCommand& Command : LatenetDrawCommands) { switch (Command.Type) { case FLatentDrawCommand::EDrawType::Point: { DrawDebugPoint(World, Command.LineStart, Command.Thickness, Command.Color, Command.bPersistentLines, Command.LifeTime, Command.DepthPriority); break; } case FLatentDrawCommand::EDrawType::Line: { DrawDebugLine(World, Command.LineStart, Command.LineEnd, Command.Color, Command.bPersistentLines, Command.LifeTime, Command.DepthPriority, Command.Thickness); break; } case FLatentDrawCommand::EDrawType::DirectionalArrow: { DrawDebugDirectionalArrow(World, Command.LineStart, Command.LineEnd, Command.ArrowSize, Command.Color, Command.bPersistentLines, Command.LifeTime, Command.DepthPriority, Command.Thickness); break; } case FLatentDrawCommand::EDrawType::Sphere: { DrawDebugSphere(World, Command.LineStart, Command.Radius, Command.Segments, Command.Color, Command.bPersistentLines, Command.LifeTime, Command.DepthPriority, Command.Thickness); break; } case FLatentDrawCommand::EDrawType::Box: { DrawDebugBox(World, Command.Center, Command.Extent, Command.Rotation, Command.Color, Command.bPersistentLines, Command.LifeTime, Command.DepthPriority, Command.Thickness); break; } default: break; } } } #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 (bRenderStateDirty && DynamicCollection) //todo: always send for now if(RestCollection) { //if (RestCollection->HasVisibleGeometry() || DynamicCollection->IsDirty()) if(RestCollection->HasVisibleGeometry()) { MarkRenderTransformDirty(); MarkRenderDynamicDataDirty(); bRenderStateDirty = false; //DynamicCollection->MakeClean(); clean? // 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(); // } // } } } #if CHAOS_DEBUG_DRAW using namespace Chaos; // General debug drawing if (FDebugDrawQueue::EnableDebugDrawing) { if (UWorld* World = GetWorld()) { DebugDrawChaos(World); } } #endif } void UGeometryCollectionComponent::OnRegister() { //UE_LOG(UGCC_LOG, Log, TEXT("GeometryCollectionComponent[%p]::OnRegister()[%p]"), this,RestCollection ); ResetDynamicCollection(); #if WITH_EDITOR FScopedColorEdit ColorEdit(this); ColorEdit.ResetBoneSelection(); ColorEdit.ResetHighlightedBones(); #endif 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 (!PhysicsObject) { #if WITH_EDITOR && WITH_EDITORONLY_DATA EditorActor = nullptr; if (RestCollection) { //hack: find a better place for this UGeometryCollection* RestCollectionMutable = const_cast(RestCollection); RestCollectionMutable->EnsureDataIsCooked(); } #endif #if INCLUDE_CHAOS const bool bValidWorld = GetWorld() && GetWorld()->IsGameWorld(); const bool bValidCollection = DynamicCollection && DynamicCollection->Transform.Num() > 0; if (bValidWorld && bValidCollection) { if (PhysicalMaterial) { ChaosMaterial->Friction = PhysicalMaterial->Friction; ChaosMaterial->Restitution = PhysicalMaterial->Restitution; ChaosMaterial->SleepingLinearThreshold = PhysicalMaterial->SleepingLinearVelocityThreshold; ChaosMaterial->SleepingAngularThreshold = PhysicalMaterial->SleepingAngularVelocityThreshold; } auto InitFunc = [this](FSimulationParameters& InParams) { #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) InParams.Name = GetPathName(); #endif InParams.RestCollection = RestCollection->GetGeometryCollection().Get(); GetInitializationCommands(InParams.InitializationCommands); InParams.InitializationState = InitializationState; InParams.Simulating = Simulating; InParams.WorldTransform = GetComponentToWorld(); RestCollection->GetSharedSimulationParams(InParams.Shared); InParams.EnableClustering = EnableClustering; InParams.ClusterGroupIndex = EnableClustering?ClusterGroupIndex:0; InParams.MaxClusterLevel = MaxClusterLevel; InParams.DamageThreshold = DamageThreshold; InParams.ClusterConnectionMethod = (Chaos::FClusterCreationParameters::EConnectionMethod)ClusterConnectionType; InParams.CollisionGroup = CollisionGroup; InParams.CollisionSampleFraction = CollisionSampleFraction; InParams.InitialVelocityType = InitialVelocityType; InParams.InitialLinearVelocity = InitialLinearVelocity; InParams.InitialAngularVelocity = InitialAngularVelocity; InParams.bClearCache = true; InParams.PhysicalMaterial = MakeSerializable(ChaosMaterial); InParams.CacheType = CacheParameters.CacheMode; InParams.ReverseCacheBeginTime = CacheParameters.ReverseCacheBeginTime; InParams.CollisionData.SaveCollisionData = CacheParameters.SaveCollisionData; InParams.CollisionData.DoGenerateCollisionData = CacheParameters.DoGenerateCollisionData; InParams.CollisionData.CollisionDataSizeMax = CacheParameters.CollisionDataSizeMax; InParams.CollisionData.DoCollisionDataSpatialHash = CacheParameters.DoCollisionDataSpatialHash; InParams.CollisionData.CollisionDataSpatialHashRadius = CacheParameters.CollisionDataSpatialHashRadius; InParams.CollisionData.MaxCollisionPerCell = CacheParameters.MaxCollisionPerCell; InParams.BreakingData.SaveBreakingData = CacheParameters.SaveBreakingData; InParams.BreakingData.DoGenerateBreakingData = CacheParameters.DoGenerateBreakingData; InParams.BreakingData.BreakingDataSizeMax = CacheParameters.BreakingDataSizeMax; InParams.BreakingData.DoBreakingDataSpatialHash = CacheParameters.DoBreakingDataSpatialHash; InParams.BreakingData.BreakingDataSpatialHashRadius = CacheParameters.BreakingDataSpatialHashRadius; InParams.BreakingData.MaxBreakingPerCell = CacheParameters.MaxBreakingPerCell; InParams.TrailingData.SaveTrailingData = CacheParameters.SaveTrailingData; InParams.TrailingData.DoGenerateTrailingData = CacheParameters.DoGenerateTrailingData; InParams.TrailingData.TrailingDataSizeMax = CacheParameters.TrailingDataSizeMax; InParams.TrailingData.TrailingMinSpeedThreshold = CacheParameters.TrailingMinSpeedThreshold; InParams.TrailingData.TrailingMinVolumeThreshold = CacheParameters.TrailingMinVolumeThreshold; InParams.RemoveOnFractureEnabled = InParams.Shared.RemoveOnFractureIndices.Num() > 0; 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; #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 TSharedPtr Scene = GetPhysicsScene(); 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(FindField(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 }; // @todo(temporary) : This is Temporary code for the collection to match the ObjectType // attribute on initialization. Once proper per-object manipulation is // in place this code will need to be removed. // TManagedArray & DynamicState = DynamicCollection->DynamicState; if (ObjectType != EObjectStateTypeEnum::Chaos_Object_UserDefined) { for (int i = 0; i < DynamicState.Num(); i++) { DynamicState[i] = (int32)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; } } // end temporary PhysicsObject = new FGeometryCollectionPhysicsObject(this, DynamicCollection.Get(), InitFunc, CacheSyncFunc, FinalSyncFunc); TSharedPtr Scene = GetPhysicsScene(); Scene->AddObject(this, PhysicsObject); RegisterForEvents(); } #endif } #if INCLUDE_CHAOS #if WITH_PHYSX && !WITH_CHAOS_NEEDS_TO_BE_FIXED if (PhysicsObject) { GlobalGeomCollectionAccelerator.AddComponent(this); } #endif #endif } void UGeometryCollectionComponent::OnDestroyPhysicsState() { UActorComponent::OnDestroyPhysicsState(); #if INCLUDE_CHAOS #if WITH_PHYSX && !WITH_CHAOS_NEEDS_TO_BE_FIXED GlobalGeomCollectionAccelerator.RemoveComponent(this); #endif #endif #if WITH_PHYSX if(DummyBodyInstance.IsValidBodyInstance()) { DummyBodyInstance.TermBody(); } #endif #if INCLUDE_CHAOS if(PhysicsObject) { TSharedPtr Scene = GetPhysicsScene(); Scene->RemoveObject(PhysicsObject); InitializationState = ESimulationInitializationState::Unintialized; // Discard the pointer (cleanup happens through the scene or dedicated thread) PhysicsObject = 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 = ::new FGeometryCollectionDynamicData; InitDynamicData(DynamicData); // // Only send dynamic data if the transform arrys on the past N frames are different // bool PastFramesTransformsAreEqual = true; for (int i = 0; i < TransformsAreEqual.Num(); ++i) { PastFramesTransformsAreEqual = PastFramesTransformsAreEqual && TransformsAreEqual[i]; } if (PastFramesTransformsAreEqual) { delete DynamicData; } else { // Enqueue command to send to render thread 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(); //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(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, 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, 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, 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); DispatchCommand({ TargetName,new FRadialIntMask(Radius, Position, (int32)EObjectStateTypeEnum::Chaos_Object_Dynamic, (int32)EObjectStateTypeEnum::Chaos_Object_Kinematic, ESetMaskConditionType::Field_Set_IFF_NOT_Interior) }); } void UGeometryCollectionComponent::ApplyPhysicsField(bool Enabled, EGeometryCollectionPhysicsTypeEnum Target, UFieldSystemMetaData* MetaData, UFieldNodeBase* Field) { if (Enabled && Field) { TArray Nodes; FFieldSystemCommand Command = { GetGeometryCollectionPhysicsTypeName(Target), Field->NewEvaluationGraph(Nodes) }; if (ensureMsgf(Command.RootNode, TEXT("Failed to generate physics field command for target attribute."))) { if (MetaData) { switch (MetaData->Type()) { case FFieldSystemMetaData::EMetaType::ECommandData_ProcessingResolution: UFieldSystemMetaDataProcessingResolution * ResolutionMeta = static_cast(MetaData); Command.MetaData.Add(FFieldSystemMetaData::EMetaType::ECommandData_ProcessingResolution).Reset(new FFieldSystemMetaDataProcessingResolution(ResolutionMeta->ResolutionType)); break; } } ensure(!Command.TargetAttribute.IsEqual("None")); DispatchCommand(Command); } } } void UGeometryCollectionComponent::DispatchCommand(const FFieldSystemCommand& InCommand) { #if INCLUDE_CHAOS if (PhysicsObject) { FChaosSolversModule* ChaosModule = FModuleManager::Get().GetModulePtr("ChaosSolvers"); checkSlow(ChaosModule); Chaos::IDispatcher* PhysicsDispatcher = ChaosModule->GetDispatcher(); checkSlow(PhysicsDispatcher); // Should always have one of these PhysicsDispatcher->EnqueueCommand([PhysicsObject = this->PhysicsObject, NewCommand = InCommand]() { // Pass through nullptr here as geom component commands can never affect other solvers PhysicsObject->BufferCommand(nullptr, NewCommand); }); } #endif } void UGeometryCollectionComponent::GetInitializationCommands(TArray& CombinedCommmands) { #if INCLUDE_CHAOS CombinedCommmands.Reset(); for (const AFieldSystemActor * FieldSystemActor : InitializationFields) { if (FieldSystemActor != nullptr) { if (FieldSystemActor->GetFieldSystemComponent() && 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); } } } } #endif } #if INCLUDE_CHAOS const TSharedPtr UGeometryCollectionComponent::GetPhysicsScene() const { if (ChaosSolverActor) { return ChaosSolverActor->GetPhysicsScene(); } else { if (ensure(GetOwner()) && ensure(GetOwner()->GetWorld())) { return GetOwner()->GetWorld()->PhysicsScene_Chaos; } check(GWorld); return GWorld->PhysicsScene_Chaos; } } AChaosSolverActor* UGeometryCollectionComponent::GetPhysicsSolverActor() const { if (ChaosSolverActor) { return ChaosSolverActor; } else { FPhysScene_Chaos const* const Scene = GetPhysicsScene().Get(); return Scene ? Cast(Scene->GetSolverActor()) : nullptr; } return nullptr; } #endif 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); #if INCLUDE_CHAOS FChaosSolversModule* Module = FChaosSolversModule::GetModule(); Module->LockResultsRead(); const FGeometryCollectionResults* Results = PhysicsObject ? &PhysicsObject->GetPhysicsResults().GetGameDataForRead() : nullptr; const int32 NumTransforms = Results ? Results->GlobalTransforms.Num() : 0; if(NumTransforms > 0) { // Just calc from results GlobalMatrices.Empty(); 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 Module->UnlockResultsRead(); #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; } TArray PrimitiveComponents = Actor->GetComponentsByClass(UPrimitiveComponent::StaticClass()); for (UActorComponent* PrimitiveComponent : PrimitiveComponents) { bool ValidComponent = false; if (UStaticMeshComponent* StaticMeshComp = Cast(PrimitiveComponent)) { StaticMeshComp->SetVisibility(false); } else if (UGeometryCollectionComponent* GeometryCollectionComponent = Cast(PrimitiveComponent)) { if (!GeometryCollectionComponent->IsVisible()) { continue; } GeometryCollectionComponent->SetVisibility(true); } } TArray ChildActorComponents; Actor->GetComponents(ChildActorComponents); for (UChildActorComponent* ChildComponent : ChildActorComponents) { AActor* ChildActor = ChildComponent->GetChildActor(); if (ChildActor) { SwitchRenderModels(ChildActor); } } } bool UGeometryCollectionComponent::IsEqual(const TArray &A, const TArray &B, const float Tolerance) { if (A.Num() != B.Num()) { return false; } for (int Index = 0; Index < A.Num(); ++Index) { if (!A[Index].Equals(B[Index], Tolerance)) { return false; } } return true; } #if GEOMETRYCOLLECTION_EDITOR_SELECTION void UGeometryCollectionComponent::EnableTransformSelectionMode(bool bEnable) { if (SceneProxy && RestCollection && RestCollection->HasVisibleGeometry()) { static_cast(SceneProxy)->UseSubSections(bEnable, true); } bIsTransformSelectionModeEnabled = bEnable; } #endif // #if GEOMETRYCOLLECTION_EDITOR_SELECTION