From 29c4b643272a43022081cce063394bac823ab529 Mon Sep 17 00:00:00 2001 From: Alan Alpert Date: Mon, 4 Jul 2011 18:15:28 +1000 Subject: [PATCH] Squashed Particle System Stateful Rewrite Add TargetAffector Fix for ParticlePainter offsets Adds a particleStates property to ParticleSystem Augment SpriteGoal to change system states as well Also add 'collidingParticles' list to affector. Particle Stochastic States Now actually working, and you can put emitters, affectors and painters inside their targeted state. Fireworks example uses states instead of delegates. Replaced the delegate example with a text thing. The examples launcher now also contains all the custom examples. Adds CumulativeDirection and working null Affector (for affected signal). Add spaces after all flow control keywords. Change-Id: I77b7e3044a9800dbfff6db833914d63127602cf5 Reviewed-on: http://codereview.qt.nokia.com/968 Reviewed-by: Alan Alpert --- demos/declarative/flickr/content/StreamView.qml | 1 - .../declarative/particles/custom/delegates.qml | 89 +++ .../declarative/particles/exampleslauncher.qml | 6 +- .../particles/launcherContent/icons/combustion.png | Bin 0 -> 7173 bytes .../particles/launcherContent/icons/delegates.png | Bin 0 -> 1892 bytes .../particles/launcherContent/icons/fireworks.png | Bin 0 -> 16139 bytes .../particles/launcherContent/icons/shader.png | Bin 0 -> 22294 bytes .../particles/modelparticles/package.qml | 2 +- .../particles/modelparticles/stream.qml | 1 - examples/declarative/particles/snow/snow.qml | 1 + .../declarative/particles/trails/combustion.qml | 199 ++++++ .../particles/trails/content/matchmask.png | Bin 0 -> 2369 bytes .../particles/trails/dynamicemitters.qml | 1 + .../particles/{custom => trails}/fireworks.qml | 86 +-- src/declarative/items/qsgsprite.cpp | 16 + src/declarative/items/qsgsprite_p.h | 10 +- src/declarative/items/qsgspriteengine.cpp | 174 +++-- src/declarative/items/qsgspriteengine_p.h | 7 +- src/declarative/particles/particles.pri | 11 +- .../particles/qsgcumulativedirection.cpp | 62 ++ .../particles/qsgcumulativedirection_p.h | 64 ++ src/declarative/particles/qsgcustomparticle.cpp | 183 ++--- src/declarative/particles/qsgcustomparticle_p.h | 9 +- src/declarative/particles/qsgemitter.cpp | 21 +- src/declarative/particles/qsgfollowemitter.cpp | 36 +- src/declarative/particles/qsgfriction.cpp | 2 +- src/declarative/particles/qsggravity.cpp | 4 +- src/declarative/particles/qsgimageparticle.cpp | 485 +++++++------ src/declarative/particles/qsgimageparticle_p.h | 16 +- src/declarative/particles/qsgitemparticle.cpp | 111 +-- src/declarative/particles/qsgitemparticle_p.h | 8 +- src/declarative/particles/qsgkill.cpp | 3 +- src/declarative/particles/qsglineextruder.cpp | 6 +- src/declarative/particles/qsgmaskextruder.cpp | 16 +- src/declarative/particles/qsgmodelparticle.cpp | 140 ++-- src/declarative/particles/qsgmodelparticle_p.h | 10 +- src/declarative/particles/qsgparticleaffector.cpp | 72 +- src/declarative/particles/qsgparticleaffector_p.h | 22 +- src/declarative/particles/qsgparticleemitter.cpp | 24 +- src/declarative/particles/qsgparticleextruder.cpp | 4 +- src/declarative/particles/qsgparticlepainter.cpp | 95 +-- src/declarative/particles/qsgparticlepainter_p.h | 23 +- src/declarative/particles/qsgparticlesmodule.cpp | 7 +- src/declarative/particles/qsgparticlesystem.cpp | 788 +++++++++++++++------ src/declarative/particles/qsgparticlesystem_p.h | 251 ++++--- src/declarative/particles/qsgpointattractor.cpp | 6 +- src/declarative/particles/qsgspritegoal.cpp | 48 +- src/declarative/particles/qsgspritegoal_p.h | 19 + src/declarative/particles/qsgtargetaffector.cpp | 92 +++ src/declarative/particles/qsgtargetaffector_p.h | 168 +++++ src/declarative/particles/qsgtargeteddirection.cpp | 6 +- src/declarative/particles/qsgturbulence.cpp | 38 +- src/declarative/particles/qsgwander.cpp | 20 +- 53 files changed, 2365 insertions(+), 1098 deletions(-) create mode 100644 examples/declarative/particles/custom/delegates.qml create mode 100644 examples/declarative/particles/launcherContent/icons/combustion.png create mode 100644 examples/declarative/particles/launcherContent/icons/delegates.png create mode 100644 examples/declarative/particles/launcherContent/icons/fireworks.png create mode 100644 examples/declarative/particles/launcherContent/icons/shader.png create mode 100644 examples/declarative/particles/trails/combustion.qml create mode 100644 examples/declarative/particles/trails/content/matchmask.png rename examples/declarative/particles/{custom => trails}/fireworks.qml (62%) create mode 100644 src/declarative/particles/qsgcumulativedirection.cpp create mode 100644 src/declarative/particles/qsgcumulativedirection_p.h create mode 100644 src/declarative/particles/qsgtargetaffector.cpp create mode 100644 src/declarative/particles/qsgtargetaffector_p.h diff --git a/demos/declarative/flickr/content/StreamView.qml b/demos/declarative/flickr/content/StreamView.qml index d7b608a..e2c65e2 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 0000000..4b01c66 --- /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 354bcdf..54f5765 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 new file mode 100644 index 0000000000000000000000000000000000000000..69c6f64e547d5ab5f0d8dd3ed9be155036796ee9 GIT binary patch literal 7173 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 literal 0 HcmV?d00001 diff --git a/examples/declarative/particles/launcherContent/icons/shader.png b/examples/declarative/particles/launcherContent/icons/shader.png new file mode 100644 index 0000000000000000000000000000000000000000..7c6de498d561c5fc5288e2d69721c00501f3f5c9 GIT binary patch literal 22294 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 BP&WVo literal 0 HcmV?d00001 diff --git a/examples/declarative/particles/modelparticles/package.qml b/examples/declarative/particles/modelparticles/package.qml index 0aa8903..d374a93 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 5c7a6f7..15280f7 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 ea2de17..b988c53 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 0000000..f244300 --- /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 new file mode 100644 index 0000000000000000000000000000000000000000..e575875c555ad48fc383f455e1b7d7837886105d GIT binary patch literal 2369 zcmV-H3BLA;P)MM9V&Cgtmbsms!3A|_!|5NZfbVB&c2r-%IZlVcu@-P$33E%5K|b^OlHx8XN;VASxlX2kmshdeDSYImvo zT-DbKu3x;H!MD25O<^yC`vo6t88W$6wgj%J_>22J zpaC3g2-r1zyRYdAxt8iHgMYf6z=4Fk2F}9K^{}t`eqXVXfL?=qCGfBIQoq|1unFWA zsDKiB7W$5FbOc*QAjRrx;7Z`#Oh89~c92@21{yFG7{_i38+xJQ*ZjOT_?w0F`-OyV z0$mB&%;QEtp}AEgY^9o1t&Z-s!P}|i!61db0(QDUF+DaruwBsE$+(>>x-yvit$^R{ zBz*Wz4tM+T^(|lr*aEg88^GNSxY6O>z|c2pWq_@fxo+ml-_8;?_6m6K9oXClc7Yxt zz;d{6;qH+4|9;GtnV?0pG>feT?nuGiLc(qVcL%_JJaWK>b7ulKp7EW2!Hzjy7GPp+ za6f^2MGAu)ZuNl;f|qAWD(dUn7Rfg zKngtr9nDQ6$)!NySyY&;6w8BV>5Poi&&0GUWNChN{PU1EQ@Ylz<3* zOu|5Fj10Z!nR_L;uOTriglHx%yhg@Bi0QSFkO(%7AQuv~AhK72H>C>Z%YzADZ|y7P z6mEb_vYkMoL5lfn^HLvwx2ODO3Zx1$#WYj3*-A0Z`w^T6)f+mbO+7!d{Zg=2g1(f| z%qoHuQ$M#BNaCR(3oh9*f>gvFbJEvRQ)?hM1XFigl?lO~21uvC)*4*{g{GHC3MG(S z!_-DedO|^(#5tT@I9laZ{5yd{LL%8vibM&Nn7>0`3eIp05*c1rv`W(mOF7>=onIT+ zOrg+pQc14F^`g3?B-j0#@;58R;8TzTZasn z2)1NGt^}zRmr9PM;DHgKWlWVVpgqsRW|gZjP@@<`celMRX8DD!CuIQ3*=*IeHfhYH zOGM}+G^h~Oxt17l8MF}m%iYXx7#V!4FRGlK{_TA@4>GAhYc`dp11Nbpdo2VfLeLS> z)CxlWX%~fyM+=SW9iRk-6c{ZrLR>WU;wn=~!sjC?VmC9R&ND3l8aOZ8V?m%aXo=JU zMJVhqrp<+5qa*?qMxa_?Tf@vkTh(@|UP|y`&`OB&@y@~*=4$6bD8NP2ovUh?7V#~x zo*4|_EX1-_NFP@yfe@ay_FKG2IETfBlGV1RtvLVgK^dWT=6TAi%apT1T-} zg0hufHc-}(mV_F*isyEP9}(*8aTritf%Tl#7T2nkAZB)O3&FK>oHY@8R>5ft9ug9U zjpsV5ABLl;;mkr=b8J1Ox8ObashO998^N*jFm3|$`59~v2@Af6s@v%`22KO``6(Qi zFseB5j>?MJuv-I+<7H)GV&SBOr$e}tht1Uawk07rvx??EfhT7HJaIgmSk9cs`qyVX zESXM!I4s5UiG{NoUXB#;Nb*bjNzo_Y3Ud?p4!I;( z^V;#Rq2s-m6L>s=K^o@hK?8X@?buj&JcNIb;L#~OFDZu=k1EH|Icn!|(=~bZH;}{H z^VwO&dq)X;HHP98tcBwm1_>l01kS4(o|W)$1dqn>Y|N*pHD67lVt0A_v=scJw0u5t zJUXs9crpp~ROR8g3gxbmVY;1Kcv-=d2|O87A2s}A)bONi7}bu-b1A@v4;rhYkY_++T) zClw#yFcS|aN!VGm>ASQrwD79rZ$~vBpEP_vYIspwMzyELU(M72UzLu|sK!W7Z9Vr& z$F0)}O#DpiJ(QMblZMYuEFTXWK0U2@R9Rlt{By6Y5iZQfZMt9xrAIz3ph_uG_j7cgOP>OSxOfI{=zg8moC<68wox^kVuJ& zRgw2WRpX$>Qrq}R<}&p9V1P`9-Mk=VPv;|?;79A@z01%y1FKL?y;dg1<@Ovj=j+lX n)|J74pSPr4fL-Uk|L5($5mF=g#!vEC00000NkvXXu0mjfOP_A_ literal 0 HcmV?d00001 diff --git a/examples/declarative/particles/trails/dynamicemitters.qml b/examples/declarative/particles/trails/dynamicemitters.qml index f338c20..dbf3f8f 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 similarity index 62% rename from examples/declarative/particles/custom/fireworks.qml rename to examples/declarative/particles/trails/fireworks.qml index b73a5e2..59627f8 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 694976a..794ae51 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 QT_BEGIN_NAMESPACE @@ -54,4 +57,17 @@ QSGSprite::QSGSprite(QObject *parent) : { } +void redirectError(QDeclarativeListProperty *prop, QObject *value) +{ + qWarning() << "Could not add " << value << " to state" << prop->object << "as it is not associated with a particle system."; +} + +QDeclarativeListProperty QSGSprite::particleChildren(){ + QSGParticleSystem* system = qobject_cast(parent()); + if (system) + return QDeclarativeListProperty(this, 0, &QSGParticleSystem::stateRedirect); + else + return QDeclarativeListProperty(this, 0, &redirectError); +} + QT_END_NAMESPACE diff --git a/src/declarative/items/qsgsprite_p.h b/src/declarative/items/qsgsprite_p.h index 652a4cd..c205998 100644 --- a/src/declarative/items/qsgsprite_p.h +++ b/src/declarative/items/qsgsprite_p.h @@ -45,6 +45,7 @@ #include #include #include +#include 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 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 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 27de0d9..94753d0 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; iduration() * m_states[m_sprites[index]]->frames() + m_startTimes[index]; - for(int i=0; i= m_stateUpdates.first().first){ - foreach(int idx, m_stateUpdates.first().second){ - if(idx >= m_sprites.count()) + QSet 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; iname() == iter.key()){ + for (int i=0; iname() == 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; iname() == goalName) + for (int i=0; iname() == 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; iname() == goalName) + if (iter.key() == goalName) + for (int i=0; iname() == goalName) return i; } QSet options; - for(int i=1; im_to.constBegin(); + for (int i=1; im_to.constBegin(); iter!=curState->m_to.constEnd(); iter++){ int option = -1; - for(int j=0; jname() == iter.key()) - if(goalSeek(j, spriteIdx, i) != -1) + for (int j=0; jname() == 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::const_iterator iter=options.constBegin(); + for (QSet::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; jname() == iter.key()) - if(options.contains(j)) + for (int j=0; jname() == 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; jname() == iter.key()){ + for (int j=0; jname() == 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 t){ + }else if (m_stateUpdates[i].first > t){ QList 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 8ab6e3a..33d6e82 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 m_sprites;//int is the index in m_states of the current state QVector m_goals; QVector m_startTimes; - QList > > m_stateUpdates;//### This could be done faster + QList > > 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 04200a3..bdd42ae 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 0000000..c6834aa --- /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 QSGCumulativeDirection::directions() +{ + return QDeclarativeListProperty(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 0000000..aad003a --- /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 +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGCumulativeDirection : public QSGStochasticDirection +{ + Q_OBJECT + Q_PROPERTY(QDeclarativeListProperty directions READ directions) + Q_CLASSINFO("DefaultProperty", "directions") +public: + explicit QSGCumulativeDirection(QObject *parent = 0); + QDeclarativeListProperty directions(); + const QPointF &sample(const QPointF &from); +private: + QList m_directions; +}; +#endif // QSGCUMULATIVEDIRECTION_P_H diff --git a/src/declarative/particles/qsgcustomparticle.cpp b/src/declarative/particles/qsgcustomparticle.cpp index 46f160d..ba6bf29 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; pindexDataAsUShort(); - for (int i=0; isetGeometry(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 > values; QVector > > textures; const QVector > > &oldTextures = m_material.textureProviders(); - - for (QSet::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::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 50ff37a..cccaa7e 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 m_sources; QSGShaderEffectMaterial m_material; - QSGShaderEffectNode* m_node; + QSGShaderEffectNode* m_rootNode; + QHash m_nodes; qreal m_lastTime; }; diff --git a/src/declarative/particles/qsgemitter.cpp b/src/declarative/particles/qsgemitter.cpp index c3ec3ff..8204f4d 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 ba31e8f..72577fd 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 828d205..4681681 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 b1cf3e9..579fa7c 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 836236c..ac64d58 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; px; - 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; isetGeometry(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; pindexDataAsUShort(); - for (int i=0; itexture->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; ispriteState(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 dc79c59..6cade7f 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 m_nodes; + QHash 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 5e324cd..4e6a914 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(qmlAttachedPropertiesObject(item)))) + if ((mpa = qobject_cast(qmlAttachedPropertiesObject(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(m_delegate->create(qmlContext(this))); + }else if (m_delegate){ + d->delegate = qobject_cast(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(qmlAttachedPropertiesObject(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(qmlAttachedPropertiesObject(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; idelegate; - 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; im_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 24bbcf9..233f942 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 m_deletables; - QList< int > m_loadables; + QList< QSGParticleData* > m_loadables; bool m_fade; QList m_pendingItems; diff --git a/src/declarative/particles/qsgkill.cpp b/src/declarative/particles/qsgkill.cpp index eb3a55d..e0406bc 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 f32b014..d5f3261 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 d9a4639..5e60d31 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(arg.value())) { - if(m_ownModel && m_model) + if (qobject_cast(arg.value())) { + if (m_ownModel && m_model) delete m_model; m_model = qobject_cast(arg.value()); 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 m_modelCount){ - for(int i=m_modelCount; i m_modelCount){ + for (int i=m_modelCount; idelegate(); 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(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(qmlAttachedPropertiesObject(m_data[pos]->delegate)); - if(mpa){ + QSGModelParticleAttached* mpa = qobject_cast(qmlAttachedPropertiesObject(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; idelegate) - 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; im_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 31e4025..aba64e4 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 m_deletables; - QList< int > m_requests; + QList< QSGParticleData* > m_requests; bool m_fade; QList 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 5b0936c..ccb1265 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(parentItem())) + if (!m_system && qobject_cast(parentItem())) setSystem(qobject_cast(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 7418760..806a1cc 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 143338f..a75d638 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(parentItem())) + if (!m_system && qobject_cast(parentItem())) setSystem(qobject_cast(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 91b968c..f0658f6 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 34313bd..a7a05ed 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(parentItem())) + if (!m_system && qobject_cast(parentItem())) setSystem(qobject_cast(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 > oldStarts(m_particleStarts); - //Update particle starts datastore - m_particleStarts.clear(); - m_lastStart = 0; - QList 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(gd->size, m_lastStart)); - m_lastStart += gd->size; - } - - //Shuffle stuff around - //TODO: In place shuffling because it's faster - QVector oldData(m_data); - QVector 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; idata.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 e506018..49f7881 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 m_data; //Actually stored in arbitrary order, - QVector 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 > m_particleStarts; - int particleTypeIndex(QSGParticleData* d);//Now private - void resize(int, int); - QSGParticleData* m_sentinel; //QVector 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 a7a9a92..e736e6f 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(uri, 2, 0, "PointDirection"); qmlRegisterType(uri, 2, 0, "AngledDirection"); qmlRegisterType(uri, 2, 0, "TargetedDirection"); + qmlRegisterType(uri, 2, 0, "CumulativeDirection"); - qmlRegisterType(uri, 2, 0, "ParticleAffector");//if it has a triggered signal, it's useful + qmlRegisterType(uri, 2, 0, "Affector");//for the triggered signal qmlRegisterType(uri, 2, 0, "Wander"); qmlRegisterType(uri, 2, 0, "Friction"); qmlRegisterType(uri, 2, 0, "PointAttractor"); @@ -104,8 +107,8 @@ void QSGParticlesModule::defineModule() qmlRegisterType(uri, 2, 0, "Kill"); qmlRegisterType(uri, 2, 0, "SpriteGoal"); qmlRegisterType(uri, 2, 0 , "Turbulence"); + qmlRegisterType(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 2dc2129..d648d46 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 #include 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 QSGParticleDataHeap::pop() +{ + if (!m_end) + return QSet (); + QSet 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) + 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; igroup = 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 << "," <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 QSGParticleSystem::particleStates() +{ + return QDeclarativeListProperty(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(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 previousSizes; - previousSizes.resize(previousGroups); - for(int i=0; isize; - for(int i=0; isize = 0; + QList previousSizes; + QList newSizes; + for (int i=0; isize(); + 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]; isize; 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; isetSize(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 *prop, QObject *value) +{ + //Hooks up automatic state-associated stuff + QSGParticleSystem* sys = qobject_cast(prop->object->parent()); + QSGSprite* sprite = qobject_cast(prop->object); + if (!sprite || !sys) + return; + QStringList list; + list << sprite->name(); + QSGParticleAffector* a = qobject_cast(value); + if (a){ + a->setParentItem(sys); + a->setParticles(list); + a->setSystem(sys); + return; + } + QSGFollowEmitter* e = qobject_cast(value); + if (e){ + e->setParentItem(sys); + e->setFollow(sprite->name()); + e->setSystem(sys); + return; + } + QSGParticlePainter* p = qobject_cast(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 newList; + for (int i=0; iname(); + 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 << "," < (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 45b4e16..3a902c6 100644 --- a/src/declarative/particles/qsgparticlesystem_p.h +++ b/src/declarative/particles/qsgparticlesystem_p.h @@ -48,6 +48,7 @@ #include #include #include +#include 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 painters; - QVector data; +struct QSGParticleDataHeapNode{ + int time;//in ms + QSet 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 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 m_data; + QHash 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 painters; -void fastForward(int ms) -{ - m_startTime += ms; -} + //TODO: Refactor particle data list out into a separate class + QVector data; + QSGParticleDataHeap dataHeap; + QSet 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 m_needsReset; - QHash m_groupIds; - QHash 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 > m_emitters; - QList > m_affectors; - QList > m_particlePainters; - QList > 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 particleStates READ particleStates) + +public: + explicit QSGParticleSystem(QSGItem *parent = 0); + ~QSGParticleSystem(); + QDeclarativeListProperty 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 m_needsReset; + QVector m_bySysIdx; //Another reference to the data (data owned by group), but by sysIdx + QHash m_groupIds; + QHash 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 *prop, QObject *value);//From QSGSprite +private: + void initializeSystem(); + bool m_running; + QList > m_emitters; + QList > m_affectors; + QList > m_particlePainters; + QList > m_syncList; + qint64 m_startTime; + int m_nextGroupId; + int m_nextIndex; + QSet m_reusableIndexes; + bool m_componentComplete; + QList 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 6f57eca..8f3b06f 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(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 c97bfd1..6190f39 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; istateCount(); 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; istateCount(); 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(p)) - engine = qobject_cast(p)->spriteEngine(); - if(!engine) + if (!m_systemStates){ + //TODO: Affect all engines + foreach (QSGParticlePainter *p, m_system->m_groupData[d->group]->painters) + if (qobject_cast(p)) + engine = qobject_cast(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 28fb293..720f2b4 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 0000000..bf7d4d8 --- /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 + +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(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(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 0000000..74100e4 --- /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, QPointF> m_targets; +}; + +#endif // QSGTARGETAFFECTOR_H diff --git a/src/declarative/particles/qsgtargeteddirection.cpp b/src/declarative/particles/qsgtargeteddirection.cpp index 9f1a868..294242b 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(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 bb46b99..941cf16 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_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 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 26bea4e..c7e17c5 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::const_iterator iter=m_wanderData.constBegin(); + for (QHash::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; } -- 2.7.4