1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQuick module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #ifndef PARTICLESYSTEM_H |
41 | #define PARTICLESYSTEM_H |
42 | |
43 | // |
44 | // W A R N I N G |
45 | // ------------- |
46 | // |
47 | // This file is not part of the Qt API. It exists purely as an |
48 | // implementation detail. This header file may change from version to |
49 | // version without notice, or even be removed. |
50 | // |
51 | // We mean it. |
52 | // |
53 | |
54 | #include <QtQuick/QQuickItem> |
55 | #include <QElapsedTimer> |
56 | #include <QVector> |
57 | #include <QHash> |
58 | #include <QPointer> |
59 | #include <private/qquicksprite_p.h> |
60 | #include <QAbstractAnimation> |
61 | #include <QtQml/qqml.h> |
62 | #include <private/qv4util_p.h> |
63 | #include <private/qv4global_p.h> |
64 | #include <private/qv4staticvalue_p.h> |
65 | #include "qtquickparticlesglobal_p.h" |
66 | #include "qquickparticleflatset_p.h" |
67 | |
68 | QT_BEGIN_NAMESPACE |
69 | |
70 | template<class T, int Prealloc> |
71 | class QQuickParticleVarLengthArray: public QVarLengthArray<T, Prealloc> |
72 | { |
73 | public: |
74 | void insert(const T &element) |
75 | { |
76 | if (!this->contains(element)) { |
77 | this->append(element); |
78 | } |
79 | } |
80 | |
81 | bool removeOne(const T &element) |
82 | { |
83 | for (int i = 0; i < this->size(); ++i) { |
84 | if (this->at(i) == element) { |
85 | this->remove(i); |
86 | return true; |
87 | } |
88 | } |
89 | |
90 | return false; |
91 | } |
92 | }; |
93 | |
94 | class QQuickParticleSystem; |
95 | class QQuickParticleAffector; |
96 | class QQuickParticleEmitter; |
97 | class QQuickParticlePainter; |
98 | class QQuickParticleData; |
99 | class QQuickParticleSystemAnimation; |
100 | class QQuickStochasticEngine; |
101 | class QQuickSprite; |
102 | class QQuickV4ParticleData; |
103 | class QQuickParticleGroup; |
104 | class QQuickImageParticle; |
105 | |
106 | struct QQuickParticleDataHeapNode{ |
107 | int time;//in ms |
108 | QSet<QQuickParticleData*> data;//Set ptrs instead? |
109 | }; |
110 | |
111 | class Q_QUICKPARTICLES_PRIVATE_EXPORT QQuickParticleDataHeap { |
112 | //Idea is to do a binary heap, but which also stores a set of int,Node* so that if the int already exists, you can |
113 | //add it to the data* list. Pops return the whole list at once. |
114 | public: |
115 | QQuickParticleDataHeap(); |
116 | void insert(QQuickParticleData* data); |
117 | void insertTimed(QQuickParticleData* data, int time); |
118 | |
119 | int top(); |
120 | |
121 | QSet<QQuickParticleData*> pop(); |
122 | |
123 | void clear(); |
124 | |
125 | bool contains(QQuickParticleData*);//O(n), for debugging purposes only |
126 | private: |
127 | void grow(); |
128 | void swap(int, int); |
129 | void bubbleUp(int); |
130 | void bubbleDown(int); |
131 | int m_size; |
132 | int m_end; |
133 | QQuickParticleDataHeapNode m_tmp; |
134 | QVector<QQuickParticleDataHeapNode> m_data; |
135 | QHash<int,int> m_lookups; |
136 | }; |
137 | |
138 | class Q_QUICKPARTICLES_PRIVATE_EXPORT QQuickParticleGroupData { |
139 | class FreeList |
140 | { |
141 | public: |
142 | FreeList() {} |
143 | |
144 | void resize(int newSize) |
145 | { |
146 | Q_ASSERT(newSize >= 0); |
147 | int oldSize = isUnused.size(); |
148 | isUnused.resize(newSize, newValue: true); |
149 | if (newSize > oldSize) { |
150 | if (firstUnused == UINT_MAX) { |
151 | firstUnused = oldSize; |
152 | } else { |
153 | firstUnused = std::min(a: firstUnused, b: unsigned(oldSize)); |
154 | } |
155 | } else if (firstUnused >= unsigned(newSize)) { |
156 | firstUnused = UINT_MAX; |
157 | } |
158 | } |
159 | |
160 | void free(int index) |
161 | { |
162 | isUnused.setBit(index); |
163 | firstUnused = std::min(a: firstUnused, b: unsigned(index)); |
164 | --allocated; |
165 | } |
166 | |
167 | int count() const |
168 | { return allocated; } |
169 | |
170 | bool hasUnusedEntries() const |
171 | { return firstUnused != UINT_MAX; } |
172 | |
173 | int alloc() |
174 | { |
175 | if (hasUnusedEntries()) { |
176 | int nextFree = firstUnused; |
177 | isUnused.clearBit(idx: firstUnused); |
178 | firstUnused = isUnused.findNext(start: firstUnused, value: true, wrapAround: false); |
179 | if (firstUnused >= unsigned(isUnused.size())) { |
180 | firstUnused = UINT_MAX; |
181 | } |
182 | ++allocated; |
183 | return nextFree; |
184 | } else { |
185 | return -1; |
186 | } |
187 | } |
188 | |
189 | private: |
190 | QV4::BitVector isUnused; |
191 | unsigned firstUnused = UINT_MAX; |
192 | int allocated = 0; |
193 | }; |
194 | |
195 | public: // types |
196 | typedef int ID; |
197 | enum { InvalidID = -1, DefaultGroupID = 0 }; |
198 | |
199 | public: |
200 | QQuickParticleGroupData(const QString &name, QQuickParticleSystem* sys); |
201 | ~QQuickParticleGroupData(); |
202 | |
203 | int size() |
204 | { return m_size; } |
205 | |
206 | QString name(); |
207 | |
208 | bool isActive() { return freeList.count() > 0; } |
209 | |
210 | void setSize(int newSize); |
211 | |
212 | const ID index; |
213 | QQuickParticleVarLengthArray<QQuickParticlePainter*, 4> painters;//TODO: What if they are dynamically removed? |
214 | |
215 | //TODO: Refactor particle data list out into a separate class |
216 | QVector<QQuickParticleData*> data; |
217 | FreeList freeList; |
218 | QQuickParticleDataHeap dataHeap; |
219 | bool recycle(); //Force recycling round, returns true if all indexes are now reusable |
220 | |
221 | void initList(); |
222 | void kill(QQuickParticleData* d); |
223 | |
224 | //After calling this, initialize, then call prepareRecycler(d) |
225 | QQuickParticleData* newDatum(bool respectsLimits); |
226 | |
227 | //TODO: Find and clean up those that don't get added to the recycler (currently they get lost) |
228 | void prepareRecycler(QQuickParticleData* d); |
229 | |
230 | private: |
231 | int m_size; |
232 | QQuickParticleSystem* m_system; |
233 | // Only used in recycle() for tracking of alive particles after latest recycling round |
234 | QVector<QQuickParticleData*> m_latestAliveParticles; |
235 | }; |
236 | |
237 | struct Color4ub { |
238 | uchar r; |
239 | uchar g; |
240 | uchar b; |
241 | uchar a; |
242 | }; |
243 | |
244 | class Q_QUICKPARTICLES_PRIVATE_EXPORT QQuickParticleData { |
245 | public: |
246 | //TODO: QObject like memory management (without the cost, just attached to system) |
247 | QQuickParticleData(); |
248 | ~QQuickParticleData(); |
249 | |
250 | QQuickParticleData(const QQuickParticleData &other); |
251 | QQuickParticleData &operator=(const QQuickParticleData &other); |
252 | |
253 | //Convenience functions for working backwards, because parameters are from the start of particle life |
254 | //If setting multiple parameters at once, doing the conversion yourself will be faster. |
255 | |
256 | //sets the x accleration without affecting the instantaneous x velocity or position |
257 | void setInstantaneousAX(float ax, QQuickParticleSystem *particleSystem); |
258 | //sets the x velocity without affecting the instantaneous x postion |
259 | void setInstantaneousVX(float vx, QQuickParticleSystem *particleSystem); |
260 | //sets the instantaneous x postion |
261 | void setInstantaneousX(float x, QQuickParticleSystem *particleSystem); |
262 | //sets the y accleration without affecting the instantaneous y velocity or position |
263 | void setInstantaneousAY(float ay, QQuickParticleSystem *particleSystem); |
264 | //sets the y velocity without affecting the instantaneous y postion |
265 | void setInstantaneousVY(float vy, QQuickParticleSystem *particleSystem); |
266 | //sets the instantaneous Y postion |
267 | void setInstantaneousY(float y, QQuickParticleSystem *particleSystem); |
268 | |
269 | //TODO: Slight caching? |
270 | float curX(QQuickParticleSystem *particleSystem) const; |
271 | float curVX(QQuickParticleSystem *particleSystem) const; |
272 | float curAX() const { return ax; } |
273 | float curAX(QQuickParticleSystem *) const { return ax; } // used by the macros in qquickv4particledata.cpp |
274 | float curY(QQuickParticleSystem *particleSystem) const; |
275 | float curVY(QQuickParticleSystem *particleSystem) const; |
276 | float curAY() const { return ay; } |
277 | float curAY(QQuickParticleSystem *) const { return ay; } // used by the macros in qquickv4particledata.cpp |
278 | |
279 | int index; |
280 | int systemIndex; |
281 | |
282 | //General Position Stuff |
283 | float x; |
284 | float y; |
285 | float t; |
286 | float lifeSpan; |
287 | float size; |
288 | float endSize; |
289 | float vx; |
290 | float vy; |
291 | float ax; |
292 | float ay; |
293 | |
294 | //Painter-specific stuff, now universally shared |
295 | //Used by ImageParticle color mode |
296 | Color4ub color; |
297 | //Used by ImageParticle deform mode |
298 | float xx; |
299 | float xy; |
300 | float yx; |
301 | float yy; |
302 | float rotation; |
303 | float rotationVelocity; |
304 | float autoRotate;//Assume that GPUs prefer floats to bools |
305 | //Used by ImageParticle Sprite mode |
306 | float animIdx; |
307 | float frameDuration; |
308 | float frameAt;//Used for duration -1 |
309 | float frameCount; |
310 | float animT; |
311 | float animX; |
312 | float animY; |
313 | float animWidth; |
314 | float animHeight; |
315 | |
316 | QQuickParticleGroupData::ID groupId; |
317 | |
318 | //Used by ImageParticle data shadowing |
319 | QQuickImageParticle* colorOwner; |
320 | QQuickImageParticle* rotationOwner; |
321 | QQuickImageParticle* deformationOwner; |
322 | QQuickImageParticle* animationOwner; |
323 | |
324 | //Used by ItemParticle |
325 | QQuickItem* delegate; |
326 | int modelIndex; |
327 | //Used by custom affectors |
328 | float update; |
329 | //Used by CustomParticle |
330 | float r; |
331 | |
332 | // 4 bytes wasted |
333 | |
334 | |
335 | void debugDump(QQuickParticleSystem *particleSystem) const; |
336 | bool stillAlive(QQuickParticleSystem *particleSystem) const; //Only checks end, because usually that's all you need and it's a little faster. |
337 | bool alive(QQuickParticleSystem *particleSystem) const; |
338 | float lifeLeft(QQuickParticleSystem *particleSystem) const; |
339 | |
340 | float curSize(QQuickParticleSystem *particleSystem) const; |
341 | void clone(const QQuickParticleData& other);//Not =, leaves meta-data like index |
342 | QV4::ReturnedValue v4Value(QQuickParticleSystem *particleSystem); |
343 | void extendLife(float time, QQuickParticleSystem *particleSystem); |
344 | |
345 | static inline Q_DECL_CONSTEXPR float EPSILON() Q_DECL_NOTHROW { return 0.001f; } |
346 | |
347 | private: |
348 | QQuickV4ParticleData* v8Datum; |
349 | }; |
350 | |
351 | class Q_QUICKPARTICLES_PRIVATE_EXPORT QQuickParticleSystem : public QQuickItem |
352 | { |
353 | Q_OBJECT |
354 | Q_PROPERTY(bool running READ isRunning WRITE setRunning NOTIFY runningChanged) |
355 | Q_PROPERTY(bool paused READ isPaused WRITE setPaused NOTIFY pausedChanged) |
356 | Q_PROPERTY(bool empty READ isEmpty NOTIFY emptyChanged) |
357 | QML_NAMED_ELEMENT(ParticleSystem) |
358 | |
359 | public: |
360 | explicit QQuickParticleSystem(QQuickItem *parent = nullptr); |
361 | ~QQuickParticleSystem(); |
362 | |
363 | bool isRunning() const |
364 | { |
365 | return m_running; |
366 | } |
367 | |
368 | int count(){ return particleCount; } |
369 | |
370 | static const int maxLife = 600000; |
371 | |
372 | Q_SIGNALS: |
373 | |
374 | void systemInitialized(); |
375 | void runningChanged(bool arg); |
376 | void pausedChanged(bool arg); |
377 | void emptyChanged(bool arg); |
378 | |
379 | public Q_SLOTS: |
380 | void start(){setRunning(true);} |
381 | void stop(){setRunning(false);} |
382 | void restart(){setRunning(false);setRunning(true);} |
383 | void pause(){setPaused(true);} |
384 | void resume(){setPaused(false);} |
385 | |
386 | void reset(); |
387 | void setRunning(bool arg); |
388 | void setPaused(bool arg); |
389 | |
390 | virtual int duration() const { return -1; } |
391 | |
392 | |
393 | protected: |
394 | //This one only once per frame (effectively) |
395 | void componentComplete() override; |
396 | |
397 | private Q_SLOTS: |
398 | void emittersChanged(); |
399 | void loadPainter(QQuickParticlePainter *p); |
400 | void createEngine(); //Not invoked by sprite engine, unlike Sprite uses |
401 | void particleStateChange(int idx); |
402 | |
403 | public: |
404 | //These can be called multiple times per frame, performance critical |
405 | void emitParticle(QQuickParticleData* p, QQuickParticleEmitter *particleEmitter); |
406 | QQuickParticleData* newDatum(int groupId, bool respectLimits = true, int sysIdx = -1); |
407 | void finishNewDatum(QQuickParticleData*); |
408 | void moveGroups(QQuickParticleData *d, int newGIdx); |
409 | int nextSystemIndex(); |
410 | |
411 | //This one only once per painter per frame |
412 | int systemSync(QQuickParticlePainter* p); |
413 | |
414 | //Data members here for ease of related class and auto-test usage. Not "public" API. TODO: d_ptrize |
415 | QtQuickParticlesPrivate::QFlatSet<QQuickParticleData*> needsReset; |
416 | QVector<QQuickParticleData*> bySysIdx; //Another reference to the data (data owned by group), but by sysIdx |
417 | QQuickStochasticEngine* stateEngine; |
418 | |
419 | QHash<QString, int> groupIds; |
420 | QVarLengthArray<QQuickParticleGroupData*, 32> groupData; |
421 | int nextFreeGroupId; |
422 | int registerParticleGroupData(const QString &name, QQuickParticleGroupData *pgd); |
423 | |
424 | //Also only here for auto-test usage |
425 | void updateCurrentTime( int currentTime ); |
426 | QQuickParticleSystemAnimation* m_animation; |
427 | bool m_running; |
428 | bool m_debugMode; |
429 | |
430 | int timeInt; |
431 | bool initialized; |
432 | int particleCount; |
433 | |
434 | void registerParticlePainter(QQuickParticlePainter* p); |
435 | void registerParticleEmitter(QQuickParticleEmitter* e); |
436 | void finishRegisteringParticleEmitter(QQuickParticleEmitter *e); |
437 | void registerParticleAffector(QQuickParticleAffector* a); |
438 | void registerParticleGroup(QQuickParticleGroup* g); |
439 | |
440 | static void statePropertyRedirect(QQmlListProperty<QObject> *prop, QObject *value); |
441 | static void stateRedirect(QQuickParticleGroup* group, QQuickParticleSystem* sys, QObject *value); |
442 | bool isPaused() const |
443 | { |
444 | return m_paused; |
445 | } |
446 | |
447 | bool isEmpty() const |
448 | { |
449 | return m_empty; |
450 | } |
451 | |
452 | private: |
453 | void searchNextFreeGroupId(); |
454 | |
455 | private: |
456 | void initializeSystem(); |
457 | void initGroups(); |
458 | QList<QPointer<QQuickParticleEmitter> > m_emitters; |
459 | QList<QPointer<QQuickParticleAffector> > m_affectors; |
460 | QList<QPointer<QQuickParticlePainter> > m_painters; |
461 | QList<QPointer<QQuickParticlePainter> > m_syncList; |
462 | QList<QQuickParticleGroup*> m_groups; |
463 | int m_nextIndex; |
464 | QSet<int> m_reusableIndexes; |
465 | bool m_componentComplete; |
466 | |
467 | bool m_paused; |
468 | bool m_allDead; |
469 | bool m_empty; |
470 | }; |
471 | |
472 | // Internally, this animation drives all the timing. Painters sync up in their updatePaintNode |
473 | class QQuickParticleSystemAnimation : public QAbstractAnimation |
474 | { |
475 | Q_OBJECT |
476 | public: |
477 | QQuickParticleSystemAnimation(QQuickParticleSystem* system) |
478 | : QAbstractAnimation(static_cast<QObject*>(system)), m_system(system) |
479 | { } |
480 | protected: |
481 | void updateCurrentTime(int t) override |
482 | { |
483 | m_system->updateCurrentTime(currentTime: t); |
484 | } |
485 | |
486 | int duration() const override |
487 | { |
488 | return -1; |
489 | } |
490 | |
491 | private: |
492 | QQuickParticleSystem* m_system; |
493 | }; |
494 | |
495 | inline void QQuickParticleData::setInstantaneousAX(float ax, QQuickParticleSystem* particleSystem) |
496 | { |
497 | float t = (particleSystem->timeInt / 1000.0f) - this->t; |
498 | float t_sq = t * t; |
499 | float vx = (this->vx + t * this->ax) - t * ax; |
500 | float ex = this->x + this->vx * t + 0.5f * this->ax * t_sq; |
501 | float x = ex - t * vx - 0.5f * t_sq * ax; |
502 | |
503 | this->ax = ax; |
504 | this->vx = vx; |
505 | this->x = x; |
506 | } |
507 | |
508 | inline void QQuickParticleData::setInstantaneousVX(float vx, QQuickParticleSystem* particleSystem) |
509 | { |
510 | float t = (particleSystem->timeInt / 1000.0f) - this->t; |
511 | float t_sq = t * t; |
512 | float evx = vx - t * this->ax; |
513 | float ex = this->x + this->vx * t + 0.5f * this->ax * t_sq; |
514 | float x = ex - t * evx - 0.5f * t_sq * this->ax; |
515 | |
516 | this->vx = evx; |
517 | this->x = x; |
518 | } |
519 | |
520 | inline void QQuickParticleData::setInstantaneousX(float x, QQuickParticleSystem* particleSystem) |
521 | { |
522 | float t = (particleSystem->timeInt / 1000.0f) - this->t; |
523 | float t_sq = t * t; |
524 | this->x = x - t * this->vx - 0.5f * t_sq * this->ax; |
525 | } |
526 | |
527 | inline void QQuickParticleData::setInstantaneousAY(float ay, QQuickParticleSystem* particleSystem) |
528 | { |
529 | float t = (particleSystem->timeInt / 1000.0f) - this->t; |
530 | float t_sq = t * t; |
531 | float vy = (this->vy + t * this->ay) - t * ay; |
532 | float ey = this->y + this->vy * t + 0.5f * this->ay * t_sq; |
533 | float y = ey - t * vy - 0.5f * t_sq * ay; |
534 | |
535 | this->ay = ay; |
536 | this->vy = vy; |
537 | this->y = y; |
538 | } |
539 | |
540 | inline void QQuickParticleData::setInstantaneousVY(float vy, QQuickParticleSystem* particleSystem) |
541 | { |
542 | float t = (particleSystem->timeInt / 1000.0f) - this->t; |
543 | float t_sq = t * t; |
544 | float evy = vy - t * this->ay; |
545 | float ey = this->y + this->vy * t + 0.5f * this->ay * t_sq; |
546 | float y = ey - t*evy - 0.5f * t_sq * this->ay; |
547 | |
548 | this->vy = evy; |
549 | this->y = y; |
550 | } |
551 | |
552 | inline void QQuickParticleData::setInstantaneousY(float y, QQuickParticleSystem *particleSystem) |
553 | { |
554 | float t = (particleSystem->timeInt / 1000.0f) - this->t; |
555 | float t_sq = t * t; |
556 | this->y = y - t * this->vy - 0.5f * t_sq * this->ay; |
557 | } |
558 | |
559 | inline float QQuickParticleData::curX(QQuickParticleSystem *particleSystem) const |
560 | { |
561 | float t = (particleSystem->timeInt / 1000.0f) - this->t; |
562 | float t_sq = t * t; |
563 | return this->x + this->vx * t + 0.5f * this->ax * t_sq; |
564 | } |
565 | |
566 | inline float QQuickParticleData::curVX(QQuickParticleSystem *particleSystem) const |
567 | { |
568 | float t = (particleSystem->timeInt / 1000.0f) - this->t; |
569 | return this->vx + t * this->ax; |
570 | } |
571 | |
572 | inline float QQuickParticleData::curY(QQuickParticleSystem *particleSystem) const |
573 | { |
574 | float t = (particleSystem->timeInt / 1000.0f) - this->t; |
575 | float t_sq = t * t; |
576 | return y + vy * t + 0.5f * ay * t_sq; |
577 | } |
578 | |
579 | inline float QQuickParticleData::curVY(QQuickParticleSystem *particleSystem) const |
580 | { |
581 | float t = (particleSystem->timeInt / 1000.0f) - this->t; |
582 | return vy + t*ay; |
583 | } |
584 | |
585 | inline bool QQuickParticleData::stillAlive(QQuickParticleSystem* system) const |
586 | { |
587 | if (!system) |
588 | return false; |
589 | return (t + lifeSpan - EPSILON()) > (system->timeInt / 1000.0f); |
590 | } |
591 | |
592 | inline bool QQuickParticleData::alive(QQuickParticleSystem* system) const |
593 | { |
594 | if (!system) |
595 | return false; |
596 | float st = (system->timeInt / 1000.0f); |
597 | return (t + EPSILON()) < st && (t + lifeSpan - EPSILON()) > st; |
598 | } |
599 | |
600 | inline float QQuickParticleData::lifeLeft(QQuickParticleSystem *particleSystem) const |
601 | { |
602 | if (!particleSystem) |
603 | return 0.0f; |
604 | return (t + lifeSpan) - (particleSystem->timeInt / 1000.0f); |
605 | } |
606 | |
607 | inline float QQuickParticleData::curSize(QQuickParticleSystem *particleSystem) const |
608 | { |
609 | if (!particleSystem || lifeSpan == 0.0f) |
610 | return 0.0f; |
611 | return size + (endSize - size) * (1 - (lifeLeft(particleSystem) / lifeSpan)); |
612 | } |
613 | |
614 | QT_END_NAMESPACE |
615 | |
616 | #endif // PARTICLESYSTEM_H |
617 | |
618 | |
619 | |