Refactor and further generalization of particle model

- Collapsed the per-particle data into a single struct, and
  use that to communicate with drawables, too. Let the drawables
  manage allocation of xforms, colors, etc. Helpful for non-atlas
  drawables, and just to keep the effect code simpler.
- Having all of the params in a single struct allows us to move
  the remaining animated behaviors into affectors (color/frame).
- Added SkColorCurve, which works like SkCurve for SkColor4f.
  Use that to create a color affector (rather than simple
  start/end colors in the effect params).
- Also put the stable random in SkParticleState. This is going
  to be necessary if/when we change affectors to operate on all
  particles (rather than one at a time). Still need to move t
  value into the particle struct (or eval it from the lifetime
  params on demand).

Change-Id: Icf39116acbfd5d6e8eb91e9affbd8898d106211d
Reviewed-on: https://skia-review.googlesource.com/c/193473
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
diff --git a/modules/particles/src/SkParticleEffect.cpp b/modules/particles/src/SkParticleEffect.cpp
index 39b8dd6..4dfb7c2 100644
--- a/modules/particles/src/SkParticleEffect.cpp
+++ b/modules/particles/src/SkParticleEffect.cpp
@@ -11,7 +11,6 @@
 #include "SkAnimTimer.h"
 #include "SkCanvas.h"
 #include "SkColorData.h"
-#include "SkNx.h"
 #include "SkPaint.h"
 #include "SkParticleAffector.h"
 #include "SkParticleDrawable.h"
@@ -24,8 +23,6 @@
     v->visit("Duration", fEffectDuration);
     v->visit("Rate", fRate);
     v->visit("Life", fLifetime);
-    v->visit("StartColor", fStartColor);
-    v->visit("EndColor", fEndColor);
 
     v->visit("Drawable", fDrawable);
     v->visit("Emitter", fEmitter);
@@ -66,9 +63,6 @@
     float deltaTime = static_cast<float>(now - fLastTime);
     fLastTime = now;
 
-    Sk4f startColor = Sk4f::Load(fParams->fStartColor.vec());
-    Sk4f colorScale = Sk4f::Load(fParams->fEndColor.vec()) - startColor;
-
     SkParticleUpdateParams updateParams;
     updateParams.fDeltaTime = deltaTime;
     updateParams.fRandom = &fRandom;
@@ -77,9 +71,8 @@
     for (int i = 0; i < fCount; ++i) {
         if (now > fParticles[i].fTimeOfDeath) {
             // NOTE: This is fast, but doesn't preserve drawing order. Could be a problem...
-            fParticles[i] = fParticles[fCount - 1];
-            fFrames[i]    = fFrames[fCount - 1];
-            fColors[i]    = fColors[fCount - 1];
+            fParticles[i]     = fParticles[fCount - 1];
+            fStableRandoms[i] = fStableRandoms[fCount - 1];
             --i;
             --fCount;
         }
@@ -91,24 +84,24 @@
     fSpawnRemainder = desired - numToSpawn;
     numToSpawn = SkTPin(numToSpawn, 0, fParams->fMaxCount - fCount);
     if (fParams->fEmitter) {
-        // No, this isn't "stable", but spawn affectors are only run once anyway.
-        // Would it ever make sense to give the same random to all particles spawned on a given
-        // frame? Having a hard time thinking when that would be useful.
-        updateParams.fStableRandom = &fRandom;
-        // ... and this isn't "particle" t, it's effect t.
+        // This isn't "particle" t, it's effect t.
         double t = (now - fSpawnTime) / fParams->fEffectDuration;
         updateParams.fParticleT = static_cast<float>(fLooping ? fmod(t, 1.0) : SkTPin(t, 0.0, 1.0));
 
         for (int i = 0; i < numToSpawn; ++i) {
+            // Mutate our SkRandom so each particle definitely gets a different stable generator
+            fRandom.nextU();
+
             fParticles[fCount].fTimeOfBirth = now;
             fParticles[fCount].fTimeOfDeath = now + fParams->fLifetime.eval(updateParams.fParticleT,
                                                                             fRandom);
-            fParticles[fCount].fPV.fPose = fParams->fEmitter->emit(fRandom);
-            fParticles[fCount].fPV.fVelocity.fLinear = { 0.0f, 0.0f };
-            fParticles[fCount].fPV.fVelocity.fAngular = 0.0f;
+            fParticles[fCount].fPose = fParams->fEmitter->emit(fRandom);
+            fParticles[fCount].fVelocity.fLinear = { 0.0f, 0.0f };
+            fParticles[fCount].fVelocity.fAngular = 0.0f;
+            fParticles[fCount].fColor = { 1.0f, 1.0f, 1.0f, 1.0f };
+            fParticles[fCount].fFrame = 0.0f;
 
-            fParticles[fCount].fStableRandom = fRandom;
-            fFrames[fCount] = 0.0f;
+            fParticles[fCount].fStableRandom = fStableRandoms[fCount] = fRandom;
             fCount++;
         }
 
@@ -116,47 +109,37 @@
         for (int i = fCount - numToSpawn; i < fCount; ++i) {
             for (auto affector : fParams->fSpawnAffectors) {
                 if (affector) {
-                    affector->apply(updateParams, fParticles[i].fPV);
+                    affector->apply(updateParams, fParticles[i]);
                 }
             }
         }
     }
 
+    // Restore all stable random generators so update affectors get consistent behavior each frame
+    for (int i = 0; i < fCount; ++i) {
+        fParticles[i].fStableRandom = fStableRandoms[i];
+    }
+
     // Apply update rules
     for (int i = 0; i < fCount; ++i) {
         // Compute fraction of lifetime that's elapsed
-        float t = static_cast<float>((now - fParticles[i].fTimeOfBirth) /
-            (fParticles[i].fTimeOfDeath - fParticles[i].fTimeOfBirth));
-
-        SkRandom stableRandom = fParticles[i].fStableRandom;
-        updateParams.fStableRandom = &stableRandom;
-        updateParams.fParticleT = t;
-
-        // Set sprite frame by lifetime (TODO: Remove, add affector)
-        fFrames[i] = t;
-
-        // Set color by lifetime
-        fColors[i] = Sk4f_toL32(swizzle_rb(startColor + (colorScale * t)));
+        updateParams.fParticleT =
+            static_cast<float>((now - fParticles[i].fTimeOfBirth) /
+                               (fParticles[i].fTimeOfDeath - fParticles[i].fTimeOfBirth));
 
         for (auto affector : fParams->fUpdateAffectors) {
             if (affector) {
-                affector->apply(updateParams, fParticles[i].fPV);
+                affector->apply(updateParams, fParticles[i]);
             }
         }
 
         // Integrate position / orientation
-        fParticles[i].fPV.fPose.fPosition += fParticles[i].fPV.fVelocity.fLinear * deltaTime;
+        fParticles[i].fPose.fPosition += fParticles[i].fVelocity.fLinear * deltaTime;
 
-        SkScalar c, s = SkScalarSinCos(fParticles[i].fPV.fVelocity.fAngular * deltaTime, &c);
-        SkVector oldHeading = fParticles[i].fPV.fPose.fHeading;
-        fParticles[i].fPV.fPose.fHeading = { oldHeading.fX * c - oldHeading.fY * s,
-                                             oldHeading.fX * s + oldHeading.fY * c };
-    }
-
-    // Re-generate all xforms
-    SkPoint ofs = fParams->fDrawable ? fParams->fDrawable->center() : SkPoint{ 0.0f, 0.0f };
-    for (int i = 0; i < fCount; ++i) {
-        fXforms[i] = fParticles[i].fPV.fPose.asRSXform(ofs);
+        SkScalar c, s = SkScalarSinCos(fParticles[i].fVelocity.fAngular * deltaTime, &c);
+        SkVector oldHeading = fParticles[i].fPose.fHeading;
+        fParticles[i].fPose.fHeading = { oldHeading.fX * c - oldHeading.fY * s,
+                                         oldHeading.fX * s + oldHeading.fY * c };
     }
 
     // Mark effect as dead if we've reached the end (and are not looping)
@@ -169,16 +152,13 @@
     if (this->isAlive() && fParams->fDrawable) {
         SkPaint paint;
         paint.setFilterQuality(SkFilterQuality::kMedium_SkFilterQuality);
-        fParams->fDrawable->draw(
-                canvas, fXforms.get(), fFrames.get(), fColors.get(), fCount, &paint);
+        fParams->fDrawable->draw(canvas, fParticles.get(), fCount, &paint);
     }
 }
 
 void SkParticleEffect::setCapacity(int capacity) {
     fParticles.realloc(capacity);
-    fXforms.realloc(capacity);
-    fFrames.realloc(capacity);
-    fColors.realloc(capacity);
+    fStableRandoms.realloc(capacity);
 
     fCapacity = capacity;
     fCount = SkTMin(fCount, fCapacity);