1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the Declarative module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qquickparticleemitter_p.h"
43 #include <private/qdeclarativeengine_p.h>
48 \qmlclass Emitter QQuickParticleEmitter
49 \inqmlmodule QtQuick.Particles 2
50 \brief The Emitter element allows you to emit logical particles.
52 This element emits logical particles into the ParticleSystem, with the
53 given starting attributes.
55 Note that logical particles are not
56 automatically rendered, you will need to have one or more
57 ParticlePainter elements visualizing them.
59 Note that the given starting attributes can be modified at any point
60 in the particle's lifetime by any Affector element in the same
61 ParticleSystem. This includes attributes like lifespan.
66 \qmlproperty ParticleSystem QtQuick.Particles2::Emitter::system
68 This is the Particle system that the Emitter will emit into.
69 This can be omitted if the Emitter is a direct child of the ParticleSystem
72 \qmlproperty string QtQuick.Particles2::Emitter::group
74 This is the logical particle group which it will emit into.
76 Default value is "" (empty string).
79 \qmlproperty Shape QtQuick.Particles2::Emitter::shape
81 This shape is applied with the size of the Emitter. Particles will be emitted
82 randomly from any area covered by the shape.
84 The default shape is a filled in rectangle, which corresponds to the full bounding
88 \qmlproperty bool QtQuick.Particles2::Emitter::emitting
90 If set to false, the emitter will cease emissions until it is set to true.
92 Default value is true.
95 \qmlproperty real QtQuick.Particles2::Emitter::emitRate
97 Number of particles emitted per second.
99 Default value is 10 particles per second.
102 \qmlproperty int QtQuick.Particles2::Emitter::lifeSpan
104 The time in milliseconds each emitted particle should last for.
106 If you do not want particles to automatically die after a time, for example if
107 you wish to dispose of them manually, set lifeSpan to Emitter.InfiniteLife.
109 lifeSpans greater than or equal to 600000 (10 minutes) will be treated as infinite.
110 Particles with lifeSpans less than or equal to 0 will start out dead.
112 Default value is 1000 (one second).
115 \qmlproperty int QtQuick.Particles2::Emitter::lifeSpanVariation
117 Particle lifespans will vary by up to this much in either direction.
123 \qmlproperty int QtQuick.Particles2::Emitter::maximumEmitted
125 The maximum number of particles at a time that this emitter will have alive.
127 This can be set as a performance optimization (when using burst and pulse) or
128 to stagger emissions.
130 If this is set to a number below zero, then there is no maximum limit on the number
131 of particles this emitter can have alive.
133 The default value is -1.
136 \qmlproperty int QtQuick.Particles2::Emitter::startTime
138 If this value is set when the emitter is loaded, then it will emit particles from the
139 past, up to startTime milliseconds ago. These will simulate as if they were emitted then,
140 but will not have any affectors applied to them. Affectors will take effect from the present time.
143 \qmlproperty real QtQuick.Particles2::Emitter::size
145 The size in pixels of the particles at the start of their life.
150 \qmlproperty real QtQuick.Particles2::Emitter::endSize
152 The size in pixels of the particles at the end of their life. Size will
153 be linearly interpolated during the life of the particle from this value and
154 size. If endSize is -1, then the size of the particle will remain constant at
160 \qmlproperty real QtQuick.Particles2::Emitter::sizeVariation
162 The size of a particle can vary by this much up or down from size/endSize. The same
163 random addition is made to both size and endSize for a single particle.
168 \qmlproperty StochasticDirection QtQuick.Particles2::Emitter::speed
170 The starting speed of the particles emitted.
173 \qmlproperty StochasticDirection QtQuick.Particles2::Emitter::acceleration
175 The starting acceleraton of the particles emitted.
178 \qmlproperty qreal QtQuick.Particles2::Emitter::speedFromMovement
180 If this value is non-zero, then any movement of the emitter will provide additional
181 starting velocity to the particles based on the movement. The additional vector will be the
182 same angle as the emitter's movement, with a magnitude that is the magnitude of the emitters
183 movement multiplied by speedFromMovement.
189 \qmlsignal QtQuick.Particles2::Emitter::onEmitParticles(Array particles)
191 This handler is called when particles are emitted. particles is a javascript
192 array of Particle objects. You can modify particle attributes directly within the handler.
194 Note that JS is slower to execute, so it is not recommended to use this in
195 high-volume particle systems.
198 /*! \qmlmethod QtQuick.Particles2::Emitter::burst(int count)
200 Emits count particles from this emitter immediately.
203 /*! \qmlmethod QtQuick.Particles2::Emitter::burst(int x, int y, int count)
205 Emits count particles from this emitter immediately. The particles are emitted
206 as if the Emitter was positioned at x,y but all other properties are the same.
209 /*! \qmlmethod QtQuick.Particles2::Emitter::pulse(int duration)
211 If the emitter is not enabled, enables it for duration milliseconds and then switches
215 QQuickParticleEmitter::QQuickParticleEmitter(QQuickItem *parent) :
217 , m_particlesPerSecond(10)
218 , m_particleDuration(1000)
219 , m_particleDurationVariation(0)
223 , m_defaultExtruder(0)
224 , m_speed(&m_nullVector)
225 , m_acceleration(&m_nullVector)
227 , m_particleEndSize(-1)
228 , m_particleSizeVariation(0)
232 , m_maxParticleCount(-1)
233 , m_speed_from_movement(0)
235 , m_last_timestamp(-1)
239 //TODO: Reset speed/acc back to null vector? Or allow null pointer?
240 connect(this, SIGNAL(maximumEmittedChanged(int)),
241 this, SIGNAL(particleCountChanged()));
242 connect(this, SIGNAL(particlesPerSecondChanged(qreal)),
243 this, SIGNAL(particleCountChanged()));
244 connect(this, SIGNAL(particleDurationChanged(int)),
245 this, SIGNAL(particleCountChanged()));
248 QQuickParticleEmitter::~QQuickParticleEmitter()
250 if (m_defaultExtruder)
251 delete m_defaultExtruder;
254 bool QQuickParticleEmitter::isEmitConnected()
256 static int idx = QObjectPrivate::get(this)->signalIndex("emitParticles(QDeclarativeV8Handle)");
257 return QObjectPrivate::get(this)->isSignalConnected(idx);
260 void QQuickParticleEmitter::componentComplete()
262 if (!m_system && qobject_cast<QQuickParticleSystem*>(parentItem()))
263 setSystem(qobject_cast<QQuickParticleSystem*>(parentItem()));
264 QQuickItem::componentComplete();
267 void QQuickParticleEmitter::setEnabled(bool arg)
269 if (m_enabled != arg) {
271 emit enabledChanged(arg);
276 QQuickParticleExtruder* QQuickParticleEmitter::effectiveExtruder()
280 if (!m_defaultExtruder)
281 m_defaultExtruder = new QQuickParticleExtruder;
282 return m_defaultExtruder;
285 void QQuickParticleEmitter::pulse(int milliseconds)
287 if (!particleCount())
288 qWarning() << "pulse called on an emitter with a particle count of zero";
290 m_pulseLeft = milliseconds;
293 void QQuickParticleEmitter::burst(int num)
295 if (!particleCount())
296 qWarning() << "burst called on an emitter with a particle count of zero";
297 m_burstQueue << qMakePair(num, QPointF(x(), y()));
300 void QQuickParticleEmitter::burst(int num, qreal x, qreal y)
302 if (!particleCount())
303 qWarning() << "burst called on an emitter with a particle count of zero";
304 m_burstQueue << qMakePair(num, QPointF(x, y));
307 void QQuickParticleEmitter::setMaxParticleCount(int arg)
309 if (m_maxParticleCount != arg) {
310 if (arg < 0 && m_maxParticleCount >= 0){
311 connect(this, SIGNAL(particlesPerSecondChanged(qreal)),
312 this, SIGNAL(particleCountChanged()));
313 connect(this, SIGNAL(particleDurationChanged(int)),
314 this, SIGNAL(particleCountChanged()));
315 }else if (arg >= 0 && m_maxParticleCount < 0){
316 disconnect(this, SIGNAL(particlesPerSecondChanged(qreal)),
317 this, SIGNAL(particleCountChanged()));
318 disconnect(this, SIGNAL(particleDurationChanged(int)),
319 this, SIGNAL(particleCountChanged()));
321 m_overwrite = arg < 0;
322 m_maxParticleCount = arg;
323 emit maximumEmittedChanged(arg);
327 int QQuickParticleEmitter::particleCount() const
329 if (m_maxParticleCount >= 0)
330 return m_maxParticleCount;
331 return m_particlesPerSecond*((m_particleDuration+m_particleDurationVariation)/1000.0);
334 void QQuickParticleEmitter::setSpeedFromMovement(qreal t)
336 if (t == m_speed_from_movement)
338 m_speed_from_movement = t;
339 emit speedFromMovementChanged();
342 void QQuickParticleEmitter::reset()
347 void QQuickParticleEmitter::emitWindow(int timeStamp)
351 if ((!m_enabled || !m_particlesPerSecond)&& !m_pulseLeft && m_burstQueue.isEmpty()){
357 m_last_emitter = m_last_last_emitter = QPointF(x(), y());
358 if (m_last_timestamp == -1)
359 m_last_timestamp = (timeStamp - m_startTime)/1000.;
361 m_last_timestamp = timeStamp/1000.;
362 m_last_emission = m_last_timestamp;
363 m_reset_last = false;
364 m_emitCap = particleCount();
368 m_pulseLeft -= timeStamp - m_last_timestamp * 1000.;
369 if (m_pulseLeft < 0){
371 timeStamp += m_pulseLeft;
375 qreal time = timeStamp / 1000.;
376 qreal particleRatio = 1. / m_particlesPerSecond;
377 qreal pt = m_last_emission;
378 qreal maxLife = (m_particleDuration + m_particleDurationVariation)/1000.0;
379 if (pt + maxLife < time)//We missed so much, that we should skip emiting particles that are dead by now
382 qreal opt = pt; // original particle time
383 qreal dt = time - m_last_timestamp; // timestamp delta...
387 // emitter difference since last...
388 qreal dex = (x() - m_last_emitter.x());
389 qreal dey = (y() - m_last_emitter.y());
391 qreal ax = (m_last_last_emitter.x() + m_last_emitter.x()) / 2;
392 qreal bx = m_last_emitter.x();
393 qreal cx = (x() + m_last_emitter.x()) / 2;
394 qreal ay = (m_last_last_emitter.y() + m_last_emitter.y()) / 2;
395 qreal by = m_last_emitter.y();
396 qreal cy = (y() + m_last_emitter.y()) / 2;
398 qreal sizeAtEnd = m_particleEndSize >= 0 ? m_particleEndSize : m_particleSize;
399 qreal emitter_x_offset = m_last_emitter.x() - x();
400 qreal emitter_y_offset = m_last_emitter.y() - y();
401 if (!m_burstQueue.isEmpty() && !m_pulseLeft && !m_enabled)//'outside time' emissions only
404 QList<QQuickParticleData*> toEmit;
406 while ((pt < time && m_emitCap) || !m_burstQueue.isEmpty()) {
407 //int pos = m_last_particle % m_particle_count;
408 QQuickParticleData* datum = m_system->newDatum(m_system->groupIds[m_group], !m_overwrite);
409 if (datum){//actually emit(otherwise we've been asked to skip this one)
410 datum->e = this;//###useful?
411 qreal t = 1 - (pt - opt) / dt;
414 + 2 * bx * (1 - 2 * t)
418 + 2 * by * (1 - 2 * t)
422 // Particle timestamp
426 + ((rand() % ((m_particleDurationVariation*2) + 1)) - m_particleDurationVariation))
429 if (datum->lifeSpan >= m_system->maxLife){
430 datum->lifeSpan = m_system->maxLife;
431 m_emitCap--;//emitCap keeps us from reemitting 'infinite' particles after their life. Unless you reset the emitter.
436 if (!m_burstQueue.isEmpty()){
437 boundsRect = QRectF(m_burstQueue.first().second.x() - x(), m_burstQueue.first().second.y() - y(),
440 boundsRect = QRectF(emitter_x_offset + dex * (pt - opt) / dt, emitter_y_offset + dey * (pt - opt) / dt
441 , width(), height());
443 QPointF newPos = effectiveExtruder()->extrude(boundsRect);
444 datum->x = newPos.x();
445 datum->y = newPos.y();
448 const QPointF &speed = m_speed->sample(newPos);
449 datum->vx = speed.x()
450 + m_speed_from_movement * vx;
451 datum->vy = speed.y()
452 + m_speed_from_movement * vy;
454 // Particle acceleration
455 const QPointF &accel = m_acceleration->sample(newPos);
456 datum->ax = accel.x();
457 datum->ay = accel.y();
460 float sizeVariation = -m_particleSizeVariation
461 + rand() / float(RAND_MAX) * m_particleSizeVariation * 2;
463 float size = qMax((qreal)0.0 , m_particleSize + sizeVariation);
464 float endSize = qMax((qreal)0.0 , sizeAtEnd + sizeVariation);
466 datum->size = size;// * float(m_emitting);
467 datum->endSize = endSize;// * float(m_emitting);
471 if (m_burstQueue.isEmpty()){
474 m_burstQueue.first().first--;
475 if (m_burstQueue.first().first <= 0)
476 m_burstQueue.pop_front();
480 if (isEmitConnected()) {
481 v8::HandleScope handle_scope;
482 v8::Context::Scope scope(QDeclarativeEnginePrivate::getV8Engine(qmlEngine(this))->context());
483 v8::Handle<v8::Array> array = v8::Array::New(toEmit.size());
484 for (int i=0; i<toEmit.size(); i++)
485 array->Set(i, toEmit[i]->v8Value().toHandle());
487 emitParticles(QDeclarativeV8Handle::fromHandle(array));//A chance for arbitrary JS changes
489 foreach (QQuickParticleData* d, toEmit)
490 m_system->emitParticle(d);
492 m_last_emission = pt;
494 m_last_last_last_emitter = m_last_last_emitter;
495 m_last_last_emitter = m_last_emitter;
496 m_last_emitter = QPointF(x(), y());
497 m_last_timestamp = time;