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 | |
33 | QT_BEGIN_NAMESPACE |
34 | |
35 | template<class T, int Prealloc> |
36 | class QQuickParticleVarLengthArray: public QVarLengthArray<T, Prealloc> |
37 | { |
38 | public: |
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 | |
59 | class QQuickParticleSystem; |
60 | class QQuickParticleAffector; |
61 | class QQuickParticleEmitter; |
62 | class QQuickParticlePainter; |
63 | class QQuickParticleData; |
64 | class QQuickParticleSystemAnimation; |
65 | class QQuickStochasticEngine; |
66 | class QQuickSprite; |
67 | class QQuickV4ParticleData; |
68 | class QQuickParticleGroup; |
69 | class QQuickImageParticle; |
70 | |
71 | struct QQuickParticleDataHeapNode{ |
72 | int time;//in ms |
73 | QSet<QQuickParticleData*> data;//Set ptrs instead? |
74 | }; |
75 | |
76 | class 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. |
79 | public: |
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 |
93 | private: |
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 | |
105 | class 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 | |
162 | public: // types |
163 | typedef int ID; |
164 | enum { InvalidID = -1, DefaultGroupID = 0 }; |
165 | |
166 | public: |
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 | |
199 | private: |
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 | |
206 | struct Color4ub { |
207 | uchar r; |
208 | uchar g; |
209 | uchar b; |
210 | uchar a; |
211 | }; |
212 | |
213 | class Q_QUICKPARTICLES_EXPORT QQuickParticleData |
214 | { |
215 | public: |
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 | |
305 | static_assert(std::is_trivially_copyable_v<QQuickParticleData>); |
306 | static_assert(std::is_trivially_destructible_v<QQuickParticleData>); |
307 | |
308 | class 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 | |
317 | public: |
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 | |
333 | Q_SIGNALS: |
334 | |
335 | void systemInitialized(); |
336 | void runningChanged(bool arg); |
337 | void pausedChanged(bool arg); |
338 | void emptyChanged(bool arg); |
339 | |
340 | public 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 | |
354 | protected: |
355 | //This one only once per frame (effectively) |
356 | void componentComplete() override; |
357 | |
358 | private 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 | |
364 | public: |
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 | |
415 | private: |
416 | void searchNextFreeGroupId(); |
417 | |
418 | private: |
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 |
438 | class QQuickParticleSystemAnimation : public QAbstractAnimation |
439 | { |
440 | Q_OBJECT |
441 | public: |
442 | QQuickParticleSystemAnimation(QQuickParticleSystem* system) |
443 | : QAbstractAnimation(static_cast<QObject*>(system)), m_system(system) |
444 | { } |
445 | protected: |
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 | |
456 | private: |
457 | QQuickParticleSystem* m_system; |
458 | }; |
459 | |
460 | inline 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 | |
473 | inline 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 | |
485 | inline 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 | |
492 | inline 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 | |
505 | inline 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 | |
517 | inline 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 | |
524 | inline 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 | |
531 | inline 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 | |
537 | inline 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 | |
544 | inline float QQuickParticleData::curVY(QQuickParticleSystem *particleSystem) const |
545 | { |
546 | float t = (particleSystem->timeInt / 1000.0f) - this->t; |
547 | return vy + t*ay; |
548 | } |
549 | |
550 | inline bool QQuickParticleData::stillAlive(QQuickParticleSystem* system) const |
551 | { |
552 | if (!system) |
553 | return false; |
554 | return (t + lifeSpan - EPSILON()) > (system->timeInt / 1000.0f); |
555 | } |
556 | |
557 | inline 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 | |
565 | inline float QQuickParticleData::lifeLeft(QQuickParticleSystem *particleSystem) const |
566 | { |
567 | if (!particleSystem) |
568 | return 0.0f; |
569 | return (t + lifeSpan) - (particleSystem->timeInt / 1000.0f); |
570 | } |
571 | |
572 | inline 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 | |
579 | QT_END_NAMESPACE |
580 | |
581 | #endif // PARTICLESYSTEM_H |
582 | |
583 | |
584 |
Definitions
- QQuickParticleVarLengthArray
- insert
- removeOne
- QQuickParticleDataHeapNode
- QQuickParticleDataHeap
- isEmpty
- QQuickParticleGroupData
- FreeList
- FreeList
- resize
- free
- count
- hasUnusedEntries
- alloc
- size
- isActive
- Color4ub
- QQuickParticleData
- curAX
- curAX
- curAY
- curAY
- EPSILON
- QQuickParticleSystem
- isRunning
- count
- start
- stop
- restart
- pause
- resume
- duration
- isPaused
- isEmpty
- QQuickParticleSystemAnimation
- QQuickParticleSystemAnimation
- updateCurrentTime
- duration
- setInstantaneousAX
- setInstantaneousVX
- setInstantaneousX
- setInstantaneousAY
- setInstantaneousVY
- setInstantaneousY
- curX
- curVX
- curY
- curVY
- stillAlive
- alive
- lifeLeft
Learn to use CMake with our Intro Training
Find out more