Mesh Optimizer: sibling StaticMesh duplicate remover (UE 5.7)

Editor plugin that detects geometrically-identical sibling StaticMeshes across a
level, rebases each placement onto one canonical mesh with a corrected transform
(W' = D * W, verified by exact vertex matching), and can collapse groups into HISM.
Native Slate tool panel + BlueprintCallable UOptimizerSubsystem.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-01 18:26:45 +03:00
commit a95b299680
17 changed files with 2172 additions and 0 deletions

View File

@ -0,0 +1,26 @@
// Copyright IHY.
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleInterface.h"
/**
* Editor-only module for the Mesh Optimizer.
*
* Registers a Tools-menu entry + nomad tab hosting the native Slate panel (SOptimizerPanel),
* which drives UOptimizerSubsystem: scan level StaticMesh usage, group geometrically-identical
* "sibling" meshes, rebase every user onto one canonical mesh with a pivot/rotation-corrected
* transform, and optionally collapse the group into a HierarchicalInstancedStaticMesh.
*/
class FOptimizerEditorModule : public IModuleInterface
{
public:
virtual void StartupModule() override;
virtual void ShutdownModule() override;
private:
void RegisterMenus(); // deferred via UToolMenus startup callback
TSharedRef<class SDockTab> SpawnPanelTab(const class FSpawnTabArgs& Args);
bool bTabRegistered = false;
};

View File

@ -0,0 +1,76 @@
// Copyright IHY.
#pragma once
#include "CoreMinimal.h"
#include "EditorSubsystem.h"
#include "OptimizerTypes.h"
#include "OptimizerSubsystem.generated.h"
class UStaticMesh;
class UStaticMeshComponent;
/**
* Editor subsystem driving the Mesh Optimizer pipeline. The Editor Utility Widget calls these:
* 1. ScanLevel -> dry-run plan (sibling groups + per-placement corrected transforms)
* 2. ApplyUnify -> reassign every sibling placement to its canonical mesh with the fixed transform
* 3. BuildHISM -> collapse each group into one HierarchicalInstancedStaticMesh
* Steps 2 and 3 each run inside a single undoable transaction. BuildHISM is independent of ApplyUnify
* (it uses the canonical mesh + planned transforms directly).
*/
UCLASS()
class OPTIMIZEREDITOR_API UOptimizerSubsystem : public UEditorSubsystem
{
GENERATED_BODY()
public:
/** Dry run: scan the level, group sibling meshes, plan corrected transforms. Mutates nothing. */
UFUNCTION(BlueprintCallable, Category = "Optimizer")
FOptimizerScanResult ScanLevel(const FOptimizerScanSettings& Settings);
/** Reassign all non-instanced sibling placements to their canonical mesh + corrected transform. Returns count changed. */
UFUNCTION(BlueprintCallable, Category = "Optimizer")
int32 ApplyUnify();
/** Build one HISM actor per group from the planned transforms. Returns number of HISM actors created. */
UFUNCTION(BlueprintCallable, Category = "Optimizer")
int32 BuildHISM(bool bDestroyOriginals = true);
/** Discard the current plan. */
UFUNCTION(BlueprintCallable, Category = "Optimizer")
void ClearPlan();
UFUNCTION(BlueprintCallable, Category = "Optimizer")
bool HasPlan() const;
private:
/** One placement of a mesh in the level (a component, or one instance inside an ISM/HISM). */
struct FPlacement
{
TWeakObjectPtr<UStaticMeshComponent> Component;
int32 InstanceIndex = INDEX_NONE; // >= 0 when this is an instance inside an ISM/HISM
TWeakObjectPtr<UStaticMesh> Mesh;
FTransform World = FTransform::Identity;
FTransform PlannedWorld = FTransform::Identity;
bool bNeedsTransformFix = false;
bool bShearRejected = false;
bool bInstanced = false; // inside an ISM/HISM -> ApplyUnify can't swap it (HISM-only)
};
/** A sibling group: distinct meshes that share geometry, collapsed onto one canonical. */
struct FGroup
{
TArray<TWeakObjectPtr<UStaticMesh>> Members;
TWeakObjectPtr<UStaticMesh> Canonical;
TArray<FPlacement> Placements;
float MaxDeviation = 0.0f;
bool bHasMirrored = false;
bool bHasScaled = false;
bool bMaterialMismatch = false;
int32 ShearRejected = 0;
};
TArray<FGroup> Groups;
FOptimizerScanSettings LastSettings;
FOptimizerScanResult BuildResultView() const;
};

View File

@ -0,0 +1,157 @@
// Copyright IHY.
#pragma once
#include "CoreMinimal.h"
#include "OptimizerTypes.generated.h"
/**
* Shared data contract between the C++ optimizer core and the Editor Utility Widget UI.
* BlueprintType structs/enums here are what the EUW reads/writes; the heavy internal
* algorithm structures (point clouds, recovered deltas) live in the matcher/reconciler.
*/
/** Which placements to scan. v1 = level StaticMeshActors / standalone components. */
UENUM(BlueprintType)
enum class EOptimizerScanScope : uint8
{
SelectedActors UMETA(DisplayName = "Selected Actors"),
WholeLevel UMETA(DisplayName = "Whole Level (loaded)")
};
/** How aggressively to treat two meshes as the same geometry. */
UENUM(BlueprintType)
enum class EOptimizerMatchPrecision : uint8
{
// Identical geometry AND identical orientation/pivot (recovered delta D == Identity).
ExactOnly UMETA(DisplayName = "Exact duplicates only"),
// Identical geometry up to a rigid transform (rotation and/or baked pivot offset).
RigidTransforms UMETA(DisplayName = "Exact + rotated/pivot (rigid)")
};
/** How to pick the surviving canonical mesh within a sibling group. */
UENUM(BlueprintType)
enum class EOptimizerCanonicalPolicy : uint8
{
MostInstances UMETA(DisplayName = "Most-used mesh"),
LowestVertexCount UMETA(DisplayName = "Lowest vertex count"),
FirstAlphabetical UMETA(DisplayName = "First alphabetical")
};
/** User-facing scan configuration. */
USTRUCT(BlueprintType)
struct FOptimizerScanSettings
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Scope")
EOptimizerScanScope Scope = EOptimizerScanScope::WholeLevel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Detection")
EOptimizerMatchPrecision Precision = EOptimizerMatchPrecision::RigidTransforms;
/** Treat negative-scale (mirrored) variants as siblings. They become their own canonical/HISM. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Detection")
bool bAllowMirrored = false;
/** Treat uniformly-scaled copies as siblings (instance carries the scale). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Detection")
bool bAllowUniformScale = false;
/** Merge geometry siblings even when their material sets differ (canonical's materials win — risky). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Detection")
bool bMergeAcrossMaterials = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Canonical")
EOptimizerCanonicalPolicy CanonicalPolicy = EOptimizerCanonicalPolicy::MostInstances;
/** Position weld grid, cm. Snaps near-coincident verts so seam-splitting doesn't change counts. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Advanced|Tolerances", AdvancedDisplay, meta = (ClampMin = "0.0"))
float WeldEpsilon = 0.01f;
/** Max per-vertex world deviation to ACCEPT a match, cm. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Advanced|Tolerances", AdvancedDisplay, meta = (ClampMin = "0.0"))
float AcceptTolerance = 0.01f;
/** Relative tolerance for scalar fingerprint features (area / volume / eigenvalues). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Advanced|Tolerances", AdvancedDisplay, meta = (ClampMin = "0.0"))
float ScalarRelTolerance = 0.001f;
/** Eigenvalue-gap fraction below which PCA orientation is considered degenerate -> brute-24 fallback. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Advanced|Tolerances", AdvancedDisplay, meta = (ClampMin = "0.0"))
float DegenerateEigenEpsilon = 0.05f;
};
/** One sibling group, summarised for the UI list. */
USTRUCT(BlueprintType)
struct FOptimizerGroupView
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly, Category = "Optimizer")
int32 GroupId = -1;
UPROPERTY(BlueprintReadOnly, Category = "Optimizer")
FString CanonicalMeshName;
UPROPERTY(BlueprintReadOnly, Category = "Optimizer")
FString CanonicalMeshPath;
/** Distinct sibling assets in the group (includes the canonical). */
UPROPERTY(BlueprintReadOnly, Category = "Optimizer")
TArray<FString> MemberMeshNames;
/** Total placements (actors/components) that will collapse onto the canonical. */
UPROPERTY(BlueprintReadOnly, Category = "Optimizer")
int32 InstanceCount = 0;
/** Placements that need a corrected transform (W' != W). */
UPROPERTY(BlueprintReadOnly, Category = "Optimizer")
int32 TransformFixCount = 0;
/** Worst per-vertex verification deviation across the group, cm. */
UPROPERTY(BlueprintReadOnly, Category = "Optimizer")
float MaxDeviation = 0.0f;
UPROPERTY(BlueprintReadOnly, Category = "Optimizer")
bool bHasMirrored = false;
UPROPERTY(BlueprintReadOnly, Category = "Optimizer")
bool bHasScaled = false;
UPROPERTY(BlueprintReadOnly, Category = "Optimizer")
bool bMaterialMismatch = false;
/** Placements skipped because the corrective transform would shear (non-uniform scale + rotation). */
UPROPERTY(BlueprintReadOnly, Category = "Optimizer")
int32 ShearRejectedCount = 0;
};
/** Result of a dry-run scan: the plan the user reviews before applying. */
USTRUCT(BlueprintType)
struct FOptimizerScanResult
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly, Category = "Optimizer")
TArray<FOptimizerGroupView> Groups;
UPROPERTY(BlueprintReadOnly, Category = "Optimizer")
int32 ActorsScanned = 0;
UPROPERTY(BlueprintReadOnly, Category = "Optimizer")
int32 ComponentsScanned = 0;
UPROPERTY(BlueprintReadOnly, Category = "Optimizer")
int32 UniqueMeshes = 0;
/** Placements removable by collapsing siblings (sum over groups of InstanceCount - 1 distinct survivors). */
UPROPERTY(BlueprintReadOnly, Category = "Optimizer")
int32 InstancesCollapsible = 0;
/** True if the world is partitioned and some cells were unloaded -> coverage is partial. */
UPROPERTY(BlueprintReadOnly, Category = "Optimizer")
bool bWorldPartitionCoverageWarning = false;
UPROPERTY(BlueprintReadOnly, Category = "Optimizer")
FString Summary;
};