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

source code of qtdeclarative/src/particles/qquickparticlesystem_p.h