1 /****************************************************************************
3 ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
6 ** This file is part of the QtQuick module of the Qt Toolkit.
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 Digia. For licensing terms and
14 ** conditions see http://qt.digia.com/licensing. For further information
15 ** use the contact form at http://qt.digia.com/contact-us.
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 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights. These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file. Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
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 \qmltype ParticleSystem
105 \instantiates QQuickParticleSystem
106 \inqmlmodule QtQuick.Particles 2
107 \brief A system which includes particle painter, emitter, and affector types
108 \ingroup qtquick-particles
113 \qmlproperty bool QtQuick.Particles2::ParticleSystem::running
115 If running is set to false, the particle system will stop the simulation. All particles
116 will be destroyed when the system is set to running again.
118 It can also be controlled with the start() and stop() methods.
123 \qmlproperty bool QtQuick.Particles2::ParticleSystem::paused
125 If paused is set to true, the particle system will not advance the simulation. When
126 paused is set to false again, the simulation will resume from the same point it was
129 The simulation will automatically pause if it detects that there are no live particles
130 left, and unpause when new live particles are added.
132 It can also be controlled with the pause() and resume() methods.
136 \qmlproperty bool QtQuick.Particles2::ParticleSystem::empty
138 empty is set to true when there are no live particles left in the system.
140 You can use this to pause the system, keeping it from spending any time updating,
141 but you will need to resume it in order for additional particles to be generated
144 To kill all the particles in the system, use an Age affector.
148 \qmlproperty list<Sprite> QtQuick.Particles2::ParticleSystem::particleStates
150 You can define a sub-set of particle groups in this property in order to provide them
151 with stochastic state transitions.
153 Each QtQuick2::Sprite in this list is interpreted as corresponding to the particle group
154 with ths same name. Any transitions defined in these sprites will take effect on the particle
155 groups as well. Additionally TrailEmitters, Affectors and ParticlePainters definined
156 inside one of these sprites are automatically associated with the corresponding particle group.
160 \qmlmethod QtQuick.Particles2::ParticleSystem::pause()
162 Pauses the simulation if it is running.
168 \qmlmethod QtQuick.Particles2::ParticleSystem::resume()
170 Resumes the simulation if it is paused.
176 \qmlmethod QtQuick.Particles2::ParticleSystem::start()
178 Starts the simulation if it has not already running.
180 \sa stop, restart, running
184 \qmlmethod QtQuick.Particles2::ParticleSystem::stop()
186 Stops the simulation if it is running.
188 \sa start, restart, running
192 \qmlmethod QtQuick.Particles2::ParticleSystem::restart()
194 Stops the simulation if it is running, and then starts it.
196 \sa stop, restart, running
199 \qmlmethod QtQuick.Particles2::ParticleSystem::reset()
201 Discards all currently existing particles.
204 const qreal EPSILON = 0.001;
205 //Utility functions for when within 1ms is close enough
206 bool timeEqualOrGreater(qreal a, qreal b)
208 return (a+EPSILON >= b);
211 bool timeLess(qreal a, qreal b)
213 return (a-EPSILON < b);
216 bool timeEqual(qreal a, qreal b)
218 return (a+EPSILON > b) && (a-EPSILON < b);
221 int roundedTime(qreal a)
223 return (int)qRound(a*1000.0);
226 QQuickParticleDataHeap::QQuickParticleDataHeap()
229 m_data.reserve(1000);
233 void QQuickParticleDataHeap::grow() //###Consider automatic growth vs resize() calls from GroupData
235 m_data.resize(1 << ++m_size);
238 void QQuickParticleDataHeap::insert(QQuickParticleData* data)
240 insertTimed(data, roundedTime(data->t + data->lifeSpan));
243 void QQuickParticleDataHeap::insertTimed(QQuickParticleData* data, int time)
245 //TODO: Optimize 0 lifespan (or already dead) case
246 if (m_lookups.contains(time)) {
247 m_data[m_lookups[time]].data << data;
250 if (m_end == (1 << m_size))
252 m_data[m_end].time = time;
253 m_data[m_end].data.clear();
254 m_data[m_end].data.insert(data);
255 m_lookups.insert(time, m_end);
259 int QQuickParticleDataHeap::top()
263 return m_data[0].time;
266 QSet<QQuickParticleData*> QQuickParticleDataHeap::pop()
269 return QSet<QQuickParticleData*> ();
270 QSet<QQuickParticleData*> ret = m_data[0].data;
271 m_lookups.remove(m_data[0].time);
275 m_data[0] = m_data[--m_end];
281 void QQuickParticleDataHeap::clear()
285 //m_size is in powers of two. So to start at 0 we have one allocated
290 bool QQuickParticleDataHeap::contains(QQuickParticleData* d)
292 for (int i=0; i<m_end; i++)
293 if (m_data[i].data.contains(d))
298 void QQuickParticleDataHeap::swap(int a, int b)
301 m_data[a] = m_data[b];
303 m_lookups[m_data[a].time] = a;
304 m_lookups[m_data[b].time] = b;
307 void QQuickParticleDataHeap::bubbleUp(int idx)//tends to be called once
311 int parent = (idx-1)/2;
312 if (m_data[idx].time < m_data[parent].time) {
318 void QQuickParticleDataHeap::bubbleDown(int idx)//tends to be called log n times
320 int left = idx*2 + 1;
324 int right = idx*2 + 2;
326 if (m_data[left].time > m_data[right].time)
329 if (m_data[idx].time > m_data[lesser].time) {
335 QQuickParticleGroupData::QQuickParticleGroupData(int id, QQuickParticleSystem* sys):index(id),m_size(0),m_system(sys)
340 QQuickParticleGroupData::~QQuickParticleGroupData()
342 foreach (QQuickParticleData* d, data)
346 int QQuickParticleGroupData::size()
351 QString QQuickParticleGroupData::name()//### Worth caching as well?
353 return m_system->groupIds.key(index);
356 void QQuickParticleGroupData::setSize(int newSize)
358 if (newSize == m_size)
360 Q_ASSERT(newSize > m_size);//XXX allow shrinking
361 data.resize(newSize);
362 for (int i=m_size; i<newSize; i++) {
363 data[i] = new QQuickParticleData(m_system);
364 data[i]->group = index;
366 reusableIndexes << i;
368 int delta = newSize - m_size;
370 foreach (QQuickParticlePainter* p, painters)
371 p->setCount(p->count() + delta);
374 void QQuickParticleGroupData::initList()
379 void QQuickParticleGroupData::kill(QQuickParticleData* d)
381 Q_ASSERT(d->group == index);
382 d->lifeSpan = 0;//Kill off
383 foreach (QQuickParticlePainter* p, painters)
385 reusableIndexes << d->index;
388 QQuickParticleData* QQuickParticleGroupData::newDatum(bool respectsLimits)
390 //recycle();//Extra recycler round to be sure?
392 while (!reusableIndexes.empty()) {
393 int idx = *(reusableIndexes.begin());
394 reusableIndexes.remove(idx);
395 if (data[idx]->stillAlive()) {// ### This means resurrection of 'dead' particles. Is that allowed?
396 prepareRecycler(data[idx]);
404 int oldSize = m_size;
405 setSize(oldSize + 10);//###+1,10%,+10? Choose something non-arbitrarily
406 reusableIndexes.remove(oldSize);
407 return data[oldSize];
410 bool QQuickParticleGroupData::recycle()
412 while (dataHeap.top() <= m_system->timeInt) {
413 foreach (QQuickParticleData* datum, dataHeap.pop()) {
414 if (!datum->stillAlive()) {
415 reusableIndexes << datum->index;
417 prepareRecycler(datum); //ttl has been altered mid-way, put it back
422 //TODO: If the data is clear, gc (consider shrinking stack size)?
423 return reusableIndexes.count() == m_size;
426 void QQuickParticleGroupData::prepareRecycler(QQuickParticleData* d)
428 if (d->lifeSpan*1000 < m_system->maxLife) {
431 while ((roundedTime(d->t) + 2*m_system->maxLife/3) <= m_system->timeInt)
432 d->extendLife(m_system->maxLife/3000.0);
433 dataHeap.insertTimed(d, roundedTime(d->t) + 2*m_system->maxLife/3);
437 QQuickParticleData::QQuickParticleData(QQuickParticleSystem* sys)
445 , deformationOwner(0)
464 rotationVelocity = 0;
484 QQuickParticleData::~QQuickParticleData()
489 void QQuickParticleData::clone(const QQuickParticleData& other)
494 lifeSpan = other.lifeSpan;
496 endSize = other.endSize;
505 rotation = other.rotation;
506 rotationVelocity = other.rotationVelocity;
507 autoRotate = other.autoRotate;
508 animIdx = other.animIdx;
509 frameDuration = other.frameDuration;
510 frameCount = other.frameCount;
514 animWidth = other.animWidth;
515 animHeight = other.animHeight;
516 color.r = other.color.r;
517 color.g = other.color.g;
518 color.b = other.color.b;
519 color.a = other.color.a;
521 delegate = other.delegate;
522 modelIndex = other.modelIndex;
524 colorOwner = other.colorOwner;
525 rotationOwner = other.rotationOwner;
526 deformationOwner = other.deformationOwner;
527 animationOwner = other.animationOwner;
530 QQmlV8Handle QQuickParticleData::v8Value()
533 v8Datum = new QQuickV8ParticleData(QQmlEnginePrivate::getV8Engine(qmlEngine(system)), this);
534 return v8Datum->v8Value();
536 //sets the x accleration without affecting the instantaneous x velocity or position
537 void QQuickParticleData::setInstantaneousAX(qreal ax)
539 qreal t = (system->timeInt / 1000.0) - this->t;
540 qreal vx = (this->vx + t*this->ax) - t*ax;
541 qreal ex = this->x + this->vx * t + 0.5 * this->ax * t * t;
542 qreal x = ex - t*vx - 0.5 * t*t*ax;
549 //sets the x velocity without affecting the instantaneous x postion
550 void QQuickParticleData::setInstantaneousVX(qreal vx)
552 qreal t = (system->timeInt / 1000.0) - this->t;
553 qreal evx = vx - t*this->ax;
554 qreal ex = this->x + this->vx * t + 0.5 * this->ax * t * t;
555 qreal x = ex - t*evx - 0.5 * t*t*this->ax;
561 //sets the instantaneous x postion
562 void QQuickParticleData::setInstantaneousX(qreal x)
564 qreal t = (system->timeInt / 1000.0) - this->t;
565 this->x = x - t*this->vx - 0.5 * t*t*this->ax;
568 //sets the y accleration without affecting the instantaneous y velocity or position
569 void QQuickParticleData::setInstantaneousAY(qreal ay)
571 qreal t = (system->timeInt / 1000.0) - this->t;
572 qreal vy = (this->vy + t*this->ay) - t*ay;
573 qreal ey = this->y + this->vy * t + 0.5 * this->ay * t * t;
574 qreal y = ey - t*vy - 0.5 * t*t*ay;
581 //sets the y velocity without affecting the instantaneous y position
582 void QQuickParticleData::setInstantaneousVY(qreal vy)
584 qreal t = (system->timeInt / 1000.0) - this->t;
585 qreal evy = vy - t*this->ay;
586 qreal ey = this->y + this->vy * t + 0.5 * this->ay * t * t;
587 qreal y = ey - t*evy - 0.5 * t*t*this->ay;
593 //sets the instantaneous Y position
594 void QQuickParticleData::setInstantaneousY(qreal y)
596 qreal t = (system->timeInt / 1000.0) - this->t;
597 this->y = y - t*this->vy - 0.5 * t*t*this->ay;
600 qreal QQuickParticleData::curX() const
602 qreal t = (system->timeInt / 1000.0) - this->t;
603 return this->x + this->vx * t + 0.5 * this->ax * t * t;
606 qreal QQuickParticleData::curVX() const
608 qreal t = (system->timeInt / 1000.0) - this->t;
609 return this->vx + t*this->ax;
612 qreal QQuickParticleData::curY() const
614 qreal t = (system->timeInt / 1000.0) - this->t;
615 return y + vy * t + 0.5 * ay * t * t;
618 qreal QQuickParticleData::curVY() const
620 qreal t = (system->timeInt / 1000.0) - this->t;
624 void QQuickParticleData::debugDump()
626 qDebug() << "Particle" << systemIndex << group << "/" << index << stillAlive()
627 << "Pos: " << x << "," << y
628 << "Vel: " << vx << "," << vy
629 << "Acc: " << ax << "," << ay
630 << "Size: " << size << "," << endSize
631 << "Time: " << t << "," <<lifeSpan << ";" << (system->timeInt / 1000.0) ;
634 bool QQuickParticleData::stillAlive()
638 return (t + lifeSpan - EPSILON) > ((qreal)system->timeInt/1000.0);
641 bool QQuickParticleData::alive()
645 qreal st = ((qreal)system->timeInt/1000.0);
646 return (t + EPSILON) < st && (t + lifeSpan - EPSILON) > st;
649 float QQuickParticleData::curSize()
651 if (!system || !lifeSpan)
653 return size + (endSize - size) * (1 - (lifeLeft() / lifeSpan));
656 float QQuickParticleData::lifeLeft()
660 return (t + lifeSpan) - (system->timeInt/1000.0);
663 void QQuickParticleData::extendLife(float time)
667 qreal newVX = curVX();
668 qreal newVY = curVY();
673 qreal elapsed = (system->timeInt / 1000.0) - t;
674 qreal evy = newVY - elapsed*ay;
675 qreal ey = newY - elapsed*evy - 0.5 * elapsed*elapsed*ay;
676 qreal evx = newVX - elapsed*ax;
677 qreal ex = newX - elapsed*evx - 0.5 * elapsed*elapsed*ax;
685 QQuickParticleSystem::QQuickParticleSystem(QQuickItem *parent) :
693 m_componentComplete(false),
697 connect(&m_painterMapper, SIGNAL(mapped(QObject*)),
698 this, SLOT(loadPainter(QObject*)));
700 m_debugMode = qmlParticlesDebug();
703 QQuickParticleSystem::~QQuickParticleSystem()
705 foreach (QQuickParticleGroupData* gd, groupData)
709 void QQuickParticleSystem::initGroups()
711 m_reusableIndexes.clear();
714 qDeleteAll(groupData);
718 QQuickParticleGroupData* gd = new QQuickParticleGroupData(0, this);//Default group
719 groupData.insert(0,gd);
720 groupIds.insert(QString(), 0);
724 void QQuickParticleSystem::registerParticlePainter(QQuickParticlePainter* p)
727 qDebug() << "Registering Painter" << p << "to" << this;
728 //TODO: a way to Unregister emitters, painters and affectors
729 m_painters << QPointer<QQuickParticlePainter>(p);//###Set or uniqueness checking?
730 connect(p, SIGNAL(groupsChanged(QStringList)),
731 &m_painterMapper, SLOT(map()));
735 void QQuickParticleSystem::registerParticleEmitter(QQuickParticleEmitter* e)
738 qDebug() << "Registering Emitter" << e << "to" << this;
739 m_emitters << QPointer<QQuickParticleEmitter>(e);//###How to get them out?
740 connect(e, SIGNAL(particleCountChanged()),
741 this, SLOT(emittersChanged()));
742 connect(e, SIGNAL(groupChanged(QString)),
743 this, SLOT(emittersChanged()));
745 e->reset();//Start, so that starttime factors appropriately
748 void QQuickParticleSystem::registerParticleAffector(QQuickParticleAffector* a)
751 qDebug() << "Registering Affector" << a << "to" << this;
752 m_affectors << QPointer<QQuickParticleAffector>(a);
755 void QQuickParticleSystem::registerParticleGroup(QQuickParticleGroup* g)
758 qDebug() << "Registering Group" << g << "to" << this;
759 m_groups << QPointer<QQuickParticleGroup>(g);
763 void QQuickParticleSystem::setRunning(bool arg)
765 if (m_running != arg) {
767 emit runningChanged(arg);
769 if (m_animation)//Not created until componentCompleted
770 m_running ? m_animation->start() : m_animation->stop();
775 void QQuickParticleSystem::setPaused(bool arg) {
776 if (m_paused != arg) {
778 if (m_animation && m_animation->state() != QAbstractAnimation::Stopped)
779 m_paused ? m_animation->pause() : m_animation->resume();
781 foreach (QQuickParticlePainter *p, m_painters)
784 emit pausedChanged(arg);
788 void QQuickParticleSystem::statePropertyRedirect(QQmlListProperty<QObject> *prop, QObject *value)
790 //Hooks up automatic state-associated stuff
791 QQuickParticleSystem* sys = qobject_cast<QQuickParticleSystem*>(prop->object->parent());
792 QQuickParticleGroup* group = qobject_cast<QQuickParticleGroup*>(prop->object);
793 if (!group || !sys || !value)
795 stateRedirect(group, sys, value);
798 void QQuickParticleSystem::stateRedirect(QQuickParticleGroup* group, QQuickParticleSystem* sys, QObject *value)
801 list << group->name();
802 QQuickParticleAffector* a = qobject_cast<QQuickParticleAffector*>(value);
804 a->setParentItem(sys);
809 QQuickTrailEmitter* fe = qobject_cast<QQuickTrailEmitter*>(value);
811 fe->setParentItem(sys);
812 fe->setFollow(group->name());
816 QQuickParticleEmitter* e = qobject_cast<QQuickParticleEmitter*>(value);
818 e->setParentItem(sys);
819 e->setGroup(group->name());
823 QQuickParticlePainter* p = qobject_cast<QQuickParticlePainter*>(value);
825 p->setParentItem(sys);
830 qWarning() << value << " was placed inside a particle system state but cannot be taken into the particle system. It will be lost.";
833 void QQuickParticleSystem::componentComplete()
836 QQuickItem::componentComplete();
837 m_componentComplete = true;
838 m_animation = new QQuickParticleSystemAnimation(this);
839 reset();//restarts animation as well
842 void QQuickParticleSystem::reset()
844 if (!m_componentComplete)
848 //Clear guarded pointers which have been deleted
850 cleared += m_emitters.removeAll(0);
851 cleared += m_painters.removeAll(0);
852 cleared += m_affectors.removeAll(0);
855 initGroups();//Also clears all logical particles
860 foreach (QQuickParticleEmitter* e, m_emitters)
865 foreach (QQuickParticlePainter *p, m_painters) {
870 //### Do affectors need reset too?
871 if (m_animation) {//Animation is explicitly disabled in benchmarks
872 //reset restarts animation (if running)
873 if ((m_animation->state() == QAbstractAnimation::Running))
875 m_animation->start();
877 m_animation->pause();
884 void QQuickParticleSystem::loadPainter(QObject *p)
886 if (!m_componentComplete || !p)
889 QQuickParticlePainter* painter = qobject_cast<QQuickParticlePainter*>(p);
890 Q_ASSERT(painter);//XXX
891 foreach (QQuickParticleGroupData* sg, groupData)
892 sg->painters.remove(painter);
893 int particleCount = 0;
894 if (painter->groups().isEmpty()) {//Uses default particle
897 painter->setGroups(def);
898 particleCount += groupData[0]->size();
899 groupData[0]->painters << painter;
901 foreach (const QString &group, painter->groups()) {
902 if (group != QLatin1String("") && !groupIds[group]) {//new group
903 int id = m_nextGroupId++;
904 QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
905 groupIds.insert(group, id);
906 groupData.insert(id, gd);
908 particleCount += groupData[groupIds[group]]->size();
909 groupData[groupIds[group]]->painters << painter;
912 painter->setCount(particleCount);
913 painter->update();//Initial update here
917 void QQuickParticleSystem::emittersChanged()
919 if (!m_componentComplete)
922 m_emitters.removeAll(0);
925 QList<int> previousSizes;
927 for (int i=0; i<m_nextGroupId; i++) {
928 previousSizes << groupData[i]->size();
932 foreach (QQuickParticleEmitter* e, m_emitters) {//Populate groups and set sizes.
933 if (!groupIds.contains(e->group())
934 || (!e->group().isEmpty() && !groupIds[e->group()])) {//or it was accidentally inserted by a failed lookup earlier
935 int id = m_nextGroupId++;
936 QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
937 groupIds.insert(e->group(), id);
938 groupData.insert(id, gd);
942 newSizes[groupIds[e->group()]] += e->particleCount();
943 //###: Cull emptied groups?
946 //TODO: Garbage collection?
948 for (int i=0; i<m_nextGroupId; i++) {
949 groupData[i]->setSize(qMax(newSizes[i], previousSizes[i]));
950 particleCount += groupData[i]->size();
954 qDebug() << "Particle system emitters changed. New particle count: " << particleCount;
956 if (particleCount > bySysIdx.size())//New datum requests haven't updated it
957 bySysIdx.resize(particleCount);
959 foreach (QQuickParticleAffector *a, m_affectors)//Groups may have changed
960 a->m_updateIntSet = true;
962 foreach (QQuickParticlePainter *p, m_painters)
965 if (!m_groups.isEmpty())
970 void QQuickParticleSystem::createEngine()
972 if (!m_componentComplete)
974 if (stateEngine && m_debugMode)
975 qDebug() << "Resetting Existing Sprite Engine...";
976 //### Solve the losses if size/states go down
977 foreach (QQuickParticleGroup* group, m_groups) {
979 foreach (const QString &name, groupIds.keys())
980 if (group->name() == name)
983 int id = m_nextGroupId++;
984 QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
985 groupIds.insert(group->name(), id);
986 groupData.insert(id, gd);
990 if (m_groups.count()) {
991 //Reorder groups List so as to have the same order as groupData
992 QList<QQuickParticleGroup*> newList;
993 for (int i=0; i<m_nextGroupId; i++) {
995 QString name = groupData[i]->name();
996 foreach (QQuickParticleGroup* existing, m_groups) {
997 if (existing->name() == name) {
1003 newList << new QQuickParticleGroup(this);
1004 newList.back()->setName(name);
1008 QList<QQuickStochasticState*> states;
1009 foreach (QQuickParticleGroup* g, m_groups)
1010 states << (QQuickStochasticState*)g;
1013 stateEngine = new QQuickStochasticEngine(this);
1014 stateEngine->setCount(particleCount);
1015 stateEngine->m_states = states;
1017 connect(stateEngine, SIGNAL(stateChanged(int)),
1018 this, SLOT(particleStateChange(int)));
1028 void QQuickParticleSystem::particleStateChange(int idx)
1030 moveGroups(bySysIdx[idx], stateEngine->curState(idx));
1033 void QQuickParticleSystem::moveGroups(QQuickParticleData *d, int newGIdx)
1035 if (!d || newGIdx == d->group)
1038 QQuickParticleData* pd = newDatum(newGIdx, false, d->systemIndex);
1045 d->systemIndex = -1;
1046 groupData[d->group]->kill(d);
1049 int QQuickParticleSystem::nextSystemIndex()
1051 if (!m_reusableIndexes.isEmpty()) {
1052 int ret = *(m_reusableIndexes.begin());
1053 m_reusableIndexes.remove(ret);
1056 if (m_nextIndex >= bySysIdx.size()) {
1057 bySysIdx.resize(bySysIdx.size() < 10 ? 10 : bySysIdx.size()*1.1);//###+1,10%,+10? Choose something non-arbitrarily
1059 stateEngine->setCount(bySysIdx.size());
1062 return m_nextIndex++;
1065 QQuickParticleData* QQuickParticleSystem::newDatum(int groupId, bool respectLimits, int sysIndex)
1067 Q_ASSERT(groupId < groupData.count());//XXX shouldn't really be an assert
1069 QQuickParticleData* ret = groupData[groupId]->newDatum(respectLimits);
1073 if (sysIndex == -1) {
1074 if (ret->systemIndex == -1)
1075 ret->systemIndex = nextSystemIndex();
1077 if (ret->systemIndex != -1) {
1079 stateEngine->stop(ret->systemIndex);
1080 m_reusableIndexes << ret->systemIndex;
1081 bySysIdx[ret->systemIndex] = 0;
1083 ret->systemIndex = sysIndex;
1085 bySysIdx[ret->systemIndex] = ret;
1088 stateEngine->start(ret->systemIndex, ret->group);
1094 void QQuickParticleSystem::emitParticle(QQuickParticleData* pd)
1095 {// called from prepareNextFrame()->emitWindow - enforce?
1096 //Account for relative emitter position
1097 QPointF offset = this->mapFromItem(pd->e, QPointF(0, 0));
1098 if (!offset.isNull()) {
1099 pd->x += offset.x();
1100 pd->y += offset.y();
1106 void QQuickParticleSystem::finishNewDatum(QQuickParticleData *pd)
1109 groupData[pd->group]->prepareRecycler(pd);
1111 foreach (QQuickParticleAffector *a, m_affectors)
1112 if (a && a->m_needsReset)
1114 foreach (QQuickParticlePainter* p, groupData[pd->group]->painters)
1119 void QQuickParticleSystem::updateCurrentTime( int currentTime )
1122 return;//error in initialization
1124 //### Elapsed time never shrinks - may cause problems if left emitting for weeks at a time.
1125 qreal dt = timeInt / 1000.;
1126 timeInt = currentTime;
1127 qreal time = timeInt / 1000.;
1131 m_emitters.removeAll(0);
1132 m_painters.removeAll(0);
1133 m_affectors.removeAll(0);
1135 bool oldClear = m_empty;
1137 foreach (QQuickParticleGroupData* gd, groupData)//Recycle all groups and see if they're out of live particles
1138 m_empty = gd->recycle() && m_empty;
1141 stateEngine->updateSprites(timeInt);
1143 foreach (QQuickParticleEmitter* emitter, m_emitters)
1144 emitter->emitWindow(timeInt);
1145 foreach (QQuickParticleAffector* a, m_affectors)
1146 a->affectSystem(dt);
1147 foreach (QQuickParticleData* d, needsReset)
1148 foreach (QQuickParticlePainter* p, groupData[d->group]->painters)
1151 if (oldClear != m_empty)
1152 emptyChanged(m_empty);
1155 int QQuickParticleSystem::systemSync(QQuickParticlePainter* p)
1160 return 0;//error in initialization
1161 p->performPendingCommits();