diff options
Diffstat (limited to 'src/declarative/particles/qsgparticlesystem.cpp')
-rw-r--r-- | src/declarative/particles/qsgparticlesystem.cpp | 411 |
1 files changed, 285 insertions, 126 deletions
diff --git a/src/declarative/particles/qsgparticlesystem.cpp b/src/declarative/particles/qsgparticlesystem.cpp index d12165e4e8..36cbd327ed 100644 --- a/src/declarative/particles/qsgparticlesystem.cpp +++ b/src/declarative/particles/qsgparticlesystem.cpp @@ -48,12 +48,14 @@ #include "qsgsprite_p.h" #include "qsgv8particledata_p.h" -#include "qsgfollowemitter_p.h"//###For auto-follow on states, perhaps should be in emitter? +#include "qsgtrailemitter_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 QSGParticleSystem \inqmlmodule QtQuick.Particles 2 @@ -64,15 +66,38 @@ QT_BEGIN_NAMESPACE /*! \qmlproperty bool QtQuick.Particles2::ParticleSystem::running - If running is set to false, the particle system will not advance the simulation. + 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 int QtQuick.Particles2::ParticleSystem::startTime + \qmlproperty bool QtQuick.Particles2::ParticleSystem::clear + + clear is set to true when there are no live particles left in the system. - If start time is specified, then the system will simulate up to this time - before the system starts playing. This allows you to appear to start with a - fully populated particle system, instead of starting with no particles visible. + 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 @@ -81,10 +106,55 @@ QT_BEGIN_NAMESPACE 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 FollowEmitters, Affectors and ParticlePainters definined + 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){ @@ -115,9 +185,13 @@ void QSGParticleDataHeap::grow() //###Consider automatic growth vs resize() call m_data.resize(1 << ++m_size); } -void QSGParticleDataHeap::insert(QSGParticleData* data)//TODO: Optimize 0 lifespan (or already dead) case +void QSGParticleDataHeap::insert(QSGParticleData* data) { - int time = roundedTime(data->t + data->lifeSpan); + insertTimed(data, roundedTime(data->t + data->lifeSpan)); +} + +void QSGParticleDataHeap::insertTimed(QSGParticleData* data, int time){ + //TODO: Optimize 0 lifespan (or already dead) case if (m_lookups.contains(time)){ m_data[m_lookups[time]].data << data; return; @@ -250,7 +324,8 @@ void QSGParticleGroupData::initList() dataHeap.clear(); } -void QSGParticleGroupData::kill(QSGParticleData* d){ +void QSGParticleGroupData::kill(QSGParticleData* d) +{ Q_ASSERT(d->group == index); d->lifeSpan = 0;//Kill off foreach (QSGParticlePainter* p, painters) @@ -258,21 +333,14 @@ void QSGParticleGroupData::kill(QSGParticleData* d){ reusableIndexes << d->index; } -QSGParticleData* QSGParticleGroupData::newDatum(bool respectsLimits){ - while (dataHeap.top() <= m_system->m_timeInt){ - foreach (QSGParticleData* datum, dataHeap.pop()){ - if (!datum->stillAlive()){ - reusableIndexes << datum->index; - }else{ - prepareRecycler(datum); //ttl has been altered mid-way, put it back - } - } - } +QSGParticleData* QSGParticleGroupData::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? + if (data[idx]->stillAlive()){// ### This means resurrection of 'dead' particles. Is that allowed? prepareRecycler(data[idx]); continue; } @@ -287,8 +355,30 @@ QSGParticleData* QSGParticleGroupData::newDatum(bool respectsLimits){ return data[oldSize]; } +bool QSGParticleGroupData::recycle() +{ + while (dataHeap.top() <= m_system->m_timeInt){ + foreach (QSGParticleData* 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 QSGParticleGroupData::prepareRecycler(QSGParticleData* d){ - dataHeap.insert(d); + if (d->lifeSpan*1000 < m_system->maxLife){ + dataHeap.insert(d); + } else { + while ((roundedTime(d->t) + 2*m_system->maxLife/3) <= m_system->m_timeInt) + d->extendLife(m_system->maxLife/3000.0); + dataHeap.insertTimed(d, roundedTime(d->t) + 2*m_system->maxLife/3); + } } QSGParticleData::QSGParticleData(QSGParticleSystem* sys) @@ -487,17 +577,36 @@ float QSGParticleData::lifeLeft() return (t + lifeSpan) - (system->m_timeInt/1000.0); } -QSGParticleSystem::QSGParticleSystem(QSGItem *parent) : - QSGItem(parent), m_particle_count(0), m_running(true) - , m_startTime(0), m_nextIndex(0), m_componentComplete(false), m_spriteEngine(0) +void QSGParticleData::extendLife(float time) { - QSGParticleGroupData* gd = new QSGParticleGroupData(0, this);//Default group - m_groupData.insert(0,gd); - m_groupIds.insert(QString(),0); - m_nextGroupId = 1; + qreal newX = curX(); + qreal newY = curY(); + qreal newVX = curVX(); + qreal newVY = curVY(); + + t += time; + animT += time; + + qreal elapsed = (system->m_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; +} +QSGParticleSystem::QSGParticleSystem(QSGItem *parent) : + QSGItem(parent), m_particle_count(0), m_running(true), m_paused(false) + , m_nextIndex(0), m_componentComplete(false), m_spriteEngine(0) +{ connect(&m_painterMapper, SIGNAL(mapped(QObject*)), this, SLOT(loadPainter(QObject*))); + + m_debugMode = qmlParticlesDebug(); } QSGParticleSystem::~QSGParticleSystem() @@ -506,7 +615,22 @@ QSGParticleSystem::~QSGParticleSystem() delete gd; } -QDeclarativeListProperty<QSGSprite> QSGParticleSystem::particleStates() +void QSGParticleSystem::initGroups() +{ + m_reusableIndexes.clear(); + m_nextIndex = 0; + + qDeleteAll(m_groupData); + m_groupData.clear(); + m_groupIds.clear(); + + QSGParticleGroupData* gd = new QSGParticleGroupData(0, this);//Default group + m_groupData.insert(0,gd); + m_groupIds.insert("",0); + m_nextGroupId = 1; +} + + QDeclarativeListProperty<QSGSprite> QSGParticleSystem::particleStates() { return QDeclarativeListProperty<QSGSprite>(this, &m_states, spriteAppend, spriteCount, spriteAt, spriteClear); } @@ -514,11 +638,10 @@ QDeclarativeListProperty<QSGSprite> QSGParticleSystem::particleStates() void QSGParticleSystem::registerParticlePainter(QSGParticlePainter* p) { //TODO: a way to Unregister emitters, painters and affectors - m_particlePainters << QPointer<QSGParticlePainter>(p);//###Set or uniqueness checking? + m_painters << QPointer<QSGParticlePainter>(p);//###Set or uniqueness checking? connect(p, SIGNAL(particlesChanged(QStringList)), &m_painterMapper, SLOT(map())); loadPainter(p); - p->update();//###Initial update here? } void QSGParticleSystem::registerParticleEmitter(QSGParticleEmitter* e) @@ -537,6 +660,121 @@ void QSGParticleSystem::registerParticleAffector(QSGParticleAffector* a) m_affectors << QPointer<QSGParticleAffector>(a); } +void QSGParticleSystem::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 QSGParticleSystem::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 (QSGParticlePainter *p, m_painters) + p->update(); + } + emit pausedChanged(arg); + } +} + +void QSGParticleSystem::stateRedirect(QDeclarativeListProperty<QObject> *prop, QObject *value) +{ + //Hooks up automatic state-associated stuff + QSGParticleSystem* sys = qobject_cast<QSGParticleSystem*>(prop->object->parent()); + QSGSprite* sprite = qobject_cast<QSGSprite*>(prop->object); + if (!sprite || !sys) + return; + QStringList list; + list << sprite->name(); + QSGParticleAffector* a = qobject_cast<QSGParticleAffector*>(value); + if (a){ + a->setParentItem(sys); + a->setParticles(list); + a->setSystem(sys); + return; + } + QSGTrailEmitter* fe = qobject_cast<QSGTrailEmitter*>(value); + if (fe){ + fe->setParentItem(sys); + fe->setFollow(sprite->name()); + fe->setSystem(sys); + return; + } + QSGParticleEmitter* e = qobject_cast<QSGParticleEmitter*>(value); + if (e){ + e->setParentItem(sys); + e->setParticle(sprite->name()); + e->setSystem(sys); + return; + } + QSGParticlePainter* p = qobject_cast<QSGParticlePainter*>(value); + if (p){ + p->setParentItem(sys); + p->setParticles(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 QSGParticleSystem::componentComplete() + +{ + QSGItem::componentComplete(); + m_componentComplete = true; + m_animation = new QSGParticleSystemAnimation(this); + reset();//restarts animation as well +} + +void QSGParticleSystem::reset() +{ + if (!m_componentComplete) + return; + + m_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); + + m_bySysIdx.resize(0); + initGroups();//Also clears all logical particles + + if (!m_running) + return; + + foreach (QSGParticleEmitter* e, m_emitters) + e->reset(); + + emittersChanged(); + + foreach (QSGParticlePainter *p, m_painters){ + loadPainter(p); + p->reset(); + } + + //### Do affectors need reset too? + + if (m_running) {//reset restarts animation (if running) + if ((m_animation->state() == QAbstractAnimation::Running)) + m_animation->stop(); + m_animation->start(); + if (m_paused) + m_animation->pause(); + } + m_initialized = true; +} + + void QSGParticleSystem::loadPainter(QObject *p) { if (!m_componentComplete) @@ -566,7 +804,7 @@ void QSGParticleSystem::loadPainter(QObject *p) } } painter->setCount(particleCount); - painter->update();//###Initial update here? + painter->update();//Initial update here return; } @@ -609,101 +847,14 @@ void QSGParticleSystem::emittersChanged() Q_ASSERT(m_particle_count >= m_bySysIdx.size());//XXX when GC done right m_bySysIdx.resize(m_particle_count); - foreach (QSGParticlePainter *p, m_particlePainters) + foreach (QSGParticlePainter *p, m_painters) loadPainter(p); if (!m_states.isEmpty()) createEngine(); - if (m_particle_count > 16000)//###Investigate if these limits are worth warning about? - qWarning() << "Particle system arbitarily believes it has a vast number of particles (>16000). Expect poor performance"; -} - -void QSGParticleSystem::setRunning(bool arg) -{ - if (m_running != arg) { - m_running = arg; - emit runningChanged(arg); - reset(); - } -} - -void QSGParticleSystem::stateRedirect(QDeclarativeListProperty<QObject> *prop, QObject *value) -{ - //Hooks up automatic state-associated stuff - QSGParticleSystem* sys = qobject_cast<QSGParticleSystem*>(prop->object->parent()); - QSGSprite* sprite = qobject_cast<QSGSprite*>(prop->object); - if (!sprite || !sys) - return; - QStringList list; - list << sprite->name(); - QSGParticleAffector* a = qobject_cast<QSGParticleAffector*>(value); - if (a){ - a->setParentItem(sys); - a->setParticles(list); - a->setSystem(sys); - return; - } - QSGFollowEmitter* e = qobject_cast<QSGFollowEmitter*>(value); - if (e){ - e->setParentItem(sys); - e->setFollow(sprite->name()); - e->setSystem(sys); - return; - } - QSGParticlePainter* p = qobject_cast<QSGParticlePainter*>(value); - if (p){ - p->setParentItem(sys); - p->setParticles(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 QSGParticleSystem::componentComplete() - -{ - QSGItem::componentComplete(); - m_componentComplete = true; - m_animation = new QSGParticleSystemAnimation(this); - reset();//restarts animation as well -} - -void QSGParticleSystem::reset()//TODO: Needed? Or just in component complete? -{ - if (!m_componentComplete) - return; - - m_timeInt = 0; - //Clear guarded pointers which have been deleted - int cleared = 0; - cleared += m_emitters.removeAll(0); - cleared += m_particlePainters.removeAll(0); - cleared += m_affectors.removeAll(0); - - emittersChanged(); - - //TODO: Reset data -// foreach (QSGParticlePainter* p, m_particlePainters) -// p->reset(); -// foreach (QSGParticleEmitter* e, m_emitters) -// e->reset(); - //### Do affectors need reset too? - - if (!m_running) - return; - - foreach (QSGParticlePainter *p, m_particlePainters){ - loadPainter(p); - p->reset(); - } - - if (m_animation){ - m_animation->stop(); - m_animation->start(); - } - m_initialized = true; + if (m_debugMode) + qDebug() << "Particle system emitters changed. New particle count: " << m_particle_count; } void QSGParticleSystem::createEngine() @@ -815,6 +966,7 @@ QSGParticleData* QSGParticleSystem::newDatum(int groupId, bool respectLimits, in if (m_spriteEngine) m_spriteEngine->startSprite(ret->systemIndex, ret->group); + m_empty = false; return ret; } @@ -844,17 +996,21 @@ void QSGParticleSystem::finishNewDatum(QSGParticleData *pd){ void QSGParticleSystem::updateCurrentTime( int currentTime ) { - if (!m_running) - return; if (!m_initialized) return;//error in initialization //### Elapsed time never shrinks - may cause problems if left emitting for weeks at a time. qreal dt = m_timeInt / 1000.; - m_timeInt = currentTime + m_startTime; + m_timeInt = currentTime; qreal time = m_timeInt / 1000.; dt = time - dt; m_needsReset.clear(); + + bool oldClear = m_empty; + m_empty = true; + foreach (QSGParticleGroupData* gd, m_groupData)//Recycle all groups and see if they're out of live particles + m_empty = m_empty && gd->recycle(); + if (m_spriteEngine) m_spriteEngine->updateSprites(m_timeInt); @@ -868,6 +1024,9 @@ void QSGParticleSystem::updateCurrentTime( int currentTime ) foreach (QSGParticlePainter* p, m_groupData[d->group]->painters) if (p && d) p->reload(d); + + if (oldClear != m_empty) + emptyChanged(m_empty); } int QSGParticleSystem::systemSync(QSGParticlePainter* p) |