You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
* Implemented thread safe object pool / free list allocator (FGeometryCollectionDynamicDataPool) * Added Chaos stats for total transforms and changed transforms * Removed TransformsAreEqual mechanism that caused full transform updates to occur unnecessarily for 2 subsequent frames, as the state wasn't stable until the previous 3 frames were stable. This was incredibly innefficient for GPUScene, etc.. * InitDynamicData now returns the data allocation (which can be null if not dynamic or if no transforms have changed), users of this now check if it's available, and either skip or fall back to the static/rest path otherwise * Cleaned up some code paths by combining them with similar passes * Removed tons of allocations, including heavy allocations/memcopies of transform data, even on stable frames (which were immediately deallocated right after allocation and setup of the dynamic data) * Saves ~0.5 to 1.0ms of CPU time in heavy scenes * Saves ~0.5ms of GPU time doing far less GPUScene updates * Main optimization around this refactor is forthcoming in a followup CL (avoid calling MarkRenderTransformDirty in TickComponent). #rb brett.miller, brice.criswell #lockdown nick.whiting [FYI] brian.karis, rune.stubbe #jira UETOP-1088 #ROBOMERGE-SOURCE: CL 16059409 in //UE5/Release-5.0-EarlyAccess/... #ROBOMERGE-BOT: STARSHIP (Release-5.0-EarlyAccess -> Main) (v789-15992632) [CL 16061411 by graham wihlidal in ue5-main branch]
2824 lines
92 KiB
C++
2824 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 (int 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();
|
|
|
|
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 (GeometryCollectionSceneProxy->GetPrimitiveSceneInfo())
|
|
{
|
|
GeometryCollectionSceneProxy->GetPrimitiveSceneInfo()->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()
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_GCInitDynamicData);
|
|
|
|
FGeometryCollectionDynamicData* DynamicData = nullptr;
|
|
|
|
const bool bEditorMode = bShowBoneColors || bEnableBoneSelection;
|
|
const bool bIsDynamic = GetIsObjectDynamic() || bEditorMode;
|
|
|
|
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)
|
|
{
|
|
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();
|
|
|
|
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();
|
|
|
|
}
|
|
}
|
|
|