1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQuick module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qquickparticlesystem_p.h"
43 #include <QtQuick/qsgnode.h>
44 #include "qquickparticleemitter_p.h"
45 #include "qquickparticleaffector_p.h"
46 #include "qquickparticlepainter_p.h"
47 #include <private/qquickspriteengine_p.h>
48 #include <private/qquicksprite_p.h>
49 #include "qquickv8particledata_p.h"
50 #include "qquickparticlegroup_p.h"
52 #include "qquicktrailemitter_p.h"//###For auto-follow on states, perhaps should be in emitter?
53 #include <private/qqmlengine_p.h>
54 #include <private/qqmlglobal_p.h>
59 //###Switch to define later, for now user-friendly (no compilation) debugging is worth it
60 DEFINE_BOOL_CONFIG_OPTION(qmlParticlesDebug, QML_PARTICLES_DEBUG)
63 /* \internal ParticleSystem internals documentation
65 Affectors, Painters, Emitters and Groups all register themselves on construction as a callback
66 from their setSystem (or componentComplete if they have a system from a parent).
68 Particle data is stored by group, They have a group index (used by the particle system almost
69 everywhere) and a global index (used by the Stochastic state engine powering stochastic group
70 transitions). Each group has a recycling list/heap that stores the particle data.
72 The recycling list/heap is a heap of particle data sorted by when they're expected to die. If
73 they die prematurely then they are marked as reusable (and will probably still be alive when
74 they exit the heap). If they have their life extended, then they aren't dead when expected.
75 If this happens, they go back in the heap with the new estimate. If they have died on schedule,
76 then the indexes are marked as reusable. If no indexes are reusable when new particles are
77 requested, then the list is extended. This relatively complex datastructure is because memory
78 allocation and deallocation on this scale proved to be a significant performance cost. In order
79 to reuse the indexes validly (even when particles can have their life extended or cut short
80 dynamically, or particle counts grow) this seemed to be the most efficient option for keeping
81 track of which indices could be reused.
83 When a new particle is emitted, the emitter gets a new datum from the group (through the
84 system), and sets properties on it. Then it's passed back to the group briefly so that it can
85 now guess when the particle will die. Then the painters get a change to initialize properties
86 as well, since particle data includes shared data from painters as well as logical particle
89 Every animation advance, the simulation advances by running all emitters for the elapsed
90 duration, then running all affectors, then telling all particle painters to update changed
91 particles. The ParticlePainter superclass stores these changes, and they are implemented
92 when the painter is called to paint in the render thread.
94 Particle group changes move the particle from one group to another by killing the old particle
95 and then creating a new one with the same data in the new group.
97 Note that currently groups only grow. Given that data is stored in vectors, it is non-trivial
98 to pluck out the unused indexes when the count goes down. Given the dynamic nature of the
99 system, it is difficult to tell if those unused data instances will be used again. Still,
100 some form of garbage collection is on the long term plan.
104 \qmlclass ParticleSystem QQuickParticleSystem
105 \inqmlmodule QtQuick.Particles 2
106 \brief A system which includes particle painter, emitter, and affector types
107 \ingroup qtquick-particles
112 \qmlproperty bool QtQuick.Particles2::ParticleSystem::running
114 If running is set to false, the particle system will stop the simulation. All particles
115 will be destroyed when the system is set to running again.
117 It can also be controlled with the start() and stop() methods.
122 \qmlproperty bool QtQuick.Particles2::ParticleSystem::paused
124 If paused is set to true, the particle system will not advance the simulation. When
125 paused is set to false again, the simulation will resume from the same point it was
128 The simulation will automatically pause if it detects that there are no live particles
129 left, and unpause when new live particles are added.
131 It can also be controlled with the pause() and resume() methods.
135 \qmlproperty bool QtQuick.Particles2::ParticleSystem::empty
137 empty is set to true when there are no live particles left in the system.
139 You can use this to pause the system, keeping it from spending any time updating,
140 but you will need to resume it in order for additional particles to be generated
143 To kill all the particles in the system, use a Kill affector.
147 \qmlproperty list<Sprite> QtQuick.Particles2::ParticleSystem::particleStates
149 You can define a sub-set of particle groups in this property in order to provide them
150 with stochastic state transitions.
152 Each QtQuick2::Sprite in this list is interpreted as corresponding to the particle group
153 with ths same name. Any transitions defined in these sprites will take effect on the particle
154 groups as well. Additionally TrailEmitters, Affectors and ParticlePainters definined
155 inside one of these sprites are automatically associated with the corresponding particle group.
159 \qmlmethod void QtQuick.Particles2::ParticleSystem::pause
161 Pauses the simulation if it is running.
167 \qmlmethod void QtQuick.Particles2::ParticleSystem::resume
169 Resumes the simulation if it is paused.
175 \qmlmethod void QtQuick.Particles2::ParticleSystem::start
177 Starts the simulation if it has not already running.
179 \sa stop, restart, running
183 \qmlmethod void QtQuick.Particles2::ParticleSystem::stop
185 Stops the simulation if it is running.
187 \sa start, restart, running
191 \qmlmethod void QtQuick.Particles2::ParticleSystem::restart
193 Stops the simulation if it is running, and then starts it.
195 \sa stop, restart, running
198 \qmlmethod void QtQuick.Particles2::ParticleSystem::reset
200 Discards all currently existing particles.
203 const qreal EPSILON = 0.001;
204 //Utility functions for when within 1ms is close enough
205 bool timeEqualOrGreater(qreal a, qreal b)
207 return (a+EPSILON >= b);
210 bool timeLess(qreal a, qreal b)
212 return (a-EPSILON < b);
215 bool timeEqual(qreal a, qreal b)
217 return (a+EPSILON > b) && (a-EPSILON < b);
220 int roundedTime(qreal a)
222 return (int)qRound(a*1000.0);
225 QQuickParticleDataHeap::QQuickParticleDataHeap()
228 m_data.reserve(1000);
232 void QQuickParticleDataHeap::grow() //###Consider automatic growth vs resize() calls from GroupData
234 m_data.resize(1 << ++m_size);
237 void QQuickParticleDataHeap::insert(QQuickParticleData* data)
239 insertTimed(data, roundedTime(data->t + data->lifeSpan));
242 void QQuickParticleDataHeap::insertTimed(QQuickParticleData* data, int time)
244 //TODO: Optimize 0 lifespan (or already dead) case
245 if (m_lookups.contains(time)) {
246 m_data[m_lookups[time]].data << data;
249 if (m_end == (1 << m_size))
251 m_data[m_end].time = time;
252 m_data[m_end].data.clear();
253 m_data[m_end].data.insert(data);
254 m_lookups.insert(time, m_end);
258 int QQuickParticleDataHeap::top()
262 return m_data[0].time;
265 QSet<QQuickParticleData*> QQuickParticleDataHeap::pop()
268 return QSet<QQuickParticleData*> ();
269 QSet<QQuickParticleData*> ret = m_data[0].data;
270 m_lookups.remove(m_data[0].time);
274 m_data[0] = m_data[--m_end];
280 void QQuickParticleDataHeap::clear()
284 //m_size is in powers of two. So to start at 0 we have one allocated
289 bool QQuickParticleDataHeap::contains(QQuickParticleData* d)
291 for (int i=0; i<m_end; i++)
292 if (m_data[i].data.contains(d))
297 void QQuickParticleDataHeap::swap(int a, int b)
300 m_data[a] = m_data[b];
302 m_lookups[m_data[a].time] = a;
303 m_lookups[m_data[b].time] = b;
306 void QQuickParticleDataHeap::bubbleUp(int idx)//tends to be called once
310 int parent = (idx-1)/2;
311 if (m_data[idx].time < m_data[parent].time) {
317 void QQuickParticleDataHeap::bubbleDown(int idx)//tends to be called log n times
319 int left = idx*2 + 1;
323 int right = idx*2 + 2;
325 if (m_data[left].time > m_data[right].time)
328 if (m_data[idx].time > m_data[lesser].time) {
334 QQuickParticleGroupData::QQuickParticleGroupData(int id, QQuickParticleSystem* sys):index(id),m_size(0),m_system(sys)
339 QQuickParticleGroupData::~QQuickParticleGroupData()
341 foreach (QQuickParticleData* d, data)
345 int QQuickParticleGroupData::size()
350 QString QQuickParticleGroupData::name()//### Worth caching as well?
352 return m_system->groupIds.key(index);
355 void QQuickParticleGroupData::setSize(int newSize)
357 if (newSize == m_size)
359 Q_ASSERT(newSize > m_size);//XXX allow shrinking
360 data.resize(newSize);
361 for (int i=m_size; i<newSize; i++) {
362 data[i] = new QQuickParticleData(m_system);
363 data[i]->group = index;
365 reusableIndexes << i;
367 int delta = newSize - m_size;
369 foreach (QQuickParticlePainter* p, painters)
370 p->setCount(p->count() + delta);
373 void QQuickParticleGroupData::initList()
378 void QQuickParticleGroupData::kill(QQuickParticleData* d)
380 Q_ASSERT(d->group == index);
381 d->lifeSpan = 0;//Kill off
382 foreach (QQuickParticlePainter* p, painters)
384 reusableIndexes << d->index;
387 QQuickParticleData* QQuickParticleGroupData::newDatum(bool respectsLimits)
389 //recycle();//Extra recycler round to be sure?
391 while (!reusableIndexes.empty()) {
392 int idx = *(reusableIndexes.begin());
393 reusableIndexes.remove(idx);
394 if (data[idx]->stillAlive()) {// ### This means resurrection of 'dead' particles. Is that allowed?
395 prepareRecycler(data[idx]);
403 int oldSize = m_size;
404 setSize(oldSize + 10);//###+1,10%,+10? Choose something non-arbitrarily
405 reusableIndexes.remove(oldSize);
406 return data[oldSize];
409 bool QQuickParticleGroupData::recycle()
411 while (dataHeap.top() <= m_system->timeInt) {
412 foreach (QQuickParticleData* datum, dataHeap.pop()) {
413 if (!datum->stillAlive()) {
414 reusableIndexes << datum->index;
416 prepareRecycler(datum); //ttl has been altered mid-way, put it back
421 //TODO: If the data is clear, gc (consider shrinking stack size)?
422 return reusableIndexes.count() == m_size;
425 void QQuickParticleGroupData::prepareRecycler(QQuickParticleData* d)
427 if (d->lifeSpan*1000 < m_system->maxLife) {
430 while ((roundedTime(d->t) + 2*m_system->maxLife/3) <= m_system->timeInt)
431 d->extendLife(m_system->maxLife/3000.0);
432 dataHeap.insertTimed(d, roundedTime(d->t) + 2*m_system->maxLife/3);
436 QQuickParticleData::QQuickParticleData(QQuickParticleSystem* sys)
444 , deformationOwner(0)
483 QQuickParticleData::~QQuickParticleData()
488 void QQuickParticleData::clone(const QQuickParticleData& other)
493 lifeSpan = other.lifeSpan;
495 endSize = other.endSize;
504 rotation = other.rotation;
505 rotationSpeed = other.rotationSpeed;
506 autoRotate = other.autoRotate;
507 animIdx = other.animIdx;
508 frameDuration = other.frameDuration;
509 frameCount = other.frameCount;
513 animWidth = other.animWidth;
514 animHeight = other.animHeight;
515 color.r = other.color.r;
516 color.g = other.color.g;
517 color.b = other.color.b;
518 color.a = other.color.a;
520 delegate = other.delegate;
521 modelIndex = other.modelIndex;
523 colorOwner = other.colorOwner;
524 rotationOwner = other.rotationOwner;
525 deformationOwner = other.deformationOwner;
526 animationOwner = other.animationOwner;
529 QQmlV8Handle QQuickParticleData::v8Value()
532 v8Datum = new QQuickV8ParticleData(QQmlEnginePrivate::getV8Engine(qmlEngine(system)), this);
533 return v8Datum->v8Value();
535 //sets the x accleration without affecting the instantaneous x velocity or position
536 void QQuickParticleData::setInstantaneousAX(qreal ax)
538 qreal t = (system->timeInt / 1000.0) - this->t;
539 qreal vx = (this->vx + t*this->ax) - t*ax;
540 qreal ex = this->x + this->vx * t + 0.5 * this->ax * t * t;
541 qreal x = ex - t*vx - 0.5 * t*t*ax;
548 //sets the x velocity without affecting the instantaneous x postion
549 void QQuickParticleData::setInstantaneousVX(qreal vx)
551 qreal t = (system->timeInt / 1000.0) - this->t;
552 qreal evx = vx - t*this->ax;
553 qreal ex = this->x + this->vx * t + 0.5 * this->ax * t * t;
554 qreal x = ex - t*evx - 0.5 * t*t*this->ax;
560 //sets the instantaneous x postion
561 void QQuickParticleData::setInstantaneousX(qreal x)
563 qreal t = (system->timeInt / 1000.0) - this->t;
564 this->x = x - t*this->vx - 0.5 * t*t*this->ax;
567 //sets the y accleration without affecting the instantaneous y velocity or position
568 void QQuickParticleData::setInstantaneousAY(qreal ay)
570 qreal t = (system->timeInt / 1000.0) - this->t;
571 qreal vy = (this->vy + t*this->ay) - t*ay;
572 qreal ey = this->y + this->vy * t + 0.5 * this->ay * t * t;
573 qreal y = ey - t*vy - 0.5 * t*t*ay;
580 //sets the y velocity without affecting the instantaneous y position
581 void QQuickParticleData::setInstantaneousVY(qreal vy)
583 qreal t = (system->timeInt / 1000.0) - this->t;
584 qreal evy = vy - t*this->ay;
585 qreal ey = this->y + this->vy * t + 0.5 * this->ay * t * t;
586 qreal y = ey - t*evy - 0.5 * t*t*this->ay;
592 //sets the instantaneous Y position
593 void QQuickParticleData::setInstantaneousY(qreal y)
595 qreal t = (system->timeInt / 1000.0) - this->t;
596 this->y = y - t*this->vy - 0.5 * t*t*this->ay;
599 qreal QQuickParticleData::curX() const
601 qreal t = (system->timeInt / 1000.0) - this->t;
602 return this->x + this->vx * t + 0.5 * this->ax * t * t;
605 qreal QQuickParticleData::curVX() const
607 qreal t = (system->timeInt / 1000.0) - this->t;
608 return this->vx + t*this->ax;
611 qreal QQuickParticleData::curY() const
613 qreal t = (system->timeInt / 1000.0) - this->t;
614 return y + vy * t + 0.5 * ay * t * t;
617 qreal QQuickParticleData::curVY() const
619 qreal t = (system->timeInt / 1000.0) - this->t;
623 void QQuickParticleData::debugDump()
625 qDebug() << "Particle" << systemIndex << group << "/" << index << stillAlive()
626 << "Pos: " << x << "," << y
627 << "Vel: " << vx << "," << vy
628 << "Acc: " << ax << "," << ay
629 << "Size: " << size << "," << endSize
630 << "Time: " << t << "," <<lifeSpan << ";" << (system->timeInt / 1000.0) ;
633 bool QQuickParticleData::stillAlive()
637 return (t + lifeSpan - EPSILON) > ((qreal)system->timeInt/1000.0);
640 bool QQuickParticleData::alive()
644 qreal st = ((qreal)system->timeInt/1000.0);
645 return (t + EPSILON) < st && (t + lifeSpan - EPSILON) > st;
648 float QQuickParticleData::curSize()
650 if (!system || !lifeSpan)
652 return size + (endSize - size) * (1 - (lifeLeft() / lifeSpan));
655 float QQuickParticleData::lifeLeft()
659 return (t + lifeSpan) - (system->timeInt/1000.0);
662 void QQuickParticleData::extendLife(float time)
666 qreal newVX = curVX();
667 qreal newVY = curVY();
672 qreal elapsed = (system->timeInt / 1000.0) - t;
673 qreal evy = newVY - elapsed*ay;
674 qreal ey = newY - elapsed*evy - 0.5 * elapsed*elapsed*ay;
675 qreal evx = newVX - elapsed*ax;
676 qreal ex = newX - elapsed*evx - 0.5 * elapsed*elapsed*ax;
684 QQuickParticleSystem::QQuickParticleSystem(QQuickItem *parent) :
692 m_componentComplete(false),
696 connect(&m_painterMapper, SIGNAL(mapped(QObject*)),
697 this, SLOT(loadPainter(QObject*)));
699 m_debugMode = qmlParticlesDebug();
702 QQuickParticleSystem::~QQuickParticleSystem()
704 foreach (QQuickParticleGroupData* gd, groupData)
708 void QQuickParticleSystem::initGroups()
710 m_reusableIndexes.clear();
713 qDeleteAll(groupData);
717 QQuickParticleGroupData* gd = new QQuickParticleGroupData(0, this);//Default group
718 groupData.insert(0,gd);
719 groupIds.insert(QString(), 0);
723 void QQuickParticleSystem::registerParticlePainter(QQuickParticlePainter* p)
726 qDebug() << "Registering Painter" << p << "to" << this;
727 //TODO: a way to Unregister emitters, painters and affectors
728 m_painters << QPointer<QQuickParticlePainter>(p);//###Set or uniqueness checking?
729 connect(p, SIGNAL(groupsChanged(QStringList)),
730 &m_painterMapper, SLOT(map()));
734 void QQuickParticleSystem::registerParticleEmitter(QQuickParticleEmitter* e)
737 qDebug() << "Registering Emitter" << e << "to" << this;
738 m_emitters << QPointer<QQuickParticleEmitter>(e);//###How to get them out?
739 connect(e, SIGNAL(particleCountChanged()),
740 this, SLOT(emittersChanged()));
741 connect(e, SIGNAL(groupChanged(QString)),
742 this, SLOT(emittersChanged()));
744 e->reset();//Start, so that starttime factors appropriately
747 void QQuickParticleSystem::registerParticleAffector(QQuickParticleAffector* a)
750 qDebug() << "Registering Affector" << a << "to" << this;
751 m_affectors << QPointer<QQuickParticleAffector>(a);
754 void QQuickParticleSystem::registerParticleGroup(QQuickParticleGroup* g)
757 qDebug() << "Registering Group" << g << "to" << this;
758 m_groups << QPointer<QQuickParticleGroup>(g);
762 void QQuickParticleSystem::setRunning(bool arg)
764 if (m_running != arg) {
766 emit runningChanged(arg);
768 if (m_animation)//Not created until componentCompleted
769 m_running ? m_animation->start() : m_animation->stop();
774 void QQuickParticleSystem::setPaused(bool arg) {
775 if (m_paused != arg) {
777 if (m_animation && m_animation->state() != QAbstractAnimation::Stopped)
778 m_paused ? m_animation->pause() : m_animation->resume();
780 foreach (QQuickParticlePainter *p, m_painters)
783 emit pausedChanged(arg);
787 void QQuickParticleSystem::statePropertyRedirect(QQmlListProperty<QObject> *prop, QObject *value)
789 //Hooks up automatic state-associated stuff
790 QQuickParticleSystem* sys = qobject_cast<QQuickParticleSystem*>(prop->object->parent());
791 QQuickParticleGroup* group = qobject_cast<QQuickParticleGroup*>(prop->object);
792 if (!group || !sys || !value)
794 stateRedirect(group, sys, value);
797 void QQuickParticleSystem::stateRedirect(QQuickParticleGroup* group, QQuickParticleSystem* sys, QObject *value)
800 list << group->name();
801 QQuickParticleAffector* a = qobject_cast<QQuickParticleAffector*>(value);
803 a->setParentItem(sys);
808 QQuickTrailEmitter* fe = qobject_cast<QQuickTrailEmitter*>(value);
810 fe->setParentItem(sys);
811 fe->setFollow(group->name());
815 QQuickParticleEmitter* e = qobject_cast<QQuickParticleEmitter*>(value);
817 e->setParentItem(sys);
818 e->setGroup(group->name());
822 QQuickParticlePainter* p = qobject_cast<QQuickParticlePainter*>(value);
824 p->setParentItem(sys);
829 qWarning() << value << " was placed inside a particle system state but cannot be taken into the particle system. It will be lost.";
832 void QQuickParticleSystem::componentComplete()
835 QQuickItem::componentComplete();
836 m_componentComplete = true;
837 m_animation = new QQuickParticleSystemAnimation(this);
838 reset();//restarts animation as well
841 void QQuickParticleSystem::reset()
843 if (!m_componentComplete)
847 //Clear guarded pointers which have been deleted
849 cleared += m_emitters.removeAll(0);
850 cleared += m_painters.removeAll(0);
851 cleared += m_affectors.removeAll(0);
854 initGroups();//Also clears all logical particles
859 foreach (QQuickParticleEmitter* e, m_emitters)
864 foreach (QQuickParticlePainter *p, m_painters) {
869 //### Do affectors need reset too?
870 if (m_animation) {//Animation is explicitly disabled in benchmarks
871 //reset restarts animation (if running)
872 if ((m_animation->state() == QAbstractAnimation::Running))
874 m_animation->start();
876 m_animation->pause();
883 void QQuickParticleSystem::loadPainter(QObject *p)
885 if (!m_componentComplete || !p)
888 QQuickParticlePainter* painter = qobject_cast<QQuickParticlePainter*>(p);
889 Q_ASSERT(painter);//XXX
890 foreach (QQuickParticleGroupData* sg, groupData)
891 sg->painters.remove(painter);
892 int particleCount = 0;
893 if (painter->groups().isEmpty()) {//Uses default particle
896 painter->setGroups(def);
897 particleCount += groupData[0]->size();
898 groupData[0]->painters << painter;
900 foreach (const QString &group, painter->groups()) {
901 if (group != QLatin1String("") && !groupIds[group]) {//new group
902 int id = m_nextGroupId++;
903 QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
904 groupIds.insert(group, id);
905 groupData.insert(id, gd);
907 particleCount += groupData[groupIds[group]]->size();
908 groupData[groupIds[group]]->painters << painter;
911 painter->setCount(particleCount);
912 painter->update();//Initial update here
916 void QQuickParticleSystem::emittersChanged()
918 if (!m_componentComplete)
921 m_emitters.removeAll(0);
924 QList<int> previousSizes;
926 for (int i=0; i<m_nextGroupId; i++) {
927 previousSizes << groupData[i]->size();
931 foreach (QQuickParticleEmitter* e, m_emitters) {//Populate groups and set sizes.
932 if (!groupIds.contains(e->group())
933 || (!e->group().isEmpty() && !groupIds[e->group()])) {//or it was accidentally inserted by a failed lookup earlier
934 int id = m_nextGroupId++;
935 QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
936 groupIds.insert(e->group(), id);
937 groupData.insert(id, gd);
941 newSizes[groupIds[e->group()]] += e->particleCount();
942 //###: Cull emptied groups?
945 //TODO: Garbage collection?
947 for (int i=0; i<m_nextGroupId; i++) {
948 groupData[i]->setSize(qMax(newSizes[i], previousSizes[i]));
949 particleCount += groupData[i]->size();
953 qDebug() << "Particle system emitters changed. New particle count: " << particleCount;
955 if (particleCount > bySysIdx.size())//New datum requests haven't updated it
956 bySysIdx.resize(particleCount);
958 foreach (QQuickParticleAffector *a, m_affectors)//Groups may have changed
959 a->m_updateIntSet = true;
961 foreach (QQuickParticlePainter *p, m_painters)
964 if (!m_groups.isEmpty())
969 void QQuickParticleSystem::createEngine()
971 if (!m_componentComplete)
973 if (stateEngine && m_debugMode)
974 qDebug() << "Resetting Existing Sprite Engine...";
975 //### Solve the losses if size/states go down
976 foreach (QQuickParticleGroup* group, m_groups) {
978 foreach (const QString &name, groupIds.keys())
979 if (group->name() == name)
982 int id = m_nextGroupId++;
983 QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
984 groupIds.insert(group->name(), id);
985 groupData.insert(id, gd);
989 if (m_groups.count()) {
990 //Reorder groups List so as to have the same order as groupData
991 QList<QQuickParticleGroup*> newList;
992 for (int i=0; i<m_nextGroupId; i++) {
994 QString name = groupData[i]->name();
995 foreach (QQuickParticleGroup* existing, m_groups) {
996 if (existing->name() == name) {
1002 newList << new QQuickParticleGroup(this);
1003 newList.back()->setName(name);
1007 QList<QQuickStochasticState*> states;
1008 foreach (QQuickParticleGroup* g, m_groups)
1009 states << (QQuickStochasticState*)g;
1012 stateEngine = new QQuickStochasticEngine(this);
1013 stateEngine->setCount(particleCount);
1014 stateEngine->m_states = states;
1016 connect(stateEngine, SIGNAL(stateChanged(int)),
1017 this, SLOT(particleStateChange(int)));
1027 void QQuickParticleSystem::particleStateChange(int idx)
1029 moveGroups(bySysIdx[idx], stateEngine->curState(idx));
1032 void QQuickParticleSystem::moveGroups(QQuickParticleData *d, int newGIdx)
1034 if (!d || newGIdx == d->group)
1037 QQuickParticleData* pd = newDatum(newGIdx, false, d->systemIndex);
1044 d->systemIndex = -1;
1045 groupData[d->group]->kill(d);
1048 int QQuickParticleSystem::nextSystemIndex()
1050 if (!m_reusableIndexes.isEmpty()) {
1051 int ret = *(m_reusableIndexes.begin());
1052 m_reusableIndexes.remove(ret);
1055 if (m_nextIndex >= bySysIdx.size()) {
1056 bySysIdx.resize(bySysIdx.size() < 10 ? 10 : bySysIdx.size()*1.1);//###+1,10%,+10? Choose something non-arbitrarily
1058 stateEngine->setCount(bySysIdx.size());
1061 return m_nextIndex++;
1064 QQuickParticleData* QQuickParticleSystem::newDatum(int groupId, bool respectLimits, int sysIndex)
1066 Q_ASSERT(groupId < groupData.count());//XXX shouldn't really be an assert
1068 QQuickParticleData* ret = groupData[groupId]->newDatum(respectLimits);
1072 if (sysIndex == -1) {
1073 if (ret->systemIndex == -1)
1074 ret->systemIndex = nextSystemIndex();
1076 if (ret->systemIndex != -1) {
1078 stateEngine->stop(ret->systemIndex);
1079 m_reusableIndexes << ret->systemIndex;
1080 bySysIdx[ret->systemIndex] = 0;
1082 ret->systemIndex = sysIndex;
1084 bySysIdx[ret->systemIndex] = ret;
1087 stateEngine->start(ret->systemIndex, ret->group);
1093 void QQuickParticleSystem::emitParticle(QQuickParticleData* pd)
1094 {// called from prepareNextFrame()->emitWindow - enforce?
1095 //Account for relative emitter position
1096 QPointF offset = this->mapFromItem(pd->e, QPointF(0, 0));
1097 if (!offset.isNull()) {
1098 pd->x += offset.x();
1099 pd->y += offset.y();
1105 void QQuickParticleSystem::finishNewDatum(QQuickParticleData *pd)
1108 groupData[pd->group]->prepareRecycler(pd);
1110 foreach (QQuickParticleAffector *a, m_affectors)
1111 if (a && a->m_needsReset)
1113 foreach (QQuickParticlePainter* p, groupData[pd->group]->painters)
1118 void QQuickParticleSystem::updateCurrentTime( int currentTime )
1121 return;//error in initialization
1123 //### Elapsed time never shrinks - may cause problems if left emitting for weeks at a time.
1124 qreal dt = timeInt / 1000.;
1125 timeInt = currentTime;
1126 qreal time = timeInt / 1000.;
1130 m_emitters.removeAll(0);
1131 m_painters.removeAll(0);
1132 m_affectors.removeAll(0);
1134 bool oldClear = m_empty;
1136 foreach (QQuickParticleGroupData* gd, groupData)//Recycle all groups and see if they're out of live particles
1137 m_empty = gd->recycle() && m_empty;
1140 stateEngine->updateSprites(timeInt);
1142 foreach (QQuickParticleEmitter* emitter, m_emitters)
1143 emitter->emitWindow(timeInt);
1144 foreach (QQuickParticleAffector* a, m_affectors)
1145 a->affectSystem(dt);
1146 foreach (QQuickParticleData* d, needsReset)
1147 foreach (QQuickParticlePainter* p, groupData[d->group]->painters)
1150 if (oldClear != m_empty)
1151 emptyChanged(m_empty);
1154 int QQuickParticleSystem::systemSync(QQuickParticlePainter* p)
1159 return 0;//error in initialization
1160 p->performPendingCommits();