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

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