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