1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQuick module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qquickparticleemitter_p.h"
43 #include <private/qqmlengine_p.h>
44 #include <private/qqmlglobal_p.h>
50 \instantiates QQuickParticleEmitter
51 \inqmlmodule QtQuick.Particles 2
52 \brief Emits logical particles
53 \ingroup qtquick-particles
55 This element emits logical particles into the ParticleSystem, with the
56 given starting attributes.
58 Note that logical particles are not
59 automatically rendered, you will need to have one or more
60 ParticlePainter elements visualizing them.
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.
69 \qmlproperty ParticleSystem QtQuick.Particles2::Emitter::system
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
75 \qmlproperty string QtQuick.Particles2::Emitter::group
77 This is the logical particle group which it will emit into.
79 Default value is "" (empty string).
82 \qmlproperty Shape QtQuick.Particles2::Emitter::shape
84 This shape is applied with the size of the Emitter. Particles will be emitted
85 randomly from any area covered by the shape.
87 The default shape is a filled in rectangle, which corresponds to the full bounding
91 \qmlproperty bool QtQuick.Particles2::Emitter::emitting
93 If set to false, the emitter will cease emissions until it is set to true.
95 Default value is true.
98 \qmlproperty real QtQuick.Particles2::Emitter::emitRate
100 Number of particles emitted per second.
102 Default value is 10 particles per second.
105 \qmlproperty int QtQuick.Particles2::Emitter::lifeSpan
107 The time in milliseconds each emitted particle should last for.
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.
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.
115 Default value is 1000 (one second).
118 \qmlproperty int QtQuick.Particles2::Emitter::lifeSpanVariation
120 Particle lifespans will vary by up to this much in either direction.
126 \qmlproperty int QtQuick.Particles2::Emitter::maximumEmitted
128 The maximum number of particles at a time that this emitter will have alive.
130 This can be set as a performance optimization (when using burst and pulse) or
131 to stagger emissions.
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.
136 The default value is -1.
139 \qmlproperty int QtQuick.Particles2::Emitter::startTime
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.
146 \qmlproperty real QtQuick.Particles2::Emitter::size
148 The size in pixels of the particles at the start of their life.
153 \qmlproperty real QtQuick.Particles2::Emitter::endSize
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
163 \qmlproperty real QtQuick.Particles2::Emitter::sizeVariation
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.
171 \qmlproperty StochasticDirection QtQuick.Particles2::Emitter::velocity
173 The starting velocity of the particles emitted.
176 \qmlproperty StochasticDirection QtQuick.Particles2::Emitter::acceleration
178 The starting acceleraton of the particles emitted.
181 \qmlproperty qreal QtQuick.Particles2::Emitter::velocityFromMovement
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.
192 \qmlsignal QtQuick.Particles2::Emitter::onEmitParticles(Array particles)
194 This handler is called when particles are emitted. particles is a JavaScript
195 array of Particle objects. You can modify particle attributes directly within the handler.
197 Note that JavaScript is slower to execute, so it is not recommended to use this in
198 high-volume particle systems.
201 /*! \qmlmethod QtQuick.Particles2::Emitter::burst(int count)
203 Emits count particles from this emitter immediately.
206 /*! \qmlmethod QtQuick.Particles2::Emitter::burst(int x, int y, int count)
208 Emits count particles from this emitter immediately. The particles are emitted
209 as if the Emitter was positioned at x,y but all other properties are the same.
212 /*! \qmlmethod QtQuick.Particles2::Emitter::pulse(int duration)
214 If the emitter is not enabled, enables it for duration milliseconds and then switches
218 QQuickParticleEmitter::QQuickParticleEmitter(QQuickItem *parent) :
220 , m_particlesPerSecond(10)
221 , m_particleDuration(1000)
222 , m_particleDurationVariation(0)
226 , m_defaultExtruder(0)
227 , m_velocity(&m_nullVector)
228 , m_acceleration(&m_nullVector)
230 , m_particleEndSize(-1)
231 , m_particleSizeVariation(0)
235 , m_maxParticleCount(-1)
236 , m_velocity_from_movement(0)
238 , m_last_timestamp(-1)
242 //TODO: Reset velocity/acc back to null vector? Or allow null pointer?
243 connect(this, SIGNAL(maximumEmittedChanged(int)),
244 this, SIGNAL(particleCountChanged()));
245 connect(this, SIGNAL(particlesPerSecondChanged(qreal)),
246 this, SIGNAL(particleCountChanged()));
247 connect(this, SIGNAL(particleDurationChanged(int)),
248 this, SIGNAL(particleCountChanged()));
251 QQuickParticleEmitter::~QQuickParticleEmitter()
253 if (m_defaultExtruder)
254 delete m_defaultExtruder;
257 bool QQuickParticleEmitter::isEmitConnected()
259 IS_SIGNAL_CONNECTED(this, QQuickParticleEmitter, emitParticles, (QQmlV8Handle));
262 void QQuickParticleEmitter::componentComplete()
264 if (!m_system && qobject_cast<QQuickParticleSystem*>(parentItem()))
265 setSystem(qobject_cast<QQuickParticleSystem*>(parentItem()));
266 QQuickItem::componentComplete();
269 void QQuickParticleEmitter::setEnabled(bool arg)
271 if (m_enabled != arg) {
273 emit enabledChanged(arg);
278 QQuickParticleExtruder* QQuickParticleEmitter::effectiveExtruder()
282 if (!m_defaultExtruder)
283 m_defaultExtruder = new QQuickParticleExtruder;
284 return m_defaultExtruder;
287 void QQuickParticleEmitter::pulse(int milliseconds)
290 m_pulseLeft = milliseconds;
293 void QQuickParticleEmitter::burst(int num)
295 m_burstQueue << qMakePair(num, QPointF(x(), y()));
298 void QQuickParticleEmitter::burst(int num, qreal x, qreal y)
300 m_burstQueue << qMakePair(num, QPointF(x, y));
303 void QQuickParticleEmitter::setMaxParticleCount(int arg)
305 if (m_maxParticleCount != arg) {
306 if (arg < 0 && m_maxParticleCount >= 0){
307 connect(this, SIGNAL(particlesPerSecondChanged(qreal)),
308 this, SIGNAL(particleCountChanged()));
309 connect(this, SIGNAL(particleDurationChanged(int)),
310 this, SIGNAL(particleCountChanged()));
311 }else if (arg >= 0 && m_maxParticleCount < 0){
312 disconnect(this, SIGNAL(particlesPerSecondChanged(qreal)),
313 this, SIGNAL(particleCountChanged()));
314 disconnect(this, SIGNAL(particleDurationChanged(int)),
315 this, SIGNAL(particleCountChanged()));
317 m_overwrite = arg < 0;
318 m_maxParticleCount = arg;
319 emit maximumEmittedChanged(arg);
323 int QQuickParticleEmitter::particleCount() const
325 if (m_maxParticleCount >= 0)
326 return m_maxParticleCount;
327 return m_particlesPerSecond*((m_particleDuration+m_particleDurationVariation)/1000.0);
330 void QQuickParticleEmitter::setVelocityFromMovement(qreal t)
332 if (t == m_velocity_from_movement)
334 m_velocity_from_movement = t;
335 emit velocityFromMovementChanged();
338 void QQuickParticleEmitter::reset()
343 void QQuickParticleEmitter::emitWindow(int timeStamp)
347 if ((!m_enabled || !m_particlesPerSecond)&& !m_pulseLeft && m_burstQueue.isEmpty()){
353 m_last_emitter = m_last_last_emitter = QPointF(x(), y());
354 if (m_last_timestamp == -1)
355 m_last_timestamp = (timeStamp - m_startTime)/1000.;
357 m_last_timestamp = timeStamp/1000.;
358 m_last_emission = m_last_timestamp;
359 m_reset_last = false;
360 m_emitCap = particleCount();
364 m_pulseLeft -= timeStamp - m_last_timestamp * 1000.;
365 if (m_pulseLeft < 0){
367 timeStamp += m_pulseLeft;
371 qreal time = timeStamp / 1000.;
372 qreal particleRatio = 1. / m_particlesPerSecond;
373 qreal pt = m_last_emission;
374 qreal maxLife = (m_particleDuration + m_particleDurationVariation)/1000.0;
375 if (pt + maxLife < time)//We missed so much, that we should skip emiting particles that are dead by now
378 qreal opt = pt; // original particle time
379 qreal dt = time - m_last_timestamp; // timestamp delta...
383 // emitter difference since last...
384 qreal dex = (x() - m_last_emitter.x());
385 qreal dey = (y() - m_last_emitter.y());
387 qreal ax = (m_last_last_emitter.x() + m_last_emitter.x()) / 2;
388 qreal bx = m_last_emitter.x();
389 qreal cx = (x() + m_last_emitter.x()) / 2;
390 qreal ay = (m_last_last_emitter.y() + m_last_emitter.y()) / 2;
391 qreal by = m_last_emitter.y();
392 qreal cy = (y() + m_last_emitter.y()) / 2;
394 qreal sizeAtEnd = m_particleEndSize >= 0 ? m_particleEndSize : m_particleSize;
395 qreal emitter_x_offset = m_last_emitter.x() - x();
396 qreal emitter_y_offset = m_last_emitter.y() - y();
397 if (!m_burstQueue.isEmpty() && !m_pulseLeft && !m_enabled)//'outside time' emissions only
400 QList<QQuickParticleData*> toEmit;
402 while ((pt < time && m_emitCap) || !m_burstQueue.isEmpty()) {
403 //int pos = m_last_particle % m_particle_count;
404 QQuickParticleData* datum = m_system->newDatum(m_system->groupIds[m_group], !m_overwrite);
405 if (datum){//actually emit(otherwise we've been asked to skip this one)
406 datum->e = this;//###useful?
407 qreal t = 1 - (pt - opt) / dt;
410 + 2 * bx * (1 - 2 * t)
414 + 2 * by * (1 - 2 * t)
418 // Particle timestamp
422 + ((rand() % ((m_particleDurationVariation*2) + 1)) - m_particleDurationVariation))
425 if (datum->lifeSpan >= m_system->maxLife){
426 datum->lifeSpan = m_system->maxLife;
427 m_emitCap--;//emitCap keeps us from reemitting 'infinite' particles after their life. Unless you reset the emitter.
432 if (!m_burstQueue.isEmpty()){
433 boundsRect = QRectF(m_burstQueue.first().second.x() - x(), m_burstQueue.first().second.y() - y(),
436 boundsRect = QRectF(emitter_x_offset + dex * (pt - opt) / dt, emitter_y_offset + dey * (pt - opt) / dt
437 , width(), height());
439 QPointF newPos = effectiveExtruder()->extrude(boundsRect);
440 datum->x = newPos.x();
441 datum->y = newPos.y();
444 const QPointF &velocity = m_velocity->sample(newPos);
445 datum->vx = velocity.x()
446 + m_velocity_from_movement * vx;
447 datum->vy = velocity.y()
448 + m_velocity_from_movement * vy;
450 // Particle acceleration
451 const QPointF &accel = m_acceleration->sample(newPos);
452 datum->ax = accel.x();
453 datum->ay = accel.y();
456 float sizeVariation = -m_particleSizeVariation
457 + rand() / float(RAND_MAX) * m_particleSizeVariation * 2;
459 float size = qMax((qreal)0.0 , m_particleSize + sizeVariation);
460 float endSize = qMax((qreal)0.0 , sizeAtEnd + sizeVariation);
462 datum->size = size;// * float(m_emitting);
463 datum->endSize = endSize;// * float(m_emitting);
467 if (m_burstQueue.isEmpty()){
470 m_burstQueue.first().first--;
471 if (m_burstQueue.first().first <= 0)
472 m_burstQueue.pop_front();
476 foreach (QQuickParticleData* d, toEmit)
477 m_system->emitParticle(d);
479 if (isEmitConnected()) {
480 //Done after emitParticle so that the Painter::load is done first, this allows you to customize its static variables
481 //We then don't need to request another reload, because the first reload isn't scheduled until we get back to the render thread
482 v8::HandleScope handle_scope;
483 v8::Context::Scope scope(QQmlEnginePrivate::getV8Engine(qmlEngine(this))->context());
484 v8::Handle<v8::Array> array = v8::Array::New(toEmit.size());
485 for (int i=0; i<toEmit.size(); i++)
486 array->Set(i, toEmit[i]->v8Value().toHandle());
488 emitParticles(QQmlV8Handle::fromHandle(array));//A chance for arbitrary JS changes
491 m_last_emission = pt;
493 m_last_last_last_emitter = m_last_last_emitter;
494 m_last_last_emitter = m_last_emitter;
495 m_last_emitter = QPointF(x(), y());
496 m_last_timestamp = time;