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