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