448e655416a959f939266875d3c88d9b7c38c585
[profile/ivi/qtdeclarative.git] / src / quick / particles / qquickparticleemitter.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: http://www.qt-project.org/
6 **
7 ** This file is part of the Declarative module of the Qt Toolkit.
8 **
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.
17 **
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.
21 **
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.
29 **
30 ** Other Usage
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.
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qquickparticleemitter_p.h"
43 #include <private/qdeclarativeengine_p.h>
44 QT_BEGIN_NAMESPACE
45
46
47 /*!
48     \qmlclass Emitter QQuickParticleEmitter
49     \inqmlmodule QtQuick.Particles 2
50     \brief The Emitter element allows you to emit logical particles.
51
52     This element emits logical particles into the ParticleSystem, with the
53     given starting attributes.
54
55     Note that logical particles are not
56     automatically rendered, you will need to have one or more
57     ParticlePainter elements visualizing them.
58
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.
62 */
63
64
65 /*!
66     \qmlproperty ParticleSystem QtQuick.Particles2::Emitter::system
67
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
70 */
71 /*!
72     \qmlproperty string QtQuick.Particles2::Emitter::group
73
74     This is the logical particle group which it will emit into.
75
76     Default value is "" (empty string).
77 */
78 /*!
79     \qmlproperty Shape QtQuick.Particles2::Emitter::shape
80
81     This shape is applied with the size of the Emitter. Particles will be emitted
82     randomly from any area covered by the shape.
83
84     The default shape is a filled in rectangle, which corresponds to the full bounding
85     box of the Emitter.
86 */
87 /*!
88     \qmlproperty bool QtQuick.Particles2::Emitter::emitting
89
90     If set to false, the emitter will cease emissions until it is set to true.
91
92     Default value is true.
93 */
94 /*!
95     \qmlproperty real QtQuick.Particles2::Emitter::emitRate
96
97     Number of particles emitted per second.
98
99     Default value is 10 particles per second.
100 */
101 /*!
102     \qmlproperty int QtQuick.Particles2::Emitter::lifeSpan
103
104     The time in milliseconds each emitted particle should last for.
105
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.
108
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.
111
112     Default value is 1000 (one second).
113 */
114 /*!
115     \qmlproperty int QtQuick.Particles2::Emitter::lifeSpanVariation
116
117     Particle lifespans will vary by up to this much in either direction.
118
119     Default value is 0.
120 */
121
122 /*!
123     \qmlproperty int QtQuick.Particles2::Emitter::maximumEmitted
124
125     The maximum number of particles at a time that this emitter will have alive.
126
127     This can be set as a performance optimization (when using burst and pulse) or
128     to stagger emissions.
129
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.
132
133     The default value is -1.
134 */
135 /*!
136     \qmlproperty int QtQuick.Particles2::Emitter::startTime
137
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.
141 */
142 /*!
143     \qmlproperty real QtQuick.Particles2::Emitter::size
144
145     The size in pixels of the particles at the start of their life.
146
147     Default value is 16.
148 */
149 /*!
150     \qmlproperty real QtQuick.Particles2::Emitter::endSize
151
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
155     the starting size.
156
157     Default value is -1.
158 */
159 /*!
160     \qmlproperty real QtQuick.Particles2::Emitter::sizeVariation
161
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.
164
165     Default value is 0.
166 */
167 /*!
168     \qmlproperty StochasticDirection QtQuick.Particles2::Emitter::speed
169
170     The starting speed of the particles emitted.
171 */
172 /*!
173     \qmlproperty StochasticDirection QtQuick.Particles2::Emitter::acceleration
174
175     The starting acceleraton of the particles emitted.
176 */
177 /*!
178     \qmlproperty qreal QtQuick.Particles2::Emitter::speedFromMovement
179
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.
184
185     Default value is 0.
186 */
187
188 /*!
189     \qmlsignal QtQuick.Particles2::Emitter::onEmitParticles(Array particles)
190
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.
193
194     Note that JS is slower to execute, so it is not recommended to use this in
195     high-volume particle systems.
196 */
197
198 /*! \qmlmethod QtQuick.Particles2::Emitter::burst(int count)
199
200     Emits count particles from this emitter immediately.
201 */
202
203 /*! \qmlmethod QtQuick.Particles2::Emitter::burst(int x, int y, int count)
204
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.
207 */
208
209 /*! \qmlmethod QtQuick.Particles2::Emitter::pulse(int duration)
210
211     If the emitter is not enabled, enables it for duration milliseconds and then switches
212     it back off.
213 */
214
215 QQuickParticleEmitter::QQuickParticleEmitter(QQuickItem *parent) :
216     QQuickItem(parent)
217   , m_particlesPerSecond(10)
218   , m_particleDuration(1000)
219   , m_particleDurationVariation(0)
220   , m_enabled(true)
221   , m_system(0)
222   , m_extruder(0)
223   , m_defaultExtruder(0)
224   , m_speed(&m_nullVector)
225   , m_acceleration(&m_nullVector)
226   , m_particleSize(16)
227   , m_particleEndSize(-1)
228   , m_particleSizeVariation(0)
229   , m_startTime(0)
230   , m_overwrite(true)
231   , m_pulseLeft(0)
232   , m_maxParticleCount(-1)
233   , m_speed_from_movement(0)
234   , m_reset_last(true)
235   , m_last_timestamp(-1)
236   , m_last_emission(0)
237
238 {
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()));
246 }
247
248 QQuickParticleEmitter::~QQuickParticleEmitter()
249 {
250     if (m_defaultExtruder)
251         delete m_defaultExtruder;
252 }
253
254 bool QQuickParticleEmitter::isEmitConnected()
255 {
256     static int idx = QObjectPrivate::get(this)->signalIndex("emitParticles(QDeclarativeV8Handle)");
257     return QObjectPrivate::get(this)->isSignalConnected(idx);
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 (!particleCount())
288         qWarning() << "pulse called on an emitter with a particle count of zero";
289     if (!m_enabled)
290         m_pulseLeft = milliseconds;
291 }
292
293 void QQuickParticleEmitter::burst(int num)
294 {
295     if (!particleCount())
296         qWarning() << "burst called on an emitter with a particle count of zero";
297     m_burstQueue << qMakePair(num, QPointF(x(), y()));
298 }
299
300 void QQuickParticleEmitter::burst(int num, qreal x, qreal y)
301 {
302     if (!particleCount())
303         qWarning() << "burst called on an emitter with a particle count of zero";
304     m_burstQueue << qMakePair(num, QPointF(x, y));
305 }
306
307 void QQuickParticleEmitter::setMaxParticleCount(int arg)
308 {
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()));
320         }
321         m_overwrite = arg < 0;
322         m_maxParticleCount = arg;
323         emit maximumEmittedChanged(arg);
324     }
325 }
326
327 int QQuickParticleEmitter::particleCount() const
328 {
329     if (m_maxParticleCount >= 0)
330         return m_maxParticleCount;
331     return m_particlesPerSecond*((m_particleDuration+m_particleDurationVariation)/1000.0);
332 }
333
334 void QQuickParticleEmitter::setSpeedFromMovement(qreal t)
335 {
336     if (t == m_speed_from_movement)
337         return;
338     m_speed_from_movement = t;
339     emit speedFromMovementChanged();
340 }
341
342 void QQuickParticleEmitter::reset()
343 {
344     m_reset_last = true;
345 }
346
347 void QQuickParticleEmitter::emitWindow(int timeStamp)
348 {
349     if (m_system == 0)
350         return;
351     if ((!m_enabled || !m_particlesPerSecond)&& !m_pulseLeft && m_burstQueue.isEmpty()){
352         m_reset_last = true;
353         return;
354     }
355
356     if (m_reset_last) {
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.;
360         else
361             m_last_timestamp = timeStamp/1000.;
362         m_last_emission = m_last_timestamp;
363         m_reset_last = false;
364         m_emitCap = particleCount();
365     }
366
367     if (m_pulseLeft){
368         m_pulseLeft -= timeStamp - m_last_timestamp * 1000.;
369         if (m_pulseLeft < 0){
370             if (!m_enabled)
371                 timeStamp += m_pulseLeft;
372             m_pulseLeft = 0;
373         }
374     }
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
380         pt = time - maxLife;
381
382     qreal opt = pt; // original particle time
383     qreal dt = time - m_last_timestamp; // timestamp delta...
384     if (!dt)
385         dt = 0.000001;
386
387     // emitter difference since last...
388     qreal dex = (x() - m_last_emitter.x());
389     qreal dey = (y() - m_last_emitter.y());
390
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;
397
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
402         pt = time;
403
404     QList<QQuickParticleData*> toEmit;
405
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;
412             qreal vx =
413               - 2 * ax * (1 - t)
414               + 2 * bx * (1 - 2 * t)
415               + 2 * cx * t;
416             qreal vy =
417               - 2 * ay * (1 - t)
418               + 2 * by * (1 - 2 * t)
419               + 2 * cy * t;
420
421
422             // Particle timestamp
423             datum->t = pt;
424             datum->lifeSpan =
425                     (m_particleDuration
426                      + ((rand() % ((m_particleDurationVariation*2) + 1)) - m_particleDurationVariation))
427                     / 1000.0;
428
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.
432             }
433
434             // Particle position
435             QRectF boundsRect;
436             if (!m_burstQueue.isEmpty()){
437                 boundsRect = QRectF(m_burstQueue.first().second.x() - x(), m_burstQueue.first().second.y() - y(),
438                         width(), height());
439             } else {
440                 boundsRect = QRectF(emitter_x_offset + dex * (pt - opt) / dt, emitter_y_offset + dey * (pt - opt) / dt
441                               , width(), height());
442             }
443             QPointF newPos = effectiveExtruder()->extrude(boundsRect);
444             datum->x = newPos.x();
445             datum->y = newPos.y();
446
447             // Particle speed
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;
453
454             // Particle acceleration
455             const QPointF &accel = m_acceleration->sample(newPos);
456             datum->ax = accel.x();
457             datum->ay = accel.y();
458
459             // Particle size
460             float sizeVariation = -m_particleSizeVariation
461                     + rand() / float(RAND_MAX) * m_particleSizeVariation * 2;
462
463             float size = qMax((qreal)0.0 , m_particleSize + sizeVariation);
464             float endSize = qMax((qreal)0.0 , sizeAtEnd + sizeVariation);
465
466             datum->size = size;// * float(m_emitting);
467             datum->endSize = endSize;// * float(m_emitting);
468
469             toEmit << datum;
470         }
471         if (m_burstQueue.isEmpty()){
472             pt += particleRatio;
473         }else{
474             m_burstQueue.first().first--;
475             if (m_burstQueue.first().first <= 0)
476                 m_burstQueue.pop_front();
477         }
478     }
479
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());
486
487         emitParticles(QDeclarativeV8Handle::fromHandle(array));//A chance for arbitrary JS changes
488     }
489     foreach (QQuickParticleData* d, toEmit)
490             m_system->emitParticle(d);
491
492     m_last_emission = pt;
493
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;
498 }
499
500
501 QT_END_NAMESPACE