From b76bf5a5ad378c3c4b16bcf08d21337757557101 Mon Sep 17 00:00:00 2001 From: mob-sakai Date: Wed, 8 Jun 2022 12:54:11 +0900 Subject: [PATCH] feat: support 8+ materials Instead of one CanvasRenderer with submeshes, render with multiple CanvasRenderers. This feature allows the number of materials to be unlimited. close #122, close #152, close #186 --- Scripts/BakingCamera.cs | 101 ----- Scripts/BakingCamera.cs.meta | 11 - Scripts/CombineInstanceEx.cs | 63 --- Scripts/CombineInstanceEx.cs.meta | 11 - Scripts/Editor/UIParticleEditor.cs | 67 +-- Scripts/MeshHelper.cs | 106 ----- Scripts/ModifiedMaterial.cs | 4 +- Scripts/UIParticle.cs | 417 +++++------------ Scripts/UIParticleRenderer.cs | 422 ++++++++++++++++++ ...per.cs.meta => UIParticleRenderer.cs.meta} | 2 +- Scripts/UIParticleUpdater.cs | 240 +--------- Scripts/Utils.cs | 234 +++------- 12 files changed, 610 insertions(+), 1068 deletions(-) delete mode 100644 Scripts/BakingCamera.cs delete mode 100644 Scripts/BakingCamera.cs.meta delete mode 100644 Scripts/CombineInstanceEx.cs delete mode 100644 Scripts/CombineInstanceEx.cs.meta delete mode 100644 Scripts/MeshHelper.cs mode change 100755 => 100644 Scripts/UIParticle.cs create mode 100644 Scripts/UIParticleRenderer.cs rename Scripts/{MeshHelper.cs.meta => UIParticleRenderer.cs.meta} (83%) mode change 100755 => 100644 Scripts/UIParticleUpdater.cs diff --git a/Scripts/BakingCamera.cs b/Scripts/BakingCamera.cs deleted file mode 100644 index 14fd611..0000000 --- a/Scripts/BakingCamera.cs +++ /dev/null @@ -1,101 +0,0 @@ -using UnityEngine; - -namespace Coffee.UIParticleExtensions -{ - [AddComponentMenu("")] - internal class BakingCamera : MonoBehaviour - { - static BakingCamera s_Instance; - private static readonly Vector3 s_OrthoPosition = new Vector3(0, 0, -1000); - private static readonly Quaternion s_OrthoRotation = Quaternion.identity; - -#if UNITY_2018_3_OR_NEWER && UNITY_EDITOR - static BakingCamera s_InstanceForPrefab; - - private static BakingCamera InstanceForPrefab - { - get - { - // If current scene is prefab mode, create OverlayCamera for editor. -#if UNITY_2021_2_OR_NEWER - var prefabStage = UnityEditor.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage(); -#else - var prefabStage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage(); -#endif - if (prefabStage == null || !prefabStage.scene.isLoaded) return null; - if (s_InstanceForPrefab) return s_InstanceForPrefab; - - s_InstanceForPrefab = Create(); - s_InstanceForPrefab.name += " (For Prefab Stage)"; - UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(s_InstanceForPrefab.gameObject, prefabStage.scene); - - return s_InstanceForPrefab; - } - } -#endif - - private static BakingCamera Instance - { - get - { -#if UNITY_2018_3_OR_NEWER && UNITY_EDITOR - var inst = InstanceForPrefab; - if (inst) return inst; -#endif - // Find instance in scene, or create new one. - return s_Instance - ? s_Instance - : (s_Instance = Create()); - } - } - - private Camera _camera; - - private static BakingCamera Create() - { - var gameObject = new GameObject(typeof(BakingCamera).Name); - - // This camera object is just for internal use - gameObject.hideFlags = HideFlags.HideAndDontSave; - - var inst = gameObject.AddComponent(); - inst._camera = gameObject.AddComponent(); - inst._camera.enabled = false; - inst._camera.orthographic = true; - - // Turn camera off because particle mesh baker will use only camera matrix - gameObject.SetActive(false); - - return inst; - } - - private void Awake() - { - if (this == s_Instance) - DontDestroyOnLoad(gameObject); - } - - public static Camera GetCamera(Canvas canvas) - { - if (!canvas) return Camera.main; - - canvas = canvas.rootCanvas; - // Adjust camera orthographic size to canvas size - // for canvas-based coordinates of particles' size and speed. - var size = ((RectTransform) canvas.transform).rect.size; - Instance._camera.orthographicSize = Mathf.Max(size.x, size.y) * canvas.scaleFactor; - - var camera = canvas.worldCamera; - var transform = Instance.transform; - var rotation = canvas.renderMode != RenderMode.ScreenSpaceOverlay && camera - ? camera.transform.rotation - : s_OrthoRotation; - - transform.SetPositionAndRotation(s_OrthoPosition, rotation); - Instance._camera.orthographic = true; - Instance._camera.farClipPlane = 2000f; - - return Instance._camera; - } - } -} diff --git a/Scripts/BakingCamera.cs.meta b/Scripts/BakingCamera.cs.meta deleted file mode 100644 index baee49a..0000000 --- a/Scripts/BakingCamera.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 999f0ea10cb5f48ed89190a0ca83dd53 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/CombineInstanceEx.cs b/Scripts/CombineInstanceEx.cs deleted file mode 100644 index 99df9d4..0000000 --- a/Scripts/CombineInstanceEx.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; - -namespace Coffee.UIParticleExtensions -{ - internal class CombineInstanceEx - { - private int count; - public long hash = -1; - public int index = -1; - private readonly List combineInstances = new List(32); - public Mesh mesh; - public Matrix4x4 transform; - - public void Combine() - { - switch (count) - { - case 0: - return; - case 1: - mesh = combineInstances[0].mesh; - transform = combineInstances[0].transform; - return; - default: - { - var cis = CombineInstanceArrayPool.Get(combineInstances); - mesh = MeshPool.Rent(); - mesh.CombineMeshes(cis, true, true); - transform = Matrix4x4.identity; - cis.Clear(); - return; - } - } - } - - public void Clear() - { - for (var i = 0; i < combineInstances.Count; i++) - { - var inst = combineInstances[i]; - MeshPool.Return(inst.mesh); - inst.mesh = null; - combineInstances[i] = inst; - } - - combineInstances.Clear(); - - MeshPool.Return(mesh); - mesh = null; - - count = 0; - hash = -1; - index = -1; - } - - public void Push(Mesh mesh, Matrix4x4 transform) - { - combineInstances.Add(new CombineInstance {mesh = mesh, transform = transform}); - count++; - } - } -} diff --git a/Scripts/CombineInstanceEx.cs.meta b/Scripts/CombineInstanceEx.cs.meta deleted file mode 100644 index 629b238..0000000 --- a/Scripts/CombineInstanceEx.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 0328b9fb8360e4f8e8a842f87d330466 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/UIParticleEditor.cs b/Scripts/Editor/UIParticleEditor.cs index f131bec..9b8e330 100644 --- a/Scripts/Editor/UIParticleEditor.cs +++ b/Scripts/Editor/UIParticleEditor.cs @@ -23,11 +23,9 @@ namespace Coffee.UIExtensions private static readonly GUIContent s_Content3D = new GUIContent("3D"); private static readonly GUIContent s_ContentScale = new GUIContent("Scale"); - private SerializedProperty _spMaskable; - private SerializedProperty _spScale; - private SerializedProperty _spIgnoreCanvasScaler; - private SerializedProperty _spAnimatableProperties; - private SerializedProperty _spShrinkByMaterial; + private SerializedProperty m_Maskable; + private SerializedProperty m_Scale3D; + private SerializedProperty m_AnimatableProperties; private ReorderableList _ro; static private bool _xyzMode; @@ -52,11 +50,10 @@ namespace Coffee.UIExtensions protected override void OnEnable() { base.OnEnable(); - _spMaskable = serializedObject.FindProperty("m_Maskable"); - _spScale = serializedObject.FindProperty("m_Scale3D"); - _spIgnoreCanvasScaler = serializedObject.FindProperty("m_IgnoreCanvasScaler"); - _spAnimatableProperties = serializedObject.FindProperty("m_AnimatableProperties"); - _spShrinkByMaterial = serializedObject.FindProperty("m_ShrinkByMaterial"); + + m_Maskable = serializedObject.FindProperty("m_Maskable"); + m_Scale3D = serializedObject.FindProperty("m_Scale3D"); + m_AnimatableProperties = serializedObject.FindProperty("m_AnimatableProperties"); var sp = serializedObject.FindProperty("m_Particles"); _ro = new ReorderableList(sp.serializedObject, sp, true, true, true, true); @@ -85,7 +82,7 @@ namespace Coffee.UIExtensions materials.serializedObject.ApplyModifiedProperties(); } }; - _ro.drawHeaderCallback += rect => + _ro.drawHeaderCallback = rect => { #if !UNITY_2019_3_OR_NEWER rect.y -= 1; @@ -100,6 +97,13 @@ namespace Coffee.UIExtensions } } }; + _ro.onReorderCallback = _ => + { + foreach (UIParticle t in targets) + { + t.RefreshParticles(t.particles); + } + }; } private static void MaterialField(Rect rect, GUIContent label, SerializedProperty sp, int index) @@ -127,23 +131,10 @@ namespace Coffee.UIExtensions serializedObject.Update(); // Maskable - EditorGUILayout.PropertyField(_spMaskable); - - // IgnoreCanvasScaler - using (var ccs = new EditorGUI.ChangeCheckScope()) - { - EditorGUILayout.PropertyField(_spIgnoreCanvasScaler); - if (ccs.changed) - { - foreach (UIParticle p in targets) - { - p.ignoreCanvasScaler = _spIgnoreCanvasScaler.boolValue; - } - } - } + EditorGUILayout.PropertyField(m_Maskable); // Scale - _xyzMode = DrawFloatOrVector3Field(_spScale, _xyzMode); + _xyzMode = DrawFloatOrVector3Field(m_Scale3D, _xyzMode); // AnimatableProperties var mats = current.particles @@ -154,16 +145,13 @@ namespace Coffee.UIExtensions // Animated properties EditorGUI.BeginChangeCheck(); - AnimatedPropertiesEditor.DrawAnimatableProperties(_spAnimatableProperties, mats); + AnimatedPropertiesEditor.DrawAnimatableProperties(m_AnimatableProperties, mats); if (EditorGUI.EndChangeCheck()) { foreach (UIParticle t in targets) t.SetMaterialDirty(); } - // ShrinkByMaterial - EditorGUILayout.PropertyField(_spShrinkByMaterial); - // Target ParticleSystems. _ro.DoLayoutList(); @@ -186,12 +174,10 @@ namespace Coffee.UIExtensions } } - // Does the shader support UI masks? - + // UIParticle for trail should be removed. if (FixButton(current.m_IsTrail, "This UIParticle component should be removed. The UIParticle for trails is no longer needed.")) { DestroyUIParticle(current); - return; } // #203: When using linear color space, the particle colors are not output correctly. @@ -213,7 +199,7 @@ namespace Coffee.UIExtensions } } - void DestroyUIParticle(UIParticle p, bool ignoreCurrent = false) + private void DestroyUIParticle(UIParticle p, bool ignoreCurrent = false) { if (!p || ignoreCurrent && target == p) return; @@ -239,7 +225,7 @@ namespace Coffee.UIExtensions #endif } - bool FixButton(bool show, string text) + private static bool FixButton(bool show, string text) { if (!show) return false; using (new EditorGUILayout.HorizontalScope(GUILayout.ExpandWidth(true))) @@ -266,14 +252,7 @@ namespace Coffee.UIExtensions EditorGUILayout.BeginHorizontal(); if (showXyz) { - EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(sp); - if (EditorGUI.EndChangeCheck()) - { - x.floatValue = Mathf.Max(0.001f, x.floatValue); - y.floatValue = Mathf.Max(0.001f, y.floatValue); - z.floatValue = Mathf.Max(0.001f, z.floatValue); - } } else { @@ -281,9 +260,7 @@ namespace Coffee.UIExtensions EditorGUILayout.PropertyField(x, s_ContentScale); if (EditorGUI.EndChangeCheck()) { - x.floatValue = Mathf.Max(0.001f, x.floatValue); - y.floatValue = Mathf.Max(0.001f, x.floatValue); - z.floatValue = Mathf.Max(0.001f, x.floatValue); + y.floatValue = z.floatValue = x.floatValue; } } diff --git a/Scripts/MeshHelper.cs b/Scripts/MeshHelper.cs deleted file mode 100644 index 2a4f79a..0000000 --- a/Scripts/MeshHelper.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; -using UnityEngine.Profiling; - -namespace Coffee.UIParticleExtensions -{ - internal static class MeshHelper - { - public static List activeMeshIndices { get; private set; } - private static readonly List s_CachedInstance; - private static int count; - - public static void Init() - { - activeMeshIndices = new List(); - } - - static MeshHelper() - { - s_CachedInstance = new List(8); - for (var i = 0; i < 8; i++) - { - s_CachedInstance.Add(new CombineInstanceEx()); - } - } - - private static CombineInstanceEx Get(int index, long hash) - { - if (0 < count && s_CachedInstance[count - 1].hash == hash) - return s_CachedInstance[count - 1]; - - if (s_CachedInstance.Count <= count) - { - var newInst = new CombineInstanceEx(); - s_CachedInstance.Add(newInst); - } - - var inst = s_CachedInstance[count]; - inst.hash = hash; - if (inst.index != -1) return inst; - - inst.index = index; - count++; - return inst; - } - - public static Mesh GetTemporaryMesh() - { - return MeshPool.Rent(); - } - - public static void Push(int index, long hash, Mesh mesh, Matrix4x4 transform) - { - if (mesh.vertexCount <= 0) - { - DiscardTemporaryMesh(mesh); - return; - } - - Profiler.BeginSample("[UIParticle] MeshHelper > Get CombineInstanceEx"); - var inst = Get(index, hash); - Profiler.EndSample(); - - Profiler.BeginSample("[UIParticle] MeshHelper > Push To Mesh Helper"); - inst.Push(mesh, transform); - Profiler.EndSample(); - - activeMeshIndices[inst.index] = true; - } - - public static void Clear() - { - count = 0; - activeMeshIndices.Clear(); - foreach (var inst in s_CachedInstance) - { - inst.Clear(); - } - } - - public static void CombineMesh(Mesh result) - { - if (count == 0) return; - - for (var i = 0; i < count; i++) - { - Profiler.BeginSample("[UIParticle] MeshHelper > Combine Mesh Internal"); - s_CachedInstance[i].Combine(); - Profiler.EndSample(); - } - - Profiler.BeginSample("[UIParticle] MeshHelper > Combine Mesh"); - var cis = CombineInstanceArrayPool.Get(s_CachedInstance, count); - result.CombineMeshes(cis, false, true); - cis.Clear(); - Profiler.EndSample(); - - result.RecalculateBounds(); - } - - public static void DiscardTemporaryMesh(Mesh mesh) - { - MeshPool.Return(mesh); - } - } -} diff --git a/Scripts/ModifiedMaterial.cs b/Scripts/ModifiedMaterial.cs index 0821082..e31df9c 100644 --- a/Scripts/ModifiedMaterial.cs +++ b/Scripts/ModifiedMaterial.cs @@ -28,7 +28,7 @@ namespace Coffee.UIParticleExtensions if (texture) e.customMat.mainTexture = texture; s_Entries.Add(e); - // Debug.LogFormat(">>>> ModifiedMaterial.Add -> count = {0} {1} {2} {3}", s_Entries.Count, baseMat, texture, id); + //Debug.LogFormat(">>>> ModifiedMaterial.Add -> count = count:{0}, mat:{1}, tex:{2}, id:{3}", s_Entries.Count, baseMat, texture, id); return e.customMat; } @@ -42,7 +42,7 @@ namespace Coffee.UIParticleExtensions if (e.customMat != customMat) continue; if (--e.count == 0) { - // Debug.LogFormat(">>>> ModifiedMaterial.Add -> count = {0} {1} {2} {3}", s_Entries.Count - 1, e.customMat, e.texture, e.id); + //Debug.LogFormat(">>>> ModifiedMaterial.Remove -> count:{0}, mat:{1}, tex:{2}, id:{3}", s_Entries.Count - 1, e.customMat, e.texture, e.id); DestroyImmediate(e.customMat); e.baseMat = null; e.texture = null; diff --git a/Scripts/UIParticle.cs b/Scripts/UIParticle.cs old mode 100755 new mode 100644 index 7e1dbd5..4b576ae --- a/Scripts/UIParticle.cs +++ b/Scripts/UIParticle.cs @@ -1,13 +1,11 @@ #if UNITY_2019_3_11 || UNITY_2019_3_12 || UNITY_2019_3_13 || UNITY_2019_3_14 || UNITY_2019_3_15 || UNITY_2019_4_OR_NEWER #define SERIALIZE_FIELD_MASKABLE #endif -using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; using Coffee.UIParticleExtensions; using UnityEngine; using UnityEngine.Rendering; -using UnityEngine.Serialization; using UnityEngine.UI; [assembly: InternalsVisibleTo("Coffee.UIParticle.Editor")] @@ -21,48 +19,29 @@ namespace Coffee.UIExtensions [RequireComponent(typeof(RectTransform))] [RequireComponent(typeof(CanvasRenderer))] public class UIParticle : MaskableGraphic -#if UNITY_EDITOR - , ISerializationCallbackReceiver -#endif { - [HideInInspector] [SerializeField] internal bool m_IsTrail = false; + [HideInInspector][SerializeField] internal bool m_IsTrail = false; - [Tooltip("Ignore canvas scaler")] [SerializeField] [FormerlySerializedAs("m_IgnoreParent")] - bool m_IgnoreCanvasScaler = true; + [Tooltip("Particle effect scale")] + [SerializeField] + private Vector3 m_Scale3D = new Vector3(10, 10, 10); - [Tooltip("Particle effect scale")] [SerializeField] - float m_Scale = 100; - - [Tooltip("Particle effect scale")] [SerializeField] - private Vector3 m_Scale3D; - - [Tooltip("Animatable material properties. If you want to change the material properties of the ParticleSystem in Animation, enable it.")] [SerializeField] + [Tooltip("Animatable material properties. If you want to change the material properties of the ParticleSystem in Animation, enable it.")] + [SerializeField] internal AnimatableProperty[] m_AnimatableProperties = new AnimatableProperty[0]; - [Tooltip("Particles")] [SerializeField] + [Tooltip("Particles")] + [SerializeField] private List m_Particles = new List(); - [Tooltip("Shrink rendering by material on refresh.\nNOTE: Performance will be improved, but in some cases the rendering is not correct.")] [SerializeField] - bool m_ShrinkByMaterial = false; + private List m_Renderers = new List(); #if !SERIALIZE_FIELD_MASKABLE [SerializeField] private bool m_Maskable = true; #endif - private bool _shouldBeRemoved; private DrivenRectTransformTracker _tracker; - private Mesh _bakedMesh; - private readonly List _modifiedMaterials = new List(); - private readonly List _maskMaterials = new List(); - private readonly List _activeMeshIndices = new List(); - private Vector3 _cachedPosition; - private static readonly List s_TempMaterials = new List(2); - private static MaterialPropertyBlock s_Mpb; - private static readonly List s_PrevMaskMaterials = new List(); - private static readonly List s_PrevModifiedMaterials = new List(); - private static readonly List s_Components = new List(); - private static readonly List s_ParticleSystems = new List(); - + private Camera _orthoCamera; /// /// Should this graphic be considered a target for raycasting? @@ -73,41 +52,13 @@ namespace Coffee.UIExtensions set { } } - public bool ignoreCanvasScaler - { - get { return m_IgnoreCanvasScaler; } - set - { - // if (m_IgnoreCanvasScaler == value) return; - m_IgnoreCanvasScaler = value; - _tracker.Clear(); - if (isActiveAndEnabled && m_IgnoreCanvasScaler) - _tracker.Add(this, rectTransform, DrivenTransformProperties.Scale); - } - } - - public bool shrinkByMaterial - { - get { return m_ShrinkByMaterial; } - set - { - if (m_ShrinkByMaterial == value) return; - m_ShrinkByMaterial = value; - RefreshParticles(); - } - } - /// /// Particle effect scale. /// public float scale { get { return m_Scale3D.x; } - set - { - m_Scale = Mathf.Max(0.001f, value); - m_Scale3D = new Vector3(m_Scale, m_Scale, m_Scale); - } + set { m_Scale3D = new Vector3(value, value, value); } } /// @@ -116,18 +67,7 @@ namespace Coffee.UIExtensions public Vector3 scale3D { get { return m_Scale3D; } - set - { - if (m_Scale3D == value) return; - m_Scale3D.x = Mathf.Max(0.001f, value.x); - m_Scale3D.y = Mathf.Max(0.001f, value.y); - m_Scale3D.z = Mathf.Max(0.001f, value.z); - } - } - - internal Mesh bakedMesh - { - get { return _bakedMesh; } + set { m_Scale3D = value; } } public List particles @@ -135,52 +75,59 @@ namespace Coffee.UIExtensions get { return m_Particles; } } + /// + /// Get all base materials to render. + /// public IEnumerable materials { - get { return _modifiedMaterials; } + get + { + for (var i = 0; i < m_Renderers.Count; i++) + { + if (!m_Renderers[i] || !m_Renderers[i].material) continue; + yield return m_Renderers[i].material; + } + yield break; + } } public override Material materialForRendering { - get { return canvasRenderer.GetMaterial(0); } + get { return null; } } - public List activeMeshIndices - { - get { return _activeMeshIndices; } - set - { - if (_activeMeshIndices.SequenceEqualFast(value)) return; - _activeMeshIndices.Clear(); - _activeMeshIndices.AddRange(value); - UpdateMaterial(); - } - } - - internal Vector3 cachedPosition - { - get { return _cachedPosition; } - set { _cachedPosition = value; } - } + /// + /// Paused. + /// + public bool isPaused { get; internal set; } public void Play() { - particles.Exec(p => p.Play()); + particles.Exec(p => p.Simulate(0, false, true)); + isPaused = false; } public void Pause() { particles.Exec(p => p.Pause()); + isPaused = true; + } + + public void Resume() + { + isPaused = false; } public void Stop() { particles.Exec(p => p.Stop()); + isPaused = true; } public void Clear() { particles.Exec(p => p.Clear()); + isPaused = true; } public void SetParticleSystemInstance(GameObject instance) @@ -234,202 +181,60 @@ namespace Coffee.UIExtensions foreach (var ps in particles) { var tsa = ps.textureSheetAnimation; - if (tsa.mode == ParticleSystemAnimationMode.Sprites && tsa.uvChannelMask == (UVChannelFlags) 0) + if (tsa.mode == ParticleSystemAnimationMode.Sprites && tsa.uvChannelMask == 0) tsa.uvChannelMask = UVChannelFlags.UV0; } - particles.Exec(p => p.GetComponent().enabled = !enabled); - particles.SortForRendering(transform, m_ShrinkByMaterial); - - SetMaterialDirty(); + RefreshParticles(particles); } - protected override void UpdateMaterial() + public void RefreshParticles(List particles) { - // Clear mask materials. - s_PrevMaskMaterials.AddRange(_maskMaterials); - _maskMaterials.Clear(); + GetComponentsInChildren(m_Renderers); - // Clear modified materials. - s_PrevModifiedMaterials.AddRange(_modifiedMaterials); - _modifiedMaterials.Clear(); - - // Recalculate stencil value. - if (m_ShouldRecalculateStencil) + for (var i = 0; i < m_Renderers.Count; i++) { - var rootCanvas = MaskUtilities.FindRootSortOverrideCanvas(transform); - m_StencilValue = maskable ? MaskUtilities.GetStencilDepth(transform, rootCanvas) : 0; - m_ShouldRecalculateStencil = false; + GetRenderer(i).Clear(); } - // No mesh to render. - var count = activeMeshIndices.CountFast(); - if (count == 0 || !isActiveAndEnabled || particles.Count == 0) - { - canvasRenderer.Clear(); - ClearPreviousMaterials(); - return; - } - - // - GetComponents(typeof(IMaterialModifier), s_Components); - var materialCount = Mathf.Min(8, count); - canvasRenderer.materialCount = materialCount; var j = 0; for (var i = 0; i < particles.Count; i++) { - if (materialCount <= j) break; - var ps = particles[i]; - if (!ps) continue; - - var r = ps.GetComponent(); - r.GetSharedMaterials(s_TempMaterials); - - // Main - var index = i * 2; - if (activeMeshIndices.Count <= index) break; - if (activeMeshIndices[index] && 0 < s_TempMaterials.Count) + GetRenderer(j++).Set(this, particles[i], false); + if (particles[i].trails.enabled) { - var mat = GetModifiedMaterial(s_TempMaterials[0], ps.GetTextureForSprite()); - for (var k = 1; k < s_Components.Count; k++) - mat = (s_Components[k] as IMaterialModifier).GetModifiedMaterial(mat); - canvasRenderer.SetMaterial(mat, j); - UpdateMaterialProperties(r, j); - j++; - } - - // Trails - index++; - if (activeMeshIndices.Count <= index || materialCount <= j) break; - if (activeMeshIndices[index] && 1 < s_TempMaterials.Count) - { - var mat = GetModifiedMaterial(s_TempMaterials[1], null); - for (var k = 1; k < s_Components.Count; k++) - mat = (s_Components[k] as IMaterialModifier).GetModifiedMaterial(mat); - canvasRenderer.SetMaterial(mat, j++); + GetRenderer(j++).Set(this, particles[i], true); } } - ClearPreviousMaterials(); } - private void ClearPreviousMaterials() + public void UpdateRenderers() { - foreach (var m in s_PrevMaskMaterials) - StencilMaterial.Remove(m); - s_PrevMaskMaterials.Clear(); - - foreach (var m in s_PrevModifiedMaterials) - ModifiedMaterial.Remove(m); - s_PrevModifiedMaterials.Clear(); - } - - private Material GetModifiedMaterial(Material baseMaterial, Texture2D texture) - { - if (0 < m_StencilValue) + var newScale = Vector3.one; + if (transform.localScale != newScale) { - baseMaterial = StencilMaterial.Add(baseMaterial, (1 << m_StencilValue) - 1, StencilOp.Keep, CompareFunction.Equal, ColorWriteMask.All, (1 << m_StencilValue) - 1, 0); - _maskMaterials.Add(baseMaterial); + transform.localScale = newScale; } - if (texture == null && m_AnimatableProperties.Length == 0) return baseMaterial; - - var id = m_AnimatableProperties.Length == 0 ? 0 : GetInstanceID(); - baseMaterial = ModifiedMaterial.Add(baseMaterial, texture, id); - _modifiedMaterials.Add(baseMaterial); - - return baseMaterial; - } - - internal void UpdateMaterialProperties() - { - if (m_AnimatableProperties.Length == 0) return; - - // - var count = activeMeshIndices.CountFast(); - var materialCount = Mathf.Max(8, count); - canvasRenderer.materialCount = materialCount; - var j = 0; - for (var i = 0; i < particles.Count; i++) + var bakeCamera = GetBakeCamera(); + for (var i = 0; i < m_Renderers.Count; i++) { - if (materialCount <= j) break; - var ps = particles[i]; - if (!ps) continue; - - var r = ps.GetComponent(); - r.GetSharedMaterials(s_TempMaterials); - - // Main - if (activeMeshIndices[i * 2] && 0 < s_TempMaterials.Count) - { - UpdateMaterialProperties(r, j); - j++; - } + m_Renderers[i].UpdateMesh(bakeCamera); } } - internal void UpdateMaterialProperties(Renderer r, int index) - { - if (m_AnimatableProperties.Length == 0 || canvasRenderer.materialCount <= index) return; - - r.GetPropertyBlock(s_Mpb ?? (s_Mpb = new MaterialPropertyBlock())); - if (s_Mpb.isEmpty) return; - - // #41: Copy the value from MaterialPropertyBlock to CanvasRenderer - var mat = canvasRenderer.GetMaterial(index); - if (!mat) return; - - foreach (var ap in m_AnimatableProperties) - { - ap.UpdateMaterialProperties(mat, s_Mpb); - } - - s_Mpb.Clear(); - } - - /// - /// This function is called when the object becomes enabled and active. - /// protected override void OnEnable() { #if !SERIALIZE_FIELD_MASKABLE maskable = m_Maskable; #endif - activeMeshIndices.Clear(); - + _tracker.Add(this, rectTransform, DrivenTransformProperties.Scale); UIParticleUpdater.Register(this); - particles.Exec(p => p.GetComponent().enabled = false); - - if (isActiveAndEnabled && m_IgnoreCanvasScaler) - { - _tracker.Add(this, rectTransform, DrivenTransformProperties.Scale); - } - - // Create objects. - _bakedMesh = MeshPool.Rent(); + RegisterDirtyMaterialCallback(UpdateRendererMaterial); + RefreshParticles(particles); base.OnEnable(); - - InitializeIfNeeded(); - } - - private new IEnumerator Start() - { - // #147: ParticleSystem creates Particles in wrong position during prewarm - // #148: Particle Sub Emitter not showing when start game - var delayToPlay = particles.AnyFast(ps => - { - ps.GetComponentsInChildren(false, s_ParticleSystems); - return s_ParticleSystems.AnyFast(p => p.isPlaying && (p.subEmitters.enabled || p.main.prewarm)); - }); - s_ParticleSystems.Clear(); - if (!delayToPlay) yield break; - - Stop(); - Clear(); - yield return null; - - Play(); } /// @@ -437,18 +242,18 @@ namespace Coffee.UIExtensions /// protected override void OnDisable() { - UIParticleUpdater.Unregister(this); - if (!_shouldBeRemoved) - particles.Exec(p => p.GetComponent().enabled = true); _tracker.Clear(); - - // Destroy object. - MeshPool.Return(_bakedMesh); - _bakedMesh = null; + UIParticleUpdater.Unregister(this); + m_Renderers.ForEach(r=>r.Clear()); + UnregisterDirtyMaterialCallback(UpdateRendererMaterial); base.OnDisable(); } + protected override void UpdateMaterial() + { + } + /// /// Call to update the geometry of the Graphic onto the CanvasRenderer. /// @@ -463,62 +268,58 @@ namespace Coffee.UIExtensions { } - private void InitializeIfNeeded() + private void UpdateRendererMaterial() { - if (enabled && m_IsTrail) + for (var i = 0; i < m_Renderers.Count; i++) { - UnityEngine.Debug.LogWarningFormat(this, "[UIParticle] The UIParticle component should be removed: {0}\nReason: UIParticle for trails is no longer needed.", name); - gameObject.hideFlags = HideFlags.None; - _shouldBeRemoved = true; - enabled = false; - return; + if (!m_Renderers[i]) continue; + m_Renderers[i].maskable = maskable; + m_Renderers[i].SetMaterialDirty(); } + } - if (!this || particles.AnyFast()) return; + private UIParticleRenderer GetRenderer(int index) + { + if (m_Renderers.Count <= index) + { + m_Renderers.Add(UIParticleRenderer.AddRenderer(this)); + } + return m_Renderers[index]; + } - // refresh. -#if UNITY_EDITOR - if (!Application.isPlaying) - UnityEditor.EditorApplication.delayCall += () => + private Camera GetBakeCamera() + { + if (!canvas) return Camera.main; + + // World camera. + var root = canvas.rootCanvas; + if (root.renderMode != RenderMode.ScreenSpaceOverlay) return root.worldCamera ? root.worldCamera : Camera.main; + + // Create ortho-camera. + if (!_orthoCamera) + { + _orthoCamera = GetComponentInChildren(); + if (!_orthoCamera) { - if (this) RefreshParticles(); - }; - else -#endif - RefreshParticles(); - } - -#if UNITY_EDITOR - protected override void OnValidate() - { - SetLayoutDirty(); - SetVerticesDirty(); - m_ShouldRecalculateStencil = true; - RecalculateClipping(); -#if !SERIALIZE_FIELD_MASKABLE - maskable = m_Maskable; -#endif - } - - void ISerializationCallbackReceiver.OnBeforeSerialize() - { - if (Application.isPlaying) return; - InitializeIfNeeded(); - } - - void ISerializationCallbackReceiver.OnAfterDeserialize() - { - if (m_Scale3D == Vector3.zero) - { - scale = m_Scale; + var go = new GameObject("UIParticleOverlayCamera") + { + hideFlags = HideFlags.DontSave, + }; + go.SetActive(false); + go.transform.SetParent(transform, false); + _orthoCamera = go.AddComponent(); + _orthoCamera.enabled = false; + } } - UnityEditor.EditorApplication.delayCall += () => - { - if (Application.isPlaying || !this) return; - InitializeIfNeeded(); - }; + // + var size = ((RectTransform)root.transform).rect.size; + _orthoCamera.orthographicSize = Mathf.Max(size.x, size.y) * root.scaleFactor; + _orthoCamera.transform.SetPositionAndRotation(new Vector3(0, 0, -1000), Quaternion.identity); + _orthoCamera.orthographic = true; + _orthoCamera.farClipPlane = 2000f; + + return _orthoCamera; } -#endif } } diff --git a/Scripts/UIParticleRenderer.cs b/Scripts/UIParticleRenderer.cs new file mode 100644 index 0000000..e97fe41 --- /dev/null +++ b/Scripts/UIParticleRenderer.cs @@ -0,0 +1,422 @@ +using UnityEngine; +using UnityEngine.UI; +using Coffee.UIParticleExtensions; +using UnityEngine.Profiling; +using UnityEngine.Rendering; +using System.Collections.Generic; + +namespace Coffee.UIExtensions +{ + [ExecuteAlways] + [RequireComponent(typeof(RectTransform))] + [RequireComponent(typeof(CanvasRenderer))] + [AddComponentMenu("")] + internal class UIParticleRenderer : MaskableGraphic + { + private static readonly CombineInstance[] s_CombineInstances = new CombineInstance[] { new CombineInstance() }; + private static ParticleSystem.Particle[] s_Particles = new ParticleSystem.Particle[2048]; + private static List s_Materials = new List(2); + private static MaterialPropertyBlock s_Mpb; + + private ParticleSystemRenderer _renderer; + private ParticleSystem _particleSystem; + //private ParticleSystem _emitter; + private UIParticle _parent; + private bool _isTrail; + private Material _modifiedMaterial; + private Vector3 _prevScale; + private Vector3 _prevPsPos; + private bool _delay = false; + private bool _prewarm = false; + + public override Texture mainTexture + { + get + { + return _isTrail ? null : _particleSystem.GetTextureForSprite(); + } + } + + public override bool raycastTarget + { + get + { + return false; + } + } + + public static UIParticleRenderer AddRenderer(UIParticle parent) + { + // Create renderer object. + var go = new GameObject("UIParticleRenderer", typeof(UIParticleRenderer)) + { + hideFlags = HideFlags.DontSave, + layer = parent.gameObject.layer, + }; + + // Set parent. + var transform = go.transform; + transform.SetParent(parent.transform, false); + transform.localPosition = Vector3.zero; + transform.localRotation = Quaternion.identity; + transform.localScale = Vector3.one; + + // Add renderer component. + var renderer = go.GetComponent(); + renderer._parent = parent; + + return renderer; + } + + /// + /// Perform material modification in this function. + /// + public override Material GetModifiedMaterial(Material baseMaterial) + { + if (!IsActive()) return baseMaterial; + + var modifiedMaterial = base.GetModifiedMaterial(baseMaterial); + + // + var texture = mainTexture; + if (texture == null && _parent.m_AnimatableProperties.Length == 0) + { + ModifiedMaterial.Remove(_modifiedMaterial); + _modifiedMaterial = null; + return modifiedMaterial; + } + + // + var id = _parent.m_AnimatableProperties.Length == 0 ? 0 : GetInstanceID(); + modifiedMaterial = ModifiedMaterial.Add(modifiedMaterial, texture, id); + ModifiedMaterial.Remove(_modifiedMaterial); + _modifiedMaterial = modifiedMaterial; + + return modifiedMaterial; + } + + public void Clear() + { + if (_renderer) + { + _renderer.enabled = true; + } + _parent = null; + _particleSystem = null; + _renderer = null; + //_emitter = null; + + material = null; + enabled = false; + + workerMesh.Clear(); + canvasRenderer.SetMesh(workerMesh); + } + + public void Set(UIParticle parent, ParticleSystem particleSystem, bool isTrail) + { + _parent = parent; + maskable = parent.maskable; + + gameObject.layer = parent.gameObject.layer; + + _particleSystem = particleSystem; + if (_particleSystem.isPlaying) + { + _particleSystem.Clear(); + } + _prewarm = _particleSystem.main.prewarm; + + _renderer = particleSystem.GetComponent(); + _renderer.enabled = false; + + //_emitter = emitter; + _isTrail = isTrail; + + _renderer.GetSharedMaterials(s_Materials); + material = s_Materials[isTrail ? 1 : 0]; + s_Materials.Clear(); + + // Support sprite. + var tsa = particleSystem.textureSheetAnimation; + if (tsa.mode == ParticleSystemAnimationMode.Sprites && tsa.uvChannelMask == 0) + tsa.uvChannelMask = UVChannelFlags.UV0; + + _prevScale = GetWorldScale(); + _prevPsPos = _particleSystem.transform.position; + _delay = true; + + canvasRenderer.SetTexture(null); + + enabled = true; + } + + public void UpdateMesh(Camera bakeCamera) + { + // No particle to render: Clear mesh. + if ( + !enabled || !_particleSystem || !_parent || !canvasRenderer || !canvas || !bakeCamera + || !transform.lossyScale.GetScaled(_parent.scale3D).IsVisible() // Scale is not visible. + || (!_particleSystem.IsAlive() && !_particleSystem.isPlaying) // No particle. + || (_isTrail && !_particleSystem.trails.enabled) // Trail, but it is not enabled. +#if UNITY_2018_3_OR_NEWER + || canvasRenderer.GetInheritedAlpha() < 0.01f // #102: Do not bake particle system to mesh when the alpha is zero. +#endif + ) + { + Profiler.BeginSample("[UIParticleRenderer] Clear Mesh"); + workerMesh.Clear(); + canvasRenderer.SetMesh(workerMesh); + Profiler.EndSample(); + + return; + } + + var main = _particleSystem.main; + var scale = GetWorldScale(); + var psPos = _particleSystem.transform.position; + + // Simulate particles. + if (!_isTrail) + { + Profiler.BeginSample("[UIParticle] Bake Mesh > Simulate Particles"); +#if UNITY_EDITOR + if (!Application.isPlaying) + { + SimulateForEditor(psPos - _prevPsPos, scale); + } + else +#endif + { + Simulate(scale, _parent.isPaused || _delay); + + // When the ParticleSystem simulation is complete, stop it. + if (!main.loop && main.duration <= _particleSystem.time && (_particleSystem.IsAlive() || _particleSystem.particleCount == 0)) + { + _particleSystem.Stop(false); + } + } + Profiler.EndSample(); + _prevScale = scale; + _prevPsPos = psPos; + _delay = false; + } + + // Bake mesh. + Profiler.BeginSample("[UIParticleRenderer] Bake Mesh"); + { + if (_isTrail) + { + _renderer.BakeTrailsMesh(s_CombineInstances[0].mesh, bakeCamera, true); + } + else if (_renderer.CanBakeMesh()) + { + _renderer.BakeMesh(s_CombineInstances[0].mesh, bakeCamera, true); + } + else + { + s_CombineInstances[0].mesh.Clear(); + } + } + Profiler.EndSample(); + + // Combine mesh to transform. ([ParticleSystem local ->] world -> renderer local) + Profiler.BeginSample("[UIParticleRenderer] Combine Mesh"); + { + s_CombineInstances[0].transform = canvasRenderer.transform.worldToLocalMatrix * GetWorldMatrix(psPos, scale); + workerMesh.CombineMeshes(s_CombineInstances, true, true); + } + Profiler.EndSample(); + + // Set mesh to the CanvasRenderer. + Profiler.BeginSample("[UIParticleRenderer] Set Mesh"); + canvasRenderer.SetMesh(workerMesh); + Profiler.EndSample(); + + // Update animatable material properties. + Profiler.BeginSample("[UIParticleRenderer] Update Animatable Material Properties"); + UpdateMaterialProperties(); + Profiler.EndSample(); + } + + protected override void OnEnable() + { + base.OnEnable(); + + if (!s_CombineInstances[0].mesh) + { + s_CombineInstances[0].mesh = new Mesh() + { + name = "[UIParticleRenderer] Combine Instance Mesh", + hideFlags = HideFlags.HideAndDontSave, + }; + } + } + + protected override void OnDisable() + { + base.OnDisable(); + + ModifiedMaterial.Remove(_modifiedMaterial); + _modifiedMaterial = null; + } + + /// + /// Call to update the geometry of the Graphic onto the CanvasRenderer. + /// + protected override void UpdateGeometry() + { + } + + private Vector3 GetWorldScale() + { + Profiler.BeginSample("[UIParticleRenderer] GetWorldScale"); + var scale = _parent.scale3D; + //else if (_parent.scalingMode == UIParticle.ScalingMode.UI && _particleSystem.main.scalingMode != ParticleSystemScalingMode.Hierarchy) + //{ + // var gscale = _parent.transform.lossyScale.GetScaled(canvas.transform.lossyScale.Inverse()); + // scale.Scale(gscale * canvas.scaleFactor); + //} + Profiler.EndSample(); + return scale; + } + + private Matrix4x4 GetWorldMatrix(Vector3 psPos, Vector3 scale) + { + var space = _particleSystem.GetActualSimulationSpace(); + if (_isTrail && _particleSystem.trails.worldSpace) + { + space = ParticleSystemSimulationSpace.World; + } + +#if UNITY_EDITOR + if (!Application.isPlaying) + { + switch (space) + { + case ParticleSystemSimulationSpace.World: + return Matrix4x4.Translate(psPos) + * Matrix4x4.Scale(scale) + * Matrix4x4.Translate(-psPos); + } + } +#endif + + switch (space) + { + case ParticleSystemSimulationSpace.Local: + return Matrix4x4.Translate(psPos) + * Matrix4x4.Scale(scale); + case ParticleSystemSimulationSpace.World: + return Matrix4x4.Scale(scale); + case ParticleSystemSimulationSpace.Custom: + return Matrix4x4.Translate(_particleSystem.main.customSimulationSpace.position.GetScaled(scale)) + //* Matrix4x4.Translate(wpos) + * Matrix4x4.Scale(scale) + //* Matrix4x4.Translate(-wpos) + ; + default: + throw new System.NotSupportedException(); + } + } + + private void Simulate(Vector3 scale, bool paused) + { + var main = _particleSystem.main; + var deltaTime = paused + ? 0 + : main.useUnscaledTime + ? Time.unscaledDeltaTime + : Time.deltaTime; + + // Prewarm: + if (0 < deltaTime && _prewarm) + { + deltaTime += main.duration; + _prewarm = false; + } + + // Normal simulation for non-scaling or local spacing. + var isScaling = scale != Vector3.one; + if (!isScaling || _particleSystem.GetActualSimulationSpace() == ParticleSystemSimulationSpace.Local) + { + _particleSystem.Simulate(deltaTime, false, false, false); + return; + } + + // get world position. + var psTransform = _particleSystem.transform; + var originWorldPosition = psTransform.position; + var originWorldRotation = psTransform.rotation; + + var emission = _particleSystem.emission; + var rateOverDistance = emission.enabled && 0 < emission.rateOverDistance.constant && 0 < emission.rateOverDistanceMultiplier; + if (rateOverDistance) + { + // (For rate-over-distance emission,) Move to previous scaled position, simulate (delta = 0). + Vector3 prevScaledPos = _prevPsPos.GetScaled(_prevScale.Inverse()); + psTransform.SetPositionAndRotation(prevScaledPos, originWorldRotation); + _particleSystem.Simulate(0, false, false, false); + } + + // Move to scaled position, simulate, revert to origin position. + var scaledPos = originWorldPosition.GetScaled(scale.Inverse()); + psTransform.SetPositionAndRotation(scaledPos, originWorldRotation); + _particleSystem.Simulate(deltaTime, false, false, false); + psTransform.SetPositionAndRotation(originWorldPosition, originWorldRotation); + } + +#if UNITY_EDITOR + private void SimulateForEditor(Vector3 diffPos, Vector3 scale) + { + // Extra world simulation. + if (_particleSystem.main.simulationSpace == ParticleSystemSimulationSpace.World && 0 < Vector3.SqrMagnitude(diffPos)) + { + Profiler.BeginSample("[UIParticle] Bake Mesh > Extra world simulation"); + diffPos.x *= 1f - 1f / Mathf.Max(0.001f, scale.x); + diffPos.y *= 1f - 1f / Mathf.Max(0.001f, scale.y); + diffPos.z *= 1f - 1f / Mathf.Max(0.001f, scale.z); + + var count = _particleSystem.particleCount; + if (s_Particles.Length < count) + { + var size = Mathf.NextPowerOfTwo(count); + s_Particles = new ParticleSystem.Particle[size]; + } + + _particleSystem.GetParticles(s_Particles); + for (var j = 0; j < count; j++) + { + var p = s_Particles[j]; + p.position += diffPos; + s_Particles[j] = p; + } + + _particleSystem.SetParticles(s_Particles, count); + Profiler.EndSample(); + } + } +#endif + + private void UpdateMaterialProperties() + { + if (_parent.m_AnimatableProperties.Length == 0) return; + + if (s_Mpb == null) + s_Mpb = new MaterialPropertyBlock(); + + _renderer.GetPropertyBlock(s_Mpb); + if (s_Mpb.isEmpty) return; + + // #41: Copy the value from MaterialPropertyBlock to CanvasRenderer + if (!_modifiedMaterial) return; + + foreach (var ap in _parent.m_AnimatableProperties) + { + ap.UpdateMaterialProperties(_modifiedMaterial, s_Mpb); + } + + s_Mpb.Clear(); + } + } +} \ No newline at end of file diff --git a/Scripts/MeshHelper.cs.meta b/Scripts/UIParticleRenderer.cs.meta similarity index 83% rename from Scripts/MeshHelper.cs.meta rename to Scripts/UIParticleRenderer.cs.meta index a903bf0..2f3f29c 100644 --- a/Scripts/MeshHelper.cs.meta +++ b/Scripts/UIParticleRenderer.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: f689ea5a2e9f140288c8874127aa9ee0 +guid: 0e66d2d1ba43c4cc4bc3e754e403297b MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Scripts/UIParticleUpdater.cs b/Scripts/UIParticleUpdater.cs old mode 100755 new mode 100644 index 129965e..0ccaed6 --- a/Scripts/UIParticleUpdater.cs +++ b/Scripts/UIParticleUpdater.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Coffee.UIParticleExtensions; using UnityEngine; using UnityEngine.Profiling; @@ -9,11 +8,8 @@ namespace Coffee.UIExtensions internal static class UIParticleUpdater { static readonly List s_ActiveParticles = new List(); - static MaterialPropertyBlock s_Mpb; - static ParticleSystem.Particle[] s_Particles = new ParticleSystem.Particle[2048]; private static int frameCount = 0; - public static void Register(UIParticle particle) { if (!particle) return; @@ -32,10 +28,6 @@ namespace Coffee.UIExtensions [RuntimeInitializeOnLoadMethod] private static void InitializeOnLoad() { - MeshHelper.Init(); - MeshPool.Init(); - CombineInstanceArrayPool.Init(); - Canvas.willRenderCanvases -= Refresh; Canvas.willRenderCanvases += Refresh; } @@ -51,7 +43,7 @@ namespace Coffee.UIExtensions { try { - Refresh(s_ActiveParticles[i]); + s_ActiveParticles[i].UpdateRenderers(); } catch (Exception e) { @@ -61,235 +53,5 @@ namespace Coffee.UIExtensions Profiler.EndSample(); } - - private static void Refresh(UIParticle particle) - { - if (!particle || !particle.bakedMesh || !particle.canvas || !particle.canvasRenderer) return; - - Profiler.BeginSample("[UIParticle] Modify scale"); - ModifyScale(particle); - Profiler.EndSample(); - - Profiler.BeginSample("[UIParticle] Bake mesh"); - BakeMesh(particle); - Profiler.EndSample(); - - // if (QualitySettings.activeColorSpace == ColorSpace.Linear) - // { - // Profiler.BeginSample("[UIParticle] Modify color space to linear"); - // particle.bakedMesh.ModifyColorSpaceToLinear(); - // Profiler.EndSample(); - // } - - Profiler.BeginSample("[UIParticle] Set mesh to CanvasRenderer"); - particle.canvasRenderer.SetMesh(particle.bakedMesh); - Profiler.EndSample(); - - Profiler.BeginSample("[UIParticle] Update Animatable Material Properties"); - particle.UpdateMaterialProperties(); - Profiler.EndSample(); - } - - private static void ModifyScale(UIParticle particle) - { - if (!particle.ignoreCanvasScaler || !particle.canvas) return; - - // Ignore Canvas scaling. - var s = particle.canvas.rootCanvas.transform.localScale; - var modifiedScale = new Vector3( - Mathf.Approximately(s.x, 0) ? 1 : 1 / s.x, - Mathf.Approximately(s.y, 0) ? 1 : 1 / s.y, - Mathf.Approximately(s.z, 0) ? 1 : 1 / s.z); - - // Scale is already modified. - var transform = particle.transform; - if (Mathf.Approximately((transform.localScale - modifiedScale).sqrMagnitude, 0)) return; - - transform.localScale = modifiedScale; - } - - private static Matrix4x4 GetScaledMatrix(ParticleSystem particle) - { - var transform = particle.transform; - var main = particle.main; - var space = main.simulationSpace; - if (space == ParticleSystemSimulationSpace.Custom && !main.customSimulationSpace) - space = ParticleSystemSimulationSpace.Local; - - switch (space) - { - case ParticleSystemSimulationSpace.Local: - return Matrix4x4.Rotate(transform.rotation).inverse - * Matrix4x4.Scale(transform.lossyScale).inverse; - case ParticleSystemSimulationSpace.World: - return transform.worldToLocalMatrix; - case ParticleSystemSimulationSpace.Custom: - // #78: Support custom simulation space. - return transform.worldToLocalMatrix - * Matrix4x4.Translate(main.customSimulationSpace.position); - default: - return Matrix4x4.identity; - } - } - - private static void BakeMesh(UIParticle particle) - { - // Clear mesh before bake. - Profiler.BeginSample("[UIParticle] Bake Mesh > Clear mesh before bake"); - MeshHelper.Clear(); - particle.bakedMesh.Clear(false); - Profiler.EndSample(); - - // Get camera for baking mesh. - var camera = BakingCamera.GetCamera(particle.canvas); - var root = particle.transform; - var rootMatrix = Matrix4x4.Rotate(root.rotation).inverse - * Matrix4x4.Scale(root.lossyScale).inverse; - var scale = particle.ignoreCanvasScaler - ? Vector3.Scale(particle.canvas.rootCanvas.transform.localScale, particle.scale3D) - : particle.scale3D; - var scaleMatrix = Matrix4x4.Scale(scale); - - // Cache position - var position = particle.transform.position; - var diff = position - particle.cachedPosition; - diff.x *= 1f - 1f / Mathf.Max(0.001f, scale.x); - diff.y *= 1f - 1f / Mathf.Max(0.001f, scale.y); - diff.z *= 1f - 1f / Mathf.Max(0.001f, scale.z); - - particle.cachedPosition = position; - - if (particle.activeMeshIndices.CountFast() == 0) - diff = Vector3.zero; - - for (var i = 0; i < particle.particles.Count; i++) - { - Profiler.BeginSample("[UIParticle] Bake Mesh > Push index"); - MeshHelper.activeMeshIndices.Add(false); - MeshHelper.activeMeshIndices.Add(false); - Profiler.EndSample(); - - // No particle to render. - var currentPs = particle.particles[i]; - if (!currentPs || !currentPs.IsAlive() || currentPs.particleCount == 0) continue; - var r = currentPs.GetComponent(); - if (!r.sharedMaterial && !r.trailMaterial) continue; - - // Calc matrix. - Profiler.BeginSample("[UIParticle] Bake Mesh > Calc matrix"); - var matrix = rootMatrix; - if (currentPs.transform != root) - { - if (currentPs.main.simulationSpace == ParticleSystemSimulationSpace.Local) - { - var relativePos = root.InverseTransformPoint(currentPs.transform.position); - matrix = Matrix4x4.Translate(relativePos) * matrix; - } - else - { - matrix = matrix * Matrix4x4.Translate(-root.position); - } - } - else - { - matrix = GetScaledMatrix(currentPs); - } - - matrix = scaleMatrix * matrix; - Profiler.EndSample(); - - // Extra world simulation. - if (currentPs.main.simulationSpace == ParticleSystemSimulationSpace.World && 0 < diff.sqrMagnitude) - { - Profiler.BeginSample("[UIParticle] Bake Mesh > Extra world simulation"); - var count = currentPs.particleCount; - if (s_Particles.Length < count) - { - var size = Mathf.NextPowerOfTwo(count); - s_Particles = new ParticleSystem.Particle[size]; - } - - currentPs.GetParticles(s_Particles); - for (var j = 0; j < count; j++) - { - var p = s_Particles[j]; - p.position += diff; - s_Particles[j] = p; - } - - currentPs.SetParticles(s_Particles, count); - Profiler.EndSample(); - } - -#if UNITY_2018_3_OR_NEWER - // #102: Do not bake particle system to mesh when the alpha is zero. - if (Mathf.Approximately(particle.canvasRenderer.GetInheritedAlpha(), 0)) - continue; -#endif - - // Bake main particles. - if (CanBakeMesh(r)) - { - Profiler.BeginSample("[UIParticle] Bake Mesh > Bake Main Particles"); - var hash = currentPs.GetMaterialHash(false); - if (hash != 0) - { - var m = MeshHelper.GetTemporaryMesh(); - r.BakeMesh(m, camera, true); - MeshHelper.Push(i * 2, hash, m, matrix); - } - - Profiler.EndSample(); - } - - // Bake trails particles. - if (currentPs.trails.enabled) - { - Profiler.BeginSample("[UIParticle] Bake Mesh > Bake Trails Particles"); - var hash = currentPs.GetMaterialHash(true); - if (hash != 0) - { - matrix = currentPs.main.simulationSpace == ParticleSystemSimulationSpace.Local && currentPs.trails.worldSpace - ? matrix * Matrix4x4.Translate(-currentPs.transform.position) - : matrix; - - var m = MeshHelper.GetTemporaryMesh(); - try - { - r.BakeTrailsMesh(m, camera, true); - MeshHelper.Push(i * 2 + 1, hash, m, matrix); - } - catch - { - MeshHelper.DiscardTemporaryMesh(m); - } - } - - Profiler.EndSample(); - } - } - - // Set active indices. - Profiler.BeginSample("[UIParticle] Bake Mesh > Set active indices"); - particle.activeMeshIndices = MeshHelper.activeMeshIndices; - Profiler.EndSample(); - - // Combine - Profiler.BeginSample("[UIParticle] Bake Mesh > CombineMesh"); - MeshHelper.CombineMesh(particle.bakedMesh); - MeshHelper.Clear(); - Profiler.EndSample(); - } - - private static bool CanBakeMesh(ParticleSystemRenderer renderer) - { - // #69: Editor crashes when mesh is set to null when `ParticleSystem.RenderMode = Mesh` - if (renderer.renderMode == ParticleSystemRenderMode.Mesh && renderer.mesh == null) return false; - - // #61: When `ParticleSystem.RenderMode = None`, an error occurs - if (renderer.renderMode == ParticleSystemRenderMode.None) return false; - - return true; - } } } diff --git a/Scripts/Utils.cs b/Scripts/Utils.cs index 4a6c3c3..934b7a7 100644 --- a/Scripts/Utils.cs +++ b/Scripts/Utils.cs @@ -6,6 +6,43 @@ using Object = UnityEngine.Object; namespace Coffee.UIParticleExtensions { + public static class Vector3Extensions + { + public static Vector3 Inverse(this Vector3 self) + { + self.x = Mathf.Approximately(self.x, 0) ? 1 : 1 / self.x; + self.y = Mathf.Approximately(self.y, 0) ? 1 : 1 / self.y; + self.z = Mathf.Approximately(self.z, 0) ? 1 : 1 / self.z; + return self; + } + + public static Vector3 GetScaled(this Vector3 self, Vector3 other1) + { + self.Scale(other1); + return self; + } + + public static Vector3 GetScaled(this Vector3 self, Vector3 other1, Vector3 other2) + { + self.Scale(other1); + self.Scale(other2); + return self; + } + + public static Vector3 GetScaled(this Vector3 self, Vector3 other1, Vector3 other2, Vector3 other3) + { + self.Scale(other1); + self.Scale(other2); + self.Scale(other3); + return self; + } + + public static bool IsVisible(this Vector3 self) + { + return 0 < Mathf.Abs(self.x * self.y * self.z); + } + } + internal static class SpriteExtensions { #if UNITY_EDITOR @@ -21,7 +58,7 @@ namespace Coffee.UIParticleExtensions if (!self) return null; if (Application.isPlaying) return self.texture; - var ret = miGetActiveAtlasTexture.Invoke(null, new[] {self}) as Texture2D; + var ret = miGetActiveAtlasTexture.Invoke(null, new[] { self }) as Texture2D; return ret ? ret : self.texture; } #else @@ -32,181 +69,29 @@ namespace Coffee.UIParticleExtensions #endif } - internal static class ListExtensions + public static class ParticleSystemExtensions { - public static bool SequenceEqualFast(this List self, List value) + public static bool CanBakeMesh(this ParticleSystemRenderer self) { - if (self.Count != value.Count) return false; - for (var i = 0; i < self.Count; ++i) - { - if (self[i] != value[i]) return false; - } + // #69: Editor crashes when mesh is set to null when `ParticleSystem.RenderMode = Mesh` + if (self.renderMode == ParticleSystemRenderMode.Mesh && self.mesh == null) return false; + + // #61: When `ParticleSystem.RenderMode = None`, an error occurs + if (self.renderMode == ParticleSystemRenderMode.None) return false; return true; } - public static int CountFast(this List self) + public static ParticleSystemSimulationSpace GetActualSimulationSpace(this ParticleSystem self) { - var count = 0; - for (var i = 0; i < self.Count; ++i) - { - if (self[i]) count++; - } + var main = self.main; + var space = main.simulationSpace; + if (space == ParticleSystemSimulationSpace.Custom && !main.customSimulationSpace) + space = ParticleSystemSimulationSpace.Local; - return count; + return space; } - public static bool AnyFast(this List self) where T : Object - { - for (var i = 0; i < self.Count; ++i) - { - if (self[i]) return true; - } - - return false; - } - - public static bool AnyFast(this List self, Predicate predicate) where T : Object - { - for (var i = 0; i < self.Count; ++i) - { - if (self[i] && predicate(self[i])) return true; - } - - return false; - } - } - - internal static class MeshExtensions - { - // static readonly List s_Colors = new List(); - - // public static void ModifyColorSpaceToLinear(this Mesh self) - // { - // self.GetColors(s_Colors); - // - // for (var i = 0; i < s_Colors.Count; i++) - // s_Colors[i] = ((Color) s_Colors[i]).gamma; - // - // self.SetColors(s_Colors); - // s_Colors.Clear(); - // } - - public static void Clear(this CombineInstance[] self) - { - for (var i = 0; i < self.Length; i++) - { - MeshPool.Return(self[i].mesh); - self[i].mesh = null; - } - } - } - - internal static class MeshPool - { - private static readonly Stack s_Pool = new Stack(32); - private static readonly HashSet s_HashPool = new HashSet(); - - public static void Init() - { - } - - static MeshPool() - { - for (var i = 0; i < 32; i++) - { - var m = new Mesh(); - m.MarkDynamic(); - s_Pool.Push(m); - s_HashPool.Add(m.GetInstanceID()); - } - } - - public static Mesh Rent() - { - Mesh m; - while (0 < s_Pool.Count) - { - m = s_Pool.Pop(); - if (m) - { - s_HashPool.Remove(m.GetInstanceID()); - return m; - } - } - - m = new Mesh(); - m.MarkDynamic(); - return m; - } - - public static void Return(Mesh mesh) - { - if (!mesh) return; - - var id = mesh.GetInstanceID(); - if (s_HashPool.Contains(id)) return; - - mesh.Clear(false); - s_Pool.Push(mesh); - s_HashPool.Add(id); - } - } - - internal static class CombineInstanceArrayPool - { - private static readonly Dictionary s_Pool; - - public static void Init() - { - s_Pool.Clear(); - } - - static CombineInstanceArrayPool() - { - s_Pool = new Dictionary(); - } - - public static CombineInstance[] Get(List src) - { - CombineInstance[] dst; - var count = src.Count; - if (!s_Pool.TryGetValue(count, out dst)) - { - dst = new CombineInstance[count]; - s_Pool.Add(count, dst); - } - - for (var i = 0; i < src.Count; i++) - { - dst[i].mesh = src[i].mesh; - dst[i].transform = src[i].transform; - } - - return dst; - } - - public static CombineInstance[] Get(List src, int count) - { - CombineInstance[] dst; - if (!s_Pool.TryGetValue(count, out dst)) - { - dst = new CombineInstance[count]; - s_Pool.Add(count, dst); - } - - for (var i = 0; i < count; i++) - { - dst[i].mesh = src[i].mesh; - dst[i].transform = src[i].transform; - } - - return dst; - } - } - - internal static class ParticleSystemExtensions - { public static void SortForRendering(this List self, Transform transform, bool sortByMaterial) { self.Sort((a, b) => @@ -242,9 +127,9 @@ namespace Coffee.UIParticleExtensions var aPos = tr.InverseTransformPoint(aTransform.position).z + aRenderer.sortingFudge; var bPos = tr.InverseTransformPoint(bTransform.position).z + bRenderer.sortingFudge; if (!Mathf.Approximately(aPos, bPos)) - return (int) Mathf.Sign(bPos - aPos); + return (int)Mathf.Sign(bPos - aPos); - return (int) Mathf.Sign(GetIndex(self, a) - GetIndex(self, b)); + return (int)Mathf.Sign(GetIndex(self, a) - GetIndex(self, b)); }); } @@ -258,19 +143,6 @@ namespace Coffee.UIParticleExtensions return 0; } - public static long GetMaterialHash(this ParticleSystem self, bool trail) - { - if (!self) return 0; - - var r = self.GetComponent(); - var mat = trail ? r.trailMaterial : r.sharedMaterial; - - if (!mat) return 0; - - var tex = trail ? null : self.GetTextureForSprite(); - return ((long) mat.GetHashCode() << 32) + (tex ? tex.GetHashCode() : 0); - } - public static Texture2D GetTextureForSprite(this ParticleSystem self) { if (!self) return null;