/** * 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 System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; namespace Live2D.Cubism.Rendering.Masking { /// /// Controls rendering of Cubism masks. /// [ExecuteInEditMode, CubismDontMoveOnReimport] public sealed class CubismMaskController : MonoBehaviour, ICubismMaskTextureCommandSource, ICubismUpdatable { /// /// backing field. /// [SerializeField, HideInInspector] private CubismMaskTexture _maskTexture; /// /// Mask texture. /// public CubismMaskTexture MaskTexture { get { // Fall back to global mask texture. if (_maskTexture == null) { _maskTexture = CubismMaskTexture.GlobalMaskTexture; } return _maskTexture; } set { // Return early if same value given. if (value == _maskTexture) { return; } _maskTexture = value; // Try switch mask textures. OnDestroy(); Start(); } } /// /// s. /// private CubismMaskMaskedJunction[] Junctions { get; set; } /// /// True if controller is revived. /// private bool IsRevived { get { return Junctions != null; } } /// /// Model has update controller component. /// [HideInInspector] public bool HasUpdateController { get; set; } /// /// Makes sure controller is initialized once. /// private void TryRevive() { if (IsRevived) { return; } ForceRevive(); } /// /// Initializes . /// private void ForceRevive() { var drawables = this .FindCubismModel() .Drawables; // Find mask pairs. var pairs = new MasksMaskedsPairs(); for (var i = 0; i < drawables.Length; i++) { if (!drawables[i].IsMasked) { continue; } // Make sure no leftover null-entries are added as mask. var masks = Array.FindAll(drawables[i].Masks, mask => mask != null); if (masks.Length == 0) { continue; } pairs.Add(drawables[i], masks); } // Initialize junctions. Junctions = new CubismMaskMaskedJunction[pairs.Entries.Count]; for (var i = 0; i < Junctions.Length; ++i) { // Create mask renderers for junction. var masks = new CubismMaskRenderer[pairs.Entries[i].Masks.Length]; for (var j = 0; j < masks.Length; ++j) { masks[j] = new CubismMaskRenderer() .SetMainRenderer(pairs.Entries[i].Masks[j]); } // Create junction. Junctions[i] = new CubismMaskMaskedJunction() .SetMasks(masks) .SetMaskeds(pairs.Entries[i].Maskeds.ToArray()) .SetMaskTexture(MaskTexture); } } /// /// Called by cubism update controller. Order to invoke OnLateUpdate. /// public int ExecutionOrder { get { return CubismUpdateExecutionOrder.CubismMaskController; } } /// /// Called by cubism update controller. Needs to invoke OnLateUpdate on Editing. /// public bool NeedsUpdateOnEditing { get { return true; } } /// /// Called by cubism update controller. Updates . /// public void OnLateUpdate() { if (!enabled || !IsRevived) { return; } for (var i = 0; i < Junctions.Length; ++i) { Junctions[i].Update(); } } #region Unity Event Handling /// /// Initializes instance. /// private void Start() { // Fail silently. if (MaskTexture == null) { return; } MaskTexture.AddSource(this); // Get cubism update controller. HasUpdateController = (GetComponent() != null); } /// /// Called by Unity. /// private void LateUpdate() { if(!HasUpdateController) { OnLateUpdate(); } } /// /// Finalizes instance. /// private void OnDestroy() { if (MaskTexture == null) { return; } MaskTexture.RemoveSource(this); } #endregion #region ICubismMaskDrawSource /// /// Queries the number of tiles needed by the source. /// /// The necessary number of tiles needed. int ICubismMaskTextureCommandSource.GetNecessaryTileCount() { TryRevive(); return Junctions.Length; } /// /// Assigns the tiles. /// /// Tiles to assign. void ICubismMaskTextureCommandSource.SetTiles(CubismMaskTile[] value) { for (var i = 0; i < Junctions.Length; ++i) { Junctions[i].SetMaskTile(value[i]); } } /// /// Called when source should instantly draw. /// void ICubismMaskCommandSource.AddToCommandBuffer(CommandBuffer buffer) { for (var i = 0; i < Junctions.Length; ++i) { Junctions[i].AddToCommandBuffer(buffer); } } #endregion #region Mask-Masked Pair /// /// Pair of masks and masked drawables. /// private struct MasksMaskedsPair { /// /// Mask drawables. /// public CubismRenderer[] Masks; /// /// Masked drawables. /// public List Maskeds; } private class MasksMaskedsPairs { /// /// List of /// public List Entries = new List(); /// /// Add to the list. /// public void Add(CubismDrawable masked, CubismDrawable[] masks) { // Try to add masked to existing mask compound. for (var i = 0; i < Entries.Count; ++i) { var match = (Entries[i].Masks.Length == masks.Length); if (!match) { continue; } for (var j = 0; j < Entries[i].Masks.Length; ++j) { if (Entries[i].Masks[j] != masks[j].GetComponent()) { match = false; break; } } if (!match) { continue; } Entries[i].Maskeds.Add(masked.GetComponent()); return; } // Create new pair. var renderers = new CubismRenderer[masks.Length]; for (var i = 0; i < masks.Length; ++i) { renderers[i] = masks[i].GetComponent(); } Entries.Add(new MasksMaskedsPair { Masks = renderers, Maskeds = new List() { masked.GetComponent() } }); } } #endregion } }