/** * 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 System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; namespace Live2D.Cubism.Rendering.Masking { /// /// Texture for rendering masks. /// [CreateAssetMenu(menuName = "Live2D Cubism/Mask Texture")] public sealed class CubismMaskTexture : ScriptableObject, ICubismMaskCommandSource { #region Conversion /// /// Converts a to a . /// /// Value to convert. public static implicit operator Texture(CubismMaskTexture value) { return value.RenderTexture; } #endregion /// /// The global mask texture. /// public static CubismMaskTexture GlobalMaskTexture { get { return Resources.Load("Live2D/Cubism/GlobalMaskTexture"); } } /// /// backing field. /// [SerializeField, HideInInspector] private int _size = 1024; /// /// Texture size in pixels. /// public int Size { get { return _size; } set { // Return early if same value given. if (value == _size) { return; } // Fail silently if not power-of-two. if (!value.IsPowerOfTwo()) { return; } // Apply changes. _size = value; RefreshRenderTexture(); } } /// /// Channel count. /// public int Channels { get { return 4; } } /// /// backing field. /// [SerializeField, HideInInspector] private int _subdivisions = 3; /// /// Subdivision level. /// public int Subdivisions { get { return _subdivisions; } set { if (value == _subdivisions) { return; } // Apply changes. _subdivisions = value; RefreshRenderTexture(); } } /// /// Tile pool 'allocator'. /// private CubismMaskTilePool TilePool { get; set; } /// /// backing field. /// private RenderTexture _renderTexture; /// /// to draw on. /// private RenderTexture RenderTexture { get { if (_renderTexture == null) { RefreshRenderTexture(); } return _renderTexture; } set { _renderTexture = value; } } /// /// Sources. /// private List Sources { get; set; } /// /// True if instance is revived. /// private bool IsRevived { get { return TilePool != null; } } /// /// True if instance contains any sources. /// private bool ContainsSources { get { return Sources != null && Sources.Count > 0; } } #region Interface For ICubismMaskSources /// /// Add source of masks for drawing. /// public void AddSource(ICubismMaskTextureCommandSource source) { // Make sure instance is valid. TryRevive(); // Initialize container if necessary. if (Sources == null) { Sources = new List(); } // Return early if source already exists. else if (Sources.FindIndex(i => i.Source == source) != -1) { return; } // Register source. var item = new SourcesItem { Source = source, Tiles = TilePool.AcquireTiles(source.GetNecessaryTileCount()) }; Sources.Add(item); // Apply tiles to source. source.SetTiles(item.Tiles); } /// /// Remove source of masks /// public void RemoveSource(ICubismMaskTextureCommandSource source) { // Return early if empty. if (!ContainsSources) { return; } var itemIndex = Sources.FindIndex(i => i.Source == source); // Return if source is invalid. if (itemIndex == -1) { return; } // Return tiles and deregister source. TilePool.ReturnTiles(Sources[itemIndex].Tiles); Sources.RemoveAt(itemIndex); } #endregion private void TryRevive() { // Return early if already revived. if (IsRevived) { return; } RefreshRenderTexture(); } private void ReinitializeSources() { // Reallocate tiles if sources exist. if (ContainsSources) { for (var i = 0; i < Sources.Count; ++i) { var source = Sources[i]; source.Tiles = TilePool.AcquireTiles(source.Source.GetNecessaryTileCount()); source.Source.SetTiles(source.Tiles); Sources[i] = source; } } } private void RefreshRenderTexture() { // Recreate render texture. RenderTexture = new RenderTexture(Size, Size, 0, RenderTextureFormat.ARGB32); // Recreate allocator. TilePool = new CubismMaskTilePool(Subdivisions, Channels); // Reinitialize sources. ReinitializeSources(); } #region Unity Event Handling /// /// Initializes instance. /// // ReSharper disable once UnusedMember.Local private void OnEnable() { CubismMaskCommandBuffer.AddSource(this); } /// /// Finalizes instance. /// // ReSharper disable once UnusedMember.Local private void OnDestroy() { CubismMaskCommandBuffer.RemoveSource(this); } #endregion #region ICubismMaskCommandSource /// /// Called to enqueue source. /// /// Buffer to enqueue in. void ICubismMaskCommandSource.AddToCommandBuffer(CommandBuffer buffer) { // Return early if empty. if (!ContainsSources) { return; } // Enqueue render target. buffer.SetRenderTarget(RenderTexture); buffer.ClearRenderTarget(false, true, Color.clear); // Enqueue sources. for (var i = 0; i < Sources.Count; ++i) { Sources[i].Source.AddToCommandBuffer(buffer); } } #endregion #region Source Item /// /// Source of masks and its tiles /// private struct SourcesItem { /// /// SourcesItem instance. /// public ICubismMaskTextureCommandSource Source; /// /// Tiles assigned to the instance. /// public CubismMaskTile[] Tiles; } #endregion } }