1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses |
5 | |
6 | #include "qquicktrailemitter_p.h" |
7 | |
8 | #include <private/qqmlglobal_p.h> |
9 | #include <private/qquickv4particledata_p.h> |
10 | |
11 | #include <QtCore/qrandom.h> |
12 | |
13 | #include <cmath> |
14 | |
15 | QT_BEGIN_NAMESPACE |
16 | |
17 | /*! |
18 | \qmltype TrailEmitter |
19 | \nativetype QQuickTrailEmitter |
20 | \inqmlmodule QtQuick.Particles |
21 | \inherits QQuickParticleEmitter |
22 | \brief Emits logical particles from other logical particles. |
23 | \ingroup qtquick-particles |
24 | |
25 | This element emits logical particles into the ParticleSystem, with the |
26 | starting positions based on those of other logical particles. |
27 | */ |
28 | QQuickTrailEmitter::QQuickTrailEmitter(QQuickItem *parent) : |
29 | QQuickParticleEmitter(parent) |
30 | , m_particlesPerParticlePerSecond(0) |
31 | , m_lastTimeStamp(0) |
32 | , m_emitterXVariation(0) |
33 | , m_emitterYVariation(0) |
34 | , m_followCount(0) |
35 | , m_emissionExtruder(nullptr) |
36 | , m_defaultEmissionExtruder(new QQuickParticleExtruder(this)) |
37 | { |
38 | //TODO: If followed increased their size |
39 | connect(sender: this, signal: &QQuickTrailEmitter::followChanged, |
40 | context: this, slot: &QQuickTrailEmitter::recalcParticlesPerSecond); |
41 | connect(sender: this, signal: &QQuickTrailEmitter::particleDurationChanged, |
42 | context: this, slot: &QQuickTrailEmitter::recalcParticlesPerSecond); |
43 | connect(sender: this, signal: &QQuickTrailEmitter::particlesPerParticlePerSecondChanged, |
44 | context: this, slot: &QQuickTrailEmitter::recalcParticlesPerSecond); |
45 | } |
46 | |
47 | /*! |
48 | \qmlproperty string QtQuick.Particles::TrailEmitter::follow |
49 | |
50 | The type of logical particle which this is emitting from. |
51 | */ |
52 | /*! |
53 | \qmlproperty qreal QtQuick.Particles::TrailEmitter::velocityFromMovement |
54 | |
55 | If this value is non-zero, then any movement of the emitter will provide additional |
56 | starting velocity to the particles based on the movement. The additional vector will be the |
57 | same angle as the emitter's movement, with a magnitude that is the magnitude of the emitters |
58 | movement multiplied by velocityFromMovement. |
59 | |
60 | Default value is 0. |
61 | */ |
62 | /*! |
63 | \qmlproperty Shape QtQuick.Particles::TrailEmitter::emitShape |
64 | |
65 | As the area of a TrailEmitter is the area it follows, a separate shape can be provided |
66 | to be the shape it emits out of. This shape has width and height specified by emitWidth |
67 | and emitHeight, and is centered on the followed particle's position. |
68 | |
69 | The default shape is a filled Rectangle. |
70 | */ |
71 | /*! |
72 | \qmlproperty real QtQuick.Particles::TrailEmitter::emitWidth |
73 | |
74 | The width in pixels the emitShape is scaled to. If set to TrailEmitter.ParticleSize, |
75 | the width will be the current size of the particle being followed. |
76 | |
77 | Default is 0. |
78 | */ |
79 | /*! |
80 | \qmlproperty real QtQuick.Particles::TrailEmitter::emitHeight |
81 | |
82 | The height in pixels the emitShape is scaled to. If set to TrailEmitter.ParticleSize, |
83 | the height will be the current size of the particle being followed. |
84 | |
85 | Default is 0. |
86 | */ |
87 | /*! |
88 | \qmlproperty real QtQuick.Particles::TrailEmitter::emitRatePerParticle |
89 | */ |
90 | /*! |
91 | \qmlsignal QtQuick.Particles::TrailEmitter::emitFollowParticles(Array particles, Particle followed) |
92 | |
93 | This signal is emitted when particles are emitted from the \a followed particle. \a particles contains an array of particle objects which can be directly manipulated. |
94 | |
95 | If you use this signal handler, emitParticles will not be emitted. |
96 | */ |
97 | |
98 | bool QQuickTrailEmitter::isEmitFollowConnected() |
99 | { |
100 | IS_SIGNAL_CONNECTED( |
101 | this, QQuickTrailEmitter, emitFollowParticles, |
102 | (const QList<QQuickV4ParticleData> &, const QQuickV4ParticleData &)); |
103 | } |
104 | |
105 | void QQuickTrailEmitter::recalcParticlesPerSecond(){ |
106 | if (!m_system) |
107 | return; |
108 | m_followCount = m_system->groupData[m_system->groupIds[m_follow]]->size(); |
109 | if (!m_followCount){ |
110 | 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) |
111 | }else{ |
112 | setParticlesPerSecond(m_particlesPerParticlePerSecond * m_followCount); |
113 | m_lastEmission.resize(size: m_followCount); |
114 | m_lastEmission.fill(t: m_lastTimeStamp); |
115 | } |
116 | } |
117 | |
118 | void QQuickTrailEmitter::reset() |
119 | { |
120 | m_followCount = 0; |
121 | } |
122 | |
123 | void QQuickTrailEmitter::emitWindow(int timeStamp) |
124 | { |
125 | if (m_system == nullptr) |
126 | return; |
127 | if (!m_enabled && !m_pulseLeft && m_burstQueue.isEmpty()) |
128 | return; |
129 | if (m_followCount != m_system->groupData[m_system->groupIds[m_follow]]->size()){ |
130 | qreal oldPPS = m_particlesPerSecond; |
131 | recalcParticlesPerSecond(); |
132 | if (m_particlesPerSecond != oldPPS) |
133 | return;//system may need to update |
134 | } |
135 | |
136 | if (m_pulseLeft){ |
137 | m_pulseLeft -= timeStamp - m_lastTimeStamp * 1000.; |
138 | if (m_pulseLeft < 0){ |
139 | timeStamp += m_pulseLeft; |
140 | m_pulseLeft = 0; |
141 | } |
142 | } |
143 | |
144 | //TODO: Implement startTime and velocityFromMovement |
145 | qreal time = timeStamp / 1000.; |
146 | qreal particleRatio = 1. / m_particlesPerParticlePerSecond; |
147 | qreal pt; |
148 | qreal maxLife = (m_particleDuration + m_particleDurationVariation)/1000.0; |
149 | |
150 | //Have to map it into this system, because particlesystem automaps it back |
151 | QPointF offset = m_system->mapFromItem(item: this, point: QPointF(0, 0)); |
152 | qreal sizeAtEnd = m_particleEndSize >= 0 ? m_particleEndSize : m_particleSize; |
153 | |
154 | int gId = m_system->groupIds[m_follow]; |
155 | int gId2 = groupId(); |
156 | for (int i=0; i<m_system->groupData[gId]->data.size(); i++) { |
157 | QQuickParticleData *d = m_system->groupData[gId]->data[i]; |
158 | if (!d->stillAlive(system: m_system)){ |
159 | m_lastEmission[i] = time; //Should only start emitting when it returns to life |
160 | continue; |
161 | } |
162 | pt = m_lastEmission[i]; |
163 | if (pt < d->t) |
164 | pt = d->t; |
165 | if (pt + maxLife < time)//We missed so much, that we should skip emiting particles that are dead by now |
166 | pt = time - maxLife; |
167 | |
168 | if ((width() || height()) && !effectiveExtruder()->contains(bounds: QRectF(offset.x(), offset.y(), width(), height()), |
169 | point: QPointF(d->curX(particleSystem: m_system), d->curY(particleSystem: m_system)))) { |
170 | m_lastEmission[d->index] = time;//jump over this time period without emitting, because it's outside |
171 | continue; |
172 | } |
173 | |
174 | QList<QQuickParticleData*> toEmit; |
175 | |
176 | while (pt < time || !m_burstQueue.isEmpty()){ |
177 | QQuickParticleData* datum = m_system->newDatum(groupId: gId2, respectLimits: !m_overwrite); |
178 | if (datum){//else, skip this emission |
179 | // Particle timestamp |
180 | datum->t = pt; |
181 | datum->lifeSpan = |
182 | (m_particleDuration |
183 | + (QRandomGenerator::global()->bounded(highest: (m_particleDurationVariation*2) + 1) - m_particleDurationVariation)) |
184 | / 1000.0; |
185 | |
186 | // Particle position |
187 | // Note that burst location doesn't get used for follow emitter |
188 | qreal followT = pt - d->t; |
189 | qreal followT2 = followT * followT * 0.5; |
190 | qreal eW = m_emitterXVariation < 0 ? d->curSize(particleSystem: m_system) : m_emitterXVariation; |
191 | qreal eH = m_emitterYVariation < 0 ? d->curSize(particleSystem: m_system) : m_emitterYVariation; |
192 | //Subtract offset, because PS expects this in emitter coordinates |
193 | QRectF boundsRect(d->x - offset.x() + d->vx * followT + d->ax * followT2 - eW/2, |
194 | d->y - offset.y() + d->vy * followT + d->ay * followT2 - eH/2, |
195 | eW, eH); |
196 | |
197 | QQuickParticleExtruder* effectiveEmissionExtruder = m_emissionExtruder ? m_emissionExtruder : m_defaultEmissionExtruder; |
198 | const QPointF &newPos = effectiveEmissionExtruder->extrude(boundsRect); |
199 | datum->x = newPos.x(); |
200 | datum->y = newPos.y(); |
201 | |
202 | // Particle velocity |
203 | const QPointF &velocity = m_velocity->sample(from: newPos); |
204 | datum->vx = velocity.x() |
205 | + m_velocity_from_movement * d->vx; |
206 | datum->vy = velocity.y() |
207 | + m_velocity_from_movement * d->vy; |
208 | |
209 | // Particle acceleration |
210 | const QPointF &accel = m_acceleration->sample(from: newPos); |
211 | datum->ax = accel.x(); |
212 | datum->ay = accel.y(); |
213 | |
214 | // Particle size |
215 | float sizeVariation = -m_particleSizeVariation |
216 | + QRandomGenerator::global()->generateDouble() * m_particleSizeVariation * 2; |
217 | |
218 | float size = qMax(a: (qreal)0.0, b: m_particleSize + sizeVariation); |
219 | float endSize = qMax(a: (qreal)0.0, b: sizeAtEnd + sizeVariation); |
220 | |
221 | datum->size = size * float(m_enabled); |
222 | datum->endSize = endSize * float(m_enabled); |
223 | |
224 | toEmit << datum; |
225 | |
226 | m_system->emitParticle(p: datum, particleEmitter: this); |
227 | } |
228 | if (!m_burstQueue.isEmpty()){ |
229 | m_burstQueue.first().first--; |
230 | if (m_burstQueue.first().first <= 0) |
231 | m_burstQueue.pop_front(); |
232 | }else{ |
233 | pt += particleRatio; |
234 | } |
235 | } |
236 | |
237 | foreach (QQuickParticleData* d, toEmit) |
238 | m_system->emitParticle(p: d, particleEmitter: this); |
239 | |
240 | if (isEmitConnected() || isEmitFollowConnected()) { |
241 | |
242 | QList<QQuickV4ParticleData> particles; |
243 | particles.reserve(asize: toEmit.size()); |
244 | for (QQuickParticleData *particle : std::as_const(t&: toEmit)) |
245 | particles.push_back(t: particle->v4Value(particleSystem: m_system)); |
246 | |
247 | if (isEmitFollowConnected()) { |
248 | //A chance for many arbitrary JS changes |
249 | emit emitFollowParticles(particles, followed: d->v4Value(particleSystem: m_system)); |
250 | } else if (isEmitConnected()) { |
251 | emit emitParticles(particles);//A chance for arbitrary JS changes |
252 | } |
253 | } |
254 | m_lastEmission[d->index] = pt; |
255 | } |
256 | |
257 | m_lastTimeStamp = time; |
258 | } |
259 | QT_END_NAMESPACE |
260 | |
261 | #include "moc_qquicktrailemitter_p.cpp" |
262 | |