Fixes a crash in QQuickImageParticle
[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     \qmltype Emitter
50     \instantiates QQuickParticleEmitter
51     \inqmlmodule QtQuick.Particles 2
52     \brief Emits logical particles
53     \ingroup qtquick-particles
54
55     This element emits logical particles into the ParticleSystem, with the
56     given starting attributes.
57
58     Note that logical particles are not
59     automatically rendered, you will need to have one or more
60     ParticlePainter elements visualizing them.
61
62     Note that the given starting attributes can be modified at any point
63     in the particle's lifetime by any Affector element in the same
64     ParticleSystem. This includes attributes like lifespan.
65 */
66
67
68 /*!
69     \qmlproperty ParticleSystem QtQuick.Particles2::Emitter::system
70
71     This is the Particle system that the Emitter will emit into.
72     This can be omitted if the Emitter is a direct child of the ParticleSystem
73 */
74 /*!
75     \qmlproperty string QtQuick.Particles2::Emitter::group
76
77     This is the logical particle group which it will emit into.
78
79     Default value is "" (empty string).
80 */
81 /*!
82     \qmlproperty Shape QtQuick.Particles2::Emitter::shape
83
84     This shape is applied with the size of the Emitter. Particles will be emitted
85     randomly from any area covered by the shape.
86
87     The default shape is a filled in rectangle, which corresponds to the full bounding
88     box of the Emitter.
89 */
90 /*!
91     \qmlproperty bool QtQuick.Particles2::Emitter::emitting
92
93     If set to false, the emitter will cease emissions until it is set to true.
94
95     Default value is true.
96 */
97 /*!
98     \qmlproperty real QtQuick.Particles2::Emitter::emitRate
99
100     Number of particles emitted per second.
101
102     Default value is 10 particles per second.
103 */
104 /*!
105     \qmlproperty int QtQuick.Particles2::Emitter::lifeSpan
106
107     The time in milliseconds each emitted particle should last for.
108
109     If you do not want particles to automatically die after a time, for example if
110     you wish to dispose of them manually, set lifeSpan to Emitter.InfiniteLife.
111
112     lifeSpans greater than or equal to 600000 (10 minutes) will be treated as infinite.
113     Particles with lifeSpans less than or equal to 0 will start out dead.
114
115     Default value is 1000 (one second).
116 */
117 /*!
118     \qmlproperty int QtQuick.Particles2::Emitter::lifeSpanVariation
119
120     Particle lifespans will vary by up to this much in either direction.
121
122     Default value is 0.
123 */
124
125 /*!
126     \qmlproperty int QtQuick.Particles2::Emitter::maximumEmitted
127
128     The maximum number of particles at a time that this emitter will have alive.
129
130     This can be set as a performance optimization (when using burst and pulse) or
131     to stagger emissions.
132
133     If this is set to a number below zero, then there is no maximum limit on the number
134     of particles this emitter can have alive.
135
136     The default value is -1.
137 */
138 /*!
139     \qmlproperty int QtQuick.Particles2::Emitter::startTime
140
141     If this value is set when the emitter is loaded, then it will emit particles from the
142     past, up to startTime milliseconds ago. These will simulate as if they were emitted then,
143     but will not have any affectors applied to them. Affectors will take effect from the present time.
144 */
145 /*!
146     \qmlproperty real QtQuick.Particles2::Emitter::size
147
148     The size in pixels of the particles at the start of their life.
149
150     Default value is 16.
151 */
152 /*!
153     \qmlproperty real QtQuick.Particles2::Emitter::endSize
154
155     The size in pixels of the particles at the end of their life. Size will
156     be linearly interpolated during the life of the particle from this value and
157     size. If endSize is -1, then the size of the particle will remain constant at
158     the starting size.
159
160     Default value is -1.
161 */
162 /*!
163     \qmlproperty real QtQuick.Particles2::Emitter::sizeVariation
164
165     The size of a particle can vary by this much up or down from size/endSize. The same
166     random addition is made to both size and endSize for a single particle.
167
168     Default value is 0.
169 */
170 /*!
171     \qmlproperty StochasticDirection QtQuick.Particles2::Emitter::velocity
172
173     The starting velocity of the particles emitted.
174 */
175 /*!
176     \qmlproperty StochasticDirection QtQuick.Particles2::Emitter::acceleration
177
178     The starting acceleraton of the particles emitted.
179 */
180 /*!
181     \qmlproperty qreal QtQuick.Particles2::Emitter::velocityFromMovement
182
183     If this value is non-zero, then any movement of the emitter will provide additional
184     starting velocity to the particles based on the movement. The additional vector will be the
185     same angle as the emitter's movement, with a magnitude that is the magnitude of the emitters
186     movement multiplied by velocityFromMovement.
187
188     Default value is 0.
189 */
190
191 /*!
192     \qmlsignal QtQuick.Particles2::Emitter::onEmitParticles(Array particles)
193
194     This handler is called when particles are emitted. particles is a JavaScript
195     array of Particle objects. You can modify particle attributes directly within the handler.
196
197     Note that JavaScript is slower to execute, so it is not recommended to use this in
198     high-volume particle systems.
199 */
200
201 /*! \qmlmethod QtQuick.Particles2::Emitter::burst(int count)
202
203     Emits count particles from this emitter immediately.
204 */
205
206 /*! \qmlmethod QtQuick.Particles2::Emitter::burst(int x, int y, int count)
207
208     Emits count particles from this emitter immediately. The particles are emitted
209     as if the Emitter was positioned at x,y but all other properties are the same.
210 */
211
212 /*! \qmlmethod QtQuick.Particles2::Emitter::pulse(int duration)
213
214     If the emitter is not enabled, enables it for duration milliseconds and then switches
215     it back off.
216 */
217
218 QQuickParticleEmitter::QQuickParticleEmitter(QQuickItem *parent) :
219     QQuickItem(parent)
220   , m_particlesPerSecond(10)
221   , m_particleDuration(1000)
222   , m_particleDurationVariation(0)
223   , m_enabled(true)
224   , m_system(0)
225   , m_extruder(0)
226   , m_defaultExtruder(0)
227   , m_velocity(&m_nullVector)
228   , m_acceleration(&m_nullVector)
229   , m_particleSize(16)
230   , m_particleEndSize(-1)
231   , m_particleSizeVariation(0)
232   , m_startTime(0)
233   , m_overwrite(true)
234   , m_pulseLeft(0)
235   , m_maxParticleCount(-1)
236   , m_velocity_from_movement(0)
237   , m_reset_last(true)
238   , m_last_timestamp(-1)
239   , m_last_emission(0)
240
241 {
242     //TODO: Reset velocity/acc back to null vector? Or allow null pointer?
243     connect(this, SIGNAL(maximumEmittedChanged(int)),
244             this, SIGNAL(particleCountChanged()));
245     connect(this, SIGNAL(particlesPerSecondChanged(qreal)),
246             this, SIGNAL(particleCountChanged()));
247     connect(this, SIGNAL(particleDurationChanged(int)),
248             this, SIGNAL(particleCountChanged()));
249 }
250
251 QQuickParticleEmitter::~QQuickParticleEmitter()
252 {
253     if (m_defaultExtruder)
254         delete m_defaultExtruder;
255 }
256
257 bool QQuickParticleEmitter::isEmitConnected()
258 {
259     IS_SIGNAL_CONNECTED(this, QQuickParticleEmitter, emitParticles, (QQmlV8Handle));
260 }
261
262 void QQuickParticleEmitter::componentComplete()
263 {
264     if (!m_system && qobject_cast<QQuickParticleSystem*>(parentItem()))
265         setSystem(qobject_cast<QQuickParticleSystem*>(parentItem()));
266     QQuickItem::componentComplete();
267 }
268
269 void QQuickParticleEmitter::setEnabled(bool arg)
270 {
271     if (m_enabled != arg) {
272         m_enabled = arg;
273         emit enabledChanged(arg);
274     }
275 }
276
277
278 QQuickParticleExtruder* QQuickParticleEmitter::effectiveExtruder()
279 {
280     if (m_extruder)
281         return m_extruder;
282     if (!m_defaultExtruder)
283         m_defaultExtruder = new QQuickParticleExtruder;
284     return m_defaultExtruder;
285 }
286
287 void QQuickParticleEmitter::pulse(int milliseconds)
288 {
289     if (!m_enabled)
290         m_pulseLeft = milliseconds;
291 }
292
293 void QQuickParticleEmitter::burst(int num)
294 {
295     m_burstQueue << qMakePair(num, QPointF(x(), y()));
296 }
297
298 void QQuickParticleEmitter::burst(int num, qreal x, qreal y)
299 {
300     m_burstQueue << qMakePair(num, QPointF(x, y));
301 }
302
303 void QQuickParticleEmitter::setMaxParticleCount(int arg)
304 {
305     if (m_maxParticleCount != arg) {
306         if (arg < 0 && m_maxParticleCount >= 0){
307             connect(this, SIGNAL(particlesPerSecondChanged(qreal)),
308                     this, SIGNAL(particleCountChanged()));
309             connect(this, SIGNAL(particleDurationChanged(int)),
310                     this, SIGNAL(particleCountChanged()));
311         }else if (arg >= 0 && m_maxParticleCount < 0){
312             disconnect(this, SIGNAL(particlesPerSecondChanged(qreal)),
313                     this, SIGNAL(particleCountChanged()));
314             disconnect(this, SIGNAL(particleDurationChanged(int)),
315                     this, SIGNAL(particleCountChanged()));
316         }
317         m_overwrite = arg < 0;
318         m_maxParticleCount = arg;
319         emit maximumEmittedChanged(arg);
320     }
321 }
322
323 int QQuickParticleEmitter::particleCount() const
324 {
325     if (m_maxParticleCount >= 0)
326         return m_maxParticleCount;
327     return m_particlesPerSecond*((m_particleDuration+m_particleDurationVariation)/1000.0);
328 }
329
330 void QQuickParticleEmitter::setVelocityFromMovement(qreal t)
331 {
332     if (t == m_velocity_from_movement)
333         return;
334     m_velocity_from_movement = t;
335     emit velocityFromMovementChanged();
336 }
337
338 void QQuickParticleEmitter::reset()
339 {
340     m_reset_last = true;
341 }
342
343 void QQuickParticleEmitter::emitWindow(int timeStamp)
344 {
345     if (m_system == 0)
346         return;
347     if ((!m_enabled || !m_particlesPerSecond)&& !m_pulseLeft && m_burstQueue.isEmpty()){
348         m_reset_last = true;
349         return;
350     }
351
352     if (m_reset_last) {
353         m_last_emitter = m_last_last_emitter = QPointF(x(), y());
354         if (m_last_timestamp == -1)
355             m_last_timestamp = (timeStamp - m_startTime)/1000.;
356         else
357             m_last_timestamp = timeStamp/1000.;
358         m_last_emission = m_last_timestamp;
359         m_reset_last = false;
360         m_emitCap = particleCount();
361     }
362
363     if (m_pulseLeft){
364         m_pulseLeft -= timeStamp - m_last_timestamp * 1000.;
365         if (m_pulseLeft < 0){
366             if (!m_enabled)
367                 timeStamp += m_pulseLeft;
368             m_pulseLeft = 0;
369         }
370     }
371     qreal time = timeStamp / 1000.;
372     qreal particleRatio = 1. / m_particlesPerSecond;
373     qreal pt = m_last_emission;
374     qreal maxLife = (m_particleDuration + m_particleDurationVariation)/1000.0;
375     if (pt + maxLife < time)//We missed so much, that we should skip emiting particles that are dead by now
376         pt = time - maxLife;
377
378     qreal opt = pt; // original particle time
379     qreal dt = time - m_last_timestamp; // timestamp delta...
380     if (!dt)
381         dt = 0.000001;
382
383     // emitter difference since last...
384     qreal dex = (x() - m_last_emitter.x());
385     qreal dey = (y() - m_last_emitter.y());
386
387     qreal ax = (m_last_last_emitter.x() + m_last_emitter.x()) / 2;
388     qreal bx = m_last_emitter.x();
389     qreal cx = (x() + m_last_emitter.x()) / 2;
390     qreal ay = (m_last_last_emitter.y() + m_last_emitter.y()) / 2;
391     qreal by = m_last_emitter.y();
392     qreal cy = (y() + m_last_emitter.y()) / 2;
393
394     qreal sizeAtEnd = m_particleEndSize >= 0 ? m_particleEndSize : m_particleSize;
395     qreal emitter_x_offset = m_last_emitter.x() - x();
396     qreal emitter_y_offset = m_last_emitter.y() - y();
397     if (!m_burstQueue.isEmpty() && !m_pulseLeft && !m_enabled)//'outside time' emissions only
398         pt = time;
399
400     QList<QQuickParticleData*> toEmit;
401
402     while ((pt < time && m_emitCap) || !m_burstQueue.isEmpty()) {
403         //int pos = m_last_particle % m_particle_count;
404         QQuickParticleData* datum = m_system->newDatum(m_system->groupIds[m_group], !m_overwrite);
405         if (datum){//actually emit(otherwise we've been asked to skip this one)
406             datum->e = this;//###useful?
407             qreal t = 1 - (pt - opt) / dt;
408             qreal vx =
409               - 2 * ax * (1 - t)
410               + 2 * bx * (1 - 2 * t)
411               + 2 * cx * t;
412             qreal vy =
413               - 2 * ay * (1 - t)
414               + 2 * by * (1 - 2 * t)
415               + 2 * cy * t;
416
417
418             // Particle timestamp
419             datum->t = pt;
420             datum->lifeSpan =
421                     (m_particleDuration
422                      + ((rand() % ((m_particleDurationVariation*2) + 1)) - m_particleDurationVariation))
423                     / 1000.0;
424
425             if (datum->lifeSpan >= m_system->maxLife){
426                 datum->lifeSpan = m_system->maxLife;
427                 m_emitCap--;//emitCap keeps us from reemitting 'infinite' particles after their life. Unless you reset the emitter.
428             }
429
430             // Particle position
431             QRectF boundsRect;
432             if (!m_burstQueue.isEmpty()){
433                 boundsRect = QRectF(m_burstQueue.first().second.x() - x(), m_burstQueue.first().second.y() - y(),
434                         width(), height());
435             } else {
436                 boundsRect = QRectF(emitter_x_offset + dex * (pt - opt) / dt, emitter_y_offset + dey * (pt - opt) / dt
437                               , width(), height());
438             }
439             QPointF newPos = effectiveExtruder()->extrude(boundsRect);
440             datum->x = newPos.x();
441             datum->y = newPos.y();
442
443             // Particle velocity
444             const QPointF &velocity = m_velocity->sample(newPos);
445             datum->vx = velocity.x()
446                     + m_velocity_from_movement * vx;
447             datum->vy = velocity.y()
448                     + m_velocity_from_movement * vy;
449
450             // Particle acceleration
451             const QPointF &accel = m_acceleration->sample(newPos);
452             datum->ax = accel.x();
453             datum->ay = accel.y();
454
455             // Particle size
456             float sizeVariation = -m_particleSizeVariation
457                     + rand() / float(RAND_MAX) * m_particleSizeVariation * 2;
458
459             float size = qMax((qreal)0.0 , m_particleSize + sizeVariation);
460             float endSize = qMax((qreal)0.0 , sizeAtEnd + sizeVariation);
461
462             datum->size = size;// * float(m_emitting);
463             datum->endSize = endSize;// * float(m_emitting);
464
465             toEmit << datum;
466         }
467         if (m_burstQueue.isEmpty()){
468             pt += particleRatio;
469         }else{
470             m_burstQueue.first().first--;
471             if (m_burstQueue.first().first <= 0)
472                 m_burstQueue.pop_front();
473         }
474     }
475
476     foreach (QQuickParticleData* d, toEmit)
477             m_system->emitParticle(d);
478
479     if (isEmitConnected()) {
480         //Done after emitParticle so that the Painter::load is done first, this allows you to customize its static variables
481         //We then don't need to request another reload, because the first reload isn't scheduled until we get back to the render thread
482         v8::HandleScope handle_scope;
483         v8::Context::Scope scope(QQmlEnginePrivate::getV8Engine(qmlEngine(this))->context());
484         v8::Handle<v8::Array> array = v8::Array::New(toEmit.size());
485         for (int i=0; i<toEmit.size(); i++)
486             array->Set(i, toEmit[i]->v8Value().toHandle());
487
488         emitParticles(QQmlV8Handle::fromHandle(array));//A chance for arbitrary JS changes
489     }
490
491     m_last_emission = pt;
492
493     m_last_last_last_emitter = m_last_last_emitter;
494     m_last_last_emitter = m_last_emitter;
495     m_last_emitter = QPointF(x(), y());
496     m_last_timestamp = time;
497 }
498
499
500 QT_END_NAMESPACE