blob: d1f7b6486b75acf83e69ee95d51ba4d9f6050937 [file] [log] [blame]
/*
* 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 "SkParticleEffect.h"
#include "Resources.h"
#include "SkAnimTimer.h"
#include "SkCanvas.h"
#include "SkColorData.h"
#include "SkPaint.h"
#include "SkParticleAffector.h"
#include "SkParticleDrawable.h"
#include "SkReflected.h"
#include "SkRSXform.h"
void SkParticleEffectParams::visitFields(SkFieldVisitor* v) {
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);
}
SkParticleEffect::SkParticleEffect(sk_sp<SkParticleEffectParams> params, const SkRandom& random)
: fParams(std::move(params))
, fRandom(random)
, fLooping(false)
, fSpawnTime(-1.0)
, fCount(0)
, fLastTime(-1.0)
, fSpawnRemainder(0.0f) {
this->setCapacity(fParams->fMaxCount);
}
void SkParticleEffect::start(const SkAnimTimer& timer, bool looping) {
fCount = 0;
fLastTime = fSpawnTime = timer.secs();
fSpawnRemainder = 0.0f;
fLooping = looping;
}
void SkParticleEffect::update(const SkAnimTimer& timer) {
if (!timer.isRunning() || !this->isAlive() || !fParams->fDrawable) {
return;
}
double now = timer.secs();
float deltaTime = static_cast<float>(now - fLastTime);
if (deltaTime < 0.0f) {
return;
}
fLastTime = now;
// Handle user edits to fMaxCount
if (fParams->fMaxCount != fCapacity) {
this->setCapacity(fParams->fMaxCount);
}
SkParticleUpdateParams updateParams;
updateParams.fDeltaTime = deltaTime;
// 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) {
// NOTE: This is fast, but doesn't preserve drawing order. Could be a problem...
fParticles[i] = fParticles[fCount - 1];
fStableRandoms[i] = fStableRandoms[fCount - 1];
--i;
--fCount;
}
}
// Spawn new particles
float desired = fParams->fRate * deltaTime + fSpawnRemainder;
int numToSpawn = sk_float_round2int(desired);
fSpawnRemainder = desired - numToSpawn;
numToSpawn = SkTPin(numToSpawn, 0, fParams->fMaxCount - fCount);
if (numToSpawn) {
// This isn't "particle" t, it's effect t.
float t = static_cast<float>((now - fSpawnTime) / fParams->fEffectDuration);
t = fLooping ? fmodf(t, 1.0f) : SkTPin(t, 0.0f, 1.0f);
for (int i = 0; i < numToSpawn; ++i) {
// Mutate our SkRandom so each particle definitely gets a different stable generator
fRandom.nextU();
// Temporarily set our age to the *effect* age, so spawn affectors are driven by that
fParticles[fCount].fAge = t;
fParticles[fCount].fInvLifetime =
sk_ieee_float_divide(1.0f, fParams->fLifetime.eval(t, fRandom));
fParticles[fCount].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 = fStableRandoms[fCount] = fRandom;
fCount++;
}
// Apply spawn affectors, then reset our age to 0 (the *particle* age)
for (auto affector : fParams->fSpawnAffectors) {
if (affector) {
affector->apply(updateParams, fParticles + (fCount - numToSpawn), numToSpawn);
}
}
for (int i = fCount - numToSpawn; i < fCount; ++i) {
fParticles[i].fAge = 0.0f;
}
}
// 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];
}
// Apply update rules
for (auto affector : fParams->fUpdateAffectors) {
if (affector) {
affector->apply(updateParams, fParticles, 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;
SkScalar c, s = SkScalarSinCos(fParticles[i].fVelocity.fAngular * deltaTime, &c);
SkVector oldHeading = fParticles[i].fPose.fHeading;
fParticles[i].fPose.fHeading = { oldHeading.fX * c - oldHeading.fY * s,
oldHeading.fX * s + oldHeading.fY * c };
}
// Mark effect as dead if we've reached the end (and are not looping)
if (!fLooping && (now - fSpawnTime) > fParams->fEffectDuration) {
fSpawnTime = -1.0;
}
}
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);
}
}
void SkParticleEffect::setCapacity(int capacity) {
fParticles.realloc(capacity);
fStableRandoms.realloc(capacity);
fCapacity = capacity;
fCount = SkTMin(fCount, fCapacity);
}