31b05451b28217f14ab38d8e8c6798e05cdd8a9a
[profile/ivi/qtdeclarative.git] / src / quick / particles / qquicktrailemitter.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 "qquicktrailemitter_p.h"
43 #include <private/qdeclarativeengine_p.h>
44 #include <cmath>
45 QT_BEGIN_NAMESPACE
46
47 /*!
48     \qmlclass TrailEmitter QQuickTrailEmitter
49     \inqmlmodule QtQuick.Particles 2
50     \inherits QQuickParticleEmitter
51     \brief The TrailEmitter element allows you to emit logical particles from other logical particles.
52
53     This element emits logical particles into the ParticleSystem, with the
54     starting positions based on those of other logical particles.
55 */
56 QQuickTrailEmitter::QQuickTrailEmitter(QQuickItem *parent) :
57     QQuickParticleEmitter(parent)
58   , m_particlesPerParticlePerSecond(0)
59   , m_lastTimeStamp(0)
60   , m_emitterXVariation(0)
61   , m_emitterYVariation(0)
62   , m_followCount(0)
63   , m_emissionExtruder(0)
64   , m_defaultEmissionExtruder(new QQuickParticleExtruder(this))
65 {
66     //TODO: If followed increased their size
67     connect(this, SIGNAL(followChanged(QString)),
68             this, SLOT(recalcParticlesPerSecond()));
69     connect(this, SIGNAL(particleDurationChanged(int)),
70             this, SLOT(recalcParticlesPerSecond()));
71     connect(this, SIGNAL(particlesPerParticlePerSecondChanged(int)),
72             this, SLOT(recalcParticlesPerSecond()));
73 }
74
75 /*!
76     \qmlproperty string QtQuick.Particles2::TrailEmitter::follow
77
78     The type of logical particle which this is emitting from.
79 */
80 /*!
81     \qmlproperty qreal QtQuick.Particles2::TrailEmitter::speedFromMovement
82
83     If this value is non-zero, then any movement of the emitter will provide additional
84     starting velocity to the particles based on the movement. The additional vector will be the
85     same angle as the emitter's movement, with a magnitude that is the magnitude of the emitters
86     movement multiplied by speedFromMovement.
87
88     Default value is 0.
89 */
90 /*!
91     \qmlproperty Shape QtQuick.Particles2::TrailEmitter::emitShape
92
93     As the area of a TrailEmitter is the area it follows, a separate shape can be provided
94     to be the shape it emits out of. This shape has width and height specified by emitWidth
95     and emitHeight, and is centered on the followed particle's position.
96
97     The default shape is a filled Rectangle.
98 */
99 /*!
100     \qmlproperty real QtQuick.Particles2::TrailEmitter::emitWidth
101
102     The width in pixels the emitShape is scaled to. If set to TrailEmitter.ParticleSize,
103     the width will be the current size of the particle being followed.
104
105     Default is 0.
106 */
107 /*!
108     \qmlproperty real QtQuick.Particles2::TrailEmitter::emitHeight
109
110     The height in pixels the emitShape is scaled to. If set to TrailEmitter.ParticleSize,
111     the height will be the current size of the particle being followed.
112
113     Default is 0.
114 */
115 /*!
116     \qmlproperty real QtQuick.Particles2::TrailEmitter::emitRatePerParticle
117 */
118 /*!
119     \qmlsignal QtQuick.Particles2::TrailEmitter::emitFollowParticles(Array particles, real followed)
120
121     This handler is called when particles are emitted from the \a followed particle. \a particles contains an array of particle objects which can be directly manipulated.
122
123     If you use this signal handler, emitParticles will not be emitted.
124
125 */
126
127 bool QQuickTrailEmitter::isEmitFollowConnected()
128 {
129     static int idx = QObjectPrivate::get(this)->signalIndex("emitFollowParticles(QDeclarativeV8Handle,QDeclarativeV8Handle)");
130     return QObjectPrivate::get(this)->isSignalConnected(idx);
131 }
132
133 void QQuickTrailEmitter::recalcParticlesPerSecond(){
134     if (!m_system)
135         return;
136     m_followCount = m_system->groupData[m_system->groupIds[m_follow]]->size();
137     if (!m_followCount){
138         setParticlesPerSecond(1);//XXX: Fix this horrendous hack, needed so they aren't turned off from start (causes crashes - test that when gone you don't crash with 0 PPPS)
139     }else{
140         setParticlesPerSecond(m_particlesPerParticlePerSecond * m_followCount);
141         m_lastEmission.resize(m_followCount);
142         m_lastEmission.fill(m_lastTimeStamp);
143     }
144 }
145
146 void QQuickTrailEmitter::reset()
147 {
148     m_followCount = 0;
149 }
150
151 void QQuickTrailEmitter::emitWindow(int timeStamp)
152 {
153     if (m_system == 0)
154         return;
155     if (!m_enabled && !m_pulseLeft && m_burstQueue.isEmpty())
156         return;
157     if (m_followCount != m_system->groupData[m_system->groupIds[m_follow]]->size()){
158         qreal oldPPS = m_particlesPerSecond;
159         recalcParticlesPerSecond();
160         if (m_particlesPerSecond != oldPPS)
161             return;//system may need to update
162     }
163
164     if (m_pulseLeft){
165         m_pulseLeft -= timeStamp - m_lastTimeStamp * 1000.;
166         if (m_pulseLeft < 0){
167             timeStamp += m_pulseLeft;
168             m_pulseLeft = 0;
169         }
170     }
171
172     //TODO: Implement startTime and speedFromMovement
173     qreal time = timeStamp / 1000.;
174     qreal particleRatio = 1. / m_particlesPerParticlePerSecond;
175     qreal pt;
176     qreal maxLife = (m_particleDuration + m_particleDurationVariation)/1000.0;
177
178     //Have to map it into this system, because particlesystem automaps it back
179     QPointF offset = m_system->mapFromItem(this, QPointF(0, 0));
180     qreal sizeAtEnd = m_particleEndSize >= 0 ? m_particleEndSize : m_particleSize;
181
182     int gId = m_system->groupIds[m_follow];
183     int gId2 = m_system->groupIds[m_group];
184     for (int i=0; i<m_system->groupData[gId]->data.count(); i++) {
185         QQuickParticleData *d = m_system->groupData[gId]->data[i];
186         if (!d->stillAlive()){
187             m_lastEmission[i] = time; //Should only start emitting when it returns to life
188             continue;
189         }
190         pt = m_lastEmission[i];
191         if (pt < d->t)
192             pt = d->t;
193         if (pt + maxLife < time)//We missed so much, that we should skip emiting particles that are dead by now
194             pt = time - maxLife;
195
196         if ((width() || height()) && !effectiveExtruder()->contains(QRectF(offset.x(), offset.y(), width(), height()),QPointF(d->curX(), d->curY()))){
197             m_lastEmission[d->index] = time;//jump over this time period without emitting, because it's outside
198             continue;
199         }
200
201         QList<QQuickParticleData*> toEmit;
202
203         while (pt < time || !m_burstQueue.isEmpty()){
204             QQuickParticleData* datum = m_system->newDatum(gId2, !m_overwrite);
205             if (datum){//else, skip this emission
206                 datum->e = this;//###useful?
207
208                 // Particle timestamp
209                 datum->t = pt;
210                 datum->lifeSpan =
211                         (m_particleDuration
212                          + ((rand() % ((m_particleDurationVariation*2) + 1)) - m_particleDurationVariation))
213                         / 1000.0;
214
215                 // Particle position
216                 // Note that burst location doesn't get used for follow emitter
217                 qreal followT =  pt - d->t;
218                 qreal followT2 = followT * followT * 0.5;
219                 qreal eW = m_emitterXVariation < 0 ? d->curSize() : m_emitterXVariation;
220                 qreal eH = m_emitterYVariation < 0 ? d->curSize() : m_emitterYVariation;
221                 //Subtract offset, because PS expects this in emitter coordinates
222                 QRectF boundsRect(d->x - offset.x() + d->vx * followT + d->ax * followT2 - eW/2,
223                                   d->y - offset.y() + d->vy * followT + d->ay * followT2 - eH/2,
224                                   eW, eH);
225
226                 QQuickParticleExtruder* effectiveEmissionExtruder = m_emissionExtruder ? m_emissionExtruder : m_defaultEmissionExtruder;
227                 const QPointF &newPos = effectiveEmissionExtruder->extrude(boundsRect);
228                 datum->x = newPos.x();
229                 datum->y = newPos.y();
230
231                 // Particle speed
232                 const QPointF &speed = m_speed->sample(newPos);
233                 datum->vx = speed.x()
234                     + m_speed_from_movement * d->vx;
235                 datum->vy = speed.y()
236                     + m_speed_from_movement * d->vy;
237
238                 // Particle acceleration
239                 const QPointF &accel = m_acceleration->sample(newPos);
240                 datum->ax = accel.x();
241                 datum->ay = accel.y();
242
243                 // Particle size
244                 float sizeVariation = -m_particleSizeVariation
245                         + rand() / float(RAND_MAX) * m_particleSizeVariation * 2;
246
247                 float size = qMax((qreal)0.0, m_particleSize + sizeVariation);
248                 float endSize = qMax((qreal)0.0, sizeAtEnd + sizeVariation);
249
250                 datum->size = size * float(m_enabled);
251                 datum->endSize = endSize * float(m_enabled);
252
253                 toEmit << datum;
254
255                 m_system->emitParticle(datum);
256             }
257             if (!m_burstQueue.isEmpty()){
258                 m_burstQueue.first().first--;
259                 if (m_burstQueue.first().first <= 0)
260                     m_burstQueue.pop_front();
261             }else{
262                 pt += particleRatio;
263             }
264         }
265
266         if (isEmitConnected() || isEmitFollowConnected()) {
267             v8::HandleScope handle_scope;
268             v8::Context::Scope scope(QDeclarativeEnginePrivate::getV8Engine(qmlEngine(this))->context());
269             v8::Handle<v8::Array> array = v8::Array::New(toEmit.size());
270             for (int i=0; i<toEmit.size(); i++)
271                 array->Set(i, toEmit[i]->v8Value().toHandle());
272
273             if (isEmitFollowConnected())
274                 emitFollowParticles(QDeclarativeV8Handle::fromHandle(array), d->v8Value());//A chance for many arbitrary JS changes
275             else if (isEmitConnected())
276                 emitParticles(QDeclarativeV8Handle::fromHandle(array));//A chance for arbitrary JS changes
277         }
278         foreach (QQuickParticleData* d, toEmit)
279             m_system->emitParticle(d);
280         m_lastEmission[d->index] = pt;
281     }
282
283     m_lastTimeStamp = time;
284 }
285 QT_END_NAMESPACE