diff --git a/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/Anglelimit.cs b/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/Anglelimit.cs
index 52f916f6c..e3b47c205 100644
--- a/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/Anglelimit.cs
+++ b/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/Anglelimit.cs
@@ -1,3 +1,4 @@
+using System;
using UniGLTF.SpringBoneJobs.Blittables;
using Unity.Mathematics;
@@ -5,62 +6,76 @@ namespace UniGLTF.SpringBoneJobs
{
public static class Anglelimit
{
+ public static readonly float SINGULARITY_EPSILON = 1e-8f;
+
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
+ )
{
+ if (joint.anglelimitType == AnglelimitTypes.None)
+ {
+ // do nothing
+ return nextTail;
+ }
+
+ var angleSpaceToWorld = anglelimitSpaceToWorld(logic, joint, parentRotation);
+ var tailDir = math.mul(
+ math.inverse(angleSpaceToWorld),
+ math.normalizesafe(nextTail - head)
+ );
+
switch (joint.anglelimitType)
{
case AnglelimitTypes.None:
- // do nothing
- return nextTail;
+ throw new Exception("not reach here");
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;
- }
+
+ tailDir = AnglelimitCone.Apply(tailDir, joint.anglelimit1);
+ break;
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 = 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 +86,22 @@ namespace UniGLTF.SpringBoneJobs
///
/// 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 <= -1f + SINGULARITY_EPSILON)
{
- 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 d8c159f86..bd530af4e 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;
- // y要素を、jointに設定されたangleの余弦とする
- tailDir.y = cosAngle;
+ if (horizontalLengthSquared <= Anglelimit.SINGULARITY_EPSILON)
+ {
+ // 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要素を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 45f291929..9e4fcf387 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 <= Anglelimit.SINGULARITY_EPSILON)
{
- // 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);
+
+ // zの符号を維持し、z==0 の場合は 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 b15ce10fa..86dfe8264 100644
--- a/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/AnglelimitSpherical.cs
+++ b/Packages/UniGLTF/Runtime/SpringBoneJobs/Anglelimit/AnglelimitSpherical.cs
@@ -5,33 +5,55 @@ 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.0f + Anglelimit.SINGULARITY_EPSILON)
+ {
+ // tailDirがy軸負方向の場合、Z軸正方向側の境界を選択するため、pitchをπとする
+ pitch = math.PI;
+ }
+ else if (math.abs(tailDir.x) >= 1.0f - Anglelimit.SINGULARITY_EPSILON)
+ {
+ // tailDirがx軸正方向または負方向の場合、pitchを0とする
+ pitch = 0.0f;
+ }
+ else
+ {
+ pitch = math.atan2(tailDir.z, tailDir.y);
+ }
+ var yaw = math.asin(math.clamp(tailDir.x, -1f, 1f));
+
+ // 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/UniGLTF/Runtime/SpringBoneJobs/Blittables/BlittableJointMutable.cs b/Packages/UniGLTF/Runtime/SpringBoneJobs/Blittables/BlittableJointMutable.cs
index ccc69d2b6..1f63fae43 100644
--- a/Packages/UniGLTF/Runtime/SpringBoneJobs/Blittables/BlittableJointMutable.cs
+++ b/Packages/UniGLTF/Runtime/SpringBoneJobs/Blittables/BlittableJointMutable.cs
@@ -38,6 +38,8 @@ namespace UniGLTF.SpringBoneJobs.Blittables
public float anglelimit2 => _data.c2.z;
public quaternion anglelimitOffset => _data.c3.xyzw;
+ // angleLimit1: angle (Cone/Hinge), pitch (Spherical)
+ // angleLimit2: yaw (Spherical)
public BlittableJointMutable(float stiffnessForce = 0,
float gravityPower = 0,
float3 gravityDir = default,
diff --git a/Packages/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs b/Packages/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs
index d2f44d272..77ac620e8 100644
--- a/Packages/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs
+++ b/Packages/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs
@@ -63,10 +63,11 @@ namespace UniVRM10
/// spring 情報。readonly。mouse click による hierarchy 参照補助
///
EditorGUI.BeginDisabledGroup(true);
- var isLastTail = ShowSpringInfo();
+ var info = Vrm10InstanceSpringBone.GetEditInfo(m_target, m_root);
+ ShowSpringInfo(info);
EditorGUI.EndDisabledGroup();
- if (isLastTail)
+ if (info.IsLastTail)
{
EditorGUILayout.HelpBox("末端 joint です。下記の設定は使用されません。", MessageType.Info);
}
@@ -88,7 +89,7 @@ namespace UniVRM10
//
// angle limit
//
- var fold = EditorGUILayout.Foldout(m_showAnglelimitSettings, "AngleLimit Settings (experimental)");
+ var fold = EditorGUILayout.Foldout(m_showAnglelimitSettings, $"AngleLimit Settings (1.0-draft)");
if (m_showAnglelimitSettings != fold)
{
m_showAnglelimitSettings = fold;
@@ -96,8 +97,13 @@ namespace UniVRM10
}
if (m_showAnglelimitSettings)
{
- EditorGUILayout.HelpBox("SpringBoneの角度制限はまだdraft仕様です。将来的に仕様が変更される可能性があります。また、VRMファイルへのインポート・エクスポート機能はまだ実装されていません。\nThe angle limit feature for SpringBone is still in draft status. The specifications may change in the future. Also, the import/export of VRM files has not yet been implemented.", MessageType.Warning);
+ EditorGUILayout.HelpBox("SpringBoneの角度制限はまだdraft仕様です。将来的に仕様が変更される可能性があります。\nThe angle limit feature for SpringBone is still in draft status. The specifications may change in the future.", MessageType.Warning);
+
+ if (info.IsLastTail)
+ {
+ EditorGUILayout.HelpBox("末端ノードではAngleLimitは無視されます。", MessageType.Info);
+ }
EditorGUILayout.PropertyField(m_angleLimitType);
switch ((UniGLTF.SpringBoneJobs.AnglelimitTypes)m_angleLimitType.enumValueIndex)
{
@@ -106,12 +112,12 @@ namespace UniVRM10
case UniGLTF.SpringBoneJobs.AnglelimitTypes.Cone:
EditorGUILayout.PropertyField(m_angleLimitRotation);
- EditorGUILayout.PropertyField(m_angleLimitPitch);
+ EditorGUILayout.PropertyField(m_angleLimitPitch, new GUIContent("Angle"));
break;
case UniGLTF.SpringBoneJobs.AnglelimitTypes.Hinge:
EditorGUILayout.PropertyField(m_angleLimitRotation);
- EditorGUILayout.PropertyField(m_angleLimitPitch);
+ EditorGUILayout.PropertyField(m_angleLimitPitch, new GUIContent("Angle"));
break;
case UniGLTF.SpringBoneJobs.AnglelimitTypes.Spherical:
@@ -139,52 +145,51 @@ namespace UniVRM10
/// - Springに関連付けられたColliderの情報を表示する
///
/// 末端
- bool ShowSpringInfo()
+ static void ShowSpringInfo(Vrm10InstanceSpringBone.EditInfo info)
{
- if (m_root == null)
+ if (info.Root == null)
{
EditorGUILayout.HelpBox("no vrm-1.0", MessageType.Warning);
- return false;
+ return;
}
- EditorGUILayout.ObjectField("Vrm-1.0", m_root, typeof(Vrm10Instance), true, null);
+ EditorGUILayout.ObjectField("Vrm-1.0", info.Root, typeof(Vrm10Instance), true, null);
- var found = m_root.SpringBone.FindJoint(m_target);
- if (!found.HasValue)
+ if (info.Spring == null)
{
EditorGUILayout.HelpBox("This joint not belongs any spring", MessageType.Warning);
- return false;
+ return;
}
- var (spring, i, _) = found.Value;
- m_showJoints = EditorGUILayout.Foldout(m_showJoints, $"Springs[{i}]({spring.Name})");
+ // var (spring, i, _) = found.Value;
+ m_showJoints = EditorGUILayout.Foldout(m_showJoints, $"Springs[{info.SpringIndex}]({info.Spring.Name})");
int? jointIndex = default;
// joints
- for (int j = 0; j < spring.Joints.Count; ++j)
+ for (int j = 0; j < info.Spring.Joints.Count; ++j)
{
- var joint = spring.Joints[j];
+ var joint = info.Spring.Joints[j];
if (m_showJoints)
{
var label = $"Joints[{j}]";
- if (joint == m_target)
+ if (joint == info.Target)
{
label += "★";
}
EditorGUILayout.ObjectField(label, joint, typeof(VRM10SpringBoneJoint), true, null);
}
- if (joint == m_target)
+ if (joint == info.Target)
{
jointIndex = j;
}
}
m_showColliders = EditorGUILayout.Foldout(m_showColliders, "ColliderGroups");
- if (m_showColliders && found.HasValue)
+ if (m_showColliders)
{
// collider groups
- for (int j = 0; j < spring.ColliderGroups.Count; ++j)
+ for (int j = 0; j < info.Spring.ColliderGroups.Count; ++j)
{
- var group = spring.ColliderGroups[j];
+ var group = info.Spring.ColliderGroups[j];
EditorGUILayout.LabelField($"ColliderGroups[{j}]({group.Name})");
for (int k = 0; k < group.Colliders.Count; ++k)
{
@@ -194,8 +199,6 @@ namespace UniVRM10
}
}
}
-
- return jointIndex == (spring.Joints.Count - 1);
}
///
diff --git a/Packages/VRM10/Runtime/Components/SpringBone/VRM10SpringBoneJoint.cs b/Packages/VRM10/Runtime/Components/SpringBone/VRM10SpringBoneJoint.cs
index d6a7949d7..3a4e9f4c2 100644
--- a/Packages/VRM10/Runtime/Components/SpringBone/VRM10SpringBoneJoint.cs
+++ b/Packages/VRM10/Runtime/Components/SpringBone/VRM10SpringBoneJoint.cs
@@ -26,15 +26,27 @@ namespace UniVRM10
[SerializeField]
public float m_jointRadius = 0.02f;
+ ///
+ /// VRMC_springBone_limit
+ ///
[SerializeField]
public UniGLTF.SpringBoneJobs.AnglelimitTypes m_anglelimitType;
+ ///
+ /// VRMC_springBone_limit
+ ///
[SerializeField]
public Quaternion m_limitSpaceOffset = Quaternion.identity;
+ ///
+ /// VRMC_springBone_limit
+ ///
[SerializeField, Range(0, Mathf.PI)]
public float m_pitch = Mathf.PI;
+ ///
+ /// VRMC_springBone_limit
+ ///
[SerializeField, Range(0, Mathf.PI / 2)]
public float m_yaw = 0;
@@ -59,7 +71,7 @@ namespace UniVRM10
}
}
- void AddJointRecursive(Transform t, VRM10SpringBoneJoint src)
+ void AddJointRecursive(Transform t, VRM10SpringBoneJoint src, bool copyLimit)
{
var joint = t.gameObject.GetOrAddComponent();
@@ -70,10 +82,19 @@ namespace UniVRM10
joint.m_dragForce = src.m_dragForce;
joint.m_jointRadius = src.m_jointRadius;
+ if (copyLimit)
+ {
+ joint.m_anglelimitType = src.m_anglelimitType;
+ joint.m_limitSpaceOffset = src.m_limitSpaceOffset;
+ joint.m_yaw = src.m_yaw;
+ joint.m_pitch = src.m_pitch;
+ }
+
+
if (t.childCount > 0)
{
// only first child
- AddJointRecursive(t.GetChild(0), src);
+ AddJointRecursive(t.GetChild(0), src, copyLimit);
}
}
@@ -106,7 +127,7 @@ namespace UniVRM10
return;
}
- AddJointRecursive(transform.GetChild(0), this);
+ AddJointRecursive(transform.GetChild(0), this, true);
// updater root
foreach (var spring in root.SpringBone.Springs)
diff --git a/Packages/VRM10/Runtime/Components/Vrm10Instance/Vrm10InstanceSpringBone.cs b/Packages/VRM10/Runtime/Components/Vrm10Instance/Vrm10InstanceSpringBone.cs
index f47a972c3..16ef83465 100644
--- a/Packages/VRM10/Runtime/Components/Vrm10Instance/Vrm10InstanceSpringBone.cs
+++ b/Packages/VRM10/Runtime/Components/Vrm10Instance/Vrm10InstanceSpringBone.cs
@@ -89,5 +89,58 @@ namespace UniVRM10
}
return default;
}
+
+ public struct EditInfo
+ {
+ public VRM10SpringBoneJoint Target;
+ public Vrm10Instance Root;
+ public Spring Spring;
+ public int SpringIndex;
+ public int JointIndex;
+ public bool IsLastTail;
+ }
+
+ ///
+ /// Return SpringBone EditInfo.
+ /// Do not call on runtime play for performance.
+ ///
+ /// On Editor may null.
+ /// GetComponentInParent cache.
+ ///
+ public static EditInfo GetEditInfo(VRM10SpringBoneJoint target, Vrm10Instance root)
+ {
+ EditInfo info = default;
+ if (target == null)
+ {
+ return info;
+ }
+ info.Target = target;
+
+ if (root == null)
+ {
+ root = target.GetComponentInParent();
+ }
+ if (root == null)
+ {
+ return info;
+ }
+ info.Root = root;
+
+ var found = root.SpringBone.FindJoint(target);
+ if (!found.HasValue)
+ {
+ return info;
+ }
+ (info.Spring, info.SpringIndex, info.JointIndex) = found.Value;
+
+
+ // ヒエラルキーの末端ではなく、Springの末端である
+ if (info.Spring.Joints.Count > 0 && info.Spring.Joints[info.Spring.Joints.Count - 1] == target)
+ {
+ info.IsLastTail = true;
+ }
+
+ return info;
+ }
}
}
diff --git a/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs b/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs
index fc6724f5d..e285ec744 100644
--- a/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs
+++ b/Packages/VRM10/Runtime/IO/Vrm10Exporter.cs
@@ -14,6 +14,7 @@ namespace UniVRM10
public const string SPRINGBONE_EXTENDED_COLLIDER_SPEC_VERSION = "1.0";
public const string NODE_CONSTRAINT_SPEC_VERSION = "1.0";
public const string MTOON_SPEC_VERSION = "1.0";
+ public const string VRMC_springBone_limit_SPEC_VERSION = "1.0-draft";
public const string LICENSE_URL_JA = "https://vrm.dev/licenses/1.0/";
public const string LICENSE_URL_EN = "https://vrm.dev/licenses/1.0/en/";
@@ -451,63 +452,70 @@ namespace UniVRM10
GravityPower = y.m_gravityPower,
};
- switch (y.m_anglelimitType)
+ if (y.transform.childCount > 0)
{
- case UniGLTF.SpringBoneJobs.AnglelimitTypes.Cone:
- {
- var limit = new UniGLTF.Extensions.VRMC_springBone_limit.VRMC_springBone_limit
+ // childが存在する => 末端でない
+ switch (y.m_anglelimitType)
+ {
+ case UniGLTF.SpringBoneJobs.AnglelimitTypes.Cone:
{
- Limit = new UniGLTF.Extensions.VRMC_springBone_limit.Limit
+ var limit = new UniGLTF.Extensions.VRMC_springBone_limit.VRMC_springBone_limit
{
- Cone = new UniGLTF.Extensions.VRMC_springBone_limit.ConeLimit
+ SpecVersion = VRMC_springBone_limit_SPEC_VERSION,
+ Limit = new UniGLTF.Extensions.VRMC_springBone_limit.Limit
{
- Rotation = ReverseXToFloat4(y.m_limitSpaceOffset),
- Angle = y.m_pitch,
+ Cone = new UniGLTF.Extensions.VRMC_springBone_limit.ConeLimit
+ {
+ Rotation = ReverseXToFloat4(y.m_limitSpaceOffset),
+ Angle = y.m_pitch,
+ }
}
- }
- };
- glTFExtension extensions = default;
- UniGLTF.Extensions.VRMC_springBone_limit.GltfSerializer.SerializeTo(ref extensions, limit);
- joint.Extensions = extensions;
- break;
- }
- case UniGLTF.SpringBoneJobs.AnglelimitTypes.Hinge:
- {
- var limit = new UniGLTF.Extensions.VRMC_springBone_limit.VRMC_springBone_limit
+ };
+ glTFExtension extensions = default;
+ UniGLTF.Extensions.VRMC_springBone_limit.GltfSerializer.SerializeTo(ref extensions, limit);
+ joint.Extensions = extensions;
+ break;
+ }
+ case UniGLTF.SpringBoneJobs.AnglelimitTypes.Hinge:
{
- Limit = new UniGLTF.Extensions.VRMC_springBone_limit.Limit
+ var limit = new UniGLTF.Extensions.VRMC_springBone_limit.VRMC_springBone_limit
{
- Hinge = new UniGLTF.Extensions.VRMC_springBone_limit.HingeLimit
+ SpecVersion = VRMC_springBone_limit_SPEC_VERSION,
+ Limit = new UniGLTF.Extensions.VRMC_springBone_limit.Limit
{
- Rotation = ReverseXToFloat4(y.m_limitSpaceOffset),
- Angle = y.m_pitch,
+ Hinge = new UniGLTF.Extensions.VRMC_springBone_limit.HingeLimit
+ {
+ Rotation = ReverseXToFloat4(y.m_limitSpaceOffset),
+ Angle = y.m_pitch,
+ }
}
- }
- };
- glTFExtension extensions = default;
- UniGLTF.Extensions.VRMC_springBone_limit.GltfSerializer.SerializeTo(ref extensions, limit);
- joint.Extensions = extensions;
- break;
- }
- case UniGLTF.SpringBoneJobs.AnglelimitTypes.Spherical:
- {
- var limit = new UniGLTF.Extensions.VRMC_springBone_limit.VRMC_springBone_limit
+ };
+ glTFExtension extensions = default;
+ UniGLTF.Extensions.VRMC_springBone_limit.GltfSerializer.SerializeTo(ref extensions, limit);
+ joint.Extensions = extensions;
+ break;
+ }
+ case UniGLTF.SpringBoneJobs.AnglelimitTypes.Spherical:
{
- Limit = new UniGLTF.Extensions.VRMC_springBone_limit.Limit
+ var limit = new UniGLTF.Extensions.VRMC_springBone_limit.VRMC_springBone_limit
{
- Spherical = new UniGLTF.Extensions.VRMC_springBone_limit.SphericalLimit
+ SpecVersion = VRMC_springBone_limit_SPEC_VERSION,
+ Limit = new UniGLTF.Extensions.VRMC_springBone_limit.Limit
{
- Rotation = ReverseXToFloat4(y.m_limitSpaceOffset),
- Pitch = y.m_pitch,
- Yaw = y.m_yaw,
+ Spherical = new UniGLTF.Extensions.VRMC_springBone_limit.SphericalLimit
+ {
+ Rotation = ReverseXToFloat4(y.m_limitSpaceOffset),
+ Pitch = y.m_pitch,
+ Yaw = y.m_yaw,
+ }
}
- }
- };
- glTFExtension extensions = default;
- UniGLTF.Extensions.VRMC_springBone_limit.GltfSerializer.SerializeTo(ref extensions, limit);
- joint.Extensions = extensions;
- break;
- }
+ };
+ glTFExtension extensions = default;
+ UniGLTF.Extensions.VRMC_springBone_limit.GltfSerializer.SerializeTo(ref extensions, limit);
+ joint.Extensions = extensions;
+ break;
+ }
+ }
}
return joint;
}
diff --git a/Packages/VRM10/Runtime/IO/Vrm10Importer.cs b/Packages/VRM10/Runtime/IO/Vrm10Importer.cs
index 557b51b7d..3293a901b 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
@@ -651,24 +650,27 @@ namespace UniVRM10
if (UniGLTF.Extensions.VRMC_springBone_limit.GltfDeserializer.TryGet(gltfJoint.Extensions as glTFExtension,
out var extensionSpringBoneLimit))
{
- if (extensionSpringBoneLimit.Limit.Cone is UniGLTF.Extensions.VRMC_springBone_limit.ConeLimit cone)
+ if (VRMC_springBone_limit_Validate(go.transform, extensionSpringBoneLimit))
{
- joint.m_anglelimitType = UniGLTF.SpringBoneJobs.AnglelimitTypes.Cone;
- joint.m_limitSpaceOffset = QuaternionFromFloat4(cone.Rotation);
- joint.m_pitch = cone.Angle.GetValueOrDefault();
- }
- 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();
- }
- 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();
+ if (extensionSpringBoneLimit.Limit.Cone is UniGLTF.Extensions.VRMC_springBone_limit.ConeLimit cone)
+ {
+ joint.m_anglelimitType = UniGLTF.SpringBoneJobs.AnglelimitTypes.Cone;
+ joint.m_limitSpaceOffset = QuaternionFromFloat4(cone.Rotation);
+ joint.m_pitch = cone.Angle.GetValueOrDefault();
+ }
+ 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();
+ }
+ 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();
+ }
}
}
@@ -679,6 +681,60 @@ namespace UniVRM10
}
}
+ private static bool VRMC_springBone_limit_Validate(Transform node, VRMC_springBone_limit extensionSpringBoneLimit)
+ {
+ var validate = true;
+ if (extensionSpringBoneLimit.SpecVersion != Vrm10Exporter.VRMC_springBone_limit_SPEC_VERSION)
+ {
+ validate = false;
+ UniGLTFLogger.Warning($"Unknown VRMC_springBone_limit specVersion: {extensionSpringBoneLimit.SpecVersion}");
+ }
+
+ var count = 0;
+ if (extensionSpringBoneLimit.Limit != null)
+ {
+ if (extensionSpringBoneLimit.Limit.Cone is UniGLTF.Extensions.VRMC_springBone_limit.ConeLimit cone)
+ {
+ ++count;
+ }
+ if (extensionSpringBoneLimit.Limit.Hinge is UniGLTF.Extensions.VRMC_springBone_limit.HingeLimit hinge)
+ {
+ ++count;
+ }
+ if (extensionSpringBoneLimit.Limit.Spherical is UniGLTF.Extensions.VRMC_springBone_limit.SphericalLimit spherical)
+ {
+ ++count;
+ }
+ }
+
+ switch (count)
+ {
+ case 0:
+ // Limit 欠落
+ validate = false;
+ UniGLTFLogger.Warning($"No VRMC_springBone_limit.Limit: skip");
+ break;
+
+ case 1:
+ // ok
+ break;
+
+ default:
+ validate = false;
+ UniGLTFLogger.Warning($"Multiple VRMC_springBone_limit.Limit: skip");
+ break;
+ }
+
+ if (node.childCount == 0)
+ {
+ // leaf
+ UniGLTFLogger.Warning($"VRMC_springBone_limit: leaf node '{node.name}' must not have limit. skip");
+ validate = false;
+ }
+
+ return validate;
+ }
+
private static Quaternion QuaternionFromFloat4(float[] xyzw)
{
var q = (xyzw != null && xyzw.Length == 4)