using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using RotateParticle.Components; using SphereTriangle; using UniGLTF; using UniGLTF.SpringBoneJobs.Blittables; using UnityEngine; using UniVRM10; namespace RotateParticle { public class RotateParticleSpringboneRuntime : IVrm10SpringBoneRuntime { Vrm10Instance _vrm; Action _onInit; bool _initialized = false; bool _building = false; public SimulationEnv Env = new() { DragForce = 0.6f, Stiffness = 0.07f, }; public List _colliderGroups = new(); public float _clothFactor = 0.5f; // runtime public List _strands = new List(); public ParticleList _list = new(); ClothRectList _clothRects; public List _clothRectCollisions; public PositionList _newPos; Vector3[] _restPositions; static Color[] Colors = new Color[] { Color.yellow, Color.green, Color.magenta, }; Color GetGizmoColor(VRM10SpringBoneColliderGroup g) { for (int i = 0; i < _colliderGroups.Count; ++i) { if (_colliderGroups[i] == g) { return Colors[i]; } } return Color.gray; } public async Task InitializeAsync(Vrm10Instance vrm, IAwaitCaller awaitCaller) { _building = true; _vrm = vrm; if (_onInit != null) { _onInit(vrm); _onInit = null; } _initialized = false; var strandMap = new Dictionary(); var warps = vrm.GetComponentsInChildren(); foreach (var warp in warps) { var strands = new List(); var strand = _list.MakeParticleStrand(Env, warp); strands.Add(strand); _strands.AddRange(strands); strandMap.Add(warp, strand); foreach (var g in warp.ColliderGroups) { foreach (var c in g.Colliders) { AddColliderIfNotExists(g.name, c); } } } _clothRects = new ClothRectList(_list._particleTransforms, vrm); _newPos = new(_list._particles.Count); _list.EndInitialize(_newPos.Init); _restPositions = new Vector3[_list._particles.Count]; _newPos.EndInitialize(); _clothRectCollisions = new(); for (int i = 0; i < _clothRects.List.Count; ++i) { var (s, r) = _clothRects.List[i]; _clothRectCollisions.Add(new()); var c = _clothRectCollisions.Last(); c.InitializeColliderSide(_newPos, _colliderGroups, r); } await awaitCaller.NextFrame(); _initialized = true; _building = false; } /// /// すべての Particle を Init 状態にする。 /// Verlet の Prev を現在地に更新する(速度0)。 /// public void RestoreInitialTransform() { foreach (var strand in _strands) { strand.Reset(_list._particleTransforms); } // foreach (var p in _system._list._particleTransforms) // { // p.transform.localRotation = _vrm.DefaultTransformStates[p.transform].LocalRotation; // } } public void Process() { Process(Time.deltaTime); } void Process(float deltaTime) { if (!_initialized) { return; } using var profile = new ProfileSample("RotateParticle"); _newPos.BoundsCache.Clear(); using (new ProfileSample("UpdateRoot")) { // // input // // 各strandのrootの移動と回転を外部から入力する。 // それらを元に各 joint の方向を元に戻した場合の戻り位置を計算する foreach (var strand in _strands) { strand.UpdateRoot(_list._particleTransforms, _newPos, _restPositions); } } using (new ProfileSample("Verlet")) { // // particle simulation // // verlet 積分 var time = new FrameTime(deltaTime); _list.BeginFrame(Env, time, _restPositions); foreach (var (spring, collision) in _clothRects.List) { // cloth constraint spring.Resolve(time, _clothFactor, _list._particles); } _list.Verlet(Env, time, _newPos.Init); // 長さで拘束 foreach (var strand in _strands) { strand.ForceLength(_list._particleTransforms, _newPos); } } // collision using (new ProfileSample("Collision")) { for (int i = 0; i < _colliderGroups.Count; ++i) { var g = _colliderGroups[i]; for (int j = 0; j < _clothRects.List.Count; ++j) { var (spring, rect) = _clothRects.List[j]; var collision = _clothRectCollisions[j]; // using var prof = new ProfileSample("Collision: Cloth"); // 頂点 abcd は同じ CollisionMask // TODO: // if (_list._particles[rect._a].Init.CollisionMask.HasFlag((CollisionGroupMask)(i + 1))) { // cloth collision.Collide(_newPos, g.Colliders, rect); } } for (int j = 0; j < _list._particles.Count; ++j) { // using var prof = new ProfileSample("Collision: Strand"); if (_clothRects.ClothUsedParticles[j]) { // 布で処理された continue; } var particle = _list._particles[j]; if (particle.Init.Mass == 0) { continue; } // 紐の当たり判定 // TODO: // if (particle.Init.CollisionMask.HasFlag((CollisionGroupMask)(i + 1))) { var p = _newPos.Get(j); foreach (var c in g.Colliders) { // strand if (c != null && TryCollide(c, p, particle.Init.Radius, out var resolved)) { _newPos.CollisionMove(particle.Init.Index, resolved, c.Radius); } } } } } } using (new ProfileSample("Apply")) { for (int i = 0; i < _newPos.CollisionCount.Length; ++i) { if (_newPos.CollisionCount[i] > 0) { _list._particles[i].HasCollide = true; } } var result = _newPos.Resolve(); // // apply result // // apply positions and // calc rotation from positions recursive foreach (var strand in _strands) { strand.Apply(_list._particleTransforms, result); } } } public VRM10SpringBoneColliderGroup GetOrAddColliderGroup(string groupName, GameObject go) { foreach (var g in _colliderGroups) { if (g.Name == groupName) { return g; } } var group = go.GetOrAddComponent(); _colliderGroups.Add(group); return group; } void AddColliderIfNotExists(string groupName, VRM10SpringBoneCollider c) { var group = GetOrAddColliderGroup(groupName, c.gameObject); foreach (var collider in group.Colliders) { if (collider == c) { return; } } // c.GizmoColor = GetGizmoColor(group); group.Colliders.Add(c); } /// /// collide sphere a and sphere b. /// move sphere b to resolved if collide. /// /// /// /// /// /// /// static bool TryCollideSphereAndSphere( in Vector3 from, float ra, in Vector3 to, float rb, out LineSegment resolved ) { var d = Vector3.Distance(from, to); if (d > (ra + rb)) { resolved = default; return false; } Vector3 normal = (to - from).normalized; resolved = new(from, from + normal * (d - rb)); return true; } /// /// collide capsule and sphere b. /// move sphere b to resolved if collide. /// /// /// /// /// /// static bool TryCollideCapsuleAndSphere( in Vector3 capsuleHead, in Vector3 capsuleTail, float capsuleRadius, in Vector3 b, float rb, out LineSegment resolved ) { var P = (capsuleTail - capsuleHead).normalized; var Q = b - capsuleHead; var dot = Vector3.Dot(P, Q); if (dot <= 0) { // head側半球の球判定 return TryCollideSphereAndSphere(capsuleHead, capsuleRadius, b, rb, out resolved); } var t = dot / P.magnitude; if (t >= 1.0f) { // tail側半球の球判定 return TryCollideSphereAndSphere(capsuleTail, capsuleRadius, b, rb, out resolved); } // head-tail上の m_transform.position との最近点 var p = capsuleHead + P * t; return TryCollideSphereAndSphere(p, capsuleRadius, b, rb, out resolved); } /// /// collision for strand /// /// /// /// /// public bool TryCollide(UniVRM10.VRM10SpringBoneCollider c, in Vector3 p, float radius, out LineSegment resolved) { if (c.ColliderType == UniVRM10.VRM10SpringBoneColliderTypes.Capsule) { return TryCollideCapsuleAndSphere(c.HeadWorldPosition, c.TailWorldPosition, c.Radius, p, radius, out resolved); } else { return TryCollideSphereAndSphere(c.HeadWorldPosition, c.Radius, p, radius, out resolved); } } void IVrm10SpringBoneRuntime.DrawGizmos() { _list.DrawGizmos(); for (int i = 0; i < _clothRectCollisions.Count; ++i) { // var (spring, rect) = _clothRects[i]; var collision = _clothRectCollisions[i]; collision.DrawGizmos(); } if (_newPos != null) { _newPos.DrawGizmos(); } } public void SetJointLevel(Transform joint, BlittableJointMutable jointSettings) { throw new NotImplementedException(); } public void SetModelLevel(Transform modelRoot, BlittableModelLevel modelSettings) { } public RotateParticleSpringboneRuntime(Action onInit = null) { _onInit = onInit; } public void Dispose() { } public bool ReconstructSpringBone() { if (_vrm == null) { return false; } if (_building) { return false; } var task = InitializeAsync(_vrm, new ImmediateCaller()); task.Wait(); return true; } } }