using System; using System.Collections.Generic; using UnityEngine; namespace LeTai.TrueShadow { [ExecuteAlways] public class ShadowSorter : MonoBehaviour { #region SortDataContainer readonly struct SortEntry : IComparable { public readonly TrueShadow shadow; public readonly Transform shadowTransform; public readonly Transform rendererTransform; public SortEntry(TrueShadow shadow) { this.shadow = shadow; shadowTransform = shadow.transform; rendererTransform = shadow.shadowRenderer.transform; } public int CompareTo(SortEntry other) { return other.shadowTransform.GetSiblingIndex().CompareTo(shadowTransform.GetSiblingIndex()); } } readonly struct SortGroup { public readonly Transform parentTransform; public readonly List sortEntries; public SortGroup(SortEntry firstEntry) { sortEntries = new List {firstEntry}; parentTransform = firstEntry.shadowTransform.parent; } public void Add(SortEntry pair) { if (pair.shadowTransform.parent != parentTransform) return; var index = sortEntries.BinarySearch(pair); if (index < 0) sortEntries.Insert(~index, pair); } public override int GetHashCode() { return parentTransform.GetHashCode(); } public override bool Equals(object obj) { return obj is SortGroup other && other.parentTransform == parentTransform; } } #endregion private static ShadowSorter instance; public static ShadowSorter Instance { get { if (!instance) { var existings = FindObjectsOfType(); for (int i = existings.Length - 1; i > 0; i--) { Destroy(existings[i]); } #if UNITY_EDITOR var hidden = GameObject.Find("/" + nameof(ShadowSorter)); while (hidden) { DestroyImmediate(hidden); hidden = GameObject.Find("/" + nameof(ShadowSorter)); } #endif instance = existings.Length > 0 ? existings[0] : null; if (!instance) { var obj = new GameObject(nameof(ShadowSorter)) { #if LETAI_TRUESHADOW_DEBUG hideFlags = DebugSettings.Instance.showObjects ? HideFlags.DontSave : HideFlags.HideAndDontSave #else hideFlags = HideFlags.HideAndDontSave #endif }; instance = obj.AddComponent(); } } return instance; } } readonly IndexedSet shadows = new IndexedSet(); readonly IndexedSet sortGroups = new IndexedSet(); public void Register(TrueShadow shadow) { shadows.AddUnique(shadow); } public void UnRegister(TrueShadow shadow) { shadows.Remove(shadow); } void LateUpdate() { if (!this) return; for (var i = 0; i < shadows.Count; i++) { var shadow = shadows[i]; if (!shadow || !shadow.isActiveAndEnabled) continue; shadow.CheckHierarchyDirtied(); if (shadow.hierachyDirty) AddSortEntry(shadow); } Sort(); } void AddSortEntry(TrueShadow shadow) { var entry = new SortEntry(shadow); var group = new SortGroup(entry); var oldIndex = sortGroups.IndexOf(group); if (oldIndex > -1) sortGroups[oldIndex].Add(entry); else sortGroups.Add(group); } public void Sort() { for (var i = 0; i < sortGroups.Count; i++) { var group = sortGroups[i]; if (!group.parentTransform) continue; foreach (var entry in group.sortEntries) { entry.rendererTransform.SetParent(group.parentTransform, false); var rendererSid = entry.rendererTransform.GetSiblingIndex(); var shadowSid = entry.shadowTransform.GetSiblingIndex(); if (rendererSid > shadowSid) { entry.rendererTransform.SetSiblingIndex(shadowSid); } else { entry.rendererTransform.SetSiblingIndex(shadowSid - 1); } entry.shadow.UnSetHierachyDirty(); } // This is a separated loop, as siblind index of an entry will be affected by the laters foreach (var entry in group.sortEntries) { entry.shadow.ForgetSiblingIndexChanges(); } } sortGroups.Clear(); } void OnApplicationQuit() { // make sure object are recreated when exit play mode. Otherwise it turn into some weird state. need more research Destroy(gameObject); } } }