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/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
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..1a7aab51c 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;
@@ -41,28 +40,33 @@ namespace UniGLTF
}
}
- private static async Task BuildBlendShapeAsync(IAwaitCaller awaitCaller, Mesh mesh, BlendShape blendShape,
+ private static async Task BuildBlendShapeAsync(
+ IAwaitCaller awaitCaller,
+ Mesh mesh,
+ BlendShape blendShape,
Vector3[] emptyVertices)
{
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;
+
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 +80,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
);
}
@@ -132,7 +136,11 @@ namespace UniGLTF
var emptyVertices = new Vector3[mesh.vertexCount];
foreach (var blendShape in data.BlendShapes)
{
- await BuildBlendShapeAsync(awaitCaller, mesh, blendShape, emptyVertices);
+ await BuildBlendShapeAsync(
+ awaitCaller,
+ mesh,
+ blendShape,
+ emptyVertices);
}
}
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