Animation-like API for ParticleSystem
authorAlan Alpert <alan.alpert@nokia.com>
Thu, 1 Sep 2011 05:27:05 +0000 (15:27 +1000)
committerQt by Nokia <qt-info@nokia.com>
Wed, 7 Sep 2011 07:26:15 +0000 (09:26 +0200)
Includes skipping rendering when paused.

Change-Id: I353ac415fb877917d46ba1832ad9cb5a84640b57
Reviewed-on: http://codereview.qt.nokia.com/4041
Reviewed-by: Martin Jones <martin.jones@nokia.com>

examples/declarative/particles/trails/shimmer.qml
src/declarative/particles/qsgcustomparticle.cpp
src/declarative/particles/qsgimageparticle.cpp
src/declarative/particles/qsgparticleemitter.cpp
src/declarative/particles/qsgparticleemitter_p.h
src/declarative/particles/qsgparticlesystem.cpp
src/declarative/particles/qsgparticlesystem_p.h

index 2bd4f69..d195a44 100644 (file)
@@ -47,7 +47,13 @@ Rectangle{
     color: "black"
     MouseArea{
         anchors.fill: parent
-        onClicked: particles.running = !particles.running
+        acceptedButtons: Qt.LeftButton | Qt.RightButton
+        onClicked: {
+            if (mouse.button == Qt.LeftButton)
+                particles.running = !particles.running
+            else
+                particles.paused = !particles.paused;
+        }
     }
     ParticleSystem{ 
         id: particles 
index 11bc7e3..00e3ec1 100644 (file)
@@ -413,13 +413,14 @@ QSGNode *QSGCustomParticle::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeDat
         m_dirtyData = false;
     }
 
-    if (m_system && m_system->isRunning())
+    if (m_system && m_system->isRunning() && !m_system->isPaused()){
         prepareNextFrame();
-    if (m_rootNode){
-        update();
-        //### Should I be using dirty geometry too/instead?
-        foreach (QSGShaderEffectNode* node, m_nodes)
-            node->markDirty(QSGNode::DirtyMaterial); //done in buildData?
+        if (m_rootNode) {
+            update();
+            //### Should I be using dirty geometry too/instead?
+            foreach (QSGGeometryNode* node, m_nodes)
+                node->markDirty(QSGNode::DirtyMaterial);//done in buildData?
+        }
     }
 
     return m_rootNode;
index ec6c737..5e0e977 100644 (file)
@@ -53,7 +53,6 @@
 #include <qsgengine.h>
 
 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)
 
@@ -1024,13 +1023,14 @@ QSGNode *QSGImageParticle::updatePaintNode(QSGNode *, UpdatePaintNodeData *)
         m_pleaseReset = false;
     }
 
-    if (m_system && m_system->isRunning())
+    if (m_system && m_system->isRunning() && !m_system->isPaused()){
         prepareNextFrame();
-    if (m_rootNode){
-        update();
-        //### Should I be using dirty geometry too/instead?
-        foreach (QSGGeometryNode* node, m_nodes)
-            node->markDirty(QSGNode::DirtyMaterial);
+        if (m_rootNode) {
+            update();
+            //### Should I be using dirty geometry too/instead?
+            foreach (QSGGeometryNode* node, m_nodes)
+                node->markDirty(QSGNode::DirtyMaterial);
+        }
     }
 
     return m_rootNode;
index bf4f570..2a76f77 100644 (file)
@@ -317,6 +317,11 @@ void QSGParticleEmitter::setSpeedFromMovement(qreal t)
     emit speedFromMovementChanged();
 }
 
+void QSGParticleEmitter::reset()
+{
+    m_reset_last = true;
+}
+
 void QSGParticleEmitter::emitWindow(int timeStamp)
 {
     if (m_system == 0)
@@ -346,7 +351,6 @@ void QSGParticleEmitter::emitWindow(int timeStamp)
     }
 
     qreal time = timeStamp / 1000.;
-
     qreal particleRatio = 1. / m_particlesPerSecond;
     qreal pt = m_last_emission;
 
index 2c75fbf..cd14fa5 100644 (file)
@@ -257,17 +257,17 @@ public slots:
        }
 
        void setOverWrite(bool arg)
-{
-    if (m_overwrite != arg) {
-    m_overwrite = arg;
-emit overwriteChanged(arg);
-}
-}
+       {
+           if (m_overwrite != arg) {
+               m_overwrite = arg;
+               emit overwriteChanged(arg);
+           }
+       }
 
 public:
        int particleCount() const;
 
-       virtual void reset(){;}
+       virtual void reset();
        QSGParticleExtruder* extruder() const
        {
            return m_extruder;
index efd6c22..8bd3998 100644 (file)
@@ -54,6 +54,8 @@
 #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,8 +66,23 @@ 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.
+
+    It can also be controlled with the pause() and resume() methods.
 */
+
 /*!
     \qmlproperty int QtQuick.Particles2::ParticleSystem::startTime
 
@@ -491,13 +508,10 @@ 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)
 {
-    QSGParticleGroupData* gd = new QSGParticleGroupData(0, this);//Default group
-    m_groupData.insert(0,gd);
-    m_groupIds.insert("",0);
-    m_nextGroupId = 1;
-
     connect(&m_painterMapper, SIGNAL(mapped(QObject*)),
             this, SLOT(loadPainter(QObject*)));
+
+    m_debugMode = qmlParticlesDebug();
 }
 
 QSGParticleSystem::~QSGParticleSystem()
@@ -506,7 +520,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 +543,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 +565,120 @@ 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;
+    }
+    QSGFollowEmitter* fe = qobject_cast<QSGFollowEmitter*>(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_animation){//reset restarts animation (if 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 +708,7 @@ void QSGParticleSystem::loadPainter(QObject *p)
         }
     }
     painter->setCount(particleCount);
-    painter->update();//###Initial update here?
+    painter->update();//Initial update here
     return;
 }
 
@@ -609,101 +751,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()
index abb7f52..f578c43 100644 (file)
@@ -218,6 +218,7 @@ class QSGParticleSystem : public QSGItem
 {
     Q_OBJECT
     Q_PROPERTY(bool running READ isRunning WRITE setRunning NOTIFY runningChanged)
+    Q_PROPERTY(bool paused READ isPaused WRITE setPaused NOTIFY pausedChanged)
     Q_PROPERTY(int startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged)
     Q_PROPERTY(QDeclarativeListProperty<QSGSprite> particleStates READ particleStates)
 
@@ -247,10 +248,18 @@ signals:
     void startTimeChanged(int arg);
 
 
+    void pausedChanged(bool arg);
+
 public slots:
+    void start(){setRunning(true);}
+    void stop(){setRunning(false);}
+    void restart(){setRunning(false);setRunning(true);}
+    void pause(){setPaused(true);}
+    void resume(){setPaused(false);}
+
     void reset();
     void setRunning(bool arg);
-
+    void setPaused(bool arg);
 
     void setStartTime(int arg)
     {
@@ -264,6 +273,7 @@ public slots:
 
     virtual int duration() const { return -1; }
 
+
 protected:
     //This one only once per frame (effectively)
     void componentComplete();
@@ -300,12 +310,18 @@ public://###but only really for related class usage. Perhaps we should all be fr
 
     int m_particle_count;
     static void stateRedirect(QDeclarativeListProperty<QObject> *prop, QObject *value);//From QSGSprite
+    bool isPaused() const
+    {
+        return m_paused;
+    }
+
 private:
     void initializeSystem();
+    void initGroups();
     bool m_running;
     QList<QPointer<QSGParticleEmitter> > m_emitters;
     QList<QPointer<QSGParticleAffector> > m_affectors;
-    QList<QPointer<QSGParticlePainter> > m_particlePainters;
+    QList<QPointer<QSGParticlePainter> > m_painters;
     QList<QPointer<QSGParticlePainter> > m_syncList;
     qint64 m_startTime;
     int m_nextGroupId;
@@ -319,6 +335,8 @@ private:
     friend class QSGParticleSystemAnimation;
     void updateCurrentTime( int currentTime );
     QSGParticleSystemAnimation* m_animation;
+    bool m_paused;
+    bool m_debugMode;
 };
 
 // Internally, this animation drives all the timing. Painters sync up in their updatePaintNode