447 lines
13 KiB
C++
447 lines
13 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "DatasmithSketchUpComponent.h"
|
|
|
|
#include "DatasmithSketchUpExportContext.h"
|
|
#include "DatasmithSketchUpMaterial.h"
|
|
#include "DatasmithSketchUpMetadata.h"
|
|
#include "DatasmithSketchUpString.h"
|
|
#include "DatasmithSketchUpSummary.h"
|
|
#include "DatasmithSketchUpUtils.h"
|
|
|
|
// SketchUp SDK.
|
|
#include "DatasmithSketchUpSDKBegins.h"
|
|
#include "SketchUpAPI/model/drawing_element.h"
|
|
#include <SketchUpAPI/model/entity.h>
|
|
#include <SketchUpAPI/model/image.h>
|
|
#include <SketchUpAPI/model/image_rep.h>
|
|
#include "SketchUpAPI/model/layer.h"
|
|
#include "SketchUpAPI/geometry/transformation.h"
|
|
#include "SketchUpAPI/geometry/vector3d.h"
|
|
|
|
#include "DatasmithSketchUpSDKCeases.h"
|
|
|
|
#include "IDatasmithSceneElements.h"
|
|
#include "DatasmithSceneFactory.h"
|
|
#include "DatasmithUtils.h"
|
|
|
|
#include "Algo/AnyOf.h"
|
|
#include "Misc/Paths.h"
|
|
|
|
|
|
using namespace DatasmithSketchUp;
|
|
|
|
FImage::FImage(SUImageRef InEntityRef)
|
|
: Super(SUImageToEntity(InEntityRef))
|
|
{
|
|
FString IdStr = FString::Printf(TEXT("%llx"), GetPersistentId());
|
|
MeshElementName = FString::Printf(TEXT("M%s"), *IdStr);
|
|
}
|
|
|
|
FImage::~FImage()
|
|
{
|
|
}
|
|
|
|
void FImage::RemoveImage(FExportContext& Context)
|
|
{
|
|
RemoveOccurrences(Context);
|
|
RemoveImageFromDatasmithScene(Context);
|
|
}
|
|
|
|
int64 FImage::GetPersistentId()
|
|
{
|
|
int64 PersistentId = 0;
|
|
SUEntityGetPersistentID(EntityRef, &PersistentId);
|
|
|
|
return PersistentId;
|
|
}
|
|
|
|
FString FImage::GetFileName()
|
|
{
|
|
// Get file name to use in name(label) computation. Not used to export file image!
|
|
|
|
// Note:
|
|
// Not using SUImageGetName - returns empty string:
|
|
// https://github.com/SketchUp/api-issue-tracker/issues/303
|
|
|
|
// Also, newer SU has access to Definition of an Image
|
|
// SUComponentDefinitionRef Definition = SU_INVALID;
|
|
// SUResult ImageGetDefinitionResult = SUImageGetDefinition(SUImageFromEntity(EntityRef), &Definition);
|
|
// ensure(SU_ERROR_NONE == ImageGetDefinitionResult);
|
|
|
|
// So for SU 2019+ file name is the only way to get Image's 'name'
|
|
|
|
FString FileName = SuGetString(SUImageGetFileName, SUImageFromEntity(EntityRef));
|
|
FString Name = FPaths::GetCleanFilename(FileName);
|
|
|
|
if (!Name.IsEmpty())
|
|
{
|
|
return Name;
|
|
}
|
|
|
|
return FString::Printf(TEXT("image_%llx.png"), GetPersistentId());
|
|
}
|
|
|
|
void FImage::InvalidateImage()
|
|
{
|
|
InvalidateEntityProperties();
|
|
InvalidateEntityGeometry();
|
|
}
|
|
|
|
|
|
FString FImage::GetEntityName()
|
|
{
|
|
return FDatasmithUtils::SanitizeObjectName(GetFileName());
|
|
}
|
|
|
|
FString FImage::GetEntityLabel()
|
|
{
|
|
return FDatasmithUtils::SanitizeObjectName(GetFileName());
|
|
}
|
|
|
|
void FImage::ApplyOverrideMaterialToNode(FNodeOccurence& Node, FMaterialOccurrence& Material)
|
|
{
|
|
// Image entity is not affected by override materials on components
|
|
}
|
|
|
|
void FImage::UpdateOccurrence(FExportContext& Context, FNodeOccurence& Node)
|
|
{
|
|
Node.EffectiveLayerRef = DatasmithSketchUpUtils::GetEffectiveLayer( SUImageToDrawingElement(SUImageFromEntity(EntityRef)), Node.ParentNode->EffectiveLayerRef);
|
|
|
|
BuildNodeNames(Node);
|
|
|
|
FString EffectiveLayerName = FDatasmithUtils::SanitizeObjectName(SuGetString(SULayerGetName, Node.EffectiveLayerRef));
|
|
Node.DatasmithActorElement->SetLayer(*EffectiveLayerName);
|
|
Node.DatasmithActorElement->SetLabel(*Node.GetActorLabel());
|
|
|
|
// Compute the world transform of the SketchUp component instance.
|
|
SUTransformation LocalTransform;
|
|
|
|
// note: For Image, transformation handling is a bit than for Component(see further)
|
|
SUImageGetTransform(SUImageFromEntity(EntityRef), &LocalTransform);
|
|
|
|
// Remove scaling from local Image's transform
|
|
// SU embeds image dimensions into its transform's scaling and we don't need it since we construct geometry using Image entity's width/height
|
|
SUVector3D XAxis{1, 0, 0};
|
|
SUTransformationGetXAxis(&LocalTransform, &XAxis);
|
|
SUVector3D YAxis{0, 1, 0};
|
|
SUTransformationGetYAxis(&LocalTransform, &YAxis);
|
|
SUVector3D ZAxis{0, 0, 1};
|
|
SUTransformationGetZAxis(&LocalTransform, &ZAxis);
|
|
|
|
SUVector3DNormalize(&XAxis);
|
|
SUVector3DNormalize(&YAxis);
|
|
SUVector3DNormalize(&ZAxis);
|
|
|
|
SUPoint3D Origin{0, 0, 0};
|
|
SUTransformationGetOrigin(&LocalTransform, &Origin);
|
|
|
|
// SUTransformationSetFromPointAndAxes point parameter is not 'Origin' of transformation(or in other words translation of transform from local to world)
|
|
// but rather inverse of translation
|
|
SUPoint3D Point = {-Origin.x, -Origin.y, -Origin.z};
|
|
SUTransformationSetFromPointAndAxes(&LocalTransform, &Point, &XAxis, &YAxis, &ZAxis);
|
|
|
|
SUTransformation WorldTransform;
|
|
SUTransformationMultiply(&Node.ParentNode->WorldTransformSource, &LocalTransform, &WorldTransform);
|
|
Node.WorldTransform = WorldTransform;
|
|
|
|
// Set the Datasmith actor world transform.
|
|
DatasmithSketchUpUtils::SetActorTransform(Node.DatasmithActorElement, Node.WorldTransform);
|
|
|
|
Node.ResetMetadataElement(Context);
|
|
|
|
// Update Datasmith Mesh Actors
|
|
for (int32 MeshIndex = 0; MeshIndex < Node.MeshActors.Num(); ++MeshIndex)
|
|
{
|
|
const TSharedPtr<IDatasmithMeshActorElement>& MeshActor = Node.MeshActors[MeshIndex];
|
|
|
|
// Set mesh actor transform after node transform
|
|
MeshActor->SetScale(Node.DatasmithActorElement->GetScale());
|
|
MeshActor->SetRotation(Node.DatasmithActorElement->GetRotation());
|
|
MeshActor->SetTranslation(Node.DatasmithActorElement->GetTranslation());
|
|
}
|
|
|
|
FString MeshActorLabel = Node.GetActorLabel();
|
|
|
|
// Update Datasmith Mesh Actors
|
|
for (int32 MeshIndex = 0; MeshIndex < Node.MeshActors.Num(); ++MeshIndex)
|
|
{
|
|
const TSharedPtr<IDatasmithMeshActorElement>& MeshActor = Node.MeshActors[MeshIndex];
|
|
MeshActor->SetLabel(*MeshActorLabel);
|
|
MeshActor->SetLayer(*EffectiveLayerName);
|
|
}
|
|
}
|
|
|
|
void FImage::RemoveImageFromDatasmithScene(FExportContext& Context)
|
|
{
|
|
Context.Images.ReleaseMaterial(*this);
|
|
|
|
if (DatasmithMeshElement)
|
|
{
|
|
Context.DatasmithScene->RemoveMesh(DatasmithMeshElement);
|
|
DatasmithMeshElement.Reset();
|
|
}
|
|
}
|
|
|
|
void FImage::EntityOccurrenceVisible(FNodeOccurence* Node, bool bUses)
|
|
{
|
|
bool bNodesInvisibleBefore = VisibleNodes.IsEmpty();
|
|
FEntity::EntityOccurrenceVisible(Node, bUses);
|
|
if (bNodesInvisibleBefore != VisibleNodes.IsEmpty())
|
|
{
|
|
// Just invalidate geometry to create/remove geometry depending on existing occurrences
|
|
InvalidateEntityGeometry();
|
|
}
|
|
|
|
}
|
|
|
|
const TCHAR* FImage::GetMeshElementName()
|
|
{
|
|
return *MeshElementName;
|
|
}
|
|
|
|
void FImage::Update(FExportContext& Context)
|
|
{
|
|
if (bGeometryInvalidated)
|
|
{
|
|
RemoveImageFromDatasmithScene(Context);
|
|
|
|
if (!VisibleNodes.IsEmpty())
|
|
{
|
|
UpdateEntityGeometry(Context);
|
|
UpdateGeometry(Context);
|
|
|
|
DatasmithMeshElement->SetMaterial(Context.Images.AcquireMaterial(*this), 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FImage::BuildNodeNames(FNodeOccurence& Node)
|
|
{
|
|
int64 SketchupPersistentID = Node.Entity.GetPersistentId();
|
|
Node.DatasmithActorName = FString::Printf(TEXT("%ls_%lld"), *Node.ParentNode->GetActorName(), SketchupPersistentID);
|
|
|
|
FString EntityName = Node.Entity.GetEntityName();
|
|
Node.DatasmithActorLabel = FDatasmithUtils::SanitizeObjectName(EntityName.IsEmpty() ? GetEntityName() : EntityName);
|
|
}
|
|
|
|
void FImage::SetupActor(FExportContext& Context, FNodeOccurence& Node)
|
|
{
|
|
// Add the Datasmith actor component depth tag.
|
|
// We use component depth + 1 to factor in the added Datasmith scene root once imported in Unreal.
|
|
FString ComponentDepthTag = FString::Printf(TEXT("SU.DEPTH.%d"), Node.Depth);
|
|
Node.DatasmithActorElement->AddTag(*ComponentDepthTag);
|
|
|
|
// Add the Datasmith actor component instance path tag.
|
|
FString InstancePathTag = Node.GetActorName().Replace(TEXT("SU"), TEXT("SU.PATH.0")).Replace(TEXT("_"), TEXT("."));
|
|
Node.DatasmithActorElement->AddTag(*InstancePathTag);
|
|
|
|
if (Node.ParentNode->DatasmithActorElement)
|
|
{
|
|
Node.ParentNode->DatasmithActorElement->AddChild(Node.DatasmithActorElement);
|
|
}
|
|
else
|
|
{
|
|
Context.DatasmithScene->AddActor(Node.DatasmithActorElement);
|
|
}
|
|
}
|
|
|
|
void FImage::UpdateOccurrenceLayer(FExportContext& Context, FNodeOccurence& Node)
|
|
{
|
|
// Parent node, component instance and layer - all should be visible to have node visible
|
|
Node.SetVisibility(Node.ParentNode->bVisible && !bHidden && bLayerVisible);
|
|
}
|
|
|
|
void FImage::UpdateOccurrenceVisibility(FExportContext& Context, FNodeOccurence& Node)
|
|
{
|
|
EntityOccurrenceVisible(&Node, Node.bVisible);
|
|
|
|
if (Node.bVisible)
|
|
{
|
|
Node.InvalidateProperties();
|
|
Node.InvalidateMeshActors();
|
|
}
|
|
else
|
|
{
|
|
Node.RemoveDatasmithActorHierarchy(Context);
|
|
}
|
|
|
|
for (FNodeOccurence* ChildNode : Node.Children)
|
|
{
|
|
ChildNode->bVisibilityInvalidated = true;
|
|
}
|
|
}
|
|
|
|
void FImage::UpdateOccurrenceMeshActors(FExportContext& Context, FNodeOccurence& Node)
|
|
{
|
|
BuildNodeNames(Node);
|
|
|
|
FString ComponentActorName = Node.GetActorName();
|
|
|
|
{
|
|
FString MeshActorName = ComponentActorName;
|
|
|
|
TSharedPtr<IDatasmithMeshActorElement> DMeshActorPtr = FDatasmithSceneFactory::CreateMeshActor(*MeshActorName);
|
|
|
|
Node.MeshActors.Add(DMeshActorPtr);
|
|
|
|
// Add the Datasmith actor component depth tag.
|
|
// We use component depth + 1 to factor in the added Datasmith scene root once imported in Unreal.
|
|
FString ComponentDepthTag = FString::Printf(TEXT("SU.DEPTH.%d"), Node.Depth + 1);
|
|
DMeshActorPtr->AddTag(*ComponentDepthTag);
|
|
|
|
// Add the Datasmith actor component instance path tag.
|
|
FString InstancePathTag = ComponentActorName.Replace(TEXT("SU"), TEXT("SU.PATH.0")).Replace(TEXT("_"), TEXT("."));
|
|
DMeshActorPtr->AddTag(*InstancePathTag);
|
|
|
|
// ADD_TRACE_LINE(TEXT("Actor %ls: %ls %ls %ls"), *MeshActorLabel, *ComponentDepthTag, *DefinitionGUIDTag, *InstancePathTag);
|
|
|
|
// Set the Datasmith mesh element used by the mesh actor.
|
|
DMeshActorPtr->SetStaticMeshPathName(GetMeshElementName());
|
|
}
|
|
|
|
Node.DatasmithActorElement = Node.MeshActors[0];
|
|
|
|
SetupActor(Context, Node);
|
|
}
|
|
|
|
void FImage::ResetOccurrenceActors(FExportContext& Context, FNodeOccurence& Node)
|
|
{
|
|
Node.ResetNodeActors(Context);
|
|
}
|
|
|
|
void FImage::InvalidateOccurrencesGeometry(FExportContext& Context)
|
|
{
|
|
for (FNodeOccurence* Node : Occurrences)
|
|
{
|
|
Node->InvalidateMeshActors();
|
|
Node->InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
void FImage::InvalidateOccurrencesProperties(FExportContext& Context)
|
|
{
|
|
// When ComponentInstance is modified we need to determine if its visibility might have changed foremost
|
|
// because this determines whether corresponding node would exist in the Datasmith scene
|
|
// Two things affect this - Hidden instance flag and layer(tag):
|
|
|
|
bool bNewHidden = false;
|
|
SUDrawingElementRef DrawingElementRef = SUImageToDrawingElement(SUImageFromEntity(EntityRef));
|
|
SUDrawingElementGetHidden(DrawingElementRef, &bNewHidden);
|
|
|
|
SUDrawingElementGetLayer(DrawingElementRef, &LayerRef);
|
|
bool bNewLayerVisible = Context.Layers.IsLayerVisible(LayerRef);
|
|
|
|
if (bHidden != bNewHidden || bLayerVisible != bNewLayerVisible)
|
|
{
|
|
bHidden = bNewHidden;
|
|
bLayerVisible = bNewLayerVisible;
|
|
for (FNodeOccurence* Node : Occurrences)
|
|
{
|
|
Node->bVisibilityInvalidated = true;
|
|
}
|
|
}
|
|
|
|
for (FNodeOccurence* Node : Occurrences)
|
|
{
|
|
Node->InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
void FImage::UpdateMetadata(FExportContext& Context)
|
|
{
|
|
ParsedMetadata = MakeUnique<FMetadata>(EntityRef);;
|
|
}
|
|
|
|
void FImage::UpdateEntityProperties(FExportContext& Context)
|
|
{
|
|
if (bPropertiesInvalidated)
|
|
{
|
|
InvalidateEntityGeometry();
|
|
}
|
|
|
|
FEntity::UpdateEntityProperties(Context);
|
|
}
|
|
|
|
namespace DatasmithSketchUp
|
|
{
|
|
class FImageFile
|
|
{
|
|
public:
|
|
struct FOptions
|
|
{
|
|
bool bColorized = false;
|
|
};
|
|
|
|
SUImageRepRef ImageRep;
|
|
FOptions Options;
|
|
|
|
FString FilePath;
|
|
|
|
bool bHasAlpha = false;
|
|
|
|
FImageFile(SUImageRepRef InImageRep)
|
|
: ImageRep(InImageRep)
|
|
{
|
|
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());
|
|
|
|
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*>(&Options.bColorized), sizeof(Options.bColorized)); // Colorized flag affects resulting texture image
|
|
MD5.Update(Data.GetData(), Data.Num());
|
|
|
|
ImageHash.Set(MD5);
|
|
|
|
TArray<SUColor> Colors;
|
|
Colors.SetNum(Width*Height);
|
|
SUImageRepGetDataAsColors(ImageRep, Colors.GetData());
|
|
|
|
bHasAlpha = Algo::AnyOf(Colors, [](const SUColor& Color) { return Color.alpha != 255; });
|
|
}
|
|
|
|
FMD5Hash ImageHash;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
TSharedPtr<FImageFile> FImageFileCollection::AddImage(SUImageRepRef ImageRep, FString FileName)
|
|
{
|
|
TSharedPtr<FImageFile> ImageFile = MakeShared<FImageFile>(ImageRep);
|
|
if (TSharedPtr<FImageFile>* Found = ImageFiles.Find(ImageFile->ImageHash))
|
|
{
|
|
return *Found;
|
|
}
|
|
|
|
ImageFile->FilePath = FPaths::Combine(Context.GetAssetsOutputPath(), TCHAR_TO_UTF8(*FileName));
|
|
SUResult SaveToFileResult = SUImageRepSaveToFile(ImageRep, TCHAR_TO_UTF8(*ImageFile->FilePath));
|
|
ensure(SaveToFileResult == SU_ERROR_NONE);
|
|
|
|
return ImageFiles.Add(ImageFile->ImageHash, ImageFile);
|
|
}
|
|
|
|
const TCHAR* FImageFileCollection::GetImageFilePath(FImageFile& ImageFile)
|
|
{
|
|
return *ImageFile.FilePath;
|
|
}
|
|
|
|
FMD5Hash FImageFileCollection::GetImageFileHash(FImageFile& ImageFile)
|
|
{
|
|
return ImageFile.ImageHash;
|
|
}
|
|
|
|
bool FImageFileCollection::GetImageHasAlpha(FImageFile& ImageFile)
|
|
{
|
|
return ImageFile.bHasAlpha;
|
|
}
|