2392 lines
109 KiB
C++
2392 lines
109 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Factories/FbxSceneImportFactory.h"
|
|
#include "Misc/CoreMisc.h"
|
|
#include "Misc/MessageDialog.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Misc/FeedbackContext.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "UObject/Package.h"
|
|
#include "Misc/PackageName.h"
|
|
#include "Widgets/DeclarativeSyntaxSupport.h"
|
|
#include "Widgets/SWindow.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Engine/EngineTypes.h"
|
|
#include "Components/SceneComponent.h"
|
|
#include "Materials/MaterialInterface.h"
|
|
#include "Components/StaticMeshComponent.h"
|
|
#include "ActorFactories/ActorFactoryEmptyActor.h"
|
|
#include "Engine/SkeletalMesh.h"
|
|
#include "Components/SkeletalMeshComponent.h"
|
|
#include "Materials/Material.h"
|
|
#include "Engine/Texture.h"
|
|
#include "Factories/FbxAnimSequenceImportData.h"
|
|
#include "Factories/FbxSkeletalMeshImportData.h"
|
|
#include "Factories/FbxStaticMeshImportData.h"
|
|
#include "Factories/FbxTextureImportData.h"
|
|
#include "Factories/FbxSceneImportData.h"
|
|
#include "Factories/FbxSceneImportOptions.h"
|
|
#include "Factories/FbxSceneImportOptionsSkeletalMesh.h"
|
|
#include "Factories/FbxSceneImportOptionsStaticMesh.h"
|
|
#include "EngineGlobals.h"
|
|
#include "Camera/CameraComponent.h"
|
|
#include "Components/SpotLightComponent.h"
|
|
#include "Components/PointLightComponent.h"
|
|
#include "Engine/StaticMesh.h"
|
|
#include "Engine/StaticMeshActor.h"
|
|
#include "Components/DirectionalLightComponent.h"
|
|
#include "AssetData.h"
|
|
#include "Editor.h"
|
|
#include "FileHelpers.h"
|
|
#include "CineCameraComponent.h"
|
|
#include "Rendering/SkeletalMeshModel.h"
|
|
|
|
#include "AssetSelection.h"
|
|
|
|
#include "Logging/TokenizedMessage.h"
|
|
#include "FbxImporter.h"
|
|
#include "Misc/FbxErrors.h"
|
|
#include "AssetRegistryModule.h"
|
|
|
|
#include "Fbx/SSceneImportNodeTreeView.h"
|
|
#include "SFbxSceneOptionWindow.h"
|
|
#include "Interfaces/IMainFrameModule.h"
|
|
#include "Interfaces/ITargetPlatform.h"
|
|
#include "Interfaces/ITargetPlatformManagerModule.h"
|
|
#include "PackageTools.h"
|
|
|
|
#include "Kismet2/KismetEditorUtilities.h"
|
|
#include "Math/UnitConversion.h"
|
|
|
|
#include "HAL/FileManager.h"
|
|
#include "LODUtilities.h"
|
|
#include "ComponentReregisterContext.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "FBXSceneImportFactory"
|
|
|
|
using namespace UnFbx;
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// TODO list
|
|
// -. Set the combineMesh to true when importing a group of staticmesh
|
|
// -. Export correctly skeletal mesh, in fbxreview the skeletal mesh is not
|
|
// playing is animation.
|
|
// -. Create some tests
|
|
// -. Support for camera
|
|
// -. Support for light
|
|
|
|
// Initialize static default option name
|
|
FString UFbxSceneImportFactory::DefaultOptionName = FString(TEXT("Default"));
|
|
|
|
FbxNode* FindFbxNodeById(UnFbx::FFbxImporter* FbxImporter, FbxNode* CurrentNode, uint64 UniqueId)
|
|
{
|
|
if (CurrentNode == nullptr)
|
|
{
|
|
CurrentNode = FbxImporter->Scene->GetRootNode();
|
|
}
|
|
if (CurrentNode->GetUniqueID() == UniqueId)
|
|
{
|
|
return CurrentNode;
|
|
}
|
|
for (int32 ChildIndex = 0; ChildIndex < CurrentNode->GetChildCount(); ++ChildIndex)
|
|
{
|
|
FbxNode* FoundNode = FindFbxNodeById(FbxImporter, CurrentNode->GetChild(ChildIndex), UniqueId);
|
|
if (FoundNode != nullptr)
|
|
{
|
|
return FoundNode;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool GetFbxSceneImportOptions(UnFbx::FFbxImporter* FbxImporter, TSharedPtr<FFbxSceneInfo> SceneInfoPtr, UnFbx::FBXImportOptions* GlobalImportSettings, UFbxSceneImportOptions* SceneImportOptions, UFbxSceneImportOptionsStaticMesh* StaticMeshImportData, ImportOptionsNameMap& NameOptionsMap, UFbxSceneImportOptionsSkeletalMesh* SkeletalMeshImportData, FString Path)
|
|
{
|
|
// Make sure we don't put the global transform into the vertex position of the mesh
|
|
GlobalImportSettings->bTransformVertexToAbsolute = false;
|
|
// Avoid combining meshes
|
|
GlobalImportSettings->bCombineToSingle = false;
|
|
// Use the full name (avoid creating one) to let us retrieve node transform and attachment later
|
|
GlobalImportSettings->bUsedAsFullName = true;
|
|
// Make sure we import the textures
|
|
GlobalImportSettings->bImportTextures = true;
|
|
// Make sure Material get imported
|
|
GlobalImportSettings->bImportMaterials = true;
|
|
// TODO support T0AsRefPose
|
|
GlobalImportSettings->bUseT0AsRefPose = false;
|
|
|
|
GlobalImportSettings->ImportTranslation = FVector(0);
|
|
GlobalImportSettings->ImportRotation = FRotator(0);
|
|
GlobalImportSettings->ImportUniformScale = 1.0f;
|
|
|
|
GlobalImportSettings->bConvertScene = true;
|
|
GlobalImportSettings->bConvertSceneUnit = true;
|
|
|
|
GlobalImportSettings->bBakePivotInVertex = SceneImportOptions->bBakePivotInVertex;
|
|
GlobalImportSettings->bInvertNormalMap = SceneImportOptions->bInvertNormalMaps;
|
|
|
|
// TODO this options will be set by the fbxscene UI in the material options tab, it also should be save/load from config file
|
|
// Prefix materials package name to put all material under Material folder (this avoid name clash with meshes)
|
|
GlobalImportSettings->MaterialBasePath = NAME_None;
|
|
|
|
GlobalImportSettings->OverrideMaterials.Reset();
|
|
|
|
// Don't show the import options in unattended mode
|
|
if (!GIsRunningUnattendedScript)
|
|
{
|
|
TSharedPtr<SWindow> ParentWindow;
|
|
if (FModuleManager::Get().IsModuleLoaded("MainFrame"))
|
|
{
|
|
IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked<IMainFrameModule>("MainFrame");
|
|
ParentWindow = MainFrame.GetParentWindow();
|
|
}
|
|
TSharedRef<SWindow> Window = SNew(SWindow)
|
|
.ClientSize(FVector2D(820.f, 650.f))
|
|
.Title(NSLOCTEXT("UnrealEd", "FBXSceneImportOpionsTitle", "FBX Scene Import Options"));
|
|
TSharedPtr<SFbxSceneOptionWindow> FbxSceneOptionWindow;
|
|
|
|
Window->SetContent(
|
|
SAssignNew(FbxSceneOptionWindow, SFbxSceneOptionWindow)
|
|
.SceneInfo(SceneInfoPtr)
|
|
.GlobalImportSettings(GlobalImportSettings)
|
|
.SceneImportOptionsDisplay(SceneImportOptions)
|
|
.SceneImportOptionsStaticMeshDisplay(StaticMeshImportData)
|
|
.OverrideNameOptionsMap(&NameOptionsMap)
|
|
.SceneImportOptionsSkeletalMeshDisplay(SkeletalMeshImportData)
|
|
.OwnerWindow(Window)
|
|
.FullPath(Path));
|
|
|
|
FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false);
|
|
|
|
if (!FbxSceneOptionWindow->ShouldImport())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Copy static and skeletalmesh options
|
|
SFbxSceneOptionWindow::CopyStaticMeshOptionsToFbxOptions(GlobalImportSettings, StaticMeshImportData);
|
|
SFbxSceneOptionWindow::CopySkeletalMeshOptionsToFbxOptions(GlobalImportSettings, SkeletalMeshImportData);
|
|
NameOptionsMap.Add(UFbxSceneImportFactory::DefaultOptionName, GlobalImportSettings);
|
|
}
|
|
|
|
// setup all options
|
|
GlobalImportSettings->bForceFrontXAxis = SceneImportOptions->bForceFrontXAxis;
|
|
GlobalImportSettings->bBakePivotInVertex = SceneImportOptions->bBakePivotInVertex;
|
|
GlobalImportSettings->bImportStaticMeshLODs = SceneImportOptions->bImportStaticMeshLODs;
|
|
GlobalImportSettings->bImportSkeletalMeshLODs = SceneImportOptions->bImportSkeletalMeshLODs;
|
|
GlobalImportSettings->bInvertNormalMap = SceneImportOptions->bInvertNormalMaps;
|
|
GlobalImportSettings->ImportTranslation = SceneImportOptions->ImportTranslation;
|
|
GlobalImportSettings->ImportRotation = SceneImportOptions->ImportRotation;
|
|
GlobalImportSettings->ImportUniformScale = SceneImportOptions->ImportUniformScale;
|
|
|
|
// Set the override material into the options
|
|
for (TSharedPtr<FFbxNodeInfo> NodeInfo: SceneInfoPtr->HierarchyInfo)
|
|
{
|
|
for (TSharedPtr<FFbxMaterialInfo> Material: NodeInfo->Materials)
|
|
{
|
|
if (!GlobalImportSettings->OverrideMaterials.Contains(Material->UniqueId))
|
|
{
|
|
// If user dont want to import a material we have to replace it by the default material
|
|
if (!Material->bImportAttribute)
|
|
{
|
|
UMaterial* DefaultMaterial = UMaterial::GetDefaultMaterial(MD_Surface);
|
|
if (DefaultMaterial != nullptr)
|
|
{
|
|
GlobalImportSettings->OverrideMaterials.Add(Material->UniqueId, DefaultMaterial);
|
|
}
|
|
}
|
|
else if (Material->bOverridePath)
|
|
{
|
|
UMaterialInterface* UnrealMaterial = static_cast<UMaterialInterface*>(Material->GetContentObject());
|
|
if (UnrealMaterial != nullptr)
|
|
{
|
|
GlobalImportSettings->OverrideMaterials.Add(Material->UniqueId, UnrealMaterial);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Save the import options in ini config file
|
|
SceneImportOptions->SaveConfig();
|
|
|
|
// Save the Default setting copy them in the UObject and save them
|
|
SFbxSceneOptionWindow::CopyFbxOptionsToStaticMeshOptions(GlobalImportSettings, StaticMeshImportData);
|
|
StaticMeshImportData->SaveConfig();
|
|
|
|
SFbxSceneOptionWindow::CopyFbxOptionsToSkeletalMeshOptions(GlobalImportSettings, SkeletalMeshImportData);
|
|
SkeletalMeshImportData->SaveConfig();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IsEmptyAttribute(FString AttributeType)
|
|
{
|
|
return (AttributeType.Compare("eNull") == 0 || AttributeType.Compare("eUnknown") == 0);
|
|
}
|
|
|
|
static void ExtractPropertyTextures(FbxSurfaceMaterial* FbxMaterial, TSharedPtr<FFbxMaterialInfo> MaterialInfo, const char* MaterialProperty, TMap<uint64, TSharedPtr<FFbxTextureInfo>> ExtractedTextures)
|
|
{
|
|
FbxProperty FbxProperty = FbxMaterial->FindProperty(MaterialProperty);
|
|
if (FbxProperty.IsValid())
|
|
{
|
|
int32 LayeredTextureCount = FbxProperty.GetSrcObjectCount<FbxLayeredTexture>();
|
|
if (LayeredTextureCount == 0)
|
|
{
|
|
int32 TextureCount = FbxProperty.GetSrcObjectCount<FbxFileTexture>();
|
|
if (TextureCount > 0)
|
|
{
|
|
for (int32 TextureIndex = 0; TextureIndex < TextureCount; ++TextureIndex)
|
|
{
|
|
FbxFileTexture* FbxTexture = FbxProperty.GetSrcObject<FbxFileTexture>(TextureIndex);
|
|
TSharedPtr<FFbxTextureInfo> TextureInfo = nullptr;
|
|
if (ExtractedTextures.Contains(FbxTexture->GetUniqueID()))
|
|
{
|
|
TextureInfo = *(ExtractedTextures.Find(FbxTexture->GetUniqueID()));
|
|
}
|
|
else
|
|
{
|
|
TextureInfo = MakeShareable(new FFbxTextureInfo());
|
|
TextureInfo->Name = UTF8_TO_TCHAR(FbxTexture->GetName());
|
|
TextureInfo->UniqueId = FbxTexture->GetUniqueID();
|
|
TextureInfo->TexturePath = UTF8_TO_TCHAR(FbxTexture->GetFileName());
|
|
ExtractedTextures.Add(TextureInfo->UniqueId, TextureInfo);
|
|
}
|
|
// Add the texture
|
|
MaterialInfo->Textures.Add(TextureInfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ExtractMaterialInfoFromNode(UnFbx::FFbxImporter* FbxImporter, FbxNode* Node, TSharedPtr<FFbxSceneInfo> SceneInfoPtr, TMap<uint64, TSharedPtr<FFbxMaterialInfo>> ExtractedMaterials, TMap<uint64, TSharedPtr<FFbxTextureInfo>> ExtractedTextures, FString CurrentHierarchyPath)
|
|
{
|
|
TSharedPtr<FFbxNodeInfo> FoundNode = nullptr;
|
|
for (TSharedPtr<FFbxNodeInfo> Nodeinfo: SceneInfoPtr->HierarchyInfo)
|
|
{
|
|
if (Nodeinfo->UniqueId == Node->GetUniqueID())
|
|
{
|
|
FoundNode = Nodeinfo;
|
|
}
|
|
}
|
|
if (FoundNode.IsValid())
|
|
{
|
|
if (!CurrentHierarchyPath.IsEmpty())
|
|
{
|
|
CurrentHierarchyPath += TEXT("/");
|
|
}
|
|
CurrentHierarchyPath += FoundNode->NodeName;
|
|
|
|
for (int MaterialIndex = 0; MaterialIndex < Node->GetMaterialCount(); ++MaterialIndex)
|
|
{
|
|
FbxSurfaceMaterial* FbxMaterial = Node->GetMaterial(MaterialIndex);
|
|
TSharedPtr<FFbxMaterialInfo> MaterialInfo = nullptr;
|
|
if (ExtractedMaterials.Contains(FbxMaterial->GetUniqueID()))
|
|
{
|
|
MaterialInfo = *(ExtractedMaterials.Find(FbxMaterial->GetUniqueID()));
|
|
}
|
|
else
|
|
{
|
|
MaterialInfo = MakeShareable(new FFbxMaterialInfo());
|
|
MaterialInfo->HierarchyPath = CurrentHierarchyPath;
|
|
MaterialInfo->UniqueId = FbxMaterial->GetUniqueID();
|
|
MaterialInfo->Name = UTF8_TO_TCHAR(FbxMaterial->GetName());
|
|
TCHAR IllegalCharacters[7] = {'/', '\\', ' ', '`', '\t', '\r', '\n'};
|
|
bool DisplayInvalidNameError = false;
|
|
FString OldMaterialName = MaterialInfo->Name;
|
|
for (TCHAR IllegalCharacter: IllegalCharacters)
|
|
{
|
|
TCHAR IllegalChar[2];
|
|
IllegalChar[0] = IllegalCharacter;
|
|
IllegalChar[1] = '\0';
|
|
if (MaterialInfo->Name.Contains(&IllegalChar[0]))
|
|
{
|
|
MaterialInfo->Name = MaterialInfo->Name.Replace(&IllegalChar[0], TEXT("_"));
|
|
DisplayInvalidNameError = true;
|
|
}
|
|
}
|
|
if (DisplayInvalidNameError)
|
|
{
|
|
FbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(LOCTEXT("FoundInvalidCharacterInMaterialName", "Found invalid character in a material name. Original name: {0} New name: {1}"), FText::FromString(OldMaterialName), FText::FromString(MaterialInfo->Name))), FFbxErrors::Generic_InvalidCharacterInName);
|
|
}
|
|
ExtractPropertyTextures(FbxMaterial, MaterialInfo, FbxSurfaceMaterial::sDiffuse, ExtractedTextures);
|
|
ExtractPropertyTextures(FbxMaterial, MaterialInfo, FbxSurfaceMaterial::sEmissive, ExtractedTextures);
|
|
ExtractPropertyTextures(FbxMaterial, MaterialInfo, FbxSurfaceMaterial::sSpecular, ExtractedTextures);
|
|
// ExtractPropertyTextures(FbxMaterial, MaterialInfo, FbxSurfaceMaterial::sSpecularFactor, ExtractedTextures);
|
|
// ExtractPropertyTextures(FbxMaterial, MaterialInfo, FbxSurfaceMaterial::sShininess, ExtractedTextures);
|
|
ExtractPropertyTextures(FbxMaterial, MaterialInfo, FbxSurfaceMaterial::sNormalMap, ExtractedTextures);
|
|
ExtractPropertyTextures(FbxMaterial, MaterialInfo, FbxSurfaceMaterial::sBump, ExtractedTextures);
|
|
ExtractPropertyTextures(FbxMaterial, MaterialInfo, FbxSurfaceMaterial::sTransparentColor, ExtractedTextures);
|
|
ExtractPropertyTextures(FbxMaterial, MaterialInfo, FbxSurfaceMaterial::sTransparencyFactor, ExtractedTextures);
|
|
ExtractedMaterials.Add(MaterialInfo->UniqueId, MaterialInfo);
|
|
}
|
|
// Add the Material to the node
|
|
FoundNode->Materials.AddUnique(MaterialInfo);
|
|
}
|
|
}
|
|
for (int ChildIndex = 0; ChildIndex < Node->GetChildCount(); ++ChildIndex)
|
|
{
|
|
FbxNode* ChildNode = Node->GetChild(ChildIndex);
|
|
ExtractMaterialInfoFromNode(FbxImporter, ChildNode, SceneInfoPtr, ExtractedMaterials, ExtractedTextures, CurrentHierarchyPath);
|
|
}
|
|
}
|
|
|
|
void UFbxSceneImportFactory::ExtractMaterialInfo(void* FbxImporterVoid, TSharedPtr<FFbxSceneInfo> SceneInfoPtr)
|
|
{
|
|
UnFbx::FFbxImporter* FbxImporter = (UnFbx::FFbxImporter*)FbxImporterVoid;
|
|
TMap<uint64, TSharedPtr<FFbxMaterialInfo>> ExtractedMaterials;
|
|
TMap<uint64, TSharedPtr<FFbxTextureInfo>> ExtractedTextures;
|
|
FbxNode* RootNode = FbxImporter->Scene->GetRootNode();
|
|
FString CurrentHierarchyPath = TEXT("");
|
|
ExtractMaterialInfoFromNode(FbxImporter, RootNode, SceneInfoPtr, ExtractedMaterials, ExtractedTextures, CurrentHierarchyPath);
|
|
}
|
|
|
|
bool IsPartOfSkeletonHierarchy(const TMap<uint64, const UnFbx::FbxNodeInfo*>& NodeInfoMap, const UnFbx::FbxNodeInfo& NodeInfo)
|
|
{
|
|
FString AttributeType = NodeInfo.AttributeType;
|
|
if (AttributeType.Compare(TEXT("eSkeleton")) == 0)
|
|
{
|
|
return true;
|
|
}
|
|
if (NodeInfoMap.Contains(NodeInfo.ParentUniqueId))
|
|
{
|
|
const UnFbx::FbxNodeInfo* ParentNodeInfo = *NodeInfoMap.Find(NodeInfo.ParentUniqueId);
|
|
return IsPartOfSkeletonHierarchy(NodeInfoMap, *ParentNodeInfo);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FetchFbxCameraInScene(UnFbx::FFbxImporter* FbxImporter, FbxNode* ParentNode, TSharedPtr<FFbxSceneInfo> SceneInfoPtr)
|
|
{
|
|
if (ParentNode->GetNodeAttribute() && ParentNode->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eCamera)
|
|
{
|
|
FbxCamera* CameraAttribute = (FbxCamera*)ParentNode->GetNodeAttribute();
|
|
if (CameraAttribute != nullptr && CameraAttribute->GetNode() != nullptr && !SceneInfoPtr->CameraInfo.Contains(CameraAttribute->GetUniqueID()))
|
|
{
|
|
FbxNode* CameraNode = CameraAttribute->GetNode();
|
|
TSharedPtr<FFbxCameraInfo> CameraInfo = MakeShareable(new FFbxCameraInfo());
|
|
|
|
if (CameraAttribute->GetName()[0] != '\0')
|
|
{
|
|
CameraInfo->Name = FbxImporter->MakeName(CameraAttribute->GetName());
|
|
}
|
|
else
|
|
{
|
|
CameraInfo->Name = FbxImporter->MakeString(CameraNode ? CameraNode->GetName() : "None");
|
|
}
|
|
CameraInfo->UniqueId = CameraAttribute->GetUniqueID();
|
|
|
|
float FieldOfView;
|
|
float FocalLength;
|
|
|
|
if (CameraAttribute->GetApertureMode() == FbxCamera::eFocalLength)
|
|
{
|
|
FocalLength = CameraAttribute->FocalLength.Get();
|
|
FieldOfView = CameraAttribute->ComputeFieldOfView(FocalLength);
|
|
}
|
|
else
|
|
{
|
|
FieldOfView = CameraAttribute->FieldOfView.Get();
|
|
FocalLength = CameraAttribute->ComputeFocalLength(FieldOfView);
|
|
}
|
|
|
|
CameraInfo->AspectWidth = CameraAttribute->AspectWidth.Get();
|
|
CameraInfo->AspectHeight = CameraAttribute->AspectHeight.Get();
|
|
CameraInfo->NearPlane = CameraAttribute->NearPlane.Get();
|
|
CameraInfo->FarPlane = CameraAttribute->FarPlane.Get();
|
|
CameraInfo->ProjectionPerspective = CameraAttribute->ProjectionType.Get() == FbxCamera::ePerspective;
|
|
CameraInfo->OrthoZoom = CameraAttribute->OrthoZoom.Get();
|
|
CameraInfo->FieldOfView = FieldOfView;
|
|
CameraInfo->FocalLength = FocalLength;
|
|
CameraInfo->ApertureWidth = CameraAttribute->GetApertureWidth();
|
|
CameraInfo->ApertureHeight = CameraAttribute->GetApertureHeight();
|
|
SceneInfoPtr->CameraInfo.Add(CameraInfo->UniqueId, CameraInfo);
|
|
}
|
|
}
|
|
for (int i = 0; i < ParentNode->GetChildCount(); ++i)
|
|
{
|
|
FbxNode* Child = ParentNode->GetChild(i);
|
|
FetchFbxCameraInScene(FbxImporter, Child, SceneInfoPtr);
|
|
}
|
|
}
|
|
|
|
void FetchFbxLightInScene(UnFbx::FFbxImporter* FbxImporter, FbxNode* ParentNode, TSharedPtr<FFbxSceneInfo> SceneInfoPtr)
|
|
{
|
|
if (ParentNode->GetNodeAttribute() && ParentNode->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLight)
|
|
{
|
|
FbxLight* LightAttribute = (FbxLight*)ParentNode->GetNodeAttribute();
|
|
if (LightAttribute != nullptr && LightAttribute->GetNode() != nullptr && !SceneInfoPtr->LightInfo.Contains(LightAttribute->GetUniqueID()))
|
|
{
|
|
FbxNode* LightNode = LightAttribute->GetNode();
|
|
TSharedPtr<FFbxLightInfo> LightInfo = MakeShareable(new FFbxLightInfo());
|
|
|
|
if (LightAttribute->GetName()[0] != '\0')
|
|
{
|
|
LightInfo->Name = FbxImporter->MakeName(LightAttribute->GetName());
|
|
}
|
|
else
|
|
{
|
|
LightInfo->Name = FbxImporter->MakeString(LightNode ? LightNode->GetName() : "None");
|
|
}
|
|
LightInfo->UniqueId = LightAttribute->GetUniqueID();
|
|
switch (LightAttribute->LightType.Get())
|
|
{
|
|
case FbxLight::ePoint:
|
|
LightInfo->Type = 0;
|
|
break;
|
|
case FbxLight::eDirectional:
|
|
LightInfo->Type = 1;
|
|
break;
|
|
case FbxLight::eSpot:
|
|
LightInfo->Type = 2;
|
|
break;
|
|
case FbxLight::eArea:
|
|
LightInfo->Type = 3;
|
|
break;
|
|
case FbxLight::eVolume:
|
|
LightInfo->Type = 4;
|
|
break;
|
|
}
|
|
LightInfo->Color = FFbxDataConverter::ConvertColor(LightAttribute->Color);
|
|
LightInfo->Intensity = (float)(LightAttribute->Intensity.Get());
|
|
switch (LightAttribute->DecayType.Get())
|
|
{
|
|
case FbxLight::EDecayType::eNone:
|
|
LightInfo->Decay = 0;
|
|
break;
|
|
case FbxLight::EDecayType::eLinear:
|
|
LightInfo->Decay = 1;
|
|
break;
|
|
case FbxLight::EDecayType::eQuadratic:
|
|
LightInfo->Decay = 2;
|
|
break;
|
|
case FbxLight::EDecayType::eCubic:
|
|
LightInfo->Decay = 3;
|
|
break;
|
|
}
|
|
LightInfo->CastLight = LightAttribute->CastLight.Get();
|
|
LightInfo->CastShadow = LightAttribute->CastShadows.Get();
|
|
LightInfo->ShadowColor = FFbxDataConverter::ConvertColor(LightAttribute->ShadowColor);
|
|
|
|
LightInfo->InnerAngle = (float)(LightAttribute->InnerAngle.Get());
|
|
LightInfo->OuterAngle = (float)(LightAttribute->OuterAngle.Get());
|
|
LightInfo->Fog = (float)(LightAttribute->Fog.Get());
|
|
LightInfo->DecayStart = (float)(LightAttribute->DecayStart.Get());
|
|
LightInfo->EnableNearAttenuation = LightAttribute->EnableNearAttenuation.Get();
|
|
LightInfo->NearAttenuationStart = (float)(LightAttribute->NearAttenuationStart.Get());
|
|
LightInfo->NearAttenuationEnd = (float)(LightAttribute->NearAttenuationEnd.Get());
|
|
LightInfo->EnableFarAttenuation = LightAttribute->EnableFarAttenuation.Get();
|
|
LightInfo->FarAttenuationStart = (float)(LightAttribute->FarAttenuationStart.Get());
|
|
LightInfo->FarAttenuationEnd = (float)(LightAttribute->FarAttenuationEnd.Get());
|
|
SceneInfoPtr->LightInfo.Add(LightInfo->UniqueId, LightInfo);
|
|
}
|
|
}
|
|
for (int i = 0; i < ParentNode->GetChildCount(); ++i)
|
|
{
|
|
FbxNode* Child = ParentNode->GetChild(i);
|
|
FetchFbxLightInScene(FbxImporter, Child, SceneInfoPtr);
|
|
}
|
|
}
|
|
|
|
// TODO we should replace the old UnFbx:: data by the new data that use shared pointer.
|
|
// For now we convert the old structure to the new one
|
|
TSharedPtr<FFbxSceneInfo> UFbxSceneImportFactory::ConvertSceneInfo(void* VoidFbxImporter, void* VoidFbxSceneInfo)
|
|
{
|
|
UnFbx::FFbxImporter* FbxImporter = (UnFbx::FFbxImporter*)VoidFbxImporter;
|
|
UnFbx::FbxSceneInfo& SceneInfo = *((UnFbx::FbxSceneInfo*)VoidFbxSceneInfo);
|
|
TSharedPtr<FFbxSceneInfo> SceneInfoPtr = MakeShareable(new FFbxSceneInfo());
|
|
SceneInfoPtr->NonSkinnedMeshNum = SceneInfo.NonSkinnedMeshNum;
|
|
SceneInfoPtr->SkinnedMeshNum = SceneInfo.SkinnedMeshNum;
|
|
SceneInfoPtr->TotalGeometryNum = SceneInfo.TotalGeometryNum;
|
|
SceneInfoPtr->TotalMaterialNum = SceneInfo.TotalMaterialNum;
|
|
SceneInfoPtr->TotalTextureNum = SceneInfo.TotalTextureNum;
|
|
SceneInfoPtr->bHasAnimation = SceneInfo.bHasAnimation;
|
|
SceneInfoPtr->FrameRate = SceneInfo.FrameRate;
|
|
SceneInfoPtr->TotalTime = SceneInfo.TotalTime;
|
|
|
|
// Get the valid skeletal mesh from the fbx file and store it in the map
|
|
TMap<uint64, FbxMesh*> ValidSkeletalMesh;
|
|
FbxNode* RootNodeToImport = FbxImporter->Scene->GetRootNode();
|
|
TArray<TArray<FbxNode*>*> SkelMeshArray;
|
|
UnFbx::FBXImportOptions* FbxImportOptionsPtr = FbxImporter->GetImportOptions();
|
|
bool OldValueImportMeshesInBoneHierarchy = FbxImportOptionsPtr->bImportMeshesInBoneHierarchy;
|
|
FbxImportOptionsPtr->bImportMeshesInBoneHierarchy = true;
|
|
FbxImporter->FillFbxSkelMeshArrayInScene(RootNodeToImport, SkelMeshArray, false, false, true);
|
|
FbxImportOptionsPtr->bImportMeshesInBoneHierarchy = OldValueImportMeshesInBoneHierarchy;
|
|
|
|
for (int32 i = 0; i < SkelMeshArray.Num(); i++)
|
|
{
|
|
TArray<FbxNode*> NodeArray = *SkelMeshArray[i];
|
|
if (NodeArray.Num() < 1)
|
|
continue;
|
|
FbxNode* RootNodeArrayNode = NodeArray[0];
|
|
if (RootNodeArrayNode->GetNodeAttribute() && RootNodeArrayNode->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup)
|
|
{
|
|
RootNodeArrayNode = FbxImporter->FindLODGroupNode(RootNodeArrayNode, 0);
|
|
}
|
|
FbxMesh* Mesh = RootNodeArrayNode->GetMesh();
|
|
if (Mesh != nullptr)
|
|
{
|
|
ValidSkeletalMesh.Add(Mesh->GetUniqueID(), Mesh);
|
|
for (FbxNode* SkelMeshNode: NodeArray)
|
|
{
|
|
uint64 MeshNodeID = SkelMeshNode->GetUniqueID();
|
|
FbxMesh* SkeletalMeshAttribute = SkelMeshNode->GetMesh();
|
|
|
|
if (SkeletalMeshAttribute != nullptr)
|
|
MeshNodeID = SkeletalMeshAttribute->GetUniqueID();
|
|
|
|
for (UnFbx::FbxMeshInfo& MeshInfo: SceneInfo.MeshInfo)
|
|
{
|
|
if (MeshInfo.UniqueId == MeshNodeID)
|
|
{
|
|
// We have either a skeletal mesh or a rigid mesh
|
|
MeshInfo.bIsSkelMesh = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const UnFbx::FbxMeshInfo& MeshInfo: SceneInfo.MeshInfo)
|
|
{
|
|
// Add the skeletal mesh if its a valid one
|
|
if (MeshInfo.bIsSkelMesh && !ValidSkeletalMesh.Contains(MeshInfo.UniqueId))
|
|
{
|
|
continue;
|
|
}
|
|
TSharedPtr<FFbxMeshInfo> MeshInfoPtr = MakeShareable(new FFbxMeshInfo());
|
|
MeshInfoPtr->FaceNum = MeshInfo.FaceNum;
|
|
MeshInfoPtr->VertexNum = MeshInfo.VertexNum;
|
|
MeshInfoPtr->bTriangulated = MeshInfo.bTriangulated;
|
|
MeshInfoPtr->MaterialNum = MeshInfo.MaterialNum;
|
|
MeshInfoPtr->bIsSkelMesh = MeshInfo.bIsSkelMesh;
|
|
MeshInfoPtr->SkeletonRoot = MeshInfo.SkeletonRoot;
|
|
MeshInfoPtr->SkeletonElemNum = MeshInfo.SkeletonElemNum;
|
|
MeshInfoPtr->LODGroup = MeshInfo.LODGroup;
|
|
MeshInfoPtr->LODLevel = MeshInfo.LODLevel;
|
|
MeshInfoPtr->MorphNum = MeshInfo.MorphNum;
|
|
MeshInfoPtr->Name = MeshInfo.Name;
|
|
MeshInfoPtr->UniqueId = MeshInfo.UniqueId;
|
|
MeshInfoPtr->OptionName = DefaultOptionName;
|
|
|
|
MeshInfoPtr->IsLod = MeshInfoPtr->LODLevel > 0;
|
|
MeshInfoPtr->IsCollision = MeshInfoPtr->Name.Contains(TEXT("UCX"), ESearchCase::IgnoreCase) || MeshInfoPtr->Name.Contains(TEXT("UBX"), ESearchCase::IgnoreCase) || MeshInfoPtr->Name.Contains(TEXT("MCDCX"), ESearchCase::IgnoreCase) || MeshInfoPtr->Name.Contains(TEXT("USP"), ESearchCase::IgnoreCase) || MeshInfoPtr->Name.Contains(TEXT("UCP"), ESearchCase::IgnoreCase);
|
|
|
|
SceneInfoPtr->MeshInfo.Add(MeshInfoPtr);
|
|
}
|
|
|
|
// Find all light and camera in the scene
|
|
FetchFbxCameraInScene(FbxImporter, RootNodeToImport, SceneInfoPtr);
|
|
FetchFbxLightInScene(FbxImporter, RootNodeToImport, SceneInfoPtr);
|
|
|
|
TMap<uint64, const UnFbx::FbxNodeInfo*> NodeInfoMap;
|
|
for (const UnFbx::FbxNodeInfo& NodeInfo: SceneInfo.HierarchyInfo)
|
|
{
|
|
NodeInfoMap.Add(NodeInfo.UniqueId, &NodeInfo);
|
|
if (IsPartOfSkeletonHierarchy(NodeInfoMap, NodeInfo))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TSharedPtr<FFbxNodeInfo> NodeInfoPtr = MakeShareable(new FFbxNodeInfo());
|
|
NodeInfoPtr->NodeName = NodeInfo.ObjectName;
|
|
NodeInfoPtr->UniqueId = NodeInfo.UniqueId;
|
|
NodeInfoPtr->AttributeType = NodeInfo.AttributeType;
|
|
NodeInfoPtr->AttributeUniqueId = NodeInfo.AttributeUniqueId;
|
|
|
|
// Find the parent
|
|
NodeInfoPtr->ParentNodeInfo = nullptr;
|
|
for (TSharedPtr<FFbxNodeInfo> ParentPtr: SceneInfoPtr->HierarchyInfo)
|
|
{
|
|
if (ParentPtr->UniqueId == NodeInfo.ParentUniqueId)
|
|
{
|
|
NodeInfoPtr->ParentNodeInfo = ParentPtr;
|
|
ParentPtr->Childrens.Add(NodeInfoPtr);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Find the attribute info
|
|
NodeInfoPtr->AttributeInfo = nullptr;
|
|
if (NodeInfoPtr->AttributeType.Compare(TEXT("eMesh")) == 0)
|
|
{
|
|
for (TSharedPtr<FFbxMeshInfo> AttributePtr: SceneInfoPtr->MeshInfo)
|
|
{
|
|
if (AttributePtr->UniqueId == NodeInfo.AttributeUniqueId)
|
|
{
|
|
NodeInfoPtr->AttributeInfo = AttributePtr;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set the transform
|
|
NodeInfoPtr->Transform = FTransform::Identity;
|
|
FbxVector4 NewLocalT = NodeInfo.Transform.GetT();
|
|
FbxVector4 NewLocalS = NodeInfo.Transform.GetS();
|
|
FbxQuaternion NewLocalQ = NodeInfo.Transform.GetQ();
|
|
NodeInfoPtr->Transform.SetTranslation(UnFbx::FFbxDataConverter::ConvertPos(NewLocalT));
|
|
NodeInfoPtr->Transform.SetScale3D(UnFbx::FFbxDataConverter::ConvertScale(NewLocalS));
|
|
NodeInfoPtr->Transform.SetRotation(UnFbx::FFbxDataConverter::ConvertRotToQuat(NewLocalQ));
|
|
NodeInfoPtr->PivotRotation = UnFbx::FFbxDataConverter::ConvertPos(NodeInfo.RotationPivot);
|
|
NodeInfoPtr->PivotScaling = UnFbx::FFbxDataConverter::ConvertPos(NodeInfo.ScalePivot);
|
|
|
|
// Set the attribute pivot dictionary
|
|
if (NodeInfoPtr->AttributeInfo.IsValid())
|
|
{
|
|
if (NodeInfoPtr->AttributeInfo->NodeReferencePivots.Contains(NodeInfoPtr->PivotRotation))
|
|
{
|
|
TArray<uint64>* NodeUidArray = NodeInfoPtr->AttributeInfo->NodeReferencePivots.Find(NodeInfoPtr->PivotRotation);
|
|
check(NodeUidArray);
|
|
NodeUidArray->Add(NodeInfoPtr->UniqueId);
|
|
}
|
|
else
|
|
{
|
|
TArray<uint64> NodeUidArray;
|
|
NodeUidArray.Add(NodeInfoPtr->UniqueId);
|
|
NodeInfoPtr->AttributeInfo->NodeReferencePivots.Add(NodeInfoPtr->PivotRotation, NodeUidArray);
|
|
}
|
|
if (NodeInfoPtr->AttributeInfo->PivotNodeUid == INVALID_UNIQUE_ID)
|
|
{
|
|
NodeInfoPtr->AttributeInfo->PivotNodeUid = NodeInfoPtr->UniqueId;
|
|
NodeInfoPtr->AttributeInfo->PivotNodeName = NodeInfoPtr->NodeName;
|
|
}
|
|
}
|
|
|
|
if (SceneInfoPtr->LightInfo.Contains(NodeInfoPtr->AttributeUniqueId))
|
|
{
|
|
// Add the z rotation of 90 degree locally for every light. Light direction differ from fbx to unreal
|
|
FRotator LightRotator(0.0f, 90.0f, 0.0f);
|
|
FTransform LightTransform = FTransform(LightRotator);
|
|
NodeInfoPtr->Transform = LightTransform * NodeInfoPtr->Transform;
|
|
}
|
|
else if (SceneInfoPtr->CameraInfo.Contains(NodeInfoPtr->AttributeUniqueId))
|
|
{
|
|
// Add a roll of -90 degree locally for every cameras. Camera up vector differ from fbx to unreal
|
|
FRotator LightRotator(0.0f, 0.0f, -90.0f);
|
|
FTransform LightTransform = FTransform(LightRotator);
|
|
// Remove the scale of the node holding a camera (the mesh is provide by the engine and can be different in size)
|
|
NodeInfoPtr->Transform.SetScale3D(FVector(1.0f));
|
|
NodeInfoPtr->Transform = LightTransform * NodeInfoPtr->Transform;
|
|
}
|
|
|
|
// by default we import all node
|
|
NodeInfoPtr->bImportNode = true;
|
|
|
|
// Add the node to the hierarchy
|
|
SceneInfoPtr->HierarchyInfo.Add(NodeInfoPtr);
|
|
}
|
|
|
|
for (TSharedPtr<FFbxNodeInfo> NodeInfo: SceneInfoPtr->HierarchyInfo)
|
|
{
|
|
if (NodeInfo->AttributeType.Compare(TEXT("eLODGroup")) == 0)
|
|
{
|
|
for (TSharedPtr<FFbxNodeInfo> ChildNodeInfo: NodeInfo->Childrens)
|
|
{
|
|
if (ChildNodeInfo->AttributeType.Compare(TEXT("eMesh")) != 0)
|
|
{
|
|
// We don't import under LOD group other stuff then the mesh
|
|
ChildNodeInfo->bImportNode = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return SceneInfoPtr;
|
|
}
|
|
|
|
UClass* FFbxMeshInfo::GetType()
|
|
{
|
|
if (bIsSkelMesh)
|
|
return USkeletalMesh::StaticClass();
|
|
return UStaticMesh::StaticClass();
|
|
}
|
|
|
|
UClass* FFbxTextureInfo::GetType()
|
|
{
|
|
return UTexture::StaticClass();
|
|
}
|
|
|
|
UClass* FFbxMaterialInfo::GetType()
|
|
{
|
|
return UMaterial::StaticClass();
|
|
}
|
|
|
|
UPackage* FFbxAttributeInfo::GetContentPackage()
|
|
{
|
|
if (!IsContentObjectUpToDate)
|
|
{
|
|
// Update the Object, this will update the ContentPackage and set the IsContentUpToDate state
|
|
GetContentObject();
|
|
}
|
|
return ContentPackage;
|
|
}
|
|
|
|
UObject* FFbxAttributeInfo::GetContentObject()
|
|
{
|
|
if (IsContentObjectUpToDate)
|
|
return ContentObject;
|
|
ContentPackage = nullptr;
|
|
ContentObject = nullptr;
|
|
FString ImportPath = UPackageTools::SanitizePackageName(GetImportPath());
|
|
FString AssetName = GetFullImportName();
|
|
if (!ImportPath.IsEmpty())
|
|
{
|
|
ContentPackage = LoadPackage(nullptr, *ImportPath, LOAD_Verify | LOAD_NoWarn);
|
|
}
|
|
|
|
if (ContentPackage != nullptr)
|
|
{
|
|
ContentPackage->FullyLoad();
|
|
}
|
|
ContentObject = FindObjectSafe<UObject>(ANY_PACKAGE, *AssetName);
|
|
if (ContentObject != nullptr)
|
|
{
|
|
if (ContentObject->HasAnyFlags(RF_Transient) || ContentObject->IsPendingKill())
|
|
{
|
|
ContentObject = nullptr;
|
|
}
|
|
else if (ContentPackage == nullptr)
|
|
{
|
|
// If we are able to find the object but not to load the package, this mean that the package is a new created package that is not save yet
|
|
ContentPackage = ContentObject->GetOutermost();
|
|
}
|
|
}
|
|
|
|
IsContentObjectUpToDate = true;
|
|
return ContentObject;
|
|
}
|
|
|
|
UFbxSceneImportFactory::UFbxSceneImportFactory(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
SupportedClass = UWorld::StaticClass();
|
|
Formats.Add(TEXT("fbx;Fbx Scene"));
|
|
Formats.Add(TEXT("obj;OBJ Scene"));
|
|
|
|
bCreateNew = false;
|
|
bText = false;
|
|
bEditorImport = true;
|
|
Path = "";
|
|
ImportWasCancel = false;
|
|
|
|
SceneImportOptions = CreateDefaultSubobject<UFbxSceneImportOptions>(TEXT("SceneImportOptions"), true);
|
|
SceneImportOptions->SetFlags(RF_Transactional);
|
|
SceneImportOptionsStaticMesh = CreateDefaultSubobject<UFbxSceneImportOptionsStaticMesh>(TEXT("SceneImportOptionsStaticMesh"), true);
|
|
SceneImportOptionsStaticMesh->SetFlags(RF_Transactional);
|
|
SceneImportOptionsSkeletalMesh = CreateDefaultSubobject<UFbxSceneImportOptionsSkeletalMesh>(TEXT("SceneImportOptionsSkeletalMesh"), true);
|
|
SceneImportOptionsSkeletalMesh->SetFlags(RF_Transactional);
|
|
|
|
StaticMeshImportData = CreateDefaultSubobject<UFbxStaticMeshImportData>(TEXT("StaticMeshImportData"), true);
|
|
StaticMeshImportData->SetFlags(RF_Transactional);
|
|
SkeletalMeshImportData = CreateDefaultSubobject<UFbxSkeletalMeshImportData>(TEXT("SkeletalMeshImportData"), true);
|
|
SkeletalMeshImportData->SetFlags(RF_Transactional);
|
|
AnimSequenceImportData = CreateDefaultSubobject<UFbxAnimSequenceImportData>(TEXT("AnimSequenceImportData"), true);
|
|
AnimSequenceImportData->SetFlags(RF_Transactional);
|
|
TextureImportData = CreateDefaultSubobject<UFbxTextureImportData>(TEXT("TextureImportData"), true);
|
|
TextureImportData->SetFlags(RF_Transactional);
|
|
|
|
ReimportData = nullptr;
|
|
}
|
|
|
|
void UFbxSceneImportFactory::FillSceneHierarchyPath(TSharedPtr<FFbxSceneInfo> SceneInfo)
|
|
{
|
|
// Set the hierarchy path for every node this data will be use by the reimport
|
|
for (FbxNodeInfoPtr NodeInfo: SceneInfo->HierarchyInfo)
|
|
{
|
|
FString NodeTreePath = NodeInfo->NodeName;
|
|
FbxNodeInfoPtr CurrentNode = NodeInfo->ParentNodeInfo;
|
|
while (CurrentNode.IsValid())
|
|
{
|
|
NodeTreePath += TEXT(".");
|
|
NodeTreePath += CurrentNode->NodeName;
|
|
CurrentNode = CurrentNode->ParentNodeInfo;
|
|
}
|
|
NodeInfo->NodeHierarchyPath = NodeTreePath;
|
|
}
|
|
}
|
|
|
|
UFbxSceneImportData* CreateReImportAsset(const FString& PackagePath, const FString& FbxImportFileName, UFbxSceneImportOptions* SceneImportOptions, TSharedPtr<FFbxSceneInfo> SceneInfo, ImportOptionsNameMap& NameOptionsMap)
|
|
{
|
|
// Create or use existing package
|
|
// The data must have the name of the import file to support drag drop reimport
|
|
FString FilenameBase = FPaths::GetBaseFilename(FbxImportFileName);
|
|
FString FbxReImportPkgName = PackagePath + TEXT("/") + FilenameBase;
|
|
FbxReImportPkgName = UPackageTools::SanitizePackageName(FbxReImportPkgName);
|
|
FString AssetName = FilenameBase;
|
|
AssetName = UPackageTools::SanitizePackageName(AssetName);
|
|
UPackage* Pkg = CreatePackage(*FbxReImportPkgName);
|
|
if (!ensure(Pkg))
|
|
{
|
|
// TODO log an import warning stipulate that there is no re-import asset created
|
|
return nullptr;
|
|
}
|
|
Pkg->FullyLoad();
|
|
|
|
FbxReImportPkgName = FPackageName::GetLongPackageAssetName(Pkg->GetOutermost()->GetName());
|
|
// Save the re-import data asset
|
|
UFbxSceneImportData* ReImportAsset = NewObject<UFbxSceneImportData>(Pkg, NAME_None, RF_Public | RF_Standalone);
|
|
FString NewUniqueName = AssetName;
|
|
if (!ReImportAsset->Rename(*NewUniqueName, nullptr, REN_Test))
|
|
{
|
|
NewUniqueName = MakeUniqueObjectName(ReImportAsset, UFbxSceneImportData::StaticClass(), FName(*AssetName)).ToString();
|
|
}
|
|
ReImportAsset->Rename(*NewUniqueName, nullptr, REN_DontCreateRedirectors);
|
|
ReImportAsset->SceneInfoSourceData = SceneInfo;
|
|
// Copy the options map
|
|
for (auto kvp: NameOptionsMap)
|
|
{
|
|
ReImportAsset->NameOptionsMap.Add(kvp.Key, kvp.Value);
|
|
}
|
|
|
|
ReImportAsset->SourceFbxFile = FPaths::ConvertRelativePathToFull(FbxImportFileName);
|
|
ReImportAsset->bCreateFolderHierarchy = SceneImportOptions->bCreateContentFolderHierarchy;
|
|
ReImportAsset->bForceFrontXAxis = SceneImportOptions->bForceFrontXAxis;
|
|
ReImportAsset->HierarchyType = (int32)SceneImportOptions->HierarchyType;
|
|
return ReImportAsset;
|
|
}
|
|
|
|
UObject* UFbxSceneImportFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled)
|
|
{
|
|
// This function performs shortcut to call FactoryCreateBinary without loading a file to array.
|
|
FString FileExtension = FPaths::GetExtension(Filename);
|
|
|
|
if (!IFileManager::Get().FileExists(*Filename))
|
|
{
|
|
UE_LOG(LogFbx, Error, TEXT("Failed to load file '%s'"), *Filename)
|
|
return nullptr;
|
|
}
|
|
|
|
ParseParms(Parms);
|
|
|
|
const uint8* Buffer = nullptr;
|
|
const uint8* BufferEnd = nullptr;
|
|
return FactoryCreateBinary(InClass, InParent, InName, Flags, nullptr, *FileExtension, Buffer, BufferEnd, Warn, bOutOperationCanceled);
|
|
}
|
|
|
|
UObject* UFbxSceneImportFactory::FactoryCreateBinary(
|
|
UClass* Class,
|
|
UObject* InParent,
|
|
FName Name,
|
|
EObjectFlags Flags,
|
|
UObject* Context,
|
|
const TCHAR* Type,
|
|
const uint8*& Buffer,
|
|
const uint8* BufferEnd,
|
|
FFeedbackContext* Warn,
|
|
bool& bOutOperationCanceled)
|
|
{
|
|
UObject* ReturnObject = FactoryCreateBinary(Class, InParent, Name, Flags, Context, Type, Buffer, BufferEnd, Warn);
|
|
bOutOperationCanceled = ImportWasCancel;
|
|
ImportWasCancel = false;
|
|
return ReturnObject;
|
|
}
|
|
|
|
bool UFbxSceneImportFactory::FactoryCanImport(const FString& Filename)
|
|
{
|
|
const FString Extension = FPaths::GetExtension(Filename);
|
|
|
|
if (Extension == TEXT("fbx") || Extension == TEXT("obj"))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
TSharedPtr<FFbxNodeInfo> GetNodeInfoPtrById(TArray<TSharedPtr<FFbxNodeInfo>>& HierarchyInfo, uint64 SearchId)
|
|
{
|
|
for (TSharedPtr<FFbxNodeInfo> NodeInfoPtr: HierarchyInfo)
|
|
{
|
|
if (NodeInfoPtr->UniqueId == SearchId)
|
|
{
|
|
return NodeInfoPtr;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void UFbxSceneImportFactory::ChangeFrontAxis(void* VoidFbxImporter, void* VoidSceneInfo, TSharedPtr<FFbxSceneInfo> SceneInfoPtr)
|
|
{
|
|
UnFbx::FFbxImporter* FbxImporter = (UnFbx::FFbxImporter*)VoidFbxImporter;
|
|
UnFbx::FbxSceneInfo* SceneInfo = (UnFbx::FbxSceneInfo*)VoidSceneInfo;
|
|
|
|
FbxImporter->ConvertScene();
|
|
// Adjust the root node with the new apply scene conversion
|
|
FbxNode* RootNode = FbxImporter->Scene->GetRootNode();
|
|
if (SceneInfo->HierarchyInfo.Num() > 0)
|
|
{
|
|
// Set the fbx data
|
|
UnFbx::FbxNodeInfo& RootNodeInfo = SceneInfo->HierarchyInfo[0];
|
|
check(RootNodeInfo.UniqueId == RootNode->GetUniqueID());
|
|
RootNodeInfo.Transform = RootNode->EvaluateGlobalTransform();
|
|
// Set the UE4 data
|
|
TSharedPtr<FFbxNodeInfo> RootNodeInfoPtr = GetNodeInfoPtrById(SceneInfoPtr->HierarchyInfo, RootNodeInfo.UniqueId);
|
|
if (RootNodeInfoPtr.IsValid())
|
|
{
|
|
RootNodeInfoPtr->Transform = FTransform::Identity;
|
|
FbxVector4 NewLocalT = RootNodeInfo.Transform.GetT();
|
|
FbxVector4 NewLocalS = RootNodeInfo.Transform.GetS();
|
|
FbxQuaternion NewLocalQ = RootNodeInfo.Transform.GetQ();
|
|
RootNodeInfoPtr->Transform.SetTranslation(UnFbx::FFbxDataConverter::ConvertPos(NewLocalT));
|
|
RootNodeInfoPtr->Transform.SetScale3D(UnFbx::FFbxDataConverter::ConvertScale(NewLocalS));
|
|
RootNodeInfoPtr->Transform.SetRotation(UnFbx::FFbxDataConverter::ConvertRotToQuat(NewLocalQ));
|
|
for (int32 NodeIndex = 1; NodeIndex < SceneInfo->HierarchyInfo.Num(); ++NodeIndex)
|
|
{
|
|
UnFbx::FbxNodeInfo& LocalNodeInfo = SceneInfo->HierarchyInfo[NodeIndex];
|
|
FbxNode* RealFbxNode = FindFbxNodeById(FbxImporter, nullptr, LocalNodeInfo.UniqueId);
|
|
if (!RealFbxNode)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
LocalNodeInfo.Transform = RealFbxNode->EvaluateLocalTransform();
|
|
TSharedPtr<FFbxNodeInfo> LocalNodeInfoPtr = GetNodeInfoPtrById(SceneInfoPtr->HierarchyInfo, LocalNodeInfo.UniqueId);
|
|
if (LocalNodeInfoPtr.IsValid())
|
|
{
|
|
LocalNodeInfoPtr->Transform = FTransform::Identity;
|
|
NewLocalT = LocalNodeInfo.Transform.GetT();
|
|
NewLocalS = LocalNodeInfo.Transform.GetS();
|
|
NewLocalQ = LocalNodeInfo.Transform.GetQ();
|
|
LocalNodeInfoPtr->Transform.SetTranslation(UnFbx::FFbxDataConverter::ConvertPos(NewLocalT));
|
|
LocalNodeInfoPtr->Transform.SetScale3D(UnFbx::FFbxDataConverter::ConvertScale(NewLocalS));
|
|
LocalNodeInfoPtr->Transform.SetRotation(UnFbx::FFbxDataConverter::ConvertRotToQuat(NewLocalQ));
|
|
|
|
FString AttributeType = LocalNodeInfo.AttributeType;
|
|
if (AttributeType.Compare(TEXT("eLight")) == 0)
|
|
{
|
|
// Add the z rotation of 90 degree locally for every light. Light direction differ from fbx to unreal
|
|
FRotator LightRotator(0.0f, 90.0f, 0.0f);
|
|
FTransform LightTransform = FTransform(LightRotator);
|
|
LocalNodeInfoPtr->Transform = LightTransform * LocalNodeInfoPtr->Transform;
|
|
}
|
|
if (AttributeType.Compare(TEXT("eCamera")) == 0)
|
|
{
|
|
// Add a roll of -90 degree locally for every cameras. Camera up vector differ from fbx to unreal
|
|
FRotator CameraRotator(0.0f, 0.0f, -90.0f);
|
|
FTransform CameraTransform = FTransform(CameraRotator);
|
|
// Remove the scale of the node holding a camera (the mesh is provide by the engine and can be different in size)
|
|
LocalNodeInfoPtr->Transform.SetScale3D(FVector(1.0f));
|
|
LocalNodeInfoPtr->Transform = CameraTransform * LocalNodeInfoPtr->Transform;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
UObject* UFbxSceneImportFactory::FactoryCreateBinary(
|
|
UClass* Class,
|
|
UObject* InParent,
|
|
FName Name,
|
|
EObjectFlags Flags,
|
|
UObject* Context,
|
|
const TCHAR* Type,
|
|
const uint8*& Buffer,
|
|
const uint8* BufferEnd,
|
|
FFeedbackContext* Warn)
|
|
{
|
|
if (InParent == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(UFbxSceneImportFactory::FactoryCreateBinary);
|
|
|
|
NameOptionsMap.Reset();
|
|
UWorld* World = GWorld;
|
|
ULevel* CurrentLevel = World->GetCurrentLevel();
|
|
// We will call other factory store the filename value since UFactory::CurrentFilename is static
|
|
FString FbxImportFileName = UFactory::CurrentFilename;
|
|
// Unselect all actors.
|
|
GEditor->SelectNone(false, false);
|
|
|
|
GEditor->GetEditorSubsystem<UImportSubsystem>()->BroadcastAssetPreImport(this, Class, InParent, Name, Type);
|
|
|
|
// logger for all error/warnings
|
|
// this one prints all messages that are stored in FFbxImporter
|
|
UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance();
|
|
|
|
UnFbx::FFbxLoggerSetter Logger(FbxImporter);
|
|
|
|
Warn->BeginSlowTask(NSLOCTEXT("FbxSceneFactory", "BeginImportingFbxSceneTask", "Importing FBX scene"), true);
|
|
|
|
GlobalImportSettings = FbxImporter->GetImportOptions();
|
|
UnFbx::FBXImportOptions::ResetOptions(GlobalImportSettings);
|
|
|
|
// Always convert the scene
|
|
GlobalImportSettings->bConvertScene = true;
|
|
GlobalImportSettings->bConvertSceneUnit = true;
|
|
|
|
// Set the import option in importscene mode
|
|
GlobalImportSettings->bImportScene = true;
|
|
bool OriginalForceFrontXAxis = GlobalImportSettings->bForceFrontXAxis;
|
|
// Read the fbx and store the hierarchy's information so we can reuse it after importing all the model in the fbx file
|
|
if (!FbxImporter->ImportFromFile(*FbxImportFileName, Type, true))
|
|
{
|
|
// Log the error message and fail the import.
|
|
Warn->Log(ELogVerbosity::Error, FbxImporter->GetErrorMessage());
|
|
FbxImporter->ReleaseScene();
|
|
FbxImporter = nullptr;
|
|
Warn->EndSlowTask();
|
|
GEditor->GetEditorSubsystem<UImportSubsystem>()->BroadcastAssetPostImport(this, World);
|
|
return nullptr;
|
|
}
|
|
|
|
// Make sure the Skeleton is null and not garbage, as we are importing the skeletalmesh for the first time we do not need any skeleton
|
|
GlobalImportSettings->SkeletonForAnimation = nullptr;
|
|
GlobalImportSettings->PhysicsAsset = nullptr;
|
|
|
|
FString PackageName = "";
|
|
InParent->GetName(PackageName);
|
|
Path = FPaths::GetPath(PackageName);
|
|
|
|
UnFbx::FbxSceneInfo SceneInfo;
|
|
// Read the scene and found all instance with their scene information.
|
|
FbxImporter->GetSceneInfo(FbxImportFileName, SceneInfo, true);
|
|
|
|
GlobalImportSettingsReference = new UnFbx::FBXImportOptions();
|
|
SFbxSceneOptionWindow::CopyFbxOptionsToFbxOptions(GlobalImportSettings, GlobalImportSettingsReference);
|
|
|
|
// Convert old structure to the new scene export structure
|
|
TSharedPtr<FFbxSceneInfo> SceneInfoPtr = ConvertSceneInfo(FbxImporter, &SceneInfo);
|
|
|
|
// Get import material info
|
|
ExtractMaterialInfo(FbxImporter, SceneInfoPtr);
|
|
|
|
if (!GetFbxSceneImportOptions(FbxImporter, SceneInfoPtr, GlobalImportSettingsReference, SceneImportOptions, SceneImportOptionsStaticMesh, NameOptionsMap, SceneImportOptionsSkeletalMesh, Path))
|
|
{
|
|
// User cancel the scene import
|
|
ImportWasCancel = true;
|
|
FbxImporter->ReleaseScene();
|
|
FbxImporter = nullptr;
|
|
GlobalImportSettings = nullptr;
|
|
GlobalImportSettingsReference = nullptr;
|
|
Warn->EndSlowTask();
|
|
GEditor->GetEditorSubsystem<UImportSubsystem>()->BroadcastAssetPostImport(this, World);
|
|
return nullptr;
|
|
}
|
|
|
|
SFbxSceneOptionWindow::CopyFbxOptionsToFbxOptions(GlobalImportSettingsReference, GlobalImportSettings);
|
|
|
|
// Convert the scene to the correct axis system. Option like force front X or ConvertScene affect the scene conversion
|
|
// We need to get the new convert transform
|
|
if (OriginalForceFrontXAxis != GlobalImportSettings->bForceFrontXAxis)
|
|
{
|
|
ChangeFrontAxis(FbxImporter, &SceneInfo, SceneInfoPtr);
|
|
}
|
|
|
|
FillSceneHierarchyPath(SceneInfoPtr);
|
|
|
|
ReimportData = CreateReImportAsset(Path, FbxImportFileName, SceneImportOptions, SceneInfoPtr, NameOptionsMap);
|
|
if (ReimportData == nullptr)
|
|
{
|
|
// Cannot save the reimport data
|
|
const FText CreateReimportDataFailed = LOCTEXT("CreateReimportDataFailed", "Failed to create the re import data asset, which will make impossible the re import of this scene.\nLook in the logs to see the reason.\nPress Ok to continue or Cancel to abort the import process");
|
|
if (FMessageDialog::Open(EAppMsgType::OkCancel, CreateReimportDataFailed) == EAppReturnType::Cancel)
|
|
{
|
|
// User cancel the scene import
|
|
ImportWasCancel = true;
|
|
FbxImporter->ReleaseScene();
|
|
FbxImporter = nullptr;
|
|
GlobalImportSettings = nullptr;
|
|
Warn->EndSlowTask();
|
|
GEditor->GetEditorSubsystem<UImportSubsystem>()->BroadcastAssetPostImport(this, World);
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
// We are a scene import set the flag for the reimport factory for both static mesh and skeletal mesh
|
|
StaticMeshImportData->bImportAsScene = true;
|
|
StaticMeshImportData->FbxSceneImportDataReference = ReimportData;
|
|
|
|
SkeletalMeshImportData->bImportAsScene = true;
|
|
SkeletalMeshImportData->FbxSceneImportDataReference = ReimportData;
|
|
|
|
AnimSequenceImportData->bImportAsScene = true;
|
|
AnimSequenceImportData->FbxSceneImportDataReference = ReimportData;
|
|
|
|
// Get the scene root node
|
|
FbxNode* RootNodeToImport = FbxImporter->Scene->GetRootNode();
|
|
|
|
// For animation and static mesh we assume there is at lease one interesting node by default
|
|
int32 InterestingNodeCount = 1;
|
|
|
|
AllNewAssets.Empty();
|
|
|
|
int32 NodeIndex = 0;
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// IMPORT ALL SKELETAL MESH
|
|
ImportAllSkeletalMesh(RootNodeToImport, FbxImporter, Flags, NodeIndex, InterestingNodeCount, SceneInfoPtr);
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// IMPORT ALL STATIC MESH
|
|
ImportAllStaticMesh(RootNodeToImport, FbxImporter, Flags, NodeIndex, InterestingNodeCount, SceneInfoPtr);
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
UObject* ReturnObject = nullptr;
|
|
for (auto ItAsset = AllNewAssets.CreateIterator(); ItAsset; ++ItAsset)
|
|
{
|
|
UObject* AssetObject = ItAsset.Value();
|
|
if (AssetObject)
|
|
{
|
|
if (ReturnObject == nullptr)
|
|
{
|
|
// Set the first import object as the return object to prevent false error from the caller of this factory
|
|
ReturnObject = AssetObject;
|
|
}
|
|
if (AssetObject->IsA(UStaticMesh::StaticClass()) || AssetObject->IsA(USkeletalMesh::StaticClass()))
|
|
{
|
|
// Mark the mesh as modified so the render will draw the mesh correctly
|
|
AssetObject->Modify();
|
|
AssetObject->PostEditChange();
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// CREATE AND PLACE ACTOR
|
|
// Instantiate all the scene hierarchy in the current level with link to previous created objects
|
|
// go through the hierarchy and instantiate actor in the current level
|
|
switch (SceneImportOptions->HierarchyType)
|
|
{
|
|
case EFBXSceneOptionsCreateHierarchyType::FBXSOCHT_CreateLevelActors: {
|
|
CreateLevelActorHierarchy(SceneInfoPtr);
|
|
}
|
|
break;
|
|
case EFBXSceneOptionsCreateHierarchyType::FBXSOCHT_CreateActorComponents:
|
|
case EFBXSceneOptionsCreateHierarchyType::FBXSOCHT_CreateBlueprint: {
|
|
AActor* HierarchyActor = CreateActorComponentsHierarchy(SceneInfoPtr);
|
|
// If the user want to export to a BP replace the container actor with a BP link
|
|
if (SceneImportOptions->HierarchyType == EFBXSceneOptionsCreateHierarchyType::FBXSOCHT_CreateBlueprint && HierarchyActor != nullptr)
|
|
{
|
|
// The location+name of the BP is the user select content path + fbx base filename
|
|
FString FullnameBP = Path + TEXT("/FbxScene_") + FPaths::GetBaseFilename(UFactory::CurrentFilename);
|
|
FullnameBP = UPackageTools::SanitizePackageName(FullnameBP);
|
|
FString AssetName = TEXT("FbxScene_") + FPaths::GetBaseFilename(UFactory::CurrentFilename);
|
|
UPackage* Pkg = CreatePackageForNode(FullnameBP, AssetName);
|
|
|
|
// Create the blueprint from the actor and replace the actor with an instance of the created blueprint
|
|
FKismetEditorUtilities::FCreateBlueprintFromActorParams Params;
|
|
Params.bReplaceActor = true;
|
|
Params.bKeepMobility = true;
|
|
UBlueprint* SceneBlueprint = FKismetEditorUtilities::CreateBlueprintFromActor(Pkg->GetName(), HierarchyActor, Params);
|
|
|
|
if (SceneBlueprint != nullptr && ReimportData != nullptr)
|
|
{
|
|
// let the scene blueprint be the return object for this import
|
|
ReturnObject = SceneBlueprint;
|
|
// Set the blueprint path name in the re import scene data asset, this will allow re import to find the original import blueprint
|
|
ReimportData->BluePrintFullName = SceneBlueprint->GetPathName();
|
|
}
|
|
GEngine->BroadcastLevelActorListChanged();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
// If there is no content asset create return the fbx scene import data
|
|
// This can happen if we only import actor in the scene like lights and camera
|
|
if (ReturnObject == nullptr)
|
|
{
|
|
ReturnObject = ReimportData;
|
|
}
|
|
// Release the FbxImporter
|
|
FbxImporter->ReleaseScene();
|
|
FbxImporter = nullptr;
|
|
GlobalImportSettings = nullptr;
|
|
GlobalImportSettingsReference = nullptr;
|
|
|
|
ReimportData = nullptr;
|
|
|
|
Warn->EndSlowTask();
|
|
GEditor->GetEditorSubsystem<UImportSubsystem>()->BroadcastAssetPostImport(this, World);
|
|
|
|
return ReturnObject;
|
|
}
|
|
|
|
bool UFbxSceneImportFactory::SetStaticMeshComponentOverrideMaterial(UStaticMeshComponent* StaticMeshComponent, TSharedPtr<FFbxNodeInfo> NodeInfo)
|
|
{
|
|
bool bOverrideMaterial = false;
|
|
UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
|
|
if (StaticMesh->GetStaticMaterials().Num() == NodeInfo->Materials.Num())
|
|
{
|
|
for (int32 MaterialIndex = 0; MaterialIndex < NodeInfo->Materials.Num(); ++MaterialIndex)
|
|
{
|
|
TSharedPtr<FFbxMaterialInfo> MaterialInfo = NodeInfo->Materials[MaterialIndex];
|
|
UMaterialInterface* MaterialInterface = Cast<UMaterialInterface>(MaterialInfo->GetContentObject());
|
|
if (MaterialInterface != nullptr && StaticMesh->GetMaterial(MaterialIndex) != MaterialInterface)
|
|
{
|
|
bOverrideMaterial = true;
|
|
break;
|
|
}
|
|
}
|
|
if (bOverrideMaterial)
|
|
{
|
|
for (int32 MaterialIndex = 0; MaterialIndex < NodeInfo->Materials.Num(); ++MaterialIndex)
|
|
{
|
|
TSharedPtr<FFbxMaterialInfo> MaterialInfo = NodeInfo->Materials[MaterialIndex];
|
|
UMaterialInterface* MaterialInterface = Cast<UMaterialInterface>(MaterialInfo->GetContentObject());
|
|
if (MaterialInterface != nullptr && StaticMesh->GetMaterial(MaterialIndex) != MaterialInterface)
|
|
{
|
|
StaticMeshComponent->SetMaterial(MaterialIndex, MaterialInterface);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return bOverrideMaterial;
|
|
}
|
|
|
|
USceneComponent* CreateCameraComponent(AActor* ParentActor, TSharedPtr<FFbxCameraInfo> CameraInfo)
|
|
{
|
|
UCineCameraComponent* CameraComponent = NewObject<UCineCameraComponent>(ParentActor, *(CameraInfo->Name));
|
|
CameraComponent->SetProjectionMode(CameraInfo->ProjectionPerspective ? ECameraProjectionMode::Perspective : ECameraProjectionMode::Orthographic);
|
|
CameraComponent->SetAspectRatio(CameraInfo->AspectWidth / CameraInfo->AspectHeight);
|
|
CameraComponent->SetOrthoNearClipPlane(CameraInfo->NearPlane);
|
|
CameraComponent->SetOrthoFarClipPlane(CameraInfo->FarPlane);
|
|
CameraComponent->SetOrthoWidth(CameraInfo->AspectWidth);
|
|
CameraComponent->SetFieldOfView(CameraInfo->FieldOfView);
|
|
CameraComponent->Filmback.SensorWidth = FUnitConversion::Convert(CameraInfo->ApertureWidth, EUnit::Inches, EUnit::Millimeters);
|
|
CameraComponent->Filmback.SensorHeight = FUnitConversion::Convert(CameraInfo->ApertureHeight, EUnit::Inches, EUnit::Millimeters);
|
|
CameraComponent->LensSettings.MaxFocalLength = CameraInfo->FocalLength;
|
|
CameraComponent->LensSettings.MinFocalLength = CameraInfo->FocalLength;
|
|
CameraComponent->FocusSettings.FocusMethod = ECameraFocusMethod::DoNotOverride;
|
|
|
|
return CameraComponent;
|
|
}
|
|
|
|
USceneComponent* CreateLightComponent(AActor* ParentActor, TSharedPtr<FFbxLightInfo> LightInfo)
|
|
{
|
|
ULightComponent* LightComponent = nullptr;
|
|
switch (LightInfo->Type)
|
|
{
|
|
case 0: {
|
|
// Point light
|
|
UPointLightComponent* PointLightComponent = NewObject<UPointLightComponent>(ParentActor, *(LightInfo->Name));
|
|
PointLightComponent->SetAttenuationRadius(LightInfo->EnableFarAttenuation ? LightInfo->FarAttenuationEnd : 16384.0f);
|
|
LightComponent = static_cast<ULightComponent*>(PointLightComponent);
|
|
LightComponent->SetIntensity(LightComponent->Intensity * LightInfo->Intensity / 100.0f);
|
|
}
|
|
break;
|
|
case 1: {
|
|
// Directional light
|
|
UDirectionalLightComponent* DirectionalLightComponent = NewObject<UDirectionalLightComponent>(ParentActor, *(LightInfo->Name));
|
|
LightComponent = static_cast<ULightComponent*>(DirectionalLightComponent);
|
|
// We cannot convert fbx value to unreal value so we kept the default object value
|
|
LightComponent->SetIntensity(LightComponent->Intensity * LightInfo->Intensity / 100.0f);
|
|
}
|
|
break;
|
|
case 2: {
|
|
// Spot light
|
|
USpotLightComponent* SpotLightComponent = NewObject<USpotLightComponent>(ParentActor, *(LightInfo->Name));
|
|
SpotLightComponent->SetInnerConeAngle(LightInfo->InnerAngle / 2.0f);
|
|
SpotLightComponent->SetOuterConeAngle(LightInfo->OuterAngle / 2.0f);
|
|
SpotLightComponent->SetAttenuationRadius(LightInfo->EnableFarAttenuation ? LightInfo->FarAttenuationEnd : 16384.0f);
|
|
LightComponent = static_cast<ULightComponent*>(SpotLightComponent);
|
|
|
|
LightComponent->SetIntensity(LightComponent->Intensity * LightInfo->Intensity / 100.0f);
|
|
}
|
|
break;
|
|
case 3:
|
|
case 4:
|
|
return nullptr;
|
|
break;
|
|
}
|
|
LightComponent->SetLightColor(LightInfo->Color);
|
|
LightComponent->SetCastShadows(LightInfo->CastShadow);
|
|
return LightComponent;
|
|
}
|
|
|
|
FVector GetParentPivotAccumulation(TSharedPtr<FFbxNodeInfo> NodeInfo, TSharedPtr<FFbxSceneInfo> SceneInfoPtr, FTransform& RootTransform)
|
|
{
|
|
TArray<TSharedPtr<FFbxNodeInfo>> ParentHierarchy;
|
|
FVector PivotAccumulation(0.0f);
|
|
TSharedPtr<FFbxNodeInfo> ParentNodeInfo = NodeInfo->ParentNodeInfo;
|
|
while (ParentNodeInfo.IsValid())
|
|
{
|
|
ParentHierarchy.Insert(ParentNodeInfo, 0);
|
|
ParentNodeInfo = ParentNodeInfo->ParentNodeInfo;
|
|
}
|
|
FTransform CurrentGlobalMatrix;
|
|
for (TSharedPtr<FFbxNodeInfo> ParentNode: ParentHierarchy)
|
|
{
|
|
FTransform LocalTransformAdjusted = ParentNode->Transform;
|
|
FVector PivotLocation(0.0f);
|
|
if (ParentNode->AttributeInfo.IsValid())
|
|
{
|
|
for (TSharedPtr<FFbxNodeInfo> NodeInfoIter: SceneInfoPtr->HierarchyInfo)
|
|
{
|
|
if (NodeInfoIter->UniqueId == ParentNode->AttributeInfo->PivotNodeUid)
|
|
{
|
|
PivotLocation = NodeInfoIter->PivotRotation;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
FTransform LocalTransform = ParentNode->Transform;
|
|
if (!PivotLocation.IsNearlyZero())
|
|
{
|
|
FTransform ParentPivotTransform;
|
|
ParentPivotTransform.SetLocation(PivotLocation);
|
|
FTransform AlmostNextCurrentTransform = LocalTransform * CurrentGlobalMatrix;
|
|
// Get the final matrix with pivot
|
|
LocalTransform = ParentPivotTransform * LocalTransform;
|
|
CurrentGlobalMatrix = LocalTransform * CurrentGlobalMatrix;
|
|
ParentPivotTransform = CurrentGlobalMatrix * AlmostNextCurrentTransform.Inverse();
|
|
PivotAccumulation = ParentPivotTransform.GetLocation();
|
|
}
|
|
else
|
|
{
|
|
CurrentGlobalMatrix = LocalTransform * CurrentGlobalMatrix;
|
|
}
|
|
}
|
|
return PivotAccumulation;
|
|
}
|
|
|
|
void UFbxSceneImportFactory::CreateLevelActorHierarchy(TSharedPtr<FFbxSceneInfo> SceneInfoPtr)
|
|
{
|
|
EComponentMobility::Type MobilityType = SceneImportOptions->bImportAsDynamic ? EComponentMobility::Movable : EComponentMobility::Static;
|
|
TMap<uint64, AActor*> NewActorNameMap;
|
|
FTransform RootTransform = FTransform::Identity;
|
|
bool bSelectActor = true;
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// iterate the whole hierarchy and create all actors
|
|
for (TSharedPtr<FFbxNodeInfo> NodeInfo: SceneInfoPtr->HierarchyInfo)
|
|
{
|
|
if (NodeInfo->NodeName.Compare("RootNode") == 0)
|
|
{
|
|
RootTransform = NodeInfo->Transform;
|
|
continue;
|
|
}
|
|
|
|
// Export only the node that are mark for export
|
|
if (!NodeInfo->bImportNode)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TSharedPtr<FFbxNodeInfo> LODParentNodeInfo = nullptr;
|
|
if (NodeInfo->AttributeType.Compare(TEXT("eMesh")) == 0)
|
|
{
|
|
LODParentNodeInfo = FFbxSceneInfo::RecursiveFindLODParentNode(NodeInfo);
|
|
}
|
|
|
|
// Find the asset that link with this node attribute
|
|
UObject* AssetToPlace = (NodeInfo->AttributeInfo.IsValid() && AllNewAssets.Contains(NodeInfo->AttributeInfo)) ? AllNewAssets[NodeInfo->AttributeInfo] : nullptr;
|
|
|
|
bool IsSkeletalMesh = false;
|
|
// create actor
|
|
AActor* PlacedActor = nullptr;
|
|
if (AssetToPlace != nullptr)
|
|
{
|
|
// Create an actor from the asset
|
|
// default flag is RF_Transactional;
|
|
PlacedActor = FActorFactoryAssetProxy::AddActorForAsset(AssetToPlace, bSelectActor);
|
|
|
|
// Set the actor override material
|
|
if (PlacedActor->IsA(AStaticMeshActor::StaticClass()))
|
|
{
|
|
UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(PlacedActor->GetComponentByClass(UStaticMeshComponent::StaticClass()));
|
|
SetStaticMeshComponentOverrideMaterial(StaticMeshComponent, NodeInfo);
|
|
}
|
|
IsSkeletalMesh = (AssetToPlace->GetClass() == USkeletalMesh::StaticClass());
|
|
}
|
|
else if (IsEmptyAttribute(NodeInfo->AttributeType) || NodeInfo->AttributeType.Compare("eMesh") == 0 || NodeInfo->AttributeUniqueId != INVALID_UNIQUE_ID)
|
|
{
|
|
if (NodeInfo->AttributeType.Compare("eMesh") == 0)
|
|
{
|
|
bool bIsSubSkeletalMesh = true;
|
|
for (TSharedPtr<FFbxMeshInfo> MeshInfo: SceneInfoPtr->MeshInfo)
|
|
{
|
|
if (!MeshInfo->bIsSkelMesh && NodeInfo->AttributeUniqueId == MeshInfo->UniqueId)
|
|
{
|
|
bIsSubSkeletalMesh = false;
|
|
break;
|
|
}
|
|
}
|
|
if (bIsSubSkeletalMesh == true)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
// Create an empty actor if the node is an empty attribute or the attribute is a mesh(static mesh or skeletal mesh) that was not export
|
|
UActorFactory* Factory = GEditor->FindActorFactoryByClass(UActorFactoryEmptyActor::StaticClass());
|
|
FAssetData EmptyActorAssetData = FAssetData(Factory->GetDefaultActorClass(FAssetData()));
|
|
// This is a group create an empty actor that just have a transform
|
|
UObject* EmptyActorAsset = EmptyActorAssetData.GetAsset();
|
|
// Place an empty actor
|
|
PlacedActor = FActorFactoryAssetProxy::AddActorForAsset(EmptyActorAsset, bSelectActor);
|
|
USceneComponent* RootComponent = nullptr;
|
|
if (NodeInfo->AttributeType.Compare("eLight") == 0)
|
|
{
|
|
TSharedPtr<FFbxLightInfo> LightInfo = *SceneInfoPtr->LightInfo.Find(NodeInfo->AttributeUniqueId);
|
|
RootComponent = CreateLightComponent(PlacedActor, LightInfo);
|
|
}
|
|
else if (NodeInfo->AttributeType.Compare("eCamera") == 0)
|
|
{
|
|
TSharedPtr<FFbxCameraInfo> CameraInfo = *SceneInfoPtr->CameraInfo.Find(NodeInfo->AttributeUniqueId);
|
|
RootComponent = CreateCameraComponent(PlacedActor, CameraInfo);
|
|
}
|
|
|
|
if (RootComponent == nullptr)
|
|
{
|
|
if (LODParentNodeInfo.IsValid())
|
|
{
|
|
// This is not LOD index 0, don't export the transform. Lod 0 should have an asset to place
|
|
continue;
|
|
}
|
|
RootComponent = NewObject<USceneComponent>(PlacedActor, USceneComponent::GetDefaultSceneRootVariableName());
|
|
}
|
|
RootComponent->Mobility = MobilityType;
|
|
RootComponent->bVisualizeComponent = true;
|
|
PlacedActor->SetRootComponent(RootComponent);
|
|
PlacedActor->AddInstanceComponent(RootComponent);
|
|
RootComponent->RegisterComponent();
|
|
}
|
|
else
|
|
{
|
|
// TODO log which fbx attribute we cannot create an actor from
|
|
}
|
|
|
|
if (PlacedActor != nullptr)
|
|
{
|
|
PlacedActor->SetFlags(RF_Transactional);
|
|
// Rename the actor correctly
|
|
// When importing a scene we don't want to change the actor name even if there is similar label already existing
|
|
PlacedActor->SetActorLabel(NodeInfo->NodeName);
|
|
|
|
USceneComponent* RootComponent = PlacedActor->GetRootComponent();
|
|
if (RootComponent)
|
|
{
|
|
RootComponent->SetFlags(RF_Transactional);
|
|
// Set the mobility
|
|
RootComponent->Mobility = MobilityType;
|
|
// Map the new actor name with the old name in case the name is changing
|
|
NewActorNameMap.Add(NodeInfo->UniqueId, PlacedActor);
|
|
uint64 ParentUniqueId = NodeInfo->ParentNodeInfo.IsValid() ? NodeInfo->ParentNodeInfo->UniqueId : 0;
|
|
if (LODParentNodeInfo.IsValid())
|
|
{
|
|
ParentUniqueId = LODParentNodeInfo->UniqueId;
|
|
}
|
|
AActor* ParentActor = nullptr;
|
|
// If there is a parent we must set the parent actor
|
|
if (NewActorNameMap.Contains(ParentUniqueId))
|
|
{
|
|
ParentActor = *NewActorNameMap.Find(ParentUniqueId);
|
|
if (ParentActor != nullptr)
|
|
{
|
|
USceneComponent* ParentRootComponent = ParentActor->GetRootComponent();
|
|
if (ParentRootComponent)
|
|
{
|
|
if (GEditor->CanParentActors(ParentActor, PlacedActor))
|
|
{
|
|
GEditor->ParentActors(ParentActor, PlacedActor, NAME_None);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Find the pivot location
|
|
FVector PivotLocation(0.0f);
|
|
FVector ParentPivotAccumulation(0.0f);
|
|
if (!IsSkeletalMesh && GlobalImportSettings->bBakePivotInVertex)
|
|
{
|
|
ParentPivotAccumulation -= GetParentPivotAccumulation(NodeInfo, SceneInfoPtr, RootTransform);
|
|
if (NodeInfo->AttributeInfo.IsValid() && NodeInfo->AttributeInfo->PivotNodeUid != INVALID_UNIQUE_ID)
|
|
{
|
|
for (TSharedPtr<FFbxNodeInfo> NodeInfoIter: SceneInfoPtr->HierarchyInfo)
|
|
{
|
|
if (NodeInfoIter->UniqueId == NodeInfo->AttributeInfo->PivotNodeUid)
|
|
{
|
|
PivotLocation = NodeInfoIter->PivotRotation;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply the hierarchy local transform to the root component
|
|
ApplyTransformToComponent(RootComponent, &(NodeInfo->Transform), ParentActor == nullptr ? &RootTransform : nullptr, PivotLocation, ParentPivotAccumulation);
|
|
// Notify people that the component get created/changed
|
|
RootComponent->PostEditChange();
|
|
}
|
|
}
|
|
// We select only the first actor
|
|
bSelectActor = false;
|
|
}
|
|
// End of iteration of the hierarchy
|
|
//////////////////////////////////////////////////////////////////////////
|
|
}
|
|
|
|
AActor* UFbxSceneImportFactory::CreateActorComponentsHierarchy(TSharedPtr<FFbxSceneInfo> SceneInfoPtr)
|
|
{
|
|
FString FbxImportFileName = UFactory::CurrentFilename;
|
|
UBlueprint* NewBluePrintActor = nullptr;
|
|
AActor* RootActorContainer = nullptr;
|
|
FString FilenameBase = FbxImportFileName.IsEmpty() ? TEXT("TransientToBlueprintActor") : FPaths::GetBaseFilename(FbxImportFileName);
|
|
USceneComponent* ActorRootComponent = nullptr;
|
|
TMap<uint64, USceneComponent*> NewSceneComponentNameMap;
|
|
EComponentMobility::Type MobilityType = SceneImportOptions->bImportAsDynamic ? EComponentMobility::Movable : EComponentMobility::Static;
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Create the Actor where to put components in
|
|
UActorFactory* Factory = GEditor->FindActorFactoryByClass(UActorFactoryEmptyActor::StaticClass());
|
|
FAssetData EmptyActorAssetData = FAssetData(Factory->GetDefaultActorClass(FAssetData()));
|
|
// This is a group create an empty actor that just have a transform
|
|
UObject* EmptyActorAsset = EmptyActorAssetData.GetAsset();
|
|
// Place an empty actor
|
|
RootActorContainer = FActorFactoryAssetProxy::AddActorForAsset(EmptyActorAsset, false);
|
|
check(RootActorContainer != nullptr);
|
|
ActorRootComponent = NewObject<USceneComponent>(RootActorContainer, USceneComponent::GetDefaultSceneRootVariableName());
|
|
check(ActorRootComponent != nullptr);
|
|
ActorRootComponent->Mobility = MobilityType;
|
|
ActorRootComponent->bVisualizeComponent = true;
|
|
RootActorContainer->SetRootComponent(ActorRootComponent);
|
|
RootActorContainer->AddInstanceComponent(ActorRootComponent);
|
|
ActorRootComponent->RegisterComponent();
|
|
RootActorContainer->SetActorLabel(FilenameBase);
|
|
RootActorContainer->SetFlags(RF_Transactional);
|
|
ActorRootComponent->SetFlags(RF_Transactional);
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// iterate the whole hierarchy and create all component
|
|
FTransform RootTransform = FTransform::Identity;
|
|
for (TSharedPtr<FFbxNodeInfo> NodeInfo: SceneInfoPtr->HierarchyInfo)
|
|
{
|
|
// Set the root transform if its the root node and skip the node
|
|
// The root transform will be use for every node under the root node
|
|
if (NodeInfo->NodeName.Compare("RootNode") == 0)
|
|
{
|
|
RootTransform = NodeInfo->Transform;
|
|
continue;
|
|
}
|
|
// Export only the node that are mark for export
|
|
if (!NodeInfo->bImportNode)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TSharedPtr<FFbxNodeInfo> LODParentNodeInfo = nullptr;
|
|
if (NodeInfo->AttributeType.Compare(TEXT("eMesh")) == 0)
|
|
{
|
|
LODParentNodeInfo = FFbxSceneInfo::RecursiveFindLODParentNode(NodeInfo);
|
|
}
|
|
// Find the asset that link with this node attribute
|
|
UObject* AssetToPlace = (NodeInfo->AttributeInfo.IsValid() && AllNewAssets.Contains(NodeInfo->AttributeInfo)) ? AllNewAssets[NodeInfo->AttributeInfo] : nullptr;
|
|
|
|
bool IsSkeletalMesh = false;
|
|
// Create the component where the type depend on the asset point by the component
|
|
// In case there is no asset we create a SceneComponent
|
|
USceneComponent* SceneComponent = nullptr;
|
|
if (AssetToPlace != nullptr)
|
|
{
|
|
if (AssetToPlace->GetClass() == UStaticMesh::StaticClass())
|
|
{
|
|
// Component will be rename later
|
|
UStaticMeshComponent* StaticMeshComponent = NewObject<UStaticMeshComponent>(RootActorContainer, NAME_None);
|
|
StaticMeshComponent->SetStaticMesh(Cast<UStaticMesh>(AssetToPlace));
|
|
StaticMeshComponent->DepthPriorityGroup = SDPG_World;
|
|
SetStaticMeshComponentOverrideMaterial(StaticMeshComponent, NodeInfo);
|
|
SceneComponent = StaticMeshComponent;
|
|
SceneComponent->Mobility = MobilityType;
|
|
}
|
|
else if (AssetToPlace->GetClass() == USkeletalMesh::StaticClass())
|
|
{
|
|
// Component will be rename later
|
|
USkeletalMeshComponent* SkeletalMeshComponent = NewObject<USkeletalMeshComponent>(RootActorContainer, NAME_None);
|
|
SkeletalMeshComponent->SetSkeletalMesh(Cast<USkeletalMesh>(AssetToPlace));
|
|
SkeletalMeshComponent->DepthPriorityGroup = SDPG_World;
|
|
SceneComponent = SkeletalMeshComponent;
|
|
SceneComponent->Mobility = MobilityType;
|
|
IsSkeletalMesh = true;
|
|
}
|
|
}
|
|
else if (IsEmptyAttribute(NodeInfo->AttributeType) || NodeInfo->AttributeType.Compare("eMesh") == 0 || NodeInfo->AttributeUniqueId != INVALID_UNIQUE_ID)
|
|
{
|
|
if (NodeInfo->AttributeType.Compare("eMesh") == 0)
|
|
{
|
|
bool bIsSubSkeletalMesh = true;
|
|
for (TSharedPtr<FFbxMeshInfo> MeshInfo: SceneInfoPtr->MeshInfo)
|
|
{
|
|
if (!MeshInfo->bIsSkelMesh && NodeInfo->AttributeUniqueId == MeshInfo->UniqueId)
|
|
{
|
|
bIsSubSkeletalMesh = false;
|
|
break;
|
|
}
|
|
}
|
|
if (bIsSubSkeletalMesh == true)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (NodeInfo->AttributeType.Compare("eLight") == 0 && SceneInfoPtr->LightInfo.Contains(NodeInfo->AttributeUniqueId))
|
|
{
|
|
TSharedPtr<FFbxLightInfo> LightInfo = *SceneInfoPtr->LightInfo.Find(NodeInfo->AttributeUniqueId);
|
|
SceneComponent = CreateLightComponent(RootActorContainer, LightInfo);
|
|
}
|
|
else if (NodeInfo->AttributeType.Compare("eCamera") == 0 && SceneInfoPtr->CameraInfo.Contains(NodeInfo->AttributeUniqueId))
|
|
{
|
|
TSharedPtr<FFbxCameraInfo> CameraInfo = *SceneInfoPtr->CameraInfo.Find(NodeInfo->AttributeUniqueId);
|
|
SceneComponent = CreateCameraComponent(RootActorContainer, CameraInfo);
|
|
}
|
|
|
|
if (SceneComponent == nullptr)
|
|
{
|
|
if (LODParentNodeInfo.IsValid())
|
|
{
|
|
// This is not LOD index 0, don't export the transform. Lod 0 should have an asset to place
|
|
continue;
|
|
}
|
|
SceneComponent = NewObject<USceneComponent>(RootActorContainer, NAME_None);
|
|
}
|
|
// Component will be rename later
|
|
SceneComponent->Mobility = MobilityType;
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Make sure undo/redo is working
|
|
SceneComponent->SetFlags(RF_Transactional);
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Make sure scenecomponent name are unique in the hierarchy of the outer
|
|
FString NewUniqueName = NodeInfo->NodeName;
|
|
if (!SceneComponent->Rename(*NewUniqueName, nullptr, REN_Test))
|
|
{
|
|
NewUniqueName = MakeUniqueObjectName(RootActorContainer, USceneComponent::StaticClass(), FName(*NodeInfo->NodeName)).ToString();
|
|
}
|
|
SceneComponent->Rename(*NewUniqueName, nullptr, REN_DontCreateRedirectors);
|
|
|
|
// Add the component to the owner actor and register it
|
|
RootActorContainer->AddInstanceComponent(SceneComponent);
|
|
SceneComponent->RegisterComponent();
|
|
|
|
// Add the component to the temporary map so we can retrieve it later when we search for parent
|
|
NewSceneComponentNameMap.Add(NodeInfo->UniqueId, SceneComponent);
|
|
|
|
// Find the parent component by unique ID and attach(as child) the newly created scenecomponent
|
|
// Attach the component to the rootcomponent if we dont find any parent component
|
|
uint64 ParentUniqueId = NodeInfo->ParentNodeInfo.IsValid() ? NodeInfo->ParentNodeInfo->UniqueId : 0;
|
|
if (LODParentNodeInfo.IsValid())
|
|
{
|
|
ParentUniqueId = LODParentNodeInfo->UniqueId;
|
|
}
|
|
USceneComponent* ParentRootComponent = nullptr;
|
|
if (NewSceneComponentNameMap.Contains(ParentUniqueId))
|
|
{
|
|
ParentRootComponent = *NewSceneComponentNameMap.Find(ParentUniqueId);
|
|
if (ParentRootComponent != nullptr)
|
|
{
|
|
SceneComponent->AttachToComponent(ParentRootComponent, FAttachmentTransformRules::KeepWorldTransform);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SceneComponent->AttachToComponent(ActorRootComponent, FAttachmentTransformRules::KeepWorldTransform);
|
|
}
|
|
|
|
// Find the pivot location
|
|
FVector PivotLocation(0.0f);
|
|
FVector ParentPivotAccumulation(0.0f);
|
|
if (!IsSkeletalMesh && GlobalImportSettings->bBakePivotInVertex)
|
|
{
|
|
ParentPivotAccumulation -= GetParentPivotAccumulation(NodeInfo, SceneInfoPtr, RootTransform);
|
|
if (NodeInfo->AttributeInfo.IsValid() && NodeInfo->AttributeInfo->PivotNodeUid != INVALID_UNIQUE_ID)
|
|
{
|
|
for (TSharedPtr<FFbxNodeInfo> NodeInfoIter: SceneInfoPtr->HierarchyInfo)
|
|
{
|
|
if (NodeInfoIter->UniqueId == NodeInfo->AttributeInfo->PivotNodeUid)
|
|
{
|
|
PivotLocation = NodeInfoIter->PivotRotation;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply the local transform to the scene component
|
|
ApplyTransformToComponent(SceneComponent, &(NodeInfo->Transform), ParentRootComponent != nullptr ? nullptr : &RootTransform, PivotLocation, ParentPivotAccumulation);
|
|
// Notify people that the component get created/changed
|
|
SceneComponent->PostEditChange();
|
|
}
|
|
// End of iteration of the hierarchy
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
return RootActorContainer;
|
|
}
|
|
|
|
void UFbxSceneImportFactory::ApplyTransformToComponent(USceneComponent* SceneComponent, FTransform* LocalTransform, FTransform* PreMultiplyTransform, FVector& PivotLocation, FVector& ParentPivotAccumulation)
|
|
{
|
|
check(SceneComponent);
|
|
check(LocalTransform);
|
|
FTransform LocalTransformAdjusted = (*LocalTransform);
|
|
if (GlobalImportSettings->bBakePivotInVertex && (!PivotLocation.IsNearlyZero() || !ParentPivotAccumulation.IsNearlyZero()))
|
|
{
|
|
FTransform PivotTransform;
|
|
PivotTransform.SetLocation(ParentPivotAccumulation);
|
|
LocalTransformAdjusted = LocalTransformAdjusted * PivotTransform;
|
|
PivotTransform.SetIdentity();
|
|
PivotTransform.SetLocation(PivotLocation);
|
|
LocalTransformAdjusted = PivotTransform * LocalTransformAdjusted;
|
|
}
|
|
// In case there is no parent we must multiply the root transform
|
|
if (PreMultiplyTransform != nullptr)
|
|
{
|
|
FTransform OutTransform = FTransform::Identity;
|
|
FTransform::Multiply(&OutTransform, &LocalTransformAdjusted, PreMultiplyTransform);
|
|
SceneComponent->SetRelativeTransform(OutTransform);
|
|
}
|
|
else
|
|
{
|
|
SceneComponent->SetRelativeTransform(LocalTransformAdjusted);
|
|
}
|
|
}
|
|
void UFbxSceneImportFactory::ApplyMeshInfoFbxOptions(TSharedPtr<FFbxMeshInfo> MeshInfo)
|
|
{
|
|
if (!MeshInfo.IsValid())
|
|
{
|
|
// Use the default options
|
|
SFbxSceneOptionWindow::CopyFbxOptionsToFbxOptions(GlobalImportSettingsReference, GlobalImportSettings);
|
|
SFbxSceneOptionWindow::CopyFbxOptionsToSkeletalMeshOptions(GlobalImportSettingsReference, SceneImportOptionsSkeletalMesh);
|
|
SFbxSceneOptionWindow::CopyFbxOptionsToStaticMeshOptions(GlobalImportSettingsReference, SceneImportOptionsStaticMesh);
|
|
}
|
|
else
|
|
{
|
|
UnFbx::FBXImportOptions* OverrideImportSettings = GetOptionsFromName(MeshInfo->OptionName);
|
|
if (OverrideImportSettings != nullptr)
|
|
{
|
|
// Use the override options
|
|
SFbxSceneOptionWindow::CopyFbxOptionsToFbxOptions(OverrideImportSettings, GlobalImportSettings);
|
|
SFbxSceneOptionWindow::CopyFbxOptionsToSkeletalMeshOptions(OverrideImportSettings, SceneImportOptionsSkeletalMesh);
|
|
SFbxSceneOptionWindow::CopyFbxOptionsToStaticMeshOptions(OverrideImportSettings, SceneImportOptionsStaticMesh);
|
|
}
|
|
else
|
|
{
|
|
// Use the default options if we found no options
|
|
SFbxSceneOptionWindow::CopyFbxOptionsToFbxOptions(GlobalImportSettingsReference, GlobalImportSettings);
|
|
SFbxSceneOptionWindow::CopyFbxOptionsToSkeletalMeshOptions(GlobalImportSettingsReference, SceneImportOptionsSkeletalMesh);
|
|
SFbxSceneOptionWindow::CopyFbxOptionsToStaticMeshOptions(GlobalImportSettingsReference, SceneImportOptionsStaticMesh);
|
|
}
|
|
}
|
|
SceneImportOptionsSkeletalMesh->FillSkeletalMeshInmportData(SkeletalMeshImportData, AnimSequenceImportData, SceneImportOptions);
|
|
SceneImportOptionsStaticMesh->FillStaticMeshInmportData(StaticMeshImportData, SceneImportOptions);
|
|
}
|
|
|
|
UObject* UFbxSceneImportFactory::ImportOneSkeletalMesh(void* VoidRootNodeToImport, void* VoidFbxImporter, TSharedPtr<FFbxSceneInfo> SceneInfo, EObjectFlags Flags, TArray<void*>& VoidNodeArray, int32& TotalNumNodes)
|
|
{
|
|
FbxNode* RootNodeToImport = (FbxNode*)VoidRootNodeToImport;
|
|
UnFbx::FFbxImporter* FbxImporter = (UnFbx::FFbxImporter*)VoidFbxImporter;
|
|
TArray<FbxNode*> NodeArray;
|
|
for (void* VoidNode: VoidNodeArray)
|
|
{
|
|
FbxNode* Node = (FbxNode*)VoidNode;
|
|
NodeArray.Add(Node);
|
|
}
|
|
UObject* NewObject = nullptr;
|
|
UPackage* Pkg = nullptr;
|
|
TotalNumNodes += NodeArray.Num();
|
|
TSharedPtr<FFbxNodeInfo> RootNodeInfo;
|
|
if (TotalNumNodes > 0)
|
|
{
|
|
FbxNode* RootNodeArrayNode = NodeArray[0];
|
|
if (RootNodeArrayNode->GetNodeAttribute() && RootNodeArrayNode->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup)
|
|
{
|
|
// In case we have a LOD group we must have only one node in the array
|
|
check(NodeArray.Num() == 1);
|
|
RootNodeArrayNode = FbxImporter->FindLODGroupNode(RootNodeArrayNode, 0);
|
|
if (RootNodeArrayNode == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
if (!FindSceneNodeInfo(SceneInfo, RootNodeArrayNode->GetUniqueID(), RootNodeInfo))
|
|
{
|
|
return nullptr;
|
|
}
|
|
if (!RootNodeInfo->AttributeInfo.IsValid() || RootNodeInfo->AttributeInfo->GetType() != USkeletalMesh::StaticClass() || !RootNodeInfo->AttributeInfo->bImportAttribute)
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
if (!RootNodeInfo.IsValid())
|
|
{
|
|
return nullptr;
|
|
}
|
|
// Set the options
|
|
// Apply the correct fbx options
|
|
TSharedPtr<FFbxMeshInfo> MeshInfo = StaticCastSharedPtr<FFbxMeshInfo>(RootNodeInfo->AttributeInfo);
|
|
|
|
ApplyMeshInfoFbxOptions(MeshInfo);
|
|
// TODO support bBakePivotInVertex
|
|
bool Old_bBakePivotInVertex = GlobalImportSettings->bBakePivotInVertex;
|
|
GlobalImportSettings->bBakePivotInVertex = false;
|
|
GlobalImportSettings->bImportBoneTracks = true;
|
|
// if (GlobalImportSettings->bBakePivotInVertex && RootNodeInfo->AttributeInfo->PivotNodeUid == INVALID_UNIQUE_ID)
|
|
//{
|
|
// GlobalImportSettings->bBakePivotInVertex = false;
|
|
//}
|
|
|
|
// check if there is LODGroup for this skeletal mesh
|
|
int32 MaxLODLevel = 1;
|
|
for (int32 j = 0; j < NodeArray.Num(); j++)
|
|
{
|
|
FbxNode* Node = NodeArray[j];
|
|
if (Node->GetNodeAttribute() && Node->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup)
|
|
{
|
|
// get max LODgroup level
|
|
if (MaxLODLevel < Node->GetChildCount())
|
|
{
|
|
MaxLODLevel = Node->GetChildCount();
|
|
}
|
|
}
|
|
}
|
|
|
|
// The skeletalmesh will be set after we import the LOD 0 since it is not created yet.
|
|
FScopedSkeletalMeshPostEditChange ScopedPostEditChange(nullptr);
|
|
|
|
int32 LODIndex;
|
|
for (LODIndex = 0; LODIndex < MaxLODLevel; LODIndex++)
|
|
{
|
|
TArray<FbxNode*> SkelMeshNodeArray;
|
|
for (int32 j = 0; j < NodeArray.Num(); j++)
|
|
{
|
|
FbxNode* Node = NodeArray[j];
|
|
if (Node->GetNodeAttribute() && Node->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup)
|
|
{
|
|
TArray<FbxNode*> NodeInLod;
|
|
if (Node->GetChildCount() > LODIndex)
|
|
{
|
|
FbxImporter->FindAllLODGroupNode(NodeInLod, Node, LODIndex);
|
|
}
|
|
else // in less some LODGroups have less level, use the last level
|
|
{
|
|
FbxImporter->FindAllLODGroupNode(NodeInLod, Node, Node->GetChildCount() - 1);
|
|
}
|
|
for (FbxNode* MeshNode: NodeInLod)
|
|
{
|
|
SkelMeshNodeArray.Add(MeshNode);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SkelMeshNodeArray.Add(Node);
|
|
}
|
|
}
|
|
|
|
// Make sure to bake the pivot the user choose to bake
|
|
TArray<FbxNode*> SkelMeshNodePivotArray;
|
|
bool bUseSkelMeshNodePivotArray = false;
|
|
if (GlobalImportSettings->bBakePivotInVertex)
|
|
{
|
|
for (FbxNode* SkelMeshNode: SkelMeshNodeArray)
|
|
{
|
|
TSharedPtr<FFbxNodeInfo> ExportNodeInfo;
|
|
if (FindSceneNodeInfo(SceneInfo, SkelMeshNode->GetUniqueID(), ExportNodeInfo))
|
|
{
|
|
if (ExportNodeInfo->AttributeInfo.IsValid())
|
|
{
|
|
FbxNode* NodePivot = FindFbxNodeById(FbxImporter, nullptr, ExportNodeInfo->AttributeInfo->PivotNodeUid);
|
|
if (NodePivot != nullptr)
|
|
{
|
|
SkelMeshNodePivotArray.Add(NodePivot);
|
|
bUseSkelMeshNodePivotArray = true;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
SkelMeshNodePivotArray.Add(SkelMeshNode);
|
|
}
|
|
}
|
|
FSkeletalMeshImportData OutData;
|
|
if (LODIndex == 0 && SkelMeshNodeArray.Num() != 0)
|
|
{
|
|
FName OutputName = FbxImporter->MakeNameForMesh(SkelMeshNodeArray[0]->GetName(), SkelMeshNodeArray[0]);
|
|
FString PackageName = Path + TEXT("/") + OutputName.ToString();
|
|
FString SkeletalMeshName;
|
|
Pkg = CreatePackageForNode(PackageName, SkeletalMeshName);
|
|
if (Pkg == nullptr)
|
|
break;
|
|
RootNodeInfo->AttributeInfo->SetOriginalImportPath(PackageName);
|
|
FName SkeletalMeshFName = FName(*SkeletalMeshName);
|
|
|
|
// Import the skeletal mesh
|
|
UnFbx::FFbxImporter::FImportSkeletalMeshArgs ImportSkeletalMeshArgs;
|
|
ImportSkeletalMeshArgs.InParent = Pkg;
|
|
ImportSkeletalMeshArgs.NodeArray = bUseSkelMeshNodePivotArray ? SkelMeshNodePivotArray : SkelMeshNodeArray;
|
|
ImportSkeletalMeshArgs.Name = SkeletalMeshFName;
|
|
ImportSkeletalMeshArgs.Flags = Flags;
|
|
ImportSkeletalMeshArgs.TemplateImportData = SkeletalMeshImportData;
|
|
ImportSkeletalMeshArgs.LodIndex = LODIndex;
|
|
ImportSkeletalMeshArgs.OutData = &OutData;
|
|
|
|
USkeletalMesh* NewMesh = FbxImporter->ImportSkeletalMesh(ImportSkeletalMeshArgs);
|
|
NewObject = NewMesh;
|
|
if (NewMesh)
|
|
{
|
|
ScopedPostEditChange.SetSkeletalMesh(NewMesh);
|
|
TSharedPtr<FFbxNodeInfo> SkelMeshNodeInfo;
|
|
if (FindSceneNodeInfo(SceneInfo, SkelMeshNodeArray[0]->GetUniqueID(), SkelMeshNodeInfo) && SkelMeshNodeInfo.IsValid() && SkelMeshNodeInfo->AttributeInfo.IsValid())
|
|
{
|
|
AllNewAssets.Add(SkelMeshNodeInfo->AttributeInfo, NewObject);
|
|
}
|
|
if (GlobalImportSettings->bImportAnimations)
|
|
{
|
|
// We need to remove all scaling from the root node before we set up animation data.
|
|
// Othewise some of the global transform calculations will be incorrect.
|
|
FbxImporter->RemoveTransformSettingsFromFbxNode(RootNodeToImport, SkeletalMeshImportData);
|
|
FbxImporter->SetupAnimationDataFromMesh(NewMesh, Pkg, SkelMeshNodeArray, AnimSequenceImportData, OutputName.ToString());
|
|
|
|
// Reapply the transforms for the rest of the import
|
|
FbxImporter->ApplyTransformSettingsToFbxNode(RootNodeToImport, SkeletalMeshImportData);
|
|
}
|
|
|
|
// Set the data in the node info
|
|
RootNodeInfo->AttributeInfo->SetOriginalImportPath(PackageName);
|
|
RootNodeInfo->AttributeInfo->SetOriginalFullImportName(NewObject->GetPathName());
|
|
}
|
|
}
|
|
else if (NewObject && GlobalImportSettings->bImportSkeletalMeshLODs) // the base skeletal mesh is imported successfully
|
|
{
|
|
USkeletalMesh* BaseSkeletalMesh = Cast<USkeletalMesh>(NewObject);
|
|
if ((bUseSkelMeshNodePivotArray ? SkelMeshNodePivotArray : SkelMeshNodeArray)[0]->GetMesh() == nullptr)
|
|
{
|
|
FSkeletalMeshUpdateContext UpdateContext;
|
|
UpdateContext.SkeletalMesh = BaseSkeletalMesh;
|
|
// Add a autogenerated LOD to the BaseSkeletalMesh
|
|
FSkeletalMeshLODInfo& LODInfo = BaseSkeletalMesh->AddLODInfo();
|
|
LODInfo.ReductionSettings.NumOfTrianglesPercentage = FMath::Pow(0.5f, (float)(LODIndex));
|
|
LODInfo.ReductionSettings.BaseLOD = 0;
|
|
LODInfo.bImportWithBaseMesh = true;
|
|
LODInfo.SourceImportFilename = FString(TEXT(""));
|
|
FLODUtilities::SimplifySkeletalMeshLOD(UpdateContext, LODIndex, GetTargetPlatformManagerRef().GetRunningTargetPlatform(), false);
|
|
}
|
|
else
|
|
{
|
|
FName LODObjectName = NAME_None;
|
|
// Import skeletal mesh LOD
|
|
UnFbx::FFbxImporter::FImportSkeletalMeshArgs ImportSkeletalMeshArgs;
|
|
ImportSkeletalMeshArgs.InParent = BaseSkeletalMesh->GetOutermost();
|
|
ImportSkeletalMeshArgs.NodeArray = bUseSkelMeshNodePivotArray ? SkelMeshNodePivotArray : SkelMeshNodeArray;
|
|
ImportSkeletalMeshArgs.Name = LODObjectName;
|
|
ImportSkeletalMeshArgs.Flags = RF_Transient;
|
|
ImportSkeletalMeshArgs.TemplateImportData = SkeletalMeshImportData;
|
|
ImportSkeletalMeshArgs.LodIndex = LODIndex;
|
|
ImportSkeletalMeshArgs.OutData = &OutData;
|
|
|
|
USkeletalMesh* LODObject = FbxImporter->ImportSkeletalMesh(ImportSkeletalMeshArgs);
|
|
bool bImportSucceeded = FbxImporter->ImportSkeletalMeshLOD(LODObject, BaseSkeletalMesh, LODIndex);
|
|
if (!bImportSucceeded)
|
|
{
|
|
FbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("FailedToImport_SkeletalMeshLOD", "Failed to import Skeletal mesh LOD.")), FFbxErrors::SkeletalMesh_LOD_FailedToImport);
|
|
}
|
|
}
|
|
}
|
|
|
|
// import morph target
|
|
if (NewObject && SkeletalMeshImportData->bImportMorphTargets)
|
|
{
|
|
if (Pkg == nullptr)
|
|
continue;
|
|
|
|
USkeletalMesh* NewSkelMesh = Cast<USkeletalMesh>(NewObject);
|
|
if ((GlobalImportSettings->bImportSkeletalMeshLODs || LODIndex == 0) &&
|
|
GlobalImportSettings->bImportMorph &&
|
|
NewSkelMesh &&
|
|
NewSkelMesh->GetImportedModel() &&
|
|
NewSkelMesh->GetImportedModel()->LODModels.IsValidIndex(LODIndex))
|
|
{
|
|
// TODO: Disable material importing when importing morph targets
|
|
FbxImporter->ImportFbxMorphTarget(SkelMeshNodeArray, NewSkelMesh, LODIndex, OutData);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Put back the options
|
|
GlobalImportSettings->bBakePivotInVertex = Old_bBakePivotInVertex;
|
|
|
|
// FScopedSkeletalMeshPostEditChange will call post edit change when going out of scope
|
|
return NewObject;
|
|
}
|
|
|
|
void UFbxSceneImportFactory::ImportAllSkeletalMesh(void* VoidRootNodeToImport, void* VoidFbxImporter, EObjectFlags Flags, int32& NodeIndex, int32& InterestingNodeCount, TSharedPtr<FFbxSceneInfo> SceneInfo)
|
|
{
|
|
UnFbx::FFbxImporter* FbxImporter = (UnFbx::FFbxImporter*)VoidFbxImporter;
|
|
FbxNode* RootNodeToImport = (FbxNode*)VoidRootNodeToImport;
|
|
InterestingNodeCount = 1;
|
|
TArray<TArray<FbxNode*>*> SkelMeshArray;
|
|
FbxImporter->FillFbxSkelMeshArrayInScene(RootNodeToImport, SkelMeshArray, false, false, true);
|
|
InterestingNodeCount = SkelMeshArray.Num();
|
|
|
|
int32 TotalNumNodes = 0;
|
|
|
|
for (int32 i = 0; i < SkelMeshArray.Num(); i++)
|
|
{
|
|
TArray<FbxNode*> NodeArray = *SkelMeshArray[i];
|
|
TArray<void*> VoidNodeArray;
|
|
for (FbxNode* Node: NodeArray)
|
|
{
|
|
void* VoidNode = (void*)Node;
|
|
VoidNodeArray.Add(VoidNode);
|
|
}
|
|
UObject* NewObject = ImportOneSkeletalMesh(VoidRootNodeToImport, VoidFbxImporter, SceneInfo, Flags, VoidNodeArray, TotalNumNodes);
|
|
if (NewObject)
|
|
{
|
|
NodeIndex++;
|
|
}
|
|
}
|
|
|
|
for (int32 i = 0; i < SkelMeshArray.Num(); i++)
|
|
{
|
|
delete SkelMeshArray[i];
|
|
}
|
|
|
|
// if total nodes we found is 0, we didn't find anything.
|
|
if (SkelMeshArray.Num() > 0 && TotalNumNodes == 0)
|
|
{
|
|
FbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("FailedToImport_NoMeshFoundOnRoot", "Could not find any valid mesh on the root hierarchy. If you have mesh in the sub hierarchy, please enable option of [Import Meshes In Bone Hierarchy] when import.")),
|
|
FFbxErrors::SkeletalMesh_NoMeshFoundOnRoot);
|
|
}
|
|
}
|
|
|
|
void UFbxSceneImportFactory::ImportAllStaticMesh(void* VoidRootNodeToImport, void* VoidFbxImporter, EObjectFlags Flags, int32& NodeIndex, int32& InterestingNodeCount, TSharedPtr<FFbxSceneInfo> SceneInfo)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(UFbxSceneImportFactory::ImportAllStaticMesh);
|
|
|
|
UnFbx::FFbxImporter* FbxImporter = (UnFbx::FFbxImporter*)VoidFbxImporter;
|
|
FbxNode* RootNodeToImport = (FbxNode*)VoidRootNodeToImport;
|
|
|
|
// Copy default options to StaticMeshImportData
|
|
SFbxSceneOptionWindow::CopyFbxOptionsToStaticMeshOptions(GlobalImportSettingsReference, SceneImportOptionsStaticMesh);
|
|
SceneImportOptionsStaticMesh->FillStaticMeshInmportData(StaticMeshImportData, SceneImportOptions);
|
|
|
|
FbxImporter->ApplyTransformSettingsToFbxNode(RootNodeToImport, StaticMeshImportData);
|
|
|
|
// count meshes in lod groups if we don't care about importing LODs
|
|
int32 NumLODGroups = 0;
|
|
bool bCountLODGroupMeshes = !GlobalImportSettingsReference->bImportStaticMeshLODs;
|
|
InterestingNodeCount = FbxImporter->GetFbxMeshCount(RootNodeToImport, bCountLODGroupMeshes, NumLODGroups);
|
|
|
|
int32 ImportedMeshCount = 0;
|
|
UStaticMesh* NewStaticMesh = nullptr;
|
|
UObject* Object = RecursiveImportNode(FbxImporter, RootNodeToImport, Flags, NodeIndex, InterestingNodeCount, SceneInfo, Path);
|
|
|
|
NewStaticMesh = Cast<UStaticMesh>(Object);
|
|
|
|
// Make sure to notify the asset registry of all assets created other than the one returned, which will notify the asset registry automatically.
|
|
for (auto AssetItKvp = AllNewAssets.CreateIterator(); AssetItKvp; ++AssetItKvp)
|
|
{
|
|
UObject* Asset = AssetItKvp.Value();
|
|
if (Asset != NewStaticMesh)
|
|
{
|
|
FAssetRegistryModule::AssetCreated(Asset);
|
|
Asset->MarkPackageDirty();
|
|
}
|
|
}
|
|
ImportedMeshCount = AllNewAssets.Num();
|
|
if (ImportedMeshCount == 1 && NewStaticMesh)
|
|
{
|
|
FbxImporter->ImportStaticMeshGlobalSockets(NewStaticMesh);
|
|
}
|
|
}
|
|
|
|
// @todo document
|
|
UObject* UFbxSceneImportFactory::RecursiveImportNode(void* VoidFbxImporter, void* VoidNode, EObjectFlags Flags, int32& NodeIndex, int32 Total, TSharedPtr<FFbxSceneInfo> SceneInfo, FString PackagePath)
|
|
{
|
|
UObject* FirstBaseObject = nullptr;
|
|
TSharedPtr<FFbxNodeInfo> OutNodeInfo;
|
|
UnFbx::FFbxImporter* FFbxImporter = (UnFbx::FFbxImporter*)VoidFbxImporter;
|
|
|
|
FbxNode* Node = (FbxNode*)VoidNode;
|
|
|
|
if (Node->GetNodeAttribute() && Node->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup && Node->GetChildCount() > 0)
|
|
{
|
|
// Find the deepest mesh child for the first LOD
|
|
TArray<FbxNode*> AllNodeInLod;
|
|
FFbxImporter->FindAllLODGroupNode(AllNodeInLod, Node, 0);
|
|
UObject* NewObject = nullptr;
|
|
// Combine LOD group
|
|
TArray<void*> TmpVoidArray;
|
|
if (AllNodeInLod.Num() > 0)
|
|
{
|
|
for (FbxNode* LodNode: AllNodeInLod)
|
|
{
|
|
TmpVoidArray.Add(LodNode);
|
|
}
|
|
NewObject = ImportANode(VoidFbxImporter, TmpVoidArray, Flags, NodeIndex, SceneInfo, OutNodeInfo, PackagePath, Total);
|
|
}
|
|
|
|
if (NewObject)
|
|
{
|
|
// We should always have a valid attribute if we just create a new asset
|
|
check(OutNodeInfo.IsValid() && OutNodeInfo->AttributeInfo.IsValid());
|
|
|
|
AllNewAssets.Add(OutNodeInfo->AttributeInfo, NewObject);
|
|
if (GlobalImportSettingsReference->bImportStaticMeshLODs)
|
|
{
|
|
// We use ImportedLodIndex in case there is an empty LOD (FindAllLODGroupNode do not find geometry for the LOD)
|
|
int32 ImportedLodIndex = 1;
|
|
// import LOD meshes
|
|
for (int32 LODIndex = 1; LODIndex < Node->GetChildCount(); LODIndex++)
|
|
{
|
|
if (LODIndex >= MAX_STATIC_MESH_LODS)
|
|
{
|
|
FFbxImporter->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(Node->GetChildCount() - MAX_STATIC_MESH_LODS))),
|
|
FFbxErrors::Generic_Mesh_TooManyLODs);
|
|
break;
|
|
}
|
|
AllNodeInLod.Empty();
|
|
FFbxImporter->FindAllLODGroupNode(AllNodeInLod, Node, LODIndex);
|
|
if (AllNodeInLod.Num() > 0)
|
|
{
|
|
if (AllNodeInLod[0]->GetMesh() == nullptr)
|
|
{
|
|
UStaticMesh* BaseStaticMesh = Cast<UStaticMesh>(NewObject);
|
|
FFbxImporter->AddStaticMeshSourceModelGeneratedLOD(BaseStaticMesh, LODIndex);
|
|
}
|
|
else
|
|
{
|
|
TmpVoidArray.Empty();
|
|
for (FbxNode* LodNode: AllNodeInLod)
|
|
{
|
|
TmpVoidArray.Add(LodNode);
|
|
}
|
|
ImportANode(VoidFbxImporter, TmpVoidArray, Flags, NodeIndex, SceneInfo, OutNodeInfo, PackagePath, Total, NewObject, LODIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
UStaticMesh* NewStaticMesh = Cast<UStaticMesh>(NewObject);
|
|
if (NewStaticMesh != nullptr)
|
|
{
|
|
// Build the staticmesh
|
|
FFbxImporter->FindAllLODGroupNode(AllNodeInLod, Node, 0);
|
|
FFbxImporter->PostImportStaticMesh(NewStaticMesh, AllNodeInLod);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Node->GetMesh() && Node->GetMesh()->GetPolygonVertexCount() > 0)
|
|
{
|
|
TArray<void*> TmpVoidArray;
|
|
TmpVoidArray.Add(Node);
|
|
FirstBaseObject = ImportANode(VoidFbxImporter, TmpVoidArray, Flags, NodeIndex, SceneInfo, OutNodeInfo, PackagePath, Total);
|
|
|
|
if (FirstBaseObject)
|
|
{
|
|
// We should always have a valid attribute if we just create a new asset
|
|
check(OutNodeInfo.IsValid() && OutNodeInfo->AttributeInfo.IsValid());
|
|
|
|
UStaticMesh* NewStaticMesh = Cast<UStaticMesh>(FirstBaseObject);
|
|
if (NewStaticMesh != nullptr)
|
|
{
|
|
// Build the staticmesh
|
|
TArray<FbxNode*> AllNodeInLod;
|
|
AllNodeInLod.Add(Node);
|
|
FFbxImporter->PostImportStaticMesh(NewStaticMesh, AllNodeInLod);
|
|
}
|
|
|
|
AllNewAssets.Add(OutNodeInfo->AttributeInfo, FirstBaseObject);
|
|
}
|
|
}
|
|
|
|
if (SceneImportOptions->bCreateContentFolderHierarchy)
|
|
{
|
|
FString NodeName = FString(FFbxImporter->MakeName(Node->GetName()));
|
|
if (NodeName.Compare("RootNode") != 0)
|
|
{
|
|
PackagePath += TEXT("/") + NodeName;
|
|
}
|
|
}
|
|
|
|
for (int32 ChildIndex = 0; ChildIndex < Node->GetChildCount(); ++ChildIndex)
|
|
{
|
|
UObject* SubObject = RecursiveImportNode(VoidFbxImporter, Node->GetChild(ChildIndex), Flags, NodeIndex, Total, SceneInfo, PackagePath);
|
|
if (FirstBaseObject == nullptr)
|
|
{
|
|
FirstBaseObject = SubObject;
|
|
}
|
|
}
|
|
}
|
|
|
|
return FirstBaseObject;
|
|
}
|
|
|
|
// @todo document
|
|
UObject* UFbxSceneImportFactory::ImportANode(void* VoidFbxImporter, TArray<void*>& VoidNodes, EObjectFlags Flags, int32& NodeIndex, TSharedPtr<FFbxSceneInfo> SceneInfo, TSharedPtr<FFbxNodeInfo>& OutNodeInfo, FString PackagePath, int32 Total, UObject* InMesh, int LODIndex)
|
|
{
|
|
UnFbx::FFbxImporter* FFbxImporter = (UnFbx::FFbxImporter*)VoidFbxImporter;
|
|
TArray<FbxNode*> Nodes;
|
|
for (void* VoidNode: VoidNodes)
|
|
{
|
|
Nodes.Add((FbxNode*)VoidNode);
|
|
}
|
|
check(Nodes.Num() > 0 && Nodes[0] != nullptr);
|
|
FString ParentName;
|
|
if (Nodes[0]->GetParent() != nullptr)
|
|
{
|
|
ParentName = FFbxImporter->MakeName(Nodes[0]->GetParent()->GetName());
|
|
}
|
|
else
|
|
{
|
|
ParentName.Empty();
|
|
}
|
|
|
|
FString NodeName = FFbxImporter->MakeName(Nodes[0]->GetName());
|
|
// Find the scene node info in the hierarchy
|
|
if (!FindSceneNodeInfo(SceneInfo, Nodes[0]->GetUniqueID(), OutNodeInfo) || !OutNodeInfo->AttributeInfo.IsValid())
|
|
{
|
|
// We cannot instantiate this asset if its not part of the hierarchy
|
|
return nullptr;
|
|
}
|
|
|
|
if (OutNodeInfo->AttributeInfo->GetType() != UStaticMesh::StaticClass() || !OutNodeInfo->AttributeInfo->bImportAttribute)
|
|
{
|
|
// export only static mesh or the user specify to not import this mesh
|
|
return nullptr;
|
|
}
|
|
|
|
// Check if the Mesh was already import
|
|
if (AllNewAssets.Contains(OutNodeInfo->AttributeInfo))
|
|
{
|
|
return AllNewAssets[OutNodeInfo->AttributeInfo];
|
|
}
|
|
|
|
UObject* NewObject = nullptr;
|
|
// skip collision models
|
|
if (NodeName.Contains(TEXT("UCX"), ESearchCase::IgnoreCase) || NodeName.Contains(TEXT("MCDCX"), ESearchCase::IgnoreCase) ||
|
|
NodeName.Contains(TEXT("UBX"), ESearchCase::IgnoreCase) || NodeName.Contains(TEXT("USP"), ESearchCase::IgnoreCase) || NodeName.Contains(TEXT("UCP"), ESearchCase::IgnoreCase))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// Create a package for this node
|
|
FString PackageName = PackagePath + TEXT("/") + OutNodeInfo->AttributeInfo->Name;
|
|
FString StaticMeshName;
|
|
UPackage* Pkg = CreatePackageForNode(PackageName, StaticMeshName);
|
|
if (Pkg == nullptr)
|
|
return nullptr;
|
|
|
|
// Apply the correct fbx options
|
|
TSharedPtr<FFbxMeshInfo> MeshInfo = StaticCastSharedPtr<FFbxMeshInfo>(OutNodeInfo->AttributeInfo);
|
|
|
|
ApplyMeshInfoFbxOptions(MeshInfo);
|
|
bool Old_bBakePivotInVertex = GlobalImportSettings->bBakePivotInVertex;
|
|
if (GlobalImportSettings->bBakePivotInVertex && OutNodeInfo->AttributeInfo->PivotNodeUid == INVALID_UNIQUE_ID)
|
|
{
|
|
GlobalImportSettings->bBakePivotInVertex = false;
|
|
}
|
|
FName StaticMeshFName = FName(*(OutNodeInfo->AttributeInfo->Name));
|
|
// Make sure to bake the pivot the user choose to bake
|
|
if (GlobalImportSettings->bBakePivotInVertex && Nodes.Num() == 1)
|
|
{
|
|
FbxNode* NodePivot = FindFbxNodeById(FFbxImporter, nullptr, OutNodeInfo->AttributeInfo->PivotNodeUid);
|
|
if (NodePivot != nullptr)
|
|
{
|
|
Nodes[0] = NodePivot;
|
|
}
|
|
}
|
|
|
|
NewObject = FFbxImporter->ImportStaticMeshAsSingle(Pkg, Nodes, StaticMeshFName, Flags, StaticMeshImportData, Cast<UStaticMesh>(InMesh), LODIndex);
|
|
|
|
OutNodeInfo->AttributeInfo->SetOriginalImportPath(PackageName);
|
|
|
|
if (NewObject)
|
|
{
|
|
OutNodeInfo->AttributeInfo->SetOriginalFullImportName(NewObject->GetPathName());
|
|
|
|
NodeIndex++;
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("NodeIndex"), NodeIndex);
|
|
Args.Add(TEXT("ArrayLength"), Total);
|
|
GWarn->StatusUpdate(NodeIndex, Total, FText::Format(NSLOCTEXT("UnrealEd", "Importingf", "Importing ({NodeIndex} of {ArrayLength})"), Args));
|
|
}
|
|
else
|
|
{
|
|
Pkg->SetDirtyFlag(false);
|
|
Pkg->RemoveFromRoot();
|
|
Pkg->ConditionalBeginDestroy();
|
|
}
|
|
|
|
// Destroy Fbx mesh to save memory.
|
|
for (int32 Index = 0; Index < Nodes.Num(); Index++)
|
|
{
|
|
FbxMesh* Mesh = Nodes[Index]->GetMesh();
|
|
Mesh->Destroy(true);
|
|
}
|
|
|
|
GlobalImportSettings->bBakePivotInVertex = Old_bBakePivotInVertex;
|
|
return NewObject;
|
|
}
|
|
|
|
UnFbx::FBXImportOptions* UFbxSceneImportFactory::GetOptionsFromName(FString OptionsName)
|
|
{
|
|
for (auto kvp: NameOptionsMap)
|
|
{
|
|
if (kvp.Key.Compare(OptionsName) == 0)
|
|
{
|
|
return kvp.Value;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool UFbxSceneImportFactory::FindSceneNodeInfo(TSharedPtr<FFbxSceneInfo> SceneInfo, uint64 NodeInfoUniqueId, TSharedPtr<FFbxNodeInfo>& OutNodeInfo)
|
|
{
|
|
for (auto NodeIt = SceneInfo->HierarchyInfo.CreateIterator(); NodeIt; ++NodeIt)
|
|
{
|
|
if (NodeInfoUniqueId == (*NodeIt)->UniqueId)
|
|
{
|
|
OutNodeInfo = (*NodeIt);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
UPackage* UFbxSceneImportFactory::CreatePackageForNode(FString PackageName, FString& StaticMeshName)
|
|
{
|
|
FString PackageNameOfficial = UPackageTools::SanitizePackageName(PackageName);
|
|
// We can not create assets that share the name of a map file in the same location
|
|
if (FEditorFileUtils::IsMapPackageAsset(PackageNameOfficial))
|
|
{
|
|
return nullptr;
|
|
}
|
|
bool IsPkgExist = FPackageName::DoesPackageExist(PackageNameOfficial);
|
|
if (!IsPkgExist)
|
|
{
|
|
IsPkgExist = FindObject<UPackage>(nullptr, *PackageNameOfficial) != nullptr;
|
|
}
|
|
int32 tryCount = 1;
|
|
while (IsPkgExist)
|
|
{
|
|
PackageNameOfficial = PackageName;
|
|
PackageNameOfficial += TEXT("_");
|
|
PackageNameOfficial += FString::FromInt(tryCount++);
|
|
PackageNameOfficial = UPackageTools::SanitizePackageName(PackageNameOfficial);
|
|
IsPkgExist = FPackageName::DoesPackageExist(PackageNameOfficial);
|
|
if (!IsPkgExist)
|
|
{
|
|
IsPkgExist = FindObject<UPackage>(nullptr, *PackageNameOfficial) != nullptr;
|
|
}
|
|
}
|
|
UPackage* Pkg = CreatePackage(*PackageNameOfficial);
|
|
if (!ensure(Pkg))
|
|
{
|
|
return nullptr;
|
|
}
|
|
Pkg->FullyLoad();
|
|
|
|
StaticMeshName = FPackageName::GetLongPackageAssetName(Pkg->GetOutermost()->GetName());
|
|
return Pkg;
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|