/**
* 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.Pose
{
///
/// Cubism pose controller.
///
public sealed class CubismPoseController : MonoBehaviour, ICubismUpdatable
{
#region variable
///
/// Default visible pose index.
///
[SerializeField]
public int defaultPoseIndex = 0;
///
/// Back opacity threshold.
///
private const float BackOpacityThreshold = 0.15f;
///
/// Cubism model cache.
///
private CubismModel _model;
///
/// Model has update controller component.
///
[HideInInspector]
public bool HasUpdateController { get; set; }
///
/// Pose data.
///
private CubismPoseData[][] _poseData;
#endregion
#region Function
///
/// update hidden part opacity.
///
public void Refresh()
{
_model = this.FindCubismModel();
// Fail silently...
if (_model == null)
{
return;
}
var tags = _model
.Parts
.GetComponentsMany();
for(var i = 0; i < tags.Length; ++i)
{
var groupIndex = tags[i].GroupIndex;
var partIndex = tags[i].PartIndex;
if(_poseData == null || _poseData.Length <= groupIndex)
{
Array.Resize(ref _poseData, groupIndex + 1);
}
if(_poseData[groupIndex] == null || _poseData[groupIndex].Length <= partIndex)
{
Array.Resize(ref _poseData[groupIndex], partIndex + 1);
}
_poseData[groupIndex][partIndex].PosePart = tags[i];
_poseData[groupIndex][partIndex].Part= tags[i].GetComponent();
defaultPoseIndex = (defaultPoseIndex < 0) ? 0 : defaultPoseIndex;
if (partIndex != defaultPoseIndex)
{
_poseData[groupIndex][partIndex].Part.Opacity = 0.0f;
}
_poseData[groupIndex][partIndex].Opacity = _poseData[groupIndex][partIndex].Part.Opacity;
if(tags[i].Link == null || tags[i].Link.Length == 0)
{
continue;
}
_poseData[groupIndex][partIndex].LinkParts = new CubismPart[tags[i].Link.Length];
for(var j = 0; j < tags[i].Link.Length; ++j)
{
var linkId = tags[i].Link[j];
_poseData[groupIndex][partIndex].LinkParts[j] = _model.Parts.FindById(linkId);
}
}
// Get cubism update controller.
HasUpdateController = (GetComponent() != null);
}
///
/// update hidden part opacity.
///
private void DoFade()
{
for(var groupIndex = 0; groupIndex < _poseData.Length; ++groupIndex)
{
var appearPartsGroupIndex = -1;
var appearPartsGroupOpacity = 1.0f;
// Find appear parts group index and opacity.
for (var i = 0; i < _poseData[groupIndex].Length; ++i)
{
var part = _poseData[groupIndex][i].Part;
if(part.Opacity > _poseData[groupIndex][i].Opacity)
{
appearPartsGroupIndex = i;
appearPartsGroupOpacity = part.Opacity;
break;
}
}
// Fail silently...
if(appearPartsGroupIndex < 0)
{
return;
}
// Delay disappearing parts groups disappear.
for (var i = 0; i < _poseData[groupIndex].Length; ++i)
{
// Fail silently...
if(i == appearPartsGroupIndex)
{
continue;
}
var part = _poseData[groupIndex][i].Part;
var delayedOpacity = part.Opacity;
var backOpacity = (1.0f - delayedOpacity) * (1.0f - appearPartsGroupOpacity);
// When restricting the visible proportion of the background
if (backOpacity > BackOpacityThreshold)
{
delayedOpacity = 1.0f - BackOpacityThreshold / (1.0f - appearPartsGroupOpacity);
}
// Overwrite the opacity if it's greater than the delayed opacity.
if (part.Opacity > delayedOpacity)
{
part.Opacity = delayedOpacity;
}
}
}
}
///
/// Copy opacity to linked parts.
///
private void CopyPartOpacities()
{
for(var groupIndex = 0; groupIndex < _poseData.Length; ++groupIndex)
{
for (var partIndex = 0; partIndex < _poseData[groupIndex].Length; ++partIndex)
{
var linkParts = _poseData[groupIndex][partIndex].LinkParts;
if(linkParts == null)
{
continue;
}
var opacity = _poseData[groupIndex][partIndex].Part.Opacity;
for (var linkIndex = 0; linkIndex < linkParts.Length; ++linkIndex)
{
var linkPart = linkParts[linkIndex];
if(linkPart != null)
{
linkPart.Opacity = opacity;
}
}
}
}
}
///
/// Save parts opacity.
///
private void SavePartOpacities()
{
for(var groupIndex = 0; groupIndex < _poseData.Length; ++groupIndex)
{
for (var partIndex = 0; partIndex < _poseData[groupIndex].Length; ++partIndex)
{
_poseData[groupIndex][partIndex].Opacity = _poseData[groupIndex][partIndex].Part.Opacity;
}
}
}
///
/// Called by cubism update controller. Order to invoke OnLateUpdate.
///
public int ExecutionOrder
{
get { return CubismUpdateExecutionOrder.CubismPoseController; }
}
///
/// Called by cubism update controller. Needs to invoke OnLateUpdate on Editing.
///
public bool NeedsUpdateOnEditing
{
get { return false; }
}
///
/// Called by cubism update manager. Updates controller.
///
public void OnLateUpdate()
{
// Fail silently...
if (!enabled || _model == null || _poseData == null)
{
return;
}
DoFade();
CopyPartOpacities();
SavePartOpacities();
}
#endregion
#region Unity Event Handling
///
/// Called by Unity. Makes sure cache is initialized.
///
private void OnEnable()
{
Refresh();
}
///
/// Called by Unity.
///
private void LateUpdate()
{
if(!HasUpdateController)
{
OnLateUpdate();
}
}
#endregion
}
}