You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
2137 lines
72 KiB
C++
2137 lines
72 KiB
C++
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "GeometryCollection/GeometryCollectionComponent.h"
|
|
|
|
#include "Async/ParallelFor.h"
|
|
#include "Components/BoxComponent.h"
|
|
#include "GeometryCollection/GeometryCollectionObject.h"
|
|
#include "GeometryCollection/GeometryCollectionAlgo.h"
|
|
#include "GeometryCollection/GeometryCollectionComponentPluginPrivate.h"
|
|
#include "GeometryCollection/GeometryCollectionSceneProxy.h"
|
|
#include "GeometryCollection/GeometryCollectionSQAccelerator.h"
|
|
#include "GeometryCollection/GeometryCollectionUtility.h"
|
|
#include "GeometryCollection/GeometryCollectionClusteringUtility.h"
|
|
#include "GeometryCollection/GeometryCollectionCache.h"
|
|
#include "GeometryCollection/GeometryCollectionActor.h"
|
|
#include "GeometryCollection/GeometryCollectionDebugDrawComponent.h"
|
|
#include "Physics/Experimental/PhysScene_Chaos.h"
|
|
#include "Chaos/DebugDrawQueue.h"
|
|
#include "DrawDebugHelpers.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "ChaosSolversModule.h"
|
|
#include "ChaosStats.h"
|
|
#include "SolverObjects/GeometryCollectionPhysicsObject.h"
|
|
#include "PBDRigidsSolver.h"
|
|
|
|
#if WITH_EDITOR
|
|
#include "AssetToolsModule.h"
|
|
#include "Editor.h"
|
|
#endif
|
|
|
|
#include "PhysicsEngine/BodySetup.h"
|
|
#include "PhysicsEngine/BodyInstance.h"
|
|
#include "Chaos/ChaosGameplayEventDispatcher.h"
|
|
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
#include "Logging/MessageLog.h"
|
|
#include "Misc/UObjectToken.h"
|
|
#endif //!(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
|
|
#if INTEL_ISPC
|
|
#include "GeometryCollectionComponent.ispc.generated.h"
|
|
#endif
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(UGCC_LOG, Error, All);
|
|
|
|
#if INCLUDE_CHAOS && WITH_PHYSX && !WITH_CHAOS_NEEDS_TO_BE_FIXED
|
|
FGeometryCollectionSQAccelerator GlobalGeomCollectionAccelerator; //todo(ocohen): proper lifetime management needed
|
|
|
|
void HackRegisterGeomAccelerator(UGeometryCollectionComponent& Component)
|
|
{
|
|
if (UWorld* World = Component.GetWorld())
|
|
{
|
|
if (FPhysScene* PhysScene = World->GetPhysicsScene())
|
|
{
|
|
if (FSQAcceleratorUnion* SQAccelerationUnion = PhysScene->GetSQAcceleratorUnion())
|
|
{
|
|
SQAccelerationUnion->AddSQAccelerator(&GlobalGeomCollectionAccelerator);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
FGeomComponentCacheParameters::FGeomComponentCacheParameters()
|
|
: CacheMode(EGeometryCollectionCacheType::None)
|
|
, TargetCache(nullptr)
|
|
, ReverseCacheBeginTime(0.0f)
|
|
, SaveCollisionData(false)
|
|
, DoGenerateCollisionData(false)
|
|
, CollisionDataSizeMax(512)
|
|
, DoCollisionDataSpatialHash(false)
|
|
, CollisionDataSpatialHashRadius(50.f)
|
|
, MaxCollisionPerCell(1)
|
|
, SaveBreakingData(false)
|
|
, DoGenerateBreakingData(false)
|
|
, BreakingDataSizeMax(512)
|
|
, DoBreakingDataSpatialHash(false)
|
|
, BreakingDataSpatialHashRadius(50.f)
|
|
, MaxBreakingPerCell(1)
|
|
, SaveTrailingData(false)
|
|
, DoGenerateTrailingData(false)
|
|
, TrailingDataSizeMax(512)
|
|
, TrailingMinSpeedThreshold(200.f)
|
|
, TrailingMinVolumeThreshold(10000.f)
|
|
{
|
|
}
|
|
|
|
UGeometryCollectionComponent::UGeometryCollectionComponent(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
, ChaosSolverActor(nullptr)
|
|
, Simulating(true)
|
|
, InitializationState(ESimulationInitializationState::Unintialized)
|
|
, ObjectType(EObjectStateTypeEnum::Chaos_Object_Dynamic)
|
|
, EnableClustering(true)
|
|
, ClusterGroupIndex(0)
|
|
, MaxClusterLevel(100)
|
|
, DamageThreshold({250.0})
|
|
, ClusterConnectionType(EClusterConnectionTypeEnum::Chaos_PointImplicit)
|
|
, CollisionGroup(0)
|
|
, CollisionSampleFraction(1.0)
|
|
, InitialVelocityType(EInitialVelocityTypeEnum::Chaos_Initial_Velocity_User_Defined)
|
|
, InitialLinearVelocity(0.f, 0.f, 0.f)
|
|
, InitialAngularVelocity(0.f, 0.f, 0.f)
|
|
, BaseRigidBodyIndex(INDEX_NONE)
|
|
, NumParticlesAdded(0)
|
|
, CachePlayback(false)
|
|
, bNotifyBreaks(false)
|
|
, bNotifyCollisions(false)
|
|
, bRenderStateDirty(true)
|
|
, bShowBoneColors(false)
|
|
, bEnableBoneSelection(false)
|
|
, ViewLevel(-1)
|
|
, NavmeshInvalidationTimeSliceIndex(0)
|
|
, IsObjectDynamic(false)
|
|
, IsObjectLoading(true)
|
|
, PhysicsObject(nullptr)
|
|
#if WITH_EDITOR && WITH_EDITORONLY_DATA
|
|
, EditorActor(nullptr)
|
|
#endif
|
|
#if GEOMETRYCOLLECTION_EDITOR_SELECTION
|
|
, bIsTransformSelectionModeEnabled(false)
|
|
#endif // #if GEOMETRYCOLLECTION_EDITOR_SELECTION
|
|
{
|
|
PrimaryComponentTick.bCanEverTick = true;
|
|
bTickInEditor = true;
|
|
bAutoActivate = true;
|
|
|
|
static uint32 GlobalNavMeshInvalidationCounter = 0;
|
|
//space these out over several frames (3 is arbitrary)
|
|
GlobalNavMeshInvalidationCounter += 3;
|
|
NavmeshInvalidationTimeSliceIndex = GlobalNavMeshInvalidationCounter;
|
|
|
|
ChaosMaterial = MakeUnique<Chaos::TChaosPhysicsMaterial<float>>();
|
|
|
|
WorldBounds = FBoxSphereBounds(FBox(ForceInit));
|
|
|
|
// default current cache time
|
|
CurrentCacheTime = MAX_flt;
|
|
|
|
// Buffer for rolling cache of past N=3 transforms being equal.
|
|
TransformsAreEqual.AddDefaulted(3);
|
|
TransformsAreEqualIndex = 0;
|
|
|
|
SetGenerateOverlapEvents(false);
|
|
}
|
|
|
|
|
|
#if INCLUDE_CHAOS
|
|
Chaos::FPBDRigidsSolver* GetSolver(const UGeometryCollectionComponent& GeometryCollectionComponent)
|
|
{
|
|
return GeometryCollectionComponent.ChaosSolverActor != nullptr ? GeometryCollectionComponent.ChaosSolverActor->GetSolver() : GeometryCollectionComponent.GetOwner()->GetWorld()->PhysicsScene_Chaos->GetSolver();
|
|
}
|
|
#endif
|
|
|
|
void UGeometryCollectionComponent::BeginPlay()
|
|
{
|
|
Super::BeginPlay();
|
|
#if INCLUDE_CHAOS
|
|
#if WITH_PHYSX && !WITH_CHAOS_NEEDS_TO_BE_FIXED
|
|
HackRegisterGeomAccelerator(*this);
|
|
#endif
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Commenting out these callbacks for now due to the threading model. The callbacks here
|
|
// expect the rest collection to be mutable which is not the case when running in multiple
|
|
// threads. Ideally we have some separate animation collection or track that we cache to
|
|
// without affecting the data we've dispatched to the physics thread
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// ---------- SolverCallbacks->SetResetAnimationCacheFunction([&]()
|
|
// ---------- {
|
|
// ---------- FGeometryCollectionEdit Edit = EditRestCollection();
|
|
// ---------- Edit.GetRestCollection()->RecordedData.SetNum(0);
|
|
// ---------- });
|
|
// ---------- SolverCallbacks->SetUpdateTransformsFunction([&](const TArrayView<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);
|
|
// ---------- }
|
|
// ---------- });
|
|
// ---------- }
|
|
// ---------- });
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
FChaosSolversModule* ChaosModule = FModuleManager::Get().GetModulePtr<FChaosSolversModule>("ChaosSolvers");
|
|
if (ChaosModule != nullptr)
|
|
{
|
|
Chaos::FPBDRigidsSolver* Solver = GetSolver(*this);
|
|
if (Solver != nullptr)
|
|
{
|
|
if (PhysicsObject != nullptr)
|
|
{
|
|
ChaosModule->GetDispatcher()->EnqueueCommand(Solver, [&InPhysicsObject = PhysicsObject](Chaos::FPBDRigidsSolver* InSolver)
|
|
{
|
|
if (InPhysicsObject)
|
|
{
|
|
InPhysicsObject->ActivateBodies();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
InitializationState = ESimulationInitializationState::Activated;
|
|
|
|
#endif
|
|
|
|
// default current cache time
|
|
CurrentCacheTime = MAX_flt;
|
|
}
|
|
|
|
|
|
void UGeometryCollectionComponent::EndPlay(EEndPlayReason::Type ReasonEnd)
|
|
{
|
|
#if WITH_EDITOR && WITH_EDITORONLY_DATA
|
|
// Track our editor component if needed for syncing simulations back from PIE on shutdown
|
|
EditorActor = EditorUtilities::GetEditorWorldCounterpartActor(GetTypedOuter<AActor>());
|
|
#endif
|
|
|
|
Super::EndPlay(ReasonEnd);
|
|
|
|
CurrentCacheTime = MAX_flt;
|
|
}
|
|
|
|
FBoxSphereBounds UGeometryCollectionComponent::CalcBounds(const FTransform& LocalToWorldIn) const
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_GCCUpdateBounds);
|
|
|
|
// Don't use bounds calculated in the physics object if we are doing sequencer cache playback
|
|
// because we are overriding the transforms in the GC
|
|
if (!CachePlayback && WorldBounds.GetSphere().W > 1e-5)
|
|
{
|
|
return WorldBounds;
|
|
}
|
|
else if (RestCollection && RestCollection->HasVisibleGeometry())
|
|
{
|
|
const FMatrix LocalToWorldWithScale = LocalToWorldIn.ToMatrixWithScale();
|
|
|
|
FBox BoundingBox(ForceInit);
|
|
|
|
//Hold on to reference so it doesn't get GC'ed
|
|
auto HackGeometryCollectionPtr = RestCollection->GetGeometryCollection();
|
|
|
|
const TManagedArray<FBox>& BoundingBoxes = GetBoundingBoxArray();
|
|
const TManagedArray<int32>& TransformIndices = GetTransformIndexArray();
|
|
const TManagedArray<int32>& ParentIndices = GetParentArray();
|
|
const TManagedArray<int32>& TransformToGeometryIndex = GetTransformToGeometryIndexArray();
|
|
|
|
const int32 NumBoxes = BoundingBoxes.Num();
|
|
|
|
// #todo(dmp): we could do the bbox transform in parallel with a bit of reformulating
|
|
// #todo(dmp): there are some cases where the calcbounds function is called before the component
|
|
// has set the global matrices cache while in the editor. This is a somewhat weak guard against this
|
|
// to default to just calculating tmp global matrices. This should be removed or modified somehow
|
|
// such that we always cache the global matrices and this method always does the correct behavior
|
|
if (GlobalMatrices.Num() != RestCollection->NumElements(FGeometryCollection::TransformGroup))
|
|
{
|
|
TArray<FMatrix> TmpGlobalMatrices;
|
|
GeometryCollectionAlgo::GlobalMatrices(GetTransformArray(), ParentIndices, TmpGlobalMatrices);
|
|
|
|
if (TmpGlobalMatrices.Num() == 0)
|
|
{
|
|
return FBoxSphereBounds(ForceInitToZero);
|
|
}
|
|
|
|
for (int32 BoxIdx = 0; BoxIdx < NumBoxes; ++BoxIdx)
|
|
{
|
|
const int32 TransformIndex = TransformIndices[BoxIdx];
|
|
|
|
if(RestCollection->GetGeometryCollection()->IsGeometry(TransformIndex))
|
|
{
|
|
BoundingBox += BoundingBoxes[BoxIdx].TransformBy(TmpGlobalMatrices[TransformIndex] * LocalToWorldWithScale);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#if INTEL_ISPC
|
|
ispc::BoxCalcBounds(
|
|
(int32 *)&TransformToGeometryIndex[0],
|
|
(int32 *)&TransformIndices[0],
|
|
(ispc::FMatrix *)&GlobalMatrices[0],
|
|
(ispc::FBox *)&BoundingBoxes[0],
|
|
(ispc::FMatrix &)LocalToWorldWithScale,
|
|
(ispc::FBox &)BoundingBox,
|
|
NumBoxes);
|
|
#else
|
|
for (int32 BoxIdx = 0; BoxIdx < NumBoxes; ++BoxIdx)
|
|
{
|
|
const int32 TransformIndex = TransformIndices[BoxIdx];
|
|
|
|
if(RestCollection->GetGeometryCollection()->IsGeometry(TransformIndex))
|
|
{
|
|
BoundingBox += BoundingBoxes[BoxIdx].TransformBy(GlobalMatrices[TransformIndex] * LocalToWorldWithScale);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return FBoxSphereBounds(BoundingBox);
|
|
}
|
|
return FBoxSphereBounds(ForceInitToZero);
|
|
}
|
|
|
|
void UGeometryCollectionComponent::CreateRenderState_Concurrent()
|
|
{
|
|
Super::CreateRenderState_Concurrent();
|
|
|
|
if (SceneProxy && RestCollection && RestCollection->HasVisibleGeometry())
|
|
{
|
|
FGeometryCollectionSceneProxy* const GeometryCollectionSceneProxy = static_cast<FGeometryCollectionSceneProxy*>(SceneProxy);
|
|
#if GEOMETRYCOLLECTION_EDITOR_SELECTION
|
|
// Re-init subsections
|
|
if (bIsTransformSelectionModeEnabled)
|
|
{
|
|
GeometryCollectionSceneProxy->UseSubSections(true, false); // Do not force reinit now, it'll be done in SetConstantData_RenderThread
|
|
}
|
|
#endif // #if GEOMETRYCOLLECTION_EDITOR_SELECTION
|
|
|
|
FGeometryCollectionConstantData* const ConstantData = ::new FGeometryCollectionConstantData;
|
|
InitConstantData(ConstantData);
|
|
|
|
FGeometryCollectionDynamicData* const DynamicData = ::new FGeometryCollectionDynamicData;
|
|
InitDynamicData(DynamicData);
|
|
|
|
ENQUEUE_RENDER_COMMAND(CreateRenderState)(
|
|
[GeometryCollectionSceneProxy, ConstantData, DynamicData](FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
if (GeometryCollectionSceneProxy)
|
|
{
|
|
GeometryCollectionSceneProxy->SetConstantData_RenderThread(ConstantData);
|
|
GeometryCollectionSceneProxy->SetDynamicData_RenderThread(DynamicData);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
FPrimitiveSceneProxy* UGeometryCollectionComponent::CreateSceneProxy()
|
|
{
|
|
if (RestCollection)
|
|
{
|
|
return new FGeometryCollectionSceneProxy(this);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool UGeometryCollectionComponent::ShouldCreatePhysicsState() const
|
|
{
|
|
// Geometry collections always create physics state, not relying on the
|
|
// underlying implementation that requires the body instance to decide
|
|
return true;
|
|
}
|
|
|
|
bool UGeometryCollectionComponent::HasValidPhysicsState() const
|
|
{
|
|
return PhysicsObject != nullptr;
|
|
}
|
|
|
|
void UGeometryCollectionComponent::SetNotifyBreaks(bool bNewNotifyBreaks)
|
|
{
|
|
if (bNotifyBreaks != bNewNotifyBreaks)
|
|
{
|
|
bNotifyBreaks = bNewNotifyBreaks;
|
|
UpdateBreakEventRegistration();
|
|
}
|
|
}
|
|
|
|
FBodyInstance* UGeometryCollectionComponent::GetBodyInstance(FName BoneName /*= NAME_None*/, bool bGetWelded /*= true*/) const
|
|
{
|
|
return nullptr;// const_cast<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);
|
|
}
|
|
}
|
|
|
|
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 INCLUDE_CHAOS
|
|
if (BodyInstance.bNotifyRigidBodyCollision || bNotifyBreaks || bNotifyCollisions)
|
|
{
|
|
if (AChaosSolverActor* const SolverActor = GetPhysicsSolverActor())
|
|
{
|
|
if (UChaosGameplayEventDispatcher* const EventDispatcher = SolverActor->GetGameplayEventDispatcher())
|
|
{
|
|
if (bNotifyCollisions || BodyInstance.bNotifyRigidBodyCollision)
|
|
{
|
|
EventDispatcher->RegisterForCollisionEvents(this, this);
|
|
}
|
|
|
|
if (bNotifyBreaks)
|
|
{
|
|
EventDispatcher->RegisterForBreakEvents(this, &DispatchGeometryCollectionBreakEvent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void UGeometryCollectionComponent::UpdateRBCollisionEventRegistration()
|
|
{
|
|
#if INCLUDE_CHAOS
|
|
if (AChaosSolverActor* const SolverActor = GetPhysicsSolverActor())
|
|
{
|
|
if (UChaosGameplayEventDispatcher* const EventDispatcher = SolverActor->GetGameplayEventDispatcher())
|
|
{
|
|
if (bNotifyCollisions || BodyInstance.bNotifyRigidBodyCollision)
|
|
{
|
|
EventDispatcher->RegisterForCollisionEvents(this, this);
|
|
}
|
|
else
|
|
{
|
|
EventDispatcher->UnRegisterForCollisionEvents(this, this);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void UGeometryCollectionComponent::UpdateBreakEventRegistration()
|
|
{
|
|
#if INCLUDE_CHAOS
|
|
if (AChaosSolverActor* const SolverActor = GetPhysicsSolverActor())
|
|
{
|
|
if (UChaosGameplayEventDispatcher* const EventDispatcher = SolverActor->GetGameplayEventDispatcher())
|
|
{
|
|
if (bNotifyBreaks)
|
|
{
|
|
EventDispatcher->RegisterForBreakEvents(this, &DispatchGeometryCollectionBreakEvent);
|
|
}
|
|
else
|
|
{
|
|
EventDispatcher->UnRegisterForBreakEvents(this);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void UGeometryCollectionComponent::InitConstantData(FGeometryCollectionConstantData* ConstantData) const
|
|
{
|
|
// Constant data should all be moved to the DDC as time permits.
|
|
|
|
check(ConstantData);
|
|
check(RestCollection);
|
|
const FGeometryCollection* Collection = RestCollection->GetGeometryCollection().Get();
|
|
check(Collection);
|
|
|
|
const int32 NumPoints = Collection->NumElements(FGeometryCollection::VerticesGroup);
|
|
const TManagedArray<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);
|
|
|
|
// #todo(dmp): we should eventually get this working where we use geometry nodes
|
|
// that signify original unfractured geometry. For now, this system is broken.
|
|
/*
|
|
for (int i = 0; i < Collection->NumElements(FGeometryCollection::TransformGroup); ++i)
|
|
{
|
|
const FGeometryCollectionBoneNode &CurrBone = BoneHierarchy[i];
|
|
|
|
// root node could be parent geo
|
|
if (CurrBone.Parent == INDEX_NONE)
|
|
{
|
|
int32 GeometryIndex = TransformToGeometryIndex[i];
|
|
|
|
// found geometry associated with base mesh root node
|
|
if (GeometryIndex != INDEX_NONE)
|
|
{
|
|
int32 CurrFaceStart = FaceStart[GeometryIndex];
|
|
int32 CurrFaceCount = FaceCount[GeometryIndex];
|
|
|
|
// add all the faces to the original geometry face array
|
|
for (int face = CurrFaceStart; face < CurrFaceStart + CurrFaceCount; ++face)
|
|
{
|
|
BaseMeshIndices.Add(Indices[face]);
|
|
BaseMeshOriginalFaceIndices.Add(face);
|
|
}
|
|
|
|
// build an array of mesh sections
|
|
ConstantData->HasOriginalMesh = true;
|
|
}
|
|
else
|
|
{
|
|
// all the direct decedents of the root node with no geometry are original geometry
|
|
for (int32 CurrChild : CurrBone.Children)
|
|
{
|
|
int32 GeometryIndex = TransformToGeometryIndex[CurrChild];
|
|
if (GeometryIndex != INDEX_NONE)
|
|
{
|
|
// original geo static mesh
|
|
int32 CurrFaceStart = FaceStart[GeometryIndex];
|
|
int32 CurrFaceCount = FaceCount[GeometryIndex];
|
|
|
|
// add all the faces to the original geometry face array
|
|
for (int face = CurrFaceStart; face < CurrFaceStart + CurrFaceCount; ++face)
|
|
{
|
|
BaseMeshIndices.Add(Indices[face]);
|
|
BaseMeshOriginalFaceIndices.Add(face);
|
|
}
|
|
|
|
ConstantData->HasOriginalMesh = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
|
|
ConstantData->OriginalMeshSections = Collection->BuildMeshSections(BaseMeshIndices, BaseMeshOriginalFaceIndices, ConstantData->OriginalMeshIndices);
|
|
|
|
TArray<FMatrix> RestMatrices;
|
|
GeometryCollectionAlgo::GlobalMatrices(RestCollection->GetGeometryCollection()->Transform, RestCollection->GetGeometryCollection()->Parent, RestMatrices);
|
|
|
|
ConstantData->RestTransforms = MoveTemp(RestMatrices);
|
|
}
|
|
|
|
void UGeometryCollectionComponent::InitDynamicData(FGeometryCollectionDynamicData * DynamicData)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_GCInitDynamicData);
|
|
|
|
check(DynamicData);
|
|
DynamicData->IsDynamic = this->GetIsObjectDynamic() || bShowBoneColors || bEnableBoneSelection;
|
|
DynamicData->IsLoading = this->GetIsObjectLoading();
|
|
|
|
if (CachePlayback && CacheParameters.TargetCache && CacheParameters.TargetCache->GetData())
|
|
{
|
|
const TManagedArray<int32> &Parents = GetParentArray();
|
|
const TManagedArray<TSet<int32>> &Children = GetChildrenArray();
|
|
const TManagedArray<FTransform> &Transform = RestCollection->GetGeometryCollection()->Transform;
|
|
const TManagedArray<FTransform> &MassToLocal = RestCollection->GetGeometryCollection()->GetAttribute<FTransform>("MassToLocal", FGeometryCollection::TransformGroup);
|
|
|
|
DynamicData->IsDynamic = true;
|
|
|
|
// if we are already on the current cached frame, return
|
|
if (FMath::IsNearlyEqual(CurrentCacheTime, DesiredCacheTime) && GlobalMatrices.Num() != 0)
|
|
{
|
|
DynamicData->PrevTransforms = GlobalMatrices;
|
|
DynamicData->Transforms = GlobalMatrices;
|
|
return;
|
|
}
|
|
|
|
// #todo(dmp): find a better place to calculate and store this
|
|
float CacheDt = CacheParameters.TargetCache->GetData()->GetDt();
|
|
|
|
// if the input simulation time to playback is the first frame, reset simulation time
|
|
if (DesiredCacheTime <= CacheDt || FMath::IsNearlyEqual(CurrentCacheTime, FLT_MAX))
|
|
{
|
|
CurrentCacheTime = DesiredCacheTime;
|
|
|
|
GeometryCollectionAlgo::GlobalMatrices(GetTransformArray(), GetParentArray(), GlobalMatrices);
|
|
DynamicData->PrevTransforms = GlobalMatrices;
|
|
DynamicData->Transforms = GlobalMatrices;
|
|
|
|
EventsPlayed.Empty();
|
|
EventsPlayed.AddDefaulted(CacheParameters.TargetCache->GetData()->Records.Num());
|
|
}
|
|
else if (GlobalMatrices.Num() == 0)
|
|
{
|
|
// bad case here. Sequencer starts at non zero position We cannot correctly reconstruct the current frame, so give a warning
|
|
GeometryCollectionAlgo::GlobalMatrices(GetTransformArray(), GetParentArray(), GlobalMatrices);
|
|
DynamicData->PrevTransforms = GlobalMatrices;
|
|
DynamicData->Transforms = GlobalMatrices;
|
|
|
|
EventsPlayed.Empty();
|
|
EventsPlayed.AddDefaulted(CacheParameters.TargetCache->GetData()->Records.Num());
|
|
|
|
UE_LOG(UGCC_LOG, Warning, TEXT("Cache out of sync - must rewind sequencer to start frame"));
|
|
}
|
|
else if (DesiredCacheTime >= CurrentCacheTime)
|
|
{
|
|
int NumSteps = floor((DesiredCacheTime - CurrentCacheTime) / CacheDt);
|
|
float LastDt = FMath::Fmod(DesiredCacheTime - CurrentCacheTime, CacheDt);
|
|
NumSteps += LastDt > SMALL_NUMBER ? 1 : 0;
|
|
|
|
FTransform ActorToWorld = GetComponentTransform();
|
|
|
|
// Jump ahead in increments of CacheDt evaluating the cache until we reach our desired time
|
|
for (int st = 0; st < NumSteps; ++st)
|
|
{
|
|
float TimeIncrement = st == NumSteps - 1 ? LastDt : CacheDt;
|
|
CurrentCacheTime += TimeIncrement;
|
|
|
|
DynamicData->PrevTransforms = GlobalMatrices;
|
|
|
|
const FRecordedFrame* FirstFrame = nullptr;
|
|
const FRecordedFrame* SecondFrame = nullptr;
|
|
CacheParameters.TargetCache->GetData()->GetFramesForTime(DesiredCacheTime, FirstFrame, SecondFrame);
|
|
|
|
if (FirstFrame && !SecondFrame)
|
|
{
|
|
|
|
const TArray<FTransform> &xforms = FirstFrame->Transforms;
|
|
const TArray<int32> &TransformIndices = FirstFrame->TransformIndices;
|
|
|
|
const int32 NumActives = FirstFrame->TransformIndices.Num();
|
|
for (int i = 0; i < NumActives; ++i)
|
|
{
|
|
const int32 InternalIndexTmp = TransformIndices[i];
|
|
|
|
if (InternalIndexTmp >= GlobalMatrices.Num())
|
|
{
|
|
UE_LOG(UGCC_LOG, Error,
|
|
TEXT("%s: TargetCache (%s) is out of sync with GeometryCollection. Regenerate the cache."),
|
|
*RestCollection->GetName(), *CacheParameters.TargetCache->GetName());
|
|
DynamicData->PrevTransforms = GlobalMatrices;
|
|
DynamicData->Transforms = GlobalMatrices;
|
|
return;
|
|
}
|
|
|
|
// calculate global matrix for current
|
|
FTransform ParticleToWorld = xforms[i];
|
|
|
|
FTransform CurrGlobalTransform = MassToLocal[InternalIndexTmp].GetRelativeTransformReverse(ParticleToWorld).GetRelativeTransform(ActorToWorld);
|
|
CurrGlobalTransform.NormalizeRotation();
|
|
GlobalMatrices[InternalIndexTmp] = CurrGlobalTransform.ToMatrixWithScale();
|
|
|
|
// Traverse from active parent node down to all children and set global transforms
|
|
GeometryCollectionAlgo::GlobalMatricesFromRoot(InternalIndexTmp, Transform, Children, GlobalMatrices);
|
|
}
|
|
}
|
|
else if (FirstFrame && SecondFrame && DesiredCacheTime > FirstFrame->Timestamp)
|
|
{
|
|
const float Alpha = (DesiredCacheTime - FirstFrame->Timestamp) / (SecondFrame->Timestamp - FirstFrame->Timestamp);
|
|
check(0 <= Alpha && Alpha <= 1.0f);
|
|
|
|
const int32 NumActives = SecondFrame->TransformIndices.Num();
|
|
for (int Index = 0; Index < NumActives; ++Index)
|
|
{
|
|
const int32 InternalIndexTmp = SecondFrame->TransformIndices[Index];
|
|
|
|
// check if transform index is valid
|
|
if (InternalIndexTmp >= GlobalMatrices.Num())
|
|
{
|
|
UE_LOG(UGCC_LOG, Error,
|
|
TEXT("%s: TargetCache (%s) is out of sync with GeometryCollection. Regenerate the cache."),
|
|
*RestCollection->GetName(), *CacheParameters.TargetCache->GetName());
|
|
DynamicData->PrevTransforms = GlobalMatrices;
|
|
DynamicData->Transforms = GlobalMatrices;
|
|
return;
|
|
}
|
|
|
|
const int32 PreviousIndexSlot = Index < SecondFrame->PreviousTransformIndices.Num() ? SecondFrame->PreviousTransformIndices[Index] : INDEX_NONE;
|
|
|
|
if (PreviousIndexSlot != INDEX_NONE)
|
|
{
|
|
FTransform ParticleToWorld;
|
|
ParticleToWorld.Blend(FirstFrame->Transforms[PreviousIndexSlot], SecondFrame->Transforms[Index], Alpha);
|
|
|
|
FTransform CurrGlobalTransform = MassToLocal[InternalIndexTmp].GetRelativeTransformReverse(ParticleToWorld).GetRelativeTransform(ActorToWorld);
|
|
CurrGlobalTransform.NormalizeRotation();
|
|
GlobalMatrices[InternalIndexTmp] = CurrGlobalTransform.ToMatrixWithScale();
|
|
|
|
// Traverse from active parent node down to all children and set global transforms
|
|
GeometryCollectionAlgo::GlobalMatricesFromRoot(InternalIndexTmp, Transform, Children, GlobalMatrices);
|
|
}
|
|
else
|
|
{
|
|
FTransform ParticleToWorld = SecondFrame->Transforms[Index];
|
|
|
|
FTransform CurrGlobalTransform = MassToLocal[InternalIndexTmp].GetRelativeTransformReverse(ParticleToWorld).GetRelativeTransform(ActorToWorld);
|
|
CurrGlobalTransform.NormalizeRotation();
|
|
GlobalMatrices[InternalIndexTmp] = CurrGlobalTransform.ToMatrixWithScale();
|
|
|
|
// Traverse from active parent node down to all children and set global transforms
|
|
GeometryCollectionAlgo::GlobalMatricesFromRoot(InternalIndexTmp, Transform, Children, GlobalMatrices);
|
|
}
|
|
}
|
|
}
|
|
|
|
DynamicData->Transforms = GlobalMatrices;
|
|
|
|
/**********************************************************************************************************************************************************************************/
|
|
// Capture all events for the given time
|
|
|
|
if(false)
|
|
{
|
|
#if INCLUDE_CHAOS
|
|
// clear events on the solver
|
|
Chaos::FPBDRigidsSolver *Solver = GetSolver(*this);
|
|
|
|
if (Solver)
|
|
{
|
|
Chaos::FSolverWriteLock ScopedWriteLock(Solver);
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Temporary workaround for writing on multiple threads.
|
|
// The following is called wide from the render thread to populate
|
|
// Niagara data from a cache without invoking a solver directly
|
|
//
|
|
// The above write lock guarantees we can safely write to these buffers
|
|
//
|
|
// TO BE REFACTORED
|
|
// #TODO BG
|
|
//////////////////////////////////////////////////////////////////////////
|
|
Chaos::FPBDRigidsSolver::FAllCollisionData *CollisionDataToWriteTo = const_cast<Chaos::FPBDRigidsSolver::FAllCollisionData*>(Solver->GetAllCollisions_FromSequencerCache_NEEDSLOCK());
|
|
Chaos::FPBDRigidsSolver::FAllBreakingData *BreakingDataToWriteTo = const_cast<Chaos::FPBDRigidsSolver::FAllBreakingData*>(Solver->GetAllBreakings_FromSequencerCache_NEEDSLOCK());
|
|
Chaos::FPBDRigidsSolver::FAllTrailingData *TrailingDataToWriteTo = const_cast<Chaos::FPBDRigidsSolver::FAllTrailingData*>(Solver->GetAllTrailings_FromSequencerCache_NEEDSLOCK());
|
|
|
|
if (!FMath::IsNearlyEqual(CollisionDataToWriteTo->TimeCreated, DesiredCacheTime))
|
|
{
|
|
CollisionDataToWriteTo->AllCollisionsArray.Empty();
|
|
CollisionDataToWriteTo->TimeCreated = DesiredCacheTime;
|
|
}
|
|
|
|
int32 Index = CacheParameters.TargetCache->GetData()->FindLastKeyBefore(CurrentCacheTime);
|
|
const FRecordedFrame *RecordedFrame = &CacheParameters.TargetCache->GetData()->Records[Index];
|
|
|
|
if (RecordedFrame && PhysicsObject && !EventsPlayed[Index])
|
|
{
|
|
EventsPlayed[Index] = true;
|
|
|
|
// Collisions
|
|
if (RecordedFrame->Collisions.Num() > 0)
|
|
{
|
|
for (int32 Idx = 0; Idx < RecordedFrame->Collisions.Num(); ++Idx)
|
|
{
|
|
// Check if the particle is still kinematic
|
|
int32 NewIdx = CollisionDataToWriteTo->AllCollisionsArray.Add(Chaos::TCollisionData<float, 3>());
|
|
Chaos::TCollisionData<float, 3>& AllCollisionsDataArrayItem = CollisionDataToWriteTo->AllCollisionsArray[NewIdx];
|
|
|
|
AllCollisionsDataArrayItem.Location = RecordedFrame->Collisions[Idx].Location;
|
|
AllCollisionsDataArrayItem.AccumulatedImpulse = RecordedFrame->Collisions[Idx].AccumulatedImpulse;
|
|
AllCollisionsDataArrayItem.Normal = RecordedFrame->Collisions[Idx].Normal;
|
|
AllCollisionsDataArrayItem.Velocity1 = RecordedFrame->Collisions[Idx].Velocity1;
|
|
AllCollisionsDataArrayItem.Velocity2 = RecordedFrame->Collisions[Idx].Velocity2;
|
|
AllCollisionsDataArrayItem.AngularVelocity1 = RecordedFrame->Collisions[Idx].AngularVelocity1;
|
|
AllCollisionsDataArrayItem.AngularVelocity2 = RecordedFrame->Collisions[Idx].AngularVelocity2;
|
|
AllCollisionsDataArrayItem.Mass1 = RecordedFrame->Collisions[Idx].Mass1;
|
|
AllCollisionsDataArrayItem.Mass2 = RecordedFrame->Collisions[Idx].Mass2;
|
|
AllCollisionsDataArrayItem.ParticleIndex = RecordedFrame->Collisions[Idx].ParticleIndex;
|
|
AllCollisionsDataArrayItem.LevelsetIndex = RecordedFrame->Collisions[Idx].LevelsetIndex;
|
|
AllCollisionsDataArrayItem.ParticleIndexMesh = RecordedFrame->Collisions[Idx].ParticleIndexMesh;
|
|
AllCollisionsDataArrayItem.LevelsetIndexMesh = RecordedFrame->Collisions[Idx].LevelsetIndexMesh;
|
|
}
|
|
}
|
|
|
|
// Breaking
|
|
if (RecordedFrame->Breakings.Num() > 0)
|
|
{
|
|
for (int32 Idx = 0; Idx < RecordedFrame->Breakings.Num(); ++Idx)
|
|
{
|
|
// Check if the particle is still kinematic
|
|
int32 NewIdx = BreakingDataToWriteTo->AllBreakingsArray.Add(Chaos::TBreakingData<float, 3>());
|
|
Chaos::TBreakingData<float, 3>& AllBreakingsDataArrayItem = BreakingDataToWriteTo->AllBreakingsArray[NewIdx];
|
|
|
|
AllBreakingsDataArrayItem.Location = RecordedFrame->Breakings[Idx].Location;
|
|
AllBreakingsDataArrayItem.Velocity = RecordedFrame->Breakings[Idx].Velocity;
|
|
AllBreakingsDataArrayItem.AngularVelocity = RecordedFrame->Breakings[Idx].AngularVelocity;
|
|
AllBreakingsDataArrayItem.Mass = RecordedFrame->Breakings[Idx].Mass;
|
|
AllBreakingsDataArrayItem.ParticleIndex = RecordedFrame->Breakings[Idx].ParticleIndex;
|
|
AllBreakingsDataArrayItem.ParticleIndexMesh = RecordedFrame->Breakings[Idx].ParticleIndexMesh;
|
|
}
|
|
}
|
|
|
|
// Trailing
|
|
if (RecordedFrame->Trailings.Num() > 0)
|
|
{
|
|
for (FSolverTrailingData Trailing : RecordedFrame->Trailings)
|
|
{
|
|
// Check if the particle is still kinematic
|
|
int32 NewIdx = TrailingDataToWriteTo->AllTrailingsArray.Add(Chaos::TTrailingData<float, 3>());
|
|
Chaos::TTrailingData<float, 3>& AllTrailingsDataArrayItem = TrailingDataToWriteTo->AllTrailingsArray[NewIdx];
|
|
|
|
AllTrailingsDataArrayItem.Location = Trailing.Location;
|
|
AllTrailingsDataArrayItem.Velocity = Trailing.Velocity;
|
|
AllTrailingsDataArrayItem.AngularVelocity = Trailing.AngularVelocity;
|
|
AllTrailingsDataArrayItem.Mass = Trailing.Mass;
|
|
AllTrailingsDataArrayItem.ParticleIndex = Trailing.ParticleIndex;
|
|
AllTrailingsDataArrayItem.ParticleIndexMesh = Trailing.ParticleIndexMesh;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// time is before current cache time so maintain the matrices we have since we can't rewind
|
|
DynamicData->PrevTransforms = GlobalMatrices;
|
|
DynamicData->Transforms = GlobalMatrices;
|
|
}
|
|
}
|
|
else if (DynamicData->IsDynamic)
|
|
{
|
|
// If we have no transforms stored in the dynamic data, then assign both prev and current to the same global matrices
|
|
if (GlobalMatrices.Num() == 0)
|
|
{
|
|
// Copy global matrices over to DynamicData
|
|
CalculateGlobalMatrices();
|
|
DynamicData->PrevTransforms = GlobalMatrices;
|
|
DynamicData->Transforms = GlobalMatrices;
|
|
}
|
|
else
|
|
{
|
|
// Copy existing global matrices into prev transforms
|
|
DynamicData->PrevTransforms = GlobalMatrices;
|
|
|
|
// Copy global matrices over to DynamicData
|
|
CalculateGlobalMatrices();
|
|
|
|
// if the number of matrices has changed between frames, then sync previous to current
|
|
if (GlobalMatrices.Num() != DynamicData->PrevTransforms.Num())
|
|
{
|
|
DynamicData->PrevTransforms = GlobalMatrices;
|
|
}
|
|
|
|
DynamicData->Transforms = GlobalMatrices;
|
|
}
|
|
}
|
|
|
|
// check if previous transforms are the same as current
|
|
TransformsAreEqual[(TransformsAreEqualIndex++) % TransformsAreEqual.Num()] = IsEqual(DynamicData->PrevTransforms, DynamicData->Transforms);
|
|
}
|
|
|
|
#if CHAOS_DEBUG_DRAW
|
|
void DebugDrawChaos(UWorld* World)
|
|
{
|
|
if (!World->IsGameWorld())
|
|
{
|
|
return;
|
|
}
|
|
|
|
using namespace Chaos;
|
|
TArray<FLatentDrawCommand> LatenetDrawCommands;
|
|
|
|
FDebugDrawQueue::GetInstance().ExtractAllElements(LatenetDrawCommands);
|
|
for (const FLatentDrawCommand& Command : LatenetDrawCommands)
|
|
{
|
|
switch (Command.Type)
|
|
{
|
|
case FLatentDrawCommand::EDrawType::Point:
|
|
{
|
|
DrawDebugPoint(World, Command.LineStart, Command.Thickness, Command.Color, Command.bPersistentLines, Command.LifeTime, Command.DepthPriority);
|
|
break;
|
|
}
|
|
case FLatentDrawCommand::EDrawType::Line:
|
|
{
|
|
DrawDebugLine(World, Command.LineStart, Command.LineEnd, Command.Color, Command.bPersistentLines, Command.LifeTime, Command.DepthPriority, Command.Thickness);
|
|
break;
|
|
}
|
|
case FLatentDrawCommand::EDrawType::DirectionalArrow:
|
|
{
|
|
DrawDebugDirectionalArrow(World, Command.LineStart, Command.LineEnd, Command.ArrowSize, Command.Color, Command.bPersistentLines, Command.LifeTime, Command.DepthPriority, Command.Thickness);
|
|
break;
|
|
}
|
|
case FLatentDrawCommand::EDrawType::Sphere:
|
|
{
|
|
DrawDebugSphere(World, Command.LineStart, Command.Radius, Command.Segments, Command.Color, Command.bPersistentLines, Command.LifeTime, Command.DepthPriority, Command.Thickness);
|
|
break;
|
|
}
|
|
case FLatentDrawCommand::EDrawType::Box:
|
|
{
|
|
DrawDebugBox(World, Command.Center, Command.Extent, Command.Rotation, Command.Color, Command.bPersistentLines, Command.LifeTime, Command.DepthPriority, Command.Thickness);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void UGeometryCollectionComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
|
|
{
|
|
//UE_LOG(UGCC_LOG, Log, TEXT("GeometryCollectionComponent[%p]::TickComponent()"), this);
|
|
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
|
//if (bRenderStateDirty && DynamicCollection) //todo: always send for now
|
|
if(RestCollection)
|
|
{
|
|
//if (RestCollection->HasVisibleGeometry() || DynamicCollection->IsDirty())
|
|
if(RestCollection->HasVisibleGeometry())
|
|
{
|
|
MarkRenderTransformDirty();
|
|
MarkRenderDynamicDataDirty();
|
|
bRenderStateDirty = false;
|
|
//DynamicCollection->MakeClean(); clean?
|
|
|
|
// const UWorld* MyWorld = GetWorld();
|
|
// if (MyWorld && MyWorld->IsGameWorld())
|
|
// {
|
|
// //cycle every 0xff frames
|
|
// //@todo - Need way of seeing if the collection is actually changing
|
|
// if (bNavigationRelevant && bRegistered && (((GFrameCounter + NavmeshInvalidationTimeSliceIndex) & 0xff) == 0))
|
|
// {
|
|
// UpdateNavigationData();
|
|
// }
|
|
// }
|
|
}
|
|
}
|
|
|
|
#if CHAOS_DEBUG_DRAW
|
|
using namespace Chaos;
|
|
|
|
// General debug drawing
|
|
if (FDebugDrawQueue::EnableDebugDrawing)
|
|
{
|
|
if (UWorld* World = GetWorld())
|
|
{
|
|
DebugDrawChaos(World);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void UGeometryCollectionComponent::OnRegister()
|
|
{
|
|
//UE_LOG(UGCC_LOG, Log, TEXT("GeometryCollectionComponent[%p]::OnRegister()[%p]"), this,RestCollection );
|
|
ResetDynamicCollection();
|
|
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 (!PhysicsObject)
|
|
{
|
|
#if WITH_EDITOR && WITH_EDITORONLY_DATA
|
|
EditorActor = nullptr;
|
|
|
|
if (RestCollection)
|
|
{
|
|
//hack: find a better place for this
|
|
UGeometryCollection* RestCollectionMutable = const_cast<UGeometryCollection*>(RestCollection);
|
|
RestCollectionMutable->EnsureDataIsCooked();
|
|
}
|
|
#endif
|
|
|
|
#if INCLUDE_CHAOS
|
|
const bool bValidWorld = GetWorld() && GetWorld()->IsGameWorld();
|
|
const bool bValidCollection = DynamicCollection && DynamicCollection->Transform.Num() > 0;
|
|
if (bValidWorld && bValidCollection)
|
|
{
|
|
if (PhysicalMaterial)
|
|
{
|
|
ChaosMaterial->Friction = PhysicalMaterial->Friction;
|
|
ChaosMaterial->Restitution = PhysicalMaterial->Restitution;
|
|
ChaosMaterial->SleepingLinearThreshold = PhysicalMaterial->SleepingLinearVelocityThreshold;
|
|
ChaosMaterial->SleepingAngularThreshold = PhysicalMaterial->SleepingAngularVelocityThreshold;
|
|
}
|
|
auto InitFunc = [this](FSimulationParameters& InParams)
|
|
{
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
InParams.Name = GetPathName();
|
|
#endif
|
|
InParams.RestCollection = RestCollection->GetGeometryCollection().Get();
|
|
GetInitializationCommands(InParams.InitializationCommands);
|
|
InParams.InitializationState = InitializationState;
|
|
InParams.Simulating = Simulating;
|
|
InParams.WorldTransform = GetComponentToWorld();
|
|
RestCollection->GetSharedSimulationParams(InParams.Shared);
|
|
InParams.EnableClustering = EnableClustering;
|
|
InParams.ClusterGroupIndex = EnableClustering?ClusterGroupIndex:0;
|
|
InParams.MaxClusterLevel = MaxClusterLevel;
|
|
InParams.DamageThreshold = DamageThreshold;
|
|
InParams.ClusterConnectionMethod = (Chaos::FClusterCreationParameters<float>::EConnectionMethod)ClusterConnectionType;
|
|
InParams.CollisionGroup = CollisionGroup;
|
|
InParams.CollisionSampleFraction = CollisionSampleFraction;
|
|
InParams.InitialVelocityType = InitialVelocityType;
|
|
InParams.InitialLinearVelocity = InitialLinearVelocity;
|
|
InParams.InitialAngularVelocity = InitialAngularVelocity;
|
|
InParams.bClearCache = true;
|
|
InParams.PhysicalMaterial = MakeSerializable(ChaosMaterial);
|
|
InParams.CacheType = CacheParameters.CacheMode;
|
|
InParams.ReverseCacheBeginTime = CacheParameters.ReverseCacheBeginTime;
|
|
InParams.CollisionData.SaveCollisionData = CacheParameters.SaveCollisionData;
|
|
InParams.CollisionData.DoGenerateCollisionData = CacheParameters.DoGenerateCollisionData;
|
|
InParams.CollisionData.CollisionDataSizeMax = CacheParameters.CollisionDataSizeMax;
|
|
InParams.CollisionData.DoCollisionDataSpatialHash = CacheParameters.DoCollisionDataSpatialHash;
|
|
InParams.CollisionData.CollisionDataSpatialHashRadius = CacheParameters.CollisionDataSpatialHashRadius;
|
|
InParams.CollisionData.MaxCollisionPerCell = CacheParameters.MaxCollisionPerCell;
|
|
InParams.BreakingData.SaveBreakingData = CacheParameters.SaveBreakingData;
|
|
InParams.BreakingData.DoGenerateBreakingData = CacheParameters.DoGenerateBreakingData;
|
|
InParams.BreakingData.BreakingDataSizeMax = CacheParameters.BreakingDataSizeMax;
|
|
InParams.BreakingData.DoBreakingDataSpatialHash = CacheParameters.DoBreakingDataSpatialHash;
|
|
InParams.BreakingData.BreakingDataSpatialHashRadius = CacheParameters.BreakingDataSpatialHashRadius;
|
|
InParams.BreakingData.MaxBreakingPerCell = CacheParameters.MaxBreakingPerCell;
|
|
InParams.TrailingData.SaveTrailingData = CacheParameters.SaveTrailingData;
|
|
InParams.TrailingData.DoGenerateTrailingData = CacheParameters.DoGenerateTrailingData;
|
|
InParams.TrailingData.TrailingDataSizeMax = CacheParameters.TrailingDataSizeMax;
|
|
InParams.TrailingData.TrailingMinSpeedThreshold = CacheParameters.TrailingMinSpeedThreshold;
|
|
InParams.TrailingData.TrailingMinVolumeThreshold = CacheParameters.TrailingMinVolumeThreshold;
|
|
InParams.RemoveOnFractureEnabled = InParams.Shared.RemoveOnFractureIndices.Num() > 0;
|
|
|
|
UGeometryCollectionCache* Cache = CacheParameters.TargetCache;
|
|
if(Cache && CacheParameters.CacheMode != EGeometryCollectionCacheType::None)
|
|
{
|
|
bool bCacheValid = false;
|
|
|
|
switch(CacheParameters.CacheMode)
|
|
{
|
|
case EGeometryCollectionCacheType::Record:
|
|
case EGeometryCollectionCacheType::RecordAndPlay:
|
|
bCacheValid = Cache->CompatibleWithForRecord(RestCollection);
|
|
break;
|
|
case EGeometryCollectionCacheType::Play:
|
|
bCacheValid = Cache->CompatibleWithForPlayback(RestCollection);
|
|
break;
|
|
default:
|
|
check(false);
|
|
break;
|
|
}
|
|
|
|
if(bCacheValid)
|
|
{
|
|
InParams.RecordedTrack = (InParams.IsCachePlaying() && CacheParameters.TargetCache) ? CacheParameters.TargetCache->GetData() : nullptr;
|
|
}
|
|
else
|
|
{
|
|
// We attempted to utilize a cache that was not compatible with this component. Report and do not use
|
|
UE_LOG(LogChaos, Error, TEXT("Geometry collection '%s' attempted to use cache '%s' but it is not compatible."), *GetName(), *(CacheParameters.TargetCache->GetName()));
|
|
InParams.RecordedTrack = nullptr;
|
|
InParams.CacheType = EGeometryCollectionCacheType::None;
|
|
CacheParameters.CacheMode = EGeometryCollectionCacheType::None;
|
|
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
FMessageLog("PIE").Error()
|
|
->AddToken(FTextToken::Create(NSLOCTEXT("GeomCollectionComponent", "BadCache_01", "Geometry collection")))
|
|
->AddToken(FUObjectToken::Create(this))
|
|
->AddToken(FTextToken::Create(NSLOCTEXT("GeomCollectionComponent", "BadCache_02", "attempted to use cache")))
|
|
->AddToken(FUObjectToken::Create(CacheParameters.TargetCache))
|
|
->AddToken(FTextToken::Create(NSLOCTEXT("GeomCollectionComponent", "BadCache_03", "but it is not compatible")));
|
|
#endif // !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
auto CacheSyncFunc = [this](const FGeometryCollectionResults& Results)
|
|
{
|
|
#if GEOMETRYCOLLECTION_DEBUG_DRAW
|
|
const bool bHasNumParticlesChanged = (NumParticlesAdded != Results.NumParticlesAdded); // Needs to be evaluated before NumParticlesAdded gets updated
|
|
#endif // #if GEOMETRYCOLLECTION_DEBUG_DRAW
|
|
RigidBodyIds.Init(Results.RigidBodyIds);
|
|
BaseRigidBodyIndex = Results.BaseIndex;
|
|
NumParticlesAdded = Results.NumParticlesAdded;
|
|
DisabledFlags = Results.DisabledStates;
|
|
|
|
if (!IsObjectDynamic && Results.IsObjectDynamic)
|
|
{
|
|
IsObjectDynamic = Results.IsObjectDynamic;
|
|
|
|
NotifyGeometryCollectionPhysicsStateChange.Broadcast(this);
|
|
SwitchRenderModels(GetOwner());
|
|
}
|
|
|
|
if (IsObjectLoading && !Results.IsObjectLoading)
|
|
{
|
|
IsObjectLoading = Results.IsObjectLoading;
|
|
|
|
NotifyGeometryCollectionPhysicsLoadingStateChange.Broadcast(this);
|
|
}
|
|
|
|
|
|
WorldBounds = Results.WorldBounds;
|
|
|
|
#if GEOMETRYCOLLECTION_DEBUG_DRAW
|
|
// Notify debug draw componentUGeometryCollectionDebugDrawComponent of particle changes
|
|
if (bHasNumParticlesChanged)
|
|
{
|
|
if (const AGeometryCollectionActor* const Owner = Cast<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
|
|
TSharedPtr<FPhysScene_Chaos> Scene = GetPhysicsScene();
|
|
|
|
CacheParameters.TargetCache->PreEditChange(nullptr);
|
|
CacheParameters.TargetCache->Modify();
|
|
CacheParameters.TargetCache->SetFromRawTrack(InTrack);
|
|
CacheParameters.TargetCache->PostEditChange();
|
|
|
|
Scene->AddPieModifiedObject(CacheParameters.TargetCache);
|
|
|
|
if (EditorActor)
|
|
{
|
|
UGeometryCollectionComponent* EditorComponent = Cast<UGeometryCollectionComponent>(EditorUtilities::FindMatchingComponentInstance(this, EditorActor));
|
|
|
|
if (EditorComponent)
|
|
{
|
|
EditorComponent->PreEditChange(FindField<UProperty>(EditorComponent->GetClass(), GET_MEMBER_NAME_CHECKED(UGeometryCollectionComponent, CacheParameters)));
|
|
EditorComponent->Modify();
|
|
|
|
EditorComponent->CacheParameters.TargetCache = CacheParameters.TargetCache;
|
|
|
|
EditorComponent->PostEditChange();
|
|
|
|
Scene->AddPieModifiedObject(EditorComponent);
|
|
Scene->AddPieModifiedObject(EditorActor);
|
|
}
|
|
|
|
EditorActor = nullptr;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
};
|
|
|
|
// @todo(temporary) : This is Temporary code for the collection to match the ObjectType
|
|
// attribute on initialization. Once proper per-object manipulation is
|
|
// in place this code will need to be removed.
|
|
//
|
|
TManagedArray<int32> & DynamicState = DynamicCollection->DynamicState;
|
|
if (ObjectType != EObjectStateTypeEnum::Chaos_Object_UserDefined)
|
|
{
|
|
for (int i = 0; i < DynamicState.Num(); i++)
|
|
{
|
|
DynamicState[i] = (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;
|
|
}
|
|
}
|
|
// end temporary
|
|
|
|
PhysicsObject = new FGeometryCollectionPhysicsObject(this, DynamicCollection.Get(), InitFunc, CacheSyncFunc, FinalSyncFunc);
|
|
TSharedPtr<FPhysScene_Chaos> Scene = GetPhysicsScene();
|
|
Scene->AddObject(this, PhysicsObject);
|
|
|
|
RegisterForEvents();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if INCLUDE_CHAOS
|
|
#if WITH_PHYSX && !WITH_CHAOS_NEEDS_TO_BE_FIXED
|
|
if (PhysicsObject)
|
|
{
|
|
GlobalGeomCollectionAccelerator.AddComponent(this);
|
|
}
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
void UGeometryCollectionComponent::OnDestroyPhysicsState()
|
|
{
|
|
UActorComponent::OnDestroyPhysicsState();
|
|
|
|
#if INCLUDE_CHAOS
|
|
#if WITH_PHYSX && !WITH_CHAOS_NEEDS_TO_BE_FIXED
|
|
GlobalGeomCollectionAccelerator.RemoveComponent(this);
|
|
#endif
|
|
#endif
|
|
|
|
#if WITH_PHYSX
|
|
if(DummyBodyInstance.IsValidBodyInstance())
|
|
{
|
|
DummyBodyInstance.TermBody();
|
|
}
|
|
#endif
|
|
|
|
#if INCLUDE_CHAOS
|
|
if(PhysicsObject)
|
|
{
|
|
TSharedPtr<FPhysScene_Chaos> Scene = GetPhysicsScene();
|
|
Scene->RemoveObject(PhysicsObject);
|
|
InitializationState = ESimulationInitializationState::Unintialized;
|
|
|
|
// Discard the pointer (cleanup happens through the scene or dedicated thread)
|
|
PhysicsObject = nullptr;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void UGeometryCollectionComponent::SendRenderDynamicData_Concurrent()
|
|
{
|
|
//UE_LOG(UGCC_LOG, Log, TEXT("GeometryCollectionComponent[%p]::SendRenderDynamicData_Concurrent()"), this);
|
|
Super::SendRenderDynamicData_Concurrent();
|
|
|
|
// Only update the dynamic data if the dynamic collection is dirty
|
|
if (SceneProxy && ((DynamicCollection && DynamicCollection->IsDirty()) || CachePlayback))
|
|
{
|
|
FGeometryCollectionDynamicData * DynamicData = ::new FGeometryCollectionDynamicData;
|
|
InitDynamicData(DynamicData);
|
|
|
|
//
|
|
// Only send dynamic data if the transform arrys on the past N frames are different
|
|
//
|
|
bool PastFramesTransformsAreEqual = true;
|
|
for (int i = 0; i < TransformsAreEqual.Num(); ++i)
|
|
{
|
|
PastFramesTransformsAreEqual = PastFramesTransformsAreEqual && TransformsAreEqual[i];
|
|
}
|
|
|
|
if (PastFramesTransformsAreEqual)
|
|
{
|
|
delete DynamicData;
|
|
}
|
|
else
|
|
{
|
|
// Enqueue command to send to render thread
|
|
FGeometryCollectionSceneProxy* GeometryCollectionSceneProxy = static_cast<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();
|
|
|
|
//ResetDynamicCollection();
|
|
}
|
|
}
|
|
|
|
FGeometryCollectionEdit::FGeometryCollectionEdit(UGeometryCollectionComponent* InComponent, bool InUpdate /*= true*/)
|
|
: Component(InComponent)
|
|
, bUpdate(InUpdate)
|
|
{
|
|
bHadPhysicsState = Component->HasValidPhysicsState();
|
|
if(bUpdate && bHadPhysicsState)
|
|
{
|
|
Component->DestroyPhysicsState();
|
|
}
|
|
}
|
|
|
|
FGeometryCollectionEdit::~FGeometryCollectionEdit()
|
|
{
|
|
#if WITH_EDITOR
|
|
if (bUpdate)
|
|
{
|
|
//Component->ResetDynamicCollection();
|
|
if (GetRestCollection())
|
|
{
|
|
GetRestCollection()->Modify();
|
|
}
|
|
|
|
if(bHadPhysicsState)
|
|
{
|
|
Component->RecreatePhysicsState();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
UGeometryCollection* FGeometryCollectionEdit::GetRestCollection()
|
|
{
|
|
if (Component)
|
|
{
|
|
return const_cast<UGeometryCollection*>(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)
|
|
{
|
|
Component->Modify();
|
|
bUpdated = true;
|
|
Component->SelectedBones = SelectedBonesIn;
|
|
}
|
|
|
|
void FScopedColorEdit::AppendSelectedBones(const TArray<int32>& SelectedBonesIn)
|
|
{
|
|
Component->Modify();
|
|
bUpdated = true;
|
|
Component->SelectedBones.Append(SelectedBonesIn);
|
|
}
|
|
|
|
void FScopedColorEdit::ToggleSelectedBones(const TArray<int32>& SelectedBonesIn)
|
|
{
|
|
Component->Modify();
|
|
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))
|
|
{
|
|
Component->Modify();
|
|
bUpdated = true;
|
|
Component->SelectedBones.Push(BoneIndex);
|
|
}
|
|
}
|
|
|
|
void FScopedColorEdit::ClearSelectedBone(int32 BoneIndex)
|
|
{
|
|
if (Component->SelectedBones.Contains(BoneIndex))
|
|
{
|
|
Component->Modify();
|
|
bUpdated = true;
|
|
Component->SelectedBones.Remove(BoneIndex);
|
|
}
|
|
}
|
|
|
|
const TArray<int32>& FScopedColorEdit::GetSelectedBones() const
|
|
{
|
|
return Component->GetSelectedBones();
|
|
}
|
|
|
|
void FScopedColorEdit::ResetBoneSelection()
|
|
{
|
|
if (Component->SelectedBones.Num() > 0)
|
|
{
|
|
Component->Modify();
|
|
bUpdated = true;
|
|
}
|
|
|
|
Component->SelectedBones.Empty();
|
|
}
|
|
|
|
void FScopedColorEdit::SelectBones(GeometryCollection::ESelectionMode SelectionMode)
|
|
{
|
|
check(Component);
|
|
|
|
Component->Modify();
|
|
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, 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, 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, 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();
|
|
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 (int BoneIndex = 0; BoneIndex < Parents.Num(); BoneIndex++)
|
|
{
|
|
FLinearColor BoneColor = FLinearColor(FColor::Black);
|
|
|
|
if (Component->ViewLevel == -1)
|
|
{
|
|
BoneColor = RandomColors[BoneIndex % RandomColors.Num()];
|
|
}
|
|
else
|
|
{
|
|
if (HasLevelAttribute && (*Levels)[BoneIndex] >= Component->ViewLevel)
|
|
{
|
|
// go up until we find parent at the required ViewLevel
|
|
int32 Bone = BoneIndex;
|
|
while (Bone != -1 && (*Levels)[Bone] > Component->ViewLevel)
|
|
{
|
|
Bone = Parents[Bone];
|
|
}
|
|
|
|
int32 ColorIndex = Bone + 1; // parent can be -1 for root, range [-1..n]
|
|
BoneColor = RandomColors[ColorIndex % RandomColors.Num()];
|
|
|
|
BoneColor.LinearRGBToHSV();
|
|
BoneColor.B *= .5;
|
|
BoneColor.HSVToLinearRGB();
|
|
}
|
|
else
|
|
{
|
|
BoneColor = BlankColor;
|
|
}
|
|
}
|
|
|
|
// store the bone selected toggle in alpha so we can use it in the shader
|
|
BoneColor.A = IsBoneHighlighted(BoneIndex) ? 1 : 0;
|
|
|
|
BoneColors[BoneIndex] = BoneColor;
|
|
}
|
|
|
|
Component->MarkRenderStateDirty();
|
|
Component->MarkRenderDynamicDataDirty();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void UGeometryCollectionComponent::ApplyKinematicField(float Radius, FVector Position)
|
|
{
|
|
FName TargetName = GetGeometryCollectionPhysicsTypeName(EGeometryCollectionPhysicsTypeEnum::Chaos_DynamicState);
|
|
DispatchCommand({ TargetName,new FRadialIntMask(Radius, Position, (int32)EObjectStateTypeEnum::Chaos_Object_Dynamic,
|
|
(int32)EObjectStateTypeEnum::Chaos_Object_Kinematic, ESetMaskConditionType::Field_Set_IFF_NOT_Interior) });
|
|
}
|
|
|
|
void UGeometryCollectionComponent::ApplyPhysicsField(bool Enabled, EGeometryCollectionPhysicsTypeEnum Target, UFieldSystemMetaData* MetaData, UFieldNodeBase* Field)
|
|
{
|
|
if (Enabled && Field)
|
|
{
|
|
TArray<const UFieldNodeBase*> Nodes;
|
|
FFieldSystemCommand Command = { GetGeometryCollectionPhysicsTypeName(Target), Field->NewEvaluationGraph(Nodes) };
|
|
if (ensureMsgf(Command.RootNode,
|
|
TEXT("Failed to generate physics field command for target attribute.")))
|
|
{
|
|
if (MetaData)
|
|
{
|
|
switch (MetaData->Type())
|
|
{
|
|
case FFieldSystemMetaData::EMetaType::ECommandData_ProcessingResolution:
|
|
UFieldSystemMetaDataProcessingResolution * ResolutionMeta = static_cast<UFieldSystemMetaDataProcessingResolution*>(MetaData);
|
|
Command.MetaData.Add(FFieldSystemMetaData::EMetaType::ECommandData_ProcessingResolution).Reset(new FFieldSystemMetaDataProcessingResolution(ResolutionMeta->ResolutionType));
|
|
break;
|
|
}
|
|
}
|
|
ensure(!Command.TargetAttribute.IsEqual("None"));
|
|
DispatchCommand(Command);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UGeometryCollectionComponent::DispatchCommand(const FFieldSystemCommand& InCommand)
|
|
{
|
|
#if INCLUDE_CHAOS
|
|
if (PhysicsObject)
|
|
{
|
|
FChaosSolversModule* ChaosModule = FModuleManager::Get().GetModulePtr<FChaosSolversModule>("ChaosSolvers");
|
|
checkSlow(ChaosModule);
|
|
|
|
Chaos::IDispatcher* PhysicsDispatcher = ChaosModule->GetDispatcher();
|
|
checkSlow(PhysicsDispatcher); // Should always have one of these
|
|
|
|
PhysicsDispatcher->EnqueueCommand([PhysicsObject = this->PhysicsObject, NewCommand = InCommand]()
|
|
{
|
|
// Pass through nullptr here as geom component commands can never affect other solvers
|
|
PhysicsObject->BufferCommand(nullptr, NewCommand);
|
|
});
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void UGeometryCollectionComponent::GetInitializationCommands(TArray<FFieldSystemCommand>& CombinedCommmands)
|
|
{
|
|
#if INCLUDE_CHAOS
|
|
CombinedCommmands.Reset();
|
|
for (const AFieldSystemActor * FieldSystemActor : InitializationFields)
|
|
{
|
|
if (FieldSystemActor != nullptr)
|
|
{
|
|
if (FieldSystemActor->GetFieldSystemComponent() && FieldSystemActor->GetFieldSystemComponent()->GetFieldSystem())
|
|
{
|
|
for (const FFieldSystemCommand& Command : FieldSystemActor->GetFieldSystemComponent()->GetFieldSystem()->Commands)
|
|
{
|
|
FFieldSystemCommand NewCommand = { Command.TargetAttribute, Command.RootNode->NewCopy() };
|
|
for (auto & Elem : Command.MetaData)
|
|
{
|
|
NewCommand.MetaData.Add(Elem.Key, TUniquePtr<FFieldSystemMetaData>(Elem.Value->NewCopy()));
|
|
}
|
|
CombinedCommmands.Add(NewCommand);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
#if INCLUDE_CHAOS
|
|
const TSharedPtr<FPhysScene_Chaos> UGeometryCollectionComponent::GetPhysicsScene() const
|
|
{
|
|
if (ChaosSolverActor)
|
|
{
|
|
return ChaosSolverActor->GetPhysicsScene();
|
|
}
|
|
else
|
|
{
|
|
if (ensure(GetOwner()) && ensure(GetOwner()->GetWorld()))
|
|
{
|
|
return GetOwner()->GetWorld()->PhysicsScene_Chaos;
|
|
}
|
|
check(GWorld);
|
|
return GWorld->PhysicsScene_Chaos;
|
|
}
|
|
}
|
|
|
|
AChaosSolverActor* UGeometryCollectionComponent::GetPhysicsSolverActor() const
|
|
{
|
|
if (ChaosSolverActor)
|
|
{
|
|
return ChaosSolverActor;
|
|
}
|
|
else
|
|
{
|
|
FPhysScene_Chaos const* const Scene = GetPhysicsScene().Get();
|
|
return Scene ? Cast<AChaosSolverActor>(Scene->GetSolverActor()) : nullptr;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
#endif
|
|
|
|
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);
|
|
#if INCLUDE_CHAOS
|
|
FChaosSolversModule* Module = FChaosSolversModule::GetModule();
|
|
Module->LockResultsRead();
|
|
|
|
const FGeometryCollectionResults* Results = PhysicsObject ? &PhysicsObject->GetPhysicsResults().GetGameDataForRead() : nullptr;
|
|
|
|
const int32 NumTransforms = Results ? Results->GlobalTransforms.Num() : 0;
|
|
if(NumTransforms > 0)
|
|
{
|
|
// Just calc from results
|
|
GlobalMatrices.Empty();
|
|
GlobalMatrices.Append(Results->GlobalTransforms);
|
|
}
|
|
else
|
|
{
|
|
// Have to fully rebuild
|
|
GeometryCollectionAlgo::GlobalMatrices(GetTransformArray(), GetParentArray(), GlobalMatrices);
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (GlobalMatrices.Num() > 0)
|
|
{
|
|
if (RestCollection->GetGeometryCollection()->HasAttribute("ExplodedVector", FGeometryCollection::TransformGroup))
|
|
{
|
|
const TManagedArray<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
|
|
|
|
Module->UnlockResultsRead();
|
|
#endif
|
|
}
|
|
|
|
// #todo(dmp): for backwards compatibility with existing maps, we need to have a default of 3 materials. Otherwise
|
|
// some existing test scenes will crash
|
|
int32 UGeometryCollectionComponent::GetNumMaterials() const
|
|
{
|
|
return !RestCollection || RestCollection->Materials.Num() == 0 ? 3 : RestCollection->Materials.Num();
|
|
}
|
|
|
|
UMaterialInterface* UGeometryCollectionComponent::GetMaterial(int32 MaterialIndex) const
|
|
{
|
|
// If we have a base materials array, use that
|
|
if (OverrideMaterials.IsValidIndex(MaterialIndex) && OverrideMaterials[MaterialIndex])
|
|
{
|
|
return OverrideMaterials[MaterialIndex];
|
|
}
|
|
// Otherwise get from geom collection
|
|
else
|
|
{
|
|
return RestCollection && RestCollection->Materials.IsValidIndex(MaterialIndex) ? RestCollection->Materials[MaterialIndex] : nullptr;
|
|
}
|
|
}
|
|
|
|
// #temp HACK for demo, When fracture happens (physics state changes to dynamic) then switch the visible render meshes in a blueprint/actor from static meshes to geometry collections
|
|
void UGeometryCollectionComponent::SwitchRenderModels(const AActor* Actor)
|
|
{
|
|
TArray<UActorComponent*> PrimitiveComponents = Actor->GetComponentsByClass(UPrimitiveComponent::StaticClass());
|
|
for (UActorComponent* PrimitiveComponent : PrimitiveComponents)
|
|
{
|
|
bool ValidComponent = false;
|
|
|
|
if (UStaticMeshComponent* StaticMeshComp = Cast<UStaticMeshComponent>(PrimitiveComponent))
|
|
{
|
|
StaticMeshComp->SetVisibility(false);
|
|
}
|
|
else if (UGeometryCollectionComponent* GeometryCollectionComponent = Cast<UGeometryCollectionComponent>(PrimitiveComponent))
|
|
{
|
|
GeometryCollectionComponent->SetVisibility(true);
|
|
}
|
|
}
|
|
|
|
TArray<UChildActorComponent*> ChildActorComponents;
|
|
Actor->GetComponents<UChildActorComponent>(ChildActorComponents);
|
|
for (UChildActorComponent* ChildComponent : ChildActorComponents)
|
|
{
|
|
AActor* ChildActor = ChildComponent->GetChildActor();
|
|
if (ChildActor)
|
|
{
|
|
SwitchRenderModels(ChildActor);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
bool UGeometryCollectionComponent::IsEqual(const TArray<FMatrix> &A, const TArray<FMatrix> &B, const float Tolerance)
|
|
{
|
|
if (A.Num() != B.Num())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (int Index = 0; Index < A.Num(); ++Index)
|
|
{
|
|
if (!A[Index].Equals(B[Index], Tolerance))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#if GEOMETRYCOLLECTION_EDITOR_SELECTION
|
|
void UGeometryCollectionComponent::EnableTransformSelectionMode(bool bEnable)
|
|
{
|
|
if (SceneProxy && RestCollection && RestCollection->HasVisibleGeometry())
|
|
{
|
|
static_cast<FGeometryCollectionSceneProxy*>(SceneProxy)->UseSubSections(bEnable, true);
|
|
}
|
|
bIsTransformSelectionModeEnabled = bEnable;
|
|
}
|
|
#endif // #if GEOMETRYCOLLECTION_EDITOR_SELECTION
|