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#include "qquickparticlesystem_p.h"
5#include <QtQuick/qsgnode.h>
6#include "qquickparticleemitter_p.h"
7#include "qquickparticleaffector_p.h"
8#include "qquickparticlepainter_p.h"
9#include <private/qquickspriteengine_p.h>
10#include <private/qquicksprite_p.h>
11#include "qquickv4particledata_p.h"
12#include "qquickparticlegroup_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
20QT_BEGIN_NAMESPACE
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/*!
66 \qmltype ParticleSystem
67//! \instantiates QQuickParticleSystem
68 \inqmlmodule QtQuick.Particles
69 \brief A system which includes particle painter, emitter, and affector types.
70 \ingroup qtquick-particles
71
72*/
73
74/*!
75 \qmlproperty bool QtQuick.Particles::ParticleSystem::running
76
77 If running is set to false, the particle system will stop the simulation. All particles
78 will be destroyed when the system is set to running again.
79
80 It can also be controlled with the start() and stop() methods.
81*/
82
83
84/*!
85 \qmlproperty bool QtQuick.Particles::ParticleSystem::paused
86
87 If paused is set to true, the particle system will not advance the simulation. When
88 paused is set to false again, the simulation will resume from the same point it was
89 paused.
90
91 The simulation will automatically pause if it detects that there are no live particles
92 left, and unpause when new live particles are added.
93
94 It can also be controlled with the pause() and resume() methods.
95*/
96
97/*!
98 \qmlproperty bool QtQuick.Particles::ParticleSystem::empty
99
100 empty is set to true when there are no live particles left in the system.
101
102 You can use this to pause the system, keeping it from spending any time updating,
103 but you will need to resume it in order for additional particles to be generated
104 by the system.
105
106 To kill all the particles in the system, use an Age affector.
107*/
108
109/*!
110 \qmlproperty list<Sprite> QtQuick.Particles::ParticleSystem::particleStates
111
112 You can define a sub-set of particle groups in this property in order to provide them
113 with stochastic state transitions.
114
115 Each QtQuick::Sprite in this list is interpreted as corresponding to the particle group
116 with the same name. Any transitions defined in these sprites will take effect on the particle
117 groups as well. Additionally TrailEmitters, Affectors and ParticlePainters defined
118 inside one of these sprites are automatically associated with the corresponding particle group.
119*/
120
121/*!
122 \qmlmethod QtQuick.Particles::ParticleSystem::pause()
123
124 Pauses the simulation if it is running.
125
126 \sa resume, paused
127*/
128
129/*!
130 \qmlmethod QtQuick.Particles::ParticleSystem::resume()
131
132 Resumes the simulation if it is paused.
133
134 \sa pause, paused
135*/
136
137/*!
138 \qmlmethod QtQuick.Particles::ParticleSystem::start()
139
140 Starts the simulation if it has not already running.
141
142 \sa stop, restart, running
143*/
144
145/*!
146 \qmlmethod QtQuick.Particles::ParticleSystem::stop()
147
148 Stops the simulation if it is running.
149
150 \sa start, restart, running
151*/
152
153/*!
154 \qmlmethod QtQuick.Particles::ParticleSystem::restart()
155
156 Stops the simulation if it is running, and then starts it.
157
158 \sa start, stop, running
159*/
160/*!
161 \qmlmethod QtQuick.Particles::ParticleSystem::reset()
162
163 Discards all currently existing particles.
164
165*/
166
167static inline int roundedTime(qreal a)
168{// in ms
169 return (int)qRound(d: a*1000.0);
170}
171
172QQuickParticleDataHeap::QQuickParticleDataHeap()
173 : m_data(0)
174{
175 m_data.reserve(size: 1000);
176 clear();
177}
178
179void QQuickParticleDataHeap::grow() //###Consider automatic growth vs resize() calls from GroupData
180{
181 m_data.resize(size: qsizetype(1) << ++m_size);
182}
183
184void QQuickParticleDataHeap::insert(QQuickParticleData* data)
185{
186 insertTimed(data, time: roundedTime(a: data->t + data->lifeSpan));
187}
188
189void QQuickParticleDataHeap::insertTimed(QQuickParticleData* data, int time)
190{
191 //TODO: Optimize 0 lifespan (or already dead) case
192 if (m_lookups.contains(key: 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(value: data);
201 m_lookups.insert(key: time, value: m_end);
202 bubbleUp(m_end++);
203}
204
205int QQuickParticleDataHeap::top()
206{
207 if (m_end == 0)
208 return 1 << 30;
209 return m_data[0].time;
210}
211
212QSet<QQuickParticleData*> QQuickParticleDataHeap::pop()
213{
214 if (!m_end)
215 return QSet<QQuickParticleData*> ();
216 QSet<QQuickParticleData*> ret = m_data[0].data;
217 m_lookups.remove(key: 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
227void QQuickParticleDataHeap::clear()
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(size: 1);
233 m_lookups.clear();
234}
235
236bool QQuickParticleDataHeap::contains(QQuickParticleData* d)
237{
238 for (int i=0; i<m_end; i++)
239 if (m_data[i].data.contains(value: 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(a: idx, b: parent);
260 bubbleUp(idx: 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(a: idx, b: lesser);
277 bubbleDown(idx: lesser);
278 }
279}
280
281QQuickParticleGroupData::QQuickParticleGroupData(const QString &name, QQuickParticleSystem* sys)
282 : index(sys->registerParticleGroupData(name, pgd: this))
283 , m_size(0)
284 , m_system(sys)
285{
286 initList();
287}
288
289QQuickParticleGroupData::~QQuickParticleGroupData()
290{
291 for (QQuickParticleData *d : std::as_const(t&: data))
292 delete d;
293}
294
295QString QQuickParticleGroupData::name() const//### Worth caching as well?
296{
297 return m_system->groupIds.key(value: index);
298}
299
300void QQuickParticleGroupData::setSize(int newSize)
301{
302 if (newSize == m_size)
303 return;
304 Q_ASSERT(newSize > m_size);//XXX allow shrinking
305 data.resize(size: newSize);
306 freeList.resize(newSize);
307 for (int i=m_size; i<newSize; i++) {
308 data[i] = new QQuickParticleData;
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(t&: painters))
315 p->setCount(p->count() + delta);
316}
317
318void QQuickParticleGroupData::initList()
319{
320 dataHeap.clear();
321}
322
323void QQuickParticleGroupData::kill(QQuickParticleData* d)
324{
325 Q_ASSERT(d->groupId == index);
326 d->lifeSpan = 0;//Kill off
327 for (QQuickParticlePainter *p : std::as_const(t&: painters))
328 p->reload(d);
329 freeList.free(index: d->index);
330}
331
332QQuickParticleData* QQuickParticleGroupData::newDatum(bool respectsLimits)
333{
334 //recycle();//Extra recycler round to be sure?
335
336 while (freeList.hasUnusedEntries()) {
337 int idx = freeList.alloc();
338 if (data[idx]->stillAlive(system: m_system)) {// ### This means resurrection of 'dead' particles. Is that allowed?
339 prepareRecycler(d: 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
354bool QQuickParticleGroupData::recycle()
355{
356 m_latestAliveParticles.clear();
357
358 while (dataHeap.top() <= m_system->timeInt) {
359 for (QQuickParticleData *datum : dataHeap.pop()) {
360 if (!datum->stillAlive(system: m_system)) {
361 freeList.free(index: datum->index);
362 } else {
363 m_latestAliveParticles.push_back(t: datum);
364 }
365 }
366 }
367
368 for (auto particle : m_latestAliveParticles)
369 prepareRecycler(d: 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
375void QQuickParticleGroupData::prepareRecycler(QQuickParticleData* d)
376{
377 if (d->lifeSpan*1000 < m_system->maxLife) {
378 dataHeap.insert(data: d);
379 } else {
380 int extend = 2 * m_system->maxLife / 3;
381 while ((roundedTime(a: d->t) + extend) <= m_system->timeInt)
382 d->extendLife(time: m_system->maxLife / 3000.0, particleSystem: m_system);
383 dataHeap.insertTimed(data: d, time: roundedTime(a: d->t) + extend);
384 }
385}
386
387QQuickParticleData::QQuickParticleData()
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;
412 rotationVelocity = 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
430QQuickParticleData::~QQuickParticleData()
431{
432 delete v4Datum;
433}
434
435QQuickParticleData::QQuickParticleData(const QQuickParticleData &other)
436{
437 *this = other;
438}
439
440QQuickParticleData &QQuickParticleData::operator=(const QQuickParticleData &other)
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
453void QQuickParticleData::clone(const QQuickParticleData& other)
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
489QV4::ReturnedValue QQuickParticleData::v4Value(QQuickParticleSystem* particleSystem)
490{
491 if (!v4Datum)
492 v4Datum = new QQuickV4ParticleData(qmlEngine(particleSystem)->handle(), this, particleSystem);
493 return v4Datum->v4Value();
494}
495
496void QQuickParticleData::debugDump(QQuickParticleSystem* particleSystem) const
497{
498 qDebug() << "Particle" << systemIndex << groupId << "/" << index << stillAlive(system: 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
506void QQuickParticleData::extendLife(float time, QQuickParticleSystem* particleSystem)
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
528QQuickParticleSystem::QQuickParticleSystem(QQuickItem *parent) :
529 QQuickItem(parent),
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
544QQuickParticleSystem::~QQuickParticleSystem()
545{
546 for (QQuickParticleGroupData *gd : std::as_const(t&: groupData))
547 delete gd;
548}
549
550void QQuickParticleSystem::initGroups()
551{
552 m_reusableIndexes.clear();
553 m_nextIndex = 0;
554
555 qDeleteAll(c: groupData);
556 groupData.clear();
557 groupIds.clear();
558 nextFreeGroupId = 0;
559
560 for (auto e : std::as_const(t&: m_emitters)) {
561 e->reclaculateGroupId();
562 }
563 for (QQuickParticlePainter *p : std::as_const(t&: 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
572void QQuickParticleSystem::registerParticlePainter(QQuickParticlePainter* p)
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(sender: p, signal: &QQuickParticlePainter::groupsChanged, context: this, slot: [this, p] { this->loadPainter(p); }, type: Qt::QueuedConnection);
580 loadPainter(p);
581}
582
583void QQuickParticleSystem::registerParticleEmitter(QQuickParticleEmitter* e)
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
590void QQuickParticleSystem::finishRegisteringParticleEmitter(QQuickParticleEmitter* e)
591{
592 connect(sender: e, SIGNAL(particleCountChanged()),
593 receiver: this, SLOT(emittersChanged()));
594 connect(sender: e, SIGNAL(groupChanged(QString)),
595 receiver: this, SLOT(emittersChanged()));
596 if (m_componentComplete)
597 emittersChanged();
598 e->reset();//Start, so that starttime factors appropriately
599}
600
601void QQuickParticleSystem::registerParticleAffector(QQuickParticleAffector* a)
602{
603 if (m_debugMode)
604 qDebug() << "Registering Affector" << a << "to" << this;
605 if (!m_affectors.contains(t: a))
606 m_affectors << QPointer<QQuickParticleAffector>(a);
607}
608
609void QQuickParticleSystem::registerParticleGroup(QQuickParticleGroup* g)
610{
611 if (m_debugMode)
612 qDebug() << "Registering Group" << g << "to" << this;
613 m_groups << QPointer<QQuickParticleGroup>(g);
614 createEngine();
615}
616
617void QQuickParticleSystem::setRunning(bool arg)
618{
619 if (m_running != arg) {
620 m_running = arg;
621 emit runningChanged(arg);
622 setPaused(false);
623 if (m_animation)//Not created until componentCompleted
624 m_running ? m_animation->start() : m_animation->stop();
625 reset();
626 }
627}
628
629void QQuickParticleSystem::setPaused(bool arg) {
630 if (m_paused != arg) {
631 m_paused = arg;
632 if (m_animation && m_animation->state() != QAbstractAnimation::Stopped)
633 m_paused ? m_animation->pause() : m_animation->resume();
634 if (!m_paused) {
635 for (QQuickParticlePainter *p : std::as_const(t&: m_painters)) {
636 if (p) {
637 p->update();
638 }
639 }
640 }
641 emit pausedChanged(arg);
642 }
643}
644
645void QQuickParticleSystem::statePropertyRedirect(QQmlListProperty<QObject> *prop, QObject *value)
646{
647 //Hooks up automatic state-associated stuff
648 QQuickParticleSystem* sys = qobject_cast<QQuickParticleSystem*>(object: prop->object->parent());
649 QQuickParticleGroup* group = qobject_cast<QQuickParticleGroup*>(object: prop->object);
650 if (!group || !sys || !value)
651 return;
652 stateRedirect(group, sys, value);
653}
654
655void QQuickParticleSystem::stateRedirect(QQuickParticleGroup* group, QQuickParticleSystem* sys, QObject *value)
656{
657 QStringList list;
658 list << group->name();
659 QQuickParticleAffector* a = qobject_cast<QQuickParticleAffector*>(object: 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*>(object: 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*>(object: 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*>(object: 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
691int QQuickParticleSystem::registerParticleGroupData(const QString &name, QQuickParticleGroupData *pgd)
692{
693 Q_ASSERT(!groupIds.contains(name));
694 int id;
695 if (nextFreeGroupId >= groupData.size()) {
696 groupData.push_back(t: pgd);
697 nextFreeGroupId = groupData.size();
698 id = nextFreeGroupId - 1;
699 } else {
700 id = nextFreeGroupId;
701 groupData[id] = pgd;
702 searchNextFreeGroupId();
703 }
704 groupIds.insert(key: name, value: id);
705 return id;
706}
707
708void QQuickParticleSystem::searchNextFreeGroupId()
709{
710 ++nextFreeGroupId;
711 for (int ei = groupData.size(); nextFreeGroupId != ei; ++nextFreeGroupId) {
712 if (groupData[nextFreeGroupId] == nullptr) {
713 return;
714 }
715 }
716}
717
718void QQuickParticleSystem::componentComplete()
719
720{
721 QQuickItem::componentComplete();
722 m_componentComplete = true;
723 m_animation = new QQuickParticleSystemAnimation(this);
724 reset();//restarts animation as well
725}
726
727void QQuickParticleSystem::reset()
728{
729 if (!m_componentComplete)
730 return;
731
732 timeInt = 0;
733 //Clear guarded pointers which have been deleted
734 m_emitters.removeAll(t: nullptr);
735 m_painters.removeAll(t: nullptr);
736 m_affectors.removeAll(t: nullptr);
737
738 bySysIdx.resize(size: 0);
739 initGroups();//Also clears all logical particles
740
741 if (!m_running)
742 return;
743
744 for (QQuickParticleEmitter *e : std::as_const(t&: m_emitters))
745 e->reset();
746
747 emittersChanged();
748
749 for (QQuickParticlePainter *p : std::as_const(t&: 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)
757 if ((m_animation->state() == QAbstractAnimation::Running))
758 m_animation->stop();
759 m_animation->start();
760 if (m_paused)
761 m_animation->pause();
762 }
763
764 initialized = true;
765}
766
767
768void QQuickParticleSystem::loadPainter(QQuickParticlePainter *painter)
769{
770 if (!m_componentComplete || !painter)
771 return;
772
773 for (QQuickParticleGroupData *sg : groupData) {
774 sg->painters.removeOne(element: 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);
781 particleCount += groupData[0]->size();
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(size: groupData.size());
803 newSizes.reserve(size: 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(a: newSizes[i], b: previousSizes[i]));
833 particleCount += groupData[i]->size();
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(size: particleCount);
841
842 for (QQuickParticleAffector *a : std::as_const(t&: m_affectors)) {//Groups may have changed
843 if (a) {
844 a->m_updateIntSet = true;
845 }
846 }
847
848 for (QQuickParticlePainter *p : std::as_const(t&: m_painters))
849 loadPainter(painter: p);
850
851 if (!m_groups.isEmpty())
852 createEngine();
853
854}
855
856void QQuickParticleSystem::createEngine()
857{
858 if (!m_componentComplete)
859 return;
860 if (stateEngine && m_debugMode)
861 qDebug() << "Resetting Existing Sprite Engine...";
862 //### Solve the losses if size/states go down
863 for (QQuickParticleGroup *group : std::as_const(t&: 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?
879 QList<QQuickParticleGroup*> newList;
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(t&: 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;
895 QList<QQuickStochasticState*> states;
896 states.reserve(size: m_groups.size());
897 for (QQuickParticleGroup *g : std::as_const(t&: m_groups))
898 states << (QQuickStochasticState*)g;
899
900 if (!stateEngine)
901 stateEngine = new QQuickStochasticEngine(this);
902 stateEngine->setCount(particleCount);
903 stateEngine->m_states = states;
904
905 connect(sender: stateEngine, SIGNAL(stateChanged(int)),
906 receiver: 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{
918 moveGroups(d: bySysIdx[idx], newGIdx: stateEngine->curState(index: idx));
919}
920
921void QQuickParticleSystem::moveGroups(QQuickParticleData *d, int newGIdx)
922{
923 if (!d || newGIdx == d->groupId)
924 return;
925
926 QQuickParticleData* pd = newDatum(groupId: newGIdx, respectLimits: false, sysIdx: d->systemIndex);
927 if (!pd)
928 return;
929
930 pd->clone(other: *d);
931 finishNewDatum(pd);
932
933 d->systemIndex = -1;
934 groupData[d->groupId]->kill(d);
935}
936
937int QQuickParticleSystem::nextSystemIndex()
938{
939 if (!m_reusableIndexes.isEmpty()) {
940 int ret = *(m_reusableIndexes.begin());
941 m_reusableIndexes.remove(value: ret);
942 return ret;
943 }
944 if (m_nextIndex >= bySysIdx.size()) {
945 bySysIdx.resize(size: bySysIdx.size() < 10 ? 10 : bySysIdx.size()*1.1);//###+1,10%,+10? Choose something non-arbitrarily
946 if (stateEngine)
947 stateEngine->setCount(bySysIdx.size());
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(respectsLimits: 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(index: 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(index: ret->systemIndex, state: ret->groupId);
977
978 m_empty = false;
979 return ret;
980}
981
982void QQuickParticleSystem::emitParticle(QQuickParticleData* pd, QQuickParticleEmitter* particleEmitter)
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(x: pd->x, y: pd->y, tx: &tx, ty: &ty);
990 pd->x = tx;
991 pd->y = ty;
992 }
993
994 finishNewDatum(pd);
995}
996
997void QQuickParticleSystem::finishNewDatum(QQuickParticleData *pd)
998{
999 Q_ASSERT(pd);
1000 groupData[pd->groupId]->prepareRecycler(d: pd);
1001
1002 for (QQuickParticleAffector *a : std::as_const(t&: m_affectors))
1003 if (a && a->m_needsReset)
1004 a->reset(pd);
1005 for (QQuickParticlePainter *p : std::as_const(t&: groupData[pd->groupId]->painters))
1006 if (p)
1007 p->load(pd);
1008}
1009
1010void QQuickParticleSystem::updateCurrentTime( int currentTime )
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.;
1017 timeInt = currentTime;
1018 qreal time = timeInt / 1000.;
1019 dt = time - dt;
1020 needsReset.clear();
1021
1022 m_emitters.removeAll(t: nullptr);
1023 m_painters.removeAll(t: nullptr);
1024 m_affectors.removeAll(t: nullptr);
1025
1026 bool oldClear = m_empty;
1027 m_empty = true;
1028 for (QQuickParticleGroupData *gd : std::as_const(t&: groupData))//Recycle all groups and see if they're out of live particles
1029 m_empty = gd->recycle() && m_empty;
1030
1031 if (stateEngine)
1032 stateEngine->updateSprites(time: timeInt);
1033
1034 for (QQuickParticleEmitter *emitter : std::as_const(t&: m_emitters))
1035 emitter->emitWindow(timeStamp: timeInt);
1036 for (QQuickParticleAffector *a : std::as_const(t&: m_affectors))
1037 a->affectSystem(dt);
1038 for (QQuickParticleData *d : needsReset)
1039 for (QQuickParticlePainter *p : std::as_const(t&: groupData[d->groupId]->painters))
1040 p->reload(d);
1041
1042 if (oldClear != m_empty)
1043 emptyChanged(arg: m_empty);
1044}
1045
1046int QQuickParticleSystem::systemSync(QQuickParticlePainter* p)
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
1057QT_END_NAMESPACE
1058
1059#include "moc_qquickparticlesystem_p.cpp"
1060

source code of qtdeclarative/src/particles/qquickparticlesystem.cpp