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

258 lines
7.5 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DatasmithSketchUpTexture.h"
#include "DatasmithSketchUpCommon.h"
#include "DatasmithSketchUpUtils.h"
#include "DatasmithSketchUpExportContext.h"
#include "DatasmithSketchUpString.h"
#include "DatasmithSketchUpMaterial.h"
#include "DatasmithSceneFactory.h"
#include "DatasmithUtils.h"
#include "IDatasmithSceneElements.h"
#include "Misc/Paths.h"
#include "HAL/FileManager.h"
// SketchUp SDK.
#include "DatasmithSketchUpSDKBegins.h"
#include "SketchUpAPI/model/texture.h"
#include "SketchUpAPI/model/image_rep.h"
#include "DatasmithSketchUpSDKCeases.h"
using namespace DatasmithSketchUp;
TSharedPtr<FTexture> FTextureCollection::FindOrAdd(SUTextureRef TextureRef)
{
FTextureIDType TextureId = DatasmithSketchUpUtils::GetEntityID(SUTextureToEntity(TextureRef));
if (TSharedPtr<FTexture>* TexturePtr = TexturesMap.Find(TextureId))
{
return *TexturePtr;
}
TSharedPtr<FTexture> Texture = MakeShared<FTexture>(TextureRef, TextureId);
TexturesMap.Add(TextureId, Texture);
return Texture;
}
FTexture* FTextureCollection::AddTexture(SUTextureRef TextureRef, FString MaterialName, bool bColorized)
{
TSharedPtr<FTexture> Texture = FindOrAdd(TextureRef);
Texture->bColorized = bColorized;
Texture->MaterialName = MaterialName;
Texture->Invalidate();
return Texture.Get();
}
FTexture* FTextureCollection::AddColorizedTexture(SUTextureRef TextureRef, FString MaterialName)
{
return AddTexture(TextureRef, MaterialName, true);
}
void FTextureCollection::Update()
{
if (!TexturesMap.IsEmpty())
{
// Making sure _Assets folder is present
// SU API need to have folder created before writing image files
// It's not neccessarily present at this point - it's only created when meshes are exported and they are being exported in a separate thread
if (!FPaths::FileExists(Context.GetAssetsOutputPath()))
{
IFileManager::Get().MakeDirectory(Context.GetAssetsOutputPath());
}
}
for (TPair<FTextureIDType, TSharedPtr<FTexture>>& TextureNameAndTextureImageFile : TexturesMap)
{
TSharedPtr<FTexture> Texture = TextureNameAndTextureImageFile.Value;
Texture->Update(Context);
if (Texture->TextureImageFile)
{
Texture->TextureImageFile->Update(Context);
}
}
}
void FTextureCollection::RegisterMaterial(FMaterial* Material)
{
FTexture* Texture = Material->GetTexture();
if (!Texture)
{
return;
}
}
void FTextureCollection::UnregisterMaterial(FMaterial* Material)
{
FTexture* Texture = Material->GetTexture();
if (!Texture)
{
return;
}
// Remove texture that is not used by its material
// todo: remove in descturtor?
ReleaseImage(*Texture);
TexturesMap.Remove(Texture->TextureId);
}
bool FTexture::GetTextureUseAlphaChannel()
{
// Get the flag indicating whether or not the SketchUp texture alpha channel is used.
bool bUseAlphaChannel = false;
// Make sure the flag was retrieved properly (no SU_ERROR_NO_DATA).
return (SUTextureGetUseAlphaChannel(TextureRef, &bUseAlphaChannel) == SU_ERROR_NONE) && bUseAlphaChannel;
}
void FTexture::WriteImageFile(FExportContext& Context, const FString& TextureFilePath)
{
// Write the SketchUp texture into a file when required.
SUResult SResult = SUTextureWriteToFile(TextureRef, TCHAR_TO_UTF8(*TextureFilePath));
if (SResult == SU_ERROR_SERIALIZATION)
{
// TODO: Append an error message to the export summary.
}
}
const TCHAR* FTexture::GetDatasmithElementName()
{
return TextureImageFile->TextureElement->GetName();
}
TSharedPtr<FTextureImageFile> FTextureImageFile::Create(FTexture& Texture, const FMD5Hash& ImageHash)
{
FString SourceTextureFileName;
FString TextureBaseName;
SourceTextureFileName = SuGetString(SUTextureGetFileName, Texture.TextureRef);
// Set texture to be material-specific. SketchUp allows to have different material have different texture images under the same name
TextureBaseName = Texture.MaterialName;
TSharedPtr<FTextureImageFile> TextureImageFile = MakeShared<FTextureImageFile>();
TextureImageFile->ImageHash = ImageHash;
TextureImageFile->Texture = &Texture;
TextureImageFile->TextureFileName = FDatasmithUtils::SanitizeFileName(TextureBaseName) + FPaths::GetExtension(SourceTextureFileName, /*bIncludeDot*/ true);
TextureImageFile->TextureName = FDatasmithUtils::SanitizeObjectName(FPaths::GetBaseFilename(TextureImageFile->TextureFileName));
TextureImageFile->TextureElement = FDatasmithSceneFactory::CreateTexture(*TextureImageFile->TextureName);
TextureImageFile->TextureElement->SetSRGB(EDatasmithColorSpace::sRGB);
return TextureImageFile;
}
void FTexture::Invalidate()
{
bInvalidated = true;
}
void FTexture::Update(FExportContext& Context)
{
if(!bInvalidated)
{
return;
}
// Get the pixel scale factors of the source SketchUp texture.
size_t TextureWidth = 0;
size_t TextureHeight = 0;
double TextureSScale = 1.0;
double TextureTScale = 1.0;
SUTextureGetDimensions(TextureRef, &TextureWidth, &TextureHeight, &TextureSScale, &TextureTScale); // we can ignore the returned SU_RESULT
TextureScale = FVector2D(TextureSScale, TextureTScale);
Context.Textures.AcquireImage(*this);
}
void FTextureImageFile::Update(FExportContext& Context)
{
if (!bInvalidated)
{
return;
}
FString TextureFilePath = FPaths::Combine(Context.GetAssetsOutputPath(), TextureFileName);
Texture->WriteImageFile(Context, TextureFilePath);
TextureElement->SetFile(*TextureFilePath);
Context.DatasmithScene->AddTexture(TextureElement); // todo: make sure that texture not created/added twice
bInvalidated = false;
}
FTextureImageFile::~FTextureImageFile()
{
}
void FTextureCollection::AcquireImage(FTexture& Texture)
{
// Compute image md5
SUImageRepRef ImageRep = SU_INVALID;
SUImageRepCreate(&ImageRep);
SUTextureGetImageRep(Texture.TextureRef, &ImageRep);
size_t Width, Height;
SUImageRepGetPixelDimensions(ImageRep, &Width, &Height);
size_t DataSize, Bpp;
SUImageRepGetDataSize(ImageRep, &DataSize, &Bpp);
TArray<SUByte> Data;
Data.SetNumUninitialized(DataSize);
SUImageRepGetData(ImageRep, DataSize, Data.GetData());
SUImageRepRelease(&ImageRep);
FMD5 MD5;
MD5.Update(reinterpret_cast<const uint8*>(&Width), sizeof(Width));
MD5.Update(reinterpret_cast<const uint8*>(&Height), sizeof(Height));
MD5.Update(reinterpret_cast<const uint8*>(&Bpp), sizeof(Bpp));
MD5.Update(reinterpret_cast<const uint8*>(&Texture.bColorized), sizeof(Texture.bColorized)); // Colorized flag affects resulting texture image
MD5.Update(Data.GetData(), Data.Num());
// colorized textures need an additional differentiation since the texture should be baked
// once per different tint it has applied in the scene
// Here we're using MaterialName, which works fine, but we could also use the tint color
if(Texture.bColorized)
{
MD5.Update(reinterpret_cast<const uint8*>(&Texture.MaterialName), sizeof(Texture.MaterialName));
}
FMD5Hash TextureHash;
TextureHash.Set(MD5);
TSharedPtr<FTextureImageFile>& Image = Images.FindOrAdd(TextureHash);
if(!Image)
{
Image = FTextureImageFile::Create(Texture, TextureHash);
}
Texture.TextureImageFile = Image;
Image->Textures.Add(&Texture);
}
void FTextureCollection::ReleaseImage(FTexture& Texture)
{
TSharedPtr<FTextureImageFile> Image = Texture.TextureImageFile;
Texture.TextureImageFile.Reset();
// Remove reference from Image to texture
Image->Textures.Remove(&Texture);
// When image not used remove it along with Datasmith texture element
if (!Image->Textures.Num())
{
if (Image->TextureElement)
{
Context.DatasmithScene->RemoveTexture(Image->TextureElement); // todo: make sure that texture not created/added twice
}
Images.Remove(Image->ImageHash);
}
}