Merge pull request #2727 from ousttrue/fix/springbone_limit
Some checks failed
Expired Issues Closure / cycle-weekly-close (push) Has been cancelled

[SpringboneLimit] gizmo 仕様合わせ
This commit is contained in:
ousttrue 2025-10-02 16:47:28 +09:00 committed by GitHub
commit de05f993b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 213 additions and 160 deletions

View File

@ -18,8 +18,8 @@ namespace UniVRM10
private SerializedProperty m_jointRadiusProp;
private SerializedProperty m_angleLimitType;
private SerializedProperty m_angleLimitRotation;
private SerializedProperty m_angleLimitAngle1;
private SerializedProperty m_angleLimitAngle2;
private SerializedProperty m_angleLimitPitch;
private SerializedProperty m_angleLimitYaw;
private Vrm10Instance m_root;
@ -38,9 +38,9 @@ namespace UniVRM10
m_dragForceProp = serializedObject.FindProperty(nameof(VRM10SpringBoneJoint.m_dragForce));
m_jointRadiusProp = serializedObject.FindProperty(nameof(VRM10SpringBoneJoint.m_jointRadius));
m_angleLimitType = serializedObject.FindProperty(nameof(VRM10SpringBoneJoint.m_anglelimitType));
m_angleLimitRotation = serializedObject.FindProperty(nameof(VRM10SpringBoneJoint.m_angleLimitRotation));
m_angleLimitAngle1 = serializedObject.FindProperty(nameof(VRM10SpringBoneJoint.m_angleLimitAngle1));
m_angleLimitAngle2 = serializedObject.FindProperty(nameof(VRM10SpringBoneJoint.m_angleLimitAngle2));
m_angleLimitRotation = serializedObject.FindProperty(nameof(VRM10SpringBoneJoint.m_limitSpaceOffset));
m_angleLimitPitch = serializedObject.FindProperty(nameof(VRM10SpringBoneJoint.m_pitch));
m_angleLimitYaw = serializedObject.FindProperty(nameof(VRM10SpringBoneJoint.m_yaw));
m_root = m_target.GetComponentInParent<Vrm10Instance>();
}
@ -88,7 +88,12 @@ namespace UniVRM10
//
// angle limit
//
m_showAnglelimitSettings = EditorGUILayout.Foldout(m_showAnglelimitSettings, "AngleLimit Settings (experimental)");
var fold = EditorGUILayout.Foldout(m_showAnglelimitSettings, "AngleLimit Settings (experimental)");
if (m_showAnglelimitSettings != fold)
{
m_showAnglelimitSettings = fold;
SceneView.RepaintAll();
}
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);
@ -101,18 +106,18 @@ namespace UniVRM10
case UniGLTF.SpringBoneJobs.AnglelimitTypes.Cone:
EditorGUILayout.PropertyField(m_angleLimitRotation);
EditorGUILayout.PropertyField(m_angleLimitAngle1);
EditorGUILayout.PropertyField(m_angleLimitPitch);
break;
case UniGLTF.SpringBoneJobs.AnglelimitTypes.Hinge:
EditorGUILayout.PropertyField(m_angleLimitRotation);
EditorGUILayout.PropertyField(m_angleLimitAngle1);
EditorGUILayout.PropertyField(m_angleLimitPitch);
break;
case UniGLTF.SpringBoneJobs.AnglelimitTypes.Spherical:
EditorGUILayout.PropertyField(m_angleLimitRotation);
EditorGUILayout.PropertyField(m_angleLimitAngle1);
EditorGUILayout.PropertyField(m_angleLimitAngle2);
EditorGUILayout.PropertyField(m_angleLimitPitch);
EditorGUILayout.PropertyField(m_angleLimitYaw);
break;
}
}
@ -212,11 +217,11 @@ namespace UniVRM10
{
Handles.matrix = limitSpace;
EditorGUI.BeginChangeCheck();
Quaternion rot = Handles.RotationHandle(m_target.m_angleLimitRotation, Vector3.zero);
Quaternion rot = Handles.RotationHandle(m_target.m_limitSpaceOffset, Vector3.zero);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, "m_angleLimitRotation");
m_target.m_angleLimitRotation = rot;
m_target.m_limitSpaceOffset = rot;
return true;
}
else
@ -227,7 +232,7 @@ namespace UniVRM10
static void DrawChain(Vrm10InstanceSpringBone.Spring spring)
{
Handles.color = Color.yellow;
Handles.color = new Color(1, 0.5f, 0);
var head = spring.Joints[0];
for (int i = 1; i < spring.Joints.Count; ++i)
{
@ -240,8 +245,27 @@ namespace UniVRM10
}
}
static void DrawSpace(Matrix4x4 limitSpace, float size)
{
float half = size * 0.5f;
Handles.matrix = limitSpace;
Handles.color = Color.red;
var x = Vector3.right * half;
Handles.DrawLine(x, -x);
Handles.color = Color.green;
Handles.DrawLine(Vector3.zero, Vector3.up * size);
Handles.color = Color.blue;
var y = Vector3.forward * half;
Handles.DrawLine(-y, y);
Handles.color = new Color(1, 1, 1, 0.1f);
Handles.DrawSolidDisc(Vector3.zero, Vector3.up, half);
}
void OnSceneGUI()
{
Tools.hidden = false;
if (m_root == null)
{
return;
@ -273,140 +297,169 @@ namespace UniVRM10
var limit_tail_pos = Vector3.up * local_axis.magnitude;
var limitRotation = calcSpringboneLimitSpace(head.rotation, local_axis);
if (m_target.m_anglelimitType == UniGLTF.SpringBoneJobs.AnglelimitTypes.None)
{
Tools.hidden = false;
}
else
if (m_showAnglelimitSettings)
{
Tools.hidden = true;
if (HandleLimitRotation(Matrix4x4.TRS(head.position, limitRotation, Vector3.one)))
if (m_target.m_anglelimitType != UniGLTF.SpringBoneJobs.AnglelimitTypes.None)
{
if (Application.isPlaying)
if (HandleLimitRotation(Matrix4x4.TRS(head.position, limitRotation, Vector3.one)))
{
if (m_root != null)
if (Application.isPlaying)
{
m_root.Runtime.SpringBone.SetJointLevel(m_target.transform, m_target.Blittable);
if (m_root != null)
{
m_root.Runtime.SpringBone.SetJointLevel(m_target.transform, m_target.Blittable);
}
}
}
}
}
limitRotation = limitRotation * m_target.m_angleLimitRotation;
limitRotation = limitRotation * m_target.m_limitSpaceOffset;
var limitSpace = Matrix4x4.TRS(head.position, limitRotation, Vector3.one);
Handles.matrix = limitSpace;
Handles.color = Color.red;
Handles.DrawLine(Vector3.zero, Vector3.right * limit_tail_pos.magnitude);
Handles.color = Color.green;
Handles.DrawLine(Vector3.zero, Vector3.up * limit_tail_pos.magnitude);
DrawSpace(limitSpace, limit_tail_pos.magnitude);
switch (m_target.m_anglelimitType)
{
case UniGLTF.SpringBoneJobs.AnglelimitTypes.Cone:
{
var s = Mathf.Sin(m_target.m_angleLimitAngle1 * 0.5f);
var c = Mathf.Cos(m_target.m_angleLimitAngle1 * 0.5f);
Handles.color = Color.cyan;
var r = Mathf.Tan(m_target.m_angleLimitAngle1 * 0.5f) * limit_tail_pos.magnitude * c;
Handles.DrawWireDisc(limit_tail_pos * c, Vector3.up, r, 1);
// o head
// r /
// |/
// -r --+-- r
// |
// -r
Handles.DrawLine(Vector3.zero, new Vector3(0, c, s) * limit_tail_pos.magnitude);
Handles.DrawLine(Vector3.zero, new Vector3(0, c, -s) * limit_tail_pos.magnitude);
Handles.DrawLine(Vector3.zero, new Vector3(s, c, 0) * limit_tail_pos.magnitude);
Handles.DrawLine(Vector3.zero, new Vector3(-s, c, 0) * limit_tail_pos.magnitude);
break;
}
DrawCone(limit_tail_pos, m_target.m_pitch);
break;
case UniGLTF.SpringBoneJobs.AnglelimitTypes.Hinge:
{
var s = Mathf.Sin(m_target.m_angleLimitAngle1 * 0.5f);
var c = Mathf.Cos(m_target.m_angleLimitAngle1 * 0.5f);
Handles.color = Color.cyan;
Handles.DrawWireArc(Vector3.zero, Vector3.left,
new Vector3(0, c, s) * limit_tail_pos.magnitude,
m_target.m_angleLimitAngle1 * Mathf.Rad2Deg,
limit_tail_pos.magnitude
);
// yz plane
// o o head
// / \
// / \
// -r -+- r
//
Handles.DrawLine(Vector3.zero, new Vector3(0, c, s) * limit_tail_pos.magnitude);
Handles.DrawLine(Vector3.zero, new Vector3(0, c, -s) * limit_tail_pos.magnitude);
break;
}
DrawHinge(limit_tail_pos, m_target.m_pitch, Color.cyan);
break;
case UniGLTF.SpringBoneJobs.AnglelimitTypes.Spherical:
{
Handles.color = Color.cyan;
var ts = Mathf.Sin(m_target.m_angleLimitAngle1 * 0.5f); // theta sin
var tc = Mathf.Cos(m_target.m_angleLimitAngle1 * 0.5f); // theta cos
var ps = Mathf.Sin(m_target.m_angleLimitAngle2 * 0.5f); // phi sin
var pc = Mathf.Cos(m_target.m_angleLimitAngle2 * 0.5f); // phi cos
// y = tc * pc
// ^ z = tc * ps
// |/
// +-> x = ts
var x = ts;
var y = tc * pc;
var z = tc * ps;
// z
// ^
// b|a
// -+->x
// c|d
var a = new Vector3(x, y, z);
var b = new Vector3(-x, y, z);
var c = new Vector3(-x, y, -z);
var d = new Vector3(x, y, -z);
Handles.DrawLine(Vector3.zero, a * limit_tail_pos.magnitude);
Handles.DrawLine(Vector3.zero, b * limit_tail_pos.magnitude);
Handles.DrawLine(Vector3.zero, c * limit_tail_pos.magnitude);
Handles.DrawLine(Vector3.zero, d * limit_tail_pos.magnitude);
// ab / cd
Handles.DrawWireArc(Vector3.zero, Vector3.Cross(a, b).normalized,
a * limit_tail_pos.magnitude,
m_target.m_angleLimitAngle1 * Mathf.Rad2Deg,
limit_tail_pos.magnitude
);
Handles.DrawWireArc(Vector3.zero, Vector3.Cross(c, d).normalized,
c * limit_tail_pos.magnitude,
m_target.m_angleLimitAngle1 * Mathf.Rad2Deg,
limit_tail_pos.magnitude
);
// bc / da
Handles.DrawWireArc(Vector3.zero, Vector3.Cross(b, c).normalized,
b * limit_tail_pos.magnitude,
Vector3.Angle(b, c),
limit_tail_pos.magnitude
);
Handles.DrawWireArc(Vector3.zero, Vector3.Cross(d, a).normalized,
d * limit_tail_pos.magnitude,
Vector3.Angle(d, a),
limit_tail_pos.magnitude
);
break;
}
DrawHinge(limit_tail_pos, m_target.m_pitch, Color.cyan * 0.5f);
DrawSpherical(limit_tail_pos, m_target.m_pitch, m_target.m_yaw);
break;
}
}
}
}
private static void DrawCone(in Vector3 limit_tail_pos, float pitch)
{
var s = Mathf.Sin(pitch);
var c = Mathf.Cos(pitch);
Handles.color = Color.cyan;
var r = Mathf.Tan(pitch) * limit_tail_pos.magnitude * c;
Handles.DrawWireDisc(limit_tail_pos * c, Vector3.up, r, 1);
// o head
// r /
// |/
// -r --+-- r
// |
// -r
var pz = new Vector3(0, c, s) * limit_tail_pos.magnitude;
var nz = new Vector3(0, c, -s) * limit_tail_pos.magnitude;
var px = new Vector3(s, c, 0) * limit_tail_pos.magnitude;
var nx = new Vector3(-s, c, 0) * limit_tail_pos.magnitude;
Handles.DrawLine(Vector3.zero, pz);
Handles.DrawLine(Vector3.zero, nz);
Handles.DrawLine(Vector3.zero, px);
Handles.DrawLine(Vector3.zero, nx);
Handles.color = new Color(0, 1, 1, 0.1f);
Handles.Label(Vector3.Slerp(limit_tail_pos, pz, 0.5f) * 0.5f, $"pitch: {pitch * Mathf.Rad2Deg:F0}°");
Handles.DrawSolidArc(Vector3.zero, Vector3.Cross(limit_tail_pos, pz),
limit_tail_pos,
pitch * Mathf.Rad2Deg,
limit_tail_pos.magnitude * 0.5f
);
}
private static void DrawHinge(in Vector3 limit_tail_pos, float pitch, Color color)
{
var s = Mathf.Sin(pitch);
var c = Mathf.Cos(pitch);
// yz plane
// o o head
// / \
// / \
// -r -+- r
//
var a = new Vector3(0, c, s) * limit_tail_pos.magnitude;
var b = new Vector3(0, c, -s) * limit_tail_pos.magnitude;
Handles.color = color;
Handles.DrawLine(Vector3.zero, a);
Handles.DrawLine(Vector3.zero, b);
Handles.DrawWireArc(Vector3.zero, Vector3.left,
new Vector3(0, c, s),
pitch * 2 * Mathf.Rad2Deg,
limit_tail_pos.magnitude
);
color.a = 0.1f;
Handles.color = color;
Handles.Label(Vector3.Slerp(limit_tail_pos, a, 0.5f) * 0.5f, $"pitch: {pitch * Mathf.Rad2Deg:F0}°");
Handles.DrawSolidArc(Vector3.zero, Vector3.left,
new Vector3(0, c, s),
pitch * Mathf.Rad2Deg,
limit_tail_pos.magnitude * 0.5f
);
}
private static void DrawSpherical(in Vector3 limit_tail_pos, float pitch, float yaw)
{
Handles.color = Color.cyan;
var ts = Mathf.Sin(pitch);
var tc = Mathf.Cos(pitch);
var ps = Mathf.Sin(yaw);
var pc = Mathf.Cos(yaw);
// y = tc * pc
// ^ z = tc * ps
// |/
// +-> x = ts
var x = ps;
var y = pc * tc;
var z = pc * ts;
// z
// ^
// b|a
// -+->x
// c|d
var a = new Vector3(x, y, z);
var b = new Vector3(-x, y, z);
var c = new Vector3(-x, y, -z);
var d = new Vector3(x, y, -z);
Handles.DrawLine(Vector3.zero, a * limit_tail_pos.magnitude);
Handles.DrawLine(Vector3.zero, b * limit_tail_pos.magnitude);
Handles.DrawLine(Vector3.zero, c * limit_tail_pos.magnitude);
Handles.DrawLine(Vector3.zero, d * limit_tail_pos.magnitude);
// ab / cd
Handles.DrawWireArc(Vector3.zero, Vector3.Cross(a, b).normalized,
a * limit_tail_pos.magnitude,
Vector3.Angle(a, b),
limit_tail_pos.magnitude
);
Handles.DrawWireArc(Vector3.zero, Vector3.Cross(c, d).normalized,
c * limit_tail_pos.magnitude,
Vector3.Angle(c, d),
limit_tail_pos.magnitude
);
Handles.Label(Vector3.Slerp(a, b, 0.25f) * limit_tail_pos.magnitude, $"yaw: {yaw * Mathf.Rad2Deg:F0}°");
// bc / da
Handles.DrawWireArc(Vector3.zero, Vector3.Cross(b, c).normalized,
b * limit_tail_pos.magnitude,
Vector3.Angle(b, c),
limit_tail_pos.magnitude
);
Handles.DrawWireArc(Vector3.zero, Vector3.Cross(d, a).normalized,
d * limit_tail_pos.magnitude,
Vector3.Angle(d, a),
limit_tail_pos.magnitude
);
}
}
}
}

View File

@ -30,13 +30,13 @@ namespace UniVRM10
public UniGLTF.SpringBoneJobs.AnglelimitTypes m_anglelimitType;
[SerializeField]
public Quaternion m_angleLimitRotation = Quaternion.identity;
public Quaternion m_limitSpaceOffset = Quaternion.identity;
[SerializeField, Range(0, Mathf.PI)]
public float m_angleLimitAngle1 = Mathf.PI;
public float m_pitch = Mathf.PI;
[SerializeField, Range(0, Mathf.PI)]
public float m_angleLimitAngle2 = Mathf.PI;
[SerializeField, Range(0, Mathf.PI / 2)]
public float m_yaw = 0;
public BlittableJointMutable Blittable => new BlittableJointMutable(
stiffnessForce: m_stiffnessForce,
@ -46,16 +46,16 @@ namespace UniVRM10
radius: m_jointRadius,
// v0.129.4
angleLimitType: (float)m_anglelimitType,
angleLimit1: m_angleLimitAngle1,
angleLimit2: m_angleLimitAngle2,
angleLimitOffset: m_angleLimitRotation
angleLimit1: m_pitch,
angleLimit2: m_yaw,
angleLimitOffset: m_limitSpaceOffset
);
void OnValidate()
{
if (m_angleLimitRotation == default)
if (m_limitSpaceOffset == default)
{
m_angleLimitRotation = Quaternion.identity;
m_limitSpaceOffset = Quaternion.identity;
}
}

View File

@ -202,13 +202,13 @@ public static SphericalLimit __limit_Deserialize_Spherical(JsonNode parsed)
continue;
}
if(key=="phi"){
value.Phi = kv.Value.GetSingle();
if(key=="pitch"){
value.Pitch = kv.Value.GetSingle();
continue;
}
if(key=="theta"){
value.Theta = kv.Value.GetSingle();
if(key=="yaw"){
value.Yaw = kv.Value.GetSingle();
continue;
}

View File

@ -45,10 +45,10 @@ namespace UniGLTF.Extensions.VRMC_springBone_limit
public object Extras;
// The phi angle of the spherical limit in radians. If the phi angle is set to π or greater, the angle will be interpreted as π by the implementation.
public float? Phi;
public float? Pitch;
// The theta angle of the spherical limit in radians. If the theta angle is set to π/2 or greater, the angle will be interpreted as π/2 by the implementation.
public float? Theta;
public float? Yaw;
// The rotation from the default orientation of the spherical limit. The rotation is represented as a quaternion (x, y, z, w), where w is the scalar.
public float[] Rotation;

View File

@ -184,14 +184,14 @@ public static void __limit_Serialize_Spherical(JsonFormatter f, SphericalLimit v
(value.Extras as glTFExtension).Serialize(f);
}
if(value.Phi.HasValue){
f.Key("phi");
f.Value(value.Phi.GetValueOrDefault());
if(value.Pitch.HasValue){
f.Key("pitch");
f.Value(value.Pitch.GetValueOrDefault());
}
if(value.Theta.HasValue){
f.Key("theta");
f.Value(value.Theta.GetValueOrDefault());
if(value.Yaw.HasValue){
f.Key("yaw");
f.Value(value.Yaw.GetValueOrDefault());
}
if(value.Rotation!=null&&value.Rotation.Count()>=4){

View File

@ -461,8 +461,8 @@ namespace UniVRM10
{
Cone = new UniGLTF.Extensions.VRMC_springBone_limit.ConeLimit
{
Rotation = ReverseX(y.m_angleLimitRotation),
Angle = y.m_angleLimitAngle1,
Rotation = ReverseX(y.m_limitSpaceOffset),
Angle = y.m_pitch,
}
}
};
@ -479,8 +479,8 @@ namespace UniVRM10
{
Hinge = new UniGLTF.Extensions.VRMC_springBone_limit.HingeLimit
{
Rotation = ReverseX(y.m_angleLimitRotation),
Angle = y.m_angleLimitAngle1,
Rotation = ReverseX(y.m_limitSpaceOffset),
Angle = y.m_pitch,
}
}
};
@ -497,9 +497,9 @@ namespace UniVRM10
{
Spherical = new UniGLTF.Extensions.VRMC_springBone_limit.SphericalLimit
{
Rotation = ReverseX(y.m_angleLimitRotation),
Theta = y.m_angleLimitAngle1,
Phi = y.m_angleLimitAngle2,
Rotation = ReverseX(y.m_limitSpaceOffset),
Pitch = y.m_pitch,
Yaw = y.m_yaw,
}
}
};

View File

@ -635,21 +635,21 @@ namespace UniVRM10
if (extensionSpringBoneLimit.Limit.Cone is UniGLTF.Extensions.VRMC_springBone_limit.ConeLimit cone)
{
joint.m_anglelimitType = UniGLTF.SpringBoneJobs.AnglelimitTypes.Cone;
joint.m_angleLimitRotation = QuaternionFromFloat4(cone.Rotation);
joint.m_angleLimitAngle1 = cone.Angle.GetValueOrDefault(Mathf.PI * 0.5f);
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_angleLimitRotation = QuaternionFromFloat4(hinge.Rotation);
joint.m_angleLimitAngle1 = hinge.Angle.GetValueOrDefault(Mathf.PI * 0.5f);
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_angleLimitRotation = QuaternionFromFloat4(spherical.Rotation);
joint.m_angleLimitAngle1 = spherical.Theta.GetValueOrDefault(Mathf.PI * 0.5f);
joint.m_angleLimitAngle2 = spherical.Phi.GetValueOrDefault(Mathf.PI * 0.5f);
joint.m_limitSpaceOffset = QuaternionFromFloat4(spherical.Rotation);
joint.m_pitch = spherical.Pitch.GetValueOrDefault();
joint.m_yaw = spherical.Yaw.GetValueOrDefault();
}
}