From 3fdecc005b7a1fcf95536e8acd7fdb957c0431eb Mon Sep 17 00:00:00 2001 From: ousttrue Date: Mon, 8 Sep 2025 14:42:42 +0900 Subject: [PATCH 1/7] label --- .../SpringBone/VRM10SpringBoneJointEditor.cs | 20 +++++++++---------- .../Vrm10Instance/Vrm10InstanceSpringBone.cs | 7 ++++--- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs b/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs index 4a06dc63f..4e86a5153 100644 --- a/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs +++ b/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs @@ -149,7 +149,7 @@ namespace UniVRM10 return false; } - var (spring, i) = found.Value; + var (spring, i, _) = found.Value; m_showJoints = EditorGUILayout.Foldout(m_showJoints, $"Springs[{i}]({spring.Name})"); int? jointIndex = default; // joints @@ -198,22 +198,20 @@ namespace UniVRM10 { return; } + + // joint(m_target) から、所属する spring を検索する。 var found = m_root.SpringBone.FindJoint(m_target); if (!found.HasValue) { return; } + // 所属 spring と joint(m_target) の index を得る + var (spring, i, j) = found.Value; - var (spring, i) = found.Value; - if (spring.Joints.Count > 0 && spring.Joints[0] != null) - { - var label = $"Springs[{i}]"; - if (!string.IsNullOrEmpty(spring.Name)) - { - label = spring.Name; - } - Handles.Label(spring.Joints[0].transform.position, label); - } + var label = string.IsNullOrEmpty(spring.Name) + ? $"[{i}][{j}]{m_target.name}" + : $"[{i}]{spring.Name}[{j}]{m_target.name}"; + Handles.Label(spring.Joints[j].transform.position, label); } } } diff --git a/Assets/VRM10/Runtime/Components/Vrm10Instance/Vrm10InstanceSpringBone.cs b/Assets/VRM10/Runtime/Components/Vrm10Instance/Vrm10InstanceSpringBone.cs index edc9b5397..f47a972c3 100644 --- a/Assets/VRM10/Runtime/Components/Vrm10Instance/Vrm10InstanceSpringBone.cs +++ b/Assets/VRM10/Runtime/Components/Vrm10Instance/Vrm10InstanceSpringBone.cs @@ -77,13 +77,14 @@ namespace UniVRM10 [SerializeField] public List Springs = new List(); - public (Spring, int)? FindJoint(VRM10SpringBoneJoint joint) + public (Spring spring, int springIndex, int jointIndex)? FindJoint(VRM10SpringBoneJoint joint) { for (int i = 0; i < Springs.Count; ++i) { - if (Springs[i].Joints.Contains(joint)) + var j = Springs[i].Joints.IndexOf(joint); + if (j >= 0) { - return (Springs[i], i); + return new(Springs[i], i, j); } } return default; From 721ad2466f0452bb9cd5ce43161b4450a0a222c8 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Mon, 8 Sep 2025 15:47:57 +0900 Subject: [PATCH 2/7] calcSpringboneLimitSpace --- .../SpringBone/VRM10SpringBoneJointEditor.cs | 56 ++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs b/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs index 4e86a5153..55a638e6a 100644 --- a/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs +++ b/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs @@ -192,6 +192,21 @@ namespace UniVRM10 return jointIndex == (spring.Joints.Count - 1); } + /// + /// (0,1,0) + /// ^ / + /// |/ + /// +--> + /// 初期姿勢(T-Pose)の tail position が 0,1,0 になるように + /// joint local 空間を補正した空間である。回転 offset として angleLimitRotation(default は identity) も乗算する。 + /// + /// + static Quaternion calcSpringboneLimitSpace(Quaternion head, Vector3 boneAxis, Quaternion angleLimitRotation) + { + var jointLocalAxisSpace = Quaternion.FromToRotation(Vector3.up, boneAxis); + return head * jointLocalAxisSpace * angleLimitRotation; + } + void OnSceneGUI() { if (m_root == null) @@ -208,10 +223,47 @@ namespace UniVRM10 // 所属 spring と joint(m_target) の index を得る var (spring, i, j) = found.Value; + var head = spring.Joints[j].transform; var label = string.IsNullOrEmpty(spring.Name) ? $"[{i}][{j}]{m_target.name}" : $"[{i}]{spring.Name}[{j}]{m_target.name}"; - Handles.Label(spring.Joints[j].transform.position, label); + Handles.Label(head.transform.position, label); + + if (j + 1 < spring.Joints.Count) + { + var _tail = spring.Joints[j + 1]; + if (_tail != null) + { + var tail = _tail.transform; + switch (m_target.m_anglelimitType) + { + case UniGLTF.SpringBoneJobs.AnglelimitTypes.Cone: + { + var local_axis = head.worldToLocalMatrix.MultiplyPoint(tail.position); + var limit_tail_pos = Vector3.up * local_axis.magnitude; + var limitRotation = calcSpringboneLimitSpace(head.rotation, local_axis, m_target.m_angleLimitRotation); + var limitSpace = Matrix4x4.TRS(head.position, limitRotation, Vector3.one); + + var r = Mathf.Tan(m_target.m_angleLimitAngle1 * 0.5f); + + Handles.matrix = limitSpace; + Handles.DrawLine(Vector3.zero, limit_tail_pos); + Handles.DrawWireDisc(limit_tail_pos, Vector3.up, r, 1); + // o head + // r / + // |/ + // -r --+-- r + // | + // -r + Handles.DrawLine(Vector3.zero, limit_tail_pos + Vector3.right * r); + Handles.DrawLine(Vector3.zero, limit_tail_pos + Vector3.left * r); + Handles.DrawLine(Vector3.zero, limit_tail_pos + Vector3.forward * r); + Handles.DrawLine(Vector3.zero, limit_tail_pos + Vector3.back * r); + break; + } + } + } + } } } -} +} \ No newline at end of file From db5ff3529ce3afc87af849bd25325722393b8b74 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Mon, 8 Sep 2025 16:30:26 +0900 Subject: [PATCH 3/7] Hinge --- .../SpringBone/VRM10SpringBoneJointEditor.cs | 54 ++++++++++++++----- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs b/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs index 55a638e6a..36d1a57bb 100644 --- a/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs +++ b/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs @@ -1,3 +1,4 @@ +using System; using UnityEditor; using UnityEngine; @@ -235,32 +236,59 @@ namespace UniVRM10 if (_tail != null) { var tail = _tail.transform; + var local_axis = head.worldToLocalMatrix.MultiplyPoint(tail.position); + var limit_tail_pos = Vector3.up * local_axis.magnitude; + var limitRotation = calcSpringboneLimitSpace(head.rotation, local_axis, m_target.m_angleLimitRotation); + 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); + + var s = Mathf.Sin(m_target.m_angleLimitAngle1 * 0.5f); + var c = Mathf.Cos(m_target.m_angleLimitAngle1 * 0.5f); + switch (m_target.m_anglelimitType) { case UniGLTF.SpringBoneJobs.AnglelimitTypes.Cone: { - var local_axis = head.worldToLocalMatrix.MultiplyPoint(tail.position); - var limit_tail_pos = Vector3.up * local_axis.magnitude; - var limitRotation = calcSpringboneLimitSpace(head.rotation, local_axis, m_target.m_angleLimitRotation); - var limitSpace = Matrix4x4.TRS(head.position, limitRotation, Vector3.one); + var r = Mathf.Tan(m_target.m_angleLimitAngle1 * 0.5f) * limit_tail_pos.magnitude * c; - var r = Mathf.Tan(m_target.m_angleLimitAngle1 * 0.5f); - - Handles.matrix = limitSpace; - Handles.DrawLine(Vector3.zero, limit_tail_pos); - Handles.DrawWireDisc(limit_tail_pos, Vector3.up, r, 1); + Handles.color = Color.cyan; + Handles.DrawWireDisc(limit_tail_pos * c, Vector3.up, r, 1); // o head // r / // |/ // -r --+-- r // | // -r - Handles.DrawLine(Vector3.zero, limit_tail_pos + Vector3.right * r); - Handles.DrawLine(Vector3.zero, limit_tail_pos + Vector3.left * r); - Handles.DrawLine(Vector3.zero, limit_tail_pos + Vector3.forward * r); - Handles.DrawLine(Vector3.zero, limit_tail_pos + Vector3.back * 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; } + + case UniGLTF.SpringBoneJobs.AnglelimitTypes.Hinge: + { + 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; + } + } } } From 925a9d8b0c53adfc1e9f4906faae3eb03e39251f Mon Sep 17 00:00:00 2001 From: ousttrue Date: Mon, 8 Sep 2025 17:53:08 +0900 Subject: [PATCH 4/7] HandleLimitRotation --- .../SpringBone/VRM10SpringBoneJointEditor.cs | 66 ++++++++++++++++++- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs b/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs index 36d1a57bb..44a0bdec0 100644 --- a/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs +++ b/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs @@ -202,10 +202,30 @@ namespace UniVRM10 /// joint local 空間を補正した空間である。回転 offset として angleLimitRotation(default は identity) も乗算する。 /// /// - static Quaternion calcSpringboneLimitSpace(Quaternion head, Vector3 boneAxis, Quaternion angleLimitRotation) + static Quaternion calcSpringboneLimitSpace(Quaternion head, Vector3 boneAxis) { var jointLocalAxisSpace = Quaternion.FromToRotation(Vector3.up, boneAxis); - return head * jointLocalAxisSpace * angleLimitRotation; + return head * jointLocalAxisSpace; + } + + bool HandleLimitRotation(Matrix4x4 limitSpace) + { + Handles.matrix = limitSpace; + EditorGUI.BeginChangeCheck(); + Quaternion rot = Handles.RotationHandle(m_target.m_angleLimitRotation, Vector3.zero); + if (EditorGUI.EndChangeCheck()) + { + Undo.RecordObject(target, "m_angleLimitRotation"); + // serializedObject.Update(); + // m_angleLimitRotation.quaternionValue = rot; + // serializedObject.ApplyModifiedProperties(); + m_target.m_angleLimitRotation = rot; + return true; + } + else + { + return false; + } } void OnSceneGUI() @@ -238,7 +258,29 @@ namespace UniVRM10 var tail = _tail.transform; var local_axis = head.worldToLocalMatrix.MultiplyPoint(tail.position); var limit_tail_pos = Vector3.up * local_axis.magnitude; - var limitRotation = calcSpringboneLimitSpace(head.rotation, local_axis, m_target.m_angleLimitRotation); + var limitRotation = calcSpringboneLimitSpace(head.rotation, local_axis); + + if (m_target.m_anglelimitType == UniGLTF.SpringBoneJobs.AnglelimitTypes.None) + { + Tools.hidden = false; + } + else + { + Tools.hidden = true; + if (HandleLimitRotation(Matrix4x4.TRS(head.position, limitRotation, Vector3.one))) + { + if (Application.isPlaying) + { + if (m_root != null) + { + m_root.Runtime.SpringBone.SetJointLevel(m_target.transform, m_target.Blittable); + } + } + } + } + + limitRotation = limitRotation * m_target.m_angleLimitRotation; + var limitSpace = Matrix4x4.TRS(head.position, limitRotation, Vector3.one); Handles.matrix = limitSpace; Handles.color = Color.red; @@ -289,6 +331,24 @@ namespace UniVRM10 break; } + case UniGLTF.SpringBoneJobs.AnglelimitTypes.Spherical: + { + 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; + } } } } From 61e79dca4e0add296d52a105d7a4238efdbcc0af Mon Sep 17 00:00:00 2001 From: ousttrue Date: Mon, 8 Sep 2025 18:02:25 +0900 Subject: [PATCH 5/7] DrawChain --- .../SpringBone/VRM10SpringBoneJointEditor.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs b/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs index 44a0bdec0..f918e3c4c 100644 --- a/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs +++ b/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs @@ -228,6 +228,21 @@ namespace UniVRM10 } } + static void DrawChain(Vrm10InstanceSpringBone.Spring spring) + { + Handles.color = Color.yellow; + var head = spring.Joints[0]; + for (int i = 1; i < spring.Joints.Count; ++i) + { + var tail = spring.Joints[i]; + if (head != null && tail != null) + { + Handles.DrawLine(head.transform.position, tail.transform.position); + } + head = tail; + } + } + void OnSceneGUI() { if (m_root == null) @@ -249,6 +264,7 @@ namespace UniVRM10 ? $"[{i}][{j}]{m_target.name}" : $"[{i}]{spring.Name}[{j}]{m_target.name}"; Handles.Label(head.transform.position, label); + DrawChain(spring); if (j + 1 < spring.Joints.Count) { From 22b1363c78e598e0b5c1f7f1e86716e0b6d2eba2 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Mon, 8 Sep 2025 18:45:18 +0900 Subject: [PATCH 6/7] Spherical --- .../SpringBone/VRM10SpringBoneJointEditor.cs | 70 +++++++++++++++---- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs b/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs index f918e3c4c..df3feab2c 100644 --- a/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs +++ b/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs @@ -304,16 +304,15 @@ namespace UniVRM10 Handles.color = Color.green; Handles.DrawLine(Vector3.zero, Vector3.up * limit_tail_pos.magnitude); - var s = Mathf.Sin(m_target.m_angleLimitAngle1 * 0.5f); - var c = Mathf.Cos(m_target.m_angleLimitAngle1 * 0.5f); - switch (m_target.m_anglelimitType) { case UniGLTF.SpringBoneJobs.AnglelimitTypes.Cone: { - var r = Mathf.Tan(m_target.m_angleLimitAngle1 * 0.5f) * limit_tail_pos.magnitude * c; + 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 / @@ -330,6 +329,9 @@ namespace UniVRM10 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, @@ -350,19 +352,59 @@ namespace UniVRM10 case UniGLTF.SpringBoneJobs.AnglelimitTypes.Spherical: { Handles.color = Color.cyan; - Handles.DrawWireArc(Vector3.zero, Vector3.left, - new Vector3(0, c, s) * limit_tail_pos.magnitude, + + 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 ); - // 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); + 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; } } From c783ed4640a90bef26dd72ecdc78efd6ce481f31 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Fri, 12 Sep 2025 13:47:58 +0900 Subject: [PATCH 7/7] remove comment. cannot access to serializedObject in OnSceneGUI --- .../Components/SpringBone/VRM10SpringBoneJointEditor.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs b/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs index df3feab2c..2b24129b7 100644 --- a/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs +++ b/Assets/VRM10/Editor/Components/SpringBone/VRM10SpringBoneJointEditor.cs @@ -216,9 +216,6 @@ namespace UniVRM10 if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(target, "m_angleLimitRotation"); - // serializedObject.Update(); - // m_angleLimitRotation.quaternionValue = rot; - // serializedObject.ApplyModifiedProperties(); m_target.m_angleLimitRotation = rot; return true; } @@ -412,4 +409,4 @@ namespace UniVRM10 } } } -} \ No newline at end of file +}