From c27848a093a43ffe714891213d31ccd87f65ed06 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Tue, 7 Jul 2020 19:32:59 +0900 Subject: [PATCH 1/3] fix normalize if bones contains null #467 --- .../Scripts/SkinnedMeshUtility/BoneNormalizer.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/BoneNormalizer.cs b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/BoneNormalizer.cs index 295561774..a9066f213 100644 --- a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/BoneNormalizer.cs +++ b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/BoneNormalizer.cs @@ -184,7 +184,7 @@ namespace VRM .Select(x => { Transform dstBone; - if (boneMap.TryGetValue(x.x, out dstBone)) + if (x.x != null && boneMap.TryGetValue(x.x, out dstBone)) { return dstBones.IndexOf(dstBone); } @@ -199,7 +199,15 @@ namespace VRM { if (indexMap[i] < 0) { - Debug.LogWarningFormat("{0} is removed", srcBones[i].name); + var srcBone = srcBones[i]; + if (srcBone == null) + { + Debug.LogWarningFormat("bones[{0}] is null", i); + } + else + { + Debug.LogWarningFormat("{0} is removed", srcBone.name); + } } } @@ -289,9 +297,10 @@ namespace VRM } var dstBones = srcRenderer.bones - .Where(x => boneMap.ContainsKey(x)) + .Where(x => x != null && boneMap.ContainsKey(x)) .Select(x => boneMap[x]) .ToArray(); + var hasBoneWeight = srcRenderer.bones != null && srcRenderer.bones.Length > 0; if (!hasBoneWeight) { From d4326e5f70fcbf99faec52181b109be914a48ffb Mon Sep 17 00:00:00 2001 From: ousttrue Date: Wed, 8 Jul 2020 11:26:06 +0900 Subject: [PATCH 2/3] rewrite MapBoneWeight MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Transform[] srcBones から Transform[] dstBones に移し替えるのだけど、src と dst の両方に null が入りうる(その考慮が無かった) * 後で見て分かるようにコメント追加 --- .../SkinnedMeshUtility/BoneNormalizer.cs | 146 ++++++++++-------- 1 file changed, 80 insertions(+), 66 deletions(-) diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/BoneNormalizer.cs b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/BoneNormalizer.cs index a9066f213..609549912 100644 --- a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/BoneNormalizer.cs +++ b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/BoneNormalizer.cs @@ -172,98 +172,110 @@ namespace VRM } } + /// + /// index が 有効であれば、setter に weight を渡す。無効であれば setter に 0 を渡す。 + /// + /// + /// + /// + /// + static bool CopyOrDropWeight(int[] indexMap, int srcIndex, float weight, Action setter) + { + var dstIndex = indexMap[srcIndex]; + if (dstIndex != -1) + { + // 有効なindex。weightをコピーする + setter(dstIndex, weight); + return true; + } + else + { + // 無効なindex。0でクリアする + setter(0, 0); + return false; + } + } + + /// + /// BoneWeight[] src から新しいボーンウェイトを作成する。 + /// + /// 変更前のBoneWeight[] + /// 新旧のボーンの対応表。新しい方は無効なボーンが除去されてnullの部分がある + /// 変更前のボーン配列 + /// 変更後のボーン配列。除去されたボーンがある場合、変更前より短い + /// static BoneWeight[] MapBoneWeight(BoneWeight[] src, Dictionary boneMap, Transform[] srcBones, Transform[] dstBones ) { - var indexMap = - srcBones - .Select((x, i) => new { i, x }) - .Select(x => - { - Transform dstBone; - if (x.x != null && boneMap.TryGetValue(x.x, out dstBone)) - { - return dstBones.IndexOf(dstBone); - } - else - { - return -1; - } - }) - .ToArray(); - + // 処理前後の index の対応表を作成する + var indexMap = new int[srcBones.Length]; for (int i = 0; i < srcBones.Length; ++i) { - if (indexMap[i] < 0) + var srcBone = srcBones[i]; + if (srcBone == null) { - var srcBone = srcBones[i]; - if (srcBone == null) + // 元のボーンが無い + indexMap[i] = -1; + Debug.LogWarningFormat("bones[{0}] is null", i); + } + else + { + if (boneMap.TryGetValue(srcBone, out Transform dstBone)) { - Debug.LogWarningFormat("bones[{0}] is null", i); + // 対応するボーンが存在する + var dstIndex = dstBones.IndexOf(dstBone); + if (dstIndex == -1) + { + // ありえない。バグ + throw new Exception(); + } + indexMap[i] = dstIndex; } else { + // 先のボーンが無い + indexMap[i] = -1; Debug.LogWarningFormat("{0} is removed", srcBone.name); } } } - var dst = new BoneWeight[src.Length]; - Array.Copy(src, dst, src.Length); - + // 新しいBoneWeightを作成する + var newBoneWeights = new BoneWeight[src.Length]; for (int i = 0; i < src.Length; ++i) { - var x = src[i]; + BoneWeight srcBoneWeight = src[i]; - if (indexMap[x.boneIndex0] != -1) + // 0 + CopyOrDropWeight(indexMap, srcBoneWeight.boneIndex0, srcBoneWeight.weight0, (newIndex, newWeight) => { - dst[i].boneIndex0 = indexMap[x.boneIndex0]; - dst[i].weight0 = x.weight0; - } - else if (x.weight0 > 0) + newBoneWeights[i].boneIndex0 = newIndex; + newBoneWeights[i].weight0 = newWeight; + }); + // 1 + CopyOrDropWeight(indexMap, srcBoneWeight.boneIndex1, srcBoneWeight.weight1, (newIndex, newWeight) => { - Debug.LogWarningFormat("{0} weight0 to {1} is lost", i, srcBones[x.boneIndex0].name); - dst[i].weight0 = 0; - } - - if (indexMap[x.boneIndex1] != -1) + newBoneWeights[i].boneIndex1 = newIndex; + newBoneWeights[i].weight1 = newWeight; + }); + // 2 + CopyOrDropWeight(indexMap, srcBoneWeight.boneIndex2, srcBoneWeight.weight2, (newIndex, newWeight) => { - dst[i].boneIndex1 = indexMap[x.boneIndex1]; - dst[i].weight1 = x.weight1; - } - else if (x.weight1 > 0) + newBoneWeights[i].boneIndex2 = newIndex; + newBoneWeights[i].weight2 = newWeight; + }); + // 3 + CopyOrDropWeight(indexMap, srcBoneWeight.boneIndex3, srcBoneWeight.weight3, (newIndex, newWeight) => { - Debug.LogWarningFormat("{0} weight0 to {1} is lost", i, srcBones[x.boneIndex1].name); - dst[i].weight1 = 0; - } - - if (indexMap[x.boneIndex2] != -1) - { - dst[i].boneIndex2 = indexMap[x.boneIndex2]; - dst[i].weight2 = x.weight2; - } - else if (x.weight2 > 0) - { - Debug.LogWarningFormat("{0} weight0 to {1} is lost", i, srcBones[x.boneIndex2].name); - dst[i].weight2 = 0; - } - - if (indexMap[x.boneIndex3] != -1) - { - dst[i].boneIndex3 = indexMap[x.boneIndex3]; - dst[i].weight3 = x.weight3; - } - else if (x.weight3 > 0) - { - Debug.LogWarningFormat("{0} weight0 to {1} is lost", i, srcBones[x.boneIndex3].name); - dst[i].weight3 = 0; - } + newBoneWeights[i].boneIndex3 = newIndex; + newBoneWeights[i].weight3 = newWeight; + }); } - return dst; + return newBoneWeights; } /// @@ -296,6 +308,7 @@ namespace VRM } } + // 元の Transform[] bones から、無効なboneを取り除いて前に詰めた配列を作る var dstBones = srcRenderer.bones .Where(x => x != null && boneMap.ContainsKey(x)) .Select(x => boneMap[x]) @@ -340,7 +353,8 @@ namespace VRM if (val > 0) blendShapeValues.Add(i, val); } - mesh.boneWeights = MapBoneWeight(srcMesh.boneWeights, boneMap, srcRenderer.bones, dstBones); // restore weights. clear when BakeMesh + // 新しい骨格のボーンウェイトを作成する + mesh.boneWeights = MapBoneWeight(srcMesh.boneWeights, boneMap, srcRenderer.bones, dstBones); // recalc bindposes mesh.bindposes = dstBones.Select(x => x.worldToLocalMatrix * dst.transform.localToWorldMatrix).ToArray(); From 6d61db7b177a620f0995d88a7495bca920aba71c Mon Sep 17 00:00:00 2001 From: ousttrue Date: Wed, 8 Jul 2020 14:16:53 +0900 Subject: [PATCH 3/3] =?UTF-8?q?test=E8=BF=BD=E5=8A=A0=E3=80=82=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=81=AE=E3=81=9F=E3=82=81=E3=81=AB=20public?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../VRM/UniVRM/Editor/Tests/NormalizeTests.cs | 89 +++++++++++++++++++ .../Editor/Tests/NormalizeTests.cs.meta | 11 +++ .../SkinnedMeshUtility/BoneNormalizer.cs | 9 +- 3 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 Assets/VRM/UniVRM/Editor/Tests/NormalizeTests.cs create mode 100644 Assets/VRM/UniVRM/Editor/Tests/NormalizeTests.cs.meta diff --git a/Assets/VRM/UniVRM/Editor/Tests/NormalizeTests.cs b/Assets/VRM/UniVRM/Editor/Tests/NormalizeTests.cs new file mode 100644 index 000000000..51510cfb7 --- /dev/null +++ b/Assets/VRM/UniVRM/Editor/Tests/NormalizeTests.cs @@ -0,0 +1,89 @@ +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using UnityEngine; + +namespace VRM +{ + public class NormalizeTests + { + class BoneMap + { + public List SrcBones = new List(); + public List DstBones = new List(); + public Dictionary Map = new Dictionary(); + + public void Add(GameObject src, GameObject dst) + { + SrcBones.Add(src?.transform); + if (dst != null) + { + DstBones.Add(dst.transform); + } + if (src != null) + { + Map.Add(src?.transform, dst?.transform); + } + } + + public IEnumerable CreateBoneWeight(int vertexCount) + { + int j = 0; + for (int i = 0; i < vertexCount; ++i) + { + yield return new BoneWeight + { + boneIndex0 = j++, + boneIndex1 = j++, + boneIndex2 = j++, + boneIndex3 = j++, + weight0 = 0.25f, + weight1 = 0.25f, + weight2 = 0.25f, + weight3 = 0.25f, + }; + } + } + } + + [Test] + public void MapBoneWeightTest() + { + { + var map = new BoneMap(); + map.Add(new GameObject("a"), new GameObject("A")); + map.Add(new GameObject("b"), new GameObject("B")); + map.Add(new GameObject("c"), new GameObject("C")); + map.Add(new GameObject("d"), new GameObject("D")); + map.Add(null, new GameObject("null")); + // map.Add(new GameObject("c"), null); // ありえないので Exception にしてある + var boneWeights = map.CreateBoneWeight(64).ToArray(); + var newBoneWeight = BoneNormalizer.MapBoneWeight(boneWeights, map.Map, + map.SrcBones.ToArray(), map.DstBones.ToArray()); + + // 正常系 + // exception が出なければよい + } + + { + var map = new BoneMap(); + map.Add(new GameObject("a"), new GameObject("A")); + map.Add(new GameObject("b"), new GameObject("B")); + map.Add(new GameObject("c"), new GameObject("C")); + map.Add(new GameObject("d"), new GameObject("D")); + map.Add(null, new GameObject("null")); + // map.Add(new GameObject("c"), null); // ありえないので Exception にしてある + var boneWeights = map.CreateBoneWeight(64).ToArray(); + var newBoneWeight = BoneNormalizer.MapBoneWeight(boneWeights, map.Map, + map.SrcBones.ToArray(), map.DstBones.ToArray()); + + // 4 つめが 0 になる + Assert.AreEqual(0, newBoneWeight[1].boneIndex0); + Assert.AreEqual(0, newBoneWeight[1].weight0); + // 5 つめ以降が 0 になる。out of range + Assert.AreEqual(0, newBoneWeight[1].boneIndex1); + Assert.AreEqual(0, newBoneWeight[1].weight1); + } + } + } +} diff --git a/Assets/VRM/UniVRM/Editor/Tests/NormalizeTests.cs.meta b/Assets/VRM/UniVRM/Editor/Tests/NormalizeTests.cs.meta new file mode 100644 index 000000000..b3822bbd4 --- /dev/null +++ b/Assets/VRM/UniVRM/Editor/Tests/NormalizeTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4014814a6ed5bf84faa36c7b99d6d4b8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/BoneNormalizer.cs b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/BoneNormalizer.cs index 609549912..b4b51ee25 100644 --- a/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/BoneNormalizer.cs +++ b/Assets/VRM/UniVRM/Scripts/SkinnedMeshUtility/BoneNormalizer.cs @@ -181,6 +181,13 @@ namespace VRM /// static bool CopyOrDropWeight(int[] indexMap, int srcIndex, float weight, Action setter) { + if (srcIndex < 0 || srcIndex >= indexMap.Length) + { + // ありえるかどうかわからないが BoneWeight.boneIndexN に変な値が入っている. + setter(0, 0); + return false; + } + var dstIndex = indexMap[srcIndex]; if (dstIndex != -1) { @@ -204,7 +211,7 @@ namespace VRM /// 変更前のボーン配列 /// 変更後のボーン配列。除去されたボーンがある場合、変更前より短い /// - static BoneWeight[] MapBoneWeight(BoneWeight[] src, + public static BoneWeight[] MapBoneWeight(BoneWeight[] src, Dictionary boneMap, Transform[] srcBones, Transform[] dstBones