Major rewrite of the particle system based on the SkSL interpreter
This removes all of the fixed-function particle affector classes. Instead, each particle effect just has two SkSL snippets, one for spawn logic, and one for update logic. Each one gets an inout copy of the particle struct. Ultimately, this makes the effects much simpler and smaller, while also being far more flexible (you can do whatever you want with any values you want). Finally, because the interpreter is vectorized and a particular effect's scripts are usually tuned to the specific behaviors desired, it's faster on basically every effect I compared. I re-created all of the old effects in the new system. Many just use pure SkSL (no curves or anything). Some of the old curve and path/text stuff was very handy, though - so those are now exposed as external values in the interpreter. Basically, an effect can have any number of named "bindings" that are a callable thing. This can be a path, text (shortcut for making fancy paths), curve, or color curve. The path ones return a float4 with position and normal, the curves return one or four floats. ... and this transposes all of the particle data storage into SoA form, so that it can use the much faster interpreter entry point. Change-Id: Iebe711c45994c4201041b12d171af976bc5e758e Reviewed-on: https://skia-review.googlesource.com/c/skia/+/222057 Commit-Queue: Brian Osman <brianosman@google.com> Reviewed-by: Mike Reed <reed@google.com>
This commit is contained in:
parent
ca2640423d
commit
fe49163cd1
@ -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;
|
||||
}
|
||||
|
@ -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<float, true> fXValues;
|
||||
SkTArray<SkCurveSegment, true> 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<float, true> fXValues;
|
||||
SkTArray<SkColorCurveSegment, true> fSegments;
|
||||
};
|
||||
|
@ -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<SkParticleAffector> MakeLinearVelocity(const SkCurve& angle,
|
||||
const SkCurve& strength,
|
||||
bool force,
|
||||
SkParticleFrame frame);
|
||||
static sk_sp<SkParticleAffector> MakeAngularVelocity(const SkCurve& strength,
|
||||
bool force);
|
||||
|
||||
// Set the orientation of a particle, relative to the world, local, or velocity frame.
|
||||
static sk_sp<SkParticleAffector> MakeOrientation(const SkCurve& angle,
|
||||
SkParticleFrame frame);
|
||||
|
||||
static sk_sp<SkParticleAffector> MakePointForce(SkPoint point, SkScalar constant,
|
||||
SkScalar invSquare);
|
||||
|
||||
static sk_sp<SkParticleAffector> MakeSize(const SkCurve& curve);
|
||||
static sk_sp<SkParticleAffector> MakeFrame(const SkCurve& curve);
|
||||
static sk_sp<SkParticleAffector> MakeColor(const SkColorCurve& curve);
|
||||
|
||||
private:
|
||||
virtual void onApply(const SkParticleUpdateParams& params, SkParticleState ps[], int count) = 0;
|
||||
|
||||
bool fEnabled = true;
|
||||
};
|
||||
|
||||
#endif // SkParticleAffector_DEFINED
|
@ -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,
|
||||
|
||||
kNumChannels,
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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<float> fData[kNumChannels];
|
||||
SkAutoTMalloc<SkRandom> fRandom;
|
||||
};
|
||||
|
||||
#endif // SkParticleData_DEFINED
|
||||
|
@ -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();
|
||||
|
@ -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 <memory>
|
||||
|
||||
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<SkParticleExternalValue> 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<SkParticleDrawable> fDrawable;
|
||||
|
||||
// Rules that configure particles at spawn time
|
||||
SkTArray<sk_sp<SkParticleAffector>> fSpawnAffectors;
|
||||
// Code to configure particles at spawn time
|
||||
SkString fSpawnCode;
|
||||
|
||||
// Rules that update existing particles over their lifetime
|
||||
SkTArray<sk_sp<SkParticleAffector>> fUpdateAffectors;
|
||||
// Code to update existing particles over their lifetime
|
||||
SkString fUpdateCode;
|
||||
|
||||
SkTArray<sk_sp<SkParticleBinding>> fBindings;
|
||||
|
||||
void visitFields(SkFieldVisitor* v);
|
||||
|
||||
private:
|
||||
friend class SkParticleEffect;
|
||||
|
||||
// Cached
|
||||
struct Program {
|
||||
std::unique_ptr<SkSL::ByteCode> fByteCode;
|
||||
SkTArray<std::unique_ptr<SkParticleExternalValue>> fExternalValues;
|
||||
};
|
||||
Program fSpawnProgram;
|
||||
Program fUpdateProgram;
|
||||
|
||||
void rebuild();
|
||||
};
|
||||
|
||||
class SkParticleEffect : public SkRefCnt {
|
||||
@ -64,7 +102,7 @@ private:
|
||||
double fLastTime;
|
||||
float fSpawnRemainder;
|
||||
|
||||
SkAutoTMalloc<SkParticleState> fParticles;
|
||||
SkParticles fParticles;
|
||||
SkAutoTMalloc<SkRandom> fStableRandoms;
|
||||
|
||||
// Cached
|
||||
|
@ -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",
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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<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(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<SkParticleFrame>(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<sk_sp<SkContourMeasure>> 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<sk_sp<SkContourMeasure>> 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<SkSL::ByteCode> fByteCode;
|
||||
std::unique_ptr<SkRandomExternalValue> fRandomValue;
|
||||
SkSL::ByteCodeFunction* fMain;
|
||||
|
||||
void rebuild() {
|
||||
SkSL::Compiler compiler;
|
||||
SkSL::Program::Settings settings;
|
||||
auto rand = skstd::make_unique<SkRandomExternalValue>("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> SkParticleAffector::MakeLinearVelocity(const SkCurve& angle,
|
||||
const SkCurve& strength,
|
||||
bool force,
|
||||
SkParticleFrame frame) {
|
||||
return sk_sp<SkParticleAffector>(new SkLinearVelocityAffector(angle, strength, force, frame));
|
||||
}
|
||||
|
||||
sk_sp<SkParticleAffector> SkParticleAffector::MakeAngularVelocity(const SkCurve& strength,
|
||||
bool force) {
|
||||
return sk_sp<SkParticleAffector>(new SkAngularVelocityAffector(strength, force));
|
||||
}
|
||||
|
||||
sk_sp<SkParticleAffector> SkParticleAffector::MakePointForce(SkPoint point, SkScalar constant,
|
||||
SkScalar invSquare) {
|
||||
return sk_sp<SkParticleAffector>(new SkPointForceAffector(point, constant, invSquare));
|
||||
}
|
||||
|
||||
sk_sp<SkParticleAffector> SkParticleAffector::MakeOrientation(const SkCurve& angle,
|
||||
SkParticleFrame frame) {
|
||||
return sk_sp<SkParticleAffector>(new SkOrientationAffector(angle, frame));
|
||||
}
|
||||
|
||||
sk_sp<SkParticleAffector> SkParticleAffector::MakeSize(const SkCurve& curve) {
|
||||
return sk_sp<SkParticleAffector>(new SkSizeAffector(curve));
|
||||
}
|
||||
|
||||
sk_sp<SkParticleAffector> SkParticleAffector::MakeFrame(const SkCurve& curve) {
|
||||
return sk_sp<SkParticleAffector>(new SkFrameAffector(curve));
|
||||
}
|
||||
|
||||
sk_sp<SkParticleAffector> SkParticleAffector::MakeColor(const SkColorCurve& curve) {
|
||||
return sk_sp<SkParticleAffector>(new SkColorAffector(curve));
|
||||
}
|
@ -27,14 +27,40 @@ static sk_sp<SkImage> 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<int>(particles[i].fFrame * frameCount + 0.5f);
|
||||
int frame = static_cast<int>(spriteFrames[i] * frameCount + 0.5f);
|
||||
frame = SkTPin(frame, 0, frameCount - 1);
|
||||
int row = frame / fCols;
|
||||
int col = frame % fCols;
|
||||
|
@ -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<SkParticleExternalValue> toValue(SkSL::Compiler& compiler) override {
|
||||
return std::unique_ptr<SkParticleExternalValue>(
|
||||
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<SkParticleExternalValue> toValue(SkSL::Compiler& compiler) override {
|
||||
return std::unique_ptr<SkParticleExternalValue>(
|
||||
new SkColorCurveExternalValue(fName.c_str(), compiler, fCurve));
|
||||
}
|
||||
|
||||
private:
|
||||
SkColorCurve fCurve;
|
||||
};
|
||||
|
||||
struct SkPathContours {
|
||||
SkScalar fTotalLength;
|
||||
SkTArray<sk_sp<SkContourMeasure>> 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<SkParticleExternalValue> toValue(SkSL::Compiler& compiler) override {
|
||||
return std::unique_ptr<SkParticleExternalValue>(
|
||||
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<SkParticleExternalValue> toValue(SkSL::Compiler& compiler) override {
|
||||
return std::unique_ptr<SkParticleExternalValue>(
|
||||
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<std::unique_ptr<SkParticleExternalValue>> externalValues;
|
||||
|
||||
auto rand = skstd::make_unique<SkRandomExternalValue>("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<SkParticleEffectParams> params, const SkRandom& random)
|
||||
@ -64,25 +431,40 @@ void SkParticleEffect::update(double now) {
|
||||
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;
|
||||
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<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;
|
||||
}
|
||||
|
@ -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 ]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -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": []
|
||||
}
|
@ -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": []
|
||||
}
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -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": []
|
||||
}
|
@ -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": [
|
||||
{
|
||||
|
@ -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": []
|
||||
}
|
@ -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 ]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -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": []
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user