Add accessors to get/set SkParticleEffect fields

Simplify burst handling. Scripts should just add to burst (if
they want to handle programmatic bursting, as well).

Update most effects to handle dynamic updates to position better,
and add a sample effect meant to be used with mouse tracking.

Change-Id: Ia302e1d04e62e2b07974807c44067786cc10a8ad
Bug: skia:9513
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/248798
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/modules/particles/include/SkParticleEffect.h b/modules/particles/include/SkParticleEffect.h
index 332e4af..02c7547 100644
--- a/modules/particles/include/SkParticleEffect.h
+++ b/modules/particles/include/SkParticleEffect.h
@@ -156,6 +156,28 @@
     }
     int getCount() const { return fCount; }
 
+    float     getRate()     const { return fState.fRate;     }
+    int       getBurst()    const { return fState.fBurst;    }
+    SkPoint   getPosition() const { return fState.fPosition; }
+    SkVector  getHeading()  const { return fState.fHeading;  }
+    float     getScale()    const { return fState.fScale;    }
+    SkVector  getVelocity() const { return fState.fVelocity; }
+    float     getSpin()     const { return fState.fSpin;     }
+    SkColor4f getColor()    const { return fState.fColor;    }
+    float     getFrame()    const { return fState.fFrame;    }
+    uint32_t  getFlags()    const { return fState.fFlags;    }
+
+    void setRate    (float     r) { fState.fRate     = r; }
+    void setBurst   (int       b) { fState.fBurst    = b; }
+    void setPosition(SkPoint   p) { fState.fPosition = p; }
+    void setHeading (SkVector  h) { fState.fHeading  = h; }
+    void setScale   (float     s) { fState.fScale    = s; }
+    void setVelocity(SkVector  v) { fState.fVelocity = v; }
+    void setSpin    (float     s) { fState.fSpin     = s; }
+    void setColor   (SkColor4f c) { fState.fColor    = c; }
+    void setFrame   (float     f) { fState.fFrame    = f; }
+    void setFlags   (uint32_t  f) { fState.fFlags    = f; }
+
     static void RegisterParticleTypes();
 
 private:
@@ -165,7 +187,7 @@
     void advanceTime(double now);
 
     void processEffectSpawnRequests(double now);
-    int runEffectScript(double now, const char* entry);
+    void runEffectScript(double now, const char* entry);
 
     void processParticleSpawnRequests(double now, int start);
     void runParticleScript(double now, const char* entry, int start, int count);
diff --git a/modules/particles/src/SkParticleEffect.cpp b/modules/particles/src/SkParticleEffect.cpp
index db6724b..c6d75df 100644
--- a/modules/particles/src/SkParticleEffect.cpp
+++ b/modules/particles/src/SkParticleEffect.cpp
@@ -226,8 +226,7 @@
     fSpawnRequests.reset();
 }
 
-int SkParticleEffect::runEffectScript(double now, const char* entry) {
-    fState.fBurst = 0;
+void SkParticleEffect::runEffectScript(double now, const char* entry) {
     if (const auto& byteCode = fParams->fEffectProgram.fByteCode) {
         if (auto fun = byteCode->getFunction(entry)) {
             for (const auto& value : fParams->fEffectProgram.fExternalValues) {
@@ -241,7 +240,6 @@
             this->processEffectSpawnRequests(now);
         }
     }
-    return fState.fBurst;
 }
 
 void SkParticleEffect::processParticleSpawnRequests(double now, int start) {
@@ -304,24 +302,22 @@
         this->setCapacity(fParams->fMaxCount);
     }
 
-    int burstCount = 0;
-
     // Is this the first update after calling start()?
     // Run 'effectSpawn' to set initial emitter properties.
     if (fState.fAge == 0.0f && fState.fLoopCount == 0) {
-        burstCount += this->runEffectScript(now, "effectSpawn");
+        this->runEffectScript(now, "effectSpawn");
     }
 
     fState.fAge += fState.fDeltaTime / fState.fLifetime;
     if (fState.fAge > 1) {
         // We always run effectDeath when age crosses 1, whether we're looping or actually dying
-        burstCount += this->runEffectScript(now, "effectDeath");
+        this->runEffectScript(now, "effectDeath");
 
         if (fLooping) {
             // If we looped, then run effectSpawn again (with the updated loop count)
             fState.fLoopCount += sk_float_floor2int(fState.fAge);
             fState.fAge = fmodf(fState.fAge, 1.0f);
-            burstCount += this->runEffectScript(now, "effectSpawn");
+            this->runEffectScript(now, "effectSpawn");
         } else {
             // Effect is dead if we've reached the end (and are not looping)
             return;
@@ -349,7 +345,7 @@
     this->runParticleScript(now, "death", fCount, numDyingParticles);
 
     // Run 'effectUpdate' to adjust emitter properties
-    burstCount += this->runEffectScript(now, "effectUpdate");
+    this->runEffectScript(now, "effectUpdate");
 
     // Do integration of effect position and orientation
     {
@@ -362,10 +358,11 @@
     }
 
     // Spawn new particles
-    float desired = fState.fRate * fState.fDeltaTime + fSpawnRemainder;
+    float desired = fState.fRate * fState.fDeltaTime + fSpawnRemainder + fState.fBurst;
+    fState.fBurst = 0;
     int numToSpawn = sk_float_round2int(desired);
     fSpawnRemainder = desired - numToSpawn;
-    numToSpawn = SkTPin(numToSpawn + burstCount, 0, fParams->fMaxCount - fCount);
+    numToSpawn = SkTPin(numToSpawn, 0, fParams->fMaxCount - fCount);
     if (numToSpawn) {
         const int spawnBase = fCount;
 
diff --git a/resources/particles/mouse_trail.json b/resources/particles/mouse_trail.json
new file mode 100644
index 0000000..03b8587
--- /dev/null
+++ b/resources/particles/mouse_trail.json
@@ -0,0 +1,28 @@
+{
+   "MaxCount": 2000,
+   "Drawable": {
+      "Type": "SkCircleDrawable",
+      "Radius": 4
+   },
+   "EffectCode": [
+      "void effectSpawn(inout Effect effect) {",
+      "  effect.rate = 800;",
+      "}",
+      ""
+   ],
+   "Code": [
+      "void spawn(inout Particle p) {",
+      "  p.lifetime = 2 + rand;",
+      "  float a = radians(rand * 360);",
+      "  p.vel = float2(cos(a), sin(a)) * mix(5, 15, rand);",
+      "  p.scale = mix(0.25, 0.75, rand);",
+      "}",
+      "",
+      "void update(inout Particle p) {",
+      "  p.color.r = p.age;",
+      "  p.color.g = 1 - p.age;",
+      "}",
+      ""
+   ],
+   "Bindings": []
+}
\ No newline at end of file
diff --git a/resources/particles/sinusoidal_emitter.json b/resources/particles/sinusoidal_emitter.json
index 5d2fbf9..b6d9a45 100644
--- a/resources/particles/sinusoidal_emitter.json
+++ b/resources/particles/sinusoidal_emitter.json
@@ -10,12 +10,12 @@
       "}",
       "",
       "void effectUpdate(inout Effect effect) {",
-      "  effect.pos.y = sin(effect.age * 6.28) * 40;",
       "}",
       ""
    ],
    "Code": [
       "void spawn(inout Particle p) {",
+      "  p.pos.y += sin(effect.age * 6.28) * 40;",
       "  p.lifetime = 2 + (rand * 2);",
       "  p.vel.x = (30 * rand) + 50;",
       "  p.vel.y = (20 * rand) - 10;",
diff --git a/resources/particles/sprite_frame.json b/resources/particles/sprite_frame.json
index e12eb95..645d2b6 100644
--- a/resources/particles/sprite_frame.json
+++ b/resources/particles/sprite_frame.json
@@ -25,8 +25,9 @@
       "",
       "void spawn(inout Particle p) {",
       "  p.lifetime = 1.0 + rand * 2.0;",
-      "  p.pos = circle() * 60;",
-      "  p.vel = p.pos / 3;",
+      "  float2 ofs = circle() * 60;",
+      "  p.pos += ofs;",
+      "  p.vel = ofs / 3;",
       "}",
       "",
       "void update(inout Particle p) {",
diff --git a/resources/particles/text.json b/resources/particles/text.json
index da57f2f..3846212 100644
--- a/resources/particles/text.json
+++ b/resources/particles/text.json
@@ -17,7 +17,7 @@
       "  float s = mix(10, 30, rand);",
       "  p.vel.x = cos(a) * s;",
       "  p.vel.y = sin(a) * s;",
-      "  p.pos = text(rand).xy;",
+      "  p.pos += text(rand).xy;",
       "}",
       "",
       "void update(inout Particle p) {",
diff --git a/resources/particles/variable_rate.json b/resources/particles/variable_rate.json
index 3412ce30..be92a86 100644
--- a/resources/particles/variable_rate.json
+++ b/resources/particles/variable_rate.json
@@ -14,8 +14,9 @@
       "void spawn(inout Particle p) {",
       "  p.lifetime = 6;",
       "  float a = radians(rand * 360);",
-      "  p.pos = float2(cos(a), sin(a)) * 40;",
-      "  p.vel = p.pos;",
+      "  float2 ofs = float2(cos(a), sin(a)) * 40;",
+      "  p.pos += ofs;",
+      "  p.vel = ofs;",
       "  p.scale = 0.5;",
       "}",
       ""
diff --git a/resources/particles/warp.json b/resources/particles/warp.json
index fc26fc6..0b42466 100644
--- a/resources/particles/warp.json
+++ b/resources/particles/warp.json
@@ -23,11 +23,11 @@
       "",
       "void spawn(inout Particle p) {",
       "  p.lifetime = 30;",
-      "  p.pos = circle() * 40;",
+      "  p.pos += circle() * 40;",
       "}",
       "",
       "void update(inout Particle p) {",
-      "  p.vel += normalize(p.pos) * dt * 10;",
+      "  p.vel += normalize(p.pos - effect.pos) * dt * 10;",
       "  p.scale = mix(0.25, 3, p.age);",
       "}",
       ""
diff --git a/tools/viewer/ParticlesSlide.cpp b/tools/viewer/ParticlesSlide.cpp
index 9064ba0..4c98c9f 100644
--- a/tools/viewer/ParticlesSlide.cpp
+++ b/tools/viewer/ParticlesSlide.cpp
@@ -281,7 +281,7 @@
             if (fAnimated && ImGui::Button("Play")) {
                 sk_sp<SkParticleEffect> effect(new SkParticleEffect(fLoaded[i].fParams, fRandom));
                 effect->start(fAnimationTime, looped);
-                fRunning.push_back({ fPlayPosition, fLoaded[i].fName, effect });
+                fRunning.push_back({ fPlayPosition, fLoaded[i].fName, effect, false });
                 fRandom.nextU();
             }
             ImGui::SameLine();
@@ -302,10 +302,17 @@
     if (ImGui::Begin("Running")) {
         for (int i = 0; i < fRunning.count(); ++i) {
             ImGui::PushID(i);
+            ImGui::Checkbox("##Track", &fRunning[i].fTrackMouse);
+            ImGui::SameLine();
             bool remove = ImGui::Button("X") || !fRunning[i].fEffect->isAlive();
             ImGui::SameLine();
             ImGui::Text("%4g, %4g %5d %s", fRunning[i].fPosition.fX, fRunning[i].fPosition.fY,
                         fRunning[i].fEffect->getCount(), fRunning[i].fName.c_str());
+            if (fRunning[i].fTrackMouse) {
+                fRunning[i].fEffect->setPosition({ ImGui::GetMousePos().x,
+                                                   ImGui::GetMousePos().y });
+                fRunning[i].fPosition.set(0, 0);
+            }
             if (remove) {
                 fRunning.removeShuffle(i);
             }
diff --git a/tools/viewer/ParticlesSlide.h b/tools/viewer/ParticlesSlide.h
index 5f23f48..9d98d41 100644
--- a/tools/viewer/ParticlesSlide.h
+++ b/tools/viewer/ParticlesSlide.h
@@ -49,6 +49,7 @@
         SkPoint fPosition;
         SkString fName;
         sk_sp<SkParticleEffect> fEffect;
+        bool fTrackMouse;
     };
     SkTArray<RunningEffect> fRunning;
 };