Add some more particles debugging information
[profile/ivi/qtdeclarative.git] / src / quick / particles / qquickparticlesystem.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
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     particleCount(0),
645     m_nextIndex(0),
646     m_componentComplete(false),
647     m_paused(false)
648 {
649     connect(&m_painterMapper, SIGNAL(mapped(QObject*)),
650             this, SLOT(loadPainter(QObject*)));
651
652     m_debugMode = qmlParticlesDebug();
653 }
654
655 QQuickParticleSystem::~QQuickParticleSystem()
656 {
657     foreach (QQuickParticleGroupData* gd, groupData)
658         delete gd;
659 }
660
661 void QQuickParticleSystem::initGroups()
662 {
663     m_reusableIndexes.clear();
664     m_nextIndex = 0;
665
666     qDeleteAll(groupData);
667     groupData.clear();
668     groupIds.clear();
669
670     QQuickParticleGroupData* gd = new QQuickParticleGroupData(0, this);//Default group
671     groupData.insert(0,gd);
672     groupIds.insert(QString(), 0);
673     m_nextGroupId = 1;
674 }
675
676 void QQuickParticleSystem::registerParticlePainter(QQuickParticlePainter* p)
677 {
678     if (m_debugMode)
679         qDebug() << "Registering Painter" << p << "to" << this;
680     //TODO: a way to Unregister emitters, painters and affectors
681     m_painters << QPointer<QQuickParticlePainter>(p);//###Set or uniqueness checking?
682     connect(p, SIGNAL(groupsChanged(QStringList)),
683             &m_painterMapper, SLOT(map()));
684     loadPainter(p);
685 }
686
687 void QQuickParticleSystem::registerParticleEmitter(QQuickParticleEmitter* e)
688 {
689     if (m_debugMode)
690         qDebug() << "Registering Emitter" << e << "to" << this;
691     m_emitters << QPointer<QQuickParticleEmitter>(e);//###How to get them out?
692     connect(e, SIGNAL(particleCountChanged()),
693             this, SLOT(emittersChanged()));
694     connect(e, SIGNAL(groupChanged(QString)),
695             this, SLOT(emittersChanged()));
696     emittersChanged();
697     e->reset();//Start, so that starttime factors appropriately
698 }
699
700 void QQuickParticleSystem::registerParticleAffector(QQuickParticleAffector* a)
701 {
702     if (m_debugMode)
703         qDebug() << "Registering Affector" << a << "to" << this;
704     m_affectors << QPointer<QQuickParticleAffector>(a);
705 }
706
707 void QQuickParticleSystem::registerParticleGroup(QQuickParticleGroup* g)
708 {
709     if (m_debugMode)
710         qDebug() << "Registering Group" << g << "to" << this;
711     m_groups << QPointer<QQuickParticleGroup>(g);
712     createEngine();
713 }
714
715 void QQuickParticleSystem::setRunning(bool arg)
716 {
717     if (m_running != arg) {
718         m_running = arg;
719         emit runningChanged(arg);
720         setPaused(false);
721         if (m_animation)//Not created until componentCompleted
722             m_running ? m_animation->start() : m_animation->stop();
723         reset();
724     }
725 }
726
727 void QQuickParticleSystem::setPaused(bool arg) {
728     if (m_paused != arg) {
729         m_paused = arg;
730         if (m_animation && m_animation->state() != QAbstractAnimation::Stopped)
731             m_paused ? m_animation->pause() : m_animation->resume();
732         if (!m_paused) {
733             foreach (QQuickParticlePainter *p, m_painters)
734                 p->update();
735         }
736         emit pausedChanged(arg);
737     }
738 }
739
740 void QQuickParticleSystem::statePropertyRedirect(QDeclarativeListProperty<QObject> *prop, QObject *value)
741 {
742     //Hooks up automatic state-associated stuff
743     QQuickParticleSystem* sys = qobject_cast<QQuickParticleSystem*>(prop->object->parent());
744     QQuickParticleGroup* group = qobject_cast<QQuickParticleGroup*>(prop->object);
745     if (!group || !sys || !value)
746         return;
747     stateRedirect(group, sys, value);
748 }
749
750 void QQuickParticleSystem::stateRedirect(QQuickParticleGroup* group, QQuickParticleSystem* sys, QObject *value)
751 {
752     QStringList list;
753     list << group->name();
754     QQuickParticleAffector* a = qobject_cast<QQuickParticleAffector*>(value);
755     if (a) {
756         a->setParentItem(sys);
757         a->setGroups(list);
758         a->setSystem(sys);
759         return;
760     }
761     QQuickTrailEmitter* fe = qobject_cast<QQuickTrailEmitter*>(value);
762     if (fe) {
763         fe->setParentItem(sys);
764         fe->setFollow(group->name());
765         fe->setSystem(sys);
766         return;
767     }
768     QQuickParticleEmitter* e = qobject_cast<QQuickParticleEmitter*>(value);
769     if (e) {
770         e->setParentItem(sys);
771         e->setGroup(group->name());
772         e->setSystem(sys);
773         return;
774     }
775     QQuickParticlePainter* p = qobject_cast<QQuickParticlePainter*>(value);
776     if (p) {
777         p->setParentItem(sys);
778         p->setGroups(list);
779         p->setSystem(sys);
780         return;
781     }
782     qWarning() << value << " was placed inside a particle system state but cannot be taken into the particle system. It will be lost.";
783 }
784
785 void QQuickParticleSystem::componentComplete()
786
787 {
788     QQuickItem::componentComplete();
789     m_componentComplete = true;
790     m_animation = new QQuickParticleSystemAnimation(this);
791     reset();//restarts animation as well
792 }
793
794 void QQuickParticleSystem::reset()
795 {
796     if (!m_componentComplete)
797         return;
798
799     timeInt = 0;
800     //Clear guarded pointers which have been deleted
801     int cleared = 0;
802     cleared += m_emitters.removeAll(0);
803     cleared += m_painters.removeAll(0);
804     cleared += m_affectors.removeAll(0);
805
806     bySysIdx.resize(0);
807     initGroups();//Also clears all logical particles
808
809     if (!m_running)
810         return;
811
812     foreach (QQuickParticleEmitter* e, m_emitters)
813         e->reset();
814
815     emittersChanged();
816
817     foreach (QQuickParticlePainter *p, m_painters) {
818         loadPainter(p);
819         p->reset();
820     }
821
822     //### Do affectors need reset too?
823     if (m_animation) {//Animation is explicitly disabled in benchmarks
824         //reset restarts animation (if running)
825         if ((m_animation->state() == QAbstractAnimation::Running))
826             m_animation->stop();
827         m_animation->start();
828         if (m_paused)
829             m_animation->pause();
830     }
831
832     initialized = true;
833 }
834
835
836 void QQuickParticleSystem::loadPainter(QObject *p)
837 {
838     if (!m_componentComplete)
839         return;
840
841     QQuickParticlePainter* painter = qobject_cast<QQuickParticlePainter*>(p);
842     Q_ASSERT(painter);//XXX
843     foreach (QQuickParticleGroupData* sg, groupData)
844         sg->painters.remove(painter);
845     int particleCount = 0;
846     if (painter->groups().isEmpty()) {//Uses default particle
847         QStringList def;
848         def << QString();
849         painter->setGroups(def);
850         particleCount += groupData[0]->size();
851         groupData[0]->painters << painter;
852     } else {
853         foreach (const QString &group, painter->groups()) {
854             if (group != QLatin1String("") && !groupIds[group]) {//new group
855                 int id = m_nextGroupId++;
856                 QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
857                 groupIds.insert(group, id);
858                 groupData.insert(id, gd);
859             }
860             particleCount += groupData[groupIds[group]]->size();
861             groupData[groupIds[group]]->painters << painter;
862         }
863     }
864     painter->setCount(particleCount);
865     painter->update();//Initial update here
866     return;
867 }
868
869 void QQuickParticleSystem::emittersChanged()
870 {
871     if (!m_componentComplete)
872         return;
873
874     m_emitters.removeAll(0);
875
876
877     QList<int> previousSizes;
878     QList<int> newSizes;
879     for (int i=0; i<m_nextGroupId; i++) {
880         previousSizes << groupData[i]->size();
881         newSizes << 0;
882     }
883
884     foreach (QQuickParticleEmitter* e, m_emitters) {//Populate groups and set sizes.
885         if (!groupIds.contains(e->group())
886                 || (!e->group().isEmpty() && !groupIds[e->group()])) {//or it was accidentally inserted by a failed lookup earlier
887             int id = m_nextGroupId++;
888             QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
889             groupIds.insert(e->group(), id);
890             groupData.insert(id, gd);
891             previousSizes << 0;
892             newSizes << 0;
893         }
894         newSizes[groupIds[e->group()]] += e->particleCount();
895         //###: Cull emptied groups?
896     }
897
898     //TODO: Garbage collection?
899     particleCount = 0;
900     for (int i=0; i<m_nextGroupId; i++) {
901         groupData[i]->setSize(qMax(newSizes[i], previousSizes[i]));
902         particleCount += groupData[i]->size();
903     }
904
905     if (m_debugMode)
906         qDebug() << "Particle system emitters changed. New particle count: " << particleCount;
907
908     if (particleCount > bySysIdx.size())//New datum requests haven't updated it
909         bySysIdx.resize(particleCount);
910
911     foreach (QQuickParticlePainter *p, m_painters)
912         loadPainter(p);
913
914     if (!m_groups.isEmpty())
915         createEngine();
916
917 }
918
919 void QQuickParticleSystem::createEngine()
920 {
921     if (!m_componentComplete)
922         return;
923     if (stateEngine && m_debugMode)
924         qDebug() << "Resetting Existing Sprite Engine...";
925     //### Solve the losses if size/states go down
926     foreach (QQuickParticleGroup* group, m_groups) {
927         bool exists = false;
928         foreach (const QString &name, groupIds.keys())
929             if (group->name() == name)
930                 exists = true;
931         if (!exists) {
932             int id = m_nextGroupId++;
933             QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
934             groupIds.insert(group->name(), id);
935             groupData.insert(id, gd);
936         }
937     }
938
939     if (m_groups.count()) {
940         //Reorder groups List so as to have the same order as groupData
941         QList<QQuickParticleGroup*> newList;
942         for (int i=0; i<m_nextGroupId; i++) {
943             bool exists = false;
944             QString name = groupData[i]->name();
945             foreach (QQuickParticleGroup* existing, m_groups) {
946                 if (existing->name() == name) {
947                     newList << existing;
948                     exists = true;
949                 }
950             }
951             if (!exists) {
952                 newList << new QQuickParticleGroup(this);
953                 newList.back()->setName(name);
954             }
955         }
956         m_groups = newList;
957         QList<QQuickStochasticState*> states;
958         foreach (QQuickParticleGroup* g, m_groups)
959             states << (QQuickStochasticState*)g;
960
961         if (!stateEngine)
962             stateEngine = new QQuickStochasticEngine(this);
963         stateEngine->setCount(particleCount);
964         stateEngine->m_states = states;
965
966         connect(stateEngine, SIGNAL(stateChanged(int)),
967                 this, SLOT(particleStateChange(int)));
968
969     } else {
970         if (stateEngine)
971             delete stateEngine;
972         stateEngine = 0;
973     }
974
975 }
976
977 void QQuickParticleSystem::particleStateChange(int idx)
978 {
979     moveGroups(bySysIdx[idx], stateEngine->curState(idx));
980 }
981
982 void QQuickParticleSystem::moveGroups(QQuickParticleData *d, int newGIdx)
983 {
984     if (!d || newGIdx == d->group)
985         return;
986
987     QQuickParticleData* pd = newDatum(newGIdx, false, d->systemIndex);
988     if (!pd)
989         return;
990
991     pd->clone(*d);
992     finishNewDatum(pd);
993
994     d->systemIndex = -1;
995     groupData[d->group]->kill(d);
996 }
997
998 int QQuickParticleSystem::nextSystemIndex()
999 {
1000     if (!m_reusableIndexes.isEmpty()) {
1001         int ret = *(m_reusableIndexes.begin());
1002         m_reusableIndexes.remove(ret);
1003         return ret;
1004     }
1005     if (m_nextIndex >= bySysIdx.size()) {
1006         bySysIdx.resize(bySysIdx.size() < 10 ? 10 : bySysIdx.size()*1.1);//###+1,10%,+10? Choose something non-arbitrarily
1007         if (stateEngine)
1008             stateEngine->setCount(bySysIdx.size());
1009
1010     }
1011     return m_nextIndex++;
1012 }
1013
1014 QQuickParticleData* QQuickParticleSystem::newDatum(int groupId, bool respectLimits, int sysIndex)
1015 {
1016     Q_ASSERT(groupId < groupData.count());//XXX shouldn't really be an assert
1017
1018     QQuickParticleData* ret = groupData[groupId]->newDatum(respectLimits);
1019     if (!ret) {
1020         return 0;
1021     }
1022     if (sysIndex == -1) {
1023         if (ret->systemIndex == -1)
1024             ret->systemIndex = nextSystemIndex();
1025     } else {
1026         if (ret->systemIndex != -1) {
1027             if (stateEngine)
1028                 stateEngine->stop(ret->systemIndex);
1029             m_reusableIndexes << ret->systemIndex;
1030             bySysIdx[ret->systemIndex] = 0;
1031         }
1032         ret->systemIndex = sysIndex;
1033     }
1034     bySysIdx[ret->systemIndex] = ret;
1035
1036     if (stateEngine)
1037         stateEngine->start(ret->systemIndex, ret->group);
1038
1039     m_empty = false;
1040     return ret;
1041 }
1042
1043 void QQuickParticleSystem::emitParticle(QQuickParticleData* pd)
1044 {// called from prepareNextFrame()->emitWindow - enforce?
1045     //Account for relative emitter position
1046     QPointF offset = this->mapFromItem(pd->e, QPointF(0, 0));
1047     if (!offset.isNull()) {
1048         pd->x += offset.x();
1049         pd->y += offset.y();
1050     }
1051
1052     finishNewDatum(pd);
1053 }
1054
1055 void QQuickParticleSystem::finishNewDatum(QQuickParticleData *pd)
1056 {
1057     Q_ASSERT(pd);
1058     groupData[pd->group]->prepareRecycler(pd);
1059
1060     foreach (QQuickParticleAffector *a, m_affectors)
1061         if (a && a->m_needsReset)
1062             a->reset(pd);
1063     foreach (QQuickParticlePainter* p, groupData[pd->group]->painters)
1064         if (p)
1065             p->load(pd);
1066 }
1067
1068 void QQuickParticleSystem::updateCurrentTime( int currentTime )
1069 {
1070     if (!initialized)
1071         return;//error in initialization
1072
1073     //### Elapsed time never shrinks - may cause problems if left emitting for weeks at a time.
1074     qreal dt = timeInt / 1000.;
1075     timeInt = currentTime;
1076     qreal time =  timeInt / 1000.;
1077     dt = time - dt;
1078     needsReset.clear();
1079
1080     m_emitters.removeAll(0);
1081     m_painters.removeAll(0);
1082     m_affectors.removeAll(0);
1083
1084     bool oldClear = m_empty;
1085     m_empty = true;
1086     foreach (QQuickParticleGroupData* gd, groupData)//Recycle all groups and see if they're out of live particles
1087         m_empty = gd->recycle() && m_empty;
1088
1089     if (stateEngine)
1090         stateEngine->updateSprites(timeInt);
1091
1092     foreach (QQuickParticleEmitter* emitter, m_emitters)
1093         emitter->emitWindow(timeInt);
1094     foreach (QQuickParticleAffector* a, m_affectors)
1095         a->affectSystem(dt);
1096     foreach (QQuickParticleData* d, needsReset)
1097         foreach (QQuickParticlePainter* p, groupData[d->group]->painters)
1098             p->reload(d);
1099
1100     if (oldClear != m_empty)
1101         emptyChanged(m_empty);
1102 }
1103
1104 int QQuickParticleSystem::systemSync(QQuickParticlePainter* p)
1105 {
1106     if (!m_running)
1107         return 0;
1108     if (!initialized)
1109         return 0;//error in initialization
1110     p->performPendingCommits();
1111     return timeInt;
1112 }
1113
1114
1115 QT_END_NAMESPACE