1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQuick module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qquickparticlesystem_p.h"
41#include <QtQuick/qsgnode.h>
42#include "qquickparticleemitter_p.h"
43#include "qquickparticleaffector_p.h"
44#include "qquickparticlepainter_p.h"
45#include <private/qquickspriteengine_p.h>
46#include <private/qquicksprite_p.h>
47#include "qquickv4particledata_p.h"
48#include "qquickparticlegroup_p.h"
49
50#include "qquicktrailemitter_p.h"//###For auto-follow on states, perhaps should be in emitter?
51#include <private/qqmlengine_p.h>
52#include <private/qqmlglobal_p.h>
53#include <cmath>
54#include <QDebug>
55
56QT_BEGIN_NAMESPACE
57//###Switch to define later, for now user-friendly (no compilation) debugging is worth it
58DEFINE_BOOL_CONFIG_OPTION(qmlParticlesDebug, QML_PARTICLES_DEBUG)
59
60
61/* \internal ParticleSystem internals documentation
62
63 Affectors, Painters, Emitters and Groups all register themselves on construction as a callback
64 from their setSystem (or componentComplete if they have a system from a parent).
65
66 Particle data is stored by group, They have a group index (used by the particle system almost
67 everywhere) and a global index (used by the Stochastic state engine powering stochastic group
68 transitions). Each group has a recycling list/heap that stores the particle data.
69
70 The recycling list/heap is a heap of particle data sorted by when they're expected to die. If
71 they die prematurely then they are marked as reusable (and will probably still be alive when
72 they exit the heap). If they have their life extended, then they aren't dead when expected.
73 If this happens, they go back in the heap with the new estimate. If they have died on schedule,
74 then the indexes are marked as reusable. If no indexes are reusable when new particles are
75 requested, then the list is extended. This relatively complex datastructure is because memory
76 allocation and deallocation on this scale proved to be a significant performance cost. In order
77 to reuse the indexes validly (even when particles can have their life extended or cut short
78 dynamically, or particle counts grow) this seemed to be the most efficient option for keeping
79 track of which indices could be reused.
80
81 When a new particle is emitted, the emitter gets a new datum from the group (through the
82 system), and sets properties on it. Then it's passed back to the group briefly so that it can
83 now guess when the particle will die. Then the painters get a change to initialize properties
84 as well, since particle data includes shared data from painters as well as logical particle
85 data.
86
87 Every animation advance, the simulation advances by running all emitters for the elapsed
88 duration, then running all affectors, then telling all particle painters to update changed
89 particles. The ParticlePainter superclass stores these changes, and they are implemented
90 when the painter is called to paint in the render thread.
91
92 Particle group changes move the particle from one group to another by killing the old particle
93 and then creating a new one with the same data in the new group.
94
95 Note that currently groups only grow. Given that data is stored in vectors, it is non-trivial
96 to pluck out the unused indexes when the count goes down. Given the dynamic nature of the
97 system, it is difficult to tell if those unused data instances will be used again. Still,
98 some form of garbage collection is on the long term plan.
99*/
100
101/*!
102 \qmltype ParticleSystem
103 \instantiates QQuickParticleSystem
104 \inqmlmodule QtQuick.Particles
105 \brief A system which includes particle painter, emitter, and affector types.
106 \ingroup qtquick-particles
107
108*/
109
110/*!
111 \qmlproperty bool QtQuick.Particles::ParticleSystem::running
112
113 If running is set to false, the particle system will stop the simulation. All particles
114 will be destroyed when the system is set to running again.
115
116 It can also be controlled with the start() and stop() methods.
117*/
118
119
120/*!
121 \qmlproperty bool QtQuick.Particles::ParticleSystem::paused
122
123 If paused is set to true, the particle system will not advance the simulation. When
124 paused is set to false again, the simulation will resume from the same point it was
125 paused.
126
127 The simulation will automatically pause if it detects that there are no live particles
128 left, and unpause when new live particles are added.
129
130 It can also be controlled with the pause() and resume() methods.
131*/
132
133/*!
134 \qmlproperty bool QtQuick.Particles::ParticleSystem::empty
135
136 empty is set to true when there are no live particles left in the system.
137
138 You can use this to pause the system, keeping it from spending any time updating,
139 but you will need to resume it in order for additional particles to be generated
140 by the system.
141
142 To kill all the particles in the system, use an Age affector.
143*/
144
145/*!
146 \qmlproperty list<Sprite> QtQuick.Particles::ParticleSystem::particleStates
147
148 You can define a sub-set of particle groups in this property in order to provide them
149 with stochastic state transitions.
150
151 Each QtQuick::Sprite in this list is interpreted as corresponding to the particle group
152 with the same name. Any transitions defined in these sprites will take effect on the particle
153 groups as well. Additionally TrailEmitters, Affectors and ParticlePainters defined
154 inside one of these sprites are automatically associated with the corresponding particle group.
155*/
156
157/*!
158 \qmlmethod QtQuick.Particles::ParticleSystem::pause()
159
160 Pauses the simulation if it is running.
161
162 \sa resume, paused
163*/
164
165/*!
166 \qmlmethod QtQuick.Particles::ParticleSystem::resume()
167
168 Resumes the simulation if it is paused.
169
170 \sa pause, paused
171*/
172
173/*!
174 \qmlmethod QtQuick.Particles::ParticleSystem::start()
175
176 Starts the simulation if it has not already running.
177
178 \sa stop, restart, running
179*/
180
181/*!
182 \qmlmethod QtQuick.Particles::ParticleSystem::stop()
183
184 Stops the simulation if it is running.
185
186 \sa start, restart, running
187*/
188
189/*!
190 \qmlmethod QtQuick.Particles::ParticleSystem::restart()
191
192 Stops the simulation if it is running, and then starts it.
193
194 \sa start, stop, running
195*/
196/*!
197 \qmlmethod QtQuick.Particles::ParticleSystem::reset()
198
199 Discards all currently existing particles.
200
201*/
202
203static inline int roundedTime(qreal a)
204{// in ms
205 return (int)qRound(d: a*1000.0);
206}
207
208QQuickParticleDataHeap::QQuickParticleDataHeap()
209 : m_data(0)
210{
211 m_data.reserve(size: 1000);
212 clear();
213}
214
215void QQuickParticleDataHeap::grow() //###Consider automatic growth vs resize() calls from GroupData
216{
217 m_data.resize(size: 1 << ++m_size);
218}
219
220void QQuickParticleDataHeap::insert(QQuickParticleData* data)
221{
222 insertTimed(data, time: roundedTime(a: data->t + data->lifeSpan));
223}
224
225void QQuickParticleDataHeap::insertTimed(QQuickParticleData* data, int time)
226{
227 //TODO: Optimize 0 lifespan (or already dead) case
228 if (m_lookups.contains(key: time)) {
229 m_data[m_lookups[time]].data << data;
230 return;
231 }
232 if (m_end == (1 << m_size))
233 grow();
234 m_data[m_end].time = time;
235 m_data[m_end].data.clear();
236 m_data[m_end].data.insert(value: data);
237 m_lookups.insert(key: time, value: m_end);
238 bubbleUp(m_end++);
239}
240
241int QQuickParticleDataHeap::top()
242{
243 if (m_end == 0)
244 return 1 << 30;
245 return m_data[0].time;
246}
247
248QSet<QQuickParticleData*> QQuickParticleDataHeap::pop()
249{
250 if (!m_end)
251 return QSet<QQuickParticleData*> ();
252 QSet<QQuickParticleData*> ret = m_data[0].data;
253 m_lookups.remove(key: m_data[0].time);
254 if (m_end == 1) {
255 --m_end;
256 } else {
257 m_data[0] = m_data[--m_end];
258 bubbleDown(0);
259 }
260 return ret;
261}
262
263void QQuickParticleDataHeap::clear()
264{
265 m_size = 0;
266 m_end = 0;
267 //m_size is in powers of two. So to start at 0 we have one allocated
268 m_data.resize(size: 1);
269 m_lookups.clear();
270}
271
272bool QQuickParticleDataHeap::contains(QQuickParticleData* d)
273{
274 for (int i=0; i<m_end; i++)
275 if (m_data[i].data.contains(value: d))
276 return true;
277 return false;
278}
279
280void QQuickParticleDataHeap::swap(int a, int b)
281{
282 m_tmp = m_data[a];
283 m_data[a] = m_data[b];
284 m_data[b] = m_tmp;
285 m_lookups[m_data[a].time] = a;
286 m_lookups[m_data[b].time] = b;
287}
288
289void QQuickParticleDataHeap::bubbleUp(int idx)//tends to be called once
290{
291 if (!idx)
292 return;
293 int parent = (idx-1)/2;
294 if (m_data[idx].time < m_data[parent].time) {
295 swap(a: idx, b: parent);
296 bubbleUp(idx: parent);
297 }
298}
299
300void QQuickParticleDataHeap::bubbleDown(int idx)//tends to be called log n times
301{
302 int left = idx*2 + 1;
303 if (left >= m_end)
304 return;
305 int lesser = left;
306 int right = idx*2 + 2;
307 if (right < m_end) {
308 if (m_data[left].time > m_data[right].time)
309 lesser = right;
310 }
311 if (m_data[idx].time > m_data[lesser].time) {
312 swap(a: idx, b: lesser);
313 bubbleDown(idx: lesser);
314 }
315}
316
317QQuickParticleGroupData::QQuickParticleGroupData(const QString &name, QQuickParticleSystem* sys)
318 : index(sys->registerParticleGroupData(name, pgd: this))
319 , m_size(0)
320 , m_system(sys)
321{
322 initList();
323}
324
325QQuickParticleGroupData::~QQuickParticleGroupData()
326{
327 foreach (QQuickParticleData* d, data)
328 delete d;
329}
330
331QString QQuickParticleGroupData::name()//### Worth caching as well?
332{
333 return m_system->groupIds.key(value: index);
334}
335
336void QQuickParticleGroupData::setSize(int newSize)
337{
338 if (newSize == m_size)
339 return;
340 Q_ASSERT(newSize > m_size);//XXX allow shrinking
341 data.resize(size: newSize);
342 freeList.resize(newSize);
343 for (int i=m_size; i<newSize; i++) {
344 data[i] = new QQuickParticleData;
345 data[i]->groupId = index;
346 data[i]->index = i;
347 }
348 int delta = newSize - m_size;
349 m_size = newSize;
350 foreach (QQuickParticlePainter* p, painters)
351 p->setCount(p->count() + delta);
352}
353
354void QQuickParticleGroupData::initList()
355{
356 dataHeap.clear();
357}
358
359void QQuickParticleGroupData::kill(QQuickParticleData* d)
360{
361 Q_ASSERT(d->groupId == index);
362 d->lifeSpan = 0;//Kill off
363 foreach (QQuickParticlePainter* p, painters)
364 p->reload(d);
365 freeList.free(index: d->index);
366}
367
368QQuickParticleData* QQuickParticleGroupData::newDatum(bool respectsLimits)
369{
370 //recycle();//Extra recycler round to be sure?
371
372 while (freeList.hasUnusedEntries()) {
373 int idx = freeList.alloc();
374 if (data[idx]->stillAlive(system: m_system)) {// ### This means resurrection of 'dead' particles. Is that allowed?
375 prepareRecycler(d: data[idx]);
376 continue;
377 }
378 return data[idx];
379 }
380 if (respectsLimits)
381 return nullptr;
382
383 int oldSize = m_size;
384 setSize(oldSize + 10);//###+1,10%,+10? Choose something non-arbitrarily
385 int idx = freeList.alloc();
386 Q_ASSERT(idx == oldSize);
387 return data[idx];
388}
389
390bool QQuickParticleGroupData::recycle()
391{
392 m_latestAliveParticles.clear();
393
394 while (dataHeap.top() <= m_system->timeInt) {
395 foreach (QQuickParticleData* datum, dataHeap.pop()) {
396 if (!datum->stillAlive(system: m_system)) {
397 freeList.free(index: datum->index);
398 } else {
399 m_latestAliveParticles.push_back(t: datum);
400 }
401 }
402 }
403
404 for (auto particle : m_latestAliveParticles)
405 prepareRecycler(d: particle); //ttl has been altered mid-way, put it back
406
407 //TODO: If the data is clear, gc (consider shrinking stack size)?
408 return freeList.count() == 0;
409}
410
411void QQuickParticleGroupData::prepareRecycler(QQuickParticleData* d)
412{
413 if (d->lifeSpan*1000 < m_system->maxLife) {
414 dataHeap.insert(data: d);
415 } else {
416 while ((roundedTime(a: d->t) + 2*m_system->maxLife/3) <= m_system->timeInt)
417 d->extendLife(time: m_system->maxLife / 3000.0, particleSystem: m_system);
418 dataHeap.insertTimed(data: d, time: roundedTime(a: d->t) + 2*m_system->maxLife/3);
419 }
420}
421
422QQuickParticleData::QQuickParticleData()
423 : index(0)
424 , systemIndex(-1)
425 , groupId(0)
426 , colorOwner(nullptr)
427 , rotationOwner(nullptr)
428 , deformationOwner(nullptr)
429 , animationOwner(nullptr)
430 , v8Datum(nullptr)
431{
432 x = 0;
433 y = 0;
434 t = -1;
435 lifeSpan = 0;
436 size = 0;
437 endSize = 0;
438 vx = 0;
439 vy = 0;
440 ax = 0;
441 ay = 0;
442 xx = 1;
443 xy = 0;
444 yx = 0;
445 yy = 1;
446 rotation = 0;
447 rotationVelocity = 0;
448 autoRotate = 0;
449 animIdx = 0;
450 frameDuration = 1;
451 frameAt = -1;
452 frameCount = 1;
453 animT = -1;
454 animX = 0;
455 animY = 0;
456 animWidth = 1;
457 animHeight = 1;
458 color.r = 255;
459 color.g = 255;
460 color.b = 255;
461 color.a = 255;
462 r = 0;
463 delegate = nullptr;
464 modelIndex = -1;
465}
466
467QQuickParticleData::~QQuickParticleData()
468{
469 delete v8Datum;
470}
471
472QQuickParticleData::QQuickParticleData(const QQuickParticleData &other)
473{
474 *this = other;
475}
476
477QQuickParticleData &QQuickParticleData::operator=(const QQuickParticleData &other)
478{
479 clone(other);
480
481 groupId = other.groupId;
482 index = other.index;
483 systemIndex = other.systemIndex;
484 // Lazily initialized
485 v8Datum = nullptr;
486
487 return *this;
488}
489
490void QQuickParticleData::clone(const QQuickParticleData& other)
491{
492 x = other.x;
493 y = other.y;
494 t = other.t;
495 lifeSpan = other.lifeSpan;
496 size = other.size;
497 endSize = other.endSize;
498 vx = other.vx;
499 vy = other.vy;
500 ax = other.ax;
501 ay = other.ay;
502 xx = other.xx;
503 xy = other.xy;
504 yx = other.yx;
505 yy = other.yy;
506 rotation = other.rotation;
507 rotationVelocity = other.rotationVelocity;
508 autoRotate = other.autoRotate;
509 animIdx = other.animIdx;
510 frameDuration = other.frameDuration;
511 frameCount = other.frameCount;
512 animT = other.animT;
513 animX = other.animX;
514 animY = other.animY;
515 animWidth = other.animWidth;
516 animHeight = other.animHeight;
517 color.r = other.color.r;
518 color.g = other.color.g;
519 color.b = other.color.b;
520 color.a = other.color.a;
521 r = other.r;
522 delegate = other.delegate;
523 modelIndex = other.modelIndex;
524
525 colorOwner = other.colorOwner;
526 rotationOwner = other.rotationOwner;
527 deformationOwner = other.deformationOwner;
528 animationOwner = other.animationOwner;
529}
530
531QV4::ReturnedValue QQuickParticleData::v4Value(QQuickParticleSystem* particleSystem)
532{
533 if (!v8Datum)
534 v8Datum = new QQuickV4ParticleData(qmlEngine(particleSystem)->handle(), this, particleSystem);
535 return v8Datum->v4Value();
536}
537
538void QQuickParticleData::debugDump(QQuickParticleSystem* particleSystem) const
539{
540 qDebug() << "Particle" << systemIndex << groupId << "/" << index << stillAlive(system: particleSystem)
541 << "Pos: " << x << "," << y
542 << "Vel: " << vx << "," << vy
543 << "Acc: " << ax << "," << ay
544 << "Size: " << size << "," << endSize
545 << "Time: " << t << "," <<lifeSpan << ";" << (particleSystem->timeInt / 1000.0) ;
546}
547
548void QQuickParticleData::extendLife(float time, QQuickParticleSystem* particleSystem)
549{
550 qreal newX = curX(particleSystem);
551 qreal newY = curY(particleSystem);
552 qreal newVX = curVX(particleSystem);
553 qreal newVY = curVY(particleSystem);
554
555 t += time;
556 animT += time;
557
558 qreal elapsed = (particleSystem->timeInt / 1000.0) - t;
559 qreal evy = newVY - elapsed*ay;
560 qreal ey = newY - elapsed*evy - 0.5 * elapsed*elapsed*ay;
561 qreal evx = newVX - elapsed*ax;
562 qreal ex = newX - elapsed*evx - 0.5 * elapsed*elapsed*ax;
563
564 x = ex;
565 vx = evx;
566 y = ey;
567 vy = evy;
568}
569
570QQuickParticleSystem::QQuickParticleSystem(QQuickItem *parent) :
571 QQuickItem(parent),
572 stateEngine(nullptr),
573 nextFreeGroupId(0),
574 m_animation(nullptr),
575 m_running(true),
576 initialized(0),
577 particleCount(0),
578 m_nextIndex(0),
579 m_componentComplete(false),
580 m_paused(false),
581 m_empty(true)
582{
583 m_debugMode = qmlParticlesDebug();
584}
585
586QQuickParticleSystem::~QQuickParticleSystem()
587{
588 foreach (QQuickParticleGroupData* gd, groupData)
589 delete gd;
590}
591
592void QQuickParticleSystem::initGroups()
593{
594 m_reusableIndexes.clear();
595 m_nextIndex = 0;
596
597 qDeleteAll(c: groupData);
598 groupData.clear();
599 groupIds.clear();
600 nextFreeGroupId = 0;
601
602 for (auto e : qAsConst(t&: m_emitters)) {
603 e->reclaculateGroupId();
604 }
605 foreach (QQuickParticlePainter *p, m_painters) {
606 p->recalculateGroupIds();
607 }
608
609 QQuickParticleGroupData *pd = new QQuickParticleGroupData(QString(), this); // Default group
610 Q_ASSERT(pd->index == 0);
611 Q_UNUSED(pd);
612}
613
614void QQuickParticleSystem::registerParticlePainter(QQuickParticlePainter* p)
615{
616 if (m_debugMode)
617 qDebug() << "Registering Painter" << p << "to" << this;
618 //TODO: a way to Unregister emitters, painters and affectors
619 m_painters << QPointer<QQuickParticlePainter>(p);//###Set or uniqueness checking?
620
621 connect(sender: p, signal: &QQuickParticlePainter::groupsChanged, context: this, slot: [this, p] { this->loadPainter(p); }, type: Qt::QueuedConnection);
622 loadPainter(p);
623}
624
625void QQuickParticleSystem::registerParticleEmitter(QQuickParticleEmitter* e)
626{
627 if (m_debugMode)
628 qDebug() << "Registering Emitter" << e << "to" << this;
629 m_emitters << QPointer<QQuickParticleEmitter>(e);//###How to get them out?
630}
631
632void QQuickParticleSystem::finishRegisteringParticleEmitter(QQuickParticleEmitter* e)
633{
634 connect(sender: e, SIGNAL(particleCountChanged()),
635 receiver: this, SLOT(emittersChanged()));
636 connect(sender: e, SIGNAL(groupChanged(QString)),
637 receiver: this, SLOT(emittersChanged()));
638 if (m_componentComplete)
639 emittersChanged();
640 e->reset();//Start, so that starttime factors appropriately
641}
642
643void QQuickParticleSystem::registerParticleAffector(QQuickParticleAffector* a)
644{
645 if (m_debugMode)
646 qDebug() << "Registering Affector" << a << "to" << this;
647 m_affectors << QPointer<QQuickParticleAffector>(a);
648}
649
650void QQuickParticleSystem::registerParticleGroup(QQuickParticleGroup* g)
651{
652 if (m_debugMode)
653 qDebug() << "Registering Group" << g << "to" << this;
654 m_groups << QPointer<QQuickParticleGroup>(g);
655 createEngine();
656}
657
658void QQuickParticleSystem::setRunning(bool arg)
659{
660 if (m_running != arg) {
661 m_running = arg;
662 emit runningChanged(arg);
663 setPaused(false);
664 if (m_animation)//Not created until componentCompleted
665 m_running ? m_animation->start() : m_animation->stop();
666 reset();
667 }
668}
669
670void QQuickParticleSystem::setPaused(bool arg) {
671 if (m_paused != arg) {
672 m_paused = arg;
673 if (m_animation && m_animation->state() != QAbstractAnimation::Stopped)
674 m_paused ? m_animation->pause() : m_animation->resume();
675 if (!m_paused) {
676 foreach (QQuickParticlePainter *p, m_painters) {
677 if (p) {
678 p->update();
679 }
680 }
681 }
682 emit pausedChanged(arg);
683 }
684}
685
686void QQuickParticleSystem::statePropertyRedirect(QQmlListProperty<QObject> *prop, QObject *value)
687{
688 //Hooks up automatic state-associated stuff
689 QQuickParticleSystem* sys = qobject_cast<QQuickParticleSystem*>(object: prop->object->parent());
690 QQuickParticleGroup* group = qobject_cast<QQuickParticleGroup*>(object: prop->object);
691 if (!group || !sys || !value)
692 return;
693 stateRedirect(group, sys, value);
694}
695
696void QQuickParticleSystem::stateRedirect(QQuickParticleGroup* group, QQuickParticleSystem* sys, QObject *value)
697{
698 QStringList list;
699 list << group->name();
700 QQuickParticleAffector* a = qobject_cast<QQuickParticleAffector*>(object: value);
701 if (a) {
702 a->setParentItem(sys);
703 a->setGroups(list);
704 a->setSystem(sys);
705 return;
706 }
707 QQuickTrailEmitter* fe = qobject_cast<QQuickTrailEmitter*>(object: value);
708 if (fe) {
709 fe->setParentItem(sys);
710 fe->setFollow(group->name());
711 fe->setSystem(sys);
712 return;
713 }
714 QQuickParticleEmitter* e = qobject_cast<QQuickParticleEmitter*>(object: value);
715 if (e) {
716 e->setParentItem(sys);
717 e->setGroup(group->name());
718 e->setSystem(sys);
719 return;
720 }
721 QQuickParticlePainter* p = qobject_cast<QQuickParticlePainter*>(object: value);
722 if (p) {
723 p->setParentItem(sys);
724 p->setGroups(list);
725 p->setSystem(sys);
726 return;
727 }
728 qWarning() << value << " was placed inside a particle system state but cannot be taken into the particle system. It will be lost.";
729}
730
731
732int QQuickParticleSystem::registerParticleGroupData(const QString &name, QQuickParticleGroupData *pgd)
733{
734 Q_ASSERT(!groupIds.contains(name));
735 int id;
736 if (nextFreeGroupId >= groupData.size()) {
737 groupData.push_back(t: pgd);
738 nextFreeGroupId = groupData.size();
739 id = nextFreeGroupId - 1;
740 } else {
741 id = nextFreeGroupId;
742 groupData[id] = pgd;
743 searchNextFreeGroupId();
744 }
745 groupIds.insert(key: name, value: id);
746 return id;
747}
748
749void QQuickParticleSystem::searchNextFreeGroupId()
750{
751 ++nextFreeGroupId;
752 for (int ei = groupData.size(); nextFreeGroupId != ei; ++nextFreeGroupId) {
753 if (groupData[nextFreeGroupId] == nullptr) {
754 return;
755 }
756 }
757}
758
759void QQuickParticleSystem::componentComplete()
760
761{
762 QQuickItem::componentComplete();
763 m_componentComplete = true;
764 m_animation = new QQuickParticleSystemAnimation(this);
765 reset();//restarts animation as well
766}
767
768void QQuickParticleSystem::reset()
769{
770 if (!m_componentComplete)
771 return;
772
773 timeInt = 0;
774 //Clear guarded pointers which have been deleted
775 int cleared = 0;
776 cleared += m_emitters.removeAll(t: nullptr);
777 cleared += m_painters.removeAll(t: nullptr);
778 cleared += m_affectors.removeAll(t: nullptr);
779
780 bySysIdx.resize(size: 0);
781 initGroups();//Also clears all logical particles
782
783 if (!m_running)
784 return;
785
786 foreach (QQuickParticleEmitter* e, m_emitters)
787 e->reset();
788
789 emittersChanged();
790
791 foreach (QQuickParticlePainter *p, m_painters) {
792 loadPainter(p);
793 p->reset();
794 }
795
796 //### Do affectors need reset too?
797 if (m_animation) {//Animation is explicitly disabled in benchmarks
798 //reset restarts animation (if running)
799 if ((m_animation->state() == QAbstractAnimation::Running))
800 m_animation->stop();
801 m_animation->start();
802 if (m_paused)
803 m_animation->pause();
804 }
805
806 initialized = true;
807}
808
809
810void QQuickParticleSystem::loadPainter(QQuickParticlePainter *painter)
811{
812 if (!m_componentComplete || !painter)
813 return;
814
815 for (QQuickParticleGroupData* sg : groupData) {
816 sg->painters.removeOne(element: painter);
817 }
818
819 int particleCount = 0;
820 if (painter->groups().isEmpty()) {//Uses default particle
821 static QStringList def = QStringList() << QString();
822 painter->setGroups(def);
823 particleCount += groupData[0]->size();
824 groupData[0]->painters << painter;
825 } else {
826 for (auto groupId : painter->groupIds()) {
827 QQuickParticleGroupData *gd = groupData[groupId];
828 particleCount += gd->size();
829 gd->painters << painter;
830 }
831 }
832 painter->setCount(particleCount);
833 painter->update();//Initial update here
834 return;
835}
836
837void QQuickParticleSystem::emittersChanged()
838{
839 if (!m_componentComplete)
840 return;
841
842 QVector<int> previousSizes;
843 QVector<int> newSizes;
844 previousSizes.reserve(size: groupData.size());
845 newSizes.reserve(size: groupData.size());
846 for (int i = 0, ei = groupData.size(); i != ei; ++i) {
847 previousSizes << groupData[i]->size();
848 newSizes << 0;
849 }
850
851 // Populate groups and set sizes.
852 for (int i = 0; i < m_emitters.count(); ) {
853 QQuickParticleEmitter *e = m_emitters.at(i);
854 if (!e) {
855 m_emitters.removeAt(i);
856 continue;
857 }
858
859 int groupId = e->groupId();
860 if (groupId == QQuickParticleGroupData::InvalidID) {
861 groupId = (new QQuickParticleGroupData(e->group(), this))->index;
862 previousSizes << 0;
863 newSizes << 0;
864 }
865 newSizes[groupId] += e->particleCount();
866 //###: Cull emptied groups?
867
868 ++i;
869 }
870
871 //TODO: Garbage collection?
872 particleCount = 0;
873 for (int i = 0, ei = groupData.size(); i != ei; ++i) {
874 groupData[i]->setSize(qMax(a: newSizes[i], b: previousSizes[i]));
875 particleCount += groupData[i]->size();
876 }
877
878 if (m_debugMode)
879 qDebug() << "Particle system emitters changed. New particle count: " << particleCount << "in" << groupData.size() << "groups.";
880
881 if (particleCount > bySysIdx.size())//New datum requests haven't updated it
882 bySysIdx.resize(size: particleCount);
883
884 foreach (QQuickParticleAffector *a, m_affectors) {//Groups may have changed
885 if (a) {
886 a->m_updateIntSet = true;
887 }
888 }
889
890 foreach (QQuickParticlePainter *p, m_painters)
891 loadPainter(painter: p);
892
893 if (!m_groups.isEmpty())
894 createEngine();
895
896}
897
898void QQuickParticleSystem::createEngine()
899{
900 if (!m_componentComplete)
901 return;
902 if (stateEngine && m_debugMode)
903 qDebug() << "Resetting Existing Sprite Engine...";
904 //### Solve the losses if size/states go down
905 foreach (QQuickParticleGroup* group, m_groups) {
906 bool exists = false;
907 for (auto it = groupIds.keyBegin(), end = groupIds.keyEnd(); it != end; ++it) {
908 if (group->name() == *it) {
909 exists = true;
910 break;
911 }
912 }
913 if (!exists) {
914 new QQuickParticleGroupData(group->name(), this);
915 }
916 }
917
918 if (m_groups.count()) {
919 //Reorder groups List so as to have the same order as groupData
920 // TODO: can't we just merge the two lists?
921 QList<QQuickParticleGroup*> newList;
922 for (int i = 0, ei = groupData.size(); i != ei; ++i) {
923 bool exists = false;
924 QString name = groupData[i]->name();
925 foreach (QQuickParticleGroup* existing, m_groups) {
926 if (existing->name() == name) {
927 newList << existing;
928 exists = true;
929 }
930 }
931 if (!exists) {
932 newList << new QQuickParticleGroup(this);
933 newList.back()->setName(name);
934 }
935 }
936 m_groups = newList;
937 QList<QQuickStochasticState*> states;
938 states.reserve(size: m_groups.count());
939 foreach (QQuickParticleGroup* g, m_groups)
940 states << (QQuickStochasticState*)g;
941
942 if (!stateEngine)
943 stateEngine = new QQuickStochasticEngine(this);
944 stateEngine->setCount(particleCount);
945 stateEngine->m_states = states;
946
947 connect(sender: stateEngine, SIGNAL(stateChanged(int)),
948 receiver: this, SLOT(particleStateChange(int)));
949
950 } else {
951 if (stateEngine)
952 delete stateEngine;
953 stateEngine = nullptr;
954 }
955
956}
957
958void QQuickParticleSystem::particleStateChange(int idx)
959{
960 moveGroups(d: bySysIdx[idx], newGIdx: stateEngine->curState(index: idx));
961}
962
963void QQuickParticleSystem::moveGroups(QQuickParticleData *d, int newGIdx)
964{
965 if (!d || newGIdx == d->groupId)
966 return;
967
968 QQuickParticleData* pd = newDatum(groupId: newGIdx, respectLimits: false, sysIdx: d->systemIndex);
969 if (!pd)
970 return;
971
972 pd->clone(other: *d);
973 finishNewDatum(pd);
974
975 d->systemIndex = -1;
976 groupData[d->groupId]->kill(d);
977}
978
979int QQuickParticleSystem::nextSystemIndex()
980{
981 if (!m_reusableIndexes.isEmpty()) {
982 int ret = *(m_reusableIndexes.begin());
983 m_reusableIndexes.remove(value: ret);
984 return ret;
985 }
986 if (m_nextIndex >= bySysIdx.size()) {
987 bySysIdx.resize(size: bySysIdx.size() < 10 ? 10 : bySysIdx.size()*1.1);//###+1,10%,+10? Choose something non-arbitrarily
988 if (stateEngine)
989 stateEngine->setCount(bySysIdx.size());
990
991 }
992 return m_nextIndex++;
993}
994
995QQuickParticleData* QQuickParticleSystem::newDatum(int groupId, bool respectLimits, int sysIndex)
996{
997 Q_ASSERT(groupId < groupData.count());//XXX shouldn't really be an assert
998
999 QQuickParticleData* ret = groupData[groupId]->newDatum(respectsLimits: respectLimits);
1000 if (!ret) {
1001 return nullptr;
1002 }
1003 if (sysIndex == -1) {
1004 if (ret->systemIndex == -1)
1005 ret->systemIndex = nextSystemIndex();
1006 } else {
1007 if (ret->systemIndex != -1) {
1008 if (stateEngine)
1009 stateEngine->stop(index: ret->systemIndex);
1010 m_reusableIndexes << ret->systemIndex;
1011 bySysIdx[ret->systemIndex] = 0;
1012 }
1013 ret->systemIndex = sysIndex;
1014 }
1015 bySysIdx[ret->systemIndex] = ret;
1016
1017 if (stateEngine)
1018 stateEngine->start(index: ret->systemIndex, state: ret->groupId);
1019
1020 m_empty = false;
1021 return ret;
1022}
1023
1024void QQuickParticleSystem::emitParticle(QQuickParticleData* pd, QQuickParticleEmitter* particleEmitter)
1025{// called from prepareNextFrame()->emitWindow - enforce?
1026 //Account for relative emitter position
1027 bool okay = false;
1028 QTransform t = particleEmitter->itemTransform(this, &okay);
1029 if (okay) {
1030 qreal tx,ty;
1031 t.map(x: pd->x, y: pd->y, tx: &tx, ty: &ty);
1032 pd->x = tx;
1033 pd->y = ty;
1034 }
1035
1036 finishNewDatum(pd);
1037}
1038
1039void QQuickParticleSystem::finishNewDatum(QQuickParticleData *pd)
1040{
1041 Q_ASSERT(pd);
1042 groupData[pd->groupId]->prepareRecycler(d: pd);
1043
1044 foreach (QQuickParticleAffector *a, m_affectors)
1045 if (a && a->m_needsReset)
1046 a->reset(pd);
1047 foreach (QQuickParticlePainter* p, groupData[pd->groupId]->painters)
1048 if (p)
1049 p->load(pd);
1050}
1051
1052void QQuickParticleSystem::updateCurrentTime( int currentTime )
1053{
1054 if (!initialized)
1055 return;//error in initialization
1056
1057 //### Elapsed time never shrinks - may cause problems if left emitting for weeks at a time.
1058 qreal dt = timeInt / 1000.;
1059 timeInt = currentTime;
1060 qreal time = timeInt / 1000.;
1061 dt = time - dt;
1062 needsReset.clear();
1063
1064 m_emitters.removeAll(t: nullptr);
1065 m_painters.removeAll(t: nullptr);
1066 m_affectors.removeAll(t: nullptr);
1067
1068 bool oldClear = m_empty;
1069 m_empty = true;
1070 foreach (QQuickParticleGroupData* gd, groupData)//Recycle all groups and see if they're out of live particles
1071 m_empty = gd->recycle() && m_empty;
1072
1073 if (stateEngine)
1074 stateEngine->updateSprites(time: timeInt);
1075
1076 foreach (QQuickParticleEmitter* emitter, m_emitters)
1077 emitter->emitWindow(timeStamp: timeInt);
1078 foreach (QQuickParticleAffector* a, m_affectors)
1079 a->affectSystem(dt);
1080 for (QQuickParticleData* d : needsReset)
1081 foreach (QQuickParticlePainter* p, groupData[d->groupId]->painters)
1082 p->reload(d);
1083
1084 if (oldClear != m_empty)
1085 emptyChanged(arg: m_empty);
1086}
1087
1088int QQuickParticleSystem::systemSync(QQuickParticlePainter* p)
1089{
1090 if (!m_running)
1091 return 0;
1092 if (!initialized)
1093 return 0;//error in initialization
1094 p->performPendingCommits();
1095 return timeInt;
1096}
1097
1098
1099QT_END_NAMESPACE
1100
1101#include "moc_qquickparticlesystem_p.cpp"
1102

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