449 lines
14 KiB
C#
449 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using Light2D;
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
|
|
namespace Light2D
|
|
{
|
|
/// <summary>
|
|
/// Custom sprite wich uses MeshFilter and MeshRenderer to render.
|
|
/// Main improvement from Unity SpriteRenderer is that you can access and modify mesh.
|
|
/// Also multiple CustomSprites could be merged to single mesh with MeshCombiner,
|
|
/// which gives much better performance for small meshes than StaticBatchingUtility.Combine.
|
|
/// </summary>
|
|
[ExecuteInEditMode]
|
|
public class CustomSprite : MonoBehaviour
|
|
{
|
|
/// <summary>
|
|
/// Vertex color of mesh.
|
|
/// </summary>
|
|
public Color Color = Color.white;
|
|
|
|
/// <summary>
|
|
/// Sprite from which mesh will be generated.
|
|
/// </summary>
|
|
public Sprite Sprite;
|
|
|
|
/// <summary>
|
|
/// Sorting order of MeshRenderer.
|
|
/// </summary>
|
|
public int SortingOrder;
|
|
|
|
/// <summary>
|
|
/// Material to be used.
|
|
/// </summary>
|
|
public Material Material;
|
|
|
|
// mesh data
|
|
protected Color[] _colors;
|
|
protected Vector2[] _uv0;
|
|
protected Vector2[] _uv1;
|
|
protected Vector3[] _vertices;
|
|
protected int[] _triangles;
|
|
protected Vector4[] _tangents;
|
|
|
|
protected bool _isMeshDirty = false;
|
|
protected MeshRenderer _meshRenderer;
|
|
protected MeshFilter _meshFilter;
|
|
protected Mesh _mesh;
|
|
private Color _oldColor;
|
|
private Sprite _oldSprite;
|
|
private Material _oldMaterial;
|
|
private MaterialKey _oldMaterialKey;
|
|
public static Dictionary<MaterialKey, MaterialValue> MaterialMap = new Dictionary<MaterialKey, MaterialValue>();
|
|
private const string GeneratedMaterialName = "Generated Material (DONT change it)";
|
|
private const string GeneratedMeshName = "Generated Mesh (DONT change it)";
|
|
|
|
public bool RendererEnabled { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Is that sprite is staticaly batched?
|
|
/// </summary>
|
|
public bool IsPartOfStaticBatch
|
|
{
|
|
get { return _meshRenderer.isPartOfStaticBatch; }
|
|
}
|
|
|
|
protected virtual void OnEnable()
|
|
{
|
|
_colors = new Color[4];
|
|
_uv1 = new Vector2[4];
|
|
_uv0 = new Vector2[4];
|
|
_vertices = new Vector3[4];
|
|
_triangles = new[] {2, 1, 0, 1, 2, 3};
|
|
_meshRenderer = GetComponent<MeshRenderer>();
|
|
_meshFilter = GetComponent<MeshFilter>();
|
|
|
|
if (_meshRenderer == null)
|
|
{
|
|
_meshRenderer = gameObject.AddComponent<MeshRenderer>();
|
|
_meshRenderer.receiveShadows = false;
|
|
_meshRenderer.shadowCastingMode = ShadowCastingMode.Off;
|
|
}
|
|
|
|
if (_meshFilter == null)
|
|
{
|
|
_meshFilter = gameObject.AddComponent<MeshFilter>();
|
|
}
|
|
|
|
|
|
if (Material == null)
|
|
{
|
|
#if UNITY_EDITOR
|
|
Material = AssetDatabase.GetBuiltinExtraResource<Material>("Sprites-Default.mat");
|
|
#else
|
|
Material = Resources.GetBuiltinResource<Material>("Sprites-Default.mat");
|
|
#endif
|
|
}
|
|
|
|
|
|
TryReleaseMesh();
|
|
_meshFilter.sharedMesh = _mesh = new Mesh();
|
|
_mesh.MarkDynamic();
|
|
_mesh.name = GeneratedMeshName;
|
|
|
|
_tangents = new Vector4[4];
|
|
for (int i = 0; i < _tangents.Length; i++)
|
|
_tangents[i] = new Vector4(1, 0, 0);
|
|
|
|
UpdateMeshData(true);
|
|
|
|
RendererEnabled = _meshRenderer.enabled;
|
|
}
|
|
|
|
protected virtual void Start()
|
|
{
|
|
UpdateMeshData(true);
|
|
}
|
|
|
|
private void OnWillRenderObject()
|
|
{
|
|
UpdateMeshData();
|
|
if (Application.isPlaying && LightingSystem.Instance.EnableNormalMapping)
|
|
{
|
|
RendererEnabled = _meshRenderer.enabled;
|
|
_meshRenderer.enabled = false;
|
|
}
|
|
}
|
|
|
|
private void OnRenderObject()
|
|
{
|
|
if (Application.isPlaying && LightingSystem.Instance.EnableNormalMapping)
|
|
{
|
|
_meshRenderer.enabled = RendererEnabled;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Getting material from cache or instantiating new one.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public Material GetOrCreateMaterial()
|
|
{
|
|
TryReleaseMaterial();
|
|
|
|
if (Material == null || Sprite == null)
|
|
return null;
|
|
|
|
MaterialValue matValue;
|
|
var key = new MaterialKey(Material, Sprite.texture);
|
|
|
|
if (!MaterialMap.TryGetValue(key, out matValue))
|
|
{
|
|
var mat = (Material)Instantiate(Material);
|
|
mat.name = GeneratedMaterialName;
|
|
mat.mainTexture = Sprite.texture;
|
|
MaterialMap[key] = matValue = new MaterialValue(mat, 1);
|
|
}
|
|
else
|
|
{
|
|
matValue.UsageCount++;
|
|
}
|
|
|
|
_oldMaterialKey = key;
|
|
|
|
return matValue.Material;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Getting material from cache or instantiating new one.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public static Material GetOrCreateMaterial(Material baseMaterial, Texture2D texture, out MaterialKey materialKey)
|
|
{
|
|
if (baseMaterial == null || texture == null)
|
|
{
|
|
materialKey = null;
|
|
return null;
|
|
}
|
|
|
|
MaterialValue matValue;
|
|
var key = materialKey = new MaterialKey(baseMaterial, texture);
|
|
|
|
if (!MaterialMap.TryGetValue(key, out matValue))
|
|
{
|
|
var mat = (Material)Instantiate(baseMaterial);
|
|
mat.name = GeneratedMaterialName;
|
|
mat.mainTexture = texture;
|
|
MaterialMap[key] = matValue = new MaterialValue(mat, 1);
|
|
}
|
|
else
|
|
{
|
|
matValue.UsageCount++;
|
|
}
|
|
|
|
return matValue.Material;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deleting material from cache with reference counting.
|
|
/// </summary>
|
|
/// <param name="key"></param>
|
|
public static void ReleaseMaterial(MaterialKey key)
|
|
{
|
|
MaterialValue matValue;
|
|
|
|
if (!MaterialMap.TryGetValue(key, out matValue))
|
|
return;
|
|
|
|
matValue.UsageCount--;
|
|
|
|
if (matValue.UsageCount <= 0)
|
|
{
|
|
Util.Destroy(matValue.Material);
|
|
MaterialMap.Remove(key);
|
|
}
|
|
}
|
|
|
|
void TryReleaseMesh()
|
|
{
|
|
if (_meshFilter != null && _meshFilter.sharedMesh != null &&
|
|
_meshFilter.sharedMesh.name == GeneratedMeshName && _mesh == _meshFilter.sharedMesh)
|
|
{
|
|
Util.Destroy(_meshFilter.sharedMesh);
|
|
_meshFilter.sharedMesh = null;
|
|
}
|
|
}
|
|
|
|
void TryReleaseMaterial()
|
|
{
|
|
if (_oldMaterialKey != default(MaterialKey))
|
|
{
|
|
ReleaseMaterial(_oldMaterialKey);
|
|
_oldMaterialKey = default(MaterialKey);
|
|
}
|
|
}
|
|
|
|
void OnDestroy()
|
|
{
|
|
TryReleaseMesh();
|
|
TryReleaseMaterial();
|
|
}
|
|
|
|
protected virtual void UpdateColor()
|
|
{
|
|
for (int i = 0; i < _colors.Length; i++)
|
|
_colors[i] = Color;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recreating mesh data for Sprite based on it's bounds.
|
|
/// </summary>
|
|
protected virtual void UpdateSprite()
|
|
{
|
|
if (Sprite == null)
|
|
return;
|
|
|
|
var rect = Sprite.textureRect;
|
|
var bounds = Sprite.bounds;
|
|
var tex = Sprite.texture;
|
|
var textureSize = new Point2(tex.width, tex.height);
|
|
|
|
// HACK: mipmap could cause texture padding sometimes so padded size of texture needs to be computed.
|
|
var realSize =
|
|
#if UNITY_EDITOR || UNITY_STANDALONE
|
|
tex.mipmapCount <= 1
|
|
#else
|
|
true
|
|
#endif
|
|
? textureSize
|
|
: new Point2(Mathf.NextPowerOfTwo(textureSize.x), Mathf.NextPowerOfTwo(textureSize.y));
|
|
|
|
var unitSize2 = rect.size/Sprite.pixelsPerUnit/2f;
|
|
var offest = (Vector2) bounds.center;
|
|
|
|
_vertices[0] = new Vector3(-unitSize2.x + offest.x, -unitSize2.y + offest.y, 0);
|
|
_vertices[1] = new Vector3(unitSize2.x + offest.x, -unitSize2.y + offest.y, 0);
|
|
_vertices[2] = new Vector3(-unitSize2.x + offest.x, unitSize2.y + offest.y, 0);
|
|
_vertices[3] = new Vector3(unitSize2.x + offest.x, unitSize2.y + offest.y, 0);
|
|
|
|
_uv0[0] = new Vector2(rect.xMin/realSize.x, rect.yMin/realSize.y); // 0, 0
|
|
_uv0[1] = new Vector2(rect.xMax/realSize.x, rect.yMin/realSize.y); // 1, 0
|
|
_uv0[2] = new Vector2(rect.xMin/realSize.x, rect.yMax/realSize.y); // 0, 1
|
|
_uv0[3] = new Vector2(rect.xMax/realSize.x, rect.yMax/realSize.y); // 1, 1
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
_colors[i] = Color;
|
|
}
|
|
|
|
_meshRenderer.sharedMaterial = GetOrCreateMaterial();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clearing and rebuilding mesh.
|
|
/// </summary>
|
|
protected virtual void UpdateMesh()
|
|
{
|
|
_mesh.Clear();
|
|
_mesh.vertices = _vertices;
|
|
_mesh.triangles = _triangles;
|
|
_mesh.uv = _uv0;
|
|
_mesh.uv2 = _uv1;
|
|
_mesh.colors = _colors;
|
|
_mesh.tangents = _tangents;
|
|
_mesh.RecalculateBounds();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checking public fields and mesh data, then rebuilding internal state if changes found.
|
|
/// </summary>
|
|
/// <param name="forceUpdate">Force update even if no changes found.</param>
|
|
protected virtual void UpdateMeshData(bool forceUpdate = false)
|
|
{
|
|
if (_meshRenderer == null || _meshFilter == null || IsPartOfStaticBatch)
|
|
return;
|
|
|
|
_meshRenderer.sortingOrder = SortingOrder;
|
|
|
|
if (Color != _oldColor || forceUpdate)
|
|
{
|
|
UpdateColor();
|
|
_isMeshDirty = true;
|
|
_oldColor = Color;
|
|
}
|
|
if (Sprite != _oldSprite || Material != _oldMaterial || forceUpdate)
|
|
{
|
|
UpdateSprite();
|
|
_isMeshDirty = true;
|
|
_oldSprite = Sprite;
|
|
_oldMaterial = Material;
|
|
}
|
|
if (_isMeshDirty)
|
|
{
|
|
UpdateMesh();
|
|
_isMeshDirty = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used as a value to material map to support reference counting.
|
|
/// </summary>
|
|
public class MaterialValue
|
|
{
|
|
/// <summary>
|
|
/// Instantiated material from MaterialKey.Material with texture from MaterialKey.Texture.
|
|
/// </summary>
|
|
public Material Material;
|
|
|
|
/// <summary>
|
|
/// Count of CustomSprites using that material.
|
|
/// </summary>
|
|
public int UsageCount;
|
|
|
|
public MaterialValue(Material material, int usageCount)
|
|
{
|
|
Material = material;
|
|
UsageCount = usageCount;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used as a key to material map.
|
|
/// </summary>
|
|
public class MaterialKey : IEquatable<MaterialKey>
|
|
{
|
|
/// <summary>
|
|
/// Sprite's texture.
|
|
/// </summary>
|
|
public Texture2D Texture;
|
|
|
|
/// <summary>
|
|
/// Non instantiated material.
|
|
/// </summary>
|
|
public Material Material;
|
|
|
|
public MaterialKey(Material material, Texture2D texture)
|
|
{
|
|
Material = material;
|
|
Texture = texture;
|
|
}
|
|
|
|
private sealed class TextureMaterialEqualityComparer : IEqualityComparer<MaterialKey>
|
|
{
|
|
public bool Equals(MaterialKey x, MaterialKey y)
|
|
{
|
|
if (ReferenceEquals(x, y)) return true;
|
|
if (ReferenceEquals(x, null)) return false;
|
|
if (ReferenceEquals(y, null)) return false;
|
|
if (x.GetType() != y.GetType()) return false;
|
|
return Equals(x.Texture, y.Texture) && Equals(x.Material, y.Material);
|
|
}
|
|
|
|
public int GetHashCode(MaterialKey obj)
|
|
{
|
|
unchecked
|
|
{
|
|
return ((obj.Texture != null ? obj.Texture.GetHashCode() : 0)*397) ^ (obj.Material != null ? obj.Material.GetHashCode() : 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static readonly IEqualityComparer<MaterialKey> TextureMaterialComparerInstance = new TextureMaterialEqualityComparer();
|
|
|
|
public static IEqualityComparer<MaterialKey> TextureMaterialComparer
|
|
{
|
|
get { return TextureMaterialComparerInstance; }
|
|
}
|
|
|
|
public bool Equals(MaterialKey other)
|
|
{
|
|
if (ReferenceEquals(null, other)) return false;
|
|
if (ReferenceEquals(this, other)) return true;
|
|
return Equals(Texture, other.Texture) && Equals(Material, other.Material);
|
|
}
|
|
|
|
public override bool Equals(object obj)
|
|
{
|
|
if (ReferenceEquals(null, obj)) return false;
|
|
if (ReferenceEquals(this, obj)) return true;
|
|
if (obj.GetType() != this.GetType()) return false;
|
|
return Equals((MaterialKey) obj);
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
unchecked
|
|
{
|
|
return ((Texture != null ? Texture.GetHashCode() : 0)*397) ^ (Material != null ? Material.GetHashCode() : 0);
|
|
}
|
|
}
|
|
|
|
public static bool operator ==(MaterialKey left, MaterialKey right)
|
|
{
|
|
return Equals(left, right);
|
|
}
|
|
|
|
public static bool operator !=(MaterialKey left, MaterialKey right)
|
|
{
|
|
return !Equals(left, right);
|
|
}
|
|
}
|
|
}
|
|
}
|