// Copyright Epic Games, Inc. All Rights Reserved. #include "Components/DynamicMeshComponent.h" #include "PrimitiveSceneProxy.h" #include "MaterialShared.h" #include "Engine/CollisionProfile.h" #include "Engine/World.h" #include "Materials/Material.h" #include "Async/Async.h" #include "HAL/UESemaphore.h" #include "Engine/CollisionProfile.h" #include "PhysicsEngine/PhysicsSettings.h" #include "DynamicMesh/DynamicMeshAttributeSet.h" #include "DynamicMesh/MeshNormals.h" #include "MeshDescriptionToDynamicMesh.h" #include "Util/ColorConstants.h" #include "Changes/MeshVertexChange.h" #include "Changes/MeshChange.h" #include "DynamicMesh/MeshTransforms.h" #include "UObject/UE5ReleaseStreamObjectVersion.h" #include "UObject/UObjectGlobals.h" // default proxy for this component #include "Components/DynamicMeshSceneProxy.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(DynamicMeshComponent) using namespace UE::Geometry; static TAutoConsoleVariable CVarDynamicMeshComponent_MaxComplexCollisionTriCount( TEXT("geometry.DynamicMesh.MaxComplexCollisionTriCount"), 250000, TEXT("If a DynamicMeshCompnent's UDynamicMesh has a larger triangle count than this value, it will not be passed to the Physics system to be used as Complex Collision geometry. A negative value indicates no limit.") ); 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 } namespace UELocal { static EMeshRenderAttributeFlags ConvertChangeFlagsToUpdateFlags(EDynamicMeshAttributeChangeFlags ChangeFlags) { EMeshRenderAttributeFlags UpdateFlags = EMeshRenderAttributeFlags::None; if ((ChangeFlags & EDynamicMeshAttributeChangeFlags::VertexPositions) != EDynamicMeshAttributeChangeFlags::Unknown) { UpdateFlags |= EMeshRenderAttributeFlags::Positions; } if ((ChangeFlags & EDynamicMeshAttributeChangeFlags::NormalsTangents) != EDynamicMeshAttributeChangeFlags::Unknown) { UpdateFlags |= EMeshRenderAttributeFlags::VertexNormals; } if ((ChangeFlags & EDynamicMeshAttributeChangeFlags::VertexColors) != EDynamicMeshAttributeChangeFlags::Unknown) { UpdateFlags |= EMeshRenderAttributeFlags::VertexColors; } if ((ChangeFlags & EDynamicMeshAttributeChangeFlags::UVs) != EDynamicMeshAttributeChangeFlags::Unknown) { UpdateFlags |= EMeshRenderAttributeFlags::VertexUVs; } return UpdateFlags; } } UDynamicMeshComponent::UDynamicMeshComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { PrimaryComponentTick.bCanEverTick = false; SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); MeshObject = CreateDefaultSubobject(TEXT("DynamicMesh")); //MeshObject->SetFlags(RF_Transactional); MeshObjectChangedHandle = MeshObject->OnMeshChanged().AddUObject(this, &UDynamicMeshComponent::OnMeshObjectChanged); ResetProxy(); } void UDynamicMeshComponent::Serialize(FArchive& Ar) { Super::Serialize(Ar); Ar.UsingCustomVersion(FUE5ReleaseStreamObjectVersion::GUID); } void UDynamicMeshComponent::PostLoad() { Super::PostLoad(); const int32 UE5ReleaseStreamObjectVersion = GetLinkerCustomVersion(FUE5ReleaseStreamObjectVersion::GUID); if (UE5ReleaseStreamObjectVersion < FUE5ReleaseStreamObjectVersion::DynamicMeshComponentsDefaultUseExternalTangents) { // Set the old default value if (TangentsType == EDynamicMeshComponentTangentsMode::Default) { TangentsType = EDynamicMeshComponentTangentsMode::NoTangents; } } // The intention here is that MeshObject is never nullptr, however we cannot guarantee this as a subclass // may have set it to null, and/or some type of serialization issue has caused it to fail to save/load. // Avoid immediate crashes by creating a new UDynamicMesh here in such cases if (ensure(MeshObject != nullptr) == false) { MeshObject = NewObject(this, TEXT("DynamicMesh")); } 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(); } void UDynamicMeshComponent::PostEditImport() { Super::PostEditImport(); // MeshObject should never be null here, but we re-validate that it isn't (similar to PostLoad method, above) if (ensure(MeshObject != nullptr) == false) { MeshObject = NewObject(this, TEXT("DynamicMesh")); MeshObjectChangedHandle = MeshObject->OnMeshChanged().AddUObject(this, &UDynamicMeshComponent::OnMeshObjectChanged); } } #if WITH_EDITOR void UDynamicMeshComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { Super::PostEditChangeProperty(PropertyChangedEvent); const FName PropName = PropertyChangedEvent.GetPropertyName(); if (PropName == GET_MEMBER_NAME_CHECKED(UDynamicMeshComponent, TangentsType)) { InvalidateAutoCalculatedTangents(); } else 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) { if (ensure(MeshObject) && ensureMsgf(IsEditable(), TEXT("Attempted to modify the internal mesh of a UDynamicMeshComponent that is not editable"))) { MeshObject->SetMesh(MoveTemp(MoveMesh)); } } void UDynamicMeshComponent::ProcessMesh( TFunctionRef ProcessFunc ) const { if (MeshObject) { MeshObject->ProcessMesh(ProcessFunc); } } void UDynamicMeshComponent::EditMesh(TFunctionRef EditFunc, EDynamicMeshComponentRenderUpdateMode UpdateMode ) { if (MeshObject && ensureMsgf(IsEditable(), TEXT("Attempted to modify the internal mesh of a UDynamicMeshComponent that is not editable"))) { MeshObject->EditMesh(EditFunc); if (UpdateMode != EDynamicMeshComponentRenderUpdateMode::NoUpdate) { NotifyMeshUpdated(); } } } void UDynamicMeshComponent::SetRenderMeshPostProcessor(TUniquePtr Processor) { if (!ensure(MeshObject)) { return; } RenderMeshPostProcessor = MoveTemp(Processor); if (RenderMeshPostProcessor) { if (!RenderMesh) { RenderMesh = MakeUnique(*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 FTransform3d& Transform, bool bInvert) { if (ensure(MeshObject) && ensureMsgf(IsEditable(), TEXT("Attempted to modify the internal mesh of a UDynamicMeshComponent that is not editable"))) { MeshObject->EditMesh([&](FDynamicMesh3& EditMesh) { if (bInvert) { MeshTransforms::ApplyTransformInverse(EditMesh, Transform, true); } else { MeshTransforms::ApplyTransform(EditMesh, Transform, true); } }, EDynamicMeshChangeType::DeformationEdit, EDynamicMeshAttributeChangeFlags::VertexPositions | EDynamicMeshAttributeChangeFlags::NormalsTangents, /*bDeferChangeEvents*/ false); } } bool UDynamicMeshComponent::ValidateMaterialSlots(bool bCreateIfMissing, bool bDeleteExtraSlots) { int32 MaxMeshMaterialID = 0; 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& NewMaterialSet, bool bDeleteExtraSlots) { for (int k = 0; k < NewMaterialSet.Num(); ++k) { SetMaterial(k, NewMaterialSet[k]); } if (bDeleteExtraSlots) { SetNumMaterials(NewMaterialSet.Num()); } } void UDynamicMeshComponent::SetTangentsType(EDynamicMeshComponentTangentsMode NewTangentsType) { if (NewTangentsType != TangentsType) { TangentsType = NewTangentsType; InvalidateAutoCalculatedTangents(); } } void UDynamicMeshComponent::InvalidateAutoCalculatedTangents() { bAutoCalculatedTangentsValid = false; } const UE::Geometry::FMeshTangentsf* UDynamicMeshComponent::GetAutoCalculatedTangents() { if (ensure(MeshObject) && GetTangentsType() == EDynamicMeshComponentTangentsMode::AutoCalculated && GetDynamicMesh()->GetMeshRef().HasAttributes()) { UpdateAutoCalculatedTangents(); return (bAutoCalculatedTangentsValid) ? &AutoCalculatedTangents : nullptr; } return nullptr; } void UDynamicMeshComponent::UpdateAutoCalculatedTangents() { if (GetTangentsType() == 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 = MeshObject ? GetMesh()->GetBounds(true) : FAxisAlignedBox3d::Empty(); 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 (MeshObject && RenderMeshPostProcessor) { RenderMeshPostProcessor->ProcessMesh(*GetMesh(), *RenderMesh); } ResetProxy(); } void UDynamicMeshComponent::NotifyMeshModified() { NotifyMeshUpdated(); } void UDynamicMeshComponent::FastNotifyColorsUpdated() { if (!ensure(MeshObject)) { return; } // 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 && AllowFastUpdate()) { if (HasTriangleColorFunction() && Proxy->MeshRenderBufferSetConverter.bUsePerTriangleColor == false ) { Proxy->MeshRenderBufferSetConverter.bUsePerTriangleColor = true; Proxy->MeshRenderBufferSetConverter.PerTriangleColorFunc = [this](const FDynamicMesh3* MeshIn, int TriangleID) { return GetTriangleColor(MeshIn, TriangleID); }; } else if ( !HasTriangleColorFunction() && Proxy->MeshRenderBufferSetConverter.bUsePerTriangleColor == true) { Proxy->MeshRenderBufferSetConverter.bUsePerTriangleColor = false; Proxy->MeshRenderBufferSetConverter.PerTriangleColorFunc = nullptr; } if (HasVertexColorRemappingFunction() && Proxy->MeshRenderBufferSetConverter.bApplyVertexColorRemapping == false) { Proxy->MeshRenderBufferSetConverter.bApplyVertexColorRemapping = true; Proxy->MeshRenderBufferSetConverter.VertexColorRemappingFunc = [this](FVector4f& Color) { RemapVertexColor(Color); }; } else if (!HasVertexColorRemappingFunction() && Proxy->MeshRenderBufferSetConverter.bApplyVertexColorRemapping == true) { Proxy->MeshRenderBufferSetConverter.bApplyVertexColorRemapping = false; Proxy->MeshRenderBufferSetConverter.VertexColorRemappingFunc = nullptr; } Proxy->FastUpdateVertices(false, false, true, false); //MarkRenderDynamicDataDirty(); } else { ResetProxy(); } } void UDynamicMeshComponent::FastNotifyPositionsUpdated(bool bNormals, bool bColors, bool bUVs) { if (!ensure(MeshObject)) { return; } // 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 && AllowFastUpdate()) { // calculate bounds while we are updating vertices TFuture 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) { if (!ensure(MeshObject)) { return; } // 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) && AllowFastUpdate()) { GetCurrentSceneProxy()->FastUpdateVertices(false, bNormals, bColors, bUVs); //MarkRenderDynamicDataDirty(); //MarkRenderTransformDirty(); GetDynamicMesh()->PostRealtimeUpdate(); } else { ResetProxy(); } } void UDynamicMeshComponent::FastNotifyVertexAttributesUpdated(EMeshRenderAttributeFlags UpdatedAttributes) { if (!ensure(MeshObject)) { return; } // 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) && AllowFastUpdate()) { bool bPositions = (UpdatedAttributes & EMeshRenderAttributeFlags::Positions) != EMeshRenderAttributeFlags::None; // calculate bounds while we are updating vertices TFuture 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::NotifyMeshVertexAttributesModified( bool bPositions, bool bNormals, bool bUVs, bool bColors ) { EMeshRenderAttributeFlags Flags = EMeshRenderAttributeFlags::None; if (bPositions) { Flags |= EMeshRenderAttributeFlags::Positions; } if (bNormals) { Flags |= EMeshRenderAttributeFlags::VertexNormals; } if (bUVs) { Flags |= EMeshRenderAttributeFlags::VertexUVs; } if (bColors) { Flags |= EMeshRenderAttributeFlags::VertexColors; } if (Flags == EMeshRenderAttributeFlags::None) { return; } FastNotifyVertexAttributesUpdated(Flags); } void UDynamicMeshComponent::FastNotifySecondaryTrianglesChanged() { if (!ensure(MeshObject)) { return; } // 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 && AllowFastUpdate()) { GetCurrentSceneProxy()->FastUpdateAllIndexBuffers(); GetDynamicMesh()->PostRealtimeUpdate(); } else { ResetProxy(); } } void UDynamicMeshComponent::FastNotifyTriangleVerticesUpdated(const TArray& Triangles, EMeshRenderAttributeFlags UpdatedAttributes) { if (!ensure(MeshObject)) { return; } // 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 || !AllowFastUpdate()) { ResetProxy(); } else if ( ! Decomposition ) { FastNotifyVertexAttributesUpdated(UpdatedAttributes); if (bUpdateSecondarySort) { Proxy->FastUpdateAllIndexBuffers(); } GetDynamicMesh()->PostRealtimeUpdate(); } else { // compute list of sets to update TArray 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 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& Triangles, EMeshRenderAttributeFlags UpdatedAttributes) { if (!ensure(MeshObject)) { return; } // 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 || !AllowFastUpdate()) { ResetProxy(); } else if (!Decomposition) { FastNotifyVertexAttributesUpdated(UpdatedAttributes); if (bUpdateSecondarySort) { Proxy->FastUpdateAllIndexBuffers(); } GetDynamicMesh()->PostRealtimeUpdate(); } else { // compute list of sets to update TArray 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 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& 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 UDynamicMeshComponent::FastNotifyTriangleVerticesUpdated_TryPrecompute( const TArray& Triangles, TArray& UpdateSetsOut, FAxisAlignedBox3d& BoundsOut) { if (!ensure(MeshObject) || (!!RenderMeshPostProcessor) || (GetCurrentSceneProxy() == nullptr) || (!Decomposition) || !AllowFastUpdate()) { // is there a simpler way to do this? cannot seem to just make a TFuture... return Async(DynamicMeshComponentAsyncExecTarget, []() { return false; }); } return Async(DynamicMeshComponentAsyncExecTarget, [this, &Triangles, &UpdateSetsOut, &BoundsOut]() { TFuture ComputeBounds = Async(DynamicMeshComponentAsyncExecTarget, [this, &BoundsOut, &Triangles]() { TRACE_CPUPROFILER_EVENT_SCOPE(SimpleDynamicMeshComponent_FastVertexUpdatePrecomp_CalcBounds); BoundsOut = ParallelComputeROIBounds(*GetMesh(), Triangles); }); TFuture ComputeSets = Async(DynamicMeshComponentAsyncExecTarget, [this, &UpdateSetsOut, &Triangles]() { TRACE_CPUPROFILER_EVENT_SCOPE(SimpleDynamicMeshComponent_FastVertexUpdatePrecomp_FindSets); int32 NumBuffers = Decomposition->Num(); TArray> 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& Triangles, EMeshRenderAttributeFlags UpdatedAttributes, TFuture& Precompute, const TArray& UpdateSets, const FAxisAlignedBox3d& UpdateSetBounds) { Precompute.Wait(); bool bPrecomputeOK = Precompute.Get(); if (bPrecomputeOK == false || GetCurrentSceneProxy() == nullptr || !AllowFastUpdate()) { 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 (MeshObject && GetMesh()->TriangleCount() > 0) { NewProxy = new FDynamicMeshSceneProxy(this); if (TriangleColorFunc) { NewProxy->MeshRenderBufferSetConverter.bUsePerTriangleColor = true; NewProxy->MeshRenderBufferSetConverter.PerTriangleColorFunc = [this](const FDynamicMesh3* MeshIn, int TriangleID) { return GetTriangleColor(MeshIn, TriangleID); }; } else if ( GetColorOverrideMode() == EDynamicMeshComponentColorOverrideMode::Polygroups ) { NewProxy->MeshRenderBufferSetConverter.bUsePerTriangleColor = true; NewProxy->MeshRenderBufferSetConverter.PerTriangleColorFunc = [this](const FDynamicMesh3* MeshIn, int TriangleID) { return GetGroupColor(MeshIn, TriangleID); }; } if (HasVertexColorRemappingFunction()) { NewProxy->MeshRenderBufferSetConverter.bApplyVertexColorRemapping = true; NewProxy->MeshRenderBufferSetConverter.VertexColorRemappingFunc = [this](FVector4f& Color) { RemapVertexColor(Color); }; } if (SecondaryTriFilterFunc) { NewProxy->MeshRenderBufferSetConverter.bUseSecondaryTriBuffers = true; NewProxy->MeshRenderBufferSetConverter.SecondaryTriFilterFunc = [this](const FDynamicMesh3* MeshIn, int32 TriangleID) { return (SecondaryTriFilterFunc) ? SecondaryTriFilterFunc(MeshIn, TriangleID) : false; }; } if (Decomposition) { NewProxy->InitializeFromDecomposition(Decomposition); } else { NewProxy->Initialize(); } NewProxy->SetVerifyUsedMaterials(bProxyVerifyUsedMaterials); } bProxyValid = true; return NewProxy; } void UDynamicMeshComponent::NotifyMaterialSetUpdated() { if (GetCurrentSceneProxy() != nullptr) { GetCurrentSceneProxy()->UpdatedReferencedMaterials(); } } void UDynamicMeshComponent::SetTriangleColorFunction( TUniqueFunction 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::SetVertexColorRemappingFunction( TUniqueFunction ColorMapFuncIn, EDynamicMeshComponentRenderUpdateMode UpdateMode) { VertexColorMappingFunc = MoveTemp(ColorMapFuncIn); if (UpdateMode == EDynamicMeshComponentRenderUpdateMode::FastUpdate) { FastNotifyColorsUpdated(); } else if (UpdateMode == EDynamicMeshComponentRenderUpdateMode::FullUpdate) { NotifyMeshUpdated(); } } void UDynamicMeshComponent::ClearVertexColorRemappingFunction(EDynamicMeshComponentRenderUpdateMode UpdateMode) { if (VertexColorMappingFunc) { VertexColorMappingFunc = nullptr; if (UpdateMode == EDynamicMeshComponentRenderUpdateMode::FastUpdate) { FastNotifyColorsUpdated(); } else if (UpdateMode == EDynamicMeshComponentRenderUpdateMode::FullUpdate) { NotifyMeshUpdated(); } } } bool UDynamicMeshComponent::HasVertexColorRemappingFunction() { return !!VertexColorMappingFunc; } void UDynamicMeshComponent::RemapVertexColor(FVector4f& VertexColorInOut) { if (VertexColorMappingFunc) { VertexColorMappingFunc(VertexColorInOut); } } void UDynamicMeshComponent::EnableSecondaryTriangleBuffers(TUniqueFunction SecondaryTriFilterFuncIn) { SecondaryTriFilterFunc = MoveTemp(SecondaryTriFilterFuncIn); NotifyMeshUpdated(); } void UDynamicMeshComponent::DisableSecondaryTriangleBuffers() { SecondaryTriFilterFunc = nullptr; NotifyMeshUpdated(); } void UDynamicMeshComponent::SetExternalDecomposition(TUniquePtr DecompositionIn) { ensure(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; } } FColor UDynamicMeshComponent::GetGroupColor(const FDynamicMesh3* Mesh, int TriangleID) const { int32 GroupID = Mesh->HasTriangleGroups() ? Mesh->GetTriangleGroup(TriangleID) : 0; return UE::Geometry::LinearColors::SelectFColor(GroupID); } 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 if (ensure(MeshObject)) { 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 if (ensure(MeshObject)) { 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 if (ensure(MeshObject)) { 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(); BroadcastMeshPropertyChangeEvent(); if (ChangeInfo.Type == EDynamicMeshChangeType::MeshVertexChange) { OnMeshVerticesChanged.Broadcast(this, ChangeInfo.VertexChange, ChangeInfo.bIsRevertChange); } OnMeshRegionChanged.Broadcast(this, ChangeInfo.GetChange(), ChangeInfo.bIsRevertChange); } else { if (ChangeInfo.Type == EDynamicMeshChangeType::DeformationEdit) { // if ChangeType is a vertex deformation, we can do a fast-update of the vertex buffers // without fully rebuilding the SceneProxy EMeshRenderAttributeFlags UpdateFlags = UELocal::ConvertChangeFlagsToUpdateFlags(ChangeInfo.Flags); FastNotifyVertexAttributesUpdated(UpdateFlags); } else { NotifyMeshUpdated(); } OnMeshChanged.Broadcast(); BroadcastMeshPropertyChangeEvent(); } InternalOnMeshUpdated(); } void UDynamicMeshComponent::BroadcastMeshPropertyChangeEvent() { #if WITH_EDITOR if (FProperty* MeshProperty = UDynamicMeshComponent::StaticClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(UDynamicMeshComponent, MeshObject))) { FPropertyChangedEvent MeshChangedEvent(MeshProperty); FCoreUObjectDelegates::OnObjectPropertyChanged.Broadcast(this, MeshChangedEvent); } #endif } void UDynamicMeshComponent::SetDynamicMesh(UDynamicMesh* NewMesh) { if (ensure(NewMesh) == false) { return; } if (ensure(MeshObject)) { MeshObject->OnMeshChanged().Remove(MeshObjectChangedHandle); } // set Outer of NewMesh to be this Component, ie transfer ownership. This is done via "renaming", which is // a bit odd, so the flags prevent some standard "renaming" behaviors from happening NewMesh->Rename( nullptr, this, REN_DontCreateRedirectors); MeshObject = NewMesh; MeshObjectChangedHandle = MeshObject->OnMeshChanged().AddUObject(this, &UDynamicMeshComponent::OnMeshObjectChanged); NotifyMeshUpdated(); OnMeshChanged.Broadcast(); BroadcastMeshPropertyChangeEvent(); InternalOnMeshUpdated(); } void UDynamicMeshComponent::OnChildAttached(USceneComponent* ChildComponent) { Super::OnChildAttached(ChildComponent); OnChildAttachmentModified.Broadcast(ChildComponent, true); } void UDynamicMeshComponent::OnChildDetached(USceneComponent* ChildComponent) { Super::OnChildDetached(ChildComponent); OnChildAttachmentModified.Broadcast(ChildComponent, false); } void UDynamicMeshComponent::InternalOnMeshUpdated() { // Rebuild physics data if (bDeferCollisionUpdates || bTransientDeferCollisionUpdates) { InvalidatePhysicsData(); } else { RebuildPhysicsData(); } } bool UDynamicMeshComponent::GetTriMeshSizeEstimates(struct FTriMeshCollisionDataEstimates& OutTriMeshEstimates, bool bInUseAllTriData) const { ProcessMesh([&](const FDynamicMesh3& Mesh) { bool bCopyUVs = UPhysicsSettings::Get()->bSupportUVFromHitResults && Mesh.HasAttributes() && Mesh.Attributes()->NumUVLayers() > 0 && !bDisableMeshUVHitResults; if (bCopyUVs) { // conservative estimate OutTriMeshEstimates.VerticeCount = Mesh.TriangleCount() * 3; } else { OutTriMeshEstimates.VerticeCount = Mesh.VertexCount(); } } ); return true; } bool UDynamicMeshComponent::GetPhysicsTriMeshData(struct FTriMeshCollisionData* CollisionData, bool InUseAllTriData) { // this is something we currently assume, if you hit this ensure, we made a mistake ensure(bEnableComplexCollision); ProcessMesh([&](const FDynamicMesh3& Mesh) { // See if we should copy UVs const bool bCopyUVs = UPhysicsSettings::Get()->bSupportUVFromHitResults && Mesh.HasAttributes() && Mesh.Attributes()->NumUVLayers() > 0 && !bDisableMeshUVHitResults; if (bCopyUVs) { CollisionData->UVs.SetNum(Mesh.Attributes()->NumUVLayers()); } const FDynamicMeshMaterialAttribute* MaterialAttrib = Mesh.HasAttributes() && Mesh.Attributes()->HasMaterialID() ? Mesh.Attributes()->GetMaterialID() : nullptr; TArray VertexMap; const bool bIsSparseV = !Mesh.IsCompactV(); // copy vertices if (!bCopyUVs) { if (bIsSparseV) { VertexMap.SetNum(Mesh.MaxVertexID()); } CollisionData->Vertices.Reserve(Mesh.VertexCount()); for (int32 vid : Mesh.VertexIndicesItr()) { int32 Index = CollisionData->Vertices.Add((FVector3f)Mesh.GetVertex(vid)); if (bIsSparseV) { VertexMap[vid] = Index; } else { check(vid == Index); } } } else { // map vertices per wedge VertexMap.SetNumZeroed(Mesh.MaxTriangleID() * 3); // temp array to store the UVs on a vertex (per triangle) TArray VertUVs; const FDynamicMeshAttributeSet* Attribs = Mesh.Attributes(); const int32 NumUVLayers = Attribs->NumUVLayers(); for (int32 VID : Mesh.VertexIndicesItr()) { FVector3f Pos = (FVector3f)Mesh.GetVertex(VID); int32 VertStart = CollisionData->Vertices.Num(); Mesh.EnumerateVertexTriangles(VID, [&](int32 TID) { FIndex3i Tri = Mesh.GetTriangle(TID); int32 VSubIdx = Tri.IndexOf(VID); // Get the UVs on this wedge VertUVs.Reset(8); for (int32 UVIdx = 0; UVIdx < NumUVLayers; ++UVIdx) { const FDynamicMeshUVOverlay* Overlay = Attribs->GetUVLayer(UVIdx); FIndex3i UVTri = Overlay->GetTriangle(TID); int32 ElID = UVTri[VSubIdx]; FVector2D UV(0, 0); if (ElID >= 0) { UV = (FVector2D)Overlay->GetElement(ElID); } VertUVs.Add(UV); } // Check if we've already added these UVs via an earlier wedge int32 OutputVIdx = INDEX_NONE; for (int32 VIdx = VertStart; VIdx < CollisionData->Vertices.Num(); ++VIdx) { bool bFound = true; for (int32 UVIdx = 0; UVIdx < NumUVLayers; ++UVIdx) { if (CollisionData->UVs[UVIdx][VIdx] != VertUVs[UVIdx]) { bFound = false; break; } } if (bFound) { OutputVIdx = VIdx; break; } } // If not, add the vertex w/ the UVs if (OutputVIdx == INDEX_NONE) { OutputVIdx = CollisionData->Vertices.Add(Pos); for (int32 UVIdx = 0; UVIdx < NumUVLayers; ++UVIdx) { CollisionData->UVs[UVIdx].Add(VertUVs[UVIdx]); } } // Map the wedge to the output vertex VertexMap[TID * 3 + VSubIdx] = OutputVIdx; }); } } // copy triangles CollisionData->Indices.Reserve(Mesh.TriangleCount()); CollisionData->MaterialIndices.Reserve(Mesh.TriangleCount()); for (int32 tid : Mesh.TriangleIndicesItr()) { FIndex3i Tri = Mesh.GetTriangle(tid); FTriIndices Triangle; if (bCopyUVs) { // UVs need a wedge-based map Triangle.v0 = VertexMap[tid * 3 + 0]; Triangle.v1 = VertexMap[tid * 3 + 1]; Triangle.v2 = VertexMap[tid * 3 + 2]; } else if (bIsSparseV) { Triangle.v0 = VertexMap[Tri.A]; Triangle.v1 = VertexMap[Tri.B]; Triangle.v2 = VertexMap[Tri.C]; } else { Triangle.v0 = Tri.A; Triangle.v1 = Tri.B; Triangle.v2 = Tri.C; } // Filter out triangles which will cause physics system to emit degenerate-geometry warnings. // These checks reproduce tests in Chaos::CleanTrimesh const FVector3f& A = CollisionData->Vertices[Triangle.v0]; const FVector3f& B = CollisionData->Vertices[Triangle.v1]; const FVector3f& C = CollisionData->Vertices[Triangle.v2]; if (A == B || A == C || B == C) { continue; } // anything that fails the first check should also fail this, but Chaos does both so doing the same here... const float SquaredArea = FVector3f::CrossProduct(A - B, A - C).SizeSquared(); if (SquaredArea < UE_SMALL_NUMBER) { continue; } CollisionData->Indices.Add(Triangle); int32 MaterialID = MaterialAttrib ? MaterialAttrib->GetValue(tid) : 0; CollisionData->MaterialIndices.Add(MaterialID); } CollisionData->bFlipNormals = true; CollisionData->bDeformableMesh = true; CollisionData->bFastCook = true; }); return true; } bool UDynamicMeshComponent::ContainsPhysicsTriMeshData(bool InUseAllTriData) const { if (bEnableComplexCollision && (MeshObject != nullptr)) { int32 TriangleCount = MeshObject->GetTriangleCount(); // if the triangle count is too large, skip building complex collision int32 MaxComplexCollisionTriCount = CVarDynamicMeshComponent_MaxComplexCollisionTriCount.GetValueOnAnyThread(); if (MaxComplexCollisionTriCount >= 0 && TriangleCount > MaxComplexCollisionTriCount) { static bool bHavePrintedWarningMessage = false; if (!bHavePrintedWarningMessage) { UE_LOG(LogGeometry, Warning, TEXT("Ignoring attempt to build Complex Collision for a DynamicMeshComponent with triangle count larger than %d. Increase the geometry.DynamicMesh.MaxComplexCollisionTriCount value if you are certain you want to build Complex Collision for very large meshes."), MaxComplexCollisionTriCount); bHavePrintedWarningMessage = true; } return false; } if (TriangleCount > 0) { return true; } } return false; } bool UDynamicMeshComponent::WantsNegXTriMesh() { return true; } UBodySetup* UDynamicMeshComponent::CreateBodySetupHelper() { 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(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; */ return NewBodySetup; } UBodySetup* UDynamicMeshComponent::GetBodySetup() { if (MeshBodySetup == nullptr) { UBodySetup* NewBodySetup = CreateBodySetupHelper(); SetBodySetup(NewBodySetup); } return MeshBodySetup; } void UDynamicMeshComponent::SetBodySetup(UBodySetup* NewSetup) { if (ensure(NewSetup)) { MeshBodySetup = NewSetup; } } void UDynamicMeshComponent::SetSimpleCollisionShapes(const struct FKAggregateGeom& AggGeomIn, bool bUpdateCollision) { AggGeom = AggGeomIn; if (bUpdateCollision) { UpdateCollision(false); } } void UDynamicMeshComponent::ClearSimpleCollisionShapes(bool bUpdateCollision) { AggGeom.EmptyElements(); if (bUpdateCollision) { UpdateCollision(false); } } void UDynamicMeshComponent::InvalidatePhysicsData() { if (GetBodySetup()) { GetBodySetup()->InvalidatePhysicsData(); bCollisionUpdatePending = true; } } void UDynamicMeshComponent::RebuildPhysicsData() { UWorld* World = GetWorld(); const bool bUseAsyncCook = bUseAsyncCooking // It's uncertain whether these checks are actually needed. UProceduralMeshComponent checked for a game, // not editor, world. It's possible that at the time the editor was not ticked in a way that caused // physics builds to complete. // We need to allow asynchronous builds in editor since dynamic meshes get used for real time modifications // that can otherwise hitch as things are dragged. We'll keep the guard against null or Inactive/None worlds // for now just in case, but it may not be necessary. && World && (World->IsGameWorld() || World->IsEditorWorld()); UBodySetup* BodySetup = nullptr; if (bUseAsyncCook) { // Abort all previous ones still standing for (UBodySetup* OldBody : AsyncBodySetupQueue) { OldBody->AbortPhysicsMeshAsyncCreation(); } BodySetup = CreateBodySetupHelper(); if (BodySetup) { AsyncBodySetupQueue.Add(BodySetup); } } else { AsyncBodySetupQueue.Empty(); // If for some reason we modified the async at runtime, just clear any pending async body setups BodySetup = GetBodySetup(); } if (!BodySetup) { return; } BodySetup->CollisionTraceFlag = this->CollisionType; // Note: Directly assigning AggGeom wouldn't do some important-looking cleanup (clearing pointers on convex elements) // so we RemoveSimpleCollision then AddCollisionFrom instead BodySetup->RemoveSimpleCollision(); BodySetup->AddCollisionFrom(this->AggGeom); if (bUseAsyncCook) { BodySetup->CreatePhysicsMeshesAsync(FOnAsyncPhysicsCookFinished::CreateUObject(this, &UDynamicMeshComponent::FinishPhysicsAsyncCook, BodySetup)); } else { // New GUID as collision has changed BodySetup->BodySetupGuid = FGuid::NewGuid(); // Also we want cooked data for this BodySetup->bHasCookedCollisionData = true; BodySetup->InvalidatePhysicsData(); BodySetup->CreatePhysicsMeshes(); RecreatePhysicsState(); bCollisionUpdatePending = false; } if (FDynamicMeshSceneProxy* Proxy = GetCurrentSceneProxy()) { Proxy->SetCollisionData(); } } void UDynamicMeshComponent::FinishPhysicsAsyncCook(bool bSuccess, UBodySetup* FinishedBodySetup) { TArray NewQueue; NewQueue.Reserve(AsyncBodySetupQueue.Num()); int32 FoundIdx; if (AsyncBodySetupQueue.Find(FinishedBodySetup, FoundIdx)) { // Note: currently no-cook-needed is reported identically to cook failed. // Checking AggGeom.ConvexElems & ContainsPhysicsTriMeshData here is a hack to distinguish the no-cook-needed case // These checks mirror those from UBodySetup::GetCookInfo. // TODO: remove this hack to distinguish the no-cook-needed case when/if that is no longer identical to the cook failed case const ECollisionTraceFlag BodyCollisionType = FinishedBodySetup->GetCollisionTraceFlag(); const bool bEmptySimpleCollision = FinishedBodySetup->AggGeom.ConvexElems.Num() == 0; const bool bEmptyComplexCollision = !ContainsPhysicsTriMeshData(false); const bool bNoCookNeeded = (BodyCollisionType == CTF_UseSimpleAsComplex && bEmptySimpleCollision) || (BodyCollisionType == CTF_UseComplexAsSimple && bEmptyComplexCollision) || (BodyCollisionType == CTF_UseSimpleAndComplex && bEmptySimpleCollision && bEmptyComplexCollision); if (bSuccess || bNoCookNeeded) { // The new body was found in the array meaning it's newer, so use it MeshBodySetup = FinishedBodySetup; RecreatePhysicsState(); // remove any async body setups that were requested before this one for (int32 AsyncIdx = FoundIdx + 1; AsyncIdx < AsyncBodySetupQueue.Num(); ++AsyncIdx) { NewQueue.Add(AsyncBodySetupQueue[AsyncIdx]); } AsyncBodySetupQueue = NewQueue; } else { AsyncBodySetupQueue.RemoveAt(FoundIdx); } } } void UDynamicMeshComponent::UpdateCollision(bool bOnlyIfPending) { if (bOnlyIfPending == false || bCollisionUpdatePending) { RebuildPhysicsData(); } } void UDynamicMeshComponent::BeginDestroy() { Super::BeginDestroy(); AggGeom.FreeRenderInfo(); } void UDynamicMeshComponent::EnableComplexAsSimpleCollision() { SetComplexAsSimpleCollisionEnabled(true, true); } void UDynamicMeshComponent::SetComplexAsSimpleCollisionEnabled(bool bEnabled, bool bImmediateUpdate) { bool bModified = false; if (bEnabled) { if (bEnableComplexCollision == false) { bEnableComplexCollision = true; bModified = true; } if (CollisionType != ECollisionTraceFlag::CTF_UseComplexAsSimple) { CollisionType = ECollisionTraceFlag::CTF_UseComplexAsSimple; bModified = true; } } else { if (bEnableComplexCollision == true) { bEnableComplexCollision = false; bModified = true; } if (CollisionType == ECollisionTraceFlag::CTF_UseComplexAsSimple) { CollisionType = ECollisionTraceFlag::CTF_UseDefault; bModified = true; } } if (bModified) { InvalidatePhysicsData(); } if (bImmediateUpdate) { UpdateCollision(true); } } void UDynamicMeshComponent::SetDeferredCollisionUpdatesEnabled(bool bEnabled, bool bImmediateUpdate) { if (bDeferCollisionUpdates != bEnabled) { bDeferCollisionUpdates = bEnabled; if (bEnabled == false && bImmediateUpdate) { UpdateCollision(true); } } } void UDynamicMeshComponent::SetTransientDeferCollisionUpdates(bool bEnabled) { bTransientDeferCollisionUpdates = bEnabled; } void UDynamicMeshComponent::SetSceneProxyVerifyUsedMaterials(bool bState) { bProxyVerifyUsedMaterials = bState; if (FDynamicMeshSceneProxy* Proxy = GetCurrentSceneProxy()) { Proxy->SetVerifyUsedMaterials(bState); } }