/** * 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 } }