// Copyright IHY. #include "SOptimizerPanel.h" #include "OptimizerSubsystem.h" #include "Editor.h" #include "Widgets/SBoxPanel.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Layout/SScrollBox.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SCheckBox.h" #include "Widgets/Text/STextBlock.h" #include "Styling/AppStyle.h" #include "Styling/CoreStyle.h" #define LOCTEXT_NAMESPACE "OptimizerPanel" void SOptimizerPanel::Construct(const FArguments& InArgs) { const FSlateFontInfo TitleFont = FCoreStyle::GetDefaultFontStyle("Bold", 15); const FSlateFontInfo MonoFont = FCoreStyle::GetDefaultFontStyle("Mono", 9); ChildSlot [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) .Padding(FMargin(14.f)) [ SNew(SVerticalBox) // Title + SVerticalBox::Slot().AutoHeight().Padding(0, 0, 0, 2) [ SNew(STextBlock) .Text(LOCTEXT("Title", "Mesh Optimizer")) .Font(TitleFont) ] // Subtitle + SVerticalBox::Slot().AutoHeight().Padding(0, 0, 0, 12) [ SNew(STextBlock) .Text(LOCTEXT("Subtitle", "Find sibling StaticMeshes, unify onto one canonical mesh, then build HISM.")) .ColorAndOpacity(FSlateColor::UseSubduedForeground()) .AutoWrapText(true) ] // Detection toggles + SVerticalBox::Slot().AutoHeight().Padding(0, 0, 0, 10) [ SNew(SHorizontalBox) + SHorizontalBox::Slot().AutoWidth()[ MakeLabeledCheck(ChkMirror, LOCTEXT("Mirror", "Mirrored"), false) ] + SHorizontalBox::Slot().AutoWidth()[ MakeLabeledCheck(ChkScale, LOCTEXT("Scale", "Scaled"), false) ] + SHorizontalBox::Slot().AutoWidth()[ MakeLabeledCheck(ChkMergeMat, LOCTEXT("MergeMat", "Merge materials"), false) ] ] // Action buttons + SVerticalBox::Slot().AutoHeight().Padding(0, 0, 0, 8) [ SNew(SHorizontalBox) + SHorizontalBox::Slot().AutoWidth().Padding(0, 0, 8, 0) [ MakeButton(LOCTEXT("Scan", "Scan"), FOnClicked::CreateSP(this, &SOptimizerPanel::OnScan)) ] + SHorizontalBox::Slot().AutoWidth().Padding(0, 0, 8, 0) [ MakeButton(LOCTEXT("Unify", "Unify"), FOnClicked::CreateSP(this, &SOptimizerPanel::OnUnify)) ] + SHorizontalBox::Slot().AutoWidth() [ MakeButton(LOCTEXT("HISM", "Build HISM"), FOnClicked::CreateSP(this, &SOptimizerPanel::OnBuildHISM)) ] ] // Destroy-originals toggle + SVerticalBox::Slot().AutoHeight().Padding(0, 0, 0, 12) [ MakeLabeledCheck(ChkDestroy, LOCTEXT("Destroy", "Destroy originals when building HISM"), true) ] // Results area + SVerticalBox::Slot().FillHeight(1.f) [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder")) .Padding(FMargin(8.f)) [ SNew(SScrollBox) + SScrollBox::Slot() [ SAssignNew(ResultsBox, STextBlock) .Text(LOCTEXT("Idle", "Press Scan to analyze the level.")) .Font(MonoFont) .AutoWrapText(true) ] ] ] ] ]; } TSharedRef SOptimizerPanel::MakeLabeledCheck(TSharedPtr& Out, const FText& Label, bool bDefault) { return SNew(SHorizontalBox) + SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center) [ SAssignNew(Out, SCheckBox) .IsChecked(bDefault ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) ] + SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(5, 0, 16, 0) [ SNew(STextBlock).Text(Label) ]; } TSharedRef SOptimizerPanel::MakeButton(const FText& Label, FOnClicked OnClicked) { return SNew(SButton) .Text(Label) .OnClicked(OnClicked) .ContentPadding(FMargin(18.f, 6.f)) .HAlign(HAlign_Center); } bool SOptimizerPanel::IsChecked(const TSharedPtr& Chk) const { return Chk.IsValid() && Chk->IsChecked(); } void SOptimizerPanel::SetReport(const FString& Text) { if (ResultsBox.IsValid()) { ResultsBox->SetText(FText::FromString(Text)); } } FReply SOptimizerPanel::OnScan() { UOptimizerSubsystem* SS = GEditor ? GEditor->GetEditorSubsystem() : nullptr; if (!SS) { SetReport(TEXT("Subsystem unavailable.")); return FReply::Handled(); } FOptimizerScanSettings Settings; Settings.bAllowMirrored = IsChecked(ChkMirror); Settings.bAllowUniformScale = IsChecked(ChkScale); Settings.bMergeAcrossMaterials = IsChecked(ChkMergeMat); const FOptimizerScanResult R = SS->ScanLevel(Settings); FString Report = R.Summary + TEXT("\n"); Report += FString::Printf(TEXT("actors=%d components=%d unique meshes=%d\n\n"), R.ActorsScanned, R.ComponentsScanned, R.UniqueMeshes); if (R.Groups.Num() == 0) { Report += TEXT("No sibling groups found."); } for (const FOptimizerGroupView& G : R.Groups) { FString Flags; if (G.bHasMirrored) { Flags += TEXT(" [mirror]"); } if (G.bHasScaled) { Flags += TEXT(" [scale]"); } if (G.bMaterialMismatch) { Flags += TEXT(" [mat!=]"); } if (G.ShearRejectedCount > 0) { Flags += FString::Printf(TEXT(" [shear x%d]"), G.ShearRejectedCount); } Report += FString::Printf(TEXT("- %s (members=%d, placements=%d, fix=%d, maxDev=%.4f cm)%s\n %s\n"), *G.CanonicalMeshName, G.MemberMeshNames.Num(), G.InstanceCount, G.TransformFixCount, G.MaxDeviation, *Flags, *FString::Join(G.MemberMeshNames, TEXT(", "))); } SetReport(Report); return FReply::Handled(); } FReply SOptimizerPanel::OnUnify() { UOptimizerSubsystem* SS = GEditor ? GEditor->GetEditorSubsystem() : nullptr; if (!SS) { SetReport(TEXT("Subsystem unavailable.")); return FReply::Handled(); } if (!SS->HasPlan()) { SetReport(TEXT("Run Scan first.")); return FReply::Handled(); } const int32 N = SS->ApplyUnify(); SetReport(FString::Printf(TEXT("Unify done: %d placement(s) reassigned to canonical mesh + corrected transform.\nUndo with Ctrl+Z. Re-scan before Build HISM."), N)); return FReply::Handled(); } FReply SOptimizerPanel::OnBuildHISM() { UOptimizerSubsystem* SS = GEditor ? GEditor->GetEditorSubsystem() : nullptr; if (!SS) { SetReport(TEXT("Subsystem unavailable.")); return FReply::Handled(); } if (!SS->HasPlan()) { SetReport(TEXT("Run Scan first.")); return FReply::Handled(); } const bool bDestroy = IsChecked(ChkDestroy); const int32 N = SS->BuildHISM(bDestroy); SetReport(FString::Printf(TEXT("Build HISM done: %d HISM actor(s) created%s.\nUndo with Ctrl+Z."), N, bDestroy ? TEXT(" (originals destroyed)") : TEXT(""))); return FReply::Handled(); } #undef LOCTEXT_NAMESPACE