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>
49 \qmlclass Emitter QQuickParticleEmitter
50 \inqmlmodule QtQuick.Particles 2
51 \brief The Emitter element allows you to emit logical particles.
53 This element emits logical particles into the ParticleSystem, with the
54 given starting attributes.
56 Note that logical particles are not
57 automatically rendered, you will need to have one or more
58 ParticlePainter elements visualizing them.
60 Note that the given starting attributes can be modified at any point
61 in the particle's lifetime by any Affector element in the same
62 ParticleSystem. This includes attributes like lifespan.
67 \qmlproperty ParticleSystem QtQuick.Particles2::Emitter::system
69 This is the Particle system that the Emitter will emit into.
70 This can be omitted if the Emitter is a direct child of the ParticleSystem
73 \qmlproperty string QtQuick.Particles2::Emitter::group
75 This is the logical particle group which it will emit into.
77 Default value is "" (empty string).
80 \qmlproperty Shape QtQuick.Particles2::Emitter::shape
82 This shape is applied with the size of the Emitter. Particles will be emitted
83 randomly from any area covered by the shape.
85 The default shape is a filled in rectangle, which corresponds to the full bounding
89 \qmlproperty bool QtQuick.Particles2::Emitter::emitting
91 If set to false, the emitter will cease emissions until it is set to true.
93 Default value is true.
96 \qmlproperty real QtQuick.Particles2::Emitter::emitRate
98 Number of particles emitted per second.
100 Default value is 10 particles per second.
103 \qmlproperty int QtQuick.Particles2::Emitter::lifeSpan
105 The time in milliseconds each emitted particle should last for.
107 If you do not want particles to automatically die after a time, for example if
108 you wish to dispose of them manually, set lifeSpan to Emitter.InfiniteLife.
110 lifeSpans greater than or equal to 600000 (10 minutes) will be treated as infinite.
111 Particles with lifeSpans less than or equal to 0 will start out dead.
113 Default value is 1000 (one second).
116 \qmlproperty int QtQuick.Particles2::Emitter::lifeSpanVariation
118 Particle lifespans will vary by up to this much in either direction.
124 \qmlproperty int QtQuick.Particles2::Emitter::maximumEmitted
126 The maximum number of particles at a time that this emitter will have alive.
128 This can be set as a performance optimization (when using burst and pulse) or
129 to stagger emissions.
131 If this is set to a number below zero, then there is no maximum limit on the number
132 of particles this emitter can have alive.
134 The default value is -1.
137 \qmlproperty int QtQuick.Particles2::Emitter::startTime
139 If this value is set when the emitter is loaded, then it will emit particles from the
140 past, up to startTime milliseconds ago. These will simulate as if they were emitted then,
141 but will not have any affectors applied to them. Affectors will take effect from the present time.
144 \qmlproperty real QtQuick.Particles2::Emitter::size
146 The size in pixels of the particles at the start of their life.
151 \qmlproperty real QtQuick.Particles2::Emitter::endSize
153 The size in pixels of the particles at the end of their life. Size will
154 be linearly interpolated during the life of the particle from this value and
155 size. If endSize is -1, then the size of the particle will remain constant at
161 \qmlproperty real QtQuick.Particles2::Emitter::sizeVariation
163 The size of a particle can vary by this much up or down from size/endSize. The same
164 random addition is made to both size and endSize for a single particle.
169 \qmlproperty StochasticDirection QtQuick.Particles2::Emitter::speed
171 The starting speed of the particles emitted.
174 \qmlproperty StochasticDirection QtQuick.Particles2::Emitter::acceleration
176 The starting acceleraton of the particles emitted.
179 \qmlproperty qreal QtQuick.Particles2::Emitter::speedFromMovement
181 If this value is non-zero, then any movement of the emitter will provide additional
182 starting velocity to the particles based on the movement. The additional vector will be the
183 same angle as the emitter's movement, with a magnitude that is the magnitude of the emitters
184 movement multiplied by speedFromMovement.
190 \qmlsignal QtQuick.Particles2::Emitter::onEmitParticles(Array particles)
192 This handler is called when particles are emitted. particles is a javascript
193 array of Particle objects. You can modify particle attributes directly within the handler.
195 Note that JS is slower to execute, so it is not recommended to use this in
196 high-volume particle systems.
199 /*! \qmlmethod QtQuick.Particles2::Emitter::burst(int count)
201 Emits count particles from this emitter immediately.
204 /*! \qmlmethod QtQuick.Particles2::Emitter::burst(int x, int y, int count)
206 Emits count particles from this emitter immediately. The particles are emitted
207 as if the Emitter was positioned at x,y but all other properties are the same.
210 /*! \qmlmethod QtQuick.Particles2::Emitter::pulse(int duration)
212 If the emitter is not enabled, enables it for duration milliseconds and then switches
216 QQuickParticleEmitter::QQuickParticleEmitter(QQuickItem *parent) :
218 , m_particlesPerSecond(10)
219 , m_particleDuration(1000)
220 , m_particleDurationVariation(0)
224 , m_defaultExtruder(0)
225 , m_speed(&m_nullVector)
226 , m_acceleration(&m_nullVector)
228 , m_particleEndSize(-1)
229 , m_particleSizeVariation(0)
233 , m_maxParticleCount(-1)
234 , m_speed_from_movement(0)
236 , m_last_timestamp(-1)
240 //TODO: Reset speed/acc back to null vector? Or allow null pointer?
241 connect(this, SIGNAL(maximumEmittedChanged(int)),
242 this, SIGNAL(particleCountChanged()));
243 connect(this, SIGNAL(particlesPerSecondChanged(qreal)),
244 this, SIGNAL(particleCountChanged()));
245 connect(this, SIGNAL(particleDurationChanged(int)),
246 this, SIGNAL(particleCountChanged()));
249 QQuickParticleEmitter::~QQuickParticleEmitter()
251 if (m_defaultExtruder)
252 delete m_defaultExtruder;
255 bool QQuickParticleEmitter::isEmitConnected()
257 IS_SIGNAL_CONNECTED(this, "emitParticles(QQmlV8Handle)");
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)
288 m_pulseLeft = milliseconds;
291 void QQuickParticleEmitter::burst(int num)
293 m_burstQueue << qMakePair(num, QPointF(x(), y()));
296 void QQuickParticleEmitter::burst(int num, qreal x, qreal y)
298 m_burstQueue << qMakePair(num, QPointF(x, y));
301 void QQuickParticleEmitter::setMaxParticleCount(int arg)
303 if (m_maxParticleCount != arg) {
304 if (arg < 0 && m_maxParticleCount >= 0){
305 connect(this, SIGNAL(particlesPerSecondChanged(qreal)),
306 this, SIGNAL(particleCountChanged()));
307 connect(this, SIGNAL(particleDurationChanged(int)),
308 this, SIGNAL(particleCountChanged()));
309 }else if (arg >= 0 && m_maxParticleCount < 0){
310 disconnect(this, SIGNAL(particlesPerSecondChanged(qreal)),
311 this, SIGNAL(particleCountChanged()));
312 disconnect(this, SIGNAL(particleDurationChanged(int)),
313 this, SIGNAL(particleCountChanged()));
315 m_overwrite = arg < 0;
316 m_maxParticleCount = arg;
317 emit maximumEmittedChanged(arg);
321 int QQuickParticleEmitter::particleCount() const
323 if (m_maxParticleCount >= 0)
324 return m_maxParticleCount;
325 return m_particlesPerSecond*((m_particleDuration+m_particleDurationVariation)/1000.0);
328 void QQuickParticleEmitter::setSpeedFromMovement(qreal t)
330 if (t == m_speed_from_movement)
332 m_speed_from_movement = t;
333 emit speedFromMovementChanged();
336 void QQuickParticleEmitter::reset()
341 void QQuickParticleEmitter::emitWindow(int timeStamp)
345 if ((!m_enabled || !m_particlesPerSecond)&& !m_pulseLeft && m_burstQueue.isEmpty()){
351 m_last_emitter = m_last_last_emitter = QPointF(x(), y());
352 if (m_last_timestamp == -1)
353 m_last_timestamp = (timeStamp - m_startTime)/1000.;
355 m_last_timestamp = timeStamp/1000.;
356 m_last_emission = m_last_timestamp;
357 m_reset_last = false;
358 m_emitCap = particleCount();
362 m_pulseLeft -= timeStamp - m_last_timestamp * 1000.;
363 if (m_pulseLeft < 0){
365 timeStamp += m_pulseLeft;
369 qreal time = timeStamp / 1000.;
370 qreal particleRatio = 1. / m_particlesPerSecond;
371 qreal pt = m_last_emission;
372 qreal maxLife = (m_particleDuration + m_particleDurationVariation)/1000.0;
373 if (pt + maxLife < time)//We missed so much, that we should skip emiting particles that are dead by now
376 qreal opt = pt; // original particle time
377 qreal dt = time - m_last_timestamp; // timestamp delta...
381 // emitter difference since last...
382 qreal dex = (x() - m_last_emitter.x());
383 qreal dey = (y() - m_last_emitter.y());
385 qreal ax = (m_last_last_emitter.x() + m_last_emitter.x()) / 2;
386 qreal bx = m_last_emitter.x();
387 qreal cx = (x() + m_last_emitter.x()) / 2;
388 qreal ay = (m_last_last_emitter.y() + m_last_emitter.y()) / 2;
389 qreal by = m_last_emitter.y();
390 qreal cy = (y() + m_last_emitter.y()) / 2;
392 qreal sizeAtEnd = m_particleEndSize >= 0 ? m_particleEndSize : m_particleSize;
393 qreal emitter_x_offset = m_last_emitter.x() - x();
394 qreal emitter_y_offset = m_last_emitter.y() - y();
395 if (!m_burstQueue.isEmpty() && !m_pulseLeft && !m_enabled)//'outside time' emissions only
398 QList<QQuickParticleData*> toEmit;
400 while ((pt < time && m_emitCap) || !m_burstQueue.isEmpty()) {
401 //int pos = m_last_particle % m_particle_count;
402 QQuickParticleData* datum = m_system->newDatum(m_system->groupIds[m_group], !m_overwrite);
403 if (datum){//actually emit(otherwise we've been asked to skip this one)
404 datum->e = this;//###useful?
405 qreal t = 1 - (pt - opt) / dt;
408 + 2 * bx * (1 - 2 * t)
412 + 2 * by * (1 - 2 * t)
416 // Particle timestamp
420 + ((rand() % ((m_particleDurationVariation*2) + 1)) - m_particleDurationVariation))
423 if (datum->lifeSpan >= m_system->maxLife){
424 datum->lifeSpan = m_system->maxLife;
425 m_emitCap--;//emitCap keeps us from reemitting 'infinite' particles after their life. Unless you reset the emitter.
430 if (!m_burstQueue.isEmpty()){
431 boundsRect = QRectF(m_burstQueue.first().second.x() - x(), m_burstQueue.first().second.y() - y(),
434 boundsRect = QRectF(emitter_x_offset + dex * (pt - opt) / dt, emitter_y_offset + dey * (pt - opt) / dt
435 , width(), height());
437 QPointF newPos = effectiveExtruder()->extrude(boundsRect);
438 datum->x = newPos.x();
439 datum->y = newPos.y();
442 const QPointF &speed = m_speed->sample(newPos);
443 datum->vx = speed.x()
444 + m_speed_from_movement * vx;
445 datum->vy = speed.y()
446 + m_speed_from_movement * vy;
448 // Particle acceleration
449 const QPointF &accel = m_acceleration->sample(newPos);
450 datum->ax = accel.x();
451 datum->ay = accel.y();
454 float sizeVariation = -m_particleSizeVariation
455 + rand() / float(RAND_MAX) * m_particleSizeVariation * 2;
457 float size = qMax((qreal)0.0 , m_particleSize + sizeVariation);
458 float endSize = qMax((qreal)0.0 , sizeAtEnd + sizeVariation);
460 datum->size = size;// * float(m_emitting);
461 datum->endSize = endSize;// * float(m_emitting);
465 if (m_burstQueue.isEmpty()){
468 m_burstQueue.first().first--;
469 if (m_burstQueue.first().first <= 0)
470 m_burstQueue.pop_front();
474 if (isEmitConnected()) {
475 v8::HandleScope handle_scope;
476 v8::Context::Scope scope(QQmlEnginePrivate::getV8Engine(qmlEngine(this))->context());
477 v8::Handle<v8::Array> array = v8::Array::New(toEmit.size());
478 for (int i=0; i<toEmit.size(); i++)
479 array->Set(i, toEmit[i]->v8Value().toHandle());
481 emitParticles(QQmlV8Handle::fromHandle(array));//A chance for arbitrary JS changes
483 foreach (QQuickParticleData* d, toEmit)
484 m_system->emitParticle(d);
486 m_last_emission = pt;
488 m_last_last_last_emitter = m_last_last_emitter;
489 m_last_last_emitter = m_last_emitter;
490 m_last_emitter = QPointF(x(), y());
491 m_last_timestamp = time;