1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the Declarative module of the Qt Toolkit.
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.
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.
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.
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.
40 ****************************************************************************/
42 #include "qsgtrailemitter_p.h"
43 #include <private/qdeclarativeengine_p.h>
48 \qmlclass TrailEmitter QSGTrailEmitter
49 \inqmlmodule QtQuick.Particles 2
50 \inherits QSGParticleEmitter
51 \brief The TrailEmitter element allows you to emit logical particles from other logical particles.
53 This element emits logical particles into the ParticleSystem, with the
54 starting positions based on those of other logical particles.
56 QSGTrailEmitter::QSGTrailEmitter(QQuickItem *parent) :
57 QSGParticleEmitter(parent)
58 , m_particlesPerParticlePerSecond(0)
60 , m_emitterXVariation(0)
61 , m_emitterYVariation(0)
63 , m_emissionExtruder(0)
64 , m_defaultEmissionExtruder(new QSGParticleExtruder(this))
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()));
76 \qmlproperty string QtQuick.Particles2::TrailEmitter::follow
78 The type of logical particle which this is emitting from.
81 \qmlproperty qreal QtQuick.Particles2::TrailEmitter::speedFromMovement
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.
91 \qmlproperty Shape QtQuick.Particles2::TrailEmitter::emitShape
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.
97 The default shape is a filled Rectangle.
100 \qmlproperty real QtQuick.Particles2::TrailEmitter::emitWidth
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.
108 \qmlproperty real QtQuick.Particles2::TrailEmitter::emitHeight
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.
116 \qmlproperty real QtQuick.Particles2::TrailEmitter::emitRatePerParticle
119 \qmlsignal QtQuick.Particles2::TrailEmitter::emitFollowParticles(Array particles, real followed)
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.
123 If you use this signal handler, emitParticles will not be emitted.
127 bool QSGTrailEmitter::isEmitFollowConnected()
129 static int idx = QObjectPrivate::get(this)->signalIndex("emitFollowParticles(QDeclarativeV8Handle,QDeclarativeV8Handle)");
130 return QObjectPrivate::get(this)->isSignalConnected(idx);
133 void QSGTrailEmitter::recalcParticlesPerSecond(){
136 m_followCount = m_system->groupData[m_system->groupIds[m_follow]]->size();
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)
140 setParticlesPerSecond(m_particlesPerParticlePerSecond * m_followCount);
141 m_lastEmission.resize(m_followCount);
142 m_lastEmission.fill(m_lastTimeStamp);
146 void QSGTrailEmitter::reset()
151 void QSGTrailEmitter::emitWindow(int timeStamp)
155 if (!m_enabled && !m_pulseLeft && m_burstQueue.isEmpty())
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
165 m_pulseLeft -= timeStamp - m_lastTimeStamp * 1000.;
166 if (m_pulseLeft < 0){
167 timeStamp += m_pulseLeft;
172 //TODO: Implement startTime and speedFromMovement
173 qreal time = timeStamp / 1000.;
174 qreal particleRatio = 1. / m_particlesPerParticlePerSecond;
176 qreal maxLife = (m_particleDuration + m_particleDurationVariation)/1000.0;
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;
182 int gId = m_system->groupIds[m_follow];
183 int gId2 = m_system->groupIds[m_group];
184 foreach (QSGParticleData *d, m_system->groupData[gId]->data){
185 if (!d || !d->stillAlive()){
186 m_lastEmission[d->index] = time; //Should only start emitting when it returns to life
189 pt = m_lastEmission[d->index];
192 if (pt + maxLife < time)//We missed so much, that we should skip emiting particles that are dead by now
195 if ((width() || height()) && !effectiveExtruder()->contains(QRectF(offset.x(), offset.y(), width(), height()),QPointF(d->curX(), d->curY()))){
196 m_lastEmission[d->index] = time;//jump over this time period without emitting, because it's outside
200 QList<QSGParticleData*> toEmit;
202 while (pt < time || !m_burstQueue.isEmpty()){
203 QSGParticleData* datum = m_system->newDatum(gId2, !m_overwrite);
204 if (datum){//else, skip this emission
205 datum->e = this;//###useful?
207 // Particle timestamp
211 + ((rand() % ((m_particleDurationVariation*2) + 1)) - m_particleDurationVariation))
215 // Note that burst location doesn't get used for follow emitter
216 qreal followT = pt - d->t;
217 qreal followT2 = followT * followT * 0.5;
218 qreal eW = m_emitterXVariation < 0 ? d->curSize() : m_emitterXVariation;
219 qreal eH = m_emitterYVariation < 0 ? d->curSize() : m_emitterYVariation;
220 //Subtract offset, because PS expects this in emitter coordinates
221 QRectF boundsRect(d->x - offset.x() + d->vx * followT + d->ax * followT2 - eW/2,
222 d->y - offset.y() + d->vy * followT + d->ay * followT2 - eH/2,
225 QSGParticleExtruder* effectiveEmissionExtruder = m_emissionExtruder ? m_emissionExtruder : m_defaultEmissionExtruder;
226 const QPointF &newPos = effectiveEmissionExtruder->extrude(boundsRect);
227 datum->x = newPos.x();
228 datum->y = newPos.y();
231 const QPointF &speed = m_speed->sample(newPos);
232 datum->vx = speed.x()
233 + m_speed_from_movement * d->vx;
234 datum->vy = speed.y()
235 + m_speed_from_movement * d->vy;
237 // Particle acceleration
238 const QPointF &accel = m_acceleration->sample(newPos);
239 datum->ax = accel.x();
240 datum->ay = accel.y();
243 float sizeVariation = -m_particleSizeVariation
244 + rand() / float(RAND_MAX) * m_particleSizeVariation * 2;
246 float size = qMax((qreal)0.0, m_particleSize + sizeVariation);
247 float endSize = qMax((qreal)0.0, sizeAtEnd + sizeVariation);
249 datum->size = size * float(m_enabled);
250 datum->endSize = endSize * float(m_enabled);
254 m_system->emitParticle(datum);
256 if (!m_burstQueue.isEmpty()){
257 m_burstQueue.first().first--;
258 if (m_burstQueue.first().first <= 0)
259 m_burstQueue.pop_front();
265 if (isEmitConnected() || isEmitFollowConnected()) {
266 v8::HandleScope handle_scope;
267 v8::Context::Scope scope(QDeclarativeEnginePrivate::getV8Engine(qmlEngine(this))->context());
268 v8::Handle<v8::Array> array = v8::Array::New(toEmit.size());
269 for (int i=0; i<toEmit.size(); i++)
270 array->Set(i, toEmit[i]->v8Value().toHandle());
272 if (isEmitFollowConnected())
273 emitFollowParticles(QDeclarativeV8Handle::fromHandle(array), d->v8Value());//A chance for many arbitrary JS changes
274 else if (isEmitConnected())
275 emitParticles(QDeclarativeV8Handle::fromHandle(array));//A chance for arbitrary JS changes
277 foreach (QSGParticleData* d, toEmit)
278 m_system->emitParticle(d);
279 m_lastEmission[d->index] = pt;
282 m_lastTimeStamp = time;