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