/** * Copyright(c) Live2D Inc. All rights reserved. * * Use of this source code is governed by the Live2D Open Software license * that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html. */ using System; using Live2D.Cubism.Core; using UnityEngine; namespace Live2D.Cubism.Framework.Physics { /// /// Physics rig. /// [Serializable] public class CubismPhysicsRig { /// /// Children of rig. /// [SerializeField] public CubismPhysicsSubRig[] SubRigs; [SerializeField] public Vector2 Gravity = CubismPhysics.Gravity; [SerializeField] public Vector2 Wind = CubismPhysics.Wind; [SerializeField] public float Fps = 0.0f; private float _currentRemainTime; // Time not processed by physics. public float[] ParametersCache { get { return _parametersCache; } set { _parametersCache = value; } } [NonSerialized] private float[] _parametersCache; // Cache parameters used by Evaluate. [NonSerialized] private float[] _parametersInputCache; // Cache input when UpdateParticles runs. /// /// Reference of controller to refer from children rig. /// public CubismPhysicsController Controller { get; set; } /// /// Get by name /// /// /// public CubismPhysicsSubRig GetSubRig(string name) { for (int i = 0; i < SubRigs.Length; i++) { if (SubRigs[i].Name == name) { return SubRigs[i]; } } return null; } /// /// Initializes rigs. /// public void Initialize() { _currentRemainTime = 0.0f; Controller.gameObject.FindCubismModel(); _parametersCache = new float[Controller.Parameters.Length]; _parametersInputCache = new float[Controller.Parameters.Length]; for (var i = 0; i < SubRigs.Length; ++i) { SubRigs[i].Initialize(); } } /// /// Calculations are performed until the physics are stable. /// public void Stabilization() { if (Controller == null) { return; } // Initialize. if (_parametersCache == null) { _parametersCache = new float[Controller.Parameters.Length]; } if (_parametersCache.Length < Controller.Parameters.Length) { Array.Resize(ref _parametersCache, Controller.Parameters.Length); } if (_parametersInputCache == null) { _parametersInputCache = new float[Controller.Parameters.Length]; } if (_parametersInputCache.Length < Controller.Parameters.Length) { Array.Resize(ref _parametersInputCache, Controller.Parameters.Length); } // Obtain and cache the current parameter posture. for (var i = 0; i < Controller.Parameters.Length; i++) { _parametersCache[i] = Controller.Parameters[i].Value; _parametersInputCache[i] = _parametersCache[i]; } // Evaluate. for (var i = 0; i < SubRigs.Length; ++i) { SubRigs[i].Stabilization(); } var model = Controller.gameObject.FindCubismModel(); model.ForceUpdateNow(); } /// /// Evaluate rigs. /// /// Pendulum interpolation weights /// /// The result of the pendulum calculation is saved and /// the output to the parameters is interpolated with the saved previous result of the pendulum calculation. /// /// The figure shows the interpolation between [1] and [2]. /// /// The weight of the interpolation are determined by the current time seen between /// the latest pendulum calculation timing and the next timing. /// /// Figure shows the weight of position (3) as seen between [2] and [4]. /// /// As an interpretation, the pendulum calculation and weights are misaligned. /// /// If there is no FPS information in physics3.json, it is always set in the previous pendulum state. /// /// The purpose of this specification is to avoid the quivering appearance caused by deviations from the interpolation range. /// /// ------------ time --------------> /// ///         |+++++|------| <- weight /// ==[1]====#=====[2]---(3)----(4) /// ^ output contents /// /// 1: _previousRigOutput /// 2: _currentRigOutput /// 3: _currentRemainTime (now rendering) /// 4: next particles timing /// /// public void Evaluate(float deltaTime) { if (0.0f >= deltaTime) { return; } _currentRemainTime += deltaTime; if (_currentRemainTime > CubismPhysics.MaxDeltaTime) { _currentRemainTime = 0.0f; } var physicsDeltaTime = 0.0f; if (Fps > 0.0f) { physicsDeltaTime = 1.0f / Fps; } else { physicsDeltaTime = deltaTime; } if (_parametersCache == null) { _parametersCache = new float[Controller.Parameters.Length]; } if (_parametersCache.Length < Controller.Parameters.Length) { Array.Resize(ref _parametersCache, Controller.Parameters.Length); } if (_parametersInputCache == null) { _parametersInputCache = new float[Controller.Parameters.Length]; } if (_parametersInputCache.Length < Controller.Parameters.Length) { Array.Resize(ref _parametersInputCache, Controller.Parameters.Length); for (var i = 0; i < _parametersInputCache.Length; i++) { _parametersInputCache[i] = _parametersCache[i]; } } while (_currentRemainTime >= physicsDeltaTime) { var inputWeight = physicsDeltaTime / _currentRemainTime; // Calculate the input at the timing to UpdateParticles by linear interpolation with the _parameterInputCache and parameterValue. // _parameterCache needs to be separated from _parameterInputCache because of its role in propagating values between groups. for (var i = 0; i < Controller.Parameters.Length; i++) { _parametersCache[i] = _parametersInputCache[i] * (1.0f - inputWeight) + Controller.Parameters[i].Value * inputWeight; _parametersInputCache[i] = _parametersCache[i]; } for (var i = 0; i < SubRigs.Length; ++i) { SubRigs[i].Evaluate(physicsDeltaTime); } _currentRemainTime -= physicsDeltaTime; } float alpha = _currentRemainTime / physicsDeltaTime; for (var i = 0; i < SubRigs.Length; ++i) { SubRigs[i].Interpolate(alpha); } } } }