/**
* 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 Live2D.Cubism.Core;
using System;
using UnityEngine;
namespace Live2D.Cubism.Framework.Physics
{
///
/// Children of rig.
///
[Serializable]
public class CubismPhysicsSubRig
{
///
/// Input.
///
[SerializeField]
public CubismPhysicsInput[] Input;
///
/// Output.
///
[SerializeField]
public CubismPhysicsOutput[] Output;
///
/// Particles.
///
[SerializeField]
public CubismPhysicsParticle[] Particles;
///
/// Normalization.
///
[SerializeField]
public CubismPhysicsNormalization Normalization;
///
/// Rig.
///
public CubismPhysicsRig Rig
{
get { return _rig; }
set { _rig = value; }
}
[NonSerialized]
private CubismPhysicsRig _rig;
///
/// Updates parameter from output value.
///
/// Target parameter.
/// Translation.
/// Output value.
private void UpdateOutputParameterValue(CubismParameter parameter, float translation, CubismPhysicsOutput output)
{
var outputScale = 1.0f;
outputScale = output.GetScale();
var value = translation * outputScale;
if (value < parameter.MinimumValue)
{
if (value < output.ValueBelowMinimum)
{
output.ValueBelowMinimum = value;
}
value = parameter.MinimumValue;
}
else if (value > parameter.MaximumValue)
{
if (value > output.ValueExceededMaximum)
{
output.ValueExceededMaximum = value;
}
value = parameter.MaximumValue;
}
var weight = (output.Weight / CubismPhysics.MaximumWeight);
if (weight >= 1.0f)
{
parameter.Value = value;
}
else
{
value = (parameter.Value * (1.0f - weight)) + (value * weight);
parameter.Value = value;
}
}
///
/// Updates particles in every frame.
///
/// Particles.
/// Total translation.
/// Total angle.
/// Direction of wind.
/// Value of threshold.
/// Time of delta.
private void UpdateParticles(
CubismPhysicsParticle[] strand,
Vector2 totalTranslation,
float totalAngle,
Vector2 wind,
float thresholdValue,
float deltaTime
)
{
strand[0].Position = totalTranslation;
var totalRadian = CubismPhysicsMath.DegreesToRadian(totalAngle);
var currentGravity = CubismPhysicsMath.RadianToDirection(totalRadian);
currentGravity.Normalize();
for (var i = 1; i < strand.Length; ++i)
{
strand[i].Force = (currentGravity * strand[i].Acceleration) + wind;
strand[i].LastPosition = strand[i].Position;
// The Cubism Editor expects 30 FPS so we scale here by 30...
var delay = strand[i].Delay * deltaTime * 30.0f;
var direction = strand[i].Position - strand[i - 1].Position;
var radian = CubismPhysicsMath.DirectionToRadian(strand[i].LastGravity, currentGravity) / CubismPhysics.AirResistance;
direction.x = ((Mathf.Cos(radian) * direction.x) - (direction.y * Mathf.Sin(radian)));
direction.y = ((Mathf.Sin(radian) * direction.x) + (direction.y * Mathf.Cos(radian)));
strand[i].Position = strand[i - 1].Position + direction;
var velocity = strand[i].Velocity * delay;
var force = strand[i].Force * delay * delay;
strand[i].Position = strand[i].Position + velocity + force;
var newDirection = strand[i].Position - strand[i - 1].Position;
newDirection.Normalize();
strand[i].Position = strand[i - 1].Position + newDirection * strand[i].Radius;
if (Mathf.Abs(strand[i].Position.x) < thresholdValue)
{
strand[i].Position.x = 0.0f;
}
if (delay != 0.0f)
{
strand[i].Velocity =
((strand[i].Position - strand[i].LastPosition) / delay) * strand[i].Mobility;
}
strand[i].Force = Vector2.zero;
strand[i].LastGravity = currentGravity;
}
}
///
/// Initializes .
///
public void Initialize()
{
var strand = Particles;
// Initialize the top of particle.
strand[0].InitialPosition = Vector2.zero;
strand[0].LastPosition = strand[0].InitialPosition;
strand[0].LastGravity = Rig.Gravity;
strand[0].LastGravity.y *= -1.0f;
// Initialize particles.
for (var i = 1; i < strand.Length; ++i)
{
var radius = Vector2.zero;
radius.y = strand[i].Radius;
strand[i].InitialPosition = strand[i - 1].InitialPosition + radius;
strand[i].Position = strand[i].InitialPosition;
strand[i].LastPosition = strand[i].InitialPosition;
strand[i].LastGravity = Rig.Gravity;
strand[i].LastGravity.y *= -1.0f;
}
// Initialize inputs.
for (var i = 0; i < Input.Length; ++i)
{
Input[i].InitializeGetter();
}
// Initialize outputs.
for (var i = 0; i < Output.Length; ++i)
{
Output[i].InitializeGetter();
}
}
///
/// Evaluate rig in every frame.
///
///
public void Evaluate(float deltaTime)
{
var totalAngle = 0.0f;
var totalTranslation = Vector2.zero;
for (var i = 0; i < Input.Length; ++i)
{
var weight = Input[i].Weight / CubismPhysics.MaximumWeight;
if (Input[i].Source == null)
{
Input[i].Source = Rig.Controller.Parameters.FindById(Input[i].SourceId);
}
var parameter = Input[i].Source;
Input[i].GetNormalizedParameterValue(
ref totalTranslation,
ref totalAngle,
parameter,
Normalization,
weight
);
}
var radAngle = CubismPhysicsMath.DegreesToRadian(-totalAngle);
totalTranslation.x = (totalTranslation.x * Mathf.Cos(radAngle) - totalTranslation.y * Mathf.Sin(radAngle));
totalTranslation.y = (totalTranslation.x * Mathf.Sin(radAngle) + totalTranslation.y * Mathf.Cos(radAngle));
UpdateParticles(
Particles,
totalTranslation,
totalAngle,
Rig.Wind,
CubismPhysics.MovementThreshold * Normalization.Position.Maximum,
deltaTime
);
for (var i = 0; i < Output.Length; ++i)
{
var particleIndex = Output[i].ParticleIndex;
if (particleIndex < 1 || particleIndex >= Particles.Length)
{
break;
}
if (Output[i].Destination == null)
{
Output[i].Destination = Rig.Controller.Parameters.FindById(Output[i].DestinationId);
}
var parameter = Output[i].Destination;
var translation = Particles[particleIndex].Position -
Particles[particleIndex - 1].Position;
var outputValue = Output[i].GetValue(
translation,
parameter,
Particles,
particleIndex,
Rig.Gravity
);
UpdateOutputParameterValue(parameter, outputValue, Output[i]);
}
}
}
}