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> |
45 | QT_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 | |
219 | QQuickParticleEmitter::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 | |
252 | QQuickParticleEmitter::~QQuickParticleEmitter() |
253 | { |
254 | if (m_defaultExtruder) |
255 | delete m_defaultExtruder; |
256 | } |
257 | |
258 | bool QQuickParticleEmitter::isEmitConnected() |
259 | { |
260 | IS_SIGNAL_CONNECTED(this, QQuickParticleEmitter, emitParticles, (const QJSValue &)); |
261 | } |
262 | |
263 | void 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 | |
273 | void 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 | |
282 | void QQuickParticleEmitter::setEnabled(bool arg) |
283 | { |
284 | if (m_enabled != arg) { |
285 | m_enabled = arg; |
286 | emit enabledChanged(arg); |
287 | } |
288 | } |
289 | |
290 | |
291 | QQuickParticleExtruder* 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 | |
300 | void QQuickParticleEmitter::pulse(int milliseconds) |
301 | { |
302 | if (!m_enabled) |
303 | m_pulseLeft = milliseconds; |
304 | } |
305 | |
306 | void QQuickParticleEmitter::burst(int num) |
307 | { |
308 | m_burstQueue << qMakePair(x: num, y: QPointF(x(), y())); |
309 | } |
310 | |
311 | void QQuickParticleEmitter::burst(int num, qreal x, qreal y) |
312 | { |
313 | m_burstQueue << qMakePair(x: num, y: QPointF(x, y)); |
314 | } |
315 | |
316 | void 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 | |
337 | void 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 | |
345 | void QQuickParticleEmitter::reset() |
346 | { |
347 | m_reset_last = true; |
348 | } |
349 | |
350 | void 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 | |
513 | QT_END_NAMESPACE |
514 | |
515 | #include "moc_qquickparticleemitter_p.cpp" |
516 | |