a7010393854708d095240a005491a4a3c014372c
[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     frameCount = 1;
424     animT = -1;
425     animX = 0;
426     animY = 0;
427     animWidth = 1;
428     animHeight = 1;
429     color.r = 255;
430     color.g = 255;
431     color.b = 255;
432     color.a = 255;
433     r = 0;
434     delegate = 0;
435     modelIndex = -1;
436 }
437
438 QQuickParticleData::~QQuickParticleData()
439 {
440     delete v8Datum;
441 }
442
443 void QQuickParticleData::clone(const QQuickParticleData& other)
444 {
445     x = other.x;
446     y = other.y;
447     t = other.t;
448     lifeSpan = other.lifeSpan;
449     size = other.size;
450     endSize = other.endSize;
451     vx = other.vx;
452     vy = other.vy;
453     ax = other.ax;
454     ay = other.ay;
455     xx = other.xx;
456     xy = other.xy;
457     yx = other.yx;
458     yy = other.yy;
459     rotation = other.rotation;
460     rotationSpeed = other.rotationSpeed;
461     autoRotate = other.autoRotate;
462     animIdx = other.animIdx;
463     frameDuration = other.frameDuration;
464     frameCount = other.frameCount;
465     animT = other.animT;
466     animX = other.animX;
467     animY = other.animY;
468     animWidth = other.animWidth;
469     animHeight = other.animHeight;
470     color.r = other.color.r;
471     color.g = other.color.g;
472     color.b = other.color.b;
473     color.a = other.color.a;
474     r = other.r;
475     delegate = other.delegate;
476     modelIndex = other.modelIndex;
477
478     colorOwner = other.colorOwner;
479     rotationOwner = other.rotationOwner;
480     deformationOwner = other.deformationOwner;
481     animationOwner = other.animationOwner;
482 }
483
484 QDeclarativeV8Handle QQuickParticleData::v8Value()
485 {
486     if (!v8Datum)
487         v8Datum = new QQuickV8ParticleData(QDeclarativeEnginePrivate::getV8Engine(qmlEngine(system)), this);
488     return v8Datum->v8Value();
489 }
490 //sets the x accleration without affecting the instantaneous x velocity or position
491 void QQuickParticleData::setInstantaneousAX(qreal ax)
492 {
493     qreal t = (system->timeInt / 1000.0) - this->t;
494     qreal vx = (this->vx + t*this->ax) - t*ax;
495     qreal ex = this->x + this->vx * t + 0.5 * this->ax * t * t;
496     qreal x = ex - t*vx - 0.5 * t*t*ax;
497
498     this->ax = ax;
499     this->vx = vx;
500     this->x = x;
501 }
502
503 //sets the x velocity without affecting the instantaneous x postion
504 void QQuickParticleData::setInstantaneousVX(qreal vx)
505 {
506     qreal t = (system->timeInt / 1000.0) - this->t;
507     qreal evx = vx - t*this->ax;
508     qreal ex = this->x + this->vx * t + 0.5 * this->ax * t * t;
509     qreal x = ex - t*evx - 0.5 * t*t*this->ax;
510
511     this->vx = evx;
512     this->x = x;
513 }
514
515 //sets the instantaneous x postion
516 void QQuickParticleData::setInstantaneousX(qreal x)
517 {
518     qreal t = (system->timeInt / 1000.0) - this->t;
519     this->x = x - t*this->vx - 0.5 * t*t*this->ax;
520 }
521
522 //sets the y accleration without affecting the instantaneous y velocity or position
523 void QQuickParticleData::setInstantaneousAY(qreal ay)
524 {
525     qreal t = (system->timeInt / 1000.0) - this->t;
526     qreal vy = (this->vy + t*this->ay) - t*ay;
527     qreal ey = this->y + this->vy * t + 0.5 * this->ay * t * t;
528     qreal y = ey - t*vy - 0.5 * t*t*ay;
529
530     this->ay = ay;
531     this->vy = vy;
532     this->y = y;
533 }
534
535 //sets the y velocity without affecting the instantaneous y position
536 void QQuickParticleData::setInstantaneousVY(qreal vy)
537 {
538     qreal t = (system->timeInt / 1000.0) - this->t;
539     qreal evy = vy - t*this->ay;
540     qreal ey = this->y + this->vy * t + 0.5 * this->ay * t * t;
541     qreal y = ey - t*evy - 0.5 * t*t*this->ay;
542
543     this->vy = evy;
544     this->y = y;
545 }
546
547 //sets the instantaneous Y position
548 void QQuickParticleData::setInstantaneousY(qreal y)
549 {
550     qreal t = (system->timeInt / 1000.0) - this->t;
551     this->y = y - t*this->vy - 0.5 * t*t*this->ay;
552 }
553
554 qreal QQuickParticleData::curX() const
555 {
556     qreal t = (system->timeInt / 1000.0) - this->t;
557     return this->x + this->vx * t + 0.5 * this->ax * t * t;
558 }
559
560 qreal QQuickParticleData::curVX() const
561 {
562     qreal t = (system->timeInt / 1000.0) - this->t;
563     return this->vx + t*this->ax;
564 }
565
566 qreal QQuickParticleData::curY() const
567 {
568     qreal t = (system->timeInt / 1000.0) - this->t;
569     return y + vy * t + 0.5 * ay * t * t;
570 }
571
572 qreal QQuickParticleData::curVY() const
573 {
574     qreal t = (system->timeInt / 1000.0) - this->t;
575     return vy + t*ay;
576 }
577
578 void QQuickParticleData::debugDump()
579 {
580     qDebug() << "Particle" << systemIndex << group << "/" << index << stillAlive()
581              << "Pos: " << x << "," << y
582              << "Vel: " << vx << "," << vy
583              << "Acc: " << ax << "," << ay
584              << "Size: " << size << "," << endSize
585              << "Time: " << t << "," <<lifeSpan << ";" << (system->timeInt / 1000.0) ;
586 }
587
588 bool QQuickParticleData::stillAlive()
589 {
590     if (!system)
591         return false;
592     return (t + lifeSpan - EPSILON) > ((qreal)system->timeInt/1000.0);
593 }
594
595 bool QQuickParticleData::alive()
596 {
597     if (!system)
598         return false;
599     qreal st = ((qreal)system->timeInt/1000.0);
600     return (t + EPSILON) < st && (t + lifeSpan - EPSILON) > st;
601 }
602
603 float QQuickParticleData::curSize()
604 {
605     if (!system || !lifeSpan)
606         return 0.0f;
607     return size + (endSize - size) * (1 - (lifeLeft() / lifeSpan));
608 }
609
610 float QQuickParticleData::lifeLeft()
611 {
612     if (!system)
613         return 0.0f;
614     return (t + lifeSpan) - (system->timeInt/1000.0);
615 }
616
617 void QQuickParticleData::extendLife(float time)
618 {
619     qreal newX = curX();
620     qreal newY = curY();
621     qreal newVX = curVX();
622     qreal newVY = curVY();
623
624     t += time;
625     animT += time;
626
627     qreal elapsed = (system->timeInt / 1000.0) - t;
628     qreal evy = newVY - elapsed*ay;
629     qreal ey = newY - elapsed*evy - 0.5 * elapsed*elapsed*ay;
630     qreal evx = newVX - elapsed*ax;
631     qreal ex = newX - elapsed*evx - 0.5 * elapsed*elapsed*ax;
632
633     x = ex;
634     vx = evx;
635     y = ey;
636     vy = evy;
637 }
638
639 QQuickParticleSystem::QQuickParticleSystem(QQuickItem *parent) :
640     QQuickItem(parent),
641     stateEngine(0),
642     m_animation(0),
643     m_running(true),
644     initialized(0),
645     particleCount(0),
646     m_nextIndex(0),
647     m_componentComplete(false),
648     m_paused(false),
649     m_empty(true)
650 {
651     connect(&m_painterMapper, SIGNAL(mapped(QObject*)),
652             this, SLOT(loadPainter(QObject*)));
653
654     m_debugMode = qmlParticlesDebug();
655 }
656
657 QQuickParticleSystem::~QQuickParticleSystem()
658 {
659     foreach (QQuickParticleGroupData* gd, groupData)
660         delete gd;
661 }
662
663 void QQuickParticleSystem::initGroups()
664 {
665     m_reusableIndexes.clear();
666     m_nextIndex = 0;
667
668     qDeleteAll(groupData);
669     groupData.clear();
670     groupIds.clear();
671
672     QQuickParticleGroupData* gd = new QQuickParticleGroupData(0, this);//Default group
673     groupData.insert(0,gd);
674     groupIds.insert(QString(), 0);
675     m_nextGroupId = 1;
676 }
677
678 void QQuickParticleSystem::registerParticlePainter(QQuickParticlePainter* p)
679 {
680     if (m_debugMode)
681         qDebug() << "Registering Painter" << p << "to" << this;
682     //TODO: a way to Unregister emitters, painters and affectors
683     m_painters << QPointer<QQuickParticlePainter>(p);//###Set or uniqueness checking?
684     connect(p, SIGNAL(groupsChanged(QStringList)),
685             &m_painterMapper, SLOT(map()));
686     loadPainter(p);
687 }
688
689 void QQuickParticleSystem::registerParticleEmitter(QQuickParticleEmitter* e)
690 {
691     if (m_debugMode)
692         qDebug() << "Registering Emitter" << e << "to" << this;
693     m_emitters << QPointer<QQuickParticleEmitter>(e);//###How to get them out?
694     connect(e, SIGNAL(particleCountChanged()),
695             this, SLOT(emittersChanged()));
696     connect(e, SIGNAL(groupChanged(QString)),
697             this, SLOT(emittersChanged()));
698     emittersChanged();
699     e->reset();//Start, so that starttime factors appropriately
700 }
701
702 void QQuickParticleSystem::registerParticleAffector(QQuickParticleAffector* a)
703 {
704     if (m_debugMode)
705         qDebug() << "Registering Affector" << a << "to" << this;
706     m_affectors << QPointer<QQuickParticleAffector>(a);
707 }
708
709 void QQuickParticleSystem::registerParticleGroup(QQuickParticleGroup* g)
710 {
711     if (m_debugMode)
712         qDebug() << "Registering Group" << g << "to" << this;
713     m_groups << QPointer<QQuickParticleGroup>(g);
714     createEngine();
715 }
716
717 void QQuickParticleSystem::setRunning(bool arg)
718 {
719     if (m_running != arg) {
720         m_running = arg;
721         emit runningChanged(arg);
722         setPaused(false);
723         if (m_animation)//Not created until componentCompleted
724             m_running ? m_animation->start() : m_animation->stop();
725         reset();
726     }
727 }
728
729 void QQuickParticleSystem::setPaused(bool arg) {
730     if (m_paused != arg) {
731         m_paused = arg;
732         if (m_animation && m_animation->state() != QAbstractAnimation::Stopped)
733             m_paused ? m_animation->pause() : m_animation->resume();
734         if (!m_paused) {
735             foreach (QQuickParticlePainter *p, m_painters)
736                 p->update();
737         }
738         emit pausedChanged(arg);
739     }
740 }
741
742 void QQuickParticleSystem::statePropertyRedirect(QDeclarativeListProperty<QObject> *prop, QObject *value)
743 {
744     //Hooks up automatic state-associated stuff
745     QQuickParticleSystem* sys = qobject_cast<QQuickParticleSystem*>(prop->object->parent());
746     QQuickParticleGroup* group = qobject_cast<QQuickParticleGroup*>(prop->object);
747     if (!group || !sys || !value)
748         return;
749     stateRedirect(group, sys, value);
750 }
751
752 void QQuickParticleSystem::stateRedirect(QQuickParticleGroup* group, QQuickParticleSystem* sys, QObject *value)
753 {
754     QStringList list;
755     list << group->name();
756     QQuickParticleAffector* a = qobject_cast<QQuickParticleAffector*>(value);
757     if (a) {
758         a->setParentItem(sys);
759         a->setGroups(list);
760         a->setSystem(sys);
761         return;
762     }
763     QQuickTrailEmitter* fe = qobject_cast<QQuickTrailEmitter*>(value);
764     if (fe) {
765         fe->setParentItem(sys);
766         fe->setFollow(group->name());
767         fe->setSystem(sys);
768         return;
769     }
770     QQuickParticleEmitter* e = qobject_cast<QQuickParticleEmitter*>(value);
771     if (e) {
772         e->setParentItem(sys);
773         e->setGroup(group->name());
774         e->setSystem(sys);
775         return;
776     }
777     QQuickParticlePainter* p = qobject_cast<QQuickParticlePainter*>(value);
778     if (p) {
779         p->setParentItem(sys);
780         p->setGroups(list);
781         p->setSystem(sys);
782         return;
783     }
784     qWarning() << value << " was placed inside a particle system state but cannot be taken into the particle system. It will be lost.";
785 }
786
787 void QQuickParticleSystem::componentComplete()
788
789 {
790     QQuickItem::componentComplete();
791     m_componentComplete = true;
792     m_animation = new QQuickParticleSystemAnimation(this);
793     reset();//restarts animation as well
794 }
795
796 void QQuickParticleSystem::reset()
797 {
798     if (!m_componentComplete)
799         return;
800
801     timeInt = 0;
802     //Clear guarded pointers which have been deleted
803     int cleared = 0;
804     cleared += m_emitters.removeAll(0);
805     cleared += m_painters.removeAll(0);
806     cleared += m_affectors.removeAll(0);
807
808     bySysIdx.resize(0);
809     initGroups();//Also clears all logical particles
810
811     if (!m_running)
812         return;
813
814     foreach (QQuickParticleEmitter* e, m_emitters)
815         e->reset();
816
817     emittersChanged();
818
819     foreach (QQuickParticlePainter *p, m_painters) {
820         loadPainter(p);
821         p->reset();
822     }
823
824     //### Do affectors need reset too?
825     if (m_animation) {//Animation is explicitly disabled in benchmarks
826         //reset restarts animation (if running)
827         if ((m_animation->state() == QAbstractAnimation::Running))
828             m_animation->stop();
829         m_animation->start();
830         if (m_paused)
831             m_animation->pause();
832     }
833
834     initialized = true;
835 }
836
837
838 void QQuickParticleSystem::loadPainter(QObject *p)
839 {
840     if (!m_componentComplete || !p)
841         return;
842
843     QQuickParticlePainter* painter = qobject_cast<QQuickParticlePainter*>(p);
844     Q_ASSERT(painter);//XXX
845     foreach (QQuickParticleGroupData* sg, groupData)
846         sg->painters.remove(painter);
847     int particleCount = 0;
848     if (painter->groups().isEmpty()) {//Uses default particle
849         QStringList def;
850         def << QString();
851         painter->setGroups(def);
852         particleCount += groupData[0]->size();
853         groupData[0]->painters << painter;
854     } else {
855         foreach (const QString &group, painter->groups()) {
856             if (group != QLatin1String("") && !groupIds[group]) {//new group
857                 int id = m_nextGroupId++;
858                 QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
859                 groupIds.insert(group, id);
860                 groupData.insert(id, gd);
861             }
862             particleCount += groupData[groupIds[group]]->size();
863             groupData[groupIds[group]]->painters << painter;
864         }
865     }
866     painter->setCount(particleCount);
867     painter->update();//Initial update here
868     return;
869 }
870
871 void QQuickParticleSystem::emittersChanged()
872 {
873     if (!m_componentComplete)
874         return;
875
876     m_emitters.removeAll(0);
877
878
879     QList<int> previousSizes;
880     QList<int> newSizes;
881     for (int i=0; i<m_nextGroupId; i++) {
882         previousSizes << groupData[i]->size();
883         newSizes << 0;
884     }
885
886     foreach (QQuickParticleEmitter* e, m_emitters) {//Populate groups and set sizes.
887         if (!groupIds.contains(e->group())
888                 || (!e->group().isEmpty() && !groupIds[e->group()])) {//or it was accidentally inserted by a failed lookup earlier
889             int id = m_nextGroupId++;
890             QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
891             groupIds.insert(e->group(), id);
892             groupData.insert(id, gd);
893             previousSizes << 0;
894             newSizes << 0;
895         }
896         newSizes[groupIds[e->group()]] += e->particleCount();
897         //###: Cull emptied groups?
898     }
899
900     //TODO: Garbage collection?
901     particleCount = 0;
902     for (int i=0; i<m_nextGroupId; i++) {
903         groupData[i]->setSize(qMax(newSizes[i], previousSizes[i]));
904         particleCount += groupData[i]->size();
905     }
906
907     if (m_debugMode)
908         qDebug() << "Particle system emitters changed. New particle count: " << particleCount;
909
910     if (particleCount > bySysIdx.size())//New datum requests haven't updated it
911         bySysIdx.resize(particleCount);
912
913     foreach (QQuickParticleAffector *a, m_affectors)//Groups may have changed
914         a->m_updateIntSet = true;
915
916     foreach (QQuickParticlePainter *p, m_painters)
917         loadPainter(p);
918
919     if (!m_groups.isEmpty())
920         createEngine();
921
922 }
923
924 void QQuickParticleSystem::createEngine()
925 {
926     if (!m_componentComplete)
927         return;
928     if (stateEngine && m_debugMode)
929         qDebug() << "Resetting Existing Sprite Engine...";
930     //### Solve the losses if size/states go down
931     foreach (QQuickParticleGroup* group, m_groups) {
932         bool exists = false;
933         foreach (const QString &name, groupIds.keys())
934             if (group->name() == name)
935                 exists = true;
936         if (!exists) {
937             int id = m_nextGroupId++;
938             QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
939             groupIds.insert(group->name(), id);
940             groupData.insert(id, gd);
941         }
942     }
943
944     if (m_groups.count()) {
945         //Reorder groups List so as to have the same order as groupData
946         QList<QQuickParticleGroup*> newList;
947         for (int i=0; i<m_nextGroupId; i++) {
948             bool exists = false;
949             QString name = groupData[i]->name();
950             foreach (QQuickParticleGroup* existing, m_groups) {
951                 if (existing->name() == name) {
952                     newList << existing;
953                     exists = true;
954                 }
955             }
956             if (!exists) {
957                 newList << new QQuickParticleGroup(this);
958                 newList.back()->setName(name);
959             }
960         }
961         m_groups = newList;
962         QList<QQuickStochasticState*> states;
963         foreach (QQuickParticleGroup* g, m_groups)
964             states << (QQuickStochasticState*)g;
965
966         if (!stateEngine)
967             stateEngine = new QQuickStochasticEngine(this);
968         stateEngine->setCount(particleCount);
969         stateEngine->m_states = states;
970
971         connect(stateEngine, SIGNAL(stateChanged(int)),
972                 this, SLOT(particleStateChange(int)));
973
974     } else {
975         if (stateEngine)
976             delete stateEngine;
977         stateEngine = 0;
978     }
979
980 }
981
982 void QQuickParticleSystem::particleStateChange(int idx)
983 {
984     moveGroups(bySysIdx[idx], stateEngine->curState(idx));
985 }
986
987 void QQuickParticleSystem::moveGroups(QQuickParticleData *d, int newGIdx)
988 {
989     if (!d || newGIdx == d->group)
990         return;
991
992     QQuickParticleData* pd = newDatum(newGIdx, false, d->systemIndex);
993     if (!pd)
994         return;
995
996     pd->clone(*d);
997     finishNewDatum(pd);
998
999     d->systemIndex = -1;
1000     groupData[d->group]->kill(d);
1001 }
1002
1003 int QQuickParticleSystem::nextSystemIndex()
1004 {
1005     if (!m_reusableIndexes.isEmpty()) {
1006         int ret = *(m_reusableIndexes.begin());
1007         m_reusableIndexes.remove(ret);
1008         return ret;
1009     }
1010     if (m_nextIndex >= bySysIdx.size()) {
1011         bySysIdx.resize(bySysIdx.size() < 10 ? 10 : bySysIdx.size()*1.1);//###+1,10%,+10? Choose something non-arbitrarily
1012         if (stateEngine)
1013             stateEngine->setCount(bySysIdx.size());
1014
1015     }
1016     return m_nextIndex++;
1017 }
1018
1019 QQuickParticleData* QQuickParticleSystem::newDatum(int groupId, bool respectLimits, int sysIndex)
1020 {
1021     Q_ASSERT(groupId < groupData.count());//XXX shouldn't really be an assert
1022
1023     QQuickParticleData* ret = groupData[groupId]->newDatum(respectLimits);
1024     if (!ret) {
1025         return 0;
1026     }
1027     if (sysIndex == -1) {
1028         if (ret->systemIndex == -1)
1029             ret->systemIndex = nextSystemIndex();
1030     } else {
1031         if (ret->systemIndex != -1) {
1032             if (stateEngine)
1033                 stateEngine->stop(ret->systemIndex);
1034             m_reusableIndexes << ret->systemIndex;
1035             bySysIdx[ret->systemIndex] = 0;
1036         }
1037         ret->systemIndex = sysIndex;
1038     }
1039     bySysIdx[ret->systemIndex] = ret;
1040
1041     if (stateEngine)
1042         stateEngine->start(ret->systemIndex, ret->group);
1043
1044     m_empty = false;
1045     return ret;
1046 }
1047
1048 void QQuickParticleSystem::emitParticle(QQuickParticleData* pd)
1049 {// called from prepareNextFrame()->emitWindow - enforce?
1050     //Account for relative emitter position
1051     QPointF offset = this->mapFromItem(pd->e, QPointF(0, 0));
1052     if (!offset.isNull()) {
1053         pd->x += offset.x();
1054         pd->y += offset.y();
1055     }
1056
1057     finishNewDatum(pd);
1058 }
1059
1060 void QQuickParticleSystem::finishNewDatum(QQuickParticleData *pd)
1061 {
1062     Q_ASSERT(pd);
1063     groupData[pd->group]->prepareRecycler(pd);
1064
1065     foreach (QQuickParticleAffector *a, m_affectors)
1066         if (a && a->m_needsReset)
1067             a->reset(pd);
1068     foreach (QQuickParticlePainter* p, groupData[pd->group]->painters)
1069         if (p)
1070             p->load(pd);
1071 }
1072
1073 void QQuickParticleSystem::updateCurrentTime( int currentTime )
1074 {
1075     if (!initialized)
1076         return;//error in initialization
1077
1078     //### Elapsed time never shrinks - may cause problems if left emitting for weeks at a time.
1079     qreal dt = timeInt / 1000.;
1080     timeInt = currentTime;
1081     qreal time =  timeInt / 1000.;
1082     dt = time - dt;
1083     needsReset.clear();
1084
1085     m_emitters.removeAll(0);
1086     m_painters.removeAll(0);
1087     m_affectors.removeAll(0);
1088
1089     bool oldClear = m_empty;
1090     m_empty = true;
1091     foreach (QQuickParticleGroupData* gd, groupData)//Recycle all groups and see if they're out of live particles
1092         m_empty = gd->recycle() && m_empty;
1093
1094     if (stateEngine)
1095         stateEngine->updateSprites(timeInt);
1096
1097     foreach (QQuickParticleEmitter* emitter, m_emitters)
1098         emitter->emitWindow(timeInt);
1099     foreach (QQuickParticleAffector* a, m_affectors)
1100         a->affectSystem(dt);
1101     foreach (QQuickParticleData* d, needsReset)
1102         foreach (QQuickParticlePainter* p, groupData[d->group]->painters)
1103             p->reload(d);
1104
1105     if (oldClear != m_empty)
1106         emptyChanged(m_empty);
1107 }
1108
1109 int QQuickParticleSystem::systemSync(QQuickParticlePainter* p)
1110 {
1111     if (!m_running)
1112         return 0;
1113     if (!initialized)
1114         return 0;//error in initialization
1115     p->performPendingCommits();
1116     return timeInt;
1117 }
1118
1119
1120 QT_END_NAMESPACE