/**
* 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.Rendering.Masking;
using System;
using UnityEngine;
using UnityEngine.Rendering;
namespace Live2D.Cubism.Rendering
{
///
/// Wrapper for drawing s.
///
[ExecuteInEditMode, RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public sealed class CubismRenderer : MonoBehaviour
{
///
/// backing field.
///
[SerializeField, HideInInspector]
private int _localSortingOrder;
///
/// Local sorting order.
///
public int LocalSortingOrder
{
get
{
return _localSortingOrder;
}
set
{
// Return early if same value given.
if (value == _localSortingOrder)
{
return;
}
// Store value.
_localSortingOrder = value;
// Apply it.
ApplySorting();
}
}
///
/// backing field.
///
[SerializeField, HideInInspector]
private Color _color = Color.white;
///
/// Color.
///
public Color Color
{
get { return _color; }
set
{
// Return early if same value given.
if (value == _color)
{
return;
}
// Store value.
_color = value;
// Apply color.
ApplyVertexColors();
}
}
///
/// .
///
public Material Material
{
get
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
return MeshRenderer.sharedMaterial;
}
#endif
return MeshRenderer.material;
}
set
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
MeshRenderer.sharedMaterial = value;
return;
}
#endif
MeshRenderer.material = value;
}
}
///
/// backing field.
///
[SerializeField, HideInInspector]
private Texture2D _mainTexture;
///
/// 's main texture.
///
public Texture2D MainTexture
{
get { return _mainTexture; }
set
{
// Return early if same value given and main texture is valid.
if (value == _mainTexture && _mainTexture != null)
{
return;
}
// Store value.
_mainTexture = (value != null)
? value
: Texture2D.whiteTexture;
// Apply it.
ApplyMainTexture();
}
}
///
/// Meshes.
///
///
/// Double buffering dynamic meshes increases performance on mobile, so we double-buffer them here.
///
private Mesh[] Meshes { get; set; }
///
/// Index of front buffer mesh.
///
private int FrontMesh { get; set; }
///
/// Index of back buffer mesh..
///
private int BackMesh { get; set; }
///
/// .
///
public Mesh Mesh
{
get { return Meshes[FrontMesh]; }
}
///
/// backing field.
///
[NonSerialized]
private MeshFilter _meshFilter;
///
/// .
///
public MeshFilter MeshFilter
{
get
{
return _meshFilter;
}
}
///
/// backing field.
///
[NonSerialized]
private MeshRenderer _meshRenderer;
///
/// .
///
public MeshRenderer MeshRenderer
{
get
{
return _meshRenderer;
}
}
#region Interface For CubismRenderController
///
/// backing field.
///
[SerializeField, HideInInspector]
private CubismSortingMode _sortingMode;
///
/// Sorting mode.
///
private CubismSortingMode SortingMode
{
get { return _sortingMode; }
set { _sortingMode = value; }
}
///
/// backing field.
///
[SerializeField, HideInInspector]
private int _sortingOrder;
///
/// Sorting mode.
///
private int SortingOrder
{
get { return _sortingOrder; }
set { _sortingOrder = value; }
}
///
/// backing field.
///
[SerializeField, HideInInspector]
private int _renderOrder;
///
/// Sorting mode.
///
private int RenderOrder
{
get { return _renderOrder; }
set { _renderOrder = value; }
}
///
/// backing field.
///
[SerializeField, HideInInspector]
private float _depthOffset = 0.00001f;
///
/// Offset to apply in case of depth sorting.
///
private float DepthOffset
{
get { return _depthOffset; }
set { _depthOffset = value; }
}
///
/// backing field.
///
[SerializeField, HideInInspector]
private float _opacity;
///
/// Opacity.
///
private float Opacity
{
get { return _opacity; }
set { _opacity = value; }
}
///
/// Buffer for vertex colors.
///
private Color[] VertexColors { get; set; }
///
/// Allows tracking of what vertex data was updated last swap.
///
private SwapInfo LastSwap { get; set; }
///
/// Allows tracking of what vertex data will be swapped.
///
private SwapInfo ThisSwap { get; set; }
///
/// Swaps mesh buffers.
///
///
/// Make sure to manually call this method in case you changed the .
///
public void SwapMeshes()
{
// Perform internal swap.
BackMesh = FrontMesh;
FrontMesh = (FrontMesh == 0) ? 1 : 0;
var mesh = Meshes[FrontMesh];
// Update colors.
Meshes[BackMesh].colors = VertexColors;
// Update swap info.
LastSwap = ThisSwap;
ResetSwapInfoFlags();
// Apply swap.
#if UNITY_EDITOR
if (!Application.isPlaying)
{
MeshFilter.mesh = mesh;
return;
}
#endif
MeshFilter.mesh = mesh;
}
///
/// Updates visibility.
///
public void UpdateVisibility()
{
if (LastSwap.DidBecomeVisible)
{
MeshRenderer.enabled = true;
}
else if (LastSwap.DidBecomeInvisible)
{
MeshRenderer.enabled = false;
}
ResetVisibilityFlags();
}
///
/// Updates render order.
///
public void UpdateRenderOrder()
{
if (LastSwap.NewRenderOrder)
{
ApplySorting();
}
ResetRenderOrderFlag();
}
///
/// Updates sorting layer.
///
/// New sorting layer.
internal void OnControllerSortingLayerDidChange(int newSortingLayer)
{
MeshRenderer.sortingLayerID = newSortingLayer;
}
///
/// Updates sorting mode.
///
/// New sorting mode.
internal void OnControllerSortingModeDidChange(CubismSortingMode newSortingMode)
{
SortingMode = newSortingMode;
ApplySorting();
}
///
/// Updates sorting order.
///
/// New sorting order.
internal void OnControllerSortingOrderDidChange(int newSortingOrder)
{
SortingOrder = newSortingOrder;
ApplySorting();
}
///
/// Updates depth offset.
///
///
internal void OnControllerDepthOffsetDidChange(float newDepthOffset)
{
DepthOffset = newDepthOffset;
ApplySorting();
}
///
/// Sets the opacity.
///
/// New opacity.
internal void OnDrawableOpacityDidChange(float newOpacity)
{
Opacity = newOpacity;
ApplyVertexColors();
}
///
/// Updates render order.
///
/// New render order.
internal void OnDrawableRenderOrderDidChange(int newRenderOrder)
{
RenderOrder = newRenderOrder;
SetNewRenderOrder();
}
///
/// Sets the .
///
/// Vertex positions to set.
internal void OnDrawableVertexPositionsDidChange(Vector3[] newVertexPositions)
{
var mesh = Mesh;
// Apply positions and update bounds.
mesh.vertices = newVertexPositions;
mesh.RecalculateBounds();
// Set swap flag.
SetNewVertexPositions();
}
///
/// Sets visibility.
///
/// New visibility.
internal void OnDrawableVisiblityDidChange(bool newVisibility)
{
// Set swap flag if visible.
if (newVisibility)
{
BecomeVisible();
}
else
{
BecomeInvisible();
}
}
///
/// Sets mask properties.
///
/// Value to set.
internal void OnMaskPropertiesDidChange(CubismMaskProperties newMaskProperties)
{
MeshRenderer.GetPropertyBlock(SharedPropertyBlock);
// Write properties.
SharedPropertyBlock.SetTexture(CubismShaderVariables.MaskTexture, newMaskProperties.Texture);
SharedPropertyBlock.SetVector(CubismShaderVariables.MaskTile, newMaskProperties.Tile);
SharedPropertyBlock.SetVector(CubismShaderVariables.MaskTransform, newMaskProperties.Transform);
MeshRenderer.SetPropertyBlock(SharedPropertyBlock);
}
///
/// Sets model opacity.
///
/// Opacity to set.
internal void OnModelOpacityDidChange(float newModelOpacity)
{
_meshRenderer.GetPropertyBlock(SharedPropertyBlock);
// Write property.
SharedPropertyBlock.SetFloat(CubismShaderVariables.ModelOpacity, newModelOpacity);
MeshRenderer.SetPropertyBlock(SharedPropertyBlock);
}
#endregion
///
/// backing field.
///
private static MaterialPropertyBlock _sharedPropertyBlock;
///
/// that can be shared on the main script thread.
///
private static MaterialPropertyBlock SharedPropertyBlock
{
get
{
// Lazily initialize.
if (_sharedPropertyBlock == null)
{
_sharedPropertyBlock = new MaterialPropertyBlock();
}
return _sharedPropertyBlock;
}
}
///
/// Applies main texture for rendering.
///
private void ApplyMainTexture()
{
MeshRenderer.GetPropertyBlock(SharedPropertyBlock);
// Write property.
SharedPropertyBlock.SetTexture(CubismShaderVariables.MainTexture, MainTexture);
MeshRenderer.SetPropertyBlock(SharedPropertyBlock);
}
///
/// Applies sorting.
///
private void ApplySorting()
{
// Sort by order.
if (SortingMode.SortByOrder())
{
MeshRenderer.sortingOrder = SortingOrder + ((SortingMode == CubismSortingMode.BackToFrontOrder)
? (RenderOrder + LocalSortingOrder)
: -(RenderOrder + LocalSortingOrder));
transform.localPosition = Vector3.zero;
return;
}
// Sort by depth.
var offset = (SortingMode == CubismSortingMode.BackToFrontZ)
? -DepthOffset
: DepthOffset;
MeshRenderer.sortingOrder = SortingOrder + LocalSortingOrder;
transform.localPosition = new Vector3(0f, 0f, RenderOrder * offset);
}
///
/// Uploads mesh vertex colors.
///
public void ApplyVertexColors()
{
var vertexColors = VertexColors;
var color = Color;
color.a *= Opacity;
for (var i = 0; i < vertexColors.Length; ++i)
{
vertexColors[i] = color;
}
// Set swap flag.
SetNewVertexColors();
}
///
/// Initializes the mesh renderer.
///
private void TryInitializeMeshRenderer()
{
if (_meshRenderer == null)
{
_meshRenderer = GetComponent();
// Lazily add component.
if (_meshRenderer == null)
{
_meshRenderer = gameObject.AddComponent();
_meshRenderer.hideFlags = HideFlags.HideInInspector;
_meshRenderer.receiveShadows = false;
_meshRenderer.shadowCastingMode = ShadowCastingMode.Off;
_meshRenderer.lightProbeUsage = LightProbeUsage.BlendProbes;
}
}
}
///
/// Initializes the mesh filter.
///
private void TryInitializeMeshFilter()
{
if (_meshFilter == null)
{
_meshFilter = GetComponent();
// Lazily add component.
if (_meshFilter == null)
{
_meshFilter = gameObject.AddComponent();
_meshFilter.hideFlags = HideFlags.HideInInspector;
}
}
}
///
/// Initializes the mesh if necessary.
///
private void TryInitializeMesh()
{
// Only create mesh if necessary.
// HACK 'Mesh.vertex > 0' makes sure mesh is recreated in case of runtime instantiation.
if (Meshes != null && Mesh.vertexCount > 0)
{
return;
}
// Create mesh for attached drawable.
var drawable = GetComponent();
if (Meshes == null)
{
Meshes = new Mesh[2];
}
for (var i = 0; i < 2; ++i)
{
var mesh = new Mesh
{
name = drawable.name,
vertices = drawable.VertexPositions,
uv = drawable.VertexUvs,
triangles = drawable.Indices
};
mesh.MarkDynamic();
mesh.RecalculateBounds();
// Store mesh.
Meshes[i] = mesh;
}
}
///
/// Initializes vertex colors.
///
private void TryInitializeVertexColor()
{
var mesh = Mesh;
VertexColors = new Color[mesh.vertexCount];
for (var i = 0; i < VertexColors.Length; ++i)
{
VertexColors[i] = Color;
VertexColors[i].a *= Opacity;
}
}
///
/// Initializes the main texture if possible.
///
private void TryInitializeMainTexture()
{
if (MainTexture == null)
{
MainTexture = null;
}
ApplyMainTexture();
}
///
/// Initializes components if possible.
///
public void TryInitialize()
{
TryInitializeMeshRenderer();
TryInitializeMeshFilter();
TryInitializeMesh();
TryInitializeVertexColor();
TryInitializeMainTexture();
ApplySorting();
}
#region Swap Info
///
/// Sets .
///
private void SetNewVertexPositions()
{
var swapInfo = ThisSwap;
swapInfo.NewVertexPositions = true;
ThisSwap = swapInfo;
}
///
/// Sets .
///
private void SetNewVertexColors()
{
var swapInfo = ThisSwap;
swapInfo.NewVertexColors = true;
ThisSwap = swapInfo;
}
///
/// Sets on visible.
///
private void BecomeVisible()
{
var swapInfo = ThisSwap;
swapInfo.DidBecomeVisible = true;
ThisSwap = swapInfo;
}
///
/// Sets on invisible.
///
private void BecomeInvisible()
{
var swapInfo = ThisSwap;
swapInfo.DidBecomeInvisible = true;
ThisSwap = swapInfo;
}
///
/// Sets .
///
private void SetNewRenderOrder()
{
var swapInfo = ThisSwap;
swapInfo.NewRenderOrder = true;
ThisSwap = swapInfo;
}
///
/// Resets flags.
///
private void ResetSwapInfoFlags()
{
var swapInfo = ThisSwap;
swapInfo.NewVertexColors = false;
swapInfo.NewVertexPositions = false;
swapInfo.DidBecomeVisible = false;
swapInfo.DidBecomeInvisible = false;
ThisSwap = swapInfo;
}
///
/// Reset visibility flags.
///
private void ResetVisibilityFlags()
{
var swapInfo = LastSwap;
swapInfo.DidBecomeVisible = false;
swapInfo.DidBecomeInvisible = false;
LastSwap = swapInfo;
}
///
/// Reset render order flag.
///
private void ResetRenderOrderFlag()
{
var swapInfo = LastSwap;
swapInfo.NewRenderOrder = false;
LastSwap = swapInfo;
}
///
/// Allows tracking of data changed on a swap.
///
private struct SwapInfo
{
///
/// Vertex positions were changed.
///
public bool NewVertexPositions { get; set; }
///
/// Vertex colors were changed.
///
public bool NewVertexColors { get; set; }
///
/// Visibility were changed to visible.
///
public bool DidBecomeVisible { get; set; }
///
/// Visibility were changed to invisible.
///
public bool DidBecomeInvisible { get; set; }
///
/// Render order were changed.
///
public bool NewRenderOrder { get; set; }
}
#endregion
#region Unity Events Handling
///
/// Finalizes instance.
///
private void OnDestroy()
{
if (Meshes == null)
{
return;
}
for (var i = 0; i < Meshes.Length; i++)
{
DestroyImmediate(Meshes[i]);
}
}
#endregion
}
}