Also check notifier endpoints when checking whether a signal is connected.
[profile/ivi/qtdeclarative.git] / src / particles / qquickparticleemitter.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the QtQuick module of the Qt Toolkit.
7 **
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.
16 **
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.
20 **
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.
28 **
29 ** Other Usage
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.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qquickparticleemitter_p.h"
43 #include <private/qqmlengine_p.h>
44 #include <private/qqmlglobal_p.h>
45 QT_BEGIN_NAMESPACE
46
47
48 /*!
49     \qmlclass Emitter QQuickParticleEmitter
50     \inqmlmodule QtQuick.Particles 2
51     \brief The Emitter element allows you to emit logical particles.
52
53     This element emits logical particles into the ParticleSystem, with the
54     given starting attributes.
55
56     Note that logical particles are not
57     automatically rendered, you will need to have one or more
58     ParticlePainter elements visualizing them.
59
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.
63 */
64
65
66 /*!
67     \qmlproperty ParticleSystem QtQuick.Particles2::Emitter::system
68
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
71 */
72 /*!
73     \qmlproperty string QtQuick.Particles2::Emitter::group
74
75     This is the logical particle group which it will emit into.
76
77     Default value is "" (empty string).
78 */
79 /*!
80     \qmlproperty Shape QtQuick.Particles2::Emitter::shape
81
82     This shape is applied with the size of the Emitter. Particles will be emitted
83     randomly from any area covered by the shape.
84
85     The default shape is a filled in rectangle, which corresponds to the full bounding
86     box of the Emitter.
87 */
88 /*!
89     \qmlproperty bool QtQuick.Particles2::Emitter::emitting
90
91     If set to false, the emitter will cease emissions until it is set to true.
92
93     Default value is true.
94 */
95 /*!
96     \qmlproperty real QtQuick.Particles2::Emitter::emitRate
97
98     Number of particles emitted per second.
99
100     Default value is 10 particles per second.
101 */
102 /*!
103     \qmlproperty int QtQuick.Particles2::Emitter::lifeSpan
104
105     The time in milliseconds each emitted particle should last for.
106
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.
109
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.
112
113     Default value is 1000 (one second).
114 */
115 /*!
116     \qmlproperty int QtQuick.Particles2::Emitter::lifeSpanVariation
117
118     Particle lifespans will vary by up to this much in either direction.
119
120     Default value is 0.
121 */
122
123 /*!
124     \qmlproperty int QtQuick.Particles2::Emitter::maximumEmitted
125
126     The maximum number of particles at a time that this emitter will have alive.
127
128     This can be set as a performance optimization (when using burst and pulse) or
129     to stagger emissions.
130
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.
133
134     The default value is -1.
135 */
136 /*!
137     \qmlproperty int QtQuick.Particles2::Emitter::startTime
138
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.
142 */
143 /*!
144     \qmlproperty real QtQuick.Particles2::Emitter::size
145
146     The size in pixels of the particles at the start of their life.
147
148     Default value is 16.
149 */
150 /*!
151     \qmlproperty real QtQuick.Particles2::Emitter::endSize
152
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
156     the starting size.
157
158     Default value is -1.
159 */
160 /*!
161     \qmlproperty real QtQuick.Particles2::Emitter::sizeVariation
162
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.
165
166     Default value is 0.
167 */
168 /*!
169     \qmlproperty StochasticDirection QtQuick.Particles2::Emitter::speed
170
171     The starting speed of the particles emitted.
172 */
173 /*!
174     \qmlproperty StochasticDirection QtQuick.Particles2::Emitter::acceleration
175
176     The starting acceleraton of the particles emitted.
177 */
178 /*!
179     \qmlproperty qreal QtQuick.Particles2::Emitter::speedFromMovement
180
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.
185
186     Default value is 0.
187 */
188
189 /*!
190     \qmlsignal QtQuick.Particles2::Emitter::onEmitParticles(Array particles)
191
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.
194
195     Note that JS is slower to execute, so it is not recommended to use this in
196     high-volume particle systems.
197 */
198
199 /*! \qmlmethod QtQuick.Particles2::Emitter::burst(int count)
200
201     Emits count particles from this emitter immediately.
202 */
203
204 /*! \qmlmethod QtQuick.Particles2::Emitter::burst(int x, int y, int count)
205
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.
208 */
209
210 /*! \qmlmethod QtQuick.Particles2::Emitter::pulse(int duration)
211
212     If the emitter is not enabled, enables it for duration milliseconds and then switches
213     it back off.
214 */
215
216 QQuickParticleEmitter::QQuickParticleEmitter(QQuickItem *parent) :
217     QQuickItem(parent)
218   , m_particlesPerSecond(10)
219   , m_particleDuration(1000)
220   , m_particleDurationVariation(0)
221   , m_enabled(true)
222   , m_system(0)
223   , m_extruder(0)
224   , m_defaultExtruder(0)
225   , m_speed(&m_nullVector)
226   , m_acceleration(&m_nullVector)
227   , m_particleSize(16)
228   , m_particleEndSize(-1)
229   , m_particleSizeVariation(0)
230   , m_startTime(0)
231   , m_overwrite(true)
232   , m_pulseLeft(0)
233   , m_maxParticleCount(-1)
234   , m_speed_from_movement(0)
235   , m_reset_last(true)
236   , m_last_timestamp(-1)
237   , m_last_emission(0)
238
239 {
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()));
247 }
248
249 QQuickParticleEmitter::~QQuickParticleEmitter()
250 {
251     if (m_defaultExtruder)
252         delete m_defaultExtruder;
253 }
254
255 bool QQuickParticleEmitter::isEmitConnected()
256 {
257     IS_SIGNAL_CONNECTED(this, "emitParticles(QQmlV8Handle)");
258 }
259
260 void QQuickParticleEmitter::componentComplete()
261 {
262     if (!m_system && qobject_cast<QQuickParticleSystem*>(parentItem()))
263         setSystem(qobject_cast<QQuickParticleSystem*>(parentItem()));
264     QQuickItem::componentComplete();
265 }
266
267 void QQuickParticleEmitter::setEnabled(bool arg)
268 {
269     if (m_enabled != arg) {
270         m_enabled = arg;
271         emit enabledChanged(arg);
272     }
273 }
274
275
276 QQuickParticleExtruder* QQuickParticleEmitter::effectiveExtruder()
277 {
278     if (m_extruder)
279         return m_extruder;
280     if (!m_defaultExtruder)
281         m_defaultExtruder = new QQuickParticleExtruder;
282     return m_defaultExtruder;
283 }
284
285 void QQuickParticleEmitter::pulse(int milliseconds)
286 {
287     if (!m_enabled)
288         m_pulseLeft = milliseconds;
289 }
290
291 void QQuickParticleEmitter::burst(int num)
292 {
293     m_burstQueue << qMakePair(num, QPointF(x(), y()));
294 }
295
296 void QQuickParticleEmitter::burst(int num, qreal x, qreal y)
297 {
298     m_burstQueue << qMakePair(num, QPointF(x, y));
299 }
300
301 void QQuickParticleEmitter::setMaxParticleCount(int arg)
302 {
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()));
314         }
315         m_overwrite = arg < 0;
316         m_maxParticleCount = arg;
317         emit maximumEmittedChanged(arg);
318     }
319 }
320
321 int QQuickParticleEmitter::particleCount() const
322 {
323     if (m_maxParticleCount >= 0)
324         return m_maxParticleCount;
325     return m_particlesPerSecond*((m_particleDuration+m_particleDurationVariation)/1000.0);
326 }
327
328 void QQuickParticleEmitter::setSpeedFromMovement(qreal t)
329 {
330     if (t == m_speed_from_movement)
331         return;
332     m_speed_from_movement = t;
333     emit speedFromMovementChanged();
334 }
335
336 void QQuickParticleEmitter::reset()
337 {
338     m_reset_last = true;
339 }
340
341 void QQuickParticleEmitter::emitWindow(int timeStamp)
342 {
343     if (m_system == 0)
344         return;
345     if ((!m_enabled || !m_particlesPerSecond)&& !m_pulseLeft && m_burstQueue.isEmpty()){
346         m_reset_last = true;
347         return;
348     }
349
350     if (m_reset_last) {
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.;
354         else
355             m_last_timestamp = timeStamp/1000.;
356         m_last_emission = m_last_timestamp;
357         m_reset_last = false;
358         m_emitCap = particleCount();
359     }
360
361     if (m_pulseLeft){
362         m_pulseLeft -= timeStamp - m_last_timestamp * 1000.;
363         if (m_pulseLeft < 0){
364             if (!m_enabled)
365                 timeStamp += m_pulseLeft;
366             m_pulseLeft = 0;
367         }
368     }
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
374         pt = time - maxLife;
375
376     qreal opt = pt; // original particle time
377     qreal dt = time - m_last_timestamp; // timestamp delta...
378     if (!dt)
379         dt = 0.000001;
380
381     // emitter difference since last...
382     qreal dex = (x() - m_last_emitter.x());
383     qreal dey = (y() - m_last_emitter.y());
384
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;
391
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
396         pt = time;
397
398     QList<QQuickParticleData*> toEmit;
399
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;
406             qreal vx =
407               - 2 * ax * (1 - t)
408               + 2 * bx * (1 - 2 * t)
409               + 2 * cx * t;
410             qreal vy =
411               - 2 * ay * (1 - t)
412               + 2 * by * (1 - 2 * t)
413               + 2 * cy * t;
414
415
416             // Particle timestamp
417             datum->t = pt;
418             datum->lifeSpan =
419                     (m_particleDuration
420                      + ((rand() % ((m_particleDurationVariation*2) + 1)) - m_particleDurationVariation))
421                     / 1000.0;
422
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.
426             }
427
428             // Particle position
429             QRectF boundsRect;
430             if (!m_burstQueue.isEmpty()){
431                 boundsRect = QRectF(m_burstQueue.first().second.x() - x(), m_burstQueue.first().second.y() - y(),
432                         width(), height());
433             } else {
434                 boundsRect = QRectF(emitter_x_offset + dex * (pt - opt) / dt, emitter_y_offset + dey * (pt - opt) / dt
435                               , width(), height());
436             }
437             QPointF newPos = effectiveExtruder()->extrude(boundsRect);
438             datum->x = newPos.x();
439             datum->y = newPos.y();
440
441             // Particle speed
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;
447
448             // Particle acceleration
449             const QPointF &accel = m_acceleration->sample(newPos);
450             datum->ax = accel.x();
451             datum->ay = accel.y();
452
453             // Particle size
454             float sizeVariation = -m_particleSizeVariation
455                     + rand() / float(RAND_MAX) * m_particleSizeVariation * 2;
456
457             float size = qMax((qreal)0.0 , m_particleSize + sizeVariation);
458             float endSize = qMax((qreal)0.0 , sizeAtEnd + sizeVariation);
459
460             datum->size = size;// * float(m_emitting);
461             datum->endSize = endSize;// * float(m_emitting);
462
463             toEmit << datum;
464         }
465         if (m_burstQueue.isEmpty()){
466             pt += particleRatio;
467         }else{
468             m_burstQueue.first().first--;
469             if (m_burstQueue.first().first <= 0)
470                 m_burstQueue.pop_front();
471         }
472     }
473
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());
480
481         emitParticles(QQmlV8Handle::fromHandle(array));//A chance for arbitrary JS changes
482     }
483     foreach (QQuickParticleData* d, toEmit)
484             m_system->emitParticle(d);
485
486     m_last_emission = pt;
487
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;
492 }
493
494
495 QT_END_NAMESPACE