/* * 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 "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 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(double now, bool looping) { fCount = 0; fLastTime = fSpawnTime = now; fSpawnRemainder = 0.0f; fLooping = looping; } void SkParticleEffect::update(double now) { if (!this->isAlive() || !fParams->fDrawable) { return; } float deltaTime = static_cast(now - fLastTime); if (deltaTime <= 0.0f) { return; } fLastTime = now; // Handle user edits to fMaxCount if (fParams->fMaxCount != fCapacity) { this->setCapacity(fParams->fMaxCount); } float effectAge = static_cast((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; // 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) { const int spawnBase = fCount; 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; fCount++; } // Apply spawn affectors for (auto affector : fParams->fSpawnAffectors) { if (affector) { affector->apply(updateParams, fParticles + spawnBase, numToSpawn); } } // Now stash copies of the random generators and compute particle lifetimes // (so the curve can refer to spawn-computed source values) 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; } } // 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]; } // 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); } } // 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); } 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(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; }