Skip to content

Commit

Permalink
Merge pull request #1334 from anatawa12/merge-material-slot
Browse files Browse the repository at this point in the history
feat: merge material slot in same mesh
  • Loading branch information
anatawa12 authored Nov 7, 2024
2 parents 80af0a7 + 9740f43 commit 9b162f8
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 32 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG-PRERELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ The format is based on [Keep a Changelog].

## [Unreleased]
### Added
- Automatically Merge Material Slot `#1334`
- If you have multile material slots with same material, it will be merged automatically.

### Changed

Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ The format is based on [Keep a Changelog].
- Improved OSC Gimmick Support `#1306`
- We added two information for OSC Gimmick in Asset Description.
- By defining parameters read / written by OSC Gimmick, your OSC Gimmick no longer breaks.
- Automatically Merge Material Slot `#1334`
- If you have multile material slots with same material, it will be merged automatically.

### Changed
- Skip Enablement Mismatched Renderers is now disabled by default `#1169`
Expand Down
57 changes: 40 additions & 17 deletions Editor/Processors/TraceAndOptimize/AutoMergeSkinnedMesh.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,22 @@ protected override void Execute(BuildContext context, TraceAndOptimizeState stat
var mergeMeshes = FilterMergeMeshes(context, state);
if (mergeMeshes.Count == 0) return;

var categorizedMeshes = CategoryMeshesForMerge(context, mergeMeshes);
if (categorizedMeshes.Count == 0) return;
CategoryMeshesForMerge(context, mergeMeshes, out var categorizedMeshes, out var orphanMeshes);

Func<MeshInfo2[], (int[][], List<(MeshTopology, Material?)>)> createSubMeshes;

MergeMeshes(context, state, categorizedMeshes);
// createSubMeshes must preserve first material to be the first material
if (state.SkipMergeMaterials)
createSubMeshes = CreateSubMeshesNoMerge;
else if (state.AllowShuffleMaterialSlots)
createSubMeshes = CreateSubMeshesMergeShuffling;
else
createSubMeshes = CreateSubMeshesMergePreserveOrder;

foreach (var orphanMesh in orphanMeshes)
MergeMaterialSlot(orphanMesh, createSubMeshes);

MergeMeshes(context, state, categorizedMeshes, createSubMeshes);
}

public static List<MeshInfo2> FilterMergeMeshes(BuildContext context, TraceAndOptimizeState state)
Expand Down Expand Up @@ -130,10 +142,11 @@ public static List<MeshInfo2> FilterMergeMeshes(BuildContext context, TraceAndOp
return mergeMeshes;
}

public static Dictionary<CategorizationKey, List<MeshInfo2>> CategoryMeshesForMerge(BuildContext context, List<MeshInfo2> mergeMeshes)
public static void CategoryMeshesForMerge(BuildContext context, List<MeshInfo2> mergeMeshes,
out Dictionary<CategorizationKey, List<MeshInfo2>> categorizedMeshes, out List<MeshInfo2> orphanMeshes)
{
// then, group by mesh attributes
var categorizedMeshes = new Dictionary<CategorizationKey, List<MeshInfo2>>();
categorizedMeshes = new Dictionary<CategorizationKey, List<MeshInfo2>>();
foreach (var meshInfo2 in mergeMeshes)
{
var activenessInfo = GetActivenessInformation(context, meshInfo2.SourceRenderer);
Expand All @@ -157,31 +170,41 @@ public static Dictionary<CategorizationKey, List<MeshInfo2>> CategoryMeshesForMe
list.Add(meshInfo2);
}

orphanMeshes = new List<MeshInfo2>();

// remove single mesh group
foreach (var (key, list) in categorizedMeshes.ToArray())
{
if (list.Count == 1)
{
categorizedMeshes.Remove(key);
orphanMeshes.Add(list[0]);
}
}

Profiler.EndSample();
}

private void MergeMaterialSlot(MeshInfo2 orphanMesh,
Func<MeshInfo2[], (int[][], List<(MeshTopology, Material?)>)> createSubMeshes)
{
var (mapping, subMeshInfos) = createSubMeshes(new[] { orphanMesh });
var subMeshes = orphanMesh.SubMeshes.ToList();

orphanMesh.SubMeshes.Clear();
foreach (var (meshTopology, material) in subMeshInfos)
orphanMesh.SubMeshes.Add(new SubMesh(material, meshTopology));

return categorizedMeshes;
for (var i = 0; i < subMeshes.Count; i++)
orphanMesh.SubMeshes[mapping[0][i]].Vertices.AddRange(subMeshes[i].Vertices);
}

public static void MergeMeshes(BuildContext context, TraceAndOptimizeState state,
Dictionary<CategorizationKey, List<MeshInfo2>> categorizedMeshes)
Dictionary<CategorizationKey, List<MeshInfo2>> categorizedMeshes,
Func<MeshInfo2[], (int[][], List<(MeshTopology, Material?)>)> createSubMeshes)
{
Profiler.BeginSample("Merge Meshes");

Func<MeshInfo2[], (int[][], List<(MeshTopology, Material?)>)> createSubMeshes;

// createSubMeshes must preserve first material to be the first material
if (state.SkipMergeMaterials)
createSubMeshes = CreateSubMeshesNoMerge;
else if (state.AllowShuffleMaterialSlots)
createSubMeshes = CreateSubMeshesMergeShuffling;
else
createSubMeshes = CreateSubMeshesMergePreserveOrder;

var index = 0;
Func<GameObject> gameObjectFactory = () => new GameObject($"$$AAO_AUTO_MERGE_SKINNED_MESH_{index++}");

Expand Down
30 changes: 15 additions & 15 deletions Test~/Basic/AutoMergeSkinnedMeshTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,11 @@ public void CategorizeMeshesForMerge_Split_DifferentActivenessAnimation()
var buildContext = PreprocessAvatar(avatar);

// do process
var categorization = AutoMergeSkinnedMesh.CategoryMeshesForMerge(buildContext, new List<MeshInfo2>()
AutoMergeSkinnedMesh.CategoryMeshesForMerge(buildContext, new List<MeshInfo2>()
{
buildContext.GetMeshInfoFor(renderer0), buildContext.GetMeshInfoFor(renderer1),
buildContext.GetMeshInfoFor(renderer2), buildContext.GetMeshInfoFor(renderer3),
});
}, out var categorization, out _);

Assert.That(categorization.Values, Is.EquivalentTo(new[]
{
Expand Down Expand Up @@ -186,10 +186,10 @@ public void CategorizeMeshesForMerge_Merge_AlwaysAppliedAnimationWithSameInitial
Is.EqualTo(ApplyState.Always));

// do process
var categorization = AutoMergeSkinnedMesh.CategoryMeshesForMerge(buildContext, new List<MeshInfo2>()
AutoMergeSkinnedMesh.CategoryMeshesForMerge(buildContext, new List<MeshInfo2>()
{
buildContext.GetMeshInfoFor(avatar.renderer0), buildContext.GetMeshInfoFor(avatar.renderer1),
});
}, out var categorization, out _);

Assert.That(categorization.Values, Is.EquivalentTo(new[]
{
Expand Down Expand Up @@ -227,10 +227,10 @@ public void CategorizeMeshesForMerge_Merge_AlwaysAppliedAnimationWithDifferentIn
Is.EqualTo(ApplyState.Always));

// do process
var categorization = AutoMergeSkinnedMesh.CategoryMeshesForMerge(buildContext, new List<MeshInfo2>()
AutoMergeSkinnedMesh.CategoryMeshesForMerge(buildContext, new List<MeshInfo2>()
{
buildContext.GetMeshInfoFor(avatar.renderer0), buildContext.GetMeshInfoFor(avatar.renderer1),
});
}, out var categorization, out _);

Assert.That(categorization.Values, Is.EquivalentTo(new[]
{
Expand Down Expand Up @@ -267,10 +267,10 @@ public void CategorizeMeshesForMerge_Merge_PartiallyAppliedAnimationWithSameInit
Is.EqualTo(ApplyState.Partially));

// do process
var categorization = AutoMergeSkinnedMesh.CategoryMeshesForMerge(buildContext, new List<MeshInfo2>()
AutoMergeSkinnedMesh.CategoryMeshesForMerge(buildContext, new List<MeshInfo2>()
{
buildContext.GetMeshInfoFor(avatar.renderer0), buildContext.GetMeshInfoFor(avatar.renderer1),
});
}, out var categorization, out _);

Assert.That(categorization.Values, Is.EquivalentTo(new[]
{
Expand Down Expand Up @@ -307,10 +307,10 @@ public void CategorizeMeshesForMerge_Split_PartiallyAppliedAnimationWithDifferen
Is.EqualTo(ApplyState.Partially));

// do process
var categorization = AutoMergeSkinnedMesh.CategoryMeshesForMerge(buildContext, new List<MeshInfo2>()
AutoMergeSkinnedMesh.CategoryMeshesForMerge(buildContext, new List<MeshInfo2>()
{
buildContext.GetMeshInfoFor(avatar.renderer0), buildContext.GetMeshInfoFor(avatar.renderer1),
});
}, out var categorization, out _);

Assert.That(categorization.Values, Is.EquivalentTo(Enumerable.Empty<List<MeshInfo2>>()));
}
Expand Down Expand Up @@ -344,10 +344,10 @@ public void CategorizeMeshesForMerge_Merge_NeverAppliedAnimationWithSameInitialV
Is.EqualTo(ApplyState.Never));

// do process
var categorization = AutoMergeSkinnedMesh.CategoryMeshesForMerge(buildContext, new List<MeshInfo2>()
AutoMergeSkinnedMesh.CategoryMeshesForMerge(buildContext, new List<MeshInfo2>()
{
buildContext.GetMeshInfoFor(avatar.renderer0), buildContext.GetMeshInfoFor(avatar.renderer1),
});
}, out var categorization, out _);

Assert.That(categorization.Values, Is.EquivalentTo(new[]
{
Expand Down Expand Up @@ -386,10 +386,10 @@ public void CategorizeMeshesForMerge_Split_NeverAppliedAnimationWithDifferentIni
Is.EqualTo(ApplyState.Never));

// do process
var categorization = AutoMergeSkinnedMesh.CategoryMeshesForMerge(buildContext, new List<MeshInfo2>()
AutoMergeSkinnedMesh.CategoryMeshesForMerge(buildContext, new List<MeshInfo2>()
{
buildContext.GetMeshInfoFor(avatar.renderer0), buildContext.GetMeshInfoFor(avatar.renderer1),
});
}, out var categorization, out _);

Assert.That(categorization.Values, Is.EquivalentTo(Enumerable.Empty<List<MeshInfo2>>()));
}
Expand All @@ -416,7 +416,7 @@ public void Issue1252NoCrashWithObjectReferenceCurve()
AutoMergeSkinnedMesh.CategoryMeshesForMerge(buildContext, new List<MeshInfo2>()
{
buildContext.GetMeshInfoFor(avatar.renderer0), buildContext.GetMeshInfoFor(avatar.renderer1),
});
}, out _, out _);

// No crash
}
Expand Down

0 comments on commit 9b162f8

Please sign in to comment.