Skip to content

Commit 019b06c

Browse files
committed
[EmtMake] Implement meshCombinator recovery. Fix #145
1 parent 79b444a commit 019b06c

1 file changed

Lines changed: 133 additions & 13 deletions

File tree

FreeMote.PsBuild/MmoBuilder.cs

Lines changed: 133 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,8 @@ PsbList BuildChildrenFromMotion(PsbDictionary dic, IPsbCollection parent)
418418
objectChildrenItem["parameterize"] = motionItem.ContainsKey("parameterize") && motionItem["parameterize"] is not PsbNull ?
419419
parameter[((PsbNumber)motionItem["parameterize"]).IntValue]
420420
: FillDefaultParameterize(dic);
421+
// priorityFrameList is a draw-order list of flattened layer indices, not a layer id list.
422+
// After mc=1 helper layers are inserted, these indices must be remapped.
421423
objectChildrenItem["priorityFrameList"] = BuildPriorityFrameList((PsbList)motionItem["priority"]);
422424
objectChildrenItem["referenceModelFileList"] = motionItem["referenceModelFileList"];
423425
objectChildrenItem["referenceProjectFileList"] = motionItem["referenceProjectFileList"];
@@ -429,7 +431,22 @@ PsbList BuildChildrenFromMotion(PsbDictionary dic, IPsbCollection parent)
429431
int lastTime = lastTimeVal?.IntValue ?? 61;
430432
var layer = (PsbList)motionItem["layer"];
431433
layer.Parent = objectChildrenItem;
434+
435+
// Capture flat layer order before topology changes.
436+
// Note: mc=1 means meshCombine-enabled helper layer.
437+
// Once meshCombinator is materialized into physical helper layers, flatten indices shift.
438+
var oldFlatList = FlattenLayerTree(layer);
439+
432440
BuildLayerChildren(layer, parameter, lastTime);
441+
442+
// priorityFrameList stores indices into flattened layer order.
443+
// If we inserted helpers (count changed), remap old indices to new positions.
444+
var newFlatList = FlattenLayerTree(layer);
445+
if (oldFlatList.Count != newFlatList.Count)
446+
{
447+
RemapPriorityFrameList(objectChildrenItem["priorityFrameList"] as PsbList, oldFlatList, newFlatList);
448+
}
449+
433450
objectChildrenItem["layerChildren"] = motionItem["layer"];
434451

435452
objectChildren_children.Add(objectChildrenItem);
@@ -463,7 +480,8 @@ void BuildLayerChildren(IPsbCollection child, PsbList parameter, int lastTime)
463480
dic["className"] = classType.ToString().ToPsbString();
464481
dic["comment"] = PsbString.Empty;
465482

466-
//Restore meshCombinator BEFORE BuildFrameList so new frames get processed
483+
// Restore meshCombinator before BuildFrameList so packed mesh data is expanded into real helper layers.
484+
// meshCombinator is the packed container that stores ci=0 / ci>0 meshCombine tracks.
467485
if (dic.ContainsKey("meshCombinator") && dic["meshCombinator"] is PsbDictionary meshCombinator)
468486
{
469487
RestoreMeshCombinator(dic, meshCombinator, parameter, lastTime);
@@ -527,7 +545,8 @@ void BuildLayerChildren(IPsbCollection child, PsbList parameter, int lastTime)
527545
};
528546
}
529547

530-
//Expand meshSyncChildMask
548+
// meshSyncChildMask controls which child meshCombine layers inherit or sync to this layer.
549+
// Expand the packed bitmask into explicit MMO fields.
531550
if (dic["meshSyncChildMask"] is PsbNumber number)
532551
{
533552
dic["meshSyncChildShape"] = (number.IntValue & 8) == 8 ? 1.ToPsbNumber() : PsbNumber.Zero;
@@ -740,6 +759,8 @@ PsbDictionary BuildCharaProfileItem(string id, string label, string path)
740759
/// Restore meshCombinator to individual layers with mesh frame data.
741760
/// <para>During MMO→PSB compilation, stripMeshCombineLayer extracts mesh data from layers
742761
/// and packs it into meshCombinator on a parent layer. This method reverses that process:</para>
762+
/// <para>Terminology: in combinatorList, ci means combinator index.
763+
/// Typically ci=0 is the parent/self mesh track, ci&gt;0 are helper-layer tracks.</para>
743764
/// <para>If the parent already has its own explicit parameter, keep it and materialize
744765
/// combinator entries as nested meshCombine child layers.</para>
745766
/// <para>Otherwise ci=0 restores into the parent layer's frameList and the remaining
@@ -777,6 +798,7 @@ private void RestoreMeshCombinator(PsbDictionary parent, PsbDictionary meshCombi
777798

778799
// If the parent does not already expose a different parameter, restore the first
779800
// combinator directly onto the parent and materialize the remaining ones as child layers.
801+
// In most assets this first entry corresponds to ci=0.
780802
if (restoreFirstToParent && combinatorList[0] is PsbDictionary firstCombinator)
781803
{
782804
var variable = firstCombinator["variable"] as PsbDictionary;
@@ -839,14 +861,8 @@ private void RestoreMeshCombinator(PsbDictionary parent, PsbDictionary meshCombi
839861
}
840862
}
841863

842-
// Reparenting the original children under restored helper nodes changes the
843-
// canonical layer paths for MAkar120 and regresses visibility back to a
844-
// lower-body-only state. Keep the remaining combinators packed until we can
845-
// restore them without rewriting the visible child topology.
846-
if (startIndex >= combinatorList.Count)
847-
{
848-
parent.Remove("meshCombinator");
849-
}
864+
MaterializeMeshCombinatorChildren(parent, combinatorList, startIndex, parameter, lastTime, valuesPerMesh, templateContent);
865+
parent.Remove("meshCombinator");
850866
}
851867

852868
private void MaterializeMeshCombinatorChildren(PsbDictionary parent, PsbList combinatorList, int startIndex,
@@ -862,6 +878,8 @@ private void MaterializeMeshCombinatorChildren(PsbDictionary parent, PsbList com
862878
PsbList currentChildren = topChildren;
863879
PsbDictionary deepestHelper = null;
864880

881+
// Build helper layers as nested chain, not flat siblings:
882+
// helper(startIndex) -> helper(startIndex+1) -> ... -> originalChildren
865883
for (var index = startIndex; index < combinatorList.Count; index++)
866884
{
867885
if (!(combinatorList[index] is PsbDictionary combinator) ||
@@ -882,7 +900,7 @@ private void MaterializeMeshCombinatorChildren(PsbDictionary parent, PsbList com
882900
continue;
883901
}
884902

885-
var allValues = DecodeMeshValues(rawMeshList, meshCount, valuesPerMesh, isDelta: false);
903+
var allValues = DecodeMeshValues(rawMeshList, meshCount, valuesPerMesh, isDelta: true);
886904
if (allValues == null)
887905
{
888906
continue;
@@ -894,6 +912,7 @@ private void MaterializeMeshCombinatorChildren(PsbDictionary parent, PsbList com
894912
helper["label"] = GetMeshCombinatorLayerLabel(key).ToPsbString();
895913
helper["frameList"] = BuildMeshFrameList(allValues, meshCount, neutralIndex, valuesPerMesh, lastTime, templateContent);
896914
helper["parameterize"] = paramIndex.ToPsbNumber();
915+
// mc means meshCombine. 1 means this node is a meshCombine helper layer.
897916
helper["meshCombine"] = 1.ToPsbNumber();
898917
helper.Remove("meshCombinator");
899918
helper.Parent = currentChildren;
@@ -956,6 +975,7 @@ private PsbList BuildMeshFrameList(double[] allValues, int meshCount, int neutra
956975
content["src"] = "blank".ToPsbString();
957976
}
958977

978+
// mesh is the PSB-side mesh payload; it contains bp (BezierPatch) and cc (ColorControl).
959979
content["mesh"] = BuildMeshDict(allValues, meshIndex, neutralIndex, valuesPerMesh);
960980
frameList.Add(new PsbDictionary
961981
{
@@ -1136,6 +1156,7 @@ private static PsbDictionary BuildMeshDict(double[] allValues, int meshIndex, in
11361156
{
11371157
if (meshIndex == neutralIndex)
11381158
{
1159+
// bp = BezierPatch, cc = ColorControl.
11391160
return new PsbDictionary {{"bp", PsbNull.Null}, {"cc", PsbNull.Null}};
11401161
}
11411162

@@ -1144,6 +1165,7 @@ private static PsbDictionary BuildMeshDict(double[] allValues, int meshIndex, in
11441165
{
11451166
bp.Add(new PsbNumber(allValues[meshIndex * valuesPerMesh + vi]));
11461167
}
1168+
// BuildFrameList later maps them to MMO keys mbp/mcc.
11471169
return new PsbDictionary {{"bp", bp}, {"cc", PsbNull.Null}};
11481170
}
11491171

@@ -1442,6 +1464,7 @@ private void BuildFrameList(PsbList frameList, MmoItemClass classType, out MmoFr
14421464

14431465
if (content.ContainsKey("mesh")) //25
14441466
{
1467+
// mbp/mcc are MMO-side mesh keys corresponding to PSB mesh.bp / mesh.cc.
14451468
content["mbp"] = content["mesh"].Children("bp");
14461469
content["mcc"] = content["mesh"].Children("cc");
14471470
content.Remove("mesh"); //PSB v4 field, not needed in MMO
@@ -2203,7 +2226,7 @@ private static void FillDefaultsIntoChildren(PsbDictionary dic, MmoItemClass cla
22032226

22042227
/// <summary>
22052228
/// Default identity 4×4 bezier patch control points (32 doubles).
2206-
/// Layout: (x,y) pairs, row by row. x ∈ {-0.1, 7/30, 17/30, 0.9}, y ∈ {0, 1/3, 2/3, 1}
2229+
/// Layout: (x,y) pairs, row by row. x ∈ {0, 1/3, 2/3, 1}, y ∈ {0, 1/3, 2/3, 1}
22072230
/// </summary>
22082231
private static readonly double[] BezierPatchDefault = GenerateBezierPatchDefault();
22092232

@@ -2214,7 +2237,7 @@ private static double[] GenerateBezierPatchDefault()
22142237
{
22152238
for (int col = 0; col < 4; col++)
22162239
{
2217-
values[(row * 4 + col) * 2] = -0.1 + col / 3.0;
2240+
values[(row * 4 + col) * 2] = col / 3.0;
22182241
values[(row * 4 + col) * 2 + 1] = row / 3.0;
22192242
}
22202243
}
@@ -2256,6 +2279,103 @@ private static IPsbValue FillDefaultTargetOwn()
22562279
};
22572280
}
22582281

2282+
/// <summary>
2283+
/// Flatten a layer tree into a pre-order list (same order as EMT editor's flattenLayerList).
2284+
/// </summary>
2285+
private static List<PsbDictionary> FlattenLayerTree(PsbList layers)
2286+
{
2287+
var result = new List<PsbDictionary>();
2288+
foreach (var item in layers)
2289+
{
2290+
if (item is PsbDictionary dic)
2291+
{
2292+
result.Add(dic);
2293+
if (dic["children"] is PsbList children)
2294+
{
2295+
result.AddRange(FlattenLayerTree(children));
2296+
}
2297+
}
2298+
}
2299+
return result;
2300+
}
2301+
2302+
/// <summary>
2303+
/// After mc=1 layers are inserted by RestoreMeshCombinator, the flat layer order changes
2304+
/// but priorityFrameList still contains indices from the old (pre-insertion) order.
2305+
/// This method remaps those indices and adds new layers to the priority list.
2306+
/// mc = meshCombine helper flag; ci = combinator index during restoration.
2307+
/// </summary>
2308+
private static void RemapPriorityFrameList(PsbList priorityFrameList, List<PsbDictionary> oldFlatList, List<PsbDictionary> newFlatList)
2309+
{
2310+
if (priorityFrameList == null || priorityFrameList.Count == 0)
2311+
return;
2312+
2313+
// Build lookup: layer reference → new index
2314+
var newIndexMap = new Dictionary<PsbDictionary, int>(newFlatList.Count);
2315+
for (int i = 0; i < newFlatList.Count; i++)
2316+
{
2317+
newIndexMap[newFlatList[i]] = i;
2318+
}
2319+
2320+
foreach (var frame in priorityFrameList)
2321+
{
2322+
if (!(frame is PsbDictionary fd) || !(fd["content"] is PsbList content))
2323+
continue;
2324+
2325+
var remappedIndices = new List<int>(newFlatList.Count);
2326+
var usedNewIndices = new HashSet<int>();
2327+
2328+
// Remap existing entries: old index → same layer object → new index
2329+
foreach (var idx in content)
2330+
{
2331+
if (idx is PsbNumber num && num.IntValue >= 0 && num.IntValue < oldFlatList.Count)
2332+
{
2333+
var layer = oldFlatList[num.IntValue];
2334+
if (newIndexMap.TryGetValue(layer, out int newIdx))
2335+
{
2336+
remappedIndices.Add(newIdx);
2337+
usedNewIndices.Add(newIdx);
2338+
}
2339+
}
2340+
}
2341+
2342+
// Insert new layers (mc=1 etc.) that weren't in old list.
2343+
// Place each at its natural position relative to its first mapped neighbour.
2344+
for (int ni = 0; ni < newFlatList.Count; ni++)
2345+
{
2346+
if (usedNewIndices.Contains(ni))
2347+
continue;
2348+
2349+
// Find the first already-mapped index that comes after this new layer
2350+
// in the flat list, and insert the new layer just before it in the priority.
2351+
int insertPos = -1;
2352+
for (int search = ni + 1; search < newFlatList.Count; search++)
2353+
{
2354+
if (usedNewIndices.Contains(search))
2355+
{
2356+
insertPos = remappedIndices.IndexOf(search);
2357+
break;
2358+
}
2359+
}
2360+
2361+
if (insertPos >= 0)
2362+
remappedIndices.Insert(insertPos, ni);
2363+
else
2364+
remappedIndices.Add(ni);
2365+
2366+
usedNewIndices.Add(ni);
2367+
}
2368+
2369+
// Replace content with remapped indices
2370+
var newContent = new PsbList(remappedIndices.Count);
2371+
foreach (var ri in remappedIndices)
2372+
{
2373+
newContent.Add(ri.ToPsbNumber());
2374+
}
2375+
fd["content"] = newContent;
2376+
}
2377+
}
2378+
22592379
#endregion
22602380
}
22612381
}

0 commit comments

Comments
 (0)