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 EmitType ParticleEmitter3D::emitType
570 \since 6.10
571
572 This property defines the type of the shape.
573
574 \default ParticleEmitter3D.Default
575*/
576
577/*!
578 \qmlproperty enumeration ParticleEmitter3D::EmitType
579 \since 6.10
580
581 Defines the emit type 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 QVariant fill = m_shape->property("fill");
807 if (fill.isValid() && !fill.toBool()) {
808 const auto n = m_shape->getSurfaceNormal(particleIdIndex);
809 d->surfaceNormal = n;
810 if (m_emitMode != EmitMode::Default)
811 normalBasedVelocity = true;
812 }
813 } else if (!normal.isNull() && m_emitMode != EmitMode::Default) {
814 normalBasedVelocity = true;
815 }
816 d->startPosition = transform.map(pos);
817 }
818
819 // Velocity
820 // Rotate velocity based on parent node rotation and emitter rotation
821 if (m_velocity)
822 d->startVelocity = parentRotation * rotation() * m_velocity->sample(*d);
823 if (normalBasedVelocity) {
824 if (m_emitMode == EmitMode::SurfaceNormal)
825 d->startVelocity = rotationFromNormal(d->surfaceNormal).mapVector(d->startVelocity);
826 else if (!velocity.isNull()) // EmitMode::SurfaceReflected
827 d->startVelocity = rotationFromNormal(-reflect(velocity, d->surfaceNormal)).mapVector(d->startVelocity);
828 else
829 d->startVelocity = rotationFromNormal(-reflect(d->startVelocity.normalized(), d->surfaceNormal)).mapVector(d->startVelocity);
830 }
831
832 // Rotation
833 if (!m_particleRotation.isNull() || !m_particleRotationVariation.isNull()) {
834 Vector3b rot;
835 constexpr float step = 127.0f / 360.0f; // +/- 360-degrees as qint8 (-127..127)
836 rot.x = m_particleRotation.x() * step;
837 rot.y = m_particleRotation.y() * step;
838 rot.z = m_particleRotation.z() * step;
839 rot.x += (m_particleRotationVariation.x() - 2.0f * rand->get(particleIdIndex, QPRand::RotXV) * m_particleRotationVariation.x()) * step;
840 rot.y += (m_particleRotationVariation.y() - 2.0f * rand->get(particleIdIndex, QPRand::RotYV) * m_particleRotationVariation.y()) * step;
841 rot.z += (m_particleRotationVariation.z() - 2.0f * rand->get(particleIdIndex, QPRand::RotZV) * m_particleRotationVariation.z()) * step;
842 d->startRotation = rot;
843 }
844 // Rotation velocity
845 if (!m_particleRotationVelocity.isNull() || !m_particleRotationVelocityVariation.isNull()) {
846 float rotVelX = m_particleRotationVelocity.x();
847 float rotVelY = m_particleRotationVelocity.y();
848 float rotVelZ = m_particleRotationVelocity.z();
849 rotVelX += (m_particleRotationVelocityVariation.x() - 2.0f * rand->get(particleIdIndex, QPRand::RotXVV) * m_particleRotationVelocityVariation.x());
850 rotVelY += (m_particleRotationVelocityVariation.y() - 2.0f * rand->get(particleIdIndex, QPRand::RotYVV) * m_particleRotationVelocityVariation.y());
851 rotVelZ += (m_particleRotationVelocityVariation.z() - 2.0f * rand->get(particleIdIndex, QPRand::RotZVV) * m_particleRotationVelocityVariation.z());
852 // Particle data rotations are in qint8 vec3 to save memory.
853 // max value 127*127 = 16129 degrees/second
854 float sign;
855 sign = rotVelX < 0.0f ? -1.0f : 1.0f;
856 rotVelX = std::max(-127.0f, std::min<float>(127.0f, sign * std::sqrt(abs(rotVelX))));
857 sign = rotVelY < 0.0f ? -1.0f : 1.0f;
858 rotVelY = std::max(-127.0f, std::min<float>(127.0f, sign * std::sqrt(abs(rotVelY))));
859 sign = rotVelZ < 0.0f ? -1.0f : 1.0f;
860 rotVelZ = std::max(-127.0f, std::min<float>(127.0f, sign * std::sqrt(abs(rotVelZ))));
861 d->startRotationVelocity = { qint8(rotVelX), qint8(rotVelY), qint8(rotVelZ) };
862 }
863
864 // Colors
865 QColor pc = particle->color();
866 QVector4D pcv = particle->colorVariation();
867 uchar r, g, b, a;
868 if (particle->unifiedColorVariation()) {
869 // Vary all color channels using the same random amount
870 const int randVar = int(rand->get(particleIdIndex, QPRand::ColorAV) * 256);
871 r = pc.red() * (1.0f - pcv.x()) + randVar * pcv.x();
872 g = pc.green() * (1.0f - pcv.y()) + randVar * pcv.y();
873 b = pc.blue() * (1.0f - pcv.z()) + randVar * pcv.z();
874 a = pc.alpha() * (1.0f - pcv.w()) + randVar * pcv.w();
875 } else {
876 r = pc.red() * (1.0f - pcv.x()) + int(rand->get(particleIdIndex, QPRand::ColorRV) * 256) * pcv.x();
877 g = pc.green() * (1.0f - pcv.y()) + int(rand->get(particleIdIndex, QPRand::ColorGV) * 256) * pcv.y();
878 b = pc.blue() * (1.0f - pcv.z()) + int(rand->get(particleIdIndex, QPRand::ColorBV) * 256) * pcv.z();
879 a = pc.alpha() * (1.0f - pcv.w()) + int(rand->get(particleIdIndex, QPRand::ColorAV) * 256) * pcv.w();
880 }
881 d->startColor = {r, g, b, a};
882
883 // Sprite sequence animation
884 if (auto sequence = particle->m_spriteSequence) {
885 if (sequence->duration() > 0) {
886 float animationTimeMs = float(sequence->duration()) / 1000.0f;
887 float animationTimeVarMs = float(sequence->durationVariation()) / 1000.0f;
888 animationTimeVarMs = animationTimeVarMs - 2.0f * rand->get(particleIdIndex, QPRand::SpriteAnimationV) * animationTimeVarMs;
889 // Sequence duration to be at least 1ms
890 const float MIN_DURATION = 0.001f;
891 d->animationTime = std::max(MIN_DURATION, animationTimeMs + animationTimeVarMs);
892 } else {
893 // Duration not set, so use the lifetime of the particle
894 d->animationTime = d->lifetime;
895 }
896 }
897 d->reversed = m_reversed;
898}
899
900int QQuick3DParticleEmitter::getEmitAmountFromDynamicBursts(int triggerType)
901{
902 int amount = 0;
903 const int currentTime = m_system->time();
904 const int prevTime = m_prevBurstTime;
905 // First go through dynamic bursts and see if any of them tiggers
906 for (auto *burst : std::as_const(m_emitBursts)) {
907 auto *burstPtr = qobject_cast<QQuick3DParticleDynamicBurst *>(burst);
908 if (!burstPtr)
909 continue;
910 if (!burstPtr->m_enabled)
911 continue;
912 // Trigering on trail emitter start / end
913 const bool trailTriggering = triggerType && (burstPtr->m_triggerMode) == triggerType;
914 // Triggering on time for the first time
915 const bool timeTriggeringStart = !triggerType && currentTime >= burstPtr->m_time && prevTime <= burstPtr->m_time;
916 if (trailTriggering || timeTriggeringStart) {
917 int burstAmount = burstPtr->m_amount;
918 if (burstPtr->m_amountVariation > 0) {
919 auto rand = m_system->rand();
920 int randAmount = 2 * rand->get() * burstPtr->m_amountVariation;
921 burstAmount += burstPtr->m_amountVariation - randAmount;
922 }
923 if (burstAmount > 0) {
924 if (timeTriggeringStart && burstPtr->m_duration > 0) {
925 // Burst with duration, so generate burst data
926 BurstEmitData emitData;
927 emitData.startTime = currentTime;
928 emitData.endTime = currentTime + burstPtr->m_duration;
929 emitData.emitAmount = burstAmount;
930 emitData.prevBurstTime = prevTime;
931 m_burstEmitData << emitData;
932 } else {
933 // Directly trigger the amount
934 amount += burstAmount;
935 }
936 }
937 }
938 }
939 // Then go through the triggered emit bursts list
940 for (int burstIndex = 0; burstIndex < m_burstEmitData.size(); ++burstIndex) {
941 auto &burstData = m_burstEmitData[burstIndex];
942 const int amountLeft = burstData.emitAmount - burstData.emitCounter;
943 if (currentTime >= burstData.endTime) {
944 // Burst time has ended, emit all rest of the particles and remove the burst
945 amount += amountLeft;
946 m_burstEmitData.removeAt(burstIndex);
947 } else {
948 // Otherwise burst correct amount depending on burst duration
949 const int durationTime = currentTime - burstData.prevBurstTime;
950 const int burstDurationTime = burstData.endTime - burstData.startTime;
951 int burstAmount = burstData.emitAmount * (float(durationTime) / float(burstDurationTime));
952 burstAmount = std::min(amountLeft, burstAmount);
953 if (burstAmount > 0) {
954 amount += burstAmount;
955 burstData.emitCounter += burstAmount;
956 burstData.prevBurstTime = currentTime;
957 }
958 }
959 }
960 // Reset the prev burst time
961 m_prevBurstTime = currentTime;
962 return amount;
963}
964
965int QQuick3DParticleEmitter::getEmitAmount()
966{
967 if (!m_system)
968 return 0;
969
970 if (!m_enabled)
971 return 0;
972
973 if (m_emitRate <= 0.0f)
974 return 0;
975
976 float timeChange = m_system->currentTime() - m_prevEmitTime;
977 float emitAmountF = timeChange / (1000.0f / m_emitRate);
978 int emitAmount = floorf(emitAmountF);
979 // Store the partly unemitted particles
980 // When emitAmount = 0, we just let the timeChange grow.
981 if (emitAmount > 0) {
982 m_unemittedF += (emitAmountF - emitAmount);
983 // When unemitted grow to a full particle, emit it
984 // This way if emit rate is 140 emitAmounts can be e.g. 2,2,3,2,2,3 etc.
985 if (m_unemittedF >= 1.0f) {
986 emitAmount++;
987 m_unemittedF--;
988 }
989 }
990 return emitAmount;
991}
992
993void QQuick3DParticleEmitter::emitParticlesBurst(const QQuick3DParticleEmitBurstData &burst)
994{
995 if (!m_system)
996 return;
997
998 if (!m_enabled)
999 return;
1000
1001 if (!m_particle)
1002 return;
1003
1004 QMatrix4x4 transform = calculateParticleTransform(parentNode(), m_systemSharedParent);
1005 QQuaternion rotation = calculateParticleRotation(parentNode(), m_systemSharedParent);
1006 QVector3D centerPos = position() + burst.position;
1007
1008 int emitAmount = std::min(burst.amount, int(m_particle->maxAmount()));
1009 for (int i = 0; i < emitAmount; i++) {
1010 // Distribute evenly between time and time+duration.
1011 float startTime = (burst.time / 1000.0f) + (float(1 + i) / emitAmount) * ((burst.duration) / 1000.0f);
1012 emitParticle(m_particle, startTime, transform, rotation, centerPos);
1013 }
1014}
1015
1016// Called to emit set of particles
1017void QQuick3DParticleEmitter::emitParticles()
1018{
1019 if (!m_system)
1020 return;
1021
1022 if (!m_enabled)
1023 return;
1024
1025 if (!m_particle)
1026 return;
1027
1028 auto *mbp = qobject_cast<QQuick3DParticleModelBlendParticle *>(m_particle);
1029 if (mbp && mbp->activationNode() && mbp->emitMode() == QQuick3DParticleModelBlendParticle::Activation) {
1030 // The particles are emitted using the activationNode instead of regular emit
1031 emitActivationNodeParticles(mbp);
1032 return;
1033 }
1034
1035 const int systemTime = m_system->currentTime();
1036
1037 if (systemTime < m_prevEmitTime) {
1038 // If we are goint backwards, reset previous emit time to current time.
1039 m_prevEmitTime = systemTime;
1040 } else {
1041 // Keep previous emitting time within max the life span.
1042 // This way emitting is reasonable also with big time jumps.
1043 const int maxLifeSpan = m_lifeSpan + m_lifeSpanVariation;
1044 m_prevEmitTime = std::max(m_prevEmitTime, systemTime - maxLifeSpan);
1045 }
1046
1047 // If bursts have changed, generate them first in the beginning
1048 if (!m_burstGenerated)
1049 generateEmitBursts();
1050
1051 int emitAmount = getEmitAmount() + getEmitAmountFromDynamicBursts();
1052
1053 // With lower emitRates, let timeChange grow until at least 1 particle is emitted
1054 if (emitAmount < 1)
1055 return;
1056
1057 QMatrix4x4 transform = calculateParticleTransform(parentNode(), m_systemSharedParent);
1058 QQuaternion rotation = calculateParticleRotation(parentNode(), m_systemSharedParent);
1059 QVector3D centerPos = position();
1060
1061 emitAmount = std::min(emitAmount, int(m_particle->maxAmount()));
1062 for (int i = 0; i < emitAmount; i++) {
1063 // Distribute evenly between previous and current time, important especially
1064 // when time has jumped a lot (like a starttime).
1065 float startTime = (m_prevEmitTime / 1000.0f) + (float(1+i) / emitAmount) * ((systemTime - m_prevEmitTime) / 1000.0f);
1066 emitParticle(m_particle, startTime, transform, rotation, centerPos);
1067 }
1068
1069 m_prevEmitTime = systemTime;
1070}
1071
1072void QQuick3DParticleEmitter::emitActivationNodeParticles(QQuick3DParticleModelBlendParticle *particle)
1073{
1074 QMatrix4x4 matrix = particle->activationNode()->sceneTransform();
1075 QMatrix4x4 actTransform = sceneTransform().inverted() * matrix;
1076 QVector3D front = actTransform.column(2).toVector3D();
1077 QVector3D pos = actTransform.column(3).toVector3D();
1078 float d = QVector3D::dotProduct(pos, front);
1079
1080 const int systemTime = m_system->currentTime();
1081
1082 // Keep previous emitting time within max the life span.
1083 // This way emitting is reasonable also with big time jumps.
1084 const int maxLifeSpan = m_lifeSpan + m_lifeSpanVariation;
1085 m_prevEmitTime = std::max(m_prevEmitTime, systemTime - maxLifeSpan);
1086
1087 float startTime = systemTime / 1000.0f;
1088
1089 QMatrix4x4 transform = calculateParticleTransform(parentNode(), m_systemSharedParent);
1090 QQuaternion rotation = calculateParticleRotation(parentNode(), m_systemSharedParent);
1091 QVector3D centerPos = position();
1092
1093 for (int i = 0; i < particle->maxAmount(); i++) {
1094 if (particle->m_particleData[i].startTime >= 0)
1095 continue;
1096 const QVector3D pc = particle->particleCenter(i);
1097 if (QVector3D::dotProduct(front, pc) - d > 0.0f)
1098 emitParticle(particle, startTime, transform, rotation, centerPos, i);
1099 }
1100
1101 m_prevEmitTime = systemTime;
1102}
1103
1104void QQuick3DParticleEmitter::componentComplete()
1105{
1106 if (!m_system && qobject_cast<QQuick3DParticleSystem *>(parentItem()))
1107 setSystem(qobject_cast<QQuick3DParticleSystem *>(parentItem()));
1108
1109 // When dynamically creating emitters, start from the current time.
1110 if (m_system)
1111 m_prevEmitTime = m_system->currentTime();
1112
1113 QQuick3DNode::componentComplete();
1114}
1115
1116// EmitBursts - list handling
1117
1118/*!
1119 \qmlproperty List<EmitBurst3D> ParticleEmitter3D::emitBursts
1120
1121 This property takes a list of \l EmitBurst3D elements, to declaratively define bursts.
1122 If the burst starting time, amount, and duration are known beforehand, it is better to
1123 use this property than e.g. calling \l burst() with a \l Timer.
1124
1125 For example, to emit 100 particles at the beginning, and 50 particles at 2 seconds:
1126
1127 \qml
1128 ParticleEmitter3D {
1129 emitBursts: [
1130 EmitBurst3D {
1131 time: 0
1132 amount: 100
1133 },
1134 EmitBurst3D {
1135 time: 2000
1136 amount: 50
1137 }
1138 ]
1139 }
1140 \endqml
1141
1142 \sa burst()
1143*/
1144QQmlListProperty<QQuick3DParticleEmitBurst> QQuick3DParticleEmitter::emitBursts()
1145{
1146 return {this, this,
1147 &QQuick3DParticleEmitter::appendEmitBurst,
1148 &QQuick3DParticleEmitter::emitBurstCount,
1149 &QQuick3DParticleEmitter::emitBurst,
1150 &QQuick3DParticleEmitter::clearEmitBursts,
1151 &QQuick3DParticleEmitter::replaceEmitBurst,
1152 &QQuick3DParticleEmitter::removeLastEmitBurst};
1153}
1154
1155void QQuick3DParticleEmitter::appendEmitBurst(QQuick3DParticleEmitBurst* n) {
1156 m_emitBursts.append(n);
1157}
1158
1159qsizetype QQuick3DParticleEmitter::emitBurstCount() const
1160{
1161 return m_emitBursts.size();
1162}
1163
1164QQuick3DParticleEmitBurst *QQuick3DParticleEmitter::emitBurst(qsizetype index) const
1165{
1166 return m_emitBursts.at(index);
1167}
1168
1169void QQuick3DParticleEmitter::clearEmitBursts() {
1170 m_emitBursts.clear();
1171}
1172
1173void QQuick3DParticleEmitter::replaceEmitBurst(qsizetype index, QQuick3DParticleEmitBurst *n)
1174{
1175 m_emitBursts[index] = n;
1176}
1177
1178void QQuick3DParticleEmitter::removeLastEmitBurst()
1179{
1180 m_emitBursts.removeLast();
1181}
1182
1183// EmitBursts - static
1184void QQuick3DParticleEmitter::appendEmitBurst(QQmlListProperty<QQuick3DParticleEmitBurst> *list, QQuick3DParticleEmitBurst *p) {
1185 reinterpret_cast< QQuick3DParticleEmitter *>(list->data)->appendEmitBurst(p);
1186}
1187
1188void QQuick3DParticleEmitter::clearEmitBursts(QQmlListProperty<QQuick3DParticleEmitBurst> *list) {
1189 reinterpret_cast< QQuick3DParticleEmitter *>(list->data)->clearEmitBursts();
1190}
1191
1192void QQuick3DParticleEmitter::replaceEmitBurst(QQmlListProperty<QQuick3DParticleEmitBurst> *list, qsizetype i, QQuick3DParticleEmitBurst *p)
1193{
1194 reinterpret_cast< QQuick3DParticleEmitter *>(list->data)->replaceEmitBurst(i, p);
1195}
1196
1197void QQuick3DParticleEmitter::removeLastEmitBurst(QQmlListProperty<QQuick3DParticleEmitBurst> *list)
1198{
1199 reinterpret_cast< QQuick3DParticleEmitter *>(list->data)->removeLastEmitBurst();
1200}
1201
1202QQuick3DParticleEmitBurst* QQuick3DParticleEmitter::emitBurst(QQmlListProperty<QQuick3DParticleEmitBurst> *list, qsizetype i) {
1203 return reinterpret_cast< QQuick3DParticleEmitter *>(list->data)->emitBurst(i);
1204}
1205
1206qsizetype QQuick3DParticleEmitter::emitBurstCount(QQmlListProperty<QQuick3DParticleEmitBurst> *list) {
1207 return reinterpret_cast< QQuick3DParticleEmitter *>(list->data)->emitBurstCount();
1208}
1209
1210QT_END_NAMESPACE
The QMatrix4x4 class represents a 4x4 transformation matrix in 3D space.
Definition qmatrix4x4.h:27
Combined button and popup list for selecting options.
static QVector3D reflect(const QVector3D &I, QVector3D &N)
static QMatrix4x4 rotationFromNormal(const QVector3D &n)