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>
154 lines
6.3 KiB
Markdown
154 lines
6.3 KiB
Markdown
# 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.
|