924a2d010288cf0c1eb608d9dc5bf355355cd8af
[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
62 /* \internal ParticleSystem internals documentation
63
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).
66
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.
70
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.
81
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
86    data.
87
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.
92
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.
95
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.
100 */
101
102 /*!
103     \qmlclass ParticleSystem QQuickParticleSystem
104     \inqmlmodule QtQuick.Particles 2
105     \brief The ParticleSystem brings together ParticlePainter, Emitter and Affector elements.
106
107 */
108
109 /*!
110     \qmlproperty bool QtQuick.Particles2::ParticleSystem::running
111
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.
114
115     It can also be controlled with the start() and stop() methods.
116 */
117
118
119 /*!
120     \qmlproperty bool QtQuick.Particles2::ParticleSystem::paused
121
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
124     paused.
125
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.
128
129     It can also be controlled with the pause() and resume() methods.
130 */
131
132 /*!
133     \qmlproperty bool QtQuick.Particles2::ParticleSystem::empty
134
135     empty is set to true when there are no live particles left in the system.
136
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
139     by the system.
140
141     To kill all the particles in the system, use a Kill affector.
142 */
143
144 /*!
145     \qmlproperty list<Sprite> QtQuick.Particles2::ParticleSystem::particleStates
146
147     You can define a sub-set of particle groups in this property in order to provide them
148     with stochastic state transitions.
149
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.
154 */
155
156 /*!
157     \qmlmethod void QtQuick.Particles2::ParticleSystem::pause
158
159     Pauses the simulation if it is running.
160
161     \sa resume, paused
162 */
163
164 /*!
165     \qmlmethod void QtQuick.Particles2::ParticleSystem::resume
166
167     Resumes the simulation if it is paused.
168
169     \sa pause, paused
170 */
171
172 /*!
173     \qmlmethod void QtQuick.Particles2::ParticleSystem::start
174
175     Starts the simulation if it has not already running.
176
177     \sa stop, restart, running
178 */
179
180 /*!
181     \qmlmethod void QtQuick.Particles2::ParticleSystem::stop
182
183     Stops the simulation if it is running.
184
185     \sa start, restart, running
186 */
187
188 /*!
189     \qmlmethod void QtQuick.Particles2::ParticleSystem::restart
190
191     Stops the simulation if it is running, and then starts it.
192
193     \sa stop, restart, running
194 */
195 /*!
196     \qmlmethod void QtQuick.Particles2::ParticleSystem::reset
197
198     Discards all currently existing particles.
199
200 */
201 const qreal EPSILON = 0.001;
202 //Utility functions for when within 1ms is close enough
203 bool timeEqualOrGreater(qreal a, qreal b)
204 {
205     return (a+EPSILON >= b);
206 }
207
208 bool timeLess(qreal a, qreal b)
209 {
210     return (a-EPSILON < b);
211 }
212
213 bool timeEqual(qreal a, qreal b)
214 {
215     return (a+EPSILON > b) && (a-EPSILON < b);
216 }
217
218 int roundedTime(qreal a)
219 {// in ms
220     return (int)qRound(a*1000.0);
221 }
222
223 QQuickParticleDataHeap::QQuickParticleDataHeap()
224     : m_data(0)
225 {
226     m_data.reserve(1000);
227     clear();
228 }
229
230 void QQuickParticleDataHeap::grow() //###Consider automatic growth vs resize() calls from GroupData
231 {
232     m_data.resize(1 << ++m_size);
233 }
234
235 void QQuickParticleDataHeap::insert(QQuickParticleData* data)
236 {
237     insertTimed(data, roundedTime(data->t + data->lifeSpan));
238 }
239
240 void QQuickParticleDataHeap::insertTimed(QQuickParticleData* data, int time)
241 {
242     //TODO: Optimize 0 lifespan (or already dead) case
243     if (m_lookups.contains(time)) {
244         m_data[m_lookups[time]].data << data;
245         return;
246     }
247     if (m_end == (1 << m_size))
248         grow();
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);
253     bubbleUp(m_end++);
254 }
255
256 int QQuickParticleDataHeap::top()
257 {
258     if (m_end == 0)
259         return 1 << 30;
260     return m_data[0].time;
261 }
262
263 QSet<QQuickParticleData*> QQuickParticleDataHeap::pop()
264 {
265     if (!m_end)
266         return QSet<QQuickParticleData*> ();
267     QSet<QQuickParticleData*> ret = m_data[0].data;
268     m_lookups.remove(m_data[0].time);
269     if (m_end == 1) {
270         --m_end;
271     } else {
272         m_data[0] = m_data[--m_end];
273         bubbleDown(0);
274     }
275     return ret;
276 }
277
278 void QQuickParticleDataHeap::clear()
279 {
280     m_size = 0;
281     m_end = 0;
282     //m_size is in powers of two. So to start at 0 we have one allocated
283     m_data.resize(1);
284     m_lookups.clear();
285 }
286
287 bool QQuickParticleDataHeap::contains(QQuickParticleData* d)
288 {
289     for (int i=0; i<m_end; i++)
290         if (m_data[i].data.contains(d))
291             return true;
292     return false;
293 }
294
295 void QQuickParticleDataHeap::swap(int a, int b)
296 {
297     m_tmp = m_data[a];
298     m_data[a] = m_data[b];
299     m_data[b] = m_tmp;
300     m_lookups[m_data[a].time] = a;
301     m_lookups[m_data[b].time] = b;
302 }
303
304 void QQuickParticleDataHeap::bubbleUp(int idx)//tends to be called once
305 {
306     if (!idx)
307         return;
308     int parent = (idx-1)/2;
309     if (m_data[idx].time < m_data[parent].time) {
310         swap(idx, parent);
311         bubbleUp(parent);
312     }
313 }
314
315 void QQuickParticleDataHeap::bubbleDown(int idx)//tends to be called log n times
316 {
317     int left = idx*2 + 1;
318     if (left >= m_end)
319         return;
320     int lesser = left;
321     int right = idx*2 + 2;
322     if (right < m_end) {
323         if (m_data[left].time > m_data[right].time)
324             lesser = right;
325     }
326     if (m_data[idx].time > m_data[lesser].time) {
327         swap(idx, lesser);
328         bubbleDown(lesser);
329     }
330 }
331
332 QQuickParticleGroupData::QQuickParticleGroupData(int id, QQuickParticleSystem* sys):index(id),m_size(0),m_system(sys)
333 {
334     initList();
335 }
336
337 QQuickParticleGroupData::~QQuickParticleGroupData()
338 {
339     foreach (QQuickParticleData* d, data)
340         delete d;
341 }
342
343 int QQuickParticleGroupData::size()
344 {
345     return m_size;
346 }
347
348 QString QQuickParticleGroupData::name()//### Worth caching as well?
349 {
350     return m_system->groupIds.key(index);
351 }
352
353 void QQuickParticleGroupData::setSize(int newSize)
354 {
355     if (newSize == m_size)
356         return;
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;
362         data[i]->index = i;
363         reusableIndexes << i;
364     }
365     int delta = newSize - m_size;
366     m_size = newSize;
367     foreach (QQuickParticlePainter* p, painters)
368         p->setCount(p->count() + delta);
369 }
370
371 void QQuickParticleGroupData::initList()
372 {
373     dataHeap.clear();
374 }
375
376 void QQuickParticleGroupData::kill(QQuickParticleData* d)
377 {
378     Q_ASSERT(d->group == index);
379     d->lifeSpan = 0;//Kill off
380     foreach (QQuickParticlePainter* p, painters)
381         p->reload(d);
382     reusableIndexes << d->index;
383 }
384
385 QQuickParticleData* QQuickParticleGroupData::newDatum(bool respectsLimits)
386 {
387     //recycle();//Extra recycler round to be sure?
388
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]);
394             continue;
395         }
396         return data[idx];
397     }
398     if (respectsLimits)
399         return 0;
400
401     int oldSize = m_size;
402     setSize(oldSize + 10);//###+1,10%,+10? Choose something non-arbitrarily
403     reusableIndexes.remove(oldSize);
404     return data[oldSize];
405 }
406
407 bool QQuickParticleGroupData::recycle()
408 {
409     while (dataHeap.top() <= m_system->timeInt) {
410         foreach (QQuickParticleData* datum, dataHeap.pop()) {
411             if (!datum->stillAlive()) {
412                 reusableIndexes << datum->index;
413             } else {
414                 prepareRecycler(datum); //ttl has been altered mid-way, put it back
415             }
416         }
417     }
418
419     //TODO: If the data is clear, gc (consider shrinking stack size)?
420     return reusableIndexes.count() == m_size;
421 }
422
423 void QQuickParticleGroupData::prepareRecycler(QQuickParticleData* d)
424 {
425     if (d->lifeSpan*1000 < m_system->maxLife) {
426         dataHeap.insert(d);
427     } else {
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);
431     }
432 }
433
434 QQuickParticleData::QQuickParticleData(QQuickParticleSystem* sys)
435     : group(0)
436     , e(0)
437     , system(sys)
438     , index(0)
439     , systemIndex(-1)
440     , colorOwner(0)
441     , rotationOwner(0)
442     , deformationOwner(0)
443     , animationOwner(0)
444     , v8Datum(0)
445 {
446     x = 0;
447     y = 0;
448     t = -1;
449     lifeSpan = 0;
450     size = 0;
451     endSize = 0;
452     vx = 0;
453     vy = 0;
454     ax = 0;
455     ay = 0;
456     xx = 1;
457     xy = 0;
458     yx = 0;
459     yy = 1;
460     rotation = 0;
461     rotationSpeed = 0;
462     autoRotate = 0;
463     animIdx = 0;
464     frameDuration = 1;
465     frameAt = -1;
466     frameCount = 1;
467     animT = -1;
468     animX = 0;
469     animY = 0;
470     animWidth = 1;
471     animHeight = 1;
472     color.r = 255;
473     color.g = 255;
474     color.b = 255;
475     color.a = 255;
476     r = 0;
477     delegate = 0;
478     modelIndex = -1;
479 }
480
481 QQuickParticleData::~QQuickParticleData()
482 {
483     delete v8Datum;
484 }
485
486 void QQuickParticleData::clone(const QQuickParticleData& other)
487 {
488     x = other.x;
489     y = other.y;
490     t = other.t;
491     lifeSpan = other.lifeSpan;
492     size = other.size;
493     endSize = other.endSize;
494     vx = other.vx;
495     vy = other.vy;
496     ax = other.ax;
497     ay = other.ay;
498     xx = other.xx;
499     xy = other.xy;
500     yx = other.yx;
501     yy = other.yy;
502     rotation = other.rotation;
503     rotationSpeed = other.rotationSpeed;
504     autoRotate = other.autoRotate;
505     animIdx = other.animIdx;
506     frameDuration = other.frameDuration;
507     frameCount = other.frameCount;
508     animT = other.animT;
509     animX = other.animX;
510     animY = other.animY;
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;
517     r = other.r;
518     delegate = other.delegate;
519     modelIndex = other.modelIndex;
520
521     colorOwner = other.colorOwner;
522     rotationOwner = other.rotationOwner;
523     deformationOwner = other.deformationOwner;
524     animationOwner = other.animationOwner;
525 }
526
527 QDeclarativeV8Handle QQuickParticleData::v8Value()
528 {
529     if (!v8Datum)
530         v8Datum = new QQuickV8ParticleData(QDeclarativeEnginePrivate::getV8Engine(qmlEngine(system)), this);
531     return v8Datum->v8Value();
532 }
533 //sets the x accleration without affecting the instantaneous x velocity or position
534 void QQuickParticleData::setInstantaneousAX(qreal ax)
535 {
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;
540
541     this->ax = ax;
542     this->vx = vx;
543     this->x = x;
544 }
545
546 //sets the x velocity without affecting the instantaneous x postion
547 void QQuickParticleData::setInstantaneousVX(qreal vx)
548 {
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;
553
554     this->vx = evx;
555     this->x = x;
556 }
557
558 //sets the instantaneous x postion
559 void QQuickParticleData::setInstantaneousX(qreal x)
560 {
561     qreal t = (system->timeInt / 1000.0) - this->t;
562     this->x = x - t*this->vx - 0.5 * t*t*this->ax;
563 }
564
565 //sets the y accleration without affecting the instantaneous y velocity or position
566 void QQuickParticleData::setInstantaneousAY(qreal ay)
567 {
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;
572
573     this->ay = ay;
574     this->vy = vy;
575     this->y = y;
576 }
577
578 //sets the y velocity without affecting the instantaneous y position
579 void QQuickParticleData::setInstantaneousVY(qreal vy)
580 {
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;
585
586     this->vy = evy;
587     this->y = y;
588 }
589
590 //sets the instantaneous Y position
591 void QQuickParticleData::setInstantaneousY(qreal y)
592 {
593     qreal t = (system->timeInt / 1000.0) - this->t;
594     this->y = y - t*this->vy - 0.5 * t*t*this->ay;
595 }
596
597 qreal QQuickParticleData::curX() const
598 {
599     qreal t = (system->timeInt / 1000.0) - this->t;
600     return this->x + this->vx * t + 0.5 * this->ax * t * t;
601 }
602
603 qreal QQuickParticleData::curVX() const
604 {
605     qreal t = (system->timeInt / 1000.0) - this->t;
606     return this->vx + t*this->ax;
607 }
608
609 qreal QQuickParticleData::curY() const
610 {
611     qreal t = (system->timeInt / 1000.0) - this->t;
612     return y + vy * t + 0.5 * ay * t * t;
613 }
614
615 qreal QQuickParticleData::curVY() const
616 {
617     qreal t = (system->timeInt / 1000.0) - this->t;
618     return vy + t*ay;
619 }
620
621 void QQuickParticleData::debugDump()
622 {
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) ;
629 }
630
631 bool QQuickParticleData::stillAlive()
632 {
633     if (!system)
634         return false;
635     return (t + lifeSpan - EPSILON) > ((qreal)system->timeInt/1000.0);
636 }
637
638 bool QQuickParticleData::alive()
639 {
640     if (!system)
641         return false;
642     qreal st = ((qreal)system->timeInt/1000.0);
643     return (t + EPSILON) < st && (t + lifeSpan - EPSILON) > st;
644 }
645
646 float QQuickParticleData::curSize()
647 {
648     if (!system || !lifeSpan)
649         return 0.0f;
650     return size + (endSize - size) * (1 - (lifeLeft() / lifeSpan));
651 }
652
653 float QQuickParticleData::lifeLeft()
654 {
655     if (!system)
656         return 0.0f;
657     return (t + lifeSpan) - (system->timeInt/1000.0);
658 }
659
660 void QQuickParticleData::extendLife(float time)
661 {
662     qreal newX = curX();
663     qreal newY = curY();
664     qreal newVX = curVX();
665     qreal newVY = curVY();
666
667     t += time;
668     animT += time;
669
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;
675
676     x = ex;
677     vx = evx;
678     y = ey;
679     vy = evy;
680 }
681
682 QQuickParticleSystem::QQuickParticleSystem(QQuickItem *parent) :
683     QQuickItem(parent),
684     stateEngine(0),
685     m_animation(0),
686     m_running(true),
687     initialized(0),
688     particleCount(0),
689     m_nextIndex(0),
690     m_componentComplete(false),
691     m_paused(false),
692     m_empty(true)
693 {
694     connect(&m_painterMapper, SIGNAL(mapped(QObject*)),
695             this, SLOT(loadPainter(QObject*)));
696
697     m_debugMode = qmlParticlesDebug();
698 }
699
700 QQuickParticleSystem::~QQuickParticleSystem()
701 {
702     foreach (QQuickParticleGroupData* gd, groupData)
703         delete gd;
704 }
705
706 void QQuickParticleSystem::initGroups()
707 {
708     m_reusableIndexes.clear();
709     m_nextIndex = 0;
710
711     qDeleteAll(groupData);
712     groupData.clear();
713     groupIds.clear();
714
715     QQuickParticleGroupData* gd = new QQuickParticleGroupData(0, this);//Default group
716     groupData.insert(0,gd);
717     groupIds.insert(QString(), 0);
718     m_nextGroupId = 1;
719 }
720
721 void QQuickParticleSystem::registerParticlePainter(QQuickParticlePainter* p)
722 {
723     if (m_debugMode)
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()));
729     loadPainter(p);
730 }
731
732 void QQuickParticleSystem::registerParticleEmitter(QQuickParticleEmitter* e)
733 {
734     if (m_debugMode)
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()));
741     emittersChanged();
742     e->reset();//Start, so that starttime factors appropriately
743 }
744
745 void QQuickParticleSystem::registerParticleAffector(QQuickParticleAffector* a)
746 {
747     if (m_debugMode)
748         qDebug() << "Registering Affector" << a << "to" << this;
749     m_affectors << QPointer<QQuickParticleAffector>(a);
750 }
751
752 void QQuickParticleSystem::registerParticleGroup(QQuickParticleGroup* g)
753 {
754     if (m_debugMode)
755         qDebug() << "Registering Group" << g << "to" << this;
756     m_groups << QPointer<QQuickParticleGroup>(g);
757     createEngine();
758 }
759
760 void QQuickParticleSystem::setRunning(bool arg)
761 {
762     if (m_running != arg) {
763         m_running = arg;
764         emit runningChanged(arg);
765         setPaused(false);
766         if (m_animation)//Not created until componentCompleted
767             m_running ? m_animation->start() : m_animation->stop();
768         reset();
769     }
770 }
771
772 void QQuickParticleSystem::setPaused(bool arg) {
773     if (m_paused != arg) {
774         m_paused = arg;
775         if (m_animation && m_animation->state() != QAbstractAnimation::Stopped)
776             m_paused ? m_animation->pause() : m_animation->resume();
777         if (!m_paused) {
778             foreach (QQuickParticlePainter *p, m_painters)
779                 p->update();
780         }
781         emit pausedChanged(arg);
782     }
783 }
784
785 void QQuickParticleSystem::statePropertyRedirect(QDeclarativeListProperty<QObject> *prop, QObject *value)
786 {
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)
791         return;
792     stateRedirect(group, sys, value);
793 }
794
795 void QQuickParticleSystem::stateRedirect(QQuickParticleGroup* group, QQuickParticleSystem* sys, QObject *value)
796 {
797     QStringList list;
798     list << group->name();
799     QQuickParticleAffector* a = qobject_cast<QQuickParticleAffector*>(value);
800     if (a) {
801         a->setParentItem(sys);
802         a->setGroups(list);
803         a->setSystem(sys);
804         return;
805     }
806     QQuickTrailEmitter* fe = qobject_cast<QQuickTrailEmitter*>(value);
807     if (fe) {
808         fe->setParentItem(sys);
809         fe->setFollow(group->name());
810         fe->setSystem(sys);
811         return;
812     }
813     QQuickParticleEmitter* e = qobject_cast<QQuickParticleEmitter*>(value);
814     if (e) {
815         e->setParentItem(sys);
816         e->setGroup(group->name());
817         e->setSystem(sys);
818         return;
819     }
820     QQuickParticlePainter* p = qobject_cast<QQuickParticlePainter*>(value);
821     if (p) {
822         p->setParentItem(sys);
823         p->setGroups(list);
824         p->setSystem(sys);
825         return;
826     }
827     qWarning() << value << " was placed inside a particle system state but cannot be taken into the particle system. It will be lost.";
828 }
829
830 void QQuickParticleSystem::componentComplete()
831
832 {
833     QQuickItem::componentComplete();
834     m_componentComplete = true;
835     m_animation = new QQuickParticleSystemAnimation(this);
836     reset();//restarts animation as well
837 }
838
839 void QQuickParticleSystem::reset()
840 {
841     if (!m_componentComplete)
842         return;
843
844     timeInt = 0;
845     //Clear guarded pointers which have been deleted
846     int cleared = 0;
847     cleared += m_emitters.removeAll(0);
848     cleared += m_painters.removeAll(0);
849     cleared += m_affectors.removeAll(0);
850
851     bySysIdx.resize(0);
852     initGroups();//Also clears all logical particles
853
854     if (!m_running)
855         return;
856
857     foreach (QQuickParticleEmitter* e, m_emitters)
858         e->reset();
859
860     emittersChanged();
861
862     foreach (QQuickParticlePainter *p, m_painters) {
863         loadPainter(p);
864         p->reset();
865     }
866
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))
871             m_animation->stop();
872         m_animation->start();
873         if (m_paused)
874             m_animation->pause();
875     }
876
877     initialized = true;
878 }
879
880
881 void QQuickParticleSystem::loadPainter(QObject *p)
882 {
883     if (!m_componentComplete || !p)
884         return;
885
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
892         QStringList def;
893         def << QString();
894         painter->setGroups(def);
895         particleCount += groupData[0]->size();
896         groupData[0]->painters << painter;
897     } else {
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);
904             }
905             particleCount += groupData[groupIds[group]]->size();
906             groupData[groupIds[group]]->painters << painter;
907         }
908     }
909     painter->setCount(particleCount);
910     painter->update();//Initial update here
911     return;
912 }
913
914 void QQuickParticleSystem::emittersChanged()
915 {
916     if (!m_componentComplete)
917         return;
918
919     m_emitters.removeAll(0);
920
921
922     QList<int> previousSizes;
923     QList<int> newSizes;
924     for (int i=0; i<m_nextGroupId; i++) {
925         previousSizes << groupData[i]->size();
926         newSizes << 0;
927     }
928
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);
936             previousSizes << 0;
937             newSizes << 0;
938         }
939         newSizes[groupIds[e->group()]] += e->particleCount();
940         //###: Cull emptied groups?
941     }
942
943     //TODO: Garbage collection?
944     particleCount = 0;
945     for (int i=0; i<m_nextGroupId; i++) {
946         groupData[i]->setSize(qMax(newSizes[i], previousSizes[i]));
947         particleCount += groupData[i]->size();
948     }
949
950     if (m_debugMode)
951         qDebug() << "Particle system emitters changed. New particle count: " << particleCount;
952
953     if (particleCount > bySysIdx.size())//New datum requests haven't updated it
954         bySysIdx.resize(particleCount);
955
956     foreach (QQuickParticleAffector *a, m_affectors)//Groups may have changed
957         a->m_updateIntSet = true;
958
959     foreach (QQuickParticlePainter *p, m_painters)
960         loadPainter(p);
961
962     if (!m_groups.isEmpty())
963         createEngine();
964
965 }
966
967 void QQuickParticleSystem::createEngine()
968 {
969     if (!m_componentComplete)
970         return;
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) {
975         bool exists = false;
976         foreach (const QString &name, groupIds.keys())
977             if (group->name() == name)
978                 exists = true;
979         if (!exists) {
980             int id = m_nextGroupId++;
981             QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
982             groupIds.insert(group->name(), id);
983             groupData.insert(id, gd);
984         }
985     }
986
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++) {
991             bool exists = false;
992             QString name = groupData[i]->name();
993             foreach (QQuickParticleGroup* existing, m_groups) {
994                 if (existing->name() == name) {
995                     newList << existing;
996                     exists = true;
997                 }
998             }
999             if (!exists) {
1000                 newList << new QQuickParticleGroup(this);
1001                 newList.back()->setName(name);
1002             }
1003         }
1004         m_groups = newList;
1005         QList<QQuickStochasticState*> states;
1006         foreach (QQuickParticleGroup* g, m_groups)
1007             states << (QQuickStochasticState*)g;
1008
1009         if (!stateEngine)
1010             stateEngine = new QQuickStochasticEngine(this);
1011         stateEngine->setCount(particleCount);
1012         stateEngine->m_states = states;
1013
1014         connect(stateEngine, SIGNAL(stateChanged(int)),
1015                 this, SLOT(particleStateChange(int)));
1016
1017     } else {
1018         if (stateEngine)
1019             delete stateEngine;
1020         stateEngine = 0;
1021     }
1022
1023 }
1024
1025 void QQuickParticleSystem::particleStateChange(int idx)
1026 {
1027     moveGroups(bySysIdx[idx], stateEngine->curState(idx));
1028 }
1029
1030 void QQuickParticleSystem::moveGroups(QQuickParticleData *d, int newGIdx)
1031 {
1032     if (!d || newGIdx == d->group)
1033         return;
1034
1035     QQuickParticleData* pd = newDatum(newGIdx, false, d->systemIndex);
1036     if (!pd)
1037         return;
1038
1039     pd->clone(*d);
1040     finishNewDatum(pd);
1041
1042     d->systemIndex = -1;
1043     groupData[d->group]->kill(d);
1044 }
1045
1046 int QQuickParticleSystem::nextSystemIndex()
1047 {
1048     if (!m_reusableIndexes.isEmpty()) {
1049         int ret = *(m_reusableIndexes.begin());
1050         m_reusableIndexes.remove(ret);
1051         return ret;
1052     }
1053     if (m_nextIndex >= bySysIdx.size()) {
1054         bySysIdx.resize(bySysIdx.size() < 10 ? 10 : bySysIdx.size()*1.1);//###+1,10%,+10? Choose something non-arbitrarily
1055         if (stateEngine)
1056             stateEngine->setCount(bySysIdx.size());
1057
1058     }
1059     return m_nextIndex++;
1060 }
1061
1062 QQuickParticleData* QQuickParticleSystem::newDatum(int groupId, bool respectLimits, int sysIndex)
1063 {
1064     Q_ASSERT(groupId < groupData.count());//XXX shouldn't really be an assert
1065
1066     QQuickParticleData* ret = groupData[groupId]->newDatum(respectLimits);
1067     if (!ret) {
1068         return 0;
1069     }
1070     if (sysIndex == -1) {
1071         if (ret->systemIndex == -1)
1072             ret->systemIndex = nextSystemIndex();
1073     } else {
1074         if (ret->systemIndex != -1) {
1075             if (stateEngine)
1076                 stateEngine->stop(ret->systemIndex);
1077             m_reusableIndexes << ret->systemIndex;
1078             bySysIdx[ret->systemIndex] = 0;
1079         }
1080         ret->systemIndex = sysIndex;
1081     }
1082     bySysIdx[ret->systemIndex] = ret;
1083
1084     if (stateEngine)
1085         stateEngine->start(ret->systemIndex, ret->group);
1086
1087     m_empty = false;
1088     return ret;
1089 }
1090
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();
1098     }
1099
1100     finishNewDatum(pd);
1101 }
1102
1103 void QQuickParticleSystem::finishNewDatum(QQuickParticleData *pd)
1104 {
1105     Q_ASSERT(pd);
1106     groupData[pd->group]->prepareRecycler(pd);
1107
1108     foreach (QQuickParticleAffector *a, m_affectors)
1109         if (a && a->m_needsReset)
1110             a->reset(pd);
1111     foreach (QQuickParticlePainter* p, groupData[pd->group]->painters)
1112         if (p)
1113             p->load(pd);
1114 }
1115
1116 void QQuickParticleSystem::updateCurrentTime( int currentTime )
1117 {
1118     if (!initialized)
1119         return;//error in initialization
1120
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.;
1125     dt = time - dt;
1126     needsReset.clear();
1127
1128     m_emitters.removeAll(0);
1129     m_painters.removeAll(0);
1130     m_affectors.removeAll(0);
1131
1132     bool oldClear = m_empty;
1133     m_empty = true;
1134     foreach (QQuickParticleGroupData* gd, groupData)//Recycle all groups and see if they're out of live particles
1135         m_empty = gd->recycle() && m_empty;
1136
1137     if (stateEngine)
1138         stateEngine->updateSprites(timeInt);
1139
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)
1146             p->reload(d);
1147
1148     if (oldClear != m_empty)
1149         emptyChanged(m_empty);
1150 }
1151
1152 int QQuickParticleSystem::systemSync(QQuickParticlePainter* p)
1153 {
1154     if (!m_running)
1155         return 0;
1156     if (!initialized)
1157         return 0;//error in initialization
1158     p->performPendingCommits();
1159     return timeInt;
1160 }
1161
1162
1163 QT_END_NAMESPACE