1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQuick module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qquickparticleaffector_p.h"
44 #include <private/qqmlglobal_p.h>
49 \instantiates QQuickParticleAffector
50 \inqmlmodule QtQuick.Particles 2
51 \brief Applies alterations to the attributes of logical particles at any
52 point in their lifetime
53 \ingroup qtquick-particles
55 The base Affector does not alter any attributes, but can be used to emit a signal
56 when a particle meets certain conditions.
58 If an affector has a defined size, then it will only affect particles within its size and position on screen.
60 Affectors have different performance characteristics to the other particle system elements. In particular,
61 they have some simplifications to try to maintain a simulation at real-time or faster. When running a system
62 with Affectors, irregular frame timings that grow too large ( > one second per frame) will cause the Affectors
63 to try and cut corners with a faster but less accurate simulation. If the system has multiple affectors the order
64 in which they are applied is not guaranteed, and when simulating larger time shifts they will simulate the whole
65 shift each, which can lead to different results compared to smaller time shifts.
67 Accurate simulation for large numbers of particles (hundreds) with multiple affectors may be possible on some hardware,
68 but on less capable hardware you should expect small irregularties in the simulation as simulates with worse granularity.
71 \qmlproperty ParticleSystem QtQuick.Particles2::Affector::system
72 This is the system which will be affected by the element.
73 If the Affector is a direct child of a ParticleSystem, it will automatically be associated with it.
76 \qmlproperty list<string> QtQuick.Particles2::Affector::groups
77 Which logical particle groups will be affected.
79 If empty, it will affect all particles.
82 \qmlproperty list<string> QtQuick.Particles2::Affector::whenCollidingWith
83 If any logical particle groups are specified here, then the affector
84 will only be triggered if the particle being examined intersects with
85 a particle of one of these groups.
87 This is different from the groups property. The groups property selects which
88 particles might be examined, and if they meet other criteria (including being
89 within the bounds of the Affector, modified by shape) then they will be tested
90 again to see if they intersect with a particles from one of the particle groups
93 By default, no groups are specified.
96 \qmlproperty bool QtQuick.Particles2::Affector::enabled
97 If enabled is set to false, this affector will not affect any particles.
99 Usually this is used to conditionally turn an affector on or off.
101 Default value is true.
104 \qmlproperty bool QtQuick.Particles2::Affector::once
105 If once is set to true, this affector will only affect each particle
106 once in their lifetimes. If the affector normally simulates a continuous
107 effect over time, then it will simulate the effect of one second of time
108 the one instant it affects the particle.
110 Default value is false.
113 \qmlproperty Shape QtQuick.Particles2::Affector::shape
114 If a size has been defined, the shape property can be used to affect a
115 non-rectangular area.
118 \qmlsignal QtQuick.Particles2::Affector::onAffected(real x, real y)
120 This handler is called when a particle is selected to be affected. It will not be called
121 if a particle is considered by the Affector but not actually altered in any way.
123 In the special case where an Affector has no possible effect (e.g. Affector {}), affected
124 will be emitted for all particles being considered if you connect to it. This allows you to
125 execute arbitrary code in response to particles (use the Affector::onAffectParticles
126 signal handler if you want to execute code which affects the particles
127 themselves). As this executes JavaScript code per particle, it is not recommended to use this
128 signal with a high-volume particle system.
130 x,y is the particle's current position.
132 QQuickParticleAffector::QQuickParticleAffector(QQuickItem *parent) :
133 QQuickItem(parent), m_needsReset(false), m_ignoresTime(false), m_onceOff(false), m_enabled(true)
134 , m_system(0), m_updateIntSet(false), m_shape(new QQuickParticleExtruder(this))
138 bool QQuickParticleAffector::isAffectedConnected()
140 IS_SIGNAL_CONNECTED(this, QQuickParticleAffector, affected, (qreal,qreal));
144 void QQuickParticleAffector::componentComplete()
146 if (!m_system && qobject_cast<QQuickParticleSystem*>(parentItem()))
147 setSystem(qobject_cast<QQuickParticleSystem*>(parentItem()));
148 QQuickItem::componentComplete();
151 bool QQuickParticleAffector::activeGroup(int g) {
152 if (m_updateIntSet){ //This can occur before group ids are properly assigned, but that resets the flag
154 foreach (const QString &p, m_groups)
155 m_groupIds << m_system->groupIds[p];
156 m_updateIntSet = false;
158 return m_groupIds.isEmpty() || m_groupIds.contains(g);
161 bool QQuickParticleAffector::shouldAffect(QQuickParticleData* d)
165 if (activeGroup(d->group)){
166 if ((m_onceOff && m_onceOffed.contains(qMakePair(d->group, d->index)))
169 //Need to have previous location for affected anyways
170 if (width() == 0 || height() == 0
171 || m_shape->contains(QRectF(m_offset.x(), m_offset.y(), width(), height()), QPointF(d->curX(), d->curY()))){
172 if (m_whenCollidingWith.isEmpty() || isColliding(d)){
181 void QQuickParticleAffector::postAffect(QQuickParticleData* d)
183 m_system->needsReset << d;
185 m_onceOffed << qMakePair(d->group, d->index);
186 if (isAffectedConnected())
187 emit affected(d->curX(), d->curY());
190 const qreal QQuickParticleAffector::simulationDelta = 0.020;
191 const qreal QQuickParticleAffector::simulationCutoff = 1.000;//If this goes above 1.0, then m_once behaviour needs special codepath
193 void QQuickParticleAffector::affectSystem(qreal dt)
197 //If not reimplemented, calls affectParticle per particle
198 //But only on particles in targeted system/area
199 updateOffsets();//### Needed if an ancestor is transformed.
202 foreach (QQuickParticleGroupData* gd, m_system->groupData) {
203 if (activeGroup(m_system->groupData.key(gd))) {
204 foreach (QQuickParticleData* d, gd->data) {
205 if (shouldAffect(d)) {
206 bool affected = false;
208 if (!m_ignoresTime && myDt < simulationCutoff) {
209 int realTime = m_system->timeInt;
210 m_system->timeInt -= myDt * 1000.0;
211 while (myDt > simulationDelta) {
212 m_system->timeInt += simulationDelta * 1000.0;
213 if (d->alive())//Only affect during the parts it was alive for
214 affected = affectParticle(d, simulationDelta) || affected;
215 myDt -= simulationDelta;
217 m_system->timeInt = realTime;
220 affected = affectParticle(d, myDt) || affected;
229 bool QQuickParticleAffector::affectParticle(QQuickParticleData *, qreal )
234 void QQuickParticleAffector::reset(QQuickParticleData* pd)
235 {//TODO: This, among other ones, should be restructured so they don't all need to remember to call the superclass
237 if (activeGroup(pd->group))
238 m_onceOffed.remove(qMakePair(pd->group, pd->index));
241 void QQuickParticleAffector::updateOffsets()
244 m_offset = m_system->mapFromItem(this, QPointF(0, 0));
247 bool QQuickParticleAffector::isColliding(QQuickParticleData *d)
249 qreal myCurX = d->curX();
250 qreal myCurY = d->curY();
251 qreal myCurSize = d->curSize()/2;
252 foreach (const QString &group, m_whenCollidingWith){
253 foreach (QQuickParticleData* other, m_system->groupData[m_system->groupIds[group]]->data){
254 if (!other->stillAlive())
256 qreal otherCurX = other->curX();
257 qreal otherCurY = other->curY();
258 qreal otherCurSize = other->curSize()/2;
259 if ((myCurX + myCurSize > otherCurX - otherCurSize
260 && myCurX - myCurSize < otherCurX + otherCurSize)
261 && (myCurY + myCurSize > otherCurY - otherCurSize
262 && myCurY - myCurSize < otherCurY + otherCurSize))