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