// Copyright Epic Games, Inc. All Rights Reserved. #include "DatasmithSketchUpMaterial.h" #include "DatasmithSketchUpCommon.h" #include "DatasmithSketchUpComponent.h" #include "DatasmithSketchUpExportContext.h" #include "DatasmithSketchUpMetadata.h" #include "DatasmithSketchUpMesh.h" #include "DatasmithSketchUpString.h" #include "DatasmithSketchUpTexture.h" #include "DatasmithSketchUpUtils.h" // SketchUp SDK. #include "DatasmithSketchUpSDKBegins.h" #include "SketchUpAPI/model/component_instance.h" #include #include #include #include "SketchUpAPI/model/model.h" #include "SketchUpAPI/model/texture.h" #include "SketchUpAPI/model/typed_value.h" #include "SketchUpAPI/model/rendering_options.h" #include "DatasmithSketchUpSDKCeases.h" // Datasmith SDK. #include "Containers/Array.h" #include "DatasmithSceneFactory.h" #include "DatasmithUtils.h" #include "DatasmithMaterialsUtils.h" #include "Misc/Paths.h" #include "Misc/SecureHash.h" using namespace DatasmithSketchUp; // Structure that holds extracted data from SketchUp material during export process class FExtractedMaterial { public: FExtractedMaterial( FExportContext& Context, SUMaterialRef InSMaterialDefinitionRef // source SketchUp material definition ); // Source SketchUp material referecene SUMaterialRef SourceMaterialRef; // Extracted from SketchUp // Source SketchUp material ID. FEntityIDType SketchupSourceID; // Source SketchUp material name. FString SketchupSourceName; // Source SketchUp material type: SUMaterialType_Colored, SUMaterialType_Textured or SUMaterialType_ColorizedTexture. SUMaterialType SourceType; // Source SketchUp material color. SUColor SourceColor; // Whether or not the source SketchUp color alpha values are used. bool bSourceColorAlphaUsed; SUTextureRef TextureRef = SU_INVALID; FString LocalizedMaterialName; FString InheritedMaterialName; private: // Make the material names sanitized for Datasmith. void InitMaterialNames(); }; FExtractedMaterial::FExtractedMaterial( FExportContext& Context, SUMaterialRef InSMaterialDefinitionRef) : SourceMaterialRef(InSMaterialDefinitionRef) , SourceColor({ 128, 128, 128, 255 }) // default RGBA: sRGB opaque middle gray { SUResult SResult = SU_ERROR_NONE; // Get the material ID of the SketckUp material. SketchupSourceID = DatasmithSketchUpUtils::GetMaterialID(SourceMaterialRef); // Retrieve the SketchUp material name. SketchupSourceName = SuGetString(SUMaterialGetName, SourceMaterialRef); // Remove any name encasing "[]". SketchupSourceName.RemoveFromStart(TEXT("[")); SketchupSourceName.RemoveFromEnd(TEXT("]")); // Get the SketchUp material type. SUMaterialGetType(SourceMaterialRef, &SourceType); // we can ignore the returned SU_RESULT // Get the SketchUp material color. SUColor SMaterialColor; SResult = SUMaterialGetColor(SourceMaterialRef, &SMaterialColor); // Keep the default opaque middle gray when the material does not have a color value (SU_ERROR_NO_DATA). if (SResult == SU_ERROR_NONE) { SourceColor = SMaterialColor; } // Get the flag indicating whether or not the SketchUp color alpha values are used. SUMaterialGetUseOpacity(SourceMaterialRef, &bSourceColorAlphaUsed); // we can ignore the returned SU_RESULT // Retrieve the SketchUp material texture. SResult = SUMaterialGetTexture(SourceMaterialRef, &TextureRef); // Make the material names sanitized for Datasmith. InitMaterialNames(); } void FExtractedMaterial::InitMaterialNames() { FString SanitizedName = FDatasmithUtils::SanitizeObjectName(SketchupSourceName); FString HashedName = FMD5::HashAnsiString(*SketchupSourceName); LocalizedMaterialName = FString::Printf(TEXT("%ls-L%ls"), *SanitizedName, *HashedName); InheritedMaterialName = FString::Printf(TEXT("%ls-I%ls"), *SanitizedName, *HashedName); } FMaterialIDType const FMaterial::DEFAULT_MATERIAL_ID; FMaterialIDType const FMaterial::INHERITED_MATERIAL_ID; namespace DatasmithSketchUp { TSharedPtr CreateMaterialElement( FExportContext& Context, FExtractedMaterial& InMaterial, TCHAR const* InMaterialName, FTexture* Texture ) { // Create a Datasmith material element for the material definition. TSharedRef DatasmithMaterialElementPtr = FDatasmithSceneFactory::CreateUEPbrMaterial(InMaterialName); // Set the material element label used in the Unreal UI. FString MaterialLabel = FDatasmithUtils::SanitizeObjectName(InMaterial.SketchupSourceName); DatasmithMaterialElementPtr->SetLabel(*MaterialLabel); FLinearColor LinearColor = FMaterial::ConvertColor(InMaterial.SourceColor, InMaterial.bSourceColorAlphaUsed); DatasmithMaterialElementPtr->SetTwoSided(false);// todo: consider this IDatasmithMaterialExpression* ExpressionOpacityFromTexture = nullptr; IDatasmithMaterialExpressionScalar* ExpressionOpacityScalar = nullptr; if (Texture) { IDatasmithMaterialExpressionTexture* ExpressionTexture = DatasmithMaterialElementPtr->AddMaterialExpression< IDatasmithMaterialExpressionTexture >(); ExpressionTexture->SetName(TEXT("Texture")); ExpressionTexture->SetTexturePathName(Texture->GetDatasmithElementName()); // Apply texture scaling IDatasmithMaterialExpressionFunctionCall* UVEditExpression = DatasmithMaterialElementPtr->AddMaterialExpression< IDatasmithMaterialExpressionFunctionCall >(); UVEditExpression->SetFunctionPathName(TEXT("/DatasmithContent/Materials/UVEdit.UVEdit")); UVEditExpression->ConnectExpression(ExpressionTexture->GetInputCoordinate()); // Tiling IDatasmithMaterialExpressionColor* TilingValue = DatasmithMaterialElementPtr->AddMaterialExpression< IDatasmithMaterialExpressionColor >(); TilingValue->SetName(TEXT("UV Tiling")); TilingValue->GetColor() = FLinearColor(Texture->TextureScale.X, Texture->TextureScale.Y, 0.f); TilingValue->ConnectExpression(*UVEditExpression->GetInput(2)); IDatasmithMaterialExpressionTextureCoordinate* TextureCoordinateExpression = DatasmithMaterialElementPtr->AddMaterialExpression< IDatasmithMaterialExpressionTextureCoordinate >(); TextureCoordinateExpression->SetCoordinateIndex(0); TextureCoordinateExpression->ConnectExpression(*UVEditExpression->GetInput(0)); ExpressionTexture->ConnectExpression(DatasmithMaterialElementPtr->GetBaseColor()); // Set the Datasmith material element opacity. if (Texture->GetTextureUseAlphaChannel()) { ExpressionOpacityFromTexture = ExpressionTexture; } } else { IDatasmithMaterialExpressionColor* ExpressionColor = DatasmithMaterialElementPtr->AddMaterialExpression(); ExpressionColor->SetName(TEXT("Base Color")); ExpressionColor->GetColor() = LinearColor; ExpressionColor->ConnectExpression(DatasmithMaterialElementPtr->GetBaseColor()); } // Create scalar parameter if opacity is set in material or texture has opacity channel recognized by SU if (InMaterial.bSourceColorAlphaUsed || ExpressionOpacityFromTexture) { ExpressionOpacityScalar = DatasmithMaterialElementPtr->AddMaterialExpression(); ExpressionOpacityScalar->SetName(TEXT("Opacity")); ExpressionOpacityScalar->GetScalar() = InMaterial.bSourceColorAlphaUsed ? (float(InMaterial.SourceColor.alpha) / float(255)) : 1.f; ExpressionOpacityScalar->ConnectExpression(DatasmithMaterialElementPtr->GetOpacity()); } IDatasmithMaterialExpression* ExpressionOpacity = nullptr; if (ExpressionOpacityFromTexture) { IDatasmithMaterialExpressionGeneric* Multiply = DatasmithMaterialElementPtr->AddMaterialExpression(); Multiply->SetExpressionName(TEXT("Multiply")); ExpressionOpacityFromTexture->ConnectExpression(*Multiply->GetInput(0), 4); // Use Alpha output from Texture sampler ExpressionOpacityScalar->ConnectExpression(*Multiply->GetInput(1)); ExpressionOpacity = Multiply; } else { ExpressionOpacity = ExpressionOpacityScalar; } if (ExpressionOpacity) { ExpressionOpacity->ConnectExpression(DatasmithMaterialElementPtr->GetOpacity()); DatasmithMaterialElementPtr->SetBlendMode(/*EBlendMode::BLEND_Translucent*/2); } Context.DatasmithScene->AddMaterial(DatasmithMaterialElementPtr); // Return the Datasmith material element. return DatasmithMaterialElementPtr; } void FModel::ApplyOverrideMaterialToNode(FNodeOccurence& Node, FMaterialOccurrence& Material) { GetDefinition()->ApplyOverrideMaterialToNode(Node, Material); } void FComponentInstance::ApplyOverrideMaterialToNode(FNodeOccurence& Node, FMaterialOccurrence& Material) { GetDefinition()->ApplyOverrideMaterialToNode(Node, Material); } void FDefinition::ApplyOverrideMaterialToNode(FNodeOccurence& Node, FMaterialOccurrence& Material) { FEntitiesGeometry& EntitiesGeometry = *GetEntities().EntitiesGeometry; FEntitiesGeometry::FExportedGeometry& ExportedGeometry = EntitiesGeometry.GetOccurrenceExportedGeometry(Node); for (int32 MeshIndex = 0; MeshIndex < Node.MeshActors.Num(); ++MeshIndex) { // Update Override(Inherited) Material // todo: set inherited material only on mesh actors that have faces with default material, right now setting on every mesh, hot harmful but excessive if (ExportedGeometry.IsMeshUsingInheritedMaterial(MeshIndex)) { const TSharedPtr& MeshActor = Node.MeshActors[MeshIndex]; // SketchUp has 'material override' only for single('Default') material. // So we reset overrides on the actor to remove this single override(if it was set) and re-add new override MeshActor->ResetMaterialOverrides(); // Clear previous override if was set MeshActor->AddMaterialOverride(Material.GetName(), EntitiesGeometry.GetInheritedMaterialOverrideSlotId()); } } } } FMaterial::FMaterial(SUMaterialRef InMaterialRef) : MaterialRef(InMaterialRef) , bInvalidated(true) {} TSharedPtr FMaterial::Create(FExportContext& Context, SUMaterialRef InMaterialRef) { TSharedPtr Material = MakeShared(InMaterialRef); return Material; } void FMaterial::Invalidate() { bInvalidated = true; } FLinearColor FMaterial::ConvertColor(const SUColor& C, bool bAlphaUsed) { FColor SRGBColor(C.red, C.green, C.blue, bAlphaUsed ? C.alpha : 255); return FLinearColor(SRGBColor); } TSharedRef FMaterial::CreateDefaultMaterialElement(FExportContext& Context) { TSharedRef DatasmithMaterialElementPtr = FDatasmithSceneFactory::CreateUEPbrMaterial(TEXT("Default")); FLinearColor LinearColor(0.5, 0.5, 0.5, 1); // Retrieve Front Face color current Style - Styles api doesn't have a way to access it, using RenderingOptions instead SURenderingOptionsRef RenderingOptionsRef = SU_INVALID; if (SUModelGetRenderingOptions(Context.ModelRef, &RenderingOptionsRef) == SU_ERROR_NONE) { SUTypedValueRef ColorTypedValue = SU_INVALID; SUTypedValueCreate(&ColorTypedValue); if (SURenderingOptionsGetValue(RenderingOptionsRef, "FaceFrontColor", &ColorTypedValue) == SU_ERROR_NONE) { SUColor Color; if (SUTypedValueGetColor(ColorTypedValue, &Color) == SU_ERROR_NONE) { LinearColor = ConvertColor(Color); } } SUTypedValueRelease(&ColorTypedValue); } DatasmithMaterialElementPtr->SetTwoSided(false);// todo: consider this IDatasmithMaterialExpressionColor* ExpressionColor = DatasmithMaterialElementPtr->AddMaterialExpression(); ExpressionColor->SetName(TEXT("Base Color")); ExpressionColor->GetColor() = LinearColor; ExpressionColor->ConnectExpression(DatasmithMaterialElementPtr->GetBaseColor()); Context.DatasmithScene->AddMaterial(DatasmithMaterialElementPtr); return DatasmithMaterialElementPtr; } void FMaterial::Remove(FExportContext& Context) { // Expecting that SketchUp removes material from geometry/instances prior to removing it from the scene, but in case it doesn't... if (!ensure(MaterialDirectlyAppliedToMeshes.MeshesMaterialDirectlyAppliedTo.IsEmpty())) { for (FEntitiesGeometry* Geom: MaterialDirectlyAppliedToMeshes.MeshesMaterialDirectlyAppliedTo.Array()) { UnregisterGeometry(Context, Geom); } } // Expecting that SketchUp removes material from geometry/instances prior to removing it from the scene, but in case it doesn't... if (!ensure(MaterialInheritedByNodes.NodesMaterialInheritedBy.IsEmpty())) { for (FNodeOccurence* Node: MaterialDirectlyAppliedToMeshes.NodesMaterialInheritedBy.Array()) { UnregisterInstance(Context, Node); } } Context.Textures.UnregisterMaterial(this); } FMD5Hash FMaterial::ComputeHash(FExportContext& Context) { FMD5 MD5; FExtractedMaterial ExtractedMaterial(Context, MaterialRef); MD5.Update(reinterpret_cast(&ExtractedMaterial.SourceType), sizeof(ExtractedMaterial.SourceType)); MD5.Update(reinterpret_cast(&ExtractedMaterial.SourceColor), sizeof(ExtractedMaterial.SourceColor)); MD5.Update(reinterpret_cast(&ExtractedMaterial.bSourceColorAlphaUsed), sizeof(ExtractedMaterial.bSourceColorAlphaUsed)); // MD5.Update(reinterpret_cast(&ExtractedMaterial.SketchupSourceName), sizeof(ExtractedMaterial.SketchupSourceName)); size_t TextureWidth = 0; size_t TextureHeight = 0; double TextureSScale = 1.0; double TextureTScale = 1.0; SUTextureGetDimensions(ExtractedMaterial.TextureRef, &TextureWidth, &TextureHeight, &TextureSScale, &TextureTScale); bool bUseAlphaChannel = false; SUTextureGetUseAlphaChannel(ExtractedMaterial.TextureRef, &bUseAlphaChannel); MD5.Update(reinterpret_cast(&TextureWidth), sizeof(TextureWidth)); MD5.Update(reinterpret_cast(&TextureHeight), sizeof(TextureHeight)); MD5.Update(reinterpret_cast(&TextureSScale), sizeof(TextureSScale)); MD5.Update(reinterpret_cast(&TextureTScale), sizeof(TextureTScale)); MD5.Update(reinterpret_cast(&bUseAlphaChannel), sizeof(bUseAlphaChannel)); FMD5Hash Hash; Hash.Set(MD5); return Hash; } void FMaterial::UpdateTexturesUsage(FExportContext& Context) { if (!bInvalidated) { return; } if (Texture) { Context.Textures.UnregisterMaterial(this); Texture = nullptr; } // todo: extract once(another usage in Update) FExtractedMaterial ExtractedMaterial(Context, MaterialRef); if (SUIsValid(ExtractedMaterial.TextureRef)) { Texture = (ExtractedMaterial.SourceType == SUMaterialType::SUMaterialType_ColorizedTexture) ? Context.Textures.AddColorizedTexture(ExtractedMaterial.TextureRef, ExtractedMaterial.SketchupSourceName) : Context.Textures.AddTexture(ExtractedMaterial.TextureRef, ExtractedMaterial.SketchupSourceName); Context.Textures.RegisterMaterial(this); } } void FMaterial::Update(FExportContext& Context) { FExtractedMaterial ExtractedMaterial(Context, MaterialRef); EntityId = ExtractedMaterial.SketchupSourceID.EntityID; MaterialDirectlyAppliedToMeshes.RemoveDatasmithElement(Context); if (MaterialDirectlyAppliedToMeshes.HasUsers()) { MaterialDirectlyAppliedToMeshes.DatasmithElement = CreateMaterialElement(Context, ExtractedMaterial, *ExtractedMaterial.LocalizedMaterialName, Texture); } MaterialInheritedByNodes.RemoveDatasmithElement(Context); if (MaterialInheritedByNodes.HasUsers()) { MaterialInheritedByNodes.DatasmithElement = CreateMaterialElement(Context, ExtractedMaterial, *ExtractedMaterial.InheritedMaterialName, Texture); } bInvalidated = false; } FMaterialOccurrence& FMaterial::RegisterGeometry(FEntitiesGeometry* Geom) { MaterialDirectlyAppliedToMeshes.RegisterGeometry(Geom); if (MaterialDirectlyAppliedToMeshes.IsInvalidated()) { Invalidate(); // Invalidate material if occurrence is not built } return MaterialDirectlyAppliedToMeshes; } void FMaterial::UnregisterGeometry(FExportContext& Context, FEntitiesGeometry* Geom) { MaterialDirectlyAppliedToMeshes.UnregisterGeometry(Context, Geom); } FMaterialOccurrence& FMaterial::RegisterInstance(FNodeOccurence* NodeOccurrence) { MaterialInheritedByNodes.RegisterInstance(NodeOccurrence); if (MaterialInheritedByNodes.IsInvalidated()) { Invalidate(); // Invalidate material if occurrence is not built } NodeOccurrence->MaterialOverride = this; return MaterialInheritedByNodes; } void FMaterial::UnregisterInstance(FExportContext& Context, FNodeOccurence* NodeOccurrence) { MaterialInheritedByNodes.UnregisterInstance(Context, NodeOccurrence); NodeOccurrence->MaterialOverride = nullptr; } const TCHAR* FMaterialOccurrence::GetName() { if (!DatasmithElement) { return nullptr; } return DatasmithElement->GetName(); } void FEntitiesGeometry::SetMaterial(const TCHAR* MaterialName, TFunctionRef SlotMapping) { ForEachExportedMesh([MaterialName, SlotMapping](const FDatasmithInstantiatedMesh& Mesh) { int32 SlotId; if (SlotMapping(Mesh, SlotId)) { Mesh.DatasmithMesh->SetMaterial(MaterialName, SlotId); } }); } void FEntitiesGeometry::ForEachExportedMesh(TFunctionRef Callback) { for (const TSharedPtr& Mesh : ExportedGeometryForInstances.Meshes) { Callback(*Mesh); } for (const TPair& NodeExportedGeometry : ExportedGeometryForTransform) { for (TSharedPtr Mesh : NodeExportedGeometry.Value.Meshes) { Callback(*Mesh); } } } // todo: make virtual FMaterialOccurrence - for layer/regular materials different? // they differ by the way they are applied to geometry/nodes - // VERY tied to meshes composition - they either map slots from SU Material or from SU Layer void FMaterialOccurrence::ApplyRegularMaterial(FMaterialIDType MaterialId) { // Apply material to meshes for (FEntitiesGeometry* Geometry : MeshesMaterialDirectlyAppliedTo) { Geometry->SetMaterial(GetName(), [MaterialId](const FDatasmithInstantiatedMesh& Mesh, int32& OutSlotId) { if (const int32* SlotIdPtr = Mesh.SlotIdForMaterialId.Find(MaterialId)) { OutSlotId = *SlotIdPtr; return true; } return false; }); } // Apply material to mesh actors for (FNodeOccurence* NodePtr : NodesMaterialInheritedBy) { NodePtr->Entity.ApplyOverrideMaterialToNode(*NodePtr, *this); } } void FMaterialOccurrence::ApplyLayerMaterial(FLayerIDType LayerId) { // Apply material to meshes for (FEntitiesGeometry* Geometry : MeshesMaterialDirectlyAppliedTo) { Geometry->SetMaterial(GetName(), [LayerId](const FDatasmithInstantiatedMesh& Mesh, int32& OutSlotId) { if (const int32* SlotIdPtr = Mesh.SlotIdForLayerId.Find(LayerId)) { OutSlotId = *SlotIdPtr; return true; } return false; }); } // Apply material to mesh actors for (FNodeOccurence* NodePtr : NodesMaterialInheritedBy) { NodePtr->Entity.ApplyOverrideMaterialToNode(*NodePtr, *this); } } void FMaterialOccurrence::RemoveDatasmithElement(FExportContext& Context) { if (!DatasmithElement) { return; } Context.DatasmithScene->RemoveMaterial(DatasmithElement); DatasmithElement.Reset(); } bool FMaterialOccurrence::RemoveUser(FExportContext& Context) { if(!ensure(UserCount != 0)) { return true; } UserCount--; if (UserCount != 0) { return false; } RemoveDatasmithElement(Context); return true; } void FMaterialOccurrence::RegisterGeometry(FEntitiesGeometry* Geom) { if (!MeshesMaterialDirectlyAppliedTo.Contains(Geom)) { MeshesMaterialDirectlyAppliedTo.Add(Geom); AddUser(); } } void FMaterialOccurrence::UnregisterGeometry(FExportContext& Context, FEntitiesGeometry* Geom) { if (!MeshesMaterialDirectlyAppliedTo.Contains(Geom)) { return; } MeshesMaterialDirectlyAppliedTo.Remove(Geom); RemoveUser(Context); } void FMaterialOccurrence::RegisterInstance(FNodeOccurence* NodeOccurrence) { NodesMaterialInheritedBy.Add(NodeOccurrence); AddUser(); } void FMaterialOccurrence::UnregisterInstance(FExportContext& Context, FNodeOccurence* NodeOccurrence) { NodesMaterialInheritedBy.Remove(NodeOccurrence); RemoveUser(Context); } void FRegularMaterials::RemoveUnused() { TArray UnusedMaterials; for (TPair> IdAndMaterial : MaterialForMaterialId) { FMaterial& Material = *IdAndMaterial.Value; if (!Material.IsUsed()) { UnusedMaterials.Add(IdAndMaterial.Key); } } // Remove unused materials(SketchUp material removal notification is unusable but we need to remove unused materials) for (FMaterialIDType MaterialId: UnusedMaterials) { RemoveMaterial(MaterialId); } if (!DefaultMaterial.HasUsers()) { DefaultMaterial.Invalidate(Context); } } void FRegularMaterials::UpdateDefaultMaterial() { if (DefaultMaterial.IsInvalidated() && DefaultMaterial.HasUsers()) { DefaultMaterial.DatasmithElement = FMaterial::CreateDefaultMaterialElement(Context); DefaultMaterial.ApplyRegularMaterial(FMaterial::INHERITED_MATERIAL_ID); } } void FRegularMaterials::Apply(FMaterial* Material) { FMaterialIDType* MaterialIdFound = MaterialIdForMaterial.Find(Material); if (!MaterialIdFound) { return; } FMaterialIDType MaterialId = *MaterialIdFound; Material->MaterialDirectlyAppliedToMeshes.ApplyRegularMaterial(MaterialId); Material->MaterialInheritedByNodes.ApplyRegularMaterial(MaterialId); } void FLayerMaterials::RemoveUnused() { TArray UnusedMaterials; for (TPair> IdAndMaterial : MaterialForLayerId) { FMaterial& Material = *IdAndMaterial.Value; if (!Material.IsUsed()) { UnusedMaterials.Add(IdAndMaterial.Key); } } for (FLayerIDType LayerId: UnusedMaterials) { TSharedPtr Material; if (MaterialForLayerId.RemoveAndCopyValue(LayerId, Material)) { MaterialHashForLayerId.Remove(LayerId); LayerIdForMaterial.Remove(Material.Get()); Context.Materials.RemoveMaterial(Material.Get()); } } } void FLayerMaterials::Apply(FMaterial* Material) { FLayerIDType* Found = LayerIdForMaterial.Find(Material); if (!Found) { return; } FLayerIDType LayerId = *Found; MaterialHashForLayerId.Add(LayerId, Material->ComputeHash(Context)); Material->MaterialDirectlyAppliedToMeshes.ApplyLayerMaterial(LayerId); Material->MaterialInheritedByNodes.ApplyLayerMaterial(LayerId); } void FLayerMaterials::UpdateLayer(SULayerRef LayerRef) { if (TSharedPtr* Found = MaterialForLayerId.Find(DatasmithSketchUpUtils::GetEntityID(SULayerToEntity(LayerRef)))) { (*Found)->Invalidate(); } } // todo: VERY similar to regular material's RegisterInstance FMaterialOccurrence* FLayerMaterials::RegisterInstance(FLayerIDType LayerID, FNodeOccurence* NodeOccurrence) { if (NodeOccurrence->MaterialOverride) { NodeOccurrence->MaterialOverride->UnregisterInstance(Context, NodeOccurrence); } if (const TSharedPtr Material = FindOrCreateMaterialForLayer(LayerID)) { return &Material->RegisterInstance(NodeOccurrence); } return {}; // Don't use a material if material id is unknown(of default) } // Test if a layer material has changed since last update bool FLayerMaterials::CheckForModifications() { size_t LayerCount = 0; SUModelGetNumLayers(Context.ModelRef, &LayerCount); TArray Layers; Layers.Init(SU_INVALID, LayerCount); SUResult SResult = SUModelGetLayers(Context.ModelRef, LayerCount, Layers.GetData(), &LayerCount); Layers.SetNum(LayerCount); bool bHasModifications = false; for (SULayerRef LayerRef : Layers) { FLayerIDType LayerId = DatasmithSketchUpUtils::GetEntityID(SULayerToEntity(LayerRef)); if (TSharedPtr* Found = MaterialForLayerId.Find(LayerId)) { TSharedPtr Material = *Found; FMD5Hash Hash = Material->ComputeHash(Context); FMD5Hash* HashFound = MaterialHashForLayerId.Find(LayerId); if (!HashFound || (*HashFound != Hash)) { Material->Invalidate(); bHasModifications = true; } } } return bHasModifications; } void FMaterialCollection::Update() { LayerMaterials.RemoveUnused(); RegularMaterials.RemoveUnused(); // Update usage of materials and textures by materials before updating textures(to only update used textures) for (FMaterial* Material : Materials) { Material->UpdateTexturesUsage(Context); } Context.Textures.Update(); // Update materials after textures are updated - some materials might end up using shared texture(in case it has same contents, which is determined in Textures Update) for (FMaterial* Material: Materials) { if (Material->IsInvalidated()) { Material->Update(Context); LayerMaterials.Apply(Material); RegularMaterials.Apply(Material); } } RegularMaterials.UpdateDefaultMaterial(); } namespace DatasmithSketchUp { class FImageMaterial { public: struct FOptions { bool bScaleTexture = false; FVector2D TextureScale; bool bUseAlphaChannel = false; bool bTranslucent = false; }; FImageMaterial(FExportContext& Context, FImage& Image, FImageFile& ImageFile) { FString NameBase = Image.GetEntityName(); FString MaterialName = FString::Printf(TEXT("image_material_%s"), *NameBase); FString MaterialLabel = NameBase; FString TextureName = FString::Printf(TEXT("image_texture_%s"), *NameBase); TextureElement = FDatasmithSceneFactory::CreateTexture(*TextureName); TextureElement->SetSRGB(EDatasmithColorSpace::sRGB); TextureElement->SetFile(Context.ImageFiles.GetImageFilePath(ImageFile)); Context.DatasmithScene->AddTexture(TextureElement); FImageMaterial::FOptions MaterialOptions; MaterialOptions.bUseAlphaChannel = Context.ImageFiles.GetImageHasAlpha(ImageFile); CreateMaterialElement(Context, *MaterialName, *MaterialLabel, TextureElement->GetName(), MaterialOptions); Hash = Context.ImageFiles.GetImageFileHash(ImageFile); } void Release(FExportContext& Context) { if (DatasmithMaterialElement) { Context.DatasmithScene->RemoveMaterial(DatasmithMaterialElement); DatasmithMaterialElement.Reset(); } if (TextureElement) { Context.DatasmithScene->RemoveTexture(TextureElement); TextureElement.Reset(); } } const TCHAR* GetMaterialElementName() { return DatasmithMaterialElement->GetName(); } TSharedPtr CreateMaterialElement( FExportContext& Context, const TCHAR* InMaterialName, const TCHAR* InMaterialLabel, const TCHAR* InDatasmithTextureElementName, const FOptions& Options ) { // Create a Datasmith material element for the material definition. DatasmithMaterialElement = FDatasmithSceneFactory::CreateUEPbrMaterial(InMaterialName); // Set the material element label used in the Unreal UI. FString MaterialLabel = FDatasmithUtils::SanitizeObjectName(InMaterialLabel); DatasmithMaterialElement->SetLabel(*MaterialLabel); DatasmithMaterialElement->SetTwoSided(true); // imported Image in SketchUp is displayed as two-sided with no way to controls it, so bool bTranslucent = false; { IDatasmithMaterialExpressionTexture* ExpressionTexture = DatasmithMaterialElement->AddMaterialExpression< IDatasmithMaterialExpressionTexture >(); ExpressionTexture->SetName(TEXT("Texture")); ExpressionTexture->SetTexturePathName(InDatasmithTextureElementName); // Apply texture scaling if (Options.bScaleTexture && !Options.TextureScale.Equals(FVector2D::UnitVector)) { IDatasmithMaterialExpressionFunctionCall* UVEditExpression = DatasmithMaterialElement->AddMaterialExpression< IDatasmithMaterialExpressionFunctionCall >(); UVEditExpression->SetFunctionPathName(TEXT("/DatasmithContent/Materials/UVEdit.UVEdit")); UVEditExpression->ConnectExpression(ExpressionTexture->GetInputCoordinate()); // Tiling IDatasmithMaterialExpressionColor* TilingValue = DatasmithMaterialElement->AddMaterialExpression< IDatasmithMaterialExpressionColor >(); TilingValue->SetName(TEXT("UV Tiling")); TilingValue->GetColor() = FLinearColor(Options.TextureScale.X, Options.TextureScale.Y, 0.f); TilingValue->ConnectExpression(*UVEditExpression->GetInput(2)); IDatasmithMaterialExpressionTextureCoordinate* TextureCoordinateExpression = DatasmithMaterialElement->AddMaterialExpression< IDatasmithMaterialExpressionTextureCoordinate >(); TextureCoordinateExpression->SetCoordinateIndex(0); TextureCoordinateExpression->ConnectExpression(*UVEditExpression->GetInput(0)); } ExpressionTexture->ConnectExpression(DatasmithMaterialElement->GetBaseColor()); bTranslucent = Options.bTranslucent || Options.bUseAlphaChannel; // Set the Datasmith material element opacity. if (Options.bUseAlphaChannel) { ExpressionTexture->ConnectExpression(DatasmithMaterialElement->GetOpacity(), 4); // Use Alpha output from Texture sampler } } if (bTranslucent) { DatasmithMaterialElement->SetBlendMode(/*EBlendMode::BLEND_Translucent*/2); } // World Position Offset // Shift image along local Z { IDatasmithMaterialExpressionGeneric* ExpressionObjectOrientation = DatasmithMaterialElement->AddMaterialExpression(); ExpressionObjectOrientation->SetExpressionName(TEXT("ObjectOrientation")); IDatasmithMaterialExpressionScalar* ExpressionPositionOffset = DatasmithMaterialElement->AddMaterialExpression(); ExpressionPositionOffset->SetName(TEXT("PositionOffset")); ExpressionPositionOffset->GetScalar() = 1.f; IDatasmithMaterialExpressionGeneric* MultiplyOrientationWithOffset = DatasmithMaterialElement->AddMaterialExpression(); MultiplyOrientationWithOffset->SetExpressionName(TEXT("Multiply")); ExpressionObjectOrientation->ConnectExpression(*MultiplyOrientationWithOffset->GetInput(0)); ExpressionPositionOffset->ConnectExpression(*MultiplyOrientationWithOffset->GetInput(1)); MultiplyOrientationWithOffset->ConnectExpression(DatasmithMaterialElement->GetWorldPositionOffset()); } Context.DatasmithScene->AddMaterial(DatasmithMaterialElement); // Return the Datasmith material element. return DatasmithMaterialElement; } TSet ImagesForMaterial; FMD5Hash Hash; private: TSharedPtr DatasmithMaterialElement; TSharedPtr TextureElement; }; } void FImageCollection::ReleaseMaterial(FImage& Image) { if (Image.ImageMaterial) { Image.ImageMaterial->ImagesForMaterial.Remove(&Image); if (Image.ImageMaterial->ImagesForMaterial.IsEmpty()) { Image.ImageMaterial->Release(Context); // Clean up material from the datasmith scene before deallocating ImageMaterials.Remove(Image.ImageMaterial->Hash); } Image.ImageMaterial = nullptr; } } const TCHAR* FImageCollection::AcquireMaterial(FImage& Image) { if (!ensure(!Image.ImageMaterial)) { ReleaseMaterial(Image); } SUImageRepRef ImageRep = SU_INVALID; ensure(SU_ERROR_NONE == SUImageRepCreate(&ImageRep)); SUResult ImageGetImageRepResult = SUImageGetImageRep(SUImageFromEntity(Image.EntityRef), &ImageRep); ensure(ImageGetImageRepResult == SU_ERROR_NONE); TSharedPtr ImageFile = Context.ImageFiles.AddImage(ImageRep, Image.GetFileName()); SUResult ImageRepReleaseResult = SUImageRepRelease(&ImageRep); ensure(SU_ERROR_NONE == ImageRepReleaseResult); TSharedPtr Material; if (TSharedPtr* Found = ImageMaterials.Find(Context.ImageFiles.GetImageFileHash(*ImageFile))) { Material = (*Found); } else { Material = MakeShared(Context, Image, *ImageFile); ImageMaterials.Add(Material->Hash, Material); } Material->ImagesForMaterial.Add(&Image); Image.ImageMaterial = Material.Get(); return Material->GetMaterialElementName(); }