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..d3d75e220
--- /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;
+
+ //FIXME: Disposeされた後にUpdateColliderGroupsが呼ばれるのをとりあえず防ぐためのフラグ
+ private bool _isDisposed = false;
+
+ 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