// 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 #include #include #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& 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& 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 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(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 Data; Data.SetNumUninitialized(DataSize); SUImageRepGetData(ImageRep, DataSize, Data.GetData()); FMD5 MD5; MD5.Update(reinterpret_cast(&Width), sizeof(Width)); MD5.Update(reinterpret_cast(&Height), sizeof(Height)); MD5.Update(reinterpret_cast(&Bpp), sizeof(Bpp)); MD5.Update(reinterpret_cast(&Options.bColorized), sizeof(Options.bColorized)); // Colorized flag affects resulting texture image MD5.Update(Data.GetData(), Data.Num()); ImageHash.Set(MD5); TArray Colors; Colors.SetNum(Width*Height); SUImageRepGetDataAsColors(ImageRep, Colors.GetData()); bHasAlpha = Algo::AnyOf(Colors, [](const SUColor& Color) { return Color.alpha != 255; }); } FMD5Hash ImageHash; }; } TSharedPtr FImageFileCollection::AddImage(SUImageRepRef ImageRep, FString FileName) { TSharedPtr ImageFile = MakeShared(ImageRep); if (TSharedPtr* 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; }