/** * 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 { /// /// Name. /// [SerializeField] public string Name; /// /// Input. /// [SerializeField] public CubismPhysicsInput[] Input; /// /// Original Input. /// [NonSerialized] public CubismPhysicsInput[] OriginalInput; /// /// Output. /// [SerializeField] public CubismPhysicsOutput[] Output; /// /// Original Output. /// [NonSerialized] public CubismPhysicsOutput[] OriginalOutput; /// /// Particles. /// [SerializeField] public CubismPhysicsParticle[] Particles; /// /// Normalization. /// [SerializeField] public CubismPhysicsNormalization Normalization; /// /// Rig. /// public CubismPhysicsRig Rig { get { return _rig; } set { _rig = value; } } [NonSerialized] private CubismPhysicsRig _rig; /// /// Output result of physics operations before applying to parameters. /// private struct SubRigPhysicsOutput { public float[] Output; } [NonSerialized] private SubRigPhysicsOutput _currentRigOutput; // Results of the latest pendulum calculation. [NonSerialized] private SubRigPhysicsOutput _previousRigOutput; // Result of previous pendulum calculation. /// /// Applies the specified weights from the latest and one previous result of the pendulum operation. /// /// Weight of latest results. public void Interpolate(float weight) { // Load input parameters. for (int i = 0; i < Output.Length; ++i) { if (Output[i].Destination == null) { var destination = Rig.Controller.Parameters.FindById(Output[i].DestinationId); if (destination == null) { continue; } Output[i].Destination = destination; } UpdateOutputParameterValue( Output[i].Destination, ref Output[i].Destination.Value, _previousRigOutput.Output[i] * (1 - weight) + _currentRigOutput.Output[i] * weight, Output[i] ); } } /// /// Updates parameter from output value. /// /// Target parameter. /// Target parameter Value. /// Translation. /// Output value. private void UpdateOutputParameterValue(CubismParameter parameter, ref float parameterValue, 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) { parameterValue = value; } else { value = (parameterValue * (1.0f - weight)) + (value * weight); parameterValue = 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; } } /// /// Updates particles in stabilization function. /// /// Particles /// Total translation. /// Total angle. /// Direction of wind. /// Value of threshold. private void UpdateParticlesForStabilization( CubismPhysicsParticle[] strand, Vector2 totalTranslation, float totalAngle, Vector2 wind, float thresholdValue ) { 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; strand[i].Velocity = Vector2.zero; var force = strand[i].Force; force.Normalize(); strand[i].Position = strand[i - 1].Position + force * strand[i].Radius; if (Mathf.Abs(strand[i].Position.x) < thresholdValue) { strand[i].Position.x = 0.0f; } 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. OriginalInput = new CubismPhysicsInput[Input.Length]; for (var i = 0; i < Input.Length; ++i) { OriginalInput[i] = Input[i]; Input[i].InitializeGetter(); } _previousRigOutput = new SubRigPhysicsOutput(); _currentRigOutput = new SubRigPhysicsOutput(); Array.Resize(ref _previousRigOutput.Output, Output.Length); Array.Resize(ref _currentRigOutput.Output, Output.Length); // Initialize outputs. OriginalOutput = new CubismPhysicsOutput[Output.Length]; for (var i = 0; i < Output.Length; ++i) { OriginalOutput[i] = Output[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) { ref var input = ref Input[i]; var weight = input.Weight / CubismPhysics.MaximumWeight; if (input.Source == null) { input.Source = Rig.Controller.Parameters.FindById(input.SourceId); input.SourceIndex = Array.IndexOf(Rig.Controller.Parameters, input.Source); } var parameter = input.Source; input.GetNormalizedParameterValue( ref totalTranslation, ref totalAngle, parameter, ref Rig.ParametersCache[input.SourceIndex], 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) { ref var currentRigOutput = ref _currentRigOutput.Output[i]; _previousRigOutput.Output[i] = currentRigOutput; ref var output = ref Output[i]; if (output.Destination == null) { var destination = Rig.Controller.Parameters.FindById(output.DestinationId); if (destination == null) { continue; } output.Destination = destination; } var particleIndex = output.ParticleIndex; if (particleIndex < 1 || particleIndex >= Particles.Length) { continue; } // Update each time as the index may fluctuate. output.DestinationIndex = Array.IndexOf(Rig.Controller.Parameters, output.Destination); var translation = Particles[particleIndex].Position - Particles[particleIndex - 1].Position; var parameter = output.Destination; var outputValue = output.GetValue( translation, Particles, particleIndex, Rig.Gravity ); currentRigOutput = outputValue; UpdateOutputParameterValue(parameter, ref Rig.ParametersCache[output.DestinationIndex], outputValue, output); } } /// /// Calculate the state in which the physics operation stabilizes at the current parameter values. /// public void Stabilization() { 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 index = Array.IndexOf(Rig.Controller.Parameters, Input[i].Source); var parameter = Input[i].Source; Input[i].GetNormalizedParameterValue( ref totalTranslation, ref totalAngle, parameter, ref Input[i].Source.Value, Normalization, weight ); Rig.ParametersCache[index] = Input[i].Source.Value; } 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)); UpdateParticlesForStabilization( Particles, totalTranslation, totalAngle, Rig.Wind, CubismPhysics.MovementThreshold * Normalization.Position.Maximum ); for (var i = 0; i < Output.Length; ++i) { _previousRigOutput.Output[i] = _currentRigOutput.Output[i]; if (Output[i].Destination == null) { var destination = Rig.Controller.Parameters.FindById(Output[i].DestinationId); if (destination == null) { continue; } Output[i].Destination = destination; } var particleIndex = Output[i].ParticleIndex; if (particleIndex < 1 || particleIndex >= Particles.Length) { continue; } var index = Array.IndexOf(Rig.Controller.Parameters, Output[i].Destination); var translation = Particles[particleIndex].Position - Particles[particleIndex - 1].Position; var parameter = Output[i].Destination; var outputValue = Output[i].GetValue( translation, Particles, particleIndex, Rig.Gravity ); _currentRigOutput.Output[i] = outputValue; _previousRigOutput.Output[i] = outputValue; UpdateOutputParameterValue(parameter, ref Output[i].Destination.Value, outputValue, Output[i]); Rig.ParametersCache[index] = Output[i].Destination.Value; } } } }