diff --git a/CHANGELOG.md b/CHANGELOG.md index f03ed638..f89f4c1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this ## [Unreleased] +### Fixed +- Bounded the source-shell overlap scan used by grouped UV transfer so highly fragmented, attacker-controlled meshes cannot trigger an unbounded quadratic pre-transfer pass and stall the Unity Editor. + ### Added - **`UvProgress` service** (`Editor/Framework/UvProgress.cs`) — central non-modal progress reporting. Routes status to `UnityEditor.Progress` (Background Tasks panel) plus an inline strip drawn at the bottom of the hub window. Supports nested scopes, phase labels, indeterminate/determinate fractions, cooperative cancellation via `UvProgress.CancelRequested` (`Volatile.Read`-backed `_cancelFlag` so background `Task.Run` work observes user-cancel reliably across the memory barrier), a thread-safe `ReportFromBackground` for `Task.Run` callers (with `Interlocked.Exchange`-guarded snapshot/clear so a racing writer can't lose an update; the `EditorApplication.update` pump is hooked once on assembly load from the main thread via `[InitializeOnLoadMethod]`), and a `Last` outcome shown while idle. - **Inline progress strip in `UvToolHub`** — sits at the bottom of the window as a status bar. Reserves fixed height unconditionally so toggling active state doesn't displace any layout. Shows title · phase · detail · elapsed in distinct columns with a Cancel button pinned to the right; while idle displays `✓ / ✗ Last-operation · 12.3s`. Marquee animation for indeterminate fractions; orange tint while cancelling. diff --git a/Editor/GroupedShellTransfer.cs b/Editor/GroupedShellTransfer.cs index 297ae1b9..be249a6d 100644 --- a/Editor/GroupedShellTransfer.cs +++ b/Editor/GroupedShellTransfer.cs @@ -21,6 +21,11 @@ namespace SashaRX.UnityMeshLab { public static class GroupedShellTransfer { + // Keep overlap detection bounded: FindOverlapGroups compares every shell pair. + // Typical production meshes stay well below this limit; pathological meshes + // fall back to the original fixed retry count instead of stalling the Editor. + const int kMaxShellsForOverlapScan = 512; + // ─── Similarity Transform (4 params: a, b, tx, ty) ─── public struct SimilarityTransform { @@ -1180,12 +1185,21 @@ static TransferResult TransferCore( : 0.001f; float kUv0BadThreshold = Mathf.Max(avgUv0Edge * avgUv0Edge, 0.001f); - // Adaptive kMaxRetries based on overlap group size - var overlapGroups = UvShellExtractor.FindOverlapGroups(srcShells); + // Adaptive kMaxRetries based on overlap group size. The overlap detector is + // quadratic, so never run it for attacker-controlled, highly fragmented meshes. + bool scanOverlapGroups = srcShells.Count <= kMaxShellsForOverlapScan; + var overlapGroups = scanOverlapGroups + ? UvShellExtractor.FindOverlapGroups(srcShells) + : new List>(); int maxOverlapGroupSize = 0; foreach (var group in overlapGroups) maxOverlapGroupSize = Mathf.Max(maxOverlapGroupSize, group.Count); - int kMaxRetries = Mathf.Clamp(maxOverlapGroupSize + 2, 5, srcShells.Count); + int kMaxRetries = scanOverlapGroups + ? Mathf.Clamp(maxOverlapGroupSize + 2, 5, srcShells.Count) + : Mathf.Min(5, srcShells.Count); + + if (!scanOverlapGroups) + UvtLog.Warn($"[GroupedTransfer] Skipping quadratic UV overlap scan for {srcShells.Count} source shells (limit {kMaxShellsForOverlapScan})."); // Build overlap group membership: srcShell → list of all group members var srcShellOverlapMembers = new List[srcShells.Count];