// Copyright Epic Games, Inc. All Rights Reserved. #include "SMaterialAnalyzer.h" #include "AssetRegistry/AssetRegistryModule.h" #include "ContentBrowserDataLegacyBridge.h" #include "PropertyCustomizationHelpers.h" #include "CollectionManagerModule.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Framework/Views/TableViewMetadata.h" #include "Framework/Application/SlateApplication.h" #include "ICollectionContainer.h" #include "ICollectionManager.h" #include "ICollectionSource.h" #include "AssetManagerEditorModule.h" #include "Materials/Material.h" #include "Materials/MaterialInstance.h" #include "Misc/Paths.h" #include "Misc/FileHelper.h" #include "SAnalyzedMaterialNodeWidgetItem.h" #include "Widgets/Images/SThrobber.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SEditableText.h" #include "Widgets/Layout/SScrollBox.h" #include "Widgets/Views/STreeView.h" #include "Filters/SFilterSearchBox.h" #include "Styling/StyleColors.h" #include "DesktopPlatformModule.h" #include "ProfilingDebugging/DiagnosticTable.h" #define LOCTEXT_NAMESPACE "MaterialAnalyzer" static TMap BasePropertyOverrideNames; SMaterialAnalyzer::SMaterialAnalyzer() : BuildBaseMaterialTreeTask(nullptr) , AnalyzeTreeTask(nullptr) , AnalyzeForIdenticalPermutationsTask(nullptr) , bWaitingForAssetRegistryLoad(false) { BasePropertyOverrideNames.Empty(); BasePropertyOverrideNames.Add(TEXT("bOverride_OpacityMaskClipValue"), TEXT("OpacityMaskClipValueOverride")); BasePropertyOverrideNames.Add(TEXT("bOverride_BlendMode"), TEXT("BlendModeOverride")); BasePropertyOverrideNames.Add(TEXT("bOverride_ShadingModel"), TEXT("ShadingModelOverride")); BasePropertyOverrideNames.Add(TEXT("bOverride_DitheredLODTransition"), TEXT("DitheredLODTransitionOverride")); BasePropertyOverrideNames.Add(TEXT("bOverride_CastDynamicShadowAsMasked"), TEXT("CastDynamicShadowAsMaskedOverride")); BasePropertyOverrideNames.Add(TEXT("bOverride_TwoSided"), TEXT("TwoSidedOverride")); BasePropertyOverrideNames.Add(TEXT("bOverride_bIsThinSurface"), TEXT("bIsThinSurfaceOverride")); BasePropertyOverrideNames.Add(TEXT("bOverride_OutputTranslucentVelocity"), TEXT("bOutputTranslucentVelocity")); BasePropertyOverrideNames.Add(TEXT("bOverride_bHasPixelAnimation"), TEXT("bHasPixelAnimation")); BasePropertyOverrideNames.Add(TEXT("bOverride_bEnableTessellation"), TEXT("bEnableTessellation")); BasePropertyOverrideNames.Add(TEXT("bOverride_DisplacementScaling"), TEXT("DisplacementScaling")); BasePropertyOverrideNames.Add(TEXT("bOverride_bEnableDisplacementFade"), TEXT("bEnableDisplacementFade")); BasePropertyOverrideNames.Add(TEXT("bOverride_DisplacementFadeRange"), TEXT("DisplacementFadeRange")); BasePropertyOverrideNames.Add(TEXT("bOverride_MaxWorldPositionOffsetDisplacement"), TEXT("MaxWorldPositionOffsetDisplacement")); BasePropertyOverrideNames.Add(TEXT("bOverride_CompatibleWithLumenCardSharing"), TEXT("bCompatibleWithLumenCardSharing")); } SMaterialAnalyzer::~SMaterialAnalyzer() { } const FAssetData* FindParentAssetData(const FAssetData* InAssetData, const TArray& ArrayToSearch) { check(InAssetData != nullptr); static const FName NAME_Parent = TEXT("Parent"); FString ParentPathString = InAssetData->GetTagValueRef(NAME_Parent); int32 FirstCut = INDEX_NONE; ParentPathString.FindChar(L'\'', FirstCut); FSoftObjectPath ParentPath; if(FirstCut != INDEX_NONE) { ParentPath = ParentPathString.Mid(FirstCut + 1, ParentPathString.Len() - FirstCut - 2); } else { ParentPath = ParentPathString; } if(ParentPath.IsValid()) { return ArrayToSearch.FindByPredicate( [&](FAssetData& Entry) { return Entry.GetSoftObjectPath() == ParentPath; } ); } return nullptr; } void SMaterialAnalyzer::Construct(const FArguments& InArgs, const TSharedRef& ConstructUnderMajorTab, const TSharedPtr& ConstructUnderWindow) { TArray AllowedClasses; AllowedClasses.Add(UMaterialInterface::StaticClass()); TSharedRef AssetPickerWidget = SNew(SObjectPropertyEntryBox) .ObjectPath(this, &SMaterialAnalyzer::GetCurrentAssetPath) .AllowedClass(UMaterialInterface::StaticClass()) .OnObjectChanged(this, &SMaterialAnalyzer::OnAssetSelected) .AllowClear(false) .DisplayUseSelected(true) .DisplayBrowse(true) .NewAssetFactories(TArray()) .IsEnabled(this, &SMaterialAnalyzer::IsMaterialSelectionAllowed); this->ChildSlot [ SNew(SBorder) .BorderImage(FCoreStyle::Get().GetBrush("ToolPanel.GroupBorder")) .BorderBackgroundColor(FLinearColor::Gray) // Darken the outer border [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() .Padding(FMargin(5.0f, 5.0f, 5.0f, 5.0f)) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(LOCTEXT("MaterialToAnalyzeLabel", "Material To Analyze: ")) ] + SHorizontalBox::Slot() .FillWidth(0.4f) [ AssetPickerWidget ] + SHorizontalBox::Slot() .FillWidth(0.4f) [ SNew(SFilterSearchBox) .HintText(LOCTEXT("MaterialParametersToFilterHint", "Parameters to Filter...")) .ToolTipText(LOCTEXT("FilterSearchHint", "Type here to search (pressing enter selects the results)")) .OnTextCommitted(this, &SMaterialAnalyzer::OnParameterFilterChanged) ] +SHorizontalBox::Slot() .FillWidth(0.2f) [ SNew(SButton) .ButtonStyle(FAppStyle::Get(), "ButtonStyle") .OnClicked(this, &SMaterialAnalyzer::OnExportAnalyzedMaterialToCSV) .ContentPadding(FMargin(2.0f)) .Content() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f) [ SNew(SImage) .Image(FAppStyle::Get().GetBrush("Icons.Save")) .ColorAndOpacity(FSlateColor(EStyleColor::Black)) ] +SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f) [ SNew(STextBlock) .Text(LOCTEXT("ExportToCSV", "Export to CSV")) .ColorAndOpacity(FSlateColor(EStyleColor::Black)) ] ] ] ] + SVerticalBox::Slot() .FillHeight(1.0f) [ SNew(SSplitter) .Orientation(EOrientation::Orient_Vertical) + SSplitter::Slot() [ SNew(SBorder) .Padding(0) .BorderImage(FCoreStyle::Get().GetBrush("ToolPanel.GroupBorder")) [ SAssignNew(MaterialTree, SAnalyzedMaterialTree) .TreeItemsSource(&MaterialTreeRoot) .OnGenerateRow(this, &SMaterialAnalyzer::HandleReflectorTreeGenerateRow) .OnGetChildren(this, &SMaterialAnalyzer::HandleReflectorTreeGetChildren) .OnSetExpansionRecursive(this, &SMaterialAnalyzer::HandleReflectorTreeRecursiveExpansion) .HeaderRow ( SNew(SHeaderRow) + SHeaderRow::Column(SAnalyzedMaterialNodeWidgetItem::NAME_MaterialName) .DefaultLabel(LOCTEXT("MaterialNameLabel", "Material Name")) .FillWidth(0.80f) + SHeaderRow::Column(SAnalyzedMaterialNodeWidgetItem::NAME_NumberOfChildren) .DefaultLabel(LOCTEXT("NumberOfMaterialChildrenLabel", "Number of Children (Direct/Total)")) + SHeaderRow::Column(SAnalyzedMaterialNodeWidgetItem::NAME_BasePropertyOverrides) .DefaultLabel(LOCTEXT("BasePropertyOverridesLabel", "Base Property Overrides")) + SHeaderRow::Column(SAnalyzedMaterialNodeWidgetItem::NAME_MaterialLayerParameters) .DefaultLabel(LOCTEXT("MaterialLayerParametersLabel", "Material Layer Parameters")) + SHeaderRow::Column(SAnalyzedMaterialNodeWidgetItem::NAME_StaticSwitchParameters) .DefaultLabel(LOCTEXT("StaticSwitchParametersLabel", "Static Switch Parameters")) + SHeaderRow::Column(SAnalyzedMaterialNodeWidgetItem::NAME_StaticComponentMaskParameters) .DefaultLabel(LOCTEXT("StaticComponenetMaskParametersLabel", "Static Component Mask Parameters")) ) ] ] + SSplitter::Slot() [ SNew(SBorder) .BorderImage(FCoreStyle::Get().GetBrush("ToolPanel.GroupBorder")) .BorderBackgroundColor(FLinearColor::Gray) // Darken the outer border [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(LOCTEXT("SuggestionsLabel", "Suggestions")) ] + SVerticalBox::Slot() [ SAssignNew(SuggestionsBox, SScrollBox) ] ] ] ] + SVerticalBox::Slot() .AutoHeight() .VAlign(EVerticalAlignment::VAlign_Bottom) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .VAlign(VAlign_Center) [ SAssignNew(StatusBox, STextBlock) .Text(LOCTEXT("DoneLabel", "Done")) ] +SHorizontalBox::Slot() .HAlign(HAlign_Right) .Padding(0,0,4,0) [ SAssignNew(StatusThrobber, SThrobber) .Animate(SThrobber::EAnimation::None) ] ] ] ]; // Load the asset registry module to listen for updates FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); if(AssetRegistryModule.Get().IsLoadingAssets()) { StartAsyncWork(LOCTEXT("WaitingForAssetRegistry", "Waiting for Asset Registry to finish loading")); bWaitingForAssetRegistryLoad = true; } else { SetupAssetRegistryCallbacks(); BuildBasicMaterialTree(); } } void SMaterialAnalyzer::SetupAssetRegistryCallbacks() { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); AssetRegistryModule.Get().OnAssetAdded().AddSP(this, &SMaterialAnalyzer::OnAssetAdded); } void SMaterialAnalyzer::OnAssetAdded(const FAssetData& InAssetData) { if(InAssetData.IsInstanceOf(UMaterialInterface::StaticClass())) { RecentlyAddedAssetData.Add(InAssetData); } } void SMaterialAnalyzer::OnAssetSelected(const FAssetData& AssetData) { if (AnalyzeTreeTask == nullptr) { CurrentlySelectedAsset = AssetData; UpdateViewForSelectedAsset(); } } void SMaterialAnalyzer::OnParameterFilterChanged(const FText& Filter, const ETextCommit::Type InTextAction) { if (AnalyzeTreeTask == nullptr) { ParameterFilter = Filter; bHasParameterFilterChanged = true; // Parameter filter is applied in two steps: // 1. Re-build analyzed material tree in order to highlight the filtered parameters (FBuildBasicMaterialTreeAsyncTask) // 2. Apply filter to UI elements by re-analyzing material tree (FAnalyzeMaterialTreeAsyncTask) if (CurrentlySelectedAsset.IsValid()) { RecentlyAddedAssetData.Add(CurrentlySelectedAsset); } } } void WriteAnalyzedMaterialNodeToCSVStringInternal(const FAnalyzedMaterialNodeRef& Node, FDiagnosticTableWriterCSV& CSVTable) { auto AddCSVCell = [&CSVTable](int32 NumElements, const TFunction& ElementNameCallback) -> void { FString Cell; for (int32 ElementIndex = 0; ElementIndex < NumElements; ++ElementIndex) { Cell += ElementNameCallback(ElementIndex); if (ElementIndex + 1 < NumElements) { Cell += TEXT("\n"); } } CSVTable.AddColumn(TEXT("%s"), *Cell); }; FString Output; CSVTable.AddColumn(TEXT("%s"), *Node->AssetData.AssetName.ToString()); AddCSVCell( Node->BasePropertyOverrides.Num(), [Node](int32 ElementIndex) { return FString::Printf(TEXT("%s ( %f )"), *Node->BasePropertyOverrides[ElementIndex]->ParameterName.ToString(), Node->BasePropertyOverrides[ElementIndex]->ParameterValue); } ); AddCSVCell( Node->MaterialLayerParameters.Num(), [Node](int32 ElementIndex) { return FString::Printf(TEXT("%s ( %s )"), *Node->MaterialLayerParameters[ElementIndex]->ParameterName.ToString(), *Node->MaterialLayerParameters[ElementIndex]->ParameterValue); } ); AddCSVCell( Node->StaticSwitchParameters.Num(), [Node](int32 ElementIndex) { return FString::Printf(TEXT("%s ( %s )"), *Node->StaticSwitchParameters[ElementIndex]->ParameterName.ToString(), Node->StaticSwitchParameters[ElementIndex]->ParameterValue ? TEXT("True") : TEXT("False")); } ); AddCSVCell( Node->StaticComponentMaskParameters.Num(), [Node](int32 ElementIndex) { const FStaticComponentMaskParameterNode& Parameter = *Node->StaticComponentMaskParameters[ElementIndex]; return FString::Printf( TEXT("%s ( %s%s%s%s )"), *Parameter.ParameterName.ToString(), Parameter.R ? TEXT("R") : TEXT("_"), Parameter.G ? TEXT("G") : TEXT("_"), Parameter.B ? TEXT("B") : TEXT("_"), Parameter.A ? TEXT("A") : TEXT("_") ); } ); CSVTable.CycleRow(); for (const FAnalyzedMaterialNodeRef& ChildNode : Node->GetChildNodes()) { WriteAnalyzedMaterialNodeToCSVStringInternal(ChildNode, CSVTable); } } static void WriteAnalyzedMaterialNodeToCSVString(const FAnalyzedMaterialNodeRef& Node, FDiagnosticTableWriterCSV& CSVTable) { CSVTable.AddColumn(TEXT("MATERIAL")); CSVTable.AddColumn(TEXT("BASE PROPERTY OVERRIDES")); CSVTable.AddColumn(TEXT("LAYER PARAMETERS")); CSVTable.AddColumn(TEXT("STATIC SWITCHES")); CSVTable.AddColumn(TEXT("STATIC COMPONENT MASKS")); CSVTable.CycleRow(); WriteAnalyzedMaterialNodeToCSVStringInternal(Node, CSVTable); } static bool SaveFileDialog(const FString& Title, const FString& FileTypes, FString& OutFilename, FString& InOutLastFileanme) { OutFilename.Empty(); TArray OutFilenames; if (IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get()) { const bool bFileChosen = DesktopPlatform->SaveFileDialog( FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr), Title, FPaths::GetPath(InOutLastFileanme), FPaths::GetPathLeaf(InOutLastFileanme), FileTypes, EFileDialogFlags::None, OutFilenames ); if (bFileChosen && OutFilenames.Num() > 0) { // User successfully chose a file; remember the path for the next time the dialog opens. OutFilename = OutFilenames[0]; InOutLastFileanme = OutFilenames[0]; return true; } } return false; } FReply SMaterialAnalyzer::OnExportAnalyzedMaterialToCSV() { if (!MaterialTreeRoot.IsEmpty()) { FString ExportFilename; static FString LastUsedFilename = FPaths::Combine(FPaths::GetProjectFilePath(), TEXT("Saved"), TEXT("Logs"), TEXT("MaterialProperties.csv")); if (SaveFileDialog(NSLOCTEXT("UnrealEd", "Export", "Export").ToString(), TEXT("Comma Separated Value (CSV) Files|*.csv"), ExportFilename, LastUsedFilename)) { if (TUniquePtr CSVTableFile = TUniquePtr{ IFileManager::Get().CreateFileWriter(*ExportFilename) }) { FDiagnosticTableWriterCSV CSVTable{ CSVTableFile.Get() }; WriteAnalyzedMaterialNodeToCSVString(MaterialTreeRoot[0], CSVTable); } else { FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, TEXT("Export operation failed!"), *NSLOCTEXT("UnrealEd", "Error", "!").ToString()); } } } return FReply::Handled(); } void SMaterialAnalyzer::UpdateViewForSelectedAsset() { if (CurrentlySelectedAsset.IsValid()) { const FAssetData* ParentAssetData = &CurrentlySelectedAsset; const FAssetData* NextParentAssetData = FindParentAssetData(&CurrentlySelectedAsset, AssetDataArray); // get the topmost parent while (NextParentAssetData != nullptr) { ParentAssetData = NextParentAssetData; NextParentAssetData = FindParentAssetData(ParentAssetData, AssetDataArray); } // empty the previous tree root MaterialTreeRoot.Empty(1); // Add the new tree root FAnalyzedMaterialNodeRef* NewRoot = AllMaterialTreeRoots.FindByPredicate([&](FAnalyzedMaterialNodeRef& Entry) { return Entry->ObjectPath == ParentAssetData->GetSoftObjectPath(); }); check(NewRoot != nullptr); MaterialTreeRoot.Add(*NewRoot); MaterialTree->RequestTreeRefresh(); SuggestionsBox->ClearChildren(); AnalyzeTreeTask = new FAsyncTask(*NewRoot, AssetDataArray, ParameterFilter); StartAsyncWork(FText::Format(LOCTEXT("AnalyzingMaterial", "Analyzing {0}"), FText::FromString(AnalyzeTreeTask->GetTask().CurrentMaterialNode->Path))); AnalyzeTreeTask->StartBackgroundTask(); } } void SMaterialAnalyzer::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) { SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime); if (bWaitingForAssetRegistryLoad) { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); if(!AssetRegistryModule.Get().IsLoadingAssets()) { SetupAssetRegistryCallbacks(); BuildBasicMaterialTree(); bWaitingForAssetRegistryLoad = false; } } else { if (BuildBaseMaterialTreeTask != nullptr && BuildBaseMaterialTreeTask->IsDone()) { delete BuildBaseMaterialTreeTask; BuildBaseMaterialTreeTask = nullptr; AsyncWorkFinished(FText::Format(FTextFormat(LOCTEXT("DoneWithMaterialInterfaces", "Done with {0} MaterialInterfaces")), GetTotalNumberOfMaterialNodes())); // If the parameter filter has changed, we also have to run analysis async task again, // which applies the actual parameter filter to UI elements in the tree view pane. if (bHasParameterFilterChanged) { UpdateViewForSelectedAsset(); bHasParameterFilterChanged = false; } else { MaterialTree->RequestTreeRefresh(); } } if (BuildBaseMaterialTreeTask == nullptr && RecentlyAddedAssetData.Num() > 0) { // Need to make this append to the previously generated list instead of erase all of the old info // Current problem is that if we only have a portion of the asset registry it will create duplicate // nodes since it won't find all parents in the tree. Need to modify the async task to not create // nodes that don't have parents until we can find their parent. AssetDataArray.Append(RecentlyAddedAssetData); RecentlyAddedAssetData.Empty(); AllMaterialTreeRoots.Empty(AllMaterialTreeRoots.Num()); BuildBaseMaterialTreeTask = new FAsyncTask(AllMaterialTreeRoots, AssetDataArray); BuildBaseMaterialTreeTask->StartBackgroundTask(); StartAsyncWork(LOCTEXT("BuildingBasicTree", "Building Basic MaterialTree")); } if (AnalyzeTreeTask != nullptr && AnalyzeTreeTask->IsDone()) { if (AnalyzeTreeTask->GetTask().LoadNextMaterial()) { StartAsyncWork(FText::Format(LOCTEXT("AnalyzingMaterial", "Analyzing {0}"), FText::FromString(AnalyzeTreeTask->GetTask().CurrentMaterialNode->Path))); AnalyzeTreeTask->StartBackgroundTask(); } else { MaterialTree->RequestListRefresh(); // Kick off a check for identical permutations // @todo make this a series of tests that users can choose to run AnalyzeForIdenticalPermutationsTask = new FAsyncTask(AnalyzeTreeTask->GetTask().MaterialTreeRoot); AnalyzeForIdenticalPermutationsTask->StartBackgroundTask(); delete AnalyzeTreeTask; AnalyzeTreeTask = nullptr; StartAsyncWork(LOCTEXT("AnalyzingTreeForIdenticalPermutations", "Analyzing material tree for identical permutations")); } } if (AnalyzeForIdenticalPermutationsTask != nullptr && AnalyzeForIdenticalPermutationsTask->IsDone()) { MaterialTree->RequestListRefresh(); AsyncWorkFinished(LOCTEXT("Done", "Done!")); TMultiMap Suggestions = AnalyzeForIdenticalPermutationsTask->GetTask().GetSuggestions(); Suggestions.KeySort([](int32 A, int32 B) { return A > B; // sort to show most improvement possibility first }); int32 BackgroundColorCounter = 0; SuggestionDataArray.Empty(); for (auto It = Suggestions.CreateConstIterator(); It; ++It) { TSharedPtr SuggestionHeader = MakeShareable(new FPermutationSuggestionView()); SuggestionHeader->Header = It.Value().Header; for (FString Material : It.Value().Materials) { TSharedPtr SuggestionChild = MakeShareable(new FPermutationSuggestionView()); SuggestionChild->Header = FText::FromString(Material); SuggestionHeader->Children.Add(SuggestionChild); } SuggestionDataArray.Add(SuggestionHeader); } SuggestionsBox->AddSlot() [ SAssignNew(SuggestionsTree, STreeView>) .TreeItemsSource(&SuggestionDataArray) .OnGenerateRow(this, &SMaterialAnalyzer::OnGenerateSuggestionRow) .OnGetChildren(this, &SMaterialAnalyzer::OnGetSuggestionChildren) ]; delete AnalyzeForIdenticalPermutationsTask; AnalyzeForIdenticalPermutationsTask = nullptr; } } } TSharedRef< ITableRow > SMaterialAnalyzer::OnGenerateSuggestionRow(TSharedPtr Item, const TSharedRef< STableViewBase >& OwnerTable) { if (Item->Children.Num() > 0) { return SNew(STableRow>, OwnerTable) [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() .VAlign(VAlign_Bottom) [ SNew(SEditableText) .IsReadOnly(true) .Text(Item->Header) ] + SVerticalBox::Slot() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() [ SNew(STextBlock) .Visibility(this, &SMaterialAnalyzer::ShouldShowAdvancedRecommendations, Item) .Text(LOCTEXT("PermutationRecommendation", "It is recommended that you reparent them in a way so only dynamic parameters differ.")) ] + SHorizontalBox::Slot() .FillWidth(1.0f) [ SNullWidget::NullWidget ] +SHorizontalBox::Slot() .AutoWidth() [ SNew(SButton) .ButtonStyle(FAppStyle::Get(), "SimpleSharpButton") .Visibility(this, &SMaterialAnalyzer::ShouldShowAdvancedRecommendations, Item) .OnClicked(this, &SMaterialAnalyzer::CreateLocalSuggestionCollectionClicked, Item) .ContentPadding(FMargin(2.0f)) .Content() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f) [ SNew(SImage) .Image(FAppStyle::Get().GetBrush("Icons.Plus")) .ColorAndOpacity(FSlateColor(EStyleColor::Black)) ] +SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f) [ SNew(STextBlock) .Text(LOCTEXT("CreateLocalCollection", "Create Local Collection")) .ColorAndOpacity(FSlateColor(EStyleColor::Black)) ] ] ] ] ]; } return SNew(STableRow>, OwnerTable) [ SNew(SEditableText) .IsReadOnly(true) .Text(Item->Header) ]; } EVisibility SMaterialAnalyzer::ShouldShowAdvancedRecommendations(TSharedPtr Item) const { return SuggestionsTree->IsItemExpanded(Item) ? EVisibility::Visible : EVisibility::Collapsed; } void SMaterialAnalyzer::OnGetSuggestionChildren(TSharedPtr InParent, TArray< TSharedPtr >& OutChildren) { OutChildren = InParent->Children; } FReply SMaterialAnalyzer::CreateLocalSuggestionCollectionClicked(TSharedPtr InSuggestion) { TArray> CollectionContainers; FCollectionManagerModule::GetModule().Get().GetVisibleCollectionContainers(CollectionContainers); if (CollectionContainers.Num() == 1) { CreateLocalSuggestionCollection(*CollectionContainers[0], *InSuggestion); } else { // Create a menu to select the collection container. FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, nullptr); MenuBuilder.BeginSection(NAME_None, LOCTEXT("MaterialAnalyzerCollectionContainersMenuHeading", "Collection Containers")); for (const TSharedPtr& CollectionContainer : CollectionContainers) { MenuBuilder.AddMenuEntry( CollectionContainer->GetCollectionSource()->GetTitle(), TAttribute(), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([this, CollectionContainer, InSuggestion]() { CreateLocalSuggestionCollection(*CollectionContainer, *InSuggestion); }), FCanExecuteAction::CreateLambda([CollectionContainer]() { return !CollectionContainer->IsReadOnly(ECollectionShareType::CST_Local); }))); } MenuBuilder.EndSection(); FWidgetPath WidgetPath; if (FSlateApplication::Get().GeneratePathToWidgetUnchecked(AsShared(), WidgetPath)) { FSlateApplication::Get().PushMenu( AsShared(), WidgetPath, MenuBuilder.MakeWidget(), FSlateApplication::Get().GetCursorPos(), FPopupTransitionEffect(FPopupTransitionEffect::TopMenu) ); } } return FReply::Handled(); } void SMaterialAnalyzer::CreateLocalSuggestionCollection(ICollectionContainer& InCollectionContainer, const FPermutationSuggestionView& InSuggestion) { TArray AllSelectedPackageNames; ECollectionShareType::Type ShareType = ECollectionShareType::CST_Local; for (TSharedPtr Child : InSuggestion.Children) { AllSelectedPackageNames.Add(Child->Header.ToString()); } if (AllSelectedPackageNames.Num() > 0) { FString FirstAssetString = CurrentlySelectedAsset.AssetName.ToString() + TEXT("_") + FString::FromInt(InSuggestion.Children.Num()); FName FirstAssetName = FName(*FirstAssetString); InCollectionContainer.CreateUniqueCollectionName(FirstAssetName, ShareType, FirstAssetName); FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); TArray PackageNamesToAddToCollection; TArray PackageNameSet; for (FString PackageToAdd : AllSelectedPackageNames) { PackageNameSet.Add(FName(*FPaths::GetBaseFilename(*PackageToAdd, false))); } const FText MaterialAnalyzerText = LOCTEXT("MaterialAnalyzerPrefix", "MaterialAnalyzer"); FName ParentName = FName(*MaterialAnalyzerText.ToString()); if (!InCollectionContainer.CollectionExists(ParentName, ShareType)) { InCollectionContainer.CreateCollection(ParentName, ShareType, ECollectionStorageMode::Static); } const bool bCollectionSucceeded = IAssetManagerEditorModule::Get().WriteCollection(InCollectionContainer, FirstAssetName, ShareType, PackageNameSet, true); if (bCollectionSucceeded) { InCollectionContainer.ReparentCollection(FirstAssetName, ShareType, ParentName, ShareType); } } } void SMaterialAnalyzer::StartAsyncWork(const FText& WorkText) { bIsAsyncWorkInProgress = true; if(StatusBox.IsValid()) { StatusBox->SetText(WorkText); } if(StatusThrobber.IsValid()) { StatusThrobber->SetAnimate(SThrobber::Horizontal); StatusThrobber->SetVisibility(EVisibility::SelfHitTestInvisible); } } void SMaterialAnalyzer::AsyncWorkFinished(const FText& CompleteText) { if (StatusBox.IsValid()) { StatusBox->SetText(CompleteText); } if (StatusThrobber.IsValid()) { StatusThrobber->SetAnimate(SThrobber::None); StatusThrobber->SetVisibility(EVisibility::Collapsed); } bIsAsyncWorkInProgress = false; } int32 SMaterialAnalyzer::GetTotalNumberOfMaterialNodes() { int32 NumMaterialNodes = AllMaterialTreeRoots.Num(); for(int i = 0; i < AllMaterialTreeRoots.Num(); ++i) { NumMaterialNodes += AllMaterialTreeRoots[i]->TotalNumberOfChildren(); } return NumMaterialNodes; } FString SMaterialAnalyzer::GetCurrentAssetPath() const { return CurrentlySelectedAsset.IsValid() ? CurrentlySelectedAsset.GetObjectPathString() : FString(""); } void SMaterialAnalyzer::BuildBasicMaterialTree() { static const FName AssetRegistryName(TEXT("AssetRegistry")); FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(AssetRegistryName); IAssetRegistry& AssetRegistry = AssetRegistryModule.Get(); AssetRegistry.GetAssetsByClass(UMaterial::StaticClass()->GetClassPathName(), AssetDataArray, true); AssetRegistry.GetAssetsByClass(UMaterialInstance::StaticClass()->GetClassPathName(), AssetDataArray, true); if (BuildBaseMaterialTreeTask == nullptr && AssetDataArray.Num() > 0) { AllMaterialTreeRoots.Empty(AllMaterialTreeRoots.Num()); BuildBaseMaterialTreeTask = new FAsyncTask(AllMaterialTreeRoots, AssetDataArray); BuildBaseMaterialTreeTask->StartBackgroundTask(); StartAsyncWork(LOCTEXT("BuildingBasicTree", "Building Basic MaterialTree")); if (StatusThrobber.IsValid()) { StatusThrobber->SetAnimate(SThrobber::EAnimation::Horizontal); } } } TSharedRef SMaterialAnalyzer::HandleReflectorTreeGenerateRow(FAnalyzedMaterialNodeRef InMaterialNode, const TSharedRef& OwnerTable) { TSharedPtr NewWidget = SNew(SAnalyzedMaterialNodeWidgetItem, OwnerTable) .MaterialInfoToVisualize(InMaterialNode); // if we're the base level we're going to expand right away if(!InMaterialNode->Parent.IsValid()) { MaterialTree->SetItemExpansion(InMaterialNode, true); } return NewWidget.ToSharedRef(); } void SMaterialAnalyzer::HandleReflectorTreeGetChildren(FAnalyzedMaterialNodeRef InMaterialNode, TArray& OutChildren) { if (ParameterFilter.IsEmpty()) { OutChildren = InMaterialNode->GetChildNodes(); } else { OutChildren.Empty(InMaterialNode->GetChildNodes().Num()); for (const FAnalyzedMaterialNodeRef& ChildNode : InMaterialNode->GetChildNodes()) { if (ChildNode->HasAnyFilteredParameters(ParameterFilter.ToString())) { OutChildren.Add(ChildNode); } } } } void SMaterialAnalyzer::HandleReflectorTreeRecursiveExpansion(FAnalyzedMaterialNodeRef InTreeNode, bool bIsItemExpanded) { TArray< FAnalyzedMaterialNodeRef > Children = InTreeNode->GetChildNodes(); if (Children.Num()) { MaterialTree->SetItemExpansion(InTreeNode, bIsItemExpanded); const bool bShouldSaveState = true; for (int32 ChildIndex = 0; ChildIndex < Children.Num(); ++ChildIndex) { FAnalyzedMaterialNodeRef Child = Children[ChildIndex]; MaterialTree->SetItemExpansion(Child, bIsItemExpanded); } } } FAnalyzedMaterialNodePtr FBuildBasicMaterialTreeAsyncTask::FindOrMakeBranchNode(FAnalyzedMaterialNodePtr ParentNode, const FAssetData* ChildData) { check(ChildData != nullptr); FAnalyzedMaterialNodeRef* OutNode = nullptr; FSoftObjectPath ChildPath = ChildData->GetSoftObjectPath(); TArray& NodesToSearch = ParentNode.IsValid() ? ParentNode->GetChildNodes() : MaterialTreeRoot; OutNode = NodesToSearch.FindByPredicate([&](FAnalyzedMaterialNodeRef& Entry) { return Entry->ObjectPath == ChildPath; }); if (OutNode == nullptr) { FAnalyzedMaterialNode ChildNode; ChildNode.Path = ChildData->AssetName.ToString(); ChildNode.ObjectPath = ChildData->GetSoftObjectPath(); ChildNode.Parent = ParentNode; ChildNode.AssetData = *ChildData; NodesToSearch.Add(FAnalyzedMaterialNodeRef(new FAnalyzedMaterialNode(ChildNode))); OutNode = &NodesToSearch[NodesToSearch.Num() - 1]; } return FAnalyzedMaterialNodePtr (*OutNode); } void FBuildBasicMaterialTreeAsyncTask::DoWork() { for(int i = 0; i < AssetDataToAnalyze.Num(); ++i) { const FAssetData& AssetData = AssetDataToAnalyze[i]; TArray FullBranch; const FAssetData* CurrentBranchNode = &AssetData; while(CurrentBranchNode) { FullBranch.Add(CurrentBranchNode); CurrentBranchNode = FindParentAssetData(CurrentBranchNode, AssetDataToAnalyze); } FAnalyzedMaterialNodePtr ParentNode = nullptr; for(int Depth = FullBranch.Num() - 1; Depth >= 0; --Depth) { ParentNode = FindOrMakeBranchNode(ParentNode, FullBranch[Depth]); } } } bool FAnalyzeMaterialTreeAsyncTask::LoadNextMaterial() { if (CurrentMaterialQueueIndex < MaterialQueue.Num()) { CurrentMaterialNode = MaterialQueue[CurrentMaterialQueueIndex]; check(CurrentMaterialNode->ObjectPath.IsValid()); CurrentMaterialInterface = FindObject(NULL, *CurrentMaterialNode->ObjectPath.ToString()); if (CurrentMaterialInterface == nullptr) { CurrentMaterialInterface = LoadObject(NULL, *CurrentMaterialNode->ObjectPath.ToString()); check(CurrentMaterialInterface); } return true; } return false; } void FAnalyzeMaterialTreeAsyncTask::DoWork() { MaterialQueue.Append(CurrentMaterialNode->GetChildNodes()); TArray MaterialLayersParameterInfo; check(CurrentMaterialInterface != nullptr); TArray ParameterInfo; TArray Guids; UMaterial* CurrentMaterial = Cast(CurrentMaterialInterface); UMaterialInstance* CurrentMaterialInstance = Cast(CurrentMaterialInterface); bool bCanBeOverriden = CurrentMaterial != nullptr; if(CurrentMaterial != nullptr) { CurrentMaterial->GetAllStaticSwitchParameterInfo(StaticSwitchParameterInfo, StaticSwitchGuids); CurrentMaterial->GetAllStaticComponentMaskParameterInfo(StaticMaskParameterInfo, StaticMaskGuids); } CurrentMaterialNode->BasePropertyOverrides.Empty(BasePropertyOverrideNames.Num()); const FString ParameterFilterString = this->ParameterFilter.ToString(); auto IsIncludedInParameterFilter = [&ParameterFilterString](const FName& Name) -> bool { return ParameterFilterString.IsEmpty() || Name.ToString().Contains(ParameterFilterString); }; for (const TPair& BasePropertyOverrideName : BasePropertyOverrideNames) { float TempValue = 0.0f; bool bIsOverridden = false; if (!IsIncludedInParameterFilter(BasePropertyOverrideName.Value)) { continue; } if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_OpacityMaskClipValue"))) { TempValue = CurrentMaterialInterface->GetOpacityMaskClipValue(); if (CurrentMaterialInstance) { bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_OpacityMaskClipValue; } } else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_BlendMode"))) { TempValue = (float)CurrentMaterialInterface->GetBlendMode(); if (CurrentMaterialInstance) { bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_BlendMode; } } else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_ShadingModel"))) { if (CurrentMaterialInterface->IsShadingModelFromMaterialExpression()) { TempValue = (float)MSM_FromMaterialExpression; } else { ensure(CurrentMaterialInterface->GetShadingModels().CountShadingModels() == 1); TempValue = (float)CurrentMaterialInterface->GetShadingModels().GetFirstShadingModel(); } if (CurrentMaterialInstance) { bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_ShadingModel; } } else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_DitheredLODTransition"))) { TempValue = (float)CurrentMaterialInterface->IsDitheredLODTransition(); if (CurrentMaterialInstance) { bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_DitheredLODTransition; } } else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_CastDynamicShadowAsMasked"))) { TempValue = CurrentMaterialInterface->GetCastShadowAsMasked(); if (CurrentMaterialInstance) { bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_CastDynamicShadowAsMasked; } } else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_TwoSided"))) { TempValue = CurrentMaterialInterface->IsTwoSided(); if (CurrentMaterialInstance) { bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_TwoSided; } } else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_bIsThinSurface"))) { TempValue = CurrentMaterialInterface->IsThinSurface(); if (CurrentMaterialInstance) { bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_bIsThinSurface; } } else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_OutputTranslucentVelocity"))) { TempValue = CurrentMaterialInterface->IsTranslucencyWritingVelocity(); if (CurrentMaterialInstance) { bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_OutputTranslucentVelocity; } } else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_bHasPixelAnimation"))) { TempValue = CurrentMaterialInterface->HasPixelAnimation(); if (CurrentMaterialInstance) { bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_bHasPixelAnimation; } } else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_bEnableTessellation"))) { TempValue = CurrentMaterialInterface->IsTessellationEnabled(); if (CurrentMaterialInstance) { bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_bEnableTessellation; } } else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_DisplacementScaling"))) { TempValue = CurrentMaterialInterface->GetDisplacementScaling().Magnitude; if (CurrentMaterialInstance) { bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_DisplacementScaling; } } else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_bEnableDisplacementFade"))) { TempValue = CurrentMaterialInterface->IsDisplacementFadeEnabled(); if (CurrentMaterialInstance) { bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_bEnableDisplacementFade; } } else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_DisplacementFadeRange"))) { TempValue = CurrentMaterialInterface->GetDisplacementFadeRange().EndSizePixels; if (CurrentMaterialInstance) { bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_DisplacementFadeRange; } } else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_MaxWorldPositionOffsetDisplacement"))) { TempValue = CurrentMaterialInterface->GetMaxWorldPositionOffsetDisplacement(); if (CurrentMaterialInstance) { bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_MaxWorldPositionOffsetDisplacement; } } else if (BasePropertyOverrideName.Key.IsEqual(TEXT("bOverride_CompatibleWithLumenCardSharing"))) { TempValue = (float)CurrentMaterialInterface->IsCompatibleWithLumenCardSharing(); if (CurrentMaterialInstance) { bIsOverridden = CurrentMaterialInstance->BasePropertyOverrides.bOverride_CompatibleWithLumenCardSharing; } } // Check the parent for this variable FAnalyzedMaterialNodePtr Parent = CurrentMaterialNode->Parent; if (!bIsOverridden && Parent.IsValid()) { // We shouldn't be able to get in here for the base Material FBasePropertyOverrideNodeRef ParentParameter = Parent->FindBasePropertyOverride(BasePropertyOverrideName.Value); CurrentMaterialNode->BasePropertyOverrides.Add(FBasePropertyOverrideNodeRef( new FBasePropertyOverrideNode(ParentParameter->ParameterName, ParentParameter->ParameterID, ParentParameter->ParameterValue, false, this->ParameterFilter))); } else { CurrentMaterialNode->BasePropertyOverrides.Add(FBasePropertyOverrideNodeRef( new FBasePropertyOverrideNode(BasePropertyOverrideName.Value, BasePropertyOverrideName.Key, TempValue, bIsOverridden, this->ParameterFilter))); } } FMaterialLayersFunctions MaterialLayers; if (CurrentMaterialInterface->GetMaterialLayers(MaterialLayers)) { bool bIsOverridden = false; if (CurrentMaterialInstance) { FMaterialLayersFunctions ParentMaterialLayers; const bool bParentHasLayers = CurrentMaterialInstance->Parent->GetMaterialLayers(ParentMaterialLayers); bIsOverridden = !bParentHasLayers || MaterialLayers != ParentMaterialLayers; } CurrentMaterialNode->MaterialLayerParameters.Add(FStaticMaterialLayerParameterNodeRef( new FStaticMaterialLayerParameterNode(FName(), MaterialLayers.GetStaticPermutationString(), bIsOverridden, this->ParameterFilter))); } CurrentMaterialNode->StaticSwitchParameters.Empty(StaticSwitchParameterInfo.Num()); for (int ParameterIndex = 0; ParameterIndex < StaticSwitchParameterInfo.Num(); ++ParameterIndex) { FMaterialParameterMetadata Meta; bool bIsOverridden = false; if (CurrentMaterialInstance) { bIsOverridden = CurrentMaterialInstance->GetParameterOverrideValue(EMaterialParameterType::StaticSwitch, StaticSwitchParameterInfo[ParameterIndex], Meta); } else if(CurrentMaterial) { bIsOverridden = CurrentMaterial->GetParameterValue(EMaterialParameterType::StaticSwitch, StaticSwitchParameterInfo[ParameterIndex], Meta); } if (!IsIncludedInParameterFilter(StaticSwitchParameterInfo[ParameterIndex].Name)) { continue; } if (!bIsOverridden) { // Check the parent for this variable FAnalyzedMaterialNodePtr Parent = CurrentMaterialNode->Parent; // We shouldn't be able to get in here for the base Material check(Parent.IsValid()); FStaticSwitchParameterNodeRef ParentParameter = Parent->FindStaticSwitchParameter(StaticSwitchParameterInfo[ParameterIndex].Name); CurrentMaterialNode->StaticSwitchParameters.Add(FStaticSwitchParameterNodeRef( new FStaticSwitchParameterNode(ParentParameter->ParameterName, ParentParameter->ParameterValue, false, this->ParameterFilter))); } else { CurrentMaterialNode->StaticSwitchParameters.Add(FStaticSwitchParameterNodeRef( new FStaticSwitchParameterNode(StaticSwitchParameterInfo[ParameterIndex].Name, Meta.Value.AsStaticSwitch(), true, this->ParameterFilter))); } } CurrentMaterialNode->StaticComponentMaskParameters.Empty(StaticMaskParameterInfo.Num()); for (int ParameterIndex = 0; ParameterIndex < StaticMaskParameterInfo.Num(); ++ParameterIndex) { if (!IsIncludedInParameterFilter(StaticMaskParameterInfo[ParameterIndex].Name)) { continue; } FMaterialParameterMetadata Meta; bool bIsOverridden = false; if (CurrentMaterialInstance) { bIsOverridden = CurrentMaterialInstance->GetParameterOverrideValue(EMaterialParameterType::StaticComponentMask, StaticMaskParameterInfo[ParameterIndex], Meta); } else if (CurrentMaterial) { bIsOverridden = CurrentMaterial->GetParameterValue(EMaterialParameterType::StaticComponentMask, StaticMaskParameterInfo[ParameterIndex], Meta); } if(!bIsOverridden) { // Check the parent for this variable FAnalyzedMaterialNodePtr Parent = CurrentMaterialNode->Parent; // We shouldn't be able to get in here for the base Material check(Parent.IsValid()); FStaticComponentMaskParameterNodeRef ParentParameter = Parent->FindStaticComponentMaskParameter(StaticMaskParameterInfo[ParameterIndex].Name); CurrentMaterialNode->StaticComponentMaskParameters.Add(FStaticComponentMaskParameterNodeRef( new FStaticComponentMaskParameterNode(ParentParameter->ParameterName, ParentParameter->R, ParentParameter->G, ParentParameter->B, ParentParameter->A, false, this->ParameterFilter))); } else { CurrentMaterialNode->StaticComponentMaskParameters.Add(FStaticComponentMaskParameterNodeRef( new FStaticComponentMaskParameterNode(StaticMaskParameterInfo[ParameterIndex].Name, Meta.Value.Bool[0], Meta.Value.Bool[1], Meta.Value.Bool[2], Meta.Value.Bool[3], true, this->ParameterFilter))); } } CurrentMaterialQueueIndex++; } bool FAnalyzeForIdenticalPermutationsAsyncTask::CreateMaterialPermutationHashForNode(const FAnalyzedMaterialNodeRef& MaterialNode, uint32& OutHash) { TArray ByteArray; bool bAnyOverrides = false; for (int ParameterIndex = 0; ParameterIndex < MaterialNode->BasePropertyOverrides.Num(); ++ParameterIndex) { FString FloatToHash = FString::SanitizeFloat(MaterialNode->BasePropertyOverrides[ParameterIndex]->ParameterValue); ByteArray.Append(TCHAR_TO_ANSI(*FloatToHash), FloatToHash.Len()); bAnyOverrides = bAnyOverrides || MaterialNode->BasePropertyOverrides[ParameterIndex]->bOverride; } for (int ParameterIndex = 0; ParameterIndex < MaterialNode->MaterialLayerParameters.Num(); ++ParameterIndex) { ByteArray.Append(TCHAR_TO_ANSI(*MaterialNode->MaterialLayerParameters[ParameterIndex]->ParameterValue), MaterialNode->MaterialLayerParameters[ParameterIndex]->ParameterValue.Len()); bAnyOverrides = bAnyOverrides || MaterialNode->MaterialLayerParameters[ParameterIndex]->bOverride; } for(int ParameterIndex = 0; ParameterIndex < MaterialNode->StaticSwitchParameters.Num(); ++ParameterIndex) { ByteArray.Add(MaterialNode->StaticSwitchParameters[ParameterIndex]->ParameterValue ? 1 : 0); bAnyOverrides = bAnyOverrides || MaterialNode->StaticSwitchParameters[ParameterIndex]->bOverride; } for(int ParameterIndex = 0; ParameterIndex < MaterialNode->StaticComponentMaskParameters.Num(); ++ParameterIndex) { FStaticComponentMaskParameterNodeRef NodeRef = MaterialNode->StaticComponentMaskParameters[ParameterIndex]; ByteArray.Add(NodeRef->R ? 1 : 0); ByteArray.Add(NodeRef->G ? 1 : 0); ByteArray.Add(NodeRef->B ? 1 : 0); ByteArray.Add(NodeRef->A ? 1 : 0); bAnyOverrides = bAnyOverrides || NodeRef->bOverride; } OutHash = CityHash32(ByteArray.GetData(), ByteArray.Num()); return bAnyOverrides; } void FAnalyzeForIdenticalPermutationsAsyncTask::DoWork() { for(int i = 0; i < MaterialQueue.Num(); ++i) { FAnalyzedMaterialNodeRef CurrentMaterialNode = MaterialQueue[i]; MaterialQueue.Append(CurrentMaterialNode->GetChildNodes()); uint32 MaterialPermutationHash = 0; if(CreateMaterialPermutationHashForNode(CurrentMaterialNode, MaterialPermutationHash)) { MaterialPermutationHashToMaterialObjectPath.FindOrAdd(MaterialPermutationHash).Add(CurrentMaterialNode->ObjectPath); } } GatherSuggestions(); } void FAnalyzeForIdenticalPermutationsAsyncTask::GatherSuggestions() { Suggestions.Empty(); for (TPair>& IdenticalPermutations : MaterialPermutationHashToMaterialObjectPath) { if (IdenticalPermutations.Value.Num() > 1) { TArray AllNames; AssetCount = IdenticalPermutations.Value.Num(); for (int i = 0; i < IdenticalPermutations.Value.Num(); ++i) { FString PermutationString = IdenticalPermutations.Value[i].ToString(); AllNames.Add(PermutationString); } FPermutationSuggestionData NewData = FPermutationSuggestionData(FText::Format(LOCTEXT("IdenticalStaticPermutationSuggestions", "The following {0} materials all have identical static parameter permutations."), FText::AsNumber(AssetCount)), AllNames); Suggestions.Add ( AssetCount, NewData ); } } } #undef LOCTEXT_NAMESPACE