/**
* 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;
using System;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Live2D.Cubism.Rendering
{
///
/// Controls rendering of a .
///
[ExecuteInEditMode, CubismDontMoveOnReimport]
public sealed class CubismRenderController : MonoBehaviour, ICubismUpdatable
{
///
/// Model opacity.
///
///
/// This is turned into a field to be available to s...
///
[SerializeField, HideInInspector]
public float Opacity = 1f;
///
/// backing field.
///
[SerializeField, HideInInspector]
private float _lastOpacity;
///
/// Last model opacity.
///
private float LastOpacity
{
get { return _lastOpacity; }
set { _lastOpacity = value; }
}
///
/// Sorting layer name.
///
public string SortingLayer
{
get
{
return UnityEngine.SortingLayer.IDToName(SortingLayerId);
}
set
{
SortingLayerId = UnityEngine.SortingLayer.NameToID(value);
}
}
///
/// backing field.
///
[SerializeField, HideInInspector]
private int _sortingLayerId;
///
/// Sorting layer Id.
///
public int SortingLayerId
{
get
{
return _sortingLayerId;
}
set
{
if (value == _sortingLayerId)
{
return;
}
_sortingLayerId = value;
// Apply sorting layer.
var renderers = Renderers;
for (var i = 0; i < renderers.Length; ++i)
{
renderers[i].OnControllerSortingLayerDidChange(_sortingLayerId);
}
}
}
///
/// backing field.
///
[SerializeField, HideInInspector]
private CubismSortingMode _sortingMode;
///
/// sorting.
///
public CubismSortingMode SortingMode
{
get
{
return _sortingMode;
}
set
{
// Return early if same value given.
if (value == _sortingMode)
{
return;
}
_sortingMode = value;
// Flip sorting.
var renderers = Renderers;
for (var i = 0; i < renderers.Length; ++i)
{
renderers[i].OnControllerSortingModeDidChange(_sortingMode);
}
}
}
///
/// Order in sorting layer.
///
[SerializeField, HideInInspector]
private int _sortingOrder;
///
/// Order in sorting layer.
///
public int SortingOrder
{
get
{
return _sortingOrder;
}
set
{
// Return early in case same value given.
if (value == _sortingOrder)
{
return;
}
_sortingOrder = value;
// Apply new sorting order.
var renderers = Renderers;
for (var i = 0; i < renderers.Length; ++i)
{
renderers[i].OnControllerSortingOrderDidChange(SortingOrder);
}
}
}
///
/// [Optional] Camera to face.
///
[SerializeField]
public Camera CameraToFace;
///
/// backing field.
///
[SerializeField, HideInInspector]
private Object _drawOrderHandler;
///
/// Draw order handler proxy object.
///
public Object DrawOrderHandler
{
get { return _drawOrderHandler; }
set { _drawOrderHandler = value.ToNullUnlessImplementsInterface(); }
}
///
/// backing field.
///
[NonSerialized]
private ICubismDrawOrderHandler _drawOrderHandlerInterface;
///
/// Listener for draw order changes.
///
private ICubismDrawOrderHandler DrawOrderHandlerInterface
{
get
{
if (_drawOrderHandlerInterface == null)
{
_drawOrderHandlerInterface = DrawOrderHandler.GetInterface();
}
return _drawOrderHandlerInterface;
}
}
///
/// backing field.
///
[SerializeField, HideInInspector]
private Object _opacityHandler;
///
/// Opacity handler proxy object.
///
public Object OpacityHandler
{
get { return _opacityHandler; }
set { _opacityHandler = value.ToNullUnlessImplementsInterface(); }
}
///
/// backing field.
///
private ICubismOpacityHandler _opacityHandlerInterface;
///
/// Listener for opacity changes.
///
private ICubismOpacityHandler OpacityHandlerInterface
{
get
{
if (_opacityHandlerInterface == null)
{
_opacityHandlerInterface = OpacityHandler.GetInterface();
}
return _opacityHandlerInterface;
}
}
///
/// The value to offset the s by.
///
///
/// You only need to adjust this value when using perspective cameras.
///
[SerializeField, HideInInspector]
public float _depthOffset = 0.00001f;
///
/// Depth offset used when sorting by depth.
///
public float DepthOffset
{
get { return _depthOffset; }
set
{
// Return if same value given.
if (Mathf.Abs(value - _depthOffset) < Mathf.Epsilon)
{
return;
}
// Store value.
_depthOffset = value;
// Apply it.
var renderers = Renderers;
for (var i = 0; i < renderers.Length; ++i)
{
renderers[i].OnControllerDepthOffsetDidChange(_depthOffset);
}
}
}
///
/// Model the controller belongs to.
///
private CubismModel Model
{
get { return this.FindCubismModel(); }
}
///
/// backing field.
///
private Transform _drawablesRootTransform;
///
/// Root transform of all s of the model.
///
private Transform DrawablesRootTransform
{
get
{
if (_drawablesRootTransform == null)
{
_drawablesRootTransform = Model.Drawables[0].transform.parent;
}
return _drawablesRootTransform;
}
}
///
/// s backing field.
///
[NonSerialized]
private CubismRenderer[] _renderers;
///
/// s.
///
public CubismRenderer[] Renderers
{
get
{
if (_renderers== null)
{
_renderers = Model.Drawables.GetComponentsMany();
}
return _renderers;
}
private set { _renderers = value; }
}
///
/// Model has update controller component.
///
[HideInInspector]
public bool HasUpdateController { get; set; }
///
/// Makes sure all s have s attached to them.
///
private void TryInitializeRenderers()
{
// Try get renderers.
var renderers = Renderers;
// Create renderers if necesssary.
if (renderers == null || renderers.Length == 0)
{
// Create renders and apply it to backing field...
var drawables = this
.FindCubismModel()
.Drawables;
renderers = drawables.AddComponentEach();
// Store renderers.
Renderers = renderers;
}
// Make sure renderers are initialized.
for (var i = 0; i < renderers.Length; ++i)
{
renderers[i].TryInitialize();
}
// Initialize sorting layer.
// We set the backing field here directly because we pull the sorting layer directly from the renderer.
_sortingLayerId = renderers[0]
.MeshRenderer
.sortingLayerID;
}
///
/// Updates opacity if necessary.
///
private void UpdateOpacity()
{
// Return if same value given.
if (Mathf.Abs(Opacity - LastOpacity) < Mathf.Epsilon)
{
return;
}
// Store value.
Opacity = Mathf.Clamp(Opacity, 0f, 1f);
LastOpacity = Opacity;
// Apply opacity.
var applyOpacityToRenderers = (OpacityHandlerInterface == null || Opacity > (1f - Mathf.Epsilon));
if (applyOpacityToRenderers)
{
var renderers = Renderers;
for (var i = 0; i < renderers.Length; ++i)
{
renderers[i].OnModelOpacityDidChange(Opacity);
}
}
// Call handler.
if (OpacityHandlerInterface != null)
{
OpacityHandlerInterface.OnOpacityDidChange(this, Opacity);
}
}
///
/// Called by cubism update controller. Order to invoke OnLateUpdate.
///
public int ExecutionOrder
{
get { return CubismUpdateExecutionOrder.CubismRenderController; }
}
///
/// Called by cubism update controller. Needs to invoke OnLateUpdate on Editing.
///
public bool NeedsUpdateOnEditing
{
get { return true; }
}
///
/// Called by cubism update controller. Applies billboarding.
///
public void OnLateUpdate()
{
// Fail silently...
if(!enabled)
{
return;
}
// Update opacity if necessary.
UpdateOpacity();
// Return early in case no camera is to be faced.
if (CameraToFace == null)
{
return;
}
// Face camera.
DrawablesRootTransform.rotation = (Quaternion.LookRotation(CameraToFace.transform.forward, Vector3.up));
}
#region Unity Event Handling
///
/// Called by Unity.
///
private void Start()
{
// Get cubism update controller.
HasUpdateController = (GetComponent() != null);
}
///
/// Called by Unity. Enables listening to render data updates.
///
private void OnEnable()
{
// Fail silently.
if (Model == null)
{
return;
}
// Make sure renderers are available.
TryInitializeRenderers();
// Register listener.
Model.OnDynamicDrawableData += OnDynamicDrawableData;
}
///
/// Called by Unity. Disables listening to render data updates.
///
private void OnDisable()
{
// Fail silently.
if (Model == null)
{
return;
}
// Deregister listener.
Model.OnDynamicDrawableData -= OnDynamicDrawableData;
}
#endregion
#region Cubism Event Handling
///
/// Called by Unity.
///
private void LateUpdate()
{
if(!HasUpdateController)
{
OnLateUpdate();
}
}
///
/// Called whenever new render data is available.
///
/// Model with new render data.
/// New render data.
private void OnDynamicDrawableData(CubismModel sender, CubismDynamicDrawableData[] data)
{
// Get drawables.
var drawables = sender.Drawables;
var renderers = Renderers;
// Handle render data changes.
for (var i = 0; i < data.Length; ++i)
{
// Controls whether mesh buffers are to be swapped.
var swapMeshes = false;
// Update visibility if last SwapInfo flag is true.
renderers[i].UpdateVisibility();
// Update render order if last SwapInfo flags is true.
renderers[i].UpdateRenderOrder();
// Skip completely non-dirty data.
if (!data[i].IsAnyDirty)
{
continue;
}
// Update visibility.
if (data[i].IsVisibilityDirty)
{
renderers[i].OnDrawableVisiblityDidChange(data[i].IsVisible);
swapMeshes = true;
}
// Update render order.
if (data[i].IsRenderOrderDirty)
{
renderers[i].OnDrawableRenderOrderDidChange(data[i].RenderOrder);
swapMeshes = true;
}
// Update opacity.
if (data[i].IsOpacityDirty)
{
renderers[i].OnDrawableOpacityDidChange(data[i].Opacity);
swapMeshes = true;
}
// Update vertex positions.
if (data[i].AreVertexPositionsDirty)
{
renderers[i].OnDrawableVertexPositionsDidChange(data[i].VertexPositions);
swapMeshes = true;
}
// Swap buffers if necessary.
// [INV] Swapping only half of the meshes might improve performance even. Would that be visually feasible?
if (swapMeshes)
{
renderers[i].SwapMeshes();
}
}
// Pass draw order changes to handler (if available).
var drawOrderHandler = DrawOrderHandlerInterface;
if (drawOrderHandler != null)
{
for (var i = 0; i < data.Length; ++i)
{
if (data[i].IsDrawOrderDirty)
{
drawOrderHandler.OnDrawOrderDidChange(this, drawables[i], data[i].DrawOrder);
}
}
}
}
#endregion
}
}