using System; using System.Collections.Generic; using System.Threading.Tasks; using RotateParticle.Components; using UniGLTF; using Unity.Collections; using Unity.Jobs; using UnityEngine; using UnityEngine.Jobs; using UniVRM10; namespace RotateParticle.Jobs { public class RotateParticleJobSystem : IRotateParticleSystem { Vrm10Instance _vrm; // [Input] // center // rootParent // root // // の入力(jobにわたす) List _inputTransforms; TransformAccessArray _inputTransformAccessArray; public struct InputTransformData { public Matrix4x4 ToWorld; public Matrix4x4 TOLocal; public InputTransformData(TransformAccess t) { ToWorld = t.localToWorldMatrix; TOLocal = t.worldToLocalMatrix; } } NativeArray _inputData; public struct InputTransformJob : IJobParallelForTransform { [WriteOnly] public NativeArray InputData; public void Execute(int index, TransformAccess transform) { InputData[index] = new InputTransformData(transform); } } // [Output] // root 回転のみ // particle 回転 + 移動 // を出力(jobから反映する) List _outputTransforms; List _positions; NativeArray _nextPositions; TransformAccessArray _outputTransformAccessArray; public struct OutputTransformJob : IJobParallelForTransform { [ReadOnly] public NativeArray NextPositions; public void Execute(int index, TransformAccess transform) { transform.position = NextPositions[index]; } } public struct ParticleIndo { public int InputTransformIndex; public int? ParentIndex; public Vector3 InitLocalPosition; // DragForce: 1 で即時停止 public float DragForce; } NativeArray _particles; // [Init/State] // 初期化時に値を作って毎フレーム更新していく // output と同じ長さ index NativeArray _currentPositions; NativeArray _prevPositions; public struct VerletJob : IJobParallelFor { public FrameInfo Frame; [WriteOnly] public NativeArray NextPositions; [ReadOnly] public NativeArray CurrentPositions; [ReadOnly] public NativeArray PrevPositions; [ReadOnly] public NativeArray Particles; public void Execute(int index) { var particle = Particles[index]; if (particle.ParentIndex.HasValue) { var velocity = CurrentPositions[index] - PrevPositions[index]; velocity *= (1 - particle.DragForce); var newPosition = CurrentPositions[index] + velocity + Frame.Force * Frame.SqDeltaTime; var parentIndex = particle.ParentIndex.Value; var parentPosition = CurrentPositions[parentIndex]; // 位置を長さで拘束 newPosition = parentPosition + (newPosition - parentPosition).normalized * particle.InitLocalPosition.magnitude; NextPositions[index] = newPosition; } else { // kinematic NextPositions[index] = CurrentPositions[index]; } } } void IDisposable.Dispose() { } async Task IRotateParticleSystem.InitializeAsync(Vrm10Instance vrm, IAwaitCaller awaitCaller) { _vrm = vrm; _inputTransforms = new(); _outputTransforms = new(); _positions = new(); List particles = new(); var warpSrcs = vrm.GetComponentsInChildren(); for (int warpIndex = 0; warpIndex < warpSrcs.Length; ++warpIndex) { var warp = warpSrcs[warpIndex]; var inputCenterTransformIndex = GetInputTransformIndex(warp.Center); var inputWarpRootParentTransformIndex = GetInputTransformIndex(warp.transform.parent); Debug.Assert(inputWarpRootParentTransformIndex != -1); var inputWarpRootTransformIndex = GetInputTransformIndex(warp.transform); Debug.Assert(inputWarpRootParentTransformIndex != -1); var ouputWarpRootTransformIndex = GetOutputTransformIndex(warp.transform); particles.Add(new ParticleIndo { InputTransformIndex = inputWarpRootTransformIndex, }); var parentIndex = ouputWarpRootTransformIndex; foreach (var particle in warp.Particles) { if (particle != null && particle.Transform != null) { var outputParticleTransformIndex = GetOutputTransformIndex(particle.Transform); particles.Add(new ParticleIndo { ParentIndex = parentIndex, InitLocalPosition = vrm.DefaultTransformStates[particle.Transform].LocalPosition, DragForce = particle.GetSettings(warp.BaseSettings).DragForce, }); parentIndex = outputParticleTransformIndex; } } await awaitCaller.NextFrame(); } _inputTransformAccessArray = new(_inputTransforms.ToArray(), 128); _inputData = new(_inputTransforms.Count, Allocator.Persistent); _outputTransformAccessArray = new(_outputTransforms.ToArray(), 128); var positions = _positions.ToArray(); _currentPositions = new(positions, Allocator.Persistent); _prevPositions = new(positions, Allocator.Persistent); _nextPositions = new(positions, Allocator.Persistent); _particles = new(particles.ToArray(), Allocator.Persistent); } int GetInputTransformIndex(Transform t) { if (t == null) return -1; var i = _inputTransforms.IndexOf(t); if (i == -1) { i = _inputTransforms.Count; _inputTransforms.Add(t); } return i; } int GetOutputTransformIndex(Transform t) { if (t == null) return -1; var i = _outputTransforms.IndexOf(t); if (i == -1) { i = _outputTransforms.Count; _outputTransforms.Add(t); _positions.Add(t.position); } return i; } void IRotateParticleSystem.Process(float deltaTime) { var frame = new FrameInfo(deltaTime, Vector3.zero); // input new InputTransformJob { InputData = _inputData, }.Schedule(_inputTransformAccessArray, default).Complete(); // update root position for (int i = 0; i < _particles.Length; ++i) { var particle = _particles[i]; if (!particle.ParentIndex.HasValue) { _currentPositions[i] = _inputData[particle.InputTransformIndex].ToWorld.GetPosition(); } } // verlet new VerletJob { Frame = frame, Particles = _particles, PrevPositions = _prevPositions, CurrentPositions = _currentPositions, NextPositions = _nextPositions, }.Run(_particles.Length); // output new OutputTransformJob { NextPositions = _nextPositions, }.Schedule(_outputTransformAccessArray, default).Complete(); // update state NativeArray.Copy(_currentPositions, _prevPositions); NativeArray.Copy(_nextPositions, _currentPositions); } void IRotateParticleSystem.ResetInitialRotation() { } void IRotateParticleSystem.DrawGizmos() { } } }