diff --git a/Assets/VRM/Runtime/SpringBone/Logic/SceneInfo.cs b/Assets/VRM/Runtime/SpringBone/Logic/SceneInfo.cs index 546673013..15053e9d2 100644 --- a/Assets/VRM/Runtime/SpringBone/Logic/SceneInfo.cs +++ b/Assets/VRM/Runtime/SpringBone/Logic/SceneInfo.cs @@ -3,10 +3,16 @@ using UnityEngine; namespace VRM.SpringBone { - struct SceneInfo + readonly struct SceneInfo { - public IReadOnlyList RootBones; - public Transform Center; - public VRMSpringBoneColliderGroup[] ColliderGroups; + public readonly IReadOnlyList RootBones; + public readonly Transform Center; + public readonly VRMSpringBoneColliderGroup[] ColliderGroups; + + public SceneInfo( + IReadOnlyList rootBones, + Transform center, + VRMSpringBoneColliderGroup[] colliderGroups) => + (RootBones, Center, ColliderGroups) = (rootBones, center, colliderGroups); } } \ No newline at end of file diff --git a/Assets/VRM/Runtime/SpringBone/Logic/SphereCollider.cs b/Assets/VRM/Runtime/SpringBone/Logic/SphereCollider.cs index a59c36e8c..ded97a744 100644 --- a/Assets/VRM/Runtime/SpringBone/Logic/SphereCollider.cs +++ b/Assets/VRM/Runtime/SpringBone/Logic/SphereCollider.cs @@ -2,7 +2,7 @@ using UnityEngine; namespace VRM.SpringBone { - struct SphereCollider + readonly struct SphereCollider { public readonly Vector3 Position; public readonly float Radius; @@ -14,5 +14,23 @@ namespace VRM.SpringBone var scale = Mathf.Max(Mathf.Max(ls.x, ls.y), ls.z); Radius = scale * collider.Radius; } + + public bool TryCollide(SpringBoneSettings settings, Transform transform, Vector3 nextTail, out Vector3 posFromCollider) + { + var m_radius = settings.HitRadius * transform.UniformedLossyScale(); + var r = m_radius + Radius; + if (Vector3.SqrMagnitude(nextTail - Position) <= (r * r)) + { + // ヒット。Colliderの半径方向に押し出す + var normal = (nextTail - Position).normalized; + posFromCollider = Position + normal * (m_radius + Radius); + return true; + } + else + { + posFromCollider = default; + return false; + } + } } } \ No newline at end of file diff --git a/Assets/VRM/Runtime/SpringBone/Logic/SpringBoneJoint.cs b/Assets/VRM/Runtime/SpringBone/Logic/SpringBoneJoint.cs deleted file mode 100644 index d15e48201..000000000 --- a/Assets/VRM/Runtime/SpringBone/Logic/SpringBoneJoint.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; - -namespace VRM.SpringBone -{ - /// - /// original from - /// http://rocketjump.skr.jp/unity3d/109/ - /// - class SpringBoneJoint - { - Transform m_transform; - public Transform Head => m_transform; - - private Vector3 m_boneAxis; - private Vector3 m_currentTail; - - private readonly float m_length; - private Vector3 m_localDir; - private Vector3 m_prevTail; - - public SpringBoneJoint(Transform center, Transform transform, Vector3 localChildPosition) - { - m_transform = transform; - var worldChildPosition = m_transform.TransformPoint(localChildPosition); - m_currentTail = center != null - ? center.InverseTransformPoint(worldChildPosition) - : worldChildPosition; - m_prevTail = m_currentTail; - LocalRotation = transform.localRotation; - m_boneAxis = localChildPosition.normalized; - m_length = localChildPosition.magnitude; - } - - public Vector3 Tail => m_transform.localToWorldMatrix.MultiplyPoint(m_boneAxis * m_length); - - private Quaternion LocalRotation { get; } - - float m_radius; - public void SetRadius(float radius) - { - m_radius = radius * m_transform.UniformedLossyScale(); - } - - private Quaternion ParentRotation => - m_transform.parent != null - ? m_transform.parent.rotation - : Quaternion.identity; - - public void Update(Transform center, - float stiffnessForce, float dragForce, Vector3 external, - List colliders) - { - var currentTail = center != null - ? center.TransformPoint(m_currentTail) - : m_currentTail; - var prevTail = center != null - ? center.TransformPoint(m_prevTail) - : m_prevTail; - - // verlet積分で次の位置を計算 - var nextTail = currentTail - + (currentTail - prevTail) * (1.0f - dragForce) // 前フレームの移動を継続する(減衰もあるよ) - + ParentRotation * LocalRotation * m_boneAxis * stiffnessForce // 親の回転による子ボーンの移動目標 - + external; // 外力による移動量 - - // 長さをboneLengthに強制 - var position = m_transform.position; - nextTail = position + (nextTail - position).normalized * m_length; - - // Collisionで移動 - nextTail = Collision(colliders, nextTail); - - m_prevTail = center != null - ? center.InverseTransformPoint(currentTail) - : currentTail; - - m_currentTail = center != null - ? center.InverseTransformPoint(nextTail) - : nextTail; - - //回転を適用 - m_transform.rotation = ApplyRotation(nextTail); - } - - protected virtual Quaternion ApplyRotation(Vector3 nextTail) - { - var rotation = ParentRotation * LocalRotation; - return Quaternion.FromToRotation(rotation * m_boneAxis, - nextTail - m_transform.position) * rotation; - } - - protected virtual Vector3 Collision(List colliders, Vector3 nextTail) - { - foreach (var collider in colliders) - { - var r = m_radius + collider.Radius; - if (Vector3.SqrMagnitude(nextTail - collider.Position) <= (r * r)) - { - // ヒット。Colliderの半径方向に押し出す - var normal = (nextTail - collider.Position).normalized; - var posFromCollider = collider.Position + normal * (m_radius + collider.Radius); - // 長さをboneLengthに強制 - nextTail = m_transform.position + (posFromCollider - m_transform.position).normalized * m_length; - } - } - return nextTail; - } - - public void DrawGizmo(Transform center, Color color) - { - var currentTail = center != null - ? center.TransformPoint(m_currentTail) - : m_currentTail; - var prevTail = center != null - ? center.TransformPoint(m_prevTail) - : m_prevTail; - - Gizmos.color = Color.gray; - Gizmos.DrawLine(currentTail, prevTail); - Gizmos.DrawWireSphere(prevTail, m_radius); - - Gizmos.color = color; - Gizmos.DrawLine(currentTail, m_transform.position); - Gizmos.DrawWireSphere(currentTail, m_radius); - } - } - -} \ No newline at end of file diff --git a/Assets/VRM/Runtime/SpringBone/Logic/SpringBoneJointInit.cs b/Assets/VRM/Runtime/SpringBone/Logic/SpringBoneJointInit.cs new file mode 100644 index 000000000..6b2b92ce0 --- /dev/null +++ b/Assets/VRM/Runtime/SpringBone/Logic/SpringBoneJointInit.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace VRM.SpringBone +{ + /// + /// original from + /// http://rocketjump.skr.jp/unity3d/109/ + /// + /// この型のフィールドはSpringBoneのライフサイクルを通じて不変。 + /// + readonly struct SpringBoneJointInit + { + public readonly Vector3 BoneAxis; + public readonly float Length; + public readonly Quaternion LocalRotation; + + public SpringBoneJointInit( + Vector3 boneAxis, + float length, + Quaternion localRotation) => (BoneAxis, Length, LocalRotation) = (boneAxis, length, localRotation); + + /// + /// しっぽの位置から回転を計算する + /// + public Quaternion WorldRotationFromTailPosition(Transform m_transform, Vector3 nextTail) + { + var rotation = (m_transform.parent != null ? m_transform.parent.rotation : Quaternion.identity) * LocalRotation; + return Quaternion.FromToRotation(rotation * BoneAxis, + nextTail - m_transform.position) * rotation; + } + + /// + /// Verlet積分で次の位置を計算する + /// + public Vector3 VerletIntegration(float deltaTime, Transform center, Quaternion parentRotation, + SpringBoneSettings settings, SpringBoneJointState _state) + { + var state = _state.ToWorld(center); + + // verlet積分で次の位置を計算 + var nextTail = state.CurrentTail + + (state.CurrentTail - state.PrevTail) * (1.0f - settings.DragForce) // 前フレームの移動を継続する(減衰もあるよ) + + parentRotation * LocalRotation * BoneAxis * settings.StiffnessForce * deltaTime // 親の回転による子ボーンの移動目標 + + settings.GravityDir * (settings.GravityPower * deltaTime); // 外力による移動量 + return nextTail; + } + + public void DrawGizmo(Transform center, Transform m_transform, SpringBoneSettings settings, Color color, SpringBoneJointState m_state) + { + var state = m_state.ToWorld(center); + var m_radius = settings.HitRadius * m_transform.UniformedLossyScale(); + + Gizmos.color = Color.gray; + Gizmos.DrawLine(state.CurrentTail, state.PrevTail); + Gizmos.DrawWireSphere(state.PrevTail, m_radius); + + Gizmos.color = color; + Gizmos.DrawLine(state.CurrentTail, m_transform.position); + Gizmos.DrawWireSphere(state.CurrentTail, m_radius); + } + + }; +} \ No newline at end of file diff --git a/Assets/VRM/Runtime/SpringBone/Logic/SpringBoneJoint.cs.meta b/Assets/VRM/Runtime/SpringBone/Logic/SpringBoneJointInit.cs.meta similarity index 100% rename from Assets/VRM/Runtime/SpringBone/Logic/SpringBoneJoint.cs.meta rename to Assets/VRM/Runtime/SpringBone/Logic/SpringBoneJointInit.cs.meta diff --git a/Assets/VRM/Runtime/SpringBone/Logic/SpringBoneJointState.cs b/Assets/VRM/Runtime/SpringBone/Logic/SpringBoneJointState.cs new file mode 100644 index 000000000..31bde5ea7 --- /dev/null +++ b/Assets/VRM/Runtime/SpringBone/Logic/SpringBoneJointState.cs @@ -0,0 +1,49 @@ +using UnityEngine; + +namespace VRM.SpringBone +{ + /// + /// 毎フレーム更新される Verlet 積分の位置状態 + /// + readonly struct SpringBoneJointState + { + public readonly Vector3 CurrentTail; + public readonly Vector3 PrevTail; + + public SpringBoneJointState(Vector3 currentTail, Vector3 prevTail) => + (CurrentTail, PrevTail) = (currentTail, prevTail); + + public static SpringBoneJointState Init(Transform center, Transform transform, Vector3 localChildPosition) + { + var worldChildPosition = transform.TransformPoint(localChildPosition); + var tail = center != null + ? center.InverseTransformPoint(worldChildPosition) + : worldChildPosition; + return new SpringBoneJointState(currentTail: tail, prevTail: tail); + } + + public static SpringBoneJointState Make(Transform center, Vector3 currentTail, Vector3 nextTail) + { + return new SpringBoneJointState + ( + prevTail: center != null + ? center.InverseTransformPoint(currentTail) + : currentTail, + currentTail: center != null + ? center.InverseTransformPoint(nextTail) + : nextTail); + } + + public SpringBoneJointState ToWorld(Transform center) + { + return new SpringBoneJointState + ( + currentTail: center != null + ? center.TransformPoint(CurrentTail) + : CurrentTail, + prevTail: center != null + ? center.TransformPoint(PrevTail) + : PrevTail); + } + }; +} \ No newline at end of file diff --git a/Assets/VRM/Runtime/SpringBone/Logic/SpringBoneJointState.cs.meta b/Assets/VRM/Runtime/SpringBone/Logic/SpringBoneJointState.cs.meta new file mode 100644 index 000000000..b68add524 --- /dev/null +++ b/Assets/VRM/Runtime/SpringBone/Logic/SpringBoneJointState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3bbcafd66c035184b8aa868b65c01c1d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM/Runtime/SpringBone/Logic/SpringBoneSettings.cs b/Assets/VRM/Runtime/SpringBone/Logic/SpringBoneSettings.cs index bc6209047..8ddabf49d 100644 --- a/Assets/VRM/Runtime/SpringBone/Logic/SpringBoneSettings.cs +++ b/Assets/VRM/Runtime/SpringBone/Logic/SpringBoneSettings.cs @@ -2,12 +2,15 @@ using UnityEngine; namespace VRM.SpringBone { - struct SpringBoneSettings + readonly struct SpringBoneSettings { - public float StiffnessForce; - public Vector3 GravityDir; - public float GravityPower; - public float HitRadius; - public float DragForce; + public readonly float StiffnessForce; + public readonly float DragForce; + public readonly Vector3 GravityDir; + public readonly float GravityPower; + public readonly float HitRadius; + + public SpringBoneSettings(float stiffnessForce, float dragForce, Vector3 gravityDir, float gravityPower, float hitRadius) => + (StiffnessForce, DragForce, GravityDir, GravityPower, HitRadius) = (stiffnessForce, dragForce, gravityDir, gravityPower, hitRadius); } } \ No newline at end of file diff --git a/Assets/VRM/Runtime/SpringBone/Logic/SpringBoneSystem.cs b/Assets/VRM/Runtime/SpringBone/Logic/SpringBoneSystem.cs index 98d5d433d..1badea980 100644 --- a/Assets/VRM/Runtime/SpringBone/Logic/SpringBoneSystem.cs +++ b/Assets/VRM/Runtime/SpringBone/Logic/SpringBoneSystem.cs @@ -4,25 +4,12 @@ using UnityEngine; namespace VRM.SpringBone { - /// - /// 同じ設定のスプリングをまとめて処理する。 - /// - /// root o-o-o-x tail - /// - /// [vrm0] tail は 7cm 遠にダミーの joint があるようにふるまう。 - /// - /// class SpringBoneSystem { Dictionary m_initialLocalRotationMap; - List m_joints = new(); + List<(Transform, SpringBoneJointInit, SpringBoneJointState)> m_joints = new(); List m_colliders = new(); - public void SetLocalRotationsIdentity() - { - foreach (var verlet in m_joints) verlet.Head.localRotation = Quaternion.identity; - } - public void Setup(SceneInfo scene, bool force) { if (force || m_initialLocalRotationMap == null) @@ -54,8 +41,8 @@ namespace VRM.SpringBone private void SetupRecursive(Transform center, Transform parent) { - Vector3 localPosition = default; - Vector3 scale = default; + Vector3 localPosition; + Vector3 scale; if (parent.childCount == 0) { // 子ノードが無い。7cm 固定 @@ -70,18 +57,25 @@ namespace VRM.SpringBone localPosition = firstChild.localPosition; scale = firstChild.lossyScale; } - m_joints.Add(new SpringBone.SpringBoneJoint(center, parent, - new Vector3( + + var localChildPosition = new Vector3( localPosition.x * scale.x, localPosition.y * scale.y, localPosition.z * scale.z - ))) - ; + ); + m_joints.Add(( + parent, + new SpringBoneJointInit + ( + localRotation: parent.localRotation, + boneAxis: localChildPosition.normalized, + length: localChildPosition.magnitude + ), + SpringBoneJointState.Init(center, parent, localChildPosition))); foreach (Transform child in parent) SetupRecursive(center, child); } - public void UpdateProcess(float deltaTime, SceneInfo scene, SpringBoneSettings settings @@ -93,6 +87,7 @@ namespace VRM.SpringBone Setup(scene, false); } + // collider の収集 m_colliders.Clear(); if (scene.ColliderGroups != null) { @@ -108,26 +103,39 @@ namespace VRM.SpringBone } } - var stiffness = settings.StiffnessForce * deltaTime; - var external = settings.GravityDir * (settings.GravityPower * deltaTime); - - foreach (var verlet in m_joints) + for (int i = 0; i < m_joints.Count; ++i) { - verlet.SetRadius(settings.HitRadius); - verlet.Update(scene.Center, - stiffness, - settings.DragForce, - external, - m_colliders - ); + var (transform, init, state) = m_joints[i]; + + // Spring処理 + var parentRotation = (transform.parent != null ? transform.parent.rotation : Quaternion.identity); + var nextTail = init.VerletIntegration(deltaTime, scene.Center, parentRotation, settings, state); + + // 長さをboneLengthに強制 + nextTail = transform.position + (nextTail - transform.position).normalized * init.Length; + + // Collision + foreach (var collider in m_colliders) + { + if (collider.TryCollide(settings, transform, nextTail, out var posFromCollider)) + { + // 長さをboneLengthに強制 + nextTail = transform.position + (posFromCollider - transform.position).normalized * init.Length; + } + } + + // 状態更新 + m_joints[i] = (transform, init, SpringBoneJointState.Make(scene.Center, currentTail: state.CurrentTail, nextTail: nextTail)); + //回転を適用 + transform.rotation = init.WorldRotationFromTailPosition(transform, nextTail); } } - public void PlayingGizmo(Transform m_center, Color m_gizmoColor) + public void PlayingGizmo(Transform m_center, SpringBoneSettings settings, Color m_gizmoColor) { - foreach (var verlet in m_joints) + foreach (var (transform, init, state) in m_joints) { - verlet.DrawGizmo(m_center, m_gizmoColor); + init.DrawGizmo(m_center, transform, settings, m_gizmoColor, state); } } diff --git a/Assets/VRM/Runtime/SpringBone/VRMSpringBone.cs b/Assets/VRM/Runtime/SpringBone/VRMSpringBone.cs index 2e971d2cc..9932e3394 100644 --- a/Assets/VRM/Runtime/SpringBone/VRMSpringBone.cs +++ b/Assets/VRM/Runtime/SpringBone/VRMSpringBone.cs @@ -37,15 +37,17 @@ namespace VRM Setup(); } - SpringBone.SceneInfo Scene => new SpringBone.SceneInfo { RootBones = RootBones, Center = m_center }; - SpringBone.SpringBoneSettings Settings => new SpringBone.SpringBoneSettings - { - StiffnessForce = m_stiffnessForce, - GravityDir = m_gravityDir, - GravityPower = m_gravityPower, - HitRadius = m_hitRadius, - DragForce = m_dragForce, - }; + SpringBone.SceneInfo Scene => new( + rootBones: RootBones, + center: m_center, + colliderGroups: ColliderGroups); + SpringBone.SpringBoneSettings Settings => new + ( + stiffnessForce: m_stiffnessForce, + dragForce: m_dragForce, + gravityDir: m_gravityDir, + gravityPower: m_gravityPower, + hitRadius: m_hitRadius); [ContextMenu("Reset bones")] public void Setup(bool force = false) @@ -85,7 +87,7 @@ namespace VRM { if (Application.isPlaying) { - m_system.PlayingGizmo(m_center, m_gizmoColor); + m_system.PlayingGizmo(m_center, Settings, m_gizmoColor); } else {