Add SkParticleValue to allow further customization of curve behavior
All curves (and path affectors) are driven by an SkParticleValue. The value can derive its value from the current defaults (age of particle or effect), or explicitly choose the other one, a random value, or any other particle value. Values can be range adjusted and support repeat, clamp, and mirror tiling. Also fixed some more issues related to resource path in the slide GUI. Bug: skia: Change-Id: I4755018d5b57ae2d5ec400d541055ca4fb542978 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/196760 Reviewed-by: Brian Osman <brianosman@google.com> Commit-Queue: Brian Osman <brianosman@google.com>
This commit is contained in:
parent
0f27b5ee2b
commit
bdcdf1a7c3
@ -9,6 +9,7 @@
|
||||
#define SkCurve_DEFINED
|
||||
|
||||
#include "SkColor.h"
|
||||
#include "SkParticleData.h"
|
||||
#include "SkScalar.h"
|
||||
#include "SkTArray.h"
|
||||
|
||||
@ -73,10 +74,12 @@ struct SkCurve {
|
||||
fSegments.push_back().setConstant(c);
|
||||
}
|
||||
|
||||
// Evaluate this curve at x, using random for curves that have ranged or bidirectional segments.
|
||||
SkScalar eval(SkScalar x, SkRandom& random) const;
|
||||
SkScalar eval(const SkParticleUpdateParams& params, SkParticleState& ps) 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<SkScalar, true> fXValues;
|
||||
SkTArray<SkCurveSegment, true> fSegments;
|
||||
@ -96,7 +99,7 @@ struct SkColorCurveSegment {
|
||||
}
|
||||
}
|
||||
|
||||
SkColor4f eval(SkScalar x, SkRandom& random) const;
|
||||
SkColor4f eval(SkScalar x, SkScalar t) const;
|
||||
void visitFields(SkFieldVisitor* v);
|
||||
|
||||
void setConstant(SkColor4f c) {
|
||||
@ -117,9 +120,10 @@ struct SkColorCurve {
|
||||
fSegments.push_back().setConstant(c);
|
||||
}
|
||||
|
||||
SkColor4f eval(SkScalar x, SkRandom& random) const;
|
||||
SkColor4f eval(const SkParticleUpdateParams& params, SkParticleState& ps) const;
|
||||
void visitFields(SkFieldVisitor* v);
|
||||
|
||||
SkParticleValue fInput;
|
||||
SkTArray<SkScalar, true> fXValues;
|
||||
SkTArray<SkColorCurveSegment, true> fSegments;
|
||||
};
|
||||
|
@ -10,24 +10,17 @@
|
||||
|
||||
#include "SkReflected.h"
|
||||
|
||||
#include "SkParticleData.h"
|
||||
#include "SkPoint.h"
|
||||
|
||||
struct SkColorCurve;
|
||||
struct SkCurve;
|
||||
struct SkParticleState;
|
||||
struct SkParticleUpdateParams;
|
||||
|
||||
enum SkParticleFrame {
|
||||
kWorld_ParticleFrame, // "Up" is { 0, -1 }
|
||||
kLocal_ParticleFrame, // "Up" is particle's heading
|
||||
kVelocity_ParticleFrame, // "Up" is particle's direction of travel
|
||||
};
|
||||
|
||||
class SkParticleAffector : public SkReflected {
|
||||
public:
|
||||
REFLECTED_ABSTRACT(SkParticleAffector, SkReflected)
|
||||
|
||||
void apply(SkParticleUpdateParams& params, SkParticleState ps[], int count);
|
||||
void apply(const SkParticleUpdateParams& params, SkParticleState ps[], int count);
|
||||
void visitFields(SkFieldVisitor* v) override;
|
||||
|
||||
static void RegisterAffectorTypes();
|
||||
@ -53,7 +46,7 @@ public:
|
||||
static sk_sp<SkParticleAffector> MakeColor(const SkColorCurve& curve);
|
||||
|
||||
private:
|
||||
virtual void onApply(SkParticleUpdateParams& params, SkParticleState ps[], int count) = 0;
|
||||
virtual void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) = 0;
|
||||
|
||||
bool fEnabled = true;
|
||||
};
|
||||
|
@ -11,12 +11,25 @@
|
||||
#include "SkColor.h"
|
||||
#include "SkPoint.h"
|
||||
#include "SkRandom.h"
|
||||
#include "SkReflected.h"
|
||||
#include "SkRSXform.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
|
||||
};
|
||||
|
||||
static constexpr SkFieldVisitor::EnumStringMapping gParticleFrameMapping[] = {
|
||||
{ kWorld_ParticleFrame, "World" },
|
||||
{ kLocal_ParticleFrame, "Local" },
|
||||
{ kVelocity_ParticleFrame, "Velocity" },
|
||||
};
|
||||
|
||||
struct SkParticlePose {
|
||||
SkPoint fPosition;
|
||||
SkVector fHeading;
|
||||
@ -44,10 +57,83 @@ struct SkParticleState {
|
||||
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,
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
#endif // SkParticleData_DEFINED
|
||||
|
@ -9,13 +9,13 @@
|
||||
#define SkReflected_DEFINED
|
||||
|
||||
#include "SkColor.h"
|
||||
#include "SkCurve.h"
|
||||
#include "SkRefCnt.h"
|
||||
#include "SkString.h"
|
||||
#include "SkTArray.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
struct SkCurve;
|
||||
class SkFieldVisitor;
|
||||
class SkRandom;
|
||||
|
||||
@ -158,11 +158,7 @@ public:
|
||||
virtual void visit(const char*, int&, const EnumStringMapping*, int count) = 0;
|
||||
|
||||
// Specific virtual signature for SkCurve, to allow for heavily customized UI in SkGuiVisitor.
|
||||
virtual void visit(const char* name, SkCurve& c) {
|
||||
this->enterObject(name);
|
||||
c.visitFields(this);
|
||||
this->exitObject();
|
||||
}
|
||||
virtual void visit(const char* name, SkCurve& c);
|
||||
|
||||
// Default visit function for structs with no special behavior. It is assumed that any such
|
||||
// struct implements visitFields(SkFieldVisitor*) to recursively visit each of its fields.
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include "SkCurve.h"
|
||||
|
||||
#include "SkParticleData.h"
|
||||
#include "SkRandom.h"
|
||||
#include "SkReflected.h"
|
||||
|
||||
@ -78,9 +79,11 @@ void SkCurveSegment::visitFields(SkFieldVisitor* v) {
|
||||
}
|
||||
}
|
||||
|
||||
SkScalar SkCurve::eval(SkScalar x, SkRandom& random) const {
|
||||
SkScalar SkCurve::eval(const SkParticleUpdateParams& params, SkParticleState& ps) 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]) {
|
||||
@ -98,12 +101,13 @@ SkScalar SkCurve::eval(SkScalar x, SkRandom& random) const {
|
||||
|
||||
// Always pull t and negate here, so that the stable generator behaves consistently, even if
|
||||
// our segments use an inconsistent feature-set.
|
||||
SkScalar t = random.nextF();
|
||||
bool negate = random.nextBool();
|
||||
SkScalar t = ps.fRandom.nextF();
|
||||
bool negate = ps.fRandom.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);
|
||||
|
||||
@ -117,10 +121,10 @@ void SkCurve::visitFields(SkFieldVisitor* v) {
|
||||
}
|
||||
}
|
||||
|
||||
SkColor4f SkColorCurveSegment::eval(SkScalar x, SkRandom& random) const {
|
||||
SkColor4f SkColorCurveSegment::eval(SkScalar x, SkScalar t) const {
|
||||
SkColor4f result = eval_segment(fMin, x, fType);
|
||||
if (fRanged) {
|
||||
result = result + (eval_segment(fMax, x, fType) - result) * random.nextF();
|
||||
result = result + (eval_segment(fMax, x, fType) - result) * t;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -148,9 +152,11 @@ void SkColorCurveSegment::visitFields(SkFieldVisitor* v) {
|
||||
}
|
||||
}
|
||||
|
||||
SkColor4f SkColorCurve::eval(SkScalar x, SkRandom& random) const {
|
||||
SkColor4f SkColorCurve::eval(const SkParticleUpdateParams& params, SkParticleState& ps) 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]) {
|
||||
@ -165,10 +171,11 @@ SkColor4f SkColorCurve::eval(SkScalar x, SkRandom& random) const {
|
||||
segmentX = rangeMin;
|
||||
}
|
||||
SkASSERT(0.0f <= segmentX && segmentX <= 1.0f);
|
||||
return fSegments[i].eval(segmentX, random);
|
||||
return fSegments[i].eval(segmentX, ps.fRandom.nextF());
|
||||
}
|
||||
|
||||
void SkColorCurve::visitFields(SkFieldVisitor* v) {
|
||||
v->visit("Input", fInput);
|
||||
v->visit("XValues", fXValues);
|
||||
v->visit("Segments", fSegments);
|
||||
|
||||
|
@ -17,13 +17,8 @@
|
||||
|
||||
#include "sk_tool_utils.h"
|
||||
|
||||
constexpr SkFieldVisitor::EnumStringMapping gParticleFrameMapping[] = {
|
||||
{ kWorld_ParticleFrame, "World" },
|
||||
{ kLocal_ParticleFrame, "Local" },
|
||||
{ kVelocity_ParticleFrame, "Velocity" },
|
||||
};
|
||||
|
||||
void SkParticleAffector::apply(SkParticleUpdateParams& params, SkParticleState ps[], int count) {
|
||||
void SkParticleAffector::apply(const SkParticleUpdateParams& params,
|
||||
SkParticleState ps[], int count) {
|
||||
if (fEnabled) {
|
||||
this->onApply(params, ps, count);
|
||||
}
|
||||
@ -33,23 +28,6 @@ void SkParticleAffector::visitFields(SkFieldVisitor* v) {
|
||||
v->visit("Enabled", fEnabled);
|
||||
}
|
||||
|
||||
static inline SkVector get_heading(const SkParticleState& ps, SkParticleFrame frame) {
|
||||
switch (frame) {
|
||||
case kLocal_ParticleFrame:
|
||||
return ps.fPose.fHeading;
|
||||
case kVelocity_ParticleFrame: {
|
||||
SkVector heading = ps.fVelocity.fLinear;
|
||||
if (!heading.normalize()) {
|
||||
heading.set(0, -1);
|
||||
}
|
||||
return heading;
|
||||
}
|
||||
case kWorld_ParticleFrame:
|
||||
default:
|
||||
return SkVector{ 0, -1 };
|
||||
}
|
||||
}
|
||||
|
||||
class SkLinearVelocityAffector : public SkParticleAffector {
|
||||
public:
|
||||
SkLinearVelocityAffector(const SkCurve& angle = 0.0f,
|
||||
@ -63,14 +41,14 @@ public:
|
||||
|
||||
REFLECTED(SkLinearVelocityAffector, SkParticleAffector)
|
||||
|
||||
void onApply(SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
|
||||
void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
float angle = fAngle.eval(ps[i].fAge, ps[i].fRandom);
|
||||
float angle = fAngle.eval(params, ps[i]);
|
||||
SkScalar c_local, s_local = SkScalarSinCos(SkDegreesToRadians(angle), &c_local);
|
||||
SkVector heading = get_heading(ps[i], static_cast<SkParticleFrame>(fFrame));
|
||||
SkVector heading = ps[i].getFrameHeading(static_cast<SkParticleFrame>(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(ps[i].fAge, ps[i].fRandom);
|
||||
float strength = fStrength.eval(params, ps[i]);
|
||||
SkVector force = { c * strength, s * strength };
|
||||
if (fForce) {
|
||||
ps[i].fVelocity.fLinear += force * params.fDeltaTime;
|
||||
@ -103,9 +81,9 @@ public:
|
||||
|
||||
REFLECTED(SkAngularVelocityAffector, SkParticleAffector)
|
||||
|
||||
void onApply(SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
|
||||
void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
float strength = fStrength.eval(ps[i].fAge, ps[i].fRandom);
|
||||
float strength = fStrength.eval(params, ps[i]);
|
||||
if (fForce) {
|
||||
ps[i].fVelocity.fAngular += strength * params.fDeltaTime;
|
||||
} else {
|
||||
@ -133,7 +111,7 @@ public:
|
||||
|
||||
REFLECTED(SkPointForceAffector, SkParticleAffector)
|
||||
|
||||
void onApply(SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
|
||||
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);
|
||||
@ -165,11 +143,11 @@ public:
|
||||
|
||||
REFLECTED(SkOrientationAffector, SkParticleAffector)
|
||||
|
||||
void onApply(SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
|
||||
void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
float angle = fAngle.eval(ps[i].fAge, ps[i].fRandom);
|
||||
float angle = fAngle.eval(params, ps[i]);
|
||||
SkScalar c_local, s_local = SkScalarSinCos(SkDegreesToRadians(angle), &c_local);
|
||||
SkVector heading = get_heading(ps[i], static_cast<SkParticleFrame>(fFrame));
|
||||
SkVector heading = ps[i].getFrameHeading(static_cast<SkParticleFrame>(fFrame));
|
||||
ps[i].fPose.fHeading.set(heading.fX * c_local - heading.fY * s_local,
|
||||
heading.fX * s_local + heading.fY * c_local);
|
||||
}
|
||||
@ -197,7 +175,7 @@ public:
|
||||
|
||||
REFLECTED(SkPositionInCircleAffector, SkParticleAffector)
|
||||
|
||||
void onApply(SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
|
||||
void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
SkVector v;
|
||||
do {
|
||||
@ -205,9 +183,8 @@ public:
|
||||
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);
|
||||
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()) {
|
||||
@ -235,22 +212,23 @@ private:
|
||||
|
||||
class SkPositionOnPathAffector : public SkParticleAffector {
|
||||
public:
|
||||
SkPositionOnPathAffector(const char* path = "", bool setHeading = true, bool random = true)
|
||||
SkPositionOnPathAffector(const char* path = "", bool setHeading = true,
|
||||
SkParticleValue input = SkParticleValue())
|
||||
: fPath(path)
|
||||
, fSetHeading(setHeading)
|
||||
, fRandom(random) {
|
||||
, fInput(input)
|
||||
, fSetHeading(setHeading) {
|
||||
this->rebuild();
|
||||
}
|
||||
|
||||
REFLECTED(SkPositionOnPathAffector, SkParticleAffector)
|
||||
|
||||
void onApply(SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
|
||||
void onApply(const 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;
|
||||
float t = fInput.eval(params, ps[i]);
|
||||
SkScalar len = fTotalLength * t;
|
||||
int idx = 0;
|
||||
while (idx < fContours.count() && len > fContours[idx]->length()) {
|
||||
@ -271,8 +249,8 @@ public:
|
||||
SkString oldPath = fPath;
|
||||
|
||||
SkParticleAffector::visitFields(v);
|
||||
v->visit("Input", fInput);
|
||||
v->visit("SetHeading", fSetHeading);
|
||||
v->visit("Random", fRandom);
|
||||
v->visit("Path", fPath);
|
||||
|
||||
if (fPath != oldPath) {
|
||||
@ -281,9 +259,9 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
SkString fPath;
|
||||
bool fSetHeading;
|
||||
bool fRandom;
|
||||
SkString fPath;
|
||||
SkParticleValue fInput;
|
||||
bool fSetHeading;
|
||||
|
||||
void rebuild() {
|
||||
SkPath path;
|
||||
@ -309,24 +287,24 @@ private:
|
||||
class SkPositionOnTextAffector : public SkParticleAffector {
|
||||
public:
|
||||
SkPositionOnTextAffector(const char* text = "", SkScalar fontSize = 96, bool setHeading = true,
|
||||
bool random = true)
|
||||
SkParticleValue input = SkParticleValue())
|
||||
: fText(text)
|
||||
, fFontSize(fontSize)
|
||||
, fSetHeading(setHeading)
|
||||
, fRandom(random) {
|
||||
, fInput(input)
|
||||
, fSetHeading(setHeading) {
|
||||
this->rebuild();
|
||||
}
|
||||
|
||||
REFLECTED(SkPositionOnTextAffector, SkParticleAffector)
|
||||
|
||||
void onApply(SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
|
||||
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 = fRandom ? ps[i].fRandom.nextF() : ps[i].fAge;
|
||||
float t = fInput.eval(params, ps[i]);
|
||||
SkScalar len = fTotalLength * t;
|
||||
int idx = 0;
|
||||
while (idx < fContours.count() && len > fContours[idx]->length()) {
|
||||
@ -348,8 +326,8 @@ public:
|
||||
SkScalar oldSize = fFontSize;
|
||||
|
||||
SkParticleAffector::visitFields(v);
|
||||
v->visit("Input", fInput);
|
||||
v->visit("SetHeading", fSetHeading);
|
||||
v->visit("Random", fRandom);
|
||||
v->visit("Text", fText);
|
||||
v->visit("FontSize", fFontSize);
|
||||
|
||||
@ -359,10 +337,10 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
SkString fText;
|
||||
SkScalar fFontSize;
|
||||
bool fSetHeading;
|
||||
bool fRandom;
|
||||
SkString fText;
|
||||
SkScalar fFontSize;
|
||||
SkParticleValue fInput;
|
||||
bool fSetHeading;
|
||||
|
||||
void rebuild() {
|
||||
fTotalLength = 0;
|
||||
@ -394,9 +372,9 @@ public:
|
||||
|
||||
REFLECTED(SkSizeAffector, SkParticleAffector)
|
||||
|
||||
void onApply(SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
|
||||
void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
ps[i].fPose.fScale = fCurve.eval(ps[i].fAge, ps[i].fRandom);
|
||||
ps[i].fPose.fScale = fCurve.eval(params, ps[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -415,9 +393,9 @@ public:
|
||||
|
||||
REFLECTED(SkFrameAffector, SkParticleAffector)
|
||||
|
||||
void onApply(SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
|
||||
void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
ps[i].fFrame = fCurve.eval(ps[i].fAge, ps[i].fRandom);
|
||||
ps[i].fFrame = fCurve.eval(params, ps[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -437,9 +415,9 @@ public:
|
||||
|
||||
REFLECTED(SkColorAffector, SkParticleAffector)
|
||||
|
||||
void onApply(SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
|
||||
void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) override {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
ps[i].fColor = fCurve.eval(ps[i].fAge, ps[i].fRandom);
|
||||
ps[i].fColor = fCurve.eval(params, ps[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,8 +64,15 @@ void SkParticleEffect::update(const SkAnimTimer& timer) {
|
||||
this->setCapacity(fParams->fMaxCount);
|
||||
}
|
||||
|
||||
float effectAge = static_cast<float>((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;
|
||||
|
||||
// Advance age for existing particles, and remove any that have reached their end of life
|
||||
for (int i = 0; i < fCount; ++i) {
|
||||
@ -85,18 +92,12 @@ void SkParticleEffect::update(const SkAnimTimer& timer) {
|
||||
fSpawnRemainder = desired - numToSpawn;
|
||||
numToSpawn = SkTPin(numToSpawn, 0, fParams->fMaxCount - fCount);
|
||||
if (numToSpawn) {
|
||||
// This isn't "particle" t, it's effect t.
|
||||
float t = static_cast<float>((now - fSpawnTime) / fParams->fEffectDuration);
|
||||
t = fLooping ? fmodf(t, 1.0f) : SkTPin(t, 0.0f, 1.0f);
|
||||
const int spawnBase = fCount;
|
||||
|
||||
for (int i = 0; i < numToSpawn; ++i) {
|
||||
// Mutate our SkRandom so each particle definitely gets a different stable generator
|
||||
fRandom.nextU();
|
||||
|
||||
// Temporarily set our age to the *effect* age, so spawn affectors are driven by that
|
||||
fParticles[fCount].fAge = t;
|
||||
fParticles[fCount].fInvLifetime =
|
||||
sk_ieee_float_divide(1.0f, fParams->fLifetime.eval(t, fRandom));
|
||||
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;
|
||||
@ -104,19 +105,21 @@ void SkParticleEffect::update(const SkAnimTimer& timer) {
|
||||
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 = fStableRandoms[fCount] = fRandom;
|
||||
fCount++;
|
||||
}
|
||||
|
||||
// Apply spawn affectors, then reset our age to 0 (the *particle* age)
|
||||
// Apply spawn affectors
|
||||
for (auto affector : fParams->fSpawnAffectors) {
|
||||
if (affector) {
|
||||
affector->apply(updateParams, fParticles + (fCount - numToSpawn), numToSpawn);
|
||||
affector->apply(updateParams, fParticles + spawnBase, numToSpawn);
|
||||
}
|
||||
}
|
||||
for (int i = fCount - numToSpawn; i < fCount; ++i) {
|
||||
fParticles[i].fAge = 0.0f;
|
||||
|
||||
// Now compute particle lifetimes (so the curve can refer to spawn-computed source values)
|
||||
for (int i = spawnBase; i < fCount; ++i) {
|
||||
fParticles[i].fInvLifetime =
|
||||
sk_ieee_float_divide(1.0f, fParams->fLifetime.eval(updateParams, fParticles[i]));
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,6 +128,9 @@ void SkParticleEffect::update(const SkAnimTimer& timer) {
|
||||
fParticles[i].fRandom = 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) {
|
||||
@ -163,3 +169,116 @@ void SkParticleEffect::setCapacity(int 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<SkParticleFrame>(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;
|
||||
}
|
||||
|
@ -7,6 +7,8 @@
|
||||
|
||||
#include "SkReflected.h"
|
||||
|
||||
#include "SkCurve.h"
|
||||
|
||||
SkSTArray<16, const SkReflected::Type*, true> SkReflected::gTypes;
|
||||
|
||||
void SkReflected::VisitTypes(std::function<void(const Type*)> visitor, const Type* baseType) {
|
||||
@ -16,3 +18,9 @@ void SkReflected::VisitTypes(std::function<void(const Type*)> visitor, const Typ
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkFieldVisitor::visit(const char* name, SkCurve& c) {
|
||||
this->enterObject(name);
|
||||
c.visitFields(this);
|
||||
this->exitObject();
|
||||
}
|
||||
|
@ -3,6 +3,12 @@
|
||||
"Duration": 1,
|
||||
"Rate": 1000,
|
||||
"Life": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -25,6 +31,12 @@
|
||||
"Force": false,
|
||||
"Frame": "World",
|
||||
"Angle": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -37,6 +49,12 @@
|
||||
]
|
||||
},
|
||||
"Strength": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -52,6 +70,12 @@
|
||||
{
|
||||
"Type": "SkPositionOnTextAffector",
|
||||
"Enabled": true,
|
||||
"Input": {
|
||||
"Source": "Random",
|
||||
"TileMode": "Clamp",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"SetHeading": true,
|
||||
"Text": "SKIA",
|
||||
"FontSize": 96
|
||||
@ -62,6 +86,12 @@
|
||||
"Type": "SkColorAffector",
|
||||
"Enabled": true,
|
||||
"Curve": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
|
@ -3,6 +3,12 @@
|
||||
"Duration": 1,
|
||||
"Rate": 8,
|
||||
"Life": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -26,6 +32,12 @@
|
||||
"Enabled": true,
|
||||
"SetHeading": true,
|
||||
"X": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -37,6 +49,12 @@
|
||||
]
|
||||
},
|
||||
"Y": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -48,6 +66,12 @@
|
||||
]
|
||||
},
|
||||
"Radius": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -72,6 +96,12 @@
|
||||
"Type": "SkFrameAffector",
|
||||
"Enabled": true,
|
||||
"Curve": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
|
@ -3,6 +3,12 @@
|
||||
"Duration": 1,
|
||||
"Rate": 0.5,
|
||||
"Life": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -26,6 +32,12 @@
|
||||
"Force": false,
|
||||
"Frame": "World",
|
||||
"Angle": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -38,6 +50,12 @@
|
||||
]
|
||||
},
|
||||
"Strength": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -58,6 +76,12 @@
|
||||
"Force": true,
|
||||
"Frame": "World",
|
||||
"Angle": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -69,6 +93,12 @@
|
||||
]
|
||||
},
|
||||
"Strength": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -85,6 +115,12 @@
|
||||
"Enabled": true,
|
||||
"Frame": "Velocity",
|
||||
"Angle": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
|
@ -3,6 +3,12 @@
|
||||
"Duration": 1,
|
||||
"Rate": 30,
|
||||
"Life": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -24,6 +30,12 @@
|
||||
"Force": false,
|
||||
"Frame": "World",
|
||||
"Angle": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -36,6 +48,12 @@
|
||||
]
|
||||
},
|
||||
"Strength": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -51,6 +69,12 @@
|
||||
{
|
||||
"Type": "SkPositionOnPathAffector",
|
||||
"Enabled": true,
|
||||
"Input": {
|
||||
"Source": "Random",
|
||||
"TileMode": "Clamp",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"SetHeading": false,
|
||||
"Path": "h500"
|
||||
}
|
||||
@ -60,6 +84,12 @@
|
||||
"Type": "SkSizeAffector",
|
||||
"Enabled": true,
|
||||
"Curve": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
|
@ -3,6 +3,12 @@
|
||||
"Duration": 4,
|
||||
"Rate": 120,
|
||||
"Life": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -25,6 +31,12 @@
|
||||
"Force": false,
|
||||
"Frame": "World",
|
||||
"Angle": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -37,6 +49,12 @@
|
||||
]
|
||||
},
|
||||
"Strength": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -55,6 +73,12 @@
|
||||
"Type": "SkSizeAffector",
|
||||
"Enabled": true,
|
||||
"Curve": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -71,6 +95,12 @@
|
||||
"Type": "SkColorAffector",
|
||||
"Enabled": true,
|
||||
"Curve": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
|
@ -3,6 +3,12 @@
|
||||
"Duration": 1,
|
||||
"Rate": 400,
|
||||
"Life": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -22,6 +28,12 @@
|
||||
{
|
||||
"Type": "SkPositionOnPathAffector",
|
||||
"Enabled": true,
|
||||
"Input": {
|
||||
"Source": "Random",
|
||||
"TileMode": "Clamp",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"SetHeading": true,
|
||||
"Path": "h50"
|
||||
},
|
||||
@ -31,6 +43,12 @@
|
||||
"Force": false,
|
||||
"Frame": "World",
|
||||
"Angle": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -43,6 +61,12 @@
|
||||
]
|
||||
},
|
||||
"Strength": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -63,6 +87,12 @@
|
||||
"Force": true,
|
||||
"Frame": "World",
|
||||
"Angle": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -74,6 +104,12 @@
|
||||
]
|
||||
},
|
||||
"Strength": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -92,6 +128,12 @@
|
||||
"Type": "SkSizeAffector",
|
||||
"Enabled": true,
|
||||
"Curve": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -108,6 +150,12 @@
|
||||
"Type": "SkColorAffector",
|
||||
"Enabled": true,
|
||||
"Curve": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
|
@ -3,6 +3,12 @@
|
||||
"Duration": 1,
|
||||
"Rate": 90,
|
||||
"Life": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -23,6 +29,12 @@
|
||||
"Enabled": true,
|
||||
"SetHeading": true,
|
||||
"X": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -34,6 +46,12 @@
|
||||
]
|
||||
},
|
||||
"Y": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -45,6 +63,12 @@
|
||||
]
|
||||
},
|
||||
"Radius": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
@ -69,6 +93,12 @@
|
||||
"Type": "SkSizeAffector",
|
||||
"Enabled": true,
|
||||
"Curve": {
|
||||
"Input": {
|
||||
"Source": "Age",
|
||||
"TileMode": "Repeat",
|
||||
"Left": 0,
|
||||
"Right": 1
|
||||
},
|
||||
"XValues": [],
|
||||
"Segments": [
|
||||
{
|
||||
|
@ -215,7 +215,7 @@ void ParticlesSlide::draw(SkCanvas* canvas) {
|
||||
if (fTimer && ImGui::Button("Play")) {
|
||||
fEffect->start(*fTimer, looped);
|
||||
}
|
||||
static char filename[64] = "resources/particles/default.json";
|
||||
static char filename[64] = "particles/default.json";
|
||||
ImGui::InputText("Filename", filename, sizeof(filename));
|
||||
if (ImGui::Button("Load")) {
|
||||
if (auto newParams = LoadEffectParams(filename)) {
|
||||
@ -225,7 +225,8 @@ void ParticlesSlide::draw(SkCanvas* canvas) {
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Save")) {
|
||||
SkFILEWStream fileStream(filename);
|
||||
SkString fullPath = GetResourcePath(filename);
|
||||
SkFILEWStream fileStream(fullPath.c_str());
|
||||
if (fileStream.isValid()) {
|
||||
SkJSONWriter writer(&fileStream, SkJSONWriter::Mode::kPretty);
|
||||
SkToJsonVisitor toJson(writer);
|
||||
|
Loading…
Reference in New Issue
Block a user