diff options
Diffstat (limited to 'src/quick/particles/qquickparticlesystem.cpp')
-rw-r--r-- | src/quick/particles/qquickparticlesystem.cpp | 1106 |
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 ([email protected]) +** +** 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 |