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

6038 lines
228 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
Main implementation of FFbxExporter : export FBX data from Unreal
=============================================================================*/
#include "CoreMinimal.h"
#include "EngineDefines.h"
#include "Misc/MessageDialog.h"
#include "Misc/Guid.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/EngineVersion.h"
#include "Misc/App.h"
#include "Components/ActorComponent.h"
#include "GameFramework/Actor.h"
#include "Engine/Blueprint.h"
#include "RawIndexBuffer.h"
#include "CinematicExporter.h"
#include "Components/StaticMeshComponent.h"
#include "Components/LightComponent.h"
#include "Model.h"
#include "Channels/MovieSceneDoubleChannel.h"
#include "Channels/MovieSceneFloatChannel.h"
#include "Channels/MovieSceneIntegerChannel.h"
#include "Channels/MovieSceneStringChannel.h"
#include "Channels/MovieSceneByteChannel.h"
#include "Animation/AnimTypes.h"
#include "Animation/AnimSequenceBase.h"
#include "Animation/AnimSequence.h"
#include "Components/SkeletalMeshComponent.h"
#include "Editor/EditorPerProjectUserSettings.h"
#include "Engine/Brush.h"
#include "Camera/CameraActor.h"
#include "Camera/CameraComponent.h"
#include "Particles/Emitter.h"
#include "Components/PointLightComponent.h"
#include "Components/SpotLightComponent.h"
#include "Engine/Light.h"
#include "Engine/StaticMeshActor.h"
#include "Components/ChildActorComponent.h"
#include "Components/DirectionalLightComponent.h"
#include "Components/InstancedStaticMeshComponent.h"
#include "Engine/Polys.h"
#include "Engine/SkeletalMesh.h"
#include "Engine/StaticMesh.h"
#include "Editor.h"
#include "Channels/MovieSceneChannelProxy.h"
#include "Materials/Material.h"
#include "Materials/MaterialExpressionConstant.h"
#include "Materials/MaterialExpressionVectorParameter.h"
#include "Materials/MaterialExpressionConstant2Vector.h"
#include "Materials/MaterialExpressionConstant3Vector.h"
#include "Materials/MaterialExpressionConstant4Vector.h"
#include "Materials/MaterialExpressionTextureSample.h"
#include "Materials/MaterialInstance.h"
#include "LandscapeProxy.h"
#include "LandscapeInfo.h"
#include "LandscapeComponent.h"
#include "LandscapeDataAccess.h"
#include "Components/SplineMeshComponent.h"
#include "StaticMeshComponentLODInfo.h"
#include "StaticMeshResources.h"
#include "FbxExporter.h"
#include "Exporters/FbxExportOption.h"
#include "FbxExportOptionsWindow.h"
#include "FbxAnimUtils.h"
#include "INodeAndChannelMappings.h"
#include "StaticMeshAttributes.h"
#include "StaticMeshOperations.h"
#include "Components/BrushComponent.h"
#include "CineCameraComponent.h"
#include "Math/UnitConversion.h"
#include "IMovieScenePlayer.h"
#include "MovieScene.h"
#include "Tracks/MovieScene3DTransformTrack.h"
#include "Tracks/MovieSceneColorTrack.h"
#include "Tracks/MovieSceneDoubleTrack.h"
#include "Tracks/MovieSceneFloatTrack.h"
#include "Tracks/MovieSceneSkeletalAnimationTrack.h"
#include "Tracks/MovieSceneVectorTrack.h"
#include "Sections/MovieSceneSkeletalAnimationSection.h"
#include "Sections/MovieScene3DTransformSection.h"
#include "Sections/MovieSceneColorSection.h"
#include "Sections/MovieSceneDoubleSection.h"
#include "Sections/MovieSceneFloatSection.h"
#include "Sections/MovieSceneVectorSection.h"
#include "Evaluation/MovieScenePlayback.h"
#include "Evaluation/MovieSceneEvaluationTemplateInstance.h"
#include "MovieSceneSequenceID.h"
#include "Evaluation/MovieSceneSequenceHierarchy.h"
#include "Compilation/MovieSceneCompiledDataManager.h"
#include "MovieSceneSequence.h"
#include "MovieSceneTimeHelpers.h"
#include "DynamicMeshBuilder.h"
#include "PhysicsEngine/AggregateGeom.h"
#include "PhysicsEngine/BodySetup.h"
#include "PhysicsEngine/ConvexElem.h"
#include "PhysicsEngine/SphereElem.h"
#include "Chaos/Core.h"
#include "Chaos/Particles.h"
#include "Chaos/Plane.h"
#include "ChaosCheck.h"
#include "Animation/AnimationSettings.h"
#include "Chaos/Convex.h"
#include "Widgets/SWindow.h"
#include "Framework/Application/SlateApplication.h"
#include "Interfaces/IMainFrameModule.h"
#include "UObject/MetaData.h"
#include "FbxMaterialExportUtilities.h"
#include "Exporters/Exporter.h"
#include "MaterialPropertyEx.h"
namespace UnFbx
{
TSharedPtr<FFbxExporter> FFbxExporter::StaticInstance;
FFbxExporter::FFbxExporter()
{
bBakeKeys = true;
bKeepHierarchy = true;
//We use the FGCObject pattern to keep the fbx export option alive during the editor session
ExportOptionsUI = NewObject<UFbxExportOption>();
//Load the option from the user save ini file
ExportOptionsUI->LoadOptions();
ExportOptionsOverride = nullptr;
// Create the SdkManager
SdkManager = FbxManager::Create();
// create an IOSettings object
FbxIOSettings * ios = FbxIOSettings::Create(SdkManager, IOSROOT );
SdkManager->SetIOSettings(ios);
DefaultCamera = NULL;
}
FFbxExporter::~FFbxExporter()
{
if (SdkManager)
{
SdkManager->Destroy();
SdkManager = NULL;
}
}
FFbxExporter* FFbxExporter::GetInstance()
{
if (!StaticInstance.IsValid())
{
StaticInstance = MakeShareable( new FFbxExporter() );
}
return StaticInstance.Get();
}
void FFbxExporter::DeleteInstance()
{
StaticInstance.Reset();
}
void FFbxExporter::AddReferencedObjects(FReferenceCollector& Collector)
{
if (ExportOptionsUI != nullptr)
{
Collector.AddReferencedObject(ExportOptionsUI);
}
if (ExportOptionsOverride != nullptr)
{
Collector.AddReferencedObject(ExportOptionsOverride);
}
}
void FFbxExporter::FillExportOptions(bool BatchMode, bool bShowOptionDialog, const FString& FullPath, bool& OutOperationCanceled, bool& bOutExportAll)
{
OutOperationCanceled = false;
//Export option should have been set in the constructor
check(ExportOptionsUI != nullptr);
//Load the option from the user save ini file
ExportOptionsUI->LoadOptions();
//Return if we do not show the export options or we are running automation test or we are unattended
if (!bShowOptionDialog || GIsAutomationTesting || FApp::IsUnattended())
{
return;
}
bOutExportAll = false;
TSharedPtr<SWindow> ParentWindow;
if (FModuleManager::Get().IsModuleLoaded("MainFrame"))
{
IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked<IMainFrameModule>("MainFrame");
ParentWindow = MainFrame.GetParentWindow();
}
TSharedRef<SWindow> Window = SNew(SWindow)
.Title(NSLOCTEXT("UnrealEd", "FBXExportOpionsTitle", "FBX Export Options"))
.SizingRule(ESizingRule::UserSized)
.AutoCenter(EAutoCenter::PrimaryWorkArea)
.ClientSize(FVector2D(500, 445));
TSharedPtr<SFbxExportOptionsWindow> FbxOptionWindow;
Window->SetContent
(
SAssignNew(FbxOptionWindow, SFbxExportOptionsWindow)
.ExportOptions(ExportOptionsUI)
.WidgetWindow(Window)
.FullPath(FText::FromString(FullPath))
.BatchMode(BatchMode)
);
FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false);
//Respect the edit condition
if(ExportOptionsUI->bExportSourceMesh)
{
ExportOptionsUI->Collision = false;
ExportOptionsUI->LevelOfDetail = false;
}
ExportOptionsUI->SaveOptions();
if (FbxOptionWindow->ShouldExport())
{
bOutExportAll = FbxOptionWindow->ShouldExportAll();
}
else
{
OutOperationCanceled = true;
}
}
void FFbxExporter::SetExportOptionsOverride(UFbxExportOption* OverrideOptions)
{
ExportOptionsOverride = OverrideOptions;
}
UFbxExportOption* FFbxExporter::GetExportOptions()
{
return ExportOptionsOverride ? ExportOptionsOverride : ExportOptionsUI;
}
void FFbxExporter::CreateDocument()
{
Scene = FbxScene::Create(SdkManager,"");
// create scene info
FbxDocumentInfo* SceneInfo = FbxDocumentInfo::Create(SdkManager,"SceneInfo");
SceneInfo->mTitle = "Unreal FBX Exporter";
SceneInfo->mSubject = "Export FBX meshes from Unreal";
SceneInfo->Original_ApplicationVendor.Set( "Epic Games" );
SceneInfo->Original_ApplicationName.Set( "Unreal Engine" );
SceneInfo->Original_ApplicationVersion.Set( TCHAR_TO_UTF8(*FEngineVersion::Current().ToString()) );
SceneInfo->LastSaved_ApplicationVendor.Set( "Epic Games" );
SceneInfo->LastSaved_ApplicationName.Set( "Unreal Engine" );
SceneInfo->LastSaved_ApplicationVersion.Set( TCHAR_TO_UTF8(*FEngineVersion::Current().ToString()) );
Scene->SetSceneInfo(SceneInfo);
//FbxScene->GetGlobalSettings().SetOriginalUpAxis(KFbxAxisSystem::Max);
FbxAxisSystem::EFrontVector FrontVector = (FbxAxisSystem::EFrontVector)-FbxAxisSystem::eParityOdd;
if (GetExportOptions()->bForceFrontXAxis)
FrontVector = FbxAxisSystem::eParityEven;
const FbxAxisSystem UnrealZUp(FbxAxisSystem::eZAxis, FrontVector, FbxAxisSystem::eRightHanded);
Scene->GetGlobalSettings().SetAxisSystem(UnrealZUp);
Scene->GetGlobalSettings().SetOriginalUpAxis(UnrealZUp);
// Maya use cm by default
Scene->GetGlobalSettings().SetSystemUnit(FbxSystemUnit::cm);
//FbxScene->GetGlobalSettings().SetOriginalSystemUnit( KFbxSystemUnit::m );
bSceneGlobalTimeLineSet = false;
Scene->GetGlobalSettings().SetTimeMode(FbxTime::eDefaultMode);
// setup anim stack
AnimStack = FbxAnimStack::Create(Scene, "Unreal Take");
//KFbxSet<KTime>(AnimStack->LocalStart, KTIME_ONE_SECOND);
AnimStack->Description.Set("Animation Take for Unreal.");
// this take contains one base layer. In fact having at least one layer is mandatory.
AnimLayer = FbxAnimLayer::Create(Scene, "Base Layer");
AnimStack->AddMember(AnimLayer);
}
#ifdef IOS_REF
#undef IOS_REF
#define IOS_REF (*(SdkManager->GetIOSettings()))
#endif
void FFbxExporter::WriteToFile(const TCHAR* Filename)
{
int32 Major, Minor, Revision;
bool Status = true;
int32 FileFormat = -1;
bool bEmbedMedia = false;
bool bASCII = GetExportOptions()->bASCII;
// Create an exporter.
FbxExporter* Exporter = FbxExporter::Create(SdkManager, "");
// set file format
// Write in fall back format if pEmbedMedia is true
if (bASCII)
{
FileFormat = SdkManager->GetIOPluginRegistry()->FindWriterIDByDescription("FBX ascii (*.fbx)");
}
else
{
FileFormat = SdkManager->GetIOPluginRegistry()->GetNativeWriterFormat();
}
// Set the export states. By default, the export states are always set to
// true except for the option eEXPORT_TEXTURE_AS_EMBEDDED. The code below
// shows how to change these states.
IOS_REF.SetBoolProp(EXP_FBX_MATERIAL, true);
IOS_REF.SetBoolProp(EXP_FBX_TEXTURE, true);
IOS_REF.SetBoolProp(EXP_FBX_EMBEDDED, bEmbedMedia);
IOS_REF.SetBoolProp(EXP_FBX_SHAPE, true);
IOS_REF.SetBoolProp(EXP_FBX_GOBO, true);
IOS_REF.SetBoolProp(EXP_FBX_ANIMATION, true);
IOS_REF.SetBoolProp(EXP_FBX_GLOBAL_SETTINGS, true);
IOS_REF.SetBoolProp(EXP_ASCIIFBX, bASCII);
//Get the compatibility from the editor settings
const char* CompatibilitySetting = FBX_2013_00_COMPATIBLE;
const EFbxExportCompatibility FbxExportCompatibility = GetExportOptions()->FbxExportCompatibility;
switch (FbxExportCompatibility)
{
case EFbxExportCompatibility::FBX_2011:
CompatibilitySetting = FBX_2011_00_COMPATIBLE;
break;
case EFbxExportCompatibility::FBX_2012:
CompatibilitySetting = FBX_2012_00_COMPATIBLE;
break;
case EFbxExportCompatibility::FBX_2013:
CompatibilitySetting = FBX_2013_00_COMPATIBLE;
break;
case EFbxExportCompatibility::FBX_2014:
CompatibilitySetting = FBX_2014_00_COMPATIBLE;
break;
case EFbxExportCompatibility::FBX_2016:
CompatibilitySetting = FBX_2016_00_COMPATIBLE;
break;
case EFbxExportCompatibility::FBX_2018:
CompatibilitySetting = FBX_2018_00_COMPATIBLE;
break;
case EFbxExportCompatibility::FBX_2019:
CompatibilitySetting = FBX_2019_00_COMPATIBLE;
break;
case EFbxExportCompatibility::FBX_2020:
CompatibilitySetting = FBX_2020_00_COMPATIBLE;
break;
default:
CompatibilitySetting = FBX_2013_00_COMPATIBLE;
break;
}
// We export using FBX 2013 format because many users are still on that version and FBX 2014 files has compatibility issues with
// normals when importing to an earlier version of the plugin
if (!Exporter->SetFileExportVersion(CompatibilitySetting, FbxSceneRenamer::eNone))
{
UE_LOG(LogFbx, Warning, TEXT("Call to KFbxExporter::SetFileExportVersion(FBX_2013_00_COMPATIBLE) to export 2013 fbx file format failed.\n"));
}
// Initialize the exporter by providing a filename.
if( !Exporter->Initialize(TCHAR_TO_UTF8(Filename), FileFormat, SdkManager->GetIOSettings()) )
{
UE_LOG(LogFbx, Warning, TEXT("Call to KFbxExporter::Initialize() failed.\n"));
UE_LOG(LogFbx, Warning, TEXT("Error returned: %hs\n\n"), Exporter->GetStatus().GetErrorString() );
return;
}
FbxManager::GetFileFormatVersion(Major, Minor, Revision);
UE_LOG(LogFbx, Log, TEXT("FBX version number for this version of the FBX SDK is %d.%d.%d\n\n"), Major, Minor, Revision);
// Export the scene.
Status = Exporter->Export(Scene);
// Destroy the exporter.
Exporter->Destroy();
CloseDocument();
return;
}
/**
* Release the FBX scene, releasing its memory.
*/
void FFbxExporter::CloseDocument()
{
FbxActors.Reset();
FbxSkeletonRoots.Reset();
FbxMaterials.Reset();
FbxMeshes.Reset();
FbxCollisionMeshes.Reset();
FbxNodeNameToIndexMap.Reset();
if (Scene)
{
Scene->Destroy();
Scene = NULL;
}
bSceneGlobalTimeLineSet = false;
}
template <typename T>
void FFbxExporter::CreateAnimatableUserProperty(FbxNode* Node, T Value, const char* Name, const char* Label, FbxDataType DataType)
{
// Add one user property for recording the animation
FbxProperty UserProp = FbxProperty::Create(Node, DataType, Name, Label);
UserProp.Set(Value);
UserProp.ModifyFlag(FbxPropertyFlags::eUserDefined, true);
UserProp.ModifyFlag(FbxPropertyFlags::eAnimatable, true);
}
/**
* Sorts actors such that parent actors will appear before children actors in the list
* Stable sort
*/
static void SortActorsHierarchy(TArray<AActor*>& Actors)
{
auto CalcAttachDepth = [](AActor* InActor) -> int32 {
int32 Depth = MAX_int32;
if (InActor)
{
Depth = 0;
if (InActor->GetRootComponent())
{
for (const USceneComponent* Test = InActor->GetRootComponent()->GetAttachParent(); Test != nullptr; Test = Test->GetAttachParent(), Depth++);
}
}
return Depth;
};
// Unfortunately TArray.StableSort assumes no null entries in the array
// So it forces me to use internal unrestricted version
StableSortInternal(Actors.GetData(), Actors.Num(), [&](AActor* L, AActor* R) {
return CalcAttachDepth(L) < CalcAttachDepth(R);
});
}
/**
* Exports the basic scene information to the FBX document.
*/
void FFbxExporter::ExportLevelMesh(ULevel* InLevel, bool bExportLevelGeometry, TArray<AActor*>& ActorToExport, INodeNameAdapter& NodeNameAdapter, bool bSaveAnimSeq)
{
if (InLevel == NULL)
{
return;
}
if (bExportLevelGeometry)
{
// Exports the level's scene geometry
// the vertex number of Model must be more than 2 (at least a triangle panel)
if (InLevel->Model != NULL && InLevel->Model->VertexBuffer.Vertices.Num() > 2 && InLevel->Model->MaterialIndexBuffers.Num() > 0)
{
// create a FbxNode
FbxNode* Node = FbxNode::Create(Scene, "LevelMesh");
// set the shading mode to view texture
Node->SetShadingMode(FbxNode::eTextureShading);
Node->LclScaling.Set(FbxVector4(1.0, 1.0, 1.0));
Scene->GetRootNode()->AddChild(Node);
// Export the mesh for the world
ExportModel(InLevel->Model, Node, "Level Mesh", FFbxMaterialBakingMeshData(InLevel->Model));
}
}
//Sort the hierarchy to make sure parent come first
SortActorsHierarchy(ActorToExport);
int32 ActorCount = ActorToExport.Num();
for (int32 ActorIndex = 0; ActorIndex < ActorCount; ++ActorIndex)
{
AActor* Actor = ActorToExport[ActorIndex];
//We export only valid actor
if (Actor == nullptr)
continue;
bool bIsBlueprintClass = false;
if (UClass* ActorClass = Actor->GetClass())
{
// Check if we export the actor as a blueprint
bIsBlueprintClass = UBlueprint::GetBlueprintFromClass(ActorClass) != nullptr;
}
//Blueprint can be any type of actor so it must be done first
if (bIsBlueprintClass)
{
// Export blueprint actors and all their components.
ExportActor(Actor, true, NodeNameAdapter, bSaveAnimSeq);
}
else if (Actor->IsA(ALight::StaticClass()))
{
ExportLight((ALight*)Actor, NodeNameAdapter);
}
else if (Actor->IsA(AStaticMeshActor::StaticClass()))
{
ExportStaticMesh(Actor, CastChecked<AStaticMeshActor>(Actor)->GetStaticMeshComponent(), NodeNameAdapter);
}
else if (Actor->IsA(ALandscapeProxy::StaticClass()))
{
ExportLandscape(CastChecked<ALandscapeProxy>(Actor), false, NodeNameAdapter);
}
else if (Actor->IsA(ABrush::StaticClass()))
{
// All brushes should be included within the world geometry exported above.
ExportBrush((ABrush*)Actor, NULL, 0, NodeNameAdapter);
}
else if (Actor->IsA(AEmitter::StaticClass()))
{
ExportActor(Actor, false, NodeNameAdapter, bSaveAnimSeq); // Just export the placement of the particle emitter.
}
else if (Actor->IsA(ACameraActor::StaticClass()))
{
ExportCamera(CastChecked<ACameraActor>(Actor), false, NodeNameAdapter);
}
else
{
// Export any other type of actors and all their components.
ExportActor(Actor, true, NodeNameAdapter, bSaveAnimSeq);
}
}
}
/**
* Exports the basic scene information to the FBX document.
*/
void FFbxExporter::ExportLevelMesh( ULevel* InLevel, bool bSelectedOnly, INodeNameAdapter& NodeNameAdapter, bool bSaveAnimSeq)
{
if (InLevel == NULL)
{
return;
}
TArray<AActor*> ActorToExport;
int32 ActorCount = InLevel->Actors.Num();
for (int32 ActorIndex = 0; ActorIndex < ActorCount; ++ActorIndex)
{
AActor* Actor = InLevel->Actors[ActorIndex];
if (Actor != NULL && (!bSelectedOnly || (bSelectedOnly && Actor->IsSelected())))
{
ActorToExport.Add(Actor);
}
}
ExportLevelMesh(InLevel, !bSelectedOnly, ActorToExport, NodeNameAdapter, bSaveAnimSeq);
}
void FFbxExporter::FillFbxLightAttribute(FbxLight* Light, FbxNode* FbxParentNode, ULightComponent* BaseLight)
{
Light->Intensity.Set(BaseLight->Intensity);
Light->Color.Set(Converter.ConvertToFbxColor(BaseLight->LightColor));
// Add one user property for recording the Brightness animation
CreateAnimatableUserProperty(FbxParentNode, BaseLight->Intensity, "UE_Intensity", "UE_Matinee_Light_Intensity");
CreateAnimatableUserProperty(FbxParentNode, BaseLight->bUseTemperature, "UE_UseTemperature", "UE_Matinee_Light_UseTemperature", FbxBoolDT);
CreateAnimatableUserProperty(FbxParentNode, BaseLight->GetLightUnits(), "UE_IntensityUnits", "UE_Matinee_UE_IntensityUnits", FbxEnumDT);
// Look for the higher-level light types and determine the lighting method
if (BaseLight->IsA(UPointLightComponent::StaticClass()))
{
UPointLightComponent* PointLight = (UPointLightComponent*)BaseLight;
if (BaseLight->IsA(USpotLightComponent::StaticClass()))
{
USpotLightComponent* SpotLight = (USpotLightComponent*)BaseLight;
Light->LightType.Set(FbxLight::eSpot);
// Export the spot light parameters.
if (!FMath::IsNearlyZero(SpotLight->InnerConeAngle*2.0f))
{
Light->InnerAngle.Set(SpotLight->InnerConeAngle*2.0f);
}
else // Maya requires a non-zero inner cone angle
{
Light->InnerAngle.Set(0.01f);
}
Light->OuterAngle.Set(SpotLight->OuterConeAngle*2.0f);
}
else
{
Light->LightType.Set(FbxLight::ePoint);
}
// Export the point light parameters.
Light->EnableFarAttenuation.Set(true);
Light->FarAttenuationEnd.Set(PointLight->AttenuationRadius);
// Add one user property for recording the FalloffExponent animation
CreateAnimatableUserProperty(FbxParentNode, PointLight->AttenuationRadius, "UE_Radius", "UE_Matinee_Light_Radius");
// Add one user property for recording the FalloffExponent animation
CreateAnimatableUserProperty(FbxParentNode, PointLight->LightFalloffExponent, "UE_FalloffExponent", "UE_Matinee_Light_FalloffExponent");
}
else if (BaseLight->IsA(UDirectionalLightComponent::StaticClass()))
{
// The directional light has no interesting properties.
Light->LightType.Set(FbxLight::eDirectional);
Light->Intensity.Set(BaseLight->Intensity*100.0f);
}
}
/**
* Exports the light-specific information for a light actor.
*/
void FFbxExporter::ExportLight( ALight* Actor, INodeNameAdapter& NodeNameAdapter )
{
if (Scene == NULL || Actor == NULL || !Actor->GetLightComponent()) return;
// Export the basic actor information.
FbxNode* FbxActor = ExportActor( Actor, false, NodeNameAdapter ); // this is the pivot node
// The real fbx light node
FbxNode* FbxLightNode = FbxActor->GetParent();
ULightComponent* BaseLight = Actor->GetLightComponent();
FString FbxNodeName = NodeNameAdapter.GetActorNodeName(Actor);
// Export the basic light information
FbxLight* Light = FbxLight::Create(Scene, TCHAR_TO_UTF8(*FbxNodeName));
FillFbxLightAttribute(Light, FbxLightNode, BaseLight);
FbxActor->SetNodeAttribute(Light);
}
void FFbxExporter::FillFbxCameraAttribute(FbxNode* ParentNode, FbxCamera* Camera, UCameraComponent *CameraComponent)
{
float ApertureHeightInInches = 0.612f; // 0.612f is a magic number from Maya that represents the ApertureHeight
float ApertureWidthInInches = CameraComponent->AspectRatio * ApertureHeightInInches;
float FocalLength = Camera->ComputeFocalLength(CameraComponent->FieldOfView);
TOptional<float> FocusDistance;
if (CameraComponent->IsA(UCineCameraComponent::StaticClass()))
{
UCineCameraComponent* CineCameraComponent = Cast<UCineCameraComponent>(CameraComponent);
if (CineCameraComponent)
{
ApertureWidthInInches = FUnitConversion::Convert(CineCameraComponent->Filmback.SensorWidth, EUnit::Millimeters, EUnit::Inches);
ApertureHeightInInches = FUnitConversion::Convert(CineCameraComponent->Filmback.SensorHeight, EUnit::Millimeters, EUnit::Inches);
FocalLength = CineCameraComponent->CurrentFocalLength;
FocusDistance = CineCameraComponent->FocusSettings.ManualFocusDistance;
}
}
// Export the view area information
Camera->ProjectionType.Set(CameraComponent->ProjectionMode == ECameraProjectionMode::Type::Perspective ? FbxCamera::ePerspective : FbxCamera::eOrthogonal);
Camera->SetAspect(FbxCamera::eFixedRatio, CameraComponent->AspectRatio, 1.0f);
Camera->FilmAspectRatio.Set(CameraComponent->AspectRatio);
Camera->SetApertureWidth(ApertureWidthInInches);
Camera->SetApertureHeight(ApertureHeightInInches);
Camera->SetApertureMode(FbxCamera::eFocalLength);
Camera->FocalLength.Set(FocalLength);
if (FocusDistance.IsSet())
{
Camera->FocusDistance.Set(FocusDistance.GetValue());
}
// Add one user property for recording the AspectRatio animation
CreateAnimatableUserProperty(ParentNode, CameraComponent->AspectRatio, "UE_AspectRatio", "UE_Matinee_Camera_AspectRatio");
// Push the near/far clip planes away, as the engine uses larger values than the default.
Camera->SetNearPlane(10.0f);
Camera->SetFarPlane(100000.0f);
}
void FFbxExporter::ExportCamera( ACameraActor* Actor, bool bExportComponents,INodeNameAdapter& NodeNameAdapter )
{
if (Scene == NULL || Actor == NULL) return;
UCameraComponent *CameraComponent = Actor->GetCameraComponent();
// Export the basic actor information.
FbxNode* FbxActor = ExportActor( Actor, bExportComponents, NodeNameAdapter ); // this is the pivot node
// The real fbx camera node
FbxNode* FbxCameraNode = FbxActor->GetParent();
FString FbxNodeName = NodeNameAdapter.GetActorNodeName(Actor);
// Create a properly-named FBX camera structure and instantiate it in the FBX scene graph
FbxCamera* Camera = FbxCamera::Create(Scene, TCHAR_TO_UTF8(*FbxNodeName));
FillFbxCameraAttribute(FbxCameraNode, Camera, CameraComponent);
FbxActor->SetNodeAttribute(Camera);
DefaultCamera = Camera;
}
/**
* Exports the mesh and the actor information for a brush actor.
*/
void FFbxExporter::ExportBrush(ABrush* Actor, UModel* InModel, bool bConvertToStaticMesh, INodeNameAdapter& NodeNameAdapter )
{
if (Scene == NULL || Actor == NULL || !Actor->GetBrushComponent()) return;
if (!bConvertToStaticMesh)
{
// Retrieve the information structures, verifying the integrity of the data.
UModel* Model = Actor->GetBrushComponent()->Brush;
if (Model == NULL || Model->VertexBuffer.Vertices.Num() < 3 || Model->MaterialIndexBuffers.Num() == 0) return;
// Create the FBX actor, the FBX geometry and instantiate it.
FbxNode* FbxActor = ExportActor( Actor, false, NodeNameAdapter );
Scene->GetRootNode()->AddChild(FbxActor);
// Export the mesh information
ExportModel(Model, FbxActor, TCHAR_TO_UTF8(*Actor->GetName()), FFbxMaterialBakingMeshData(InModel, Actor));
}
else
{
FMeshDescription Mesh;
FStaticMeshAttributes MeshAttributes(Mesh);
MeshAttributes.Register();
TArray<FStaticMaterial> Materials;
GetBrushMesh(Actor,Actor->Brush,Mesh,Materials);
if( Mesh.Vertices().Num() )
{
UStaticMesh* StaticMesh = CreateStaticMesh(Mesh,Materials,GetTransientPackage(),Actor->GetFName());
ExportStaticMesh( StaticMesh, &Materials );
}
}
}
void FFbxExporter::ExportModel(UModel* Model, FbxNode* Node, const char* Name, const FFbxMaterialBakingMeshData& MaterialBakingMeshData)
{
//int32 VertexCount = Model->VertexBuffer.Vertices.Num();
int32 MaterialCount = Model->MaterialIndexBuffers.Num();
const float BiasedHalfWorldExtent = HALF_WORLD_MAX * 0.95f;
// Create the mesh and three data sources for the vertex positions, normals and texture coordinates.
FbxMesh* Mesh = FbxMesh::Create(Scene, Name);
// Create control points.
uint32 VertCount(Model->VertexBuffer.Vertices.Num());
Mesh->InitControlPoints(VertCount);
FbxVector4* ControlPoints = Mesh->GetControlPoints();
// Set the normals on Layer 0.
FbxLayer* Layer = Mesh->GetLayer(0);
if (Layer == NULL)
{
Mesh->CreateLayer();
Layer = Mesh->GetLayer(0);
}
// We want to have one normal for each vertex (or control point),
// so we set the mapping mode to eBY_CONTROL_POINT.
FbxLayerElementNormal* LayerElementNormal= FbxLayerElementNormal::Create(Mesh, "");
LayerElementNormal->SetMappingMode(FbxLayerElement::eByControlPoint);
// Set the normal values for every control point.
LayerElementNormal->SetReferenceMode(FbxLayerElement::eDirect);
// Create UV for Diffuse channel.
FbxLayerElementUV* UVDiffuseLayer = FbxLayerElementUV::Create(Mesh, "DiffuseUV");
UVDiffuseLayer->SetMappingMode(FbxLayerElement::eByControlPoint);
UVDiffuseLayer->SetReferenceMode(FbxLayerElement::eDirect);
Layer->SetUVs(UVDiffuseLayer, FbxLayerElement::eTextureDiffuse);
for (uint32 VertexIdx = 0; VertexIdx < VertCount; ++VertexIdx)
{
FModelVertex& Vertex = Model->VertexBuffer.Vertices[VertexIdx];
FVector Normal = (FVector4)Vertex.TangentZ;
// If the vertex is outside of the world extent, snap it to the origin. The faces associated with
// these vertices will be removed before exporting. We leave the snapped vertex in the buffer so
// we won't have to deal with reindexing everything.
FVector FinalVertexPos = (FVector)Vertex.Position;
if( FMath::Abs( Vertex.Position.X ) > BiasedHalfWorldExtent ||
FMath::Abs( Vertex.Position.Y ) > BiasedHalfWorldExtent ||
FMath::Abs( Vertex.Position.Z ) > BiasedHalfWorldExtent )
{
FinalVertexPos = FVector::ZeroVector;
}
ControlPoints[VertexIdx] = FbxVector4(FinalVertexPos.X, -FinalVertexPos.Y, FinalVertexPos.Z);
FbxVector4 FbxNormal = FbxVector4(Normal.X, -Normal.Y, Normal.Z);
FbxAMatrix NodeMatrix;
FbxVector4 Trans = Node->LclTranslation.Get();
NodeMatrix.SetT(FbxVector4(Trans[0], Trans[1], Trans[2]));
FbxVector4 Rot = Node->LclRotation.Get();
NodeMatrix.SetR(FbxVector4(Rot[0], Rot[1], Rot[2]));
NodeMatrix.SetS(Node->LclScaling.Get());
FbxNormal = NodeMatrix.MultT(FbxNormal);
FbxNormal.Normalize();
LayerElementNormal->GetDirectArray().Add(FbxNormal);
// update the index array of the UVs that map the texture to the face
UVDiffuseLayer->GetDirectArray().Add(FbxVector2(Vertex.TexCoord.X, -Vertex.TexCoord.Y));
}
Layer->SetNormals(LayerElementNormal);
Layer->SetUVs(UVDiffuseLayer);
FbxLayerElementMaterial* MatLayer = FbxLayerElementMaterial::Create(Mesh, "");
MatLayer->SetMappingMode(FbxLayerElement::eByPolygon);
MatLayer->SetReferenceMode(FbxLayerElement::eIndexToDirect);
Layer->SetMaterials(MatLayer);
//Make sure the Index buffer is accessible.
for (auto MaterialIterator = Model->MaterialIndexBuffers.CreateIterator(); MaterialIterator; ++MaterialIterator)
{
BeginReleaseResource(MaterialIterator->Value.Get());
}
FlushRenderingCommands();
// Create the materials and the per-material tesselation structures.
for (auto MaterialIterator = Model->MaterialIndexBuffers.CreateIterator(); MaterialIterator; ++MaterialIterator)
{
UMaterialInterface* MaterialInterface = MaterialIterator.Key();
FRawIndexBuffer16or32& IndexBuffer = *MaterialIterator.Value();
int32 IndexCount = IndexBuffer.Indices.Num();
if (IndexCount < 3) continue;
// Are NULL materials okay?
int32 MaterialIndex = -1;
FbxSurfaceMaterial* FbxMaterial;
if (MaterialInterface != NULL && MaterialInterface->GetMaterial() != NULL)
{
FbxMaterial = ExportMaterial(MaterialInterface, MaterialBakingMeshData.GetUModelStaticMeshMaterialIndex(MaterialInterface), MaterialBakingMeshData);
}
else
{
// Set default material
FbxMaterial = CreateDefaultMaterial();
}
MaterialIndex = Node->AddMaterial(FbxMaterial);
// Create the Fbx polygons set.
// Retrieve and fill in the index buffer.
const int32 TriangleCount = IndexCount / 3;
for( int32 TriangleIdx = 0; TriangleIdx < TriangleCount; ++TriangleIdx )
{
bool bSkipTriangle = false;
for( int32 IndexIdx = 0; IndexIdx < 3; ++IndexIdx )
{
// Skip triangles that belong to BSP geometry close to the world extent, since its probably
// the automatically-added-brush for new levels. The vertices will be left in the buffer (unreferenced)
FVector VertexPos = (FVector)Model->VertexBuffer.Vertices[ IndexBuffer.Indices[ TriangleIdx * 3 + IndexIdx ] ].Position;
if( FMath::Abs( VertexPos.X ) > BiasedHalfWorldExtent ||
FMath::Abs( VertexPos.Y ) > BiasedHalfWorldExtent ||
FMath::Abs( VertexPos.Z ) > BiasedHalfWorldExtent )
{
bSkipTriangle = true;
break;
}
}
if( !bSkipTriangle )
{
// all faces of the cube have the same texture
Mesh->BeginPolygon(MaterialIndex);
for( int32 IndexIdx = 0; IndexIdx < 3; ++IndexIdx )
{
// Control point index
Mesh->AddPolygon(IndexBuffer.Indices[ TriangleIdx * 3 + IndexIdx ]);
}
Mesh->EndPolygon ();
}
}
BeginInitResource(&IndexBuffer);
}
FlushRenderingCommands();
Node->SetNodeAttribute(Mesh);
}
FString FFbxExporter::GetFbxObjectName(const FString &FbxObjectNode, INodeNameAdapter& NodeNameAdapter)
{
FString FbxTestName = FbxObjectNode;
int32 *NodeIndex = FbxNodeNameToIndexMap.Find(FbxTestName);
if (NodeIndex)
{
FbxTestName = FString::Printf(TEXT("%s%d"), *FbxTestName, *NodeIndex);
++(*NodeIndex);
}
else
{
FbxNodeNameToIndexMap.Add(FbxTestName, 1);
}
return FbxTestName;
}
void FFbxExporter::ExportStaticMesh(AActor* Actor, UStaticMeshComponent* StaticMeshComponent, INodeNameAdapter& NodeNameAdapter)
{
if (Scene == NULL || Actor == NULL || StaticMeshComponent == NULL)
{
return;
}
// Retrieve the static mesh rendering information at the correct LOD level.
UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
if (StaticMesh == NULL || !StaticMesh->HasValidRenderData())
{
return;
}
FString FbxNodeName = NodeNameAdapter.GetActorNodeName(Actor);
FString FbxMeshName = StaticMesh->GetName().Replace(TEXT("-"), TEXT("_"));
FColorVertexBuffer* ColorBuffer = NULL;
if(GetExportOptions()->LevelOfDetail && StaticMesh->GetNumLODs() > 1)
{
//Create a fbx LOD Group node
FbxNode* FbxActor = ExportActor(Actor, false, NodeNameAdapter);
FString FbxLODGroupName = NodeNameAdapter.GetActorNodeName(Actor);
FbxLODGroupName += TEXT("_LodGroup");
FbxLODGroupName = GetFbxObjectName(FbxLODGroupName, NodeNameAdapter);
FbxLODGroup *FbxLodGroupAttribute = FbxLODGroup::Create(Scene, TCHAR_TO_UTF8(*FbxLODGroupName));
FbxActor->AddNodeAttribute(FbxLodGroupAttribute);
FbxLodGroupAttribute->ThresholdsUsedAsPercentage = true;
//Export an Fbx Mesh Node for every LOD and child them to the fbx node (LOD Group)
for (int CurrentLodIndex = 0; CurrentLodIndex < StaticMesh->GetNumLODs(); ++CurrentLodIndex)
{
if (CurrentLodIndex < StaticMeshComponent->LODData.Num())
{
ColorBuffer = StaticMeshComponent->LODData[CurrentLodIndex].OverrideVertexColors;
}
else
{
ColorBuffer = nullptr;
}
FString FbxLODNodeName = NodeNameAdapter.GetActorNodeName(Actor);
FbxLODNodeName += TEXT("_LOD") + FString::FromInt(CurrentLodIndex);
FbxLODNodeName = GetFbxObjectName(FbxLODNodeName, NodeNameAdapter);
FbxNode* FbxActorLOD = FbxNode::Create(Scene, TCHAR_TO_UTF8(*FbxLODNodeName));
FbxActor->AddChild(FbxActorLOD);
if (CurrentLodIndex + 1 < StaticMesh->GetNumLODs())
{
//Convert the screen size to a threshold, it is just to be sure that we set some threshold, there is no way to convert this precisely
double LodScreenSize = (double)(10.0f / StaticMesh->GetRenderData()->ScreenSize[CurrentLodIndex].Default);
FbxLodGroupAttribute->AddThreshold(LodScreenSize);
}
const int32 LightmapUVChannel = -1;
const TArray<FStaticMaterial>* MaterialOrderOverride = nullptr;
ExportStaticMeshToFbx(StaticMesh, CurrentLodIndex, *FbxMeshName, FbxActorLOD, FFbxMaterialBakingMeshData(StaticMesh, StaticMeshComponent, CurrentLodIndex), LightmapUVChannel, ColorBuffer, MaterialOrderOverride, &ToRawPtrTArrayUnsafe(StaticMeshComponent->OverrideMaterials));
}
}
else
{
const int32 LODIndex = (StaticMeshComponent->ForcedLodModel > 0 ? FMath::Min(StaticMesh->GetNumLODs(), StaticMeshComponent->ForcedLodModel) - 1 : /* auto-select*/ 0);
if (StaticMeshComponent->LODData.IsValidIndex(LODIndex))
{
ColorBuffer = StaticMeshComponent->LODData[LODIndex].OverrideVertexColors;
}
FbxNode* FbxActor = ExportActor(Actor, false, NodeNameAdapter);
const int32 LightmapUVChannel = -1;
const TArray<FStaticMaterial>* MaterialOrderOverride = nullptr;
ExportStaticMeshToFbx(StaticMesh, LODIndex, *FbxMeshName, FbxActor, FFbxMaterialBakingMeshData(StaticMesh, StaticMeshComponent, LODIndex), LightmapUVChannel, ColorBuffer, MaterialOrderOverride, &ToRawPtrTArrayUnsafe(StaticMeshComponent->OverrideMaterials));
}
}
struct FBSPExportData
{
FMeshDescription Mesh;
TArray<FStaticMaterial> Materials;
TArray<uint32> SmoothGroups;
uint32 NumVerts;
uint32 NumFaces;
uint32 CurrentVertAddIndex;
uint32 CurrentFaceAddIndex;
bool bInitialised;
FBSPExportData()
:NumVerts(0)
,NumFaces(0)
,CurrentVertAddIndex(0)
,CurrentFaceAddIndex(0)
,bInitialised(false)
{
}
};
void FFbxExporter::ExportBSP( UModel* Model, bool bSelectedOnly )
{
TMap< ABrush*, FBSPExportData > BrushToMeshMap;
TArray<FStaticMaterial> AllMaterials;
for(int32 NodeIndex = 0;NodeIndex < Model->Nodes.Num();NodeIndex++)
{
FBspNode& Node = Model->Nodes[NodeIndex];
if( Node.NumVertices >= 3 )
{
FBspSurf& Surf = Model->Surfs[Node.iSurf];
ABrush* BrushActor = Surf.Actor;
if( (Surf.PolyFlags & PF_Selected) || !bSelectedOnly || (BrushActor && BrushActor->IsSelected() ) )
{
FBSPExportData& Data = BrushToMeshMap.FindOrAdd( BrushActor );
FPoly Poly;
GEditor->polyFindBrush(Model, Node.iSurf, Poly);
Data.NumVerts += Node.NumVertices;
Data.NumFaces += Node.NumVertices-2;
UMaterialInterface* Material = Poly.Material;
FName MaterialName = Material != nullptr ? Material->GetFName() : NAME_None;
Data.Materials.AddUnique(FStaticMaterial(Material, MaterialName, MaterialName));
AllMaterials.AddUnique(FStaticMaterial(Material, MaterialName, MaterialName));
}
}
}
for(int32 NodeIndex = 0;NodeIndex < Model->Nodes.Num();NodeIndex++)
{
FBspNode& Node = Model->Nodes[NodeIndex];
FBspSurf& Surf = Model->Surfs[Node.iSurf];
ABrush* BrushActor = Surf.Actor;
if( (Surf.PolyFlags & PF_Selected) || !bSelectedOnly || (BrushActor && BrushActor->IsSelected() && Node.NumVertices >= 3) )
{
FPoly Poly;
GEditor->polyFindBrush( Model, Node.iSurf, Poly );
FBSPExportData* ExportData = BrushToMeshMap.Find( BrushActor );
if( NULL == ExportData )
{
UE_LOG(LogFbx, Fatal, TEXT("Error in FBX export of BSP."));
return;
}
TArray<FStaticMaterial>& Materials = ExportData->Materials;
FMeshDescription& Mesh = ExportData->Mesh;
//Pre-allocate space for this mesh.
if( !ExportData->bInitialised )
{
uint32 NumWedges = ExportData->NumFaces * 3;
FStaticMeshAttributes InitMeshAttributes(Mesh);
InitMeshAttributes.Register();
ExportData->bInitialised = true;
Mesh.Empty();
Mesh.ReserveNewVertices(ExportData->NumVerts);
Mesh.ReserveNewVertexInstances(NumWedges);
Mesh.ReserveNewPolygons(ExportData->NumFaces);
Mesh.ReserveNewEdges(ExportData->NumFaces * 2);
Mesh.ReserveNewPolygonGroups(Materials.Num());
//We need to get the PolygonGroupImportedMaterial
TPolygonGroupAttributesRef<FName> InitPolygonGroupNames = InitMeshAttributes.GetPolygonGroupMaterialSlotNames();
for (const FStaticMaterial& StaticMaterial : Materials)
{
const FPolygonGroupID PolygonGroupID = Mesh.CreatePolygonGroup();
InitPolygonGroupNames[PolygonGroupID] = StaticMaterial.ImportedMaterialSlotName;
}
ExportData->SmoothGroups.Empty(ExportData->NumFaces);
}
//Get the Attributes
FStaticMeshAttributes MeshAttributes(Mesh);
TVertexAttributesRef<FVector3f> VertexPositions = MeshAttributes.GetVertexPositions();
TVertexInstanceAttributesRef<FVector2f> UVs = MeshAttributes.GetVertexInstanceUVs();
TVertexInstanceAttributesRef<FVector4f> Colors = MeshAttributes.GetVertexInstanceColors();
TVertexInstanceAttributesRef<FVector3f> Normals = MeshAttributes.GetVertexInstanceNormals();
TEdgeAttributesRef<bool> EdgeHardnesses = MeshAttributes.GetEdgeHardnesses();
UMaterialInterface* Material = Poly.Material;
FName MaterialName = Material != nullptr ? Material->GetFName() : NAME_None;
int32 MaterialIndex = INDEX_NONE;
if (!ExportData->Materials.Find(FStaticMaterial(Material, MaterialName, MaterialName), MaterialIndex))
{
MaterialIndex = 0;
}
const FPolygonGroupID CurrentPolygonGroupID(MaterialIndex);
//The material ID should follow the unique materials
check(Mesh.IsPolygonGroupValid(CurrentPolygonGroupID));
const FVector& TextureBase = (FVector)Model->Points[Surf.pBase];
const FVector& TextureX = (FVector)Model->Vectors[Surf.vTextureU];
const FVector& TextureY = (FVector)Model->Vectors[Surf.vTextureV];
const FVector& Normal = (FVector)Model->Vectors[Surf.vNormal];
const int32 StartIndex = ExportData->CurrentVertAddIndex;
for(int32 VertexIndex = 0; VertexIndex < Node.NumVertices ; VertexIndex++ )
{
const FVert& Vert = Model->Verts[Node.iVertPool + VertexIndex];
const FVector& Vertex = (FVector)Model->Points[Vert.pVertex];
FVertexID VertexID = Mesh.CreateVertex();
VertexPositions[VertexID] = (FVector3f)Vertex;
}
ExportData->CurrentVertAddIndex += Node.NumVertices;
for (int32 StartVertexIndex = 1; StartVertexIndex < Node.NumVertices - 1; ++StartVertexIndex)
{
TArray<FVertexInstanceID> VertexInstanceIDs;
VertexInstanceIDs.SetNum(3);
FVertexID VertexIDs[3];
// These map the node's vertices to the 3 triangle indices to triangulate the convex polygon.
int32 TriVertIndices[3] = {
Node.iVertPool + StartVertexIndex + 1,
Node.iVertPool + StartVertexIndex,
Node.iVertPool };
int32 WedgeIndices[3] = {
StartIndex + StartVertexIndex + 1,
StartIndex + StartVertexIndex,
StartIndex };
for (uint32 Corner = 0; Corner < 3; ++Corner)
{
VertexIDs[Corner] = FVertexID(WedgeIndices[Corner]);
VertexInstanceIDs[Corner] = Mesh.CreateVertexInstance(VertexIDs[Corner]);
const FVert& Vert = Model->Verts[TriVertIndices[Corner]];
const FVector& Vertex = (FVector)Model->Points[Vert.pVertex];
float U = ((Vertex - TextureBase) | TextureX) / UModel::GetGlobalBSPTexelScale();
float V = ((Vertex - TextureBase) | TextureY) / UModel::GetGlobalBSPTexelScale();
UVs.Set(VertexInstanceIDs[Corner], 0, FVector2f(U, V));
//This is not exported when exporting the whole level via ExportModel so leaving out here for now.
//UVs.Set(VertexInstanceIDs[Corner], 1, Vert.ShadowTexCoord);
Colors[VertexInstanceIDs[Corner]] = FVector4f(FLinearColor::White);
Normals[VertexInstanceIDs[Corner]] = (FVector3f)Normal;
}
// Insert a polygon into the mesh
TArray<FEdgeID> NewEdgeIDs;
const FPolygonID NewPolygonID = Mesh.CreatePolygon(CurrentPolygonGroupID, VertexInstanceIDs, &NewEdgeIDs);
for (const FEdgeID& EdgeID : NewEdgeIDs)
{
EdgeHardnesses[EdgeID] = false;
}
//Add to the smoothGroup array so we can compute hard edge later
ExportData->SmoothGroups.Add((1 << (Node.iSurf % 32)));
++ExportData->CurrentFaceAddIndex;
}
}
}
for( TMap< ABrush*, FBSPExportData >::TIterator It(BrushToMeshMap); It; ++It )
{
if( It.Value().Mesh.Vertices().Num() )
{
FStaticMeshOperations::ConvertSmoothGroupToHardEdges(It.Value().SmoothGroups, It.Value().Mesh);
UStaticMesh* NewMesh = CreateStaticMesh( It.Value().Mesh, It.Value().Materials, GetTransientPackage(), It.Key()->GetFName() );
ExportStaticMesh( NewMesh, &It.Value().Materials);
}
}
}
void FFbxExporter::ExportStaticMesh( UStaticMesh* StaticMesh, const TArray<FStaticMaterial>* MaterialOrder )
{
if (Scene == NULL || StaticMesh == NULL || !StaticMesh->HasValidRenderData()) return;
FString MeshName;
StaticMesh->GetName(MeshName);
FbxNode* MeshNode = FbxNode::Create(Scene, TCHAR_TO_UTF8(*MeshName));
Scene->GetRootNode()->AddChild(MeshNode);
if (GetExportOptions()->LevelOfDetail && StaticMesh->GetNumLODs() > 1)
{
FString LodGroup_MeshName = MeshName + ("_LodGroup");
FbxLODGroup *FbxLodGroupAttribute = FbxLODGroup::Create(Scene, TCHAR_TO_UTF8(*LodGroup_MeshName));
MeshNode->AddNodeAttribute(FbxLodGroupAttribute);
FbxLodGroupAttribute->ThresholdsUsedAsPercentage = true;
//Export an Fbx Mesh Node for every LOD and child them to the fbx node (LOD Group)
for (int CurrentLodIndex = 0; CurrentLodIndex < StaticMesh->GetNumLODs(); ++CurrentLodIndex)
{
FString FbxLODNodeName = MeshName + TEXT("_LOD") + FString::FromInt(CurrentLodIndex);
FbxNode* FbxActorLOD = FbxNode::Create(Scene, TCHAR_TO_UTF8(*FbxLODNodeName));
MeshNode->AddChild(FbxActorLOD);
if (CurrentLodIndex + 1 < StaticMesh->GetNumLODs())
{
//Convert the screen size to a threshold, it is just to be sure that we set some threshold, there is no way to convert this precisely
double LodScreenSize = (double)(10.0f / StaticMesh->GetRenderData()->ScreenSize[CurrentLodIndex].Default);
FbxLodGroupAttribute->AddThreshold(LodScreenSize);
}
ExportStaticMeshToFbx(StaticMesh, CurrentLodIndex, *MeshName, FbxActorLOD, FFbxMaterialBakingMeshData(StaticMesh, nullptr, CurrentLodIndex), -1, nullptr, MaterialOrder);
}
}
else
{
ExportStaticMeshToFbx(StaticMesh, 0, *MeshName, MeshNode, FFbxMaterialBakingMeshData(StaticMesh), -1, NULL, MaterialOrder);
}
}
void FFbxExporter::ExportStaticMeshLightMap( UStaticMesh* StaticMesh, int32 LODIndex, int32 UVChannel )
{
if (Scene == NULL || StaticMesh == NULL || !StaticMesh->HasValidRenderData()) return;
FString MeshName;
StaticMesh->GetName(MeshName);
FbxNode* MeshNode = FbxNode::Create(Scene, TCHAR_TO_UTF8(*MeshName));
Scene->GetRootNode()->AddChild(MeshNode);
ExportStaticMeshToFbx(StaticMesh, LODIndex, *MeshName, MeshNode, FFbxMaterialBakingMeshData(StaticMesh, nullptr, LODIndex), UVChannel);
}
void FFbxExporter::ExportSkeletalMesh( USkeletalMesh* SkeletalMesh )
{
if (Scene == NULL || SkeletalMesh == NULL) return;
FString MeshName;
SkeletalMesh->GetName(MeshName);
FbxNode* MeshNode = FbxNode::Create(Scene, TCHAR_TO_UTF8(*MeshName));
Scene->GetRootNode()->AddChild(MeshNode);
ExportSkeletalMeshToFbx(SkeletalMesh, NULL, *MeshName, MeshNode);
}
void FFbxExporter::ExportSkeletalMesh( AActor* Actor, USkeletalMeshComponent* SkeletalMeshComponent, INodeNameAdapter& NodeNameAdapter )
{
if (Scene == NULL || Actor == NULL || SkeletalMeshComponent == NULL) return;
// Retrieve the skeletal mesh rendering information.
USkeletalMesh* SkeletalMesh = SkeletalMeshComponent->GetSkeletalMeshAsset();
FString FbxNodeName = NodeNameAdapter.GetActorNodeName(Actor);
ExportActor( Actor, true, NodeNameAdapter );
}
FbxSurfaceMaterial* FFbxExporter::CreateDefaultMaterial()
{
// TODO(sbc): the below cast is needed to avoid clang warning. The upstream
// signature in FBX should really use 'const char *'.
FbxSurfaceMaterial* FbxMaterial = Scene->GetMaterial(const_cast<char*>("Fbx Default Material"));
if (!FbxMaterial)
{
FbxMaterial = FbxSurfaceLambert::Create(Scene, "Fbx Default Material");
((FbxSurfaceLambert*)FbxMaterial)->Diffuse.Set(FbxDouble3(0.72, 0.72, 0.72));
}
return FbxMaterial;
}
void FFbxExporter::ExportLandscape(ALandscapeProxy* Actor, bool bSelectedOnly, INodeNameAdapter& NodeNameAdapter)
{
if (Scene == NULL || Actor == NULL)
{
return;
}
FString FbxNodeName = NodeNameAdapter.GetActorNodeName(Actor);
FbxNode* FbxActor = ExportActor(Actor, true, NodeNameAdapter);
ExportLandscapeToFbx(Actor, *FbxNodeName, FbxActor, bSelectedOnly);
}
FbxDouble3 SetMaterialComponent(FColorMaterialInput& MatInput, bool ToLinear)
{
FColor RGBColor;
FLinearColor LinearColor = FLinearColor::Black;
bool LinearSet = false;
if (MatInput.Expression)
{
if (Cast<UMaterialExpressionConstant>(MatInput.Expression))
{
UMaterialExpressionConstant* Expr = Cast<UMaterialExpressionConstant>(MatInput.Expression);
RGBColor = FColor(Expr->R);
}
else if (Cast<UMaterialExpressionVectorParameter>(MatInput.Expression))
{
UMaterialExpressionVectorParameter* Expr = Cast<UMaterialExpressionVectorParameter>(MatInput.Expression);
LinearColor = Expr->DefaultValue;
LinearSet = true;
//Linear to sRGB color space conversion
RGBColor = Expr->DefaultValue.ToFColor(true);
}
else if (Cast<UMaterialExpressionConstant3Vector>(MatInput.Expression))
{
UMaterialExpressionConstant3Vector* Expr = Cast<UMaterialExpressionConstant3Vector>(MatInput.Expression);
RGBColor.R = Expr->Constant.R;
RGBColor.G = Expr->Constant.G;
RGBColor.B = Expr->Constant.B;
}
else if (Cast<UMaterialExpressionConstant4Vector>(MatInput.Expression))
{
UMaterialExpressionConstant4Vector* Expr = Cast<UMaterialExpressionConstant4Vector>(MatInput.Expression);
RGBColor.R = Expr->Constant.R;
RGBColor.G = Expr->Constant.G;
RGBColor.B = Expr->Constant.B;
//RGBColor.A = Expr->A;
}
else if (Cast<UMaterialExpressionConstant2Vector>(MatInput.Expression))
{
UMaterialExpressionConstant2Vector* Expr = Cast<UMaterialExpressionConstant2Vector>(MatInput.Expression);
RGBColor.R = Expr->R;
RGBColor.G = Expr->G;
RGBColor.B = 0;
}
else
{
RGBColor.R = MatInput.Constant.R;
RGBColor.G = MatInput.Constant.G;
RGBColor.B = MatInput.Constant.B;
}
}
else
{
RGBColor.R = MatInput.Constant.R;
RGBColor.G = MatInput.Constant.G;
RGBColor.B = MatInput.Constant.B;
}
if (ToLinear)
{
if (!LinearSet)
{
//sRGB to linear color space conversion
LinearColor = FLinearColor(RGBColor);
}
return FbxDouble3(LinearColor.R, LinearColor.G, LinearColor.B);
}
return FbxDouble3(RGBColor.R, RGBColor.G, RGBColor.B);
}
bool FFbxExporter::FillFbxTextureProperty(const char *PropertyName, const FExpressionInput& MaterialInput, FbxSurfaceMaterial* FbxMaterial)
{
if (Scene == NULL)
{
return false;
}
FbxProperty FbxColorProperty = FbxMaterial->FindProperty(PropertyName);
if (FbxColorProperty.IsValid())
{
if (MaterialInput.IsConnected() && MaterialInput.Expression)
{
if (MaterialInput.Expression->IsA(UMaterialExpressionTextureSample::StaticClass()))
{
UMaterialExpressionTextureSample *TextureSample = Cast<UMaterialExpressionTextureSample>(MaterialInput.Expression);
if (TextureSample && TextureSample->Texture && TextureSample->Texture->AssetImportData)
{
FString TextureSourceFullPath = TextureSample->Texture->AssetImportData->GetFirstFilename();
//Create a fbx property
FbxFileTexture* lTexture = FbxFileTexture::Create(Scene, "EnvSamplerTex");
lTexture->SetFileName(TCHAR_TO_UTF8(*TextureSourceFullPath));
lTexture->SetTextureUse(FbxTexture::eStandard);
lTexture->SetMappingType(FbxTexture::eUV);
lTexture->ConnectDstProperty(FbxColorProperty);
return true;
}
}
}
}
return false;
}
/**
* Exports the profile_COMMON information for a material.
*/
FbxSurfaceMaterial* FFbxExporter::ExportMaterial(UMaterialInterface* MaterialInterface, const int32& MaterialIndex, const FFbxMaterialBakingMeshData& BakingMeshData)
{
FString ExportFolderPath = FPaths::GetPath(UExporter::CurrentFilename);
if (Scene == nullptr || MaterialInterface == nullptr || MaterialInterface->GetMaterial() == nullptr) return nullptr;
// Verify that this material has not already been exported:
TMap<int32, FbxSurfaceMaterial*>* MaterialIndexToFbxMaterials = FbxMaterials.Find(MaterialInterface);
if (MaterialIndexToFbxMaterials && MaterialIndexToFbxMaterials->Find(MaterialIndex))
{
return *MaterialIndexToFbxMaterials->Find(MaterialIndex);
}
// Create the Fbx material
FbxSurfaceMaterial* FbxMaterial = nullptr;
UMaterial *Material = MaterialInterface->GetMaterial();
if (!Material)
{
//Nothing to export
return nullptr;
}
if (!MaterialIndexToFbxMaterials)
{
MaterialIndexToFbxMaterials = &FbxMaterials.Add(MaterialInterface);
}
UMaterialEditorOnlyData* MaterialEditorOnly = Material->GetEditorOnlyData();
// Set the shading model
bool bUseLambert = false;
bool bInterchangeOriginated = FFbxMaterialExportUtilities::GetInterchangeShadingModel(MaterialInterface, bUseLambert);
FString FbxMaterialName = MaterialInterface->GetName();
if (MaterialIndexToFbxMaterials && MaterialIndexToFbxMaterials->Num() > 0)
{
FbxMaterialName += FString(TEXT("_")) + FString::FromInt(MaterialIndex);
}
if ((bInterchangeOriginated && !bUseLambert) || (!bInterchangeOriginated && Material->GetShadingModels().HasOnlyShadingModel(MSM_DefaultLit)))
{
FbxMaterial = FbxSurfacePhong::Create(Scene, TCHAR_TO_UTF8(*FbxMaterialName));
//((FbxSurfacePhong*)FbxMaterial)->Specular.Set(Material->Specular));
//((FbxSurfacePhong*)FbxMaterial)->Shininess.Set(Material->SpecularPower.Constant);
}
else // if (Material->ShadingModel == MSM_Unlit)
{
FbxMaterial = FbxSurfaceLambert::Create(Scene, TCHAR_TO_UTF8(*FbxMaterialName));
}
if (bInterchangeOriginated)
{
FFbxMaterialExportUtilities::ProcessInterchangeMaterials(MaterialInterface, Scene, FbxMaterial);
MaterialIndexToFbxMaterials->Add(MaterialIndex, FbxMaterial);
return FbxMaterial;
}
//Get the base material connected expression parameter name for all the supported fbx material exported properties
//We only support material input where the connected expression is a parameter of type (constant, scalar, vector, texture, TODO virtual texture)
bool bBaseColorNonConstAndNonDefault = (!MaterialEditorOnly->BaseColor.UseConstant && MaterialEditorOnly->BaseColor.Expression);
bool bEmissiveNonConstAndNonDefault = (!MaterialEditorOnly->EmissiveColor.UseConstant && MaterialEditorOnly->EmissiveColor.Expression);
bool bNormalNonConstAndNonDefault = (MaterialEditorOnly->Normal.Expression != nullptr);
bool bOpacityNonConstAndNonDefault = (!MaterialEditorOnly->Opacity.UseConstant && MaterialEditorOnly->Opacity.Expression);
bool bOpacityMaskNonConstAndNonDefault = (!MaterialEditorOnly->OpacityMask.UseConstant && MaterialEditorOnly->OpacityMask.Expression);
FName BaseColorParamName = bBaseColorNonConstAndNonDefault ? MaterialEditorOnly->BaseColor.Expression->GetParameterName() : NAME_None;
FName EmissiveParamName = bEmissiveNonConstAndNonDefault ? MaterialEditorOnly->EmissiveColor.Expression->GetParameterName() : NAME_None;
FName NormalParamName = bNormalNonConstAndNonDefault ? MaterialEditorOnly->Normal.Expression->GetParameterName() : NAME_None;
FName OpacityParamName = bOpacityNonConstAndNonDefault ? MaterialEditorOnly->Opacity.Expression->GetParameterName() : NAME_None;
FName OpacityMaskParamName = bOpacityMaskNonConstAndNonDefault ? MaterialEditorOnly->OpacityMask.Expression->GetParameterName() : NAME_None;
bool BaseColorParamSet = false;
bool EmissiveParamSet = false;
bool NormalParamSet = false;
bool OpacityParamSet = false;
bool OpacityMaskParamSet = false;
UMaterialInstance* MaterialInstance = Cast<UMaterialInstance>(MaterialInterface);
EBlendMode BlendMode = MaterialInstance != nullptr && MaterialInstance->BasePropertyOverrides.bOverride_BlendMode ? MaterialInstance->BasePropertyOverrides.BlendMode : Material->BlendMode;
// Loop through all types of parameters for this base material and add the supported one to the fbx material.
//The order is important we search Texture, Vector, Scalar.
TArray<FMaterialParameterInfo> ParameterInfos;
TArray<FGuid> Guids;
//Query all base material texture parameters.
Material->GetAllTextureParameterInfo(ParameterInfos, Guids);
for (int32 ParameterIdx = 0; ParameterIdx < ParameterInfos.Num(); ParameterIdx++)
{
const FMaterialParameterInfo& ParameterInfo = ParameterInfos[ParameterIdx];
FName ParameterName = ParameterInfo.Name;
UTexture* Value;
//Query the material instance parameter overridden value
if (MaterialInterface->GetTextureParameterValue(ParameterInfo, Value) && Value && Value->AssetImportData)
{
//This lambda extract the source file path and create the fbx file texture node and connect it to the fbx material
auto SetTextureProperty = [&FbxMaterial, &Value](const char* FbxPropertyName, FbxScene* InScene)->bool
{
FbxProperty FbxColorProperty = FbxMaterial->FindProperty(FbxPropertyName);
if (FbxColorProperty.IsValid())
{
const FString TextureName = Value->GetName();
const FString TextureSourceFullPath = Value->AssetImportData->GetFirstFilename();
if(!TextureSourceFullPath.IsEmpty())
{
//Create a fbx property
FbxFileTexture* lTexture = FbxFileTexture::Create(InScene, TCHAR_TO_UTF8(*TextureName));
lTexture->SetFileName(TCHAR_TO_UTF8(*TextureSourceFullPath));
lTexture->SetTextureUse(FbxTexture::eStandard);
lTexture->SetMappingType(FbxTexture::eUV);
lTexture->ConnectDstProperty(FbxColorProperty);
return true;
}
}
return false;
};
if (!BaseColorParamSet && BaseColorParamName != NAME_None && ParameterName == BaseColorParamName)
{
BaseColorParamSet = SetTextureProperty(FbxSurfaceMaterial::sDiffuse, Scene);
}
if (!EmissiveParamSet && EmissiveParamName != NAME_None && ParameterName == EmissiveParamName)
{
EmissiveParamSet = SetTextureProperty(FbxSurfaceMaterial::sEmissive, Scene);
}
if (BlendMode == BLEND_Translucent)
{
if (!OpacityParamSet && OpacityParamName != NAME_None && ParameterName == OpacityParamName)
{
OpacityParamSet = SetTextureProperty(FbxSurfaceMaterial::sTransparentColor, Scene);
}
if (!OpacityMaskParamSet && OpacityMaskParamName != NAME_None && ParameterName == OpacityMaskParamName)
{
OpacityMaskParamSet = SetTextureProperty(FbxSurfaceMaterial::sTransparencyFactor, Scene);
}
}
else
{
//There is no normal input in Blend translucent mode
if (!NormalParamSet && NormalParamName != NAME_None && ParameterName == NormalParamName)
{
NormalParamSet = SetTextureProperty(FbxSurfaceMaterial::sNormalMap, Scene);
}
}
}
}
Material->GetAllVectorParameterInfo(ParameterInfos, Guids);
//Query all base material vector parameters.
for (int32 ParameterIdx = 0; ParameterIdx < ParameterInfos.Num(); ParameterIdx++)
{
const FMaterialParameterInfo& ParameterInfo = ParameterInfos[ParameterIdx];
FName ParameterName = ParameterInfo.Name;
FLinearColor LinearColor;
//Query the material instance parameter overridden value
if (MaterialInterface->GetVectorParameterValue(ParameterInfo, LinearColor))
{
FbxDouble3 LinearFbxValue(LinearColor.R, LinearColor.G, LinearColor.B);
if (!BaseColorParamSet && BaseColorParamName != NAME_None && ParameterName == BaseColorParamName)
{
((FbxSurfaceLambert*)FbxMaterial)->Diffuse.Set(LinearFbxValue);
BaseColorParamSet = true;
}
if (!EmissiveParamSet && EmissiveParamName != NAME_None && ParameterName == EmissiveParamName)
{
((FbxSurfaceLambert*)FbxMaterial)->Emissive.Set(LinearFbxValue);
EmissiveParamSet = true;
}
}
}
//Query all base material scalar parameters.
Material->GetAllScalarParameterInfo(ParameterInfos, Guids);
for (int32 ParameterIdx = 0; ParameterIdx < ParameterInfos.Num(); ParameterIdx++)
{
const FMaterialParameterInfo& ParameterInfo = ParameterInfos[ParameterIdx];
FName ParameterName = ParameterInfo.Name;
float Value;
//Query the material instance parameter overridden value
if (MaterialInterface->GetScalarParameterValue(ParameterInfo, Value))
{
FbxDouble FbxValue = (FbxDouble)Value;
FbxDouble3 FbxVector(FbxValue, FbxValue, FbxValue);
if (!BaseColorParamSet && BaseColorParamName != NAME_None && ParameterName == BaseColorParamName)
{
((FbxSurfaceLambert*)FbxMaterial)->Diffuse.Set(FbxVector);
BaseColorParamSet = true;
}
if (!EmissiveParamSet && EmissiveParamName != NAME_None && ParameterName == EmissiveParamName)
{
((FbxSurfaceLambert*)FbxMaterial)->Emissive.Set(FbxVector);
EmissiveParamSet = true;
}
if (BlendMode == BLEND_Translucent)
{
if (!OpacityParamSet && OpacityParamName != NAME_None && ParameterName == OpacityParamName)
{
((FbxSurfaceLambert*)FbxMaterial)->TransparentColor.Set(FbxVector);
OpacityParamSet = true;
}
if (!OpacityMaskParamSet && OpacityMaskParamName != NAME_None && ParameterName == OpacityMaskParamName)
{
((FbxSurfaceLambert*)FbxMaterial)->TransparencyFactor.Set(FbxValue);
OpacityMaskParamSet = true;
}
}
}
}
//TODO: export virtual texture to fbx
//Query all base material runtime virtual texture parameters.
// Material->GetAllRuntimeVirtualTextureParameterInfo(ParameterInfos, Guids);
// for (int32 ParameterIdx = 0; ParameterIdx < ParameterInfos.Num(); ParameterIdx++)
// {
// const FMaterialParameterInfo& ParameterInfo = ParameterInfos[ParameterIdx];
// FName ParameterName = ParameterInfo.Name;
// URuntimeVirtualTexture* Value;
// //Query the material instance parameter overridden value
// if (MaterialInterface->GetRuntimeVirtualTextureParameterValue(ParameterInfo, Value, bOveriddenOnly))
// {
// }
// }
//
//In case there is no material instance override, we extract the value from the base material
//
//The OpacityMaskParam set the transparency factor, so set it only if it was not set
if(!OpacityMaskParamSet)
{
((FbxSurfaceLambert*)FbxMaterial)->TransparencyFactor.Set(MaterialEditorOnly->Opacity.Constant);
}
UFbxExportOption* FbxExportOptions = GetExportOptions();
// Fill in the profile_COMMON effect with the material information.
//Fill the texture or constant
if(!BaseColorParamSet)
{
if (!FillFbxTextureProperty(FbxSurfaceMaterial::sDiffuse, MaterialEditorOnly->BaseColor, FbxMaterial))
{
if (bBaseColorNonConstAndNonDefault)
{
FFbxMaterialExportUtilities::BakeMaterialProperty(FbxExportOptions,
Scene, FbxMaterial, FbxSurfaceMaterial::sDiffuse,
MP_BaseColor, MaterialInterface, MaterialIndex,
BakingMeshData,
ExportFolderPath);
}
else
{
((FbxSurfaceLambert*)FbxMaterial)->Diffuse.Set(SetMaterialComponent(MaterialEditorOnly->BaseColor, true));
}
}
}
if (!EmissiveParamSet)
{
if (!FillFbxTextureProperty(FbxSurfaceMaterial::sEmissive, MaterialEditorOnly->EmissiveColor, FbxMaterial))
{
if (bEmissiveNonConstAndNonDefault)
{
FFbxMaterialExportUtilities::BakeMaterialProperty(FbxExportOptions,
Scene, FbxMaterial, FbxSurfaceMaterial::sEmissive,
MP_EmissiveColor, MaterialInterface, MaterialIndex,
BakingMeshData,
ExportFolderPath);
}
else
{
((FbxSurfaceLambert*)FbxMaterial)->Emissive.Set(SetMaterialComponent(MaterialEditorOnly->EmissiveColor, true));
}
}
}
//Always set the ambient to zero since we dont have ambient in unreal we want to avoid default value in DCCs
((FbxSurfaceLambert*)FbxMaterial)->Ambient.Set(FbxDouble3(0.0, 0.0, 0.0));
if (BlendMode == BLEND_Translucent)
{
if (!OpacityParamSet)
{
if (!FillFbxTextureProperty(FbxSurfaceMaterial::sTransparentColor, MaterialEditorOnly->Opacity, FbxMaterial))
{
if (bOpacityNonConstAndNonDefault)
{
FFbxMaterialExportUtilities::BakeMaterialProperty(FbxExportOptions,
Scene, FbxMaterial, FbxSurfaceMaterial::sTransparentColor,
MP_Opacity, MaterialInterface, MaterialIndex,
BakingMeshData,
ExportFolderPath);
}
else
{
FbxDouble3 OpacityValue((FbxDouble)(MaterialEditorOnly->Opacity.Constant), (FbxDouble)(MaterialEditorOnly->Opacity.Constant), (FbxDouble)(MaterialEditorOnly->Opacity.Constant));
((FbxSurfaceLambert*)FbxMaterial)->TransparentColor.Set(OpacityValue);
}
}
}
if (!OpacityMaskParamSet)
{
if (!FillFbxTextureProperty(FbxSurfaceMaterial::sTransparencyFactor, MaterialEditorOnly->OpacityMask, FbxMaterial))
{
if (bOpacityMaskNonConstAndNonDefault)
{
FFbxMaterialExportUtilities::BakeMaterialProperty(FbxExportOptions,
Scene, FbxMaterial, FbxSurfaceMaterial::sTransparencyFactor,
MP_OpacityMask, MaterialInterface, MaterialIndex,
BakingMeshData,
ExportFolderPath);
}
else
{
((FbxSurfaceLambert*)FbxMaterial)->TransparencyFactor.Set(MaterialEditorOnly->OpacityMask.Constant);
}
}
}
}
else
{
//There is no normal input in Blend translucent mode
if (!NormalParamSet)
{
//Set the Normal map only if there is a texture sampler
if (!FillFbxTextureProperty(FbxSurfaceMaterial::sNormalMap, MaterialEditorOnly->Normal, FbxMaterial))
{
if (bNormalNonConstAndNonDefault)
{
FFbxMaterialExportUtilities::BakeMaterialProperty(FbxExportOptions,
Scene, FbxMaterial, FbxSurfaceMaterial::sNormalMap,
MP_Normal, MaterialInterface, MaterialIndex,
BakingMeshData,
ExportFolderPath);
}
}
}
}
MaterialIndexToFbxMaterials->Add(MaterialIndex, FbxMaterial);
return FbxMaterial;
}
FFbxExporter::FLevelSequenceNodeNameAdapter::FLevelSequenceNodeNameAdapter( UMovieScene* InMovieScene, IMovieScenePlayer* InMovieScenePlayer, FMovieSceneSequenceIDRef InSequenceID )
{
MovieScene = InMovieScene;
MovieScenePlayer = InMovieScenePlayer;
SequenceID = InSequenceID;
}
FString FFbxExporter::FLevelSequenceNodeNameAdapter::GetActorNodeName(const AActor* Actor)
{
FString NodeName = Actor->GetName();
for ( const FMovieSceneBinding& MovieSceneBinding : MovieScene->GetBindings() )
{
for ( TWeakObjectPtr<UObject> RuntimeObject : MovieScenePlayer->FindBoundObjects(MovieSceneBinding.GetObjectGuid(), SequenceID) )
{
if (RuntimeObject.Get() == Actor)
{
NodeName = MovieScene->GetObjectDisplayName(MovieSceneBinding.GetObjectGuid()).ToString();
}
}
}
// Maya does not support dashes. Change all dashes to underscores
NodeName = NodeName.Replace(TEXT("-"), TEXT("_") );
// Maya does not support spaces. Change all spaces to underscores
NodeName = NodeName.Replace(TEXT(" "), TEXT("_") );
return NodeName;
}
void FFbxExporter::FLevelSequenceNodeNameAdapter::AddFbxNode(UObject* InObject, FbxNode* FbxNode)
{
FGuid ObjectGuid = MovieScenePlayer->FindObjectId(*InObject, SequenceID);
if (ObjectGuid.IsValid())
{
if (!GuidToFbxNodeMap.Contains(ObjectGuid))
{
GuidToFbxNodeMap.Add(ObjectGuid);
}
GuidToFbxNodeMap[ObjectGuid] = FbxNode;
}
}
FbxNode* FFbxExporter::FLevelSequenceNodeNameAdapter::GetFbxNode(UObject* InObject)
{
FGuid ObjectGuid = MovieScenePlayer->FindObjectId(*InObject, SequenceID);
if (ObjectGuid.IsValid())
{
if (GuidToFbxNodeMap.Contains(ObjectGuid))
{
return GuidToFbxNodeMap[ObjectGuid];
}
}
return nullptr;
}
FLevelSequenceAnimTrackAdapter::FLevelSequenceAnimTrackAdapter(const FAnimTrackSettings& Settings)
{
MovieScenePlayer = Settings.MovieScenePlayer;
MovieSceneSequence = Settings.MovieSceneSequence;
RootMovieSceneSequence = Settings.RootMovieSceneSequence;
MovieScene = Settings.MovieSceneSequence->GetMovieScene();
RootToLocalTransform = Settings.RootToLocalTransform;
AnimTrack = Settings.AnimTrack;
bForceUseOfMovieScenePlaybackRange = Settings.bForceUseOfMovieScenePlaybackRange;
}
//5.6 deprecrated
FLevelSequenceAnimTrackAdapter::FLevelSequenceAnimTrackAdapter(IMovieScenePlayer* InMovieScenePlayer, UMovieSceneSequence* InMovieSceneSequence, UMovieSceneSequence* InRootMovieSceneSequence, const FMovieSceneSequenceTransform& InRootToLocalTransform, UMovieSceneSkeletalAnimationTrack* InAnimTrack)
{
MovieScenePlayer = InMovieScenePlayer;
MovieSceneSequence = InMovieSceneSequence;
RootMovieSceneSequence = InRootMovieSceneSequence;
MovieScene = MovieSceneSequence->GetMovieScene();
RootToLocalTransform = InRootToLocalTransform;
AnimTrack = InAnimTrack;
bForceUseOfMovieScenePlaybackRange = false;
}
//5.5 deprecrated
FLevelSequenceAnimTrackAdapter::FLevelSequenceAnimTrackAdapter(IMovieScenePlayer* InMovieScenePlayer, UMovieScene* InMovieScene, const FMovieSceneSequenceTransform& InRootToLocalTransform, UMovieSceneSkeletalAnimationTrack* InAnimTrack)
{
MovieScenePlayer = InMovieScenePlayer;
MovieScene = InMovieScene;
RootToLocalTransform = InRootToLocalTransform;
AnimTrack = InAnimTrack;
MovieSceneSequence = nullptr;
RootMovieSceneSequence = nullptr;
if (MovieScene)
{
if (UMovieSceneSequence* OwnerSceneSequence = MovieScene->GetTypedOuter<UMovieSceneSequence>())
{
MovieSceneSequence = OwnerSceneSequence;
RootMovieSceneSequence = OwnerSceneSequence;
}
}
bForceUseOfMovieScenePlaybackRange = false;
}
void FLevelSequenceAnimTrackAdapter::SetRange(const FFrameNumber& StartFrame, const FFrameNumber& EndFrame)
{
TPair<FFrameNumber, FFrameNumber> Range(StartFrame, EndFrame);
OptionalRange = Range;
}
TRange<FFrameNumber> FLevelSequenceAnimTrackAdapter::GetSequenceRange() const
{
if (OptionalRange.IsSet())
{
TRange<FFrameNumber> SetRange(OptionalRange.GetValue().Key, (OptionalRange.GetValue().Value));
return SetRange;
}
FMovieSceneEvaluationState* State = MovieScenePlayer->GetEvaluationState();
State->AssignSequence(MovieSceneSequenceID::Root, *RootMovieSceneSequence, *MovieScenePlayer);
FMovieSceneSequenceIDRef Template = State->FindSequenceId(MovieSceneSequence);
FMovieSceneSequenceHierarchy Hierarchy = FMovieSceneSequenceHierarchy();
UMovieSceneCompiledDataManager::CompileHierarchy(RootMovieSceneSequence, &Hierarchy,EMovieSceneServerClientMask::All);
const FMovieSceneSubSequenceData* SubSequenceData = Hierarchy.FindSubData(Template);
TRange<FFrameNumber> Range = MovieScene->GetPlaybackRange();
FFrameTime StartTime = FFrameRate::TransformTime(UE::MovieScene::DiscreteInclusiveLower(MovieScene->GetPlaybackRange()).Value, MovieScene->GetTickResolution(), GetDisplayRate());
if (SubSequenceData && bForceUseOfMovieScenePlaybackRange == false)
{
Range = SubSequenceData->PlayRange.Value;
}
return Range;
}
int32 FLevelSequenceAnimTrackAdapter::GetLocalStartFrame() const
{
TRange<FFrameNumber> Range = GetSequenceRange();
FFrameRate TickResolution = MovieScene->GetTickResolution();
FFrameRate DisplayRate = GetDisplayRate();
return FFrameRate::TransformTime(FFrameTime(UE::MovieScene::DiscreteInclusiveLower(Range)), TickResolution, DisplayRate).RoundToFrame().Value;
}
int32 FLevelSequenceAnimTrackAdapter::GetStartFrame() const
{
TRange<FFrameNumber> Range = GetSequenceRange();
FFrameRate TickResolution = MovieScene->GetTickResolution();
FFrameRate DisplayRate = GetDisplayRate();
FFrameNumber StartFrame = UE::MovieScene::DiscreteInclusiveLower(Range);
FMovieSceneInverseSequenceTransform Inverse = RootToLocalTransform.Inverse();
TOptional<FFrameTime> RootTime = Inverse.TryTransformTime(StartFrame);
return FFrameRate::TransformTime(RootTime.Get(StartFrame), TickResolution, DisplayRate).RoundToFrame().Value;
}
int32 FLevelSequenceAnimTrackAdapter::GetLength() const
{
TRange<FFrameNumber> Range = GetSequenceRange();
FFrameRate TickResolution = MovieScene->GetTickResolution();
FFrameRate DisplayRate = GetDisplayRate();
return FFrameRate::TransformTime(FFrameTime(UE::MovieScene::DiscreteSize(Range)), TickResolution, DisplayRate).RoundToFrame().Value;
}
void FLevelSequenceAnimTrackAdapter::UpdateAnimation( int32 LocalFrame )
{
FFrameRate TickResolution = MovieScene->GetTickResolution();
FFrameRate DisplayRate = GetDisplayRate();
FFrameTime LocalTime = FFrameRate::TransformTime(FFrameTime(LocalFrame), DisplayRate, TickResolution);
TOptional<FFrameTime> GlobalTime = RootToLocalTransform.Inverse().TryTransformTime(LocalTime);
if (GlobalTime)
{
FMovieSceneContext Context = FMovieSceneContext(FMovieSceneEvaluationRange(GlobalTime.GetValue(), TickResolution), MovieScenePlayer->GetPlaybackStatus()).SetHasJumped(true);
MovieScenePlayer->GetEvaluationTemplate().EvaluateSynchronousBlocking( Context );
}
}
void FLevelSequenceAnimTrackAdapter::SetFrameRate(FFrameRate InFrameRate)
{
OptionalFrameRate = InFrameRate;
}
double FLevelSequenceAnimTrackAdapter::GetFrameRate() const
{
return OptionalFrameRate.IsSet() ? OptionalFrameRate.GetValue().AsDecimal() : MovieScene->GetDisplayRate().AsDecimal();
}
FFrameRate FLevelSequenceAnimTrackAdapter::GetDisplayRate() const
{
return OptionalFrameRate.IsSet() ? OptionalFrameRate.GetValue() : MovieScene->GetDisplayRate();
}
UAnimSequence* FLevelSequenceAnimTrackAdapter::GetAnimSequence(int32 LocalFrame) const
{
if (!AnimTrack)
{
return nullptr;
}
FFrameRate TickResolution = MovieScene->GetTickResolution();
FFrameRate DisplayRate = GetDisplayRate();
FFrameTime LocalTime = FFrameRate::TransformTime(FFrameTime(LocalFrame), DisplayRate, TickResolution);
for (UMovieSceneSection* Section : AnimTrack->GetAnimSectionsAtTime(LocalTime.FrameNumber))
{
if (UMovieSceneSkeletalAnimationSection* AnimSection = Cast<UMovieSceneSkeletalAnimationSection>(Section))
{
return Cast<UAnimSequence>(AnimSection->Params.Animation);
}
}
return nullptr;
}
float FLevelSequenceAnimTrackAdapter::GetAnimTime(int32 LocalFrame) const
{
if (!AnimTrack)
{
return 0.f;
}
FFrameRate TickResolution = MovieScene->GetTickResolution();
FFrameRate DisplayRate = GetDisplayRate();
FFrameTime LocalTime = FFrameRate::TransformTime(FFrameTime(LocalFrame), DisplayRate, TickResolution);
for (UMovieSceneSection* Section : AnimTrack->GetAnimSectionsAtTime(LocalTime.FrameNumber))
{
if (UMovieSceneSkeletalAnimationSection* AnimSection = Cast<UMovieSceneSkeletalAnimationSection>(Section))
{
if (AnimSection->Params.Animation)
{
return static_cast<float>(AnimSection->MapTimeToAnimation(LocalTime, TickResolution));
}
}
}
return 0.f;
}
bool FFbxExporter::ExportLevelSequenceTracks(UMovieScene* MovieScene, IMovieScenePlayer* MovieScenePlayer, FMovieSceneSequenceIDRef InSequenceID, FbxNode* FbxActor, UObject* BoundObject, const TArray<UMovieSceneTrack*>& Tracks, const FMovieSceneSequenceTransform& RootToLocalTransform)
{
if (MovieScene)
{
if (UMovieSceneSequence* OwnerSceneSequence = MovieScene->GetTypedOuter<UMovieSceneSequence>())
{
return ExportLevelSequenceTracks(OwnerSceneSequence, OwnerSceneSequence, MovieScenePlayer, InSequenceID, FbxActor, BoundObject, Tracks, RootToLocalTransform);
}
}
return false;
}
bool FFbxExporter::ExportLevelSequenceTracks(UMovieSceneSequence* MovieSceneSequence, UMovieSceneSequence* RootMovieSceneSequence, IMovieScenePlayer* MovieScenePlayer, FMovieSceneSequenceIDRef InSequenceID, FbxNode* FbxActor, UObject* BoundObject, const TArray<UMovieSceneTrack*>& Tracks, const FMovieSceneSequenceTransform& RootToLocalTransform)
{
AActor* Actor = Cast<AActor>(BoundObject);
if (!Actor)
{
UActorComponent* Component = Cast<UActorComponent>(BoundObject);
if (Component)
{
Actor = Component->GetOwner();
}
}
USkeletalMeshComponent* SkeletalMeshComp = Cast<USkeletalMeshComponent>(BoundObject);
if (!SkeletalMeshComp)
{
SkeletalMeshComp = Actor ? Cast<USkeletalMeshComponent>(Actor->GetComponentByClass(USkeletalMeshComponent::StaticClass())) : nullptr;
}
UMovieScene* MovieScene = MovieSceneSequence->GetMovieScene();
FFrameRate DisplayRate = MovieScene->GetDisplayRate();
bool bSkip3DTransformTrack = SkeletalMeshComp && GetExportOptions()->MapSkeletalMotionToRoot;
// Get all the transform tracks that affect this binding
TArray<TWeakObjectPtr<UMovieScene3DTransformTrack> > TransformTracks;
if (BoundObject)
{
for ( const FMovieSceneBinding& MovieSceneBinding : MovieScene->GetBindings() )
{
for ( TWeakObjectPtr<UObject> RuntimeObject : MovieScenePlayer->FindBoundObjects(MovieSceneBinding.GetObjectGuid(), InSequenceID) )
{
if (RuntimeObject.IsValid())
{
AActor* RuntimeActor = Cast<AActor>(RuntimeObject);
UActorComponent* RuntimeComponent = nullptr;
if (!RuntimeActor)
{
RuntimeComponent = Cast<UActorComponent>(RuntimeObject);
if (RuntimeComponent)
{
RuntimeActor = RuntimeComponent->GetOwner();
}
}
if (RuntimeActor == Actor || RuntimeComponent == BoundObject)
{
for (UMovieSceneTrack* Track : MovieSceneBinding.GetTracks())
{
if (Track->IsA(UMovieScene3DTransformTrack::StaticClass()))
{
TransformTracks.Add(Cast<UMovieScene3DTransformTrack>(Track));
}
}
}
}
}
}
// Also need to skip 3d transform track if this object is an actor and the component has a transform track because otherwise
if (BoundObject->IsA<AActor>())
{
for ( const FMovieSceneBinding& MovieSceneBinding : MovieScene->GetBindings() )
{
for ( TWeakObjectPtr<UObject> RuntimeObject : MovieScenePlayer->FindBoundObjects(MovieSceneBinding.GetObjectGuid(), InSequenceID) )
{
if (RuntimeObject.IsValid())
{
UActorComponent* RuntimeComponent = Cast<UActorComponent>(RuntimeObject);
if (RuntimeComponent && RuntimeComponent->GetOwner() == BoundObject)
{
for (UMovieSceneTrack* Track : MovieSceneBinding.GetTracks())
{
if (Track->IsA(UMovieScene3DTransformTrack::StaticClass()))
{
bSkip3DTransformTrack = true;
}
}
}
}
}
}
}
}
AActor* BoundActor = Cast<AActor>(BoundObject);
USceneComponent* BoundComponent = Cast<USceneComponent>(BoundObject);
const bool bIsCameraActor = BoundActor ? BoundActor->IsA(ACameraActor::StaticClass()) : BoundComponent ? BoundComponent->IsA(UCameraComponent::StaticClass()) : false;
const bool bIsLightActor = BoundActor ? BoundActor->IsA(ALight::StaticClass()) : BoundComponent ? BoundComponent->IsA(ULightComponent::StaticClass()) : false;
bool bBakeAllTransformChannels = false;
if (bIsCameraActor || bIsLightActor)
{
bBakeAllTransformChannels = (static_cast<uint8>(GetExportOptions()->BakeCameraAndLightAnimation) & static_cast<uint8>(EMovieSceneBakeType::BakeTransforms));
}
else
{
bBakeAllTransformChannels = (static_cast<uint8>(GetExportOptions()->BakeActorAnimation) & static_cast<uint8>(EMovieSceneBakeType::BakeTransforms));
}
// If this actor has attached actors, and the bound component isn't the root component, skip the 3d transform track because we don't want the
// component transform to be assigned to the actor, which would subsequently apply to the attached children
if (Actor && BoundComponent && Actor->GetRootComponent() != BoundComponent)
{
TArray<AActor*> AttachedActors;
Actor->GetAttachedActors(AttachedActors);
if (AttachedActors.Num() != 0)
{
bSkip3DTransformTrack = true;
}
}
// If there's more than one transform track for this actor (ie. on the actor and on the root component) or if there's more than one section, evaluate baked
if (bBakeAllTransformChannels || TransformTracks.Num() > 1 || (TransformTracks.Num() != 0 && TransformTracks[0].Get()->GetAllSections().Num() > 1))
{
if (!bSkip3DTransformTrack)
{
UnFbx::FLevelSequenceAnimTrackAdapter::FAnimTrackSettings Settings;
Settings.MovieScenePlayer = MovieScenePlayer;
Settings.MovieSceneSequence = MovieSceneSequence;
Settings.RootMovieSceneSequence = RootMovieSceneSequence;
Settings.RootToLocalTransform = RootToLocalTransform;
Settings.bForceUseOfMovieScenePlaybackRange = false;
FLevelSequenceAnimTrackAdapter AnimTrackAdapter(Settings);
ExportLevelSequenceBaked3DTransformTrack(AnimTrackAdapter, FbxActor, MovieScenePlayer, InSequenceID, TransformTracks, BoundObject, MovieScene->GetPlaybackRange(), RootToLocalTransform);
}
bSkip3DTransformTrack = true;
}
// Look for the tracks that we currently support
UMovieSceneSkeletalAnimationTrack* SkeletalAnimationTrack = nullptr;
bool IsControlRigTrack = false;
for (UMovieSceneTrack* Track : Tracks)
{
if (Track->IsA(UMovieScene3DTransformTrack::StaticClass()))
{
if (!bSkip3DTransformTrack)
{
UMovieScene3DTransformTrack* TransformTrack = (UMovieScene3DTransformTrack*)Track;
ExportLevelSequence3DTransformTrack(FbxActor, MovieScenePlayer, InSequenceID, *TransformTrack, BoundObject, MovieScene->GetPlaybackRange(), RootToLocalTransform);
}
}
else if (Track->IsA(UMovieSceneSkeletalAnimationTrack::StaticClass()))
{
SkeletalAnimationTrack = Cast<UMovieSceneSkeletalAnimationTrack>(Track);
}
else if (Track->IsA(UMovieSceneColorTrack::StaticClass()))
{
ExportLevelSequenceColorTrack(FbxActor, *Cast<UMovieSceneColorTrack>(Track), BoundObject, MovieScene->GetPlaybackRange(), RootToLocalTransform);
}
else if (Track->IsA(UMovieSceneDoubleVectorTrack::StaticClass()))
{
ExportLevelSequenceVectorTrack(FbxActor, *Cast<UMovieSceneDoubleVectorTrack>(Track), BoundObject, MovieScene->GetPlaybackRange(), RootToLocalTransform);
}
else if(Cast<INodeAndChannelMappings>(Track))
{
IsControlRigTrack = true;
}
else
{
bool bBakeChannels = false;
if (bIsCameraActor || bIsLightActor)
{
bBakeChannels = (static_cast<uint8>(GetExportOptions()->BakeCameraAndLightAnimation) & static_cast<uint8>(EMovieSceneBakeType::BakeChannels));
}
else
{
bBakeChannels = (static_cast<uint8>(GetExportOptions()->BakeActorAnimation) & static_cast<uint8>(EMovieSceneBakeType::BakeChannels));
}
ExportLevelSequenceTrackChannels(FbxActor, *Track, MovieScene->GetPlaybackRange(), RootToLocalTransform, bBakeChannels);
}
}
// Export all of the skeletal animation components for this actor
if (SkeletalMeshComp && (SkeletalAnimationTrack || IsControlRigTrack))
{
TArray<USkeletalMeshComponent*> SkeletalMeshComponents;
SkeletalMeshComp->GetOwner()->GetComponents(SkeletalMeshComponents);
UnFbx::FLevelSequenceAnimTrackAdapter::FAnimTrackSettings Settings;
Settings.MovieScenePlayer = MovieScenePlayer;
Settings.MovieSceneSequence = MovieSceneSequence;
Settings.RootMovieSceneSequence = RootMovieSceneSequence;
Settings.RootToLocalTransform = RootToLocalTransform;
Settings.bForceUseOfMovieScenePlaybackRange = false;
for (USkeletalMeshComponent* SkeletalMeshComponent : SkeletalMeshComponents)
{
Settings.AnimTrack = SkeletalAnimationTrack;
FLevelSequenceAnimTrackAdapter AnimTrackAdapter(Settings);
ExportAnimTrack(AnimTrackAdapter, Actor, SkeletalMeshComponent, 1.0 / DisplayRate.AsDecimal());
}
}
return true;
}
bool FFbxExporter::ExportLevelSequence(UMovieScene* MovieScene, const TArray<FGuid>& Bindings, IMovieScenePlayer* MovieScenePlayer, INodeNameAdapter& NodeNameAdapter, FMovieSceneSequenceIDRef SequenceID, const FMovieSceneSequenceTransform& RootToLocalTransform)
{
if (MovieScene)
{
if (UMovieSceneSequence* OwnerSceneSequence = MovieScene->GetTypedOuter<UMovieSceneSequence>())
{
return ExportLevelSequence(OwnerSceneSequence, OwnerSceneSequence, Bindings, MovieScenePlayer, NodeNameAdapter, SequenceID, RootToLocalTransform);
}
}
return false;
}
bool FFbxExporter::ExportLevelSequence(UMovieSceneSequence* MovieSceneSequence, UMovieSceneSequence* RootMovieSceneSequence, const TArray<FGuid>& Bindings, IMovieScenePlayer* MovieScenePlayer, INodeNameAdapter& NodeNameAdapter, FMovieSceneSequenceIDRef SequenceID, const FMovieSceneSequenceTransform& RootToLocalTransform)
{
if (MovieSceneSequence == nullptr || RootMovieSceneSequence == nullptr || MovieScenePlayer == nullptr)
{
return false;
}
UMovieScene* MovieScene = MovieSceneSequence->GetMovieScene();
double FrameRate = MovieScene->GetDisplayRate().AsDecimal();
FbxTime::EMode TimeMode = FbxTime::ConvertFrameRateToTimeMode(FrameRate);
// Unknown frame rates, (ie. 12 fps) return eDefaultMode, so override with custom so that the custom frame rate takes effect
if (TimeMode == FbxTime::eDefaultMode)
{
TimeMode = FbxTime::eCustom;
}
Scene->GetGlobalSettings().SetTimeMode(TimeMode);
if (TimeMode == FbxTime::eCustom)
{
Scene->GetGlobalSettings().SetCustomFrameRate(FrameRate);
}
for (const FMovieSceneBinding& MovieSceneBinding : MovieScene->GetBindings())
{
// If there are specific bindings to export, export those only
if (Bindings.Num() != 0 && !Bindings.Contains(MovieSceneBinding.GetObjectGuid()))
{
continue;
}
bool bAnyBindingsExported = false;
for (TWeakObjectPtr<UObject> RuntimeObject : MovieScenePlayer->FindBoundObjects(MovieSceneBinding.GetObjectGuid(), SequenceID))
{
if (RuntimeObject.IsValid())
{
AActor* Actor = Cast<AActor>(RuntimeObject.Get());
UActorComponent* Component = Cast<UActorComponent>(RuntimeObject.Get());
if (Actor == nullptr && Component != nullptr)
{
Actor = Component->GetOwner();
}
if (Actor == nullptr)
{
continue;
}
FbxNode* FbxActor = FindActor(Actor, &NodeNameAdapter);
// now it should export everybody
if (FbxActor)
{
ExportLevelSequenceTracks(MovieSceneSequence, RootMovieSceneSequence, MovieScenePlayer, SequenceID, FbxActor, RuntimeObject.Get(), MovieSceneBinding.GetTracks(), RootToLocalTransform);
bAnyBindingsExported = true;
}
}
}
// If no bindings exported, create a dummy actor to export tracks onto
if (!bAnyBindingsExported)
{
ExportLevelSequenceTracks(MovieSceneSequence, RootMovieSceneSequence, MovieScenePlayer, SequenceID, nullptr, nullptr, MovieSceneBinding.GetTracks(), RootToLocalTransform);
}
}
return true;
}
void FFbxExporter::AddTimecodeAttributesAndSetKey(const UMovieSceneSection* InSection, FbxNode* InFbxNode, const FMovieSceneSequenceTransform& RootToLocalTransform)
{
FbxAnimLayer* BaseLayer = AnimStack->GetMember<FbxAnimLayer>(0);
const FFrameRate TickResolution = InSection->GetTypedOuter<UMovieScene>()->GetTickResolution();
FName TCHourAttrName(TEXT("TCHour"));
FName TCMinuteAttrName(TEXT("TCMinute"));
FName TCSecondAttrName(TEXT("TCSecond"));
FName TCFrameAttrName(TEXT("TCFrame"));
if (const UAnimationSettings* AnimationSettings = UAnimationSettings::Get())
{
TCHourAttrName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.HourAttributeName;
TCMinuteAttrName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.MinuteAttributeName;
TCSecondAttrName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.SecondAttributeName;
TCFrameAttrName = AnimationSettings->BoneTimecodeCustomAttributeNameSettings.FrameAttributeName;
}
const TArray<FString> TimecodePropertyNames = {
TCHourAttrName.ToString(),
TCMinuteAttrName.ToString(),
TCSecondAttrName.ToString(),
TCFrameAttrName.ToString()
};
const TArray<int32> TimecodeValues = {
InSection->TimecodeSource.Timecode.Hours,
InSection->TimecodeSource.Timecode.Minutes,
InSection->TimecodeSource.Timecode.Seconds,
InSection->TimecodeSource.Timecode.Frames
};
const FFrameNumber KeyTime = InSection->GetTypedOuter<UMovieScene>()->GetPlaybackRange().GetLowerBoundValue();
for (int i = 0; i < TimecodePropertyNames.Num(); i++)
{
const FString PropertyName = TimecodePropertyNames[i];
CreateAnimatableUserProperty(InFbxNode, 0.0, StringCast<char>(*PropertyName).Get(), StringCast<char>(*PropertyName).Get(), FbxFloatDT);
FbxProperty Property = InFbxNode->FindProperty(StringCast<char>(*PropertyName).Get());
if (Property.IsValid())
{
if (FbxAnimCurve* AnimCurve = Property.GetCurve(BaseLayer, nullptr, true))
{
const float KeyValue = (float)TimecodeValues[i];
AnimCurve->KeyModifyBegin();
FbxTime FbxTime;
TOptional<FFrameTime> GlobalKeyTime = RootToLocalTransform.Inverse().TryTransformTime(KeyTime);
const double KeyTimeSeconds = GetExportOptions()->bExportLocalTime ? KeyTime / TickResolution : GlobalKeyTime.Get(KeyTime) / TickResolution;
FbxTime.SetSecondDouble(KeyTimeSeconds);
const int FbxKeyIndex = AnimCurve->KeyAdd(FbxTime);
AnimCurve->KeySet(FbxKeyIndex, FbxTime, KeyValue, FbxAnimCurveDef::eInterpolationConstant);
AnimCurve->KeySetConstantMode(FbxKeyIndex, FbxAnimCurveDef::EConstantMode::eConstantStandard);
AnimCurve->KeyModifyEnd();
}
}
}
}
bool FFbxExporter::ExportControlRigSection(const UMovieSceneSection* Section, const TArray<FControlRigFbxNodeMapping>& ChannelsMapping,
const TArray<FName>& FilterControls, const FMovieSceneSequenceTransform& RootToLocalTransform)
{
if (!Section)
{
return false;
}
UMovieSceneTrack* Track = Section->GetTypedOuter<UMovieSceneTrack>();
FbxAnimLayer* BaseLayer = AnimStack->GetMember<FbxAnimLayer>(0);
const FFrameRate TickResolution = Track->GetTypedOuter<UMovieScene>()->GetTickResolution();
// Helpers to convert channel/property names to indices and vice versa
const TArray<FString> TransformComponentsArray = {"X", "Y", "Z"};
const TArray<FString> TransformPropertyArray = {"Location", "Rotation", "Scale"};
// Cached transform channels for a single property to bake and export the 3 channels in one go
TPair<FMovieSceneFloatChannel*, bool> CachedTransformChannels[3] = {{nullptr, false}, {nullptr, false}, {nullptr, false}};
// Cached transform property index keep track of which property we are currently caching to bake & export Transforms & TransformNoScale
int CurrentTmPropertyIndex = 0;
// The control rig parent group node
FbxNode* RootFbxNode = CreateNode(Track->GetDisplayName().ToString());
// Export timecode
AddTimecodeAttributesAndSetKey(Section, RootFbxNode, RootToLocalTransform);
const FName BoolChannelTypeName = FMovieSceneBoolChannel::StaticStruct()->GetFName();
const FName ByteChannelTypeName = FMovieSceneByteChannel::StaticStruct()->GetFName();
const FName DoubleChannelTypeName = FMovieSceneDoubleChannel::StaticStruct()->GetFName();
const FName FloatChannelTypeName = FMovieSceneFloatChannel::StaticStruct()->GetFName();
const FName IntegerChannelTypeName = FMovieSceneIntegerChannel::StaticStruct()->GetFName();
FMovieSceneChannelProxy& ChannelProxy = Section->GetChannelProxy();
for (const FMovieSceneChannelEntry& Entry : ChannelProxy.GetAllEntries())
{
TArrayView<FMovieSceneChannel* const> Channels = Entry.GetChannels();
TArrayView<const FMovieSceneChannelMetaData> AllMetaData = Entry.GetMetaData();
for (int32 Index = 0; Index < Channels.Num(); ++Index)
{
const FName ChannelTypeName = Entry.GetChannelTypeName();
FMovieSceneChannelHandle ChannelHandle = ChannelProxy.MakeHandle(ChannelTypeName, Index);
FMovieSceneBoolChannel* BoolChannel = ChannelTypeName == BoolChannelTypeName ? ChannelHandle.Cast<FMovieSceneBoolChannel>().Get() : nullptr;
FMovieSceneByteChannel* ByteChannel = ChannelTypeName == ByteChannelTypeName ? ChannelHandle.Cast<FMovieSceneByteChannel>().Get() : nullptr;
FMovieSceneDoubleChannel* DoubleChannel = ChannelTypeName == DoubleChannelTypeName ? ChannelHandle.Cast<FMovieSceneDoubleChannel>().Get() : nullptr;
FMovieSceneFloatChannel* FloatChannel = ChannelTypeName == FloatChannelTypeName ? ChannelHandle.Cast<FMovieSceneFloatChannel>().Get() : nullptr;
FMovieSceneIntegerChannel* IntegerChannel = ChannelTypeName == IntegerChannelTypeName ? ChannelHandle.Cast<FMovieSceneIntegerChannel>().Get() : nullptr;
if (!BoolChannel && !ByteChannel && !DoubleChannel && !FloatChannel && !IntegerChannel)
{
continue;
}
// Find the node and attribute names
const FMovieSceneChannelMetaData& MetaData = AllMetaData[Index];
const FString ChannelName = MetaData.Name.ToString();
FControlRigFbxCurveData FbxCurveData;
if (INodeAndChannelMappings* ChannelMapping = Cast<INodeAndChannelMappings>(Track))
{
if (!ChannelMapping->GetFbxCurveDataFromChannelMetadata(MetaData, FbxCurveData))
{
continue;
}
}
// Skip any control not in the filter if the filter isn't empty
if (!FilterControls.IsEmpty() && !FilterControls.Contains(FbxCurveData.ControlName))
{
continue;
}
// Find or create the node
FbxNode* ControlFbxNode = RootFbxNode->FindChild(StringCast<char>(*FbxCurveData.NodeName).Get());
if (ControlFbxNode == nullptr)
{
ControlFbxNode = FbxNode::Create(Scene, StringCast<char>(*FbxCurveData.NodeName).Get());
RootFbxNode->AddChild(ControlFbxNode);
}
// Remap channel, optionally baking & exporting it for transforms & rotations
bool bNegate = false;
int TmComponentIndex = TransformComponentsArray.Find(FbxCurveData.AttributeName);
// Find the attribute to remap onto and whether to negate the channel if required
const FControlRigFbxNodeMapping* NodeMapping = ChannelsMapping.FindByPredicate(
[ChannelTypeName, FbxCurveData, TmComponentIndex](const FControlRigFbxNodeMapping& AMapping)
{
const bool bAttrMatch = AMapping.ChannelAttrIndex == TmComponentIndex;
const bool bControlTypeOk = AMapping.ControlType == FbxCurveData.ControlType;
// todo: am: need to allow mapping of position/rotation/scale for transforms & euler transforms to allow export of negated transform curves
const bool bChannelTypeOk = AMapping.ChannelType == ChannelTypeName;
// If the control is treated as an attribute of another control, we don't need to check the attribute to remap
const bool bAttrOk = !FbxCurveData.IsControlNode() || bAttrMatch;
const bool bRemapControl = bControlTypeOk && bChannelTypeOk && bAttrOk;
return bRemapControl;
});
if (TmComponentIndex != INDEX_NONE)
{
// Retrieve property name for Vector2D, Position, Scale
if (FbxCurveData.ControlType == FFBXControlRigTypeProxyEnum::Vector2D || FbxCurveData.ControlType == FFBXControlRigTypeProxyEnum::Position)
{
FbxCurveData.AttributePropertyName = "Location";
}
if (FbxCurveData.ControlType == FFBXControlRigTypeProxyEnum::Scale)
{
FbxCurveData.AttributePropertyName = "Scale";
}
// Export converted & baked for Rotations & Transforms
if ((uint8)FbxCurveData.ControlType >= (uint8)FFBXControlRigTypeProxyEnum::Rotator)
{
const bool bExportCachedTransforms = TmComponentIndex == 2;
const bool bRotator = FbxCurveData.ControlType == FFBXControlRigTypeProxyEnum::Rotator;
int TmPropertyIndex = bRotator ? 1 : CurrentTmPropertyIndex;
// todo: am: does not handle remapping to another property (i.e. rotation to location)
// Remap transform channel
if (NodeMapping)
{
TmPropertyIndex = NodeMapping->FbxAttrIndex / 3;
TmComponentIndex = NodeMapping->FbxAttrIndex % 3;
bNegate = NodeMapping->bNegate;
}
// Cache a single transform property channel
CachedTransformChannels[TmComponentIndex].Key = FloatChannel;
CachedTransformChannels[TmComponentIndex].Value = bNegate;
// Once all 3 channels in the current property have been cached, export baked
if (bExportCachedTransforms)
{
for (int i = 0; i < 3; i++)
{
if (CachedTransformChannels[i].Key && CachedTransformChannels[i].Key->GetNumKeys() > 0)
{
// Only export baked if at least one curve has keys
ExportTransformChannelsToFbxCurve(ControlFbxNode, CachedTransformChannels[0],
CachedTransformChannels[1], CachedTransformChannels[2],
TmPropertyIndex, Track, RootToLocalTransform);
break;
}
}
// Increase or reset CurrentTmPropertyIndex to keep track of which property in a Transform we are currently caching
const bool bNoScale = FbxCurveData.ControlType == FFBXControlRigTypeProxyEnum::TransformNoScale;
const bool bTransformProcessed = bRotator || CurrentTmPropertyIndex == 2 || (bNoScale && CurrentTmPropertyIndex == 1);
CurrentTmPropertyIndex = bTransformProcessed ? 0 : CurrentTmPropertyIndex + 1;
}
continue;
}
}
// Remap non transform channels if required (rotations & transforms remapped separately)
if (NodeMapping)
{
FbxCurveData.AttributePropertyName = TransformPropertyArray[NodeMapping->FbxAttrIndex / 3];
FbxCurveData.AttributeName = TransformComponentsArray[NodeMapping->FbxAttrIndex % 3];
bNegate = NodeMapping->bNegate;
}
FbxProperty Property;
// Use AttributeName (i.e. Weight) as the property if no AttributePropertyName (i.e. Scale). Use NodeName if AttributeName was also empty...
const FString FbxPropertyName = FbxCurveData.AttributePropertyName.IsEmpty() ? (!FbxCurveData.AttributeName.IsEmpty() ? FbxCurveData.AttributeName : FbxCurveData.NodeName) : FbxCurveData.AttributePropertyName;
// Try and find the existing property
if (FbxCurveData.AttributePropertyName == "Location")
{
Property = ControlFbxNode->LclTranslation;
}
else if (FbxCurveData.AttributePropertyName == "Rotation")
{
Property = ControlFbxNode->LclRotation;
}
else if (FbxCurveData.AttributePropertyName == "Scale")
{
Property = ControlFbxNode->LclScaling;
}
else
{
Property = ControlFbxNode->FindProperty(StringCast<char>(*FbxPropertyName).Get());
}
// Or create it otherwise
if (!Property.IsValid())
{
if (DoubleChannel)
{
double Default = DoubleChannel->GetDefault().Get(0);
CreateAnimatableUserProperty(ControlFbxNode, Default, StringCast<char>(*FbxPropertyName).Get(), StringCast<char>(*FbxPropertyName).Get(), FbxDoubleDT);
}
else if (FloatChannel)
{
float Default = FloatChannel->GetDefault().Get(0);
CreateAnimatableUserProperty(ControlFbxNode, Default, StringCast<char>(*FbxPropertyName).Get(), StringCast<char>(*FbxPropertyName).Get(), FbxFloatDT);
}
else if (IntegerChannel)
{
int32 Default = IntegerChannel->GetDefault().Get(0);
CreateAnimatableUserProperty(ControlFbxNode, Default, StringCast<char>(*FbxPropertyName).Get(), StringCast<char>(*FbxPropertyName).Get(), FbxIntDT);
}
else if (BoolChannel)
{
bool Default = BoolChannel->GetDefault().Get(false);
CreateAnimatableUserProperty(ControlFbxNode, Default, StringCast<char>(*FbxPropertyName).Get(), StringCast<char>(*FbxPropertyName).Get(), FbxBoolDT);
}
else if (ByteChannel)
{
uint8 Default = ByteChannel->GetDefault().Get(0);
CreateAnimatableUserProperty(ControlFbxNode, Default, StringCast<char>(*FbxPropertyName).Get(), StringCast<char>(*FbxPropertyName).Get(), FbxEnumDT);
}
Property = ControlFbxNode->FindProperty(StringCast<char>(*FbxPropertyName).Get());
}
if (!Property.IsValid())
{
continue;
}
// Create the anim curve, optionally of the given name if the attribute had a component
FbxAnimCurveNode* AnimCurveNode = Property.GetCurveNode(BaseLayer, true);
FbxAnimCurve* AnimCurve = Property.GetCurve(BaseLayer, FbxCurveData.AttributePropertyName.IsEmpty() ? nullptr : StringCast<char>(*FbxCurveData.AttributeName).Get(), true);
if (!AnimCurve)
{
continue;
}
// Export the curve
if (BoolChannel)
{
ExportChannelToFbxCurve(*AnimCurve, *BoolChannel, TickResolution, RootToLocalTransform);
}
else if (ByteChannel)
{
ExportChannelToFbxCurve(*AnimCurve, *ByteChannel, TickResolution, RootToLocalTransform);
}
else if (DoubleChannel)
{
ExportChannelToFbxCurve(*AnimCurve, *DoubleChannel, TickResolution, ERichCurveValueMode::Default, bNegate, RootToLocalTransform);
}
else if (FloatChannel)
{
ExportChannelToFbxCurve(*AnimCurve, *FloatChannel, TickResolution, ERichCurveValueMode::Default, bNegate, RootToLocalTransform);
}
else if (IntegerChannel)
{
ExportChannelToFbxCurve(*AnimCurve, *IntegerChannel, TickResolution, RootToLocalTransform);
}
}
}
return true;
}
void FFbxExporter::ExportTransformChannelsToFbxCurve(FbxNode* InFbxNode, TPair<FMovieSceneFloatChannel*, bool> ChannelX, TPair<FMovieSceneFloatChannel*, bool> ChannelY, TPair<FMovieSceneFloatChannel*, bool> ChannelZ, int TmPropertyIndex, const UMovieSceneTrack* Track, const FMovieSceneSequenceTransform& RootToLocalTransform)
{
FbxAnimLayer* BaseLayer = AnimStack->GetMember<FbxAnimLayer>(0);
FbxProperty Property = TmPropertyIndex == 0 ? InFbxNode->LclTranslation : TmPropertyIndex == 1 ? InFbxNode->LclRotation : InFbxNode->LclScaling;
FbxAnimCurve* FbxCurveX = Property.GetCurve(BaseLayer, FBXSDK_CURVENODE_COMPONENT_X, true);
FbxAnimCurve* FbxCurveY = Property.GetCurve(BaseLayer, FBXSDK_CURVENODE_COMPONENT_Y, true);
FbxAnimCurve* FbxCurveZ = Property.GetCurve(BaseLayer, FBXSDK_CURVENODE_COMPONENT_Z, true);
FTransform RotationDirectionConvert;
FbxCurveX->KeyModifyBegin();
FbxCurveY->KeyModifyBegin();
FbxCurveZ->KeyModifyBegin();
FFrameRate TickResolution = Track->GetTypedOuter<UMovieScene>()->GetTickResolution();
FFrameRate DisplayRate = Track->GetTypedOuter<UMovieScene>()->GetDisplayRate();
TRange<FFrameNumber> PlaybackRange = Track->GetTypedOuter<UMovieScene>()->GetPlaybackRange();
FMovieSceneInverseSequenceTransform SequenceToRootTransform = RootToLocalTransform.Inverse();
int32 LocalStartFrame = FFrameRate::TransformTime(FFrameTime(UE::MovieScene::DiscreteInclusiveLower(PlaybackRange)), TickResolution, DisplayRate).RoundToFrame().Value;
int32 AnimationLength = FFrameRate::TransformTime(FFrameTime(FFrameNumber(UE::MovieScene::DiscreteSize(PlaybackRange))), TickResolution, DisplayRate).RoundToFrame().Value;
for (int32 FrameCount = 0; FrameCount <= AnimationLength; ++FrameCount)
{
int32 LocalFrame = LocalStartFrame + FrameCount;
FFrameTime LocalTime = FFrameRate::TransformTime(FFrameTime(LocalFrame), DisplayRate, TickResolution);
FVector3f Vec = FVector3f::ZeroVector;
if (ChannelX.Key)
{
ChannelX.Key->Evaluate(LocalTime, Vec.X);
if (ChannelX.Value)
{
Vec.X = -Vec.X;
}
}
if (ChannelY.Key)
{
ChannelY.Key->Evaluate(LocalTime, Vec.Y);
if (ChannelY.Value)
{
Vec.Y = -Vec.Y;
}
}
if (ChannelZ.Key)
{
ChannelZ.Key->Evaluate(LocalTime, Vec.Z);
if (ChannelZ.Value)
{
Vec.Z = -Vec.Z;
}
}
FbxVector4 KeyVec;
if (TmPropertyIndex == 0)
{
KeyVec = Converter.ConvertToFbxPos((FVector)Vec);
}
else if (TmPropertyIndex == 1)
{
KeyVec = Converter.ConvertToFbxRot((FVector)Vec);
}
else
{
KeyVec = Converter.ConvertToFbxScale((FVector)Vec);
}
FbxTime FbxTime;
if (GetExportOptions()->bExportLocalTime)
{
FbxTime.SetSecondDouble(DisplayRate.AsSeconds(LocalFrame));
}
// @todo: This code does not handle the root sequence having a different tick resolution than the local space, but we do
// not have that information here so we have to just assume they match. This should be improved so that we have
// all the information we need to do the right thing
else if (TOptional<FFrameTime> GlobalKeyTime = SequenceToRootTransform.TryTransformTime(LocalTime))
{
FbxTime.SetSecondDouble(TickResolution.AsSeconds(GlobalKeyTime.GetValue()));
}
else
{
// Doesn't map to a root time
continue;
}
FbxCurveX->KeySet(FbxCurveX->KeyAdd(FbxTime), FbxTime, KeyVec[0]);
FbxCurveY->KeySet(FbxCurveY->KeyAdd(FbxTime), FbxTime, KeyVec[1]);
FbxCurveZ->KeySet(FbxCurveZ->KeyAdd(FbxTime), FbxTime, KeyVec[2]);
}
FbxCurveX->KeyModifyEnd();
FbxCurveY->KeyModifyEnd();
FbxCurveZ->KeyModifyEnd();
}
/**
* Exports a scene node with the placement indicated by a given actor.
* This scene node will always have two transformations: one translation vector and one Euler rotation.
*/
FbxNode* FFbxExporter::ExportActor(AActor* Actor, bool bExportComponents, INodeNameAdapter& NodeNameAdapter, bool bSaveAnimSeq )
{
// Verify that this actor isn't already exported, create a structure for it
// and buffer it.
FbxNode* ActorNode = FindActor(Actor, &NodeNameAdapter);
if (ActorNode == NULL)
{
FString FbxNodeName = NodeNameAdapter.GetActorNodeName(Actor);
FbxNodeName = GetFbxObjectName(FbxNodeName, NodeNameAdapter);
ActorNode = FbxNode::Create(Scene, TCHAR_TO_UTF8(*FbxNodeName));
AActor* ParentActor = Actor->GetAttachParentActor();
// this doesn't work with skeletalmeshcomponent
FbxNode* ParentNode = FindActor(ParentActor, &NodeNameAdapter);
FVector ActorLocation, ActorRotation, ActorScale;
TArray<AActor*> AttachedActors;
Actor->GetAttachedActors(AttachedActors);
const bool bHasAttachedActors = AttachedActors.Num() != 0;
// For cameras and lights: always add a rotation to get the correct coordinate system.
FTransform RotationDirectionConvert = FTransform::Identity;
const bool bIsCameraActor = Actor->IsA(ACameraActor::StaticClass());
const bool bIsLightActor = Actor->IsA(ALight::StaticClass());
if (bIsCameraActor || bIsLightActor)
{
if (bIsCameraActor)
{
FRotator Rotator = FFbxDataConverter::GetCameraRotation().GetInverse();
RotationDirectionConvert = FTransform(Rotator);
}
else if (bIsLightActor)
{
FRotator Rotator = FFbxDataConverter::GetLightRotation().GetInverse();
RotationDirectionConvert = FTransform(Rotator);
}
}
//If the parent is the root or is not export use the root node as the parent
if (bKeepHierarchy && ParentNode)
{
// Set the default position of the actor on the transforms
// The transformation is different from FBX's Z-up: invert the Y-axis for translations and the Y/Z angle values in rotations.
const FTransform RelativeTransform = RotationDirectionConvert * Actor->GetTransform().GetRelativeTransform(ParentActor->GetTransform());
ActorLocation = RelativeTransform.GetTranslation();
ActorRotation = RelativeTransform.GetRotation().Euler();
ActorScale = RelativeTransform.GetScale3D();
}
else
{
ParentNode = Scene->GetRootNode();
// Set the default position of the actor on the transforms
// The transformation is different from FBX's Z-up: invert the Y-axis for translations and the Y/Z angle values in rotations.
if (ParentActor != NULL)
{
//In case the parent was not export, get the absolute transform
const FTransform AbsoluteTransform = RotationDirectionConvert * Actor->GetTransform();
ActorLocation = AbsoluteTransform.GetTranslation();
ActorRotation = AbsoluteTransform.GetRotation().Euler();
ActorScale = AbsoluteTransform.GetScale3D();
}
else
{
const FTransform ConvertedTransform = RotationDirectionConvert * Actor->GetTransform();
ActorLocation = ConvertedTransform.GetTranslation();
ActorRotation = ConvertedTransform.GetRotation().Euler();
ActorScale = ConvertedTransform.GetScale3D();
}
}
ParentNode->AddChild(ActorNode);
FbxActors.Add(Actor, ActorNode);
NodeNameAdapter.AddFbxNode(Actor, ActorNode);
// Set the default position of the actor on the transforms
// The transformation is different from FBX's Z-up: invert the Y-axis for translations and the Y/Z angle values in rotations.
ActorNode->LclTranslation.Set(Converter.ConvertToFbxPos(ActorLocation));
ActorNode->LclRotation.Set(Converter.ConvertToFbxRot(ActorRotation));
ActorNode->LclScaling.Set(Converter.ConvertToFbxScale(ActorScale));
if( bExportComponents )
{
TInlineComponentArray<USceneComponent*> ComponentsToExport;
for (UActorComponent* ActorComp : Actor->GetComponents())
{
USceneComponent* Component = Cast<USceneComponent>(ActorComp);
if (Component == nullptr || Component->bHiddenInGame)
{
//Skip hidden component like camera mesh or other editor helper
continue;
}
UStaticMeshComponent* StaticMeshComp = Cast<UStaticMeshComponent>( Component );
USkeletalMeshComponent* SkelMeshComp = Cast<USkeletalMeshComponent>( Component );
UChildActorComponent* ChildActorComp = Cast<UChildActorComponent>( Component );
if( StaticMeshComp && StaticMeshComp->GetStaticMesh())
{
ComponentsToExport.Add( StaticMeshComp );
}
else if( SkelMeshComp && SkelMeshComp->GetSkeletalMeshAsset())
{
ComponentsToExport.Add( SkelMeshComp );
}
else if (Component->IsA(UCameraComponent::StaticClass()))
{
ComponentsToExport.Add(Component);
}
else if (Component->IsA(ULightComponent::StaticClass()))
{
ComponentsToExport.Add(Component);
}
else if (ChildActorComp && ChildActorComp->GetChildActor())
{
ComponentsToExport.Add(ChildActorComp);
}
}
for( int32 CompIndex = 0; CompIndex < ComponentsToExport.Num(); ++CompIndex )
{
USceneComponent* Component = ComponentsToExport[CompIndex];
RotationDirectionConvert = FTransform::Identity;
// For cameras and lights: always add a rotation to get the correct coordinate system
// Unless we are parented to an Actor of the same type, since the rotation direction was already added
if (Component->IsA(UCameraComponent::StaticClass()) || Component->IsA(ULightComponent::StaticClass()))
{
if (!bIsCameraActor && Component->IsA(UCameraComponent::StaticClass()))
{
FRotator Rotator = FFbxDataConverter::GetCameraRotation().GetInverse();
RotationDirectionConvert = FTransform(Rotator);
}
else if (!bIsLightActor && Component->IsA(ULightComponent::StaticClass()))
{
FRotator Rotator = FFbxDataConverter::GetLightRotation().GetInverse();
RotationDirectionConvert = FTransform(Rotator);
}
}
FbxNode* ExportNode = ActorNode;
if( ComponentsToExport.Num() > 1 )
{
// This actor has multiple components
// create a child node under the actor for each component
FbxNode* CompNode = FbxNode::Create(Scene, TCHAR_TO_UTF8(*Component->GetName()));
if( Component != Actor->GetRootComponent() )
{
// Transform is relative to the root component
const FTransform RelativeTransform = RotationDirectionConvert * Component->GetComponentToWorld().GetRelativeTransform(Actor->GetTransform());
CompNode->LclTranslation.Set(Converter.ConvertToFbxPos(RelativeTransform.GetTranslation()));
CompNode->LclRotation.Set(Converter.ConvertToFbxRot(RelativeTransform.GetRotation().Euler()));
CompNode->LclScaling.Set(Converter.ConvertToFbxScale(RelativeTransform.GetScale3D()));
}
ExportNode = CompNode;
ActorNode->AddChild(CompNode);
}
// If this actor has attached actors, don't allow its non root component components to contribute to the transform
else if(Component != Actor->GetRootComponent() && !bHasAttachedActors)
{
// Merge the component relative transform in the ActorNode transform since this is the only component to export and its not the root
const FTransform RelativeTransform = RotationDirectionConvert * Component->GetComponentToWorld().GetRelativeTransform(Actor->GetTransform());
FTransform ActorTransform(FRotator::MakeFromEuler(ActorRotation).Quaternion(), ActorLocation, ActorScale);
FTransform TotalTransform = RelativeTransform * ActorTransform;
ActorNode->LclTranslation.Set(Converter.ConvertToFbxPos(TotalTransform.GetLocation()));
ActorNode->LclRotation.Set(Converter.ConvertToFbxRot(TotalTransform.GetRotation().Euler()));
ActorNode->LclScaling.Set(Converter.ConvertToFbxScale(TotalTransform.GetScale3D()));
}
UStaticMeshComponent* StaticMeshComp = Cast<UStaticMeshComponent>( Component );
USkeletalMeshComponent* SkelMeshComp = Cast<USkeletalMeshComponent>( Component );
UChildActorComponent* ChildActorComp = Cast<UChildActorComponent>( Component );
if (StaticMeshComp && StaticMeshComp->GetStaticMesh())
{
const int32 LODIndex = (StaticMeshComp->ForcedLodModel > 0 ? FMath::Min(StaticMeshComp->GetStaticMesh()->GetNumLODs(), StaticMeshComp->ForcedLodModel) - 1 : /* auto-select*/ 0);
if (USplineMeshComponent* SplineMeshComp = Cast<USplineMeshComponent>(StaticMeshComp))
{
//TODO: Validate Spline material baking
//(Spline mesh staticmesh does not take into account the Spline transforms, might not be accurate/enough to use Spline->StaticMesh for material baking.)
ExportSplineMeshToFbx(SplineMeshComp, *SplineMeshComp->GetName(), ExportNode, FFbxMaterialBakingMeshData(SplineMeshComp->GetStaticMesh(), SplineMeshComp, LODIndex));
}
else if (UInstancedStaticMeshComponent* InstancedMeshComp = Cast<UInstancedStaticMeshComponent>(StaticMeshComp))
{
ExportInstancedMeshToFbx(InstancedMeshComp, *InstancedMeshComp->GetName(), ExportNode, FFbxMaterialBakingMeshData(InstancedMeshComp->GetStaticMesh(), InstancedMeshComp, LODIndex));
}
else
{
const int32 LightmapUVChannel = -1;
const TArray<FStaticMaterial>* MaterialOrderOverride = nullptr;
const FColorVertexBuffer* ColorBuffer = nullptr;
ExportStaticMeshToFbx(StaticMeshComp->GetStaticMesh(), LODIndex, *StaticMeshComp->GetName(), ExportNode, FFbxMaterialBakingMeshData(StaticMeshComp->GetStaticMesh(), StaticMeshComp, LODIndex), LightmapUVChannel, ColorBuffer, MaterialOrderOverride, &ToRawPtrTArrayUnsafe(StaticMeshComp->OverrideMaterials));
}
}
else if (SkelMeshComp && SkelMeshComp->GetSkeletalMeshAsset())
{
ExportSkeletalMeshComponent(SkelMeshComp, *SkelMeshComp->GetName(), ExportNode, NodeNameAdapter, bSaveAnimSeq);
}
// If this actor has attached actors, don't allow a camera component to determine the node attributes because that would alter the transform
else if (Component->IsA(UCameraComponent::StaticClass()) && !bHasAttachedActors)
{
FbxCamera* Camera = FbxCamera::Create(Scene, TCHAR_TO_UTF8(*Component->GetName()));
FillFbxCameraAttribute(ActorNode, Camera, Cast<UCameraComponent>(Component));
ExportNode->SetNodeAttribute(Camera);
}
else if (Component->IsA(ULightComponent::StaticClass()) && !bHasAttachedActors)
{
FbxLight* Light = FbxLight::Create(Scene, TCHAR_TO_UTF8(*Component->GetName()));
FillFbxLightAttribute(Light, ActorNode, Cast<ULightComponent>(Component));
ExportNode->SetNodeAttribute(Light);
}
else if (ChildActorComp && ChildActorComp->GetChildActor())
{
FbxNode* ChildActorNode = ExportActor(ChildActorComp->GetChildActor(), true, NodeNameAdapter, bSaveAnimSeq);
FbxActors.Add(ChildActorComp->GetChildActor(), ChildActorNode);
NodeNameAdapter.AddFbxNode(ChildActorComp->GetChildActor(), ChildActorNode);
}
}
}
}
return ActorNode;
}
void ConvertInterpToFBX(uint8 UnrealInterpMode, FbxAnimCurveDef::EInterpolationType& Interpolation, FbxAnimCurveDef::ETangentMode& Tangent)
{
switch(UnrealInterpMode)
{
case CIM_Linear:
Interpolation = FbxAnimCurveDef::eInterpolationLinear;
Tangent = FbxAnimCurveDef::eTangentUser;
break;
case CIM_CurveAuto:
Interpolation = FbxAnimCurveDef::eInterpolationCubic;
Tangent = FbxAnimCurveDef::eTangentAuto;
break;
case CIM_Constant:
Interpolation = FbxAnimCurveDef::eInterpolationConstant;
Tangent = (FbxAnimCurveDef::ETangentMode)FbxAnimCurveDef::eConstantStandard;
break;
case CIM_CurveUser:
Interpolation = FbxAnimCurveDef::eInterpolationCubic;
Tangent = FbxAnimCurveDef::eTangentUser;
break;
case CIM_CurveBreak:
Interpolation = FbxAnimCurveDef::eInterpolationCubic;
Tangent = (FbxAnimCurveDef::ETangentMode) FbxAnimCurveDef::eTangentBreak;
break;
case CIM_CurveAutoClamped:
Interpolation = FbxAnimCurveDef::eInterpolationCubic;
Tangent = (FbxAnimCurveDef::ETangentMode) (FbxAnimCurveDef::eTangentAuto | FbxAnimCurveDef::eTangentGenericClamp);
break;
case CIM_Unknown: // ???
Interpolation = FbxAnimCurveDef::eInterpolationConstant;
Tangent = FbxAnimCurveDef::eTangentAuto;
break;
}
}
// float-float comparison that allows for a certain error in the floating point values
// due to floating-point operations never being exact.
static bool IsEquivalent(float a, float b, float Tolerance = KINDA_SMALL_NUMBER)
{
return (a - b) > -Tolerance && (a - b) < Tolerance;
}
void RichCurveInterpolationToFbxInterpolation(ERichCurveInterpMode InInterpolation, ERichCurveTangentMode InTangentMode, ERichCurveTangentWeightMode InTangentWeightMode,
FbxAnimCurveDef::EInterpolationType &OutInterpolation, FbxAnimCurveDef::ETangentMode &OutTangentMode, FbxAnimCurveDef::EWeightedMode &OutTangentWeightMode)
{
if (InInterpolation == ERichCurveInterpMode::RCIM_Cubic)
{
OutInterpolation = FbxAnimCurveDef::eInterpolationCubic;
OutTangentMode = FbxAnimCurveDef::eTangentUser;
// Always set tangent on the fbx key, so OutTangentMode should explicitly be eTangentUser unless Break.
if (InTangentMode == RCTM_Break)
{
OutTangentMode = FbxAnimCurveDef::eTangentBreak;
}
switch (InTangentWeightMode)
{
case ERichCurveTangentWeightMode::RCTWM_WeightedBoth:
OutTangentWeightMode = FbxAnimCurveDef::eWeightedAll;
break;
case ERichCurveTangentWeightMode::RCTWM_WeightedArrive:
OutTangentWeightMode = FbxAnimCurveDef::eWeightedNextLeft;
break;
case ERichCurveTangentWeightMode::RCTWM_WeightedLeave:
OutTangentWeightMode = FbxAnimCurveDef::eWeightedRight;
break;
case ERichCurveTangentWeightMode::RCTWM_WeightedNone:
default:
OutTangentWeightMode = FbxAnimCurveDef::eWeightedNone;
break;
};
}
else if (InInterpolation == ERichCurveInterpMode::RCIM_Linear)
{
OutInterpolation = FbxAnimCurveDef::eInterpolationLinear;
OutTangentMode = FbxAnimCurveDef::eTangentUser;
OutTangentWeightMode = FbxAnimCurveDef::eWeightedNone;
}
else if (InInterpolation == ERichCurveInterpMode::RCIM_Constant)
{
OutInterpolation = FbxAnimCurveDef::eInterpolationConstant;
OutTangentMode = (FbxAnimCurveDef::ETangentMode)FbxAnimCurveDef::eConstantStandard;
OutTangentWeightMode = FbxAnimCurveDef::eWeightedNone;
}
else
{
OutInterpolation = FbxAnimCurveDef::eInterpolationCubic;
OutTangentMode = FbxAnimCurveDef::eTangentUser;
OutTangentWeightMode = FbxAnimCurveDef::eWeightedNone;
}
}
template<typename ChannelType>
void FFbxExporter::ExportBezierChannelToFbxCurveBaked(FbxAnimCurve& InFbxCurve, const ChannelType& InChannel, FFrameRate TickResolution, const UMovieSceneTrack* Track, ERichCurveValueMode ValueMode, bool bNegative, const FMovieSceneSequenceTransform& RootToLocalTransform)
{
using ChannelValueType = typename ChannelType::ChannelValueType;
using CurveValueType = typename ChannelType::CurveValueType;
const float NegateFactor = bNegative ? -1.f : 1.f;
const float kOneThird = 1.f / 3.f;
InFbxCurve.KeyModifyBegin();
FMovieSceneInverseSequenceTransform SequenceToRootTransform = RootToLocalTransform.Inverse();
FFrameRate DisplayRate = Track->GetTypedOuter<UMovieScene>()->GetDisplayRate();
TRange<FFrameNumber> PlaybackRange = Track->GetTypedOuter<UMovieScene>()->GetPlaybackRange();
int32 LocalStartFrame = FFrameRate::TransformTime(FFrameTime(UE::MovieScene::DiscreteInclusiveLower(PlaybackRange)), TickResolution, DisplayRate).RoundToFrame().Value;
int32 AnimationLength = FFrameRate::TransformTime(FFrameTime(FFrameNumber(UE::MovieScene::DiscreteSize(PlaybackRange))), TickResolution, DisplayRate).RoundToFrame().Value;
for (int32 FrameCount = 0; FrameCount <= AnimationLength; ++FrameCount)
{
int32 LocalFrame = LocalStartFrame + FrameCount;
FFrameTime LocalTime = FFrameRate::TransformTime(FFrameTime(LocalFrame), DisplayRate, TickResolution);
float Value;
InChannel.Evaluate(LocalTime, Value);
FbxTime FbxTime;
if (GetExportOptions()->bExportLocalTime)
{
FbxTime.SetSecondDouble(DisplayRate.AsSeconds(LocalFrame));
}
// @todo: This code does not handle the root sequence having a different tick resolution than the local space, but we do
// not have that information here so we have to just assume they match. This should be improved so that we have
// all the information we need to do the right thing
else if (TOptional<FFrameTime> GlobalKeyTime = SequenceToRootTransform.TryTransformTime(LocalTime))
{
FbxTime.SetSecondDouble(TickResolution.AsSeconds(GlobalKeyTime.GetValue()));
}
else
{
// Doesn't map to a root time
continue;
}
InFbxCurve.KeySet(InFbxCurve.KeyAdd(FbxTime), FbxTime, Value, FbxAnimCurveDef::eInterpolationLinear);
}
InFbxCurve.KeyModifyEnd();
}
template<typename ChannelType>
void FFbxExporter::ExportBezierChannelToFbxCurve(FbxAnimCurve& InFbxCurve, const ChannelType& InChannel, FFrameRate TickResolution, ERichCurveValueMode ValueMode, bool bNegative, const FMovieSceneSequenceTransform& RootToLocalTransform)
{
using ChannelValueType = typename ChannelType::ChannelValueType;
using CurveValueType = typename ChannelType::CurveValueType;
const float NegateFactor = bNegative ? -1.f : 1.f;
const float kOneThird = 1.f / 3.f;
InFbxCurve.KeyModifyBegin();
FMovieSceneInverseSequenceTransform SequenceToRootTransform = RootToLocalTransform.Inverse();
TArrayView<const FFrameNumber> Times = InChannel.GetTimes();
TArrayView<const ChannelValueType> Values = InChannel.GetValues();
for (int32 Index = 0; Index < Times.Num(); ++Index)
{
const FFrameNumber KeyTime = Times[Index];
const ChannelValueType KeyValue = Values[Index];
const CurveValueType Value = (ValueMode == ERichCurveValueMode::Fov ? DefaultCamera->ComputeFocalLength( KeyValue.Value ) : KeyValue.Value) * NegateFactor;
FbxTime FbxTime;
FbxAnimCurveKey FbxKey;
if (GetExportOptions()->bExportLocalTime)
{
FbxTime.SetSecondDouble(TickResolution.AsSeconds(KeyTime));
}
// @todo: This code does not handle the root sequence having a different tick resolution than the local space, but we do
// not have that information here so we have to just assume they match. This should be improved so that we have
// all the information we need to do the right thing
else if (TOptional<FFrameTime> GlobalKeyTime = SequenceToRootTransform.TryTransformTime(KeyTime))
{
FbxTime.SetSecondDouble(TickResolution.AsSeconds(GlobalKeyTime.GetValue()));
}
else
{
// Doesn't map to a root time
continue;
}
const int FbxKeyIndex = InFbxCurve.KeyAdd(FbxTime);
FbxAnimCurveDef::EInterpolationType Interpolation = FbxAnimCurveDef::eInterpolationCubic;
FbxAnimCurveDef::ETangentMode Tangent = FbxAnimCurveDef::eTangentAuto;
FbxAnimCurveDef::EWeightedMode WeightedMode = FbxAnimCurveDef::eWeightedNone;
RichCurveInterpolationToFbxInterpolation(KeyValue.InterpMode, KeyValue.TangentMode, KeyValue.Tangent.TangentWeightMode, Interpolation, Tangent, WeightedMode);
if (Interpolation == FbxAnimCurveDef::eInterpolationCubic)
{
if (Index < Times.Num() - 1)
{
float LeaveTangentWeight = kOneThird;
float NextArriveTangentWeight = kOneThird;
const double NextTime = Times[Index + 1] / TickResolution;
const float TimeDiff = static_cast<float>(NextTime - FbxTime.GetSecondDouble());
float LeaveTangent = KeyValue.Tangent.LeaveTangent * TickResolution.AsDecimal();
float NextArriveTangent = Values[Index + 1].Tangent.ArriveTangent * TickResolution.AsDecimal();
//Need to convert UE tangent weight which is the length of the hypotenuse to FBX normalized X(time) weight
if (WeightedMode == FbxAnimCurveDef::eWeightedAll || WeightedMode == FbxAnimCurveDef::eWeightedRight)
{
const CurveValueType XVal = FMath::Sqrt((KeyValue.Tangent.LeaveTangentWeight * KeyValue.Tangent.LeaveTangentWeight) / (1.0f + LeaveTangent * LeaveTangent));
LeaveTangentWeight = XVal / TimeDiff;
}
//make sure next tangent is weighted else use default weight
if ((Values[Index + 1].Tangent.TangentWeightMode == ERichCurveTangentWeightMode::RCTWM_WeightedBoth || Values[Index + 1].Tangent.TangentWeightMode == ERichCurveTangentWeightMode::RCTWM_WeightedArrive) )
{
const CurveValueType XVal = FMath::Sqrt((Values[Index + 1].Tangent.ArriveTangentWeight * Values[Index + 1].Tangent.ArriveTangentWeight) / (1.0f + NextArriveTangent * NextArriveTangent));
NextArriveTangentWeight = XVal / TimeDiff;
}
if (bNegative)
{
LeaveTangent = -LeaveTangent;
NextArriveTangent = -NextArriveTangent;
}
InFbxCurve.KeySet(FbxKeyIndex, FbxTime, Value, Interpolation, Tangent, LeaveTangent, NextArriveTangent, WeightedMode, LeaveTangentWeight, NextArriveTangentWeight );
}
else
{
InFbxCurve.KeySet(FbxKeyIndex, FbxTime, Value, Interpolation, Tangent, 0.f, 0.f, WeightedMode);
}
}
else
{
InFbxCurve.KeySet(FbxKeyIndex, FbxTime, Value, Interpolation, Tangent);
}
}
InFbxCurve.KeyModifyEnd();
}
void FFbxExporter::ExportChannelToFbxCurve(FbxAnimCurve& InFbxCurve, const FMovieSceneFloatChannel& InChannel, FFrameRate TickResolution, ERichCurveValueMode ValueMode, bool bNegative, const FMovieSceneSequenceTransform& RootToLocalTransform)
{
ExportBezierChannelToFbxCurve(InFbxCurve, InChannel, TickResolution, ValueMode, bNegative, RootToLocalTransform);
}
void FFbxExporter::ExportChannelToFbxCurve(FbxAnimCurve& InFbxCurve, const FMovieSceneDoubleChannel& InChannel, FFrameRate TickResolution, ERichCurveValueMode ValueMode, bool bNegative, const FMovieSceneSequenceTransform& RootToLocalTransform)
{
ExportBezierChannelToFbxCurve(InFbxCurve, InChannel, TickResolution, ValueMode, bNegative, RootToLocalTransform);
}
void FFbxExporter::ExportChannelToFbxCurve(FbxAnimCurve& InFbxCurve, const FMovieSceneIntegerChannel& InChannel, FFrameRate TickResolution, const FMovieSceneSequenceTransform& RootToLocalTransform)
{
ExportConstantChannelToFbxCurve<FMovieSceneIntegerChannel, int32>(InFbxCurve, InChannel, TickResolution, RootToLocalTransform);
}
void FFbxExporter::ExportChannelToFbxCurve(FbxAnimCurve& InFbxCurve, const FMovieSceneBoolChannel& InChannel, FFrameRate TickResolution, const FMovieSceneSequenceTransform& RootToLocalTransform)
{
ExportConstantChannelToFbxCurve<FMovieSceneBoolChannel, bool>(InFbxCurve, InChannel, TickResolution, RootToLocalTransform);
}
void FFbxExporter::ExportChannelToFbxCurve(FbxAnimCurve& InFbxCurve, const FMovieSceneByteChannel& InChannel, FFrameRate TickResolution, const FMovieSceneSequenceTransform& RootToLocalTransform)
{
ExportConstantChannelToFbxCurve<FMovieSceneByteChannel, uint8>(InFbxCurve, InChannel, TickResolution, RootToLocalTransform);
}
template<typename ChannelType, typename T>
void FFbxExporter::ExportConstantChannelToFbxCurve(FbxAnimCurve& InFbxCurve, const ChannelType& InChannel, FFrameRate TickResolution, const FMovieSceneSequenceTransform& RootToLocalTransform)
{
InFbxCurve.KeyModifyBegin();
const TArrayView<const FFrameNumber> Times = InChannel.GetTimes();
const TArrayView<const T> Values = InChannel.GetValues();
FMovieSceneInverseSequenceTransform SequenceToRootTransform = RootToLocalTransform.Inverse();
for (int32 Index = 0; Index < Times.Num(); ++Index)
{
const FFrameNumber KeyTime = Times[Index];
const T KeyValue = Values[Index];
FbxTime FbxTime;
if (GetExportOptions()->bExportLocalTime)
{
FbxTime.SetSecondDouble(TickResolution.AsSeconds(KeyTime));
}
// @todo: This code does not handle the root sequence having a different tick resolution than the local space, but we do
// not have that information here so we have to just assume they match. This should be improved so that we have
// all the information we need to do the right thing
else if (TOptional<FFrameTime> GlobalKeyTime = SequenceToRootTransform.TryTransformTime(KeyTime))
{
FbxTime.SetSecondDouble(TickResolution.AsSeconds(GlobalKeyTime.GetValue()));
}
else
{
// Doesn't map to a root time
continue;
}
const int FbxKeyIndex = InFbxCurve.KeyAdd(FbxTime);
InFbxCurve.KeySet(FbxKeyIndex, FbxTime, KeyValue, FbxAnimCurveDef::eInterpolationConstant);
InFbxCurve.KeySetConstantMode(FbxKeyIndex, FbxAnimCurveDef::EConstantMode::eConstantStandard);
}
InFbxCurve.KeyModifyEnd();
}
void FFbxExporter::ExportLevelSequenceColorTrack(FbxNode* FbxNode, UMovieSceneColorTrack& ColorTrack, UObject* BoundObject, const TRange<FFrameNumber>& InPlaybackRange, const FMovieSceneSequenceTransform& RootToLocalTransform)
{
UMovieSceneColorSection* ColorSection = ColorTrack.GetAllSections().Num() > 0
? Cast<UMovieSceneColorSection>(ColorTrack.GetAllSections()[0])
: nullptr;
if(!ColorSection)
{
return;
}
if(!FbxNode)
{
FbxNode = CreateNode(ColorTrack.GetDisplayName().ToString());
}
FbxLight* FbxLight = FbxNode->GetLight();
const FString PropertyName = ColorTrack.GetTrackName().ToString();
FbxProperty Property;
if(PropertyName == TEXT("LightColor") && FbxLight)
{
Property = FbxLight->Color;
}
if(Property == 0)
{
CreateAnimatableUserProperty(FbxNode, FbxDouble3{ 0 }, TCHAR_TO_UTF8(*PropertyName), TCHAR_TO_UTF8(*PropertyName), FbxDouble3DT);
Property = FbxNode->FindProperty(TCHAR_TO_UTF8(*PropertyName));
}
if(Property == 0)
{
return;
}
FbxAnimLayer* BaseLayer = AnimStack->GetMember<FbxAnimLayer>(0);
FbxAnimCurve* CurveRed = Property.GetCurve(BaseLayer, FBXSDK_CURVENODE_COLOR_RED, true);
FbxAnimCurve* CurveGreen = Property.GetCurve(BaseLayer, FBXSDK_CURVENODE_COLOR_GREEN, true);
FbxAnimCurve* CurveBlue = Property.GetCurve(BaseLayer, FBXSDK_CURVENODE_COLOR_BLUE, true);
FFrameRate TickResolution = ColorTrack.GetTypedOuter<UMovieScene>()->GetTickResolution();
ExportChannelToFbxCurve(*CurveRed, ColorSection->GetRedChannel(), TickResolution, ERichCurveValueMode::Default, false, RootToLocalTransform);
ExportChannelToFbxCurve(*CurveGreen, ColorSection->GetGreenChannel(), TickResolution, ERichCurveValueMode::Default, false, RootToLocalTransform);
ExportChannelToFbxCurve(*CurveBlue, ColorSection->GetBlueChannel(), TickResolution, ERichCurveValueMode::Default, false, RootToLocalTransform);
}
void FFbxExporter::ExportLevelSequenceVectorTrack(FbxNode* FbxNode, UMovieSceneDoubleVectorTrack& VectorTrack, UObject* BoundObject, const TRange<FFrameNumber>& InPlaybackRange, const FMovieSceneSequenceTransform& RootToLocalTransform)
{
UMovieSceneDoubleVectorSection* VectorSection = VectorTrack.GetAllSections().Num() > 0
? Cast<UMovieSceneDoubleVectorSection>(VectorTrack.GetAllSections()[0])
: nullptr;
if(!VectorSection)
{
return;
}
if(!FbxNode)
{
FbxNode = CreateNode(VectorTrack.GetDisplayName().ToString());
}
const FString PropertyName = VectorTrack.GetTrackName().ToString();
//VectorSections supports up to 2-4 channels
const int32 NumChannelsUsed = VectorSection->GetChannelsUsed();
CreateAnimatableUserProperty(FbxNode,
NumChannelsUsed == 2 ? FbxDouble2{ 0 } :
NumChannelsUsed == 3 ? FbxDouble3{ 0 } :
FbxDouble4{ 0 },
TCHAR_TO_UTF8(*PropertyName),
TCHAR_TO_UTF8(*PropertyName),
NumChannelsUsed == 2 ? FbxDouble2DT :
NumChannelsUsed == 3 ? FbxDouble3DT :
FbxDouble4DT);
FbxProperty Property = FbxNode->FindProperty(TCHAR_TO_UTF8(*PropertyName));
if(Property == 0)
{
return;
}
const char* CurvesNames[] = { "X", "Y", "Z", "W" };
FbxAnimLayer* BaseLayer = AnimStack->GetMember<FbxAnimLayer>(0);
FFrameRate TickResolution = VectorTrack.GetTypedOuter<UMovieScene>()->GetTickResolution();
for(int32 Index = 0; Index < NumChannelsUsed; ++Index)
{
FbxAnimCurve* Curve = Property.GetCurve(BaseLayer, CurvesNames[Index], true);
ExportChannelToFbxCurve(*Curve, VectorSection->GetChannel(Index), TickResolution, ERichCurveValueMode::Default, false, RootToLocalTransform);
}
}
void FFbxExporter::ExportLevelSequence3DTransformTrack(FbxNode* FbxNode, IMovieScenePlayer* MovieScenePlayer, FMovieSceneSequenceIDRef InSequenceID, UMovieScene3DTransformTrack& TransformTrack, UObject* BoundObject, const TRange<FFrameNumber>& InPlaybackRange, const FMovieSceneSequenceTransform& RootToLocalTransform)
{
// TODO: Support more than one section?
UMovieScene3DTransformSection* TransformSection = TransformTrack.GetAllSections().Num() > 0
? Cast<UMovieScene3DTransformSection>(TransformTrack.GetAllSections()[0])
: nullptr;
if (TransformSection == nullptr)
{
return;
}
FMovieSceneInverseSequenceTransform SequenceToRootTransform = RootToLocalTransform.Inverse();
FbxAnimLayer* BaseLayer = AnimStack->GetMember<FbxAnimLayer>(0);
AActor* BoundActor = Cast<AActor>(BoundObject);
USceneComponent* BoundComponent = Cast<USceneComponent>(BoundObject);
const bool bIsCameraActor = BoundActor ? BoundActor->IsA(ACameraActor::StaticClass()) : BoundComponent ? BoundComponent->IsA(UCameraComponent::StaticClass()) : false;
const bool bIsLightActor = BoundActor ? BoundActor->IsA(ALight::StaticClass()) : BoundComponent ? BoundComponent->IsA(ULightComponent::StaticClass()) : false;
// If bake rotations is set, and actor is a camera or light actor, only bake rotation channel
const bool bBakeRotations = (bIsCameraActor || bIsLightActor) && (static_cast<uint8>(GetExportOptions()->BakeCameraAndLightAnimation) & static_cast<uint8>(EMovieSceneBakeType::BakeTransforms));
if (!FbxNode)
{
FbxNode = CreateNode(TransformTrack.GetDisplayName().ToString());
}
FFrameRate TickResolution = TransformTrack.GetTypedOuter<UMovieScene>()->GetTickResolution();
FFrameRate DisplayRate = TransformTrack.GetTypedOuter<UMovieScene>()->GetDisplayRate();
FbxAnimCurveNode* TranslationNode = FbxNode->LclTranslation.GetCurveNode(BaseLayer, true);
FbxAnimCurveNode* RotationNode = FbxNode->LclRotation.GetCurveNode(BaseLayer, true);
FbxAnimCurveNode* ScaleNode = FbxNode->LclScaling.GetCurveNode(BaseLayer, true);
FbxAnimCurve* FbxCurveTransX = FbxNode->LclTranslation.GetCurve(BaseLayer, FBXSDK_CURVENODE_COMPONENT_X, true);
FbxAnimCurve* FbxCurveTransY = FbxNode->LclTranslation.GetCurve(BaseLayer, FBXSDK_CURVENODE_COMPONENT_Y, true);
FbxAnimCurve* FbxCurveTransZ = FbxNode->LclTranslation.GetCurve(BaseLayer, FBXSDK_CURVENODE_COMPONENT_Z, true);
FbxAnimCurve* FbxCurveRotX = FbxNode->LclRotation.GetCurve(BaseLayer, FBXSDK_CURVENODE_COMPONENT_X, true);
FbxAnimCurve* FbxCurveRotY = FbxNode->LclRotation.GetCurve(BaseLayer, FBXSDK_CURVENODE_COMPONENT_Y, true);
FbxAnimCurve* FbxCurveRotZ = FbxNode->LclRotation.GetCurve(BaseLayer, FBXSDK_CURVENODE_COMPONENT_Z, true);
FbxAnimCurve* FbxCurveScaleX = FbxNode->LclScaling.GetCurve(BaseLayer, FBXSDK_CURVENODE_COMPONENT_X, true);
FbxAnimCurve* FbxCurveScaleY = FbxNode->LclScaling.GetCurve(BaseLayer, FBXSDK_CURVENODE_COMPONENT_Y, true);
FbxAnimCurve* FbxCurveScaleZ = FbxNode->LclScaling.GetCurve(BaseLayer, FBXSDK_CURVENODE_COMPONENT_Z, true);
FMovieSceneChannelProxy& SectionChannelProxy = TransformSection->GetChannelProxy();
TMovieSceneChannelHandle<FMovieSceneDoubleChannel> DoubleChannels[] = {
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Location.X"),
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Location.Y"),
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Location.Z"),
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Rotation.X"),
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Rotation.Y"),
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Rotation.Z"),
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Scale.X"),
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Scale.Y"),
SectionChannelProxy.GetChannelByName<FMovieSceneDoubleChannel>("Scale.Z")
};
// Translation
if (DoubleChannels[0].Get())
{
ExportChannelToFbxCurve(*FbxCurveTransX, *DoubleChannels[0].Get(), TickResolution, ERichCurveValueMode::Default, false, RootToLocalTransform);
}
if (DoubleChannels[1].Get())
{
ExportChannelToFbxCurve(*FbxCurveTransY, *DoubleChannels[1].Get(), TickResolution, ERichCurveValueMode::Default, true, RootToLocalTransform);
}
if (DoubleChannels[2].Get())
{
ExportChannelToFbxCurve(*FbxCurveTransZ, *DoubleChannels[2].Get(), TickResolution, ERichCurveValueMode::Default, false, RootToLocalTransform);
}
// Scale - don't generate scale keys for cameras
if (!bIsCameraActor)
{
if (DoubleChannels[6].Get())
{
ExportChannelToFbxCurve(*FbxCurveScaleX, *DoubleChannels[6].Get(), TickResolution, ERichCurveValueMode::Default, false, RootToLocalTransform);
}
if (DoubleChannels[7].Get())
{
ExportChannelToFbxCurve(*FbxCurveScaleY, *DoubleChannels[7].Get(), TickResolution, ERichCurveValueMode::Default, false, RootToLocalTransform);
}
if (DoubleChannels[8].Get())
{
ExportChannelToFbxCurve(*FbxCurveScaleZ, *DoubleChannels[8].Get(), TickResolution, ERichCurveValueMode::Default, false, RootToLocalTransform);
}
}
// Rotation - bake rotation for cameras and lights
if (!bBakeRotations)
{
if (DoubleChannels[3].Get())
{
ExportChannelToFbxCurve(*FbxCurveRotX, *DoubleChannels[3].Get(), TickResolution, ERichCurveValueMode::Default, false, RootToLocalTransform);
}
if (DoubleChannels[4].Get())
{
ExportChannelToFbxCurve(*FbxCurveRotY, *DoubleChannels[4].Get(), TickResolution, ERichCurveValueMode::Default, true, RootToLocalTransform);
}
if (DoubleChannels[5].Get())
{
ExportChannelToFbxCurve(*FbxCurveRotZ, *DoubleChannels[5].Get(), TickResolution, ERichCurveValueMode::Default, true, RootToLocalTransform);
}
}
else
{
FTransform RotationDirectionConvert;
if (bIsCameraActor)
{
FRotator Rotator = FFbxDataConverter::GetCameraRotation().GetInverse();
RotationDirectionConvert = FTransform(Rotator);
}
else if (bIsLightActor)
{
FRotator Rotator = FFbxDataConverter::GetLightRotation().GetInverse();
RotationDirectionConvert = FTransform(Rotator);
}
FbxCurveRotX->KeyModifyBegin();
FbxCurveRotY->KeyModifyBegin();
FbxCurveRotZ->KeyModifyBegin();
int32 LocalStartFrame = FFrameRate::TransformTime(FFrameTime(UE::MovieScene::DiscreteInclusiveLower(InPlaybackRange)), TickResolution, DisplayRate).RoundToFrame().Value;
int32 AnimationLength = FFrameRate::TransformTime(FFrameTime(FFrameNumber(UE::MovieScene::DiscreteSize(InPlaybackRange))), TickResolution, DisplayRate).RoundToFrame().Value;
for (int32 FrameCount = 0; FrameCount <= AnimationLength; ++FrameCount)
{
int32 LocalFrame = LocalStartFrame + FrameCount;
FFrameTime LocalTime = FFrameRate::TransformTime(FFrameTime(LocalFrame), DisplayRate, TickResolution);
FVector3f Trans = FVector3f::ZeroVector;
if (DoubleChannels[0].Get())
{
DoubleChannels[0].Get()->Evaluate(LocalTime, Trans.X);
}
if (DoubleChannels[1].Get())
{
DoubleChannels[1].Get()->Evaluate(LocalTime, Trans.Y);
}
if (DoubleChannels[2].Get())
{
DoubleChannels[2].Get()->Evaluate(LocalTime, Trans.Z);
}
FRotator Rotator;
if (DoubleChannels[3].Get())
{
DoubleChannels[3].Get()->Evaluate(LocalTime, Rotator.Roll);
}
if (DoubleChannels[4].Get())
{
DoubleChannels[4].Get()->Evaluate(LocalTime, Rotator.Pitch);
}
if (DoubleChannels[5].Get())
{
DoubleChannels[5].Get()->Evaluate(LocalTime, Rotator.Yaw);
}
FVector3f Scale;
if (DoubleChannels[6].Get())
{
DoubleChannels[6].Get()->Evaluate(LocalTime, Scale.X);
}
if (DoubleChannels[7].Get())
{
DoubleChannels[7].Get()->Evaluate(LocalTime, Scale.Y);
}
if (DoubleChannels[8].Get())
{
DoubleChannels[8].Get()->Evaluate(LocalTime, Scale.Z);
}
FTransform RelativeTransform;
RelativeTransform.SetTranslation((FVector)Trans);
RelativeTransform.SetRotation(Rotator.Quaternion());
RelativeTransform.SetScale3D((FVector)Scale);
RelativeTransform = RotationDirectionConvert * RelativeTransform;
FbxVector4 KeyTrans = Converter.ConvertToFbxPos(RelativeTransform.GetTranslation());
FbxVector4 KeyRot = Converter.ConvertToFbxRot(RelativeTransform.GetRotation().Euler());
FbxVector4 KeyScale = Converter.ConvertToFbxScale(RelativeTransform.GetScale3D());
FbxTime FbxTime;
if (GetExportOptions()->bExportLocalTime)
{
FbxTime.SetSecondDouble(DisplayRate.AsSeconds(LocalFrame));
}
// @todo: This code does not handle the root sequence having a different tick resolution than the local space, but we do
// not have that information here so we have to just assume they match. This should be improved so that we have
// all the information we need to do the right thing
else if (TOptional<FFrameTime> GlobalKeyTime = SequenceToRootTransform.TryTransformTime(LocalTime))
{
FbxTime.SetSecondDouble(TickResolution.AsSeconds(GlobalKeyTime.GetValue()));
}
else
{
// Doesn't map to a root time
continue;
}
FbxCurveRotX->KeySet(FbxCurveRotX->KeyAdd(FbxTime), FbxTime, KeyRot[0]);
FbxCurveRotY->KeySet(FbxCurveRotY->KeyAdd(FbxTime), FbxTime, KeyRot[1]);
FbxCurveRotZ->KeySet(FbxCurveRotZ->KeyAdd(FbxTime), FbxTime, KeyRot[2]);
}
FbxCurveRotX->KeyModifyEnd();
FbxCurveRotY->KeyModifyEnd();
FbxCurveRotZ->KeyModifyEnd();
}
}
void FFbxExporter::ExportLevelSequenceBaked3DTransformTrack(IAnimTrackAdapter& AnimTrackAdapter, FbxNode* FbxNode, IMovieScenePlayer* MovieScenePlayer, FMovieSceneSequenceIDRef InSequenceID, TArray<TWeakObjectPtr<UMovieScene3DTransformTrack> > TransformTracks, UObject* BoundObject, const TRange<FFrameNumber>& InPlaybackRange, const FMovieSceneSequenceTransform& RootToLocalTransform)
{
using namespace UE::MovieScene;
UMovieScene3DTransformTrack* TransformTrack = nullptr;
int32 NumSections = 0;
for (TWeakObjectPtr<UMovieScene3DTransformTrack> WeakTransformTrack : TransformTracks)
{
if (WeakTransformTrack.IsValid())
{
TransformTrack = WeakTransformTrack.Get();
NumSections += TransformTrack->GetAllSections().Num();
}
}
if (NumSections <= 0 || !TransformTrack)
{
return;
}
FbxAnimLayer* BaseLayer = AnimStack->GetMember<FbxAnimLayer>(0);
AActor* BoundActor = Cast<AActor>(BoundObject);
USceneComponent* BoundComponent = Cast<USceneComponent>(BoundObject);
const bool bIsCameraActor = BoundActor ? BoundActor->IsA(ACameraActor::StaticClass()) : BoundComponent ? BoundComponent->IsA(UCameraComponent::StaticClass()) : false;
const bool bIsLightActor = BoundActor ? BoundActor->IsA(ALight::StaticClass()) : BoundComponent ? BoundComponent->IsA(ULightComponent::StaticClass()) : false;
const bool bBakeRotations = bIsCameraActor || bIsLightActor;
USceneComponent* InterrogatedComponent = nullptr;
if (BoundComponent)
{
InterrogatedComponent = BoundComponent;
}
else if (BoundActor)
{
InterrogatedComponent = BoundActor->GetRootComponent();
}
if (bIsCameraActor)
{
if (InterrogatedComponent && InterrogatedComponent->IsA<UCameraComponent>())
{
// all set
}
else if (BoundActor && BoundActor->IsA(ACameraActor::StaticClass()))
{
ACameraActor* CameraActor = Cast<ACameraActor>(BoundActor);
InterrogatedComponent = CameraActor->GetCameraComponent();
}
}
if (!InterrogatedComponent)
{
UE_LOG(LogFbx, Warning, TEXT("Export transform track for %s failed because could not find suitable scene component"), *BoundObject->GetName());
return;
}
if (!FbxNode)
{
FbxNode = CreateNode(TransformTrack->GetDisplayName().ToString());
}
FFrameRate TickResolution = TransformTrack->GetTypedOuter<UMovieScene>()->GetTickResolution();
FFrameRate DisplayRate = TransformTrack->GetTypedOuter<UMovieScene>()->GetDisplayRate();
FbxAnimCurveNode* TranslationNode = FbxNode->LclTranslation.GetCurveNode(BaseLayer, true);
FbxAnimCurveNode* RotationNode = FbxNode->LclRotation.GetCurveNode(BaseLayer, true);
FbxAnimCurveNode* ScaleNode = FbxNode->LclScaling.GetCurveNode(BaseLayer, true);
FbxAnimCurve* FbxCurveTransX = FbxNode->LclTranslation.GetCurve(BaseLayer, FBXSDK_CURVENODE_COMPONENT_X, true);
FbxAnimCurve* FbxCurveTransY = FbxNode->LclTranslation.GetCurve(BaseLayer, FBXSDK_CURVENODE_COMPONENT_Y, true);
FbxAnimCurve* FbxCurveTransZ = FbxNode->LclTranslation.GetCurve(BaseLayer, FBXSDK_CURVENODE_COMPONENT_Z, true);
FbxAnimCurve* FbxCurveRotX = FbxNode->LclRotation.GetCurve(BaseLayer, FBXSDK_CURVENODE_COMPONENT_X, true);
FbxAnimCurve* FbxCurveRotY = FbxNode->LclRotation.GetCurve(BaseLayer, FBXSDK_CURVENODE_COMPONENT_Y, true);
FbxAnimCurve* FbxCurveRotZ = FbxNode->LclRotation.GetCurve(BaseLayer, FBXSDK_CURVENODE_COMPONENT_Z, true);
FbxAnimCurve* FbxCurveScaleX = FbxNode->LclScaling.GetCurve(BaseLayer, FBXSDK_CURVENODE_COMPONENT_X, true);
FbxAnimCurve* FbxCurveScaleY = FbxNode->LclScaling.GetCurve(BaseLayer, FBXSDK_CURVENODE_COMPONENT_Y, true);
FbxAnimCurve* FbxCurveScaleZ = FbxNode->LclScaling.GetCurve(BaseLayer, FBXSDK_CURVENODE_COMPONENT_Z, true);
FTransform RotationDirectionConvert;
if (bIsCameraActor)
{
FRotator Rotator = FFbxDataConverter::GetCameraRotation().GetInverse();
RotationDirectionConvert = FTransform(Rotator);
}
else if (bIsLightActor)
{
FRotator Rotator = FFbxDataConverter::GetLightRotation().GetInverse();
RotationDirectionConvert = FTransform(Rotator);
}
FbxCurveTransX->KeyModifyBegin();
FbxCurveTransY->KeyModifyBegin();
FbxCurveTransZ->KeyModifyBegin();
FbxCurveRotX->KeyModifyBegin();
FbxCurveRotY->KeyModifyBegin();
FbxCurveRotZ->KeyModifyBegin();
if (!bIsCameraActor)
{
FbxCurveScaleX->KeyModifyBegin();
FbxCurveScaleY->KeyModifyBegin();
FbxCurveScaleZ->KeyModifyBegin();
}
FMovieSceneInverseSequenceTransform LocalToRootTransform = RootToLocalTransform.Inverse();
TArray<FTransform> RelativeTransforms;
int32 LocalStartFrame = FFrameRate::TransformTime(FFrameTime(DiscreteInclusiveLower(InPlaybackRange)), TickResolution, DisplayRate).RoundToFrame().Value;
int32 AnimationLength = FFrameRate::TransformTime(FFrameTime(FFrameNumber(DiscreteSize(InPlaybackRange))), TickResolution, DisplayRate).RoundToFrame().Value + 1; // Add one so that we export a key for the end frame
const double SampleRate = 1.0/DisplayRate.AsDecimal();
for (int32 FrameNumber = LocalStartFrame; FrameNumber < LocalStartFrame + AnimationLength; ++FrameNumber)
{
const FFrameTime FrameTime = FFrameRate::TransformTime(FrameNumber, DisplayRate, TickResolution);
// This will call UpdateSkelPose on the skeletal mesh component to move bones based on animations in the matinee group
AnimTrackAdapter.UpdateAnimation(FrameNumber);
USceneComponent* Child = InterrogatedComponent;
while (Child)
{
if (USkeletalMeshComponent* ChildSkeletalMeshComponent = Cast<USkeletalMeshComponent>(Child))
{
ChildSkeletalMeshComponent->TickAnimation(SampleRate, false);
ChildSkeletalMeshComponent->RefreshBoneTransforms();
ChildSkeletalMeshComponent->RefreshFollowerComponents();
ChildSkeletalMeshComponent->UpdateComponentToWorld();
ChildSkeletalMeshComponent->FinalizeBoneTransform();
ChildSkeletalMeshComponent->MarkRenderTransformDirty();
ChildSkeletalMeshComponent->MarkRenderDynamicDataDirty();
}
if (Child->GetOwner())
{
Child->GetOwner()->Tick(SampleRate);
}
Child = Child->GetAttachParent();
}
// Get the relative transform for this component. This can be complicated because we don't export scene components in the hierarchy. For example,
// ParentActor
// ChildActor
// SceneComponent
// CameraComponent
// When exporting CameraComponent, we don't export SceneComponent, so we need to get CameraComponent's world transform relative to ParentActor
//
AActor* InterrogatedOwner = InterrogatedComponent->GetOwner();
AActor* AttachParentActor = InterrogatedOwner ? InterrogatedOwner->GetAttachParentActor() : nullptr;
FTransform ParentTransform = AttachParentActor ? AttachParentActor->GetTransform() : FTransform::Identity;
FTransform RelativeTransform = InterrogatedComponent->GetComponentToWorld().GetRelativeTransform(ParentTransform);
RelativeTransforms.Add(RelativeTransform);
}
// Reset
AnimTrackAdapter.UpdateAnimation(LocalStartFrame);
for (int32 TransformIndex = 0; TransformIndex < RelativeTransforms.Num(); ++TransformIndex)
{
FTransform RelativeTransform = RotationDirectionConvert * RelativeTransforms[TransformIndex];
FbxVector4 KeyTrans = Converter.ConvertToFbxPos(RelativeTransform.GetTranslation());
FbxVector4 KeyRot = Converter.ConvertToFbxRot(RelativeTransform.GetRotation().Euler());
FbxVector4 KeyScale = Converter.ConvertToFbxScale(RelativeTransform.GetScale3D());
const int32 CurrentFrame = LocalStartFrame + TransformIndex;
const FFrameTime LocalTime = FFrameRate::TransformTime(CurrentFrame, DisplayRate, TickResolution);
FbxTime FbxTime;
if (GetExportOptions()->bExportLocalTime)
{
FbxTime.SetSecondDouble(DisplayRate.AsSeconds(CurrentFrame));
}
// @todo: This code does not handle the root sequence having a different tick resolution than the local space, but we do
// not have that information here so we have to just assume they match. This should be improved so that we have
// all the information we need to do the right thing
else if (TOptional<FFrameTime> GlobalKeyTime = LocalToRootTransform.TryTransformTime(LocalTime))
{
FbxTime.SetSecondDouble(TickResolution.AsSeconds(GlobalKeyTime.GetValue()));
}
else
{
// Doesn't map to a root time
continue;
}
FbxCurveTransX->KeySet(FbxCurveTransX->KeyAdd(FbxTime), FbxTime, KeyTrans[0]);
FbxCurveTransY->KeySet(FbxCurveTransY->KeyAdd(FbxTime), FbxTime, KeyTrans[1]);
FbxCurveTransZ->KeySet(FbxCurveTransZ->KeyAdd(FbxTime), FbxTime, KeyTrans[2]);
FbxCurveRotX->KeySet(FbxCurveRotX->KeyAdd(FbxTime), FbxTime, KeyRot[0]);
FbxCurveRotY->KeySet(FbxCurveRotY->KeyAdd(FbxTime), FbxTime, KeyRot[1]);
FbxCurveRotZ->KeySet(FbxCurveRotZ->KeyAdd(FbxTime), FbxTime, KeyRot[2]);
if (!bIsCameraActor)
{
FbxCurveScaleX->KeySet(FbxCurveScaleX->KeyAdd(FbxTime), FbxTime, KeyScale[0]);
FbxCurveScaleY->KeySet(FbxCurveScaleY->KeyAdd(FbxTime), FbxTime, KeyScale[1]);
FbxCurveScaleZ->KeySet(FbxCurveScaleZ->KeyAdd(FbxTime), FbxTime, KeyScale[2]);
}
}
FbxCurveTransX->KeyModifyEnd();
FbxCurveTransY->KeyModifyEnd();
FbxCurveTransZ->KeyModifyEnd();
FbxCurveRotX->KeyModifyEnd();
FbxCurveRotY->KeyModifyEnd();
FbxCurveRotZ->KeyModifyEnd();
if (!bIsCameraActor)
{
FbxCurveScaleX->KeyModifyEnd();
FbxCurveScaleY->KeyModifyEnd();
FbxCurveScaleZ->KeyModifyEnd();
}
}
void FFbxExporter::ExportLevelSequenceTrackChannels( FbxNode* FbxNode, UMovieSceneTrack& Track, const TRange<FFrameNumber>& InPlaybackRange, const FMovieSceneSequenceTransform& RootToLocalTransform, bool bBakeBezierCurves)
{
// TODO: Support more than one section?
UMovieSceneSection* Section = Track.GetAllSections().Num() > 0 ? Track.GetAllSections()[0] : nullptr;
if (!Section)
{
return;
}
if (!FbxNode)
{
FbxNode = CreateNode(Track.GetDisplayName().ToString());
}
FbxCamera* FbxCamera = FbxNode->GetCamera();
FbxLight* FbxLight = FbxNode->GetLight();
FFrameRate TickResolution = Track.GetTypedOuter<UMovieScene>()->GetTickResolution();
const FName BoolChannelTypeName = FMovieSceneBoolChannel::StaticStruct()->GetFName();
const FName DoubleChannelTypeName = FMovieSceneDoubleChannel::StaticStruct()->GetFName();
const FName EnumChannelTypeName = FMovieSceneByteChannel::StaticStruct()->GetFName();
const FName FloatChannelTypeName = FMovieSceneFloatChannel::StaticStruct()->GetFName();
const FName IntegerChannelTypeName = FMovieSceneIntegerChannel::StaticStruct()->GetFName();
const FName StringChannelTypeName = FMovieSceneStringChannel::StaticStruct()->GetFName();
FMovieSceneChannelProxy& ChannelProxy = Section->GetChannelProxy();
for (const FMovieSceneChannelEntry& Entry : Section->GetChannelProxy().GetAllEntries())
{
const FName ChannelTypeName = Entry.GetChannelTypeName();
if (ChannelTypeName != BoolChannelTypeName &&
ChannelTypeName != DoubleChannelTypeName &&
ChannelTypeName != EnumChannelTypeName &&
ChannelTypeName != FloatChannelTypeName &&
ChannelTypeName != IntegerChannelTypeName &&
ChannelTypeName != StringChannelTypeName)
{
continue;
}
TArrayView<FMovieSceneChannel* const> Channels = Entry.GetChannels();
TArrayView<const FMovieSceneChannelMetaData> AllMetaData = Entry.GetMetaData();
for (int32 Index = 0; Index < Channels.Num(); ++Index)
{
FMovieSceneChannelHandle Channel = ChannelProxy.MakeHandle(ChannelTypeName, Index);
FMovieSceneBoolChannel* BoolChannel = Entry.GetChannelTypeName() == BoolChannelTypeName ? Channel.Cast<FMovieSceneBoolChannel>().Get() : nullptr;
FMovieSceneDoubleChannel* DoubleChannel = Entry.GetChannelTypeName() == DoubleChannelTypeName ? Channel.Cast<FMovieSceneDoubleChannel>().Get() : nullptr;
FMovieSceneByteChannel* EnumChannel = Entry.GetChannelTypeName() == EnumChannelTypeName ? Channel.Cast<FMovieSceneByteChannel>().Get() : nullptr;
FMovieSceneFloatChannel* FloatChannel = Entry.GetChannelTypeName() == FloatChannelTypeName ? Channel.Cast<FMovieSceneFloatChannel>().Get() : nullptr;
FMovieSceneIntegerChannel* IntegerChannel = Entry.GetChannelTypeName() == IntegerChannelTypeName ? Channel.Cast<FMovieSceneIntegerChannel>().Get() : nullptr;
FMovieSceneStringChannel* StringChannel = Entry.GetChannelTypeName() == StringChannelTypeName ? Channel.Cast<FMovieSceneStringChannel>().Get() : nullptr;
if (!BoolChannel && !DoubleChannel && !EnumChannel && !FloatChannel && !IntegerChannel && !StringChannel)
{
continue;
}
const FMovieSceneChannelMetaData& MetaData = AllMetaData[Index];
FText Name = FText::FromName(MetaData.Name);
FbxProperty Property;
FString PropertyName = MetaData.Name.IsNone() ? Track.GetTrackName().ToString() : MetaData.Name.ToString();
bool IsFoV = false;
// most properties are created as user property, only FOV of camera in FBX supports animation
if (PropertyName == "Intensity" && FbxLight)
{
Property = FbxLight->Intensity;
}
else if (PropertyName == "FalloffExponent")
{
Property = FbxNode->FindProperty("UE_FalloffExponent", false);
}
else if (PropertyName == "AttenuationRadius")
{
Property = FbxNode->FindProperty("UE_Radius", false);
}
else if (PropertyName == "FieldOfView" && FbxCamera)
{
Property = FbxCamera->FieldOfView;
IsFoV = true;
}
else if (PropertyName == "FOVAngle" && FbxCamera)
{
Property = FbxCamera->FocalLength;
IsFoV = true;
}
else if (PropertyName == "CurrentFocalLength" && FbxCamera)
{
Property = FbxCamera->FocalLength;
}
else if (PropertyName == "AspectRatio")
{
Property = FbxNode->FindProperty("UE_AspectRatio", false);
}
else if (PropertyName == "MotionBlur_Amount")
{
Property = FbxNode->FindProperty("UE_MotionBlur_Amount", false);
}
else if ( PropertyName == "FocusSettings.ManualFocusDistance" && FbxCamera )
{
Property = FbxCamera->FocusDistance;
}
else if(PropertyName == "bUseTemperature")
{
Property = FbxNode->FindProperty("UE_UseTemperature", false);
}
else if(PropertyName == "IntensityUnits")
{
Property = FbxNode->FindProperty("UE_IntensityUnits", false);
}
else if(PropertyName == "Filmback.SensorAspectRatio" && FbxCamera)
{
Property = FbxCamera->FilmAspectRatio;
}
else if(PropertyName == "Filmback.SensorHeight" && FbxCamera)
{
Property = FbxCamera->FilmHeight;
}
else if(PropertyName == "Filmback.SensorWidth" && FbxCamera)
{
Property = FbxCamera->FilmWidth;
}
if (Property == 0)
{
if(BoolChannel)
{
CreateAnimatableUserProperty(FbxNode, BoolChannel->GetDefault().Get(false), TCHAR_TO_UTF8(*PropertyName), TCHAR_TO_UTF8(*PropertyName), FbxBoolDT);
}
else if (DoubleChannel)
{
CreateAnimatableUserProperty(FbxNode, DoubleChannel->GetDefault().Get(MAX_flt), TCHAR_TO_UTF8(*PropertyName), TCHAR_TO_UTF8(*PropertyName));
}
else if (EnumChannel)
{
CreateAnimatableUserProperty(FbxNode, EnumChannel->GetDefault().Get(0), TCHAR_TO_UTF8(*PropertyName), TCHAR_TO_UTF8(*PropertyName), FbxEnumDT);
}
else if (FloatChannel)
{
CreateAnimatableUserProperty(FbxNode, FloatChannel->GetDefault().Get(MAX_flt), TCHAR_TO_UTF8(*PropertyName), TCHAR_TO_UTF8(*PropertyName));
}
else if (IntegerChannel)
{
CreateAnimatableUserProperty(FbxNode, IntegerChannel->GetDefault().Get(0), TCHAR_TO_UTF8(*PropertyName), TCHAR_TO_UTF8(*PropertyName), FbxIntDT);
}
else if (StringChannel)
{
FbxString FbxValueString(TCHAR_TO_UTF8(*StringChannel->GetDefault().Get(TEXT(""))));
CreateAnimatableUserProperty(FbxNode, FbxValueString, TCHAR_TO_UTF8(*PropertyName), TCHAR_TO_UTF8(*PropertyName), FbxStringDT);
}
Property = FbxNode->FindProperty(TCHAR_TO_UTF8(*PropertyName), false);
}
if (Property == 0)
{
continue;
}
// Ensure that the property is animatable so that GetCurveNode succeeds
Property.ModifyFlag(FbxPropertyFlags::eAnimatable, true);
FbxAnimCurve* AnimCurve = FbxAnimCurve::Create(Scene, "");
FbxAnimCurveNode* CurveNode = Property.GetCurveNode(true);
if (!CurveNode)
{
continue;
}
if (DoubleChannel)
{
CurveNode->SetChannelValue<double>(0U, DoubleChannel->GetDefault().Get(MAX_dbl));
CurveNode->ConnectToChannel(AnimCurve, 0U);
if (bBakeBezierCurves)
{
ExportBezierChannelToFbxCurveBaked(*AnimCurve, *DoubleChannel, TickResolution, &Track, IsFoV ? ERichCurveValueMode::Fov : ERichCurveValueMode::Default, false, RootToLocalTransform);
}
else
{
ExportChannelToFbxCurve(*AnimCurve, *DoubleChannel, TickResolution, IsFoV ? ERichCurveValueMode::Fov : ERichCurveValueMode::Default, false, RootToLocalTransform);
}
}
else if (FloatChannel)
{
CurveNode->SetChannelValue<double>(0U, FloatChannel->GetDefault().Get(MAX_flt));
CurveNode->ConnectToChannel(AnimCurve, 0U);
if (bBakeBezierCurves)
{
ExportBezierChannelToFbxCurveBaked(*AnimCurve, *FloatChannel, TickResolution, &Track, IsFoV ? ERichCurveValueMode::Fov : ERichCurveValueMode::Default, false, RootToLocalTransform);
}
else
{
ExportChannelToFbxCurve(*AnimCurve, *FloatChannel, TickResolution, IsFoV ? ERichCurveValueMode::Fov : ERichCurveValueMode::Default, false, RootToLocalTransform);
}
}
else if (IntegerChannel)
{
CurveNode->SetChannelValue<int32>(0U, IntegerChannel->GetDefault().Get(0));
CurveNode->ConnectToChannel(AnimCurve, 0U);
ExportChannelToFbxCurve(*AnimCurve, *IntegerChannel, TickResolution, RootToLocalTransform);
}
else if (BoolChannel)
{
CurveNode->SetChannelValue<bool>(0U, BoolChannel->GetDefault().Get(false));
CurveNode->ConnectToChannel(AnimCurve, 0U);
ExportChannelToFbxCurve(*AnimCurve, *BoolChannel, TickResolution, RootToLocalTransform);
}
else if (EnumChannel)
{
CurveNode->SetChannelValue<uint8>(0U, EnumChannel->GetDefault().Get(0U));
CurveNode->ConnectToChannel(AnimCurve, 0U);
ExportChannelToFbxCurve(*AnimCurve, *EnumChannel, TickResolution, RootToLocalTransform);
}
}
}
}
/**
* Finds the given actor in the already-exported list of structures
*/
FbxNode* FFbxExporter::FindActor(AActor* Actor, INodeNameAdapter* NodeNameAdapter)
{
if (NodeNameAdapter)
{
FbxNode* ActorNode = NodeNameAdapter->GetFbxNode(Actor);
if (ActorNode)
{
return ActorNode;
}
}
if (FbxActors.Find(Actor))
{
return *FbxActors.Find(Actor);
}
else
{
return NULL;
}
}
FbxNode* FFbxExporter::CreateNode(const FString& NodeName)
{
FbxNode* FbxNode = FbxNode::Create(Scene, TCHAR_TO_UTF8(*NodeName));
Scene->GetRootNode()->AddChild(FbxNode);
return FbxNode;
}
bool FFbxExporter::FindSkeleton(USkeletalMeshComponent* SkelComp, TArray<FbxNode*>& BoneNodes, INodeNameAdapter* NodeNameAdapter)
{
if (NodeNameAdapter)
{
FbxNode* SkelRoot = NodeNameAdapter->GetFbxNode(SkelComp);
if (SkelRoot)
{
BoneNodes.Empty();
GetSkeleton(SkelRoot, BoneNodes);
return true;
}
}
FbxNode** SkelRoot = FbxSkeletonRoots.Find(SkelComp);
if (SkelRoot)
{
BoneNodes.Empty();
GetSkeleton(*SkelRoot, BoneNodes);
return true;
}
return false;
}
/**
* Determines the UVS to weld when exporting a Static Mesh
*
* @param VertRemap Index of each UV (out)
* @param UniqueVerts
*/
void DetermineUVsToWeld(TArray<int32>& VertRemap, TArray<int32>& UniqueVerts, const FStaticMeshVertexBuffer& VertexBuffer, int32 TexCoordSourceIndex)
{
const int32 VertexCount = VertexBuffer.GetNumVertices();
// Maps unreal verts to reduced list of verts
VertRemap.Empty(VertexCount);
VertRemap.AddUninitialized(VertexCount);
// List of Unreal Verts to keep
UniqueVerts.Empty(VertexCount);
// Combine matching verts using hashed search to maintain good performance
TMap<FVector2D,int32> HashedVerts;
for(int32 Vertex=0; Vertex < VertexCount; Vertex++)
{
const FVector2D& PositionA = FVector2D(VertexBuffer.GetVertexUV(Vertex,TexCoordSourceIndex));
const int32* FoundIndex = HashedVerts.Find(PositionA);
if ( !FoundIndex )
{
int32 NewIndex = UniqueVerts.Add(Vertex);
VertRemap[Vertex] = NewIndex;
HashedVerts.Add(PositionA, NewIndex);
}
else
{
VertRemap[Vertex] = *FoundIndex;
}
}
}
void DetermineVertsToWeld(TArray<int32>& VertRemap, TArray<int32>& UniqueVerts, const FStaticMeshLODResources& RenderMesh)
{
const int32 VertexCount = RenderMesh.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices();
// Maps unreal verts to reduced list of verts
VertRemap.Empty(VertexCount);
VertRemap.AddUninitialized(VertexCount);
// List of Unreal Verts to keep
UniqueVerts.Empty(VertexCount);
// Combine matching verts using hashed search to maintain good performance
TMap<FVector,int32> HashedVerts;
for(int32 a=0; a < VertexCount; a++)
{
const FVector& PositionA = (FVector)RenderMesh.VertexBuffers.PositionVertexBuffer.VertexPosition(a);
const int32* FoundIndex = HashedVerts.Find(PositionA);
if ( !FoundIndex )
{
int32 NewIndex = UniqueVerts.Add(a);
VertRemap[a] = NewIndex;
HashedVerts.Add(PositionA, NewIndex);
}
else
{
VertRemap[a] = *FoundIndex;
}
}
}
class FCollisionFbxExporter
{
public:
FCollisionFbxExporter(const UStaticMesh *StaticMeshToExport, FbxMesh* ExportMesh, int32 ActualMatIndexToExport)
{
BoxPositions[0] = FVector(-1, -1, +1);
BoxPositions[1] = FVector(-1, +1, +1);
BoxPositions[2] = FVector(+1, +1, +1);
BoxPositions[3] = FVector(+1, -1, +1);
BoxFaceRotations[0] = FRotator(0, 0, 0);
BoxFaceRotations[1] = FRotator(90.f, 0, 0);
BoxFaceRotations[2] = FRotator(-90.f, 0, 0);
BoxFaceRotations[3] = FRotator(0, 0, 90.f);
BoxFaceRotations[4] = FRotator(0, 0, -90.f);
BoxFaceRotations[5] = FRotator(180.f, 0, 0);
DrawCollisionSides = 16;
SpherNumSides = DrawCollisionSides;
SphereNumRings = DrawCollisionSides / 2;
SphereNumVerts = (SpherNumSides + 1) * (SphereNumRings + 1);
CapsuleNumSides = DrawCollisionSides;
CapsuleNumRings = (DrawCollisionSides / 2) + 1;
CapsuleNumVerts = (CapsuleNumSides + 1) * (CapsuleNumRings + 1);
CurrentVertexOffset = 0;
StaticMesh = StaticMeshToExport;
Mesh = ExportMesh;
ActualMatIndex = ActualMatIndexToExport;
}
void ExportCollisions()
{
const FKAggregateGeom& AggGeo = StaticMesh->GetBodySetup()->AggGeom;
int32 VerticeNumber = 0;
for (const FKConvexElem &ConvexElem : AggGeo.ConvexElems)
{
VerticeNumber += GetConvexVerticeNumber(ConvexElem);
}
for (const FKBoxElem &BoxElem : AggGeo.BoxElems)
{
VerticeNumber += GetBoxVerticeNumber();
}
for (const FKSphereElem &SphereElem : AggGeo.SphereElems)
{
VerticeNumber += GetSphereVerticeNumber();
}
for (const FKSphylElem &CapsuleElem : AggGeo.SphylElems)
{
VerticeNumber += GetCapsuleVerticeNumber();
}
Mesh->InitControlPoints(VerticeNumber);
ControlPoints = Mesh->GetControlPoints();
CurrentVertexOffset = 0;
//////////////////////////////////////////////////////////////////////////
// Set all vertex
for (const FKConvexElem &ConvexElem : AggGeo.ConvexElems)
{
AddConvexVertex(ConvexElem);
}
for (const FKBoxElem &BoxElem : AggGeo.BoxElems)
{
AddBoxVertex(BoxElem);
}
for (const FKSphereElem &SphereElem : AggGeo.SphereElems)
{
AddSphereVertex(SphereElem);
}
for (const FKSphylElem &CapsuleElem : AggGeo.SphylElems)
{
AddCapsuleVertex(CapsuleElem);
}
// Set the normals on Layer 0.
FbxLayer* Layer = Mesh->GetLayer(0);
if (Layer == nullptr)
{
Mesh->CreateLayer();
Layer = Mesh->GetLayer(0);
}
// Create and fill in the per-face-vertex normal data source.
LayerElementNormal = FbxLayerElementNormal::Create(Mesh, "");
// Set the normals per polygon instead of storing normals on positional control points
LayerElementNormal->SetMappingMode(FbxLayerElement::eByPolygonVertex);
// Set the normal values for every polygon vertex.
LayerElementNormal->SetReferenceMode(FbxLayerElement::eDirect);
//////////////////////////////////////////////////////////////////////////
//Set the Normals
for (const FKConvexElem &ConvexElem : AggGeo.ConvexElems)
{
AddConvexNormals(ConvexElem);
}
for (const FKBoxElem &BoxElem : AggGeo.BoxElems)
{
AddBoxNormal(BoxElem);
}
int32 SphereIndex = 0;
for (const FKSphereElem &SphereElem : AggGeo.SphereElems)
{
AddSphereNormals(SphereElem, SphereIndex);
SphereIndex++;
}
int32 CapsuleIndex = 0;
for (const FKSphylElem &CapsuleElem : AggGeo.SphylElems)
{
AddCapsuleNormals(CapsuleElem, CapsuleIndex);
CapsuleIndex++;
}
Layer->SetNormals(LayerElementNormal);
//////////////////////////////////////////////////////////////////////////
// Set polygons
// Build list of polygon re-used multiple times to lookup Normals, UVs, other per face vertex information
CurrentVertexOffset = 0; //Reset the current VertexCount
for (const FKConvexElem &ConvexElem : AggGeo.ConvexElems)
{
AddConvexPolygon(ConvexElem);
}
for (const FKBoxElem &BoxElem : AggGeo.BoxElems)
{
AddBoxPolygons();
}
for (const FKSphereElem &SphereElem : AggGeo.SphereElems)
{
AddSpherePolygons();
}
for (const FKSphylElem &CapsuleElem : AggGeo.SphylElems)
{
AddCapsulePolygons();
}
//////////////////////////////////////////////////////////////////////////
//Free the sphere resources
for (FDynamicMeshVertex* DynamicMeshVertex : SpheresVerts)
{
FMemory::Free(DynamicMeshVertex);
}
SpheresVerts.Empty();
//////////////////////////////////////////////////////////////////////////
//Free the capsule resources
for (FDynamicMeshVertex* DynamicMeshVertex : CapsuleVerts)
{
FMemory::Free(DynamicMeshVertex);
}
CapsuleVerts.Empty();
}
private:
uint32 GetConvexVerticeNumber(const FKConvexElem &ConvexElem)
{
return ConvexElem.VertexData.Num();
}
uint32 GetBoxVerticeNumber() { return 24; }
uint32 GetSphereVerticeNumber() { return SphereNumVerts; }
uint32 GetCapsuleVerticeNumber() { return CapsuleNumVerts; }
void AddConvexVertex(const FKConvexElem &ConvexElem)
{
const TArray<FVector>& VertexArray = ConvexElem.VertexData;
for (int32 PosIndex = 0; PosIndex < VertexArray.Num(); ++PosIndex)
{
FVector Position = VertexArray[PosIndex];
ControlPoints[CurrentVertexOffset + PosIndex] = FbxVector4(Position.X, -Position.Y, Position.Z);
}
CurrentVertexOffset += VertexArray.Num();
}
void AddConvexNormals(const FKConvexElem &ConvexElem)
{
const auto& ConvexMesh = ConvexElem.GetChaosConvexMesh();
if (!ConvexMesh.IsValid())
{
return;
}
const TArray<Chaos::FConvex::FPlaneType>& Faces = ConvexMesh->GetFaces();
for (int32 PolyIndex = 0; PolyIndex < Faces.Num(); ++PolyIndex)
{
FVector Normal = (FVector)Faces[PolyIndex].Normal().GetSafeNormal();
FbxVector4 FbxNormal = FbxVector4(Normal.X, -Normal.Y, Normal.Z);
// add vertices
for (int32 j = 0; j < 3; ++j)
{
LayerElementNormal->GetDirectArray().Add(FbxNormal);
}
}
}
void AddConvexPolygon(const FKConvexElem &ConvexElem)
{
const auto& ConvexMesh = ConvexElem.GetChaosConvexMesh();
if (!ConvexMesh.IsValid())
{
return;
}
TArray<int32> IndexData(ConvexElem.IndexData);
if (IndexData.Num() == 0)
{
IndexData = ConvexElem.GetChaosConvexIndices();
}
check(IndexData.Num() % 3 == 0);
for (int32 VertexIndex = 0; VertexIndex < IndexData.Num(); VertexIndex += 3)
{
Mesh->BeginPolygon(ActualMatIndex);
Mesh->AddPolygon(CurrentVertexOffset + IndexData[VertexIndex]);
Mesh->AddPolygon(CurrentVertexOffset + IndexData[VertexIndex + 1]);
Mesh->AddPolygon(CurrentVertexOffset + IndexData[VertexIndex + 2]);
Mesh->EndPolygon();
}
CurrentVertexOffset += ConvexElem.VertexData.Num();
}
void AddBoxVertex(const FKBoxElem &BoxElem)
{
FScaleMatrix ExtendScale(0.5f * FVector(BoxElem.X, BoxElem.Y, BoxElem.Z));
// Calculate verts for a face pointing down Z
FMatrix BoxTransform = BoxElem.GetTransform().ToMatrixWithScale();
for (int32 f = 0; f < 6; f++)
{
FMatrix FaceTransform = FRotationMatrix(BoxFaceRotations[f])*ExtendScale*BoxTransform;
for (int32 VertexIndex = 0; VertexIndex < 4; VertexIndex++)
{
FVector4 VertexPosition = FaceTransform.TransformPosition(BoxPositions[VertexIndex]);
ControlPoints[CurrentVertexOffset + VertexIndex] = FbxVector4(VertexPosition.X, -VertexPosition.Y, VertexPosition.Z);
}
CurrentVertexOffset += 4;
}
}
void AddBoxNormal(const FKBoxElem &BoxElem)
{
FScaleMatrix ExtendScale(0.5f * FVector(BoxElem.X, BoxElem.Y, BoxElem.Z));
FMatrix BoxTransform = BoxElem.GetTransform().ToMatrixWithScale();
for (int32 f = 0; f < 6; f++)
{
FMatrix FaceTransform = FRotationMatrix(BoxFaceRotations[f])*ExtendScale*BoxTransform;
FVector4 TangentZ = FaceTransform.TransformVector(FVector(0, 0, 1));
FbxVector4 FbxNormal = FbxVector4(TangentZ.X, -TangentZ.Y, TangentZ.Z);
FbxNormal.Normalize();
for (int32 VertexIndex = 0; VertexIndex < 4; VertexIndex++)
{
LayerElementNormal->GetDirectArray().Add(FbxNormal);
}
}
}
void AddBoxPolygons()
{
for (int32 f = 0; f < 6; f++)
{
Mesh->BeginPolygon(ActualMatIndex);
for (int32 VertexIndex = 0; VertexIndex < 4; VertexIndex++)
{
const uint32 VertIndex = CurrentVertexOffset + VertexIndex;
Mesh->AddPolygon(VertIndex);
}
Mesh->EndPolygon();
CurrentVertexOffset += 4;
}
}
void AddSphereVertex(const FKSphereElem &SphereElem)
{
FMatrix SphereTransform = FScaleMatrix(SphereElem.Radius * FVector(1.0f)) * SphereElem.GetTransform().ToMatrixWithScale();
FDynamicMeshVertex* Verts = (FDynamicMeshVertex*)FMemory::Malloc(SphereNumVerts * sizeof(FDynamicMeshVertex));
// Calculate verts for one arc
FDynamicMeshVertex* ArcVerts = (FDynamicMeshVertex*)FMemory::Malloc((SphereNumRings + 1) * sizeof(FDynamicMeshVertex));
for (int32 i = 0; i < SphereNumRings + 1; i++)
{
FDynamicMeshVertex* ArcVert = &ArcVerts[i];
float angle = ((float)i / SphereNumRings) * PI;
// Note- unit sphere, so position always has mag of one. We can just use it for normal!
ArcVert->Position.X = 0.0f;
ArcVert->Position.Y = FMath::Sin(angle);
ArcVert->Position.Z = FMath::Cos(angle);
ArcVert->SetTangents(
FVector3f(1, 0, 0),
FVector3f(0.0f, -ArcVert->Position.Z, ArcVert->Position.Y),
ArcVert->Position
);
}
// Then rotate this arc SpherNumSides+1 times.
for (int32 s = 0; s < SpherNumSides + 1; s++)
{
FRotator3f ArcRotator(0, 360.f * (float)s / SpherNumSides, 0);
FRotationMatrix44f ArcRot(ArcRotator);
for (int32 v = 0; v < SphereNumRings + 1; v++)
{
int32 VIx = (SphereNumRings + 1)*s + v;
Verts[VIx].Position = ArcRot.TransformPosition(ArcVerts[v].Position);
Verts[VIx].SetTangents(
ArcRot.TransformVector(ArcVerts[v].TangentX.ToFVector3f()),
ArcRot.TransformVector(ArcVerts[v].GetTangentY()),
ArcRot.TransformVector(ArcVerts[v].TangentZ.ToFVector3f())
);
}
}
// Add all of the vertices we generated to the mesh builder.
for (int32 VertexIndex = 0; VertexIndex < SphereNumVerts; VertexIndex++)
{
FVector Position = (FVector)SphereTransform.TransformPosition((FVector)Verts[VertexIndex].Position);
ControlPoints[CurrentVertexOffset + VertexIndex] = FbxVector4(Position.X, -Position.Y, Position.Z);
}
CurrentVertexOffset += SphereNumVerts;
// Free our local copy of arc verts
FMemory::Free(ArcVerts);
SpheresVerts.Add(Verts);
}
void AddSphereNormals(const FKSphereElem &SphereElem, int32 SphereIndex)
{
FMatrix SphereTransform = FScaleMatrix(SphereElem.Radius * FVector(1.0f)) * SphereElem.GetTransform().ToMatrixWithScale();
for (int32 s = 0; s < SpherNumSides; s++)
{
int32 a0start = (s + 0) * (SphereNumRings + 1);
int32 a1start = (s + 1) * (SphereNumRings + 1);
for (int32 r = 0; r < SphereNumRings; r++)
{
if (r != 0)
{
int32 indexV = a0start + r + 0;
FVector TangentZ = SphereTransform.TransformVector(SpheresVerts[SphereIndex][indexV].TangentZ.ToFVector());
FbxVector4 FbxNormal = FbxVector4(TangentZ.X, -TangentZ.Y, TangentZ.Z);
FbxNormal.Normalize();
LayerElementNormal->GetDirectArray().Add(FbxNormal);
indexV = a1start + r + 0;
TangentZ = SphereTransform.TransformVector(SpheresVerts[SphereIndex][indexV].TangentZ.ToFVector());
FbxNormal = FbxVector4(TangentZ.X, -TangentZ.Y, TangentZ.Z);
FbxNormal.Normalize();
LayerElementNormal->GetDirectArray().Add(FbxNormal);
indexV = a0start + r + 1;
TangentZ = SphereTransform.TransformVector(SpheresVerts[SphereIndex][indexV].TangentZ.ToFVector());
FbxNormal = FbxVector4(TangentZ.X, -TangentZ.Y, TangentZ.Z);
FbxNormal.Normalize();
LayerElementNormal->GetDirectArray().Add(FbxNormal);
}
if (r != SphereNumRings - 1)
{
int32 indexV = a1start + r + 0;
FVector TangentZ = SphereTransform.TransformVector(SpheresVerts[SphereIndex][indexV].TangentZ.ToFVector());
FbxVector4 FbxNormal = FbxVector4(TangentZ.X, -TangentZ.Y, TangentZ.Z);
FbxNormal.Normalize();
LayerElementNormal->GetDirectArray().Add(FbxNormal);
indexV = a1start + r + 1;
TangentZ = SphereTransform.TransformVector(SpheresVerts[SphereIndex][indexV].TangentZ.ToFVector());
FbxNormal = FbxVector4(TangentZ.X, -TangentZ.Y, TangentZ.Z);
FbxNormal.Normalize();
LayerElementNormal->GetDirectArray().Add(FbxNormal);
indexV = a0start + r + 1;
TangentZ = SphereTransform.TransformVector(SpheresVerts[SphereIndex][indexV].TangentZ.ToFVector());
FbxNormal = FbxVector4(TangentZ.X, -TangentZ.Y, TangentZ.Z);
FbxNormal.Normalize();
LayerElementNormal->GetDirectArray().Add(FbxNormal);
}
}
}
}
void AddSpherePolygons()
{
for (int32 s = 0; s < SpherNumSides; s++)
{
int32 a0start = (s + 0) * (SphereNumRings + 1);
int32 a1start = (s + 1) * (SphereNumRings + 1);
for (int32 r = 0; r < SphereNumRings; r++)
{
if (r != 0)
{
Mesh->BeginPolygon(ActualMatIndex);
Mesh->AddPolygon(CurrentVertexOffset + a0start + r + 0);
Mesh->AddPolygon(CurrentVertexOffset + a1start + r + 0);
Mesh->AddPolygon(CurrentVertexOffset + a0start + r + 1);
Mesh->EndPolygon();
}
if (r != SphereNumRings - 1)
{
Mesh->BeginPolygon(ActualMatIndex);
Mesh->AddPolygon(CurrentVertexOffset + a1start + r + 0);
Mesh->AddPolygon(CurrentVertexOffset + a1start + r + 1);
Mesh->AddPolygon(CurrentVertexOffset + a0start + r + 1);
Mesh->EndPolygon();
}
}
}
CurrentVertexOffset += SphereNumVerts;
}
void AddCapsuleVertex(const FKSphylElem &CapsuleElem)
{
FMatrix CapsuleTransform = CapsuleElem.GetTransform().ToMatrixWithScale();
float Length = CapsuleElem.Length;
float Radius = CapsuleElem.Radius;
FDynamicMeshVertex* Verts = (FDynamicMeshVertex*)FMemory::Malloc(CapsuleNumVerts * sizeof(FDynamicMeshVertex));
// Calculate verts for one arc
FDynamicMeshVertex* ArcVerts = (FDynamicMeshVertex*)FMemory::Malloc((CapsuleNumRings + 1) * sizeof(FDynamicMeshVertex));
for (int32 RingIdx = 0; RingIdx < CapsuleNumRings + 1; RingIdx++)
{
FDynamicMeshVertex* ArcVert = &ArcVerts[RingIdx];
float Angle;
float ZOffset;
if (RingIdx <= DrawCollisionSides / 4)
{
Angle = ((float)RingIdx / (CapsuleNumRings - 1)) * PI;
ZOffset = 0.5 * Length;
}
else
{
Angle = ((float)(RingIdx - 1) / (CapsuleNumRings - 1)) * PI;
ZOffset = -0.5 * Length;
}
// Note- unit sphere, so position always has mag of one. We can just use it for normal!
FVector SpherePos;
SpherePos.X = 0.0f;
SpherePos.Y = Radius * FMath::Sin(Angle);
SpherePos.Z = Radius * FMath::Cos(Angle);
ArcVert->Position = (FVector3f)SpherePos + FVector3f(0, 0, ZOffset);
ArcVert->SetTangents(
FVector3f(1, 0, 0),
FVector3f(0.0f, -SpherePos.Z, SpherePos.Y),
(FVector3f)SpherePos
);
}
// Then rotate this arc NumSides+1 times.
for (int32 SideIdx = 0; SideIdx < CapsuleNumSides + 1; SideIdx++)
{
const FRotator3f ArcRotator(0, 360.f * ((float)SideIdx / CapsuleNumSides), 0);
const FRotationMatrix44f ArcRot(ArcRotator);
for (int32 VertIdx = 0; VertIdx < CapsuleNumRings + 1; VertIdx++)
{
int32 VIx = (CapsuleNumRings + 1)*SideIdx + VertIdx;
Verts[VIx].Position = ArcRot.TransformPosition(ArcVerts[VertIdx].Position);
Verts[VIx].SetTangents(
ArcRot.TransformVector(ArcVerts[VertIdx].TangentX.ToFVector3f()),
ArcRot.TransformVector(ArcVerts[VertIdx].GetTangentY()),
ArcRot.TransformVector(ArcVerts[VertIdx].TangentZ.ToFVector3f())
);
}
}
// Add all of the vertices we generated to the mesh builder.
for (int32 VertexIndex = 0; VertexIndex < CapsuleNumVerts; VertexIndex++)
{
FVector Position = (FVector)CapsuleTransform.TransformPosition((FVector)Verts[VertexIndex].Position);
ControlPoints[CurrentVertexOffset + VertexIndex] = FbxVector4(Position.X, -Position.Y, Position.Z);
}
CurrentVertexOffset += CapsuleNumVerts;
// Free our local copy of arc verts
FMemory::Free(ArcVerts);
CapsuleVerts.Add(Verts);
}
void AddCapsuleNormals(const FKSphylElem &CapsuleElem, int32 CapsuleIndex)
{
FMatrix CapsuleTransform = CapsuleElem.GetTransform().ToMatrixWithScale();
// Add all of the triangles to the mesh.
for (int32 SideIdx = 0; SideIdx < CapsuleNumSides; SideIdx++)
{
const int32 a0start = (SideIdx + 0) * (CapsuleNumRings + 1);
const int32 a1start = (SideIdx + 1) * (CapsuleNumRings + 1);
for (int32 RingIdx = 0; RingIdx < CapsuleNumRings; RingIdx++)
{
if (RingIdx != 0)
{
int32 indexV = a0start + RingIdx + 0;
FVector TangentZ = CapsuleTransform.TransformVector(CapsuleVerts[CapsuleIndex][indexV].TangentZ.ToFVector());
FbxVector4 FbxNormal = FbxVector4(TangentZ.X, -TangentZ.Y, TangentZ.Z);
FbxNormal.Normalize();
LayerElementNormal->GetDirectArray().Add(FbxNormal);
indexV = a1start + RingIdx + 0;
TangentZ = CapsuleTransform.TransformVector(CapsuleVerts[CapsuleIndex][indexV].TangentZ.ToFVector());
FbxNormal = FbxVector4(TangentZ.X, -TangentZ.Y, TangentZ.Z);
FbxNormal.Normalize();
LayerElementNormal->GetDirectArray().Add(FbxNormal);
indexV = a0start + RingIdx + 1;
TangentZ = CapsuleTransform.TransformVector(CapsuleVerts[CapsuleIndex][indexV].TangentZ.ToFVector());
FbxNormal = FbxVector4(TangentZ.X, -TangentZ.Y, TangentZ.Z);
FbxNormal.Normalize();
LayerElementNormal->GetDirectArray().Add(FbxNormal);
}
if (RingIdx != CapsuleNumRings - 1)
{
int32 indexV = a1start + RingIdx + 0;
FVector TangentZ = CapsuleTransform.TransformVector(CapsuleVerts[CapsuleIndex][indexV].TangentZ.ToFVector());
FbxVector4 FbxNormal = FbxVector4(TangentZ.X, -TangentZ.Y, TangentZ.Z);
FbxNormal.Normalize();
LayerElementNormal->GetDirectArray().Add(FbxNormal);
indexV = a1start + RingIdx + 1;
TangentZ = CapsuleTransform.TransformVector(CapsuleVerts[CapsuleIndex][indexV].TangentZ.ToFVector());
FbxNormal = FbxVector4(TangentZ.X, -TangentZ.Y, TangentZ.Z);
FbxNormal.Normalize();
LayerElementNormal->GetDirectArray().Add(FbxNormal);
indexV = a0start + RingIdx + 1;
TangentZ = CapsuleTransform.TransformVector(CapsuleVerts[CapsuleIndex][indexV].TangentZ.ToFVector());
FbxNormal = FbxVector4(TangentZ.X, -TangentZ.Y, TangentZ.Z);
FbxNormal.Normalize();
LayerElementNormal->GetDirectArray().Add(FbxNormal);
}
}
}
}
void AddCapsulePolygons()
{
// Add all of the triangles to the mesh.
for (int32 SideIdx = 0; SideIdx < CapsuleNumSides; SideIdx++)
{
const int32 a0start = (SideIdx + 0) * (CapsuleNumRings + 1);
const int32 a1start = (SideIdx + 1) * (CapsuleNumRings + 1);
for (int32 RingIdx = 0; RingIdx < CapsuleNumRings; RingIdx++)
{
if (RingIdx != 0)
{
Mesh->BeginPolygon(ActualMatIndex);
Mesh->AddPolygon(CurrentVertexOffset + a0start + RingIdx + 0);
Mesh->AddPolygon(CurrentVertexOffset + a1start + RingIdx + 0);
Mesh->AddPolygon(CurrentVertexOffset + a0start + RingIdx + 1);
Mesh->EndPolygon();
}
if (RingIdx != CapsuleNumRings - 1)
{
Mesh->BeginPolygon(ActualMatIndex);
Mesh->AddPolygon(CurrentVertexOffset + a1start + RingIdx + 0);
Mesh->AddPolygon(CurrentVertexOffset + a1start + RingIdx + 1);
Mesh->AddPolygon(CurrentVertexOffset + a0start + RingIdx + 1);
Mesh->EndPolygon();
}
}
}
CurrentVertexOffset += CapsuleNumVerts;
}
//////////////////////////////////////////////////////////////////////////
//Box data
FVector BoxPositions[4];
FRotator BoxFaceRotations[6];
int32 DrawCollisionSides;
//////////////////////////////////////////////////////////////////////////
//Sphere data
int32 SpherNumSides;
int32 SphereNumRings;
int32 SphereNumVerts;
TArray<FDynamicMeshVertex*> SpheresVerts;
//////////////////////////////////////////////////////////////////////////
//Capsule data
int32 CapsuleNumSides;
int32 CapsuleNumRings;
int32 CapsuleNumVerts;
TArray<FDynamicMeshVertex*> CapsuleVerts;
//////////////////////////////////////////////////////////////////////////
//Mesh Data
uint32 CurrentVertexOffset;
const UStaticMesh *StaticMesh;
FbxMesh* Mesh;
int32 ActualMatIndex;
FbxVector4* ControlPoints;
FbxLayerElementNormal* LayerElementNormal;
};
FbxNode* FFbxExporter::ExportCollisionMesh(const UStaticMesh* StaticMesh, const TCHAR* MeshName, FbxNode* ParentActor)
{
const FKAggregateGeom& AggGeo = StaticMesh->GetBodySetup()->AggGeom;
if (AggGeo.GetElementCount() <= 0)
{
return nullptr;
}
FbxMesh* Mesh = FbxMeshes.FindRef(StaticMesh);
if (!Mesh)
{
//We export collision only if the mesh is already exported
return nullptr;
}
//Name the node with the actor name
FString MeshCollisionName = TEXT("UCX_");
MeshCollisionName += UTF8_TO_TCHAR(ParentActor->GetName()); //-V595
FbxNode* FbxActor = FbxNode::Create(Scene, TCHAR_TO_UTF8(*MeshCollisionName));
if (ParentActor != nullptr)
{
// Collision meshes are added directly to the scene root, so we need to use the global transform instead of the relative one.
FbxAMatrix& GlobalTransform = ParentActor->EvaluateGlobalTransform();
FbxActor->LclTranslation.Set(GlobalTransform.GetT());
FbxActor->LclRotation.Set(GlobalTransform.GetR());
FbxActor->LclScaling.Set(GlobalTransform.GetS());
}
Scene->GetRootNode()->AddChild(FbxActor);
FbxMesh* CollisionMesh = FbxCollisionMeshes.FindRef(StaticMesh);
if (!CollisionMesh)
{
//Name the mesh attribute with the mesh name
MeshCollisionName = TEXT("UCX_");
MeshCollisionName += MeshName;
CollisionMesh = FbxMesh::Create(Scene, TCHAR_TO_UTF8(*MeshCollisionName));
//Export all collision elements in one mesh
FbxSurfaceMaterial* FbxMaterial = nullptr;
int32 ActualMatIndex = FbxActor->AddMaterial(FbxMaterial);
FCollisionFbxExporter CollisionFbxExporter(StaticMesh, CollisionMesh, ActualMatIndex);
CollisionFbxExporter.ExportCollisions();
FbxCollisionMeshes.Add(StaticMesh, CollisionMesh);
}
//Set the original meshes in case it was already existing
FbxActor->SetNodeAttribute(CollisionMesh);
return FbxActor;
}
void FFbxExporter::ExportObjectMetadata(const UObject* ObjectToExport, FbxNode* Node)
{
if (ObjectToExport && Node)
{
// Retrieve the metadata map without creating it
const TMap<FName, FString>* MetadataMap = FMetaData::GetMapForObject(ObjectToExport);
if (MetadataMap)
{
static const FString MetadataPrefix(FBX_METADATA_PREFIX);
for (const auto& MetadataIt : *MetadataMap)
{
// Export object metadata tags that are prefixed as FBX custom user-defined properties
// Remove the prefix since it's for Unreal use only (and '.' is considered an invalid character for user property names in DCC like Maya)
FString TagAsString = MetadataIt.Key.ToString();
if (TagAsString.RemoveFromStart(MetadataPrefix))
{
// Remaining tag follows the format NodeName.PropertyName, so replace '.' with '_'
TagAsString.ReplaceInline(TEXT("."), TEXT("_"));
if (MetadataIt.Value == TEXT("true") || MetadataIt.Value == TEXT("false"))
{
FbxProperty Property = FbxProperty::Create(Node, FbxBoolDT, TCHAR_TO_UTF8(*TagAsString));
FbxBool ValueBool = MetadataIt.Value == TEXT("true") ? true : false;
Property.Set(ValueBool);
Property.ModifyFlag(FbxPropertyFlags::eUserDefined, true);
}
else
{
FbxProperty Property = FbxProperty::Create(Node, FbxStringDT, TCHAR_TO_UTF8(*TagAsString));
FbxString ValueString(TCHAR_TO_UTF8(*MetadataIt.Value));
Property.Set(ValueString);
Property.ModifyFlag(FbxPropertyFlags::eUserDefined, true);
}
}
}
}
}
}
bool FFbxExporter::ExportStaticMeshFromMeshDescription(FbxMesh* Mesh
, const UStaticMesh* StaticMesh
, const FMeshDescription* MeshDescription
, FbxNode* FbxActor
, int32 LightmapUVChannel
, const TArray<FStaticMaterial>* MaterialOrderOverride
, const TArray<UMaterialInterface*>* OverrideMaterials
, const FFbxMaterialBakingMeshData& MaterialBakingMeshData)
{
if (MeshDescription->IsEmpty() || MeshDescription->Vertices().Num() == 0)
{
return false;
}
FStaticMeshConstAttributes Attributes(*MeshDescription);
TVertexAttributesConstRef<FVector3f> VertexPositions = Attributes.GetVertexPositions();
TVertexInstanceAttributesConstRef<FVector3f> VertexInstanceNormals = Attributes.GetVertexInstanceNormals();
TVertexInstanceAttributesConstRef<FVector3f> VertexInstanceTangents = Attributes.GetVertexInstanceTangents();
TVertexInstanceAttributesConstRef<float> VertexInstanceBinormalSigns = Attributes.GetVertexInstanceBinormalSigns();
TVertexInstanceAttributesConstRef<FVector2f> VertexInstanceUVs = Attributes.GetVertexInstanceUVs();
TVertexInstanceAttributesConstRef<FVector4f> VertexInstanceColors = Attributes.GetVertexInstanceColors();
TPolygonGroupAttributesConstRef<FName> PolygonGroupMaterialSlotNames = Attributes.GetPolygonGroupMaterialSlotNames();
TEdgeAttributesConstRef<bool> EdgeHardnesses = Attributes.GetEdgeHardnesses();
const int32 VertexCount = MeshDescription->Vertices().Num();
const int32 VertexInstanceCount = MeshDescription->VertexInstances().Num();
Mesh->InitControlPoints(VertexCount);
FbxVector4* ControlPoints = Mesh->GetControlPoints();
for (int32 PosIndex = 0; PosIndex < VertexCount; ++PosIndex)
{
FVector Position = (FVector)VertexPositions[FVertexID(PosIndex)];
ControlPoints[PosIndex] = FbxVector4(Position.X, -Position.Y, Position.Z);
}
// Set the normals on Layer 0.
FbxLayer* Layer = Mesh->GetLayer(0);
if (Layer == nullptr)
{
Mesh->CreateLayer();
Layer = Mesh->GetLayer(0);
}
TArray<uint32> Indices;
Indices.Reserve(VertexInstanceCount);
for (const FPolygonGroupID& PolygonGroupID : MeshDescription->PolygonGroups().GetElementIDs())
{
for(const FTriangleID& TriangleID : MeshDescription->GetPolygonGroupTriangles(PolygonGroupID))
{
TArrayView<const FVertexInstanceID> TriangleVertexInstanceIDs = MeshDescription->GetTriangleVertexInstances(TriangleID);
for(const FVertexInstanceID& VertexInstanceID : TriangleVertexInstanceIDs)
{
Indices.Add(MeshDescription->GetVertexInstanceVertex(VertexInstanceID).GetValue());
}
}
}
// Create and fill in the per-face-vertex normal data source.
// We extract the Z-tangent and the X/Y-tangents which are also stored in the render mesh.
FbxLayerElementNormal* LayerElementNormal = FbxLayerElementNormal::Create(Mesh, "");
FbxLayerElementTangent* LayerElementTangent = FbxLayerElementTangent::Create(Mesh, "");
FbxLayerElementBinormal* LayerElementBinormal = FbxLayerElementBinormal::Create(Mesh, "");
// Set 3 NTBs per triangle instead of storing on positional control points
LayerElementNormal->SetMappingMode(FbxLayerElement::eByPolygonVertex);
LayerElementTangent->SetMappingMode(FbxLayerElement::eByPolygonVertex);
LayerElementBinormal->SetMappingMode(FbxLayerElement::eByPolygonVertex);
// Set the NTBs values for every polygon vertex.
LayerElementNormal->SetReferenceMode(FbxLayerElement::eDirect);
LayerElementTangent->SetReferenceMode(FbxLayerElement::eDirect);
LayerElementBinormal->SetReferenceMode(FbxLayerElement::eDirect);
//Extract the tangent space
{
for (const FPolygonGroupID& PolygonGroupID : MeshDescription->PolygonGroups().GetElementIDs())
{
for (const FTriangleID& TriangleID : MeshDescription->GetPolygonGroupTriangles(PolygonGroupID))
{
TArrayView<const FVertexInstanceID> TriangleVertexInstanceIDs = MeshDescription->GetTriangleVertexInstances(TriangleID);
for (const FVertexInstanceID& VertexInstanceID : TriangleVertexInstanceIDs)
{
//Query the tangent space
FVector3f Normal, Tangent, Binormal;
Tangent = VertexInstanceTangents[VertexInstanceID];
Normal = VertexInstanceNormals[VertexInstanceID];
float BinormalSign = VertexInstanceBinormalSigns[VertexInstanceID];
Binormal = (FVector3f::CrossProduct(Normal, Tangent).GetSafeNormal() * BinormalSign);
FbxVector4 FbxNormal = FbxVector4(Normal.X, -Normal.Y, Normal.Z);
FbxNormal.Normalize();
LayerElementNormal->GetDirectArray().Add(FbxNormal);
FbxVector4 FbxTangent = FbxVector4(Tangent.X, -Tangent.Y, Tangent.Z);
FbxTangent.Normalize();
LayerElementTangent->GetDirectArray().Add(FbxTangent);
FbxVector4 FbxBinormal = FbxVector4(-Binormal.X, Binormal.Y, -Binormal.Z);
FbxBinormal.Normalize();
LayerElementBinormal->GetDirectArray().Add(FbxBinormal);
}
}
}
}
Layer->SetNormals(LayerElementNormal);
Layer->SetTangents(LayerElementTangent);
Layer->SetBinormals(LayerElementBinormal);
// Create and fill in the per-face-vertex texture coordinate data source(s).
// Create UV for Diffuse channel.
int32 TexCoordSourceCount = (LightmapUVChannel == -1) ? MeshDescription->GetNumUVElementChannels() : LightmapUVChannel + 1;
int32 TexCoordSourceIndex = (LightmapUVChannel == -1) ? 0 : LightmapUVChannel;
for (; TexCoordSourceIndex < TexCoordSourceCount; ++TexCoordSourceIndex)
{
FbxLayer* UVsLayer = (LightmapUVChannel == -1) ? Mesh->GetLayer(TexCoordSourceIndex) : Mesh->GetLayer(0);
if (UVsLayer == NULL)
{
Mesh->CreateLayer();
UVsLayer = (LightmapUVChannel == -1) ? Mesh->GetLayer(TexCoordSourceIndex) : Mesh->GetLayer(0);
}
check(UVsLayer);
FString UVChannelNameBuilder = TEXT("UVmap_") + FString::FromInt(TexCoordSourceIndex);
const auto UVChannelNameUTF8 = TStringConversion<FTCHARToUTF8_Convert>(*UVChannelNameBuilder); // Do not inline it! The lifetime of this object needs to extend over the usage of the converted buffer.
const char* UVChannelName = UVChannelNameUTF8.Get(); // actually UTF8 as required by Fbx, but can't use UE's UTF8CHAR type because that's a uint8 aka *unsigned* char
if ((LightmapUVChannel >= 0) || ((LightmapUVChannel == -1) && (TexCoordSourceIndex == StaticMesh->GetLightMapCoordinateIndex())))
{
UVChannelName = "LightMapUV";
}
FbxLayerElementUV* UVDiffuseLayer = FbxLayerElementUV::Create(Mesh, UVChannelName);
UVDiffuseLayer->SetMappingMode(FbxLayerElement::eByPolygonVertex);
UVDiffuseLayer->SetReferenceMode(FbxLayerElement::eDirect);
for (const FPolygonGroupID& PolygonGroupID : MeshDescription->PolygonGroups().GetElementIDs())
{
for (const FTriangleID& TriangleID : MeshDescription->GetPolygonGroupTriangles(PolygonGroupID))
{
TArrayView<const FVertexInstanceID> TriangleVertexInstanceIDs = MeshDescription->GetTriangleVertexInstances(TriangleID);
for (const FVertexInstanceID& VertexInstanceID : TriangleVertexInstanceIDs)
{
const FVector2f& VertexUV = VertexInstanceUVs.Get(VertexInstanceID, TexCoordSourceIndex);
UVDiffuseLayer->GetDirectArray().Add(FbxVector2(VertexUV.X, -VertexUV.Y + 1.0));
}
}
}
UVsLayer->SetUVs(UVDiffuseLayer, FbxLayerElement::eTextureDiffuse);
}
FbxLayerElementMaterial* MatLayer = FbxLayerElementMaterial::Create(Mesh, "");
MatLayer->SetMappingMode(FbxLayerElement::eByPolygon);
MatLayer->SetReferenceMode(FbxLayerElement::eIndexToDirect);
Layer->SetMaterials(MatLayer);
FbxLayerElementSmoothing* SmoothingInfoLayer = FbxLayerElementSmoothing::Create(Mesh, "");
SmoothingInfoLayer->SetMappingMode(FbxLayerElement::eByEdge);
SmoothingInfoLayer->SetReferenceMode(FbxLayerElement::eDirect);
Layer->SetSmoothing(SmoothingInfoLayer);
//Create the polygon with the correct material
{
int32 IndiceIndex = 0;
TSet<FEdgeID> ProcessEdges;
ProcessEdges.Reserve(Indices.Num());
for(const FPolygonGroupID& PolygonGroupID : MeshDescription->PolygonGroups().GetElementIDs())
{
UMaterialInterface* Material = nullptr;
FName CurrentMaterialSlotName = PolygonGroupMaterialSlotNames[PolygonGroupID];
int32 MaterialIndex = StaticMesh->GetMaterialIndexFromImportedMaterialSlotName(CurrentMaterialSlotName);
if (MaterialIndex == INDEX_NONE)
{
MaterialIndex = PolygonGroupID.GetValue();
}
if (!StaticMesh->GetStaticMaterials().IsValidIndex(MaterialIndex))
{
MaterialIndex = 0;
}
if (OverrideMaterials && OverrideMaterials->IsValidIndex(PolygonGroupID.GetValue()))
{
Material = (*OverrideMaterials)[PolygonGroupID.GetValue()];
}
else
{
Material = StaticMesh->GetMaterial(MaterialIndex);
}
FbxSurfaceMaterial* FbxMaterial = Material ? ExportMaterial(Material, MaterialIndex, MaterialBakingMeshData) : nullptr;
if (!FbxMaterial)
{
FbxMaterial = CreateDefaultMaterial();
}
int32 MatIndex = FbxActor->AddMaterial(FbxMaterial);
// Determine the actual material index
int32 ActualMatIndex = MatIndex;
if (MaterialOrderOverride)
{
ActualMatIndex = MaterialOrderOverride->Find(Material);
}
//Create the triangles
{
for (const FTriangleID& TriangleID : MeshDescription->GetPolygonGroupTriangles(PolygonGroupID))
{
FVertexID Corner[3];
int32 CornerIndex = 0;
Mesh->BeginPolygon(ActualMatIndex);
TArrayView<const FVertexInstanceID> TriangleVertexInstanceIDs = MeshDescription->GetTriangleVertexInstances(TriangleID);
for (const FVertexInstanceID& VertexInstanceID : TriangleVertexInstanceIDs)
{
Corner[CornerIndex] = MeshDescription->GetVertexInstanceVertex(VertexInstanceID);
Mesh->AddPolygon(Indices[IndiceIndex]);
IndiceIndex++;
CornerIndex++;
}
Mesh->EndPolygon();
}
}
}
//Build the edge so we can set the edge hardness
Mesh->BuildMeshEdgeArray();
Mesh->BeginGetMeshEdgeIndexForPolygon();
int32 PolygonIndex = 0;
for (const FPolygonGroupID& PolygonGroupID : MeshDescription->PolygonGroups().GetElementIDs())
{
for (const FTriangleID& TriangleID : MeshDescription->GetPolygonGroupTriangles(PolygonGroupID))
{
FVertexID Corner[3];
int32 CornerIndex = 0;
TArrayView<const FVertexInstanceID> TriangleVertexInstanceIDs = MeshDescription->GetTriangleVertexInstances(TriangleID);
for (const FVertexInstanceID& VertexInstanceID : TriangleVertexInstanceIDs)
{
Corner[CornerIndex] = MeshDescription->GetVertexInstanceVertex(VertexInstanceID);
CornerIndex++;
}
//Add the smoothing group for the triangle
for (CornerIndex = 0; CornerIndex < 3; ++CornerIndex)
{
FVertexID EdgeStart = Corner[CornerIndex];
FVertexID EdgeEnd = Corner[(CornerIndex + 1) % 3];
FEdgeID MatchEdgeId = MeshDescription->GetVertexPairEdge(EdgeStart, EdgeEnd);
if (ProcessEdges.Contains(MatchEdgeId))
{
continue;
}
ProcessEdges.Add(MatchEdgeId);
int32 FbxEdgeIndex = Mesh->GetMeshEdgeIndexForPolygon(PolygonIndex, CornerIndex);
if (FbxEdgeIndex == -1)
{
continue;
}
int32 EdgeHardnessValue = 1;
if (MatchEdgeId != INDEX_NONE)
{
EdgeHardnessValue = EdgeHardnesses[MatchEdgeId] ? 0 : 1;
}
int32 LayerAddIndex = SmoothingInfoLayer->GetDirectArray().Add(EdgeHardnessValue);
ensure(LayerAddIndex == FbxEdgeIndex);
}
PolygonIndex++;
}
}
Mesh->EndGetMeshEdgeIndexForPolygon();
}
// Create and fill in the vertex color data source.
uint32 ColorVertexCount = MeshDescription->VertexInstanceAttributes().HasAttribute(MeshAttribute::VertexInstance::Color) ? MeshDescription->VertexInstances().Num() : 0;
// Only export vertex colors if they exist
if (GetExportOptions()->VertexColor && ColorVertexCount > 0)
{
FbxLayerElementVertexColor* VertexColor = FbxLayerElementVertexColor::Create(Mesh, "");
VertexColor->SetMappingMode(FbxLayerElement::eByPolygonVertex);
VertexColor->SetReferenceMode(FbxLayerElement::eDirect);
FbxLayerElementArrayTemplate<FbxColor>& VertexColorArray = VertexColor->GetDirectArray();
Layer->SetVertexColors(VertexColor);
for (const FPolygonGroupID& PolygonGroupID : MeshDescription->PolygonGroups().GetElementIDs())
{
for (const FTriangleID& TriangleID : MeshDescription->GetPolygonGroupTriangles(PolygonGroupID))
{
TArrayView<const FVertexInstanceID> TriangleVertexInstanceIDs = MeshDescription->GetTriangleVertexInstances(TriangleID);
for (const FVertexInstanceID& VertexInstanceID : TriangleVertexInstanceIDs)
{
const FVector4f& SourceVertexColor = VertexInstanceColors[VertexInstanceID];
FLinearColor LinearColor(SourceVertexColor);
//Convert to sRGB
FColor Color = LinearColor.ToFColor(true);
VertexColorArray.Add(FbxColor(static_cast<float>(Color.R)/255.0f, static_cast<float>(Color.G) / 255.0f, static_cast<float>(Color.B) / 255.0f, static_cast<float>(Color.A) / 255.0f));
}
}
}
}
return true;
}
bool FFbxExporter::ExportStaticMeshFromRenderData(FbxMesh* Mesh
, const UStaticMesh* StaticMesh
, const FStaticMeshLODResources& RenderMesh
, FbxNode* FbxActor
, int32 LightmapUVChannel
, const FColorVertexBuffer* ColorBuffer
, const TArray<FStaticMaterial>* MaterialOrderOverride
, const TArray<UMaterialInterface*>* OverrideMaterials
, const FFbxMaterialBakingMeshData& MaterialBakingMeshData)
{
// Verify the integrity of the static mesh.
if (RenderMesh.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices() == 0)
{
return false;
}
if (RenderMesh.Sections.Num() == 0)
{
return false;
}
// Remaps an Unreal vert to final reduced vertex list
TArray<int32> VertRemap;
TArray<int32> UniqueVerts;
// Weld verts
DetermineVertsToWeld(VertRemap, UniqueVerts, RenderMesh);
// Create and fill in the vertex position data source.
// The position vertices are duplicated, for some reason, retrieve only the first half vertices.
const int32 VertexCount = VertRemap.Num();
const int32 PolygonsCount = RenderMesh.Sections.Num();
Mesh->InitControlPoints(UniqueVerts.Num());
FbxVector4* ControlPoints = Mesh->GetControlPoints();
for (int32 PosIndex = 0; PosIndex < UniqueVerts.Num(); ++PosIndex)
{
int32 UnrealPosIndex = UniqueVerts[PosIndex];
FVector Position = (FVector)RenderMesh.VertexBuffers.PositionVertexBuffer.VertexPosition(UnrealPosIndex);
ControlPoints[PosIndex] = FbxVector4(Position.X, -Position.Y, Position.Z);
}
// Set the normals on Layer 0.
FbxLayer* Layer = Mesh->GetLayer(0);
if (Layer == nullptr)
{
Mesh->CreateLayer();
Layer = Mesh->GetLayer(0);
}
// Build list of Indices re-used multiple times to lookup Normals, UVs, other per face vertex information
TArray<uint32> Indices;
for (int32 PolygonsIndex = 0; PolygonsIndex < PolygonsCount; ++PolygonsIndex)
{
FIndexArrayView RawIndices = RenderMesh.IndexBuffer.GetArrayView();
const FStaticMeshSection& Polygons = RenderMesh.Sections[PolygonsIndex];
const uint32 TriangleCount = Polygons.NumTriangles;
for (uint32 TriangleIndex = 0; TriangleIndex < TriangleCount; ++TriangleIndex)
{
for (uint32 PointIndex = 0; PointIndex < 3; PointIndex++)
{
uint32 UnrealVertIndex = RawIndices[Polygons.FirstIndex + ((TriangleIndex * 3) + PointIndex)];
Indices.Add(UnrealVertIndex);
}
}
}
// Create and fill in the per-face-vertex normal data source.
// We extract the Z-tangent and the X/Y-tangents which are also stored in the render mesh.
FbxLayerElementNormal* LayerElementNormal = FbxLayerElementNormal::Create(Mesh, "");
FbxLayerElementTangent* LayerElementTangent = FbxLayerElementTangent::Create(Mesh, "");
FbxLayerElementBinormal* LayerElementBinormal = FbxLayerElementBinormal::Create(Mesh, "");
// Set 3 NTBs per triangle instead of storing on positional control points
LayerElementNormal->SetMappingMode(FbxLayerElement::eByPolygonVertex);
LayerElementTangent->SetMappingMode(FbxLayerElement::eByPolygonVertex);
LayerElementBinormal->SetMappingMode(FbxLayerElement::eByPolygonVertex);
// Set the NTBs values for every polygon vertex.
LayerElementNormal->SetReferenceMode(FbxLayerElement::eDirect);
LayerElementTangent->SetReferenceMode(FbxLayerElement::eDirect);
LayerElementBinormal->SetReferenceMode(FbxLayerElement::eDirect);
TArray<FbxVector4> FbxNormals;
TArray<FbxVector4> FbxTangents;
TArray<FbxVector4> FbxBinormals;
FbxNormals.AddUninitialized(VertexCount);
FbxTangents.AddUninitialized(VertexCount);
FbxBinormals.AddUninitialized(VertexCount);
for (int32 NTBIndex = 0; NTBIndex < VertexCount; ++NTBIndex)
{
FVector3f Normal = (FVector3f)(RenderMesh.VertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(NTBIndex));
FbxVector4& FbxNormal = FbxNormals[NTBIndex];
FbxNormal = FbxVector4(Normal.X, -Normal.Y, Normal.Z);
FbxNormal.Normalize();
FVector3f Tangent = (FVector3f)(RenderMesh.VertexBuffers.StaticMeshVertexBuffer.VertexTangentX(NTBIndex));
FbxVector4& FbxTangent = FbxTangents[NTBIndex];
FbxTangent = FbxVector4(Tangent.X, -Tangent.Y, Tangent.Z);
FbxTangent.Normalize();
FVector3f Binormal = -(FVector3f)(RenderMesh.VertexBuffers.StaticMeshVertexBuffer.VertexTangentY(NTBIndex));
FbxVector4& FbxBinormal = FbxBinormals[NTBIndex];
FbxBinormal = FbxVector4(Binormal.X, -Binormal.Y, Binormal.Z);
FbxBinormal.Normalize();
}
// Add one normal per each face index (3 per triangle)
for (int32 FbxVertIndex = 0; FbxVertIndex < Indices.Num(); FbxVertIndex++)
{
uint32 UnrealVertIndex = Indices[FbxVertIndex];
LayerElementNormal->GetDirectArray().Add(FbxNormals[UnrealVertIndex]);
LayerElementTangent->GetDirectArray().Add(FbxTangents[UnrealVertIndex]);
LayerElementBinormal->GetDirectArray().Add(FbxBinormals[UnrealVertIndex]);
}
Layer->SetNormals(LayerElementNormal);
Layer->SetTangents(LayerElementTangent);
Layer->SetBinormals(LayerElementBinormal);
FbxNormals.Empty();
FbxTangents.Empty();
FbxBinormals.Empty();
// Create and fill in the per-face-vertex texture coordinate data source(s).
// Create UV for Diffuse channel.
int32 TexCoordSourceCount = (LightmapUVChannel == -1) ? RenderMesh.VertexBuffers.StaticMeshVertexBuffer.GetNumTexCoords() : LightmapUVChannel + 1;
int32 TexCoordSourceIndex = (LightmapUVChannel == -1) ? 0 : LightmapUVChannel;
for (; TexCoordSourceIndex < TexCoordSourceCount; ++TexCoordSourceIndex)
{
FbxLayer* UVsLayer = (LightmapUVChannel == -1) ? Mesh->GetLayer(TexCoordSourceIndex) : Mesh->GetLayer(0);
if (UVsLayer == NULL)
{
Mesh->CreateLayer();
UVsLayer = (LightmapUVChannel == -1) ? Mesh->GetLayer(TexCoordSourceIndex) : Mesh->GetLayer(0);
}
check(UVsLayer);
FString UVChannelNameBuilder = TEXT("UVmap_") + FString::FromInt(TexCoordSourceIndex);
const auto UVChannelNameUTF8 = TStringConversion<FTCHARToUTF8_Convert>(*UVChannelNameBuilder); // Do not inline it! The lifetime of this object needs to extend over the usage of the converted buffer.
const char* UVChannelName = UVChannelNameUTF8.Get(); // actually UTF8 as required by Fbx, but can't use UE's UTF8CHAR type because that's a uint8 aka *unsigned* char
if ((LightmapUVChannel >= 0) || ((LightmapUVChannel == -1) && (TexCoordSourceIndex == StaticMesh->GetLightMapCoordinateIndex())))
{
UVChannelName = "LightMapUV";
}
FbxLayerElementUV* UVDiffuseLayer = FbxLayerElementUV::Create(Mesh, UVChannelName);
// Note: when eINDEX_TO_DIRECT is used, IndexArray must be 3xTriangle count, DirectArray can be smaller
UVDiffuseLayer->SetMappingMode(FbxLayerElement::eByPolygonVertex);
UVDiffuseLayer->SetReferenceMode(FbxLayerElement::eIndexToDirect);
TArray<int32> UvsRemap;
TArray<int32> UniqueUVs;
// Weld UVs
DetermineUVsToWeld(UvsRemap, UniqueUVs, RenderMesh.VertexBuffers.StaticMeshVertexBuffer, TexCoordSourceIndex);
// Create the texture coordinate data source.
for (int32 FbxVertIndex = 0; FbxVertIndex < UniqueUVs.Num(); FbxVertIndex++)
{
int32 UnrealVertIndex = UniqueUVs[FbxVertIndex];
const FVector2f& TexCoord = RenderMesh.VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(UnrealVertIndex, TexCoordSourceIndex);
UVDiffuseLayer->GetDirectArray().Add(FbxVector2(TexCoord.X, -TexCoord.Y + 1.0));
}
// For each face index, point to a texture uv
UVDiffuseLayer->GetIndexArray().SetCount(Indices.Num());
for (int32 FbxVertIndex = 0; FbxVertIndex < Indices.Num(); FbxVertIndex++)
{
uint32 UnrealVertIndex = Indices[FbxVertIndex];
int32 NewVertIndex = UvsRemap[UnrealVertIndex];
UVDiffuseLayer->GetIndexArray().SetAt(FbxVertIndex, NewVertIndex);
}
UVsLayer->SetUVs(UVDiffuseLayer, FbxLayerElement::eTextureDiffuse);
}
FbxLayerElementMaterial* MatLayer = FbxLayerElementMaterial::Create(Mesh, "");
MatLayer->SetMappingMode(FbxLayerElement::eByPolygon);
MatLayer->SetReferenceMode(FbxLayerElement::eIndexToDirect);
Layer->SetMaterials(MatLayer);
// Keep track of the number of tri's we export
uint32 AccountedTriangles = 0;
for (int32 PolygonsIndex = 0; PolygonsIndex < PolygonsCount; ++PolygonsIndex)
{
const FStaticMeshSection& Polygons = RenderMesh.Sections[PolygonsIndex];
FIndexArrayView RawIndices = RenderMesh.IndexBuffer.GetArrayView();
UMaterialInterface* Material = nullptr;
if (OverrideMaterials && OverrideMaterials->IsValidIndex(Polygons.MaterialIndex))
{
Material = (*OverrideMaterials)[Polygons.MaterialIndex];
}
else
{
Material = StaticMesh->GetMaterial(Polygons.MaterialIndex);
}
FbxSurfaceMaterial* FbxMaterial = Material ? ExportMaterial(Material, Polygons.MaterialIndex, MaterialBakingMeshData) : NULL;
if (!FbxMaterial)
{
FbxMaterial = CreateDefaultMaterial();
}
int32 MatIndex = FbxActor->AddMaterial(FbxMaterial);
// Determine the actual material index
int32 ActualMatIndex = MatIndex;
if (MaterialOrderOverride)
{
ActualMatIndex = MaterialOrderOverride->Find(Material);
}
// Static meshes contain one triangle list per element.
// [GLAFORTE] Could it occasionally contain triangle strips? How do I know?
uint32 TriangleCount = Polygons.NumTriangles;
// Copy over the index buffer into the FBX polygons set.
for (uint32 TriangleIndex = 0; TriangleIndex < TriangleCount; ++TriangleIndex)
{
Mesh->BeginPolygon(ActualMatIndex);
for (uint32 PointIndex = 0; PointIndex < 3; PointIndex++)
{
uint32 OriginalUnrealVertIndex = RawIndices[Polygons.FirstIndex + ((TriangleIndex * 3) + PointIndex)];
int32 RemappedVertIndex = VertRemap[OriginalUnrealVertIndex];
Mesh->AddPolygon(RemappedVertIndex);
}
Mesh->EndPolygon();
}
AccountedTriangles += TriangleCount;
}
#ifdef TODO_FBX
// Throw a warning if this is a lightmap export and the exported poly count does not match the raw triangle data count
if (LightmapUVChannel != -1 && AccountedTriangles != RenderMesh.RawTriangles.GetElementCount())
{
FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "StaticMeshEditor_LightmapExportFewerTriangles", "Fewer polygons have been exported than the raw triangle count. This Lightmapped UV mesh may contain fewer triangles than the destination mesh on import."));
}
// Create and fill in the smoothing data source.
FbxLayerElementSmoothing* SmoothingInfo = FbxLayerElementSmoothing::Create(Mesh, "");
SmoothingInfo->SetMappingMode(FbxLayerElement::eByPolygon);
SmoothingInfo->SetReferenceMode(FbxLayerElement::eDirect);
FbxLayerElementArrayTemplate<int>& SmoothingArray = SmoothingInfo->GetDirectArray();
Layer->SetSmoothing(SmoothingInfo);
// This is broken. We are exporting the render mesh but providing smoothing
// information from the source mesh. The render triangles are not in the
// same order. Therefore we should export the raw mesh or not export
// smoothing group information!
int32 TriangleCount = RenderMesh.RawTriangles.GetElementCount();
FStaticMeshTriangle* RawTriangleData = (FStaticMeshTriangle*)RenderMesh.RawTriangles.Lock(LOCK_READ_ONLY);
for (int32 TriangleIndex = 0; TriangleIndex < TriangleCount; TriangleIndex++)
{
FStaticMeshTriangle* Triangle = (RawTriangleData++);
SmoothingArray.Add(Triangle->SmoothingMask);
}
RenderMesh.RawTriangles.Unlock();
#endif // #if TODO_FBX
// Create and fill in the vertex color data source.
const FColorVertexBuffer* ColorBufferToUse = ColorBuffer ? ColorBuffer : &RenderMesh.VertexBuffers.ColorVertexBuffer;
uint32 ColorVertexCount = ColorBufferToUse->GetNumVertices();
// Only export vertex colors if they exist
if (GetExportOptions()->VertexColor && ColorVertexCount > 0)
{
FbxLayerElementVertexColor* VertexColor = FbxLayerElementVertexColor::Create(Mesh, "");
VertexColor->SetMappingMode(FbxLayerElement::eByPolygonVertex);
VertexColor->SetReferenceMode(FbxLayerElement::eIndexToDirect);
FbxLayerElementArrayTemplate<FbxColor>& VertexColorArray = VertexColor->GetDirectArray();
Layer->SetVertexColors(VertexColor);
for (int32 FbxVertIndex = 0; FbxVertIndex < Indices.Num(); FbxVertIndex++)
{
FLinearColor VertColor(1.0f, 1.0f, 1.0f);
uint32 UnrealVertIndex = Indices[FbxVertIndex];
if (UnrealVertIndex < ColorVertexCount)
{
VertColor = ColorBufferToUse->VertexColor(UnrealVertIndex).ReinterpretAsLinear();
}
VertexColorArray.Add(FbxColor(VertColor.R, VertColor.G, VertColor.B, VertColor.A));
}
VertexColor->GetIndexArray().SetCount(Indices.Num());
for (int32 FbxVertIndex = 0; FbxVertIndex < Indices.Num(); FbxVertIndex++)
{
VertexColor->GetIndexArray().SetAt(FbxVertIndex, FbxVertIndex);
}
}
return true;
}
/**
* Exports a static mesh
* @param StaticMesh The static mesh to export
* @param MeshName The name of the mesh for the FBX file
* @param FbxActor The fbx node representing the mesh
* @param ExportLOD The LOD of the mesh to export
* @param LightmapUVChannel If set, performs a "lightmap export" and exports only the single given UV channel
* @param ColorBuffer Vertex color overrides to export
* @param MaterialOrderOverride Optional ordering of materials to set up correct material ID's across multiple meshes being export such as BSP surfaces which share common materials. Should be used sparingly
*/
FbxNode* FFbxExporter::ExportStaticMeshToFbx(const UStaticMesh* StaticMesh, int32 ExportLOD, const TCHAR* MeshName, FbxNode* FbxActor, const FFbxMaterialBakingMeshData& MaterialBakingMeshData, int32 LightmapUVChannel /*= -1*/, const FColorVertexBuffer* ColorBuffer /*= NULL*/, const TArray<FStaticMaterial>* MaterialOrderOverride /*= NULL*/, const TArray<UMaterialInterface*>* OverrideMaterials /*= NULL*/)
{
FbxMesh* Mesh = nullptr;
if ((ExportLOD == 0 || ExportLOD == -1) && LightmapUVChannel == -1 && ColorBuffer == nullptr && MaterialOrderOverride == nullptr)
{
Mesh = FbxMeshes.FindRef(StaticMesh);
}
if (!Mesh)
{
Mesh = FbxMesh::Create(Scene, TCHAR_TO_UTF8(MeshName));
int32 LodIndex = ExportLOD == INDEX_NONE ? 0 : ExportLOD;
bool bExportSourceMesh = GetExportOptions()->bExportSourceMesh && !GetExportOptions()->LevelOfDetail && !GetExportOptions()->Collision;
const bool bUseNaniteData = LodIndex == 0 && StaticMesh->IsNaniteEnabled() && StaticMesh->IsHiResMeshDescriptionValid();
const bool bUseLodData = !bUseNaniteData && StaticMesh->IsMeshDescriptionValid(LodIndex);
bExportSourceMesh &= (bUseNaniteData || bUseLodData);
if (bExportSourceMesh)
{
if (bUseNaniteData)
{
//Export the nanite mesh description
if (!ExportStaticMeshFromMeshDescription(Mesh, StaticMesh, StaticMesh->GetHiResMeshDescription(), FbxActor, LightmapUVChannel, MaterialOrderOverride, OverrideMaterials, MaterialBakingMeshData))
{
return nullptr;
}
}
else
{
ensure(bUseLodData);
//Export the lod mesh description
if(!ExportStaticMeshFromMeshDescription(Mesh, StaticMesh, StaticMesh->GetMeshDescription(LodIndex), FbxActor, LightmapUVChannel, MaterialOrderOverride, OverrideMaterials, MaterialBakingMeshData))
{
return nullptr;
}
}
}
else
{
//Export the render data
const FStaticMeshLODResources& RenderMesh = StaticMesh->GetLODForExport(LodIndex);
if (!ExportStaticMeshFromRenderData(Mesh, StaticMesh, RenderMesh, FbxActor, LightmapUVChannel, ColorBuffer, MaterialOrderOverride, OverrideMaterials, MaterialBakingMeshData))
{
return nullptr;
}
}
if (LodIndex == 0 && LightmapUVChannel == -1 && ColorBuffer == nullptr && MaterialOrderOverride == nullptr)
{
FbxMeshes.Add(StaticMesh, Mesh);
}
}
else
{
//Materials in fbx are store in the node and not in the mesh, so even if the mesh was already export
//we have to find and assign the mesh material.
const FStaticMeshLODResources& RenderMesh = StaticMesh->GetLODForExport(ExportLOD);
const int32 PolygonsCount = RenderMesh.Sections.Num();
uint32 AccountedTriangles = 0;
for (int32 PolygonsIndex = 0; PolygonsIndex < PolygonsCount; ++PolygonsIndex)
{
const FStaticMeshSection& Polygons = RenderMesh.Sections[PolygonsIndex];
FIndexArrayView RawIndices = RenderMesh.IndexBuffer.GetArrayView();
UMaterialInterface* Material = nullptr;
if (OverrideMaterials && OverrideMaterials->IsValidIndex(Polygons.MaterialIndex))
{
Material = (*OverrideMaterials)[Polygons.MaterialIndex];
}
else
{
Material = StaticMesh->GetMaterial(Polygons.MaterialIndex);
}
FbxSurfaceMaterial* FbxMaterial = Material ? ExportMaterial(Material, Polygons.MaterialIndex, MaterialBakingMeshData) : NULL;
if (!FbxMaterial)
{
FbxMaterial = CreateDefaultMaterial();
}
FbxActor->AddMaterial(FbxMaterial);
}
}
if ((ExportLOD == 0 || ExportLOD == -1) && GetExportOptions()->Collision)
{
ExportCollisionMesh(StaticMesh, MeshName, FbxActor);
}
//Set the original meshes in case it was already existing
FbxActor->SetNodeAttribute(Mesh);
ExportObjectMetadata(StaticMesh, FbxActor);
return FbxActor;
}
void FFbxExporter::ExportSplineMeshToFbx(const USplineMeshComponent* SplineMeshComp, const TCHAR* MeshName, FbxNode* FbxActor, const FFbxMaterialBakingMeshData& MaterialBakingMeshData)
{
const UStaticMesh* StaticMesh = SplineMeshComp->GetStaticMesh();
check(StaticMesh);
const int32 LODIndex = (SplineMeshComp->ForcedLodModel > 0 ? FMath::Min(StaticMesh->GetNumLODs(), SplineMeshComp->ForcedLodModel) - 1 : /* auto-select*/ 0);
if (!SplineMeshComp->LODData.IsValidIndex(LODIndex))
{
return;
}
const FStaticMeshLODResources& RenderMesh = StaticMesh->GetLODForExport(LODIndex);
// Verify the integrity of the static mesh.
if (RenderMesh.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices() == 0)
{
return;
}
if (RenderMesh.Sections.Num() == 0)
{
return;
}
// Remaps an Unreal vert to final reduced vertex list
TArray<int32> VertRemap;
TArray<int32> UniqueVerts;
// Weld verts
DetermineVertsToWeld(VertRemap, UniqueVerts, RenderMesh);
FbxMesh* Mesh = FbxMesh::Create(Scene, TCHAR_TO_UTF8(MeshName));
// Create and fill in the vertex position data source.
// The position vertices are duplicated, for some reason, retrieve only the first half vertices.
const int32 VertexCount = VertRemap.Num();
const int32 PolygonsCount = RenderMesh.Sections.Num();
Mesh->InitControlPoints(UniqueVerts.Num());
FbxVector4* ControlPoints = Mesh->GetControlPoints();
for (int32 PosIndex = 0; PosIndex < UniqueVerts.Num(); ++PosIndex)
{
int32 UnrealPosIndex = UniqueVerts[PosIndex];
FVector Position = (FVector)RenderMesh.VertexBuffers.PositionVertexBuffer.VertexPosition(UnrealPosIndex);
const FTransform SliceTransform = SplineMeshComp->CalcSliceTransform(USplineMeshComponent::GetAxisValueRef(Position, SplineMeshComp->ForwardAxis));
USplineMeshComponent::GetAxisValueRef(Position, SplineMeshComp->ForwardAxis) = 0;
Position = SliceTransform.TransformPosition(Position);
ControlPoints[PosIndex] = FbxVector4(Position.X, -Position.Y, Position.Z);
}
// Set the normals on Layer 0.
FbxLayer* Layer = Mesh->GetLayer(0);
if (Layer == NULL)
{
Mesh->CreateLayer();
Layer = Mesh->GetLayer(0);
}
// Build list of Indices re-used multiple times to lookup Normals, UVs, other per face vertex information
TArray<uint32> Indices;
for (int32 PolygonsIndex = 0; PolygonsIndex < PolygonsCount; ++PolygonsIndex)
{
FIndexArrayView RawIndices = RenderMesh.IndexBuffer.GetArrayView();
const FStaticMeshSection& Polygons = RenderMesh.Sections[PolygonsIndex];
const uint32 TriangleCount = Polygons.NumTriangles;
for (uint32 TriangleIndex = 0; TriangleIndex < TriangleCount; ++TriangleIndex)
{
for (uint32 PointIndex = 0; PointIndex < 3; PointIndex++)
{
uint32 UnrealVertIndex = RawIndices[Polygons.FirstIndex + ((TriangleIndex * 3) + PointIndex)];
Indices.Add(UnrealVertIndex);
}
}
}
// Create and fill in the per-face-vertex normal data source.
// We extract the Z-tangent and drop the X/Y-tangents which are also stored in the render mesh.
FbxLayerElementNormal* LayerElementNormal = FbxLayerElementNormal::Create(Mesh, "");
// Set 3 normals per triangle instead of storing normals on positional control points
LayerElementNormal->SetMappingMode(FbxLayerElement::eByPolygonVertex);
// Set the normal values for every polygon vertex.
LayerElementNormal->SetReferenceMode(FbxLayerElement::eDirect);
TArray<FbxVector4> FbxNormals;
FbxNormals.AddUninitialized(VertexCount);
for (int32 VertIndex = 0; VertIndex < VertexCount; ++VertIndex)
{
FVector Position = (FVector)RenderMesh.VertexBuffers.PositionVertexBuffer.VertexPosition(VertIndex);
const FTransform SliceTransform = SplineMeshComp->CalcSliceTransform(USplineMeshComponent::GetAxisValueRef(Position, SplineMeshComp->ForwardAxis));
FVector Normal = FVector(RenderMesh.VertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(VertIndex));
Normal = SliceTransform.TransformVector(Normal);
FbxVector4& FbxNormal = FbxNormals[VertIndex];
FbxNormal = FbxVector4(Normal.X, -Normal.Y, Normal.Z);
FbxNormal.Normalize();
}
// Add one normal per each face index (3 per triangle)
for (uint32 UnrealVertIndex : Indices)
{
LayerElementNormal->GetDirectArray().Add(FbxNormals[UnrealVertIndex]);
}
Layer->SetNormals(LayerElementNormal);
FbxNormals.Empty();
// Create and fill in the per-face-vertex texture coordinate data source(s).
// Create UV for Diffuse channel.
int32 TexCoordSourceCount = RenderMesh.VertexBuffers.StaticMeshVertexBuffer.GetNumTexCoords();
for (int32 TexCoordSourceIndex = 0; TexCoordSourceIndex < TexCoordSourceCount; ++TexCoordSourceIndex)
{
FbxLayer* UVsLayer = Mesh->GetLayer(TexCoordSourceIndex);
if (UVsLayer == NULL)
{
Mesh->CreateLayer();
UVsLayer = Mesh->GetLayer(TexCoordSourceIndex);
}
FString UVChannelNameBuilder = TEXT("UVmap_") + FString::FromInt(TexCoordSourceIndex);
const auto UVChannelNameUTF8 = TStringConversion<FTCHARToUTF8_Convert>(*UVChannelNameBuilder); // Do not inline it! The lifetime of this object needs to extend over the usage of the converted buffer.
const char* UVChannelName = UVChannelNameUTF8.Get(); // actually UTF8 as required by Fbx, but can't use UE's UTF8CHAR type because that's a uint8 aka *unsigned* char
if (TexCoordSourceIndex == StaticMesh->GetLightMapCoordinateIndex())
{
UVChannelName = "LightMapUV";
}
FbxLayerElementUV* UVDiffuseLayer = FbxLayerElementUV::Create(Mesh, UVChannelName);
// Note: when eINDEX_TO_DIRECT is used, IndexArray must be 3xTriangle count, DirectArray can be smaller
UVDiffuseLayer->SetMappingMode(FbxLayerElement::eByPolygonVertex);
UVDiffuseLayer->SetReferenceMode(FbxLayerElement::eIndexToDirect);
TArray<int32> UvsRemap;
TArray<int32> UniqueUVs;
// Weld UVs
DetermineUVsToWeld(UvsRemap, UniqueUVs, RenderMesh.VertexBuffers.StaticMeshVertexBuffer, TexCoordSourceIndex);
// Create the texture coordinate data source.
for (int32 UnrealVertIndex : UniqueUVs)
{
const FVector2f& TexCoord = RenderMesh.VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(UnrealVertIndex, TexCoordSourceIndex);
UVDiffuseLayer->GetDirectArray().Add(FbxVector2(TexCoord.X, -TexCoord.Y + 1.0));
}
// For each face index, point to a texture uv
UVDiffuseLayer->GetIndexArray().SetCount(Indices.Num());
for (int32 FbxVertIndex = 0; FbxVertIndex < Indices.Num(); FbxVertIndex++)
{
uint32 UnrealVertIndex = Indices[FbxVertIndex];
int32 NewVertIndex = UvsRemap[UnrealVertIndex];
UVDiffuseLayer->GetIndexArray().SetAt(FbxVertIndex, NewVertIndex);
}
UVsLayer->SetUVs(UVDiffuseLayer, FbxLayerElement::eTextureDiffuse);
}
FbxLayerElementMaterial* MatLayer = FbxLayerElementMaterial::Create(Mesh, "");
MatLayer->SetMappingMode(FbxLayerElement::eByPolygon);
MatLayer->SetReferenceMode(FbxLayerElement::eIndexToDirect);
Layer->SetMaterials(MatLayer);
for (int32 PolygonsIndex = 0; PolygonsIndex < PolygonsCount; ++PolygonsIndex)
{
const FStaticMeshSection& Polygons = RenderMesh.Sections[PolygonsIndex];
FIndexArrayView RawIndices = RenderMesh.IndexBuffer.GetArrayView();
UMaterialInterface* Material = StaticMesh->GetMaterial(Polygons.MaterialIndex);
FbxSurfaceMaterial* FbxMaterial = Material ? ExportMaterial(Material, Polygons.MaterialIndex, MaterialBakingMeshData) : NULL;
if (!FbxMaterial)
{
FbxMaterial = CreateDefaultMaterial();
}
int32 MatIndex = FbxActor->AddMaterial(FbxMaterial);
// Static meshes contain one triangle list per element.
uint32 TriangleCount = Polygons.NumTriangles;
// Copy over the index buffer into the FBX polygons set.
for (uint32 TriangleIndex = 0; TriangleIndex < TriangleCount; ++TriangleIndex)
{
Mesh->BeginPolygon(MatIndex);
for (uint32 PointIndex = 0; PointIndex < 3; PointIndex++)
{
uint32 OriginalUnrealVertIndex = RawIndices[Polygons.FirstIndex + ((TriangleIndex * 3) + PointIndex)];
int32 RemappedVertIndex = VertRemap[OriginalUnrealVertIndex];
Mesh->AddPolygon(RemappedVertIndex);
}
Mesh->EndPolygon();
}
}
#ifdef TODO_FBX
// This is broken. We are exporting the render mesh but providing smoothing
// information from the source mesh. The render triangles are not in the
// same order. Therefore we should export the raw mesh or not export
// smoothing group information!
int32 TriangleCount = RenderMesh.RawTriangles.GetElementCount();
FStaticMeshTriangle* RawTriangleData = (FStaticMeshTriangle*)RenderMesh.RawTriangles.Lock(LOCK_READ_ONLY);
for (int32 TriangleIndex = 0; TriangleIndex < TriangleCount; TriangleIndex++)
{
FStaticMeshTriangle* Triangle = (RawTriangleData++);
SmoothingArray.Add(Triangle->SmoothingMask);
}
RenderMesh.RawTriangles.Unlock();
#endif // #if TODO_FBX
// Create and fill in the vertex color data source.
const FColorVertexBuffer* ColorBufferToUse = &RenderMesh.VertexBuffers.ColorVertexBuffer;
uint32 ColorVertexCount = ColorBufferToUse->GetNumVertices();
// Only export vertex colors if they exist
if (GetExportOptions()->VertexColor && ColorVertexCount > 0)
{
FbxLayerElementVertexColor* VertexColor = FbxLayerElementVertexColor::Create(Mesh, "");
VertexColor->SetMappingMode(FbxLayerElement::eByPolygonVertex);
VertexColor->SetReferenceMode(FbxLayerElement::eIndexToDirect);
FbxLayerElementArrayTemplate<FbxColor>& VertexColorArray = VertexColor->GetDirectArray();
Layer->SetVertexColors(VertexColor);
for (int32 FbxVertIndex = 0; FbxVertIndex < Indices.Num(); FbxVertIndex++)
{
FLinearColor VertColor(1.0f, 1.0f, 1.0f);
uint32 UnrealVertIndex = Indices[FbxVertIndex];
if (UnrealVertIndex < ColorVertexCount)
{
VertColor = ColorBufferToUse->VertexColor(UnrealVertIndex).ReinterpretAsLinear();
}
VertexColorArray.Add(FbxColor(VertColor.R, VertColor.G, VertColor.B, VertColor.A));
}
VertexColor->GetIndexArray().SetCount(Indices.Num());
for (int32 FbxVertIndex = 0; FbxVertIndex < Indices.Num(); FbxVertIndex++)
{
VertexColor->GetIndexArray().SetAt(FbxVertIndex, FbxVertIndex);
}
}
FbxActor->SetNodeAttribute(Mesh);
}
void FFbxExporter::ExportInstancedMeshToFbx(const UInstancedStaticMeshComponent* InstancedMeshComp, const TCHAR* MeshName, FbxNode* FbxActor, const FFbxMaterialBakingMeshData& MaterialBakingMeshData)
{
const UStaticMesh* StaticMesh = InstancedMeshComp->GetStaticMesh();
check(StaticMesh);
const int32 LODIndex = (InstancedMeshComp->ForcedLodModel > 0 ? FMath::Min(StaticMesh->GetNumLODs(), InstancedMeshComp->ForcedLodModel) - 1 : /* auto-select*/ 0);
const int32 NumInstances = InstancedMeshComp->GetInstanceCount();
for (int32 InstanceIndex = 0; InstanceIndex < NumInstances; ++InstanceIndex)
{
FTransform RelativeTransform;
if (ensure(InstancedMeshComp->GetInstanceTransform(InstanceIndex, RelativeTransform, /*bWorldSpace=*/false)))
{
FbxNode* InstNode = FbxNode::Create(Scene, TCHAR_TO_UTF8(*FString::Printf(TEXT("%d"), InstanceIndex)));
InstNode->LclTranslation.Set(Converter.ConvertToFbxPos(RelativeTransform.GetTranslation()));
InstNode->LclRotation.Set(Converter.ConvertToFbxRot(RelativeTransform.GetRotation().Euler()));
InstNode->LclScaling.Set(Converter.ConvertToFbxScale(RelativeTransform.GetScale3D()));
// Todo - export once and then clone the node
const int32 LightmapUVChannel = -1;
const TArray<FStaticMaterial>* MaterialOrderOverride = nullptr;
const FColorVertexBuffer* ColorBuffer = nullptr;
ExportStaticMeshToFbx(StaticMesh, LODIndex, *FString::Printf(TEXT("%d"), InstanceIndex), InstNode, MaterialBakingMeshData, LightmapUVChannel, ColorBuffer, MaterialOrderOverride, &ToRawPtrTArrayUnsafe(InstancedMeshComp->OverrideMaterials));
FbxActor->AddChild(InstNode);
}
}
}
/**
* Exports a Landscape
*/
void FFbxExporter::ExportLandscapeToFbx(ALandscapeProxy* Landscape, const TCHAR* MeshName, FbxNode* FbxActor, bool bSelectedOnly)
{
const ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo();
TSet<ULandscapeComponent*> SelectedComponents;
if (bSelectedOnly && LandscapeInfo)
{
SelectedComponents = LandscapeInfo->GetSelectedComponents();
}
bSelectedOnly = bSelectedOnly && SelectedComponents.Num() > 0;
int32 MinX = MAX_int32, MinY = MAX_int32;
int32 MaxX = MIN_int32, MaxY = MIN_int32;
// Find range of entire landscape
for (int32 ComponentIndex = 0; ComponentIndex < Landscape->LandscapeComponents.Num(); ComponentIndex++)
{
ULandscapeComponent* Component = Landscape->LandscapeComponents[ComponentIndex];
if (bSelectedOnly && !SelectedComponents.Contains(Component))
{
continue;
}
Component->GetComponentExtent(MinX, MinY, MaxX, MaxY);
}
FbxMesh* Mesh = FbxMesh::Create(Scene, TCHAR_TO_UTF8(MeshName));
// Create and fill in the vertex position data source.
const int32 ComponentSizeQuads = ((Landscape->ComponentSizeQuads + 1) >> Landscape->ExportLOD) - 1;
const float ScaleFactor = (float)Landscape->ComponentSizeQuads / (float)ComponentSizeQuads;
const int32 NumComponents = bSelectedOnly ? SelectedComponents.Num() : Landscape->LandscapeComponents.Num();
const int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1);
const int32 VertexCount = NumComponents * VertexCountPerComponent;
const int32 TriangleCount = NumComponents * FMath::Square(ComponentSizeQuads) * 2;
Mesh->InitControlPoints(VertexCount);
// Normals and Tangents
FbxLayerElementNormal* LayerElementNormals= FbxLayerElementNormal::Create(Mesh, "");
LayerElementNormals->SetMappingMode(FbxLayerElement::eByControlPoint);
LayerElementNormals->SetReferenceMode(FbxLayerElement::eDirect);
FbxLayerElementTangent* LayerElementTangents= FbxLayerElementTangent::Create(Mesh, "");
LayerElementTangents->SetMappingMode(FbxLayerElement::eByControlPoint);
LayerElementTangents->SetReferenceMode(FbxLayerElement::eDirect);
FbxLayerElementBinormal* LayerElementBinormals= FbxLayerElementBinormal::Create(Mesh, "");
LayerElementBinormals->SetMappingMode(FbxLayerElement::eByControlPoint);
LayerElementBinormals->SetReferenceMode(FbxLayerElement::eDirect);
// Add Texture UVs (which are simply incremented 1.0 per vertex)
FbxLayerElementUV* LayerElementTextureUVs = FbxLayerElementUV::Create(Mesh, "TextureUVs");
LayerElementTextureUVs->SetMappingMode(FbxLayerElement::eByControlPoint);
LayerElementTextureUVs->SetReferenceMode(FbxLayerElement::eDirect);
// Add Weightmap UVs (to match up with an exported weightmap, not the original weightmap UVs, which are per-component)
const FVector2D UVScale = FVector2D(1.0f, 1.0f) / FVector2D((MaxX - MinX) + 1, (MaxY - MinY) + 1);
FbxLayerElementUV* LayerElementWeightmapUVs = FbxLayerElementUV::Create(Mesh, "WeightmapUVs");
LayerElementWeightmapUVs->SetMappingMode(FbxLayerElement::eByControlPoint);
LayerElementWeightmapUVs->SetReferenceMode(FbxLayerElement::eDirect);
FbxVector4* ControlPoints = Mesh->GetControlPoints();
FbxLayerElementArrayTemplate<FbxVector4>& Normals = LayerElementNormals->GetDirectArray();
Normals.Resize(VertexCount);
FbxLayerElementArrayTemplate<FbxVector4>& Tangents = LayerElementTangents->GetDirectArray();
Tangents.Resize(VertexCount);
FbxLayerElementArrayTemplate<FbxVector4>& Binormals = LayerElementBinormals->GetDirectArray();
Binormals.Resize(VertexCount);
FbxLayerElementArrayTemplate<FbxVector2>& TextureUVs = LayerElementTextureUVs->GetDirectArray();
TextureUVs.Resize(VertexCount);
FbxLayerElementArrayTemplate<FbxVector2>& WeightmapUVs = LayerElementWeightmapUVs->GetDirectArray();
WeightmapUVs.Resize(VertexCount);
TArray<uint8> VisibilityData;
VisibilityData.Empty(VertexCount);
VisibilityData.AddZeroed(VertexCount);
for (int32 ComponentIndex = 0, SelectedComponentIndex = 0; ComponentIndex < Landscape->LandscapeComponents.Num(); ComponentIndex++)
{
ULandscapeComponent* Component = Landscape->LandscapeComponents[ComponentIndex];
if (bSelectedOnly && !SelectedComponents.Contains(Component))
{
continue;
}
FLandscapeComponentDataInterface CDI(Component, Landscape->ExportLOD);
const int32 BaseVertIndex = SelectedComponentIndex++ * VertexCountPerComponent;
const TArray<FWeightmapLayerAllocationInfo>& ComponentWeightmapLayerAllocations = Component->GetWeightmapLayerAllocations();
TArray<uint8> CompVisData;
for (int32 AllocIdx = 0; AllocIdx < ComponentWeightmapLayerAllocations.Num(); AllocIdx++)
{
const FWeightmapLayerAllocationInfo& AllocInfo = ComponentWeightmapLayerAllocations[AllocIdx];
if (AllocInfo.LayerInfo == ALandscapeProxy::VisibilityLayer)
{
CDI.GetWeightmapTextureData(AllocInfo.LayerInfo, CompVisData);
}
}
if (CompVisData.Num() > 0)
{
for (int32 i = 0; i < VertexCountPerComponent; ++i)
{
VisibilityData[BaseVertIndex + i] = CompVisData[CDI.VertexIndexToTexel(i)];
}
}
for (int32 VertIndex = 0; VertIndex < VertexCountPerComponent; VertIndex++)
{
int32 VertX, VertY;
CDI.VertexIndexToXY(VertIndex, VertX, VertY);
FVector Position = CDI.GetLocalVertex(VertX, VertY) + Component->GetRelativeLocation();
FbxVector4 FbxPosition = FbxVector4(Position.X, -Position.Y, Position.Z);
ControlPoints[BaseVertIndex + VertIndex] = FbxPosition;
FVector Normal, TangentX, TangentY;
CDI.GetLocalTangentVectors(VertX, VertY, TangentX, TangentY, Normal);
Normal /= Component->GetComponentTransform().GetScale3D(); Normal.Normalize();
TangentX /= Component->GetComponentTransform().GetScale3D(); TangentX.Normalize();
TangentY /= Component->GetComponentTransform().GetScale3D(); TangentY.Normalize();
FbxVector4 FbxNormal = FbxVector4(Normal.X, -Normal.Y, Normal.Z); FbxNormal.Normalize();
Normals.SetAt(BaseVertIndex + VertIndex, FbxNormal);
FbxVector4 FbxTangent = FbxVector4(TangentX.X, -TangentX.Y, TangentX.Z); FbxTangent.Normalize();
Tangents.SetAt(BaseVertIndex + VertIndex, FbxTangent);
FbxVector4 FbxBinormal = FbxVector4(TangentY.X, -TangentY.Y, TangentY.Z); FbxBinormal.Normalize();
Binormals.SetAt(BaseVertIndex + VertIndex, FbxBinormal);
FVector2D TextureUV = FVector2D(VertX * ScaleFactor + Component->GetSectionBase().X, VertY * ScaleFactor + Component->GetSectionBase().Y);
FbxVector2 FbxTextureUV = FbxVector2(TextureUV.X, TextureUV.Y);
TextureUVs.SetAt(BaseVertIndex + VertIndex, FbxTextureUV);
FVector2D WeightmapUV = (TextureUV - FVector2D(MinX, MinY)) * UVScale;
FbxVector2 FbxWeightmapUV = FbxVector2(WeightmapUV.X, WeightmapUV.Y);
WeightmapUVs.SetAt(BaseVertIndex + VertIndex, FbxWeightmapUV);
}
}
FbxLayer* Layer0 = Mesh->GetLayer(0);
if (Layer0 == NULL)
{
Mesh->CreateLayer();
Layer0 = Mesh->GetLayer(0);
}
Layer0->SetNormals(LayerElementNormals);
Layer0->SetTangents(LayerElementTangents);
Layer0->SetBinormals(LayerElementBinormals);
Layer0->SetUVs(LayerElementTextureUVs);
Layer0->SetUVs(LayerElementWeightmapUVs, FbxLayerElement::eTextureBump);
// this doesn't seem to work, on import the mesh has no smoothing layer at all
//FbxLayerElementSmoothing* SmoothingInfo = FbxLayerElementSmoothing::Create(Mesh, "");
//SmoothingInfo->SetMappingMode(FbxLayerElement::eAllSame);
//SmoothingInfo->SetReferenceMode(FbxLayerElement::eDirect);
//FbxLayerElementArrayTemplate<int>& Smoothing = SmoothingInfo->GetDirectArray();
//Smoothing.Add(0);
//Layer0->SetSmoothing(SmoothingInfo);
FbxLayerElementMaterial* LayerElementMaterials = FbxLayerElementMaterial::Create(Mesh, "");
LayerElementMaterials->SetMappingMode(FbxLayerElement::eAllSame);
LayerElementMaterials->SetReferenceMode(FbxLayerElement::eIndexToDirect);
Layer0->SetMaterials(LayerElementMaterials);
UMaterialInterface* Material = Landscape->GetLandscapeMaterial();
FbxSurfaceMaterial* FbxMaterial = Material ? ExportMaterial(Material, 0, FFbxMaterialBakingMeshData()) : NULL;
if (!FbxMaterial)
{
FbxMaterial = CreateDefaultMaterial();
}
const int32 FbxMaterialIndex = FbxActor->AddMaterial(FbxMaterial);
LayerElementMaterials->GetIndexArray().Add(FbxMaterialIndex);
const int32 VisThreshold = 170;
// Copy over the index buffer into the FBX polygons set.
for (int32 ComponentIndex = 0; ComponentIndex < NumComponents; ComponentIndex++)
{
int32 BaseVertIndex = ComponentIndex * VertexCountPerComponent;
for (int32 Y = 0; Y < ComponentSizeQuads; Y++)
{
for (int32 X = 0; X < ComponentSizeQuads; X++)
{
if (VisibilityData[BaseVertIndex + Y * (ComponentSizeQuads + 1) + X] < VisThreshold)
{
Mesh->BeginPolygon();
Mesh->AddPolygon(BaseVertIndex + (X + 0) + (Y + 0)*(ComponentSizeQuads + 1));
Mesh->AddPolygon(BaseVertIndex + (X + 1) + (Y + 1)*(ComponentSizeQuads + 1));
Mesh->AddPolygon(BaseVertIndex + (X + 1) + (Y + 0)*(ComponentSizeQuads + 1));
Mesh->EndPolygon();
Mesh->BeginPolygon();
Mesh->AddPolygon(BaseVertIndex + (X + 0) + (Y + 0)*(ComponentSizeQuads + 1));
Mesh->AddPolygon(BaseVertIndex + (X + 0) + (Y + 1)*(ComponentSizeQuads + 1));
Mesh->AddPolygon(BaseVertIndex + (X + 1) + (Y + 1)*(ComponentSizeQuads + 1));
Mesh->EndPolygon();
}
}
}
}
FbxActor->SetNodeAttribute(Mesh);
}
} // namespace UnFbx