Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Documentation~/EXPERIMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@
> **Обновлять этот документ при каждом эксперименте с transfer pipeline.**
> Последнее обновление: v0.15.39 (2026-04-07)

## Эксперимент 2026-08-06 — Изоляция cross-LOD hints по mesh group

- **Проблема:** индексы source shell локальны для mesh, но overlap/match hints
накапливались в общих списках и могли влиять на несвязанный mesh с совпавшим
числовым индексом shell.
- **Изменение:** состояние hints разделено по паре source `MeshEntry` +
`meshGroupKey`; между LOD передаются только hints той же пары.
- **Сохранение поведения:** внутри одной LOD-цепочки приоритет issues → hint →
3D distance не меняется; очищение состояния перед новым transfer/auto-tune
запуском сохранено.
- **Проверка:** требуется ручной Unity-прогон на нескольких mesh groups с
совпадающими локальными индексами shell и сравнение UV2 на всех LOD.

## Правила экспериментов

1. Один PR = одно изменение. Не наслаивать фиксы.
Expand Down
43 changes: 29 additions & 14 deletions Editor/Tools/LightmapTransferTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,18 @@ static UvtLog.Category[] BuildLogCategoryList()
// ── Transfer cache ──
Dictionary<int, GroupedShellTransfer.SourceShellInfo[]> shellTransformCache =
new Dictionary<int, GroupedShellTransfer.SourceShellInfo[]>();
List<GroupedShellTransfer.OverlapSourceHint> accumulatedOverlapHints =
new List<GroupedShellTransfer.OverlapSourceHint>();
List<GroupedShellTransfer.CrossLodMatchHint> accumulatedMatchHints =
new List<GroupedShellTransfer.CrossLodMatchHint>();
sealed class CrossLodHintState
{
public readonly List<GroupedShellTransfer.OverlapSourceHint> overlapHints =
new List<GroupedShellTransfer.OverlapSourceHint>();
public readonly List<GroupedShellTransfer.CrossLodMatchHint> matchHints =
new List<GroupedShellTransfer.CrossLodMatchHint>();
}

// Shell indices are local to a source mesh. Keep cross-LOD hints isolated
// to the source/mesh-group pair that produced them.
readonly Dictionary<(MeshEntry source, string meshGroupKey), CrossLodHintState> crossLodHints =
new Dictionary<(MeshEntry, string), CrossLodHintState>();

// ── Preview ──
// Three mutually-exclusive preview modes. Only one should be active at a time.
Expand Down Expand Up @@ -1807,7 +1815,7 @@ async Task<bool> ExecFullPipelineCoreImpl(bool useAsync)
kv.Key.shellTransferResult = null;
}
ctx.ClearAllCaches();
accumulatedOverlapHints.Clear();
crossLodHints.Clear();
shellTransformCache.Clear();
ctx.HasRepack = false;
ctx.HasTransfer = false;
Expand Down Expand Up @@ -2121,8 +2129,7 @@ async Task ExecTransferAllImpl(bool useAsync)
return;
}

accumulatedOverlapHints.Clear();
accumulatedMatchHints.Clear();
crossLodHints.Clear();
int processed = 0;
for (int li = 0; li < ctx.LodCount; li++)
{
Expand Down Expand Up @@ -2191,6 +2198,14 @@ async Task ExecTransferLodImpl(int tLod, bool useAsync)
Mesh tgtMesh = tgt.originalMesh;
if (srcMesh == null || tgtMesh == null) continue;

string meshGroupKey = tgt.meshGroupKey ?? tgt.renderer.name;
var hintKey = (source: srcEntry, meshGroupKey: meshGroupKey);
if (!crossLodHints.TryGetValue(hintKey, out var hintState))
{
hintState = new CrossLodHintState();
crossLodHints.Add(hintKey, hintState);
}

int srcId = srcMesh.GetInstanceID();
if (!shellTransformCache.TryGetValue(srcId, out var srcInfos))
{
Expand All @@ -2202,26 +2217,26 @@ async Task ExecTransferLodImpl(int tLod, bool useAsync)
UvProgress.Report(-1f, $"Transfer LOD{tLod} ← '{tgt.renderer.name}'");
var tr = useAsync
? await GroupedShellTransfer.TransferAsync(tgtMesh, srcMesh,
accumulatedOverlapHints.Count > 0 ? accumulatedOverlapHints : null,
accumulatedMatchHints.Count > 0 ? accumulatedMatchHints : null,
hintState.overlapHints.Count > 0 ? hintState.overlapHints : null,
hintState.matchHints.Count > 0 ? hintState.matchHints : null,
srcEntry.repackedAtlasWidth > 0 ? (int)srcEntry.repackedAtlasWidth : 0,
srcEntry.repackedAtlasHeight > 0 ? (int)srcEntry.repackedAtlasHeight : 0)
: GroupedShellTransfer.Transfer(tgtMesh, srcMesh,
accumulatedOverlapHints.Count > 0 ? accumulatedOverlapHints : null,
accumulatedMatchHints.Count > 0 ? accumulatedMatchHints : null,
hintState.overlapHints.Count > 0 ? hintState.overlapHints : null,
hintState.matchHints.Count > 0 ? hintState.matchHints : null,
srcEntry.repackedAtlasWidth > 0 ? (int)srcEntry.repackedAtlasWidth : 0,
srcEntry.repackedAtlasHeight > 0 ? (int)srcEntry.repackedAtlasHeight : 0);
if (tr.uv2 == null) { UvtLog.Warn($"[Transfer] Failed for '{tgt.renderer.name}'"); continue; }

// Accumulate overlap hints for subsequent LODs
if (tr.overlapHints != null && tr.overlapHints.Count > 0)
accumulatedOverlapHints.AddRange(tr.overlapHints);
hintState.overlapHints.AddRange(tr.overlapHints);
// Replace match hints with this LOD's matches (latest LOD drives
// next LOD's hint-guided matching; stale hints from older LODs
// could conflict with changing geometry)
accumulatedMatchHints.Clear();
hintState.matchHints.Clear();
if (tr.matchHints != null && tr.matchHints.Count > 0)
accumulatedMatchHints.AddRange(tr.matchHints);
hintState.matchHints.AddRange(tr.matchHints);

// Build output mesh with UV2 applied
var om = UnityEngine.Object.Instantiate(tgtMesh);
Expand Down
Loading