Add accessors to get/set SkParticleEffect fields

Simplify burst handling. Scripts should just add to burst (if
they want to handle programmatic bursting, as well).

Update most effects to handle dynamic updates to position better,
and add a sample effect meant to be used with mouse tracking.

Change-Id: Ia302e1d04e62e2b07974807c44067786cc10a8ad
Bug: skia:9513
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/248798
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
This commit is contained in:
Brian Osman 2019-10-15 10:27:59 -04:00 committed by Skia Commit-Bot
parent f22c57ddcc
commit df18296f98
10 changed files with 78 additions and 21 deletions

View File

@ -156,6 +156,28 @@ public:
}
int getCount() const { return fCount; }
float getRate() const { return fState.fRate; }
int getBurst() const { return fState.fBurst; }
SkPoint getPosition() const { return fState.fPosition; }
SkVector getHeading() const { return fState.fHeading; }
float getScale() const { return fState.fScale; }
SkVector getVelocity() const { return fState.fVelocity; }
float getSpin() const { return fState.fSpin; }
SkColor4f getColor() const { return fState.fColor; }
float getFrame() const { return fState.fFrame; }
uint32_t getFlags() const { return fState.fFlags; }
void setRate (float r) { fState.fRate = r; }
void setBurst (int b) { fState.fBurst = b; }
void setPosition(SkPoint p) { fState.fPosition = p; }
void setHeading (SkVector h) { fState.fHeading = h; }
void setScale (float s) { fState.fScale = s; }
void setVelocity(SkVector v) { fState.fVelocity = v; }
void setSpin (float s) { fState.fSpin = s; }
void setColor (SkColor4f c) { fState.fColor = c; }
void setFrame (float f) { fState.fFrame = f; }
void setFlags (uint32_t f) { fState.fFlags = f; }
static void RegisterParticleTypes();
private:
@ -165,7 +187,7 @@ private:
void advanceTime(double now);
void processEffectSpawnRequests(double now);
int runEffectScript(double now, const char* entry);
void runEffectScript(double now, const char* entry);
void processParticleSpawnRequests(double now, int start);
void runParticleScript(double now, const char* entry, int start, int count);

View File

@ -226,8 +226,7 @@ void SkParticleEffect::processEffectSpawnRequests(double now) {
fSpawnRequests.reset();
}
int SkParticleEffect::runEffectScript(double now, const char* entry) {
fState.fBurst = 0;
void SkParticleEffect::runEffectScript(double now, const char* entry) {
if (const auto& byteCode = fParams->fEffectProgram.fByteCode) {
if (auto fun = byteCode->getFunction(entry)) {
for (const auto& value : fParams->fEffectProgram.fExternalValues) {
@ -241,7 +240,6 @@ int SkParticleEffect::runEffectScript(double now, const char* entry) {
this->processEffectSpawnRequests(now);
}
}
return fState.fBurst;
}
void SkParticleEffect::processParticleSpawnRequests(double now, int start) {
@ -304,24 +302,22 @@ void SkParticleEffect::advanceTime(double now) {
this->setCapacity(fParams->fMaxCount);
}
int burstCount = 0;
// Is this the first update after calling start()?
// Run 'effectSpawn' to set initial emitter properties.
if (fState.fAge == 0.0f && fState.fLoopCount == 0) {
burstCount += this->runEffectScript(now, "effectSpawn");
this->runEffectScript(now, "effectSpawn");
}
fState.fAge += fState.fDeltaTime / fState.fLifetime;
if (fState.fAge > 1) {
// We always run effectDeath when age crosses 1, whether we're looping or actually dying
burstCount += this->runEffectScript(now, "effectDeath");
this->runEffectScript(now, "effectDeath");
if (fLooping) {
// If we looped, then run effectSpawn again (with the updated loop count)
fState.fLoopCount += sk_float_floor2int(fState.fAge);
fState.fAge = fmodf(fState.fAge, 1.0f);
burstCount += this->runEffectScript(now, "effectSpawn");
this->runEffectScript(now, "effectSpawn");
} else {
// Effect is dead if we've reached the end (and are not looping)
return;
@ -349,7 +345,7 @@ void SkParticleEffect::advanceTime(double now) {
this->runParticleScript(now, "death", fCount, numDyingParticles);
// Run 'effectUpdate' to adjust emitter properties
burstCount += this->runEffectScript(now, "effectUpdate");
this->runEffectScript(now, "effectUpdate");
// Do integration of effect position and orientation
{
@ -362,10 +358,11 @@ void SkParticleEffect::advanceTime(double now) {
}
// Spawn new particles
float desired = fState.fRate * fState.fDeltaTime + fSpawnRemainder;
float desired = fState.fRate * fState.fDeltaTime + fSpawnRemainder + fState.fBurst;
fState.fBurst = 0;
int numToSpawn = sk_float_round2int(desired);
fSpawnRemainder = desired - numToSpawn;
numToSpawn = SkTPin(numToSpawn + burstCount, 0, fParams->fMaxCount - fCount);
numToSpawn = SkTPin(numToSpawn, 0, fParams->fMaxCount - fCount);
if (numToSpawn) {
const int spawnBase = fCount;

View File

@ -0,0 +1,28 @@
{
"MaxCount": 2000,
"Drawable": {
"Type": "SkCircleDrawable",
"Radius": 4
},
"EffectCode": [
"void effectSpawn(inout Effect effect) {",
" effect.rate = 800;",
"}",
""
],
"Code": [
"void spawn(inout Particle p) {",
" p.lifetime = 2 + rand;",
" float a = radians(rand * 360);",
" p.vel = float2(cos(a), sin(a)) * mix(5, 15, rand);",
" p.scale = mix(0.25, 0.75, rand);",
"}",
"",
"void update(inout Particle p) {",
" p.color.r = p.age;",
" p.color.g = 1 - p.age;",
"}",
""
],
"Bindings": []
}

View File

@ -10,12 +10,12 @@
"}",
"",
"void effectUpdate(inout Effect effect) {",
" effect.pos.y = sin(effect.age * 6.28) * 40;",
"}",
""
],
"Code": [
"void spawn(inout Particle p) {",
" p.pos.y += sin(effect.age * 6.28) * 40;",
" p.lifetime = 2 + (rand * 2);",
" p.vel.x = (30 * rand) + 50;",
" p.vel.y = (20 * rand) - 10;",

View File

@ -25,8 +25,9 @@
"",
"void spawn(inout Particle p) {",
" p.lifetime = 1.0 + rand * 2.0;",
" p.pos = circle() * 60;",
" p.vel = p.pos / 3;",
" float2 ofs = circle() * 60;",
" p.pos += ofs;",
" p.vel = ofs / 3;",
"}",
"",
"void update(inout Particle p) {",

View File

@ -17,7 +17,7 @@
" float s = mix(10, 30, rand);",
" p.vel.x = cos(a) * s;",
" p.vel.y = sin(a) * s;",
" p.pos = text(rand).xy;",
" p.pos += text(rand).xy;",
"}",
"",
"void update(inout Particle p) {",

View File

@ -14,8 +14,9 @@
"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;",
" float2 ofs = float2(cos(a), sin(a)) * 40;",
" p.pos += ofs;",
" p.vel = ofs;",
" p.scale = 0.5;",
"}",
""

View File

@ -23,11 +23,11 @@
"",
"void spawn(inout Particle p) {",
" p.lifetime = 30;",
" p.pos = circle() * 40;",
" p.pos += circle() * 40;",
"}",
"",
"void update(inout Particle p) {",
" p.vel += normalize(p.pos) * dt * 10;",
" p.vel += normalize(p.pos - effect.pos) * dt * 10;",
" p.scale = mix(0.25, 3, p.age);",
"}",
""

View File

@ -281,7 +281,7 @@ void ParticlesSlide::draw(SkCanvas* canvas) {
if (fAnimated && ImGui::Button("Play")) {
sk_sp<SkParticleEffect> effect(new SkParticleEffect(fLoaded[i].fParams, fRandom));
effect->start(fAnimationTime, looped);
fRunning.push_back({ fPlayPosition, fLoaded[i].fName, effect });
fRunning.push_back({ fPlayPosition, fLoaded[i].fName, effect, false });
fRandom.nextU();
}
ImGui::SameLine();
@ -302,10 +302,17 @@ void ParticlesSlide::draw(SkCanvas* canvas) {
if (ImGui::Begin("Running")) {
for (int i = 0; i < fRunning.count(); ++i) {
ImGui::PushID(i);
ImGui::Checkbox("##Track", &fRunning[i].fTrackMouse);
ImGui::SameLine();
bool remove = ImGui::Button("X") || !fRunning[i].fEffect->isAlive();
ImGui::SameLine();
ImGui::Text("%4g, %4g %5d %s", fRunning[i].fPosition.fX, fRunning[i].fPosition.fY,
fRunning[i].fEffect->getCount(), fRunning[i].fName.c_str());
if (fRunning[i].fTrackMouse) {
fRunning[i].fEffect->setPosition({ ImGui::GetMousePos().x,
ImGui::GetMousePos().y });
fRunning[i].fPosition.set(0, 0);
}
if (remove) {
fRunning.removeShuffle(i);
}

View File

@ -49,6 +49,7 @@ private:
SkPoint fPosition;
SkString fName;
sk_sp<SkParticleEffect> fEffect;
bool fTrackMouse;
};
SkTArray<RunningEffect> fRunning;
};