diff --git a/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/Anglelimit.cs b/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/Anglelimit.cs
index 52f916f6ce..fd58c3d231 100644
--- a/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/Anglelimit.cs
+++ b/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/Anglelimit.cs
@@ -6,9 +6,19 @@ namespace UniGLTF.SpringBoneJobs
public static class Anglelimit
{
public static float3 Apply(
- in BlittableJointImmutable logic, in BlittableJointMutable joint,
- in quaternion parentRotation, in float3 head, in float3 nextTail)
+ in BlittableJointImmutable logic,
+ in BlittableJointMutable joint,
+ in quaternion parentRotation,
+ in float3 head,
+ in float3 nextTail
+ )
{
+ var angleSpaceToWorld = anglelimitSpaceToWorld(logic, joint, parentRotation);
+ var tailDir = math.mul(
+ math.inverse(angleSpaceToWorld),
+ math.normalizesafe(nextTail - head)
+ );
+
switch (joint.anglelimitType)
{
case AnglelimitTypes.None:
@@ -16,51 +26,48 @@ public static float3 Apply(
return nextTail;
case AnglelimitTypes.Cone:
- {
- var angleSpaceToWorld = anglelimitSpaceToWorld(logic, joint, parentRotation);
- 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;
- }
- case AnglelimitTypes.Hinge:
- {
- var angleSpaceToWorld = anglelimitSpaceToWorld(logic, joint, parentRotation);
- 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;
- }
+ tailDir = AnglelimitCone.Apply(tailDir, joint.anglelimit1);
+ break;
+ case AnglelimitTypes.Hinge:
+ tailDir = AnglelimitHinge.Apply(tailDir, joint.anglelimit1);
+ break;
case AnglelimitTypes.Spherical:
- {
- var angleSpaceToWorld = anglelimitSpaceToWorld(logic, joint, parentRotation);
- 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;
- }
+ tailDir = AnglelimitSpherical.Apply(
+ tailDir,
+ joint.anglelimit1,
+ joint.anglelimit2
+ );
+ break;
default:
- throw new System.ArgumentException($"unknown joint.anglelimitType: {joint.anglelimitType}");
+ throw new System.ArgumentException(
+ $"unknown joint.anglelimitType: {joint.anglelimitType}"
+ );
}
+
+ return head + math.mul(angleSpaceToWorld, tailDir) * logic.length;
}
/// nextTail(position vector in world space)
/// tailDir(directionay vector in angle space)
///
- public static quaternion anglelimitSpaceToWorld(in BlittableJointImmutable logic, in BlittableJointMutable joint,
- in quaternion parentRotation)
+ public static quaternion anglelimitSpaceToWorld(
+ in BlittableJointImmutable logic,
+ in BlittableJointMutable joint,
+ in quaternion parentRotation
+ )
{
// Y+方向からjointのheadからtailに向かうベクトルへの最小回転
var axisRotation = getAxisRotation(logic.boneAxis);
// limitのローカル空間をワールド空間に写像する回転
- return
- math.mul(parentRotation,
- math.mul(logic.localRotation,
- math.mul(axisRotation,
- joint.anglelimitOffset)))
- ;
+ return math.mul(
+ parentRotation,
+ math.mul(logic.localRotation, math.mul(axisRotation, joint.anglelimitOffset))
+ );
}
///
@@ -71,20 +78,22 @@ public static quaternion anglelimitSpaceToWorld(in BlittableJointImmutable logic
///
/// TODO: Replace with the appropriate link to the specification later
///
- public static quaternion getAxisRotation(in float3 to)
+ public static quaternion getAxisRotation(in float3 boneAxis)
{
- // dot(from, to) + 1
- var dot1 = to.y + 1f;
+ // headからtailに向かうベクトルとY+方向との内積
+ var dot = boneAxis.y;
- // Handle the case where from and to are parallel and opposite
- if (dot1 < 1e-8f) // dot is approximately -1
+ if (dot == -1.0)
{
- return new quaternion(1f, 0f, 0f, 0f);
+ // headからtailに向かうベクトルがY-方向の場合、X軸周りに180度回転させた回転を設定する
+ return new quaternion(1, 0, 0, 0);
+ }
+ else
+ {
+ // それ以外の場合、Y+方向からjointのheadからtailに向かうベクトルへの最小回転を設定する
+ // quaternion(cross(from, to); dot(from, to) + 1).normalized
+ return math.normalizesafe(new quaternion(boneAxis.z, 0, -boneAxis.x, dot + 1));
}
-
- // General case
- // quaternion(cross(from, to); dot(from, to) + 1).normalized
- 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/AnglelimitCone.cs b/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/AnglelimitCone.cs
index d8c159f866..041f7bc94c 100644
--- a/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/AnglelimitCone.cs
+++ b/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/AnglelimitCone.cs
@@ -4,30 +4,41 @@ namespace UniGLTF.SpringBoneJobs
{
public static class AnglelimitCone
{
- /// AngleLimit空間の方向ベクトル
- /// radius
+ /// AngleLimit空間の方向ベクトル
+ /// radius
/// AngleLimit空間の方向ベクトル
- public static float3 Apply(in float3 src, float angleLimit)
+ public static float3 Apply(float3 tailDir, float limitAngle)
{
- // tailDirのy要素をjointに設定されたangleの余弦と比較する
- var cosAngle = math.cos(angleLimit);
- if (src.y >= cosAngle)
- {
- return src;
- }
+ // angleを0以上π以下に制限する
+ limitAngle = math.clamp(limitAngle, 0.0f, math.PI);
- var tailDir = src;
+ // tailDirのy要素をlimitに設定されたangleの余弦と比較する
+ var cosLimitAngle = math.cos(limitAngle);
+ if (tailDir.y < cosLimitAngle)
{
// x・z要素を、tailDirの正弦とjointに設定されたangleの正弦の比を用いてスケールする
- var ratio = math.sqrt((1.0f - cosAngle * cosAngle) / (1.0f - tailDir.y * tailDir.y));
- tailDir.x *= ratio;
- tailDir.z *= ratio;
+ var horizontalLengthSquared = 1.0f - tailDir.y * tailDir.y;
+
+ if (horizontalLengthSquared == 0.0)
+ {
+ // tailDirがy軸負方向の場合、z軸正方向側を選択する
+ tailDir.x = 0.0f;
+ tailDir.z = math.sqrt(1.0f - cosLimitAngle * cosLimitAngle);
+ }
+ else
+ {
+ var scale = math.sqrt(
+ (1.0f - cosLimitAngle * cosLimitAngle) / horizontalLengthSquared
+ );
+ tailDir.x *= scale;
+ tailDir.z *= scale;
+ }
- // y要素を、jointに設定されたangleの余弦とする
- tailDir.y = cosAngle;
+ // y要素をlimitに設定されたangleの余弦とする
+ tailDir.y = cosLimitAngle;
}
return tailDir;
}
}
-}
\ 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 45f291929e..6010d94be4 100644
--- a/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/AnglelimitHinge.cs
+++ b/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/AnglelimitHinge.cs
@@ -4,29 +4,39 @@ namespace UniGLTF.SpringBoneJobs
{
public static class AnglelimitHinge
{
- /// AngleLimit空間の方向ベクトル
- /// radius
+ /// AngleLimit空間の方向ベクトル
+ /// radius
/// AngleLimit空間の方向ベクトル
- public static float3 Apply(in float3 src, float limitAngle)
+ public static float3 Apply(float3 tailDir, float limitAngle)
{
- // x要素を0にし、正規化する
- float3 tailDir = src;
- tailDir.x = 0.0f;
- tailDir = math.normalizesafe(tailDir);
+ // angleを0以上π以下に制限する
+ limitAngle = math.clamp(limitAngle, 0.0f, math.PI);
- // tailDirのy要素をjointに設定されたangleの余弦と比較する
- var cosAngle = math.cos(limitAngle);
- if (tailDir.y < cosAngle)
+ var projectedLengthSquared = tailDir.y * tailDir.y + tailDir.z * tailDir.z;
+ if (projectedLengthSquared == 0.0f)
{
- // z要素を、tailDirの正弦とjointに設定されたangleの正弦の比を用いてスケールする
- var ratio = math.sqrt((1.0f - cosAngle * cosAngle) / (1.0f - tailDir.y * tailDir.y));
- tailDir.z *= ratio;
-
- // y要素を、jointに設定されたangleの余弦とする
- tailDir.y = cosAngle;
+ // tailDirがx軸正方向または負方向の場合、Y軸正方向を選択する
+ tailDir = math.float3(0.0f, 1.0f, 0.0f);
}
+ else
+ {
+ // tailDirをヒンジのYZ平面へ射影する
+ tailDir =
+ math.float3(0.0f, tailDir.y, tailDir.z) / math.sqrt(projectedLengthSquared);
+ // tailDirのy要素をlimitに設定されたangleの余弦と比較する
+ var cosLimitAngle = math.cos(limitAngle);
+ if (tailDir.y < cosLimitAngle)
+ {
+ var sinLimitAngle = math.sqrt(1.0f - cosLimitAngle * cosLimitAngle);
+
+ // tailDirがy軸負方向の場合、z軸正方向側を選択する
+ var zSign = (tailDir.z < 0.0f) ? -1.0f : 1.0f;
+ tailDir.y = cosLimitAngle;
+ tailDir.z = sinLimitAngle * zSign;
+ }
+ }
return tailDir;
}
}
-}
\ No newline at end of file
+}
diff --git a/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/AnglelimitSpherical.cs b/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/AnglelimitSpherical.cs
index b15ce10fad..06090da62b 100644
--- a/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/AnglelimitSpherical.cs
+++ b/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/AnglelimitSpherical.cs
@@ -1,37 +1,61 @@
+using System;
using Unity.Mathematics;
+using UnityEngine;
namespace UniGLTF.SpringBoneJobs
{
public static class AnglelimitSpherical
{
/// AngleLimit空間の方向ベクトル
- /// radius
- /// radius
+ /// radius
+ /// radius
/// AngleLimit空間の方向ベクトル
- public static float3 Apply(in float3 tailDir, float limitAnglePhi, float limitAngleTheta)
+ public static float3 Apply(float3 tailDir, float limitPitch, float limitYaw)
{
- // tailDirのphi・thetaを計算する
- var phi = math.atan2(tailDir.z, tailDir.y);
- var theta = math.asin(tailDir.x);
+ // pitchを0以上π以下、yawを0以上π/2以下に制限する
+ limitPitch = math.clamp(limitPitch, 0.0f, math.PI);
+ limitYaw = math.clamp(limitYaw, 0.0f, math.PI / 2.0f);
- // phi・thetaをjointに設定されたphi・thetaを用いて制限する
- // var isLimited = false;
- if (math.abs(phi) > limitAnglePhi)
+ // tailDirのpitch・yawを計算する
+ float pitch;
+ if (tailDir.y == -1.0)
+ {
+ // tailDirがy軸負方向の場合、Z軸正方向側の境界を選択するため、pitchをπとする
+ pitch = math.PI;
+ }
+ else if (math.abs(tailDir.x) == 1.0f)
+ {
+ // tailDirがx軸正方向または負方向の場合、pitchを0とする
+ pitch = 0.0f;
+ }
+ else
+ {
+ pitch = math.atan2(tailDir.z, tailDir.y);
+ }
+ var yaw = math.asin(tailDir.x);
+
+ // pitchをlimitに設定されたpitchを用いて制限する
+ if (math.abs(pitch) > limitPitch)
{
// isLimited = true;
- phi = limitAnglePhi * math.sign(phi);
+ pitch = limitPitch * math.sign(pitch);
}
- // thetaをjointに設定されたthetaを用いて制限する
- if (math.abs(theta) > limitAngleTheta)
+ // yawをlimitに設定されたyawを用いて制限する
+ if (math.abs(yaw) > limitYaw)
{
- // isLimited = true;
- theta = limitAngleTheta * math.sign(theta);
+ // isLimited = true;
+ yaw = limitYaw * math.sign(yaw);
}
- // tailDirをphi・thetaを用いて再計算する
- var cos_theta = math.cos(theta);
- return new float3(math.sin(theta), cos_theta * math.cos(phi), cos_theta * math.sin(phi));
+ // tailDirをpitch・yawを用いて再計算する
+ tailDir = math.float3(
+ math.sin(yaw),
+ math.cos(yaw) * math.cos(pitch),
+ math.cos(yaw) * math.sin(pitch)
+ );
+
+ return tailDir;
}
}
-}
\ No newline at end of file
+}
diff --git a/Packages/VRM10/Runtime/IO/Vrm10Importer.cs b/Packages/VRM10/Runtime/IO/Vrm10Importer.cs
index 557b51b7d4..698d115545 100644
--- a/Packages/VRM10/Runtime/IO/Vrm10Importer.cs
+++ b/Packages/VRM10/Runtime/IO/Vrm10Importer.cs
@@ -1,11 +1,10 @@
using System;
using System.Collections.Generic;
-using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using UniGLTF;
-using UniGLTF.Extensions.VRMC_springBone_limit;
using UniGLTF.Utils;
+using Unity.Mathematics;
using UnityEngine;
namespace UniVRM10
@@ -655,20 +654,20 @@ async Task LoadSpringBoneAsync(IAwaitCaller awaitCaller, Vrm10Instance controlle
{
joint.m_anglelimitType = UniGLTF.SpringBoneJobs.AnglelimitTypes.Cone;
joint.m_limitSpaceOffset = QuaternionFromFloat4(cone.Rotation);
- joint.m_pitch = cone.Angle.GetValueOrDefault();
+ joint.m_pitch = math.clamp(cone.Angle.GetValueOrDefault(), 0.0f, math.PI);
}
else if (extensionSpringBoneLimit.Limit.Hinge is UniGLTF.Extensions.VRMC_springBone_limit.HingeLimit hinge)
{
joint.m_anglelimitType = UniGLTF.SpringBoneJobs.AnglelimitTypes.Hinge;
joint.m_limitSpaceOffset = QuaternionFromFloat4(hinge.Rotation);
- joint.m_pitch = hinge.Angle.GetValueOrDefault();
+ joint.m_pitch = math.clamp(hinge.Angle.GetValueOrDefault(), 0.0f, math.PI);
}
else if (extensionSpringBoneLimit.Limit.Spherical is UniGLTF.Extensions.VRMC_springBone_limit.SphericalLimit spherical)
{
joint.m_anglelimitType = UniGLTF.SpringBoneJobs.AnglelimitTypes.Spherical;
joint.m_limitSpaceOffset = QuaternionFromFloat4(spherical.Rotation);
- joint.m_pitch = spherical.Pitch.GetValueOrDefault();
- joint.m_yaw = spherical.Yaw.GetValueOrDefault();
+ joint.m_pitch = math.clamp(spherical.Pitch.GetValueOrDefault(), 0.0f, math.PI);
+ joint.m_yaw = math.clamp(spherical.Yaw.GetValueOrDefault(), 0.0f, math.PI / 2);
}
}