Files
UnrealEngineUWP/Engine/Source/Runtime/GeometryFramework/Private/Components/DynamicMeshComponent.cpp
aurel cordonnier a12d56ff31 Merge from Release-Engine-Staging @ 17791557 to Release-Engine-Test
This represents UE4/Main @17774255, Release-5.0 @17791557 and Dev-PerfTest @17789485

[CL 17794212 by aurel cordonnier in ue5-release-engine-test branch]
2021-10-12 21:21:22 -04:00

1202 lines
32 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Components/DynamicMeshComponent.h"
#include "PrimitiveSceneProxy.h"
#include "MaterialShared.h"
#include "Engine/CollisionProfile.h"
#include "Materials/Material.h"
#include "Async/Async.h"
#include "Engine/CollisionProfile.h"
#include "DynamicMesh/DynamicMeshAttributeSet.h"
#include "DynamicMesh/MeshNormals.h"
#include "MeshDescriptionToDynamicMesh.h"
#include "Changes/MeshVertexChange.h"
#include "Changes/MeshChange.h"
#include "DynamicMesh/MeshTransforms.h"
// default proxy for this component
#include "Components/DynamicMeshSceneProxy.h"
#include "ExplicitUseGeometryMathTypes.h" // using UE::Geometry::(math types)
using namespace UE::Geometry;
namespace
{
// probably should be something defined for the whole tool framework...
#if WITH_EDITOR
static EAsyncExecution DynamicMeshComponentAsyncExecTarget = EAsyncExecution::LargeThreadPool;
#else
static EAsyncExecution DynamicMeshComponentAsyncExecTarget = EAsyncExecution::ThreadPool;
#endif
}
UDynamicMeshComponent::UDynamicMeshComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
PrimaryComponentTick.bCanEverTick = false;
SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
MeshObject = CreateDefaultSubobject<UDynamicMesh>(TEXT("DynamicMesh"));
//MeshObject->SetFlags(RF_Transactional);
MeshObjectChangedHandle = MeshObject->OnMeshChanged().AddUObject(this, &UDynamicMeshComponent::OnMeshObjectChanged);
ResetProxy();
}
void UDynamicMeshComponent::PostLoad()
{
Super::PostLoad();
check(MeshObject != nullptr);
MeshObjectChangedHandle = MeshObject->OnMeshChanged().AddUObject(this, &UDynamicMeshComponent::OnMeshObjectChanged);
ResetProxy();
// This is a fixup for existing UDynamicMeshComponents that did not have the correct flags
// on the Instanced UBodySetup, these flags are now set in GetBodySetup() on new instances
if (MeshBodySetup && IsTemplate())
{
MeshBodySetup->SetFlags(RF_Public | RF_ArchetypeObject);
}
// make sure BodySetup is created
GetBodySetup();
}
#if WITH_EDITOR
void UDynamicMeshComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
const FName PropName = PropertyChangedEvent.GetPropertyName();
if ( (PropName == GET_MEMBER_NAME_CHECKED(UDynamicMeshComponent, bEnableComplexCollision)) ||
(PropName == GET_MEMBER_NAME_CHECKED(UDynamicMeshComponent, CollisionType)) ||
(PropName == GET_MEMBER_NAME_CHECKED(UDynamicMeshComponent, bDeferCollisionUpdates)) )
{
if (bDeferCollisionUpdates)
{
InvalidatePhysicsData();
}
else
{
RebuildPhysicsData();
}
}
}
#endif
void UDynamicMeshComponent::SetMesh(UE::Geometry::FDynamicMesh3&& MoveMesh)
{
MeshObject->SetMesh(MoveTemp(MoveMesh));
}
void UDynamicMeshComponent::ProcessMesh(
TFunctionRef<void(const UE::Geometry::FDynamicMesh3&)> ProcessFunc ) const
{
MeshObject->ProcessMesh(ProcessFunc);
}
void UDynamicMeshComponent::EditMesh(TFunctionRef<void(UE::Geometry::FDynamicMesh3&)> EditFunc,
EDynamicMeshComponentRenderUpdateMode UpdateMode )
{
MeshObject->EditMesh(EditFunc);
if (UpdateMode != EDynamicMeshComponentRenderUpdateMode::NoUpdate)
{
NotifyMeshUpdated();
}
}
void UDynamicMeshComponent::SetRenderMeshPostProcessor(TUniquePtr<IRenderMeshPostProcessor> Processor)
{
RenderMeshPostProcessor = MoveTemp(Processor);
if (RenderMeshPostProcessor)
{
if (!RenderMesh)
{
RenderMesh = MakeUnique<FDynamicMesh3>(*GetMesh());
}
}
else
{
// No post processor, no render mesh
RenderMesh = nullptr;
}
}
FDynamicMesh3* UDynamicMeshComponent::GetRenderMesh()
{
if (RenderMeshPostProcessor && RenderMesh)
{
return RenderMesh.Get();
}
else
{
return GetMesh();
}
}
const FDynamicMesh3* UDynamicMeshComponent::GetRenderMesh() const
{
if (RenderMeshPostProcessor && RenderMesh)
{
return RenderMesh.Get();
}
else
{
return GetMesh();
}
}
void UDynamicMeshComponent::ApplyTransform(const UE::Geometry::FTransform3d& Transform, bool bInvert)
{
MeshObject->EditMesh([&](FDynamicMesh3& EditMesh)
{
if (bInvert)
{
MeshTransforms::ApplyTransformInverse(EditMesh, Transform);
}
else
{
MeshTransforms::ApplyTransform(EditMesh, Transform);
}
}, EDynamicMeshChangeType::DeformationEdit);
}
bool UDynamicMeshComponent::ValidateMaterialSlots(bool bCreateIfMissing, bool bDeleteExtraSlots)
{
int32 MaxMeshMaterialID = 0;
MeshObject->ProcessMesh([&](const FDynamicMesh3& EditMesh)
{
if (EditMesh.HasAttributes() && EditMesh.Attributes()->HasMaterialID() && EditMesh.Attributes()->GetMaterialID() != nullptr)
{
const FDynamicMeshMaterialAttribute* MaterialIDs = EditMesh.Attributes()->GetMaterialID();
for (int TriangleID : EditMesh.TriangleIndicesItr())
{
MaxMeshMaterialID = FMath::Max(MaxMeshMaterialID, MaterialIDs->GetValue(TriangleID));
}
}
});
int32 NumRequiredMaterials = MaxMeshMaterialID + 1;
int32 NumMaterials = GetNumMaterials();
if ( bCreateIfMissing && NumMaterials < NumRequiredMaterials )
{
for (int32 k = NumMaterials; k < NumRequiredMaterials; ++k)
{
SetMaterial(k, nullptr);
}
}
NumMaterials = GetNumMaterials();
if (bDeleteExtraSlots && NumMaterials > NumRequiredMaterials)
{
SetNumMaterials(NumRequiredMaterials);
}
NumMaterials = GetNumMaterials();
return (NumMaterials == NumRequiredMaterials);
}
void UDynamicMeshComponent::ConfigureMaterialSet(const TArray<UMaterialInterface*>& NewMaterialSet)
{
for (int k = 0; k < NewMaterialSet.Num(); ++k)
{
SetMaterial(k, NewMaterialSet[k]);
}
}
void UDynamicMeshComponent::SetTangentsType(EDynamicMeshComponentTangentsMode NewTangentsType)
{
if (NewTangentsType != TangentsType)
{
TangentsType = NewTangentsType;
InvalidateAutoCalculatedTangents();
}
}
void UDynamicMeshComponent::InvalidateAutoCalculatedTangents()
{
bAutoCalculatedTangentsValid = false;
}
const UE::Geometry::FMeshTangentsf* UDynamicMeshComponent::GetAutoCalculatedTangents()
{
if (TangentsType == EDynamicMeshComponentTangentsMode::AutoCalculated && GetDynamicMesh()->GetMeshRef().HasAttributes())
{
UpdateAutoCalculatedTangents();
return (bAutoCalculatedTangentsValid) ? &AutoCalculatedTangents : nullptr;
}
return nullptr;
}
void UDynamicMeshComponent::UpdateAutoCalculatedTangents()
{
if (TangentsType == EDynamicMeshComponentTangentsMode::AutoCalculated && bAutoCalculatedTangentsValid == false)
{
GetDynamicMesh()->ProcessMesh([&](const FDynamicMesh3& Mesh)
{
if (Mesh.HasAttributes())
{
const FDynamicMeshUVOverlay* UVOverlay = Mesh.Attributes()->PrimaryUV();
const FDynamicMeshNormalOverlay* NormalOverlay = Mesh.Attributes()->PrimaryNormals();
if (UVOverlay && NormalOverlay)
{
AutoCalculatedTangents.SetMesh(&Mesh);
AutoCalculatedTangents.ComputeTriVertexTangents(NormalOverlay, UVOverlay, FComputeTangentsOptions());
AutoCalculatedTangents.SetMesh(nullptr);
}
}
});
bAutoCalculatedTangentsValid = true;
}
}
void UDynamicMeshComponent::UpdateLocalBounds()
{
LocalBounds = GetMesh()->GetBounds(true);
if (LocalBounds.MaxDim() <= 0)
{
// If bbox is empty, set a very small bbox to avoid log spam/etc in other engine systems.
// The check used is generally IsNearlyZero(), which defaults to KINDA_SMALL_NUMBER, so set
// a slightly larger box here to be above that threshold
LocalBounds = FAxisAlignedBox3d(FVector3d::Zero(), (double)(KINDA_SMALL_NUMBER + SMALL_NUMBER) );
}
}
FDynamicMeshSceneProxy* UDynamicMeshComponent::GetCurrentSceneProxy()
{
if (bProxyValid)
{
return (FDynamicMeshSceneProxy*)SceneProxy;
}
return nullptr;
}
void UDynamicMeshComponent::ResetProxy()
{
bProxyValid = false;
InvalidateAutoCalculatedTangents();
// Need to recreate scene proxy to send it over
MarkRenderStateDirty();
UpdateLocalBounds();
UpdateBounds();
// this seems speculative, ie we may not actually have a mesh update, but we currently ResetProxy() in lots
// of places where that is what it means
GetDynamicMesh()->PostRealtimeUpdate();
}
void UDynamicMeshComponent::NotifyMeshUpdated()
{
if (RenderMeshPostProcessor)
{
RenderMeshPostProcessor->ProcessMesh(*GetMesh(), *RenderMesh);
}
ResetProxy();
}
void UDynamicMeshComponent::FastNotifyColorsUpdated()
{
// should not be using fast paths if we have to run mesh postprocessor
if (ensure(!RenderMeshPostProcessor) == false)
{
RenderMeshPostProcessor->ProcessMesh(*GetMesh(), *RenderMesh);
ResetProxy();
return;
}
FDynamicMeshSceneProxy* Proxy = GetCurrentSceneProxy();
if (Proxy)
{
if (HasTriangleColorFunction() && Proxy->bUsePerTriangleColor == false )
{
Proxy->bUsePerTriangleColor = true;
Proxy->PerTriangleColorFunc = [this](const FDynamicMesh3* MeshIn, int TriangleID) { return GetTriangleColor(MeshIn, TriangleID); };
}
else if ( !HasTriangleColorFunction() && Proxy->bUsePerTriangleColor == true)
{
Proxy->bUsePerTriangleColor = false;
Proxy->PerTriangleColorFunc = nullptr;
}
Proxy->FastUpdateVertices(false, false, true, false);
//MarkRenderDynamicDataDirty();
}
else
{
ResetProxy();
}
}
void UDynamicMeshComponent::FastNotifyPositionsUpdated(bool bNormals, bool bColors, bool bUVs)
{
// should not be using fast paths if we have to run mesh postprocessor
if (ensure(!RenderMeshPostProcessor) == false)
{
RenderMeshPostProcessor->ProcessMesh(*GetMesh(), *RenderMesh);
ResetProxy();
return;
}
FDynamicMeshSceneProxy* Proxy = GetCurrentSceneProxy();
if (Proxy)
{
// calculate bounds while we are updating vertices
TFuture<void> UpdateBoundsCalc;
UpdateBoundsCalc = Async(DynamicMeshComponentAsyncExecTarget, [this]()
{
TRACE_CPUPROFILER_EVENT_SCOPE(SimpleDynamicMeshComponent_FastPositionsUpdate_AsyncBoundsUpdate);
UpdateLocalBounds();
});
GetCurrentSceneProxy()->FastUpdateVertices(true, bNormals, bColors, bUVs);
//MarkRenderDynamicDataDirty();
MarkRenderTransformDirty();
UpdateBoundsCalc.Wait();
UpdateBounds();
GetDynamicMesh()->PostRealtimeUpdate();
}
else
{
ResetProxy();
}
}
void UDynamicMeshComponent::FastNotifyVertexAttributesUpdated(bool bNormals, bool bColors, bool bUVs)
{
// should not be using fast paths if we have to run mesh postprocessor
if (ensure(!RenderMeshPostProcessor) == false)
{
RenderMeshPostProcessor->ProcessMesh(*GetMesh(), *RenderMesh);
ResetProxy();
return;
}
FDynamicMeshSceneProxy* Proxy = GetCurrentSceneProxy();
if (Proxy && ensure(bNormals || bColors || bUVs) )
{
GetCurrentSceneProxy()->FastUpdateVertices(false, bNormals, bColors, bUVs);
//MarkRenderDynamicDataDirty();
//MarkRenderTransformDirty();
GetDynamicMesh()->PostRealtimeUpdate();
}
else
{
ResetProxy();
}
}
void UDynamicMeshComponent::FastNotifyVertexAttributesUpdated(EMeshRenderAttributeFlags UpdatedAttributes)
{
// should not be using fast paths if we have to run mesh postprocessor
if (ensure(!RenderMeshPostProcessor) == false)
{
RenderMeshPostProcessor->ProcessMesh(*GetMesh(), *RenderMesh);
ResetProxy();
return;
}
FDynamicMeshSceneProxy* Proxy = GetCurrentSceneProxy();
if (Proxy && ensure(UpdatedAttributes != EMeshRenderAttributeFlags::None))
{
bool bPositions = (UpdatedAttributes & EMeshRenderAttributeFlags::Positions) != EMeshRenderAttributeFlags::None;
// calculate bounds while we are updating vertices
TFuture<void> UpdateBoundsCalc;
if (bPositions)
{
UpdateBoundsCalc = Async(DynamicMeshComponentAsyncExecTarget, [this]()
{
TRACE_CPUPROFILER_EVENT_SCOPE(SimpleDynamicMeshComponent_FastVertexAttribUpdate_AsyncBoundsUpdate);
UpdateLocalBounds();
});
}
GetCurrentSceneProxy()->FastUpdateVertices(bPositions,
(UpdatedAttributes & EMeshRenderAttributeFlags::VertexNormals) != EMeshRenderAttributeFlags::None,
(UpdatedAttributes & EMeshRenderAttributeFlags::VertexColors) != EMeshRenderAttributeFlags::None,
(UpdatedAttributes & EMeshRenderAttributeFlags::VertexUVs) != EMeshRenderAttributeFlags::None);
if (bPositions)
{
MarkRenderTransformDirty();
UpdateBoundsCalc.Wait();
UpdateBounds();
}
GetDynamicMesh()->PostRealtimeUpdate();
}
else
{
ResetProxy();
}
}
void UDynamicMeshComponent::FastNotifyUVsUpdated()
{
FastNotifyVertexAttributesUpdated(EMeshRenderAttributeFlags::VertexUVs);
}
void UDynamicMeshComponent::FastNotifySecondaryTrianglesChanged()
{
// should not be using fast paths if we have to run mesh postprocessor
if (ensure(!RenderMeshPostProcessor) == false)
{
RenderMeshPostProcessor->ProcessMesh(*GetMesh(), *RenderMesh);
ResetProxy();
return;
}
FDynamicMeshSceneProxy* Proxy = GetCurrentSceneProxy();
if (Proxy)
{
GetCurrentSceneProxy()->FastUpdateAllIndexBuffers();
GetDynamicMesh()->PostRealtimeUpdate();
}
else
{
ResetProxy();
}
}
void UDynamicMeshComponent::FastNotifyTriangleVerticesUpdated(const TArray<int32>& Triangles, EMeshRenderAttributeFlags UpdatedAttributes)
{
// should not be using fast paths if we have to run mesh postprocessor
if (ensure(!RenderMeshPostProcessor) == false)
{
RenderMeshPostProcessor->ProcessMesh(*GetMesh(), *RenderMesh);
ResetProxy();
return;
}
bool bUpdateSecondarySort = (SecondaryTriFilterFunc) &&
((UpdatedAttributes & EMeshRenderAttributeFlags::SecondaryIndexBuffers) != EMeshRenderAttributeFlags::None);
FDynamicMeshSceneProxy* Proxy = GetCurrentSceneProxy();
if (!Proxy)
{
ResetProxy();
}
else if ( ! Decomposition )
{
FastNotifyVertexAttributesUpdated(UpdatedAttributes);
if (bUpdateSecondarySort)
{
Proxy->FastUpdateAllIndexBuffers();
}
GetDynamicMesh()->PostRealtimeUpdate();
}
else
{
// compute list of sets to update
TArray<int32> UpdatedSets;
{
TRACE_CPUPROFILER_EVENT_SCOPE(SimpleDynamicMeshComponent_FastVertexUpdate_FindSets);
for (int32 tid : Triangles)
{
int32 SetID = Decomposition->GetGroupForTriangle(tid);
UpdatedSets.AddUnique(SetID);
}
}
bool bPositions = (UpdatedAttributes & EMeshRenderAttributeFlags::Positions) != EMeshRenderAttributeFlags::None;
// calculate bounds while we are updating vertices
TFuture<void> UpdateBoundsCalc;
if (bPositions)
{
UpdateBoundsCalc = Async(DynamicMeshComponentAsyncExecTarget, [this]()
{
TRACE_CPUPROFILER_EVENT_SCOPE(SimpleDynamicMeshComponent_FastVertexUpdate_AsyncBoundsUpdate);
UpdateLocalBounds();
});
}
// update the render buffers
{
TRACE_CPUPROFILER_EVENT_SCOPE(SimpleDynamicMeshComponent_FastVertexUpdate_ApplyUpdate);
Proxy->FastUpdateVertices(UpdatedSets, bPositions,
(UpdatedAttributes & EMeshRenderAttributeFlags::VertexNormals) != EMeshRenderAttributeFlags::None,
(UpdatedAttributes & EMeshRenderAttributeFlags::VertexColors) != EMeshRenderAttributeFlags::None,
(UpdatedAttributes & EMeshRenderAttributeFlags::VertexUVs) != EMeshRenderAttributeFlags::None);
}
if (bUpdateSecondarySort)
{
Proxy->FastUpdateIndexBuffers(UpdatedSets);
}
if (bPositions)
{
TRACE_CPUPROFILER_EVENT_SCOPE(SimpleDynamicMeshComponent_FastVertexUpdate_FinalPositionsUpdate);
MarkRenderTransformDirty();
UpdateBoundsCalc.Wait();
UpdateBounds();
}
GetDynamicMesh()->PostRealtimeUpdate();
}
}
void UDynamicMeshComponent::FastNotifyTriangleVerticesUpdated(const TSet<int32>& Triangles, EMeshRenderAttributeFlags UpdatedAttributes)
{
// should not be using fast paths if we have to run mesh postprocessor
if (ensure(!RenderMeshPostProcessor) == false)
{
RenderMeshPostProcessor->ProcessMesh(*GetMesh(), *RenderMesh);
ResetProxy();
return;
}
bool bUpdateSecondarySort = (SecondaryTriFilterFunc) &&
((UpdatedAttributes & EMeshRenderAttributeFlags::SecondaryIndexBuffers) != EMeshRenderAttributeFlags::None);
FDynamicMeshSceneProxy* Proxy = GetCurrentSceneProxy();
if (!Proxy)
{
ResetProxy();
}
else if (!Decomposition)
{
FastNotifyVertexAttributesUpdated(UpdatedAttributes);
if (bUpdateSecondarySort)
{
Proxy->FastUpdateAllIndexBuffers();
}
GetDynamicMesh()->PostRealtimeUpdate();
}
else
{
// compute list of sets to update
TArray<int32> UpdatedSets;
{
TRACE_CPUPROFILER_EVENT_SCOPE(SimpleDynamicMeshComponent_FastVertexUpdate_FindSets);
for (int32 tid : Triangles)
{
int32 SetID = Decomposition->GetGroupForTriangle(tid);
UpdatedSets.AddUnique(SetID);
}
}
bool bPositions = (UpdatedAttributes & EMeshRenderAttributeFlags::Positions) != EMeshRenderAttributeFlags::None;
// calculate bounds while we are updating vertices
TFuture<void> UpdateBoundsCalc;
if (bPositions)
{
UpdateBoundsCalc = Async(DynamicMeshComponentAsyncExecTarget, [this]()
{
TRACE_CPUPROFILER_EVENT_SCOPE(SimpleDynamicMeshComponent_FastVertexUpdate_AsyncBoundsUpdate);
UpdateLocalBounds();
});
}
// update the render buffers
{
TRACE_CPUPROFILER_EVENT_SCOPE(SimpleDynamicMeshComponent_FastVertexUpdate_ApplyUpdate);
Proxy->FastUpdateVertices(UpdatedSets, bPositions,
(UpdatedAttributes & EMeshRenderAttributeFlags::VertexNormals) != EMeshRenderAttributeFlags::None,
(UpdatedAttributes & EMeshRenderAttributeFlags::VertexColors) != EMeshRenderAttributeFlags::None,
(UpdatedAttributes & EMeshRenderAttributeFlags::VertexUVs) != EMeshRenderAttributeFlags::None);
}
{
TRACE_CPUPROFILER_EVENT_SCOPE(SimpleDynamicMeshComponent_FastVertexUpdate_UpdateIndexBuffers);
if (bUpdateSecondarySort)
{
Proxy->FastUpdateIndexBuffers(UpdatedSets);
}
}
// finish up, have to wait for background bounds recalculation here
if (bPositions)
{
TRACE_CPUPROFILER_EVENT_SCOPE(SimpleDynamicMeshComponent_FastVertexUpdate_FinalPositionsUpdate);
MarkRenderTransformDirty();
UpdateBoundsCalc.Wait();
UpdateBounds();
}
GetDynamicMesh()->PostRealtimeUpdate();
}
}
/**
* Compute the combined bounding-box of the Triangles array in parallel, by computing
* partial boxes for subsets of this array, and then combining those boxes.
* TODO: this should move to a pulbic utility function, and possibly the block-based ParallelFor
* should be refactored out into something more general, as this pattern is useful in many places...
*/
static FAxisAlignedBox3d ParallelComputeROIBounds(const FDynamicMesh3& Mesh, const TArray<int32>& Triangles)
{
FAxisAlignedBox3d FinalBounds = FAxisAlignedBox3d::Empty();
FCriticalSection FinalBoundsLock;
int32 N = Triangles.Num();
constexpr int32 BlockSize = 4096;
int32 Blocks = (N / BlockSize) + 1;
ParallelFor(Blocks, [&](int bi)
{
FAxisAlignedBox3d BlockBounds = FAxisAlignedBox3d::Empty();
for (int32 k = 0; k < BlockSize; ++k)
{
int32 i = bi * BlockSize + k;
if (i < N)
{
int32 tid = Triangles[i];
const FIndex3i& TriV = Mesh.GetTriangleRef(tid);
BlockBounds.Contain(Mesh.GetVertexRef(TriV.A));
BlockBounds.Contain(Mesh.GetVertexRef(TriV.B));
BlockBounds.Contain(Mesh.GetVertexRef(TriV.C));
}
}
FinalBoundsLock.Lock();
FinalBounds.Contain(BlockBounds);
FinalBoundsLock.Unlock();
});
return FinalBounds;
}
TFuture<bool> UDynamicMeshComponent::FastNotifyTriangleVerticesUpdated_TryPrecompute(
const TArray<int32>& Triangles,
TArray<int32>& UpdateSetsOut,
FAxisAlignedBox3d& BoundsOut)
{
if ((!!RenderMeshPostProcessor) || (GetCurrentSceneProxy() == nullptr) || (!Decomposition))
{
// is there a simpler way to do this? cannot seem to just make a TFuture<bool>...
return Async(DynamicMeshComponentAsyncExecTarget, []() { return false; });
}
return Async(DynamicMeshComponentAsyncExecTarget, [this, &Triangles, &UpdateSetsOut, &BoundsOut]()
{
TFuture<void> ComputeBounds = Async(DynamicMeshComponentAsyncExecTarget, [this, &BoundsOut, &Triangles]()
{
TRACE_CPUPROFILER_EVENT_SCOPE(SimpleDynamicMeshComponent_FastVertexUpdatePrecomp_CalcBounds);
BoundsOut = ParallelComputeROIBounds(*GetMesh(), Triangles);
});
TFuture<void> ComputeSets = Async(DynamicMeshComponentAsyncExecTarget, [this, &UpdateSetsOut, &Triangles]()
{
TRACE_CPUPROFILER_EVENT_SCOPE(SimpleDynamicMeshComponent_FastVertexUpdatePrecomp_FindSets);
int32 NumBuffers = Decomposition->Num();
TArray<std::atomic<bool>> BufferFlags;
BufferFlags.SetNum(NumBuffers);
for (int32 k = 0; k < NumBuffers; ++k)
{
BufferFlags[k] = false;
}
ParallelFor(Triangles.Num(), [&](int32 k)
{
int32 SetID = Decomposition->GetGroupForTriangle(Triangles[k]);
BufferFlags[SetID] = true;
});
UpdateSetsOut.Reset();
for (int32 k = 0; k < NumBuffers; ++k)
{
if (BufferFlags[k])
{
UpdateSetsOut.Add(k);
}
}
});
ComputeSets.Wait();
ComputeBounds.Wait();
return true;
});
}
void UDynamicMeshComponent::FastNotifyTriangleVerticesUpdated_ApplyPrecompute(
const TArray<int32>& Triangles,
EMeshRenderAttributeFlags UpdatedAttributes,
TFuture<bool>& Precompute,
const TArray<int32>& UpdateSets,
const FAxisAlignedBox3d& UpdateSetBounds)
{
Precompute.Wait();
bool bPrecomputeOK = Precompute.Get();
if (bPrecomputeOK == false || GetCurrentSceneProxy() == nullptr )
{
FastNotifyTriangleVerticesUpdated(Triangles, UpdatedAttributes);
return;
}
FDynamicMeshSceneProxy* Proxy = GetCurrentSceneProxy();
bool bPositions = (UpdatedAttributes & EMeshRenderAttributeFlags::Positions) != EMeshRenderAttributeFlags::None;
bool bUpdateSecondarySort = (SecondaryTriFilterFunc) &&
((UpdatedAttributes & EMeshRenderAttributeFlags::SecondaryIndexBuffers) != EMeshRenderAttributeFlags::None);
{
TRACE_CPUPROFILER_EVENT_SCOPE(SimpleDynamicMeshComponent_FastVertexUpdate_ApplyUpdate);
Proxy->FastUpdateVertices(UpdateSets, bPositions,
(UpdatedAttributes & EMeshRenderAttributeFlags::VertexNormals) != EMeshRenderAttributeFlags::None,
(UpdatedAttributes & EMeshRenderAttributeFlags::VertexColors) != EMeshRenderAttributeFlags::None,
(UpdatedAttributes & EMeshRenderAttributeFlags::VertexUVs) != EMeshRenderAttributeFlags::None);
}
{
TRACE_CPUPROFILER_EVENT_SCOPE(SimpleDynamicMeshComponent_FastVertexUpdate_UpdateIndexBuffers);
if (bUpdateSecondarySort)
{
Proxy->FastUpdateIndexBuffers(UpdateSets);
}
}
if (bPositions)
{
TRACE_CPUPROFILER_EVENT_SCOPE(SimpleDynamicMeshComponent_FastVertexUpdate_FinalPositionsUpdate);
MarkRenderTransformDirty();
LocalBounds.Contain(UpdateSetBounds);
UpdateBounds();
}
GetDynamicMesh()->PostRealtimeUpdate();
}
FPrimitiveSceneProxy* UDynamicMeshComponent::CreateSceneProxy()
{
// if this is not always the case, we have made incorrect assumptions
ensure(GetCurrentSceneProxy() == nullptr);
FDynamicMeshSceneProxy* NewProxy = nullptr;
if (GetMesh()->TriangleCount() > 0)
{
NewProxy = new FDynamicMeshSceneProxy(this);
if (TriangleColorFunc)
{
NewProxy->bUsePerTriangleColor = true;
NewProxy->PerTriangleColorFunc = [this](const FDynamicMesh3* MeshIn, int TriangleID) { return GetTriangleColor(MeshIn, TriangleID); };
}
if (SecondaryTriFilterFunc)
{
NewProxy->bUseSecondaryTriBuffers = true;
NewProxy->SecondaryTriFilterFunc = [this](const FDynamicMesh3* MeshIn, int32 TriangleID)
{
return (SecondaryTriFilterFunc) ? SecondaryTriFilterFunc(MeshIn, TriangleID) : false;
};
}
if (Decomposition)
{
NewProxy->InitializeFromDecomposition(Decomposition);
}
else
{
NewProxy->Initialize();
}
}
bProxyValid = true;
return NewProxy;
}
void UDynamicMeshComponent::NotifyMaterialSetUpdated()
{
if (GetCurrentSceneProxy() != nullptr)
{
GetCurrentSceneProxy()->UpdatedReferencedMaterials();
}
}
void UDynamicMeshComponent::SetTriangleColorFunction(
TUniqueFunction<FColor(const FDynamicMesh3*, int)> TriangleColorFuncIn,
EDynamicMeshComponentRenderUpdateMode UpdateMode)
{
TriangleColorFunc = MoveTemp(TriangleColorFuncIn);
if (UpdateMode == EDynamicMeshComponentRenderUpdateMode::FastUpdate)
{
FastNotifyColorsUpdated();
}
else if (UpdateMode == EDynamicMeshComponentRenderUpdateMode::FullUpdate)
{
NotifyMeshUpdated();
}
}
void UDynamicMeshComponent::ClearTriangleColorFunction(EDynamicMeshComponentRenderUpdateMode UpdateMode)
{
if (TriangleColorFunc)
{
TriangleColorFunc = nullptr;
if (UpdateMode == EDynamicMeshComponentRenderUpdateMode::FastUpdate)
{
FastNotifyColorsUpdated();
}
else if (UpdateMode == EDynamicMeshComponentRenderUpdateMode::FullUpdate)
{
NotifyMeshUpdated();
}
}
}
bool UDynamicMeshComponent::HasTriangleColorFunction()
{
return !!TriangleColorFunc;
}
void UDynamicMeshComponent::EnableSecondaryTriangleBuffers(TUniqueFunction<bool(const FDynamicMesh3*, int32)> SecondaryTriFilterFuncIn)
{
SecondaryTriFilterFunc = MoveTemp(SecondaryTriFilterFuncIn);
NotifyMeshUpdated();
}
void UDynamicMeshComponent::DisableSecondaryTriangleBuffers()
{
SecondaryTriFilterFunc = nullptr;
NotifyMeshUpdated();
}
void UDynamicMeshComponent::SetExternalDecomposition(TUniquePtr<FMeshRenderDecomposition> DecompositionIn)
{
check(DecompositionIn->Num() > 0);
Decomposition = MoveTemp(DecompositionIn);
NotifyMeshUpdated();
}
FColor UDynamicMeshComponent::GetTriangleColor(const FDynamicMesh3* MeshIn, int TriangleID)
{
if (TriangleColorFunc)
{
return TriangleColorFunc(MeshIn, TriangleID);
}
else
{
return (TriangleID % 2 == 0) ? FColor::Red : FColor::White;
}
}
FBoxSphereBounds UDynamicMeshComponent::CalcBounds(const FTransform& LocalToWorld) const
{
// can get a tighter box by calculating in world space, but we care more about performance
FBox LocalBoundingBox = (FBox)LocalBounds;
FBoxSphereBounds Ret(LocalBoundingBox.TransformBy(LocalToWorld));
Ret.BoxExtent *= BoundsScale;
Ret.SphereRadius *= BoundsScale;
return Ret;
}
void UDynamicMeshComponent::SetInvalidateProxyOnChangeEnabled(bool bEnabled)
{
bInvalidateProxyOnChange = bEnabled;
}
void UDynamicMeshComponent::ApplyChange(const FMeshVertexChange* Change, bool bRevert)
{
// will fire UDynamicMesh::MeshChangedEvent, which will call OnMeshObjectChanged() below to invalidate proxy, fire change events, etc
MeshObject->ApplyChange(Change, bRevert);
}
void UDynamicMeshComponent::ApplyChange(const FMeshChange* Change, bool bRevert)
{
// will fire UDynamicMesh::MeshChangedEvent, which will call OnMeshObjectChanged() below to invalidate proxy, fire change events, etc
MeshObject->ApplyChange(Change, bRevert);
}
void UDynamicMeshComponent::ApplyChange(const FMeshReplacementChange* Change, bool bRevert)
{
// will fire UDynamicMesh::MeshChangedEvent, which will call OnMeshObjectChanged() below to invalidate proxy, fire change events, etc
MeshObject->ApplyChange(Change, bRevert);
}
void UDynamicMeshComponent::OnMeshObjectChanged(UDynamicMesh* ChangedMeshObject, FDynamicMeshChangeInfo ChangeInfo)
{
bool bIsFChange = (
ChangeInfo.Type == EDynamicMeshChangeType::MeshChange
|| ChangeInfo.Type == EDynamicMeshChangeType::MeshVertexChange
|| ChangeInfo.Type == EDynamicMeshChangeType::MeshReplacementChange);
if (bIsFChange)
{
if (bInvalidateProxyOnChange)
{
NotifyMeshUpdated();
}
OnMeshChanged.Broadcast();
if (ChangeInfo.Type == EDynamicMeshChangeType::MeshVertexChange)
{
OnMeshVerticesChanged.Broadcast(this, ChangeInfo.VertexChange, ChangeInfo.bIsRevertChange);
}
}
else
{
NotifyMeshUpdated();
OnMeshChanged.Broadcast();
}
// Rebuild body setup. Should this be deferred until proxy creation? Sometimes multiple changes are emitted...
// todo: can possibly skip this in some change situations, eg if only changing attributes
if (bDeferCollisionUpdates)
{
InvalidatePhysicsData();
}
else
{
RebuildPhysicsData();
}
}
void UDynamicMeshComponent::SetDynamicMesh(UDynamicMesh* NewMesh)
{
if (ensure(NewMesh) == false)
{
return;
}
if (ensure(MeshObject))
{
MeshObject->OnMeshChanged().Remove(MeshObjectChangedHandle);
}
MeshObject = NewMesh;
MeshObjectChangedHandle = MeshObject->OnMeshChanged().AddUObject(this, &UDynamicMeshComponent::OnMeshObjectChanged);
NotifyMeshUpdated();
OnMeshChanged.Broadcast();
// Rebuild physics data
if (bDeferCollisionUpdates)
{
InvalidatePhysicsData();
}
else
{
RebuildPhysicsData();
}
}
void UDynamicMeshComponent::OnChildAttached(USceneComponent* ChildComponent)
{
Super::OnChildAttached(ChildComponent);
OnChildAttachmentModified.Broadcast(ChildComponent, true);
}
void UDynamicMeshComponent::OnChildDetached(USceneComponent* ChildComponent)
{
Super::OnChildDetached(ChildComponent);
OnChildAttachmentModified.Broadcast(ChildComponent, false);
}
bool UDynamicMeshComponent::GetPhysicsTriMeshData(struct FTriMeshCollisionData* CollisionData, bool InUseAllTriData)
{
// todo: support physical materials
// todo: support UPhysicsSettings::Get()->bSupportUVFromHitResults
// this is something we currently assume, if you hit this ensure, we made a mistake
ensure(bEnableComplexCollision);
ProcessMesh([&](const FDynamicMesh3& Mesh)
{
TArray<int32> VertexMap;
bool bIsSparseV = !Mesh.IsCompactV();
if (bIsSparseV)
{
VertexMap.SetNum(Mesh.MaxVertexID());
}
// copy vertices
CollisionData->Vertices.Reserve(Mesh.VertexCount());
for (int32 vid : Mesh.VertexIndicesItr())
{
int32 Index = CollisionData->Vertices.Add((FVector)Mesh.GetVertex(vid));
if (bIsSparseV)
{
VertexMap[vid] = Index;
}
else
{
check(vid == Index);
}
}
// copy triangles
CollisionData->Indices.Reserve(Mesh.TriangleCount());
CollisionData->MaterialIndices.Reserve(Mesh.TriangleCount());
for (int32 tid : Mesh.TriangleIndicesItr())
{
FIndex3i Tri = Mesh.GetTriangle(tid);
FTriIndices Triangle;
Triangle.v0 = (bIsSparseV) ? VertexMap[Tri.A] : Tri.A;
Triangle.v1 = (bIsSparseV) ? VertexMap[Tri.B] : Tri.B;
Triangle.v2 = (bIsSparseV) ? VertexMap[Tri.C] : Tri.C;
CollisionData->Indices.Add(Triangle);
CollisionData->MaterialIndices.Add(0); // not supporting physical materials yet
}
CollisionData->bFlipNormals = true;
CollisionData->bDeformableMesh = true;
CollisionData->bFastCook = true;
});
return true;
}
bool UDynamicMeshComponent::ContainsPhysicsTriMeshData(bool InUseAllTriData) const
{
return bEnableComplexCollision && ((MeshObject != nullptr) ? (MeshObject->GetTriangleCount() > 0) : false);
}
bool UDynamicMeshComponent::WantsNegXTriMesh()
{
return true;
}
UBodySetup* UDynamicMeshComponent::GetBodySetup()
{
if (MeshBodySetup == nullptr)
{
UBodySetup* NewBodySetup = nullptr;
{
FGCScopeGuard Scope;
// Below flags are copied from UProceduralMeshComponent::CreateBodySetupHelper(). Without these flags, DynamicMeshComponents inside
// a DynamicMeshActor BP will result on a GLEO error after loading and modifying a saved Level (but *not* on the initial save)
// The UBodySetup in a template needs to be public since the property is Instanced and thus is the archetype of the instance meaning there is a direct reference
NewBodySetup = NewObject<UBodySetup>(this, NAME_None, (IsTemplate() ? RF_Public | RF_ArchetypeObject : RF_NoFlags));
}
NewBodySetup->BodySetupGuid = FGuid::NewGuid();
NewBodySetup->bGenerateMirroredCollision = false;
NewBodySetup->CollisionTraceFlag = this->CollisionType;
NewBodySetup->DefaultInstance.SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName);
NewBodySetup->bSupportUVsAndFaceRemap = false; /* bSupportPhysicalMaterialMasks; */
SetBodySetup(NewBodySetup);
}
return MeshBodySetup;
}
void UDynamicMeshComponent::SetBodySetup(UBodySetup* NewSetup)
{
if (ensure(NewSetup))
{
MeshBodySetup = NewSetup;
}
}
void UDynamicMeshComponent::InvalidatePhysicsData()
{
if (GetBodySetup())
{
GetBodySetup()->InvalidatePhysicsData();
bCollisionUpdatePending = true;
}
}
void UDynamicMeshComponent::RebuildPhysicsData()
{
UBodySetup* BodySetup = GetBodySetup();
if (BodySetup)
{
// New GUID as collision has changed
BodySetup->BodySetupGuid = FGuid::NewGuid();
// Also we want cooked data for this
BodySetup->bHasCookedCollisionData = true;
BodySetup->CollisionTraceFlag = this->CollisionType;
BodySetup->InvalidatePhysicsData();
BodySetup->CreatePhysicsMeshes();
RecreatePhysicsState();
bCollisionUpdatePending = false;
}
}
void UDynamicMeshComponent::UpdateCollision(bool bOnlyIfPending)
{
if (bOnlyIfPending == false || bCollisionUpdatePending)
{
RebuildPhysicsData();
}
}
void UDynamicMeshComponent::EnableComplexAsSimpleCollision()
{
bEnableComplexCollision = true;
CollisionType = ECollisionTraceFlag::CTF_UseComplexAsSimple;
UpdateCollision(true);
}