1 | // Copyright (C) 2021 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include "qquick3dparticleemitter_p.h" |
5 | #include "qquick3dparticlemodelparticle_p.h" |
6 | #include "qquick3dparticlerandomizer_p.h" |
7 | #include "qquick3dparticleutils_p.h" |
8 | #include "qquick3dparticlespritesequence_p.h" |
9 | #include "qquick3dparticlemodelblendparticle_p.h" |
10 | |
11 | QT_BEGIN_NAMESPACE |
12 | |
13 | /*! |
14 | \qmltype ParticleEmitter3D |
15 | \inherits Node |
16 | \inqmlmodule QtQuick3D.Particles3D |
17 | \brief Emitter for logical particles. |
18 | \since 6.2 |
19 | |
20 | This element emits logical particles into the \l ParticleSystem3D, with the given starting attributes. |
21 | |
22 | At least one emitter is required to have particles in the \l ParticleSystem3D. There are a few different |
23 | ways to control the emitting amount: |
24 | \list |
25 | \li Set the \l emitRate which controls how many particles per second get emitted continuously. |
26 | \li Add \l EmitBurst3D elements into emitBursts property to emit bursts declaratively. |
27 | \li Call any of the \l burst() methods to emit bursts immediately. |
28 | \endlist |
29 | */ |
30 | |
31 | QQuick3DParticleEmitter::QQuick3DParticleEmitter(QQuick3DNode *parent) |
32 | : QQuick3DNode(parent) |
33 | { |
34 | } |
35 | |
36 | QQuick3DParticleEmitter::~QQuick3DParticleEmitter() |
37 | { |
38 | qDeleteAll(c: m_emitBursts); |
39 | m_emitBursts.clear(); |
40 | if (m_system) |
41 | m_system->unRegisterParticleEmitter(e: this); |
42 | } |
43 | |
44 | /*! |
45 | \qmlproperty bool ParticleEmitter3D::enabled |
46 | |
47 | If enabled is set to \c false, this emitter will not emit any particles. |
48 | Usually this is used to conditionally turn an emitter on or off. |
49 | If you want to continue emitting burst, keep \l emitRate at 0 instead of |
50 | toggling this to \c false. |
51 | |
52 | The default value is \c true. |
53 | */ |
54 | bool QQuick3DParticleEmitter::enabled() const |
55 | { |
56 | return m_enabled; |
57 | } |
58 | void QQuick3DParticleEmitter::setEnabled(bool enabled) |
59 | { |
60 | if (m_enabled == enabled) |
61 | return; |
62 | |
63 | if (enabled && m_system) { |
64 | // When enabling, we need to reset the |
65 | // previous emit time as it might be a long time ago. |
66 | m_prevEmitTime = m_system->currentTime(); |
67 | m_prevBurstTime = m_prevEmitTime; |
68 | } |
69 | |
70 | m_enabled = enabled; |
71 | Q_EMIT enabledChanged(); |
72 | } |
73 | |
74 | /*! |
75 | \qmlproperty Direction3D ParticleEmitter3D::velocity |
76 | |
77 | This property can be used to set a starting velocity for emitted particles. |
78 | If velocity is not set, particles start motionless and velocity comes from |
79 | \l {Affector3D}{affectors} if they are used. |
80 | */ |
81 | QQuick3DParticleDirection *QQuick3DParticleEmitter::velocity() const |
82 | { |
83 | return m_velocity; |
84 | } |
85 | |
86 | void QQuick3DParticleEmitter::setVelocity(QQuick3DParticleDirection *velocity) |
87 | { |
88 | if (m_velocity == velocity) |
89 | return; |
90 | |
91 | m_velocity = velocity; |
92 | if (m_velocity && m_system) |
93 | m_velocity->m_system = m_system; |
94 | |
95 | Q_EMIT velocityChanged(); |
96 | } |
97 | |
98 | /*! |
99 | \qmlproperty ParticleSystem3D ParticleEmitter3D::system |
100 | |
101 | This property defines the \l ParticleSystem3D for the emitter. If system is direct parent of the emitter, |
102 | this property does not need to be defined. |
103 | */ |
104 | QQuick3DParticleSystem *QQuick3DParticleEmitter::system() const |
105 | { |
106 | return m_system; |
107 | } |
108 | |
109 | void QQuick3DParticleEmitter::setSystem(QQuick3DParticleSystem *system) |
110 | { |
111 | if (m_system == system) |
112 | return; |
113 | |
114 | if (m_system) |
115 | m_system->unRegisterParticleEmitter(e: this); |
116 | |
117 | m_system = system; |
118 | if (m_system) { |
119 | m_system->registerParticleEmitter(e: this); |
120 | // Reset prev emit time to time of the new system |
121 | m_prevEmitTime = m_system->currentTime(); |
122 | m_prevBurstTime = m_prevEmitTime; |
123 | } |
124 | |
125 | if (m_particle) |
126 | m_particle->setSystem(m_system); |
127 | |
128 | if (m_shape) |
129 | m_shape->m_system = m_system; |
130 | |
131 | if (m_velocity) |
132 | m_velocity->m_system = m_system; |
133 | |
134 | m_systemSharedParent = getSharedParentNode(node: this, system: m_system); |
135 | |
136 | Q_EMIT systemChanged(); |
137 | } |
138 | |
139 | /*! |
140 | \qmlproperty real ParticleEmitter3D::emitRate |
141 | |
142 | This property defines the constant emitting rate in particles per second. |
143 | For example, if the emitRate is 120 and system animates at 60 frames per |
144 | second, 2 new particles are emitted at every frame. |
145 | |
146 | The default value is \c 0. |
147 | */ |
148 | float QQuick3DParticleEmitter::emitRate() const |
149 | { |
150 | return m_emitRate; |
151 | } |
152 | |
153 | void QQuick3DParticleEmitter::setEmitRate(float emitRate) |
154 | { |
155 | if (qFuzzyCompare(p1: m_emitRate, p2: emitRate)) |
156 | return; |
157 | |
158 | if (m_emitRate == 0 && m_system) { |
159 | // When changing emit rate from 0 we need to reset |
160 | // previous emit time as it may be long time ago |
161 | m_prevEmitTime = m_system->currentTime(); |
162 | } |
163 | m_emitRate = emitRate; |
164 | Q_EMIT emitRateChanged(); |
165 | } |
166 | |
167 | |
168 | /*! |
169 | \qmlproperty real ParticleEmitter3D::particleScale |
170 | |
171 | This property defines the scale multiplier of the particles at the beginning. |
172 | To have variation in the particle sizes, use \l particleScaleVariation. |
173 | |
174 | The default value is \c 1.0. |
175 | |
176 | \sa particleEndScale, particleScaleVariation |
177 | */ |
178 | float QQuick3DParticleEmitter::particleScale() const |
179 | { |
180 | return m_particleScale; |
181 | } |
182 | |
183 | void QQuick3DParticleEmitter::setParticleScale(float particleScale) |
184 | { |
185 | if (qFuzzyCompare(p1: m_particleScale, p2: particleScale)) |
186 | return; |
187 | |
188 | m_particleScale = particleScale; |
189 | Q_EMIT particleScaleChanged(); |
190 | } |
191 | |
192 | /*! |
193 | \qmlproperty real ParticleEmitter3D::particleEndScale |
194 | |
195 | This property defines the scale multiplier of the particles at the end |
196 | of particle \l lifeSpan. To have variation in the particle end sizes, use |
197 | \l particleEndScaleVariation. When the value is negative, end scale is the |
198 | same as the \l particleScale, so scale doesn't change during the particle |
199 | \l lifeSpan. |
200 | |
201 | The default value is \c -1.0. |
202 | |
203 | \sa particleScale, particleScaleVariation |
204 | */ |
205 | float QQuick3DParticleEmitter::particleEndScale() const |
206 | { |
207 | return m_particleEndScale; |
208 | } |
209 | |
210 | void QQuick3DParticleEmitter::setParticleEndScale(float particleEndScale) |
211 | { |
212 | if (qFuzzyCompare(p1: m_particleEndScale, p2: particleEndScale)) |
213 | return; |
214 | |
215 | m_particleEndScale = particleEndScale; |
216 | Q_EMIT particleEndScaleChanged(); |
217 | } |
218 | |
219 | /*! |
220 | \qmlproperty real ParticleEmitter3D::particleScaleVariation |
221 | |
222 | This property defines the scale variation of the particles. For example, to |
223 | emit particles at scale 0.5 - 1.5: |
224 | |
225 | \qml |
226 | ParticleEmitter3D { |
227 | ... |
228 | particleScale: 1.0 |
229 | particleScaleVariation: 0.5 |
230 | } |
231 | \endqml |
232 | |
233 | The default value is \c 0.0. |
234 | |
235 | \sa particleScale, particleEndScaleVariation |
236 | */ |
237 | float QQuick3DParticleEmitter::particleScaleVariation() const |
238 | { |
239 | return m_particleScaleVariation; |
240 | } |
241 | |
242 | void QQuick3DParticleEmitter::setParticleScaleVariation(float particleScaleVariation) |
243 | { |
244 | if (qFuzzyCompare(p1: m_particleScaleVariation, p2: particleScaleVariation)) |
245 | return; |
246 | |
247 | m_particleScaleVariation = particleScaleVariation; |
248 | Q_EMIT particleScaleVariationChanged(); |
249 | } |
250 | |
251 | /*! |
252 | \qmlproperty real ParticleEmitter3D::particleEndScaleVariation |
253 | |
254 | This property defines the scale variation of the particles in the end. |
255 | When the value is negative, \l particleScaleVariation is used also for the |
256 | end scale. For example, to emit particles which start at scale 0.5 - 1.5 and end |
257 | at scale 1.0 - 5.0: |
258 | |
259 | \qml |
260 | ParticleEmitter3D { |
261 | ... |
262 | particleScale: 1.0 |
263 | particleScaleVariation: 0.5 |
264 | particleEndScale: 3.0 |
265 | particleEndScaleVariation: 2.0 |
266 | } |
267 | \endqml |
268 | |
269 | The default value is \c -1.0. |
270 | |
271 | \sa particleEndScale |
272 | */ |
273 | float QQuick3DParticleEmitter::particleEndScaleVariation() const |
274 | { |
275 | return m_particleEndScaleVariation; |
276 | } |
277 | |
278 | void QQuick3DParticleEmitter::setParticleEndScaleVariation(float particleEndScaleVariation) |
279 | { |
280 | if (qFuzzyCompare(p1: m_particleEndScaleVariation, p2: particleEndScaleVariation)) |
281 | return; |
282 | |
283 | m_particleEndScaleVariation = particleEndScaleVariation; |
284 | Q_EMIT particleEndScaleVariationChanged(); |
285 | } |
286 | |
287 | /*! |
288 | \qmlproperty int ParticleEmitter3D::lifeSpan |
289 | |
290 | This property defines the lifespan of a single particle in milliseconds. |
291 | |
292 | The default value is \c 1000. |
293 | |
294 | \sa lifeSpanVariation |
295 | */ |
296 | int QQuick3DParticleEmitter::lifeSpan() const |
297 | { |
298 | return m_lifeSpan; |
299 | } |
300 | |
301 | void QQuick3DParticleEmitter::setLifeSpan(int lifeSpan) |
302 | { |
303 | if (m_lifeSpan == lifeSpan) |
304 | return; |
305 | |
306 | m_lifeSpan = lifeSpan; |
307 | Q_EMIT lifeSpanChanged(); |
308 | } |
309 | |
310 | /*! |
311 | \qmlproperty int ParticleEmitter3D::lifeSpanVariation |
312 | |
313 | This property defines the lifespan variation of a single particle in milliseconds. |
314 | |
315 | For example, to emit particles which will exist between 3 and 4 seconds: |
316 | |
317 | \qml |
318 | ParticleEmitter3D { |
319 | ... |
320 | lifeSpan: 3500 |
321 | lifeSpanVariation: 500 |
322 | } |
323 | \endqml |
324 | |
325 | The default value is \c 0. |
326 | |
327 | \sa lifeSpan |
328 | */ |
329 | int QQuick3DParticleEmitter::lifeSpanVariation() const |
330 | { |
331 | return m_lifeSpanVariation; |
332 | } |
333 | |
334 | void QQuick3DParticleEmitter::setLifeSpanVariation(int lifeSpanVariation) |
335 | { |
336 | if (m_lifeSpanVariation == lifeSpanVariation) |
337 | return; |
338 | |
339 | m_lifeSpanVariation = lifeSpanVariation; |
340 | Q_EMIT lifeSpanVariationChanged(); |
341 | } |
342 | |
343 | /*! |
344 | \qmlproperty Particle3D ParticleEmitter3D::particle |
345 | |
346 | This property defines the logical particle which this emitter emits. |
347 | Emitter must have a particle defined, or it doesn't emit anything. |
348 | Particle can be either \l SpriteParticle3D or \l ModelParticle3D. |
349 | */ |
350 | QQuick3DParticle *QQuick3DParticleEmitter::particle() const |
351 | { |
352 | return m_particle; |
353 | } |
354 | |
355 | void QQuick3DParticleEmitter::setParticle(QQuick3DParticle *particle) |
356 | { |
357 | if (m_particle == particle) |
358 | return; |
359 | if (particle && particle->system() != nullptr && m_system && particle->system() != m_system) { |
360 | qWarning(msg: "ParticleEmitter3D: Emitter and Particle must be in the same system." ); |
361 | return; |
362 | } |
363 | |
364 | QObject::connect(sender: this, signal: &QQuick3DParticleEmitter::depthBiasChanged, slot: [this](){ |
365 | m_particle->setDepthBias(m_depthBias); |
366 | }); |
367 | if (m_particle && m_system && !m_system->isShared(particle: m_particle)) |
368 | m_particle->setSystem(nullptr); |
369 | m_particle = particle; |
370 | if (particle) { |
371 | particle->setDepthBias(m_depthBias); |
372 | particle->setSystem(system()); |
373 | } |
374 | Q_EMIT particleChanged(); |
375 | } |
376 | |
377 | /*! |
378 | \qmlproperty ParticleAbstractShape3D ParticleEmitter3D::shape |
379 | |
380 | This property defines optional shape for the emitting area. It can be either |
381 | \l ParticleShape3D or \l ParticleModelShape3D. Shape is scaled, |
382 | positioned and rotated based on the emitter node properties. When the Shape |
383 | \l {ParticleShape3D::fill}{fill} property is set to false, emitting happens |
384 | only from the surface of the shape. |
385 | |
386 | When the shape is not defined, emitting is done from the center point of the |
387 | emitter node. |
388 | */ |
389 | QQuick3DParticleAbstractShape *QQuick3DParticleEmitter::shape() const |
390 | { |
391 | return m_shape; |
392 | } |
393 | |
394 | void QQuick3DParticleEmitter::setShape(QQuick3DParticleAbstractShape *shape) |
395 | { |
396 | if (m_shape == shape) |
397 | return; |
398 | |
399 | m_shape = shape; |
400 | if (m_shape && m_system) |
401 | m_shape->m_system = m_system; |
402 | Q_EMIT shapeChanged(); |
403 | } |
404 | |
405 | /*! |
406 | \qmlproperty vector3d ParticleEmitter3D::particleRotation |
407 | |
408 | This property defines the rotation of the particles in the beginning. |
409 | Rotation is defined as degrees in euler angles. |
410 | |
411 | \sa particleRotationVariation |
412 | */ |
413 | QVector3D QQuick3DParticleEmitter::particleRotation() const |
414 | { |
415 | return m_particleRotation; |
416 | } |
417 | |
418 | void QQuick3DParticleEmitter::setParticleRotation(const QVector3D &particleRotation) |
419 | { |
420 | if (m_particleRotation == particleRotation) |
421 | return; |
422 | |
423 | m_particleRotation = particleRotation; |
424 | Q_EMIT particleRotationChanged(); |
425 | } |
426 | |
427 | /*! |
428 | \qmlproperty vector3d ParticleEmitter3D::particleRotationVariation |
429 | |
430 | This property defines the rotation variation of the particles in the beginning. |
431 | Rotation variation is defined as degrees in euler angles. |
432 | |
433 | For example, to emit particles in fully random rotations: |
434 | |
435 | \qml |
436 | ParticleEmitter3D { |
437 | ... |
438 | particleRotationVariation: Qt.vector3d(180, 180, 180) |
439 | } |
440 | \endqml |
441 | |
442 | \sa particleRotation |
443 | */ |
444 | QVector3D QQuick3DParticleEmitter::particleRotationVariation() const |
445 | { |
446 | return m_particleRotationVariation; |
447 | } |
448 | |
449 | void QQuick3DParticleEmitter::setParticleRotationVariation(const QVector3D &particleRotationVariation) |
450 | { |
451 | if (m_particleRotationVariation == particleRotationVariation) |
452 | return; |
453 | |
454 | m_particleRotationVariation = particleRotationVariation; |
455 | Q_EMIT particleRotationVariationChanged(); |
456 | } |
457 | |
458 | /*! |
459 | \qmlproperty vector3d ParticleEmitter3D::particleRotationVelocity |
460 | |
461 | This property defines the rotation velocity of the particles in the beginning. |
462 | Rotation velocity is defined as degrees per second in euler angles. |
463 | |
464 | \sa particleRotationVelocityVariation |
465 | */ |
466 | QVector3D QQuick3DParticleEmitter::particleRotationVelocity() const |
467 | { |
468 | return m_particleRotationVelocity; |
469 | } |
470 | |
471 | void QQuick3DParticleEmitter::setParticleRotationVelocity(const QVector3D &particleRotationVelocity) |
472 | { |
473 | if (m_particleRotationVelocity == particleRotationVelocity) |
474 | return; |
475 | |
476 | m_particleRotationVelocity = particleRotationVelocity; |
477 | Q_EMIT particleRotationVelocityChanged(); |
478 | } |
479 | |
480 | /*! |
481 | \qmlproperty vector3d ParticleEmitter3D::particleRotationVelocityVariation |
482 | |
483 | This property defines the rotation velocity variation of the particles. |
484 | Rotation velocity variation is defined as degrees per second in euler angles. |
485 | |
486 | For example, to emit particles in random rotations which have random rotation |
487 | velocity between -100 and 100 degrees per second into any directions: |
488 | |
489 | \qml |
490 | ParticleEmitter3D { |
491 | ... |
492 | particleRotationVariation: Qt.vector3d(180, 180, 180) |
493 | particleRotationVelocityVariation: Qt.vector3d(100, 100, 100) |
494 | } |
495 | \endqml |
496 | |
497 | \sa particleRotationVelocity |
498 | */ |
499 | QVector3D QQuick3DParticleEmitter::particleRotationVelocityVariation() const |
500 | { |
501 | return m_particleRotationVelocityVariation; |
502 | } |
503 | |
504 | void QQuick3DParticleEmitter::setParticleRotationVelocityVariation(const QVector3D &particleRotationVelocityVariation) |
505 | { |
506 | if (m_particleRotationVelocityVariation == particleRotationVelocityVariation) |
507 | return; |
508 | |
509 | m_particleRotationVelocityVariation = particleRotationVelocityVariation; |
510 | Q_EMIT particleRotationVariationVelocityChanged(); |
511 | } |
512 | |
513 | /*! |
514 | \qmlproperty real ParticleEmitter3D::depthBias |
515 | |
516 | Holds the depth bias of the emitter. Depth bias is added to the object distance from camera when sorting |
517 | objects. This can be used to force rendering order between objects close to each other, that |
518 | might otherwise be rendered in different order in different frames. Negative values cause the |
519 | sorting value to move closer to the camera while positive values move it further from the camera. |
520 | */ |
521 | float QQuick3DParticleEmitter::depthBias() const |
522 | { |
523 | return m_depthBias; |
524 | } |
525 | |
526 | void QQuick3DParticleEmitter::setDepthBias(float bias) |
527 | { |
528 | if (qFuzzyCompare(p1: bias, p2: m_depthBias)) |
529 | return; |
530 | |
531 | m_depthBias = bias; |
532 | emit depthBiasChanged(); |
533 | } |
534 | |
535 | // Called to reset when system stop/continue |
536 | void QQuick3DParticleEmitter::reset() |
537 | { |
538 | m_prevEmitTime = 0; |
539 | m_unemittedF = 0.0f; |
540 | m_prevBurstTime = 0; |
541 | m_burstEmitData.clear(); |
542 | } |
543 | |
544 | /*! |
545 | \qmlmethod vector3d ParticleEmitter3D::burst(int count) |
546 | |
547 | This method emits \a count amount of particles from this emitter immediately. |
548 | */ |
549 | void QQuick3DParticleEmitter::burst(int count) |
550 | { |
551 | burst(count, duration: 0, position: QVector3D()); |
552 | } |
553 | |
554 | /*! |
555 | \qmlmethod vector3d ParticleEmitter3D::burst(int count, int duration) |
556 | |
557 | This method emits \a count amount of particles from this emitter during the |
558 | next \a duration milliseconds. |
559 | */ |
560 | void QQuick3DParticleEmitter::burst(int count, int duration) |
561 | { |
562 | burst(count, duration, position: QVector3D()); |
563 | } |
564 | |
565 | /*! |
566 | \qmlmethod vector3d ParticleEmitter3D::burst(int count, int duration, vector3d position) |
567 | |
568 | This method emits \a count amount of particles from this emitter during the |
569 | next \a duration milliseconds. The particles are emitted as if the emitter was |
570 | at \a position but all other properties are the same. |
571 | */ |
572 | void QQuick3DParticleEmitter::burst(int count, int duration, const QVector3D &position) |
573 | { |
574 | if (!m_system) |
575 | return; |
576 | QQuick3DParticleEmitBurstData burst; |
577 | burst.time = m_system->currentTime(); |
578 | burst.amount = count; |
579 | burst.duration = duration; |
580 | burst.position = position; |
581 | emitParticlesBurst(burst); |
582 | } |
583 | |
584 | void QQuick3DParticleEmitter::generateEmitBursts() |
585 | { |
586 | if (!m_system) |
587 | return; |
588 | |
589 | if (!m_particle) |
590 | return; |
591 | |
592 | if (m_emitBursts.isEmpty()) { |
593 | m_burstGenerated = true; |
594 | return; |
595 | } |
596 | |
597 | // Generating burst causes all particle data reseting |
598 | // as bursts take first particles in the list. |
599 | m_particle->reset(); |
600 | |
601 | // TODO: In trail emitter case centerPos should be calculated |
602 | // taking into account each particle position at emitburst time |
603 | QMatrix4x4 transform = calculateParticleTransform(parent: parentNode(), systemSharedParent: m_systemSharedParent); |
604 | QQuaternion rotation = calculateParticleRotation(parent: parentNode(), systemSharedParent: m_systemSharedParent); |
605 | QVector3D centerPos = position(); |
606 | |
607 | for (auto emitBurst : std::as_const(t&: m_emitBursts)) { |
608 | // Ignore all dynamic bursts here |
609 | if (qobject_cast<QQuick3DParticleDynamicBurst *>(object: emitBurst)) |
610 | continue; |
611 | int emitAmount = emitBurst->amount(); |
612 | if (emitAmount <= 0) |
613 | return; |
614 | // Distribute start times between burst time and time+duration. |
615 | float startTime = float(emitBurst->time() / 1000.0f); |
616 | float timeStep = float(emitBurst->duration() / 1000.0f) / emitAmount; |
617 | for (int i = 0; i < emitAmount; i++) { |
618 | emitParticle(particle: m_particle, startTime, transform, parentRotation: rotation, centerPos); |
619 | startTime += timeStep; |
620 | } |
621 | // Increase burst index (for statically allocated particles) |
622 | m_particle->updateBurstIndex(amount: emitBurst->amount()); |
623 | } |
624 | m_burstGenerated = true; |
625 | } |
626 | |
627 | void QQuick3DParticleEmitter::registerEmitBurst(QQuick3DParticleEmitBurst* emitBurst) |
628 | { |
629 | m_emitBursts.removeAll(t: emitBurst); |
630 | m_emitBursts << emitBurst; |
631 | m_burstGenerated = false; |
632 | } |
633 | |
634 | void QQuick3DParticleEmitter::unRegisterEmitBurst(QQuick3DParticleEmitBurst* emitBurst) |
635 | { |
636 | m_emitBursts.removeAll(t: emitBurst); |
637 | m_burstGenerated = false; |
638 | } |
639 | |
640 | void QQuick3DParticleEmitter::emitParticle(QQuick3DParticle *particle, float startTime, const QMatrix4x4 &transform, const QQuaternion &parentRotation, const QVector3D ¢erPos, int index) |
641 | { |
642 | if (!m_system) |
643 | return; |
644 | auto rand = m_system->rand(); |
645 | |
646 | QQuick3DParticleModelBlendParticle *mbp = qobject_cast<QQuick3DParticleModelBlendParticle *>(object: particle); |
647 | if (mbp && mbp->lastParticle()) |
648 | return; |
649 | |
650 | int particleDataIndex = index == -1 ? particle->nextCurrentIndex(emitter: this) : index; |
651 | if (index == -1 && mbp && mbp->emitMode() == QQuick3DParticleModelBlendParticle::Random) |
652 | particleDataIndex = mbp->randomIndex(particleIndex: particleDataIndex); |
653 | |
654 | auto d = &particle->m_particleData[particleDataIndex]; |
655 | int particleIdIndex = m_system->m_particleIdIndex++; |
656 | if (m_system->m_particleIdIndex == INT_MAX) |
657 | m_system->m_particleIdIndex = 0; |
658 | |
659 | *d = m_clearData; // Reset the data as it might be reused |
660 | d->index = particleIdIndex; |
661 | d->startTime = startTime; |
662 | |
663 | // Life time in seconds |
664 | float lifeSpanMs = m_lifeSpanVariation / 1000.0f; |
665 | float lifeSpanVariationMs = lifeSpanMs - 2.0f * rand->get(particleIndex: particleIdIndex, user: QPRand::LifeSpanV) * lifeSpanMs; |
666 | d->lifetime = (m_lifeSpan / 1000.0f) + lifeSpanVariationMs; |
667 | |
668 | // Size |
669 | float sVar = m_particleScaleVariation - 2.0f * rand->get(particleIndex: particleIdIndex, user: QPRand::ScaleV) * m_particleScaleVariation; |
670 | float endScale = (m_particleEndScale < 0.0f) ? m_particleScale : m_particleEndScale; |
671 | float sEndVar = (m_particleEndScaleVariation < 0.0f) |
672 | ? sVar |
673 | : m_particleEndScaleVariation - 2.0f * rand->get(particleIndex: particleIdIndex, user: QPRand::ScaleEV) * m_particleEndScaleVariation; |
674 | d->startSize = std::max(a: 0.0f, b: float(m_particleScale + sVar)); |
675 | d->endSize = std::max(a: 0.0f, b: float(endScale + sEndVar)); |
676 | |
677 | // Emiting area/shape |
678 | if (mbp && mbp->modelBlendMode() != QQuick3DParticleModelBlendParticle::Construct) { |
679 | // We emit from model position unless in construct mode |
680 | d->startPosition = mbp->particleCenter(particleIndex: particleDataIndex); |
681 | } else { |
682 | // When shape is not set, default to node center point. |
683 | QVector3D pos = centerPos; |
684 | if (m_shape) |
685 | pos += m_shape->getPosition(particleIndex: particleIdIndex); |
686 | d->startPosition = transform.map(point: pos); |
687 | } |
688 | |
689 | // Velocity |
690 | if (m_velocity) { |
691 | // Rotate velocity based on parent node rotation and emitter rotation |
692 | d->startVelocity = parentRotation * rotation() * m_velocity->sample(d: *d); |
693 | } |
694 | |
695 | // Rotation |
696 | if (!m_particleRotation.isNull() || !m_particleRotationVariation.isNull()) { |
697 | Vector3b rot; |
698 | constexpr float step = 127.0f / 360.0f; // +/- 360-degrees as qint8 (-127..127) |
699 | rot.x = m_particleRotation.x() * step; |
700 | rot.y = m_particleRotation.y() * step; |
701 | rot.z = m_particleRotation.z() * step; |
702 | rot.x += (m_particleRotationVariation.x() - 2.0f * rand->get(particleIndex: particleIdIndex, user: QPRand::RotXV) * m_particleRotationVariation.x()) * step; |
703 | rot.y += (m_particleRotationVariation.y() - 2.0f * rand->get(particleIndex: particleIdIndex, user: QPRand::RotYV) * m_particleRotationVariation.y()) * step; |
704 | rot.z += (m_particleRotationVariation.z() - 2.0f * rand->get(particleIndex: particleIdIndex, user: QPRand::RotZV) * m_particleRotationVariation.z()) * step; |
705 | d->startRotation = rot; |
706 | } |
707 | // Rotation velocity |
708 | if (!m_particleRotationVelocity.isNull() || !m_particleRotationVelocityVariation.isNull()) { |
709 | float rotVelX = m_particleRotationVelocity.x(); |
710 | float rotVelY = m_particleRotationVelocity.y(); |
711 | float rotVelZ = m_particleRotationVelocity.z(); |
712 | rotVelX += (m_particleRotationVelocityVariation.x() - 2.0f * rand->get(particleIndex: particleIdIndex, user: QPRand::RotXVV) * m_particleRotationVelocityVariation.x()); |
713 | rotVelY += (m_particleRotationVelocityVariation.y() - 2.0f * rand->get(particleIndex: particleIdIndex, user: QPRand::RotYVV) * m_particleRotationVelocityVariation.y()); |
714 | rotVelZ += (m_particleRotationVelocityVariation.z() - 2.0f * rand->get(particleIndex: particleIdIndex, user: QPRand::RotZVV) * m_particleRotationVelocityVariation.z()); |
715 | // Particle data rotations are in qint8 vec3 to save memory. |
716 | // max value 127*127 = 16129 degrees/second |
717 | float sign; |
718 | sign = rotVelX < 0.0f ? -1.0f : 1.0f; |
719 | rotVelX = std::max(a: -127.0f, b: std::min<float>(a: 127.0f, b: sign * std::sqrt(x: abs(x: rotVelX)))); |
720 | sign = rotVelY < 0.0f ? -1.0f : 1.0f; |
721 | rotVelY = std::max(a: -127.0f, b: std::min<float>(a: 127.0f, b: sign * std::sqrt(x: abs(x: rotVelY)))); |
722 | sign = rotVelZ < 0.0f ? -1.0f : 1.0f; |
723 | rotVelZ = std::max(a: -127.0f, b: std::min<float>(a: 127.0f, b: sign * std::sqrt(x: abs(x: rotVelZ)))); |
724 | d->startRotationVelocity = { .x: qint8(rotVelX), .y: qint8(rotVelY), .z: qint8(rotVelZ) }; |
725 | } |
726 | |
727 | // Colors |
728 | QColor pc = particle->color(); |
729 | QVector4D pcv = particle->colorVariation(); |
730 | uchar r, g, b, a; |
731 | if (particle->unifiedColorVariation()) { |
732 | // Vary all color channels using the same random amount |
733 | const int randVar = int(rand->get(particleIndex: particleIdIndex, user: QPRand::ColorAV) * 256); |
734 | r = pc.red() * (1.0f - pcv.x()) + randVar * pcv.x(); |
735 | g = pc.green() * (1.0f - pcv.y()) + randVar * pcv.y(); |
736 | b = pc.blue() * (1.0f - pcv.z()) + randVar * pcv.z(); |
737 | a = pc.alpha() * (1.0f - pcv.w()) + randVar * pcv.w(); |
738 | } else { |
739 | r = pc.red() * (1.0f - pcv.x()) + int(rand->get(particleIndex: particleIdIndex, user: QPRand::ColorRV) * 256) * pcv.x(); |
740 | g = pc.green() * (1.0f - pcv.y()) + int(rand->get(particleIndex: particleIdIndex, user: QPRand::ColorGV) * 256) * pcv.y(); |
741 | b = pc.blue() * (1.0f - pcv.z()) + int(rand->get(particleIndex: particleIdIndex, user: QPRand::ColorBV) * 256) * pcv.z(); |
742 | a = pc.alpha() * (1.0f - pcv.w()) + int(rand->get(particleIndex: particleIdIndex, user: QPRand::ColorAV) * 256) * pcv.w(); |
743 | } |
744 | d->startColor = {.r: r, .g: g, .b: b, .a: a}; |
745 | |
746 | // Sprite sequence animation |
747 | if (auto sequence = particle->m_spriteSequence) { |
748 | if (sequence->duration() > 0) { |
749 | float animationTimeMs = float(sequence->duration()) / 1000.0f; |
750 | float animationTimeVarMs = float(sequence->durationVariation()) / 1000.0f; |
751 | animationTimeVarMs = animationTimeVarMs - 2.0f * rand->get(particleIndex: particleIdIndex, user: QPRand::SpriteAnimationV) * animationTimeVarMs; |
752 | // Sequence duration to be at least 1ms |
753 | const float MIN_DURATION = 0.001f; |
754 | d->animationTime = std::max(a: MIN_DURATION, b: animationTimeMs + animationTimeVarMs); |
755 | } else { |
756 | // Duration not set, so use the lifetime of the particle |
757 | d->animationTime = d->lifetime; |
758 | } |
759 | } |
760 | } |
761 | |
762 | int QQuick3DParticleEmitter::getEmitAmountFromDynamicBursts(int triggerType) |
763 | { |
764 | int amount = 0; |
765 | const int currentTime = m_system->time(); |
766 | const int prevTime = m_prevBurstTime; |
767 | // First go through dynamic bursts and see if any of them tiggers |
768 | for (auto *burst : std::as_const(t&: m_emitBursts)) { |
769 | auto *burstPtr = qobject_cast<QQuick3DParticleDynamicBurst *>(object: burst); |
770 | if (!burstPtr) |
771 | continue; |
772 | if (!burstPtr->m_enabled) |
773 | continue; |
774 | // Trigering on trail emitter start / end |
775 | const bool trailTriggering = triggerType && (burstPtr->m_triggerMode) == triggerType; |
776 | // Triggering on time for the first time |
777 | const bool timeTriggeringStart = !triggerType && currentTime >= burstPtr->m_time && prevTime <= burstPtr->m_time; |
778 | if (trailTriggering || timeTriggeringStart) { |
779 | int burstAmount = burstPtr->m_amount; |
780 | if (burstPtr->m_amountVariation > 0) { |
781 | auto rand = m_system->rand(); |
782 | int randAmount = 2 * rand->get() * burstPtr->m_amountVariation; |
783 | burstAmount += burstPtr->m_amountVariation - randAmount; |
784 | } |
785 | if (burstAmount > 0) { |
786 | if (timeTriggeringStart && burstPtr->m_duration > 0) { |
787 | // Burst with duration, so generate burst data |
788 | BurstEmitData emitData; |
789 | emitData.startTime = currentTime; |
790 | emitData.endTime = currentTime + burstPtr->m_duration; |
791 | emitData.emitAmount = burstAmount; |
792 | emitData.prevBurstTime = prevTime; |
793 | m_burstEmitData << emitData; |
794 | } else { |
795 | // Directly trigger the amount |
796 | amount += burstAmount; |
797 | } |
798 | } |
799 | } |
800 | } |
801 | // Then go through the triggered emit bursts list |
802 | for (int burstIndex = 0; burstIndex < m_burstEmitData.size(); ++burstIndex) { |
803 | auto &burstData = m_burstEmitData[burstIndex]; |
804 | const int amountLeft = burstData.emitAmount - burstData.emitCounter; |
805 | if (currentTime >= burstData.endTime) { |
806 | // Burst time has ended, emit all rest of the particles and remove the burst |
807 | amount += amountLeft; |
808 | m_burstEmitData.removeAt(i: burstIndex); |
809 | } else { |
810 | // Otherwise burst correct amount depending on burst duration |
811 | const int durationTime = currentTime - burstData.prevBurstTime; |
812 | const int burstDurationTime = burstData.endTime - burstData.startTime; |
813 | int burstAmount = burstData.emitAmount * (float(durationTime) / float(burstDurationTime)); |
814 | burstAmount = std::min(a: amountLeft, b: burstAmount); |
815 | if (burstAmount > 0) { |
816 | amount += burstAmount; |
817 | burstData.emitCounter += burstAmount; |
818 | burstData.prevBurstTime = currentTime; |
819 | } |
820 | } |
821 | } |
822 | // Reset the prev burst time |
823 | m_prevBurstTime = currentTime; |
824 | return amount; |
825 | } |
826 | |
827 | int QQuick3DParticleEmitter::getEmitAmount() |
828 | { |
829 | if (!m_system) |
830 | return 0; |
831 | |
832 | if (!m_enabled) |
833 | return 0; |
834 | |
835 | if (m_emitRate <= 0.0f) |
836 | return 0; |
837 | |
838 | float timeChange = m_system->currentTime() - m_prevEmitTime; |
839 | float emitAmountF = timeChange / (1000.0f / m_emitRate); |
840 | int emitAmount = floorf(x: emitAmountF); |
841 | // Store the partly unemitted particles |
842 | // When emitAmount = 0, we just let the timeChange grow. |
843 | if (emitAmount > 0) { |
844 | m_unemittedF += (emitAmountF - emitAmount); |
845 | // When unemitted grow to a full particle, emit it |
846 | // This way if emit rate is 140 emitAmounts can be e.g. 2,2,3,2,2,3 etc. |
847 | if (m_unemittedF >= 1.0f) { |
848 | emitAmount++; |
849 | m_unemittedF--; |
850 | } |
851 | } |
852 | return emitAmount; |
853 | } |
854 | |
855 | void QQuick3DParticleEmitter::emitParticlesBurst(const QQuick3DParticleEmitBurstData &burst) |
856 | { |
857 | if (!m_system) |
858 | return; |
859 | |
860 | if (!m_enabled) |
861 | return; |
862 | |
863 | if (!m_particle) |
864 | return; |
865 | |
866 | QMatrix4x4 transform = calculateParticleTransform(parent: parentNode(), systemSharedParent: m_systemSharedParent); |
867 | QQuaternion rotation = calculateParticleRotation(parent: parentNode(), systemSharedParent: m_systemSharedParent); |
868 | QVector3D centerPos = position() + burst.position; |
869 | |
870 | int emitAmount = std::min(a: burst.amount, b: int(m_particle->maxAmount())); |
871 | for (int i = 0; i < emitAmount; i++) { |
872 | // Distribute evenly between time and time+duration. |
873 | float startTime = (burst.time / 1000.0f) + (float(1 + i) / emitAmount) * ((burst.duration) / 1000.0f); |
874 | emitParticle(particle: m_particle, startTime, transform, parentRotation: rotation, centerPos); |
875 | } |
876 | } |
877 | |
878 | // Called to emit set of particles |
879 | void QQuick3DParticleEmitter::emitParticles() |
880 | { |
881 | if (!m_system) |
882 | return; |
883 | |
884 | if (!m_enabled) |
885 | return; |
886 | |
887 | if (!m_particle) |
888 | return; |
889 | |
890 | auto *mbp = qobject_cast<QQuick3DParticleModelBlendParticle *>(object: m_particle); |
891 | if (mbp && mbp->activationNode() && mbp->emitMode() == QQuick3DParticleModelBlendParticle::Activation) { |
892 | // The particles are emitted using the activationNode instead of regular emit |
893 | emitActivationNodeParticles(particle: mbp); |
894 | return; |
895 | } |
896 | |
897 | const int systemTime = m_system->currentTime(); |
898 | |
899 | if (systemTime < m_prevEmitTime) { |
900 | // If we are goint backwards, reset previous emit time to current time. |
901 | m_prevEmitTime = systemTime; |
902 | } else { |
903 | // Keep previous emitting time within max the life span. |
904 | // This way emitting is reasonable also with big time jumps. |
905 | const int maxLifeSpan = m_lifeSpan + m_lifeSpanVariation; |
906 | m_prevEmitTime = std::max(a: m_prevEmitTime, b: systemTime - maxLifeSpan); |
907 | } |
908 | |
909 | // If bursts have changed, generate them first in the beginning |
910 | if (!m_burstGenerated) |
911 | generateEmitBursts(); |
912 | |
913 | int emitAmount = getEmitAmount() + getEmitAmountFromDynamicBursts(); |
914 | |
915 | // With lower emitRates, let timeChange grow until at least 1 particle is emitted |
916 | if (emitAmount < 1) |
917 | return; |
918 | |
919 | QMatrix4x4 transform = calculateParticleTransform(parent: parentNode(), systemSharedParent: m_systemSharedParent); |
920 | QQuaternion rotation = calculateParticleRotation(parent: parentNode(), systemSharedParent: m_systemSharedParent); |
921 | QVector3D centerPos = position(); |
922 | |
923 | emitAmount = std::min(a: emitAmount, b: int(m_particle->maxAmount())); |
924 | for (int i = 0; i < emitAmount; i++) { |
925 | // Distribute evenly between previous and current time, important especially |
926 | // when time has jumped a lot (like a starttime). |
927 | float startTime = (m_prevEmitTime / 1000.0f) + (float(1+i) / emitAmount) * ((systemTime - m_prevEmitTime) / 1000.0f); |
928 | emitParticle(particle: m_particle, startTime, transform, parentRotation: rotation, centerPos); |
929 | } |
930 | |
931 | m_prevEmitTime = systemTime; |
932 | } |
933 | |
934 | void QQuick3DParticleEmitter::emitActivationNodeParticles(QQuick3DParticleModelBlendParticle *particle) |
935 | { |
936 | QMatrix4x4 matrix = particle->activationNode()->sceneTransform(); |
937 | QMatrix4x4 actTransform = sceneTransform().inverted() * matrix; |
938 | QVector3D front = actTransform.column(index: 2).toVector3D(); |
939 | QVector3D pos = actTransform.column(index: 3).toVector3D(); |
940 | float d = QVector3D::dotProduct(v1: pos, v2: front); |
941 | |
942 | const int systemTime = m_system->currentTime(); |
943 | |
944 | // Keep previous emitting time within max the life span. |
945 | // This way emitting is reasonable also with big time jumps. |
946 | const int maxLifeSpan = m_lifeSpan + m_lifeSpanVariation; |
947 | m_prevEmitTime = std::max(a: m_prevEmitTime, b: systemTime - maxLifeSpan); |
948 | |
949 | float startTime = systemTime / 1000.0f; |
950 | |
951 | QMatrix4x4 transform = calculateParticleTransform(parent: parentNode(), systemSharedParent: m_systemSharedParent); |
952 | QQuaternion rotation = calculateParticleRotation(parent: parentNode(), systemSharedParent: m_systemSharedParent); |
953 | QVector3D centerPos = position(); |
954 | |
955 | for (int i = 0; i < particle->maxAmount(); i++) { |
956 | if (particle->m_particleData[i].startTime >= 0) |
957 | continue; |
958 | const QVector3D pc = particle->particleCenter(particleIndex: i); |
959 | if (QVector3D::dotProduct(v1: front, v2: pc) - d > 0.0f) |
960 | emitParticle(particle, startTime, transform, parentRotation: rotation, centerPos, index: i); |
961 | } |
962 | |
963 | m_prevEmitTime = systemTime; |
964 | } |
965 | |
966 | void QQuick3DParticleEmitter::componentComplete() |
967 | { |
968 | if (!m_system && qobject_cast<QQuick3DParticleSystem *>(object: parentItem())) |
969 | setSystem(qobject_cast<QQuick3DParticleSystem *>(object: parentItem())); |
970 | |
971 | // When dynamically creating emitters, start from the current time. |
972 | if (m_system) |
973 | m_prevEmitTime = m_system->currentTime(); |
974 | |
975 | QQuick3DNode::componentComplete(); |
976 | } |
977 | |
978 | // EmitBursts - list handling |
979 | |
980 | /*! |
981 | \qmlproperty List<EmitBurst3D> ParticleEmitter3D::emitBursts |
982 | |
983 | This property takes a list of \l EmitBurst3D elements, to declaratively define bursts. |
984 | If the burst starting time, amount, and duration are known beforehand, it is better to |
985 | use this property than e.g. calling \l burst() with a \l Timer. |
986 | |
987 | For example, to emit 100 particles at the beginning, and 50 particles at 2 seconds: |
988 | |
989 | \qml |
990 | ParticleEmitter3D { |
991 | emitBursts: [ |
992 | EmitBurst3D { |
993 | time: 0 |
994 | amount: 100 |
995 | }, |
996 | EmitBurst3D { |
997 | time: 2000 |
998 | amount: 50 |
999 | } |
1000 | ] |
1001 | } |
1002 | \endqml |
1003 | |
1004 | \sa burst() |
1005 | */ |
1006 | QQmlListProperty<QQuick3DParticleEmitBurst> QQuick3DParticleEmitter::emitBursts() |
1007 | { |
1008 | return {this, this, |
1009 | &QQuick3DParticleEmitter::appendEmitBurst, |
1010 | &QQuick3DParticleEmitter::emitBurstCount, |
1011 | &QQuick3DParticleEmitter::emitBurst, |
1012 | &QQuick3DParticleEmitter::clearEmitBursts, |
1013 | &QQuick3DParticleEmitter::replaceEmitBurst, |
1014 | &QQuick3DParticleEmitter::removeLastEmitBurst}; |
1015 | } |
1016 | |
1017 | void QQuick3DParticleEmitter::appendEmitBurst(QQuick3DParticleEmitBurst* n) { |
1018 | m_emitBursts.append(t: n); |
1019 | } |
1020 | |
1021 | qsizetype QQuick3DParticleEmitter::emitBurstCount() const |
1022 | { |
1023 | return m_emitBursts.size(); |
1024 | } |
1025 | |
1026 | QQuick3DParticleEmitBurst *QQuick3DParticleEmitter::emitBurst(qsizetype index) const |
1027 | { |
1028 | return m_emitBursts.at(i: index); |
1029 | } |
1030 | |
1031 | void QQuick3DParticleEmitter::clearEmitBursts() { |
1032 | m_emitBursts.clear(); |
1033 | } |
1034 | |
1035 | void QQuick3DParticleEmitter::replaceEmitBurst(qsizetype index, QQuick3DParticleEmitBurst *n) |
1036 | { |
1037 | m_emitBursts[index] = n; |
1038 | } |
1039 | |
1040 | void QQuick3DParticleEmitter::removeLastEmitBurst() |
1041 | { |
1042 | m_emitBursts.removeLast(); |
1043 | } |
1044 | |
1045 | // EmitBursts - static |
1046 | void QQuick3DParticleEmitter::appendEmitBurst(QQmlListProperty<QQuick3DParticleEmitBurst> *list, QQuick3DParticleEmitBurst *p) { |
1047 | reinterpret_cast< QQuick3DParticleEmitter *>(list->data)->appendEmitBurst(n: p); |
1048 | } |
1049 | |
1050 | void QQuick3DParticleEmitter::clearEmitBursts(QQmlListProperty<QQuick3DParticleEmitBurst> *list) { |
1051 | reinterpret_cast< QQuick3DParticleEmitter *>(list->data)->clearEmitBursts(); |
1052 | } |
1053 | |
1054 | void QQuick3DParticleEmitter::replaceEmitBurst(QQmlListProperty<QQuick3DParticleEmitBurst> *list, qsizetype i, QQuick3DParticleEmitBurst *p) |
1055 | { |
1056 | reinterpret_cast< QQuick3DParticleEmitter *>(list->data)->replaceEmitBurst(index: i, n: p); |
1057 | } |
1058 | |
1059 | void QQuick3DParticleEmitter::removeLastEmitBurst(QQmlListProperty<QQuick3DParticleEmitBurst> *list) |
1060 | { |
1061 | reinterpret_cast< QQuick3DParticleEmitter *>(list->data)->removeLastEmitBurst(); |
1062 | } |
1063 | |
1064 | QQuick3DParticleEmitBurst* QQuick3DParticleEmitter::emitBurst(QQmlListProperty<QQuick3DParticleEmitBurst> *list, qsizetype i) { |
1065 | return reinterpret_cast< QQuick3DParticleEmitter *>(list->data)->emitBurst(index: i); |
1066 | } |
1067 | |
1068 | qsizetype QQuick3DParticleEmitter::emitBurstCount(QQmlListProperty<QQuick3DParticleEmitBurst> *list) { |
1069 | return reinterpret_cast< QQuick3DParticleEmitter *>(list->data)->emitBurstCount(); |
1070 | } |
1071 | |
1072 | QT_END_NAMESPACE |
1073 | |