From 3d76d1bf4689d1b95fbcadd8da3fc42d9430d13d Mon Sep 17 00:00:00 2001 From: Brian Osman Date: Thu, 28 Feb 2019 15:48:05 -0500 Subject: [PATCH] Particles: Removed emitters, added more full-featured position affectors Bug: skia: Change-Id: Ie6485a11bb57fecef470d727dcf3b4fe5dff0b90 Reviewed-on: https://skia-review.googlesource.com/c/195582 Reviewed-by: Brian Osman Commit-Queue: Brian Osman --- modules/particles/include/SkParticleEffect.h | 4 - modules/particles/include/SkParticleEmitter.h | 30 --- modules/particles/particles.gni | 1 - modules/particles/src/SkParticleAffector.cpp | 211 ++++++++++++++++++ modules/particles/src/SkParticleEffect.cpp | 8 +- modules/particles/src/SkParticleEmitter.cpp | 163 -------------- resources/particles/default.json | 12 +- resources/particles/explosion.json | 46 +++- resources/particles/penguin_cannon.json | 5 - resources/particles/snowfall.json | 11 +- resources/particles/spiral.json | 5 - resources/particles/swirl.json | 11 +- resources/particles/warp.json | 48 +++- tools/viewer/ParticlesSlide.cpp | 2 - 14 files changed, 315 insertions(+), 242 deletions(-) delete mode 100644 modules/particles/include/SkParticleEmitter.h delete mode 100644 modules/particles/src/SkParticleEmitter.cpp diff --git a/modules/particles/include/SkParticleEffect.h b/modules/particles/include/SkParticleEffect.h index c636409392..993ffbbcb6 100644 --- a/modules/particles/include/SkParticleEffect.h +++ b/modules/particles/include/SkParticleEffect.h @@ -21,7 +21,6 @@ class SkCanvas; class SkFieldVisitor; class SkParticleAffector; class SkParticleDrawable; -class SkParticleEmitter; class SkParticleEffectParams : public SkRefCnt { public: @@ -33,9 +32,6 @@ public: // Drawable (image, sprite sheet, etc.) sk_sp fDrawable; - // Emitter shape & parameters - sk_sp fEmitter; - // Rules that configure particles at spawn time SkTArray> fSpawnAffectors; diff --git a/modules/particles/include/SkParticleEmitter.h b/modules/particles/include/SkParticleEmitter.h deleted file mode 100644 index eb91c75d1d..0000000000 --- a/modules/particles/include/SkParticleEmitter.h +++ /dev/null @@ -1,30 +0,0 @@ -/* -* Copyright 2019 Google LLC -* -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE file. -*/ - -#ifndef SkParticleEmitter_DEFINED -#define SkParticleEmitter_DEFINED - -#include "SkReflected.h" - -struct SkParticlePose; -struct SkPoint; -class SkRandom; - -class SkParticleEmitter : public SkReflected { -public: - REFLECTED_ABSTRACT(SkParticleEmitter, SkReflected) - - virtual SkParticlePose emit(SkRandom&) const = 0; - - static void RegisterEmitterTypes(); - - static sk_sp MakeCircle(SkPoint center, SkScalar radius); - static sk_sp MakeLine(SkPoint p1, SkPoint p2); - static sk_sp MakeText(const char* text, SkScalar fontSize); -}; - -#endif // SkParticleEmitter_DEFINED diff --git a/modules/particles/particles.gni b/modules/particles/particles.gni index 970fc8a610..44ed52b310 100644 --- a/modules/particles/particles.gni +++ b/modules/particles/particles.gni @@ -11,6 +11,5 @@ skia_particle_sources = [ "$_src/SkParticleAffector.cpp", "$_src/SkParticleDrawable.cpp", "$_src/SkParticleEffect.cpp", - "$_src/SkParticleEmitter.cpp", "$_src/SkReflected.cpp", ] diff --git a/modules/particles/src/SkParticleAffector.cpp b/modules/particles/src/SkParticleAffector.cpp index ba73855297..9904ca21b6 100644 --- a/modules/particles/src/SkParticleAffector.cpp +++ b/modules/particles/src/SkParticleAffector.cpp @@ -7,9 +7,15 @@ #include "SkParticleAffector.h" +#include "SkContourMeasure.h" #include "SkCurve.h" +#include "SkParsePath.h" #include "SkParticleData.h" +#include "SkPath.h" #include "SkRandom.h" +#include "SkTextUtils.h" + +#include "sk_tool_utils.h" constexpr SkFieldVisitor::EnumStringMapping gParticleFrameMapping[] = { { kWorld_ParticleFrame, "World" }, @@ -180,6 +186,208 @@ private: int fFrame; }; +class SkPositionInCircleAffector : public SkParticleAffector { +public: + SkPositionInCircleAffector(const SkCurve& x = 0.0f, const SkCurve& y = 0.0f, + const SkCurve& radius = 0.0f, bool setHeading = true) + : fX(x) + , fY(y) + , fRadius(radius) + , fSetHeading(setHeading) {} + + REFLECTED(SkPositionInCircleAffector, SkParticleAffector) + + void onApply(SkParticleUpdateParams& params, SkParticleState ps[], int count) override { + for (int i = 0; i < count; ++i) { + SkVector v; + do { + v.fX = ps[i].fRandom.nextSScalar1(); + v.fY = ps[i].fRandom.nextSScalar1(); + } while (v.dot(v) > 1); + + SkPoint center = { fX.eval(ps[i].fAge, ps[i].fRandom), + fY.eval(ps[i].fAge, ps[i].fRandom) }; + SkScalar radius = fRadius.eval(ps[i].fAge, ps[i].fRandom); + ps[i].fPose.fPosition = center + (v * radius); + if (fSetHeading) { + if (!v.normalize()) { + v.set(0, -1); + } + ps[i].fPose.fHeading = v; + } + } + } + + void visitFields(SkFieldVisitor* v) override { + SkParticleAffector::visitFields(v); + v->visit("SetHeading", fSetHeading); + v->visit("X", fX); + v->visit("Y", fY); + v->visit("Radius", fRadius); + } + +private: + SkCurve fX; + SkCurve fY; + SkCurve fRadius; + bool fSetHeading; +}; + +class SkPositionOnPathAffector : public SkParticleAffector { +public: + SkPositionOnPathAffector(const char* path = "", bool setHeading = true, bool random = true) + : fPath(path) + , fSetHeading(setHeading) + , fRandom(random) { + this->rebuild(); + } + + REFLECTED(SkPositionOnPathAffector, SkParticleAffector) + + void onApply(SkParticleUpdateParams& params, SkParticleState ps[], int count) override { + if (fContours.empty()) { + return; + } + + for (int i = 0; i < count; ++i) { + float t = fRandom ? ps[i].fRandom.nextF() : ps[i].fAge; + SkScalar len = fTotalLength * t; + int idx = 0; + while (idx < fContours.count() && len > fContours[idx]->length()) { + len -= fContours[idx++]->length(); + } + SkVector localXAxis; + if (!fContours[idx]->getPosTan(len, &ps[i].fPose.fPosition, &localXAxis)) { + ps[i].fPose.fPosition = { 0, 0 }; + localXAxis = { 1, 0 }; + } + if (fSetHeading) { + ps[i].fPose.fHeading.set(localXAxis.fY, -localXAxis.fX); + } + } + } + + void visitFields(SkFieldVisitor* v) override { + SkString oldPath = fPath; + + SkParticleAffector::visitFields(v); + v->visit("SetHeading", fSetHeading); + v->visit("Random", fRandom); + v->visit("Path", fPath); + + if (fPath != oldPath) { + this->rebuild(); + } + } + +private: + SkString fPath; + bool fSetHeading; + bool fRandom; + + void rebuild() { + SkPath path; + if (!SkParsePath::FromSVGString(fPath.c_str(), &path)) { + return; + } + + fTotalLength = 0; + fContours.reset(); + + SkContourMeasureIter iter(path, false); + while (auto contour = iter.next()) { + fContours.push_back(contour); + fTotalLength += contour->length(); + } + } + + // Cached + SkScalar fTotalLength; + SkTArray> fContours; +}; + +class SkPositionOnTextAffector : public SkParticleAffector { +public: + SkPositionOnTextAffector(const char* text = "", SkScalar fontSize = 96, bool setHeading = true, + bool random = true) + : fText(text) + , fFontSize(fontSize) + , fSetHeading(setHeading) + , fRandom(random) { + this->rebuild(); + } + + REFLECTED(SkPositionOnTextAffector, SkParticleAffector) + + void onApply(SkParticleUpdateParams& params, SkParticleState ps[], int count) override { + if (fContours.empty()) { + return; + } + + // TODO: Refactor to share code with PositionOnPathAffector + for (int i = 0; i < count; ++i) { + float t = fRandom ? ps[i].fRandom.nextF() : ps[i].fAge; + SkScalar len = fTotalLength * t; + int idx = 0; + while (idx < fContours.count() && len > fContours[idx]->length()) { + len -= fContours[idx++]->length(); + } + SkVector localXAxis; + if (!fContours[idx]->getPosTan(len, &ps[i].fPose.fPosition, &localXAxis)) { + ps[i].fPose.fPosition = { 0, 0 }; + localXAxis = { 1, 0 }; + } + if (fSetHeading) { + ps[i].fPose.fHeading.set(localXAxis.fY, -localXAxis.fX); + } + } + } + + void visitFields(SkFieldVisitor* v) override { + SkString oldText = fText; + SkScalar oldSize = fFontSize; + + SkParticleAffector::visitFields(v); + v->visit("SetHeading", fSetHeading); + v->visit("Random", fRandom); + v->visit("Text", fText); + v->visit("FontSize", fFontSize); + + if (fText != oldText || fFontSize != oldSize) { + this->rebuild(); + } + } + +private: + SkString fText; + SkScalar fFontSize; + bool fSetHeading; + bool fRandom; + + void rebuild() { + fTotalLength = 0; + fContours.reset(); + + if (fText.isEmpty()) { + return; + } + + SkFont font(sk_tool_utils::create_portable_typeface()); + font.setSize(fFontSize); + SkPath path; + SkTextUtils::GetPath(fText.c_str(), fText.size(), kUTF8_SkTextEncoding, 0, 0, font, &path); + SkContourMeasureIter iter(path, false); + while (auto contour = iter.next()) { + fContours.push_back(contour); + fTotalLength += contour->length(); + } + } + + // Cached + SkScalar fTotalLength; + SkTArray> fContours; +}; + class SkSizeAffector : public SkParticleAffector { public: SkSizeAffector(const SkCurve& curve = 1.0f) : fCurve(curve) {} @@ -250,6 +458,9 @@ void SkParticleAffector::RegisterAffectorTypes() { REGISTER_REFLECTED(SkAngularVelocityAffector); REGISTER_REFLECTED(SkPointForceAffector); REGISTER_REFLECTED(SkOrientationAffector); + REGISTER_REFLECTED(SkPositionInCircleAffector); + REGISTER_REFLECTED(SkPositionOnPathAffector); + REGISTER_REFLECTED(SkPositionOnTextAffector); REGISTER_REFLECTED(SkSizeAffector); REGISTER_REFLECTED(SkFrameAffector); REGISTER_REFLECTED(SkColorAffector); diff --git a/modules/particles/src/SkParticleEffect.cpp b/modules/particles/src/SkParticleEffect.cpp index 5443548414..d1f7b6486b 100644 --- a/modules/particles/src/SkParticleEffect.cpp +++ b/modules/particles/src/SkParticleEffect.cpp @@ -14,7 +14,6 @@ #include "SkPaint.h" #include "SkParticleAffector.h" #include "SkParticleDrawable.h" -#include "SkParticleEmitter.h" #include "SkReflected.h" #include "SkRSXform.h" @@ -25,7 +24,6 @@ void SkParticleEffectParams::visitFields(SkFieldVisitor* v) { v->visit("Life", fLifetime); v->visit("Drawable", fDrawable); - v->visit("Emitter", fEmitter); v->visit("Spawn", fSpawnAffectors); v->visit("Update", fUpdateAffectors); @@ -86,7 +84,7 @@ void SkParticleEffect::update(const SkAnimTimer& timer) { int numToSpawn = sk_float_round2int(desired); fSpawnRemainder = desired - numToSpawn; numToSpawn = SkTPin(numToSpawn, 0, fParams->fMaxCount - fCount); - if (fParams->fEmitter) { + if (numToSpawn) { // This isn't "particle" t, it's effect t. float t = static_cast((now - fSpawnTime) / fParams->fEffectDuration); t = fLooping ? fmodf(t, 1.0f) : SkTPin(t, 0.0f, 1.0f); @@ -99,7 +97,9 @@ void SkParticleEffect::update(const SkAnimTimer& timer) { fParticles[fCount].fAge = t; fParticles[fCount].fInvLifetime = sk_ieee_float_divide(1.0f, fParams->fLifetime.eval(t, fRandom)); - fParticles[fCount].fPose = fParams->fEmitter->emit(fRandom); + fParticles[fCount].fPose.fPosition = { 0.0f, 0.0f }; + fParticles[fCount].fPose.fHeading = { 0.0f, -1.0f }; + fParticles[fCount].fPose.fScale = 1.0f; 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 }; diff --git a/modules/particles/src/SkParticleEmitter.cpp b/modules/particles/src/SkParticleEmitter.cpp deleted file mode 100644 index 846b4872f3..0000000000 --- a/modules/particles/src/SkParticleEmitter.cpp +++ /dev/null @@ -1,163 +0,0 @@ -/* -* Copyright 2019 Google LLC -* -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE file. -*/ - -#include "SkParticleEmitter.h" - -#include "SkContourMeasure.h" -#include "SkParticleData.h" -#include "SkRandom.h" -#include "SkTextUtils.h" - -#include "sk_tool_utils.h" - -class SkCircleEmitter : public SkParticleEmitter { -public: - SkCircleEmitter(SkPoint center = { 0.0f, 0.0f }, SkScalar radius = 0.0f) - : fCenter(center), fRadius(radius) {} - - REFLECTED(SkCircleEmitter, SkParticleEmitter) - - SkParticlePose emit(SkRandom& random) const override { - SkVector v; - do { - v.fX = random.nextSScalar1(); - v.fY = random.nextSScalar1(); - } while (v.distanceToOrigin() > 1); - SkParticlePose pose = { fCenter + (v * fRadius), v, 1.0f }; - if (!pose.fHeading.normalize()) { - pose.fHeading.set(0, -1); - } - return pose; - } - - void visitFields(SkFieldVisitor* v) override { - v->visit("Center", fCenter); - v->visit("Radius", fRadius); - } - -private: - SkPoint fCenter; - SkScalar fRadius; -}; - -class SkLineEmitter : public SkParticleEmitter { -public: - SkLineEmitter(SkPoint p1 = { 0.0f, 0.0f }, SkPoint p2 = { 0.0f, 0.0f }) : fP1(p1), fP2(p2) {} - - REFLECTED(SkLineEmitter, SkParticleEmitter) - - SkParticlePose emit(SkRandom& random) const override { - SkVector localXAxis = (fP2 - fP1); - SkParticlePose pose = { fP1 + (fP2 - fP1) * random.nextUScalar1(), - { localXAxis.fY, -localXAxis.fX }, - 1.0f }; - if (!pose.fHeading.normalize()) { - pose.fHeading.set(0, -1); - } - return pose; - } - - void visitFields(SkFieldVisitor* v) override { - v->visit("P1", fP1); - v->visit("P2", fP2); - } - -private: - SkPoint fP1; - SkPoint fP2; -}; - -class SkTextEmitter : public SkParticleEmitter { -public: - SkTextEmitter(const char* text = "", SkScalar fontSize = 96) - : fText(text), fFontSize(fontSize) { - this->rebuild(); - } - - REFLECTED(SkTextEmitter, SkParticleEmitter) - - SkParticlePose emit(SkRandom& random) const override { - if (fContours.count() == 0) { - return SkParticlePose{ { 0, 0 }, { 0, -1 }, 0.0f }; - } - - SkScalar len = random.nextRangeScalar(0, fTotalLength); - int idx = 0; - while (idx < fContours.count() && len > fContours[idx]->length()) { - len -= fContours[idx++]->length(); - } - SkParticlePose pose; - SkVector localXAxis; - if (!fContours[idx]->getPosTan(len, &pose.fPosition, &localXAxis)) { - pose.fPosition = { 0, 0 }; - localXAxis = { 1, 0 }; - } - pose.fHeading = { localXAxis.fY, -localXAxis.fX }; - pose.fScale = 1.0f; - return pose; - } - - void visitFields(SkFieldVisitor* v) override { - SkString oldText = fText; - SkScalar oldSize = fFontSize; - - v->visit("Text", fText); - v->visit("FontSize", fFontSize); - - if (fText != oldText || fFontSize != oldSize) { - this->rebuild(); - } - } - -private: - SkString fText; - SkScalar fFontSize; - - void rebuild() { - fTotalLength = 0; - fContours.reset(); - - if (fText.isEmpty()) { - return; - } - - SkFont font(sk_tool_utils::create_portable_typeface()); - font.setSize(fFontSize); - SkPath path; - SkTextUtils::GetPath(fText.c_str(), fText.size(), kUTF8_SkTextEncoding, 0, 0, font, &path); - fIter.reset(path, false); - while (auto contour = fIter.next()) { - fContours.push_back(contour); - fTotalLength += contour->length(); - } - } - - // Cached - SkScalar fTotalLength; - SkContourMeasureIter fIter; - SkTArray> fContours; -}; - -void SkParticleEmitter::RegisterEmitterTypes() { - // Register types for serialization - REGISTER_REFLECTED(SkParticleEmitter); - REGISTER_REFLECTED(SkCircleEmitter); - REGISTER_REFLECTED(SkLineEmitter); - REGISTER_REFLECTED(SkTextEmitter); -} - -sk_sp SkParticleEmitter::MakeCircle(SkPoint center, SkScalar radius) { - return sk_sp(new SkCircleEmitter(center, radius)); -} - -sk_sp SkParticleEmitter::MakeLine(SkPoint p1, SkPoint p2) { - return sk_sp(new SkLineEmitter(p1, p2)); -} - -sk_sp SkParticleEmitter::MakeText(const char* text, SkScalar fontSize) { - return sk_sp(new SkTextEmitter(text, fontSize)); -} diff --git a/resources/particles/default.json b/resources/particles/default.json index f9da78fe77..8f220f3767 100644 --- a/resources/particles/default.json +++ b/resources/particles/default.json @@ -18,11 +18,6 @@ "Type": "SkCircleDrawable", "Radius": 1 }, - "Emitter": { - "Type": "SkTextEmitter", - "Text": "SKIA", - "FontSize": 96 - }, "Spawn": [ { "Type": "SkLinearVelocityAffector", @@ -53,6 +48,13 @@ } ] } + }, + { + "Type": "SkPositionOnTextAffector", + "Enabled": true, + "SetHeading": true, + "Text": "SKIA", + "FontSize": 96 } ], "Update": [ diff --git a/resources/particles/explosion.json b/resources/particles/explosion.json index e403d3cb6e..238f3520e5 100644 --- a/resources/particles/explosion.json +++ b/resources/particles/explosion.json @@ -20,12 +20,46 @@ "Columns": 4, "Rows": 4 }, - "Emitter": { - "Type": "SkCircleEmitter", - "Center": { "x": 200, "y": 200 }, - "Radius": 60 - }, - "Spawn": [], + "Spawn": [ + { + "Type": "SkPositionInCircleAffector", + "Enabled": true, + "SetHeading": true, + "X": { + "XValues": [], + "Segments": [ + { + "Type": "Constant", + "Ranged": false, + "Bidirectional": false, + "A0": 0 + } + ] + }, + "Y": { + "XValues": [], + "Segments": [ + { + "Type": "Constant", + "Ranged": false, + "Bidirectional": false, + "A0": 0 + } + ] + }, + "Radius": { + "XValues": [], + "Segments": [ + { + "Type": "Constant", + "Ranged": false, + "Bidirectional": false, + "A0": 60 + } + ] + } + } + ], "Update": [ { "Type": "SkPointForceAffector", diff --git a/resources/particles/penguin_cannon.json b/resources/particles/penguin_cannon.json index 17415b9d53..227a1af0f6 100644 --- a/resources/particles/penguin_cannon.json +++ b/resources/particles/penguin_cannon.json @@ -19,11 +19,6 @@ "Columns": 1, "Rows": 1 }, - "Emitter": { - "Type": "SkLineEmitter", - "P1": { "x": 237, "y": 396 }, - "P2": { "x": 214, "y": 398 } - }, "Spawn": [ { "Type": "SkLinearVelocityAffector", diff --git a/resources/particles/snowfall.json b/resources/particles/snowfall.json index 843e65ff50..2c796c8d54 100644 --- a/resources/particles/snowfall.json +++ b/resources/particles/snowfall.json @@ -17,11 +17,6 @@ "Type": "SkCircleDrawable", "Radius": 1 }, - "Emitter": { - "Type": "SkLineEmitter", - "P1": { "x": 61, "y": 34 }, - "P2": { "x": 606, "y": 32 } - }, "Spawn": [ { "Type": "SkLinearVelocityAffector", @@ -52,6 +47,12 @@ } ] } + }, + { + "Type": "SkPositionOnPathAffector", + "Enabled": true, + "SetHeading": false, + "Path": "h500" } ], "Update": [ diff --git a/resources/particles/spiral.json b/resources/particles/spiral.json index b13df1fd58..d52326dd07 100644 --- a/resources/particles/spiral.json +++ b/resources/particles/spiral.json @@ -18,11 +18,6 @@ "Type": "SkCircleDrawable", "Radius": 2 }, - "Emitter": { - "Type": "SkCircleEmitter", - "Center": { "x": 0, "y": 0 }, - "Radius": 0 - }, "Spawn": [ { "Type": "SkLinearVelocityAffector", diff --git a/resources/particles/swirl.json b/resources/particles/swirl.json index d3ccf5fcf4..23dc2a7282 100644 --- a/resources/particles/swirl.json +++ b/resources/particles/swirl.json @@ -18,12 +18,13 @@ "Type": "SkCircleDrawable", "Radius": 2 }, - "Emitter": { - "Type": "SkLineEmitter", - "P1": { "x": 200, "y": 200 }, - "P2": { "x": 250, "y": 200 } - }, "Spawn": [ + { + "Type": "SkPositionOnPathAffector", + "Enabled": true, + "SetHeading": true, + "Path": "h50" + }, { "Type": "SkLinearVelocityAffector", "Enabled": true, diff --git a/resources/particles/warp.json b/resources/particles/warp.json index 9b81db7a78..96ede19d39 100644 --- a/resources/particles/warp.json +++ b/resources/particles/warp.json @@ -17,17 +17,51 @@ "Type": "SkCircleDrawable", "Radius": 2 }, - "Emitter": { - "Type": "SkCircleEmitter", - "Center": { "x": 380.8, "y": 273.92 }, - "Radius": 43 - }, - "Spawn": [], + "Spawn": [ + { + "Type": "SkPositionInCircleAffector", + "Enabled": true, + "SetHeading": true, + "X": { + "XValues": [], + "Segments": [ + { + "Type": "Constant", + "Ranged": false, + "Bidirectional": false, + "A0": 0 + } + ] + }, + "Y": { + "XValues": [], + "Segments": [ + { + "Type": "Constant", + "Ranged": false, + "Bidirectional": false, + "A0": 0 + } + ] + }, + "Radius": { + "XValues": [], + "Segments": [ + { + "Type": "Constant", + "Ranged": false, + "Bidirectional": false, + "A0": 40 + } + ] + } + } + ], "Update": [ { "Type": "SkPointForceAffector", "Enabled": true, - "Point": { "x": 375, "y": 273 }, + "Point": { "x": 0, "y": 0 }, "Constant": -10, "InvSquare": 0 }, diff --git a/tools/viewer/ParticlesSlide.cpp b/tools/viewer/ParticlesSlide.cpp index 5742cbddba..8098b5fc23 100644 --- a/tools/viewer/ParticlesSlide.cpp +++ b/tools/viewer/ParticlesSlide.cpp @@ -11,7 +11,6 @@ #include "SkParticleAffector.h" #include "SkParticleDrawable.h" #include "SkParticleEffect.h" -#include "SkParticleEmitter.h" #include "SkParticleSerialization.h" #include "SkReflected.h" @@ -197,7 +196,6 @@ ParticlesSlide::ParticlesSlide() { REGISTER_REFLECTED(SkReflected); SkParticleAffector::RegisterAffectorTypes(); SkParticleDrawable::RegisterDrawableTypes(); - SkParticleEmitter::RegisterEmitterTypes(); fName = "Particles"; }