blob: 57b6b5458d3fa511a6da81b81c08ddef7b3e8c03 [file] [log] [blame]
Brian Osman7c979f52019-02-12 13:27:51 -05001/*
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
Brian Osman7c979f52019-02-12 13:27:51 -050010#include "SkCanvas.h"
11#include "SkColorData.h"
Brian Osman7c979f52019-02-12 13:27:51 -050012#include "SkPaint.h"
13#include "SkParticleAffector.h"
Brian Osman543d2e22019-02-15 14:29:38 -050014#include "SkParticleDrawable.h"
Brian Osman7c979f52019-02-12 13:27:51 -050015#include "SkReflected.h"
16#include "SkRSXform.h"
17
Brian Osman7c979f52019-02-12 13:27:51 -050018void SkParticleEffectParams::visitFields(SkFieldVisitor* v) {
19 v->visit("MaxCount", fMaxCount);
Brian Osman5c1f8eb2019-02-14 14:49:55 -050020 v->visit("Duration", fEffectDuration);
Brian Osman7c979f52019-02-12 13:27:51 -050021 v->visit("Rate", fRate);
22 v->visit("Life", fLifetime);
Brian Osman7c979f52019-02-12 13:27:51 -050023
Brian Osman543d2e22019-02-15 14:29:38 -050024 v->visit("Drawable", fDrawable);
Brian Osman7c979f52019-02-12 13:27:51 -050025
Brian Osman5c1f8eb2019-02-14 14:49:55 -050026 v->visit("Spawn", fSpawnAffectors);
27 v->visit("Update", fUpdateAffectors);
Brian Osman7c979f52019-02-12 13:27:51 -050028}
29
Brian Osman5c1f8eb2019-02-14 14:49:55 -050030SkParticleEffect::SkParticleEffect(sk_sp<SkParticleEffectParams> params, const SkRandom& random)
Brian Osman7c979f52019-02-12 13:27:51 -050031 : fParams(std::move(params))
Brian Osman5c1f8eb2019-02-14 14:49:55 -050032 , fRandom(random)
33 , fLooping(false)
34 , fSpawnTime(-1.0)
Brian Osman7c979f52019-02-12 13:27:51 -050035 , fCount(0)
Brian Osman5c1f8eb2019-02-14 14:49:55 -050036 , fLastTime(-1.0)
Brian Osman7c979f52019-02-12 13:27:51 -050037 , fSpawnRemainder(0.0f) {
38 this->setCapacity(fParams->fMaxCount);
Brian Osman7c979f52019-02-12 13:27:51 -050039}
40
Kevin Lubick269fe892019-03-06 09:32:55 -050041void SkParticleEffect::start(double now, bool looping) {
Brian Osman5c1f8eb2019-02-14 14:49:55 -050042 fCount = 0;
Kevin Lubick269fe892019-03-06 09:32:55 -050043 fLastTime = fSpawnTime = now;
Brian Osman5c1f8eb2019-02-14 14:49:55 -050044 fSpawnRemainder = 0.0f;
45 fLooping = looping;
46}
47
Kevin Lubick269fe892019-03-06 09:32:55 -050048void SkParticleEffect::update(double now) {
49 if (!this->isAlive() || !fParams->fDrawable) {
50 return;
51 }
52
Brian Osmand8e1ee92019-02-20 14:33:49 -050053 float deltaTime = static_cast<float>(now - fLastTime);
Brian Osmanb77d5022019-03-06 11:08:48 -050054 if (deltaTime <= 0.0f) {
Brian Osmand8e1ee92019-02-20 14:33:49 -050055 return;
56 }
57 fLastTime = now;
58
Brian Osman7c979f52019-02-12 13:27:51 -050059 // Handle user edits to fMaxCount
60 if (fParams->fMaxCount != fCapacity) {
61 this->setCapacity(fParams->fMaxCount);
62 }
63
Brian Osmanbdcdf1a2019-03-04 10:55:22 -050064 float effectAge = static_cast<float>((now - fSpawnTime) / fParams->fEffectDuration);
65 effectAge = fLooping ? fmodf(effectAge, 1.0f) : SkTPin(effectAge, 0.0f, 1.0f);
66
Brian Osman7c979f52019-02-12 13:27:51 -050067 SkParticleUpdateParams updateParams;
68 updateParams.fDeltaTime = deltaTime;
Brian Osmanbdcdf1a2019-03-04 10:55:22 -050069 updateParams.fEffectAge = effectAge;
70
71 // During spawn, values that refer to kAge_Source get the *effect* age
72 updateParams.fAgeSource = SkParticleValue::kEffectAge_Source;
Brian Osman7c979f52019-02-12 13:27:51 -050073
Brian Osmand8e1ee92019-02-20 14:33:49 -050074 // Advance age for existing particles, and remove any that have reached their end of life
Brian Osman7c979f52019-02-12 13:27:51 -050075 for (int i = 0; i < fCount; ++i) {
Brian Osmand8e1ee92019-02-20 14:33:49 -050076 fParticles[i].fAge += fParticles[i].fInvLifetime * deltaTime;
77 if (fParticles[i].fAge > 1.0f) {
Brian Osman7c979f52019-02-12 13:27:51 -050078 // NOTE: This is fast, but doesn't preserve drawing order. Could be a problem...
Brian Osman125daa42019-02-20 12:25:20 -050079 fParticles[i] = fParticles[fCount - 1];
80 fStableRandoms[i] = fStableRandoms[fCount - 1];
Brian Osman7c979f52019-02-12 13:27:51 -050081 --i;
82 --fCount;
Brian Osman5c1f8eb2019-02-14 14:49:55 -050083 }
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);
Brian Osman3d76d1b2019-02-28 15:48:05 -050091 if (numToSpawn) {
Brian Osmanbdcdf1a2019-03-04 10:55:22 -050092 const int spawnBase = fCount;
Brian Osman5c1f8eb2019-02-14 14:49:55 -050093
Brian Osman8b6283f2019-02-14 16:55:21 -050094 for (int i = 0; i < numToSpawn; ++i) {
Brian Osman125daa42019-02-20 12:25:20 -050095 // Mutate our SkRandom so each particle definitely gets a different stable generator
96 fRandom.nextU();
Brian Osmanbdcdf1a2019-03-04 10:55:22 -050097 fParticles[fCount].fAge = 0.0f;
Brian Osman3d76d1b2019-02-28 15:48:05 -050098 fParticles[fCount].fPose.fPosition = { 0.0f, 0.0f };
99 fParticles[fCount].fPose.fHeading = { 0.0f, -1.0f };
100 fParticles[fCount].fPose.fScale = 1.0f;
Brian Osman125daa42019-02-20 12:25:20 -0500101 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;
Brian Osmane5d532e2019-02-26 14:58:40 -0500105 fParticles[fCount].fRandom = fStableRandoms[fCount] = fRandom;
Brian Osman8b6283f2019-02-14 16:55:21 -0500106 fCount++;
107 }
108
Brian Osmanbdcdf1a2019-03-04 10:55:22 -0500109 // Apply spawn affectors
Brian Osman14a67a32019-02-25 14:30:44 -0500110 for (auto affector : fParams->fSpawnAffectors) {
111 if (affector) {
Brian Osmanbdcdf1a2019-03-04 10:55:22 -0500112 affector->apply(updateParams, fParticles + spawnBase, numToSpawn);
Brian Osman5c1f8eb2019-02-14 14:49:55 -0500113 }
Brian Osman14a67a32019-02-25 14:30:44 -0500114 }
Brian Osmanbdcdf1a2019-03-04 10:55:22 -0500115
116 // Now compute particle lifetimes (so the curve can refer to spawn-computed source values)
117 for (int i = spawnBase; i < fCount; ++i) {
118 fParticles[i].fInvLifetime =
119 sk_ieee_float_divide(1.0f, fParams->fLifetime.eval(updateParams, fParticles[i]));
Brian Osman5c1f8eb2019-02-14 14:49:55 -0500120 }
121 }
122
Brian Osman125daa42019-02-20 12:25:20 -0500123 // Restore all stable random generators so update affectors get consistent behavior each frame
124 for (int i = 0; i < fCount; ++i) {
Brian Osmane5d532e2019-02-26 14:58:40 -0500125 fParticles[i].fRandom = fStableRandoms[i];
Brian Osman125daa42019-02-20 12:25:20 -0500126 }
127
Brian Osmanbdcdf1a2019-03-04 10:55:22 -0500128 // During update, values that refer to kAge_Source get the *particle* age
129 updateParams.fAgeSource = SkParticleValue::kParticleAge_Source;
130
Brian Osman5c1f8eb2019-02-14 14:49:55 -0500131 // Apply update rules
Brian Osman14a67a32019-02-25 14:30:44 -0500132 for (auto affector : fParams->fUpdateAffectors) {
133 if (affector) {
134 affector->apply(updateParams, fParticles, fCount);
Brian Osman7c979f52019-02-12 13:27:51 -0500135 }
Brian Osman14a67a32019-02-25 14:30:44 -0500136 }
Brian Osman7c979f52019-02-12 13:27:51 -0500137
Brian Osman14a67a32019-02-25 14:30:44 -0500138 // Do fixed-function update work (integration of position and orientation)
139 for (int i = 0; i < fCount; ++i) {
Brian Osman125daa42019-02-20 12:25:20 -0500140 fParticles[i].fPose.fPosition += fParticles[i].fVelocity.fLinear * deltaTime;
Brian Osman7c979f52019-02-12 13:27:51 -0500141
Brian Osman125daa42019-02-20 12:25:20 -0500142 SkScalar c, s = SkScalarSinCos(fParticles[i].fVelocity.fAngular * deltaTime, &c);
143 SkVector oldHeading = fParticles[i].fPose.fHeading;
144 fParticles[i].fPose.fHeading = { oldHeading.fX * c - oldHeading.fY * s,
145 oldHeading.fX * s + oldHeading.fY * c };
Brian Osman7c979f52019-02-12 13:27:51 -0500146 }
Brian Osman5c1f8eb2019-02-14 14:49:55 -0500147
148 // Mark effect as dead if we've reached the end (and are not looping)
149 if (!fLooping && (now - fSpawnTime) > fParams->fEffectDuration) {
150 fSpawnTime = -1.0;
151 }
Brian Osman7c979f52019-02-12 13:27:51 -0500152}
153
154void SkParticleEffect::draw(SkCanvas* canvas) {
Brian Osman543d2e22019-02-15 14:29:38 -0500155 if (this->isAlive() && fParams->fDrawable) {
Brian Osman5c1f8eb2019-02-14 14:49:55 -0500156 SkPaint paint;
157 paint.setFilterQuality(SkFilterQuality::kMedium_SkFilterQuality);
Brian Osman125daa42019-02-20 12:25:20 -0500158 fParams->fDrawable->draw(canvas, fParticles.get(), fCount, &paint);
Brian Osman5c1f8eb2019-02-14 14:49:55 -0500159 }
Brian Osman7c979f52019-02-12 13:27:51 -0500160}
161
162void SkParticleEffect::setCapacity(int capacity) {
163 fParticles.realloc(capacity);
Brian Osman125daa42019-02-20 12:25:20 -0500164 fStableRandoms.realloc(capacity);
Brian Osman7c979f52019-02-12 13:27:51 -0500165
166 fCapacity = capacity;
167 fCount = SkTMin(fCount, fCapacity);
168}
Brian Osmanbdcdf1a2019-03-04 10:55:22 -0500169
170constexpr SkFieldVisitor::EnumStringMapping gValueSourceMapping[] = {
171 { SkParticleValue::kAge_Source, "Age" },
172 { SkParticleValue::kRandom_Source, "Random" },
173 { SkParticleValue::kParticleAge_Source, "ParticleAge" },
174 { SkParticleValue::kEffectAge_Source, "EffectAge" },
175 { SkParticleValue::kPositionX_Source, "PositionX" },
176 { SkParticleValue::kPositionY_Source, "PositionY" },
177 { SkParticleValue::kHeadingX_Source, "HeadingX" },
178 { SkParticleValue::kHeadingY_Source, "HeadingY" },
179 { SkParticleValue::kScale_Source, "Scale" },
180 { SkParticleValue::kVelocityX_Source, "VelocityX" },
181 { SkParticleValue::kVelocityY_Source, "VelocityY" },
182 { SkParticleValue::kRotation_Source, "Rotation" },
183 { SkParticleValue::kColorR_Source, "ColorR" },
184 { SkParticleValue::kColorG_Source, "ColorG" },
185 { SkParticleValue::kColorB_Source, "ColorB" },
186 { SkParticleValue::kColorA_Source, "ColorA" },
187 { SkParticleValue::kSpriteFrame_Source, "SpriteFrame" },
188};
189
190constexpr SkFieldVisitor::EnumStringMapping gValueTileModeMapping[] = {
191 { SkParticleValue::kClamp_TileMode, "Clamp" },
192 { SkParticleValue::kRepeat_TileMode, "Repeat" },
193 { SkParticleValue::kMirror_TileMode, "Mirror" },
194};
195
196static bool source_needs_frame(int source) {
197 switch (source) {
198 case SkParticleValue::kHeadingX_Source:
199 case SkParticleValue::kHeadingY_Source:
200 case SkParticleValue::kVelocityX_Source:
201 case SkParticleValue::kVelocityY_Source:
202 return true;
203 default:
204 return false;
205 }
206}
207
208void SkParticleValue::visitFields(SkFieldVisitor* v) {
209 v->visit("Source", fSource, gValueSourceMapping, SK_ARRAY_COUNT(gValueSourceMapping));
210 if (source_needs_frame(fSource)) {
211 v->visit("Frame", fFrame, gParticleFrameMapping, SK_ARRAY_COUNT(gParticleFrameMapping));
212 }
213 v->visit("TileMode", fTileMode, gValueTileModeMapping, SK_ARRAY_COUNT(gValueTileModeMapping));
214 v->visit("Left", fLeft);
215 v->visit("Right", fRight);
216
217 // Re-compute cached evaluation parameters
218 fScale = sk_float_isfinite(1.0f / (fRight - fLeft)) ? 1.0f / (fRight - fLeft) : 0;
219 fBias = -fLeft * fScale;
220}
221
222float SkParticleValue::getSourceValue(const SkParticleUpdateParams& params,
223 SkParticleState& ps) const {
224 switch ((kAge_Source == fSource) ? params.fAgeSource : fSource) {
225 // Do all the simple (non-frame-dependent) sources first:
226 case kRandom_Source: return ps.fRandom.nextF();
227 case kParticleAge_Source: return ps.fAge;
228 case kEffectAge_Source: return params.fEffectAge;
229
230 case kPositionX_Source: return ps.fPose.fPosition.fX;
231 case kPositionY_Source: return ps.fPose.fPosition.fY;
232 case kScale_Source: return ps.fPose.fScale;
233 case kRotation_Source: return ps.fVelocity.fAngular;
234
235 case kColorR_Source: return ps.fColor.fR;
236 case kColorG_Source: return ps.fColor.fG;
237 case kColorB_Source: return ps.fColor.fB;
238 case kColorA_Source: return ps.fColor.fA;
239 case kSpriteFrame_Source: return ps.fFrame;
240 }
241
242 SkASSERT(source_needs_frame(fSource));
243 SkVector frameUp = ps.getFrameHeading(static_cast<SkParticleFrame>(fFrame));
244 SkVector frameRight = { -frameUp.fY, frameUp.fX };
245
246 switch (fSource) {
247 case kHeadingX_Source: return ps.fPose.fHeading.dot(frameRight);
248 case kHeadingY_Source: return ps.fPose.fHeading.dot(frameUp);
249 case kVelocityX_Source: return ps.fVelocity.fLinear.dot(frameRight);
250 case kVelocityY_Source: return ps.fVelocity.fLinear.dot(frameUp);
251 }
252
253 SkDEBUGFAIL("Unreachable");
254 return 0.0f;
255}
256
257float SkParticleValue::eval(const SkParticleUpdateParams& params, SkParticleState& ps) const {
258 float v = this->getSourceValue(params, ps);
259 v = (v * fScale) + fBias;
260
261 switch (fTileMode) {
262 case kClamp_TileMode:
263 v = SkTPin(v, 0.0f, 1.0f);
264 break;
265 case kRepeat_TileMode:
266 v = sk_float_mod(v, 1.0f);
267 if (v < 0) {
268 v += 1.0f;
269 }
270 break;
271 case kMirror_TileMode:
272 v = sk_float_mod(v, 2.0f);
273 if (v < 0) {
274 v += 2.0f;
275 }
276 v = 1.0f - sk_float_abs(v - 1.0f);
277 break;
278 }
279
280 return v;
281}