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

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