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::reclaculateGroupId() const
232{
233 if (!m_system) {
234 m_groupId = QQuickParticleGroupData::InvalidID;
235 return;
236 }
237 m_groupId = m_system->groupIds.value(group(), QQuickParticleGroupData::InvalidID);
238 m_groupIdNeedRecalculation = m_groupId == QQuickParticleGroupData::InvalidID;
239}
240
241void QQuickParticleEmitter::componentComplete()
242{
243 if (!m_system && qobject_cast<QQuickParticleSystem*>(parentItem()))
244 setSystem(qobject_cast<QQuickParticleSystem*>(parentItem()));
245 if (m_system)
246 m_system->finishRegisteringParticleEmitter(this);
247 QQuickItem::componentComplete();
248}
249
250void QQuickParticleEmitter::setEnabled(bool arg)
251{
252 if (m_enabled != arg) {
253 m_enabled = arg;
254 emit enabledChanged(arg);
255 }
256}
257
258
259QQuickParticleExtruder* QQuickParticleEmitter::effectiveExtruder()
260{
261 if (m_extruder)
262 return m_extruder;
263 if (!m_defaultExtruder)
264 m_defaultExtruder = new QQuickParticleExtruder;
265 return m_defaultExtruder;
266}
267
268void QQuickParticleEmitter::pulse(int milliseconds)
269{
270 if (!m_enabled)
271 m_pulseLeft = milliseconds;
272}
273
274void QQuickParticleEmitter::burst(int num)
275{
276 m_burstQueue << std::make_pair(num, QPointF(x(), y()));
277}
278
279void QQuickParticleEmitter::burst(int num, qreal x, qreal y)
280{
281 m_burstQueue << std::make_pair(num, QPointF(x, y));
282}
283
284void QQuickParticleEmitter::setMaxParticleCount(int arg)
285{
286 if (m_maxParticleCount != arg) {
287 if (arg < 0 && m_maxParticleCount >= 0){
288 connect(this, &QQuickParticleEmitter::particlesPerSecondChanged,
289 this, &QQuickParticleEmitter::particleCountChanged);
290 connect(this, &QQuickParticleEmitter::particleDurationChanged,
291 this, &QQuickParticleEmitter::particleCountChanged);
292 } else if (arg >= 0 && m_maxParticleCount < 0){
293 disconnect(this, &QQuickParticleEmitter::particlesPerSecondChanged,
294 this, &QQuickParticleEmitter::particleCountChanged);
295 disconnect(this, &QQuickParticleEmitter::particleDurationChanged,
296 this, &QQuickParticleEmitter::velocityFromMovementChanged);
297 }
298 m_overwrite = arg < 0;
299 m_maxParticleCount = arg;
300 emit maximumEmittedChanged(arg);
301 emit particleCountChanged();
302 }
303}
304
305void QQuickParticleEmitter::setVelocityFromMovement(qreal t)
306{
307 if (t == m_velocity_from_movement)
308 return;
309 m_velocity_from_movement = t;
310 emit velocityFromMovementChanged();
311}
312
313void QQuickParticleEmitter::reset()
314{
315 m_reset_last = true;
316}
317
318void QQuickParticleEmitter::emitWindow(int timeStamp)
319{
320 if (m_system == nullptr)
321 return;
322 if ((!m_enabled || m_particlesPerSecond <= 0)&& !m_pulseLeft && m_burstQueue.isEmpty()){
323 m_reset_last = true;
324 return;
325 }
326
327 if (m_reset_last) {
328 m_last_emitter = m_last_last_emitter = QPointF(x(), y());
329 if (m_last_timestamp == -1)
330 m_last_timestamp = (timeStamp - m_startTime)/1000.;
331 else
332 m_last_timestamp = timeStamp/1000.;
333 m_last_emission = m_last_timestamp;
334 m_reset_last = false;
335 m_emitCap = -1;
336 }
337
338 if (m_pulseLeft){
339 m_pulseLeft -= timeStamp - m_last_timestamp * 1000.;
340 if (m_pulseLeft < 0){
341 if (!m_enabled)
342 timeStamp += m_pulseLeft;
343 m_pulseLeft = 0;
344 }
345 }
346 qreal time = timeStamp / 1000.;
347 qreal particleRatio = 1. / m_particlesPerSecond;
348 qreal pt = m_last_emission;
349 qreal maxLife = (m_particleDuration + m_particleDurationVariation)/1000.0;
350 if (pt + maxLife < time)//We missed so much, that we should skip emiting particles that are dead by now
351 pt = time - maxLife;
352
353 qreal opt = pt; // original particle time
354 qreal dt = time - m_last_timestamp; // timestamp delta...
355 if (!dt)
356 dt = 0.000001;
357
358 // emitter difference since last...
359 qreal dex = (x() - m_last_emitter.x());
360 qreal dey = (y() - m_last_emitter.y());
361
362 qreal ax = (m_last_last_emitter.x() + m_last_emitter.x()) / 2;
363 qreal bx = m_last_emitter.x();
364 qreal cx = (x() + m_last_emitter.x()) / 2;
365 qreal ay = (m_last_last_emitter.y() + m_last_emitter.y()) / 2;
366 qreal by = m_last_emitter.y();
367 qreal cy = (y() + m_last_emitter.y()) / 2;
368
369 qreal sizeAtEnd = m_particleEndSize >= 0 ? m_particleEndSize : m_particleSize;
370 qreal emitter_x_offset = m_last_emitter.x() - x();
371 qreal emitter_y_offset = m_last_emitter.y() - y();
372 if (!m_burstQueue.isEmpty() && !m_pulseLeft && !m_enabled)//'outside time' emissions only
373 pt = time;
374
375 QList<QQuickParticleData*> toEmit;
376
377 while ((pt < time && m_emitCap) || !m_burstQueue.isEmpty()) {
378 //int pos = m_last_particle % m_particle_count;
379 QQuickParticleData* datum = m_system->newDatum(m_system->groupIds[m_group], !m_overwrite);
380 if (datum){//actually emit(otherwise we've been asked to skip this one)
381 qreal t = 1 - (pt - opt) / dt;
382 qreal vx =
383 - 2 * ax * (1 - t)
384 + 2 * bx * (1 - 2 * t)
385 + 2 * cx * t;
386 qreal vy =
387 - 2 * ay * (1 - t)
388 + 2 * by * (1 - 2 * t)
389 + 2 * cy * t;
390
391
392 // Particle timestamp
393 datum->t = pt;
394 datum->lifeSpan =
395 (m_particleDuration
396 + (QRandomGenerator::global()->bounded((m_particleDurationVariation*2) + 1) - m_particleDurationVariation))
397 / 1000.0;
398
399 if (datum->lifeSpan >= m_system->maxLife){
400 datum->lifeSpan = m_system->maxLife;
401 if (m_emitCap == -1)
402 m_emitCap = particleCount();
403 m_emitCap--;//emitCap keeps us from reemitting 'infinite' particles after their life. Unless you reset the emitter.
404 }
405
406 // Particle position
407 QRectF boundsRect;
408 if (!m_burstQueue.isEmpty()){
409 boundsRect = QRectF(m_burstQueue.first().second.x() - x(), m_burstQueue.first().second.y() - y(),
410 width(), height());
411 } else {
412 boundsRect = QRectF(emitter_x_offset + dex * (pt - opt) / dt, emitter_y_offset + dey * (pt - opt) / dt
413 , width(), height());
414 }
415 QPointF newPos = effectiveExtruder()->extrude(boundsRect);
416 datum->x = newPos.x();
417 datum->y = newPos.y();
418
419 // Particle velocity
420 const QPointF &velocity = m_velocity->sample(newPos);
421 datum->vx = velocity.x()
422 + m_velocity_from_movement * vx;
423 datum->vy = velocity.y()
424 + m_velocity_from_movement * vy;
425
426 // Particle acceleration
427 const QPointF &accel = m_acceleration->sample(newPos);
428 datum->ax = accel.x();
429 datum->ay = accel.y();
430
431 // Particle size
432 float sizeVariation = -m_particleSizeVariation
433 + QRandomGenerator::global()->bounded(m_particleSizeVariation * 2);
434
435 float size = qMax((qreal)0.0 , m_particleSize + sizeVariation);
436 float endSize = qMax((qreal)0.0 , sizeAtEnd + sizeVariation);
437
438 datum->size = size;// * float(m_emitting);
439 datum->endSize = endSize;// * float(m_emitting);
440
441 toEmit << datum;
442 }
443 if (m_burstQueue.isEmpty()){
444 pt += particleRatio;
445 }else{
446 m_burstQueue.first().first--;
447 if (m_burstQueue.first().first <= 0)
448 m_burstQueue.pop_front();
449 }
450 }
451
452 foreach (QQuickParticleData* d, toEmit)
453 m_system->emitParticle(d, this);
454
455 if (isEmitConnected()) {
456 //Done after emitParticle so that the Painter::load is done first, this allows you to customize its static variables
457 //We then don't need to request another reload, because the first reload isn't scheduled until we get back to the render thread
458
459 QList<QQuickV4ParticleData> particles;
460 particles.reserve(toEmit.size());
461 for (QQuickParticleData *particle : std::as_const(toEmit))
462 particles.push_back(particle->v4Value(m_system));
463
464 emit emitParticles(particles);//A chance for arbitrary JS changes
465 }
466
467 m_last_emission = pt;
468
469 m_last_last_emitter = m_last_emitter;
470 m_last_emitter = QPointF(x(), y());
471 m_last_timestamp = time;
472}
473
474
475QT_END_NAMESPACE
476
477#include "moc_qquickparticleemitter_p.cpp"
Combined button and popup list for selecting options.