blob: 24262da80c1fb4151a6ca9cb26380788a5c0b3a7 [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 "SkNx.h"
#include "SkPaint.h"
#include "SkParticleAffector.h"
#include "SkParticleEmitter.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("StartColor", fStartColor);
v->visit("EndColor", fEndColor);
v->visit("Image", fImage);
v->visit("ImageCols", fImageCols);
v->visit("ImageRows", fImageRows);
v->visit("Emitter", fEmitter);
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);
// Load image, determine sprite rect size
fImage = GetResourceAsImage(fParams->fImage.c_str());
if (!fImage) {
uint32_t whitePixel = ~0;
SkPixmap pmap(SkImageInfo::MakeN32Premul(1, 1), &whitePixel, sizeof(uint32_t));
fImage = SkImage::MakeRasterCopy(pmap);
}
int w = fImage->width();
int h = fImage->height();
SkASSERT(w % fParams->fImageCols == 0);
SkASSERT(h % fParams->fImageRows == 0);
fImageRect = SkRect::MakeIWH(w / fParams->fImageCols, h / fParams->fImageRows);
}
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()) {
return;
}
// Handle user edits to fMaxCount
if (fParams->fMaxCount != fCapacity) {
this->setCapacity(fParams->fMaxCount);
}
double now = timer.secs();
float deltaTime = static_cast<float>(now - fLastTime);
fLastTime = now;
Sk4f startColor = Sk4f::Load(fParams->fStartColor.vec());
Sk4f colorScale = Sk4f::Load(fParams->fEndColor.vec()) - startColor;
SkParticleUpdateParams updateParams;
updateParams.fDeltaTime = deltaTime;
updateParams.fRandom = &fRandom;
// Remove particles that have reached their end of life
for (int i = 0; i < fCount; ++i) {
if (now > fParticles[i].fTimeOfDeath) {
// NOTE: This is fast, but doesn't preserve drawing order. Could be a problem...
fParticles[i] = fParticles[fCount - 1];
fSpriteRects[i] = fSpriteRects[fCount - 1];
fColors[i] = fColors[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 (fParams->fEmitter) {
// No, this isn't "stable", but spawn affectors are only run once anyway.
// Would it ever make sense to give the same random to all particles spawned on a given
// frame? Having a hard time thinking when that would be useful.
updateParams.fStableRandom = &fRandom;
// ... and this isn't "particle" t, it's effect t.
double t = (now - fSpawnTime) / fParams->fEffectDuration;
updateParams.fParticleT = static_cast<float>(fLooping ? fmod(t, 1.0) : SkTPin(t, 0.0, 1.0));
for (int i = 0; i < numToSpawn; ++i) {
fParticles[fCount].fTimeOfBirth = now;
fParticles[fCount].fTimeOfDeath = now + fParams->fLifetime.eval(updateParams.fParticleT,
fRandom);
fParticles[fCount].fPV.fPose = fParams->fEmitter->emit(fRandom);
fParticles[fCount].fPV.fVelocity.fLinear = { 0.0f, 0.0f };
fParticles[fCount].fPV.fVelocity.fAngular = 0.0f;
fParticles[fCount].fStableRandom = fRandom;
fSpriteRects[fCount] = this->spriteRect(0);
fCount++;
}
// Apply spawn affectors
for (int i = fCount - numToSpawn; i < fCount; ++i) {
for (auto affector : fParams->fSpawnAffectors) {
if (affector) {
affector->apply(updateParams, fParticles[i].fPV);
}
}
}
}
// Apply update rules
for (int i = 0; i < fCount; ++i) {
// Compute fraction of lifetime that's elapsed
float t = static_cast<float>((now - fParticles[i].fTimeOfBirth) /
(fParticles[i].fTimeOfDeath - fParticles[i].fTimeOfBirth));
SkRandom stableRandom = fParticles[i].fStableRandom;
updateParams.fStableRandom = &stableRandom;
updateParams.fParticleT = t;
// Set sprite rect by lifetime
int frame = static_cast<int>(t * this->spriteCount() + 0.5);
frame = SkTPin(frame, 0, this->spriteCount() - 1);
fSpriteRects[i] = this->spriteRect(frame);
// Set color by lifetime
fColors[i] = Sk4f_toL32(swizzle_rb(startColor + (colorScale * t)));
for (auto affector : fParams->fUpdateAffectors) {
if (affector) {
affector->apply(updateParams, fParticles[i].fPV);
}
}
// Integrate position / orientation
fParticles[i].fPV.fPose.fPosition += fParticles[i].fPV.fVelocity.fLinear * deltaTime;
SkScalar c, s = SkScalarSinCos(fParticles[i].fPV.fVelocity.fAngular * deltaTime, &c);
SkVector oldHeading = fParticles[i].fPV.fPose.fHeading;
fParticles[i].fPV.fPose.fHeading = { oldHeading.fX * c - oldHeading.fY * s,
oldHeading.fX * s + oldHeading.fY * c };
}
// Re-generate all xforms
SkPoint ofs = this->spriteCenter();
for (int i = 0; i < fCount; ++i) {
fXforms[i] = fParticles[i].fPV.fPose.asRSXform(ofs);
}
// 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()) {
SkPaint paint;
paint.setFilterQuality(SkFilterQuality::kMedium_SkFilterQuality);
canvas->drawAtlas(fImage, fXforms.get(), fSpriteRects.get(), fColors.get(), fCount,
SkBlendMode::kModulate, nullptr, &paint);
}
}
void SkParticleEffect::setCapacity(int capacity) {
fParticles.realloc(capacity);
fXforms.realloc(capacity);
fSpriteRects.realloc(capacity);
fColors.realloc(capacity);
fCapacity = capacity;
fCount = SkTMin(fCount, fCapacity);
}