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