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