1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: http://www.qt-project.org/
7 ** This file is part of the Declarative module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** 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/qdeclarativeengine_p.h>
58 //###Switch to define later, for now user-friendly (no compilation) debugging is worth it
59 DEFINE_BOOL_CONFIG_OPTION(qmlParticlesDebug, QML_PARTICLES_DEBUG)
62 /* \internal ParticleSystem internals documentation
64 Affectors, Painters, Emitters and Groups all register themselves on construction as a callback
65 from their setSystem (or componentComplete if they have a system from a parent).
67 Particle data is stored by group, They have a group index (used by the particle system almost
68 everywhere) and a global index (used by the Stochastic state engine powering stochastic group
69 transitions). Each group has a recycling list/heap that stores the particle data.
71 The recycling list/heap is a heap of particle data sorted by when they're expected to die. If
72 they die prematurely then they are marked as reusable (and will probably still be alive when
73 they exit the heap). If they have their life extended, then they aren't dead when expected.
74 If this happens, they go back in the heap with the new estimate. If they have died on schedule,
75 then the indexes are marked as reusable. If no indexes are reusable when new particles are
76 requested, then the list is extended. This relatively complex datastructure is because memory
77 allocation and deallocation on this scale proved to be a significant performance cost. In order
78 to reuse the indexes validly (even when particles can have their life extended or cut short
79 dynamically, or particle counts grow) this seemed to be the most efficient option for keeping
80 track of which indices could be reused.
82 When a new particle is emitted, the emitter gets a new datum from the group (through the
83 system), and sets properties on it. Then it's passed back to the group briefly so that it can
84 now guess when the particle will die. Then the painters get a change to initialize properties
85 as well, since particle data includes shared data from painters as well as logical particle
88 Every animation advance, the simulation advances by running all emitters for the elapsed
89 duration, then running all affectors, then telling all particle painters to update changed
90 particles. The ParticlePainter superclass stores these changes, and they are implemented
91 when the painter is called to paint in the render thread.
93 Particle group changes move the particle from one group to another by killing the old particle
94 and then creating a new one with the same data in the new group.
96 Note that currently groups only grow. Given that data is stored in vectors, it is non-trivial
97 to pluck out the unused indexes when the count goes down. Given the dynamic nature of the
98 system, it is difficult to tell if those unused data instances will be used again. Still,
99 some form of garbage collection is on the long term plan.
103 \qmlclass ParticleSystem QQuickParticleSystem
104 \inqmlmodule QtQuick.Particles 2
105 \brief The ParticleSystem brings together ParticlePainter, Emitter and Affector elements.
110 \qmlproperty bool QtQuick.Particles2::ParticleSystem::running
112 If running is set to false, the particle system will stop the simulation. All particles
113 will be destroyed when the system is set to running again.
115 It can also be controlled with the start() and stop() methods.
120 \qmlproperty bool QtQuick.Particles2::ParticleSystem::paused
122 If paused is set to true, the particle system will not advance the simulation. When
123 paused is set to false again, the simulation will resume from the same point it was
126 The simulation will automatically pause if it detects that there are no live particles
127 left, and unpause when new live particles are added.
129 It can also be controlled with the pause() and resume() methods.
133 \qmlproperty bool QtQuick.Particles2::ParticleSystem::empty
135 empty is set to true when there are no live particles left in the system.
137 You can use this to pause the system, keeping it from spending any time updating,
138 but you will need to resume it in order for additional particles to be generated
141 To kill all the particles in the system, use a Kill affector.
145 \qmlproperty list<Sprite> QtQuick.Particles2::ParticleSystem::particleStates
147 You can define a sub-set of particle groups in this property in order to provide them
148 with stochastic state transitions.
150 Each QtQuick2::Sprite in this list is interpreted as corresponding to the particle group
151 with ths same name. Any transitions defined in these sprites will take effect on the particle
152 groups as well. Additionally TrailEmitters, Affectors and ParticlePainters definined
153 inside one of these sprites are automatically associated with the corresponding particle group.
157 \qmlmethod void QtQuick.Particles2::ParticleSystem::pause
159 Pauses the simulation if it is running.
165 \qmlmethod void QtQuick.Particles2::ParticleSystem::resume
167 Resumes the simulation if it is paused.
173 \qmlmethod void QtQuick.Particles2::ParticleSystem::start
175 Starts the simulation if it has not already running.
177 \sa stop, restart, running
181 \qmlmethod void QtQuick.Particles2::ParticleSystem::stop
183 Stops the simulation if it is running.
185 \sa start, restart, running
189 \qmlmethod void QtQuick.Particles2::ParticleSystem::restart
191 Stops the simulation if it is running, and then starts it.
193 \sa stop, restart, running
196 \qmlmethod void QtQuick.Particles2::ParticleSystem::reset
198 Discards all currently existing particles.
201 const qreal EPSILON = 0.001;
202 //Utility functions for when within 1ms is close enough
203 bool timeEqualOrGreater(qreal a, qreal b)
205 return (a+EPSILON >= b);
208 bool timeLess(qreal a, qreal b)
210 return (a-EPSILON < b);
213 bool timeEqual(qreal a, qreal b)
215 return (a+EPSILON > b) && (a-EPSILON < b);
218 int roundedTime(qreal a)
220 return (int)qRound(a*1000.0);
223 QQuickParticleDataHeap::QQuickParticleDataHeap()
226 m_data.reserve(1000);
230 void QQuickParticleDataHeap::grow() //###Consider automatic growth vs resize() calls from GroupData
232 m_data.resize(1 << ++m_size);
235 void QQuickParticleDataHeap::insert(QQuickParticleData* data)
237 insertTimed(data, roundedTime(data->t + data->lifeSpan));
240 void QQuickParticleDataHeap::insertTimed(QQuickParticleData* data, int time)
242 //TODO: Optimize 0 lifespan (or already dead) case
243 if (m_lookups.contains(time)) {
244 m_data[m_lookups[time]].data << data;
247 if (m_end == (1 << m_size))
249 m_data[m_end].time = time;
250 m_data[m_end].data.clear();
251 m_data[m_end].data.insert(data);
252 m_lookups.insert(time, m_end);
256 int QQuickParticleDataHeap::top()
260 return m_data[0].time;
263 QSet<QQuickParticleData*> QQuickParticleDataHeap::pop()
266 return QSet<QQuickParticleData*> ();
267 QSet<QQuickParticleData*> ret = m_data[0].data;
268 m_lookups.remove(m_data[0].time);
272 m_data[0] = m_data[--m_end];
278 void QQuickParticleDataHeap::clear()
282 //m_size is in powers of two. So to start at 0 we have one allocated
287 bool QQuickParticleDataHeap::contains(QQuickParticleData* d)
289 for (int i=0; i<m_end; i++)
290 if (m_data[i].data.contains(d))
295 void QQuickParticleDataHeap::swap(int a, int b)
298 m_data[a] = m_data[b];
300 m_lookups[m_data[a].time] = a;
301 m_lookups[m_data[b].time] = b;
304 void QQuickParticleDataHeap::bubbleUp(int idx)//tends to be called once
308 int parent = (idx-1)/2;
309 if (m_data[idx].time < m_data[parent].time) {
315 void QQuickParticleDataHeap::bubbleDown(int idx)//tends to be called log n times
317 int left = idx*2 + 1;
321 int right = idx*2 + 2;
323 if (m_data[left].time > m_data[right].time)
326 if (m_data[idx].time > m_data[lesser].time) {
332 QQuickParticleGroupData::QQuickParticleGroupData(int id, QQuickParticleSystem* sys):index(id),m_size(0),m_system(sys)
337 QQuickParticleGroupData::~QQuickParticleGroupData()
339 foreach (QQuickParticleData* d, data)
343 int QQuickParticleGroupData::size()
348 QString QQuickParticleGroupData::name()//### Worth caching as well?
350 return m_system->groupIds.key(index);
353 void QQuickParticleGroupData::setSize(int newSize)
355 if (newSize == m_size)
357 Q_ASSERT(newSize > m_size);//XXX allow shrinking
358 data.resize(newSize);
359 for (int i=m_size; i<newSize; i++) {
360 data[i] = new QQuickParticleData(m_system);
361 data[i]->group = index;
363 reusableIndexes << i;
365 int delta = newSize - m_size;
367 foreach (QQuickParticlePainter* p, painters)
368 p->setCount(p->count() + delta);
371 void QQuickParticleGroupData::initList()
376 void QQuickParticleGroupData::kill(QQuickParticleData* d)
378 Q_ASSERT(d->group == index);
379 d->lifeSpan = 0;//Kill off
380 foreach (QQuickParticlePainter* p, painters)
382 reusableIndexes << d->index;
385 QQuickParticleData* QQuickParticleGroupData::newDatum(bool respectsLimits)
387 //recycle();//Extra recycler round to be sure?
389 while (!reusableIndexes.empty()) {
390 int idx = *(reusableIndexes.begin());
391 reusableIndexes.remove(idx);
392 if (data[idx]->stillAlive()) {// ### This means resurrection of 'dead' particles. Is that allowed?
393 prepareRecycler(data[idx]);
401 int oldSize = m_size;
402 setSize(oldSize + 10);//###+1,10%,+10? Choose something non-arbitrarily
403 reusableIndexes.remove(oldSize);
404 return data[oldSize];
407 bool QQuickParticleGroupData::recycle()
409 while (dataHeap.top() <= m_system->timeInt) {
410 foreach (QQuickParticleData* datum, dataHeap.pop()) {
411 if (!datum->stillAlive()) {
412 reusableIndexes << datum->index;
414 prepareRecycler(datum); //ttl has been altered mid-way, put it back
419 //TODO: If the data is clear, gc (consider shrinking stack size)?
420 return reusableIndexes.count() == m_size;
423 void QQuickParticleGroupData::prepareRecycler(QQuickParticleData* d)
425 if (d->lifeSpan*1000 < m_system->maxLife) {
428 while ((roundedTime(d->t) + 2*m_system->maxLife/3) <= m_system->timeInt)
429 d->extendLife(m_system->maxLife/3000.0);
430 dataHeap.insertTimed(d, roundedTime(d->t) + 2*m_system->maxLife/3);
434 QQuickParticleData::QQuickParticleData(QQuickParticleSystem* sys)
442 , deformationOwner(0)
481 QQuickParticleData::~QQuickParticleData()
486 void QQuickParticleData::clone(const QQuickParticleData& other)
491 lifeSpan = other.lifeSpan;
493 endSize = other.endSize;
502 rotation = other.rotation;
503 rotationSpeed = other.rotationSpeed;
504 autoRotate = other.autoRotate;
505 animIdx = other.animIdx;
506 frameDuration = other.frameDuration;
507 frameCount = other.frameCount;
511 animWidth = other.animWidth;
512 animHeight = other.animHeight;
513 color.r = other.color.r;
514 color.g = other.color.g;
515 color.b = other.color.b;
516 color.a = other.color.a;
518 delegate = other.delegate;
519 modelIndex = other.modelIndex;
521 colorOwner = other.colorOwner;
522 rotationOwner = other.rotationOwner;
523 deformationOwner = other.deformationOwner;
524 animationOwner = other.animationOwner;
527 QDeclarativeV8Handle QQuickParticleData::v8Value()
530 v8Datum = new QQuickV8ParticleData(QDeclarativeEnginePrivate::getV8Engine(qmlEngine(system)), this);
531 return v8Datum->v8Value();
533 //sets the x accleration without affecting the instantaneous x velocity or position
534 void QQuickParticleData::setInstantaneousAX(qreal ax)
536 qreal t = (system->timeInt / 1000.0) - this->t;
537 qreal vx = (this->vx + t*this->ax) - t*ax;
538 qreal ex = this->x + this->vx * t + 0.5 * this->ax * t * t;
539 qreal x = ex - t*vx - 0.5 * t*t*ax;
546 //sets the x velocity without affecting the instantaneous x postion
547 void QQuickParticleData::setInstantaneousVX(qreal vx)
549 qreal t = (system->timeInt / 1000.0) - this->t;
550 qreal evx = vx - t*this->ax;
551 qreal ex = this->x + this->vx * t + 0.5 * this->ax * t * t;
552 qreal x = ex - t*evx - 0.5 * t*t*this->ax;
558 //sets the instantaneous x postion
559 void QQuickParticleData::setInstantaneousX(qreal x)
561 qreal t = (system->timeInt / 1000.0) - this->t;
562 this->x = x - t*this->vx - 0.5 * t*t*this->ax;
565 //sets the y accleration without affecting the instantaneous y velocity or position
566 void QQuickParticleData::setInstantaneousAY(qreal ay)
568 qreal t = (system->timeInt / 1000.0) - this->t;
569 qreal vy = (this->vy + t*this->ay) - t*ay;
570 qreal ey = this->y + this->vy * t + 0.5 * this->ay * t * t;
571 qreal y = ey - t*vy - 0.5 * t*t*ay;
578 //sets the y velocity without affecting the instantaneous y position
579 void QQuickParticleData::setInstantaneousVY(qreal vy)
581 qreal t = (system->timeInt / 1000.0) - this->t;
582 qreal evy = vy - t*this->ay;
583 qreal ey = this->y + this->vy * t + 0.5 * this->ay * t * t;
584 qreal y = ey - t*evy - 0.5 * t*t*this->ay;
590 //sets the instantaneous Y position
591 void QQuickParticleData::setInstantaneousY(qreal y)
593 qreal t = (system->timeInt / 1000.0) - this->t;
594 this->y = y - t*this->vy - 0.5 * t*t*this->ay;
597 qreal QQuickParticleData::curX() const
599 qreal t = (system->timeInt / 1000.0) - this->t;
600 return this->x + this->vx * t + 0.5 * this->ax * t * t;
603 qreal QQuickParticleData::curVX() const
605 qreal t = (system->timeInt / 1000.0) - this->t;
606 return this->vx + t*this->ax;
609 qreal QQuickParticleData::curY() const
611 qreal t = (system->timeInt / 1000.0) - this->t;
612 return y + vy * t + 0.5 * ay * t * t;
615 qreal QQuickParticleData::curVY() const
617 qreal t = (system->timeInt / 1000.0) - this->t;
621 void QQuickParticleData::debugDump()
623 qDebug() << "Particle" << systemIndex << group << "/" << index << stillAlive()
624 << "Pos: " << x << "," << y
625 << "Vel: " << vx << "," << vy
626 << "Acc: " << ax << "," << ay
627 << "Size: " << size << "," << endSize
628 << "Time: " << t << "," <<lifeSpan << ";" << (system->timeInt / 1000.0) ;
631 bool QQuickParticleData::stillAlive()
635 return (t + lifeSpan - EPSILON) > ((qreal)system->timeInt/1000.0);
638 bool QQuickParticleData::alive()
642 qreal st = ((qreal)system->timeInt/1000.0);
643 return (t + EPSILON) < st && (t + lifeSpan - EPSILON) > st;
646 float QQuickParticleData::curSize()
648 if (!system || !lifeSpan)
650 return size + (endSize - size) * (1 - (lifeLeft() / lifeSpan));
653 float QQuickParticleData::lifeLeft()
657 return (t + lifeSpan) - (system->timeInt/1000.0);
660 void QQuickParticleData::extendLife(float time)
664 qreal newVX = curVX();
665 qreal newVY = curVY();
670 qreal elapsed = (system->timeInt / 1000.0) - t;
671 qreal evy = newVY - elapsed*ay;
672 qreal ey = newY - elapsed*evy - 0.5 * elapsed*elapsed*ay;
673 qreal evx = newVX - elapsed*ax;
674 qreal ex = newX - elapsed*evx - 0.5 * elapsed*elapsed*ax;
682 QQuickParticleSystem::QQuickParticleSystem(QQuickItem *parent) :
690 m_componentComplete(false),
694 connect(&m_painterMapper, SIGNAL(mapped(QObject*)),
695 this, SLOT(loadPainter(QObject*)));
697 m_debugMode = qmlParticlesDebug();
700 QQuickParticleSystem::~QQuickParticleSystem()
702 foreach (QQuickParticleGroupData* gd, groupData)
706 void QQuickParticleSystem::initGroups()
708 m_reusableIndexes.clear();
711 qDeleteAll(groupData);
715 QQuickParticleGroupData* gd = new QQuickParticleGroupData(0, this);//Default group
716 groupData.insert(0,gd);
717 groupIds.insert(QString(), 0);
721 void QQuickParticleSystem::registerParticlePainter(QQuickParticlePainter* p)
724 qDebug() << "Registering Painter" << p << "to" << this;
725 //TODO: a way to Unregister emitters, painters and affectors
726 m_painters << QPointer<QQuickParticlePainter>(p);//###Set or uniqueness checking?
727 connect(p, SIGNAL(groupsChanged(QStringList)),
728 &m_painterMapper, SLOT(map()));
732 void QQuickParticleSystem::registerParticleEmitter(QQuickParticleEmitter* e)
735 qDebug() << "Registering Emitter" << e << "to" << this;
736 m_emitters << QPointer<QQuickParticleEmitter>(e);//###How to get them out?
737 connect(e, SIGNAL(particleCountChanged()),
738 this, SLOT(emittersChanged()));
739 connect(e, SIGNAL(groupChanged(QString)),
740 this, SLOT(emittersChanged()));
742 e->reset();//Start, so that starttime factors appropriately
745 void QQuickParticleSystem::registerParticleAffector(QQuickParticleAffector* a)
748 qDebug() << "Registering Affector" << a << "to" << this;
749 m_affectors << QPointer<QQuickParticleAffector>(a);
752 void QQuickParticleSystem::registerParticleGroup(QQuickParticleGroup* g)
755 qDebug() << "Registering Group" << g << "to" << this;
756 m_groups << QPointer<QQuickParticleGroup>(g);
760 void QQuickParticleSystem::setRunning(bool arg)
762 if (m_running != arg) {
764 emit runningChanged(arg);
766 if (m_animation)//Not created until componentCompleted
767 m_running ? m_animation->start() : m_animation->stop();
772 void QQuickParticleSystem::setPaused(bool arg) {
773 if (m_paused != arg) {
775 if (m_animation && m_animation->state() != QAbstractAnimation::Stopped)
776 m_paused ? m_animation->pause() : m_animation->resume();
778 foreach (QQuickParticlePainter *p, m_painters)
781 emit pausedChanged(arg);
785 void QQuickParticleSystem::statePropertyRedirect(QDeclarativeListProperty<QObject> *prop, QObject *value)
787 //Hooks up automatic state-associated stuff
788 QQuickParticleSystem* sys = qobject_cast<QQuickParticleSystem*>(prop->object->parent());
789 QQuickParticleGroup* group = qobject_cast<QQuickParticleGroup*>(prop->object);
790 if (!group || !sys || !value)
792 stateRedirect(group, sys, value);
795 void QQuickParticleSystem::stateRedirect(QQuickParticleGroup* group, QQuickParticleSystem* sys, QObject *value)
798 list << group->name();
799 QQuickParticleAffector* a = qobject_cast<QQuickParticleAffector*>(value);
801 a->setParentItem(sys);
806 QQuickTrailEmitter* fe = qobject_cast<QQuickTrailEmitter*>(value);
808 fe->setParentItem(sys);
809 fe->setFollow(group->name());
813 QQuickParticleEmitter* e = qobject_cast<QQuickParticleEmitter*>(value);
815 e->setParentItem(sys);
816 e->setGroup(group->name());
820 QQuickParticlePainter* p = qobject_cast<QQuickParticlePainter*>(value);
822 p->setParentItem(sys);
827 qWarning() << value << " was placed inside a particle system state but cannot be taken into the particle system. It will be lost.";
830 void QQuickParticleSystem::componentComplete()
833 QQuickItem::componentComplete();
834 m_componentComplete = true;
835 m_animation = new QQuickParticleSystemAnimation(this);
836 reset();//restarts animation as well
839 void QQuickParticleSystem::reset()
841 if (!m_componentComplete)
845 //Clear guarded pointers which have been deleted
847 cleared += m_emitters.removeAll(0);
848 cleared += m_painters.removeAll(0);
849 cleared += m_affectors.removeAll(0);
852 initGroups();//Also clears all logical particles
857 foreach (QQuickParticleEmitter* e, m_emitters)
862 foreach (QQuickParticlePainter *p, m_painters) {
867 //### Do affectors need reset too?
868 if (m_animation) {//Animation is explicitly disabled in benchmarks
869 //reset restarts animation (if running)
870 if ((m_animation->state() == QAbstractAnimation::Running))
872 m_animation->start();
874 m_animation->pause();
881 void QQuickParticleSystem::loadPainter(QObject *p)
883 if (!m_componentComplete || !p)
886 QQuickParticlePainter* painter = qobject_cast<QQuickParticlePainter*>(p);
887 Q_ASSERT(painter);//XXX
888 foreach (QQuickParticleGroupData* sg, groupData)
889 sg->painters.remove(painter);
890 int particleCount = 0;
891 if (painter->groups().isEmpty()) {//Uses default particle
894 painter->setGroups(def);
895 particleCount += groupData[0]->size();
896 groupData[0]->painters << painter;
898 foreach (const QString &group, painter->groups()) {
899 if (group != QLatin1String("") && !groupIds[group]) {//new group
900 int id = m_nextGroupId++;
901 QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
902 groupIds.insert(group, id);
903 groupData.insert(id, gd);
905 particleCount += groupData[groupIds[group]]->size();
906 groupData[groupIds[group]]->painters << painter;
909 painter->setCount(particleCount);
910 painter->update();//Initial update here
914 void QQuickParticleSystem::emittersChanged()
916 if (!m_componentComplete)
919 m_emitters.removeAll(0);
922 QList<int> previousSizes;
924 for (int i=0; i<m_nextGroupId; i++) {
925 previousSizes << groupData[i]->size();
929 foreach (QQuickParticleEmitter* e, m_emitters) {//Populate groups and set sizes.
930 if (!groupIds.contains(e->group())
931 || (!e->group().isEmpty() && !groupIds[e->group()])) {//or it was accidentally inserted by a failed lookup earlier
932 int id = m_nextGroupId++;
933 QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
934 groupIds.insert(e->group(), id);
935 groupData.insert(id, gd);
939 newSizes[groupIds[e->group()]] += e->particleCount();
940 //###: Cull emptied groups?
943 //TODO: Garbage collection?
945 for (int i=0; i<m_nextGroupId; i++) {
946 groupData[i]->setSize(qMax(newSizes[i], previousSizes[i]));
947 particleCount += groupData[i]->size();
951 qDebug() << "Particle system emitters changed. New particle count: " << particleCount;
953 if (particleCount > bySysIdx.size())//New datum requests haven't updated it
954 bySysIdx.resize(particleCount);
956 foreach (QQuickParticleAffector *a, m_affectors)//Groups may have changed
957 a->m_updateIntSet = true;
959 foreach (QQuickParticlePainter *p, m_painters)
962 if (!m_groups.isEmpty())
967 void QQuickParticleSystem::createEngine()
969 if (!m_componentComplete)
971 if (stateEngine && m_debugMode)
972 qDebug() << "Resetting Existing Sprite Engine...";
973 //### Solve the losses if size/states go down
974 foreach (QQuickParticleGroup* group, m_groups) {
976 foreach (const QString &name, groupIds.keys())
977 if (group->name() == name)
980 int id = m_nextGroupId++;
981 QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
982 groupIds.insert(group->name(), id);
983 groupData.insert(id, gd);
987 if (m_groups.count()) {
988 //Reorder groups List so as to have the same order as groupData
989 QList<QQuickParticleGroup*> newList;
990 for (int i=0; i<m_nextGroupId; i++) {
992 QString name = groupData[i]->name();
993 foreach (QQuickParticleGroup* existing, m_groups) {
994 if (existing->name() == name) {
1000 newList << new QQuickParticleGroup(this);
1001 newList.back()->setName(name);
1005 QList<QQuickStochasticState*> states;
1006 foreach (QQuickParticleGroup* g, m_groups)
1007 states << (QQuickStochasticState*)g;
1010 stateEngine = new QQuickStochasticEngine(this);
1011 stateEngine->setCount(particleCount);
1012 stateEngine->m_states = states;
1014 connect(stateEngine, SIGNAL(stateChanged(int)),
1015 this, SLOT(particleStateChange(int)));
1025 void QQuickParticleSystem::particleStateChange(int idx)
1027 moveGroups(bySysIdx[idx], stateEngine->curState(idx));
1030 void QQuickParticleSystem::moveGroups(QQuickParticleData *d, int newGIdx)
1032 if (!d || newGIdx == d->group)
1035 QQuickParticleData* pd = newDatum(newGIdx, false, d->systemIndex);
1042 d->systemIndex = -1;
1043 groupData[d->group]->kill(d);
1046 int QQuickParticleSystem::nextSystemIndex()
1048 if (!m_reusableIndexes.isEmpty()) {
1049 int ret = *(m_reusableIndexes.begin());
1050 m_reusableIndexes.remove(ret);
1053 if (m_nextIndex >= bySysIdx.size()) {
1054 bySysIdx.resize(bySysIdx.size() < 10 ? 10 : bySysIdx.size()*1.1);//###+1,10%,+10? Choose something non-arbitrarily
1056 stateEngine->setCount(bySysIdx.size());
1059 return m_nextIndex++;
1062 QQuickParticleData* QQuickParticleSystem::newDatum(int groupId, bool respectLimits, int sysIndex)
1064 Q_ASSERT(groupId < groupData.count());//XXX shouldn't really be an assert
1066 QQuickParticleData* ret = groupData[groupId]->newDatum(respectLimits);
1070 if (sysIndex == -1) {
1071 if (ret->systemIndex == -1)
1072 ret->systemIndex = nextSystemIndex();
1074 if (ret->systemIndex != -1) {
1076 stateEngine->stop(ret->systemIndex);
1077 m_reusableIndexes << ret->systemIndex;
1078 bySysIdx[ret->systemIndex] = 0;
1080 ret->systemIndex = sysIndex;
1082 bySysIdx[ret->systemIndex] = ret;
1085 stateEngine->start(ret->systemIndex, ret->group);
1091 void QQuickParticleSystem::emitParticle(QQuickParticleData* pd)
1092 {// called from prepareNextFrame()->emitWindow - enforce?
1093 //Account for relative emitter position
1094 QPointF offset = this->mapFromItem(pd->e, QPointF(0, 0));
1095 if (!offset.isNull()) {
1096 pd->x += offset.x();
1097 pd->y += offset.y();
1103 void QQuickParticleSystem::finishNewDatum(QQuickParticleData *pd)
1106 groupData[pd->group]->prepareRecycler(pd);
1108 foreach (QQuickParticleAffector *a, m_affectors)
1109 if (a && a->m_needsReset)
1111 foreach (QQuickParticlePainter* p, groupData[pd->group]->painters)
1116 void QQuickParticleSystem::updateCurrentTime( int currentTime )
1119 return;//error in initialization
1121 //### Elapsed time never shrinks - may cause problems if left emitting for weeks at a time.
1122 qreal dt = timeInt / 1000.;
1123 timeInt = currentTime;
1124 qreal time = timeInt / 1000.;
1128 m_emitters.removeAll(0);
1129 m_painters.removeAll(0);
1130 m_affectors.removeAll(0);
1132 bool oldClear = m_empty;
1134 foreach (QQuickParticleGroupData* gd, groupData)//Recycle all groups and see if they're out of live particles
1135 m_empty = gd->recycle() && m_empty;
1138 stateEngine->updateSprites(timeInt);
1140 foreach (QQuickParticleEmitter* emitter, m_emitters)
1141 emitter->emitWindow(timeInt);
1142 foreach (QQuickParticleAffector* a, m_affectors)
1143 a->affectSystem(dt);
1144 foreach (QQuickParticleData* d, needsReset)
1145 foreach (QQuickParticlePainter* p, groupData[d->group]->painters)
1148 if (oldClear != m_empty)
1149 emptyChanged(m_empty);
1152 int QQuickParticleSystem::systemSync(QQuickParticlePainter* p)
1157 return 0;//error in initialization
1158 p->performPendingCommits();