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

source code of qtquick3d/src/quick3dparticles/qquick3dparticleemitter.cpp