From 603a688c4243ef7a84dca71324a80efb55dca6b4 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 26 Oct 2023 14:29:59 +0900 Subject: [PATCH 01/61] test: add test for extreme small scaling --- Editor/Processors/MergeBoneProcessor.cs | 2 +- Test~/MergeBoneTest.cs | 34 +++++++++++++++++++++++++ Test~/MergeBoneTest.cs.meta | 3 +++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 Test~/MergeBoneTest.cs create mode 100644 Test~/MergeBoneTest.cs.meta diff --git a/Editor/Processors/MergeBoneProcessor.cs b/Editor/Processors/MergeBoneProcessor.cs index f4b8b22b3..cef25a221 100644 --- a/Editor/Processors/MergeBoneProcessor.cs +++ b/Editor/Processors/MergeBoneProcessor.cs @@ -264,7 +264,7 @@ public override int GetHashCode() => } - struct MergeBoneTransParentInfo + public struct MergeBoneTransParentInfo { public Quaternion ParentRotation; public Matrix4x4 ParentMatrix; diff --git a/Test~/MergeBoneTest.cs b/Test~/MergeBoneTest.cs new file mode 100644 index 000000000..907538b33 --- /dev/null +++ b/Test~/MergeBoneTest.cs @@ -0,0 +1,34 @@ +using Anatawa12.AvatarOptimizer.Processors; +using NUnit.Framework; +using UnityEngine; + +namespace Anatawa12.AvatarOptimizer.Test +{ + public class MergeBoneTest + { + [Test] + public void ExtremeSmall() + { + var epsilonVector3 = new Vector3(float.Epsilon, float.Epsilon, float.Epsilon); + var root = TestUtils.NewAvatar(); + var merged = Utils.NewGameObject("merged", root.transform); + merged.transform.localScale = epsilonVector3; + + var identity = Utils.NewGameObject("identity", merged.transform); + var moved = Utils.NewGameObject("moved", merged.transform); + moved.transform.localPosition = Vector3.one; + + var transInfo = MergeBoneProcessor.MergeBoneTransParentInfo.Compute(merged.transform, root.transform); + + var identityAfter = transInfo.ComputeInfoFor(identity.transform); + Assert.That(identityAfter.scale, Is.EqualTo(epsilonVector3)); + Assert.That(identityAfter.position, Is.EqualTo(Vector3.zero)); + Assert.That(identityAfter.rotation, Is.EqualTo(Quaternion.identity)); + + var movedAfter = transInfo.ComputeInfoFor(moved.transform); + Assert.That(movedAfter.scale, Is.EqualTo(epsilonVector3)); + Assert.That(movedAfter.position, Is.EqualTo(epsilonVector3)); + Assert.That(movedAfter.rotation, Is.EqualTo(Quaternion.identity)); + } + } +} \ No newline at end of file diff --git a/Test~/MergeBoneTest.cs.meta b/Test~/MergeBoneTest.cs.meta new file mode 100644 index 000000000..b6b4b7191 --- /dev/null +++ b/Test~/MergeBoneTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d513e5d706854597b6990b5ad7dfc559 +timeCreated: 1698296645 \ No newline at end of file From bc78806069618f97a823774a282bd82c95d97e35 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 26 Oct 2023 14:49:59 +0900 Subject: [PATCH 02/61] test: add test for AvatarMask mapping --- Editor/ObjectMapping/ObjectMappingContext.cs | 1 + Test~/ApplyObjectMappingTest.cs | 45 ++++++++++++++++++++ Test~/ApplyObjectMappingTest.cs.meta | 3 ++ 3 files changed, 49 insertions(+) create mode 100644 Test~/ApplyObjectMappingTest.cs create mode 100644 Test~/ApplyObjectMappingTest.cs.meta diff --git a/Editor/ObjectMapping/ObjectMappingContext.cs b/Editor/ObjectMapping/ObjectMappingContext.cs index 22dba13e1..f77f4ada8 100644 --- a/Editor/ObjectMapping/ObjectMappingContext.cs +++ b/Editor/ObjectMapping/ObjectMappingContext.cs @@ -195,6 +195,7 @@ private Object CustomClone(Object o) newMask.SetTransformActive(dstI, mask.GetTransformActive(srcI)); dstI++; } + if (path != newPath) _mapped = true; } newMask.transformCount = dstI; diff --git a/Test~/ApplyObjectMappingTest.cs b/Test~/ApplyObjectMappingTest.cs new file mode 100644 index 000000000..0ab5bf2c8 --- /dev/null +++ b/Test~/ApplyObjectMappingTest.cs @@ -0,0 +1,45 @@ +using NUnit.Framework; +using UnityEditor.Animations; +using UnityEngine; + +namespace Anatawa12.AvatarOptimizer.Test +{ + public class ApplyObjectMappingTest + { + [Test] + public void AvatarMask() + { + var root = new GameObject(); + var child1 = Utils.NewGameObject("child1", root.transform); + var child11 = Utils.NewGameObject("child11", child1.transform); + var builder = new ObjectMappingBuilder(root); + + child11.name = "child12"; + + var built = builder.BuildObjectMapping(); + + var rootMapper = new AnimatorControllerMapper(built.CreateAnimationMapper(root)); + + var avatarMask = new AvatarMask(); + avatarMask.transformCount = 1; + avatarMask.SetHumanoidBodyPartActive(AvatarMaskBodyPart.Head, true); + avatarMask.SetHumanoidBodyPartActive(AvatarMaskBodyPart.LeftLeg, false); + avatarMask.SetTransformPath(0, "child1/child11"); + + var animatorController = new AnimatorController(); + animatorController.AddLayer(new AnimatorControllerLayer() + { + name = "layer", + avatarMask = avatarMask, + stateMachine = new AnimatorStateMachine() { name = "layer" }, + }); + + var mappedController = rootMapper.MapAnimatorController(animatorController); + Assert.That(mappedController, Is.Not.EqualTo(animatorController)); + Assert.That(mappedController.layers[0].avatarMask.GetTransformPath(0), + Is.EqualTo("child1/child12")); + Assert.That(avatarMask.GetHumanoidBodyPartActive(AvatarMaskBodyPart.Head), Is.True); + Assert.That(avatarMask.GetHumanoidBodyPartActive(AvatarMaskBodyPart.LeftLeg), Is.False); + } + } +} \ No newline at end of file diff --git a/Test~/ApplyObjectMappingTest.cs.meta b/Test~/ApplyObjectMappingTest.cs.meta new file mode 100644 index 000000000..5c0a23352 --- /dev/null +++ b/Test~/ApplyObjectMappingTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 17ff08d02c7e45adac0adee86f7137d6 +timeCreated: 1698298544 \ No newline at end of file From 1e3f34925f7d3c710019dbb7625ce9b5b80afef8 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 30 Oct 2023 13:22:58 +0900 Subject: [PATCH 03/61] feat: add RemoveZeroSizedPolygon --- Editor/APIInternal/ComponentInfos.cs | 13 ++++ Editor/OptimizerPlugin.cs | 4 +- .../RemoveZeroSizedPolygonProcessor.cs | 63 +++++++++++++++++++ .../RemoveZeroSizedPolygonProcessor.cs.meta | 3 + Localization/en.po | 7 +++ Localization/ja.po | 7 +++ Runtime/RemoveZeroSizedPolygon.cs | 12 ++++ Runtime/RemoveZeroSizedPolygon.cs.meta | 3 + 8 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 Editor/Processors/RemoveZeroSizedPolygonProcessor.cs create mode 100644 Editor/Processors/RemoveZeroSizedPolygonProcessor.cs.meta create mode 100644 Runtime/RemoveZeroSizedPolygon.cs create mode 100644 Runtime/RemoveZeroSizedPolygon.cs.meta diff --git a/Editor/APIInternal/ComponentInfos.cs b/Editor/APIInternal/ComponentInfos.cs index 50437df22..c97477df7 100644 --- a/Editor/APIInternal/ComponentInfos.cs +++ b/Editor/APIInternal/ComponentInfos.cs @@ -404,6 +404,19 @@ void DeriveMergeSkinnedMeshProperties(MergeSkinnedMesh mergeSkinnedMesh) } } + [ComponentInformation(typeof(RemoveZeroSizedPolygon))] + internal class RemoveZeroSizedPolygonInformation : ComponentInformation + { + protected override void CollectDependency(RemoveZeroSizedPolygon component, ComponentDependencyCollector collector) + { + collector.AddDependency(component.GetComponent(), component); + } + + protected override void CollectMutations(RemoveZeroSizedPolygon component, ComponentMutationsCollector collector) + { + } + } + [ComponentInformation(typeof(MergeBone))] internal class MergeBoneInformation : ComponentInformation { diff --git a/Editor/OptimizerPlugin.cs b/Editor/OptimizerPlugin.cs index 8df3fdfb8..a375da597 100644 --- a/Editor/OptimizerPlugin.cs +++ b/Editor/OptimizerPlugin.cs @@ -58,7 +58,9 @@ protected override void Configure() ctx => new Processors.MakeChildrenProcessor(early: false).Process(ctx) ) .Then.Run(Processors.TraceAndOptimizes.FindUnusedObjects.Instance) - .Then.Run(Processors.MergeBoneProcessor.Instance); + .Then.Run(Processors.MergeBoneProcessor.Instance) + .Then.Run(Processors.RemoveZeroSizedPolygonProcessor.Instance) + ; }); seq.Run("EmptyPass for Context Ordering", _ => {}); }); diff --git a/Editor/Processors/RemoveZeroSizedPolygonProcessor.cs b/Editor/Processors/RemoveZeroSizedPolygonProcessor.cs new file mode 100644 index 000000000..c044a2667 --- /dev/null +++ b/Editor/Processors/RemoveZeroSizedPolygonProcessor.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using Anatawa12.AvatarOptimizer.Processors.SkinnedMeshes; +using nadena.dev.ndmf; +using UnityEngine; + +namespace Anatawa12.AvatarOptimizer.Processors +{ + public class RemoveZeroSizedPolygonProcessor : Pass + { + public override string DisplayName => "RemoveZeroSizedPolygonProcessor"; + + protected override void Execute(BuildContext context) + { + foreach (var removeZeroSizedPolygon in context.GetComponents()) + { + var mesh = removeZeroSizedPolygon.GetComponent(); + if (!mesh) continue; + Process(context.GetMeshInfoFor(mesh), removeZeroSizedPolygon); + Object.DestroyImmediate(removeZeroSizedPolygon); + } + } + + private static void Process(MeshInfo2 meshInfo2, RemoveZeroSizedPolygon _) + { + foreach (var subMesh in meshInfo2.SubMeshes) + { + var dstI = 0; + for (var srcI = 0; srcI < subMesh.Triangles.Count; srcI += 3) + { + if (!IsPolygonEmpty(subMesh.Triangles[srcI], subMesh.Triangles[srcI + 1], subMesh.Triangles[srcI + 2])) + { + subMesh.Triangles[dstI] = subMesh.Triangles[srcI]; + subMesh.Triangles[dstI + 1] = subMesh.Triangles[srcI + 1]; + subMesh.Triangles[dstI + 2] = subMesh.Triangles[srcI + 2]; + dstI += 3; + } + } + + subMesh.Triangles.RemoveRange(dstI, subMesh.Triangles.Count - dstI); + } + } + + private static bool IsPolygonEmpty(Vertex a, Vertex b, Vertex c) + { + // BlendShapes are hard to check so disallow it + // TODO: check BlendShape delta is same + if (a.BlendShapes.Count != 0) return false; + if (b.BlendShapes.Count != 0) return false; + if (c.BlendShapes.Count != 0) return false; + + // check three points are at same position + // TODO: should we use cross product instead? + if (a.Position != b.Position) return false; + if (a.Position != c.Position) return false; + + // check bone and bone weights are same + var aWeights = new HashSet<(Bone bone, float weight)>(a.BoneWeights); + if (!aWeights.SetEquals(b.BoneWeights)) return false; + if (!aWeights.SetEquals(c.BoneWeights)) return false; + return true; + } + } +} \ No newline at end of file diff --git a/Editor/Processors/RemoveZeroSizedPolygonProcessor.cs.meta b/Editor/Processors/RemoveZeroSizedPolygonProcessor.cs.meta new file mode 100644 index 000000000..88d6e7560 --- /dev/null +++ b/Editor/Processors/RemoveZeroSizedPolygonProcessor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 37bd4776160b4b74a31637d91faf35e3 +timeCreated: 1698635819 \ No newline at end of file diff --git a/Localization/en.po b/Localization/en.po index 881394c91..778faeeee 100644 --- a/Localization/en.po +++ b/Localization/en.po @@ -347,6 +347,13 @@ msgstr "Invert All" # endregion +# region RemoveZeroSizedPolygon + +msgid "RemoveZeroSizedPolygon:description" +msgstr "Removes polygons whose area is zero" + +# endregion + # region AvatarGlobalComponent msgid "DeleteEditorOnlyGameObjects:NotOnAvatarDescriptor" diff --git a/Localization/ja.po b/Localization/ja.po index 928e82bdf..98182ce18 100644 --- a/Localization/ja.po +++ b/Localization/ja.po @@ -284,6 +284,13 @@ msgstr "すべての有効/無効を入れ替える" # endregion +# region RemoveZeroSizedPolygon + +msgid "RemoveZeroSizedPolygon:description" +msgstr "面積が零なポリゴンを削除します" + +# endregion + # region AvatarGlobalComponent msgid "DeleteEditorOnlyGameObjects:NotOnAvatarDescriptor" diff --git a/Runtime/RemoveZeroSizedPolygon.cs b/Runtime/RemoveZeroSizedPolygon.cs new file mode 100644 index 000000000..11bd888c6 --- /dev/null +++ b/Runtime/RemoveZeroSizedPolygon.cs @@ -0,0 +1,12 @@ +using UnityEngine; + +namespace Anatawa12.AvatarOptimizer +{ + [AddComponentMenu("Avatar Optimizer/AAO Remove Zero Sized Polygon")] + [DisallowMultipleComponent] + [RequireComponent(typeof(SkinnedMeshRenderer))] + [HelpURL("https://vpm.anatawa12.com/avatar-optimizer/ja/docs/reference/remove-zero-sized-polygon/")] + internal class RemoveZeroSizedPolygon : AvatarTagComponent + { + } +} diff --git a/Runtime/RemoveZeroSizedPolygon.cs.meta b/Runtime/RemoveZeroSizedPolygon.cs.meta new file mode 100644 index 000000000..35d63a690 --- /dev/null +++ b/Runtime/RemoveZeroSizedPolygon.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b87714a042f7485184e185edd88fec6c +timeCreated: 1698635337 \ No newline at end of file From 1ea76b6972d857a5ba5bc4096d5c41d61e42fee6 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 30 Oct 2023 13:47:36 +0900 Subject: [PATCH 04/61] fix: destroying SkinnedMeshRenderer fail due to RemoveZeroSizedPolygon (add warn) --- .../Processors/SkinnedMeshes/MergeSkinnedMeshProcessor.cs | 7 +++++++ Localization/en.po | 3 +++ Localization/ja.po | 3 +++ 3 files changed, 13 insertions(+) diff --git a/Editor/Processors/SkinnedMeshes/MergeSkinnedMeshProcessor.cs b/Editor/Processors/SkinnedMeshes/MergeSkinnedMeshProcessor.cs index c4628c12f..842b3fcef 100644 --- a/Editor/Processors/SkinnedMeshes/MergeSkinnedMeshProcessor.cs +++ b/Editor/Processors/SkinnedMeshes/MergeSkinnedMeshProcessor.cs @@ -208,6 +208,13 @@ TexCoordStatus TexCoordStatusMax(TexCoordStatus x, TexCoordStatus y) => context.RecordRemoveProperty(renderer, "m_Enabled"); context.RecordMergeComponent(renderer, Target); var rendererGameObject = renderer.gameObject; + var toDestroy = renderer.GetComponent(); + if (toDestroy) + { + BuildReport.LogWarning("MergeSkinnedMesh:warning:removeZeroSizedPolygonOnSources") + ?.WithContext(toDestroy); + Object.DestroyImmediate(toDestroy); + } Object.DestroyImmediate(renderer); // process removeEmptyRendererObject diff --git a/Localization/en.po b/Localization/en.po index 778faeeee..4aa84c387 100644 --- a/Localization/en.po +++ b/Localization/en.po @@ -277,6 +277,9 @@ msgstr "" "Merging both meshes with and without normal is not supported." "Please change import setting of models to include normals!" +msgid "MergeSkinnedMesh:warning:removeZeroSizedPolygonOnSources" +msgstr "Since Remove Zero Sized Polygons are proceed later, it on the source Skinned Mesh Renderers have no effects." + # endregion # region MergeToonLitMaterial diff --git a/Localization/ja.po b/Localization/ja.po index 98182ce18..b3cf961bb 100644 --- a/Localization/ja.po +++ b/Localization/ja.po @@ -214,6 +214,9 @@ msgstr "" "法線があるメッシュとないメッシュの両方を1つに統合する操作には対応していません。" "法線が含まれるようにモデルのインポート設定を変更してください!" +msgid "MergeSkinnedMesh:warning:removeZeroSizedPolygonOnSources" +msgstr "Remove Zero Sized Polygonsはのちに処理されるため、ソースレンダラにつけても効果がありません" + # endregion # region MergeToonLitMaterial From aa921a56924d37a988cd9012ea2a10e14c31ff31 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 30 Oct 2023 14:32:53 +0900 Subject: [PATCH 05/61] feat: add Remove Zero Sized Polygons to TraceAndOptimize --- Editor/AutomaticConfiguration.cs | 3 +++ Editor/OptimizerPlugin.cs | 1 + .../ConfigureRemoveZeroSizedPolygon.cs | 19 +++++++++++++++++++ .../ConfigureRemoveZeroSizedPolygon.cs.meta | 3 +++ .../TraceAndOptimizeProcessor.cs | 2 ++ Localization/en.po | 3 +++ Localization/ja.po | 3 +++ Runtime/TraceAndOptimize.cs | 5 +++++ 8 files changed, 39 insertions(+) create mode 100644 Editor/Processors/TraceAndOptimize/ConfigureRemoveZeroSizedPolygon.cs create mode 100644 Editor/Processors/TraceAndOptimize/ConfigureRemoveZeroSizedPolygon.cs.meta diff --git a/Editor/AutomaticConfiguration.cs b/Editor/AutomaticConfiguration.cs index c95e69941..c4d35b5db 100644 --- a/Editor/AutomaticConfiguration.cs +++ b/Editor/AutomaticConfiguration.cs @@ -10,6 +10,7 @@ internal class TraceAndOptimizeEditor : AvatarGlobalComponentEditorBase private SerializedProperty _freezeBlendShape; private SerializedProperty _removeUnusedObjects; private SerializedProperty _preserveEndBone; + private SerializedProperty _removeZeroSizedPolygons; private SerializedProperty _mmdWorldCompatibility; private SerializedProperty _advancedSettings; private GUIContent _advancedSettingsLabel = new GUIContent(); @@ -19,6 +20,7 @@ private void OnEnable() _freezeBlendShape = serializedObject.FindProperty(nameof(TraceAndOptimize.freezeBlendShape)); _removeUnusedObjects = serializedObject.FindProperty(nameof(TraceAndOptimize.removeUnusedObjects)); _preserveEndBone = serializedObject.FindProperty(nameof(TraceAndOptimize.preserveEndBone)); + _removeZeroSizedPolygons = serializedObject.FindProperty(nameof(TraceAndOptimize.removeZeroSizedPolygons)); _mmdWorldCompatibility = serializedObject.FindProperty(nameof(TraceAndOptimize.mmdWorldCompatibility)); _advancedSettings = serializedObject.FindProperty(nameof(TraceAndOptimize.advancedSettings)); } @@ -39,6 +41,7 @@ protected override void OnInspectorGUIInner() EditorGUILayout.PropertyField(_preserveEndBone); EditorGUI.indentLevel--; } + EditorGUILayout.PropertyField(_removeZeroSizedPolygons); _advancedSettingsLabel.text = CL4EE.Tr("TraceAndOptimize:prop:advancedSettings"); if (EditorGUILayout.PropertyField(_advancedSettings, _advancedSettingsLabel, false)) diff --git a/Editor/OptimizerPlugin.cs b/Editor/OptimizerPlugin.cs index a375da597..38185242b 100644 --- a/Editor/OptimizerPlugin.cs +++ b/Editor/OptimizerPlugin.cs @@ -58,6 +58,7 @@ protected override void Configure() ctx => new Processors.MakeChildrenProcessor(early: false).Process(ctx) ) .Then.Run(Processors.TraceAndOptimizes.FindUnusedObjects.Instance) + .Then.Run(Processors.TraceAndOptimizes.ConfigureRemoveZeroSizedPolygon.Instance) .Then.Run(Processors.MergeBoneProcessor.Instance) .Then.Run(Processors.RemoveZeroSizedPolygonProcessor.Instance) ; diff --git a/Editor/Processors/TraceAndOptimize/ConfigureRemoveZeroSizedPolygon.cs b/Editor/Processors/TraceAndOptimize/ConfigureRemoveZeroSizedPolygon.cs new file mode 100644 index 000000000..ee7064810 --- /dev/null +++ b/Editor/Processors/TraceAndOptimize/ConfigureRemoveZeroSizedPolygon.cs @@ -0,0 +1,19 @@ +using nadena.dev.ndmf; +using UnityEngine; + +namespace Anatawa12.AvatarOptimizer.Processors.TraceAndOptimizes +{ + public class ConfigureRemoveZeroSizedPolygon : Pass + { + public override string DisplayName => "T&O: ConfigureRemoveZeroSizedPolygon"; + + protected override void Execute(BuildContext context) + { + var state = context.GetState(); + if (!state.RemoveZeroSizedPolygon) return; + + foreach (var renderer in context.GetComponents()) + renderer.gameObject.GetOrAddComponent(); + } + } +} \ No newline at end of file diff --git a/Editor/Processors/TraceAndOptimize/ConfigureRemoveZeroSizedPolygon.cs.meta b/Editor/Processors/TraceAndOptimize/ConfigureRemoveZeroSizedPolygon.cs.meta new file mode 100644 index 000000000..f2454e1e9 --- /dev/null +++ b/Editor/Processors/TraceAndOptimize/ConfigureRemoveZeroSizedPolygon.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: becbc671d0aa43ecaf0e48ec3cf5d7fe +timeCreated: 1698643078 \ No newline at end of file diff --git a/Editor/Processors/TraceAndOptimize/TraceAndOptimizeProcessor.cs b/Editor/Processors/TraceAndOptimize/TraceAndOptimizeProcessor.cs index e04d91d01..0d2428107 100644 --- a/Editor/Processors/TraceAndOptimize/TraceAndOptimizeProcessor.cs +++ b/Editor/Processors/TraceAndOptimize/TraceAndOptimizeProcessor.cs @@ -9,6 +9,7 @@ class TraceAndOptimizeState public bool Enabled; public bool FreezeBlendShape; public bool RemoveUnusedObjects; + public bool RemoveZeroSizedPolygon; public bool MmdWorldCompatibility; public bool PreserveEndBone; @@ -32,6 +33,7 @@ public void Initialize(TraceAndOptimize config) { FreezeBlendShape = config.freezeBlendShape; RemoveUnusedObjects = config.removeUnusedObjects; + RemoveZeroSizedPolygon = config.removeZeroSizedPolygons; MmdWorldCompatibility = config.mmdWorldCompatibility; PreserveEndBone = config.preserveEndBone; diff --git a/Localization/en.po b/Localization/en.po index 4aa84c387..caa0dcbb8 100644 --- a/Localization/en.po +++ b/Localization/en.po @@ -441,6 +441,9 @@ msgstr "Preserve EndBone" msgid "TraceAndOptimize:tooltip:preserveEndBone" msgstr "Prevents removing end bones whose parent is not removed." +msgid "TraceAndOptimize:prop:removeZeroSizedPolygons" +msgstr "Remove Polygons with Area of Zero" + # endregion #region ApplyObjectMapping diff --git a/Localization/ja.po b/Localization/ja.po index b3cf961bb..457d6881a 100644 --- a/Localization/ja.po +++ b/Localization/ja.po @@ -377,6 +377,9 @@ msgstr "endボーンを残す" msgid "TraceAndOptimize:tooltip:preserveEndBone" msgstr "親が削除されていないendボーンを削除しないようにします。" +msgid "TraceAndOptimize:prop:removeZeroSizedPolygons" +msgstr "大きさのないポリゴンを削除する" + # endregion #region ApplyObjectMapping diff --git a/Runtime/TraceAndOptimize.cs b/Runtime/TraceAndOptimize.cs index 87382af52..d7ad28053 100644 --- a/Runtime/TraceAndOptimize.cs +++ b/Runtime/TraceAndOptimize.cs @@ -27,6 +27,11 @@ internal class TraceAndOptimize : AvatarGlobalComponent [ToggleLeft] public bool preserveEndBone; + [NotKeyable] + [CL4EELocalized("TraceAndOptimize:prop:removeZeroSizedPolygons")] + [ToggleLeft] + public bool removeZeroSizedPolygons = true; + // common parsing configuration [NotKeyable] [CL4EELocalized("TraceAndOptimize:prop:mmdWorldCompatibility", From a347ab85beb4dea4991603be2849d430863d6a64 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 30 Oct 2023 14:36:04 +0900 Subject: [PATCH 06/61] docs: add remove-zero-sized-polygon and add about that in trace and optimize --- .../remove-zero-sized-polygon/component.png | Bin 0 -> 6603 bytes .../remove-zero-sized-polygon/index.ja.md | 34 ++++++++++++++++++ .../remove-zero-sized-polygon/index.md | 34 ++++++++++++++++++ .../reference/trace-and-optimize/index.ja.md | 2 ++ .../reference/trace-and-optimize/index.md | 2 ++ 5 files changed, 72 insertions(+) create mode 100644 .docs/content/docs/reference/remove-zero-sized-polygon/component.png create mode 100644 .docs/content/docs/reference/remove-zero-sized-polygon/index.ja.md create mode 100644 .docs/content/docs/reference/remove-zero-sized-polygon/index.md diff --git a/.docs/content/docs/reference/remove-zero-sized-polygon/component.png b/.docs/content/docs/reference/remove-zero-sized-polygon/component.png new file mode 100644 index 0000000000000000000000000000000000000000..6f47ed253fe820a5b3423633e160a91377b9a139 GIT binary patch literal 6603 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 + +このコンポーネントはビルドプロセスの最後の方に実行されるため、このコンポーネントは[Modifying Edit Skinned Mesh Component](../../component-kind/edit-skinned-mesh-components#modifying-component) では**ありません**。 + +このコンポーネントを[Merge Skinned Mesh](../merge-skinned-mesh)のソースSkinnedMeshRendererに追加しても効果はありません。 + +{{< /hint >}} + +## 利点 {#benefits} + +面積が0なポリゴンを削除することで、描画負荷を減らすことができます。 +面積が0なポリゴンは見た目にほとんど影響を与えません。 + +## 設定 {#settings} + +現在のところ設定はありません。 + +![component.png](component.png) + +## 備考 {#notes} + +このコンポーネントは[Trace and Optimize](../trace-and-optimize)コンポーネントによって自動的に追加されます。 +このコンポーネントを手動で追加するよりも、Trace and Optimizeを使うことをお勧めします。 diff --git a/.docs/content/docs/reference/remove-zero-sized-polygon/index.md b/.docs/content/docs/reference/remove-zero-sized-polygon/index.md new file mode 100644 index 000000000..ad5bf06a9 --- /dev/null +++ b/.docs/content/docs/reference/remove-zero-sized-polygon/index.md @@ -0,0 +1,34 @@ +--- +title: Remove Zero Sized Polygon +weight: 100 +--- + +# Remove Zero Sized Polygon + +Remove polygons with area of zero. + +This component should be added to a GameObject which has a SkinnedMeshRenderer component. + +{{< hint warning >}} + +Since this component works very late in the build process, this component is **NOT** [Modifying Edit Skinned Mesh Component](../../component-kind/edit-skinned-mesh-components#modifying-component). + +Putting this component on the source SkinnedMeshRenderers of [Merge Skinned Mesh](../merge-skinned-mesh) has no effect. + +{{< /hint >}} + +## Benefits + +By removing polygons which has zero size, you can reduce rendering cost. +Polygons with zero size will have almost zero effect on the appearance. + +## Settings + +Currently no settings. + +![component.png](component.png) + +## Notes + +This component will be attached by [Trace and Optimize](../trace-and-optimize) component. +I recommend you to use Trace and Optimize instead of attaching this component manually. diff --git a/.docs/content/docs/reference/trace-and-optimize/index.ja.md b/.docs/content/docs/reference/trace-and-optimize/index.ja.md index bf9346acf..15321eecc 100644 --- a/.docs/content/docs/reference/trace-and-optimize/index.ja.md +++ b/.docs/content/docs/reference/trace-and-optimize/index.ja.md @@ -21,6 +21,8 @@ aliases: アニメーションなどを走査して、使われていないObject(GameObjectやコンポーネントなど)を自動的に削除します。 - `endボーンを残す` 親が削除されていないendボーン[^endbone]を削除しないようにします。 +- `大きさのないポリゴンを削除する` + [Remove Zero Sized Polygon](../remove-zero-sized-polygon)を使用して、面積が0のポリゴンを削除します。 また、以下の設定で自動設定を調節できます。 - `MMDワールドとの互換性` diff --git a/.docs/content/docs/reference/trace-and-optimize/index.md b/.docs/content/docs/reference/trace-and-optimize/index.md index f2ac51c63..964aefe8a 100644 --- a/.docs/content/docs/reference/trace-and-optimize/index.md +++ b/.docs/content/docs/reference/trace-and-optimize/index.md @@ -21,6 +21,8 @@ Currently the following optimizations are applied automatically. By scanning animation etc., automatically removes unused Objects (e.g. GameObjects, Components). - `Preserve EndBone` Prevents removing end bones[^endbone] whose parent is not removed. +- `Remove Polygons with Area of Zero` + Removes polygons with area of zero with attaching [Remove Zero Sized Polygon](../remove-zero-sized-polygon) component. Also, You can adjust optimization with the following settings - `MMD World Compatibility` From 1588857b85c2245db69e143da9afede0d229d7b7 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 30 Oct 2023 14:37:43 +0900 Subject: [PATCH 07/61] docs(changelog): Remove Zero Sized Polygons --- CHANGELOG-PRERELEASE.md | 1 + CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG-PRERELEASE.md b/CHANGELOG-PRERELEASE.md index 9a65ffa14..a2d1dc162 100644 --- a/CHANGELOG-PRERELEASE.md +++ b/CHANGELOG-PRERELEASE.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog]. ## [Unreleased] ### Added - Small performance improve `#641` +- Remove Zero Sized Polygons `#659` ### Changed - All logs passed to ErrorReport is now shown on the console log `#643` diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a9135977..cfe76ed53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog]. - Disabling PhysBone animation based on mesh renderer enabled animation `#640` - If you toggles your clothes with simple toggle, PhysBones on the your avatar will also be toggled automatically! - Small performance improve `#641` +- Remove Zero Sized Polygons `#659` ### Changed - All logs passed to ErrorReport is now shown on the console log `#643` From 4a2d76d30b3738c579a508781fd330803b5f38eb Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 30 Oct 2023 18:21:56 +0900 Subject: [PATCH 08/61] test: test animation length preserved --- Test~/ApplyObjectMappingTest.cs | 37 +++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Test~/ApplyObjectMappingTest.cs b/Test~/ApplyObjectMappingTest.cs index 0ab5bf2c8..6f6ee44dd 100644 --- a/Test~/ApplyObjectMappingTest.cs +++ b/Test~/ApplyObjectMappingTest.cs @@ -1,4 +1,5 @@ using NUnit.Framework; +using UnityEditor; using UnityEditor.Animations; using UnityEngine; @@ -41,5 +42,41 @@ public void AvatarMask() Assert.That(avatarMask.GetHumanoidBodyPartActive(AvatarMaskBodyPart.Head), Is.True); Assert.That(avatarMask.GetHumanoidBodyPartActive(AvatarMaskBodyPart.LeftLeg), Is.False); } + + [Test] + public void PreserveAnimationLength() + { + var root = new GameObject(); + var child1 = Utils.NewGameObject("child1", root.transform); + var child11 = Utils.NewGameObject("child11", child1.transform); + var builder = new ObjectMappingBuilder(root); + + Object.DestroyImmediate(child11); + + var built = builder.BuildObjectMapping(); + + var rootMapper = new AnimatorControllerMapper(built.CreateAnimationMapper(root)); + + var animatorController = new AnimatorController(); + var layer = new AnimatorControllerLayer() + { + name = "layer", + stateMachine = new AnimatorStateMachine() { name = "layer" }, + }; + var state = layer.stateMachine.AddState("theState"); + var clip = new AnimationClip(); + clip.SetCurve("child1/child11", typeof(GameObject), "m_IsActive", AnimationCurve.Constant(0, 0.3f, 1)); + state.motion = clip; + animatorController.AddLayer(layer); + + var mappedController = rootMapper.MapAnimatorController(animatorController); + Assert.That(mappedController, Is.Not.EqualTo(animatorController)); + var mappedClip = mappedController.layers[0].stateMachine.states[0].state.motion as AnimationClip; + Assert.That(mappedClip, Is.Not.Null); + + Assert.That(mappedClip.length, Is.EqualTo(0.3f)); + Assert.That(AnimationUtility.GetCurveBindings(mappedClip)[0].path, + Contains.Substring("AvatarOptimizerClipLengthDummy")); + } } } \ No newline at end of file From c8eb904c392612b0281dffc35e5aaf7bda2a7d0f Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Tue, 31 Oct 2023 21:36:04 +0900 Subject: [PATCH 09/61] chore(T&O): make removeZeroSizedPolygons disabled by default --- Runtime/TraceAndOptimize.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Runtime/TraceAndOptimize.cs b/Runtime/TraceAndOptimize.cs index d7ad28053..d7626fa58 100644 --- a/Runtime/TraceAndOptimize.cs +++ b/Runtime/TraceAndOptimize.cs @@ -30,7 +30,7 @@ internal class TraceAndOptimize : AvatarGlobalComponent [NotKeyable] [CL4EELocalized("TraceAndOptimize:prop:removeZeroSizedPolygons")] [ToggleLeft] - public bool removeZeroSizedPolygons = true; + public bool removeZeroSizedPolygons = false; // common parsing configuration [NotKeyable] From a2d26456b2cf4580b43e1172673f551f01a9d1b0 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 1 Nov 2023 21:49:15 +0900 Subject: [PATCH 10/61] docs: Apply suggestions from code review Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> --- .../remove-zero-sized-polygon/index.ja.md | 12 ++++++------ .../reference/remove-zero-sized-polygon/index.md | 14 +++++++------- .../docs/reference/trace-and-optimize/index.ja.md | 2 +- .../docs/reference/trace-and-optimize/index.md | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.docs/content/docs/reference/remove-zero-sized-polygon/index.ja.md b/.docs/content/docs/reference/remove-zero-sized-polygon/index.ja.md index 243960275..3c7b92c95 100644 --- a/.docs/content/docs/reference/remove-zero-sized-polygon/index.ja.md +++ b/.docs/content/docs/reference/remove-zero-sized-polygon/index.ja.md @@ -5,26 +5,26 @@ weight: 100 # Remove Zero Sized Polygon -面積が0なポリゴンを削除します。 +面積がゼロのポリゴンを削除します。 このコンポーネントは、SkinnedMeshRendererコンポーネントのあるGameObjectに追加してください。 {{< hint warning >}} -このコンポーネントはビルドプロセスの最後の方に実行されるため、このコンポーネントは[Modifying Edit Skinned Mesh Component](../../component-kind/edit-skinned-mesh-components#modifying-component) では**ありません**。 +このコンポーネントはビルドの最後の方で実行されるため、[Modifying Edit Skinned Mesh Component](../../component-kind/edit-skinned-mesh-components#modifying-component) では**ありません**。 -このコンポーネントを[Merge Skinned Mesh](../merge-skinned-mesh)のソースSkinnedMeshRendererに追加しても効果はありません。 +このコンポーネントを[Merge Skinned Mesh](../merge-skinned-mesh)の統合対象となるSkinnedMeshRendererに追加しても効果がありません。 {{< /hint >}} ## 利点 {#benefits} -面積が0なポリゴンを削除することで、描画負荷を減らすことができます。 -面積が0なポリゴンは見た目にほとんど影響を与えません。 +面積がゼロのポリゴンを削除することで、描画負荷を減らすことができます。 +見た目に影響を与えることはほとんどありません。 ## 設定 {#settings} -現在のところ設定はありません。 +今のところ、このコンポーネントに設定項目はありません。 ![component.png](component.png) diff --git a/.docs/content/docs/reference/remove-zero-sized-polygon/index.md b/.docs/content/docs/reference/remove-zero-sized-polygon/index.md index ad5bf06a9..ee4a1c61d 100644 --- a/.docs/content/docs/reference/remove-zero-sized-polygon/index.md +++ b/.docs/content/docs/reference/remove-zero-sized-polygon/index.md @@ -5,7 +5,7 @@ weight: 100 # Remove Zero Sized Polygon -Remove polygons with area of zero. +Remove polygons whose area are zero. This component should be added to a GameObject which has a SkinnedMeshRenderer component. @@ -13,22 +13,22 @@ This component should be added to a GameObject which has a SkinnedMeshRenderer c Since this component works very late in the build process, this component is **NOT** [Modifying Edit Skinned Mesh Component](../../component-kind/edit-skinned-mesh-components#modifying-component). -Putting this component on the source SkinnedMeshRenderers of [Merge Skinned Mesh](../merge-skinned-mesh) has no effect. +Adding this component to the SkinnedMeshRenderers to be merged by [Merge Skinned Mesh](../merge-skinned-mesh) component has no effect. {{< /hint >}} ## Benefits -By removing polygons which has zero size, you can reduce rendering cost. -Polygons with zero size will have almost zero effect on the appearance. +By removing polygons whose area are zero, you can reduce rendering cost. +This will have almost zero effect on the appearance. ## Settings -Currently no settings. +This Component doesn't have any configuration for now. ![component.png](component.png) ## Notes -This component will be attached by [Trace and Optimize](../trace-and-optimize) component. -I recommend you to use Trace and Optimize instead of attaching this component manually. +This component will be added by [Trace and Optimize](../trace-and-optimize) component. +I recommend you to use Trace and Optimize instead of adding this component manually. diff --git a/.docs/content/docs/reference/trace-and-optimize/index.ja.md b/.docs/content/docs/reference/trace-and-optimize/index.ja.md index 15321eecc..e2484865a 100644 --- a/.docs/content/docs/reference/trace-and-optimize/index.ja.md +++ b/.docs/content/docs/reference/trace-and-optimize/index.ja.md @@ -22,7 +22,7 @@ aliases: - `endボーンを残す` 親が削除されていないendボーン[^endbone]を削除しないようにします。 - `大きさのないポリゴンを削除する` - [Remove Zero Sized Polygon](../remove-zero-sized-polygon)を使用して、面積が0のポリゴンを削除します。 + 面積がゼロのポリゴンを削除します。 また、以下の設定で自動設定を調節できます。 - `MMDワールドとの互換性` diff --git a/.docs/content/docs/reference/trace-and-optimize/index.md b/.docs/content/docs/reference/trace-and-optimize/index.md index 964aefe8a..d70b622e4 100644 --- a/.docs/content/docs/reference/trace-and-optimize/index.md +++ b/.docs/content/docs/reference/trace-and-optimize/index.md @@ -22,7 +22,7 @@ Currently the following optimizations are applied automatically. - `Preserve EndBone` Prevents removing end bones[^endbone] whose parent is not removed. - `Remove Polygons with Area of Zero` - Removes polygons with area of zero with attaching [Remove Zero Sized Polygon](../remove-zero-sized-polygon) component. + Removes polygons whose area are zero. Also, You can adjust optimization with the following settings - `MMD World Compatibility` From f3184eb997251b935aeb43b17a0efc5190ad2c4b Mon Sep 17 00:00:00 2001 From: Sayamame-beans <61457993+sayamame-beans@users.noreply.github.com> Date: Wed, 1 Nov 2023 21:55:08 +0900 Subject: [PATCH 11/61] chore: Remove Polygons with Area of Zero -> Automatically Remove Zero Sized Polygons --- .docs/content/docs/reference/trace-and-optimize/index.md | 2 +- Localization/en.po | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.docs/content/docs/reference/trace-and-optimize/index.md b/.docs/content/docs/reference/trace-and-optimize/index.md index d70b622e4..a55ff55d3 100644 --- a/.docs/content/docs/reference/trace-and-optimize/index.md +++ b/.docs/content/docs/reference/trace-and-optimize/index.md @@ -21,7 +21,7 @@ Currently the following optimizations are applied automatically. By scanning animation etc., automatically removes unused Objects (e.g. GameObjects, Components). - `Preserve EndBone` Prevents removing end bones[^endbone] whose parent is not removed. -- `Remove Polygons with Area of Zero` +- `Automatically Remove Zero Sized Polygons` Removes polygons whose area are zero. Also, You can adjust optimization with the following settings diff --git a/Localization/en.po b/Localization/en.po index 9567afb4a..3b06f56ca 100644 --- a/Localization/en.po +++ b/Localization/en.po @@ -442,7 +442,7 @@ msgid "TraceAndOptimize:tooltip:preserveEndBone" msgstr "Prevents removing end bones whose parent is not removed." msgid "TraceAndOptimize:prop:removeZeroSizedPolygons" -msgstr "Remove Polygons with Area of Zero" +msgstr "Automatically Remove Zero Sized Polygons" # endregion From d080004c08744b8e7e14abea41735c27a9d727b1 Mon Sep 17 00:00:00 2001 From: Sayamame-beans <61457993+sayamame-beans@users.noreply.github.com> Date: Wed, 1 Nov 2023 21:56:15 +0900 Subject: [PATCH 12/61] =?UTF-8?q?chore:=20=E5=A4=A7=E3=81=8D=E3=81=95?= =?UTF-8?q?=E3=81=AE=E3=81=AA=E3=81=84=E3=83=9D=E3=83=AA=E3=82=B4=E3=83=B3?= =?UTF-8?q?=E3=82=92=E5=89=8A=E9=99=A4=E3=81=99=E3=82=8B=20->=20=E9=9D=A2?= =?UTF-8?q?=E7=A9=8D=E3=81=8C=E3=82=BC=E3=83=AD=E3=81=AE=E3=83=9D=E3=83=AA?= =?UTF-8?q?=E3=82=B4=E3=83=B3=E3=82=92=E8=87=AA=E5=8B=95=E7=9A=84=E3=81=AB?= =?UTF-8?q?=E5=89=8A=E9=99=A4=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .docs/content/docs/reference/trace-and-optimize/index.ja.md | 2 +- Localization/ja.po | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.docs/content/docs/reference/trace-and-optimize/index.ja.md b/.docs/content/docs/reference/trace-and-optimize/index.ja.md index e2484865a..e25afae91 100644 --- a/.docs/content/docs/reference/trace-and-optimize/index.ja.md +++ b/.docs/content/docs/reference/trace-and-optimize/index.ja.md @@ -21,7 +21,7 @@ aliases: アニメーションなどを走査して、使われていないObject(GameObjectやコンポーネントなど)を自動的に削除します。 - `endボーンを残す` 親が削除されていないendボーン[^endbone]を削除しないようにします。 -- `大きさのないポリゴンを削除する` +- `面積がゼロのポリゴンを自動的に削除する` 面積がゼロのポリゴンを削除します。 また、以下の設定で自動設定を調節できます。 diff --git a/Localization/ja.po b/Localization/ja.po index 399d317f3..0ecd6fe9d 100644 --- a/Localization/ja.po +++ b/Localization/ja.po @@ -378,7 +378,7 @@ msgid "TraceAndOptimize:tooltip:preserveEndBone" msgstr "親が削除されていないendボーンを削除しないようにします。" msgid "TraceAndOptimize:prop:removeZeroSizedPolygons" -msgstr "大きさのないポリゴンを削除する" +msgstr "面積がゼロのポリゴンを自動的に削除する" # endregion From bd8285473d076db75c13a1c5a0419640bd4d0cd4 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 1 Nov 2023 21:58:51 +0900 Subject: [PATCH 13/61] chore(localization): Apply suggestions from code review Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> --- Localization/en.po | 4 ++-- Localization/ja.po | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Localization/en.po b/Localization/en.po index 3b06f56ca..f2eeff8dc 100644 --- a/Localization/en.po +++ b/Localization/en.po @@ -278,7 +278,7 @@ msgstr "" "Please change import setting of models to include normals!" msgid "MergeSkinnedMesh:warning:removeZeroSizedPolygonOnSources" -msgstr "Since Remove Zero Sized Polygons are proceed later, it on the source Skinned Mesh Renderers have no effects." +msgstr "Since Remove Zero Sized Polygons are processed later, it has no effects if it is added with the source Skinned Mesh Renderers." # endregion @@ -353,7 +353,7 @@ msgstr "Invert All" # region RemoveZeroSizedPolygon msgid "RemoveZeroSizedPolygon:description" -msgstr "Removes polygons whose area is zero" +msgstr "Removes polygons whose area are zero" # endregion diff --git a/Localization/ja.po b/Localization/ja.po index 0ecd6fe9d..c2f8e4c0b 100644 --- a/Localization/ja.po +++ b/Localization/ja.po @@ -215,7 +215,7 @@ msgstr "" "法線が含まれるようにモデルのインポート設定を変更してください!" msgid "MergeSkinnedMesh:warning:removeZeroSizedPolygonOnSources" -msgstr "Remove Zero Sized Polygonsはのちに処理されるため、ソースレンダラにつけても効果がありません" +msgstr "Remove Zero Sized Polygonsは遅めに処理されるため、統合対象のメッシュにつけても効果がありません" # endregion @@ -290,7 +290,7 @@ msgstr "すべての有効/無効を入れ替える" # region RemoveZeroSizedPolygon msgid "RemoveZeroSizedPolygon:description" -msgstr "面積が零なポリゴンを削除します" +msgstr "面積がゼロのポリゴンを削除します。" # endregion From a360bdc44002c9a39ef140335423017137ec0fce Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 1 Nov 2023 22:03:29 +0900 Subject: [PATCH 14/61] chore(docs): update screenshot --- .../remove-zero-sized-polygon/component.png | Bin 6603 -> 6431 bytes .../trace-and-optimize/component.png | Bin 16757 -> 21653 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/.docs/content/docs/reference/remove-zero-sized-polygon/component.png b/.docs/content/docs/reference/remove-zero-sized-polygon/component.png index 6f47ed253fe820a5b3423633e160a91377b9a139..760d2ba5e9a90994e6e2686cb20fcbbe14b79919 100644 GIT binary patch literal 6431 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 a=oFZHP(@wWLDaG$D3r3az literal 6603 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 EKbYBC5dZ)H literal 16757 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 B5TgJ9 From 96c39b9c3bbba05e3f764e332b5cd99a66d06164 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 2 Nov 2023 05:32:04 +0900 Subject: [PATCH 15/61] docs: API Breaking Changes is not part of stable changes --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18cef258d..199f8c4b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ The format is based on [Keep a Changelog]. ## [Unreleased] ### Added -- Public API for registering component information `#632` +- Public API for registering component information `#632` `#668` - Disabling PhysBone animation based on mesh renderer enabled animation `#640` - If you toggles your clothes with simple toggle, PhysBones on the your avatar will also be toggled automatically! - Small performance improve `#641` @@ -19,7 +19,6 @@ The format is based on [Keep a Changelog]. - Improved Behaviour with multi-material multi pass rendering `#662` - Previously, multi-material multi pass rendering are flattened. - Since 1.6, flattened if component doesn't support that. -- BREAKING API CHANGES: Behaviour components are renamed to HeavyBehaviour `#668` ### Deprecated From e746e5fe977c9d2442f400aeb7b2fd856d97d419 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 2 Nov 2023 05:36:36 +0900 Subject: [PATCH 16/61] refactor: use NDMF's RuntimeUtils instead of our one --- Editor/Processors/TraceAndOptimize/GCDebug.cs | 1 + Runtime/AvatarTagComponent.cs | 4 +- Runtime/RuntimeUtil.cs | 37 ------------------- Runtime/RuntimeUtil.cs.meta | 3 -- ....anatawa12.avatar-optimizer.runtime.asmdef | 3 +- 5 files changed, 5 insertions(+), 43 deletions(-) delete mode 100644 Runtime/RuntimeUtil.cs delete mode 100644 Runtime/RuntimeUtil.cs.meta diff --git a/Editor/Processors/TraceAndOptimize/GCDebug.cs b/Editor/Processors/TraceAndOptimize/GCDebug.cs index 7497924e7..f84507ec4 100644 --- a/Editor/Processors/TraceAndOptimize/GCDebug.cs +++ b/Editor/Processors/TraceAndOptimize/GCDebug.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using nadena.dev.ndmf.runtime; using UnityEditor; using UnityEngine; diff --git a/Runtime/AvatarTagComponent.cs b/Runtime/AvatarTagComponent.cs index b554cde87..75056544f 100644 --- a/Runtime/AvatarTagComponent.cs +++ b/Runtime/AvatarTagComponent.cs @@ -1,5 +1,5 @@ -using System; using Anatawa12.AvatarOptimizer.ErrorReporting; +using nadena.dev.ndmf.runtime; using UnityEngine; namespace Anatawa12.AvatarOptimizer @@ -19,7 +19,7 @@ internal abstract class AvatarTagComponent : MonoBehaviour { private void OnValidate() { - if (RuntimeUtil.isPlaying) return; + if (RuntimeUtil.IsPlaying) return; ErrorReporterRuntime.TriggerChange(); } diff --git a/Runtime/RuntimeUtil.cs b/Runtime/RuntimeUtil.cs deleted file mode 100644 index 2ef7cb776..000000000 --- a/Runtime/RuntimeUtil.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using JetBrains.Annotations; -using UnityEngine; - -namespace Anatawa12.AvatarOptimizer -{ - // https://github.com/bdunderscore/modular-avatar/blob/db49e2e210bc070671af963ff89df853ae4514a5/Packages/nadena.dev.modular-avatar/Runtime/RuntimeUtil.cs - // Originally under MIT License - // Copyright (c) 2022 bd_ - internal static class RuntimeUtil - { - [CanBeNull] - public static string RelativePath(GameObject root, GameObject child) - { - if (root == child) return ""; - - List pathSegments = new List(); - while (child != root && child != null) - { - pathSegments.Add(child.name); - child = child.transform.parent != null ? child.transform.parent.gameObject : null; - } - - if (child == null) return null; - - pathSegments.Reverse(); - return String.Join("/", pathSegments); - } - -#if UNITY_EDITOR - public static bool isPlaying => UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode; -#else - public static bool isPlaying => true; -#endif - } -} diff --git a/Runtime/RuntimeUtil.cs.meta b/Runtime/RuntimeUtil.cs.meta deleted file mode 100644 index a0d03ce79..000000000 --- a/Runtime/RuntimeUtil.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 3a3212f462e5430a81a74acb08e41adb -timeCreated: 1672489040 \ No newline at end of file diff --git a/Runtime/com.anatawa12.avatar-optimizer.runtime.asmdef b/Runtime/com.anatawa12.avatar-optimizer.runtime.asmdef index 32e8ead24..8a83ad091 100644 --- a/Runtime/com.anatawa12.avatar-optimizer.runtime.asmdef +++ b/Runtime/com.anatawa12.avatar-optimizer.runtime.asmdef @@ -4,7 +4,8 @@ "com.anatawa12.avatar-optimizer.internal.error-reporter.runtime", "com.anatawa12.custom-localization-for-editor-extension.runtime", "com.anatawa12.avatar-optimizer.internal.prefab-safe-set", - "Unity.Burst" + "Unity.Burst", + "nadena.dev.ndmf.runtime" ], "includePlatforms": [], "excludePlatforms": [], From 5d711b6f2cf4887993267d43819dceedf2d445da Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 2 Nov 2023 13:14:54 +0900 Subject: [PATCH 17/61] docs(remove-zero-sized-mesh): add note --- .../docs/reference/remove-zero-sized-polygon/index.ja.md | 4 ++++ .../docs/reference/remove-zero-sized-polygon/index.md | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/.docs/content/docs/reference/remove-zero-sized-polygon/index.ja.md b/.docs/content/docs/reference/remove-zero-sized-polygon/index.ja.md index 3c7b92c95..1b2a46a93 100644 --- a/.docs/content/docs/reference/remove-zero-sized-polygon/index.ja.md +++ b/.docs/content/docs/reference/remove-zero-sized-polygon/index.ja.md @@ -22,6 +22,10 @@ weight: 100 面積がゼロのポリゴンを削除することで、描画負荷を減らすことができます。 見た目に影響を与えることはほとんどありません。 +## 備考 {#notes} + +シェーダーによってはモデルファイルでのポリゴンの大きさが0でも実際にはなにかが描画されることがあるため、見た目に影響がある可能性があります。 + ## 設定 {#settings} 今のところ、このコンポーネントに設定項目はありません。 diff --git a/.docs/content/docs/reference/remove-zero-sized-polygon/index.md b/.docs/content/docs/reference/remove-zero-sized-polygon/index.md index ee4a1c61d..72fda2586 100644 --- a/.docs/content/docs/reference/remove-zero-sized-polygon/index.md +++ b/.docs/content/docs/reference/remove-zero-sized-polygon/index.md @@ -22,6 +22,11 @@ Adding this component to the SkinnedMeshRenderers to be merged by [Merge Skinned By removing polygons whose area are zero, you can reduce rendering cost. This will have almost zero effect on the appearance. +## Notes + +In some shaders, even if size of polygon in model file is zero, something can be rendered so +there may be effect on the appearance. + ## Settings This Component doesn't have any configuration for now. From 93b0e0e8e3c0632c9ed0f4c20730cd3ecec476f2 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 2 Nov 2023 23:47:34 +0900 Subject: [PATCH 18/61] fix: unnecessary some properties are mapped --- Editor/ObjectMapping/AnimationObjectMapper.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Editor/ObjectMapping/AnimationObjectMapper.cs b/Editor/ObjectMapping/AnimationObjectMapper.cs index 0a1002652..e3b01b2bb 100644 --- a/Editor/ObjectMapping/AnimationObjectMapper.cs +++ b/Editor/ObjectMapping/AnimationObjectMapper.cs @@ -130,6 +130,12 @@ public EditorCurveBinding[] MapBinding(EditorCurveBinding binding) if (componentInfo.PropertyMapping.TryGetValue(binding.propertyName, out var newProp)) { + // if mapped one is exactly same as original, return null + if (newProp.AllCopiedTo.Length == 1 + && newProp.AllCopiedTo[0].InstanceId == instanceId + && newProp.AllCopiedTo[0].Name == binding.propertyName) + return null; + // there are mapping for property var curveBindings = new EditorCurveBinding[newProp.AllCopiedTo.Length]; var copiedToIndex = 0; From 55b4b77f8ed3e2789fba8957e916110c4674c642 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 2 Nov 2023 23:59:47 +0900 Subject: [PATCH 19/61] fix: Proxy animation can be remapped --- Editor/ObjectMapping/ObjectMappingContext.cs | 4 ++++ Editor/Utils/Utils.VRCSDK.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Editor/ObjectMapping/ObjectMappingContext.cs b/Editor/ObjectMapping/ObjectMappingContext.cs index b5d0736ba..8dae14d66 100644 --- a/Editor/ObjectMapping/ObjectMappingContext.cs +++ b/Editor/ObjectMapping/ObjectMappingContext.cs @@ -142,6 +142,10 @@ private Object CustomClone(Object o) { if (o is AnimationClip clip) { +#if AAO_VRCSDK3_AVATARS + // TODO: when BuildContext have property to check if it is for VRCSDK3, additionally use it. + if (clip.IsProxy()) return clip; +#endif var newClip = new AnimationClip(); newClip.name = "rebased " + clip.name; diff --git a/Editor/Utils/Utils.VRCSDK.cs b/Editor/Utils/Utils.VRCSDK.cs index bc4a82af1..cd4ee1369 100644 --- a/Editor/Utils/Utils.VRCSDK.cs +++ b/Editor/Utils/Utils.VRCSDK.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using UnityEngine; using VRC.Dynamics; @@ -25,5 +26,8 @@ public static IEnumerable GetAffectedTransforms(this VRCPhysBoneBase queue.Enqueue(child); } } + + // https://creators.vrchat.com/avatars/#proxy-animations + public static bool IsProxy(this AnimationClip clip) => clip.name.StartsWith("proxy_", StringComparison.Ordinal); } } \ No newline at end of file From 164e610161f0d387e2210525d8d7a028c714a883 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Fri, 3 Nov 2023 00:04:03 +0900 Subject: [PATCH 20/61] fix: proxy animation can be renamed --- CHANGELOG-PRERELEASE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG-PRERELEASE.md b/CHANGELOG-PRERELEASE.md index f859d2812..decb336fe 100644 --- a/CHANGELOG-PRERELEASE.md +++ b/CHANGELOG-PRERELEASE.md @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog]. ### Removed ### Fixed +- proxy animation can be modified `#678` ### Security From e4a61f6fd697c8de68cd0cea0371647be3530f9f Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Fri, 3 Nov 2023 09:36:23 +0900 Subject: [PATCH 21/61] test: preserve proxy animation --- Test~/ApplyObjectMappingTest.cs | 44 +++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/Test~/ApplyObjectMappingTest.cs b/Test~/ApplyObjectMappingTest.cs index 6f6ee44dd..e9830c23d 100644 --- a/Test~/ApplyObjectMappingTest.cs +++ b/Test~/ApplyObjectMappingTest.cs @@ -78,5 +78,49 @@ public void PreserveAnimationLength() Assert.That(AnimationUtility.GetCurveBindings(mappedClip)[0].path, Contains.Substring("AvatarOptimizerClipLengthDummy")); } + + [Test] + public void PreserveProxyAnimation() + { + var root = new GameObject(); + var child1 = Utils.NewGameObject("child1", root.transform); + var child11 = Utils.NewGameObject("child11", child1.transform); + var builder = new ObjectMappingBuilder(root); + + Object.DestroyImmediate(child11); + + var built = builder.BuildObjectMapping(); + + var rootMapper = new AnimatorControllerMapper(built.CreateAnimationMapper(root)); + + var animatorController = new AnimatorController(); + var layer = new AnimatorControllerLayer() + { + name = "layer", + stateMachine = new AnimatorStateMachine() { name = "layer" }, + }; + var state = layer.stateMachine.AddState("theState"); + var clip = new AnimationClip(); + clip.SetCurve("child1/child11", typeof(GameObject), "m_IsActive", AnimationCurve.Constant(0, 0.3f, 1)); + state.motion = clip; + + var proxyMotion = + AssetDatabase.LoadAssetAtPath( + AssetDatabase.GUIDToAssetPath("806c242c97b686d4bac4ad50defd1fdb")); + state = layer.stateMachine.AddState("afk"); + state.motion = proxyMotion; + + animatorController.AddLayer(layer); + + var mappedController = rootMapper.MapAnimatorController(animatorController); + Assert.That(mappedController, Is.Not.EqualTo(animatorController)); + + // ensure non-proxy mapped + var mappedClip = mappedController.layers[0].stateMachine.states[0].state.motion as AnimationClip; + Assert.That(mappedClip, Is.Not.EqualTo(clip)); + + mappedClip = mappedController.layers[0].stateMachine.states[1].state.motion as AnimationClip; + Assert.That(mappedClip, Is.EqualTo(proxyMotion)); + } } } \ No newline at end of file From 75397bde80ab7a5fb2fe59ebcc755d13f9cb8cc8 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Fri, 3 Nov 2023 10:30:43 +0900 Subject: [PATCH 22/61] test: add test for multi frame BlendShape with partially identity frames --- Test~/MeshInfo2/MeshInfo2Test.cs | 74 +++++++++++++++++++ ...com.anatawa12.avatar-optimizer.test.asmdef | 3 +- 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/Test~/MeshInfo2/MeshInfo2Test.cs b/Test~/MeshInfo2/MeshInfo2Test.cs index 471958e0e..82cf7f9c2 100644 --- a/Test~/MeshInfo2/MeshInfo2Test.cs +++ b/Test~/MeshInfo2/MeshInfo2Test.cs @@ -1,6 +1,8 @@ +using System; using Anatawa12.AvatarOptimizer.Processors.SkinnedMeshes; using NUnit.Framework; using UnityEngine; +using UnityEngine.Rendering; namespace Anatawa12.AvatarOptimizer.Test { @@ -95,5 +97,77 @@ public void RootBoneWithNoneMeshSkinnedMeshRenderer() var meshInfo2 = new MeshInfo2(smr); Assert.That(meshInfo2.RootBone, Is.EqualTo(secondGo.transform)); } + + [Test] + public void MultiFrameBlendShapeWithPartiallyIdentity() + { + var mesh = BoxMesh(); + var deltas = new Vector3[8]; + deltas.AsSpan().Fill(new Vector3(1, 2, 3)); + mesh.AddBlendShapeFrame("shape", 0, new Vector3[8], null, null); + mesh.AddBlendShapeFrame("shape", 1, new Vector3[8], null, null); + mesh.AddBlendShapeFrame("shape", 2, new Vector3[8], null, null); + mesh.AddBlendShapeFrame("shape", 3, deltas, null, null); + mesh.AddBlendShapeFrame("shape", 4, new Vector3[8], null, null); + + var go = new GameObject(); + var smr = go.AddComponent(); + smr.sharedMesh = mesh; + + var meshInfo2 = new MeshInfo2(smr); + + foreach (var vertex in meshInfo2.Vertices) + { + var frames = vertex.BlendShapes["shape"]; + Assert.That(frames.Length, Is.EqualTo(5)); + for (var i = 0; i < frames.Length; i++) + { + Assert.That(frames[i].Weight, Is.EqualTo((float)i)); + Assert.That(frames[i].Position, Is.EqualTo(i == 3 ? new Vector3(1, 2, 3) : new Vector3())); + } + } + } + + private Mesh BoxMesh() + { + var mesh = new Mesh + { + vertices = new[] + { + new Vector3(-1, -1, -1), + new Vector3(+1, -1, -1), + new Vector3(-1, +1, -1), + new Vector3(+1, +1, -1), + new Vector3(-1, -1, +1), + new Vector3(+1, -1, +1), + new Vector3(-1, +1, +1), + new Vector3(+1, +1, +1), + }, + triangles = new[] + { + 0, 1, 2, + 1, 3, 2, + + 4, 6, 5, + 5, 6, 7, + + 0, 4, 1, + + 1, 4, 5, + 1, 5, 3, + + 3, 5, 7, + 3, 7, 2, + + 2, 7, 6, + 2, 6, 0, + }, + }; + + mesh.subMeshCount = 1; + mesh.SetSubMesh(0, new SubMeshDescriptor(0, mesh.triangles.Length)); + + return mesh; + } } } diff --git a/Test~/com.anatawa12.avatar-optimizer.test.asmdef b/Test~/com.anatawa12.avatar-optimizer.test.asmdef index 739364e38..0a3eb57ee 100644 --- a/Test~/com.anatawa12.avatar-optimizer.test.asmdef +++ b/Test~/com.anatawa12.avatar-optimizer.test.asmdef @@ -22,7 +22,8 @@ "VRC.SDK3.Dynamics.PhysBone.dll", "VRC.Dynamics.dll", "VRCSDK3A.dll", - "VRCSDKBase.dll" + "VRCSDKBase.dll", + "System.Memory.dll" ], "autoReferenced": false, "defineConstraints": [], From d3453b9ff060a460469551178c9a993098a52d8a Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Fri, 3 Nov 2023 10:40:45 +0900 Subject: [PATCH 23/61] test: blendShape with frame at zero --- Test~/MeshInfo2/MeshInfo2Test.cs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Test~/MeshInfo2/MeshInfo2Test.cs b/Test~/MeshInfo2/MeshInfo2Test.cs index 82cf7f9c2..ce6d21d5f 100644 --- a/Test~/MeshInfo2/MeshInfo2Test.cs +++ b/Test~/MeshInfo2/MeshInfo2Test.cs @@ -128,6 +128,30 @@ public void MultiFrameBlendShapeWithPartiallyIdentity() } } + [Test] + public void BlendShapeWithFrameAtZero() + { + var mesh = BoxMesh(); + var deltas = new Vector3[8]; + deltas.AsSpan().Fill(new Vector3(1, 2, 3)); + mesh.AddBlendShapeFrame("shape", 0, deltas, null, null); + mesh.AddBlendShapeFrame("shape", 1, deltas, null, null); + + var go = new GameObject(); + var smr = go.AddComponent(); + smr.sharedMesh = mesh; + + var meshInfo2 = new MeshInfo2(smr); + + Vector3 position; + var vertex = meshInfo2.Vertices[0]; + Assert.That(vertex.TryGetBlendShape("shape", 0, out position, out _, out _), Is.True); + Assert.That(position, Is.EqualTo(new Vector3(0, 0, 0))); + + Assert.That(vertex.TryGetBlendShape("shape", 0, out position, out _, out _, getDefined: true), Is.True); + Assert.That(position, Is.EqualTo(new Vector3(1, 2, 3))); + } + private Mesh BoxMesh() { var mesh = new Mesh From e5269512b8d79850135876d72d7cc0fc6115f322 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sat, 4 Nov 2023 14:08:29 +0900 Subject: [PATCH 24/61] fix: Joint can be disappeared unexpectedly --- .../ComponentDependencyCollector.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Editor/Processors/TraceAndOptimize/ComponentDependencyCollector.cs b/Editor/Processors/TraceAndOptimize/ComponentDependencyCollector.cs index f8a964b25..94fb4226a 100644 --- a/Editor/Processors/TraceAndOptimize/ComponentDependencyCollector.cs +++ b/Editor/Processors/TraceAndOptimize/ComponentDependencyCollector.cs @@ -406,8 +406,17 @@ private static void InitByTypeParsers() AddParserWithExtends(); AddParser((collector, deps, component) => { - collector.GetDependencies(component.GetComponent()).AddActiveDependency(component); - deps.AddActiveDependency(component.connectedBody); + var rigidBody = component.GetComponent(); + if (rigidBody) + { + collector.GetDependencies(rigidBody).AddActiveDependency(component); + deps.AddActiveDependency(rigidBody); + } + if (component.connectedBody) + { + collector.GetDependencies(component.connectedBody).AddActiveDependency(component); + deps.AddActiveDependency(component.connectedBody); + } }); AddParserWithExtends(); AddParserWithExtends(); From c0e7c018dbd990e4df96d5f13fdf66b72ba5b4be Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sat, 4 Nov 2023 14:11:32 +0900 Subject: [PATCH 25/61] docs(changelog): RigidBody Joint can be broken --- CHANGELOG-PRERELEASE.md | 1 + CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG-PRERELEASE.md b/CHANGELOG-PRERELEASE.md index 51ba66336..93f7c1460 100644 --- a/CHANGELOG-PRERELEASE.md +++ b/CHANGELOG-PRERELEASE.md @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog]. ### Removed ### Fixed +- RigidBody Joint can be broken `#683` ### Security diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ba0fd0db..394fadd7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog]. ### Removed ### Fixed +- RigidBody Joint can be broken `#683` ### Security From e15f34780733dcbd390de027b0136ce0a5fc74e7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 4 Nov 2023 05:37:57 +0000 Subject: [PATCH 26/61] chore: bump version to 1.5.10 --- CHANGELOG-PRERELEASE.md | 8 ++++++-- CHANGELOG.md | 8 ++++++-- package.json | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CHANGELOG-PRERELEASE.md b/CHANGELOG-PRERELEASE.md index 93f7c1460..2ec77cfdc 100644 --- a/CHANGELOG-PRERELEASE.md +++ b/CHANGELOG-PRERELEASE.md @@ -16,10 +16,13 @@ The format is based on [Keep a Changelog]. ### Removed ### Fixed -- RigidBody Joint can be broken `#683` ### Security +## [1.5.10] - 2023-11-04 +### Fixed +- RigidBody Joint can be broken [`#683`](https://github.com/anatawa12/AvatarOptimizer/pull/683) + ## [1.5.9] - 2023-10-29 ## [1.5.9-rc.1] - 2023-10-28 ### Fixed @@ -940,7 +943,8 @@ This release is mistake. - Merge Bone - Clear Endpoint Position -[Unreleased]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.9...HEAD +[Unreleased]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.10...HEAD +[1.5.10]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.9...v1.5.10 [1.5.9]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.9-rc.1...v1.5.9 [1.5.9-rc.1]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.8...v1.5.9-rc.1 [1.5.8]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.8-rc.1...v1.5.8 diff --git a/CHANGELOG.md b/CHANGELOG.md index 394fadd7b..c60dd51e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,10 +16,13 @@ The format is based on [Keep a Changelog]. ### Removed ### Fixed -- RigidBody Joint can be broken `#683` ### Security +## [1.5.10] - 2023-11-04 +### Fixed +- RigidBody Joint can be broken [`#683`](https://github.com/anatawa12/AvatarOptimizer/pull/683) + ## [1.5.9] - 2023-10-29 ### Fixed - Animation clip length can be changed [`#647`](https://github.com/anatawa12/AvatarOptimizer/pull/647) @@ -625,7 +628,8 @@ The format is based on [Keep a Changelog]. - Merge Bone - Clear Endpoint Position -[Unreleased]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.9...HEAD +[Unreleased]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.10...HEAD +[1.5.10]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.9...v1.5.10 [1.5.9]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.8...v1.5.9 [1.5.8]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.7...v1.5.8 [1.5.7]: https://github.com/anatawa12/AvatarOptimizer/compare/v1.5.6...v1.5.7 diff --git a/package.json b/package.json index bbe92f72b..d5c8cad8c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.anatawa12.avatar-optimizer", - "version": "1.5.10-beta.0", + "version": "1.5.10", "unity": "2019.4", "description": "Set of Anatawa12's Small Avatar Optimization Utilities", "displayName": "Anatawa12's AvatarOptimizer", From d5f7da8a588631a321dcd1a036f34eb61fc2ea15 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 4 Nov 2023 05:38:57 +0000 Subject: [PATCH 27/61] chore: prepare for next version: 1.5.11 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d5c8cad8c..52ebc0dbc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.anatawa12.avatar-optimizer", - "version": "1.5.10", + "version": "1.5.11-beta.0", "unity": "2019.4", "description": "Set of Anatawa12's Small Avatar Optimization Utilities", "displayName": "Anatawa12's AvatarOptimizer", From 965fd32eef456048e81df94ca90af8e193bc313f Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sat, 4 Nov 2023 14:45:40 +0900 Subject: [PATCH 28/61] ci: check if release is public --- .github/workflows/release.yml | 7 +++++++ package.json | 1 + 2 files changed, 8 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 40f1b1a09..e193c8e6b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,6 +47,13 @@ jobs: hugo-version: '0.111.3' extended: true + - name: Check release is public + run: + if [[ "$(jq '.private == true' < package.json)" == "true" ]]; then + echo "package.json is private" + exit 255 + fi + - name: Update Version Name id: update-version run: | diff --git a/package.json b/package.json index c99029650..e849ff02e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "com.anatawa12.avatar-optimizer", "version": "1.6.0-beta.2", + "private": true, "unity": "2019.4", "description": "Set of Anatawa12's Small Avatar Optimization Utilities", "displayName": "Anatawa12's AvatarOptimizer", From 259f266ca0136ae4baaa307026a0efae247155f8 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sat, 4 Nov 2023 17:08:43 +0900 Subject: [PATCH 29/61] ci: check for private only for stable release --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e193c8e6b..898728e51 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,6 +48,7 @@ jobs: extended: true - name: Check release is public + if: github.event.inputs.release_kind == 'stable'` run: if [[ "$(jq '.private == true' < package.json)" == "true" ]]; then echo "package.json is private" From 58ebcbc4ebc71d93a91a9b40fce014c20fa604b2 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sat, 4 Nov 2023 17:13:38 +0900 Subject: [PATCH 30/61] ci: fix workflow syntax --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 898728e51..5019306d0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,7 +48,7 @@ jobs: extended: true - name: Check release is public - if: github.event.inputs.release_kind == 'stable'` + if: github.event.inputs.release_kind == 'stable' run: if [[ "$(jq '.private == true' < package.json)" == "true" ]]; then echo "package.json is private" From 214242827df87371f48d7f13220477a418a3a492 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sat, 4 Nov 2023 21:38:18 +0900 Subject: [PATCH 31/61] chore: add note about relationship between `ModifyProperties` and `TryMapProperty` --- API-Editor/ComponentInformation.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/API-Editor/ComponentInformation.cs b/API-Editor/ComponentInformation.cs index 3b1f2ad99..a5b17737a 100644 --- a/API-Editor/ComponentInformation.cs +++ b/API-Editor/ComponentInformation.cs @@ -249,6 +249,10 @@ internal MappedComponentInfo() /// Maps animation property name to component and MappedPropertyInfo. /// If the property is not removed, returns true and is set. /// If the property is removed, returns false and will be default. + /// + /// To get mapped property probably, you must register the property as modified property by + /// . + /// Unless that, renaming or moving the property may not be tracked by Avatar Optimizer. /// /// The name of property will be mapped /// The result parameter From a17505448c5c2df1fd35532844e82b3397b85f2c Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sat, 4 Nov 2023 22:04:59 +0900 Subject: [PATCH 32/61] docs: add more API docs --- API-Editor/ComponentInformation.cs | 39 +++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/API-Editor/ComponentInformation.cs b/API-Editor/ComponentInformation.cs index a5b17737a..3ab91bf5b 100644 --- a/API-Editor/ComponentInformation.cs +++ b/API-Editor/ComponentInformation.cs @@ -207,23 +207,49 @@ internal ComponentMutationsCollector() { } + /// + /// Registers of the will be changed by current component. + /// + /// The component current component will modifies + /// The list of properties current component will modifies [PublicAPI] public abstract void ModifyProperties([NotNull] Component component, [NotNull] IEnumerable properties); + /// [PublicAPI] public void ModifyProperties([NotNull] Component component, [NotNull] string[] properties) => ModifyProperties(component, (IEnumerable) properties); } + /// + /// The class provides object and property replaced by Avatar Optimizer. + /// + /// Avatar Optimizer may replaces or merges component to another component. + /// This class provide the information about the replacement. + /// In addition, Avatar Optimizer may replace or merges some properties of the component. + /// This class also provide the information about the property replacement. + /// public abstract class MappingSource { internal MappingSource() { } + /// + /// Returns about the component instance. + /// The instance can be a missing component. + /// + /// The component to get information about + /// The type of component [PublicAPI] public abstract MappedComponentInfo GetMappedComponent(T component) where T : Component; + /// + /// Returns about the GameObject instance. + /// The instance can be a missing component. + /// + /// The component to get information about + /// The type of component [PublicAPI] public abstract MappedComponentInfo GetMappedGameObject(GameObject component); } @@ -237,10 +263,11 @@ internal MappedComponentInfo() /// /// The mapped component (or GameObject). /// The component may be removed without mapped component. - /// If there are not mapped component, this will be null. + /// If there are no mapped component, this will be null. /// - /// Even if the component is removed without mapped component, - /// each animation property can be mapped to another component. + /// Even if the component is removed without mapped component, some animation property can be mapped + /// to a property on another component so you should use if the component is highly related + /// to animation property, for example, blendShape related SkinnedMeshRenderer. /// [PublicAPI] public abstract T MappedComponent { get; } @@ -263,9 +290,15 @@ internal MappedComponentInfo() public readonly struct MappedPropertyInfo { + /// + /// The Component or GameObject the property is on. + /// [PublicAPI] public Object Component { get; } + /// + /// The name of the mapped property. + /// [PublicAPI] public string Property { get; } From 0e464542a9e7bdb20ebe6ceaf2522ce077e9bebb Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sat, 4 Nov 2023 22:10:33 +0900 Subject: [PATCH 33/61] chore: make `ModifyProperties` takes params --- API-Editor/ComponentInformation.cs | 8 +++++--- Editor/APIInternal/ComponentInfos.VRCSDK.cs | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/API-Editor/ComponentInformation.cs b/API-Editor/ComponentInformation.cs index 3ab91bf5b..bafa006c1 100644 --- a/API-Editor/ComponentInformation.cs +++ b/API-Editor/ComponentInformation.cs @@ -213,12 +213,14 @@ internal ComponentMutationsCollector() /// The component current component will modifies /// The list of properties current component will modifies [PublicAPI] - public abstract void ModifyProperties([NotNull] Component component, [NotNull] IEnumerable properties); + public abstract void ModifyProperties([NotNull] Component component, + [NotNull] [ItemNotNull] IEnumerable properties); /// [PublicAPI] - public void ModifyProperties([NotNull] Component component, [NotNull] string[] properties) => - ModifyProperties(component, (IEnumerable) properties); + public void ModifyProperties([NotNull] Component component, + [NotNull] [ItemNotNull] params string[] properties) => + ModifyProperties(component, (IEnumerable)properties); } /// diff --git a/Editor/APIInternal/ComponentInfos.VRCSDK.cs b/Editor/APIInternal/ComponentInfos.VRCSDK.cs index 85a1c5f27..fbb0724af 100644 --- a/Editor/APIInternal/ComponentInfos.VRCSDK.cs +++ b/Editor/APIInternal/ComponentInfos.VRCSDK.cs @@ -56,7 +56,7 @@ protected override void CollectMutations(T component, ComponentMutationsCollecto case VRC_AvatarDescriptor.LipSyncStyle.JawFlapBlendShape when component.VisemeSkinnedMesh != null: { collector.ModifyProperties(component.VisemeSkinnedMesh, - new[] { $"blendShape.{component.MouthOpenBlendShapeName}" }); + $"blendShape.{component.MouthOpenBlendShapeName}"); break; } case VRC_AvatarDescriptor.LipSyncStyle.VisemeBlendShape when component.VisemeSkinnedMesh != null: From 7eb0155363e55ad9dd1fad9bfb333ed1056e7e47 Mon Sep 17 00:00:00 2001 From: kaikoga Date: Tue, 31 Oct 2023 06:53:24 +0900 Subject: [PATCH 34/61] fix: if AAO_VRCSDK3_AVATARS --- Editor/Utils/Utils.VRCSDK.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Editor/Utils/Utils.VRCSDK.cs b/Editor/Utils/Utils.VRCSDK.cs index cd4ee1369..1c133a4ac 100644 --- a/Editor/Utils/Utils.VRCSDK.cs +++ b/Editor/Utils/Utils.VRCSDK.cs @@ -1,3 +1,5 @@ +#if AAO_VRCSDK3_AVATARS + using System; using System.Collections.Generic; using UnityEngine; @@ -30,4 +32,6 @@ public static IEnumerable GetAffectedTransforms(this VRCPhysBoneBase // https://creators.vrchat.com/avatars/#proxy-animations public static bool IsProxy(this AnimationClip clip) => clip.name.StartsWith("proxy_", StringComparison.Ordinal); } -} \ No newline at end of file +} + +#endif \ No newline at end of file From bffe1db9d4dbeb65b3bf02ec07f2568d476ee444 Mon Sep 17 00:00:00 2001 From: kaikoga Date: Fri, 20 Oct 2023 04:14:15 +0900 Subject: [PATCH 35/61] chore: remove unused VRCSDK3 defines --- ...avatar-optimizer.internal.error-reporter.editor.asmdef | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Internal/ErrorReporter/Editor/com.anatawa12.avatar-optimizer.internal.error-reporter.editor.asmdef b/Internal/ErrorReporter/Editor/com.anatawa12.avatar-optimizer.internal.error-reporter.editor.asmdef index 03dc40fc9..6df7aaf5b 100644 --- a/Internal/ErrorReporter/Editor/com.anatawa12.avatar-optimizer.internal.error-reporter.editor.asmdef +++ b/Internal/ErrorReporter/Editor/com.anatawa12.avatar-optimizer.internal.error-reporter.editor.asmdef @@ -20,12 +20,6 @@ ], "autoReferenced": false, "defineConstraints": [], - "versionDefines": [ - { - "name": "com.vrchat.avatars", - "expression": "", - "define": "AAO_VRCSDK3_AVATARS" - } - ], + "versionDefines": [], "noEngineReferences": false } \ No newline at end of file From 579d4637bed3158888eb56018cee4e1b3d1489b0 Mon Sep 17 00:00:00 2001 From: kaikoga Date: Fri, 20 Oct 2023 04:14:30 +0900 Subject: [PATCH 36/61] chore: add VRM defines --- Editor/com.anatawa12.avatar-optimizer.editor.asmdef | 10 ++++++++++ Runtime/com.anatawa12.avatar-optimizer.runtime.asmdef | 10 ++++++++++ Test~/com.anatawa12.avatar-optimizer.test.asmdef | 10 ++++++++++ 3 files changed, 30 insertions(+) diff --git a/Editor/com.anatawa12.avatar-optimizer.editor.asmdef b/Editor/com.anatawa12.avatar-optimizer.editor.asmdef index 10cc54940..7dae84279 100644 --- a/Editor/com.anatawa12.avatar-optimizer.editor.asmdef +++ b/Editor/com.anatawa12.avatar-optimizer.editor.asmdef @@ -43,6 +43,16 @@ "name": "com.vrchat.avatars", "expression": "", "define": "AAO_VRCSDK3_AVATARS" + }, + { + "name": "com.vrmc.univrm", + "expression": "", + "define": "AAO_VRM0" + }, + { + "name": "com.vrmc.vrm", + "expression": "", + "define": "AAO_VRM1" } ], "noEngineReferences": false diff --git a/Runtime/com.anatawa12.avatar-optimizer.runtime.asmdef b/Runtime/com.anatawa12.avatar-optimizer.runtime.asmdef index 8a83ad091..c3414c1f5 100644 --- a/Runtime/com.anatawa12.avatar-optimizer.runtime.asmdef +++ b/Runtime/com.anatawa12.avatar-optimizer.runtime.asmdef @@ -24,6 +24,16 @@ "name": "com.vrchat.avatars", "expression": "", "define": "AAO_VRCSDK3_AVATARS" + }, + { + "name": "com.vrmc.univrm", + "expression": "", + "define": "AAO_VRM0" + }, + { + "name": "com.vrmc.vrm", + "expression": "", + "define": "AAO_VRM1" } ], "noEngineReferences": false diff --git a/Test~/com.anatawa12.avatar-optimizer.test.asmdef b/Test~/com.anatawa12.avatar-optimizer.test.asmdef index 0a3eb57ee..023d6db02 100644 --- a/Test~/com.anatawa12.avatar-optimizer.test.asmdef +++ b/Test~/com.anatawa12.avatar-optimizer.test.asmdef @@ -32,6 +32,16 @@ "name": "com.vrchat.avatars", "expression": "", "define": "AAO_VRCSDK3_AVATARS" + }, + { + "name": "com.vrmc.univrm", + "expression": "", + "define": "AAO_VRM0" + }, + { + "name": "com.vrmc.vrm", + "expression": "", + "define": "AAO_VRM1" } ], "noEngineReferences": false From 6c6aad611d61a3bdbc31fda025f6498c43185ab0 Mon Sep 17 00:00:00 2001 From: kaikoga Date: Fri, 20 Oct 2023 04:42:48 +0900 Subject: [PATCH 37/61] chore: add VRM asmdef references --- Editor/com.anatawa12.avatar-optimizer.editor.asmdef | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Editor/com.anatawa12.avatar-optimizer.editor.asmdef b/Editor/com.anatawa12.avatar-optimizer.editor.asmdef index 7dae84279..c7d9e51af 100644 --- a/Editor/com.anatawa12.avatar-optimizer.editor.asmdef +++ b/Editor/com.anatawa12.avatar-optimizer.editor.asmdef @@ -12,7 +12,10 @@ "Unity.Burst", "nadena.dev.ndmf", "nadena.dev.ndmf.runtime", - "com.anatawa12.avatar-optimizer.api.editor" + "com.anatawa12.avatar-optimizer.api.editor", + "UniHumanoid", + "VRM", + "VRM10" ], "includePlatforms": [ "Editor" From 6b0590b9c0a948acdfc615b621a0075a4a60f1a2 Mon Sep 17 00:00:00 2001 From: kaikoga Date: Sun, 29 Oct 2023 11:13:35 +0900 Subject: [PATCH 38/61] feat: add VRM component information --- Editor/APIInternal/ComponentInfos.VRM0.cs | 126 +++++++++++ .../APIInternal/ComponentInfos.VRM0.cs.meta | 3 + Editor/APIInternal/ComponentInfos.VRM1.cs | 199 ++++++++++++++++++ .../APIInternal/ComponentInfos.VRM1.cs.meta | 3 + 4 files changed, 331 insertions(+) create mode 100644 Editor/APIInternal/ComponentInfos.VRM0.cs create mode 100644 Editor/APIInternal/ComponentInfos.VRM0.cs.meta create mode 100644 Editor/APIInternal/ComponentInfos.VRM1.cs create mode 100644 Editor/APIInternal/ComponentInfos.VRM1.cs.meta diff --git a/Editor/APIInternal/ComponentInfos.VRM0.cs b/Editor/APIInternal/ComponentInfos.VRM0.cs new file mode 100644 index 000000000..e21bf3907 --- /dev/null +++ b/Editor/APIInternal/ComponentInfos.VRM0.cs @@ -0,0 +1,126 @@ +#if AAO_VRM0 + +using Anatawa12.AvatarOptimizer.API; +using UnityEngine; +using VRM; + +namespace Anatawa12.AvatarOptimizer.APIInternal +{ + + // NOTE: VRM0 bones are not animated, therefore no need to configure ComponentDependencyInfo + + [ComponentInformation(typeof(VRMMeta))] + internal class VRMMetaInformation : ComponentInformation + { + protected override void CollectDependency(VRMMeta component, ComponentDependencyCollector collector) + { + collector.MarkEntrypoint(); + } + } + + [ComponentInformation(typeof(VRMSpringBone))] + internal class VRMSpringBoneInformation : ComponentInformation + { + protected override void CollectDependency(VRMSpringBone component, ComponentDependencyCollector collector) + { + collector.MarkHeavyBehaviour(); + foreach (var transform in component.GetComponentsInChildren()) collector.AddDependency(transform); + foreach (var collider in component.ColliderGroups) collector.AddDependency(collider); + } + + protected override void CollectMutations(VRMSpringBone component, ComponentMutationsCollector collector) + { + foreach (var transform in component.GetComponentsInChildren()) + collector.TransformPositionAndRotation(transform); + } + } + + [ComponentInformation(typeof(VRMSpringBoneColliderGroup))] + internal class VRMSpringBoneColliderGroupInformation : ComponentInformation + { + protected override void CollectDependency(VRMSpringBoneColliderGroup component, + ComponentDependencyCollector collector) + { + } + } + + [ComponentInformation(typeof(VRMBlendShapeProxy))] + internal class VRMBlendShapeProxyInformation : ComponentInformation + { + protected override void CollectDependency(VRMBlendShapeProxy component, ComponentDependencyCollector collector) + { + var avatarRootTransform = component.transform; + + collector.MarkHeavyBehaviour(); + foreach (var clip in component.BlendShapeAvatar.Clips) + { + foreach (var binding in clip.Values) + { + var target = avatarRootTransform.Find(binding.RelativePath); + collector.AddDependency(target, component); + collector.AddDependency(target); + } + foreach (var materialBinding in clip.MaterialValues) + { + // TODO: I don't know what to do with BlendShape materials, so I pretend material names does not change (ex. MergeToonLitMaterial) + } + } + } + } + + [ComponentInformation(typeof(VRMLookAtHead))] + internal class VRMLookAtHeadInformation : ComponentInformation + { + protected override void CollectDependency(VRMLookAtHead component, ComponentDependencyCollector collector) + { + collector.MarkHeavyBehaviour(); + collector.AddDependency(component.Head, component); + collector.AddDependency(component.Head); + } + } + + [ComponentInformation(typeof(VRMLookAtBoneApplyer))] + internal class VRMLookAtBoneApplyerInformation : ComponentInformation + { + protected override void CollectDependency(VRMLookAtBoneApplyer component, ComponentDependencyCollector collector) + { + collector.MarkHeavyBehaviour(); + collector.AddDependency(component.GetComponent()); + collector.AddDependency(component.LeftEye.Transform); + collector.AddDependency(component.RightEye.Transform); + } + + protected override void CollectMutations(VRMLookAtBoneApplyer component, ComponentMutationsCollector collector) + { + collector.TransformRotation(component.LeftEye.Transform); + collector.TransformRotation(component.RightEye.Transform); + } + } + + + [ComponentInformation(typeof(VRMLookAtBlendShapeApplyer))] + internal class VRMLookAtBlendShapeApplyerInformation : ComponentInformation + { + protected override void CollectDependency(VRMLookAtBlendShapeApplyer component, ComponentDependencyCollector collector) + { + collector.MarkHeavyBehaviour(); + collector.AddDependency(component.GetComponent()); + } + + } + + + [ComponentInformation(typeof(VRMFirstPerson))] + internal class VRMFirstPersonInformation : ComponentInformation + { + protected override void CollectDependency(VRMFirstPerson component, ComponentDependencyCollector collector) + { + collector.MarkHeavyBehaviour(); + collector.AddDependency(component.FirstPersonBone, component); + collector.AddDependency(component.FirstPersonBone); + } + } + +} + +#endif \ No newline at end of file diff --git a/Editor/APIInternal/ComponentInfos.VRM0.cs.meta b/Editor/APIInternal/ComponentInfos.VRM0.cs.meta new file mode 100644 index 000000000..8f4538747 --- /dev/null +++ b/Editor/APIInternal/ComponentInfos.VRM0.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 82842faa704e42cca084ea181df31a3e +timeCreated: 1698426374 \ No newline at end of file diff --git a/Editor/APIInternal/ComponentInfos.VRM1.cs b/Editor/APIInternal/ComponentInfos.VRM1.cs new file mode 100644 index 000000000..df82e6a59 --- /dev/null +++ b/Editor/APIInternal/ComponentInfos.VRM1.cs @@ -0,0 +1,199 @@ +#if AAO_VRM1 + +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Anatawa12.AvatarOptimizer.API; +using UniGLTF.Extensions.VRMC_vrm; +using UnityEngine; +using UniVRM10; +using Humanoid = UniHumanoid.Humanoid; + +namespace Anatawa12.AvatarOptimizer.APIInternal +{ + + // NOTE: VRM1 bones are not animated their enabled states, therefore no need to configure ComponentDependencyInfo + + [ComponentInformation(typeof(Vrm10Instance))] + internal class Vrm10InstanceInformation : ComponentInformation + { + protected override void CollectDependency(Vrm10Instance component, ComponentDependencyCollector collector) + { + var avatarRootTransform = component.transform; + + collector.MarkEntrypoint(); + + // SpringBones + + foreach (var spring in component.SpringBone.Springs) + { + foreach (var joint in spring.Joints) collector.AddDependency(joint); + foreach (var collider in spring.ColliderGroups) collector.AddDependency(collider); + } + + // Expressions + + foreach (var clip in component.Vrm.Expression.Clips.Select(c => c.Clip)) + { + foreach (var binding in clip.MorphTargetBindings) + { + var target = avatarRootTransform.Find(binding.RelativePath); + collector.AddDependency(target, component); + collector.AddDependency(target); + } + foreach (var materialUVBinding in clip.MaterialUVBindings) + { + // TODO: I don't know what to do with BlendShape materials, so I pretend material names does not change (ex. MergeToonLitMaterial) + } + foreach (var materialColorBinding in clip.MaterialColorBindings) + { + // TODO: I don't know what to do with BlendShape materials, so I pretend material names does not change (ex. MergeToonLitMaterial) + } + } + + // First Person and LookAt + // NOTE: these dependencies are satisfied by either Animator or Humanoid + // collector.AddDependency(GetBoneTransformForVrm10(component, HumanBodyBones.Head)); + + // if (component.Vrm.LookAt.LookAtType == LookAtType.bone) + // { + // if (GetBoneTransformForVrm10(component, HumanBodyBones.LeftEye) is Transform leftEye) + // { + // collector.AddDependency(leftEye); + // } + // if (GetBoneTransformForVrm10(component, HumanBodyBones.RightEye) is Transform rightEye) + // { + // collector.AddDependency(rightEye); + // } + // } + } + + protected override void CollectMutations(Vrm10Instance component, ComponentMutationsCollector collector) + { + // SpringBones + foreach (var joint in component.SpringBone.Springs.SelectMany(spring => spring.Joints)) + { + collector.TransformPositionAndRotation(joint.transform); + } + + // Expressions + + // First Person and LookAt + if (component.Vrm.LookAt.LookAtType == LookAtType.bone) + { + if (GetBoneTransformForVrm10(component, HumanBodyBones.LeftEye) is Transform leftEye) + { + collector.TransformRotation(leftEye); + } + if (GetBoneTransformForVrm10(component, HumanBodyBones.RightEye) is Transform rightEye) + { + collector.TransformRotation(rightEye); + } + } + } + + Transform GetBoneTransformForVrm10(Vrm10Instance component, HumanBodyBones bones) + { + if (component.GetComponent() is Humanoid avatarHumanoid) + { + return avatarHumanoid.GetBoneTransform(bones); + } + + if (component.GetComponent() is Animator avatarAnimator) + { + return avatarAnimator.GetBoneTransform(bones); + } + + return null; + } + } + + [ComponentInformation(typeof(VRM10SpringBoneColliderGroup))] + internal class Vrm10SpringBoneColliderGroupInformation : ComponentInformation + { + protected override void CollectDependency(VRM10SpringBoneColliderGroup component, + ComponentDependencyCollector collector) + { + foreach (var collider in component.Colliders) collector.AddDependency(collider); + } + } + + [ComponentInformation(typeof(VRM10SpringBoneJoint))] + [ComponentInformation(typeof(VRM10SpringBoneCollider))] + internal class Vrm10ReferenceHolderInformation : ComponentInformation + { + protected override void CollectDependency(Component component, + ComponentDependencyCollector collector) + { + } + } + + [ComponentInformation(typeof(Vrm10AimConstraint))] + internal class Vrm10AimConstraintInformation : ComponentInformation + { + protected override void CollectDependency(Vrm10AimConstraint component, + ComponentDependencyCollector collector) + { + collector.MarkHeavyBehaviour(); + collector.AddDependency(component.transform, component.Source); + } + + protected override void CollectMutations(Vrm10AimConstraint component, ComponentMutationsCollector collector) + { + collector.TransformRotation(component.transform); + } + } + + [ComponentInformation(typeof(Vrm10RollConstraint))] + internal class Vrm10RollConstraintInformation : ComponentInformation + { + protected override void CollectDependency(Vrm10RollConstraint component, + ComponentDependencyCollector collector) + { + collector.MarkHeavyBehaviour(); + collector.AddDependency(component.transform, component.Source); + } + + protected override void CollectMutations(Vrm10RollConstraint component, ComponentMutationsCollector collector) + { + collector.TransformRotation(component.transform); + } + } + + [ComponentInformation(typeof(Vrm10RotationConstraint))] + internal class Vrm10RotationConstraintInformation : ComponentInformation + { + protected override void CollectDependency(Vrm10RotationConstraint component, + ComponentDependencyCollector collector) + { + collector.MarkHeavyBehaviour(); + collector.AddDependency(component.transform, component.Source); + } + + protected override void CollectMutations(Vrm10RotationConstraint component, ComponentMutationsCollector collector) + { + collector.TransformRotation(component.transform); + } + } + + [ComponentInformation(typeof(Humanoid))] + internal class HumanoidInformation : ComponentInformation + { + protected override void CollectDependency(Humanoid component, ComponentDependencyCollector collector) + { + // VRM1 Humanoid has side effect because it overwrites Animator's Avatar on VRM1 export + collector.MarkEntrypoint(); + + // Use reflection to support UniVRM 0.99.4 + var boneMapProperty = typeof(Humanoid).GetProperty("BoneMap", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + var boneMap = (IEnumerable<(Transform, HumanBodyBones)>)boneMapProperty.GetValue(component); + foreach ((Transform transform, HumanBodyBones) bone in boneMap) + { + if (bone.transform) collector.AddDependency(bone.transform); + } + } + } + +} + +#endif \ No newline at end of file diff --git a/Editor/APIInternal/ComponentInfos.VRM1.cs.meta b/Editor/APIInternal/ComponentInfos.VRM1.cs.meta new file mode 100644 index 000000000..ff06b75c8 --- /dev/null +++ b/Editor/APIInternal/ComponentInfos.VRM1.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: cda357a894554e8bb67cb0fd3cd02659 +timeCreated: 1698673843 \ No newline at end of file From 463d9c1cb0c46c2f7435b0fbe3e0d9dc61483609 Mon Sep 17 00:00:00 2001 From: kaikoga Date: Sat, 28 Oct 2023 21:19:37 +0900 Subject: [PATCH 39/61] feat: mark VRM0 BlendShapeClip / VRM1 Expression BlendShapes --- Editor/AnimatorParsers/AnimatorParser.cs | 41 +++++++ .../TraceAndOptimize/AutoFreezeBlendShape.cs | 100 ++++++++++++------ 2 files changed, 109 insertions(+), 32 deletions(-) diff --git a/Editor/AnimatorParsers/AnimatorParser.cs b/Editor/AnimatorParsers/AnimatorParser.cs index abef9721d..579632c08 100644 --- a/Editor/AnimatorParsers/AnimatorParser.cs +++ b/Editor/AnimatorParsers/AnimatorParser.cs @@ -174,6 +174,18 @@ private IModificationsContainer CollectAvatarRootAnimatorModifications(BuildCont if (descriptor) CollectAvatarDescriptorModifications(modificationsContainer, descriptor); #endif + +#if AAO_VRM0 + var blendShapeProxy = session.AvatarRootObject.GetComponent(); + if (blendShapeProxy) + CollectBlendShapeProxyModifications(session, modificationsContainer, blendShapeProxy); +#endif + +#if AAO_VRM1 + var vrm10Instance = session.AvatarRootObject.GetComponent(); + if (vrm10Instance) + CollectVrm10InstanceModifications(session, modificationsContainer, vrm10Instance); +#endif return modificationsContainer; } @@ -356,6 +368,35 @@ private static RuntimeAnimatorController GetPlayableLayerController(VRCAvatarDes } #endif +#if AAO_VRM0 + private void CollectBlendShapeProxyModifications(BuildContext context, ModificationsContainer modificationsContainer, VRM.VRMBlendShapeProxy vrmBlendShapeProxy) + { + + var bindings = vrmBlendShapeProxy.BlendShapeAvatar.Clips.SelectMany(clip => clip.Values); + foreach (var binding in bindings) + { + var skinnedMeshRenderer = context.AvatarRootTransform.Find(binding.RelativePath).GetComponent(); + var blendShapePropName = $"blendShape.{skinnedMeshRenderer.sharedMesh.GetBlendShapeName(binding.Index)}"; + modificationsContainer.ModifyObject(skinnedMeshRenderer) + .AddModificationAsNewLayer(blendShapePropName, AnimationFloatProperty.Variable(vrmBlendShapeProxy)); + } + } +#endif + +#if AAO_VRM1 + private void CollectVrm10InstanceModifications(BuildContext context, ModificationsContainer modificationsContainer, UniVRM10.Vrm10Instance vrm10Instance) + { + var bindings = vrm10Instance.Vrm.Expression.Clips.SelectMany(clip => clip.Clip.MorphTargetBindings); + foreach (var binding in bindings) + { + var skinnedMeshRenderer = context.AvatarRootTransform.Find(binding.RelativePath).GetComponent(); + var blendShapePropName = $"blendShape.{skinnedMeshRenderer.sharedMesh.GetBlendShapeName(binding.Index)}"; + modificationsContainer.ModifyObject(skinnedMeshRenderer) + .AddModificationAsNewLayer(blendShapePropName, AnimationFloatProperty.Variable(vrm10Instance)); + } + } +#endif + #endregion #region Animator diff --git a/Editor/Processors/TraceAndOptimize/AutoFreezeBlendShape.cs b/Editor/Processors/TraceAndOptimize/AutoFreezeBlendShape.cs index 07dba60af..05e4d72f3 100644 --- a/Editor/Processors/TraceAndOptimize/AutoFreezeBlendShape.cs +++ b/Editor/Processors/TraceAndOptimize/AutoFreezeBlendShape.cs @@ -96,52 +96,88 @@ private void ComputePreserveBlendShapes(BuildContext context, Dictionary()); - set.UnionWith(descriptor.VisemeBlendShapes); - break; + case VRC_AvatarDescriptor.LipSyncStyle.VisemeBlendShape when descriptor.VisemeSkinnedMesh != null: + { + var skinnedMeshRenderer = descriptor.VisemeSkinnedMesh; + if (!preserveBlendShapes.TryGetValue(skinnedMeshRenderer, out var set)) + preserveBlendShapes.Add(skinnedMeshRenderer, set = new HashSet()); + set.UnionWith(descriptor.VisemeBlendShapes); + break; + } + case VRC_AvatarDescriptor.LipSyncStyle.JawFlapBlendShape when descriptor.VisemeSkinnedMesh != null: + { + var skinnedMeshRenderer = descriptor.VisemeSkinnedMesh; + if (!preserveBlendShapes.TryGetValue(skinnedMeshRenderer, out var set)) + preserveBlendShapes.Add(skinnedMeshRenderer, set = new HashSet()); + set.Add(descriptor.MouthOpenBlendShapeName); + break; + } } - case VRC_AvatarDescriptor.LipSyncStyle.JawFlapBlendShape when descriptor.VisemeSkinnedMesh != null: + + if (descriptor.enableEyeLook) { - var skinnedMeshRenderer = descriptor.VisemeSkinnedMesh; + switch (descriptor.customEyeLookSettings.eyelidType) + { + case VRCAvatarDescriptor.EyelidType.None: + break; + case VRCAvatarDescriptor.EyelidType.Bones: + break; + case VRCAvatarDescriptor.EyelidType.Blendshapes + when descriptor.customEyeLookSettings.eyelidsSkinnedMesh != null: + { + var skinnedMeshRenderer = descriptor.customEyeLookSettings.eyelidsSkinnedMesh; + if (!preserveBlendShapes.TryGetValue(skinnedMeshRenderer, out var set)) + preserveBlendShapes.Add(skinnedMeshRenderer, set = new HashSet()); + + var mesh = skinnedMeshRenderer.sharedMesh; + set.UnionWith( + from index in descriptor.customEyeLookSettings.eyelidsBlendshapes + where 0 <= index && index < mesh.blendShapeCount + select mesh.GetBlendShapeName(index) + ); + } + break; + } + } + } +#endif + +#if AAO_VRM0 + var vrmBlendShapeProxy = context.AvatarRootTransform.GetComponent(); + if (vrmBlendShapeProxy) + { + var bindings = vrmBlendShapeProxy.BlendShapeAvatar.Clips.SelectMany(clip => clip.Values); + foreach (var binding in bindings) + { + var skinnedMeshRenderer = context.AvatarRootTransform.Find(binding.RelativePath).GetComponent(); if (!preserveBlendShapes.TryGetValue(skinnedMeshRenderer, out var set)) preserveBlendShapes.Add(skinnedMeshRenderer, set = new HashSet()); - set.Add(descriptor.MouthOpenBlendShapeName); - break; + var blendShapePropName = $"blendShape.{skinnedMeshRenderer.sharedMesh.GetBlendShapeName(binding.Index)}"; + set.Add(blendShapePropName); } } +#endif - if (descriptor.enableEyeLook) +#if AAO_VRM1 + var vrm10Instance = context.AvatarRootTransform.GetComponent(); + if (vrm10Instance) { - switch (descriptor.customEyeLookSettings.eyelidType) + var bindings = vrm10Instance.Vrm.Expression.Clips.SelectMany(clip => clip.Clip.MorphTargetBindings); + foreach (var binding in bindings) { - case VRCAvatarDescriptor.EyelidType.None: - break; - case VRCAvatarDescriptor.EyelidType.Bones: - break; - case VRCAvatarDescriptor.EyelidType.Blendshapes - when descriptor.customEyeLookSettings.eyelidsSkinnedMesh != null: - { - var skinnedMeshRenderer = descriptor.customEyeLookSettings.eyelidsSkinnedMesh; - if (!preserveBlendShapes.TryGetValue(skinnedMeshRenderer, out var set)) - preserveBlendShapes.Add(skinnedMeshRenderer, set = new HashSet()); - - var mesh = skinnedMeshRenderer.sharedMesh; - set.UnionWith( - from index in descriptor.customEyeLookSettings.eyelidsBlendshapes - where 0 <= index && index < mesh.blendShapeCount - select mesh.GetBlendShapeName(index) - ); - } - break; + var skinnedMeshRenderer = context.AvatarRootTransform.Find(binding.RelativePath).GetComponent(); + if (!preserveBlendShapes.TryGetValue(skinnedMeshRenderer, out var set)) + preserveBlendShapes.Add(skinnedMeshRenderer, set = new HashSet()); + var blendShapePropName = $"blendShape.{skinnedMeshRenderer.sharedMesh.GetBlendShapeName(binding.Index)}"; + set.Add(blendShapePropName); } } #endif } + } } From f70a23d060eb200fd3710352b229f1cb853d693e Mon Sep 17 00:00:00 2001 From: kaikoga Date: Sun, 29 Oct 2023 07:53:42 +0900 Subject: [PATCH 40/61] feat: apply VRM0 BlendShapeClip / VRM1 Expression BlendShape mappings --- Editor/ObjectMapping/AnimationObjectMapper.cs | 27 ++++ Editor/ObjectMapping/ObjectMappingContext.cs | 144 ++++++++++++++++-- 2 files changed, 162 insertions(+), 9 deletions(-) diff --git a/Editor/ObjectMapping/AnimationObjectMapper.cs b/Editor/ObjectMapping/AnimationObjectMapper.cs index e3b01b2bb..354192276 100644 --- a/Editor/ObjectMapping/AnimationObjectMapper.cs +++ b/Editor/ObjectMapping/AnimationObjectMapper.cs @@ -115,6 +115,33 @@ public string MapPath(string srcPath, Type type) } } + [CanBeNull] + public string MapPropertyName(string srcPath, string propertyName, Type type) + { + var gameObjectInfo = GetGameObjectInfo(srcPath); + if (gameObjectInfo == null) return srcPath; + var (instanceId, componentInfo) = gameObjectInfo.GetComponentByType(type); + + if (componentInfo != null) + { + // there's mapping about component. + // this means the component is merged or some prop has mapping + + if (componentInfo.PropertyMapping.TryGetValue(propertyName, out var newProp)) + { + return newProp.MappedProperty.Name; + } + else + { + return propertyName; + } + } + else + { + return propertyName; + } + } + [CanBeNull] public EditorCurveBinding[] MapBinding(EditorCurveBinding binding) { diff --git a/Editor/ObjectMapping/ObjectMappingContext.cs b/Editor/ObjectMapping/ObjectMappingContext.cs index b5846c292..020d5c8ec 100644 --- a/Editor/ObjectMapping/ObjectMappingContext.cs +++ b/Editor/ObjectMapping/ObjectMappingContext.cs @@ -44,16 +44,50 @@ public void OnDeactivate(BuildContext context) if (mapping.MapComponentInstance(p.objectReferenceInstanceIDValue, out var mappedComponent)) p.objectReferenceValue = mappedComponent; - if (p.objectReferenceValue is RuntimeAnimatorController controller) + switch (p.objectReferenceValue) { - if (mapper == null) - mapper = new AnimatorControllerMapper(mapping.CreateAnimationMapper(component.gameObject)); - - // ReSharper disable once AccessToModifiedClosure - var mapped = BuildReport.ReportingObject(controller, - () => mapper.MapAnimatorController(controller)); - if (mapped != controller) - p.objectReferenceValue = mapped; + case RuntimeAnimatorController controller: + { + if (mapper == null) + mapper = new AnimatorControllerMapper(mapping.CreateAnimationMapper(component.gameObject)); + + // ReSharper disable once AccessToModifiedClosure + var mapped = BuildReport.ReportingObject(controller, + () => mapper.MapAnimatorController(controller)); + if (mapped != controller) + p.objectReferenceValue = mapped; + break; + } + +#if AAO_VRM0 + case VRM.BlendShapeAvatar blendShapeAvatar: + { + if (mapper == null) + mapper = new AnimatorControllerMapper(mapping.CreateAnimationMapper(component.gameObject)); + + // ReSharper disable once AccessToModifiedClosure + var mapped = BuildReport.ReportingObject(blendShapeAvatar, + () => mapper.MapBlendShapeAvatar(blendShapeAvatar)); + if (mapped != blendShapeAvatar) + p.objectReferenceValue = mapped; + break; + } +#endif + +#if AAO_VRM1 + case UniVRM10.VRM10Object vrm10Object: + { + if (mapper == null) + mapper = new AnimatorControllerMapper(mapping.CreateAnimationMapper(component.gameObject)); + + // ReSharper disable once AccessToModifiedClosure + var mapped = BuildReport.ReportingObject(vrm10Object, + () => mapper.MapVrm10Object(vrm10Object)); + if (mapped != vrm10Object) + p.objectReferenceValue = mapped; + break; + } +#endif } } @@ -135,6 +169,16 @@ public AnimatorControllerMapper(AnimationObjectMapper mapping) public T MapAnimatorController(T controller) where T : RuntimeAnimatorController => DeepClone(controller, CustomClone); +#if AAO_VRM0 + public T MapBlendShapeAvatar(T blendShapeAvatar) where T : VRM.BlendShapeAvatar => + DeepClone(blendShapeAvatar, CustomClone); +#endif + +#if AAO_VRM1 + public T MapVrm10Object(T vrm10Object) where T : UniVRM10.VRM10Object => + DeepClone(vrm10Object, CustomClone); +#endif + // https://github.com/bdunderscore/modular-avatar/blob/db49e2e210bc070671af963ff89df853ae4514a5/Packages/nadena.dev.modular-avatar/Editor/AnimatorMerger.cs#L199-L241 // Originally under MIT License // Copyright (c) 2022 bd_ @@ -238,6 +282,48 @@ private Object CustomClone(Object o) return newMask; } +#if AAO_VRM0 + else if (o is VRM.BlendShapeClip blendShapeClip) + { + var newBlendShapeClip = DefaultDeepClone(blendShapeClip, CustomClone); + newBlendShapeClip.Prefab = null; // This likely to point prefab before mapping, which is invalid by now + newBlendShapeClip.name = "rebased " + blendShapeClip.name; + newBlendShapeClip.Values = newBlendShapeClip.Values.Select(binding => + { + var propertyName = VProp.BlendShapeIndex(binding.Index); + var mappedPropertyName = _mapping.MapPropertyName(binding.RelativePath, propertyName, typeof(SkinnedMeshRenderer)); + _mapped = true; + return new VRM.BlendShapeBinding + { + RelativePath = _mapping.MapPath(binding.RelativePath, typeof(SkinnedMeshRenderer)), + Index = VProp.ParseBlendShapeIndex(mappedPropertyName), + Weight = binding.Weight + }; + }).ToArray(); + return newBlendShapeClip; + } +#endif +#if AAO_VRM1 + else if (o is UniVRM10.VRM10Expression vrm10Expression) + { + var newVrm10Expression = DefaultDeepClone(vrm10Expression, CustomClone); + newVrm10Expression.Prefab = null; // This likely to point prefab before mapping, which is invalid by now + newVrm10Expression.name = "rebased " + vrm10Expression.name; + newVrm10Expression.MorphTargetBindings = newVrm10Expression.MorphTargetBindings.Select(binding => + { + var propertyName = VProp.BlendShapeIndex(binding.Index); + var mappedPropertyName = _mapping.MapPropertyName(binding.RelativePath, propertyName, typeof(SkinnedMeshRenderer)); + _mapped = true; + return new UniVRM10.MorphTargetBinding + { + RelativePath = _mapping.MapPath(binding.RelativePath, typeof(SkinnedMeshRenderer)), + Index = VProp.ParseBlendShapeIndex(mappedPropertyName), + Weight = binding.Weight + }; + }).ToArray(); + return newVrm10Expression; + } +#endif else if (o is RuntimeAnimatorController controller) { using (new MappedScope(this)) @@ -249,6 +335,32 @@ private Object CustomClone(Object o) return newController; } } +#if AAO_VRM0 + else if (o is VRM.BlendShapeAvatar blendShapeAvatar) + { + using (new MappedScope(this)) + { + var newBlendShapeAvatar = DefaultDeepClone(blendShapeAvatar, CustomClone); + newBlendShapeAvatar.name = blendShapeAvatar.name + " (rebased)"; + if (!_mapped) newBlendShapeAvatar = blendShapeAvatar; + _cache[blendShapeAvatar] = newBlendShapeAvatar; + return newBlendShapeAvatar; + } + } +#endif +#if AAO_VRM1 + else if (o is UniVRM10.VRM10Object vrm10Object) + { + using (new MappedScope(this)) + { + var newVrm10Object = DefaultDeepClone(vrm10Object, CustomClone); + newVrm10Object.name = vrm10Object.name + " (rebased)"; + if (!_mapped) newVrm10Object = vrm10Object; + _cache[vrm10Object] = newVrm10Object; + return newVrm10Object; + } + } +#endif else { return null; @@ -295,10 +407,24 @@ private T DeepClone(T original, Func visitor) where T : Objec case AvatarMask _: break; // We want to clone these types + // also handle VRM objects here +#if AAO_VRM0 + case VRM.BlendShapeAvatar _: + case VRM.BlendShapeClip _: + break; // We want to clone these types +#endif + +#if AAO_VRM1 + case UniVRM10.VRM10Object _: + case UniVRM10.VRM10Expression _: + break; // We want to clone these types +#endif + // Leave textures, materials, and script definitions alone case Texture _: case MonoScript _: case Material _: + case GameObject _: return original; // Also avoid copying unknown scriptable objects. From d7b66332ba5f2d430d175d734952a44a842c7454 Mon Sep 17 00:00:00 2001 From: kaikoga Date: Sat, 4 Nov 2023 13:23:16 +0900 Subject: [PATCH 41/61] feat: compact VRM0 FirstPersonFlags --- Editor/APIInternal/ComponentInfos.VRM0.cs | 25 ++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/Editor/APIInternal/ComponentInfos.VRM0.cs b/Editor/APIInternal/ComponentInfos.VRM0.cs index e21bf3907..aa5106609 100644 --- a/Editor/APIInternal/ComponentInfos.VRM0.cs +++ b/Editor/APIInternal/ComponentInfos.VRM0.cs @@ -1,5 +1,6 @@ #if AAO_VRM0 +using System.Linq; using Anatawa12.AvatarOptimizer.API; using UnityEngine; using VRM; @@ -119,8 +120,30 @@ protected override void CollectDependency(VRMFirstPerson component, ComponentDep collector.AddDependency(component.FirstPersonBone, component); collector.AddDependency(component.FirstPersonBone); } + + protected override void ApplySpecialMapping(VRMFirstPerson component, MappingSource mappingSource) + { + component.Renderers = component.Renderers + .Select(r => new VRMFirstPerson.RendererFirstPersonFlags + { + Renderer = mappingSource.GetMappedComponent(r.Renderer).MappedComponent, + FirstPersonFlag = r.FirstPersonFlag + }) + .Where(r => r.Renderer) + .GroupBy(r => r.Renderer, r => r.FirstPersonFlag) + .Select(grouping => + { + var firstPersonFlags = grouping.Distinct().ToArray(); + return new VRMFirstPerson.RendererFirstPersonFlags + { + Renderer = grouping.Key, + FirstPersonFlag = firstPersonFlags.Length == 1 ? firstPersonFlags[0] : + firstPersonFlags.Contains(FirstPersonFlag.Both) ? FirstPersonFlag.Both : + FirstPersonFlag.Auto + }; + }).ToList(); + } } - } #endif \ No newline at end of file From 7142093657e2bdf1add9dd09d458b0afa56920ba Mon Sep 17 00:00:00 2001 From: kaikoga Date: Sat, 4 Nov 2023 13:51:26 +0900 Subject: [PATCH 42/61] chore: seal ApplySpecialMappingInternal() --- API-Editor/ComponentInformation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API-Editor/ComponentInformation.cs b/API-Editor/ComponentInformation.cs index bafa006c1..41099d7c9 100644 --- a/API-Editor/ComponentInformation.cs +++ b/API-Editor/ComponentInformation.cs @@ -48,7 +48,7 @@ internal sealed override void CollectMutationsInternal(Component component, ComponentMutationsCollector collector) => CollectMutations((TComponent)component, collector); - internal override void ApplySpecialMappingInternal(Component component, MappingSource collector) => + internal sealed override void ApplySpecialMappingInternal(Component component, MappingSource collector) => ApplySpecialMapping((TComponent)component, collector); /// From ac68c389622f461b139f366be41ea437b1051bf9 Mon Sep 17 00:00:00 2001 From: kaikoga Date: Sun, 5 Nov 2023 00:55:54 +0900 Subject: [PATCH 43/61] feat: compact VRM1 FirstPersonFlags --- Editor/ObjectMapping/ObjectMappingContext.cs | 21 ++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Editor/ObjectMapping/ObjectMappingContext.cs b/Editor/ObjectMapping/ObjectMappingContext.cs index 020d5c8ec..8b8b3a98e 100644 --- a/Editor/ObjectMapping/ObjectMappingContext.cs +++ b/Editor/ObjectMapping/ObjectMappingContext.cs @@ -357,6 +357,27 @@ private Object CustomClone(Object o) newVrm10Object.name = vrm10Object.name + " (rebased)"; if (!_mapped) newVrm10Object = vrm10Object; _cache[vrm10Object] = newVrm10Object; + + newVrm10Object.FirstPerson.Renderers = newVrm10Object.FirstPerson.Renderers + .Select(r => new UniVRM10.RendererFirstPersonFlags + { + Renderer = _mapping.MapPath(r.Renderer, typeof(Renderer)), + FirstPersonFlag = r.FirstPersonFlag + }) + .Where(r => r.Renderer != null) + .GroupBy(r => r.Renderer, r => r.FirstPersonFlag) + .Select(grouping => + { + var firstPersonFlags = grouping.Distinct().ToArray(); + return new UniVRM10.RendererFirstPersonFlags + { + Renderer = grouping.Key, + FirstPersonFlag = firstPersonFlags.Length == 1 ? firstPersonFlags[0] : + firstPersonFlags.Contains(UniGLTF.Extensions.VRMC_vrm.FirstPersonType.both) ? UniGLTF.Extensions.VRMC_vrm.FirstPersonType.both : + UniGLTF.Extensions.VRMC_vrm.FirstPersonType.auto + }; + }).ToList(); + return newVrm10Object; } } From e186e45305ce97680b1a0b02b2405f1ac9e7b58a Mon Sep 17 00:00:00 2001 From: kaikoga Date: Tue, 31 Oct 2023 07:14:45 +0900 Subject: [PATCH 44/61] docs(changelog): Add support of UniVRM components --- CHANGELOG-PRERELEASE.md | 1 + CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG-PRERELEASE.md b/CHANGELOG-PRERELEASE.md index 1ecc230df..cc4a6d1a0 100644 --- a/CHANGELOG-PRERELEASE.md +++ b/CHANGELOG-PRERELEASE.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog]. ## [Unreleased] ### Added +- Add support of UniVRM components `#653` ### Changed diff --git a/CHANGELOG.md b/CHANGELOG.md index f30c5edfe..e6854e3a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog]. - If you toggles your clothes with simple toggle, PhysBones on the your avatar will also be toggled automatically! - Small performance improve `#641` - Ability to prevent changing enablement of component `#668` +- Add support of UniVRM components `#653` ### Changed - All logs passed to ErrorReport is now shown on the console log `#643` From 1795fcdf26733fc283bba759e7e0f40e2e80c966 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sun, 5 Nov 2023 01:08:08 +0900 Subject: [PATCH 45/61] feat(meshinfo2): basic support for non-triangle meshes --- Editor/Processors/SkinnedMeshes/MeshInfo2.cs | 86 +++++++++++--------- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/Editor/Processors/SkinnedMeshes/MeshInfo2.cs b/Editor/Processors/SkinnedMeshes/MeshInfo2.cs index 55051b66d..ea78f4f63 100644 --- a/Editor/Processors/SkinnedMeshes/MeshInfo2.cs +++ b/Editor/Processors/SkinnedMeshes/MeshInfo2.cs @@ -124,7 +124,7 @@ private void SetMaterials(Renderer renderer) public void AssertInvariantContract(string context) { var vertices = new HashSet(Vertices); - Debug.Assert(SubMeshes.SelectMany(x => x.Triangles).All(vertices.Contains), + Debug.Assert(SubMeshes.SelectMany(x => x.Vertices).All(vertices.Contains), $"{context}: some SubMesh has invalid triangles"); var bones = new HashSet(Bones); Debug.Assert(Vertices.SelectMany(x => x.BoneWeights).Select(x => x.bone).All(bones.Contains), @@ -336,11 +336,15 @@ public void ReadStaticMesh([NotNull] Mesh mesh) // ReSharper restore AccessToModifiedClosure } - var triangles = mesh.triangles; SubMeshes.Clear(); SubMeshes.Capacity = Math.Max(SubMeshes.Capacity, mesh.subMeshCount); + + var triangles = new List(); for (var i = 0; i < mesh.subMeshCount; i++) + { + mesh.GetIndices(triangles, i); SubMeshes.Add(new SubMesh(Vertices, triangles, mesh.GetSubMesh(i))); + } Profiler.EndSample(); } @@ -412,7 +416,7 @@ public void FlattenMultiPassRendering(string reasonComponent) SubMeshes.Clear(); foreach (var subMesh in subMeshes) foreach (var material in subMesh.SharedMaterials) - SubMeshes.Add(new SubMesh(subMesh.Triangles, material)); + SubMeshes.Add(new SubMesh(subMesh, material)); } public void WriteToMesh(Mesh destMesh) @@ -512,59 +516,50 @@ public void WriteToMesh(Mesh destMesh) for (var i = 0; i < Vertices.Count; i++) vertexIndices.Add(Vertices[i], i); - var totalTriangles = 0; + var maxIndices = 0; var totalSubMeshes = 0; for (var i = 0; i < SubMeshes.Count - 1; i++) { + maxIndices = Mathf.Max(maxIndices, SubMeshes[i].Vertices.Count); // for non-last submesh, we have to duplicate submesh for multi pass rendering for (var j = 0; j < SubMeshes[i].SharedMaterials.Length; j++) - { - totalTriangles += SubMeshes[i].Triangles.Count; totalSubMeshes++; - } } { + maxIndices = Mathf.Max(maxIndices, SubMeshes[SubMeshes.Count - 1].Vertices.Count); // for last submesh, we can use single submesh for multi pass reendering - totalTriangles += SubMeshes[SubMeshes.Count - 1].Triangles.Count; totalSubMeshes++; } - var triangles = new int[totalTriangles]; - var subMeshDescriptors = new SubMeshDescriptor[totalSubMeshes]; - var trianglesIndex = 0; + var indices = new int[maxIndices]; var submeshIndex = 0; + destMesh.indexFormat = Vertices.Count <= ushort.MaxValue ? IndexFormat.UInt16 : IndexFormat.UInt32; + destMesh.subMeshCount = totalSubMeshes; + for (var i = 0; i < SubMeshes.Count - 1; i++) { var subMesh = SubMeshes[i]; - var descriptor = new SubMeshDescriptor(trianglesIndex, subMesh.Triangles.Count); - foreach (var triangle in subMesh.Triangles) - triangles[trianglesIndex++] = vertexIndices[triangle]; + + for (var index = 0; index < subMesh.Vertices.Count; index++) + indices[index] = vertexIndices[subMesh.Vertices[index]]; // general case: for non-last submesh, we have to duplicate submesh for multi pass rendering for (var j = 0; j < subMesh.SharedMaterials.Length; j++) - subMeshDescriptors[submeshIndex++] = descriptor; + destMesh.SetIndices(indices, 0, subMesh.Vertices.Count, subMesh.Topology, submeshIndex++); } { var subMesh = SubMeshes[SubMeshes.Count - 1]; - var descriptor = new SubMeshDescriptor(trianglesIndex, subMesh.Triangles.Count); - foreach (var triangle in subMesh.Triangles) - triangles[trianglesIndex++] = vertexIndices[triangle]; + for (var index = 0; index < subMesh.Vertices.Count; index++) + indices[index] = vertexIndices[subMesh.Vertices[index]]; // for last submesh, we can use single submesh for multi pass reendering - subMeshDescriptors[submeshIndex++] = descriptor; + destMesh.SetIndices(indices, 0, subMesh.Vertices.Count, subMesh.Topology, submeshIndex++); } - Debug.Assert(subMeshDescriptors.Length == submeshIndex); - Debug.Assert(triangles.Length == trianglesIndex); - - destMesh.indexFormat = Vertices.Count <= ushort.MaxValue ? IndexFormat.UInt16 : IndexFormat.UInt32; - destMesh.triangles = triangles; - destMesh.subMeshCount = submeshIndex; - for (var i = 0; i < subMeshDescriptors.Length; i++) - destMesh.SetSubMesh(i, subMeshDescriptors[i]); + Debug.Assert(totalSubMeshes == submeshIndex); } Profiler.EndSample(); @@ -674,8 +669,19 @@ public void WriteToMeshRenderer(MeshRenderer targetRenderer) internal class SubMesh { + public readonly MeshTopology Topology = MeshTopology.Triangles; + // size of this must be 3 * n - public readonly List Triangles = new List(); + public List Triangles + { + get + { + Assert.AreEqual(MeshTopology.Triangles, Topology); + return Vertices; + } + } + + public List Vertices { get; } = new List(); public Material SharedMaterial { @@ -689,18 +695,24 @@ public SubMesh() { } - public SubMesh(List vertices) => Triangles = vertices; + public SubMesh(List vertices) => Vertices = vertices; public SubMesh(List vertices, Material sharedMaterial) => - (Triangles, SharedMaterial) = (vertices, sharedMaterial); - public SubMesh(Material sharedMaterial) => - SharedMaterial = sharedMaterial; + (Vertices, SharedMaterial) = (vertices, sharedMaterial); + public SubMesh(Material sharedMaterial) => SharedMaterial = sharedMaterial; + + public SubMesh(SubMesh subMesh, Material triangles) + { + Topology = subMesh.Topology; + Vertices = new List(subMesh.Vertices); + SharedMaterial = triangles; + } - public SubMesh(List vertices, ReadOnlySpan triangles, SubMeshDescriptor descriptor) + public SubMesh(List vertices, List triangles, SubMeshDescriptor descriptor) { - Assert.AreEqual(MeshTopology.Triangles, descriptor.topology); - Triangles.Capacity = descriptor.indexCount; - foreach (var i in triangles.Slice(descriptor.indexStart, descriptor.indexCount)) - Triangles.Add(vertices[i]); + Topology = descriptor.topology; + Vertices.Capacity = descriptor.indexCount; + foreach (var i in triangles) + Vertices.Add(vertices[i]); } } From 26112cdad69baa39f4c8cff4c7c44ca52fdbb9d6 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sun, 5 Nov 2023 02:25:24 +0900 Subject: [PATCH 46/61] feat: non-triangle support for RemoveMesh* components --- Editor/Processors/SkinnedMeshes/MeshInfo2.cs | 47 +++++++++++++++++++ .../RemoveMeshByBlendShapeProcessor.cs | 23 ++------- .../SkinnedMeshes/RemoveMeshInBoxProcessor.cs | 26 ++-------- Localization/en.po | 3 ++ Localization/ja.po | 3 ++ 5 files changed, 61 insertions(+), 41 deletions(-) diff --git a/Editor/Processors/SkinnedMeshes/MeshInfo2.cs b/Editor/Processors/SkinnedMeshes/MeshInfo2.cs index ea78f4f63..fe0dec4a6 100644 --- a/Editor/Processors/SkinnedMeshes/MeshInfo2.cs +++ b/Editor/Processors/SkinnedMeshes/MeshInfo2.cs @@ -714,6 +714,53 @@ public SubMesh(List vertices, List triangles, SubMeshDescriptor des foreach (var i in triangles) Vertices.Add(vertices[i]); } + + public bool TryGetPrimitiveSize(string component, out int primitiveSize) + { + switch (Topology) + { + case MeshTopology.Triangles: + primitiveSize = 3; + return true; + case MeshTopology.Quads: + primitiveSize = 4; + return true; + case MeshTopology.Lines: + primitiveSize = 2; + return true; + case MeshTopology.Points: + primitiveSize = 1; + return true; + case MeshTopology.LineStrip: + BuildReport.LogWarning("MeshInfo2:warning:lineStrip", component); + primitiveSize = default; + return false; + default: + throw new ArgumentOutOfRangeException(); + } + } + + public void RemovePrimitives(string component, Func condition) + { + if (!TryGetPrimitiveSize(component, out var primitiveSize)) + return; + var primitiveBuffer = new Vertex[primitiveSize]; + int srcI = 0, dstI = 0; + for (; srcI < Vertices.Count; srcI += primitiveSize) + { + for (var i = 0; i < primitiveSize; i++) + primitiveBuffer[i] = Vertices[srcI + i]; + + if (condition(primitiveBuffer)) + continue; + + // no vertex is in box: + for (var i = 0; i < primitiveSize; i++) + Vertices[dstI + i] = primitiveBuffer[i]; + dstI += primitiveSize; + } + Vertices.RemoveRange(dstI, Vertices.Count - dstI); + } } internal class Vertex diff --git a/Editor/Processors/SkinnedMeshes/RemoveMeshByBlendShapeProcessor.cs b/Editor/Processors/SkinnedMeshes/RemoveMeshByBlendShapeProcessor.cs index 95ae1d4e0..96a571f45 100644 --- a/Editor/Processors/SkinnedMeshes/RemoveMeshByBlendShapeProcessor.cs +++ b/Editor/Processors/SkinnedMeshes/RemoveMeshByBlendShapeProcessor.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using nadena.dev.ndmf; @@ -26,27 +27,9 @@ public override void Process(BuildContext context, MeshInfo2 target) byBlendShapeVertices.Add(vertex); } + Func condition = primitive => primitive.Any(byBlendShapeVertices.Contains); foreach (var subMesh in target.SubMeshes) - { - int srcI = 0, dstI = 0; - for (; srcI < subMesh.Triangles.Count; srcI += 3) - { - // process 3 vertex in sub mesh at once to process one polygon - var v0 = subMesh.Triangles[srcI + 0]; - var v1 = subMesh.Triangles[srcI + 1]; - var v2 = subMesh.Triangles[srcI + 2]; - - if (byBlendShapeVertices.Contains(v0) || byBlendShapeVertices.Contains(v1) || byBlendShapeVertices.Contains(v2)) - continue; - - // no vertex is affected by the blend shape: - subMesh.Triangles[dstI + 0] = v0; - subMesh.Triangles[dstI + 1] = v1; - subMesh.Triangles[dstI + 2] = v2; - dstI += 3; - } - subMesh.Triangles.RemoveRange(dstI, subMesh.Triangles.Count - dstI); - } + subMesh.RemovePrimitives("RemoveMeshByBlendShape", condition); // remove unused vertices target.Vertices.RemoveAll(x => byBlendShapeVertices.Contains(x)); diff --git a/Editor/Processors/SkinnedMeshes/RemoveMeshInBoxProcessor.cs b/Editor/Processors/SkinnedMeshes/RemoveMeshInBoxProcessor.cs index fef449231..9125830d0 100644 --- a/Editor/Processors/SkinnedMeshes/RemoveMeshInBoxProcessor.cs +++ b/Editor/Processors/SkinnedMeshes/RemoveMeshInBoxProcessor.cs @@ -1,5 +1,7 @@ +using System; using System.Collections.Generic; using System.Linq; +using Anatawa12.AvatarOptimizer.ErrorReporting; using nadena.dev.ndmf; using UnityEngine; @@ -24,27 +26,9 @@ public override void Process(BuildContext context, MeshInfo2 target) inBoxVertices.Add(vertex); } + Func condition = primitive => primitive.All(inBoxVertices.Contains); foreach (var subMesh in target.SubMeshes) - { - int srcI = 0, dstI = 0; - for (; srcI < subMesh.Triangles.Count; srcI += 3) - { - // process 3 vertex in sub mesh at once to process one polygon - var v0 = subMesh.Triangles[srcI + 0]; - var v1 = subMesh.Triangles[srcI + 1]; - var v2 = subMesh.Triangles[srcI + 2]; - - if (inBoxVertices.Contains(v0) && inBoxVertices.Contains(v1) && inBoxVertices.Contains(v2)) - continue; - - // some vertex is not in box: - subMesh.Triangles[dstI + 0] = v0; - subMesh.Triangles[dstI + 1] = v1; - subMesh.Triangles[dstI + 2] = v2; - dstI += 3; - } - subMesh.Triangles.RemoveRange(dstI, subMesh.Triangles.Count - dstI); - } + subMesh.RemovePrimitives("RemoveMeshInBox", condition); // We don't need to reset AdditionalTemporal because if out of box, it always be used. // Vertex.AdditionalTemporal: 0 if unused, 1 if used @@ -52,7 +36,7 @@ public override void Process(BuildContext context, MeshInfo2 target) inBoxVertices.Clear(); var usingVertices = inBoxVertices; foreach (var subMesh in target.SubMeshes) - foreach (var vertex in subMesh.Triangles) + foreach (var vertex in subMesh.Vertices) usingVertices.Add(vertex); // remove unused vertices diff --git a/Localization/en.po b/Localization/en.po index 23a0b4339..26e4712d4 100644 --- a/Localization/en.po +++ b/Localization/en.po @@ -451,6 +451,9 @@ msgstr "" "There's no big difference in actual performance, but the number of polygons in the performance rank will increase.\n" "Using multi pass rendering often not be intended. Please check if you intended to use multi pass rendering." +msgid "MeshInfo2:warning:lineStrip" +msgstr "SubMesh with LineStrip will be skipped with {0} Component." + #endregion # region ErrorReporter diff --git a/Localization/ja.po b/Localization/ja.po index a8bb2af39..b93963d65 100644 --- a/Localization/ja.po +++ b/Localization/ja.po @@ -387,6 +387,9 @@ msgstr "" "実際の負荷に大きな差はありませんが、パフォーマンスランクにおけるポリゴン数が増えるなどの影響があります。\n" "マルチパスレンダリングの使用が意図したものであるかを確認してください。" +msgid "MeshInfo2:warning:lineStrip" +msgstr "LineStripを使用しているSubMeshは{0}コンポーネントによってスキップされます。" + #endregion # region ErrorReporter From c7bcb725a5c9b109c0380678f7c1984d9dee1798 Mon Sep 17 00:00:00 2001 From: kaikoga Date: Sun, 5 Nov 2023 02:28:01 +0900 Subject: [PATCH 47/61] feat: add some warnings if mixed FirstPersonFlags are being merged --- Editor/APIInternal/ComponentInfos.VRM0.cs | 16 +++++++++++++--- Editor/ObjectMapping/ObjectMappingContext.cs | 15 ++++++++++++--- Localization/en.po | 5 +++++ Localization/ja.po | 5 +++++ 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/Editor/APIInternal/ComponentInfos.VRM0.cs b/Editor/APIInternal/ComponentInfos.VRM0.cs index aa5106609..eda2e3945 100644 --- a/Editor/APIInternal/ComponentInfos.VRM0.cs +++ b/Editor/APIInternal/ComponentInfos.VRM0.cs @@ -2,6 +2,7 @@ using System.Linq; using Anatawa12.AvatarOptimizer.API; +using Anatawa12.AvatarOptimizer.ErrorReporting; using UnityEngine; using VRM; @@ -133,13 +134,22 @@ protected override void ApplySpecialMapping(VRMFirstPerson component, MappingSou .GroupBy(r => r.Renderer, r => r.FirstPersonFlag) .Select(grouping => { + FirstPersonFlag mergedFirstPersonFlag; var firstPersonFlags = grouping.Distinct().ToArray(); + if (firstPersonFlags.Length == 1) + { + mergedFirstPersonFlag = firstPersonFlags[0]; + } + else + { + mergedFirstPersonFlag = firstPersonFlags.Contains(FirstPersonFlag.Both) ? FirstPersonFlag.Both : FirstPersonFlag.Auto; + BuildReport.LogWarning("MergeSkinnedMesh:warning:VRM:FirstPersonFlagsMismatch", mergedFirstPersonFlag.ToString()); + } + return new VRMFirstPerson.RendererFirstPersonFlags { Renderer = grouping.Key, - FirstPersonFlag = firstPersonFlags.Length == 1 ? firstPersonFlags[0] : - firstPersonFlags.Contains(FirstPersonFlag.Both) ? FirstPersonFlag.Both : - FirstPersonFlag.Auto + FirstPersonFlag = mergedFirstPersonFlag }; }).ToList(); } diff --git a/Editor/ObjectMapping/ObjectMappingContext.cs b/Editor/ObjectMapping/ObjectMappingContext.cs index 8b8b3a98e..2454e2097 100644 --- a/Editor/ObjectMapping/ObjectMappingContext.cs +++ b/Editor/ObjectMapping/ObjectMappingContext.cs @@ -368,13 +368,22 @@ private Object CustomClone(Object o) .GroupBy(r => r.Renderer, r => r.FirstPersonFlag) .Select(grouping => { + UniGLTF.Extensions.VRMC_vrm.FirstPersonType mergedFirstPersonFlag; var firstPersonFlags = grouping.Distinct().ToArray(); + if (firstPersonFlags.Length == 1) + { + mergedFirstPersonFlag = firstPersonFlags[0]; + } + else + { + mergedFirstPersonFlag = firstPersonFlags.Contains(UniGLTF.Extensions.VRMC_vrm.FirstPersonType.both) ? UniGLTF.Extensions.VRMC_vrm.FirstPersonType.both : UniGLTF.Extensions.VRMC_vrm.FirstPersonType.auto; + BuildReport.LogWarning("MergeSkinnedMesh:warning:VRM:FirstPersonFlagsMismatch", mergedFirstPersonFlag.ToString()); + } + return new UniVRM10.RendererFirstPersonFlags { Renderer = grouping.Key, - FirstPersonFlag = firstPersonFlags.Length == 1 ? firstPersonFlags[0] : - firstPersonFlags.Contains(UniGLTF.Extensions.VRMC_vrm.FirstPersonType.both) ? UniGLTF.Extensions.VRMC_vrm.FirstPersonType.both : - UniGLTF.Extensions.VRMC_vrm.FirstPersonType.auto + FirstPersonFlag = mergedFirstPersonFlag }; }).ToList(); diff --git a/Localization/en.po b/Localization/en.po index 23a0b4339..f528039fa 100644 --- a/Localization/en.po +++ b/Localization/en.po @@ -246,6 +246,11 @@ msgstr "" "Some weights of BlendShape '{0}' of some source SkinnedMeshRenderer are not same value.\n" "In this case, the weight of final SkinnedMeshRenderer is not defined so please make uniform weight or freeze BlendShape." +msgid "MergeSkinnedMesh:warning:VRM:FirstPersonFlagsMismatch" +msgstr "" +"Source Renderers had mixed FirstPersonFlags values specified, and fallbacked into '{0}'.\n" +"It is advisable to set same FirstPersonFlags values for Renderers to be merged by MergeSkinnedMesh.\n" + msgid "MergeSkinnedMesh:warning:MeshIsNotNone" msgstr "" "Mesh of SkinnedMeshRenderer is not None!\n" diff --git a/Localization/ja.po b/Localization/ja.po index a8bb2af39..d25027dec 100644 --- a/Localization/ja.po +++ b/Localization/ja.po @@ -183,6 +183,11 @@ msgstr "" "統合対象のSkinnedMeshRenderer間でBlendShape '{0}'の値が揃っていません。\n" "どの値を適用するかが不定になってしまうため、統合対象の同名BlendShapeの値は揃えるか固定・除去してください。" +msgid "MergeSkinnedMesh:warning:VRM:FirstPersonFlagsMismatch" +msgstr "" +"統合対象のRenderer間でFirstPersonFlagsの値が揃っていなかったため、'{0}'に統合されました。\n" +"MergeSkinnedMeshで統合されるRendererのFirstPersonFlagsの値は揃えることを推奨します。" + msgid "MergeSkinnedMesh:warning:MeshIsNotNone" msgstr "" "SkinnedMeshRendererのMeshがNoneではありません!\n" From 51d0fa13aa4841abae9a19fba798b42d15005f12 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sun, 5 Nov 2023 02:37:07 +0900 Subject: [PATCH 48/61] feat: support non-triangle topology in MergeSkinnedMesh --- .../MergeSkinnedMeshProcessor.cs | 20 ++++++++++++------- Editor/Processors/SkinnedMeshes/MeshInfo2.cs | 2 ++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Editor/Processors/SkinnedMeshes/MergeSkinnedMeshProcessor.cs b/Editor/Processors/SkinnedMeshes/MergeSkinnedMeshProcessor.cs index f7e3f222d..9819977c3 100644 --- a/Editor/Processors/SkinnedMeshes/MergeSkinnedMeshProcessor.cs +++ b/Editor/Processors/SkinnedMeshes/MergeSkinnedMeshProcessor.cs @@ -52,7 +52,7 @@ public override void Process(BuildContext context, MeshInfo2 target) foreach (var meshInfo2 in meshInfos) meshInfo2.FlattenMultiPassRendering("Merge Skinned Mesh"); - var sourceMaterials = meshInfos.Select(x => x.SubMeshes.Select(y => y.SharedMaterial).ToArray()).ToArray(); + var sourceMaterials = meshInfos.Select(x => x.SubMeshes.Select(y => (y.Topology, y.SharedMaterial)).ToArray()).ToArray(); Profiler.EndSample(); Profiler.BeginSample("Material Normal Configuration Check"); @@ -97,7 +97,7 @@ public override void Process(BuildContext context, MeshInfo2 target) target.Clear(); target.SubMeshes.Capacity = Math.Max(target.SubMeshes.Capacity, materials.Count); foreach (var material in materials) - target.SubMeshes.Add(new SubMesh(material)); + target.SubMeshes.Add(new SubMesh(material.material, material.topology)); TexCoordStatus TexCoordStatusMax(TexCoordStatus x, TexCoordStatus y) => (TexCoordStatus)Math.Max((int)x, (int)y); @@ -124,7 +124,10 @@ TexCoordStatus TexCoordStatusMax(TexCoordStatus x, TexCoordStatus y) => for (var j = 0; j < meshInfo.SubMeshes.Count; j++) { var targetSubMeshIndex = subMeshIndexMap[i][j]; - target.SubMeshes[targetSubMeshIndex].Triangles.AddRange(meshInfo.SubMeshes[j].Triangles); + var targetSubMesh = target.SubMeshes[targetSubMeshIndex]; + var sourceSubMesh = meshInfo.SubMeshes[j]; + System.Diagnostics.Debug.Assert(targetSubMesh.Topology == sourceSubMesh.Topology); + targetSubMesh.Vertices.AddRange(sourceSubMesh.Vertices); mappings.Add(($"m_Materials.Array.data[{j}]", $"m_Materials.Array.data[{targetSubMeshIndex}]")); } @@ -252,11 +255,12 @@ TexCoordStatus TexCoordStatusMax(TexCoordStatus x, TexCoordStatus y) => #endif } - private (int[][] mapping, List materials) CreateMergedMaterialsAndSubMeshIndexMapping( - Material[][] sourceMaterials) + private (int[][] mapping, List<(MeshTopology topology, Material material)> materials) + CreateMergedMaterialsAndSubMeshIndexMapping( + (MeshTopology topology, Material material)[][] sourceMaterials) { var doNotMerges = Component.doNotMergeMaterials.GetAsSet(); - var resultMaterials = new List(); + var resultMaterials = new List<(MeshTopology, Material)>(); var resultIndices = new int[sourceMaterials.Length][]; for (var i = 0; i < sourceMaterials.Length; i++) @@ -268,7 +272,7 @@ TexCoordStatus TexCoordStatusMax(TexCoordStatus x, TexCoordStatus y) => { var material = materials[j]; var foundIndex = resultMaterials.IndexOf(material); - if (doNotMerges.Contains(material) || foundIndex == -1) + if (doNotMerges.Contains(material.material) || foundIndex == -1) { indices[j] = resultMaterials.Count; resultMaterials.Add(material); @@ -329,10 +333,12 @@ public Material[] Materials(bool fast = true) { var sourceMaterials = _processor.SkinnedMeshRenderers.Select(EditSkinnedMeshComponentUtil.GetMaterials) .Concat(_processor.StaticMeshRenderers.Select(x => x.sharedMaterials)) + .Select(a => a.Select(b => (MeshTopology.Triangles, b)).ToArray()) .ToArray(); return _processor.CreateMergedMaterialsAndSubMeshIndexMapping(sourceMaterials) .materials + .Select(x => x.material) .ToArray(); } diff --git a/Editor/Processors/SkinnedMeshes/MeshInfo2.cs b/Editor/Processors/SkinnedMeshes/MeshInfo2.cs index fe0dec4a6..2d72136ac 100644 --- a/Editor/Processors/SkinnedMeshes/MeshInfo2.cs +++ b/Editor/Processors/SkinnedMeshes/MeshInfo2.cs @@ -699,6 +699,8 @@ public SubMesh() public SubMesh(List vertices, Material sharedMaterial) => (Vertices, SharedMaterial) = (vertices, sharedMaterial); public SubMesh(Material sharedMaterial) => SharedMaterial = sharedMaterial; + public SubMesh(Material sharedMaterial, MeshTopology topology) => + (SharedMaterial, Topology) = (sharedMaterial, topology); public SubMesh(SubMesh subMesh, Material triangles) { From 657c997b456e4afae4af5b933db654bb4967f20b Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sun, 5 Nov 2023 02:39:39 +0900 Subject: [PATCH 49/61] feat(merge-toonlit): add support for non-triangle topology --- .../MergeToonLitMaterialProcessor.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Editor/Processors/SkinnedMeshes/MergeToonLitMaterialProcessor.cs b/Editor/Processors/SkinnedMeshes/MergeToonLitMaterialProcessor.cs index 4fc5296a6..2175a0d50 100644 --- a/Editor/Processors/SkinnedMeshes/MergeToonLitMaterialProcessor.cs +++ b/Editor/Processors/SkinnedMeshes/MergeToonLitMaterialProcessor.cs @@ -36,7 +36,7 @@ public override void Process(BuildContext context, MeshInfo2 target) foreach (var v in target.Vertices) users[v] = 0; foreach (var targetSubMesh in target.SubMeshes) - foreach (var v in targetSubMesh.Triangles.Distinct()) + foreach (var v in targetSubMesh.Vertices.Distinct()) users[v]++; // compute per-material data @@ -55,30 +55,30 @@ public override void Process(BuildContext context, MeshInfo2 target) var subMesh = target.SubMeshes[subMeshI]; var targetRect = targetRectForMaterial[subMeshI]; var vertexCache = new Dictionary(); - for (var i = 0; i < subMesh.Triangles.Count; i++) + for (var i = 0; i < subMesh.Vertices.Count; i++) { - if (vertexCache.TryGetValue(subMesh.Triangles[i], out var cached)) + if (vertexCache.TryGetValue(subMesh.Vertices[i], out var cached)) { - subMesh.Triangles[i] = cached; + subMesh.Vertices[i] = cached; continue; } - if (users[subMesh.Triangles[i]] != 1) + if (users[subMesh.Vertices[i]] != 1) { // if there are multiple users for the vertex: duplicate it - var cloned = subMesh.Triangles[i].Clone(); + var cloned = subMesh.Vertices[i].Clone(); target.Vertices.Add(cloned); - users[subMesh.Triangles[i]]--; + users[subMesh.Vertices[i]]--; - vertexCache[subMesh.Triangles[i]] = cloned; - subMesh.Triangles[i] = cloned; + vertexCache[subMesh.Vertices[i]] = cloned; + subMesh.Vertices[i] = cloned; } else { - vertexCache[subMesh.Triangles[i]] = subMesh.Triangles[i]; + vertexCache[subMesh.Vertices[i]] = subMesh.Vertices[i]; } - subMesh.Triangles[i].TexCoord0 = MapUV(subMesh.Triangles[i].TexCoord0, targetRect); + subMesh.Vertices[i].TexCoord0 = MapUV(subMesh.Vertices[i].TexCoord0, targetRect); } } } From a512a6bea9709dd15519f99631aa05496789f49d Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sun, 5 Nov 2023 02:39:57 +0900 Subject: [PATCH 50/61] chore: use Debug.Assert --- Editor/Processors/SkinnedMeshes/MeshInfo2.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Editor/Processors/SkinnedMeshes/MeshInfo2.cs b/Editor/Processors/SkinnedMeshes/MeshInfo2.cs index 2d72136ac..b86c0a52b 100644 --- a/Editor/Processors/SkinnedMeshes/MeshInfo2.cs +++ b/Editor/Processors/SkinnedMeshes/MeshInfo2.cs @@ -676,7 +676,7 @@ public List Triangles { get { - Assert.AreEqual(MeshTopology.Triangles, Topology); + Debug.Assert(Topology == MeshTopology.Triangles); return Vertices; } } From b091458819d214227ebbd60676bd9ececa26f223 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sun, 5 Nov 2023 02:43:51 +0900 Subject: [PATCH 51/61] docs(changelog): Support for Mesh Topologies other than Triangles --- CHANGELOG-PRERELEASE.md | 1 + CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG-PRERELEASE.md b/CHANGELOG-PRERELEASE.md index 1ecc230df..e9ddbbc74 100644 --- a/CHANGELOG-PRERELEASE.md +++ b/CHANGELOG-PRERELEASE.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog]. ## [Unreleased] ### Added +- Support for Mesh Topologies other than Triangles `#692` ### Changed diff --git a/CHANGELOG.md b/CHANGELOG.md index f30c5edfe..c08d9bd8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog]. - If you toggles your clothes with simple toggle, PhysBones on the your avatar will also be toggled automatically! - Small performance improve `#641` - Ability to prevent changing enablement of component `#668` +- Support for Mesh Topologies other than Triangles `#692` ### Changed - All logs passed to ErrorReport is now shown on the console log `#643` From eef4d2af0c74de13c5a50413ac2b5c066037284e Mon Sep 17 00:00:00 2001 From: kaikoga Date: Sun, 5 Nov 2023 11:00:13 +0900 Subject: [PATCH 52/61] refactor: emerge MapObject() from common base class --- Editor/ObjectMapping/ObjectMappingContext.cs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/Editor/ObjectMapping/ObjectMappingContext.cs b/Editor/ObjectMapping/ObjectMappingContext.cs index 2454e2097..38e2c4b04 100644 --- a/Editor/ObjectMapping/ObjectMappingContext.cs +++ b/Editor/ObjectMapping/ObjectMappingContext.cs @@ -53,7 +53,7 @@ public void OnDeactivate(BuildContext context) // ReSharper disable once AccessToModifiedClosure var mapped = BuildReport.ReportingObject(controller, - () => mapper.MapAnimatorController(controller)); + () => mapper.MapObject(controller)); if (mapped != controller) p.objectReferenceValue = mapped; break; @@ -67,7 +67,7 @@ public void OnDeactivate(BuildContext context) // ReSharper disable once AccessToModifiedClosure var mapped = BuildReport.ReportingObject(blendShapeAvatar, - () => mapper.MapBlendShapeAvatar(blendShapeAvatar)); + () => mapper.MapObject(blendShapeAvatar)); if (mapped != blendShapeAvatar) p.objectReferenceValue = mapped; break; @@ -82,7 +82,7 @@ public void OnDeactivate(BuildContext context) // ReSharper disable once AccessToModifiedClosure var mapped = BuildReport.ReportingObject(vrm10Object, - () => mapper.MapVrm10Object(vrm10Object)); + () => mapper.MapObject(vrm10Object)); if (mapped != vrm10Object) p.objectReferenceValue = mapped; break; @@ -169,15 +169,8 @@ public AnimatorControllerMapper(AnimationObjectMapper mapping) public T MapAnimatorController(T controller) where T : RuntimeAnimatorController => DeepClone(controller, CustomClone); -#if AAO_VRM0 - public T MapBlendShapeAvatar(T blendShapeAvatar) where T : VRM.BlendShapeAvatar => - DeepClone(blendShapeAvatar, CustomClone); -#endif - -#if AAO_VRM1 - public T MapVrm10Object(T vrm10Object) where T : UniVRM10.VRM10Object => - DeepClone(vrm10Object, CustomClone); -#endif + public T MapObject(T obj) where T : Object => + DeepClone(obj, CustomClone); // https://github.com/bdunderscore/modular-avatar/blob/db49e2e210bc070671af963ff89df853ae4514a5/Packages/nadena.dev.modular-avatar/Editor/AnimatorMerger.cs#L199-L241 // Originally under MIT License From abb93a18d83e89341ba244d3fe7ded3c1072736b Mon Sep 17 00:00:00 2001 From: kaikoga Date: Sun, 5 Nov 2023 11:14:43 +0900 Subject: [PATCH 53/61] refactor: deduplicate code --- Editor/ObjectMapping/ObjectMappingContext.cs | 43 ++++---------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/Editor/ObjectMapping/ObjectMappingContext.cs b/Editor/ObjectMapping/ObjectMappingContext.cs index 38e2c4b04..d2ffa5d7f 100644 --- a/Editor/ObjectMapping/ObjectMappingContext.cs +++ b/Editor/ObjectMapping/ObjectMappingContext.cs @@ -44,50 +44,25 @@ public void OnDeactivate(BuildContext context) if (mapping.MapComponentInstance(p.objectReferenceInstanceIDValue, out var mappedComponent)) p.objectReferenceValue = mappedComponent; - switch (p.objectReferenceValue) + var objectReferenceValue = p.objectReferenceValue; + switch (objectReferenceValue) { - case RuntimeAnimatorController controller: - { - if (mapper == null) - mapper = new AnimatorControllerMapper(mapping.CreateAnimationMapper(component.gameObject)); - - // ReSharper disable once AccessToModifiedClosure - var mapped = BuildReport.ReportingObject(controller, - () => mapper.MapObject(controller)); - if (mapped != controller) - p.objectReferenceValue = mapped; - break; - } - + case RuntimeAnimatorController _: #if AAO_VRM0 - case VRM.BlendShapeAvatar blendShapeAvatar: - { - if (mapper == null) - mapper = new AnimatorControllerMapper(mapping.CreateAnimationMapper(component.gameObject)); - - // ReSharper disable once AccessToModifiedClosure - var mapped = BuildReport.ReportingObject(blendShapeAvatar, - () => mapper.MapObject(blendShapeAvatar)); - if (mapped != blendShapeAvatar) - p.objectReferenceValue = mapped; - break; - } + case VRM.BlendShapeAvatar _: #endif - #if AAO_VRM1 - case UniVRM10.VRM10Object vrm10Object: - { + case UniVRM10.VRM10Object _: +#endif if (mapper == null) mapper = new AnimatorControllerMapper(mapping.CreateAnimationMapper(component.gameObject)); // ReSharper disable once AccessToModifiedClosure - var mapped = BuildReport.ReportingObject(vrm10Object, - () => mapper.MapObject(vrm10Object)); - if (mapped != vrm10Object) + var mapped = BuildReport.ReportingObject(objectReferenceValue, + () => mapper.MapObject(objectReferenceValue)); + if (mapped != objectReferenceValue) p.objectReferenceValue = mapped; break; - } -#endif } } From d04ec63ed5613cca94ecae7ab8c3848c93ac9619 Mon Sep 17 00:00:00 2001 From: kaikoga Date: Sun, 5 Nov 2023 11:32:36 +0900 Subject: [PATCH 54/61] fix: handle property copy / remove --- Editor/ObjectMapping/AnimationObjectMapper.cs | 66 ++++++++++++++++--- Editor/ObjectMapping/ObjectMappingContext.cs | 44 ++++++++----- 2 files changed, 84 insertions(+), 26 deletions(-) diff --git a/Editor/ObjectMapping/AnimationObjectMapper.cs b/Editor/ObjectMapping/AnimationObjectMapper.cs index 354192276..d704c36c0 100644 --- a/Editor/ObjectMapping/AnimationObjectMapper.cs +++ b/Editor/ObjectMapping/AnimationObjectMapper.cs @@ -116,29 +116,79 @@ public string MapPath(string srcPath, Type type) } [CanBeNull] - public string MapPropertyName(string srcPath, string propertyName, Type type) + public (string path, Type type, string propertyName)[] MapBinding((string path, Type type, string propertyName) binding) { - var gameObjectInfo = GetGameObjectInfo(srcPath); - if (gameObjectInfo == null) return srcPath; - var (instanceId, componentInfo) = gameObjectInfo.GetComponentByType(type); + var gameObjectInfo = GetGameObjectInfo(binding.path); + if (gameObjectInfo == null) + return null; + var (instanceId, componentInfo) = gameObjectInfo.GetComponentByType(binding.type); if (componentInfo != null) { // there's mapping about component. // this means the component is merged or some prop has mapping - if (componentInfo.PropertyMapping.TryGetValue(propertyName, out var newProp)) + if (componentInfo.PropertyMapping.TryGetValue(binding.propertyName, out var newProp)) { - return newProp.MappedProperty.Name; + // if mapped one is exactly same as original, return null + if (newProp.AllCopiedTo.Length == 1 + && newProp.AllCopiedTo[0].InstanceId == instanceId + && newProp.AllCopiedTo[0].Name == binding.propertyName) + return null; + + // there are mapping for property + var curveBindings = new (string path, Type type, string propertyName)[newProp.AllCopiedTo.Length]; + var copiedToIndex = 0; + for (var i = 0; i < newProp.AllCopiedTo.Length; i++) + { + var descriptor = newProp.AllCopiedTo[copiedToIndex++]; + var component = new ComponentOrGameObject(EditorUtility.InstanceIDToObject(descriptor.InstanceId)); + // this means removed. + if (!component) + { + copiedToIndex -= 1; + continue; + } + + var newPath = Utils.RelativePath(_rootGameObject.transform, component.transform); + + // this means moved to out of the animator scope + // TODO: add warning + if (newPath == null) return Array.Empty<(string path, Type type, string propertyName)>(); + + binding.path = newPath; + binding.type = descriptor.Type; + binding.propertyName = descriptor.Name; + curveBindings[i] = binding; // copy + } + + if (copiedToIndex != curveBindings.Length) + return curveBindings.AsSpan().Slice(0, copiedToIndex).ToArray(); + return curveBindings; } else { - return propertyName; + var component = new ComponentOrGameObject(EditorUtility.InstanceIDToObject(componentInfo.MergedInto)); + if (!component) return Array.Empty<(string path, Type type, string propertyName)>(); // this means removed. + + var newPath = Utils.RelativePath(_rootGameObject.transform, component.transform); + if (newPath == null) return Array.Empty<(string path, Type type, string propertyName)>(); // this means moved to out of the animator scope + if (binding.path == newPath) return null; + binding.path = newPath; + return new []{ binding }; } } else { - return propertyName; + // The component is not merged & no prop mapping so process GameObject mapping + + var component = EditorUtility.InstanceIDToObject(instanceId); + if (!component) return Array.Empty<(string path, Type type, string propertyName)>(); // this means removed + + if (gameObjectInfo.NewPath == null) return Array.Empty<(string path, Type type, string propertyName)>(); + if (binding.path == gameObjectInfo.NewPath) return null; + binding.path = gameObjectInfo.NewPath; + return new[] { binding }; } } diff --git a/Editor/ObjectMapping/ObjectMappingContext.cs b/Editor/ObjectMapping/ObjectMappingContext.cs index d2ffa5d7f..0e0138e4f 100644 --- a/Editor/ObjectMapping/ObjectMappingContext.cs +++ b/Editor/ObjectMapping/ObjectMappingContext.cs @@ -256,17 +256,21 @@ private Object CustomClone(Object o) var newBlendShapeClip = DefaultDeepClone(blendShapeClip, CustomClone); newBlendShapeClip.Prefab = null; // This likely to point prefab before mapping, which is invalid by now newBlendShapeClip.name = "rebased " + blendShapeClip.name; - newBlendShapeClip.Values = newBlendShapeClip.Values.Select(binding => + newBlendShapeClip.Values = newBlendShapeClip.Values.SelectMany(binding => { - var propertyName = VProp.BlendShapeIndex(binding.Index); - var mappedPropertyName = _mapping.MapPropertyName(binding.RelativePath, propertyName, typeof(SkinnedMeshRenderer)); - _mapped = true; - return new VRM.BlendShapeBinding + var mappedBinding = _mapping.MapBinding((binding.RelativePath, typeof(SkinnedMeshRenderer), VProp.BlendShapeIndex(binding.Index))); + if (mappedBinding == null) { - RelativePath = _mapping.MapPath(binding.RelativePath, typeof(SkinnedMeshRenderer)), - Index = VProp.ParseBlendShapeIndex(mappedPropertyName), - Weight = binding.Weight - }; + return new[] { binding }; + } + _mapped = true; + return mappedBinding + .Select(mapped => new VRM.BlendShapeBinding + { + RelativePath = _mapping.MapPath(mapped.path, typeof(SkinnedMeshRenderer)), + Index = VProp.ParseBlendShapeIndex(mapped.propertyName), + Weight = binding.Weight + }); }).ToArray(); return newBlendShapeClip; } @@ -277,17 +281,21 @@ private Object CustomClone(Object o) var newVrm10Expression = DefaultDeepClone(vrm10Expression, CustomClone); newVrm10Expression.Prefab = null; // This likely to point prefab before mapping, which is invalid by now newVrm10Expression.name = "rebased " + vrm10Expression.name; - newVrm10Expression.MorphTargetBindings = newVrm10Expression.MorphTargetBindings.Select(binding => + newVrm10Expression.MorphTargetBindings = newVrm10Expression.MorphTargetBindings.SelectMany(binding => { - var propertyName = VProp.BlendShapeIndex(binding.Index); - var mappedPropertyName = _mapping.MapPropertyName(binding.RelativePath, propertyName, typeof(SkinnedMeshRenderer)); - _mapped = true; - return new UniVRM10.MorphTargetBinding + var mappedBinding = _mapping.MapBinding((binding.RelativePath, typeof(SkinnedMeshRenderer), VProp.BlendShapeIndex(binding.Index))); + if (mappedBinding == null) { - RelativePath = _mapping.MapPath(binding.RelativePath, typeof(SkinnedMeshRenderer)), - Index = VProp.ParseBlendShapeIndex(mappedPropertyName), - Weight = binding.Weight - }; + return new[] { binding }; + } + _mapped = true; + return mappedBinding + .Select(mapped => new UniVRM10.MorphTargetBinding + { + RelativePath = _mapping.MapPath(mapped.path, typeof(SkinnedMeshRenderer)), + Index = VProp.ParseBlendShapeIndex(mapped.propertyName), + Weight = binding.Weight + }); }).ToArray(); return newVrm10Expression; } From 89d319859220377b2e24a2cebb5ccb5bcaa266cf Mon Sep 17 00:00:00 2001 From: kaikoga Date: Sun, 5 Nov 2023 11:45:36 +0900 Subject: [PATCH 55/61] refactor: use generic MapBinding() from MapBinding(EditorCurveBinding) --- Editor/ObjectMapping/AnimationObjectMapper.cs | 112 ++++-------------- Editor/ObjectMapping/ObjectMappingContext.cs | 12 +- 2 files changed, 31 insertions(+), 93 deletions(-) diff --git a/Editor/ObjectMapping/AnimationObjectMapper.cs b/Editor/ObjectMapping/AnimationObjectMapper.cs index d704c36c0..db3c2e776 100644 --- a/Editor/ObjectMapping/AnimationObjectMapper.cs +++ b/Editor/ObjectMapping/AnimationObjectMapper.cs @@ -116,28 +116,28 @@ public string MapPath(string srcPath, Type type) } [CanBeNull] - public (string path, Type type, string propertyName)[] MapBinding((string path, Type type, string propertyName) binding) + public (string path, Type type, string propertyName)[] MapBinding(string path, Type type, string propertyName) { - var gameObjectInfo = GetGameObjectInfo(binding.path); + var gameObjectInfo = GetGameObjectInfo(path); if (gameObjectInfo == null) return null; - var (instanceId, componentInfo) = gameObjectInfo.GetComponentByType(binding.type); + var (instanceId, componentInfo) = gameObjectInfo.GetComponentByType(type); if (componentInfo != null) { // there's mapping about component. // this means the component is merged or some prop has mapping - if (componentInfo.PropertyMapping.TryGetValue(binding.propertyName, out var newProp)) + if (componentInfo.PropertyMapping.TryGetValue(propertyName, out var newProp)) { // if mapped one is exactly same as original, return null if (newProp.AllCopiedTo.Length == 1 && newProp.AllCopiedTo[0].InstanceId == instanceId - && newProp.AllCopiedTo[0].Name == binding.propertyName) + && newProp.AllCopiedTo[0].Name == propertyName) return null; // there are mapping for property - var curveBindings = new (string path, Type type, string propertyName)[newProp.AllCopiedTo.Length]; + var mappedBindings = new (string path, Type type, string propertyName)[newProp.AllCopiedTo.Length]; var copiedToIndex = 0; for (var i = 0; i < newProp.AllCopiedTo.Length; i++) { @@ -156,15 +156,12 @@ public string MapPath(string srcPath, Type type) // TODO: add warning if (newPath == null) return Array.Empty<(string path, Type type, string propertyName)>(); - binding.path = newPath; - binding.type = descriptor.Type; - binding.propertyName = descriptor.Name; - curveBindings[i] = binding; // copy + mappedBindings[i] = (newPath, descriptor.Type, descriptor.Name); } - if (copiedToIndex != curveBindings.Length) - return curveBindings.AsSpan().Slice(0, copiedToIndex).ToArray(); - return curveBindings; + if (copiedToIndex != mappedBindings.Length) + return mappedBindings.AsSpan().Slice(0, copiedToIndex).ToArray(); + return mappedBindings; } else { @@ -173,9 +170,8 @@ public string MapPath(string srcPath, Type type) var newPath = Utils.RelativePath(_rootGameObject.transform, component.transform); if (newPath == null) return Array.Empty<(string path, Type type, string propertyName)>(); // this means moved to out of the animator scope - if (binding.path == newPath) return null; - binding.path = newPath; - return new []{ binding }; + if (path == newPath) return null; + return new []{ (newPath, type, propertyName) }; } } else @@ -186,87 +182,29 @@ public string MapPath(string srcPath, Type type) if (!component) return Array.Empty<(string path, Type type, string propertyName)>(); // this means removed if (gameObjectInfo.NewPath == null) return Array.Empty<(string path, Type type, string propertyName)>(); - if (binding.path == gameObjectInfo.NewPath) return null; - binding.path = gameObjectInfo.NewPath; - return new[] { binding }; + if (path == gameObjectInfo.NewPath) return null; + return new[] { (gameObjectInfo.NewPath, type, propertyName) }; } } [CanBeNull] public EditorCurveBinding[] MapBinding(EditorCurveBinding binding) { - var gameObjectInfo = GetGameObjectInfo(binding.path); - if (gameObjectInfo == null) - return null; - var (instanceId, componentInfo) = gameObjectInfo.GetComponentByType(binding.type); - - if (componentInfo != null) + var mappedBindings = MapBinding(binding.path, binding.type, binding.propertyName); + if (mappedBindings == null) { - // there's mapping about component. - // this means the component is merged or some prop has mapping - - if (componentInfo.PropertyMapping.TryGetValue(binding.propertyName, out var newProp)) - { - // if mapped one is exactly same as original, return null - if (newProp.AllCopiedTo.Length == 1 - && newProp.AllCopiedTo[0].InstanceId == instanceId - && newProp.AllCopiedTo[0].Name == binding.propertyName) - return null; - - // there are mapping for property - var curveBindings = new EditorCurveBinding[newProp.AllCopiedTo.Length]; - var copiedToIndex = 0; - for (var i = 0; i < newProp.AllCopiedTo.Length; i++) - { - var descriptor = newProp.AllCopiedTo[copiedToIndex++]; - var component = new ComponentOrGameObject(EditorUtility.InstanceIDToObject(descriptor.InstanceId)); - // this means removed. - if (!component) - { - copiedToIndex -= 1; - continue; - } - - var newPath = Utils.RelativePath(_rootGameObject.transform, component.transform); - - // this means moved to out of the animator scope - // TODO: add warning - if (newPath == null) return Array.Empty(); - - binding.path = newPath; - binding.type = descriptor.Type; - binding.propertyName = descriptor.Name; - curveBindings[i] = binding; // copy - } - - if (copiedToIndex != curveBindings.Length) - return curveBindings.AsSpan().Slice(0, copiedToIndex).ToArray(); - return curveBindings; - } - else - { - var component = new ComponentOrGameObject(EditorUtility.InstanceIDToObject(componentInfo.MergedInto)); - if (!component) return Array.Empty(); // this means removed. - - var newPath = Utils.RelativePath(_rootGameObject.transform, component.transform); - if (newPath == null) return Array.Empty(); // this means moved to out of the animator scope - if (binding.path == newPath) return null; - binding.path = newPath; - return new []{ binding }; - } + return null; } - else + + var curveBindings = new EditorCurveBinding[mappedBindings.Length]; + for (var i = 0; i < mappedBindings.Length; i++) { - // The component is not merged & no prop mapping so process GameObject mapping - - var component = EditorUtility.InstanceIDToObject(instanceId); - if (!component) return Array.Empty(); // this means removed - - if (gameObjectInfo.NewPath == null) return Array.Empty(); - if (binding.path == gameObjectInfo.NewPath) return null; - binding.path = gameObjectInfo.NewPath; - return new[] { binding }; + binding.path = mappedBindings[i].path; + binding.type = mappedBindings[i].type; + binding.propertyName = mappedBindings[i].propertyName; + curveBindings[i] = binding; // copy everything else } + return curveBindings; } } } diff --git a/Editor/ObjectMapping/ObjectMappingContext.cs b/Editor/ObjectMapping/ObjectMappingContext.cs index 0e0138e4f..71a4d95fb 100644 --- a/Editor/ObjectMapping/ObjectMappingContext.cs +++ b/Editor/ObjectMapping/ObjectMappingContext.cs @@ -258,13 +258,13 @@ private Object CustomClone(Object o) newBlendShapeClip.name = "rebased " + blendShapeClip.name; newBlendShapeClip.Values = newBlendShapeClip.Values.SelectMany(binding => { - var mappedBinding = _mapping.MapBinding((binding.RelativePath, typeof(SkinnedMeshRenderer), VProp.BlendShapeIndex(binding.Index))); - if (mappedBinding == null) + var mappedBindings = _mapping.MapBinding(binding.RelativePath, typeof(SkinnedMeshRenderer), VProp.BlendShapeIndex(binding.Index)); + if (mappedBindings == null) { return new[] { binding }; } _mapped = true; - return mappedBinding + return mappedBindings .Select(mapped => new VRM.BlendShapeBinding { RelativePath = _mapping.MapPath(mapped.path, typeof(SkinnedMeshRenderer)), @@ -283,13 +283,13 @@ private Object CustomClone(Object o) newVrm10Expression.name = "rebased " + vrm10Expression.name; newVrm10Expression.MorphTargetBindings = newVrm10Expression.MorphTargetBindings.SelectMany(binding => { - var mappedBinding = _mapping.MapBinding((binding.RelativePath, typeof(SkinnedMeshRenderer), VProp.BlendShapeIndex(binding.Index))); - if (mappedBinding == null) + var mappedBindings = _mapping.MapBinding(binding.RelativePath, typeof(SkinnedMeshRenderer), VProp.BlendShapeIndex(binding.Index)); + if (mappedBindings == null) { return new[] { binding }; } _mapped = true; - return mappedBinding + return mappedBindings .Select(mapped => new UniVRM10.MorphTargetBinding { RelativePath = _mapping.MapPath(mapped.path, typeof(SkinnedMeshRenderer)), From 9aff5d75dfeaa62d4f327286504fb197ce1a22f5 Mon Sep 17 00:00:00 2001 From: kaikoga Date: Sun, 5 Nov 2023 12:23:53 +0900 Subject: [PATCH 56/61] fix: partially revert 463d9c1: mark VRM0 BlendShapeClip / VRM1 Expression BlendShapes --- .../TraceAndOptimize/AutoFreezeBlendShape.cs | 100 ++++++------------ 1 file changed, 32 insertions(+), 68 deletions(-) diff --git a/Editor/Processors/TraceAndOptimize/AutoFreezeBlendShape.cs b/Editor/Processors/TraceAndOptimize/AutoFreezeBlendShape.cs index 05e4d72f3..07dba60af 100644 --- a/Editor/Processors/TraceAndOptimize/AutoFreezeBlendShape.cs +++ b/Editor/Processors/TraceAndOptimize/AutoFreezeBlendShape.cs @@ -96,88 +96,52 @@ private void ComputePreserveBlendShapes(BuildContext context, Dictionary()); - set.UnionWith(descriptor.VisemeBlendShapes); - break; - } - case VRC_AvatarDescriptor.LipSyncStyle.JawFlapBlendShape when descriptor.VisemeSkinnedMesh != null: - { - var skinnedMeshRenderer = descriptor.VisemeSkinnedMesh; - if (!preserveBlendShapes.TryGetValue(skinnedMeshRenderer, out var set)) - preserveBlendShapes.Add(skinnedMeshRenderer, set = new HashSet()); - set.Add(descriptor.MouthOpenBlendShapeName); - break; - } + var skinnedMeshRenderer = descriptor.VisemeSkinnedMesh; + if (!preserveBlendShapes.TryGetValue(skinnedMeshRenderer, out var set)) + preserveBlendShapes.Add(skinnedMeshRenderer, set = new HashSet()); + set.UnionWith(descriptor.VisemeBlendShapes); + break; } - - if (descriptor.enableEyeLook) + case VRC_AvatarDescriptor.LipSyncStyle.JawFlapBlendShape when descriptor.VisemeSkinnedMesh != null: { - switch (descriptor.customEyeLookSettings.eyelidType) - { - case VRCAvatarDescriptor.EyelidType.None: - break; - case VRCAvatarDescriptor.EyelidType.Bones: - break; - case VRCAvatarDescriptor.EyelidType.Blendshapes - when descriptor.customEyeLookSettings.eyelidsSkinnedMesh != null: - { - var skinnedMeshRenderer = descriptor.customEyeLookSettings.eyelidsSkinnedMesh; - if (!preserveBlendShapes.TryGetValue(skinnedMeshRenderer, out var set)) - preserveBlendShapes.Add(skinnedMeshRenderer, set = new HashSet()); - - var mesh = skinnedMeshRenderer.sharedMesh; - set.UnionWith( - from index in descriptor.customEyeLookSettings.eyelidsBlendshapes - where 0 <= index && index < mesh.blendShapeCount - select mesh.GetBlendShapeName(index) - ); - } - break; - } - } - } -#endif - -#if AAO_VRM0 - var vrmBlendShapeProxy = context.AvatarRootTransform.GetComponent(); - if (vrmBlendShapeProxy) - { - var bindings = vrmBlendShapeProxy.BlendShapeAvatar.Clips.SelectMany(clip => clip.Values); - foreach (var binding in bindings) - { - var skinnedMeshRenderer = context.AvatarRootTransform.Find(binding.RelativePath).GetComponent(); + var skinnedMeshRenderer = descriptor.VisemeSkinnedMesh; if (!preserveBlendShapes.TryGetValue(skinnedMeshRenderer, out var set)) preserveBlendShapes.Add(skinnedMeshRenderer, set = new HashSet()); - var blendShapePropName = $"blendShape.{skinnedMeshRenderer.sharedMesh.GetBlendShapeName(binding.Index)}"; - set.Add(blendShapePropName); + set.Add(descriptor.MouthOpenBlendShapeName); + break; } } -#endif -#if AAO_VRM1 - var vrm10Instance = context.AvatarRootTransform.GetComponent(); - if (vrm10Instance) + if (descriptor.enableEyeLook) { - var bindings = vrm10Instance.Vrm.Expression.Clips.SelectMany(clip => clip.Clip.MorphTargetBindings); - foreach (var binding in bindings) + switch (descriptor.customEyeLookSettings.eyelidType) { - var skinnedMeshRenderer = context.AvatarRootTransform.Find(binding.RelativePath).GetComponent(); - if (!preserveBlendShapes.TryGetValue(skinnedMeshRenderer, out var set)) - preserveBlendShapes.Add(skinnedMeshRenderer, set = new HashSet()); - var blendShapePropName = $"blendShape.{skinnedMeshRenderer.sharedMesh.GetBlendShapeName(binding.Index)}"; - set.Add(blendShapePropName); + case VRCAvatarDescriptor.EyelidType.None: + break; + case VRCAvatarDescriptor.EyelidType.Bones: + break; + case VRCAvatarDescriptor.EyelidType.Blendshapes + when descriptor.customEyeLookSettings.eyelidsSkinnedMesh != null: + { + var skinnedMeshRenderer = descriptor.customEyeLookSettings.eyelidsSkinnedMesh; + if (!preserveBlendShapes.TryGetValue(skinnedMeshRenderer, out var set)) + preserveBlendShapes.Add(skinnedMeshRenderer, set = new HashSet()); + + var mesh = skinnedMeshRenderer.sharedMesh; + set.UnionWith( + from index in descriptor.customEyeLookSettings.eyelidsBlendshapes + where 0 <= index && index < mesh.blendShapeCount + select mesh.GetBlendShapeName(index) + ); + } + break; } } #endif } - } } From 7140b34044c8190e37e47837054583ca0b4509bf Mon Sep 17 00:00:00 2001 From: kaikoga Date: Sun, 5 Nov 2023 12:26:03 +0900 Subject: [PATCH 57/61] fix: skip ComputePreserveBlendShapes when non-VRChat avatar --- .../TraceAndOptimize/AutoFreezeBlendShape.cs | 73 ++++++++++--------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/Editor/Processors/TraceAndOptimize/AutoFreezeBlendShape.cs b/Editor/Processors/TraceAndOptimize/AutoFreezeBlendShape.cs index 07dba60af..b9cd30f5b 100644 --- a/Editor/Processors/TraceAndOptimize/AutoFreezeBlendShape.cs +++ b/Editor/Processors/TraceAndOptimize/AutoFreezeBlendShape.cs @@ -96,49 +96,52 @@ private void ComputePreserveBlendShapes(BuildContext context, Dictionary()); - set.UnionWith(descriptor.VisemeBlendShapes); - break; - } - case VRC_AvatarDescriptor.LipSyncStyle.JawFlapBlendShape when descriptor.VisemeSkinnedMesh != null: - { - var skinnedMeshRenderer = descriptor.VisemeSkinnedMesh; - if (!preserveBlendShapes.TryGetValue(skinnedMeshRenderer, out var set)) - preserveBlendShapes.Add(skinnedMeshRenderer, set = new HashSet()); - set.Add(descriptor.MouthOpenBlendShapeName); - break; - } - } - - if (descriptor.enableEyeLook) - { - switch (descriptor.customEyeLookSettings.eyelidType) - { - case VRCAvatarDescriptor.EyelidType.None: - break; - case VRCAvatarDescriptor.EyelidType.Bones: + case VRC_AvatarDescriptor.LipSyncStyle.VisemeBlendShape when descriptor.VisemeSkinnedMesh != null: + { + var skinnedMeshRenderer = descriptor.VisemeSkinnedMesh; + if (!preserveBlendShapes.TryGetValue(skinnedMeshRenderer, out var set)) + preserveBlendShapes.Add(skinnedMeshRenderer, set = new HashSet()); + set.UnionWith(descriptor.VisemeBlendShapes); break; - case VRCAvatarDescriptor.EyelidType.Blendshapes - when descriptor.customEyeLookSettings.eyelidsSkinnedMesh != null: + } + case VRC_AvatarDescriptor.LipSyncStyle.JawFlapBlendShape when descriptor.VisemeSkinnedMesh != null: { - var skinnedMeshRenderer = descriptor.customEyeLookSettings.eyelidsSkinnedMesh; + var skinnedMeshRenderer = descriptor.VisemeSkinnedMesh; if (!preserveBlendShapes.TryGetValue(skinnedMeshRenderer, out var set)) preserveBlendShapes.Add(skinnedMeshRenderer, set = new HashSet()); + set.Add(descriptor.MouthOpenBlendShapeName); + break; + } + } - var mesh = skinnedMeshRenderer.sharedMesh; - set.UnionWith( - from index in descriptor.customEyeLookSettings.eyelidsBlendshapes - where 0 <= index && index < mesh.blendShapeCount - select mesh.GetBlendShapeName(index) - ); + if (descriptor.enableEyeLook) + { + switch (descriptor.customEyeLookSettings.eyelidType) + { + case VRCAvatarDescriptor.EyelidType.None: + break; + case VRCAvatarDescriptor.EyelidType.Bones: + break; + case VRCAvatarDescriptor.EyelidType.Blendshapes + when descriptor.customEyeLookSettings.eyelidsSkinnedMesh != null: + { + var skinnedMeshRenderer = descriptor.customEyeLookSettings.eyelidsSkinnedMesh; + if (!preserveBlendShapes.TryGetValue(skinnedMeshRenderer, out var set)) + preserveBlendShapes.Add(skinnedMeshRenderer, set = new HashSet()); + + var mesh = skinnedMeshRenderer.sharedMesh; + set.UnionWith( + from index in descriptor.customEyeLookSettings.eyelidsBlendshapes + where 0 <= index && index < mesh.blendShapeCount + select mesh.GetBlendShapeName(index) + ); + } + break; } - break; } } #endif From 5e73d48ca4c6c9f56a56b6de3ad41e24b2a7c746 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sun, 5 Nov 2023 21:42:26 +0900 Subject: [PATCH 58/61] chore(localization): Apply suggestions from code review Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> --- Localization/ja.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Localization/ja.po b/Localization/ja.po index b93963d65..207ebddee 100644 --- a/Localization/ja.po +++ b/Localization/ja.po @@ -388,7 +388,7 @@ msgstr "" "マルチパスレンダリングの使用が意図したものであるかを確認してください。" msgid "MeshInfo2:warning:lineStrip" -msgstr "LineStripを使用しているSubMeshは{0}コンポーネントによってスキップされます。" +msgstr "{0}コンポーネントはLineStripが使用されているSubMeshをスキップします。" #endregion From a554394b90e6d66567bf724bdf284cdfaf3b5216 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sun, 5 Nov 2023 23:37:22 +0900 Subject: [PATCH 59/61] docs(remove-zero-sized-mesh/ja): Apply suggestions from code review Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> --- .../docs/reference/remove-zero-sized-polygon/index.ja.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.docs/content/docs/reference/remove-zero-sized-polygon/index.ja.md b/.docs/content/docs/reference/remove-zero-sized-polygon/index.ja.md index 1b2a46a93..61f6e2c56 100644 --- a/.docs/content/docs/reference/remove-zero-sized-polygon/index.ja.md +++ b/.docs/content/docs/reference/remove-zero-sized-polygon/index.ja.md @@ -24,7 +24,7 @@ weight: 100 ## 備考 {#notes} -シェーダーによってはモデルファイルでのポリゴンの大きさが0でも実際にはなにかが描画されることがあるため、見た目に影響がある可能性があります。 +モデルファイルでのポリゴンの大きさがゼロであっても、シェーダーによって何かを描画していることがあるため、見た目に影響が出ることがあります。 ## 設定 {#settings} From 0085425c92e4cfd1c99f840a0e9540130ecf0281 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 6 Nov 2023 00:12:00 +0900 Subject: [PATCH 60/61] chore: Apply suggestions from code review Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> --- CHANGELOG-PRERELEASE.md | 2 +- CHANGELOG.md | 2 +- Localization/en.po | 4 ++-- Localization/ja.po | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG-PRERELEASE.md b/CHANGELOG-PRERELEASE.md index cc4a6d1a0..3dac3ee7c 100644 --- a/CHANGELOG-PRERELEASE.md +++ b/CHANGELOG-PRERELEASE.md @@ -8,7 +8,7 @@ The format is based on [Keep a Changelog]. ## [Unreleased] ### Added -- Add support of UniVRM components `#653` +- Add support for UniVRM components `#653` ### Changed diff --git a/CHANGELOG.md b/CHANGELOG.md index e6854e3a3..9b5066309 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ The format is based on [Keep a Changelog]. - If you toggles your clothes with simple toggle, PhysBones on the your avatar will also be toggled automatically! - Small performance improve `#641` - Ability to prevent changing enablement of component `#668` -- Add support of UniVRM components `#653` +- Add support for UniVRM components `#653` ### Changed - All logs passed to ErrorReport is now shown on the console log `#643` diff --git a/Localization/en.po b/Localization/en.po index f528039fa..81055cd4e 100644 --- a/Localization/en.po +++ b/Localization/en.po @@ -248,8 +248,8 @@ msgstr "" msgid "MergeSkinnedMesh:warning:VRM:FirstPersonFlagsMismatch" msgstr "" -"Source Renderers had mixed FirstPersonFlags values specified, and fallbacked into '{0}'.\n" -"It is advisable to set same FirstPersonFlags values for Renderers to be merged by MergeSkinnedMesh.\n" +"Source Renderers had specified mixed FirstPersonFlags values, so fallbacked into '{0}'.\n" +"It is recommended to set same FirstPersonFlags values for Renderers to be merged by MergeSkinnedMesh.\n" msgid "MergeSkinnedMesh:warning:MeshIsNotNone" msgstr "" diff --git a/Localization/ja.po b/Localization/ja.po index d25027dec..8a4d733d9 100644 --- a/Localization/ja.po +++ b/Localization/ja.po @@ -185,7 +185,7 @@ msgstr "" msgid "MergeSkinnedMesh:warning:VRM:FirstPersonFlagsMismatch" msgstr "" -"統合対象のRenderer間でFirstPersonFlagsの値が揃っていなかったため、'{0}'に統合されました。\n" +"統合対象のRenderer間でFirstPersonFlagsの値が揃っていなかったため、'{0}'に統一されました。\n" "MergeSkinnedMeshで統合されるRendererのFirstPersonFlagsの値は揃えることを推奨します。" msgid "MergeSkinnedMesh:warning:MeshIsNotNone" From 57cd9b3fcff142efcb67cdbba22a14ad87efc8f9 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 6 Nov 2023 00:40:00 +0900 Subject: [PATCH 61/61] chore(localization): Apply suggestions from code review Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> --- Localization/en.po | 2 +- Localization/ja.po | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Localization/en.po b/Localization/en.po index 26e4712d4..d7e7c8d82 100644 --- a/Localization/en.po +++ b/Localization/en.po @@ -452,7 +452,7 @@ msgstr "" "Using multi pass rendering often not be intended. Please check if you intended to use multi pass rendering." msgid "MeshInfo2:warning:lineStrip" -msgstr "SubMesh with LineStrip will be skipped with {0} Component." +msgstr "{0} Component does not process SubMeshes with LineStrip." #endregion diff --git a/Localization/ja.po b/Localization/ja.po index 207ebddee..5d0797cc2 100644 --- a/Localization/ja.po +++ b/Localization/ja.po @@ -388,7 +388,7 @@ msgstr "" "マルチパスレンダリングの使用が意図したものであるかを確認してください。" msgid "MeshInfo2:warning:lineStrip" -msgstr "{0}コンポーネントはLineStripが使用されているSubMeshをスキップします。" +msgstr "{0}コンポーネントはLineStripが使用されているSubMeshを処理しません。" #endregion