Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquicktrailemitter.cpp
Go to the documentation of this file.
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 <QtCore/qtconfigmacros.h>
5#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
6
8
9#include <private/qqmlglobal_p.h>
10#include <private/qquickv4particledata_p.h>
11
12#include <QtCore/qrandom.h>
13
14#include <cmath>
15
16QT_BEGIN_NAMESPACE
17
18/*!
19 \qmltype TrailEmitter
20 \nativetype QQuickTrailEmitter
21 \inqmlmodule QtQuick.Particles
22 \inherits Emitter
23 \brief Emits logical particles from other logical particles.
24 \ingroup qtquick-particles
25
26 This element emits logical particles into the ParticleSystem, with the
27 starting positions based on those of other logical particles.
28*/
29QQuickTrailEmitter::QQuickTrailEmitter(QQuickItem *parent) :
30 QQuickParticleEmitter(parent)
31 , m_particlesPerParticlePerSecond(0)
32 , m_lastTimeStamp(0)
33 , m_emitterXVariation(0)
34 , m_emitterYVariation(0)
35 , m_followCount(0)
36 , m_emissionExtruder(nullptr)
37 , m_defaultEmissionExtruder(new QQuickParticleExtruder(this))
38{
39 //TODO: If followed increased their size
40 connect(this, &QQuickTrailEmitter::followChanged,
41 this, &QQuickTrailEmitter::recalcParticlesPerSecond);
42 connect(this, &QQuickTrailEmitter::particleDurationChanged,
43 this, &QQuickTrailEmitter::recalcParticlesPerSecond);
44 connect(this, &QQuickTrailEmitter::particlesPerParticlePerSecondChanged,
45 this, &QQuickTrailEmitter::recalcParticlesPerSecond);
46}
47
48/*!
49 \qmlproperty string QtQuick.Particles::TrailEmitter::follow
50
51 The type of logical particle which this is emitting from.
52*/
53/*!
54 \qmlproperty real QtQuick.Particles::TrailEmitter::velocityFromMovement
55
56 If this value is non-zero, then any movement of the emitter will provide additional
57 starting velocity to the particles based on the movement. The additional vector will be the
58 same angle as the emitter's movement, with a magnitude that is the magnitude of the emitters
59 movement multiplied by velocityFromMovement.
60
61 Default value is 0.
62*/
63/*!
64 \qmlproperty Shape QtQuick.Particles::TrailEmitter::emitShape
65
66 As the area of a TrailEmitter is the area it follows, a separate shape can be provided
67 to be the shape it emits out of. This shape has width and height specified by emitWidth
68 and emitHeight, and is centered on the followed particle's position.
69
70 The default shape is a filled Rectangle.
71*/
72/*!
73 \qmlproperty real QtQuick.Particles::TrailEmitter::emitWidth
74
75 The width in pixels the emitShape is scaled to. If set to TrailEmitter.ParticleSize,
76 the width will be the current size of the particle being followed.
77
78 Default is 0.
79*/
80/*!
81 \qmlproperty real QtQuick.Particles::TrailEmitter::emitHeight
82
83 The height in pixels the emitShape is scaled to. If set to TrailEmitter.ParticleSize,
84 the height will be the current size of the particle being followed.
85
86 Default is 0.
87*/
88/*!
89 \qmlproperty real QtQuick.Particles::TrailEmitter::emitRatePerParticle
90*/
91/*!
92 \qmlsignal QtQuick.Particles::TrailEmitter::emitFollowParticles(Array particles, Particle followed)
93
94 This signal is emitted when particles are emitted from the \a followed particle. \a particles contains an array of particle objects which can be directly manipulated.
95
96 If you use this signal handler, emitParticles will not be emitted.
97*/
98
99bool QQuickTrailEmitter::isEmitFollowConnected()
100{
101 IS_SIGNAL_CONNECTED(
102 this, QQuickTrailEmitter, emitFollowParticles,
103 (const QList<QQuickV4ParticleData> &, const QQuickV4ParticleData &));
104}
105
106void QQuickTrailEmitter::recalcParticlesPerSecond(){
107 if (!m_system)
108 return;
109 m_followCount = m_system->groupData[m_system->groupIds[m_follow]]->size();
110 if (!m_followCount){
111 setParticlesPerSecond(1);//XXX: Fix this horrendous hack, needed so they aren't turned off from start (causes crashes - test that when gone you don't crash with 0 PPPS)
112 }else{
113 setParticlesPerSecond(m_particlesPerParticlePerSecond * m_followCount);
114 m_lastEmission.resize(m_followCount);
115 m_lastEmission.fill(m_lastTimeStamp);
116 }
117}
118
119void QQuickTrailEmitter::reset()
120{
121 m_followCount = 0;
122}
123
124void QQuickTrailEmitter::emitWindow(int timeStamp)
125{
126 if (m_system == nullptr)
127 return;
128 if (!m_enabled && !m_pulseLeft && m_burstQueue.isEmpty())
129 return;
130 if (m_followCount != m_system->groupData[m_system->groupIds[m_follow]]->size()){
131 qreal oldPPS = m_particlesPerSecond;
132 recalcParticlesPerSecond();
133 if (m_particlesPerSecond != oldPPS)
134 return;//system may need to update
135 }
136
137 if (m_pulseLeft){
138 m_pulseLeft -= timeStamp - m_lastTimeStamp * 1000.;
139 if (m_pulseLeft < 0){
140 timeStamp += m_pulseLeft;
141 m_pulseLeft = 0;
142 }
143 }
144
145 //TODO: Implement startTime and velocityFromMovement
146 qreal time = timeStamp / 1000.;
147 qreal particleRatio = 1. / m_particlesPerParticlePerSecond;
148 qreal pt;
149 qreal maxLife = (m_particleDuration + m_particleDurationVariation)/1000.0;
150
151 //Have to map it into this system, because particlesystem automaps it back
152 QPointF offset = m_system->mapFromItem(this, QPointF(0, 0));
153 qreal sizeAtEnd = m_particleEndSize >= 0 ? m_particleEndSize : m_particleSize;
154
155 int gId = m_system->groupIds[m_follow];
156 int gId2 = groupId();
157 for (int i=0; i<m_system->groupData[gId]->data.size(); i++) {
158 QQuickParticleData *d = m_system->groupData[gId]->data[i];
159 if (!d->stillAlive(m_system)){
160 m_lastEmission[i] = time; //Should only start emitting when it returns to life
161 continue;
162 }
163 pt = m_lastEmission[i];
164 if (pt < d->t)
165 pt = d->t;
166 if (pt + maxLife < time)//We missed so much, that we should skip emiting particles that are dead by now
167 pt = time - maxLife;
168
169 if ((width() || height()) && !effectiveExtruder()->contains(QRectF(offset.x(), offset.y(), width(), height()),
170 QPointF(d->curX(m_system), d->curY(m_system)))) {
171 m_lastEmission[d->index] = time;//jump over this time period without emitting, because it's outside
172 continue;
173 }
174
175 QList<QQuickParticleData*> toEmit;
176
177 while (pt < time || !m_burstQueue.isEmpty()){
178 QQuickParticleData* datum = m_system->newDatum(gId2, !m_overwrite);
179 if (datum){//else, skip this emission
180 // Particle timestamp
181 datum->t = pt;
182 datum->lifeSpan =
183 (m_particleDuration
184 + (QRandomGenerator::global()->bounded((m_particleDurationVariation*2) + 1) - m_particleDurationVariation))
185 / 1000.0;
186
187 // Particle position
188 // Note that burst location doesn't get used for follow emitter
189 qreal followT = pt - d->t;
190 qreal followT2 = followT * followT * 0.5;
191 qreal eW = m_emitterXVariation < 0 ? d->curSize(m_system) : m_emitterXVariation;
192 qreal eH = m_emitterYVariation < 0 ? d->curSize(m_system) : m_emitterYVariation;
193 //Subtract offset, because PS expects this in emitter coordinates
194 QRectF boundsRect(d->x - offset.x() + d->vx * followT + d->ax * followT2 - eW/2,
195 d->y - offset.y() + d->vy * followT + d->ay * followT2 - eH/2,
196 eW, eH);
197
198 QQuickParticleExtruder* effectiveEmissionExtruder = m_emissionExtruder ? m_emissionExtruder : m_defaultEmissionExtruder;
199 const QPointF &newPos = effectiveEmissionExtruder->extrude(boundsRect);
200 datum->x = newPos.x();
201 datum->y = newPos.y();
202
203 // Particle velocity
204 const QPointF &velocity = m_velocity->sample(newPos);
205 datum->vx = velocity.x()
206 + m_velocity_from_movement * d->vx;
207 datum->vy = velocity.y()
208 + m_velocity_from_movement * d->vy;
209
210 // Particle acceleration
211 const QPointF &accel = m_acceleration->sample(newPos);
212 datum->ax = accel.x();
213 datum->ay = accel.y();
214
215 // Particle size
216 float sizeVariation = -m_particleSizeVariation
217 + QRandomGenerator::global()->generateDouble() * m_particleSizeVariation * 2;
218
219 float size = qMax((qreal)0.0, m_particleSize + sizeVariation);
220 float endSize = qMax((qreal)0.0, sizeAtEnd + sizeVariation);
221
222 datum->size = size * float(m_enabled);
223 datum->endSize = endSize * float(m_enabled);
224
225 toEmit << datum;
226
227 m_system->emitParticle(datum, this);
228 }
229 if (!m_burstQueue.isEmpty()){
230 m_burstQueue.first().first--;
231 if (m_burstQueue.first().first <= 0)
232 m_burstQueue.pop_front();
233 }else{
234 pt += particleRatio;
235 }
236 }
237
238 foreach (QQuickParticleData* d, toEmit)
239 m_system->emitParticle(d, this);
240
241 if (isEmitConnected() || isEmitFollowConnected()) {
242
243 QList<QQuickV4ParticleData> particles;
244 particles.reserve(toEmit.size());
245 for (QQuickParticleData *particle : std::as_const(toEmit))
246 particles.push_back(particle->v4Value(m_system));
247
248 if (isEmitFollowConnected()) {
249 //A chance for many arbitrary JS changes
250 emit emitFollowParticles(particles, d->v4Value(m_system));
251 } else if (isEmitConnected()) {
252 emit emitParticles(particles);//A chance for arbitrary JS changes
253 }
254 }
255 m_lastEmission[d->index] = pt;
256 }
257
258 m_lastTimeStamp = time;
259}
260QT_END_NAMESPACE
261
262#include "moc_qquicktrailemitter_p.cpp"