diff --git a/Assets/VRM10/Runtime/FastSpringBone/System/FastSpringBoneBufferCombiner.cs b/Assets/VRM10/Runtime/FastSpringBone/System/FastSpringBoneBufferCombiner.cs
new file mode 100644
index 000000000..8af7f1988
--- /dev/null
+++ b/Assets/VRM10/Runtime/FastSpringBone/System/FastSpringBoneBufferCombiner.cs
@@ -0,0 +1,185 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Unity.Collections;
+using UnityEngine;
+using UnityEngine.Jobs;
+using UnityEngine.Profiling;
+using UniVRM10.FastSpringBones.Blittables;
+
+namespace UniVRM10.FastSpringBones.System
+{
+ ///
+ /// FastSpringBoneの処理に利用するバッファを全て結合して持つクラス
+ ///
+ public sealed class FastSpringBoneBufferCombiner : IDisposable
+ {
+ private NativeArray _springs;
+ private NativeArray _joints;
+ private NativeArray _transforms;
+ private TransformAccessArray _transformAccessArray;
+ private NativeArray _colliders;
+ private NativeArray _logics;
+
+ private readonly LinkedList _fastSpringBoneScopes = new LinkedList();
+
+ private bool _isDirty;
+
+ public NativeArray Springs => _springs;
+ public NativeArray Joints => _joints;
+ public NativeArray Transforms => _transforms;
+ public TransformAccessArray TransformAccessArray => _transformAccessArray;
+ public NativeArray Colliders => _colliders;
+ public NativeArray Logics => _logics;
+
+ public void Register(FastSpringBoneScope scope)
+ {
+ _fastSpringBoneScopes.AddLast(scope);
+ _isDirty = true;
+ }
+
+ public void Unregister(FastSpringBoneScope scope)
+ {
+ _fastSpringBoneScopes.Remove(scope);
+ _isDirty = true;
+ }
+
+ ///
+ /// 変更があったならばバッファを再構築する
+ ///
+ public void ReconstructIfDirty()
+ {
+ if (_isDirty)
+ {
+ ReconstructBuffers();
+ _isDirty = false;
+ }
+ }
+
+ ///
+ /// バッファを再構築する
+ /// TODO: 最適化
+ ///
+ private void ReconstructBuffers()
+ {
+ Profiler.BeginSample("FastSpringBone.ReconstructBuffers");
+ DisposeAllBuffers();
+
+ var springs = _fastSpringBoneScopes.SelectMany(scope => scope.Springs).ToArray();
+
+ // Transformの列挙
+ var transformHashSet = new HashSet();
+ foreach (var spring in springs)
+ {
+ foreach (var joint in spring.joints)
+ {
+ transformHashSet.Add(joint.Transform);
+ if (joint.Transform.parent != null) transformHashSet.Add(joint.Transform.parent);
+ }
+ foreach (var collider in spring.colliders)
+ {
+ transformHashSet.Add(collider.Transform);
+ }
+
+ if (spring.center != null) transformHashSet.Add(spring.center);
+ }
+ var transforms = transformHashSet.ToArray();
+ var transformIndexDictionary = transforms.Select((trs, index) => (trs, index))
+ .ToDictionary(tuple => tuple.trs, tuple => tuple.index);
+
+ // 各種bufferの構築
+ var blittableColliders = new List();
+ var blittableJoints = new List();
+ var blittableSprings = new List();
+ var blittableLogics = new List();
+
+ foreach (var spring in springs)
+ {
+ var blittableSpring = new BlittableSpring
+ {
+ colliderSpan = new BlittableSpan
+ {
+ startIndex = blittableColliders.Count,
+ count = spring.colliders.Length,
+ },
+ logicSpan = new BlittableSpan
+ {
+ startIndex = blittableJoints.Count,
+ count = spring.joints.Length - 1,
+ },
+ centerTransformIndex = spring.center ? transformIndexDictionary[spring.center] : -1
+ };
+ blittableSprings.Add(blittableSpring);
+
+ blittableColliders.AddRange(spring.colliders.Select(collider =>
+ {
+ var blittable = collider.Collider;
+ blittable.transformIndex = transformIndexDictionary[collider.Transform];
+ return blittable;
+ }));
+ blittableJoints.AddRange(spring.joints.Take(spring.joints.Length - 1).Select(joint =>
+ {
+ var blittable = joint.Joint;
+ return blittable;
+ }));
+
+ for (var i = 0; i < spring.joints.Length - 1; ++i)
+ {
+ var joint = spring.joints[i];
+ var tailJoint = spring.joints[i + 1];
+ var localPosition = tailJoint.Transform.localPosition;
+ var scale = tailJoint.Transform.lossyScale;
+ var localChildPosition =
+ new Vector3(
+ localPosition.x * scale.x,
+ localPosition.y * scale.y,
+ localPosition.z * scale.z
+ );
+
+ var worldChildPosition = joint.Transform.TransformPoint(localChildPosition);
+ var currentTail = spring.center != null
+ ? spring.center.InverseTransformPoint(worldChildPosition)
+ : worldChildPosition;
+ var parent = joint.Transform.parent;
+ blittableLogics.Add(new BlittableLogic
+ {
+ headTransformIndex = transformIndexDictionary[joint.Transform],
+ parentTransformIndex = parent != null ? transformIndexDictionary[parent] : -1,
+ currentTail = currentTail,
+ prevTail = currentTail,
+ localRotation = joint.Transform.localRotation,
+ boneAxis = localChildPosition.normalized,
+ length = localChildPosition.magnitude
+ });
+ }
+ }
+
+ // 各種bufferの初期化
+ _springs = new NativeArray(blittableSprings.ToArray(), Allocator.Persistent);
+
+ _joints = new NativeArray(blittableJoints.ToArray(), Allocator.Persistent);
+ _colliders = new NativeArray(blittableColliders.ToArray(), Allocator.Persistent);
+ _logics = new NativeArray(blittableLogics.ToArray(), Allocator.Persistent);
+
+ _transforms = new NativeArray(transforms.Length, Allocator.Persistent);
+ _transformAccessArray = new TransformAccessArray(transforms.ToArray());
+
+ Profiler.EndSample();
+ }
+
+ private void DisposeAllBuffers()
+ {
+ if (_springs.IsCreated) _springs.Dispose();
+ if (_joints.IsCreated) _joints.Dispose();
+ if (_transforms.IsCreated) _transforms.Dispose();
+ if (_transformAccessArray.isCreated) _transformAccessArray.Dispose();
+ if (_colliders.IsCreated) _colliders.Dispose();
+ if (_logics.IsCreated) _logics.Dispose();
+ }
+
+ public void Dispose()
+ {
+ DisposeAllBuffers();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/VRM10/Runtime/FastSpringBone/System/FastSpringBoneBufferCombiner.cs.meta b/Assets/VRM10/Runtime/FastSpringBone/System/FastSpringBoneBufferCombiner.cs.meta
new file mode 100644
index 000000000..ce9f4ec5a
--- /dev/null
+++ b/Assets/VRM10/Runtime/FastSpringBone/System/FastSpringBoneBufferCombiner.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 2b0299bf5bfe431e8e18a9b033b719a3
+timeCreated: 1633072502
\ No newline at end of file
diff --git a/Assets/VRM10/Runtime/FastSpringBone/System/UpdateFastSpringBoneJob.cs b/Assets/VRM10/Runtime/FastSpringBone/System/UpdateFastSpringBoneJob.cs
index b9ae16115..33b2246c2 100644
--- a/Assets/VRM10/Runtime/FastSpringBone/System/UpdateFastSpringBoneJob.cs
+++ b/Assets/VRM10/Runtime/FastSpringBone/System/UpdateFastSpringBoneJob.cs
@@ -14,13 +14,12 @@ namespace UniVRM10.FastSpringBones.System
public struct UpdateFastSpringBoneJob : IJobParallelFor
{
[ReadOnly] public NativeArray Springs;
-
[ReadOnly] public NativeArray Joints;
+ [ReadOnly] public NativeArray Colliders;
+
[NativeDisableParallelForRestriction] public NativeArray Logics;
[NativeDisableParallelForRestriction] public NativeArray Transforms;
- [ReadOnly] public NativeArray Colliders;
-
public float DeltaTime;
public void Execute(int index)