• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2 * Copyright 2019 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7 
8 #include "SkParticleEffect.h"
9 
10 #include "SkCanvas.h"
11 #include "SkColorData.h"
12 #include "SkPaint.h"
13 #include "SkParticleAffector.h"
14 #include "SkParticleDrawable.h"
15 #include "SkReflected.h"
16 #include "SkRSXform.h"
17 
visitFields(SkFieldVisitor * v)18 void SkParticleEffectParams::visitFields(SkFieldVisitor* v) {
19     v->visit("MaxCount", fMaxCount);
20     v->visit("Duration", fEffectDuration);
21     v->visit("Rate", fRate);
22     v->visit("Life", fLifetime);
23 
24     v->visit("Drawable", fDrawable);
25 
26     v->visit("Spawn", fSpawnAffectors);
27     v->visit("Update", fUpdateAffectors);
28 }
29 
SkParticleEffect(sk_sp<SkParticleEffectParams> params,const SkRandom & random)30 SkParticleEffect::SkParticleEffect(sk_sp<SkParticleEffectParams> params, const SkRandom& random)
31         : fParams(std::move(params))
32         , fRandom(random)
33         , fLooping(false)
34         , fSpawnTime(-1.0)
35         , fCount(0)
36         , fLastTime(-1.0)
37         , fSpawnRemainder(0.0f) {
38     this->setCapacity(fParams->fMaxCount);
39 }
40 
start(double now,bool looping)41 void SkParticleEffect::start(double now, bool looping) {
42     fCount = 0;
43     fLastTime = fSpawnTime = now;
44     fSpawnRemainder = 0.0f;
45     fLooping = looping;
46 }
47 
update(double now)48 void SkParticleEffect::update(double now) {
49     if (!this->isAlive() || !fParams->fDrawable) {
50         return;
51     }
52 
53     float deltaTime = static_cast<float>(now - fLastTime);
54     if (deltaTime <= 0.0f) {
55         return;
56     }
57     fLastTime = now;
58 
59     // Handle user edits to fMaxCount
60     if (fParams->fMaxCount != fCapacity) {
61         this->setCapacity(fParams->fMaxCount);
62     }
63 
64     float effectAge = static_cast<float>((now - fSpawnTime) / fParams->fEffectDuration);
65     effectAge = fLooping ? fmodf(effectAge, 1.0f) : SkTPin(effectAge, 0.0f, 1.0f);
66 
67     SkParticleUpdateParams updateParams;
68     updateParams.fDeltaTime = deltaTime;
69     updateParams.fEffectAge = effectAge;
70 
71     // During spawn, values that refer to kAge_Source get the *effect* age
72     updateParams.fAgeSource = SkParticleValue::kEffectAge_Source;
73 
74     // Advance age for existing particles, and remove any that have reached their end of life
75     for (int i = 0; i < fCount; ++i) {
76         fParticles[i].fAge += fParticles[i].fInvLifetime * deltaTime;
77         if (fParticles[i].fAge > 1.0f) {
78             // NOTE: This is fast, but doesn't preserve drawing order. Could be a problem...
79             fParticles[i]     = fParticles[fCount - 1];
80             fStableRandoms[i] = fStableRandoms[fCount - 1];
81             --i;
82             --fCount;
83         }
84     }
85 
86     // Spawn new particles
87     float desired = fParams->fRate * deltaTime + fSpawnRemainder;
88     int numToSpawn = sk_float_round2int(desired);
89     fSpawnRemainder = desired - numToSpawn;
90     numToSpawn = SkTPin(numToSpawn, 0, fParams->fMaxCount - fCount);
91     if (numToSpawn) {
92         const int spawnBase = fCount;
93 
94         for (int i = 0; i < numToSpawn; ++i) {
95             // Mutate our SkRandom so each particle definitely gets a different generator
96             fRandom.nextU();
97             fParticles[fCount].fAge = 0.0f;
98             fParticles[fCount].fPose.fPosition = { 0.0f, 0.0f };
99             fParticles[fCount].fPose.fHeading = { 0.0f, -1.0f };
100             fParticles[fCount].fPose.fScale = 1.0f;
101             fParticles[fCount].fVelocity.fLinear = { 0.0f, 0.0f };
102             fParticles[fCount].fVelocity.fAngular = 0.0f;
103             fParticles[fCount].fColor = { 1.0f, 1.0f, 1.0f, 1.0f };
104             fParticles[fCount].fFrame = 0.0f;
105             fParticles[fCount].fRandom = fRandom;
106             fCount++;
107         }
108 
109         // Apply spawn affectors
110         for (auto affector : fParams->fSpawnAffectors) {
111             if (affector) {
112                 affector->apply(updateParams, fParticles + spawnBase, numToSpawn);
113             }
114         }
115 
116         // Now stash copies of the random generators and compute particle lifetimes
117         // (so the curve can refer to spawn-computed source values)
118         for (int i = spawnBase; i < fCount; ++i) {
119             fParticles[i].fInvLifetime =
120                 sk_ieee_float_divide(1.0f, fParams->fLifetime.eval(updateParams, fParticles[i]));
121             fStableRandoms[i] = fParticles[i].fRandom;
122         }
123     }
124 
125     // Restore all stable random generators so update affectors get consistent behavior each frame
126     for (int i = 0; i < fCount; ++i) {
127         fParticles[i].fRandom = fStableRandoms[i];
128     }
129 
130     // During update, values that refer to kAge_Source get the *particle* age
131     updateParams.fAgeSource = SkParticleValue::kParticleAge_Source;
132 
133     // Apply update rules
134     for (auto affector : fParams->fUpdateAffectors) {
135         if (affector) {
136             affector->apply(updateParams, fParticles, fCount);
137         }
138     }
139 
140     // Do fixed-function update work (integration of position and orientation)
141     for (int i = 0; i < fCount; ++i) {
142         fParticles[i].fPose.fPosition += fParticles[i].fVelocity.fLinear * deltaTime;
143 
144         SkScalar c, s = SkScalarSinCos(fParticles[i].fVelocity.fAngular * deltaTime, &c);
145         SkVector oldHeading = fParticles[i].fPose.fHeading;
146         fParticles[i].fPose.fHeading = { oldHeading.fX * c - oldHeading.fY * s,
147                                          oldHeading.fX * s + oldHeading.fY * c };
148     }
149 
150     // Mark effect as dead if we've reached the end (and are not looping)
151     if (!fLooping && (now - fSpawnTime) > fParams->fEffectDuration) {
152         fSpawnTime = -1.0;
153     }
154 }
155 
draw(SkCanvas * canvas)156 void SkParticleEffect::draw(SkCanvas* canvas) {
157     if (this->isAlive() && fParams->fDrawable) {
158         SkPaint paint;
159         paint.setFilterQuality(SkFilterQuality::kMedium_SkFilterQuality);
160         fParams->fDrawable->draw(canvas, fParticles.get(), fCount, &paint);
161     }
162 }
163 
setCapacity(int capacity)164 void SkParticleEffect::setCapacity(int capacity) {
165     fParticles.realloc(capacity);
166     fStableRandoms.realloc(capacity);
167 
168     fCapacity = capacity;
169     fCount = SkTMin(fCount, fCapacity);
170 }
171 
172 constexpr SkFieldVisitor::EnumStringMapping gValueSourceMapping[] = {
173     { SkParticleValue::kAge_Source,          "Age" },
174     { SkParticleValue::kRandom_Source,       "Random" },
175     { SkParticleValue::kParticleAge_Source,  "ParticleAge" },
176     { SkParticleValue::kEffectAge_Source,    "EffectAge" },
177     { SkParticleValue::kPositionX_Source,    "PositionX" },
178     { SkParticleValue::kPositionY_Source,    "PositionY" },
179     { SkParticleValue::kHeadingX_Source,     "HeadingX" },
180     { SkParticleValue::kHeadingY_Source,     "HeadingY" },
181     { SkParticleValue::kScale_Source,        "Scale" },
182     { SkParticleValue::kVelocityX_Source,    "VelocityX" },
183     { SkParticleValue::kVelocityY_Source,    "VelocityY" },
184     { SkParticleValue::kRotation_Source,     "Rotation" },
185     { SkParticleValue::kColorR_Source,       "ColorR" },
186     { SkParticleValue::kColorG_Source,       "ColorG" },
187     { SkParticleValue::kColorB_Source,       "ColorB" },
188     { SkParticleValue::kColorA_Source,       "ColorA" },
189     { SkParticleValue::kSpriteFrame_Source,  "SpriteFrame" },
190 };
191 
192 constexpr SkFieldVisitor::EnumStringMapping gValueTileModeMapping[] = {
193     { SkParticleValue::kClamp_TileMode,  "Clamp" },
194     { SkParticleValue::kRepeat_TileMode, "Repeat" },
195     { SkParticleValue::kMirror_TileMode, "Mirror" },
196 };
197 
source_needs_frame(int source)198 static bool source_needs_frame(int source) {
199     switch (source) {
200         case SkParticleValue::kHeadingX_Source:
201         case SkParticleValue::kHeadingY_Source:
202         case SkParticleValue::kVelocityX_Source:
203         case SkParticleValue::kVelocityY_Source:
204             return true;
205         default:
206             return false;
207     }
208 }
209 
visitFields(SkFieldVisitor * v)210 void SkParticleValue::visitFields(SkFieldVisitor* v) {
211     v->visit("Source", fSource, gValueSourceMapping, SK_ARRAY_COUNT(gValueSourceMapping));
212     if (source_needs_frame(fSource)) {
213         v->visit("Frame", fFrame, gParticleFrameMapping, SK_ARRAY_COUNT(gParticleFrameMapping));
214     }
215     v->visit("TileMode", fTileMode, gValueTileModeMapping, SK_ARRAY_COUNT(gValueTileModeMapping));
216     v->visit("Left", fLeft);
217     v->visit("Right", fRight);
218 
219     // Re-compute cached evaluation parameters
220     fScale = sk_float_isfinite(1.0f / (fRight - fLeft)) ? 1.0f / (fRight - fLeft) : 0;
221     fBias = -fLeft * fScale;
222 }
223 
getSourceValue(const SkParticleUpdateParams & params,SkParticleState & ps) const224 float SkParticleValue::getSourceValue(const SkParticleUpdateParams& params,
225                                       SkParticleState& ps) const {
226     switch ((kAge_Source == fSource) ? params.fAgeSource : fSource) {
227         // Do all the simple (non-frame-dependent) sources first:
228         case kRandom_Source:      return ps.fRandom.nextF();
229         case kParticleAge_Source: return ps.fAge;
230         case kEffectAge_Source:   return params.fEffectAge;
231 
232         case kPositionX_Source:   return ps.fPose.fPosition.fX;
233         case kPositionY_Source:   return ps.fPose.fPosition.fY;
234         case kScale_Source:       return ps.fPose.fScale;
235         case kRotation_Source:    return ps.fVelocity.fAngular;
236 
237         case kColorR_Source:      return ps.fColor.fR;
238         case kColorG_Source:      return ps.fColor.fG;
239         case kColorB_Source:      return ps.fColor.fB;
240         case kColorA_Source:      return ps.fColor.fA;
241         case kSpriteFrame_Source: return ps.fFrame;
242     }
243 
244     SkASSERT(source_needs_frame(fSource));
245     SkVector frameUp = ps.getFrameHeading(static_cast<SkParticleFrame>(fFrame));
246     SkVector frameRight = { -frameUp.fY, frameUp.fX };
247 
248     switch (fSource) {
249         case kHeadingX_Source:  return ps.fPose.fHeading.dot(frameRight);
250         case kHeadingY_Source:  return ps.fPose.fHeading.dot(frameUp);
251         case kVelocityX_Source: return ps.fVelocity.fLinear.dot(frameRight);
252         case kVelocityY_Source: return ps.fVelocity.fLinear.dot(frameUp);
253     }
254 
255     SkDEBUGFAIL("Unreachable");
256     return 0.0f;
257 }
258 
eval(const SkParticleUpdateParams & params,SkParticleState & ps) const259 float SkParticleValue::eval(const SkParticleUpdateParams& params, SkParticleState& ps) const {
260     float v = this->getSourceValue(params, ps);
261     v = (v * fScale) + fBias;
262 
263     switch (fTileMode) {
264         case kClamp_TileMode:
265             v = SkTPin(v, 0.0f, 1.0f);
266             break;
267         case kRepeat_TileMode:
268             v = sk_float_mod(v, 1.0f);
269             if (v < 0) {
270                 v += 1.0f;
271             }
272             break;
273         case kMirror_TileMode:
274             v = sk_float_mod(v, 2.0f);
275             if (v < 0) {
276                 v += 2.0f;
277             }
278             v = 1.0f - sk_float_abs(v - 1.0f);
279             break;
280     }
281 
282     return v;
283 }
284