Merge branch 'master' into version/v0_131_3

This commit is contained in:
ousttrue
2026-09-25 10:32:23 +09:00
committed by GitHub
10 changed files with 387 additions and 184 deletions

View File

@@ -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;
}
/// <param name="nextTail">nextTail(position vector in world space)</param>
/// <returns>tailDir(directionay vector in angle space)</returns>
/// <exception cref="System.NotImplementedException"></exception>
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))
);
}
/// <summary>
@@ -71,20 +86,22 @@ namespace UniGLTF.SpringBoneJobs
///
/// TODO: Replace with the appropriate link to the specification later
/// </summary>
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));
}
}
}
}

View File

@@ -4,30 +4,41 @@ namespace UniGLTF.SpringBoneJobs
{
public static class AnglelimitCone
{
/// <param name="src">AngleLimit空間の方向ベクトル</param>
/// <param name="angleLimit">radius</param>
/// <param name="tailDir">AngleLimit空間の方向ベクトル</param>
/// <param name="limitAngle">radius</param>
/// <returns>AngleLimit空間の方向ベクトル</returns>
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;
}
}
}
}

View File

@@ -4,29 +4,39 @@ namespace UniGLTF.SpringBoneJobs
{
public static class AnglelimitHinge
{
/// <param name="src">AngleLimit空間の方向ベクトル</param>
/// <param name="angleLimit">radius</param>
/// <param name="tailDir">AngleLimit空間の方向ベクトル</param>
/// <param name="limitAngle">radius</param>
/// <returns>AngleLimit空間の方向ベクトル</returns>
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;
}
}
}
}

View File

@@ -5,33 +5,55 @@ namespace UniGLTF.SpringBoneJobs
public static class AnglelimitSpherical
{
/// <param name="tailDir">AngleLimit空間の方向ベクトル</param>
/// <param name="limitAnglePhi">radius</param>
/// <param name="limitAngleTheta">radius</param>
/// <param name="limitPitch">radius</param>
/// <param name="limitYaw">radius</param>
/// <returns>AngleLimit空間の方向ベクトル</returns>
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;
}
}
}
}

View File

@@ -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,

View File

@@ -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の情報を表示する
/// </summary>
/// <returns>末端</returns>
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);
}
/// <summary>

View File

@@ -26,15 +26,27 @@ namespace UniVRM10
[SerializeField]
public float m_jointRadius = 0.02f;
/// <summary>
/// VRMC_springBone_limit
/// </summary>
[SerializeField]
public UniGLTF.SpringBoneJobs.AnglelimitTypes m_anglelimitType;
/// <summary>
/// VRMC_springBone_limit
/// </summary>
[SerializeField]
public Quaternion m_limitSpaceOffset = Quaternion.identity;
/// <summary>
/// VRMC_springBone_limit
/// </summary>
[SerializeField, Range(0, Mathf.PI)]
public float m_pitch = Mathf.PI;
/// <summary>
/// VRMC_springBone_limit
/// </summary>
[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<VRM10SpringBoneJoint>();
@@ -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)

View File

@@ -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;
}
/// <summary>
/// Return SpringBone EditInfo.
/// Do not call on runtime play for performance.
/// </summary>
/// <param name="target">On Editor may null.</param>
/// <param name="root">GetComponentInParent cache.</param>
/// <returns></returns>
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<Vrm10Instance>();
}
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;
}
}
}

View File

@@ -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;
}

View File

@@ -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)