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
qquick3dparticlesystem.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
6#include <QtQuick3D/private/qquick3dquaternionutils_p.h>
12#include <private/qqmldelegatemodel_p.h>
17#include <QtQuick3DUtils/private/qquick3dprofiler_p.h>
18#include <qtquick3d_tracepoints_p.h>
19
20#include <QtGui/qquaternion.h>
21
22#include <cmath>
23
25
26/*!
27 \qmltype ParticleSystem3D
28 \inherits Node
29 \inqmlmodule QtQuick3D.Particles3D
30 \brief A system which includes particle, emitter, and affector types.
31 \since 6.2
32
33 This element is the root of the particle system, which handles the system timing and groups all
34 the other related elements like particles, emitters, and affectors together. To group the system
35 elements, they either need to be direct children of the ParticleSystem3D like this:
36
37 \qml,
38 ParticleSystem3D {
39 ParticleEmitter3D {
40 ...
41 }
42 SpriteParticle3D {
43 ...
44 }
45 }
46 \endqml
47
48 Or if the system elements are not direct children, they need to use \c system property to point
49 which ParticleSystem3D they belong to. Like this:
50
51 \qml
52 ParticleSystem3D {
53 id: psystem
54 }
55 ParticleEmitter3D {
56 system: psystem
57 ...
58 }
59 SpriteParticle3D {
60 system: psystem
61 ...
62 }
63 \endqml
64*/
65
68
69QQuick3DParticleSystem::QQuick3DParticleSystem(QQuick3DNode *parent)
70 : QQuick3DNode(parent)
71 , m_running(true)
72 , m_paused(false)
73 , m_initialized(false)
74 , m_componentComplete(false)
75 , m_animation(new QQuick3DParticleSystemAnimation(this))
76 , m_updateAnimation(new QQuick3DParticleSystemUpdate(this))
77 , m_logging(false)
78 , m_loggingData(new QQuick3DParticleSystemLogging(this))
79{
80 connect(m_loggingData, &QQuick3DParticleSystemLogging::loggingIntervalChanged, &m_loggingTimer, [this]() {
81 m_loggingTimer.setInterval(m_loggingData->m_loggingInterval);
82 });
83}
84
85QQuick3DParticleSystem::~QQuick3DParticleSystem()
86{
87 m_animation->stop();
88 m_updateAnimation->stop();
89
90 for (const auto &connection : std::as_const(m_connections))
91 QObject::disconnect(connection);
92 // purposeful copy
93 const auto particles = m_particles;
94 const auto emitters = m_emitters;
95 const auto trailEmitters = m_trailEmitters;
96 const auto affectors = m_affectors;
97 for (auto *particle : particles)
98 particle->setSystem(nullptr);
99 for (auto *emitter : emitters)
100 emitter->setSystem(nullptr);
101 for (auto *emitter : trailEmitters)
102 emitter->setSystem(nullptr);
103 for (auto *affector : affectors)
104 affector->setSystem(nullptr);
105}
106
107/*!
108 \qmlproperty bool ParticleSystem3D::running
109
110 This property defines if system is currently running. If running is set to \c false,
111 the particle system will stop the simulation. All particles will be destroyed when
112 the system is set to running again.
113
114 Running should be set to \c false when manually modifying/animating the \l {ParticleSystem3D::time}{time} property.
115
116 The default value is \c true.
117*/
118bool QQuick3DParticleSystem::isRunning() const
119{
120 return m_running;
121}
122
123/*!
124 \qmlproperty bool ParticleSystem3D::paused
125
126 This property defines if system is currently paused. If paused is set to \c true, the
127 particle system will not advance the simulation. When paused is set to \c false again,
128 the simulation will resume from the same point where it was paused.
129
130 The default value is \c false.
131*/
132bool QQuick3DParticleSystem::isPaused() const
133{
134 return m_paused;
135}
136
137/*!
138 \qmlproperty int ParticleSystem3D::startTime
139
140 This property defines time in milliseconds where the system starts. This can be useful
141 to warm up the system so that a set of particles has already been emitted. If for example
142 \l startTime is set to 2000 and system \l time is animating from 0 to 1000, actually
143 animation shows particles from 2000 to 3000ms.
144
145 The default value is \c 0.
146*/
147int QQuick3DParticleSystem::startTime() const
148{
149 return m_startTime;
150}
151
152/*!
153 \qmlproperty int ParticleSystem3D::time
154
155 This property defines time in milliseconds for the system.
156 \note When modifying the time property, \l {ParticleSystem3D::running}{running}
157 should usually be set to \c false.
158
159 Here is an example how to manually animate the system for 3 seconds, in a loop, at half speed:
160
161 \qml
162 ParticleSystem3D {
163 running: false
164 NumberAnimation on time {
165 loops: Animation.Infinite
166 from: 0
167 to: 3000
168 duration: 6000
169 }
170 }
171 \endqml
172*/
173int QQuick3DParticleSystem::time() const
174{
175 return m_time;
176}
177
178/*!
179 \qmlproperty bool ParticleSystem3D::useRandomSeed
180
181 This property defines if particle system seed should be random or user defined.
182 When \c true, a new random value for \l {ParticleSystem3D::seed}{seed} is generated every time particle
183 system is restarted.
184
185 The default value is \c true.
186
187 \note This property should not be modified during the particle animations.
188
189 \sa seed
190*/
191bool QQuick3DParticleSystem::useRandomSeed() const
192{
193 return m_useRandomSeed;
194}
195
196/*!
197 \qmlproperty int ParticleSystem3D::seed
198
199 This property defines the seed value used for particles randomization. With the same seed,
200 particles effect will be identical on every run. This is useful when deterministic behavior
201 is desired over random behavior.
202
203 The default value is \c 0 when \l {ParticleSystem3D::useRandomSeed}{useRandomSeed} is set to
204 \c false, and something in between \c 1..INT32_MAX when \l {ParticleSystem3D::useRandomSeed}{useRandomSeed}
205 is set to \c true.
206
207 \note This property should not be modified during the particle animations.
208
209 \sa useRandomSeed
210*/
211int QQuick3DParticleSystem::seed() const
212{
213 return m_seed;
214}
215
216/*!
217 \qmlproperty bool ParticleSystem3D::logging
218
219 Set this to true to collect \l {ParticleSystem3D::loggingData}{loggingData}.
220
221 \note This property has some performance impact, so it should not be enabled in releases.
222
223 The default value is \c false.
224
225 \sa loggingData
226*/
227bool QQuick3DParticleSystem::logging() const
228{
229 return m_logging;
230}
231
232/*!
233 \qmlproperty ParticleSystem3DLogging ParticleSystem3D::loggingData
234 \readonly
235
236 This property contains logging data which can be useful when developing and optimizing
237 the particle effects.
238
239 \note This property contains correct data only when \l {ParticleSystem3D::logging}{logging} is set
240 to \c true and particle system is running.
241
242 \sa logging
243*/
244QQuick3DParticleSystemLogging *QQuick3DParticleSystem::loggingData() const
245{
246 return m_loggingData;
247}
248
249/*!
250 \qmlmethod ParticleSystem3D::reset()
251
252 This method resets the internal state of the particle system to it's initial state.
253 This can be used when \l running property is \c false to reset the system.
254 The \l running is \c true this method does not need to be called as the system is managing
255 the internal state, but when it is \c false the system needs to be told when the system should
256 be reset.
257*/
258void QQuick3DParticleSystem::reset()
259{
260 for (auto emitter : std::as_const(m_emitters))
261 emitter->reset();
262 for (auto emitter : std::as_const(m_trailEmitters))
263 emitter->reset();
264 for (auto particle : std::as_const(m_particles))
265 particle->reset();
266 m_particleIdIndex = 0;
267}
268
269/*!
270 Returns the current time of the system (m_time + m_startTime).
271 \internal
272*/
273int QQuick3DParticleSystem::currentTime() const
274{
275 return m_currentTime;
276}
277
278void QQuick3DParticleSystem::setRunning(bool running)
279{
280 if (m_running != running) {
281 m_running = running;
282 Q_EMIT runningChanged();
283 setPaused(false);
284
285 if (m_running)
286 reset();
287
288 if (m_componentComplete && !m_running && m_useRandomSeed)
289 doSeedRandomization();
290
291 (m_running && !isEditorModeOn()) ? m_animation->start() : m_animation->stop();
292 }
293}
294
295void QQuick3DParticleSystem::setPaused(bool paused)
296{
297 if (m_paused != paused) {
298 m_paused = paused;
299 if (m_animation->state() != QAbstractAnimation::Stopped)
300 m_paused ? m_animation->pause() : m_animation->resume();
301 Q_EMIT pausedChanged();
302 }
303}
304
305void QQuick3DParticleSystem::setStartTime(int startTime)
306{
307 if (m_startTime == startTime)
308 return;
309
310 m_startTime = startTime;
311 m_updateAnimation->setDirty(true);
312 Q_EMIT startTimeChanged();
313}
314
315void QQuick3DParticleSystem::setTime(int time)
316{
317 if (m_time == time)
318 return;
319
320 // Update the time and mark the system dirty
321 m_time = time;
322 m_updateAnimation->setDirty(true);
323
324 Q_EMIT timeChanged();
325}
326
327void QQuick3DParticleSystem::setUseRandomSeed(bool randomize)
328{
329 if (m_useRandomSeed == randomize)
330 return;
331
332 m_useRandomSeed = randomize;
333 // When set to true, random values are recalculated with a random seed
334 // and random values will become independent of particle index when possible.
335 if (m_useRandomSeed)
336 doSeedRandomization();
337 m_rand.setDeterministic(!m_useRandomSeed);
338 Q_EMIT useRandomSeedChanged();
339}
340
341void QQuick3DParticleSystem::setSeed(int seed)
342{
343 if (m_seed == seed)
344 return;
345
346 m_seed = seed;
347 m_rand.init(m_seed);
348 Q_EMIT seedChanged();
349}
350
351void QQuick3DParticleSystem::setLogging(bool logging)
352{
353 if (m_logging == logging)
354 return;
355
356 m_logging = logging;
357
358 resetLoggingVariables();
359 m_loggingData->resetData();
360
361 if (m_logging)
362 m_loggingTimer.start();
363 else
364 m_loggingTimer.stop();
365
366 Q_EMIT loggingChanged();
367}
368
369/*!
370 Set editor time which in editor mode overwrites the time.
371 \internal
372*/
373void QQuick3DParticleSystem::setEditorTime(int time)
374{
375 if (m_editorTime == time)
376 return;
377
378 // Update the time and mark the system dirty
379 m_editorTime = time;
380 m_updateAnimation->setDirty(true);
381}
382
383void QQuick3DParticleSystem::componentComplete()
384{
385 QQuick3DNode::componentComplete();
386 m_componentComplete = true;
387 m_updateAnimation->start();
388
389 connect(&m_loggingTimer, &QTimer::timeout, this, &QQuick3DParticleSystem::updateLoggingData);
390 m_loggingTimer.setInterval(m_loggingData->m_loggingInterval);
391
392 if (m_useRandomSeed)
393 doSeedRandomization();
394 else
395 m_rand.init(m_seed);
396
397 m_time = 0;
398 m_currentTime = 0;
399 m_editorTime = 0;
400
401 Q_EMIT timeChanged();
402
403 // Reset restarts the animation (if running)
404 if (m_animation->state() == QAbstractAnimation::Running)
405 m_animation->stop();
406 if (m_running && !isEditorModeOn())
407 m_animation->start();
408 if (m_paused)
409 m_animation->pause();
410
411 m_initialized = true;
412}
413
414void QQuick3DParticleSystem::refresh()
415{
416 // If the system isn't running, force refreshing by calling update
417 // with the current time. QAbstractAnimation::setCurrentTime() implementation
418 // always calls updateCurrentTime() even if the time would remain the same.
419 if (!m_running || m_paused || isEditorModeOn())
420 m_animation->setCurrentTime(isEditorModeOn() ? m_editorTime : m_time);
421}
422
423void QQuick3DParticleSystem::markDirty()
424{
425 // Mark the system dirty so things are updated at the next frame.
426 m_updateAnimation->setDirty(true);
427}
428
429int QQuick3DParticleSystem::particleCount() const
430{
431 int pCount = 0;
432 for (auto particle : std::as_const(m_particles))
433 pCount += particle->maxAmount();
434 return pCount;
435}
436
437void QQuick3DParticleSystem::registerParticle(QQuick3DParticle *particle)
438{
439 auto *model = qobject_cast<QQuick3DParticleModelParticle *>(particle);
440 if (model) {
441 registerParticleModel(model);
442 return;
443 }
444 auto *sprite = qobject_cast<QQuick3DParticleSpriteParticle *>(particle);
445 if (sprite) {
446 registerParticleSprite(sprite);
447 return;
448 }
449 m_particles << particle;
450}
451
452void QQuick3DParticleSystem::registerParticleModel(QQuick3DParticleModelParticle *m)
453{
454 m_particles << m;
455}
456
457void QQuick3DParticleSystem::registerParticleSprite(QQuick3DParticleSpriteParticle *m)
458{
459 m_particles << m;
460}
461
462void QQuick3DParticleSystem::unRegisterParticle(QQuick3DParticle *particle)
463{
464 auto *model = qobject_cast<QQuick3DParticleModelParticle *>(particle);
465 if (model) {
466 m_particles.removeAll(particle);
467 return;
468 }
469 auto *sprite = qobject_cast<QQuick3DParticleSpriteParticle *>(particle);
470 if (sprite) {
471 m_particles.removeAll(particle);
472 return;
473 }
474
475 m_particles.removeAll(particle);
476}
477
478void QQuick3DParticleSystem::registerParticleEmitter(QQuick3DParticleEmitter *e)
479{
480 auto te = qobject_cast<QQuick3DParticleTrailEmitter *>(e);
481 if (te)
482 m_trailEmitters << te;
483 else
484 m_emitters << e;
485}
486
487void QQuick3DParticleSystem::unRegisterParticleEmitter(QQuick3DParticleEmitter *e)
488{
489 auto te = qobject_cast<QQuick3DParticleTrailEmitter *>(e);
490 if (te)
491 m_trailEmitters.removeAll(te);
492 else
493 m_emitters.removeAll(e);
494}
495
496void QQuick3DParticleSystem::registerParticleAffector(QQuick3DParticleAffector *a)
497{
498 m_affectors << a;
499 m_connections.insert(a, connect(a, &QQuick3DParticleAffector::update, this, &QQuick3DParticleSystem::markDirty));
500}
501
502void QQuick3DParticleSystem::unRegisterParticleAffector(QQuick3DParticleAffector *a)
503{
504 QObject::disconnect(m_connections[a]);
505 m_connections.remove(a);
506 m_affectors.removeAll(a);
507}
508
509void QQuick3DParticleSystem::updateCurrentTime(int currentTime)
510{
511 if (!m_initialized || isGloballyDisabled() || (isEditorModeOn() && !visible()))
512 return;
513
514 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DParticleUpdate);
515
516 Q_TRACE(QSSG_particleUpdate_entry);
517
518 m_currentTime = currentTime;
519 const float timeS = float(m_currentTime / 1000.0f);
520
521 m_particlesMax = 0;
522 m_particlesUsed = 0;
523 m_updates++;
524
525 m_perfTimer.start();
526
527 // Emit new particles
528 for (auto emitter : std::as_const(m_emitters))
529 emitter->emitParticles();
530
531 // Prepare Affectors
532 for (auto affector : std::as_const(m_affectors)) {
533 if (affector->m_enabled)
534 affector->prepareToAffect();
535 }
536
537 // Animate current particles
538 for (auto particle : std::as_const(m_particles)) {
539
540 // Collect possible trail emits
541 QVector<TrailEmits> trailEmits;
542 for (auto emitter : std::as_const(m_trailEmitters)) {
543 if (emitter->follow() == particle) {
544 int emitAmount = emitter->getEmitAmount();
545 if (emitAmount > 0 || emitter->hasBursts()) {
546 TrailEmits e;
547 e.emitter = emitter;
548 e.amount = emitAmount;
549 trailEmits << e;
550 }
551 }
552 }
553
554 m_particlesMax += particle->maxAmount();
555
556 QQuick3DParticleSpriteParticle *spriteParticle = qobject_cast<QQuick3DParticleSpriteParticle *>(particle);
557 if (spriteParticle) {
558 processSpriteParticle(spriteParticle, trailEmits, timeS);
559 continue;
560 }
561 QQuick3DParticleModelParticle *modelParticle = qobject_cast<QQuick3DParticleModelParticle *>(particle);
562 if (modelParticle) {
563 processModelParticle(modelParticle, trailEmits, timeS);
564 continue;
565 }
566 QQuick3DParticleModelBlendParticle *mbp = qobject_cast<QQuick3DParticleModelBlendParticle *>(particle);
567 if (mbp) {
568 processModelBlendParticle(mbp, trailEmits, timeS);
569 continue;
570 }
571 }
572
573 // Clear bursts from trailemitters
574 for (auto emitter : std::as_const(m_trailEmitters))
575 emitter->clearBursts();
576
577 m_timeAnimation += m_perfTimer.nsecsElapsed();
578 m_updateAnimation->setDirty(false);
579 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DParticleUpdate, m_particlesUsed, Q_QUICK3D_PROFILE_GET_ID(this));
580
581 Q_TRACE(QSSG_particleUpdate_exit, m_particlesUsed);
582
583}
584
585void QQuick3DParticleSystem::processModelParticle(QQuick3DParticleModelParticle *modelParticle, const QVector<TrailEmits> &trailEmits, float timeS)
586{
587 modelParticle->clearInstanceTable();
588
589 const int c = modelParticle->maxAmount();
590
591 for (int i = 0; i < c; i++) {
592 const auto d = &modelParticle->m_particleData.at(i);
593
594 const float particleTimeEnd = d->startTime + d->lifetime;
595
596 if (timeS < d->startTime || timeS > particleTimeEnd) {
597 if (timeS > particleTimeEnd && d->lifetime > 0.0f) {
598 const auto pos = d->reversed ? d->startPosition : d->startPosition + (d->startVelocity * (particleTimeEnd - d->startTime));
599 for (auto trailEmit : std::as_const(trailEmits))
600 trailEmit.emitter->emitTrailParticles(pos, 0, QQuick3DParticleDynamicBurst::TriggerEnd, d->surfaceNormal, d->startVelocity.normalized());
601 }
602 // Particle not alive currently
603 continue;
604 }
605
606 QQuick3DParticleDataCurrent currentData;
607 if (timeS >= d->startTime && d->lifetime <= 0.0f) {
608 for (auto trailEmit : std::as_const(trailEmits))
609 trailEmit.emitter->emitTrailParticles(d->startPosition, 0, QQuick3DParticleDynamicBurst::TriggerStart, d->surfaceNormal, d->startVelocity.normalized());
610 }
611
612 // Adjust time for reversed particles
613 const float particleTimeS = d->reversed ? particleTimeEnd - timeS : timeS - d->startTime;
614
615 // Process features shared for both model & sprite particles
616 processParticleCommon(currentData, d, particleTimeS);
617
618 // Add a base rotation if alignment requested
619 if (modelParticle->m_alignMode != QQuick3DParticle::AlignNone)
620 processParticleAlignment(currentData, modelParticle, d);
621
622 // 0.0 -> 1.0 during the particle lifetime
623 const float timeChange = std::max(0.0f, std::min(1.0f, particleTimeS / d->lifetime));
624
625 // Scale from initial to endScale
626 currentData.scale = modelParticle->m_initialScale * (d->endSize * timeChange + d->startSize * (1.0f - timeChange));
627
628 // Fade in & out
629 const float particleTimeLeftS = d->lifetime - particleTimeS;
630 processParticleFadeInOut(currentData, modelParticle, particleTimeS, particleTimeLeftS);
631
632 // Affectors
633 for (auto affector : std::as_const(m_affectors)) {
634 // If affector is set to affect only particular particles, check these are included
635 if (affector->m_enabled && (affector->m_particles.isEmpty() || affector->m_particles.contains(modelParticle)))
636 affector->affectParticle(*d, &currentData, particleTimeS);
637 }
638
639 // Emit new particles from trails
640 for (auto trailEmit : std::as_const(trailEmits))
641 trailEmit.emitter->emitTrailParticles(currentData.position, trailEmit.amount, QQuick3DParticleDynamicBurst::TriggerTime, d->surfaceNormal, d->startVelocity.normalized());
642
643 const QColor color(currentData.color.r, currentData.color.g, currentData.color.b, currentData.color.a);
644 // Set current particle properties
645 modelParticle->addInstance(currentData.position, currentData.scale, currentData.rotation, color, timeChange);
646 }
647 modelParticle->commitInstance();
648}
649
650static QVector3D mix(const QVector3D &a, const QVector3D &b, float f)
651{
652 return (b - a) * f + a;
653}
654
655void QQuick3DParticleSystem::processModelBlendParticle(QQuick3DParticleModelBlendParticle *particle, const QVector<TrailEmits> &trailEmits, float timeS)
656{
657 const int c = particle->maxAmount();
658
659 for (int i = 0; i < c; i++) {
660 const auto d = &particle->m_particleData.at(i);
661
662 const float particleTimeEnd = d->startTime + d->lifetime;
663
664 if (timeS < d->startTime || timeS > particleTimeEnd) {
665 if (timeS > particleTimeEnd && d->lifetime > 0.0f) {
666 const auto pos = d->reversed ? d->startPosition : d->startPosition + (d->startVelocity * (particleTimeEnd - d->startTime));
667 for (auto trailEmit : std::as_const(trailEmits))
668 trailEmit.emitter->emitTrailParticles(pos, 0, QQuick3DParticleDynamicBurst::TriggerEnd, d->surfaceNormal, d->startVelocity.normalized());
669 }
670 // Particle not alive currently
671 float age = 0.0f;
672 float size = 0.0f;
673 QVector3D pos;
674 QVector3D rot;
675 QVector4D color(float(d->startColor.r)/ 255.0f,
676 float(d->startColor.g)/ 255.0f,
677 float(d->startColor.b)/ 255.0f,
678 float(d->startColor.a)/ 255.0f);
679 if (d->startTime > 0.0f && timeS > particleTimeEnd
680 && (particle->modelBlendMode() == QQuick3DParticleModelBlendParticle::Construct ||
681 particle->modelBlendMode() == QQuick3DParticleModelBlendParticle::Transfer)) {
682 age = 1.0f;
683 size = 1.0f;
684 pos = particle->particleEndPosition(i);
685 rot = particle->particleEndRotation(i);
686 if (particle->fadeOutEffect() == QQuick3DParticle::FadeOpacity)
687 color.setW(0.0f);
688 } else if (particle->modelBlendMode() == QQuick3DParticleModelBlendParticle::Explode ||
689 particle->modelBlendMode() == QQuick3DParticleModelBlendParticle::Transfer) {
690 age = 0.0f;
691 size = 1.0f;
692 pos = particle->particleCenter(i);
693 if (particle->fadeInEffect() == QQuick3DParticle::FadeOpacity)
694 color.setW(0.0f);
695 }
696 particle->setParticleData(i, pos, rot, color, size, age);
697 continue;
698 }
699
700 QQuick3DParticleDataCurrent currentData;
701 if (timeS >= d->startTime && d->lifetime <= 0.0f) {
702 for (auto trailEmit : std::as_const(trailEmits))
703 trailEmit.emitter->emitTrailParticles(d->startPosition, 0, QQuick3DParticleDynamicBurst::TriggerStart, d->surfaceNormal, d->startVelocity.normalized());
704 }
705
706 // Adjust time for reversed particles
707 const float particleTimeS = d->reversed ? particleTimeEnd - timeS : timeS - d->startTime;
708
709 // Process features shared for both model & sprite particles
710 processParticleCommon(currentData, d, particleTimeS);
711
712 // 0.0 -> 1.0 during the particle lifetime
713 const float timeChange = std::max(0.0f, std::min(1.0f, particleTimeS / d->lifetime));
714
715 // Scale from initial to endScale
716 const float scale = d->endSize * timeChange + d->startSize * (1.0f - timeChange);
717 currentData.scale = QVector3D(scale, scale, scale);
718
719 // Fade in & out
720 const float particleTimeLeftS = d->lifetime - particleTimeS;
721 processParticleFadeInOut(currentData, particle, particleTimeS, particleTimeLeftS);
722
723 // Affectors
724 for (auto affector : std::as_const(m_affectors)) {
725 // If affector is set to affect only particular particles, check these are included
726 if (affector->m_enabled && (affector->m_particles.isEmpty() || affector->m_particles.contains(particle)))
727 affector->affectParticle(*d, &currentData, particleTimeS);
728 }
729
730 // Emit new particles from trails
731 for (auto trailEmit : std::as_const(trailEmits))
732 trailEmit.emitter->emitTrailParticles(currentData.position, trailEmit.amount, QQuick3DParticleDynamicBurst::TriggerTime, d->surfaceNormal, d->startVelocity.normalized());
733
734 // Set current particle properties
735 const QVector4D color(float(currentData.color.r) / 255.0f,
736 float(currentData.color.g) / 255.0f,
737 float(currentData.color.b) / 255.0f,
738 float(currentData.color.a) / 255.0f);
739 float endTimeS = particle->endTime() * 0.001f;
740 if ((particle->modelBlendMode() == QQuick3DParticleModelBlendParticle::Construct ||
741 particle->modelBlendMode() == QQuick3DParticleModelBlendParticle::Transfer)
742 && particleTimeLeftS < endTimeS) {
743 QVector3D endPosition = particle->particleEndPosition(i);
744 QVector3D endRotation = particle->particleEndRotation(i);
745 float factor = 1.0f - particleTimeLeftS / endTimeS;
746 currentData.position = mix(currentData.position, endPosition, factor);
747 currentData.rotation = mix(currentData.rotation, endRotation, factor);
748 }
749 particle->setParticleData(i, currentData.position, currentData.rotation,
750 color, currentData.scale.x(), timeChange);
751 }
752 particle->commitParticles();
753}
754
755void QQuick3DParticleSystem::processSpriteParticle(QQuick3DParticleSpriteParticle *spriteParticle, const QVector<TrailEmits> &trailEmits, float timeS)
756{
757 const int c = spriteParticle->maxAmount();
758
759 for (int i = 0; i < c; i++) {
760 const auto d = &spriteParticle->m_particleData.at(i);
761
762 const float particleTimeEnd = d->startTime + d->lifetime;
763 auto &particleData = spriteParticle->m_spriteParticleData[i];
764 if (timeS < d->startTime || timeS > particleTimeEnd) {
765 if (timeS > particleTimeEnd && particleData.age > 0.0f) {
766 const auto pos = d->reversed ? d->startPosition : d->startPosition + (d->startVelocity * (particleTimeEnd - d->startTime));
767 for (auto trailEmit : std::as_const(trailEmits))
768 trailEmit.emitter->emitTrailParticles(pos, 0, QQuick3DParticleDynamicBurst::TriggerEnd, d->surfaceNormal, d->startVelocity.normalized());
769 auto *lineParticle = qobject_cast<QQuick3DParticleLineParticle *>(spriteParticle);
770 if (lineParticle)
771 lineParticle->saveLineSegment(i, timeS);
772 }
773 // Particle not alive currently
774 spriteParticle->resetParticleData(i);
775 continue;
776 }
777
778 QQuick3DParticleDataCurrent currentData;
779 if (timeS >= d->startTime && timeS < particleTimeEnd && particleData.age == 0.0f) {
780 for (auto trailEmit : std::as_const(trailEmits))
781 trailEmit.emitter->emitTrailParticles(d->startPosition, 0, QQuick3DParticleDynamicBurst::TriggerStart, d->surfaceNormal, d->startVelocity.normalized());
782 }
783
784 // Adjust time for reversed particles
785 const float particleTimeS = d->reversed ? particleTimeEnd - timeS : timeS - d->startTime;
786
787 // Process features shared for both model & sprite particles
788 processParticleCommon(currentData, d, particleTimeS);
789
790 // Add a base rotation if alignment requested
791 if (!spriteParticle->m_billboard && spriteParticle->m_alignMode != QQuick3DParticle::AlignNone)
792 processParticleAlignment(currentData, spriteParticle, d);
793
794 // 0.0 -> 1.0 during the particle lifetime
795 const float timeChange = std::max(0.0f, std::min(1.0f, particleTimeS / d->lifetime));
796
797 // Scale from initial to endScale
798 const float scale = d->endSize * timeChange + d->startSize * (1.0f - timeChange);
799 currentData.scale = QVector3D(scale, scale, scale);
800
801 // Fade in & out
802 const float particleTimeLeftS = d->lifetime - particleTimeS;
803 processParticleFadeInOut(currentData, spriteParticle, particleTimeS, particleTimeLeftS);
804
805 float animationFrame = 0.0f;
806 if (auto sequence = spriteParticle->m_spriteSequence) {
807 // animationFrame range is [0..1) where 0.0 is the beginning of the first frame
808 // and 0.9999 is the end of the last frame.
809 const bool isSingleFrame = (sequence->animationDirection() == QQuick3DParticleSpriteSequence::SingleFrame);
810 float startFrame = sequence->firstFrame(d->index, isSingleFrame);
811 if (sequence->animationDirection() == QQuick3DParticleSpriteSequence::Normal) {
812 animationFrame = fmodf(startFrame + particleTimeS / d->animationTime, 1.0f);
813 } else if (sequence->animationDirection() == QQuick3DParticleSpriteSequence::Reverse) {
814 animationFrame = fmodf(startFrame + 0.9999f - fmodf(particleTimeS / d->animationTime, 1.0f), 1.0f);
815 } else if (sequence->animationDirection() == QQuick3DParticleSpriteSequence::Alternate) {
816 animationFrame = startFrame + particleTimeS / d->animationTime;
817 animationFrame = fabsf(fmodf(1.0f + animationFrame, 2.0f) - 1.0f);
818 } else if (sequence->animationDirection() == QQuick3DParticleSpriteSequence::AlternateReverse) {
819 animationFrame = fmodf(startFrame + 0.9999f, 1.0f) - particleTimeS / d->animationTime;
820 animationFrame = fabsf(fmodf(fabsf(1.0f + animationFrame), 2.0f) - 1.0f);
821 } else {
822 // SingleFrame
823 animationFrame = startFrame;
824 }
825 animationFrame = std::clamp(animationFrame, 0.0f, 0.9999f);
826 }
827
828 // Affectors
829 for (auto affector : std::as_const(m_affectors)) {
830 // If affector is set to affect only particular particles, check these are included
831 if (affector->m_enabled && (affector->m_particles.isEmpty() || affector->m_particles.contains(spriteParticle)))
832 affector->affectParticle(*d, &currentData, particleTimeS);
833 }
834
835 // Emit new particles from trails
836 for (auto trailEmit : std::as_const(trailEmits))
837 trailEmit.emitter->emitTrailParticles(currentData.position, trailEmit.amount, QQuick3DParticleDynamicBurst::TriggerTime, d->surfaceNormal, d->startVelocity.normalized());
838
839
840 // Set current particle properties
841 const QVector4D color(float(currentData.color.r) / 255.0f,
842 float(currentData.color.g) / 255.0f,
843 float(currentData.color.b) / 255.0f,
844 float(currentData.color.a) / 255.0f);
845 const QVector3D offset(spriteParticle->offsetX(), spriteParticle->offsetY(), 0);
846 spriteParticle->setParticleData(i, currentData.position + (offset * currentData.scale.x()),
847 currentData.rotation, color, currentData.scale.x(), timeChange,
848 animationFrame);
849 }
850 spriteParticle->commitParticles(timeS);
851}
852
853void QQuick3DParticleSystem::processParticleCommon(QQuick3DParticleDataCurrent &currentData, const QQuick3DParticleData *d, float particleTimeS)
854{
855 m_particlesUsed++;
856
857 currentData.position = d->startPosition;
858
859 // Initial color from start color
860 currentData.color = d->startColor;
861
862 // Initial position from start velocity
863 currentData.position += d->startVelocity * particleTimeS;
864
865 // Initial rotation from start velocity
866 constexpr float step = 360.0f / 127.0f;
867 currentData.rotation = QVector3D(
868 d->startRotation.x * step + abs(d->startRotationVelocity.x) * d->startRotationVelocity.x * particleTimeS,
869 d->startRotation.y * step + abs(d->startRotationVelocity.y) * d->startRotationVelocity.y * particleTimeS,
870 d->startRotation.z * step + abs(d->startRotationVelocity.z) * d->startRotationVelocity.z * particleTimeS);
871}
872
873void QQuick3DParticleSystem::processParticleFadeInOut(QQuick3DParticleDataCurrent &currentData, const QQuick3DParticle *particle, float particleTimeS, float particleTimeLeftS)
874{
875 const float fadeInS = particle->m_fadeInDuration / 1000.0f;
876 const float fadeOutS = particle->m_fadeOutDuration / 1000.0f;
877 if (particleTimeS < fadeInS) {
878 // 0.0 -> 1.0 during the particle fadein
879 const float fadeIn = particleTimeS / fadeInS;
880 if (particle->m_fadeInEffect == QQuick3DParticleModelParticle::FadeOpacity)
881 currentData.color.a *= fadeIn;
882 else if (particle->m_fadeInEffect == QQuick3DParticleModelParticle::FadeScale)
883 currentData.scale *= fadeIn;
884 }
885 if (particleTimeLeftS < fadeOutS) {
886 // 1.0 -> 0.0 during the particle fadeout
887 const float fadeOut = particleTimeLeftS / fadeOutS;
888 if (particle->m_fadeOutEffect == QQuick3DParticleModelParticle::FadeOpacity)
889 currentData.color.a *= fadeOut;
890 else if (particle->m_fadeOutEffect == QQuick3DParticleModelParticle::FadeScale)
891 currentData.scale *= fadeOut;
892 }
893}
894
895void QQuick3DParticleSystem::processParticleAlignment(QQuick3DParticleDataCurrent &currentData, const QQuick3DParticle *particle, const QQuick3DParticleData *d)
896{
897 if (particle->m_alignMode == QQuick3DParticle::AlignTowardsTarget) {
898 QQuaternion alignQuat = QQuick3DQuaternionUtils::lookAt(particle->alignTargetPosition(), currentData.position);
899 currentData.rotation = (alignQuat * QQuaternion::fromEulerAngles(currentData.rotation)).toEulerAngles();
900 } else if (particle->m_alignMode == QQuick3DParticle::AlignTowardsStartVelocity) {
901 QQuaternion alignQuat = QQuick3DQuaternionUtils::lookAt(d->startVelocity, QVector3D());
902 currentData.rotation = (alignQuat * QQuaternion::fromEulerAngles(currentData.rotation)).toEulerAngles();
903 }
904}
905
906bool QQuick3DParticleSystem::isGloballyDisabled()
907{
908 static const bool disabled = qEnvironmentVariableIntValue("QT_QUICK3D_DISABLE_PARTICLE_SYSTEMS");
909 return disabled;
910}
911
912bool QQuick3DParticleSystem::isEditorModeOn()
913{
914 static const bool editorMode = qEnvironmentVariableIntValue("QT_QUICK3D_EDITOR_PARTICLE_SYSTEMS");
915 return editorMode;
916}
917
918void QQuick3DParticleSystem::updateLoggingData()
919{
920 if (m_updates == 0)
921 return;
922
923 if (m_loggingData->m_particlesMax != m_particlesMax) {
924 m_loggingData->m_particlesMax = m_particlesMax;
925 Q_EMIT m_loggingData->particlesMaxChanged();
926 }
927 if (m_loggingData->m_particlesUsed != m_particlesUsed) {
928 m_loggingData->m_particlesUsed = m_particlesUsed;
929 Q_EMIT m_loggingData->particlesUsedChanged();
930 }
931 if (m_loggingData->m_updates != m_updates) {
932 m_loggingData->m_updates = m_updates;
933 Q_EMIT m_loggingData->updatesChanged();
934 }
935
936 m_loggingData->updateTimes(m_timeAnimation);
937
938 Q_EMIT loggingDataChanged();
939 resetLoggingVariables();
940}
941
942void QQuick3DParticleSystem::resetLoggingVariables()
943{
944 m_particlesMax = 0;
945 m_particlesUsed = 0;
946 m_updates = 0;
947 m_timeAnimation = 0;
948}
949
950QPRand *QQuick3DParticleSystem::rand()
951{
952 return &m_rand;
953}
954
955void QQuick3DParticleSystem::doSeedRandomization()
956{
957 // Random 1..INT32_MAX, making sure seed changes from the initial 0.
958 setSeed(QRandomGenerator::global()->bounded(1 + (INT32_MAX - 1)));
959}
960
961bool QQuick3DParticleSystem::isShared(const QQuick3DParticle *particle) const
962{
963 int count = 0;
964 for (auto emitter : std::as_const(m_emitters)) {
965 count += emitter->particle() == particle;
966 if (count > 1)
967 return true;
968 }
969 for (auto emitter : std::as_const(m_trailEmitters)) {
970 count += emitter->particle() == particle;
971 if (count > 1)
972 return true;
973 }
974 return false;
975}
976
977QT_END_NAMESPACE
Q_TRACE_POINT(qtcore, QCoreApplication_postEvent_exit)
Q_TRACE_POINT(qtquick3d, QSSG_particleUpdate_exit, int particleCount)
static QVector3D mix(const QVector3D &a, const QVector3D &b, float f)