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": [],