/**
* 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 Live2D.Cubism.Rendering.URP.RenderingInterceptor;
using System;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Live2D.Cubism.Rendering
{
///
/// Controls rendering of a .
///
[ExecuteInEditMode, CubismDontMoveOnReimport]
public sealed partial 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; }
}
///
/// backing field.
///
[SerializeField, HideInInspector]
private bool _isOverriddenModelMultiplyColors;
///
/// Whether to override with multiply color from the model.
///
public bool MultiplyColorEnabled
{
get { return _isOverriddenModelMultiplyColors; }
set { _isOverriddenModelMultiplyColors = value; }
}
///
/// backing field.
///
[SerializeField, HideInInspector]
private bool _isOverriddenModelScreenColors;
///
/// Whether to override with screen color from the model.
///
public bool ScreenColorEnabled
{
get { return _isOverriddenModelScreenColors; }
set { _isOverriddenModelScreenColors = value; }
}
///
/// backing field.
///
[SerializeField, HideInInspector]
private Color _modelMultiplyColor;
///
/// Multiply colors used throughout the model.
///
public Color ModelMultiplyColor
{
get { return _modelMultiplyColor; }
set { _modelMultiplyColor = value; }
}
///
/// backing field.
///
[SerializeField, HideInInspector]
private Color _modelScreenColor;
///
/// Screen colors used throughout the model.
///
public Color ModelScreenColor
{
get { return _modelScreenColor; }
set { _modelScreenColor = 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);
}
SortRenderers();
}
}
///
/// 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);
}
SortRenderers();
}
}
///
/// 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);
}
SortRenderers();
}
}
///
/// [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;
}
}
///
/// backing field.
///
[SerializeField, HideInInspector]
private Object _multiplyColorHandler;
///
/// Opacity handler proxy object.
///
public Object MultiplyColorHandler
{
get { return _multiplyColorHandler; }
set { _multiplyColorHandler = value.ToNullUnlessImplementsInterface(); }
}
///
/// backing field.
///
private ICubismBlendColorHandler _multiplyColorHandlerInterface;
///
/// Listener for blend color changes.
///
private ICubismBlendColorHandler MultiplyColorHandlerInterface
{
get
{
if (_multiplyColorHandlerInterface == null)
{
_multiplyColorHandlerInterface = MultiplyColorHandler?.GetInterface();
}
return _multiplyColorHandlerInterface;
}
}
///
/// backing field.
///
[SerializeField, HideInInspector]
private Object _screenColorHandler;
///
/// Screen color handler proxy object.
///
public Object ScreenColorHandler
{
get { return _screenColorHandler; }
set { _screenColorHandler = value.ToNullUnlessImplementsInterface(); }
}
///
/// backing field.
///
private ICubismBlendColorHandler _screenColorHandlerInterface;
///
/// Listener for blend color changes.
///
private ICubismBlendColorHandler ScreenColorHandlerInterface
{
get
{
if (_screenColorHandlerInterface == null)
{
_screenColorHandlerInterface = ScreenColorHandler?.GetInterface();
}
return _screenColorHandlerInterface;
}
}
///
/// The value to offset the s by.
///
///
/// You only need to adjust this value when using perspective cameras.
///
[SerializeField, HideInInspector]
private 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);
}
}
}
///
/// 's backing field.
///
[NonSerialized]
private CubismModel _cubismModel;
///
/// Model the controller belongs to.
///
public CubismModel Model
{
get
{
if (_cubismModel == null)
{
_cubismModel = this.FindCubismModel();
}
return _cubismModel;
}
}
///
/// 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.
///
[SerializeField]
private CubismRenderer[] _renderers;
///
/// s.
///
public CubismRenderer[] Renderers
{
get
{
if (_renderers == null)
{
TryInitialize();
}
return _renderers;
}
private set { _renderers = value; }
}
///
/// multiply color buffer.
///
private Color[] _newMultiplyColors;
///
/// screen color buffer.
///
private Color[] _newScreenColors;
///
/// Model has update controller component.
///
[HideInInspector]
public bool HasUpdateController { get; set; }
///
/// s backing field.
///
private bool _isInitialized = false;
///
/// Is renderers initialized.
///
[HideInInspector]
public bool IsInitialized
{
get
{
return _isInitialized;
}
private set
{
_isInitialized = value;
}
}
///
/// Makes sure all s have s attached to them.
///
public void TryInitialize()
{
// If the number of drawables has changed,
// discard the cached renderers so they are rebuilt to match the new drawable count.
if (_renderers != null && Model && Model.Drawables != null)
{
var newCount = Model.Drawables.Length;
if (Model.Offscreens != null)
{
newCount += Model.Offscreens.Length;
}
if (_renderers.Length != newCount)
{
_renderers = null;
}
}
// Try to get renderers.
var renderers = _renderers;
TryInitializeRenderers(renderers);
if (_renderers == null
|| _renderers.Length < 1)
{
return;
}
// Make sure renderers are initialized.
for (var i = 0; i < Renderers.Length; ++i)
{
var targetRenderer = Renderers[i];
targetRenderer.TryInitialize(this);
if (!HasRootPartOffscreen)
{
continue;
}
HasRootPartOffscreen = CheckHasRootPartOffscreen(targetRenderer);
}
// 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;
OnAfterRenderersInitialize(Renderers);
IsInitialized = true;
}
///
/// 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 && Renderers != null)
{
var renderers = Renderers;
for (var i = 0; i < renderers.Length; ++i)
{
renderers[i].OnModelOpacityDidChange(Opacity);
}
}
// Call handler.
if (OpacityHandlerInterface != null)
{
OpacityHandlerInterface.OnOpacityDidChange(this, Opacity);
}
}
///
/// Updates Blend Colors if necessary.
///
private void UpdateDrawableBlendColors()
{
if (Renderers == null
|| !IsInitialized)
{
return;
}
var isMultiplyColorUpdated = false;
var isScreenColorUpdated = false;
if ((_newMultiplyColors?.Length ?? 0) != Renderers.Length)
{
_newMultiplyColors = new Color[Renderers.Length];
}
if ((_newScreenColors?.Length ?? 0) != Renderers.Length)
{
_newScreenColors = new Color[Renderers.Length];
}
for (var i = 0; i < Renderers.Length; i++)
{
if (!Renderers[i])
{
continue;
}
var isUseUserMultiplyColor = (Renderers[i].DrawObjectMultiplyColorEnabled ||
MultiplyColorEnabled);
if (isUseUserMultiplyColor)
{
// If you switch from a setting that uses the color of the model, revert to the color that was retained.
if (!Renderers[i].LastIsUseUserMultiplyColor)
{
Renderers[i].MultiplyColor = Renderers[i].LastMultiplyColor;
Renderers[i].ApplyMultiplyColor();
isMultiplyColorUpdated = true;
}
else if (Renderers[i].LastMultiplyColor != Renderers[i].MultiplyColor)
{
Renderers[i].ApplyMultiplyColor();
isMultiplyColorUpdated = true;
}
Renderers[i].LastMultiplyColor = Renderers[i].MultiplyColor;
}
else if (Renderers[i].LastIsUseUserMultiplyColor)
{
Renderers[i].MultiplyColor = Renderers[i].LastMultiplyColor;
Renderers[i].ApplyMultiplyColor();
isMultiplyColorUpdated = true;
}
_newMultiplyColors[i] = Renderers[i].MultiplyColor;
Renderers[i].LastIsUseUserMultiplyColor = isUseUserMultiplyColor;
var isUseUserScreenColor = (Renderers[i].DrawObjectScreenColorEnabled ||
ScreenColorEnabled);
if (isUseUserScreenColor)
{
// If you switch from a setting that uses the color of the model, revert to the color that was retained.
if (!Renderers[i].LastIsUseUserScreenColors)
{
Renderers[i].ScreenColor = Renderers[i].LastScreenColor;
Renderers[i].ApplyScreenColor();
isScreenColorUpdated = true;
}
else if (Renderers[i].LastScreenColor != Renderers[i].ScreenColor)
{
Renderers[i].ApplyScreenColor();
isScreenColorUpdated = true;
}
Renderers[i].LastScreenColor = Renderers[i].ScreenColor;
}
else if (Renderers[i].LastIsUseUserScreenColors)
{
Renderers[i].ScreenColor = Renderers[i].LastScreenColor;
Renderers[i].ApplyScreenColor();
isScreenColorUpdated = true;
}
_newScreenColors[i] = Renderers[i].ScreenColor;
Renderers[i].LastIsUseUserScreenColors = isUseUserScreenColor;
}
if (MultiplyColorHandler != null && isMultiplyColorUpdated)
{
MultiplyColorHandlerInterface.OnBlendColorDidChange(this, _newMultiplyColors);
}
if (ScreenColorHandler != null && isScreenColorUpdated)
{
ScreenColorHandlerInterface.OnBlendColorDidChange(this, _newScreenColors);
}
}
///
/// Updates from direction changes.
///
internal void UpdateDidChangeSortingFromZ(Vector3 cameraPosition)
{
// Return early if not sorting by depth.
if (!SortingMode.SortByDepth())
{
return;
}
for (var i = 0; i < Renderers?.Length; i++)
{
var cubismRenderer = Renderers[i];
if (!cubismRenderer)
{
continue;
}
// Check if direction updated from last sorted.
DidChangeSorting |= cubismRenderer.DidUpdateDirectionFromLastSorted(cameraPosition);
}
}
///
/// 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();
// Updates Blend Colors if necessary.
UpdateDrawableBlendColors();
// 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)
{
return;
}
CurrentOffscreenUnmanagedIndex = -1;
// Make sure renderers are available.
if (!IsInitialized)
{
Model.Revive();
TryInitialize();
}
// Register listener.
Model.OnDynamicDrawableData += OnDynamicDrawableData;
#if UNITY_EDITOR
if (!Application.isPlaying)
{
Model.ForceUpdateNow();
for (var drawableIndex = 0; drawableIndex < DrawableRenderers.Length; drawableIndex++)
{
DrawableRenderers[drawableIndex].SwapMeshes();
}
}
#endif
if (GetComponent() != null)
{
// Do not register at common controller when a rendering interceptor is attached.
return;
}
// Register at common controller.
CubismRenderControllerGroup.GetInstance().AddRenderController(this);
}
///
/// Called by Unity. Disables listening to render data updates.
///
private void OnDisable()
{
// Fail silently.
if (!Model)
{
return;
}
// Deregister listener.
Model.OnDynamicDrawableData -= OnDynamicDrawableData;
// Deregister at common controller.
CubismRenderControllerGroup.GetInstance().RemoveRenderController(this);
}
#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 = DrawableRenderers;
// Handle render data changes.
for (var dataIndex = 0; dataIndex < data.Length; ++dataIndex)
{
var rendererIndex = Array.FindIndex(renderers, cubismRenderer => cubismRenderer.Drawable.UnmanagedIndex == dataIndex);
// Skip if no renderer found.
if (rendererIndex < 0) {
continue;
}
// Controls whether mesh buffers are to be swapped.
var swapMeshes = false;
// Update visibility if last SwapInfo flag is true.
renderers[rendererIndex].UpdateVisibility();
// Update render order if last SwapInfo flags is true.
renderers[rendererIndex].UpdateRenderOrder();
// Skip completely non-dirty data.
if (!data[dataIndex].IsAnyDirty)
{
continue;
}
// Update visibility.
if (data[dataIndex].IsVisibilityDirty)
{
renderers[rendererIndex].OnDrawableVisiblityDidChange(data[dataIndex].IsVisible);
swapMeshes = true;
}
// Update render order.
if (data[dataIndex].IsRenderOrderDirty)
{
renderers[rendererIndex].OnDrawableRenderOrderDidChange(data[dataIndex].RenderOrder);
DidChangeDrawableRenderOrder = true;
swapMeshes = true;
}
// Update opacity.
if (data[dataIndex].IsOpacityDirty)
{
renderers[rendererIndex].OnDrawableOpacityDidChange(data[dataIndex].Opacity);
swapMeshes = true;
}
// Update vertex positions.
if (data[dataIndex].AreVertexPositionsDirty)
{
renderers[rendererIndex].OnDrawableVertexPositionsDidChange(data[dataIndex].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[rendererIndex].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);
}
}
}
var isMultiplyColorUpdated = false;
var isScreenColorUpdated = false;
_newMultiplyColors ??= new Color[renderers.Length];
_newScreenColors ??= new Color[renderers.Length];
var newMultiplyColors = _newMultiplyColors;
var newScreenColors = _newScreenColors;
for (var dataIndex = 0; dataIndex < data.Length; ++dataIndex)
{
var rendererIndex = Array.FindIndex(renderers, cubismRenderer => cubismRenderer.Drawable.UnmanagedIndex == dataIndex);
// Skip if no renderer found.
if (rendererIndex < 0)
{
continue;
}
var isUseModelMultiplyColor = !(renderers[rendererIndex].DrawObjectMultiplyColorEnabled ||
MultiplyColorEnabled);
// Skip processing when not using model colors.
if (data[dataIndex].IsBlendColorDirty && isUseModelMultiplyColor)
{
renderers[rendererIndex].ApplyMultiplyColor();
isMultiplyColorUpdated = true;
}
newMultiplyColors[rendererIndex] = renderers[rendererIndex].MultiplyColor;
}
for (var dataIndex = 0; dataIndex < data.Length; ++dataIndex)
{
var rendererIndex = Array.FindIndex(renderers, cubismRenderer => cubismRenderer.Drawable.UnmanagedIndex == dataIndex);
// Skip if no renderer found.
if (rendererIndex < 0)
{
continue;
}
var isUseModelScreenColor = !(renderers[rendererIndex].DrawObjectScreenColorEnabled ||
ScreenColorEnabled);
// Skip processing when not using model colors.
if (data[dataIndex].IsBlendColorDirty && isUseModelScreenColor)
{
renderers[rendererIndex].ApplyScreenColor();
isScreenColorUpdated = true;
}
newScreenColors[rendererIndex] = renderers[rendererIndex].ScreenColor;
}
// Pass blend color changes to handler (if available).
var multiplyColorHandlerInterface = MultiplyColorHandlerInterface;
var screenColorHandlerInterface = ScreenColorHandlerInterface;
if (MultiplyColorHandler != null && isMultiplyColorUpdated)
{
multiplyColorHandlerInterface.OnBlendColorDidChange(this, newMultiplyColors);
}
if (ScreenColorHandler != null && isScreenColorUpdated)
{
screenColorHandlerInterface.OnBlendColorDidChange(this, newScreenColors);
}
}
#endregion
}
}