/**
* 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);
}
}
}
}