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 Emits logical particles
52 \ingroup qtquick-particles
54 This element emits logical particles into the ParticleSystem, with the
55 given starting attributes.
57 Note that logical particles are not
58 automatically rendered, you will need to have one or more
59 ParticlePainter elements visualizing them.
61 Note that the given starting attributes can be modified at any point
62 in the particle's lifetime by any Affector element in the same
63 ParticleSystem. This includes attributes like lifespan.
68 \qmlproperty ParticleSystem QtQuick.Particles2::Emitter::system
70 This is the Particle system that the Emitter will emit into.
71 This can be omitted if the Emitter is a direct child of the ParticleSystem
74 \qmlproperty string QtQuick.Particles2::Emitter::group
76 This is the logical particle group which it will emit into.
78 Default value is "" (empty string).
81 \qmlproperty Shape QtQuick.Particles2::Emitter::shape
83 This shape is applied with the size of the Emitter. Particles will be emitted
84 randomly from any area covered by the shape.
86 The default shape is a filled in rectangle, which corresponds to the full bounding
90 \qmlproperty bool QtQuick.Particles2::Emitter::emitting
92 If set to false, the emitter will cease emissions until it is set to true.
94 Default value is true.
97 \qmlproperty real QtQuick.Particles2::Emitter::emitRate
99 Number of particles emitted per second.
101 Default value is 10 particles per second.
104 \qmlproperty int QtQuick.Particles2::Emitter::lifeSpan
106 The time in milliseconds each emitted particle should last for.
108 If you do not want particles to automatically die after a time, for example if
109 you wish to dispose of them manually, set lifeSpan to Emitter.InfiniteLife.
111 lifeSpans greater than or equal to 600000 (10 minutes) will be treated as infinite.
112 Particles with lifeSpans less than or equal to 0 will start out dead.
114 Default value is 1000 (one second).
117 \qmlproperty int QtQuick.Particles2::Emitter::lifeSpanVariation
119 Particle lifespans will vary by up to this much in either direction.
125 \qmlproperty int QtQuick.Particles2::Emitter::maximumEmitted
127 The maximum number of particles at a time that this emitter will have alive.
129 This can be set as a performance optimization (when using burst and pulse) or
130 to stagger emissions.
132 If this is set to a number below zero, then there is no maximum limit on the number
133 of particles this emitter can have alive.
135 The default value is -1.
138 \qmlproperty int QtQuick.Particles2::Emitter::startTime
140 If this value is set when the emitter is loaded, then it will emit particles from the
141 past, up to startTime milliseconds ago. These will simulate as if they were emitted then,
142 but will not have any affectors applied to them. Affectors will take effect from the present time.
145 \qmlproperty real QtQuick.Particles2::Emitter::size
147 The size in pixels of the particles at the start of their life.
152 \qmlproperty real QtQuick.Particles2::Emitter::endSize
154 The size in pixels of the particles at the end of their life. Size will
155 be linearly interpolated during the life of the particle from this value and
156 size. If endSize is -1, then the size of the particle will remain constant at
162 \qmlproperty real QtQuick.Particles2::Emitter::sizeVariation
164 The size of a particle can vary by this much up or down from size/endSize. The same
165 random addition is made to both size and endSize for a single particle.
170 \qmlproperty StochasticDirection QtQuick.Particles2::Emitter::velocity
172 The starting velocity of the particles emitted.
175 \qmlproperty StochasticDirection QtQuick.Particles2::Emitter::acceleration
177 The starting acceleraton of the particles emitted.
180 \qmlproperty qreal QtQuick.Particles2::Emitter::velocityFromMovement
182 If this value is non-zero, then any movement of the emitter will provide additional
183 starting velocity to the particles based on the movement. The additional vector will be the
184 same angle as the emitter's movement, with a magnitude that is the magnitude of the emitters
185 movement multiplied by velocityFromMovement.
191 \qmlsignal QtQuick.Particles2::Emitter::onEmitParticles(Array particles)
193 This handler is called when particles are emitted. particles is a javascript
194 array of Particle objects. You can modify particle attributes directly within the handler.
196 Note that JS is slower to execute, so it is not recommended to use this in
197 high-volume particle systems.
200 /*! \qmlmethod QtQuick.Particles2::Emitter::burst(int count)
202 Emits count particles from this emitter immediately.
205 /*! \qmlmethod QtQuick.Particles2::Emitter::burst(int x, int y, int count)
207 Emits count particles from this emitter immediately. The particles are emitted
208 as if the Emitter was positioned at x,y but all other properties are the same.
211 /*! \qmlmethod QtQuick.Particles2::Emitter::pulse(int duration)
213 If the emitter is not enabled, enables it for duration milliseconds and then switches
217 QQuickParticleEmitter::QQuickParticleEmitter(QQuickItem *parent) :
219 , m_particlesPerSecond(10)
220 , m_particleDuration(1000)
221 , m_particleDurationVariation(0)
225 , m_defaultExtruder(0)
226 , m_velocity(&m_nullVector)
227 , m_acceleration(&m_nullVector)
229 , m_particleEndSize(-1)
230 , m_particleSizeVariation(0)
234 , m_maxParticleCount(-1)
235 , m_velocity_from_movement(0)
237 , m_last_timestamp(-1)
241 //TODO: Reset velocity/acc back to null vector? Or allow null pointer?
242 connect(this, SIGNAL(maximumEmittedChanged(int)),
243 this, SIGNAL(particleCountChanged()));
244 connect(this, SIGNAL(particlesPerSecondChanged(qreal)),
245 this, SIGNAL(particleCountChanged()));
246 connect(this, SIGNAL(particleDurationChanged(int)),
247 this, SIGNAL(particleCountChanged()));
250 QQuickParticleEmitter::~QQuickParticleEmitter()
252 if (m_defaultExtruder)
253 delete m_defaultExtruder;
256 bool QQuickParticleEmitter::isEmitConnected()
258 IS_SIGNAL_CONNECTED(this, QQuickParticleEmitter, emitParticles, (QQmlV8Handle));
261 void QQuickParticleEmitter::componentComplete()
263 if (!m_system && qobject_cast<QQuickParticleSystem*>(parentItem()))
264 setSystem(qobject_cast<QQuickParticleSystem*>(parentItem()));
265 QQuickItem::componentComplete();
268 void QQuickParticleEmitter::setEnabled(bool arg)
270 if (m_enabled != arg) {
272 emit enabledChanged(arg);
277 QQuickParticleExtruder* QQuickParticleEmitter::effectiveExtruder()
281 if (!m_defaultExtruder)
282 m_defaultExtruder = new QQuickParticleExtruder;
283 return m_defaultExtruder;
286 void QQuickParticleEmitter::pulse(int milliseconds)
289 m_pulseLeft = milliseconds;
292 void QQuickParticleEmitter::burst(int num)
294 m_burstQueue << qMakePair(num, QPointF(x(), y()));
297 void QQuickParticleEmitter::burst(int num, qreal x, qreal y)
299 m_burstQueue << qMakePair(num, QPointF(x, y));
302 void QQuickParticleEmitter::setMaxParticleCount(int arg)
304 if (m_maxParticleCount != arg) {
305 if (arg < 0 && m_maxParticleCount >= 0){
306 connect(this, SIGNAL(particlesPerSecondChanged(qreal)),
307 this, SIGNAL(particleCountChanged()));
308 connect(this, SIGNAL(particleDurationChanged(int)),
309 this, SIGNAL(particleCountChanged()));
310 }else if (arg >= 0 && m_maxParticleCount < 0){
311 disconnect(this, SIGNAL(particlesPerSecondChanged(qreal)),
312 this, SIGNAL(particleCountChanged()));
313 disconnect(this, SIGNAL(particleDurationChanged(int)),
314 this, SIGNAL(particleCountChanged()));
316 m_overwrite = arg < 0;
317 m_maxParticleCount = arg;
318 emit maximumEmittedChanged(arg);
322 int QQuickParticleEmitter::particleCount() const
324 if (m_maxParticleCount >= 0)
325 return m_maxParticleCount;
326 return m_particlesPerSecond*((m_particleDuration+m_particleDurationVariation)/1000.0);
329 void QQuickParticleEmitter::setVelocityFromMovement(qreal t)
331 if (t == m_velocity_from_movement)
333 m_velocity_from_movement = t;
334 emit velocityFromMovementChanged();
337 void QQuickParticleEmitter::reset()
342 void QQuickParticleEmitter::emitWindow(int timeStamp)
346 if ((!m_enabled || !m_particlesPerSecond)&& !m_pulseLeft && m_burstQueue.isEmpty()){
352 m_last_emitter = m_last_last_emitter = QPointF(x(), y());
353 if (m_last_timestamp == -1)
354 m_last_timestamp = (timeStamp - m_startTime)/1000.;
356 m_last_timestamp = timeStamp/1000.;
357 m_last_emission = m_last_timestamp;
358 m_reset_last = false;
359 m_emitCap = particleCount();
363 m_pulseLeft -= timeStamp - m_last_timestamp * 1000.;
364 if (m_pulseLeft < 0){
366 timeStamp += m_pulseLeft;
370 qreal time = timeStamp / 1000.;
371 qreal particleRatio = 1. / m_particlesPerSecond;
372 qreal pt = m_last_emission;
373 qreal maxLife = (m_particleDuration + m_particleDurationVariation)/1000.0;
374 if (pt + maxLife < time)//We missed so much, that we should skip emiting particles that are dead by now
377 qreal opt = pt; // original particle time
378 qreal dt = time - m_last_timestamp; // timestamp delta...
382 // emitter difference since last...
383 qreal dex = (x() - m_last_emitter.x());
384 qreal dey = (y() - m_last_emitter.y());
386 qreal ax = (m_last_last_emitter.x() + m_last_emitter.x()) / 2;
387 qreal bx = m_last_emitter.x();
388 qreal cx = (x() + m_last_emitter.x()) / 2;
389 qreal ay = (m_last_last_emitter.y() + m_last_emitter.y()) / 2;
390 qreal by = m_last_emitter.y();
391 qreal cy = (y() + m_last_emitter.y()) / 2;
393 qreal sizeAtEnd = m_particleEndSize >= 0 ? m_particleEndSize : m_particleSize;
394 qreal emitter_x_offset = m_last_emitter.x() - x();
395 qreal emitter_y_offset = m_last_emitter.y() - y();
396 if (!m_burstQueue.isEmpty() && !m_pulseLeft && !m_enabled)//'outside time' emissions only
399 QList<QQuickParticleData*> toEmit;
401 while ((pt < time && m_emitCap) || !m_burstQueue.isEmpty()) {
402 //int pos = m_last_particle % m_particle_count;
403 QQuickParticleData* datum = m_system->newDatum(m_system->groupIds[m_group], !m_overwrite);
404 if (datum){//actually emit(otherwise we've been asked to skip this one)
405 datum->e = this;//###useful?
406 qreal t = 1 - (pt - opt) / dt;
409 + 2 * bx * (1 - 2 * t)
413 + 2 * by * (1 - 2 * t)
417 // Particle timestamp
421 + ((rand() % ((m_particleDurationVariation*2) + 1)) - m_particleDurationVariation))
424 if (datum->lifeSpan >= m_system->maxLife){
425 datum->lifeSpan = m_system->maxLife;
426 m_emitCap--;//emitCap keeps us from reemitting 'infinite' particles after their life. Unless you reset the emitter.
431 if (!m_burstQueue.isEmpty()){
432 boundsRect = QRectF(m_burstQueue.first().second.x() - x(), m_burstQueue.first().second.y() - y(),
435 boundsRect = QRectF(emitter_x_offset + dex * (pt - opt) / dt, emitter_y_offset + dey * (pt - opt) / dt
436 , width(), height());
438 QPointF newPos = effectiveExtruder()->extrude(boundsRect);
439 datum->x = newPos.x();
440 datum->y = newPos.y();
443 const QPointF &velocity = m_velocity->sample(newPos);
444 datum->vx = velocity.x()
445 + m_velocity_from_movement * vx;
446 datum->vy = velocity.y()
447 + m_velocity_from_movement * vy;
449 // Particle acceleration
450 const QPointF &accel = m_acceleration->sample(newPos);
451 datum->ax = accel.x();
452 datum->ay = accel.y();
455 float sizeVariation = -m_particleSizeVariation
456 + rand() / float(RAND_MAX) * m_particleSizeVariation * 2;
458 float size = qMax((qreal)0.0 , m_particleSize + sizeVariation);
459 float endSize = qMax((qreal)0.0 , sizeAtEnd + sizeVariation);
461 datum->size = size;// * float(m_emitting);
462 datum->endSize = endSize;// * float(m_emitting);
466 if (m_burstQueue.isEmpty()){
469 m_burstQueue.first().first--;
470 if (m_burstQueue.first().first <= 0)
471 m_burstQueue.pop_front();
475 foreach (QQuickParticleData* d, toEmit)
476 m_system->emitParticle(d);
478 if (isEmitConnected()) {
479 //Done after emitParticle so that the Painter::load is done first, this allows you to customize its static variables
480 //We then don't need to request another reload, because the first reload isn't scheduled until we get back to the render thread
481 v8::HandleScope handle_scope;
482 v8::Context::Scope scope(QQmlEnginePrivate::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(QQmlV8Handle::fromHandle(array));//A chance for arbitrary JS changes
490 m_last_emission = pt;
492 m_last_last_last_emitter = m_last_last_emitter;
493 m_last_last_emitter = m_last_emitter;
494 m_last_emitter = QPointF(x(), y());
495 m_last_timestamp = time;