Files
Bonchellon a95b299680 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>
2026-07-01 18:26:45 +03:00

154 lines
6.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Mesh Optimizer — Sibling Duplicate Remover
Editor-only Unreal Engine plugin that finds **geometrically identical StaticMesh assets** used across a
level, rebases every placement onto a single **canonical** mesh with a corrected transform, and
optionally collapses the group into a **Hierarchical Instanced Static Mesh (HISM)**.
> UE 5.7 · Editor module `OptimizerEditor` · native Slate tool panel.
---
## The problem it solves
Bought kits and **Kitbash3D**-style packs import the *same* geometry as many separate assets:
`SM_Fence_01`, `SM_Fence_02`, … are literal twins — identical vertices, sometimes with a different
**baked pivot or rotation**. That defeats instancing: the engine sees N different meshes, so it can't
batch them, and you can't make one HISM out of them.
Mesh Optimizer detects those "sibling" meshes, picks one to keep, and re-points every actor that used a
sibling to the survivor — **fixing each actor's transform** so nothing moves on screen — leaving a set
that is ready to become a single HISM.
---
## What it does
1. **Scan** (dry run — mutates nothing): walk the level's StaticMesh placements, fingerprint every
unique mesh, group the geometric siblings, and report each group with a confidence figure
(`maxDeviation`).
2. **Unify**: reassign every sibling placement to its group's canonical mesh and apply the corrected
world transform. Undoable (`Ctrl+Z`).
3. **Build HISM**: create one `HierarchicalInstancedStaticMeshComponent` per group from the corrected
transforms, optionally destroying the originals. Undoable.
Every mutation runs inside a single editor transaction.
---
## How it works
```
extract geometry (LOD0 FMeshDescription)
→ fingerprint (welded vertex/tri counts, surface area, |volume|,
covariance eigenvalues, radial histogram — all rotation-invariant)
→ bucket by (welded vertex count, triangle count)
→ recover the rigid delta between candidates
(identity fast-path, then the 24/48 signed-permutation axis rotations)
→ verify by order-independent nearest-neighbour vertex match (spatial hash)
→ union-find into groups, pick a canonical
→ plan the corrected transform per placement
```
**The corrective transform.** With `D` = the recovered *canonical-local → sibling-local* delta and
`W` = the sibling placement's current world transform, the canonical mesh must be placed at
```
W' = D * W (UE FTransform multiply order; matrix W'_mat = D_mat * W_mat)
```
built via `FMatrix` and reconstructed into an `FTransform`. Because a non-uniform actor scale combined
with a rotational `D` can produce **shear** (which `FTransform` cannot represent), each result is
checked at the bounding-box corners and any placement that would shear is **skipped and reported**
rather than silently corrupted.
Every candidate match is confirmed by an **exact vertex-set verification** before it is ever applied, so
a bad transform is rejected, not shipped.
---
## Installation
1. Copy this folder into your project's `Plugins/` directory (so it lives at `Plugins/Optimizer/`).
2. Add it to your `.uproject` (editor-only):
```json
{ "Name": "Optimizer", "Enabled": true, "TargetAllowList": [ "Editor" ] }
```
3. Regenerate project files and build the editor target (a new module needs a full build — Live Coding
cannot register a brand-new module).
Requires **Unreal Engine 5.7** and an editor build (`<Project>Editor Win64 Development`).
---
## Usage
Open the panel from **Tools → Optimizer → Mesh Optimizer**.
| Control | Effect |
|---|---|
| **Scan** | Dry-run analysis of the current level; fills the report. Changes nothing. |
| **Unify** | Reassign sibling placements to the canonical mesh + corrected transform. |
| **Build HISM** | Collapse each group into one HISM actor. |
| ☐ Mirrored | Also treat negative-scale (mirrored) variants as siblings. |
| ☐ Scaled | Also treat uniformly-scaled copies as siblings. |
| ☐ Merge materials | Group siblings even if their material sets differ (the canonical's materials win). |
| ☐ Destroy originals | When building HISM, delete the original actors/components. |
Recommended flow: **Scan → review the report** (check `maxDev` and the flags) **→ Unify** (or **Build
HISM**). Everything is undoable with `Ctrl+Z`.
---
## Detection modes
- **Exact + rigid (default):** identical geometry up to a rigid transform (rotation and/or a baked
pivot offset). Covers almost all Kitbash cases.
- **+ Mirrored:** negative-scale reflections (kept as a separate canonical group, since HISM winding
differs).
- **+ Scaled:** uniform-scale copies (the instance carries the scale).
Advanced tolerances (weld epsilon, accept tolerance, scalar relative tolerance, degenerate-eigenvalue
epsilon) are exposed on the scan settings for tuning against specific content.
---
## Safety & limitations
- **World Partition:** only *loaded* cells are visible to the scan — coverage is partial and the report
says so.
- **Shear:** non-uniform actor scale combined with a rotational delta is unrepresentable by
`FTransform`; those placements are skipped and counted, never corrupted.
- **ISM/HISM sources:** existing instanced components are out of scope in this version (only plain
`StaticMeshActor`s / components are processed).
- **Materials:** by default siblings with different material sets are kept in separate groups so each
HISM keeps one material set.
---
## Architecture
```
Source/OptimizerEditor/
OptimizerEditor.Build.cs
Public/
OptimizerEditorModule.h module + Tools menu / nomad tab
OptimizerSubsystem.h UEditorSubsystem: ScanLevel / ApplyUnify / BuildHISM
OptimizerTypes.h settings + result structs/enums
Private/
OptimizerEditorModule.cpp
OptimizerGeometry.{h,cpp} geometry extraction + fingerprint + 3x3 Jacobi eigensolver
OptimizerMatcher.{h,cpp} bucketing, transform recovery, spatial-hash verification
OptimizerReconciler.{h,cpp} W' = D * W + shear detection
OptimizerSubsystem.cpp enumeration, planning, transacted mutation, HISM build
SOptimizerPanel.{h,cpp} native Slate tool panel
```
The heavy logic is UI-agnostic C++ in `UOptimizerSubsystem` (`BlueprintCallable`); the Slate panel is a
thin driver.
---
## License
Internal tool. © IHY / ExbyteLabs.