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 The ParticleSystem brings together ParticlePainter, Emitter and Affector elements.
111 \qmlproperty bool QtQuick.Particles2::ParticleSystem::running
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.
116 It can also be controlled with the start() and stop() methods.
121 \qmlproperty bool QtQuick.Particles2::ParticleSystem::paused
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
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.
130 It can also be controlled with the pause() and resume() methods.
134 \qmlproperty bool QtQuick.Particles2::ParticleSystem::empty
136 empty is set to true when there are no live particles left in the system.
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
142 To kill all the particles in the system, use a Kill affector.
146 \qmlproperty list<Sprite> QtQuick.Particles2::ParticleSystem::particleStates
148 You can define a sub-set of particle groups in this property in order to provide them
149 with stochastic state transitions.
151 Each QtQuick2::Sprite in this list is interpreted as corresponding to the particle group
152 with ths same name. Any transitions defined in these sprites will take effect on the particle
153 groups as well. Additionally TrailEmitters, Affectors and ParticlePainters definined
154 inside one of these sprites are automatically associated with the corresponding particle group.
158 \qmlmethod void QtQuick.Particles2::ParticleSystem::pause
160 Pauses the simulation if it is running.
166 \qmlmethod void QtQuick.Particles2::ParticleSystem::resume
168 Resumes the simulation if it is paused.
174 \qmlmethod void QtQuick.Particles2::ParticleSystem::start
176 Starts the simulation if it has not already running.
178 \sa stop, restart, running
182 \qmlmethod void QtQuick.Particles2::ParticleSystem::stop
184 Stops the simulation if it is running.
186 \sa start, restart, running
190 \qmlmethod void QtQuick.Particles2::ParticleSystem::restart
192 Stops the simulation if it is running, and then starts it.
194 \sa stop, restart, running
197 \qmlmethod void QtQuick.Particles2::ParticleSystem::reset
199 Discards all currently existing particles.
202 const qreal EPSILON = 0.001;
203 //Utility functions for when within 1ms is close enough
204 bool timeEqualOrGreater(qreal a, qreal b)
206 return (a+EPSILON >= b);
209 bool timeLess(qreal a, qreal b)
211 return (a-EPSILON < b);
214 bool timeEqual(qreal a, qreal b)
216 return (a+EPSILON > b) && (a-EPSILON < b);
219 int roundedTime(qreal a)
221 return (int)qRound(a*1000.0);
224 QQuickParticleDataHeap::QQuickParticleDataHeap()
227 m_data.reserve(1000);
231 void QQuickParticleDataHeap::grow() //###Consider automatic growth vs resize() calls from GroupData
233 m_data.resize(1 << ++m_size);
236 void QQuickParticleDataHeap::insert(QQuickParticleData* data)
238 insertTimed(data, roundedTime(data->t + data->lifeSpan));
241 void QQuickParticleDataHeap::insertTimed(QQuickParticleData* data, int time)
243 //TODO: Optimize 0 lifespan (or already dead) case
244 if (m_lookups.contains(time)) {
245 m_data[m_lookups[time]].data << data;
248 if (m_end == (1 << m_size))
250 m_data[m_end].time = time;
251 m_data[m_end].data.clear();
252 m_data[m_end].data.insert(data);
253 m_lookups.insert(time, m_end);
257 int QQuickParticleDataHeap::top()
261 return m_data[0].time;
264 QSet<QQuickParticleData*> QQuickParticleDataHeap::pop()
267 return QSet<QQuickParticleData*> ();
268 QSet<QQuickParticleData*> ret = m_data[0].data;
269 m_lookups.remove(m_data[0].time);
273 m_data[0] = m_data[--m_end];
279 void QQuickParticleDataHeap::clear()
283 //m_size is in powers of two. So to start at 0 we have one allocated
288 bool QQuickParticleDataHeap::contains(QQuickParticleData* d)
290 for (int i=0; i<m_end; i++)
291 if (m_data[i].data.contains(d))
296 void QQuickParticleDataHeap::swap(int a, int b)
299 m_data[a] = m_data[b];
301 m_lookups[m_data[a].time] = a;
302 m_lookups[m_data[b].time] = b;
305 void QQuickParticleDataHeap::bubbleUp(int idx)//tends to be called once
309 int parent = (idx-1)/2;
310 if (m_data[idx].time < m_data[parent].time) {
316 void QQuickParticleDataHeap::bubbleDown(int idx)//tends to be called log n times
318 int left = idx*2 + 1;
322 int right = idx*2 + 2;
324 if (m_data[left].time > m_data[right].time)
327 if (m_data[idx].time > m_data[lesser].time) {
333 QQuickParticleGroupData::QQuickParticleGroupData(int id, QQuickParticleSystem* sys):index(id),m_size(0),m_system(sys)
338 QQuickParticleGroupData::~QQuickParticleGroupData()
340 foreach (QQuickParticleData* d, data)
344 int QQuickParticleGroupData::size()
349 QString QQuickParticleGroupData::name()//### Worth caching as well?
351 return m_system->groupIds.key(index);
354 void QQuickParticleGroupData::setSize(int newSize)
356 if (newSize == m_size)
358 Q_ASSERT(newSize > m_size);//XXX allow shrinking
359 data.resize(newSize);
360 for (int i=m_size; i<newSize; i++) {
361 data[i] = new QQuickParticleData(m_system);
362 data[i]->group = index;
364 reusableIndexes << i;
366 int delta = newSize - m_size;
368 foreach (QQuickParticlePainter* p, painters)
369 p->setCount(p->count() + delta);
372 void QQuickParticleGroupData::initList()
377 void QQuickParticleGroupData::kill(QQuickParticleData* d)
379 Q_ASSERT(d->group == index);
380 d->lifeSpan = 0;//Kill off
381 foreach (QQuickParticlePainter* p, painters)
383 reusableIndexes << d->index;
386 QQuickParticleData* QQuickParticleGroupData::newDatum(bool respectsLimits)
388 //recycle();//Extra recycler round to be sure?
390 while (!reusableIndexes.empty()) {
391 int idx = *(reusableIndexes.begin());
392 reusableIndexes.remove(idx);
393 if (data[idx]->stillAlive()) {// ### This means resurrection of 'dead' particles. Is that allowed?
394 prepareRecycler(data[idx]);
402 int oldSize = m_size;
403 setSize(oldSize + 10);//###+1,10%,+10? Choose something non-arbitrarily
404 reusableIndexes.remove(oldSize);
405 return data[oldSize];
408 bool QQuickParticleGroupData::recycle()
410 while (dataHeap.top() <= m_system->timeInt) {
411 foreach (QQuickParticleData* datum, dataHeap.pop()) {
412 if (!datum->stillAlive()) {
413 reusableIndexes << datum->index;
415 prepareRecycler(datum); //ttl has been altered mid-way, put it back
420 //TODO: If the data is clear, gc (consider shrinking stack size)?
421 return reusableIndexes.count() == m_size;
424 void QQuickParticleGroupData::prepareRecycler(QQuickParticleData* d)
426 if (d->lifeSpan*1000 < m_system->maxLife) {
429 while ((roundedTime(d->t) + 2*m_system->maxLife/3) <= m_system->timeInt)
430 d->extendLife(m_system->maxLife/3000.0);
431 dataHeap.insertTimed(d, roundedTime(d->t) + 2*m_system->maxLife/3);
435 QQuickParticleData::QQuickParticleData(QQuickParticleSystem* sys)
443 , deformationOwner(0)
482 QQuickParticleData::~QQuickParticleData()
487 void QQuickParticleData::clone(const QQuickParticleData& other)
492 lifeSpan = other.lifeSpan;
494 endSize = other.endSize;
503 rotation = other.rotation;
504 rotationSpeed = other.rotationSpeed;
505 autoRotate = other.autoRotate;
506 animIdx = other.animIdx;
507 frameDuration = other.frameDuration;
508 frameCount = other.frameCount;
512 animWidth = other.animWidth;
513 animHeight = other.animHeight;
514 color.r = other.color.r;
515 color.g = other.color.g;
516 color.b = other.color.b;
517 color.a = other.color.a;
519 delegate = other.delegate;
520 modelIndex = other.modelIndex;
522 colorOwner = other.colorOwner;
523 rotationOwner = other.rotationOwner;
524 deformationOwner = other.deformationOwner;
525 animationOwner = other.animationOwner;
528 QQmlV8Handle QQuickParticleData::v8Value()
531 v8Datum = new QQuickV8ParticleData(QQmlEnginePrivate::getV8Engine(qmlEngine(system)), this);
532 return v8Datum->v8Value();
534 //sets the x accleration without affecting the instantaneous x velocity or position
535 void QQuickParticleData::setInstantaneousAX(qreal ax)
537 qreal t = (system->timeInt / 1000.0) - this->t;
538 qreal vx = (this->vx + t*this->ax) - t*ax;
539 qreal ex = this->x + this->vx * t + 0.5 * this->ax * t * t;
540 qreal x = ex - t*vx - 0.5 * t*t*ax;
547 //sets the x velocity without affecting the instantaneous x postion
548 void QQuickParticleData::setInstantaneousVX(qreal vx)
550 qreal t = (system->timeInt / 1000.0) - this->t;
551 qreal evx = vx - t*this->ax;
552 qreal ex = this->x + this->vx * t + 0.5 * this->ax * t * t;
553 qreal x = ex - t*evx - 0.5 * t*t*this->ax;
559 //sets the instantaneous x postion
560 void QQuickParticleData::setInstantaneousX(qreal x)
562 qreal t = (system->timeInt / 1000.0) - this->t;
563 this->x = x - t*this->vx - 0.5 * t*t*this->ax;
566 //sets the y accleration without affecting the instantaneous y velocity or position
567 void QQuickParticleData::setInstantaneousAY(qreal ay)
569 qreal t = (system->timeInt / 1000.0) - this->t;
570 qreal vy = (this->vy + t*this->ay) - t*ay;
571 qreal ey = this->y + this->vy * t + 0.5 * this->ay * t * t;
572 qreal y = ey - t*vy - 0.5 * t*t*ay;
579 //sets the y velocity without affecting the instantaneous y position
580 void QQuickParticleData::setInstantaneousVY(qreal vy)
582 qreal t = (system->timeInt / 1000.0) - this->t;
583 qreal evy = vy - t*this->ay;
584 qreal ey = this->y + this->vy * t + 0.5 * this->ay * t * t;
585 qreal y = ey - t*evy - 0.5 * t*t*this->ay;
591 //sets the instantaneous Y position
592 void QQuickParticleData::setInstantaneousY(qreal y)
594 qreal t = (system->timeInt / 1000.0) - this->t;
595 this->y = y - t*this->vy - 0.5 * t*t*this->ay;
598 qreal QQuickParticleData::curX() const
600 qreal t = (system->timeInt / 1000.0) - this->t;
601 return this->x + this->vx * t + 0.5 * this->ax * t * t;
604 qreal QQuickParticleData::curVX() const
606 qreal t = (system->timeInt / 1000.0) - this->t;
607 return this->vx + t*this->ax;
610 qreal QQuickParticleData::curY() const
612 qreal t = (system->timeInt / 1000.0) - this->t;
613 return y + vy * t + 0.5 * ay * t * t;
616 qreal QQuickParticleData::curVY() const
618 qreal t = (system->timeInt / 1000.0) - this->t;
622 void QQuickParticleData::debugDump()
624 qDebug() << "Particle" << systemIndex << group << "/" << index << stillAlive()
625 << "Pos: " << x << "," << y
626 << "Vel: " << vx << "," << vy
627 << "Acc: " << ax << "," << ay
628 << "Size: " << size << "," << endSize
629 << "Time: " << t << "," <<lifeSpan << ";" << (system->timeInt / 1000.0) ;
632 bool QQuickParticleData::stillAlive()
636 return (t + lifeSpan - EPSILON) > ((qreal)system->timeInt/1000.0);
639 bool QQuickParticleData::alive()
643 qreal st = ((qreal)system->timeInt/1000.0);
644 return (t + EPSILON) < st && (t + lifeSpan - EPSILON) > st;
647 float QQuickParticleData::curSize()
649 if (!system || !lifeSpan)
651 return size + (endSize - size) * (1 - (lifeLeft() / lifeSpan));
654 float QQuickParticleData::lifeLeft()
658 return (t + lifeSpan) - (system->timeInt/1000.0);
661 void QQuickParticleData::extendLife(float time)
665 qreal newVX = curVX();
666 qreal newVY = curVY();
671 qreal elapsed = (system->timeInt / 1000.0) - t;
672 qreal evy = newVY - elapsed*ay;
673 qreal ey = newY - elapsed*evy - 0.5 * elapsed*elapsed*ay;
674 qreal evx = newVX - elapsed*ax;
675 qreal ex = newX - elapsed*evx - 0.5 * elapsed*elapsed*ax;
683 QQuickParticleSystem::QQuickParticleSystem(QQuickItem *parent) :
691 m_componentComplete(false),
695 connect(&m_painterMapper, SIGNAL(mapped(QObject*)),
696 this, SLOT(loadPainter(QObject*)));
698 m_debugMode = qmlParticlesDebug();
701 QQuickParticleSystem::~QQuickParticleSystem()
703 foreach (QQuickParticleGroupData* gd, groupData)
707 void QQuickParticleSystem::initGroups()
709 m_reusableIndexes.clear();
712 qDeleteAll(groupData);
716 QQuickParticleGroupData* gd = new QQuickParticleGroupData(0, this);//Default group
717 groupData.insert(0,gd);
718 groupIds.insert(QString(), 0);
722 void QQuickParticleSystem::registerParticlePainter(QQuickParticlePainter* p)
725 qDebug() << "Registering Painter" << p << "to" << this;
726 //TODO: a way to Unregister emitters, painters and affectors
727 m_painters << QPointer<QQuickParticlePainter>(p);//###Set or uniqueness checking?
728 connect(p, SIGNAL(groupsChanged(QStringList)),
729 &m_painterMapper, SLOT(map()));
733 void QQuickParticleSystem::registerParticleEmitter(QQuickParticleEmitter* e)
736 qDebug() << "Registering Emitter" << e << "to" << this;
737 m_emitters << QPointer<QQuickParticleEmitter>(e);//###How to get them out?
738 connect(e, SIGNAL(particleCountChanged()),
739 this, SLOT(emittersChanged()));
740 connect(e, SIGNAL(groupChanged(QString)),
741 this, SLOT(emittersChanged()));
743 e->reset();//Start, so that starttime factors appropriately
746 void QQuickParticleSystem::registerParticleAffector(QQuickParticleAffector* a)
749 qDebug() << "Registering Affector" << a << "to" << this;
750 m_affectors << QPointer<QQuickParticleAffector>(a);
753 void QQuickParticleSystem::registerParticleGroup(QQuickParticleGroup* g)
756 qDebug() << "Registering Group" << g << "to" << this;
757 m_groups << QPointer<QQuickParticleGroup>(g);
761 void QQuickParticleSystem::setRunning(bool arg)
763 if (m_running != arg) {
765 emit runningChanged(arg);
767 if (m_animation)//Not created until componentCompleted
768 m_running ? m_animation->start() : m_animation->stop();
773 void QQuickParticleSystem::setPaused(bool arg) {
774 if (m_paused != arg) {
776 if (m_animation && m_animation->state() != QAbstractAnimation::Stopped)
777 m_paused ? m_animation->pause() : m_animation->resume();
779 foreach (QQuickParticlePainter *p, m_painters)
782 emit pausedChanged(arg);
786 void QQuickParticleSystem::statePropertyRedirect(QQmlListProperty<QObject> *prop, QObject *value)
788 //Hooks up automatic state-associated stuff
789 QQuickParticleSystem* sys = qobject_cast<QQuickParticleSystem*>(prop->object->parent());
790 QQuickParticleGroup* group = qobject_cast<QQuickParticleGroup*>(prop->object);
791 if (!group || !sys || !value)
793 stateRedirect(group, sys, value);
796 void QQuickParticleSystem::stateRedirect(QQuickParticleGroup* group, QQuickParticleSystem* sys, QObject *value)
799 list << group->name();
800 QQuickParticleAffector* a = qobject_cast<QQuickParticleAffector*>(value);
802 a->setParentItem(sys);
807 QQuickTrailEmitter* fe = qobject_cast<QQuickTrailEmitter*>(value);
809 fe->setParentItem(sys);
810 fe->setFollow(group->name());
814 QQuickParticleEmitter* e = qobject_cast<QQuickParticleEmitter*>(value);
816 e->setParentItem(sys);
817 e->setGroup(group->name());
821 QQuickParticlePainter* p = qobject_cast<QQuickParticlePainter*>(value);
823 p->setParentItem(sys);
828 qWarning() << value << " was placed inside a particle system state but cannot be taken into the particle system. It will be lost.";
831 void QQuickParticleSystem::componentComplete()
834 QQuickItem::componentComplete();
835 m_componentComplete = true;
836 m_animation = new QQuickParticleSystemAnimation(this);
837 reset();//restarts animation as well
840 void QQuickParticleSystem::reset()
842 if (!m_componentComplete)
846 //Clear guarded pointers which have been deleted
848 cleared += m_emitters.removeAll(0);
849 cleared += m_painters.removeAll(0);
850 cleared += m_affectors.removeAll(0);
853 initGroups();//Also clears all logical particles
858 foreach (QQuickParticleEmitter* e, m_emitters)
863 foreach (QQuickParticlePainter *p, m_painters) {
868 //### Do affectors need reset too?
869 if (m_animation) {//Animation is explicitly disabled in benchmarks
870 //reset restarts animation (if running)
871 if ((m_animation->state() == QAbstractAnimation::Running))
873 m_animation->start();
875 m_animation->pause();
882 void QQuickParticleSystem::loadPainter(QObject *p)
884 if (!m_componentComplete || !p)
887 QQuickParticlePainter* painter = qobject_cast<QQuickParticlePainter*>(p);
888 Q_ASSERT(painter);//XXX
889 foreach (QQuickParticleGroupData* sg, groupData)
890 sg->painters.remove(painter);
891 int particleCount = 0;
892 if (painter->groups().isEmpty()) {//Uses default particle
895 painter->setGroups(def);
896 particleCount += groupData[0]->size();
897 groupData[0]->painters << painter;
899 foreach (const QString &group, painter->groups()) {
900 if (group != QLatin1String("") && !groupIds[group]) {//new group
901 int id = m_nextGroupId++;
902 QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
903 groupIds.insert(group, id);
904 groupData.insert(id, gd);
906 particleCount += groupData[groupIds[group]]->size();
907 groupData[groupIds[group]]->painters << painter;
910 painter->setCount(particleCount);
911 painter->update();//Initial update here
915 void QQuickParticleSystem::emittersChanged()
917 if (!m_componentComplete)
920 m_emitters.removeAll(0);
923 QList<int> previousSizes;
925 for (int i=0; i<m_nextGroupId; i++) {
926 previousSizes << groupData[i]->size();
930 foreach (QQuickParticleEmitter* e, m_emitters) {//Populate groups and set sizes.
931 if (!groupIds.contains(e->group())
932 || (!e->group().isEmpty() && !groupIds[e->group()])) {//or it was accidentally inserted by a failed lookup earlier
933 int id = m_nextGroupId++;
934 QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
935 groupIds.insert(e->group(), id);
936 groupData.insert(id, gd);
940 newSizes[groupIds[e->group()]] += e->particleCount();
941 //###: Cull emptied groups?
944 //TODO: Garbage collection?
946 for (int i=0; i<m_nextGroupId; i++) {
947 groupData[i]->setSize(qMax(newSizes[i], previousSizes[i]));
948 particleCount += groupData[i]->size();
952 qDebug() << "Particle system emitters changed. New particle count: " << particleCount;
954 if (particleCount > bySysIdx.size())//New datum requests haven't updated it
955 bySysIdx.resize(particleCount);
957 foreach (QQuickParticleAffector *a, m_affectors)//Groups may have changed
958 a->m_updateIntSet = true;
960 foreach (QQuickParticlePainter *p, m_painters)
963 if (!m_groups.isEmpty())
968 void QQuickParticleSystem::createEngine()
970 if (!m_componentComplete)
972 if (stateEngine && m_debugMode)
973 qDebug() << "Resetting Existing Sprite Engine...";
974 //### Solve the losses if size/states go down
975 foreach (QQuickParticleGroup* group, m_groups) {
977 foreach (const QString &name, groupIds.keys())
978 if (group->name() == name)
981 int id = m_nextGroupId++;
982 QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
983 groupIds.insert(group->name(), id);
984 groupData.insert(id, gd);
988 if (m_groups.count()) {
989 //Reorder groups List so as to have the same order as groupData
990 QList<QQuickParticleGroup*> newList;
991 for (int i=0; i<m_nextGroupId; i++) {
993 QString name = groupData[i]->name();
994 foreach (QQuickParticleGroup* existing, m_groups) {
995 if (existing->name() == name) {
1001 newList << new QQuickParticleGroup(this);
1002 newList.back()->setName(name);
1006 QList<QQuickStochasticState*> states;
1007 foreach (QQuickParticleGroup* g, m_groups)
1008 states << (QQuickStochasticState*)g;
1011 stateEngine = new QQuickStochasticEngine(this);
1012 stateEngine->setCount(particleCount);
1013 stateEngine->m_states = states;
1015 connect(stateEngine, SIGNAL(stateChanged(int)),
1016 this, SLOT(particleStateChange(int)));
1026 void QQuickParticleSystem::particleStateChange(int idx)
1028 moveGroups(bySysIdx[idx], stateEngine->curState(idx));
1031 void QQuickParticleSystem::moveGroups(QQuickParticleData *d, int newGIdx)
1033 if (!d || newGIdx == d->group)
1036 QQuickParticleData* pd = newDatum(newGIdx, false, d->systemIndex);
1043 d->systemIndex = -1;
1044 groupData[d->group]->kill(d);
1047 int QQuickParticleSystem::nextSystemIndex()
1049 if (!m_reusableIndexes.isEmpty()) {
1050 int ret = *(m_reusableIndexes.begin());
1051 m_reusableIndexes.remove(ret);
1054 if (m_nextIndex >= bySysIdx.size()) {
1055 bySysIdx.resize(bySysIdx.size() < 10 ? 10 : bySysIdx.size()*1.1);//###+1,10%,+10? Choose something non-arbitrarily
1057 stateEngine->setCount(bySysIdx.size());
1060 return m_nextIndex++;
1063 QQuickParticleData* QQuickParticleSystem::newDatum(int groupId, bool respectLimits, int sysIndex)
1065 Q_ASSERT(groupId < groupData.count());//XXX shouldn't really be an assert
1067 QQuickParticleData* ret = groupData[groupId]->newDatum(respectLimits);
1071 if (sysIndex == -1) {
1072 if (ret->systemIndex == -1)
1073 ret->systemIndex = nextSystemIndex();
1075 if (ret->systemIndex != -1) {
1077 stateEngine->stop(ret->systemIndex);
1078 m_reusableIndexes << ret->systemIndex;
1079 bySysIdx[ret->systemIndex] = 0;
1081 ret->systemIndex = sysIndex;
1083 bySysIdx[ret->systemIndex] = ret;
1086 stateEngine->start(ret->systemIndex, ret->group);
1092 void QQuickParticleSystem::emitParticle(QQuickParticleData* pd)
1093 {// called from prepareNextFrame()->emitWindow - enforce?
1094 //Account for relative emitter position
1095 QPointF offset = this->mapFromItem(pd->e, QPointF(0, 0));
1096 if (!offset.isNull()) {
1097 pd->x += offset.x();
1098 pd->y += offset.y();
1104 void QQuickParticleSystem::finishNewDatum(QQuickParticleData *pd)
1107 groupData[pd->group]->prepareRecycler(pd);
1109 foreach (QQuickParticleAffector *a, m_affectors)
1110 if (a && a->m_needsReset)
1112 foreach (QQuickParticlePainter* p, groupData[pd->group]->painters)
1117 void QQuickParticleSystem::updateCurrentTime( int currentTime )
1120 return;//error in initialization
1122 //### Elapsed time never shrinks - may cause problems if left emitting for weeks at a time.
1123 qreal dt = timeInt / 1000.;
1124 timeInt = currentTime;
1125 qreal time = timeInt / 1000.;
1129 m_emitters.removeAll(0);
1130 m_painters.removeAll(0);
1131 m_affectors.removeAll(0);
1133 bool oldClear = m_empty;
1135 foreach (QQuickParticleGroupData* gd, groupData)//Recycle all groups and see if they're out of live particles
1136 m_empty = gd->recycle() && m_empty;
1139 stateEngine->updateSprites(timeInt);
1141 foreach (QQuickParticleEmitter* emitter, m_emitters)
1142 emitter->emitWindow(timeInt);
1143 foreach (QQuickParticleAffector* a, m_affectors)
1144 a->affectSystem(dt);
1145 foreach (QQuickParticleData* d, needsReset)
1146 foreach (QQuickParticlePainter* p, groupData[d->group]->painters)
1149 if (oldClear != m_empty)
1150 emptyChanged(m_empty);
1153 int QQuickParticleSystem::systemSync(QQuickParticlePainter* p)
1158 return 0;//error in initialization
1159 p->performPendingCommits();