// Copyright Epic Games, Inc. All Rights Reserved. #include "NaniteDisplacedMeshCustomization.h" #include "DetailLayoutBuilder.h" #include "DetailWidgetRow.h" #include "NaniteDisplacedMesh.h" #include "ScopedTransaction.h" #include "UObject/StructOnScope.h" #include "Widgets/Input/SButton.h" #include "Widgets/Text/STextBlock.h" #define LOCTEXT_NAMESPACE "FNaniteDisplacedMeshDetails" TSharedRef FNaniteDisplacedMeshDetails::MakeInstance() { return MakeShared(); } void FNaniteDisplacedMeshDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { TArray> ObjectsCustomized; DetailBuilder.GetObjectsBeingCustomized(ObjectsCustomized); CustomizedPairs.Reserve(ObjectsCustomized.Num()); for (const TWeakObjectPtr& ObjectCustomized : ObjectsCustomized) { if (UNaniteDisplacedMesh* DisplacedMesh = Cast(ObjectCustomized.Get())) { CustomizedPairs.Emplace(TWeakObjectPtr(DisplacedMesh), DisplacedMesh->Parameters); } } { IDetailCategoryBuilder& ParametersCategory = DetailBuilder.EditCategory(TEXT("Parameters")); UStruct* NaniteDisplacedMeshStaticStruct = FNaniteDisplacedMeshParams::StaticStruct(); for (int32 Index = 0; Index < CustomizedPairs.Num(); ++Index) { FNaniteDisplacedMeshParams& DisplacedMeshParams = CustomizedPairs[Index].Value; // The custom struct work well with minimum code added, but it doesn't support transaction like the existing stuff for the static meshes TArray PropertyRows; ParametersCategory.AddAllExternalStructureProperties(MakeShared(NaniteDisplacedMeshStaticStruct, static_cast(static_cast(&DisplacedMeshParams))), EPropertyLocation::Default, &PropertyRows); for (IDetailPropertyRow* PropertyRow : PropertyRows) { // Turned off the custom reset to default for now so that we can submit the rest until this is in a working state. /* const bool bPropagateToChildren = true; PropertyRow->OverrideResetToDefault(FResetToDefaultOverride::Create( FIsResetToDefaultVisible::CreateSP(this, &FNaniteDisplacedMeshDetails::DoesParamDifferFromOriginalValue, Index), FResetToDefaultHandler::CreateSP(this, &FNaniteDisplacedMeshDetails::ResetParamToOriginalValue, Index), bPropagateToChildren )); */ PropertyRow->EditCondition(GetCanEditAttribute(Index), FOnBooleanValueChanged()); } ParametersCategory.AddCustomRow(LOCTEXT("ApplyChanges", "Apply Changes")) .ValueContent() .HAlign(HAlign_Left) .VAlign(VAlign_Center) [ SNew(SButton) .OnClicked(this, &FNaniteDisplacedMeshDetails::ApplyNaniteDisplacedMeshParams) .IsEnabled(this, &FNaniteDisplacedMeshDetails::IsApplyNaniteDisplacedMeshParamsNeeded) .VAlign(VAlign_Center) .HAlign(HAlign_Center) [ SNew(STextBlock) .Text(LOCTEXT("ApplyChanges", "Apply Changes")) .Font(DetailBuilder.GetDetailFont()) ] ]; } } TSharedRef ParametersPropertyHandle = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UNaniteDisplacedMesh, Parameters)); ParametersPropertyHandle->MarkHiddenByCustomization(); } FReply FNaniteDisplacedMeshDetails::ApplyNaniteDisplacedMeshParams() { FScopedTransaction Transaction(LOCTEXT("ApplyDisplacementParamaterToMesh", "Apply Parameters to Nanite Displaced Mesh")); FProperty* ParametersProperty = UNaniteDisplacedMesh::StaticClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(UNaniteDisplacedMesh, Parameters)); for (TPair, FNaniteDisplacedMeshParams>& Pair : CustomizedPairs) { if (UNaniteDisplacedMesh* DisplacedMesh = Pair.Key.Get()) { DisplacedMesh->PreEditChange(ParametersProperty); DisplacedMesh->Parameters = Pair.Value; FPropertyChangedEvent ChangedEvent(ParametersProperty, EPropertyChangeType::ValueSet); DisplacedMesh->PostEditChangeProperty(ChangedEvent); } } return FReply::Handled(); } bool FNaniteDisplacedMeshDetails::IsApplyNaniteDisplacedMeshParamsNeeded() const { for (const TPair, FNaniteDisplacedMeshParams>& Pair : CustomizedPairs) { if (UNaniteDisplacedMesh* DisplacedMesh = Pair.Key.Get()) { if (DisplacedMesh->Parameters != Pair.Value) { return true; } } } return false; } bool FNaniteDisplacedMeshDetails::DoesParamDifferFromOriginalValue(TSharedPtr Handle, int32 ObjectIndex) { /* TPair, FNaniteDisplacedMeshParams>& Pair = CustomizedPairs[ObjectIndex]; if (UNaniteDisplacedMesh* DisplacedMesh = Pair.Key.Get()) { // Check if the property value exist in the NaniteDisplacedMesh if (uint8* DestAddress = Handle->GetValueBaseAddress(static_cast(static_cast(&(DisplacedMesh->Parameters))))) { uint8* SourceAddress = Handle->GetValueBaseAddress(static_cast(static_cast(&(Pair.Value)))); return !Handle->GetProperty()->Identical(DestAddress, SourceAddress); } } */ return false; } void FNaniteDisplacedMeshDetails::ResetParamToOriginalValue(TSharedPtr Handle, int32 ObjectIndex) { /* TPair, FNaniteDisplacedMeshParams>& Pair = CustomizedPairs[ObjectIndex]; if (UNaniteDisplacedMesh* DisplacedMesh = Pair.Key.Get()) { // Check if the property value exist in the NaniteDisplacedMesh if (uint8* SourceAddress = Handle->GetValueBaseAddress(static_cast(static_cast(&(DisplacedMesh->Parameters))))) { uint8* DestAddress = Handle->GetValueBaseAddress(static_cast(static_cast(&(Pair.Value)))); Handle->GetProperty()->CopyCompleteValue(DestAddress, SourceAddress); } } */ } TAttribute FNaniteDisplacedMeshDetails::GetCanEditAttribute(int32 ObjectIndex) { TPair, FNaniteDisplacedMeshParams>& Pair = CustomizedPairs[ObjectIndex]; return TAttribute::Create([WeakMesh = Pair.Key] { if (UNaniteDisplacedMesh* DisplacedMesh = WeakMesh.Get()) { return DisplacedMesh->bIsEditable; } return false; }); } #undef LOCTEXT_NAMESPACE