diff --git a/Assets/VRM/Runtime/FastSpringBone.meta b/Assets/VRM/Runtime/FastSpringBone.meta
new file mode 100644
index 000000000..cf175ab02
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: cf09adfffebbff24eb2185e1d9b5b0a9
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/VRM/Runtime/FastSpringBone/Blittables.meta b/Assets/VRM/Runtime/FastSpringBone/Blittables.meta
new file mode 100644
index 000000000..f41a4746d
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Blittables.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b2c4f2f05fcb4fb49be96d3b99236983
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableCollider.cs b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableCollider.cs
new file mode 100644
index 000000000..351806a00
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableCollider.cs
@@ -0,0 +1,20 @@
+using UnityEngine;
+
+namespace VRM.FastSpringBones.Blittables
+{
+ ///
+ /// VRMSpringBoneのSphereColliderをBlittableにしたもの
+ /// 位置情報は親であるColliderGroupが持つ
+ ///
+ public readonly struct BlittableCollider
+ {
+ public Vector3 Offset { get; }
+ public float Radius { get; }
+
+ public BlittableCollider(Vector3 offset, float radius)
+ {
+ Offset = offset;
+ Radius = radius;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableCollider.cs.meta b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableCollider.cs.meta
new file mode 100644
index 000000000..be5b7aec3
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableCollider.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: cc94117758e24aca9cf44d3f930fdcb0
+timeCreated: 1550209189
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableColliderGroup.cs b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableColliderGroup.cs
new file mode 100644
index 000000000..a53c1ab79
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableColliderGroup.cs
@@ -0,0 +1,20 @@
+using Unity.Collections;
+using Unity.Collections.LowLevel.Unsafe;
+
+namespace VRM.FastSpringBones.Blittables
+{
+ ///
+ /// VRMSpringBoneのColliderGroupをBlittableにしたもの
+ ///
+ public readonly struct BlittableColliderGroup
+ {
+ public BlittableColliders Colliders { get; }
+ public unsafe BlittableTransform* Transform { get; }
+
+ public unsafe BlittableColliderGroup(NativeArray colliders, BlittableTransform* transform)
+ {
+ Colliders = new BlittableColliders((BlittableCollider*)colliders.GetUnsafePtr(), colliders.Length);
+ Transform = transform;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableColliderGroup.cs.meta b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableColliderGroup.cs.meta
new file mode 100644
index 000000000..64eb463ac
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableColliderGroup.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 9bceae7ad8eb4021912778484d1b2ebc
+timeCreated: 1550223602
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableColliderGroups.cs b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableColliderGroups.cs
new file mode 100644
index 000000000..e8eb037bc
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableColliderGroups.cs
@@ -0,0 +1,34 @@
+using UnityEngine;
+
+namespace VRM.FastSpringBones.Blittables
+{
+ ///
+ /// BlittableColliderGroupのポインタの配列
+ ///
+ public readonly unsafe struct BlittableColliderGroups
+ {
+ private readonly BlittableColliderGroup* _data;
+ public int Length { get; }
+
+ public BlittableColliderGroup this[int i] => _data[i];
+
+ public void DrawGizmos()
+ {
+ for (var i = 0; i < Length; i++)
+ {
+ var group = this[i];
+ var colliders = group.Colliders;
+ for (var j = 0; j < colliders.Count; ++j)
+ {
+ Gizmos.DrawWireSphere(group.Transform->WorldPosition, colliders[j].Radius);
+ }
+ }
+ }
+
+ public BlittableColliderGroups(BlittableColliderGroup* data, int length)
+ {
+ _data = data;
+ Length = length;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableColliderGroups.cs.meta b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableColliderGroups.cs.meta
new file mode 100644
index 000000000..4c768f0d9
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableColliderGroups.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 61f56040664345a8aba5309e7714f8f2
+timeCreated: 1550563011
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableColliders.cs b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableColliders.cs
new file mode 100644
index 000000000..adbc4d739
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableColliders.cs
@@ -0,0 +1,19 @@
+namespace VRM.FastSpringBones.Blittables
+{
+ ///
+ /// BlittableColliderのポインタの配列
+ ///
+ public unsafe struct BlittableColliders
+ {
+ private readonly BlittableCollider* _colliders;
+ public int Count { get; }
+
+ public BlittableCollider this[int i] => _colliders[i];
+
+ public BlittableColliders(BlittableCollider* colliders, int count)
+ {
+ _colliders = colliders;
+ Count = count;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableColliders.cs.meta b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableColliders.cs.meta
new file mode 100644
index 000000000..b93aac749
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableColliders.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 07087baebec34286ac230d58cd0163d2
+timeCreated: 1550562383
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittablePoint.cs b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittablePoint.cs
new file mode 100644
index 000000000..f508200c8
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittablePoint.cs
@@ -0,0 +1,149 @@
+using UnityEngine;
+
+namespace VRM.FastSpringBones.Blittables
+{
+ ///
+ /// VRMSpringBoneLogicをBlittableにしたもの
+ /// ベルレ積分など、コアな計算を行う
+ ///
+ public unsafe struct BlittablePoint
+ {
+ private readonly float _length;
+ private readonly Quaternion _localRotation;
+ private readonly Vector3 _boneAxis;
+ private readonly float _radius;
+ private Vector3 _prevPosition;
+
+ private readonly BlittableColliderGroups* _blittableColliderGroups;
+ private readonly BlittableTransform* _center;
+
+ public Vector3 CurrentPosition { get; private set; }
+
+ private readonly BlittableTransform* _transform;
+
+ public BlittablePoint(
+ Transform transform,
+ float radius,
+ BlittableTransform* center,
+ BlittableColliderGroups* blittableColliderGroups,
+ BlittableTransform* blittableTransform)
+ {
+ Vector3 localPosition;
+ if (transform.childCount == 0)
+ {
+ var delta = transform.position - transform.parent.position;
+ var childPosition = transform.position + delta.normalized * 0.07f;
+ localPosition = transform.worldToLocalMatrix.MultiplyPoint(childPosition);
+ }
+ else
+ {
+ var firstChild = transform.GetChild(0);
+ var scale = firstChild.lossyScale;
+ localPosition = firstChild.localPosition;
+ localPosition.x *= scale.x;
+ localPosition.y *= scale.y;
+ localPosition.z *= scale.z;
+ }
+
+ var worldChildPosition = (Vector3)transform.TransformPoint(localPosition);
+ _prevPosition = CurrentPosition = center != null ? center->LocalToWorld.inverse.MultiplyPoint3x4(worldChildPosition) : worldChildPosition;
+
+ _localRotation = transform.localRotation;
+ _boneAxis = localPosition.normalized;
+ _length = localPosition.magnitude;
+ _radius = radius;
+ _blittableColliderGroups = blittableColliderGroups;
+ _transform = blittableTransform;
+ _center = center;
+ }
+
+ public void Update(float stiffnessForce, float dragForce, Vector3 external)
+ {
+ // 親のRotationが変わっている可能性があるので更新する
+ _transform->UpdateLocalToWorldMatrix();
+
+ Vector3 currentPosition;
+ Vector3 prevPosition;
+
+ if (_center == null)
+ {
+ currentPosition = CurrentPosition;
+ prevPosition = _prevPosition;
+ }
+ else
+ {
+ var centerLocalToWorld = _center->LocalToWorld;
+ currentPosition = centerLocalToWorld.MultiplyPoint3x4(CurrentPosition);
+ prevPosition = centerLocalToWorld.MultiplyPoint3x4(_prevPosition);
+ }
+
+ // verlet積分で次の位置を計算
+ var nextPosition = currentPosition
+ + (currentPosition - prevPosition) * (1.0f - dragForce) // 前フレームの移動を継続する(減衰もあるよ)
+ + _transform->ParentWorldRotation * _localRotation * _boneAxis * stiffnessForce // 親の回転による子ボーンの移動目標
+ + external; // 外力による移動量
+
+ // 長さをboneLengthに強制
+ var position = _transform->WorldPosition;
+ nextPosition = position + (nextPosition - position).normalized * _length;
+
+ nextPosition = Collision(nextPosition, position);
+
+ if (_center == null)
+ {
+ _prevPosition = currentPosition;
+ CurrentPosition = nextPosition;
+ }
+ else
+ {
+ var centerWorldToLocal = _center->LocalToWorld.inverse;
+ _prevPosition = centerWorldToLocal.MultiplyPoint3x4(currentPosition);
+ CurrentPosition = centerWorldToLocal.MultiplyPoint3x4(nextPosition);
+ }
+
+ //回転を適用
+ _transform->SetWorldRotation(ApplyRotation(nextPosition));
+ }
+
+ private Vector3 Collision(Vector3 nextPosition, Vector3 position)
+ {
+ for (var i = 0; i < _blittableColliderGroups->Length; ++i)
+ {
+ var colliderGroup = (*_blittableColliderGroups)[i];
+
+ for (var j = 0; j < colliderGroup.Colliders.Count; ++j)
+ {
+ var collider = colliderGroup.Colliders[j];
+ var colliderPosition = colliderGroup.Transform->TransformPoint(collider.Offset);
+ var r = _radius + collider.Radius;
+
+ if (!((nextPosition - colliderPosition).sqrMagnitude <= (r * r))) continue;
+
+ // ヒット。Colliderの半径方向に押し出す
+ var normal = (nextPosition - colliderPosition).normalized;
+ var posFromCollider = colliderPosition + normal * (_radius + collider.Radius);
+
+ // 長さをboneLengthに強制
+ nextPosition = position + (posFromCollider - position).normalized * _length;
+ }
+ }
+ return nextPosition;
+ }
+
+ private static Quaternion FromToRotation(Vector3 from, Vector3 to)
+ => Quaternion.AxisAngle(
+ angle: Mathf.Acos(Mathf.Clamp(Vector3.Dot(from.normalized, to.normalized), -1f, 1f)),
+ axis: Vector3.Cross(from, to).normalized
+ );
+
+ private Quaternion ApplyRotation(Vector3 nextTail)
+ {
+ var rotation = _transform->ParentWorldRotation * _localRotation;
+ return
+ FromToRotation(
+ rotation * _boneAxis,
+ nextTail - _transform->WorldPosition) *
+ rotation;
+ }
+ }
+}
diff --git a/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittablePoint.cs.meta b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittablePoint.cs.meta
new file mode 100644
index 000000000..2e93186cc
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittablePoint.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: f3f0fc54e34944c892cec2b1e86de7d7
+timeCreated: 1549430605
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittablePoints.cs b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittablePoints.cs
new file mode 100644
index 000000000..8a91baf39
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittablePoints.cs
@@ -0,0 +1,23 @@
+namespace VRM.FastSpringBones.Blittables
+{
+ ///
+ /// BlittablePointのポインタの配列
+ ///
+ public unsafe struct BlittablePoints
+ {
+ private readonly BlittablePoint* _points;
+ public int Count { get; }
+
+ public BlittablePoint this[int i]
+ {
+ get => _points[i];
+ set => _points[i] = value;
+ }
+
+ public BlittablePoints(BlittablePoint* points, int count)
+ {
+ _points = points;
+ Count = count;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittablePoints.cs.meta b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittablePoints.cs.meta
new file mode 100644
index 000000000..a2653992c
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittablePoints.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 0c39cdb389d141bbb084030dd8ebfe8b
+timeCreated: 1550721598
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableRootBone.cs b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableRootBone.cs
new file mode 100644
index 000000000..81753210d
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableRootBone.cs
@@ -0,0 +1,64 @@
+using UnityEngine;
+
+namespace VRM.FastSpringBones.Blittables
+{
+ ///
+ /// 1本の毛束を示すBlittable型
+ ///
+ public unsafe readonly struct BlittableRootBone
+ {
+ private readonly float _gravityPower;
+ private readonly Vector3 _gravityDir;
+ private readonly float _dragForce;
+ private readonly float _stiffnessForce;
+ private readonly BlittablePoints* _blittablePoints;
+
+ public void DrawGizmos()
+ {
+ for (var i = 0; i < _blittablePoints->Count; i++)
+ {
+ var point = (*_blittablePoints)[i];
+ Gizmos.DrawWireSphere(point.CurrentPosition, 0.05f);
+ }
+ for (var i = 0; i < _blittablePoints->Count - 1; i++)
+ {
+ var point1 = (*_blittablePoints)[i];
+ var point2 = (*_blittablePoints)[i + 1];
+ Gizmos.DrawLine(point1.CurrentPosition, point2.CurrentPosition);
+ }
+ }
+
+ public BlittableRootBone(
+ float gravityPower,
+ Vector3 gravityDir,
+ float dragForce,
+ float stiffnessForce,
+ BlittablePoints* blittablePoints)
+ {
+ _gravityPower = gravityPower;
+ _gravityDir = gravityDir;
+ _dragForce = dragForce;
+ _stiffnessForce = stiffnessForce;
+ _blittablePoints = blittablePoints;
+ }
+
+ public void Update(float deltaTime)
+ {
+ var stiffness = _stiffnessForce * deltaTime;
+ var external = _gravityDir * (_gravityPower * deltaTime);
+ for (var i = 0; i < _blittablePoints->Count; i++)
+ {
+ var point = (*_blittablePoints)[i];
+
+ // Pointを更新
+ point.Update(
+ stiffness,
+ _dragForce,
+ external
+ );
+
+ (*_blittablePoints)[i] = point;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableRootBone.cs.meta b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableRootBone.cs.meta
new file mode 100644
index 000000000..dbba64284
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableRootBone.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: bc470487e0a546f087709dbab3c7a6f1
+timeCreated: 1549952425
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableTransform.cs b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableTransform.cs
new file mode 100644
index 000000000..b2e4f9947
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableTransform.cs
@@ -0,0 +1,72 @@
+using UnityEngine;
+using UnityEngine.Jobs;
+
+namespace VRM.FastSpringBones.Blittables
+{
+ ///
+ /// Transformの必要な機能だけを絞り、Blittableに対応させたクラス
+ ///
+ public unsafe struct BlittableTransform
+ {
+ private readonly BlittableTransform* _parent;
+ private Quaternion _worldRotation;
+ private Vector3 _localPosition;
+ private Vector3 _localScale;
+ private Quaternion _localRotation;
+ private Matrix4x4 _localToWorld;
+
+ public Vector3 WorldPosition { get; private set; }
+
+ public void SetWorldRotation(Quaternion rotation)
+ {
+ var parentWorldRotation = ParentWorldRotation;
+ _localRotation = Quaternion.Inverse(parentWorldRotation) * rotation;
+ UpdateLocalToWorldMatrix();
+ }
+
+ public Matrix4x4 LocalToWorld => _localToWorld;
+
+ private Matrix4x4 LocalTransform => Matrix4x4.TRS(_localPosition, _localRotation, _localScale);
+
+ public Quaternion ParentWorldRotation => _parent != null ? _parent->_worldRotation : Quaternion.identity;
+
+ public BlittableTransform(BlittableTransform* parent, Transform transform)
+ {
+ _parent = parent;
+
+ WorldPosition = transform.position;
+ _worldRotation = transform.rotation;
+ _localPosition = transform.localPosition;
+ _localRotation = transform.localRotation;
+ _localScale = transform.localScale;
+
+ _localToWorld = transform.localToWorldMatrix;
+ }
+
+ public void PullFrom(TransformAccess transform)
+ {
+ WorldPosition = transform.position;
+ _worldRotation = transform.rotation;
+ _localPosition = transform.localPosition;
+ _localRotation = transform.localRotation;
+ _localScale = transform.localScale;
+
+ _localToWorld = transform.localToWorldMatrix;
+ }
+
+ public void PushTo(TransformAccess transform)
+ {
+ transform.localPosition = _localPosition;
+ transform.localRotation = _localRotation;
+ }
+
+ public Vector3 TransformPoint(Vector3 offset) => _localToWorld.MultiplyPoint3x4(offset);
+
+ public void UpdateLocalToWorldMatrix()
+ {
+ _localToWorld = _parent == null ? LocalTransform : _parent->_localToWorld * LocalTransform;
+ WorldPosition = _localToWorld.MultiplyPoint3x4(Vector3.zero);
+ _worldRotation = _localToWorld.rotation;
+ }
+ }
+}
diff --git a/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableTransform.cs.meta b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableTransform.cs.meta
new file mode 100644
index 000000000..e5f912161
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Blittables/BlittableTransform.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: bffbbf422e984d028cd82c11f2ca00b1
+timeCreated: 1550544819
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Components.meta b/Assets/VRM/Runtime/FastSpringBone/Components.meta
new file mode 100644
index 000000000..8560bc54d
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Components.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 89d63c6c8787b9a4c80df0fb18aa3d43
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/VRM/Runtime/FastSpringBone/Components/FastSpringBoneColliderGroup.cs b/Assets/VRM/Runtime/FastSpringBone/Components/FastSpringBoneColliderGroup.cs
new file mode 100644
index 000000000..b5fe7be3b
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Components/FastSpringBoneColliderGroup.cs
@@ -0,0 +1,31 @@
+using UnityEngine;
+using VRM.FastSpringBones.Blittables;
+using VRM.FastSpringBones.NativeWrappers;
+using VRM.FastSpringBones.Registries;
+
+namespace VRM.FastSpringBones.Components
+{
+ ///
+ /// VRMSpringBoneColliderGroupに対応したクラス
+ /// バッファの作成も行う
+ ///
+ public sealed unsafe class FastSpringBoneColliderGroup : MonoBehaviour
+ {
+ private NativeTransform _nativeTransform;
+ private NativeColliderGroup _nativeColliderGroup;
+
+ public BlittableColliderGroup* ColliderGroupPtr => _nativeColliderGroup.GetUnsafePtr();
+
+ public void Initialize(TransformRegistry transformRegistry, BlittableCollider[] colliders)
+ {
+ _nativeTransform = new NativeTransform(transformRegistry, TransformSynchronizationType.PullOnly, transform);
+ _nativeColliderGroup = new NativeColliderGroup(colliders, _nativeTransform);
+ }
+
+ private void OnDestroy()
+ {
+ _nativeTransform?.Dispose();
+ _nativeColliderGroup?.Dispose();
+ }
+ }
+}
diff --git a/Assets/VRM/Runtime/FastSpringBone/Components/FastSpringBoneColliderGroup.cs.meta b/Assets/VRM/Runtime/FastSpringBone/Components/FastSpringBoneColliderGroup.cs.meta
new file mode 100644
index 000000000..ef47a8fc6
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Components/FastSpringBoneColliderGroup.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 2c6ab135b4b945ee9d75c5476ea6bcc0
+timeCreated: 1550213674
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Components/FastSpringBoneScheduler.cs b/Assets/VRM/Runtime/FastSpringBone/Components/FastSpringBoneScheduler.cs
new file mode 100644
index 000000000..fc125b062
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Components/FastSpringBoneScheduler.cs
@@ -0,0 +1,100 @@
+using Unity.Jobs;
+using UnityEngine;
+using UnityEngine.Profiling;
+using VRM.FastSpringBones.Registries;
+using VRM.FastSpringBones.Schedulers;
+
+namespace VRM.FastSpringBones.Components
+{
+ ///
+ /// Jobを連続して発火させるComponent
+ /// シーンに1つだけあればいい
+ ///
+ [DefaultExecutionOrder(11000)]
+ public sealed class FastSpringBoneScheduler : MonoBehaviour
+ {
+ [SerializeField] private bool showGizmos;
+
+ private CustomSampler _updateSampler;
+
+ private PullTransformJobScheduler _pullTransformJobScheduler;
+ private PushTransformJobScheduler _pushTransformJobScheduler;
+ private UpdateSpringBoneJobScheduler _updateSpringBoneJobScheduler;
+
+ private RootBoneRegistry _rootBoneRegistry;
+ private ColliderGroupRegistry _colliderGroupRegistry;
+
+ private JobHandle _prevJobHandle;
+
+ public bool ShowGizmos { get => showGizmos; set => showGizmos = value; }
+
+ public void Initialize(
+ RootBoneRegistry rootBoneRegistry,
+ TransformRegistry transformRegistry,
+ ColliderGroupRegistry colliderGroupRegistry)
+ {
+ _rootBoneRegistry = rootBoneRegistry;
+ _colliderGroupRegistry = colliderGroupRegistry;
+
+ _updateSampler = CustomSampler.Create("FastSpringBone(Update)");
+
+ _pullTransformJobScheduler = new PullTransformJobScheduler(transformRegistry);
+ _pushTransformJobScheduler = new PushTransformJobScheduler(transformRegistry);
+ _updateSpringBoneJobScheduler = new UpdateSpringBoneJobScheduler(_rootBoneRegistry);
+
+ _rootBoneRegistry.SubscribeOnValueChanged(OnRootBoneChanged);
+ }
+
+ private void OnDestroy()
+ {
+ _rootBoneRegistry.UnSubscribeOnValueChanged(OnRootBoneChanged);
+ _prevJobHandle.Complete();
+
+ _pullTransformJobScheduler.Dispose();
+ _pushTransformJobScheduler.Dispose();
+ _updateSpringBoneJobScheduler.Dispose();
+ }
+
+ private void OnRootBoneChanged()
+ {
+ _prevJobHandle.Complete();
+ }
+
+#if UNITY_EDITOR
+ private void OnDrawGizmos()
+ {
+ if (!ShowGizmos) return;
+
+ _prevJobHandle.Complete();
+
+ Gizmos.color = Color.blue;
+ foreach (var rootBoneWrapper in _rootBoneRegistry.Items)
+ {
+ rootBoneWrapper.Value.DrawGizmos();
+ }
+
+ Gizmos.color = Color.yellow;
+ foreach (var colliderGroup in _colliderGroupRegistry.Items)
+ {
+ colliderGroup.DrawGizmos();
+ }
+ }
+#endif
+
+ private void LateUpdate()
+ {
+ _updateSampler.Begin();
+
+ _prevJobHandle.Complete();
+
+ var tempJobHandle = default(JobHandle);
+ tempJobHandle = _pullTransformJobScheduler.Schedule(tempJobHandle);
+ tempJobHandle = _updateSpringBoneJobScheduler.Schedule(tempJobHandle);
+ tempJobHandle = _pushTransformJobScheduler.Schedule(tempJobHandle);
+
+ _prevJobHandle = tempJobHandle;
+
+ _updateSampler.End();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Components/FastSpringBoneScheduler.cs.meta b/Assets/VRM/Runtime/FastSpringBone/Components/FastSpringBoneScheduler.cs.meta
new file mode 100644
index 000000000..bea119324
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Components/FastSpringBoneScheduler.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 2ea019701873487a8533c05f0daa1b37
+timeCreated: 1549521562
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Components/FastSpringRootBone.cs b/Assets/VRM/Runtime/FastSpringBone/Components/FastSpringRootBone.cs
new file mode 100644
index 000000000..a6ec42ece
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Components/FastSpringRootBone.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using VRM.FastSpringBones.Blittables;
+using VRM.FastSpringBones.NativeWrappers;
+using VRM.FastSpringBones.Registries;
+
+namespace VRM.FastSpringBones.Components
+{
+ ///
+ /// SpringBoneの1本の毛束の処理を担当するクラス
+ ///
+ public sealed class FastSpringRootBone : IDisposable
+ {
+ private readonly TransformRegistry _transformRegistry;
+ private readonly RootBoneRegistry _rootBoneRegistry;
+ private readonly ColliderGroupRegistry _colliderGroupRegistry;
+ private readonly Transform _transform;
+
+ private float _radius;
+ private NativeTransform _center;
+
+ private IReadOnlyDictionary _transformIndexMap;
+ private NativeColliderGroups _nativeColliderGroups;
+
+ private NativePoints _nativePoints;
+ private NativePointer _rootBoneWrapper;
+
+ private readonly IList _transformWrappers = new List();
+ private readonly IList> _points = new List>();
+
+ public FastSpringRootBone(
+ TransformRegistry transformRegistry,
+ Transform transform,
+ RootBoneRegistry rootBoneRegistry,
+ ColliderGroupRegistry colliderGroupRegistry
+ )
+ {
+ _transformRegistry = transformRegistry;
+ _transform = transform;
+ _rootBoneRegistry = rootBoneRegistry;
+ _colliderGroupRegistry = colliderGroupRegistry;
+ }
+
+ public IReadOnlyList ColliderGroups
+ {
+ get => _nativeColliderGroups.ColliderGroups;
+ set => _nativeColliderGroups.ColliderGroups = value;
+ }
+
+ public unsafe void Initialize(
+ float gravityPower,
+ Vector3 gravityDir,
+ float dragForce,
+ float stiffnessForce,
+ IReadOnlyList colliderGroups,
+ float radius,
+ Transform center)
+ {
+ _radius = radius;
+ if (center != null)
+ {
+ _center = new NativeTransform(_transformRegistry, TransformSynchronizationType.PullOnly, center);
+ }
+
+ _nativeColliderGroups = new NativeColliderGroups(colliderGroups);
+
+ NativeTransform parent = null;
+ if (_transform.parent)
+ {
+ parent = new NativeTransform(_transformRegistry, TransformSynchronizationType.PullOnly, _transform.parent);
+ }
+ SetupRecursive(_transform, parent);
+
+ _nativePoints = new NativePoints(_points);
+
+ _rootBoneWrapper = new NativePointer(new BlittableRootBone(gravityPower, gravityDir, dragForce, stiffnessForce, _nativePoints.GetUnsafePtr()));
+ _rootBoneRegistry.Register(_rootBoneWrapper);
+ _colliderGroupRegistry.Register(_nativeColliderGroups);
+ }
+
+ public void Dispose()
+ {
+ _colliderGroupRegistry.Unregister(_nativeColliderGroups);
+ _rootBoneRegistry.Unregister(_rootBoneWrapper);
+
+ foreach (var transformWrapper in _transformWrappers)
+ {
+ transformWrapper.Dispose();
+ }
+
+ foreach (var point in _points)
+ {
+ point.Dispose();
+ }
+
+ _center?.Dispose();
+ _nativeColliderGroups?.Dispose();
+ _nativePoints.Dispose();
+ _rootBoneWrapper.Dispose();
+ }
+
+ private unsafe void SetupRecursive(Transform trs, NativeTransform parent = null)
+ {
+ var transformWrapper = new NativeTransform(_transformRegistry, TransformSynchronizationType.PushOnly, trs, parent);
+ _transformWrappers.Add(transformWrapper);
+
+ var point = new NativePointer(
+ new BlittablePoint(
+ trs,
+ _radius,
+ _center != null ? _center.GetUnsafePtr() : null,
+ _nativeColliderGroups.GetUnsafePtr(),
+ transformWrapper.GetUnsafePtr())
+ );
+ _points.Add(point);
+
+ for (var i = 0; i < trs.childCount; ++i)
+ {
+ SetupRecursive(trs.GetChild(i), transformWrapper);
+ }
+ }
+ }
+}
diff --git a/Assets/VRM/Runtime/FastSpringBone/Components/FastSpringRootBone.cs.meta b/Assets/VRM/Runtime/FastSpringBone/Components/FastSpringRootBone.cs.meta
new file mode 100644
index 000000000..6f9dcd2fe
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Components/FastSpringRootBone.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 24cdd60af2d54c908e84b17802bfb5bc
+timeCreated: 1550036996
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/FastSpringBone.asmdef b/Assets/VRM/Runtime/FastSpringBone/FastSpringBone.asmdef
new file mode 100644
index 000000000..43d6fca9b
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/FastSpringBone.asmdef
@@ -0,0 +1,21 @@
+{
+ "name": "FastSpringBone",
+ "references": [
+ "GUID:2665a8d13d1b3f18800f46e256720795"
+ ],
+ "includePlatforms": [],
+ "excludePlatforms": [],
+ "allowUnsafeCode": true,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": false,
+ "defineConstraints": [],
+ "versionDefines": [
+ {
+ "name": "com.unity.burst",
+ "expression": "0.0.1",
+ "define": "ENABLE_SPRINGBONE_BURST"
+ }
+ ],
+ "noEngineReferences": false
+}
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/FastSpringBone.asmdef.meta b/Assets/VRM/Runtime/FastSpringBone/FastSpringBone.asmdef.meta
new file mode 100644
index 000000000..c086fa0df
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/FastSpringBone.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: ac229b552c3025545b074203f857547c
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/VRM/Runtime/FastSpringBone/NativeWrappers.meta b/Assets/VRM/Runtime/FastSpringBone/NativeWrappers.meta
new file mode 100644
index 000000000..c09ce17be
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/NativeWrappers.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 96634b0bb2d12d044a21f3fdff7a2c43
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativeColliderGroup.cs b/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativeColliderGroup.cs
new file mode 100644
index 000000000..821d7acc7
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativeColliderGroup.cs
@@ -0,0 +1,31 @@
+using System;
+using Unity.Collections;
+using VRM.FastSpringBones.Blittables;
+
+namespace VRM.FastSpringBones.NativeWrappers
+{
+ ///
+ /// BlittableColliderGroupのライフサイクルを管理するWrapper
+ ///
+ public sealed unsafe class NativeColliderGroup : IDisposable
+ {
+ private readonly NativePointer _nativePointer;
+
+ private NativeArray Colliders { get; }
+
+ public BlittableColliderGroup* GetUnsafePtr() => _nativePointer.GetUnsafePtr();
+
+ public NativeColliderGroup(BlittableCollider[] colliders, NativeTransform nativeTransform)
+ {
+ Colliders = new NativeArray(colliders, Allocator.Persistent);
+ _nativePointer = new NativePointer(new BlittableColliderGroup(Colliders, nativeTransform.GetUnsafePtr()));
+ }
+
+ public void Dispose()
+ {
+ if (Colliders.IsCreated) Colliders.Dispose();
+
+ _nativePointer.Dispose();
+ }
+ }
+}
diff --git a/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativeColliderGroup.cs.meta b/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativeColliderGroup.cs.meta
new file mode 100644
index 000000000..64eae28a2
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativeColliderGroup.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 4cf2baba01384c31a1fdcca7489007f4
+timeCreated: 1550640666
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativeColliderGroups.cs b/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativeColliderGroups.cs
new file mode 100644
index 000000000..751dfd571
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativeColliderGroups.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using Unity.Collections;
+using Unity.Collections.LowLevel.Unsafe;
+using VRM.FastSpringBones.Blittables;
+using VRM.FastSpringBones.Components;
+
+namespace VRM.FastSpringBones.NativeWrappers
+{
+ ///
+ /// BlittableColliderGroupsのライフサイクルを管理するWrapper
+ ///
+ public sealed unsafe class NativeColliderGroups : IDisposable
+ {
+ private readonly NativePointer _nativePointer = new NativePointer();
+ private NativeArray _colliderGroupArray;
+ private IReadOnlyList _colliderGroups;
+
+ //Disposeされた後にUpdateColliderGroupsが呼ばれるのを防ぐためのフラグ
+ private bool _isDisposed;
+
+ public BlittableColliderGroups* GetUnsafePtr() => _nativePointer.GetUnsafePtr();
+ public void DrawGizmos() => _nativePointer.Value.DrawGizmos();
+
+ public IReadOnlyList ColliderGroups
+ {
+ get => _colliderGroups;
+ set
+ {
+ _colliderGroups = value;
+ UpdateColliderGroups();
+ }
+ }
+
+ private void UpdateColliderGroups()
+ {
+ if (_isDisposed) return;
+ if (_colliderGroupArray.IsCreated)
+ {
+ _colliderGroupArray.Dispose();
+ }
+ CreateColliderGroupArray(_colliderGroups);
+ UpdateData();
+ }
+
+ public NativeColliderGroups(IReadOnlyList colliderGroups)
+ {
+ _colliderGroups = colliderGroups;
+ UpdateColliderGroups();
+ }
+
+ public void Dispose()
+ {
+ if (_colliderGroupArray.IsCreated)
+ {
+ _colliderGroupArray.Dispose();
+ _isDisposed = true;
+ }
+ _nativePointer.Dispose();
+ }
+
+ private void CreateColliderGroupArray(IReadOnlyList colliderGroups)
+ {
+ _colliderGroupArray = new NativeArray(colliderGroups.Count, Allocator.Persistent);
+ for (var i = 0; i < _colliderGroupArray.Length; ++i)
+ {
+ _colliderGroupArray[i] = *colliderGroups[i].ColliderGroupPtr;
+ }
+ }
+
+ private void UpdateData()
+ {
+ _nativePointer.Value = new BlittableColliderGroups(
+ (BlittableColliderGroup*)_colliderGroupArray.GetUnsafePtr(),
+ _colliderGroupArray.Length);
+ }
+ }
+}
diff --git a/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativeColliderGroups.cs.meta b/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativeColliderGroups.cs.meta
new file mode 100644
index 000000000..e70ff2e4e
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativeColliderGroups.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 3944dc9ee4a94814bc1d333fd0b7ba82
+timeCreated: 1550645184
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativePointer.cs b/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativePointer.cs
new file mode 100644
index 000000000..cc42524d7
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativePointer.cs
@@ -0,0 +1,35 @@
+using System;
+using Unity.Collections;
+using Unity.Collections.LowLevel.Unsafe;
+
+namespace VRM.FastSpringBones.NativeWrappers
+{
+ public sealed class NativePointer : IDisposable where T : unmanaged
+ {
+ private readonly unsafe T* _unsafePtr;
+
+ public unsafe T* GetUnsafePtr() => _unsafePtr;
+
+ public unsafe T Value
+ {
+ get => *_unsafePtr;
+ set => *_unsafePtr = value;
+ }
+
+ public unsafe NativePointer()
+ {
+ _unsafePtr = (T*)UnsafeUtility.Malloc(sizeof(T), 16, Allocator.Persistent);
+ }
+
+ public unsafe NativePointer(T value)
+ {
+ _unsafePtr = (T*)UnsafeUtility.Malloc(sizeof(T), 16, Allocator.Persistent);
+ Value = value;
+ }
+
+ public unsafe void Dispose()
+ {
+ UnsafeUtility.Free(_unsafePtr, Allocator.Persistent);
+ }
+ }
+}
diff --git a/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativePointer.cs.meta b/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativePointer.cs.meta
new file mode 100644
index 000000000..2d48eac46
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativePointer.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 70f8b67ae2644eb999cfb45ea5bf25a3
+timeCreated: 1595316733
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativePoints.cs b/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativePoints.cs
new file mode 100644
index 000000000..f62b0baaf
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativePoints.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using Unity.Collections;
+using Unity.Collections.LowLevel.Unsafe;
+using VRM.FastSpringBones.Blittables;
+
+namespace VRM.FastSpringBones.NativeWrappers
+{
+ ///
+ /// BlittablePointsGroupのライフサイクルを管理するWrapper
+ ///
+ public sealed unsafe class NativePoints : IDisposable
+ {
+ private readonly NativePointer _nativePointer;
+ private NativeArray _buffer;
+
+ public BlittablePoints* GetUnsafePtr() => _nativePointer.GetUnsafePtr();
+
+ public NativePoints(IList> points)
+ {
+ _buffer = new NativeArray(points.Count, Allocator.Persistent);
+ for (var i = 0; i < _buffer.Length; ++i)
+ {
+ _buffer[i] = points[i].Value;
+ }
+
+ _nativePointer = new NativePointer(new BlittablePoints((BlittablePoint*) _buffer.GetUnsafePtr(), _buffer.Length));
+ }
+
+ public void Dispose()
+ {
+ if (_buffer.IsCreated)
+ {
+ _buffer.Dispose();
+ }
+ _nativePointer.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativePoints.cs.meta b/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativePoints.cs.meta
new file mode 100644
index 000000000..0139520d7
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativePoints.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 7a8763ad7e8243d3b4001a2beb37bf9d
+timeCreated: 1550723674
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativeTransform.cs b/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativeTransform.cs
new file mode 100644
index 000000000..89c592c11
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativeTransform.cs
@@ -0,0 +1,43 @@
+using System;
+using UnityEngine;
+using VRM.FastSpringBones.Blittables;
+using VRM.FastSpringBones.Registries;
+
+namespace VRM.FastSpringBones.NativeWrappers
+{
+ ///
+ /// BlittableTransformのライフサイクルを管理するWrapper
+ ///
+ public sealed class NativeTransform : IDisposable
+ {
+ private readonly NativePointer _nativePointer;
+ public Transform Transform { get; }
+
+ private readonly TransformRegistry _transformRegistry;
+
+ public unsafe BlittableTransform* GetUnsafePtr() => _nativePointer.GetUnsafePtr();
+ public BlittableTransform Value => _nativePointer.Value;
+
+ public unsafe NativeTransform(
+ TransformRegistry transformRegistry,
+ TransformSynchronizationType transformSynchronizationType,
+ Transform transform,
+ NativeTransform parent = null
+ )
+ {
+ _nativePointer = new NativePointer(new BlittableTransform(parent != null ? parent.GetUnsafePtr() : null, transform));
+
+ Transform = transform;
+
+ _transformRegistry = transformRegistry;
+ _transformRegistry.Register(this, transformSynchronizationType);
+ }
+
+ public void Dispose()
+ {
+ _transformRegistry.Unregister(this);
+
+ _nativePointer.Dispose();
+ }
+ }
+}
diff --git a/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativeTransform.cs.meta b/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativeTransform.cs.meta
new file mode 100644
index 000000000..b62050944
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/NativeWrappers/NativeTransform.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 2acc8862ffb44f4fbd4e1e03e9a9d46e
+timeCreated: 1550545201
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Registries.meta b/Assets/VRM/Runtime/FastSpringBone/Registries.meta
new file mode 100644
index 000000000..b148ee1f1
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Registries.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: daa8d50bee84b0c4387d22784cce66ff
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/VRM/Runtime/FastSpringBone/Registries/ColliderGroupRegistry.cs b/Assets/VRM/Runtime/FastSpringBone/Registries/ColliderGroupRegistry.cs
new file mode 100644
index 000000000..2af47cc47
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Registries/ColliderGroupRegistry.cs
@@ -0,0 +1,8 @@
+using VRM.FastSpringBones.NativeWrappers;
+
+namespace VRM.FastSpringBones.Registries
+{
+ public sealed class ColliderGroupRegistry : Registry
+ {
+ }
+}
diff --git a/Assets/VRM/Runtime/FastSpringBone/Registries/ColliderGroupRegistry.cs.meta b/Assets/VRM/Runtime/FastSpringBone/Registries/ColliderGroupRegistry.cs.meta
new file mode 100644
index 000000000..5c1188773
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Registries/ColliderGroupRegistry.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: eec0fe3cd3694a379885fa62a9994bd5
+timeCreated: 1594963749
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Registries/Registry.cs b/Assets/VRM/Runtime/FastSpringBone/Registries/Registry.cs
new file mode 100644
index 000000000..ce0209d49
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Registries/Registry.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+
+namespace VRM.FastSpringBones.Registries
+{
+ public class Registry
+ {
+ private readonly List _items = new List();
+ private Action _onValueChanged;
+
+ public IReadOnlyList Items => _items;
+
+ public void Register(T value)
+ {
+ _items.Add(value);
+ _onValueChanged?.Invoke();
+ }
+
+ public void Unregister(T value)
+ {
+ _items.Remove(value);
+ _onValueChanged?.Invoke();
+ }
+
+ public void SubscribeOnValueChanged(Action action) => _onValueChanged += action;
+ public void UnSubscribeOnValueChanged(Action action) => _onValueChanged -= action;
+ }
+}
diff --git a/Assets/VRM/Runtime/FastSpringBone/Registries/Registry.cs.meta b/Assets/VRM/Runtime/FastSpringBone/Registries/Registry.cs.meta
new file mode 100644
index 000000000..5020d1e78
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Registries/Registry.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 0d3b558d03dd42b184512fe19797ff07
+timeCreated: 1595407071
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Registries/RootBoneRegistry.cs b/Assets/VRM/Runtime/FastSpringBone/Registries/RootBoneRegistry.cs
new file mode 100644
index 000000000..8da01d254
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Registries/RootBoneRegistry.cs
@@ -0,0 +1,12 @@
+using VRM.FastSpringBones.Blittables;
+using VRM.FastSpringBones.NativeWrappers;
+
+namespace VRM.FastSpringBones.Registries
+{
+ ///
+ /// 今生きているRootBoneの一覧を返すクラス
+ ///
+ public sealed class RootBoneRegistry : Registry>
+ {
+ }
+}
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Registries/RootBoneRegistry.cs.meta b/Assets/VRM/Runtime/FastSpringBone/Registries/RootBoneRegistry.cs.meta
new file mode 100644
index 000000000..0d07dcafc
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Registries/RootBoneRegistry.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 4609c8e2a4c64fb7b3bfa587f475e965
+timeCreated: 1550734399
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Registries/TransformRegistry.cs b/Assets/VRM/Runtime/FastSpringBone/Registries/TransformRegistry.cs
new file mode 100644
index 000000000..05c956f2b
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Registries/TransformRegistry.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using VRM.FastSpringBones.NativeWrappers;
+
+namespace VRM.FastSpringBones.Registries
+{
+ ///
+ /// 今生きているTransformの一覧を返すクラス
+ ///
+ public sealed class TransformRegistry
+ {
+ private readonly List _transforms = new List();
+ public IReadOnlyList Transforms => _transforms;
+
+ private readonly List _pullTargets = new List();
+ public IReadOnlyList PullTargets => _pullTargets;
+
+ private readonly List _pushTargets = new List();
+ public IReadOnlyList PushTargets => _pushTargets;
+
+ private Action _onValueChanged;
+
+ public void SubscribeOnValueChanged(Action action) => _onValueChanged += action;
+
+ public void UnSubscribeOnValueChanged(Action action) => _onValueChanged -= action;
+
+ public void Register(NativeTransform nativeTransform, TransformSynchronizationType synchronizationType)
+ {
+ _transforms.Add(nativeTransform);
+ switch (synchronizationType)
+ {
+ case TransformSynchronizationType.PullOnly:
+ _pullTargets.Add(nativeTransform);
+ break;
+ case TransformSynchronizationType.PushOnly:
+ _pushTargets.Add(nativeTransform);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(synchronizationType), synchronizationType, null);
+ }
+ _onValueChanged?.Invoke();
+ }
+
+ public void Unregister(NativeTransform nativeTransform)
+ {
+ _transforms.Remove(nativeTransform);
+
+ if (_pullTargets.Contains(nativeTransform))
+ {
+ _pullTargets.Remove(nativeTransform);
+ }
+
+ if (_pushTargets.Contains(nativeTransform))
+ {
+ _pushTargets.Remove(nativeTransform);
+ }
+ _onValueChanged?.Invoke();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Registries/TransformRegistry.cs.meta b/Assets/VRM/Runtime/FastSpringBone/Registries/TransformRegistry.cs.meta
new file mode 100644
index 000000000..d0c6267e8
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Registries/TransformRegistry.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 102dd7059fb34c00a7d1d6d4f2c38729
+timeCreated: 1550666913
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Registries/TransformSynchronizationType.cs b/Assets/VRM/Runtime/FastSpringBone/Registries/TransformSynchronizationType.cs
new file mode 100644
index 000000000..bd5a34b87
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Registries/TransformSynchronizationType.cs
@@ -0,0 +1,8 @@
+namespace VRM.FastSpringBones.Registries
+{
+ public enum TransformSynchronizationType
+ {
+ PullOnly,
+ PushOnly,
+ }
+}
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Registries/TransformSynchronizationType.cs.meta b/Assets/VRM/Runtime/FastSpringBone/Registries/TransformSynchronizationType.cs.meta
new file mode 100644
index 000000000..c68e0bc18
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Registries/TransformSynchronizationType.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 6d3911eb7ee54861b51bccf4ed32aec2
+timeCreated: 1551169872
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Schedulers.meta b/Assets/VRM/Runtime/FastSpringBone/Schedulers.meta
new file mode 100644
index 000000000..8165aee2d
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Schedulers.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 802ad91bcfdacbd4bbce16eff061cd68
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/VRM/Runtime/FastSpringBone/Schedulers/PullTransformJobScheduler.cs b/Assets/VRM/Runtime/FastSpringBone/Schedulers/PullTransformJobScheduler.cs
new file mode 100644
index 000000000..600ac0d39
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Schedulers/PullTransformJobScheduler.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+#if ENABLE_SPRINGBONE_BURST
+using Unity.Burst;
+#endif
+using Unity.Collections;
+using Unity.Collections.LowLevel.Unsafe;
+using Unity.Jobs;
+using UnityEngine.Jobs;
+using UnityEngine.Profiling;
+using VRM.FastSpringBones.Blittables;
+using VRM.FastSpringBones.NativeWrappers;
+using VRM.FastSpringBones.Registries;
+
+namespace VRM.FastSpringBones.Schedulers
+{
+ ///
+ /// GameObjectの世界からBlittableな世界へTransformを複製する処理を行うクラス
+ ///
+ public sealed unsafe class PullTransformJobScheduler : IDisposable
+ {
+ private BlittableTransform** _transformPointers;
+ private TransformAccessArray _transformAccessArray;
+
+ private readonly CustomSampler _sampler = CustomSampler.Create("Schedule CopyFromTransformJob");
+ private readonly TransformRegistry _transformRegistry;
+
+ private bool _dirty = true;
+
+ private IReadOnlyList Targets => _transformRegistry.PullTargets;
+
+ public PullTransformJobScheduler(TransformRegistry transformRegistry)
+ {
+ _transformRegistry = transformRegistry;
+
+ _transformRegistry.SubscribeOnValueChanged(OnTransformChanged);
+ }
+
+ private void OnTransformChanged()
+ {
+ _dirty = true;
+ }
+
+ public JobHandle Schedule(JobHandle dependOn = default(JobHandle))
+ {
+ if (Targets.Count == 0)
+ {
+ return dependOn;
+ }
+
+ _sampler.Begin();
+
+ // リストが変更されていたらバッファを再構築
+ if (_dirty)
+ {
+ ReconstructBuffers();
+
+ _dirty = false;
+ }
+
+ // ジョブを発火
+ var job = new Job { TransformPointers = _transformPointers };
+ var jobHandle = job.Schedule(_transformAccessArray, dependOn);
+
+ _sampler.End();
+
+ return jobHandle;
+ }
+
+ private void ReconstructBuffers()
+ {
+ ReleaseBuffers();
+
+ var transforms = Targets;
+ _transformPointers = (BlittableTransform**)UnsafeUtility.Malloc(
+ sizeof(BlittableTransform*) * transforms.Count,
+ 16,
+ Allocator.Persistent
+ );
+
+ _transformAccessArray = new TransformAccessArray(transforms.Count);
+
+ for (var i = 0; i < transforms.Count; i++)
+ {
+ _transformPointers[i] = transforms[i].GetUnsafePtr();
+ _transformAccessArray.Add(transforms[i].Transform);
+ }
+ }
+
+ public void Dispose()
+ {
+ ReleaseBuffers();
+ _transformRegistry.UnSubscribeOnValueChanged(OnTransformChanged);
+ }
+
+ private void ReleaseBuffers()
+ {
+ if (_transformAccessArray.isCreated) _transformAccessArray.Dispose();
+ if (_transformPointers != null)
+ {
+ UnsafeUtility.Free(_transformPointers, Allocator.Persistent);
+ _transformPointers = null;
+ }
+ }
+
+#if ENABLE_SPRINGBONE_BURST
+ [BurstCompile]
+#endif
+ private struct Job : IJobParallelForTransform
+ {
+ [NativeDisableUnsafePtrRestriction] public BlittableTransform** TransformPointers;
+
+ public void Execute(int index, TransformAccess transform)
+ {
+ TransformPointers[index]->PullFrom(transform);
+ }
+ }
+ }
+}
diff --git a/Assets/VRM/Runtime/FastSpringBone/Schedulers/PullTransformJobScheduler.cs.meta b/Assets/VRM/Runtime/FastSpringBone/Schedulers/PullTransformJobScheduler.cs.meta
new file mode 100644
index 000000000..a8c9666a6
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Schedulers/PullTransformJobScheduler.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 83ca6a73284247c0a809eb9b83b6296b
+timeCreated: 1550547066
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Schedulers/PushTransformJobScheduler.cs b/Assets/VRM/Runtime/FastSpringBone/Schedulers/PushTransformJobScheduler.cs
new file mode 100644
index 000000000..6d66f61f9
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Schedulers/PushTransformJobScheduler.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+#if ENABLE_SPRINGBONE_BURST
+using Unity.Burst;
+#endif
+using Unity.Collections;
+using Unity.Collections.LowLevel.Unsafe;
+using Unity.Jobs;
+using UnityEngine.Jobs;
+using UnityEngine.Profiling;
+using VRM.FastSpringBones.Blittables;
+using VRM.FastSpringBones.NativeWrappers;
+using VRM.FastSpringBones.Registries;
+
+namespace VRM.FastSpringBones.Schedulers
+{
+ ///
+ /// Blittableな世界からGameObjectの世界へTransformを送り込む処理を行うクラス
+ ///
+ public sealed unsafe class PushTransformJobScheduler : IDisposable
+ {
+ private BlittableTransform** _transformPointers;
+ private TransformAccessArray _transformAccessArray;
+
+ private readonly CustomSampler _sampler = CustomSampler.Create("Schedule CopyFromTransformJob");
+ private readonly TransformRegistry _transformRegistry;
+
+ private bool _dirty = true;
+
+ private IReadOnlyList Targets => _transformRegistry.PushTargets;
+
+ public PushTransformJobScheduler(TransformRegistry transformRegistry)
+ {
+ _transformRegistry = transformRegistry;
+
+ _transformRegistry.SubscribeOnValueChanged(OnTransformChanged);
+ }
+
+ private void OnTransformChanged()
+ {
+ _dirty = true;
+ }
+
+ public JobHandle Schedule(JobHandle dependOn = default(JobHandle))
+ {
+ if (Targets.Count == 0)
+ {
+ return dependOn;
+ }
+
+ _sampler.Begin();
+
+ // リストが変更されていたらバッファを再構築
+ if (_dirty)
+ {
+ ReconstructBuffers();
+
+ _dirty = false;
+ }
+
+ // Jobを発火
+ var job = new Job { TransformPointers = _transformPointers };
+ var jobHandle = job.Schedule(_transformAccessArray, dependOn);
+
+ _sampler.End();
+
+ return jobHandle;
+ }
+
+ private void ReconstructBuffers()
+ {
+ ReleaseBuffers();
+
+ var transforms = Targets;
+ _transformPointers = (BlittableTransform**)UnsafeUtility.Malloc(
+ sizeof(BlittableTransform*) * transforms.Count,
+ 16,
+ Allocator.Persistent
+ );
+
+ _transformAccessArray = new TransformAccessArray(transforms.Count);
+
+ for (var i = 0; i < transforms.Count; i++)
+ {
+ _transformPointers[i] = transforms[i].GetUnsafePtr();
+ _transformAccessArray.Add(transforms[i].Transform);
+ }
+ }
+
+ public void Dispose()
+ {
+ ReleaseBuffers();
+ _transformRegistry.UnSubscribeOnValueChanged(OnTransformChanged);
+ }
+
+ private void ReleaseBuffers()
+ {
+ if (_transformAccessArray.isCreated) _transformAccessArray.Dispose();
+ if (_transformPointers != null)
+ {
+ UnsafeUtility.Free(_transformPointers, Allocator.Persistent);
+ _transformPointers = null;
+ }
+ }
+
+#if ENABLE_SPRINGBONE_BURST
+ [BurstCompile]
+#endif
+ private struct Job : IJobParallelForTransform
+ {
+ [NativeDisableUnsafePtrRestriction] public BlittableTransform** TransformPointers;
+
+ public void Execute(int index, TransformAccess transform)
+ {
+ TransformPointers[index]->PushTo(transform);
+ }
+ }
+ }
+}
diff --git a/Assets/VRM/Runtime/FastSpringBone/Schedulers/PushTransformJobScheduler.cs.meta b/Assets/VRM/Runtime/FastSpringBone/Schedulers/PushTransformJobScheduler.cs.meta
new file mode 100644
index 000000000..75f7c9618
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Schedulers/PushTransformJobScheduler.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 5a12fcea984d4387a5f3d0f5291df34e
+timeCreated: 1550717407
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/FastSpringBone/Schedulers/UpdateSpringBoneJobScheduler.cs b/Assets/VRM/Runtime/FastSpringBone/Schedulers/UpdateSpringBoneJobScheduler.cs
new file mode 100644
index 000000000..74297dc2d
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Schedulers/UpdateSpringBoneJobScheduler.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Collections.Generic;
+#if ENABLE_SPRINGBONE_BURST
+using Unity.Burst;
+#endif
+using Unity.Collections;
+using Unity.Collections.LowLevel.Unsafe;
+using Unity.Jobs;
+using UnityEngine;
+using UnityEngine.Profiling;
+using VRM.FastSpringBones.Blittables;
+using VRM.FastSpringBones.NativeWrappers;
+using VRM.FastSpringBones.Registries;
+
+namespace VRM.FastSpringBones.Schedulers
+{
+ ///
+ /// SpringBoneを更新する処理を行うクラス
+ ///
+ public sealed unsafe class UpdateSpringBoneJobScheduler : IDisposable
+ {
+ private BlittableRootBone** _rootBonePointers;
+
+ private readonly RootBoneRegistry _rootBoneRegistry;
+ private readonly CustomSampler _sampler = CustomSampler.Create("Schedule CopyFromTransformJob");
+
+ private bool _dirty = true;
+
+ private IReadOnlyList> Targets => _rootBoneRegistry.Items;
+
+ public UpdateSpringBoneJobScheduler(RootBoneRegistry rootBoneRegistry)
+ {
+ _rootBoneRegistry = rootBoneRegistry;
+
+ _rootBoneRegistry.SubscribeOnValueChanged(OnRootBoneChanged);
+ }
+
+ public void Dispose()
+ {
+ ReleaseBuffer();
+ _rootBoneRegistry.UnSubscribeOnValueChanged(OnRootBoneChanged);
+ }
+
+ private void OnRootBoneChanged()
+ {
+ _dirty = true;
+ }
+
+ public JobHandle Schedule(JobHandle dependOn = default(JobHandle))
+ {
+ if (Targets.Count == 0)
+ {
+ return dependOn;
+ }
+
+ _sampler.Begin();
+
+ // リストが変更されていたらバッファを再構築
+ if (_dirty)
+ {
+ ReconstructBuffers();
+
+ _dirty = false;
+ }
+
+ // Jobを発火
+ var job = new Job { RootBonePointers = _rootBonePointers, DeltaTime = Time.deltaTime };
+ var jobHandle = job.Schedule(Targets.Count, 0, dependOn);
+
+ _sampler.End();
+
+ return jobHandle;
+ }
+
+ private void ReconstructBuffers()
+ {
+ ReleaseBuffer();
+
+ _rootBonePointers = (BlittableRootBone**)UnsafeUtility.Malloc(
+ sizeof(BlittableTransform*) * Targets.Count,
+ 16,
+ Allocator.Persistent
+ );
+
+ for (var i = 0; i < Targets.Count; i++)
+ {
+ _rootBonePointers[i] = Targets[i].GetUnsafePtr();
+ }
+ }
+
+ private void ReleaseBuffer()
+ {
+ if (_rootBonePointers == null) return;
+ UnsafeUtility.Free(_rootBonePointers, Allocator.Persistent);
+ _rootBonePointers = null;
+ }
+
+#if ENABLE_SPRINGBONE_BURST
+ [BurstCompile]
+#endif
+ private struct Job : IJobParallelFor
+ {
+ [NativeDisableUnsafePtrRestriction] public BlittableRootBone** RootBonePointers;
+ public float DeltaTime;
+
+ public void Execute(int index)
+ {
+ // 各点を更新する
+ RootBonePointers[index]->Update(DeltaTime);
+ }
+ }
+ }
+}
diff --git a/Assets/VRM/Runtime/FastSpringBone/Schedulers/UpdateSpringBoneJobScheduler.cs.meta b/Assets/VRM/Runtime/FastSpringBone/Schedulers/UpdateSpringBoneJobScheduler.cs.meta
new file mode 100644
index 000000000..2d47c1232
--- /dev/null
+++ b/Assets/VRM/Runtime/FastSpringBone/Schedulers/UpdateSpringBoneJobScheduler.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: a6b730c674ce4484862ddee8459c8598
+timeCreated: 1549516763
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/SpringBone/FastSpringBoneDisposer.cs b/Assets/VRM/Runtime/SpringBone/FastSpringBoneDisposer.cs
new file mode 100644
index 000000000..ce0a464d1
--- /dev/null
+++ b/Assets/VRM/Runtime/SpringBone/FastSpringBoneDisposer.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace VRM
+{
+ ///
+ /// FastSpringBoneに関連して、特定のGameObjectと紐付いたIDisposableの破棄を担当するクラス
+ ///
+ public sealed class FastSpringBoneDisposer : MonoBehaviour
+ {
+ private readonly List _disposables = new List();
+
+ public void Add(IDisposable disposable)
+ {
+ _disposables.Add(disposable);
+ }
+
+ private void OnDestroy()
+ {
+ foreach (var disposable in _disposables)
+ {
+ disposable.Dispose();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/SpringBone/FastSpringBoneDisposer.cs.meta b/Assets/VRM/Runtime/SpringBone/FastSpringBoneDisposer.cs.meta
new file mode 100644
index 000000000..7f55e4b79
--- /dev/null
+++ b/Assets/VRM/Runtime/SpringBone/FastSpringBoneDisposer.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 0b96d696de06439e97ac33ed2d890c88
+timeCreated: 1632893490
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/SpringBone/FastSpringBoneReplacer.cs b/Assets/VRM/Runtime/SpringBone/FastSpringBoneReplacer.cs
new file mode 100644
index 000000000..356ab345c
--- /dev/null
+++ b/Assets/VRM/Runtime/SpringBone/FastSpringBoneReplacer.cs
@@ -0,0 +1,130 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using UnityEngine;
+using VRM.FastSpringBones.Blittables;
+using VRM.FastSpringBones.Components;
+using VRMShaders;
+using Object = UnityEngine.Object;
+
+namespace VRM
+{
+ ///
+ /// 指定されたGameObject内にあるSpringBoneをFastSpringBoneに差し替えるユーティリティ
+ ///
+ public static class FastSpringBoneReplacer
+ {
+ public static async Task ReplaceAsync(GameObject gameObject, IAwaitCaller awaitCaller = null, CancellationToken token = default)
+ {
+ var service = FastSpringBoneService.Instance;
+ var springBones = gameObject.GetComponentsInChildren();
+ var disposer = gameObject.AddComponent();
+
+ // VRMSpringBoneで動いた後の状態がFastSpringBoneの初期状態にならないようにするためawait UniTask.Yield()する前にVRMSpringBoneをdisableにしておく
+ foreach (var springBone in springBones)
+ {
+ springBone.enabled = false;
+ };
+
+ if (awaitCaller != null)
+ {
+ await awaitCaller.NextFrame();
+ token.ThrowIfCancellationRequested();
+ }
+
+ var vrmColliderGroups = gameObject.GetComponentsInChildren();
+ var colliderGroupDictionary = new Dictionary();
+
+ // Colliderを差し替える
+ foreach (var vrmColliderGroup in vrmColliderGroups)
+ {
+ if (awaitCaller != null)
+ {
+ await awaitCaller.NextFrame();
+ token.ThrowIfCancellationRequested();
+ }
+
+ var fastSpringBoneCollider = vrmColliderGroup.gameObject.AddComponent();
+ fastSpringBoneCollider.Initialize(
+ service.TransformRegistry,
+ vrmColliderGroup.Colliders
+ .Select(data => new BlittableCollider(data.Offset, data.Radius))
+ .ToArray()
+ );
+ colliderGroupDictionary[vrmColliderGroup] = fastSpringBoneCollider;
+ }
+
+ var springRootBones =
+ (
+ from springBone in springBones
+ from rootBone in springBone.RootBones
+ select (springBone, rootBone)
+ ).ToList();
+
+ for (var i = 0; i < springRootBones.Count; i++)
+ {
+ var current = springRootBones[i];
+
+ // 他のRootBoneのどれかが、自分の親(もしくは同じTransform)なら自分自身を削除する
+ if (springRootBones
+ .Where(other => other != current)
+ .Any(other => current.rootBone.IsChildOf(other.rootBone)))
+ {
+ springRootBones.RemoveAt(i);
+ --i;
+ }
+ }
+
+ if (awaitCaller != null)
+ {
+ await awaitCaller.NextFrame();
+ token.ThrowIfCancellationRequested();
+ }
+ token.ThrowIfCancellationRequested();
+
+ foreach (var (vrmSpringBone, rootBoneTransform) in springRootBones)
+ {
+ // FastSpringRootBoneに差し替える
+ var fastSpringRootBone =
+ new FastSpringRootBone(
+ service.TransformRegistry,
+ rootBoneTransform,
+ service.RootBoneRegistry,
+ service.ColliderGroupRegistry);
+ disposer.Add(fastSpringRootBone);
+
+ var colliderGroups =
+ vrmSpringBone.ColliderGroups != null
+ ? vrmSpringBone.ColliderGroups.Select(group => colliderGroupDictionary[@group]).ToArray()
+ : Array.Empty();
+
+ fastSpringRootBone.Initialize(
+ vrmSpringBone.m_gravityPower,
+ vrmSpringBone.m_gravityDir,
+ vrmSpringBone.m_dragForce,
+ vrmSpringBone.m_stiffnessForce,
+ colliderGroups,
+ vrmSpringBone.m_hitRadius,
+ vrmSpringBone.m_center
+ );
+
+ Object.Destroy(vrmSpringBone);
+
+ if (awaitCaller != null)
+ {
+ await awaitCaller.NextFrame();
+ token.ThrowIfCancellationRequested();
+ }
+ token.ThrowIfCancellationRequested();
+ }
+
+ // Colliderを削除
+ foreach (var vrmSpringBoneColliderGroup in vrmColliderGroups)
+ {
+ Object.Destroy(vrmSpringBoneColliderGroup);
+ }
+ }
+ }
+}
diff --git a/Assets/VRM/Runtime/SpringBone/FastSpringBoneReplacer.cs.meta b/Assets/VRM/Runtime/SpringBone/FastSpringBoneReplacer.cs.meta
new file mode 100644
index 000000000..76ca1edb5
--- /dev/null
+++ b/Assets/VRM/Runtime/SpringBone/FastSpringBoneReplacer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a344a05a3634bac419be8a218d8b85d6
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/VRM/Runtime/SpringBone/FastSpringBoneService.cs b/Assets/VRM/Runtime/SpringBone/FastSpringBoneService.cs
new file mode 100644
index 000000000..86fab4112
--- /dev/null
+++ b/Assets/VRM/Runtime/SpringBone/FastSpringBoneService.cs
@@ -0,0 +1,54 @@
+using UnityEngine;
+using VRM.FastSpringBones.Components;
+using VRM.FastSpringBones.Registries;
+
+namespace VRM
+{
+ ///
+ /// Scene 上に単一で存在する、FastSpringBone の ServiceLocator 兼 EntryPoint
+ ///
+ public class FastSpringBoneService : MonoBehaviour
+ {
+ public RootBoneRegistry RootBoneRegistry { get; private set; }
+ public TransformRegistry TransformRegistry { get; private set; }
+ public ColliderGroupRegistry ColliderGroupRegistry { get; private set; }
+ public FastSpringBoneScheduler FastSpringBoneScheduler { get; private set; }
+
+ private static FastSpringBoneService _instance;
+
+ public static FastSpringBoneService Instance
+ {
+ get
+ {
+ if (!_instance)
+ {
+ var gameObject = new GameObject("FastSpringBone Service");
+ DontDestroyOnLoad(gameObject);
+ _instance = gameObject.AddComponent();
+ }
+ return _instance;
+ }
+ }
+
+ ///
+ /// 専有しているインスタンスを破棄する
+ ///
+ public static void Free()
+ {
+ Destroy(_instance.gameObject);
+ _instance = null;
+ }
+
+ private void Awake()
+ {
+ RootBoneRegistry = new RootBoneRegistry();
+ TransformRegistry = new TransformRegistry();
+ ColliderGroupRegistry = new ColliderGroupRegistry();
+ FastSpringBoneScheduler = gameObject.AddComponent();
+ FastSpringBoneScheduler.Initialize(
+ RootBoneRegistry,
+ TransformRegistry,
+ ColliderGroupRegistry);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/SpringBone/FastSpringBoneService.cs.meta b/Assets/VRM/Runtime/SpringBone/FastSpringBoneService.cs.meta
new file mode 100644
index 000000000..8db972822
--- /dev/null
+++ b/Assets/VRM/Runtime/SpringBone/FastSpringBoneService.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 7c3edad580b54158b37169025a1e207d
+timeCreated: 1632894654
\ No newline at end of file
diff --git a/Assets/VRM/Runtime/VRM.asmdef b/Assets/VRM/Runtime/VRM.asmdef
index 053d4f4d5..c89f8bc23 100644
--- a/Assets/VRM/Runtime/VRM.asmdef
+++ b/Assets/VRM/Runtime/VRM.asmdef
@@ -5,7 +5,8 @@
"GUID:8d76e605759c3f64a957d63ef96ada7c",
"GUID:da3e51d19d51a544fa14d43fee843098",
"GUID:301b251fd9834274c9228e0532f444f7",
- "GUID:a9bc101fb0471f94a8f99fd242fdd934"
+ "GUID:a9bc101fb0471f94a8f99fd242fdd934",
+ "GUID:ac229b552c3025545b074203f857547c"
],
"includePlatforms": [],
"excludePlatforms": [],
diff --git a/Assets/VRM/Samples/SimpleViewer/SimpleViewer.unity b/Assets/VRM/Samples/SimpleViewer/SimpleViewer.unity
index 9b0860765..272c3b778 100644
--- a/Assets/VRM/Samples/SimpleViewer/SimpleViewer.unity
+++ b/Assets/VRM/Samples/SimpleViewer/SimpleViewer.unity
@@ -867,8 +867,11 @@ RectTransform:
- {fileID: 597950322}
- {fileID: 935566651}
- {fileID: 634488421}
- - {fileID: 1923377807}
- {fileID: 630871733}
+ - {fileID: 1365661828}
+ - {fileID: 1329594714}
+ - {fileID: 1391337186}
+ - {fileID: 1923377807}
m_Father: {fileID: 124675794}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
@@ -1426,7 +1429,7 @@ RectTransform:
- {fileID: 1736108988}
- {fileID: 13043734}
m_Father: {fileID: 339774397}
- m_RootOrder: 11
+ m_RootOrder: 10
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
@@ -1682,6 +1685,84 @@ MonoBehaviour:
m_ChildControlHeight: 0
m_ChildScaleWidth: 0
m_ChildScaleHeight: 0
+--- !u!1 &668840864
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 668840865}
+ - component: {fileID: 668840867}
+ - component: {fileID: 668840866}
+ m_Layer: 5
+ m_Name: Label
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &668840865
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 668840864}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children: []
+ m_Father: {fileID: 1391337186}
+ m_RootOrder: 1
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 1, y: 1}
+ m_AnchoredPosition: {x: 9, y: -0.5}
+ m_SizeDelta: {x: -28, y: -3}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &668840866
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 668840864}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
+ m_RaycastTarget: 1
+ m_Maskable: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_FontData:
+ m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
+ m_FontSize: 14
+ m_FontStyle: 0
+ m_BestFit: 0
+ m_MinSize: 10
+ m_MaxSize: 40
+ m_Alignment: 0
+ m_AlignByGeometry: 0
+ m_RichText: 1
+ m_HorizontalOverflow: 0
+ m_VerticalOverflow: 0
+ m_LineSpacing: 1
+ m_Text: Use FastSpringBone
+--- !u!222 &668840867
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 668840864}
+ m_CullTransparentMesh: 0
--- !u!1 &773923918
GameObject:
m_ObjectHideFlags: 0
@@ -2325,9 +2406,7 @@ MonoBehaviour:
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
- m_Text: 'ResetSpringBone
-
-'
+ m_Text: ResetSpringBone
--- !u!222 &919548010
CanvasRenderer:
m_ObjectHideFlags: 0
@@ -3360,6 +3439,80 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1140410863}
m_CullTransparentMesh: 0
+--- !u!1 &1160967486
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1160967487}
+ - component: {fileID: 1160967489}
+ - component: {fileID: 1160967488}
+ m_Layer: 5
+ m_Name: Checkmark
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &1160967487
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1160967486}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children: []
+ m_Father: {fileID: 1808134149}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0.5, y: 0.5}
+ m_AnchorMax: {x: 0.5, y: 0.5}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: 20, y: 20}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &1160967488
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1160967486}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 1, g: 1, b: 1, a: 1}
+ m_RaycastTarget: 1
+ m_Maskable: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_Sprite: {fileID: 10901, guid: 0000000000000000f000000000000000, type: 0}
+ m_Type: 0
+ m_PreserveAspect: 0
+ m_FillCenter: 1
+ m_FillMethod: 4
+ m_FillAmount: 1
+ m_FillClockwise: 1
+ m_FillOrigin: 0
+ m_UseSpriteMesh: 0
+ m_PixelsPerUnitMultiplier: 1
+--- !u!222 &1160967489
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1160967486}
+ m_CullTransparentMesh: 0
--- !u!1 &1166391798
GameObject:
m_ObjectHideFlags: 0
@@ -4142,6 +4295,247 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1322834809}
m_CullTransparentMesh: 0
+--- !u!1 &1329594713
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1329594714}
+ - component: {fileID: 1329594716}
+ - component: {fileID: 1329594715}
+ m_Layer: 5
+ m_Name: _SpringBone_
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &1329594714
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1329594713}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children: []
+ m_Father: {fileID: 339774397}
+ m_RootOrder: 12
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 0, y: 0}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: 178, y: 30}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &1329594715
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1329594713}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
+ m_RaycastTarget: 1
+ m_Maskable: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_FontData:
+ m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
+ m_FontSize: 14
+ m_FontStyle: 1
+ m_BestFit: 0
+ m_MinSize: 10
+ m_MaxSize: 40
+ m_Alignment: 4
+ m_AlignByGeometry: 0
+ m_RichText: 1
+ m_HorizontalOverflow: 0
+ m_VerticalOverflow: 0
+ m_LineSpacing: 1
+ m_Text: "\u30B9\u30D7\u30EA\u30F3\u30B0\u30DC\u30FC\u30F3"
+--- !u!222 &1329594716
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1329594713}
+ m_CullTransparentMesh: 0
+--- !u!1 &1365661827
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1365661828}
+ - component: {fileID: 1365661830}
+ - component: {fileID: 1365661829}
+ m_Layer: 5
+ m_Name: '_ '
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &1365661828
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1365661827}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children: []
+ m_Father: {fileID: 339774397}
+ m_RootOrder: 11
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 0, y: 0}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: 178, y: 30}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &1365661829
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1365661827}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
+ m_RaycastTarget: 1
+ m_Maskable: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_FontData:
+ m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
+ m_FontSize: 14
+ m_FontStyle: 1
+ m_BestFit: 0
+ m_MinSize: 10
+ m_MaxSize: 40
+ m_Alignment: 4
+ m_AlignByGeometry: 0
+ m_RichText: 1
+ m_HorizontalOverflow: 0
+ m_VerticalOverflow: 0
+ m_LineSpacing: 1
+ m_Text:
+--- !u!222 &1365661830
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1365661827}
+ m_CullTransparentMesh: 0
+--- !u!1 &1391337185
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1391337186}
+ - component: {fileID: 1391337187}
+ m_Layer: 5
+ m_Name: UseFastSpringBone
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &1391337186
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1391337185}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children:
+ - {fileID: 1808134149}
+ - {fileID: 668840865}
+ m_Father: {fileID: 339774397}
+ m_RootOrder: 13
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 0}
+ m_AnchorMax: {x: 0, y: 0}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: 162, y: 20}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &1391337187
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1391337185}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 9085046f02f69544eb97fd06b6048fe2, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Navigation:
+ m_Mode: 3
+ m_SelectOnUp: {fileID: 0}
+ m_SelectOnDown: {fileID: 0}
+ m_SelectOnLeft: {fileID: 0}
+ m_SelectOnRight: {fileID: 0}
+ m_Transition: 1
+ m_Colors:
+ m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
+ m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
+ m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
+ m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
+ m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
+ m_ColorMultiplier: 1
+ m_FadeDuration: 0.1
+ m_SpriteState:
+ m_HighlightedSprite: {fileID: 0}
+ m_PressedSprite: {fileID: 0}
+ m_SelectedSprite: {fileID: 0}
+ m_DisabledSprite: {fileID: 0}
+ m_AnimationTriggers:
+ m_NormalTrigger: Normal
+ m_HighlightedTrigger: Highlighted
+ m_PressedTrigger: Pressed
+ m_SelectedTrigger: Highlighted
+ m_DisabledTrigger: Disabled
+ m_Interactable: 1
+ m_TargetGraphic: {fileID: 1808134150}
+ toggleTransition: 1
+ graphic: {fileID: 1160967488}
+ m_Group: {fileID: 0}
+ onValueChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_IsOn: 1
--- !u!1 &1476033060
GameObject:
m_ObjectHideFlags: 0
@@ -4981,6 +5375,7 @@ MonoBehaviour:
m_enableLipSync: {fileID: 935566650}
m_enableAutoBlink: {fileID: 634488422}
m_useUrpMaterial: {fileID: 630871734}
+ m_useFastSpringBone: {fileID: 1391337187}
m_src: {fileID: 0}
m_target: {fileID: 802105000}
Root: {fileID: 124675793}
@@ -5090,6 +5485,81 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_IsOn: 0
+--- !u!1 &1808134148
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1808134149}
+ - component: {fileID: 1808134151}
+ - component: {fileID: 1808134150}
+ m_Layer: 5
+ m_Name: Background
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!224 &1808134149
+RectTransform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1808134148}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children:
+ - {fileID: 1160967487}
+ m_Father: {fileID: 1391337186}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+ m_AnchorMin: {x: 0, y: 1}
+ m_AnchorMax: {x: 0, y: 1}
+ m_AnchoredPosition: {x: 10, y: -10}
+ m_SizeDelta: {x: 20, y: 20}
+ m_Pivot: {x: 0.5, y: 0.5}
+--- !u!114 &1808134150
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1808134148}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ m_Material: {fileID: 0}
+ m_Color: {r: 1, g: 1, b: 1, a: 1}
+ m_RaycastTarget: 1
+ m_Maskable: 1
+ m_OnCullStateChanged:
+ m_PersistentCalls:
+ m_Calls: []
+ m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
+ m_Type: 1
+ m_PreserveAspect: 0
+ m_FillCenter: 1
+ m_FillMethod: 4
+ m_FillAmount: 1
+ m_FillClockwise: 1
+ m_FillOrigin: 0
+ m_UseSpriteMesh: 0
+ m_PixelsPerUnitMultiplier: 1
+--- !u!222 &1808134151
+CanvasRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1808134148}
+ m_CullTransparentMesh: 0
--- !u!1 &1866921957
GameObject:
m_ObjectHideFlags: 0
@@ -5354,7 +5824,7 @@ RectTransform:
m_Children:
- {fileID: 919548008}
m_Father: {fileID: 339774397}
- m_RootOrder: 10
+ m_RootOrder: 14
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
diff --git a/Assets/VRM/Samples/SimpleViewer/ViewerUI.cs b/Assets/VRM/Samples/SimpleViewer/ViewerUI.cs
index 3c9ade18c..11027f6ce 100644
--- a/Assets/VRM/Samples/SimpleViewer/ViewerUI.cs
+++ b/Assets/VRM/Samples/SimpleViewer/ViewerUI.cs
@@ -30,6 +30,9 @@ namespace VRM.SimpleViewer
[SerializeField]
Toggle m_useUrpMaterial = default;
+
+ [SerializeField]
+ Toggle m_useFastSpringBone = default;
#endregion
[SerializeField]
@@ -172,6 +175,7 @@ namespace VRM.SimpleViewer
m_reset = buttons.First(x => x.name == "ResetSpringBone");
var toggles = GameObject.FindObjectsOfType();
+ m_useFastSpringBone = toggles.First(x => x.name == "UseFastSpringBone");
m_enableLipSync = toggles.First(x => x.name == "EnableLipSync");
m_enableAutoBlink = toggles.First(x => x.name == "EnableAutoBlink");
@@ -300,6 +304,8 @@ namespace VRM.SimpleViewer
m_version.text = string.Format("VRMViewer {0}.{1}",
VRMVersion.MAJOR, VRMVersion.MINOR);
m_open.onClick.AddListener(OnOpenClicked);
+ m_useFastSpringBone.onValueChanged.AddListener(OnUseFastSpringBoneValueChanged);
+ OnUseFastSpringBoneValueChanged(m_useFastSpringBone.isOn);
m_reset.onClick.AddListener(() => m_loaded.OnResetClicked());
@@ -381,6 +387,11 @@ namespace VRM.SimpleViewer
}
}
+ void OnUseFastSpringBoneValueChanged(bool flag)
+ {
+ m_reset.gameObject.SetActive(!flag);
+ }
+
static IMaterialDescriptorGenerator GetGltfMaterialGenerator(bool useUrp)
{
if (useUrp)
@@ -467,6 +478,11 @@ namespace VRM.SimpleViewer
m_loaded = null;
}
+ if (m_useFastSpringBone.isOn)
+ {
+ FastSpringBoneReplacer.ReplaceAsync(instance.Root);
+ }
+
instance.EnableUpdateWhenOffscreen();
instance.ShowMeshes();
diff --git a/Assets/VRM/Samples/SpringBone.meta b/Assets/VRM/Samples/SpringBone.meta
new file mode 100644
index 000000000..77a337faa
--- /dev/null
+++ b/Assets/VRM/Samples/SpringBone.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 3fc3f1af567d73f41a5172249769f047
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Packages/manifest.json b/Packages/manifest.json
index 07f53197f..f3278fa9d 100644
--- a/Packages/manifest.json
+++ b/Packages/manifest.json
@@ -1,5 +1,6 @@
{
"dependencies": {
+ "com.unity.burst": "1.4.11",
"com.unity.ide.rider": "1.2.1",
"com.unity.ide.visualstudio": "2.0.11",
"com.unity.ide.vscode": "1.2.3",
diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json
index 3aa890b3f..2a839c2b5 100644
--- a/Packages/packages-lock.json
+++ b/Packages/packages-lock.json
@@ -1,5 +1,14 @@
{
"dependencies": {
+ "com.unity.burst": {
+ "version": "1.4.11",
+ "depth": 0,
+ "source": "registry",
+ "dependencies": {
+ "com.unity.mathematics": "1.2.1"
+ },
+ "url": "https://packages.unity.com"
+ },
"com.unity.ext.nunit": {
"version": "1.0.6",
"depth": 1,
@@ -32,6 +41,13 @@
"dependencies": {},
"url": "https://packages.unity.com"
},
+ "com.unity.mathematics": {
+ "version": "1.2.1",
+ "depth": 1,
+ "source": "registry",
+ "dependencies": {},
+ "url": "https://packages.unity.com"
+ },
"com.unity.postprocessing": {
"version": "3.1.1",
"depth": 0,