feat: improve extra world simulation
parent
39c001f935
commit
47b29fa55a
|
@ -44,7 +44,6 @@ namespace Coffee.UIExtensions
|
|||
private readonly List<Material> _modifiedMaterials = new List<Material>();
|
||||
private readonly List<Material> _maskMaterials = new List<Material>();
|
||||
private readonly List<bool> _activeMeshIndices = new List<bool>();
|
||||
private Vector3 _cachedPosition;
|
||||
private static readonly List<Material> s_TempMaterials = new List<Material>(2);
|
||||
private static MaterialPropertyBlock s_Mpb;
|
||||
private static readonly List<Material> s_PrevMaskMaterials = new List<Material>();
|
||||
|
@ -123,12 +122,6 @@ namespace Coffee.UIExtensions
|
|||
}
|
||||
}
|
||||
|
||||
internal Vector3 cachedPosition
|
||||
{
|
||||
get { return _cachedPosition; }
|
||||
set { _cachedPosition = value; }
|
||||
}
|
||||
|
||||
public void Play()
|
||||
{
|
||||
particles.Exec(p => p.Play());
|
||||
|
|
|
@ -6,11 +6,42 @@ using UnityEngine.Profiling;
|
|||
|
||||
namespace Coffee.UIExtensions
|
||||
{
|
||||
internal static class WorldPositionCache
|
||||
{
|
||||
private static readonly Dictionary<Transform, Vector3> _cache = new Dictionary<Transform, Vector3>();
|
||||
private static readonly HashSet<Transform> _tmp = new HashSet<Transform>();
|
||||
|
||||
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<UIParticle> s_ActiveParticles = new List<UIParticle>();
|
||||
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<ParticleSystem, Matrix4x4>();
|
||||
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<ParticleSystemRenderer>();
|
||||
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`
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue