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 <brianosman@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
diff --git a/modules/particles/src/SkParticleAffector.cpp b/modules/particles/src/SkParticleAffector.cpp
index ba73855..9904ca2 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 @@
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<sk_sp<SkContourMeasure>> 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<sk_sp<SkContourMeasure>> fContours;
+};
+
class SkSizeAffector : public SkParticleAffector {
public:
SkSizeAffector(const SkCurve& curve = 1.0f) : fCurve(curve) {}
@@ -250,6 +458,9 @@
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);