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 { /// /// 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. /// [ExecuteInEditMode] public class CustomSprite : MonoBehaviour { /// /// Vertex color of mesh. /// public Color Color = Color.white; /// /// Sprite from which mesh will be generated. /// public Sprite Sprite; /// /// Sorting order of MeshRenderer. /// public int SortingOrder; /// /// Material to be used. /// 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 MaterialMap = new Dictionary(); private const string GeneratedMaterialName = "Generated Material (DONT change it)"; private const string GeneratedMeshName = "Generated Mesh (DONT change it)"; public bool RendererEnabled { get; private set; } /// /// Is that sprite is staticaly batched? /// 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(); _meshFilter = GetComponent(); if (_meshRenderer == null) { _meshRenderer = gameObject.AddComponent(); _meshRenderer.receiveShadows = false; _meshRenderer.shadowCastingMode = ShadowCastingMode.Off; } if (_meshFilter == null) { _meshFilter = gameObject.AddComponent(); } if (Material == null) { #if UNITY_EDITOR Material = AssetDatabase.GetBuiltinExtraResource("Sprites-Default.mat"); #else Material = Resources.GetBuiltinResource("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; } } /// /// Getting material from cache or instantiating new one. /// /// 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; } /// /// Getting material from cache or instantiating new one. /// /// 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; } /// /// Deleting material from cache with reference counting. /// /// 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; } /// /// Recreating mesh data for Sprite based on it's bounds. /// 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(); } /// /// Clearing and rebuilding mesh. /// 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(); } /// /// Checking public fields and mesh data, then rebuilding internal state if changes found. /// /// Force update even if no changes found. 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; } } /// /// Used as a value to material map to support reference counting. /// public class MaterialValue { /// /// Instantiated material from MaterialKey.Material with texture from MaterialKey.Texture. /// public Material Material; /// /// Count of CustomSprites using that material. /// public int UsageCount; public MaterialValue(Material material, int usageCount) { Material = material; UsageCount = usageCount; } } /// /// Used as a key to material map. /// public class MaterialKey : IEquatable { /// /// Sprite's texture. /// public Texture2D Texture; /// /// Non instantiated material. /// public Material Material; public MaterialKey(Material material, Texture2D texture) { Material = material; Texture = texture; } private sealed class TextureMaterialEqualityComparer : IEqualityComparer { 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 TextureMaterialComparerInstance = new TextureMaterialEqualityComparer(); public static IEqualityComparer 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); } } } }