blob: 1cd0eab54171601ba5045385f024b7f24f0dd54c [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 Osmanca4ed902019-03-08 13:34:12 -050095 // Mutate our SkRandom so each particle definitely gets a different generator
Brian Osman125daa42019-02-20 12:25:20 -050096 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 Osmanca4ed902019-03-08 13:34:12 -0500105 fParticles[fCount].fRandom = 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
Brian Osmanca4ed902019-03-08 13:34:12 -0500116 // Now stash copies of the random generators and compute particle lifetimes
117 // (so the curve can refer to spawn-computed source values)
Brian Osmanbdcdf1a2019-03-04 10:55:22 -0500118 for (int i = spawnBase; i < fCount; ++i) {
119 fParticles[i].fInvLifetime =
120 sk_ieee_float_divide(1.0f, fParams->fLifetime.eval(updateParams, fParticles[i]));
Brian Osmanca4ed902019-03-08 13:34:12 -0500121 fStableRandoms[i] = fParticles[i].fRandom;
Brian Osman5c1f8eb2019-02-14 14:49:55 -0500122 }
123 }
124
Brian Osman125daa42019-02-20 12:25:20 -0500125 // Restore all stable random generators so update affectors get consistent behavior each frame
126 for (int i = 0; i < fCount; ++i) {
Brian Osmane5d532e2019-02-26 14:58:40 -0500127 fParticles[i].fRandom = fStableRandoms[i];
Brian Osman125daa42019-02-20 12:25:20 -0500128 }
129
Brian Osmanbdcdf1a2019-03-04 10:55:22 -0500130 // During update, values that refer to kAge_Source get the *particle* age
131 updateParams.fAgeSource = SkParticleValue::kParticleAge_Source;
132
Brian Osman5c1f8eb2019-02-14 14:49:55 -0500133 // Apply update rules
Brian Osman14a67a32019-02-25 14:30:44 -0500134 for (auto affector : fParams->fUpdateAffectors) {
135 if (affector) {
136 affector->apply(updateParams, fParticles, fCount);
Brian Osman7c979f52019-02-12 13:27:51 -0500137 }
Brian Osman14a67a32019-02-25 14:30:44 -0500138 }
Brian Osman7c979f52019-02-12 13:27:51 -0500139
Brian Osman14a67a32019-02-25 14:30:44 -0500140 // Do fixed-function update work (integration of position and orientation)
141 for (int i = 0; i < fCount; ++i) {
Brian Osman125daa42019-02-20 12:25:20 -0500142 fParticles[i].fPose.fPosition += fParticles[i].fVelocity.fLinear * deltaTime;
Brian Osman7c979f52019-02-12 13:27:51 -0500143
Brian Osman4428f2c2019-04-02 10:59:28 -0400144 SkScalar s = SkScalarSin(fParticles[i].fVelocity.fAngular * deltaTime),
145 c = SkScalarCos(fParticles[i].fVelocity.fAngular * deltaTime);
Brian Osman125daa42019-02-20 12:25:20 -0500146 SkVector oldHeading = fParticles[i].fPose.fHeading;
147 fParticles[i].fPose.fHeading = { oldHeading.fX * c - oldHeading.fY * s,
148 oldHeading.fX * s + oldHeading.fY * c };
Brian Osman7c979f52019-02-12 13:27:51 -0500149 }
Brian Osman5c1f8eb2019-02-14 14:49:55 -0500150
151 // Mark effect as dead if we've reached the end (and are not looping)
152 if (!fLooping && (now - fSpawnTime) > fParams->fEffectDuration) {
153 fSpawnTime = -1.0;
154 }
Brian Osman7c979f52019-02-12 13:27:51 -0500155}
156
157void SkParticleEffect::draw(SkCanvas* canvas) {
Brian Osman543d2e22019-02-15 14:29:38 -0500158 if (this->isAlive() && fParams->fDrawable) {
Brian Osman5c1f8eb2019-02-14 14:49:55 -0500159 SkPaint paint;
160 paint.setFilterQuality(SkFilterQuality::kMedium_SkFilterQuality);
Brian Osman125daa42019-02-20 12:25:20 -0500161 fParams->fDrawable->draw(canvas, fParticles.get(), fCount, &paint);
Brian Osman5c1f8eb2019-02-14 14:49:55 -0500162 }
Brian Osman7c979f52019-02-12 13:27:51 -0500163}
164
165void SkParticleEffect::setCapacity(int capacity) {
166 fParticles.realloc(capacity);
Brian Osman125daa42019-02-20 12:25:20 -0500167 fStableRandoms.realloc(capacity);
Brian Osman7c979f52019-02-12 13:27:51 -0500168
169 fCapacity = capacity;
170 fCount = SkTMin(fCount, fCapacity);
171}
Brian Osmanbdcdf1a2019-03-04 10:55:22 -0500172
173constexpr SkFieldVisitor::EnumStringMapping gValueSourceMapping[] = {
174 { SkParticleValue::kAge_Source, "Age" },
175 { SkParticleValue::kRandom_Source, "Random" },
176 { SkParticleValue::kParticleAge_Source, "ParticleAge" },
177 { SkParticleValue::kEffectAge_Source, "EffectAge" },
178 { SkParticleValue::kPositionX_Source, "PositionX" },
179 { SkParticleValue::kPositionY_Source, "PositionY" },
180 { SkParticleValue::kHeadingX_Source, "HeadingX" },
181 { SkParticleValue::kHeadingY_Source, "HeadingY" },
182 { SkParticleValue::kScale_Source, "Scale" },
183 { SkParticleValue::kVelocityX_Source, "VelocityX" },
184 { SkParticleValue::kVelocityY_Source, "VelocityY" },
185 { SkParticleValue::kRotation_Source, "Rotation" },
186 { SkParticleValue::kColorR_Source, "ColorR" },
187 { SkParticleValue::kColorG_Source, "ColorG" },
188 { SkParticleValue::kColorB_Source, "ColorB" },
189 { SkParticleValue::kColorA_Source, "ColorA" },
190 { SkParticleValue::kSpriteFrame_Source, "SpriteFrame" },
191};
192
193constexpr SkFieldVisitor::EnumStringMapping gValueTileModeMapping[] = {
194 { SkParticleValue::kClamp_TileMode, "Clamp" },
195 { SkParticleValue::kRepeat_TileMode, "Repeat" },
196 { SkParticleValue::kMirror_TileMode, "Mirror" },
197};
198
199static bool source_needs_frame(int source) {
200 switch (source) {
201 case SkParticleValue::kHeadingX_Source:
202 case SkParticleValue::kHeadingY_Source:
203 case SkParticleValue::kVelocityX_Source:
204 case SkParticleValue::kVelocityY_Source:
205 return true;
206 default:
207 return false;
208 }
209}
210
211void SkParticleValue::visitFields(SkFieldVisitor* v) {
212 v->visit("Source", fSource, gValueSourceMapping, SK_ARRAY_COUNT(gValueSourceMapping));
213 if (source_needs_frame(fSource)) {
214 v->visit("Frame", fFrame, gParticleFrameMapping, SK_ARRAY_COUNT(gParticleFrameMapping));
215 }
216 v->visit("TileMode", fTileMode, gValueTileModeMapping, SK_ARRAY_COUNT(gValueTileModeMapping));
217 v->visit("Left", fLeft);
218 v->visit("Right", fRight);
219
220 // Re-compute cached evaluation parameters
221 fScale = sk_float_isfinite(1.0f / (fRight - fLeft)) ? 1.0f / (fRight - fLeft) : 0;
222 fBias = -fLeft * fScale;
223}
224
225float SkParticleValue::getSourceValue(const SkParticleUpdateParams& params,
226 SkParticleState& ps) const {
227 switch ((kAge_Source == fSource) ? params.fAgeSource : fSource) {
228 // Do all the simple (non-frame-dependent) sources first:
229 case kRandom_Source: return ps.fRandom.nextF();
230 case kParticleAge_Source: return ps.fAge;
231 case kEffectAge_Source: return params.fEffectAge;
232
233 case kPositionX_Source: return ps.fPose.fPosition.fX;
234 case kPositionY_Source: return ps.fPose.fPosition.fY;
235 case kScale_Source: return ps.fPose.fScale;
236 case kRotation_Source: return ps.fVelocity.fAngular;
237
238 case kColorR_Source: return ps.fColor.fR;
239 case kColorG_Source: return ps.fColor.fG;
240 case kColorB_Source: return ps.fColor.fB;
241 case kColorA_Source: return ps.fColor.fA;
242 case kSpriteFrame_Source: return ps.fFrame;
243 }
244
245 SkASSERT(source_needs_frame(fSource));
246 SkVector frameUp = ps.getFrameHeading(static_cast<SkParticleFrame>(fFrame));
247 SkVector frameRight = { -frameUp.fY, frameUp.fX };
248
249 switch (fSource) {
250 case kHeadingX_Source: return ps.fPose.fHeading.dot(frameRight);
251 case kHeadingY_Source: return ps.fPose.fHeading.dot(frameUp);
252 case kVelocityX_Source: return ps.fVelocity.fLinear.dot(frameRight);
253 case kVelocityY_Source: return ps.fVelocity.fLinear.dot(frameUp);
254 }
255
256 SkDEBUGFAIL("Unreachable");
257 return 0.0f;
258}
259
260float SkParticleValue::eval(const SkParticleUpdateParams& params, SkParticleState& ps) const {
261 float v = this->getSourceValue(params, ps);
262 v = (v * fScale) + fBias;
263
264 switch (fTileMode) {
265 case kClamp_TileMode:
266 v = SkTPin(v, 0.0f, 1.0f);
267 break;
268 case kRepeat_TileMode:
269 v = sk_float_mod(v, 1.0f);
270 if (v < 0) {
271 v += 1.0f;
272 }
273 break;
274 case kMirror_TileMode:
275 v = sk_float_mod(v, 2.0f);
276 if (v < 0) {
277 v += 2.0f;
278 }
279 v = 1.0f - sk_float_abs(v - 1.0f);
280 break;
281 }
282
283 return v;
284}