/** * 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 Live2D.Cubism.Framework.MotionFade; using System.Collections.Generic; using UnityEngine; namespace Live2D.Cubism.Framework.Expression { /// /// Expression controller. /// public class CubismExpressionController : MonoBehaviour, ICubismUpdatable { #region variable /// /// Expressions data list. /// [SerializeField] public CubismExpressionList ExpressionsList; /// /// Use the expression calculation method. /// [SerializeField] public bool UseLegacyBlendCalculation = false; /// /// CubismModel cache. /// private CubismModel _model = null; /// /// Playing expressions. /// private List _playingExpressions = new List(); /// /// Playing expressions index. /// [SerializeField] public int CurrentExpressionIndex = -1; /// /// Last playing expressions index. /// private int _lastExpressionIndex = -1; /// /// Model has update controller component. /// [HideInInspector] public bool HasUpdateController { get; set; } /// /// Value of each parameter to be applied to the model. /// private List _expressionParameterValues = new List(); /// /// Default value for applying additive. /// private const float DefaultAdditiveValue = 0.0f; /// /// Initial value of multiply applied. /// private const float DefaultMultiplyValue = 1.0f; #endregion /// /// Add new expression to playing expressions. /// private void StartExpression() { // Fail silently... if(ExpressionsList == null || ExpressionsList.CubismExpressionObjects == null) { return; } // Backup expression. _lastExpressionIndex = CurrentExpressionIndex; // Set last expression end time if(_playingExpressions.Count > 0) { var playingExpression = _playingExpressions[_playingExpressions.Count - 1]; var newExpressionEndTime = playingExpression.ExpressionUserTime + playingExpression.FadeOutTime; if (playingExpression.ExpressionEndTime == 0.0f || newExpressionEndTime < playingExpression.ExpressionEndTime) { playingExpression.ExpressionEndTime = newExpressionEndTime; } _playingExpressions[_playingExpressions.Count - 1] = playingExpression; } // Fail silently... if (CurrentExpressionIndex < 0 || CurrentExpressionIndex >= ExpressionsList.CubismExpressionObjects.Length) { return; } var palyingExpression = CubismPlayingExpression.Create(_model, ExpressionsList.CubismExpressionObjects[CurrentExpressionIndex]); if(palyingExpression == null) { return; } // Add to PlayingExList. _playingExpressions.Add(palyingExpression); } /// /// Called by cubism update controller. Order to invoke OnLateUpdate. /// public int ExecutionOrder { get { return CubismUpdateExecutionOrder.CubismExpressionController; } } /// /// Called by cubism update controller. Needs to invoke OnLateUpdate on Editing. /// public bool NeedsUpdateOnEditing { get { return false; } } /// /// Called by cubism update manager. /// public void OnLateUpdate() { // Fail silently... if(!enabled || _model == null) { return; } // Start expression when current expression changed. if(CurrentExpressionIndex != _lastExpressionIndex) { StartExpression(); } // Update of expressions. if (UseLegacyBlendCalculation) { UpdateExpressionLegacy(); } else { UpdateExpression(); } } /// /// Update of expressions. (old method) /// private void UpdateExpressionLegacy() { // Update expression for (var expressionIndex = 0; expressionIndex < _playingExpressions.Count; ++expressionIndex) { var playingExpression = _playingExpressions[expressionIndex]; UpdateFadeWeight(playingExpression); // Apply value. for (var i = 0; i < playingExpression.Destinations.Length; ++i) { // Fail silently... if (playingExpression.Destinations[i] == null) { continue; } switch (playingExpression.Blend[i]) { case CubismParameterBlendMode.Additive: playingExpression.Destinations[i].AddToValue(playingExpression.Value[i], playingExpression.FadeWeight); break; case CubismParameterBlendMode.Multiply: playingExpression.Destinations[i].MultiplyValueBy(playingExpression.Value[i], playingExpression.FadeWeight); break; case CubismParameterBlendMode.Override: playingExpression.Destinations[i].OverrideValue(playingExpression.Destinations[i].Value * (1 - playingExpression.FadeWeight) + (playingExpression.Value[i] * playingExpression.FadeInWeight)); break; default: // When an unspecified value is set, it is already in addition mode. break; } } // Apply update value _playingExpressions[expressionIndex] = playingExpression; } // Remove expression from playing expressions for (var expressionIndex = _playingExpressions.Count - 1; expressionIndex >= 0; --expressionIndex) { if (_playingExpressions[expressionIndex].FadeWeight > 0.0f) { continue; } _playingExpressions.RemoveAt(expressionIndex); } } /// /// Update of expressions. /// private void UpdateExpression() { var expressionWeight = 0.0f; // Update expression for (var expressionIndex = 0; expressionIndex < _playingExpressions.Count; ++expressionIndex) { var playingExpression = _playingExpressions[expressionIndex]; // List all parameters referenced by the Expression being played. for (var i = 0; i < playingExpression.Destinations.Length; ++i) { var index = -1; // Search for the presence of a parameter ID in the list. for (var j = 0; j < _expressionParameterValues.Count; j++) { if (_expressionParameterValues[j].Parameter != playingExpression.Destinations[i]) { continue; } index = j; break; } if (index >= 0) { continue; } // If the parameter does not exist in the list, add a new one. CubismExpressionParameterValue item = new CubismExpressionParameterValue(); item.Parameter = playingExpression.Destinations[i]; if (item.Parameter != null) { item.AdditiveValue = DefaultAdditiveValue; item.MultiplyValue = DefaultMultiplyValue; item.OverwriteValue = item.Parameter.Value; _expressionParameterValues.Add(item); } } // ------ Calculate value ------ CalculateExpressionParameters(expressionIndex, playingExpression); expressionWeight += playingExpression.FadeInTime == 0.0f ? 1.0f : CubismFadeMath.GetEasingSine( (playingExpression.ExpressionUserTime - playingExpression.ExpressionStartTime) / playingExpression.FadeInTime); } // ----- If the latest Expression fade is complete, delete the earlier one. ------ if (_playingExpressions.Count > 1 && _playingExpressions[_playingExpressions.Count-1].FadeWeight >= 1.0f) { // The last element of the array is not deleted. for (var i = _playingExpressions.Count - 2; i >= 0; --i) { _playingExpressions.RemoveAt(i); } } if (expressionWeight > 1.0f) { expressionWeight = 1.0f; } // Apply each value to the model. for (var i = 0; i < _expressionParameterValues.Count; i++) { var expressionParameterValue = _expressionParameterValues[i]; expressionParameterValue.Parameter.OverrideValue( (expressionParameterValue.OverwriteValue + expressionParameterValue.AdditiveValue) * expressionParameterValue.MultiplyValue, expressionWeight); expressionParameterValue.AdditiveValue = DefaultAdditiveValue; expressionParameterValue.MultiplyValue = DefaultMultiplyValue; } } /// /// Update motion weights. /// /// Expression motion during playback. private void UpdateFadeWeight(CubismPlayingExpression playingExpression) { // Update expression user time. playingExpression.ExpressionUserTime += Time.deltaTime; // Update weight playingExpression.FadeInWeight = (Mathf.Abs(playingExpression.FadeInTime) < float.Epsilon) ? 1.0f : CubismFadeMath.GetEasingSine(playingExpression.ExpressionUserTime / playingExpression.FadeInTime); playingExpression.FadeOutWeight = ((Mathf.Abs(playingExpression.ExpressionEndTime) < float.Epsilon) || (playingExpression.ExpressionEndTime < 0.0f)) ? 1.0f : CubismFadeMath.GetEasingSine( (playingExpression.ExpressionEndTime - playingExpression.ExpressionUserTime) / playingExpression.FadeOutTime); playingExpression.FadeWeight = playingExpression.Weight * playingExpression.FadeInWeight * playingExpression.FadeOutWeight; } /// /// Calculate parameters related to the model's facial expressions. /// /// Index of currently processed facial expression motion. /// Expression motion during playback. private void CalculateExpressionParameters(int expressionIndex, CubismPlayingExpression playingExpression) { UpdateFadeWeight(playingExpression); for (var i = 0; i < _expressionParameterValues.Count; i++) { var expressionParameterValue = _expressionParameterValues[i]; if (expressionParameterValue.Parameter == null) { continue; } var currentParameterValue = expressionParameterValue.OverwriteValue = expressionParameterValue.Parameter.Value; var expressionParameters = playingExpression.Destinations; var parameterIndex = -1; for (var j = 0; j < expressionParameters.Length; j++) { if (expressionParameterValue.Parameter != expressionParameters[j]) { continue; } parameterIndex = j; break; } // Parameters not referenced by the Expression being played have their initial values applied. if (parameterIndex < 0) { if (expressionIndex == 0) { expressionParameterValue.AdditiveValue = DefaultAdditiveValue; expressionParameterValue.MultiplyValue = DefaultMultiplyValue; expressionParameterValue.OverwriteValue = currentParameterValue; } else { expressionParameterValue.AdditiveValue = CalculateValue( expressionParameterValue.AdditiveValue, DefaultAdditiveValue, playingExpression.FadeWeight); expressionParameterValue.MultiplyValue = CalculateValue( expressionParameterValue.MultiplyValue, DefaultMultiplyValue, playingExpression.FadeWeight); expressionParameterValue.OverwriteValue = CalculateValue( expressionParameterValue.OverwriteValue, currentParameterValue, playingExpression.FadeWeight); } _expressionParameterValues[i] = expressionParameterValue; continue; } // Calculate value. var value = playingExpression.Value[parameterIndex]; float newAdditiveValue, newMultiplyValue, newSetValue; switch (playingExpression.Blend[parameterIndex]) { case CubismParameterBlendMode.Additive: newAdditiveValue = value; newMultiplyValue = DefaultMultiplyValue; newSetValue = currentParameterValue; break; case CubismParameterBlendMode.Multiply: newAdditiveValue = DefaultAdditiveValue; newMultiplyValue = value; newSetValue = currentParameterValue; break; case CubismParameterBlendMode.Override: newAdditiveValue = DefaultAdditiveValue; newMultiplyValue = DefaultMultiplyValue; newSetValue = value; break; default: return; } if (expressionIndex == 0) { expressionParameterValue.AdditiveValue = newAdditiveValue; expressionParameterValue.MultiplyValue = newMultiplyValue; expressionParameterValue.OverwriteValue = newSetValue; } else { expressionParameterValue.AdditiveValue = CalculateValue( expressionParameterValue.AdditiveValue, newAdditiveValue, playingExpression.FadeWeight); expressionParameterValue.MultiplyValue = CalculateValue( expressionParameterValue.MultiplyValue, newMultiplyValue, playingExpression.FadeWeight); expressionParameterValue.OverwriteValue = CalculateValue( expressionParameterValue.OverwriteValue, newSetValue, playingExpression.FadeWeight); } _expressionParameterValues[i] = expressionParameterValue; } } /// /// Blend calculation. /// /// Source value. /// Destination value. /// Weight value. /// private float CalculateValue(float source, float destination, float fadeWeight) { return (source * (1.0f - fadeWeight)) + (destination * fadeWeight); } #region Unity Event Handling /// /// Called by Unity. /// private void OnEnable() { _model = this.FindCubismModel(); // Get cubism update controller. HasUpdateController = (GetComponent() != null); } /// /// Called by Unity. /// private void LateUpdate() { if(!HasUpdateController) { OnLateUpdate(); } } #endregion } }