diff --git a/CHANGELOG-PRERELEASE.md b/CHANGELOG-PRERELEASE.md index 23f72e8c8..0d874153f 100644 --- a/CHANGELOG-PRERELEASE.md +++ b/CHANGELOG-PRERELEASE.md @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog]. ### Removed ### Fixed +- Error with nested merge skinned mesh `#1340` ### Security diff --git a/Editor/Processors/SkinnedMeshes/MergeSkinnedMeshProcessor.cs b/Editor/Processors/SkinnedMeshes/MergeSkinnedMeshProcessor.cs index 8c4e28a52..9e889480d 100644 --- a/Editor/Processors/SkinnedMeshes/MergeSkinnedMeshProcessor.cs +++ b/Editor/Processors/SkinnedMeshes/MergeSkinnedMeshProcessor.cs @@ -367,8 +367,8 @@ TexCoordStatus TexCoordStatusMax(TexCoordStatus x, TexCoordStatus y) => foreach (var buffer in buffers) { - buffer.Shapes[newName] = buffer.Shapes[name]; - buffer.Shapes.Remove(name); + if (buffer.Shapes.Remove(name, out var shape)) + buffer.Shapes[newName] = shape; } mappings.Add(($"blendShape.{name}", $"blendShape.{newName}")); diff --git a/Runtime/assembly-info.cs b/Runtime/assembly-info.cs index d9f366f32..015dce03a 100644 --- a/Runtime/assembly-info.cs +++ b/Runtime/assembly-info.cs @@ -3,3 +3,4 @@ [assembly:InternalsVisibleTo("com.anatawa12.avatar-optimizer.editor")] [assembly:InternalsVisibleTo("com.anatawa12.avatar-optimizer.internal.trace-and-optimize-base")] [assembly:InternalsVisibleTo("com.anatawa12.avatar-optimizer.test.basic")] +[assembly:InternalsVisibleTo("com.anatawa12.avatar-optimizer.test.utils")] diff --git a/Test~/Basic/MergeSkinnedMeshTest.cs b/Test~/Basic/MergeSkinnedMeshTest.cs index a3bfc182c..8f77b8dde 100644 --- a/Test~/Basic/MergeSkinnedMeshTest.cs +++ b/Test~/Basic/MergeSkinnedMeshTest.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Anatawa12.AvatarOptimizer.AnimatorParsersV2; using Anatawa12.AvatarOptimizer.ndmf; using Anatawa12.AvatarOptimizer.Processors.SkinnedMeshes; @@ -243,4 +244,55 @@ public void CopySourceAnimationErrorActivenessAnimationOfSourceMismatch() new MergeSkinnedMeshProcessor(merged).Process(context, context.GetMeshInfoFor(mergedRenderer)); }); } + + [Test] + public void NestedMergeSkinnedMeshWithPartialBlendShapes() + { + var mesh1 = TestUtils.NewCubeMesh(); + mesh1.AddBlendShapeFrame("mesh1", 100, TestUtils.NewCubeBlendShapeFrame((0, Vector3.up)), null, null); + var mesh2 = TestUtils.NewCubeMesh(); + mesh2.AddBlendShapeFrame("mesh2", 100, TestUtils.NewCubeBlendShapeFrame((0, Vector3.right)), null, null); + var mesh3 = TestUtils.NewCubeMesh(); + mesh3.AddBlendShapeFrame("mesh3", 100, TestUtils.NewCubeBlendShapeFrame((0, Vector3.forward)), null, null); + + var avatar = TestUtils.NewAvatar(); + var renderer1GameObject = Utils.NewGameObject("Renderer1", avatar.transform); + var renderer1 = renderer1GameObject.AddComponent(); + renderer1.sharedMesh = mesh1; + var renderer2GameObject = Utils.NewGameObject("Renderer2", avatar.transform); + var renderer2 = renderer2GameObject.AddComponent(); + renderer2.sharedMesh = mesh2; + var renderer3GameObject = Utils.NewGameObject("Renderer3", avatar.transform); + var renderer3 = renderer3GameObject.AddComponent(); + renderer3.sharedMesh = mesh3; + var mergedIntermediateGameObject = Utils.NewGameObject("MergedIntermediate", avatar.transform); + var mergedIntermediateRenderer = mergedIntermediateGameObject.AddComponent(); + var mergedFinalGameObject = Utils.NewGameObject("MergedFinal", avatar.transform); + var mergedFinalRenderer = mergedFinalGameObject.AddComponent(); + + var context = new BuildContext(avatar, null); + context.ActivateExtensionContext(); + context.ActivateExtensionContext(); + context.ActivateExtensionContext(); + + var meshInfo1 = context.GetMeshInfoFor(renderer1); + var meshInfo2 = context.GetMeshInfoFor(renderer2); + var meshInfo3 = context.GetMeshInfoFor(renderer3); + var meshInfoIntermediate = context.GetMeshInfoFor(mergedIntermediateRenderer); + var meshInfoFinal = context.GetMeshInfoFor(mergedFinalRenderer); + + MergeSkinnedMeshProcessor.DoMerge(context, + meshInfoIntermediate, + new[] { meshInfo1, meshInfo2 }, + new[] { new[] { 0 }, new[] { 0 } }, + new List<(MeshTopology, Material)>() { (MeshTopology.Triangles, null) } + ); + + MergeSkinnedMeshProcessor.DoMerge(context, + meshInfoFinal, + new[] { meshInfoIntermediate, meshInfo3 }, + new[] { new[] { 0 }, new[] { 0 } }, + new List<(MeshTopology, Material)>() { (MeshTopology.Triangles, null) } + ); + } } diff --git a/Test~/Utils/TestUtils.cs b/Test~/Utils/TestUtils.cs index f0a9aea1b..102a080b4 100644 --- a/Test~/Utils/TestUtils.cs +++ b/Test~/Utils/TestUtils.cs @@ -11,6 +11,8 @@ namespace Anatawa12.AvatarOptimizer.Test { public static class TestUtils { + class DummyAvatarTagComponent : AvatarTagComponent {} + public static GameObject NewAvatar(string name = null) { var root = new GameObject(); @@ -20,6 +22,8 @@ public static GameObject NewAvatar(string name = null) #if AAO_VRCSDK3_AVATARS var descriptor = root.AddComponent(); #endif + // for any AvatarTagComponent checks on the avatar + root.AddComponent(); return root; } @@ -126,6 +130,13 @@ public static Mesh NewCubeMesh() mesh.SetSubMesh(0, new SubMeshDescriptor(0, 12 * 3)); return mesh; } + + public static Vector3[] NewCubeBlendShapeFrame(params (int index, Vector3 delta)[] deltas) + { + var frame = new Vector3[8]; + foreach (var (index, delta) in deltas) frame[index] = delta; + return frame; + } public static SkinnedMeshRenderer NewSkinnedMeshRenderer(Mesh mesh) {