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