// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= EditorExporters.cpp: Editor exporters. =============================================================================*/ #include "CoreMinimal.h" #include "Animation/Skeleton.h" #include "Audio.h" #include "Misc/MessageDialog.h" #include "HAL/FileManager.h" #include "Misc/FileHelper.h" #include "Misc/Paths.h" #include "Misc/OutputDeviceFile.h" #include "UObject/Object.h" #include "UObject/UObjectIterator.h" #include "Misc/ScopedSlowTask.h" #include "Misc/TextBuffer.h" #include "UObject/Package.h" #include "Engine/EngineTypes.h" #include "Engine/MaterialMerging.h" #include "GameFramework/Actor.h" #include "SceneTypes.h" #include "RawIndexBuffer.h" #include "RenderingThread.h" #include "AI/NavigationSystemBase.h" #include "Model.h" #include "Exporters/Exporter.h" #include "Exporters/AnimSequenceExporterFBX.h" #include "Engine/SkeletalMesh.h" #include "Animation/AnimSequence.h" #include "Editor/EditorEngine.h" #include "Exporters/ExportTextContainer.h" #include "Editor/GroupActor.h" #include "Exporters/LevelExporterFBX.h" #include "Exporters/LevelExporterLOD.h" #include "Exporters/LevelExporterOBJ.h" #include "Exporters/LevelExporterSTL.h" #include "Exporters/LevelExporterT3D.h" #include "Exporters/ModelExporterT3D.h" #include "Exporters/ObjectExporterT3D.h" #include "Exporters/PolysExporterOBJ.h" #include "Exporters/PolysExporterT3D.h" #include "Exporters/SequenceExporterT3D.h" #include "Exporters/SkeletalMeshExporterFBX.h" #include "Exporters/SoundExporterOGG.h" #include "Exporters/SoundExporterWAV.h" #include "Exporters/SoundSurroundExporterWAV.h" #include "Exporters/StaticMeshExporterFBX.h" #include "Exporters/StaticMeshExporterOBJ.h" #include "Exporters/TextBufferExporterTXT.h" #include "Exporters/FbxExportOption.h" #include "Engine/StaticMesh.h" #include "Sound/SoundWave.h" #include "Sound/SoundWaveProcedural.h" #include "Engine/StaticMeshActor.h" #include "Engine/BlueprintGeneratedClass.h" #include "Engine/Polys.h" #include "Misc/FeedbackContext.h" #include "UObject/PropertyPortFlags.h" #include "GameFramework/PhysicsVolume.h" #include "EngineUtils.h" #include "Editor.h" #include "FbxExporter.h" #include "StaticMeshAttributes.h" #include "StaticMeshOperations.h" #include "MaterialUtilities.h" #include "MaterialShared.h" #include "InstancedFoliageActor.h" #include "LandscapeProxy.h" #include "Landscape.h" #include "LandscapeInfo.h" #include "LandscapeComponent.h" #include "LandscapeDataAccess.h" #include "LevelInstance/LevelInstanceActor.h" #include "UnrealExporter.h" #include "InstancedFoliage.h" #include "Engine/Selection.h" #include "AssetExportTask.h" #include "IMaterialBakingModule.h" #include "MaterialBakingStructures.h" #include "MaterialOptions.h" #include "WorldPartition/DataLayer/ExternalDataLayerAsset.h" #include "Components/SkeletalMeshComponent.h" #include "Camera/CameraComponent.h" #include "Components/LightComponent.h" #include "Particles/Emitter.h" #include "Engine/Brush.h" #include "Components/BrushComponent.h" #include "Exporters/ActorExporterT3D.h" #include "Misc/App.h" #include "Widgets/Notifications/SNotificationList.h" #include "Framework/Docking/TabManager.h" #include "Framework/Notifications/NotificationManager.h" DEFINE_LOG_CATEGORY_STATIC(LogEditorExporters, Log, All); namespace Private { bool bLevelExportT3D_UseActorExporters = false; static FAutoConsoleVariableRef CVarDumpHandlerTick( TEXT("UnrealEd.T3DUseActorExporters"), bLevelExportT3D_UseActorExporters, TEXT(" \n"), ECVF_Default ); } struct FScopedFbxExporterInstance { FScopedFbxExporterInstance() { Exporter = UnFbx::FFbxExporter::GetInstance(); } ~FScopedFbxExporterInstance() { UnFbx::FFbxExporter::DeleteInstance(); Exporter = nullptr; } UnFbx::FFbxExporter* GetExporter() { return Exporter; } private: UnFbx::FFbxExporter* Exporter = nullptr; }; /*------------------------------------------------------------------------------ UTextBufferExporterTXT implementation. ------------------------------------------------------------------------------*/ UTextBufferExporterTXT::UTextBufferExporterTXT(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SupportedClass = UTextBuffer::StaticClass(); FormatExtension.Add(TEXT("TXT")); PreferredFormatIndex = 0; FormatDescription.Add(TEXT("Text file")); bText = true; } bool UTextBufferExporterTXT::ExportText(const FExportObjectInnerContext* Context, UObject* Object, const TCHAR* Type, FOutputDevice& Ar, FFeedbackContext* Warn, uint32 PortFlags) { UTextBuffer* TextBuffer = CastChecked( Object ); FString Str( TextBuffer->GetText() ); TCHAR* Start = const_cast(*Str); TCHAR* End = Start + Str.Len(); while( StartStart && (End [-1]=='\r' || End [-1]=='\n' || End [-1]==' ') ) End--; *End = 0; Ar.Log( Start ); return 1; } /*------------------------------------------------------------------------------ USoundExporterWAV implementation. ------------------------------------------------------------------------------*/ USoundExporterWAV::USoundExporterWAV(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SupportedClass = USoundWave::StaticClass(); bText = false; FormatDescription.Add(TEXT("Sound")); FormatExtension.Add(TEXT("WAV")); } bool USoundExporterWAV::SupportsObject(UObject* Object) const { bool bSupportsObject = false; if (Super::SupportsObject(Object)) { USoundWave* SoundWave = CastChecked(Object); if (!SoundWave->IsA()) { bSupportsObject = (SoundWave->NumChannels <= 2); } } return bSupportsObject; } bool USoundExporterWAV::ExportBinary( UObject* Object, const TCHAR* Type, FArchive& Ar, FFeedbackContext* Warn, int32 FileIndex, uint32 PortFlags ) { USoundWave* Sound = CastChecked( Object ); // We a non-const pointer for the sake of calling serialize, but this is on writing FArchive so it's never modified // Assert that's the case if(ensure(Ar.IsSaving())) { TFuture FutureBuffer = Sound->RawData.GetPayload(USoundWave::FEditorAudioBulkData::EPayloadFlags::ReturnCopyWithTransformationData); // This will block, but for now that's ok. void* RawWaveData = (void*)FutureBuffer.Get().GetData(); Ar.Serialize(RawWaveData, FutureBuffer.Get().GetSize()); } return true; } /*------------------------------------------------------------------------------ USoundExporterOGG implementation. ------------------------------------------------------------------------------*/ USoundExporterOGG::USoundExporterOGG(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SupportedClass = USoundWave::StaticClass(); bText = false; FormatDescription.Add(TEXT("Sound")); FormatExtension.Add(TEXT("OGG")); } bool USoundExporterOGG::SupportsObject(UObject* Object) const { bool bSupportsObject = false; if (Super::SupportsObject(Object)) { USoundWave* SoundWave = CastChecked(Object); bSupportsObject = (SoundWave->GetCompressedData("OGG") != NULL); } return bSupportsObject; } bool USoundExporterOGG::ExportBinary( UObject* Object, const TCHAR* Type, FArchive& Ar, FFeedbackContext* Warn, int32 FileIndex, uint32 PortFlags ) { USoundWave* Sound = CastChecked( Object ); FByteBulkData* Bulk = Sound->GetCompressedData("OGG"); if (Bulk) { Ar.Serialize(Bulk->Lock(LOCK_READ_ONLY), Bulk->GetBulkDataSize()); Bulk->Unlock(); return true; } return false; } /*------------------------------------------------------------------------------ USoundSurroundExporterWAV implementation. ------------------------------------------------------------------------------*/ USoundSurroundExporterWAV::USoundSurroundExporterWAV(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SupportedClass = USoundWave::StaticClass(); bText = false; FormatExtension.Add(TEXT("WAV")); FormatDescription.Add(TEXT("Multichannel Sound")); } bool USoundSurroundExporterWAV::SupportsObject(UObject* Object) const { bool bSupportsObject = false; if (Super::SupportsObject(Object)) { USoundWave* SoundWave = CastChecked(Object); bSupportsObject = (SoundWave->NumChannels > 2); } return bSupportsObject; } int32 USoundSurroundExporterWAV::GetFileCount( UObject* Object ) const { return( SPEAKER_Count ); } FString USoundSurroundExporterWAV::GetUniqueFilename( UObject* Object, const TCHAR* Filename, int32 FileIndex, int32 FileCount ) const { static FString SpeakerLocations[SPEAKER_Count] = { TEXT( "_fl" ), // SPEAKER_FrontLeft TEXT( "_fr" ), // SPEAKER_FrontRight TEXT( "_fc" ), // SPEAKER_FrontCenter TEXT( "_lf" ), // SPEAKER_LowFrequency TEXT( "_sl" ), // SPEAKER_SideLeft TEXT( "_sr" ), // SPEAKER_SideRight TEXT( "_bl" ), // SPEAKER_BackLeft TEXT( "_br" ) // SPEAKER_BackRight }; FString ReturnName = FPaths::GetBaseFilename( Filename, false ) + SpeakerLocations[FileIndex] + FString( ".WAV" ); return( ReturnName ); } bool USoundSurroundExporterWAV::ExportBinary( UObject* Object, const TCHAR* Type, FArchive& Ar, FFeedbackContext* Warn, int32 FileIndex, uint32 PortFlags ) { bool bResult = false; USoundWave* Sound = CastChecked( Object ); if ( Sound->ChannelSizes.Num() > 0 ) { if (ensure(Ar.IsSaving())) { // Non-const pointer for the sake of calling serialize, so assert that we are writing an archive. TFuture FutureBuffer = Sound->RawData.GetPayload(USoundWave::FEditorAudioBulkData::EPayloadFlags::ReturnCopyWithTransformationData); uint8* RawWaveData = (uint8*) FutureBuffer.Get().GetData(); if( Sound->ChannelSizes[ FileIndex ] ) { Ar.Serialize( RawWaveData + Sound->ChannelOffsets[ FileIndex ], Sound->ChannelSizes[ FileIndex ] ); } } bResult = Sound->ChannelSizes[ FileIndex ] != 0; } return bResult; } /*------------------------------------------------------------------------------ UObjectExporterT3D implementation. ------------------------------------------------------------------------------*/ UObjectExporterT3D::UObjectExporterT3D(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SupportedClass = UObject::StaticClass(); bText = true; PreferredFormatIndex = 0; FormatExtension.Add(TEXT("T3D")); FormatExtension.Add(TEXT("COPY")); FormatDescription.Add(TEXT("Unreal object text")); FormatDescription.Add(TEXT("Unreal object text")); } bool UObjectExporterT3D::ExportText( const FExportObjectInnerContext* Context, UObject* Object, const TCHAR* Type, FOutputDevice& Ar, FFeedbackContext* Warn, uint32 PortFlags ) { EmitBeginObject(Ar, Object, PortFlags); ExportObjectInner( Context, Object, Ar, PortFlags); EmitEndObject(Ar); return true; } /*------------------------------------------------------------------------------ UPolysExporterT3D implementation. ------------------------------------------------------------------------------*/ UPolysExporterT3D::UPolysExporterT3D(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SupportedClass = UPolys::StaticClass(); bText = true; PreferredFormatIndex = 0; FormatExtension.Add(TEXT("T3D")); FormatDescription.Add(TEXT("Unreal poly text")); } bool UPolysExporterT3D::ExportText( const FExportObjectInnerContext* Context, UObject* Object, const TCHAR* Type, FOutputDevice& Ar, FFeedbackContext* Warn, uint32 PortFlags ) { UPolys* Polys = CastChecked( Object ); Ar.Logf( TEXT("%sBegin PolyList\r\n"), FCString::Spc(TextIndent) ); for( int32 i=0; iElement.Num(); i++ ) { FPoly* Poly = &Polys->Element[i]; TCHAR TempStr[MAX_SPRINTF]=TEXT(""); // Start of polygon plus group/item name if applicable. // The default values need to jive FPoly::Init(). Ar.Logf( TEXT("%s Begin Polygon"), FCString::Spc(TextIndent) ); if( Poly->ItemName != NAME_None ) { Ar.Logf( TEXT(" Item=%s"), *Poly->ItemName.ToString() ); } if( Poly->Material ) { Ar.Logf( TEXT(" Texture=%s"), *Poly->Material->GetPathName() ); } if( Poly->PolyFlags != 0 ) { Ar.Logf( TEXT(" Flags=%i"), Poly->PolyFlags ); } if( Poly->iLink != INDEX_NONE ) { Ar.Logf( TEXT(" Link=%i"), Poly->iLink ); } if ( Poly->LightMapScale != 32.0f ) { Ar.Logf( TEXT(" LightMapScale=%f"), Poly->LightMapScale ); } Ar.Logf( TEXT("\r\n") ); // All coordinates. FVector Base(Poly->Base), Normal(Poly->Normal), TextureU(Poly->TextureU), TextureV(Poly->TextureV); Ar.Logf( TEXT("%s Origin %s\r\n"), FCString::Spc(TextIndent), SetFVECTOR(TempStr,&Base) ); Ar.Logf( TEXT("%s Normal %s\r\n"), FCString::Spc(TextIndent), SetFVECTOR(TempStr,&Normal) ); Ar.Logf( TEXT("%s TextureU %s\r\n"), FCString::Spc(TextIndent), SetFVECTOR(TempStr,&TextureU) ); Ar.Logf( TEXT("%s TextureV %s\r\n"), FCString::Spc(TextIndent), SetFVECTOR(TempStr,&TextureV) ); for( int32 j=0; jVertices.Num(); j++ ) { FVector Vertex(Poly->Vertices[j]); Ar.Logf( TEXT("%s Vertex %s\r\n"), FCString::Spc(TextIndent), SetFVECTOR(TempStr,&Vertex) ); } Ar.Logf( TEXT("%s End Polygon\r\n"), FCString::Spc(TextIndent) ); } Ar.Logf( TEXT("%sEnd PolyList\r\n"), FCString::Spc(TextIndent) ); return 1; } /*------------------------------------------------------------------------------ UModelExporterT3D implementation. ------------------------------------------------------------------------------*/ UModelExporterT3D::UModelExporterT3D(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SupportedClass = UModel::StaticClass(); bText = true; PreferredFormatIndex = 0; FormatExtension.Add(TEXT("T3D")); FormatExtension.Add(TEXT("COPY")); FormatDescription.Add(TEXT("Unreal model text")); FormatDescription.Add(TEXT("Unreal model text")); } bool UModelExporterT3D::ExportText( const FExportObjectInnerContext* Context, UObject* Object, const TCHAR* Type, FOutputDevice& Ar, FFeedbackContext* Warn, uint32 PortFlags ) { UModel* Model = CastChecked( Object ); Ar.Logf( TEXT("%sBegin Brush Name=%s\r\n"), FCString::Spc(TextIndent), *Model->GetName() ); if (!(PortFlags & PPF_SeparateDeclare)) { UExporter::ExportToOutputDevice(Context, Model->Polys, NULL, Ar, Type, TextIndent + 3, PortFlags); } Ar.Logf( TEXT("%sEnd Brush\r\n"), FCString::Spc(TextIndent) ); return 1; } /*------------------------------------------------------------------------------ ULevelExporterT3D implementation. ------------------------------------------------------------------------------*/ void ExporterHelper_DumpPackageInners(const FExportObjectInnerContext* Context, UPackage* InPackage, int32 TabCount) { const TArray* Inners = Context->GetObjectInners(InPackage); if (Inners) { for (int32 InnerIndex = 0; InnerIndex < Inners->Num(); InnerIndex++) { UObject* InnerObj = (*Inners)[InnerIndex]; FString TabString; for (int32 TabOutIndex = 0; TabOutIndex < TabCount; TabOutIndex++) { TabString += TEXT("\t"); } UE_LOG(LogEditorExporters, Log, TEXT("%s%s : %s (%s)"), *TabString, InnerObj ? *InnerObj->GetClass()->GetName() : TEXT("*NULL*"), InnerObj ? *InnerObj->GetName() : TEXT("*NULL*"), InnerObj ? *InnerObj->GetPathName() : TEXT("*NULL*")); UPackage* InnerPackage = Cast(InnerObj); if (InnerPackage) { TabCount++; ExporterHelper_DumpPackageInners(Context, InnerPackage, TabCount); TabCount--; } } } } UActorExporterT3D::UActorExporterT3D(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SupportedClass = AActor::StaticClass(); bText = true; PreferredFormatIndex = 0; FormatExtension.Add(TEXT("T3D")); FormatExtension.Add(TEXT("COPY")); FormatDescription.Add(TEXT("Unreal Actor text")); FormatDescription.Add(TEXT("Unreal Actor text")); } bool UActorExporterT3D::ExportText(const FExportObjectInnerContext* Context, UObject* Object, const TCHAR* Type, FOutputDevice& Ar, FFeedbackContext* Warn, uint32 PortFlags) { AActor* Actor = Cast(Object); if (Actor->ShouldExport()) { // Temporarily unbind dynamic delegates so we don't export the bindings. UBlueprintGeneratedClass::UnbindDynamicDelegates(Actor->GetClass(), Actor); AActor* ParentActor = Actor->GetAttachParentActor(); FName SocketName = Actor->GetAttachParentSocketName(); FDetachmentTransformRules DetachmentTransformRules = FDetachmentTransformRules::KeepWorldTransform; DetachmentTransformRules.bCallModify = false; Actor->DetachFromActor(DetachmentTransformRules); const bool IsUsingActorFolders = Actor->GetLevel() && Actor->GetLevel()->IsUsingActorFolders(); FString ParentActorString = ( ParentActor ? FString::Printf(TEXT(" ParentActor=%s"), *ParentActor->GetName() ) : TEXT("")); FString SocketNameString = ( (ParentActor && SocketName != NAME_None) ? FString::Printf(TEXT(" SocketName=%s"), *SocketName.ToString() ) : TEXT("")); FString GroupActor = (Actor->GroupActor ? FString::Printf(TEXT(" GroupActor=%s"), *Actor->GroupActor->GetName() ) : TEXT("")); FString GroupFolder = (Actor->GroupActor ? FString::Printf(TEXT(" GroupFolder=%s"), *Actor->GroupActor->GetFolderPath().ToString() ) : TEXT("")); FString ActorFolderPath = (IsUsingActorFolders ? FString::Printf(TEXT(" ActorFolderPath=\"%s\""), *Actor->GetFolderPath().ToString()) : TEXT("")); FString CopyPasteId = (Actor->CopyPasteId != INDEX_NONE) ? FString::Printf(TEXT(" CopyPasteId=%d"), Actor->CopyPasteId ) : TEXT(""); FString ContentBundleGuid = (Actor->GetContentBundleGuid().IsValid() ? FString::Printf(TEXT(" ActorContentBundleGuid=%s"), *Actor->GetContentBundleGuid().ToString()) : TEXT("")); FString ExternalDataLayer = (Actor->GetExternalDataLayerAsset() ? FString::Printf(TEXT(" ExternalDataLayerAsset=%s"), *FSoftObjectPath(Actor->GetExternalDataLayerAsset()).ToString()) : TEXT("")); Ar.Logf( TEXT("%sBegin Actor Class=%s Name=%s Archetype=%s%s%s%s%s%s%s%s%s"), FCString::Spc(TextIndent), *Actor->GetClass()->GetPathName(), *Actor->GetName(), *FObjectPropertyBase::GetExportPath(Actor->GetArchetype(), nullptr, nullptr, (PortFlags | PPF_Delimited) & ~PPF_ExportsNotFullyQualified), *ParentActorString, *SocketNameString, *GroupActor, *GroupFolder, *ActorFolderPath, *CopyPasteId, *ContentBundleGuid, *ExternalDataLayer); // When exporting for diffs, export paths can cause false positives. since diff files don't get imported, we can // skip adding this info the file. if (!(PortFlags & PPF_ForDiff)) { // Emit the actor path Ar.Logf(TEXT(" ExportPath=%s"), *FObjectPropertyBase::GetExportPath(Actor, nullptr, nullptr, (PortFlags | PPF_Delimited) & ~PPF_ExportsNotFullyQualified)); } Ar.Logf(LINE_TERMINATOR); ExportRootScope = Actor; ExportObjectInner( Context, Actor, Ar, PortFlags | PPF_ExportsNotFullyQualified ); ExportRootScope = nullptr; Ar.Logf( TEXT("%sEnd Actor\r\n"), FCString::Spc(TextIndent) ); Actor->AttachToActor(ParentActor, FAttachmentTransformRules::KeepWorldTransform, SocketName); // Restore dynamic delegate bindings. UBlueprintGeneratedClass::BindDynamicDelegates(Actor->GetClass(), Actor); } else if (GEditor) { GEditor->GetSelectedActors()->Deselect(Actor); } return true; } bool UActorExporterT3D::SupportsObject(UObject* Object) const { if (Private::bLevelExportT3D_UseActorExporters) { return Super::SupportsObject(Object); } return false; } UGroupActorExporterT3D::UGroupActorExporterT3D(const FObjectInitializer& ObjectInitializer) { SupportedClass = AGroupActor::StaticClass(); } bool UGroupActorExporterT3D::ExportText(const FExportObjectInnerContext* Context, UObject* Object, const TCHAR* Type, FOutputDevice& Ar, FFeedbackContext* Warn, uint32 PortFlags) { // Don't export group actors return true; } UPhysicsVolumeExporterT3D::UPhysicsVolumeExporterT3D(const FObjectInitializer& ObjectInitializer) { SupportedClass = APhysicsVolume::StaticClass(); } bool UPhysicsVolumeExporterT3D::ExportText(const FExportObjectInnerContext* Context, UObject* Object, const TCHAR* Type, FOutputDevice& Ar, FFeedbackContext* Warn, uint32 PortFlags) { APhysicsVolume* PhysicsVolume = Cast(Object); if (UWorld* World = PhysicsVolume->GetWorld()) { if (World->GetDefaultPhysicsVolume() == PhysicsVolume) { // Don't export the default physics volume, as it doesn't have a UModel associated with it // and thus will not import properly. return true; } return Super::ExportText(Context, Object, Type, Ar, Warn, PortFlags); } return true; } ULevelExporterT3D::ULevelExporterT3D(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SupportedClass = UWorld::StaticClass(); bText = true; PreferredFormatIndex = 0; FormatExtension.Add(TEXT("T3D")); FormatExtension.Add(TEXT("COPY")); FormatDescription.Add(TEXT("Unreal world text")); FormatDescription.Add(TEXT("Unreal world text")); } bool ULevelExporterT3D::ExportText( const FExportObjectInnerContext* Context, UObject* Object, const TCHAR* Type, FOutputDevice& Ar, FFeedbackContext* Warn, uint32 PortFlags ) { UWorld* World = CastChecked( Object ); APhysicsVolume* DefaultPhysicsVolume = World->GetDefaultPhysicsVolume(); UnMarkAllObjects(EObjectMark(OBJECTMARK_TagExp | OBJECTMARK_TagImp)); UPackage* MapPackage = NULL; if ((PortFlags & PPF_Copy) == 0) { // If we are not copying to clipboard, then export objects contained in the map package itself... MapPackage = Object->GetOutermost(); } // this is the top level in the .t3d file if (MapPackage) { Ar.Logf(TEXT("%sBegin Map Name=%s\r\n"), FCString::Spc(TextIndent), *(MapPackage->GetName())); } else { Ar.Logf(TEXT("%sBegin Map\r\n"), FCString::Spc(TextIndent)); } // are we exporting all actors or just selected actors? bool bAllActors = FCString::Stricmp(Type,TEXT("COPY"))!=0 && !bSelectedOnly; TextIndent += 3; ULevel* Level; // start a new level section if (FCString::Stricmp(Type, TEXT("COPY")) == 0) { // for copy and paste, we want to select actors in the current level Level = World->GetCurrentLevel(); // if we are copy/pasting, then we don't name the level - we paste into the current level Ar.Logf(TEXT("%sBegin Level\r\n"), FCString::Spc(TextIndent)); // mark that we are doing a clipboard copy PortFlags |= PPF_Copy; } else { // for export, we only want the persistent level Level = World->PersistentLevel; //@todo seamless if we are exporting only selected, should we export from all levels? or maybe from the current level? // if we aren't copy/pasting, then we name the level so that when we import, we get the same level structure Ar.Logf(TEXT("%sBegin Level NAME=%s\r\n"), FCString::Spc(TextIndent), *Level->GetName()); } TextIndent += 3; const int32 ActorNumberToCopy = bAllActors ? Level->Actors.Num() : Context->GetObjectNumber(); FScopedSlowTask SlowTask(ActorNumberToCopy, NSLOCTEXT("UnrealEd", "ExportingActors", "Exporting Actors")); const bool bShowCancelButton = true; SlowTask.MakeDialogDelayed(1.f, bShowCancelButton); // Allow for a pre-specified (and pre-sorted) list of level inners TArray> SpecifiedActors; const TArray* LevelInners = Context->GetObjectInners(Level); if (LevelInners) { SpecifiedActors.Reserve(LevelInners->Num()); for (UObject* Inner : *LevelInners) { if (AActor* Actor = Cast(Inner)) { if (ensureMsgf(Actor->GetLevel() == Level, TEXT("Specified actors must actually be inners of the level!"))) { SpecifiedActors.Add(Actor); } } } } // loop through all of the actors just in this level const TArray>& Actors = LevelInners ? SpecifiedActors : Level->Actors; for (AActor* Actor : Actors) { if (SlowTask.ShouldCancel()) { break; } if (Private::bLevelExportT3D_UseActorExporters) { // Ensure the actor is currently selected if( Actor && ( bAllActors || IsObjectSelectedForExport(Context, Actor) ) ) { UExporter* Exporter = UExporter::FindExporter(Actor, Type); if (Exporter) { Exporter->TextIndent = TextIndent; Exporter->bSelectedOnly = bSelectedOnly; bool Result = Exporter->ExportText(Context, Actor, Type, Ar, Warn, PortFlags); if (Result == false) { return Result; } } SlowTask.EnterProgressFrame(); } } else { if ( Actor == DefaultPhysicsVolume ) { continue; } // Ensure actor is not a group if grouping is disabled and that the actor is currently selected if( Actor && !Actor->IsA(AGroupActor::StaticClass()) && ( bAllActors || IsObjectSelectedForExport(Context, Actor) ) ) { if (Actor->ShouldExport()) { // Temporarily unbind dynamic delegates so we don't export the bindings. UBlueprintGeneratedClass::UnbindDynamicDelegates(Actor->GetClass(), Actor); AActor* ParentActor = Actor->GetAttachParentActor(); FName SocketName = Actor->GetAttachParentSocketName(); FDetachmentTransformRules DetachmentTransformRules = FDetachmentTransformRules::KeepWorldTransform; DetachmentTransformRules.bCallModify = false; Actor->DetachFromActor(DetachmentTransformRules); FString ParentActorString = ( ParentActor ? FString::Printf(TEXT(" ParentActor=%s"), *ParentActor->GetName() ) : TEXT("")); FString SocketNameString = ( (ParentActor && SocketName != NAME_None) ? FString::Printf(TEXT(" SocketName=%s"), *SocketName.ToString() ) : TEXT("")); FString GroupActor = (Actor->GroupActor ? FString::Printf(TEXT(" GroupActor=%s"), *Actor->GroupActor->GetName() ) : TEXT("")); FString GroupFolder = (Actor->GroupActor ? FString::Printf(TEXT(" GroupFolder=%s"), *Actor->GroupActor->GetFolderPath().ToString() ) : TEXT("")); FString ActorFolderPath = (Level->IsUsingActorFolders() ? FString::Printf(TEXT(" ActorFolderPath=\"%s\""), *Actor->GetFolderPath().ToString()) : TEXT("")); FString CopyPasteId = (Actor->CopyPasteId != INDEX_NONE) ? FString::Printf(TEXT(" CopyPasteId=%d"), Actor->CopyPasteId ) : TEXT(""); FString ContentBundleGuid = (Actor->GetContentBundleGuid().IsValid() ? FString::Printf(TEXT(" ActorContentBundleGuid=%s"), *Actor->GetContentBundleGuid().ToString()) : TEXT("")); FString ExternalDataLayer = (Actor->GetExternalDataLayerAsset() ? FString::Printf(TEXT(" ExternalDataLayerAsset=%s"), *FSoftObjectPath(Actor->GetExternalDataLayerAsset()).ToString()) : TEXT("")); Ar.Logf( TEXT("%sBegin Actor Class=%s Name=%s Archetype=%s%s%s%s%s%s%s%s%s"), FCString::Spc(TextIndent), *Actor->GetClass()->GetPathName(), *Actor->GetName(), *FObjectPropertyBase::GetExportPath(Actor->GetArchetype(), nullptr, nullptr, (PortFlags | PPF_Delimited) & ~PPF_ExportsNotFullyQualified), *ParentActorString, *SocketNameString, *GroupActor, *GroupFolder, *ActorFolderPath, *CopyPasteId, *ContentBundleGuid, *ExternalDataLayer); // When exporting for diffs, export paths can cause false positives. since diff files don't get imported, we can // skip adding this info the file. if (!(PortFlags & PPF_ForDiff)) { // Emit the actor path Ar.Logf(TEXT(" ExportPath=%s"), *FObjectPropertyBase::GetExportPath(Actor, nullptr, nullptr, (PortFlags | PPF_Delimited) & ~PPF_ExportsNotFullyQualified)); } Ar.Logf(LINE_TERMINATOR); ExportRootScope = Actor; ExportObjectInner( Context, Actor, Ar, PortFlags | PPF_ExportsNotFullyQualified ); ExportRootScope = nullptr; Ar.Logf( TEXT("%sEnd Actor\r\n"), FCString::Spc(TextIndent) ); Actor->AttachToActor(ParentActor, FAttachmentTransformRules::KeepWorldTransform, SocketName); // Restore dynamic delegate bindings. UBlueprintGeneratedClass::BindDynamicDelegates(Actor->GetClass(), Actor); } else if (GEditor) { GEditor->GetSelectedActors()->Deselect(Actor); } SlowTask.EnterProgressFrame(); } } } TextIndent -= 3; Ar.Logf(TEXT("%sEnd Level\r\n"), FCString::Spc(TextIndent)); TextIndent -= 3; // Export information about the first selected surface in the map. Used for copying/pasting // information from poly to poly. Ar.Logf( TEXT("%sBegin Surface\r\n"), FCString::Spc(TextIndent) ); TCHAR TempStr[256]; const UModel* Model = World->GetModel(); for (const FBspSurf& Poly : Model->Surfs) { if (Poly.PolyFlags & PF_Selected ) { FVector pBase = (FVector)Model->Points[Poly.pBase]; FVector vTextureU = (FVector)Model->Vectors[Poly.vTextureU]; FVector vTextureV = (FVector)Model->Vectors[Poly.vTextureV]; FVector vNormal = (FVector)Model->Vectors[Poly.vNormal]; Ar.Logf(TEXT("%sTEXTURE=%s\r\n"), FCString::Spc(TextIndent + 3), *Poly.Material->GetPathName()); Ar.Logf(TEXT("%sBASE %s\r\n"), FCString::Spc(TextIndent + 3), SetFVECTOR(TempStr, &pBase)); Ar.Logf(TEXT("%sTEXTUREU %s\r\n"), FCString::Spc(TextIndent + 3), SetFVECTOR(TempStr, &vTextureU)); Ar.Logf(TEXT("%sTEXTUREV %s\r\n"), FCString::Spc(TextIndent + 3), SetFVECTOR(TempStr, &vTextureV)); Ar.Logf(TEXT("%sNORMAL %s\r\n"), FCString::Spc(TextIndent + 3), SetFVECTOR(TempStr, &vNormal)); Ar.Logf(TEXT("%sPOLYFLAGS=%d\r\n"), FCString::Spc(TextIndent + 3), Poly.PolyFlags); break; } } Ar.Logf( TEXT("%sEnd Surface\r\n"), FCString::Spc(TextIndent) ); Ar.Logf( TEXT("%sEnd Map\r\n"), FCString::Spc(TextIndent) ); return !SlowTask.ShouldCancel(); } void ULevelExporterT3D::ExportComponentExtra(const FExportObjectInnerContext* Context, const TArray& Components, FOutputDevice& Ar, uint32 PortFlags) { for (UActorComponent* ActorComponent : Components) { if (ActorComponent != nullptr && ActorComponent->GetWorld() != nullptr) { // Go through all FoliageActors in the world, since we support cross-level bases for (TActorIterator It(ActorComponent->GetWorld()); It; ++It) { AInstancedFoliageActor* IFA = *It; if (IsValid(IFA)) { auto FoliageInstanceMap = IFA->GetInstancesForComponent(ActorComponent); for (const auto& MapEntry : FoliageInstanceMap) { Ar.Logf(TEXT("%sBegin Foliage FoliageType=%s Component=%s%s"), FCString::Spc(TextIndent), *MapEntry.Key->GetPathName(), *ActorComponent->GetName(), LINE_TERMINATOR); for (const FFoliageInstancePlacementInfo* Inst : MapEntry.Value) { Ar.Logf(TEXT("%sLocation=%f,%f,%f Rotation=%f,%f,%f PreAlignRotation=%f,%f,%f DrawScale3D=%f,%f,%f Flags=%u%s"), FCString::Spc(TextIndent+3), Inst->Location.X, Inst->Location.Y, Inst->Location.Z, Inst->Rotation.Pitch, Inst->Rotation.Yaw, Inst->Rotation.Roll, Inst->PreAlignRotation.Pitch, Inst->PreAlignRotation.Yaw, Inst->PreAlignRotation.Roll, Inst->DrawScale3D.X, Inst->DrawScale3D.Y, Inst->DrawScale3D.Z, Inst->Flags, LINE_TERMINATOR); } Ar.Logf(TEXT("%sEnd Foliage%s"), FCString::Spc(TextIndent), LINE_TERMINATOR); } } } } } } void ULevelExporterT3D::ExportPackageObject(FExportPackageParams& ExpPackageParams){} void ULevelExporterT3D::ExportPackageInners(FExportPackageParams& ExpPackageParams){} /*------------------------------------------------------------------------------ ULevelExporterSTL implementation. ------------------------------------------------------------------------------*/ ULevelExporterSTL::ULevelExporterSTL(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SupportedClass = UWorld::StaticClass(); bText = true; PreferredFormatIndex = 0; FormatExtension.Add(TEXT("STL")); FormatDescription.Add(TEXT("Stereolithography")); } bool ULevelExporterSTL::ExportText( const FExportObjectInnerContext* Context, UObject* Object, const TCHAR* Type, FOutputDevice& Ar, FFeedbackContext* Warn, uint32 PortFlags ) { //@todo seamless - this needs to be world, like the t3d version above UWorld* World = CastChecked(Object); ULevel* Level = World->PersistentLevel; for (FThreadSafeObjectIterator It; It; ++It) { It->UnMark(EObjectMark(OBJECTMARK_TagImp | OBJECTMARK_TagExp)); } // // GATHER TRIANGLES // TArray Triangles; for( int32 iActor=0; iActorActors.Num(); iActor++ ) { // Landscape ALandscape* Landscape = Cast(Level->Actors[iActor]); if( Landscape && ( !bSelectedOnly || IsObjectSelectedForExport(Context, Landscape) ) ) { ULandscapeInfo* LandscapeInfo = Landscape ? Landscape->GetLandscapeInfo() : NULL; if( Landscape && LandscapeInfo ) { auto SelectedComponents = LandscapeInfo->GetSelectedComponents(); // Export data for each component for( auto It = LandscapeInfo->XYtoComponentMap.CreateIterator(); It; ++It ) { if (bSelectedOnly && SelectedComponents.Num() && !SelectedComponents.Contains(It.Value())) { continue; } ULandscapeComponent* Component = It.Value(); FLandscapeComponentDataInterface CDI(Component); for( int32 y=0;yComponentSizeQuads;y++ ) { for( int32 x=0;xComponentSizeQuads;x++ ) { FVector P00 = CDI.GetWorldVertex(x,y); FVector P01 = CDI.GetWorldVertex(x,y+1); FVector P11 = CDI.GetWorldVertex(x+1,y+1); FVector P10 = CDI.GetWorldVertex(x+1,y); // triangulation matches FLandscapeIndexBuffer constructor Triangles.Add(P00); Triangles.Add(P11); Triangles.Add(P10); Triangles.Add(P00); Triangles.Add(P01); Triangles.Add(P11); } } } } } // Static meshes AStaticMeshActor* Actor = Cast(Level->Actors[iActor]); if( Actor && ( !bSelectedOnly || IsObjectSelectedForExport(Context, Actor) ) && Actor->GetStaticMeshComponent()->GetStaticMesh() && Actor->GetStaticMeshComponent()->GetStaticMesh()->HasValidRenderData() ) { FStaticMeshLODResources& LODModel = Actor->GetStaticMeshComponent()->GetStaticMesh()->GetRenderData()->LODResources[0]; FIndexArrayView Indices = LODModel.IndexBuffer.GetArrayView(); int32 NumSections = LODModel.Sections.Num(); for (int32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) { const FStaticMeshSection& Section = LODModel.Sections[SectionIndex]; for (int32 TriIndex = 0; TriIndex < (int32)Section.NumTriangles; ++TriIndex) { int32 BaseIndex = Section.FirstIndex + TriIndex * 3; for( int32 v = 2 ; v > -1 ; v-- ) { int32 i = Indices[BaseIndex + v]; FVector vtx = Actor->ActorToWorld().TransformPosition((FVector)LODModel.VertexBuffers.PositionVertexBuffer.VertexPosition(i)); Triangles.Add(vtx); } } } } } // BSP Surfaces for( int32 i=0;iGetModel()->Nodes.Num();i++ ) { FBspNode* Node = &World->GetModel()->Nodes[i]; if( !bSelectedOnly || World->GetModel()->Surfs[Node->iSurf].PolyFlags&PF_Selected ) { if( Node->NumVertices > 2 ) { FVector vtx1(World->GetModel()->Points[World->GetModel()->Verts[Node->iVertPool+0].pVertex]), vtx2(World->GetModel()->Points[World->GetModel()->Verts[Node->iVertPool+1].pVertex]), vtx3; for( int32 v = 2 ; v < Node->NumVertices ; v++ ) { vtx3 = (FVector)World->GetModel()->Points[World->GetModel()->Verts[Node->iVertPool+v].pVertex]; Triangles.Add( vtx1 ); Triangles.Add( vtx2 ); Triangles.Add( vtx3 ); vtx2 = vtx3; } } } } // // WRITE THE FILE // Ar.Logf( TEXT("%ssolid LevelBSP\r\n"), FCString::Spc(TextIndent) ); for( int32 tri = 0 ; tri < Triangles.Num() ; tri += 3 ) { FVector vtx[3]; vtx[0] = Triangles[tri] * FVector(1,-1,1); vtx[1] = Triangles[tri+1] * FVector(1,-1,1); vtx[2] = Triangles[tri+2] * FVector(1,-1,1); FPlane Normal( vtx[0], vtx[1], vtx[2] ); Ar.Logf( TEXT("%sfacet normal %1.6f %1.6f %1.6f\r\n"), FCString::Spc(TextIndent+2), Normal.X, Normal.Y, Normal.Z ); Ar.Logf( TEXT("%souter loop\r\n"), FCString::Spc(TextIndent+4) ); Ar.Logf( TEXT("%svertex %1.6f %1.6f %1.6f\r\n"), FCString::Spc(TextIndent+6), vtx[0].X, vtx[0].Y, vtx[0].Z ); Ar.Logf( TEXT("%svertex %1.6f %1.6f %1.6f\r\n"), FCString::Spc(TextIndent+6), vtx[1].X, vtx[1].Y, vtx[1].Z ); Ar.Logf( TEXT("%svertex %1.6f %1.6f %1.6f\r\n"), FCString::Spc(TextIndent+6), vtx[2].X, vtx[2].Y, vtx[2].Z ); Ar.Logf( TEXT("%sendloop\r\n"), FCString::Spc(TextIndent+4) ); Ar.Logf( TEXT("%sendfacet\r\n"), FCString::Spc(TextIndent+2) ); } Ar.Logf( TEXT("%sendsolid LevelBSP\r\n"), FCString::Spc(TextIndent) ); Triangles.Empty(); return 1; } /*------------------------------------------------------------------------------ Helper classes for the OBJ exporters. ------------------------------------------------------------------------------*/ // An individual face. class FOBJFace { public: // index into FOBJGeom::VertexData (local within FOBJGeom) uint32 VertexIndex[3]; /** List of vertices that make up this face. */ /** The material that was applied to this face. */ UMaterialInterface* Material; }; class FOBJVertex { public: // position FVector Vert; // texture coordiante FVector2D UV; // normal FVector Normal; // FLinearColor Colors[3]; }; // A geometric object. This will show up as a separate object when imported into a modeling program. class FOBJGeom { public: /** List of faces that make up this object. */ TArray Faces; /** Vertex positions that make up this object. */ TArray VertexData; /** Name used when writing this object to the OBJ file. */ FString Name; // Constructors. FORCEINLINE FOBJGeom( const FString& InName ) : Name( InName ) {} }; inline FString FixupMaterialName(UMaterialInterface* Material) { return Material->GetName().Replace(TEXT("."), TEXT("_")).Replace(TEXT(":"), TEXT("_")); } /** * Adds the given actor's mesh to the GOBJObjects array if possible * * @param Actor The actor to export * @param Objects The array that contains cached exportable object data * @param Materials Optional set of materials to gather all used materials by the objects (currently only StaticMesh materials are supported) */ static void AddActorToOBJs(AActor* Actor, TArray& Objects, TSet* Materials, bool bSelectedOnly) { FMatrix LocalToWorld = Actor->ActorToWorld().ToMatrixWithScale(); // Landscape ALandscape* Landscape = Cast( Actor ); ULandscapeInfo* LandscapeInfo = Landscape ? Landscape->GetLandscapeInfo() : NULL; if( Landscape && LandscapeInfo ) { auto SelectedComponents = LandscapeInfo->GetSelectedComponents(); // Export data for each component for( auto It = Landscape->GetLandscapeInfo()->XYtoComponentMap.CreateIterator(); It; ++It ) { if (bSelectedOnly && SelectedComponents.Num() && !SelectedComponents.Contains(It.Value())) { continue; } ULandscapeComponent* Component = It.Value(); FLandscapeComponentDataInterface CDI(Component, Landscape->ExportLOD); const int32 ComponentSizeQuads = ((Component->ComponentSizeQuads+1)>>Landscape->ExportLOD)-1; const int32 SubsectionSizeQuads = ((Component->SubsectionSizeQuads+1)>>Landscape->ExportLOD)-1; const float ScaleFactor = (float)Component->ComponentSizeQuads / (float)ComponentSizeQuads; FOBJGeom* OBJGeom = new FOBJGeom( Component->GetName() ); OBJGeom->VertexData.AddZeroed( FMath::Square(ComponentSizeQuads + 1) ); OBJGeom->Faces.AddZeroed( FMath::Square(ComponentSizeQuads) * 2 ); // Check if there are any holes TArray64 RawVisData; uint8* VisDataMap = NULL; int32 TexIndex = INDEX_NONE; int32 WeightMapSize = (SubsectionSizeQuads + 1) * Component->NumSubsections; int32 ChannelOffsets[4] = {(int32)STRUCT_OFFSET(FColor,R),(int32)STRUCT_OFFSET(FColor,G),(int32)STRUCT_OFFSET(FColor,B),(int32)STRUCT_OFFSET(FColor,A)}; const TArray& ComponentWeightmapLayerAllocations = Component->GetWeightmapLayerAllocations(); const TArray& ComponentWeightmapTextures = Component->GetWeightmapTextures(); for( int32 AllocIdx=0;AllocIdx < ComponentWeightmapLayerAllocations.Num(); AllocIdx++ ) { const FWeightmapLayerAllocationInfo& AllocInfo = ComponentWeightmapLayerAllocations[AllocIdx]; if( AllocInfo.LayerInfo == ALandscapeProxy::VisibilityLayer ) { TexIndex = AllocInfo.WeightmapTextureIndex; ComponentWeightmapTextures[TexIndex]->Source.GetMipData(RawVisData, 0); VisDataMap = RawVisData.GetData() + ChannelOffsets[AllocInfo.WeightmapTextureChannel]; } } // Export verts FOBJVertex* Vert = OBJGeom->VertexData.GetData(); for( int32 y=0;yVert = WorldPos; Vert->UV = FVector2D(Component->GetSectionBase().X + x * ScaleFactor, Component->GetSectionBase().Y + y * ScaleFactor); Vert->Normal = WorldTangentZ; Vert++; } } int32 VisThreshold = 170; int32 SubNumX, SubNumY, SubX, SubY; FOBJFace* Face = OBJGeom->Faces.GetData(); for( int32 y=0;y= VisThreshold; // triangulation matches FLandscapeIndexBuffer constructor Face->VertexIndex[0] = (x+0) + (y+0) * (ComponentSizeQuads+1); Face->VertexIndex[1] = bInvisible ? Face->VertexIndex[0] : (x+1) + (y+1) * (ComponentSizeQuads+1); Face->VertexIndex[2] = bInvisible ? Face->VertexIndex[0] : (x+1) + (y+0) * (ComponentSizeQuads+1); Face++; Face->VertexIndex[0] = (x+0) + (y+0) * (ComponentSizeQuads+1); Face->VertexIndex[1] = bInvisible ? Face->VertexIndex[0] : (x+0) + (y+1) * (ComponentSizeQuads+1); Face->VertexIndex[2] = bInvisible ? Face->VertexIndex[0] : (x+1) + (y+1) * (ComponentSizeQuads+1); Face++; } } Objects.Add( OBJGeom ); } } // Static mesh components UStaticMeshComponent* StaticMeshComponent = NULL; UStaticMesh* StaticMesh = NULL; TInlineComponentArray StaticMeshComponents; Actor->GetComponents(StaticMeshComponents); for( int32 j=0; jIsRegistered() && StaticMeshComponent->GetStaticMesh() && StaticMeshComponent->GetStaticMesh()->HasValidRenderData() ) { LocalToWorld = StaticMeshComponent->GetComponentTransform().ToMatrixWithScale(); StaticMesh = StaticMeshComponent->GetStaticMesh(); if (StaticMesh) { // make room for the faces FOBJGeom* OBJGeom = new FOBJGeom(StaticMeshComponents.Num() > 1 ? StaticMesh->GetName() : Actor->GetName()); FStaticMeshLODResources* RenderData = &StaticMesh->GetRenderData()->LODResources[0]; FIndexArrayView Indices = RenderData->IndexBuffer.GetArrayView(); uint32 NumIndices = Indices.Num(); // 3 indices for each triangle check(NumIndices % 3 == 0); uint32 TriangleCount = NumIndices / 3; OBJGeom->Faces.AddUninitialized(TriangleCount); uint32 VertexCount = RenderData->VertexBuffers.PositionVertexBuffer.GetNumVertices(); OBJGeom->VertexData.AddUninitialized(VertexCount); FOBJVertex* VerticesOut = OBJGeom->VertexData.GetData(); check(VertexCount == RenderData->VertexBuffers.StaticMeshVertexBuffer.GetNumVertices()); FMatrix LocalToWorldInverseTranspose = LocalToWorld.InverseFast().GetTransposed(); for (uint32 i = 0; i < VertexCount; i++) { // Vertices VerticesOut[i].Vert = LocalToWorld.TransformPosition((FVector)RenderData->VertexBuffers.PositionVertexBuffer.VertexPosition(i)); // UVs from channel 0 VerticesOut[i].UV = FVector2D(RenderData->VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(i, 0)); // Normal VerticesOut[i].Normal = LocalToWorldInverseTranspose.TransformVector((FVector4)RenderData->VertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(i)); } bool bFlipCullMode = LocalToWorld.RotDeterminant() < 0.0f; uint32 CurrentTriangleId = 0; for (int32 SectionIndex = 0; SectionIndex < RenderData->Sections.Num(); ++SectionIndex) { FStaticMeshSection& Section = RenderData->Sections[SectionIndex]; UMaterialInterface* Material = 0; // Get the material for this triangle by first looking at the material overrides array and if that is NULL by looking at the material array in the original static mesh Material = StaticMeshComponent->GetMaterial(Section.MaterialIndex); // cache the set of needed materials if desired if (Materials && Material) { Materials->Add(Material); } for (uint32 i = 0; i < Section.NumTriangles; i++) { FOBJFace& OBJFace = OBJGeom->Faces[CurrentTriangleId++]; uint32 a = Indices[Section.FirstIndex + i * 3 + 0]; uint32 b = Indices[Section.FirstIndex + i * 3 + 1]; uint32 c = Indices[Section.FirstIndex + i * 3 + 2]; if (bFlipCullMode) { Swap(a, c); } OBJFace.VertexIndex[0] = a; OBJFace.VertexIndex[1] = b; OBJFace.VertexIndex[2] = c; // Material OBJFace.Material = Material; } } Objects.Add(OBJGeom); } } } } // @param Material must not be 0 // @param MatProp e.g. MP_DiffuseColor static void ExportMaterialPropertyTexture(const FString& BMPFilename, UMaterialInterface* Material, const EMaterialProperty MatProp) { check(Material); // make the BMP for the diffuse channel TArray OutputBMP; FIntPoint OutSize{}; bool bIsValidProperty = FMaterialUtilities::SupportsExport(IsOpaqueBlendMode(*Material), MatProp); if (bIsValidProperty) { FMeshData MeshSettings; MeshSettings.MeshDescription = nullptr; MeshSettings.TextureCoordinateBox = FBox2D(FVector2D(0.0f, 0.0f), FVector2D(1.0f, 1.0f)); MeshSettings.TextureCoordinateIndex = 0; FMaterialData MaterialSettings; MaterialSettings.Material = Material; // Add all user defined properties for baking out FPropertyEntry Entry(MatProp); if (!Entry.bUseConstantValue && Material->IsPropertyActive(Entry.Property) && Entry.Property != MP_MAX) { FIntPoint TextureSize = FMaterialUtilities::FindMaxTextureSize(Material, MatProp); MaterialSettings.PropertySizes.Add(Entry.Property, TextureSize); } TArray MeshSettingPtrs{ &MeshSettings }; TArray MaterialSettingPtrs{ &MaterialSettings }; IMaterialBakingModule& Module = FModuleManager::Get().LoadModuleChecked("MaterialBaking"); bool bSetData = false; bool bSetSize = false; TArray BakeOutputs; Module.BakeMaterials(MaterialSettingPtrs, MeshSettingPtrs, BakeOutputs); if (BakeOutputs.Num() > 0) { FBakeOutput& Output = BakeOutputs[0]; if (TArray* PropertyData = Output.PropertyData.Find(MatProp)) { OutputBMP = MoveTemp(*PropertyData); bSetData = true; } if (FIntPoint* PropertySize = Output.PropertySizes.Find(MatProp)) { OutSize = *PropertySize; bSetSize = true; } } bIsValidProperty = bSetData && bSetSize; } // make invalid textures a solid red if (!bIsValidProperty) { OutSize = FIntPoint(1, 1); OutputBMP.Empty(); OutputBMP.Add(FColor(255, 0, 0, 255)); } // export the diffuse channel bmp FFileHelper::CreateBitmap(*BMPFilename, OutSize.X, OutSize.Y, OutputBMP.GetData()); } /** * Exports the GOBJObjects array to the given Archive * * @param FileAr The main archive to output device. However, if MemAr exists, it will write to that until and flush it out for each object * @param MemAr Optional string output device for caching writes to * @param Warn Feedback context for updating status * @param OBJFilename Name of the main OBJ file to export to, used for tagalong files (.mtl, etc) * @param Objects The list of objects to export * @param Materials Optional list of materials to export */ void ExportOBJs(FOutputDevice& FileAr, FStringOutputDevice* MemAr, FFeedbackContext* Warn, const FString& OBJFilename, TArray& Objects, const TSet* Materials, uint32 &IndexOffset) { // write to the memory archive if it exists, otherwise use the FileAr FOutputDevice& Ar = MemAr ? *MemAr : FileAr; //Make sure we don't corrupt the obj file with terminator line FileAr.SetAutoEmitLineTerminator(false); // export extra material info if we added any if (Materials) { // make a .MTL file next to the .obj file that contains the materials FString MaterialLibFilename = FPaths::GetBaseFilename(OBJFilename, false) + TEXT(".mtl"); // use the output device file, just like the Exporter makes for the .obj, no backup TUniquePtr MaterialLib = MakeUnique(*MaterialLibFilename, true); MaterialLib->SetSuppressEventTag(true); MaterialLib->SetAutoEmitLineTerminator(false); // export the material set to a mtllib int32 MaterialIndex = 0; for (TSet::TConstIterator It(*Materials); It; ++It, ++MaterialIndex) { UMaterialInterface* Material = *It; FString MaterialName = FixupMaterialName(Material); // export the material info MaterialLib->Logf(TEXT("newmtl %s\r\n"), *MaterialName); { FString BMPFilename = FPaths::Combine(FPaths::GetPath(MaterialLibFilename), MaterialName + TEXT("_D.bmp")); ExportMaterialPropertyTexture(BMPFilename, Material, MP_BaseColor); MaterialLib->Logf(TEXT("\tmap_Kd %s\r\n"), *FPaths::GetCleanFilename(BMPFilename)); } { FString BMPFilename = FPaths::Combine(FPaths::GetPath(MaterialLibFilename), MaterialName + TEXT("_S.bmp")); ExportMaterialPropertyTexture(BMPFilename, Material, MP_Specular); MaterialLib->Logf(TEXT("\tmap_Ks %s\r\n"), *FPaths::GetCleanFilename(BMPFilename)); } { FString BMPFilename = FPaths::Combine(FPaths::GetPath(MaterialLibFilename), MaterialName + TEXT("_N.bmp")); ExportMaterialPropertyTexture(BMPFilename, Material, MP_Normal); MaterialLib->Logf(TEXT("\tbump %s\r\n"), *FPaths::GetCleanFilename(BMPFilename)); } MaterialLib->Logf(TEXT("\r\n")); } Ar.Logf(TEXT("mtllib %s\n"), *FPaths::GetCleanFilename(MaterialLibFilename)); } for( int32 o = 0 ; o < Objects.Num() ; ++o ) { FOBJGeom* object = Objects[o]; UMaterialInterface* CurrentMaterial = NULL; // Object header Ar.Logf( TEXT("g %s\n"), *object->Name ); Ar.Logf( TEXT("\n") ); // Verts for( int32 f = 0 ; f < object->VertexData.Num() ; ++f ) { const FOBJVertex& vertex = object->VertexData[f]; const FVector& vtx = vertex.Vert; Ar.Logf( TEXT("v %.4f %.4f %.4f\n"), vtx.X, vtx.Z, vtx.Y ); } Ar.Logf( TEXT("\n") ); // Texture coordinates for( int32 f = 0 ; f < object->VertexData.Num() ; ++f ) { const FOBJVertex& face = object->VertexData[f]; const FVector2D& uv = face.UV; Ar.Logf( TEXT("vt %.4f %.4f\n"), uv.X, 1.0f - uv.Y ); } Ar.Logf( TEXT("\n") ); // Normals for( int32 f = 0 ; f < object->VertexData.Num() ; ++f ) { const FOBJVertex& face = object->VertexData[f]; const FVector& Normal = face.Normal; Ar.Logf( TEXT("vn %.3f %.3f %.3f\n"), Normal.X, Normal.Z, Normal.Y ); } Ar.Logf( TEXT("\n") ); // Faces for( int32 f = 0 ; f < object->Faces.Num() ; ++f ) { const FOBJFace& face = object->Faces[f]; if( face.Material != CurrentMaterial ) { CurrentMaterial = face.Material; Ar.Logf( TEXT("usemtl %s\n"), *FixupMaterialName(face.Material)); } Ar.Logf( TEXT("f ") ); for( int32 v = 0 ; v < 3 ; ++v ) { // +1 as Wavefront files are 1 index based uint32 VertexIndex = IndexOffset + face.VertexIndex[v] + 1; Ar.Logf( TEXT("%d/%d/%d "), VertexIndex, VertexIndex, VertexIndex); } Ar.Logf( TEXT("\n") ); } IndexOffset += object->VertexData.Num(); Ar.Logf( TEXT("\n") ); // dump to disk so we don't run out of memory ganging up all objects if (MemAr) { FileAr.Log(*MemAr); FileAr.Flush(); MemAr->Empty(); } // we are now done with the object, free it delete object; Objects[o] = NULL; } } /** * Compiles the selection order array by putting every geometry object * with a valid selection index into the array, and then sorting it. */ struct FCompareMaterial { FORCEINLINE bool operator()( const FOBJFace& InA, const FOBJFace& InB ) const { PTRINT A = (PTRINT)(InA.Material); PTRINT B = (PTRINT)(InB.Material); return (A < B) ? true : false; } }; /*------------------------------------------------------------------------------ ULevelExporterLOD implementation. ------------------------------------------------------------------------------*/ ULevelExporterLOD::ULevelExporterLOD(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SupportedClass = UWorld::StaticClass(); bText = true; bForceFileOperations = true; PreferredFormatIndex = 0; FormatExtension.Add(TEXT("LOD.OBJ")); FormatDescription.Add(TEXT("Object File for LOD")); } bool ULevelExporterLOD::ExportText(const FExportObjectInnerContext* Context, UObject* Object, const TCHAR* Type, FOutputDevice& FileAr, FFeedbackContext* Warn, uint32 PortFlags) { GWarn->BeginSlowTask( NSLOCTEXT("UnrealEd", "ExportingLevelToLOD OBJ", "Exporting Level To LOD OBJ"), true ); // containers to hold exportable objects and their materials TArray Objects; TSet Materials; UWorld* World = CastChecked(Object); // write to memory to buffer file writes FStringOutputDevice Ar; // OBJ file header Ar.Logf (TEXT("# LOD OBJ File Generated by UnrealEd\n")); Ar.Logf( TEXT("\n") ); TArray ActorsToExport; for( FActorIterator It(World); It; ++It ) { AActor* Actor = *It; // only export selected actors if the flag is set if( !Actor || (bSelectedOnly && !IsObjectSelectedForExport(Context, Actor))) { continue; } ActorsToExport.Add(Actor); } // Export actors uint32 IndexOffset = 0; for (int32 Index = 0; Index < ActorsToExport.Num(); ++Index) { AActor* Actor = ActorsToExport[Index]; Warn->StatusUpdate( Index, ActorsToExport.Num(), NSLOCTEXT("UnrealEd", "ExportingLevelToOBJ", "Exporting Level To OBJ") ); // for now, only export static mesh actors if (Cast(Actor) == NULL) { continue; } // export any actor that passes the tests AddActorToOBJs(Actor, Objects, &Materials, bSelectedOnly); for( int32 o = 0 ; o < Objects.Num() ; ++o ) { FOBJGeom* object = Objects[o]; object->Faces.Sort(FCompareMaterial()); } // Export to the OBJ file ExportOBJs(FileAr, &Ar, Warn, CurrentFilename, Objects, &Materials, IndexOffset); Objects.Reset(); } // OBJ file footer Ar.Logf (TEXT("# dElaernU yb detareneG eliF JBO DOL\n")); GWarn->EndSlowTask(); // dump the rest to the file FileAr.Log(*Ar); return true; } /*------------------------------------------------------------------------------ ULevelExporterOBJ implementation. ------------------------------------------------------------------------------*/ static void ExportPolys( UPolys* Polys, int32 &PolyNum, int32 TotalPolys, FFeedbackContext* Warn, bool bSelectedOnly, UModel* Model, TArray& Objects ) { FOBJGeom* OBJGeom = new FOBJGeom( TEXT("BSP") ); for( int32 i=0; iNodes.Num(); i++ ) { FBspNode* Node = &Model->Nodes[i]; FBspSurf& Surf = Model->Surfs[Node->iSurf]; if( (Surf.PolyFlags & PF_Selected) || !bSelectedOnly ) { 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]; FPoly Poly; GEditor->polyFindBrush( Model, Node->iSurf, Poly ); // Triangulate this node and generate an OBJ face from the vertices. for(int32 StartVertexIndex = 1;StartVertexIndex < Node->NumVertices-1;StartVertexIndex++) { int32 TriangleIndex = OBJGeom->Faces.AddZeroed(); FOBJFace& OBJFace = OBJGeom->Faces[TriangleIndex]; int32 VertexIndex = OBJGeom->VertexData.AddZeroed(3); FOBJVertex* Vertices = &OBJGeom->VertexData[VertexIndex]; OBJFace.VertexIndex[0] = VertexIndex; OBJFace.VertexIndex[1] = VertexIndex + 1; OBJFace.VertexIndex[2] = VertexIndex + 2; // These map the node's vertices to the 3 triangle indices to triangulate the convex polygon. int32 TriVertIndices[3] = { Node->iVertPool, Node->iVertPool + StartVertexIndex, Node->iVertPool + StartVertexIndex + 1 }; for(uint32 TriVertexIndex = 0; TriVertexIndex < 3; TriVertexIndex++) { const FVert& Vert = Model->Verts[TriVertIndices[TriVertexIndex]]; const FVector& Vertex = (FVector)Model->Points[Vert.pVertex]; double U = ((Vertex - TextureBase) | TextureX) / UModel::GetGlobalBSPTexelScale(); double V = ((Vertex - TextureBase) | TextureY) / UModel::GetGlobalBSPTexelScale(); Vertices[TriVertexIndex].Vert = Vertex; Vertices[TriVertexIndex].UV = FVector2D( U, V ); Vertices[TriVertexIndex].Normal = Normal; } } } } // Save the object representing the BSP into the OBJ pool if( OBJGeom->Faces.Num() > 0 ) { Objects.Add( OBJGeom ); } else { delete OBJGeom; } } ULevelExporterOBJ::ULevelExporterOBJ(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SupportedClass = UWorld::StaticClass(); bText = true; bForceFileOperations = true; PreferredFormatIndex = 0; FormatExtension.Add(TEXT("OBJ")); FormatDescription.Add(TEXT("Object File")); } bool ULevelExporterOBJ::ExportText(const FExportObjectInnerContext* Context, UObject* Object, const TCHAR* Type, FOutputDevice& FileAr, FFeedbackContext* Warn, uint32 PortFlags) { TSet GlobalMaterials; TSet *Materials = 0; int32 YesNoCancelReply = EAppReturnType::Yes; if (!(GIsAutomationTesting || FApp::IsUnattended() || ExportTask->bAutomated)) { YesNoCancelReply = FMessageDialog::Open( EAppMsgType::YesNoCancel, NSLOCTEXT("UnrealEd", "Prompt_OBJExportWithBMP", "Would you like to export the materials as images (slower)?")); } else { UE_LOG(LogEditorExporters, Display, TEXT("Executing OBJ automated export, materials will be exported as images by default.")); } switch (YesNoCancelReply) { case EAppReturnType::Yes: Materials = &GlobalMaterials; break; case EAppReturnType::No: break; case EAppReturnType::Cancel: return 1; } GWarn->BeginSlowTask( NSLOCTEXT("UnrealEd", "ExportingLevelToOBJ", "Exporting Level To OBJ"), true ); // container to hold all exportable objects TArray Objects; UWorld* World = CastChecked(Object); GEditor->bspBuildFPolys(World->GetModel(), 0, 0 ); UPolys* Polys = World->GetModel()->Polys; // write to memory to buffer file writes FStringOutputDevice Ar; // OBJ file header Ar.Logf (TEXT("# OBJ File Generated by UnrealEd\n")); Ar.Logf( TEXT("\n") ); uint32 IndexOffset = 0; // Export the BSP int32 Dummy; ExportPolys( Polys, Dummy, 0, Warn, bSelectedOnly, World->GetModel(), Objects ); // Export polys to the OBJ file ExportOBJs(FileAr, &Ar, Warn, CurrentFilename, Objects, NULL, IndexOffset); Objects.Reset(); // Export actors TArray ActorsToExport; for( FActorIterator It(World); It; ++It ) { AActor* Actor = *It; // only export selected actors if the flag is set if( !Actor || (bSelectedOnly && !IsObjectSelectedForExport(Context, Actor))) { continue; } ActorsToExport.Add(Actor); } for( int32 Index = 0; Index < ActorsToExport.Num(); ++Index ) { AActor* Actor = ActorsToExport[Index]; Warn->StatusUpdate( Index, ActorsToExport.Num(), NSLOCTEXT("UnrealEd", "ExportingLevelToOBJ", "Exporting Level To OBJ") ); // try to export every object AddActorToOBJs(Actor, Objects, Materials, bSelectedOnly); for( int32 o = 0 ; o < Objects.Num() ; ++o ) { FOBJGeom* object = Objects[o]; object->Faces.Sort(FCompareMaterial()); } } // Export to the OBJ file ExportOBJs(FileAr, &Ar, Warn, CurrentFilename, Objects, Materials, IndexOffset); Objects.Reset(); // OBJ file footer Ar.Logf (TEXT("# dElaernU yb detareneG eliF JBO\n")); GWarn->EndSlowTask(); // write anything left in the memory Ar to disk FileAr.Log(*Ar); return 1; } /*------------------------------------------------------------------------------ ULevelExporterFBX implementation. ------------------------------------------------------------------------------*/ ULevelExporterFBX::ULevelExporterFBX(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SupportedClass = UWorld::StaticClass(); bText = false; bForceFileOperations = false; PreferredFormatIndex = 0; FormatExtension.Add(TEXT("FBX")); FormatDescription.Add(TEXT("FBX File")); } bool IsSomethingToExport(AActor* Actor, TArray& OutReasons) { check(Actor); if (ALevelInstance* LevelInstance = Cast(Actor)) { OutReasons.Add(FString::Printf(TEXT("%s: Exporting Level Instances to FBX is not supported."), *Actor->GetActorLabel())); return false; } TInlineComponentArray ComponentsToExport; for (UActorComponent* ActorComp : Actor->GetComponents()) { USceneComponent* Component = Cast(ActorComp); if (Component == nullptr || Component->bHiddenInGame) { //Skip hidden component like camera mesh or other editor helper continue; } UStaticMeshComponent* StaticMeshComp = Cast(Component); USkeletalMeshComponent* SkelMeshComp = Cast(Component); UChildActorComponent* ChildActorComp = Cast(Component); if ((StaticMeshComp && StaticMeshComp->GetStaticMesh()) || (SkelMeshComp && SkelMeshComp->GetSkeletalMeshAsset()) || Component->IsA(UCameraComponent::StaticClass()) || Component->IsA(ULightComponent::StaticClass()) || (ChildActorComp && ChildActorComp->GetChildActor() && IsSomethingToExport(ChildActorComp->GetChildActor(), OutReasons)) ) { return true; } } return false; } bool IsSomethingToExport(ULevel* InLevel, bool bSelectedOnly, TArray& OutReasons) { TArray ActorToExport; int32 ActorCount = InLevel->Actors.Num(); for (int32 ActorIndex = 0; ActorIndex < ActorCount; ++ActorIndex) { AActor* Actor = InLevel->Actors[ActorIndex]; if (Actor != nullptr && (!bSelectedOnly || (bSelectedOnly && Actor->IsSelected()))) { if (IsSomethingToExport(Actor, OutReasons)) { return true; } else if(Actor->IsA(ALandscapeProxy::StaticClass()) || Actor->IsA(AEmitter::StaticClass())) { return true; } else if (Actor->IsA(ABrush::StaticClass())) { ABrush* BrushActor = Cast(Actor); UModel* Model = BrushActor->GetBrushComponent()->Brush; if (Model != NULL && Model->VertexBuffer.Vertices.Num() >= 3 && Model->MaterialIndexBuffers.Num() != 0) { return true; } } } } return false; } bool IsSomethingToExport(UWorld* World, bool bSelectedOnly, TArray& OutReasons) { if (bSelectedOnly && World->GetModel()->Nodes.Num()) { return true; } ULevel* Level = World->PersistentLevel; if (IsSomethingToExport(Level, bSelectedOnly, OutReasons)) { return true; } // Export streaming levels and actors for (int32 CurLevelIndex = 0; CurLevelIndex < World->GetNumLevels(); ++CurLevelIndex) { ULevel* CurLevel = World->GetLevel(CurLevelIndex); if (CurLevel != NULL && CurLevel != Level) { if (IsSomethingToExport(CurLevel, bSelectedOnly, OutReasons)) { return true; } } } return false; } void OpenOutputLogTab() { FGlobalTabmanager::Get()->TryInvokeTab(FName("OutputLog")); } bool ULevelExporterFBX::ExportBinary( UObject* Object, const TCHAR* Type, FArchive& Ar, FFeedbackContext* Warn, int32 FileIndex, uint32 PortFlags ) { UWorld* World = CastChecked(Object); TArray GeneratedWarnings; //Check if there is something to export if (!IsSomethingToExport(World, bSelectedOnly, GeneratedWarnings)) { if (GeneratedWarnings.Num() > 0) { for (const FString& WarningMessage : GeneratedWarnings) { UE_LOG(LogEditorExporters, Warning, TEXT("%s"), *WarningMessage); } } else { UE_LOG(LogEditorExporters, Warning, TEXT("There is nothing to export to a fbx file.")); } if (!GIsAutomationTesting && !FApp::IsUnattended()) { FNotificationInfo* NotificationInfo = new FNotificationInfo(FText::GetEmpty()); if (GeneratedWarnings.Num() > 0) { NotificationInfo->Text = FText(NSLOCTEXT("UnrealEd", "ExportingLevelToFBX_GeneratedWarnings", "Some warnings were generated while exporting to FBX!")); NotificationInfo->Hyperlink = FSimpleDelegate::CreateStatic(OpenOutputLogTab); NotificationInfo->HyperlinkText = NSLOCTEXT("UnrealEd", "ShowOutputLogButton", "Show Output Log"); NotificationInfo->ExpireDuration = 8.0f; } else { NotificationInfo->Text = bSelectedOnly ? FText(NSLOCTEXT("UnrealEd", "ExportingLevelToFBX_Selection", "The selection has nothing that can be exported to FBX!")) : FText(NSLOCTEXT("UnrealEd", "ExportingLevelToFBX_World", "The world has nothing that can be exported to FBX!")); NotificationInfo->ExpireDuration = 5.0f; } //QueueNotification will add the pointer to a list and the FSlateNotificationManager::Tick will delete the pointer when adding the notification to the active list. FSlateNotificationManager::Get().QueueNotification(NotificationInfo); } return false; } GWarn->BeginSlowTask(NSLOCTEXT("UnrealEd", "ExportingLevelToFBX", "Exporting Level To FBX"), true); FScopedFbxExporterInstance ScopedExporterInstance; bool ExportCancel = false; if (UFbxExportOption* AutomatedExportOptions = GetAutomatedExportOptionsFbx()) { ScopedExporterInstance.GetExporter()->SetExportOptionsOverride(AutomatedExportOptions); } else { //Show the fbx export dialog options bool ExportAll = false; ScopedExporterInstance.GetExporter()->FillExportOptions(false, true, UExporter::CurrentFilename, ExportCancel, ExportAll); } if (!ExportCancel) { ScopedExporterInstance.GetExporter()->CreateDocument(); GWarn->StatusUpdate(0, 1, NSLOCTEXT("UnrealEd", "ExportingLevelToFBX", "Exporting Level To FBX")); { ULevel* Level = World->PersistentLevel; if (bSelectedOnly) { ScopedExporterInstance.GetExporter()->ExportBSP(World->GetModel(), true); } class FLevelExporterFBXNodeNameAdapter : public INodeNameAdapter { public: virtual FString GetActorNodeName(const AActor* InActor) override { if (ActorNames.Contains(InActor)) { return ActorNames[InActor].ToString(); } FString ActorNodeName = InActor->GetActorLabel(); if (UniqueActorNames.Contains(ActorNodeName)) { FString ActorNodeNamePrefix = ActorNodeName; int32 ActorNodeNameIndex = 1; FActorLabelUtilities::SplitActorLabel(ActorNodeNamePrefix, ActorNodeNameIndex); do { ActorNodeName = FString::Printf(TEXT("%s%d"), *ActorNodeNamePrefix, ++ActorNodeNameIndex); } while (UniqueActorNames.Contains(ActorNodeName)); } UniqueActorNames.Add(ActorNodeName); ActorNames.Add(InActor) = FName(ActorNodeName); return ActorNodeName; } private: TSet UniqueActorNames; TMap ActorNames; }; FLevelExporterFBXNodeNameAdapter NodeNameAdapter; ScopedExporterInstance.GetExporter()->ExportLevelMesh(Level, bSelectedOnly, NodeNameAdapter); // Export streaming levels and actors for (int32 CurLevelIndex = 0; CurLevelIndex < World->GetNumLevels(); ++CurLevelIndex) { ULevel* CurLevel = World->GetLevel(CurLevelIndex); if (CurLevel != NULL && CurLevel != Level) { ScopedExporterInstance.GetExporter()->ExportLevelMesh(CurLevel, bSelectedOnly, NodeNameAdapter); } } } ScopedExporterInstance.GetExporter()->WriteToFile(*UExporter::CurrentFilename); } ScopedExporterInstance.GetExporter()->SetExportOptionsOverride(nullptr); GWarn->EndSlowTask(); return true; } /*------------------------------------------------------------------------------ UPolysExporterOBJ implementation. ------------------------------------------------------------------------------*/ UPolysExporterOBJ::UPolysExporterOBJ(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SupportedClass = UPolys::StaticClass(); bText = true; PreferredFormatIndex = 0; FormatExtension.Add(TEXT("OBJ")); FormatDescription.Add(TEXT("Object File")); } bool UPolysExporterOBJ::ExportText(const FExportObjectInnerContext* Context, UObject* Object, const TCHAR* Type, FOutputDevice& Ar, FFeedbackContext* Warn, uint32 PortFlags) { TArray Objects; UPolys* Polys = CastChecked (Object); int32 PolyNum = 0; int32 TotalPolys = Polys->Element.Num(); Ar.Logf (TEXT("# OBJ File Generated by UnrealEd\n")); UWorld* World = CastChecked(Object); ExportPolys( Polys, PolyNum, TotalPolys, Warn, false, World->GetModel(), Objects ); for( int32 o = 0 ; o < Objects.Num() ; ++o ) { FOBJGeom* object = Objects[o]; object->Faces.Sort(FCompareMaterial()); } uint32 IndexOffset = 0; // Export to the OBJ file ExportOBJs(Ar, NULL, Warn, CurrentFilename, Objects, NULL, IndexOffset); Ar.Logf (TEXT("# dElaernU yb detareneG eliF JBO\n")); return 1; } /*------------------------------------------------------------------------------ USequenceExporterT3D implementation. ------------------------------------------------------------------------------*/ USequenceExporterT3D::USequenceExporterT3D(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } bool USequenceExporterT3D::ExportText(const FExportObjectInnerContext* Context, UObject* Object, const TCHAR* Type, FOutputDevice& Ar, FFeedbackContext* Warn, uint32 PortFlags) { return true; } /*------------------------------------------------------------------------------ UStaticMeshExporterOBJ implementation. ------------------------------------------------------------------------------*/ UStaticMeshExporterOBJ::UStaticMeshExporterOBJ(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SupportedClass = UStaticMesh::StaticClass(); bText = true; PreferredFormatIndex = 0; FormatExtension.Add(TEXT("OBJ")); FormatDescription.Add(TEXT("Object File")); } bool UStaticMeshExporterOBJ::ExportText(const FExportObjectInnerContext* Context, UObject* Object, const TCHAR* Type, FOutputDevice& Ar, FFeedbackContext* Warn, uint32 PortFlags) { UStaticMesh* StaticMesh = CastChecked( Object ); { // Currently, we only export LOD 0 of the static mesh. In the future, we could potentially export all available LODs const FStaticMeshLODResources& RenderData = StaticMesh->GetLODForExport(0); uint32 Count = RenderData.GetNumTriangles(); bool bHasUVLightMap = RenderData.VertexBuffers.StaticMeshVertexBuffer.GetNumTexCoords() > 1; // Create a new filename for the lightmap coordinate OBJ file (by adding "_UV1" to the end of the filename) FString Filename = UExporter::CurrentFilename.Left( UExporter::CurrentFilename.Len() - 4 ) + "_UV1." + UExporter::CurrentFilename.Right( 3 ); // Open a second archive here so we can export lightmap coordinates at the same time we export the regular mesh FArchive* UV1File = bHasUVLightMap ? IFileManager::Get().CreateFileWriter( *Filename ) : nullptr; TArray Verts; // The verts in the mesh TArray UVs; // UV coords from channel 0 TArray UVLMs; // Lightmap UVs from channel 1 TArray Normals; // Normals TArray SmoothingMasks; // Complete collection of the smoothing groups from all triangles TArray UniqueSmoothingMasks; // Collection of the unique smoothing groups (used when writing out the face info into the OBJ file so we can group by smoothing group) if (bHasUVLightMap) { UV1File->Logf(TEXT("# UnrealEd OBJ exporter\r\n")); } Ar.Log( TEXT("# UnrealEd OBJ exporter\r\n") ); FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(0); if (MeshDescription != nullptr) { uint32 TriangleCount = MeshDescription->Triangles().Num(); if (Count == TriangleCount) { SmoothingMasks.AddZeroed(TriangleCount); FStaticMeshOperations::ConvertHardEdgesToSmoothGroup(*MeshDescription, SmoothingMasks); for (uint32 SmoothValue : SmoothingMasks) { UniqueSmoothingMasks.AddUnique(SmoothValue); } } } //This can happen in case the base LOD is reduce or the recompute normals has generate a different number of cases //the count will be lower and in such a case the smooth group of the original mesh description cannot be used since it is out of sync with the render data. if (SmoothingMasks.Num() != Count) { //Create one SmoothGroup with 0 values SmoothingMasks.Empty(Count); SmoothingMasks.AddZeroed(Count); UniqueSmoothingMasks.Empty(1); UniqueSmoothingMasks.Add(0); } // Collect all the data about the mesh Verts.Reserve(3 * Count); UVs.Reserve(3 * Count); UVLMs.Reserve(3 * Count); Normals.Reserve(3 * Count); FIndexArrayView Indices = RenderData.IndexBuffer.GetArrayView(); for( uint32 tri = 0 ; tri < Count ; tri++ ) { uint32 Index1 = Indices[(tri * 3) + 0]; uint32 Index2 = Indices[(tri * 3) + 1]; uint32 Index3 = Indices[(tri * 3) + 2]; FVector Vertex1 = (FVector)RenderData.VertexBuffers.PositionVertexBuffer.VertexPosition(Index1); //(FStaticMeshFullVertex*)(RawVertexData + Index1 * VertexStride); FVector Vertex2 = (FVector)RenderData.VertexBuffers.PositionVertexBuffer.VertexPosition(Index2); FVector Vertex3 = (FVector)RenderData.VertexBuffers.PositionVertexBuffer.VertexPosition(Index3); // Vertices Verts.Add( Vertex1 ); Verts.Add( Vertex2 ); Verts.Add( Vertex3 ); // UVs from channel 0 UVs.Add( FVector2D(RenderData.VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(Index1, 0)) ); UVs.Add( FVector2D(RenderData.VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(Index2, 0)) ); UVs.Add( FVector2D(RenderData.VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(Index3, 0)) ); // UVs from channel 1 (lightmap coords) if (bHasUVLightMap) { UVLMs.Add(FVector2D(RenderData.VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(Index1, 1))); UVLMs.Add(FVector2D(RenderData.VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(Index2, 1))); UVLMs.Add(FVector2D(RenderData.VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(Index3, 1))); } // Normals Normals.Add( FVector4(RenderData.VertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(Index1)) ); Normals.Add( FVector4(RenderData.VertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(Index2)) ); Normals.Add( FVector4(RenderData.VertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(Index3)) ); } // Write out the vertex data if (bHasUVLightMap) { UV1File->Logf(TEXT("\r\n")); } Ar.Log( TEXT("\r\n") ); for( int32 v = 0 ; v < Verts.Num() ; ++v ) { // Transform to Lightwave's coordinate system if (bHasUVLightMap) { UV1File->Logf(TEXT("v %f %f %f\r\n"), Verts[v].X, Verts[v].Z, Verts[v].Y); } Ar.Logf( TEXT("v %f %f %f\r\n"), Verts[v].X, Verts[v].Z, Verts[v].Y ); } // Write out the UV data (the lightmap file differs here in that it writes from the UVLMs array instead of UVs) if (bHasUVLightMap) { UV1File->Logf(TEXT("\r\n")); } Ar.Log( TEXT("\r\n") ); for( int32 uv = 0 ; uv < UVs.Num() ; ++uv ) { // Invert the y-coordinate (Lightwave has their bitmaps upside-down from us). if (bHasUVLightMap) { UV1File->Logf(TEXT("vt %f %f\r\n"), UVLMs[uv].X, 1.0f - UVLMs[uv].Y); } Ar.Logf( TEXT("vt %f %f\r\n"), UVs[uv].X, 1.0f - UVs[uv].Y ); } // Write object header if (bHasUVLightMap) { UV1File->Logf(TEXT("\r\n")); UV1File->Logf(TEXT("g UnrealEdObject\r\n")); UV1File->Logf(TEXT("\r\n")); } Ar.Log( TEXT("\r\n") ); Ar.Log( TEXT("g UnrealEdObject\r\n") ); Ar.Log( TEXT("\r\n") ); // Write out the face windings, sectioned by unique smoothing groups int32 SmoothingGroup = 0; for( int32 sm = 0 ; sm < UniqueSmoothingMasks.Num() ; ++sm ) { if (bHasUVLightMap) { UV1File->Logf(TEXT("s %i\r\n"), SmoothingGroup); } Ar.Logf( TEXT("s %i\r\n"), SmoothingGroup ); SmoothingGroup++; for( int32 tri = 0 ; tri < RenderData.GetNumTriangles() ; tri++ ) { if( SmoothingMasks[tri] == UniqueSmoothingMasks[sm] ) { int idx = 1 + (tri * 3); if (bHasUVLightMap) { UV1File->Logf(TEXT("f %d/%d %d/%d %d/%d\r\n"), idx, idx, idx + 1, idx + 1, idx + 2, idx + 2); } Ar.Logf( TEXT("f %d/%d %d/%d %d/%d\r\n"), idx, idx, idx+1, idx+1, idx+2, idx+2 ); } } } // Write out footer if (bHasUVLightMap) { UV1File->Logf(TEXT("\r\n")); UV1File->Logf(TEXT("g\r\n")); } Ar.Log( TEXT("\r\n") ); Ar.Log( TEXT("g\r\n") ); if (bHasUVLightMap) { // Clean up and finish delete UV1File; } } // ------------------------------------------------------ // { // Create a new filename for the internal OBJ file (by adding "_Internal" to the end of the filename) FString Filename = UExporter::CurrentFilename.Left( UExporter::CurrentFilename.Len() - 4 ) + "_Internal." + UExporter::CurrentFilename.Right( 3 ); // Open another archive FArchive* File = IFileManager::Get().CreateFileWriter( *Filename ); File->Logf( TEXT("# UnrealEd OBJ exporter (_Internal)\r\n") ); // Currently, we only export LOD 0 of the static mesh. In the future, we could potentially export all available LODs const FStaticMeshLODResources& RenderData = StaticMesh->GetLODForExport(0); uint32 VertexCount = RenderData.GetNumVertices(); check(VertexCount == RenderData.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices()); File->Logf( TEXT("\r\n") ); for(uint32 i = 0; i < VertexCount; i++) { const FVector& OSPos = (FVector)RenderData.VertexBuffers.PositionVertexBuffer.VertexPosition( i ); // const FVector WSPos = StaticMeshComponent->LocalToWorld.TransformPosition( OSPos ); const FVector WSPos = OSPos; // Transform to Lightwave's coordinate system File->Logf( TEXT("v %f %f %f\r\n"), WSPos.X, WSPos.Z, WSPos.Y ); } File->Logf( TEXT("\r\n") ); for(uint32 i = 0 ; i < VertexCount; ++i) { // takes the first UV const FVector2f UV = RenderData.VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(i, 0); // Invert the y-coordinate (Lightwave has their bitmaps upside-down from us). File->Logf( TEXT("vt %f %f\r\n"), UV.X, 1.0f - UV.Y ); } File->Logf( TEXT("\r\n") ); for(uint32 i = 0 ; i < VertexCount; ++i) { const FVector3f& OSNormal = RenderData.VertexBuffers.StaticMeshVertexBuffer.VertexTangentZ( i ); const FVector WSNormal = (FVector)OSNormal; // Transform to Lightwave's coordinate system File->Logf( TEXT("vn %f %f %f\r\n"), WSNormal.X, WSNormal.Z, WSNormal.Y ); } { FIndexArrayView Indices = RenderData.IndexBuffer.GetArrayView(); uint32 NumIndices = Indices.Num(); check(NumIndices % 3 == 0); for(uint32 i = 0; i < NumIndices / 3; i++) { // Wavefront indices are 1 based uint32 a = Indices[3 * i + 0] + 1; uint32 b = Indices[3 * i + 1] + 1; uint32 c = Indices[3 * i + 2] + 1; File->Logf( TEXT("f %d/%d/%d %d/%d/%d %d/%d/%d\r\n"), a,a,a, b,b,b, c,c,c); } } delete File; } return true; } /*------------------------------------------------------------------------------ UExporterFBX implementation. ------------------------------------------------------------------------------*/ UFbxExportOption* UExporterFBX::GetAutomatedExportOptionsFbx() { if (ExportTask && ExportTask->bAutomated) { return Cast(ExportTask->Options); } return nullptr; } namespace UE::EditorExporters::Private { bool SetupExporterForAutomatedFBXExport(FScopedFbxExporterInstance& ScopedExporterInstance, UExporterFBX& CurrentExporter) { if (CurrentExporter.ExportTask && CurrentExporter.ExportTask->bAutomated) { if (UFbxExportOption* AutomatedExportOptions = CurrentExporter.GetAutomatedExportOptionsFbx()) { ScopedExporterInstance.GetExporter()->SetExportOptionsOverride(AutomatedExportOptions); } CurrentExporter.SetShowExportOption(false); return true; } return false; } } /*------------------------------------------------------------------------------ UStaticMeshExporterFBX implementation. ------------------------------------------------------------------------------*/ UStaticMeshExporterFBX::UStaticMeshExporterFBX(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SupportedClass = UStaticMesh::StaticClass(); bText = false; PreferredFormatIndex = 0; FormatExtension.Add(TEXT("FBX")); FormatDescription.Add(TEXT("FBX File")); } bool UStaticMeshExporterFBX::ExportBinary( UObject* Object, const TCHAR* Type, FArchive& Ar, FFeedbackContext* Warn, int32 FileIndex, uint32 PortFlags ) { UStaticMesh* StaticMesh = CastChecked( Object ); FScopedFbxExporterInstance ScopedExporterInstance; if (!UE::EditorExporters::Private::SetupExporterForAutomatedFBXExport(ScopedExporterInstance, *this)) { //Show the fbx export dialog options bool ExportAll = GetBatchMode() && !GetShowExportOption(); bool ExportCancel = false; ScopedExporterInstance.GetExporter()->FillExportOptions(GetBatchMode(), GetShowExportOption(), UExporter::CurrentFilename, ExportCancel, ExportAll); if (ExportCancel) { SetCancelBatch(GetBatchMode()); //User cancel the FBX export return false; } SetShowExportOption(!ExportAll); } ScopedExporterInstance.GetExporter()->CreateDocument(); ScopedExporterInstance.GetExporter()->ExportStaticMesh(StaticMesh); ScopedExporterInstance.GetExporter()->WriteToFile(*UExporter::CurrentFilename); ScopedExporterInstance.GetExporter()->SetExportOptionsOverride(nullptr); return true; } /*------------------------------------------------------------------------------ USkeletalMeshExporterFBX implementation. ------------------------------------------------------------------------------*/ USkeletalMeshExporterFBX::USkeletalMeshExporterFBX(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SupportedClass = USkeletalMesh::StaticClass(); bText = false; PreferredFormatIndex = 0; FormatExtension.Add(TEXT("FBX")); FormatDescription.Add(TEXT("FBX File")); } bool USkeletalMeshExporterFBX::ExportBinary( UObject* Object, const TCHAR* Type, FArchive& Ar, FFeedbackContext* Warn, int32 FileIndex, uint32 PortFlags ) { USkeletalMesh* SkeletalMesh = CastChecked( Object ); FScopedFbxExporterInstance ScopedExporterInstance; if (!UE::EditorExporters::Private::SetupExporterForAutomatedFBXExport(ScopedExporterInstance, *this)) { //Show the fbx export dialog options bool ExportAll = GetBatchMode() && !GetShowExportOption(); bool ExportCancel = false; ScopedExporterInstance.GetExporter()->FillExportOptions(GetBatchMode(), GetShowExportOption(), UExporter::CurrentFilename, ExportCancel, ExportAll); if (ExportCancel) { SetCancelBatch(GetBatchMode()); //User cancel the FBX export return false; } SetShowExportOption(!ExportAll); } ScopedExporterInstance.GetExporter()->CreateDocument(); ScopedExporterInstance.GetExporter()->ExportSkeletalMesh(SkeletalMesh); ScopedExporterInstance.GetExporter()->WriteToFile(*UExporter::CurrentFilename); ScopedExporterInstance.GetExporter()->SetExportOptionsOverride(nullptr); return true; } /*------------------------------------------------------------------------------ UAnimSequenceExporterFBX implementation. ------------------------------------------------------------------------------*/ UAnimSequenceExporterFBX::UAnimSequenceExporterFBX(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SupportedClass = UAnimSequence::StaticClass(); bText = false; PreferredFormatIndex = 0; FormatExtension.Add(TEXT("FBX")); FormatDescription.Add(TEXT("FBX File")); } bool UAnimSequenceExporterFBX::ExportBinary( UObject* Object, const TCHAR* Type, FArchive& Ar, FFeedbackContext* Warn, int32 FileIndex, uint32 PortFlags ) { UAnimSequence* AnimSequence = CastChecked( Object ); USkeleton* AnimSkeleton = AnimSequence->GetSkeleton(); USkeletalMesh* PreviewMesh = AnimSkeleton ? AnimSkeleton->GetAssetPreviewMesh(AnimSequence) : NULL; if (AnimSkeleton && PreviewMesh) { FScopedFbxExporterInstance ScopedExporterInstance; if (!UE::EditorExporters::Private::SetupExporterForAutomatedFBXExport(ScopedExporterInstance, *this)) { //Show the fbx export dialog options bool ExportAll = GetBatchMode() && !GetShowExportOption(); bool ExportCancel = false; ScopedExporterInstance.GetExporter()->FillExportOptions(GetBatchMode(), GetShowExportOption(), UExporter::CurrentFilename, ExportCancel, ExportAll); if (ExportCancel) { SetCancelBatch(GetBatchMode()); //User cancel the FBX export return false; } SetShowExportOption(!ExportAll); } ScopedExporterInstance.GetExporter()->CreateDocument(); ScopedExporterInstance.GetExporter()->ExportAnimSequence(AnimSequence, PreviewMesh, ScopedExporterInstance.GetExporter()->GetExportOptions()->bExportPreviewMesh); ScopedExporterInstance.GetExporter()->WriteToFile( *UExporter::CurrentFilename ); ScopedExporterInstance.GetExporter()->SetExportOptionsOverride(nullptr); return true; } if(!AnimSkeleton) { UE_LOG(LogEditorExporters, Warning, TEXT("Cannot export animation sequence [%s] because the skeleton is not set."), *AnimSequence->GetName()); } else { UE_LOG(LogEditorExporters, Warning, TEXT("Cannot export animation sequence [%s] because the preview mesh is not set."), *AnimSequence->GetName()); } return false; } void UEditorEngine::RebuildStaticNavigableGeometry(ULevel* Level) { // iterate through all BSPs and gather it's geometry, without any filtering - filtering will be done while building // NOTE: any other game-time static geometry can (and should) be added here if (Level != NULL) { Level->StaticNavigableGeometry.Reset(); if (Level->Model != NULL) { UModel* Model = Level->Model; int32 TotalPolys = 0; TArray TempPolys; bspBuildFPolys(Model, 0, 0, &TempPolys); UPolys* Polys = Model->Polys; int32 PolyNum = TempPolys.Num(); TotalPolys = TotalPolys + PolyNum; for( int32 i = 0; i < Model->Nodes.Num(); ++i ) { FBspNode* Node = &Model->Nodes[i]; FBspSurf& Surf = Model->Surfs[Node->iSurf]; if (!(Surf.Actor && Surf.Actor->ShouldExportStaticNavigableGeometry())) { continue; } 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]; FPoly Poly; polyFindBrush( Model, Node->iSurf, Poly ); // Triangulate this node and generate an OBJ face from the vertices. for(int32 StartVertexIndex = 1;StartVertexIndex < Node->NumVertices-1;StartVertexIndex++) { // These map the node's vertices to the 3 triangle indices to triangulate the convex polygon. int32 TriVertIndices[3] = { Node->iVertPool, Node->iVertPool + StartVertexIndex, Node->iVertPool + StartVertexIndex + 1 }; for(uint32 TriVertexIndex = 0; TriVertexIndex < 3; TriVertexIndex++) { const FVert& Vert = Model->Verts[TriVertIndices[TriVertexIndex]]; Level->StaticNavigableGeometry.Add( (FVector)Model->Points[Vert.pVertex] ); } } } } FNavigationSystem::UpdateLevelCollision(*Level); } } /*----------------------------------------------------------------------------- UExportTextContainer -----------------------------------------------------------------------------*/ UExportTextContainer::UExportTextContainer(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { }