Files
UnrealEngineUWP/Engine/Source/Runtime/Experimental/GeometryCollectionEngine/Private/GeometryCollection/GeometryCollectionComponent.cpp
graham wihlidal 0fa361fa13 Fixed Nanite geometry collection velocity vectors, and optimizing transform updates for dummy GPU scene instance culling placeholders. Scene proxies can now fully own the concatenation of transform updates and avoid doing incorrect or redundant work during GPU Scene updates.
#jira UETOP-1352
#rb brandon.dawson
[FYI] ola.olsson, brian.karis, brice.criswell, brett.miller, michal.valient
#lockdown nick.whiting

#ROBOMERGE-OWNER: graham.wihlidal
#ROBOMERGE-AUTHOR: graham.wihlidal
#ROBOMERGE-SOURCE: CL 16141209 in //UE5/Release-5.0-EarlyAccess/...
#ROBOMERGE-BOT: STARSHIP (Release-5.0-EarlyAccess -> Main) (v789-15992632)
#ROBOMERGE-CONFLICT from-shelf

[CL 16141875 by graham wihlidal in ue5-main branch]
2021-04-28 05:36:47 -04:00

2826 lines
92 KiB
C++

// 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 RecreateGlobalRenderState(IConsoleVariable* Var)
{
FGlobalComponentRecreateRenderStateContext Context;
}
int32 GGeometryCollectionNanite = 1;
FAutoConsoleVariableRef CVarGeometryCollectionNanite(
TEXT("r.GeometryCollection.Nanite"),
GGeometryCollectionNanite,
TEXT("Render geometry collections using Nanite."),
FConsoleVariableDelegate::CreateStatic(&RecreateGlobalRenderState),
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
{
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<UChaosGameplayEventDispatcher>(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<FTransform>&)
// ---------- {
// ---------- // todo : Move the update to the array passed here...
// ---------- });
// ----------
// ---------- SolverCallbacks->SetUpdateRestStateFunction([&](const int32 & CurrentFrame, const TManagedArray<int32> & RigidBodyID, const TManagedArray<FGeometryCollectionBoneNode>& 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<AActor>());
#endif
Super::EndPlay(ReasonEnd);
CurrentCacheTime = MAX_flt;
}
void UGeometryCollectionComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
FDoRepLifetimeParams Params;
Params.bIsPushBased = true;
Params.RepNotifyCondition = REPNOTIFY_OnChanged;
DOREPLIFETIME_WITH_PARAMS_FAST(UGeometryCollectionComponent, RepData, Params);
}
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<FBox>& BoundingBoxes = GetBoundingBoxArray();
const TManagedArray<int32>& TransformIndices = GetTransformIndexArray();
const TManagedArray<int32>& ParentIndices = GetParentArray();
const TManagedArray<int32>& TransformToGeometryIndex = GetTransformToGeometryIndexArray();
const TManagedArray<FTransform>& Transforms = GetTransformArray();
const int32 NumBoxes = BoundingBoxes.Num();
int32 NumElements = HackGeometryCollectionPtr->NumElements(FGeometryCollection::TransformGroup);
if (RestCollection->EnableNanite && HackGeometryCollectionPtr->HasAttribute("BoundingBox", FGeometryCollection::TransformGroup) && NumElements)
{
TArray<FMatrix> TmpGlobalMatrices;
GeometryCollectionAlgo::GlobalMatrices(Transforms, ParentIndices, TmpGlobalMatrices);
TManagedArray<FBox>& TransformBounds = HackGeometryCollectionPtr->GetAttribute<FBox>("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<FMatrix> 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<FNaniteGeometryCollectionSceneProxy*>(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<FGeometryCollectionSceneProxy*>(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<FBodyInstance*>(&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<FVector> OutVertexBuffer;
TArray<int32> OutIndexBuffer;
const FGeometryCollection* const Collection = RestCollection->GetGeometryCollection().Get();
check(Collection);
const float SizeThreshold = GGeometryCollectionNavigationSizeThreshold * GGeometryCollectionNavigationSizeThreshold;
// for all geometry. inspect bounding box build int list of transform indices.
int32 VertexCount = 0;
int32 FaceCountEstimate = 0;
TArray<int32> GeometryIndexBuffer;
TArray<int32> TransformIndexBuffer;
int32 NumGeometry = Collection->NumElements(FGeometryCollection::GeometryGroup);
const TManagedArray<FBox>& BoundingBox = Collection->BoundingBox;
const TManagedArray<int32>& TransformIndexArray = Collection->TransformIndex;
const TManagedArray<int32>& VertexCountArray = Collection->VertexCount;
const TManagedArray<int32>& FaceCountArray = Collection->FaceCount;
const TManagedArray<int32>& VertexStartArray = Collection->VertexStart;
const TManagedArray<FVector>& 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<FTransform> 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<int32>& FaceStartArray = Collection->FaceStart;
const TManagedArray<FIntVector>& Indices = Collection->Indices;
const TManagedArray<bool>& Visible = GetVisibleArray();
const TManagedArray<int32>& MaterialIndex = Collection->MaterialIndex;
//pre-allocate enough room (assuming all faces are visible)
OutIndexBuffer.AddUninitialized(3 * FaceCountEstimate);
//reset vertex counter so that we base the indices off the new location rather than the global vertex list
DestVertex = 0;
int32 DestinationIndex = 0;
//leaving index traversal in a different loop to help cache coherency of source data
for(int32 SubsetIndex = 0; SubsetIndex < GeometryIndexBuffer.Num(); ++SubsetIndex)
{
int32 GeometryIndex = GeometryIndexBuffer[SubsetIndex];
//for each index, subtract the starting vertex for that geometry to make it 0-based. Then add the new starting vertex index for this geometry
int32 SourceGeometryVertexStart = VertexStartArray[GeometryIndex];
int32 SourceGeometryVertexCount = VertexCountArray[GeometryIndex];
int32 IndexDelta = DestVertex - SourceGeometryVertexStart;
int32 FaceStart = FaceStartArray[GeometryIndex];
int32 FaceCount = FaceCountArray[GeometryIndex];
//Copy the faces
for(int FaceIdx = FaceStart; FaceIdx < FaceStart + FaceCount; FaceIdx++)
{
if(Visible[FaceIdx])
{
OutIndexBuffer[DestinationIndex++] = Indices[FaceIdx].X + IndexDelta;
OutIndexBuffer[DestinationIndex++] = Indices[FaceIdx].Y + IndexDelta;
OutIndexBuffer[DestinationIndex++] = Indices[FaceIdx].Z + IndexDelta;
}
}
DestVertex += SourceGeometryVertexCount;
}
// Invisible faces make the index buffer smaller
OutIndexBuffer.SetNum(DestinationIndex);
// Push as a custom mesh to navigation system
// #CHAOSTODO This is pretty inefficient as it copies the whole buffer transforming each vert by the component to world
// transform. Investigate a move aware custom mesh for pre-transformed verts to speed this up.
GeomExport.ExportCustomMesh(OutVertexBuffer.GetData(), OutVertexBuffer.Num(), OutIndexBuffer.GetData(), OutIndexBuffer.Num(), GetComponentToWorld());
return true;
}
UPhysicalMaterial* UGeometryCollectionComponent::GetPhysicalMaterial() const
{
// Pull material from first mesh element to grab physical material. Prefer an override if one exists
UPhysicalMaterial* PhysMatToUse = 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<int32>& ExemplarIndexArray = GetExemplarIndexArray();
const int32 TransformCount = GlobalMatrices.Num();
const int32 ExemplarCount = EmbeddedGeometryComponents.Num();
for (int32 ExemplarIndex = 0; ExemplarIndex < ExemplarCount; ++ExemplarIndex)
{
TArray<FTransform> InstanceTransforms;
InstanceTransforms.Reserve(TransformCount); // Allocate for worst case
// Construct instance transforms for this exemplar
for (int32 Idx = 0; Idx < TransformCount; ++Idx)
{
if (ExemplarIndexArray[Idx] == ExemplarIndex)
{
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<FVector>("LinearVelocity", FTransformCollection::TransformGroup);
DynamicCollection->AddAttribute<FVector>("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<bool>& ControlFlags = DynamicCollection->AddAttribute<bool>("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<Chaos::FReal>::Max() - TNumericLimits<Chaos::FReal>::Min();
TArray<Chaos::TPBDRigidClusteredParticleHandle<Chaos::FReal, 3>*> Particles = Prox->GetParticles();
for(Chaos::TPBDRigidClusteredParticleHandle<Chaos::FReal, 3> * 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<UGeometryCollectionComponent>(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<float, 3>* 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<float, 3>* Particle = Prox->GetParticles()[SourcePose.ParticleIndex];
Chaos::FPhysicsSolver* Solver = Prox->GetSolver<Chaos::FPhysicsSolver>();
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<FVector>* LinearVelocity = DynamicCollection->FindAttributeTyped<FVector>("LinearVelocity", FTransformCollection::TransformGroup);
TManagedArray<FVector>* AngularVelocity = DynamicCollection->FindAttributeTyped<FVector>("AngularVelocity", FTransformCollection::TransformGroup);
for(int32 Index = 0; Index < NumTransforms; ++Index)
{
TManagedArray<TUniquePtr<Chaos::FGeometryParticle>>& GTParticles = PhysicsProxy->GetExternalParticles();
Chaos::FGeometryParticle* Particle = GTParticles[Index].Get();
if(!DynamicCollection->Active[Index] || DynamicCollection->DynamicState[Index] != static_cast<uint8>(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<bool>* ControlFlags = DynamicCollection->FindAttributeTyped<bool>("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<Chaos::FReal, 3>* P, TMap<Chaos::TPBDRigidClusteredParticleHandle<Chaos::FReal, 3>*, TArray<Chaos::TPBDRigidParticleHandle<Chaos::FReal, 3>*>>& Map, float Strain)
{
TArray<Chaos::TPBDRigidParticleHandle<Chaos::FReal, 3>*>* Children = Map.Find(P);
if(Children)
{
for(Chaos::TPBDRigidParticleHandle<Chaos::FReal, 3> * 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<Chaos::FPBDRigidsEvolution, Chaos::FPBDCollisionConstraints>& 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<FVector>& Vertex = Collection->Vertex;
const TManagedArray<int32>& BoneMap = Collection->BoneMap;
const TManagedArray<FVector>& TangentU = Collection->TangentU;
const TManagedArray<FVector>& TangentV = Collection->TangentV;
const TManagedArray<FVector>& Normal = Collection->Normal;
const TManagedArray<FVector2D>& UV = Collection->UV;
const TManagedArray<FLinearColor>& Color = Collection->Color;
const TManagedArray<FLinearColor>& BoneColors = Collection->BoneColor;
ConstantData->Vertices = TArray<FVector>(Vertex.GetData(), Vertex.Num());
ConstantData->BoneMap = TArray<int32>(BoneMap.GetData(), BoneMap.Num());
ConstantData->TangentU = TArray<FVector>(TangentU.GetData(), TangentU.Num());
ConstantData->TangentV = TArray<FVector>(TangentV.GetData(), TangentV.Num());
ConstantData->Normals = TArray<FVector>(Normal.GetData(), Normal.Num());
ConstantData->UVs = TArray<FVector2D>(UV.GetData(), UV.Num());
ConstantData->Colors = TArray<FLinearColor>(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<FIntVector>& Indices = Collection->Indices;
const TManagedArray<int32>& MaterialID = Collection->MaterialID;
const TManagedArray<bool>& 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<int32>& MaterialIndex = Collection->MaterialIndex;
const int32 NumFaceGroupEntries = Collection->NumElements(FGeometryCollection::FacesGroup);
for (int FaceIndex = 0; FaceIndex < NumFaceGroupEntries; ++FaceIndex)
{
NumIndices += static_cast<int>(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<FGeometryCollectionSection>& 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<int32>& TransformToGeometryIndex = Collection->TransformToGeometryIndex;
const TManagedArray<int32>& FaceStart = Collection->FaceStart;
const TManagedArray<int32>& FaceCount = Collection->FaceCount;
const int32 NumFaces = Collection->NumElements(FGeometryCollection::FacesGroup);
TArray<FIntVector> BaseMeshIndices;
TArray<int32> 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<FMatrix> 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<FTransform>& Transform = RestCollection->GetGeometryCollection()->Transform;
const TManagedArray<TSet<int32>>& Children = GetChildrenArray();
const TManagedArray<FTransform>& MassToLocal = RestCollection->GetGeometryCollection()->GetAttribute<FTransform>("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<FTransform>& FirstFrameTransforms = FirstFrame->Transforms;
const TArray<int32>& 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 (DynamicData && DynamicData->ChangedCount == 0 && !bEditorMode && !bInitialization)
{
GDynamicDataPool.Release(DynamicData);
DynamicData = nullptr;
}
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<const void*>(this));
if (bCreateDynamicCollection && RestCollection)
{
DynamicCollection = MakeUnique<FGeometryDynamicCollection>();
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<UBodySetup>(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<UGeometryCollection*>(ToRawPtr(RestCollection));
RestCollectionMutable->EnsureDataIsCooked();
}
#endif
const bool bValidWorld = GetWorld() && GetWorld()->IsGameWorld();
const bool bValidCollection = DynamicCollection && DynamicCollection->Transform.Num() > 0;
if (bValidWorld && bValidCollection)
{
FPhysxUserData::Set<UPrimitiveComponent>(&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<void*>(&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<AGeometryCollectionActor>(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<UGeometryCollectionComponent>(EditorUtilities::FindMatchingComponentInstance(this, EditorActor));
if (EditorComponent)
{
EditorComponent->PreEditChange(FindFProperty<FProperty>(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<int32> & DynamicState = DynamicCollection->DynamicState;
if (ObjectType != EObjectStateTypeEnum::Chaos_Object_UserDefined)
{
if (RestCollection && (ObjectType == EObjectStateTypeEnum::Chaos_Object_Dynamic))
{
TManagedArray<int32>& InitialDynamicState = RestCollection->GetGeometryCollection()->InitialDynamicState;
for (int i = 0; i < DynamicState.Num(); i++)
{
DynamicState[i] = (InitialDynamicState[i] == static_cast<int32>(Chaos::EObjectStateType::Uninitialized)) ? static_cast<int32>(ObjectType) : InitialDynamicState[i];
}
}
else
{
for (int i = 0; i < DynamicState.Num(); i++)
{
DynamicState[i] = static_cast<int32>(ObjectType);
}
}
}
TManagedArray<bool> & Active = DynamicCollection->Active;
{
for (int i = 0; i < Active.Num(); i++)
{
Active[i] = Simulating;
}
}
TManagedArray<int32> & 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<UBodySetup>(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)
{
INC_DWORD_STAT_BY(STAT_GCTotalTransforms, DynamicData->Transforms.Num());
INC_DWORD_STAT_BY(STAT_GCChangedTransforms, DynamicData->ChangedCount);
// #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<FNaniteGeometryCollectionSceneProxy*>(SceneProxy);
ENQUEUE_RENDER_COMMAND(SendRenderDynamicData)(
[GeometryCollectionSceneProxy, DynamicData](FRHICommandListImmediate& RHICmdList)
{
GeometryCollectionSceneProxy->SetDynamicData_RenderThread(DynamicData);
GeometryCollectionSceneProxy->GetPrimitiveSceneInfo()->RequestGPUSceneUpdate();
}
);
}
else
{
FGeometryCollectionSceneProxy* GeometryCollectionSceneProxy = static_cast<FGeometryCollectionSceneProxy*>(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<UGeometryCollection*>(ToRawPtr(Component->RestCollection)); //const cast is ok here since we are explicitly in edit mode. Should all this editor code be in an editor module?
}
return nullptr;
}
#if WITH_EDITOR
TArray<FLinearColor> FScopedColorEdit::RandomColors;
FScopedColorEdit::FScopedColorEdit(UGeometryCollectionComponent* InComponent, bool bForceUpdate) : bUpdated(bForceUpdate), Component(InComponent)
{
if (RandomColors.Num() == 0)
{
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<int32>& SelectedBonesIn)
{
bUpdated = true;
Component->SelectedBones = SelectedBonesIn;
}
void FScopedColorEdit::AppendSelectedBones(const TArray<int32>& SelectedBonesIn)
{
bUpdated = true;
Component->SelectedBones.Append(SelectedBonesIn);
}
void FScopedColorEdit::ToggleSelectedBones(const TArray<int32>& 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<int32>& 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<FGeometryCollection, ESPMode::ThreadSafe> GeometryCollectionPtr = GeometryCollection->GetGeometryCollection();
switch (SelectionMode)
{
case GeometryCollection::ESelectionMode::None:
ResetBoneSelection();
break;
case GeometryCollection::ESelectionMode::AllGeometry:
{
TArray<int32> Roots;
FGeometryCollectionClusteringUtility::GetRootBones(GeometryCollectionPtr.Get(), Roots);
ResetBoneSelection();
for (int32 RootElement : Roots)
{
TArray<int32> LeafBones;
FGeometryCollectionClusteringUtility::GetLeafBones(GeometryCollectionPtr.Get(), RootElement, true, LeafBones);
AppendSelectedBones(LeafBones);
}
}
break;
case GeometryCollection::ESelectionMode::InverseGeometry:
{
TArray<int32> Roots;
FGeometryCollectionClusteringUtility::GetRootBones(GeometryCollectionPtr.Get(), Roots);
TArray<int32> NewSelection;
for (int32 RootElement : Roots)
{
TArray<int32> 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<int32>& TransformIndex = GeometryCollectionPtr->TransformIndex;
const TManagedArray<int32>& TransformToGeometryIndex = GeometryCollectionPtr->TransformToGeometryIndex;
const TManagedArray<TSet<int32>>& Proximity = GeometryCollectionPtr->GetAttribute<TSet<int32>>("Proximity", FGeometryCollection::GeometryGroup);
const TArray<int32> SelectedBones = GetSelectedBones();
TArray<int32> NewSelection;
for (int32 Bone : SelectedBones)
{
NewSelection.AddUnique(Bone);
const TSet<int32> &Neighbors = Proximity[TransformToGeometryIndex[Bone]];
for (int32 NeighborGeometryIndex : Neighbors)
{
NewSelection.AddUnique(TransformIndex[NeighborGeometryIndex]);
}
}
ResetBoneSelection();
AppendSelectedBones(NewSelection);
}
}
break;
case GeometryCollection::ESelectionMode::Siblings:
{
const TManagedArray<int32>& Parents = GeometryCollectionPtr->Parent;
const TManagedArray<TSet<int32>>& Children = GeometryCollectionPtr->Children;
const TArray<int32> SelectedBones = GetSelectedBones();
TArray<int32> 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<int32>& Parents = GeometryCollectionPtr->Parent;
const TArray<int32> SelectedBones = GetSelectedBones();
TArray<int32> NewSelection;
for (int32 Bone : SelectedBones)
{
int32 ParentBone = Parents[Bone];
TArray<int32> 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<int32>& SelectedBones = GetSelectedBones();
SetHighlightedBones(SelectedBones);
}
}
bool FScopedColorEdit::IsBoneHighlighted(int BoneIndex) const
{
return Component->HighlightedBones.Contains(BoneIndex);
}
void FScopedColorEdit::SetHighlightedBones(const TArray<int32>& HighlightedBonesIn)
{
if (Component->HighlightedBones != HighlightedBonesIn)
{
bUpdated = true;
Component->HighlightedBones = HighlightedBonesIn;
}
}
void FScopedColorEdit::AddHighlightedBone(int32 BoneIndex)
{
Component->HighlightedBones.Push(BoneIndex);
}
const TArray<int32>& FScopedColorEdit::GetHighlightedBones() const
{
return Component->GetHighlightedBones();
}
void FScopedColorEdit::ResetHighlightedBones()
{
if (Component->HighlightedBones.Num() > 0)
{
bUpdated = true;
Component->HighlightedBones.Empty();
}
}
void FScopedColorEdit::SetLevelViewMode(int ViewLevelIn)
{
if (Component->ViewLevel != ViewLevelIn)
{
bUpdated = true;
Component->ViewLevel = ViewLevelIn;
}
}
int FScopedColorEdit::GetViewLevel()
{
return Component->ViewLevel;
}
void FScopedColorEdit::UpdateBoneColors()
{
// @todo FractureTools - For large fractures updating colors this way is extremely slow because the render state (and thus all buffers) must be recreated.
// It would be better to push the update to the proxy via a render command and update the existing buffer directly
FGeometryCollectionEdit GeometryCollectionEdit = Component->EditRestCollection(GeometryCollection::EEditUpdate::None);
UGeometryCollection* GeometryCollection = GeometryCollectionEdit.GetRestCollection();
if(GeometryCollection)
{
FGeometryCollection* Collection = GeometryCollection->GetGeometryCollection().Get();
FLinearColor BlankColor(FColor(80, 80, 80, 50));
const TManagedArray<int>& Parents = Collection->Parent;
bool HasLevelAttribute = Collection->HasAttribute("Level", FTransformCollection::TransformGroup);
TManagedArray<int>* Levels = nullptr;
if (HasLevelAttribute)
{
Levels = &Collection->GetAttribute<int32>("Level", FTransformCollection::TransformGroup);
}
TManagedArray<FLinearColor>& BoneColors = Collection->BoneColor;
for (int32 BoneIndex = 0, NumBones = Parents.Num() ; BoneIndex < NumBones; ++BoneIndex)
{
FLinearColor BoneColor = FLinearColor(FColor::Black);
if (Component->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<Chaos::FPBDRigidsSolver>();
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<FFieldSystemCommand>& CombinedCommmands)
{
CombinedCommmands.Reset();
for (const AFieldSystemActor* FieldSystemActor : InitializationFields)
{
if (FieldSystemActor != nullptr)
{
if (FieldSystemActor->GetFieldSystemComponent())
{
const int32 NumCommands = FieldSystemActor->GetFieldSystemComponent()->ConstructionCommands.GetNumCommands();
if (NumCommands > 0)
{
for (int32 CommandIndex = 0; CommandIndex < NumCommands; ++CommandIndex)
{
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<FFieldSystemMetaData>(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<AChaosSolverActor>(Scene->GetSolverActor()) : nullptr;
}
return nullptr;
}
void UGeometryCollectionComponent::CalculateLocalBounds()
{
LocalBounds.Init();
const TManagedArray<FBox>& BoundingBoxes = GetBoundingBoxArray();
const TManagedArray<int32>& 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<FVector>& ExplodedVectors = RestCollection->GetGeometryCollection()->GetAttribute<FVector>("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<UPrimitiveComponent*> PrimitiveComponents;
Actor->GetComponents(PrimitiveComponents);
for (UPrimitiveComponent* PrimitiveComponent : PrimitiveComponents)
{
bool ValidComponent = false;
if (UStaticMeshComponent* StaticMeshComp = Cast<UStaticMeshComponent>(PrimitiveComponent))
{
// unhacked.
//StaticMeshComp->SetVisibility(false);
}
else if (UGeometryCollectionComponent* GeometryCollectionComponent = Cast<UGeometryCollectionComponent>(PrimitiveComponent))
{
if (!GeometryCollectionComponent->IsVisible())
{
continue;
}
GeometryCollectionComponent->SetVisibility(true);
}
}
TInlineComponentArray<UChildActorComponent*> ChildActorComponents;
Actor->GetComponents(ChildActorComponents);
for (UChildActorComponent* ChildComponent : ChildActorComponents)
{
AActor* ChildActor = ChildComponent->GetChildActor();
if (ChildActor)
{
SwitchRenderModels(ChildActor);
}
}
}
#if GEOMETRYCOLLECTION_EDITOR_SELECTION
void UGeometryCollectionComponent::EnableTransformSelectionMode(bool bEnable)
{
// TODO: Support for Nanite?
if (SceneProxy && !SceneProxy->IsNaniteMesh() && RestCollection && RestCollection->HasVisibleGeometry())
{
static_cast<FGeometryCollectionSceneProxy*>(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<UStaticMesh>(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<UActorComponent*> TargetComponents;
OwningActor->GetComponents(TargetComponents, false);
for (UActorComponent* TargetComponent : TargetComponents)
{
if (TargetComponent->GetOuter() == this)
{
if (UInstancedStaticMeshComponent* ISMComponent = Cast<UInstancedStaticMeshComponent>(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<UStaticMesh>(Exemplar.StaticMeshExemplar.TryLoad()))
{
if (UInstancedStaticMeshComponent* ISMC = NewObject<UInstancedStaticMeshComponent>(this))
{
ISMC->SetStaticMesh(ExemplarStaticMesh);
ISMC->SetCullDistances(Exemplar.StartCullDistance, Exemplar.EndCullDistance);
ISMC->SetCanEverAffectNavigation(false);
ISMC->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
ISMC->SetCastShadow(false);
ISMC->SetMobility(EComponentMobility::Stationary);
ISMC->SetupAttachment(this);
ISMC->RegisterComponent();
EmbeddedGeometryComponents.Add(ISMC);
}
}
}
CalculateGlobalMatrices();
RefreshEmbeddedGeometry();
}
}