Particle effect scripting update
This change adds another layer of complexity and control to the particle system. There are now two code chunks: the old code that's run per-particle, and new code that's run for the effect itself. This allows for effect lifetime to be set by the script (eg, randomly), as well as the emission rate. Rate can vary over time (see pulse.json), and particles can be emitted in bursts by setting the effect's burst field (see fireworks.json). Additionally, the effect has its own frame of reference and color, which becomes the default state for newly emitted particles. This allows synchronizing state across particles in various interesting ways (see color in fireworks.json). Change-Id: Iec2f7a3427ce1d6411ed7ef5b3023cbef2e8a134 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/240498 Reviewed-by: Brian Osman <brianosman@google.com> Reviewed-by: Michael Ludwig <michaelludwig@google.com> Commit-Queue: Brian Osman <brianosman@google.com>
This commit is contained in:
parent
debc2a3013
commit
d46cb9729b
@ -193,15 +193,20 @@
|
||||
|
||||
const snowfall = {
|
||||
"MaxCount": 4096,
|
||||
"Duration": 1,
|
||||
"Rate": 30,
|
||||
"Drawable": {
|
||||
"Type": "SkCircleDrawable",
|
||||
"Radius": 1
|
||||
},
|
||||
"Code": [
|
||||
"// float rand; Every read returns a random float [0 .. 1)",
|
||||
"EffectCode": [
|
||||
"void effectSpawn(inout Effect effect) {",
|
||||
" effect.rate = 30;",
|
||||
"}",
|
||||
"",
|
||||
"void effectUpdate(inout Effect effect) {",
|
||||
"}",
|
||||
""
|
||||
],
|
||||
"Code": [
|
||||
"void spawn(inout Particle p) {",
|
||||
" p.lifetime = 10;",
|
||||
" p.vel.y = 10 + rand * 20;",
|
||||
|
@ -8,6 +8,8 @@
|
||||
#ifndef SkParticleEffect_DEFINED
|
||||
#define SkParticleEffect_DEFINED
|
||||
|
||||
#include "include/core/SkColor.h"
|
||||
#include "include/core/SkPoint.h"
|
||||
#include "include/core/SkRefCnt.h"
|
||||
#include "include/core/SkString.h"
|
||||
#include "include/private/SkTArray.h"
|
||||
@ -31,21 +33,26 @@ class SkParticleEffectParams : public SkRefCnt {
|
||||
public:
|
||||
SkParticleEffectParams();
|
||||
|
||||
int fMaxCount; // Maximum number of particles per instance of the effect
|
||||
float fEffectDuration; // How long does the effect last after being played, in seconds?
|
||||
float fRate; // How many particles are emitted per second?
|
||||
// Maximum number of particles per instance of the effect
|
||||
int fMaxCount;
|
||||
|
||||
// What is drawn for each particle? (Image, shape, sprite sheet, etc.)
|
||||
// See SkParticleDrawable::Make*
|
||||
sk_sp<SkParticleDrawable> fDrawable;
|
||||
|
||||
// Particle behavior is driven by two SkSL functions defined in the fCode string.
|
||||
// Both functions get a mutable Particle struct:
|
||||
// Particle behavior is driven by two chunks of SkSL code. Effect functions are defined in
|
||||
// fEffectCode, and get a mutable Effect struct:
|
||||
//
|
||||
// struct Particle {
|
||||
// float age;
|
||||
// float lifetime;
|
||||
// float2 pos = { 0, 0 }; // Local position, relative to the effect.
|
||||
// struct Effect {
|
||||
// float age;
|
||||
// float lifetime;
|
||||
// int loop;
|
||||
// float rate;
|
||||
// int burst; // Set to trigger a burst of particles.
|
||||
//
|
||||
// // Everything below this line controls the state of the effect, which is also the
|
||||
// // default values for new particles.
|
||||
// float2 pos = { 0, 0 }; // Local position
|
||||
// float2 dir = { 0, -1 }; // Heading. Should be a normalized vector.
|
||||
// float scale = 1; // Size, normalized relative to the drawable's native size
|
||||
// float2 vel = { 0, 0 }; // Linear velocity, in (units / second)
|
||||
@ -54,24 +61,48 @@ public:
|
||||
// float frame = 0; // Normalized sprite index for multi-frame drawables
|
||||
// };
|
||||
//
|
||||
// In addition, both functions have access to a global variable named 'rand'. Every read of
|
||||
// 'rand' returns a random floating point value in [0, 1). The random generator is stored
|
||||
// per-particle, and the state is rewound after each update, so calls to 'rand' will return
|
||||
// consistent values from one update to the next.
|
||||
// Particle functions are defined in fParticleCode, and get a mutable Particle struct, as well
|
||||
// as a uniform copy of the current Effect, named 'effect'.
|
||||
//
|
||||
// Finally, there are two global uniform values available. The first is 'dt', a floating point
|
||||
// number of seconds that have elapsed since the last update. The second is 'effectAge', which
|
||||
// is the normalized age of the effect (not particle). For looping effects, this will wrap
|
||||
// back to zero when the effect's age exceeds its duration.
|
||||
// struct Particle {
|
||||
// float age;
|
||||
// float lifetime;
|
||||
// float2 pos;
|
||||
// float2 dir;
|
||||
// float scale;
|
||||
// float2 vel;
|
||||
// float spin;
|
||||
// float4 color;
|
||||
// float frame;
|
||||
// };
|
||||
//
|
||||
// All functions have access to a global variable named 'rand'. Every read of 'rand' returns a
|
||||
// random floating point value in [0, 1). For particle functions, the state is rewound after
|
||||
// each update, so calls to 'rand' will return consistent values from one update to the next.
|
||||
//
|
||||
// Finally, there is one global uniform values available, 'dt'. This is a floating point
|
||||
// number of seconds that have elapsed since the last update.
|
||||
//
|
||||
// Effect code should define two functions:
|
||||
//
|
||||
// 'void effectSpawn(inout Effect e)' is called when an instance of the effect is first
|
||||
// created, and again at every loop point (if the effect is played with the looping flag).
|
||||
//
|
||||
// 'void effectUpdate(inout Effect e)' is called once per update to adjust properties of the
|
||||
// effect (ie emitter).
|
||||
//
|
||||
// Particle code should also define two functions:
|
||||
//
|
||||
// 'void spawn(inout Particle p)' is called once for each particle when it is first created,
|
||||
// to set initial values. At a minimum, this should set 'lifetime' to the number of seconds
|
||||
// that the particle will exist. Other parameters have defaults shown above.
|
||||
// that the particle will exist. Other parameters will will get default values from the effect.
|
||||
//
|
||||
// 'void update(inout Particle p)' is called for each particle on every call to the running
|
||||
// SkParticleEffect's update() method. It can animate any of the particle's values. Note that
|
||||
// the 'lifetime' field has a different meaning in 'update', and should not be used or changed.
|
||||
SkString fCode;
|
||||
|
||||
SkString fEffectCode;
|
||||
SkString fParticleCode;
|
||||
|
||||
// External objects accessible by the effect's SkSL code. Each binding is a name and particular
|
||||
// kind of object. See SkParticleBinding::Make* for details.
|
||||
@ -83,8 +114,13 @@ private:
|
||||
friend class SkParticleEffect;
|
||||
|
||||
// Cached
|
||||
std::unique_ptr<SkSL::ByteCode> fByteCode;
|
||||
SkTArray<std::unique_ptr<SkParticleExternalValue>> fExternalValues;
|
||||
struct Program {
|
||||
std::unique_ptr<SkSL::ByteCode> fByteCode;
|
||||
SkTArray<std::unique_ptr<SkParticleExternalValue>> fExternalValues;
|
||||
};
|
||||
|
||||
Program fEffectProgram;
|
||||
Program fParticleProgram;
|
||||
|
||||
void rebuild();
|
||||
};
|
||||
@ -97,7 +133,7 @@ public:
|
||||
void update(double now);
|
||||
void draw(SkCanvas* canvas);
|
||||
|
||||
bool isAlive() const { return fEffectAge >= 0 && fEffectAge <= 1; }
|
||||
bool isAlive() const { return fState.fAge >= 0 && fState.fAge <= 1; }
|
||||
int getCount() const { return fCount; }
|
||||
|
||||
static void RegisterParticleTypes();
|
||||
@ -110,12 +146,34 @@ private:
|
||||
SkRandom fRandom;
|
||||
|
||||
bool fLooping;
|
||||
float fEffectAge;
|
||||
|
||||
int fCount;
|
||||
double fLastTime;
|
||||
float fSpawnRemainder;
|
||||
|
||||
// Effect-associated values exposed to script. They are some mix of uniform and inout,
|
||||
// depending on whether we're executing per-feffect or per-particle scripts.
|
||||
struct EffectState {
|
||||
float fDeltaTime;
|
||||
|
||||
// Above this line is always uniform. Below is uniform for particles, inout for effect.
|
||||
|
||||
float fAge;
|
||||
float fLifetime;
|
||||
int fLoopCount;
|
||||
float fRate;
|
||||
int fBurst;
|
||||
|
||||
// Properties that determine default values for new particles
|
||||
SkPoint fPosition;
|
||||
SkVector fHeading;
|
||||
float fScale;
|
||||
SkVector fVelocity;
|
||||
float fSpin;
|
||||
SkColor4f fColor;
|
||||
float fFrame;
|
||||
};
|
||||
EffectState fState;
|
||||
|
||||
SkParticles fParticles;
|
||||
SkAutoTMalloc<SkRandom> fStableRandoms;
|
||||
|
||||
|
@ -28,11 +28,29 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
static const char* kCodeHeader =
|
||||
static const char* kCommonHeader =
|
||||
R"(
|
||||
layout(ctype=float) in uniform float dt;
|
||||
layout(ctype=float) in uniform float effectAge;
|
||||
struct Effect {
|
||||
float age;
|
||||
float lifetime;
|
||||
int loop;
|
||||
float rate;
|
||||
int burst;
|
||||
|
||||
float2 pos;
|
||||
float2 dir;
|
||||
float scale;
|
||||
float2 vel;
|
||||
float spin;
|
||||
float4 color;
|
||||
float frame;
|
||||
};
|
||||
|
||||
in uniform float dt;
|
||||
)";
|
||||
|
||||
static const char* kParticleHeader =
|
||||
R"(
|
||||
struct Particle {
|
||||
float age;
|
||||
float lifetime;
|
||||
@ -44,12 +62,20 @@ struct Particle {
|
||||
float4 color;
|
||||
float frame;
|
||||
};
|
||||
|
||||
in uniform Effect effect;
|
||||
)";
|
||||
|
||||
static const char* kDefaultCode =
|
||||
R"(// float rand; Every read returns a random float [0 .. 1)
|
||||
static const char* kDefaultEffectCode =
|
||||
R"(void effectSpawn(inout Effect effect) {
|
||||
}
|
||||
|
||||
void spawn(inout Particle p) {
|
||||
void effectUpdate(inout Effect effect) {
|
||||
}
|
||||
)";
|
||||
|
||||
static const char* kDefaultParticleCode =
|
||||
R"(void spawn(inout Particle p) {
|
||||
}
|
||||
|
||||
void update(inout Particle p) {
|
||||
@ -58,86 +84,114 @@ void update(inout Particle p) {
|
||||
|
||||
SkParticleEffectParams::SkParticleEffectParams()
|
||||
: fMaxCount(128)
|
||||
, fEffectDuration(1.0f)
|
||||
, fRate(8.0f)
|
||||
, fDrawable(nullptr)
|
||||
, fCode(kDefaultCode) {
|
||||
, fEffectCode(kDefaultEffectCode)
|
||||
, fParticleCode(kDefaultParticleCode) {
|
||||
this->rebuild();
|
||||
}
|
||||
|
||||
void SkParticleEffectParams::visitFields(SkFieldVisitor* v) {
|
||||
SkString oldCode = fCode;
|
||||
SkString oldEffectCode = fEffectCode;
|
||||
SkString oldParticleCode = fParticleCode;
|
||||
|
||||
v->visit("MaxCount", fMaxCount);
|
||||
v->visit("Duration", fEffectDuration);
|
||||
v->visit("Rate", fRate);
|
||||
|
||||
v->visit("Drawable", fDrawable);
|
||||
|
||||
v->visit("Code", fCode);
|
||||
v->visit("EffectCode", fEffectCode);
|
||||
v->visit("Code", fParticleCode);
|
||||
|
||||
v->visit("Bindings", fBindings);
|
||||
|
||||
// TODO: Or, if any change to binding metadata?
|
||||
if (fCode != oldCode) {
|
||||
if (fParticleCode != oldParticleCode || fEffectCode != oldEffectCode) {
|
||||
this->rebuild();
|
||||
}
|
||||
}
|
||||
|
||||
void SkParticleEffectParams::rebuild() {
|
||||
SkSL::Compiler compiler;
|
||||
SkSL::Program::Settings settings;
|
||||
auto buildProgram = [this](const SkSL::String& code, Program* p) {
|
||||
SkSL::Compiler compiler;
|
||||
SkSL::Program::Settings settings;
|
||||
|
||||
SkTArray<std::unique_ptr<SkParticleExternalValue>> externalValues;
|
||||
SkTArray<std::unique_ptr<SkParticleExternalValue>> externalValues;
|
||||
|
||||
auto rand = skstd::make_unique<SkRandomExternalValue>("rand", compiler);
|
||||
compiler.registerExternalValue(rand.get());
|
||||
externalValues.push_back(std::move(rand));
|
||||
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));
|
||||
for (const auto& binding : fBindings) {
|
||||
if (binding) {
|
||||
auto value = binding->toValue(compiler);
|
||||
compiler.registerExternalValue(value.get());
|
||||
externalValues.push_back(std::move(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SkSL::String code(kCodeHeader);
|
||||
code.append(fCode.c_str());
|
||||
auto program = compiler.convertProgram(SkSL::Program::kGeneric_Kind, code, settings);
|
||||
if (!program) {
|
||||
SkDebugf("%s\n", compiler.errorText().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
auto program = compiler.convertProgram(SkSL::Program::kGeneric_Kind, code, 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;
|
||||
}
|
||||
|
||||
auto byteCode = compiler.toByteCode(*program);
|
||||
if (!byteCode) {
|
||||
SkDebugf("%s\n", compiler.errorText().c_str());
|
||||
return;
|
||||
}
|
||||
p->fByteCode = std::move(byteCode);
|
||||
p->fExternalValues.swap(externalValues);
|
||||
};
|
||||
|
||||
fByteCode = std::move(byteCode);
|
||||
fExternalValues.swap(externalValues);
|
||||
SkSL::String effectCode(kCommonHeader);
|
||||
effectCode.append(fEffectCode.c_str());
|
||||
|
||||
SkSL::String particleCode(kCommonHeader);
|
||||
particleCode.append(kParticleHeader);
|
||||
particleCode.append(fParticleCode.c_str());
|
||||
|
||||
buildProgram(effectCode, &fEffectProgram);
|
||||
buildProgram(particleCode, &fParticleProgram);
|
||||
}
|
||||
|
||||
SkParticleEffect::SkParticleEffect(sk_sp<SkParticleEffectParams> params, const SkRandom& random)
|
||||
: fParams(std::move(params))
|
||||
, fRandom(random)
|
||||
, fLooping(false)
|
||||
, fEffectAge(-1.0)
|
||||
, fCount(0)
|
||||
, fLastTime(-1.0)
|
||||
, fSpawnRemainder(0.0f) {
|
||||
fState.fAge = -1.0f;
|
||||
this->setCapacity(fParams->fMaxCount);
|
||||
}
|
||||
|
||||
void SkParticleEffect::start(double now, bool looping) {
|
||||
fCount = 0;
|
||||
fLastTime = now;
|
||||
fEffectAge = 0.0f;
|
||||
fSpawnRemainder = 0.0f;
|
||||
fLooping = looping;
|
||||
|
||||
fState.fDeltaTime = 0.0f;
|
||||
fState.fAge = 0.0f;
|
||||
|
||||
// A default lifetime makes sense - many effects are simple loops that don't really care.
|
||||
// Every effect should define its own rate of emission, or only use bursts, so leave that as
|
||||
// zero initially.
|
||||
fState.fLifetime = 1.0f;
|
||||
fState.fLoopCount = 0;
|
||||
fState.fRate = 0.0f;
|
||||
fState.fBurst = 0;
|
||||
|
||||
fState.fPosition = { 0.0f, 0.0f };
|
||||
fState.fHeading = { 0.0f, -1.0f };
|
||||
fState.fScale = 1.0f;
|
||||
fState.fVelocity = { 0.0f, 0.0f };
|
||||
fState.fSpin = 0.0f;
|
||||
fState.fColor = { 1.0f, 1.0f, 1.0f, 1.0f };
|
||||
fState.fFrame = 0.0f;
|
||||
|
||||
// Defer running effectSpawn until the first update (to reuse the code when looping)
|
||||
}
|
||||
|
||||
void SkParticleEffect::update(double now) {
|
||||
@ -145,8 +199,10 @@ void SkParticleEffect::update(double now) {
|
||||
return;
|
||||
}
|
||||
|
||||
float deltaTime = static_cast<float>(now - fLastTime);
|
||||
if (deltaTime <= 0.0f) {
|
||||
// TODO: Sub-frame spawning. Tricky with script driven position. Supply variable effect.age?
|
||||
// Could be done if effect.age were an external value that offset by particle lane, perhaps.
|
||||
fState.fDeltaTime = static_cast<float>(now - fLastTime);
|
||||
if (fState.fDeltaTime <= 0.0f) {
|
||||
return;
|
||||
}
|
||||
fLastTime = now;
|
||||
@ -156,22 +212,39 @@ void SkParticleEffect::update(double now) {
|
||||
this->setCapacity(fParams->fMaxCount);
|
||||
}
|
||||
|
||||
fEffectAge += deltaTime / fParams->fEffectDuration;
|
||||
if (fEffectAge > 1) {
|
||||
bool runEffectSpawn = (fState.fAge == 0.0f) && (fState.fLoopCount == 0);
|
||||
|
||||
fState.fAge += fState.fDeltaTime / fState.fLifetime;
|
||||
if (fState.fAge > 1) {
|
||||
if (fLooping) {
|
||||
fEffectAge = fmodf(fEffectAge, 1.0f);
|
||||
fState.fLoopCount += sk_float_floor2int(fState.fAge);
|
||||
fState.fAge = fmodf(fState.fAge, 1.0f);
|
||||
runEffectSpawn = true;
|
||||
} else {
|
||||
// Effect is dead if we've reached the end (and are not looping)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
float updateParams[2] = { deltaTime, fEffectAge };
|
||||
// Run optional effectSpawn to set initial spawn rate and other emitter properties.
|
||||
// This also runs on each loop point, for looped effects.
|
||||
if (runEffectSpawn) {
|
||||
if (const auto& byteCode = fParams->fEffectProgram.fByteCode) {
|
||||
if (auto fun = byteCode->getFunction("effectSpawn")) {
|
||||
for (const auto& value : fParams->fEffectProgram.fExternalValues) {
|
||||
value->setRandom(&fRandom);
|
||||
}
|
||||
SkAssertResult(byteCode->run(fun, &fState.fAge, nullptr, 1,
|
||||
&fState.fDeltaTime, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Advance age for existing particles, and remove any that have reached their end of life
|
||||
// TODO: Add an (optional) death script for particles?
|
||||
for (int i = 0; i < fCount; ++i) {
|
||||
fParticles.fData[SkParticles::kAge][i] +=
|
||||
fParticles.fData[SkParticles::kLifetime][i] * deltaTime;
|
||||
fParticles.fData[SkParticles::kLifetime][i] * fState.fDeltaTime;
|
||||
if (fParticles.fData[SkParticles::kAge][i] > 1.0f) {
|
||||
// NOTE: This is fast, but doesn't preserve drawing order. Could be a problem...
|
||||
for (int j = 0; j < SkParticles::kNumChannels; ++j) {
|
||||
@ -183,28 +256,55 @@ void SkParticleEffect::update(double now) {
|
||||
}
|
||||
}
|
||||
|
||||
auto runProgram = [](const SkParticleEffectParams* params, const char* entry,
|
||||
SkParticles& particles, float updateParams[], int start, int count) {
|
||||
if (const auto& byteCode = params->fByteCode) {
|
||||
// On first frame, we may have a pending burst from effectSpawn
|
||||
int burstCount = fState.fBurst;
|
||||
fState.fBurst = 0;
|
||||
|
||||
// Run optional effectUpdate to adjust spawn rate and other emitter properties
|
||||
if (const auto& byteCode = fParams->fEffectProgram.fByteCode) {
|
||||
if (auto fun = byteCode->getFunction("effectUpdate")) {
|
||||
for (const auto& value : fParams->fEffectProgram.fExternalValues) {
|
||||
value->setRandom(&fRandom);
|
||||
}
|
||||
SkAssertResult(byteCode->run(fun, &fState.fAge, nullptr, 1,
|
||||
&fState.fDeltaTime, 1));
|
||||
burstCount += fState.fBurst;
|
||||
}
|
||||
}
|
||||
|
||||
// Do integration of effect position and orientation
|
||||
{
|
||||
fState.fPosition += fState.fVelocity * fState.fDeltaTime;
|
||||
float s = sk_float_sin(fState.fSpin * fState.fDeltaTime),
|
||||
c = sk_float_cos(fState.fSpin * fState.fDeltaTime);
|
||||
// Using setNormalize to prevent scale drift
|
||||
fState.fHeading.setNormalize(fState.fHeading.fX * c - fState.fHeading.fY * s,
|
||||
fState.fHeading.fX * s + fState.fHeading.fY * c);
|
||||
}
|
||||
|
||||
auto runProgram = [this](const SkParticleEffectParams* params, const char* entry,
|
||||
SkParticles& particles, int start, int count) {
|
||||
if (const auto& byteCode = params->fParticleProgram.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 : params->fExternalValues) {
|
||||
for (const auto& value : params->fParticleProgram.fExternalValues) {
|
||||
value->setRandom(randomBase);
|
||||
}
|
||||
SkAssertResult(byteCode->runStriped(byteCode->getFunction(entry),
|
||||
args, SkParticles::kNumChannels, count,
|
||||
updateParams, 2, nullptr, 0));
|
||||
&fState.fDeltaTime, sizeof(EffectState) / 4,
|
||||
nullptr, 0));
|
||||
}
|
||||
};
|
||||
|
||||
// Spawn new particles
|
||||
float desired = fParams->fRate * deltaTime + fSpawnRemainder;
|
||||
float desired = fState.fRate * fState.fDeltaTime + fSpawnRemainder;
|
||||
int numToSpawn = sk_float_round2int(desired);
|
||||
fSpawnRemainder = desired - numToSpawn;
|
||||
numToSpawn = SkTPin(numToSpawn, 0, fParams->fMaxCount - fCount);
|
||||
numToSpawn = SkTPin(numToSpawn + burstCount, 0, fParams->fMaxCount - fCount);
|
||||
if (numToSpawn) {
|
||||
const int spawnBase = fCount;
|
||||
|
||||
@ -213,25 +313,25 @@ void SkParticleEffect::update(double now) {
|
||||
fRandom.nextU();
|
||||
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.fData[SkParticles::kPositionX ][fCount] = fState.fPosition.fX;
|
||||
fParticles.fData[SkParticles::kPositionY ][fCount] = fState.fPosition.fY;
|
||||
fParticles.fData[SkParticles::kHeadingX ][fCount] = fState.fHeading.fX;
|
||||
fParticles.fData[SkParticles::kHeadingY ][fCount] = fState.fHeading.fY;
|
||||
fParticles.fData[SkParticles::kScale ][fCount] = fState.fScale;
|
||||
fParticles.fData[SkParticles::kVelocityX ][fCount] = fState.fVelocity.fX;
|
||||
fParticles.fData[SkParticles::kVelocityY ][fCount] = fState.fVelocity.fY;
|
||||
fParticles.fData[SkParticles::kVelocityAngular][fCount] = fState.fSpin;
|
||||
fParticles.fData[SkParticles::kColorR ][fCount] = fState.fColor.fR;
|
||||
fParticles.fData[SkParticles::kColorG ][fCount] = fState.fColor.fG;
|
||||
fParticles.fData[SkParticles::kColorB ][fCount] = fState.fColor.fB;
|
||||
fParticles.fData[SkParticles::kColorA ][fCount] = fState.fColor.fA;
|
||||
fParticles.fData[SkParticles::kSpriteFrame ][fCount] = fState.fFrame;
|
||||
fParticles.fRandom[fCount] = fRandom;
|
||||
fCount++;
|
||||
}
|
||||
|
||||
// Run the spawn script
|
||||
runProgram(fParams.get(), "spawn", fParticles, updateParams, spawnBase, numToSpawn);
|
||||
runProgram(fParams.get(), "spawn", fParticles, spawnBase, numToSpawn);
|
||||
|
||||
// Now stash copies of the random generators and compute inverse particle lifetimes
|
||||
// (so that subsequent updates are faster)
|
||||
@ -248,17 +348,18 @@ void SkParticleEffect::update(double now) {
|
||||
}
|
||||
|
||||
// Run the update script
|
||||
runProgram(fParams.get(), "update", fParticles, updateParams, 0, fCount);
|
||||
runProgram(fParams.get(), "update", fParticles, 0, fCount);
|
||||
|
||||
// Do fixed-function update work (integration of position and orientation)
|
||||
for (int i = 0; i < fCount; ++i) {
|
||||
fParticles.fData[SkParticles::kPositionX][i] +=
|
||||
fParticles.fData[SkParticles::kVelocityX][i] * deltaTime;
|
||||
fParticles.fData[SkParticles::kVelocityX][i] * fState.fDeltaTime;
|
||||
fParticles.fData[SkParticles::kPositionY][i] +=
|
||||
fParticles.fData[SkParticles::kVelocityY][i] * deltaTime;
|
||||
fParticles.fData[SkParticles::kVelocityY][i] * fState.fDeltaTime;
|
||||
|
||||
SkScalar s = SkScalarSin(fParticles.fData[SkParticles::kVelocityAngular][i] * deltaTime),
|
||||
c = SkScalarCos(fParticles.fData[SkParticles::kVelocityAngular][i] * deltaTime);
|
||||
float spin = fParticles.fData[SkParticles::kVelocityAngular][i];
|
||||
float s = sk_float_sin(spin * fState.fDeltaTime),
|
||||
c = sk_float_cos(spin * fState.fDeltaTime);
|
||||
float oldHeadingX = fParticles.fData[SkParticles::kHeadingX][i],
|
||||
oldHeadingY = fParticles.fData[SkParticles::kHeadingY][i];
|
||||
fParticles.fData[SkParticles::kHeadingX][i] = oldHeadingX * c - oldHeadingY * s;
|
||||
|
@ -1,14 +1,19 @@
|
||||
{
|
||||
"MaxCount": 4096,
|
||||
"Duration": 1,
|
||||
"Rate": 1000,
|
||||
"Drawable": {
|
||||
"Type": "SkCircleDrawable",
|
||||
"Radius": 1
|
||||
},
|
||||
"Code": [
|
||||
"// float rand; Every read returns a random float [0 .. 1)",
|
||||
"EffectCode": [
|
||||
"void effectSpawn(inout Effect effect) {",
|
||||
" effect.rate = 1000;",
|
||||
"}",
|
||||
"",
|
||||
"void effectUpdate(inout Effect effect) {",
|
||||
"}",
|
||||
""
|
||||
],
|
||||
"Code": [
|
||||
"void spawn(inout Particle p) {",
|
||||
" p.lifetime = mix(1, 3, rand);",
|
||||
" float a = radians(mix(250, 290, rand));",
|
||||
|
@ -1,16 +1,21 @@
|
||||
{
|
||||
"MaxCount": 32,
|
||||
"Duration": 1,
|
||||
"Rate": 8,
|
||||
"Drawable": {
|
||||
"Type": "SkImageDrawable",
|
||||
"Path": "resources/images/explosion_sprites.png",
|
||||
"Columns": 4,
|
||||
"Rows": 4
|
||||
},
|
||||
"Code": [
|
||||
"// float rand; Every read returns a random float [0 .. 1)",
|
||||
"EffectCode": [
|
||||
"void effectSpawn(inout Effect effect) {",
|
||||
" effect.rate = 8;",
|
||||
"}",
|
||||
"",
|
||||
"void effectUpdate(inout Effect effect) {",
|
||||
"}",
|
||||
""
|
||||
],
|
||||
"Code": [
|
||||
"float2 circle() {",
|
||||
" float x;",
|
||||
" float y;",
|
||||
|
34
resources/particles/fireworks.json
Normal file
34
resources/particles/fireworks.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"MaxCount": 1000,
|
||||
"Drawable": {
|
||||
"Type": "SkCircleDrawable",
|
||||
"Radius": 3
|
||||
},
|
||||
"EffectCode": [
|
||||
"void effectSpawn(inout Effect effect) {",
|
||||
" effect.burst = 50;",
|
||||
" effect.pos.x = mix(-100, 100, rand);",
|
||||
" effect.pos.y = mix(-100, 100, rand);",
|
||||
" effect.color.rgb = float3(rand, rand, rand);",
|
||||
"}",
|
||||
"",
|
||||
"void effectUpdate(inout Effect effect) {",
|
||||
"}",
|
||||
""
|
||||
],
|
||||
"Code": [
|
||||
"void spawn(inout Particle p) {",
|
||||
" p.lifetime = 2.5;",
|
||||
" float a = radians(rand * 360);",
|
||||
" float s = mix(90, 100, rand);",
|
||||
" p.vel.x = cos(a) * s;",
|
||||
" p.vel.y = sin(a) * s;",
|
||||
"}",
|
||||
"",
|
||||
"void update(inout Particle p) {",
|
||||
" p.color.a = 1 - p.age;",
|
||||
"}",
|
||||
""
|
||||
],
|
||||
"Bindings": []
|
||||
}
|
@ -1,14 +1,20 @@
|
||||
{
|
||||
"MaxCount": 6000,
|
||||
"Duration": 5,
|
||||
"Rate": 2000,
|
||||
"Drawable": {
|
||||
"Type": "SkCircleDrawable",
|
||||
"Radius": 2
|
||||
},
|
||||
"Code": [
|
||||
"// float rand; Every read returns a random float [0 .. 1)",
|
||||
"EffectCode": [
|
||||
"void effectSpawn(inout Effect effect) {",
|
||||
" effect.rate = 2000;",
|
||||
"}",
|
||||
"",
|
||||
"void effectUpdate(inout Effect effect) {",
|
||||
" effect.pos.y = sin(effect.age * 6.28) * 40;",
|
||||
"}",
|
||||
""
|
||||
],
|
||||
"Code": [
|
||||
"void spawn(inout Particle p) {",
|
||||
" p.lifetime = 2 + (rand * 2);",
|
||||
" p.vel.x = (30 * rand) + 50;",
|
||||
|
@ -1,16 +1,21 @@
|
||||
{
|
||||
"MaxCount": 32,
|
||||
"Duration": 1,
|
||||
"Rate": 0.5,
|
||||
"Drawable": {
|
||||
"Type": "SkImageDrawable",
|
||||
"Path": "resources/images/baby_tux.png",
|
||||
"Columns": 1,
|
||||
"Rows": 1
|
||||
},
|
||||
"Code": [
|
||||
"// float rand; Every read returns a random float [0 .. 1)",
|
||||
"EffectCode": [
|
||||
"void effectSpawn(inout Effect effect) {",
|
||||
" effect.rate = 0.5;",
|
||||
"}",
|
||||
"",
|
||||
"void effectUpdate(inout Effect effect) {",
|
||||
"}",
|
||||
""
|
||||
],
|
||||
"Code": [
|
||||
"void spawn(inout Particle p) {",
|
||||
" p.lifetime = 20;",
|
||||
" float a = radians(10 + 60 * rand);",
|
||||
|
31
resources/particles/pulse.json
Normal file
31
resources/particles/pulse.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"MaxCount": 4000,
|
||||
"Drawable": {
|
||||
"Type": "SkCircleDrawable",
|
||||
"Radius": 3
|
||||
},
|
||||
"EffectCode": [
|
||||
"void effectSpawn(inout Effect effect) {",
|
||||
" effect.lifetime = 1;",
|
||||
"}",
|
||||
"",
|
||||
"void effectUpdate(inout Effect effect) {",
|
||||
" effect.rate = (sin(radians(effect.age * 720)) + 0.7) * 200;",
|
||||
"}",
|
||||
""
|
||||
],
|
||||
"Code": [
|
||||
"void spawn(inout Particle p) {",
|
||||
" p.lifetime = 6;",
|
||||
" float a = radians(rand * 360);",
|
||||
" p.pos = float2(cos(a), sin(a)) * 40;",
|
||||
" p.vel = p.pos;",
|
||||
" p.scale = 0.5;",
|
||||
"}",
|
||||
"",
|
||||
"void update(inout Particle p) {",
|
||||
"}",
|
||||
""
|
||||
],
|
||||
"Bindings": []
|
||||
}
|
@ -1,14 +1,19 @@
|
||||
{
|
||||
"MaxCount": 4096,
|
||||
"Duration": 1,
|
||||
"Rate": 30,
|
||||
"Drawable": {
|
||||
"Type": "SkCircleDrawable",
|
||||
"Radius": 1
|
||||
},
|
||||
"Code": [
|
||||
"// float rand; Every read returns a random float [0 .. 1)",
|
||||
"EffectCode": [
|
||||
"void effectSpawn(inout Effect effect) {",
|
||||
" effect.rate = 30;",
|
||||
"}",
|
||||
"",
|
||||
"void effectUpdate(inout Effect effect) {",
|
||||
"}",
|
||||
""
|
||||
],
|
||||
"Code": [
|
||||
"void spawn(inout Particle p) {",
|
||||
" p.lifetime = 10;",
|
||||
" p.vel.y = 10 + rand * 20;",
|
||||
|
@ -1,20 +1,24 @@
|
||||
{
|
||||
"MaxCount": 800,
|
||||
"Duration": 4,
|
||||
"Rate": 120,
|
||||
"Drawable": {
|
||||
"Type": "SkCircleDrawable",
|
||||
"Radius": 2
|
||||
},
|
||||
"Code": [
|
||||
"// float rand; Every read returns a random float [0 .. 1)",
|
||||
"EffectCode": [
|
||||
"void effectSpawn(inout Effect effect) {",
|
||||
" effect.lifetime = 4;",
|
||||
" effect.rate = 120;",
|
||||
" effect.spin = 6;",
|
||||
"}",
|
||||
"",
|
||||
"void effectUpdate(inout Effect effect) {",
|
||||
"}",
|
||||
""
|
||||
],
|
||||
"Code": [
|
||||
"void spawn(inout Particle p) {",
|
||||
" p.lifetime = 2 + rand;",
|
||||
" float a = radians(effectAge * 1080);",
|
||||
" float s = 50 + rand * 10;",
|
||||
" p.vel.x = cos(a) * s;",
|
||||
" p.vel.y = sin(a) * s;",
|
||||
" p.vel = p.dir * mix(50, 60, rand);",
|
||||
"}",
|
||||
"",
|
||||
"void update(inout Particle p) {",
|
||||
|
@ -1,14 +1,19 @@
|
||||
{
|
||||
"MaxCount": 4096,
|
||||
"Duration": 1,
|
||||
"Rate": 400,
|
||||
"Drawable": {
|
||||
"Type": "SkCircleDrawable",
|
||||
"Radius": 2
|
||||
},
|
||||
"Code": [
|
||||
"// float rand; Every read returns a random float [0 .. 1)",
|
||||
"EffectCode": [
|
||||
"void effectSpawn(inout Effect effect) {",
|
||||
" effect.rate = 400;",
|
||||
"}",
|
||||
"",
|
||||
"void effectUpdate(inout Effect effect) {",
|
||||
"}",
|
||||
""
|
||||
],
|
||||
"Code": [
|
||||
"void spawn(inout Particle p) {",
|
||||
" p.lifetime = 1 + 2 * rand;",
|
||||
" p.pos.x = rand * 50;",
|
||||
|
@ -1,15 +1,19 @@
|
||||
{
|
||||
"MaxCount": 4096,
|
||||
"Duration": 1,
|
||||
"Rate": 90,
|
||||
"Drawable": {
|
||||
"Type": "SkCircleDrawable",
|
||||
"Radius": 2
|
||||
},
|
||||
"EffectCode": [
|
||||
"void effectSpawn(inout Effect effect) {",
|
||||
" effect.rate = 90;",
|
||||
"}",
|
||||
"",
|
||||
"void effectUpdate(inout Effect effect) {",
|
||||
"}",
|
||||
""
|
||||
],
|
||||
"Code": [
|
||||
"",
|
||||
"// float rand; Every read returns a random float [0 .. 1)",
|
||||
"",
|
||||
"float2 circle() {",
|
||||
" float x;",
|
||||
" float y;",
|
||||
|
@ -72,6 +72,7 @@ public:
|
||||
int lines = count_lines(s);
|
||||
ImGuiInputTextFlags flags = ImGuiInputTextFlags_CallbackResize;
|
||||
if (lines > 1) {
|
||||
ImGui::LabelText("##Label", "%s", name);
|
||||
ImVec2 boxSize(-1.0f, ImGui::GetTextLineHeight() * (lines + 1));
|
||||
ImGui::InputTextMultiline(item(name), s.writable_str(), s.size() + 1, boxSize,
|
||||
flags, InputTextCallback, &s);
|
||||
@ -281,6 +282,7 @@ void ParticlesSlide::draw(SkCanvas* canvas) {
|
||||
sk_sp<SkParticleEffect> effect(new SkParticleEffect(fLoaded[i].fParams, fRandom));
|
||||
effect->start(fAnimationTime, looped);
|
||||
fRunning.push_back({ fPlayPosition, fLoaded[i].fName, effect });
|
||||
fRandom.nextU();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user