1
/****************************************************************************
3
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4
** Contact: http://www.qt-project.org/
6
** This file is part of the QtQuick module of the Qt Toolkit.
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.
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.
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.
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.
40
****************************************************************************/
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"
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>
59
//###Switch to define later, for now user-friendly (no compilation) debugging is worth it
60
DEFINE_BOOL_CONFIG_OPTION(qmlParticlesDebug, QML_PARTICLES_DEBUG)
63
/* \internal ParticleSystem internals documentation
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).
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.
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.
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
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.
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.
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.
104
\qmlclass ParticleSystem QQuickParticleSystem
105
\inqmlmodule QtQuick.Particles 2
106
\brief The ParticleSystem brings together ParticlePainter, Emitter and Affector elements.
111
\qmlproperty bool QtQuick.Particles2::ParticleSystem::running
113
If running is set to false, the particle system will stop the simulation. All particles
114
will be destroyed when the system is set to running again.
116
It can also be controlled with the start() and stop() methods.
121
\qmlproperty bool QtQuick.Particles2::ParticleSystem::paused
123
If paused is set to true, the particle system will not advance the simulation. When
124
paused is set to false again, the simulation will resume from the same point it was
127
The simulation will automatically pause if it detects that there are no live particles
128
left, and unpause when new live particles are added.
130
It can also be controlled with the pause() and resume() methods.
134
\qmlproperty bool QtQuick.Particles2::ParticleSystem::empty
136
empty is set to true when there are no live particles left in the system.
138
You can use this to pause the system, keeping it from spending any time updating,
139
but you will need to resume it in order for additional particles to be generated
142
To kill all the particles in the system, use a Kill affector.
146
\qmlproperty list<Sprite> QtQuick.Particles2::ParticleSystem::particleStates
148
You can define a sub-set of particle groups in this property in order to provide them
149
with stochastic state transitions.
151
Each QtQuick2::Sprite in this list is interpreted as corresponding to the particle group
152
with ths same name. Any transitions defined in these sprites will take effect on the particle
153
groups as well. Additionally TrailEmitters, Affectors and ParticlePainters definined
154
inside one of these sprites are automatically associated with the corresponding particle group.
158
\qmlmethod void QtQuick.Particles2::ParticleSystem::pause
160
Pauses the simulation if it is running.
166
\qmlmethod void QtQuick.Particles2::ParticleSystem::resume
168
Resumes the simulation if it is paused.
174
\qmlmethod void QtQuick.Particles2::ParticleSystem::start
176
Starts the simulation if it has not already running.
178
\sa stop, restart, running
182
\qmlmethod void QtQuick.Particles2::ParticleSystem::stop
184
Stops the simulation if it is running.
186
\sa start, restart, running
190
\qmlmethod void QtQuick.Particles2::ParticleSystem::restart
192
Stops the simulation if it is running, and then starts it.
194
\sa stop, restart, running
197
\qmlmethod void QtQuick.Particles2::ParticleSystem::reset
199
Discards all currently existing particles.
202
const qreal EPSILON = 0.001;
203
//Utility functions for when within 1ms is close enough
204
bool timeEqualOrGreater(qreal a, qreal b)
206
return (a+EPSILON >= b);
209
bool timeLess(qreal a, qreal b)
211
return (a-EPSILON < b);
214
bool timeEqual(qreal a, qreal b)
216
return (a+EPSILON > b) && (a-EPSILON < b);
219
int roundedTime(qreal a)
221
return (int)qRound(a*1000.0);
224
QQuickParticleDataHeap::QQuickParticleDataHeap()
227
m_data.reserve(1000);
231
void QQuickParticleDataHeap::grow() //###Consider automatic growth vs resize() calls from GroupData
233
m_data.resize(1 << ++m_size);
236
void QQuickParticleDataHeap::insert(QQuickParticleData* data)
238
insertTimed(data, roundedTime(data->t + data->lifeSpan));
241
void QQuickParticleDataHeap::insertTimed(QQuickParticleData* data, int time)
243
//TODO: Optimize 0 lifespan (or already dead) case
244
if (m_lookups.contains(time)) {
245
m_data[m_lookups[time]].data << data;
248
if (m_end == (1 << m_size))
250
m_data[m_end].time = time;
251
m_data[m_end].data.clear();
252
m_data[m_end].data.insert(data);
253
m_lookups.insert(time, m_end);
257
int QQuickParticleDataHeap::top()
261
return m_data[0].time;
264
QSet<QQuickParticleData*> QQuickParticleDataHeap::pop()
267
return QSet<QQuickParticleData*> ();
268
QSet<QQuickParticleData*> ret = m_data[0].data;
269
m_lookups.remove(m_data[0].time);
273
m_data[0] = m_data[--m_end];
279
void QQuickParticleDataHeap::clear()
283
//m_size is in powers of two. So to start at 0 we have one allocated
288
bool QQuickParticleDataHeap::contains(QQuickParticleData* d)
290
for (int i=0; i<m_end; i++)
291
if (m_data[i].data.contains(d))
296
void QQuickParticleDataHeap::swap(int a, int b)
299
m_data[a] = m_data[b];
301
m_lookups[m_data[a].time] = a;
302
m_lookups[m_data[b].time] = b;
305
void QQuickParticleDataHeap::bubbleUp(int idx)//tends to be called once
309
int parent = (idx-1)/2;
310
if (m_data[idx].time < m_data[parent].time) {
316
void QQuickParticleDataHeap::bubbleDown(int idx)//tends to be called log n times
318
int left = idx*2 + 1;
322
int right = idx*2 + 2;
324
if (m_data[left].time > m_data[right].time)
327
if (m_data[idx].time > m_data[lesser].time) {
333
QQuickParticleGroupData::QQuickParticleGroupData(int id, QQuickParticleSystem* sys):index(id),m_size(0),m_system(sys)
338
QQuickParticleGroupData::~QQuickParticleGroupData()
340
foreach (QQuickParticleData* d, data)
344
int QQuickParticleGroupData::size()
349
QString QQuickParticleGroupData::name()//### Worth caching as well?
351
return m_system->groupIds.key(index);
354
void QQuickParticleGroupData::setSize(int newSize)
356
if (newSize == m_size)
358
Q_ASSERT(newSize > m_size);//XXX allow shrinking
359
data.resize(newSize);
360
for (int i=m_size; i<newSize; i++) {
361
data[i] = new QQuickParticleData(m_system);
362
data[i]->group = index;
364
reusableIndexes << i;
366
int delta = newSize - m_size;
368
foreach (QQuickParticlePainter* p, painters)
369
p->setCount(p->count() + delta);
372
void QQuickParticleGroupData::initList()
377
void QQuickParticleGroupData::kill(QQuickParticleData* d)
379
Q_ASSERT(d->group == index);
380
d->lifeSpan = 0;//Kill off
381
foreach (QQuickParticlePainter* p, painters)
383
reusableIndexes << d->index;
386
QQuickParticleData* QQuickParticleGroupData::newDatum(bool respectsLimits)
388
//recycle();//Extra recycler round to be sure?
390
while (!reusableIndexes.empty()) {
391
int idx = *(reusableIndexes.begin());
392
reusableIndexes.remove(idx);
393
if (data[idx]->stillAlive()) {// ### This means resurrection of 'dead' particles. Is that allowed?
394
prepareRecycler(data[idx]);
402
int oldSize = m_size;
403
setSize(oldSize + 10);//###+1,10%,+10? Choose something non-arbitrarily
404
reusableIndexes.remove(oldSize);
405
return data[oldSize];
408
bool QQuickParticleGroupData::recycle()
410
while (dataHeap.top() <= m_system->timeInt) {
411
foreach (QQuickParticleData* datum, dataHeap.pop()) {
412
if (!datum->stillAlive()) {
413
reusableIndexes << datum->index;
415
prepareRecycler(datum); //ttl has been altered mid-way, put it back
420
//TODO: If the data is clear, gc (consider shrinking stack size)?
421
return reusableIndexes.count() == m_size;
424
void QQuickParticleGroupData::prepareRecycler(QQuickParticleData* d)
426
if (d->lifeSpan*1000 < m_system->maxLife) {
429
while ((roundedTime(d->t) + 2*m_system->maxLife/3) <= m_system->timeInt)
430
d->extendLife(m_system->maxLife/3000.0);
431
dataHeap.insertTimed(d, roundedTime(d->t) + 2*m_system->maxLife/3);
435
QQuickParticleData::QQuickParticleData(QQuickParticleSystem* sys)
443
, deformationOwner(0)
482
QQuickParticleData::~QQuickParticleData()
487
void QQuickParticleData::clone(const QQuickParticleData& other)
492
lifeSpan = other.lifeSpan;
494
endSize = other.endSize;
503
rotation = other.rotation;
504
rotationSpeed = other.rotationSpeed;
505
autoRotate = other.autoRotate;
506
animIdx = other.animIdx;
507
frameDuration = other.frameDuration;
508
frameCount = other.frameCount;
512
animWidth = other.animWidth;
513
animHeight = other.animHeight;
514
color.r = other.color.r;
515
color.g = other.color.g;
516
color.b = other.color.b;
517
color.a = other.color.a;
519
delegate = other.delegate;
520
modelIndex = other.modelIndex;
522
colorOwner = other.colorOwner;
523
rotationOwner = other.rotationOwner;
524
deformationOwner = other.deformationOwner;
525
animationOwner = other.animationOwner;
528
QQmlV8Handle QQuickParticleData::v8Value()
531
v8Datum = new QQuickV8ParticleData(QQmlEnginePrivate::getV8Engine(qmlEngine(system)), this);
532
return v8Datum->v8Value();
534
//sets the x accleration without affecting the instantaneous x velocity or position
535
void QQuickParticleData::setInstantaneousAX(qreal ax)
537
qreal t = (system->timeInt / 1000.0) - this->t;
538
qreal vx = (this->vx + t*this->ax) - t*ax;
539
qreal ex = this->x + this->vx * t + 0.5 * this->ax * t * t;
540
qreal x = ex - t*vx - 0.5 * t*t*ax;
547
//sets the x velocity without affecting the instantaneous x postion
548
void QQuickParticleData::setInstantaneousVX(qreal vx)
550
qreal t = (system->timeInt / 1000.0) - this->t;
551
qreal evx = vx - t*this->ax;
552
qreal ex = this->x + this->vx * t + 0.5 * this->ax * t * t;
553
qreal x = ex - t*evx - 0.5 * t*t*this->ax;
559
//sets the instantaneous x postion
560
void QQuickParticleData::setInstantaneousX(qreal x)
562
qreal t = (system->timeInt / 1000.0) - this->t;
563
this->x = x - t*this->vx - 0.5 * t*t*this->ax;
566
//sets the y accleration without affecting the instantaneous y velocity or position
567
void QQuickParticleData::setInstantaneousAY(qreal ay)
569
qreal t = (system->timeInt / 1000.0) - this->t;
570
qreal vy = (this->vy + t*this->ay) - t*ay;
571
qreal ey = this->y + this->vy * t + 0.5 * this->ay * t * t;
572
qreal y = ey - t*vy - 0.5 * t*t*ay;
579
//sets the y velocity without affecting the instantaneous y position
580
void QQuickParticleData::setInstantaneousVY(qreal vy)
582
qreal t = (system->timeInt / 1000.0) - this->t;
583
qreal evy = vy - t*this->ay;
584
qreal ey = this->y + this->vy * t + 0.5 * this->ay * t * t;
585
qreal y = ey - t*evy - 0.5 * t*t*this->ay;
591
//sets the instantaneous Y position
592
void QQuickParticleData::setInstantaneousY(qreal y)
594
qreal t = (system->timeInt / 1000.0) - this->t;
595
this->y = y - t*this->vy - 0.5 * t*t*this->ay;
598
qreal QQuickParticleData::curX() const
600
qreal t = (system->timeInt / 1000.0) - this->t;
601
return this->x + this->vx * t + 0.5 * this->ax * t * t;
604
qreal QQuickParticleData::curVX() const
606
qreal t = (system->timeInt / 1000.0) - this->t;
607
return this->vx + t*this->ax;
610
qreal QQuickParticleData::curY() const
612
qreal t = (system->timeInt / 1000.0) - this->t;
613
return y + vy * t + 0.5 * ay * t * t;
616
qreal QQuickParticleData::curVY() const
618
qreal t = (system->timeInt / 1000.0) - this->t;
622
void QQuickParticleData::debugDump()
624
qDebug() << "Particle" << systemIndex << group << "/" << index << stillAlive()
625
<< "Pos: " << x << "," << y
626
<< "Vel: " << vx << "," << vy
627
<< "Acc: " << ax << "," << ay
628
<< "Size: " << size << "," << endSize
629
<< "Time: " << t << "," <<lifeSpan << ";" << (system->timeInt / 1000.0) ;
632
bool QQuickParticleData::stillAlive()
636
return (t + lifeSpan - EPSILON) > ((qreal)system->timeInt/1000.0);
639
bool QQuickParticleData::alive()
643
qreal st = ((qreal)system->timeInt/1000.0);
644
return (t + EPSILON) < st && (t + lifeSpan - EPSILON) > st;
647
float QQuickParticleData::curSize()
649
if (!system || !lifeSpan)
651
return size + (endSize - size) * (1 - (lifeLeft() / lifeSpan));
654
float QQuickParticleData::lifeLeft()
658
return (t + lifeSpan) - (system->timeInt/1000.0);
661
void QQuickParticleData::extendLife(float time)
665
qreal newVX = curVX();
666
qreal newVY = curVY();
671
qreal elapsed = (system->timeInt / 1000.0) - t;
672
qreal evy = newVY - elapsed*ay;
673
qreal ey = newY - elapsed*evy - 0.5 * elapsed*elapsed*ay;
674
qreal evx = newVX - elapsed*ax;
675
qreal ex = newX - elapsed*evx - 0.5 * elapsed*elapsed*ax;
683
QQuickParticleSystem::QQuickParticleSystem(QQuickItem *parent) :
691
m_componentComplete(false),
695
connect(&m_painterMapper, SIGNAL(mapped(QObject*)),
696
this, SLOT(loadPainter(QObject*)));
698
m_debugMode = qmlParticlesDebug();
701
QQuickParticleSystem::~QQuickParticleSystem()
703
foreach (QQuickParticleGroupData* gd, groupData)
707
void QQuickParticleSystem::initGroups()
709
m_reusableIndexes.clear();
712
qDeleteAll(groupData);
716
QQuickParticleGroupData* gd = new QQuickParticleGroupData(0, this);//Default group
717
groupData.insert(0,gd);
718
groupIds.insert(QString(), 0);
722
void QQuickParticleSystem::registerParticlePainter(QQuickParticlePainter* p)
725
qDebug() << "Registering Painter" << p << "to" << this;
726
//TODO: a way to Unregister emitters, painters and affectors
727
m_painters << QPointer<QQuickParticlePainter>(p);//###Set or uniqueness checking?
728
connect(p, SIGNAL(groupsChanged(QStringList)),
729
&m_painterMapper, SLOT(map()));
733
void QQuickParticleSystem::registerParticleEmitter(QQuickParticleEmitter* e)
736
qDebug() << "Registering Emitter" << e << "to" << this;
737
m_emitters << QPointer<QQuickParticleEmitter>(e);//###How to get them out?
738
connect(e, SIGNAL(particleCountChanged()),
739
this, SLOT(emittersChanged()));
740
connect(e, SIGNAL(groupChanged(QString)),
741
this, SLOT(emittersChanged()));
743
e->reset();//Start, so that starttime factors appropriately
746
void QQuickParticleSystem::registerParticleAffector(QQuickParticleAffector* a)
749
qDebug() << "Registering Affector" << a << "to" << this;
750
m_affectors << QPointer<QQuickParticleAffector>(a);
753
void QQuickParticleSystem::registerParticleGroup(QQuickParticleGroup* g)
756
qDebug() << "Registering Group" << g << "to" << this;
757
m_groups << QPointer<QQuickParticleGroup>(g);
761
void QQuickParticleSystem::setRunning(bool arg)
763
if (m_running != arg) {
765
emit runningChanged(arg);
767
if (m_animation)//Not created until componentCompleted
768
m_running ? m_animation->start() : m_animation->stop();
773
void QQuickParticleSystem::setPaused(bool arg) {
774
if (m_paused != arg) {
776
if (m_animation && m_animation->state() != QAbstractAnimation::Stopped)
777
m_paused ? m_animation->pause() : m_animation->resume();
779
foreach (QQuickParticlePainter *p, m_painters)
782
emit pausedChanged(arg);
786
void QQuickParticleSystem::statePropertyRedirect(QQmlListProperty<QObject> *prop, QObject *value)
788
//Hooks up automatic state-associated stuff
789
QQuickParticleSystem* sys = qobject_cast<QQuickParticleSystem*>(prop->object->parent());
790
QQuickParticleGroup* group = qobject_cast<QQuickParticleGroup*>(prop->object);
791
if (!group || !sys || !value)
793
stateRedirect(group, sys, value);
796
void QQuickParticleSystem::stateRedirect(QQuickParticleGroup* group, QQuickParticleSystem* sys, QObject *value)
799
list << group->name();
800
QQuickParticleAffector* a = qobject_cast<QQuickParticleAffector*>(value);
802
a->setParentItem(sys);
807
QQuickTrailEmitter* fe = qobject_cast<QQuickTrailEmitter*>(value);
809
fe->setParentItem(sys);
810
fe->setFollow(group->name());
814
QQuickParticleEmitter* e = qobject_cast<QQuickParticleEmitter*>(value);
816
e->setParentItem(sys);
817
e->setGroup(group->name());
821
QQuickParticlePainter* p = qobject_cast<QQuickParticlePainter*>(value);
823
p->setParentItem(sys);
828
qWarning() << value << " was placed inside a particle system state but cannot be taken into the particle system. It will be lost.";
831
void QQuickParticleSystem::componentComplete()
834
QQuickItem::componentComplete();
835
m_componentComplete = true;
836
m_animation = new QQuickParticleSystemAnimation(this);
837
reset();//restarts animation as well
840
void QQuickParticleSystem::reset()
842
if (!m_componentComplete)
846
//Clear guarded pointers which have been deleted
848
cleared += m_emitters.removeAll(0);
849
cleared += m_painters.removeAll(0);
850
cleared += m_affectors.removeAll(0);
853
initGroups();//Also clears all logical particles
858
foreach (QQuickParticleEmitter* e, m_emitters)
863
foreach (QQuickParticlePainter *p, m_painters) {
868
//### Do affectors need reset too?
869
if (m_animation) {//Animation is explicitly disabled in benchmarks
870
//reset restarts animation (if running)
871
if ((m_animation->state() == QAbstractAnimation::Running))
873
m_animation->start();
875
m_animation->pause();
882
void QQuickParticleSystem::loadPainter(QObject *p)
884
if (!m_componentComplete || !p)
887
QQuickParticlePainter* painter = qobject_cast<QQuickParticlePainter*>(p);
888
Q_ASSERT(painter);//XXX
889
foreach (QQuickParticleGroupData* sg, groupData)
890
sg->painters.remove(painter);
891
int particleCount = 0;
892
if (painter->groups().isEmpty()) {//Uses default particle
895
painter->setGroups(def);
896
particleCount += groupData[0]->size();
897
groupData[0]->painters << painter;
899
foreach (const QString &group, painter->groups()) {
900
if (group != QLatin1String("") && !groupIds[group]) {//new group
901
int id = m_nextGroupId++;
902
QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
903
groupIds.insert(group, id);
904
groupData.insert(id, gd);
906
particleCount += groupData[groupIds[group]]->size();
907
groupData[groupIds[group]]->painters << painter;
910
painter->setCount(particleCount);
911
painter->update();//Initial update here
915
void QQuickParticleSystem::emittersChanged()
917
if (!m_componentComplete)
920
m_emitters.removeAll(0);
923
QList<int> previousSizes;
925
for (int i=0; i<m_nextGroupId; i++) {
926
previousSizes << groupData[i]->size();
930
foreach (QQuickParticleEmitter* e, m_emitters) {//Populate groups and set sizes.
931
if (!groupIds.contains(e->group())
932
|| (!e->group().isEmpty() && !groupIds[e->group()])) {//or it was accidentally inserted by a failed lookup earlier
933
int id = m_nextGroupId++;
934
QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
935
groupIds.insert(e->group(), id);
936
groupData.insert(id, gd);
940
newSizes[groupIds[e->group()]] += e->particleCount();
941
//###: Cull emptied groups?
944
//TODO: Garbage collection?
946
for (int i=0; i<m_nextGroupId; i++) {
947
groupData[i]->setSize(qMax(newSizes[i], previousSizes[i]));
948
particleCount += groupData[i]->size();
952
qDebug() << "Particle system emitters changed. New particle count: " << particleCount;
954
if (particleCount > bySysIdx.size())//New datum requests haven't updated it
955
bySysIdx.resize(particleCount);
957
foreach (QQuickParticleAffector *a, m_affectors)//Groups may have changed
958
a->m_updateIntSet = true;
960
foreach (QQuickParticlePainter *p, m_painters)
963
if (!m_groups.isEmpty())
968
void QQuickParticleSystem::createEngine()
970
if (!m_componentComplete)
972
if (stateEngine && m_debugMode)
973
qDebug() << "Resetting Existing Sprite Engine...";
974
//### Solve the losses if size/states go down
975
foreach (QQuickParticleGroup* group, m_groups) {
977
foreach (const QString &name, groupIds.keys())
978
if (group->name() == name)
981
int id = m_nextGroupId++;
982
QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
983
groupIds.insert(group->name(), id);
984
groupData.insert(id, gd);
988
if (m_groups.count()) {
989
//Reorder groups List so as to have the same order as groupData
990
QList<QQuickParticleGroup*> newList;
991
for (int i=0; i<m_nextGroupId; i++) {
993
QString name = groupData[i]->name();
994
foreach (QQuickParticleGroup* existing, m_groups) {
995
if (existing->name() == name) {
1001
newList << new QQuickParticleGroup(this);
1002
newList.back()->setName(name);
1006
QList<QQuickStochasticState*> states;
1007
foreach (QQuickParticleGroup* g, m_groups)
1008
states << (QQuickStochasticState*)g;
1011
stateEngine = new QQuickStochasticEngine(this);
1012
stateEngine->setCount(particleCount);
1013
stateEngine->m_states = states;
1015
connect(stateEngine, SIGNAL(stateChanged(int)),
1016
this, SLOT(particleStateChange(int)));
1026
void QQuickParticleSystem::particleStateChange(int idx)
1028
moveGroups(bySysIdx[idx], stateEngine->curState(idx));
1031
void QQuickParticleSystem::moveGroups(QQuickParticleData *d, int newGIdx)
1033
if (!d || newGIdx == d->group)
1036
QQuickParticleData* pd = newDatum(newGIdx, false, d->systemIndex);
1043
d->systemIndex = -1;
1044
groupData[d->group]->kill(d);
1047
int QQuickParticleSystem::nextSystemIndex()
1049
if (!m_reusableIndexes.isEmpty()) {
1050
int ret = *(m_reusableIndexes.begin());
1051
m_reusableIndexes.remove(ret);
1054
if (m_nextIndex >= bySysIdx.size()) {
1055
bySysIdx.resize(bySysIdx.size() < 10 ? 10 : bySysIdx.size()*1.1);//###+1,10%,+10? Choose something non-arbitrarily
1057
stateEngine->setCount(bySysIdx.size());
1060
return m_nextIndex++;
1063
QQuickParticleData* QQuickParticleSystem::newDatum(int groupId, bool respectLimits, int sysIndex)
1065
Q_ASSERT(groupId < groupData.count());//XXX shouldn't really be an assert
1067
QQuickParticleData* ret = groupData[groupId]->newDatum(respectLimits);
1071
if (sysIndex == -1) {
1072
if (ret->systemIndex == -1)
1073
ret->systemIndex = nextSystemIndex();
1075
if (ret->systemIndex != -1) {
1077
stateEngine->stop(ret->systemIndex);
1078
m_reusableIndexes << ret->systemIndex;
1079
bySysIdx[ret->systemIndex] = 0;
1081
ret->systemIndex = sysIndex;
1083
bySysIdx[ret->systemIndex] = ret;
1086
stateEngine->start(ret->systemIndex, ret->group);
1092
void QQuickParticleSystem::emitParticle(QQuickParticleData* pd)
1093
{// called from prepareNextFrame()->emitWindow - enforce?
1094
//Account for relative emitter position
1095
QPointF offset = this->mapFromItem(pd->e, QPointF(0, 0));
1096
if (!offset.isNull()) {
1097
pd->x += offset.x();
1098
pd->y += offset.y();
1104
void QQuickParticleSystem::finishNewDatum(QQuickParticleData *pd)
1107
groupData[pd->group]->prepareRecycler(pd);
1109
foreach (QQuickParticleAffector *a, m_affectors)
1110
if (a && a->m_needsReset)
1112
foreach (QQuickParticlePainter* p, groupData[pd->group]->painters)
1117
void QQuickParticleSystem::updateCurrentTime( int currentTime )
1120
return;//error in initialization
1122
//### Elapsed time never shrinks - may cause problems if left emitting for weeks at a time.
1123
qreal dt = timeInt / 1000.;
1124
timeInt = currentTime;
1125
qreal time = timeInt / 1000.;
1129
m_emitters.removeAll(0);
1130
m_painters.removeAll(0);
1131
m_affectors.removeAll(0);
1133
bool oldClear = m_empty;
1135
foreach (QQuickParticleGroupData* gd, groupData)//Recycle all groups and see if they're out of live particles
1136
m_empty = gd->recycle() && m_empty;
1139
stateEngine->updateSprites(timeInt);
1141
foreach (QQuickParticleEmitter* emitter, m_emitters)
1142
emitter->emitWindow(timeInt);
1143
foreach (QQuickParticleAffector* a, m_affectors)
1144
a->affectSystem(dt);
1145
foreach (QQuickParticleData* d, needsReset)
1146
foreach (QQuickParticlePainter* p, groupData[d->group]->painters)
1149
if (oldClear != m_empty)
1150
emptyChanged(m_empty);
1153
int QQuickParticleSystem::systemSync(QQuickParticlePainter* p)
1158
return 0;//error in initialization
1159
p->performPendingCommits();