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