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 "qquickparticleemitter_p.h"
41#include <private/qqmlengine_p.h>
42#include <private/qqmlglobal_p.h>
43#include <private/qjsvalue_p.h>
44#include <QRandomGenerator>
45QT_BEGIN_NAMESPACE
46
47
48/*!
49 \qmltype Emitter
50 \instantiates QQuickParticleEmitter
51 \inqmlmodule QtQuick.Particles
52 \brief Emits logical particles.
53 \ingroup qtquick-particles
54
55 This element emits logical particles into the ParticleSystem, with the
56 given starting attributes.
57
58 Note that logical particles are not
59 automatically rendered, you will need to have one or more
60 ParticlePainter elements visualizing them.
61
62 Note that the given starting attributes can be modified at any point
63 in the particle's lifetime by any Affector element in the same
64 ParticleSystem. This includes attributes like lifespan.
65*/
66
67
68/*!
69 \qmlproperty ParticleSystem QtQuick.Particles::Emitter::system
70
71 This is the Particle system that the Emitter will emit into.
72 This can be omitted if the Emitter is a direct child of the ParticleSystem
73*/
74/*!
75 \qmlproperty string QtQuick.Particles::Emitter::group
76
77 This is the logical particle group which it will emit into.
78
79 Default value is "" (empty string).
80*/
81/*!
82 \qmlproperty Shape QtQuick.Particles::Emitter::shape
83
84 This shape is applied with the size of the Emitter. Particles will be emitted
85 randomly from any area covered by the shape.
86
87 The default shape is a filled in rectangle, which corresponds to the full bounding
88 box of the Emitter.
89*/
90/*!
91 \qmlproperty bool QtQuick.Particles::Emitter::enabled
92
93 If set to false, the emitter will cease emissions until it is set to true.
94
95 Default value is true.
96*/
97/*!
98 \qmlproperty real QtQuick.Particles::Emitter::emitRate
99
100 Number of particles emitted per second.
101
102 Default value is 10 particles per second.
103*/
104/*!
105 \qmlproperty int QtQuick.Particles::Emitter::lifeSpan
106
107 The time in milliseconds each emitted particle should last for.
108
109 If you do not want particles to automatically die after a time, for example if
110 you wish to dispose of them manually, set lifeSpan to Emitter.InfiniteLife.
111
112 lifeSpans greater than or equal to 600000 (10 minutes) will be treated as infinite.
113 Particles with lifeSpans less than or equal to 0 will start out dead.
114
115 Default value is 1000 (one second).
116*/
117/*!
118 \qmlproperty int QtQuick.Particles::Emitter::lifeSpanVariation
119
120 Particle lifespans will vary by up to this much in either direction.
121
122 Default value is 0.
123*/
124
125/*!
126 \qmlproperty int QtQuick.Particles::Emitter::maximumEmitted
127
128 The maximum number of particles at a time that this emitter will have alive.
129
130 This can be set as a performance optimization (when using burst and pulse) or
131 to stagger emissions.
132
133 If this is set to a number below zero, then there is no maximum limit on the number
134 of particles this emitter can have alive.
135
136 The default value is -1.
137*/
138/*!
139 \qmlproperty int QtQuick.Particles::Emitter::startTime
140
141 If this value is set when the emitter is loaded, then it will emit particles from the
142 past, up to startTime milliseconds ago. These will simulate as if they were emitted then,
143 but will not have any affectors applied to them. Affectors will take effect from the present time.
144*/
145/*!
146 \qmlproperty real QtQuick.Particles::Emitter::size
147
148 The size in pixels of the particles at the start of their life.
149
150 Default value is 16.
151*/
152/*!
153 \qmlproperty real QtQuick.Particles::Emitter::endSize
154
155 The size in pixels of the particles at the end of their life. Size will
156 be linearly interpolated during the life of the particle from this value and
157 size. If endSize is -1, then the size of the particle will remain constant at
158 the starting size.
159
160 Default value is -1.
161*/
162/*!
163 \qmlproperty real QtQuick.Particles::Emitter::sizeVariation
164
165 The size of a particle can vary by this much up or down from size/endSize. The same
166 random addition is made to both size and endSize for a single particle.
167
168 Default value is 0.
169*/
170/*!
171 \qmlproperty StochasticDirection QtQuick.Particles::Emitter::velocity
172
173 The starting velocity of the particles emitted.
174*/
175/*!
176 \qmlproperty StochasticDirection QtQuick.Particles::Emitter::acceleration
177
178 The starting acceleraton of the particles emitted.
179*/
180/*!
181 \qmlproperty qreal QtQuick.Particles::Emitter::velocityFromMovement
182
183 If this value is non-zero, then any movement of the emitter will provide additional
184 starting velocity to the particles based on the movement. The additional vector will be the
185 same angle as the emitter's movement, with a magnitude that is the magnitude of the emitters
186 movement multiplied by velocityFromMovement.
187
188 Default value is 0.
189*/
190
191/*!
192 \qmlsignal QtQuick.Particles::Emitter::emitParticles(Array particles)
193
194 This signal is emitted when particles are emitted. \a particles is a JavaScript
195 array of Particle objects. You can modify particle attributes directly within the handler.
196
197 \note JavaScript is slower to execute, so it is not recommended to use this in
198 high-volume particle systems.
199*/
200
201/*! \qmlmethod QtQuick.Particles::Emitter::burst(int count)
202
203 Emits a number of particles, specified by \a count, from this emitter immediately.
204*/
205
206/*! \qmlmethod QtQuick.Particles::Emitter::burst(int count, int x, int y)
207
208 Emits a number of particles, specified by \a count, from this emitter immediately.
209 The particles are emitted as if the Emitter was positioned at (\a {x}, \a {y}) but
210 all other properties are the same.
211*/
212
213/*! \qmlmethod QtQuick.Particles::Emitter::pulse(int duration)
214
215 If the emitter is not enabled, enables it for a specified \a duration
216 (in milliseconds) and then switches it back off.
217*/
218
219QQuickParticleEmitter::QQuickParticleEmitter(QQuickItem *parent) :
220 QQuickItem(parent)
221 , m_particlesPerSecond(10)
222 , m_particleDuration(1000)
223 , m_particleDurationVariation(0)
224 , m_enabled(true)
225 , m_system(nullptr)
226 , m_extruder(nullptr)
227 , m_defaultExtruder(nullptr)
228 , m_velocity(&m_nullVector)
229 , m_acceleration(&m_nullVector)
230 , m_particleSize(16)
231 , m_particleEndSize(-1)
232 , m_particleSizeVariation(0)
233 , m_startTime(0)
234 , m_overwrite(true)
235 , m_pulseLeft(0)
236 , m_maxParticleCount(-1)
237 , m_velocity_from_movement(0)
238 , m_reset_last(true)
239 , m_last_timestamp(-1)
240 , m_last_emission(0)
241 , m_groupIdNeedRecalculation(false)
242 , m_groupId(QQuickParticleGroupData::DefaultGroupID)
243
244{
245 //TODO: Reset velocity/acc back to null vector? Or allow null pointer?
246 connect(sender: this, SIGNAL(particlesPerSecondChanged(qreal)),
247 receiver: this, SIGNAL(particleCountChanged()));
248 connect(sender: this, SIGNAL(particleDurationChanged(int)),
249 receiver: this, SIGNAL(particleCountChanged()));
250}
251
252QQuickParticleEmitter::~QQuickParticleEmitter()
253{
254 if (m_defaultExtruder)
255 delete m_defaultExtruder;
256}
257
258bool QQuickParticleEmitter::isEmitConnected()
259{
260 IS_SIGNAL_CONNECTED(this, QQuickParticleEmitter, emitParticles, (const QJSValue &));
261}
262
263void QQuickParticleEmitter::reclaculateGroupId() const
264{
265 if (!m_system) {
266 m_groupId = QQuickParticleGroupData::InvalidID;
267 return;
268 }
269 m_groupId = m_system->groupIds.value(key: group(), defaultValue: QQuickParticleGroupData::InvalidID);
270 m_groupIdNeedRecalculation = m_groupId == QQuickParticleGroupData::InvalidID;
271}
272
273void QQuickParticleEmitter::componentComplete()
274{
275 if (!m_system && qobject_cast<QQuickParticleSystem*>(object: parentItem()))
276 setSystem(qobject_cast<QQuickParticleSystem*>(object: parentItem()));
277 if (m_system)
278 m_system->finishRegisteringParticleEmitter(e: this);
279 QQuickItem::componentComplete();
280}
281
282void QQuickParticleEmitter::setEnabled(bool arg)
283{
284 if (m_enabled != arg) {
285 m_enabled = arg;
286 emit enabledChanged(arg);
287 }
288}
289
290
291QQuickParticleExtruder* QQuickParticleEmitter::effectiveExtruder()
292{
293 if (m_extruder)
294 return m_extruder;
295 if (!m_defaultExtruder)
296 m_defaultExtruder = new QQuickParticleExtruder;
297 return m_defaultExtruder;
298}
299
300void QQuickParticleEmitter::pulse(int milliseconds)
301{
302 if (!m_enabled)
303 m_pulseLeft = milliseconds;
304}
305
306void QQuickParticleEmitter::burst(int num)
307{
308 m_burstQueue << qMakePair(x: num, y: QPointF(x(), y()));
309}
310
311void QQuickParticleEmitter::burst(int num, qreal x, qreal y)
312{
313 m_burstQueue << qMakePair(x: num, y: QPointF(x, y));
314}
315
316void QQuickParticleEmitter::setMaxParticleCount(int arg)
317{
318 if (m_maxParticleCount != arg) {
319 if (arg < 0 && m_maxParticleCount >= 0){
320 connect(sender: this, SIGNAL(particlesPerSecondChanged(qreal)),
321 receiver: this, SIGNAL(particleCountChanged()));
322 connect(sender: this, SIGNAL(particleDurationChanged(int)),
323 receiver: this, SIGNAL(particleCountChanged()));
324 }else if (arg >= 0 && m_maxParticleCount < 0){
325 disconnect(sender: this, SIGNAL(particlesPerSecondChanged(qreal)),
326 receiver: this, SIGNAL(particleCountChanged()));
327 disconnect(sender: this, SIGNAL(particleDurationChanged(int)),
328 receiver: this, SIGNAL(particleCountChanged()));
329 }
330 m_overwrite = arg < 0;
331 m_maxParticleCount = arg;
332 emit maximumEmittedChanged(arg);
333 emit particleCountChanged();
334 }
335}
336
337void QQuickParticleEmitter::setVelocityFromMovement(qreal t)
338{
339 if (t == m_velocity_from_movement)
340 return;
341 m_velocity_from_movement = t;
342 emit velocityFromMovementChanged();
343}
344
345void QQuickParticleEmitter::reset()
346{
347 m_reset_last = true;
348}
349
350void QQuickParticleEmitter::emitWindow(int timeStamp)
351{
352 if (m_system == nullptr)
353 return;
354 if ((!m_enabled || m_particlesPerSecond <= 0)&& !m_pulseLeft && m_burstQueue.isEmpty()){
355 m_reset_last = true;
356 return;
357 }
358
359 if (m_reset_last) {
360 m_last_emitter = m_last_last_emitter = QPointF(x(), y());
361 if (m_last_timestamp == -1)
362 m_last_timestamp = (timeStamp - m_startTime)/1000.;
363 else
364 m_last_timestamp = timeStamp/1000.;
365 m_last_emission = m_last_timestamp;
366 m_reset_last = false;
367 m_emitCap = -1;
368 }
369
370 if (m_pulseLeft){
371 m_pulseLeft -= timeStamp - m_last_timestamp * 1000.;
372 if (m_pulseLeft < 0){
373 if (!m_enabled)
374 timeStamp += m_pulseLeft;
375 m_pulseLeft = 0;
376 }
377 }
378 qreal time = timeStamp / 1000.;
379 qreal particleRatio = 1. / m_particlesPerSecond;
380 qreal pt = m_last_emission;
381 qreal maxLife = (m_particleDuration + m_particleDurationVariation)/1000.0;
382 if (pt + maxLife < time)//We missed so much, that we should skip emiting particles that are dead by now
383 pt = time - maxLife;
384
385 qreal opt = pt; // original particle time
386 qreal dt = time - m_last_timestamp; // timestamp delta...
387 if (!dt)
388 dt = 0.000001;
389
390 // emitter difference since last...
391 qreal dex = (x() - m_last_emitter.x());
392 qreal dey = (y() - m_last_emitter.y());
393
394 qreal ax = (m_last_last_emitter.x() + m_last_emitter.x()) / 2;
395 qreal bx = m_last_emitter.x();
396 qreal cx = (x() + m_last_emitter.x()) / 2;
397 qreal ay = (m_last_last_emitter.y() + m_last_emitter.y()) / 2;
398 qreal by = m_last_emitter.y();
399 qreal cy = (y() + m_last_emitter.y()) / 2;
400
401 qreal sizeAtEnd = m_particleEndSize >= 0 ? m_particleEndSize : m_particleSize;
402 qreal emitter_x_offset = m_last_emitter.x() - x();
403 qreal emitter_y_offset = m_last_emitter.y() - y();
404 if (!m_burstQueue.isEmpty() && !m_pulseLeft && !m_enabled)//'outside time' emissions only
405 pt = time;
406
407 QList<QQuickParticleData*> toEmit;
408
409 while ((pt < time && m_emitCap) || !m_burstQueue.isEmpty()) {
410 //int pos = m_last_particle % m_particle_count;
411 QQuickParticleData* datum = m_system->newDatum(groupId: m_system->groupIds[m_group], respectLimits: !m_overwrite);
412 if (datum){//actually emit(otherwise we've been asked to skip this one)
413 qreal t = 1 - (pt - opt) / dt;
414 qreal vx =
415 - 2 * ax * (1 - t)
416 + 2 * bx * (1 - 2 * t)
417 + 2 * cx * t;
418 qreal vy =
419 - 2 * ay * (1 - t)
420 + 2 * by * (1 - 2 * t)
421 + 2 * cy * t;
422
423
424 // Particle timestamp
425 datum->t = pt;
426 datum->lifeSpan =
427 (m_particleDuration
428 + (QRandomGenerator::global()->bounded(highest: (m_particleDurationVariation*2) + 1) - m_particleDurationVariation))
429 / 1000.0;
430
431 if (datum->lifeSpan >= m_system->maxLife){
432 datum->lifeSpan = m_system->maxLife;
433 if (m_emitCap == -1)
434 m_emitCap = particleCount();
435 m_emitCap--;//emitCap keeps us from reemitting 'infinite' particles after their life. Unless you reset the emitter.
436 }
437
438 // Particle position
439 QRectF boundsRect;
440 if (!m_burstQueue.isEmpty()){
441 boundsRect = QRectF(m_burstQueue.first().second.x() - x(), m_burstQueue.first().second.y() - y(),
442 width(), height());
443 } else {
444 boundsRect = QRectF(emitter_x_offset + dex * (pt - opt) / dt, emitter_y_offset + dey * (pt - opt) / dt
445 , width(), height());
446 }
447 QPointF newPos = effectiveExtruder()->extrude(boundsRect);
448 datum->x = newPos.x();
449 datum->y = newPos.y();
450
451 // Particle velocity
452 const QPointF &velocity = m_velocity->sample(from: newPos);
453 datum->vx = velocity.x()
454 + m_velocity_from_movement * vx;
455 datum->vy = velocity.y()
456 + m_velocity_from_movement * vy;
457
458 // Particle acceleration
459 const QPointF &accel = m_acceleration->sample(from: newPos);
460 datum->ax = accel.x();
461 datum->ay = accel.y();
462
463 // Particle size
464 float sizeVariation = -m_particleSizeVariation
465 + QRandomGenerator::global()->bounded(highest: m_particleSizeVariation * 2);
466
467 float size = qMax(a: (qreal)0.0 , b: m_particleSize + sizeVariation);
468 float endSize = qMax(a: (qreal)0.0 , b: sizeAtEnd + sizeVariation);
469
470 datum->size = size;// * float(m_emitting);
471 datum->endSize = endSize;// * float(m_emitting);
472
473 toEmit << datum;
474 }
475 if (m_burstQueue.isEmpty()){
476 pt += particleRatio;
477 }else{
478 m_burstQueue.first().first--;
479 if (m_burstQueue.first().first <= 0)
480 m_burstQueue.pop_front();
481 }
482 }
483
484 foreach (QQuickParticleData* d, toEmit)
485 m_system->emitParticle(p: d, particleEmitter: this);
486
487 if (isEmitConnected()) {
488 QQmlEngine *qmlEngine = ::qmlEngine(this);
489 QV4::ExecutionEngine *v4 = qmlEngine->handle();
490 QV4::Scope scope(v4);
491
492 //Done after emitParticle so that the Painter::load is done first, this allows you to customize its static variables
493 //We then don't need to request another reload, because the first reload isn't scheduled until we get back to the render thread
494 QV4::ScopedArrayObject array(scope, v4->newArrayObject(count: toEmit.size()));
495 QV4::ScopedValue v(scope);
496 for (int i=0; i<toEmit.size(); i++)
497 array->put(idx: i, v: (v = toEmit[i]->v4Value(particleSystem: m_system)));
498
499 QJSValue particles;
500 QJSValuePrivate::setValue(jsval: &particles, engine: v4, v: array);
501 emit emitParticles(particles);//A chance for arbitrary JS changes
502 }
503
504 m_last_emission = pt;
505
506 m_last_last_last_emitter = m_last_last_emitter;
507 m_last_last_emitter = m_last_emitter;
508 m_last_emitter = QPointF(x(), y());
509 m_last_timestamp = time;
510}
511
512
513QT_END_NAMESPACE
514
515#include "moc_qquickparticleemitter_p.cpp"
516

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