Files
UnrealEngine/Engine/Source/Editor/FoliageEdit/Private/ProceduralFoliageComponentDetails.cpp
2025-05-18 13:04:45 +08:00

211 lines
6.1 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ProceduralFoliageComponentDetails.h"
#include "DetailCategoryBuilder.h"
#include "DetailLayoutBuilder.h"
#include "DetailWidgetRow.h"
#include "FoliageTypeObject.h"
#include "Fonts/SlateFontInfo.h"
#include "HAL/PlatformCrt.h"
#include "Internationalization/Internationalization.h"
#include "Misc/AssertionMacros.h"
#include "Misc/Attribute.h"
#include "ProceduralFoliageComponent.h"
#include "ProceduralFoliageEditorLibrary.h"
#include "ProceduralFoliageSpawner.h"
#include "ScopedTransaction.h"
#include "SlotBase.h"
#include "Templates/Casts.h"
#include "UObject/NameTypes.h"
#include "UObject/Object.h"
#include "UObject/ObjectPtr.h"
#include "UObject/WeakObjectPtr.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Text/STextBlock.h"
class IPropertyHandle;
#define LOCTEXT_NAMESPACE "ProceduralFoliageComponentDetails"
TSharedRef<IDetailCustomization> FProceduralFoliageComponentDetails::MakeInstance()
{
return MakeShareable(new FProceduralFoliageComponentDetails());
}
void FProceduralFoliageComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
const FName ProceduralFoliageCategoryName("ProceduralFoliage");
IDetailCategoryBuilder& ProceduralFoliageCategory = DetailBuilder.EditCategory(ProceduralFoliageCategoryName);
const FText ResimulateText = LOCTEXT("ResimulateButtonText", "Resimulate");
const FText LoadUnloadedAreasText = LOCTEXT("LoadUnloadedAreasButtonText", "Load Unloaded Areas");
TArray< TWeakObjectPtr<UObject> > ObjectsBeingCustomized;
DetailBuilder.GetObjectsBeingCustomized(ObjectsBeingCustomized);
for (TWeakObjectPtr<UObject>& Object : ObjectsBeingCustomized)
{
UProceduralFoliageComponent* Component = Cast<UProceduralFoliageComponent>(Object.Get());
if (ensure(Component))
{
SelectedComponents.Add(Component);
}
}
TArray<TSharedRef<IPropertyHandle>> AllProperties;
bool bSimpleProperties = true;
bool bAdvancedProperties = false;
// Add all properties in the category in order
ProceduralFoliageCategory.GetDefaultProperties(AllProperties, bSimpleProperties, bAdvancedProperties);
for (auto& Property : AllProperties)
{
ProceduralFoliageCategory.AddProperty(Property);
}
FDetailWidgetRow& NewRow = ProceduralFoliageCategory.AddCustomRow(FText::GetEmpty());
NewRow.ValueContent()
.MaxDesiredWidth(120.f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(4.0f)
[
SNew(SButton)
.OnClicked(this, &FProceduralFoliageComponentDetails::OnResimulateClicked)
.ToolTipText(this, &FProceduralFoliageComponentDetails::GetResimulateTooltipText)
.IsEnabled(this, &FProceduralFoliageComponentDetails::IsResimulateEnabled)
[
SNew(STextBlock)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Text(ResimulateText)
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(4.0f)
[
SNew(SButton)
.OnClicked(this, &FProceduralFoliageComponentDetails::OnLoadUnloadedAreas)
.ToolTipText(LOCTEXT("Load_UnloadedAreas", "Load unloaded areas required to simulate."))
.IsEnabled(this, &FProceduralFoliageComponentDetails::HasUnloadedAreas)
[
SNew(STextBlock)
.Font(IDetailLayoutBuilder::GetDetailFont())
.Text(LoadUnloadedAreasText)
]
]
];
}
FReply FProceduralFoliageComponentDetails::OnResimulateClicked()
{
TArray<UProceduralFoliageComponent*> SelectedFoliageComponents;
SelectedFoliageComponents.Reserve(SelectedComponents.Num());
for (TWeakObjectPtr<UProceduralFoliageComponent>& Component : SelectedComponents)
{
if (Component.IsValid())
{
SelectedFoliageComponents.Add(Component.Get());
}
}
FScopedTransaction Transaction(LOCTEXT("Resimulate_Transaction", "Procedural Foliage Simulation"));
UProceduralFoliageEditorLibrary::ResimulateProceduralFoliageComponents(SelectedFoliageComponents);
return FReply::Handled();
}
FReply FProceduralFoliageComponentDetails::OnLoadUnloadedAreas()
{
for(const TWeakObjectPtr<UProceduralFoliageComponent>& Component : SelectedComponents)
{
if(Component.IsValid())
{
Component->LoadSimulatedRegion();
}
}
return FReply::Handled();
}
bool FProceduralFoliageComponentDetails::IsResimulateEnabled() const
{
FText Reason;
return IsResimulateEnabledWithReason(Reason);
}
bool FProceduralFoliageComponentDetails::IsResimulateEnabledWithReason(FText& OutReason) const
{
bool bCanSimulate = false;
for(const TWeakObjectPtr<UProceduralFoliageComponent>& Component : SelectedComponents)
{
if(Component.IsValid())
{
if (!Component->FoliageSpawner)
{
OutReason = LOCTEXT("Resimulate_Tooltip_NeedSpawner", "Cannot generate foliage: Assign a Procedural Foliage Spawner to run the procedural foliage simulation");
return false;
}
if (!bCanSimulate)
{
for (const FFoliageTypeObject& FoliageTypeObject : Component->FoliageSpawner->GetFoliageTypes())
{
// Make sure at least one foliage type is ready to spawn
if (FoliageTypeObject.HasFoliageType())
{
bCanSimulate = true;
break;
}
}
if (!bCanSimulate)
{
OutReason = LOCTEXT("Resimulate_Tooltip_EmptySpawner", "Cannot generate foliage: The assigned Procedural Foliage Spawner does not contain any foliage types to spawn.");
return false;
}
}
}
}
if (bCanSimulate && HasUnloadedAreas())
{
OutReason = LOCTEXT("Resimulate_Tooltip_UnloadedRegion", "Cannot generate foliage: The assigned Procedural Foliage Volume covers an unloaded area.");
return false;
}
OutReason = LOCTEXT("Resimulate_Tooltip", "Runs the procedural foliage spawner simulation. Replaces any existing instances spawned by a previous simulation.");
return true;
}
FText FProceduralFoliageComponentDetails::GetResimulateTooltipText() const
{
FText TooltipText;
IsResimulateEnabledWithReason(TooltipText);
return TooltipText;
}
bool FProceduralFoliageComponentDetails::HasUnloadedAreas() const
{
for(const TWeakObjectPtr<UProceduralFoliageComponent>& Component : SelectedComponents)
{
if(Component.IsValid())
{
if (!Component->IsSimulatedRegionLoaded())
{
return true;
}
}
}
return false;
}
#undef LOCTEXT_NAMESPACE