Files
UnrealEngine/Engine/Source/Programs/Enterprise/Datasmith/DatasmithARCHICADExporter/Private/TexturesCache.cpp
2025-05-18 13:04:45 +08:00

412 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "TexturesCache.h"
#include "Utils/LibPartInfo.h"
#include "ModelMaterial.hpp"
#include "Texture.hpp"
#include "AttributeIndex.hpp"
#include "GXImage.hpp"
#include "Graphics2D.h"
#include "Folder.hpp"
#include "FileSystem.hpp"
#include "Paths.h"
#include "DatasmithUtils.h"
BEGIN_NAMESPACE_UE_AC
FTexturesCache::FTexturesCache(const GS::UniString& InAssetsCache)
: AssetsCache(InAssetsCache)
{
AbsolutePath = AssetsCache + UE_AC_DirSep + GS::UniString("Textures") + UE_AC_DirSep;
IESTexturesPath = AssetsCache + UE_AC_DirSep + GS::UniString("IESTextures") + UE_AC_DirSep;
}
const FTexturesCache::FTexturesCacheElem& FTexturesCache::GetTexture(const FSyncContext& InSyncContext,
GS::Int32 InTextureIndex)
{
FTexturesCacheElem* ExistingTexture = Textures.Find(InTextureIndex);
if (ExistingTexture != nullptr)
{
return *ExistingTexture;
}
UE_AC_Assert(InTextureIndex > 0 && InTextureIndex <= InSyncContext.GetModel().GetTextureCount());
// Create an new texture element
FTexturesCacheElem& Texture = Textures.FindOrAdd(InTextureIndex);
ModelerAPI::Texture AcTexture;
ModelerAPI::AttributeIndex IndexTextureIndex(ModelerAPI::AttributeIndex::TextureIndex, InTextureIndex);
InSyncContext.GetModel().GetTexture(IndexTextureIndex, &AcTexture);
if (AcTexture.GetXSize() > 0)
{
Texture.InvXSize = 1 / AcTexture.GetXSize();
}
if (AcTexture.GetYSize() > 0)
{
Texture.InvYSize = 1 / AcTexture.GetYSize();
}
Texture.bHasAlpha = AcTexture.HasAlphaChannel();
Texture.bMirrorX = AcTexture.IsMirroredInX();
Texture.bMirrorY = AcTexture.IsMirroredInY();
Texture.bAlphaIsTransparence = AcTexture.IsTransparentPattern();
Texture.bIsAvailable = AcTexture.IsAvailable();
Texture.bUsed = false;
if (Texture.bIsAvailable)
{
if (InSyncContext.bUseFingerPrint)
{
#if 0
// Compute pixel fingerprint to be redesing
Texture.TextureLabel = AcTexture.GetPixelMapCheckSum();
Texture.Fingerprint = GSGuid2APIGuid(GS::Guid(Texture.TextureLabel));
#endif
// Compute fingerprint with content and used texture informations
char Tmp[256];
AcTexture.GetPixelMapCheckSum(Tmp, sizeof(Tmp));
size_t FingerprintLen = strnlen(Tmp, sizeof(Tmp));
UE_AC_Assert(FingerprintLen == 32);
MD5::Generator MD5Generator;
MD5Generator.Update(Tmp, (unsigned short)FingerprintLen);
MD5Generator.Update(&Texture.InvXSize, sizeof(Texture.InvXSize));
MD5Generator.Update(&Texture.InvYSize, sizeof(Texture.InvYSize));
MD5Generator.Update(&Texture.bAlphaIsTransparence, Texture.bAlphaIsTransparence);
MD5::FingerPrint FingerPrint;
MD5Generator.Finish(FingerPrint);
UE_AC_Assert(FingerPrint.GetAsString(Tmp) == NoError);
Texture.TextureLabel = Tmp;
Texture.Fingerprint = Fingerprint2API_Guid(FingerPrint);
UE_AC_VerboseF("Texture name=\"%s\": TMFingerPrint=\"%s\"\n", AcTexture.GetName().ToUtf8(), Tmp);
}
else
{
// Create a unique name
Texture.TextureLabel = AcTexture.GetName();
unsigned int SequencialNumber = 0;
while (TexturesNameSet.Contains(&Texture.TextureLabel))
{
Texture.TextureLabel = AcTexture.GetName() + GS::UniString::Printf(" %d", ++SequencialNumber);
}
TexturesNameSet.Add(&Texture.TextureLabel);
GS::UniString fp(AcTexture.GetFingerprint());
Texture.Fingerprint = GSGuid2APIGuid(GS::Guid(fp));
UE_AC_VerboseF("Texture name=\"%s\": ACFingerprint=\"%s\"\n", Texture.TextureLabel.ToUtf8(), fp.ToUtf8());
}
CreateCacheFolders();
Texture.TexturePath = AbsolutePath + Texture.TextureLabel + GetGSName(kName_TextureExtension);
WriteTexture(AcTexture, Texture.TexturePath, InSyncContext.bUseFingerPrint);
}
else
{
GS::UniString fp(AcTexture.GetFingerprint());
Texture.Fingerprint = GSGuid2APIGuid(GS::Guid(fp));
UE_AC_ReportF("FTexturesCache::GetTexture - Texture name \"%s\" missing: ACFingerprint=%s\n",
AcTexture.GetName().ToUtf8(), fp.ToUtf8());
}
GS::UniString Fingerprint = APIGuidToString(Texture.Fingerprint);
FString TextureId = GSStringToUE(Fingerprint);
bool bTextureIdAlreadyInSet = false;
TexturesIdsSet.Add(TextureId, &bTextureIdAlreadyInSet);
if (!bTextureIdAlreadyInSet)
{
TSharedRef< IDatasmithTextureElement > BaseTexture =
FDatasmithSceneFactory::CreateTexture(GSStringToUE(Fingerprint));
BaseTexture->SetLabel(GSStringToUE(AcTexture.GetName()));
BaseTexture->SetFile(GSStringToUE(Texture.TexturePath));
if (*BaseTexture->GetFile() != 0)
{
FMD5Hash FileHash = FMD5Hash::HashFile(BaseTexture->GetFile());
BaseTexture->SetFileHash(FileHash);
}
else
{
BaseTexture->SetFile(TEXT("Missing_Texture_File"));
}
BaseTexture->SetSRGB(EDatasmithColorSpace::sRGB);
InSyncContext.GetScene().AddTexture(BaseTexture);
}
return Texture;
}
/* Tool class to search file in Folder and Sub folder of a Parent folder.
* If found, copy the file to destination folder */
class FSearchAndCopyFile
{
public:
// Constructor
FSearchAndCopyFile(IO::Folder& InDestination, const IO::Name& InFileName)
: Destination(InDestination)
, FileName(InFileName)
, bFileInDestination(false)
{
// Check if the file is already in the destination folder
GSErrCode GSErr = InDestination.Contains(InFileName, &bFileInDestination);
if (GSErr != NoError)
{
UE_AC_DebugF("FSearchAndCopyFile::FSearchAndCopyFile - IESTexturesFolder.Contains return error %s\n",
GetErrorName(GSErr));
}
}
// Search in the folder tree starting at parent folder.
bool DoSearchIn(const IO::Folder& Parent) { return Parent.Enumerate(EnumCallBack, this) == NoError; }
// Callback function called for each element of the parent specified
bool EnumCallBack(const IO::Folder& Parent, const IO::Name& EntryName, bool bIsFolder);
// Return true if the file is in the destination folder
bool IsFileInDestination() const { return bFileInDestination; }
private:
// Callback to folder enumerate function
static bool CCALL EnumCallBack(const IO::Folder& Parent, const IO::Name& EntryName, bool bIsFolder, void* UserData);
// Destination where we must copy the file
IO::Folder& Destination;
// File's name to be copied
const IO::Name& FileName;
// True if the file is in the destination folder
bool bFileInDestination;
};
// Callback to folder enumerate function
bool CCALL FSearchAndCopyFile::EnumCallBack(const IO::Folder& Parent, const IO::Name& EntryName, bool bIsFolder,
void* UserData)
{
return reinterpret_cast< FSearchAndCopyFile* >(UserData)->EnumCallBack(Parent, EntryName, bIsFolder);
}
// Callback function called for each element of the parent specified
bool FSearchAndCopyFile::EnumCallBack(const IO::Folder& Parent, const IO::Name& EntryName, bool bIsFolder)
{
if (bIsFolder)
{
// Search in sub folder
DoSearchIn(IO::Folder(Parent, EntryName));
}
else
{
if (EntryName == FileName)
{
// Try to copy the file
GSErrCode GSErr = Parent.Copy(EntryName, Destination, FileName);
if (GSErr == NoError)
{
bFileInDestination = true;
}
else
{
UE_AC_DebugF("FSearchAndCopyFile::EnumCallBack - IO::Folder::Copy returned error %s\n",
GetErrorName(GSErr));
}
}
}
return !bFileInDestination; // Stop if copy is done
}
// Insure we have a copy of the IES file in the cache folder
GS::UniString FTexturesCache::CopyIESFile(const GS::UniString& InIESFileName)
{
IO::Location IESTexturesLocation(IESTexturesPath);
IO::Name IESFileName(InIESFileName);
// Create the cached texture path
IO::Location IESTextureLocation(IESTexturesLocation, IESFileName);
GS::UniString IESTexturePath;
IESTextureLocation.ToPath(&IESTexturePath);
// Create the texture folder if it's not present
IO::Folder IESTexturesFolder(IESTexturesLocation, IO::Folder::Create);
#if 1
// Check if the file is already in the destination folder
bool bFileInDestination = false;
GSErrCode GSErr = IESTexturesFolder.Contains(IESFileName, &bFileInDestination);
if (GSErr != NoError)
{
UE_AC_DebugF("FTexturesCache::CopyIESFile - IESTexturesFolder.Contains return error %s\n", GetErrorName(GSErr));
}
if (!bFileInDestination)
{
FAuto_API_LibPart LibPart;
USize Lenght = InIESFileName.GetLength() + 1;
if (Lenght > API_UniLongNameLen)
{
Lenght = API_UniLongNameLen;
}
memcpy(LibPart.file_UName, InIESFileName.ToUStr().Get(), Lenght * sizeof(GS::uchar_t));
GSErr = ACAPI_LibPart_Search(&LibPart, false);
if (GSErr == NoError && LibPart.location != nullptr)
{
// Try to copy the file
GSErr = IO::fileSystem.Copy(*LibPart.location, IESTextureLocation);
if (GSErr != NoError)
{
UE_AC_ReportF("FTexturesCache::CopyIESFile - Cannot copy IES File \"%s\"", InIESFileName.ToUtf8());
UE_AC_DebugF("FTexturesCache::CopyIESFile - Cannot copy IES File \"%s\", error=%s\n",
InIESFileName.ToUtf8(), GetErrorName(GSErr));
}
}
else
{
UE_AC_ReportF("FTexturesCache::CopyIESFile - Cannot find IES File \"%s\"\n", InIESFileName.ToUtf8());
if (GSErr != NoError)
{
UE_AC_DebugF("FTexturesCache::CopyIESFile - ACAPI_LibPart_Search error %s for IES File \"%s\"\n",
GetErrorName(GSErr), InIESFileName.ToUtf8());
}
}
}
#else
// Search the IES file and copy it in the cache
FSearchAndCopyFile SearchAndCopyIESFile(IESTexturesFolder, IESFileName);
if (!SearchAndCopyIESFile.IsFileInDestination())
{
GS::Array< API_LibraryInfo > LibInfoArray;
GSErrCode GSErr = ACAPI_Environment(APIEnv_GetLibrariesID, &LibInfoArray);
if (GSErr == NoError)
{
for (UInt32 IndexLibrary = 0;
IndexLibrary < LibInfoArray.GetSize() && !SearchAndCopyIESFile.IsFileInDestination(); IndexLibrary++)
{
const API_LibraryInfo& LibInfo = LibInfoArray[IndexLibrary];
if (LibInfo.libraryType == API_LocalLibrary || LibInfo.libraryType == API_EmbeddedLibrary ||
LibInfo.libraryType == API_ServerLibrary)
{
IO::Folder LibraryFolder(LibInfo.location, IO::Folder::Ignore);
SearchAndCopyIESFile.DoSearchIn(LibraryFolder);
}
}
}
}
if (!SearchAndCopyIESFile.IsFileInDestination())
{
UE_AC_ReportF("FTexturesCache::CopyIESFile - Cannot find IES File \"%s\"\n", InIESFileName.ToUtf8());
}
#endif
return IESTexturePath;
}
// Create IES texture
const FTexturesCache::FIESTexturesCacheElem& FTexturesCache::GetIESTexture(const FSyncContext& InSyncContext,
const FString& InIESFileName)
{
CreateCacheFolders();
FTexturesCache::FIESTexturesCacheElem* found = IESTextures.Find(InIESFileName);
if (found == nullptr)
{
// Find and copy the texture in the cache.
FString IESFilePath(GSStringToUE(CopyIESFile(UEToGSString(*InIESFileName))));
// Create IES texture
const FString BaseFilename = FPaths::GetBaseFilename(IESFilePath);
FString TextureName = FDatasmithUtils::SanitizeObjectName(BaseFilename + TEXT("_IES"));
TSharedPtr< IDatasmithTextureElement > Texture = FDatasmithSceneFactory::CreateTexture(*TextureName);
Texture->SetTextureMode(EDatasmithTextureMode::Ies);
Texture->SetLabel(*BaseFilename);
Texture->SetFile(*IESFilePath);
InSyncContext.GetScene().AddTexture(Texture);
found = &IESTextures.Add(InIESFileName, TextureName);
}
return *found;
}
void FTexturesCache::CreateCacheFolders()
{
if (bCacheFoldersCreated == false)
{
{
// Create the asset folder if it's not present
IO::Location AssetsFolderLocation(AssetsCache);
IO::Folder AssetsFolder(AssetsFolderLocation, IO::Folder::Create);
if (AssetsFolder.GetStatus() != NoError)
{
UE_AC_ReportF("Unable to create/access assets cache folder: \"%s\"", AssetsCache.ToUtf8());
UE_AC::ThrowGSError(AssetsFolder.GetStatus(), __FILE__, __LINE__);
}
}
{
// Create the textures folder if it's not present
IO::Location TexturesLocation(AbsolutePath);
IO::Folder TexturesFolder(TexturesLocation, IO::Folder::Create);
if (TexturesFolder.GetStatus() != NoError)
{
UE_AC_ReportF("Unable to create/access cache textures cache folder: \"%s\"", AbsolutePath.ToUtf8());
UE_AC::ThrowGSError(TexturesFolder.GetStatus(), __FILE__, __LINE__);
}
}
bCacheFoldersCreated = true;
}
}
// Write the texture to the cache
void FTexturesCache::WriteTexture(const ModelerAPI::Texture& InACTexture, const GS::UniString& InPath,
bool InIsFingerprint) const
{
IO::Location TextureLoc(InPath);
// If texture already exist, we do nothing
if (InIsFingerprint)
{
IO::File TextureFile(TextureLoc);
if ((TextureFile.GetStatus() == NoError) &&
(TextureFile.IsOpen() || (TextureFile.Open(IO::File::ReadMode) == NoError)))
{
return;
}
}
// Create a pixmap of the same size of the texture
GSPixMapHandle PixMap = GXCreateGSPixMap(InACTexture.GetPixelMapXSize(), InACTexture.GetPixelMapYSize());
UE_AC_TestPtr(PixMap);
try
{
// Test the invariant
UE_AC_Assert(InACTexture.GetPixelMapSize() * sizeof(ModelerAPI::Texture::Pixel) ==
GXGetGSPixMapBytesPerRow(PixMap) * InACTexture.GetPixelMapYSize());
// Copy the pixels from the texture to the PixMap.
GSPtr Pixels = GXGetGSPixMapBaseAddr(PixMap);
UE_AC_TestPtr(Pixels);
InACTexture.GetPixelMap(reinterpret_cast< ModelerAPI::Texture::Pixel* >(Pixels));
GX::ImageSaveOptions ImgSaveOpt(GX::PixelBits_MillionsWithAlpha);
GX::ImageSaveOptions* PixelBitSize = &ImgSaveOpt;
GX::Image Img(PixMap);
UE_AC_TestGSError(Img.WriteToFile(
TextureLoc, FTM::FileTypeManager::SearchForMime(GetStdName(kName_TextureMime), NULL), PixelBitSize));
}
catch (...)
{
GXDeleteGSPixMap(PixMap);
throw;
}
// Delete the pixmap
GXDeleteGSPixMap(PixMap);
}
END_NAMESPACE_UE_AC