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 QtQuick 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>
44 #include <private/qqmlglobal_p.h>
50 \instantiates QQuickTrailEmitter
51 \inqmlmodule QtQuick.Particles 2
52 \inherits QQuickParticleEmitter
53 \brief Emits logical particles from other logical particles
54 \ingroup qtquick-particles
56 This element emits logical particles into the ParticleSystem, with the
57 starting positions based on those of other logical particles.
59 QQuickTrailEmitter::QQuickTrailEmitter(QQuickItem *parent) :
60 QQuickParticleEmitter(parent)
61 , m_particlesPerParticlePerSecond(0)
63 , m_emitterXVariation(0)
64 , m_emitterYVariation(0)
66 , m_emissionExtruder(0)
67 , m_defaultEmissionExtruder(new QQuickParticleExtruder(this))
69 //TODO: If followed increased their size
70 connect(this, SIGNAL(followChanged(QString)),
71 this, SLOT(recalcParticlesPerSecond()));
72 connect(this, SIGNAL(particleDurationChanged(int)),
73 this, SLOT(recalcParticlesPerSecond()));
74 connect(this, SIGNAL(particlesPerParticlePerSecondChanged(int)),
75 this, SLOT(recalcParticlesPerSecond()));
79 \qmlproperty string QtQuick.Particles2::TrailEmitter::follow
81 The type of logical particle which this is emitting from.
84 \qmlproperty qreal QtQuick.Particles2::TrailEmitter::velocityFromMovement
86 If this value is non-zero, then any movement of the emitter will provide additional
87 starting velocity to the particles based on the movement. The additional vector will be the
88 same angle as the emitter's movement, with a magnitude that is the magnitude of the emitters
89 movement multiplied by velocityFromMovement.
94 \qmlproperty Shape QtQuick.Particles2::TrailEmitter::emitShape
96 As the area of a TrailEmitter is the area it follows, a separate shape can be provided
97 to be the shape it emits out of. This shape has width and height specified by emitWidth
98 and emitHeight, and is centered on the followed particle's position.
100 The default shape is a filled Rectangle.
103 \qmlproperty real QtQuick.Particles2::TrailEmitter::emitWidth
105 The width in pixels the emitShape is scaled to. If set to TrailEmitter.ParticleSize,
106 the width will be the current size of the particle being followed.
111 \qmlproperty real QtQuick.Particles2::TrailEmitter::emitHeight
113 The height in pixels the emitShape is scaled to. If set to TrailEmitter.ParticleSize,
114 the height will be the current size of the particle being followed.
119 \qmlproperty real QtQuick.Particles2::TrailEmitter::emitRatePerParticle
122 \qmlsignal QtQuick.Particles2::TrailEmitter::onEmitFollowParticles(Array particles, Particle followed)
124 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.
126 If you use this signal handler, emitParticles will not be emitted.
130 bool QQuickTrailEmitter::isEmitFollowConnected()
132 IS_SIGNAL_CONNECTED(this, QQuickTrailEmitter, emitFollowParticles, (QQmlV8Handle,QQmlV8Handle));
135 void QQuickTrailEmitter::recalcParticlesPerSecond(){
138 m_followCount = m_system->groupData[m_system->groupIds[m_follow]]->size();
140 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)
142 setParticlesPerSecond(m_particlesPerParticlePerSecond * m_followCount);
143 m_lastEmission.resize(m_followCount);
144 m_lastEmission.fill(m_lastTimeStamp);
148 void QQuickTrailEmitter::reset()
153 void QQuickTrailEmitter::emitWindow(int timeStamp)
157 if (!m_enabled && !m_pulseLeft && m_burstQueue.isEmpty())
159 if (m_followCount != m_system->groupData[m_system->groupIds[m_follow]]->size()){
160 qreal oldPPS = m_particlesPerSecond;
161 recalcParticlesPerSecond();
162 if (m_particlesPerSecond != oldPPS)
163 return;//system may need to update
167 m_pulseLeft -= timeStamp - m_lastTimeStamp * 1000.;
168 if (m_pulseLeft < 0){
169 timeStamp += m_pulseLeft;
174 //TODO: Implement startTime and velocityFromMovement
175 qreal time = timeStamp / 1000.;
176 qreal particleRatio = 1. / m_particlesPerParticlePerSecond;
178 qreal maxLife = (m_particleDuration + m_particleDurationVariation)/1000.0;
180 //Have to map it into this system, because particlesystem automaps it back
181 QPointF offset = m_system->mapFromItem(this, QPointF(0, 0));
182 qreal sizeAtEnd = m_particleEndSize >= 0 ? m_particleEndSize : m_particleSize;
184 int gId = m_system->groupIds[m_follow];
185 int gId2 = m_system->groupIds[m_group];
186 for (int i=0; i<m_system->groupData[gId]->data.count(); i++) {
187 QQuickParticleData *d = m_system->groupData[gId]->data[i];
188 if (!d->stillAlive()){
189 m_lastEmission[i] = time; //Should only start emitting when it returns to life
192 pt = m_lastEmission[i];
195 if (pt + maxLife < time)//We missed so much, that we should skip emiting particles that are dead by now
198 if ((width() || height()) && !effectiveExtruder()->contains(QRectF(offset.x(), offset.y(), width(), height()),QPointF(d->curX(), d->curY()))){
199 m_lastEmission[d->index] = time;//jump over this time period without emitting, because it's outside
203 QList<QQuickParticleData*> toEmit;
205 while (pt < time || !m_burstQueue.isEmpty()){
206 QQuickParticleData* datum = m_system->newDatum(gId2, !m_overwrite);
207 if (datum){//else, skip this emission
208 datum->e = this;//###useful?
210 // Particle timestamp
214 + ((rand() % ((m_particleDurationVariation*2) + 1)) - m_particleDurationVariation))
218 // Note that burst location doesn't get used for follow emitter
219 qreal followT = pt - d->t;
220 qreal followT2 = followT * followT * 0.5;
221 qreal eW = m_emitterXVariation < 0 ? d->curSize() : m_emitterXVariation;
222 qreal eH = m_emitterYVariation < 0 ? d->curSize() : m_emitterYVariation;
223 //Subtract offset, because PS expects this in emitter coordinates
224 QRectF boundsRect(d->x - offset.x() + d->vx * followT + d->ax * followT2 - eW/2,
225 d->y - offset.y() + d->vy * followT + d->ay * followT2 - eH/2,
228 QQuickParticleExtruder* effectiveEmissionExtruder = m_emissionExtruder ? m_emissionExtruder : m_defaultEmissionExtruder;
229 const QPointF &newPos = effectiveEmissionExtruder->extrude(boundsRect);
230 datum->x = newPos.x();
231 datum->y = newPos.y();
234 const QPointF &velocity = m_velocity->sample(newPos);
235 datum->vx = velocity.x()
236 + m_velocity_from_movement * d->vx;
237 datum->vy = velocity.y()
238 + m_velocity_from_movement * d->vy;
240 // Particle acceleration
241 const QPointF &accel = m_acceleration->sample(newPos);
242 datum->ax = accel.x();
243 datum->ay = accel.y();
246 float sizeVariation = -m_particleSizeVariation
247 + rand() / float(RAND_MAX) * m_particleSizeVariation * 2;
249 float size = qMax((qreal)0.0, m_particleSize + sizeVariation);
250 float endSize = qMax((qreal)0.0, sizeAtEnd + sizeVariation);
252 datum->size = size * float(m_enabled);
253 datum->endSize = endSize * float(m_enabled);
257 m_system->emitParticle(datum);
259 if (!m_burstQueue.isEmpty()){
260 m_burstQueue.first().first--;
261 if (m_burstQueue.first().first <= 0)
262 m_burstQueue.pop_front();
268 foreach (QQuickParticleData* d, toEmit)
269 m_system->emitParticle(d);
271 if (isEmitConnected() || isEmitFollowConnected()) {
272 v8::HandleScope handle_scope;
273 v8::Context::Scope scope(QQmlEnginePrivate::getV8Engine(qmlEngine(this))->context());
274 v8::Handle<v8::Array> array = v8::Array::New(toEmit.size());
275 for (int i=0; i<toEmit.size(); i++)
276 array->Set(i, toEmit[i]->v8Value().toHandle());
278 if (isEmitFollowConnected())
279 emitFollowParticles(QQmlV8Handle::fromHandle(array), d->v8Value());//A chance for many arbitrary JS changes
280 else if (isEmitConnected())
281 emitParticles(QQmlV8Handle::fromHandle(array));//A chance for arbitrary JS changes
283 m_lastEmission[d->index] = pt;
286 m_lastTimeStamp = time;