1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the Declarative module of the Qt Toolkit.
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.
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.
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.
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.
40 ****************************************************************************/
42 #include "qquicktrailemitter_p.h"
43 #include <private/qqmlengine_p.h>
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.
53 This element emits logical particles into the ParticleSystem, with the
54 starting positions based on those of other logical particles.
56 QQuickTrailEmitter::QQuickTrailEmitter(QQuickItem *parent) :
57 QQuickParticleEmitter(parent)
58 , m_particlesPerParticlePerSecond(0)
60 , m_emitterXVariation(0)
61 , m_emitterYVariation(0)
63 , m_emissionExtruder(0)
64 , m_defaultEmissionExtruder(new QQuickParticleExtruder(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 QQuickTrailEmitter::isEmitFollowConnected()
129 static int idx = QObjectPrivate::get(this)->signalIndex("emitFollowParticles(QQmlV8Handle,QQmlV8Handle)");
130 return QObjectPrivate::get(this)->isSignalConnected(idx);
133 void QQuickTrailEmitter::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 QQuickTrailEmitter::reset()
151 void QQuickTrailEmitter::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 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
190 pt = m_lastEmission[i];
193 if (pt + maxLife < time)//We missed so much, that we should skip emiting particles that are dead by now
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
201 QList<QQuickParticleData*> toEmit;
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?
208 // Particle timestamp
212 + ((rand() % ((m_particleDurationVariation*2) + 1)) - m_particleDurationVariation))
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,
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();
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;
238 // Particle acceleration
239 const QPointF &accel = m_acceleration->sample(newPos);
240 datum->ax = accel.x();
241 datum->ay = accel.y();
244 float sizeVariation = -m_particleSizeVariation
245 + rand() / float(RAND_MAX) * m_particleSizeVariation * 2;
247 float size = qMax((qreal)0.0, m_particleSize + sizeVariation);
248 float endSize = qMax((qreal)0.0, sizeAtEnd + sizeVariation);
250 datum->size = size * float(m_enabled);
251 datum->endSize = endSize * float(m_enabled);
255 m_system->emitParticle(datum);
257 if (!m_burstQueue.isEmpty()){
258 m_burstQueue.first().first--;
259 if (m_burstQueue.first().first <= 0)
260 m_burstQueue.pop_front();
266 if (isEmitConnected() || isEmitFollowConnected()) {
267 v8::HandleScope handle_scope;
268 v8::Context::Scope scope(QQmlEnginePrivate::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());
273 if (isEmitFollowConnected())
274 emitFollowParticles(QQmlV8Handle::fromHandle(array), d->v8Value());//A chance for many arbitrary JS changes
275 else if (isEmitConnected())
276 emitParticles(QQmlV8Handle::fromHandle(array));//A chance for arbitrary JS changes
278 foreach (QQuickParticleData* d, toEmit)
279 m_system->emitParticle(d);
280 m_lastEmission[d->index] = pt;
283 m_lastTimeStamp = time;