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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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