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 Declarative 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"
47 \qmlclass Affector QQuickParticleAffector
48 \inqmlmodule QtQuick.Particles 2
49 \brief Affector elements can alter the attributes of logical particles at any point in their lifetime.
51 The base Affector does not alter any attributes, but can be used to emit a signal
52 when a particle meets certain conditions.
54 If an affector has a defined size, then it will only affect particles within its size and position on screen.
56 Affectors have different performance characteristics to the other particle system elements. In particular,
57 they have some simplifications to try to maintain a simulation at real-time or faster. When running a system
58 with Affectors, irregular frame timings that grow too large ( > one second per frame) will cause the Affectors
59 to try and cut corners with a faster but less accurate simulation. If the system has multiple affectors the order
60 in which they are applied is not guaranteed, and when simulating larger time shifts they will simulate the whole
61 shift each, which can lead to different results compared to smaller time shifts.
63 Accurate simulation for large numbers of particles (hundreds) with multiple affectors may be possible on some hardware,
64 but on less capable hardware you should expect small irregularties in the simulation as simulates with worse granularity.
67 \qmlproperty ParticleSystem QtQuick.Particles2::Affector::system
68 This is the system which will be affected by the element.
69 If the Affector is a direct child of a ParticleSystem, it will automatically be associated with it.
72 \qmlproperty list<string> QtQuick.Particles2::Affector::groups
73 Which logical particle groups will be affected.
75 If empty, it will affect all particles.
78 \qmlproperty list<string> QtQuick.Particles2::Affector::whenCollidingWith
79 If any logical particle groups are specified here, then the affector
80 will only be triggered if the particle being examined intersects with
81 a particle of one of these groups.
83 This is different from the groups property. The groups property selects which
84 particles might be examined, and if they meet other criteria (including being
85 within the bounds of the Affector, modified by shape) then they will be tested
86 again to see if they intersect with a particles from one of the particle groups
89 By default, no groups are specified.
92 \qmlproperty bool QtQuick.Particles2::Affector::enabled
93 If enabled is set to false, this affector will not affect any particles.
95 Usually this is used to conditionally turn an affector on or off.
97 Default value is true.
100 \qmlproperty bool QtQuick.Particles2::Affector::once
101 If once is set to true, this affector will only affect each particle
102 once in their lifetimes. If the affector normally simulates a continuous
103 effect over time, then it will simulate the effect of one second of time
104 the one instant it affects the particle.
106 Default value is false.
109 \qmlproperty Shape QtQuick.Particles2::Affector::shape
110 If a size has been defined, the shape property can be used to affect a
111 non-rectangular area.
114 \qmlsignal QtQuick.Particles2::Affector::onAffected(x, y)
116 This signal is emitted each time the affector actually affects a particle.
118 x,y are the coordinates of the affected particle, relative to the ParticleSystem.
123 \qmlsignal QtQuick.Particles2::Affector::affectParticle(particle particle, real dt)
125 This handler is called when particles are selected to be affected.
127 dt is the time since the last time it was affected. Use dt to normalize
128 trajectory manipulations to real time.
130 Note that JS is slower to execute, so it is not recommended to use this in
131 high-volume particle systems.
134 \qmlsignal QtQuick.Particles2::Affector::affected(real x, real y)
136 This handler is called when a particle is selected to be affected. It will
137 only be called if signal is set to true.
139 x,y is the particles current position.
141 QQuickParticleAffector::QQuickParticleAffector(QQuickItem *parent) :
142 QQuickItem(parent), m_needsReset(false), m_ignoresTime(false), m_onceOff(false), m_enabled(true)
143 , m_system(0), m_updateIntSet(false), m_shape(new QQuickParticleExtruder(this))
147 bool QQuickParticleAffector::isAffectedConnected()
149 static int idx = QObjectPrivate::get(this)->signalIndex("affected(qreal,qreal)");
150 return QObjectPrivate::get(this)->isSignalConnected(idx);
154 void QQuickParticleAffector::componentComplete()
156 if (!m_system && qobject_cast<QQuickParticleSystem*>(parentItem()))
157 setSystem(qobject_cast<QQuickParticleSystem*>(parentItem()));
158 QQuickItem::componentComplete();
161 bool QQuickParticleAffector::activeGroup(int g) {
162 if (m_updateIntSet){ //This can occur before group ids are properly assigned, but that resets the flag
164 foreach (const QString &p, m_groups)
165 m_groupIds << m_system->groupIds[p];
166 m_updateIntSet = false;
168 return m_groupIds.isEmpty() || m_groupIds.contains(g);
171 bool QQuickParticleAffector::shouldAffect(QQuickParticleData* d)
175 if (activeGroup(d->group)){
176 if ((m_onceOff && m_onceOffed.contains(qMakePair(d->group, d->index)))
179 //Need to have previous location for affected anyways
180 if (width() == 0 || height() == 0
181 || m_shape->contains(QRectF(m_offset.x(), m_offset.y(), width(), height()), QPointF(d->curX(), d->curY()))){
182 if (m_whenCollidingWith.isEmpty() || isColliding(d)){
191 void QQuickParticleAffector::postAffect(QQuickParticleData* d)
193 m_system->needsReset << d;
195 m_onceOffed << qMakePair(d->group, d->index);
196 if (isAffectedConnected())
197 emit affected(d->curX(), d->curY());
200 const qreal QQuickParticleAffector::simulationDelta = 0.020;
201 const qreal QQuickParticleAffector::simulationCutoff = 1.000;//If this goes above 1.0, then m_once behaviour needs special codepath
203 void QQuickParticleAffector::affectSystem(qreal dt)
207 //If not reimplemented, calls affectParticle per particle
208 //But only on particles in targeted system/area
209 updateOffsets();//### Needed if an ancestor is transformed.
212 foreach (QQuickParticleGroupData* gd, m_system->groupData) {
213 if (activeGroup(m_system->groupData.key(gd))) {
214 foreach (QQuickParticleData* d, gd->data) {
215 if (shouldAffect(d)) {
216 bool affected = false;
218 if (!m_ignoresTime && myDt < simulationCutoff) {
219 int realTime = m_system->timeInt;
220 m_system->timeInt -= myDt * 1000.0;
221 while (myDt > simulationDelta) {
222 m_system->timeInt += simulationDelta * 1000.0;
223 if (d->alive())//Only affect during the parts it was alive for
224 affected = affectParticle(d, simulationDelta) || affected;
225 myDt -= simulationDelta;
227 m_system->timeInt = realTime;
230 affected = affectParticle(d, myDt) || affected;
239 bool QQuickParticleAffector::affectParticle(QQuickParticleData *, qreal )
244 void QQuickParticleAffector::reset(QQuickParticleData* pd)
245 {//TODO: This, among other ones, should be restructured so they don't all need to remember to call the superclass
247 if (activeGroup(pd->group))
248 m_onceOffed.remove(qMakePair(pd->group, pd->index));
251 void QQuickParticleAffector::updateOffsets()
254 m_offset = m_system->mapFromItem(this, QPointF(0, 0));
257 bool QQuickParticleAffector::isColliding(QQuickParticleData *d)
259 qreal myCurX = d->curX();
260 qreal myCurY = d->curY();
261 qreal myCurSize = d->curSize()/2;
262 foreach (const QString &group, m_whenCollidingWith){
263 foreach (QQuickParticleData* other, m_system->groupData[m_system->groupIds[group]]->data){
264 if (!other->stillAlive())
266 qreal otherCurX = other->curX();
267 qreal otherCurY = other->curY();
268 qreal otherCurSize = other->curSize()/2;
269 if ((myCurX + myCurSize > otherCurX - otherCurSize
270 && myCurX - myCurSize < otherCurX + otherCurSize)
271 && (myCurY + myCurSize > otherCurY - otherCurSize
272 && myCurY - myCurSize < otherCurY + otherCurSize))