mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-08-28 13:54:45 -05:00
Merge pull request #2419 from ousttrue/fix/springbone_refactoring
[SpringBone][vrm0]コード整理その2
This commit is contained in:
@@ -3,10 +3,16 @@ using UnityEngine;
|
||||
|
||||
namespace VRM.SpringBone
|
||||
{
|
||||
struct SceneInfo
|
||||
readonly struct SceneInfo
|
||||
{
|
||||
public IReadOnlyList<Transform> RootBones;
|
||||
public Transform Center;
|
||||
public VRMSpringBoneColliderGroup[] ColliderGroups;
|
||||
public readonly IReadOnlyList<Transform> RootBones;
|
||||
public readonly Transform Center;
|
||||
public readonly VRMSpringBoneColliderGroup[] ColliderGroups;
|
||||
|
||||
public SceneInfo(
|
||||
IReadOnlyList<Transform> rootBones,
|
||||
Transform center,
|
||||
VRMSpringBoneColliderGroup[] colliderGroups) =>
|
||||
(RootBones, Center, ColliderGroups) = (rootBones, center, colliderGroups);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRM.SpringBone
|
||||
{
|
||||
/// <summary>
|
||||
/// original from
|
||||
/// http://rocketjump.skr.jp/unity3d/109/
|
||||
/// </summary>
|
||||
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<SphereCollider> 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<SpringBone.SphereCollider> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
64
Assets/VRM/Runtime/SpringBone/Logic/SpringBoneJointInit.cs
Normal file
64
Assets/VRM/Runtime/SpringBone/Logic/SpringBoneJointInit.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRM.SpringBone
|
||||
{
|
||||
/// <summary>
|
||||
/// original from
|
||||
/// http://rocketjump.skr.jp/unity3d/109/
|
||||
///
|
||||
/// この型のフィールドはSpringBoneのライフサイクルを通じて不変。
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// しっぽの位置から回転を計算する
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verlet積分で次の位置を計算する
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
49
Assets/VRM/Runtime/SpringBone/Logic/SpringBoneJointState.cs
Normal file
49
Assets/VRM/Runtime/SpringBone/Logic/SpringBoneJointState.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRM.SpringBone
|
||||
{
|
||||
/// <summary>
|
||||
/// 毎フレーム更新される Verlet 積分の位置状態
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3bbcafd66c035184b8aa868b65c01c1d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -4,25 +4,12 @@ using UnityEngine;
|
||||
|
||||
namespace VRM.SpringBone
|
||||
{
|
||||
/// <summary>
|
||||
/// 同じ設定のスプリングをまとめて処理する。
|
||||
///
|
||||
/// root o-o-o-x tail
|
||||
///
|
||||
/// [vrm0] tail は 7cm 遠にダミーの joint があるようにふるまう。
|
||||
///
|
||||
/// </summary>
|
||||
class SpringBoneSystem
|
||||
{
|
||||
Dictionary<Transform, Quaternion> m_initialLocalRotationMap;
|
||||
List<SpringBoneJoint> m_joints = new();
|
||||
List<(Transform, SpringBoneJointInit, SpringBoneJointState)> m_joints = new();
|
||||
List<SphereCollider> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user