aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/particles/qquickparticlesystem.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/particles/qquickparticlesystem.cpp')
-rw-r--r--src/quick/particles/qquickparticlesystem.cpp1106
1 files changed, 1106 insertions, 0 deletions
diff --git a/src/quick/particles/qquickparticlesystem.cpp b/src/quick/particles/qquickparticlesystem.cpp
new file mode 100644
index 0000000000..9df086a045
--- /dev/null
+++ b/src/quick/particles/qquickparticlesystem.cpp
@@ -0,0 +1,1106 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the Declarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qquickparticlesystem_p.h"
+#include <QtQuick/qsgnode.h>
+#include "qquickparticleemitter_p.h"
+#include "qquickparticleaffector_p.h"
+#include "qquickparticlepainter_p.h"
+#include <private/qquickspriteengine_p.h>
+#include <private/qquicksprite_p.h>
+#include "qquickv8particledata_p.h"
+#include "qquickparticlegroup_p.h"
+
+#include "qquicktrailemitter_p.h"//###For auto-follow on states, perhaps should be in emitter?
+#include <private/qdeclarativeengine_p.h>
+#include <cmath>
+#include <QDebug>
+
+QT_BEGIN_NAMESPACE
+//###Switch to define later, for now user-friendly (no compilation) debugging is worth it
+DEFINE_BOOL_CONFIG_OPTION(qmlParticlesDebug, QML_PARTICLES_DEBUG)
+/*!
+ \qmlclass ParticleSystem QQuickParticleSystem
+ \inqmlmodule QtQuick.Particles 2
+ \brief The ParticleSystem brings together ParticlePainter, Emitter and Affector elements.
+
+*/
+
+/*!
+ \qmlproperty bool QtQuick.Particles2::ParticleSystem::running
+
+ If running is set to false, the particle system will stop the simulation. All particles
+ will be destroyed when the system is set to running again.
+
+ It can also be controlled with the start() and stop() methods.
+*/
+
+
+/*!
+ \qmlproperty bool QtQuick.Particles2::ParticleSystem::paused
+
+ If paused is set to true, the particle system will not advance the simulation. When
+ paused is set to false again, the simulation will resume from the same point it was
+ paused.
+
+ The simulation will automatically pause if it detects that there are no live particles
+ left, and unpause when new live particles are added.
+
+ It can also be controlled with the pause() and resume() methods.
+*/
+
+/*!
+ \qmlproperty bool QtQuick.Particles2::ParticleSystem::empty
+
+ empty is set to true when there are no live particles left in the system.
+
+ You can use this to pause the system, keeping it from spending any time updating,
+ but you will need to resume it in order for additional particles to be generated
+ by the system.
+
+ To kill all the particles in the system, use a Kill affector.
+*/
+
+/*!
+ \qmlproperty list<Sprite> QtQuick.Particles2::ParticleSystem::particleStates
+
+ You can define a sub-set of particle groups in this property in order to provide them
+ with stochastic state transitions.
+
+ Each QtQuick2::Sprite in this list is interpreted as corresponding to the particle group
+ with ths same name. Any transitions defined in these sprites will take effect on the particle
+ groups as well. Additionally TrailEmitters, Affectors and ParticlePainters definined
+ inside one of these sprites are automatically associated with the corresponding particle group.
+*/
+
+/*!
+ \qmlmethod void QtQuick.Particles2::ParticleSystem::pause
+
+ Pauses the simulation if it is running.
+
+ \sa resume, paused
+*/
+
+/*!
+ \qmlmethod void QtQuick.Particles2::ParticleSystem::resume
+
+ Resumes the simulation if it is paused.
+
+ \sa pause, paused
+*/
+
+/*!
+ \qmlmethod void QtQuick.Particles2::ParticleSystem::start
+
+ Starts the simulation if it has not already running.
+
+ \sa stop, restart, running
+*/
+
+/*!
+ \qmlmethod void QtQuick.Particles2::ParticleSystem::stop
+
+ Stops the simulation if it is running.
+
+ \sa start, restart, running
+*/
+
+/*!
+ \qmlmethod void QtQuick.Particles2::ParticleSystem::restart
+
+ Stops the simulation if it is running, and then starts it.
+
+ \sa stop, restart, running
+*/
+/*!
+ \qmlmethod void QtQuick.Particles2::ParticleSystem::reset
+
+ Discards all currently existing particles.
+
+*/
+const qreal EPSILON = 0.001;
+//Utility functions for when within 1ms is close enough
+bool timeEqualOrGreater(qreal a, qreal b)
+{
+ return (a+EPSILON >= b);
+}
+
+bool timeLess(qreal a, qreal b)
+{
+ return (a-EPSILON < b);
+}
+
+bool timeEqual(qreal a, qreal b)
+{
+ return (a+EPSILON > b) && (a-EPSILON < b);
+}
+
+int roundedTime(qreal a)
+{// in ms
+ return (int)qRound(a*1000.0);
+}
+
+QQuickParticleDataHeap::QQuickParticleDataHeap()
+ : m_data(0)
+{
+ m_data.reserve(1000);
+ clear();
+}
+
+void QQuickParticleDataHeap::grow() //###Consider automatic growth vs resize() calls from GroupData
+{
+ m_data.resize(1 << ++m_size);
+}
+
+void QQuickParticleDataHeap::insert(QQuickParticleData* data)
+{
+ insertTimed(data, roundedTime(data->t + data->lifeSpan));
+}
+
+void QQuickParticleDataHeap::insertTimed(QQuickParticleData* data, int time)
+{
+ //TODO: Optimize 0 lifespan (or already dead) case
+ if (m_lookups.contains(time)) {
+ m_data[m_lookups[time]].data << data;
+ return;
+ }
+ if (m_end == (1 << m_size))
+ grow();
+ m_data[m_end].time = time;
+ m_data[m_end].data.clear();
+ m_data[m_end].data.insert(data);
+ m_lookups.insert(time, m_end);
+ bubbleUp(m_end++);
+}
+
+int QQuickParticleDataHeap::top()
+{
+ if (m_end == 0)
+ return 1 << 30;
+ return m_data[0].time;
+}
+
+QSet<QQuickParticleData*> QQuickParticleDataHeap::pop()
+{
+ if (!m_end)
+ return QSet<QQuickParticleData*> ();
+ QSet<QQuickParticleData*> ret = m_data[0].data;
+ m_lookups.remove(m_data[0].time);
+ if (m_end == 1) {
+ --m_end;
+ } else {
+ m_data[0] = m_data[--m_end];
+ bubbleDown(0);
+ }
+ return ret;
+}
+
+void QQuickParticleDataHeap::clear()
+{
+ m_size = 0;
+ m_end = 0;
+ //m_size is in powers of two. So to start at 0 we have one allocated
+ m_data.resize(1);
+ m_lookups.clear();
+}
+
+bool QQuickParticleDataHeap::contains(QQuickParticleData* d)
+{
+ for (int i=0; i<m_end; i++)
+ if (m_data[i].data.contains(d))
+ return true;
+ return false;
+}
+
+void QQuickParticleDataHeap::swap(int a, int b)
+{
+ m_tmp = m_data[a];
+ m_data[a] = m_data[b];
+ m_data[b] = m_tmp;
+ m_lookups[m_data[a].time] = a;
+ m_lookups[m_data[b].time] = b;
+}
+
+void QQuickParticleDataHeap::bubbleUp(int idx)//tends to be called once
+{
+ if (!idx)
+ return;
+ int parent = (idx-1)/2;
+ if (m_data[idx].time < m_data[parent].time) {
+ swap(idx, parent);
+ bubbleUp(parent);
+ }
+}
+
+void QQuickParticleDataHeap::bubbleDown(int idx)//tends to be called log n times
+{
+ int left = idx*2 + 1;
+ if (left >= m_end)
+ return;
+ int lesser = left;
+ int right = idx*2 + 2;
+ if (right < m_end) {
+ if (m_data[left].time > m_data[right].time)
+ lesser = right;
+ }
+ if (m_data[idx].time > m_data[lesser].time) {
+ swap(idx, lesser);
+ bubbleDown(lesser);
+ }
+}
+
+QQuickParticleGroupData::QQuickParticleGroupData(int id, QQuickParticleSystem* sys):index(id),m_size(0),m_system(sys)
+{
+ initList();
+}
+
+QQuickParticleGroupData::~QQuickParticleGroupData()
+{
+ foreach (QQuickParticleData* d, data)
+ delete d;
+}
+
+int QQuickParticleGroupData::size()
+{
+ return m_size;
+}
+
+QString QQuickParticleGroupData::name()//### Worth caching as well?
+{
+ return m_system->groupIds.key(index);
+}
+
+void QQuickParticleGroupData::setSize(int newSize)
+{
+ if (newSize == m_size)
+ return;
+ Q_ASSERT(newSize > m_size);//XXX allow shrinking
+ data.resize(newSize);
+ for (int i=m_size; i<newSize; i++) {
+ data[i] = new QQuickParticleData(m_system);
+ data[i]->group = index;
+ data[i]->index = i;
+ reusableIndexes << i;
+ }
+ int delta = newSize - m_size;
+ m_size = newSize;
+ foreach (QQuickParticlePainter* p, painters)
+ p->setCount(p->count() + delta);
+}
+
+void QQuickParticleGroupData::initList()
+{
+ dataHeap.clear();
+}
+
+void QQuickParticleGroupData::kill(QQuickParticleData* d)
+{
+ Q_ASSERT(d->group == index);
+ d->lifeSpan = 0;//Kill off
+ foreach (QQuickParticlePainter* p, painters)
+ p->reload(d);
+ reusableIndexes << d->index;
+}
+
+QQuickParticleData* QQuickParticleGroupData::newDatum(bool respectsLimits)
+{
+ //recycle();//Extra recycler round to be sure?
+
+ while (!reusableIndexes.empty()) {
+ int idx = *(reusableIndexes.begin());
+ reusableIndexes.remove(idx);
+ if (data[idx]->stillAlive()) {// ### This means resurrection of 'dead' particles. Is that allowed?
+ prepareRecycler(data[idx]);
+ continue;
+ }
+ return data[idx];
+ }
+ if (respectsLimits)
+ return 0;
+
+ int oldSize = m_size;
+ setSize(oldSize + 10);//###+1,10%,+10? Choose something non-arbitrarily
+ reusableIndexes.remove(oldSize);
+ return data[oldSize];
+}
+
+bool QQuickParticleGroupData::recycle()
+{
+ while (dataHeap.top() <= m_system->timeInt) {
+ foreach (QQuickParticleData* datum, dataHeap.pop()) {
+ if (!datum->stillAlive()) {
+ reusableIndexes << datum->index;
+ } else {
+ prepareRecycler(datum); //ttl has been altered mid-way, put it back
+ }
+ }
+ }
+
+ //TODO: If the data is clear, gc (consider shrinking stack size)?
+ return reusableIndexes.count() == m_size;
+}
+
+void QQuickParticleGroupData::prepareRecycler(QQuickParticleData* d)
+{
+ if (d->lifeSpan*1000 < m_system->maxLife) {
+ dataHeap.insert(d);
+ } else {
+ while ((roundedTime(d->t) + 2*m_system->maxLife/3) <= m_system->timeInt)
+ d->extendLife(m_system->maxLife/3000.0);
+ dataHeap.insertTimed(d, roundedTime(d->t) + 2*m_system->maxLife/3);
+ }
+}
+
+QQuickParticleData::QQuickParticleData(QQuickParticleSystem* sys)
+ : group(0)
+ , e(0)
+ , system(sys)
+ , index(0)
+ , systemIndex(-1)
+ , colorOwner(0)
+ , rotationOwner(0)
+ , deformationOwner(0)
+ , animationOwner(0)
+ , v8Datum(0)
+{
+ x = 0;
+ y = 0;
+ t = -1;
+ lifeSpan = 0;
+ size = 0;
+ endSize = 0;
+ vx = 0;
+ vy = 0;
+ ax = 0;
+ ay = 0;
+ xx = 1;
+ xy = 0;
+ yx = 0;
+ yy = 1;
+ rotation = 0;
+ rotationSpeed = 0;
+ autoRotate = 0;
+ animIdx = 0;
+ frameDuration = 1;
+ frameCount = 1;
+ animT = -1;
+ animX = 0;
+ animY = 0;
+ animWidth = 1;
+ animHeight = 1;
+ color.r = 255;
+ color.g = 255;
+ color.b = 255;
+ color.a = 255;
+ r = 0;
+ delegate = 0;
+ modelIndex = -1;
+}
+
+QQuickParticleData::~QQuickParticleData()
+{
+ delete v8Datum;
+}
+
+void QQuickParticleData::clone(const QQuickParticleData& other)
+{
+ x = other.x;
+ y = other.y;
+ t = other.t;
+ lifeSpan = other.lifeSpan;
+ size = other.size;
+ endSize = other.endSize;
+ vx = other.vx;
+ vy = other.vy;
+ ax = other.ax;
+ ay = other.ay;
+ xx = other.xx;
+ xy = other.xy;
+ yx = other.yx;
+ yy = other.yy;
+ rotation = other.rotation;
+ rotationSpeed = other.rotationSpeed;
+ autoRotate = other.autoRotate;
+ animIdx = other.animIdx;
+ frameDuration = other.frameDuration;
+ frameCount = other.frameCount;
+ animT = other.animT;
+ animX = other.animX;
+ animY = other.animY;
+ animWidth = other.animWidth;
+ animHeight = other.animHeight;
+ color.r = other.color.r;
+ color.g = other.color.g;
+ color.b = other.color.b;
+ color.a = other.color.a;
+ r = other.r;
+ delegate = other.delegate;
+ modelIndex = other.modelIndex;
+
+ colorOwner = other.colorOwner;
+ rotationOwner = other.rotationOwner;
+ deformationOwner = other.deformationOwner;
+ animationOwner = other.animationOwner;
+}
+
+QDeclarativeV8Handle QQuickParticleData::v8Value()
+{
+ if (!v8Datum)
+ v8Datum = new QQuickV8ParticleData(QDeclarativeEnginePrivate::getV8Engine(qmlEngine(system)), this);
+ return v8Datum->v8Value();
+}
+//sets the x accleration without affecting the instantaneous x velocity or position
+void QQuickParticleData::setInstantaneousAX(qreal ax)
+{
+ qreal t = (system->timeInt / 1000.0) - this->t;
+ qreal vx = (this->vx + t*this->ax) - t*ax;
+ qreal ex = this->x + this->vx * t + 0.5 * this->ax * t * t;
+ qreal x = ex - t*vx - 0.5 * t*t*ax;
+
+ this->ax = ax;
+ this->vx = vx;
+ this->x = x;
+}
+
+//sets the x velocity without affecting the instantaneous x postion
+void QQuickParticleData::setInstantaneousVX(qreal vx)
+{
+ qreal t = (system->timeInt / 1000.0) - this->t;
+ qreal evx = vx - t*this->ax;
+ qreal ex = this->x + this->vx * t + 0.5 * this->ax * t * t;
+ qreal x = ex - t*evx - 0.5 * t*t*this->ax;
+
+ this->vx = evx;
+ this->x = x;
+}
+
+//sets the instantaneous x postion
+void QQuickParticleData::setInstantaneousX(qreal x)
+{
+ qreal t = (system->timeInt / 1000.0) - this->t;
+ this->x = x - t*this->vx - 0.5 * t*t*this->ax;
+}
+
+//sets the y accleration without affecting the instantaneous y velocity or position
+void QQuickParticleData::setInstantaneousAY(qreal ay)
+{
+ qreal t = (system->timeInt / 1000.0) - this->t;
+ qreal vy = (this->vy + t*this->ay) - t*ay;
+ qreal ey = this->y + this->vy * t + 0.5 * this->ay * t * t;
+ qreal y = ey - t*vy - 0.5 * t*t*ay;
+
+ this->ay = ay;
+ this->vy = vy;
+ this->y = y;
+}
+
+//sets the y velocity without affecting the instantaneous y position
+void QQuickParticleData::setInstantaneousVY(qreal vy)
+{
+ qreal t = (system->timeInt / 1000.0) - this->t;
+ qreal evy = vy - t*this->ay;
+ qreal ey = this->y + this->vy * t + 0.5 * this->ay * t * t;
+ qreal y = ey - t*evy - 0.5 * t*t*this->ay;
+
+ this->vy = evy;
+ this->y = y;
+}
+
+//sets the instantaneous Y position
+void QQuickParticleData::setInstantaneousY(qreal y)
+{
+ qreal t = (system->timeInt / 1000.0) - this->t;
+ this->y = y - t*this->vy - 0.5 * t*t*this->ay;
+}
+
+qreal QQuickParticleData::curX() const
+{
+ qreal t = (system->timeInt / 1000.0) - this->t;
+ return this->x + this->vx * t + 0.5 * this->ax * t * t;
+}
+
+qreal QQuickParticleData::curVX() const
+{
+ qreal t = (system->timeInt / 1000.0) - this->t;
+ return this->vx + t*this->ax;
+}
+
+qreal QQuickParticleData::curY() const
+{
+ qreal t = (system->timeInt / 1000.0) - this->t;
+ return y + vy * t + 0.5 * ay * t * t;
+}
+
+qreal QQuickParticleData::curVY() const
+{
+ qreal t = (system->timeInt / 1000.0) - this->t;
+ return vy + t*ay;
+}
+
+void QQuickParticleData::debugDump()
+{
+ qDebug() << "Particle" << systemIndex << group << "/" << index << stillAlive()
+ << "Pos: " << x << "," << y
+ << "Vel: " << vx << "," << vy
+ << "Acc: " << ax << "," << ay
+ << "Size: " << size << "," << endSize
+ << "Time: " << t << "," <<lifeSpan << ";" << (system->timeInt / 1000.0) ;
+}
+
+bool QQuickParticleData::stillAlive()
+{
+ if (!system)
+ return false;
+ return (t + lifeSpan - EPSILON) > ((qreal)system->timeInt/1000.0);
+}
+
+bool QQuickParticleData::alive()
+{
+ if (!system)
+ return false;
+ qreal st = ((qreal)system->timeInt/1000.0);
+ return (t + EPSILON) < st && (t + lifeSpan - EPSILON) > st;
+}
+
+float QQuickParticleData::curSize()
+{
+ if (!system || !lifeSpan)
+ return 0.0f;
+ return size + (endSize - size) * (1 - (lifeLeft() / lifeSpan));
+}
+
+float QQuickParticleData::lifeLeft()
+{
+ if (!system)
+ return 0.0f;
+ return (t + lifeSpan) - (system->timeInt/1000.0);
+}
+
+void QQuickParticleData::extendLife(float time)
+{
+ qreal newX = curX();
+ qreal newY = curY();
+ qreal newVX = curVX();
+ qreal newVY = curVY();
+
+ t += time;
+ animT += time;
+
+ qreal elapsed = (system->timeInt / 1000.0) - t;
+ qreal evy = newVY - elapsed*ay;
+ qreal ey = newY - elapsed*evy - 0.5 * elapsed*elapsed*ay;
+ qreal evx = newVX - elapsed*ax;
+ qreal ex = newX - elapsed*evx - 0.5 * elapsed*elapsed*ax;
+
+ x = ex;
+ vx = evx;
+ y = ey;
+ vy = evy;
+}
+
+QQuickParticleSystem::QQuickParticleSystem(QQuickItem *parent) :
+ QQuickItem(parent),
+ stateEngine(0),
+ m_running(true),
+ particleCount(0),
+ m_nextIndex(0),
+ m_componentComplete(false),
+ m_paused(false)
+{
+ connect(&m_painterMapper, SIGNAL(mapped(QObject*)),
+ this, SLOT(loadPainter(QObject*)));
+
+ m_debugMode = qmlParticlesDebug();
+}
+
+QQuickParticleSystem::~QQuickParticleSystem()
+{
+ foreach (QQuickParticleGroupData* gd, groupData)
+ delete gd;
+}
+
+void QQuickParticleSystem::initGroups()
+{
+ m_reusableIndexes.clear();
+ m_nextIndex = 0;
+
+ qDeleteAll(groupData);
+ groupData.clear();
+ groupIds.clear();
+
+ QQuickParticleGroupData* gd = new QQuickParticleGroupData(0, this);//Default group
+ groupData.insert(0,gd);
+ groupIds.insert(QString(), 0);
+ m_nextGroupId = 1;
+}
+
+void QQuickParticleSystem::registerParticlePainter(QQuickParticlePainter* p)
+{
+ //TODO: a way to Unregister emitters, painters and affectors
+ m_painters << QPointer<QQuickParticlePainter>(p);//###Set or uniqueness checking?
+ connect(p, SIGNAL(groupsChanged(QStringList)),
+ &m_painterMapper, SLOT(map()));
+ loadPainter(p);
+}
+
+void QQuickParticleSystem::registerParticleEmitter(QQuickParticleEmitter* e)
+{
+ m_emitters << QPointer<QQuickParticleEmitter>(e);//###How to get them out?
+ connect(e, SIGNAL(particleCountChanged()),
+ this, SLOT(emittersChanged()));
+ connect(e, SIGNAL(groupChanged(QString)),
+ this, SLOT(emittersChanged()));
+ emittersChanged();
+ e->reset();//Start, so that starttime factors appropriately
+}
+
+void QQuickParticleSystem::registerParticleAffector(QQuickParticleAffector* a)
+{
+ m_affectors << QPointer<QQuickParticleAffector>(a);
+}
+
+void QQuickParticleSystem::registerParticleGroup(QQuickParticleGroup* g)
+{
+ m_groups << QPointer<QQuickParticleGroup>(g);
+ createEngine();
+}
+
+void QQuickParticleSystem::setRunning(bool arg)
+{
+ if (m_running != arg) {
+ m_running = arg;
+ emit runningChanged(arg);
+ setPaused(false);
+ if (m_animation)//Not created until componentCompleted
+ m_running ? m_animation->start() : m_animation->stop();
+ reset();
+ }
+}
+
+void QQuickParticleSystem::setPaused(bool arg) {
+ if (m_paused != arg) {
+ m_paused = arg;
+ if (m_animation && m_animation->state() != QAbstractAnimation::Stopped)
+ m_paused ? m_animation->pause() : m_animation->resume();
+ if (!m_paused) {
+ foreach (QQuickParticlePainter *p, m_painters)
+ p->update();
+ }
+ emit pausedChanged(arg);
+ }
+}
+
+void QQuickParticleSystem::statePropertyRedirect(QDeclarativeListProperty<QObject> *prop, QObject *value)
+{
+ //Hooks up automatic state-associated stuff
+ QQuickParticleSystem* sys = qobject_cast<QQuickParticleSystem*>(prop->object->parent());
+ QQuickParticleGroup* group = qobject_cast<QQuickParticleGroup*>(prop->object);
+ if (!group || !sys || !value)
+ return;
+ stateRedirect(group, sys, value);
+}
+
+void QQuickParticleSystem::stateRedirect(QQuickParticleGroup* group, QQuickParticleSystem* sys, QObject *value)
+{
+ QStringList list;
+ list << group->name();
+ QQuickParticleAffector* a = qobject_cast<QQuickParticleAffector*>(value);
+ if (a) {
+ a->setParentItem(sys);
+ a->setGroups(list);
+ a->setSystem(sys);
+ return;
+ }
+ QQuickTrailEmitter* fe = qobject_cast<QQuickTrailEmitter*>(value);
+ if (fe) {
+ fe->setParentItem(sys);
+ fe->setFollow(group->name());
+ fe->setSystem(sys);
+ return;
+ }
+ QQuickParticleEmitter* e = qobject_cast<QQuickParticleEmitter*>(value);
+ if (e) {
+ e->setParentItem(sys);
+ e->setGroup(group->name());
+ e->setSystem(sys);
+ return;
+ }
+ QQuickParticlePainter* p = qobject_cast<QQuickParticlePainter*>(value);
+ if (p) {
+ p->setParentItem(sys);
+ p->setGroups(list);
+ p->setSystem(sys);
+ return;
+ }
+ qWarning() << value << " was placed inside a particle system state but cannot be taken into the particle system. It will be lost.";
+}
+
+void QQuickParticleSystem::componentComplete()
+
+{
+ QQuickItem::componentComplete();
+ m_componentComplete = true;
+ m_animation = new QQuickParticleSystemAnimation(this);
+ reset();//restarts animation as well
+}
+
+void QQuickParticleSystem::reset()
+{
+ if (!m_componentComplete)
+ return;
+
+ timeInt = 0;
+ //Clear guarded pointers which have been deleted
+ int cleared = 0;
+ cleared += m_emitters.removeAll(0);
+ cleared += m_painters.removeAll(0);
+ cleared += m_affectors.removeAll(0);
+
+ bySysIdx.resize(0);
+ initGroups();//Also clears all logical particles
+
+ if (!m_running)
+ return;
+
+ foreach (QQuickParticleEmitter* e, m_emitters)
+ e->reset();
+
+ emittersChanged();
+
+ foreach (QQuickParticlePainter *p, m_painters) {
+ loadPainter(p);
+ p->reset();
+ }
+
+ //### Do affectors need reset too?
+ if (m_animation) {//Animation is explicitly disabled in benchmarks
+ //reset restarts animation (if running)
+ if ((m_animation->state() == QAbstractAnimation::Running))
+ m_animation->stop();
+ m_animation->start();
+ if (m_paused)
+ m_animation->pause();
+ }
+
+ initialized = true;
+}
+
+
+void QQuickParticleSystem::loadPainter(QObject *p)
+{
+ if (!m_componentComplete)
+ return;
+
+ QQuickParticlePainter* painter = qobject_cast<QQuickParticlePainter*>(p);
+ Q_ASSERT(painter);//XXX
+ foreach (QQuickParticleGroupData* sg, groupData)
+ sg->painters.remove(painter);
+ int particleCount = 0;
+ if (painter->groups().isEmpty()) {//Uses default particle
+ QStringList def;
+ def << QString();
+ painter->setGroups(def);
+ particleCount += groupData[0]->size();
+ groupData[0]->painters << painter;
+ } else {
+ foreach (const QString &group, painter->groups()) {
+ if (group != QLatin1String("") && !groupIds[group]) {//new group
+ int id = m_nextGroupId++;
+ QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
+ groupIds.insert(group, id);
+ groupData.insert(id, gd);
+ }
+ particleCount += groupData[groupIds[group]]->size();
+ groupData[groupIds[group]]->painters << painter;
+ }
+ }
+ painter->setCount(particleCount);
+ painter->update();//Initial update here
+ return;
+}
+
+void QQuickParticleSystem::emittersChanged()
+{
+ if (!m_componentComplete)
+ return;
+
+ m_emitters.removeAll(0);
+
+
+ QList<int> previousSizes;
+ QList<int> newSizes;
+ for (int i=0; i<m_nextGroupId; i++) {
+ previousSizes << groupData[i]->size();
+ newSizes << 0;
+ }
+
+ foreach (QQuickParticleEmitter* e, m_emitters) {//Populate groups and set sizes.
+ if (!groupIds.contains(e->group())
+ || (!e->group().isEmpty() && !groupIds[e->group()])) {//or it was accidentally inserted by a failed lookup earlier
+ int id = m_nextGroupId++;
+ QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
+ groupIds.insert(e->group(), id);
+ groupData.insert(id, gd);
+ previousSizes << 0;
+ newSizes << 0;
+ }
+ newSizes[groupIds[e->group()]] += e->particleCount();
+ //###: Cull emptied groups?
+ }
+
+ //TODO: Garbage collection?
+ particleCount = 0;
+ for (int i=0; i<m_nextGroupId; i++) {
+ groupData[i]->setSize(qMax(newSizes[i], previousSizes[i]));
+ particleCount += groupData[i]->size();
+ }
+
+ if (m_debugMode)
+ qDebug() << "Particle system emitters changed. New particle count: " << particleCount;
+
+ if (particleCount > bySysIdx.size())//New datum requests haven't updated it
+ bySysIdx.resize(particleCount);
+
+ foreach (QQuickParticlePainter *p, m_painters)
+ loadPainter(p);
+
+ if (!m_groups.isEmpty())
+ createEngine();
+
+}
+
+void QQuickParticleSystem::createEngine()
+{
+ if (!m_componentComplete)
+ return;
+ if (stateEngine && m_debugMode)
+ qDebug() << "Resetting Existing Sprite Engine...";
+ //### Solve the losses if size/states go down
+ foreach (QQuickParticleGroup* group, m_groups) {
+ bool exists = false;
+ foreach (const QString &name, groupIds.keys())
+ if (group->name() == name)
+ exists = true;
+ if (!exists) {
+ int id = m_nextGroupId++;
+ QQuickParticleGroupData* gd = new QQuickParticleGroupData(id, this);
+ groupIds.insert(group->name(), id);
+ groupData.insert(id, gd);
+ }
+ }
+
+ if (m_groups.count()) {
+ //Reorder groups List so as to have the same order as groupData
+ QList<QQuickParticleGroup*> newList;
+ for (int i=0; i<m_nextGroupId; i++) {
+ bool exists = false;
+ QString name = groupData[i]->name();
+ foreach (QQuickParticleGroup* existing, m_groups) {
+ if (existing->name() == name) {
+ newList << existing;
+ exists = true;
+ }
+ }
+ if (!exists) {
+ newList << new QQuickParticleGroup(this);
+ newList.back()->setName(name);
+ }
+ }
+ m_groups = newList;
+ QList<QQuickStochasticState*> states;
+ foreach (QQuickParticleGroup* g, m_groups)
+ states << (QQuickStochasticState*)g;
+
+ if (!stateEngine)
+ stateEngine = new QQuickStochasticEngine(this);
+ stateEngine->setCount(particleCount);
+ stateEngine->m_states = states;
+
+ connect(stateEngine, SIGNAL(stateChanged(int)),
+ this, SLOT(particleStateChange(int)));
+
+ } else {
+ if (stateEngine)
+ delete stateEngine;
+ stateEngine = 0;
+ }
+
+}
+
+void QQuickParticleSystem::particleStateChange(int idx)
+{
+ moveGroups(bySysIdx[idx], stateEngine->curState(idx));
+}
+
+void QQuickParticleSystem::moveGroups(QQuickParticleData *d, int newGIdx)
+{
+ if (!d || newGIdx == d->group)
+ return;
+
+ QQuickParticleData* pd = newDatum(newGIdx, false, d->systemIndex);
+ if (!pd)
+ return;
+
+ pd->clone(*d);
+ finishNewDatum(pd);
+
+ d->systemIndex = -1;
+ groupData[d->group]->kill(d);
+}
+
+int QQuickParticleSystem::nextSystemIndex()
+{
+ if (!m_reusableIndexes.isEmpty()) {
+ int ret = *(m_reusableIndexes.begin());
+ m_reusableIndexes.remove(ret);
+ return ret;
+ }
+ if (m_nextIndex >= bySysIdx.size()) {
+ bySysIdx.resize(bySysIdx.size() < 10 ? 10 : bySysIdx.size()*1.1);//###+1,10%,+10? Choose something non-arbitrarily
+ if (stateEngine)
+ stateEngine->setCount(bySysIdx.size());
+
+ }
+ return m_nextIndex++;
+}
+
+QQuickParticleData* QQuickParticleSystem::newDatum(int groupId, bool respectLimits, int sysIndex)
+{
+ Q_ASSERT(groupId < groupData.count());//XXX shouldn't really be an assert
+
+ QQuickParticleData* ret = groupData[groupId]->newDatum(respectLimits);
+ if (!ret) {
+ return 0;
+ }
+ if (sysIndex == -1) {
+ if (ret->systemIndex == -1)
+ ret->systemIndex = nextSystemIndex();
+ } else {
+ if (ret->systemIndex != -1) {
+ if (stateEngine)
+ stateEngine->stop(ret->systemIndex);
+ m_reusableIndexes << ret->systemIndex;
+ bySysIdx[ret->systemIndex] = 0;
+ }
+ ret->systemIndex = sysIndex;
+ }
+ bySysIdx[ret->systemIndex] = ret;
+
+ if (stateEngine)
+ stateEngine->start(ret->systemIndex, ret->group);
+
+ m_empty = false;
+ return ret;
+}
+
+void QQuickParticleSystem::emitParticle(QQuickParticleData* pd)
+{// called from prepareNextFrame()->emitWindow - enforce?
+ //Account for relative emitter position
+ QPointF offset = this->mapFromItem(pd->e, QPointF(0, 0));
+ if (!offset.isNull()) {
+ pd->x += offset.x();
+ pd->y += offset.y();
+ }
+
+ finishNewDatum(pd);
+}
+
+void QQuickParticleSystem::finishNewDatum(QQuickParticleData *pd)
+{
+ Q_ASSERT(pd);
+ groupData[pd->group]->prepareRecycler(pd);
+
+ foreach (QQuickParticleAffector *a, m_affectors)
+ if (a && a->m_needsReset)
+ a->reset(pd);
+ foreach (QQuickParticlePainter* p, groupData[pd->group]->painters)
+ if (p)
+ p->load(pd);
+}
+
+void QQuickParticleSystem::updateCurrentTime( int currentTime )
+{
+ if (!initialized)
+ return;//error in initialization
+
+ //### Elapsed time never shrinks - may cause problems if left emitting for weeks at a time.
+ qreal dt = timeInt / 1000.;
+ timeInt = currentTime;
+ qreal time = timeInt / 1000.;
+ dt = time - dt;
+ needsReset.clear();
+
+ m_emitters.removeAll(0);
+ m_painters.removeAll(0);
+ m_affectors.removeAll(0);
+
+ bool oldClear = m_empty;
+ m_empty = true;
+ foreach (QQuickParticleGroupData* gd, groupData)//Recycle all groups and see if they're out of live particles
+ m_empty = gd->recycle() && m_empty;
+
+ if (stateEngine)
+ stateEngine->updateSprites(timeInt);
+
+ foreach (QQuickParticleEmitter* emitter, m_emitters)
+ emitter->emitWindow(timeInt);
+ foreach (QQuickParticleAffector* a, m_affectors)
+ a->affectSystem(dt);
+ foreach (QQuickParticleData* d, needsReset)
+ foreach (QQuickParticlePainter* p, groupData[d->group]->painters)
+ p->reload(d);
+
+ if (oldClear != m_empty)
+ emptyChanged(m_empty);
+}
+
+int QQuickParticleSystem::systemSync(QQuickParticlePainter* p)
+{
+ if (!m_running)
+ return 0;
+ if (!initialized)
+ return 0;//error in initialization
+ p->performPendingCommits();
+ return timeInt;
+}
+
+
+QT_END_NAMESPACE