Files
UnrealEngine/Engine/Plugins/Interchange/Runtime/Source/Pipelines/Private/InterchangeGenericStaticMeshPipeline.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

1167 lines
46 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "InterchangeGenericMeshPipeline.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "Async/Async.h"
#include "CoreMinimal.h"
#include "InterchangeMaterialFactoryNode.h"
#include "InterchangeMeshLODContainerNode.h"
#include "InterchangeMeshNode.h"
#include "InterchangePipelineLog.h"
#include "InterchangePipelineMeshesUtilities.h"
#include "InterchangeSceneComponentNodes.h"
#include "InterchangeSceneNode.h"
#include "InterchangeStaticMeshFactoryNode.h"
#include "InterchangeStaticMeshLodDataNode.h"
#include "Mesh/InterchangeMeshHelper.h"
#include "Nodes/InterchangeBaseNode.h"
#include "Nodes/InterchangeBaseNodeContainer.h"
#include "Nodes/InterchangeSourceNode.h"
#include "Nodes/InterchangeUserDefinedAttribute.h"
#include "Async/Async.h"
#include "CoreMinimal.h"
#include "Tasks/Task.h"
#include "UObject/Object.h"
#include "UObject/ObjectMacros.h"
namespace UE::Interchange::Private
{
FString GetNodeName(TObjectPtr<UInterchangePipelineMeshesUtilities> PipelineMeshesUtilities, const UInterchangeBaseNodeContainer& NodeContainer, const FString& NodeUid)
{
const UInterchangeBaseNode* BaseNode = NodeContainer.GetNode(NodeUid);
if (BaseNode)
{
FString NodeName = BaseNode->GetDisplayLabel();
if (const UInterchangeSceneNode* SceneNode = Cast<UInterchangeSceneNode>(BaseNode))
{
if (SceneNode->IsSpecializedTypeContains(UE::Interchange::FSceneNodeStaticData::GetLodGroupSpecializeTypeString()))
{
TArray<FString> LodGroupChildrens = NodeContainer.GetNodeChildrenUids(SceneNode->GetUniqueID());
if (LodGroupChildrens.Num() > 0)
{
return GetNodeName(PipelineMeshesUtilities, NodeContainer, LodGroupChildrens[0]);
}
}
}
else if (const UInterchangeMeshNode* MeshNode = Cast<UInterchangeMeshNode>(BaseNode))
{
//If this mesh is reference by only one scene node that do not have any children, use the scene node display label
const FInterchangeMeshGeometry& MeshGeometry = PipelineMeshesUtilities->GetMeshGeometryByUid(NodeUid);
if (MeshGeometry.ReferencingMeshInstanceUids.Num() == 1
&& NodeContainer.GetNodeChildrenCount(MeshGeometry.ReferencingMeshInstanceUids[0]) == 0)
{
if(const UInterchangeBaseNode* InstanceMeshNode = NodeContainer.GetNode(MeshGeometry.ReferencingMeshInstanceUids[0]))
{
return InstanceMeshNode->GetDisplayLabel();
}
}
}
return NodeName;
}
else
{
return FString();
}
}
}
static TTuple<EInterchangeMeshCollision, FString> GetCollisionMeshType(
TObjectPtr<UInterchangePipelineMeshesUtilities> PipelineMeshesUtilities,
const UInterchangeBaseNodeContainer& NodeContainer,
const FString& NodeUid,
const TArray<FString>& AllNodeUids,
bool bImportCollisionAccordingToMeshName
)
{
EInterchangeMeshCollision CollisionType = EInterchangeMeshCollision::None;
// Try to find the mesh node we're actually talking about
const UInterchangeBaseNode* BaseNode = NodeContainer.GetNode(NodeUid);
const UInterchangeMeshNode* MeshNode = Cast<const UInterchangeMeshNode>(BaseNode);
if (!MeshNode)
{
if (const UInterchangeSceneNode* SceneNode = Cast<UInterchangeSceneNode>(BaseNode))
{
FString MeshDependency;
if (SceneNode->GetCustomAssetInstanceUid(MeshDependency))
{
MeshNode = Cast<const UInterchangeMeshNode>(NodeContainer.GetNode(MeshDependency));
}
}
}
// If we have an explicit collision type on the mesh, we know the mesh node is a collision mesh itself
if (MeshNode)
{
if (MeshNode->GetCustomCollisionType(CollisionType) && CollisionType != EInterchangeMeshCollision::None)
{
return {CollisionType, NodeUid};
}
}
if (bImportCollisionAccordingToMeshName)
{
FString MeshName = UE::Interchange::Private::GetNodeName(PipelineMeshesUtilities, NodeContainer, NodeUid);
// Determine if the mesh name is a potential collision mesh
if (MeshName.StartsWith(TEXT("UBX_")))
{
CollisionType = EInterchangeMeshCollision::Box;
}
else if (MeshName.StartsWith(TEXT("UCX_")) || MeshName.StartsWith(TEXT("MCDCX_")))
{
CollisionType = EInterchangeMeshCollision::Convex18DOP;
}
else if (MeshName.StartsWith(TEXT("USP_")))
{
CollisionType = EInterchangeMeshCollision::Sphere;
}
else if (MeshName.StartsWith(TEXT("UCP_")))
{
CollisionType = EInterchangeMeshCollision::Capsule;
}
else
{
return { EInterchangeMeshCollision::None, FString() };
}
// We have a mesh name with a collision type suffix.
// However it should only be treated as a collision mesh if its body name corresponds to one of the other meshes.
// If we get here, we know there is at least one underscore, so we never expect either of these character searches to fail.
int32 FirstUnderscore = INDEX_NONE;
verify(MeshName.FindChar(TEXT('_'), FirstUnderscore));
int32 LastUnderscore = INDEX_NONE;
verify(MeshName.FindLastChar(TEXT('_'), LastUnderscore));
auto MatchPredicate = [&PipelineMeshesUtilities , &NodeContainer](FString Body)
{
// Generate a predicate to be used by the below Finds.
return [Body, &PipelineMeshesUtilities, &NodeContainer](const FString& ToCompare) { return Body == UE::Interchange::Private::GetNodeName(PipelineMeshesUtilities, NodeContainer, ToCompare); };
};
// If we find a mesh named the same as the collision mesh (following the collision prefix), we have a match
// e.g. this will match 'UBX_House' with a mesh called 'House'
{
const FString RenderMeshName = MeshName.RightChop(FirstUnderscore + 1);
const FString* CorrespondingMeshUid = AllNodeUids.FindByPredicate(MatchPredicate(RenderMeshName));
if (CorrespondingMeshUid)
{
return { CollisionType, *CorrespondingMeshUid };
}
}
// Otherwise strip the final underscore suffix from the collision mesh name and look again
// e.g. this will match 'UBX_House_01' with a mesh called 'House'
if (FirstUnderscore != LastUnderscore)
{
const FString RenderMeshName = MeshName.Mid(FirstUnderscore + 1, LastUnderscore - FirstUnderscore - 1);
const FString* CorrespondingMeshUid = AllNodeUids.FindByPredicate(MatchPredicate(RenderMeshName));
if (CorrespondingMeshUid)
{
return { CollisionType, *CorrespondingMeshUid };
}
}
// It is possible our target mesh has been renamed away by a name sanitizing process (see UE-248981 and
// FFbxParser::EnsureNodeNameAreValid). Since it is unusual to have a prefixed collision mesh and no target, let's
// assume we actually have a render mesh somewhere and try stripping any nameclash-looking suffixes from mesh names
// to see if we have a match
{
// Like above start by just stripping the prefix (e.g. 'UBX_House_01' --> 'House_01')
FString RenderMeshName = MeshName.RightChop(FirstUnderscore + 1);
auto NameCollisionSearchPredicate = [&RenderMeshName, &PipelineMeshesUtilities, &NodeContainer](const FString& Other)
{
FString MeshName = UE::Interchange::Private::GetNodeName(PipelineMeshesUtilities, NodeContainer, Other);
if (!MeshName.StartsWith(RenderMeshName))
{
return false;
}
FString MeshNameSuffix = MeshName.RightChop(RenderMeshName.Len());
// Check if everything after the render mesh name is just some combination of "ncl" (nameclash), numbers and/or underscores
MeshNameSuffix = MeshNameSuffix.Replace(TEXT("ncl"), TEXT(""));
FString LastChar = MeshNameSuffix.Right(1);
while ((LastChar.IsNumeric() || LastChar == TEXT("_")) && MeshNameSuffix.Len() > 0)
{
MeshNameSuffix.LeftChopInline(1, EAllowShrinking::No);
LastChar = MeshNameSuffix.Right(1);
}
// If there's nothing left, everything after the RenderMeshName was nameclash stuff, so this is likely our mesh
return MeshNameSuffix.Len() == 0;
};
const FString* CorrespondingMeshUid = AllNodeUids.FindByPredicate(NameCollisionSearchPredicate);
if (CorrespondingMeshUid)
{
return {CollisionType, *CorrespondingMeshUid};
}
// If we still have nothing, try also stripping numbered suffixes from the render mesh name and repeating the search
// (e.g. 'UBX_House_01' --> 'House')
RenderMeshName = MeshName.Mid(FirstUnderscore + 1, LastUnderscore - FirstUnderscore - 1);
CorrespondingMeshUid = AllNodeUids.FindByPredicate(NameCollisionSearchPredicate);
if (CorrespondingMeshUid)
{
return {CollisionType, *CorrespondingMeshUid};
}
}
}
return { EInterchangeMeshCollision::None, FString() };
}
// Returns true if MeshUid describes a Mesh node that is purely the collision mesh of some other mesh
static bool IsJustCollisionMesh(TObjectPtr<UInterchangePipelineMeshesUtilities> PipelineMeshesUtilities, const UInterchangeBaseNodeContainer& NodeContainer, const FString& MeshUid, const TArray<FString>& MeshUids, bool bImportCollisionAccordingToMeshName)
{
TTuple<EInterchangeMeshCollision, FString> Tuple = GetCollisionMeshType(PipelineMeshesUtilities, NodeContainer, MeshUid, MeshUids, bImportCollisionAccordingToMeshName);
// If MeshUid is a collision mesh but *of itself*, then it is both a collision and a render mesh, and we should return false
return Tuple.Key != EInterchangeMeshCollision::None && Tuple.Value != MeshUid;
}
static void BuildMeshToCollisionMeshMap(TObjectPtr<UInterchangePipelineMeshesUtilities> PipelineMeshesUtilities, const UInterchangeBaseNodeContainer& NodeContainer, const TArray<FString>& MeshUids, TMap<FString, TArray<FString>>& MeshToCollisionMeshMap, bool bImportCollisionAccordingToMeshName)
{
for (const FString& MeshUid : MeshUids)
{
TTuple<EInterchangeMeshCollision, FString> CollisionType = GetCollisionMeshType(PipelineMeshesUtilities, NodeContainer, MeshUid, MeshUids, bImportCollisionAccordingToMeshName);
if (CollisionType.Get<0>() != EInterchangeMeshCollision::None)
{
MeshToCollisionMeshMap.FindOrAdd(FString(CollisionType.Get<1>())).Emplace(MeshUid);
}
}
}
void UInterchangeGenericMeshPipeline::ExecutePreImportPipelineStaticMesh()
{
check(CommonMeshesProperties.IsValid());
#if WITH_EDITOR
//Make sure the generic pipeline will cover all staticmesh build settings when we import
Async(EAsyncExecution::TaskGraphMainThread, []()
{
static bool bVerifyBuildProperties = false;
if (!bVerifyBuildProperties)
{
bVerifyBuildProperties = true;
TArray<const UClass*> Classes;
Classes.Add(UInterchangeGenericCommonMeshesProperties::StaticClass());
Classes.Add(UInterchangeGenericMeshPipeline::StaticClass());
if (!DoClassesIncludeAllEditableStructProperties(Classes, FMeshBuildSettings::StaticStruct()))
{
UE_LOG(LogInterchangePipeline, Log, TEXT("UInterchangeGenericMeshPipeline: The generic pipeline does not cover all static mesh build options."));
}
}
});
#endif
if (UInterchangeSourceNode* SourceNode = UInterchangeSourceNode::FindOrCreateUniqueInstance(BaseNodeContainer))
{
// Keep a single triangle threshold for the entire scene
SourceNode->SetCustomNaniteTriangleThreshold(NaniteTriangleThreshold);
}
if (bImportStaticMeshes && (CommonMeshesProperties->ForceAllMeshAsType == EInterchangeForceMeshType::IFMT_None || CommonMeshesProperties->ForceAllMeshAsType == EInterchangeForceMeshType::IFMT_StaticMesh))
{
if (bCombineStaticMeshes)
{
// Combine all the static meshes
bool bFoundMeshes = false;
{
// If baking transforms, get all the static mesh instance nodes, and group them by LOD
TArray<FString> MeshUids;
PipelineMeshesUtilities->GetAllStaticMeshInstance(MeshUids);
TMap<int32, TArray<FString>> MeshUidsPerLodIndex;
for (const FString& MeshUid : MeshUids)
{
const FInterchangeMeshInstance& MeshInstance = PipelineMeshesUtilities->GetMeshInstanceByUid(MeshUid);
for (const auto& LodIndexAndSceneNodeContainer : MeshInstance.SceneNodePerLodIndex)
{
const int32 LodIndex = LodIndexAndSceneNodeContainer.Key;
const FInterchangeLodSceneNodeContainer& SceneNodeContainer = LodIndexAndSceneNodeContainer.Value;
TArray<FString>& TranslatedNodes = MeshUidsPerLodIndex.FindOrAdd(LodIndex);
for (const UInterchangeBaseNode* BaseNode : SceneNodeContainer.BaseNodes)
{
TranslatedNodes.Add(BaseNode->GetUniqueID());
}
}
}
//We must add all nodes that are not part of any lod to all lods upper then 0
if (MeshUidsPerLodIndex.Num() > 1)
{
constexpr int32 LodIndexZero = 0;
for (const FString& MeshUid : MeshUids)
{
const FInterchangeMeshInstance& MeshInstance = PipelineMeshesUtilities->GetMeshInstanceByUid(MeshUid);
if (MeshInstance.LodGroupNode == nullptr
&& MeshInstance.SceneNodePerLodIndex.Num() == 1
&& MeshInstance.SceneNodePerLodIndex.Contains(LodIndexZero)
&& MeshInstance.SceneNodePerLodIndex[LodIndexZero].BaseNodes.Num() == 1)
{
const UInterchangeBaseNode* BaseNode = MeshInstance.SceneNodePerLodIndex[LodIndexZero].BaseNodes[0];
//This mesh must be added to all existing LODs over 0
for (TPair<int32, TArray<FString>>& MeshUidsPerLodIndexPair : MeshUidsPerLodIndex)
{
if (MeshUidsPerLodIndexPair.Key > LodIndexZero)
{
TArray<FString>& TranslatedNodes = MeshUidsPerLodIndexPair.Value;
TranslatedNodes.AddUnique(BaseNode->GetUniqueID());
}
}
}
}
}
// If we got some instances, create a static mesh factory node
if (MeshUidsPerLodIndex.Num() > 0)
{
UInterchangeStaticMeshFactoryNode* StaticMeshFactoryNode = CreateStaticMeshFactoryNode(MeshUidsPerLodIndex);
StaticMeshFactoryNodes.Add(StaticMeshFactoryNode);
bFoundMeshes = true;
}
}
if (!bFoundMeshes)
{
// If we haven't yet managed to build a factory node, look at static mesh geometry directly.
TArray<FString> MeshUids;
PipelineMeshesUtilities->GetAllStaticMeshGeometry(MeshUids);
TMap<int32, TArray<FString>> MeshUidsPerLodIndex;
for (const FString& MeshUid : MeshUids)
{
// MeshGeometry cannot have Lod since LODs are defined in the scene node
const FInterchangeMeshGeometry& MeshGeometry = PipelineMeshesUtilities->GetMeshGeometryByUid(MeshUid);
const int32 LodIndex = 0;
TArray<FString>& TranslatedNodes = MeshUidsPerLodIndex.FindOrAdd(LodIndex);
TranslatedNodes.Add(MeshGeometry.MeshUid);
}
if (MeshUidsPerLodIndex.Num() > 0)
{
UInterchangeStaticMeshFactoryNode* StaticMeshFactoryNode = CreateStaticMeshFactoryNode(MeshUidsPerLodIndex);
StaticMeshFactoryNodes.Add(StaticMeshFactoryNode);
}
}
}
else
{
// Do not combine static meshes
auto CreateMeshFactoryNodeUnCombined = [this](const TArray<FString>& MeshUids, const bool bInstancedMesh)->bool
{
constexpr int32 LodIndexZero = 0;
bool bFoundMeshes = false;
// Work out which meshes are collision meshes which correspond to another mesh
TMap<FString, TArray<FString>> MeshToCollisionMeshMap;
BuildMeshToCollisionMeshMap(PipelineMeshesUtilities, *BaseNodeContainer, MeshUids, MeshToCollisionMeshMap, bImportCollisionAccordingToMeshName);
// Now iterate through each mesh UID, creating a new factory for each one
for (const FString& MeshUid : MeshUids)
{
// If this is just a collision mesh, don't add a factory node; it will be added as part of another factory node
if (IsJustCollisionMesh(PipelineMeshesUtilities, *BaseNodeContainer, MeshUid, MeshUids, bImportCollisionAccordingToMeshName))
{
continue;
}
TMap<int32, TArray<FString>> MeshUidsPerLodIndex;
if (bInstancedMesh)
{
//Instanced geometry can have lods
const FInterchangeMeshInstance& MeshInstance = PipelineMeshesUtilities->GetMeshInstanceByUid(MeshUid);
for (const auto& LodIndexAndSceneNodeContainer : MeshInstance.SceneNodePerLodIndex)
{
const int32 LodIndex = LodIndexAndSceneNodeContainer.Key;
const FInterchangeLodSceneNodeContainer& SceneNodeContainer = LodIndexAndSceneNodeContainer.Value;
TArray<FString>& TranslatedNodes = MeshUidsPerLodIndex.FindOrAdd(LodIndex);
for (const UInterchangeBaseNode* BaseNode : SceneNodeContainer.BaseNodes)
{
TranslatedNodes.Add(BaseNode->GetUniqueID());
}
}
}
else
{
// #interchange_lod_rework
//Original presumption was: 'Non instanced geometry cannot have lods', which is not quite correct,
// will have to fix when refactoring LOD handling.
const FInterchangeMeshGeometry& MeshGeometry = PipelineMeshesUtilities->GetMeshGeometryByUid(MeshUid);
TArray<FString>& TranslatedNodes = MeshUidsPerLodIndex.FindOrAdd(LodIndexZero);
TranslatedNodes.Add(MeshGeometry.MeshUid);
}
if (MeshUidsPerLodIndex.Num() > 0)
{
if (bCollision)
{
if (const TArray<FString>* CorrespondingCollisionMeshes = MeshToCollisionMeshMap.Find(MeshUid))
{
TArray<FString>& TranslatedNodes = MeshUidsPerLodIndex.FindOrAdd(LodIndexZero);
for (const FString& CollisionMesh : *CorrespondingCollisionMeshes)
{
// AddUnique here because now we support meshes being both collision AND render meshes at the
// same time we may already have these entries in MeshUidsPerLodIndex
TranslatedNodes.AddUnique(CollisionMesh);
}
}
}
if (UInterchangeStaticMeshFactoryNode* StaticMeshFactoryNode = CreateStaticMeshFactoryNode(MeshUidsPerLodIndex, MeshUid))
{
StaticMeshFactoryNodes.Add(StaticMeshFactoryNode);
UpdateAssemblyPartDependencyTable(StaticMeshFactoryNode, MeshUidsPerLodIndex);
bFoundMeshes = true;
}
}
}
return bFoundMeshes;
};
TArray<FString> MeshUids;
PipelineMeshesUtilities->GetAllStaticMeshInstance(MeshUids);
if (!CreateMeshFactoryNodeUnCombined(MeshUids, true/*bInstancedMesh*/))
{
MeshUids.Reset();
PipelineMeshesUtilities->GetAllStaticMeshGeometry(MeshUids);
CreateMeshFactoryNodeUnCombined(MeshUids, false/*bInstancedMesh*/);
}
}
}
CreateAssemblyPartDependencies();
}
namespace UE::InterchangeStaticMeshFactory::Private
{
void ApplyNaniteTriangleThreshold(
const UInterchangeStaticMeshFactoryNode* FactoryNode,
UStaticMesh* StaticMesh,
const UInterchangeBaseNodeContainer* NodeContainer
)
{
#if WITH_EDITORONLY_DATA
if (!FactoryNode || !NodeContainer || !StaticMesh || !StaticMesh->GetNaniteSettings().bEnabled)
{
return;
}
int64 TriangleThreshold = 0;
if (const UInterchangeSourceNode* SourceNode = UInterchangeSourceNode::GetUniqueInstance(NodeContainer))
{
SourceNode->GetCustomNaniteTriangleThreshold(TriangleThreshold);
}
if (TriangleThreshold <= 0)
{
return;
}
const int32 LodIndex = 0;
FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(LodIndex);
if (!MeshDescription)
{
return;
}
if (MeshDescription->Triangles().Num() < TriangleThreshold)
{
StaticMesh->GetNaniteSettings().bEnabled = false;
}
#endif // #if WITH_EDITORONLY_DATA
}
} // namespace UE::InterchangeStaticMeshFactory::Private
void UInterchangeGenericMeshPipeline::ExecutePostFactoryPipelineStaticMesh(
const UInterchangeBaseNodeContainer* InBaseNodeContainer,
const FString& NodeKey,
UObject* CreatedAsset,
bool bIsAReimport
)
{
UStaticMesh* StaticMesh = Cast<UStaticMesh>(CreatedAsset);
if (!StaticMesh)
{
return;
}
UInterchangeStaticMeshFactoryNode* StaticMeshFactoryNode = Cast<UInterchangeStaticMeshFactoryNode>(InBaseNodeContainer->GetFactoryNode(NodeKey));
if (!StaticMeshFactoryNode)
{
return;
}
// If we're reimporting and being told to leave the properties alone, just early out instead
if (bIsAReimport)
{
EReimportStrategyFlags Strategy = StaticMeshFactoryNode->GetReimportStrategyFlags();
if (Strategy == EReimportStrategyFlags::ApplyNoProperties)
{
return;
}
}
UE::InterchangeStaticMeshFactory::Private::ApplyNaniteTriangleThreshold(StaticMeshFactoryNode, StaticMesh, InBaseNodeContainer);
}
bool UInterchangeGenericMeshPipeline::MakeMeshFactoryNodeUidAndDisplayLabel(const TMap<int32, TArray<FString>>& MeshUidsPerLodIndex, int32 LodIndex, FString& NewNodeUid, FString& DisplayLabel, const FString& BaseMeshUid)
{
int32 SceneNodeCount = 0;
if (!ensure(LodIndex >= 0 && MeshUidsPerLodIndex.Num() > LodIndex))
{
return false;
}
auto SetUidLabel = [&NewNodeUid, &DisplayLabel](const UInterchangeBaseNode* BaseNode)
{
NewNodeUid = BaseNode->GetUniqueID();
DisplayLabel = BaseNode->GetDisplayLabel();
};
auto SetUidLabelFromSceneNode = [&NewNodeUid, &DisplayLabel](const UInterchangeSceneNode* SceneNode, bool& bIsLodGroup)
{
bIsLodGroup = SceneNode->IsSpecializedTypeContains(UE::Interchange::FSceneNodeStaticData::GetLodGroupSpecializeTypeString());
//We can't have the ScenNode's id become a FactoryNodeUID as is, so we are adding a \\Mesh\\ prefix.
NewNodeUid = (bIsLodGroup ? TEXT("\\MeshLODGroupInstance\\") : TEXT("\\MeshInstance\\")) + SceneNode->GetUniqueID();
DisplayLabel = SceneNode->GetDisplayLabel();
};
if (!BaseMeshUid.IsEmpty())
{
//BaseMeshUid represents either:
// - UInterchangeSceneNode's UID that is a LOD Group.
// - UInterchangeSceneNode's UID that is instantiating a UInterchangeMeshNode. (via CustomAssetInstanceUid).
// - UInterchangeMeshNode's UID.
// - UInterchangeMeshLODContainerNode's UID.
// - in case of bCombineStaticMeshes == true this ID should be empty for now.
const UInterchangeBaseNode* BaseNode = BaseNodeContainer->GetNode(BaseMeshUid);
if (BaseNode)
{
if (const UInterchangeSceneNode* SceneNode = Cast<const UInterchangeSceneNode>(BaseNode))
{
//Regardless of the SceneNode being a LOD Group, or just instantiating a Mesh,
// if it was marked as the baseMeshUid we should use it's UID and Name:
bool bIsLodGroup = false;
SetUidLabelFromSceneNode(SceneNode, bIsLodGroup);
if (!CommonMeshesProperties->bBakeMeshes && !bIsLodGroup)
{
//We recursively check the AssetInstanceUid's chain to make sure we find LOD Group or MeshUID.
FString AssetInstanceUid;
while (SceneNode && SceneNode->GetCustomAssetInstanceUid(AssetInstanceUid))
{
const UInterchangeBaseNode* AssetInstanceNode = BaseNodeContainer->GetNode(AssetInstanceUid);
SceneNode = Cast<UInterchangeSceneNode>(AssetInstanceNode); //if its a sceneNode without a LOD group specialization we continue the search
if (SceneNode && SceneNode->IsSpecializedTypeContains(UE::Interchange::FSceneNodeStaticData::GetLodGroupSpecializeTypeString()))
{
//if it is a LOD Group specialization then we use the original NewNodeUid. (as then we want the 'instantiation of the LOD Group)
NewNodeUid = TEXT("\\MeshLODGroupInstance\\") + BaseMeshUid;
return true;
}
if (const UInterchangeMeshNode* MeshNode = Cast<UInterchangeMeshNode>(AssetInstanceNode))
{
//if it is a Mesh Node then we confirmed that it is an instanced Mesh
// but since we are not baking meshes we want the MeshNode
//It cannot be part of a LOD group as that would have broken out sooner due to the LodGroup check.
//For this case we are using MeshNode's UID and DisplayName.
SetUidLabel(AssetInstanceNode);
return true;
}
}
}
else
{
return true;
}
}
else if (const UInterchangeMeshLODContainerNode* LODContainerNode = Cast<const UInterchangeMeshLODContainerNode>(BaseNode))
{
SetUidLabel(BaseNode);
return true;
}
else if (const UInterchangeMeshNode* MeshNode = Cast<const UInterchangeMeshNode>(BaseNode))
{
SetUidLabel(BaseNode);
return true;
}
}
}
//If BaseMeshUID is not provided and or BaseMeshUID is not a LOD Group, fallback onto LOD=0 UID[0] to generate UID and Name.
for (const TPair<int32, TArray<FString>>& LodIndexAndMeshUids : MeshUidsPerLodIndex)
{
if (LodIndex == LodIndexAndMeshUids.Key)
{
const TArray<FString>& Uids = LodIndexAndMeshUids.Value;
if (Uids.Num() > 0)
{
const FString& Uid = Uids[0];
const UInterchangeBaseNode* BaseNode = BaseNodeContainer->GetNode(Uid);
if (const UInterchangeMeshNode* MeshNode = Cast<const UInterchangeMeshNode>(BaseNode))
{
SetUidLabel(BaseNode);
return true;
}
if (const UInterchangeSceneNode* SceneNode = Cast<const UInterchangeSceneNode>(BaseNode))
{
bool bIsLodGroup = false;
SetUidLabelFromSceneNode(SceneNode, bIsLodGroup);
if (!CommonMeshesProperties->bBakeMeshes && !bIsLodGroup)
{
//We recursively check the AssetInstanceUid's chain to make sure we find LOD Group or MeshUID.
FString AssetInstanceUid;
while (SceneNode && SceneNode->GetCustomAssetInstanceUid(AssetInstanceUid))
{
const UInterchangeBaseNode* AssetInstanceNode = BaseNodeContainer->GetNode(AssetInstanceUid);
SceneNode = Cast<UInterchangeSceneNode>(AssetInstanceNode); //if its a sceneNode without a LOD group specialization we continue the search
if (SceneNode && SceneNode->IsSpecializedTypeContains(UE::Interchange::FSceneNodeStaticData::GetLodGroupSpecializeTypeString()))
{
//if it is a LOD Group specialization then we use the original NewNodeUid. (as then we want the 'instantiation' of the LOD Group)
NewNodeUid = TEXT("\\MeshLODGroupInstance\\") + Uid;
return true;
}
if (const UInterchangeMeshNode* MeshNode = Cast<UInterchangeMeshNode>(AssetInstanceNode))
{
//if it is a Mesh Node then we confirmed that it is an instanced Mesh
// but since we are not baking meshes we want the MeshNode
//It cannot be part of a LOD group as that would have broken out sooner due to the LodGroup check.
//For this case we are using MeshNode's UID and DisplayName.
SetUidLabel(AssetInstanceNode);
return true;
}
}
}
else
{
return true;
}
}
if (const UInterchangeInstancedStaticMeshComponentNode* ISMComponentNode = Cast<const UInterchangeInstancedStaticMeshComponentNode>(BaseNode))
{
FString RefMeshUid;
if (ISMComponentNode->GetCustomInstancedAssetUid(RefMeshUid))
{
const UInterchangeBaseNode* MeshNode = BaseNodeContainer->GetNode(RefMeshUid);
if (MeshNode)
{
SetUidLabel(MeshNode);
return true;
}
}
}
}
// We found the lod but there is no valid Mesh node to return the Uid
break;
}
}
return false;
}
UInterchangeStaticMeshFactoryNode* UInterchangeGenericMeshPipeline::CreateStaticMeshFactoryNode(const TMap<int32, TArray<FString>>& MeshUidsPerLodIndex, const FString& BaseMeshUid)
{
check(CommonMeshesProperties.IsValid());
if (MeshUidsPerLodIndex.Num() == 0)
{
return nullptr;
}
// Create the static mesh factory node, name it according to the first mesh node compositing the meshes
FString StaticMeshUid_MeshNamePart;
FString DisplayLabel;
const int32 BaseLodIndex = 0;
if (!MakeMeshFactoryNodeUidAndDisplayLabel(MeshUidsPerLodIndex, BaseLodIndex, StaticMeshUid_MeshNamePart, DisplayLabel, BaseMeshUid))
{
// Log an error
return nullptr;
}
// #interchange_lod_rework: if rework will be calling out LOD Groups directly this won't be needed.
auto CheckAndStoreBaseMeshUidForLODGroup = [this, BaseMeshUid](UInterchangeStaticMeshFactoryNode* StaticMeshFactoryNode)
{
if (const UInterchangeSceneNode* BaseMeshSceneNode = Cast<UInterchangeSceneNode>(BaseNodeContainer->GetNode(BaseMeshUid)))
{
if (BaseMeshSceneNode->IsSpecializedTypeContains(UE::Interchange::FSceneNodeStaticData::GetLodGroupSpecializeTypeString()))
{
StaticMeshFactoryNode->RegisterAttribute<FString>(UE::Interchange::FAttributeKey(FString(TEXT("LODGroupSceneNodeUid"))), BaseMeshUid);
}
else
{
bool bLODGroup_WithoutLODSpecialization = false;
if (BaseMeshSceneNode->GetAttribute<bool>(TEXT("LODGroup_WithoutLODSpecialization"), bLODGroup_WithoutLODSpecialization))
{
if (bLODGroup_WithoutLODSpecialization)
{
StaticMeshFactoryNode->RegisterAttribute<FString>(UE::Interchange::FAttributeKey(FString(TEXT("LODGroupSceneNodeUid"))), BaseMeshUid);
}
}
}
}
};
const FString StaticMeshUid = UInterchangeFactoryBaseNode::BuildFactoryNodeUid(StaticMeshUid_MeshNamePart);
UInterchangeFactoryBaseNode* FactoryBaseNode = BaseNodeContainer->GetFactoryNode(StaticMeshUid);
UInterchangeStaticMeshFactoryNode* StaticMeshFactoryNode = Cast<UInterchangeStaticMeshFactoryNode>(FactoryBaseNode);
if (StaticMeshFactoryNode)
{
CheckAndStoreBaseMeshUidForLODGroup(StaticMeshFactoryNode);
return StaticMeshFactoryNode;
}
StaticMeshFactoryNode = NewObject<UInterchangeStaticMeshFactoryNode>(BaseNodeContainer, NAME_None);
if (!ensure(StaticMeshFactoryNode))
{
return nullptr;
}
StaticMeshFactoryNode->InitializeStaticMeshNode(StaticMeshUid, DisplayLabel, UStaticMesh::StaticClass()->GetName(), BaseNodeContainer);
CheckAndStoreBaseMeshUidForLODGroup(StaticMeshFactoryNode);
//Set the pipeline import sockets property
StaticMeshFactoryNode->SetCustomImportSockets(CommonMeshesProperties->bImportSockets);
if (CommonMeshesProperties->bKeepSectionsSeparate)
{
StaticMeshFactoryNode->SetCustomKeepSectionsSeparate(CommonMeshesProperties->bKeepSectionsSeparate);
}
AddLodDataToStaticMesh(StaticMeshFactoryNode, MeshUidsPerLodIndex);
switch (CommonMeshesProperties->VertexColorImportOption)
{
case EInterchangeVertexColorImportOption::IVCIO_Replace:
{
StaticMeshFactoryNode->SetCustomVertexColorReplace(true);
}
break;
case EInterchangeVertexColorImportOption::IVCIO_Ignore:
{
StaticMeshFactoryNode->SetCustomVertexColorIgnore(true);
}
break;
case EInterchangeVertexColorImportOption::IVCIO_Override:
{
StaticMeshFactoryNode->SetCustomVertexColorOverride(CommonMeshesProperties->VertexOverrideColor);
}
break;
}
StaticMeshFactoryNode->SetCustomLODGroup(LodGroup);
//Common meshes build options
StaticMeshFactoryNode->SetCustomRecomputeNormals(CommonMeshesProperties->bRecomputeNormals);
StaticMeshFactoryNode->SetCustomRecomputeTangents(CommonMeshesProperties->bRecomputeTangents);
StaticMeshFactoryNode->SetCustomUseMikkTSpace(CommonMeshesProperties->bUseMikkTSpace);
StaticMeshFactoryNode->SetCustomComputeWeightedNormals(CommonMeshesProperties->bComputeWeightedNormals);
StaticMeshFactoryNode->SetCustomUseHighPrecisionTangentBasis(CommonMeshesProperties->bUseHighPrecisionTangentBasis);
StaticMeshFactoryNode->SetCustomUseFullPrecisionUVs(CommonMeshesProperties->bUseFullPrecisionUVs);
StaticMeshFactoryNode->SetCustomUseBackwardsCompatibleF16TruncUVs(CommonMeshesProperties->bUseBackwardsCompatibleF16TruncUVs);
StaticMeshFactoryNode->SetCustomRemoveDegenerates(CommonMeshesProperties->bRemoveDegenerates);
//Static meshes build options
StaticMeshFactoryNode->SetCustomBuildReversedIndexBuffer(bBuildReversedIndexBuffer);
StaticMeshFactoryNode->SetCustomGenerateLightmapUVs(bGenerateLightmapUVs);
StaticMeshFactoryNode->SetCustomGenerateDistanceFieldAsIfTwoSided(bGenerateDistanceFieldAsIfTwoSided);
StaticMeshFactoryNode->SetCustomSupportFaceRemap(bSupportFaceRemap);
StaticMeshFactoryNode->SetCustomMinLightmapResolution(MinLightmapResolution);
StaticMeshFactoryNode->SetCustomSrcLightmapIndex(SrcLightmapIndex);
StaticMeshFactoryNode->SetCustomDstLightmapIndex(DstLightmapIndex);
StaticMeshFactoryNode->SetCustomBuildScale3D(BuildScale3D);
StaticMeshFactoryNode->SetCustomDistanceFieldResolutionScale(DistanceFieldResolutionScale);
StaticMeshFactoryNode->SetCustomDistanceFieldReplacementMesh(DistanceFieldReplacementMesh.Get());
StaticMeshFactoryNode->SetCustomMaxLumenMeshCards(MaxLumenMeshCards);
StaticMeshFactoryNode->SetCustomBuildNanite(bBuildNanite);
StaticMeshFactoryNode->SetCustomAutoComputeLODScreenSizes(bAutoComputeLODScreenSizes);
StaticMeshFactoryNode->SetLODScreenSizes(LODScreenSizes);
return StaticMeshFactoryNode;
}
UInterchangeStaticMeshLodDataNode* UInterchangeGenericMeshPipeline::CreateStaticMeshLodDataNode(const FString& NodeName, const FString& NodeUniqueID)
{
FString DisplayLabel(NodeName);
FString NodeUID(NodeUniqueID);
UInterchangeStaticMeshLodDataNode* StaticMeshLodDataNode = NewObject<UInterchangeStaticMeshLodDataNode>(BaseNodeContainer, NAME_None);
if (!ensure(StaticMeshLodDataNode))
{
// @TODO: log error
return nullptr;
}
BaseNodeContainer->SetupNode(StaticMeshLodDataNode, NodeUID, DisplayLabel, EInterchangeNodeContainerType::FactoryData);
StaticMeshLodDataNode->SetOneConvexHullPerUCX(bOneConvexHullPerUCX);
StaticMeshLodDataNode->SetImportCollision(bCollision);
StaticMeshLodDataNode->SetImportCollisionType(Collision);
StaticMeshLodDataNode->SetForceCollisionPrimitiveGeneration(bForceCollisionPrimitiveGeneration);
return StaticMeshLodDataNode;
}
void UInterchangeGenericMeshPipeline::AddLodDataToStaticMesh(UInterchangeStaticMeshFactoryNode* StaticMeshFactoryNode, const TMap<int32, TArray<FString>>& NodeUidsPerLodIndex)
{
check(CommonMeshesProperties.IsValid());
const FString StaticMeshFactoryUid = StaticMeshFactoryNode->GetUniqueID();
int32 MaxLodIndex = 0;
for (const TPair<int32, TArray<FString>>& LodIndexAndNodeUids : NodeUidsPerLodIndex)
{
MaxLodIndex = FMath::Max(MaxLodIndex, LodIndexAndNodeUids.Key);
}
TArray<FString> EmptyLodData;
const int32 LodCount = MaxLodIndex+1;
for(int32 LodIndex = 0; LodIndex < LodCount; ++LodIndex)
{
if (!CommonMeshesProperties->bImportLods && LodIndex > 0)
{
// If the pipeline should not import lods, skip any lod over base lod
continue;
}
const TArray<FString>& NodeUids = NodeUidsPerLodIndex.Contains(LodIndex) ? NodeUidsPerLodIndex.FindChecked(LodIndex) : EmptyLodData;
// Create a lod data node with all the meshes for this LOD
const FString StaticMeshLodDataName = TEXT("LodData") + FString::FromInt(LodIndex);
const FString LODDataPrefix = TEXT("\\LodData") + (LodIndex > 0 ? FString::FromInt(LodIndex) : TEXT(""));
const FString StaticMeshLodDataUniqueID = LODDataPrefix + StaticMeshFactoryUid;
// Create LodData node if it doesn't already exist
UInterchangeStaticMeshLodDataNode* LodDataNode = Cast<UInterchangeStaticMeshLodDataNode>(BaseNodeContainer->GetFactoryNode(StaticMeshLodDataUniqueID));
if (!LodDataNode)
{
// Add the data for the LOD (all the mesh node fbx path, so we can find them when we will create the payload data)
LodDataNode = CreateStaticMeshLodDataNode(StaticMeshLodDataName, StaticMeshLodDataUniqueID);
BaseNodeContainer->SetNodeParentUid(StaticMeshLodDataUniqueID, StaticMeshFactoryUid);
StaticMeshFactoryNode->AddLodDataUniqueId(StaticMeshLodDataUniqueID);
}
TMap<FString, FString> ExistingLodSlotMaterialDependencies;
constexpr bool bAddSourceNodeName = true;
for (const FString& NodeUid : NodeUids)
{
TMap<FString, FString> SlotMaterialDependencies;
if (const UInterchangeSceneNode* SceneNode = Cast<UInterchangeSceneNode>(BaseNodeContainer->GetNode(NodeUid)))
{
FString MeshDependency;
SceneNode->GetCustomAssetInstanceUid(MeshDependency);
if (BaseNodeContainer->IsNodeUidValid(MeshDependency))
{
const UInterchangeMeshNode* MeshDependencyNode = Cast<UInterchangeMeshNode>(BaseNodeContainer->GetNode(MeshDependency));
UInterchangeUserDefinedAttributesAPI::DuplicateAllUserDefinedAttribute(MeshDependencyNode, StaticMeshFactoryNode, bAddSourceNodeName);
StaticMeshFactoryNode->AddTargetNodeUid(MeshDependency);
StaticMeshFactoryNode->AddSocketUids(PipelineMeshesUtilities->GetMeshGeometryByUid(MeshDependency).AttachedSocketUids);
MeshDependencyNode->AddTargetNodeUid(StaticMeshFactoryNode->GetUniqueID());
MeshDependencyNode->GetSlotMaterialDependencies(SlotMaterialDependencies);
}
else
{
SceneNode->GetSlotMaterialDependencies(SlotMaterialDependencies);
}
UInterchangeUserDefinedAttributesAPI::DuplicateAllUserDefinedAttribute(SceneNode, StaticMeshFactoryNode, bAddSourceNodeName);
}
else if (const UInterchangeMeshNode* MeshNode = Cast<UInterchangeMeshNode>(BaseNodeContainer->GetNode(NodeUid)))
{
UInterchangeUserDefinedAttributesAPI::DuplicateAllUserDefinedAttribute(MeshNode, StaticMeshFactoryNode, bAddSourceNodeName);
StaticMeshFactoryNode->AddTargetNodeUid(NodeUid);
StaticMeshFactoryNode->AddSocketUids(PipelineMeshesUtilities->GetMeshGeometryByUid(NodeUid).AttachedSocketUids);
MeshNode->AddTargetNodeUid(StaticMeshFactoryNode->GetUniqueID());
MeshNode->GetSlotMaterialDependencies(SlotMaterialDependencies);
}
else if (const UInterchangeInstancedStaticMeshComponentNode* ISMComponentNode = Cast<UInterchangeInstancedStaticMeshComponentNode>(BaseNodeContainer->GetNode(NodeUid)))
{
FString MeshDependency;
ISMComponentNode->GetCustomInstancedAssetUid(MeshDependency);
if (BaseNodeContainer->IsNodeUidValid(MeshDependency))
{
const UInterchangeMeshNode* MeshDependencyNode = Cast<UInterchangeMeshNode>(BaseNodeContainer->GetNode(MeshDependency));
UInterchangeUserDefinedAttributesAPI::DuplicateAllUserDefinedAttribute(MeshDependencyNode, StaticMeshFactoryNode, bAddSourceNodeName);
StaticMeshFactoryNode->AddTargetNodeUid(MeshDependency);
StaticMeshFactoryNode->AddSocketUids(PipelineMeshesUtilities->GetMeshGeometryByUid(MeshDependency).AttachedSocketUids);
MeshDependencyNode->AddTargetNodeUid(StaticMeshFactoryNode->GetUniqueID());
MeshDependencyNode->GetSlotMaterialDependencies(SlotMaterialDependencies);
}
UInterchangeUserDefinedAttributesAPI::DuplicateAllUserDefinedAttribute(ISMComponentNode, StaticMeshFactoryNode, bAddSourceNodeName);
}
UE::Interchange::MeshesUtilities::ApplySlotMaterialDependencies(*StaticMeshFactoryNode, SlotMaterialDependencies, *BaseNodeContainer, &ExistingLodSlotMaterialDependencies);
const FString MeshName = UE::Interchange::Private::GetNodeName(PipelineMeshesUtilities, *BaseNodeContainer, NodeUid);
// Ignore the "_" so we can detect potential typos done by the user.
const bool bHasCollisionPrefix = MeshName.StartsWith(TEXT("UBX")) || MeshName.StartsWith(TEXT("UCX")) || MeshName.StartsWith(TEXT("USP")) || MeshName.StartsWith(TEXT("UCP")) || MeshName.StartsWith(TEXT("MCDCX"));
auto LogWarning = [this, &StaticMeshFactoryNode](const FText& Text)
{
UInterchangeResultWarning_Generic* Message = AddMessage<UInterchangeResultWarning_Generic>();
Message->SourceAssetName = SourceDatas[0]->GetFilename();
Message->AssetFriendlyName = StaticMeshFactoryNode->GetAssetName();
Message->AssetType = StaticMeshFactoryNode->GetObjectClass();
Message->Text = Text;
};
if (bImportCollisionAccordingToMeshName)
{
TTuple<EInterchangeMeshCollision, FString> MeshType = GetCollisionMeshType(PipelineMeshesUtilities, *BaseNodeContainer, NodeUid, NodeUids, bImportCollisionAccordingToMeshName);
EInterchangeMeshCollision CollisionType = MeshType.Key;
const FString& RenderMeshUid = MeshType.Value;
const FString& ColliderMeshUid = NodeUid;
switch (CollisionType)
{
case EInterchangeMeshCollision::None:
LodDataNode->AddMeshUid(NodeUid);
// If the user is attempting to import a collision mesh using a naming convention but the mesh doesn't match, warn them that it won't be imported as collision.
if (bHasCollisionPrefix)
{
LogWarning(FText::Format(NSLOCTEXT("UInterchangeGenericStaticMeshPipeline", "NoMatchingRenderMeshForCustomCollision",
"No render mesh found for custom collision '{0}'. The name must follow the pattern 'UCX_[ExactRenderMeshName]', and a matching render mesh with that name must exist in the source file."),
FText::FromString(MeshName)));
}
break;
case EInterchangeMeshCollision::Box:
LodDataNode->AddBoxCollisionMeshUids(ColliderMeshUid, RenderMeshUid);
break;
case EInterchangeMeshCollision::Sphere:
LodDataNode->AddSphereCollisionMeshUids(ColliderMeshUid, RenderMeshUid);
break;
case EInterchangeMeshCollision::Capsule:
LodDataNode->AddCapsuleCollisionMeshUids(ColliderMeshUid, RenderMeshUid);
break;
case EInterchangeMeshCollision::Convex10DOP_X:
case EInterchangeMeshCollision::Convex10DOP_Y:
case EInterchangeMeshCollision::Convex10DOP_Z:
case EInterchangeMeshCollision::Convex18DOP:
case EInterchangeMeshCollision::Convex26DOP:
LodDataNode->AddConvexCollisionMeshUids(ColliderMeshUid, RenderMeshUid);
break;
}
// If the Mesh is both render AND collision mesh let's also add it as a render mesh,
// as the above switch will have only added the collision mesh to the LodDataNode
if (RenderMeshUid == NodeUid && CollisionType != EInterchangeMeshCollision::None)
{
LodDataNode->AddMeshUid(NodeUid);
}
}
else
{
LodDataNode->AddMeshUid(NodeUid);
// Warn the user that this mesh appears to be a custom collision mesh but that the pipeline is not set to import it as such
if (bHasCollisionPrefix)
{
LogWarning(FText::Format(NSLOCTEXT("UInterchangeGenericStaticMeshPipeline", "ImportCollisionAccordingToMeshNameDisabled",
"The mesh '{0}' appears to use custom collision naming pattern, but the setting 'Import Collisions According to Mesh Name' is disabled. It will be treated as a render mesh."),
FText::FromString(MeshName)));
}
}
}
UE::Interchange::MeshesUtilities::ReorderSlotMaterialDependencies(*StaticMeshFactoryNode, *BaseNodeContainer);
}
}
// #interchange_lod_rework
void UInterchangeGenericMeshPipeline::FindAndProcessIdenticalStaticMeshLODGroups(const UInterchangeBaseNodeContainer* InBaseNodeContainer)
{
if (!CommonMeshesProperties->bBakeMeshes)
{
auto GetGlobalTransform = [&InBaseNodeContainer](
const FString& InMeshOrSceneNodeUid,
FTransform& OutGlobalTransform,
bool& bMeshNode,
FString& LODMeshAssetUid
)
{
bMeshNode = true;
const UInterchangeBaseNode* Node = InBaseNodeContainer->GetNode(InMeshOrSceneNodeUid);
const UInterchangeMeshNode* MeshNode = Cast<const UInterchangeMeshNode>(Node);
if (MeshNode == nullptr)
{
bMeshNode = false;
// MeshUid must refer to a scene node
const UInterchangeSceneNode* SceneNode = Cast<const UInterchangeSceneNode>(Node);
if (!ensure(SceneNode))
{
return;
}
// Get the transform from the scene node
FTransform SceneNodeGlobalTransform;
if (SceneNode->GetCustomGlobalTransform(InBaseNodeContainer, FTransform::Identity, SceneNodeGlobalTransform))
{
OutGlobalTransform = SceneNodeGlobalTransform;
}
UE::Interchange::Private::MeshHelper::AddSceneNodeGeometricAndPivotToGlobalTransform(OutGlobalTransform, SceneNode, true /*bakeMeshes*/, true /*bakePivotMeshes*/);
// And get the mesh node which it references
FString MeshDependencyUid;
SceneNode->GetCustomAssetInstanceUid(MeshDependencyUid);
LODMeshAssetUid = MeshDependencyUid;
}
else
{
LODMeshAssetUid = InMeshOrSceneNodeUid;
}
};
//Iterate through all UInterchangeStaticMeshFactoryNode and build a helper struct that will help us identify identical LOD Group Static Meshes.
struct FLODGroupIIHelper //Identical Identifier
{
TMap<FString, FTransform> Meshes;
UInterchangeStaticMeshFactoryNode* LODGroupStaticMeshFactoryNode;
TArray<UInterchangeStaticMeshFactoryNode*> DisabledIdenticals;
bool DataEquals(const FLODGroupIIHelper& LODGroupIIHelper)
{
if (Meshes.Num() != LODGroupIIHelper.Meshes.Num())
{
return false;
}
for (const TPair<FString, FTransform>& Entry : Meshes)
{
if (!LODGroupIIHelper.Meshes.Contains(Entry.Key))
{
return false;
}
if (!LODGroupIIHelper.Meshes[Entry.Key].Equals(Entry.Value))
{
return false;
}
}
return true;
}
};
TArray<FLODGroupIIHelper> LODGroupIdenticalIdentifier;
auto ProcessEntryCandidate = [&LODGroupIdenticalIdentifier](FLODGroupIIHelper& ToCompare)
{
for (FLODGroupIIHelper& LODGroupIIHelper : LODGroupIdenticalIdentifier)
{
//TODO/Note: we should compare all attributes of the UInterchangeStaticMeshFactoryNode to make sure that regardless of Mesh and Transforms they are identical and can be subsituted.
//For now however we are only checking Mesh and Transform.
//For now this is fine as the rest of the UInterchangeStaticMeshFactoryNode attributes are set from the Pipeline and Utilities properties which are shared across.
if (LODGroupIIHelper.DataEquals(ToCompare))
{
LODGroupIIHelper.DisabledIdenticals.Add(ToCompare.LODGroupStaticMeshFactoryNode);
ToCompare.LODGroupStaticMeshFactoryNode->SetEnabled(false);
ToCompare.LODGroupStaticMeshFactoryNode->SetCustomSubstituteUID(LODGroupIIHelper.LODGroupStaticMeshFactoryNode->GetUniqueID());
return;
}
}
LODGroupIdenticalIdentifier.Add(ToCompare);
};
InBaseNodeContainer->IterateNodesOfType<UInterchangeStaticMeshFactoryNode>(
[&InBaseNodeContainer, &GetGlobalTransform, &LODGroupIdenticalIdentifier, &ProcessEntryCandidate](const FString& NodeUid, UInterchangeStaticMeshFactoryNode* StaticMeshFactoryNode)
{
FString LODGroupSceneUid;
if (StaticMeshFactoryNode->GetAttribute<FString>(TEXT("LODGroupSceneNodeUid"), LODGroupSceneUid))
{
const UInterchangeSceneNode* LODGroupSceneNode = Cast<UInterchangeSceneNode>(InBaseNodeContainer->GetNode(LODGroupSceneUid));
if (!LODGroupSceneNode)
{
return;
}
FLODGroupIIHelper LODGroupIIHelper;
LODGroupIIHelper.LODGroupStaticMeshFactoryNode = StaticMeshFactoryNode;
//Get LOD Group's Global Transform:
FTransform LODGroupGlobalTransform;
bool bLODGroupMeshNode; //Dummy
FString LODMeshAssetUid; //Dummy
GetGlobalTransform(LODGroupSceneUid, LODGroupGlobalTransform, bLODGroupMeshNode, LODMeshAssetUid);
FTransform LODGroupGlobalTransformInverse = LODGroupGlobalTransform.Inverse();
TArray<FString> LodDataUniqueIds;
StaticMeshFactoryNode->GetLodDataUniqueIds(LodDataUniqueIds);
for (size_t LODIndex = 0; LODIndex < LodDataUniqueIds.Num(); LODIndex++)
{
const FString& LodUniqueId = LodDataUniqueIds[LODIndex];
const FString LODIndexString = FString::FromInt(LODIndex);
const UInterchangeStaticMeshLodDataNode* LodDataNode = Cast<UInterchangeStaticMeshLodDataNode>(InBaseNodeContainer->GetNode(LodUniqueId));
if (!ensure(LodDataNode))
{
continue;
}
TArray<FString> MeshUids;
LodDataNode->GetMeshUids(MeshUids);
for (const FString& MeshUid : MeshUids)
{
FTransform MeshGlobalTransform;
bool bMeshNode;
FString MeshAssetUid;
GetGlobalTransform(MeshUid, MeshGlobalTransform, bMeshNode, MeshAssetUid);
if (!MeshAssetUid.IsEmpty())
{
if (bMeshNode)
{
LODGroupIIHelper.Meshes.Add(LODIndexString + TEXT("_") + MeshAssetUid, FTransform::Identity);
}
else
{
//Note: Need the relative transform from the LODGroup not from the root for equal check purposes.
LODGroupIIHelper.Meshes.Add(LODIndexString + TEXT("_") + MeshAssetUid, LODGroupGlobalTransformInverse * MeshGlobalTransform);
}
}
}
}
ProcessEntryCandidate(LODGroupIIHelper);
}
});
}
}