// Copyright Epic Games, Inc. All Rights Reserved. #include "StaticMeshEditorTools.h" #include "Framework/Commands/UIAction.h" #include "Textures/SlateIcon.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "EngineDefines.h" #include "Styling/AppStyle.h" #include "PropertyHandle.h" #include "IDetailGroup.h" #include "IDetailChildrenBuilder.h" #include "Misc/MessageDialog.h" #include "Misc/FeedbackContext.h" #include "Modules/ModuleManager.h" #include "SlateOptMacros.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SCheckBox.h" #include "MaterialDomain.h" #include "Materials/Material.h" #include "DetailLayoutBuilder.h" #include "DetailCategoryBuilder.h" #include "RawMesh.h" #include "MeshUtilities.h" #include "StaticMeshResources.h" #include "StaticMeshEditor.h" #include "PropertyCustomizationHelpers.h" #include "MaterialList.h" #include "PhysicsEngine/BodySetup.h" #include "FbxMeshUtils.h" #include "Widgets/Input/SVectorInputBox.h" #include "Widgets/Input/SNumericEntryBox.h" #include "SPerQualityLevelPropertiesWidget.h" #include "PlatformInfo.h" #include "ContentStreaming.h" #include "EditorDirectories.h" #include "EditorFramework/AssetImportData.h" #include "Engine/SkeletalMesh.h" #include "EngineAnalytics.h" #include "Factories/FbxStaticMeshImportData.h" #include "HAL/PlatformApplicationMisc.h" #include "IMeshReductionManagerModule.h" #include "Interfaces/ITargetPlatform.h" #include "Interfaces/ITargetPlatformManagerModule.h" #include "JsonObjectConverter.h" #include "Interfaces/IAnalyticsProvider.h" #include "ScopedTransaction.h" #include "ThumbnailRendering/ThumbnailManager.h" #include "UObject/UObjectGlobals.h" #include "Widgets/Input/SFilePathPicker.h" #include "Widgets/Input/STextComboBox.h" #include "PerPlatformPropertyCustomization.h" #include "Misc/ScopedSlowTask.h" #include "MeshCardRepresentation.h" #include "NaniteDefinitions.h" #include "NaniteLayout.h" const uint32 MaxHullCount = 64; const uint32 MinHullCount = 1; const uint32 DefaultHullCount = 4; const uint32 HullCountDelta = 1; const uint32 MaxHullPrecision = 1000000; const uint32 MinHullPrecision = 10000; const uint32 DefaultHullPrecision = 100000; const uint32 HullPrecisionDelta = 10000; const int32 MaxVertsPerHullCount = 32; const int32 MinVertsPerHullCount = 6; const int32 DefaultVertsPerHull = 16; #define LOCTEXT_NAMESPACE "StaticMeshEditor" DEFINE_LOG_CATEGORY_STATIC(LogStaticMeshEditorTools,Log,All); bool GIsNaniteStaticMeshSettingsInitiallyCollapsed = 0; static FAutoConsoleVariableRef CVarIsNaniteStaticMeshSettingsInitiallyCollapsed( TEXT("r.Nanite.IsNaniteStaticMeshSettingsInitiallyCollapsed"), GIsNaniteStaticMeshSettingsInitiallyCollapsed, TEXT("If the Nanite Settings are initially collapsed in the details panel in the Static Mesh Editor Tool."), ECVF_ReadOnly ); static bool GEnableStaticMeshMaterialSlotOverlayMaterialUI = false; static FAutoConsoleVariableRef CCvarEnableStaticMeshMaterialSlotOverlayMaterialUI( TEXT("StaticMesh.FeatureFlags.UI.ShowMaterialSlotOverlayMaterial"), GEnableStaticMeshMaterialSlotOverlayMaterialUI, TEXT("Whether Static mesh editor show material overlay per material slot UI."), ECVF_Default); /** * Window for Nanite settings. */ typedef Nanite::FSettingsLayout FNaniteStaticMeshLayoutImpl; class FNaniteStaticMeshLayout : public TSharedFromThis { public: FNaniteStaticMeshLayout(FStaticMeshEditor& InStaticMeshEditor) : StaticMeshEditor(InStaticMeshEditor) { LayoutImpl = MakeShareable(new FNaniteStaticMeshLayoutImpl()); LayoutImpl->OnGetMesh = TDelegate::CreateLambda([this] { return StaticMeshEditor.GetStaticMesh(); }); LayoutImpl->OnRefreshTool = TDelegate::CreateLambda([this] { StaticMeshEditor.RefreshTool(); }); const UStaticMesh* StaticMesh = LayoutImpl->GetMesh(); check(StaticMesh); LayoutImpl->UpdateSettings(StaticMesh->NaniteSettings); } ~FNaniteStaticMeshLayout() { } void AddToDetailsPanel(IDetailLayoutBuilder& DetailBuilder) { const bool bInitiallyCollapsed = GIsNaniteStaticMeshSettingsInitiallyCollapsed != 0; UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); TWeakObjectPtr WeakStaticMesh = StaticMesh; const int32 SortOrder = 10; LayoutImpl->AddToDetailsPanel(WeakStaticMesh, DetailBuilder, SortOrder, bInitiallyCollapsed); } inline bool IsApplyNeeded() const { return LayoutImpl->IsApplyNeeded(); } inline void ApplyChanges() { return LayoutImpl->ApplyChanges(); } private: // The Static Mesh Editor this tool is associated with. FStaticMeshEditor& StaticMeshEditor; TSharedPtr LayoutImpl; }; /* * Custom data key */ enum SM_CustomDataKey { CustomDataKey_LODVisibilityState = 0, //This is the key to know if a LOD is shown in custom mode. Do CustomDataKey_LODVisibilityState + LodIndex for a specific LOD CustomDataKey_LODEditMode = 100 //This is the key to know the state of the custom lod edit mode. }; FStaticMeshDetails::FStaticMeshDetails( class FStaticMeshEditor& InStaticMeshEditor ) : StaticMeshEditor( InStaticMeshEditor ) {} FStaticMeshDetails::~FStaticMeshDetails() { } void FStaticMeshDetails::CustomizeDetails( class IDetailLayoutBuilder& DetailBuilder ) { IDetailCategoryBuilder& LODSettingsCategory = DetailBuilder.EditCategory( "LodSettings", LOCTEXT("LodSettingsCategory", "LOD Settings") ); IDetailCategoryBuilder& StaticMeshCategory = DetailBuilder.EditCategory( "StaticMesh", LOCTEXT("StaticMeshGeneralSettings", "General Settings") ); IDetailCategoryBuilder& CollisionCategory = DetailBuilder.EditCategory( "Collision", LOCTEXT("CollisionCategory", "Collision") ); IDetailCategoryBuilder& ImportSettingsCategory = DetailBuilder.EditCategory("ImportSettings"); TSharedRef LightMapCoordinateIndexProperty = DetailBuilder.GetProperty(UStaticMesh::GetLightMapCoordinateIndexName()); TSharedRef LightMapResolutionProperty = DetailBuilder.GetProperty(UStaticMesh::GetLightMapResolutionName()); LightMapCoordinateIndexProperty->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FStaticMeshDetails::OnLightmapSettingsChanged)); LightMapResolutionProperty->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FStaticMeshDetails::OnLightmapSettingsChanged)); TSharedRef StaticMaterials = DetailBuilder.GetProperty(UStaticMesh::GetStaticMaterialsName()); StaticMaterials->MarkHiddenByCustomization(); TSharedRef ImportSettings = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UStaticMesh, AssetImportData)); if (!StaticMeshEditor.GetStaticMesh() || !StaticMeshEditor.GetStaticMesh()->AssetImportData || !StaticMeshEditor.GetStaticMesh()->AssetImportData->IsA()) { ImportSettings->MarkResetToDefaultCustomized(); IDetailPropertyRow& Row = ImportSettingsCategory.AddProperty(ImportSettings); Row.CustomWidget(true) .NameContent() [ ImportSettings->CreatePropertyNameWidget() ]; } else { // If the AssetImportData is an instance of UFbxStaticMeshImportData we create a custom UI. // Since DetailCustomization UI is not supported on instanced properties and because IDetailLayoutBuilder does not work well inside instanced objects scopes, // we need to manually recreate the whole FbxStaticMeshImportData UI in order to customize it. ImportSettings->MarkHiddenByCustomization(); VertexColorImportOptionHandle = ImportSettings->GetChildHandle(GET_MEMBER_NAME_CHECKED(UFbxStaticMeshImportData, VertexColorImportOption)); VertexColorImportOverrideHandle = ImportSettings->GetChildHandle(GET_MEMBER_NAME_CHECKED(UFbxStaticMeshImportData, VertexOverrideColor)); TMap ExistingGroup; PropertyCustomizationHelpers::MakeInstancedPropertyCustomUI(ExistingGroup, ImportSettingsCategory, ImportSettings, FOnInstancedPropertyIteration::CreateSP(this, &FStaticMeshDetails::OnInstancedFbxStaticMeshImportDataPropertyIteration)); } DetailBuilder.EditCategory( "Navigation", FText::GetEmpty(), ECategoryPriority::Uncommon ); LevelOfDetailSettings = MakeShareable( new FLevelOfDetailSettingsLayout( StaticMeshEditor ) ); LevelOfDetailSettings->AddToDetailsPanel( DetailBuilder ); // Hide the existing NaniteSettings property so we can use the customization instead TSharedRef NaniteSettingsProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UStaticMesh, NaniteSettings)); NaniteSettingsProperty->MarkHiddenByCustomization(); NaniteSettings = MakeShareable(new FNaniteStaticMeshLayout(StaticMeshEditor)); NaniteSettings->AddToDetailsPanel(DetailBuilder); TSharedPtr SupportRayTracingProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UStaticMesh, bSupportRayTracing)); // Hide the existing RayTracingProxySettings property so we can use the customization instead TSharedRef RayTracingProxySettingsProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UStaticMesh, RayTracingProxySettings)); RayTracingProxySettingsProperty->MarkHiddenByCustomization(); RayTracingProxySettings = MakeShareable(new FRayTracingProxySettingsLayout(StaticMeshEditor)); IDetailCategoryBuilder& RayTracingCategory = DetailBuilder.EditCategory("RayTracing"); RayTracingCategory.AddProperty(SupportRayTracingProperty); // need to manually add bSupportRayTracing so it shows up first RayTracingCategory.AddCustomBuilder(RayTracingProxySettings.ToSharedRef()); TSharedRef BodyProp = DetailBuilder.GetProperty(UStaticMesh::GetBodySetupName()); BodyProp->MarkHiddenByCustomization(); static TArray HiddenBodyInstanceProps; if( HiddenBodyInstanceProps.Num() == 0 ) { //HiddenBodyInstanceProps.Add("DefaultInstance"); HiddenBodyInstanceProps.Add("BoneName"); HiddenBodyInstanceProps.Add("PhysicsType"); HiddenBodyInstanceProps.Add("bConsiderForBounds"); HiddenBodyInstanceProps.Add("CollisionReponse"); } uint32 NumChildren = 0; BodyProp->GetNumChildren( NumChildren ); TSharedPtr BodyPropObject; if( NumChildren == 1 ) { // This is an edit inline new property so the first child is the object instance for the edit inline new. The instance contains the child we want to display BodyPropObject = BodyProp->GetChildHandle( 0 ); NumChildren = 0; BodyPropObject->GetNumChildren( NumChildren ); for( uint32 ChildIndex = 0; ChildIndex < NumChildren; ++ChildIndex ) { TSharedPtr ChildProp = BodyPropObject->GetChildHandle(ChildIndex); if( ChildProp.IsValid() && ChildProp->GetProperty() && !HiddenBodyInstanceProps.Contains(ChildProp->GetProperty()->GetFName()) ) { CollisionCategory.AddProperty( ChildProp ); } } } } void FStaticMeshDetails::OnInstancedFbxStaticMeshImportDataPropertyIteration(IDetailCategoryBuilder& BaseCategory, IDetailGroup* PropertyGroup, TSharedRef& Property) const { IDetailPropertyRow* Row = nullptr; if (Property->GetBoolMetaData(TEXT("ReimportRestrict"))) { //Dont create a row for reimport restricted property, we do not want to show them return; } if (PropertyGroup) { Row = &PropertyGroup->AddPropertyRow(Property); } else { Row = &BaseCategory.AddProperty(Property); } if (Row) { //Vertex Override Color property should be disabled if we are not in override mode. if (Property->IsValidHandle() && Property->GetProperty() == VertexColorImportOverrideHandle->GetProperty()) { Row->IsEnabled(TAttribute(this, &FStaticMeshDetails::GetVertexOverrideColorEnabledState)); } } } void FStaticMeshDetails::OnLightmapSettingsChanged() { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); StaticMesh->EnforceLightmapRestrictions(false); } bool FStaticMeshDetails::GetVertexOverrideColorEnabledState() const { uint8 VertexColorImportOption; check(VertexColorImportOptionHandle.IsValid()); ensure(VertexColorImportOptionHandle->GetValue(VertexColorImportOption) == FPropertyAccess::Success); return (VertexColorImportOption == EVertexColorImportOption::Override); } void SConvexDecomposition::Construct(const FArguments& InArgs) { StaticMeshEditorPtr = InArgs._StaticMeshEditorPtr; CurrentHullPrecision = DefaultHullPrecision; CurrentHullCount = DefaultHullCount; CurrentMaxVertsPerHullCount = DefaultVertsPerHull; this->ChildSlot [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() .Padding(4.0f, 8.0f, 0.0f, 8.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.0f) .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(LOCTEXT("HullCount_ConvexDecomp", "Hull Count")) ] + SHorizontalBox::Slot() .FillWidth(3.0f) [ SAssignNew(HullCount, SSpinBox) .ToolTipText(LOCTEXT("HullCount_ConvexDecomp_Tip", "Maximum number of convex pieces that will be created.")) .MinValue(MinHullCount) .MaxValue(MaxHullCount) .Delta(HullCountDelta) .Value(this, &SConvexDecomposition::GetHullCount) .OnValueCommitted(this, &SConvexDecomposition::OnHullCountCommitted) .OnValueChanged(this, &SConvexDecomposition::OnHullCountChanged) ] ] +SVerticalBox::Slot() .AutoHeight() .Padding(4.0f, 8.0f, 0.0f, 8.0f) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .FillWidth(1.0f) .VAlign(VAlign_Center) [ SNew(STextBlock) .Text( LOCTEXT("MaxHullVerts_ConvexDecomp", "Max Hull Verts") ) ] +SHorizontalBox::Slot() .FillWidth(3.0f) [ SAssignNew(MaxVertsPerHull, SSpinBox) .ToolTipText(LOCTEXT("MaxHullVerts_ConvexDecomp_Tip", "Maximum number of vertices allowed for any generated convex hull.")) .MinValue(MinVertsPerHullCount) .MaxValue(MaxVertsPerHullCount) .Value( this, &SConvexDecomposition::GetVertsPerHullCount ) .OnValueCommitted( this, &SConvexDecomposition::OnVertsPerHullCountCommitted ) .OnValueChanged( this, &SConvexDecomposition::OnVertsPerHullCountChanged ) ] ] + SVerticalBox::Slot() .AutoHeight() .Padding(4.0f, 8.0f, 0.0f, 8.0f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.0f) .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(LOCTEXT("HullPrecision_ConvexDecomp", "Hull Precision")) ] + SHorizontalBox::Slot() .FillWidth(3.0f) [ SAssignNew(HullPrecision, SSpinBox) .ToolTipText(LOCTEXT("HullPrecision_ConvexDecomp_Tip", "Number of voxels to use when generating collision.")) .MinValue(MinHullPrecision) .MaxValue(MaxHullPrecision) .Delta(HullPrecisionDelta) .Value(this, &SConvexDecomposition::GetHullPrecision) .OnValueCommitted(this, &SConvexDecomposition::OnHullPrecisionCommitted) .OnValueChanged(this, &SConvexDecomposition::OnHullPrecisionChanged) ] ] +SVerticalBox::Slot() .AutoHeight() .HAlign(HAlign_Center) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .Padding(8.0f, 0.0f, 8.0f, 0.0f) [ SNew(SButton) .Text( LOCTEXT("Apply_ConvexDecomp", "Apply") ) .OnClicked(this, &SConvexDecomposition::OnApplyDecomp) ] +SHorizontalBox::Slot() .AutoWidth() .Padding(8.0f, 0.0f, 8.0f, 0.0f) [ SNew(SButton) .Text( LOCTEXT("Defaults_ConvexDecomp", "Defaults") ) .OnClicked(this, &SConvexDecomposition::OnDefaults) ] ] ]; } bool FStaticMeshDetails::IsApplyNeeded() const { return (LevelOfDetailSettings.IsValid() && LevelOfDetailSettings->IsApplyNeeded()) || (NaniteSettings.IsValid() && NaniteSettings->IsApplyNeeded()); } void FStaticMeshDetails::ApplyChanges() { if (LevelOfDetailSettings.IsValid()) { LevelOfDetailSettings->ApplyChanges(); } if (NaniteSettings.IsValid()) { NaniteSettings->ApplyChanges(); } } SConvexDecomposition::~SConvexDecomposition() { } FReply SConvexDecomposition::OnApplyDecomp() { StaticMeshEditorPtr.Pin()->DoDecomp(CurrentHullCount, CurrentMaxVertsPerHullCount, CurrentHullPrecision); return FReply::Handled(); } FReply SConvexDecomposition::OnDefaults() { CurrentHullCount = DefaultHullCount; CurrentHullPrecision = DefaultHullPrecision; CurrentMaxVertsPerHullCount = DefaultVertsPerHull; return FReply::Handled(); } void SConvexDecomposition::OnHullCountCommitted(uint32 InNewValue, ETextCommit::Type CommitInfo) { OnHullCountChanged(InNewValue); } void SConvexDecomposition::OnHullCountChanged(uint32 InNewValue) { CurrentHullCount = InNewValue; } uint32 SConvexDecomposition::GetHullCount() const { return CurrentHullCount; } void SConvexDecomposition::OnHullPrecisionCommitted(uint32 InNewValue, ETextCommit::Type CommitInfo) { OnHullPrecisionChanged(InNewValue); } void SConvexDecomposition::OnHullPrecisionChanged(uint32 InNewValue) { CurrentHullPrecision = InNewValue; } uint32 SConvexDecomposition::GetHullPrecision() const { return CurrentHullPrecision; } void SConvexDecomposition::OnVertsPerHullCountCommitted(int32 InNewValue, ETextCommit::Type CommitInfo) { OnVertsPerHullCountChanged(InNewValue); } void SConvexDecomposition::OnVertsPerHullCountChanged(int32 InNewValue) { CurrentMaxVertsPerHullCount = InNewValue; } int32 SConvexDecomposition::GetVertsPerHullCount() const { return CurrentMaxVertsPerHullCount; } static UEnum& GetFeatureImportanceEnum() { static UEnum* FeatureImportanceEnum = nullptr; if (FeatureImportanceEnum == nullptr) { FTopLevelAssetPath MeshFeatureImportanceEnumPath(TEXT("/Script/Engine"), TEXT("EMeshFeatureImportance")); FeatureImportanceEnum = FindObject(MeshFeatureImportanceEnumPath); check(FeatureImportanceEnum); } return *FeatureImportanceEnum; } static UEnum& GetTerminationCriterionEunum() { static UEnum* EnumPtr = nullptr; if (EnumPtr == nullptr) { FTopLevelAssetPath StaticMeshReductionTerimationCriterionEnumPath(TEXT("/Script/Engine"), TEXT("EStaticMeshReductionTerimationCriterion")); EnumPtr = FindObject(StaticMeshReductionTerimationCriterionEnumPath); check(EnumPtr); } return *EnumPtr; } static void FillEnumOptions(TArray >& OutStrings, UEnum& InEnum) { for (int32 EnumIndex = 0; EnumIndex < InEnum.NumEnums() - 1; ++EnumIndex) { OutStrings.Add(MakeShareable(new FString(InEnum.GetNameStringByIndex(EnumIndex)))); } } FMeshBuildSettingsLayout::FMeshBuildSettingsLayout( TSharedRef InParentLODSettings, int32 InLODIndex) : ParentLODSettings( InParentLODSettings ) , LODIndex(InLODIndex) { } FMeshBuildSettingsLayout::~FMeshBuildSettingsLayout() { } void FMeshBuildSettingsLayout::GenerateHeaderRowContent( FDetailWidgetRow& NodeRow ) { NodeRow.NameContent() [ SNew( STextBlock ) .Text( LOCTEXT("MeshBuildSettings", "Build Settings") ) .Font( IDetailLayoutBuilder::GetDetailFont() ) ]; } FString FMeshBuildSettingsLayout::GetCurrentDistanceFieldReplacementMeshPath() const { return BuildSettings.DistanceFieldReplacementMesh ? BuildSettings.DistanceFieldReplacementMesh->GetPathName() : FString(""); } void FMeshBuildSettingsLayout::OnDistanceFieldReplacementMeshSelected(const FAssetData& AssetData) { BuildSettings.DistanceFieldReplacementMesh = Cast(AssetData.GetAsset()); } void FMeshBuildSettingsLayout::GenerateChildContent( IDetailChildrenBuilder& ChildrenBuilder ) { { ChildrenBuilder.AddCustomRow( LOCTEXT("RecomputeNormals", "Recompute Normals") ) .RowTag("RecomputeNormals") .NameContent() [ SNew(STextBlock) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Text(LOCTEXT("RecomputeNormals", "Recompute Normals")) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FMeshBuildSettingsLayout::ShouldRecomputeNormals) .OnCheckStateChanged(this, &FMeshBuildSettingsLayout::OnRecomputeNormalsChanged) ]; } { ChildrenBuilder.AddCustomRow( LOCTEXT("RecomputeTangents", "Recompute Tangents") ) .RowTag("RecomputeTangents") .NameContent() [ SNew(STextBlock) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Text(LOCTEXT("RecomputeTangents", "Recompute Tangents")) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FMeshBuildSettingsLayout::ShouldRecomputeTangents) .OnCheckStateChanged(this, &FMeshBuildSettingsLayout::OnRecomputeTangentsChanged) ]; } { ChildrenBuilder.AddCustomRow( LOCTEXT("UseMikkTSpace", "Use MikkTSpace Tangent Space") ) .RowTag("UseMikkTSpace") .NameContent() [ SNew(STextBlock) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Text(LOCTEXT("UseMikkTSpace", "Use MikkTSpace Tangent Space")) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FMeshBuildSettingsLayout::ShouldUseMikkTSpace) .OnCheckStateChanged(this, &FMeshBuildSettingsLayout::OnUseMikkTSpaceChanged) ]; } { ChildrenBuilder.AddCustomRow( LOCTEXT("ComputeWeightedNormals", "Compute Weighted Normals") ) .RowTag("ComputeWeightedNormals") .NameContent() [ SNew(STextBlock) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Text(LOCTEXT("ComputeWeightedNormals", "Compute Weighted Normals")) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FMeshBuildSettingsLayout::ShouldComputeWeightedNormals) .OnCheckStateChanged(this, &FMeshBuildSettingsLayout::OnComputeWeightedNormalsChanged) ]; } { ChildrenBuilder.AddCustomRow( LOCTEXT("RemoveDegenerates", "Remove Degenerates") ) .RowTag("RemoveDegenerates") .NameContent() [ SNew(STextBlock) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Text(LOCTEXT("RemoveDegenerates", "Remove Degenerates")) ] .ValueContent() [ SNew(SCheckBox) .IsEnabled(this, &FMeshBuildSettingsLayout::IsRemoveDegeneratesDisabled) .IsChecked(this, &FMeshBuildSettingsLayout::ShouldRemoveDegenerates) .OnCheckStateChanged(this, &FMeshBuildSettingsLayout::OnRemoveDegeneratesChanged) ]; } { ChildrenBuilder.AddCustomRow( LOCTEXT("BuildReversedIndexBuffer", "Build Reversed Index Buffer") ) .RowTag("BuildReversedIndexBuffer") .NameContent() [ SNew(STextBlock) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Text(LOCTEXT("BuildReversedIndexBuffer", "Build Reversed Index Buffer")) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FMeshBuildSettingsLayout::ShouldBuildReversedIndexBuffer) .OnCheckStateChanged(this, &FMeshBuildSettingsLayout::OnBuildReversedIndexBufferChanged) ]; } { ChildrenBuilder.AddCustomRow(LOCTEXT("UseHighPrecisionTangentBasis", "Use High Precision Tangent Basis")) .RowTag("UseHighPrecisionTangentBasis") .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("UseHighPrecisionTangentBasis", "Use High Precision Tangent Basis")) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FMeshBuildSettingsLayout::ShouldUseHighPrecisionTangentBasis) .OnCheckStateChanged(this, &FMeshBuildSettingsLayout::OnUseHighPrecisionTangentBasisChanged) ]; } { ChildrenBuilder.AddCustomRow( LOCTEXT("UseFullPrecisionUVs", "Use Full Precision UVs") ) .RowTag("UseFullPrecisionUVs") .NameContent() [ SNew(STextBlock) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Text(LOCTEXT("UseFullPrecisionUVs", "Use Full Precision UVs")) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FMeshBuildSettingsLayout::ShouldUseFullPrecisionUVs) .OnCheckStateChanged(this, &FMeshBuildSettingsLayout::OnUseFullPrecisionUVsChanged) ]; } { ChildrenBuilder.AddCustomRow( LOCTEXT("UseBackwardsCompatibleF16TruncUVs", "UE4 Compatible UVs") ) .RowTag("UseBackwardsCompatibleF16TruncUVs") .NameContent() [ SNew(STextBlock) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Text(LOCTEXT("UseBackwardsCompatibleF16TruncUVs", "UE4 Compatible UVs")) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FMeshBuildSettingsLayout::ShouldUseBackwardsCompatibleF16TruncUVs) .OnCheckStateChanged(this, &FMeshBuildSettingsLayout::OnUseBackwardsCompatibleF16TruncUVsChanged) ]; } { ChildrenBuilder.AddCustomRow( LOCTEXT("GenerateLightmapUVs", "Generate Lightmap UVs") ) .RowTag("GenerateLightmapUVs") .NameContent() [ SNew(STextBlock) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Text(LOCTEXT("GenerateLightmapUVs", "Generate Lightmap UVs")) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FMeshBuildSettingsLayout::ShouldGenerateLightmapUVs) .OnCheckStateChanged(this, &FMeshBuildSettingsLayout::OnGenerateLightmapUVsChanged) ]; } { ChildrenBuilder.AddCustomRow( LOCTEXT("MinLightmapResolution", "Min Lightmap Resolution") ) .RowTag("MinLightmapResolution") .NameContent() [ SNew(STextBlock) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Text(LOCTEXT("MinLightmapResolution", "Min Lightmap Resolution")) ] .ValueContent() [ SNew(SSpinBox) .Font( IDetailLayoutBuilder::GetDetailFont() ) .MinValue(1) .MaxValue(2048) .Value(this, &FMeshBuildSettingsLayout::GetMinLightmapResolution) .OnValueChanged(this, &FMeshBuildSettingsLayout::OnMinLightmapResolutionChanged) ]; } { ChildrenBuilder.AddCustomRow( LOCTEXT("SourceLightmapIndex", "Source Lightmap Index") ) .RowTag("SourceLightmapIndex") .NameContent() [ SNew(STextBlock) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Text(LOCTEXT("SourceLightmapIndex", "Source Lightmap Index")) ] .ValueContent() [ SNew(SSpinBox) .Font( IDetailLayoutBuilder::GetDetailFont() ) .MinValue(0) .MaxValue(7) .Value(this, &FMeshBuildSettingsLayout::GetSrcLightmapIndex) .OnValueChanged(this, &FMeshBuildSettingsLayout::OnSrcLightmapIndexChanged) ]; } { ChildrenBuilder.AddCustomRow( LOCTEXT("DestinationLightmapIndex", "Destination Lightmap Index") ) .RowTag("DestinationLightmapIndex") .NameContent() [ SNew(STextBlock) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Text(LOCTEXT("DestinationLightmapIndex", "Destination Lightmap Index")) ] .ValueContent() [ SNew(SSpinBox) .Font( IDetailLayoutBuilder::GetDetailFont() ) .MinValue(0) .MaxValue(7) .Value(this, &FMeshBuildSettingsLayout::GetDstLightmapIndex) .OnValueChanged(this, &FMeshBuildSettingsLayout::OnDstLightmapIndexChanged) ]; } { ChildrenBuilder.AddCustomRow(LOCTEXT("BuildScale", "Build Scale")) .RowTag("BuildScale") .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("BuildScale", "Build Scale")) .ToolTipText( LOCTEXT("BuildScale_ToolTip", "The local scale applied when building the mesh") ) ] .ValueContent() .MinDesiredWidth(125.0f * 3.0f) .MaxDesiredWidth(125.0f * 3.0f) [ SNew(SVectorInputBox) .X(this, &FMeshBuildSettingsLayout::GetBuildScaleX) .Y(this, &FMeshBuildSettingsLayout::GetBuildScaleY) .Z(this, &FMeshBuildSettingsLayout::GetBuildScaleZ) .bColorAxisLabels(false) .AllowSpin(false) .OnXCommitted(this, &FMeshBuildSettingsLayout::OnBuildScaleXChanged) .OnYCommitted(this, &FMeshBuildSettingsLayout::OnBuildScaleYChanged) .OnZCommitted(this, &FMeshBuildSettingsLayout::OnBuildScaleZChanged) .Font(IDetailLayoutBuilder::GetDetailFont()) ]; } { ChildrenBuilder.AddCustomRow( LOCTEXT("DistanceFieldResolutionScale", "Distance Field Resolution Scale") ) .RowTag("DistanceFieldResolutionScale") .NameContent() [ SNew(STextBlock) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Text(LOCTEXT("DistanceFieldResolutionScale", "Distance Field Resolution Scale")) ] .ValueContent() [ SNew(SSpinBox) .Font( IDetailLayoutBuilder::GetDetailFont() ) .MinValue(0.0f) .MaxValue(100.0f) .Value(this, &FMeshBuildSettingsLayout::GetDistanceFieldResolutionScale) .OnValueChanged(this, &FMeshBuildSettingsLayout::OnDistanceFieldResolutionScaleChanged) .OnValueCommitted(this, &FMeshBuildSettingsLayout::OnDistanceFieldResolutionScaleCommitted) ]; } { ChildrenBuilder.AddCustomRow( LOCTEXT("GenerateDistanceFieldAsIfTwoSided", "Two-Sided Distance Field Generation") ) .RowTag("GenerateDistanceFieldAsIfTwoSided") .NameContent() [ SNew(STextBlock) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Text(LOCTEXT("GenerateDistanceFieldAsIfTwoSided", "Two-Sided Distance Field Generation")) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FMeshBuildSettingsLayout::ShouldGenerateDistanceFieldAsIfTwoSided) .OnCheckStateChanged(this, &FMeshBuildSettingsLayout::OnGenerateDistanceFieldAsIfTwoSidedChanged) ]; } { TSharedRef PropWidget = SNew(SObjectPropertyEntryBox) .AllowedClass(UStaticMesh::StaticClass()) .AllowClear(true) .ObjectPath(this, &FMeshBuildSettingsLayout::GetCurrentDistanceFieldReplacementMeshPath) .OnObjectChanged(this, &FMeshBuildSettingsLayout::OnDistanceFieldReplacementMeshSelected); ChildrenBuilder.AddCustomRow( LOCTEXT("DistanceFieldReplacementMesh", "Distance Field Replacement Mesh") ) .RowTag("DistanceFieldReplacementMesh") .NameContent() [ SNew(STextBlock) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Text(LOCTEXT("DistanceFieldReplacementMesh", "Distance Field Replacement Mesh")) ] .ValueContent() [ PropWidget ]; } { ChildrenBuilder.AddCustomRow(LOCTEXT("MaxLumenMeshCards", "Max Lumen Mesh Cards")) .RowTag("MaxLumenMeshCards") .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("MaxLumenMeshCards", "Max Lumen Mesh Cards")) ] .ValueContent() [ SNew(SSpinBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .MinValue(0) .MaxValue(32) .Value(this, &FMeshBuildSettingsLayout::GetMaxLumenMeshCards) .OnValueChanged(this, &FMeshBuildSettingsLayout::OnMaxLumenMeshCardsChanged) .OnValueCommitted(this, &FMeshBuildSettingsLayout::OnMaxLumenMeshCardsCommitted) ]; } { ChildrenBuilder.AddCustomRow( LOCTEXT("ApplyChanges", "Apply Changes") ) .RowTag("ApplyChanges") .ValueContent() .HAlign(HAlign_Left) [ SNew(SButton) .OnClicked(this, &FMeshBuildSettingsLayout::OnApplyChanges) .IsEnabled(ParentLODSettings.Pin().ToSharedRef(), &FLevelOfDetailSettingsLayout::IsApplyNeeded) [ SNew( STextBlock ) .Text(LOCTEXT("ApplyChanges", "Apply Changes")) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] ]; } } void FMeshBuildSettingsLayout::UpdateSettings(const FMeshBuildSettings& InSettings) { BuildSettings = InSettings; } FReply FMeshBuildSettingsLayout::OnApplyChanges() { if( ParentLODSettings.IsValid() ) { ParentLODSettings.Pin()->ApplyChanges(); } return FReply::Handled(); } ECheckBoxState FMeshBuildSettingsLayout::ShouldRecomputeNormals() const { return BuildSettings.bRecomputeNormals ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } ECheckBoxState FMeshBuildSettingsLayout::ShouldRecomputeTangents() const { return BuildSettings.bRecomputeTangents ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } ECheckBoxState FMeshBuildSettingsLayout::ShouldUseMikkTSpace() const { return BuildSettings.bUseMikkTSpace ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } ECheckBoxState FMeshBuildSettingsLayout::ShouldComputeWeightedNormals() const { return BuildSettings.bComputeWeightedNormals ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } ECheckBoxState FMeshBuildSettingsLayout::ShouldRemoveDegenerates() const { return BuildSettings.bRemoveDegenerates ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } ECheckBoxState FMeshBuildSettingsLayout::ShouldBuildReversedIndexBuffer() const { return BuildSettings.bBuildReversedIndexBuffer ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } ECheckBoxState FMeshBuildSettingsLayout::ShouldUseHighPrecisionTangentBasis() const { return BuildSettings.bUseHighPrecisionTangentBasis ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } ECheckBoxState FMeshBuildSettingsLayout::ShouldUseFullPrecisionUVs() const { return BuildSettings.bUseFullPrecisionUVs ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } ECheckBoxState FMeshBuildSettingsLayout::ShouldUseBackwardsCompatibleF16TruncUVs() const { return BuildSettings.bUseBackwardsCompatibleF16TruncUVs ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } ECheckBoxState FMeshBuildSettingsLayout::ShouldGenerateLightmapUVs() const { return BuildSettings.bGenerateLightmapUVs ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } ECheckBoxState FMeshBuildSettingsLayout::ShouldGenerateDistanceFieldAsIfTwoSided() const { return BuildSettings.bGenerateDistanceFieldAsIfTwoSided ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } bool FMeshBuildSettingsLayout::IsRemoveDegeneratesDisabled() const { if (TSharedPtr LODSettingsLayout = ParentLODSettings.Pin()) { return !LODSettingsLayout->IsNaniteEnabled(); } return true; } int32 FMeshBuildSettingsLayout::GetMinLightmapResolution() const { return BuildSettings.MinLightmapResolution; } int32 FMeshBuildSettingsLayout::GetSrcLightmapIndex() const { return BuildSettings.SrcLightmapIndex; } int32 FMeshBuildSettingsLayout::GetDstLightmapIndex() const { return BuildSettings.DstLightmapIndex; } TOptional FMeshBuildSettingsLayout::GetBuildScaleX() const { return static_cast(BuildSettings.BuildScale3D.X); } TOptional FMeshBuildSettingsLayout::GetBuildScaleY() const { return static_cast(BuildSettings.BuildScale3D.Y); } TOptional FMeshBuildSettingsLayout::GetBuildScaleZ() const { return static_cast(BuildSettings.BuildScale3D.Z); } float FMeshBuildSettingsLayout::GetDistanceFieldResolutionScale() const { return BuildSettings.DistanceFieldResolutionScale; } int32 FMeshBuildSettingsLayout::GetMaxLumenMeshCards() const { return BuildSettings.MaxLumenMeshCards; } void FMeshBuildSettingsLayout::OnRecomputeNormalsChanged(ECheckBoxState NewState) { const bool bRecomputeNormals = (NewState == ECheckBoxState::Checked) ? true : false; if (BuildSettings.bRecomputeNormals != bRecomputeNormals) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.BuildSettings"), TEXT("bRecomputeNormals"), bRecomputeNormals ? TEXT("True") : TEXT("False")); } BuildSettings.bRecomputeNormals = bRecomputeNormals; } } void FMeshBuildSettingsLayout::OnRecomputeTangentsChanged(ECheckBoxState NewState) { const bool bRecomputeTangents = (NewState == ECheckBoxState::Checked) ? true : false; if (BuildSettings.bRecomputeTangents != bRecomputeTangents) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.BuildSettings"), TEXT("bRecomputeTangents"), bRecomputeTangents ? TEXT("True") : TEXT("False")); } BuildSettings.bRecomputeTangents = bRecomputeTangents; } } void FMeshBuildSettingsLayout::OnUseMikkTSpaceChanged(ECheckBoxState NewState) { const bool bUseMikkTSpace = (NewState == ECheckBoxState::Checked) ? true : false; if (BuildSettings.bUseMikkTSpace != bUseMikkTSpace) { BuildSettings.bUseMikkTSpace = bUseMikkTSpace; } } void FMeshBuildSettingsLayout::OnComputeWeightedNormalsChanged(ECheckBoxState NewState) { const bool bComputeWeightedNormals = (NewState == ECheckBoxState::Checked) ? true : false; if (BuildSettings.bComputeWeightedNormals != bComputeWeightedNormals) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.BuildSettings"), TEXT("bComputeWeightedNormals"), bComputeWeightedNormals ? TEXT("True") : TEXT("False")); } BuildSettings.bComputeWeightedNormals = bComputeWeightedNormals; } } void FMeshBuildSettingsLayout::OnRemoveDegeneratesChanged(ECheckBoxState NewState) { const bool bRemoveDegenerates = (NewState == ECheckBoxState::Checked) ? true : false; if (BuildSettings.bRemoveDegenerates != bRemoveDegenerates) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.BuildSettings"), TEXT("bRemoveDegenerates"), bRemoveDegenerates ? TEXT("True") : TEXT("False")); } BuildSettings.bRemoveDegenerates = bRemoveDegenerates; } } void FMeshBuildSettingsLayout::OnBuildReversedIndexBufferChanged(ECheckBoxState NewState) { const bool bBuildReversedIndexBuffer = (NewState == ECheckBoxState::Checked) ? true : false; if (BuildSettings.bBuildReversedIndexBuffer != bBuildReversedIndexBuffer) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.BuildSettings"), TEXT("bBuildReversedIndexBuffer"), bBuildReversedIndexBuffer ? TEXT("True") : TEXT("False")); } BuildSettings.bBuildReversedIndexBuffer = bBuildReversedIndexBuffer; } } void FMeshBuildSettingsLayout::OnUseHighPrecisionTangentBasisChanged(ECheckBoxState NewState) { const bool bUseHighPrecisionTangents = (NewState == ECheckBoxState::Checked) ? true : false; if (BuildSettings.bUseHighPrecisionTangentBasis != bUseHighPrecisionTangents) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.BuildSettings"), TEXT("bUseHighPrecisionTangentBasis"), bUseHighPrecisionTangents ? TEXT("True") : TEXT("False")); } BuildSettings.bUseHighPrecisionTangentBasis = bUseHighPrecisionTangents; } } void FMeshBuildSettingsLayout::OnUseFullPrecisionUVsChanged(ECheckBoxState NewState) { const bool bUseFullPrecisionUVs = (NewState == ECheckBoxState::Checked) ? true : false; if (BuildSettings.bUseFullPrecisionUVs != bUseFullPrecisionUVs) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.BuildSettings"), TEXT("bUseFullPrecisionUVs"), bUseFullPrecisionUVs ? TEXT("True") : TEXT("False")); } BuildSettings.bUseFullPrecisionUVs = bUseFullPrecisionUVs; } } void FMeshBuildSettingsLayout::OnUseBackwardsCompatibleF16TruncUVsChanged(ECheckBoxState NewState) { const bool bUseBackwardsCompatibleF16TruncUVs = (NewState == ECheckBoxState::Checked) ? true : false; if (BuildSettings.bUseBackwardsCompatibleF16TruncUVs != bUseBackwardsCompatibleF16TruncUVs) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.BuildSettings"), TEXT("bUseBackwardsCompatibleF16TruncUVs"), bUseBackwardsCompatibleF16TruncUVs ? TEXT("True") : TEXT("False")); } BuildSettings.bUseBackwardsCompatibleF16TruncUVs = bUseBackwardsCompatibleF16TruncUVs; } } void FMeshBuildSettingsLayout::OnGenerateLightmapUVsChanged(ECheckBoxState NewState) { const bool bGenerateLightmapUVs = (NewState == ECheckBoxState::Checked) ? true : false; if (BuildSettings.bGenerateLightmapUVs != bGenerateLightmapUVs) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.BuildSettings"), TEXT("bGenerateLightmapUVs"), bGenerateLightmapUVs ? TEXT("True") : TEXT("False")); } BuildSettings.bGenerateLightmapUVs = bGenerateLightmapUVs; } } void FMeshBuildSettingsLayout::OnGenerateDistanceFieldAsIfTwoSidedChanged(ECheckBoxState NewState) { const bool bGenerateDistanceFieldAsIfTwoSided = (NewState == ECheckBoxState::Checked) ? true : false; if (BuildSettings.bGenerateDistanceFieldAsIfTwoSided != bGenerateDistanceFieldAsIfTwoSided) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.BuildSettings"), TEXT("bGenerateDistanceFieldAsIfTwoSided"), bGenerateDistanceFieldAsIfTwoSided ? TEXT("True") : TEXT("False")); } BuildSettings.bGenerateDistanceFieldAsIfTwoSided = bGenerateDistanceFieldAsIfTwoSided; } } void FMeshBuildSettingsLayout::OnMinLightmapResolutionChanged( int32 NewValue ) { if (BuildSettings.MinLightmapResolution != NewValue) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.BuildSettings"), TEXT("MinLightmapResolution"), FString::Printf(TEXT("%i"), NewValue)); } BuildSettings.MinLightmapResolution = NewValue; } } void FMeshBuildSettingsLayout::OnSrcLightmapIndexChanged( int32 NewValue ) { if (BuildSettings.SrcLightmapIndex != NewValue) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.BuildSettings"), TEXT("SrcLightmapIndex"), FString::Printf(TEXT("%i"), NewValue)); } BuildSettings.SrcLightmapIndex = NewValue; } } void FMeshBuildSettingsLayout::OnDstLightmapIndexChanged( int32 NewValue ) { if (BuildSettings.DstLightmapIndex != NewValue) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.BuildSettings"), TEXT("DstLightmapIndex"), FString::Printf(TEXT("%i"), NewValue)); } BuildSettings.DstLightmapIndex = NewValue; } } void FMeshBuildSettingsLayout::OnBuildScaleXChanged( float NewScaleX, ETextCommit::Type TextCommitType ) { if (!FMath::IsNearlyEqual(NewScaleX, 0.0f) && BuildSettings.BuildScale3D.X != NewScaleX) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.BuildSettings"), TEXT("BuildScale3D.X"), FString::Printf(TEXT("%.3f"), NewScaleX)); } BuildSettings.BuildScale3D.X = NewScaleX; } } void FMeshBuildSettingsLayout::OnBuildScaleYChanged( float NewScaleY, ETextCommit::Type TextCommitType ) { if (!FMath::IsNearlyEqual(NewScaleY, 0.0f) && BuildSettings.BuildScale3D.Y != NewScaleY) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.BuildSettings"), TEXT("BuildScale3D.Y"), FString::Printf(TEXT("%.3f"), NewScaleY)); } BuildSettings.BuildScale3D.Y = NewScaleY; } } void FMeshBuildSettingsLayout::OnBuildScaleZChanged( float NewScaleZ, ETextCommit::Type TextCommitType ) { if (!FMath::IsNearlyEqual(NewScaleZ, 0.0f) && BuildSettings.BuildScale3D.Z != NewScaleZ) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.BuildSettings"), TEXT("BuildScale3D.Z"), FString::Printf(TEXT("%.3f"), NewScaleZ)); } BuildSettings.BuildScale3D.Z = NewScaleZ; } } void FMeshBuildSettingsLayout::OnDistanceFieldResolutionScaleChanged(float NewValue) { BuildSettings.DistanceFieldResolutionScale = NewValue; } void FMeshBuildSettingsLayout::OnDistanceFieldResolutionScaleCommitted(float NewValue, ETextCommit::Type TextCommitType) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.BuildSettings"), TEXT("DistanceFieldResolutionScale"), FString::Printf(TEXT("%.3f"), NewValue)); } OnDistanceFieldResolutionScaleChanged(NewValue); } void FMeshBuildSettingsLayout::OnMaxLumenMeshCardsChanged(int32 NewValue) { BuildSettings.MaxLumenMeshCards = NewValue; } void FMeshBuildSettingsLayout::OnMaxLumenMeshCardsCommitted(int32 NewValue, ETextCommit::Type TextCommitType) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.BuildSettings"), TEXT("MaxLumenMeshCards"), FString::Printf(TEXT("%d"), NewValue)); } OnMaxLumenMeshCardsChanged(NewValue); } FMeshReductionSettingsLayout::FMeshReductionSettingsLayout( TSharedRef InParentLODSettings, int32 InCurrentLODIndex, bool InCanReduceMyself) : ParentLODSettings( InParentLODSettings ) , CurrentLODIndex(InCurrentLODIndex) , bCanReduceMyself(InCanReduceMyself) { FillEnumOptions(ImportanceOptions, GetFeatureImportanceEnum()); FillEnumOptions(TerminationOptions, GetTerminationCriterionEunum()); bUseQuadricSimplifier = UseNativeToolLayout(); } FMeshReductionSettingsLayout::~FMeshReductionSettingsLayout() { } void FMeshReductionSettingsLayout::GenerateHeaderRowContent( FDetailWidgetRow& NodeRow ) { NodeRow.NameContent() [ SNew( STextBlock ) .Text( LOCTEXT("MeshReductionSettings", "Reduction Settings") ) .Font( IDetailLayoutBuilder::GetDetailFont() ) ]; } bool FMeshReductionSettingsLayout::UseNativeToolLayout() const { // Are we using our tool, or simplygon? The tool is only changed during editor restarts IMeshReduction* ReductionModule = FModuleManager::Get().LoadModuleChecked("MeshReductionInterface").GetStaticMeshReductionInterface(); FString VersionString = ReductionModule->GetVersionString(); TArray SplitVersionString; VersionString.ParseIntoArray(SplitVersionString, TEXT("_"), true); bool bUseQuadricSimplier = SplitVersionString[0].Equals("QuadricMeshReduction"); return bUseQuadricSimplier; } EVisibility FMeshReductionSettingsLayout::GetTriangleCriterionVisibility() const { EVisibility VisibilityValue; if (!bUseQuadricSimplifier || ReductionSettings.TerminationCriterion == EStaticMeshReductionTerimationCriterion::Triangles || ReductionSettings.TerminationCriterion == EStaticMeshReductionTerimationCriterion::Any) { VisibilityValue = EVisibility::Visible; } else { VisibilityValue = EVisibility::Hidden; } return VisibilityValue; } EVisibility FMeshReductionSettingsLayout::GetVertexCriterionVisibility() const { EVisibility VisibilityValue; if (!bUseQuadricSimplifier || ReductionSettings.TerminationCriterion == EStaticMeshReductionTerimationCriterion::Vertices || ReductionSettings.TerminationCriterion == EStaticMeshReductionTerimationCriterion::Any) { VisibilityValue = EVisibility::Visible; } else { VisibilityValue = EVisibility::Hidden; } return VisibilityValue; } BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void FMeshReductionSettingsLayout::GenerateChildContent( IDetailChildrenBuilder& ChildrenBuilder ) { if (bUseQuadricSimplifier) { { ChildrenBuilder.AddCustomRow(LOCTEXT("Termination_MeshSimplification", "Termination")) .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("Termination_MeshSimplification", "Termination")) ] .ValueContent() [ SAssignNew(TerminationCriterionCombo, STextComboBox) .Font( IDetailLayoutBuilder::GetDetailFont() ) .OptionsSource(&TerminationOptions) .InitiallySelectedItem(TerminationOptions[static_cast(ReductionSettings.TerminationCriterion)]) .OnSelectionChanged(this, &FMeshReductionSettingsLayout::OnTerminationCriterionChanged) ]; } } { ChildrenBuilder.AddCustomRow( LOCTEXT("PercentTriangles", "Percent Triangles") ) .RowTag("PercentTriangles") .NameContent() [ SNew(STextBlock) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Text(LOCTEXT("PercentTriangles", "Percent Triangles")) ] .ValueContent() [ SNew(SSpinBox) .Font( IDetailLayoutBuilder::GetDetailFont() ) .MinValue(0.0f) .MaxValue(100.0f) .Value(this, &FMeshReductionSettingsLayout::GetPercentTriangles) .OnValueChanged(this, &FMeshReductionSettingsLayout::OnPercentTrianglesChanged) .OnValueCommitted(this, &FMeshReductionSettingsLayout::OnPercentTrianglesCommitted) ] .Visibility(TAttribute(this, &FMeshReductionSettingsLayout::GetTriangleCriterionVisibility)); if (bUseQuadricSimplifier) { ChildrenBuilder.AddCustomRow( LOCTEXT("MaxTrianglesPercentage_Row", "Max Number of Triangles") ) .RowTag("MaxPercentTriangles") .NameContent() [ SNew(STextBlock) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Text(LOCTEXT("MaxTrianglesPercentage", "Max Triangles Count")) .ToolTipText(LOCTEXT("MaxTrianglesPercentage_ToolTip", "The maximum number of triangles to retain when using percentage criterion.")) ] .ValueContent() [ SNew(SSpinBox) .Font( IDetailLayoutBuilder::GetDetailFont() ) .MinValue(2) .MaxValue(MAX_uint32) .Value(this, &FMeshReductionSettingsLayout::GetMaxNumOfPercentTriangles) .OnValueChanged(this, &FMeshReductionSettingsLayout::OnMaxNumOfPercentTrianglesChanged) .OnValueCommitted(this, &FMeshReductionSettingsLayout::OnMaxNumOfPercentTrianglesCommitted) ] .Visibility(TAttribute(this, &FMeshReductionSettingsLayout::GetTriangleCriterionVisibility)); } } if (bUseQuadricSimplifier) { //Percent vertices ChildrenBuilder.AddCustomRow(LOCTEXT("PercentVertices", "Percent Vertices")) .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("PercentVertices", "Percent Vertices")) ] .ValueContent() [ SNew(SSpinBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .MinValue(0.0f) .MaxValue(100.0f) .Value(this, &FMeshReductionSettingsLayout::GetPercentVertices) .OnValueChanged(this, &FMeshReductionSettingsLayout::OnPercentVerticesChanged) .OnValueCommitted(this, &FMeshReductionSettingsLayout::OnPercentVerticesCommitted) ] .Visibility(TAttribute(this, &FMeshReductionSettingsLayout::GetVertexCriterionVisibility)); //Max percent absolute vertices ChildrenBuilder.AddCustomRow( LOCTEXT("MaxVerticesPercentage_Row", "Max Number of Vertices") ) .RowTag("MaxVertices") .NameContent() [ SNew(STextBlock) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Text(LOCTEXT("MaxVerticesPercentage", "Max Vertices Count")) .ToolTipText(LOCTEXT("MaxVerticesPercentage_ToolTip", "The maximum number of Vertices to retain when using percentage criterion.")) ] .ValueContent() [ SNew(SSpinBox) .Font( IDetailLayoutBuilder::GetDetailFont() ) .MinValue(2) .MaxValue(MAX_uint32) .Value(this, &FMeshReductionSettingsLayout::GetMaxNumOfPercentVertices) .OnValueChanged(this, &FMeshReductionSettingsLayout::OnMaxNumOfPercentVerticesChanged) .OnValueCommitted(this, &FMeshReductionSettingsLayout::OnMaxNumOfPercentVerticesCommitted) ] .Visibility(TAttribute(this, &FMeshReductionSettingsLayout::GetVertexCriterionVisibility)); } // Controls that only simplygon uses. if (!bUseQuadricSimplifier) { { ChildrenBuilder.AddCustomRow(LOCTEXT("MaxDeviation", "Max Deviation")) .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("MaxDeviation", "Max Deviation")) ] .ValueContent() [ SNew(SSpinBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .MinValue(0.0f) .MaxValue(1000.0f) .Value(this, &FMeshReductionSettingsLayout::GetMaxDeviation) .OnValueChanged(this, &FMeshReductionSettingsLayout::OnMaxDeviationChanged) .OnValueCommitted(this, &FMeshReductionSettingsLayout::OnMaxDeviationCommitted) ]; } { ChildrenBuilder.AddCustomRow(LOCTEXT("PixelError", "Pixel Error")) .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("PixelError", "Pixel Error")) ] .ValueContent() [ SNew(SSpinBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .MinValue(0.0f) .MaxValue(40.0f) .Value(this, &FMeshReductionSettingsLayout::GetPixelError) .OnValueChanged(this, &FMeshReductionSettingsLayout::OnPixelErrorChanged) .OnValueCommitted(this, &FMeshReductionSettingsLayout::OnPixelErrorCommitted) ]; } { ChildrenBuilder.AddCustomRow(LOCTEXT("Silhouette_MeshSimplification", "Silhouette")) .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("Silhouette_MeshSimplification", "Silhouette")) ] .ValueContent() [ SAssignNew(SilhouetteCombo, STextComboBox) //.Font( IDetailLayoutBuilder::GetDetailFont() ) .ContentPadding(0.f) .OptionsSource(&ImportanceOptions) .InitiallySelectedItem(ImportanceOptions[ReductionSettings.SilhouetteImportance]) .OnSelectionChanged(this, &FMeshReductionSettingsLayout::OnSilhouetteImportanceChanged) ]; } { ChildrenBuilder.AddCustomRow(LOCTEXT("Texture_MeshSimplification", "Texture")) .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("Texture_MeshSimplification", "Texture")) ] .ValueContent() [ SAssignNew(TextureCombo, STextComboBox) //.Font( IDetailLayoutBuilder::GetDetailFont() ) .ContentPadding(0.f) .OptionsSource(&ImportanceOptions) .InitiallySelectedItem(ImportanceOptions[ReductionSettings.TextureImportance]) .OnSelectionChanged(this, &FMeshReductionSettingsLayout::OnTextureImportanceChanged) ]; } { ChildrenBuilder.AddCustomRow(LOCTEXT("Shading_MeshSimplification", "Shading")) .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("Shading_MeshSimplification", "Shading")) ] .ValueContent() [ SAssignNew(ShadingCombo, STextComboBox) //.Font( IDetailLayoutBuilder::GetDetailFont() ) .ContentPadding(0.f) .OptionsSource(&ImportanceOptions) .InitiallySelectedItem(ImportanceOptions[ReductionSettings.ShadingImportance]) .OnSelectionChanged(this, &FMeshReductionSettingsLayout::OnShadingImportanceChanged) ]; } } { ChildrenBuilder.AddCustomRow( LOCTEXT("WeldingThreshold", "Welding Threshold") ) .NameContent() [ SNew(STextBlock) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Text(LOCTEXT("WeldingThreshold", "Welding Threshold")) ] .ValueContent() [ SNew(SSpinBox) .Font( IDetailLayoutBuilder::GetDetailFont() ) .MinValue(0.0f) .MaxValue(10.0f) .Value(this, &FMeshReductionSettingsLayout::GetWeldingThreshold) .OnValueChanged(this, &FMeshReductionSettingsLayout::OnWeldingThresholdChanged) .OnValueCommitted(this, &FMeshReductionSettingsLayout::OnWeldingThresholdCommitted) ]; } // controls that only simplygon uses if (!bUseQuadricSimplifier) { { ChildrenBuilder.AddCustomRow(LOCTEXT("RecomputeNormals", "Recompute Normals")) .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("RecomputeNormals", "Recompute Normals")) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FMeshReductionSettingsLayout::ShouldRecalculateNormals) .OnCheckStateChanged(this, &FMeshReductionSettingsLayout::OnRecalculateNormalsChanged) ]; } { ChildrenBuilder.AddCustomRow(LOCTEXT("HardEdgeAngle", "Hard Edge Angle")) .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("HardEdgeAngle", "Hard Edge Angle")) ] .ValueContent() [ SNew(SSpinBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .MinValue(0.0f) .MaxValue(180.0f) .Value(this, &FMeshReductionSettingsLayout::GetHardAngleThreshold) .OnValueChanged(this, &FMeshReductionSettingsLayout::OnHardAngleThresholdChanged) .OnValueCommitted(this, &FMeshReductionSettingsLayout::OnHardAngleThresholdCommitted) ]; } } //Base LOD { int32 MaxBaseReduceIndex = bCanReduceMyself ? CurrentLODIndex : CurrentLODIndex - 1; ChildrenBuilder.AddCustomRow(LOCTEXT("ReductionBaseLOD", "Base LOD")) .NameContent() .HAlign(HAlign_Left) [ SNew(STextBlock) .Text(LOCTEXT("ReductionBaseLOD", "Base LOD")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() .HAlign(HAlign_Left) [ SNew(SNumericEntryBox) .AllowSpin(true) .MinSliderValue(0) .MaxSliderValue(MaxBaseReduceIndex) .MinValue(0) .MaxValue(MaxBaseReduceIndex) .Value(this, &FMeshReductionSettingsLayout::GetBaseLODIndex) .OnValueChanged(this, &FMeshReductionSettingsLayout::SetBaseLODIndex) ]; } { ChildrenBuilder.AddCustomRow( LOCTEXT("ApplyChanges", "Apply Changes") ) .RowTag("ApplyChanges") .ValueContent() .HAlign(HAlign_Left) [ SNew(SButton) .OnClicked(this, &FMeshReductionSettingsLayout::OnApplyChanges) .IsEnabled(ParentLODSettings.Pin().ToSharedRef(), &FLevelOfDetailSettingsLayout::IsApplyNeeded) [ SNew( STextBlock ) .Text(LOCTEXT("ApplyChanges", "Apply Changes")) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] ]; } if (!bUseQuadricSimplifier) { SilhouetteCombo->SetSelectedItem(ImportanceOptions[ReductionSettings.SilhouetteImportance]); TextureCombo->SetSelectedItem(ImportanceOptions[ReductionSettings.TextureImportance]); ShadingCombo->SetSelectedItem(ImportanceOptions[ReductionSettings.ShadingImportance]); } else { TerminationCriterionCombo->SetSelectedItem(TerminationOptions[static_cast(ReductionSettings.TerminationCriterion)]); } } END_SLATE_FUNCTION_BUILD_OPTIMIZATION const FMeshReductionSettings& FMeshReductionSettingsLayout::GetSettings() const { return ReductionSettings; } void FMeshReductionSettingsLayout::UpdateSettings(const FMeshReductionSettings& InSettings) { ReductionSettings = InSettings; } FReply FMeshReductionSettingsLayout::OnApplyChanges() { if( ParentLODSettings.IsValid() ) { ParentLODSettings.Pin()->ApplyChanges(); } return FReply::Handled(); } float FMeshReductionSettingsLayout::GetPercentTriangles() const { return ReductionSettings.PercentTriangles * 100.0f; // Display fraction as percentage. } uint32 FMeshReductionSettingsLayout::GetMaxNumOfPercentTriangles() const { return ReductionSettings.MaxNumOfTriangles; } float FMeshReductionSettingsLayout::GetPercentVertices() const { return ReductionSettings.PercentVertices * 100.0f; // Display fraction as percentage. } uint32 FMeshReductionSettingsLayout::GetMaxNumOfPercentVertices() const { return ReductionSettings.MaxNumOfVerts; } float FMeshReductionSettingsLayout::GetMaxDeviation() const { return ReductionSettings.MaxDeviation; } float FMeshReductionSettingsLayout::GetPixelError() const { return ReductionSettings.PixelError; } float FMeshReductionSettingsLayout::GetWeldingThreshold() const { return ReductionSettings.WeldingThreshold; } ECheckBoxState FMeshReductionSettingsLayout::ShouldRecalculateNormals() const { return ReductionSettings.bRecalculateNormals ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } float FMeshReductionSettingsLayout::GetHardAngleThreshold() const { return ReductionSettings.HardAngleThreshold; } void FMeshReductionSettingsLayout::OnPercentTrianglesChanged(float NewValue) { // Percentage -> fraction. ReductionSettings.PercentTriangles = NewValue * 0.01f; } void FMeshReductionSettingsLayout::OnPercentVerticesChanged(float NewValue) { // Percentage -> fraction. ReductionSettings.PercentVertices = NewValue * 0.01f; } void FMeshReductionSettingsLayout::OnPercentTrianglesCommitted(float NewValue, ETextCommit::Type TextCommitType) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.ReductionSettings"), TEXT("PercentTriangles"), FString::Printf(TEXT("%.1f"), NewValue)); } OnPercentTrianglesChanged(NewValue); } void FMeshReductionSettingsLayout::OnPercentVerticesCommitted(float NewValue, ETextCommit::Type TextCommitType) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.ReductionSettings"), TEXT("PercentVertices"), FString::Printf(TEXT("%.1f"), NewValue)); } OnPercentVerticesChanged(NewValue); } void FMeshReductionSettingsLayout::OnMaxNumOfPercentTrianglesChanged(uint32 NewValue) { ReductionSettings.MaxNumOfTriangles = NewValue; } void FMeshReductionSettingsLayout::OnMaxNumOfPercentTrianglesCommitted(uint32 NewValue, ETextCommit::Type TextCommitType) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.ReductionSettings"), TEXT("MaxNumOfTrianglesPercentage"), FString::Printf(TEXT("%d"), NewValue)); } OnMaxNumOfPercentTrianglesChanged(NewValue); } void FMeshReductionSettingsLayout::OnMaxNumOfPercentVerticesChanged(uint32 NewValue) { ReductionSettings.MaxNumOfVerts = NewValue; } void FMeshReductionSettingsLayout::OnMaxNumOfPercentVerticesCommitted(uint32 NewValue, ETextCommit::Type TextCommitType) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.ReductionSettings"), TEXT("MaxNumOfVertsPercentage"), FString::Printf(TEXT("%d"), NewValue)); } OnMaxNumOfPercentVerticesChanged(NewValue); } void FMeshReductionSettingsLayout::OnMaxDeviationChanged(float NewValue) { ReductionSettings.MaxDeviation = NewValue; } void FMeshReductionSettingsLayout::OnMaxDeviationCommitted(float NewValue, ETextCommit::Type TextCommitType) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.ReductionSettings"), TEXT("MaxDeviation"), FString::Printf(TEXT("%.1f"), NewValue)); } OnMaxDeviationChanged(NewValue); } void FMeshReductionSettingsLayout::OnPixelErrorChanged(float NewValue) { ReductionSettings.PixelError = NewValue; } void FMeshReductionSettingsLayout::OnPixelErrorCommitted(float NewValue, ETextCommit::Type TextCommitType) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.ReductionSettings"), TEXT("PixelError"), FString::Printf(TEXT("%.1f"), NewValue)); } OnPixelErrorChanged(NewValue); } void FMeshReductionSettingsLayout::OnWeldingThresholdChanged(float NewValue) { ReductionSettings.WeldingThreshold = NewValue; } void FMeshReductionSettingsLayout::OnWeldingThresholdCommitted(float NewValue, ETextCommit::Type TextCommitType) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.ReductionSettings"), TEXT("WeldingThreshold"), FString::Printf(TEXT("%.2f"), NewValue)); } OnWeldingThresholdChanged(NewValue); } void FMeshReductionSettingsLayout::OnRecalculateNormalsChanged(ECheckBoxState NewValue) { const bool bRecalculateNormals = NewValue == ECheckBoxState::Checked; if (ReductionSettings.bRecalculateNormals != bRecalculateNormals) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.ReductionSettings"), TEXT("bRecalculateNormals"), bRecalculateNormals ? TEXT("True") : TEXT("False")); } ReductionSettings.bRecalculateNormals = bRecalculateNormals; } } void FMeshReductionSettingsLayout::OnHardAngleThresholdChanged(float NewValue) { ReductionSettings.HardAngleThreshold = NewValue; } void FMeshReductionSettingsLayout::OnHardAngleThresholdCommitted(float NewValue, ETextCommit::Type TextCommitType) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.ReductionSettings"), TEXT("HardAngleThreshold"), FString::Printf(TEXT("%.3f"), NewValue)); } OnHardAngleThresholdChanged(NewValue); } void FMeshReductionSettingsLayout::OnSilhouetteImportanceChanged(TSharedPtr NewValue, ESelectInfo::Type SelectInfo) { const EMeshFeatureImportance::Type SilhouetteImportance = (EMeshFeatureImportance::Type)ImportanceOptions.Find(NewValue); if (ReductionSettings.SilhouetteImportance != SilhouetteImportance) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.ReductionSettings"), TEXT("SilhouetteImportance"), *NewValue.Get()); } ReductionSettings.SilhouetteImportance = SilhouetteImportance; } } void FMeshReductionSettingsLayout::OnTextureImportanceChanged(TSharedPtr NewValue, ESelectInfo::Type SelectInfo) { const EMeshFeatureImportance::Type TextureImportance = (EMeshFeatureImportance::Type)ImportanceOptions.Find(NewValue); if (ReductionSettings.TextureImportance != TextureImportance) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.ReductionSettings"), TEXT("TextureImportance"), *NewValue.Get()); } ReductionSettings.TextureImportance = TextureImportance; } } void FMeshReductionSettingsLayout::OnShadingImportanceChanged(TSharedPtr NewValue, ESelectInfo::Type SelectInfo) { const EMeshFeatureImportance::Type ShadingImportance = (EMeshFeatureImportance::Type)ImportanceOptions.Find(NewValue); if (ReductionSettings.ShadingImportance != ShadingImportance) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.ReductionSettings"), TEXT("ShadingImportance"), *NewValue.Get()); } ReductionSettings.ShadingImportance = ShadingImportance; } } void FMeshReductionSettingsLayout::OnTerminationCriterionChanged(TSharedPtr NewValue, ESelectInfo::Type SelectInfo) { const EStaticMeshReductionTerimationCriterion TerminationCriterion = (EStaticMeshReductionTerimationCriterion)TerminationOptions.Find(NewValue); if (ReductionSettings.TerminationCriterion != TerminationCriterion) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.ReductionSettings"), TEXT("TerminationCriterion"), *NewValue.Get()); } ReductionSettings.TerminationCriterion = TerminationCriterion; } } TOptional FMeshReductionSettingsLayout::GetBaseLODIndex() const { return ReductionSettings.BaseLODModel; } void FMeshReductionSettingsLayout::SetBaseLODIndex(int32 NewLODBaseIndex) { if (NewLODBaseIndex <= CurrentLODIndex) { ReductionSettings.BaseLODModel = NewLODBaseIndex; } } FMeshSectionSettingsLayout::~FMeshSectionSettingsLayout() { } UStaticMesh& FMeshSectionSettingsLayout::GetStaticMesh() const { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); return *StaticMesh; } void FMeshSectionSettingsLayout::AddToCategory( IDetailCategoryBuilder& CategoryBuilder ) { FSectionListDelegates SectionListDelegates; SectionListDelegates.OnGetSections.BindSP(this, &FMeshSectionSettingsLayout::OnGetSectionsForView, LODIndex); SectionListDelegates.OnSectionChanged.BindSP(this, &FMeshSectionSettingsLayout::OnSectionChanged); SectionListDelegates.OnGenerateCustomNameWidgets.BindSP(this, &FMeshSectionSettingsLayout::OnGenerateCustomNameWidgetsForSection); SectionListDelegates.OnGenerateCustomSectionWidgets.BindSP(this, &FMeshSectionSettingsLayout::OnGenerateCustomSectionWidgetsForSection); SectionListDelegates.OnCopySectionList.BindSP(this, &FMeshSectionSettingsLayout::OnCopySectionList, LODIndex); SectionListDelegates.OnCanCopySectionList.BindSP(this, &FMeshSectionSettingsLayout::OnCanCopySectionList, LODIndex); SectionListDelegates.OnPasteSectionList.BindSP(this, &FMeshSectionSettingsLayout::OnPasteSectionList, LODIndex); SectionListDelegates.OnCopySectionItem.BindSP(this, &FMeshSectionSettingsLayout::OnCopySectionItem); SectionListDelegates.OnCanCopySectionItem.BindSP(this, &FMeshSectionSettingsLayout::OnCanCopySectionItem); SectionListDelegates.OnPasteSectionItem.BindSP(this, &FMeshSectionSettingsLayout::OnPasteSectionItem); //We need a valid name if we want the section expand state to be saved FName StaticMeshSectionListName = FName(*(FString(TEXT("StaticMeshSectionListNameLOD_")) + FString::FromInt(LODIndex))); CategoryBuilder.AddCustomBuilder(MakeShareable(new FSectionList(CategoryBuilder.GetParentLayout(), SectionListDelegates, true, 48, LODIndex, StaticMeshSectionListName))); StaticMeshEditor.RegisterOnSelectedLODChanged(FOnSelectedLODChanged::CreateSP(this, &FMeshSectionSettingsLayout::UpdateLODCategoryVisibility), false); } void FMeshSectionSettingsLayout::OnCopySectionList(int32 CurrentLODIndex) { TSharedRef RootJsonObject = MakeShareable(new FJsonObject()); UStaticMesh& StaticMesh = GetStaticMesh(); FStaticMeshRenderData* RenderData = StaticMesh.GetRenderData(); if (RenderData != nullptr && RenderData->LODResources.IsValidIndex(CurrentLODIndex)) { FStaticMeshLODResources& LOD = RenderData->LODResources[CurrentLODIndex]; for (int32 SectionIndex = 0; SectionIndex < LOD.Sections.Num(); ++SectionIndex) { const FStaticMeshSection& Section = LOD.Sections[SectionIndex]; TSharedPtr JSonSection = MakeShareable(new FJsonObject); JSonSection->SetNumberField(TEXT("MaterialIndex"), Section.MaterialIndex); JSonSection->SetBoolField(TEXT("EnableCollision"), Section.bEnableCollision); JSonSection->SetBoolField(TEXT("CastShadow"), Section.bCastShadow); RootJsonObject->SetObjectField(FString::Printf(TEXT("Section_%d"), SectionIndex), JSonSection); } } typedef TJsonWriter> FStringWriter; typedef TJsonWriterFactory> FStringWriterFactory; FString CopyStr; TSharedRef Writer = FStringWriterFactory::Create(&CopyStr); FJsonSerializer::Serialize(RootJsonObject, Writer); if (!CopyStr.IsEmpty()) { FPlatformApplicationMisc::ClipboardCopy(*CopyStr); } } bool FMeshSectionSettingsLayout::OnCanCopySectionList(int32 CurrentLODIndex) const { UStaticMesh& StaticMesh = GetStaticMesh(); FStaticMeshRenderData* RenderData = StaticMesh.GetRenderData(); if (RenderData != nullptr && RenderData->LODResources.IsValidIndex(CurrentLODIndex)) { FStaticMeshLODResources& LOD = RenderData->LODResources[CurrentLODIndex]; return LOD.Sections.Num() > 0; } return false; } void FMeshSectionSettingsLayout::OnPasteSectionList(int32 CurrentLODIndex) { FString PastedText; FPlatformApplicationMisc::ClipboardPaste(PastedText); TSharedPtr RootJsonObject; TSharedRef> Reader = TJsonReaderFactory<>::Create(PastedText); bool bResult = FJsonSerializer::Deserialize(Reader, RootJsonObject); if (RootJsonObject.IsValid()) { UStaticMesh& StaticMesh = GetStaticMesh(); FStaticMeshRenderData* RenderData = StaticMesh.GetRenderData(); if (RenderData != nullptr && RenderData->LODResources.IsValidIndex(CurrentLODIndex)) { FProperty* Property = UStaticMesh::StaticClass()->FindPropertyByName(UStaticMesh::GetSectionInfoMapName()); GetStaticMesh().PreEditChange(Property); FScopedTransaction Transaction(LOCTEXT("StaticMeshToolChangedPasteSectionList", "Staticmesh editor: Pasted section list")); GetStaticMesh().Modify(); FStaticMeshLODResources& LOD = RenderData->LODResources[CurrentLODIndex]; for (int32 SectionIndex = 0; SectionIndex < LOD.Sections.Num(); ++SectionIndex) { FStaticMeshSection& Section = LOD.Sections[SectionIndex]; const TSharedPtr* JSonSection = nullptr; if (RootJsonObject->TryGetObjectField(FString::Printf(TEXT("Section_%d"), SectionIndex), JSonSection)) { (*JSonSection)->TryGetNumberField(TEXT("MaterialIndex"), Section.MaterialIndex); (*JSonSection)->TryGetBoolField(TEXT("EnableCollision"), Section.bEnableCollision); (*JSonSection)->TryGetBoolField(TEXT("CastShadow"), Section.bCastShadow); // Update the section info FMeshSectionInfo Info = StaticMesh.GetSectionInfoMap().Get(LODIndex, SectionIndex); Info.MaterialIndex = Section.MaterialIndex; Info.bCastShadow = Section.bCastShadow; Info.bEnableCollision = Section.bEnableCollision; StaticMesh.GetSectionInfoMap().Set(LODIndex, SectionIndex, Info); } } CallPostEditChange(Property); } } } void FMeshSectionSettingsLayout::OnCopySectionItem(int32 CurrentLODIndex, int32 SectionIndex) { TSharedRef RootJsonObject = MakeShareable(new FJsonObject()); UStaticMesh& StaticMesh = GetStaticMesh(); FStaticMeshRenderData* RenderData = StaticMesh.GetRenderData(); if (RenderData != nullptr && RenderData->LODResources.IsValidIndex(CurrentLODIndex)) { FStaticMeshLODResources& LOD = RenderData->LODResources[CurrentLODIndex]; if (LOD.Sections.IsValidIndex(SectionIndex)) { const FStaticMeshSection& Section = LOD.Sections[SectionIndex]; RootJsonObject->SetNumberField(TEXT("MaterialIndex"), Section.MaterialIndex); RootJsonObject->SetBoolField(TEXT("EnableCollision"), Section.bEnableCollision); RootJsonObject->SetBoolField(TEXT("CastShadow"), Section.bCastShadow); } } typedef TJsonWriter> FStringWriter; typedef TJsonWriterFactory> FStringWriterFactory; FString CopyStr; TSharedRef Writer = FStringWriterFactory::Create(&CopyStr); FJsonSerializer::Serialize(RootJsonObject, Writer); if (!CopyStr.IsEmpty()) { FPlatformApplicationMisc::ClipboardCopy(*CopyStr); } } bool FMeshSectionSettingsLayout::OnCanCopySectionItem(int32 CurrentLODIndex, int32 SectionIndex) const { UStaticMesh& StaticMesh = GetStaticMesh(); FStaticMeshRenderData* RenderData = StaticMesh.GetRenderData(); if (RenderData != nullptr && RenderData->LODResources.IsValidIndex(CurrentLODIndex)) { FStaticMeshLODResources& LOD = RenderData->LODResources[CurrentLODIndex]; return LOD.Sections.IsValidIndex(SectionIndex); } return false; } void FMeshSectionSettingsLayout::OnPasteSectionItem(int32 CurrentLODIndex, int32 SectionIndex) { FString PastedText; FPlatformApplicationMisc::ClipboardPaste(PastedText); TSharedPtr RootJsonObject; TSharedRef> Reader = TJsonReaderFactory<>::Create(PastedText); bool bResult = FJsonSerializer::Deserialize(Reader, RootJsonObject); if (RootJsonObject.IsValid()) { UStaticMesh& StaticMesh = GetStaticMesh(); FStaticMeshRenderData* RenderData = StaticMesh.GetRenderData(); if (RenderData != nullptr && RenderData->LODResources.IsValidIndex(CurrentLODIndex)) { FProperty* Property = UStaticMesh::StaticClass()->FindPropertyByName(UStaticMesh::GetSectionInfoMapName()); GetStaticMesh().PreEditChange(Property); FScopedTransaction Transaction(LOCTEXT("StaticMeshToolChangedPasteSectionItem", "Staticmesh editor: Pasted section item")); GetStaticMesh().Modify(); FStaticMeshLODResources& LOD = RenderData->LODResources[CurrentLODIndex]; if (LOD.Sections.IsValidIndex(SectionIndex)) { FStaticMeshSection& Section = LOD.Sections[SectionIndex]; RootJsonObject->TryGetNumberField(TEXT("MaterialIndex"), Section.MaterialIndex); RootJsonObject->TryGetBoolField(TEXT("EnableCollision"), Section.bEnableCollision); RootJsonObject->TryGetBoolField(TEXT("CastShadow"), Section.bCastShadow); // Update the section info FMeshSectionInfo Info = StaticMesh.GetSectionInfoMap().Get(LODIndex, SectionIndex); Info.MaterialIndex = Section.MaterialIndex; Info.bCastShadow = Section.bCastShadow; Info.bEnableCollision = Section.bEnableCollision; StaticMesh.GetSectionInfoMap().Set(LODIndex, SectionIndex, Info); } CallPostEditChange(Property); } } } void FMeshSectionSettingsLayout::OnGetSectionsForView(ISectionListBuilder& OutSections, int32 ForLODIndex) { check(LODIndex == ForLODIndex); UStaticMesh& StaticMesh = GetStaticMesh(); FStaticMeshRenderData* RenderData = StaticMesh.GetRenderData(); if (RenderData && RenderData->LODResources.IsValidIndex(LODIndex)) { FStaticMeshLODResources& LOD = RenderData->LODResources[LODIndex]; int32 NumSections = LOD.Sections.Num(); for (int32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) { FMeshSectionInfo Info = StaticMesh.GetSectionInfoMap().Get(LODIndex, SectionIndex); int32 MaterialIndex = Info.MaterialIndex; if (StaticMesh.GetStaticMaterials().IsValidIndex(MaterialIndex)) { FName CurrentSectionMaterialSlotName = StaticMesh.GetStaticMaterials()[MaterialIndex].MaterialSlotName; FName CurrentSectionOriginalImportedMaterialName = StaticMesh.GetStaticMaterials()[MaterialIndex].ImportedMaterialSlotName; TMap AvailableSectionName; int32 CurrentIterMaterialIndex = 0; for (const FStaticMaterial &SkeletalMaterial : StaticMesh.GetStaticMaterials()) { if (MaterialIndex != CurrentIterMaterialIndex) AvailableSectionName.Add(CurrentIterMaterialIndex, SkeletalMaterial.MaterialSlotName); CurrentIterMaterialIndex++; } UMaterialInterface* SectionMaterial = StaticMesh.GetStaticMaterials()[MaterialIndex].MaterialInterface; if (SectionMaterial == NULL) { SectionMaterial = UMaterial::GetDefaultMaterial(MD_Surface); } //TODO: Need to know if a section material slot assignment was change from the default value (implemented in skeletalmesh editor) OutSections.AddSection(LODIndex, SectionIndex, CurrentSectionMaterialSlotName, MaterialIndex, CurrentSectionOriginalImportedMaterialName, AvailableSectionName, StaticMesh.GetStaticMaterials()[MaterialIndex].MaterialInterface, false, false, MaterialIndex); } } } } void FMeshSectionSettingsLayout::OnSectionChanged(int32 ForLODIndex, int32 SectionIndex, int32 NewMaterialSlotIndex, FName NewMaterialSlotName) { check(LODIndex == ForLODIndex); UStaticMesh& StaticMesh = GetStaticMesh(); check(StaticMesh.GetStaticMaterials().IsValidIndex(NewMaterialSlotIndex)); int32 NewStaticMaterialIndex = INDEX_NONE; for (int StaticMaterialIndex = 0; StaticMaterialIndex < StaticMesh.GetStaticMaterials().Num(); ++StaticMaterialIndex) { if (NewMaterialSlotIndex == StaticMaterialIndex && StaticMesh.GetStaticMaterials()[StaticMaterialIndex].MaterialSlotName == NewMaterialSlotName) { NewStaticMaterialIndex = StaticMaterialIndex; break; } } check(NewStaticMaterialIndex != INDEX_NONE); check(StaticMesh.GetRenderData()); FStaticMeshRenderData* RenderData = StaticMesh.GetRenderData(); if (RenderData && RenderData->LODResources.IsValidIndex(LODIndex)) { bool bRefreshAll = false; FStaticMeshLODResources& LOD = RenderData->LODResources[LODIndex]; if (LOD.Sections.IsValidIndex(SectionIndex)) { FProperty* Property = UStaticMesh::StaticClass()->FindPropertyByName(UStaticMesh::GetSectionInfoMapName()); GetStaticMesh().PreEditChange(Property); FScopedTransaction Transaction(LOCTEXT("StaticMeshOnSectionChangedTransaction", "Staticmesh editor: Section material slot changed")); GetStaticMesh().Modify(); FMeshSectionInfo Info = StaticMesh.GetSectionInfoMap().Get(LODIndex, SectionIndex); int32 CancelOldValue = Info.MaterialIndex; Info.MaterialIndex = NewStaticMaterialIndex; StaticMesh.GetSectionInfoMap().Set(LODIndex, SectionIndex, Info); CallPostEditChange(); } if (bRefreshAll) { StaticMeshEditor.RefreshTool(); } } } TSharedRef FMeshSectionSettingsLayout::OnGenerateCustomNameWidgetsForSection(int32 ForLODIndex, int32 SectionIndex) { return SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ SNew(SCheckBox) .IsChecked(this, &FMeshSectionSettingsLayout::IsSectionHighlighted, SectionIndex) .OnCheckStateChanged(this, &FMeshSectionSettingsLayout::OnSectionHighlightedChanged, SectionIndex) .ToolTipText(LOCTEXT("Highlight_ToolTip", "Highlights this section in the viewport")) [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .ColorAndOpacity( FLinearColor( 0.4f, 0.4f, 0.4f, 1.0f) ) .Text(LOCTEXT("Highlight", "Highlight")) ] ] + SVerticalBox::Slot() .AutoHeight() .Padding(0, 2, 0, 0) [ SNew(SCheckBox) .IsChecked(this, &FMeshSectionSettingsLayout::IsSectionIsolatedEnabled, SectionIndex) .OnCheckStateChanged(this, &FMeshSectionSettingsLayout::OnSectionIsolatedChanged, SectionIndex) .ToolTipText(LOCTEXT("Isolate_ToolTip", "Isolates this section in the viewport")) [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .ColorAndOpacity(FLinearColor(0.4f, 0.4f, 0.4f, 1.0f)) .Text(LOCTEXT("Isolate", "Isolate")) ] ]; } TSharedRef FMeshSectionSettingsLayout::OnGenerateCustomSectionWidgetsForSection(int32 ForLODIndex, int32 SectionIndex) { return SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(2, 0, 2, 0) [ SNew(SCheckBox) .IsChecked(this, &FMeshSectionSettingsLayout::DoesSectionCastShadow, SectionIndex) .OnCheckStateChanged(this, &FMeshSectionSettingsLayout::OnSectionCastShadowChanged, SectionIndex) [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("CastShadow", "Cast Shadow")) ] ] + SHorizontalBox::Slot() .AutoWidth() .Padding(2,0,2,0) [ SNew(SCheckBox) .IsEnabled(this, &FMeshSectionSettingsLayout::SectionCollisionEnabled) .ToolTipText(this, &FMeshSectionSettingsLayout::GetCollisionEnabledToolTip) .IsChecked(this, &FMeshSectionSettingsLayout::DoesSectionCollide, SectionIndex) .OnCheckStateChanged(this, &FMeshSectionSettingsLayout::OnSectionCollisionChanged, SectionIndex) [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("EnableCollision", "Enable Collision")) ] ] + SHorizontalBox::Slot() .AutoWidth() .Padding(2, 0, 2, 0) [ SNew(SCheckBox) .IsChecked(this, &FMeshSectionSettingsLayout::IsSectionVisibleInRayTracing, SectionIndex) .OnCheckStateChanged(this, &FMeshSectionSettingsLayout::OnSectionVisibleInRayTracingChanged, SectionIndex) [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("VisibleInRayTracing", "Visible In Ray Tracing")) ] ] + SHorizontalBox::Slot() .AutoWidth() .Padding(2, 0, 2, 0) [ SNew(SCheckBox) .IsChecked(this, &FMeshSectionSettingsLayout::DoesSectionAffectDistanceFieldLighting, SectionIndex) .OnCheckStateChanged(this, &FMeshSectionSettingsLayout::OnSectionAffectDistanceFieldLightingChanged, SectionIndex) [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("AffectDistanceFieldLighting", "Affect Distance Field Lighting")) ] ] + SHorizontalBox::Slot() .AutoWidth() .Padding(2, 0, 2, 0) [ SNew(SCheckBox) .IsChecked(this, &FMeshSectionSettingsLayout::IsSectionOpaque, SectionIndex) .OnCheckStateChanged(this, &FMeshSectionSettingsLayout::OnSectionForceOpaqueFlagChanged, SectionIndex) [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("ForceOpaque", "Force Opaque")) ] ]; } ECheckBoxState FMeshSectionSettingsLayout::IsSectionVisibleInRayTracing(int32 SectionIndex) const { UStaticMesh& StaticMesh = GetStaticMesh(); FMeshSectionInfo Info = StaticMesh.GetSectionInfoMap().Get(LODIndex, SectionIndex); return Info.bVisibleInRayTracing ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } void FMeshSectionSettingsLayout::OnSectionVisibleInRayTracingChanged(ECheckBoxState NewState, int32 SectionIndex) { UStaticMesh& StaticMesh = GetStaticMesh(); FText TransactionTest = LOCTEXT("StaticMeshEditorSetVisibleInRayTracingSectionFlag", "Staticmesh editor: Set VisibleInRayTracing For section, the section will be visible in ray tracing effects"); if (NewState == ECheckBoxState::Unchecked) { TransactionTest = LOCTEXT("StaticMeshEditorClearVisibleInRayTracingSectionFlag", "Staticmesh editor: Clear VisibleInRayTracing For section"); } FScopedTransaction Transaction(TransactionTest); FProperty* Property = UStaticMesh::StaticClass()->FindPropertyByName(UStaticMesh::GetSectionInfoMapName()); StaticMesh.PreEditChange(Property); StaticMesh.Modify(); FMeshSectionInfo Info = StaticMesh.GetSectionInfoMap().Get(LODIndex, SectionIndex); Info.bVisibleInRayTracing = (NewState == ECheckBoxState::Checked) ? true : false; StaticMesh.GetSectionInfoMap().Set(LODIndex, SectionIndex, Info); CallPostEditChange(); } ECheckBoxState FMeshSectionSettingsLayout::DoesSectionAffectDistanceFieldLighting(int32 SectionIndex) const { UStaticMesh& StaticMesh = GetStaticMesh(); FMeshSectionInfo Info = StaticMesh.GetSectionInfoMap().Get(LODIndex, SectionIndex); return Info.bAffectDistanceFieldLighting ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } void FMeshSectionSettingsLayout::OnSectionAffectDistanceFieldLightingChanged(ECheckBoxState NewState, int32 SectionIndex) { UStaticMesh& StaticMesh = GetStaticMesh(); FText TransactionTest = LOCTEXT("StaticMeshEditorSetAffectDistanceFieldLightingSectionFlag", "Staticmesh editor: Set AffectDistanceFieldLighting For section, the section will be affect distance field lighting"); if (NewState == ECheckBoxState::Unchecked) { TransactionTest = LOCTEXT("StaticMeshEditorClearAffectDistanceFieldLightingSectionFlag", "Staticmesh editor: Clear AffectDistanceFieldLighting For section"); } FScopedTransaction Transaction(TransactionTest); FProperty* Property = UStaticMesh::StaticClass()->FindPropertyByName(UStaticMesh::GetSectionInfoMapName()); StaticMesh.PreEditChange(Property); StaticMesh.Modify(); FMeshSectionInfo Info = StaticMesh.GetSectionInfoMap().Get(LODIndex, SectionIndex); Info.bAffectDistanceFieldLighting = (NewState == ECheckBoxState::Checked) ? true : false; StaticMesh.GetSectionInfoMap().Set(LODIndex, SectionIndex, Info); CallPostEditChange(); } ECheckBoxState FMeshSectionSettingsLayout::IsSectionOpaque(int32 SectionIndex) const { UStaticMesh& StaticMesh = GetStaticMesh(); FMeshSectionInfo Info = StaticMesh.GetSectionInfoMap().Get(LODIndex, SectionIndex); return Info.bForceOpaque ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } void FMeshSectionSettingsLayout::OnSectionForceOpaqueFlagChanged( ECheckBoxState NewState, int32 SectionIndex ) { UStaticMesh& StaticMesh = GetStaticMesh(); FText TransactionTest = LOCTEXT("StaticMeshEditorSetForceOpaqueSectionFlag", "Staticmesh editor: Set Force Opaque For section, the section will be considered opaque in ray tracing effects"); if (NewState == ECheckBoxState::Unchecked) { TransactionTest = LOCTEXT("StaticMeshEditorClearForceOpaqueSectionFlag", "Staticmesh editor: Clear Force Opaque For section"); } FScopedTransaction Transaction(TransactionTest); FProperty* Property = UStaticMesh::StaticClass()->FindPropertyByName(UStaticMesh::GetSectionInfoMapName()); StaticMesh.PreEditChange(Property); StaticMesh.Modify(); FMeshSectionInfo Info = StaticMesh.GetSectionInfoMap().Get(LODIndex, SectionIndex); Info.bForceOpaque = (NewState == ECheckBoxState::Checked) ? true : false; StaticMesh.GetSectionInfoMap().Set(LODIndex, SectionIndex, Info); CallPostEditChange(); } ECheckBoxState FMeshSectionSettingsLayout::DoesSectionCastShadow(int32 SectionIndex) const { UStaticMesh& StaticMesh = GetStaticMesh(); FMeshSectionInfo Info = StaticMesh.GetSectionInfoMap().Get(LODIndex, SectionIndex); return Info.bCastShadow ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } void FMeshSectionSettingsLayout::OnSectionCastShadowChanged(ECheckBoxState NewState, int32 SectionIndex) { UStaticMesh& StaticMesh = GetStaticMesh(); FText TransactionTest = LOCTEXT("StaticMeshEditorSetShadowCastingSectionFlag", "Staticmesh editor: Set Shadow Casting For section"); if (NewState == ECheckBoxState::Unchecked) { TransactionTest = LOCTEXT("StaticMeshEditorClearShadowCastingSectionFlag", "Staticmesh editor: Clear Shadow Casting For section"); } FScopedTransaction Transaction(TransactionTest); FProperty* Property = UStaticMesh::StaticClass()->FindPropertyByName(UStaticMesh::GetSectionInfoMapName()); StaticMesh.PreEditChange(Property); StaticMesh.Modify(); FMeshSectionInfo Info = StaticMesh.GetSectionInfoMap().Get(LODIndex, SectionIndex); Info.bCastShadow = (NewState == ECheckBoxState::Checked) ? true : false; StaticMesh.GetSectionInfoMap().Set(LODIndex, SectionIndex, Info); CallPostEditChange(); } bool FMeshSectionSettingsLayout::SectionCollisionEnabled() const { UStaticMesh& StaticMesh = GetStaticMesh(); // Only enable 'Enable Collision' check box if this LOD is used for collision return (StaticMesh.LODForCollision == LODIndex); } FText FMeshSectionSettingsLayout::GetCollisionEnabledToolTip() const { UStaticMesh& StaticMesh = GetStaticMesh(); // If using a different LOD for collision, disable the check box if (StaticMesh.LODForCollision != LODIndex) { return LOCTEXT("EnableCollisionToolTipDisabled", "This LOD is not used for collision, see the LODForCollision setting."); } // This LOD is used for collision, give info on what flag does else { return LOCTEXT("EnableCollisionToolTipEnabled", "Controls whether this section ever has per-poly collision. Disabling this where possible will lower memory usage for this mesh."); } } ECheckBoxState FMeshSectionSettingsLayout::DoesSectionCollide(int32 SectionIndex) const { UStaticMesh& StaticMesh = GetStaticMesh(); FMeshSectionInfo Info = StaticMesh.GetSectionInfoMap().Get(LODIndex, SectionIndex); return Info.bEnableCollision ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } void FMeshSectionSettingsLayout::OnSectionCollisionChanged(ECheckBoxState NewState, int32 SectionIndex) { UStaticMesh& StaticMesh = GetStaticMesh(); FText TransactionTest = LOCTEXT("StaticMeshEditorSetCollisionSectionFlag", "Staticmesh editor: Set Collision For section"); if (NewState == ECheckBoxState::Unchecked) { TransactionTest = LOCTEXT("StaticMeshEditorClearCollisionSectionFlag", "Staticmesh editor: Clear Collision For section"); } FScopedTransaction Transaction(TransactionTest); FProperty* Property = UStaticMesh::StaticClass()->FindPropertyByName(UStaticMesh::GetSectionInfoMapName()); StaticMesh.PreEditChange(Property); StaticMesh.Modify(); FMeshSectionInfo Info = StaticMesh.GetSectionInfoMap().Get(LODIndex, SectionIndex); Info.bEnableCollision = (NewState == ECheckBoxState::Checked) ? true : false; StaticMesh.GetSectionInfoMap().Set(LODIndex, SectionIndex, Info); CallPostEditChange(); } ECheckBoxState FMeshSectionSettingsLayout::IsSectionHighlighted(int32 SectionIndex) const { ECheckBoxState State = ECheckBoxState::Unchecked; UStaticMeshComponent* Component = StaticMeshEditor.GetStaticMeshComponent(); if (Component) { State = Component->SelectedEditorSection == SectionIndex ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } return State; } void FMeshSectionSettingsLayout::OnSectionHighlightedChanged(ECheckBoxState NewState, int32 SectionIndex) { UStaticMeshComponent* Component = StaticMeshEditor.GetStaticMeshComponent(); if (Component) { if (NewState == ECheckBoxState::Checked) { Component->SelectedEditorSection = SectionIndex; if (Component->SectionIndexPreview != SectionIndex) { // Unhide all mesh sections Component->SetSectionPreview(INDEX_NONE); } Component->SetMaterialPreview(INDEX_NONE); Component->SelectedEditorMaterial = INDEX_NONE; } else if (NewState == ECheckBoxState::Unchecked) { Component->SelectedEditorSection = INDEX_NONE; } Component->MarkRenderStateDirty(); StaticMeshEditor.RefreshViewport(); } } ECheckBoxState FMeshSectionSettingsLayout::IsSectionIsolatedEnabled(int32 SectionIndex) const { ECheckBoxState State = ECheckBoxState::Unchecked; const UStaticMeshComponent* Component = StaticMeshEditor.GetStaticMeshComponent(); if (Component) { State = Component->SectionIndexPreview == SectionIndex ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } return State; } void FMeshSectionSettingsLayout::OnSectionIsolatedChanged(ECheckBoxState NewState, int32 SectionIndex) { UStaticMeshComponent* Component = StaticMeshEditor.GetStaticMeshComponent(); if (Component) { if (NewState == ECheckBoxState::Checked) { Component->SetSectionPreview(SectionIndex); if (Component->SelectedEditorSection != SectionIndex) { Component->SelectedEditorSection = INDEX_NONE; } Component->SetMaterialPreview(INDEX_NONE); Component->SelectedEditorMaterial = INDEX_NONE; } else if (NewState == ECheckBoxState::Unchecked) { Component->SetSectionPreview(INDEX_NONE); } Component->MarkRenderStateDirty(); StaticMeshEditor.RefreshViewport(); } } void FMeshSectionSettingsLayout::CallPostEditChange(FProperty* PropertyChanged/*=nullptr*/) { UStaticMesh& StaticMesh = GetStaticMesh(); if( PropertyChanged ) { FPropertyChangedEvent PropertyUpdateStruct(PropertyChanged); StaticMesh.PostEditChangeProperty(PropertyUpdateStruct); } else { StaticMesh.Modify(); StaticMesh.PostEditChange(); } if(StaticMesh.GetBodySetup()) { StaticMesh.GetBodySetup()->CreatePhysicsMeshes(); } StaticMeshEditor.RefreshViewport(); } void FMeshSectionSettingsLayout::SetCurrentLOD(int32 NewLodIndex) { if (StaticMeshEditor.GetStaticMeshComponent() == nullptr || LodCategoriesPtr == nullptr) { return; } int32 CurrentDisplayLOD = StaticMeshEditor.GetStaticMeshComponent()->ForcedLodModel; int32 RealCurrentDisplayLOD = CurrentDisplayLOD == 0 ? 0 : CurrentDisplayLOD - 1; int32 RealNewLOD = NewLodIndex == 0 ? 0 : NewLodIndex - 1; if (CurrentDisplayLOD == NewLodIndex || !LodCategoriesPtr->IsValidIndex(RealCurrentDisplayLOD) || !LodCategoriesPtr->IsValidIndex(RealNewLOD)) { return; } StaticMeshEditor.GetStaticMeshComponent()->SetForcedLodModel(NewLodIndex); //Reset the preview section since we do not edit the same LOD StaticMeshEditor.GetStaticMeshComponent()->SetSectionPreview(INDEX_NONE); StaticMeshEditor.GetStaticMeshComponent()->SelectedEditorSection = INDEX_NONE; } void FMeshSectionSettingsLayout::UpdateLODCategoryVisibility() { if (StaticMeshEditor.GetCustomData(CustomDataKey_LODEditMode) > 0) { //Do not change the Category visibility if we are in custom mode return; } bool bAutoLod = false; if (StaticMeshEditor.GetStaticMeshComponent() != nullptr) { bAutoLod = StaticMeshEditor.GetStaticMeshComponent()->ForcedLodModel == 0; } int32 CurrentDisplayLOD = bAutoLod ? 0 : StaticMeshEditor.GetStaticMeshComponent()->ForcedLodModel - 1; if (LodCategoriesPtr != nullptr && LodCategoriesPtr->IsValidIndex(CurrentDisplayLOD) && StaticMeshEditor.GetStaticMesh()) { int32 StaticMeshLodNumber = StaticMeshEditor.GetStaticMesh()->GetNumLODs(); for (int32 LodCategoryIndex = 0; LodCategoryIndex < StaticMeshLodNumber; ++LodCategoryIndex) { if (!LodCategoriesPtr->IsValidIndex(LodCategoryIndex)) { break; } (*LodCategoriesPtr)[LodCategoryIndex]->SetCategoryVisibility(CurrentDisplayLOD == LodCategoryIndex); } //Reset the preview section since we do not edit the same LOD StaticMeshEditor.GetStaticMeshComponent()->SetSectionPreview(INDEX_NONE); StaticMeshEditor.GetStaticMeshComponent()->SelectedEditorSection = INDEX_NONE; } } ////////////////////////////////////////////////////////////////////////// // FMeshMaterialLayout ////////////////////////////////////////////////////////////////////////// FMeshMaterialsLayout::~FMeshMaterialsLayout() { } UStaticMesh& FMeshMaterialsLayout::GetStaticMesh() const { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); return *StaticMesh; } void FMeshMaterialsLayout::AddToCategory(IDetailCategoryBuilder& CategoryBuilder, const TArray& AssetDataArray) { CategoryBuilder.AddCustomRow(LOCTEXT("AddLODLevelCategories_MaterialArrayOperationAdd", "Add Material Slot")) .RowTag("MaterialSlots") .CopyAction(FUIAction(FExecuteAction::CreateSP(this, &FMeshMaterialsLayout::OnCopyMaterialList), FCanExecuteAction::CreateSP(this, &FMeshMaterialsLayout::OnCanCopyMaterialList))) .PasteAction(FUIAction(FExecuteAction::CreateSP(this, &FMeshMaterialsLayout::OnPasteMaterialList))) .NameContent() .HAlign(HAlign_Left) .VAlign(VAlign_Center) [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("AddLODLevelCategories_MaterialArrayOperations", "Material Slots")) ] .ValueContent() .HAlign(HAlign_Left) .VAlign(VAlign_Center) [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.0f) .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(this, &FMeshMaterialsLayout::GetMaterialArrayText) .Font(IDetailLayoutBuilder::GetDetailFont()) ] + SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Center) .VAlign(VAlign_Center) .Padding(2.0f, 1.0f) [ SNew(SButton) .ButtonStyle(FAppStyle::Get(), "HoverHintOnly") .Text(LOCTEXT("AddLODLevelCategories_MaterialArrayOpAdd", "Add Material Slot")) .ToolTipText(LOCTEXT("AddLODLevelCategories_MaterialArrayOpAdd_Tooltip", "Add Material Slot at the end of the Material slot array. Those Material slots can be used to override a LODs section, (not the base LOD)")) .ContentPadding(4.0f) .ForegroundColor(FSlateColor::UseForeground()) .OnClicked(this, &FMeshMaterialsLayout::AddMaterialSlot) .IsEnabled(true) .IsFocusable(false) [ SNew(SImage) .Image(FAppStyle::GetBrush("Icons.PlusCircle")) .ColorAndOpacity(FSlateColor::UseForeground()) ] ] ] ]; FMaterialListDelegates MaterialListDelegates; MaterialListDelegates.OnGetMaterials.BindSP(this, &FMeshMaterialsLayout::GetMaterials); MaterialListDelegates.OnMaterialChanged.BindSP(this, &FMeshMaterialsLayout::OnMaterialChanged); MaterialListDelegates.OnGenerateCustomMaterialWidgets.BindSP(this, &FMeshMaterialsLayout::OnGenerateWidgetsForMaterial); MaterialListDelegates.OnGenerateCustomNameWidgets.BindSP(this, &FMeshMaterialsLayout::OnGenerateNameWidgetsForMaterial); MaterialListDelegates.OnGenerateExtraBottomWidget.BindSP(this, &FMeshMaterialsLayout::OnGenerateMaterialListExtraBottomWidget); MaterialListDelegates.OnMaterialListDirty.BindSP(this, &FMeshMaterialsLayout::OnMaterialListDirty); MaterialListDelegates.OnResetMaterialToDefaultClicked.BindSP(this, &FMeshMaterialsLayout::OnResetMaterialToDefaultClicked); MaterialListDelegates.OnCopyMaterialItem.BindSP(this, &FMeshMaterialsLayout::OnCopyMaterialItem); MaterialListDelegates.OnCanCopyMaterialItem.BindSP(this, &FMeshMaterialsLayout::OnCanCopyMaterialItem); MaterialListDelegates.OnPasteMaterialItem.BindSP(this, &FMeshMaterialsLayout::OnPasteMaterialItem); CategoryBuilder.AddCustomBuilder(MakeShareable(new FMaterialList(CategoryBuilder.GetParentLayout(), MaterialListDelegates, AssetDataArray, false, true))); } void FMeshMaterialsLayout::OnCopyMaterialList() { FProperty* Property = UStaticMesh::StaticClass()->FindPropertyByName(UStaticMesh::GetStaticMaterialsName()); check(Property != nullptr); auto JsonValue = FJsonObjectConverter::UPropertyToJsonValue(Property, &GetStaticMesh().GetStaticMaterials(), 0, 0); typedef TJsonWriter> FStringWriter; typedef TJsonWriterFactory> FStringWriterFactory; FString CopyStr; TSharedRef Writer = FStringWriterFactory::Create(&CopyStr); FJsonSerializer::Serialize(JsonValue.ToSharedRef(), TEXT(""), Writer); if (!CopyStr.IsEmpty()) { FPlatformApplicationMisc::ClipboardCopy(*CopyStr); } } bool FMeshMaterialsLayout::OnCanCopyMaterialList() const { return GetStaticMesh().GetStaticMaterials().Num() > 0; } void FMeshMaterialsLayout::OnPasteMaterialList() { FString PastedText; FPlatformApplicationMisc::ClipboardPaste(PastedText); TSharedPtr RootJsonValue; TSharedRef> Reader = TJsonReaderFactory<>::Create(PastedText); FJsonSerializer::Deserialize(Reader, RootJsonValue); if (RootJsonValue.IsValid()) { FProperty* Property = UStaticMesh::StaticClass()->FindPropertyByName(UStaticMesh::GetStaticMaterialsName()); check(Property != nullptr); GetStaticMesh().PreEditChange(Property); FScopedTransaction Transaction(LOCTEXT("StaticMeshToolChangedPasteMaterialList", "Staticmesh editor: Pasted material list")); GetStaticMesh().Modify(); TArray TempMaterials; FJsonObjectConverter::JsonValueToUProperty(RootJsonValue, Property, &TempMaterials, 0, 0); //Do not change the number of material in the array for (int32 MaterialIndex = 0; MaterialIndex < TempMaterials.Num(); ++MaterialIndex) { if (GetStaticMesh().GetStaticMaterials().IsValidIndex(MaterialIndex)) { GetStaticMesh().GetStaticMaterials()[MaterialIndex].MaterialInterface = TempMaterials[MaterialIndex].MaterialInterface; GetStaticMesh().GetStaticMaterials()[MaterialIndex].OverlayMaterialInterface = TempMaterials[MaterialIndex].OverlayMaterialInterface; } } CallPostEditChange(Property); } } void FMeshMaterialsLayout::OnCopyMaterialItem(int32 CurrentSlot) { TSharedRef RootJsonObject = MakeShareable(new FJsonObject()); if (GetStaticMesh().GetStaticMaterials().IsValidIndex(CurrentSlot)) { const FStaticMaterial &Material = GetStaticMesh().GetStaticMaterials()[CurrentSlot]; FJsonObjectConverter::UStructToJsonObject(FStaticMaterial::StaticStruct(), &Material, RootJsonObject, 0, 0); } typedef TJsonWriter> FStringWriter; typedef TJsonWriterFactory> FStringWriterFactory; FString CopyStr; TSharedRef Writer = FStringWriterFactory::Create(&CopyStr); FJsonSerializer::Serialize(RootJsonObject, Writer); if (!CopyStr.IsEmpty()) { FPlatformApplicationMisc::ClipboardCopy(*CopyStr); } } bool FMeshMaterialsLayout::OnCanCopyMaterialItem(int32 CurrentSlot) const { return GetStaticMesh().GetStaticMaterials().IsValidIndex(CurrentSlot); } void FMeshMaterialsLayout::OnPasteMaterialItem(int32 CurrentSlot) { FString PastedText; FPlatformApplicationMisc::ClipboardPaste(PastedText); TSharedPtr RootJsonObject; TSharedRef> Reader = TJsonReaderFactory<>::Create(PastedText); FJsonSerializer::Deserialize(Reader, RootJsonObject); if (RootJsonObject.IsValid()) { FProperty* Property = UStaticMesh::StaticClass()->FindPropertyByName(UStaticMesh::GetStaticMaterialsName()); check(Property != nullptr); GetStaticMesh().PreEditChange(Property); FScopedTransaction Transaction(LOCTEXT("StaticMeshToolChangedPasteMaterialItem", "Staticmesh editor: Pasted material item")); GetStaticMesh().Modify(); if (GetStaticMesh().GetStaticMaterials().IsValidIndex(CurrentSlot)) { FStaticMaterial TmpStaticMaterial; FJsonObjectConverter::JsonObjectToUStruct(RootJsonObject.ToSharedRef(), FStaticMaterial::StaticStruct(), &TmpStaticMaterial, 0, 0); GetStaticMesh().GetStaticMaterials()[CurrentSlot].MaterialInterface = TmpStaticMaterial.MaterialInterface; GetStaticMesh().GetStaticMaterials()[CurrentSlot].OverlayMaterialInterface = TmpStaticMaterial.OverlayMaterialInterface; } CallPostEditChange(Property); } } FReply FMeshMaterialsLayout::AddMaterialSlot() { UStaticMesh& StaticMesh = GetStaticMesh(); FScopedTransaction Transaction(LOCTEXT("FMeshMaterialsLayout_AddMaterialSlot", "Staticmesh editor: Add material slot")); StaticMesh.Modify(); StaticMesh.GetStaticMaterials().Add(FStaticMaterial()); StaticMesh.PostEditChange(); return FReply::Handled(); } FText FMeshMaterialsLayout::GetMaterialArrayText() const { UStaticMesh& StaticMesh = GetStaticMesh(); FString MaterialArrayText = TEXT(" Material Slots"); int32 SlotNumber = 0; SlotNumber = StaticMesh.GetStaticMaterials().Num(); MaterialArrayText = FString::FromInt(SlotNumber) + MaterialArrayText; return FText::FromString(MaterialArrayText); } void FMeshMaterialsLayout::GetMaterials(IMaterialListBuilder& ListBuilder) { UStaticMesh& StaticMesh = GetStaticMesh(); for (int32 MaterialIndex = 0; MaterialIndex < StaticMesh.GetStaticMaterials().Num(); ++MaterialIndex) { UMaterialInterface* Material = StaticMesh.GetMaterial(MaterialIndex); if (Material == NULL) { Material = UMaterial::GetDefaultMaterial(MD_Surface); } ListBuilder.AddMaterial(MaterialIndex, Material, /*bCanBeReplaced=*/ true); } } void FMeshMaterialsLayout::OnMaterialChanged(UMaterialInterface* NewMaterial, UMaterialInterface* PrevMaterial, int32 MaterialIndex, bool bReplaceAll) { UStaticMesh& StaticMesh = GetStaticMesh(); StaticMesh.SetMaterial(MaterialIndex, NewMaterial); StaticMeshEditor.RefreshTool(); } TSharedRef FMeshMaterialsLayout::OnGenerateWidgetsForMaterial(UMaterialInterface* Material, int32 SlotIndex) { UStaticMesh& StaticMesh = GetStaticMesh(); bool bMaterialIsUsed = false; if(MaterialUsedMap.Contains(SlotIndex)) { bMaterialIsUsed = MaterialUsedMap.Find(SlotIndex)->Num() > 0; } return SNew(SMaterialSlotWidget, SlotIndex, bMaterialIsUsed) .MaterialName(this, &FMeshMaterialsLayout::GetMaterialNameText, SlotIndex) .OnMaterialNameCommitted(this, &FMeshMaterialsLayout::OnMaterialNameCommitted, SlotIndex) .CanDeleteMaterialSlot(this, &FMeshMaterialsLayout::CanDeleteMaterialSlot, SlotIndex) .OnDeleteMaterialSlot(this, &FMeshMaterialsLayout::OnDeleteMaterialSlot, SlotIndex) .ToolTipText(this, &FMeshMaterialsLayout::GetOriginalImportMaterialNameText, SlotIndex); #if 0 // HACK!!! Temporary disabled +SVerticalBox::Slot() .AutoHeight() .Padding(0,2,0,0) [ SNew(SCheckBox) .Visibility(this, &FMeshMaterialsLayout::GetOverrideUVDensityVisibililty) .IsChecked(this, &FMeshMaterialsLayout::IsUVDensityOverridden, SlotIndex) .OnCheckStateChanged(this, &FMeshMaterialsLayout::OnOverrideUVDensityChanged, SlotIndex) [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("OverrideUVDensity", "Override UV Density")) ] ] + GetUVDensitySlot(SlotIndex, 0) + GetUVDensitySlot(SlotIndex, 1) + GetUVDensitySlot(SlotIndex, 2) + GetUVDensitySlot(SlotIndex, 3); #endif } TSharedRef FMeshMaterialsLayout::OnGenerateNameWidgetsForMaterial(UMaterialInterface* Material, int32 SlotIndex) { return SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ SNew(SCheckBox) .IsChecked(this, &FMeshMaterialsLayout::IsMaterialHighlighted, SlotIndex) .OnCheckStateChanged(this, &FMeshMaterialsLayout::OnMaterialHighlightedChanged, SlotIndex) .ToolTipText(LOCTEXT("Highlight_CustomMaterialName_ToolTip", "Highlights this material in the viewport")) [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .ColorAndOpacity(FLinearColor(0.4f, 0.4f, 0.4f, 1.0f)) .Text(LOCTEXT("Highlight", "Highlight")) ] ] + SVerticalBox::Slot() .AutoHeight() .Padding(0, 2, 0, 0) [ SNew(SCheckBox) .IsChecked(this, &FMeshMaterialsLayout::IsMaterialIsolatedEnabled, SlotIndex) .OnCheckStateChanged(this, &FMeshMaterialsLayout::OnMaterialIsolatedChanged, SlotIndex) .ToolTipText(LOCTEXT("Isolate_CustomMaterialName_ToolTip", "Isolates this material in the viewport")) [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .ColorAndOpacity(FLinearColor(0.4f, 0.4f, 0.4f, 1.0f)) .Text(LOCTEXT("Isolate", "Isolate")) ] ]; } TSharedRef FMeshMaterialsLayout::OnGenerateMaterialListExtraBottomWidget(UMaterialInterface* Material, int32 SlotIndex) { UStaticMesh& StaticMesh = GetStaticMesh(); if (!StaticMesh.GetStaticMaterials().IsValidIndex(SlotIndex)) { return SNullWidget::NullWidget; } TSharedRef VerticalWidget = SNew(SVerticalBox); const FStaticMaterial& StaticlMaterial = StaticMesh.GetStaticMaterials()[SlotIndex]; if (GEnableStaticMeshMaterialSlotOverlayMaterialUI || StaticlMaterial.OverlayMaterialInterface) { VerticalWidget->AddSlot() .AutoHeight() .Padding(0, 2, 0, 0) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(SBox) .HAlign(HAlign_Right) [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("OverlayMaterial", "Overlay Material")) ] ] + SHorizontalBox::Slot() .FillWidth(1.0f) .Padding(5, 0, 0, 0) [ SNew(SObjectPropertyEntryBox) .ObjectPath_Lambda([this, SlotIndex]() { UStaticMesh& StaticMesh = GetStaticMesh(); UMaterialInterface* OverlayMaterial = nullptr; if (StaticMesh.GetStaticMaterials().IsValidIndex(SlotIndex)) { OverlayMaterial = StaticMesh.GetStaticMaterials()[SlotIndex].OverlayMaterialInterface; } if (OverlayMaterial) { return OverlayMaterial->GetPathName(); } return FString(); }) .AllowClear(true) .AllowedClass(UMaterialInterface::StaticClass()) .OnObjectChanged_Lambda([this, SlotIndex](const FAssetData& AssetData) { UMaterialInterface* MaterialInterface = AssetData.IsValid() ? Cast(AssetData.GetAsset()) : nullptr; OnMaterialSlotOverlayMaterialChanged(MaterialInterface, SlotIndex); }) .ThumbnailPool(UThumbnailManager::Get().GetSharedThumbnailPool()) .DisplayCompactSize(true) ] ]; } return VerticalWidget; } void FMeshMaterialsLayout::OnMaterialSlotOverlayMaterialChanged(UMaterialInterface* NewOverlayMaterial, int32 SlotIndex) { UStaticMesh& StaticMesh = GetStaticMesh(); if (!StaticMesh.GetStaticMaterials().IsValidIndex(SlotIndex)) { return; } FStaticMaterial& StaticlMaterial = StaticMesh.GetStaticMaterials()[SlotIndex]; if (StaticlMaterial.OverlayMaterialInterface == NewOverlayMaterial) { //Nothing change return; } //Make a transactional change { FScopedTransaction ScopeTransaction(LOCTEXT("StaticMeshSetMaterialSlotOverlayMaterialTransaction", "Staticmesh editor: Set Overlay Material For Material Slot")); FProperty* ChangedProperty = NULL; ChangedProperty = FindFProperty(UStaticMesh::StaticClass(), "StaticMaterials"); check(ChangedProperty); StaticMesh.PreEditChange(ChangedProperty); StaticlMaterial.OverlayMaterialInterface = NewOverlayMaterial; FPropertyChangedEvent PropertyUpdateStruct(ChangedProperty); StaticMesh.PostEditChangeProperty(PropertyUpdateStruct); } } ECheckBoxState FMeshMaterialsLayout::IsMaterialHighlighted(int32 SlotIndex) const { ECheckBoxState State = ECheckBoxState::Unchecked; UStaticMeshComponent* Component = StaticMeshEditor.GetStaticMeshComponent(); if (Component) { State = Component->SelectedEditorMaterial == SlotIndex ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } return State; } void FMeshMaterialsLayout::OnMaterialHighlightedChanged(ECheckBoxState NewState, int32 SlotIndex) { UStaticMeshComponent* Component = StaticMeshEditor.GetStaticMeshComponent(); if (Component) { if (NewState == ECheckBoxState::Checked) { Component->SelectedEditorMaterial = SlotIndex; if (Component->MaterialIndexPreview != SlotIndex) { Component->SetMaterialPreview(INDEX_NONE); } Component->SetSectionPreview(INDEX_NONE); Component->SelectedEditorSection = INDEX_NONE; } else if (NewState == ECheckBoxState::Unchecked) { Component->SelectedEditorMaterial = INDEX_NONE; } Component->MarkRenderStateDirty(); Component->PushSelectionToProxy(); StaticMeshEditor.RefreshViewport(); } } ECheckBoxState FMeshMaterialsLayout::IsMaterialIsolatedEnabled(int32 SlotIndex) const { ECheckBoxState State = ECheckBoxState::Unchecked; const UStaticMeshComponent* Component = StaticMeshEditor.GetStaticMeshComponent(); if (Component) { State = Component->MaterialIndexPreview == SlotIndex ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } return State; } void FMeshMaterialsLayout::OnMaterialIsolatedChanged(ECheckBoxState NewState, int32 SlotIndex) { UStaticMeshComponent* Component = StaticMeshEditor.GetStaticMeshComponent(); if (Component) { if (NewState == ECheckBoxState::Checked) { Component->SetMaterialPreview(SlotIndex); if (Component->SelectedEditorMaterial != SlotIndex) { Component->SelectedEditorMaterial = INDEX_NONE; } Component->SetSectionPreview(INDEX_NONE); Component->SelectedEditorSection = INDEX_NONE; } else if (NewState == ECheckBoxState::Unchecked) { Component->SetMaterialPreview(INDEX_NONE); } Component->MarkRenderStateDirty(); StaticMeshEditor.RefreshViewport(); } } void FMeshMaterialsLayout::OnResetMaterialToDefaultClicked(UMaterialInterface* Material, int32 MaterialIndex) { UStaticMesh& StaticMesh = GetStaticMesh(); check(StaticMesh.GetStaticMaterials().IsValidIndex(MaterialIndex)); StaticMesh.GetStaticMaterials()[MaterialIndex].MaterialInterface = UMaterial::GetDefaultMaterial(MD_Surface); CallPostEditChange(); } FText FMeshMaterialsLayout::GetOriginalImportMaterialNameText(int32 MaterialIndex) const { UStaticMesh& StaticMesh = GetStaticMesh(); if (StaticMesh.GetStaticMaterials().IsValidIndex(MaterialIndex)) { FString OriginalImportMaterialName; StaticMesh.GetStaticMaterials()[MaterialIndex].ImportedMaterialSlotName.ToString(OriginalImportMaterialName); OriginalImportMaterialName = TEXT("Original Imported Material Name: ") + OriginalImportMaterialName; return FText::FromString(OriginalImportMaterialName); } return FText::FromName(NAME_None); } FText FMeshMaterialsLayout::GetMaterialNameText(int32 MaterialIndex) const { UStaticMesh& StaticMesh = GetStaticMesh(); if (StaticMesh.GetStaticMaterials().IsValidIndex(MaterialIndex)) { return FText::FromName(StaticMesh.GetStaticMaterials()[MaterialIndex].MaterialSlotName); } return FText::FromName(NAME_None); } void FMeshMaterialsLayout::OnMaterialNameCommitted(const FText& InValue, ETextCommit::Type CommitType, int32 MaterialIndex) { UStaticMesh& StaticMesh = GetStaticMesh(); FName InValueName = FName(*(InValue.ToString())); if (StaticMesh.GetStaticMaterials().IsValidIndex(MaterialIndex) && StaticMesh.GetStaticMaterials()[MaterialIndex].MaterialSlotName != InValueName) { FScopedTransaction ScopeTransaction(LOCTEXT("StaticMeshEditorMaterialSlotNameChanged", "Staticmesh editor: Material slot name change")); FProperty* ChangedProperty = NULL; ChangedProperty = FindFProperty(UStaticMesh::StaticClass(), "StaticMaterials"); check(ChangedProperty); StaticMesh.PreEditChange(ChangedProperty); StaticMesh.GetStaticMaterials()[MaterialIndex].MaterialSlotName = InValueName; FPropertyChangedEvent PropertyUpdateStruct(ChangedProperty); StaticMesh.PostEditChangeProperty(PropertyUpdateStruct); } } bool FMeshMaterialsLayout::CanDeleteMaterialSlot(int32 MaterialIndex) const { UStaticMesh& StaticMesh = GetStaticMesh(); return StaticMesh.GetStaticMaterials().IsValidIndex(MaterialIndex); } void FMeshMaterialsLayout::OnDeleteMaterialSlot(int32 MaterialIndex) { UStaticMesh& StaticMesh = GetStaticMesh(); if (CanDeleteMaterialSlot(MaterialIndex)) { if (!bDeleteWarningConsumed) { EAppReturnType::Type Answer = FMessageDialog::Open(EAppMsgType::OkCancel, LOCTEXT("FMeshMaterialsLayout_DeleteMaterialSlot", "WARNING - Deleting a material slot can break the game play blueprint or the game play code. All indexes after the delete slot will change")); if (Answer == EAppReturnType::Cancel) { return; } bDeleteWarningConsumed = true; } FScopedTransaction Transaction(LOCTEXT("StaticMeshEditorDeletedMaterialSlot", "Staticmesh editor: Deleted material slot")); StaticMesh.Modify(); StaticMesh.GetStaticMaterials().RemoveAt(MaterialIndex); //Fix the section info, the FMeshDescription use FName to retrieve the indexes when we build so no need to fix it for (int32 LodIndex = 0; LodIndex < StaticMesh.GetNumLODs(); ++LodIndex) { for (int32 SectionIndex = 0; SectionIndex < StaticMesh.GetNumSections(LodIndex); ++SectionIndex) { if (StaticMesh.GetSectionInfoMap().IsValidSection(LodIndex, SectionIndex)) { FMeshSectionInfo SectionInfo = StaticMesh.GetSectionInfoMap().Get(LodIndex, SectionIndex); if (SectionInfo.MaterialIndex > MaterialIndex) { SectionInfo.MaterialIndex -= 1; StaticMesh.GetSectionInfoMap().Set(LodIndex, SectionIndex, SectionInfo); } } } } StaticMesh.PostEditChange(); } } TSharedRef FMeshMaterialsLayout::OnGetMaterialSlotUsedByMenuContent(int32 MaterialIndex) { UStaticMesh& StaticMesh = GetStaticMesh(); FMenuBuilder MenuBuilder(true, NULL); TArray *SectionLocalizers; if (MaterialUsedMap.Contains(MaterialIndex)) { SectionLocalizers = MaterialUsedMap.Find(MaterialIndex); FUIAction Action; FText EmptyTooltip; // Add a menu item for each texture. Clicking on the texture will display it in the content browser for (const FSectionLocalizer& SectionUsingMaterial : (*SectionLocalizers)) { FString ArrayItemName = TEXT("Lod ") + FString::FromInt(SectionUsingMaterial.LODIndex) + TEXT(" Index ") + FString::FromInt(SectionUsingMaterial.SectionIndex); MenuBuilder.AddMenuEntry(FText::FromString(ArrayItemName), EmptyTooltip, FSlateIcon(), Action); } } return MenuBuilder.MakeWidget(); } FText FMeshMaterialsLayout::GetFirstMaterialSlotUsedBySection(int32 MaterialIndex) const { UStaticMesh& StaticMesh = GetStaticMesh(); if (MaterialUsedMap.Contains(MaterialIndex)) { const TArray *SectionLocalizers = MaterialUsedMap.Find(MaterialIndex); if (SectionLocalizers->Num() > 0) { FString ArrayItemName = FString::FromInt(SectionLocalizers->Num()) + TEXT(" Sections"); return FText::FromString(ArrayItemName); } } return FText(); } bool FMeshMaterialsLayout::OnMaterialListDirty() { UStaticMesh& StaticMesh = GetStaticMesh(); bool ForceMaterialListRefresh = false; TMap> TempMaterialUsedMap; for (int32 MaterialIndex = 0; MaterialIndex < StaticMesh.GetStaticMaterials().Num(); ++MaterialIndex) { TArray SectionLocalizers; for (int32 LODIndex = 0; LODIndex < StaticMesh.GetNumLODs(); ++LODIndex) { for (int32 SectionIndex = 0; SectionIndex < StaticMesh.GetNumSections(LODIndex); ++SectionIndex) { FMeshSectionInfo Info = StaticMesh.GetSectionInfoMap().Get(LODIndex, SectionIndex); if (Info.MaterialIndex == MaterialIndex) { SectionLocalizers.Add(FSectionLocalizer(LODIndex, SectionIndex)); } } } TempMaterialUsedMap.Add(MaterialIndex, SectionLocalizers); } if (TempMaterialUsedMap.Num() != MaterialUsedMap.Num()) { ForceMaterialListRefresh = true; } else if (!ForceMaterialListRefresh) { for (auto KvpOld : MaterialUsedMap) { if (!TempMaterialUsedMap.Contains(KvpOld.Key)) { ForceMaterialListRefresh = true; break; } const TArray &TempSectionLocalizers = (*(TempMaterialUsedMap.Find(KvpOld.Key))); const TArray &OldSectionLocalizers = KvpOld.Value; if (TempSectionLocalizers.Num() != OldSectionLocalizers.Num()) { ForceMaterialListRefresh = true; break; } for (int32 SectionLocalizerIndex = 0; SectionLocalizerIndex < OldSectionLocalizers.Num(); ++SectionLocalizerIndex) { if (OldSectionLocalizers[SectionLocalizerIndex] != TempSectionLocalizers[SectionLocalizerIndex]) { ForceMaterialListRefresh = true; break; } } if (ForceMaterialListRefresh) { break; } } } MaterialUsedMap = TempMaterialUsedMap; return ForceMaterialListRefresh; } ECheckBoxState FMeshMaterialsLayout::IsShadowCastingEnabled(int32 SlotIndex) const { bool FirstEvalDone = false; bool ShadowCastingValue = false; UStaticMesh& StaticMesh = GetStaticMesh(); for (int32 LODIndex = 0; LODIndex < StaticMesh.GetNumLODs(); ++LODIndex) { for (int32 SectionIndex = 0; SectionIndex < StaticMesh.GetNumSections(LODIndex); ++SectionIndex) { FMeshSectionInfo Info = StaticMesh.GetSectionInfoMap().Get(LODIndex, SectionIndex); if (Info.MaterialIndex == SlotIndex) { if (!FirstEvalDone) { ShadowCastingValue = Info.bCastShadow; FirstEvalDone = true; } else if (ShadowCastingValue != Info.bCastShadow) { return ECheckBoxState::Undetermined; } } } } if (FirstEvalDone) { return ShadowCastingValue ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } return ECheckBoxState::Undetermined; } void FMeshMaterialsLayout::OnShadowCastingChanged(ECheckBoxState NewState, int32 SlotIndex) { UStaticMesh& StaticMesh = GetStaticMesh(); if (NewState == ECheckBoxState::Undetermined) return; bool CastShadow = (NewState == ECheckBoxState::Checked) ? true : false; bool SomethingChange = false; for (int32 LODIndex = 0; LODIndex < StaticMesh.GetNumLODs(); ++LODIndex) { for (int32 SectionIndex = 0; SectionIndex < StaticMesh.GetNumSections(LODIndex); ++SectionIndex) { FMeshSectionInfo Info = StaticMesh.GetSectionInfoMap().Get(LODIndex, SectionIndex); if (Info.MaterialIndex == SlotIndex) { Info.bCastShadow = CastShadow; StaticMesh.GetSectionInfoMap().Set(LODIndex, SectionIndex, Info); SomethingChange = true; } } } if (SomethingChange) { CallPostEditChange(); } } EVisibility FMeshMaterialsLayout::GetOverrideUVDensityVisibililty() const { if (StaticMeshEditor.GetViewMode() == VMI_MeshUVDensityAccuracy) { return EVisibility::SelfHitTestInvisible; } else { return EVisibility::Collapsed; } } ECheckBoxState FMeshMaterialsLayout::IsUVDensityOverridden(int32 SlotIndex) const { const UStaticMesh& StaticMesh = GetStaticMesh(); if (!StaticMesh.GetStaticMaterials().IsValidIndex(SlotIndex)) { return ECheckBoxState::Undetermined; } else if (StaticMesh.GetStaticMaterials()[SlotIndex].UVChannelData.bOverrideDensities) { return ECheckBoxState::Checked; } else { return ECheckBoxState::Unchecked; } } void FMeshMaterialsLayout::OnOverrideUVDensityChanged(ECheckBoxState NewState, int32 SlotIndex) { UStaticMesh& StaticMesh = GetStaticMesh(); if (NewState != ECheckBoxState::Undetermined && StaticMesh.GetStaticMaterials().IsValidIndex(SlotIndex)) { StaticMesh.GetStaticMaterials()[SlotIndex].UVChannelData.bOverrideDensities = (NewState == ECheckBoxState::Checked); StaticMesh.UpdateUVChannelData(true); } } EVisibility FMeshMaterialsLayout::GetUVDensityVisibility(int32 SlotIndex, int32 UVChannelIndex) const { UStaticMesh& StaticMesh = GetStaticMesh(); if (StaticMeshEditor.GetViewMode() == VMI_MeshUVDensityAccuracy && IsUVDensityOverridden(SlotIndex) == ECheckBoxState::Checked && UVChannelIndex < StaticMeshEditor.GetNumUVChannels()) { return EVisibility::SelfHitTestInvisible; } else { return EVisibility::Collapsed; } } TOptional FMeshMaterialsLayout::GetUVDensityValue(int32 SlotIndex, int32 UVChannelIndex) const { const UStaticMesh& StaticMesh = GetStaticMesh(); if (StaticMesh.GetStaticMaterials().IsValidIndex(SlotIndex)) { float Value = StaticMesh.GetStaticMaterials()[SlotIndex].UVChannelData.LocalUVDensities[UVChannelIndex]; return FMath::RoundToFloat(Value * 4.f) * .25f; } return TOptional(); } void FMeshMaterialsLayout::SetUVDensityValue(float InDensity, ETextCommit::Type CommitType, int32 SlotIndex, int32 UVChannelIndex) { UStaticMesh& StaticMesh = GetStaticMesh(); if (StaticMesh.GetStaticMaterials().IsValidIndex(SlotIndex)) { StaticMesh.GetStaticMaterials()[SlotIndex].UVChannelData.LocalUVDensities[UVChannelIndex] = FMath::Max(0, InDensity); StaticMesh.UpdateUVChannelData(true); } } void FMeshMaterialsLayout::CallPostEditChange(FProperty* PropertyChanged/*=nullptr*/) { UStaticMesh& StaticMesh = GetStaticMesh(); if (PropertyChanged) { FPropertyChangedEvent PropertyUpdateStruct(PropertyChanged); StaticMesh.PostEditChangeProperty(PropertyUpdateStruct); } else { StaticMesh.Modify(); StaticMesh.PostEditChange(); } if (StaticMesh.GetBodySetup()) { StaticMesh.GetBodySetup()->CreatePhysicsMeshes(); } StaticMeshEditor.RefreshViewport(); } ///////////////////////////////// // FLevelOfDetailSettingsLayout ///////////////////////////////// FLevelOfDetailSettingsLayout::FLevelOfDetailSettingsLayout( FStaticMeshEditor& InStaticMeshEditor ) : StaticMeshEditor( InStaticMeshEditor ) { LODGroupNames.Reset(); UStaticMesh::GetLODGroups(LODGroupNames); for (int32 GroupIndex = 0; GroupIndex < LODGroupNames.Num(); ++GroupIndex) { LODGroupOptions.Add(MakeShareable(new FString(LODGroupNames[GroupIndex].GetPlainNameString()))); } for (int32 i = 0; i < MAX_STATIC_MESH_LODS; ++i) { bBuildSettingsExpanded[i] = false; bReductionSettingsExpanded[i] = false; bSectionSettingsExpanded[i] = (i == 0); LODScreenSizes[i] = 0.0f; } LODCount = StaticMeshEditor.GetStaticMesh()->GetNumLODs(); UpdateLODNames(); OnAssetPostLODImportDelegateHandle = GEditor->GetEditorSubsystem()->OnAssetPostLODImport.AddLambda([this](UObject* InObject, int32 InLODIndex) { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); if (InObject == StaticMesh) { StaticMeshEditor.RefreshTool(); } }); } /** Returns true if automatic mesh reduction is available. */ static bool IsAutoMeshReductionAvailable() { bool bAutoMeshReductionAvailable = FModuleManager::Get().LoadModuleChecked("MeshReductionInterface").GetStaticMeshReductionInterface() != NULL; return bAutoMeshReductionAvailable; } void FLevelOfDetailSettingsLayout::AddToDetailsPanel( IDetailLayoutBuilder& DetailBuilder ) { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); IDetailCategoryBuilder& LODSettingsCategory = DetailBuilder.EditCategory( "LodSettings", LOCTEXT("LodSettingsCategory", "LOD Settings") ); int32 LODGroupIndex = LODGroupNames.Find(StaticMesh->LODGroup); check(LODGroupIndex == INDEX_NONE || LODGroupIndex < LODGroupOptions.Num()); IDetailPropertyRow& LODGroupRow = LODSettingsCategory.AddProperty(GET_MEMBER_NAME_CHECKED(UStaticMesh, LODGroup)); LODGroupRow.CustomWidget() .NameContent() [ SNew(STextBlock) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Text(LOCTEXT("LODGroup", "LOD Group")) ] .ValueContent() .VAlign(VAlign_Center) [ SAssignNew(LODGroupComboBox, STextComboBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .OptionsSource(&LODGroupOptions) .InitiallySelectedItem(LODGroupOptions[(LODGroupIndex == INDEX_NONE) ? 0 : LODGroupIndex]) .OnSelectionChanged(this, &FLevelOfDetailSettingsLayout::OnLODGroupChanged) ]; LODSettingsCategory.AddCustomRow( LOCTEXT("LODImport", "LOD Import") ) .RowTag("LODImport") .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("LODImport", "LOD Import")) ] .ValueContent() .VAlign(VAlign_Center) [ SNew(STextComboBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .OptionsSource(&LODNames) .InitiallySelectedItem(LODNames[0]) .OnSelectionChanged(this, &FLevelOfDetailSettingsLayout::OnImportLOD) ]; TAttribute IsMinLODEnabled = TAttribute::CreateLambda([this]() { return FLevelOfDetailSettingsLayout::GetLODCount() > 1 && !GEngine->UseStaticMeshMinLODPerQualityLevels; }); { TAttribute> PlatformOverrideNames = TAttribute>::CreateSP(this, &FLevelOfDetailSettingsLayout::GetMinLODPlatformOverrideNames); FPerPlatformPropertyCustomNodeBuilderArgs Args; Args.FilterText = LOCTEXT("MinLOD", "Minimum LOD"); Args.Name = "MinLod"; Args.OnGenerateNameWidget = FOnGetContent::CreateLambda([]() { return SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("MinLOD", "Minimum LOD")); } ); Args.PlatformOverrideNames = PlatformOverrideNames; Args.OnAddPlatformOverride = FOnPlatformOverrideAction::CreateSP(this, &FLevelOfDetailSettingsLayout::AddMinLODPlatformOverride); Args.OnRemovePlatformOverride = FOnPlatformOverrideAction::CreateSP(this, &FLevelOfDetailSettingsLayout::RemoveMinLODPlatformOverride); Args.OnGenerateWidgetForPlatformRow = FOnGenerateWidget::CreateSP(this, &FLevelOfDetailSettingsLayout::GetMinLODWidget); Args.IsEnabled = IsMinLODEnabled; LODSettingsCategory.AddCustomBuilder(MakeShared(MoveTemp(Args))); } TAttribute IsQualityLevelLODEnabled = TAttribute::CreateLambda([this]() { return FLevelOfDetailSettingsLayout::GetLODCount() > 1 && GEngine->UseStaticMeshMinLODPerQualityLevels; }); LODSettingsCategory.AddCustomRow(LOCTEXT("QualityLevelMinLOD", "Quality Level Min LOD")) .Visibility(GEngine->UseStaticMeshMinLODPerQualityLevels ? EVisibility::Visible : EVisibility::Collapsed) .RowTag("QualityLevelMinLOD") .IsEnabled(IsQualityLevelLODEnabled) .EditCondition(IsQualityLevelLODEnabled, NULL) .NameContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(0.0f, 4.0f) .HAlign(HAlign_Left) .AutoWidth() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("QualityLevelMinLOD", "Quality Level Min LOD")) ] + SHorizontalBox::Slot() .HAlign(HAlign_Right) .Padding(50.0f, 0.0f) .AutoWidth() [ SNew(SButton) .OnClicked(this, &FLevelOfDetailSettingsLayout::ResetToDefault) .ButtonStyle(FAppStyle::Get(), "HoverHintOnly") .ToolTipText(LOCTEXT("QualityLevelMinLodToolTip", "Clear MinLOD conversion data")) .ForegroundColor(FSlateColor::UseForeground()) .IsEnabled(TAttribute::CreateLambda([this]() { return GetMinLOD().PerPlatform.Num() != 0 || GetMinLOD().Default != 0; })) .Content() [ SNew(SImage) .Image(FAppStyle::GetBrush("Icons.Delete")) ] ] ] .ValueContent() .MinDesiredWidth((float)(StaticMesh->GetQualityLevelMinLOD().PerQuality.Num() + 1)*125.0f) .MaxDesiredWidth((float)((int32)EPerQualityLevels::Num + 1)*125.0f) [ SNew(SPerQualityLevelPropertiesWidget) .OnGenerateWidget(this, &FLevelOfDetailSettingsLayout::GetMinQualityLevelLODWidget) .OnAddEntry(this, &FLevelOfDetailSettingsLayout::AddMinLODQualityLevelOverride) .OnRemoveEntry(this, &FLevelOfDetailSettingsLayout::RemoveMinLODQualityLevelOverride) .EntryNames(this, &FLevelOfDetailSettingsLayout::GetMinQualityLevelLODOverrideNames) ]; LODSettingsCategory.AddCustomRow(LOCTEXT("NoRefStreamingLODBias", "NoRef Streaming LOD Bias")) .RowTag("NoRefStreamingLODBias") .IsEnabled(TAttribute::CreateLambda([this]() { return GetLODCount() > 1; })) .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("NoRefStreamingLODBias", "NoRef Streaming LOD Bias")) ] .ValueContent() .MinDesiredWidth(float(StaticMesh->GetNoRefStreamingLODBias().PerQuality.Num() + 1) * 125.f) .MaxDesiredWidth(float((int32)EPerQualityLevels::Num + 1) * 125.f) [ SNew(SPerQualityLevelPropertiesWidget) .OnGenerateWidget(this, &FLevelOfDetailSettingsLayout::GetNoRefStreamingLODBiasWidget) .OnAddEntry(this, &FLevelOfDetailSettingsLayout::AddNoRefStreamingLODBiasOverride) .OnRemoveEntry(this, &FLevelOfDetailSettingsLayout::RemoveNoRefStreamingLODBiasOverride) .EntryNames(this, &FLevelOfDetailSettingsLayout::GetNoRefStreamingLODBiasOverrideNames) ]; { TAttribute> PlatformOverrideNames = TAttribute>::CreateSP(this, &FLevelOfDetailSettingsLayout::GetNumStreamedLODsPlatformOverrideNames); FPerPlatformPropertyCustomNodeBuilderArgs Args; Args.FilterText = LOCTEXT("NumStreamdLODs", "Num Streamed LODs"); Args.OnGenerateNameWidget = FOnGetContent::CreateLambda([]() { return SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("NumStreamdLODs", "Num Streamed LODs")); } ); Args.PlatformOverrideNames = PlatformOverrideNames; Args.OnAddPlatformOverride = FOnPlatformOverrideAction::CreateSP(this, &FLevelOfDetailSettingsLayout::AddNumStreamedLODsPlatformOverride); Args.OnRemovePlatformOverride = FOnPlatformOverrideAction::CreateSP(this, &FLevelOfDetailSettingsLayout::RemoveNumStreamedLODsPlatformOverride); Args.OnGenerateWidgetForPlatformRow = FOnGenerateWidget::CreateSP(this, &FLevelOfDetailSettingsLayout::GetNumStreamedLODsWidget); Args.IsEnabled = TAttribute::CreateLambda([this]() { return GetLODCount() > 1; }); LODSettingsCategory.AddCustomBuilder(MakeShared(MoveTemp(Args))); } // Add Number of LODs slider. const int32 MinAllowedLOD = 1; LODSettingsCategory.AddCustomRow( LOCTEXT("NumberOfLODs", "Number of LODs") ) .RowTag("NumberOfLODs") .NameContent() [ SNew(STextBlock) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Text(LOCTEXT("NumberOfLODs", "Number of LODs")) ] .ValueContent().VAlign(VAlign_Center) [ SNew(SSpinBox) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Value(this, &FLevelOfDetailSettingsLayout::GetLODCount) .OnValueChanged(this, &FLevelOfDetailSettingsLayout::OnLODCountChanged) .OnValueCommitted(this, &FLevelOfDetailSettingsLayout::OnLODCountCommitted) .MinValue(MinAllowedLOD) .MaxValue(MAX_STATIC_MESH_LODS) .ToolTipText(this, &FLevelOfDetailSettingsLayout::GetLODCountTooltip) .IsEnabled(IsAutoMeshReductionAvailable()) ]; // Auto LOD distance check box. LODSettingsCategory.AddCustomRow( LOCTEXT("AutoComputeLOD", "Auto Compute LOD Distances") ) .RowTag("AutoComputeLOD") .NameContent() [ SNew(STextBlock) .Font( IDetailLayoutBuilder::GetDetailFont() ) .Text(LOCTEXT("AutoComputeLOD", "Auto Compute LOD Distances")) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FLevelOfDetailSettingsLayout::IsAutoLODChecked) .OnCheckStateChanged(this, &FLevelOfDetailSettingsLayout::OnAutoLODChanged) ]; LODSettingsCategory.AddCustomRow( LOCTEXT("ApplyChanges", "Apply Changes") ) .RowTag("ApplyChanges") .ValueContent() .HAlign(HAlign_Left) .VAlign(VAlign_Center) [ SNew(SButton) .OnClicked(this, &FLevelOfDetailSettingsLayout::OnApply) .IsEnabled(this, &FLevelOfDetailSettingsLayout::IsApplyNeeded) .VAlign(VAlign_Center) .HAlign(HAlign_Center) [ SNew( STextBlock ) .Text(LOCTEXT("ApplyChanges", "Apply Changes")) .Font( DetailBuilder.GetDetailFont() ) ] ]; AddLODLevelCategories( DetailBuilder ); } bool FLevelOfDetailSettingsLayout::CanRemoveLOD(int32 LODIndex) const { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); if (StaticMesh != nullptr) { const int32 NumLODs = StaticMesh->GetNumLODs(); // LOD0 should never be removed return (NumLODs > 1 && LODIndex > 0 && LODIndex < NumLODs); } return false; } FReply FLevelOfDetailSettingsLayout::OnRemoveLOD(int32 LODIndex) { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); if (StaticMesh != nullptr) { const int32 NumLODs = StaticMesh->GetNumLODs(); if (NumLODs > 1 && LODIndex > 0 && LODIndex < NumLODs) { FText RemoveLODText = FText::Format( LOCTEXT("ConfirmRemoveLOD", "Are you sure you want to remove LOD {0} from {1}?"), LODIndex, FText::FromString(StaticMesh->GetName()) ); if (FMessageDialog::Open(EAppMsgType::YesNo, RemoveLODText) == EAppReturnType::Yes) { FText TransactionDescription = FText::Format( LOCTEXT("OnRemoveLOD", "Staticmesh editor: Remove LOD {0}"), LODIndex); FScopedTransaction Transaction( TEXT(""), TransactionDescription, StaticMesh ); StaticMesh->Modify(); StaticMesh->RemoveSourceModel(LODIndex); --LODCount; StaticMesh->PostEditChange(); StaticMeshEditor.RefreshTool(); } } } return FReply::Handled(); } void FLevelOfDetailSettingsLayout::AddLODLevelCategories( IDetailLayoutBuilder& DetailBuilder ) { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); if( StaticMesh ) { const int32 StaticMeshLODCount = StaticMesh->GetNumLODs(); //Add the Materials array { FString CategoryName = FString(TEXT("StaticMeshMaterials")); IDetailCategoryBuilder& MaterialsCategory = DetailBuilder.EditCategory(*CategoryName, LOCTEXT("StaticMeshMaterialsLabel", "Material Slots"), ECategoryPriority::Important); MaterialsCategory.SetSortOrder(0); MaterialsLayoutWidget = MakeShareable(new FMeshMaterialsLayout(StaticMeshEditor)); TArray AssetDataArray; AssetDataArray.Add(FAssetData(StaticMesh, false)); MaterialsLayoutWidget->AddToCategory(MaterialsCategory, AssetDataArray); } int32 CurrentLodIndex = 0; if (StaticMeshEditor.GetStaticMeshComponent() != nullptr) { CurrentLodIndex = StaticMeshEditor.GetStaticMeshComponent()->ForcedLodModel; } LodCategories.Empty(StaticMeshLODCount); FString LODControllerCategoryName = FString(TEXT("LODCustomMode")); FText LODControllerString = LOCTEXT("LODCustomModeCategoryName", "LOD Picker"); IDetailCategoryBuilder& LODCustomModeCategory = DetailBuilder.EditCategory( *LODControllerCategoryName, LODControllerString, ECategoryPriority::Important ); LodCustomCategory = &LODCustomModeCategory; LODCustomModeCategory.AddCustomRow((LOCTEXT("LODCustomModeSelect", "Select LOD"))) .RowTag("SelectLOD") .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("LODCustomModeSelectTitle", "LOD")) .Font(IDetailLayoutBuilder::GetDetailFont()) .IsEnabled(this, &FLevelOfDetailSettingsLayout::IsLodComboBoxEnabledForLodPicker) ] .ValueContent() .VAlign(VAlign_Center) [ OnGenerateLodComboBoxForLodPicker() ]; LODCustomModeCategory.AddCustomRow((LOCTEXT("LODCustomModeFirstRowName", "LODCustomMode"))) .RowTag("LODCustomMode") .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(this, &FLevelOfDetailSettingsLayout::GetLODCustomModeNameContent, (int32)INDEX_NONE) .ToolTipText(LOCTEXT("LODCustomModeFirstRowTooltip", "Custom Mode shows multiple LOD's properties at the same time for easier editing.")) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FLevelOfDetailSettingsLayout::IsLODCustomModeCheck, (int32)INDEX_NONE) .OnCheckStateChanged(this, &FLevelOfDetailSettingsLayout::SetLODCustomModeCheck, (int32)INDEX_NONE) .ToolTipText(LOCTEXT("LODCustomModeFirstRowTooltip", "Custom Mode shows multiple LOD's properties at the same time for easier editing.")) ]; // Create information panel for each LOD level. for(int32 LODIndex = 0; LODIndex < StaticMeshLODCount; ++LODIndex) { //Show the viewport LOD at start bool IsViewportLOD = (CurrentLodIndex == 0 ? 0 : CurrentLodIndex - 1) == LODIndex; DetailDisplayLODs[LODIndex] = true; //enable all LOD in custom mode LODCustomModeCategory.AddCustomRow((LOCTEXT("LODCustomModeRowName", "LODCheckBoxRowName")), true) .RowTag("LODCheckBoxRowName") .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(this, &FLevelOfDetailSettingsLayout::GetLODCustomModeNameContent, LODIndex) .IsEnabled(this, &FLevelOfDetailSettingsLayout::IsLODCustomModeEnable, LODIndex) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FLevelOfDetailSettingsLayout::IsLODCustomModeCheck, LODIndex) .OnCheckStateChanged(this, &FLevelOfDetailSettingsLayout::SetLODCustomModeCheck, LODIndex) .IsEnabled(this, &FLevelOfDetailSettingsLayout::IsLODCustomModeEnable, LODIndex) ]; if (IsAutoMeshReductionAvailable()) { ReductionSettingsWidgets[LODIndex] = MakeShareable( new FMeshReductionSettingsLayout(AsShared(), LODIndex, StaticMesh->IsMeshDescriptionValid(LODIndex))); } if (LODIndex < StaticMesh->GetNumSourceModels()) { const FStaticMeshSourceModel& SrcModel = StaticMesh->GetSourceModel(LODIndex); if (ReductionSettingsWidgets[LODIndex].IsValid()) { ReductionSettingsWidgets[LODIndex]->UpdateSettings(SrcModel.ReductionSettings); } if (StaticMesh->IsMeshDescriptionValid(LODIndex)) { BuildSettingsWidgets[LODIndex] = MakeShareable( new FMeshBuildSettingsLayout( AsShared(), LODIndex ) ); BuildSettingsWidgets[LODIndex]->UpdateSettings(SrcModel.BuildSettings); } LODScreenSizes[LODIndex] = SrcModel.ScreenSize; } else if (LODIndex > 0) { if (ReductionSettingsWidgets[LODIndex].IsValid() && ReductionSettingsWidgets[LODIndex-1].IsValid()) { FMeshReductionSettings ReductionSettings = ReductionSettingsWidgets[LODIndex-1]->GetSettings(); // By default create LODs with half the triangles of the previous LOD. ReductionSettings.PercentTriangles *= 0.5f; ReductionSettingsWidgets[LODIndex]->UpdateSettings(ReductionSettings); } if(LODScreenSizes[LODIndex].Default >= LODScreenSizes[LODIndex-1].Default) { const float DefaultScreenSizeDifference = 0.01f; LODScreenSizes[LODIndex].Default = LODScreenSizes[LODIndex-1].Default - DefaultScreenSizeDifference; } } FString CategoryName = FString(TEXT("LOD")); CategoryName.AppendInt( LODIndex ); FText LODLevelString = FText::FromString(FString(TEXT("LOD ")) + FString::FromInt(LODIndex) ); bool bHasBeenSimplified = !StaticMesh->IsMeshDescriptionValid(LODIndex) || StaticMesh->IsReductionActive(LODIndex); FText GeneratedString = FText::FromString(bHasBeenSimplified ? TEXT("[generated]") : TEXT("")); IDetailCategoryBuilder& LODCategory = DetailBuilder.EditCategory( *CategoryName, LODLevelString, ECategoryPriority::Important ); LodCategories.Add(&LODCategory); LODCategory.HeaderContent ( SNew( SHorizontalBox ) + SHorizontalBox::Slot() .AutoWidth() [ SNew(SBox) .Padding(FMargin(4.0f, 0.0f)) [ SNew(STextBlock) .Text(GeneratedString) .Font(IDetailLayoutBuilder::GetDetailFontItalic()) ] ] +SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew( SBox ) .HAlign( HAlign_Right ) [ SNew( SHorizontalBox ) + SHorizontalBox::Slot() .Padding(FMargin(5.0f, 0.0f)) .AutoWidth() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(this, &FLevelOfDetailSettingsLayout::GetLODScreenSizeTitle, LODIndex) .Visibility( LODIndex > 0 ? EVisibility::Visible : EVisibility::Collapsed ) ] + SHorizontalBox::Slot() .Padding( FMargin( 5.0f, 0.0f ) ) .AutoWidth() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text( FText::Format( LOCTEXT("Triangles_MeshSimplification", "Triangles: {0}"), FText::AsNumber( StaticMeshEditor.GetNumTriangles(LODIndex) ) ) ) ] + SHorizontalBox::Slot() .Padding( FMargin( 5.0f, 0.0f ) ) .AutoWidth() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text( FText::Format( LOCTEXT("Vertices_MeshSimplification", "Vertices: {0}"), FText::AsNumber( StaticMeshEditor.GetNumVertices(LODIndex) ) ) ) ] ] ] ); SectionSettingsWidgets[ LODIndex ] = MakeShareable( new FMeshSectionSettingsLayout( StaticMeshEditor, LODIndex, LodCategories) ); SectionSettingsWidgets[ LODIndex ]->AddToCategory( LODCategory ); int32 PlatformNumber = PlatformInfo::GetAllPlatformGroupNames().Num(); TAttribute> PlatformOverrideNames = TAttribute>::Create(TAttribute>::FGetter::CreateSP(this, &FLevelOfDetailSettingsLayout::GetLODScreenSizePlatformOverrideNames, LODIndex)); FPerPlatformPropertyCustomNodeBuilderArgs Args; { FText ScreenSizePropertyText(LOCTEXT("ScreenSizeName", "Screen Size")); TAttribute IsScreenSizeEnabled = TAttribute::CreateSP(this, &FLevelOfDetailSettingsLayout::CanChangeLODScreenSize); Args.Name = FName("ScreenSize"); Args.FilterText = ScreenSizePropertyText; Args.PlatformOverrideNames = PlatformOverrideNames; Args.OnAddPlatformOverride = FOnPlatformOverrideAction::CreateSP(this, &FLevelOfDetailSettingsLayout::AddLODScreenSizePlatformOverride, LODIndex); Args.OnRemovePlatformOverride = FOnPlatformOverrideAction::CreateSP(this, &FLevelOfDetailSettingsLayout::RemoveLODScreenSizePlatformOverride, LODIndex); Args.OnGenerateWidgetForPlatformRow = FOnGenerateWidget::CreateSP(this, &FLevelOfDetailSettingsLayout::GetLODScreenSizeWidget, LODIndex); Args.IsEnabled = IsScreenSizeEnabled; Args.OnGenerateNameWidget = FOnGetContent::CreateLambda([IsScreenSizeEnabled = MoveTemp(IsScreenSizeEnabled), ScreenSizePropertyText = MoveTemp(ScreenSizePropertyText)]() { return SNew(STextBlock) .IsEnabled(IsScreenSizeEnabled) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(ScreenSizePropertyText); }); } LODCategory.AddCustomBuilder(MakeShared(MoveTemp(Args))); if(LODIndex > 0 && StaticMesh->IsMeshDescriptionValid(LODIndex)) { FString FileTypeFilter = TEXT("All files (*.*)|*.*"); LODCategory.AddCustomRow(( LOCTEXT("SourceImporFilenameRow", "SourceImportFilename"))) .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("SourceImportFilenameName", "Source Import Filename")) ] .ValueContent() .VAlign(VAlign_Center) .MinDesiredWidth(125.0f) .MaxDesiredWidth(0.0f) [ SNew(SFilePathPicker) .BrowseButtonImage(FAppStyle::GetBrush("PropertyWindow.Button_Ellipsis")) .BrowseButtonStyle(FAppStyle::Get(), "HoverHintOnly") .BrowseButtonToolTip(LOCTEXT("FileButtonToolTipText", "Choose a source import file")) .BrowseDirectory(FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_OPEN)) .BrowseTitle(LOCTEXT("PropertyEditorTitle", "Source import file picker...")) .FilePath(this, &FLevelOfDetailSettingsLayout::GetSourceImportFilename, LODIndex) .FileTypeFilter(FileTypeFilter) .OnPathPicked(this, &FLevelOfDetailSettingsLayout::SetSourceImportFilename, LODIndex) ]; } if (BuildSettingsWidgets[LODIndex].IsValid()) { LODCategory.AddCustomBuilder( BuildSettingsWidgets[LODIndex].ToSharedRef() ); } if( ReductionSettingsWidgets[LODIndex].IsValid() ) { LODCategory.AddCustomBuilder( ReductionSettingsWidgets[LODIndex].ToSharedRef() ); } if (LODIndex != 0) { LODCategory.AddCustomRow( LOCTEXT("RemoveLOD", "Remove LOD") ) .ValueContent() .HAlign(HAlign_Left) [ SNew(SButton) .OnClicked(this, &FLevelOfDetailSettingsLayout::OnRemoveLOD, LODIndex) .IsEnabled(this, &FLevelOfDetailSettingsLayout::CanRemoveLOD, LODIndex) .ToolTipText( LOCTEXT("RemoveLOD_ToolTip", "Removes this LOD from the Static Mesh") ) .VAlign(VAlign_Center) .HAlign(HAlign_Center) [ SNew(STextBlock) .Text( LOCTEXT("RemoveLOD", "Remove LOD") ) .Font( DetailBuilder.GetDetailFont() ) ] ]; } LODCategory.SetCategoryVisibility(IsViewportLOD); } //Show the LOD custom category if (StaticMeshLODCount > 1) { LODCustomModeCategory.SetCategoryVisibility(true); LODCustomModeCategory.SetShowAdvanced(false); } //Restore the state of the custom check LOD for (int32 DetailLODIndex = 0; DetailLODIndex < StaticMeshLODCount; ++DetailLODIndex) { int32 LodCheckValue = StaticMeshEditor.GetCustomData(CustomDataKey_LODVisibilityState + DetailLODIndex); if (LodCheckValue != INDEX_NONE) { DetailDisplayLODs[DetailLODIndex] = LodCheckValue > 0; } } //Restore the state of the custom LOD mode if its true (greater then 0) bool bCustomLodEditMode = StaticMeshEditor.GetCustomData(CustomDataKey_LODEditMode) > 0; if (bCustomLodEditMode) { for (int32 DetailLODIndex = 0; DetailLODIndex < StaticMeshLODCount; ++DetailLODIndex) { if (!LodCategories.IsValidIndex(DetailLODIndex)) { break; } LodCategories[DetailLODIndex]->SetCategoryVisibility(DetailDisplayLODs[DetailLODIndex]); } } if (LodCustomCategory != nullptr) { LodCustomCategory->SetShowAdvanced(bCustomLodEditMode); } } } FLevelOfDetailSettingsLayout::~FLevelOfDetailSettingsLayout() { GEditor->GetEditorSubsystem()->OnAssetPostLODImport.Remove(OnAssetPostLODImportDelegateHandle); } FString FLevelOfDetailSettingsLayout::GetSourceImportFilename(int32 LODIndex) const { UStaticMesh* Mesh = StaticMeshEditor.GetStaticMesh(); if (!Mesh->IsSourceModelValid(LODIndex) || Mesh->GetSourceModel(LODIndex).SourceImportFilename.IsEmpty()) { return FString(TEXT("")); } return UAssetImportData::ResolveImportFilename(Mesh->GetSourceModel(LODIndex).SourceImportFilename, nullptr); } void FLevelOfDetailSettingsLayout::SetSourceImportFilename(const FString& SourceFileName, int32 LODIndex) const { UStaticMesh* Mesh = StaticMeshEditor.GetStaticMesh(); if (!Mesh->IsSourceModelValid(LODIndex)) { return; } if (SourceFileName.IsEmpty()) { Mesh->GetSourceModel(LODIndex).SourceImportFilename = SourceFileName; } else { Mesh->GetSourceModel(LODIndex).SourceImportFilename = UAssetImportData::SanitizeImportFilename(SourceFileName, nullptr); } Mesh->Modify(); } int32 FLevelOfDetailSettingsLayout::GetLODCount() const { return LODCount; } float FLevelOfDetailSettingsLayout::GetLODScreenSize(FName PlatformGroupName, int32 LODIndex) const { check(LODIndex < MAX_STATIC_MESH_LODS); UStaticMesh* Mesh = StaticMeshEditor.GetStaticMesh(); const FPerPlatformFloat& LODScreenSize = LODScreenSizes[FMath::Clamp(LODIndex, 0, MAX_STATIC_MESH_LODS - 1)]; float ScreenSize = LODScreenSize.Default; if (PlatformGroupName != NAME_None) { const float* PlatformScreenSize = LODScreenSize.PerPlatform.Find(PlatformGroupName); if (PlatformScreenSize != nullptr) { ScreenSize = *PlatformScreenSize; } } if(Mesh->bAutoComputeLODScreenSize && Mesh->GetRenderData()) { ScreenSize = Mesh->GetRenderData()->ScreenSize[LODIndex].Default; const float* PlatformScreenSize = Mesh->GetRenderData()->ScreenSize[LODIndex].PerPlatform.Find(PlatformGroupName); if (PlatformScreenSize != nullptr) { ScreenSize = *PlatformScreenSize; } } else if(Mesh->IsSourceModelValid(LODIndex)) { ScreenSize = Mesh->GetSourceModel(LODIndex).ScreenSize.Default; const float* PlatformScreenSize = Mesh->GetSourceModel(LODIndex).ScreenSize.PerPlatform.Find(PlatformGroupName); if (PlatformScreenSize != nullptr) { ScreenSize = *PlatformScreenSize; } } return ScreenSize; } FText FLevelOfDetailSettingsLayout::GetLODScreenSizeTitle( int32 LODIndex ) const { return FText::Format( LOCTEXT("ScreenSize_MeshSimplification", "Screen Size: {0}"), FText::AsNumber(GetLODScreenSize(NAME_None, LODIndex))); } bool FLevelOfDetailSettingsLayout::CanChangeLODScreenSize() const { return !IsAutoLODEnabled(); } TSharedRef FLevelOfDetailSettingsLayout::GetLODScreenSizeWidget(FName PlatformGroupName, int32 LODIndex) const { return SNew(SSpinBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .MinDesiredWidth(60.0f) .MinValue(0.0f) .MaxValue(static_cast(WORLD_MAX)) .SliderExponent(2.0f) .Value(this, &FLevelOfDetailSettingsLayout::GetLODScreenSize, PlatformGroupName, LODIndex) .OnValueChanged(const_cast(this), &FLevelOfDetailSettingsLayout::OnLODScreenSizeChanged, PlatformGroupName, LODIndex) .OnValueCommitted(const_cast(this), &FLevelOfDetailSettingsLayout::OnLODScreenSizeCommitted, PlatformGroupName, LODIndex) .IsEnabled(this, &FLevelOfDetailSettingsLayout::CanChangeLODScreenSize); } TArray FLevelOfDetailSettingsLayout::GetLODScreenSizePlatformOverrideNames(int32 LODIndex) const { TArray KeyArray; LODScreenSizes[LODIndex].PerPlatform.GenerateKeyArray(KeyArray); KeyArray.Sort(FNameLexicalLess()); return KeyArray; } float FLevelOfDetailSettingsLayout::GetScreenSizeWidgetWidth(int32 LODIndex) const { return (float)(LODScreenSizes[LODIndex].PerPlatform.Num() + 1) * 125.f; } bool FLevelOfDetailSettingsLayout::AddLODScreenSizePlatformOverride(FName PlatformGroupName, int32 LODIndex) { FScopedTransaction Transaction(LOCTEXT("AddLODScreenSizePlatformOverride", "Add LOD Screen Size Platform Override")); UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); if (LODScreenSizes[LODIndex].PerPlatform.Find(PlatformGroupName) == nullptr) { if(!StaticMesh->bAutoComputeLODScreenSize && StaticMesh->IsSourceModelValid(LODIndex)) { StaticMesh->Modify(); float Value = StaticMesh->GetSourceModel(LODIndex).ScreenSize.Default; StaticMesh->GetSourceModel(LODIndex).ScreenSize.PerPlatform.Add(PlatformGroupName, Value); OnLODScreenSizeChanged(Value, PlatformGroupName, LODIndex); return true; } } return false; } bool FLevelOfDetailSettingsLayout::RemoveLODScreenSizePlatformOverride(FName PlatformGroupName, int32 LODIndex) { FScopedTransaction Transaction(LOCTEXT("RemoveLODScreenSizePlatformOverride", "Remove LOD Screen Size Platform Override")); UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); if (!StaticMesh->bAutoComputeLODScreenSize && StaticMesh->IsSourceModelValid(LODIndex)) { StaticMesh->Modify(); if (StaticMesh->GetSourceModel(LODIndex).ScreenSize.PerPlatform.Remove(PlatformGroupName) != 0) { OnLODScreenSizeChanged(StaticMesh->GetSourceModel(LODIndex).ScreenSize.Default, PlatformGroupName, LODIndex); return true; } } return false; } void FLevelOfDetailSettingsLayout::OnLODScreenSizeChanged( float NewValue, FName PlatformGroupName, int32 LODIndex ) { check(LODIndex < MAX_STATIC_MESH_LODS); UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); if (!StaticMesh->bAutoComputeLODScreenSize) { // First propagate any changes from the source models to our local scratch. for (int32 i = 0; i < StaticMesh->GetNumSourceModels(); ++i) { LODScreenSizes[i] = StaticMesh->GetSourceModel(i).ScreenSize; } // Update Display factors for further LODs const float MinimumDifferenceInScreenSize = KINDA_SMALL_NUMBER; if (PlatformGroupName == NAME_None) { LODScreenSizes[LODIndex].Default = NewValue; // Make sure we aren't trying to overlap or have more than one LOD for a value for (int32 i = 1; i < MAX_STATIC_MESH_LODS; ++i) { float MaxValue = FMath::Max(LODScreenSizes[i-1].Default - MinimumDifferenceInScreenSize, 0.0f); LODScreenSizes[i].Default = FMath::Min(LODScreenSizes[i].Default, MaxValue); } } else { // Per-platform overrides don't have any restrictions float* PlatformScreenSize = LODScreenSizes[LODIndex].PerPlatform.Find(PlatformGroupName); if (PlatformScreenSize != nullptr) { *PlatformScreenSize = NewValue; } } // Push changes immediately. for (int32 i = 0; i < MAX_STATIC_MESH_LODS; ++i) { if (StaticMesh->IsSourceModelValid(i)) { StaticMesh->GetSourceModel(i).ScreenSize = LODScreenSizes[i]; } if (StaticMesh->GetRenderData() && StaticMesh->GetRenderData()->LODResources.IsValidIndex(i)) { StaticMesh->GetRenderData()->ScreenSize[i] = LODScreenSizes[i]; } } // Reregister static mesh components using this mesh. { FStaticMeshComponentRecreateRenderStateContext ReregisterContext(StaticMesh,false); StaticMesh->Modify(); } StaticMeshEditor.RefreshViewport(); } } void FLevelOfDetailSettingsLayout::OnLODScreenSizeCommitted( float NewValue, ETextCommit::Type CommitType, FName PlatformGroupName, int32 LODIndex ) { OnLODScreenSizeChanged(NewValue, PlatformGroupName, LODIndex); } void FLevelOfDetailSettingsLayout::UpdateLODNames() { LODNames.Empty(); LODNames.Add( MakeShareable( new FString( LOCTEXT("BaseLOD", "LOD 0").ToString() ) ) ); for(int32 LODLevelID = 1; LODLevelID < LODCount; ++LODLevelID) { LODNames.Add( MakeShareable( new FString( FText::Format( NSLOCTEXT("LODSettingsLayout", "LODLevel_Reimport", "Reimport LOD Level {0}"), FText::AsNumber( LODLevelID ) ).ToString() ) ) ); } LODNames.Add( MakeShareable( new FString( FText::Format( NSLOCTEXT("LODSettingsLayout", "LODLevel_Import", "Import LOD Level {0}"), FText::AsNumber( LODCount ) ).ToString() ) ) ); } void FLevelOfDetailSettingsLayout::OnBuildSettingsExpanded(bool bIsExpanded, int32 LODIndex) { check(LODIndex >= 0 && LODIndex < MAX_STATIC_MESH_LODS); bBuildSettingsExpanded[LODIndex] = bIsExpanded; } void FLevelOfDetailSettingsLayout::OnReductionSettingsExpanded(bool bIsExpanded, int32 LODIndex) { check(LODIndex >= 0 && LODIndex < MAX_STATIC_MESH_LODS); bReductionSettingsExpanded[LODIndex] = bIsExpanded; } void FLevelOfDetailSettingsLayout::OnSectionSettingsExpanded(bool bIsExpanded, int32 LODIndex) { check(LODIndex >= 0 && LODIndex < MAX_STATIC_MESH_LODS); bSectionSettingsExpanded[LODIndex] = bIsExpanded; } void FLevelOfDetailSettingsLayout::OnLODGroupChanged(TSharedPtr NewValue, ESelectInfo::Type SelectInfo) { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); int32 GroupIndex = LODGroupOptions.Find(NewValue); FName NewGroup = LODGroupNames[GroupIndex]; if (StaticMesh->LODGroup != NewGroup) { if (NewGroup != NAME_None) { EAppReturnType::Type DialogResult = FMessageDialog::Open( EAppMsgType::YesNo, FText::Format(LOCTEXT("ApplyDefaultLODSettings", "Changing LOD group will overwrite the current settings with the defaults from LOD group '{0}'. Do you wish to continue?"), FText::FromString(**NewValue)) ); if (DialogResult == EAppReturnType::Yes) { StaticMesh->SetLODGroup(NewGroup); // update the internal count LODCount = StaticMesh->GetNumSourceModels(); StaticMeshEditor.RefreshTool(); } else { // Overriding the selection; ensure that the widget correctly reflects the property value int32 Index = LODGroupNames.Find(StaticMesh->LODGroup); check(Index != INDEX_NONE); LODGroupComboBox->SetSelectedItem(LODGroupOptions[Index]); } } else { //Setting to none just change the LODGroup to None, the LOD count will not change StaticMesh->SetLODGroup(NewGroup); StaticMeshEditor.RefreshTool(); } } } bool FLevelOfDetailSettingsLayout::IsAutoLODEnabled() const { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); return StaticMesh->bAutoComputeLODScreenSize; } ECheckBoxState FLevelOfDetailSettingsLayout::IsAutoLODChecked() const { return IsAutoLODEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } void FLevelOfDetailSettingsLayout::OnAutoLODChanged(ECheckBoxState NewState) { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); StaticMesh->Modify(); StaticMesh->bAutoComputeLODScreenSize = (NewState == ECheckBoxState::Checked) ? true : false; if (!StaticMesh->bAutoComputeLODScreenSize) { if (StaticMesh->GetNumSourceModels() > 0) { StaticMesh->GetSourceModel(0).ScreenSize.Default = 1.0f; } for (int32 LODIndex = 1; LODIndex < StaticMesh->GetNumSourceModels(); ++LODIndex) { StaticMesh->GetSourceModel(LODIndex).ScreenSize.Default = StaticMesh->GetRenderData()->ScreenSize[LODIndex].Default; } } StaticMesh->PostEditChange(); StaticMeshEditor.RefreshTool(); } void FLevelOfDetailSettingsLayout::OnImportLOD(TSharedPtr NewValue, ESelectInfo::Type SelectInfo) { int32 LODIndex = 0; if( LODNames.Find(NewValue, LODIndex) && LODIndex > 0 ) { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); if (StaticMesh->LODGroup != NAME_None && StaticMesh->IsSourceModelValid(LODIndex)) { // Cache derived data for the running platform. ITargetPlatformManagerModule& TargetPlatformManager = GetTargetPlatformManagerRef(); ITargetPlatform* RunningPlatform = TargetPlatformManager.GetRunningTargetPlatform(); check(RunningPlatform); const FStaticMeshLODSettings& LODSettings = RunningPlatform->GetStaticMeshLODSettings(); const FStaticMeshLODGroup& LODGroup = LODSettings.GetLODGroup(StaticMesh->LODGroup); if (LODIndex < LODGroup.GetDefaultNumLODs()) { //Ask the user to change the LODGroup to None, if the user cancel do not re-import the LOD //We can have a LODGroup with custom LOD only if custom LOD are after the generated LODGroup LODs EAppReturnType::Type ReturnResult = FMessageDialog::Open(EAppMsgType::OkCancel, EAppReturnType::Ok, FText::Format(LOCTEXT("LODImport_LODGroupVersusCustomLODConflict", "This static mesh uses the LOD group \"{0}\" which controls generated LODs. Continuing this process will reset the LOD group, clear all existing LODs, and import the selected file."), FText::FromName(StaticMesh->LODGroup))); if (ReturnResult == EAppReturnType::Cancel) { StaticMeshEditor.RefreshTool(); return; } //Clear the LODGroup StaticMesh->SetLODGroup(NAME_None, false); //Make sure the importdata point on LOD Group None UFbxStaticMeshImportData* ImportData = Cast(StaticMesh->AssetImportData); if (ImportData != nullptr) { ImportData->StaticMeshLODGroup = NAME_None; } } } //Are we a new imported LOD, we want to set some value for new imported LOD. //This boolean prevent changing the value when the LOD is reimport bool bImportCustomLOD = (LODIndex >= StaticMesh->GetNumSourceModels()); FbxMeshUtils::ImportMeshLODDialog(StaticMesh, LODIndex).Then([this, StaticMesh, bImportCustomLOD, LODIndex](TFuture FutureResult) { bool bResult = FutureResult.Get(); if (bImportCustomLOD && bResult && StaticMesh->IsSourceModelValid(LODIndex)) { //Custom LOD should reduce base on them self when they get imported. StaticMesh->GetSourceModel(LODIndex).ReductionSettings.BaseLODModel = LODIndex; } StaticMesh->PostEditChange(); StaticMeshEditor.RefreshTool(); }); } } bool FLevelOfDetailSettingsLayout::IsApplyNeeded() const { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); if (StaticMesh->GetNumSourceModels() != LODCount) { return true; } for (int32 LODIndex = 0; LODIndex < LODCount; ++LODIndex) { FStaticMeshSourceModel& SrcModel = StaticMesh->GetSourceModel(LODIndex); if (BuildSettingsWidgets[LODIndex].IsValid() && SrcModel.BuildSettings != BuildSettingsWidgets[LODIndex]->GetSettings()) { return true; } if (ReductionSettingsWidgets[LODIndex].IsValid() && SrcModel.ReductionSettings != ReductionSettingsWidgets[LODIndex]->GetSettings()) { return true; } } // Allow to rebuild mesh card representation on demand when debugging it if (MeshCardRepresentation::IsDebugMode()) { return true; } return false; } void FLevelOfDetailSettingsLayout::ApplyChanges() { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); // Calling Begin and EndSlowTask are rather dangerous because they tick // Slate. Call them here and flush rendering commands to be sure!. FFormatNamedArguments Args; Args.Add( TEXT("StaticMeshName"), FText::FromString( StaticMesh->GetName() ) ); GWarn->BeginSlowTask( FText::Format( LOCTEXT("ApplyLODChanges", "Applying changes to {StaticMeshName}..."), Args ), true ); FlushRenderingCommands(); StaticMesh->Modify(); StaticMesh->SetNumSourceModels(LODCount); for (int32 LODIndex = 0; LODIndex < LODCount; ++LODIndex) { FStaticMeshSourceModel& SrcModel = StaticMesh->GetSourceModel(LODIndex); if (BuildSettingsWidgets[LODIndex].IsValid()) { SrcModel.BuildSettings = BuildSettingsWidgets[LODIndex]->GetSettings(); } if (ReductionSettingsWidgets[LODIndex].IsValid()) { SrcModel.ReductionSettings = ReductionSettingsWidgets[LODIndex]->GetSettings(); } if (LODIndex == 0) { SrcModel.ScreenSize.Default = 1.0f; } else { SrcModel.ScreenSize = LODScreenSizes[LODIndex]; FStaticMeshSourceModel& PrevModel = StaticMesh->GetSourceModel(LODIndex-1); if(SrcModel.ScreenSize.Default >= PrevModel.ScreenSize.Default) { const float DefaultScreenSizeDifference = 0.01f; LODScreenSizes[LODIndex].Default = LODScreenSizes[LODIndex-1].Default - DefaultScreenSizeDifference; // Make sure there are no incorrectly overlapping values SrcModel.ScreenSize.Default = 1.0f - 0.01f * LODIndex; } } } StaticMesh->PostEditChange(); GWarn->EndSlowTask(); StaticMeshEditor.RefreshTool(); } FReply FLevelOfDetailSettingsLayout::OnApply() { ApplyChanges(); return FReply::Handled(); } void FLevelOfDetailSettingsLayout::OnLODCountChanged(int32 NewValue) { LODCount = FMath::Clamp(NewValue, 1, MAX_STATIC_MESH_LODS); UpdateLODNames(); } void FLevelOfDetailSettingsLayout::OnLODCountCommitted(int32 InValue, ETextCommit::Type CommitInfo) { OnLODCountChanged(InValue); } FText FLevelOfDetailSettingsLayout::GetLODCountTooltip() const { if(IsAutoMeshReductionAvailable()) { return LOCTEXT("LODCountTooltip", "The number of LODs for this static mesh. If auto mesh reduction is available, setting this number will determine the number of LOD levels to auto generate."); } return LOCTEXT("LODCountTooltip_Disabled", "Auto mesh reduction is unavailable! Please provide a mesh reduction interface such as Simplygon to use this feature or manually import LOD levels."); } int32 FLevelOfDetailSettingsLayout::GetMinLOD(FName Platform) const { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); const int32* ValuePtr = (Platform == NAME_None) ? nullptr : StaticMesh->GetMinLOD().PerPlatform.Find(Platform); return (ValuePtr != nullptr) ? *ValuePtr : StaticMesh->GetMinLOD().Default; } FPerPlatformInt FLevelOfDetailSettingsLayout::GetMinLOD() const { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); return StaticMesh->GetMinLOD(); } void FLevelOfDetailSettingsLayout::OnMinLODChanged(int32 NewValue, FName Platform) { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); { FStaticMeshComponentRecreateRenderStateContext ReregisterContext(StaticMesh, false); NewValue = FMath::Clamp(NewValue, 0, MAX_STATIC_MESH_LODS - 1); FPerPlatformInt MinLOD = StaticMesh->GetMinLOD(); if (Platform == NAME_None) { MinLOD.Default = NewValue; } else { int32* ValuePtr = MinLOD.PerPlatform.Find(Platform); if (ValuePtr != nullptr) { *ValuePtr = NewValue; } } StaticMesh->SetMinLOD(MoveTemp(MinLOD)); StaticMesh->Modify(); } StaticMeshEditor.RefreshViewport(); } void FLevelOfDetailSettingsLayout::OnMinLODCommitted(int32 InValue, ETextCommit::Type CommitInfo, FName Platform) { OnMinLODChanged(InValue, Platform); } FText FLevelOfDetailSettingsLayout::GetMinLODTooltip() const { return LOCTEXT("MinLODTooltip", "The minimum LOD to use for rendering. This can be overridden in components."); } TSharedRef FLevelOfDetailSettingsLayout::GetMinLODWidget(FName PlatformGroupName) const { return SNew(SSpinBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .Value(this, &FLevelOfDetailSettingsLayout::GetMinLOD, PlatformGroupName) .OnValueChanged(const_cast(this), &FLevelOfDetailSettingsLayout::OnMinLODChanged, PlatformGroupName) .OnValueCommitted(const_cast(this), &FLevelOfDetailSettingsLayout::OnMinLODCommitted, PlatformGroupName) .MinValue(0) .MaxValue(MAX_STATIC_MESH_LODS) .ToolTipText(this, &FLevelOfDetailSettingsLayout::GetMinLODTooltip) .IsEnabled(FLevelOfDetailSettingsLayout::GetLODCount() > 1); } bool FLevelOfDetailSettingsLayout::AddMinLODPlatformOverride(FName PlatformGroupName) { FScopedTransaction Transaction(LOCTEXT("AddMinLODPlatformOverride", "Add Min LOD Platform Override")); UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); StaticMesh->Modify(); if (StaticMesh->GetMinLOD().PerPlatform.Find(PlatformGroupName) == nullptr) { FPerPlatformInt MinLOD = StaticMesh->GetMinLOD(); int32 Value = MinLOD.Default; MinLOD.PerPlatform.Add(PlatformGroupName, Value); StaticMesh->SetMinLOD(MoveTemp(MinLOD)); OnMinLODChanged(Value, PlatformGroupName); return true; } return false; } bool FLevelOfDetailSettingsLayout::RemoveMinLODPlatformOverride(FName PlatformGroupName) { FScopedTransaction Transaction(LOCTEXT("RemoveMinLODPlatformOverride", "Remove Min LOD Platform Override")); UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); StaticMesh->Modify(); FPerPlatformInt MinLOD = StaticMesh->GetMinLOD(); if (MinLOD.PerPlatform.Remove(PlatformGroupName) != 0) { int32 Value = MinLOD.Default; StaticMesh->SetMinLOD(MoveTemp(MinLOD)); OnMinLODChanged(Value, PlatformGroupName); return true; } return false; } TArray FLevelOfDetailSettingsLayout::GetMinLODPlatformOverrideNames() const { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); TArray KeyArray; StaticMesh->GetMinLOD().PerPlatform.GenerateKeyArray(KeyArray); KeyArray.Sort(FNameLexicalLess()); return KeyArray; } int32 FLevelOfDetailSettingsLayout::GetMinQualityLevelLOD(FName QualityLevel) const { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); int32 QLKey = QualityLevelProperty::FNameToQualityLevel(QualityLevel); const int32* ValuePtr = (QualityLevel == NAME_None) ? nullptr : StaticMesh->GetQualityLevelMinLOD().PerQuality.Find(QLKey); return (ValuePtr != nullptr) ? *ValuePtr : StaticMesh->GetQualityLevelMinLOD().Default; } void FLevelOfDetailSettingsLayout::OnMinQualityLevelLODChanged(int32 NewValue, FName QualityLevel) { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); { FStaticMeshComponentRecreateRenderStateContext ReregisterContext(StaticMesh, false); NewValue = FMath::Clamp(NewValue, 0, MAX_STATIC_MESH_LODS - 1); FPerQualityLevelInt MinLOD = StaticMesh->GetQualityLevelMinLOD(); int32 QLKey = QualityLevelProperty::FNameToQualityLevel(QualityLevel); if (QualityLevel == NAME_None || QLKey == INDEX_NONE) { MinLOD.Default = NewValue; } else { int32* ValuePtr = MinLOD.PerQuality.Find(QLKey); if (ValuePtr != nullptr) { *ValuePtr = NewValue; } } StaticMesh->SetQualityLevelMinLOD(MoveTemp(MinLOD)); StaticMesh->Modify(); StaticMesh->GetOnMeshChanged().Broadcast(); } StaticMeshEditor.RefreshViewport(); } void FLevelOfDetailSettingsLayout::OnMinQualityLevelLODCommitted(int32 InValue, ETextCommit::Type CommitInfo, FName QualityLevel) { OnMinQualityLevelLODChanged(InValue, QualityLevel); } TSharedRef FLevelOfDetailSettingsLayout::GetMinQualityLevelLODWidget(FName QualityLevelName) const { return SNew(SSpinBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .Value(this, &FLevelOfDetailSettingsLayout::GetMinQualityLevelLOD, QualityLevelName) .OnValueChanged(const_cast(this), &FLevelOfDetailSettingsLayout::OnMinQualityLevelLODChanged, QualityLevelName) .OnValueCommitted(const_cast(this), &FLevelOfDetailSettingsLayout::OnMinQualityLevelLODCommitted, QualityLevelName) .MinValue(0) .MaxValue(MAX_STATIC_MESH_LODS) .ToolTipText(this, &FLevelOfDetailSettingsLayout::GetMinLODTooltip) .IsEnabled(FLevelOfDetailSettingsLayout::GetLODCount() > 1); } bool FLevelOfDetailSettingsLayout::AddMinLODQualityLevelOverride(FName QualityLevelName) { FScopedTransaction Transaction(LOCTEXT("AddMinLODQualityLevelOverride", "Add Min LOD Quality Level Override")); UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); StaticMesh->Modify(); int32 QLKey = QualityLevelProperty::FNameToQualityLevel(QualityLevelName); if (StaticMesh->GetQualityLevelMinLOD().PerQuality.Find(QLKey) == nullptr) { FPerQualityLevelInt MinLOD = StaticMesh->GetQualityLevelMinLOD(); int32 Value = MinLOD.Default; MinLOD.PerQuality.Add(QLKey, Value); StaticMesh->SetQualityLevelMinLOD(MoveTemp(MinLOD)); OnMinQualityLevelLODChanged(Value, QualityLevelName); return true; } return false; } bool FLevelOfDetailSettingsLayout::RemoveMinLODQualityLevelOverride(FName QualityLevelName) { FScopedTransaction Transaction(LOCTEXT("RemoveMinLODQualityLevelOverride", "Remove Min LOD Quality Level Override")); UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); StaticMesh->Modify(); FPerQualityLevelInt MinLOD = StaticMesh->GetQualityLevelMinLOD(); int32 QL = QualityLevelProperty::FNameToQualityLevel(QualityLevelName); if (QL != INDEX_NONE && MinLOD.PerQuality.Remove(QL) != 0) { int32 Value = MinLOD.Default; StaticMesh->SetQualityLevelMinLOD(MoveTemp(MinLOD)); OnMinQualityLevelLODChanged(Value, QualityLevelName); return true; } return false; } TArray FLevelOfDetailSettingsLayout::GetMinQualityLevelLODOverrideNames() const { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); TArray OverrideNames; for (const TPair& Pair : StaticMesh->GetQualityLevelMinLOD().PerQuality) { OverrideNames.Add(QualityLevelProperty::QualityLevelToFName(Pair.Key)); } OverrideNames.Sort(FNameLexicalLess()); return OverrideNames; } void FLevelOfDetailSettingsLayout::OnNoRefStreamingLODBiasChanged(int32 NewValue, FName QualityLevel) { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); { FStaticMeshComponentRecreateRenderStateContext ReregisterContext(StaticMesh, false); NewValue = FMath::Clamp(NewValue, -1, MAX_STATIC_MESH_LODS - 1); FPerQualityLevelInt NoRefStreamingLODBias = StaticMesh->GetNoRefStreamingLODBias(); int32 QLKey = QualityLevelProperty::FNameToQualityLevel(QualityLevel); if (QualityLevel == NAME_None || QLKey == INDEX_NONE) { NoRefStreamingLODBias.Default = NewValue; } else { int32* ValuePtr = NoRefStreamingLODBias.PerQuality.Find(QLKey); if (ValuePtr != nullptr) { *ValuePtr = NewValue; } } StaticMesh->SetNoRefStreamingLODBias(MoveTemp(NoRefStreamingLODBias)); StaticMesh->Modify(); } StaticMeshEditor.RefreshViewport(); } FReply FLevelOfDetailSettingsLayout::ResetToDefault() { if (GEngine->UseStaticMeshMinLODPerQualityLevels) { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); FPerPlatformInt PlatformMinLOD; StaticMesh->SetMinLOD(MoveTemp(PlatformMinLOD)); StaticMesh->Modify(); StaticMeshEditor.RefreshTool(); } return FReply::Handled(); } void FLevelOfDetailSettingsLayout::OnNoRefStreamingLODBiasCommitted(int32 InValue, ETextCommit::Type CommitInfo, FName QualityLevel) { OnNoRefStreamingLODBiasChanged(InValue, QualityLevel); } int32 FLevelOfDetailSettingsLayout::GetNoRefStreamingLODBias(FName QualityLevel) const { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); int32 QLKey = QualityLevelProperty::FNameToQualityLevel(QualityLevel); const int32* ValuePtr = (QualityLevel == NAME_None) ? nullptr : StaticMesh->GetNoRefStreamingLODBias().PerQuality.Find(QLKey); return (ValuePtr != nullptr) ? *ValuePtr : StaticMesh->GetNoRefStreamingLODBias().Default; } TSharedRef FLevelOfDetailSettingsLayout::GetNoRefStreamingLODBiasWidget(FName QualityLevelName) const { return SNew(SSpinBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .Value(this, &FLevelOfDetailSettingsLayout::GetNoRefStreamingLODBias, QualityLevelName) .OnValueChanged(const_cast(this), &FLevelOfDetailSettingsLayout::OnNoRefStreamingLODBiasChanged, QualityLevelName) .OnValueCommitted(const_cast(this), &FLevelOfDetailSettingsLayout::OnNoRefStreamingLODBiasCommitted, QualityLevelName) .MinValue(-1) .MaxValue(MAX_STATIC_MESH_LODS - 1) .ToolTipText(this, &FLevelOfDetailSettingsLayout::GetNoRefStreamingLODBiasTooltip) .IsEnabled(FLevelOfDetailSettingsLayout::GetLODCount() > 1); } bool FLevelOfDetailSettingsLayout::AddNoRefStreamingLODBiasOverride(FName QualityLevelName) { FScopedTransaction Transaction(LOCTEXT("AddNoRefStreamingLODBiasOverride", "Add NoRef Streaming LOD Bias Override")); UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); StaticMesh->Modify(); int32 QLKey = QualityLevelProperty::FNameToQualityLevel(QualityLevelName); if (StaticMesh->GetNoRefStreamingLODBias().PerQuality.Find(QLKey) == nullptr) { FPerQualityLevelInt NoRefStreamingLODBias = StaticMesh->GetNoRefStreamingLODBias(); int32 Value = NoRefStreamingLODBias.Default; NoRefStreamingLODBias.PerQuality.Add(QLKey, Value); StaticMesh->SetNoRefStreamingLODBias(MoveTemp(NoRefStreamingLODBias)); OnNoRefStreamingLODBiasChanged(Value, QualityLevelName); return true; } return false; } bool FLevelOfDetailSettingsLayout::RemoveNoRefStreamingLODBiasOverride(FName QualityLevelName) { FScopedTransaction Transaction(LOCTEXT("RemoveNoRefStreamingLODBiasOverride", "Remove NoRef Streaming LOD Bias Override")); UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); StaticMesh->Modify(); FPerQualityLevelInt NoRefStreamingLODBias = StaticMesh->GetNoRefStreamingLODBias(); int32 QL = QualityLevelProperty::FNameToQualityLevel(QualityLevelName); if (QL != INDEX_NONE && NoRefStreamingLODBias.PerQuality.Remove(QL) != 0) { int32 Value = NoRefStreamingLODBias.Default; StaticMesh->SetNoRefStreamingLODBias(MoveTemp(NoRefStreamingLODBias)); OnNoRefStreamingLODBiasChanged(Value, QualityLevelName); return true; } return false; } TArray FLevelOfDetailSettingsLayout::GetNoRefStreamingLODBiasOverrideNames() const { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); TArray OverrideNames; for (const TPair& Pair : StaticMesh->GetNoRefStreamingLODBias().PerQuality) { OverrideNames.Add(QualityLevelProperty::QualityLevelToFName(Pair.Key)); } OverrideNames.Sort(FNameLexicalLess()); return OverrideNames; } FText FLevelOfDetailSettingsLayout::GetNoRefStreamingLODBiasTooltip() const { return LOCTEXT("NoRefStreamingLODBiasTooltip", "LOD bias for preloading no-ref mesh LODs. To use platform default, set to -1."); } /** @return - whether value was different */ static bool UpdateStaticMeshNumStreamedLODsHelper(UStaticMesh* StaticMesh, int32 NewValue, FName Platform) { bool bWasDifferent = false; StaticMesh->Modify(); { FStaticMeshComponentRecreateRenderStateContext ReregisterContext(StaticMesh, false); NewValue = FMath::Clamp(NewValue, -1, MAX_STATIC_MESH_LODS); if (Platform == NAME_None) { bWasDifferent = StaticMesh->NumStreamedLODs.Default != NewValue; StaticMesh->NumStreamedLODs.Default = NewValue; } else { int32* ValuePtr = StaticMesh->NumStreamedLODs.PerPlatform.Find(Platform); if (ValuePtr != nullptr) { bWasDifferent = *ValuePtr != NewValue; *ValuePtr = NewValue; } } } return bWasDifferent; } void FLevelOfDetailSettingsLayout::OnNumStreamedLODsChanged(int32 NewValue, FName Platform) { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); UpdateStaticMeshNumStreamedLODsHelper(StaticMesh, NewValue, Platform); StaticMeshEditor.RefreshViewport(); } void FLevelOfDetailSettingsLayout::OnNumStreamedLODsCommitted(int32 InValue, ETextCommit::Type CommitInfo, FName Platform) { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); if (UpdateStaticMeshNumStreamedLODsHelper(StaticMesh, InValue, Platform)) { if (IStreamingManager::Get().IsRenderAssetStreamingEnabled(EStreamableRenderAssetType::StaticMesh)) { // Make sure FStaticMeshRenderData::CurrentFirstLODIdx is not accessed on other threads IStreamingManager::Get().GetRenderAssetStreamingManager().BlockTillAllRequestsFinished(); } // Recache derived data and relink streaming ApplyChanges(); } StaticMeshEditor.RefreshViewport(); } int32 FLevelOfDetailSettingsLayout::GetNumStreamedLODs(FName Platform) const { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); int32* ValuePtr = (Platform == NAME_None) ? nullptr : StaticMesh->NumStreamedLODs.PerPlatform.Find(Platform); return (ValuePtr != nullptr) ? *ValuePtr : StaticMesh->NumStreamedLODs.Default; } TSharedRef FLevelOfDetailSettingsLayout::GetNumStreamedLODsWidget(FName PlatformGroupName) const { return SNew(SSpinBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .Value(this, &FLevelOfDetailSettingsLayout::GetNumStreamedLODs, PlatformGroupName) .OnValueChanged(const_cast(this), &FLevelOfDetailSettingsLayout::OnNumStreamedLODsChanged, PlatformGroupName) .OnValueCommitted(const_cast(this), &FLevelOfDetailSettingsLayout::OnNumStreamedLODsCommitted, PlatformGroupName) .MinValue(-1) .MaxValue(MAX_STATIC_MESH_LODS) .ToolTipText(this, &FLevelOfDetailSettingsLayout::GetNumStreamedLODsTooltip) .IsEnabled(FLevelOfDetailSettingsLayout::GetLODCount() > 1); } bool FLevelOfDetailSettingsLayout::AddNumStreamedLODsPlatformOverride(FName PlatformGroupName) { FScopedTransaction Transaction(LOCTEXT("AddNumStreamedLODsPlatformOverride", "Add NumStreamdLODs Platform Override")); UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); StaticMesh->Modify(); if (StaticMesh->NumStreamedLODs.PerPlatform.Find(PlatformGroupName) == nullptr) { int32 Value = StaticMesh->NumStreamedLODs.Default; StaticMesh->NumStreamedLODs.PerPlatform.Add(PlatformGroupName, Value); OnNumStreamedLODsChanged(Value, PlatformGroupName); return true; } return false; } bool FLevelOfDetailSettingsLayout::RemoveNumStreamedLODsPlatformOverride(FName PlatformGroupName) { FScopedTransaction Transaction(LOCTEXT("RemoveNumStreamedLODsPlatformOverride", "Remove NumStreamedLODs Platform Override")); UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); StaticMesh->Modify(); if (StaticMesh->NumStreamedLODs.PerPlatform.Remove(PlatformGroupName) != 0) { OnNumStreamedLODsChanged(StaticMesh->NumStreamedLODs.Default, PlatformGroupName); return true; } return false; } TArray FLevelOfDetailSettingsLayout::GetNumStreamedLODsPlatformOverrideNames() const { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); TArray KeyArray; StaticMesh->NumStreamedLODs.PerPlatform.GenerateKeyArray(KeyArray); KeyArray.Sort(FNameLexicalLess()); return KeyArray; } FText FLevelOfDetailSettingsLayout::GetNumStreamedLODsTooltip() const { return LOCTEXT("NumStreamedLODsTooltip", "If non-negative, the number of LODs that can be streamed. Only has effect if mesh LOD streaming is enabled on the target platform."); } FText FLevelOfDetailSettingsLayout::GetLODCustomModeNameContent(int32 LODIndex) const { int32 CurrentLodIndex = 0; if (StaticMeshEditor.GetStaticMeshComponent() != nullptr) { CurrentLodIndex = StaticMeshEditor.GetStaticMeshComponent()->ForcedLodModel; } int32 RealCurrentLODIndex = (CurrentLodIndex == 0 ? 0 : CurrentLodIndex - 1); if (LODIndex == INDEX_NONE) { return LOCTEXT("GetLODCustomModeNameContent", "Custom"); } return FText::Format(LOCTEXT("GetLODModeNameContent", "LOD{0}"), LODIndex); } ECheckBoxState FLevelOfDetailSettingsLayout::IsLODCustomModeCheck(int32 LODIndex) const { int32 CurrentLodIndex = 0; if (StaticMeshEditor.GetStaticMeshComponent() != nullptr) { CurrentLodIndex = StaticMeshEditor.GetStaticMeshComponent()->ForcedLodModel; } if (LODIndex == INDEX_NONE) { return StaticMeshEditor.GetCustomData(CustomDataKey_LODEditMode) > 0 ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } return DetailDisplayLODs[LODIndex] ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } void FLevelOfDetailSettingsLayout::SetLODCustomModeCheck(ECheckBoxState NewState, int32 LODIndex) { int32 CurrentLodIndex = 0; if (StaticMeshEditor.GetStaticMeshComponent() != nullptr) { CurrentLodIndex = StaticMeshEditor.GetStaticMeshComponent()->ForcedLodModel; } if (LODIndex == INDEX_NONE) { if (NewState == ECheckBoxState::Unchecked) { StaticMeshEditor.SetCustomData(CustomDataKey_LODEditMode, 0); SectionSettingsWidgets[0]->SetCurrentLOD(CurrentLodIndex); for (int32 DetailLODIndex = 0; DetailLODIndex < MAX_STATIC_MESH_LODS; ++DetailLODIndex) { if (!LodCategories.IsValidIndex(DetailLODIndex)) { break; } LodCategories[DetailLODIndex]->SetCategoryVisibility(DetailLODIndex == (CurrentLodIndex == 0 ? 0 : CurrentLodIndex-1)); } } else { StaticMeshEditor.SetCustomData(CustomDataKey_LODEditMode, 1); SectionSettingsWidgets[0]->SetCurrentLOD(0); } } else if(StaticMeshEditor.GetCustomData(CustomDataKey_LODEditMode) > 0) { DetailDisplayLODs[LODIndex] = NewState == ECheckBoxState::Checked; StaticMeshEditor.SetCustomData(CustomDataKey_LODVisibilityState + LODIndex, DetailDisplayLODs[LODIndex] ? 1 : 0); } if (StaticMeshEditor.GetCustomData(CustomDataKey_LODEditMode) > 0) { for (int32 DetailLODIndex = 0; DetailLODIndex < MAX_STATIC_MESH_LODS; ++DetailLODIndex) { if (!LodCategories.IsValidIndex(DetailLODIndex)) { break; } LodCategories[DetailLODIndex]->SetCategoryVisibility(DetailDisplayLODs[DetailLODIndex]); } } if (LodCustomCategory != nullptr) { LodCustomCategory->SetShowAdvanced(StaticMeshEditor.GetCustomData(CustomDataKey_LODEditMode) > 0); } } bool FLevelOfDetailSettingsLayout::IsLODCustomModeEnable(int32 LODIndex) const { if (LODIndex == INDEX_NONE) { // Custom checkbox is always enable return true; } return StaticMeshEditor.GetCustomData(CustomDataKey_LODEditMode) > 0; } TSharedRef FLevelOfDetailSettingsLayout::OnGenerateLodComboBoxForLodPicker() { return SNew(SComboButton) //.Visibility(this, &FLevelOfDetailSettingsLayout::LodComboBoxVisibilityForLodPicker) .IsEnabled(this, &FLevelOfDetailSettingsLayout::IsLodComboBoxEnabledForLodPicker) .OnGetMenuContent(this, &FLevelOfDetailSettingsLayout::OnGenerateLodMenuForLodPicker) .VAlign(VAlign_Center) .ButtonContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(this, &FLevelOfDetailSettingsLayout::GetCurrentLodName) .ToolTipText(this, &FLevelOfDetailSettingsLayout::GetCurrentLodTooltip) ]; } EVisibility FLevelOfDetailSettingsLayout::LodComboBoxVisibilityForLodPicker() const { //No combo box when in Custom mode if (StaticMeshEditor.GetCustomData(CustomDataKey_LODEditMode) > 0) { return EVisibility::Hidden; } return EVisibility::All; } bool FLevelOfDetailSettingsLayout::IsLodComboBoxEnabledForLodPicker() const { //No combo box when in Custom mode return StaticMeshEditor.GetCustomData(CustomDataKey_LODEditMode) <= 0; } TSharedRef FLevelOfDetailSettingsLayout::OnGenerateLodMenuForLodPicker() { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); if (StaticMesh == nullptr) { return SNullWidget::NullWidget; } bool bAutoLod = false; if (StaticMeshEditor.GetStaticMeshComponent() != nullptr) { bAutoLod = StaticMeshEditor.GetStaticMeshComponent()->ForcedLodModel == 0; } const int32 StaticMeshLODCount = StaticMesh->GetNumLODs(); if (StaticMeshLODCount < 2) { return SNullWidget::NullWidget; } FMenuBuilder MenuBuilder(true, NULL); FText AutoLodText = FText::FromString((TEXT("LOD Auto"))); FUIAction AutoLodAction(FExecuteAction::CreateSP(this, &FLevelOfDetailSettingsLayout::OnSelectedLODChanged, 0)); MenuBuilder.AddMenuEntry(AutoLodText, LOCTEXT("OnGenerateLodMenuForLodPicker_Auto_ToolTip", "With Auto LOD selected, LOD0's properties are visible for editing."), FSlateIcon(), AutoLodAction); // Add a menu item for each texture. Clicking on the texture will display it in the content browser for (int32 AllLodIndex = 0; AllLodIndex < StaticMeshLODCount; ++AllLodIndex) { FText LODLevelString = FText::FromString((TEXT("LOD ") + FString::FromInt(AllLodIndex))); FUIAction Action(FExecuteAction::CreateSP(this, &FLevelOfDetailSettingsLayout::OnSelectedLODChanged, AllLodIndex + 1)); MenuBuilder.AddMenuEntry(LODLevelString, FText::GetEmpty(), FSlateIcon(), Action); } return MenuBuilder.MakeWidget(); } void FLevelOfDetailSettingsLayout::OnSelectedLODChanged(int32 NewLodIndex) { if (StaticMeshEditor.GetStaticMeshComponent() == nullptr) { return; } int32 CurrentDisplayLOD = StaticMeshEditor.GetStaticMeshComponent()->ForcedLodModel; int32 RealNewLOD = NewLodIndex == 0 ? 0 : NewLodIndex - 1; if (CurrentDisplayLOD == NewLodIndex || !LodCategories.IsValidIndex(RealNewLOD)) { return; } StaticMeshEditor.GetStaticMeshComponent()->SetForcedLodModel(NewLodIndex); //Reset the preview section since we do not edit the same LOD StaticMeshEditor.GetStaticMeshComponent()->SetSectionPreview(INDEX_NONE); StaticMeshEditor.GetStaticMeshComponent()->SelectedEditorSection = INDEX_NONE; //Broadcast that the LOD model has changed StaticMeshEditor.BroadcastOnSelectedLODChanged(); } FText FLevelOfDetailSettingsLayout::GetCurrentLodName() const { bool bAutoLod = false; if (StaticMeshEditor.GetStaticMeshComponent() != nullptr) { bAutoLod = StaticMeshEditor.GetStaticMeshComponent()->ForcedLodModel == 0; } int32 CurrentDisplayLOD = bAutoLod ? 0 : StaticMeshEditor.GetStaticMeshComponent()->ForcedLodModel - 1; return FText::FromString(bAutoLod ? FString(TEXT("LOD Auto")) : (FString(TEXT("LOD ")) + FString::FromInt(CurrentDisplayLOD))); } FText FLevelOfDetailSettingsLayout::GetCurrentLodTooltip() const { if (StaticMeshEditor.GetStaticMeshComponent() != nullptr && StaticMeshEditor.GetStaticMeshComponent()->ForcedLodModel == 0) { return LOCTEXT("StaticMeshEditorLODPickerCurrentLODTooltip", "With Auto LOD selected, LOD0's properties are visible for editing"); } return FText::GetEmpty(); } bool FLevelOfDetailSettingsLayout::IsNaniteEnabled() const { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh != nullptr); return StaticMesh->IsNaniteEnabled(); } ///////////////////////////////// // FRayTracingProxySettingsLayout ///////////////////////////////// FRayTracingProxySettingsLayout::FRayTracingProxySettingsLayout(FStaticMeshEditor& InStaticMeshEditor) : StaticMeshEditor(InStaticMeshEditor) { const UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); RayTracingProxySettings = StaticMesh->RayTracingProxySettings; } FRayTracingProxySettingsLayout::~FRayTracingProxySettingsLayout() { } const FMeshRayTracingProxySettings& FRayTracingProxySettingsLayout::GetSettings() const { return RayTracingProxySettings; } void FRayTracingProxySettingsLayout::UpdateSettings(const FMeshRayTracingProxySettings& InSettings) { checkf(!ShouldGenerateRayTracingProxiesByDefault() || RayTracingProxySettings.bEnabled == InSettings.bEnabled, TEXT("RayTracingProxySettings.bEnabled shouldn't be modified when ray tracing proxies are enabled by default.")); RayTracingProxySettings = InSettings; } bool FRayTracingProxySettingsLayout::IsApplyNeeded() const { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); return StaticMesh->RayTracingProxySettings != RayTracingProxySettings; } void FRayTracingProxySettingsLayout::ApplyChanges() { UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); check(StaticMesh); checkf(!ShouldGenerateRayTracingProxiesByDefault() || StaticMesh->RayTracingProxySettings.bEnabled == RayTracingProxySettings.bEnabled, TEXT("RayTracingProxySettings.bEnabled shouldn't be modified when ray tracing proxies are enabled by default.")); { FFormatNamedArguments Args; Args.Add(TEXT("StaticMeshName"), FText::FromString(StaticMesh->GetName())); FScopedSlowTask SlowTask(0, FText::Format(LOCTEXT("ApplyRayTracingChanges", "Applying changes to {StaticMeshName}..."), Args), true); SlowTask.MakeDialog(); StaticMesh->Modify(); StaticMesh->RayTracingProxySettings = RayTracingProxySettings; FProperty* ChangedProperty = FindFProperty(UStaticMesh::StaticClass(), GET_MEMBER_NAME_CHECKED(UStaticMesh, RayTracingProxySettings)); FPropertyChangedEvent Event(ChangedProperty); StaticMesh->PostEditChangeProperty(Event); } StaticMeshEditor.RefreshTool(); } FReply FRayTracingProxySettingsLayout::OnApply() { ApplyChanges(); return FReply::Handled(); } ECheckBoxState FRayTracingProxySettingsLayout::IsEnabledChecked() const { if (ShouldGenerateRayTracingProxiesByDefault()) { return ShouldEnable() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); return RayTracingProxySettings.bEnabled ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } void FRayTracingProxySettingsLayout::OnEnabledChanged(ECheckBoxState NewState) { checkf(!ShouldGenerateRayTracingProxiesByDefault(), TEXT("RayTracingProxySettings.bEnabled shouldn't be modified when ray tracing proxies are enabled by default.")); RayTracingProxySettings.bEnabled = NewState == ECheckBoxState::Checked ? true : false; } float FRayTracingProxySettingsLayout::GetFallbackPercentTriangles() const { return RayTracingProxySettings.FallbackPercentTriangles * 100.0f; // Display fraction as percentage. } void FRayTracingProxySettingsLayout::OnFallbackPercentTrianglesChanged(float NewValue) { RayTracingProxySettings.FallbackPercentTriangles = NewValue * 0.01f; } void FRayTracingProxySettingsLayout::OnFallbackPercentTrianglesCommitted(float NewValue, ETextCommit::Type TextCommitType) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.RayTracingProxySettings"), TEXT("FallbackPercentTriangles"), FString::Printf(TEXT("%.1f"), NewValue)); } OnFallbackPercentTrianglesChanged(NewValue); } float FRayTracingProxySettingsLayout::GetFallbackRelativeError() const { return RayTracingProxySettings.FallbackRelativeError; } void FRayTracingProxySettingsLayout::OnFallbackRelativeErrorChanged(float NewValue) { RayTracingProxySettings.FallbackRelativeError = NewValue; } void FRayTracingProxySettingsLayout::OnFallbackRelativeErrorCommitted(float NewValue, ETextCommit::Type TextCommitType) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.RayTracingProxySettings"), TEXT("FallbackRelativeError"), FString::Printf(TEXT("%.1f"), NewValue)); } OnFallbackRelativeErrorChanged(NewValue); } float FRayTracingProxySettingsLayout::GetLOD1PercentTriangles() const { return RayTracingProxySettings.LOD1PercentTriangles * 100.0f; // Display fraction as percentage. } void FRayTracingProxySettingsLayout::OnLOD1PercentTrianglesChanged(float NewValue) { RayTracingProxySettings.LOD1PercentTriangles = NewValue * 0.01f; } void FRayTracingProxySettingsLayout::OnLOD1PercentTrianglesCommitted(float NewValue, ETextCommit::Type TextCommitType) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.RayTracingProxySettings"), TEXT("LOD1PercentTriangles"), FString::Printf(TEXT("%.1f"), NewValue)); } OnLOD1PercentTrianglesChanged(NewValue); } float FRayTracingProxySettingsLayout::GetFoliageOverOcclusionBias() const { return RayTracingProxySettings.FoliageOverOcclusionBias; } void FRayTracingProxySettingsLayout::OnFoliageOverOcclusionBiasChanged(float NewValue) { RayTracingProxySettings.FoliageOverOcclusionBias = NewValue; } void FRayTracingProxySettingsLayout::OnFoliageOverOcclusionBiasCommitted(float NewValue, ETextCommit::Type TextCommitType) { if (FEngineAnalytics::IsAvailable()) { FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.StaticMesh.RayTracingProxySettings"), TEXT("FoliageOverOcclusionBias"), FString::Printf(TEXT("%.2f"), NewValue)); } OnFoliageOverOcclusionBiasChanged(NewValue); } void FRayTracingProxySettingsLayout::GenerateHeaderRowContent(FDetailWidgetRow& NodeRow) { NodeRow.NameContent() [ SNew(STextBlock) .Text(LOCTEXT("RayTracingProxySettings", "Ray Tracing Proxy Settings")) .Font(IDetailLayoutBuilder::GetDetailFont()) ]; } bool FRayTracingProxySettingsLayout::ShouldEnable() const { const UStaticMesh* StaticMesh = StaticMeshEditor.GetStaticMesh(); return StaticMesh && StaticMesh->bSupportRayTracing && StaticMesh->NaniteSettings.bEnabled; } bool FRayTracingProxySettingsLayout::CanToggleEnabled() const { // can only toggle ray tracing proxy checkbox is the project setting is not enabling it by default return ShouldEnable() && !ShouldGenerateRayTracingProxiesByDefault(); } void FRayTracingProxySettingsLayout::GenerateChildContent(IDetailChildrenBuilder& ChildrenBuilder) { TAttribute CanToggleEnabledAttr = TAttribute::Create(TAttribute::FGetter::CreateRaw(this, &FRayTracingProxySettingsLayout::CanToggleEnabled)); TSharedPtr RayTracingProxyEnabledCheck; { FText EnabledRayTracingSupportTooltipText = LOCTEXT("EnabledRayTracingSupportTooltip", "Use dedicated ray tracing proxy mesh instead of reusing the Nanite Fallback Mesh."); if (!ShouldEnable()) { const FText ExtraTooltipText = LOCTEXT("EnabledRayTracingSupportNaniteOnlyTooltip", "(Currently only supported for Nanite meshes)."); EnabledRayTracingSupportTooltipText = FText::Format(FText::FromString("{0}\n{1}"), EnabledRayTracingSupportTooltipText, ExtraTooltipText); } else if (ShouldGenerateRayTracingProxiesByDefault()) { const FText ExtraTooltipText = LOCTEXT("EnabledRayTracingSupportProjectOverrideTooltip", "(This setting is being overriden by the project setting \"Generate Ray Tracing Proxies\")."); EnabledRayTracingSupportTooltipText = FText::Format(FText::FromString("{0}\n{1}"), EnabledRayTracingSupportTooltipText, ExtraTooltipText); } ChildrenBuilder.AddCustomRow(LOCTEXT("Enabled", "Enabled")) .RowTag("EnabledRayTracingSupport") .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("EnabledRayTracingSupport", "Enable Ray Tracing Proxy Support")) .ToolTipText(EnabledRayTracingSupportTooltipText) ] .ValueContent() [ SAssignNew(RayTracingProxyEnabledCheck, SCheckBox) .IsChecked(this, &FRayTracingProxySettingsLayout::IsEnabledChecked) .OnCheckStateChanged(this, &FRayTracingProxySettingsLayout::OnEnabledChanged) ] .IsEnabled(CanToggleEnabledAttr); } TAttribute RayTracingProxyEnabledAttr = TAttribute::Create(TAttribute::FGetter::CreateLambda( [this, RayTracingProxyEnabledCheck]() { return ShouldEnable() && RayTracingProxyEnabledCheck->IsChecked(); })); { TSharedRef TempStruct = MakeShared(FMeshRayTracingProxySettings::StaticStruct()); FMeshRayTracingProxySettings::StaticStruct()->CopyScriptStruct(TempStruct->GetStructMemory(), &RayTracingProxySettings, 1); IDetailPropertyRow* PropertyRow = ChildrenBuilder.AddExternalStructureProperty(TempStruct, GET_MEMBER_NAME_CHECKED(FMeshRayTracingProxySettings, FallbackTarget)); PropertyRow->GetPropertyHandle()->SetOnPropertyValueChanged(FSimpleDelegate::CreateLambda( [this, TempStruct] { FMeshRayTracingProxySettings* TempStruct2 = (FMeshRayTracingProxySettings*)TempStruct->GetStructMemory(); RayTracingProxySettings.FallbackTarget = TempStruct2->FallbackTarget; } )); PropertyRow->GetPropertyHandle()->SetOnChildPropertyValueChanged(FSimpleDelegate::CreateLambda( [this, TempStruct] { FMeshRayTracingProxySettings* TempStruct2 = (FMeshRayTracingProxySettings*)TempStruct->GetStructMemory(); RayTracingProxySettings.FallbackTarget = TempStruct2->FallbackTarget; } )); PropertyRow->IsEnabled(RayTracingProxyEnabledAttr); } { ChildrenBuilder.AddCustomRow(LOCTEXT("FallbackTrianglePercent", "Fallback Triangle Percent")) .RowTag("FallbackTrianglePercent") .IsEnabled(RayTracingProxyEnabledAttr) .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("FallbackTrianglePercent", "Fallback Triangle Percent")) .ToolTipText(LOCTEXT("FallbackTrianglePercentTooltip", "Reduce until no more than this percentage of triangles remain when generating a fallback\nmesh that will be used anywhere the full detail Nanite data can't,\nincluding platforms that don't support Nanite rendering.")) ] .ValueContent() .VAlign(VAlign_Center) [ SNew(SSpinBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .MinValue(0.0f) .MaxValue(100.0f) .Value(this, &FRayTracingProxySettingsLayout::GetFallbackPercentTriangles) .OnValueChanged(this, &FRayTracingProxySettingsLayout::OnFallbackPercentTrianglesChanged) .OnValueCommitted(this, &FRayTracingProxySettingsLayout::OnFallbackPercentTrianglesCommitted) ] .Visibility(TAttribute::Create(TAttribute::FGetter::CreateLambda([this]() { return RayTracingProxySettings.FallbackTarget == ENaniteFallbackTarget::PercentTriangles ? EVisibility::Visible : EVisibility::Hidden; }))); } { ChildrenBuilder.AddCustomRow(LOCTEXT("FallbackRelativeError", "Fallback Relative Error")) .RowTag("FallbackRelativeError") .IsEnabled(RayTracingProxyEnabledAttr) .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("FallbackRelativeError", "Fallback Relative Error")) .ToolTipText(LOCTEXT("FallbackRelativeErrorTooltip", "Reduce until at least this amount of error is reached relative to its size\nwhen generating a fallback mesh that will be used anywhere the full detail Nanite data can't,\nincluding platforms that don't support Nanite rendering.")) ] .ValueContent() .VAlign(VAlign_Center) [ SNew(SSpinBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .MinValue(0.0f) .Value(this, &FRayTracingProxySettingsLayout::GetFallbackRelativeError) .OnValueChanged(this, &FRayTracingProxySettingsLayout::OnFallbackRelativeErrorChanged) .OnValueCommitted(this, &FRayTracingProxySettingsLayout::OnFallbackRelativeErrorCommitted) ] .Visibility(TAttribute::Create(TAttribute::FGetter::CreateLambda([this]() { return RayTracingProxySettings.FallbackTarget == ENaniteFallbackTarget::RelativeError ? EVisibility::Visible : EVisibility::Hidden; }))); } { ChildrenBuilder.AddCustomRow(LOCTEXT("LOD1PercentTriangles", "LOD1 Percent Triangles")) .RowTag("LOD1PercentTriangles") .IsEnabled(RayTracingProxyEnabledAttr) .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("LOD1PercentTriangles", "LOD1 Percent Triangles")) .ToolTipText(LOCTEXT("LOD1PercentTrianglesTooltip", "Percentage of triangles to keep in ray tracing proxy LOD 1.")) ] .ValueContent() .VAlign(VAlign_Center) [ SNew(SSpinBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .MinValue(0.0f) .MaxValue(100.0f) .Value(this, &FRayTracingProxySettingsLayout::GetLOD1PercentTriangles) .OnValueChanged(this, &FRayTracingProxySettingsLayout::OnLOD1PercentTrianglesChanged) .OnValueCommitted(this, &FRayTracingProxySettingsLayout::OnLOD1PercentTrianglesCommitted) ]; } { ChildrenBuilder.AddCustomRow(LOCTEXT("FoliageOverOcclusionBias", "Foliage OverOcclusion Bias")) .RowTag("FoliageOverOcclusionBias") .IsEnabled(RayTracingProxyEnabledAttr) .NameContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(LOCTEXT("FoliageOverOcclusionBias", "Foliage OverOcclusion Bias")) .ToolTipText(LOCTEXT("FoliageOverOcclusionBiasTooltip", "A bias to reduce foliage over occlusion in Lumen GI. 0: no adjustment, 1: full strength.")) ] .ValueContent() .VAlign(VAlign_Center) [ SNew(SSpinBox) .Font(IDetailLayoutBuilder::GetDetailFont()) .MinValue(0.0f) .MaxValue(0.9f) .Value(this, &FRayTracingProxySettingsLayout::GetFoliageOverOcclusionBias) .OnValueChanged(this, &FRayTracingProxySettingsLayout::OnFoliageOverOcclusionBiasChanged) .OnValueCommitted(this, &FRayTracingProxySettingsLayout::OnFoliageOverOcclusionBiasCommitted) ]; } { ChildrenBuilder.AddCustomRow(LOCTEXT("ApplyChanges", "Apply Changes")) .RowTag("ApplyChanges") .ValueContent() .HAlign(HAlign_Left) [ SNew(SButton) .OnClicked(this, &FRayTracingProxySettingsLayout::OnApply) .IsEnabled(this, &FRayTracingProxySettingsLayout::IsApplyNeeded) .VAlign(VAlign_Center) .HAlign(HAlign_Center) [ SNew(STextBlock) .Text(LOCTEXT("ApplyChanges", "Apply Changes")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] ]; } } #undef LOCTEXT_NAMESPACE