diff options
53 files changed, 2365 insertions, 1098 deletions
diff --git a/demos/declarative/flickr/content/StreamView.qml b/demos/declarative/flickr/content/StreamView.qml index d7b608a391..e2c65e22fe 100644 --- a/demos/declarative/flickr/content/StreamView.qml +++ b/demos/declarative/flickr/content/StreamView.qml @@ -50,7 +50,6 @@ Item{ ParticleSystem{ id: sys anchors.fill:parent - overwrite: false } ModelParticle{ id: mp diff --git a/examples/declarative/particles/custom/delegates.qml b/examples/declarative/particles/custom/delegates.qml new file mode 100644 index 0000000000..4b01c66396 --- /dev/null +++ b/examples/declarative/particles/custom/delegates.qml @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** 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 examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtQuick.Particles 2.0 + +Rectangle{ + id: root; + width: 360 + height: 600 + function newPithySaying(){ + switch (Math.floor(Math.random()*16)){ + case 0: return "Hello World"; + case 1: return "G'day Mate"; + case 2: return "Code Less"; + case 3: return "Create More"; + case 4: return "Deploy Everywhere"; + case 5: return "Qt Meta-object Language"; + case 6: return "Qt Magic Language"; + case 7: return "Fluid UIs"; + case 8: return "Touchable"; + case 9: return "How's it going?"; + case 10: return "Do you like text?"; + case 11: return "Enjoy!"; + case 12: return "ERROR: Out of pith"; + case 13: return "Punctuation Failure"; + case 14: return "I can go faster"; + case 15: return "I can go slower"; + default: return "OMGWTFBBQ"; + } + } + color: "black" + ParticleSystem{ + anchors.fill: parent + id: syssy + Emitter{ + anchors.centerIn: parent + emitRate: 1 + lifeSpan: 4800 + lifeSpanVariation: 1600 + speed: AngledDirection{angleVariation: 360; magnitude: 40; magnitudeVariation: 20} + } + ItemParticle{ + delegate: Text{ + text: root.newPithySaying(); + color: "white" + font.pixelSize: 18 + font.bold: true + } + } + } +} diff --git a/examples/declarative/particles/exampleslauncher.qml b/examples/declarative/particles/exampleslauncher.qml index 354bcdf65a..54f57654c3 100644 --- a/examples/declarative/particles/exampleslauncher.qml +++ b/examples/declarative/particles/exampleslauncher.qml @@ -52,7 +52,7 @@ Rectangle{ id: shell anchors.fill: parent } - VisualDataModel{//TODO: Transitions between modes + VisualDataModel{//TODO: Transitions id: vdm model: [ "../spaceexplorer/spaceexplorer.qml", @@ -60,6 +60,8 @@ Rectangle{ "../asteroid/asteroid.qml", "../asteroid/blackhole.qml", "../custom/blurparticles.qml", + "../custom/shader.qml", + "../custom/delegates.qml", "../modelparticles/bubbles.qml", "../modelparticles/gridsplosion.qml", "../modelparticles/package.qml", @@ -81,6 +83,8 @@ Rectangle{ "../trails/layered.qml", "../trails/shimmer.qml", "../trails/turbulence.qml", + "../trails/combustion.qml", + "../trails/fireworks.qml", "../../../../demos/declarative/samegame/samegame.qml", "../../../../demos/declarative/plasmapatrol/plasmapatrol.qml", "../../../../demos/declarative/flickr/flickr.qml" diff --git a/examples/declarative/particles/launcherContent/icons/combustion.png b/examples/declarative/particles/launcherContent/icons/combustion.png Binary files differnew file mode 100644 index 0000000000..69c6f64e54 --- /dev/null +++ b/examples/declarative/particles/launcherContent/icons/combustion.png diff --git a/examples/declarative/particles/launcherContent/icons/delegates.png b/examples/declarative/particles/launcherContent/icons/delegates.png Binary files differnew file mode 100644 index 0000000000..929414cd0e --- /dev/null +++ b/examples/declarative/particles/launcherContent/icons/delegates.png diff --git a/examples/declarative/particles/launcherContent/icons/fireworks.png b/examples/declarative/particles/launcherContent/icons/fireworks.png Binary files differnew file mode 100644 index 0000000000..ec9f826495 --- /dev/null +++ b/examples/declarative/particles/launcherContent/icons/fireworks.png diff --git a/examples/declarative/particles/launcherContent/icons/shader.png b/examples/declarative/particles/launcherContent/icons/shader.png Binary files differnew file mode 100644 index 0000000000..7c6de498d5 --- /dev/null +++ b/examples/declarative/particles/launcherContent/icons/shader.png diff --git a/examples/declarative/particles/modelparticles/package.qml b/examples/declarative/particles/modelparticles/package.qml index 0aa8903270..d374a93446 100644 --- a/examples/declarative/particles/modelparticles/package.qml +++ b/examples/declarative/particles/modelparticles/package.qml @@ -83,7 +83,7 @@ Rectangle { Emitter{ system: sys width: 100 - x: 50 + x: 250 speed: PointDirection{ y: 40 } lifeSpan: 5000 emitRate: 1.6 diff --git a/examples/declarative/particles/modelparticles/stream.qml b/examples/declarative/particles/modelparticles/stream.qml index 5c7a6f7fc4..15280f7f21 100644 --- a/examples/declarative/particles/modelparticles/stream.qml +++ b/examples/declarative/particles/modelparticles/stream.qml @@ -65,7 +65,6 @@ Item{ ParticleSystem{ id: sys; running: true - overwrite: false startTime: 12000//Doesn't actually work with the loading time though... } Emitter{ diff --git a/examples/declarative/particles/snow/snow.qml b/examples/declarative/particles/snow/snow.qml index ea2de17046..b988c53f4a 100644 --- a/examples/declarative/particles/snow/snow.qml +++ b/examples/declarative/particles/snow/snow.qml @@ -54,6 +54,7 @@ Rectangle{ source: "content/flake-01.png" frames: 51 duration: 40 + durationVariation: 8 } } Wander { diff --git a/examples/declarative/particles/trails/combustion.qml b/examples/declarative/particles/trails/combustion.qml new file mode 100644 index 0000000000..f244300524 --- /dev/null +++ b/examples/declarative/particles/trails/combustion.qml @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** 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 examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtQuick.Particles 2.0 + + +Rectangle { + id: root + width: 360 + height: 600 + color: "black" + + property int score: 0 + Text{ + color: "white" + anchors.right: parent.right + text: score + } + ParticleSystem{ + id: particles + anchors.fill: parent + + + particleStates:[ + Sprite{ + name: "unlit" + duration: 1000 + to: {"lighting":1, "unlit":99} + ImageParticle{ + source: "content/particleA.png" + colorVariation: 0.1 + color: "#2060160f" + } + SpriteGoal{ + collisionParticles: ["lit"] + goalState: "lighting" + jump: true + systemStates: true + } + }, + Sprite{ + name: "lighting" + duration: 100 + to: {"lit":1} + }, + Sprite{ + name: "lit" + duration: 10000 + onEntered: score++; + FollowEmitter{ + id: fireballFlame + particle: "flame" + + emitRatePerParticle: 48 + lifeSpan: 200 + emitWidth: 8 + emitHeight: 8 + + size: 24 + sizeVariation: 8 + endSize: 4 + } + + FollowEmitter{ + id: fireballSmoke + particle: "smoke" + + emitRatePerParticle: 120 + lifeSpan: 2000 + emitWidth: 16 + emitHeight: 16 + + speed: PointDirection{yVariation: 16; xVariation: 16} + acceleration: PointDirection{y: -16} + + size: 24 + sizeVariation: 8 + endSize: 8 + } + } + ] + + ImageParticle{ + id: smoke + anchors.fill: parent + particles: ["smoke"] + source: "content/particle.png" + colorVariation: 0 + color: "#00111111" + } + ImageParticle{ + id: pilot + anchors.fill: parent + particles: ["pilot"] + source: "content/particle.png" + redVariation: 0.01 + blueVariation: 0.4 + color: "#0010004f" + } + ImageParticle{ + id: flame + anchors.fill: parent + particles: ["flame", "lit", "lighting"] + source: "content/particleA.png" + colorVariation: 0.1 + color: "#00ff400f" + } + + Emitter{ + height: parent.height/2 + emitRate: 4 + lifeSpan: 4000//TODO: Infinite & kill zone + size: 24 + sizeVariation: 4 + speed: PointDirection{x:120; xVariation: 80; yVariation: 50} + acceleration: PointDirection{y:120} + particle: "unlit" + } + + Emitter{ + id: flamer + x: 100 + y: 300 + particle: "pilot" + emitRate: 80 + lifeSpan: 600 + size: 24 + sizeVariation: 2 + endSize: 0 + speed: PointDirection{ y:-100; yVariation: 4; xVariation: 4 } + SpriteGoal{ + particles: ["unlit"] + goalState: "lit" + jump: true + systemStates: true + system: particles + x: -15 + y: -55 + height: 75 + width: 30 + shape: MaskShape{source: "content/matchmask.png"} + } + } + //Click to enflame + SpriteGoal{//TODO: Aux emiiters in the state definition (which allows the occasional ball to spontaneously combust) + particles: ["unlit"] + goalState: "lighting" + jump: true + systemStates: true + active: ma.pressed + width: 18 + height: 18 + x: ma.mouseX - width/2 + y: ma.mouseY - height/2 + } + MouseArea{ + id: ma + anchors.fill: parent + } + } +} diff --git a/examples/declarative/particles/trails/content/matchmask.png b/examples/declarative/particles/trails/content/matchmask.png Binary files differnew file mode 100644 index 0000000000..e575875c55 --- /dev/null +++ b/examples/declarative/particles/trails/content/matchmask.png diff --git a/examples/declarative/particles/trails/dynamicemitters.qml b/examples/declarative/particles/trails/dynamicemitters.qml index f338c204db..dbf3f8fa82 100644 --- a/examples/declarative/particles/trails/dynamicemitters.qml +++ b/examples/declarative/particles/trails/dynamicemitters.qml @@ -114,6 +114,7 @@ Rectangle{ obj.targetX = Math.random() * 240 - 120 + obj.x obj.targetY = Math.random() * 240 - 120 + obj.y obj.life = Math.round(Math.random() * 2400) + 200 + obj.emitRate = Math.round(Math.random() * 32) + 32 obj.go(); } } diff --git a/examples/declarative/particles/custom/fireworks.qml b/examples/declarative/particles/trails/fireworks.qml index b73a5e234f..59627f8dce 100644 --- a/examples/declarative/particles/custom/fireworks.qml +++ b/examples/declarative/particles/trails/fireworks.qml @@ -45,58 +45,64 @@ Rectangle{ width: 360 height: 600 color: "black" - Component{ - id: firework - Item{ - id: container - width: 48 - height: 48 - Image{ - width: 48 - height: 48 - id: img - source: "content/particle.png" - } - Timer{ - interval: 1000 + 4000*Math.random() - running: true - repeat: false - onTriggered: { - img.visible = false; - emitter.burst(100); - } - } - Emitter{ - anchors.centerIn: parent - id: emitter - system: syssy - particle: "works" - emitting: false - emitRate: 100 - lifeSpan: 1000 - //speed: AngledDirection{angle: 270; angleVariation:60; magnitudeVariation: 60; magnitude: 20} - speed: PointDirection{y:-60; yVariation: 80; xVariation: 80} - acceleration: PointDirection{y:100; yVariation: 20} - } - } - } ParticleSystem{ anchors.fill: parent id: syssy + particleStates:[ + Sprite{ + name: "fire" + duration: 2000 + to: {"splode":1} + }, + Sprite{ + name: "splode" + duration: 400 + to: {"dead":1} + FollowEmitter{ + particle: "works" + emitRatePerParticle: 100 + lifeSpan: 1000 + emitCap: 1200 + size: 8 + speed: AngledDirection{angle: 270; angleVariation: 45; magnitude: 20; magnitudeVariation: 20;} + acceleration: PointDirection{y:100; yVariation: 20} + } + }, + Sprite{ + name: "dead" + duration: 1000 + Affector{ + onceOff: true + signal: true + onAffected: worksEmitter.burst(400,x,y) + } + } + ] Emitter{ particle: "fire" width: parent.width y: parent.height emitRate: 2 lifeSpan: 6000 - speed: PointDirection{y:-100} + speed: PointDirection{y:-100; yVariation: 40} + size: 32 } - ItemParticle{ - particles: ["fire"] - delegate: firework + Emitter{ + id: worksEmitter + particle: "works" + emitting: false + emitRate: 100 + lifeSpan: 1600 + emitCap: 6400 + size: 8 + speed: CumulativeDirection{ + PointDirection{y:-100} + AngledDirection{angleVariation: 360; magnitudeVariation: 80;} + } + acceleration: PointDirection{y:100; yVariation: 20} } ImageParticle{ - particles: ["works"] + particles: ["works", "fire", "splode"] source: "content/particle.png" } } diff --git a/src/declarative/items/qsgsprite.cpp b/src/declarative/items/qsgsprite.cpp index 694976a540..794ae519a3 100644 --- a/src/declarative/items/qsgsprite.cpp +++ b/src/declarative/items/qsgsprite.cpp @@ -40,6 +40,9 @@ ****************************************************************************/ #include "qsgsprite_p.h" +//TODO: Split out particle system dependency +#include "qsgparticlesystem_p.h" +#include <QDebug> QT_BEGIN_NAMESPACE @@ -54,4 +57,17 @@ QSGSprite::QSGSprite(QObject *parent) : { } +void redirectError(QDeclarativeListProperty<QObject> *prop, QObject *value) +{ + qWarning() << "Could not add " << value << " to state" << prop->object << "as it is not associated with a particle system."; +} + +QDeclarativeListProperty<QObject> QSGSprite::particleChildren(){ + QSGParticleSystem* system = qobject_cast<QSGParticleSystem*>(parent()); + if (system) + return QDeclarativeListProperty<QObject>(this, 0, &QSGParticleSystem::stateRedirect); + else + return QDeclarativeListProperty<QObject>(this, 0, &redirectError); +} + QT_END_NAMESPACE diff --git a/src/declarative/items/qsgsprite_p.h b/src/declarative/items/qsgsprite_p.h index 652a4cd482..c20599853f 100644 --- a/src/declarative/items/qsgsprite_p.h +++ b/src/declarative/items/qsgsprite_p.h @@ -45,6 +45,7 @@ #include <QObject> #include <QUrl> #include <QVariantMap> +#include <QDeclarativeListProperty> QT_BEGIN_HEADER @@ -64,13 +65,17 @@ class QSGSprite : public QObject Q_PROPERTY(int frameHeight READ frameHeight WRITE setFrameHeight NOTIFY frameHeightChanged) Q_PROPERTY(int frameWidth READ frameWidth WRITE setFrameWidth NOTIFY frameWidthChanged) Q_PROPERTY(int duration READ duration WRITE setDuration NOTIFY durationChanged) - Q_PROPERTY(int durationVariance READ durationVariance WRITE setDurationVariance NOTIFY durationVarianceChanged) + Q_PROPERTY(int durationVariation READ durationVariance WRITE setDurationVariance NOTIFY durationVarianceChanged) Q_PROPERTY(qreal speedModifiesDuration READ speedModifer WRITE setSpeedModifier NOTIFY speedModifierChanged) Q_PROPERTY(QVariantMap to READ to WRITE setTo NOTIFY toChanged) + Q_PROPERTY(QDeclarativeListProperty<QObject> particleChildren READ particleChildren DESIGNABLE false)//### Hidden property for in-state system definitions - ought not to be used in actual "Sprite" states + Q_CLASSINFO("DefaultProperty", "particleChildren") public: explicit QSGSprite(QObject *parent = 0); + QDeclarativeListProperty<QObject> particleChildren(); + QUrl source() const { return m_source; @@ -136,6 +141,8 @@ signals: void durationVarianceChanged(int arg); + void entered();//### Just playing around - don't expect full state API + public slots: void setSource(QUrl arg) @@ -224,6 +231,7 @@ private: QVariantMap m_to; qreal m_speedModifier; int m_durationVariance; + }; QT_END_NAMESPACE diff --git a/src/declarative/items/qsgspriteengine.cpp b/src/declarative/items/qsgspriteengine.cpp index 27de0d94f6..94753d09bc 100644 --- a/src/declarative/items/qsgspriteengine.cpp +++ b/src/declarative/items/qsgspriteengine.cpp @@ -48,6 +48,11 @@ QT_BEGIN_NAMESPACE +/* TODO: Split out image logic from stochastic state logic + Also make sharable + Also solve the state data initialization/transfer issue so as to not need to make friends +*/ + QSGSpriteEngine::QSGSpriteEngine(QObject *parent) : QObject(parent), m_timeOffset(0) { @@ -82,7 +87,7 @@ TODO: All these calculations should be pre-calculated and cached during initiali int QSGSpriteEngine::spriteState(int sprite) { int state = m_sprites[sprite]; - if(!m_states[state]->m_generatedCount) + if (!m_states[state]->m_generatedCount) return state; int rowDuration = m_states[state]->duration() * m_states[state]->m_framesPerRow; int extra = (m_timeOffset - m_startTimes[sprite])/rowDuration; @@ -92,7 +97,7 @@ int QSGSpriteEngine::spriteState(int sprite) int QSGSpriteEngine::spriteStart(int sprite) { int state = m_sprites[sprite]; - if(!m_states[state]->m_generatedCount) + if (!m_states[state]->m_generatedCount) return m_startTimes[sprite]; int rowDuration = m_states[state]->duration() * m_states[state]->m_framesPerRow; int extra = (m_timeOffset - m_startTimes[sprite])/rowDuration; @@ -102,11 +107,11 @@ int QSGSpriteEngine::spriteStart(int sprite) int QSGSpriteEngine::spriteFrames(int sprite) { int state = m_sprites[sprite]; - if(!m_states[state]->m_generatedCount) + if (!m_states[state]->m_generatedCount) return m_states[state]->frames(); int rowDuration = m_states[state]->duration() * m_states[state]->m_framesPerRow; int extra = (m_timeOffset - m_startTimes[sprite])/rowDuration; - if(extra == m_states[state]->m_generatedCount - 1)//last state + if (extra == m_states[state]->m_generatedCount - 1)//last state return m_states[state]->frames() % m_states[state]->m_framesPerRow; else return m_states[state]->m_framesPerRow; @@ -115,11 +120,11 @@ int QSGSpriteEngine::spriteFrames(int sprite) int QSGSpriteEngine::spriteDuration(int sprite) { int state = m_sprites[sprite]; - if(!m_states[state]->m_generatedCount) + if (!m_states[state]->m_generatedCount) return m_states[state]->duration(); int rowDuration = m_states[state]->duration() * m_states[state]->m_framesPerRow; int extra = (m_timeOffset - m_startTimes[sprite])/rowDuration; - if(extra == m_states[state]->m_generatedCount - 1)//last state + if (extra == m_states[state]->m_generatedCount - 1)//last state return (m_states[state]->duration() * m_states[state]->frames()) % rowDuration; else return rowDuration; @@ -132,18 +137,20 @@ int QSGSpriteEngine::spriteCount()//TODO: Actually image state count, need to re void QSGSpriteEngine::setGoal(int state, int sprite, bool jump) { - if(sprite >= m_sprites.count() || state >= m_states.count()) + if (sprite >= m_sprites.count() || state >= m_states.count()) return; - if(!jump){ + if (!jump){ m_goals[sprite] = state; return; } - if(m_sprites[sprite] == state) + if (m_sprites[sprite] == state) return;//Already there m_sprites[sprite] = state; m_goals[sprite] = -1; restartSprite(sprite); + emit stateChanged(sprite); + emit m_states[state]->entered(); return; } @@ -157,8 +164,8 @@ QImage QSGSpriteEngine::assembledImage() int maxSize; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize); - foreach(QSGSprite* state, m_states){ - if(state->frames() > m_maxFrames) + foreach (QSGSprite* state, m_states){ + if (state->frames() > m_maxFrames) m_maxFrames = state->frames(); QImage img(state->source().toLocalFile()); @@ -169,10 +176,10 @@ QImage QSGSpriteEngine::assembledImage() //Check that the frame sizes are the same within one engine int imgWidth = state->frameWidth(); - if(!imgWidth) + if (!imgWidth) imgWidth = img.width() / state->frames(); - if(frameWidth){ - if(imgWidth != frameWidth){ + if (frameWidth){ + if (imgWidth != frameWidth){ qWarning() << "SpriteEngine: Irregular frame width..." << state->source().toLocalFile(); return QImage(); } @@ -181,10 +188,10 @@ QImage QSGSpriteEngine::assembledImage() } int imgHeight = state->frameHeight(); - if(!imgHeight) + if (!imgHeight) imgHeight = img.height(); - if(frameHeight){ - if(imgHeight!=frameHeight){ + if (frameHeight){ + if (imgHeight!=frameHeight){ qWarning() << "SpriteEngine: Irregular frame height..." << state->source().toLocalFile(); return QImage(); } @@ -192,12 +199,12 @@ QImage QSGSpriteEngine::assembledImage() frameHeight = imgHeight; } - if(state->frames() * frameWidth > maxSize){ + if (state->frames() * frameWidth > maxSize){ struct helper{ static int divRoundUp(int a, int b){return (a+b-1)/b;} }; int rowsNeeded = helper::divRoundUp(state->frames(), helper::divRoundUp(maxSize, frameWidth)); - if(rowsNeeded * frameHeight > maxSize){ + if (rowsNeeded * frameHeight > maxSize){ qWarning() << "SpriteEngine: Animation too large to fit in one texture..." << state->source().toLocalFile(); qWarning() << "SpriteEngine: Your texture max size today is " << maxSize; } @@ -209,15 +216,15 @@ QImage QSGSpriteEngine::assembledImage() } //maxFrames is max number in a line of the texture - if(m_maxFrames * frameWidth > maxSize) + if (m_maxFrames * frameWidth > maxSize) m_maxFrames = maxSize/frameWidth; QImage image(frameWidth * m_maxFrames, frameHeight * m_imageStateCount, QImage::Format_ARGB32); image.fill(0); QPainter p(&image); int y = 0; - foreach(QSGSprite* state, m_states){ + foreach (QSGSprite* state, m_states){ QImage img(state->source().toLocalFile()); - if(img.height() == frameHeight && img.width() < maxSize){//Simple case + if (img.height() == frameHeight && img.width() < maxSize){//Simple case p.drawImage(0,y,img); y += frameHeight; }else{ @@ -226,8 +233,8 @@ QImage QSGSpriteEngine::assembledImage() int curX = 0; int curY = 0; int framesLeft = state->frames(); - while(framesLeft > 0){ - if(image.width() - x + curX <= img.width()){//finish a row in image (dest) + while (framesLeft > 0){ + if (image.width() - x + curX <= img.width()){//finish a row in image (dest) int copied = image.width() - x; Q_ASSERT(!(copied % frameWidth));//XXX: Just checking framesLeft -= copied/frameWidth; @@ -235,7 +242,7 @@ QImage QSGSpriteEngine::assembledImage() y += frameHeight; curX += copied; x = 0; - if(curX == img.width()){ + if (curX == img.width()){ curX = 0; curY += frameHeight; } @@ -249,12 +256,12 @@ QImage QSGSpriteEngine::assembledImage() curX = 0; } } - if(x) + if (x) y += frameHeight; } } - if(image.height() > maxSize){ + if (image.height() > maxSize){ qWarning() << "SpriteEngine: Too many animations to fit in one texture..."; qWarning() << "SpriteEngine: Your texture max size today is " << maxSize; return QImage(); @@ -269,53 +276,63 @@ void QSGSpriteEngine::setCount(int c) m_startTimes.resize(c); } -void QSGSpriteEngine::startSprite(int index) +void QSGSpriteEngine::startSprite(int index, int state) { - if(index >= m_sprites.count()) + if (index >= m_sprites.count()) return; - m_sprites[index] = 0; + m_sprites[index] = state; m_goals[index] = -1; restartSprite(index); } +void QSGSpriteEngine::stopSprite(int index) +{ + if (index >= m_sprites.count()) + return; + //Will never change until start is called again with a new state - this is not a 'pause' + for (int i=0; i<m_stateUpdates.count(); i++) + m_stateUpdates[i].second.removeAll(index); +} + void QSGSpriteEngine::restartSprite(int index) { m_startTimes[index] = m_timeOffset + m_advanceTime.elapsed(); int time = m_states[m_sprites[index]]->duration() * m_states[m_sprites[index]]->frames() + m_startTimes[index]; - for(int i=0; i<m_stateUpdates.count(); i++) + for (int i=0; i<m_stateUpdates.count(); i++) m_stateUpdates[i].second.removeAll(index); addToUpdateList(time, index); } -uint QSGSpriteEngine::updateSprites(uint time) +uint QSGSpriteEngine::updateSprites(uint time)//### would returning a list of changed idxs be faster than signals? { //Sprite State Update; - while(!m_stateUpdates.isEmpty() && time >= m_stateUpdates.first().first){ - foreach(int idx, m_stateUpdates.first().second){ - if(idx >= m_sprites.count()) + QSet<int> changedIndexes; + while (!m_stateUpdates.isEmpty() && time >= m_stateUpdates.first().first){ + foreach (int idx, m_stateUpdates.first().second){ + if (idx >= m_sprites.count()) continue;//TODO: Proper fix(because this does happen and I'm just ignoring it) int stateIdx = m_sprites[idx]; int nextIdx = -1; int goalPath = goalSeek(stateIdx, idx); - if(goalPath == -1){//Random + if (goalPath == -1){//Random qreal r =(qreal) qrand() / (qreal) RAND_MAX; qreal total = 0.0; - for(QVariantMap::const_iterator iter=m_states[stateIdx]->m_to.constBegin(); + for (QVariantMap::const_iterator iter=m_states[stateIdx]->m_to.constBegin(); iter!=m_states[stateIdx]->m_to.constEnd(); iter++) total += (*iter).toReal(); r*=total; - for(QVariantMap::const_iterator iter= m_states[stateIdx]->m_to.constBegin(); + for (QVariantMap::const_iterator iter= m_states[stateIdx]->m_to.constBegin(); iter!=m_states[stateIdx]->m_to.constEnd(); iter++){ - if(r < (*iter).toReal()){ + if (r < (*iter).toReal()){ bool superBreak = false; - for(int i=0; i<m_states.count(); i++){ - if(m_states[i]->name() == iter.key()){ + for (int i=0; i<m_states.count(); i++){ + if (m_states[i]->name() == iter.key()){ nextIdx = i; superBreak = true; break; } } - if(superBreak) + if (superBreak) break; } r -= (*iter).toReal(); @@ -323,12 +340,15 @@ uint QSGSpriteEngine::updateSprites(uint time) }else{//Random out of shortest paths to goal nextIdx = goalPath; } - if(nextIdx == -1)//No to states means stay here + if (nextIdx == -1)//No to states means stay here nextIdx = stateIdx; m_sprites[idx] = nextIdx; m_startTimes[idx] = time; - //TODO: emit something? Remember to emit this when a psuedostate changes too + if (nextIdx != stateIdx){ + changedIndexes << idx; + emit m_states[nextIdx]->entered(); + } addToUpdateList((m_states[nextIdx]->duration() * m_states[nextIdx]->frames()) + time, idx); } m_stateUpdates.pop_front(); @@ -336,7 +356,11 @@ uint QSGSpriteEngine::updateSprites(uint time) m_timeOffset = time; m_advanceTime.start(); - if(m_stateUpdates.isEmpty()) + //TODO: emit this when a psuedostate changes too + foreach (int idx, changedIndexes){//Batched so that update list doesn't change midway + emit stateChanged(idx); + } + if (m_stateUpdates.isEmpty()) return -1; return m_stateUpdates.first().first; } @@ -344,68 +368,68 @@ uint QSGSpriteEngine::updateSprites(uint time) int QSGSpriteEngine::goalSeek(int curIdx, int spriteIdx, int dist) { QString goalName; - if(m_goals[spriteIdx] != -1) + if (m_goals[spriteIdx] != -1) goalName = m_states[m_goals[spriteIdx]]->name(); else goalName = m_globalGoal; - if(goalName.isEmpty()) + if (goalName.isEmpty()) return -1; //TODO: caching instead of excessively redoing iterative deepening (which was chosen arbitarily anyways) // Paraphrased - implement in an *efficient* manner - for(int i=0; i<m_states.count(); i++) - if(m_states[curIdx]->name() == goalName) + for (int i=0; i<m_states.count(); i++) + if (m_states[curIdx]->name() == goalName) return curIdx; - if(dist < 0) + if (dist < 0) dist = m_states.count(); QSGSprite* curState = m_states[curIdx]; - for(QVariantMap::const_iterator iter = curState->m_to.constBegin(); + for (QVariantMap::const_iterator iter = curState->m_to.constBegin(); iter!=curState->m_to.constEnd(); iter++){ - if(iter.key() == goalName) - for(int i=0; i<m_states.count(); i++) - if(m_states[i]->name() == goalName) + if (iter.key() == goalName) + for (int i=0; i<m_states.count(); i++) + if (m_states[i]->name() == goalName) return i; } QSet<int> options; - for(int i=1; i<dist; i++){ - for(QVariantMap::const_iterator iter = curState->m_to.constBegin(); + for (int i=1; i<dist; i++){ + for (QVariantMap::const_iterator iter = curState->m_to.constBegin(); iter!=curState->m_to.constEnd(); iter++){ int option = -1; - for(int j=0; j<m_states.count(); j++)//One place that could be a lot more efficient... - if(m_states[j]->name() == iter.key()) - if(goalSeek(j, spriteIdx, i) != -1) + for (int j=0; j<m_states.count(); j++)//One place that could be a lot more efficient... + if (m_states[j]->name() == iter.key()) + if (goalSeek(j, spriteIdx, i) != -1) option = j; - if(option != -1) + if (option != -1) options << option; } - if(!options.isEmpty()){ - if(options.count()==1) + if (!options.isEmpty()){ + if (options.count()==1) return *(options.begin()); int option = -1; qreal r =(qreal) qrand() / (qreal) RAND_MAX; qreal total; - for(QSet<int>::const_iterator iter=options.constBegin(); + for (QSet<int>::const_iterator iter=options.constBegin(); iter!=options.constEnd(); iter++) total += curState->m_to.value(m_states[(*iter)]->name()).toReal(); r *= total; - for(QVariantMap::const_iterator iter = curState->m_to.constBegin(); + for (QVariantMap::const_iterator iter = curState->m_to.constBegin(); iter!=curState->m_to.constEnd(); iter++){ bool superContinue = true; - for(int j=0; j<m_states.count(); j++) - if(m_states[j]->name() == iter.key()) - if(options.contains(j)) + for (int j=0; j<m_states.count(); j++) + if (m_states[j]->name() == iter.key()) + if (options.contains(j)) superContinue = false; - if(superContinue) + if (superContinue) continue; - if(r < (*iter).toReal()){ + if (r < (*iter).toReal()){ bool superBreak = false; - for(int j=0; j<m_states.count(); j++){ - if(m_states[j]->name() == iter.key()){ + for (int j=0; j<m_states.count(); j++){ + if (m_states[j]->name() == iter.key()){ option = j; superBreak = true; break; } } - if(superBreak) + if (superBreak) break; } r-=(*iter).toReal(); @@ -418,11 +442,11 @@ int QSGSpriteEngine::goalSeek(int curIdx, int spriteIdx, int dist) void QSGSpriteEngine::addToUpdateList(uint t, int idx) { - for(int i=0; i<m_stateUpdates.count(); i++){ - if(m_stateUpdates[i].first==t){ + for (int i=0; i<m_stateUpdates.count(); i++){ + if (m_stateUpdates[i].first==t){ m_stateUpdates[i].second << idx; return; - }else if(m_stateUpdates[i].first > t){ + }else if (m_stateUpdates[i].first > t){ QList<int> tmpList; tmpList << idx; m_stateUpdates.insert(i, qMakePair(t, tmpList)); diff --git a/src/declarative/items/qsgspriteengine_p.h b/src/declarative/items/qsgspriteengine_p.h index 8ab6e3a30a..33d6e82bd4 100644 --- a/src/declarative/items/qsgspriteengine_p.h +++ b/src/declarative/items/qsgspriteengine_p.h @@ -92,7 +92,8 @@ public: void setGoal(int state, int sprite=0, bool jump=false); QImage assembledImage(); - void startSprite(int index=0); + void startSprite(int index=0, int state=0); + void stopSprite(int index=0); private://Nothing outside should use this? friend class QSGSpriteGoalAffector;//XXX: Fix interface @@ -102,6 +103,7 @@ private://Nothing outside should use this? signals: void globalGoalChanged(QString arg); + void stateChanged(int idx); public slots: void setGlobalGoal(QString arg) @@ -115,6 +117,7 @@ public slots: uint updateSprites(uint time); private: + friend class QSGParticleSystem; void restartSprite(int sprite); void addToUpdateList(uint t, int idx); int goalSeek(int curState, int spriteIdx, int dist=-1); @@ -122,7 +125,7 @@ private: QVector<int> m_sprites;//int is the index in m_states of the current state QVector<int> m_goals; QVector<int> m_startTimes; - QList<QPair<uint, QList<int> > > m_stateUpdates;//### This could be done faster + QList<QPair<uint, QList<int> > > m_stateUpdates;//### This could be done faster - priority queue? QTime m_advanceTime; uint m_timeOffset; diff --git a/src/declarative/particles/particles.pri b/src/declarative/particles/particles.pri index 04200a380e..bdd42aea70 100644 --- a/src/declarative/particles/particles.pri +++ b/src/declarative/particles/particles.pri @@ -26,7 +26,9 @@ HEADERS += \ $$PWD/qsgstochasticdirection_p.h \ $$PWD/qsgtargeteddirection_p.h \ $$PWD/qsgturbulence_p.h \ - $$PWD/qsgwander_p.h + $$PWD/qsgwander_p.h \ + $$PWD/qsgtargetaffector_p.h \ + $$PWD/qsgcumulativedirection_p.h SOURCES += \ $$PWD/qsgangleddirection.cpp \ @@ -54,7 +56,12 @@ SOURCES += \ $$PWD/qsgstochasticdirection.cpp \ $$PWD/qsgtargeteddirection.cpp \ $$PWD/qsgturbulence.cpp \ - $$PWD/qsgwander.cpp + $$PWD/qsgwander.cpp \ + $$PWD/qsgtargetaffector.cpp \ + $$PWD/qsgcumulativedirection.cpp RESOURCES += \ $$PWD/particles.qrc + + + diff --git a/src/declarative/particles/qsgcumulativedirection.cpp b/src/declarative/particles/qsgcumulativedirection.cpp new file mode 100644 index 0000000000..c6834aa8b1 --- /dev/null +++ b/src/declarative/particles/qsgcumulativedirection.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** 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$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgcumulativedirection_p.h" +QT_BEGIN_NAMESPACE + +QSGCumulativeDirection::QSGCumulativeDirection(QObject *parent):QSGStochasticDirection(parent) +{ +} + +QDeclarativeListProperty<QSGStochasticDirection> QSGCumulativeDirection::directions() +{ + return QDeclarativeListProperty<QSGStochasticDirection>(this, m_directions);//TODO: Proper list property +} + +const QPointF &QSGCumulativeDirection::sample(const QPointF &from) +{ + QPointF ret; + foreach (QSGStochasticDirection* dir, m_directions) + ret += dir->sample(from); + return ret; +} + +QT_END_NAMESPACE diff --git a/src/declarative/particles/qsgcumulativedirection_p.h b/src/declarative/particles/qsgcumulativedirection_p.h new file mode 100644 index 0000000000..aad003a7ce --- /dev/null +++ b/src/declarative/particles/qsgcumulativedirection_p.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** 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$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGCUMULATIVEDIRECTION_P_H +#define QSGCUMULATIVEDIRECTION_P_H +#include "qsgstochasticdirection_p.h" +#include <QDeclarativeListProperty> +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGCumulativeDirection : public QSGStochasticDirection +{ + Q_OBJECT + Q_PROPERTY(QDeclarativeListProperty<QSGStochasticDirection> directions READ directions) + Q_CLASSINFO("DefaultProperty", "directions") +public: + explicit QSGCumulativeDirection(QObject *parent = 0); + QDeclarativeListProperty<QSGStochasticDirection> directions(); + const QPointF &sample(const QPointF &from); +private: + QList<QSGStochasticDirection*> m_directions; +}; +#endif // QSGCUMULATIVEDIRECTION_P_H diff --git a/src/declarative/particles/qsgcustomparticle.cpp b/src/declarative/particles/qsgcustomparticle.cpp index 46f160ddf4..ba6bf2951c 100644 --- a/src/declarative/particles/qsgcustomparticle.cpp +++ b/src/declarative/particles/qsgcustomparticle.cpp @@ -123,6 +123,7 @@ QSGCustomParticle::QSGCustomParticle(QSGItem* parent) : QSGParticlePainter(parent) , m_pleaseReset(true) , m_dirtyData(true) + , m_rootNode(0) { setFlag(QSGItem::ItemHasContents); } @@ -194,6 +195,7 @@ void QSGCustomParticle::reset() QSGParticlePainter::reset(); m_pleaseReset = true; + update(); } @@ -334,7 +336,7 @@ void QSGCustomParticle::lookThroughShaderCode(const QByteArray &code) QByteArray name = re.cap(3).toLatin1(); // variable name if (decl == "attribute") { - if(!m_source.attributeNames.contains(name))//TODO: Can they add custom attributes? + if (!m_source.attributeNames.contains(name))//TODO: Can they add custom attributes? qWarning() << "Custom Particle: Unknown attribute " << name; } else { Q_ASSERT(decl == "uniform");//TODO: Shouldn't assert @@ -361,90 +363,53 @@ void QSGCustomParticle::lookThroughShaderCode(const QByteArray &code) QSGNode *QSGCustomParticle::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) { - if(m_pleaseReset){ - if(m_node) - delete m_node; + if (m_pleaseReset){ + //delete m_material;//Shader effect item doesn't regen material? - m_node = 0; + delete m_rootNode;//Automatically deletes children + m_rootNode = 0; + m_nodes.clear(); m_pleaseReset = false; m_dirtyData = false; } - if(m_system && m_system->isRunning()) + if (m_system && m_system->isRunning()) prepareNextFrame(); - if (m_node){ - if(oldNode) - Q_ASSERT(oldNode == m_node); + if (m_rootNode){ update(); - m_node->markDirty(QSGNode::DirtyMaterial); //done in buildData? + //### Should I be using dirty geometry too/instead? + foreach (QSGShaderEffectNode* node, m_nodes) + node->markDirty(QSGNode::DirtyMaterial); //done in buildData? } - return m_node; + return m_rootNode; } void QSGCustomParticle::prepareNextFrame(){ - if(!m_node) - m_node = buildCustomNode(); - if(!m_node) + if (!m_rootNode) + m_rootNode = buildCustomNodes(); + if (!m_rootNode) return; m_lastTime = m_system->systemSync(this) / 1000.; - if(m_dirtyData || true)//Currently this is how we update timestamp... potentially over expensive. + if (m_dirtyData || true)//Currently this is how we update timestamp... potentially over expensive. buildData(); } -QSGShaderEffectNode* QSGCustomParticle::buildCustomNode() +QSGShaderEffectNode* QSGCustomParticle::buildCustomNodes() { if (m_count * 4 > 0xffff) { printf("CustomParticle: Too many particles... \n");//####Why is this here? return 0; } - if(m_count <= 0) { + if (m_count <= 0) { printf("CustomParticle: Too few particles... \n"); return 0; } - //Create Particle Geometry - int vCount = m_count * 4; - int iCount = m_count * 6; - QSGGeometry *g = new QSGGeometry(PlainParticle_AttributeSet, vCount, iCount); - g->setDrawingMode(GL_TRIANGLES); - PlainVertex *vertices = (PlainVertex *) g->vertexData(); - for (int p=0; p<m_count; ++p) { - reload(p); - vertices[0].tx = 0; - vertices[0].ty = 0; - - vertices[1].tx = 1; - vertices[1].ty = 0; - - vertices[2].tx = 0; - vertices[2].ty = 1; - - vertices[3].tx = 1; - vertices[3].ty = 1; - vertices += 4; - } - quint16 *indices = g->indexDataAsUShort(); - for (int i=0; i<m_count; ++i) { - int o = i * 4; - indices[0] = o; - indices[1] = o + 1; - indices[2] = o + 2; - indices[3] = o + 1; - indices[4] = o + 3; - indices[5] = o + 2; - indices += 6; - } - updateProperties(); - QSGShaderEffectNode* node = new QSGShaderEffectNode(); - - - node->setGeometry(g); - node->setMaterial(&m_material); QSGShaderEffectProgram s = m_source; if (s.fragmentCode.isEmpty()) @@ -452,66 +417,120 @@ QSGShaderEffectNode* QSGCustomParticle::buildCustomNode() if (s.vertexCode.isEmpty()) s.vertexCode = qt_particles_default_vertex_code; m_material.setProgramSource(s); - return node; + foreach (const QString &str, m_particles){ + int gIdx = m_system->m_groupIds[str]; + int count = m_system->m_groupData[gIdx]->size(); + //Create Particle Geometry + int vCount = count * 4; + int iCount = count * 6; + QSGGeometry *g = new QSGGeometry(PlainParticle_AttributeSet, vCount, iCount); + g->setDrawingMode(GL_TRIANGLES); + PlainVertex *vertices = (PlainVertex *) g->vertexData(); + for (int p=0; p < count; ++p) { + commit(gIdx, p); + vertices[0].tx = 0; + vertices[0].ty = 0; + + vertices[1].tx = 1; + vertices[1].ty = 0; + + vertices[2].tx = 0; + vertices[2].ty = 1; + + vertices[3].tx = 1; + vertices[3].ty = 1; + vertices += 4; + } + quint16 *indices = g->indexDataAsUShort(); + for (int i=0; i < count; ++i) { + int o = i * 4; + indices[0] = o; + indices[1] = o + 1; + indices[2] = o + 2; + indices[3] = o + 1; + indices[4] = o + 3; + indices[5] = o + 2; + indices += 6; + } + + QSGShaderEffectNode* node = new QSGShaderEffectNode(); + + node->setGeometry(g); + node->setMaterial(&m_material); + node->markDirty(QSGNode::DirtyMaterial); + + m_nodes.insert(gIdx, node); + } + foreach (QSGShaderEffectNode* node, m_nodes){ + if (node == *(m_nodes.begin())) + continue; + (*(m_nodes.begin()))->appendChildNode(node); + } + + return *(m_nodes.begin()); } static const QByteArray timestampName("timestamp"); void QSGCustomParticle::buildData() { - if(!m_node)//Operates on m_node + if (!m_rootNode) return; QVector<QPair<QByteArray, QVariant> > values; QVector<QPair<QByteArray, QPointer<QSGItem> > > textures; const QVector<QPair<QByteArray, QPointer<QSGItem> > > &oldTextures = m_material.textureProviders(); - - for (QSet<QByteArray>::const_iterator it = m_source.uniformNames.begin(); - it != m_source.uniformNames.end(); ++it) { - values.append(qMakePair(*it, property(*it))); - } for (int i = 0; i < oldTextures.size(); ++i) { QSGTextureProvider *oldSource = QSGTextureProvider::from(oldTextures.at(i).second); if (oldSource && oldSource->textureChangedSignal()) - disconnect(oldTextures.at(i).second, oldSource->textureChangedSignal(), m_node, SLOT(markDirtyTexture())); + foreach (QSGShaderEffectNode* node, m_nodes) + disconnect(oldTextures.at(i).second, oldSource->textureChangedSignal(), node, SLOT(markDirtyTexture())); } for (int i = 0; i < m_sources.size(); ++i) { const SourceData &source = m_sources.at(i); textures.append(qMakePair(source.name, source.item)); QSGTextureProvider *t = QSGTextureProvider::from(source.item); if (t && t->textureChangedSignal()) - connect(source.item, t->textureChangedSignal(), m_node, SLOT(markDirtyTexture()), Qt::DirectConnection); + foreach (QSGShaderEffectNode* node, m_nodes) + connect(source.item, t->textureChangedSignal(), node, SLOT(markDirtyTexture()), Qt::DirectConnection); + } + for (QSet<QByteArray>::const_iterator it = m_source.uniformNames.begin(); + it != m_source.uniformNames.end(); ++it) { + values.append(qMakePair(*it, property(*it))); } values.append(qMakePair(timestampName, QVariant(m_lastTime))); m_material.setUniforms(values); m_material.setTextureProviders(textures); - m_node->markDirty(QSGNode::DirtyMaterial); m_dirtyData = false; + foreach (QSGShaderEffectNode* node, m_nodes) + node->markDirty(QSGNode::DirtyMaterial); } -void QSGCustomParticle::initialize(int idx) +void QSGCustomParticle::initialize(int gIdx, int pIdx) { - m_data[idx]->r = rand()/(qreal)RAND_MAX; + QSGParticleData* datum = m_system->m_groupData[gIdx]->data[pIdx]; + datum->r = rand()/(qreal)RAND_MAX; } -void QSGCustomParticle::reload(int idx) +void QSGCustomParticle::commit(int gIdx, int pIdx) { - if (m_node == 0) + if (m_nodes[gIdx] == 0) return; - PlainVertices *particles = (PlainVertices *) m_node->geometry()->vertexData(); - PlainVertex *vertices = (PlainVertex *)&particles[idx]; + QSGParticleData* datum = m_system->m_groupData[gIdx]->data[pIdx]; + PlainVertices *particles = (PlainVertices *) m_nodes[gIdx]->geometry()->vertexData(); + PlainVertex *vertices = (PlainVertex *)&particles[pIdx]; for (int i=0; i<4; ++i) { - vertices[i].x = m_data[idx]->x - m_systemOffset.x(); - vertices[i].y = m_data[idx]->y - m_systemOffset.y(); - vertices[i].t = m_data[idx]->t; - vertices[i].lifeSpan = m_data[idx]->lifeSpan; - vertices[i].size = m_data[idx]->size; - vertices[i].endSize = m_data[idx]->endSize; - vertices[i].sx = m_data[idx]->sx; - vertices[i].sy = m_data[idx]->sy; - vertices[i].ax = m_data[idx]->ax; - vertices[i].ay = m_data[idx]->ay; - vertices[i].r = m_data[idx]->r; + vertices[i].x = datum->x - m_systemOffset.x(); + vertices[i].y = datum->y - m_systemOffset.y(); + vertices[i].t = datum->t; + vertices[i].lifeSpan = datum->lifeSpan; + vertices[i].size = datum->size; + vertices[i].endSize = datum->endSize; + vertices[i].sx = datum->sx; + vertices[i].sy = datum->sy; + vertices[i].ax = datum->ax; + vertices[i].ay = datum->ay; + vertices[i].r = datum->r; } } diff --git a/src/declarative/particles/qsgcustomparticle_p.h b/src/declarative/particles/qsgcustomparticle_p.h index 50ff37a2a0..cccaa7e3a8 100644 --- a/src/declarative/particles/qsgcustomparticle_p.h +++ b/src/declarative/particles/qsgcustomparticle_p.h @@ -75,8 +75,8 @@ Q_SIGNALS: void fragmentShaderChanged(); void vertexShaderChanged(); protected: - virtual void initialize(int idx); - virtual void reload(int idx); + virtual void initialize(int gIdx, int pIdx); + virtual void commit(int gIdx, int pIdx); QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *); void prepareNextFrame(); @@ -88,7 +88,7 @@ protected: void updateProperties(); void lookThroughShaderCode(const QByteArray &code); virtual void componentComplete(); - QSGShaderEffectNode *buildCustomNode(); + QSGShaderEffectNode *buildCustomNodes(); void performPendingResize(); private: @@ -106,7 +106,8 @@ private: }; QVector<SourceData> m_sources; QSGShaderEffectMaterial m_material; - QSGShaderEffectNode* m_node; + QSGShaderEffectNode* m_rootNode; + QHash<int, QSGShaderEffectNode*> m_nodes; qreal m_lastTime; }; diff --git a/src/declarative/particles/qsgemitter.cpp b/src/declarative/particles/qsgemitter.cpp index c3ec3ffc9e..8204f4d162 100644 --- a/src/declarative/particles/qsgemitter.cpp +++ b/src/declarative/particles/qsgemitter.cpp @@ -41,7 +41,6 @@ #include "qsgemitter_p.h" #include "qsgparticlesystem_p.h" -#include "qsgparticlepainter_p.h"//TODO: What was this for again? QT_BEGIN_NAMESPACE QSGBasicEmitter::QSGBasicEmitter(QSGItem* parent) @@ -72,7 +71,7 @@ void QSGBasicEmitter::emitWindow(int timeStamp) { if (m_system == 0) return; - if((!m_emitting || !m_particlesPerSecond)&& !m_burstLeft && m_burstQueue.isEmpty()){ + if ((!m_emitting || !m_particlesPerSecond)&& !m_burstLeft && m_burstQueue.isEmpty()){ m_reset_last = true; return; } @@ -84,10 +83,10 @@ void QSGBasicEmitter::emitWindow(int timeStamp) m_reset_last = false; } - if(m_burstLeft){ + if (m_burstLeft){ m_burstLeft -= timeStamp - m_last_timestamp * 1000.; - if(m_burstLeft < 0){ - if(!m_emitting) + if (m_burstLeft < 0){ + if (!m_emitting) timeStamp += m_burstLeft; m_burstLeft = 0; } @@ -100,7 +99,7 @@ void QSGBasicEmitter::emitWindow(int timeStamp) qreal opt = pt; // original particle time qreal dt = time - m_last_timestamp; // timestamp delta... - if(!dt) + if (!dt) dt = 0.000001; // emitter difference since last... @@ -117,12 +116,12 @@ void QSGBasicEmitter::emitWindow(int timeStamp) qreal sizeAtEnd = m_particleEndSize >= 0 ? m_particleEndSize : m_particleSize; qreal emitter_x_offset = m_last_emitter.x() - x(); qreal emitter_y_offset = m_last_emitter.y() - y(); - if(!m_burstQueue.isEmpty() && !m_burstLeft && !m_emitting)//'outside time' emissions only + if (!m_burstQueue.isEmpty() && !m_burstLeft && !m_emitting)//'outside time' emissions only pt = time; while (pt < time || !m_burstQueue.isEmpty()) { //int pos = m_last_particle % m_particle_count; QSGParticleData* datum = m_system->newDatum(m_system->m_groupIds[m_particle]); - if(datum){//actually emit(otherwise we've been asked to skip this one) + if (datum){//actually emit(otherwise we've been asked to skip this one) datum->e = this;//###useful? qreal t = 1 - (pt - opt) / dt; qreal vx = @@ -144,7 +143,7 @@ void QSGBasicEmitter::emitWindow(int timeStamp) // Particle position QRectF boundsRect; - if(!m_burstQueue.isEmpty()){ + if (!m_burstQueue.isEmpty()){ boundsRect = QRectF(m_burstQueue.first().second.x() - x(), m_burstQueue.first().second.y() - y(), width(), height()); } else { @@ -179,11 +178,11 @@ void QSGBasicEmitter::emitWindow(int timeStamp) m_system->emitParticle(datum); } - if(m_burstQueue.isEmpty()){ + if (m_burstQueue.isEmpty()){ pt += particleRatio; }else{ m_burstQueue.first().first--; - if(m_burstQueue.first().first <= 0) + if (m_burstQueue.first().first <= 0) m_burstQueue.pop_front(); } } diff --git a/src/declarative/particles/qsgfollowemitter.cpp b/src/declarative/particles/qsgfollowemitter.cpp index ba31e8f834..72577fd816 100644 --- a/src/declarative/particles/qsgfollowemitter.cpp +++ b/src/declarative/particles/qsgfollowemitter.cpp @@ -64,15 +64,15 @@ QSGFollowEmitter::QSGFollowEmitter(QSGItem *parent) : } void QSGFollowEmitter::recalcParticlesPerSecond(){ - if(!m_system) + if (!m_system) return; - m_followCount = m_system->m_groupData[m_system->m_groupIds[m_follow]]->size; - if(!m_followCount){ + m_followCount = m_system->m_groupData[m_system->m_groupIds[m_follow]]->size(); + if (!m_followCount){ setParticlesPerSecond(1);//XXX: Fix this horrendous hack, needed so they aren't turned off from start (causes crashes - test that when gone you don't crash with 0 PPPS) }else{ setParticlesPerSecond(m_particlesPerParticlePerSecond * m_followCount); m_lastEmission.resize(m_followCount); - m_lastEmission.fill(0); + m_lastEmission.fill(m_lastTimeStamp); } } @@ -85,18 +85,18 @@ void QSGFollowEmitter::emitWindow(int timeStamp) { if (m_system == 0) return; - if(!m_emitting && !m_burstLeft && m_burstQueue.isEmpty()) + if (!m_emitting && !m_burstLeft && m_burstQueue.isEmpty()) return; - if(m_followCount != m_system->m_groupData[m_system->m_groupIds[m_follow]]->size){ + if (m_followCount != m_system->m_groupData[m_system->m_groupIds[m_follow]]->size()){ qreal oldPPS = m_particlesPerSecond; recalcParticlesPerSecond(); - if(m_particlesPerSecond != oldPPS) + if (m_particlesPerSecond != oldPPS) return;//system may need to update } - if(m_burstLeft){ + if (m_burstLeft){ m_burstLeft -= timeStamp - m_lastTimeStamp * 1000.; - if(m_burstLeft < 0){ + if (m_burstLeft < 0){ timeStamp += m_burstLeft; m_burstLeft = 0; } @@ -112,20 +112,22 @@ void QSGFollowEmitter::emitWindow(int timeStamp) int gId = m_system->m_groupIds[m_follow]; int gId2 = m_system->m_groupIds[m_particle]; - foreach(QSGParticleData *d, m_system->m_groupData[gId]->data){ - if(!d || !d->stillAlive()) + foreach (QSGParticleData *d, m_system->m_groupData[gId]->data){ + if (!d || !d->stillAlive()){ + m_lastEmission[d->index] = time; //Should only start emitting when it returns to life continue; + } pt = m_lastEmission[d->index]; - if(pt < d->t) + if (pt < d->t) pt = d->t; - if(!effectiveExtruder()->contains(QRectF(offset.x(), offset.y(), width(), height()),QPointF(d->curX(), d->curY()))){ + if ((width() || height()) && !effectiveExtruder()->contains(QRectF(offset.x(), offset.y(), width(), height()),QPointF(d->curX(), d->curY()))){ m_lastEmission[d->index] = time;//jump over this time period without emitting, because it's outside continue; } - while(pt < time || !m_burstQueue.isEmpty()){ + while (pt < time || !m_burstQueue.isEmpty()){ QSGParticleData* datum = m_system->newDatum(gId2); - if(datum){//else, skip this emission + if (datum){//else, skip this emission datum->e = this;//###useful? // Particle timestamp @@ -178,9 +180,9 @@ void QSGFollowEmitter::emitWindow(int timeStamp) m_system->emitParticle(datum); } - if(!m_burstQueue.isEmpty()){ + if (!m_burstQueue.isEmpty()){ m_burstQueue.first().first--; - if(m_burstQueue.first().first <= 0) + if (m_burstQueue.first().first <= 0) m_burstQueue.pop_front(); }else{ pt += particleRatio; diff --git a/src/declarative/particles/qsgfriction.cpp b/src/declarative/particles/qsgfriction.cpp index 828d20556d..46816817fd 100644 --- a/src/declarative/particles/qsgfriction.cpp +++ b/src/declarative/particles/qsgfriction.cpp @@ -48,7 +48,7 @@ QSGFrictionAffector::QSGFrictionAffector(QSGItem *parent) : bool QSGFrictionAffector::affectParticle(QSGParticleData *d, qreal dt) { - if(!m_factor) + if (!m_factor) return false; qreal curSX = d->curSX(); qreal curSY = d->curSY(); diff --git a/src/declarative/particles/qsggravity.cpp b/src/declarative/particles/qsggravity.cpp index b1cf3e9481..579fa7c4e7 100644 --- a/src/declarative/particles/qsggravity.cpp +++ b/src/declarative/particles/qsggravity.cpp @@ -64,11 +64,11 @@ bool QSGGravityAffector::affectParticle(QSGParticleData *d, qreal dt) { Q_UNUSED(dt); bool changed = false; - if(d->ax != m_xAcc){ + if (d->ax != m_xAcc){ d->setInstantaneousAX(m_xAcc); changed = true; } - if(d->ay != m_yAcc){ + if (d->ay != m_yAcc){ d->setInstantaneousAY(m_yAcc); changed = true; } diff --git a/src/declarative/particles/qsgimageparticle.cpp b/src/declarative/particles/qsgimageparticle.cpp index 836236c13e..ac64d589f5 100644 --- a/src/declarative/particles/qsgimageparticle.cpp +++ b/src/declarative/particles/qsgimageparticle.cpp @@ -193,7 +193,7 @@ float UltraMaterialData::chunkOfBytes[1024]; QSGMaterialShader *UltraMaterial::createShader() const { - if(usesSprites)//TODO: Perhaps just swap the shaders, and don't mind the extra vector? + if (usesSprites)//TODO: Perhaps just swap the shaders, and don't mind the extra vector? return new UltraMaterialData; else return new UltraMaterialData; @@ -285,7 +285,7 @@ QSGImageParticle::QSGImageParticle(QSGItem* parent) : QSGParticlePainter(parent) , m_do_reset(false) , m_color_variation(0.0) - , m_node(0) + , m_rootNode(0) , m_material(0) , m_alphaVariation(0.0) , m_alpha(1.0) @@ -355,7 +355,7 @@ void QSGImageParticle::setColor(const QColor &color) return; m_color = color; emit colorChanged(); - if(perfLevel < Colored) + if (perfLevel < Colored) reset(); } @@ -365,7 +365,7 @@ void QSGImageParticle::setColorVariation(qreal var) return; m_color_variation = var; emit colorVariationChanged(); - if(perfLevel < Colored) + if (perfLevel < Colored) reset(); } @@ -375,7 +375,7 @@ void QSGImageParticle::setAlphaVariation(qreal arg) m_alphaVariation = arg; emit alphaVariationChanged(arg); } - if(perfLevel < Colored) + if (perfLevel < Colored) reset(); } @@ -385,7 +385,7 @@ void QSGImageParticle::setAlpha(qreal arg) m_alpha = arg; emit alphaChanged(arg); } - if(perfLevel < Colored) + if (perfLevel < Colored) reset(); } @@ -395,7 +395,7 @@ void QSGImageParticle::setRedVariation(qreal arg) m_redVariation = arg; emit redVariationChanged(arg); } - if(perfLevel < Colored) + if (perfLevel < Colored) reset(); } @@ -405,7 +405,7 @@ void QSGImageParticle::setGreenVariation(qreal arg) m_greenVariation = arg; emit greenVariationChanged(arg); } - if(perfLevel < Colored) + if (perfLevel < Colored) reset(); } @@ -415,7 +415,7 @@ void QSGImageParticle::setBlueVariation(qreal arg) m_blueVariation = arg; emit blueVariationChanged(arg); } - if(perfLevel < Colored) + if (perfLevel < Colored) reset(); } @@ -425,7 +425,7 @@ void QSGImageParticle::setRotation(qreal arg) m_rotation = arg; emit rotationChanged(arg); } - if(perfLevel < Deformable) + if (perfLevel < Deformable) reset(); } @@ -435,7 +435,7 @@ void QSGImageParticle::setRotationVariation(qreal arg) m_rotationVariation = arg; emit rotationVariationChanged(arg); } - if(perfLevel < Deformable) + if (perfLevel < Deformable) reset(); } @@ -445,7 +445,7 @@ void QSGImageParticle::setRotationSpeed(qreal arg) m_rotationSpeed = arg; emit rotationSpeedChanged(arg); } - if(perfLevel < Deformable) + if (perfLevel < Deformable) reset(); } @@ -455,7 +455,7 @@ void QSGImageParticle::setRotationSpeedVariation(qreal arg) m_rotationSpeedVariation = arg; emit rotationSpeedVariationChanged(arg); } - if(perfLevel < Deformable) + if (perfLevel < Deformable) reset(); } @@ -465,7 +465,7 @@ void QSGImageParticle::setAutoRotation(bool arg) m_autoRotation = arg; emit autoRotationChanged(arg); } - if(perfLevel < Deformable) + if (perfLevel < Deformable) reset(); } @@ -475,7 +475,7 @@ void QSGImageParticle::setXVector(QSGStochasticDirection* arg) m_xVector = arg; emit xVectorChanged(arg); } - if(perfLevel < Deformable) + if (perfLevel < Deformable) reset(); } @@ -485,7 +485,7 @@ void QSGImageParticle::setYVector(QSGStochasticDirection* arg) m_yVector = arg; emit yVectorChanged(arg); } - if(perfLevel < Deformable) + if (perfLevel < Deformable) reset(); } @@ -495,21 +495,22 @@ void QSGImageParticle::setBloat(bool arg) m_bloat = arg; emit bloatChanged(arg); } - if(perfLevel < 9999) + if (perfLevel < 9999) reset(); } void QSGImageParticle::reset() { QSGParticlePainter::reset(); - m_pleaseReset = true; + m_pleaseReset = true; + update(); } void QSGImageParticle::createEngine() { - if(m_spriteEngine) + if (m_spriteEngine) delete m_spriteEngine; - if(m_sprites.count()) + if (m_sprites.count()) m_spriteEngine = new QSGSpriteEngine(m_sprites, this); else m_spriteEngine = 0; @@ -548,7 +549,7 @@ static QSGGeometry::AttributeSet UltraParticle_AttributeSet = UltraParticle_Attributes }; -QSGGeometryNode* QSGImageParticle::buildSimpleParticleNode() +QSGGeometryNode* QSGImageParticle::buildSimpleParticleNodes() { perfLevel = Simple;//TODO: Intermediate levels QImage image = QImage(m_image_name.toLocalFile()); @@ -556,56 +557,6 @@ QSGGeometryNode* QSGImageParticle::buildSimpleParticleNode() printf("UltraParticle: loading image failed... '%s'\n", qPrintable(m_image_name.toLocalFile())); return 0; } - int vCount = m_count * 4; - int iCount = m_count * 6; - qDebug() << "Simple Case"; - - QSGGeometry *g = new QSGGeometry(SimpleParticle_AttributeSet, vCount, iCount); - g->setDrawingMode(GL_TRIANGLES); - - SimpleVertex *vertices = (SimpleVertex *) g->vertexData(); - for (int p=0; p<m_count; ++p){ - for(int i=0; i<4; i++){ - vertices[i].x = m_data[p]->x; - vertices[i].y = m_data[p]->y; - vertices[i].t = m_data[p]->t; - vertices[i].size = m_data[p]->size; - vertices[i].endSize = m_data[p]->endSize; - vertices[i].sx = m_data[p]->sx; - vertices[i].sy = m_data[p]->sy; - vertices[i].ax = m_data[p]->ax; - vertices[i].ay = m_data[p]->ay; - } - //reload(p); - vertices[0].tx = 0; - vertices[0].ty = 0; - - vertices[1].tx = 1; - vertices[1].ty = 0; - - vertices[2].tx = 0; - vertices[2].ty = 1; - - vertices[3].tx = 1; - vertices[3].ty = 1; - - vertices += 4; - } - - quint16 *indices = g->indexDataAsUShort(); - for (int i=0; i<m_count; ++i) { - int o = i * 4; - indices[0] = o; - indices[1] = o + 1; - indices[2] = o + 2; - indices[3] = o + 1; - indices[4] = o + 3; - indices[5] = o + 2; - indices += 6; - } - - m_node = new QSGGeometryNode(); - m_node->setGeometry(g); if (m_material) { delete m_material; @@ -616,25 +567,73 @@ QSGGeometryNode* QSGImageParticle::buildSimpleParticleNode() m_material->texture = sceneGraphEngine()->createTextureFromImage(image); m_material->texture->setFiltering(QSGTexture::Linear); m_material->framecount = 1; - m_node->setMaterial(m_material); - m_last_particle = 0; - return m_node; + foreach (const QString &str, m_particles){ + int gIdx = m_system->m_groupIds[str]; + int count = m_system->m_groupData[gIdx]->size(); + + QSGGeometryNode* node = new QSGGeometryNode(); + m_nodes.insert(gIdx, node); + node->setMaterial(m_material); + + int vCount = count * 4; + int iCount = count * 6; + + QSGGeometry *g = new QSGGeometry(SimpleParticle_AttributeSet, vCount, iCount); + node->setGeometry(g); + g->setDrawingMode(GL_TRIANGLES); + + SimpleVertex *vertices = (SimpleVertex *) g->vertexData(); + for (int p=0; p < count; ++p){ + commit(gIdx, p); + vertices[0].tx = 0; + vertices[0].ty = 0; + + vertices[1].tx = 1; + vertices[1].ty = 0; + + vertices[2].tx = 0; + vertices[2].ty = 1; + + vertices[3].tx = 1; + vertices[3].ty = 1; + + vertices += 4; + } + + quint16 *indices = g->indexDataAsUShort(); + for (int i=0; i < count; ++i) { + int o = i * 4; + indices[0] = o; + indices[1] = o + 1; + indices[2] = o + 2; + indices[3] = o + 1; + indices[4] = o + 3; + indices[5] = o + 2; + indices += 6; + } + } + + foreach (QSGGeometryNode* node, m_nodes){ + if (node == *(m_nodes.begin())) + continue; + (*(m_nodes.begin()))->appendChildNode(node); + } + + return *(m_nodes.begin()); } -QSGGeometryNode* QSGImageParticle::buildParticleNode() +QSGGeometryNode* QSGImageParticle::buildParticleNodes() { if (m_count * 4 > 0xffff) { printf("UltraParticle: Too many particles... \n");//### Why is this here? return 0; } - if(m_count <= 0) { - qDebug() << "UltraParticle: Too few particles... \n";//XXX: Is now a vaild intermediate state... + if (count() <= 0) return 0; - } - if(!m_sprites.count() && !m_bloat + if (!m_sprites.count() && !m_bloat && m_colortable_name.isEmpty() && m_sizetable_name.isEmpty() && m_opacitytable_name.isEmpty() @@ -645,20 +644,19 @@ QSGGeometryNode* QSGImageParticle::buildParticleNode() && !m_redVariation && !m_blueVariation && !m_greenVariation && !m_color.isValid() ) - return buildSimpleParticleNode(); + return buildSimpleParticleNodes(); perfLevel = Sprites;//TODO: intermediate levels - if(!m_color.isValid())//But we're in colored level (or higher) + if (!m_color.isValid())//But we're in colored level (or higher) m_color = QColor(Qt::white); - qDebug() << "Complex Case"; QImage image; - if(m_sprites.count()){ + if (m_sprites.count()){ if (!m_spriteEngine) { qWarning() << "UltraParticle: No sprite engine..."; return 0; } image = m_spriteEngine->assembledImage(); - if(image.isNull())//Warning is printed in engine + if (image.isNull())//Warning is printed in engine return 0; }else{ image = QImage(m_image_name.toLocalFile()); @@ -668,46 +666,6 @@ QSGGeometryNode* QSGImageParticle::buildParticleNode() } } - int vCount = m_count * 4; - int iCount = m_count * 6; - - QSGGeometry *g = new QSGGeometry(UltraParticle_AttributeSet, vCount, iCount); - g->setDrawingMode(GL_TRIANGLES); - m_node = new QSGGeometryNode(); - m_node->setGeometry(g); - - UltraVertex *vertices = (UltraVertex *) g->vertexData(); - for (int p=0; p<m_count; ++p) { - reload(p);//reload gets geometry from node - - vertices[0].tx = 0; - vertices[0].ty = 0; - - vertices[1].tx = 1; - vertices[1].ty = 0; - - vertices[2].tx = 0; - vertices[2].ty = 1; - - vertices[3].tx = 1; - vertices[3].ty = 1; - - vertices += 4; - } - - quint16 *indices = g->indexDataAsUShort(); - for (int i=0; i<m_count; ++i) { - int o = i * 4; - indices[0] = o; - indices[1] = o + 1; - indices[2] = o + 2; - indices[3] = o + 1; - indices[4] = o + 3; - indices[5] = o + 2; - indices += 6; - } - - qFree(m_lastData); if (m_material) { delete m_material; m_material = 0; @@ -717,11 +675,11 @@ QSGGeometryNode* QSGImageParticle::buildParticleNode() QImage sizetable(m_sizetable_name.toLocalFile()); QImage opacitytable(m_opacitytable_name.toLocalFile()); m_material = new UltraMaterial(); - if(colortable.isNull()) + if (colortable.isNull()) colortable = QImage(":defaultshaders/identitytable.png"); - if(sizetable.isNull()) + if (sizetable.isNull()) sizetable = QImage(":defaultshaders/identitytable.png"); - if(opacitytable.isNull()) + if (opacitytable.isNull()) opacitytable = QImage(":defaultshaders/defaultFadeInOut.png"); Q_ASSERT(!colortable.isNull()); Q_ASSERT(!sizetable.isNull()); @@ -734,55 +692,109 @@ QSGGeometryNode* QSGImageParticle::buildParticleNode() m_material->texture->setFiltering(QSGTexture::Linear); m_material->framecount = 1; - if(m_spriteEngine){ + if (m_spriteEngine){ m_material->framecount = m_spriteEngine->maxFrames(); m_spriteEngine->setCount(m_count); } - m_node->setMaterial(m_material); + foreach (const QString &str, m_particles){ + int gIdx = m_system->m_groupIds[str]; + int count = m_system->m_groupData[gIdx]->size(); + QSGGeometryNode* node = new QSGGeometryNode(); + node->setMaterial(m_material); + + m_nodes.insert(gIdx, node); + m_idxStarts.insert(gIdx, m_lastIdxStart); + m_lastIdxStart += count; + + //Create Particle Geometry + int vCount = count * 4; + int iCount = count * 6; + + QSGGeometry *g = new QSGGeometry(UltraParticle_AttributeSet, vCount, iCount); + node->setGeometry(g); + g->setDrawingMode(GL_TRIANGLES); + + UltraVertex *vertices = (UltraVertex *) g->vertexData(); + for (int p=0; p < count; ++p) { + commit(gIdx, p);//commit sets geometry for the node + + vertices[0].tx = 0; + vertices[0].ty = 0; + + vertices[1].tx = 1; + vertices[1].ty = 0; + + vertices[2].tx = 0; + vertices[2].ty = 1; + + vertices[3].tx = 1; + vertices[3].ty = 1; + + vertices += 4; + } + + quint16 *indices = g->indexDataAsUShort(); + for (int i=0; i < count; ++i) { + int o = i * 4; + indices[0] = o; + indices[1] = o + 1; + indices[2] = o + 2; + indices[3] = o + 1; + indices[4] = o + 3; + indices[5] = o + 2; + indices += 6; + } + + } - m_last_particle = 0; + foreach (QSGGeometryNode* node, m_nodes){ + if (node == *(m_nodes.begin())) + continue; + (*(m_nodes.begin()))->appendChildNode(node); + } - return m_node; + return *(m_nodes.begin()); } QSGNode *QSGImageParticle::updatePaintNode(QSGNode *, UpdatePaintNodeData *) { - if(m_pleaseReset){ - if(m_node){ - if(perfLevel == 1){ - m_lastCount = m_node->geometry()->vertexCount() / 4; - m_lastData = qMalloc(m_lastCount*sizeof(SimpleVertices)); - memcpy(m_lastData, m_node->geometry()->vertexData(), m_lastCount * sizeof(SimpleVertices));//TODO: Multiple levels - } - m_lastLevel = perfLevel; - delete m_node; - } - if(m_material) - delete m_material; + if (m_pleaseReset){ + m_lastLevel = perfLevel; + + delete m_rootNode;//Automatically deletes children + m_rootNode = 0; + m_nodes.clear(); - m_node = 0; + m_idxStarts.clear(); + m_lastIdxStart = 0; + + if (m_material) + delete m_material; m_material = 0; + m_pleaseReset = false; } - if(m_system && m_system->isRunning()) + if (m_system && m_system->isRunning()) prepareNextFrame(); - if (m_node){ + if (m_rootNode){ update(); - m_node->markDirty(QSGNode::DirtyMaterial); + //### Should I be using dirty geometry too/instead? + foreach (QSGGeometryNode* node, m_nodes) + node->markDirty(QSGNode::DirtyMaterial); } - return m_node; + return m_rootNode; } void QSGImageParticle::prepareNextFrame() { - if (m_node == 0){//TODO: Staggered loading (as emitted) - m_node = buildParticleNode(); - if(m_node == 0) + if (m_rootNode == 0){//TODO: Staggered loading (as emitted) + m_rootNode = buildParticleNodes(); + if (m_rootNode == 0) return; - qDebug() << "Feature level: " << perfLevel; + //qDebug() << "Feature level: " << perfLevel; } qint64 timeStamp = m_system->systemSync(this); @@ -790,18 +802,25 @@ void QSGImageParticle::prepareNextFrame() m_material->timestamp = time; //Advance State - if(m_spriteEngine){//perfLevel == Sprites? + if (m_spriteEngine){//perfLevel == Sprites?//TODO: use signals? + m_material->animcount = m_spriteEngine->spriteCount(); - UltraVertices *particles = (UltraVertices *) m_node->geometry()->vertexData(); m_spriteEngine->updateSprites(timeStamp); - for(int i=0; i<m_count; i++){ - UltraVertices &p = particles[i]; - int curIdx = m_spriteEngine->spriteState(i); - if(curIdx != p.v1.animIdx){ - p.v1.animIdx = p.v2.animIdx = p.v3.animIdx = p.v4.animIdx = curIdx; - p.v1.animT = p.v2.animT = p.v3.animT = p.v4.animT = m_spriteEngine->spriteStart(i)/1000.0; - p.v1.frameCount = p.v2.frameCount = p.v3.frameCount = p.v4.frameCount = m_spriteEngine->spriteFrames(i); - p.v1.frameDuration = p.v2.frameDuration = p.v3.frameDuration = p.v4.frameDuration = m_spriteEngine->spriteDuration(i); + foreach (const QString &str, m_particles){ + int gIdx = m_system->m_groupIds[str]; + int count = m_system->m_groupData[gIdx]->size(); + + UltraVertices *particles = (UltraVertices *) m_nodes[gIdx]->geometry()->vertexData(); + for (int i=0; i < count; i++){ + int spriteIdx = m_idxStarts[gIdx] + i; + UltraVertices &p = particles[i]; + int curIdx = m_spriteEngine->spriteState(spriteIdx); + if (curIdx != p.v1.animIdx){ + p.v1.animIdx = p.v2.animIdx = p.v3.animIdx = p.v4.animIdx = curIdx; + p.v1.animT = p.v2.animT = p.v3.animT = p.v4.animT = m_spriteEngine->spriteStart(spriteIdx)/1000.0; + p.v1.frameCount = p.v2.frameCount = p.v3.frameCount = p.v4.frameCount = m_spriteEngine->spriteFrames(spriteIdx); + p.v1.frameDuration = p.v2.frameDuration = p.v3.frameDuration = p.v4.frameDuration = m_spriteEngine->spriteDuration(spriteIdx); + } } } }else{ @@ -815,43 +834,45 @@ void QSGImageParticle::reloadColor(const Color4ub &c, QSGParticleData* d) //TODO: get index for reload - or make function take an index } -void QSGImageParticle::initialize(int idx) +void QSGImageParticle::initialize(int gIdx, int pIdx) { Color4ub color; + QSGParticleData* datum = m_system->m_groupData[gIdx]->data[pIdx]; qreal redVariation = m_color_variation + m_redVariation; qreal greenVariation = m_color_variation + m_greenVariation; qreal blueVariation = m_color_variation + m_blueVariation; - switch(perfLevel){//Fall-through is intended on all of them + int spriteIdx = m_idxStarts[gIdx] + datum->index; + switch (perfLevel){//Fall-through is intended on all of them case Sprites: // Initial Sprite State - m_data[idx]->animT = m_data[idx]->t; - m_data[idx]->animIdx = 0; - if(m_spriteEngine){ - m_spriteEngine->startSprite(idx); - m_data[idx]->frameCount = m_spriteEngine->spriteFrames(idx); - m_data[idx]->frameDuration = m_spriteEngine->spriteDuration(idx); + datum->animT = datum->t; + datum->animIdx = 0; + if (m_spriteEngine){ + m_spriteEngine->startSprite(spriteIdx); + datum->frameCount = m_spriteEngine->spriteFrames(spriteIdx); + datum->frameDuration = m_spriteEngine->spriteDuration(spriteIdx); }else{ - m_data[idx]->frameCount = 1; - m_data[idx]->frameDuration = 9999; + datum->frameCount = 1; + datum->frameDuration = 9999; } case Tabled: case Deformable: //Initial Rotation - if(m_xVector){ - const QPointF &ret = m_xVector->sample(QPointF(m_data[idx]->x, m_data[idx]->y)); - m_data[idx]->xx = ret.x(); - m_data[idx]->xy = ret.y(); + if (m_xVector){ + const QPointF &ret = m_xVector->sample(QPointF(datum->x, datum->y)); + datum->xx = ret.x(); + datum->xy = ret.y(); } - if(m_yVector){ - const QPointF &ret = m_yVector->sample(QPointF(m_data[idx]->x, m_data[idx]->y)); - m_data[idx]->yx = ret.x(); - m_data[idx]->yy = ret.y(); + if (m_yVector){ + const QPointF &ret = m_yVector->sample(QPointF(datum->x, datum->y)); + datum->yx = ret.x(); + datum->yy = ret.y(); } - m_data[idx]->rotation = + datum->rotation = (m_rotation + (m_rotationVariation - 2*((qreal)rand()/RAND_MAX)*m_rotationVariation) ) * CONV; - m_data[idx]->rotationSpeed = + datum->rotationSpeed = (m_rotationSpeed + (m_rotationSpeedVariation - 2*((qreal)rand()/RAND_MAX)*m_rotationSpeedVariation) ) * CONV; - m_data[idx]->autoRotate = m_autoRotation?1.0:0.0; + datum->autoRotate = m_autoRotation?1.0:0.0; case Colored: //Color initialization // Particle color @@ -859,74 +880,78 @@ void QSGImageParticle::initialize(int idx) color.g = m_color.green() * (1 - greenVariation) + rand() % 256 * greenVariation; color.b = m_color.blue() * (1 - blueVariation) + rand() % 256 * blueVariation; color.a = m_alpha * m_color.alpha() * (1 - m_alphaVariation) + rand() % 256 * m_alphaVariation; - m_data[idx]->color = color; + datum->color = color; default: break; } } -void QSGImageParticle::reload(int idx) +void QSGImageParticle::commit(int gIdx, int pIdx) { - if(!m_node) + if (m_pleaseReset) + return; + QSGGeometryNode *node = m_nodes[gIdx]; + if (!node) return; + QSGParticleData* datum = m_system->m_groupData[gIdx]->data[pIdx]; - m_node->setFlag(QSGNode::OwnsGeometry, false); - UltraVertex *ultraVertices = (UltraVertex *) m_node->geometry()->vertexData(); - SimpleVertex *simpleVertices = (SimpleVertex *) m_node->geometry()->vertexData(); - switch(perfLevel){ + node->setFlag(QSGNode::OwnsGeometry, false); + UltraVertex *ultraVertices = (UltraVertex *) node->geometry()->vertexData(); + SimpleVertex *simpleVertices = (SimpleVertex *) node->geometry()->vertexData(); + switch (perfLevel){ case Sprites: - ultraVertices += idx*4; - for(int i=0; i<4; i++){ - ultraVertices[i].x = m_data[idx]->x - m_systemOffset.x(); - ultraVertices[i].y = m_data[idx]->y - m_systemOffset.y(); - ultraVertices[i].t = m_data[idx]->t; - ultraVertices[i].lifeSpan = m_data[idx]->lifeSpan; - ultraVertices[i].size = m_data[idx]->size; - ultraVertices[i].endSize = m_data[idx]->endSize; - ultraVertices[i].sx = m_data[idx]->sx; - ultraVertices[i].sy = m_data[idx]->sy; - ultraVertices[i].ax = m_data[idx]->ax; - ultraVertices[i].ay = m_data[idx]->ay; - ultraVertices[i].xx = m_data[idx]->xx; - ultraVertices[i].xy = m_data[idx]->xy; - ultraVertices[i].yx = m_data[idx]->yx; - ultraVertices[i].yy = m_data[idx]->yy; - ultraVertices[i].rotation = m_data[idx]->rotation; - ultraVertices[i].rotationSpeed = m_data[idx]->rotationSpeed; - ultraVertices[i].autoRotate = m_data[idx]->autoRotate; - ultraVertices[i].animIdx = m_data[idx]->animIdx; - ultraVertices[i].frameDuration = m_data[idx]->frameDuration; - ultraVertices[i].frameCount = m_data[idx]->frameCount; - ultraVertices[i].animT = m_data[idx]->animT; - ultraVertices[i].color.r = m_data[idx]->color.r; - ultraVertices[i].color.g = m_data[idx]->color.g; - ultraVertices[i].color.b = m_data[idx]->color.b; - ultraVertices[i].color.a = m_data[idx]->color.a; + ultraVertices += pIdx*4; + for (int i=0; i<4; i++){ + ultraVertices[i].x = datum->x - m_systemOffset.x(); + ultraVertices[i].y = datum->y - m_systemOffset.y(); + ultraVertices[i].t = datum->t; + ultraVertices[i].lifeSpan = datum->lifeSpan; + ultraVertices[i].size = datum->size; + ultraVertices[i].endSize = datum->endSize; + ultraVertices[i].sx = datum->sx; + ultraVertices[i].sy = datum->sy; + ultraVertices[i].ax = datum->ax; + ultraVertices[i].ay = datum->ay; + ultraVertices[i].xx = datum->xx; + ultraVertices[i].xy = datum->xy; + ultraVertices[i].yx = datum->yx; + ultraVertices[i].yy = datum->yy; + ultraVertices[i].rotation = datum->rotation; + ultraVertices[i].rotationSpeed = datum->rotationSpeed; + ultraVertices[i].autoRotate = datum->autoRotate; + ultraVertices[i].animIdx = datum->animIdx; + ultraVertices[i].frameDuration = datum->frameDuration; + ultraVertices[i].frameCount = datum->frameCount; + ultraVertices[i].animT = datum->animT; + ultraVertices[i].color.r = datum->color.r; + ultraVertices[i].color.g = datum->color.g; + ultraVertices[i].color.b = datum->color.b; + ultraVertices[i].color.a = datum->color.a; } break; case Tabled://TODO: Us case Deformable: case Colored: case Simple: - simpleVertices += idx*4; - for(int i=0; i<4; i++){ - simpleVertices[i].x = m_data[idx]->x - m_systemOffset.x(); - simpleVertices[i].y = m_data[idx]->y - m_systemOffset.y(); - simpleVertices[i].t = m_data[idx]->t; - simpleVertices[i].lifeSpan = m_data[idx]->lifeSpan; - simpleVertices[i].size = m_data[idx]->size; - simpleVertices[i].endSize = m_data[idx]->endSize; - simpleVertices[i].sx = m_data[idx]->sx; - simpleVertices[i].sy = m_data[idx]->sy; - simpleVertices[i].ax = m_data[idx]->ax; - simpleVertices[i].ay = m_data[idx]->ay; + simpleVertices += pIdx*4; + for (int i=0; i<4; i++){ + simpleVertices[i].x = datum->x - m_systemOffset.x(); + simpleVertices[i].y = datum->y - m_systemOffset.y(); + simpleVertices[i].t = datum->t; + simpleVertices[i].lifeSpan = datum->lifeSpan; + simpleVertices[i].size = datum->size; + simpleVertices[i].endSize = datum->endSize; + simpleVertices[i].sx = datum->sx; + simpleVertices[i].sy = datum->sy; + simpleVertices[i].ax = datum->ax; + simpleVertices[i].ay = datum->ay; } break; default: break; } - m_node->setFlag(QSGNode::OwnsGeometry, true); + node->setFlag(QSGNode::OwnsGeometry, true); } diff --git a/src/declarative/particles/qsgimageparticle_p.h b/src/declarative/particles/qsgimageparticle_p.h index dc79c5910e..6cade7f554 100644 --- a/src/declarative/particles/qsgimageparticle_p.h +++ b/src/declarative/particles/qsgimageparticle_p.h @@ -284,13 +284,13 @@ public slots: protected: void reset(); - virtual void initialize(int idx); - virtual void reload(int idx); + virtual void initialize(int gIdx, int pIdx); + virtual void commit(int gIdx, int pIdx); QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *); void prepareNextFrame(); - QSGGeometryNode* buildParticleNode(); - QSGGeometryNode* buildSimpleParticleNode(); + QSGGeometryNode* buildParticleNodes(); + QSGGeometryNode* buildSimpleParticleNodes(); private slots: void createEngine(); //### method invoked by sprite list changing (in engine.h) - pretty nasty @@ -308,11 +308,13 @@ private: qreal m_color_variation; qreal m_particleDuration; - QSGGeometryNode *m_node; + QSGGeometryNode *m_rootNode; + QHash<int, QSGGeometryNode *> m_nodes; + QHash<int, int> m_idxStarts;//TODO: Proper resizing will lead to needing a spriteEngine per particle - do this after sprite engine gains transparent sharing? + int m_lastIdxStart; UltraMaterial *m_material; // derived values... - int m_last_particle; qreal m_render_opacity; qreal m_alphaVariation; @@ -335,8 +337,6 @@ private: PerformanceLevel perfLevel; PerformanceLevel m_lastLevel; - void* m_lastData; - int m_lastCount; }; QT_END_NAMESPACE diff --git a/src/declarative/particles/qsgitemparticle.cpp b/src/declarative/particles/qsgitemparticle.cpp index 5e324cd43d..4e6a91416c 100644 --- a/src/declarative/particles/qsgitemparticle.cpp +++ b/src/declarative/particles/qsgitemparticle.cpp @@ -74,7 +74,7 @@ void QSGItemParticle::unfreeze(QSGItem* item) void QSGItemParticle::take(QSGItem *item, bool prioritize) { - if(prioritize) + if (prioritize) m_pendingItems.push_front(item); else m_pendingItems.push_back(item); @@ -85,54 +85,54 @@ void QSGItemParticle::give(QSGItem *item) //TODO: This } -void QSGItemParticle::initialize(int idx) +void QSGItemParticle::initialize(int gIdx, int pIdx) { - m_loadables << idx;//defer to other thread + m_loadables << m_system->m_groupData[gIdx]->data[pIdx];//defer to other thread } -void QSGItemParticle::reload(int idx) +void QSGItemParticle::commit(int, int) { } void QSGItemParticle::tick() { - foreach(QSGItem* item, m_deletables){ - if(m_fade) + foreach (QSGItem* item, m_deletables){ + if (m_fade) item->setOpacity(0.); QSGItemParticleAttached* mpa; - if((mpa = qobject_cast<QSGItemParticleAttached*>(qmlAttachedPropertiesObject<QSGItemParticle>(item)))) + if ((mpa = qobject_cast<QSGItemParticleAttached*>(qmlAttachedPropertiesObject<QSGItemParticle>(item)))) mpa->detach();//reparent as well? //TODO: Delete iff we created it m_activeCount--; } m_deletables.clear(); - foreach(int pos, m_loadables){ - if(m_stasis.contains(m_data[pos]->delegate)) + foreach (QSGParticleData* d, m_loadables){ + if (m_stasis.contains(d->delegate)) qWarning() << "Current model particles prefers overwrite:false"; //remove old item from the particle that is dying to make room for this one - if(m_data[pos]->delegate){ - m_deletables << m_data[pos]->delegate; + if (d->delegate){ + m_deletables << d->delegate; m_activeCount--; } - m_data[pos]->delegate = 0; - if(!m_pendingItems.isEmpty()){ - m_data[pos]->delegate = m_pendingItems.front(); + d->delegate = 0; + if (!m_pendingItems.isEmpty()){ + d->delegate = m_pendingItems.front(); m_pendingItems.pop_front(); - }else if(m_delegate){ - m_data[pos]->delegate = qobject_cast<QSGItem*>(m_delegate->create(qmlContext(this))); + }else if (m_delegate){ + d->delegate = qobject_cast<QSGItem*>(m_delegate->create(qmlContext(this))); } - if(m_data[pos]->delegate && m_data[pos]){//###Data can be zero if creating an item leads to a reset - this screws things up. - m_data[pos]->delegate->setX(m_data[pos]->curX() - m_data[pos]->delegate->width()/2);//TODO: adjust for system? - m_data[pos]->delegate->setY(m_data[pos]->curY() - m_data[pos]->delegate->height()/2); - QSGItemParticleAttached* mpa = qobject_cast<QSGItemParticleAttached*>(qmlAttachedPropertiesObject<QSGItemParticle>(m_data[pos]->delegate)); - if(mpa){ + if (d->delegate && d){//###Data can be zero if creating an item leads to a reset - this screws things up. + d->delegate->setX(d->curX() - d->delegate->width()/2);//TODO: adjust for system? + d->delegate->setY(d->curY() - d->delegate->height()/2); + QSGItemParticleAttached* mpa = qobject_cast<QSGItemParticleAttached*>(qmlAttachedPropertiesObject<QSGItemParticle>(d->delegate)); + if (mpa){ mpa->m_mp = this; mpa->attach(); } - m_data[pos]->delegate->setParentItem(this); - if(m_fade) - m_data[pos]->delegate->setOpacity(0.); + d->delegate->setParentItem(this); + if (m_fade) + d->delegate->setOpacity(0.); m_activeCount++; } } @@ -151,14 +151,14 @@ void QSGItemParticle::reset() QSGNode* QSGItemParticle::updatePaintNode(QSGNode* n, UpdatePaintNodeData* d) { //Dummy update just to get painting tick - if(m_pleaseReset){ + if (m_pleaseReset){ m_pleaseReset = false; reset(); } prepareNextFrame(); update();//Get called again - if(n) + if (n) n->markDirty(QSGNode::DirtyMaterial); return QSGItem::updatePaintNode(n,d); } @@ -169,38 +169,43 @@ void QSGItemParticle::prepareNextFrame() qreal curT = timeStamp/1000.0; qreal dt = curT - m_lastT; m_lastT = curT; - if(!m_activeCount) + if (!m_activeCount) return; //TODO: Size, better fade? - for(int i=0; i<count(); i++){ - QSGParticleData* data = m_data[i]; - QSGItem* item = data->delegate; - if(!item) - continue; - qreal t = ((timeStamp/1000.0) - data->t) / data->lifeSpan; - if(m_stasis.contains(item)) { - m_data[i]->t += dt;//Stasis effect - continue; - } - if(t >= 1.0){//Usually happens from load - m_deletables << item; - m_data[i]->delegate = 0; - m_activeCount--; - }else{//Fade - if(m_fade){ - qreal o = 1.; - if(t<0.2) - o = t*5; - if(t>0.8) - o = (1-t)*5; - item->setOpacity(o); - }else{ - item->setOpacity(1.);//###Without fade, it's just a binary toggle - if we turn it off we have to turn it back on + foreach (const QString &str, m_particles){ + int gIdx = m_system->m_groupIds[str]; + int count = m_system->m_groupData[gIdx]->size(); + + for (int i=0; i<count; i++){ + QSGParticleData* data = m_system->m_groupData[gIdx]->data[i]; + QSGItem* item = data->delegate; + if (!item) + continue; + qreal t = ((timeStamp/1000.0) - data->t) / data->lifeSpan; + if (m_stasis.contains(item)) { + data->t += dt;//Stasis effect + continue; + } + if (t >= 1.0){//Usually happens from load + m_deletables << item; + data->delegate = 0; + m_activeCount--; + }else{//Fade + if (m_fade){ + qreal o = 1.; + if (t<0.2) + o = t*5; + if (t>0.8) + o = (1-t)*5; + item->setOpacity(o); + }else{ + item->setOpacity(1.);//###Without fade, it's just a binary toggle - if we turn it off we have to turn it back on + } } + item->setX(data->curX() - item->width()/2 - m_systemOffset.x()); + item->setY(data->curY() - item->height()/2 - m_systemOffset.y()); } - item->setX(data->curX() - item->width()/2 - m_systemOffset.x()); - item->setY(data->curY() - item->height()/2 - m_systemOffset.y()); } } diff --git a/src/declarative/particles/qsgitemparticle_p.h b/src/declarative/particles/qsgitemparticle_p.h index 24bbcf97d8..233f942d40 100644 --- a/src/declarative/particles/qsgitemparticle_p.h +++ b/src/declarative/particles/qsgitemparticle_p.h @@ -82,7 +82,7 @@ public slots: void take(QSGItem* item,bool prioritize=false);//take by modelparticle void give(QSGItem* item);//give from modelparticle - void setFade(bool arg){if(arg == m_fade) return; m_fade = arg; emit fadeChanged();} + void setFade(bool arg){if (arg == m_fade) return; m_fade = arg; emit fadeChanged();} void setDelegate(QDeclarativeComponent* arg) { if (m_delegate != arg) { @@ -93,14 +93,14 @@ public slots: protected: virtual void reset(); - virtual void reload(int idx); - virtual void initialize(int idx); + virtual void commit(int gIdx, int pIdx); + virtual void initialize(int gIdx, int pIdx); void prepareNextFrame(); private slots: void tick(); private: QList<QSGItem* > m_deletables; - QList< int > m_loadables; + QList< QSGParticleData* > m_loadables; bool m_fade; QList<QSGItem*> m_pendingItems; diff --git a/src/declarative/particles/qsgkill.cpp b/src/declarative/particles/qsgkill.cpp index eb3a55d402..e0406bc12b 100644 --- a/src/declarative/particles/qsgkill.cpp +++ b/src/declarative/particles/qsgkill.cpp @@ -51,9 +51,10 @@ QSGKillAffector::QSGKillAffector(QSGItem *parent) : bool QSGKillAffector::affectParticle(QSGParticleData *d, qreal dt) { Q_UNUSED(dt); - if(d->stillAlive()){ + if (d->stillAlive()){ d->t -= d->lifeSpan + 1; return true; } + return false; } QT_END_NAMESPACE diff --git a/src/declarative/particles/qsglineextruder.cpp b/src/declarative/particles/qsglineextruder.cpp index f32b01402a..d5f32612b1 100644 --- a/src/declarative/particles/qsglineextruder.cpp +++ b/src/declarative/particles/qsglineextruder.cpp @@ -49,16 +49,16 @@ QSGLineExtruder::QSGLineExtruder(QObject *parent) : QPointF QSGLineExtruder::extrude(const QRectF &r) { qreal x,y; - if(!r.height()){ + if (!r.height()){ x = r.width() * ((qreal)rand())/RAND_MAX; y = 0; }else{ y = r.height() * ((qreal)rand())/RAND_MAX; - if(!r.width()){ + if (!r.width()){ x = 0; }else{ x = r.width()/r.height() * y; - if(m_mirrored) + if (m_mirrored) x = r.width() - x; } } diff --git a/src/declarative/particles/qsgmaskextruder.cpp b/src/declarative/particles/qsgmaskextruder.cpp index d9a463910d..5e60d31bcf 100644 --- a/src/declarative/particles/qsgmaskextruder.cpp +++ b/src/declarative/particles/qsgmaskextruder.cpp @@ -53,7 +53,7 @@ QSGMaskExtruder::QSGMaskExtruder(QObject *parent) : QPointF QSGMaskExtruder::extrude(const QRectF &r) { ensureInitialized(r); - if(!m_mask.count() || m_img.isNull()) + if (!m_mask.count() || m_img.isNull()) return r.topLeft(); const QPointF p = m_mask[rand() % m_mask.count()]; //### Should random sub-pixel positioning be added? @@ -63,7 +63,7 @@ QPointF QSGMaskExtruder::extrude(const QRectF &r) bool QSGMaskExtruder::contains(const QRectF &bounds, const QPointF &point) { ensureInitialized(bounds);//###Current usage patterns WILL lead to different bounds/r calls. Separate list? - if(m_img.isNull()) + if (m_img.isNull()) return false; QPoint p = point.toPoint() - bounds.topLeft().toPoint(); return m_img.rect().contains(p) && (bool)m_img.pixelIndex(p); @@ -71,26 +71,26 @@ bool QSGMaskExtruder::contains(const QRectF &bounds, const QPointF &point) void QSGMaskExtruder::ensureInitialized(const QRectF &r) { - if(m_lastWidth == r.width() && m_lastHeight == r.height()) + if (m_lastWidth == r.width() && m_lastHeight == r.height()) return;//Same as before m_lastWidth = r.width(); m_lastHeight = r.height(); m_img = QImage(); m_mask.clear(); - if(m_source.isEmpty()) + if (m_source.isEmpty()) return; m_img = QImage(m_source.toLocalFile()); - if(m_img.isNull()){ + if (m_img.isNull()){ qWarning() << "MaskShape: Cannot load" << qPrintable(m_source.toLocalFile()); return; } m_img = m_img.createAlphaMask(); m_img = m_img.convertToFormat(QImage::Format_Mono);//Else LSB, but I think that's easier m_img = m_img.scaled(r.size().toSize());//TODO: Do they need aspect ratio stuff? Or tiling? - for(int i=0; i<r.width(); i++){ - for(int j=0; j<r.height(); j++){ - if(m_img.pixelIndex(i,j))//Direct bit manipulation is presumably more efficient + for (int i=0; i<r.width(); i++){ + for (int j=0; j<r.height(); j++){ + if (m_img.pixelIndex(i,j))//Direct bit manipulation is presumably more efficient m_mask << QPointF(i,j); } } diff --git a/src/declarative/particles/qsgmodelparticle.cpp b/src/declarative/particles/qsgmodelparticle.cpp index 47b8844ad5..be10f33255 100644 --- a/src/declarative/particles/qsgmodelparticle.cpp +++ b/src/declarative/particles/qsgmodelparticle.cpp @@ -66,21 +66,21 @@ QVariant QSGModelParticle::model() const void QSGModelParticle::setModel(const QVariant &arg) { - if(arg == m_dataSource) + if (arg == m_dataSource) return; m_dataSource = arg; - if(qobject_cast<QSGVisualDataModel*>(arg.value<QObject*>())) { - if(m_ownModel && m_model) + if (qobject_cast<QSGVisualDataModel*>(arg.value<QObject*>())) { + if (m_ownModel && m_model) delete m_model; m_model = qobject_cast<QSGVisualDataModel*>(arg.value<QObject*>()); m_ownModel = false; }else{ - if(!m_model || !m_ownModel) + if (!m_model || !m_ownModel) m_model = new QSGVisualDataModel(qmlContext(this)); m_model->setModel(m_dataSource); m_ownModel = true; } - if(m_comp) + if (m_comp) m_model->setDelegate(m_comp); emit modelChanged(); emit modelCountChanged(); @@ -94,19 +94,19 @@ void QSGModelParticle::setModel(const QVariant &arg) void QSGModelParticle::updateCount() { int newCount = 0; - if(m_model) + if (m_model) newCount = m_model->count(); - if(newCount < 0) + if (newCount < 0) return;//WTF? - if(m_modelCount == 0 || newCount == 0){ + if (m_modelCount == 0 || newCount == 0){ m_available.clear(); - for(int i=0; i<newCount; i++) + for (int i=0; i<newCount; i++) m_available << i; - }else if(newCount < m_modelCount){ - for(int i=newCount; i<m_modelCount; i++) //existing ones must leave normally, but aren't readded + }else if (newCount < m_modelCount){ + for (int i=newCount; i<m_modelCount; i++) //existing ones must leave normally, but aren't readded m_available.removeAll(i); - }else if(newCount > m_modelCount){ - for(int i=m_modelCount; i<newCount; i++) + }else if (newCount > m_modelCount){ + for (int i=m_modelCount; i<newCount; i++) m_available << i; } m_modelCount = newCount; @@ -114,7 +114,7 @@ void QSGModelParticle::updateCount() QDeclarativeComponent *QSGModelParticle::delegate() const { - if(m_model) + if (m_model) return m_model->delegate(); return 0; } @@ -125,14 +125,14 @@ void QSGModelParticle::setDelegate(QDeclarativeComponent *comp) if (comp == dataModel->delegate()) return; m_comp = comp; - if(m_model) + if (m_model) m_model->setDelegate(comp); emit delegateChanged(); } int QSGModelParticle::modelCount() const { - if(m_model) + if (m_model) const_cast<QSGModelParticle*>(this)->updateCount();//TODO: Investigate why this doesn't get called properly return m_modelCount; } @@ -149,53 +149,54 @@ void QSGModelParticle::unfreeze(QSGItem* item) m_stasis.remove(item); } -void QSGModelParticle::initialize(int idx) +void QSGModelParticle::initialize(int gIdx, int pIdx) { - if(!m_model || !m_model->count()) + if (!m_model || !m_model->count()) return; - if(m_available.isEmpty()) + if (m_available.isEmpty()) return; - m_requests << idx; + m_requests << m_system->m_groupData[gIdx]->data[pIdx]; m_activeCount++; } void QSGModelParticle::processPending() {//can't create/delete arbitrary items in the render thread - foreach(QSGItem* item, m_deletables){ + foreach (QSGItem* item, m_deletables){ item->setOpacity(0.); m_model->release(item); } m_deletables.clear(); - foreach(int pos, m_requests){ - if(m_data[pos]->delegate){ - if(m_stasis.contains(m_data[pos]->delegate)) + foreach (QSGParticleData* datum, m_requests){ + if (datum->delegate){ + if (m_stasis.contains(datum->delegate)) qWarning() << "Current model particles prefers overwrite:false"; //remove old item from the particle that is dying to make room for this one - m_deletables << m_data[pos]->delegate; - m_available << m_data[pos]->modelIndex; - m_data[pos]->modelIndex = -1; - m_data[pos]->delegate = 0; - m_data[pos] = 0; + m_deletables << datum->delegate; + m_available << datum->modelIndex; + datum->modelIndex = -1; + datum->delegate = 0; + datum = 0; m_activeCount--; } - if(!m_available.isEmpty()){ - m_data[pos]->delegate = m_model->item(m_available.first()); - m_data[pos]->modelIndex = m_available.first(); + if (!m_available.isEmpty()){ + datum->delegate = m_model->item(m_available.first()); + datum->modelIndex = m_available.first(); m_available.pop_front(); - QSGModelParticleAttached* mpa = qobject_cast<QSGModelParticleAttached*>(qmlAttachedPropertiesObject<QSGModelParticle>(m_data[pos]->delegate)); - if(mpa){ + QSGModelParticleAttached* mpa = qobject_cast<QSGModelParticleAttached*>(qmlAttachedPropertiesObject<QSGModelParticle>(datum->delegate)); + if (mpa){ mpa->m_mp = this; mpa->attach(); } - m_data[pos]->delegate->setParentItem(this); + datum->delegate->setParentItem(this); + datum->delegate->setOpacity(0.0); } } m_requests.clear(); } -void QSGModelParticle::reload(int idx) +void QSGModelParticle::commit(int gIdx, int pIdx) { //No-op unless we start copying the data. } @@ -212,14 +213,14 @@ void QSGModelParticle::reset() QSGNode* QSGModelParticle::updatePaintNode(QSGNode* n, UpdatePaintNodeData* d) { //Dummy update just to get painting tick - if(m_pleaseReset){ + if (m_pleaseReset){ m_pleaseReset = false; reset(); } prepareNextFrame(); update();//Get called again - if(n) + if (n) n->markDirty(QSGNode::DirtyMaterial); return QSGItem::updatePaintNode(n,d); } @@ -230,40 +231,45 @@ void QSGModelParticle::prepareNextFrame() qreal curT = timeStamp/1000.0; qreal dt = curT - m_lastT; m_lastT = curT; - if(!m_activeCount) + if (!m_activeCount) return; //TODO: Size, better fade? - for(int i=0; i<count(); i++){ - QSGParticleData* data = m_data[i]; - if(!data->delegate) - continue; - qreal t = ((timeStamp/1000.0) - data->t) / data->lifeSpan; - if(m_stasis.contains(m_data[i]->delegate)) { - m_data[i]->t += dt;//Stasis effect - continue; - } - if(t >= 1.0){//Usually happens from load - m_available << m_data[i]->modelIndex; - m_deletables << m_data[i]->delegate; - m_data[i]->modelIndex = -1; - m_data[i]->delegate = 0; - m_data[i] = 0; - m_activeCount--; - }else{//Fade - if(m_fade){ - qreal o = 1.; - if(t<0.2) - o = t*5; - if(t>0.8) - o = (1-t)*5; - m_data[i]->delegate->setOpacity(o); - }else{ - m_data[i]->delegate->setOpacity(1.);//###Without fade, it's just a binary toggle - if we turn it off we have to turn it back on + foreach (const QString &str, m_particles){ + int gIdx = m_system->m_groupIds[str]; + int count = m_system->m_groupData[gIdx]->size(); + + for (int i=0; i<count; i++){ + QSGParticleData* data = m_system->m_groupData[gIdx]->data[i]; + if (!data || !data->delegate) + continue; + qreal t = ((timeStamp/1000.0) - data->t) / data->lifeSpan; + if (m_stasis.contains(data->delegate)) { + data->t += dt;//Stasis effect + continue; + } + if (t >= 1.0){//Usually happens from load + m_available << data->modelIndex; + m_deletables << data->delegate; + data->modelIndex = -1; + data->delegate = 0; + m_activeCount--; + continue; + }else{//Fade + if (m_fade){ + qreal o = 1.; + if (t<0.2) + o = t*5; + if (t>0.8) + o = (1-t)*5; + data->delegate->setOpacity(o); + }else{ + data->delegate->setOpacity(1.);//###Without fade, it's just a binary toggle - if we turn it off we have to turn it back on + } } + data->delegate->setX(data->curX() - data->delegate->width()/2 - m_systemOffset.x()); + data->delegate->setY(data->curY() - data->delegate->height()/2 - m_systemOffset.y()); } - m_data[i]->delegate->setX(data->curX() - m_data[i]->delegate->width()/2 - m_systemOffset.x()); - m_data[i]->delegate->setY(data->curY() - m_data[i]->delegate->height()/2 - m_systemOffset.y()); } } diff --git a/src/declarative/particles/qsgmodelparticle_p.h b/src/declarative/particles/qsgmodelparticle_p.h index 31e4025bb4..aba64e4841 100644 --- a/src/declarative/particles/qsgmodelparticle_p.h +++ b/src/declarative/particles/qsgmodelparticle_p.h @@ -86,11 +86,11 @@ public slots: void freeze(QSGItem* item); void unfreeze(QSGItem* item); - void setFade(bool arg){if(arg == m_fade) return; m_fade = arg; emit fadeChanged();} + void setFade(bool arg){if (arg == m_fade) return; m_fade = arg; emit fadeChanged();} protected: virtual void reset(); - virtual void reload(int idx); - virtual void initialize(int idx); + virtual void commit(int gIdx, int pIdx); + virtual void initialize(int gIdx, int pIdx); void prepareNextFrame(); private slots: void updateCount(); @@ -101,7 +101,7 @@ private: QSGVisualDataModel *m_model; QVariant m_dataSource; QList<QSGItem*> m_deletables; - QList< int > m_requests; + QList< QSGParticleData* > m_requests; bool m_fade; QList<QSGItem*> m_pendingItems; @@ -115,7 +115,7 @@ private: class QSGModelParticleAttached : public QObject { Q_OBJECT - Q_PROPERTY(QSGModelParticle* particle READ particle CONSTANT); + Q_PROPERTY(QSGModelParticle* particle READ particle CONSTANT) public: QSGModelParticleAttached(QObject* parent) : QObject(parent), m_mp(0) diff --git a/src/declarative/particles/qsgparticleaffector.cpp b/src/declarative/particles/qsgparticleaffector.cpp index 5b0936cc40..ccb12659fb 100644 --- a/src/declarative/particles/qsgparticleaffector.cpp +++ b/src/declarative/particles/qsgparticleaffector.cpp @@ -56,44 +56,45 @@ QSGParticleAffector::QSGParticleAffector(QSGItem *parent) : void QSGParticleAffector::componentComplete() { - if(!m_system && qobject_cast<QSGParticleSystem*>(parentItem())) + if (!m_system && qobject_cast<QSGParticleSystem*>(parentItem())) setSystem(qobject_cast<QSGParticleSystem*>(parentItem())); - if(!m_system) + if (!m_system) qWarning() << "Affector created without a particle system specified";//TODO: useful QML warnings, like line number? QSGItem::componentComplete(); } void QSGParticleAffector::affectSystem(qreal dt) { - if(!m_active) + if (!m_active) return; - if(!m_system){ - qDebug() << "No system" << this; - return; - } //If not reimplemented, calls affect particle per particle //But only on particles in targeted system/area - if(m_updateIntSet){ + if (m_updateIntSet){ m_groups.clear(); - foreach(const QString &p, m_particles) + foreach (const QString &p, m_particles) m_groups << m_system->m_groupIds[p];//###Can this occur before group ids are properly assigned? m_updateIntSet = false; } - foreach(QSGParticleGroupData* gd, m_system->m_groupData){ - foreach(QSGParticleData* d, gd->data){ - if(!d || (m_onceOff && m_onceOffed.contains(qMakePair(d->group, d->index)))) + foreach (QSGParticleGroupData* gd, m_system->m_groupData){ + foreach (QSGParticleData* d, gd->data){ + if (!d) continue; - if(m_groups.isEmpty() || m_groups.contains(d->group)){ + if (m_groups.isEmpty() || m_groups.contains(d->group)){ + if ((m_onceOff && m_onceOffed.contains(qMakePair(d->group, d->index))) + || !d->stillAlive()) + continue; //Need to have previous location for affected. if signal || shape might be faster? QPointF curPos = QPointF(d->curX(), d->curY()); - if(width() == 0 || height() == 0 + if (width() == 0 || height() == 0 || m_shape->contains(QRectF(m_offset.x(), m_offset.y(), width(), height()),curPos)){ - if(affectParticle(d, dt)){ - m_system->m_needsReset << d; - if(m_onceOff) - m_onceOffed << qMakePair(d->group, d->index); - if(m_signal) - emit affected(curPos.x(), curPos.y()); + if (m_collisionParticles.isEmpty() || isColliding(d)){ + if (affectParticle(d, dt)){ + m_system->m_needsReset << d; + if (m_onceOff) + m_onceOffed << qMakePair(d->group, d->index); + if (m_signal) + emit affected(curPos.x(), curPos.y()); + } } } } @@ -105,19 +106,42 @@ bool QSGParticleAffector::affectParticle(QSGParticleData *d, qreal dt) { Q_UNUSED(d); Q_UNUSED(dt); - return false; + return m_signal;//If signalling, then we always 'null affect' it. } void QSGParticleAffector::reset(QSGParticleData* pd) {//TODO: This, among other ones, should be restructured so they don't all need to remember to call the superclass - if(m_onceOff) - m_onceOffed.remove(qMakePair(pd->group, pd->index)); + if (m_onceOff) + if (m_groups.isEmpty() || m_groups.contains(pd->group)) + m_onceOffed.remove(qMakePair(pd->group, pd->index)); } void QSGParticleAffector::updateOffsets() { - if(m_system) + if (m_system) m_offset = m_system->mapFromItem(this, QPointF(0, 0)); } +bool QSGParticleAffector::isColliding(QSGParticleData *d) +{ + qreal myCurX = d->curX(); + qreal myCurY = d->curY(); + qreal myCurSize = d->curSize()/2; + foreach (const QString &group, m_collisionParticles){ + foreach (QSGParticleData* other, m_system->m_groupData[m_system->m_groupIds[group]]->data){ + if (!other->stillAlive()) + continue; + qreal otherCurX = other->curX(); + qreal otherCurY = other->curY(); + qreal otherCurSize = other->curSize()/2; + if ((myCurX + myCurSize > otherCurX - otherCurSize + && myCurX - myCurSize < otherCurX + otherCurSize) + && (myCurY + myCurSize > otherCurY - otherCurSize + && myCurY - myCurSize < otherCurY + otherCurSize)) + return true; + } + } + return false; +} + QT_END_NAMESPACE diff --git a/src/declarative/particles/qsgparticleaffector_p.h b/src/declarative/particles/qsgparticleaffector_p.h index 7418760941..806a1cce1a 100644 --- a/src/declarative/particles/qsgparticleaffector_p.h +++ b/src/declarative/particles/qsgparticleaffector_p.h @@ -57,10 +57,11 @@ class QSGParticleAffector : public QSGItem Q_OBJECT Q_PROPERTY(QSGParticleSystem* system READ system WRITE setSystem NOTIFY systemChanged) Q_PROPERTY(QStringList particles READ particles WRITE setParticles NOTIFY particlesChanged) + Q_PROPERTY(QStringList collisionParticles READ collisionParticles WRITE setCollisionParticles NOTIFY collisionParticlesChanged) Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged) Q_PROPERTY(bool onceOff READ onceOff WRITE setOnceOff NOTIFY onceOffChanged) Q_PROPERTY(QSGParticleExtruder* shape READ shape WRITE setShape NOTIFY shapeChanged) - Q_PROPERTY(bool signal READ signal WRITE setSignal NOTIFY signalChanged) + Q_PROPERTY(bool signal READ signal WRITE setSignal NOTIFY signalChanged)//TODO: Determine by whether it's connected public: explicit QSGParticleAffector(QSGItem *parent = 0); @@ -96,6 +97,11 @@ public: return m_signal; } + QStringList collisionParticles() const + { + return m_collisionParticles; + } + signals: void systemChanged(QSGParticleSystem* arg); @@ -111,6 +117,8 @@ signals: void affected(qreal x, qreal y); void signalChanged(bool arg); + void collisionParticlesChanged(QStringList arg); + public slots: void setSystem(QSGParticleSystem* arg) { @@ -142,6 +150,7 @@ void setOnceOff(bool arg) { if (m_onceOff != arg) { m_onceOff = arg; + m_needsReset = true; emit onceOffChanged(arg); } } @@ -162,6 +171,14 @@ void setSignal(bool arg) } } +void setCollisionParticles(QStringList arg) +{ + if (m_collisionParticles != arg) { + m_collisionParticles = arg; + emit collisionParticlesChanged(arg); + } +} + protected: friend class QSGParticleSystem; virtual bool affectParticle(QSGParticleData *d, qreal dt); @@ -183,6 +200,9 @@ private: bool m_signal; + QStringList m_collisionParticles; + + bool isColliding(QSGParticleData* d); private slots: void updateOffsets(); }; diff --git a/src/declarative/particles/qsgparticleemitter.cpp b/src/declarative/particles/qsgparticleemitter.cpp index 143338f798..a75d6388ed 100644 --- a/src/declarative/particles/qsgparticleemitter.cpp +++ b/src/declarative/particles/qsgparticleemitter.cpp @@ -70,15 +70,15 @@ QSGParticleEmitter::QSGParticleEmitter(QSGItem *parent) : QSGParticleEmitter::~QSGParticleEmitter() { - if(m_defaultExtruder) + if (m_defaultExtruder) delete m_defaultExtruder; } void QSGParticleEmitter::componentComplete() { - if(!m_system && qobject_cast<QSGParticleSystem*>(parentItem())) + if (!m_system && qobject_cast<QSGParticleSystem*>(parentItem())) setSystem(qobject_cast<QSGParticleSystem*>(parentItem())); - if(!m_system) + if (!m_system) qWarning() << "Emitter created without a particle system specified";//TODO: useful QML warnings, like line number? QSGItem::componentComplete(); } @@ -99,31 +99,31 @@ void QSGParticleEmitter::setEmitting(bool arg) QSGParticleExtruder* QSGParticleEmitter::effectiveExtruder() { - if(m_extruder) + if (m_extruder) return m_extruder; - if(!m_defaultExtruder) + if (!m_defaultExtruder) m_defaultExtruder = new QSGParticleExtruder; return m_defaultExtruder; } void QSGParticleEmitter::pulse(qreal seconds) { - if(!particleCount()) + if (!particleCount()) qWarning() << "pulse called on an emitter with a particle count of zero"; - if(!m_emitting) + if (!m_emitting) m_burstLeft = seconds*1000.0;//TODO: Change name to match } void QSGParticleEmitter::burst(int num) { - if(!particleCount()) + if (!particleCount()) qWarning() << "burst called on an emitter with a particle count of zero"; m_burstQueue << qMakePair(num, QPointF(x(), y())); } void QSGParticleEmitter::burst(int num, qreal x, qreal y) { - if(!particleCount()) + if (!particleCount()) qWarning() << "burst called on an emitter with a particle count of zero"; m_burstQueue << qMakePair(num, QPointF(x, y)); } @@ -131,12 +131,12 @@ void QSGParticleEmitter::burst(int num, qreal x, qreal y) void QSGParticleEmitter::setMaxParticleCount(int arg) { if (m_maxParticleCount != arg) { - if(arg < 0 && m_maxParticleCount >= 0){ + if (arg < 0 && m_maxParticleCount >= 0){ connect(this, SIGNAL(particlesPerSecondChanged(qreal)), this, SIGNAL(particleCountChanged())); connect(this, SIGNAL(particleDurationChanged(int)), this, SIGNAL(particleCountChanged())); - }else if(arg >= 0 && m_maxParticleCount < 0){ + }else if (arg >= 0 && m_maxParticleCount < 0){ disconnect(this, SIGNAL(particlesPerSecondChanged(qreal)), this, SIGNAL(particleCountChanged())); disconnect(this, SIGNAL(particleDurationChanged(int)), @@ -149,7 +149,7 @@ void QSGParticleEmitter::setMaxParticleCount(int arg) int QSGParticleEmitter::particleCount() const { - if(m_maxParticleCount >= 0) + if (m_maxParticleCount >= 0) return m_maxParticleCount; return m_particlesPerSecond*((m_particleDuration+m_particleDurationVariation)/1000.0); } diff --git a/src/declarative/particles/qsgparticleextruder.cpp b/src/declarative/particles/qsgparticleextruder.cpp index 91b968c8af..f0658f67e5 100644 --- a/src/declarative/particles/qsgparticleextruder.cpp +++ b/src/declarative/particles/qsgparticleextruder.cpp @@ -50,11 +50,11 @@ QSGParticleExtruder::QSGParticleExtruder(QObject *parent) : QPointF QSGParticleExtruder::extrude(const QRectF &rect) { - if(m_fill) + if (m_fill) return QPointF(((qreal)rand() / RAND_MAX) * rect.width() + rect.x(), ((qreal)rand() / RAND_MAX) * rect.height() + rect.y()); int side = rand() % 4; - switch(side){//TODO: Doesn't this overlap the corners? + switch (side){//TODO: Doesn't this overlap the corners? case 0: return QPointF(rect.x(), ((qreal)rand() / RAND_MAX) * rect.height() + rect.y()); diff --git a/src/declarative/particles/qsgparticlepainter.cpp b/src/declarative/particles/qsgparticlepainter.cpp index 34313bd4fb..a7a05ed631 100644 --- a/src/declarative/particles/qsgparticlepainter.cpp +++ b/src/declarative/particles/qsgparticlepainter.cpp @@ -44,8 +44,10 @@ QT_BEGIN_NAMESPACE QSGParticlePainter::QSGParticlePainter(QSGItem *parent) : QSGItem(parent), - m_system(0), m_count(0), m_lastStart(0), m_sentinel(new QSGParticleData) + m_system(0), m_count(0), m_sentinel(new QSGParticleData(0)) { + connect(this, SIGNAL(parentChanged(QSGItem*)), + this, SLOT(calcSystemOffset())); connect(this, SIGNAL(xChanged()), this, SLOT(calcSystemOffset())); connect(this, SIGNAL(yChanged()), @@ -54,9 +56,9 @@ QSGParticlePainter::QSGParticlePainter(QSGItem *parent) : void QSGParticlePainter::componentComplete() { - if(!m_system && qobject_cast<QSGParticleSystem*>(parentItem())) + if (!m_system && qobject_cast<QSGParticleSystem*>(parentItem())) setSystem(qobject_cast<QSGParticleSystem*>(parentItem())); - if(!m_system) + if (!m_system) qWarning() << "ParticlePainter created without a particle system specified";//TODO: useful QML warnings, like line number? QSGItem::componentComplete(); } @@ -66,7 +68,7 @@ void QSGParticlePainter::setSystem(QSGParticleSystem *arg) { if (m_system != arg) { m_system = arg; - if(m_system){ + if (m_system){ m_system->registerParticlePainter(this); connect(m_system, SIGNAL(xChanged()), this, SLOT(calcSystemOffset())); @@ -80,80 +82,28 @@ void QSGParticlePainter::setSystem(QSGParticleSystem *arg) void QSGParticlePainter::load(QSGParticleData* d) { - int idx = particleTypeIndex(d); - m_data[idx] = d; - initialize(idx); - reload(idx); + initialize(d->group, d->index); + commit(d->group, d->index); } void QSGParticlePainter::reload(QSGParticleData* d) { - reload(particleTypeIndex(d)); + commit(d->group, d->index); } void QSGParticlePainter::reset() { - //Have to every time because what it's emitting may have changed and that affects particleTypeIndex - if(m_system && !m_inResize) - resize(0,1);//###Fix this by making resize take sensible arguments - //###This also means double resets. Make reset not virtual? + calcSystemOffset(true);//In case an ancestor changed in some way } -void QSGParticlePainter::resize(int oldSize, int newSize) -{ - if(newSize == oldSize)//TODO: What if particles switched so indices change but total count is the same? - return; - - QHash<int, QPair<int, int> > oldStarts(m_particleStarts); - //Update particle starts datastore - m_particleStarts.clear(); - m_lastStart = 0; - QList<int> particleList; - if(m_particles.isEmpty()) - particleList << 0; - foreach(const QString &s, m_particles) - particleList << m_system->m_groupIds[s]; - foreach(int gIdx, particleList){ - QSGParticleGroupData *gd = m_system->m_groupData[gIdx]; - m_particleStarts.insert(gIdx, qMakePair<int, int>(gd->size, m_lastStart)); - m_lastStart += gd->size; - } - - //Shuffle stuff around - //TODO: In place shuffling because it's faster - QVector<QSGParticleData*> oldData(m_data); - QVector<QObject*> oldAttached(m_attachedData); - m_data.clear(); - m_data.resize(m_count); - m_attachedData.resize(m_count); - foreach(int gIdx, particleList){ - QSGParticleGroupData *gd = m_system->m_groupData[gIdx]; - for(int i=0; i<gd->data.size(); i++){//TODO: When group didn't exist before - int newIdx = m_particleStarts[gIdx].second + i; - int oldIdx = oldStarts[gIdx].second + i; - if(i >= oldStarts[gIdx].first || oldData.size() <= oldIdx){ - m_data[newIdx] = m_sentinel; - }else{ - m_data[newIdx] = oldData[oldIdx]; - m_attachedData[newIdx] = oldAttached[oldIdx]; - } - } - } - m_inResize = true; - reset(); - m_inResize = false; -} - - -void QSGParticlePainter::setCount(int c) +void QSGParticlePainter::setCount(int c)//### TODO: some resizeing so that particles can reallocate on size change instead of recreate { Q_ASSERT(c >= 0); //XXX - if(c == m_count) + if (c == m_count) return; - int lastCount = m_count; m_count = c; - resize(lastCount, m_count); emit countChanged(); + reset(); } int QSGParticlePainter::count() @@ -161,26 +111,17 @@ int QSGParticlePainter::count() return m_count; } -int QSGParticlePainter::particleTypeIndex(QSGParticleData* d) -{ - Q_ASSERT(d && m_particleStarts.contains(d->group));//XXX - int ret = m_particleStarts[d->group].second + d->index; - Q_ASSERT(ret >=0 && ret < m_count);//XXX:shouldn't assert, but bugs here were hard to find in the past - return ret; -} - - -void QSGParticlePainter::calcSystemOffset() +void QSGParticlePainter::calcSystemOffset(bool resetPending) { if (!m_system || !parentItem()) return; QPointF lastOffset = m_systemOffset; - m_systemOffset = -1 * this->mapFromItem(m_system, QPointF()); - if(lastOffset != m_systemOffset){ + m_systemOffset = -1 * this->mapFromItem(m_system, QPointF(0.0, 0.0)); + if (lastOffset != m_systemOffset && !resetPending){ //Reload all particles//TODO: Necessary? - foreach(const QString &g, m_particles){ + foreach (const QString &g, m_particles){ int gId = m_system->m_groupIds[g]; - foreach(QSGParticleData* d, m_system->m_groupData[gId]->data) + foreach (QSGParticleData* d, m_system->m_groupData[gId]->data) reload(d); } } diff --git a/src/declarative/particles/qsgparticlepainter_p.h b/src/declarative/particles/qsgparticlepainter_p.h index e506018f53..49f7881a0a 100644 --- a/src/declarative/particles/qsgparticlepainter_p.h +++ b/src/declarative/particles/qsgparticlepainter_p.h @@ -94,7 +94,7 @@ void setParticles(QStringList arg) } } private slots: - void calcSystemOffset(); + void calcSystemOffset(bool resetPending = false); protected: /* Reset resets all your internal data structures. But anything attached to a particle should @@ -104,11 +104,15 @@ protected: virtual void reset(); virtual void componentComplete(); - //Data interface to painters - QVector<QSGParticleData*> m_data; //Actually stored in arbitrary order, - QVector<QObject*> m_attachedData; //This data will be moved along with m_data in resizes (but you own it) - virtual void initialize(int){} - virtual void reload(int){}//If you need to do something on size changed, check m_data size in this? Or we reset you every time? + virtual void initialize(int gIdx, int pIdx){ + Q_UNUSED(gIdx); + Q_UNUSED(pIdx); + } + virtual void commit(int gIdx, int pIdx){ + //###If you need to do something on size changed, check m_data size in this? Or we reset you every time? + Q_UNUSED(gIdx); + Q_UNUSED(pIdx); + } QSGParticleSystem* m_system; friend class QSGParticleSystem; @@ -117,16 +121,9 @@ protected: QStringList m_particles; QPointF m_systemOffset; - private: - int m_lastStart; - QHash<int, QPair<int, int> > m_particleStarts; - int particleTypeIndex(QSGParticleData* d);//Now private - void resize(int, int); - QSGParticleData* m_sentinel; //QVector<QSGParticleData*> m_shadowData;//For when we implement overwrite: false - bool m_inResize; }; QT_END_NAMESPACE diff --git a/src/declarative/particles/qsgparticlesmodule.cpp b/src/declarative/particles/qsgparticlesmodule.cpp index a7a9a9253f..e736e6fd8b 100644 --- a/src/declarative/particles/qsgparticlesmodule.cpp +++ b/src/declarative/particles/qsgparticlesmodule.cpp @@ -65,6 +65,8 @@ #include "qsgtargeteddirection_p.h" #include "qsgturbulence_p.h" #include "qsgwander_p.h" +#include "qsgtargetaffector_p.h" +#include "qsgcumulativedirection_p.h" QT_BEGIN_NAMESPACE @@ -95,8 +97,9 @@ void QSGParticlesModule::defineModule() qmlRegisterType<QSGPointDirection>(uri, 2, 0, "PointDirection"); qmlRegisterType<QSGAngledDirection>(uri, 2, 0, "AngledDirection"); qmlRegisterType<QSGTargetedDirection>(uri, 2, 0, "TargetedDirection"); + qmlRegisterType<QSGCumulativeDirection>(uri, 2, 0, "CumulativeDirection"); - qmlRegisterType<QSGParticleAffector>(uri, 2, 0, "ParticleAffector");//if it has a triggered signal, it's useful + qmlRegisterType<QSGParticleAffector>(uri, 2, 0, "Affector");//for the triggered signal qmlRegisterType<QSGWanderAffector>(uri, 2, 0, "Wander"); qmlRegisterType<QSGFrictionAffector>(uri, 2, 0, "Friction"); qmlRegisterType<QSGPointAttractorAffector>(uri, 2, 0, "PointAttractor"); @@ -104,8 +107,8 @@ void QSGParticlesModule::defineModule() qmlRegisterType<QSGKillAffector>(uri, 2, 0, "Kill"); qmlRegisterType<QSGSpriteGoalAffector>(uri, 2, 0, "SpriteGoal"); qmlRegisterType<QSGTurbulenceAffector>(uri, 2, 0 , "Turbulence"); + qmlRegisterType<QSGTargetAffector>(uri, 2, 0 , "Target"); } QT_END_NAMESPACE -//Q_EXPORT_PLUGIN2(Particles, QT_PREPEND_NAMESPACE(ParticlesModule)) diff --git a/src/declarative/particles/qsgparticlesystem.cpp b/src/declarative/particles/qsgparticlesystem.cpp index 2dc21299cf..d648d46330 100644 --- a/src/declarative/particles/qsgparticlesystem.cpp +++ b/src/declarative/particles/qsgparticlesystem.cpp @@ -44,19 +44,232 @@ #include "qsgparticleemitter_p.h" #include "qsgparticleaffector_p.h" #include "qsgparticlepainter_p.h" +#include "qsgspriteengine_p.h" +#include "qsgsprite_p.h" + +#include "qsgfollowemitter_p.h"//###For auto-follow on states, perhaps should be in emitter? #include <cmath> #include <QDebug> QT_BEGIN_NAMESPACE -QSGParticleData::QSGParticleData() +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); +} + +QSGParticleDataHeap::QSGParticleDataHeap() + : m_data(0) +{ + m_data.reserve(1000); + clear(); +} + +void QSGParticleDataHeap::grow() //###Consider automatic growth vs resize() calls from GroupData +{ + m_data.resize(1 << ++m_size); +} + +void QSGParticleDataHeap::insert(QSGParticleData* data) +{ + int time = roundedTime(data->t + data->lifeSpan); + 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 QSGParticleDataHeap::top() +{ + if (m_end == 0) + return 1e24; + return m_data[0].time; +} + +QSet<QSGParticleData*> QSGParticleDataHeap::pop() +{ + if (!m_end) + return QSet<QSGParticleData*> (); + QSet<QSGParticleData*> 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 QSGParticleDataHeap::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 QSGParticleDataHeap::contains(QSGParticleData* d) +{ + for (int i=0; i<m_end; i++) + if (m_data[i].data.contains(d)) + return true; + return false; +} + +void QSGParticleDataHeap::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 QSGParticleDataHeap::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 QSGParticleDataHeap::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); + } +} + +QSGParticleGroupData::QSGParticleGroupData(int id, QSGParticleSystem* sys):index(id),m_size(0),m_system(sys) +{ + initList(); +} + +QSGParticleGroupData::~QSGParticleGroupData() +{ + foreach (QSGParticleData* d, data) + delete d; +} + +int QSGParticleGroupData::size() +{ + return m_size; +} + +QString QSGParticleGroupData::name()//### Worth caching as well? +{ + return m_system->m_groupIds.key(index); +} + +void QSGParticleGroupData::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 QSGParticleData(m_system); + data[i]->group = index; + data[i]->index = i; + reusableIndexes << i; + } + int delta = newSize - m_size; + m_size = newSize; + foreach (QSGParticlePainter* p, painters) + p->setCount(p->count() + delta); +} + +void QSGParticleGroupData::initList() +{ + dataHeap.clear(); +} + +void QSGParticleGroupData::kill(QSGParticleData* d){ + Q_ASSERT(d->group == index); + d->lifeSpan = 0;//Kill off + foreach (QSGParticlePainter* p, painters) + p->reload(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 + } + } + } + + 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]; +} + +void QSGParticleGroupData::prepareRecycler(QSGParticleData* d){ + dataHeap.insert(d); +} + +QSGParticleData::QSGParticleData(QSGParticleSystem* sys) : group(0) , e(0) + , system(sys) , index(0) + , systemIndex(-1) { x = 0; y = 0; t = -1; + lifeSpan = 0; size = 0; endSize = 0; sx = 0; @@ -70,9 +283,9 @@ QSGParticleData::QSGParticleData() rotation = 0; rotationSpeed = 0; autoRotate = 0; - animIdx = -1; + animIdx = 0; frameDuration = 1; - frameCount = 0; + frameCount = 1; animT = -1; color.r = 255; color.g = 255; @@ -83,12 +296,163 @@ QSGParticleData::QSGParticleData() modelIndex = -1; } +void QSGParticleData::clone(const QSGParticleData& other) +{ + x = other.x; + y = other.y; + t = other.t; + lifeSpan = other.lifeSpan; + size = other.size; + endSize = other.endSize; + sx = other.sx; + sy = other.sy; + 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; + 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; +} + +//sets the x accleration without affecting the instantaneous x velocity or position +void QSGParticleData::setInstantaneousAX(qreal ax) +{ + qreal t = (system->m_timeInt / 1000.0) - this->t; + qreal sx = (this->sx + t*this->ax) - t*ax; + qreal ex = this->x + this->sx * t + 0.5 * this->ax * t * t; + qreal x = ex - t*sx - 0.5 * t*t*ax; + + this->ax = ax; + this->sx = sx; + this->x = x; +} + +//sets the x velocity without affecting the instantaneous x postion +void QSGParticleData::setInstantaneousSX(qreal vx) +{ + qreal t = (system->m_timeInt / 1000.0) - this->t; + qreal sx = vx - t*this->ax; + qreal ex = this->x + this->sx * t + 0.5 * this->ax * t * t; + qreal x = ex - t*sx - 0.5 * t*t*this->ax; + + this->sx = sx; + this->x = x; +} + +//sets the instantaneous x postion +void QSGParticleData::setInstantaneousX(qreal x) +{ + qreal t = (system->m_timeInt / 1000.0) - this->t; + this->x = x - t*this->sx - 0.5 * t*t*this->ax; +} + +//sets the y accleration without affecting the instantaneous y velocity or position +void QSGParticleData::setInstantaneousAY(qreal ay) +{ + qreal t = (system->m_timeInt / 1000.0) - this->t; + qreal sy = (this->sy + t*this->ay) - t*ay; + qreal ey = this->y + this->sy * t + 0.5 * this->ay * t * t; + qreal y = ey - t*sy - 0.5 * t*t*ay; + + this->ay = ay; + this->sy = sy; + this->y = y; +} + +//sets the y velocity without affecting the instantaneous y position +void QSGParticleData::setInstantaneousSY(qreal vy) +{ + qreal t = (system->m_timeInt / 1000.0) - this->t; + qreal sy = vy - t*this->ay; + qreal ey = this->y + this->sy * t + 0.5 * this->ay * t * t; + qreal y = ey - t*sy - 0.5 * t*t*this->ay; + + this->sy = sy; + this->y = y; +} + +//sets the instantaneous Y position +void QSGParticleData::setInstantaneousY(qreal y) +{ + qreal t = (system->m_timeInt / 1000.0) - this->t; + this->y = y - t*this->sy - 0.5 * t*t*this->ay; +} + +qreal QSGParticleData::curX() const +{ + qreal t = (system->m_timeInt / 1000.0) - this->t; + return this->x + this->sx * t + 0.5 * this->ax * t * t; +} + +qreal QSGParticleData::curSX() const +{ + qreal t = (system->m_timeInt / 1000.0) - this->t; + return this->sx + t*this->ax; +} + +qreal QSGParticleData::curY() const +{ + qreal t = (system->m_timeInt / 1000.0) - this->t; + return y + sy * t + 0.5 * ay * t * t; +} + +qreal QSGParticleData::curSY() const +{ + qreal t = (system->m_timeInt / 1000.0) - this->t; + return sy + t*ay; +} + +void QSGParticleData::debugDump() +{ + qDebug() << "Particle" << systemIndex << group << "/" << index << stillAlive() + << "Pos: " << x << "," << y + //<< "Vel: " << sx << "," << sy + //<< "Acc: " << ax << "," << ay + << "Size: " << size << "," << endSize + << "Time: " << t << "," <<lifeSpan << ";" << (system->m_timeInt / 1000.0) ; +} + +bool QSGParticleData::stillAlive() +{ + if (!system) + return false; + //fprintf(stderr, "%.9lf %.9lf\n",((qreal)system->m_timeInt/1000.0), (t+lifeSpan)); + return (t + lifeSpan - EPSILON) > ((qreal)system->m_timeInt/1000.0); +} + +float QSGParticleData::curSize() +{ + if (!system || !lifeSpan) + return 0.0f; + return size + (endSize - size) * (lifeLeft() / lifeSpan); +} + +float QSGParticleData::lifeLeft() +{ + if (!system) + return 0.0f; + 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_overwrite(false) - , m_componentComplete(false) + , m_startTime(0), m_nextIndex(0), m_componentComplete(false), m_spriteEngine(0) { - QSGParticleGroupData* gd = new QSGParticleGroupData;//Default group + QSGParticleGroupData* gd = new QSGParticleGroupData(0, this);//Default group m_groupData.insert(0,gd); m_groupIds.insert("",0); m_nextGroupId = 1; @@ -97,6 +461,17 @@ QSGParticleSystem::QSGParticleSystem(QSGItem *parent) : this, SLOT(loadPainter(QObject*))); } +QSGParticleSystem::~QSGParticleSystem() +{ + foreach (QSGParticleGroupData* gd, m_groupData) + delete gd; +} + +QDeclarativeListProperty<QSGSprite> QSGParticleSystem::particleStates() +{ + return QDeclarativeListProperty<QSGSprite>(this, &m_states, spriteAppend, spriteCount, spriteAt, spriteClear); +} + void QSGParticleSystem::registerParticlePainter(QSGParticlePainter* p) { //TODO: a way to Unregister emitters, painters and affectors @@ -125,20 +500,29 @@ void QSGParticleSystem::registerParticleAffector(QSGParticleAffector* a) void QSGParticleSystem::loadPainter(QObject *p) { - if(!m_componentComplete) + if (!m_componentComplete) return; QSGParticlePainter* painter = qobject_cast<QSGParticlePainter*>(p); Q_ASSERT(painter);//XXX - foreach(QSGParticleGroupData* sg, m_groupData) + foreach (QSGParticleGroupData* sg, m_groupData) sg->painters.remove(painter); int particleCount = 0; - if(painter->particles().isEmpty()){//Uses default particle - particleCount += m_groupData[0]->size; + if (painter->particles().isEmpty()){//Uses default particle + QStringList def; + def << ""; + painter->setParticles(def); + particleCount += m_groupData[0]->size(); m_groupData[0]->painters << painter; }else{ - foreach(const QString &group, painter->particles()){ - particleCount += m_groupData[m_groupIds[group]]->size; + foreach (const QString &group, painter->particles()){ + if (group != QLatin1String("") && !m_groupIds[group]){//new group + int id = m_nextGroupId++; + QSGParticleGroupData* gd = new QSGParticleGroupData(id, this); + m_groupIds.insert(group, id); + m_groupData.insert(id, gd); + } + particleCount += m_groupData[m_groupIds[group]]->size(); m_groupData[m_groupIds[group]]->painters << painter; } } @@ -149,64 +533,50 @@ void QSGParticleSystem::loadPainter(QObject *p) void QSGParticleSystem::emittersChanged() { - if(!m_componentComplete) + if (!m_componentComplete) return; m_emitters.removeAll(0); - //Recalculate all counts, as emitter 'particle' may have changed as well - //### Worth tracking previous 'particle' per emitter to do partial recalculations? - m_particle_count = 0; - int previousGroups = m_nextGroupId; - QVector<int> previousSizes; - previousSizes.resize(previousGroups); - for(int i=0; i<previousGroups; i++) - previousSizes[i] = m_groupData[i]->size; - for(int i=0; i<previousGroups; i++) - m_groupData[i]->size = 0; + QList<int> previousSizes; + QList<int> newSizes; + for (int i=0; i<m_nextGroupId; i++){ + previousSizes << m_groupData[i]->size(); + newSizes << 0; + } - foreach(QSGParticleEmitter* e, m_emitters){//Populate groups and set sizes. - if(!m_groupIds.contains(e->particle()) + foreach (QSGParticleEmitter* e, m_emitters){//Populate groups and set sizes. + if (!m_groupIds.contains(e->particle()) || (!e->particle().isEmpty() && !m_groupIds[e->particle()])){//or it was accidentally inserted by a failed lookup earlier - QSGParticleGroupData* gd = new QSGParticleGroupData; int id = m_nextGroupId++; + QSGParticleGroupData* gd = new QSGParticleGroupData(id, this); m_groupIds.insert(e->particle(), id); m_groupData.insert(id, gd); + previousSizes << 0; + newSizes << 0; } - m_groupData[m_groupIds[e->particle()]]->size += e->particleCount(); - m_particle_count += e->particleCount(); + newSizes[m_groupIds[e->particle()]] += e->particleCount(); //###: Cull emptied groups? } - foreach(QSGParticleGroupData* gd, m_groupData){//resize groups and update painters - int id = m_groupData.key(gd); - - //TODO: Shrink back down! (but it has the problem of trying to remove the dead particles while maintaining integrity) - gd->size = qMax(gd->size, id < previousGroups?previousSizes[id]:0); - - gd->data.resize(gd->size); - if(id < previousGroups){ - for(int i=previousSizes[id]; i<gd->size; i++) - gd->data[i] = 0; - /*TODO:Consider salvaging partial updates, but have to batch changes to a single painter - int delta = 0; - delta = gd->size - previousSizes[id]; - foreach(QSGParticlePainter* painter, gd->painters){ - if(!painter->count() && delta){ - painter->reset(); - painter->update(); - } - qDebug() << "Phi" << painter << painter->count() << delta; - painter->setCount(painter->count() + delta); - } - */ - } + //TODO: Garbage collection? + m_particle_count = 0; + for (int i=0; i<m_nextGroupId; i++){ + m_groupData[i]->setSize(qMax(newSizes[i], previousSizes[i])); + m_particle_count += m_groupData[i]->size(); } - foreach(QSGParticlePainter *p, m_particlePainters) + + Q_ASSERT(m_particle_count >= m_bySysIdx.size());//XXX when GC done right + m_bySysIdx.resize(m_particle_count); + + foreach (QSGParticlePainter *p, m_particlePainters) loadPainter(p); - if(m_particle_count > 16000)//###Investigate if these limits are worth warning about? + 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"; } @@ -219,17 +589,51 @@ void QSGParticleSystem::setRunning(bool 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* 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; - //if(!m_emitters.isEmpty() && !m_particlePainters.isEmpty()) + //if (!m_emitters.isEmpty() && !m_particlePainters.isEmpty()) reset(); } -void QSGParticleSystem::reset()//TODO: Needed? +void QSGParticleSystem::reset()//TODO: Needed? Or just in component complete? { - if(!m_componentComplete) + if (!m_componentComplete) return; //Clear guarded pointers which have been deleted @@ -237,54 +641,134 @@ void QSGParticleSystem::reset()//TODO: Needed? cleared += m_emitters.removeAll(0); cleared += m_particlePainters.removeAll(0); cleared += m_affectors.removeAll(0); - //qDebug() << "Reset" << m_emitters.count() << m_particles.count() << "Cleared" << cleared; emittersChanged(); //TODO: Reset data -// foreach(QSGParticlePainter* p, m_particlePainters) +// foreach (QSGParticlePainter* p, m_particlePainters) // p->reset(); -// foreach(QSGParticleEmitter* e, m_emitters) +// foreach (QSGParticleEmitter* e, m_emitters) // e->reset(); //### Do affectors need reset too? - if(!m_running) + if (!m_running) return; - foreach(QSGParticlePainter *p, m_particlePainters){ + foreach (QSGParticlePainter *p, m_particlePainters){ loadPainter(p); p->reset(); } - m_timestamp.start();//TODO: Better placement + m_timeInt = 0; + m_timestamp.restart();//TODO: Better placement m_initialized = true; } -QSGParticleData* QSGParticleSystem::newDatum(int groupId) +void QSGParticleSystem::createEngine() { + if (!m_componentComplete) + return; + //### Solve the losses if size/states go down + foreach (QSGSprite* sprite, m_states){ + bool exists = false; + foreach (const QString &name, m_groupIds.keys()) + if (sprite->name() == name) + exists = true; + if (!exists){ + int id = m_nextGroupId++; + QSGParticleGroupData* gd = new QSGParticleGroupData(id, this); + m_groupIds.insert(sprite->name(), id); + m_groupData.insert(id, gd); + } + } + if (m_states.count()){ + //Reorder Sprite List so as to have the same order as groups + QList<QSGSprite*> newList; + for (int i=0; i<m_nextGroupId; i++){ + bool exists = false; + QString name = m_groupData[i]->name(); + foreach (QSGSprite* existing, m_states){ + if (existing->name() == name){ + newList << existing; + exists = true; + } + } + if (!exists){ + newList << new QSGSprite(this); + newList.back()->setName(name); + } + } + m_states = newList; + + if (!m_spriteEngine) + m_spriteEngine = new QSGSpriteEngine(this); + m_spriteEngine->setCount(m_particle_count); + m_spriteEngine->m_states = m_states; + + connect(m_spriteEngine, SIGNAL(stateChanged(int)), + this, SLOT(particleStateChange(int))); + + }else{ + if (m_spriteEngine) + delete m_spriteEngine; + m_spriteEngine = 0; + } + +} + +void QSGParticleSystem::particleStateChange(int idx) +{ + moveGroups(m_bySysIdx[idx], m_spriteEngine->spriteState(idx)); +} + +void QSGParticleSystem::moveGroups(QSGParticleData *d, int newGIdx) +{ + QSGParticleData* pd = newDatum(newGIdx, false, d->systemIndex); + pd->clone(*d); + finishNewDatum(pd); + + d->systemIndex = -1; + m_groupData[d->group]->kill(d); +} + +int QSGParticleSystem::nextSystemIndex() +{ + if (!m_reusableIndexes.isEmpty()){ + int ret = *(m_reusableIndexes.begin()); + m_reusableIndexes.remove(ret); + return ret; + } + if (m_nextIndex >= m_bySysIdx.size()) + m_bySysIdx.resize(m_bySysIdx.size() < 10 ? 10 : m_bySysIdx.size()*1.1);//###+1,10%,+10? Choose something non-arbitrarily + return m_nextIndex++; +} + +QSGParticleData* QSGParticleSystem::newDatum(int groupId, bool respectLimits, int sysIndex) +{ Q_ASSERT(groupId < m_groupData.count());//XXX shouldn't really be an assert - Q_ASSERT(m_groupData[groupId]->size); - - if( m_groupData[groupId]->nextIdx >= m_groupData[groupId]->size) - m_groupData[groupId]->nextIdx = 0; - int nextIdx = m_groupData[groupId]->nextIdx++; - - Q_ASSERT(nextIdx < m_groupData[groupId]->size); - QSGParticleData* ret; - if(m_groupData[groupId]->data[nextIdx]){//Recycle, it's faster. - ret = m_groupData[groupId]->data[nextIdx]; - if(!m_overwrite && ret->stillAlive()){ - return 0;//Artificial longevity (or too fast emission) means this guy hasn't died. To maintain count, don't emit a new one - }//###Reset? + + QSGParticleData* ret = m_groupData[groupId]->newDatum(respectLimits); + if (!ret){ + return 0; + } + if (sysIndex == -1){ + if (ret->systemIndex == -1) + ret->systemIndex = nextSystemIndex(); }else{ - ret = new QSGParticleData; - m_groupData[groupId]->data[nextIdx] = ret; + if (ret->systemIndex != -1){ + if (m_spriteEngine) + m_spriteEngine->stopSprite(ret->systemIndex); + m_reusableIndexes << ret->systemIndex; + m_bySysIdx[ret->systemIndex] = 0; + } + ret->systemIndex = sysIndex; } + m_bySysIdx[ret->systemIndex] = ret; + + if (m_spriteEngine) + m_spriteEngine->startSprite(ret->systemIndex, ret->group); - ret->system = this; - ret->index = nextIdx; - ret->group = groupId; return ret; } @@ -292,20 +776,24 @@ void QSGParticleSystem::emitParticle(QSGParticleData* pd) {// called from prepareNextFrame()->emitWindow - enforce? //Account for relative emitter position QPointF offset = this->mapFromItem(pd->e, QPointF(0, 0)); - if(!offset.isNull()){ + if (!offset.isNull()){ pd->x += offset.x(); pd->y += offset.y(); } - foreach(QSGParticleAffector *a, m_affectors) - if(a && a->m_needsReset) - a->reset(pd); - foreach(QSGParticlePainter* p, m_groupData[pd->group]->painters) - if(p) - p->load(pd); + finishNewDatum(pd); } +void QSGParticleSystem::finishNewDatum(QSGParticleData *pd){ + m_groupData[pd->group]->prepareRecycler(pd); + foreach (QSGParticleAffector *a, m_affectors) + if (a && a->m_needsReset) + a->reset(pd); + foreach (QSGParticlePainter* p, m_groupData[pd->group]->painters) + if (p) + p->load(pd); +} qint64 QSGParticleSystem::systemSync(QSGParticlePainter* p) { @@ -314,7 +802,7 @@ qint64 QSGParticleSystem::systemSync(QSGParticlePainter* p) if (!m_initialized) return 0;//error in initialization - if(m_syncList.isEmpty() || m_syncList.contains(p)){//Need to advance the simulation + if (m_syncList.isEmpty() || m_syncList.contains(p)){//Need to advance the simulation m_syncList.clear(); //### Elapsed time never shrinks - may cause problems if left emitting for weeks at a time. @@ -323,125 +811,23 @@ qint64 QSGParticleSystem::systemSync(QSGParticlePainter* p) qreal time = m_timeInt / 1000.; dt = time - dt; m_needsReset.clear(); - foreach(QSGParticleEmitter* emitter, m_emitters) - if(emitter) + if (m_spriteEngine) + m_spriteEngine->updateSprites(m_timeInt); + + foreach (QSGParticleEmitter* emitter, m_emitters) + if (emitter) emitter->emitWindow(m_timeInt); - foreach(QSGParticleAffector* a, m_affectors) - if(a) + foreach (QSGParticleAffector* a, m_affectors) + if (a) a->affectSystem(dt); - foreach(QSGParticleData* d, m_needsReset) - foreach(QSGParticlePainter* p, m_groupData[d->group]->painters) - if(p && d) + foreach (QSGParticleData* d, m_needsReset) + foreach (QSGParticlePainter* p, m_groupData[d->group]->painters) + if (p && d) p->reload(d); } m_syncList << p; return m_timeInt; } -//sets the x accleration without affecting the instantaneous x velocity or position -void QSGParticleData::setInstantaneousAX(qreal ax) -{ - qreal t = (system->m_timeInt / 1000.0) - this->t; - qreal sx = (this->sx + t*this->ax) - t*ax; - qreal ex = this->x + this->sx * t + 0.5 * this->ax * t * t; - qreal x = ex - t*sx - 0.5 * t*t*ax; - - this->ax = ax; - this->sx = sx; - this->x = x; -} - -//sets the x velocity without affecting the instantaneous x postion -void QSGParticleData::setInstantaneousSX(qreal vx) -{ - qreal t = (system->m_timeInt / 1000.0) - this->t; - qreal sx = vx - t*this->ax; - qreal ex = this->x + this->sx * t + 0.5 * this->ax * t * t; - qreal x = ex - t*sx - 0.5 * t*t*this->ax; - - this->sx = sx; - this->x = x; -} - -//sets the instantaneous x postion -void QSGParticleData::setInstantaneousX(qreal x) -{ - qreal t = (system->m_timeInt / 1000.0) - this->t; - this->x = x - t*this->sx - 0.5 * t*t*this->ax; -} - -//sets the y accleration without affecting the instantaneous y velocity or position -void QSGParticleData::setInstantaneousAY(qreal ay) -{ - qreal t = (system->m_timeInt / 1000.0) - this->t; - qreal sy = (this->sy + t*this->ay) - t*ay; - qreal ey = this->y + this->sy * t + 0.5 * this->ay * t * t; - qreal y = ey - t*sy - 0.5 * t*t*ay; - - this->ay = ay; - this->sy = sy; - this->y = y; -} - -//sets the y velocity without affecting the instantaneous y position -void QSGParticleData::setInstantaneousSY(qreal vy) -{ - qreal t = (system->m_timeInt / 1000.0) - this->t; - //qDebug() << t << (system->m_timeInt/1000.0) << this->x << this->sx << this->ax << this->x + this->sx * t + 0.5 * this->ax * t * t; - qreal sy = vy - t*this->ay; - qreal ey = this->y + this->sy * t + 0.5 * this->ay * t * t; - qreal y = ey - t*sy - 0.5 * t*t*this->ay; - - this->sy = sy; - this->y = y; -} - -//sets the instantaneous Y position -void QSGParticleData::setInstantaneousY(qreal y) -{ - qreal t = (system->m_timeInt / 1000.0) - this->t; - this->y = y - t*this->sy - 0.5 * t*t*this->ay; -} - -qreal QSGParticleData::curX() const -{ - qreal t = (system->m_timeInt / 1000.0) - this->t; - return this->x + this->sx * t + 0.5 * this->ax * t * t; -} - -qreal QSGParticleData::curSX() const -{ - qreal t = (system->m_timeInt / 1000.0) - this->t; - return this->sx + t*this->ax; -} - -qreal QSGParticleData::curY() const -{ - qreal t = (system->m_timeInt / 1000.0) - this->t; - return y + sy * t + 0.5 * ay * t * t; -} - -qreal QSGParticleData::curSY() const -{ - qreal t = (system->m_timeInt / 1000.0) - this->t; - return sy + t*ay; -} - -void QSGParticleData::debugDump() -{ - qDebug() << "Particle" << group - << "Pos: " << x << "," << y - << "Vel: " << sx << "," << sy - << "Acc: " << ax << "," << ay - << "Size: " << size << "," << endSize - << "Time: " << t << "," <<lifeSpan; -} - -bool QSGParticleData::stillAlive() -{ - if(!system) - return false; - return (t + lifeSpan) > (system->m_timeInt/1000.0); -} QT_END_NAMESPACE diff --git a/src/declarative/particles/qsgparticlesystem_p.h b/src/declarative/particles/qsgparticlesystem_p.h index 45b4e164f1..3a902c67d1 100644 --- a/src/declarative/particles/qsgparticlesystem_p.h +++ b/src/declarative/particles/qsgparticlesystem_p.h @@ -48,6 +48,7 @@ #include <QHash> #include <QPointer> #include <QSignalMapper> +#include <QtDeclarative/private/qsgsprite_p.h> QT_BEGIN_HEADER @@ -55,122 +56,75 @@ QT_BEGIN_NAMESPACE QT_MODULE(Declarative) - +class QSGParticleSystem; class QSGParticleAffector; class QSGParticleEmitter; class QSGParticlePainter; class QSGParticleData; +class QSGSpriteEngine; +class QSGSprite; - -class QSGParticleGroupData{ -public: - QSGParticleGroupData():size(0),nextIdx(0) - {} - int size; - int nextIdx; - QSet<QSGParticlePainter*> painters; - QVector<QSGParticleData*> data; +struct QSGParticleDataHeapNode{ + int time;//in ms + QSet<QSGParticleData*> data;//Set ptrs instead? }; -class QSGParticleSystem : public QSGItem -{ - Q_OBJECT - Q_PROPERTY(bool running READ isRunning WRITE setRunning NOTIFY runningChanged) - Q_PROPERTY(int startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged) - Q_PROPERTY(bool overwrite READ overwrite WRITE setOverwrite NOTIFY overwriteChanged)//XXX: Should just be an implementation detail, but I can't decide which way - /* The problem is that it ought to be false (usually) for stasis effects like model particles, - but it ought to be true (usually) for burst effects where you want it to burst, and forget the old stuff - Ideally burst never overflows? But that leads to crappy behaviour from crappy users... - */ - +class QSGParticleDataHeap { + //Idea is to do a binary heap, but which also stores a set of int,Node* so that if the int already exists, you can + //add it to the data* list. Pops return the whole list at once. public: - explicit QSGParticleSystem(QSGItem *parent = 0); - -bool isRunning() const -{ - return m_running; -} - -int startTime() const -{ - return m_startTime; -} + QSGParticleDataHeap(); + void insert(QSGParticleData* data); -int count(){ return m_particle_count; } + int top(); -signals: + QSet<QSGParticleData*> pop(); -void systemInitialized(); -void runningChanged(bool arg); + void clear(); -void startTimeChanged(int arg); - - -void overwriteChanged(bool arg); + bool contains(QSGParticleData*);//O(n), for debugging purposes only +private: + void grow(); + void swap(int, int); + void bubbleUp(int); + void bubbleDown(int); + int m_size; + int m_end; + QSGParticleDataHeapNode m_tmp; + QVector<QSGParticleDataHeapNode> m_data; + QHash<int,int> m_lookups; +}; -public slots: -void reset(); -void setRunning(bool arg); +class QSGParticleGroupData{ +public: + QSGParticleGroupData(int id, QSGParticleSystem* sys); + ~QSGParticleGroupData(); + int size(); + QString name(); -void setStartTime(int arg) -{ - m_startTime = arg; -} + void setSize(int newSize); -void setOverwrite(bool arg) -{ - if (m_overwrite != arg) { - m_overwrite = arg; -emit overwriteChanged(arg); -} -} + int index; + QSet<QSGParticlePainter*> painters; -void fastForward(int ms) -{ - m_startTime += ms; -} + //TODO: Refactor particle data list out into a separate class + QVector<QSGParticleData*> data; + QSGParticleDataHeap dataHeap; + QSet<int> reusableIndexes; -protected: - void componentComplete(); + void initList(); + void kill(QSGParticleData* d); -private slots: - void emittersChanged(); - void loadPainter(QObject* p); -public://but only really for related class usage. Perhaps we should all be friends? - void emitParticle(QSGParticleData* p); - QSGParticleData* newDatum(int groupId); - qint64 systemSync(QSGParticlePainter* p); - QElapsedTimer m_timestamp; - QSet<QSGParticleData*> m_needsReset; - QHash<QString, int> m_groupIds; - QHash<int, QSGParticleGroupData*> m_groupData; - qint64 m_timeInt; - bool m_initialized; + //After calling this, initialize, then call prepareRecycler(d) + QSGParticleData* newDatum(bool respectsLimits); - void registerParticlePainter(QSGParticlePainter* p); - void registerParticleEmitter(QSGParticleEmitter* e); - void registerParticleAffector(QSGParticleAffector* a); - bool overwrite() const - { - return m_overwrite; - } + //TODO: Find and clean up those that don't get added to the recycler (currently they get lost) + void prepareRecycler(QSGParticleData* d); - int m_particle_count; private: - void initializeSystem(); - bool m_running; - QList<QPointer<QSGParticleEmitter> > m_emitters; - QList<QPointer<QSGParticleAffector> > m_affectors; - QList<QPointer<QSGParticlePainter> > m_particlePainters; - QList<QPointer<QSGParticlePainter> > m_syncList; - qint64 m_startTime; - int m_nextGroupId; - bool m_overwrite; - bool m_componentComplete; - - QSignalMapper m_painterMapper; - QSignalMapper m_emitterMapper; + int m_size; + QSGParticleSystem* m_system; }; struct Color4ub { @@ -183,7 +137,7 @@ struct Color4ub { class QSGParticleData{ public: //TODO: QObject like memory management (without the cost, just attached to system) - QSGParticleData(); + QSGParticleData(QSGParticleSystem* sys); //Convenience functions for working backwards, because parameters are from the start of particle life //If setting multiple parameters at once, doing the conversion yourself will be faster. @@ -211,6 +165,7 @@ public: QSGParticleEmitter* e;//### Needed? QSGParticleSystem* system; int index; + int systemIndex; //General Position Stuff float x; @@ -243,8 +198,114 @@ public: void debugDump(); bool stillAlive(); + float lifeLeft(); + float curSize(); + void clone(const QSGParticleData& other);//Not =, leaves meta-data like index }; +class QSGParticleSystem : public QSGItem +{ + Q_OBJECT + Q_PROPERTY(bool running READ isRunning WRITE setRunning NOTIFY runningChanged) + Q_PROPERTY(int startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged) + + Q_PROPERTY(QDeclarativeListProperty<QSGSprite> particleStates READ particleStates) + +public: + explicit QSGParticleSystem(QSGItem *parent = 0); + ~QSGParticleSystem(); + QDeclarativeListProperty<QSGSprite> particleStates(); + +bool isRunning() const +{ + return m_running; +} + +int startTime() const +{ + return m_startTime; +} + +int count(){ return m_particle_count; } + +signals: + +void systemInitialized(); +void runningChanged(bool arg); + +void startTimeChanged(int arg); + + +public slots: +void reset(); +void setRunning(bool arg); + + +void setStartTime(int arg) +{ + m_startTime = arg; +} + +void fastForward(int ms) +{ + m_startTime += ms; +} + +protected: + void componentComplete(); + +private slots: + void emittersChanged(); + void loadPainter(QObject* p); + void createEngine(); //### method invoked by sprite list changing (in engine.h) - pretty nasty + void particleStateChange(int idx); + +public://###but only really for related class usage. Perhaps we should all be friends? + //These can be called multiple times per frame, performance critical + void emitParticle(QSGParticleData* p); + QSGParticleData* newDatum(int groupId, bool respectLimits = true, int sysIdx = -1);//TODO: implement respectLimits in emitters (which means interacting with maxCount?) + void finishNewDatum(QSGParticleData*); + void moveGroups(QSGParticleData *d, int newGIdx); + int nextSystemIndex(); + + //This one only once per frame (effectively) + qint64 systemSync(QSGParticlePainter* p); + + QElapsedTimer m_timestamp; + QSet<QSGParticleData*> m_needsReset; + QVector<QSGParticleData*> m_bySysIdx; //Another reference to the data (data owned by group), but by sysIdx + QHash<QString, int> m_groupIds; + QHash<int, QSGParticleGroupData*> m_groupData; + QSGSpriteEngine* m_spriteEngine; + + qint64 m_timeInt; + bool m_initialized; + + void registerParticlePainter(QSGParticlePainter* p); + void registerParticleEmitter(QSGParticleEmitter* e); + void registerParticleAffector(QSGParticleAffector* a); + + int m_particle_count; + static void stateRedirect(QDeclarativeListProperty<QObject> *prop, QObject *value);//From QSGSprite +private: + void initializeSystem(); + bool m_running; + QList<QPointer<QSGParticleEmitter> > m_emitters; + QList<QPointer<QSGParticleAffector> > m_affectors; + QList<QPointer<QSGParticlePainter> > m_particlePainters; + QList<QPointer<QSGParticlePainter> > m_syncList; + qint64 m_startTime; + int m_nextGroupId; + int m_nextIndex; + QSet<int> m_reusableIndexes; + bool m_componentComplete; + QList<QSGSprite*> m_states; + + QSignalMapper m_painterMapper; + QSignalMapper m_emitterMapper; +}; + + QT_END_NAMESPACE QT_END_HEADER diff --git a/src/declarative/particles/qsgpointattractor.cpp b/src/declarative/particles/qsgpointattractor.cpp index 6f57ecaa36..8f3b06f5f7 100644 --- a/src/declarative/particles/qsgpointattractor.cpp +++ b/src/declarative/particles/qsgpointattractor.cpp @@ -51,14 +51,14 @@ QSGPointAttractorAffector::QSGPointAttractorAffector(QSGItem *parent) : bool QSGPointAttractorAffector::affectParticle(QSGParticleData *d, qreal dt) { - if(m_strength == 0.0) + if (m_strength == 0.0) return false; qreal dx = m_y - d->curX(); qreal dy = m_x - d->curY(); qreal r = sqrt((dx*dx) + (dy*dy)); qreal theta = atan2(dy,dx); qreal ds = 0; - switch(m_proportionalToDistance){ + switch (m_proportionalToDistance){ case Quadratic: ds = (m_strength / qMax<qreal>(1.,r*r)) * dt; break; @@ -68,7 +68,7 @@ bool QSGPointAttractorAffector::affectParticle(QSGParticleData *d, qreal dt) } dx = ds * cos(theta); dy = ds * sin(theta); - switch(m_physics){ + switch (m_physics){ case Position: d->x = (d->x + dx); d->y = (d->y + dy); diff --git a/src/declarative/particles/qsgspritegoal.cpp b/src/declarative/particles/qsgspritegoal.cpp index c97bfd1f26..6190f39c1d 100644 --- a/src/declarative/particles/qsgspritegoal.cpp +++ b/src/declarative/particles/qsgspritegoal.cpp @@ -48,20 +48,24 @@ QT_BEGIN_NAMESPACE QSGSpriteGoalAffector::QSGSpriteGoalAffector(QSGItem *parent) : - QSGParticleAffector(parent), m_goalIdx(-1), m_jump(false) + QSGParticleAffector(parent), m_goalIdx(-1), m_jump(false), m_systemStates(false), m_lastEngine(0), m_notUsingEngine(false) { } void QSGSpriteGoalAffector::updateStateIndex(QSGSpriteEngine* e) { - m_lastEngine = e; - for(int i=0; i<e->stateCount(); i++){ - if(e->state(i)->name() == m_goalState){ - m_goalIdx = i; - return; + if (m_systemStates){ + m_goalIdx = m_system->m_groupIds[m_goalState]; + }else{ + m_lastEngine = e; + for (int i=0; i<e->stateCount(); i++){ + if (e->state(i)->name() == m_goalState){ + m_goalIdx = i; + return; + } } + m_goalIdx = -1;//Can't find it } - m_goalIdx = -1;//Can't find it } void QSGSpriteGoalAffector::setGoalState(QString arg) @@ -69,7 +73,7 @@ void QSGSpriteGoalAffector::setGoalState(QString arg) if (m_goalState != arg) { m_goalState = arg; emit goalStateChanged(arg); - if(m_goalState.isEmpty()) + if (m_goalState.isEmpty()) m_goalIdx = -1; else m_goalIdx = -2; @@ -79,18 +83,30 @@ void QSGSpriteGoalAffector::setGoalState(QString arg) bool QSGSpriteGoalAffector::affectParticle(QSGParticleData *d, qreal dt) { Q_UNUSED(dt); - //TODO: Affect all engines QSGSpriteEngine *engine = 0; - foreach(QSGParticlePainter *p, m_system->m_groupData[d->group]->painters) - if(qobject_cast<QSGImageParticle*>(p)) - engine = qobject_cast<QSGImageParticle*>(p)->spriteEngine(); - if(!engine) + if (!m_systemStates){ + //TODO: Affect all engines + foreach (QSGParticlePainter *p, m_system->m_groupData[d->group]->painters) + if (qobject_cast<QSGImageParticle*>(p)) + engine = qobject_cast<QSGImageParticle*>(p)->spriteEngine(); + }else{ + engine = m_system->m_spriteEngine; + if (!engine) + m_notUsingEngine = true; + } + if (!engine && !m_notUsingEngine) return false; - if(m_goalIdx == -2 || engine != m_lastEngine) + if (m_goalIdx == -2 || engine != m_lastEngine) updateStateIndex(engine); - if(engine->spriteState(d->index) != m_goalIdx){ - engine->setGoal(m_goalIdx, d->index, m_jump); + int index = d->index; + if (m_systemStates) + index = d->systemIndex; + if (m_notUsingEngine){//systemStates && no stochastic states defined. So cut out the engine + //TODO: It's possible to move to a group that is intermediate and not used by painters or emitters - but right now that will redirect to the default group + m_system->moveGroups(d, m_goalIdx); + }else if (engine->spriteState(index) != m_goalIdx){ + engine->setGoal(m_goalIdx, index, m_jump); emit affected(QPointF(d->curX(), d->curY()));//###Expensive if unconnected? Move to Affector? return true; //Doesn't affect particle data, but necessary for onceOff } diff --git a/src/declarative/particles/qsgspritegoal_p.h b/src/declarative/particles/qsgspritegoal_p.h index 28fb2939e6..720f2b42da 100644 --- a/src/declarative/particles/qsgspritegoal_p.h +++ b/src/declarative/particles/qsgspritegoal_p.h @@ -56,6 +56,7 @@ class QSGSpriteGoalAffector : public QSGParticleAffector Q_OBJECT Q_PROPERTY(QString goalState READ goalState WRITE setGoalState NOTIFY goalStateChanged) Q_PROPERTY(bool jump READ jump WRITE setJump NOTIFY jumpChanged) + Q_PROPERTY(bool systemStates READ systemStates WRITE setSystemStates NOTIFY systemStatesChanged) public: explicit QSGSpriteGoalAffector(QSGItem *parent = 0); @@ -68,6 +69,11 @@ public: { return m_jump; } + bool systemStates() const + { + return m_systemStates; + } + protected: virtual bool affectParticle(QSGParticleData *d, qreal dt); signals: @@ -77,6 +83,8 @@ signals: void jumpChanged(bool arg); void affected(const QPointF &pos); + void systemStatesChanged(bool arg); + public slots: void setGoalState(QString arg); @@ -89,12 +97,23 @@ void setJump(bool arg) } } +void setSystemStates(bool arg) +{ + if (m_systemStates != arg) { + m_systemStates = arg; + emit systemStatesChanged(arg); + } +} + private: void updateStateIndex(QSGSpriteEngine* e); QString m_goalState; int m_goalIdx; QSGSpriteEngine* m_lastEngine; bool m_jump; + bool m_systemStates; + + bool m_notUsingEngine; }; QT_END_NAMESPACE diff --git a/src/declarative/particles/qsgtargetaffector.cpp b/src/declarative/particles/qsgtargetaffector.cpp new file mode 100644 index 0000000000..bf7d4d8eb5 --- /dev/null +++ b/src/declarative/particles/qsgtargetaffector.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** 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$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgtargetaffector_p.h" +#include <QDebug> + +QSGTargetAffector::QSGTargetAffector(QSGItem *parent) : + QSGParticleAffector(parent), m_targetX(0), m_targetY(0), + m_targetWidth(0), m_targetHeight(0), m_defaultShape(new QSGParticleExtruder(this)), + m_targetShape(m_defaultShape), m_targetTime(-1) +{ + m_needsReset = true; +} + +void QSGTargetAffector::reset(QSGParticleData* d) +{ + QSGParticleAffector::reset(d); + m_targets[qMakePair<int,int>(d->group, d->index)] = m_targetShape->extrude(QRectF(m_targetX, m_targetY, m_targetWidth, m_targetHeight)); +} + +bool QSGTargetAffector::affectParticle(QSGParticleData *d, qreal dt) +{ + Q_UNUSED(dt); + QPointF target = m_targets[qMakePair<int,int>(d->group, d->index)]; + if (target.isNull()) + return false; + qreal tt = m_targetTime==-1?d->lifeSpan:(m_targetTime / 1000.0); + qreal t = tt - (d->lifeSpan - d->lifeLeft()); + if (t <= 0) + return false; + qreal tx = d->x + d->sx * tt + 0.5 * d->ax * tt * tt; + qreal ty = d->y + d->sy * tt + 0.5 * d->ay * tt * tt; + + if (QPointF(tx,ty) == target) + return false; + + qreal vX = (target.x() - d->x) / tt; + qreal vY = (target.y() - d->y) / tt; + + qreal w = 1 - (t / tt) + 0.05; + w = qMin(w, 1.0); + qreal wvX = vX * w + d->sx * (1 - w); + qreal wvY = vY * w + d->sy * (1 - w); + //Screws with the acceleration so that the given start pos with the chosen weighted velocity will still end at the target coordinates + qreal ax = (2*(target.x() - d->x - wvX*tt)) / (tt*tt); + qreal ay = (2*(target.y() - d->y - wvY*tt)) / (tt*tt); + + d->sx = wvX; + d->sy = wvY; + d->ax = ax; + d->ay = ay; + + return true; +} diff --git a/src/declarative/particles/qsgtargetaffector_p.h b/src/declarative/particles/qsgtargetaffector_p.h new file mode 100644 index 0000000000..74100e4ad0 --- /dev/null +++ b/src/declarative/particles/qsgtargetaffector_p.h @@ -0,0 +1,168 @@ +/**************************************************************************** +** +** 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$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGTARGETAFFECTOR_H +#define QSGTARGETAFFECTOR_H + +#include "qsgparticleaffector_p.h" + +class QSGTargetAffector : public QSGParticleAffector +{ + Q_OBJECT + Q_PROPERTY(int targetX READ targetX WRITE setTargetX NOTIFY targetXChanged) + Q_PROPERTY(int targetY READ targetY WRITE setTargetY NOTIFY targetYChanged) + Q_PROPERTY(int targetWidth READ targetWidth WRITE setTargetWidth NOTIFY targetWidthChanged) + Q_PROPERTY(int targetHeight READ targetHeight WRITE setTargetHeight NOTIFY targetHeightChanged) + Q_PROPERTY(QSGParticleExtruder* targetShape READ targetShape WRITE setTargetShape NOTIFY targetShapeChanged) + Q_PROPERTY(int targetTime READ targetTime WRITE setTargetTime NOTIFY targetTimeChanged) + +public: + explicit QSGTargetAffector(QSGItem *parent = 0); + + int targetX() const + { + return m_targetX; + } + + int targetY() const + { + return m_targetY; + } + + int targetWidth() const + { + return m_targetWidth; + } + + int targetHeight() const + { + return m_targetHeight; + } + + QSGParticleExtruder* targetShape() const + { + return m_targetShape; + } + + int targetTime() const + { + return m_targetTime; + } + +signals: + + void targetXChanged(int arg); + + void targetYChanged(int arg); + + void targetWidthChanged(int arg); + + void targetHeightChanged(int arg); + + void targetShapeChanged(QSGParticleExtruder* arg); + + void targetTimeChanged(int arg); + +public slots: + void setTargetX(int arg) + { + if (m_targetX != arg) { + m_targetX = arg; + emit targetXChanged(arg); + } + } + + void setTargetY(int arg) + { + if (m_targetY != arg) { + m_targetY = arg; + emit targetYChanged(arg); + } + } + + void setTargetWidth(int arg) + { + if (m_targetWidth != arg) { + m_targetWidth = arg; + emit targetWidthChanged(arg); + } + } + + void setTargetHeight(int arg) + { + if (m_targetHeight != arg) { + m_targetHeight = arg; + emit targetHeightChanged(arg); + } + } + + void setTargetShape(QSGParticleExtruder* arg) + { + if (m_targetShape != arg) { + m_targetShape = arg; + emit targetShapeChanged(arg); + } + } + + void setTargetTime(int arg) + { + if (m_targetTime != arg) { + m_targetTime = arg; + emit targetTimeChanged(arg); + } + } + +protected: + virtual void reset(QSGParticleData*); + virtual bool affectParticle(QSGParticleData *d, qreal dt); +private: + int m_targetX; + int m_targetY; + int m_targetWidth; + int m_targetHeight; + QSGParticleExtruder* m_defaultShape; + QSGParticleExtruder* m_targetShape; + int m_targetTime; + + QHash<QPair<int, int>, QPointF> m_targets; +}; + +#endif // QSGTARGETAFFECTOR_H diff --git a/src/declarative/particles/qsgtargeteddirection.cpp b/src/declarative/particles/qsgtargeteddirection.cpp index 9f1a868512..294242b938 100644 --- a/src/declarative/particles/qsgtargeteddirection.cpp +++ b/src/declarative/particles/qsgtargeteddirection.cpp @@ -62,11 +62,11 @@ const QPointF &QSGTargetedDirection::sample(const QPointF &from) //###This approach loses interpolating the last position of the target (like we could with the emitter) is it worthwhile? qreal targetX; qreal targetY; - if(m_targetItem){ + if (m_targetItem){ QSGParticleEmitter* parentEmitter = qobject_cast<QSGParticleEmitter*>(parent()); targetX = m_targetItem->width()/2; targetY = m_targetItem->height()/2; - if(!parentEmitter){ + if (!parentEmitter){ qWarning() << "Directed vector is not a child of the emitter. Mapping of target item coordinates may fail."; targetX += m_targetItem->x(); targetY += m_targetItem->y(); @@ -83,7 +83,7 @@ const QPointF &QSGTargetedDirection::sample(const QPointF &from) targetY += 0 - from.y() - m_targetVariation + rand()/(float)RAND_MAX * m_targetVariation*2; qreal theta = atan2(targetY, targetX); qreal mag = m_magnitude + rand()/(float)RAND_MAX * m_magnitudeVariation * 2 - m_magnitudeVariation; - if(m_proportionalMagnitude) + if (m_proportionalMagnitude) mag *= sqrt(targetX * targetX + targetY * targetY); m_ret.setX(mag * cos(theta)); m_ret.setY(mag * sin(theta)); diff --git a/src/declarative/particles/qsgturbulence.cpp b/src/declarative/particles/qsgturbulence.cpp index bb46b99f0d..941cf16c77 100644 --- a/src/declarative/particles/qsgturbulence.cpp +++ b/src/declarative/particles/qsgturbulence.cpp @@ -56,7 +56,7 @@ QSGTurbulenceAffector::QSGTurbulenceAffector(QSGItem *parent) : QSGTurbulenceAffector::~QSGTurbulenceAffector() { if (m_field) { - for(int i=0; i<m_gridSize; i++) + for (int i=0; i<m_gridSize; i++) free(m_field[i]); free(m_field); } @@ -70,8 +70,8 @@ static qreal magnitude(qreal x, qreal y) void QSGTurbulenceAffector::setSize(int arg) { if (m_gridSize != arg) { - if(m_field){ //deallocate and then reallocate grid - for(int i=0; i<m_gridSize; i++) + if (m_field){ //deallocate and then reallocate grid + for (int i=0; i<m_gridSize; i++) free(m_field[i]); free(m_field); m_system = 0; @@ -83,14 +83,14 @@ void QSGTurbulenceAffector::setSize(int arg) void QSGTurbulenceAffector::ensureInit() { - if(m_inited) + if (m_inited) return; m_inited = true; m_field = (QPointF**)malloc(m_gridSize * sizeof(QPointF*)); - for(int i=0; i<m_gridSize; i++) + for (int i=0; i<m_gridSize; i++) m_field[i] = (QPointF*)malloc(m_gridSize * sizeof(QPointF)); - for(int i=0; i<m_gridSize; i++) - for(int j=0; j<m_gridSize; j++) + for (int i=0; i<m_gridSize; i++) + for (int j=0; j<m_gridSize; j++) m_field[i][j] = QPointF(); m_spacing = QPointF(width()/m_gridSize, height()/m_gridSize); m_magSum = magnitude(m_spacing.x(), m_spacing.y())*2; @@ -101,11 +101,11 @@ void QSGTurbulenceAffector::mapUpdate() QPoint pos(rand() % m_gridSize, rand() % m_gridSize); QPointF vector(m_strength - (((qreal)rand() / RAND_MAX) * m_strength*2), m_strength - (((qreal)rand() / RAND_MAX) * m_strength*2)); - for(int i = 0; i < m_gridSize; i++){ - for(int j = 0; j < m_gridSize; j++){ + for (int i = 0; i < m_gridSize; i++){ + for (int j = 0; j < m_gridSize; j++){ qreal dist = magnitude(i-pos.x(), j-pos.y()); m_field[i][j] += vector/(dist + 1); - if(magnitude(m_field[i][j].x(), m_field[i][j].y()) > m_strength){ + if (magnitude(m_field[i][j].x(), m_field[i][j].y()) > m_strength){ //Speed limit qreal theta = atan2(m_field[i][j].y(), m_field[i][j].x()); m_field[i][j].setX(m_strength * cos(theta)); @@ -118,21 +118,21 @@ void QSGTurbulenceAffector::mapUpdate() void QSGTurbulenceAffector::affectSystem(qreal dt) { - if(!m_system || !m_active) + if (!m_system || !m_active) return; ensureInit(); qreal period = 1.0/m_frequency; qreal time = m_system->m_timeInt / 1000.0; - while( m_lastT < time ){ + while ( m_lastT < time ){ mapUpdate(); m_lastT += period; } - foreach(QSGParticleGroupData *gd, m_system->m_groupData){ - if(!activeGroup(m_system->m_groupData.key(gd)))//TODO: Surely this can be done better + foreach (QSGParticleGroupData *gd, m_system->m_groupData){ + if (!activeGroup(m_system->m_groupData.key(gd)))//TODO: Surely this can be done better return; - foreach(QSGParticleData *d, gd->data){ - if(!d || !activeGroup(d->group)) + foreach (QSGParticleData *d, gd->data){ + if (!d || !activeGroup(d->group)) return; qreal fx = 0.0; qreal fy = 0.0; @@ -144,14 +144,14 @@ void QSGTurbulenceAffector::affectSystem(qreal dt) nodes << qMakePair((int)floor(nodePos.x()), (int)ceil(nodePos.y())); nodes << qMakePair((int)floor(nodePos.x()), (int)floor(nodePos.y())); typedef QPair<int, int> intPair; - foreach(const intPair &p, nodes){ - if(!QRect(0,0,m_gridSize-1,m_gridSize-1).contains(QPoint(p.first, p.second))) + foreach (const intPair &p, nodes){ + if (!QRect(0,0,m_gridSize-1,m_gridSize-1).contains(QPoint(p.first, p.second))) continue; qreal dist = magnitude(pos.x() - p.first*m_spacing.x(), pos.y() - p.second*m_spacing.y());//TODO: Mathematically valid fx += m_field[p.first][p.second].x() * ((m_magSum - dist)/m_magSum);//Proportionally weight nodes fy += m_field[p.first][p.second].y() * ((m_magSum - dist)/m_magSum); } - if(fx || fy){ + if (fx || fy){ d->setInstantaneousSX(d->curSX()+ fx * dt); d->setInstantaneousSY(d->curSY()+ fy * dt); m_system->m_needsReset << d; diff --git a/src/declarative/particles/qsgwander.cpp b/src/declarative/particles/qsgwander.cpp index 26bea4ee04..c7e17c556b 100644 --- a/src/declarative/particles/qsgwander.cpp +++ b/src/declarative/particles/qsgwander.cpp @@ -52,14 +52,14 @@ QSGWanderAffector::QSGWanderAffector(QSGItem *parent) : QSGWanderAffector::~QSGWanderAffector() { - for(QHash<int, WanderData*>::const_iterator iter=m_wanderData.constBegin(); + for (QHash<int, WanderData*>::const_iterator iter=m_wanderData.constBegin(); iter != m_wanderData.constEnd(); iter++) delete (*iter); } WanderData* QSGWanderAffector::getData(int idx) { - if(m_wanderData.contains(idx)) + if (m_wanderData.contains(idx)) return m_wanderData[idx]; WanderData* d = new WanderData; d->x_vel = 0; @@ -75,7 +75,7 @@ WanderData* QSGWanderAffector::getData(int idx) void QSGWanderAffector::reset(int systemIdx) { - if(m_wanderData.contains(systemIdx)) + if (m_wanderData.contains(systemIdx)) delete m_wanderData[systemIdx]; m_wanderData.remove(systemIdx); } @@ -112,30 +112,30 @@ bool QSGWanderAffector::affectParticle(QSGParticleData* data, qreal dt) qreal dx = dt * m_pace * (2 * qreal(qrand())/RAND_MAX - 1); qreal dy = dt * m_pace * (2 * qreal(qrand())/RAND_MAX - 1); qreal newX, newY; - switch(m_physics){ + switch (m_physics){ case Position: newX = data->curX() + dx; - if(m_xVariance > qAbs(newX) ) + if (m_xVariance > qAbs(newX) ) data->x += dx; newY = data->curY() + dy; - if(m_yVariance > qAbs(newY) ) + if (m_yVariance > qAbs(newY) ) data->y += dy; break; default: case Velocity: newX = data->curSX() + dx; - if(m_xVariance > qAbs(newX) ) + if (m_xVariance > qAbs(newX) ) data->setInstantaneousSX(newX); newY = data->curSY() + dy; - if(m_yVariance > qAbs(newY) ) + if (m_yVariance > qAbs(newY) ) data->setInstantaneousSY(newY); break; case Acceleration: newX = data->ax + dx; - if(m_xVariance > qAbs(newX) ) + if (m_xVariance > qAbs(newX) ) data->setInstantaneousAX(newX); newY = data->ay + dy; - if(m_yVariance > qAbs(newY) ) + if (m_yVariance > qAbs(newY) ) data->setInstantaneousAY(newY); break; } |