Files
UnrealEngine/Engine/Plugins/Experimental/GeometryCollectionPlugin/Source/GeometryCollectionNodes/Private/Dataflow/GeometryCollectionMeshNodes.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

1270 lines
43 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Dataflow/GeometryCollectionMeshNodes.h"
#include "Dataflow/DataflowCore.h"
#include "Engine/Blueprint.h"
#include "Engine/StaticMesh.h"
#include "Materials/MaterialInterface.h"
#include "GeometryCollection/GeometryCollectionObject.h"
#include "GeometryCollection/ManagedArrayCollection.h"
#include "GeometryCollection/GeometryCollection.h"
#include "GeometryCollection/GeometryCollectionEngineUtility.h"
#include "GeometryCollection/GeometryCollectionEngineConversion.h"
#include "GeometryCollection/Facades/CollectionTransformSelectionFacade.h"
#include "GeometryCollection/Facades/CollectionHierarchyFacade.h"
#include "Logging/LogMacros.h"
#include "Templates/SharedPointer.h"
#include "UObject/UnrealTypePrivate.h"
#include "DynamicMeshToMeshDescription.h"
#include "MeshDescriptionToDynamicMesh.h"
#include "StaticMeshAttributes.h"
#include "DynamicMeshEditor.h"
#include "VertexConnectedComponents.h"
#include "GeometryCollectionToDynamicMesh.h"
#include "EngineGlobals.h"
#include "GeometryCollection/GeometryCollectionAlgo.h"
#include "GeometryCollection/GeometryCollectionClusteringUtility.h"
#include "GeometryCollection/GeometryCollectionConvexUtility.h"
#include "Voronoi/Voronoi.h"
#include "PlanarCut.h"
#include "GeometryCollection/GeometryCollectionProximityUtility.h"
#include "FractureEngineClustering.h"
#include "FractureEngineSelection.h"
#include "FractureEngineUtility.h"
#include "Dataflow/DataflowAnyTypeRegistry.h"
#include "DynamicMesh/DynamicMesh3.h"
#include "DynamicMesh/MeshTransforms.h"
#include "UDynamicMesh.h"
#if WITH_EDITOR
#include "Editor.h" // For GEditor
#include "Subsystems/AssetEditorSubsystem.h"
#endif
#include UE_INLINE_GENERATED_CPP_BY_NAME(GeometryCollectionMeshNodes)
DEFINE_LOG_CATEGORY_STATIC(LogGeometryCollectionMeshNodes, Warning, All);
namespace UE::Dataflow
{
void GeometryCollectionMeshNodes()
{
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FPointsToMeshDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FBoxToMeshDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FMeshInfoDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FMeshToCollectionDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FCollectionToMeshDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FStaticMeshToMeshDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FGetMeshBoundingBoxDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FMeshAppendDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FDataflowMeshAppendDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FMakeDataflowMeshDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FDuplicateMeshUVChannelNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FSplitDataflowMeshDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FSplitMeshIslandsDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FMeshCopyToPointsDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FGetMeshDataDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FApplyMeshProcessorToMeshDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FApplyMeshProcessorToGeometryCollectionDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FCollectionSelectionToMeshesDataflowNode);
DATAFLOW_NODE_REGISTER_CREATION_FACTORY(FAppendMeshesToCollectionDataflowNode);
UE_DATAFLOW_REGISTER_AUTOCONVERT(TObjectPtr<UStaticMesh>, TObjectPtr<UDynamicMesh>, FStaticMeshToMeshDataflowNode);
UE_DATAFLOW_REGISTER_AUTOCONVERT(FManagedArrayCollection, TObjectPtr<UDynamicMesh>, FCollectionToMeshDataflowNode);
UE_DATAFLOW_REGISTER_AUTOCONVERT(TObjectPtr<UDynamicMesh>, FManagedArrayCollection, FMeshToCollectionDataflowNode);
UE_DATAFLOW_REGISTER_AUTOCONVERT(TObjectPtr<UDynamicMesh>, FBox, FGetMeshBoundingBoxDataflowNode);
UE_DATAFLOW_REGISTER_AUTOCONVERT(FBox, TObjectPtr<UDynamicMesh>, FBoxToMeshDataflowNode);
}
}
void FPointsToMeshDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA<TObjectPtr<UDynamicMesh>>(&Mesh) || Out->IsA<int32>(&TriangleCount))
{
const TArray<FVector>& PointsArr = GetValue<TArray<FVector>>(Context, &Points);
if (PointsArr.Num() > 0)
{
TObjectPtr<UDynamicMesh> DynamicMesh = NewObject<UDynamicMesh>();
DynamicMesh->Reset();
UE::Geometry::FDynamicMesh3& DynMesh = DynamicMesh->GetMeshRef();
for (auto& Point : PointsArr)
{
DynMesh.AppendVertex(Point);
}
SetValue(Context, DynamicMesh, &Mesh);
SetValue(Context, DynamicMesh->GetTriangleCount(), &TriangleCount);
}
else
{
SetValue(Context, TObjectPtr<UDynamicMesh>(NewObject<UDynamicMesh>()), &Mesh);
SetValue(Context, 0, &TriangleCount);
}
}
}
void FBoxToMeshDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA<TObjectPtr<UDynamicMesh>>(&Mesh) || Out->IsA<int32>(&TriangleCount))
{
TObjectPtr<UDynamicMesh> NewMesh = NewObject<UDynamicMesh>();
NewMesh->Reset();
UE::Geometry::FDynamicMesh3& DynMesh = NewMesh->GetMeshRef();
FBox InBox = GetValue<FBox>(Context, &Box);
TArray<FVector3f> Vertices;
TArray<FIntVector> Triangles;
FFractureEngineUtility::ConvertBoxToVertexAndTriangleData(InBox, Vertices, Triangles);
FFractureEngineUtility::ConstructMesh(DynMesh, Vertices, Triangles);
SetValue(Context, NewMesh, &Mesh);
SetValue(Context, NewMesh->GetTriangleCount(), &TriangleCount);
}
}
void FMeshInfoDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA<FString>(&InfoString))
{
if (const TObjectPtr<const UDynamicMesh> InMesh = GetValue(Context, &Mesh))
{
const UE::Geometry::FDynamicMesh3& DynMesh = InMesh->GetMeshRef();
SetValue(Context, DynMesh.MeshInfoString(), &InfoString);
}
else
{
SetValue(Context, FString(""), &InfoString);
}
}
}
FMeshToCollectionDataflowNode::FMeshToCollectionDataflowNode(const UE::Dataflow::FNodeParameters& InParam, FGuid InGuid)
: FDataflowNode(InParam, InGuid)
{
RegisterInputConnection(&Mesh);
RegisterInputConnection(&bSplitIslands).SetCanHidePin(true).SetPinIsHidden(true);
RegisterInputConnection(&bAddClusterRootForSingleMesh).SetCanHidePin(true).SetPinIsHidden(true);
RegisterOutputConnection(&Collection);
}
void FMeshToCollectionDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
using namespace UE::Geometry;
if (Out->IsA<FManagedArrayCollection>(&Collection))
{
if (const TObjectPtr<const UDynamicMesh> InMesh = GetValue(Context, &Mesh))
{
const FDynamicMesh3& DynMesh = InMesh->GetMeshRef();
bool bInSplitIslands = GetValue(Context, &bSplitIslands);
bool bInAlwaysAddRoot = GetValue(Context, &bAddClusterRootForSingleMesh);
if (DynMesh.VertexCount() > 0)
{
FGeometryCollection NewGeometryCollection = FGeometryCollection();
FGeometryCollectionToDynamicMeshes Convert;
FGeometryCollectionToDynamicMeshes::FToCollectionOptions Options;
TArray<FDynamicMesh3> SplitMeshes;
if (bInSplitIslands)
{
FVertexConnectedComponents Components(DynMesh.MaxVertexID());
Components.ConnectTriangles(DynMesh);
if (bConnectIslandsByVertexOverlap)
{
Components.ConnectCloseVertices(DynMesh, ConnectVerticesThreshold, 2);
}
FDynamicMeshEditor::SplitMesh(&DynMesh, SplitMeshes, [&Components, &DynMesh](int32 TID)
{
return Components.GetComponent(DynMesh.GetTriangle(TID).A);
});
}
auto AddRoot = [](FGeometryCollection& ToCollection) -> int32
{
int32 Idx = ToCollection.AddElements(1, FGeometryCollection::TransformGroup);
ToCollection.Parent[Idx] = INDEX_NONE;
ToCollection.BoneColor[Idx] = FLinearColor::White;
return Idx;
};
if (SplitMeshes.Num() > 1)
{
Options.NewMeshParentIndex = AddRoot(NewGeometryCollection);
for (const FDynamicMesh3& SplitMesh : SplitMeshes)
{
Convert.AppendMeshToCollection(NewGeometryCollection, SplitMesh, FTransform::Identity, Options);
}
}
else
{
if (bInAlwaysAddRoot)
{
Options.NewMeshParentIndex = AddRoot(NewGeometryCollection);
}
else
{
Options.bAllowAppendAsRoot = true;
}
Convert.AppendMeshToCollection(NewGeometryCollection, DynMesh, FTransform::Identity, Options);
}
FManagedArrayCollection NewCollection = FManagedArrayCollection();
NewGeometryCollection.CopyTo(&NewCollection);
SetValue(Context, MoveTemp(NewCollection), &Collection);
return;
}
}
SetValue(Context, FManagedArrayCollection(), &Collection);
}
}
void FCollectionToMeshDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA(&Mesh))
{
TObjectPtr<UDynamicMesh> NewMesh = NewObject<UDynamicMesh>();
FDataflowTransformSelection InTransformSelection = GetValue(Context, &TransformSelection);
//
// If not connected select everything by default
//
if (!IsConnected(&TransformSelection))
{
const FManagedArrayCollection& InCollection = GetValue(Context, &Collection);
InTransformSelection.InitializeFromCollection(InCollection, true);
}
if (InTransformSelection.AnySelected())
{
const FManagedArrayCollection& InCollection = GetValue(Context, &Collection);
TArray<int32> TransformSelectionArray = InTransformSelection.AsArray();
GeometryCollection::Facades::FCollectionTransformSelectionFacade SelectionFacade(InCollection);
Chaos::Facades::FCollectionHierarchyFacade HierarchyFacade(InCollection);
TArray<int32> LeafSelectionArray = TransformSelectionArray;
SelectionFacade.ConvertSelectionToRigidNodes(LeafSelectionArray);
UE::Geometry::FGeometryCollectionToDynamicMeshes CollectionToMeshes;
UE::Geometry::FGeometryCollectionToDynamicMeshes::FToMeshOptions ToMeshOptions;
ToMeshOptions.bWeldVertices = bWeldVertices;
ToMeshOptions.bSaveIsolatedVertices = bPreserveIsolatedVertices;
if (CollectionToMeshes.InitFromTransformSelection(InCollection, LeafSelectionArray, ToMeshOptions)
&& !CollectionToMeshes.Meshes.IsEmpty())
{
NewMesh->EditMesh([this, &CollectionToMeshes](UE::Geometry::FDynamicMesh3& CombinedMesh)
{
CombinedMesh = MoveTemp(*CollectionToMeshes.Meshes[0].Mesh);
for (int32 MeshIdx = 1; MeshIdx < CollectionToMeshes.Meshes.Num(); ++MeshIdx)
{
CombinedMesh.AppendWithOffsets(*CollectionToMeshes.Meshes[MeshIdx].Mesh);
}
if (bCenterPivot)
{
MeshTransforms::Translate(CombinedMesh, -CombinedMesh.GetBounds().Center());
}
}
);
}
}
SetValue(Context, NewMesh, &Mesh);
}
}
void FStaticMeshToMeshDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
#if WITH_EDITORONLY_DATA
if (Out->IsA(&Mesh))
{
if (const UStaticMesh* const InStaticMesh = GetValue(Context, &StaticMesh))
{
if (const FMeshDescription* const MeshDescription = bUseHiRes ? InStaticMesh->GetHiResMeshDescription() : InStaticMesh->GetMeshDescription(LODLevel))
{
TObjectPtr<UDynamicMesh> NewMesh = NewObject<UDynamicMesh>();
NewMesh->Reset();
UE::Geometry::FDynamicMesh3& DynMesh = NewMesh->GetMeshRef();
{
FMeshDescriptionToDynamicMesh ConverterToDynamicMesh;
ConverterToDynamicMesh.Convert(MeshDescription, DynMesh);
}
SetValue(Context, NewMesh, &Mesh);
return;
}
}
SetValue(Context, TObjectPtr<UDynamicMesh>(NewObject<UDynamicMesh>()), &Mesh);
}
else if (Out->IsA(&MaterialArray))
{
// The dynamic mesh converter will set the MaterialIDs = PolyGroupID by default.
// Output materials to match this.
TArray<TObjectPtr<UMaterialInterface>> OutMaterials;
if (const UStaticMesh* const InStaticMesh = GetValue(Context, &StaticMesh))
{
const TArray<FStaticMaterial>& StaticMaterials = InStaticMesh->GetStaticMaterials();
if (const FMeshDescription* const MeshDescription = bUseHiRes ? InStaticMesh->GetHiResMeshDescription() : InStaticMesh->GetMeshDescription(LODLevel))
{
if (bUseHiRes)
{
const FStaticMeshConstAttributes MeshDescriptionAttributes(*MeshDescription);
TPolygonGroupAttributesConstRef<FName> MaterialSlotNames = MeshDescriptionAttributes.GetPolygonGroupMaterialSlotNames();
OutMaterials.Reserve(MaterialSlotNames.GetNumElements());
for (int32 PolyGroupID = 0; PolyGroupID < MaterialSlotNames.GetNumElements(); ++PolyGroupID)
{
const int32 MaterialIndex = InStaticMesh->GetMaterialIndexFromImportedMaterialSlotName(MaterialSlotNames[PolyGroupID]);
OutMaterials.Emplace(StaticMaterials.IsValidIndex(MaterialIndex) ? StaticMaterials[MaterialIndex].MaterialInterface : nullptr);
}
}
else
{
const FMeshSectionInfoMap& SectionMap = InStaticMesh->GetSectionInfoMap();
const int32 LODSectionNum = SectionMap.GetSectionNumber(LODLevel);
for (int32 SectionIndex = 0; SectionIndex < LODSectionNum; ++SectionIndex)
{
const int32 MaterialIndex = SectionMap.IsValidSection(LODLevel, SectionIndex) ? SectionMap.Get(LODLevel, SectionIndex).MaterialIndex : INDEX_NONE;
OutMaterials.Emplace(StaticMaterials.IsValidIndex(MaterialIndex) ? StaticMaterials[MaterialIndex].MaterialInterface : nullptr);
}
}
}
}
SetValue(Context, OutMaterials, &MaterialArray);
}
#endif
}
///////////////////////////////////////////////////////////////////////////////////////
FGetMeshBoundingBoxDataflowNode::FGetMeshBoundingBoxDataflowNode(const UE::Dataflow::FNodeParameters& InParam, FGuid InGuid)
: FDataflowNode(InParam, InGuid)
{
RegisterInputConnection(&Mesh);
RegisterOutputConnection(&BoundingBox);
RegisterOutputConnection(&Center);
RegisterOutputConnection(&Dimensions);
}
void FGetMeshBoundingBoxDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA(&BoundingBox) || Out->IsA(&Center) || Out->IsA(&Dimensions))
{
FBox OutBoundingBox(ForceInit);
if (const TObjectPtr<const UDynamicMesh> InMesh = GetValue(Context, &Mesh))
{
if (InMesh->GetMeshPtr())
{
const UE::Geometry::FAxisAlignedBox3d LocalBounds = InMesh->GetMeshPtr()->GetBounds();
OutBoundingBox.Min = LocalBounds.Min;
OutBoundingBox.Max = LocalBounds.Max;
}
}
SetValue(Context, OutBoundingBox, &BoundingBox);
SetValue(Context, OutBoundingBox.GetCenter(), &Center);
SetValue(Context, OutBoundingBox.GetSize(), &Dimensions);
}
}
///////////////////////////////////////////////////////////////////////////////////////
void FMeshAppendDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA<TObjectPtr<UDynamicMesh>>(&Mesh))
{
if (TObjectPtr<UDynamicMesh> InMesh1 = GetValue<TObjectPtr<UDynamicMesh>>(Context, &Mesh1))
{
if (TObjectPtr<UDynamicMesh> InMesh2 = GetValue<TObjectPtr<UDynamicMesh>>(Context, &Mesh2))
{
const UE::Geometry::FDynamicMesh3& DynMesh1 = InMesh1->GetMeshRef();
const UE::Geometry::FDynamicMesh3& DynMesh2 = InMesh2->GetMeshRef();
if (DynMesh1.VertexCount() > 0 || DynMesh2.VertexCount() > 0)
{
TObjectPtr<UDynamicMesh> NewMesh = NewObject<UDynamicMesh>();
NewMesh->Reset();
UE::Geometry::FDynamicMesh3& ResultDynMesh = NewMesh->GetMeshRef();
UE::Geometry::FDynamicMeshEditor MeshEditor(&ResultDynMesh);
UE::Geometry::FMeshIndexMappings IndexMaps1;
MeshEditor.AppendMesh(&DynMesh1, IndexMaps1);
UE::Geometry::FMeshIndexMappings IndexMaps2;
MeshEditor.AppendMesh(&DynMesh2, IndexMaps2);
SetValue(Context, NewMesh, &Mesh);
return;
}
}
}
SetValue(Context, TObjectPtr<UDynamicMesh>(NewObject<UDynamicMesh>()), &Mesh);
}
}
FDataflowMeshAppendDataflowNode::FDataflowMeshAppendDataflowNode(const UE::Dataflow::FNodeParameters& InParam, FGuid InGuid)
: FDataflowNode(InParam, InGuid)
{
RegisterInputConnection(&Mesh);
RegisterOutputConnection(&Mesh, &Mesh);
RegisterInputConnection(&AppendMesh);
}
void FDataflowMeshAppendDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA(&Mesh))
{
TObjectPtr<UDataflowMesh> NewMesh = NewObject<UDataflowMesh>();
if (const UDataflowMesh* const DataflowMesh1 = GetValue(Context, &Mesh))
{
if (const UDataflowMesh* const DataflowMesh2 = GetValue(Context, &AppendMesh))
{
if (const UE::Geometry::FDynamicMesh3* const DynamicMesh1 = DataflowMesh1->GetDynamicMesh())
{
if (const UE::Geometry::FDynamicMesh3* const DynamicMesh2 = DataflowMesh2->GetDynamicMesh())
{
if (DynamicMesh1->VertexCount() > 0 && DynamicMesh2->VertexCount() > 0)
{
UE::Geometry::FDynamicMesh3 ResultDynamicMesh;
UE::Geometry::FDynamicMeshEditor MeshEditor(&ResultDynamicMesh);
ResultDynamicMesh.EnableAttributes();
ResultDynamicMesh.Attributes()->EnableMaterialID();
UE::Geometry::FMeshIndexMappings IndexMaps1;
MeshEditor.AppendMesh(DynamicMesh1, IndexMaps1);
UE::Geometry::FMeshIndexMappings IndexMaps2;
MeshEditor.AppendMesh(DynamicMesh2, IndexMaps2);
// Reindex material IDs
if (DynamicMesh1->HasAttributes() && DynamicMesh1->Attributes()->HasMaterialID() && DynamicMesh2->HasAttributes() && DynamicMesh2->Attributes()->HasMaterialID())
{
const int32 MaterialIDOffset = DataflowMesh1->GetMaterials().Num();
for (const int32 Mesh2TriangleIndex : DynamicMesh2->TriangleIndicesItr())
{
int32 InputMaterialID;
DynamicMesh2->Attributes()->GetMaterialID()->GetValue(Mesh2TriangleIndex, &InputMaterialID);
const int32 NewTriangleIndex = IndexMaps2.GetNewTriangle(Mesh2TriangleIndex);
ResultDynamicMesh.Attributes()->GetMaterialID()->SetValue(NewTriangleIndex, MaterialIDOffset + InputMaterialID);
}
}
NewMesh->SetDynamicMesh(MoveTemp(ResultDynamicMesh));
}
else if (DynamicMesh1->VertexCount() > 0)
{
NewMesh->SetDynamicMesh(*DynamicMesh1);
}
else if (DynamicMesh2->VertexCount() > 0)
{
NewMesh->SetDynamicMesh(*DynamicMesh2);
}
}
} // end if DynamicMesh1
// Materials
NewMesh->AddMaterials(DataflowMesh1->GetMaterials());
NewMesh->AddMaterials(DataflowMesh2->GetMaterials());
}
}
SetValue(Context, NewMesh, &Mesh);
}
}
FMakeDataflowMeshDataflowNode::FMakeDataflowMeshDataflowNode(const UE::Dataflow::FNodeParameters& InParam, FGuid InGuid)
: FDataflowNode(InParam, InGuid)
{
RegisterInputConnection(&InMesh);
RegisterInputConnection(&InMaterials);
RegisterOutputConnection(&Mesh);
}
void FMakeDataflowMeshDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA(&Mesh))
{
TObjectPtr<UDataflowMesh> NewMesh = NewObject<UDataflowMesh>();
if (UDynamicMesh* const InUDynamicMesh = GetValue(Context, &InMesh))
{
InUDynamicMesh->ProcessMesh([NewMesh](const UE::Geometry::FDynamicMesh3& InFDynamicMesh)
{
NewMesh->SetDynamicMesh(InFDynamicMesh);
});
}
TArray<TObjectPtr<UMaterialInterface>> MaterialArray = GetValue(Context, &InMaterials);
NewMesh->SetMaterials(MoveTemp(MaterialArray));
SetValue(Context, NewMesh, &Mesh);
}
}
FSplitMeshIslandsDataflowNode::FSplitMeshIslandsDataflowNode(const UE::Dataflow::FNodeParameters& InParam, FGuid InGuid)
: FDataflowNode(InParam, InGuid)
{
RegisterInputConnection(&Mesh);
RegisterOutputConnection(&Meshes);
}
void FSplitMeshIslandsDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
using namespace UE::Geometry;
if (Out->IsA(&Meshes))
{
TArray<TObjectPtr<UDynamicMesh>> OutMeshes;
if (const TObjectPtr<UDynamicMesh> InMesh = GetValue(Context, &Mesh))
{
if (SplitMethod == EDataflowMeshSplitIslandsMethod::NoSplit)
{
OutMeshes.Add(InMesh);
}
else
{
InMesh->ProcessMesh([&OutMeshes, this](const FDynamicMesh3& ToSplit)
{
TArray<FDynamicMesh3> SplitMeshes;
FVertexConnectedComponents Components(ToSplit.MaxVertexID());
Components.ConnectTriangles(ToSplit);
if (SplitMethod == EDataflowMeshSplitIslandsMethod::ByVertexOverlap)
{
Components.ConnectCloseVertices(ToSplit, ConnectVerticesThreshold, 2);
}
FDynamicMeshEditor::SplitMesh(&ToSplit, SplitMeshes, [&Components, &ToSplit](int32 TID)
{
return Components.GetComponent(ToSplit.GetTriangle(TID).A);
});
OutMeshes.SetNum(SplitMeshes.Num());
for (int32 Idx = 0; Idx < SplitMeshes.Num(); ++Idx)
{
OutMeshes[Idx] = NewObject<UDynamicMesh>();
OutMeshes[Idx]->SetMesh(MoveTemp(SplitMeshes[Idx]));
}
});
}
}
SetValue(Context, OutMeshes, &Meshes);
}
}
FSplitDataflowMeshDataflowNode::FSplitDataflowMeshDataflowNode(const UE::Dataflow::FNodeParameters& InParam, FGuid InGuid)
: FDataflowNode(InParam, InGuid)
{
RegisterInputConnection(&InMesh);
RegisterOutputConnection(&Mesh);
RegisterOutputConnection(&MaterialArray);
}
void FSplitDataflowMeshDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
TObjectPtr<UDynamicMesh> NewMesh = NewObject<UDynamicMesh>();
TArray<TObjectPtr<UMaterialInterface>> Materials;
if (Out->IsA(&Mesh))
{
if (const UDataflowMesh* const InDataflowMesh = GetValue(Context, &InMesh))
{
NewMesh->SetMesh(InDataflowMesh->GetDynamicMeshRef());
}
SetValue(Context, NewMesh, &Mesh);
}
else if (Out->IsA(&MaterialArray))
{
if (UDataflowMesh* const InDataflowMesh = GetValue(Context, &InMesh))
{
Materials = InDataflowMesh->GetMaterials();
}
SetValue(Context, Materials, &MaterialArray);
}
}
FDuplicateMeshUVChannelNode::FDuplicateMeshUVChannelNode(const UE::Dataflow::FNodeParameters& InParam, FGuid InGuid)
: FDataflowNode(InParam, InGuid)
{
RegisterInputConnection(&Mesh);
RegisterOutputConnection(&Mesh, &Mesh);
RegisterOutputConnection(&NewUVChannel);
}
void FDuplicateMeshUVChannelNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
int32 NewUVLayerIndex = -1;
if (Out->IsA(&Mesh) || Out->IsA(&NewUVChannel))
{
if (TObjectPtr<UDataflowMesh> InMesh = GetValue(Context, &Mesh))
{
if (const FDynamicMesh3* const InDynamicMesh = InMesh->GetDynamicMesh())
{
if (InDynamicMesh->HasAttributes() && SourceUVChannel >= 0 && SourceUVChannel < InDynamicMesh->Attributes()->NumUVLayers())
{
UE::Geometry::FDynamicMesh3 OutDynamicMesh;
OutDynamicMesh.Copy(*InDynamicMesh);
OutDynamicMesh.EnableAttributes();
NewUVLayerIndex = OutDynamicMesh.Attributes()->NumUVLayers();
OutDynamicMesh.Attributes()->SetNumUVLayers(NewUVLayerIndex + 1);
const UE::Geometry::FDynamicMeshUVOverlay* const SourceUVLayer = OutDynamicMesh.Attributes()->GetUVLayer(SourceUVChannel);
OutDynamicMesh.Attributes()->GetUVLayer(NewUVLayerIndex)->Copy(*SourceUVLayer);
TObjectPtr<UDataflowMesh> OutMesh = NewObject<UDataflowMesh>();
OutMesh->SetDynamicMesh(MoveTemp(OutDynamicMesh));
OutMesh->SetMaterials(InMesh->GetMaterials());
SetValue(Context, OutMesh, &Mesh);
SetValue(Context, NewUVLayerIndex, &NewUVChannel);
return;
}
else
{
Context.Warning(TEXT("Invalid Source UV Channel or the Mesh does not have an AttributeSet"), this, Out);
}
}
else
{
Context.Warning(TEXT("Mesh is missing DynamicMesh object"), this, Out);
}
}
}
SafeForwardInput(Context, &Mesh, &Mesh);
SetValue(Context, NewUVLayerIndex, &NewUVChannel);
}
void FMeshCopyToPointsDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA(&Mesh))
{
if (TObjectPtr<UDynamicMesh> InMeshToCopy = GetValue(Context, &MeshToCopy))
{
TObjectPtr<UDynamicMesh> NewMesh = nullptr;
const UE::Geometry::FDynamicMesh3& InDynMeshToCopy = InMeshToCopy->GetMeshRef();
const TArray<FVector>& InPoints = GetValue(Context, &Points);
if (InPoints.Num() > 0 && InDynMeshToCopy.VertexCount() > 0)
{
NewMesh = NewObject<UDynamicMesh>();
NewMesh->Reset();
UE::Geometry::FDynamicMeshEditor MeshEditor(&NewMesh->GetMeshRef());
for (const FVector& Point : InPoints)
{
UE::Geometry::FDynamicMesh3 DynMeshTemp(InDynMeshToCopy);
UE::Geometry::FRefCountVector VertexRefCounts = DynMeshTemp.GetVerticesRefCounts();
UE::Geometry::FRefCountVector::IndexIterator ItVertexID = VertexRefCounts.BeginIndices();
const UE::Geometry::FRefCountVector::IndexIterator ItEndVertexID = VertexRefCounts.EndIndices();
while (ItVertexID != ItEndVertexID)
{
DynMeshTemp.SetVertex(*ItVertexID, Scale * DynMeshTemp.GetVertex(*ItVertexID) + Point);
++ItVertexID;
}
UE::Geometry::FMeshIndexMappings IndexMaps;
MeshEditor.AppendMesh(&DynMeshTemp, IndexMaps);
}
}
SetValue(Context, NewMesh, &Mesh);
return;
}
SetValue(Context, TObjectPtr<UDynamicMesh>(NewObject<UDynamicMesh>()), &Mesh);
}
else if (Out->IsA(&Meshes))
{
TArray<TObjectPtr<UDynamicMesh>> OutMeshes;
if (TObjectPtr<UDynamicMesh> InMeshToCopy = GetValue(Context, &MeshToCopy))
{
const UE::Geometry::FDynamicMesh3& InDynMeshToCopy = InMeshToCopy->GetMeshRef();
const TArray<FVector>& InPoints = GetValue(Context, &Points);
if (InPoints.Num() > 0 && InDynMeshToCopy.VertexCount() > 0)
{
for (const FVector& Point : InPoints)
{
TObjectPtr<UDynamicMesh> NewMesh = NewObject<UDynamicMesh>();
NewMesh->Reset();
OutMeshes.Add(NewMesh);
UE::Geometry::FDynamicMeshEditor MeshEditor(&NewMesh->GetMeshRef());
UE::Geometry::FDynamicMesh3 DynMeshTemp(InDynMeshToCopy);
UE::Geometry::FRefCountVector VertexRefCounts = DynMeshTemp.GetVerticesRefCounts();
UE::Geometry::FRefCountVector::IndexIterator ItVertexID = VertexRefCounts.BeginIndices();
const UE::Geometry::FRefCountVector::IndexIterator ItEndVertexID = VertexRefCounts.EndIndices();
while (ItVertexID != ItEndVertexID)
{
DynMeshTemp.SetVertex(*ItVertexID, Scale * DynMeshTemp.GetVertex(*ItVertexID) + Point);
++ItVertexID;
}
UE::Geometry::FMeshIndexMappings IndexMaps;
MeshEditor.AppendMesh(&DynMeshTemp, IndexMaps);
}
}
}
SetValue(Context, MoveTemp(OutMeshes), &Meshes);
return;
}
}
void FGetMeshDataDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA<int32>(&VertexCount))
{
if (const TObjectPtr<const UDynamicMesh> InMesh = GetValue<TObjectPtr<UDynamicMesh>>(Context, &Mesh))
{
SetValue(Context, InMesh->GetMeshRef().VertexCount(), &VertexCount);
}
else
{
SetValue(Context, 0, &VertexCount);
}
}
else if (Out->IsA<int32>(&EdgeCount))
{
if (const TObjectPtr<const UDynamicMesh> InMesh = GetValue<TObjectPtr<UDynamicMesh>>(Context, &Mesh))
{
SetValue(Context, InMesh->GetMeshRef().EdgeCount(), &EdgeCount);
}
else
{
SetValue(Context, 0, &EdgeCount);
}
}
else if (Out->IsA<int32>(&TriangleCount))
{
if (const TObjectPtr<const UDynamicMesh> InMesh = GetValue<TObjectPtr<UDynamicMesh>>(Context, &Mesh))
{
SetValue(Context, InMesh->GetMeshRef().TriangleCount(), &TriangleCount);
}
else
{
SetValue(Context, 0, &TriangleCount);
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
void FMeshProcessorDataflowNodeBase::PostSerialize(const FArchive& Ar)
{
Super::PostSerialize(Ar);
if (Ar.IsLoading())
{
TeardownBlueprintEvent();
SetupBlueprintEvent();
}
}
void FMeshProcessorDataflowNodeBase::OnPropertyChanged(UE::Dataflow::FContext& Context, const FPropertyChangedEvent& PropertyChangedEvent)
{
if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet
&& PropertyChangedEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(FMeshProcessorDataflowNodeBase, MeshProcessor))
{
TeardownBlueprintEvent();
if (MeshProcessor)
{
MeshProcessorInstance = NewObject<UDynamicMeshProcessorBlueprint>(OwningObject, MeshProcessor, NAME_None, RF_Transactional);
SetupBlueprintEvent();
}
else
{
MeshProcessorInstance = nullptr;
RefreshConnectionsFromBlueprint();
}
}
}
void FMeshProcessorDataflowNodeBase::SetupBlueprintEvent()
{
#if WITH_EDITOR
if (MeshProcessor)
{
if (UBlueprint* Blueprint = Cast<UBlueprint>(MeshProcessor->ClassGeneratedBy))
{
if (!ensure(!BlueprintChangeDelegateHandle.IsValid()))
{
TeardownBlueprintEvent();
}
BlueprintChangeDelegateHandle = Blueprint->OnChanged().AddLambda([this](UBlueprint* BP)
{
RefreshConnectionsFromBlueprint();
Invalidate();
});
}
}
RefreshConnectionsFromBlueprint();
#endif
}
void FMeshProcessorDataflowNodeBase::TeardownBlueprintEvent()
{
#if WITH_EDITOR
if (MeshProcessor && BlueprintChangeDelegateHandle.IsValid())
{
if (UBlueprint* Blueprint = Cast<UBlueprint>(MeshProcessor->ClassGeneratedBy))
{
Blueprint->OnChanged().Remove(BlueprintChangeDelegateHandle);
BlueprintChangeDelegateHandle.Reset();
}
}
#endif
}
void FMeshProcessorDataflowNodeBase::RefreshConnectionsFromBlueprint()
{
#if WITH_EDITOR
PropertyBag.Reset();
if (MeshProcessor)
{
if (UBlueprint* Blueprint = Cast<UBlueprint>(MeshProcessor->ClassGeneratedBy))
{
if (Blueprint->GeneratedClass != nullptr)
{
for (TFieldIterator<FProperty> PropertyIt(Blueprint->GeneratedClass, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt)
{
FProperty* Property = *PropertyIt;
if (!Property->HasAnyPropertyFlags(CPF_Parm) && Property->HasAllPropertyFlags(CPF_BlueprintVisible))
{
PropertyBag.AddProperty(Property->GetFName(), Property, false);
}
}
}
}
}
// Make sure the node is up to date with the serialized data
DynamicConnections.Refresh();
#endif
}
void FMeshProcessorDataflowNodeBase::ApplyParametersToBlueprintInstance(UE::Dataflow::FContext& Context) const
{
#if WITH_EDITOR
if (UBlueprint* Blueprint = Cast<UBlueprint>(MeshProcessor->ClassGeneratedBy))
{
if (Blueprint->GeneratedClass != nullptr && MeshProcessorInstance && PropertyBag.GetPropertyBagStruct())
{
TConstArrayView<FPropertyBagPropertyDesc> Descs = PropertyBag.GetPropertyBagStruct()->GetPropertyDescs();
for (const FPropertyBagPropertyDesc& Desc : Descs)
{
if (FProperty* BPProperty = Blueprint->GeneratedClass->FindPropertyByName(Desc.Name))
{
if (BPProperty->GetClass() == Desc.CachedProperty->GetClass())
{
if (const FDataflowInput* Input = FindInput(Desc.Name))
{
// only send the data is the input is connected, other wise keep what was there before
if (const FDataflowOutput* Output = Input->GetConnection())
{
Input->PullValue(Context);
if (Desc.ValueType == EPropertyBagPropertyType::Double)
{
// Special case : the internal storage type for Float BP variable is Double
// so we need to pass a pointer on a double but reading a float from Dataflow (Same applies for array type)
if (Desc.ContainerTypes.GetFirstContainerType() == EPropertyBagContainerType::Array)
{
const TArray<float> CacheValue = Context.GetData<TArray<float>>(Output->CacheKey(), Output->GetProperty(), {});
TArray<double> DoubleArrayValue;
Algo::Copy(CacheValue, DoubleArrayValue);
void* TargetAddress = BPProperty->ContainerPtrToValuePtr<void>(MeshProcessorInstance);
BPProperty->CopyCompleteValue(TargetAddress, &DoubleArrayValue);
}
else
{
const float CachedValue = Context.GetData<float>(Output->CacheKey(), Output->GetProperty(), 0.0f);
const double DoubleValue{ CachedValue };
void* TargetAddress = BPProperty->ContainerPtrToValuePtr<void>(MeshProcessorInstance);
BPProperty->CopyCompleteValue(TargetAddress, &DoubleValue);
}
}
else if (const void* CachedValueAddress = Context.GetUntypedData(Output->CacheKey(), nullptr))
{
void* TargetAddress = BPProperty->ContainerPtrToValuePtr<void>(MeshProcessorInstance);
BPProperty->CopyCompleteValue(TargetAddress, CachedValueAddress);
}
}
}
}
}
}
}
}
#else
UE_LOG(LogGeometryCollectionMeshNodes, Error, TEXT("FMeshProcessorDataflowNodeBase - trying to execute a mesh processing node ( running a Geometry script ) in a non-editor build - connected input parameters won't be passed to the script"));
#endif
}
#if WITH_EDITOR
void FMeshProcessorDataflowNodeBase::OnDoubleClicked() const
{
if (!MeshProcessor)
{
return;
}
if (UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>())
{
if (UBlueprint* Blueprint = Cast<UBlueprint>(MeshProcessor->ClassGeneratedBy))
{
AssetEditorSubsystem->OpenEditorForAssets({ Blueprint });
}
}
}
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////////////
void FApplyMeshProcessorToMeshDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA<TObjectPtr<UDynamicMesh>>(&Mesh))
{
if (TObjectPtr<const UDynamicMesh> InMesh = GetValue<TObjectPtr<UDynamicMesh>>(Context, &Mesh))
{
if (!MeshProcessorInstance)
{
SafeForwardInput(Context, &Mesh, &Mesh);
return;
}
// Creating a new mesh object from InMesh
TObjectPtr<UDynamicMesh> NewMesh = NewObject<UDynamicMesh>();
NewMesh->SetMesh(InMesh->GetMeshRef());
ApplyParametersToBlueprintInstance(Context);
bool bFailed = false;
MeshProcessorInstance->ProcessDynamicMesh(NewMesh, bFailed);
SetValue(Context, NewMesh, &Mesh);
}
else
{
SetValue(Context, TObjectPtr<UDynamicMesh>(NewObject<UDynamicMesh>()), &Mesh);
}
}
}
void FApplyMeshProcessorToGeometryCollectionDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA(&Collection) ||
Out->IsA(&TransformSelection))
{
if (!MeshProcessorInstance)
{
SafeForwardInput(Context, &Collection, &Collection);
return;
}
FDataflowTransformSelection InTransformSelection = GetValue(Context, &TransformSelection);
//
// If not connected select everything by default
//
if (!IsConnected(&TransformSelection))
{
const FManagedArrayCollection& InCollection = GetValue(Context, &Collection);
GeometryCollection::Facades::FCollectionTransformSelectionFacade TransformSelectionFacade(InCollection);
const TArray<int32>& SelectionArr = TransformSelectionFacade.SelectAll();
FDataflowTransformSelection NewTransformSelection;
NewTransformSelection.InitializeFromCollection(InCollection, false);
NewTransformSelection.SetFromArray(SelectionArr);
InTransformSelection = NewTransformSelection;
}
if (InTransformSelection.AnySelected())
{
const FManagedArrayCollection& InCollection = GetValue(Context, &Collection);
UE::Geometry::FGeometryCollectionToDynamicMeshes CollectionToMeshes;
UE::Geometry::FGeometryCollectionToDynamicMeshes::FToMeshOptions ToMeshOptions;
ToMeshOptions.bWeldVertices = bWeldVertices;
ToMeshOptions.bSaveIsolatedVertices = bPreserveIsolatedVertices;
if (CollectionToMeshes.InitFromTransformSelection(InCollection, InTransformSelection.AsArray(), ToMeshOptions)
&& !CollectionToMeshes.Meshes.IsEmpty())
{
// Temporarily create a UDynamicMesh as a container to hold the meshes we pass to BP
TObjectPtr<UDynamicMesh> NewMesh = NewObject<UDynamicMesh>();
bool bAnySuccess = false;
for (UE::Geometry::FGeometryCollectionToDynamicMeshes::FMeshInfo& MeshInfo : CollectionToMeshes.Meshes)
{
NewMesh->SetMesh(MoveTemp(*MeshInfo.Mesh));
ApplyParametersToBlueprintInstance(Context);
bool bFailed = false;
MeshProcessorInstance->ProcessDynamicMesh(NewMesh, bFailed);
if (!bFailed) // on success, move the mesh back
{
bAnySuccess = true;
NewMesh->EditMesh([&MeshInfo](UE::Geometry::FDynamicMesh3& Mesh)
{
*MeshInfo.Mesh = MoveTemp(Mesh);
}, EDynamicMeshChangeType::GeneralEdit, EDynamicMeshAttributeChangeFlags::Unknown, true);
}
else // on failure, clear the mesh so it won't be written back
{
MeshInfo.TransformIndex = -1;
MeshInfo.Mesh = nullptr;
}
}
if (bAnySuccess)
{
if (TUniquePtr<FGeometryCollection> GeomCollection = TUniquePtr<FGeometryCollection>(InCollection.NewCopy<FGeometryCollection>()))
{
UE::Geometry::FGeometryCollectionToDynamicMeshes::FToCollectionOptions ToCollectionOptions;
ToCollectionOptions.bDefaultFaceInternal = false;
ToCollectionOptions.bDefaultFaceVisible = true;
CollectionToMeshes.UpdateGeometryCollection(*GeomCollection, ToCollectionOptions);
SetValue<const FManagedArrayCollection&>(Context, *GeomCollection, &Collection);
return;
}
}
}
}
SafeForwardInput(Context, &Collection, &Collection);
}
}
FAppendMeshesToCollectionDataflowNode::FAppendMeshesToCollectionDataflowNode(const UE::Dataflow::FNodeParameters& InParam, FGuid InGuid)
: FDataflowNode(InParam, InGuid)
{
RegisterInputConnection(&Collection);
RegisterInputConnection(&Meshes);
RegisterInputConnection(&ParentIndex);
RegisterOutputConnection(&Collection, &Collection);
RegisterOutputConnection(&AddedSelection);
}
void FAppendMeshesToCollectionDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA<FManagedArrayCollection>(&Collection) ||
Out->IsA<FDataflowTransformSelection>(&AddedSelection))
{
if (!IsConnected(&Collection))
{
SafeForwardInput(Context, &Collection, &Collection);
return;
}
int32 UseParentIndex = GetValue(Context, &ParentIndex);
const FManagedArrayCollection& InCollection = GetValue(Context, &Collection);
const TArray<TObjectPtr<UDynamicMesh>> InMeshes = GetValue(Context, &Meshes);
FDataflowTransformSelection NewSelection;
if (TUniquePtr<FGeometryCollection> GeomCollection = TUniquePtr<FGeometryCollection>(InCollection.NewCopy<FGeometryCollection>()))
{
bool bModifiedCollection = false;
int32 FirstNewTransformIndex = INDEX_NONE;
for (const TObjectPtr<UDynamicMesh>& MeshObject : InMeshes)
{
if (MeshObject)
{
MeshObject->ProcessMesh([&GeomCollection, &bModifiedCollection, UseParentIndex, &FirstNewTransformIndex](const FDynamicMesh3& Mesh)
{
UE::Geometry::FGeometryCollectionToDynamicMeshes::FToCollectionOptions Options;
Options.NewMeshParentIndex = UseParentIndex;
int32 AddedIdx = UE::Geometry::FGeometryCollectionToDynamicMeshes::AppendMeshToCollection(*GeomCollection, Mesh, FTransform::Identity, Options);
if (AddedIdx != INDEX_NONE)
{
if (!bModifiedCollection)
{
FirstNewTransformIndex = AddedIdx;
}
bModifiedCollection = true;
}
});
}
}
if (bModifiedCollection)
{
NewSelection.InitializeFromCollection(*GeomCollection, false);
for (int32 Idx = FirstNewTransformIndex; Idx < NewSelection.Num(); ++Idx)
{
NewSelection.SetSelected(Idx);
}
SetValue<const FManagedArrayCollection&>(Context, MoveTemp(*GeomCollection), &Collection);
SetValue(Context, NewSelection, &AddedSelection);
return;
}
}
SafeForwardInput(Context, &Collection, &Collection);
SetValue(Context, NewSelection, &AddedSelection);
}
}
FCollectionSelectionToMeshesDataflowNode::FCollectionSelectionToMeshesDataflowNode(const UE::Dataflow::FNodeParameters& InParam, FGuid InGuid)
: FDataflowNode(InParam, InGuid)
{
RegisterInputConnection(&Collection);
RegisterInputConnection(&TransformSelection);
RegisterOutputConnection(&Meshes);
}
void FCollectionSelectionToMeshesDataflowNode::Evaluate(UE::Dataflow::FContext& Context, const FDataflowOutput* Out) const
{
if (Out->IsA(&Meshes))
{
TArray<TObjectPtr<UDynamicMesh>> NewMeshes;
FDataflowTransformSelection InTransformSelection = GetValue(Context, &TransformSelection);
//
// If not connected select everything by default
//
if (!IsConnected(&TransformSelection))
{
const FManagedArrayCollection& InCollection = GetValue(Context, &Collection);
InTransformSelection.InitializeFromCollection(InCollection, true);
}
if (InTransformSelection.AnySelected())
{
const FManagedArrayCollection& InCollection = GetValue(Context, &Collection);
TArray<int32> TransformSelectionArray = InTransformSelection.AsArray();
GeometryCollection::Facades::FCollectionTransformSelectionFacade SelectionFacade(InCollection);
Chaos::Facades::FCollectionHierarchyFacade HierarchyFacade(InCollection);
TArray<int32> LeafSelectionArray = TransformSelectionArray;
SelectionFacade.ConvertSelectionToRigidNodes(LeafSelectionArray);
UE::Geometry::FGeometryCollectionToDynamicMeshes CollectionToMeshes;
UE::Geometry::FGeometryCollectionToDynamicMeshes::FToMeshOptions ToMeshOptions;
ToMeshOptions.bWeldVertices = bWeldVertices;
ToMeshOptions.bSaveIsolatedVertices = bPreserveIsolatedVertices;
if (CollectionToMeshes.InitFromTransformSelection(InCollection, LeafSelectionArray, ToMeshOptions)
&& !CollectionToMeshes.Meshes.IsEmpty())
{
NewMeshes.Reserve(bConvertSelectionToLeaves ? CollectionToMeshes.Meshes.Num() : TransformSelectionArray.Num());
using FMeshInfo = UE::Geometry::FGeometryCollectionToDynamicMeshes::FMeshInfo;
if (bConvertSelectionToLeaves)
{
for (FMeshInfo& MeshInfo : CollectionToMeshes.Meshes)
{
TObjectPtr<UDynamicMesh>& NewMesh = NewMeshes.Add_GetRef(NewObject<UDynamicMesh>());
NewMesh->SetMesh(MoveTemp(*MeshInfo.Mesh));
}
}
else
{
TMap<int32, FMeshInfo*> BoneToMeshInfo;
TMap<int32, const FDynamicMesh3*> BoneToMesh;
for (FMeshInfo& MeshInfo : CollectionToMeshes.Meshes)
{
BoneToMeshInfo.Add(MeshInfo.TransformIndex, &MeshInfo);
}
for (int32 BoneIdx : TransformSelectionArray)
{
if (BoneToMeshInfo.Contains(BoneIdx))
{
TObjectPtr<UDynamicMesh>& NewMesh = NewMeshes.Add_GetRef(NewObject<UDynamicMesh>());
// move the mesh out of the collection and add the pointer to the BoneToMesh map instead
// (in case we also have to make a cluster node using the same mesh)
NewMesh->SetMesh(MoveTemp(*BoneToMeshInfo[BoneIdx]->Mesh));
BoneToMeshInfo.Remove(BoneIdx);
BoneToMesh.Add(BoneIdx, NewMesh->GetMeshPtr());
}
else
{
TObjectPtr<UDynamicMesh>& NewMesh = NewMeshes.Add_GetRef(NewObject<UDynamicMesh>());
FDynamicMesh3& Mesh = NewMesh->GetMeshRef();
UE::Geometry::FDynamicMeshEditor Editor(&Mesh);
TArray<int32> SearchBones;
SearchBones.Add(BoneIdx);
while (!SearchBones.IsEmpty())
{
int32 SearchBoneIdx = SearchBones.Pop(EAllowShrinking::No);
const FDynamicMesh3* FoundMesh = nullptr;
if (BoneToMeshInfo.Contains(SearchBoneIdx))
{
FoundMesh = BoneToMeshInfo[SearchBoneIdx]->Mesh.Get();
}
else if (BoneToMesh.Contains(SearchBoneIdx))
{
FoundMesh = BoneToMesh[SearchBoneIdx];
}
if (FoundMesh)
{
Mesh.EnableMatchingAttributes(*FoundMesh);
UE::Geometry::FMeshIndexMappings Unused;
Editor.AppendMesh(FoundMesh, Unused);
}
else
{
// No mesh for this bone; search the children for meshes
const TSet<int32>* Children = HierarchyFacade.FindChildren(SearchBoneIdx);
if (Children)
{
SearchBones.Append(Children->Array());
}
}
}
// add the built mesh to the map, in case we want to build a parent of it later
BoneToMesh.Add(BoneIdx, &Mesh);
}
}
}
}
}
SetValue(Context, NewMeshes, &Meshes);
}
}