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

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