diff --git a/modules/canvaskit/particles_bindings.cpp b/modules/canvaskit/particles_bindings.cpp index 034ffdd3c3..5003ef1b50 100644 --- a/modules/canvaskit/particles_bindings.cpp +++ b/modules/canvaskit/particles_bindings.cpp @@ -8,7 +8,6 @@ #include "include/core/SkCanvas.h" #include "include/core/SkTypes.h" #include "include/utils/SkRandom.h" -#include "modules/particles/include/SkParticleAffector.h" #include "modules/particles/include/SkParticleDrawable.h" #include "modules/particles/include/SkParticleEffect.h" #include "modules/particles/include/SkParticleSerialization.h" @@ -31,7 +30,7 @@ EMSCRIPTEN_BINDINGS(Particles) { static bool didInit = false; if (!didInit) { REGISTER_REFLECTED(SkReflected); - SkParticleAffector::RegisterAffectorTypes(); + SkParticleBinding::RegisterBindingTypes(); SkParticleDrawable::RegisterDrawableTypes(); didInit = true; } diff --git a/modules/particles/include/SkCurve.h b/modules/particles/include/SkCurve.h index efe2d81762..c308aa563e 100644 --- a/modules/particles/include/SkCurve.h +++ b/modules/particles/include/SkCurve.h @@ -10,9 +10,9 @@ #include "include/core/SkColor.h" #include "include/private/SkTArray.h" -#include "modules/particles/include/SkParticleData.h" class SkFieldVisitor; +class SkRandom; /** * SkCurve implements a keyframed 1D function, useful for animating values over time. This pattern @@ -71,12 +71,9 @@ struct SkCurve { fSegments.push_back().setConstant(c); } - float eval(const SkParticleUpdateParams& params, SkParticleState& ps) const; + float eval(float x, SkRandom& random) const; void visitFields(SkFieldVisitor* v); - // Parameters that determine our x-value during evaluation - SkParticleValue fInput; - // It should always be true that (fXValues.count() + 1) == fSegments.count() SkTArray fXValues; SkTArray fSegments; @@ -117,10 +114,9 @@ struct SkColorCurve { fSegments.push_back().setConstant(c); } - SkColor4f eval(const SkParticleUpdateParams& params, SkParticleState& ps) const; + SkColor4f eval(float x, SkRandom& random) const; void visitFields(SkFieldVisitor* v); - SkParticleValue fInput; SkTArray fXValues; SkTArray fSegments; }; diff --git a/modules/particles/include/SkParticleAffector.h b/modules/particles/include/SkParticleAffector.h deleted file mode 100644 index be3d48f4b1..0000000000 --- a/modules/particles/include/SkParticleAffector.h +++ /dev/null @@ -1,54 +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 SkParticleAffector_DEFINED -#define SkParticleAffector_DEFINED - -#include "modules/particles/include/SkReflected.h" - -#include "include/core/SkPoint.h" -#include "modules/particles/include/SkParticleData.h" - -struct SkColorCurve; -struct SkCurve; - -class SkParticleAffector : public SkReflected { -public: - REFLECTED_ABSTRACT(SkParticleAffector, SkReflected) - - void apply(const SkParticleUpdateParams& params, SkParticleState ps[], int count); - void visitFields(SkFieldVisitor* v) override; - - static void RegisterAffectorTypes(); - - // Affectors that can set the linear or angular velocity. Both have a 'force' option to apply - // the resulting value as a force, rather than directly setting the velocity. - static sk_sp MakeLinearVelocity(const SkCurve& angle, - const SkCurve& strength, - bool force, - SkParticleFrame frame); - static sk_sp MakeAngularVelocity(const SkCurve& strength, - bool force); - - // Set the orientation of a particle, relative to the world, local, or velocity frame. - static sk_sp MakeOrientation(const SkCurve& angle, - SkParticleFrame frame); - - static sk_sp MakePointForce(SkPoint point, SkScalar constant, - SkScalar invSquare); - - static sk_sp MakeSize(const SkCurve& curve); - static sk_sp MakeFrame(const SkCurve& curve); - static sk_sp MakeColor(const SkColorCurve& curve); - -private: - virtual void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) = 0; - - bool fEnabled = true; -}; - -#endif // SkParticleAffector_DEFINED diff --git a/modules/particles/include/SkParticleData.h b/modules/particles/include/SkParticleData.h index 1b2daefa0e..91d7115311 100644 --- a/modules/particles/include/SkParticleData.h +++ b/modules/particles/include/SkParticleData.h @@ -8,132 +8,36 @@ #ifndef SkParticleData_DEFINED #define SkParticleData_DEFINED -#include "include/core/SkColor.h" -#include "include/core/SkPoint.h" -#include "include/core/SkRSXform.h" +#include "include/private/SkTemplates.h" #include "include/utils/SkRandom.h" -#include "modules/particles/include/SkReflected.h" /* * Various structs used to communicate particle information among emitters, affectors, etc. */ -enum SkParticleFrame { - kWorld_ParticleFrame, // "Up" is { 0, -1 } - kLocal_ParticleFrame, // "Up" is particle's heading - kVelocity_ParticleFrame, // "Up" is particle's direction of travel -}; +struct SkParticles { + enum Channels { + kAge, + kLifetime, // During spawn, this is actual lifetime. Later, it's inverse lifetime. + kPositionX, + kPositionY, + kHeadingX, + kHeadingY, + kScale, + kVelocityX, + kVelocityY, + kVelocityAngular, + kColorR, + kColorG, + kColorB, + kColorA, + kSpriteFrame, -static constexpr SkFieldVisitor::EnumStringMapping gParticleFrameMapping[] = { - { kWorld_ParticleFrame, "World" }, - { kLocal_ParticleFrame, "Local" }, - { kVelocity_ParticleFrame, "Velocity" }, -}; - -struct SkParticlePose { - SkPoint fPosition; - SkVector fHeading; - SkScalar fScale; - - SkRSXform asRSXform(SkPoint ofs) const { - const float s = fHeading.fX * fScale; - const float c = -fHeading.fY * fScale; - return SkRSXform::Make(c, s, - fPosition.fX + -c * ofs.fX + s * ofs.fY, - fPosition.fY + -s * ofs.fX + -c * ofs.fY); - } -}; - -struct SkParticleVelocity { - SkVector fLinear; - SkScalar fAngular; -}; - -struct SkParticleState { - float fAge; // Normalized age [0, 1] - float fInvLifetime; // 1 / Lifetime - SkParticlePose fPose; - SkParticleVelocity fVelocity; - SkColor4f fColor; - SkScalar fFrame; // Parameter to drawable for animated sprites, etc. - SkRandom fRandom; - - SkVector getFrameHeading(SkParticleFrame frame) const { - switch (frame) { - case kLocal_ParticleFrame: - return fPose.fHeading; - case kVelocity_ParticleFrame: { - SkVector heading = fVelocity.fLinear; - if (!heading.normalize()) { - heading.set(0, -1); - } - return heading; - } - case kWorld_ParticleFrame: - default: - return SkVector{ 0, -1 }; - } - } -}; - -struct SkParticleUpdateParams { - float fDeltaTime; - float fEffectAge; - int fAgeSource; -}; - -/** - * SkParticleValue selects a specific value to be used when evaluating a curve, position on a path, - * or any other affector that needs a scalar float input. An SkParticleValue starts with a source - * value taken from the state of the effect or particle. That can be adjusted using a scale and - * bias, and then reduced into the desired range (typically [0, 1]) via a chosen tile mode. - */ -struct SkParticleValue { - enum Source { - // Either the particle or effect age, depending on spawn or update - kAge_Source, - - kRandom_Source, - kParticleAge_Source, - kEffectAge_Source, - kPositionX_Source, - kPositionY_Source, - kHeadingX_Source, - kHeadingY_Source, - kScale_Source, - kVelocityX_Source, - kVelocityY_Source, - kRotation_Source, - kColorR_Source, - kColorG_Source, - kColorB_Source, - kColorA_Source, - kSpriteFrame_Source, + kNumChannels, }; - enum TileMode { - kClamp_TileMode, - kRepeat_TileMode, - kMirror_TileMode, - }; - - void visitFields(SkFieldVisitor* v); - float eval(const SkParticleUpdateParams& params, SkParticleState& ps) const; - - int fSource = kAge_Source; - int fFrame = kWorld_ParticleFrame; - int fTileMode = kRepeat_TileMode; - - // We map fLeft -> 0 and fRight -> 1. This is easier to work with and reason about. - float fLeft = 0.0f; - float fRight = 1.0f; - - // Cached from the above - float fScale = 1.0f; - float fBias = 0.0f; - -private: - float getSourceValue(const SkParticleUpdateParams& params, SkParticleState& ps) const; + SkAutoTMalloc fData[kNumChannels]; + SkAutoTMalloc fRandom; }; #endif // SkParticleData_DEFINED diff --git a/modules/particles/include/SkParticleDrawable.h b/modules/particles/include/SkParticleDrawable.h index 9c67884433..b14ca74b20 100644 --- a/modules/particles/include/SkParticleDrawable.h +++ b/modules/particles/include/SkParticleDrawable.h @@ -11,7 +11,7 @@ #include "modules/particles/include/SkReflected.h" class SkCanvas; -struct SkParticleState; +struct SkParticles; class SkPaint; class SkString; @@ -19,7 +19,7 @@ class SkParticleDrawable : public SkReflected { public: REFLECTED_ABSTRACT(SkParticleDrawable, SkReflected) - virtual void draw(SkCanvas* canvas, const SkParticleState particles[], int count, + virtual void draw(SkCanvas* canvas, const SkParticles& particles, int count, const SkPaint* paint) = 0; static void RegisterDrawableTypes(); diff --git a/modules/particles/include/SkParticleEffect.h b/modules/particles/include/SkParticleEffect.h index c4086d37bd..c2648cc1b5 100644 --- a/modules/particles/include/SkParticleEffect.h +++ b/modules/particles/include/SkParticleEffect.h @@ -9,34 +9,72 @@ #define SkParticleEffect_DEFINED #include "include/core/SkRefCnt.h" +#include "include/core/SkString.h" #include "include/private/SkTArray.h" +#include "include/private/SkTemplates.h" #include "include/utils/SkRandom.h" -#include "modules/particles/include/SkCurve.h" -#include "src/core/SkAutoMalloc.h" +#include "modules/particles/include/SkParticleData.h" +#include "modules/particles/include/SkReflected.h" + +#include class SkCanvas; -class SkFieldVisitor; -class SkParticleAffector; class SkParticleDrawable; -struct SkParticleState; +class SkParticleExternalValue; + +namespace SkSL { + struct ByteCode; + class Compiler; +} + +class SkParticleBinding : public SkReflected { +public: + SkParticleBinding(const char* name = "name") : fName(name) {} + + REFLECTED_ABSTRACT(SkParticleBinding, SkReflected) + + void visitFields(SkFieldVisitor* v) override; + virtual std::unique_ptr toValue(SkSL::Compiler&) = 0; + + static void RegisterBindingTypes(); + +protected: + SkString fName; +}; class SkParticleEffectParams : public SkRefCnt { public: - int fMaxCount = 128; - float fEffectDuration = 1.0f; - float fRate = 8.0f; - SkCurve fLifetime = 1.0f; + SkParticleEffectParams(); + + int fMaxCount; + float fEffectDuration; + float fRate; // Drawable (image, sprite sheet, etc.) sk_sp fDrawable; - // Rules that configure particles at spawn time - SkTArray> fSpawnAffectors; + // Code to configure particles at spawn time + SkString fSpawnCode; - // Rules that update existing particles over their lifetime - SkTArray> fUpdateAffectors; + // Code to update existing particles over their lifetime + SkString fUpdateCode; + + SkTArray> fBindings; void visitFields(SkFieldVisitor* v); + +private: + friend class SkParticleEffect; + + // Cached + struct Program { + std::unique_ptr fByteCode; + SkTArray> fExternalValues; + }; + Program fSpawnProgram; + Program fUpdateProgram; + + void rebuild(); }; class SkParticleEffect : public SkRefCnt { @@ -64,8 +102,8 @@ private: double fLastTime; float fSpawnRemainder; - SkAutoTMalloc fParticles; - SkAutoTMalloc fStableRandoms; + SkParticles fParticles; + SkAutoTMalloc fStableRandoms; // Cached int fCapacity; diff --git a/modules/particles/particles.gni b/modules/particles/particles.gni index 44ed52b310..0a7ca8cf42 100644 --- a/modules/particles/particles.gni +++ b/modules/particles/particles.gni @@ -8,7 +8,6 @@ _src = get_path_info("src", "abspath") skia_particle_sources = [ "$_src/SkCurve.cpp", - "$_src/SkParticleAffector.cpp", "$_src/SkParticleDrawable.cpp", "$_src/SkParticleEffect.cpp", "$_src/SkReflected.cpp", diff --git a/modules/particles/src/SkCurve.cpp b/modules/particles/src/SkCurve.cpp index e0728c2663..f54ab31166 100644 --- a/modules/particles/src/SkCurve.cpp +++ b/modules/particles/src/SkCurve.cpp @@ -5,10 +5,8 @@ * found in the LICENSE file. */ -#include "modules/particles/include/SkCurve.h" - #include "include/utils/SkRandom.h" -#include "modules/particles/include/SkParticleData.h" +#include "modules/particles/include/SkCurve.h" #include "modules/particles/include/SkReflected.h" constexpr SkFieldVisitor::EnumStringMapping gCurveSegmentTypeMapping[] = { @@ -79,11 +77,9 @@ void SkCurveSegment::visitFields(SkFieldVisitor* v) { } } -float SkCurve::eval(const SkParticleUpdateParams& params, SkParticleState& ps) const { +float SkCurve::eval(float x, SkRandom& random) const { SkASSERT(fSegments.count() == fXValues.count() + 1); - float x = fInput.eval(params, ps); - int i = 0; for (; i < fXValues.count(); ++i) { if (x <= fXValues[i]) { @@ -101,13 +97,12 @@ float SkCurve::eval(const SkParticleUpdateParams& params, SkParticleState& ps) c // Always pull t and negate here, so that the stable generator behaves consistently, even if // our segments use an inconsistent feature-set. - float t = ps.fRandom.nextF(); - bool negate = ps.fRandom.nextBool(); + float t = random.nextF(); + bool negate = random.nextBool(); return fSegments[i].eval(segmentX, t, negate); } void SkCurve::visitFields(SkFieldVisitor* v) { - v->visit("Input", fInput); v->visit("XValues", fXValues); v->visit("Segments", fSegments); @@ -152,11 +147,9 @@ void SkColorCurveSegment::visitFields(SkFieldVisitor* v) { } } -SkColor4f SkColorCurve::eval(const SkParticleUpdateParams& params, SkParticleState& ps) const { +SkColor4f SkColorCurve::eval(float x, SkRandom& random) const { SkASSERT(fSegments.count() == fXValues.count() + 1); - float x = fInput.eval(params, ps); - int i = 0; for (; i < fXValues.count(); ++i) { if (x <= fXValues[i]) { @@ -171,11 +164,10 @@ SkColor4f SkColorCurve::eval(const SkParticleUpdateParams& params, SkParticleSta segmentX = rangeMin; } SkASSERT(0.0f <= segmentX && segmentX <= 1.0f); - return fSegments[i].eval(segmentX, ps.fRandom.nextF()); + return fSegments[i].eval(segmentX, random.nextF()); } void SkColorCurve::visitFields(SkFieldVisitor* v) { - v->visit("Input", fInput); v->visit("XValues", fXValues); v->visit("Segments", fSegments); diff --git a/modules/particles/src/SkParticleAffector.cpp b/modules/particles/src/SkParticleAffector.cpp deleted file mode 100644 index fe15df978e..0000000000 --- a/modules/particles/src/SkParticleAffector.cpp +++ /dev/null @@ -1,574 +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 "modules/particles/include/SkParticleAffector.h" - -#include "include/core/SkContourMeasure.h" -#include "include/core/SkPath.h" -#include "include/utils/SkParsePath.h" -#include "include/utils/SkRandom.h" -#include "include/utils/SkTextUtils.h" -#include "modules/particles/include/SkCurve.h" -#include "modules/particles/include/SkParticleData.h" -#include "src/core/SkMakeUnique.h" -#include "src/sksl/SkSLByteCode.h" -#include "src/sksl/SkSLCompiler.h" -#include "src/sksl/SkSLExternalValue.h" - -void SkParticleAffector::apply(const SkParticleUpdateParams& params, - SkParticleState ps[], int count) { - if (fEnabled) { - this->onApply(params, ps, count); - } -} - -void SkParticleAffector::visitFields(SkFieldVisitor* v) { - v->visit("Enabled", fEnabled); -} - -class SkLinearVelocityAffector : public SkParticleAffector { -public: - SkLinearVelocityAffector(const SkCurve& angle = 0.0f, - const SkCurve& strength = 0.0f, - bool force = true, - SkParticleFrame frame = kWorld_ParticleFrame) - : fAngle(angle) - , fStrength(strength) - , fForce(force) - , fFrame(frame) {} - - REFLECTED(SkLinearVelocityAffector, SkParticleAffector) - - void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override { - for (int i = 0; i < count; ++i) { - float angle = fAngle.eval(params, ps[i]); - SkScalar rad = SkDegreesToRadians(angle); - SkScalar s_local = SkScalarSin(rad), - c_local = SkScalarCos(rad); - SkVector heading = ps[i].getFrameHeading(static_cast(fFrame)); - SkScalar c = heading.fX * c_local - heading.fY * s_local; - SkScalar s = heading.fX * s_local + heading.fY * c_local; - float strength = fStrength.eval(params, ps[i]); - SkVector force = { c * strength, s * strength }; - if (fForce) { - ps[i].fVelocity.fLinear += force * params.fDeltaTime; - } else { - ps[i].fVelocity.fLinear = force; - } - } - } - - void visitFields(SkFieldVisitor* v) override { - SkParticleAffector::visitFields(v); - v->visit("Force", fForce); - v->visit("Frame", fFrame, gParticleFrameMapping, SK_ARRAY_COUNT(gParticleFrameMapping)); - v->visit("Angle", fAngle); - v->visit("Strength", fStrength); - } - -private: - SkCurve fAngle; - SkCurve fStrength; - bool fForce; - int fFrame; -}; - -class SkAngularVelocityAffector : public SkParticleAffector { -public: - SkAngularVelocityAffector(const SkCurve& strength = 0.0f, bool force = true) - : fStrength(strength) - , fForce(force) {} - - REFLECTED(SkAngularVelocityAffector, SkParticleAffector) - - void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override { - for (int i = 0; i < count; ++i) { - float strength = fStrength.eval(params, ps[i]); - if (fForce) { - ps[i].fVelocity.fAngular += strength * params.fDeltaTime; - } else { - ps[i].fVelocity.fAngular = strength; - } - } - } - - void visitFields(SkFieldVisitor* v) override { - SkParticleAffector::visitFields(v); - v->visit("Force", fForce); - v->visit("Strength", fStrength); - } - -private: - SkCurve fStrength; - bool fForce; -}; - -class SkPointForceAffector : public SkParticleAffector { -public: - SkPointForceAffector(SkPoint point = { 0.0f, 0.0f }, SkScalar constant = 0.0f, - SkScalar invSquare = 0.0f) - : fPoint(point), fConstant(constant), fInvSquare(invSquare) {} - - REFLECTED(SkPointForceAffector, SkParticleAffector) - - void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override { - for (int i = 0; i < count; ++i) { - SkVector toPoint = fPoint - ps[i].fPose.fPosition; - SkScalar lenSquare = toPoint.dot(toPoint); - toPoint.normalize(); - ps[i].fVelocity.fLinear += - toPoint * (fConstant + (fInvSquare / lenSquare)) * params.fDeltaTime; - } - } - - void visitFields(SkFieldVisitor* v) override { - SkParticleAffector::visitFields(v); - v->visit("Point", fPoint); - v->visit("Constant", fConstant); - v->visit("InvSquare", fInvSquare); - } - -private: - SkPoint fPoint; - SkScalar fConstant; - SkScalar fInvSquare; -}; - -class SkOrientationAffector : public SkParticleAffector { -public: - SkOrientationAffector(const SkCurve& angle = 0.0f, - SkParticleFrame frame = kLocal_ParticleFrame) - : fAngle(angle) - , fFrame(frame) {} - - REFLECTED(SkOrientationAffector, SkParticleAffector) - - void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override { - for (int i = 0; i < count; ++i) { - float angle = fAngle.eval(params, ps[i]); - SkScalar rad = SkDegreesToRadians(angle); - SkScalar s_local = SkScalarSin(rad), - c_local = SkScalarCos(rad); - SkVector heading = ps[i].getFrameHeading(static_cast(fFrame)); - ps[i].fPose.fHeading.set(heading.fX * c_local - heading.fY * s_local, - heading.fX * s_local + heading.fY * c_local); - } - } - - void visitFields(SkFieldVisitor *v) override { - SkParticleAffector::visitFields(v); - v->visit("Frame", fFrame, gParticleFrameMapping, SK_ARRAY_COUNT(gParticleFrameMapping)); - v->visit("Angle", fAngle); - } - -private: - SkCurve fAngle; - 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(const 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(params, ps[i]), fY.eval(params, ps[i]) }; - SkScalar radius = fRadius.eval(params, ps[i]); - 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, - SkParticleValue input = SkParticleValue()) - : fPath(path) - , fInput(input) - , fSetHeading(setHeading) { - this->rebuild(); - } - - REFLECTED(SkPositionOnPathAffector, SkParticleAffector) - - void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override { - if (fContours.empty()) { - return; - } - - for (int i = 0; i < count; ++i) { - float t = fInput.eval(params, ps[i]); - 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("Input", fInput); - v->visit("SetHeading", fSetHeading); - v->visit("Path", fPath); - - if (fPath != oldPath) { - this->rebuild(); - } - } - -private: - SkString fPath; - SkParticleValue fInput; - bool fSetHeading; - - 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, - SkParticleValue input = SkParticleValue()) - : fText(text) - , fFontSize(fontSize) - , fInput(input) - , fSetHeading(setHeading) { - this->rebuild(); - } - - REFLECTED(SkPositionOnTextAffector, SkParticleAffector) - - void onApply(const 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 = fInput.eval(params, ps[i]); - 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("Input", fInput); - v->visit("SetHeading", fSetHeading); - v->visit("Text", fText); - v->visit("FontSize", fFontSize); - - if (fText != oldText || fFontSize != oldSize) { - this->rebuild(); - } - } - -private: - SkString fText; - SkScalar fFontSize; - SkParticleValue fInput; - bool fSetHeading; - - void rebuild() { - fTotalLength = 0; - fContours.reset(); - - if (fText.isEmpty()) { - return; - } - - // Use the font manager's default font - SkFont font(nullptr, fFontSize); - SkPath path; - SkTextUtils::GetPath(fText.c_str(), fText.size(), SkTextEncoding::kUTF8, 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) {} - - REFLECTED(SkSizeAffector, SkParticleAffector) - - void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override { - for (int i = 0; i < count; ++i) { - ps[i].fPose.fScale = fCurve.eval(params, ps[i]); - } - } - - void visitFields(SkFieldVisitor* v) override { - SkParticleAffector::visitFields(v); - v->visit("Curve", fCurve); - } - -private: - SkCurve fCurve; -}; - -class SkFrameAffector : public SkParticleAffector { -public: - SkFrameAffector(const SkCurve& curve = 1.0f) : fCurve(curve) {} - - REFLECTED(SkFrameAffector, SkParticleAffector) - - void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override { - for (int i = 0; i < count; ++i) { - ps[i].fFrame = fCurve.eval(params, ps[i]); - } - } - - void visitFields(SkFieldVisitor* v) override { - SkParticleAffector::visitFields(v); - v->visit("Curve", fCurve); - } - -private: - SkCurve fCurve; -}; - -class SkColorAffector : public SkParticleAffector { -public: - SkColorAffector(const SkColorCurve& curve = SkColor4f{ 1.0f, 1.0f, 1.0f, 1.0f }) - : fCurve(curve) {} - - REFLECTED(SkColorAffector, SkParticleAffector) - - void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override { - for (int i = 0; i < count; ++i) { - ps[i].fColor = fCurve.eval(params, ps[i]); - } - } - - void visitFields(SkFieldVisitor* v) override { - SkParticleAffector::visitFields(v); - v->visit("Curve", fCurve); - } - -private: - SkColorCurve fCurve; -}; - -static const char* kDefaultCode = - "// float rand; Every read returns a random float [0 .. 1)\n" - "layout(ctype=float) in uniform float dt;\n" - "layout(ctype=float) in uniform float effectAge;\n" - "\n" - "void main(in float age,\n" - " in float invLifetime,\n" - " inout float2 pos,\n" - " inout float2 dir,\n" - " inout float scale,\n" - " inout float2 vel,\n" - " inout float spin,\n" - " inout float4 color) {\n" - "}\n"; - -class SkRandomExternalValue : public SkSL::ExternalValue { -public: - SkRandomExternalValue(const char* name, SkSL::Compiler& compiler) - : INHERITED(name, *compiler.context().fFloat_Type) - , fRandom(nullptr) { } - - void setRandom(SkRandom* random) { fRandom = random; } - bool canRead() const override { return true; } - void read(int /*unusedIndex*/, float* target) override { *target = fRandom->nextF(); } - -private: - SkRandom* fRandom; - typedef SkSL::ExternalValue INHERITED; -}; - -class SkInterpreterAffector : public SkParticleAffector { -public: - SkInterpreterAffector() : fCode(kDefaultCode) { - this->rebuild(); - } - - REFLECTED(SkInterpreterAffector, SkParticleAffector) - - void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override { - for (int i = 0; i < count; ++i) { - fRandomValue->setRandom(&ps[i].fRandom); - SkAssertResult(fByteCode->run(fMain, &ps[i].fAge, nullptr, 1, ¶ms.fDeltaTime, 2)); - } - } - - void visitFields(SkFieldVisitor* v) override { - SkString oldCode = fCode; - - SkParticleAffector::visitFields(v); - v->visit("Code", fCode); - - if (fCode != oldCode) { - this->rebuild(); - } - } - -private: - SkString fCode; - - // Cached - std::unique_ptr fByteCode; - std::unique_ptr fRandomValue; - SkSL::ByteCodeFunction* fMain; - - void rebuild() { - SkSL::Compiler compiler; - SkSL::Program::Settings settings; - auto rand = skstd::make_unique("rand", compiler); - compiler.registerExternalValue(rand.get()); - auto program = compiler.convertProgram(SkSL::Program::kGeneric_Kind, - SkSL::String(fCode.c_str()), settings); - if (!program) { - SkDebugf("%s\n", compiler.errorText().c_str()); - return; - } - - auto byteCode = compiler.toByteCode(*program); - if (compiler.errorCount()) { - SkDebugf("%s\n", compiler.errorText().c_str()); - return; - } - - fMain = byteCode->fFunctions[0].get(); - fByteCode = std::move(byteCode); - fRandomValue = std::move(rand); - } -}; - -void SkParticleAffector::RegisterAffectorTypes() { - REGISTER_REFLECTED(SkParticleAffector); - REGISTER_REFLECTED(SkLinearVelocityAffector); - 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); - REGISTER_REFLECTED(SkInterpreterAffector); -} - -sk_sp SkParticleAffector::MakeLinearVelocity(const SkCurve& angle, - const SkCurve& strength, - bool force, - SkParticleFrame frame) { - return sk_sp(new SkLinearVelocityAffector(angle, strength, force, frame)); -} - -sk_sp SkParticleAffector::MakeAngularVelocity(const SkCurve& strength, - bool force) { - return sk_sp(new SkAngularVelocityAffector(strength, force)); -} - -sk_sp SkParticleAffector::MakePointForce(SkPoint point, SkScalar constant, - SkScalar invSquare) { - return sk_sp(new SkPointForceAffector(point, constant, invSquare)); -} - -sk_sp SkParticleAffector::MakeOrientation(const SkCurve& angle, - SkParticleFrame frame) { - return sk_sp(new SkOrientationAffector(angle, frame)); -} - -sk_sp SkParticleAffector::MakeSize(const SkCurve& curve) { - return sk_sp(new SkSizeAffector(curve)); -} - -sk_sp SkParticleAffector::MakeFrame(const SkCurve& curve) { - return sk_sp(new SkFrameAffector(curve)); -} - -sk_sp SkParticleAffector::MakeColor(const SkColorCurve& curve) { - return sk_sp(new SkColorAffector(curve)); -} diff --git a/modules/particles/src/SkParticleDrawable.cpp b/modules/particles/src/SkParticleDrawable.cpp index 4f942b264c..f806c8730e 100644 --- a/modules/particles/src/SkParticleDrawable.cpp +++ b/modules/particles/src/SkParticleDrawable.cpp @@ -27,14 +27,40 @@ static sk_sp make_circle_image(int radius) { return surface->makeImageSnapshot(); } +static inline SkRSXform make_rsxform(SkPoint ofs, + float posX, float posY, float dirX, float dirY, float scale) { + const float s = dirX * scale; + const float c = -dirY * scale; + return SkRSXform::Make(c, s, + posX + -c * ofs.fX + s * ofs.fY, + posY + -s * ofs.fX + -c * ofs.fY); +} + struct DrawAtlasArrays { - DrawAtlasArrays(const SkParticleState particles[], int count, SkPoint center) + DrawAtlasArrays(const SkParticles& particles, int count, SkPoint center) : fXforms(count) , fRects(count) , fColors(count) { + float* c[] = { + particles.fData[SkParticles::kColorR].get(), + particles.fData[SkParticles::kColorG].get(), + particles.fData[SkParticles::kColorB].get(), + particles.fData[SkParticles::kColorA].get(), + }; + + float* pos[] = { + particles.fData[SkParticles::kPositionX].get(), + particles.fData[SkParticles::kPositionY].get(), + }; + float* dir[] = { + particles.fData[SkParticles::kHeadingX].get(), + particles.fData[SkParticles::kHeadingY].get(), + }; + float* scale = particles.fData[SkParticles::kScale].get(); + for (int i = 0; i < count; ++i) { - fXforms[i] = particles[i].fPose.asRSXform(center); - fColors[i] = particles[i].fColor.toSkColor(); + fXforms[i] = make_rsxform(center, pos[0][i], pos[1][i], dir[0][i], dir[1][i], scale[i]); + fColors[i] = SkColor4f{ c[0][i], c[1][i], c[2][i], c[3][i] }.toSkColor(); } } @@ -52,7 +78,7 @@ public: REFLECTED(SkCircleDrawable, SkParticleDrawable) - void draw(SkCanvas* canvas, const SkParticleState particles[], int count, + void draw(SkCanvas* canvas, const SkParticles& particles, int count, const SkPaint* paint) override { SkPoint center = { SkIntToScalar(fRadius), SkIntToScalar(fRadius) }; DrawAtlasArrays arrays(particles, count, center); @@ -93,15 +119,16 @@ public: REFLECTED(SkImageDrawable, SkParticleDrawable) - void draw(SkCanvas* canvas, const SkParticleState particles[], int count, + void draw(SkCanvas* canvas, const SkParticles& particles, int count, const SkPaint* paint) override { SkRect baseRect = getBaseRect(); SkPoint center = { baseRect.width() * 0.5f, baseRect.height() * 0.5f }; DrawAtlasArrays arrays(particles, count, center); int frameCount = fCols * fRows; + float* spriteFrames = particles.fData[SkParticles::kSpriteFrame].get(); for (int i = 0; i < count; ++i) { - int frame = static_cast(particles[i].fFrame * frameCount + 0.5f); + int frame = static_cast(spriteFrames[i] * frameCount + 0.5f); frame = SkTPin(frame, 0, frameCount - 1); int row = frame / fCols; int col = frame % fCols; diff --git a/modules/particles/src/SkParticleEffect.cpp b/modules/particles/src/SkParticleEffect.cpp index 1e79a5f4a3..056e12ff22 100644 --- a/modules/particles/src/SkParticleEffect.cpp +++ b/modules/particles/src/SkParticleEffect.cpp @@ -8,23 +8,390 @@ #include "modules/particles/include/SkParticleEffect.h" #include "include/core/SkCanvas.h" +#include "include/core/SkContourMeasure.h" #include "include/core/SkPaint.h" +#include "include/core/SkPath.h" #include "include/core/SkRSXform.h" #include "include/private/SkColorData.h" -#include "modules/particles/include/SkParticleAffector.h" +#include "include/utils/SkParsePath.h" +#include "include/utils/SkTextUtils.h" +#include "modules/particles/include/SkCurve.h" #include "modules/particles/include/SkParticleDrawable.h" #include "modules/particles/include/SkReflected.h" +#include "src/core/SkMakeUnique.h" +#include "src/sksl/SkSLByteCode.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLExternalValue.h" + +void SkParticleBinding::visitFields(SkFieldVisitor* v) { + v->visit("Name", fName); +} + +class SkParticleExternalValue : public SkSL::ExternalValue { +public: + SkParticleExternalValue(const char* name, SkSL::Compiler& compiler, const SkSL::Type& type) + : INHERITED(name, type) + , fCompiler(compiler) + , fRandom(nullptr) { + } + + void setRandom(SkRandom* random) { fRandom = random; } + +protected: + SkSL::Compiler& fCompiler; + SkRandom* fRandom; + typedef SkSL::ExternalValue INHERITED; +}; + +// Exposes an SkCurve as an external, callable value. c(x) returns a float. +class SkCurveExternalValue : public SkParticleExternalValue { +public: + SkCurveExternalValue(const char* name, SkSL::Compiler& compiler, const SkCurve& curve) + : INHERITED(name, compiler, *compiler.context().fFloat_Type) + , fCurve(curve) { } + + bool canCall() const override { return true; } + int callParameterCount() const override { return 1; } + void getCallParameterTypes(const SkSL::Type** outTypes) const override { + outTypes[0] = fCompiler.context().fFloat_Type.get(); + } + + void call(int index, float* arguments, float* outReturn) override { + *outReturn = fCurve.eval(*arguments, fRandom[index]); + } + +private: + SkCurve fCurve; + typedef SkParticleExternalValue INHERITED; +}; + +class SkCurveBinding : public SkParticleBinding { +public: + SkCurveBinding(const char* name = "", const SkCurve& curve = 0.0f) + : SkParticleBinding(name) + , fCurve(curve) {} + + REFLECTED(SkCurveBinding, SkParticleBinding) + + void visitFields(SkFieldVisitor* v) override { + SkParticleBinding::visitFields(v); + v->visit("Curve", fCurve); + } + + std::unique_ptr toValue(SkSL::Compiler& compiler) override { + return std::unique_ptr( + new SkCurveExternalValue(fName.c_str(), compiler, fCurve)); + } + +private: + SkCurve fCurve; +}; + +// Exposes an SkColorCurve as an external, callable value. c(x) returns a float4. +class SkColorCurveExternalValue : public SkParticleExternalValue { +public: + SkColorCurveExternalValue(const char* name, SkSL::Compiler& compiler, const SkColorCurve& curve) + : INHERITED(name, compiler, *compiler.context().fFloat4_Type) + , fCurve(curve) { + } + + bool canCall() const override { return true; } + int callParameterCount() const override { return 1; } + void getCallParameterTypes(const SkSL::Type** outTypes) const override { + outTypes[0] = fCompiler.context().fFloat_Type.get(); + } + + void call(int index, float* arguments, float* outReturn) override { + SkColor4f color = fCurve.eval(*arguments, fRandom[index]); + memcpy(outReturn, color.vec(), 4 * sizeof(float)); + } + +private: + SkColorCurve fCurve; + typedef SkParticleExternalValue INHERITED; +}; + +class SkColorCurveBinding : public SkParticleBinding { +public: + SkColorCurveBinding(const char* name = "", + const SkColorCurve& curve = SkColor4f{ 1.0f, 1.0f, 1.0f, 1.0f }) + : SkParticleBinding(name) + , fCurve(curve) { + } + + REFLECTED(SkColorCurveBinding, SkParticleBinding) + + void visitFields(SkFieldVisitor* v) override { + SkParticleBinding::visitFields(v); + v->visit("Curve", fCurve); + } + + std::unique_ptr toValue(SkSL::Compiler& compiler) override { + return std::unique_ptr( + new SkColorCurveExternalValue(fName.c_str(), compiler, fCurve)); + } + +private: + SkColorCurve fCurve; +}; + +struct SkPathContours { + SkScalar fTotalLength; + SkTArray> fContours; + + void reset() { + fTotalLength = 0; + fContours.reset(); + } +}; + +// Exposes an SkPath as an external, callable value. p(x) returns a float4 { pos.xy, normal.xy } +class SkPathExternalValue : public SkParticleExternalValue { +public: + SkPathExternalValue(const char* name, SkSL::Compiler& compiler, const SkPathContours* path) + : INHERITED(name, compiler, *compiler.context().fFloat4_Type) + , fPath(path) { } + + bool canCall() const override { return true; } + int callParameterCount() const override { return 1; } + void getCallParameterTypes(const SkSL::Type** outTypes) const override { + outTypes[0] = fCompiler.context().fFloat_Type.get(); + } + + void call(int index, float* arguments, float* outReturn) override { + SkScalar len = fPath->fTotalLength * arguments[0]; + int idx = 0; + while (idx < fPath->fContours.count() && len > fPath->fContours[idx]->length()) { + len -= fPath->fContours[idx++]->length(); + } + SkVector localXAxis; + if (!fPath->fContours[idx]->getPosTan(len, (SkPoint*)outReturn, &localXAxis)) { + outReturn[0] = outReturn[1] = 0.0f; + localXAxis = { 1, 0 }; + } + outReturn[2] = localXAxis.fY; + outReturn[3] = -localXAxis.fX; + } + +private: + const SkPathContours* fPath; + typedef SkParticleExternalValue INHERITED; +}; + +class SkPathBinding : public SkParticleBinding { +public: + SkPathBinding(const char* name = "", const char* path = "") + : SkParticleBinding(name) + , fPath(path) { + this->rebuild(); + } + + REFLECTED(SkPathBinding, SkParticleBinding) + + void visitFields(SkFieldVisitor* v) override { + SkString oldPath = fPath; + + SkParticleBinding::visitFields(v); + v->visit("Path", fPath); + + if (fPath != oldPath) { + this->rebuild(); + } + } + + std::unique_ptr toValue(SkSL::Compiler& compiler) override { + return std::unique_ptr( + new SkPathExternalValue(fName.c_str(), compiler, &fContours)); + } + +private: + SkString fPath; + void rebuild() { + SkPath path; + if (!SkParsePath::FromSVGString(fPath.c_str(), &path)) { + return; + } + + fContours.reset(); + + SkContourMeasureIter iter(path, false); + while (auto contour = iter.next()) { + fContours.fContours.push_back(contour); + fContours.fTotalLength += contour->length(); + } + } + + // Cached + SkPathContours fContours; +}; + +class SkTextBinding : public SkParticleBinding { +public: + SkTextBinding(const char* name = "", const char* text = "", SkScalar fontSize = 96) + : SkParticleBinding(name) + , fText(text) + , fFontSize(fontSize) { + this->rebuild(); + } + + REFLECTED(SkTextBinding, SkParticleBinding) + + void visitFields(SkFieldVisitor* v) override { + SkString oldText = fText; + SkScalar oldSize = fFontSize; + + SkParticleBinding::visitFields(v); + v->visit("Text", fText); + v->visit("FontSize", fFontSize); + + if (fText != oldText || fFontSize != oldSize) { + this->rebuild(); + } + } + + std::unique_ptr toValue(SkSL::Compiler& compiler) override { + return std::unique_ptr( + new SkPathExternalValue(fName.c_str(), compiler, &fContours)); + } + +private: + SkString fText; + SkScalar fFontSize; + void rebuild() { + if (fText.isEmpty()) { + return; + } + + fContours.reset(); + + SkFont font(nullptr, fFontSize); + SkPath path; + SkTextUtils::GetPath(fText.c_str(), fText.size(), SkTextEncoding::kUTF8, 0, 0, font, &path); + SkContourMeasureIter iter(path, false); + while (auto contour = iter.next()) { + fContours.fContours.push_back(contour); + fContours.fTotalLength += contour->length(); + } + } + + // Cached + SkPathContours fContours; +}; + +void SkParticleBinding::RegisterBindingTypes() { + REGISTER_REFLECTED(SkParticleBinding); + REGISTER_REFLECTED(SkCurveBinding); + REGISTER_REFLECTED(SkColorCurveBinding); + REGISTER_REFLECTED(SkPathBinding); + REGISTER_REFLECTED(SkTextBinding); +} + +// Exposes a particle's random generator as an external, readable value. read returns a float [0, 1) +class SkRandomExternalValue : public SkParticleExternalValue { +public: + SkRandomExternalValue(const char* name, SkSL::Compiler& compiler) + : INHERITED(name, compiler, *compiler.context().fFloat_Type) {} + + bool canRead() const override { return true; } + void read(int index, float* target) override { + *target = fRandom[index].nextF(); + } + +private: + typedef SkParticleExternalValue INHERITED; +}; + +static const char* kDefaultCode = +R"( +// float rand; Every read returns a random float [0 .. 1) +layout(ctype=float) in uniform float dt; +layout(ctype=float) in uniform float effectAge; + +struct Particle { + float age; + float lifetime; + float2 pos; + float2 dir; + float scale; + float2 vel; + float spin; + float4 color; + float frame; +}; + +void main(inout Particle p) { +} +)"; + +SkParticleEffectParams::SkParticleEffectParams() + : fMaxCount(128) + , fEffectDuration(1.0f) + , fRate(8.0f) + , fDrawable(nullptr) + , fSpawnCode(kDefaultCode) + , fUpdateCode(kDefaultCode) { + this->rebuild(); +} void SkParticleEffectParams::visitFields(SkFieldVisitor* v) { + SkString oldSpawnCode = fSpawnCode; + SkString oldUpdateCode = fUpdateCode; + v->visit("MaxCount", fMaxCount); v->visit("Duration", fEffectDuration); v->visit("Rate", fRate); - v->visit("Life", fLifetime); v->visit("Drawable", fDrawable); - v->visit("Spawn", fSpawnAffectors); - v->visit("Update", fUpdateAffectors); + v->visit("Spawn", fSpawnCode); + v->visit("Update", fUpdateCode); + + v->visit("Bindings", fBindings); + + // TODO: Or, if any change to binding metadata? + if (fSpawnCode != oldSpawnCode || fUpdateCode != oldUpdateCode) { + this->rebuild(); + } +} + +void SkParticleEffectParams::rebuild() { + auto buildProgram = [this](Program* p, const SkString& code) { + SkSL::Compiler compiler; + SkSL::Program::Settings settings; + + SkTArray> externalValues; + + auto rand = skstd::make_unique("rand", compiler); + compiler.registerExternalValue(rand.get()); + externalValues.push_back(std::move(rand)); + + for (const auto& binding : fBindings) { + if (binding) { + auto value = binding->toValue(compiler); + compiler.registerExternalValue(value.get()); + externalValues.push_back(std::move(value)); + } + } + + auto program = compiler.convertProgram(SkSL::Program::kGeneric_Kind, + SkSL::String(code.c_str()), settings); + if (!program) { + SkDebugf("%s\n", compiler.errorText().c_str()); + return; + } + + auto byteCode = compiler.toByteCode(*program); + if (!byteCode) { + SkDebugf("%s\n", compiler.errorText().c_str()); + return; + } + + p->fByteCode = std::move(byteCode); + p->fExternalValues.swap(externalValues); + }; + + buildProgram(&fSpawnProgram, fSpawnCode); + buildProgram(&fUpdateProgram, fUpdateCode); } SkParticleEffect::SkParticleEffect(sk_sp params, const SkRandom& random) @@ -64,25 +431,40 @@ void SkParticleEffect::update(double now) { float effectAge = static_cast((now - fSpawnTime) / fParams->fEffectDuration); effectAge = fLooping ? fmodf(effectAge, 1.0f) : SkTPin(effectAge, 0.0f, 1.0f); - SkParticleUpdateParams updateParams; - updateParams.fDeltaTime = deltaTime; - updateParams.fEffectAge = effectAge; - - // During spawn, values that refer to kAge_Source get the *effect* age - updateParams.fAgeSource = SkParticleValue::kEffectAge_Source; + float updateParams[2] = { deltaTime, effectAge }; // Advance age for existing particles, and remove any that have reached their end of life for (int i = 0; i < fCount; ++i) { - fParticles[i].fAge += fParticles[i].fInvLifetime * deltaTime; - if (fParticles[i].fAge > 1.0f) { + fParticles.fData[SkParticles::kAge][i] += + fParticles.fData[SkParticles::kLifetime][i] * deltaTime; + if (fParticles.fData[SkParticles::kAge][i] > 1.0f) { // NOTE: This is fast, but doesn't preserve drawing order. Could be a problem... - fParticles[i] = fParticles[fCount - 1]; + for (int j = 0; j < SkParticles::kNumChannels; ++j) { + fParticles.fData[j][i] = fParticles.fData[j][fCount - 1]; + } fStableRandoms[i] = fStableRandoms[fCount - 1]; --i; --fCount; } } + auto runProgram = [](SkParticleEffectParams::Program& program, SkParticles& particles, + float updateParams[], int start, int count) { + if (const auto& byteCode = program.fByteCode) { + float* args[SkParticles::kNumChannels]; + for (int i = 0; i < SkParticles::kNumChannels; ++i) { + args[i] = particles.fData[i].get() + start; + } + SkRandom* randomBase = particles.fRandom.get() + start; + for (const auto& value : program.fExternalValues) { + value->setRandom(randomBase); + } + SkAssertResult(byteCode->runStriped(byteCode->getFunction("main"), + args, SkParticles::kNumChannels, count, + updateParams, 2, nullptr, 0)); + } + }; + // Spawn new particles float desired = fParams->fRate * deltaTime + fSpawnRemainder; int numToSpawn = sk_float_round2int(desired); @@ -94,58 +476,58 @@ void SkParticleEffect::update(double now) { for (int i = 0; i < numToSpawn; ++i) { // Mutate our SkRandom so each particle definitely gets a different generator fRandom.nextU(); - fParticles[fCount].fAge = 0.0f; - 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 }; - fParticles[fCount].fFrame = 0.0f; - fParticles[fCount].fRandom = fRandom; + fParticles.fData[SkParticles::kAge ][fCount] = 0.0f; + fParticles.fData[SkParticles::kLifetime ][fCount] = 0.0f; + fParticles.fData[SkParticles::kPositionX ][fCount] = 0.0f; + fParticles.fData[SkParticles::kPositionY ][fCount] = 0.0f; + fParticles.fData[SkParticles::kHeadingX ][fCount] = 0.0f; + fParticles.fData[SkParticles::kHeadingY ][fCount] = -1.0f; + fParticles.fData[SkParticles::kScale ][fCount] = 1.0f; + fParticles.fData[SkParticles::kVelocityX ][fCount] = 0.0f; + fParticles.fData[SkParticles::kVelocityY ][fCount] = 0.0f; + fParticles.fData[SkParticles::kVelocityAngular][fCount] = 0.0f; + fParticles.fData[SkParticles::kColorR ][fCount] = 1.0f; + fParticles.fData[SkParticles::kColorG ][fCount] = 1.0f; + fParticles.fData[SkParticles::kColorB ][fCount] = 1.0f; + fParticles.fData[SkParticles::kColorA ][fCount] = 1.0f; + fParticles.fData[SkParticles::kSpriteFrame ][fCount] = 0.0f; + fParticles.fRandom[fCount] = fRandom; fCount++; } - // Apply spawn affectors - for (auto affector : fParams->fSpawnAffectors) { - if (affector) { - affector->apply(updateParams, fParticles + spawnBase, numToSpawn); - } - } + // Run the spawn script + runProgram(fParams->fSpawnProgram, fParticles, updateParams, spawnBase, numToSpawn); - // Now stash copies of the random generators and compute particle lifetimes - // (so the curve can refer to spawn-computed source values) + // Now stash copies of the random generators and compute inverse particle lifetimes + // (so that subsequent updates are faster) for (int i = spawnBase; i < fCount; ++i) { - fParticles[i].fInvLifetime = - sk_ieee_float_divide(1.0f, fParams->fLifetime.eval(updateParams, fParticles[i])); - fStableRandoms[i] = fParticles[i].fRandom; + fParticles.fData[SkParticles::kLifetime][i] = + sk_ieee_float_divide(1.0f, fParticles.fData[SkParticles::kLifetime][i]); + fStableRandoms[i] = fParticles.fRandom[i]; } } // Restore all stable random generators so update affectors get consistent behavior each frame for (int i = 0; i < fCount; ++i) { - fParticles[i].fRandom = fStableRandoms[i]; + fParticles.fRandom[i] = fStableRandoms[i]; } - // During update, values that refer to kAge_Source get the *particle* age - updateParams.fAgeSource = SkParticleValue::kParticleAge_Source; - - // Apply update rules - for (auto affector : fParams->fUpdateAffectors) { - if (affector) { - affector->apply(updateParams, fParticles, fCount); - } - } + // Run the update script + runProgram(fParams->fUpdateProgram, fParticles, updateParams, 0, fCount); // Do fixed-function update work (integration of position and orientation) for (int i = 0; i < fCount; ++i) { - fParticles[i].fPose.fPosition += fParticles[i].fVelocity.fLinear * deltaTime; + fParticles.fData[SkParticles::kPositionX][i] += + fParticles.fData[SkParticles::kVelocityX][i] * deltaTime; + fParticles.fData[SkParticles::kPositionY][i] += + fParticles.fData[SkParticles::kVelocityY][i] * deltaTime; - SkScalar s = SkScalarSin(fParticles[i].fVelocity.fAngular * deltaTime), - c = SkScalarCos(fParticles[i].fVelocity.fAngular * deltaTime); - SkVector oldHeading = fParticles[i].fPose.fHeading; - fParticles[i].fPose.fHeading = { oldHeading.fX * c - oldHeading.fY * s, - oldHeading.fX * s + oldHeading.fY * c }; + SkScalar s = SkScalarSin(fParticles.fData[SkParticles::kVelocityAngular][i] * deltaTime), + c = SkScalarCos(fParticles.fData[SkParticles::kVelocityAngular][i] * deltaTime); + float oldHeadingX = fParticles.fData[SkParticles::kHeadingX][i], + oldHeadingY = fParticles.fData[SkParticles::kHeadingY][i]; + fParticles.fData[SkParticles::kHeadingX][i] = oldHeadingX * c - oldHeadingY * s; + fParticles.fData[SkParticles::kHeadingY][i] = oldHeadingX * s + oldHeadingY * c; } // Mark effect as dead if we've reached the end (and are not looping) @@ -158,127 +540,17 @@ void SkParticleEffect::draw(SkCanvas* canvas) { if (this->isAlive() && fParams->fDrawable) { SkPaint paint; paint.setFilterQuality(SkFilterQuality::kMedium_SkFilterQuality); - fParams->fDrawable->draw(canvas, fParticles.get(), fCount, &paint); + fParams->fDrawable->draw(canvas, fParticles, fCount, &paint); } } void SkParticleEffect::setCapacity(int capacity) { - fParticles.realloc(capacity); + for (int i = 0; i < SkParticles::kNumChannels; ++i) { + fParticles.fData[i].realloc(capacity); + } + fParticles.fRandom.realloc(capacity); fStableRandoms.realloc(capacity); fCapacity = capacity; fCount = SkTMin(fCount, fCapacity); } - -constexpr SkFieldVisitor::EnumStringMapping gValueSourceMapping[] = { - { SkParticleValue::kAge_Source, "Age" }, - { SkParticleValue::kRandom_Source, "Random" }, - { SkParticleValue::kParticleAge_Source, "ParticleAge" }, - { SkParticleValue::kEffectAge_Source, "EffectAge" }, - { SkParticleValue::kPositionX_Source, "PositionX" }, - { SkParticleValue::kPositionY_Source, "PositionY" }, - { SkParticleValue::kHeadingX_Source, "HeadingX" }, - { SkParticleValue::kHeadingY_Source, "HeadingY" }, - { SkParticleValue::kScale_Source, "Scale" }, - { SkParticleValue::kVelocityX_Source, "VelocityX" }, - { SkParticleValue::kVelocityY_Source, "VelocityY" }, - { SkParticleValue::kRotation_Source, "Rotation" }, - { SkParticleValue::kColorR_Source, "ColorR" }, - { SkParticleValue::kColorG_Source, "ColorG" }, - { SkParticleValue::kColorB_Source, "ColorB" }, - { SkParticleValue::kColorA_Source, "ColorA" }, - { SkParticleValue::kSpriteFrame_Source, "SpriteFrame" }, -}; - -constexpr SkFieldVisitor::EnumStringMapping gValueTileModeMapping[] = { - { SkParticleValue::kClamp_TileMode, "Clamp" }, - { SkParticleValue::kRepeat_TileMode, "Repeat" }, - { SkParticleValue::kMirror_TileMode, "Mirror" }, -}; - -static bool source_needs_frame(int source) { - switch (source) { - case SkParticleValue::kHeadingX_Source: - case SkParticleValue::kHeadingY_Source: - case SkParticleValue::kVelocityX_Source: - case SkParticleValue::kVelocityY_Source: - return true; - default: - return false; - } -} - -void SkParticleValue::visitFields(SkFieldVisitor* v) { - v->visit("Source", fSource, gValueSourceMapping, SK_ARRAY_COUNT(gValueSourceMapping)); - if (source_needs_frame(fSource)) { - v->visit("Frame", fFrame, gParticleFrameMapping, SK_ARRAY_COUNT(gParticleFrameMapping)); - } - v->visit("TileMode", fTileMode, gValueTileModeMapping, SK_ARRAY_COUNT(gValueTileModeMapping)); - v->visit("Left", fLeft); - v->visit("Right", fRight); - - // Re-compute cached evaluation parameters - fScale = sk_float_isfinite(1.0f / (fRight - fLeft)) ? 1.0f / (fRight - fLeft) : 0; - fBias = -fLeft * fScale; -} - -float SkParticleValue::getSourceValue(const SkParticleUpdateParams& params, - SkParticleState& ps) const { - switch ((kAge_Source == fSource) ? params.fAgeSource : fSource) { - // Do all the simple (non-frame-dependent) sources first: - case kRandom_Source: return ps.fRandom.nextF(); - case kParticleAge_Source: return ps.fAge; - case kEffectAge_Source: return params.fEffectAge; - - case kPositionX_Source: return ps.fPose.fPosition.fX; - case kPositionY_Source: return ps.fPose.fPosition.fY; - case kScale_Source: return ps.fPose.fScale; - case kRotation_Source: return ps.fVelocity.fAngular; - - case kColorR_Source: return ps.fColor.fR; - case kColorG_Source: return ps.fColor.fG; - case kColorB_Source: return ps.fColor.fB; - case kColorA_Source: return ps.fColor.fA; - case kSpriteFrame_Source: return ps.fFrame; - } - - SkASSERT(source_needs_frame(fSource)); - SkVector frameUp = ps.getFrameHeading(static_cast(fFrame)); - SkVector frameRight = { -frameUp.fY, frameUp.fX }; - - switch (fSource) { - case kHeadingX_Source: return ps.fPose.fHeading.dot(frameRight); - case kHeadingY_Source: return ps.fPose.fHeading.dot(frameUp); - case kVelocityX_Source: return ps.fVelocity.fLinear.dot(frameRight); - case kVelocityY_Source: return ps.fVelocity.fLinear.dot(frameUp); - } - - SkDEBUGFAIL("Unreachable"); - return 0.0f; -} - -float SkParticleValue::eval(const SkParticleUpdateParams& params, SkParticleState& ps) const { - float v = this->getSourceValue(params, ps); - v = (v * fScale) + fBias; - - switch (fTileMode) { - case kClamp_TileMode: - v = SkTPin(v, 0.0f, 1.0f); - break; - case kRepeat_TileMode: - v = sk_float_mod(v, 1.0f); - if (v < 0) { - v += 1.0f; - } - break; - case kMirror_TileMode: - v = sk_float_mod(v, 2.0f); - if (v < 0) { - v += 2.0f; - } - v = 1.0f - sk_float_abs(v - 1.0f); - break; - } - - return v; -} diff --git a/resources/particles/default.json b/resources/particles/default.json index 6c5f5cc948..cc79b4fce4 100644 --- a/resources/particles/default.json +++ b/resources/particles/default.json @@ -2,106 +2,18 @@ "MaxCount": 4096, "Duration": 1, "Rate": 1000, - "Life": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": true, - "Bidirectional": false, - "A0": 1, - "A1": 3 - } - ] - }, "Drawable": { "Type": "SkCircleDrawable", "Radius": 1 }, - "Spawn": [ + "Spawn": "// float rand; Every read returns a random float [0 .. 1)\nlayout(ctype=float) in uniform float dt;\nlayout(ctype=float) in uniform float effectAge;\n\nstruct Particle {\n float age;\n float lifetime;\n float2 pos;\n float2 dir;\n float scale;\n float2 vel;\n float spin;\n float4 color;\n float frame;\n};\n\nvoid main(inout Particle p) {\n p.lifetime = mix(1, 3, rand);\n float a = radians(mix(250, 290, rand));\n float s = mix(10, 30, rand);\n p.vel.x = cos(a) * s;\n p.vel.y = sin(a) * s;\n p.pos = text(rand).xy;\n}\n", + "Update": "\n// float rand; Every read returns a random float [0 .. 1)\nlayout(ctype=float) in uniform float dt;\nlayout(ctype=float) in uniform float effectAge;\n\nstruct Particle {\n float age;\n float lifetime;\n float2 pos;\n float2 dir;\n float scale;\n float2 vel;\n float spin;\n float4 color;\n float frame;\n};\n\nvoid main(inout Particle p) {\n float4 startColor = float4(1, 0.196, 0.078, 1);\n float4 endColor = float4(1, 0.784, 0.078, 1);\n p.color = mix(startColor, endColor, p.age);\n}\n", + "Bindings": [ { - "Type": "SkLinearVelocityAffector", - "Enabled": true, - "Force": false, - "Frame": "World", - "Angle": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": true, - "Bidirectional": false, - "A0": -30, - "A1": 30 - } - ] - }, - "Strength": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": true, - "Bidirectional": false, - "A0": 10, - "A1": 30 - } - ] - } - }, - { - "Type": "SkPositionOnTextAffector", - "Enabled": true, - "Input": { - "Source": "Random", - "TileMode": "Clamp", - "Left": 0, - "Right": 1 - }, - "SetHeading": true, + "Type": "SkTextBinding", + "Name": "text", "Text": "SKIA", "FontSize": 96 } - ], - "Update": [ - { - "Type": "SkColorAffector", - "Enabled": true, - "Curve": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Linear", - "Ranged": false, - "A0": [ 1, 0.196078, 0.0784314, 1 ], - "D0": [ 1, 0.784314, 0.0784314, 1 ] - } - ] - } - } ] } \ No newline at end of file diff --git a/resources/particles/explosion.json b/resources/particles/explosion.json index 42616729bf..e07358370f 100644 --- a/resources/particles/explosion.json +++ b/resources/particles/explosion.json @@ -2,117 +2,13 @@ "MaxCount": 32, "Duration": 1, "Rate": 8, - "Life": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": true, - "Bidirectional": false, - "A0": 1, - "A1": 3 - } - ] - }, "Drawable": { "Type": "SkImageDrawable", "Path": "resources/images/explosion_sprites.png", "Columns": 4, "Rows": 4 }, - "Spawn": [ - { - "Type": "SkPositionInCircleAffector", - "Enabled": true, - "SetHeading": true, - "X": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": false, - "Bidirectional": false, - "A0": 0 - } - ] - }, - "Y": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": false, - "Bidirectional": false, - "A0": 0 - } - ] - }, - "Radius": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": false, - "Bidirectional": false, - "A0": 60 - } - ] - } - } - ], - "Update": [ - { - "Type": "SkPointForceAffector", - "Enabled": true, - "Point": { "x": 200, "y": 200 }, - "Constant": 0, - "InvSquare": -50 - }, - { - "Type": "SkFrameAffector", - "Enabled": true, - "Curve": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Linear", - "Ranged": false, - "Bidirectional": false, - "A0": 0, - "D0": 1 - } - ] - } - } - ] + "Spawn": "// float rand; Every read returns a random float [0 .. 1)\nlayout(ctype=float) in uniform float dt;\nlayout(ctype=float) in uniform float effectAge;\n\nstruct Particle {\n float age;\n float lifetime;\n float2 pos;\n float2 dir;\n float scale;\n float2 vel;\n float spin;\n float4 color;\n float frame;\n};\n\nfloat2 circle() {\n float x;\n float y;\n do {\n x = rand * 2 - 1;\n y = rand * 2 - 1;\n } while (x*x + y*y > 1);\n return float2(x, y);\n}\n\nvoid main(inout Particle p) {\n p.lifetime = 1.0 + rand * 2.0;\n p.pos = circle() * 60;\n p.vel = p.pos / 3;\n}\n", + "Update": "// float rand; Every read returns a random float [0 .. 1)\nlayout(ctype=float) in uniform float dt;\nlayout(ctype=float) in uniform float effectAge;\n\nstruct Particle {\n float age;\n float lifetime;\n float2 pos;\n float2 dir;\n float scale;\n float2 vel;\n float spin;\n float4 color;\n float frame;\n};\n\nvoid main(inout Particle p) {\n p.frame = p.age;\n}\n", + "Bindings": [] } \ No newline at end of file diff --git a/resources/particles/interp.json b/resources/particles/interp.json index 9176e0fb20..cd6f0a0ea5 100644 --- a/resources/particles/interp.json +++ b/resources/particles/interp.json @@ -2,40 +2,11 @@ "MaxCount": 6000, "Duration": 5, "Rate": 2000, - "Life": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": true, - "Bidirectional": false, - "A0": 2, - "A1": 4 - } - ] - }, "Drawable": { "Type": "SkCircleDrawable", "Radius": 2 }, - "Spawn": [ - { - "Type": "SkInterpreterAffector", - "Enabled": true, - "Code": "// float rand;\nlayout(ctype=float) in uniform float dt;\nlayout(ctype=float) in uniform float effectAge;\n\nvoid main(in float age,\n in float invLifetime,\n inout float2 pos,\n inout float2 dir,\n inout float scale,\n inout float2 vel,\n inout float spin,\n inout float4 color) {\n vel.x = 50 + (30 * rand);\n vel.y = (rand * 20) - 10;\n}\n" - } - ], - "Update": [ - { - "Type": "SkInterpreterAffector", - "Enabled": true, - "Code": "// float rand;\nlayout(ctype=float) in uniform float dt;\nlayout(ctype=float) in uniform float effectAge;\n\nvoid main(in float age,\n in float invLifetime,\n inout float2 pos,\n inout float2 dir,\n inout float scale,\n inout float2 vel,\n inout float spin,\n inout float4 color) {\n color.r = age;\n color.g = 1 - age;\n\n float s1 = 0.5 + (1.5 * age);\n float s2 = 1.0 + (-0.75 * age);\n scale = s1 + (s2 - s1) * rand;\n\n vel.y += 20.0 * dt;\n}\n" - } - ] + "Spawn": "// float rand; Every read returns a random float [0 .. 1)\nlayout(ctype=float) in uniform float dt;\nlayout(ctype=float) in uniform float effectAge;\n\nstruct Particle {\n float age;\n float lifetime;\n float2 pos;\n float2 dir;\n float scale;\n float2 vel;\n float spin;\n float4 color;\n float frame;\n};\n\nvoid main(inout Particle p) {\n p.lifetime = 2 + (rand * 2);\n p.vel.x = (30 * rand) + 50;\n p.vel.y = (20 * rand) - 10;\n}\n", + "Update": "// float rand; Every read returns a random float [0 .. 1)\nlayout(ctype=float) in uniform float dt;\nlayout(ctype=float) in uniform float effectAge;\n\nstruct Particle {\n float age;\n float lifetime;\n float2 pos;\n float2 dir;\n float scale;\n float2 vel;\n float spin;\n float4 color;\n float frame;\n};\n\nvoid main(inout Particle p) {\n p.color.r = p.age;\n p.color.g = 1 - p.age;\n float s1 = 0.5 + (1.5 * p.age);\n float s2 = 1.0 + (-0.75 * p.age);\n p.scale = s1 + (s2 - s1) * rand;\n p.vel.y += 20.0 * dt;\n}\n", + "Bindings": [] } \ No newline at end of file diff --git a/resources/particles/native.json b/resources/particles/native.json deleted file mode 100644 index 00ff327c84..0000000000 --- a/resources/particles/native.json +++ /dev/null @@ -1,158 +0,0 @@ -{ - "MaxCount": 6000, - "Duration": 5, - "Rate": 2000, - "Life": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": true, - "Bidirectional": false, - "A0": 2, - "A1": 4 - } - ] - }, - "Drawable": { - "Type": "SkCircleDrawable", - "Radius": 2 - }, - "Spawn": [ - { - "Type": "SkLinearVelocityAffector", - "Enabled": true, - "Force": false, - "Frame": "World", - "Angle": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": true, - "Bidirectional": false, - "A0": 80, - "A1": 100 - } - ] - }, - "Strength": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": true, - "Bidirectional": false, - "A0": 50, - "A1": 80 - } - ] - } - } - ], - "Update": [ - { - "Type": "SkColorAffector", - "Enabled": true, - "Curve": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Linear", - "Ranged": false, - "A0": [ 0, 1, 1, 1 ], - "D0": [ 1, 0, 1, 1 ] - } - ] - } - }, - { - "Type": "SkSizeAffector", - "Enabled": true, - "Curve": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Linear", - "Ranged": true, - "Bidirectional": false, - "A0": 0.5, - "D0": 2, - "A1": 1, - "D1": 0.25 - } - ] - } - }, - { - "Type": "SkLinearVelocityAffector", - "Enabled": true, - "Force": true, - "Frame": "World", - "Angle": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": false, - "Bidirectional": false, - "A0": 0 - } - ] - }, - "Strength": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": false, - "Bidirectional": false, - "A0": -20 - } - ] - } - } - ] -} \ No newline at end of file diff --git a/resources/particles/penguin_cannon.json b/resources/particles/penguin_cannon.json index 2f612b6eeb..5d544961fb 100644 --- a/resources/particles/penguin_cannon.json +++ b/resources/particles/penguin_cannon.json @@ -2,135 +2,13 @@ "MaxCount": 32, "Duration": 1, "Rate": 0.5, - "Life": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": false, - "Bidirectional": false, - "A0": 20 - } - ] - }, "Drawable": { "Type": "SkImageDrawable", "Path": "resources/images/baby_tux.png", "Columns": 1, "Rows": 1 }, - "Spawn": [ - { - "Type": "SkLinearVelocityAffector", - "Enabled": true, - "Force": false, - "Frame": "World", - "Angle": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": true, - "Bidirectional": false, - "A0": 10, - "A1": 70 - } - ] - }, - "Strength": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": true, - "Bidirectional": false, - "A0": 140, - "A1": 200 - } - ] - } - } - ], - "Update": [ - { - "Type": "SkLinearVelocityAffector", - "Enabled": true, - "Force": true, - "Frame": "World", - "Angle": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": false, - "Bidirectional": false, - "A0": 180 - } - ] - }, - "Strength": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": false, - "Bidirectional": false, - "A0": 50 - } - ] - } - }, - { - "Type": "SkOrientationAffector", - "Enabled": true, - "Frame": "Velocity", - "Angle": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": false, - "Bidirectional": false, - "A0": 0 - } - ] - } - } - ] + "Spawn": "// float rand; Every read returns a random float [0 .. 1)\nlayout(ctype=float) in uniform float dt;\nlayout(ctype=float) in uniform float effectAge;\n\nstruct Particle {\n float age;\n float lifetime;\n float2 pos;\n float2 dir;\n float scale;\n float2 vel;\n float spin;\n float4 color;\n float frame;\n};\n\nvoid main(inout Particle p) {\n p.lifetime = 20;\n float a = radians(10 + 60 * rand);\n float s = 140 + rand * 60;\n p.vel.x = cos(a) * s;\n p.vel.y = -sin(a) * s;\n}\n", + "Update": "// float rand; Every read returns a random float [0 .. 1)\nlayout(ctype=float) in uniform float dt;\nlayout(ctype=float) in uniform float effectAge;\n\nstruct Particle {\n float age;\n float lifetime;\n float2 pos;\n float2 dir;\n float scale;\n float2 vel;\n float spin;\n float4 color;\n float frame;\n};\n\nvoid main(inout Particle p) {\n p.vel.y += 50 * dt;\n p.dir = normalize(p.vel);\n}\n", + "Bindings": [] } \ No newline at end of file diff --git a/resources/particles/snowfall.json b/resources/particles/snowfall.json index 105960101d..c3e1d94298 100644 --- a/resources/particles/snowfall.json +++ b/resources/particles/snowfall.json @@ -2,94 +2,17 @@ "MaxCount": 4096, "Duration": 1, "Rate": 30, - "Life": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": false, - "Bidirectional": false, - "A0": 10 - } - ] - }, "Drawable": { "Type": "SkCircleDrawable", "Radius": 1 }, - "Spawn": [ + "Spawn": "// float rand; Every read returns a random float [0 .. 1)\nlayout(ctype=float) in uniform float dt;\nlayout(ctype=float) in uniform float effectAge;\n\nstruct Particle {\n float age;\n float lifetime;\n float2 pos;\n float2 dir;\n float scale;\n float2 vel;\n float spin;\n float4 color;\n float frame;\n};\n\nvoid main(inout Particle p) {\n p.lifetime = 10;\n p.vel.y = 10 + rand * 20;\n p.vel.x = -5 + 10 * rand;\n p.pos.x = rand * 500;\n}\n", + "Update": "// float rand; Every read returns a random float [0 .. 1)\nlayout(ctype=float) in uniform float dt;\nlayout(ctype=float) in uniform float effectAge;\n\nstruct Particle {\n float age;\n float lifetime;\n float2 pos;\n float2 dir;\n float scale;\n float2 vel;\n float spin;\n float4 color;\n float frame;\n};\n\nvoid main(inout Particle p) {\n p.scale = size(p.age);\n}\n", + "Bindings": [ { - "Type": "SkLinearVelocityAffector", - "Enabled": true, - "Force": false, - "Frame": "World", - "Angle": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": true, - "Bidirectional": false, - "A0": 170, - "A1": 190 - } - ] - }, - "Strength": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": true, - "Bidirectional": false, - "A0": 10, - "A1": 30 - } - ] - } - }, - { - "Type": "SkPositionOnPathAffector", - "Enabled": true, - "Input": { - "Source": "Random", - "TileMode": "Clamp", - "Left": 0, - "Right": 1 - }, - "SetHeading": false, - "Path": "h500" - } - ], - "Update": [ - { - "Type": "SkSizeAffector", - "Enabled": true, + "Type": "SkCurveBinding", + "Name": "size", "Curve": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, "XValues": [], "Segments": [ { diff --git a/resources/particles/spiral.json b/resources/particles/spiral.json index 9fda70d240..0c0a8029ce 100644 --- a/resources/particles/spiral.json +++ b/resources/particles/spiral.json @@ -2,117 +2,11 @@ "MaxCount": 800, "Duration": 4, "Rate": 120, - "Life": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": true, - "Bidirectional": false, - "A0": 2, - "A1": 3 - } - ] - }, "Drawable": { "Type": "SkCircleDrawable", "Radius": 2 }, - "Spawn": [ - { - "Type": "SkLinearVelocityAffector", - "Enabled": true, - "Force": false, - "Frame": "World", - "Angle": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Linear", - "Ranged": false, - "Bidirectional": false, - "A0": 0, - "D0": 1080 - } - ] - }, - "Strength": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": true, - "Bidirectional": false, - "A0": 50, - "A1": 60 - } - ] - } - } - ], - "Update": [ - { - "Type": "SkSizeAffector", - "Enabled": true, - "Curve": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Linear", - "Ranged": false, - "Bidirectional": false, - "A0": 0.5, - "D0": 2 - } - ] - } - }, - { - "Type": "SkColorAffector", - "Enabled": true, - "Curve": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Linear", - "Ranged": true, - "A0": [ 0.0999616, 0.140218, 0.784314, 1 ], - "D0": [ 0.523837, 0.886396, 0.980392, 1 ], - "A1": [ 0.378665, 0.121107, 0.705882, 1 ], - "D1": [ 0.934257, 0.229599, 0.955882, 1 ] - } - ] - } - } - ] + "Spawn": "// float rand; Every read returns a random float [0 .. 1)\nlayout(ctype=float) in uniform float dt;\nlayout(ctype=float) in uniform float effectAge;\n\nstruct Particle {\n float age;\n float lifetime;\n float2 pos;\n float2 dir;\n float scale;\n float2 vel;\n float spin;\n float4 color;\n float frame;\n};\n\nvoid main(inout Particle p) {\n p.lifetime = 2 + rand;\n float a = radians(effectAge * 1080);\n float s = 50 + rand * 10;\n p.vel.x = cos(a) * s;\n p.vel.y = sin(a) * s;\n}\n", + "Update": "// float rand; Every read returns a random float [0 .. 1)\nlayout(ctype=float) in uniform float dt;\nlayout(ctype=float) in uniform float effectAge;\n\nstruct Particle {\n float age;\n float lifetime;\n float2 pos;\n float2 dir;\n float scale;\n float2 vel;\n float spin;\n float4 color;\n float frame;\n};\n\nvoid main(inout Particle p) {\n p.scale = 0.5 + 1.5 * p.age;\n float3 a0 = float3(0.098, 0.141, 0.784);\n float3 a1 = float3(0.525, 0.886, 0.980);\n float3 b0 = float3(0.376, 0.121, 0.705);\n float3 b1 = float3(0.933, 0.227, 0.953);\n p.color.rgb = mix(mix(a0, a1, p.age), mix(b0, b1, p.age), rand);\n}\n", + "Bindings": [] } \ No newline at end of file diff --git a/resources/particles/swirl.json b/resources/particles/swirl.json index 231a92d8c5..e30e9eeb0b 100644 --- a/resources/particles/swirl.json +++ b/resources/particles/swirl.json @@ -2,114 +2,17 @@ "MaxCount": 4096, "Duration": 1, "Rate": 400, - "Life": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": true, - "Bidirectional": false, - "A0": 1, - "A1": 3 - } - ] - }, "Drawable": { "Type": "SkCircleDrawable", "Radius": 2 }, - "Spawn": [ + "Spawn": "// float rand; Every read returns a random float [0 .. 1)\nlayout(ctype=float) in uniform float dt;\nlayout(ctype=float) in uniform float effectAge;\n\nstruct Particle {\n float age;\n float lifetime;\n float2 pos;\n float2 dir;\n float scale;\n float2 vel;\n float spin;\n float4 color;\n float frame;\n};\n\nvoid main(inout Particle p) {\n p.lifetime = 1 + 2 * rand;\n p.pos.x = rand * 50;\n float a = radians(-10 + 20 * rand);\n float s = 50 + 10 * rand;\n p.vel.x = sin(a) * s;\n p.vel.y = -cos(a) * s;\n}\n", + "Update": "// float rand; Every read returns a random float [0 .. 1)\nlayout(ctype=float) in uniform float dt;\nlayout(ctype=float) in uniform float effectAge;\n\nstruct Particle {\n float age;\n float lifetime;\n float2 pos;\n float2 dir;\n float scale;\n float2 vel;\n float spin;\n float4 color;\n float frame;\n};\n\nvoid main(inout Particle p) {\n p.vel.x += wind(p.age) * dt;\n p.scale = 3 - (1.5 * p.age);\n p.color = color(p.age);\n}\n", + "Bindings": [ { - "Type": "SkPositionOnPathAffector", - "Enabled": true, - "Input": { - "Source": "Random", - "TileMode": "Clamp", - "Left": 0, - "Right": 1 - }, - "SetHeading": true, - "Path": "h50" - }, - { - "Type": "SkLinearVelocityAffector", - "Enabled": true, - "Force": false, - "Frame": "World", - "Angle": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": true, - "Bidirectional": false, - "A0": -10, - "A1": 10 - } - ] - }, - "Strength": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": true, - "Bidirectional": false, - "A0": 50, - "A1": 60 - } - ] - } - } - ], - "Update": [ - { - "Type": "SkLinearVelocityAffector", - "Enabled": true, - "Force": true, - "Frame": "World", - "Angle": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": false, - "Bidirectional": false, - "A0": 90 - } - ] - }, - "Strength": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, + "Type": "SkCurveBinding", + "Name": "wind", + "Curve": { "XValues": [], "Segments": [ { @@ -125,46 +28,18 @@ } }, { - "Type": "SkSizeAffector", - "Enabled": true, + "Type": "SkColorCurveBinding", + "Name": "color", "Curve": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Linear", - "Ranged": false, - "Bidirectional": false, - "A0": 3, - "D0": 1.5 - } - ] - } - }, - { - "Type": "SkColorAffector", - "Enabled": true, - "Curve": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, "XValues": [], "Segments": [ { "Type": "Linear", "Ranged": true, - "A0": [ 0.0999616, 0.140218, 0.784314, 1 ], - "D0": [ 0.523837, 0.886396, 0.980392, 1 ], - "A1": [ 0.378665, 0.121107, 0.705882, 1 ], - "D1": [ 0.934257, 0.229599, 0.955882, 1 ] + "A0": [ 1, 0, 0, 1 ], + "D0": [ 1, 0.735294, 0, 1 ], + "A1": [ 1, 0.588235, 0, 1 ], + "D1": [ 0.941177, 1, 0, 1 ] } ] } diff --git a/resources/particles/warp.json b/resources/particles/warp.json index cc079fdb81..bf597d5267 100644 --- a/resources/particles/warp.json +++ b/resources/particles/warp.json @@ -2,114 +2,11 @@ "MaxCount": 4096, "Duration": 1, "Rate": 90, - "Life": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": false, - "Bidirectional": false, - "A0": 30 - } - ] - }, "Drawable": { "Type": "SkCircleDrawable", "Radius": 2 }, - "Spawn": [ - { - "Type": "SkPositionInCircleAffector", - "Enabled": true, - "SetHeading": true, - "X": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": false, - "Bidirectional": false, - "A0": 0 - } - ] - }, - "Y": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": false, - "Bidirectional": false, - "A0": 0 - } - ] - }, - "Radius": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Constant", - "Ranged": false, - "Bidirectional": false, - "A0": 40 - } - ] - } - } - ], - "Update": [ - { - "Type": "SkPointForceAffector", - "Enabled": true, - "Point": { "x": 0, "y": 0 }, - "Constant": -10, - "InvSquare": 0 - }, - { - "Type": "SkSizeAffector", - "Enabled": true, - "Curve": { - "Input": { - "Source": "Age", - "TileMode": "Repeat", - "Left": 0, - "Right": 1 - }, - "XValues": [], - "Segments": [ - { - "Type": "Linear", - "Ranged": false, - "Bidirectional": false, - "A0": 0.25, - "D0": 3 - } - ] - } - } - ] + "Spawn": "\n// float rand; Every read returns a random float [0 .. 1)\nlayout(ctype=float) in uniform float dt;\nlayout(ctype=float) in uniform float effectAge;\n\nstruct Particle {\n float age;\n float lifetime;\n float2 pos;\n float2 dir;\n float scale;\n float2 vel;\n float spin;\n float4 color;\n float frame;\n};\n\nfloat2 circle() {\n float x;\n float y;\n do {\n x = rand * 2 - 1;\n y = rand * 2 - 1;\n } while (x*x + y*y > 1);\n return float2(x, y);\n}\n\nvoid main(inout Particle p) {\n p.lifetime = 30;\n p.pos = circle() * 40;\n}\n", + "Update": "\n// float rand; Every read returns a random float [0 .. 1)\nlayout(ctype=float) in uniform float dt;\nlayout(ctype=float) in uniform float effectAge;\n\nstruct Particle {\n float age;\n float lifetime;\n float2 pos;\n float2 dir;\n float scale;\n float2 vel;\n float spin;\n float4 color;\n float frame;\n};\n\nvoid main(inout Particle p) {\n p.vel += normalize(p.pos) * dt * 10;\n p.scale = mix(0.25, 3, p.age);\n}\n", + "Bindings": [] } \ No newline at end of file diff --git a/tools/cpu_modules.cpp b/tools/cpu_modules.cpp index fe1fd22758..4354a73619 100644 --- a/tools/cpu_modules.cpp +++ b/tools/cpu_modules.cpp @@ -5,7 +5,6 @@ * found in the LICENSE file. */ -#include "modules/particles/include/SkParticleAffector.h" #include "modules/particles/include/SkParticleDrawable.h" #include "modules/particles/include/SkParticleEffect.h" #include "modules/particles/include/SkParticleSerialization.h" @@ -16,7 +15,7 @@ int main(int argc, char** argv) { // Register types for serialization REGISTER_REFLECTED(SkReflected); - SkParticleAffector::RegisterAffectorTypes(); + SkParticleBinding::RegisterBindingTypes(); SkParticleDrawable::RegisterDrawableTypes(); return 0; } diff --git a/tools/viewer/ParticlesSlide.cpp b/tools/viewer/ParticlesSlide.cpp index 9943e9b84a..286cdeec85 100644 --- a/tools/viewer/ParticlesSlide.cpp +++ b/tools/viewer/ParticlesSlide.cpp @@ -7,7 +7,6 @@ #include "tools/viewer/ParticlesSlide.h" -#include "modules/particles/include/SkParticleAffector.h" #include "modules/particles/include/SkParticleDrawable.h" #include "modules/particles/include/SkParticleEffect.h" #include "modules/particles/include/SkParticleSerialization.h" @@ -205,7 +204,7 @@ private: ParticlesSlide::ParticlesSlide() { // Register types for serialization REGISTER_REFLECTED(SkReflected); - SkParticleAffector::RegisterAffectorTypes(); + SkParticleBinding::RegisterBindingTypes(); SkParticleDrawable::RegisterDrawableTypes(); fName = "Particles"; fPlayPosition.set(200.0f, 200.0f);