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);
+ }
}
}