Files
UnrealEngine/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxStaticMeshImport.cpp
2025-05-18 13:04:45 +08:00

3033 lines
114 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
Static mesh creation from FBX data.
Largely based on StaticMeshEdit.cpp
=============================================================================*/
#include "CoreMinimal.h"
#include "Misc/Guid.h"
#include "UObject/Object.h"
#include "UObject/GarbageCollection.h"
#include "UObject/MetaData.h"
#include "UObject/Package.h"
#include "Misc/PackageName.h"
#include "MaterialDomain.h"
#include "Materials/MaterialInterface.h"
#include "Materials/Material.h"
#include "Factories/Factory.h"
#include "Factories/FbxSceneImportFactory.h"
#include "Factories/FbxStaticMeshImportData.h"
#include "Engine/StaticMesh.h"
#include "Engine/Polys.h"
#include "Engine/StaticMeshSocket.h"
#include "Editor.h"
#include "MeshBudgetProjectSettings.h"
#include "Modules/ModuleManager.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "StaticMeshResources.h"
#include "ObjectTools.h"
#include "PackageTools.h"
#include "Logging/TokenizedMessage.h"
#include "FbxImporter.h"
#include "GeomFitUtils.h"
#include "ImportUtils/StaticMeshImportUtils.h"
#include "InterchangeProjectSettings.h"
#include "Interfaces/ITargetPlatform.h"
#include "Interfaces/ITargetPlatformManagerModule.h"
#include "Misc/FbxErrors.h"
#include "Misc/ScopedSlowTask.h"
#include "PhysicsEngine/BodySetup.h"
#include "StaticMeshAttributes.h"
#include "IMeshBuilderModule.h"
#include "Settings/EditorExperimentalSettings.h"
#define LOCTEXT_NAMESPACE "FbxStaticMeshImport"
static const int32 LARGE_MESH_MATERIAL_INDEX_THRESHOLD = 64;
using namespace UnFbx;
struct FRestoreReimportData
{
UObject* EditorObject = nullptr;
UObject* DupObject = nullptr;
FString OriginalName;
FString OriginalPackageName;
TMap<FName, FString> ObjectMetaData;
FRestoreReimportData()
{
//Unsupported default constructor
check(false);
}
FRestoreReimportData(UStaticMesh* Mesh)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FRestoreReimportData);
if (!ensure(Mesh))
{
return;
}
EditorObject = Mesh;
OriginalName = Mesh->GetName();
TMap<FName, FString>* ObjectMetaDataPtr = Mesh->GetPackage()->GetMetaData().GetMapForObject(Mesh);
if (ObjectMetaDataPtr && ObjectMetaDataPtr->Num() > 0)
{
ObjectMetaData = *ObjectMetaDataPtr;
}
OriginalPackageName = Mesh->GetOutermost()->GetName();
DupObject = StaticDuplicateObject(Mesh, GetTransientPackage());
DupObject->AddToRoot();
}
void RestoreMesh(UnFbx::FFbxImporter* FbxImporter)
{
TRACE_CPUPROFILER_EVENT_SCOPE(RestoreMesh);
if (!ensure(EditorObject && DupObject))
{
return;
}
if(ensure(FbxImporter))
{
FbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("RestoreMesh_FailReimportMesh_RestoreMessage", "Fail to reimport mesh {0}. Restoring the original mesh"), FText::FromString(OriginalPackageName))), FFbxErrors::Generic_ImportingNewObjectFailed);
}
UPackage* Package = nullptr;
//We have to close any staticmesh editor using this asset
int32 EditorCloseCount = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->CloseAllEditorsForAsset(EditorObject);
//Restore the duplicate asset
OriginalPackageName = UPackageTools::SanitizePackageName(OriginalPackageName);
Package = CreatePackage( *OriginalPackageName);
UObject* ExistingObject = StaticFindObject(UStaticMesh::StaticClass(), Package, *OriginalName, true);
if (ExistingObject)
{
//Some reimport path did not trash the package in case there is a fail (i.e. scene reimport)
//Rename the original mesh and trash it
ExistingObject->Rename(nullptr, GetTransientPackage(), REN_DoNotDirty | REN_DontCreateRedirectors | REN_NonTransactional);
ExistingObject->MarkAsGarbage();
}
//Rename the dup object
DupObject->Rename(*OriginalName, Package, REN_DoNotDirty | REN_DontCreateRedirectors | REN_NonTransactional);
if (ObjectMetaData.Num() > 0)
{
//Package was recreate so restore the metadata
FMetaData& PackageMetaData = DupObject->GetPackage()->GetMetaData();
PackageMetaData.SetObjectValues(DupObject, MoveTemp(ObjectMetaData));
}
//Since all loaded package are add to root, we have to remove the dup from the root
DupObject->RemoveFromRoot();
if (EditorCloseCount > 0)
{
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(DupObject);
}
}
void CleanupDuplicateMesh()
{
TRACE_CPUPROFILER_EVENT_SCOPE(CleanupDuplicateMesh);
if(DupObject)
{
DupObject->RemoveFromRoot();
DupObject->MarkAsGarbage();
DupObject = nullptr;
}
}
};
static FbxString GetNodeNameWithoutNamespace( const FbxString& NodeName )
{
// Namespaces are marked with colons so find the last colon which will mark the start of the actual name
int32 LastNamespceIndex = NodeName.ReverseFind(':');
if( LastNamespceIndex == -1 )
{
// No namespace
return NodeName;
}
else
{
// chop off the namespace
return NodeName.Right( NodeName.GetLen() - (LastNamespceIndex + 1) );
}
}
void CreateTokenizedErrorForDegeneratedPart(UnFbx::FFbxImporter* FbxImporter, const FString MeshName, const FString NodeName)
{
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("MeshName"), FText::FromString(MeshName));
Arguments.Add(TEXT("PartName"), FText::FromString(NodeName));
FText ErrorMsg = LOCTEXT("MeshHasNoRenderableTriangles", "Mesh name: [{MeshName}] part name: [{PartName}] could not be created because all of its polygons are degenerate.");
FbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(ErrorMsg, Arguments)), FFbxErrors::StaticMesh_AllTrianglesDegenerate);
}
//-------------------------------------------------------------------------
//
//-------------------------------------------------------------------------
UStaticMesh* UnFbx::FFbxImporter::ImportStaticMesh(UObject* InParent, FbxNode* Node, const FName& Name, EObjectFlags Flags, UFbxStaticMeshImportData* ImportData, UStaticMesh* InStaticMesh, int LODIndex, const FExistingStaticMeshData* ExistMeshDataPtr)
{
TArray<FbxNode*> MeshNodeArray;
if ( !Node->GetMesh())
{
return NULL;
}
MeshNodeArray.Add(Node);
return ImportStaticMeshAsSingle(InParent, MeshNodeArray, Name, Flags, ImportData, InStaticMesh, LODIndex, ExistMeshDataPtr);
}
// Wraps some common code useful for multiple fbx import code path
struct FFBXUVs
{
// constructor
FFBXUVs(UnFbx::FFbxImporter* FbxImporter, FbxMesh* Mesh)
: UniqueUVCount(0)
{
check(Mesh);
//
// store the UVs in arrays for fast access in the later looping of triangles
//
// mapping from UVSets to Fbx LayerElementUV
// Fbx UVSets may be duplicated, remove the duplicated UVSets in the mapping
int32 LayerCount = Mesh->GetLayerCount();
if (LayerCount > 0)
{
int32 UVLayerIndex;
for (UVLayerIndex = 0; UVLayerIndex<LayerCount; UVLayerIndex++)
{
FbxLayer* lLayer = Mesh->GetLayer(UVLayerIndex);
int UVSetCount = lLayer->GetUVSetCount();
if(UVSetCount)
{
FbxArray<FbxLayerElementUV const*> EleUVs = lLayer->GetUVSets();
for (int UVIndex = 0; UVIndex<UVSetCount; UVIndex++)
{
FbxLayerElementUV const* ElementUV = EleUVs[UVIndex];
if (ElementUV)
{
const char* UVSetName = ElementUV->GetName();
FString LocalUVSetName = UTF8_TO_TCHAR(UVSetName);
if (LocalUVSetName.IsEmpty())
{
LocalUVSetName = TEXT("UVmap_") + FString::FromInt(UVLayerIndex);
}
UVSets.AddUnique(LocalUVSetName);
}
}
}
}
}
// If the the UV sets are named using the following format (UVChannel_X; where X ranges from 1 to 4)
// we will re-order them based on these names, provided there are enough UV channels in total to do so.
// Any UV sets that do not follow this naming convention will be slotted into available spaces.
//
// Examples:
// {UVChannel_3, UVChannel_1, RandomName} will be reordered to {UVChannel_1, RandomName, UVChannel_3}.
// {UVChannel_3, UVChannel_1} will be reordered to {UVChannel?1, UVChannel_3}
// - note that UVChannel_3 cannot be placed in the appropriate slot as there are not enough sets.
if(UVSets.Num())
{
const int32 MaxPossibleUVIndex = FMath::Min(UVSets.Num(), 4);
for(int32 ChannelNumIdx = 0; ChannelNumIdx < MaxPossibleUVIndex; ChannelNumIdx++)
{
FString ChannelName = FString::Printf( TEXT("UVChannel_%d"), ChannelNumIdx+1 );
int32 SetIdx = UVSets.Find( ChannelName );
// If the specially formatted UVSet name appears in the list and it is in the wrong spot,
// we will swap it into the correct spot, provided there are enough UV channels
if( SetIdx != INDEX_NONE && SetIdx != ChannelNumIdx )
{
//Swap the entry into the appropriate spot.
UVSets.Swap( SetIdx, ChannelNumIdx );
}
}
}
}
void Phase2(UnFbx::FFbxImporter* FbxImporter, FbxMesh* Mesh)
{
//
// store the UVs in arrays for fast access in the later looping of triangles
//
UniqueUVCount = UVSets.Num();
if (UniqueUVCount > 0)
{
LayerElementUV.AddZeroed(UniqueUVCount);
UVReferenceMode.AddZeroed(UniqueUVCount);
UVMappingMode.AddZeroed(UniqueUVCount);
}
for (int32 UVIndex = 0; UVIndex < UniqueUVCount; UVIndex++)
{
LayerElementUV[UVIndex] = NULL;
for (int32 UVLayerIndex = 0, LayerCount = Mesh->GetLayerCount(); UVLayerIndex < LayerCount; UVLayerIndex++)
{
FbxLayer* lLayer = Mesh->GetLayer(UVLayerIndex);
int UVSetCount = lLayer->GetUVSetCount();
if(UVSetCount)
{
FbxArray<FbxLayerElementUV const*> EleUVs = lLayer->GetUVSets();
for (int32 FbxUVIndex = 0; FbxUVIndex<UVSetCount; FbxUVIndex++)
{
FbxLayerElementUV const* ElementUV = EleUVs[FbxUVIndex];
if (ElementUV)
{
const char* UVSetName = ElementUV->GetName();
FString LocalUVSetName = UTF8_TO_TCHAR(UVSetName);
if (LocalUVSetName.IsEmpty())
{
LocalUVSetName = TEXT("UVmap_") + FString::FromInt(UVLayerIndex);
}
if (LocalUVSetName == UVSets[UVIndex])
{
LayerElementUV[UVIndex] = ElementUV;
UVReferenceMode[UVIndex] = ElementUV->GetReferenceMode();
UVMappingMode[UVIndex] = ElementUV->GetMappingMode();
break;
}
}
}
}
}
}
if (UniqueUVCount > MAX_MESH_TEXTURE_COORDS_MD)
{
FbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(LOCTEXT("Error_TooMuchUVChannel", "Reached the maximum number of UV Channels for a Static Mesh({0}) - discarding {1} UV Channels"), FText::AsNumber(MAX_MESH_TEXTURE_COORDS_MD), FText::AsNumber(UniqueUVCount-MAX_MESH_TEXTURE_COORDS_MD))), FFbxErrors::Generic_Mesh_TooMuchUVChannels);
}
UniqueUVCount = FMath::Min<int32>(UniqueUVCount, MAX_MESH_TEXTURE_COORDS_MD);
}
int32 FindLightUVIndex() const
{
// See if any of our UV set entry names match LightMapUV.
for(int32 UVSetIdx = 0; UVSetIdx < UVSets.Num(); UVSetIdx++)
{
if( UVSets[UVSetIdx] == TEXT("LightMapUV"))
{
return UVSetIdx;
}
}
// not found
return INDEX_NONE;
}
// @param FaceCornerIndex usually TriangleIndex * 3 + CornerIndex but more complicated for mixed n-gons
int32 ComputeUVIndex(int32 UVLayerIndex, int32 lControlPointIndex, int32 FaceCornerIndex) const
{
int32 UVMapIndex = (UVMappingMode[UVLayerIndex] == FbxLayerElement::eByControlPoint) ? lControlPointIndex : FaceCornerIndex;
int32 Ret;
if(UVReferenceMode[UVLayerIndex] == FbxLayerElement::eDirect)
{
Ret = UVMapIndex;
}
else
{
FbxLayerElementArrayTemplate<int>& Array = LayerElementUV[UVLayerIndex]->GetIndexArray();
Ret = Array.GetAt(UVMapIndex);
}
return Ret;
}
// todo: is that needed? could the dtor do it?
void Cleanup()
{
//
// clean up. This needs to happen before the mesh is destroyed
//
LayerElementUV.Empty();
UVReferenceMode.Empty();
UVMappingMode.Empty();
}
TArray<FString> UVSets;
TArray<FbxLayerElementUV const*> LayerElementUV;
TArray<FbxLayerElement::EReferenceMode> UVReferenceMode;
TArray<FbxLayerElement::EMappingMode> UVMappingMode;
int32 UniqueUVCount;
};
float UnFbx::FFbxImporter::GetPointComparisonThreshold() const
{
return (ImportOptions->bRemoveDegenerates && !ImportOptions->bBuildNanite) ? THRESH_POINTS_ARE_SAME : 0.0f;
}
float UnFbx::FFbxImporter::GetTriangleAreaThreshold() const
{
return (ImportOptions->bRemoveDegenerates && !ImportOptions->bBuildNanite) ? SMALL_NUMBER : 0.0f;
}
bool UnFbx::FFbxImporter::BuildStaticMeshFromGeometry(FbxNode* Node, UStaticMesh* StaticMesh, TArray<FFbxMaterial>& MeshMaterials, int32 LODIndex,
EVertexColorImportOption::Type VertexColorImportOption, const TMap<FVector3f, FColor>& ExistingVertexColorData, const FColor& VertexOverrideColor)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FFbxImporter::BuildStaticMeshFromGeometry);
FFbxScopedOperation ScopedImportOperation(this);
//Only cancel the operation if we are creating a new asset, as we don't support existing asset restoration yet.
check(StaticMesh->IsSourceModelValid(LODIndex));
FbxMesh* Mesh = Node->GetMesh();
FString FbxNodeName = UTF8_TO_TCHAR(Node->GetName());
FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(LODIndex);
//The mesh description should have been created before calling BuildStaticMeshFromGeometry
check(MeshDescription);
FStaticMeshAttributes Attributes(*MeshDescription);
//Get the base layer of the mesh
FbxLayer* BaseLayer = Mesh->GetLayer(0);
if (BaseLayer == NULL)
{
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("Error_NoGeometryInMesh", "There is no geometry information in mesh '{0}'"), FText::FromString(Mesh->GetName()))), FFbxErrors::Generic_Mesh_NoGeometry);
return false;
}
FFBXUVs FBXUVs(this, Mesh);
int32 FBXNamedLightMapCoordinateIndex = FBXUVs.FindLightUVIndex();
if (FBXNamedLightMapCoordinateIndex != INDEX_NONE)
{
StaticMesh->SetLightMapCoordinateIndex(FBXNamedLightMapCoordinateIndex);
}
//Get the default hard/smooth edges from the project settings
const UInterchangeProjectSettings* InterchangeProjectSettings = GetDefault<UInterchangeProjectSettings>();
const bool bStaticMeshUseSmoothEdgesIfSmoothingInformationIsMissing = InterchangeProjectSettings->bStaticMeshUseSmoothEdgesIfSmoothingInformationIsMissing;
//
// create materials
//
int32 MaterialCount = 0;
int32 MaterialIndexOffset = 0;
TArray<UMaterialInterface*> FbxMeshMaterials;
{
TRACE_CPUPROFILER_EVENT_SCOPE(CreateMaterials);
const bool bForSkeletalMesh = false;
FindOrImportMaterialsFromNode(Node, FbxMeshMaterials, FBXUVs.UVSets, bForSkeletalMesh);
if (!ImportOptions->bImportMaterials && ImportOptions->bImportTextures)
{
//If we are not importing any new material, we might still want to import new textures.
ImportTexturesFromNode(Node);
}
MaterialCount = Node->GetMaterialCount();
check(FbxMeshMaterials.Num() == MaterialCount);
// Used later to offset the material indices on the raw triangle data
MaterialIndexOffset = MeshMaterials.Num();
for (int32 MaterialIndex=0; MaterialIndex<MaterialCount; MaterialIndex++)
{
FFbxMaterial* NewMaterial = new(MeshMaterials) FFbxMaterial;
FbxSurfaceMaterial *FbxMaterial = Node->GetMaterial(MaterialIndex);
NewMaterial->FbxMaterial = FbxMaterial;
if (FbxMeshMaterials[MaterialIndex])
{
NewMaterial->Material = FbxMeshMaterials[MaterialIndex];
}
else
{
NewMaterial->Material = UMaterial::GetDefaultMaterial(MD_Surface);
}
}
if ( MaterialCount == 0 )
{
UMaterial* DefaultMaterial = UMaterial::GetDefaultMaterial(MD_Surface);
check(DefaultMaterial);
FFbxMaterial* NewMaterial = new(MeshMaterials) FFbxMaterial;
NewMaterial->Material = DefaultMaterial;
NewMaterial->FbxMaterial = NULL;
MaterialCount = 1;
}
}
//
// Convert data format to unreal-compatible
//
// Must do this before triangulating the mesh due to an FBX bug in TriangulateMeshAdvance
int32 LayerSmoothingCount = Mesh->GetLayerCount(FbxLayerElement::eSmoothing);
for(int32 i = 0; i < LayerSmoothingCount; i++)
{
FbxLayerElementSmoothing const* SmoothingInfo = Mesh->GetLayer(0)->GetSmoothing();
if (SmoothingInfo && SmoothingInfo->GetMappingMode() != FbxLayerElement::eByPolygon)
{
GeometryConverter->ComputePolygonSmoothingFromEdgeSmoothing (Mesh, i);
}
}
if (!Mesh->IsTriangleMesh())
{
if(!GIsAutomationTesting)
UE_LOG(LogFbx, Display, TEXT("Triangulating static mesh %s"), *FbxNodeName);
const bool bReplace = true;
FbxNodeAttribute* ConvertedNode = GeometryConverter->Triangulate(Mesh, bReplace);
if( ConvertedNode != NULL && ConvertedNode->GetAttributeType() == FbxNodeAttribute::eMesh )
{
Mesh = (fbxsdk::FbxMesh*)ConvertedNode;
}
else
{
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(LOCTEXT("Error_FailedToTriangulateWithOmission", "Unable to triangulate mesh '{0}': it will be omitted."), FText::FromString(FbxNodeName))), FFbxErrors::Generic_Mesh_TriangulationFailed);
return false;
}
}
// renew the base layer
BaseLayer = Mesh->GetLayer(0);
//
// get the "material index" layer. Do this AFTER the triangulation step as that may reorder material indices
//
FbxLayerElementMaterial* LayerElementMaterial = BaseLayer->GetMaterials();
FbxLayerElement::EMappingMode MaterialMappingMode = LayerElementMaterial ?
LayerElementMaterial->GetMappingMode() : FbxLayerElement::eByPolygon;
// todo second phase UV, ok to put in first phase?
FBXUVs.Phase2(this, Mesh);
//
// get the smoothing group layer
//
bool bSmoothingAvailable = false;
FbxLayerElementSmoothing* SmoothingInfo = BaseLayer->GetSmoothing();
FbxLayerElement::EReferenceMode SmoothingReferenceMode(FbxLayerElement::eDirect);
FbxLayerElement::EMappingMode SmoothingMappingMode(FbxLayerElement::eByEdge);
if (SmoothingInfo)
{
if (SmoothingInfo->GetMappingMode() == FbxLayerElement::eByPolygon)
{
//Convert the base layer to edge smoothing
GeometryConverter->ComputeEdgeSmoothingFromPolygonSmoothing(Mesh, 0);
BaseLayer = Mesh->GetLayer(0);
SmoothingInfo = BaseLayer->GetSmoothing();
}
if (SmoothingInfo->GetMappingMode() == FbxLayerElement::eByEdge)
{
bSmoothingAvailable = true;
}
SmoothingReferenceMode = SmoothingInfo->GetReferenceMode();
SmoothingMappingMode = SmoothingInfo->GetMappingMode();
}
//
// get the first vertex color layer
//
FbxLayerElementVertexColor* LayerElementVertexColor = BaseLayer->GetVertexColors();
FbxLayerElement::EReferenceMode VertexColorReferenceMode(FbxLayerElement::eDirect);
FbxLayerElement::EMappingMode VertexColorMappingMode(FbxLayerElement::eByControlPoint);
if (LayerElementVertexColor)
{
VertexColorReferenceMode = LayerElementVertexColor->GetReferenceMode();
VertexColorMappingMode = LayerElementVertexColor->GetMappingMode();
}
//
// get the first normal layer
//
FbxLayerElementNormal* LayerElementNormal = BaseLayer->GetNormals();
FbxLayerElementTangent* LayerElementTangent = BaseLayer->GetTangents();
FbxLayerElementBinormal* LayerElementBinormal = BaseLayer->GetBinormals();
//whether there is normal, tangent and binormal data in this mesh
bool bHasNTBInformation = LayerElementNormal && LayerElementTangent && LayerElementBinormal;
FbxLayerElement::EReferenceMode NormalReferenceMode(FbxLayerElement::eDirect);
FbxLayerElement::EMappingMode NormalMappingMode(FbxLayerElement::eByControlPoint);
if (LayerElementNormal)
{
NormalReferenceMode = LayerElementNormal->GetReferenceMode();
NormalMappingMode = LayerElementNormal->GetMappingMode();
}
FbxLayerElement::EReferenceMode TangentReferenceMode(FbxLayerElement::eDirect);
FbxLayerElement::EMappingMode TangentMappingMode(FbxLayerElement::eByControlPoint);
if (LayerElementTangent)
{
TangentReferenceMode = LayerElementTangent->GetReferenceMode();
TangentMappingMode = LayerElementTangent->GetMappingMode();
}
FbxLayerElement::EReferenceMode BinormalReferenceMode(FbxLayerElement::eDirect);
FbxLayerElement::EMappingMode BinormalMappingMode(FbxLayerElement::eByControlPoint);
if (LayerElementBinormal)
{
BinormalReferenceMode = LayerElementBinormal->GetReferenceMode();
BinormalMappingMode = LayerElementBinormal->GetMappingMode();
}
//
// build collision
//
{
TRACE_CPUPROFILER_EVENT_SCOPE(BuildCollision);
FbxString NodeName;
if (const FbxMap<FbxString, FbxString>::RecordType* OriginalNodeNameKeyValuePair = NodeUniqueNameToOriginalNameMap.Find(Node->GetName()))
{
NodeName = GetNodeNameWithoutNamespace(OriginalNodeNameKeyValuePair->GetValue());
}
else
{
NodeName = GetNodeNameWithoutNamespace(Node->GetName());
}
bool bImportedCollision = ImportCollisionModels(StaticMesh, NodeName);
//If we import a collision or we "generate one and remove the degenerates triangles" we will automatically set the section collision boolean.
bool bEnableCollision = bImportedCollision || (GBuildStaticMeshCollision && LODIndex == 0 && ImportOptions->bRemoveDegenerates);
for(int32 SectionIndex=MaterialIndexOffset; SectionIndex<MaterialIndexOffset+MaterialCount; SectionIndex++)
{
if (StaticMesh->GetSectionInfoMap().IsValidSection(LODIndex, SectionIndex))
{
FMeshSectionInfo Info = StaticMesh->GetSectionInfoMap().Get(LODIndex, SectionIndex);
Info.bEnableCollision = bEnableCollision;
//Make sure LOD greater then 0 copy the LOD 0 sections collision flags
if (LODIndex != 0)
{
//Match the material slot index
for (int32 LodZeroSectionIndex = 0; LodZeroSectionIndex < StaticMesh->GetSectionInfoMap().GetSectionNumber(0); ++LodZeroSectionIndex)
{
FMeshSectionInfo InfoLodZero = StaticMesh->GetSectionInfoMap().Get(0, LodZeroSectionIndex);
if (InfoLodZero.MaterialIndex == Info.MaterialIndex)
{
Info.bEnableCollision = InfoLodZero.bEnableCollision;
Info.bCastShadow = InfoLodZero.bCastShadow;
break;
}
}
}
StaticMesh->GetSectionInfoMap().Set(LODIndex, SectionIndex, Info);
}
}
}
//
// build un-mesh triangles
//
bool bHasNonDegeneratePolygons = false;
{
MeshDescription->SuspendVertexInstanceIndexing();
MeshDescription->SuspendEdgeIndexing();
MeshDescription->SuspendPolygonIndexing();
MeshDescription->SuspendPolygonGroupIndexing();
MeshDescription->SuspendUVIndexing();
TRACE_CPUPROFILER_EVENT_SCOPE(BuildTriangles);
// Construct the matrices for the conversion from right handed to left handed system
FbxAMatrix TotalMatrix;
FbxAMatrix TotalMatrixForNormal;
TotalMatrix = ComputeTotalMatrix(Node);
TotalMatrixForNormal = TotalMatrix.Inverse();
TotalMatrixForNormal = TotalMatrixForNormal.Transpose();
int32 PolygonCount = Mesh->GetPolygonCount();
if(PolygonCount == 0)
{
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("Error_NoPolygonFoundInMesh", "No polygon were found on mesh '{0}'"), FText::FromString(Mesh->GetName()))), FFbxErrors::StaticMesh_NoTriangles);
return false;
}
int32 VertexCount = Mesh->GetControlPointsCount();
bool OddNegativeScale = IsOddNegativeScale(TotalMatrix);
TVertexAttributesRef<FVector3f> VertexPositions = Attributes.GetVertexPositions();
TVertexInstanceAttributesRef<FVector3f> VertexInstanceNormals = Attributes.GetVertexInstanceNormals();
TVertexInstanceAttributesRef<FVector3f> VertexInstanceTangents = Attributes.GetVertexInstanceTangents();
TVertexInstanceAttributesRef<float> VertexInstanceBinormalSigns = Attributes.GetVertexInstanceBinormalSigns();
TVertexInstanceAttributesRef<FVector4f> VertexInstanceColors = Attributes.GetVertexInstanceColors();
TVertexInstanceAttributesRef<FVector2f> VertexInstanceUVs = Attributes.GetVertexInstanceUVs();
TEdgeAttributesRef<bool> EdgeHardnesses = Attributes.GetEdgeHardnesses();
TPolygonGroupAttributesRef<FName> PolygonGroupImportedMaterialSlotNames = Attributes.GetPolygonGroupMaterialSlotNames();
int32 VertexOffset = MeshDescription->Vertices().Num();
int32 VertexInstanceOffset = MeshDescription->VertexInstances().Num();
int32 PolygonOffset = MeshDescription->Polygons().Num();
// The below code expects Num() to be equivalent to GetArraySize(), i.e. that all added elements are appended, not inserted into existing gaps
check(VertexOffset == MeshDescription->Vertices().GetArraySize());
check(VertexInstanceOffset == MeshDescription->VertexInstances().GetArraySize());
check(PolygonOffset == MeshDescription->Polygons().GetArraySize());
TMap<int32, FPolygonGroupID> PolygonGroupMapping;
//Ensure the polygon groups are create in the same order has the imported materials order
for (int32 FbxMeshMaterialIndex = 0; FbxMeshMaterialIndex < FbxMeshMaterials.Num(); ++FbxMeshMaterialIndex)
{
int32 RealMaterialIndex = FbxMeshMaterialIndex + MaterialIndexOffset;
if (!PolygonGroupMapping.Contains(RealMaterialIndex))
{
UMaterialInterface* Material = MeshMaterials.IsValidIndex(RealMaterialIndex) ? MeshMaterials[RealMaterialIndex].Material : UMaterial::GetDefaultMaterial(MD_Surface);
FName ImportedMaterialSlotName = MeshMaterials.IsValidIndex(RealMaterialIndex) ? FName(*MeshMaterials[RealMaterialIndex].GetName()) : (Material != nullptr ? FName(*Material->GetName()) : NAME_None);
FPolygonGroupID ExistingPolygonGroup = INDEX_NONE;
for (const FPolygonGroupID PolygonGroupID : MeshDescription->PolygonGroups().GetElementIDs())
{
if (PolygonGroupImportedMaterialSlotNames[PolygonGroupID] == ImportedMaterialSlotName)
{
ExistingPolygonGroup = PolygonGroupID;
break;
}
}
if (ExistingPolygonGroup == INDEX_NONE)
{
ExistingPolygonGroup = MeshDescription->CreatePolygonGroup();
PolygonGroupImportedMaterialSlotNames[ExistingPolygonGroup] = ImportedMaterialSlotName;
}
PolygonGroupMapping.Add(RealMaterialIndex, ExistingPolygonGroup);
}
}
// When importing multiple mesh pieces to the same static mesh. Ensure each mesh piece has the same number of Uv's
int32 ExistingUVCount = VertexInstanceUVs.GetNumChannels();
int32 NumUVs = FMath::Max(FBXUVs.UniqueUVCount, ExistingUVCount);
NumUVs = FMath::Min<int32>(MAX_MESH_TEXTURE_COORDS_MD, NumUVs);
// At least one UV set must exist.
// @todo: this needn't be true; we should be able to handle zero UV channels
NumUVs = FMath::Max(1, NumUVs);
//Make sure all Vertex instance have the correct number of UVs
VertexInstanceUVs.SetNumChannels( NumUVs );
MeshDescription->SetNumUVChannels(NumUVs);
TArray<int32> UVOffsets;
UVOffsets.SetNumUninitialized(NumUVs);
for (int UVChannel = 0; UVChannel < NumUVs; UVChannel++)
{
UVOffsets[UVChannel] = MeshDescription->UVs(UVChannel).GetArraySize();
}
//Fill the vertex array
MeshDescription->ReserveNewVertices(VertexCount);
for (int32 VertexIndex = 0; VertexIndex < VertexCount; ++VertexIndex)
{
int32 RealVertexIndex = VertexOffset + VertexIndex;
FbxVector4 FbxPosition = Mesh->GetControlPoints()[VertexIndex];
FbxPosition = TotalMatrix.MultT(FbxPosition);
FVector3f VertexPosition = (FVector3f)Converter.ConvertPos(FbxPosition);
FVertexID AddedVertexId = MeshDescription->CreateVertex();
VertexPositions[AddedVertexId] = VertexPosition;
if (AddedVertexId.GetValue() != RealVertexIndex)
{
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("Error_CannotCreateVertex", "Cannot create valid vertex for mesh '{0}'"), FText::FromString(Mesh->GetName()))), FFbxErrors::StaticMesh_BuildError);
return false;
}
}
// Fill the UV arrays
for (int32 UVLayerIndex = 0; UVLayerIndex < FBXUVs.UniqueUVCount; UVLayerIndex++)
{
check(FBXUVs.LayerElementUV[UVLayerIndex]);
if (FBXUVs.LayerElementUV[UVLayerIndex] != nullptr)
{
int32 UVCount = FBXUVs.LayerElementUV[UVLayerIndex]->GetDirectArray().GetCount();
TUVAttributesRef<FVector2f> UVCoordinates = MeshDescription->UVAttributes(UVLayerIndex).GetAttributesRef<FVector2f>(MeshAttribute::UV::UVCoordinate);
MeshDescription->ReserveNewUVs(UVCount, UVLayerIndex);
for (int32 UVIndex = 0; UVIndex < UVCount; UVIndex++)
{
FUVID UVID = MeshDescription->CreateUV(UVLayerIndex);
FbxVector2 UVVector = FBXUVs.LayerElementUV[UVLayerIndex]->GetDirectArray().GetAt(UVIndex);
UVCoordinates[UVID] = FVector2f(static_cast<float>(UVVector[0]), 1.0f - static_cast<float>(UVVector[1])); // flip the Y of UVs for DirectX
}
}
}
TMap<uint64, int32> RemapEdgeID;
{
TRACE_CPUPROFILER_EVENT_SCOPE(BuildMeshEdgeVertices);
Mesh->BeginGetMeshEdgeVertices();
//Fill the edge array
int32 FbxEdgeCount = Mesh->GetMeshEdgeCount();
RemapEdgeID.Reserve(FbxEdgeCount*2);
for (int32 FbxEdgeIndex = 0; FbxEdgeIndex < FbxEdgeCount; ++FbxEdgeIndex)
{
int32 EdgeStartVertexIndex = -1;
int32 EdgeEndVertexIndex = -1;
Mesh->GetMeshEdgeVertices(FbxEdgeIndex, EdgeStartVertexIndex, EdgeEndVertexIndex);
// Skip invalid edges, i.e. one of the ends is invalid, or degenerated ones
if(EdgeStartVertexIndex == -1 || EdgeEndVertexIndex == -1 || EdgeStartVertexIndex == EdgeEndVertexIndex)
{
UE_LOG(LogFbx, Warning, TEXT("Skipping invalid edge on mesh %s"), *FString(Mesh->GetName()));
continue;
}
FVertexID EdgeVertexStart(EdgeStartVertexIndex + VertexOffset);
check(MeshDescription->Vertices().IsValid(EdgeVertexStart));
FVertexID EdgeVertexEnd(EdgeEndVertexIndex + VertexOffset);
check(MeshDescription->Vertices().IsValid(EdgeVertexEnd));
uint64 CompactedKey = (((uint64)EdgeVertexStart.GetValue()) << 32) | ((uint64)EdgeVertexEnd.GetValue());
RemapEdgeID.Add(CompactedKey, FbxEdgeIndex);
//Add the other edge side
CompactedKey = (((uint64)EdgeVertexEnd.GetValue()) << 32) | ((uint64)EdgeVertexStart.GetValue());
RemapEdgeID.Add(CompactedKey, FbxEdgeIndex);
}
//Call this after all GetMeshEdgeIndexForPolygon call this is for optimization purpose.
Mesh->EndGetMeshEdgeVertices();
}
{
TRACE_CPUPROFILER_EVENT_SCOPE(BuildMeshEdgeIndexForPolygon);
// Compute and reserve memory to be used for vertex instances
{
int32 TotalVertexCount = 0;
for (int32 PolygonIndex = 0; PolygonIndex < PolygonCount; PolygonIndex++)
{
TotalVertexCount += Mesh->GetPolygonSize(PolygonIndex);
}
MeshDescription->ReserveNewPolygons(PolygonCount);
MeshDescription->ReserveNewVertexInstances(TotalVertexCount);
MeshDescription->ReserveNewEdges(Mesh->GetMeshEdgeCount());
}
bool bBeginGetMeshEdgeIndexForPolygonCalled = false;
bool bBeginGetMeshEdgeIndexForPolygonRequired = true;
int32 CurrentVertexInstanceIndex = 0;
int32 SkippedVertexInstance = 0;
// keep those for all iterations to avoid heap allocations
TArray<FVertexInstanceID> CornerInstanceIDs;
TArray<FVertexID> CornerVerticesIDs;
TArray<FVector3f, TInlineAllocator<3>> P;
//Create a slowtask with a granularity of ~5% to avoid entering progress for every polygon.
const int32 SlowTaskNumberOfUpdates = 20;
const int32 SlowTaskNumberOfWorkUnitsBetweenUpdates = PolygonCount >= SlowTaskNumberOfUpdates ? PolygonCount / SlowTaskNumberOfUpdates : 1;
int32 SlowTaskUpdateCounter = 0;
FScopedSlowTask ProcessingPolygonsSlowTask(PolygonCount % SlowTaskNumberOfUpdates == 0
? SlowTaskNumberOfUpdates
: SlowTaskNumberOfUpdates + FMath::CeilToInt((PolygonCount % SlowTaskNumberOfUpdates) / (float)SlowTaskNumberOfWorkUnitsBetweenUpdates));
ProcessingPolygonsSlowTask.MakeDialog();
bool bFaceMaterialIndexInconsistencyErrorDisplayed = false;
bool bUnsupportedSmoothingGroupErrorDisplayed = false;
bool bCorruptedMsgDone = false;
//Polygons
for (int32 PolygonIndex = 0; PolygonIndex < PolygonCount; PolygonIndex++)
{
if (SlowTaskUpdateCounter-- <= 0)
{
SlowTaskUpdateCounter += SlowTaskNumberOfWorkUnitsBetweenUpdates;
ProcessingPolygonsSlowTask.EnterProgressFrame(1);
//Only cancel the operation if we are creating a new asset, as we don't support existing asset restoration yet.
if (ImportOptions->bIsImportCancelable && ProcessingPolygonsSlowTask.ShouldCancel())
{
Mesh->EndGetMeshEdgeIndexForPolygon();
bImportOperationCanceled = true;
return false;
}
}
int32 PolygonVertexCount = Mesh->GetPolygonSize(PolygonIndex);
//Verify if the polygon is degenerate, in this case do not add them
{
float ComparisonThreshold = GetTriangleAreaThreshold();
P.Reset();
P.AddUninitialized(PolygonVertexCount);
bool bAllCornerValid = true;
for (int32 CornerIndex = 0; CornerIndex < PolygonVertexCount; CornerIndex++)
{
const int32 ControlPointIndex = Mesh->GetPolygonVertex(PolygonIndex, CornerIndex);
const FVertexID VertexID(VertexOffset + ControlPointIndex);
if (!MeshDescription->Vertices().IsValid(VertexID))
{
bAllCornerValid = false;
break;
}
P[CornerIndex] = VertexPositions[VertexID];
}
if (!bAllCornerValid)
{
if (!bCorruptedMsgDone)
{
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("Error_CorruptedFbxPolygon", "Corrupted triangle for the mesh '{0}'"), FText::FromString(Mesh->GetName()))), FFbxErrors::StaticMesh_BuildError);
bCorruptedMsgDone = true;
}
SkippedVertexInstance += PolygonVertexCount;
continue;
}
check(P.Num() > 2); //triangle is the smallest polygon we can have
const FVector3f Normal = ((P[1] - P[2]) ^ (P[0] - P[2])).GetSafeNormal(ComparisonThreshold);
//Check for degenerated polygons, avoid NAN
if (Normal.IsNearlyZero(ComparisonThreshold) || Normal.ContainsNaN())
{
SkippedVertexInstance += PolygonVertexCount;
continue;
}
}
int32 RealPolygonIndex = PolygonOffset + PolygonIndex;
CornerInstanceIDs.Reset();
CornerInstanceIDs.AddUninitialized(PolygonVertexCount);
CornerVerticesIDs.Reset();
CornerVerticesIDs.AddUninitialized(PolygonVertexCount);
for (int32 CornerIndex = 0; CornerIndex < PolygonVertexCount; CornerIndex++)
{
int32 VertexInstanceIndex = VertexInstanceOffset + CurrentVertexInstanceIndex + CornerIndex;
int32 RealFbxVertexIndex = SkippedVertexInstance + CurrentVertexInstanceIndex + CornerIndex;
const FVertexInstanceID VertexInstanceID(VertexInstanceIndex);
CornerInstanceIDs[CornerIndex] = VertexInstanceID;
const int32 ControlPointIndex = Mesh->GetPolygonVertex(PolygonIndex, CornerIndex);
const FVertexID VertexID(VertexOffset + ControlPointIndex);
const FVector3f& VertexPosition = VertexPositions[VertexID];
CornerVerticesIDs[CornerIndex] = VertexID;
FVertexInstanceID AddedVertexInstanceId = MeshDescription->CreateVertexInstance(VertexID);
//Make sure the Added vertex instance ID is matching the expected vertex instance ID
check(AddedVertexInstanceId == VertexInstanceID);
if (AddedVertexInstanceId.GetValue() != VertexInstanceIndex)
{
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("Error_CannotCreateVertexInstance", "Cannot create valid vertex instance for mesh '{0}'"), FText::FromString(Mesh->GetName()))), FFbxErrors::StaticMesh_BuildError);
return false;
}
//UVs attributes
for (int32 UVLayerIndex = 0; UVLayerIndex < FBXUVs.UniqueUVCount; UVLayerIndex++)
{
FVector2f FinalUVVector(0.0f, 0.0f);
if (FBXUVs.LayerElementUV[UVLayerIndex] != NULL)
{
int UVMapIndex = (FBXUVs.UVMappingMode[UVLayerIndex] == FbxLayerElement::eByControlPoint) ? ControlPointIndex : RealFbxVertexIndex;
int32 UVIndex = (FBXUVs.UVReferenceMode[UVLayerIndex] == FbxLayerElement::eDirect) ?
UVMapIndex : FBXUVs.LayerElementUV[UVLayerIndex]->GetIndexArray().GetAt(UVMapIndex);
FbxVector2 UVVector = FBXUVs.LayerElementUV[UVLayerIndex]->GetDirectArray().GetAt(UVIndex);
FinalUVVector.X = static_cast<float>(UVVector[0]);
FinalUVVector.Y = 1.f - static_cast<float>(UVVector[1]); //flip the Y of UVs for DirectX
}
VertexInstanceUVs.Set(AddedVertexInstanceId, UVLayerIndex, FinalUVVector);
}
//Color attribute
if (VertexColorImportOption == EVertexColorImportOption::Replace)
{
if (LayerElementVertexColor)
{
int32 VertexColorMappingIndex = (VertexColorMappingMode == FbxLayerElement::eByControlPoint) ?
Mesh->GetPolygonVertex(PolygonIndex, CornerIndex) : (RealFbxVertexIndex);
int32 VectorColorIndex = (VertexColorReferenceMode == FbxLayerElement::eDirect) ?
VertexColorMappingIndex : LayerElementVertexColor->GetIndexArray().GetAt(VertexColorMappingIndex);
FbxColor VertexColor = LayerElementVertexColor->GetDirectArray().GetAt(VectorColorIndex);
// Clamp manually to avoid wrapping around because of truncation for values outside of the 0.0 to 1.0 range
FColor VertexInstanceColor(
uint8(255.f * FMath::Clamp(VertexColor.mRed, 0.0, 1.0)),
uint8(255.f * FMath::Clamp(VertexColor.mGreen, 0.0, 1.0)),
uint8(255.f * FMath::Clamp(VertexColor.mBlue, 0.0, 1.0)),
uint8(255.f * FMath::Clamp(VertexColor.mAlpha, 0.0, 1.0))
);
VertexInstanceColors[AddedVertexInstanceId] = FVector4f(FLinearColor(VertexInstanceColor));
}
}
else if (VertexColorImportOption == EVertexColorImportOption::Ignore)
{
// try to match this triangles current vertex with one that existed in the previous mesh.
// This is a find in a tmap which uses a fast hash table lookup.
const FColor* PaintedColor = ExistingVertexColorData.Find(VertexPosition);
if (PaintedColor)
{
// A matching color for this vertex was found
VertexInstanceColors[AddedVertexInstanceId] = FVector4f(FLinearColor(*PaintedColor));
}
}
else
{
// set the triangle's vertex color to a constant override
check(VertexColorImportOption == EVertexColorImportOption::Override);
VertexInstanceColors[AddedVertexInstanceId] = FVector4f(FLinearColor(VertexOverrideColor));
}
if (LayerElementNormal)
{
//normals may have different reference and mapping mode than tangents and binormals
int NormalMapIndex = (NormalMappingMode == FbxLayerElement::eByControlPoint) ?
ControlPointIndex : RealFbxVertexIndex;
int NormalValueIndex = (NormalReferenceMode == FbxLayerElement::eDirect) ?
NormalMapIndex : LayerElementNormal->GetIndexArray().GetAt(NormalMapIndex);
FbxVector4 TempValue = LayerElementNormal->GetDirectArray().GetAt(NormalValueIndex);
TempValue = TotalMatrixForNormal.MultT(TempValue);
FVector3f TangentZ = (FVector3f)Converter.ConvertDir(TempValue);
VertexInstanceNormals[AddedVertexInstanceId] = TangentZ.GetSafeNormal();
//tangents and binormals share the same reference, mapping mode and index array
if (bHasNTBInformation)
{
int TangentMapIndex = (TangentMappingMode == FbxLayerElement::eByControlPoint) ?
ControlPointIndex : RealFbxVertexIndex;
int TangentValueIndex = (TangentReferenceMode == FbxLayerElement::eDirect) ?
TangentMapIndex : LayerElementTangent->GetIndexArray().GetAt(TangentMapIndex);
TempValue = LayerElementTangent->GetDirectArray().GetAt(TangentValueIndex);
TempValue = TotalMatrixForNormal.MultT(TempValue);
FVector3f TangentX = (FVector3f)Converter.ConvertDir(TempValue);
VertexInstanceTangents[AddedVertexInstanceId] = TangentX.GetSafeNormal();
int BinormalMapIndex = (BinormalMappingMode == FbxLayerElement::eByControlPoint) ?
ControlPointIndex : RealFbxVertexIndex;
int BinormalValueIndex = (BinormalReferenceMode == FbxLayerElement::eDirect) ?
BinormalMapIndex : LayerElementBinormal->GetIndexArray().GetAt(BinormalMapIndex);
TempValue = LayerElementBinormal->GetDirectArray().GetAt(BinormalValueIndex);
TempValue = TotalMatrixForNormal.MultT(TempValue);
FVector3f TangentY = (FVector3f)-Converter.ConvertDir(TempValue);
VertexInstanceBinormalSigns[AddedVertexInstanceId] = GetBasisDeterminantSign((FVector)TangentX.GetSafeNormal(), (FVector)TangentY.GetSafeNormal(), (FVector)TangentZ.GetSafeNormal());
}
}
}
// Check if the polygon just discovered is non-degenerate if we haven't found one yet
//TODO check all polygon vertex, not just the first 3 vertex
if (!bHasNonDegeneratePolygons)
{
const float PointComparisonThreshold = GetPointComparisonThreshold();
FVector3f VertexPosition[3];
VertexPosition[0] = VertexPositions[CornerVerticesIDs[0]];
VertexPosition[1] = VertexPositions[CornerVerticesIDs[1]];
VertexPosition[2] = VertexPositions[CornerVerticesIDs[2]];
if (!( VertexPosition[0].Equals(VertexPosition[1], PointComparisonThreshold)
|| VertexPosition[0].Equals(VertexPosition[2], PointComparisonThreshold)
|| VertexPosition[1].Equals(VertexPosition[2], PointComparisonThreshold)))
{
bHasNonDegeneratePolygons = true;
}
}
//
// material index
//
int32 MaterialIndex = 0;
if (MaterialCount > 0)
{
if (LayerElementMaterial)
{
switch (MaterialMappingMode)
{
// material index is stored in the IndexArray, not the DirectArray (which is irrelevant with 2009.1)
case FbxLayerElement::eAllSame:
{
MaterialIndex = LayerElementMaterial->GetIndexArray().GetAt(0);
}
break;
case FbxLayerElement::eByPolygon:
{
MaterialIndex = LayerElementMaterial->GetIndexArray().GetAt(PolygonIndex);
}
break;
}
}
}
if (MaterialIndex >= MaterialCount || MaterialIndex < 0)
{
if (!bFaceMaterialIndexInconsistencyErrorDisplayed)
{
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, LOCTEXT("Error_MaterialIndexInconsistency", "Face material index inconsistency - forcing to 0")), FFbxErrors::Generic_Mesh_MaterialIndexInconsistency);
bFaceMaterialIndexInconsistencyErrorDisplayed = true;
}
MaterialIndex = 0;
}
//Create a polygon with the 3 vertex instances Add it to the material group
int32 RealMaterialIndex = MaterialIndexOffset + MaterialIndex;
if (!PolygonGroupMapping.Contains(RealMaterialIndex))
{
UMaterialInterface* Material = MeshMaterials.IsValidIndex(RealMaterialIndex) ? MeshMaterials[RealMaterialIndex].Material : UMaterial::GetDefaultMaterial(MD_Surface);
FName ImportedMaterialSlotName = MeshMaterials.IsValidIndex(RealMaterialIndex) ? FName(*MeshMaterials[RealMaterialIndex].GetName()) : (Material != nullptr ? FName(*Material->GetName()) : NAME_None);
FPolygonGroupID ExistingPolygonGroup = INDEX_NONE;
for (const FPolygonGroupID PolygonGroupID : MeshDescription->PolygonGroups().GetElementIDs())
{
if (PolygonGroupImportedMaterialSlotNames[PolygonGroupID] == ImportedMaterialSlotName)
{
ExistingPolygonGroup = PolygonGroupID;
break;
}
}
if (ExistingPolygonGroup == INDEX_NONE)
{
ExistingPolygonGroup = MeshDescription->CreatePolygonGroup();
PolygonGroupImportedMaterialSlotNames[ExistingPolygonGroup] = ImportedMaterialSlotName;
}
PolygonGroupMapping.Add(RealMaterialIndex, ExistingPolygonGroup);
}
// Create polygon edges
{
// Add the edges of this polygon
for (uint32 PolygonEdgeNumber = 0; PolygonEdgeNumber < (uint32)PolygonVertexCount; ++PolygonEdgeNumber)
{
//Find the matching edge ID
uint32 CornerIndices[2];
CornerIndices[0] = (PolygonEdgeNumber + 0) % PolygonVertexCount;
CornerIndices[1] = (PolygonEdgeNumber + 1) % PolygonVertexCount;
FVertexID EdgeVertexIDs[2];
EdgeVertexIDs[0] = CornerVerticesIDs[CornerIndices[0]];
EdgeVertexIDs[1] = CornerVerticesIDs[CornerIndices[1]];
FEdgeID MatchEdgeId = MeshDescription->GetVertexPairEdge(EdgeVertexIDs[0], EdgeVertexIDs[1]);
if (MatchEdgeId == INDEX_NONE)
{
MatchEdgeId = MeshDescription->CreateEdge(EdgeVertexIDs[0], EdgeVertexIDs[1]);
}
//RawMesh do not have edges, so by ordering the edge with the triangle construction we can ensure back and forth conversion with RawMesh
//When raw mesh will be completely remove we can create the edges right after the vertex creation.
int32 EdgeIndex = INDEX_NONE;
uint64 CompactedKey = (((uint64)EdgeVertexIDs[0].GetValue()) << 32) | ((uint64)EdgeVertexIDs[1].GetValue());
if (RemapEdgeID.Contains(CompactedKey))
{
EdgeIndex = RemapEdgeID[CompactedKey];
}
else
{
// Call BeginGetMeshEdgeIndexForPolygon lazily only if we enter the code path calling GetMeshEdgeIndexForPolygon
if (bBeginGetMeshEdgeIndexForPolygonRequired)
{
//Call this before all GetMeshEdgeIndexForPolygon for optimization purpose.
//But do not spend time precomputing stuff if the mesh has no edge since
//GetMeshEdgeIndexForPolygon is going to always returns -1 anyway without going into the slow path.
if (Mesh->GetMeshEdgeCount() > 0)
{
Mesh->BeginGetMeshEdgeIndexForPolygon();
bBeginGetMeshEdgeIndexForPolygonCalled = true;
}
bBeginGetMeshEdgeIndexForPolygonRequired = false;
}
EdgeIndex = Mesh->GetMeshEdgeIndexForPolygon(PolygonIndex, PolygonEdgeNumber);
}
if (!EdgeHardnesses[MatchEdgeId])
{
if (bSmoothingAvailable && SmoothingInfo)
{
if (SmoothingMappingMode == FbxLayerElement::eByEdge)
{
int32 lSmoothingIndex = (SmoothingReferenceMode == FbxLayerElement::eDirect) ? EdgeIndex : SmoothingInfo->GetIndexArray().GetAt(EdgeIndex);
//Set the hard edges
EdgeHardnesses[MatchEdgeId] = (SmoothingInfo->GetDirectArray().GetAt(lSmoothingIndex) == 0);
}
else if(!bUnsupportedSmoothingGroupErrorDisplayed)
{
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(LOCTEXT("Error_UnsupportedSmoothingGroup", "Unsupported Smoothing group mapping mode on mesh '{0}'"), FText::FromString(Mesh->GetName()))), FFbxErrors::Generic_Mesh_UnsupportingSmoothingGroup);
bUnsupportedSmoothingGroupErrorDisplayed = true;
}
}
else
{
//When there is no smoothing group we set all edge to hard (faceted mesh) or false depending on the project settings
EdgeHardnesses[MatchEdgeId] = !bStaticMeshUseSmoothEdgesIfSmoothingInformationIsMissing;
}
}
}
}
FPolygonGroupID PolygonGroupID = PolygonGroupMapping[RealMaterialIndex];
// Insert a triangle into the mesh
// @note: we only ever import triangulated meshes currently. This could easily change as we have the infrastructure set up for arbitrary ngons.
// However, I think either we need to triangulate in the exact same way as Maya/Max if we do that.
check(CornerInstanceIDs.Num() == 3);
check(PolygonVertexCount == 3);
TArray<FEdgeID> NewEdgeIDs;
const FTriangleID NewTriangleID = MeshDescription->CreateTriangle(PolygonGroupID, CornerInstanceIDs, &NewEdgeIDs);
check(NewEdgeIDs.Num() == 0);
for (int32 UVLayerIndex = 0; UVLayerIndex < FBXUVs.UniqueUVCount; UVLayerIndex++)
{
FUVID UVIDs[3] = {INDEX_NONE, INDEX_NONE, INDEX_NONE};
if (FBXUVs.LayerElementUV[UVLayerIndex] != NULL)
{
int32 UVDirectArrayCount = FBXUVs.LayerElementUV[UVLayerIndex]->GetDirectArray().GetCount();
int32 UVIndexArrayCount = FBXUVs.LayerElementUV[UVLayerIndex]->GetIndexArray().GetCount();
for (int32 VertexIndex = 0; VertexIndex < 3; ++VertexIndex)
{
int UVMapIndex = (FBXUVs.UVMappingMode[UVLayerIndex] == FbxLayerElement::eByControlPoint)
? Mesh->GetPolygonVertex(PolygonIndex, VertexIndex)
: SkippedVertexInstance + CurrentVertexInstanceIndex + VertexIndex;
int32 UVIndex = (FBXUVs.UVReferenceMode[UVLayerIndex] == FbxLayerElement::eDirect)
? UVMapIndex
: (UVMapIndex < UVIndexArrayCount ? FBXUVs.LayerElementUV[UVLayerIndex]->GetIndexArray().GetAt(UVMapIndex) : -1);
if (UVIndex >= FBXUVs.LayerElementUV[UVLayerIndex]->GetDirectArray().GetCount())
{
UVIndex = -1;
}
if (UVIndex != -1)
{
UVIDs[VertexIndex] = UVIndex + UVOffsets[UVLayerIndex];
check(MeshDescription->VertexInstanceAttributes().GetAttribute<FVector2f>(CornerInstanceIDs[VertexIndex], MeshAttribute::VertexInstance::TextureCoordinate, UVLayerIndex) ==
MeshDescription->UVAttributes(UVLayerIndex).GetAttribute<FVector2f>(UVIndex + UVOffsets[UVLayerIndex], MeshAttribute::UV::UVCoordinate));
}
else
{
// TODO: what does it mean to have a UV index of -1?
// Investigate this case more carefully and handle it properly.
}
}
}
MeshDescription->SetTriangleUVIndices(NewTriangleID, UVIDs, UVLayerIndex);
}
CurrentVertexInstanceIndex += PolygonVertexCount;
}
//Call this after all GetMeshEdgeIndexForPolygon call this is for optimization purpose.
if (bBeginGetMeshEdgeIndexForPolygonCalled)
{
Mesh->EndGetMeshEdgeIndexForPolygon();
}
MeshDescription->ResumeVertexInstanceIndexing();
MeshDescription->ResumeEdgeIndexing();
MeshDescription->ResumePolygonIndexing();
MeshDescription->ResumePolygonGroupIndexing();
MeshDescription->ResumeUVIndexing();
if (SkippedVertexInstance > 0)
{
check(MeshDescription->Triangles().Num() == MeshDescription->Triangles().GetArraySize());
}
}
}
TArray<FPolygonGroupID> EmptyPolygonGroups;
for (const FPolygonGroupID PolygonGroupID : MeshDescription->PolygonGroups().GetElementIDs())
{
if (MeshDescription->GetNumPolygonGroupTriangles(PolygonGroupID) == 0)
{
EmptyPolygonGroups.Add(PolygonGroupID);
}
}
if (EmptyPolygonGroups.Num() > 0)
{
for (const FPolygonGroupID PolygonGroupID : EmptyPolygonGroups)
{
MeshDescription->DeletePolygonGroup(PolygonGroupID);
}
FElementIDRemappings OutRemappings;
MeshDescription->Compact(OutRemappings);
}
// needed?
FBXUVs.Cleanup();
if (!bHasNonDegeneratePolygons)
{
CreateTokenizedErrorForDegeneratedPart(this, StaticMesh->GetName(), FbxNodeName);
}
bool bIsValidMesh = bHasNonDegeneratePolygons;
return bIsValidMesh;
}
UStaticMesh* UnFbx::FFbxImporter::ReimportSceneStaticMesh(uint64 FbxNodeUniqueId, uint64 FbxUniqueId, UStaticMesh* Mesh, UFbxStaticMeshImportData* TemplateImportData)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FFbxImporter::ReimportSceneStaticMesh);
FFbxScopedOperation FbxScopedImportOperation(this);
FRestoreReimportData RestoreData(Mesh);
TArray<FbxNode*> FbxMeshArray;
UStaticMesh* FirstBaseMesh = NULL;
FbxNode* Node = NULL;
// get meshes in Fbx file
//the function also fill the collision models, so we can update collision models correctly
FillFbxMeshArray(Scene->GetRootNode(), FbxMeshArray, this);
if (FbxMeshArray.Num() < 1)
{
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(LOCTEXT("Error_NoFBXMeshAttributeFound", "No FBX attribute mesh found when reimport scene static mesh '{0}'. The FBX file contain no static mesh."), FText::FromString(Mesh->GetName()))), FFbxErrors::Generic_Mesh_MeshNotFound);
return Mesh;
}
else
{
//Find the first node using the mesh attribute with the unique ID
for (FbxNode *MeshNode : FbxMeshArray)
{
if (FbxNodeUniqueId == INVALID_UNIQUE_ID || ImportOptions->bBakePivotInVertex == false)
{
if (FbxUniqueId == MeshNode->GetMesh()->GetUniqueID())
{
Node = MeshNode;
break;
}
}
else
{
if (FbxNodeUniqueId == MeshNode->GetUniqueID() && FbxUniqueId == MeshNode->GetMesh()->GetUniqueID())
{
Node = MeshNode;
break;
}
}
}
}
if (!Node)
{
//Cannot find the staticmesh name in the fbx scene file
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(LOCTEXT("Error_NoFBXMeshNameFound", "No FBX attribute mesh with the same name was found when reimport scene static mesh '{0}'."), FText::FromString(Mesh->GetName()))), FFbxErrors::Generic_Mesh_MeshNotFound);
return Mesh;
}
TSharedPtr<FExistingStaticMeshData> ExistMeshDataPtr = StaticMeshImportUtils::SaveExistingStaticMeshData(Mesh, ImportOptions, INDEX_NONE);
if (Node)
{
FbxNode* NodeParent = RecursiveFindParentLodGroup(Node->GetParent());
// if the Fbx mesh is a part of LODGroup, update LOD
if (NodeParent && NodeParent->GetNodeAttribute() && NodeParent->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup)
{
TArray<UStaticMesh*> BaseMeshes;
TArray<FbxNode*> AllNodeInLod;
FindAllLODGroupNode(AllNodeInLod, NodeParent, 0);
FirstBaseMesh = ImportStaticMeshAsSingle(Mesh->GetOutermost(), AllNodeInLod, *Mesh->GetName(), RF_Public | RF_Standalone, TemplateImportData, Mesh, 0, ExistMeshDataPtr.Get());
//If we have a valid LOD group name we don't want to re-import LODs since they will be automatically generate by the LODGroup reduce settings
if(FirstBaseMesh && Mesh->LODGroup == NAME_None)
{
// import LOD meshes
for (int32 LODIndex = 1; LODIndex < NodeParent->GetChildCount(); LODIndex++)
{
if (LODIndex >= MAX_STATIC_MESH_LODS)
{
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(
LOCTEXT("ImporterLimits_MaximumStaticMeshLODReach_ReimportSceneStaticMesh", "Reached the maximum number of LODs for a Static Mesh({0}) - discarding {1} LOD meshes.")
, FText::AsNumber(MAX_STATIC_MESH_LODS), FText::AsNumber(NodeParent->GetChildCount() - MAX_STATIC_MESH_LODS))
), FFbxErrors::Generic_Mesh_TooManyLODs);
break;
}
AllNodeInLod.Empty();
FindAllLODGroupNode(AllNodeInLod, NodeParent, LODIndex);
if (AllNodeInLod.Num() > 0)
{
if (AllNodeInLod[0]->GetMesh() == nullptr)
{
AddStaticMeshSourceModelGeneratedLOD(FirstBaseMesh, LODIndex);
}
else
{
//For LOD we don't pass the ExistMeshDataPtr
ImportStaticMeshAsSingle(Mesh->GetOutermost(), AllNodeInLod, *Mesh->GetName(), RF_Public | RF_Standalone, TemplateImportData, FirstBaseMesh, LODIndex, nullptr);
if (FirstBaseMesh->IsSourceModelValid(LODIndex))
{
FirstBaseMesh->GetSourceModel(LODIndex).bImportWithBaseMesh = true;
}
}
}
}
}
if (FirstBaseMesh != nullptr)
{
FindAllLODGroupNode(AllNodeInLod, NodeParent, 0);
PostImportStaticMesh(FirstBaseMesh, AllNodeInLod);
}
}
else
{
FirstBaseMesh = ImportStaticMesh(Mesh->GetOutermost(), Node, *Mesh->GetName(), RF_Public | RF_Standalone, TemplateImportData, Mesh, 0, ExistMeshDataPtr.Get());
if (FirstBaseMesh != nullptr)
{
TArray<FbxNode*> AllNodeInLod;
AllNodeInLod.Add(Node);
PostImportStaticMesh(FirstBaseMesh, AllNodeInLod);
}
}
}
else
{
// no FBX mesh match, maybe the Unreal mesh is imported from multiple FBX mesh (enable option "Import As Single")
if (FbxMeshArray.Num() > 0)
{
FirstBaseMesh = ImportStaticMeshAsSingle(Mesh->GetOutermost(), FbxMeshArray, *Mesh->GetName(), RF_Public | RF_Standalone, TemplateImportData, Mesh, 0, ExistMeshDataPtr.Get());
if (FirstBaseMesh != nullptr)
{
PostImportStaticMesh(FirstBaseMesh, FbxMeshArray);
}
}
else // no mesh found in the FBX file
{
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("Error_NoFBXMeshFound", "No FBX mesh found when reimport Unreal mesh '{0}'. The FBX file is crashed."), FText::FromString(Mesh->GetName()))), FFbxErrors::Generic_Mesh_MeshNotFound);
}
}
if(FirstBaseMesh)
{
//Don't restore materials when reimporting scene
StaticMeshImportUtils::RestoreExistingMeshData(ExistMeshDataPtr, FirstBaseMesh, INDEX_NONE, false, ImportOptions->bResetToFbxOnMaterialConflict);
RestoreData.CleanupDuplicateMesh();
}
else
{
RestoreData.RestoreMesh(this);
}
return FirstBaseMesh;
}
void UnFbx::FFbxImporter::AddStaticMeshSourceModelGeneratedLOD(UStaticMesh* StaticMesh, int32 LODIndex)
{
//Add a Lod generated model
while (StaticMesh->GetNumSourceModels() <= LODIndex)
{
StaticMesh->AddSourceModel();
}
FStaticMeshSourceModel& ThisSourceModel = StaticMesh->GetSourceModel(LODIndex);
if (LODIndex - 1 > 0 && StaticMesh->IsReductionActive(LODIndex - 1))
{
const FStaticMeshSourceModel& PrevSourceModel = StaticMesh->GetSourceModel(LODIndex - 1);
if (PrevSourceModel.ReductionSettings.PercentTriangles < 1.0f)
{
ThisSourceModel.ReductionSettings.PercentTriangles = PrevSourceModel.ReductionSettings.PercentTriangles * 0.5f;
}
else if (PrevSourceModel.ReductionSettings.MaxDeviation > 0.0f)
{
ThisSourceModel.ReductionSettings.MaxDeviation = PrevSourceModel.ReductionSettings.MaxDeviation + 1.0f;
}
}
else
{
ThisSourceModel.ReductionSettings.PercentTriangles = FMath::Pow(0.5f, (float)LODIndex);
}
}
UStaticMesh* UnFbx::FFbxImporter::ReimportStaticMesh(UStaticMesh* Mesh, UFbxStaticMeshImportData* TemplateImportData)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FFbxImporter::ReimportStaticMesh);
FFbxScopedOperation FbxScopedImportOperation(this);
FRestoreReimportData RestoreData(Mesh);
TArray<FbxNode*> FbxMeshArray;
FbxNode* Node = NULL;
UStaticMesh* NewMesh = NULL;
// get meshes in Fbx file
bool bImportStaticMeshLODs = ImportOptions->bImportStaticMeshLODs;
bool bCombineMeshes = ImportOptions->bCombineToSingle;
bool bCombineMeshesLOD = false;
TArray<TArray<FbxNode*>> FbxMeshesLod;
if (bCombineMeshes && !bImportStaticMeshLODs)
{
//the function also fill the collision models, so we can update collision models correctly
FillFbxMeshArray(Scene->GetRootNode(), FbxMeshArray, this);
}
else
{
// count meshes in lod groups if we dont care about importing LODs
bool bCountLODGroupMeshes = !bImportStaticMeshLODs && bCombineMeshes;
int32 NumLODGroups = 0;
GetFbxMeshCount(Scene->GetRootNode(), bCountLODGroupMeshes, NumLODGroups);
// if there were LODs in the file, do not combine meshes even if requested
if (bImportStaticMeshLODs && bCombineMeshes && NumLODGroups > 0)
{
TArray<FbxNode*> FbxLodGroups;
FillFbxMeshAndLODGroupArray(Scene->GetRootNode(), FbxLodGroups, FbxMeshArray);
FbxMeshesLod.Add(FbxMeshArray);
for (FbxNode* LODGroup : FbxLodGroups)
{
if (LODGroup->GetNodeAttribute() && LODGroup->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup && LODGroup->GetChildCount() > 0)
{
for (int32 GroupLodIndex = 0; GroupLodIndex < LODGroup->GetChildCount(); ++GroupLodIndex)
{
if (GroupLodIndex >= MAX_STATIC_MESH_LODS)
{
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(
LOCTEXT("ImporterLimits_MaximumStaticMeshLODReach", "Reached the maximum number of LODs for a Static Mesh({0}) - discarding {1} LOD meshes."), FText::AsNumber(MAX_STATIC_MESH_LODS), FText::AsNumber(LODGroup->GetChildCount() - MAX_STATIC_MESH_LODS))
), FFbxErrors::Generic_Mesh_TooManyLODs);
break;
}
TArray<FbxNode*> AllNodeInLod;
FindAllLODGroupNode(AllNodeInLod, LODGroup, GroupLodIndex);
if (AllNodeInLod.Num() > 0)
{
if (FbxMeshesLod.Num() <= GroupLodIndex)
{
FbxMeshesLod.Add(AllNodeInLod);
}
else
{
TArray<FbxNode*> &LODGroupArray = FbxMeshesLod[GroupLodIndex];
for (FbxNode* NodeToAdd : AllNodeInLod)
{
LODGroupArray.Add(NodeToAdd);
}
}
}
}
}
}
bCombineMeshesLOD = true;
bCombineMeshes = false;
//Set the first LOD
FbxMeshArray = FbxMeshesLod[0];
}
else
{
FillFbxMeshArray(Scene->GetRootNode(), FbxMeshArray, this);
}
}
// if there is only one mesh, use it without name checking
// (because the "Used As Full Name" option enables users name the Unreal mesh by themselves
if (!bCombineMeshesLOD && FbxMeshArray.Num() == 1)
{
Node = FbxMeshArray[0];
}
else if(!bCombineMeshes && !bCombineMeshesLOD)
{
Node = GetMeshNodesFromName(Mesh->GetName(), FbxMeshArray);
}
// If there is no match it may be because an LOD group was imported where
// the mesh name does not match the file name. This is actually the common case.
if (!bCombineMeshesLOD && !Node && FbxMeshArray.IsValidIndex(0))
{
FbxNode* BaseLODNode = FbxMeshArray[0];
FbxNode* NodeParent = BaseLODNode ? RecursiveFindParentLodGroup(BaseLODNode->GetParent()) : nullptr;
if (NodeParent && NodeParent->GetNodeAttribute() && NodeParent->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup)
{
// Reimport the entire LOD chain.
Node = BaseLODNode;
}
}
ImportOptions->bImportMaterials = false;
ImportOptions->bImportTextures = false;
// Before calling SaveExistingStaticMeshData() we must remove the existing fbx metadata as we don't want to restore those.
RemoveFBXMetaData(Mesh);
TSharedPtr<FExistingStaticMeshData> ExistMeshDataPtr = StaticMeshImportUtils::SaveExistingStaticMeshData(Mesh, ImportOptions, INDEX_NONE);
TArray<int32> ReimportLodList;
if (bCombineMeshesLOD)
{
TArray<FbxNode*> LodZeroNodes;
//Import the LOD root
if (FbxMeshesLod.Num() > 0)
{
TArray<FbxNode*> &LODMeshesArray = FbxMeshesLod[0];
LodZeroNodes = FbxMeshesLod[0];
NewMesh = ImportStaticMeshAsSingle(Mesh->GetOuter(), LODMeshesArray, *Mesh->GetName(), RF_Public | RF_Standalone, TemplateImportData, Mesh, 0, ExistMeshDataPtr.Get());
ReimportLodList.Add(0);
}
//Import all LODs
for (int32 LODIndex = 1; LODIndex < FbxMeshesLod.Num(); ++LODIndex)
{
if (LODIndex >= MAX_STATIC_MESH_LODS)
{
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(
LOCTEXT("ImporterLimits_MaximumStaticMeshLODReach", "Reached the maximum number of LODs for a Static Mesh({0}) - discarding {1} LOD meshes."), FText::AsNumber(MAX_STATIC_MESH_LODS), FText::AsNumber(FbxMeshesLod.Num() - MAX_STATIC_MESH_LODS))
), FFbxErrors::Generic_Mesh_TooManyLODs);
break;
}
TArray<FbxNode*> &LODMeshesArray = FbxMeshesLod[LODIndex];
if (LODMeshesArray[0]->GetMesh() == nullptr)
{
AddStaticMeshSourceModelGeneratedLOD(NewMesh, LODIndex);
}
else
{
ImportStaticMeshAsSingle(Mesh->GetOuter(), LODMeshesArray, *Mesh->GetName(), RF_Public | RF_Standalone, TemplateImportData, NewMesh, LODIndex, nullptr);
ReimportLodList.Add(LODIndex);
if (NewMesh && NewMesh->IsSourceModelValid(LODIndex))
{
NewMesh->GetSourceModel(LODIndex).bImportWithBaseMesh = true;
}
}
}
if (NewMesh != nullptr)
{
PostImportStaticMesh(NewMesh, LodZeroNodes);
}
}
else if (Node)
{
FbxNode* NodeParent = RecursiveFindParentLodGroup(Node->GetParent());
TArray<FbxNode*> LodZeroNodes;
// if the Fbx mesh is a part of LODGroup, update LOD
if (NodeParent && NodeParent->GetNodeAttribute() && NodeParent->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup)
{
TArray<FbxNode*> AllNodeInLod;
FindAllLODGroupNode(AllNodeInLod, NodeParent, 0);
if (AllNodeInLod.Num() > 0)
{
LodZeroNodes = AllNodeInLod;
NewMesh = ImportStaticMeshAsSingle(Mesh->GetOuter(), AllNodeInLod, *Mesh->GetName(), RF_Public | RF_Standalone, TemplateImportData, Mesh, 0, ExistMeshDataPtr.Get());
ReimportLodList.Add(0);
}
//If we have a valid LOD group name we don't want to re-import LODs since they will be automatically generate by the LODGroup reduce settings
if (NewMesh && ImportOptions->bImportStaticMeshLODs && Mesh->LODGroup == NAME_None)
{
// import LOD meshes
for (int32 LODIndex = 1; LODIndex < NodeParent->GetChildCount(); LODIndex++)
{
if (LODIndex >= MAX_STATIC_MESH_LODS)
{
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(
LOCTEXT("ImporterLimits_MaximumStaticMeshLODReach", "Reached the maximum number of LODs for a Static Mesh({0}) - discarding {1} LOD meshes."), FText::AsNumber(MAX_STATIC_MESH_LODS), FText::AsNumber(NodeParent->GetChildCount() - MAX_STATIC_MESH_LODS))
), FFbxErrors::Generic_Mesh_TooManyLODs);
break;
}
AllNodeInLod.Empty();
FindAllLODGroupNode(AllNodeInLod, NodeParent, LODIndex);
if (AllNodeInLod.Num() > 0)
{
if (AllNodeInLod[0]->GetMesh() == nullptr)
{
AddStaticMeshSourceModelGeneratedLOD(NewMesh, LODIndex);
}
else
{
//For LOD we don't pass the ExistMeshDataPtr
ImportStaticMeshAsSingle(Mesh->GetOuter(), AllNodeInLod, *Mesh->GetName(), RF_Public | RF_Standalone, TemplateImportData, NewMesh, LODIndex, nullptr);
ReimportLodList.Add(LODIndex);
if (NewMesh->IsSourceModelValid(LODIndex))
{
NewMesh->GetSourceModel(LODIndex).bImportWithBaseMesh = true;
}
}
}
}
}
}
else
{
LodZeroNodes.Add(Node);
NewMesh = ImportStaticMesh(Mesh->GetOuter(), Node, *Mesh->GetName(), RF_Public|RF_Standalone, TemplateImportData, Mesh, 0, ExistMeshDataPtr.Get());
ReimportLodList.Add(0);
}
if (NewMesh != nullptr)
{
PostImportStaticMesh(NewMesh, LodZeroNodes);
}
}
else
{
// no FBX mesh match, maybe the Unreal mesh is imported from multiple FBX mesh (enable option "Import As Single")
if (FbxMeshArray.Num() > 0)
{
NewMesh = ImportStaticMeshAsSingle(Mesh->GetOuter(), FbxMeshArray, *Mesh->GetName(), RF_Public|RF_Standalone, TemplateImportData, Mesh, 0, ExistMeshDataPtr.Get());
ReimportLodList.Add(0);
if (NewMesh != nullptr)
{
PostImportStaticMesh(NewMesh, FbxMeshArray);
}
}
else // no mesh found in the FBX file
{
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("Error_NoFBXMeshFound", "No FBX mesh found when reimport Unreal mesh '{0}'. The FBX file is crashed."), FText::FromString(Mesh->GetName()))), FFbxErrors::Generic_Mesh_MeshNotFound);
}
}
if (NewMesh != nullptr)
{
StaticMeshImportUtils::UpdateSomeLodsImportMeshData(NewMesh, &ReimportLodList);
StaticMeshImportUtils::RestoreExistingMeshData(ExistMeshDataPtr, NewMesh, INDEX_NONE, ImportOptions->bCanShowDialog, ImportOptions->bResetToFbxOnMaterialConflict);
RestoreData.CleanupDuplicateMesh();
}
else
{
RestoreData.RestoreMesh(this);
}
return NewMesh;
}
void UnFbx::FFbxImporter::VerifyGeometry(UStaticMesh* StaticMesh)
{
// Calculate bounding box to check if too small
{
FVector Center, Extents;
ComputeBoundingBox(StaticMesh, Center, Extents);
if (Extents.GetAbsMax() < 5.f)
{
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, LOCTEXT("Prompt_MeshVerySmall", "Warning: The imported mesh is very small. This is most likely an issue with the units used when exporting to FBX.")), FFbxErrors::Generic_Mesh_SmallGeometry);
}
}
}
UStaticMesh* UnFbx::FFbxImporter::ImportStaticMeshAsSingle(UObject* InParent, TArray<FbxNode*>& MeshNodeArray, const FName InName, EObjectFlags Flags, UFbxStaticMeshImportData* TemplateImportData, UStaticMesh* InStaticMesh, int LODIndex, const FExistingStaticMeshData* ExistMeshDataPtr)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FbxImporter::ImportStaticMeshAsSingle);
FFbxScopedOperation FbxScopedImportOperation(this);
bool bBuildStatus = true;
// Make sure rendering is done - so we are not changing data being used by collision drawing.
FlushRenderingCommands();
if (MeshNodeArray.Num() == 0)
{
return NULL;
}
// Count the number of verts
int32 NumVerts = 0;
for (int32 MeshIndex = 0; MeshIndex < MeshNodeArray.Num(); MeshIndex++ )
{
FbxNode* Node = MeshNodeArray[MeshIndex];
FbxMesh* FbxMesh = Node->GetMesh();
if (FbxMesh)
{
NumVerts += FbxMesh->GetControlPointsCount();
// If not combining meshes, reset the vert count between meshes
if (!ImportOptions->bCombineToSingle)
{
NumVerts = 0;
}
}
}
Parent = InParent;
FString MeshName = ObjectTools::SanitizeObjectName(InName.ToString());
// warning for missing smoothing group info
CheckSmoothingInfo(MeshNodeArray[0]->GetMesh());
// Parent package to place new meshes
UPackage* Package = NULL;
if (ImportOptions->bImportScene && InParent != nullptr && InParent->IsA(UPackage::StaticClass()))
{
Package = StaticCast<UPackage*>(InParent);
}
// create empty mesh
UStaticMesh* StaticMesh = NULL;
UStaticMesh* ExistingMesh = NULL;
UObject* ExistingObject = NULL;
// A mapping of vertex positions to their color in the existing static mesh
TMap<FVector3f, FColor> ExistingVertexColorData;
EVertexColorImportOption::Type VertexColorImportOption = ImportOptions->VertexColorImportOption;
FString NewPackageName;
if( InStaticMesh == NULL || LODIndex == 0 )
{
// Create a package for each mesh
if (Package == nullptr)
{
if (Parent != nullptr && Parent->GetOutermost() != nullptr)
{
NewPackageName = FPackageName::GetLongPackagePath(Parent->GetOutermost()->GetName()) + TEXT("/") + MeshName;
}
else
{
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("ImportStaticMeshAsSingle", "Invalid Parent package when importing {0}.\nThe asset will not be imported."), FText::FromString(MeshName))), FFbxErrors::Generic_ImportingNewObjectFailed);
return nullptr;
}
NewPackageName = UPackageTools::SanitizePackageName(NewPackageName);
Package = CreatePackage( *NewPackageName);
}
Package->FullyLoad();
ExistingMesh = FindObject<UStaticMesh>( Package, *MeshName );
ExistingObject = FindObject<UObject>( Package, *MeshName );
}
if (ExistingMesh)
{
ExistingMesh->GetVertexColorData(ExistingVertexColorData);
// Free any RHI resources for existing mesh before we re-create in place.
ExistingMesh->PreEditChange(NULL);
//When we do a reimport the nanite static mesh import setting should not affect the asset since its a "reimportrestrict" option
//In that case we override the import setting
ImportOptions->bBuildNanite = ExistingMesh->IsNaniteEnabled();
}
else if (ExistingObject)
{
// Replacing an object. Here we go!
// Delete the existing object
bool bDeleteSucceeded = ObjectTools::DeleteSingleObject( ExistingObject );
if (bDeleteSucceeded)
{
// Force GC so we can cleanly create a new asset (and not do an 'in place' replacement)
CollectGarbage( GARBAGE_COLLECTION_KEEPFLAGS );
// Create a package for each mesh
Package = CreatePackage( *NewPackageName);
// Require the parent because it will have been invalidated from the garbage collection
Parent = Package;
}
else
{
// failed to delete
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("ContentBrowser_CannotDeleteReferenced", "{0} wasn't created.\n\nThe asset is referenced by other content."), FText::FromString(MeshName))), FFbxErrors::Generic_CannotDeleteReferenced);
return NULL;
}
}
if( InStaticMesh != NULL && LODIndex > 0 )
{
StaticMesh = InStaticMesh;
//Only cancel the operation if we are creating a new asset, as we don't support existing asset restoration yet.
ImportOptions->bIsImportCancelable = false;
}
else
{
StaticMesh = NewObject<UStaticMesh>(Package, FName(*MeshName), Flags | RF_Public);
CreatedObjects.Add(StaticMesh);
}
if (StaticMesh->GetNumSourceModels() < LODIndex+1)
{
// Add one LOD
StaticMesh->AddSourceModel();
if (StaticMesh->GetNumSourceModels() < LODIndex+1)
{
LODIndex = StaticMesh->GetNumSourceModels() - 1;
}
}
FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(LODIndex);
if (MeshDescription == nullptr)
{
MeshDescription = StaticMesh->CreateMeshDescription(LODIndex);
check(MeshDescription != nullptr);
UStaticMesh::FCommitMeshDescriptionParams Params;
Params.bUseHashAsGuid = true;
StaticMesh->CommitMeshDescription(LODIndex, Params);
//Make sure an imported mesh do not get reduce if there was no mesh data before reimport.
//In this case we have a generated LOD convert to a custom LOD
FStaticMeshSourceModel& SrcModel = StaticMesh->GetSourceModel(LODIndex);
SrcModel.ResetReductionSetting();
}
else if (InStaticMesh != NULL && LODIndex > 0)
{
// clear out the old mesh data
MeshDescription->Empty();
}
FStaticMeshAttributes Attributes(*MeshDescription);
// make sure it has a new lighting guid
StaticMesh->SetLightingGuid();
// Set it to use textured lightmaps. Note that Build Lighting will do the error-checking (texcoordindex exists for all LODs, etc).
StaticMesh->SetLightMapResolution(64);
StaticMesh->SetLightMapCoordinateIndex(1);
float SqrBoundingBoxThreshold = THRESH_POINTS_ARE_NEAR * THRESH_POINTS_ARE_NEAR;
bool bAllDegenerated = true;
TArray<FFbxMaterial> MeshMaterials;
int32 NodeFailCount = 0;
FScopedSlowTask BuildMeshSlowTask(MeshNodeArray.Num(), FText::Format(LOCTEXT("FbxStaticMeshBuildGeometryTask", "Importing Static Mesh Geometries {0} of {1}."), FText::AsNumber(0), FText::AsNumber(MeshNodeArray.Num())));
BuildMeshSlowTask.MakeDialog();
for (int32 MeshIndex = 0; MeshIndex < MeshNodeArray.Num(); MeshIndex++)
{
BuildMeshSlowTask.EnterProgressFrame(1, FText::Format(LOCTEXT("FbxStaticMeshBuildGeometryTaskProgress", "Importing Static Mesh Geometry {0} of {1}."), FText::AsNumber(MeshIndex+1), FText::AsNumber(MeshNodeArray.Num())));
FbxNode* Node = MeshNodeArray[MeshIndex];
if (Node->GetMesh())
{
Node->GetMesh()->ComputeBBox();
FbxVector4 GlobalScale = Node->EvaluateGlobalTransform(0).GetS();
FbxVector4 BBoxMax = FbxVector4(Node->GetMesh()->BBoxMax.Get()) * GlobalScale;
FbxVector4 BBoxMin = FbxVector4(Node->GetMesh()->BBoxMin.Get()) * GlobalScale;
FbxVector4 BoxExtend = BBoxMax - BBoxMin;
double SqrSize = BoxExtend.SquareLength();
//If the bounding box of the mesh part is smaller then the position threshold, the part is consider degenerated and will be skip.
if (SqrSize > SqrBoundingBoxThreshold)
{
bAllDegenerated = false;
if(ImportOptions->bIsImportCancelable && BuildMeshSlowTask.ShouldCancel())
{
bImportOperationCanceled = true;
bBuildStatus = false;
break;
}
if (!BuildStaticMeshFromGeometry(Node, StaticMesh, MeshMaterials, LODIndex,
VertexColorImportOption, ExistingVertexColorData, ImportOptions->VertexOverrideColor))
{
// If this FBX node failed to build, make a note and continue.
// We should only fail the mesh/LOD import if every node failed.
NodeFailCount++;
}
}
else
{
CreateTokenizedErrorForDegeneratedPart(this, StaticMesh->GetName(), UTF8_TO_TCHAR(Node->GetName()));
NodeFailCount++;
}
}
}
// If every node in the list failed to build, this counts as a failure to import the entire mesh/LOD.
if (NodeFailCount == MeshNodeArray.Num() || bAllDegenerated)
{
bBuildStatus = false;
}
if (bBuildStatus)
{
TVertexInstanceAttributesRef<FVector2f> VertexInstanceUVs = Attributes.GetVertexInstanceUVs();
int32 FirstOpenUVChannel = VertexInstanceUVs.GetNumChannels() >= MAX_MESH_TEXTURE_COORDS_MD ? 1 : VertexInstanceUVs.GetNumChannels();
TPolygonGroupAttributesRef<FName> PolygonGroupImportedMaterialSlotNames = Attributes.GetPolygonGroupMaterialSlotNames();
TArray<FStaticMaterial> MaterialToAdd;
for (const FPolygonGroupID PolygonGroupID : MeshDescription->PolygonGroups().GetElementIDs())
{
const FName& ImportedMaterialSlotName = PolygonGroupImportedMaterialSlotNames[PolygonGroupID];
const FString ImportedMaterialSlotNameString = ImportedMaterialSlotName.ToString();
const FName MaterialSlotName = ImportedMaterialSlotName;
int32 MaterialIndex = INDEX_NONE;
for (int32 FbxMaterialIndex = 0; FbxMaterialIndex < MeshMaterials.Num(); ++FbxMaterialIndex)
{
FFbxMaterial& FbxMaterial = MeshMaterials[FbxMaterialIndex];
if (FbxMaterial.GetName().Equals(ImportedMaterialSlotNameString))
{
MaterialIndex = FbxMaterialIndex;
break;
}
}
if (MaterialIndex == INDEX_NONE)
{
MaterialIndex = PolygonGroupID.GetValue();
}
UMaterialInterface* Material = MeshMaterials.IsValidIndex(MaterialIndex) ? MeshMaterials[MaterialIndex].Material : UMaterial::GetDefaultMaterial(MD_Surface);
FStaticMaterial StaticMaterial(Material, MaterialSlotName, ImportedMaterialSlotName);
MaterialToAdd.Add(StaticMaterial);
}
{
//Insert the new materials in the static mesh
//The build function will search for imported slot name to find the appropriate slot
for (int32 MaterialToAddIndex = 0; MaterialToAddIndex < MaterialToAdd.Num(); ++MaterialToAddIndex)
{
const FStaticMaterial& CandidateMaterial = MaterialToAdd[MaterialToAddIndex];
bool FoundExistingMaterial = false;
//Found matching existing material
for (int32 StaticMeshMaterialIndex = 0; StaticMeshMaterialIndex < StaticMesh->GetStaticMaterials().Num(); ++StaticMeshMaterialIndex)
{
const FStaticMaterial& StaticMeshMaterial = StaticMesh->GetStaticMaterials()[StaticMeshMaterialIndex];
if (StaticMeshMaterial.ImportedMaterialSlotName == CandidateMaterial.ImportedMaterialSlotName)
{
FoundExistingMaterial = true;
break;
}
}
if (!FoundExistingMaterial)
{
StaticMesh->GetStaticMaterials().Add(CandidateMaterial);
}
}
//Set the Section Info Map to fit the real StaticMaterials array
int32 SectionIndex = 0;
for (const FPolygonGroupID PolygonGroupID : MeshDescription->PolygonGroups().GetElementIDs())
{
const FName& ImportedMaterialSlotName = PolygonGroupImportedMaterialSlotNames[PolygonGroupID];
int32 MaterialIndex = INDEX_NONE;
for (int32 FbxMaterialIndex = 0; FbxMaterialIndex < StaticMesh->GetStaticMaterials().Num(); ++FbxMaterialIndex)
{
FName& StaticMaterialName = StaticMesh->GetStaticMaterials()[FbxMaterialIndex].ImportedMaterialSlotName;
if (StaticMaterialName == ImportedMaterialSlotName)
{
MaterialIndex = FbxMaterialIndex;
break;
}
}
if (MaterialIndex == INDEX_NONE)
{
if (LODIndex > 0 && ExistMeshDataPtr != nullptr)
{
//Do not add Material slot when reimporting a LOD just use the index found in the fbx if valid or use the last MaterialSlot index
MaterialIndex = StaticMesh->GetStaticMaterials().Num() - 1;
}
else
{
MaterialIndex = PolygonGroupID.GetValue();
}
}
FMeshSectionInfo Info = StaticMesh->GetSectionInfoMap().Get(LODIndex, SectionIndex);
Info.MaterialIndex = MaterialIndex;
StaticMesh->GetSectionInfoMap().Remove(LODIndex, SectionIndex);
StaticMesh->GetSectionInfoMap().Set(LODIndex, SectionIndex, Info);
SectionIndex++;
}
}
//Set the original mesh description to be able to do non destructive reduce
UStaticMesh::FCommitMeshDescriptionParams Params;
Params.bUseHashAsGuid = true;
StaticMesh->CommitMeshDescription(LODIndex, Params);
// Setup default LOD settings based on the selected LOD group.
if (LODIndex == 0)
{
ITargetPlatform* CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform();
check(CurrentPlatform);
const FStaticMeshLODGroup& LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(ImportOptions->StaticMeshLODGroup);
int32 NumLODs = LODGroup.GetDefaultNumLODs();
while (StaticMesh->GetNumSourceModels() < NumLODs)
{
StaticMesh->AddSourceModel();
}
for (int32 ModelLODIndex = 0; ModelLODIndex < NumLODs; ++ModelLODIndex)
{
StaticMesh->GetSourceModel(ModelLODIndex).ReductionSettings = LODGroup.GetDefaultSettings(ModelLODIndex);
}
StaticMesh->SetLightMapResolution(LODGroup.GetDefaultLightMapResolution());
//@third party BEGIN SIMPLYGON
/* ImportData->Update(UFactory::GetCurrentFilename());
Developer Note: Update method above computed Hash internally. Hash is calculated based on the file size.
Doing this for CAD files with thousands of components hugely increases the time.
The following method uses a precomputed hash (once per file). Huge time savings.
*/
UFbxStaticMeshImportData* ImportData = UFbxStaticMeshImportData::GetImportDataForStaticMesh(StaticMesh, TemplateImportData);
FString FactoryCurrentFileName = UFactory::GetCurrentFilename();
if (!FactoryCurrentFileName.IsEmpty())
{
//The factory is instantiate only when importing or re-importing the LOD 0
//The LOD re-import is not using the factory so the static function UFactory::GetCurrentFilename()
//will return the last fbx imported asset name or no name if there was no imported asset before.
ImportData->Update(FactoryCurrentFileName, UFactory::GetFileHash());
}
//@third party END SIMPLYGON
}
// @todo This overrides restored values currently but we need to be able to import over the existing settings if the user chose to do so.
FStaticMeshSourceModel& SrcModel = StaticMesh->GetSourceModel(LODIndex);
SrcModel.BuildSettings.bRemoveDegenerates = ImportOptions->bRemoveDegenerates;
SrcModel.BuildSettings.bBuildReversedIndexBuffer = ImportOptions->bBuildReversedIndexBuffer;
SrcModel.BuildSettings.bRecomputeNormals = ImportOptions->NormalImportMethod == FBXNIM_ComputeNormals;
SrcModel.BuildSettings.bRecomputeTangents = ImportOptions->NormalImportMethod != FBXNIM_ImportNormalsAndTangents;
SrcModel.BuildSettings.bUseMikkTSpace = (ImportOptions->NormalGenerationMethod == EFBXNormalGenerationMethod::MikkTSpace) && (!ImportOptions->ShouldImportNormals() || !ImportOptions->ShouldImportTangents());
SrcModel.BuildSettings.bComputeWeightedNormals = ImportOptions->bComputeWeightedNormals;
StaticMesh->NaniteSettings.bEnabled = ImportOptions->bBuildNanite;
SrcModel.BuildSettings.DistanceFieldResolutionScale = ImportOptions->DistanceFieldResolutionScale;
if (ImportOptions->bGenerateLightmapUVs)
{
SrcModel.BuildSettings.bGenerateLightmapUVs = true;
SrcModel.BuildSettings.DstLightmapIndex = FirstOpenUVChannel;
StaticMesh->SetLightMapCoordinateIndex(FirstOpenUVChannel);
}
else
{
SrcModel.BuildSettings.bGenerateLightmapUVs = false;
}
//LODGroup should never change during a re-import or when we import a LOD > 0
if (LODIndex == 0 && InStaticMesh == nullptr)
{
StaticMesh->LODGroup = ImportOptions->StaticMeshLODGroup;
}
//Set the Imported version before calling the build
//We set it here because the remap index is build in RestoreExistingMeshSettings call before the build
StaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion;
if (ExistMeshDataPtr && InStaticMesh)
{
// Patch existing mesh settings to reflect new parameters from import settings
FExistingStaticMeshData* MutableExistMeshDataPtr = const_cast<FExistingStaticMeshData*>(ExistMeshDataPtr);
FMeshBuildSettings& ExistingBuildSettings = MutableExistMeshDataPtr->ExistingLODData[LODIndex].ExistingBuildSettings;
ExistingBuildSettings.bRemoveDegenerates = SrcModel.BuildSettings.bRemoveDegenerates;
ExistingBuildSettings.bBuildReversedIndexBuffer = SrcModel.BuildSettings.bBuildReversedIndexBuffer;
ExistingBuildSettings.bRecomputeNormals = SrcModel.BuildSettings.bRecomputeNormals;
ExistingBuildSettings.bRecomputeTangents = SrcModel.BuildSettings.bRecomputeTangents;
ExistingBuildSettings.bUseMikkTSpace = SrcModel.BuildSettings.bUseMikkTSpace;
ExistingBuildSettings.bComputeWeightedNormals = SrcModel.BuildSettings.bComputeWeightedNormals;
ExistingBuildSettings.bGenerateLightmapUVs = SrcModel.BuildSettings.bGenerateLightmapUVs;
ExistingBuildSettings.DstLightmapIndex = SrcModel.BuildSettings.DstLightmapIndex;
MutableExistMeshDataPtr->ExistingLightMapCoordinateIndex = SrcModel.BuildSettings.DstLightmapIndex;
MutableExistMeshDataPtr->ExistingNaniteSettings.bEnabled = StaticMesh->IsNaniteEnabled();
StaticMeshImportUtils::RestoreExistingMeshSettings(ExistMeshDataPtr, InStaticMesh, StaticMesh->LODGroup != NAME_None ? INDEX_NONE : LODIndex);
}
// The code to check for bad lightmap UVs doesn't scale well with number of triangles.
// Skip it here because Lightmass will warn about it during a light build anyway.
bool bWarnOnBadLightmapUVs = false;
if (bWarnOnBadLightmapUVs)
{
TArray< FString > MissingUVSets;
TArray< FString > BadUVSets;
TArray< FString > ValidUVSets;
UStaticMesh::CheckLightMapUVs(StaticMesh, MissingUVSets, BadUVSets, ValidUVSets);
// NOTE: We don't care about missing UV sets here, just bad ones!
if (BadUVSets.Num() > 0)
{
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(LOCTEXT("Error_UVSetLayoutProblem", "Warning: The light map UV set for static mesh '{0}' appears to have layout problems. Either the triangle UVs are overlapping one another or the UV are out of bounds (0.0 - 1.0 range.)"), FText::FromString(MeshName))), FFbxErrors::StaticMesh_UVSetLayoutProblem);
}
}
}
else
{
// If we couldn't build the static mesh, its package is invalid. We should reject it entirely to prevent issues from arising from trying to use it in the editor.
if (!NewPackageName.IsEmpty())
{
Package->SetDirtyFlag(false);
Package->RemoveFromRoot();
Package->ConditionalBeginDestroy();
}
StaticMesh = NULL;
}
if (StaticMesh)
{
ImportStaticMeshLocalSockets(StaticMesh, MeshNodeArray);
for (FbxNode* Node : MeshNodeArray)
{
ImportNodeCustomProperties(StaticMesh, Node);
}
}
return StaticMesh;
}
void ReorderMaterialAfterImport(UStaticMesh* StaticMesh, TArray<FbxNode*>& MeshNodeArray, bool bAllowFbxReorder)
{
if (StaticMesh == nullptr)
{
return;
}
TArray<FString> MeshMaterials;
for (int32 MeshIndex = 0; MeshIndex < MeshNodeArray.Num(); MeshIndex++)
{
FbxNode* Node = MeshNodeArray[MeshIndex];
if (Node->GetMesh())
{
int32 MaterialCount = Node->GetMaterialCount();
for (int32 MaterialIndex = 0; MaterialIndex < MaterialCount; MaterialIndex++)
{
//Get the original fbx import name
FbxSurfaceMaterial *FbxMaterial = Node->GetMaterial(MaterialIndex);
FString FbxMaterialName = FbxMaterial ? ANSI_TO_TCHAR(FbxMaterial->GetName()) : TEXT("None");
if (!MeshMaterials.Contains(FbxMaterialName))
{
MeshMaterials.Add(FbxMaterialName);
}
}
}
}
//There is no material in the fbx node
if (MeshMaterials.Num() < 1)
{
return;
}
//If there is some skinxx material name we will reorder the material to follow the skinxx workflow instead of the fbx order
bool IsUsingSkinxxWorkflow = true;
TArray<FString> MeshMaterialsSkinXX;
MeshMaterialsSkinXX.AddZeroed(MeshMaterials.Num());
for (int32 FbxMaterialIndex = 0; FbxMaterialIndex < MeshMaterials.Num(); ++FbxMaterialIndex)
{
const FString &FbxMaterialName = MeshMaterials[FbxMaterialIndex];
//If we have all skinxx material name we have to re-order to skinxx workflow
int32 Offset = FbxMaterialName.Find(TEXT("_SKIN"), ESearchCase::IgnoreCase, ESearchDir::FromEnd);
if (Offset == INDEX_NONE)
{
IsUsingSkinxxWorkflow = false;
MeshMaterialsSkinXX.Empty();
break;
}
int32 SkinIndex = INDEX_NONE;
// Chop off the material name so we are left with the number in _SKINXX
FString SkinXXNumber = FbxMaterialName.Right(FbxMaterialName.Len() - (Offset + 1)).RightChop(4);
if (SkinXXNumber.IsNumeric())
{
SkinIndex = FPlatformString::Atoi(*SkinXXNumber);
}
if (SkinIndex >= MeshMaterialsSkinXX.Num())
{
MeshMaterialsSkinXX.AddZeroed((SkinIndex + 1) - MeshMaterialsSkinXX.Num());
}
if (MeshMaterialsSkinXX.IsValidIndex(SkinIndex))
{
MeshMaterialsSkinXX[SkinIndex] = FbxMaterialName;
}
else
{
//Cannot reorder this item
IsUsingSkinxxWorkflow = false;
MeshMaterialsSkinXX.Empty();
break;
}
}
if (IsUsingSkinxxWorkflow)
{
//Shrink the array to valid entry, in case the skinxx has some hole like _skin[01, 02, 04, 05...]
for (int32 FbxMaterialIndex = MeshMaterialsSkinXX.Num() - 1; FbxMaterialIndex >= 0; --FbxMaterialIndex)
{
const FString &FbxMaterial = MeshMaterialsSkinXX[FbxMaterialIndex];
if (FbxMaterial.IsEmpty())
{
MeshMaterialsSkinXX.RemoveAt(FbxMaterialIndex);
}
}
//Replace the fbx ordered materials by the skinxx ordered material
MeshMaterials = MeshMaterialsSkinXX;
}
//Reorder the StaticMaterials array to reflect the order in the fbx file
if (bAllowFbxReorder)
{
//So we make sure the order reflect the material ID in the DCCs
FMeshSectionInfoMap OldSectionInfoMap = StaticMesh->GetSectionInfoMap();
TArray<int32> FbxRemapMaterials;
TArray<FStaticMaterial> NewStaticMaterials;
for (int32 FbxMaterialIndex = 0; FbxMaterialIndex < MeshMaterials.Num(); ++FbxMaterialIndex)
{
const FString &FbxMaterial = MeshMaterials[FbxMaterialIndex];
int32 FoundMaterialIndex = INDEX_NONE;
for (int32 BuildMaterialIndex = 0; BuildMaterialIndex < StaticMesh->GetStaticMaterials().Num(); ++BuildMaterialIndex)
{
FStaticMaterial &BuildMaterial = StaticMesh->GetStaticMaterials()[BuildMaterialIndex];
if (FbxMaterial.Compare(BuildMaterial.ImportedMaterialSlotName.ToString()) == 0)
{
FoundMaterialIndex = BuildMaterialIndex;
break;
}
}
if (FoundMaterialIndex != INDEX_NONE)
{
FbxRemapMaterials.Add(FoundMaterialIndex);
NewStaticMaterials.Add(StaticMesh->GetStaticMaterials()[FoundMaterialIndex]);
}
}
//Add the materials not used by the LOD 0 at the end of the array. The order here is irrelevant since it can be used by many LOD other then LOD 0 and in different order
for (int32 BuildMaterialIndex = 0; BuildMaterialIndex < StaticMesh->GetStaticMaterials().Num(); ++BuildMaterialIndex)
{
const FStaticMaterial &StaticMaterial = StaticMesh->GetStaticMaterials()[BuildMaterialIndex];
bool bFoundMaterial = false;
for (const FStaticMaterial &BuildMaterial : NewStaticMaterials)
{
if (StaticMaterial == BuildMaterial)
{
bFoundMaterial = true;
break;
}
}
if (!bFoundMaterial)
{
FbxRemapMaterials.Add(BuildMaterialIndex);
NewStaticMaterials.Add(StaticMaterial);
}
}
StaticMesh->GetStaticMaterials().Empty();
for (const FStaticMaterial &BuildMaterial : NewStaticMaterials)
{
StaticMesh->GetStaticMaterials().Add(BuildMaterial);
}
//Remap the material instance of the staticmaterial array and remap the material index of all sections
for (int32 LODResoureceIndex = 0; LODResoureceIndex < StaticMesh->GetRenderData()->LODResources.Num(); ++LODResoureceIndex)
{
FStaticMeshLODResources& LOD = StaticMesh->GetRenderData()->LODResources[LODResoureceIndex];
int32 NumSections = LOD.Sections.Num();
for (int32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex)
{
FMeshSectionInfo Info = OldSectionInfoMap.Get(LODResoureceIndex, SectionIndex);
int32 RemapIndex = FbxRemapMaterials.Find(Info.MaterialIndex);
if (StaticMesh->GetStaticMaterials().IsValidIndex(RemapIndex))
{
Info.MaterialIndex = RemapIndex;
StaticMesh->GetSectionInfoMap().Set(LODResoureceIndex, SectionIndex, Info);
StaticMesh->GetOriginalSectionInfoMap().Set(LODResoureceIndex, SectionIndex, Info);
}
}
}
}
}
void UnFbx::FFbxImporter::PostImportStaticMesh(UStaticMesh* StaticMesh, TArray<FbxNode*>& MeshNodeArray, int32 LODIndex)
{
if (StaticMesh == nullptr)
{
return;
}
TRACE_CPUPROFILER_EVENT_SCOPE(FbxImporter::PostImportStaticMesh);
// Build the staticmesh, we move the build here because we want to avoid building the staticmesh for every LOD
// when we import the mesh.
TArray<FText> BuildErrors;
if (GIsAutomationTesting)
{
//Generate a random GUID to be sure it rebuild the asset
StaticMesh->BuildCacheAutomationTestGuid = FGuid::NewGuid();
//Avoid distance field calculation in automation test setting this to false is not suffisant since the condition OR with the CVar
//But fbx automation test turn off the CVAR
StaticMesh->bGenerateMeshDistanceField = false;
}
static const auto CVarDistanceField = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.GenerateMeshDistanceFields"));
int32 OriginalCVarDistanceFieldValue = CVarDistanceField->GetValueOnGameThread();
IConsoleVariable* CVarDistanceFieldInterface = IConsoleManager::Get().FindConsoleVariable(TEXT("r.GenerateMeshDistanceFields"));
bool bOriginalGenerateMeshDistanceField = StaticMesh->bGenerateMeshDistanceField;
//Prebuild the static mesh when we use LodGroup and we want to modify the LodNumber
if (!ImportOptions->bImportScene)
{
//Set the minimum LOD
if (ImportOptions->MinimumLodNumber > 0)
{
StaticMesh->SetMinLODIdx(ImportOptions->MinimumLodNumber);
}
//User specify a number of LOD.
if (ImportOptions->LodNumber > 0)
{
//In case we plan to change the LodNumber we will build the static mesh 2 time
//We have to disable the distance field calculation so it get calculated only during the second build
bool bSpecifiedLodGroup = ImportOptions->StaticMeshLODGroup != NAME_None;
if (bSpecifiedLodGroup)
{
//Avoid building the distance field when we prebuild
if (OriginalCVarDistanceFieldValue != 0 && CVarDistanceFieldInterface)
{
//Hack we change the distance field user console variable to control the build, but we put back the value after the first build
CVarDistanceFieldInterface->SetWithCurrentPriority(0);
}
StaticMesh->bGenerateMeshDistanceField = false;
StaticMesh->Build(false, &BuildErrors);
for (FText& Error : BuildErrors)
{
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, Error), FFbxErrors::StaticMesh_BuildError);
}
StaticMesh->bGenerateMeshDistanceField = bOriginalGenerateMeshDistanceField;
if (OriginalCVarDistanceFieldValue != 0 && CVarDistanceFieldInterface)
{
CVarDistanceFieldInterface->SetWithCurrentPriority(OriginalCVarDistanceFieldValue);
}
}
//Set the Number of LODs, this has to be done after we build the specified LOD Group
int32 LODCount = ImportOptions->LodNumber;
if (LODCount < 0)
{
LODCount = 0;
}
if (LODCount > MAX_STATIC_MESH_LODS)
{
LODCount = MAX_STATIC_MESH_LODS;
}
StaticMesh->SetNumSourceModels(LODCount);
}
}
StaticMesh->Build(false, &BuildErrors);
for (FText& Error : BuildErrors)
{
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, Error), FFbxErrors::StaticMesh_BuildError);
}
//Make sure the light map UVChannel is valid, this must be done after the build
StaticMesh->EnforceLightmapRestrictions();
//Set the specified LOD distances for every LODs we have to do this after the build in case there is a specified Lod Group
if (!ImportOptions->bAutoComputeLodDistances && !ImportOptions->bImportScene)
{
StaticMesh->bAutoComputeLODScreenSize = false;
for (int32 LodIndex = 0; LodIndex < StaticMesh->GetNumSourceModels(); ++LodIndex)
{
FStaticMeshSourceModel& StaticMeshSourceModel = StaticMesh->GetSourceModel(LodIndex);
StaticMeshSourceModel.ScreenSize = ImportOptions->LodDistances.IsValidIndex(LodIndex) ? ImportOptions->LodDistances[LodIndex] : 0.0f;
}
}
// this is damage control. After build, we'd like to absolutely sure that
// all index is pointing correctly and they're all used. Otherwise we remove them
FMeshSectionInfoMap TempOldSectionInfoMap = StaticMesh->GetSectionInfoMap();
StaticMesh->GetSectionInfoMap().Clear();
StaticMesh->GetOriginalSectionInfoMap().Clear();
if (StaticMesh->GetRenderData())
{
// fix up section data
for (int32 LODResoureceIndex = 0; LODResoureceIndex < StaticMesh->GetRenderData()->LODResources.Num(); ++LODResoureceIndex)
{
FStaticMeshLODResources& LOD = StaticMesh->GetRenderData()->LODResources[LODResoureceIndex];
int32 NumSections = LOD.Sections.Num();
for (int32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex)
{
FMeshSectionInfo Info = TempOldSectionInfoMap.Get(LODResoureceIndex, SectionIndex);
if (StaticMesh->GetStaticMaterials().IsValidIndex(Info.MaterialIndex))
{
StaticMesh->GetSectionInfoMap().Set(LODResoureceIndex, SectionIndex, Info);
StaticMesh->GetOriginalSectionInfoMap().Set(LODResoureceIndex, SectionIndex, Info);
}
}
}
}
//collision generation must be done after the build, this will ensure a valid BodySetup
if (StaticMesh->bCustomizedCollision == false && ImportOptions->bAutoGenerateCollision && StaticMesh->GetBodySetup() && LODIndex == 0)
{
FKAggregateGeom & AggGeom = StaticMesh->GetBodySetup()->AggGeom;
AggGeom.ConvexElems.Empty(1); //if no custom collision is setup we just regenerate collision when reimport
const int32 NumDirs = 18;
TArray<FVector> Dirs;
Dirs.AddUninitialized(NumDirs);
for (int32 DirIdx = 0; DirIdx < NumDirs; ++DirIdx) { Dirs[DirIdx] = KDopDir18[DirIdx]; }
GenerateKDopAsSimpleCollision(StaticMesh, Dirs);
}
// Refresh collision change back to staticmesh components, this must be done after the build.
// Some derived components rely on data computed in the build for collision computation. For example,
// USplineMeshComponent uses the static mesh's extended bounds in its convex collision computation.
RefreshCollisionChangeComponentsOnly(*StaticMesh);
//If there is less the 2 materials in the fbx file there is no need to reorder them
//If we have import a LOD other then the base, the material array cannot be sorted, because only the base LOD reorder the material array
if (LODIndex == 0 && StaticMesh->GetStaticMaterials().Num() > 1)
{
ReorderMaterialAfterImport(StaticMesh, MeshNodeArray, ImportOptions->bReorderMaterialToFbxOrder);
}
#if WITH_EDITOR
FMeshBudgetProjectSettingsUtils::SetLodGroupForStaticMesh(StaticMesh);
#endif
}
void UnFbx::FFbxImporter::UpdateStaticMeshImportData(UStaticMesh *StaticMesh, UFbxStaticMeshImportData* StaticMeshImportData)
{
if (StaticMesh == nullptr || StaticMesh->GetRenderData() == nullptr)
{
return;
}
UFbxStaticMeshImportData* ImportData = Cast<UFbxStaticMeshImportData>(StaticMesh->AssetImportData);
if (!ImportData && StaticMeshImportData)
{
ImportData = UFbxStaticMeshImportData::GetImportDataForStaticMesh(StaticMesh, StaticMeshImportData);
}
if (ImportData)
{
ImportData->ImportMaterialOriginalNameData.Empty();
ImportData->ImportMeshLodData.Empty();
for (const FStaticMaterial &Material : StaticMesh->GetStaticMaterials())
{
ImportData->ImportMaterialOriginalNameData.Add(Material.ImportedMaterialSlotName);
}
for (int32 LODResoureceIndex = 0; LODResoureceIndex < StaticMesh->GetRenderData()->LODResources.Num(); ++LODResoureceIndex)
{
ImportData->ImportMeshLodData.AddZeroed();
FStaticMeshLODResources& LOD = StaticMesh->GetRenderData()->LODResources[LODResoureceIndex];
int32 NumSections = LOD.Sections.Num();
for (int32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex)
{
int32 MaterialLodSectionIndex = LOD.Sections[SectionIndex].MaterialIndex;
if (StaticMesh->GetSectionInfoMap().GetSectionNumber(LODResoureceIndex) > SectionIndex)
{
//In case we have a different ordering then the original fbx order use the sectioninfomap
const FMeshSectionInfo &SectionInfo = StaticMesh->GetSectionInfoMap().Get(LODResoureceIndex, SectionIndex);
MaterialLodSectionIndex = SectionInfo.MaterialIndex;
}
if (ImportData->ImportMaterialOriginalNameData.IsValidIndex(MaterialLodSectionIndex))
{
ImportData->ImportMeshLodData[LODResoureceIndex].SectionOriginalMaterialName.Add(ImportData->ImportMaterialOriginalNameData[MaterialLodSectionIndex]);
}
else
{
ImportData->ImportMeshLodData[LODResoureceIndex].SectionOriginalMaterialName.Add(TEXT("InvalidMaterialIndex"));
}
}
}
}
}
struct FbxSocketNode
{
FName SocketName;
FbxNode* Node;
};
static void FindMeshSockets( FbxNode* StartNode, TArray<FbxSocketNode>& OutFbxSocketNodes )
{
if( !StartNode )
{
return;
}
static const FString SocketPrefix( TEXT("SOCKET_") );
if( StartNode->GetNodeAttributeCount() > 0 )
{
// Find null attributes, they cold be sockets
FbxNodeAttribute* Attribute = StartNode->GetNodeAttribute();
if( Attribute != NULL && (Attribute->GetAttributeType() == FbxNodeAttribute::eNull || Attribute->GetAttributeType() == FbxNodeAttribute::eSkeleton))
{
// Is this prefixed correctly? If so it is a socket
FString SocketName = UTF8_TO_TCHAR( StartNode->GetName() );
if( SocketName.StartsWith( SocketPrefix ) )
{
// Remove the prefix from the name
SocketName.RightChopInline( SocketPrefix.Len(), EAllowShrinking::No );
FbxSocketNode NewNode;
NewNode.Node = StartNode;
NewNode.SocketName = *SocketName;
OutFbxSocketNodes.Add( NewNode );
}
}
}
// Recursively examine all children
for ( int32 ChildIndex=0; ChildIndex < StartNode->GetChildCount(); ++ChildIndex )
{
FindMeshSockets( StartNode->GetChild(ChildIndex), OutFbxSocketNodes );
}
}
void UnFbx::FFbxImporter::ImportStaticMeshLocalSockets(UStaticMesh* StaticMesh, TArray<FbxNode*>& MeshNodeArray)
{
check(MeshNodeArray.Num());
FbxNode *MeshRootNode = MeshNodeArray[0];
const FbxAMatrix &MeshTotalMatrix = ComputeTotalMatrix(MeshRootNode);
TArray<FbxSocketNode> AllSocketNodes;
for (FbxNode* RootNode : MeshNodeArray)
{
// Find all nodes that are sockets
TArray<FbxSocketNode> SocketNodes;
FindMeshSockets(RootNode, SocketNodes);
for (int32 SocketIndex = 0; SocketIndex < SocketNodes.Num(); ++SocketIndex)
{
bool bFoundNewSocket = true;
for (int32 AllSocketIndex = 0; AllSocketIndex < AllSocketNodes.Num(); ++AllSocketIndex)
{
if (AllSocketNodes[AllSocketIndex].SocketName == SocketNodes[SocketIndex].SocketName)
{
bFoundNewSocket = false;
break;
}
}
if (bFoundNewSocket)
{
AllSocketNodes.Add(SocketNodes[SocketIndex]);
}
}
}
// Create a UStaticMeshSocket for each fbx socket
for (int32 SocketIndex = 0; SocketIndex < AllSocketNodes.Num(); ++SocketIndex)
{
FbxSocketNode& SocketNode = AllSocketNodes[SocketIndex];
UStaticMeshSocket* Socket = StaticMesh->FindSocket(SocketNode.SocketName);
if (!Socket)
{
// If the socket didn't exist create a new one now
Socket = NewObject<UStaticMeshSocket>(StaticMesh);
Socket->bSocketCreatedAtImport = true;
check(Socket);
Socket->SocketName = SocketNode.SocketName;
StaticMesh->AddSocket(Socket);
}
if (Socket)
{
const FbxAMatrix& SocketMatrix = Scene->GetAnimationEvaluator()->GetNodeLocalTransform(SocketNode.Node);
//Remove the axis conversion for the socket since its attach to a mesh containing this conversion.
FbxAMatrix FinalSocketMatrix = (MeshTotalMatrix * SocketMatrix) * FFbxDataConverter::GetAxisConversionMatrixInv();
FTransform SocketTransform;
SocketTransform.SetTranslation(Converter.ConvertPos(FinalSocketMatrix.GetT()));
SocketTransform.SetRotation(Converter.ConvertRotToQuat(FinalSocketMatrix.GetQ()));
SocketTransform.SetScale3D(Converter.ConvertScale(FinalSocketMatrix.GetS()));
Socket->RelativeLocation = SocketTransform.GetLocation();
Socket->RelativeRotation = SocketTransform.GetRotation().Rotator();
Socket->RelativeScale = SocketTransform.GetScale3D();
}
}
// Delete mesh sockets that were removed from the import data
for (int32 MeshSocketIx = 0; MeshSocketIx < StaticMesh->Sockets.Num(); ++MeshSocketIx)
{
bool Found = false;
UStaticMeshSocket* MeshSocket = StaticMesh->Sockets[MeshSocketIx];
//Do not remove socket that was not generated at import
if (!MeshSocket->bSocketCreatedAtImport)
{
continue;
}
for (int32 FbxSocketIx = 0; FbxSocketIx < AllSocketNodes.Num(); FbxSocketIx++)
{
if (AllSocketNodes[FbxSocketIx].SocketName == MeshSocket->SocketName)
{
Found = true;
break;
}
}
if (!Found)
{
StaticMesh->Sockets.RemoveAt(MeshSocketIx);
MeshSocketIx--;
}
}
}
void UnFbx::FFbxImporter::ImportStaticMeshGlobalSockets( UStaticMesh* StaticMesh )
{
FbxNode* RootNode = Scene->GetRootNode();
// Find all nodes that are sockets
TArray<FbxSocketNode> SocketNodes;
FindMeshSockets( RootNode, SocketNodes );
// Create a UStaticMeshSocket for each fbx socket
for( int32 SocketIndex = 0; SocketIndex < SocketNodes.Num(); ++SocketIndex )
{
FbxSocketNode& SocketNode = SocketNodes[ SocketIndex ];
UStaticMeshSocket* Socket = StaticMesh->FindSocket( SocketNode.SocketName );
if( !Socket )
{
// If the socket didn't exist create a new one now
Socket = NewObject<UStaticMeshSocket>(StaticMesh);
check(Socket);
Socket->SocketName = SocketNode.SocketName;
StaticMesh->AddSocket(Socket);
//Remove the axis conversion for the socket since its attach to a mesh containing this conversion.
const FbxAMatrix& SocketMatrix = Scene->GetAnimationEvaluator()->GetNodeGlobalTransform(SocketNode.Node) * FFbxDataConverter::GetAxisConversionMatrixInv();
FTransform SocketTransform;
SocketTransform.SetTranslation(Converter.ConvertPos(SocketMatrix.GetT()));
SocketTransform.SetRotation(Converter.ConvertRotToQuat(SocketMatrix.GetQ()));
SocketTransform.SetScale3D(Converter.ConvertScale(SocketMatrix.GetS()));
Socket->RelativeLocation = SocketTransform.GetLocation();
Socket->RelativeRotation = SocketTransform.GetRotation().Rotator();
Socket->RelativeScale = SocketTransform.GetScale3D();
Socket->bSocketCreatedAtImport = true;
}
}
for (int32 MeshSocketIx = 0; MeshSocketIx < StaticMesh->Sockets.Num(); ++MeshSocketIx)
{
bool Found = false;
UStaticMeshSocket* MeshSocket = StaticMesh->Sockets[MeshSocketIx];
//Do not remove socket that was not generated at import
if (!MeshSocket->bSocketCreatedAtImport)
{
continue;
}
for (int32 FbxSocketIx = 0; FbxSocketIx < SocketNodes.Num(); FbxSocketIx++)
{
if (SocketNodes[FbxSocketIx].SocketName == MeshSocket->SocketName)
{
Found = true;
break;
}
}
if (!Found)
{
StaticMesh->Sockets.RemoveAt(MeshSocketIx);
MeshSocketIx--;
}
}
}
bool UnFbx::FFbxImporter::FillCollisionModelList(FbxNode* Node)
{
FbxString NodeName = GetNodeNameWithoutNamespace( Node->GetName() );
if ( NodeName.Find("UCX") != -1 || NodeName.Find("MCDCX") != -1 ||
NodeName.Find("UBX") != -1 || NodeName.Find("USP") != -1 || NodeName.Find("UCP") != -1)
{
// Get name of static mesh that the collision model connect to
uint32 StartIndex = NodeName.Find('_') + 1;
int32 TmpEndIndex = NodeName.Find('_', StartIndex);
int32 EndIndex = TmpEndIndex;
// Find the last '_' (underscore)
while (TmpEndIndex >= 0)
{
EndIndex = TmpEndIndex;
TmpEndIndex = NodeName.Find('_', EndIndex+1);
}
const int32 NumMeshNames = 2;
FbxString MeshName[NumMeshNames];
if ( EndIndex >= 0 )
{
// all characters between the first '_' and the last '_' are the FBX mesh name
// convert the name to upper because we are case insensitive
MeshName[0] = NodeName.Mid(StartIndex, EndIndex - StartIndex).Upper();
// also add a version of the mesh name that includes what follows the last '_'
// in case that's not a suffix but, instead, is part of the mesh name
if (StartIndex < (int32)NodeName.GetLen())
{
MeshName[1] = NodeName.Mid(StartIndex).Upper();
}
}
else if (StartIndex < (int32)NodeName.GetLen())
{
MeshName[0] = NodeName.Mid(StartIndex).Upper();
}
for (int32 NameIdx = 0; NameIdx < NumMeshNames; ++NameIdx)
{
if ((int32)MeshName[NameIdx].GetLen() > 0)
{
FbxMap<FbxString, TSharedPtr<FbxArray<FbxNode* > > >::RecordType const *Models = CollisionModels.Find(MeshName[NameIdx]);
TSharedPtr< FbxArray<FbxNode* > > Record;
if ( !Models )
{
Record = MakeShareable( new FbxArray<FbxNode*>() );
CollisionModels.Insert(MeshName[NameIdx], Record);
}
else
{
Record = Models->GetValue();
}
//Unique add
Record->AddUnique(Node);
}
}
return true;
}
return false;
}
bool UnFbx::FFbxImporter::ImportCollisionModels(UStaticMesh* StaticMesh, const FbxString& InNodeName)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FFbxImporter::ImportCollisionModels);
// find collision models
bool bRemoveEmptyKey = false;
FbxString EmptyKey;
// convert the name to upper because we are case insensitive
FbxMap<FbxString, TSharedPtr< FbxArray<FbxNode* > > >::RecordType const *Record = CollisionModels.Find(InNodeName.Upper());
if ( !Record )
{
// compatible with old collision name format
// if CollisionModels has only one entry and the key is ""
if ( CollisionModels.GetSize() == 1 )
{
Record = CollisionModels.Find( EmptyKey );
}
if ( !Record )
{
return false;
}
else
{
bRemoveEmptyKey = true;
}
}
TSharedPtr< FbxArray<FbxNode*> > Models = Record->GetValue();
bool bAtLeastOneCollisionMeshImported = false;
StaticMesh->CreateBodySetup();
TArray<FVector3f> CollisionVertices;
TArray<int32> CollisionFaceIdx;
// construct collision model
for (int32 ModelIndex=0; ModelIndex<Models->GetCount(); ModelIndex++)
{
FbxNode* Node = Models->GetAt(ModelIndex);
FbxMesh* FbxMesh = Node->GetMesh();
FbxMesh->RemoveBadPolygons();
// Must do this before triangulating the mesh due to an FBX bug in TriangulateMeshAdvance
int32 LayerSmoothingCount = FbxMesh->GetLayerCount(FbxLayerElement::eSmoothing);
for(int32 LayerIndex = 0; LayerIndex < LayerSmoothingCount; LayerIndex++)
{
GeometryConverter->ComputePolygonSmoothingFromEdgeSmoothing (FbxMesh, LayerIndex);
}
if (!FbxMesh->IsTriangleMesh())
{
FString NodeName = MakeName(Node->GetName());
UE_LOG(LogFbx, Display, TEXT("Triangulating mesh %s for collision model"), *NodeName);
const bool bReplace = true;
FbxNodeAttribute* ConvertedNode = GeometryConverter->Triangulate(FbxMesh, bReplace); // not in place ! the old mesh is still there
if( ConvertedNode != NULL && ConvertedNode->GetAttributeType() == FbxNodeAttribute::eMesh )
{
FbxMesh = (fbxsdk::FbxMesh*)ConvertedNode;
}
else
{
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(LOCTEXT("Error_FailedToTriangulate", "Unable to triangulate mesh '{0}'"), FText::FromString(NodeName))), FFbxErrors::Generic_Mesh_TriangulationFailed);
return false;
}
}
int32 ControlPointsIndex;
int32 ControlPointsCount = FbxMesh->GetControlPointsCount();
FbxVector4* ControlPoints = FbxMesh->GetControlPoints();
FbxAMatrix Matrix = ComputeTotalMatrix(Node);
for ( ControlPointsIndex = 0; ControlPointsIndex < ControlPointsCount; ControlPointsIndex++ )
{
new(CollisionVertices)FVector3f(Converter.ConvertPos(Matrix.MultT(ControlPoints[ControlPointsIndex])));
}
int32 TriangleCount = FbxMesh->GetPolygonCount();
int32 TriangleIndex;
for ( TriangleIndex = 0 ; TriangleIndex < TriangleCount ; TriangleIndex++ )
{
new(CollisionFaceIdx)int32(FbxMesh->GetPolygonVertex(TriangleIndex,0));
new(CollisionFaceIdx)int32(FbxMesh->GetPolygonVertex(TriangleIndex,1));
new(CollisionFaceIdx)int32(FbxMesh->GetPolygonVertex(TriangleIndex,2));
}
TArray<FPoly> CollisionTriangles;
// Make triangles
for(int32 x = 0;x < CollisionFaceIdx.Num();x += 3)
{
FPoly* Poly = new( CollisionTriangles ) FPoly();
Poly->Init();
new(Poly->Vertices) FVector3f( CollisionVertices[CollisionFaceIdx[x + 2]] );
new(Poly->Vertices) FVector3f( CollisionVertices[CollisionFaceIdx[x + 1]] );
new(Poly->Vertices) FVector3f( CollisionVertices[CollisionFaceIdx[x + 0]] );
Poly->iLink = x / 3;
Poly->CalcNormal(1);
}
// Construct geometry object
FbxString ModelName(Node->GetName());
if ( ModelName.Find("UCX") != -1 || ModelName.Find("MCDCX") != -1 )
{
if( !ImportOptions->bOneConvexHullPerUCX )
{
if (StaticMeshImportUtils::DecomposeUCXMesh(CollisionVertices, CollisionFaceIdx, StaticMesh->GetBodySetup()))
{
bAtLeastOneCollisionMeshImported = true;
}
else
{
FString CollisionModelName = UTF8_TO_TCHAR(ModelName.Buffer());
AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(LOCTEXT("Error_DecomposingCollisionMesh", "Could not decompose collision mesh [{0}]."), FText::FromString(CollisionModelName))), FFbxErrors::StaticMesh_BuildError);
}
}
else
{
FKAggregateGeom& AggGeo = StaticMesh->GetBodySetup()->AggGeom;
// This function cooks the given data, so we cannot test for duplicates based on the position data
// before we call it
if (StaticMeshImportUtils::AddConvexGeomFromVertices(CollisionVertices, &AggGeo, ANSI_TO_TCHAR(Node->GetName())))
{
bAtLeastOneCollisionMeshImported = true;
FKConvexElem& NewElem = AggGeo.ConvexElems.Last();
// Now test the late element in the AggGeo list and remove it if its a duplicate
for(int32 ElementIndex = 0; ElementIndex < AggGeo.ConvexElems.Num()-1; ++ElementIndex)
{
FKConvexElem& CurrentElem = AggGeo.ConvexElems[ElementIndex];
if(CurrentElem.VertexData.Num() == NewElem.VertexData.Num())
{
bool bFoundDifference = false;
for(int32 VertexIndex = 0; VertexIndex < NewElem.VertexData.Num(); ++VertexIndex)
{
if(CurrentElem.VertexData[VertexIndex] != NewElem.VertexData[VertexIndex])
{
bFoundDifference = true;
break;
}
}
if(!bFoundDifference)
{
// The new collision geo is a duplicate, delete it
AggGeo.ConvexElems.RemoveAt(AggGeo.ConvexElems.Num()-1);
break;
}
}
}
}
}
}
else if ( ModelName.Find("UBX") != -1 )
{
FKAggregateGeom& AggGeo = StaticMesh->GetBodySetup()->AggGeom;
if(StaticMeshImportUtils::AddBoxGeomFromTris(CollisionTriangles, &AggGeo, ANSI_TO_TCHAR(Node->GetName())))
{
bAtLeastOneCollisionMeshImported = true;
FKBoxElem& NewElem = AggGeo.BoxElems.Last();
// Now test the last element in the AggGeo list and remove it if its a duplicate
for(int32 ElementIndex = 0; ElementIndex < AggGeo.BoxElems.Num()-1; ++ElementIndex)
{
FKBoxElem& CurrentElem = AggGeo.BoxElems[ElementIndex];
if( CurrentElem == NewElem )
{
// The new element is a duplicate, remove it
AggGeo.BoxElems.RemoveAt(AggGeo.BoxElems.Num()-1);
break;
}
}
}
}
else if ( ModelName.Find("USP") != -1 )
{
FKAggregateGeom& AggGeo = StaticMesh->GetBodySetup()->AggGeom;
if (StaticMeshImportUtils::AddSphereGeomFromVerts(CollisionVertices, &AggGeo, ANSI_TO_TCHAR(Node->GetName())))
{
bAtLeastOneCollisionMeshImported = true;
FKSphereElem& NewElem = AggGeo.SphereElems.Last();
// Now test the late element in the AggGeo list and remove it if its a duplicate
for(int32 ElementIndex = 0; ElementIndex < AggGeo.SphereElems.Num()-1; ++ElementIndex)
{
FKSphereElem& CurrentElem = AggGeo.SphereElems[ElementIndex];
if( CurrentElem == NewElem )
{
// The new element is a duplicate, remove it
AggGeo.SphereElems.RemoveAt(AggGeo.SphereElems.Num()-1);
break;
}
}
}
}
else if (ModelName.Find("UCP") != -1)
{
FKAggregateGeom& AggGeo = StaticMesh->GetBodySetup()->AggGeom;
if (StaticMeshImportUtils::AddCapsuleGeomFromVerts(CollisionVertices, &AggGeo, ANSI_TO_TCHAR(Node->GetName())))
{
bAtLeastOneCollisionMeshImported = true;
FKSphylElem& NewElem = AggGeo.SphylElems.Last();
// Now test the late element in the AggGeo list and remove it if its a duplicate
for (int32 ElementIndex = 0; ElementIndex < AggGeo.SphylElems.Num() - 1; ++ElementIndex)
{
FKSphylElem& CurrentElem = AggGeo.SphylElems[ElementIndex];
if (CurrentElem == NewElem)
{
// The new element is a duplicate, remove it
AggGeo.SphylElems.RemoveAt(AggGeo.SphylElems.Num() - 1);
break;
}
}
}
}
// Clear any cached rigid-body collision shapes for this body setup.
StaticMesh->GetBodySetup()->ClearPhysicsMeshes();
// Remove the empty key because we only use the model once for the first mesh
if (bRemoveEmptyKey)
{
CollisionModels.Remove(EmptyKey);
}
CollisionVertices.Empty();
CollisionFaceIdx.Empty();
}
if (bAtLeastOneCollisionMeshImported)
{
StaticMesh->bCustomizedCollision = true;
}
// Create new GUID
StaticMesh->GetBodySetup()->InvalidatePhysicsData();
// Update collision on the static mesh
// Note: do not refresh collision on components here, this must occur after the static mesh build.
{
TRACE_CPUPROFILER_EVENT_SCOPE(CreateStaticMeshNavCollision);
StaticMesh->CreateNavCollision(true);
}
return bAtLeastOneCollisionMeshImported;
}
#undef LOCTEXT_NAMESPACE