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