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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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