mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-09-26 12:50:40 -05:00
Merge branch 'master' into version/v0_131_3
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user