Files
2025-05-18 13:04:45 +08:00

363 lines
11 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DatasmithSceneExporter.h"
#include "DatasmithAnimationSerializer.h"
#include "DatasmithExportOptions.h"
#include "DatasmithExporterManager.h"
#include "DatasmithLogger.h"
#include "DatasmithMaterialElements.h"
#include "DatasmithProgressManager.h"
#include "DatasmithSceneFactory.h"
#include "DatasmithSceneXmlWriter.h"
#include "DatasmithUtils.h"
#include "GenericPlatform/GenericPlatformFile.h"
#include "Containers/Array.h"
#include "Algo/Find.h"
#include "Containers/Set.h"
#include "HAL/FileManager.h"
#include "HAL/PlatformFileManager.h"
#include "Math/UnrealMathUtility.h"
#include "Misc/Paths.h"
#include "Misc/SecureHash.h"
#include "UObject/GarbageCollection.h"
// Hash function to use FMD5Hash in TMap
inline uint32 GetTypeHash(const FMD5Hash& Hash)
{
uint32* HashAsInt32 = (uint32*)Hash.GetBytes();
return HashAsInt32[0] ^ HashAsInt32[1] ^ HashAsInt32[2] ^ HashAsInt32[3];
}
class FDatasmithSceneExporterImpl
{
public:
FDatasmithSceneExporterImpl();
// call this before export the actual bitmaps
void CheckBumpMaps( TSharedRef< IDatasmithScene > DatasmithScene );
void UpdateTextureElements( TSharedRef< IDatasmithScene > DatasmithScene );
void UpdateAssetOutputPath();
static EDatasmithTextureMode GetTextureModeFromPropertyName(const FString& PropertyName);
static FString GetFileNameWithHash(const FString& FullPath);
FString Name;
FString OutputPath;
FString AssetsOutputPath;
uint64 ExportStartCycles;
TSharedPtr<IDatasmithProgressManager> ProgressManager;
TSharedPtr<FDatasmithLogger> Logger;
};
FDatasmithSceneExporterImpl::FDatasmithSceneExporterImpl()
: ExportStartCycles(0)
{
}
void FDatasmithSceneExporterImpl::UpdateTextureElements( TSharedRef< IDatasmithScene > DatasmithScene )
{
FDatasmithTextureUtils::CalculateTextureHashes(DatasmithScene);
// No need to do anything if user required to keep images at original location or no output path is set
if (FDatasmithExportOptions::PathTexturesMode == EDSResizedTexturesPath::OriginalFolder || AssetsOutputPath.IsEmpty())
{
return;
}
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
TMap<FMD5Hash, FString> HashFilePathMap;
FDatasmithUniqueNameProvider TextureFileNameProvider;
for (int32 i = 0; i < DatasmithScene->GetTexturesCount(); i++)
{
TSharedPtr< IDatasmithTextureElement > TextureElement = DatasmithScene->GetTexture(i);
FString TextureFileName = TextureElement->GetFile();
float RatioDone = float(i + 1) / float( DatasmithScene->GetTexturesCount() );
if (ProgressManager.IsValid())
{
ProgressManager->ProgressEvent(RatioDone, *FPaths::GetBaseFilename( TextureFileName ));
}
FString& NewFilename = HashFilePathMap.FindOrAdd(TextureElement->GetFileHash());
// If this texture has not been exported yet, find a unique name for it and copy its file to the asset output path.
if (NewFilename.IsEmpty())
{
const FString UniqueFileName = TextureFileNameProvider.GenerateUniqueName( FPaths::GetBaseFilename( TextureFileName ) );
TextureFileNameProvider.AddExistingName( UniqueFileName );
const FString FileExtension = FPaths::GetExtension(TextureFileName, /*bIncludeDot=*/true);
NewFilename = FPaths::Combine(AssetsOutputPath, UniqueFileName + FileExtension);
// Copy image file to new location
if (!FPaths::IsSamePath(*NewFilename, *TextureFileName))
{
PlatformFile.CopyFile(*NewFilename, *TextureFileName);
}
}
// Update texture element
if (TextureFileName != NewFilename)
{
TextureElement->SetFile(*NewFilename);
}
}
}
void FDatasmithSceneExporterImpl::CheckBumpMaps( TSharedRef< IDatasmithScene > DatasmithScene )
{
for ( int32 MaterialIndex = 0; MaterialIndex < DatasmithScene->GetMaterialsCount(); ++MaterialIndex )
{
TSharedPtr< IDatasmithBaseMaterialElement > BaseMaterialElement = DatasmithScene->GetMaterial( MaterialIndex );
if ( BaseMaterialElement->IsA( EDatasmithElementType::Material ) )
{
const TSharedPtr< IDatasmithMaterialElement >& MaterialElement = StaticCastSharedPtr< IDatasmithMaterialElement >( BaseMaterialElement );
for (int32 j = 0; j < MaterialElement->GetShadersCount(); ++j )
{
TSharedPtr< IDatasmithShaderElement >& Shader = MaterialElement->GetShader(j);
if (Shader->GetBumpComp()->GetMode() == EDatasmithCompMode::Regular && Shader->GetBumpComp()->GetParamSurfacesCount() == 1 &&
Shader->GetNormalComp()->GetParamSurfacesCount() == 0)
{
FString TextureName = Shader->GetBumpComp()->GetParamTexture(0);
FString NormalTextureName = TextureName + TEXT("_Norm");
if ( !TextureName.IsEmpty() )
{
FDatasmithTextureSampler UVs = Shader->GetBumpComp()->GetParamTextureSampler(0);
TSharedPtr< IDatasmithTextureElement > TextureElement;
TSharedPtr< IDatasmithTextureElement > NormalTextureElement;
for ( int32 TextureIndex = 0; TextureIndex < DatasmithScene->GetTexturesCount(); ++TextureIndex )
{
if ( DatasmithScene->GetTexture( TextureIndex )->GetName() == TextureName )
{
TextureElement = DatasmithScene->GetTexture( TextureIndex );
}
else if ( DatasmithScene->GetTexture( TextureIndex )->GetName() == NormalTextureName )
{
NormalTextureElement = DatasmithScene->GetTexture( TextureIndex );
}
}
if ( TextureElement )
{
if ( !NormalTextureElement )
{
NormalTextureElement = FDatasmithSceneFactory::CreateTexture( *NormalTextureName );
NormalTextureElement->SetRGBCurve( 1.f );
NormalTextureElement->SetFile( TextureElement->GetFile() );
NormalTextureElement->SetFileHash( TextureElement->GetFileHash() );
NormalTextureElement->SetTextureMode( EDatasmithTextureMode::Bump );
DatasmithScene->AddTexture( NormalTextureElement );
}
Shader->GetNormalComp()->AddSurface( *NormalTextureName, UVs );
Shader->GetBumpComp()->ClearSurface();
}
}
}
}
}
}
}
void FDatasmithSceneExporterImpl::UpdateAssetOutputPath()
{
if (Name.IsEmpty())
{
// Just set the AssetsOutputPath to OutputPath, if the scene exporter has not been named
AssetsOutputPath = OutputPath;
}
else if (!OutputPath.IsEmpty())
{
AssetsOutputPath = FPaths::Combine(OutputPath, Name + TEXT("_Assets"));
}
}
EDatasmithTextureMode FDatasmithSceneExporterImpl::GetTextureModeFromPropertyName(const FString& PropertyName)
{
if (PropertyName.Find(TEXT("BUMP")) != INDEX_NONE)
{
return EDatasmithTextureMode::Bump;
}
else if (PropertyName.Find(TEXT("SPECULAR")) != INDEX_NONE)
{
return EDatasmithTextureMode::Specular;
}
else if (PropertyName.Find(TEXT("NORMAL")) != INDEX_NONE)
{
return EDatasmithTextureMode::Normal;
}
return EDatasmithTextureMode::Diffuse;
};
FString FDatasmithSceneExporterImpl::GetFileNameWithHash(const FString& FullPath)
{
FString Hash = FMD5::HashAnsiString(*FullPath);
FString FileName = FPaths::GetBaseFilename(FullPath);
FString Extension = FPaths::GetExtension(FileName);
FileName = FileName + TEXT("_") + Hash + Extension;
return FileName;
}
FDatasmithSceneExporter::FDatasmithSceneExporter()
: Impl( MakeUnique< FDatasmithSceneExporterImpl >() )
{
}
FDatasmithSceneExporter::~FDatasmithSceneExporter() = default;
void FDatasmithSceneExporter::PreExport()
{
// Collect start time to log amount of time spent to export scene
Impl->ExportStartCycles = FPlatformTime::Cycles64();
}
void FDatasmithSceneExporter::Export( TSharedRef< IDatasmithScene > DatasmithScene, bool bCleanupUnusedElements )
{
if ( Impl->ExportStartCycles == 0 )
{
Impl->ExportStartCycles = FPlatformTime::Cycles64();
}
FString FilePath = FPaths::Combine(Impl->OutputPath, Impl->Name ) + TEXT(".") + FDatasmithUtils::GetFileExtension();
TUniquePtr<FArchive> Archive( IFileManager::Get().CreateFileWriter( *FilePath ) );
if ( !Archive.IsValid() )
{
if ( Impl->Logger.IsValid() )
{
Impl->Logger->AddGeneralError( *( TEXT("Unable to create file ") + FilePath + TEXT(", Aborting the export process") ) );
}
return;
}
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
PlatformFile.CreateDirectoryTree( *Impl->AssetsOutputPath );
// Add Bump maps from Material objects to scene as TextureElement
Impl->CheckBumpMaps( DatasmithScene );
FDatasmithSceneUtils::CleanUpScene(DatasmithScene, bCleanupUnusedElements);
// Update TextureElements
Impl->UpdateTextureElements( DatasmithScene );
// Convert paths to relative
FString AbsoluteDir = Impl->OutputPath + TEXT("/");
for ( int32 MeshIndex = 0; MeshIndex < DatasmithScene->GetMeshesCount(); ++MeshIndex )
{
TSharedPtr< IDatasmithMeshElement > Mesh = DatasmithScene->GetMesh( MeshIndex );
FString RelativePath = Mesh->GetFile();
FPaths::MakePathRelativeTo( RelativePath, *AbsoluteDir );
Mesh->SetFile( *RelativePath );
}
for ( int32 TextureIndex = 0; TextureIndex < DatasmithScene->GetTexturesCount(); ++TextureIndex )
{
TSharedPtr< IDatasmithTextureElement > Texture = DatasmithScene->GetTexture( TextureIndex );
FString TextureFile = Texture->GetFile();
FPaths::MakePathRelativeTo( TextureFile, *AbsoluteDir );
Texture->SetFile( *TextureFile );
}
FDatasmithAnimationSerializer AnimSerializer;
int32 NumSequences = DatasmithScene->GetLevelSequencesCount();
for (int32 SequenceIndex = 0; SequenceIndex < NumSequences; ++SequenceIndex)
{
const TSharedPtr<IDatasmithLevelSequenceElement>& LevelSequence = DatasmithScene->GetLevelSequence(SequenceIndex);
if (LevelSequence.IsValid())
{
FString AnimFilePath = FPaths::Combine(Impl->AssetsOutputPath, LevelSequence->GetName()) + DATASMITH_ANIMATION_EXTENSION;
if (AnimSerializer.Serialize(LevelSequence.ToSharedRef(), *AnimFilePath))
{
TUniquePtr<FArchive> AnimArchive(IFileManager::Get().CreateFileReader(*AnimFilePath));
if (AnimArchive)
{
LevelSequence->SetFileHash(FMD5Hash::HashFileFromArchive(AnimArchive.Get()));
}
FPaths::MakePathRelativeTo(AnimFilePath, *AbsoluteDir);
LevelSequence->SetFile(*AnimFilePath);
}
}
}
// Log time spent to export scene in seconds
int ElapsedTime = (int)FPlatformTime::ToSeconds64(FPlatformTime::Cycles64() - Impl->ExportStartCycles);
DatasmithScene->SetExportDuration( ElapsedTime );
FDatasmithSceneXmlWriter DatasmithSceneXmlWriter;
DatasmithSceneXmlWriter.Serialize( DatasmithScene, *Archive );
Archive->Close();
// Run the garbage collector at this point so that we're in a good state for the next export
FDatasmithExporterManager::RunGarbageCollection();
}
void FDatasmithSceneExporter::Reset()
{
Impl->ProgressManager = nullptr;
Impl->Logger = nullptr;
Impl->ExportStartCycles = 0;
}
void FDatasmithSceneExporter::SetProgressManager( const TSharedPtr< IDatasmithProgressManager >& InProgressManager )
{
Impl->ProgressManager = InProgressManager;
}
void FDatasmithSceneExporter::SetLogger( const TSharedPtr< FDatasmithLogger >& InLogger )
{
Impl->Logger = InLogger;
}
void FDatasmithSceneExporter::SetName(const TCHAR* InName)
{
Impl->Name = InName;
Impl->UpdateAssetOutputPath();
}
const TCHAR* FDatasmithSceneExporter::GetName() const
{
return *Impl->Name;
}
void FDatasmithSceneExporter::SetOutputPath( const TCHAR* InOutputPath )
{
Impl->OutputPath = InOutputPath;
FPaths::NormalizeDirectoryName( Impl->OutputPath );
Impl->UpdateAssetOutputPath();
}
const TCHAR* FDatasmithSceneExporter::GetOutputPath() const
{
return *Impl->OutputPath;
}
const TCHAR* FDatasmithSceneExporter::GetAssetsOutputPath() const
{
return *Impl->AssetsOutputPath;
}