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 <private/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_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 bool isEmpty() const { return m_end == 0; }
87
88 QSet<QQuickParticleData*> pop();
89
90 void clear();
91
92 bool contains(QQuickParticleData*);//O(n), for debugging purposes only
93private:
94 void grow();
95 void swap(int, int);
96 void bubbleUp(int);
97 void bubbleDown(int);
98 int m_size;
99 int m_end;
100 QQuickParticleDataHeapNode m_tmp;
101 QVector<QQuickParticleDataHeapNode> m_data;
102 QHash<int,int> m_lookups;
103};
104
105class Q_QUICKPARTICLES_EXPORT QQuickParticleGroupData {
106 class FreeList
107 {
108 public:
109 FreeList() {}
110
111 void resize(int newSize)
112 {
113 Q_ASSERT(newSize >= 0);
114 int oldSize = isUnused.size();
115 isUnused.resize(newSize, newValue: true);
116 if (newSize > oldSize) {
117 if (firstUnused == UINT_MAX) {
118 firstUnused = oldSize;
119 } else {
120 firstUnused = std::min(a: firstUnused, b: unsigned(oldSize));
121 }
122 } else if (firstUnused >= unsigned(newSize)) {
123 firstUnused = UINT_MAX;
124 }
125 }
126
127 void free(int index)
128 {
129 isUnused.setBit(index);
130 firstUnused = std::min(a: firstUnused, b: unsigned(index));
131 --allocated;
132 }
133
134 int count() const
135 { return allocated; }
136
137 bool hasUnusedEntries() const
138 { return firstUnused != UINT_MAX; }
139
140 int alloc()
141 {
142 if (hasUnusedEntries()) {
143 int nextFree = firstUnused;
144 isUnused.clearBit(idx: firstUnused);
145 firstUnused = isUnused.findNext(start: firstUnused, value: true, wrapAround: false);
146 if (firstUnused >= unsigned(isUnused.size())) {
147 firstUnused = UINT_MAX;
148 }
149 ++allocated;
150 return nextFree;
151 } else {
152 return -1;
153 }
154 }
155
156 private:
157 QV4::BitVector isUnused;
158 unsigned firstUnused = UINT_MAX;
159 int allocated = 0;
160 };
161
162public: // types
163 typedef int ID;
164 enum { InvalidID = -1, DefaultGroupID = 0 };
165
166public:
167 QQuickParticleGroupData(const QString &name, QQuickParticleSystem* sys);
168 ~QQuickParticleGroupData();
169
170 int size() const
171 {
172 return m_size;
173 }
174
175 bool isActive() { return freeList.count() > 0; }
176
177 QString name() const;
178
179 void setSize(int newSize);
180
181 const ID index;
182 QQuickParticleVarLengthArray<QQuickParticlePainter*, 4> painters;//TODO: What if they are dynamically removed?
183
184 //TODO: Refactor particle data list out into a separate class
185 QVector<QQuickParticleData*> data;
186 FreeList freeList;
187 QQuickParticleDataHeap dataHeap;
188 bool recycle(); //Force recycling round, returns true if all indexes are now reusable
189
190 void initList();
191 void kill(QQuickParticleData* d);
192
193 //After calling this, initialize, then call prepareRecycler(d)
194 QQuickParticleData* newDatum(bool respectsLimits);
195
196 //TODO: Find and clean up those that don't get added to the recycler (currently they get lost)
197 void prepareRecycler(QQuickParticleData* d);
198
199private:
200 int m_size;
201 QQuickParticleSystem* m_system;
202 // Only used in recycle() for tracking of alive particles after latest recycling round
203 QVector<QQuickParticleData*> m_latestAliveParticles;
204};
205
206struct Color4ub {
207 uchar r;
208 uchar g;
209 uchar b;
210 uchar a;
211};
212
213class Q_QUICKPARTICLES_EXPORT QQuickParticleData
214{
215public:
216 //Convenience functions for working backwards, because parameters are from the start of particle life
217 //If setting multiple parameters at once, doing the conversion yourself will be faster.
218
219 //sets the x accleration without affecting the instantaneous x velocity or position
220 void setInstantaneousAX(float ax, QQuickParticleSystem *particleSystem);
221 //sets the x velocity without affecting the instantaneous x postion
222 void setInstantaneousVX(float vx, QQuickParticleSystem *particleSystem);
223 //sets the instantaneous x postion
224 void setInstantaneousX(float x, QQuickParticleSystem *particleSystem);
225 //sets the y accleration without affecting the instantaneous y velocity or position
226 void setInstantaneousAY(float ay, QQuickParticleSystem *particleSystem);
227 //sets the y velocity without affecting the instantaneous y postion
228 void setInstantaneousVY(float vy, QQuickParticleSystem *particleSystem);
229 //sets the instantaneous Y postion
230 void setInstantaneousY(float y, QQuickParticleSystem *particleSystem);
231
232 //TODO: Slight caching?
233 float curX(QQuickParticleSystem *particleSystem) const;
234 float curVX(QQuickParticleSystem *particleSystem) const;
235 float curAX() const { return ax; }
236 float curAX(QQuickParticleSystem *) const { return ax; } // used by the macros in qquickv4particledata.cpp
237 float curY(QQuickParticleSystem *particleSystem) const;
238 float curVY(QQuickParticleSystem *particleSystem) const;
239 float curAY() const { return ay; }
240 float curAY(QQuickParticleSystem *) const { return ay; } // used by the macros in qquickv4particledata.cpp
241
242 int index = 0;
243 int systemIndex = -1;
244
245 //General Position Stuff
246 float x = 0;
247 float y = 0;
248 float t = -1;
249 float lifeSpan = 0;
250 float size = 0;
251 float endSize = 0;
252 float vx = 0;
253 float vy = 0;
254 float ax = 0;
255 float ay = 0;
256
257 //Painter-specific stuff, now universally shared
258 //Used by ImageParticle color mode
259 Color4ub color = { .r: 255, .g: 255, .b: 255, .a: 255};
260 //Used by ImageParticle deform mode
261 float xx = 1;
262 float xy = 0;
263 float yx = 0;
264 float yy = 1;
265 float rotation = 0;
266 float rotationVelocity = 0;
267 uchar autoRotate = 0; // Basically a bool
268 //Used by ImageParticle Sprite mode
269 float animIdx = 0;
270 float frameDuration = 1;
271 float frameAt = -1;//Used for duration -1
272 float frameCount = 1;
273 float animT = -1;
274 float animX = 0;
275 float animY = 0;
276 float animWidth = 1;
277 float animHeight = 1;
278
279 QQuickParticleGroupData::ID groupId = 0;
280
281 //Used by ImageParticle data shadowing
282 QQuickImageParticle* colorOwner = nullptr;
283 QQuickImageParticle* rotationOwner = nullptr;
284 QQuickImageParticle* deformationOwner = nullptr;
285 QQuickImageParticle* animationOwner = nullptr;
286
287 //Used by ItemParticle
288 QQuickItem* delegate = nullptr;
289 //Used by custom affectors
290 float update = 0;
291
292 void debugDump(QQuickParticleSystem *particleSystem) const;
293 bool stillAlive(QQuickParticleSystem *particleSystem) const; //Only checks end, because usually that's all you need and it's a little faster.
294 bool alive(QQuickParticleSystem *particleSystem) const;
295 float lifeLeft(QQuickParticleSystem *particleSystem) const;
296
297 float curSize(QQuickParticleSystem *particleSystem) const;
298
299 QQuickV4ParticleData v4Value(QQuickParticleSystem *particleSystem);
300 void extendLife(float time, QQuickParticleSystem *particleSystem);
301
302 static inline constexpr float EPSILON() noexcept { return 0.001f; }
303};
304
305static_assert(std::is_trivially_copyable_v<QQuickParticleData>);
306static_assert(std::is_trivially_destructible_v<QQuickParticleData>);
307
308class Q_QUICKPARTICLES_EXPORT QQuickParticleSystem : public QQuickItem
309{
310 Q_OBJECT
311 Q_PROPERTY(bool running READ isRunning WRITE setRunning NOTIFY runningChanged)
312 Q_PROPERTY(bool paused READ isPaused WRITE setPaused NOTIFY pausedChanged)
313 Q_PROPERTY(bool empty READ isEmpty NOTIFY emptyChanged)
314 QML_NAMED_ELEMENT(ParticleSystem)
315 QML_ADDED_IN_VERSION(2, 0)
316
317public:
318 explicit QQuickParticleSystem(QQuickItem *parent = nullptr);
319 ~QQuickParticleSystem();
320
321 bool isRunning() const
322 {
323 return m_running;
324 }
325
326 int count() const
327 {
328 return particleCount;
329 }
330
331 static const int maxLife = 600000;
332
333Q_SIGNALS:
334
335 void systemInitialized();
336 void runningChanged(bool arg);
337 void pausedChanged(bool arg);
338 void emptyChanged(bool arg);
339
340public Q_SLOTS:
341 void start(){setRunning(true);}
342 void stop(){setRunning(false);}
343 void restart(){setRunning(false);setRunning(true);}
344 void pause(){setPaused(true);}
345 void resume(){setPaused(false);}
346
347 void reset();
348 void setRunning(bool arg);
349 void setPaused(bool arg);
350
351 virtual int duration() const { return -1; }
352
353
354protected:
355 //This one only once per frame (effectively)
356 void componentComplete() override;
357
358private Q_SLOTS:
359 void emittersChanged();
360 void loadPainter(QQuickParticlePainter *p);
361 void createEngine(); //Not invoked by sprite engine, unlike Sprite uses
362 void particleStateChange(int idx);
363
364public:
365 //These can be called multiple times per frame, performance critical
366 void emitParticle(QQuickParticleData* p, QQuickParticleEmitter *particleEmitter);
367 QQuickParticleData *newDatum(
368 int groupId, bool respectLimits = true, int sysIdx = -1,
369 const QQuickParticleData *cloneFrom = nullptr);
370 void finishNewDatum(QQuickParticleData*);
371 void moveGroups(QQuickParticleData *d, int newGIdx);
372 int nextSystemIndex();
373
374 //This one only once per painter per frame
375 int systemSync(QQuickParticlePainter* p);
376
377 //Data members here for ease of related class and auto-test usage. Not "public" API. TODO: d_ptrize
378 QSet<QQuickParticleData*> needsReset;
379 QVector<QQuickParticleData*> bySysIdx; //Another reference to the data (data owned by group), but by sysIdx
380 QQuickStochasticEngine* stateEngine;
381
382 QHash<QString, int> groupIds;
383 QVarLengthArray<QQuickParticleGroupData*, 32> groupData;
384 int nextFreeGroupId;
385 int registerParticleGroupData(const QString &name, QQuickParticleGroupData *pgd);
386
387 //Also only here for auto-test usage
388 void updateCurrentTime( int currentTime );
389 QQuickParticleSystemAnimation* m_animation;
390 bool m_running;
391 bool m_debugMode;
392
393 int timeInt;
394 bool initialized;
395 int particleCount;
396
397 void registerParticlePainter(QQuickParticlePainter* p);
398 void registerParticleEmitter(QQuickParticleEmitter* e);
399 void finishRegisteringParticleEmitter(QQuickParticleEmitter *e);
400 void registerParticleAffector(QQuickParticleAffector* a);
401 void registerParticleGroup(QQuickParticleGroup* g);
402
403 static void statePropertyRedirect(QQmlListProperty<QObject> *prop, QObject *value);
404 static void stateRedirect(QQuickParticleGroup* group, QQuickParticleSystem* sys, QObject *value);
405 bool isPaused() const
406 {
407 return m_paused;
408 }
409
410 bool isEmpty() const
411 {
412 return m_empty;
413 }
414
415private:
416 void searchNextFreeGroupId();
417
418private:
419 void emitterAdded(QQuickParticleEmitter *e);
420 void postProcessEmitters();
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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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