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 01/36] 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 02/36] =?UTF-8?q?Fix:=20blendshape=20normals=20import=20(U?= =?UTF-8?q?niGLTF/UniVRM)=20=20=20-=20Properly=20decode=20morph=20target?= =?UTF-8?q?=20VEC3=20accessors=20(normalized=20BYTE/SHORT,=20etc.)=20for?= =?UTF-8?q?=20POSITION/NORMAL/TANGENT=20=20=20-=20Preserve=20imported=20mo?= =?UTF-8?q?rph=20normal=20deltas=20(remove=20heuristic=20recompute)=20=20?= =?UTF-8?q?=20-=20Add=20intermediate=20frames=20for=20normal-only=20target?= =?UTF-8?q?s=20to=20stabilize=20Unity=E2=80=99s=20normal=20interpolation?= =?UTF-8?q?=20=20=20-=20Copy=20all=20blendshape=20frames=20when=20duplicat?= =?UTF-8?q?ing=20meshes=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 03/36] 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); } } From ad46c324e7b5257b4241a842e16a6c08a3972ff5 Mon Sep 17 00:00:00 2001 From: Aaron Franke Date: Mon, 5 Jan 2026 01:16:15 -0800 Subject: [PATCH 04/36] Fix crash when reading texture transform bind without scale --- Packages/VRM10/Runtime/IO/ExpressionExtensions.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Packages/VRM10/Runtime/IO/ExpressionExtensions.cs b/Packages/VRM10/Runtime/IO/ExpressionExtensions.cs index 847a922d2..4ed7d05d3 100644 --- a/Packages/VRM10/Runtime/IO/ExpressionExtensions.cs +++ b/Packages/VRM10/Runtime/IO/ExpressionExtensions.cs @@ -60,7 +60,15 @@ namespace UniVRM10 var binding = default(UniVRM10.MaterialUVBinding?); if (material != null) { - var (scale, offset) = UniGLTF.TextureTransform.VerticalFlipScaleOffset(new Vector2(bind.Scale[0], bind.Scale[1]), new Vector2(bind.Offset[0], bind.Offset[1])); + // Default values: scale [1, 1], offset [0, 0] + Vector2 scaleVec = bind.Scale != null && bind.Scale.Length >= 2 + ? new Vector2(bind.Scale[0], bind.Scale[1]) + : new Vector2(1.0f, 1.0f); + Vector2 offsetVec = bind.Offset != null && bind.Offset.Length >= 2 + ? new Vector2(bind.Offset[0], bind.Offset[1]) + : new Vector2(0.0f, 0.0f); + + var (scale, offset) = UniGLTF.TextureTransform.VerticalFlipScaleOffset(scaleVec, offsetVec); try { From 3727eee6004aed4fd82cf91af856b6d4a4e080a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ansis=20M=C4=81li=C5=86=C5=A1?= Date: Thu, 8 Jan 2026 21:20:35 +0100 Subject: [PATCH 05/36] Fix RebuildHumanAvatar so it works in play mode --- Packages/UniGLTF/Runtime/UniHumanoid/HumanoidLoader.cs | 8 +++++++- .../VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs | 7 +++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Packages/UniGLTF/Runtime/UniHumanoid/HumanoidLoader.cs b/Packages/UniGLTF/Runtime/UniHumanoid/HumanoidLoader.cs index 61f07d70b..db3b5329d 100644 --- a/Packages/UniGLTF/Runtime/UniHumanoid/HumanoidLoader.cs +++ b/Packages/UniGLTF/Runtime/UniHumanoid/HumanoidLoader.cs @@ -55,7 +55,10 @@ namespace UniHumanoid /// HumanBone のマッピングを流用して、新たな Avatar を作り直す。 /// 古い Avatar は破棄する。 /// - public static void RebuildHumanAvatar(Animator animator) + /// + /// This method runs asynchronously only in play mode. + /// + public static async Awaitable RebuildHumanAvatar(Animator animator) { if (animator == null) { @@ -77,6 +80,9 @@ namespace UniHumanoid if (Application.isPlaying) { GameObject.Destroy(animator); + + // Else, the following AddComponent call will fail. + await Awaitable.NextFrameAsync(); } else { diff --git a/Packages/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs b/Packages/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs index 77269756d..5d0762c06 100644 --- a/Packages/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs +++ b/Packages/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs @@ -53,7 +53,10 @@ namespace VRM /// 対象モデルのルート /// 強制的にT-Pose化するか /// BlendShape の現状をbakeするか - public static void Execute(GameObject go, bool forceTPose, bool useCurrentBlendShapeWeight) + /// + /// This method runs asynchronously only in play mode. + /// + public static async Awaitable Execute(GameObject go, bool forceTPose, bool useCurrentBlendShapeWeight) { if (forceTPose) { @@ -83,7 +86,7 @@ namespace VRM // 回転とスケールが除去された新しいヒエラルキーからAvatarを作る if (go.TryGetComponent(out var animator)) { - HumanoidLoader.RebuildHumanAvatar(animator); + await HumanoidLoader.RebuildHumanAvatar(animator); } } From fbae0d857f73d9ce3b2a19c88967e725f0ce380d Mon Sep 17 00:00:00 2001 From: Udayshankar Ravikumar Date: Thu, 22 Jan 2026 01:34:43 +0530 Subject: [PATCH 06/36] Fix MToon material importers ignoring custom shader --- .../Import/Materials/BuiltInVrm10MToonMaterialImporter.cs | 2 +- .../URP/Import/Materials/UrpVrm10MToonMaterialImporter.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Packages/VRM10/Runtime/IO/Material/BuiltInRP/Import/Materials/BuiltInVrm10MToonMaterialImporter.cs b/Packages/VRM10/Runtime/IO/Material/BuiltInRP/Import/Materials/BuiltInVrm10MToonMaterialImporter.cs index 6fba92303..1a4f62fec 100644 --- a/Packages/VRM10/Runtime/IO/Material/BuiltInRP/Import/Materials/BuiltInVrm10MToonMaterialImporter.cs +++ b/Packages/VRM10/Runtime/IO/Material/BuiltInRP/Import/Materials/BuiltInVrm10MToonMaterialImporter.cs @@ -44,7 +44,7 @@ namespace UniVRM10 // use material.name, because material name may renamed in GltfParser. matDesc = new MaterialDescriptor( m.name, - Shader.Find(MToon10Meta.UnityShaderName), + Shader, null, Vrm10MToonTextureImporter.EnumerateAllTextures(data, m, mtoon).ToDictionary(tuple => tuple.key, tuple => tuple.Item2.Item2), TryGetAllFloats(m, mtoon).ToDictionary(tuple => tuple.key, tuple => tuple.value), diff --git a/Packages/VRM10/Runtime/IO/Material/URP/Import/Materials/UrpVrm10MToonMaterialImporter.cs b/Packages/VRM10/Runtime/IO/Material/URP/Import/Materials/UrpVrm10MToonMaterialImporter.cs index ada10ec84..24eb37478 100644 --- a/Packages/VRM10/Runtime/IO/Material/URP/Import/Materials/UrpVrm10MToonMaterialImporter.cs +++ b/Packages/VRM10/Runtime/IO/Material/URP/Import/Materials/UrpVrm10MToonMaterialImporter.cs @@ -39,7 +39,7 @@ namespace UniVRM10 // use material.name, because material name may renamed in GltfParser. matDesc = new MaterialDescriptor( m.name, - Shader.Find(MToon10Meta.UnityUrpShaderName), + Shader, null, Vrm10MToonTextureImporter.EnumerateAllTextures(data, m, mtoon).ToDictionary(tuple => tuple.key, tuple => tuple.Item2.Item2), BuiltInVrm10MToonMaterialImporter.TryGetAllFloats(m, mtoon).ToDictionary(tuple => tuple.key, tuple => tuple.value), From 69bf34b7d7b74d70557516d785180000f75f1090 Mon Sep 17 00:00:00 2001 From: Gene Tailor Date: Mon, 16 Feb 2026 20:49:05 +0000 Subject: [PATCH 07/36] Added Stopwatch timer to measure overall prefab import time. --- Packages/VRM/Editor/Format/vrmAssetPostprocessor.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Packages/VRM/Editor/Format/vrmAssetPostprocessor.cs b/Packages/VRM/Editor/Format/vrmAssetPostprocessor.cs index 1ebaaee95..75494b092 100644 --- a/Packages/VRM/Editor/Format/vrmAssetPostprocessor.cs +++ b/Packages/VRM/Editor/Format/vrmAssetPostprocessor.cs @@ -63,6 +63,9 @@ namespace VRM return; } + System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); + sw.Start(); + /// /// これは EditorApplication.delayCall により呼び出される。 /// @@ -91,6 +94,9 @@ namespace VRM editor.SaveAsAsset(loaded); } + sw.Stop(); + + Debug.Log($"Import complete [importMs={sw.ElapsedMilliseconds}]"); }; using (var data = new GlbFileParser(vrmPath).Parse()) From c1123cf543fc444c7fce98311e50859b665b4c0c Mon Sep 17 00:00:00 2001 From: Gene Tailor Date: Mon, 16 Feb 2026 21:05:57 +0000 Subject: [PATCH 08/36] Added ProfilerMarkers for more detailed profiling info without the need for Deep Profiling. --- .../UniGLTF/ScriptedImporter/TextureExtractor.cs | 16 ++++++++++++++-- .../Editor/Format/VRMEditorImporterContext.cs | 11 +++++++++++ .../VRM/Editor/Format/vrmAssetPostprocessor.cs | 8 ++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Packages/UniGLTF/Editor/UniGLTF/ScriptedImporter/TextureExtractor.cs b/Packages/UniGLTF/Editor/UniGLTF/ScriptedImporter/TextureExtractor.cs index 05fb2110a..40fd43d10 100644 --- a/Packages/UniGLTF/Editor/UniGLTF/ScriptedImporter/TextureExtractor.cs +++ b/Packages/UniGLTF/Editor/UniGLTF/ScriptedImporter/TextureExtractor.cs @@ -1,9 +1,10 @@ using System; using System.Collections.Generic; using System.IO; -using UnityEngine; -using UnityEditor; using System.Linq; +using Unity.Profiling; +using UnityEditor; +using UnityEngine; namespace UniGLTF { @@ -20,6 +21,9 @@ namespace UniGLTF private readonly IReadOnlyDictionary m_subAssets; UnityPath m_textureDirectory; + private static ProfilerMarker s_MarkerStartExtractTextures = new ProfilerMarker("Start Extract Textures"); + private static ProfilerMarker s_MarkerDelayedExtractTextures = new ProfilerMarker("Delayed Extract Textures"); + public TextureExtractor(GltfData data, UnityPath textureDirectory, IReadOnlyDictionary subAssets) { m_data = data; @@ -77,14 +81,20 @@ namespace UniGLTF Action addRemap, Action> onCompleted = null) { + s_MarkerStartExtractTextures.Begin(); + var extractor = new TextureExtractor(data, textureDirectory, subAssets); foreach (var param in textureDescriptorGenerator.Get().GetEnumerable()) { extractor.Extract(param.SubAssetKey, param); } + s_MarkerStartExtractTextures.End(); + EditorApplication.delayCall += () => { + s_MarkerDelayedExtractTextures.Begin(); + // Wait for the texture assets to be imported foreach (var (key, targetPath) in extractor.Textures) @@ -97,6 +107,8 @@ namespace UniGLTF } } + s_MarkerDelayedExtractTextures.End(); + if (onCompleted != null) { onCompleted(extractor.Textures.Values); diff --git a/Packages/VRM/Editor/Format/VRMEditorImporterContext.cs b/Packages/VRM/Editor/Format/VRMEditorImporterContext.cs index 7bfc9826d..4f4cda57e 100644 --- a/Packages/VRM/Editor/Format/VRMEditorImporterContext.cs +++ b/Packages/VRM/Editor/Format/VRMEditorImporterContext.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using UniGLTF; +using Unity.Profiling; using UnityEditor; using UnityEngine; @@ -12,6 +13,8 @@ namespace VRM UnityPath m_prefabPath; List m_paths = new List(); + private static ProfilerMarker s_MarkerConvertAndExtractImages = new ProfilerMarker("Convert and Extract Images"); + public ITextureDescriptorGenerator TextureDescriptorGenerator => m_context.TextureDescriptorGenerator; public VRMEditorImporterContext(VRMImporterContext context, UnityPath prefabPath) @@ -76,22 +79,27 @@ namespace VRM /// public void ConvertAndExtractImages(Action> onTextureReloaded) { + s_MarkerConvertAndExtractImages.Begin(); + // // convert images(metallic roughness, occlusion map) // var task = m_context.LoadMaterialsAsync(new ImmediateCaller()); if (!task.IsCompleted) { + s_MarkerConvertAndExtractImages.End(); throw new Exception(); } if (task.IsFaulted) { if (task.Exception is AggregateException ae && ae.InnerExceptions.Count == 1) { + s_MarkerConvertAndExtractImages.End(); throw ae.InnerException; } else { + s_MarkerConvertAndExtractImages.End(); throw task.Exception; } } @@ -100,6 +108,7 @@ namespace VRM var task2 = m_context.ReadMetaAsync(new ImmediateCaller()); if (!task2.IsCompleted || task2.IsCanceled || task2.IsFaulted) { + s_MarkerConvertAndExtractImages.End(); throw new Exception(); } @@ -110,6 +119,8 @@ namespace VRM var vrmTextures = new BuiltInVrmMaterialDescriptorGenerator(m_context.VRM); var dirName = $"{m_prefabPath.FileNameWithoutExtension}.Textures"; TextureExtractor.ExtractTextures(m_context.Data, m_prefabPath.Parent.Child(dirName), m_context.TextureDescriptorGenerator, subAssets, (_x, _y) => { }, onTextureReloaded); + + s_MarkerConvertAndExtractImages.End(); } void SaveAsAsset(SubAssetKey _, UnityEngine.Object o) diff --git a/Packages/VRM/Editor/Format/vrmAssetPostprocessor.cs b/Packages/VRM/Editor/Format/vrmAssetPostprocessor.cs index 75494b092..e201d871a 100644 --- a/Packages/VRM/Editor/Format/vrmAssetPostprocessor.cs +++ b/Packages/VRM/Editor/Format/vrmAssetPostprocessor.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using UniGLTF; +using Unity.Profiling; using UnityEditor; using UnityEngine; @@ -10,6 +11,8 @@ namespace VRM { public class vrmAssetPostprocessor : AssetPostprocessor { + private static ProfilerMarker s_MarkerCreatePrefab = new ProfilerMarker("Create Prefab"); + #if !VRM_STOP_ASSETPOSTPROCESSOR static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { @@ -76,6 +79,8 @@ namespace VRM /// Action> onCompleted = texturePaths => { + s_MarkerCreatePrefab.Begin(); + var map = texturePaths .Select(x => x.LoadAsset()) .ToDictionary(x => new SubAssetKey(x), x => x as UnityEngine.Object); @@ -94,6 +99,9 @@ namespace VRM editor.SaveAsAsset(loaded); } + + s_MarkerCreatePrefab.End(); + sw.Stop(); Debug.Log($"Import complete [importMs={sw.ElapsedMilliseconds}]"); From dfe2c83a26d587536db820421578822761b77a8b Mon Sep 17 00:00:00 2001 From: Gene Tailor Date: Mon, 16 Feb 2026 21:24:13 +0000 Subject: [PATCH 09/36] Added StartAssetEditing and StopAssetEditing around creation of prefab and related assets in onCompleted callback delegate inside vrmAssetPostprocessor.ImportVrmAndCreatePrefab() to batch Unity asset importing. --- .../Editor/Format/vrmAssetPostprocessor.cs | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/Packages/VRM/Editor/Format/vrmAssetPostprocessor.cs b/Packages/VRM/Editor/Format/vrmAssetPostprocessor.cs index e201d871a..282ccc374 100644 --- a/Packages/VRM/Editor/Format/vrmAssetPostprocessor.cs +++ b/Packages/VRM/Editor/Format/vrmAssetPostprocessor.cs @@ -84,21 +84,35 @@ namespace VRM var map = texturePaths .Select(x => x.LoadAsset()) .ToDictionary(x => new SubAssetKey(x), x => x as UnityEngine.Object); - var settings = new ImporterContextSettings(); - // 確実に Dispose するために敢えて再パースしている - using (var data = new GlbFileParser(vrmPath).Parse()) - using (var context = new VRMImporterContext(new VRMData(data), externalObjectMap: map, settings: settings)) + try { - var editor = new VRMEditorImporterContext(context, prefabPath); - foreach (var textureInfo in context.TextureDescriptorGenerator.Get().GetEnumerable()) - { - TextureImporterConfigurator.Configure(textureInfo, context.TextureFactory.ExternalTextures); - } - var loaded = context.Load(); - editor.SaveAsAsset(loaded); - } + AssetDatabase.StartAssetEditing(); + var settings = new ImporterContextSettings(); + + // 確実に Dispose するために敢えて再パースしている + using (var data = new GlbFileParser(vrmPath).Parse()) + using (var context = new VRMImporterContext(new VRMData(data), externalObjectMap: map, settings: settings)) + { + var editor = new VRMEditorImporterContext(context, prefabPath); + foreach (var textureInfo in context.TextureDescriptorGenerator.Get().GetEnumerable()) + { + TextureImporterConfigurator.Configure(textureInfo, context.TextureFactory.ExternalTextures); + } + var loaded = context.Load(); + editor.SaveAsAsset(loaded); + } + + } + catch (Exception e) + { + Debug.LogException(e); + } + finally + { + AssetDatabase.StopAssetEditing(); + } s_MarkerCreatePrefab.End(); From 2b4ecb5e73b359796c9001b28d521678a64fa1f1 Mon Sep 17 00:00:00 2001 From: Gene Tailor Date: Mon, 16 Feb 2026 21:37:44 +0000 Subject: [PATCH 10/36] Applied same *AssetEditing improvements to the Texture extracting section of TextureExtractor.ExtractTextures() to batch the importing of created Textures. --- .../ScriptedImporter/TextureExtractor.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Packages/UniGLTF/Editor/UniGLTF/ScriptedImporter/TextureExtractor.cs b/Packages/UniGLTF/Editor/UniGLTF/ScriptedImporter/TextureExtractor.cs index 40fd43d10..cac1a48c8 100644 --- a/Packages/UniGLTF/Editor/UniGLTF/ScriptedImporter/TextureExtractor.cs +++ b/Packages/UniGLTF/Editor/UniGLTF/ScriptedImporter/TextureExtractor.cs @@ -84,9 +84,22 @@ namespace UniGLTF s_MarkerStartExtractTextures.Begin(); var extractor = new TextureExtractor(data, textureDirectory, subAssets); - foreach (var param in textureDescriptorGenerator.Get().GetEnumerable()) + try { - extractor.Extract(param.SubAssetKey, param); + AssetDatabase.StartAssetEditing(); + + foreach (var param in textureDescriptorGenerator.Get().GetEnumerable()) + { + extractor.Extract(param.SubAssetKey, param); + } + } + catch (Exception e) + { + Debug.LogException(e); + } + finally + { + AssetDatabase.StopAssetEditing(); } s_MarkerStartExtractTextures.End(); From ec4761ba31dc73196ac589fc1c3db6369d824e13 Mon Sep 17 00:00:00 2001 From: Victor Beaupuy Date: Sun, 22 Feb 2026 01:20:38 +0100 Subject: [PATCH 11/36] let com.unity.cloud.gltfast claim .glb format --- Packages/UniGLTF/Editor/UniGLTF.Editor.asmdef | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Packages/UniGLTF/Editor/UniGLTF.Editor.asmdef b/Packages/UniGLTF/Editor/UniGLTF.Editor.asmdef index 46cc4b400..b3736347e 100644 --- a/Packages/UniGLTF/Editor/UniGLTF.Editor.asmdef +++ b/Packages/UniGLTF/Editor/UniGLTF.Editor.asmdef @@ -37,7 +37,17 @@ "name": "com.atteneder.gltfast", "expression": "", "define": "UNIGLTF_DISABLE_DEFAULT_GLTF_IMPORTER" - } + }, + { + "name": "com.unity.cloud.gltfast", + "expression": "", + "define": "UNIGLTF_DISABLE_DEFAULT_GLB_IMPORTER" + }, + { + "name": "com.unity.cloud.gltfast", + "expression": "", + "define": "UNIGLTF_DISABLE_DEFAULT_GLTF_IMPORTER" + } ], "noEngineReferences": false } \ No newline at end of file From 43bad5636faad82fdb3f93655733b15aed260be4 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Thu, 26 Feb 2026 23:12:43 +0900 Subject: [PATCH 12/36] mv AwaitCaller to UniGLTF.Utils --- .../UniGLTF/Runtime/{UniGLTF/IO => Utils}/AwaitCaller.meta | 0 .../Runtime/{UniGLTF/IO => Utils}/AwaitCaller/IAwaitCaller.cs | 0 .../{UniGLTF/IO => Utils}/AwaitCaller/IAwaitCaller.cs.meta | 0 .../{UniGLTF/IO => Utils}/AwaitCaller/ImmediateCaller.cs | 0 .../{UniGLTF/IO => Utils}/AwaitCaller/ImmediateCaller.cs.meta | 0 .../IO => Utils}/AwaitCaller/NextFrameTaskScheduler.cs | 0 .../IO => Utils}/AwaitCaller/NextFrameTaskScheduler.cs.meta | 0 .../IO => Utils}/AwaitCaller/RuntimeOnlyAwaitCaller.cs | 0 .../IO => Utils}/AwaitCaller/RuntimeOnlyAwaitCaller.cs.meta | 0 .../AwaitCaller/RuntimeOnlyNoThreadAwaitCaller.cs | 0 .../AwaitCaller/RuntimeOnlyNoThreadAwaitCaller.cs.meta | 0 .../IO => Utils}/AwaitCaller/TinyManagedTaskScheduler.cs | 0 .../IO => Utils}/AwaitCaller/TinyManagedTaskScheduler.cs.meta | 0 Packages/VRM/Tests/VRM.Tests.asmdef | 4 ++-- Packages/VRM10/Tests/VRM10.Tests.asmdef | 4 ++-- 15 files changed, 4 insertions(+), 4 deletions(-) rename Packages/UniGLTF/Runtime/{UniGLTF/IO => Utils}/AwaitCaller.meta (100%) rename Packages/UniGLTF/Runtime/{UniGLTF/IO => Utils}/AwaitCaller/IAwaitCaller.cs (100%) rename Packages/UniGLTF/Runtime/{UniGLTF/IO => Utils}/AwaitCaller/IAwaitCaller.cs.meta (100%) rename Packages/UniGLTF/Runtime/{UniGLTF/IO => Utils}/AwaitCaller/ImmediateCaller.cs (100%) rename Packages/UniGLTF/Runtime/{UniGLTF/IO => Utils}/AwaitCaller/ImmediateCaller.cs.meta (100%) rename Packages/UniGLTF/Runtime/{UniGLTF/IO => Utils}/AwaitCaller/NextFrameTaskScheduler.cs (100%) rename Packages/UniGLTF/Runtime/{UniGLTF/IO => Utils}/AwaitCaller/NextFrameTaskScheduler.cs.meta (100%) rename Packages/UniGLTF/Runtime/{UniGLTF/IO => Utils}/AwaitCaller/RuntimeOnlyAwaitCaller.cs (100%) rename Packages/UniGLTF/Runtime/{UniGLTF/IO => Utils}/AwaitCaller/RuntimeOnlyAwaitCaller.cs.meta (100%) rename Packages/UniGLTF/Runtime/{UniGLTF/IO => Utils}/AwaitCaller/RuntimeOnlyNoThreadAwaitCaller.cs (100%) rename Packages/UniGLTF/Runtime/{UniGLTF/IO => Utils}/AwaitCaller/RuntimeOnlyNoThreadAwaitCaller.cs.meta (100%) rename Packages/UniGLTF/Runtime/{UniGLTF/IO => Utils}/AwaitCaller/TinyManagedTaskScheduler.cs (100%) rename Packages/UniGLTF/Runtime/{UniGLTF/IO => Utils}/AwaitCaller/TinyManagedTaskScheduler.cs.meta (100%) diff --git a/Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller.meta b/Packages/UniGLTF/Runtime/Utils/AwaitCaller.meta similarity index 100% rename from Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller.meta rename to Packages/UniGLTF/Runtime/Utils/AwaitCaller.meta diff --git a/Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/IAwaitCaller.cs b/Packages/UniGLTF/Runtime/Utils/AwaitCaller/IAwaitCaller.cs similarity index 100% rename from Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/IAwaitCaller.cs rename to Packages/UniGLTF/Runtime/Utils/AwaitCaller/IAwaitCaller.cs diff --git a/Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/IAwaitCaller.cs.meta b/Packages/UniGLTF/Runtime/Utils/AwaitCaller/IAwaitCaller.cs.meta similarity index 100% rename from Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/IAwaitCaller.cs.meta rename to Packages/UniGLTF/Runtime/Utils/AwaitCaller/IAwaitCaller.cs.meta diff --git a/Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/ImmediateCaller.cs b/Packages/UniGLTF/Runtime/Utils/AwaitCaller/ImmediateCaller.cs similarity index 100% rename from Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/ImmediateCaller.cs rename to Packages/UniGLTF/Runtime/Utils/AwaitCaller/ImmediateCaller.cs diff --git a/Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/ImmediateCaller.cs.meta b/Packages/UniGLTF/Runtime/Utils/AwaitCaller/ImmediateCaller.cs.meta similarity index 100% rename from Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/ImmediateCaller.cs.meta rename to Packages/UniGLTF/Runtime/Utils/AwaitCaller/ImmediateCaller.cs.meta diff --git a/Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/NextFrameTaskScheduler.cs b/Packages/UniGLTF/Runtime/Utils/AwaitCaller/NextFrameTaskScheduler.cs similarity index 100% rename from Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/NextFrameTaskScheduler.cs rename to Packages/UniGLTF/Runtime/Utils/AwaitCaller/NextFrameTaskScheduler.cs diff --git a/Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/NextFrameTaskScheduler.cs.meta b/Packages/UniGLTF/Runtime/Utils/AwaitCaller/NextFrameTaskScheduler.cs.meta similarity index 100% rename from Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/NextFrameTaskScheduler.cs.meta rename to Packages/UniGLTF/Runtime/Utils/AwaitCaller/NextFrameTaskScheduler.cs.meta diff --git a/Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/RuntimeOnlyAwaitCaller.cs b/Packages/UniGLTF/Runtime/Utils/AwaitCaller/RuntimeOnlyAwaitCaller.cs similarity index 100% rename from Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/RuntimeOnlyAwaitCaller.cs rename to Packages/UniGLTF/Runtime/Utils/AwaitCaller/RuntimeOnlyAwaitCaller.cs diff --git a/Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/RuntimeOnlyAwaitCaller.cs.meta b/Packages/UniGLTF/Runtime/Utils/AwaitCaller/RuntimeOnlyAwaitCaller.cs.meta similarity index 100% rename from Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/RuntimeOnlyAwaitCaller.cs.meta rename to Packages/UniGLTF/Runtime/Utils/AwaitCaller/RuntimeOnlyAwaitCaller.cs.meta diff --git a/Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/RuntimeOnlyNoThreadAwaitCaller.cs b/Packages/UniGLTF/Runtime/Utils/AwaitCaller/RuntimeOnlyNoThreadAwaitCaller.cs similarity index 100% rename from Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/RuntimeOnlyNoThreadAwaitCaller.cs rename to Packages/UniGLTF/Runtime/Utils/AwaitCaller/RuntimeOnlyNoThreadAwaitCaller.cs diff --git a/Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/RuntimeOnlyNoThreadAwaitCaller.cs.meta b/Packages/UniGLTF/Runtime/Utils/AwaitCaller/RuntimeOnlyNoThreadAwaitCaller.cs.meta similarity index 100% rename from Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/RuntimeOnlyNoThreadAwaitCaller.cs.meta rename to Packages/UniGLTF/Runtime/Utils/AwaitCaller/RuntimeOnlyNoThreadAwaitCaller.cs.meta diff --git a/Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/TinyManagedTaskScheduler.cs b/Packages/UniGLTF/Runtime/Utils/AwaitCaller/TinyManagedTaskScheduler.cs similarity index 100% rename from Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/TinyManagedTaskScheduler.cs rename to Packages/UniGLTF/Runtime/Utils/AwaitCaller/TinyManagedTaskScheduler.cs diff --git a/Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/TinyManagedTaskScheduler.cs.meta b/Packages/UniGLTF/Runtime/Utils/AwaitCaller/TinyManagedTaskScheduler.cs.meta similarity index 100% rename from Packages/UniGLTF/Runtime/UniGLTF/IO/AwaitCaller/TinyManagedTaskScheduler.cs.meta rename to Packages/UniGLTF/Runtime/Utils/AwaitCaller/TinyManagedTaskScheduler.cs.meta diff --git a/Packages/VRM/Tests/VRM.Tests.asmdef b/Packages/VRM/Tests/VRM.Tests.asmdef index 5d30901d9..4b755ecfd 100644 --- a/Packages/VRM/Tests/VRM.Tests.asmdef +++ b/Packages/VRM/Tests/VRM.Tests.asmdef @@ -7,9 +7,9 @@ "GUID:8d76e605759c3f64a957d63ef96ada7c", "GUID:5f875fdc81c40184c8333b9d63c6ddd5", "GUID:f9fe54bb3090be448aa10ac92648a614", - "GUID:da3e51d19d51a544fa14d43fee843098", "GUID:27619889b8ba8c24980f49ee34dbb44a", - "GUID:0acc523941302664db1f4e527237feb3" + "GUID:0acc523941302664db1f4e527237feb3", + "GUID:1cd941934d098654fa21a13f28346412" ], "includePlatforms": [ "Editor" diff --git a/Packages/VRM10/Tests/VRM10.Tests.asmdef b/Packages/VRM10/Tests/VRM10.Tests.asmdef index afe244664..2a71a7b8b 100644 --- a/Packages/VRM10/Tests/VRM10.Tests.asmdef +++ b/Packages/VRM10/Tests/VRM10.Tests.asmdef @@ -7,9 +7,9 @@ "GUID:8d76e605759c3f64a957d63ef96ada7c", "GUID:5f875fdc81c40184c8333b9d63c6ddd5", "GUID:f9fe54bb3090be448aa10ac92648a614", - "GUID:da3e51d19d51a544fa14d43fee843098", "GUID:27619889b8ba8c24980f49ee34dbb44a", - "GUID:0acc523941302664db1f4e527237feb3" + "GUID:0acc523941302664db1f4e527237feb3", + "GUID:1cd941934d098654fa21a13f28346412" ], "includePlatforms": [ "Editor" From 3b3756af898dc5d2ac7e49aa4b3decf79f7ef647 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Thu, 26 Feb 2026 23:32:43 +0900 Subject: [PATCH 13/36] Fix compilation error due to Awaitable not being referenced. Use IAwaitable with no external dependencies. --- .../Runtime/UniHumanoid/HumanoidLoader.cs | 27 ++++++++++++------- .../SkinnedMeshUtility/VRMBoneNormalizer.cs | 14 ++++++---- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/Packages/UniGLTF/Runtime/UniHumanoid/HumanoidLoader.cs b/Packages/UniGLTF/Runtime/UniHumanoid/HumanoidLoader.cs index db3b5329d..d118f2caa 100644 --- a/Packages/UniGLTF/Runtime/UniHumanoid/HumanoidLoader.cs +++ b/Packages/UniGLTF/Runtime/UniHumanoid/HumanoidLoader.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; +using UniGLTF; using UniGLTF.Utils; using UnityEngine; @@ -51,14 +53,16 @@ namespace UniHumanoid x => x); /// - /// Avatar を保持する既存の Animatorヒエラルキーの Transform を変更したのちに、 - /// HumanBone のマッピングを流用して、新たな Avatar を作り直す。 - /// 古い Avatar は破棄する。 + /// Recreates an animiator's humanoid avatar. + /// The old Avatar is discarded. /// - /// - /// This method runs asynchronously only in play mode. - /// - public static async Awaitable RebuildHumanAvatar(Animator animator) + public static void RebuildHumanAvatar(Animator animator) + { + var task = RebuildHumanAvatarAsync(animator, new ImmediateCaller()); + task.Wait(); + } + + public static async Task RebuildHumanAvatarAsync(Animator animator, IAwaitCaller awaitCaller) { if (animator == null) { @@ -76,19 +80,22 @@ namespace UniHumanoid newAvatar.name = "re-created"; // var newAvatar = LoadHumanoidAvatarFromAnimator(animator); - // Animator.avatar を代入したときに副作用でTransformが変更されるのを回避するために削除します。 + // 1. Delete this to avoid changing Transform as a side effect when assigning Animator.avatar. if (Application.isPlaying) { GameObject.Destroy(animator); + // https://github.com/vrm-c/UniVRM/pull/2764 + // Require IAwaitCaller that has NextFrame capability. RuntimeOnlyAwaitCaller etc. not ImmediateCaller. // Else, the following AddComponent call will fail. - await Awaitable.NextFrameAsync(); + await awaitCaller.NextFrame(); } else { GameObject.DestroyImmediate(animator); } - // 新たに AddComponent する + + // 2. Attach a new one target.AddComponent().avatar = newAvatar; } } diff --git a/Packages/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs b/Packages/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs index 5d0762c06..c03838d28 100644 --- a/Packages/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs +++ b/Packages/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using UniGLTF; using UniGLTF.MeshUtility; using UniGLTF.Utils; @@ -53,10 +54,13 @@ namespace VRM /// 対象モデルのルート /// 強制的にT-Pose化するか /// BlendShape の現状をbakeするか - /// - /// This method runs asynchronously only in play mode. - /// - public static async Awaitable Execute(GameObject go, bool forceTPose, bool useCurrentBlendShapeWeight) + public static void Execute(GameObject go, bool forceTPose, bool useCurrentBlendShapeWeight) + { + var task = ExecuteAsync(go, forceTPose, useCurrentBlendShapeWeight, new ImmediateCaller()); + task.Wait(); + } + + public static async Task ExecuteAsync(GameObject go, bool forceTPose, bool useCurrentBlendShapeWeight, IAwaitCaller awaitCaller) { if (forceTPose) { @@ -86,7 +90,7 @@ namespace VRM // 回転とスケールが除去された新しいヒエラルキーからAvatarを作る if (go.TryGetComponent(out var animator)) { - await HumanoidLoader.RebuildHumanAvatar(animator); + await HumanoidLoader.RebuildHumanAvatarAsync(animator, awaitCaller); } } From 5af97a569e35e6fc63f9e176e36ddb36ba9bcac0 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Thu, 26 Feb 2026 23:57:20 +0900 Subject: [PATCH 14/36] throw if not IsCompleted --- Packages/UniGLTF/Runtime/UniHumanoid/HumanoidLoader.cs | 5 ++++- Packages/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Packages/UniGLTF/Runtime/UniHumanoid/HumanoidLoader.cs b/Packages/UniGLTF/Runtime/UniHumanoid/HumanoidLoader.cs index d118f2caa..77c68c458 100644 --- a/Packages/UniGLTF/Runtime/UniHumanoid/HumanoidLoader.cs +++ b/Packages/UniGLTF/Runtime/UniHumanoid/HumanoidLoader.cs @@ -59,7 +59,10 @@ namespace UniHumanoid public static void RebuildHumanAvatar(Animator animator) { var task = RebuildHumanAvatarAsync(animator, new ImmediateCaller()); - task.Wait(); + if (!task.IsCompleted) + { + throw new Exception("task not completed"); + } } public static async Task RebuildHumanAvatarAsync(Animator animator, IAwaitCaller awaitCaller) diff --git a/Packages/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs b/Packages/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs index c03838d28..ec0b8bf09 100644 --- a/Packages/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs +++ b/Packages/VRM/Runtime/SkinnedMeshUtility/VRMBoneNormalizer.cs @@ -57,7 +57,10 @@ namespace VRM public static void Execute(GameObject go, bool forceTPose, bool useCurrentBlendShapeWeight) { var task = ExecuteAsync(go, forceTPose, useCurrentBlendShapeWeight, new ImmediateCaller()); - task.Wait(); + if (!task.IsCompleted) + { + throw new Exception("task not completed"); + } } public static async Task ExecuteAsync(GameObject go, bool forceTPose, bool useCurrentBlendShapeWeight, IAwaitCaller awaitCaller) From 635e2a6b2df20ed55264af28f4697812811debd6 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Fri, 23 Jan 2026 14:49:02 +0900 Subject: [PATCH 15/36] BoneTransformBindingMerger --- .../Expression/BoneTransformMerger.cs | 25 +++++++++++++++++++ .../Expression/BoneTransformMerger.cs.meta | 11 ++++++++ .../Components/Expression/ExpressionMerger.cs | 4 +++ 3 files changed, 40 insertions(+) create mode 100644 Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs create mode 100644 Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs.meta diff --git a/Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs b/Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs new file mode 100644 index 000000000..64134e6e6 --- /dev/null +++ b/Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + + +namespace UniVRM10 +{ + internal sealed class BoneTransformBindingMerger + { + public BoneTransformBindingMerger(Transform root) + { + + } + + public void Apply() + { + throw new NotImplementedException(); + } + + public void AccumulateValue(VRM10Expression clip, float value) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs.meta b/Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs.meta new file mode 100644 index 000000000..1e48344b2 --- /dev/null +++ b/Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fe2bf5e71647b0f4d93ca6c22bb9fb7f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs b/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs index dd54cb70d..45664fed4 100644 --- a/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs +++ b/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs @@ -23,6 +23,7 @@ namespace UniVRM10 MorphTargetBindingMerger m_morphTargetBindingMerger; MaterialValueBindingMerger m_materialValueBindingMerger; + BoneTransformBindingMerger m_boneTransformBindingMerger; public ExpressionMerger(VRM10ObjectExpression expressions, Transform root, bool isPrefabInstance) @@ -35,6 +36,7 @@ namespace UniVRM10 m_valueMap = new Dictionary(ExpressionKey.Comparer); m_morphTargetBindingMerger = new MorphTargetBindingMerger(m_clipMap, root); m_materialValueBindingMerger = new MaterialValueBindingMerger(m_clipMap, root, isPrefabInstance); + m_boneTransformBindingMerger = new BoneTransformBindingMerger(root); } /// @@ -50,6 +52,7 @@ namespace UniVRM10 m_morphTargetBindingMerger.Apply(); m_materialValueBindingMerger.Apply(); + m_boneTransformBindingMerger.Apply(); } private void AccumulateValue(ExpressionKey key, float value) @@ -69,6 +72,7 @@ namespace UniVRM10 m_morphTargetBindingMerger.AccumulateValue(key, value); m_materialValueBindingMerger.AccumulateValue(clip, value); + m_boneTransformBindingMerger.AccumulateValue(clip, value); } public void Dispose() From 38b636e417badf3b066c42e3c6c78ac31fad37bd Mon Sep 17 00:00:00 2001 From: ousttrue Date: Fri, 23 Jan 2026 21:45:29 +0900 Subject: [PATCH 16/36] Vrm10BoneTransfromExpression --- .../Expression/BoneTransformMerger.cs | 27 ++++++++++++++----- .../Components/Expression/ExpressionMerger.cs | 2 +- .../Vrm10BoneTransformExpression.cs | 19 +++++++++++++ .../Vrm10BoneTransformExpression.cs.meta | 11 ++++++++ 4 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs create mode 100644 Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs.meta diff --git a/Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs b/Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs index 64134e6e6..4bd642973 100644 --- a/Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs +++ b/Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs @@ -1,5 +1,5 @@ -using System; using System.Collections.Generic; +using UniGLTF; using UnityEngine; @@ -7,19 +7,32 @@ namespace UniVRM10 { internal sealed class BoneTransformBindingMerger { + Dictionary _acum = new(); + // Vrm10BoneTransformExpression[] _expressions; + Transform _root; + public BoneTransformBindingMerger(Transform root) { + // _expressions = root.GetComponentsInChildren(); + _root = root; + } + public void AccumulateValue(ExpressionKey key, float value) + { + _acum[key] = value; } public void Apply() { - throw new NotImplementedException(); - } - - public void AccumulateValue(VRM10Expression clip, float value) - { - throw new NotImplementedException(); + foreach (var expression in _root.GetComponentsInChildren()) + { + var transform = expression.transform; + var tr = expression.Expression.Transformation; + var m = Matrix4x4.TRS(tr.Translation, tr.Rotation, Vector3.one); + var w = m * transform.localToWorldMatrix; + var (t, r, s) = w.Decompose(); + transform.SetPositionAndRotation(t, r); + } } } } \ No newline at end of file diff --git a/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs b/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs index 45664fed4..d28bc2521 100644 --- a/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs +++ b/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs @@ -72,7 +72,7 @@ namespace UniVRM10 m_morphTargetBindingMerger.AccumulateValue(key, value); m_materialValueBindingMerger.AccumulateValue(clip, value); - m_boneTransformBindingMerger.AccumulateValue(clip, value); + m_boneTransformBindingMerger.AccumulateValue(key, value); } public void Dispose() diff --git a/Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs b/Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs new file mode 100644 index 000000000..9c7f55ec6 --- /dev/null +++ b/Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs @@ -0,0 +1,19 @@ +using System; +using UniGLTF.Utils; +using UnityEngine; + +namespace UniVRM10 +{ + [DisallowMultipleComponent] + public class Vrm10BoneTransformExpression : MonoBehaviour + { + [Serializable] + public class BoneTransformExpression + { + public ExpressionPreset Preset; + public EuclideanTransform Transformation = new EuclideanTransform(Quaternion.identity, Vector3.zero); + } + + public BoneTransformExpression Expression; + } +} \ No newline at end of file diff --git a/Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs.meta b/Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs.meta new file mode 100644 index 000000000..1622ca50d --- /dev/null +++ b/Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1520687d88c34af4eb8c16bb37ee0391 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From aa90bcc85417083e2b537c165d9d9f98bc370792 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Mon, 26 Jan 2026 15:26:25 +0900 Subject: [PATCH 17/36] SetLocalPositionAndRotation --- .../Expression/BoneTransformMerger.cs | 17 ++++++++++------- .../Components/Expression/ExpressionMerger.cs | 5 +++-- .../Expression/Vrm10BoneTransformExpression.cs | 7 +++++-- .../Components/Vrm10Runtime/Vrm10Runtime.cs | 2 +- .../Vrm10Runtime/Vrm10RuntimeExpression.cs | 10 ++++++---- 5 files changed, 25 insertions(+), 16 deletions(-) diff --git a/Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs b/Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs index 4bd642973..114edf0c5 100644 --- a/Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs +++ b/Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using UniGLTF; +using UniGLTF.Utils; using UnityEngine; @@ -22,16 +23,18 @@ namespace UniVRM10 _acum[key] = value; } - public void Apply() + public void Apply(IReadOnlyDictionary initPose) { foreach (var expression in _root.GetComponentsInChildren()) { - var transform = expression.transform; - var tr = expression.Expression.Transformation; - var m = Matrix4x4.TRS(tr.Translation, tr.Rotation, Vector3.one); - var w = m * transform.localToWorldMatrix; - var (t, r, s) = w.Decompose(); - transform.SetPositionAndRotation(t, r); + if (initPose.TryGetValue(expression.transform, out var init)) + { + var weight = _acum.GetValueOrDefault(expression.Expression.ExpressionKey, 0); + expression.transform.SetLocalPositionAndRotation( + init.LocalPosition + expression.Expression.Translation * weight, + Quaternion.Slerp(init.LocalRotation, init.LocalRotation * expression.Expression.Rotation, weight) + ); + } } } } diff --git a/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs b/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs index d28bc2521..4d85995f5 100644 --- a/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs +++ b/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using UniGLTF.Utils; using UnityEngine; namespace UniVRM10 @@ -43,7 +44,7 @@ namespace UniVRM10 /// まとめて反映する。1フレームに1回呼び出されることを想定 /// /// - public void SetValues(Dictionary expressionWeights) + public void SetValues(Dictionary expressionWeights, IReadOnlyDictionary initPose) { foreach (var (key, weight) in expressionWeights) { @@ -52,7 +53,7 @@ namespace UniVRM10 m_morphTargetBindingMerger.Apply(); m_materialValueBindingMerger.Apply(); - m_boneTransformBindingMerger.Apply(); + m_boneTransformBindingMerger.Apply(initPose); } private void AccumulateValue(ExpressionKey key, float value) diff --git a/Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs b/Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs index 9c7f55ec6..1f33be137 100644 --- a/Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs +++ b/Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs @@ -10,8 +10,11 @@ namespace UniVRM10 [Serializable] public class BoneTransformExpression { - public ExpressionPreset Preset; - public EuclideanTransform Transformation = new EuclideanTransform(Quaternion.identity, Vector3.zero); + public ExpressionPreset Preset = ExpressionPreset.custom; + public string Name = "custom"; + public ExpressionKey ExpressionKey => new(Preset, Name); + public Quaternion Rotation = Quaternion.identity; + public Vector3 Translation = Vector3.zero; } public BoneTransformExpression Expression; diff --git a/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs b/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs index e702496b1..46429762e 100644 --- a/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs +++ b/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs @@ -153,7 +153,7 @@ namespace UniVRM10 // 5. Apply Expression // LookAt の角度制限などはこちらで処理されます。 - Expression.Process(eyeDirection); + Expression.Process(eyeDirection, _initPose); // 6. SpringBone SpringBone.Process(Time.deltaTime); diff --git a/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeExpression.cs b/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeExpression.cs index 0a38b0481..276735700 100644 --- a/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeExpression.cs +++ b/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeExpression.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using UniGLTF.Utils; +using UnityEngine; namespace UniVRM10 { @@ -58,9 +60,9 @@ namespace UniVRM10 _eyeDirectionApplicable = null; } - internal void Process(LookAtEyeDirection inputEyeDirection) + internal void Process(LookAtEyeDirection inputEyeDirection, IReadOnlyDictionary initPose = null) { - Apply(inputEyeDirection); + Apply(inputEyeDirection, initPose); } public IDictionary GetWeights() @@ -112,7 +114,7 @@ namespace UniVRM10 /// 入力 Weight を基に、Validation を行い実際にモデルに適用される Weights を計算し、Merger を介して適用する。 /// この際、LookAt の情報を pull してそれも適用する。 /// - private void Apply(LookAtEyeDirection inputEyeDirection) + private void Apply(LookAtEyeDirection inputEyeDirection, IReadOnlyDictionary initPose) { // 1. Validate user input, and Output as actual weights. _validator.Validate(_inputWeights, _actualWeights, @@ -123,7 +125,7 @@ namespace UniVRM10 _eyeDirectionApplicable?.Apply(_actualEyeDirection, _actualWeights); // 3. Set actual weights to raw blendshapes. - _merger.SetValues(_actualWeights); + _merger.SetValues(_actualWeights, initPose); BlinkOverrideRate = blink; LookAtOverrideRate = lookAt; From 1e84c70bc2d0e50d5d46409cd4ee188463b5a608 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Thu, 5 Feb 2026 15:10:43 +0900 Subject: [PATCH 18/36] generate Format/ExpressionsNodeTransform --- .../VRM10/Editor/Vrm10SerializerGenerator.cs | 11 +- .../Format/ExpressionsNodeTransform.meta | 8 + .../Deserializer.g.cs | 145 ++++++++++++++++++ .../Deserializer.g.cs.meta | 11 ++ .../ExpressionsNodeTransform/Format.g.cs | 43 ++++++ .../ExpressionsNodeTransform/Format.g.cs.meta | 11 ++ .../ExpressionsNodeTransform/Serializer.g.cs | 141 +++++++++++++++++ .../Serializer.g.cs.meta | 11 ++ .../Format/SpringBoneLimit/Format.g.cs | 4 +- 9 files changed, 382 insertions(+), 3 deletions(-) create mode 100644 Packages/VRM10/Runtime/Format/ExpressionsNodeTransform.meta create mode 100644 Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Deserializer.g.cs create mode 100644 Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Deserializer.g.cs.meta create mode 100644 Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Format.g.cs create mode 100644 Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Format.g.cs.meta create mode 100644 Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Serializer.g.cs create mode 100644 Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Serializer.g.cs.meta diff --git a/Packages/VRM10/Editor/Vrm10SerializerGenerator.cs b/Packages/VRM10/Editor/Vrm10SerializerGenerator.cs index ec5b753d9..cd3b24015 100644 --- a/Packages/VRM10/Editor/Vrm10SerializerGenerator.cs +++ b/Packages/VRM10/Editor/Vrm10SerializerGenerator.cs @@ -36,6 +36,7 @@ namespace UniVRM10 private const string Vrm10SpecDir = "vrm-specification/specification"; private const string Vrm10FormatGeneratedDir = "Packages/VRM10/Runtime/Format"; + private const string UniGltfFormatGeneratedDir = "Packages/UniGLTF/Runtime/UniGLTF/Format"; public static void Run(bool debug) { @@ -48,7 +49,7 @@ namespace UniVRM10 // VRMC_hdr_emissiveMultiplier new GenerateInfo( $"{Vrm10SpecDir}/VRMC_materials_hdr_emissiveMultiplier-1.0/schema/VRMC_materials_hdr_emissiveMultiplier.json", - "Assets/UniGLTF/Runtime/UniGLTF/Format/ExtensionsAndExtras/EmissiveMultiplier" + $"{UniGltfFormatGeneratedDir}/ExtensionsAndExtras/EmissiveMultiplier" ), // VRMC_vrm @@ -94,6 +95,14 @@ namespace UniVRM10 $"{Vrm10SpecDir}/VRMC_springBone_limit-1.0/schema/VRMC_springBone_limit.schema.json", $"{Vrm10FormatGeneratedDir}/SpringBoneLimit" ), + + // VRMC_vrm_expressions_node_transform-1.0 + // (experimental) + // https://github.com/ousttrue/vrm-specification/tree/VRMC_vrm_expression_joint + new GenerateInfo( + $"{Vrm10SpecDir}/VRMC_vrm_expressions_node_transform-1.0/schema/VRMC_vrm_expressions_node_transform.schema.json", + $"{Vrm10FormatGeneratedDir}/ExpressionsNodeTransform" + ), }; foreach (var arg in args) diff --git a/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform.meta b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform.meta new file mode 100644 index 000000000..e63834052 --- /dev/null +++ b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d80842db26fe76645bf195c10b9c6bf7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Deserializer.g.cs b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Deserializer.g.cs new file mode 100644 index 000000000..dfa8a3469 --- /dev/null +++ b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Deserializer.g.cs @@ -0,0 +1,145 @@ +// This file is generated from JsonSchema. Don't modify this source code. +using UniJSON; +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UniGLTF.Extensions.VRMC_vrm_expressions_node_transform { + +public static class GltfDeserializer +{ + public static readonly Utf8String ExtensionNameUtf8 = Utf8String.From(VRMC_vrm_expressions_node_transform.ExtensionName); + +public static bool TryGet(UniGLTF.glTFExtension src, out VRMC_vrm_expressions_node_transform extension) +{ + if(src is UniGLTF.glTFExtensionImport extensions) + { + foreach(var kv in extensions.ObjectItems()) + { + if(kv.Key.GetUtf8String() == ExtensionNameUtf8) + { + extension = Deserialize(kv.Value); + return true; + } + } + } + + extension = default; + return false; +} + + +public static VRMC_vrm_expressions_node_transform Deserialize(JsonNode parsed) +{ + var value = new VRMC_vrm_expressions_node_transform(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="extensions"){ + value.Extensions = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="extras"){ + value.Extras = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="nodeTransformBinds"){ + value.NodeTransformBinds = Deserialize_NodeTransformBinds(kv.Value); + continue; + } + + } + return value; +} + +public static List Deserialize_NodeTransformBinds(JsonNode parsed) +{ + var value = new List(); + foreach(var x in parsed.ArrayItems()) + { + value.Add(Deserialize_NodeTransformBinds_ITEM(x)); + } + return value; +} + +public static NodeTransformBind Deserialize_NodeTransformBinds_ITEM(JsonNode parsed) +{ + var value = new NodeTransformBind(); + + foreach(var kv in parsed.ObjectItems()) + { + var key = kv.Key.GetString(); + + if(key=="extensions"){ + value.Extensions = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="extras"){ + value.Extras = new glTFExtensionImport(kv.Value); + continue; + } + + if(key=="node"){ + value.Node = kv.Value.GetInt32(); + continue; + } + + if(key=="rotation"){ + value.Rotation = __nodeTransformBinds_ITEM_Deserialize_Rotation(kv.Value); + continue; + } + + if(key=="scale"){ + value.Scale = __nodeTransformBinds_ITEM_Deserialize_Scale(kv.Value); + continue; + } + + if(key=="translation"){ + value.Translation = __nodeTransformBinds_ITEM_Deserialize_Translation(kv.Value); + continue; + } + + } + return value; +} + +public static float[] __nodeTransformBinds_ITEM_Deserialize_Rotation(JsonNode parsed) +{ + var value = new float[parsed.GetArrayCount()]; + int i=0; + foreach(var x in parsed.ArrayItems()) + { + value[i++] = x.GetSingle(); + } + return value; +} + +public static float[] __nodeTransformBinds_ITEM_Deserialize_Scale(JsonNode parsed) +{ + var value = new float[parsed.GetArrayCount()]; + int i=0; + foreach(var x in parsed.ArrayItems()) + { + value[i++] = x.GetSingle(); + } + return value; +} + +public static float[] __nodeTransformBinds_ITEM_Deserialize_Translation(JsonNode parsed) +{ + var value = new float[parsed.GetArrayCount()]; + int i=0; + foreach(var x in parsed.ArrayItems()) + { + value[i++] = x.GetSingle(); + } + return value; +} + +} // GltfDeserializer +} // UniGLTF diff --git a/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Deserializer.g.cs.meta b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Deserializer.g.cs.meta new file mode 100644 index 000000000..4d946ab7e --- /dev/null +++ b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Deserializer.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0a0544899292fe54e961d98e2f7d755c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Format.g.cs b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Format.g.cs new file mode 100644 index 000000000..48421d4b1 --- /dev/null +++ b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Format.g.cs @@ -0,0 +1,43 @@ +// This file is generated from JsonSchema. Don't modify this source code. +using System; +using System.Collections.Generic; + + +namespace UniGLTF.Extensions.VRMC_vrm_expressions_node_transform +{ + + public class NodeTransformBind + { + // Dictionary object with extension-specific objects. + public object Extensions; + + // Application-specific data. + public object Extras; + + // The node index. + public int? Node; + + // The node's unit quaternion rotation in the order (x, y, z, w), where w is the scalar. + public float[] Rotation; + + // The node's non-uniform scale, given as the scaling factors along the x, y, and z axes. + public float[] Scale; + + // The node's translation along the x, y, and z axes. + public float[] Translation; + } + + public class VRMC_vrm_expressions_node_transform + { + public const string ExtensionName = "VRMC_vrm_expressions_node_transform"; + + // Dictionary object with extension-specific objects. + public object Extensions; + + // Application-specific data. + public object Extras; + + // Specify a node transform + public List NodeTransformBinds; + } +} diff --git a/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Format.g.cs.meta b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Format.g.cs.meta new file mode 100644 index 000000000..311817f13 --- /dev/null +++ b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Format.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3f6d0369358084249b383bea969b16a6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Serializer.g.cs b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Serializer.g.cs new file mode 100644 index 000000000..a3f38a515 --- /dev/null +++ b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Serializer.g.cs @@ -0,0 +1,141 @@ +// This file is generated from JsonSchema. Don't modify this source code. +using System; +using System.Collections.Generic; +using System.Linq; +using UniJSON; + +namespace UniGLTF.Extensions.VRMC_vrm_expressions_node_transform { + + static public class GltfSerializer + { + + public static void SerializeTo(ref UniGLTF.glTFExtension dst, VRMC_vrm_expressions_node_transform extension) + { + if (dst is glTFExtensionImport) + { + throw new NotImplementedException(); + } + + if (!(dst is glTFExtensionExport extensions)) + { + extensions = new glTFExtensionExport(); + dst = extensions; + } + + var f = new JsonFormatter(); + Serialize(f, extension); + extensions.Add(VRMC_vrm_expressions_node_transform.ExtensionName, f.GetStoreBytes()); + } + + +public static void Serialize(JsonFormatter f, VRMC_vrm_expressions_node_transform value) +{ + f.BeginMap(); + + + if(value.Extensions!=null){ + f.Key("extensions"); + (value.Extensions as glTFExtension).Serialize(f); + } + + if(value.Extras!=null){ + f.Key("extras"); + (value.Extras as glTFExtension).Serialize(f); + } + + if(value.NodeTransformBinds!=null&&value.NodeTransformBinds.Count()>=1){ + f.Key("nodeTransformBinds"); + Serialize_NodeTransformBinds(f, value.NodeTransformBinds); + } + + f.EndMap(); +} + +public static void Serialize_NodeTransformBinds(JsonFormatter f, List value) +{ + f.BeginList(); + + foreach(var item in value) + { + Serialize_NodeTransformBinds_ITEM(f, item); + + } + f.EndList(); +} + +public static void Serialize_NodeTransformBinds_ITEM(JsonFormatter f, NodeTransformBind value) +{ + f.BeginMap(); + + + if(value.Extensions!=null){ + f.Key("extensions"); + (value.Extensions as glTFExtension).Serialize(f); + } + + if(value.Extras!=null){ + f.Key("extras"); + (value.Extras as glTFExtension).Serialize(f); + } + + if(value.Node.HasValue){ + f.Key("node"); + f.Value(value.Node.GetValueOrDefault()); + } + + if(value.Rotation!=null&&value.Rotation.Count()>=4){ + f.Key("rotation"); + __nodeTransformBinds_ITEM_Serialize_Rotation(f, value.Rotation); + } + + if(value.Scale!=null&&value.Scale.Count()>=3){ + f.Key("scale"); + __nodeTransformBinds_ITEM_Serialize_Scale(f, value.Scale); + } + + if(value.Translation!=null&&value.Translation.Count()>=3){ + f.Key("translation"); + __nodeTransformBinds_ITEM_Serialize_Translation(f, value.Translation); + } + + f.EndMap(); +} + +public static void __nodeTransformBinds_ITEM_Serialize_Rotation(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + +public static void __nodeTransformBinds_ITEM_Serialize_Scale(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + +public static void __nodeTransformBinds_ITEM_Serialize_Translation(JsonFormatter f, float[] value) +{ + f.BeginList(); + + foreach(var item in value) + { + f.Value(item); + + } + f.EndList(); +} + + } // class +} // namespace diff --git a/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Serializer.g.cs.meta b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Serializer.g.cs.meta new file mode 100644 index 000000000..773237b4d --- /dev/null +++ b/Packages/VRM10/Runtime/Format/ExpressionsNodeTransform/Serializer.g.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 16a49328d069d9f47840c104597af4a4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/VRM10/Runtime/Format/SpringBoneLimit/Format.g.cs b/Packages/VRM10/Runtime/Format/SpringBoneLimit/Format.g.cs index f820abb77..655df84f1 100644 --- a/Packages/VRM10/Runtime/Format/SpringBoneLimit/Format.g.cs +++ b/Packages/VRM10/Runtime/Format/SpringBoneLimit/Format.g.cs @@ -44,10 +44,10 @@ namespace UniGLTF.Extensions.VRMC_springBone_limit // Application-specific data. public object Extras; - // The phi angle of the spherical limit in radians. If the phi angle is set to π or greater, the angle will be interpreted as π by the implementation. + // The pitch angle of the spherical limit in radians. If the pitch angle is set to π or greater, the angle will be interpreted as π by the implementation. public float? Pitch; - // The theta angle of the spherical limit in radians. If the theta angle is set to π/2 or greater, the angle will be interpreted as π/2 by the implementation. + // The yaw angle of the spherical limit in radians. If the yaw angle is set to π/2 or greater, the angle will be interpreted as π/2 by the implementation. public float? Yaw; // The rotation from the default orientation of the spherical limit. The rotation is represented as a quaternion (x, y, z, w), where w is the scalar. From e04d0248af457c86770efb3cb132121def94a96f Mon Sep 17 00:00:00 2001 From: ousttrue Date: Fri, 6 Feb 2026 15:58:00 +0900 Subject: [PATCH 19/36] impl importer, exporter and editor --- .../Expression/ExpressionEditorHelper.cs | 15 ++ .../Expression/PreviewFaceRenderer.cs | 4 + .../ReorderableNodeTransformBindingList.cs | 131 ++++++++++++++++++ ...orderableNodeTransformBindingList.cs.meta} | 2 +- .../Expression/SerializedExpressionEditor.cs | 45 +++--- .../Expression/BoneTransformMerger.cs | 41 ------ .../Components/Expression/ExpressionMerger.cs | 13 +- .../Expression/NodeTransformBindingMerger.cs | 51 +++++++ ...eta => NodeTransformBindingMerger.cs.meta} | 0 .../Expression/NodeTransformtBinding.cs | 53 +++++++ .../Expression/NodeTransformtBinding.cs.meta | 11 ++ .../Expression/Preview/PreviewSceneManager.cs | 47 +++++-- .../Components/Expression/VRM10Expression.cs | 6 + .../Vrm10BoneTransformExpression.cs | 22 --- .../Components/Vrm10Runtime/Vrm10Runtime.cs | 4 +- .../Vrm10Runtime/Vrm10RuntimeExpression.cs | 16 ++- .../VRM10/Runtime/IO/ExpressionExtensions.cs | 25 ++++ Packages/VRM10/Runtime/IO/Vrm10Exporter.cs | 52 ++++++- Packages/VRM10/Runtime/IO/Vrm10Importer.cs | 20 +++ 19 files changed, 452 insertions(+), 106 deletions(-) create mode 100644 Packages/VRM10/Editor/Components/Expression/ReorderableNodeTransformBindingList.cs rename Packages/VRM10/{Runtime/Components/Expression/Vrm10BoneTransformExpression.cs.meta => Editor/Components/Expression/ReorderableNodeTransformBindingList.cs.meta} (83%) delete mode 100644 Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs create mode 100644 Packages/VRM10/Runtime/Components/Expression/NodeTransformBindingMerger.cs rename Packages/VRM10/Runtime/Components/Expression/{BoneTransformMerger.cs.meta => NodeTransformBindingMerger.cs.meta} (100%) create mode 100644 Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs create mode 100644 Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs.meta delete mode 100644 Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs diff --git a/Packages/VRM10/Editor/Components/Expression/ExpressionEditorHelper.cs b/Packages/VRM10/Editor/Components/Expression/ExpressionEditorHelper.cs index 4791a460a..bc23218ea 100644 --- a/Packages/VRM10/Editor/Components/Expression/ExpressionEditorHelper.cs +++ b/Packages/VRM10/Editor/Components/Expression/ExpressionEditorHelper.cs @@ -99,6 +99,21 @@ namespace UniVRM10 } } + public static bool Vec3Prop(Rect rect, SerializedProperty prop) + { + var oldValue = prop.vector3Value; + var newValue = EditorGUI.Vector3Field(rect, prop.displayName, oldValue); + if (newValue != oldValue) + { + prop.vector3Value = newValue; + return true; + } + else + { + return false; + } + } + static Rect AdvanceRect(ref float x, float y, float w, float h) { var rect = new Rect(x, y, w, h); diff --git a/Packages/VRM10/Editor/Components/Expression/PreviewFaceRenderer.cs b/Packages/VRM10/Editor/Components/Expression/PreviewFaceRenderer.cs index 4020fcf06..db49a99c0 100644 --- a/Packages/VRM10/Editor/Components/Expression/PreviewFaceRenderer.cs +++ b/Packages/VRM10/Editor/Components/Expression/PreviewFaceRenderer.cs @@ -110,6 +110,10 @@ namespace UniVRM10 int subMeshCount = item.Mesh.subMeshCount; for (int i = 0; i < subMeshCount; i++) { + if (item.SkinnedMeshRenderer != null) + { + item.SkinnedMeshRenderer.BakeMesh(item.Mesh); + } m_previewUtility.DrawMesh(item.Mesh, item.Position, item.Rotation, item.Materials[i], i); diff --git a/Packages/VRM10/Editor/Components/Expression/ReorderableNodeTransformBindingList.cs b/Packages/VRM10/Editor/Components/Expression/ReorderableNodeTransformBindingList.cs new file mode 100644 index 000000000..1903f7014 --- /dev/null +++ b/Packages/VRM10/Editor/Components/Expression/ReorderableNodeTransformBindingList.cs @@ -0,0 +1,131 @@ +using System; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; + + +namespace UniVRM10 +{ + public class ReorderableNodeTransformBindingList + { + ReorderableList m_ValuesList; + SerializedProperty m_valuesProp; + bool m_changed; + + public ReorderableNodeTransformBindingList(SerializedObject serializedObject, PreviewSceneManager previewSceneManager, int height) + { + m_valuesProp = serializedObject.FindProperty(nameof(VRM10Expression.NodeTransformBindings)); + m_ValuesList = new ReorderableList(serializedObject, m_valuesProp); + m_ValuesList.elementHeight = height * 4; + m_ValuesList.drawElementCallback = + (rect, index, isActive, isFocused) => + { + var element = m_valuesProp.GetArrayElementAtIndex(index); + rect.height -= 4; + rect.y += 2; + if (DrawNodeTransformBinding(rect, element, previewSceneManager, height)) + { + m_changed = true; + } + }; + } + + /// + /// NodeTransform List のElement描画 + /// + static bool DrawNodeTransformBinding(Rect position, SerializedProperty property, + PreviewSceneManager scene, int height) + { + bool changed = false; + if (scene != null) + { + var y = position.y; + var rect = new Rect(position.x, y, position.width, height); + int pathIndex; + if (ExpressionEditorHelper.StringPopup(rect, property.FindPropertyRelative(nameof(NodeTransformBinding.RelativePath)), scene.NodeTransformPathList, out pathIndex)) + { + changed = true; + } + + // T` + y += height; + rect = new Rect(position.x, y, position.width, height); + if (ExpressionEditorHelper.Vec3Prop(rect, property.FindPropertyRelative(nameof(NodeTransformBinding.OffsetTranslation)))) + { + changed = true; + } + + // R + y += height; + rect = new Rect(position.x, y, position.width, height); + if (EditorGUI.PropertyField(rect, property.FindPropertyRelative(nameof(NodeTransformBinding.OffsetRotation)))) + { + changed = true; + } + + // S + y += height; + rect = new Rect(position.x, y, position.width, height); + if (ExpressionEditorHelper.Vec3Prop(rect, property.FindPropertyRelative(nameof(NodeTransformBinding.TargetScale)))) + { + changed = true; + } + } + return changed; + } + + public void SetValues(NodeTransformBinding[] bindings) + { + m_valuesProp.ClearArray(); + m_valuesProp.arraySize = bindings.Length; + for (int i = 0; i < bindings.Length; ++i) + { + var item = m_valuesProp.GetArrayElementAtIndex(i); + + var endProperty = item.GetEndProperty(); + while (item.NextVisible(true)) + { + if (SerializedProperty.EqualContents(item, endProperty)) + { + break; + } + + switch (item.name) + { + case nameof(NodeTransformBinding.RelativePath): + item.stringValue = bindings[i].RelativePath; + break; + + case nameof(NodeTransformBinding.OffsetTranslation): + item.vector3Value = bindings[i].OffsetTranslation; + break; + + case nameof(NodeTransformBinding.OffsetRotation): + item.quaternionValue = bindings[i].OffsetRotation; + break; + + case nameof(NodeTransformBinding.TargetScale): + item.vector3Value = bindings[i].TargetScale; + break; + + default: + throw new Exception(); + } + } + } + + } + + public bool Draw(string label) + { + m_changed = false; + m_ValuesList.DoLayoutList(); + if (GUILayout.Button($"Clear {label}")) + { + m_changed = true; + m_valuesProp.arraySize = 0; + } + return m_changed; + } + } +} diff --git a/Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs.meta b/Packages/VRM10/Editor/Components/Expression/ReorderableNodeTransformBindingList.cs.meta similarity index 83% rename from Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs.meta rename to Packages/VRM10/Editor/Components/Expression/ReorderableNodeTransformBindingList.cs.meta index 1622ca50d..3c72f298b 100644 --- a/Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs.meta +++ b/Packages/VRM10/Editor/Components/Expression/ReorderableNodeTransformBindingList.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 1520687d88c34af4eb8c16bb37ee0391 +guid: 186e6dd105c86904497f3ac0dfd61212 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Packages/VRM10/Editor/Components/Expression/SerializedExpressionEditor.cs b/Packages/VRM10/Editor/Components/Expression/SerializedExpressionEditor.cs index 782d355a3..768cc15f0 100644 --- a/Packages/VRM10/Editor/Components/Expression/SerializedExpressionEditor.cs +++ b/Packages/VRM10/Editor/Components/Expression/SerializedExpressionEditor.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using UnityEditor; @@ -28,21 +29,24 @@ namespace UniVRM10 ReorderableMorphTargetBindingList m_morphTargetBindings; ReorderableMaterialColorBindingList m_materialColorBindings; ReorderableMaterialUVBindingList m_materialUVBindings; - + ReorderableNodeTransformBindingList m_nodeTransformBindings; #region Editor values bool m_changed; - static int s_Mode; static bool s_MorphTargetFoldout = true; static bool s_OptionFoldout; static bool s_ListFoldout; - static string[] MODES = new[]{ - "MorphTarget", - "Material Color", - "Texture Transform" - }; + enum ListMode + { + MorphTarget, + MaterialColor, + TextureTransform, + NodeTransform, + } + static ListMode s_Mode; + static string[] MODES = ((ListMode[])Enum.GetValues(typeof(ListMode))).Select(x => x.ToString()).ToArray(); PreviewMeshItem[] m_items; #endregion @@ -71,6 +75,7 @@ namespace UniVRM10 m_morphTargetBindings = new ReorderableMorphTargetBindingList(serializedObject, previewSceneManager, 20); m_materialColorBindings = new ReorderableMaterialColorBindingList(serializedObject, previewSceneManager?.MaterialNames, 20); m_materialUVBindings = new ReorderableMaterialUVBindingList(serializedObject, previewSceneManager?.MaterialNames, 20); + m_nodeTransformBindings = new ReorderableNodeTransformBindingList(serializedObject, previewSceneManager, 20); m_items = previewSceneManager.EnumRenderItems .Where(x => x.SkinnedMeshRenderer != null) @@ -109,33 +114,39 @@ namespace UniVRM10 if (s_ListFoldout) { EditorGUI.indentLevel++; - s_Mode = GUILayout.Toolbar(s_Mode, MODES); + s_Mode = (ListMode)GUILayout.Toolbar((int)s_Mode, MODES); switch (s_Mode) { - case 0: - // MorphTarget + case ListMode.MorphTarget: { - if (m_morphTargetBindings.Draw("MorphTarget")) + if (m_morphTargetBindings.Draw(s_Mode.ToString())) { m_changed = true; } } break; - case 1: - // Material + case ListMode.MaterialColor: { - if (m_materialColorBindings.Draw("MaterialColor")) + if (m_materialColorBindings.Draw(s_Mode.ToString())) { m_changed = true; } } break; - case 2: - // TextureTransform + case ListMode.TextureTransform: { - if (m_materialUVBindings.Draw("TextureTransform")) + if (m_materialUVBindings.Draw(s_Mode.ToString())) + { + m_changed = true; + } + } + break; + + case ListMode.NodeTransform: + { + if (m_nodeTransformBindings.Draw(s_Mode.ToString())) { m_changed = true; } diff --git a/Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs b/Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs deleted file mode 100644 index 114edf0c5..000000000 --- a/Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Collections.Generic; -using UniGLTF; -using UniGLTF.Utils; -using UnityEngine; - - -namespace UniVRM10 -{ - internal sealed class BoneTransformBindingMerger - { - Dictionary _acum = new(); - // Vrm10BoneTransformExpression[] _expressions; - Transform _root; - - public BoneTransformBindingMerger(Transform root) - { - // _expressions = root.GetComponentsInChildren(); - _root = root; - } - - public void AccumulateValue(ExpressionKey key, float value) - { - _acum[key] = value; - } - - public void Apply(IReadOnlyDictionary initPose) - { - foreach (var expression in _root.GetComponentsInChildren()) - { - if (initPose.TryGetValue(expression.transform, out var init)) - { - var weight = _acum.GetValueOrDefault(expression.Expression.ExpressionKey, 0); - expression.transform.SetLocalPositionAndRotation( - init.LocalPosition + expression.Expression.Translation * weight, - Quaternion.Slerp(init.LocalRotation, init.LocalRotation * expression.Expression.Rotation, weight) - ); - } - } - } - } -} \ No newline at end of file diff --git a/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs b/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs index 4d85995f5..de9a6a55f 100644 --- a/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs +++ b/Packages/VRM10/Runtime/Components/Expression/ExpressionMerger.cs @@ -24,10 +24,13 @@ namespace UniVRM10 MorphTargetBindingMerger m_morphTargetBindingMerger; MaterialValueBindingMerger m_materialValueBindingMerger; - BoneTransformBindingMerger m_boneTransformBindingMerger; + NodeTransformBindingMerger m_boneTransformBindingMerger; - public ExpressionMerger(VRM10ObjectExpression expressions, Transform root, bool isPrefabInstance) + public ExpressionMerger(VRM10ObjectExpression expressions, + Transform root, + bool isPrefabInstance, + IReadOnlyDictionary initPose) { m_clipMap = expressions.Clips.ToDictionary( x => expressions.CreateKey(x.Clip), @@ -37,14 +40,14 @@ namespace UniVRM10 m_valueMap = new Dictionary(ExpressionKey.Comparer); m_morphTargetBindingMerger = new MorphTargetBindingMerger(m_clipMap, root); m_materialValueBindingMerger = new MaterialValueBindingMerger(m_clipMap, root, isPrefabInstance); - m_boneTransformBindingMerger = new BoneTransformBindingMerger(root); + m_boneTransformBindingMerger = new NodeTransformBindingMerger(m_clipMap, root, initPose); } /// /// まとめて反映する。1フレームに1回呼び出されることを想定 /// /// - public void SetValues(Dictionary expressionWeights, IReadOnlyDictionary initPose) + public void SetValues(Dictionary expressionWeights) { foreach (var (key, weight) in expressionWeights) { @@ -53,7 +56,7 @@ namespace UniVRM10 m_morphTargetBindingMerger.Apply(); m_materialValueBindingMerger.Apply(); - m_boneTransformBindingMerger.Apply(initPose); + m_boneTransformBindingMerger.Apply(); } private void AccumulateValue(ExpressionKey key, float value) diff --git a/Packages/VRM10/Runtime/Components/Expression/NodeTransformBindingMerger.cs b/Packages/VRM10/Runtime/Components/Expression/NodeTransformBindingMerger.cs new file mode 100644 index 000000000..c972a8437 --- /dev/null +++ b/Packages/VRM10/Runtime/Components/Expression/NodeTransformBindingMerger.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using UniGLTF.Utils; +using UnityEngine; + + +namespace UniVRM10 +{ + internal sealed class NodeTransformBindingMerger + { + IReadOnlyDictionary _clipMap; + Transform _root; + IReadOnlyDictionary _initPose; + Dictionary _weightMap = new(); + + public NodeTransformBindingMerger( + IReadOnlyDictionary clipMap, + Transform root, + IReadOnlyDictionary initPose) + { + _clipMap = clipMap; + _root = root; + _initPose = initPose; + } + + public void AccumulateValue(ExpressionKey key, float value) + { + _weightMap[key] = value; + } + + public void Apply() + { + foreach (var (k, weight) in _weightMap) + { + if (_clipMap.TryGetValue(k, out var clip)) + { + foreach (var b in clip.NodeTransformBindings) + { + var node = _root.GetFromPath(b.RelativePath); + if (node != null) + { + if (_initPose.TryGetValue(node, out var init)) + { + b.Apply(node, init, weight); + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs.meta b/Packages/VRM10/Runtime/Components/Expression/NodeTransformBindingMerger.cs.meta similarity index 100% rename from Packages/VRM10/Runtime/Components/Expression/BoneTransformMerger.cs.meta rename to Packages/VRM10/Runtime/Components/Expression/NodeTransformBindingMerger.cs.meta diff --git a/Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs b/Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs new file mode 100644 index 000000000..d049fb858 --- /dev/null +++ b/Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs @@ -0,0 +1,53 @@ +using System; +using UniGLTF.Utils; +using UnityEngine; + +namespace UniVRM10 +{ + [Serializable] + public struct NodeTransformBinding + { + public string RelativePath; + + /// + /// t = init_t + offset_t * weight + /// disable if offset_t = (0, 0, 0) + /// + public Vector3 OffsetTranslation; + + /// + /// r = slerp(init_t, init_t * offset r, weight) + /// disable if rotation_t = (0, 0, 0, 1) + /// + public Quaternion OffsetRotation; + + /// + /// s = lerp(init_s, blend_s, weight) + /// disalbe if blend_s = init_s. maybe(1, 1, 1) + /// + public Vector3 TargetScale; + + /// + /// + /// + /// + /// + /// 0 to 1.0 + public NodeTransformBinding(string path, in Vector3 t, in Quaternion r, in Vector3 s) + { + RelativePath = path; + OffsetTranslation = t; + OffsetRotation = r; + TargetScale = s; + } + + public void Apply(Transform node, in TransformState init, float weight) + { + node.SetLocalPositionAndRotation( + init.LocalPosition + this.OffsetTranslation * weight, + Quaternion.Slerp(init.LocalRotation, init.LocalRotation * this.OffsetRotation, weight) + ); + node.localScale = Vector3.Lerp(init.LocalScale, this.TargetScale, weight); + } + } +} \ No newline at end of file diff --git a/Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs.meta b/Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs.meta new file mode 100644 index 000000000..bb06db17f --- /dev/null +++ b/Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dd660f7a77373234c85064f97150eef6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/VRM10/Runtime/Components/Expression/Preview/PreviewSceneManager.cs b/Packages/VRM10/Runtime/Components/Expression/Preview/PreviewSceneManager.cs index c93504344..30bdbe5a2 100644 --- a/Packages/VRM10/Runtime/Components/Expression/Preview/PreviewSceneManager.cs +++ b/Packages/VRM10/Runtime/Components/Expression/Preview/PreviewSceneManager.cs @@ -3,6 +3,7 @@ using System.Linq; using UnityEngine; using System; using UniGLTF; +using UniGLTF.Utils; namespace UniVRM10 @@ -16,6 +17,8 @@ namespace UniVRM10 public bool hasError; + private IReadOnlyDictionary m_defaultTransformStates; + #if UNITY_EDITOR public static PreviewSceneManager GetOrCreate(GameObject prefab) { @@ -85,7 +88,7 @@ namespace UniVRM10 private void Initialize(GameObject prefab) { hasError = false; - + Prefab = prefab; var materialNames = new List(); @@ -104,14 +107,14 @@ namespace UniVRM10 { dst = new Material(src); map.Add(src, dst); - + if (!PreviewMaterialUtil.TryCreateForPreview(dst, out var previewMaterialItem)) { hasError = true; // Return cloned material for preview return dst; } - + m_materialMap.Add(src.name, previewMaterialItem); materialNames.Add(src.name); @@ -119,6 +122,10 @@ namespace UniVRM10 return dst; }; + // initPose for nodeTransform + m_defaultTransformStates = transform.GetComponentsInChildren() + .ToDictionary(tf => tf, tf => new TransformState(tf)); + m_meshes = transform.Traverse() .Select(x => PreviewMeshItem.Create(x, transform, getOrCreateMaterial)) .Where(x => x != null) @@ -136,8 +143,13 @@ namespace UniVRM10 .Where(x => x.SkinnedMeshRenderer != null) .Select(x => x.Path) .ToArray(); + m_nodeTransformPathList = transform.GetComponentsInChildren() + .Select(x => x.RelativePathFrom(transform)) + .Where(x => x != null) + .ToArray() + ; - if(TryGetComponent(out var animator)) + if (TryGetComponent(out var animator)) { var head = animator.GetBoneTransform(HumanBodyBones.Head); if (head != null) @@ -184,6 +196,12 @@ namespace UniVRM10 get { return m_skinnedMeshRendererPathList; } } + string[] m_nodeTransformPathList; + public string[] NodeTransformPathList + { + get { return m_nodeTransformPathList; } + } + public string[] GetBlendShapeNames(int blendShapeMeshIndex) { if (blendShapeMeshIndex >= 0 && blendShapeMeshIndex < m_blendShapeMeshes.Length) @@ -228,19 +246,28 @@ namespace UniVRM10 return; } + // + // bake NodeTransform + // + foreach (var nodeTransform in bake.NodeTransformBindings) + { + var node = transform.GetFromPath(nodeTransform.RelativePath); + if (m_defaultTransformStates.TryGetValue(node, out var init)) + { + nodeTransform.Apply(node, init, weight); + } + } + // // Bake Expression // m_bounds = default(Bounds); if (m_meshes != null) { - if (bake != null) + foreach (var x in m_meshes) { - foreach (var x in m_meshes) - { - x.Bake(bake.MorphTargetBindings, weight); - m_bounds.Expand(x.Mesh.bounds.size); - } + x.Bake(bake.MorphTargetBindings, weight); + m_bounds.Expand(x.Mesh.bounds.size); } } diff --git a/Packages/VRM10/Runtime/Components/Expression/VRM10Expression.cs b/Packages/VRM10/Runtime/Components/Expression/VRM10Expression.cs index b75473e24..d0fd471d3 100644 --- a/Packages/VRM10/Runtime/Components/Expression/VRM10Expression.cs +++ b/Packages/VRM10/Runtime/Components/Expression/VRM10Expression.cs @@ -46,5 +46,11 @@ namespace UniVRM10 /// [SerializeField] public UniGLTF.Extensions.VRMC_vrm.ExpressionOverrideType OverrideMouth; + + /// + /// from UniVRM-132.0. experimental + /// + [SerializeField] + public NodeTransformBinding[] NodeTransformBindings = new NodeTransformBinding[] { }; } } diff --git a/Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs b/Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs deleted file mode 100644 index 1f33be137..000000000 --- a/Packages/VRM10/Runtime/Components/Expression/Vrm10BoneTransformExpression.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using UniGLTF.Utils; -using UnityEngine; - -namespace UniVRM10 -{ - [DisallowMultipleComponent] - public class Vrm10BoneTransformExpression : MonoBehaviour - { - [Serializable] - public class BoneTransformExpression - { - public ExpressionPreset Preset = ExpressionPreset.custom; - public string Name = "custom"; - public ExpressionKey ExpressionKey => new(Preset, Name); - public Quaternion Rotation = Quaternion.identity; - public Vector3 Translation = Vector3.zero; - } - - public BoneTransformExpression Expression; - } -} \ No newline at end of file diff --git a/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs b/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs index 46429762e..e22956304 100644 --- a/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs +++ b/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs @@ -77,7 +77,7 @@ namespace UniVRM10 } Constraints = instance.GetComponentsInChildren(); LookAt = new Vrm10RuntimeLookAt(instance, instance.Humanoid, ControlRig); - Expression = new Vrm10RuntimeExpression(instance, LookAt.EyeDirectionApplicable, isPrefabInstance); + Expression = new Vrm10RuntimeExpression(instance, LookAt.EyeDirectionApplicable, isPrefabInstance, initPose); SpringBone = springBoneRuntime; } @@ -153,7 +153,7 @@ namespace UniVRM10 // 5. Apply Expression // LookAt の角度制限などはこちらで処理されます。 - Expression.Process(eyeDirection, _initPose); + Expression.Process(eyeDirection); // 6. SpringBone SpringBone.Process(Time.deltaTime); diff --git a/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeExpression.cs b/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeExpression.cs index 276735700..3c13e449f 100644 --- a/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeExpression.cs +++ b/Packages/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeExpression.cs @@ -25,9 +25,13 @@ namespace UniVRM10 public float LookAtOverrideRate { get; private set; } public float MouthOverrideRate { get; private set; } - internal Vrm10RuntimeExpression(Vrm10Instance target, ILookAtEyeDirectionApplicable eyeDirectionApplicable, bool isPrefabInstance) + internal Vrm10RuntimeExpression(Vrm10Instance target, + ILookAtEyeDirectionApplicable eyeDirectionApplicable, + bool isPrefabInstance, + IReadOnlyDictionary initPose + ) { - _merger = new ExpressionMerger(target.Vrm.Expression, target.transform, isPrefabInstance); + _merger = new ExpressionMerger(target.Vrm.Expression, target.transform, isPrefabInstance, initPose); _keys = target.Vrm.Expression.Clips .Select(x => target.Vrm.Expression.CreateKey(x.Clip)) .ToList(); @@ -60,9 +64,9 @@ namespace UniVRM10 _eyeDirectionApplicable = null; } - internal void Process(LookAtEyeDirection inputEyeDirection, IReadOnlyDictionary initPose = null) + internal void Process(LookAtEyeDirection inputEyeDirection) { - Apply(inputEyeDirection, initPose); + Apply(inputEyeDirection); } public IDictionary GetWeights() @@ -114,7 +118,7 @@ namespace UniVRM10 /// 入力 Weight を基に、Validation を行い実際にモデルに適用される Weights を計算し、Merger を介して適用する。 /// この際、LookAt の情報を pull してそれも適用する。 /// - private void Apply(LookAtEyeDirection inputEyeDirection, IReadOnlyDictionary initPose) + private void Apply(LookAtEyeDirection inputEyeDirection) { // 1. Validate user input, and Output as actual weights. _validator.Validate(_inputWeights, _actualWeights, @@ -125,7 +129,7 @@ namespace UniVRM10 _eyeDirectionApplicable?.Apply(_actualEyeDirection, _actualWeights); // 3. Set actual weights to raw blendshapes. - _merger.SetValues(_actualWeights, initPose); + _merger.SetValues(_actualWeights); BlinkOverrideRate = blink; LookAtOverrideRate = lookAt; diff --git a/Packages/VRM10/Runtime/IO/ExpressionExtensions.cs b/Packages/VRM10/Runtime/IO/ExpressionExtensions.cs index 4ed7d05d3..41729d9b3 100644 --- a/Packages/VRM10/Runtime/IO/ExpressionExtensions.cs +++ b/Packages/VRM10/Runtime/IO/ExpressionExtensions.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using UniGLTF; using UniGLTF.Extensions.VRMC_vrm; +using UniGLTF.Extensions.VRMC_vrm_expressions_node_transform; using UnityEngine; namespace UniVRM10 @@ -86,5 +87,29 @@ namespace UniVRM10 } return binding; } + + public static NodeTransformBinding? Build10(this NodeTransformBind bind, GameObject root, Vrm10Importer importer) + { + if (bind.Node.TryGetValidIndex(importer.Nodes.Count, out var nodeIndex)) + { + var node = importer.Nodes[nodeIndex]; + var relativePath = node.RelativePathFrom(root.transform); + + var t = bind.Translation != null && bind.Translation.Length >= 3 + ? new Vector3(bind.Translation[0], bind.Translation[1], bind.Translation[2]) + : Vector3.zero; + var r = bind.Rotation != null && bind.Rotation.Length >= 4 + ? new Quaternion(bind.Rotation[0], bind.Rotation[1], bind.Rotation[2], bind.Rotation[3]) + : Quaternion.identity; + var s = bind.Scale != null && bind.Scale.Length >= 3 + ? new Vector3(bind.Scale[0], bind.Scale[1], bind.Scale[2]) + : Vector3.zero; + return new NodeTransformBinding(relativePath, t, r, s); + } + else + { + return default; + } + } } } diff --git a/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs b/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs index a0bf43867..32255cac1 100644 --- a/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs +++ b/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs @@ -779,6 +779,17 @@ namespace UniVRM10 }; } + static UniGLTF.Extensions.VRMC_vrm_expressions_node_transform.NodeTransformBind ExportNodeTransformBinding(NodeTransformBinding binding, Func getIndex) + { + return new UniGLTF.Extensions.VRMC_vrm_expressions_node_transform.NodeTransformBind + { + Node = getIndex(binding.RelativePath), + Translation = new float[] { binding.OffsetTranslation.x, binding.OffsetRotation.y, binding.OffsetTranslation.z }, + Rotation = new float[] { binding.OffsetRotation.x, binding.OffsetRotation.y, binding.OffsetRotation.z, binding.OffsetRotation.w }, + Scale = new float[] { binding.TargetScale.x, binding.TargetScale.y, binding.TargetScale.z }, + }; + } + static UniGLTF.Extensions.VRMC_vrm.Expression ExportExpression(VRM10Expression e, Vrm10Instance vrmController, Model model, ModelExporter converter) { if (e == null) @@ -786,7 +797,7 @@ namespace UniVRM10 return null; } - Func getIndexFromRelativePath = relativePath => + Func getRendererNodeIndexFromRelativePath = relativePath => { var rendererNode = vrmController.transform.GetFromPath(relativePath); var renderer = rendererNode.GetComponent(); @@ -831,7 +842,7 @@ namespace UniVRM10 { try { - var binding = ExportMorphTargetBinding(b, getIndexFromRelativePath); + var binding = ExportMorphTargetBinding(b, getRendererNodeIndexFromRelativePath); if (binding.Node < 0) { // node もしくは renderer が存在しない @@ -867,6 +878,43 @@ namespace UniVRM10 UniGLTFLogger.Warning($"{ex}"); } } + + if (e.NodeTransformBindings != null && e.NodeTransformBindings.Length > 0) + { + Func getNodeIndexFromRelativePath = relativePath => + { + var n = vrmController.transform.GetFromPath(relativePath); + var node = converter.Nodes[n.gameObject]; + return model.Nodes.IndexOf(node); + }; + + var nodeTransform = new UniGLTF.Extensions.VRMC_vrm_expressions_node_transform.VRMC_vrm_expressions_node_transform + { + NodeTransformBinds = new(), + }; + foreach (var b in e.NodeTransformBindings) + { + try + { + var binding = ExportNodeTransformBinding(b, getNodeIndexFromRelativePath); + if (binding.Node < 0) + { + // node もしくは renderer が存在しない + continue; + } + + nodeTransform.NodeTransformBinds.Add(binding); + } + catch (Exception ex) + { + UniGLTFLogger.Warning($"{ex}"); + } + } + glTFExtension extensions = default; + UniGLTF.Extensions.VRMC_vrm_expressions_node_transform.GltfSerializer.SerializeTo(ref extensions, nodeTransform); + vrmExpression.Extensions = extensions; + } + return vrmExpression; } diff --git a/Packages/VRM10/Runtime/IO/Vrm10Importer.cs b/Packages/VRM10/Runtime/IO/Vrm10Importer.cs index e0541a57d..ae51eb0ae 100644 --- a/Packages/VRM10/Runtime/IO/Vrm10Importer.cs +++ b/Packages/VRM10/Runtime/IO/Vrm10Importer.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using UniGLTF; using UniGLTF.Extensions.VRMC_springBone_limit; using UniGLTF.Utils; +using UnityEditor.Experimental.GraphView; using UnityEngine; namespace UniVRM10 @@ -286,6 +287,25 @@ namespace UniVRM10 clip.MaterialUVBindings = new MaterialUVBinding[] { }; } + if (UniGLTF.Extensions.VRMC_vrm_expressions_node_transform.GltfDeserializer.TryGet( + expression.Extensions as glTFExtension, + out UniGLTF.Extensions.VRMC_vrm_expressions_node_transform.VRMC_vrm_expressions_node_transform nodeTransform)) + { + if (nodeTransform.NodeTransformBinds != null) + { + clip.NodeTransformBindings = nodeTransform.NodeTransformBinds? + .Select(x => x.Build10(Root, this)) + .Where(x => x.HasValue) + .Select(x => x.Value) + .ToArray(); + } + else + { + clip.NodeTransformBindings = new NodeTransformBinding[] { }; + } + + } + m_expressions.Add((preset, clip)); } return clip; From 864f1c52ff1f3da37bbc1e18263ce60026b28f35 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Mon, 9 Feb 2026 16:26:44 +0900 Subject: [PATCH 20/36] fix preview bake --- .../Runtime/Components/Expression/Preview/PreviewMeshItem.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Packages/VRM10/Runtime/Components/Expression/Preview/PreviewMeshItem.cs b/Packages/VRM10/Runtime/Components/Expression/Preview/PreviewMeshItem.cs index 2765bd254..5d7a6a28b 100644 --- a/Packages/VRM10/Runtime/Components/Expression/Preview/PreviewMeshItem.cs +++ b/Packages/VRM10/Runtime/Components/Expression/Preview/PreviewMeshItem.cs @@ -123,7 +123,9 @@ namespace UniVRM10 { return new PreviewMeshItem(t.RelativePathFrom(root), t, skinnedMeshRenderer.sharedMaterials) { - Mesh = skinnedMeshRenderer.sharedMesh, + SkinnedMeshRenderer = skinnedMeshRenderer, + Mesh = new Mesh(), // for bake + BlendShapeNames = new string[]{}, }; } } From 2517ab54f234bbbd06a2551fb045527a74080a37 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Thu, 26 Feb 2026 21:22:44 +0900 Subject: [PATCH 21/36] Update Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Runtime/Components/Expression/NodeTransformtBinding.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs b/Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs index d049fb858..a6ecff11f 100644 --- a/Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs +++ b/Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs @@ -47,7 +47,10 @@ namespace UniVRM10 init.LocalPosition + this.OffsetTranslation * weight, Quaternion.Slerp(init.LocalRotation, init.LocalRotation * this.OffsetRotation, weight) ); - node.localScale = Vector3.Lerp(init.LocalScale, this.TargetScale, weight); + if (this.TargetScale != Vector3.zero) + { + node.localScale = Vector3.Lerp(init.LocalScale, this.TargetScale, weight); + } } } } \ No newline at end of file From e7251ec522e8c68ccb90edc0060b603727f2d215 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Thu, 26 Feb 2026 21:23:18 +0900 Subject: [PATCH 22/36] Update Packages/VRM10/Editor/Components/Expression/ReorderableNodeTransformBindingList.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Expression/ReorderableNodeTransformBindingList.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Packages/VRM10/Editor/Components/Expression/ReorderableNodeTransformBindingList.cs b/Packages/VRM10/Editor/Components/Expression/ReorderableNodeTransformBindingList.cs index 1903f7014..55dcf27f3 100644 --- a/Packages/VRM10/Editor/Components/Expression/ReorderableNodeTransformBindingList.cs +++ b/Packages/VRM10/Editor/Components/Expression/ReorderableNodeTransformBindingList.cs @@ -58,7 +58,9 @@ namespace UniVRM10 // R y += height; rect = new Rect(position.x, y, position.width, height); - if (EditorGUI.PropertyField(rect, property.FindPropertyRelative(nameof(NodeTransformBinding.OffsetRotation)))) + EditorGUI.BeginChangeCheck(); + EditorGUI.PropertyField(rect, property.FindPropertyRelative(nameof(NodeTransformBinding.OffsetRotation))); + if (EditorGUI.EndChangeCheck()) { changed = true; } From c774a910d8c7c38b1976cc819e1a99c7328cce74 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Thu, 26 Feb 2026 21:38:39 +0900 Subject: [PATCH 23/36] Update Packages/VRM10/Runtime/IO/Vrm10Exporter.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Packages/VRM10/Runtime/IO/Vrm10Exporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs b/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs index 32255cac1..6555d76d8 100644 --- a/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs +++ b/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs @@ -784,7 +784,7 @@ namespace UniVRM10 return new UniGLTF.Extensions.VRMC_vrm_expressions_node_transform.NodeTransformBind { Node = getIndex(binding.RelativePath), - Translation = new float[] { binding.OffsetTranslation.x, binding.OffsetRotation.y, binding.OffsetTranslation.z }, + Translation = new float[] { binding.OffsetTranslation.x, binding.OffsetTranslation.y, binding.OffsetTranslation.z }, Rotation = new float[] { binding.OffsetRotation.x, binding.OffsetRotation.y, binding.OffsetRotation.z, binding.OffsetRotation.w }, Scale = new float[] { binding.TargetScale.x, binding.TargetScale.y, binding.TargetScale.z }, }; From 62626d3eab5a9ede63e8cb43fb01cd8ce7e0b1d1 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Thu, 26 Feb 2026 21:38:51 +0900 Subject: [PATCH 24/36] Update Packages/VRM10/Runtime/IO/ExpressionExtensions.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Packages/VRM10/Runtime/IO/ExpressionExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Packages/VRM10/Runtime/IO/ExpressionExtensions.cs b/Packages/VRM10/Runtime/IO/ExpressionExtensions.cs index 41729d9b3..8120c52d1 100644 --- a/Packages/VRM10/Runtime/IO/ExpressionExtensions.cs +++ b/Packages/VRM10/Runtime/IO/ExpressionExtensions.cs @@ -103,7 +103,7 @@ namespace UniVRM10 : Quaternion.identity; var s = bind.Scale != null && bind.Scale.Length >= 3 ? new Vector3(bind.Scale[0], bind.Scale[1], bind.Scale[2]) - : Vector3.zero; + : node.transform.localScale; return new NodeTransformBinding(relativePath, t, r, s); } else From 42824e5c155f418c03c705e2b82c2ff832250ec5 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Thu, 26 Feb 2026 21:39:04 +0900 Subject: [PATCH 25/36] Update Packages/VRM10/Runtime/IO/Vrm10Importer.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Packages/VRM10/Runtime/IO/Vrm10Importer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Packages/VRM10/Runtime/IO/Vrm10Importer.cs b/Packages/VRM10/Runtime/IO/Vrm10Importer.cs index ae51eb0ae..557b51b7d 100644 --- a/Packages/VRM10/Runtime/IO/Vrm10Importer.cs +++ b/Packages/VRM10/Runtime/IO/Vrm10Importer.cs @@ -6,7 +6,6 @@ using System.Threading.Tasks; using UniGLTF; using UniGLTF.Extensions.VRMC_springBone_limit; using UniGLTF.Utils; -using UnityEditor.Experimental.GraphView; using UnityEngine; namespace UniVRM10 From 8f8493f129e1aa15618b9a67cf3d0cc25a600add Mon Sep 17 00:00:00 2001 From: ousttrue Date: Tue, 24 Mar 2026 14:14:21 +0900 Subject: [PATCH 26/36] rename typo --- .../{NodeTransformtBinding.cs => NodeTransformBinding.cs} | 0 ...NodeTransformtBinding.cs.meta => NodeTransformBinding.cs.meta} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename Packages/VRM10/Runtime/Components/Expression/{NodeTransformtBinding.cs => NodeTransformBinding.cs} (100%) rename Packages/VRM10/Runtime/Components/Expression/{NodeTransformtBinding.cs.meta => NodeTransformBinding.cs.meta} (100%) diff --git a/Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs b/Packages/VRM10/Runtime/Components/Expression/NodeTransformBinding.cs similarity index 100% rename from Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs rename to Packages/VRM10/Runtime/Components/Expression/NodeTransformBinding.cs diff --git a/Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs.meta b/Packages/VRM10/Runtime/Components/Expression/NodeTransformBinding.cs.meta similarity index 100% rename from Packages/VRM10/Runtime/Components/Expression/NodeTransformtBinding.cs.meta rename to Packages/VRM10/Runtime/Components/Expression/NodeTransformBinding.cs.meta From ff79bc93e88488931c24a714cabef4c29be054a6 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Tue, 24 Mar 2026 14:20:26 +0900 Subject: [PATCH 27/36] Update Packages/VRM10/Runtime/IO/Vrm10Exporter.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Packages/VRM10/Runtime/IO/Vrm10Exporter.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs b/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs index 6555d76d8..ae243a276 100644 --- a/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs +++ b/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs @@ -781,11 +781,13 @@ namespace UniVRM10 static UniGLTF.Extensions.VRMC_vrm_expressions_node_transform.NodeTransformBind ExportNodeTransformBinding(NodeTransformBinding binding, Func getIndex) { + var translation = ReverseX(binding.OffsetTranslation); + var rotation = ReverseX(binding.OffsetRotation); return new UniGLTF.Extensions.VRMC_vrm_expressions_node_transform.NodeTransformBind { Node = getIndex(binding.RelativePath), - Translation = new float[] { binding.OffsetTranslation.x, binding.OffsetTranslation.y, binding.OffsetTranslation.z }, - Rotation = new float[] { binding.OffsetRotation.x, binding.OffsetRotation.y, binding.OffsetRotation.z, binding.OffsetRotation.w }, + Translation = new float[] { translation.x, translation.y, translation.z }, + Rotation = new float[] { rotation.x, rotation.y, rotation.z, rotation.w }, Scale = new float[] { binding.TargetScale.x, binding.TargetScale.y, binding.TargetScale.z }, }; } From c872bdb87a84178d91d54ae358decbb92474ac19 Mon Sep 17 00:00:00 2001 From: hadashiA Date: Wed, 25 Mar 2026 15:48:29 +0900 Subject: [PATCH 28/36] Fix memory leak caused by unreleased static references --- .../UniGLTF/Runtime/UniGLTF/RuntimeGltfInstance.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Packages/UniGLTF/Runtime/UniGLTF/RuntimeGltfInstance.cs b/Packages/UniGLTF/Runtime/UniGLTF/RuntimeGltfInstance.cs index 6f4374687..b4c6417eb 100644 --- a/Packages/UniGLTF/Runtime/UniGLTF/RuntimeGltfInstance.cs +++ b/Packages/UniGLTF/Runtime/UniGLTF/RuntimeGltfInstance.cs @@ -255,9 +255,10 @@ namespace UniGLTF } } - static Dictionary> PoseMap = new(); + static readonly Dictionary> PoseMap = new(); + public static IReadOnlyDictionary SafeGetInitialPose( - Transform root, bool useCache = true) + Transform root, bool useCache = false) { if (useCache && PoseMap.TryGetValue(root, out var pose)) { @@ -278,7 +279,10 @@ namespace UniGLTF } // add cache - PoseMap.Add(root, pose); + if (useCache) + { + PoseMap.Add(root, pose); + } return pose; } From 776bfac48d7a46d2c35470ce94fb3622858379f8 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Thu, 2 Apr 2026 17:06:29 +0900 Subject: [PATCH 29/36] UniVRM-0.131.1 --- Packages/UniGLTF/Runtime/UniGLTF/PackageVersion.cs | 4 ++-- Packages/UniGLTF/Runtime/UniGLTF/UniGLTFVersion.cs | 4 ++-- Packages/UniGLTF/package.json | 2 +- Packages/VRM/package.json | 4 ++-- Packages/VRM10/package.json | 4 ++-- Packages/packages-lock.json | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Packages/UniGLTF/Runtime/UniGLTF/PackageVersion.cs b/Packages/UniGLTF/Runtime/UniGLTF/PackageVersion.cs index 44645ee4b..a3c5813ed 100644 --- a/Packages/UniGLTF/Runtime/UniGLTF/PackageVersion.cs +++ b/Packages/UniGLTF/Runtime/UniGLTF/PackageVersion.cs @@ -5,7 +5,7 @@ namespace UniGLTF { public const int MAJOR = 0; public const int MINOR = 131; - public const int PATCH = 0; - public const string VERSION = "0.131.0"; + public const int PATCH = 1; + public const string VERSION = "0.131.1"; } } diff --git a/Packages/UniGLTF/Runtime/UniGLTF/UniGLTFVersion.cs b/Packages/UniGLTF/Runtime/UniGLTF/UniGLTFVersion.cs index 27595cc47..6f0a7ff0c 100644 --- a/Packages/UniGLTF/Runtime/UniGLTF/UniGLTFVersion.cs +++ b/Packages/UniGLTF/Runtime/UniGLTF/UniGLTFVersion.cs @@ -5,7 +5,7 @@ namespace UniGLTF { public const int MAJOR = 2; public const int MINOR = 67; - public const int PATCH = 0; - public const string VERSION = "2.67.0"; + public const int PATCH = 1; + public const string VERSION = "2.67.1"; } } diff --git a/Packages/UniGLTF/package.json b/Packages/UniGLTF/package.json index d7c324bb2..3689c218a 100644 --- a/Packages/UniGLTF/package.json +++ b/Packages/UniGLTF/package.json @@ -1,6 +1,6 @@ { "name": "com.vrmc.gltf", - "version": "0.131.0", + "version": "0.131.1", "displayName": "UniGLTF", "description": "GLTF importer and exporter", "unity": "2021.3", diff --git a/Packages/VRM/package.json b/Packages/VRM/package.json index 8ada7d563..81a14cb9c 100644 --- a/Packages/VRM/package.json +++ b/Packages/VRM/package.json @@ -1,6 +1,6 @@ { "name": "com.vrmc.univrm", - "version": "0.131.0", + "version": "0.131.1", "displayName": "VRM", "description": "VRM importer", "unity": "2021.3", @@ -14,7 +14,7 @@ "name": "VRM Consortium" }, "dependencies": { - "com.vrmc.gltf": "0.131.0", + "com.vrmc.gltf": "0.131.1", "com.unity.ugui": "1.0.0" }, "samples": [ diff --git a/Packages/VRM10/package.json b/Packages/VRM10/package.json index c400650fa..544b77a7a 100644 --- a/Packages/VRM10/package.json +++ b/Packages/VRM10/package.json @@ -1,6 +1,6 @@ { "name": "com.vrmc.vrm", - "version": "0.131.0", + "version": "0.131.1", "displayName": "VRM-1.0", "description": "VRM-1.0 importer", "unity": "2021.3", @@ -15,7 +15,7 @@ }, "dependencies": { "com.unity.timeline": "1.7.6", - "com.vrmc.gltf": "0.131.0" + "com.vrmc.gltf": "0.131.1" }, "samples": [ { diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index c3101490b..545d38a9e 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -163,7 +163,7 @@ "depth": 0, "source": "embedded", "dependencies": { - "com.vrmc.gltf": "0.131.0", + "com.vrmc.gltf": "0.131.1", "com.unity.ugui": "1.0.0" } }, @@ -173,7 +173,7 @@ "source": "embedded", "dependencies": { "com.unity.timeline": "1.7.6", - "com.vrmc.gltf": "0.131.0" + "com.vrmc.gltf": "0.131.1" } }, "com.unity.modules.ai": { From a2f69b857c7fb5ebf01fdd74fe40536adfa506d7 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 27 Oct 2025 09:44:21 +1000 Subject: [PATCH 30/36] Change RenderPipelineUtility to use GraphicsSettings.currentRenderPipeline --- .../UniGLTF/IO/MaterialIO/RenderPipelineUtility.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Packages/UniGLTF/Runtime/UniGLTF/IO/MaterialIO/RenderPipelineUtility.cs b/Packages/UniGLTF/Runtime/UniGLTF/IO/MaterialIO/RenderPipelineUtility.cs index 76ecc0547..d072bec45 100644 --- a/Packages/UniGLTF/Runtime/UniGLTF/IO/MaterialIO/RenderPipelineUtility.cs +++ b/Packages/UniGLTF/Runtime/UniGLTF/IO/MaterialIO/RenderPipelineUtility.cs @@ -1,4 +1,4 @@ -using UnityEngine.Rendering; +using UnityEngine.Rendering; namespace UniGLTF { @@ -6,19 +6,19 @@ namespace UniGLTF { public static RenderPipelineTypes GetRenderPipelineType() { - RenderPipeline currentPipeline = RenderPipelineManager.currentPipeline; + RenderPipelineAsset currentRenderPipelineAsset = GraphicsSettings.currentRenderPipeline; - if (currentPipeline == null) + if (currentRenderPipelineAsset == null) { return RenderPipelineTypes.BuiltinRenderPipeline; } - if (currentPipeline.GetType().Name.Contains("HDRenderPipeline")) + if (currentRenderPipelineAsset.GetType().Name.Contains("HDRenderPipeline")) { return RenderPipelineTypes.HighDefinitionRenderPipeline; } - if (currentPipeline.GetType().Name.Contains("UniversalRenderPipeline")) + if (currentRenderPipelineAsset.GetType().Name.Contains("UniversalRenderPipeline")) { return RenderPipelineTypes.UniversalRenderPipeline; } @@ -26,4 +26,4 @@ namespace UniGLTF return RenderPipelineTypes.Unknown; } } -} \ No newline at end of file +} From 07126cc3523eb27557b129fbd94f87639d442f45 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Mon, 13 Apr 2026 15:47:19 +0900 Subject: [PATCH 31/36] Vrm10Exporter.ReverseX and UniGLTF.UnityExtensions.ReverseX same name and arguments, but different return type ! --- Packages/VRM10/Runtime/IO/Vrm10Exporter.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs b/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs index ae243a276..b645abf6c 100644 --- a/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs +++ b/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs @@ -781,13 +781,13 @@ namespace UniVRM10 static UniGLTF.Extensions.VRMC_vrm_expressions_node_transform.NodeTransformBind ExportNodeTransformBinding(NodeTransformBinding binding, Func getIndex) { - var translation = ReverseX(binding.OffsetTranslation); - var rotation = ReverseX(binding.OffsetRotation); + var translation = Vrm10Exporter.ReverseX(binding.OffsetTranslation); + var rotation = Vrm10Exporter.ReverseX(binding.OffsetRotation); return new UniGLTF.Extensions.VRMC_vrm_expressions_node_transform.NodeTransformBind { Node = getIndex(binding.RelativePath), - Translation = new float[] { translation.x, translation.y, translation.z }, - Rotation = new float[] { rotation.x, rotation.y, rotation.z, rotation.w }, + Translation = translation, + Rotation = rotation, Scale = new float[] { binding.TargetScale.x, binding.TargetScale.y, binding.TargetScale.z }, }; } From d5e0b8194a87717147d9ba65e865bf7e185f3f9c Mon Sep 17 00:00:00 2001 From: ousttrue Date: Thu, 16 Apr 2026 17:29:33 +0900 Subject: [PATCH 32/36] rename ReverseXToFloat3 and ReverseXToFloat4 --- Packages/VRM10/Runtime/IO/Vrm10Exporter.cs | 40 +++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs b/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs index b645abf6c..ff23d7595 100644 --- a/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs +++ b/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs @@ -135,12 +135,12 @@ namespace UniVRM10 /// /// /// - static float[] ReverseX(Vector3 v) + static float[] ReverseXToFloat3(Vector3 v) { return new float[] { -v.x, v.y, v.z }; } - static float[] ReverseX(Quaternion q) + static float[] ReverseXToFloat4(Quaternion q) { q = UniGLTF.Axes.X.Create().InvertQuaternion(q); return new float[] { q.x, q.y, q.z, q.w }; @@ -330,7 +330,7 @@ namespace UniVRM10 shape.Sphere = new UniGLTF.Extensions.VRMC_springBone.ColliderShapeSphere { Radius = z.Radius, - Offset = ReverseX(z.Offset), + Offset = ReverseXToFloat3(z.Offset), }; break; } @@ -340,8 +340,8 @@ namespace UniVRM10 shape.Capsule = new UniGLTF.Extensions.VRMC_springBone.ColliderShapeCapsule { Radius = z.Radius, - Offset = ReverseX(z.Offset), - Tail = ReverseX(z.Tail), + Offset = ReverseXToFloat3(z.Offset), + Tail = ReverseXToFloat3(z.Tail), }; break; } @@ -352,7 +352,7 @@ namespace UniVRM10 shape.Sphere = new UniGLTF.Extensions.VRMC_springBone.ColliderShapeSphere { Radius = 1000.0f, - Offset = ReverseX(z.Offset - z.TailOrNormal.normalized * DISTANCE), + Offset = ReverseXToFloat3(z.Offset - z.TailOrNormal.normalized * DISTANCE), }; break; } @@ -382,7 +382,7 @@ namespace UniVRM10 shape.Sphere = new UniGLTF.Extensions.VRMC_springBone_extended_collider.ExtendedColliderShapeSphere { Radius = z.Radius, - Offset = ReverseX(z.Offset), + Offset = ReverseXToFloat3(z.Offset), }; break; } @@ -392,8 +392,8 @@ namespace UniVRM10 shape.Capsule = new UniGLTF.Extensions.VRMC_springBone_extended_collider.ExtendedColliderShapeCapsule { Radius = z.Radius, - Offset = ReverseX(z.Offset), - Tail = ReverseX(z.Tail), + Offset = ReverseXToFloat3(z.Offset), + Tail = ReverseXToFloat3(z.Tail), }; break; } @@ -403,7 +403,7 @@ namespace UniVRM10 shape.Sphere = new UniGLTF.Extensions.VRMC_springBone_extended_collider.ExtendedColliderShapeSphere { Radius = z.Radius, - Offset = ReverseX(z.Offset), + Offset = ReverseXToFloat3(z.Offset), Inside = true, }; break; @@ -414,8 +414,8 @@ namespace UniVRM10 shape.Capsule = new UniGLTF.Extensions.VRMC_springBone_extended_collider.ExtendedColliderShapeCapsule { Radius = z.Radius, - Offset = ReverseX(z.Offset), - Tail = ReverseX(z.Tail), + Offset = ReverseXToFloat3(z.Offset), + Tail = ReverseXToFloat3(z.Tail), Inside = true, }; break; @@ -425,8 +425,8 @@ namespace UniVRM10 { shape.Plane = new UniGLTF.Extensions.VRMC_springBone_extended_collider.ExtendedColliderShapePlane { - Offset = ReverseX(z.Offset), - Normal = ReverseX(z.Normal), + Offset = ReverseXToFloat3(z.Offset), + Normal = ReverseXToFloat3(z.Normal), }; break; } @@ -447,7 +447,7 @@ namespace UniVRM10 HitRadius = y.m_jointRadius, DragForce = y.m_dragForce, Stiffness = y.m_stiffnessForce, - GravityDir = ReverseX(y.m_gravityDir), + GravityDir = ReverseXToFloat3(y.m_gravityDir), GravityPower = y.m_gravityPower, }; @@ -461,7 +461,7 @@ namespace UniVRM10 { Cone = new UniGLTF.Extensions.VRMC_springBone_limit.ConeLimit { - Rotation = ReverseX(y.m_limitSpaceOffset), + Rotation = ReverseXToFloat4(y.m_limitSpaceOffset), Angle = y.m_pitch, } } @@ -479,7 +479,7 @@ namespace UniVRM10 { Hinge = new UniGLTF.Extensions.VRMC_springBone_limit.HingeLimit { - Rotation = ReverseX(y.m_limitSpaceOffset), + Rotation = ReverseXToFloat4(y.m_limitSpaceOffset), Angle = y.m_pitch, } } @@ -497,7 +497,7 @@ namespace UniVRM10 { Spherical = new UniGLTF.Extensions.VRMC_springBone_limit.SphericalLimit { - Rotation = ReverseX(y.m_limitSpaceOffset), + Rotation = ReverseXToFloat4(y.m_limitSpaceOffset), Pitch = y.m_pitch, Yaw = y.m_yaw, } @@ -781,8 +781,8 @@ namespace UniVRM10 static UniGLTF.Extensions.VRMC_vrm_expressions_node_transform.NodeTransformBind ExportNodeTransformBinding(NodeTransformBinding binding, Func getIndex) { - var translation = Vrm10Exporter.ReverseX(binding.OffsetTranslation); - var rotation = Vrm10Exporter.ReverseX(binding.OffsetRotation); + var translation = ReverseXToFloat3(binding.OffsetTranslation); + var rotation = ReverseXToFloat4(binding.OffsetRotation); return new UniGLTF.Extensions.VRMC_vrm_expressions_node_transform.NodeTransformBind { Node = getIndex(binding.RelativePath), From 6364a0917dcd5d0f7947552da8316b2d957b6bfb Mon Sep 17 00:00:00 2001 From: ousttrue Date: Fri, 24 Apr 2026 16:06:07 +0900 Subject: [PATCH 33/36] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- Packages/VRM10/Runtime/IO/Vrm10Exporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs b/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs index ff23d7595..fc6724f5d 100644 --- a/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs +++ b/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs @@ -131,7 +131,7 @@ namespace UniVRM10 } /// - /// revere X + /// reverse X /// /// /// From da41c1b25592bdc0efebbe62d9a081994c5002f8 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Thu, 30 Apr 2026 17:55:08 +0900 Subject: [PATCH 34/36] update action --- .github/workflows/create-unitypackage.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/create-unitypackage.yml b/.github/workflows/create-unitypackage.yml index cec71c23c..030024170 100644 --- a/.github/workflows/create-unitypackage.yml +++ b/.github/workflows/create-unitypackage.yml @@ -19,7 +19,7 @@ jobs: timeout-minutes: 60 steps: - id: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: recursive lfs: true @@ -82,7 +82,7 @@ jobs: - name: Upload test results if: ${{ always() }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: run-edit-mode-tests.xml path: ${{ env.UNITY_PROJECT_PATH }}/run-edit-mode-tests.xml @@ -100,7 +100,7 @@ jobs: echo "Success to create UnityPackage." - name: Upload UnityPackage - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: unitypackage path: ${{ env.UNITY_PROJECT_PATH }}/*.unitypackage From 0c329f1e324ece66c2d7d0f76e504145ae04c5af Mon Sep 17 00:00:00 2001 From: notargs Date: Mon, 18 May 2026 19:02:57 +0900 Subject: [PATCH 35/36] replace math.normalize to math.normalizesafe --- .../Runtime/SpringBoneJobs/Anglelimit/Anglelimit.cs | 8 ++++---- .../SpringBoneJobs/Anglelimit/AnglelimitHinge.cs | 2 +- .../SpringBoneJobs/Blittables/BlittableTransform.cs | 2 +- .../InputPorts/FastSpringBoneBuffer.cs | 2 +- .../Runtime/SpringBoneJobs/SpringBoneCollision.cs | 12 ++++++------ .../SpringBoneJobs/UpdateFastSpringBoneJob.cs | 2 +- Packages/UniGLTF/Runtime/Utils/MathHelper.cs | 8 ++++---- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/Anglelimit.cs b/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/Anglelimit.cs index 145a37469..52f916f6c 100644 --- a/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/Anglelimit.cs +++ b/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/Anglelimit.cs @@ -18,7 +18,7 @@ namespace UniGLTF.SpringBoneJobs case AnglelimitTypes.Cone: { var angleSpaceToWorld = anglelimitSpaceToWorld(logic, joint, parentRotation); - var tailDir = math.mul(math.inverse(angleSpaceToWorld), math.normalize(nextTail - head)); + var tailDir = math.mul(math.inverse(angleSpaceToWorld), math.normalizesafe(nextTail - head)); tailDir = AnglelimitCone.Apply(tailDir, joint.anglelimit1); return head + math.mul(angleSpaceToWorld, tailDir) * logic.length; } @@ -26,7 +26,7 @@ namespace UniGLTF.SpringBoneJobs case AnglelimitTypes.Hinge: { var angleSpaceToWorld = anglelimitSpaceToWorld(logic, joint, parentRotation); - var tailDir = math.mul(math.inverse(angleSpaceToWorld), math.normalize(nextTail - head)); + var tailDir = math.mul(math.inverse(angleSpaceToWorld), math.normalizesafe(nextTail - head)); tailDir = AnglelimitHinge.Apply(tailDir, joint.anglelimit1); return head + math.mul(angleSpaceToWorld, tailDir) * logic.length; } @@ -35,7 +35,7 @@ namespace UniGLTF.SpringBoneJobs case AnglelimitTypes.Spherical: { var angleSpaceToWorld = anglelimitSpaceToWorld(logic, joint, parentRotation); - var tailDir = math.mul(math.inverse(angleSpaceToWorld), math.normalize(nextTail - head)); + var tailDir = math.mul(math.inverse(angleSpaceToWorld), math.normalizesafe(nextTail - head)); tailDir = AnglelimitSpherical.Apply(tailDir, joint.anglelimit1, joint.anglelimit2); return head + math.mul(angleSpaceToWorld, tailDir) * logic.length; } @@ -84,7 +84,7 @@ namespace UniGLTF.SpringBoneJobs // General case // quaternion(cross(from, to); dot(from, to) + 1).normalized - return math.normalize(new quaternion(to.z, 0f, -to.x, dot1)); + return math.normalizesafe(new quaternion(to.z, 0f, -to.x, dot1)); } } } \ No newline at end of file diff --git a/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/AnglelimitHinge.cs b/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/AnglelimitHinge.cs index 571984689..45f291929 100644 --- a/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/AnglelimitHinge.cs +++ b/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/AnglelimitHinge.cs @@ -12,7 +12,7 @@ namespace UniGLTF.SpringBoneJobs // x要素を0にし、正規化する float3 tailDir = src; tailDir.x = 0.0f; - tailDir = math.normalize(tailDir); + tailDir = math.normalizesafe(tailDir); // tailDirのy要素をjointに設定されたangleの余弦と比較する var cosAngle = math.cos(limitAngle); diff --git a/Packages/UniGLTF/Runtime/SpringBoneJobs/Blittables/BlittableTransform.cs b/Packages/UniGLTF/Runtime/SpringBoneJobs/Blittables/BlittableTransform.cs index 16bb8e855..5b4c6680a 100644 --- a/Packages/UniGLTF/Runtime/SpringBoneJobs/Blittables/BlittableTransform.cs +++ b/Packages/UniGLTF/Runtime/SpringBoneJobs/Blittables/BlittableTransform.cs @@ -140,7 +140,7 @@ namespace UniGLTF.SpringBoneJobs.Blittables if(parent.HasValue) { - newLocalRotation = math.normalize(math.mul(math.inverse(parent.Value.rotation), newRotation)); + newLocalRotation = math.normalizesafe(math.mul(math.inverse(parent.Value.rotation), newRotation)); newLocalToWorldMatrix = math.mul(parent.Value.localToWorldMatrix, float4x4.TRS(localPosition, newLocalRotation, localScale)); } else diff --git a/Packages/UniGLTF/Runtime/SpringBoneJobs/InputPorts/FastSpringBoneBuffer.cs b/Packages/UniGLTF/Runtime/SpringBoneJobs/InputPorts/FastSpringBoneBuffer.cs index b507f60d7..5af4ef132 100644 --- a/Packages/UniGLTF/Runtime/SpringBoneJobs/InputPorts/FastSpringBoneBuffer.cs +++ b/Packages/UniGLTF/Runtime/SpringBoneJobs/InputPorts/FastSpringBoneBuffer.cs @@ -125,7 +125,7 @@ namespace UniGLTF.SpringBoneJobs.InputPorts parentTransformIndex: Array.IndexOf(Transforms, joint.Transform.parent), tailTransformIndex: Array.IndexOf(Transforms, tailJoint.Transform), localRotation: joint.DefaultLocalRotation, - boneAxis: math.normalize(localChildPosition), + boneAxis: math.normalizesafe(localChildPosition), length: math.length(localChildPosition)); } } diff --git a/Packages/UniGLTF/Runtime/SpringBoneJobs/SpringBoneCollision.cs b/Packages/UniGLTF/Runtime/SpringBoneJobs/SpringBoneCollision.cs index a8f5fc3b4..8898f3804 100644 --- a/Packages/UniGLTF/Runtime/SpringBoneJobs/SpringBoneCollision.cs +++ b/Packages/UniGLTF/Runtime/SpringBoneJobs/SpringBoneCollision.cs @@ -47,10 +47,10 @@ namespace UniGLTF.SpringBoneJobs if (math.lengthsq(nextTail - worldPosition) <= (r * r)) { // ヒット。Colliderの半径方向に押し出す - var normal = math.normalize(nextTail - worldPosition); + var normal = math.normalizesafe(nextTail - worldPosition); var posFromCollider = worldPosition + normal * r; // 長さをboneLengthに強制 - newNextTail = headTransform.position + math.normalize(posFromCollider - headTransform.position) * logic.length; + newNextTail = headTransform.position + math.normalizesafe(posFromCollider - headTransform.position) * logic.length; return true; } else @@ -76,7 +76,7 @@ namespace UniGLTF.SpringBoneJobs // head側半球の球判定 return TryResolveSphereCollision(joint, collider, worldPosition, headTransform, maxColliderScale, logic, nextTail, out newNextTail); } - var P = math.normalize(direction); + var P = math.normalizesafe(direction); var Q = headTransform.position - worldPosition; var dot = math.dot(P, Q); if (dot <= 0) @@ -110,7 +110,7 @@ namespace UniGLTF.SpringBoneJobs in float3 nextTail, out float3 newNextTail) { var transformedOffset = MathHelper.MultiplyPoint(colliderTransform.localToWorldMatrix, collider.offset); - var transformedNormal = math.normalize(MathHelper.MultiplyVector(colliderTransform.localToWorldMatrix, collider.tailOrNormal)); + var transformedNormal = math.normalizesafe(MathHelper.MultiplyVector(colliderTransform.localToWorldMatrix, collider.tailOrNormal)); var delta = nextTail - transformedOffset; // ジョイントとコライダーの距離。負の値は衝突していることを示す @@ -145,7 +145,7 @@ namespace UniGLTF.SpringBoneJobs // ジョイントとコライダーの距離の方向。衝突している場合、この方向にジョイントを押し出す if (distance < 0) { - var direction = -1 * math.normalize(delta); + var direction = -1 * math.normalizesafe(delta); newNextTail = nextTail - direction * distance; return true; } @@ -192,7 +192,7 @@ namespace UniGLTF.SpringBoneJobs // ジョイントとコライダーの距離の方向。衝突している場合、この方向にジョイントを押し出す if (distance < 0) { - var direction = -1 * math.normalize(delta); + var direction = -1 * math.normalizesafe(delta); newNextTail = nextTail - direction * distance; return true; } diff --git a/Packages/UniGLTF/Runtime/SpringBoneJobs/UpdateFastSpringBoneJob.cs b/Packages/UniGLTF/Runtime/SpringBoneJobs/UpdateFastSpringBoneJob.cs index 2cec59241..9357877f5 100644 --- a/Packages/UniGLTF/Runtime/SpringBoneJobs/UpdateFastSpringBoneJob.cs +++ b/Packages/UniGLTF/Runtime/SpringBoneJobs/UpdateFastSpringBoneJob.cs @@ -92,7 +92,7 @@ namespace UniGLTF.SpringBoneJobs + external * scalingFactor; // 外力による移動量 // 長さをboneLengthに強制 - nextTail = headTransform.position + math.normalize(nextTail - headTransform.position) * logic.length; + nextTail = headTransform.position + math.normalizesafe(nextTail - headTransform.position) * logic.length; nextTail = Anglelimit.Apply(logic, joint, parentRotation, head: headTransform.position, nextTail: nextTail); diff --git a/Packages/UniGLTF/Runtime/Utils/MathHelper.cs b/Packages/UniGLTF/Runtime/Utils/MathHelper.cs index 86cb3646b..c3f41f830 100644 --- a/Packages/UniGLTF/Runtime/Utils/MathHelper.cs +++ b/Packages/UniGLTF/Runtime/Utils/MathHelper.cs @@ -32,8 +32,8 @@ namespace UniGLTF.Runtime.Utils return quaternion.identity; } - float3 from = math.normalize(fromVector); - float3 to = math.normalize(toVector); + float3 from = math.normalizesafe(fromVector); + float3 to = math.normalizesafe(toVector); var dot = math.dot(from, to); switch(dot) @@ -47,13 +47,13 @@ namespace UniGLTF.Runtime.Utils { axis = math.cross(from, new float3(0, 1, 0)); } - return quaternion.AxisAngle(math.normalize(axis), math.PI); + return quaternion.AxisAngle(math.normalizesafe(axis), math.PI); } default: { var angle = math.acos(dot); var axis = math.cross(from, to); - return quaternion.AxisAngle(math.normalize(axis), angle); + return quaternion.AxisAngle(math.normalizesafe(axis), angle); } } } From aa1ac445bb09d519fb2a2c2be1927305bbd12981 Mon Sep 17 00:00:00 2001 From: notargs Date: Wed, 20 May 2026 17:17:25 +0900 Subject: [PATCH 36/36] =?UTF-8?q?FromToRotation=E3=81=AE=E5=88=A4=E5=AE=9A?= =?UTF-8?q?=E3=82=92=E3=82=88=E3=82=8A=E5=8E=B3=E5=AF=86=E3=81=AB=E3=81=99?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Packages/UniGLTF/Runtime/Utils/MathHelper.cs | 17 +++--- Packages/UniGLTF/Tests/UniGLTF/MathTests.cs | 61 ++++++++++++++++++++ 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/Packages/UniGLTF/Runtime/Utils/MathHelper.cs b/Packages/UniGLTF/Runtime/Utils/MathHelper.cs index 86cb3646b..75d57a6b9 100644 --- a/Packages/UniGLTF/Runtime/Utils/MathHelper.cs +++ b/Packages/UniGLTF/Runtime/Utils/MathHelper.cs @@ -27,7 +27,10 @@ namespace UniGLTF.Runtime.Utils [MethodImpl(MethodImplOptions.AggressiveInlining)] public static quaternion FromToRotation(in float3 fromVector, in float3 toVector) { - if (math.lengthsq(fromVector) == 0 || math.lengthsq(toVector) == 0) + const float epsilon = 1e-6f; + const float epsilonSq = epsilon * epsilon; + + if (math.lengthsq(fromVector) < epsilonSq || math.lengthsq(toVector) < epsilonSq) { return quaternion.identity; } @@ -35,15 +38,15 @@ namespace UniGLTF.Runtime.Utils float3 from = math.normalize(fromVector); float3 to = math.normalize(toVector); - var dot = math.dot(from, to); - switch(dot) + var dot = math.clamp(math.dot(from, to), -1.0f, 1.0f); + switch (dot) { - case >= 1.0f: - return quaternion.identity; - case <= -1.0f: + case > 1.0f - epsilon: + return quaternion.identity; + case < -1.0f + epsilon: { var axis = math.cross(from, new float3(1, 0, 0)); - if (math.lengthsq(axis) < 0.0001f) + if (math.lengthsq(axis) < epsilonSq) { axis = math.cross(from, new float3(0, 1, 0)); } diff --git a/Packages/UniGLTF/Tests/UniGLTF/MathTests.cs b/Packages/UniGLTF/Tests/UniGLTF/MathTests.cs index 5c1395b64..cf1cc3b8f 100644 --- a/Packages/UniGLTF/Tests/UniGLTF/MathTests.cs +++ b/Packages/UniGLTF/Tests/UniGLTF/MathTests.cs @@ -45,5 +45,66 @@ namespace UniGLTF var expected = Quaternion.FromToRotation(_vector1, _vector2); Assert.That(MathHelper.Approximately(result, expected), Is.True); } + + [Test] + public void FromToRotationMatchesUnityForStandardCasesTest() + { + AssertFromToRotationMatchesUnity(new float3(1, 0, 0), new float3(0, 1, 0)); + AssertFromToRotationMatchesUnity(new float3(0, 1, 0), new float3(0, 0, 1)); + AssertFromToRotationMatchesUnity(new float3(1, 2, 3), new float3(4, 5, 6)); + AssertFromToRotationMatchesUnity(new float3(-2, 0.5f, 3), new float3(1, -4, 0.25f)); + } + + [Test] + public void FromToRotationSameDirectionTest() + { + AssertFromToRotation(new float3(1, 0, 0), new float3(2, 0, 0)); + AssertFromToRotation(new float3(1, 2, 3), new float3(2, 4, 6)); + } + + [Test] + public void FromToRotationOppositeDirectionTest() + { + AssertFromToRotation(new float3(1, 0, 0), new float3(-1, 0, 0)); + AssertFromToRotation(new float3(0, 1, 0), new float3(0, -1, 0)); + AssertFromToRotation(new float3(0, 0, 1), new float3(0, 0, -1)); + AssertFromToRotation(new float3(1, 2, 3), new float3(-1, -2, -3)); + } + + [Test] + public void FromToRotationNearlyOppositeDirectionTest() + { + AssertFromToRotation(new float3(1, 0, 0), math.normalize(new float3(-1, 0.0001f, 0))); + AssertFromToRotation(new float3(1, 2, 3), math.normalize(new float3(-1.0001f, -2, -3))); + } + + [Test] + public void FromToRotationZeroVectorTest() + { + Assert.That(MathHelper.Approximately(MathHelper.FromToRotation(float3.zero, new float3(0, 1, 0)), quaternion.identity), Is.True); + Assert.That(MathHelper.Approximately(MathHelper.FromToRotation(new float3(1, 0, 0), float3.zero), quaternion.identity), Is.True); + } + + [Test] + public void FromToRotationTinyVectorTest() + { + var result = MathHelper.FromToRotation(new float3(1e-12f, 0, 0), new float3(0, 1, 0)); + Assert.That(MathHelper.Approximately(result, quaternion.identity), Is.True); + } + + private static void AssertFromToRotation(float3 from, float3 to) + { + var result = MathHelper.FromToRotation(from, to); + var rotated = math.mul(result, math.normalize(from)); + var dot = math.dot(math.normalize(rotated), math.normalize(to)); + Assert.That(dot, Is.GreaterThan(0.9999f)); + } + + private static void AssertFromToRotationMatchesUnity(float3 from, float3 to) + { + var result = MathHelper.FromToRotation(from, to); + var expected = Quaternion.FromToRotation(from, to); + Assert.That(MathHelper.Approximately(result, expected), Is.True); + } } }