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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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