1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQuick module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qquickparticleaffector_p.h"
41#include <QDebug>
42#include <private/qqmlglobal_p.h>
43QT_BEGIN_NAMESPACE
44
45/*!
46 \qmltype Affector
47 \instantiates QQuickParticleAffector
48 \inqmlmodule QtQuick.Particles
49 \brief Applies alterations to the attributes of logical particles at any
50 point in their lifetime.
51 \ingroup qtquick-particles
52
53 The base Affector does not alter any attributes, but can be used to emit a signal
54 when a particle meets certain conditions.
55
56 If an affector has a defined size, then it will only affect particles within its size and position on screen.
57
58 Affectors have different performance characteristics to the other particle system elements. In particular,
59 they have some simplifications to try to maintain a simulation at real-time or faster. When running a system
60 with Affectors, irregular frame timings that grow too large ( > one second per frame) will cause the Affectors
61 to try and cut corners with a faster but less accurate simulation. If the system has multiple affectors the order
62 in which they are applied is not guaranteed, and when simulating larger time shifts they will simulate the whole
63 shift each, which can lead to different results compared to smaller time shifts.
64
65 Accurate simulation for large numbers of particles (hundreds) with multiple affectors may be possible on some hardware,
66 but on less capable hardware you should expect small irregularties in the simulation as simulates with worse granularity.
67*/
68/*!
69 \qmlproperty ParticleSystem QtQuick.Particles::Affector::system
70 This is the system which will be affected by the element.
71 If the Affector is a direct child of a ParticleSystem, it will automatically be associated with it.
72*/
73/*!
74 \qmlproperty list<string> QtQuick.Particles::Affector::groups
75 Which logical particle groups will be affected.
76
77 If empty, it will affect all particles.
78*/
79/*!
80 \qmlproperty list<string> QtQuick.Particles::Affector::whenCollidingWith
81 If any logical particle groups are specified here, then the affector
82 will only be triggered if the particle being examined intersects with
83 a particle of one of these groups.
84
85 This is different from the groups property. The groups property selects which
86 particles might be examined, and if they meet other criteria (including being
87 within the bounds of the Affector, modified by shape) then they will be tested
88 again to see if they intersect with a particles from one of the particle groups
89 in whenCollidingWith.
90
91 By default, no groups are specified.
92*/
93/*!
94 \qmlproperty bool QtQuick.Particles::Affector::enabled
95 If enabled is set to false, this affector will not affect any particles.
96
97 Usually this is used to conditionally turn an affector on or off.
98
99 Default value is true.
100*/
101/*!
102 \qmlproperty bool QtQuick.Particles::Affector::once
103 If once is set to true, this affector will only affect each particle
104 once in their lifetimes. If the affector normally simulates a continuous
105 effect over time, then it will simulate the effect of one second of time
106 the one instant it affects the particle.
107
108 Default value is false.
109*/
110/*!
111 \qmlproperty Shape QtQuick.Particles::Affector::shape
112 If a size has been defined, the shape property can be used to affect a
113 non-rectangular area.
114*/
115/*!
116 \qmlsignal QtQuick.Particles::Affector::affected(real x, real y)
117
118 This signal is emitted when a particle is selected to be affected. It will not be emitted
119 if a particle is considered by the Affector but not actually altered in any way.
120
121 In the special case where an Affector has no possible effect (e.g. Affector {}), this signal
122 will be emitted for all particles being considered if you connect to it. This allows you to
123 execute arbitrary code in response to particles (use the Affector::onAffectParticles
124 signal handler if you want to execute code which affects the particles
125 themselves). As this executes JavaScript code per particle, it is not recommended to use this
126 signal with a high-volume particle system.
127
128 (\a {x}, \a {y}) is the particle's current position.
129*/
130QQuickParticleAffector::QQuickParticleAffector(QQuickItem *parent) :
131 QQuickItem(parent), m_needsReset(false), m_ignoresTime(false), m_onceOff(false), m_enabled(true)
132 , m_system(nullptr), m_updateIntSet(false), m_shape(new QQuickParticleExtruder(this))
133{
134}
135
136bool QQuickParticleAffector::isAffectedConnected()
137{
138 IS_SIGNAL_CONNECTED(this, QQuickParticleAffector, affected, (qreal,qreal));
139}
140
141
142void QQuickParticleAffector::componentComplete()
143{
144 if (!m_system && qobject_cast<QQuickParticleSystem*>(object: parentItem()))
145 setSystem(qobject_cast<QQuickParticleSystem*>(object: parentItem()));
146 QQuickItem::componentComplete();
147}
148
149bool QQuickParticleAffector::activeGroup(int g) {
150 if (m_updateIntSet){ //This can occur before group ids are properly assigned, but that resets the flag
151 m_groupIds.clear();
152 foreach (const QString &p, m_groups)
153 m_groupIds << m_system->groupIds[p];
154 m_updateIntSet = false;
155 }
156 return m_groupIds.isEmpty() || m_groupIds.contains(value: g);
157}
158
159bool QQuickParticleAffector::shouldAffect(QQuickParticleData* d)
160{
161 if (!d)
162 return false;
163 if (activeGroup(g: d->groupId)){
164 if ((m_onceOff && m_onceOffed.contains(t: qMakePair(x: d->groupId, y: d->index)))
165 || !d->stillAlive(system: m_system))
166 return false;
167 //Need to have previous location for affected anyways
168 if (width() == 0 || height() == 0
169 || m_shape->contains(bounds: QRectF(m_offset.x(), m_offset.y(), width(), height()), point: QPointF(d->curX(particleSystem: m_system), d->curY(particleSystem: m_system)))){
170 if (m_whenCollidingWith.isEmpty() || isColliding(d)){
171 return true;
172 }
173 }
174 }
175 return false;
176
177}
178
179void QQuickParticleAffector::postAffect(QQuickParticleData* d)
180{
181 m_system->needsReset << d;
182 if (m_onceOff)
183 m_onceOffed << qMakePair(x: d->groupId, y: d->index);
184 if (isAffectedConnected())
185 emit affected(x: d->curX(particleSystem: m_system), y: d->curY(particleSystem: m_system));
186}
187
188const qreal QQuickParticleAffector::simulationDelta = 0.020;
189const qreal QQuickParticleAffector::simulationCutoff = 1.000;//If this goes above 1.0, then m_once behaviour needs special codepath
190
191void QQuickParticleAffector::affectSystem(qreal dt)
192{
193 if (!m_enabled)
194 return;
195 //If not reimplemented, calls affectParticle per particle
196 //But only on particles in targeted system/area
197 updateOffsets();//### Needed if an ancestor is transformed.
198 if (m_onceOff)
199 dt = 1.0;
200 foreach (QQuickParticleGroupData* gd, m_system->groupData) {
201 if (activeGroup(g: gd->index)) {
202 foreach (QQuickParticleData* d, gd->data) {
203 if (shouldAffect(d)) {
204 bool affected = false;
205 qreal myDt = dt;
206 if (!m_ignoresTime && myDt < simulationCutoff) {
207 int realTime = m_system->timeInt;
208 m_system->timeInt -= myDt * 1000.0;
209 while (myDt > simulationDelta) {
210 m_system->timeInt += simulationDelta * 1000.0;
211 if (d->alive(system: m_system))//Only affect during the parts it was alive for
212 affected = affectParticle(d, dt: simulationDelta) || affected;
213 myDt -= simulationDelta;
214 }
215 m_system->timeInt = realTime;
216 }
217 if (myDt > 0.0)
218 affected = affectParticle(d, dt: myDt) || affected;
219 if (affected)
220 postAffect(d);
221 }
222 }
223 }
224 }
225}
226
227bool QQuickParticleAffector::affectParticle(QQuickParticleData *, qreal )
228{
229 return true;
230}
231
232void QQuickParticleAffector::reset(QQuickParticleData* pd)
233{//TODO: This, among other ones, should be restructured so they don't all need to remember to call the superclass
234 if (m_onceOff)
235 if (activeGroup(g: pd->groupId))
236 m_onceOffed.remove(t: qMakePair(x: pd->groupId, y: pd->index));
237}
238
239void QQuickParticleAffector::updateOffsets()
240{
241 if (m_system)
242 m_offset = m_system->mapFromItem(item: this, point: QPointF(0, 0));
243}
244
245bool QQuickParticleAffector::isColliding(QQuickParticleData *d) const
246{
247 qreal myCurX = d->curX(particleSystem: m_system);
248 qreal myCurY = d->curY(particleSystem: m_system);
249 qreal myCurSize = d->curSize(particleSystem: m_system) / 2;
250 foreach (const QString &group, m_whenCollidingWith){
251 foreach (QQuickParticleData* other, m_system->groupData[m_system->groupIds[group]]->data){
252 if (!other->stillAlive(system: m_system))
253 continue;
254 qreal otherCurX = other->curX(particleSystem: m_system);
255 qreal otherCurY = other->curY(particleSystem: m_system);
256 qreal otherCurSize = other->curSize(particleSystem: m_system) / 2;
257 if ((myCurX + myCurSize > otherCurX - otherCurSize
258 && myCurX - myCurSize < otherCurX + otherCurSize)
259 && (myCurY + myCurSize > otherCurY - otherCurSize
260 && myCurY - myCurSize < otherCurY + otherCurSize))
261 return true;
262 }
263 }
264 return false;
265}
266
267QT_END_NAMESPACE
268
269#include "moc_qquickparticleaffector_p.cpp"
270

source code of qtdeclarative/src/particles/qquickparticleaffector.cpp