From 47b29fa55a1e0257c1e7401875f668231d878385 Mon Sep 17 00:00:00 2001 From: mob-sakai Date: Tue, 21 Sep 2021 16:38:55 +0900 Subject: [PATCH] feat: improve extra world simulation --- Scripts/UIParticle.cs | 7 - Scripts/UIParticleUpdater.cs | 296 +++++++++++++++++++++++------------ Scripts/Utils.cs | 24 +++ 3 files changed, 222 insertions(+), 105 deletions(-) diff --git a/Scripts/UIParticle.cs b/Scripts/UIParticle.cs index fe7f569..c6d4982 100755 --- a/Scripts/UIParticle.cs +++ b/Scripts/UIParticle.cs @@ -44,7 +44,6 @@ namespace Coffee.UIExtensions 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(); @@ -123,12 +122,6 @@ namespace Coffee.UIExtensions } } - internal Vector3 cachedPosition - { - get { return _cachedPosition; } - set { _cachedPosition = value; } - } - public void Play() { particles.Exec(p => p.Play()); diff --git a/Scripts/UIParticleUpdater.cs b/Scripts/UIParticleUpdater.cs index 76de20a..1297072 100755 --- a/Scripts/UIParticleUpdater.cs +++ b/Scripts/UIParticleUpdater.cs @@ -6,11 +6,42 @@ using UnityEngine.Profiling; namespace Coffee.UIExtensions { + internal static class WorldPositionCache + { + private static readonly Dictionary _cache = new Dictionary(); + private static readonly HashSet _tmp = new HashSet(); + + public static bool TryGetCachedWorldPosition(Transform transform, out Vector3 wp) + { + return _cache.TryGetValue(transform, out wp); + } + + public static void CacheWorldPosition(Transform transform, Vector3 wp) + { + _cache[transform] = wp; + } + + public static void Refresh() + { + foreach (var tr in _cache.Keys) + { + if (!tr) + _tmp.Add(tr); + } + + foreach (var tr in _tmp) + { + _cache.Remove(tr); + } + + _tmp.Clear(); + } + } + 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; @@ -70,13 +101,6 @@ namespace Coffee.UIExtensions 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(); @@ -84,30 +108,8 @@ namespace Coffee.UIExtensions Profiler.BeginSample("[UIParticle] Update Animatable Material Properties"); particle.UpdateMaterialProperties(); Profiler.EndSample(); - } - 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; - } + WorldPositionCache.Refresh(); } private static void BakeMesh(UIParticle particle) @@ -118,26 +120,48 @@ namespace Coffee.UIExtensions particle.bakedMesh.Clear(false); Profiler.EndSample(); - 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); +#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)) return; +#endif - // 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); + // No particle to render (scale). + { + var scale = Vector3.Scale(particle.transform.lossyScale, particle.scale3D); + if (Mathf.Approximately(scale.x * scale.y * scale.z, 0)) return; + } - particle.cachedPosition = position; - if (particle.activeMeshIndices.CountFast() == 0) - diff = Vector3.zero; + var scale3D = particle.scale3D + .GetScaled(particle.transform.lossyScale) + .GetScaled(particle.canvas.transform.lossyScale.Inverse()); + // Correct sub-particle systems. + var subTransMats = new Dictionary(); + for (var i = 0; i < particle.particles.Count; i++) + { + var currentPs = particle.particles[i]; + if (!currentPs) continue; + + var subEmitters = currentPs.subEmitters; + if (!subEmitters.enabled || subEmitters.subEmittersCount == 0) continue; + + var currentTr = currentPs.transform; + for (var j = 0; j < subEmitters.subEmittersCount; j++) + { + var subPs = subEmitters.GetSubEmitterSystem(j); + if (!subPs || subPs.main.simulationSpace != ParticleSystemSimulationSpace.Local) continue; + + var m = particle.transform.worldToLocalMatrix * currentTr.localToWorldMatrix; + m = currentTr.localToWorldMatrix; + var pos = currentTr.InverseTransformPoint(subPs.transform.position) + .GetScaled(Vector3.one - scale3D.Inverse()); + subTransMats.Add(subPs, Matrix4x4.Translate(m * pos)); + } + } + + var worldToUip = particle.transform.worldToLocalMatrix; + var camera = particle.canvas.renderMode != RenderMode.ScreenSpaceOverlay ? particle.canvas.worldCamera : null; for (var i = 0; i < particle.particles.Count; i++) { Profiler.BeginSample("[UIParticle] Bake Mesh > Push index"); @@ -145,64 +169,61 @@ namespace Coffee.UIExtensions MeshHelper.activeMeshIndices.Add(false); Profiler.EndSample(); - // No particle to render. + // No particle to render (active). var currentPs = particle.particles[i]; - if (!currentPs || !currentPs.IsAlive() || currentPs.particleCount == 0) continue; + if (!currentPs || !currentPs.IsAlive()) continue; + + // No particle to render (scale). + switch (currentPs.main.scalingMode) + { + case ParticleSystemScalingMode.Hierarchy: + { + var scalePs = currentPs.transform.lossyScale; + if (Mathf.Approximately(scalePs.x * scalePs.y * scalePs.z, 0)) continue; + } + break; + case ParticleSystemScalingMode.Local: + { + var scalePs = currentPs.transform.localScale; + if (Mathf.Approximately(scalePs.x * scalePs.y * scalePs.z, 0)) continue; + } + break; + case ParticleSystemScalingMode.Shape: + break; + } + + // Simulate particles + Profiler.BeginSample("[UIParticle] Bake Mesh > Simulate particles"); + Simulate(currentPs, scale3D); + Profiler.EndSample(); + + // No particle to render (particle count). + if (currentPs.particleCount == 0) + { + if (currentPs.main.duration <= currentPs.time) + { + currentPs.Stop(false); + } + + continue; + } + + // No particle to render (material). var r = currentPs.GetComponent(); - if (!r.sharedMaterial && !r.trailMaterial) continue; + if (!r || (!r.sharedMaterial && !r.trailMaterial)) continue; // Calc matrix. Profiler.BeginSample("[UIParticle] Bake Mesh > Calc matrix"); - var matrix = rootMatrix; - if (currentPs.transform != root) + + var matrix = worldToUip * GetScaledMatrix(currentPs, scale3D); + Matrix4x4 extraMat; + if (subTransMats.TryGetValue(currentPs, out extraMat)) { - 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 *= extraMat; } - 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)) { @@ -229,9 +250,10 @@ namespace Coffee.UIExtensions var hash = currentPs.GetMaterialHash(true); if (hash != 0) { - matrix = currentPs.main.simulationSpace == ParticleSystemSimulationSpace.Local && currentPs.trails.worldSpace - ? matrix * Matrix4x4.Translate(-currentPs.transform.position) - : matrix; + if (currentPs.trails.worldSpace) + { + matrix = worldToUip * Matrix4x4.Scale(scale3D); + } var m = MeshHelper.GetTemporaryMesh(); try @@ -265,6 +287,84 @@ namespace Coffee.UIExtensions Profiler.EndSample(); } + private static ParticleSystemSimulationSpace GetSimulationSpace(ParticleSystem ps) + { + var main = ps.main; + var space = main.simulationSpace; + if (space == ParticleSystemSimulationSpace.Custom && !main.customSimulationSpace) + space = ParticleSystemSimulationSpace.Local; + + return space; + } + + private static Matrix4x4 GetScaledMatrix(ParticleSystem ps, Vector3 scale) + { + switch (GetSimulationSpace(ps)) + { + case ParticleSystemSimulationSpace.Local: + return Matrix4x4.Translate(ps.transform.position) + * Matrix4x4.Scale(scale); + case ParticleSystemSimulationSpace.Custom: + return Matrix4x4.Translate(ps.main.customSimulationSpace.position) + * Matrix4x4.Scale(scale); + case ParticleSystemSimulationSpace.World: + return Matrix4x4.Scale(scale); + default: + throw new ArgumentOutOfRangeException(); + } + } + + private static void Simulate(ParticleSystem currentPs, Vector3 scale) + { + if (!currentPs || !currentPs.IsAlive()) return; + + var main = currentPs.main; + var deltaTime = main.useUnscaledTime ? Time.unscaledDeltaTime : Time.deltaTime; + var isScaling = scale != Vector3.one; + var space = GetSimulationSpace(currentPs); + + // non-scale or local + if (!isScaling || space == ParticleSystemSimulationSpace.Local) + { + currentPs.Simulate(deltaTime, false, false, false); + return; + } + + // get world position. + var rateOverDistance = true; + var psTransform = currentPs.transform; + var originWorldPosition = psTransform.position; + var originWorldRotation = psTransform.rotation; + var wp = originWorldPosition; + if (space == ParticleSystemSimulationSpace.Custom) + { + var emission = currentPs.emission; + rateOverDistance = emission.enabled && 0 < emission.rateOverDistance.constant && 0 < emission.rateOverDistanceMultiplier; + wp += Vector3.Scale(main.customSimulationSpace.position, scale - Vector3.one); + } + + // inverse scaling + wp.Scale(scale.Inverse()); + + // rateOverDistance issue + Vector3 oldWp; + if (rateOverDistance) + { + if (WorldPositionCache.TryGetCachedWorldPosition(psTransform, out oldWp)) + psTransform.SetPositionAndRotation(oldWp, originWorldRotation); + else + psTransform.SetPositionAndRotation(wp, originWorldRotation); + + currentPs.Simulate(0, false, false, false); + } + + // cache position + psTransform.SetPositionAndRotation(wp, originWorldRotation); + WorldPositionCache.CacheWorldPosition(psTransform, wp); + currentPs.Simulate(deltaTime, false, false, false); + psTransform.SetPositionAndRotation(originWorldPosition, originWorldRotation); + } + private static bool CanBakeMesh(ParticleSystemRenderer renderer) { // #69: Editor crashes when mesh is set to null when `ParticleSystem.RenderMode = Mesh` diff --git a/Scripts/Utils.cs b/Scripts/Utils.cs index 7d7b802..44b47d5 100644 --- a/Scripts/Utils.cs +++ b/Scripts/Utils.cs @@ -6,6 +6,30 @@ using Object = UnityEngine.Object; namespace Coffee.UIParticleExtensions { + internal 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; + } + } + internal static class SpriteExtensions { #if UNITY_EDITOR