2232 lines
92 KiB
C++
2232 lines
92 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "GroomAssetDetails.h"
|
|
#include "Widgets/Input/SCheckBox.h"
|
|
#include "Misc/MessageDialog.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "UObject/UObjectHash.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
#include "Framework/Commands/UIAction.h"
|
|
#include "Widgets/Text/STextBlock.h"
|
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
#include "IDetailChildrenBuilder.h"
|
|
#include "IDetailPropertyRow.h"
|
|
#include "IDetailGroup.h"
|
|
#include "DetailLayoutBuilder.h"
|
|
#include "DetailCategoryBuilder.h"
|
|
#include "Widgets/SToolTip.h"
|
|
#include "IDocumentation.h"
|
|
#include "GroomComponent.h"
|
|
#include "SlateOptMacros.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Widgets/Images/SImage.h"
|
|
#include "Widgets/Layout/SBox.h"
|
|
#include "Widgets/Layout/SUniformGridPanel.h"
|
|
#include "Widgets/Input/SButton.h"
|
|
#include "Widgets/Input/SSpinBox.h"
|
|
#include "Widgets/Input/SComboButton.h"
|
|
#include "Widgets/Input/SVectorInputBox.h"
|
|
#include "Widgets/Layout/SSeparator.h"
|
|
#include "Editor/UnrealEdEngine.h"
|
|
#include "EditorDirectories.h"
|
|
#include "UnrealEdGlobals.h"
|
|
#include "IDetailsView.h"
|
|
#include "MaterialList.h"
|
|
#include "PropertyCustomizationHelpers.h"
|
|
#include "Interfaces/IMainFrameModule.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "Editor.h"
|
|
#include "HAL/PlatformApplicationMisc.h"
|
|
#include "Rendering/SkeletalMeshModel.h"
|
|
#include "IContentBrowserSingleton.h"
|
|
#include "ContentBrowserModule.h"
|
|
#include "EditorFramework/AssetImportData.h"
|
|
#include "Logging/LogMacros.h"
|
|
#include "IHairCardGenerator.h"
|
|
|
|
#include "MeshDescription.h"
|
|
#include "MeshAttributes.h"
|
|
#include "MeshAttributeArray.h"
|
|
|
|
#include "Widgets/Input/STextComboBox.h"
|
|
|
|
#include "Widgets/Input/SNumericEntryBox.h"
|
|
#include "IDocumentation.h"
|
|
#include "Widgets/Layout/SWrapBox.h"
|
|
#include "Widgets/Input/SNumericDropDown.h"
|
|
#include "ComponentReregisterContext.h"
|
|
#include "Widgets/Layout/SExpandableArea.h"
|
|
#include "SKismetInspector.h"
|
|
#include "PropertyEditorDelegates.h"
|
|
#include "PropertyCustomizationHelpers.h"
|
|
#include "GroomCustomAssetEditorToolkit.h"
|
|
#include "IPropertyUtilities.h"
|
|
#include "JsonObjectConverter.h"
|
|
|
|
#include "Styling/AppStyle.h"
|
|
#include "GroomEditorStyle.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "GroomRenderingDetails"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogGroomAssetDetails, Log, All);
|
|
|
|
FText GetHairAttributeLocText(EHairAttribute In, uint32 InFlags);
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Array panel for hair strands infos
|
|
TSharedRef<SUniformGridPanel> MakeHairStrandsAttributeInfoGrid(const FSlateFontInfo& DetailFontInfo, const uint32 InAttributes, const uint32 InAttributeFlags)
|
|
{
|
|
TSharedRef<SUniformGridPanel> Grid = SNew(SUniformGridPanel).SlotPadding(2.0f);
|
|
|
|
const FLinearColor AttributeColor = FLinearColor(FColor::Yellow);
|
|
|
|
uint32 SlotIndex = 0;
|
|
for (uint32 AttributeIt = 0, AttributeCount = uint32(EHairAttribute::Count); AttributeIt<AttributeCount; ++AttributeIt)
|
|
{
|
|
const EHairAttribute Attribute = (EHairAttribute)AttributeIt;
|
|
if (HasHairAttribute(InAttributes, Attribute))
|
|
{
|
|
Grid->AddSlot(0, SlotIndex++) // x, y
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.ColorAndOpacity(AttributeColor)
|
|
.Text(GetHairAttributeLocText(Attribute, InAttributeFlags))
|
|
];
|
|
}
|
|
}
|
|
|
|
return Grid;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Array panel for hair strands curve infos
|
|
TSharedRef<SUniformGridPanel> MakeHairStrandsCurveInfoGrid(const FSlateFontInfo& DetailFontInfo, const FHairStrandsBulkData::FHeader& InHeader)
|
|
{
|
|
TSharedRef<SUniformGridPanel> Grid = SNew(SUniformGridPanel).SlotPadding(2.0f);
|
|
|
|
// Min. point per curve
|
|
Grid->AddSlot(0, 1) // x, y
|
|
.HAlign(HAlign_Left)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(LOCTEXT("HairInfo_CPPerCurve", "Pt/Curve"))
|
|
];
|
|
Grid->AddSlot(1, 1) // x, y
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(LOCTEXT("HairInfo_MinCPPerCurveText", "Min"))
|
|
];
|
|
Grid->AddSlot(2, 1) // x, y
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(LOCTEXT("HairInfo_MaxCPPerCurveText", "Max"))
|
|
];
|
|
Grid->AddSlot(3, 1) // x, y
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(LOCTEXT("HairInfo_AvgCPPerCurveText", "Avg"))
|
|
];
|
|
|
|
// Max. point per curve
|
|
Grid->AddSlot(0, 2) // x, y
|
|
.HAlign(HAlign_Left)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(FText())
|
|
];
|
|
Grid->AddSlot(1, 2) // x, y
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(FText::AsNumber(InHeader.MinPointPerCurve))
|
|
];
|
|
Grid->AddSlot(2, 2) // x, y
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(FText::AsNumber(InHeader.MaxPointPerCurve))
|
|
];
|
|
Grid->AddSlot(3, 2) // x, y
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(FText::AsNumber(InHeader.AvgPointPerCurve))
|
|
];
|
|
|
|
return Grid;
|
|
}
|
|
|
|
void AddHairStrandsCurveWarning(IDetailChildrenBuilder& ChildrenBuilder, const FSlateFontInfo& DetailFontInfo, const FHairStrandsBulkData::FHeader& InHeader)
|
|
{
|
|
// Warning if group has trimmed curves or trimmed points
|
|
const FLinearColor ErrorColor = FLinearColor(FColor::Red);
|
|
if (InHeader.Flags & uint32(FHairStrandsBulkData::DataFlags_HasTrimmedCurve))
|
|
{
|
|
ChildrenBuilder.AddCustomRow(LOCTEXT("HairStrandsCurveWarning_Curve", "HairStrandsCurveWarning"))
|
|
.ValueContent()
|
|
.HAlign(HAlign_Fill)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.ColorAndOpacity(ErrorColor)
|
|
.Text(LOCTEXT("HairInfo_TrimmedCurve", "Group has > 4M curves"))
|
|
];
|
|
}
|
|
if (InHeader.Flags & uint32(FHairStrandsBulkData::DataFlags_HasTrimmedPoint))
|
|
{
|
|
ChildrenBuilder.AddCustomRow(LOCTEXT("HairStrandsCurveWarning_Point", "HairStrandsPointWarning"))
|
|
.ValueContent()
|
|
.HAlign(HAlign_Fill)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.ColorAndOpacity(ErrorColor)
|
|
.Text(LOCTEXT("HairInfo_TrimmedPoint", "Group has curve with >255 points."))
|
|
];
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Array panel for hair strands infos
|
|
TSharedRef<SUniformGridPanel> MakeHairStrandsLODInfoGrid(const FSlateFontInfo& DetailFontInfo, const FHairLODInfo& LODInfo)
|
|
{
|
|
TSharedRef<SUniformGridPanel> Grid = SNew(SUniformGridPanel).SlotPadding(2.0f);
|
|
|
|
// Header
|
|
Grid->AddSlot(1, 0) // x, y
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(LOCTEXT("HairInfo_CurveLOD", "Curves"))
|
|
];
|
|
|
|
Grid->AddSlot(2, 0) // x, y
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(LOCTEXT("HairInfo_PointLOD", "Points"))
|
|
];
|
|
|
|
// Strands
|
|
Grid->AddSlot(1, 1) // x, y
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(FText::AsNumber(LODInfo.CurveCount))
|
|
];
|
|
Grid->AddSlot(2, 1) // x, y
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(FText::AsNumber(LODInfo.PointCount))
|
|
];
|
|
|
|
return Grid;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Array panel for hair strands infos
|
|
TSharedRef<SUniformGridPanel> MakeHairStrandsInfoGrid(const FSlateFontInfo& DetailFontInfo, FHairGroupInfoWithVisibility& CurrentAsset, float MaxRadius)
|
|
{
|
|
TSharedRef<SUniformGridPanel> Grid = SNew(SUniformGridPanel).SlotPadding(2.0f);
|
|
|
|
// Header
|
|
Grid->AddSlot(1, 0) // x, y
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(LOCTEXT("HairInfo_Curves", "Curves"))
|
|
];
|
|
|
|
Grid->AddSlot(2, 0) // x, y
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(LOCTEXT("HairInfo_Points", "Points"))
|
|
];
|
|
|
|
// Strands
|
|
Grid->AddSlot(0, 1) // x, y
|
|
.HAlign(HAlign_Left)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(LOCTEXT("HairInfo_Strands", "Strands"))
|
|
];
|
|
Grid->AddSlot(1, 1) // x, y
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(FText::AsNumber(CurrentAsset.NumCurves))
|
|
];
|
|
Grid->AddSlot(2, 1) // x, y
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(FText::AsNumber(CurrentAsset.NumCurveVertices))
|
|
];
|
|
|
|
// Guides
|
|
Grid->AddSlot(0, 2) // x, y
|
|
.HAlign(HAlign_Left)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(LOCTEXT("HairInfo_Guides", "Guides"))
|
|
];
|
|
Grid->AddSlot(1, 2) // x, y
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(FText::AsNumber(CurrentAsset.NumGuides))
|
|
];
|
|
Grid->AddSlot(2, 2) // x, y
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(FText::AsNumber(CurrentAsset.NumGuideVertices))
|
|
];
|
|
|
|
// Width (mm)
|
|
Grid->AddSlot(0, 3) // x, y
|
|
.HAlign(HAlign_Left)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(LOCTEXT("HairInfo_Width", "Max. Width"))
|
|
];
|
|
Grid->AddSlot(1, 3) // x, y
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(FText())
|
|
];
|
|
Grid->AddSlot(2, 3) // x, y
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(FText::AsNumber(MaxRadius*2.0f))
|
|
];
|
|
|
|
return Grid;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Array panel for hair cards infos
|
|
|
|
TSharedRef<SUniformGridPanel> MakeHairCardsInfoGrid(const FSlateFontInfo& DetailFontInfo, FHairGroupCardsInfo& CardsInfo)
|
|
{
|
|
TSharedRef<SUniformGridPanel> Grid = SNew(SUniformGridPanel).SlotPadding(2.0f);
|
|
|
|
// Header
|
|
Grid->AddSlot(0, 0) // x, y
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(LOCTEXT("HairCardsInfo_Curves", "Cards"))
|
|
];
|
|
|
|
Grid->AddSlot(1, 0) // x, y
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(LOCTEXT("HairCardsInfo_Vertices", "Vertices"))
|
|
];
|
|
|
|
// Strands
|
|
Grid->AddSlot(0, 1) // x, y
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(FText::AsNumber(CardsInfo.NumCards))
|
|
];
|
|
Grid->AddSlot(1, 1) // x, y
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(DetailFontInfo)
|
|
.Text(FText::AsNumber(CardsInfo.NumCardVertices))
|
|
];
|
|
|
|
return Grid;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Copy/Paste LOD
|
|
|
|
static bool IsValidLODGroup(UGroomAsset* InAsset, int32 InGroupIndex, int32 InLODIndex)
|
|
{
|
|
return InAsset->GetHairGroupsLOD().IsValidIndex(InGroupIndex) && InAsset->GetHairGroupsLOD()[InGroupIndex].LODs.IsValidIndex(InLODIndex);
|
|
}
|
|
|
|
static void CopyLODToJson(UGroomAsset* InAsset, int32 InGroupIndex, int32 InLODIndex, TSharedRef<FJsonObject> OutRootJsonObject)
|
|
{
|
|
if (IsValidLODGroup(InAsset, InGroupIndex, InLODIndex))
|
|
{
|
|
FJsonObjectConverter::UStructToJsonObject(FHairLODSettings::StaticStruct(), &InAsset->GetHairGroupsLOD()[InGroupIndex].LODs[InLODIndex], OutRootJsonObject, 0, 0);
|
|
}
|
|
}
|
|
|
|
static void PasteLODFromJson(UGroomAsset* InAsset, int32 InGroupIndex, int32 InLODIndex, TSharedRef<FJsonObject> InRootJsonObject)
|
|
{
|
|
if (IsValidLODGroup(InAsset, InGroupIndex, InLODIndex))
|
|
{
|
|
FJsonObjectConverter::JsonObjectToUStruct(InRootJsonObject, FHairLODSettings::StaticStruct(), &InAsset->GetHairGroupsLOD()[InGroupIndex].LODs[InLODIndex], 0, 0);
|
|
}
|
|
}
|
|
|
|
void FGroomRenderingDetails::OnCopyLODItem(int32 InGroupIndex, int32 InLODIndex)
|
|
{
|
|
TSharedRef<FJsonObject> RootJsonObject = MakeShareable(new FJsonObject());
|
|
|
|
CopyLODToJson(GroomAsset, InGroupIndex, InLODIndex, RootJsonObject);
|
|
|
|
typedef TJsonWriter<TCHAR, TPrettyJsonPrintPolicy<TCHAR>> FStringWriter;
|
|
typedef TJsonWriterFactory<TCHAR, TPrettyJsonPrintPolicy<TCHAR>> FStringWriterFactory;
|
|
|
|
FString CopyStr;
|
|
TSharedRef<FStringWriter> Writer = FStringWriterFactory::Create(&CopyStr);
|
|
FJsonSerializer::Serialize(RootJsonObject, Writer);
|
|
|
|
if (!CopyStr.IsEmpty())
|
|
{
|
|
FPlatformApplicationMisc::ClipboardCopy(*CopyStr);
|
|
}
|
|
}
|
|
|
|
bool FGroomRenderingDetails::OnCanCopyLODItem(int32 InGroupIndex, int32 InLODIndex) const
|
|
{
|
|
return IsValidLODGroup(GroomAsset, InGroupIndex, InLODIndex);
|
|
}
|
|
|
|
void FGroomRenderingDetails::OnPasteLODItem(int32 InGroupIndex, int32 InLODIndex)
|
|
{
|
|
FString PastedText;
|
|
FPlatformApplicationMisc::ClipboardPaste(PastedText);
|
|
|
|
TSharedPtr<FJsonObject> RootJsonObject;
|
|
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(PastedText);
|
|
FJsonSerializer::Deserialize(Reader, RootJsonObject);
|
|
|
|
if (RootJsonObject.IsValid())
|
|
{
|
|
FProperty* Property = FHairGroupsLOD::StaticStruct()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(FHairGroupsLOD, LODs));
|
|
if (Property)
|
|
{
|
|
GroomAsset->PreEditChange(Property);
|
|
|
|
FScopedTransaction Transaction(LOCTEXT("GroomAssetChangedPasteLOD", "GroomAsset editor: Pasted LOD"));
|
|
GroomAsset->Modify();
|
|
PasteLODFromJson(GroomAsset, InGroupIndex, InLODIndex, RootJsonObject.ToSharedRef());
|
|
|
|
CallPostEditChange(Property);
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Copy/Paste Group
|
|
|
|
static bool IsValidGroup(UGroomAsset* InAsset, int32 InIndex, EMaterialPanelType InType)
|
|
{
|
|
switch (InType)
|
|
{
|
|
case EMaterialPanelType::Strands: return InAsset->GetHairGroupsRendering().IsValidIndex(InIndex);
|
|
case EMaterialPanelType::Cards: return InAsset->GetHairGroupsCards().IsValidIndex(InIndex);
|
|
case EMaterialPanelType::Meshes: return InAsset->GetHairGroupsMeshes().IsValidIndex(InIndex);
|
|
case EMaterialPanelType::Physics: return InAsset->GetHairGroupsPhysics().IsValidIndex(InIndex);
|
|
case EMaterialPanelType::Interpolation: return InAsset->GetHairGroupsInterpolation().IsValidIndex(InIndex);
|
|
case EMaterialPanelType::LODs: return InAsset->GetHairGroupsLOD().IsValidIndex(InIndex);
|
|
}
|
|
return false;
|
|
|
|
}
|
|
static void CopyGroupToJson(UGroomAsset* InAsset, int32 InIndex, EMaterialPanelType InType, TSharedRef<FJsonObject> OutRootJsonObject)
|
|
{
|
|
if (!IsValidGroup(InAsset, InIndex, InType)) return;
|
|
|
|
switch (InType)
|
|
{
|
|
case EMaterialPanelType::Strands: FJsonObjectConverter::UStructToJsonObject(FHairGroupsRendering::StaticStruct(), &InAsset->GetHairGroupsRendering()[InIndex], OutRootJsonObject, 0, 0); break;
|
|
case EMaterialPanelType::Cards: FJsonObjectConverter::UStructToJsonObject(FHairGroupsCardsSourceDescription::StaticStruct(), &InAsset->GetHairGroupsCards()[InIndex], OutRootJsonObject, 0, 0); break;
|
|
case EMaterialPanelType::Meshes: FJsonObjectConverter::UStructToJsonObject(FHairGroupsMeshesSourceDescription::StaticStruct(), &InAsset->GetHairGroupsMeshes()[InIndex], OutRootJsonObject, 0, 0); break;
|
|
case EMaterialPanelType::Physics: FJsonObjectConverter::UStructToJsonObject(FHairGroupsPhysics::StaticStruct(), &InAsset->GetHairGroupsPhysics()[InIndex], OutRootJsonObject, 0, 0); break;
|
|
case EMaterialPanelType::Interpolation: FJsonObjectConverter::UStructToJsonObject(FHairGroupsInterpolation::StaticStruct(), &InAsset->GetHairGroupsInterpolation()[InIndex], OutRootJsonObject, 0, 0); break;
|
|
case EMaterialPanelType::LODs: FJsonObjectConverter::UStructToJsonObject(FHairGroupsLOD::StaticStruct(), &InAsset->GetHairGroupsLOD()[InIndex], OutRootJsonObject, 0, 0); break;
|
|
};
|
|
}
|
|
|
|
static void PasteGroupFromJson(UGroomAsset* InAsset, int32 InIndex, EMaterialPanelType InType, TSharedRef<FJsonObject> InRootJsonObject)
|
|
{
|
|
if (!IsValidGroup(InAsset, InIndex, InType)) return;
|
|
|
|
switch (InType)
|
|
{
|
|
case EMaterialPanelType::Strands: FJsonObjectConverter::JsonObjectToUStruct(InRootJsonObject, FHairGroupsRendering::StaticStruct(), &InAsset->GetHairGroupsRendering()[InIndex], 0, 0); break;
|
|
case EMaterialPanelType::Cards: FJsonObjectConverter::JsonObjectToUStruct(InRootJsonObject, FHairGroupsCardsSourceDescription::StaticStruct(), &InAsset->GetHairGroupsCards()[InIndex], 0, 0); break;
|
|
case EMaterialPanelType::Meshes: FJsonObjectConverter::JsonObjectToUStruct(InRootJsonObject, FHairGroupsMeshesSourceDescription::StaticStruct(), &InAsset->GetHairGroupsMeshes()[InIndex], 0, 0); break;
|
|
case EMaterialPanelType::Physics: FJsonObjectConverter::JsonObjectToUStruct(InRootJsonObject, FHairGroupsPhysics::StaticStruct(), &InAsset->GetHairGroupsPhysics()[InIndex], 0, 0); break;
|
|
case EMaterialPanelType::Interpolation: FJsonObjectConverter::JsonObjectToUStruct(InRootJsonObject, FHairGroupsInterpolation::StaticStruct(), &InAsset->GetHairGroupsInterpolation()[InIndex], 0, 0); break;
|
|
case EMaterialPanelType::LODs: FJsonObjectConverter::JsonObjectToUStruct(InRootJsonObject, FHairGroupsLOD::StaticStruct(), &InAsset->GetHairGroupsLOD()[InIndex], 0, 0); break;
|
|
};
|
|
}
|
|
|
|
static FProperty* GetGroupPropertyFromType(EMaterialPanelType InType)
|
|
{
|
|
switch (InType)
|
|
{
|
|
case EMaterialPanelType::Strands: return UGroomAsset::StaticClass()->FindPropertyByName(UGroomAsset::GetHairGroupsRenderingMemberName());
|
|
case EMaterialPanelType::Cards: return UGroomAsset::StaticClass()->FindPropertyByName(UGroomAsset::GetHairGroupsCardsMemberName());
|
|
case EMaterialPanelType::Meshes: return UGroomAsset::StaticClass()->FindPropertyByName(UGroomAsset::GetHairGroupsMeshesMemberName());
|
|
case EMaterialPanelType::Physics: return UGroomAsset::StaticClass()->FindPropertyByName(UGroomAsset::GetHairGroupsPhysicsMemberName());
|
|
case EMaterialPanelType::Interpolation: return UGroomAsset::StaticClass()->FindPropertyByName(UGroomAsset::GetHairGroupsInterpolationMemberName());
|
|
case EMaterialPanelType::LODs: return UGroomAsset::StaticClass()->FindPropertyByName(UGroomAsset::GetHairGroupsLODMemberName());
|
|
};
|
|
return nullptr;
|
|
}
|
|
|
|
void FGroomRenderingDetails::OnCopyGroupItem(int32 CurrentSlot, EMaterialPanelType InType)
|
|
{
|
|
TSharedRef<FJsonObject> RootJsonObject = MakeShareable(new FJsonObject());
|
|
|
|
CopyGroupToJson(GroomAsset, CurrentSlot, PanelType, RootJsonObject);
|
|
|
|
typedef TJsonWriter<TCHAR, TPrettyJsonPrintPolicy<TCHAR>> FStringWriter;
|
|
typedef TJsonWriterFactory<TCHAR, TPrettyJsonPrintPolicy<TCHAR>> FStringWriterFactory;
|
|
|
|
FString CopyStr;
|
|
TSharedRef<FStringWriter> Writer = FStringWriterFactory::Create(&CopyStr);
|
|
FJsonSerializer::Serialize(RootJsonObject, Writer);
|
|
|
|
if (!CopyStr.IsEmpty())
|
|
{
|
|
FPlatformApplicationMisc::ClipboardCopy(*CopyStr);
|
|
}
|
|
}
|
|
|
|
bool FGroomRenderingDetails::OnCanCopyGroupItem(int32 CurrentSlot, EMaterialPanelType InType) const
|
|
{
|
|
return IsValidGroup(GroomAsset, CurrentSlot, InType);
|
|
}
|
|
|
|
void FGroomRenderingDetails::OnPasteGroupItem(int32 CurrentSlot, EMaterialPanelType InType)
|
|
{
|
|
FString PastedText;
|
|
FPlatformApplicationMisc::ClipboardPaste(PastedText);
|
|
|
|
TSharedPtr<FJsonObject> RootJsonObject;
|
|
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(PastedText);
|
|
FJsonSerializer::Deserialize(Reader, RootJsonObject);
|
|
|
|
if (RootJsonObject.IsValid())
|
|
{
|
|
if (FProperty* Property = GetGroupPropertyFromType(PanelType))
|
|
{
|
|
GroomAsset->PreEditChange(Property);
|
|
|
|
FScopedTransaction Transaction(LOCTEXT("GroomAssetChangedPasteGroup", "GroomAsset editor: Pasted group"));
|
|
GroomAsset->Modify();
|
|
PasteGroupFromJson(GroomAsset, CurrentSlot, InType, RootJsonObject.ToSharedRef());
|
|
CallPostEditChange(Property);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FGroomRenderingDetails::CallPostEditChange(FProperty* PropertyChanged/*=nullptr*/)
|
|
{
|
|
if (PropertyChanged)
|
|
{
|
|
FPropertyChangedEvent PropertyUpdateStruct(PropertyChanged);
|
|
GroomAsset->PostEditChangeProperty(PropertyUpdateStruct);
|
|
}
|
|
else
|
|
{
|
|
GroomAsset->Modify();
|
|
GroomAsset->PostEditChange();
|
|
}
|
|
GroomDetailLayout->ForceRefreshDetails();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Details view
|
|
|
|
FGroomRenderingDetails::FGroomRenderingDetails(IGroomCustomAssetEditorToolkit* InToolkit, EMaterialPanelType Type)
|
|
{
|
|
if (InToolkit)
|
|
{
|
|
GroomAsset = InToolkit->GetCustomAsset();
|
|
Toolkit = InToolkit;
|
|
}
|
|
bDeleteWarningConsumed = false;
|
|
PanelType = Type;
|
|
}
|
|
|
|
FGroomRenderingDetails::~FGroomRenderingDetails()
|
|
{
|
|
|
|
}
|
|
|
|
TSharedRef<IDetailCustomization> FGroomRenderingDetails::MakeInstance(IGroomCustomAssetEditorToolkit* InToolkit, EMaterialPanelType Type)
|
|
{
|
|
return MakeShareable(new FGroomRenderingDetails(InToolkit, Type));
|
|
}
|
|
|
|
FName GetCategoryName(EMaterialPanelType Type)
|
|
{
|
|
switch (Type)
|
|
{
|
|
case EMaterialPanelType::Strands: return FName(TEXT("Strands"));
|
|
case EMaterialPanelType::Cards: return FName(TEXT("Cards"));
|
|
case EMaterialPanelType::Meshes: return FName(TEXT("Meshes"));
|
|
case EMaterialPanelType::Interpolation: return FName(TEXT("Interpolation"));
|
|
case EMaterialPanelType::LODs: return FName(TEXT("LODs"));
|
|
case EMaterialPanelType::Physics: return FName(TEXT("Physics"));
|
|
case EMaterialPanelType::Bindings: return FName(TEXT("Bindings"));
|
|
case EMaterialPanelType::Dataflow: return FName(TEXT("Dataflow"));
|
|
}
|
|
return FName(TEXT("Unknown"));
|
|
}
|
|
|
|
void FGroomRenderingDetails::ApplyChanges()
|
|
{
|
|
GroomDetailLayout->ForceRefreshDetails();
|
|
}
|
|
|
|
void FGroomRenderingDetails::CustomizeDetails(IDetailLayoutBuilder& DetailLayout)
|
|
{
|
|
const TArray<TWeakObjectPtr<UObject>>& SelectedObjects = DetailLayout.GetSelectedObjects();
|
|
check(SelectedObjects.Num() <= 1); // The OnGenerateCustomWidgets delegate will not be useful if we try to process more than one object.
|
|
|
|
FName CategoryName = GetCategoryName(PanelType);
|
|
GroomDetailLayout = &DetailLayout;
|
|
if(SelectedObjects.Num() > 0)
|
|
{
|
|
if (UGroomAsset* LocalGroomAsset = Cast<UGroomAsset>(SelectedObjects[0].Get()))
|
|
{
|
|
GroomAsset = LocalGroomAsset;
|
|
|
|
IDetailCategoryBuilder& HairGroupCategory = DetailLayout.EditCategory(CategoryName, FText::GetEmpty(), ECategoryPriority::TypeSpecific);
|
|
CustomizeStrandsGroupProperties(DetailLayout, HairGroupCategory);
|
|
}
|
|
else if (UGroomBindingAssetList* LocalGroomBindingList = Cast<UGroomBindingAssetList>(SelectedObjects[0].Get()))
|
|
{
|
|
GroomBindingAssetList = LocalGroomBindingList;
|
|
|
|
IDetailCategoryBuilder& HairGroupCategory = DetailLayout.EditCategory(CategoryName, FText::GetEmpty(), ECategoryPriority::TypeSpecific);
|
|
CustomizeStrandsGroupProperties(DetailLayout, HairGroupCategory);
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Custom widget for material slot for hair rendering
|
|
|
|
void FGroomRenderingDetails::AddNewGroupButton(IDetailCategoryBuilder& FilesCategory, FProperty* Property, const FText& HeaderText)
|
|
{
|
|
// Add a button for adding element to the hair groups array
|
|
FilesCategory.AddCustomRow(FText::FromString(TEXT("AddGroup")))
|
|
.ValueContent()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(2.f, 2.f, 10.f, 2.f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(IDetailLayoutBuilder::GetDetailFont())
|
|
.Text(HeaderText)
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Center)
|
|
.ButtonStyle(FAppStyle::Get(), "HoverHintOnly")
|
|
.OnClicked(this, &FGroomRenderingDetails::OnAddGroup, Property)
|
|
[
|
|
SNew(SImage)
|
|
.Image(FAppStyle::GetBrush("Icons.PlusCircle"))
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
static void AddLODModeProperties(const IDetailLayoutBuilder& DetailLayout, IDetailCategoryBuilder& FilesCategory)
|
|
{
|
|
const TSharedRef<IPropertyHandle> LODModeProperty = DetailLayout.GetProperty(UGroomAsset::GetLODModeMemberName(), UGroomAsset::StaticClass());
|
|
const TSharedRef<IPropertyHandle> LODBiasProperty = DetailLayout.GetProperty(UGroomAsset::GetAutoLODBiasMemberName(), UGroomAsset::StaticClass());
|
|
|
|
FilesCategory.AddProperty(LODModeProperty);
|
|
FilesCategory.AddProperty(LODBiasProperty);
|
|
}
|
|
|
|
void FGroomRenderingDetails::CustomizeStrandsGroupProperties(IDetailLayoutBuilder& DetailLayout, IDetailCategoryBuilder& FilesCategory)
|
|
{
|
|
switch (PanelType)
|
|
{
|
|
case EMaterialPanelType::Cards:
|
|
{
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsInterpolationMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsRenderingMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsPhysicsMemberName(), UGroomAsset::StaticClass()));
|
|
// DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsCardsMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsMeshesMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsMaterialsMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsLODMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsInfoMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetEnableGlobalInterpolationMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetEnableSimulationCacheMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairInterpolationTypeMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetMinLODMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetDisableBelowMinLodStrippingMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetRiggedSkeletalMeshMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetLODModeMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetAutoLODBiasMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetDataflowSettingsMemberName(), UGroomAsset::StaticClass()));
|
|
}
|
|
break;
|
|
case EMaterialPanelType::Meshes:
|
|
{
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsInterpolationMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsRenderingMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsPhysicsMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsCardsMemberName(), UGroomAsset::StaticClass()));
|
|
// DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsMeshesMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsMaterialsMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsLODMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsInfoMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetEnableGlobalInterpolationMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetEnableSimulationCacheMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairInterpolationTypeMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetMinLODMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetDisableBelowMinLodStrippingMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetRiggedSkeletalMeshMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetLODModeMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetAutoLODBiasMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetDataflowSettingsMemberName(), UGroomAsset::StaticClass()));
|
|
}
|
|
break;
|
|
case EMaterialPanelType::Strands:
|
|
{
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsInterpolationMemberName(), UGroomAsset::StaticClass()));
|
|
// DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsRenderingMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsPhysicsMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsCardsMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsMeshesMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsMaterialsMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsLODMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsInfoMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetEnableGlobalInterpolationMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetEnableSimulationCacheMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairInterpolationTypeMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetMinLODMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetDisableBelowMinLodStrippingMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetRiggedSkeletalMeshMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetLODModeMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetAutoLODBiasMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetDataflowSettingsMemberName(), UGroomAsset::StaticClass()));
|
|
}
|
|
break;
|
|
case EMaterialPanelType::Physics:
|
|
{
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsInterpolationMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsRenderingMemberName(), UGroomAsset::StaticClass()));
|
|
// DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsPhysicsMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsCardsMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsMeshesMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsMaterialsMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsLODMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsInfoMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetEnableGlobalInterpolationMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetEnableSimulationCacheMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairInterpolationTypeMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetMinLODMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetDisableBelowMinLodStrippingMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetRiggedSkeletalMeshMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetLODModeMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetAutoLODBiasMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetDataflowSettingsMemberName(), UGroomAsset::StaticClass()));
|
|
}
|
|
break;
|
|
case EMaterialPanelType::Interpolation:
|
|
{
|
|
// DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsInterpolationMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsRenderingMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsPhysicsMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsCardsMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsMeshesMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsMaterialsMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsLODMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsInfoMemberName(), UGroomAsset::StaticClass()));
|
|
// DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetEnableGlobalInterpolationMemberName(), UGroomAsset::StaticClass()));
|
|
// DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetEnableSimulationCacheMemberName(), UGroomAsset::StaticClass()));
|
|
// DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairInterpolationTypeMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetMinLODMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetDisableBelowMinLodStrippingMemberName(), UGroomAsset::StaticClass()));
|
|
// DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetRiggedSkeletalMeshMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetLODModeMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetAutoLODBiasMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetDataflowSettingsMemberName(), UGroomAsset::StaticClass()));
|
|
}
|
|
break;
|
|
case EMaterialPanelType::LODs:
|
|
{
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsInterpolationMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsRenderingMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsPhysicsMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsCardsMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsMeshesMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsMaterialsMemberName(), UGroomAsset::StaticClass()));
|
|
// DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsLODMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsInfoMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetEnableGlobalInterpolationMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetEnableSimulationCacheMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairInterpolationTypeMemberName(), UGroomAsset::StaticClass()));
|
|
// DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetMinLODMemberName(), UGroomAsset::StaticClass()));
|
|
// DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetDisableBelowMinLodStrippingMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetRiggedSkeletalMeshMemberName(), UGroomAsset::StaticClass()));
|
|
// DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetLODModeMemberName(), UGroomAsset::StaticClass()));
|
|
// DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetAutoLODBiasMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetDataflowSettingsMemberName(), UGroomAsset::StaticClass()));
|
|
}
|
|
break;
|
|
case EMaterialPanelType::Dataflow:
|
|
{
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsInterpolationMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsRenderingMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsPhysicsMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsCardsMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsMeshesMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsMaterialsMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsLODMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairGroupsInfoMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetEnableGlobalInterpolationMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetEnableSimulationCacheMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetHairInterpolationTypeMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetMinLODMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetDisableBelowMinLodStrippingMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetRiggedSkeletalMeshMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetLODModeMemberName(), UGroomAsset::StaticClass()));
|
|
DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetAutoLODBiasMemberName(), UGroomAsset::StaticClass()));
|
|
//DetailLayout.HideProperty(DetailLayout.GetProperty(UGroomAsset::GetDataflowSettingsMemberName(), UGroomAsset::StaticClass()));
|
|
}
|
|
break;
|
|
}
|
|
|
|
switch (PanelType)
|
|
{
|
|
case EMaterialPanelType::Cards:
|
|
{
|
|
TSharedRef<IPropertyHandle> Property = DetailLayout.GetProperty(UGroomAsset::GetHairGroupsCardsMemberName(), UGroomAsset::StaticClass());
|
|
AddNewGroupButton(FilesCategory, Property->GetProperty(), FText::FromString(TEXT("Add Card asset")));
|
|
if (Property->IsValidHandle())
|
|
{
|
|
TSharedRef<FDetailArrayBuilder> PropertyBuilder = MakeShareable(new FDetailArrayBuilder(Property, false, false, false));
|
|
PropertyBuilder->OnGenerateArrayElementWidget(FOnGenerateArrayElementWidget::CreateSP(this, &FGroomRenderingDetails::OnGenerateElementForHairGroup, &DetailLayout));
|
|
PropertyBuilder->SetDisplayName(FText::FromString(TEXT("Cards assets")));
|
|
FilesCategory.AddCustomBuilder(PropertyBuilder, false);
|
|
}
|
|
}
|
|
break;
|
|
case EMaterialPanelType::Meshes:
|
|
{
|
|
TSharedRef<IPropertyHandle> Property = DetailLayout.GetProperty(UGroomAsset::GetHairGroupsMeshesMemberName(), UGroomAsset::StaticClass());
|
|
AddNewGroupButton(FilesCategory, Property->GetProperty(), FText::FromString(TEXT("Add Mesh asset")));
|
|
if (Property->IsValidHandle())
|
|
{
|
|
TSharedRef<FDetailArrayBuilder> PropertyBuilder = MakeShareable(new FDetailArrayBuilder(Property, false, false, false));
|
|
PropertyBuilder->OnGenerateArrayElementWidget(FOnGenerateArrayElementWidget::CreateSP(this, &FGroomRenderingDetails::OnGenerateElementForHairGroup, &DetailLayout));
|
|
PropertyBuilder->SetDisplayName(FText::FromString(TEXT("Meshes assets")));
|
|
FilesCategory.AddCustomBuilder(PropertyBuilder, false);
|
|
}
|
|
}
|
|
break;
|
|
case EMaterialPanelType::Strands:
|
|
{
|
|
TSharedRef<IPropertyHandle> Property = DetailLayout.GetProperty(UGroomAsset::GetHairGroupsRenderingMemberName(), UGroomAsset::StaticClass());
|
|
if (Property->IsValidHandle())
|
|
{
|
|
TSharedRef<FDetailArrayBuilder> PropertyBuilder = MakeShareable(new FDetailArrayBuilder(Property, false, false, false));
|
|
PropertyBuilder->OnGenerateArrayElementWidget(FOnGenerateArrayElementWidget::CreateSP(this, &FGroomRenderingDetails::OnGenerateElementForHairGroup, &DetailLayout));
|
|
PropertyBuilder->SetDisplayName(FText::FromString(TEXT("Strands Groups")));
|
|
FilesCategory.AddCustomBuilder(PropertyBuilder, false);
|
|
}
|
|
}
|
|
break;
|
|
case EMaterialPanelType::Physics:
|
|
{
|
|
TSharedRef<IPropertyHandle> Property = DetailLayout.GetProperty(UGroomAsset::GetHairGroupsPhysicsMemberName(), UGroomAsset::StaticClass());
|
|
if (Property->IsValidHandle())
|
|
{
|
|
TSharedRef<FDetailArrayBuilder> PropertyBuilder = MakeShareable(new FDetailArrayBuilder(Property, false, false, false));
|
|
PropertyBuilder->OnGenerateArrayElementWidget(FOnGenerateArrayElementWidget::CreateSP(this, &FGroomRenderingDetails::OnGenerateElementForHairGroup, &DetailLayout));
|
|
PropertyBuilder->SetDisplayName(FText::FromString(TEXT("Physics Groups")));
|
|
FilesCategory.AddCustomBuilder(PropertyBuilder, false);
|
|
}
|
|
}
|
|
break;
|
|
case EMaterialPanelType::Interpolation:
|
|
{
|
|
TSharedRef<IPropertyHandle> Property = DetailLayout.GetProperty(UGroomAsset::GetHairGroupsInterpolationMemberName(), UGroomAsset::StaticClass());
|
|
if (Property->IsValidHandle())
|
|
{
|
|
TSharedRef<FDetailArrayBuilder> PropertyBuilder = MakeShareable(new FDetailArrayBuilder(Property, false, false, false));
|
|
PropertyBuilder->OnGenerateArrayElementWidget(FOnGenerateArrayElementWidget::CreateSP(this, &FGroomRenderingDetails::OnGenerateElementForHairGroup, &DetailLayout));
|
|
PropertyBuilder->SetDisplayName(FText::FromString(TEXT("Interpolation Groups")));
|
|
FilesCategory.AddCustomBuilder(PropertyBuilder, false);
|
|
}
|
|
}
|
|
break;
|
|
case EMaterialPanelType::LODs:
|
|
{
|
|
TSharedRef<IPropertyHandle> Property = DetailLayout.GetProperty(UGroomAsset::GetHairGroupsLODMemberName(), UGroomAsset::StaticClass());
|
|
AddLODModeProperties(DetailLayout, FilesCategory);
|
|
if (Property->IsValidHandle())
|
|
{
|
|
TSharedRef<FDetailArrayBuilder> PropertyBuilder = MakeShareable(new FDetailArrayBuilder(Property, false, false, false));
|
|
PropertyBuilder->OnGenerateArrayElementWidget(FOnGenerateArrayElementWidget::CreateSP(this, &FGroomRenderingDetails::OnGenerateElementForHairGroup, &DetailLayout));
|
|
PropertyBuilder->SetDisplayName(FText::FromString(TEXT("LOD Groups")));
|
|
FilesCategory.AddCustomBuilder(PropertyBuilder, false);
|
|
}
|
|
}
|
|
break;
|
|
case EMaterialPanelType::Bindings:
|
|
{
|
|
TSharedRef<IPropertyHandle> Property = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UGroomBindingAssetList, Bindings), UGroomBindingAssetList::StaticClass());
|
|
if (Property->IsValidHandle())
|
|
{
|
|
TSharedRef<FDetailArrayBuilder> PropertyBuilder = MakeShareable(new FDetailArrayBuilder(Property, false, false, false));
|
|
PropertyBuilder->OnGenerateArrayElementWidget(FOnGenerateArrayElementWidget::CreateSP(this, &FGroomRenderingDetails::OnGenerateElementForBindingAsset, &DetailLayout));
|
|
PropertyBuilder->SetDisplayName(FText::FromString(TEXT("Bindings")));
|
|
FilesCategory.AddCustomBuilder(PropertyBuilder, false);
|
|
}
|
|
}
|
|
break;
|
|
case EMaterialPanelType::Dataflow:
|
|
{
|
|
FilesCategory.AddProperty(DetailLayout.GetProperty(FGroomDataflowSettings::GetDataflowAssetMemberName(), UGroomAsset::StaticClass()));
|
|
FilesCategory.AddProperty(DetailLayout.GetProperty(FGroomDataflowSettings::GetDataflowTerminalMemberName(), UGroomAsset::StaticClass()));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
FReply FGroomRenderingDetails::OnAddGroup(FProperty* Property)
|
|
{
|
|
check(GroomAsset);
|
|
switch (PanelType)
|
|
{
|
|
case EMaterialPanelType::Cards:
|
|
{
|
|
FScopedTransaction Transaction(FText::FromString(TEXT("AddCardsGroup")));
|
|
GroomAsset->GetHairGroupsCards().AddDefaulted();
|
|
|
|
const int32 LODCount = GroomAsset->GetHairGroupsCards().Num();
|
|
if (LODCount > 1)
|
|
{
|
|
const FHairGroupsCardsSourceDescription& Prev = GroomAsset->GetHairGroupsCards()[LODCount - 2];
|
|
FHairGroupsCardsSourceDescription& Current = GroomAsset->GetHairGroupsCards()[LODCount - 1];
|
|
|
|
Current.GroupIndex = Prev.GroupIndex;
|
|
Current.LODIndex = FMath::Min(Prev.LODIndex + 1, 7);
|
|
}
|
|
else
|
|
{
|
|
FHairGroupsCardsSourceDescription& Current = GroomAsset->GetHairGroupsCards()[LODCount - 1];
|
|
Current.LODIndex = 0;
|
|
}
|
|
|
|
FPropertyChangedEvent PropertyChangedEvent(Property, EPropertyChangeType::ArrayAdd);
|
|
GroomAsset->PostEditChangeProperty(PropertyChangedEvent);
|
|
}
|
|
break;
|
|
case EMaterialPanelType::Meshes:
|
|
{
|
|
FScopedTransaction Transaction(FText::FromString(TEXT("AddMeshesGroup")));
|
|
GroomAsset->GetHairGroupsMeshes().AddDefaulted();
|
|
|
|
FPropertyChangedEvent PropertyChangedEvent(Property, EPropertyChangeType::ArrayAdd);
|
|
GroomAsset->PostEditChangeProperty(PropertyChangedEvent);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply FGroomRenderingDetails::OnSelectBinding(int32 BindingIndex, FProperty* Property)
|
|
{
|
|
check(GroomBindingAssetList);
|
|
|
|
// If user click twice onto the same binding index, we disable the binding;
|
|
BindingIndex = Toolkit->GetActiveBindingIndex() == BindingIndex ? -1 : BindingIndex;
|
|
Toolkit->PreviewBinding(BindingIndex);
|
|
ApplyChanges();
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FName& FGroomRenderingDetails::GetMaterialSlotName(int32 GroupIndex)
|
|
{
|
|
check(GroomAsset);
|
|
switch (PanelType)
|
|
{
|
|
case EMaterialPanelType::Cards: return GroomAsset->GetHairGroupsCards()[GroupIndex].MaterialSlotName;
|
|
case EMaterialPanelType::Meshes: return GroomAsset->GetHairGroupsMeshes()[GroupIndex].MaterialSlotName;
|
|
case EMaterialPanelType::Strands: return GroomAsset->GetHairGroupsRendering()[GroupIndex].MaterialSlotName;
|
|
}
|
|
|
|
static FName Default;
|
|
return Default;
|
|
}
|
|
|
|
const FName& FGroomRenderingDetails::GetMaterialSlotName(int32 GroupIndex) const
|
|
{
|
|
static const FName Default(TEXT("Invalid"));
|
|
check(GroomAsset);
|
|
switch (PanelType)
|
|
{
|
|
case EMaterialPanelType::Cards: return GroupIndex < GroomAsset->GetHairGroupsCards().Num() ? GroomAsset->GetHairGroupsCards()[GroupIndex].MaterialSlotName : Default;
|
|
case EMaterialPanelType::Meshes: return GroupIndex < GroomAsset->GetHairGroupsMeshes().Num() ? GroomAsset->GetHairGroupsMeshes()[GroupIndex].MaterialSlotName : Default;
|
|
case EMaterialPanelType::Strands: return GroupIndex < GroomAsset->GetHairGroupsRendering().Num() ? GroomAsset->GetHairGroupsRendering()[GroupIndex].MaterialSlotName : Default;
|
|
}
|
|
|
|
return Default;
|
|
}
|
|
|
|
int32 FGroomRenderingDetails::GetGroupCount() const
|
|
{
|
|
check(GroomAsset);
|
|
switch (PanelType)
|
|
{
|
|
case EMaterialPanelType::Cards: return GroomAsset->GetHairGroupsCards().Num();
|
|
case EMaterialPanelType::Meshes: return GroomAsset->GetHairGroupsMeshes().Num();
|
|
case EMaterialPanelType::Strands: return GroomAsset->GetHairGroupsRendering().Num();
|
|
case EMaterialPanelType::Physics: return GroomAsset->GetHairGroupsPhysics().Num();
|
|
case EMaterialPanelType::Interpolation: return GroomAsset->GetHairGroupsInterpolation().Num();
|
|
case EMaterialPanelType::LODs: return GroomAsset->GetHairGroupsLOD().Num();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
FReply FGroomRenderingDetails::OnRemoveGroupClicked(int32 GroupIndex, FProperty* Property)
|
|
{
|
|
check(GroomAsset);
|
|
switch (PanelType)
|
|
{
|
|
case EMaterialPanelType::Cards:
|
|
{
|
|
if (GroupIndex < GroomAsset->GetHairGroupsCards().Num())
|
|
{
|
|
FScopedTransaction Transaction(FText::FromString(TEXT("RemoveCardsGroup")));
|
|
GroomAsset->GetHairGroupsCards().RemoveAt(GroupIndex);
|
|
|
|
FPropertyChangedEvent PropertyChangedEvent(Property, EPropertyChangeType::ArrayRemove);
|
|
GroomAsset->PostEditChangeProperty(PropertyChangedEvent);
|
|
}
|
|
}
|
|
break;
|
|
case EMaterialPanelType::Meshes:
|
|
{
|
|
if (GroupIndex < GroomAsset->GetHairGroupsMeshes().Num())
|
|
{
|
|
FScopedTransaction Transaction(FText::FromString(TEXT("RemoveMeshesGroup")));
|
|
GroomAsset->GetHairGroupsMeshes().RemoveAt(GroupIndex);
|
|
|
|
FPropertyChangedEvent PropertyChangedEvent(Property, EPropertyChangeType::ArrayRemove);
|
|
GroomAsset->PostEditChangeProperty(PropertyChangedEvent);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
void FGroomRenderingDetails::SetMaterialSlot(int32 GroupIndex, int32 MaterialIndex)
|
|
{
|
|
if (!GroomAsset || GroupIndex < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int32 GroupCount = GetGroupCount();
|
|
if (MaterialIndex == INDEX_NONE)
|
|
{
|
|
GetMaterialSlotName(GroupIndex) = NAME_None;
|
|
}
|
|
else if (GroupIndex < GroupCount && MaterialIndex >= 0 && MaterialIndex < GroomAsset->GetHairGroupsMaterials().Num())
|
|
{
|
|
GetMaterialSlotName(GroupIndex) = GroomAsset->GetHairGroupsMaterials()[MaterialIndex].SlotName;
|
|
}
|
|
|
|
GroomAsset->MarkMaterialsHasChanged();
|
|
}
|
|
|
|
TSharedRef<SWidget> FGroomRenderingDetails::OnGenerateStrandsMaterialMenuPicker(int32 GroupIndex)
|
|
{
|
|
if (GroomAsset == nullptr)
|
|
{
|
|
return SNullWidget::NullWidget;
|
|
}
|
|
|
|
const int32 MaterialCount = GroomAsset->GetHairGroupsMaterials().Num();
|
|
if(MaterialCount == 0)
|
|
{
|
|
return SNullWidget::NullWidget;
|
|
}
|
|
FMenuBuilder MenuBuilder(true, NULL);
|
|
|
|
// Default material
|
|
{
|
|
int32 MaterialIt = INDEX_NONE;
|
|
FText DefaultString = FText::FromString(TEXT("Default"));
|
|
FUIAction Action(FExecuteAction::CreateSP(this, &FGroomRenderingDetails::SetMaterialSlot, GroupIndex, MaterialIt));
|
|
MenuBuilder.AddMenuEntry(DefaultString, FText::GetEmpty(), FSlateIcon(), Action);
|
|
}
|
|
|
|
// Add a menu item for material
|
|
for (int32 MaterialIt = 0; MaterialIt < MaterialCount; ++MaterialIt)
|
|
{
|
|
FText MaterialString = FText::FromString(FString::FromInt(MaterialIt) + TEXT(" - ") + GroomAsset->GetHairGroupsMaterials()[MaterialIt].SlotName.ToString());
|
|
FUIAction Action(FExecuteAction::CreateSP(this, &FGroomRenderingDetails::SetMaterialSlot, GroupIndex, MaterialIt));
|
|
MenuBuilder.AddMenuEntry(MaterialString, FText::GetEmpty(), FSlateIcon(), Action);
|
|
}
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
FText FGroomRenderingDetails::GetStrandsMaterialName(int32 GroupIndex) const
|
|
{
|
|
const FName& MaterialSlotName = GetMaterialSlotName(GroupIndex);
|
|
const int32 MaterialIndex = GroomAsset->GetMaterialIndex(MaterialSlotName);
|
|
FText MaterialString = FText::FromString(TEXT("Default"));
|
|
if (MaterialIndex != INDEX_NONE)
|
|
{
|
|
MaterialString = FText::FromString(FString::FromInt(MaterialIndex) + TEXT(" - ") + GroomAsset->GetHairGroupsMaterials()[MaterialIndex].SlotName.ToString());
|
|
}
|
|
return MaterialString;
|
|
}
|
|
|
|
TSharedRef<SWidget> FGroomRenderingDetails::OnGenerateStrandsMaterialPicker(int32 GroupIndex, IDetailLayoutBuilder* DetailLayoutBuilder)
|
|
{
|
|
return CreateMaterialSwatch(DetailLayoutBuilder->GetThumbnailPool(), GroupIndex);
|
|
}
|
|
|
|
bool FGroomRenderingDetails::IsStrandsMaterialPickerEnabled(int32 GroupIndex) const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
|
|
template <typename T>
|
|
bool AssignIfDifferent(T& Dest, const T& Src, bool bSetValue)
|
|
{
|
|
const bool bHasChanged = Dest != Src;
|
|
if (bHasChanged && bSetValue)
|
|
{
|
|
Dest = Src;
|
|
}
|
|
return bHasChanged;
|
|
}
|
|
|
|
#define HAIR_RESET0(GroomMemberName, StructTypeName, MemberName) { if (PropertyName == GET_MEMBER_NAME_CHECKED(StructTypeName, MemberName)) { bHasChanged = AssignIfDifferent(GroomAsset->GroomMemberName[GroupIndex].MemberName, Default.MemberName, bSetValue); } }
|
|
#define HAIR_RESET1(GroomMemberName, StructTypeName, StructMemberName, MemberName) { if (PropertyName == GET_MEMBER_NAME_CHECKED(StructTypeName, MemberName)) { bHasChanged = AssignIfDifferent(GroomAsset->GroomMemberName[GroupIndex].StructMemberName.MemberName, Default.MemberName, bSetValue); } }
|
|
#define HAIR_RESET2(GroomMemberName, StructTypeName, StructMemberName, SubStructMemberName, MemberName) { if (PropertyName == GET_MEMBER_NAME_CHECKED(StructTypeName, MemberName)) { bHasChanged = AssignIfDifferent(GroomAsset->GroomMemberName[GroupIndex].StructMemberName.SubStructMemberName.MemberName, Default.MemberName, bSetValue); } }
|
|
|
|
bool FGroomRenderingDetails::CommonResetToDefault(TSharedPtr<IPropertyHandle> ChildHandle, int32 GroupIndex, int32 LODIndex, bool bSetValue)
|
|
{
|
|
bool bHasChanged = false;
|
|
if (ChildHandle == nullptr || GroomAsset == nullptr || GroupIndex < 0)
|
|
{
|
|
return bHasChanged;
|
|
}
|
|
|
|
FName PropertyName = ChildHandle->GetProperty()->GetFName();
|
|
|
|
// For cards & meshes the incoming index is actually the cards/mesh description index, not the group index
|
|
// For the rest, the group index refers to the actual group index.
|
|
const bool bIsCardDescIndexValid = GroupIndex < GroomAsset->GetHairGroupsCards().Num();
|
|
const bool bIsMeshDescIndexValid = GroupIndex < GroomAsset->GetHairGroupsMeshes().Num();
|
|
const bool bIsGroupIndexValid = GroupIndex < GroomAsset->GetNumHairGroups();
|
|
|
|
// Hair strands
|
|
if (bIsGroupIndexValid)
|
|
{
|
|
{
|
|
FHairGeometrySettings Default;
|
|
HAIR_RESET1(GetHairGroupsRendering(), FHairGeometrySettings, GeometrySettings, HairWidth);
|
|
HAIR_RESET1(GetHairGroupsRendering(), FHairGeometrySettings, GeometrySettings, HairRootScale);
|
|
HAIR_RESET1(GetHairGroupsRendering(), FHairGeometrySettings, GeometrySettings, HairTipScale);
|
|
}
|
|
|
|
{
|
|
FHairShadowSettings Default;
|
|
HAIR_RESET1(GetHairGroupsRendering(), FHairShadowSettings, ShadowSettings, bVoxelize);
|
|
HAIR_RESET1(GetHairGroupsRendering(), FHairShadowSettings, ShadowSettings, bUseHairRaytracingGeometry);
|
|
HAIR_RESET1(GetHairGroupsRendering(), FHairShadowSettings, ShadowSettings, HairRaytracingRadiusScale);
|
|
HAIR_RESET1(GetHairGroupsRendering(), FHairShadowSettings, ShadowSettings, HairRaytracingRadiusScale);
|
|
}
|
|
|
|
{
|
|
FHairAdvancedRenderingSettings Default;
|
|
HAIR_RESET1(GetHairGroupsRendering(), FHairAdvancedRenderingSettings, AdvancedSettings, bScatterSceneLighting);
|
|
HAIR_RESET1(GetHairGroupsRendering(), FHairAdvancedRenderingSettings, AdvancedSettings, bUseStableRasterization);
|
|
}
|
|
}
|
|
|
|
// Interpolation
|
|
if (bIsGroupIndexValid)
|
|
{
|
|
{
|
|
FHairDecimationSettings Default;
|
|
HAIR_RESET1(GetHairGroupsInterpolation(), FHairDecimationSettings, DecimationSettings, CurveDecimation);
|
|
HAIR_RESET1(GetHairGroupsInterpolation(), FHairDecimationSettings, DecimationSettings, VertexDecimation);
|
|
}
|
|
|
|
{
|
|
FHairInterpolationSettings Default;
|
|
HAIR_RESET1(GetHairGroupsInterpolation(), FHairInterpolationSettings, InterpolationSettings, GuideType);
|
|
HAIR_RESET1(GetHairGroupsInterpolation(), FHairInterpolationSettings, InterpolationSettings, HairToGuideDensity);
|
|
HAIR_RESET1(GetHairGroupsInterpolation(), FHairInterpolationSettings, InterpolationSettings, bRandomizeGuide);
|
|
HAIR_RESET1(GetHairGroupsInterpolation(), FHairInterpolationSettings, InterpolationSettings, bUseUniqueGuide);
|
|
HAIR_RESET1(GetHairGroupsInterpolation(), FHairInterpolationSettings, InterpolationSettings, RiggedGuideNumCurves);
|
|
HAIR_RESET1(GetHairGroupsInterpolation(), FHairInterpolationSettings, InterpolationSettings, RiggedGuideNumPoints);
|
|
}
|
|
}
|
|
|
|
// LODs
|
|
if (bIsGroupIndexValid)
|
|
{
|
|
if (LODIndex>=0 && LODIndex < GroomAsset->GetHairGroupsLOD()[GroupIndex].LODs.Num())
|
|
{
|
|
FHairLODSettings Default;
|
|
HAIR_RESET1(GetHairGroupsLOD(), FHairLODSettings, LODs[LODIndex], CurveDecimation);
|
|
HAIR_RESET1(GetHairGroupsLOD(), FHairLODSettings, LODs[LODIndex], VertexDecimation);
|
|
HAIR_RESET1(GetHairGroupsLOD(), FHairLODSettings, LODs[LODIndex], AngularThreshold);
|
|
HAIR_RESET1(GetHairGroupsLOD(), FHairLODSettings, LODs[LODIndex], ScreenSize);
|
|
HAIR_RESET1(GetHairGroupsLOD(), FHairLODSettings, LODs[LODIndex], ThicknessScale);
|
|
HAIR_RESET1(GetHairGroupsLOD(), FHairLODSettings, LODs[LODIndex], GeometryType);
|
|
}
|
|
}
|
|
|
|
// Cards
|
|
if (bIsCardDescIndexValid)
|
|
{
|
|
{
|
|
FHairGroupCardsTextures Default;
|
|
HAIR_RESET1(GetHairGroupsCards(), FHairGroupCardsTextures, Textures, Layout);
|
|
HAIR_RESET1(GetHairGroupsCards(), FHairGroupCardsTextures, Textures, Textures);
|
|
}
|
|
}
|
|
|
|
// Meshes
|
|
if (bIsMeshDescIndexValid)
|
|
{
|
|
{
|
|
FHairGroupsMeshesSourceDescription Default;
|
|
HAIR_RESET0(GetHairGroupsMeshes(), FHairGroupsMeshesSourceDescription, ImportedMesh);
|
|
HAIR_RESET0(GetHairGroupsMeshes(), FHairGroupsMeshesSourceDescription, GroupIndex);
|
|
HAIR_RESET0(GetHairGroupsMeshes(), FHairGroupsMeshesSourceDescription, LODIndex);
|
|
}
|
|
|
|
{
|
|
FHairGroupCardsTextures Default;
|
|
HAIR_RESET1(GetHairGroupsMeshes(), FHairGroupCardsTextures, Textures, Layout);
|
|
HAIR_RESET1(GetHairGroupsMeshes(), FHairGroupCardsTextures, Textures, Textures);
|
|
}
|
|
}
|
|
|
|
// Physics
|
|
if (bIsGroupIndexValid)
|
|
{
|
|
{
|
|
FHairSolverSettings Default;
|
|
HAIR_RESET1(GetHairGroupsPhysics(), FHairSolverSettings, SolverSettings, EnableSimulation);
|
|
HAIR_RESET1(GetHairGroupsPhysics(), FHairSolverSettings, SolverSettings, NiagaraSolver);
|
|
HAIR_RESET1(GetHairGroupsPhysics(), FHairSolverSettings, SolverSettings, CustomSystem);
|
|
HAIR_RESET1(GetHairGroupsPhysics(), FHairSolverSettings, SolverSettings, SubSteps);
|
|
HAIR_RESET1(GetHairGroupsPhysics(), FHairSolverSettings, SolverSettings, IterationCount);
|
|
}
|
|
|
|
{
|
|
FHairExternalForces Default;
|
|
HAIR_RESET1(GetHairGroupsPhysics(), FHairExternalForces, ExternalForces, GravityVector);
|
|
HAIR_RESET1(GetHairGroupsPhysics(), FHairExternalForces, ExternalForces, AirDrag);
|
|
HAIR_RESET1(GetHairGroupsPhysics(), FHairExternalForces, ExternalForces, AirVelocity);
|
|
}
|
|
|
|
{
|
|
FHairBendConstraint Default;
|
|
HAIR_RESET2(GetHairGroupsPhysics(), FHairBendConstraint, MaterialConstraints, BendConstraint, SolveBend);
|
|
HAIR_RESET2(GetHairGroupsPhysics(), FHairBendConstraint, MaterialConstraints, BendConstraint, ProjectBend);
|
|
HAIR_RESET2(GetHairGroupsPhysics(), FHairBendConstraint, MaterialConstraints, BendConstraint, BendDamping);
|
|
HAIR_RESET2(GetHairGroupsPhysics(), FHairBendConstraint, MaterialConstraints, BendConstraint, BendStiffness);
|
|
// HAIR_RESET2(GetHairGroupsPhysics(), FHairBendConstraint, MaterialConstraints, BendConstraint, BendScale);
|
|
}
|
|
|
|
{
|
|
FHairStretchConstraint Default;
|
|
HAIR_RESET2(GetHairGroupsPhysics(), FHairStretchConstraint, MaterialConstraints, StretchConstraint, SolveStretch);
|
|
HAIR_RESET2(GetHairGroupsPhysics(), FHairStretchConstraint, MaterialConstraints, StretchConstraint, ProjectStretch);
|
|
HAIR_RESET2(GetHairGroupsPhysics(), FHairStretchConstraint, MaterialConstraints, StretchConstraint, StretchDamping);
|
|
HAIR_RESET2(GetHairGroupsPhysics(), FHairStretchConstraint, MaterialConstraints, StretchConstraint, StretchStiffness);
|
|
// HAIR_RESET2(GetHairGroupsPhysics(), FHairStretchConstraint, MaterialConstraints, StretchConstraint, StretchScale);
|
|
}
|
|
|
|
{
|
|
FHairCollisionConstraint Default;
|
|
HAIR_RESET2(GetHairGroupsPhysics(), FHairCollisionConstraint, MaterialConstraints, CollisionConstraint, SolveCollision);
|
|
HAIR_RESET2(GetHairGroupsPhysics(), FHairCollisionConstraint, MaterialConstraints, CollisionConstraint, ProjectCollision);
|
|
HAIR_RESET2(GetHairGroupsPhysics(), FHairCollisionConstraint, MaterialConstraints, CollisionConstraint, StaticFriction);
|
|
HAIR_RESET2(GetHairGroupsPhysics(), FHairCollisionConstraint, MaterialConstraints, CollisionConstraint, KineticFriction);
|
|
HAIR_RESET2(GetHairGroupsPhysics(), FHairCollisionConstraint, MaterialConstraints, CollisionConstraint, StrandsViscosity);
|
|
HAIR_RESET2(GetHairGroupsPhysics(), FHairCollisionConstraint, MaterialConstraints, CollisionConstraint, GridDimension);
|
|
HAIR_RESET2(GetHairGroupsPhysics(), FHairCollisionConstraint, MaterialConstraints, CollisionConstraint, CollisionRadius);
|
|
// HAIR_RESET2(GetHairGroupsPhysics(), FHairCollisionConstraint, MaterialConstraints, CollisionConstraint, RadiusScale);
|
|
}
|
|
|
|
|
|
{
|
|
FHairStrandsParameters Default;
|
|
HAIR_RESET1(GetHairGroupsPhysics(), FHairStrandsParameters, StrandsParameters, StrandsSize);
|
|
HAIR_RESET1(GetHairGroupsPhysics(), FHairStrandsParameters, StrandsParameters, StrandsDensity);
|
|
HAIR_RESET1(GetHairGroupsPhysics(), FHairStrandsParameters, StrandsParameters, StrandsSmoothing);
|
|
HAIR_RESET1(GetHairGroupsPhysics(), FHairStrandsParameters, StrandsParameters, StrandsThickness);
|
|
// HAIR_RESET1(GetHairGroupsPhysics(), FHairStrandsParameters, StrandsParameters, ThicknessScale);
|
|
}
|
|
}
|
|
|
|
if (bSetValue && bHasChanged)
|
|
{
|
|
FPropertyChangedEvent PropertyChangedEvent(ChildHandle->GetProperty(), EPropertyChangeType::ValueSet);
|
|
GroomAsset->PostEditChangeProperty(PropertyChangedEvent);
|
|
}
|
|
return bHasChanged;
|
|
}
|
|
|
|
bool FGroomRenderingDetails::ShouldResetToDefault(TSharedPtr<IPropertyHandle> ChildHandle, int32 GroupIndex, int32 LODIndex)
|
|
{
|
|
return CommonResetToDefault(ChildHandle, GroupIndex, LODIndex, false);
|
|
}
|
|
|
|
void FGroomRenderingDetails::ResetToDefault(TSharedPtr<IPropertyHandle> ChildHandle, int32 GroupIndex, int32 LODIndex)
|
|
{
|
|
CommonResetToDefault(ChildHandle, GroupIndex, LODIndex, true);
|
|
}
|
|
|
|
IDetailPropertyRow& FGroomRenderingDetails::AddPropertyWithCustomReset(TSharedPtr<IPropertyHandle>& PropertyHandle, IDetailChildrenBuilder& Builder, int32 GroupIndex, int32 LODIndex)
|
|
{
|
|
FIsResetToDefaultVisible IsResetVisible = FIsResetToDefaultVisible::CreateSP(this, &FGroomRenderingDetails::ShouldResetToDefault, GroupIndex, LODIndex);
|
|
FResetToDefaultHandler ResetHandler = FResetToDefaultHandler::CreateSP(this, &FGroomRenderingDetails::ResetToDefault, GroupIndex, LODIndex);
|
|
FResetToDefaultOverride ResetOverride = FResetToDefaultOverride::Create(IsResetVisible, ResetHandler);
|
|
return Builder.AddProperty(PropertyHandle.ToSharedRef()).OverrideResetToDefault(ResetOverride);
|
|
}
|
|
|
|
void FGroomRenderingDetails::ExpandStructForLOD(TSharedRef<IPropertyHandle>& PropertyHandle, IDetailChildrenBuilder& ChildrenBuilder, int32 GroupIndex, int32 LODIndex, bool bOverrideReset)
|
|
{
|
|
uint32 ChildrenCount = 0;
|
|
PropertyHandle->GetNumChildren(ChildrenCount);
|
|
for (uint32 ChildIt = 0; ChildIt < ChildrenCount; ++ChildIt)
|
|
{
|
|
TSharedPtr<IPropertyHandle> ChildHandle = PropertyHandle->GetChildHandle(ChildIt);
|
|
const FName ChildPropertyName = ChildHandle->GetProperty()->GetFName();
|
|
|
|
const bool bIsManualLODStrandsProperties =
|
|
(ChildPropertyName == GET_MEMBER_NAME_CHECKED(FHairLODSettings, CurveDecimation) ||
|
|
ChildPropertyName == GET_MEMBER_NAME_CHECKED(FHairLODSettings, VertexDecimation) ||
|
|
ChildPropertyName == GET_MEMBER_NAME_CHECKED(FHairLODSettings, AngularThreshold) ||
|
|
ChildPropertyName == GET_MEMBER_NAME_CHECKED(FHairLODSettings, ThicknessScale));
|
|
const bool bIsScreenSize = ChildPropertyName == GET_MEMBER_NAME_CHECKED(FHairLODSettings, ScreenSize);
|
|
|
|
// If the geometry type is not strands, then bypass the display of the strands related property
|
|
if (GroomAsset->GetGeometryType(GroupIndex, LODIndex) != EGroomGeometryType::Strands && bIsManualLODStrandsProperties)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If using AutoLOD, then the following property are not editable, since they are dedicated to Manual LOD mode
|
|
const bool bAutoLOD = GroomAsset->GetLODMode() == EGroomLODMode::Auto;
|
|
const bool bIsVisible = bAutoLOD ? !bIsManualLODStrandsProperties && !bIsScreenSize : true;
|
|
|
|
if (bOverrideReset)
|
|
{
|
|
FIsResetToDefaultVisible IsResetVisible = FIsResetToDefaultVisible::CreateSP(this, &FGroomRenderingDetails::ShouldResetToDefault, GroupIndex, LODIndex);
|
|
FResetToDefaultHandler ResetHandler = FResetToDefaultHandler::CreateSP(this, &FGroomRenderingDetails::ResetToDefault, GroupIndex, LODIndex);
|
|
FResetToDefaultOverride ResetOverride = FResetToDefaultOverride::Create(IsResetVisible, ResetHandler);
|
|
IDetailPropertyRow& Row = ChildrenBuilder.AddProperty(ChildHandle.ToSharedRef()).OverrideResetToDefault(ResetOverride);
|
|
Row.IsEnabled(bIsVisible);
|
|
}
|
|
else
|
|
{
|
|
IDetailPropertyRow& Row = ChildrenBuilder.AddProperty(ChildHandle.ToSharedRef());
|
|
Row.IsEnabled(bIsVisible);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FGroomRenderingDetails::ExpandStruct(TSharedPtr<IPropertyHandle>& PropertyHandle, IDetailChildrenBuilder& ChildrenBuilder, int32 GroupIndex, int32 LODIndex, bool bOverrideReset)
|
|
{
|
|
|
|
uint32 ChildrenCount = 0;
|
|
PropertyHandle->GetNumChildren(ChildrenCount);
|
|
for (uint32 ChildIt = 0; ChildIt < ChildrenCount; ++ChildIt)
|
|
{
|
|
TSharedPtr<IPropertyHandle> ChildHandle = PropertyHandle->GetChildHandle(ChildIt);
|
|
if (bOverrideReset)
|
|
{
|
|
FIsResetToDefaultVisible IsResetVisible = FIsResetToDefaultVisible::CreateSP(this, &FGroomRenderingDetails::ShouldResetToDefault, GroupIndex, LODIndex);
|
|
FResetToDefaultHandler ResetHandler = FResetToDefaultHandler::CreateSP(this, &FGroomRenderingDetails::ResetToDefault, GroupIndex, LODIndex);
|
|
FResetToDefaultOverride ResetOverride = FResetToDefaultOverride::Create(IsResetVisible, ResetHandler);
|
|
ChildrenBuilder.AddProperty(ChildHandle.ToSharedRef()).OverrideResetToDefault(ResetOverride);
|
|
}
|
|
else
|
|
{
|
|
ChildrenBuilder.AddProperty(ChildHandle.ToSharedRef());
|
|
}
|
|
}
|
|
}
|
|
|
|
void FGroomRenderingDetails::ExpandStruct(TSharedRef<IPropertyHandle>& PropertyHandle, IDetailChildrenBuilder& ChildrenBuilder, int32 GroupIndex, int32 LODIndex, bool bOverrideReset)
|
|
{
|
|
uint32 ChildrenCount = 0;
|
|
PropertyHandle->GetNumChildren(ChildrenCount);
|
|
for (uint32 ChildIt = 0; ChildIt < ChildrenCount; ++ChildIt)
|
|
{
|
|
TSharedPtr<IPropertyHandle> ChildHandle = PropertyHandle->GetChildHandle(ChildIt);
|
|
if (bOverrideReset)
|
|
{
|
|
FIsResetToDefaultVisible IsResetVisible = FIsResetToDefaultVisible::CreateSP(this, &FGroomRenderingDetails::ShouldResetToDefault, GroupIndex, LODIndex);
|
|
FResetToDefaultHandler ResetHandler = FResetToDefaultHandler::CreateSP(this, &FGroomRenderingDetails::ResetToDefault, GroupIndex, LODIndex);
|
|
FResetToDefaultOverride ResetOverride = FResetToDefaultOverride::Create(IsResetVisible, ResetHandler);
|
|
ChildrenBuilder.AddProperty(ChildHandle.ToSharedRef()).OverrideResetToDefault(ResetOverride);
|
|
}
|
|
else
|
|
{
|
|
ChildrenBuilder.AddProperty(ChildHandle.ToSharedRef());
|
|
}
|
|
}
|
|
}
|
|
|
|
void FGroomRenderingDetails::AddPropertySeparator(FName PropertyName, IDetailChildrenBuilder& ChildrenBuilder)
|
|
{
|
|
static const FSlateBrush* GenericBrush = FCoreStyle::Get().GetBrush("GenericWhiteBox");
|
|
float OtherMargin = 0.0f;
|
|
float RightMargin = 10.0f;
|
|
const FLinearColor SeparatorColor(0.05f, 0.05f,0.05f);
|
|
ChildrenBuilder.AddCustomRow(LOCTEXT("Hair_Separator", "Separator"))
|
|
.WholeRowContent()
|
|
.VAlign(VAlign_Fill)
|
|
.HAlign(HAlign_Fill)
|
|
[
|
|
SNew(SOverlay)
|
|
+ SOverlay::Slot()
|
|
[
|
|
SNew(SImage)
|
|
.Image(GenericBrush)
|
|
.ColorAndOpacity(SeparatorColor)
|
|
]
|
|
+ SOverlay::Slot()
|
|
.HAlign(HAlign_Left)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(RightMargin, OtherMargin, RightMargin, OtherMargin)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(IDetailLayoutBuilder::GetDetailFont())
|
|
.ColorAndOpacity(FLinearColor(0.9f, 0.9f,0.9f))
|
|
.Text(FText::FromName(PropertyName))
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
FReply FGroomRenderingDetails::OnRemoveLODClicked(int32 GroupIndex, int32 LODIndex, FProperty* Property)
|
|
{
|
|
check(GroomAsset);
|
|
if (GroupIndex < GroomAsset->GetHairGroupsLOD().Num() && LODIndex >= 0 && LODIndex < GroomAsset->GetHairGroupsLOD()[GroupIndex].LODs.Num() && GroomAsset->GetHairGroupsLOD()[GroupIndex].LODs.Num() > 1)
|
|
{
|
|
FScopedTransaction Transaction(FText::FromString(TEXT("RemoveLOD")));
|
|
|
|
GroomAsset->GetHairGroupsLOD()[GroupIndex].LODs.RemoveAt(LODIndex);
|
|
|
|
FPropertyChangedEvent PropertyChangedEvent(Property, EPropertyChangeType::ArrayRemove);
|
|
GroomAsset->PostEditChangeProperty(PropertyChangedEvent);
|
|
}
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply FGroomRenderingDetails::OnAddLODClicked(int32 GroupIndex, FProperty* Property)
|
|
{
|
|
const int32 MaxLODCount = 8;
|
|
if (GroupIndex < GroomAsset->GetHairGroupsLOD().Num() && GroomAsset->GetHairGroupsLOD()[GroupIndex].LODs.Num() < MaxLODCount)
|
|
{
|
|
FScopedTransaction Transaction(FText::FromString(TEXT("AddLOD")));
|
|
|
|
GroomAsset->GetHairGroupsLOD()[GroupIndex].LODs.AddDefaulted();
|
|
const int32 LODCount = GroomAsset->GetHairGroupsLOD()[GroupIndex].LODs.Num();
|
|
if (LODCount > 1)
|
|
{
|
|
const FHairLODSettings& PrevLODSettings = GroomAsset->GetHairGroupsLOD()[GroupIndex].LODs[LODCount-2];
|
|
FHairLODSettings& LODSettings = GroomAsset->GetHairGroupsLOD()[GroupIndex].LODs[LODCount-1];
|
|
|
|
// Prefill the LOD setting with basic preset
|
|
LODSettings.VertexDecimation = PrevLODSettings.VertexDecimation * 0.5f;
|
|
LODSettings.AngularThreshold = PrevLODSettings.AngularThreshold * 2.f;
|
|
LODSettings.CurveDecimation = PrevLODSettings.CurveDecimation * 0.5f;
|
|
LODSettings.ScreenSize = PrevLODSettings.ScreenSize * 0.5f;
|
|
LODSettings.GeometryType = PrevLODSettings.GeometryType;
|
|
}
|
|
|
|
FPropertyChangedEvent PropertyChangedEvent(Property, EPropertyChangeType::ArrayAdd);
|
|
GroomAsset->PostEditChangeProperty(PropertyChangedEvent);
|
|
}
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply FGroomRenderingDetails::OnGenerateCardDataUsingPlugin(int32 GroupIndex)
|
|
{
|
|
if (GroomAsset && GroomAsset->GetHairGroupsCards().IsValidIndex(GroupIndex))
|
|
{
|
|
TArray<IHairCardGenerator*> HairCardGeneratorPlugins = IModularFeatures::Get().GetModularFeatureImplementations<IHairCardGenerator>(IHairCardGenerator::ModularFeatureName);
|
|
if (HairCardGeneratorPlugins.Num() > 0)
|
|
{
|
|
UE_CLOG(HairCardGeneratorPlugins.Num() > 1, LogGroomAssetDetails, Warning, TEXT("There are more than one available hair-card generator options. Defaulting to the first one found."));
|
|
|
|
const FScopedTransaction Transaction(LOCTEXT("GenerateHairCardsTransaction", "Generate hair cards."));
|
|
|
|
// Use a copy so we can only apply changes on success
|
|
FHairGroupsCardsSourceDescription HairCardsCopy = GroomAsset->GetHairGroupsCards()[GroupIndex];
|
|
// Clear fields that are supposed to be set by the generation (in case it leaves any unset, and we don't cary over old settings)
|
|
HairCardsCopy.Textures = FHairGroupCardsTextures();
|
|
|
|
const bool bSuccess = HairCardGeneratorPlugins[0]->GenerateHairCardsForLOD(GroomAsset, HairCardsCopy);
|
|
if (bSuccess)
|
|
{
|
|
GroomAsset->Modify();
|
|
GroomAsset->GetHairGroupsCards()[GroupIndex] = HairCardsCopy;
|
|
}
|
|
}
|
|
}
|
|
return FReply::Handled();
|
|
}
|
|
|
|
void FGroomRenderingDetails::OnGenerateElementForLODs(TSharedRef<IPropertyHandle> StructProperty, int32 LODIndex, IDetailChildrenBuilder& ChildrenBuilder, IDetailLayoutBuilder* DetailLayout, int32 GroupIndex)
|
|
{
|
|
const FSlateFontInfo DetailFontInfo = IDetailLayoutBuilder::GetDetailFont();
|
|
FProperty* Property = StructProperty->GetProperty();
|
|
|
|
const FLinearColor LODColorBlock = GetHairGroupDebugColor(GroupIndex) * 0.25f;
|
|
const FLinearColor LODNameColor(FLinearColor::White);
|
|
static const FSlateBrush* GenericBrush = FCoreStyle::Get().GetBrush("GenericWhiteBox");
|
|
float OtherMargin = 2.0f;
|
|
|
|
// LOD Bar with add button
|
|
ChildrenBuilder.AddCustomRow(LOCTEXT("HairInfo_Separator", "Separator"))
|
|
.CopyAction(FUIAction(FExecuteAction::CreateSP(this, &FGroomRenderingDetails::OnCopyLODItem, GroupIndex, LODIndex), FCanExecuteAction::CreateSP(this, &FGroomRenderingDetails::OnCanCopyLODItem, GroupIndex, LODIndex)))
|
|
.PasteAction(FUIAction(FExecuteAction::CreateSP(this, &FGroomRenderingDetails::OnPasteLODItem, GroupIndex, LODIndex)))
|
|
.WholeRowContent()
|
|
.VAlign(VAlign_Fill)
|
|
.HAlign(HAlign_Fill)
|
|
[
|
|
SNew(SOverlay)
|
|
+ SOverlay::Slot()
|
|
[
|
|
SNew(SImage)
|
|
.Image(GenericBrush)
|
|
.ColorAndOpacity(LODColorBlock)
|
|
]
|
|
+ SOverlay::Slot()
|
|
.HAlign(HAlign_Right)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(OtherMargin)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(IDetailLayoutBuilder::GetDetailFont())
|
|
.ColorAndOpacity(LODNameColor)
|
|
.Text(FText::Format(LOCTEXT("LOD", "LOD {0}"), FText::AsNumber(LODIndex)))
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Right)
|
|
.ButtonStyle(FAppStyle::Get(), "HoverHintOnly")
|
|
.OnClicked(this, &FGroomRenderingDetails::OnRemoveLODClicked, GroupIndex, LODIndex, Property)
|
|
[
|
|
SNew(SImage)
|
|
.Image(FAppStyle::GetBrush("Icons.Delete"))
|
|
]
|
|
]
|
|
|
|
]
|
|
];
|
|
|
|
// LOD Stats
|
|
const FHairStrandsClusterBulkData& ClusterBulkData = GroomAsset->GetHairGroupsPlatformData()[GroupIndex].Strands.ClusterBulkData;
|
|
if (ClusterBulkData.IsValid() && LODIndex < ClusterBulkData.Header.LODInfos.Num())
|
|
{
|
|
const FHairLODInfo& LODInfo = ClusterBulkData.Header.LODInfos[LODIndex];
|
|
|
|
ChildrenBuilder.AddCustomRow(LOCTEXT("HairStrandsLODInfo_Array", "HairStrandsLODInfo"))
|
|
.ValueContent()
|
|
.HAlign(HAlign_Fill)
|
|
[
|
|
MakeHairStrandsLODInfoGrid(DetailFontInfo, LODInfo)
|
|
];
|
|
}
|
|
|
|
// Rename the array entry name by its group name and adds all its existing properties
|
|
StructProperty->SetPropertyDisplayName(LOCTEXT("LODProperties", "LOD Properties"));
|
|
ExpandStructForLOD(StructProperty, ChildrenBuilder, GroupIndex, LODIndex, true); ///
|
|
}
|
|
|
|
TSharedRef<SWidget> FGroomRenderingDetails::MakeGroupNameButtonCustomization(int32 GroupIndex, FProperty* Property)
|
|
{
|
|
switch (PanelType)
|
|
{
|
|
case EMaterialPanelType::LODs:
|
|
{
|
|
return SNew(SButton)
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Right)
|
|
.ButtonStyle(FAppStyle::Get(), "HoverHintOnly")
|
|
.OnClicked(this, &FGroomRenderingDetails::OnAddLODClicked, GroupIndex, Property)
|
|
[
|
|
SNew(SImage)
|
|
.Image(FAppStyle::GetBrush("Icons.PlusCircle"))
|
|
];
|
|
}
|
|
case EMaterialPanelType::Cards:
|
|
{
|
|
return SNew(SButton)
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Right)
|
|
.ButtonStyle(FAppStyle::Get(), "HoverHintOnly")
|
|
.OnClicked(this, &FGroomRenderingDetails::OnRemoveGroupClicked, GroupIndex, Property)
|
|
[
|
|
SNew(SImage)
|
|
.Image(FAppStyle::GetBrush("Icons.Delete"))
|
|
];
|
|
}
|
|
case EMaterialPanelType::Meshes:
|
|
{
|
|
return SNew(SButton)
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Right)
|
|
.ButtonStyle(FAppStyle::Get(), "HoverHintOnly")
|
|
.OnClicked(this, &FGroomRenderingDetails::OnRemoveGroupClicked, GroupIndex, Property)
|
|
[
|
|
SNew(SImage)
|
|
.Image(FAppStyle::GetBrush("Icons.Delete"))
|
|
];
|
|
}
|
|
}
|
|
|
|
return SNullWidget::NullWidget;
|
|
}
|
|
|
|
static FName GetGroupName(const UGroomAsset* GroomAsset, int32 GroupIndex)
|
|
{
|
|
if (GroomAsset && GroupIndex >= 0 && GroupIndex < GroomAsset->GetHairGroupsInfo().Num())
|
|
{
|
|
return GroomAsset->GetHairGroupsInfo()[GroupIndex].GroupName;
|
|
}
|
|
return NAME_None;
|
|
}
|
|
|
|
TSharedRef<SWidget> GetGroupNameWidget(const UGroomAsset* GroomAsset, int32 GroupIndex, const FLinearColor& GroupColor)
|
|
{
|
|
FName GroupName = GetGroupName(GroomAsset, GroupIndex);
|
|
if (GroupName != NAME_None)
|
|
{
|
|
return SNew(STextBlock)
|
|
.Font(IDetailLayoutBuilder::GetDetailFont())
|
|
.ColorAndOpacity(GroupColor)
|
|
.Text(FText::Format(LOCTEXT("GroupWithName", "Group ID {0} - {1}"), FText::AsNumber(GroupIndex), FText::FromName(GroupName)));
|
|
}
|
|
else
|
|
{
|
|
return SNew(STextBlock)
|
|
.Font(IDetailLayoutBuilder::GetDetailFont())
|
|
.ColorAndOpacity(GroupColor)
|
|
.Text(FText::Format(LOCTEXT("GroupWithoutName", "Group ID {0}"), FText::AsNumber(GroupIndex)));
|
|
}
|
|
}
|
|
|
|
TSharedRef<SWidget> FGroomRenderingDetails::MakeGroupNameCustomization(int32 GroupIndex, const FLinearColor& GroupTextColor)
|
|
{
|
|
switch (PanelType)
|
|
{
|
|
case EMaterialPanelType::Cards:
|
|
{
|
|
return SNew(STextBlock)
|
|
.Font(IDetailLayoutBuilder::GetDetailFont())
|
|
.ColorAndOpacity(GroupTextColor)
|
|
.Text(FText::Format(LOCTEXT("Cards", "Cards {0} "), FText::AsNumber(GroupIndex)));
|
|
}
|
|
case EMaterialPanelType::Meshes:
|
|
{
|
|
return SNew(STextBlock)
|
|
.Font(IDetailLayoutBuilder::GetDetailFont())
|
|
.ColorAndOpacity(GroupTextColor)
|
|
.Text(FText::Format(LOCTEXT("Meshes", "Meshes {0} "), FText::AsNumber(GroupIndex)));
|
|
}
|
|
default:
|
|
{
|
|
return GetGroupNameWidget(GroomAsset, GroupIndex, GroupTextColor);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Hair group custom display
|
|
void FGroomRenderingDetails::OnGenerateElementForHairGroup(TSharedRef<IPropertyHandle> StructProperty, int32 GroupIndex, IDetailChildrenBuilder& ChildrenBuilder, IDetailLayoutBuilder* DetailLayout)
|
|
{
|
|
const FSlateFontInfo DetailFontInfo = IDetailLayoutBuilder::GetDetailFont();
|
|
const FLinearColor GroupColorBlock = GetHairGroupDebugColor(GroupIndex) * 0.75f;
|
|
const FLinearColor GroupNameColor(FLinearColor::White);
|
|
|
|
FProperty* Property = StructProperty->GetProperty();
|
|
|
|
static const FSlateBrush* GenericBrush = FCoreStyle::Get().GetBrush("GenericWhiteBox");
|
|
|
|
float OtherMargin = 2.0f;
|
|
float RightMargin = 2.0f;
|
|
if (PanelType != EMaterialPanelType::LODs && PanelType != EMaterialPanelType::Cards && PanelType != EMaterialPanelType::Meshes)
|
|
{
|
|
RightMargin = 10.0f;
|
|
}
|
|
|
|
ChildrenBuilder.AddCustomRow(LOCTEXT("HairInfo_Separator", "Separator"))
|
|
.CopyAction(FUIAction(FExecuteAction::CreateSP(this, &FGroomRenderingDetails::OnCopyGroupItem, GroupIndex, PanelType), FCanExecuteAction::CreateSP(this, &FGroomRenderingDetails::OnCanCopyGroupItem, GroupIndex, PanelType)))
|
|
.PasteAction(FUIAction(FExecuteAction::CreateSP(this, &FGroomRenderingDetails::OnPasteGroupItem, GroupIndex, PanelType)))
|
|
.WholeRowContent()
|
|
.VAlign(VAlign_Fill)
|
|
.HAlign(HAlign_Fill)
|
|
[
|
|
SNew(SOverlay)
|
|
+ SOverlay::Slot()
|
|
[
|
|
SNew(SImage)
|
|
.Image(GenericBrush)
|
|
.ColorAndOpacity(GroupColorBlock)
|
|
]
|
|
+ SOverlay::Slot()
|
|
.HAlign(HAlign_Right)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.VAlign(VAlign_Center)
|
|
.Padding(OtherMargin, OtherMargin, RightMargin, OtherMargin)
|
|
[
|
|
MakeGroupNameCustomization(GroupIndex, GroupNameColor)
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
MakeGroupNameButtonCustomization(GroupIndex, Property)
|
|
]
|
|
|
|
]
|
|
];
|
|
|
|
if (GroomAsset != nullptr && GroupIndex>=0 && GroupIndex < GroomAsset->GetHairGroupsInfo().Num() && (PanelType == EMaterialPanelType::Strands || PanelType == EMaterialPanelType::Interpolation))
|
|
{
|
|
ChildrenBuilder.AddCustomRow(LOCTEXT("HairStrandsInfo_Array", "HairStrandsInfo"))
|
|
.ValueContent()
|
|
.HAlign(HAlign_Fill)
|
|
[
|
|
MakeHairStrandsInfoGrid(DetailFontInfo, GroomAsset->GetHairGroupsInfo()[GroupIndex], GroomAsset->GetHairGroupsPlatformData()[GroupIndex].Strands.BulkData.Header.MaxRadius)
|
|
];
|
|
ChildrenBuilder.AddCustomRow(LOCTEXT("HairStrandsCurveInfo_Array", "HairStrandsCurveInfo"))
|
|
.ValueContent()
|
|
.HAlign(HAlign_Fill)
|
|
[
|
|
MakeHairStrandsCurveInfoGrid(DetailFontInfo, GroomAsset->GetHairGroupsPlatformData()[GroupIndex].Strands.BulkData.Header)
|
|
];
|
|
AddHairStrandsCurveWarning(ChildrenBuilder, DetailFontInfo, GroomAsset->GetHairGroupsPlatformData()[GroupIndex].Strands.BulkData.Header);
|
|
ChildrenBuilder.AddCustomRow(LOCTEXT("HairStrandsAttributeInfo_Array", "HairStrandsAttributeInfo"))
|
|
.ValueContent()
|
|
.HAlign(HAlign_Fill)
|
|
[
|
|
MakeHairStrandsAttributeInfoGrid(DetailFontInfo, GroomAsset->GetHairGroupsPlatformData()[GroupIndex].Strands.BulkData.Header.ImportedAttributes, GroomAsset->GetHairGroupsPlatformData()[GroupIndex].Strands.BulkData.Header.ImportedAttributeFlags)
|
|
];
|
|
}
|
|
|
|
if (GroomAsset != nullptr && GroupIndex >= 0 && GroupIndex < GroomAsset->GetHairGroupsCards().Num() && (PanelType == EMaterialPanelType::Cards))
|
|
{
|
|
ChildrenBuilder.AddCustomRow(LOCTEXT("HairCardsInfo_Array", "HairCardsInfo"))
|
|
.ValueContent()
|
|
.HAlign(HAlign_Fill)
|
|
[
|
|
MakeHairCardsInfoGrid(DetailFontInfo, GroomAsset->GetHairGroupsCards()[GroupIndex].CardsInfo)
|
|
];
|
|
}
|
|
|
|
// Display a material picker for strands/cards/meshes panel. This material picker allows to select material among the one valid for this current asset, i.e.,
|
|
// materials which have been added by the user within the material panel
|
|
if (PanelType == EMaterialPanelType::Strands || PanelType == EMaterialPanelType::Cards || PanelType == EMaterialPanelType::Meshes)
|
|
{
|
|
FResetToDefaultOverride ResetToDefaultOverride = FResetToDefaultOverride::Create(
|
|
FIsResetToDefaultVisible::CreateSP(this, &FGroomRenderingDetails::GetReplaceVisibility),
|
|
FResetToDefaultHandler::CreateSP(this, &FGroomRenderingDetails::OnResetToBaseClicked)
|
|
);
|
|
|
|
ChildrenBuilder.AddCustomRow(LOCTEXT("HairGroup_Material", "Material"))
|
|
.OverrideResetToDefault(ResetToDefaultOverride)
|
|
.NameContent()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("HairRenderingGroup_Label_Material", "Material"))
|
|
.Font(IDetailLayoutBuilder::GetDetailFont())
|
|
.IsEnabled(this, &FGroomRenderingDetails::IsStrandsMaterialPickerEnabled, GroupIndex)
|
|
]
|
|
.ValueContent()
|
|
.MinDesiredWidth(250.f)
|
|
.MaxDesiredWidth(0.0f) // no maximum
|
|
[
|
|
OnGenerateStrandsMaterialPicker(GroupIndex, DetailLayout)
|
|
];
|
|
}
|
|
|
|
// Rename the array entry name by its group name and adds all its existing properties
|
|
StructProperty->SetPropertyDisplayName(LOCTEXT("GroupProperties", "Properties"));
|
|
|
|
uint32 ChildrenCount = 0;
|
|
StructProperty->GetNumChildren(ChildrenCount);
|
|
for (uint32 ChildIt = 0; ChildIt < ChildrenCount; ++ChildIt)
|
|
{
|
|
TSharedPtr<IPropertyHandle> ChildHandle = StructProperty->GetChildHandle(ChildIt);
|
|
FName PropertyName = ChildHandle->GetProperty()->GetFName();
|
|
|
|
switch (PanelType)
|
|
{
|
|
case EMaterialPanelType::Strands:
|
|
{
|
|
if (PropertyName == GET_MEMBER_NAME_CHECKED(FHairGroupsRendering, GeometrySettings) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(FHairGroupsRendering, ShadowSettings) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(FHairGroupsRendering, AdvancedSettings))
|
|
{
|
|
ExpandStruct(ChildHandle, ChildrenBuilder, GroupIndex, -1, true);
|
|
}
|
|
else
|
|
{
|
|
ChildrenBuilder.AddProperty(ChildHandle.ToSharedRef());
|
|
}
|
|
}
|
|
break;
|
|
case EMaterialPanelType::Cards:
|
|
{
|
|
if (PropertyName == GET_MEMBER_NAME_CHECKED(FHairGroupsCardsSourceDescription, CardsInfo))
|
|
{
|
|
// Not node display
|
|
}
|
|
else if (PropertyName == GET_MEMBER_NAME_CHECKED(FHairGroupsCardsSourceDescription, Textures))
|
|
{
|
|
EHairTextureLayout LayoutType = EHairTextureLayout::Layout0;
|
|
if (GroomAsset != nullptr && GroomAsset->GetHairGroupsCards().IsValidIndex(GroupIndex))
|
|
{
|
|
LayoutType = GroomAsset->GetHairGroupsCards()[GroupIndex].Textures.Layout;
|
|
}
|
|
|
|
IDetailGroup& TextureGroup = ChildrenBuilder.AddGroup(TEXT("HairCardsTextures"), LOCTEXT("HairCardsTextures", "Textures"));
|
|
{
|
|
// Layout type
|
|
TextureGroup.AddPropertyRow(ChildHandle->GetChildHandle(0).ToSharedRef());
|
|
|
|
// Textures
|
|
const uint32 TextureCount = GroomAsset->GetHairGroupsCards()[GroupIndex].Textures.Textures.Num();
|
|
TSharedPtr<IPropertyHandle> TextureArrayHandle = ChildHandle->GetChildHandle(1);
|
|
for (uint32 TextureIt = 0; TextureIt < TextureCount; ++TextureIt)
|
|
{
|
|
TextureGroup.AddPropertyRow(TextureArrayHandle->GetChildHandle(TextureIt).ToSharedRef()).DisplayName(FTextStringHelper::CreateFromBuffer(GetHairTextureLayoutTextureName(LayoutType, TextureIt, true /*bDetail*/)));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
IDetailPropertyRow& PropertyRow = AddPropertyWithCustomReset(ChildHandle, ChildrenBuilder, GroupIndex, -1);
|
|
|
|
if (PropertyName == GET_MEMBER_NAME_CHECKED(FHairGroupsCardsSourceDescription, ImportedMesh))
|
|
{
|
|
TSharedPtr<SWidget> NameWidget;
|
|
TSharedPtr<SWidget> ValueWidget;
|
|
FDetailWidgetRow Row;
|
|
PropertyRow.GetDefaultWidgets(NameWidget, ValueWidget, Row);
|
|
|
|
TArray<IHairCardGenerator*> HairCardGeneratorPlugins = IModularFeatures::Get().GetModularFeatureImplementations<IHairCardGenerator>(IHairCardGenerator::ModularFeatureName);
|
|
bool bHasProceduralGenerationPlugin = HairCardGeneratorPlugins.Num() > 0;
|
|
TAttribute<EVisibility> ProceduralPropertyVisibility = TAttribute<EVisibility>::CreateLambda([bHasProceduralGenerationPlugin]()
|
|
{
|
|
return bHasProceduralGenerationPlugin ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
);
|
|
|
|
const FSlateBrush* Brush = nullptr;
|
|
if (const ISlateStyle* AppStyle = Toolkit->GetSlateStyle())
|
|
{
|
|
Brush = AppStyle->GetBrush("GroomEditor.GroomCardGenerator");
|
|
}
|
|
else
|
|
{
|
|
Brush = FAppStyle::GetBrush("ContentBrowser.AssetActions.ReimportAsset");
|
|
}
|
|
|
|
TSharedRef<SHorizontalBox> ProceduralGenButtons = SNew(SHorizontalBox).Visibility(ProceduralPropertyVisibility);
|
|
if (bHasProceduralGenerationPlugin)
|
|
{
|
|
FText RegenToolTipText = LOCTEXT("GenerateNewCardsTooltip", "Generate new card assets (meshes and textures) using the current procedural settings. NOTE: This will overwrite preexisting card assets for this LOD.");
|
|
ProceduralGenButtons->AddSlot()
|
|
.AutoWidth()
|
|
.Padding(0.0f, 2.0f, 0.0f, 4.0f)
|
|
[
|
|
SNew(SButton)
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Center)
|
|
.ContentPadding(0)
|
|
.ButtonStyle(FAppStyle::Get(), "Button")
|
|
.ToolTipText(RegenToolTipText)
|
|
.OnClicked(this, &FGroomRenderingDetails::OnGenerateCardDataUsingPlugin, GroupIndex)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Center)
|
|
.Padding(-1.0f, 2.0f, 2.0f, 2.0f)
|
|
[
|
|
SNew(SImage)
|
|
.Image(Brush)
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Center)
|
|
.Padding(2.0f, 2.0f, -1.0f, 2.0f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("GenerateNewCardsButtonText","Generate Hair Cards"))
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
PropertyRow.CustomWidget()
|
|
.NameContent()
|
|
[
|
|
NameWidget.ToSharedRef()
|
|
]
|
|
.ValueContent()
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
ValueWidget.ToSharedRef()
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
ProceduralGenButtons
|
|
]
|
|
];
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case EMaterialPanelType::Meshes:
|
|
{
|
|
if (PropertyName == GET_MEMBER_NAME_CHECKED(FHairGroupsMeshesSourceDescription, Textures))
|
|
{
|
|
EHairTextureLayout LayoutType = EHairTextureLayout::Layout0;
|
|
if (GroomAsset != nullptr && GroomAsset->GetHairGroupsMeshes().IsValidIndex(GroupIndex))
|
|
{
|
|
LayoutType = GroomAsset->GetHairGroupsMeshes()[GroupIndex].Textures.Layout;
|
|
}
|
|
|
|
IDetailGroup& TextureGroup = ChildrenBuilder.AddGroup(TEXT("HairMeshesTextures"), LOCTEXT("HairMeshesTextures", "Textures"));
|
|
{
|
|
// Layout type
|
|
TextureGroup.AddPropertyRow(ChildHandle->GetChildHandle(0).ToSharedRef());
|
|
|
|
// Textures
|
|
const uint32 TextureCount = GroomAsset->GetHairGroupsMeshes()[GroupIndex].Textures.Textures.Num();
|
|
TSharedPtr<IPropertyHandle> TextureArrayHandle = ChildHandle->GetChildHandle(1);
|
|
for (uint32 TextureIt = 0; TextureIt < TextureCount; ++TextureIt)
|
|
{
|
|
TextureGroup.AddPropertyRow(TextureArrayHandle->GetChildHandle(TextureIt).ToSharedRef()).DisplayName(FTextStringHelper::CreateFromBuffer(GetHairTextureLayoutTextureName(LayoutType, TextureIt, true /*bDetail*/)));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AddPropertyWithCustomReset(ChildHandle, ChildrenBuilder, GroupIndex, -1);
|
|
}
|
|
}
|
|
break;
|
|
case EMaterialPanelType::Interpolation:
|
|
{
|
|
if (PropertyName == GET_MEMBER_NAME_CHECKED(FHairGroupsInterpolation, DecimationSettings) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(FHairGroupsInterpolation, InterpolationSettings))
|
|
{
|
|
ExpandStruct(ChildHandle, ChildrenBuilder, GroupIndex, -1, true);
|
|
}
|
|
else
|
|
{
|
|
AddPropertyWithCustomReset(ChildHandle, ChildrenBuilder, GroupIndex, -1);
|
|
}
|
|
}
|
|
break;
|
|
case EMaterialPanelType::LODs:
|
|
{
|
|
if (PropertyName == GET_MEMBER_NAME_CHECKED(FHairGroupsLOD, LODs))
|
|
{
|
|
// Add a custom builder for each LOD arrays within each group. This way we can customize this 'nested/inner' array
|
|
TSharedRef<FDetailArrayBuilder> PropertyBuilder = MakeShareable(new FDetailArrayBuilder(ChildHandle.ToSharedRef(), false, false, false));
|
|
PropertyBuilder->OnGenerateArrayElementWidget(FOnGenerateArrayElementWidget::CreateSP(this, &FGroomRenderingDetails::OnGenerateElementForLODs, DetailLayout, GroupIndex));
|
|
PropertyBuilder->SetDisplayName(FText::FromString(TEXT("LODs")));
|
|
ChildrenBuilder.AddCustomBuilder(PropertyBuilder);
|
|
}
|
|
else
|
|
{
|
|
AddPropertyWithCustomReset(ChildHandle, ChildrenBuilder, GroupIndex, -1);
|
|
}
|
|
}
|
|
break;
|
|
case EMaterialPanelType::Physics:
|
|
{
|
|
if (PropertyName == GET_MEMBER_NAME_CHECKED(FHairGroupsPhysics, SolverSettings) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(FHairGroupsPhysics, ExternalForces) ||
|
|
PropertyName == GET_MEMBER_NAME_CHECKED(FHairGroupsPhysics, StrandsParameters))
|
|
{
|
|
AddPropertySeparator(PropertyName, ChildrenBuilder);
|
|
ExpandStruct(ChildHandle, ChildrenBuilder, GroupIndex, -1, true);
|
|
}
|
|
else if (PropertyName == GET_MEMBER_NAME_CHECKED(FHairGroupsPhysics, MaterialConstraints))
|
|
{
|
|
// Expand the constraint, so that each constraints type has its own block (so that custom value reset works correctly)
|
|
uint32 SubChildrenCount = 0;
|
|
ChildHandle->GetNumChildren(SubChildrenCount);
|
|
for (uint32 SubChildIt = 0; SubChildIt < SubChildrenCount; ++SubChildIt)
|
|
{
|
|
TSharedPtr<IPropertyHandle> SubChildHandle = ChildHandle->GetChildHandle(SubChildIt);
|
|
AddPropertySeparator(SubChildHandle->GetProperty()->GetFName(), ChildrenBuilder);
|
|
ExpandStruct(SubChildHandle, ChildrenBuilder, GroupIndex, -1, true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AddPropertyWithCustomReset(ChildHandle, ChildrenBuilder, GroupIndex, -1);
|
|
}
|
|
}
|
|
break;
|
|
case EMaterialPanelType::Bindings:
|
|
{
|
|
AddPropertyWithCustomReset(ChildHandle, ChildrenBuilder, GroupIndex, -1);
|
|
}
|
|
break;
|
|
case EMaterialPanelType::Dataflow:
|
|
{
|
|
AddPropertyWithCustomReset(ChildHandle, ChildrenBuilder, GroupIndex, -1);
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
ChildrenBuilder.AddProperty(ChildHandle.ToSharedRef());
|
|
}
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void FGroomRenderingDetails::OnGroomLODTypeChanged()
|
|
{
|
|
GroomDetailLayout->ForceRefreshDetails();
|
|
}
|
|
|
|
void FGroomRenderingDetails::OnGroomTextureLayoutChanged()
|
|
{
|
|
GroomDetailLayout->ForceRefreshDetails();
|
|
}
|
|
|
|
// Hair binding display
|
|
void FGroomRenderingDetails::OnGenerateElementForBindingAsset(TSharedRef<IPropertyHandle> StructProperty, int32 BindingIndex, IDetailChildrenBuilder& ChildrenBuilder, IDetailLayoutBuilder* DetailLayout)
|
|
{
|
|
FProperty* Property = StructProperty->GetProperty();
|
|
ChildrenBuilder.AddProperty(StructProperty);
|
|
|
|
const FLinearColor Color = BindingIndex == Toolkit->GetActiveBindingIndex() ? FLinearColor::White : FLinearColor(0.1f, 0.1f, 0.1f, 1.f);
|
|
|
|
ChildrenBuilder.AddCustomRow(FText::FromString(TEXT("Preview")))
|
|
.ValueContent()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Center)
|
|
.ButtonStyle(FAppStyle::Get(), "HoverHintOnly")
|
|
.OnClicked(this, &FGroomRenderingDetails::OnSelectBinding, BindingIndex, Property)
|
|
[
|
|
SNew(SImage)
|
|
.Image(FAppStyle::GetBrush("Icons.Visible"))
|
|
.ColorAndOpacity(Color)
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void FGroomRenderingDetails::OnSetObject(const FAssetData& AssetData)
|
|
{
|
|
|
|
}
|
|
|
|
FString FGroomRenderingDetails::OnGetObjectPath(int32 GroupIndex) const
|
|
{
|
|
if (!GroomAsset || GroupIndex < 0)
|
|
return FString();
|
|
|
|
int32 MaterialIndex = INDEX_NONE;
|
|
check(GroomAsset);
|
|
switch (PanelType)
|
|
{
|
|
case EMaterialPanelType::Cards:
|
|
{
|
|
if (GroupIndex < GroomAsset->GetHairGroupsCards().Num())
|
|
{
|
|
MaterialIndex = GroomAsset->GetMaterialIndex(GroomAsset->GetHairGroupsCards()[GroupIndex].MaterialSlotName);
|
|
}
|
|
}
|
|
break;
|
|
case EMaterialPanelType::Meshes:
|
|
{
|
|
if (GroupIndex < GroomAsset->GetHairGroupsMeshes().Num())
|
|
{
|
|
MaterialIndex = GroomAsset->GetMaterialIndex(GroomAsset->GetHairGroupsMeshes()[GroupIndex].MaterialSlotName);
|
|
}
|
|
}
|
|
break;
|
|
case EMaterialPanelType::Strands:
|
|
{
|
|
if (GroupIndex < GroomAsset->GetHairGroupsRendering().Num())
|
|
{
|
|
MaterialIndex = GroomAsset->GetMaterialIndex(GroomAsset->GetHairGroupsRendering()[GroupIndex].MaterialSlotName);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (MaterialIndex == INDEX_NONE)
|
|
return FString();
|
|
|
|
return GroomAsset->GetHairGroupsMaterials()[MaterialIndex].Material->GetPathName();
|
|
}
|
|
|
|
/**
|
|
* Called to get the visibility of the replace button
|
|
*/
|
|
bool FGroomRenderingDetails::GetReplaceVisibility(TSharedPtr<IPropertyHandle> PropertyHandle) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Called when reset to base is clicked
|
|
*/
|
|
void FGroomRenderingDetails::OnResetToBaseClicked(TSharedPtr<IPropertyHandle> PropertyHandle)
|
|
{
|
|
|
|
}
|
|
|
|
TSharedRef<SWidget> FGroomRenderingDetails::CreateMaterialSwatch( const TSharedPtr<FAssetThumbnailPool>& ThumbnailPool/*, const TArray<FAssetData>& OwnerAssetDataArray*/, int32 GroupIndex)
|
|
{
|
|
FIntPoint ThumbnailSize(64, 64);
|
|
|
|
const bool bDisplayCompactSize = false;
|
|
return
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
[
|
|
SNew(SVerticalBox)
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding( 0.0f )
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Fill)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.FillWidth(1.0f)
|
|
[
|
|
SNew( SObjectPropertyEntryBox )
|
|
.EnableContentPicker(false)
|
|
.DisplayUseSelected(false)
|
|
.ObjectPath(this, &FGroomRenderingDetails::OnGetObjectPath, GroupIndex)
|
|
.AllowedClass(UMaterialInterface::StaticClass())
|
|
.OnObjectChanged(this, &FGroomRenderingDetails::OnSetObject)
|
|
.ThumbnailPool(ThumbnailPool)
|
|
.DisplayCompactSize(bDisplayCompactSize)
|
|
//.OwnerAssetDataArray(OwnerAssetDataArray)
|
|
.CustomContentSlot()
|
|
[
|
|
SNew(SComboButton)
|
|
.IsEnabled(this, &FGroomRenderingDetails::IsStrandsMaterialPickerEnabled, GroupIndex)
|
|
.OnGetMenuContent(this, &FGroomRenderingDetails::OnGenerateStrandsMaterialMenuPicker, GroupIndex)
|
|
.VAlign(VAlign_Center)
|
|
.ContentPadding(2)
|
|
.ButtonContent()
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(IDetailLayoutBuilder::GetDetailFont())
|
|
.Text(this, &FGroomRenderingDetails::GetStrandsMaterialName, GroupIndex)
|
|
]
|
|
]
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE |