From 66790404344f241bbc37cd6b18b6e7f262ae7a0d Mon Sep 17 00:00:00 2001 From: 0b5vr <0b5vr@0b5vr.com> Date: Fri, 26 Dec 2025 17:37:55 +0900 Subject: [PATCH 1/3] perf: Align the implementation of fromToQuaternion with the pseudocode in the spec Since `from` is always `(0, 1, 0)`, we can simplify the logic to reduce computational overhead When dot is approximately -1, we use `(1, 0, 0; 0)` as the spec specifies See: https://github.com/0b5vr/vrm-specification/blob/75fbd48a7cb1d7250fa955838af6140e9c84844c/specification/VRMC_springBone_limit-1.0/README.ja.md#rotation-1 --- .../SpringBoneJobs/Anglelimit/Anglelimit.cs | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/Anglelimit.cs b/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/Anglelimit.cs index 178ab3d23..145a37469 100644 --- a/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/Anglelimit.cs +++ b/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/Anglelimit.cs @@ -52,7 +52,7 @@ namespace UniGLTF.SpringBoneJobs in quaternion parentRotation) { // Y+方向からjointのheadからtailに向かうベクトルへの最小回転 - var axisRotation = fromToQuaternion(new float3(0, 1, 0), logic.boneAxis); + var axisRotation = getAxisRotation(logic.boneAxis); // limitのローカル空間をワールド空間に写像する回転 return @@ -63,29 +63,28 @@ namespace UniGLTF.SpringBoneJobs ; } - // https://discussions.unity.com/t/unity-mathematics-equivalent-to-quaternion-fromtorotation/237459 - public static quaternion fromToQuaternion(in float3 from, in float3 to) + /// + /// Y軸正方向から `to` への回転を表すクォータニオンを計算して返す。 + /// `to` は正規化されていると仮定する。 + /// + /// See: https://github.com/0b5vr/vrm-specification/blob/75fbd48a7cb1d7250fa955838af6140e9c84844c/specification/VRMC_springBone_limit-1.0/README.ja.md#rotation-1 + /// + /// TODO: Replace with the appropriate link to the specification later + /// + public static quaternion getAxisRotation(in float3 to) { - var fromNorm = math.normalize(from); - var toNorm = math.normalize(to); - var dot = math.dot(fromNorm, toNorm); + // dot(from, to) + 1 + var dot1 = to.y + 1f; - // Handle the case where from and to are parallel but opposite - if (math.abs(dot + 1f) < 1e-6f) // dot is approximately -1 + // Handle the case where from and to are parallel and opposite + if (dot1 < 1e-8f) // dot is approximately -1 { - // Find a perpendicular axis - var perpAxis = math.abs(fromNorm.x) > math.abs(fromNorm.z) - ? new float3(-fromNorm.y, fromNorm.x, 0f) - : new float3(0f, -fromNorm.z, fromNorm.y); - return quaternion.AxisAngle(math.normalize(perpAxis), math.PI); + return new quaternion(1f, 0f, 0f, 0f); } // General case - return quaternion.AxisAngle( - angle: math.acos(math.clamp(dot, -1f, 1f)), - axis: math.normalize(math.cross(fromNorm, toNorm)) - ); + // quaternion(cross(from, to); dot(from, to) + 1).normalized + return math.normalize(new quaternion(to.z, 0f, -to.x, dot1)); } - } } \ No newline at end of file From 1f2570a41c232d0631a48db6f6c363cf5e2c3c1c Mon Sep 17 00:00:00 2001 From: tdw46 Date: Mon, 29 Dec 2025 22:28:31 -0500 Subject: [PATCH 2/3] =?UTF-8?q?Fix:=20blendshape=20normals=20import=20(Uni?= =?UTF-8?q?GLTF/UniVRM)=20=20=20-=20Properly=20decode=20morph=20target=20V?= =?UTF-8?q?EC3=20accessors=20(normalized=20BYTE/SHORT,=20etc.)=20for=20POS?= =?UTF-8?q?ITION/NORMAL/TANGENT=20=20=20-=20Preserve=20imported=20morph=20?= =?UTF-8?q?normal=20deltas=20(remove=20heuristic=20recompute)=20=20=20-=20?= =?UTF-8?q?Add=20intermediate=20frames=20for=20normal-only=20targets=20to?= =?UTF-8?q?=20stabilize=20Unity=E2=80=99s=20normal=20interpolation=20=20?= =?UTF-8?q?=20-=20Copy=20all=20blendshape=20frames=20when=20duplicating=20?= =?UTF-8?q?meshes=20so=20extra=20frames=20are=20retained?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Runtime/MeshUtility/MeshExtensions.cs | 24 +-- .../Runtime/UniGLTF/IO/MeshIO/MeshData.cs | 146 +++++++++++++++++- .../Runtime/UniGLTF/IO/MeshIO/MeshUploader.cs | 90 +++++++++-- Packages/UniGLTF/Runtime/UniGLTF/IO/SByte3.cs | 25 +++ .../UniGLTF/Runtime/UniGLTF/IO/SByte3.cs.meta | 2 + Packages/UniGLTF/Runtime/UniGLTF/IO/Short3.cs | 25 +++ .../UniGLTF/Runtime/UniGLTF/IO/Short3.cs.meta | 2 + 7 files changed, 285 insertions(+), 29 deletions(-) create mode 100644 Packages/UniGLTF/Runtime/UniGLTF/IO/SByte3.cs create mode 100644 Packages/UniGLTF/Runtime/UniGLTF/IO/SByte3.cs.meta create mode 100644 Packages/UniGLTF/Runtime/UniGLTF/IO/Short3.cs create mode 100644 Packages/UniGLTF/Runtime/UniGLTF/IO/Short3.cs.meta diff --git a/Packages/UniGLTF/Runtime/MeshUtility/MeshExtensions.cs b/Packages/UniGLTF/Runtime/MeshUtility/MeshExtensions.cs index 90029be67..e1921a6eb 100644 --- a/Packages/UniGLTF/Runtime/MeshUtility/MeshExtensions.cs +++ b/Packages/UniGLTF/Runtime/MeshUtility/MeshExtensions.cs @@ -52,8 +52,8 @@ namespace UniGLTF.MeshUtility if (copyBlendShape) { - var vertices = src.vertices; - var normals = src.normals; + var deltaVertices = new Vector3[src.vertexCount]; + var deltaNormals = new Vector3[src.vertexCount]; Vector3[] tangents = null; if (Symbols.VRM_NORMALIZE_BLENDSHAPE_TANGENT) { @@ -62,14 +62,18 @@ namespace UniGLTF.MeshUtility for (int i = 0; i < src.blendShapeCount; ++i) { - src.GetBlendShapeFrameVertices(i, 0, vertices, normals, tangents); - dst.AddBlendShapeFrame( - src.GetBlendShapeName(i), - src.GetBlendShapeFrameWeight(i, 0), - vertices, - normals, - tangents - ); + var frameCount = src.GetBlendShapeFrameCount(i); + for (int f = 0; f < frameCount; ++f) + { + src.GetBlendShapeFrameVertices(i, f, deltaVertices, deltaNormals, tangents); + dst.AddBlendShapeFrame( + src.GetBlendShapeName(i), + src.GetBlendShapeFrameWeight(i, f), + deltaVertices, + deltaNormals, + tangents + ); + } } } diff --git a/Packages/UniGLTF/Runtime/UniGLTF/IO/MeshIO/MeshData.cs b/Packages/UniGLTF/Runtime/UniGLTF/IO/MeshIO/MeshData.cs index 9cb09fd4e..ccc8aa6cf 100644 --- a/Packages/UniGLTF/Runtime/UniGLTF/IO/MeshIO/MeshData.cs +++ b/Packages/UniGLTF/Runtime/UniGLTF/IO/MeshIO/MeshData.cs @@ -283,6 +283,138 @@ namespace UniGLTF } } + private static NativeArray GetMorphTargetVec3(GltfData data, int accessorIndex, string attribute) + { + if (accessorIndex < 0) return data.NativeArrayManager.CreateNativeArray(0); + + var accessor = data.GLTF.accessors[accessorIndex]; + if (accessor.type != "VEC3") + { + throw new ArgumentException($"unknown {attribute} type: {accessor.componentType}:{accessor.type}"); + } + + static float NormalizeSByte(sbyte v) + { + // glTF normalized signed integer maps min to -1.0 exactly. + return Mathf.Max(v / 127.0f, -1.0f); + } + + static float NormalizeShort(short v) + { + // glTF normalized signed integer maps min to -1.0 exactly. + return Mathf.Max(v / 32767.0f, -1.0f); + } + + switch (accessor.componentType) + { + case glComponentType.FLOAT: + return data.GetArrayFromAccessor(accessorIndex); + + case glComponentType.BYTE: + { + var src = data.GetArrayFromAccessor(accessorIndex); + var dst = data.NativeArrayManager.CreateNativeArray(src.Length); + if (accessor.normalized) + { + for (int i = 0; i < src.Length; ++i) + { + var v = src[i]; + dst[i] = new Vector3( + NormalizeSByte(v.x), + NormalizeSByte(v.y), + NormalizeSByte(v.z)); + } + } + else + { + for (int i = 0; i < src.Length; ++i) + { + var v = src[i]; + dst[i] = new Vector3(v.x, v.y, v.z); + } + } + return dst; + } + + case glComponentType.UNSIGNED_BYTE: + { + var src = data.GetArrayFromAccessor(accessorIndex); + var dst = data.NativeArrayManager.CreateNativeArray(src.Length); + if (accessor.normalized) + { + const float factor = 1.0f / 255.0f; + for (int i = 0; i < src.Length; ++i) + { + var v = src[i]; + dst[i] = new Vector3(v.x * factor, v.y * factor, v.z * factor); + } + } + else + { + for (int i = 0; i < src.Length; ++i) + { + var v = src[i]; + dst[i] = new Vector3(v.x, v.y, v.z); + } + } + return dst; + } + + case glComponentType.SHORT: + { + var src = data.GetArrayFromAccessor(accessorIndex); + var dst = data.NativeArrayManager.CreateNativeArray(src.Length); + if (accessor.normalized) + { + for (int i = 0; i < src.Length; ++i) + { + var v = src[i]; + dst[i] = new Vector3( + NormalizeShort(v.x), + NormalizeShort(v.y), + NormalizeShort(v.z)); + } + } + else + { + for (int i = 0; i < src.Length; ++i) + { + var v = src[i]; + dst[i] = new Vector3(v.x, v.y, v.z); + } + } + return dst; + } + + case glComponentType.UNSIGNED_SHORT: + { + var src = data.GetArrayFromAccessor(accessorIndex); + var dst = data.NativeArrayManager.CreateNativeArray(src.Length); + if (accessor.normalized) + { + const float factor = 1.0f / 65535.0f; + for (int i = 0; i < src.Length; ++i) + { + var v = src[i]; + dst[i] = new Vector3(v.x * factor, v.y * factor, v.z * factor); + } + } + else + { + for (int i = 0; i < src.Length; ++i) + { + var v = src[i]; + dst[i] = new Vector3(v.x, v.y, v.z); + } + } + return dst; + } + + default: + throw new NotImplementedException($"unknown {attribute} type: {accessor.componentType}:{accessor.type}"); + } + } + /// /// 各 primitive の attribute の要素が同じでない。=> uv が有るものと無いものが混在するなど /// glTF 的にはありうる。 @@ -438,7 +570,7 @@ namespace UniGLTF var blendShape = GetOrCreateBlendShape(i); if (primTarget.POSITION != -1) { - var array = data.GetArrayFromAccessor(primTarget.POSITION); + var array = GetMorphTargetVec3(data, primTarget.POSITION, "POSITION"); if (array.Length != positions.Length) { throw new Exception("different length"); @@ -449,7 +581,7 @@ namespace UniGLTF if (primTarget.NORMAL != -1) { - var array = data.GetArrayFromAccessor(primTarget.NORMAL); + var array = GetMorphTargetVec3(data, primTarget.NORMAL, "NORMAL"); if (array.Length != positions.Length) { throw new Exception("different length"); @@ -460,7 +592,7 @@ namespace UniGLTF if (primTarget.TANGENT != -1) { - var array = data.GetArrayFromAccessor(primTarget.TANGENT); + var array = GetMorphTargetVec3(data, primTarget.TANGENT, "TANGENT"); if (array.Length != positions.Length) { throw new Exception("different length"); @@ -579,7 +711,7 @@ namespace UniGLTF if (hasPosition) { - var morphPositions = data.GetArrayFromAccessor(primTarget.POSITION); + var morphPositions = GetMorphTargetVec3(data, primTarget.POSITION, "POSITION"); blendShape.Positions.Capacity = morphPositions.Length; for (var j = 0; j < positions.Length; ++j) { @@ -589,7 +721,7 @@ namespace UniGLTF if (hasNormal) { - var morphNormals = data.GetArrayFromAccessor(primTarget.NORMAL); + var morphNormals = GetMorphTargetVec3(data, primTarget.NORMAL, "NORMAL"); blendShape.Normals.Capacity = morphNormals.Length; for (var j = 0; j < positions.Length; ++j) { @@ -600,7 +732,7 @@ namespace UniGLTF if (hasTangent) { - var morphTangents = data.GetArrayFromAccessor(primTarget.TANGENT); + var morphTangents = GetMorphTargetVec3(data, primTarget.TANGENT, "TANGENT"); blendShape.Tangents.Capacity = morphTangents.Length; for (var j = 0; j < positions.Length; ++j) { @@ -639,4 +771,4 @@ namespace UniGLTF } } } -} \ No newline at end of file +} diff --git a/Packages/UniGLTF/Runtime/UniGLTF/IO/MeshIO/MeshUploader.cs b/Packages/UniGLTF/Runtime/UniGLTF/IO/MeshIO/MeshUploader.cs index 99adf2c88..e276c7847 100644 --- a/Packages/UniGLTF/Runtime/UniGLTF/IO/MeshIO/MeshUploader.cs +++ b/Packages/UniGLTF/Runtime/UniGLTF/IO/MeshIO/MeshUploader.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Threading.Tasks; using UnityEngine; using UnityEngine.Profiling; @@ -10,6 +9,36 @@ namespace UniGLTF internal static class MeshUploader { private const float FrameWeight = 100.0f; + private const float EpsilonSqr = 1e-16f; + + private static bool HasAnyNonZero(Vector3[] delta) + { + if (delta == null) return false; + for (int i = 0; i < delta.Length; i++) + { + if (delta[i].sqrMagnitude > EpsilonSqr) return true; + } + return false; + } + + private static Vector3[] CalcDeltaNormalsForWeight( + Vector3[] baseNormals, + Vector3[] deltaNormalsAt100, + float weight01) + { + var delta = new Vector3[baseNormals.Length]; + for (int i = 0; i < baseNormals.Length; i++) + { + var n = baseNormals[i] + deltaNormalsAt100[i] * weight01; + var sqr = n.sqrMagnitude; + if (sqr > float.Epsilon) + { + n *= 1.0f / Mathf.Sqrt(sqr); + } + delta[i] = n - baseNormals[i]; + } + return delta; + } /// /// 頂点情報をMeshに対して送る @@ -41,28 +70,59 @@ namespace UniGLTF } } - private static async Task BuildBlendShapeAsync(IAwaitCaller awaitCaller, Mesh mesh, BlendShape blendShape, - Vector3[] emptyVertices) + private static async Task BuildBlendShapeAsync( + IAwaitCaller awaitCaller, + Mesh mesh, + BlendShape blendShape, + Vector3[] emptyVertices, + Vector3[] baseNormals) { Vector3[] positions = null; Vector3[] normals = null; await awaitCaller.Run(() => { - positions = blendShape.Positions.ToArray(); - if (blendShape.Normals != null) - { - normals = blendShape.Normals.ToArray(); - } + positions = blendShape.Positions != null ? blendShape.Positions.ToArray() : Array.Empty(); + normals = blendShape.Normals != null ? blendShape.Normals.ToArray() : Array.Empty(); }); Profiler.BeginSample("MeshUploader.BuildBlendShapeAsync"); + var hasPositions = positions.Length == mesh.vertexCount; + var hasNormals = normals.Length == mesh.vertexCount; + + // Unity blendshape normal interpolation can look slightly off when vertex deltas are all-zero + // (normal-only targets). Add a few intermediate frames with renormalized normals to keep the + // interpolation closer to the intended (unit-length) normals across weights. + if (hasNormals && !HasAnyNonZero(positions) && HasAnyNonZero(normals)) + { + foreach (var frameWeight in new[] { 25.0f, 50.0f, 75.0f }) + { + var deltaNormals = CalcDeltaNormalsForWeight(baseNormals, normals, frameWeight / 100.0f); + mesh.AddBlendShapeFrame(blendShape.Name, frameWeight, + emptyVertices, + deltaNormals, + null + ); + } + + mesh.AddBlendShapeFrame(blendShape.Name, FrameWeight, + emptyVertices, + normals, + null + ); + + Profiler.EndSample(); + return; + } + if (positions.Length > 0) { - if (positions.Length == mesh.vertexCount) + if (hasPositions) { + var deltaNormals = hasNormals ? normals : null; + mesh.AddBlendShapeFrame(blendShape.Name, FrameWeight, positions, - normals.Length == mesh.vertexCount && normals.Length == positions.Length ? normals : null, + deltaNormals, null ); } @@ -76,7 +136,7 @@ namespace UniGLTF // add empty blend shape for keep blend shape index mesh.AddBlendShapeFrame(blendShape.Name, FrameWeight, emptyVertices, - null, + normals.Length == mesh.vertexCount ? normals : null, null ); } @@ -129,10 +189,16 @@ namespace UniGLTF if (data.BlendShapes.Count > 0) { + var baseNormals = mesh.normals; var emptyVertices = new Vector3[mesh.vertexCount]; foreach (var blendShape in data.BlendShapes) { - await BuildBlendShapeAsync(awaitCaller, mesh, blendShape, emptyVertices); + await BuildBlendShapeAsync( + awaitCaller, + mesh, + blendShape, + emptyVertices, + baseNormals); } } diff --git a/Packages/UniGLTF/Runtime/UniGLTF/IO/SByte3.cs b/Packages/UniGLTF/Runtime/UniGLTF/IO/SByte3.cs new file mode 100644 index 000000000..4dfcd1c75 --- /dev/null +++ b/Packages/UniGLTF/Runtime/UniGLTF/IO/SByte3.cs @@ -0,0 +1,25 @@ +using System; +using System.Runtime.InteropServices; + +namespace UniGLTF +{ + [Serializable, StructLayout(LayoutKind.Sequential, Pack = 1)] + public readonly struct SByte3 : IEquatable + { + public readonly sbyte x; + public readonly sbyte y; + public readonly sbyte z; + + public SByte3(sbyte _x, sbyte _y, sbyte _z) + { + x = _x; + y = _y; + z = _z; + } + + public bool Equals(SByte3 other) + { + return x == other.x && y == other.y && z == other.z; + } + } +} diff --git a/Packages/UniGLTF/Runtime/UniGLTF/IO/SByte3.cs.meta b/Packages/UniGLTF/Runtime/UniGLTF/IO/SByte3.cs.meta new file mode 100644 index 000000000..cc7a12d6e --- /dev/null +++ b/Packages/UniGLTF/Runtime/UniGLTF/IO/SByte3.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 93c772ed657e23c448e9b036d9c9071c diff --git a/Packages/UniGLTF/Runtime/UniGLTF/IO/Short3.cs b/Packages/UniGLTF/Runtime/UniGLTF/IO/Short3.cs new file mode 100644 index 000000000..264b39fc6 --- /dev/null +++ b/Packages/UniGLTF/Runtime/UniGLTF/IO/Short3.cs @@ -0,0 +1,25 @@ +using System; +using System.Runtime.InteropServices; + +namespace UniGLTF +{ + [Serializable, StructLayout(LayoutKind.Sequential, Pack = 1)] + public readonly struct Short3 : IEquatable + { + public readonly short x; + public readonly short y; + public readonly short z; + + public Short3(short _x, short _y, short _z) + { + x = _x; + y = _y; + z = _z; + } + + public bool Equals(Short3 other) + { + return x == other.x && y == other.y && z == other.z; + } + } +} diff --git a/Packages/UniGLTF/Runtime/UniGLTF/IO/Short3.cs.meta b/Packages/UniGLTF/Runtime/UniGLTF/IO/Short3.cs.meta new file mode 100644 index 000000000..c3e51f6dd --- /dev/null +++ b/Packages/UniGLTF/Runtime/UniGLTF/IO/Short3.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f74b316e688844c428c7b45dcb38ee54 From 371ceeff86506bb886e1f526c8db9c1f907b7079 Mon Sep 17 00:00:00 2001 From: tdw46 Date: Tue, 30 Dec 2025 11:04:27 -0500 Subject: [PATCH 3/3] Remove interpolation steps for blendshape normals as they were not necessary. --- .../Runtime/UniGLTF/IO/MeshIO/MeshUploader.cs | 62 +------------------ 1 file changed, 2 insertions(+), 60 deletions(-) diff --git a/Packages/UniGLTF/Runtime/UniGLTF/IO/MeshIO/MeshUploader.cs b/Packages/UniGLTF/Runtime/UniGLTF/IO/MeshIO/MeshUploader.cs index e276c7847..1a7aab51c 100644 --- a/Packages/UniGLTF/Runtime/UniGLTF/IO/MeshIO/MeshUploader.cs +++ b/Packages/UniGLTF/Runtime/UniGLTF/IO/MeshIO/MeshUploader.cs @@ -9,36 +9,6 @@ namespace UniGLTF internal static class MeshUploader { private const float FrameWeight = 100.0f; - private const float EpsilonSqr = 1e-16f; - - private static bool HasAnyNonZero(Vector3[] delta) - { - if (delta == null) return false; - for (int i = 0; i < delta.Length; i++) - { - if (delta[i].sqrMagnitude > EpsilonSqr) return true; - } - return false; - } - - private static Vector3[] CalcDeltaNormalsForWeight( - Vector3[] baseNormals, - Vector3[] deltaNormalsAt100, - float weight01) - { - var delta = new Vector3[baseNormals.Length]; - for (int i = 0; i < baseNormals.Length; i++) - { - var n = baseNormals[i] + deltaNormalsAt100[i] * weight01; - var sqr = n.sqrMagnitude; - if (sqr > float.Epsilon) - { - n *= 1.0f / Mathf.Sqrt(sqr); - } - delta[i] = n - baseNormals[i]; - } - return delta; - } /// /// 頂点情報をMeshに対して送る @@ -74,8 +44,7 @@ namespace UniGLTF IAwaitCaller awaitCaller, Mesh mesh, BlendShape blendShape, - Vector3[] emptyVertices, - Vector3[] baseNormals) + Vector3[] emptyVertices) { Vector3[] positions = null; Vector3[] normals = null; @@ -89,31 +58,6 @@ namespace UniGLTF var hasPositions = positions.Length == mesh.vertexCount; var hasNormals = normals.Length == mesh.vertexCount; - // Unity blendshape normal interpolation can look slightly off when vertex deltas are all-zero - // (normal-only targets). Add a few intermediate frames with renormalized normals to keep the - // interpolation closer to the intended (unit-length) normals across weights. - if (hasNormals && !HasAnyNonZero(positions) && HasAnyNonZero(normals)) - { - foreach (var frameWeight in new[] { 25.0f, 50.0f, 75.0f }) - { - var deltaNormals = CalcDeltaNormalsForWeight(baseNormals, normals, frameWeight / 100.0f); - mesh.AddBlendShapeFrame(blendShape.Name, frameWeight, - emptyVertices, - deltaNormals, - null - ); - } - - mesh.AddBlendShapeFrame(blendShape.Name, FrameWeight, - emptyVertices, - normals, - null - ); - - Profiler.EndSample(); - return; - } - if (positions.Length > 0) { if (hasPositions) @@ -189,7 +133,6 @@ namespace UniGLTF if (data.BlendShapes.Count > 0) { - var baseNormals = mesh.normals; var emptyVertices = new Vector3[mesh.vertexCount]; foreach (var blendShape in data.BlendShapes) { @@ -197,8 +140,7 @@ namespace UniGLTF awaitCaller, mesh, blendShape, - emptyVertices, - baseNormals); + emptyVertices); } }