Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qquickparticlesystem.cpp
Go to the documentation of this file.
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
5#include <QtQuick/qsgnode.h>
9#include <private/qquickspriteengine_p.h>
10#include <private/qquicksprite_p.h>
13
14#include "qquicktrailemitter_p.h"//###For auto-follow on states, perhaps should be in emitter?
15#include <private/qqmlengine_p.h>
16#include <private/qqmlglobal_p.h>
17#include <cmath>
18#include <QDebug>
19
21//###Switch to define later, for now user-friendly (no compilation) debugging is worth it
22DEFINE_BOOL_CONFIG_OPTION(qmlParticlesDebug, QML_PARTICLES_DEBUG)
23
24
25/* \internal ParticleSystem internals documentation
26
27 Affectors, Painters, Emitters and Groups all register themselves on construction as a callback
28 from their setSystem (or componentComplete if they have a system from a parent).
29
30 Particle data is stored by group, They have a group index (used by the particle system almost
31 everywhere) and a global index (used by the Stochastic state engine powering stochastic group
32 transitions). Each group has a recycling list/heap that stores the particle data.
33
34 The recycling list/heap is a heap of particle data sorted by when they're expected to die. If
35 they die prematurely then they are marked as reusable (and will probably still be alive when
36 they exit the heap). If they have their life extended, then they aren't dead when expected.
37 If this happens, they go back in the heap with the new estimate. If they have died on schedule,
38 then the indexes are marked as reusable. If no indexes are reusable when new particles are
39 requested, then the list is extended. This relatively complex datastructure is because memory
40 allocation and deallocation on this scale proved to be a significant performance cost. In order
41 to reuse the indexes validly (even when particles can have their life extended or cut short
42 dynamically, or particle counts grow) this seemed to be the most efficient option for keeping
43 track of which indices could be reused.
44
45 When a new particle is emitted, the emitter gets a new datum from the group (through the
46 system), and sets properties on it. Then it's passed back to the group briefly so that it can
47 now guess when the particle will die. Then the painters get a change to initialize properties
48 as well, since particle data includes shared data from painters as well as logical particle
49 data.
50
51 Every animation advance, the simulation advances by running all emitters for the elapsed
52 duration, then running all affectors, then telling all particle painters to update changed
53 particles. The ParticlePainter superclass stores these changes, and they are implemented
54 when the painter is called to paint in the render thread.
55
56 Particle group changes move the particle from one group to another by killing the old particle
57 and then creating a new one with the same data in the new group.
58
59 Note that currently groups only grow. Given that data is stored in vectors, it is non-trivial
60 to pluck out the unused indexes when the count goes down. Given the dynamic nature of the
61 system, it is difficult to tell if those unused data instances will be used again. Still,
62 some form of garbage collection is on the long term plan.
63*/
64
65
68
167static inline int roundedTime(qreal a)
168{// in ms
169 return (int)qRound(a*1000.0);
170}
171
173 : m_data(0)
174{
175 m_data.reserve(1000);
176 clear();
177}
178
179void QQuickParticleDataHeap::grow() //###Consider automatic growth vs resize() calls from GroupData
180{
181 m_data.resize(qsizetype(1) << ++m_size);
182}
183
185{
186 insertTimed(data, roundedTime(data->t + data->lifeSpan));
187}
188
190{
191 //TODO: Optimize 0 lifespan (or already dead) case
192 if (m_lookups.contains(time)) {
193 m_data[m_lookups[time]].data << data;
194 return;
195 }
196 if (m_end == (1 << m_size))
197 grow();
198 m_data[m_end].time = time;
199 m_data[m_end].data.clear();
200 m_data[m_end].data.insert(data);
201 m_lookups.insert(time, m_end);
202 bubbleUp(m_end++);
203}
204
206{
207 if (m_end == 0)
208 return 1 << 30;
209 return m_data[0].time;
210}
211
213{
214 if (!m_end)
216 QSet<QQuickParticleData*> ret = m_data[0].data;
217 m_lookups.remove(m_data[0].time);
218 if (m_end == 1) {
219 --m_end;
220 } else {
221 m_data[0] = m_data[--m_end];
222 bubbleDown(0);
223 }
224 return ret;
225}
226
228{
229 m_size = 0;
230 m_end = 0;
231 //m_size is in powers of two. So to start at 0 we have one allocated
232 m_data.resize(1);
233 m_lookups.clear();
234}
235
237{
238 for (int i=0; i<m_end; i++)
239 if (m_data[i].data.contains(d))
240 return true;
241 return false;
242}
243
244void QQuickParticleDataHeap::swap(int a, int b)
245{
246 m_tmp = m_data[a];
247 m_data[a] = m_data[b];
248 m_data[b] = m_tmp;
249 m_lookups[m_data[a].time] = a;
250 m_lookups[m_data[b].time] = b;
251}
252
253void QQuickParticleDataHeap::bubbleUp(int idx)//tends to be called once
254{
255 if (!idx)
256 return;
257 int parent = (idx-1)/2;
258 if (m_data[idx].time < m_data[parent].time) {
259 swap(idx, parent);
260 bubbleUp(parent);
261 }
262}
263
264void QQuickParticleDataHeap::bubbleDown(int idx)//tends to be called log n times
265{
266 int left = idx*2 + 1;
267 if (left >= m_end)
268 return;
269 int lesser = left;
270 int right = idx*2 + 2;
271 if (right < m_end) {
272 if (m_data[left].time > m_data[right].time)
273 lesser = right;
274 }
275 if (m_data[idx].time > m_data[lesser].time) {
276 swap(idx, lesser);
277 bubbleDown(lesser);
278 }
279}
280
282 : index(sys->registerParticleGroupData(name, this))
283 , m_size(0)
284 , m_system(sys)
285{
286 initList();
287}
288
290{
291 for (QQuickParticleData *d : std::as_const(data))
292 delete d;
293}
294
295QString QQuickParticleGroupData::name() const//### Worth caching as well?
296{
297 return m_system->groupIds.key(index);
298}
299
301{
302 if (newSize == m_size)
303 return;
304 Q_ASSERT(newSize > m_size);//XXX allow shrinking
305 data.resize(newSize);
306 freeList.resize(newSize);
307 for (int i=m_size; i<newSize; i++) {
309 data[i]->groupId = index;
310 data[i]->index = i;
311 }
312 int delta = newSize - m_size;
313 m_size = newSize;
314 for (QQuickParticlePainter *p : std::as_const(painters))
315 p->setCount(p->count() + delta);
316}
317
319{
320 dataHeap.clear();
321}
322
324{
325 Q_ASSERT(d->groupId == index);
326 d->lifeSpan = 0;//Kill off
327 for (QQuickParticlePainter *p : std::as_const(painters))
328 p->reload(d);
329 freeList.free(d->index);
330}
331
333{
334 //recycle();//Extra recycler round to be sure?
335
336 while (freeList.hasUnusedEntries()) {
337 int idx = freeList.alloc();
338 if (data[idx]->stillAlive(m_system)) {// ### This means resurrection of 'dead' particles. Is that allowed?
339 prepareRecycler(data[idx]);
340 continue;
341 }
342 return data[idx];
343 }
344 if (respectsLimits)
345 return nullptr;
346
347 int oldSize = m_size;
348 setSize(oldSize + 10);//###+1,10%,+10? Choose something non-arbitrarily
349 int idx = freeList.alloc();
350 Q_ASSERT(idx == oldSize);
351 return data[idx];
352}
353
355{
356 m_latestAliveParticles.clear();
357
358 while (dataHeap.top() <= m_system->timeInt) {
359 for (QQuickParticleData *datum : dataHeap.pop()) {
360 if (!datum->stillAlive(m_system)) {
361 freeList.free(datum->index);
362 } else {
363 m_latestAliveParticles.push_back(datum);
364 }
365 }
366 }
367
368 for (auto particle : m_latestAliveParticles)
369 prepareRecycler(particle); //ttl has been altered mid-way, put it back
370
371 //TODO: If the data is clear, gc (consider shrinking stack size)?
372 return freeList.count() == 0;
373}
374
376{
377 if (d->lifeSpan*1000 < m_system->maxLife) {
379 } else {
380 int extend = 2 * m_system->maxLife / 3;
381 while ((roundedTime(d->t) + extend) <= m_system->timeInt)
382 d->extendLife(m_system->maxLife / 3000.0, m_system);
383 dataHeap.insertTimed(d, roundedTime(d->t) + extend);
384 }
385}
386
388 : index(0)
389 , systemIndex(-1)
390 , groupId(0)
391 , colorOwner(nullptr)
392 , rotationOwner(nullptr)
393 , deformationOwner(nullptr)
394 , animationOwner(nullptr)
395 , v4Datum(nullptr)
396{
397 x = 0;
398 y = 0;
399 t = -1;
400 lifeSpan = 0;
401 size = 0;
402 endSize = 0;
403 vx = 0;
404 vy = 0;
405 ax = 0;
406 ay = 0;
407 xx = 1;
408 xy = 0;
409 yx = 0;
410 yy = 1;
411 rotation = 0;
413 autoRotate = 0;
414 animIdx = 0;
415 frameDuration = 1;
416 frameAt = -1;
417 frameCount = 1;
418 animT = -1;
419 animX = 0;
420 animY = 0;
421 animWidth = 1;
422 animHeight = 1;
423 color.r = 255;
424 color.g = 255;
425 color.b = 255;
426 color.a = 255;
427 delegate = nullptr;
428}
429
431{
432 delete v4Datum;
433}
434
436{
437 *this = other;
438}
439
441{
442 clone(other);
443
444 groupId = other.groupId;
445 index = other.index;
446 systemIndex = other.systemIndex;
447 // Lazily initialized
448 v4Datum = nullptr;
449
450 return *this;
451}
452
454{
455 x = other.x;
456 y = other.y;
457 t = other.t;
458 lifeSpan = other.lifeSpan;
459 size = other.size;
460 endSize = other.endSize;
461 vx = other.vx;
462 vy = other.vy;
463 ax = other.ax;
464 ay = other.ay;
465 xx = other.xx;
466 xy = other.xy;
467 yx = other.yx;
468 yy = other.yy;
469 rotation = other.rotation;
470 rotationVelocity = other.rotationVelocity;
471 autoRotate = other.autoRotate;
472 animIdx = other.animIdx;
473 frameDuration = other.frameDuration;
474 frameCount = other.frameCount;
475 animT = other.animT;
476 animX = other.animX;
477 animY = other.animY;
478 animWidth = other.animWidth;
479 animHeight = other.animHeight;
480 color = other.color;
481 delegate = other.delegate;
482
483 colorOwner = other.colorOwner;
484 rotationOwner = other.rotationOwner;
485 deformationOwner = other.deformationOwner;
486 animationOwner = other.animationOwner;
487}
488
490{
491 if (!v4Datum)
492 v4Datum = new QQuickV4ParticleData(qmlEngine(particleSystem)->handle(), this, particleSystem);
493 return v4Datum->v4Value();
494}
495
497{
498 qDebug() << "Particle" << systemIndex << groupId << "/" << index << stillAlive(particleSystem)
499 << "Pos: " << x << "," << y
500 << "Vel: " << vx << "," << vy
501 << "Acc: " << ax << "," << ay
502 << "Size: " << size << "," << endSize
503 << "Time: " << t << "," <<lifeSpan << ";" << (particleSystem->timeInt / 1000.0) ;
504}
505
507{
508 qreal newX = curX(particleSystem);
509 qreal newY = curY(particleSystem);
510 qreal newVX = curVX(particleSystem);
511 qreal newVY = curVY(particleSystem);
512
513 t += time;
514 animT += time;
515
516 qreal elapsed = (particleSystem->timeInt / 1000.0) - t;
517 qreal evy = newVY - elapsed*ay;
518 qreal ey = newY - elapsed*evy - 0.5 * elapsed*elapsed*ay;
519 qreal evx = newVX - elapsed*ax;
520 qreal ex = newX - elapsed*evx - 0.5 * elapsed*elapsed*ax;
521
522 x = ex;
523 vx = evx;
524 y = ey;
525 vy = evy;
526}
527
530 stateEngine(nullptr),
531 nextFreeGroupId(0),
532 m_animation(nullptr),
533 m_running(true),
534 initialized(0),
535 particleCount(0),
536 m_nextIndex(0),
537 m_componentComplete(false),
538 m_paused(false),
539 m_empty(true)
540{
541 m_debugMode = qmlParticlesDebug();
542}
543
545{
546 for (QQuickParticleGroupData *gd : std::as_const(groupData))
547 delete gd;
548}
549
550void QQuickParticleSystem::initGroups()
551{
552 m_reusableIndexes.clear();
553 m_nextIndex = 0;
554
557 groupIds.clear();
558 nextFreeGroupId = 0;
559
560 for (auto e : std::as_const(m_emitters)) {
561 e->reclaculateGroupId();
562 }
563 for (QQuickParticlePainter *p : std::as_const(m_painters)) {
564 p->recalculateGroupIds();
565 }
566
567 QQuickParticleGroupData *pd = new QQuickParticleGroupData(QString(), this); // Default group
568 Q_ASSERT(pd->index == 0);
569 Q_UNUSED(pd);
570}
571
573{
574 if (m_debugMode)
575 qDebug() << "Registering Painter" << p << "to" << this;
576 //TODO: a way to Unregister emitters, painters and affectors
577 m_painters << QPointer<QQuickParticlePainter>(p);//###Set or uniqueness checking?
578
579 connect(p, &QQuickParticlePainter::groupsChanged, this, [this, p] { this->loadPainter(p); }, Qt::QueuedConnection);
580 loadPainter(p);
581}
582
584{
585 if (m_debugMode)
586 qDebug() << "Registering Emitter" << e << "to" << this;
587 m_emitters << QPointer<QQuickParticleEmitter>(e);//###How to get them out?
588}
589
591{
592 connect(e, SIGNAL(particleCountChanged()),
593 this, SLOT(emittersChanged()));
594 connect(e, SIGNAL(groupChanged(QString)),
595 this, SLOT(emittersChanged()));
596 if (m_componentComplete)
597 emittersChanged();
598 e->reset();//Start, so that starttime factors appropriately
599}
600
602{
603 if (m_debugMode)
604 qDebug() << "Registering Affector" << a << "to" << this;
605 if (!m_affectors.contains(a))
606 m_affectors << QPointer<QQuickParticleAffector>(a);
607}
608
610{
611 if (m_debugMode)
612 qDebug() << "Registering Group" << g << "to" << this;
613 m_groups << QPointer<QQuickParticleGroup>(g);
614 createEngine();
615}
616
618{
619 if (m_running != arg) {
620 m_running = arg;
622 setPaused(false);
623 if (m_animation)//Not created until componentCompleted
625 reset();
626 }
627}
628
630 if (m_paused != arg) {
631 m_paused = arg;
633 m_paused ? m_animation->pause() : m_animation->resume();
634 if (!m_paused) {
635 for (QQuickParticlePainter *p : std::as_const(m_painters)) {
636 if (p) {
637 p->update();
638 }
639 }
640 }
642 }
643}
644
646{
647 //Hooks up automatic state-associated stuff
648 QQuickParticleSystem* sys = qobject_cast<QQuickParticleSystem*>(prop->object->parent());
649 QQuickParticleGroup* group = qobject_cast<QQuickParticleGroup*>(prop->object);
650 if (!group || !sys || !value)
651 return;
653}
654
656{
658 list << group->name();
659 QQuickParticleAffector* a = qobject_cast<QQuickParticleAffector*>(value);
660 if (a) {
661 a->setParentItem(sys);
662 a->setGroups(list);
663 a->setSystem(sys);
664 return;
665 }
666 QQuickTrailEmitter* fe = qobject_cast<QQuickTrailEmitter*>(value);
667 if (fe) {
668 fe->setParentItem(sys);
669 fe->setFollow(group->name());
670 fe->setSystem(sys);
671 return;
672 }
673 QQuickParticleEmitter* e = qobject_cast<QQuickParticleEmitter*>(value);
674 if (e) {
675 e->setParentItem(sys);
676 e->setGroup(group->name());
677 e->setSystem(sys);
678 return;
679 }
680 QQuickParticlePainter* p = qobject_cast<QQuickParticlePainter*>(value);
681 if (p) {
682 p->setParentItem(sys);
683 p->setGroups(list);
684 p->setSystem(sys);
685 return;
686 }
687 qWarning() << value << " was placed inside a particle system state but cannot be taken into the particle system. It will be lost.";
688}
689
690
692{
694 int id;
695 if (nextFreeGroupId >= groupData.size()) {
696 groupData.push_back(pgd);
698 id = nextFreeGroupId - 1;
699 } else {
700 id = nextFreeGroupId;
701 groupData[id] = pgd;
702 searchNextFreeGroupId();
703 }
704 groupIds.insert(name, id);
705 return id;
706}
707
708void QQuickParticleSystem::searchNextFreeGroupId()
709{
711 for (int ei = groupData.size(); nextFreeGroupId != ei; ++nextFreeGroupId) {
712 if (groupData[nextFreeGroupId] == nullptr) {
713 return;
714 }
715 }
716}
717
719
720{
722 m_componentComplete = true;
724 reset();//restarts animation as well
725}
726
728{
729 if (!m_componentComplete)
730 return;
731
732 timeInt = 0;
733 //Clear guarded pointers which have been deleted
734 m_emitters.removeAll(nullptr);
735 m_painters.removeAll(nullptr);
736 m_affectors.removeAll(nullptr);
737
738 bySysIdx.resize(0);
739 initGroups();//Also clears all logical particles
740
741 if (!m_running)
742 return;
743
744 for (QQuickParticleEmitter *e : std::as_const(m_emitters))
745 e->reset();
746
747 emittersChanged();
748
749 for (QQuickParticlePainter *p : std::as_const(m_painters)) {
750 loadPainter(p);
751 p->reset();
752 }
753
754 //### Do affectors need reset too?
755 if (m_animation) {//Animation is explicitly disabled in benchmarks
756 //reset restarts animation (if running)
758 m_animation->stop();
760 if (m_paused)
762 }
763
764 initialized = true;
765}
766
767
768void QQuickParticleSystem::loadPainter(QQuickParticlePainter *painter)
769{
770 if (!m_componentComplete || !painter)
771 return;
772
774 sg->painters.removeOne(painter);
775 }
776
777 int particleCount = 0;
778 if (painter->groups().isEmpty()) {//Uses default particle
779 static QStringList def = QStringList() << QString();
780 painter->setGroups(def);
782 groupData[0]->painters << painter;
783 } else {
784 for (auto groupId : painter->groupIds()) {
785 QQuickParticleGroupData *gd = groupData[groupId];
786 particleCount += gd->size();
787 gd->painters << painter;
788 }
789 }
790 painter->setCount(particleCount);
791 painter->update();//Initial update here
792 return;
793}
794
795void QQuickParticleSystem::emittersChanged()
796{
797 if (!m_componentComplete)
798 return;
799
800 QVector<int> previousSizes;
801 QVector<int> newSizes;
802 previousSizes.reserve(groupData.size());
803 newSizes.reserve(groupData.size());
804 for (int i = 0, ei = groupData.size(); i != ei; ++i) {
805 previousSizes << groupData[i]->size();
806 newSizes << 0;
807 }
808
809 // Populate groups and set sizes.
810 for (int i = 0; i < m_emitters.size(); ) {
811 QQuickParticleEmitter *e = m_emitters.at(i);
812 if (!e) {
813 m_emitters.removeAt(i);
814 continue;
815 }
816
817 int groupId = e->groupId();
818 if (groupId == QQuickParticleGroupData::InvalidID) {
819 groupId = (new QQuickParticleGroupData(e->group(), this))->index;
820 previousSizes << 0;
821 newSizes << 0;
822 }
823 newSizes[groupId] += e->particleCount();
824 //###: Cull emptied groups?
825
826 ++i;
827 }
828
829 //TODO: Garbage collection?
830 particleCount = 0;
831 for (int i = 0, ei = groupData.size(); i != ei; ++i) {
832 groupData[i]->setSize(qMax(newSizes[i], previousSizes[i]));
834 }
835
836 if (m_debugMode)
837 qDebug() << "Particle system emitters changed. New particle count: " << particleCount << "in" << groupData.size() << "groups.";
838
839 if (particleCount > bySysIdx.size())//New datum requests haven't updated it
840 bySysIdx.resize(particleCount);
841
842 for (QQuickParticleAffector *a : std::as_const(m_affectors)) {//Groups may have changed
843 if (a) {
844 a->m_updateIntSet = true;
845 }
846 }
847
848 for (QQuickParticlePainter *p : std::as_const(m_painters))
849 loadPainter(p);
850
851 if (!m_groups.isEmpty())
852 createEngine();
853
854}
855
856void QQuickParticleSystem::createEngine()
857{
858 if (!m_componentComplete)
859 return;
861 qDebug() << "Resetting Existing Sprite Engine...";
862 //### Solve the losses if size/states go down
863 for (QQuickParticleGroup *group : std::as_const(m_groups)) {
864 bool exists = false;
865 for (auto it = groupIds.keyBegin(), end = groupIds.keyEnd(); it != end; ++it) {
866 if (group->name() == *it) {
867 exists = true;
868 break;
869 }
870 }
871 if (!exists) {
872 new QQuickParticleGroupData(group->name(), this);
873 }
874 }
875
876 if (m_groups.size()) {
877 //Reorder groups List so as to have the same order as groupData
878 // TODO: can't we just merge the two lists?
880 for (int i = 0, ei = groupData.size(); i != ei; ++i) {
881 bool exists = false;
882 QString name = groupData[i]->name();
883 for (QQuickParticleGroup *existing : std::as_const(m_groups)) {
884 if (existing->name() == name) {
885 newList << existing;
886 exists = true;
887 }
888 }
889 if (!exists) {
890 newList << new QQuickParticleGroup(this);
891 newList.back()->setName(name);
892 }
893 }
894 m_groups = newList;
896 states.reserve(m_groups.size());
897 for (QQuickParticleGroup *g : std::as_const(m_groups))
899
900 if (!stateEngine)
904
906 this, SLOT(particleStateChange(int)));
907
908 } else {
909 if (stateEngine)
910 delete stateEngine;
911 stateEngine = nullptr;
912 }
913
914}
915
916void QQuickParticleSystem::particleStateChange(int idx)
917{
919}
920
922{
923 if (!d || newGIdx == d->groupId)
924 return;
925
926 QQuickParticleData* pd = newDatum(newGIdx, false, d->systemIndex);
927 if (!pd)
928 return;
929
930 pd->clone(*d);
931 finishNewDatum(pd);
932
933 d->systemIndex = -1;
934 groupData[d->groupId]->kill(d);
935}
936
938{
939 if (!m_reusableIndexes.isEmpty()) {
940 int ret = *(m_reusableIndexes.begin());
941 m_reusableIndexes.remove(ret);
942 return ret;
943 }
944 if (m_nextIndex >= bySysIdx.size()) {
945 bySysIdx.resize(bySysIdx.size() < 10 ? 10 : bySysIdx.size()*1.1);//###+1,10%,+10? Choose something non-arbitrarily
946 if (stateEngine)
948
949 }
950 return m_nextIndex++;
951}
952
953QQuickParticleData* QQuickParticleSystem::newDatum(int groupId, bool respectLimits, int sysIndex)
954{
955 Q_ASSERT(groupId < groupData.size());//XXX shouldn't really be an assert
956
957 QQuickParticleData* ret = groupData[groupId]->newDatum(respectLimits);
958 if (!ret) {
959 return nullptr;
960 }
961 if (sysIndex == -1) {
962 if (ret->systemIndex == -1)
963 ret->systemIndex = nextSystemIndex();
964 } else {
965 if (ret->systemIndex != -1) {
966 if (stateEngine)
967 stateEngine->stop(ret->systemIndex);
968 m_reusableIndexes << ret->systemIndex;
969 bySysIdx[ret->systemIndex] = 0;
970 }
971 ret->systemIndex = sysIndex;
972 }
973 bySysIdx[ret->systemIndex] = ret;
974
975 if (stateEngine)
976 stateEngine->start(ret->systemIndex, ret->groupId);
977
978 m_empty = false;
979 return ret;
980}
981
983{// called from prepareNextFrame()->emitWindow - enforce?
984 //Account for relative emitter position
985 bool okay = false;
986 QTransform t = particleEmitter->itemTransform(this, &okay);
987 if (okay) {
988 qreal tx,ty;
989 t.map(pd->x, pd->y, &tx, &ty);
990 pd->x = tx;
991 pd->y = ty;
992 }
993
994 finishNewDatum(pd);
995}
996
998{
999 Q_ASSERT(pd);
1000 groupData[pd->groupId]->prepareRecycler(pd);
1001
1002 for (QQuickParticleAffector *a : std::as_const(m_affectors))
1003 if (a && a->m_needsReset)
1004 a->reset(pd);
1005 for (QQuickParticlePainter *p : std::as_const(groupData[pd->groupId]->painters))
1006 if (p)
1007 p->load(pd);
1008}
1009
1011{
1012 if (!initialized)
1013 return;//error in initialization
1014
1015 //### Elapsed time never shrinks - may cause problems if left emitting for weeks at a time.
1016 qreal dt = timeInt / 1000.;
1018 qreal time = timeInt / 1000.;
1019 dt = time - dt;
1020 needsReset.clear();
1021
1022 m_emitters.removeAll(nullptr);
1023 m_painters.removeAll(nullptr);
1024 m_affectors.removeAll(nullptr);
1025
1026 bool oldClear = m_empty;
1027 m_empty = true;
1028 for (QQuickParticleGroupData *gd : std::as_const(groupData))//Recycle all groups and see if they're out of live particles
1029 m_empty = gd->recycle() && m_empty;
1030
1031 if (stateEngine)
1033
1034 for (QQuickParticleEmitter *emitter : std::as_const(m_emitters))
1035 emitter->emitWindow(timeInt);
1036 for (QQuickParticleAffector *a : std::as_const(m_affectors))
1037 a->affectSystem(dt);
1039 for (QQuickParticlePainter *p : std::as_const(groupData[d->groupId]->painters))
1040 p->reload(d);
1041
1042 if (oldClear != m_empty)
1043 emptyChanged(m_empty);
1044}
1045
1047{
1048 if (!m_running)
1049 return 0;
1050 if (!initialized)
1051 return 0;//error in initialization
1052 p->performPendingCommits();
1053 return timeInt;
1054}
1055
1056
1058
1059#include "moc_qquickparticlesystem_p.cpp"
NSData * m_data
State state
state of the animation.
void resume()
Resumes the animation after it was paused.
void stop()
Stops the animation.
void start(QAbstractAnimation::DeletionPolicy policy=KeepWhenStopped)
Starts the animation.
void pause()
Pauses the animation.
key_iterator keyEnd() const noexcept
Definition qhash.h:1211
bool remove(const Key &key)
Removes the item that has the key from the hash.
Definition qhash.h:956
bool contains(const Key &key) const noexcept
Returns true if the hash contains an item with the key; otherwise returns false.
Definition qhash.h:991
key_iterator keyBegin() const noexcept
Definition qhash.h:1210
Key key(const T &value) const noexcept
Definition qhash.h:1018
void clear() noexcept(std::is_nothrow_destructible< Node >::value)
Removes all items from the hash and frees up all memory used by it.
Definition qhash.h:949
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
Definition qhash.h:1283
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
bool isEmpty() const noexcept
Definition qlist.h:390
void removeAt(qsizetype i)
Definition qlist.h:573
reference back()
Definition qlist.h:686
const_reference at(qsizetype i) const noexcept
Definition qlist.h:429
qsizetype removeAll(const AT &t)
Definition qlist.h:575
void reserve(qsizetype size)
Definition qlist.h:746
\inmodule QtCore
Definition qobject.h:90
QObject * parent() const
Returns a pointer to the parent object.
Definition qobject.h:311
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
The QQmlListProperty class allows applications to expose list-like properties of QObject-derived clas...
Definition qqmllist.h:24
QObject * object
Definition qqmllist.h:81
The QQuickItem class provides the most basic of all visual items in \l {Qt Quick}.
Definition qquickitem.h:64
void setParentItem(QQuickItem *parent)
void componentComplete() override
\reimp Derived classes should call the base class method before adding their own actions to perform a...
QTransform itemTransform(QQuickItem *, bool *) const
\qmlmethod point QtQuick::Item::mapFromItem(Item item, real x, real y) \qmlmethod point QtQuick::Item...
void stateChanged(const QString &)
QSet< QQuickParticleData * > pop()
void insertTimed(QQuickParticleData *data, int time)
void insert(QQuickParticleData *data)
bool contains(QQuickParticleData *)
QQuickImageParticle * deformationOwner
QQuickImageParticle * colorOwner
QQuickParticleGroupData::ID groupId
float curY(QQuickParticleSystem *particleSystem) const
void extendLife(float time, QQuickParticleSystem *particleSystem)
float curVY(QQuickParticleSystem *particleSystem) const
QQuickImageParticle * animationOwner
bool stillAlive(QQuickParticleSystem *particleSystem) const
QQuickParticleData & operator=(const QQuickParticleData &other)
QQuickImageParticle * rotationOwner
void clone(const QQuickParticleData &other)
void debugDump(QQuickParticleSystem *particleSystem) const
float curVX(QQuickParticleSystem *particleSystem) const
float curX(QQuickParticleSystem *particleSystem) const
QV4::ReturnedValue v4Value(QQuickParticleSystem *particleSystem)
void setSystem(QQuickParticleSystem *arg)
void prepareRecycler(QQuickParticleData *d)
QQuickParticleData * newDatum(bool respectsLimits)
QQuickParticleVarLengthArray< QQuickParticlePainter *, 4 > painters
QQuickParticleGroupData(const QString &name, QQuickParticleSystem *sys)
QQuickParticleDataHeap dataHeap
void kill(QQuickParticleData *d)
void groupsChanged(const QStringList &arg)
void registerParticleEmitter(QQuickParticleEmitter *e)
QQuickStochasticEngine * stateEngine
void runningChanged(bool arg)
void finishRegisteringParticleEmitter(QQuickParticleEmitter *e)
int registerParticleGroupData(const QString &name, QQuickParticleGroupData *pgd)
void registerParticlePainter(QQuickParticlePainter *p)
void emitParticle(QQuickParticleData *p, QQuickParticleEmitter *particleEmitter)
QVarLengthArray< QQuickParticleGroupData *, 32 > groupData
QQuickParticleSystem(QQuickItem *parent=nullptr)
QQuickParticleSystemAnimation * m_animation
void registerParticleGroup(QQuickParticleGroup *g)
void componentComplete() override
\reimp Derived classes should call the base class method before adding their own actions to perform a...
static void stateRedirect(QQuickParticleGroup *group, QQuickParticleSystem *sys, QObject *value)
QHash< QString, int > groupIds
void pausedChanged(bool arg)
QQuickParticleData * newDatum(int groupId, bool respectLimits=true, int sysIdx=-1)
QVector< QQuickParticleData * > bySysIdx
static void statePropertyRedirect(QQmlListProperty< QObject > *prop, QObject *value)
void finishNewDatum(QQuickParticleData *)
void registerParticleAffector(QQuickParticleAffector *a)
void updateCurrentTime(int currentTime)
void moveGroups(QQuickParticleData *d, int newGIdx)
void emptyChanged(bool arg)
int systemSync(QQuickParticlePainter *p)
QSet< QQuickParticleData * > needsReset
void start(int index=0, int state=0)
QList< QQuickStochasticState * > m_states
int curState(int index=0) const
void setFollow(const QString &arg)
QV4::ReturnedValue v4Value() const
Definition qset.h:18
bool remove(const T &value)
Definition qset.h:63
iterator begin()
Definition qset.h:136
bool isEmpty() const
Definition qset.h:52
void clear()
Definition qset.h:61
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
The QTransform class specifies 2D transformations of a coordinate system.
Definition qtransform.h:20
constexpr size_type size() const noexcept
void push_back(const T &t)
#define this
Definition dialogs.cpp:9
qDeleteAll(list.begin(), list.end())
double e
QSet< QString >::iterator it
Combined button and popup list for selecting options.
quint64 ReturnedValue
@ QueuedConnection
static bool m_running
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
int qRound(qfloat16 d) noexcept
Definition qfloat16.h:281
#define qDebug
[1]
Definition qlogging.h:160
#define qWarning
Definition qlogging.h:162
return ret
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
#define SLOT(a)
Definition qobjectdefs.h:51
#define SIGNAL(a)
Definition qobjectdefs.h:52
GLboolean GLboolean GLboolean b
GLuint64 GLenum void * handle
GLint GLint GLint GLint GLint x
[0]
GLboolean GLboolean GLboolean GLboolean a
[7]
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint index
[2]
GLuint GLuint end
GLenum GLuint id
[7]
GLdouble GLdouble right
GLint left
GLboolean GLuint group
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLboolean GLboolean g
GLuint name
GLint y
GLdouble GLdouble t
Definition qopenglext.h:243
GLfloat GLfloat p
[1]
GLbyte ty
GLuint * states
QQmlEngine * qmlEngine(const QObject *obj)
Definition qqml.cpp:76
#define DEFINE_BOOL_CONFIG_OPTION(name, var)
static QT_BEGIN_NAMESPACE int roundedTime(qreal a)
A system which includes particle painter, emitter, and affector types.
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
SSL_CTX int(*) void arg)
static double elapsed(qint64 after, qint64 before)
#define emit
#define Q_UNUSED(x)
ptrdiff_t qsizetype
Definition qtypes.h:70
double qreal
Definition qtypes.h:92
static double currentTime()
QList< int > list
[14]
QObject::connect nullptr
QSharedPointer< T > other(t)
[5]
QPainter painter(this)
[7]
bool contains(const AT &t) const noexcept
Definition qlist.h:44
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent