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 (const auto &connection : std::as_const(m_connections))
97 QObject::disconnect(connection);
98 // purposeful copy
99 const auto particles = m_particles;
100 const auto emitters = m_emitters;
101 const auto trailEmitters = m_trailEmitters;
102 const auto affectors = 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[a]);
480 m_connections.remove(a);
481 m_affectors.removeAll(a);
482}
483
484void QQuick3DParticleSystem::updateCurrentTime(int currentTime)
485{
486 if (!m_initialized || isGloballyDisabled() || (isEditorModeOn() && !visible()))
487 return;
488
489 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DParticleUpdate);
490
491 Q_TRACE(QSSG_particleUpdate_entry);
492
493 m_currentTime = currentTime;
494 const float timeS = float(m_currentTime / 1000.0f);
495
496 m_particlesMax = 0;
497 m_particlesUsed = 0;
498 m_updates++;
499
500 m_perfTimer.start();
501
502 // Emit new particles
503 for (auto emitter : std::as_const(m_emitters))
504 emitter->emitParticles();
505
506 // Prepare Affectors
507 for (auto affector : std::as_const(m_affectors)) {
508 if (affector->m_enabled)
509 affector->prepareToAffect();
510 }
511
512 // Animate current particles
513 for (auto particle : std::as_const(m_particles)) {
514
515 // Collect possible trail emits
516 QVector<TrailEmits> trailEmits;
517 for (auto emitter : std::as_const(m_trailEmitters)) {
518 if (emitter->follow() == particle) {
519 int emitAmount = emitter->getEmitAmount();
520 if (emitAmount > 0 || emitter->hasBursts()) {
521 TrailEmits e;
522 e.emitter = emitter;
523 e.amount = emitAmount;
524 trailEmits << e;
525 }
526 }
527 }
528
529 m_particlesMax += particle->maxAmount();
530
531 QQuick3DParticleSpriteParticle *spriteParticle = qobject_cast<QQuick3DParticleSpriteParticle *>(particle);
532 if (spriteParticle) {
533 processSpriteParticle(spriteParticle, trailEmits, timeS);
534 continue;
535 }
536 QQuick3DParticleModelParticle *modelParticle = qobject_cast<QQuick3DParticleModelParticle *>(particle);
537 if (modelParticle) {
538 processModelParticle(modelParticle, trailEmits, timeS);
539 continue;
540 }
541 QQuick3DParticleModelBlendParticle *mbp = qobject_cast<QQuick3DParticleModelBlendParticle *>(particle);
542 if (mbp) {
543 processModelBlendParticle(mbp, trailEmits, timeS);
544 continue;
545 }
546 }
547
548 // Clear bursts from trailemitters
549 for (auto emitter : std::as_const(m_trailEmitters))
550 emitter->clearBursts();
551
552 m_timeAnimation += m_perfTimer.nsecsElapsed();
553 m_updateAnimation->setDirty(false);
554 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DParticleUpdate, m_particlesUsed, Q_QUICK3D_PROFILE_GET_ID(this));
555
556 Q_TRACE(QSSG_particleUpdate_exit, m_particlesUsed);
557
558}
559
560void QQuick3DParticleSystem::processModelParticle(QQuick3DParticleModelParticle *modelParticle, const QVector<TrailEmits> &trailEmits, float timeS)
561{
562 modelParticle->clearInstanceTable();
563
564 const int c = modelParticle->maxAmount();
565
566 for (int i = 0; i < c; i++) {
567 const auto d = &modelParticle->m_particleData.at(i);
568
569 const float particleTimeEnd = d->startTime + d->lifetime;
570
571 if (timeS < d->startTime || timeS > particleTimeEnd) {
572 if (timeS > particleTimeEnd && d->lifetime > 0.0f) {
573 const auto pos = d->reversed ? d->startPosition : d->startPosition + (d->startVelocity * (particleTimeEnd - d->startTime));
574 for (auto trailEmit : std::as_const(trailEmits))
575 trailEmit.emitter->emitTrailParticles(pos, 0, QQuick3DParticleDynamicBurst::TriggerEnd, d->surfaceNormal, d->startVelocity.normalized());
576 }
577 // Particle not alive currently
578 continue;
579 }
580
581 QQuick3DParticleDataCurrent currentData;
582 if (timeS >= d->startTime && d->lifetime <= 0.0f) {
583 for (auto trailEmit : std::as_const(trailEmits))
584 trailEmit.emitter->emitTrailParticles(d->startPosition, 0, QQuick3DParticleDynamicBurst::TriggerStart, d->surfaceNormal, d->startVelocity.normalized());
585 }
586
587 // Adjust time for reversed particles
588 const float particleTimeS = d->reversed ? particleTimeEnd - timeS : timeS - d->startTime;
589
590 // Process features shared for both model & sprite particles
591 processParticleCommon(currentData, d, particleTimeS);
592
593 // Add a base rotation if alignment requested
594 if (modelParticle->m_alignMode != QQuick3DParticle::AlignNone)
595 processParticleAlignment(currentData, modelParticle, d);
596
597 // 0.0 -> 1.0 during the particle lifetime
598 const float timeChange = std::max(0.0f, std::min(1.0f, particleTimeS / d->lifetime));
599
600 // Scale from initial to endScale
601 currentData.scale = modelParticle->m_initialScale * (d->endSize * timeChange + d->startSize * (1.0f - timeChange));
602
603 // Fade in & out
604 const float particleTimeLeftS = d->lifetime - particleTimeS;
605 processParticleFadeInOut(currentData, modelParticle, particleTimeS, particleTimeLeftS);
606
607 // Affectors
608 for (auto affector : std::as_const(m_affectors)) {
609 // If affector is set to affect only particular particles, check these are included
610 if (affector->m_enabled && (affector->m_particles.isEmpty() || affector->m_particles.contains(modelParticle)))
611 affector->affectParticle(*d, &currentData, particleTimeS);
612 }
613
614 // Emit new particles from trails
615 for (auto trailEmit : std::as_const(trailEmits))
616 trailEmit.emitter->emitTrailParticles(currentData.position, trailEmit.amount, QQuick3DParticleDynamicBurst::TriggerTime, d->surfaceNormal, d->startVelocity.normalized());
617
618 const QColor color(currentData.color.r, currentData.color.g, currentData.color.b, currentData.color.a);
619 // Set current particle properties
620 modelParticle->addInstance(currentData.position, currentData.scale, currentData.rotation, color, timeChange);
621 }
622 modelParticle->commitInstance();
623}
624
625static QVector3D mix(const QVector3D &a, const QVector3D &b, float f)
626{
627 return (b - a) * f + a;
628}
629
630void QQuick3DParticleSystem::processModelBlendParticle(QQuick3DParticleModelBlendParticle *particle, const QVector<TrailEmits> &trailEmits, float timeS)
631{
632 const int c = particle->maxAmount();
633
634 for (int i = 0; i < c; i++) {
635 const auto d = &particle->m_particleData.at(i);
636
637 const float particleTimeEnd = d->startTime + d->lifetime;
638
639 if (timeS < d->startTime || timeS > particleTimeEnd) {
640 if (timeS > particleTimeEnd && d->lifetime > 0.0f) {
641 const auto pos = d->reversed ? d->startPosition : d->startPosition + (d->startVelocity * (particleTimeEnd - d->startTime));
642 for (auto trailEmit : std::as_const(trailEmits))
643 trailEmit.emitter->emitTrailParticles(pos, 0, QQuick3DParticleDynamicBurst::TriggerEnd, d->surfaceNormal, d->startVelocity.normalized());
644 }
645 // Particle not alive currently
646 float age = 0.0f;
647 float size = 0.0f;
648 QVector3D pos;
649 QVector3D rot;
650 QVector4D color(float(d->startColor.r)/ 255.0f,
651 float(d->startColor.g)/ 255.0f,
652 float(d->startColor.b)/ 255.0f,
653 float(d->startColor.a)/ 255.0f);
654 if (d->startTime > 0.0f && timeS > particleTimeEnd
655 && (particle->modelBlendMode() == QQuick3DParticleModelBlendParticle::Construct ||
656 particle->modelBlendMode() == QQuick3DParticleModelBlendParticle::Transfer)) {
657 age = 1.0f;
658 size = 1.0f;
659 pos = particle->particleEndPosition(i);
660 rot = particle->particleEndRotation(i);
661 if (particle->fadeOutEffect() == QQuick3DParticle::FadeOpacity)
662 color.setW(0.0f);
663 } else if (particle->modelBlendMode() == QQuick3DParticleModelBlendParticle::Explode ||
664 particle->modelBlendMode() == QQuick3DParticleModelBlendParticle::Transfer) {
665 age = 0.0f;
666 size = 1.0f;
667 pos = particle->particleCenter(i);
668 if (particle->fadeInEffect() == QQuick3DParticle::FadeOpacity)
669 color.setW(0.0f);
670 }
671 particle->setParticleData(i, pos, rot, color, size, age);
672 continue;
673 }
674
675 QQuick3DParticleDataCurrent currentData;
676 if (timeS >= d->startTime && d->lifetime <= 0.0f) {
677 for (auto trailEmit : std::as_const(trailEmits))
678 trailEmit.emitter->emitTrailParticles(d->startPosition, 0, QQuick3DParticleDynamicBurst::TriggerStart, d->surfaceNormal, d->startVelocity.normalized());
679 }
680
681 // Adjust time for reversed particles
682 const float particleTimeS = d->reversed ? particleTimeEnd - timeS : timeS - d->startTime;
683
684 // Process features shared for both model & sprite particles
685 processParticleCommon(currentData, d, particleTimeS);
686
687 // 0.0 -> 1.0 during the particle lifetime
688 const float timeChange = std::max(0.0f, std::min(1.0f, particleTimeS / d->lifetime));
689
690 // Scale from initial to endScale
691 const float scale = d->endSize * timeChange + d->startSize * (1.0f - timeChange);
692 currentData.scale = QVector3D(scale, scale, scale);
693
694 // Fade in & out
695 const float particleTimeLeftS = d->lifetime - particleTimeS;
696 processParticleFadeInOut(currentData, particle, particleTimeS, particleTimeLeftS);
697
698 // Affectors
699 for (auto affector : std::as_const(m_affectors)) {
700 // If affector is set to affect only particular particles, check these are included
701 if (affector->m_enabled && (affector->m_particles.isEmpty() || affector->m_particles.contains(particle)))
702 affector->affectParticle(*d, &currentData, particleTimeS);
703 }
704
705 // Emit new particles from trails
706 for (auto trailEmit : std::as_const(trailEmits))
707 trailEmit.emitter->emitTrailParticles(currentData.position, trailEmit.amount, QQuick3DParticleDynamicBurst::TriggerTime, d->surfaceNormal, d->startVelocity.normalized());
708
709 // Set current particle properties
710 const QVector4D color(float(currentData.color.r) / 255.0f,
711 float(currentData.color.g) / 255.0f,
712 float(currentData.color.b) / 255.0f,
713 float(currentData.color.a) / 255.0f);
714 float endTimeS = particle->endTime() * 0.001f;
715 if ((particle->modelBlendMode() == QQuick3DParticleModelBlendParticle::Construct ||
716 particle->modelBlendMode() == QQuick3DParticleModelBlendParticle::Transfer)
717 && particleTimeLeftS < endTimeS) {
718 QVector3D endPosition = particle->particleEndPosition(i);
719 QVector3D endRotation = particle->particleEndRotation(i);
720 float factor = 1.0f - particleTimeLeftS / endTimeS;
721 currentData.position = mix(currentData.position, endPosition, factor);
722 currentData.rotation = mix(currentData.rotation, endRotation, factor);
723 }
724 particle->setParticleData(i, currentData.position, currentData.rotation,
725 color, currentData.scale.x(), timeChange);
726 }
727 particle->commitParticles();
728}
729
730void QQuick3DParticleSystem::processSpriteParticle(QQuick3DParticleSpriteParticle *spriteParticle, const QVector<TrailEmits> &trailEmits, float timeS)
731{
732 const int c = spriteParticle->maxAmount();
733
734 for (int i = 0; i < c; i++) {
735 const auto d = &spriteParticle->m_particleData.at(i);
736
737 const float particleTimeEnd = d->startTime + d->lifetime;
738 auto &particleData = spriteParticle->m_spriteParticleData[i];
739 if (timeS < d->startTime || timeS > particleTimeEnd) {
740 if (timeS > particleTimeEnd && particleData.age > 0.0f) {
741 const auto pos = d->reversed ? d->startPosition : d->startPosition + (d->startVelocity * (particleTimeEnd - d->startTime));
742 for (auto trailEmit : std::as_const(trailEmits))
743 trailEmit.emitter->emitTrailParticles(pos, 0, QQuick3DParticleDynamicBurst::TriggerEnd, d->surfaceNormal, d->startVelocity.normalized());
744 auto *lineParticle = qobject_cast<QQuick3DParticleLineParticle *>(spriteParticle);
745 if (lineParticle)
746 lineParticle->saveLineSegment(i, timeS);
747 }
748 // Particle not alive currently
749 spriteParticle->resetParticleData(i);
750 continue;
751 }
752
753 QQuick3DParticleDataCurrent currentData;
754 if (timeS >= d->startTime && timeS < particleTimeEnd && particleData.age == 0.0f) {
755 for (auto trailEmit : std::as_const(trailEmits))
756 trailEmit.emitter->emitTrailParticles(d->startPosition, 0, QQuick3DParticleDynamicBurst::TriggerStart, d->surfaceNormal, d->startVelocity.normalized());
757 }
758
759 // Adjust time for reversed particles
760 const float particleTimeS = d->reversed ? particleTimeEnd - timeS : timeS - d->startTime;
761
762 // Process features shared for both model & sprite particles
763 processParticleCommon(currentData, d, particleTimeS);
764
765 // Add a base rotation if alignment requested
766 if (!spriteParticle->m_billboard && spriteParticle->m_alignMode != QQuick3DParticle::AlignNone)
767 processParticleAlignment(currentData, spriteParticle, d);
768
769 // 0.0 -> 1.0 during the particle lifetime
770 const float timeChange = std::max(0.0f, std::min(1.0f, particleTimeS / d->lifetime));
771
772 // Scale from initial to endScale
773 const float scale = d->endSize * timeChange + d->startSize * (1.0f - timeChange);
774 currentData.scale = QVector3D(scale, scale, scale);
775
776 // Fade in & out
777 const float particleTimeLeftS = d->lifetime - particleTimeS;
778 processParticleFadeInOut(currentData, spriteParticle, particleTimeS, particleTimeLeftS);
779
780 float animationFrame = 0.0f;
781 if (auto sequence = spriteParticle->m_spriteSequence) {
782 // animationFrame range is [0..1) where 0.0 is the beginning of the first frame
783 // and 0.9999 is the end of the last frame.
784 const bool isSingleFrame = (sequence->animationDirection() == QQuick3DParticleSpriteSequence::SingleFrame);
785 float startFrame = sequence->firstFrame(d->index, isSingleFrame);
786 if (sequence->animationDirection() == QQuick3DParticleSpriteSequence::Normal) {
787 animationFrame = fmodf(startFrame + particleTimeS / d->animationTime, 1.0f);
788 } else if (sequence->animationDirection() == QQuick3DParticleSpriteSequence::Reverse) {
789 animationFrame = fmodf(startFrame + 0.9999f - fmodf(particleTimeS / d->animationTime, 1.0f), 1.0f);
790 } else if (sequence->animationDirection() == QQuick3DParticleSpriteSequence::Alternate) {
791 animationFrame = startFrame + particleTimeS / d->animationTime;
792 animationFrame = fabsf(fmodf(1.0f + animationFrame, 2.0f) - 1.0f);
793 } else if (sequence->animationDirection() == QQuick3DParticleSpriteSequence::AlternateReverse) {
794 animationFrame = fmodf(startFrame + 0.9999f, 1.0f) - particleTimeS / d->animationTime;
795 animationFrame = fabsf(fmodf(fabsf(1.0f + animationFrame), 2.0f) - 1.0f);
796 } else {
797 // SingleFrame
798 animationFrame = startFrame;
799 }
800 animationFrame = std::clamp(animationFrame, 0.0f, 0.9999f);
801 }
802
803 // Affectors
804 for (auto affector : std::as_const(m_affectors)) {
805 // If affector is set to affect only particular particles, check these are included
806 if (affector->m_enabled && (affector->m_particles.isEmpty() || affector->m_particles.contains(spriteParticle)))
807 affector->affectParticle(*d, &currentData, particleTimeS);
808 }
809
810 // Emit new particles from trails
811 for (auto trailEmit : std::as_const(trailEmits))
812 trailEmit.emitter->emitTrailParticles(currentData.position, trailEmit.amount, QQuick3DParticleDynamicBurst::TriggerTime, d->surfaceNormal, d->startVelocity.normalized());
813
814
815 // Set current particle properties
816 const QVector4D color(float(currentData.color.r) / 255.0f,
817 float(currentData.color.g) / 255.0f,
818 float(currentData.color.b) / 255.0f,
819 float(currentData.color.a) / 255.0f);
820 const QVector3D offset(spriteParticle->offsetX(), spriteParticle->offsetY(), 0);
821 spriteParticle->setParticleData(i, currentData.position + (offset * currentData.scale.x()),
822 currentData.rotation, color, currentData.scale.x(), timeChange,
823 animationFrame);
824 }
825 spriteParticle->commitParticles(timeS);
826}
827
828void QQuick3DParticleSystem::processParticleCommon(QQuick3DParticleDataCurrent &currentData, const QQuick3DParticleData *d, float particleTimeS)
829{
830 m_particlesUsed++;
831
832 currentData.position = d->startPosition;
833
834 // Initial color from start color
835 currentData.color = d->startColor;
836
837 // Initial position from start velocity
838 currentData.position += d->startVelocity * particleTimeS;
839
840 // Initial rotation from start velocity
841 constexpr float step = 360.0f / 127.0f;
842 currentData.rotation = QVector3D(
843 d->startRotation.x * step + abs(d->startRotationVelocity.x) * d->startRotationVelocity.x * particleTimeS,
844 d->startRotation.y * step + abs(d->startRotationVelocity.y) * d->startRotationVelocity.y * particleTimeS,
845 d->startRotation.z * step + abs(d->startRotationVelocity.z) * d->startRotationVelocity.z * particleTimeS);
846}
847
848void QQuick3DParticleSystem::processParticleFadeInOut(QQuick3DParticleDataCurrent &currentData, const QQuick3DParticle *particle, float particleTimeS, float particleTimeLeftS)
849{
850 const float fadeInS = particle->m_fadeInDuration / 1000.0f;
851 const float fadeOutS = particle->m_fadeOutDuration / 1000.0f;
852 if (particleTimeS < fadeInS) {
853 // 0.0 -> 1.0 during the particle fadein
854 const float fadeIn = particleTimeS / fadeInS;
855 if (particle->m_fadeInEffect == QQuick3DParticleModelParticle::FadeOpacity)
856 currentData.color.a *= fadeIn;
857 else if (particle->m_fadeInEffect == QQuick3DParticleModelParticle::FadeScale)
858 currentData.scale *= fadeIn;
859 }
860 if (particleTimeLeftS < fadeOutS) {
861 // 1.0 -> 0.0 during the particle fadeout
862 const float fadeOut = particleTimeLeftS / fadeOutS;
863 if (particle->m_fadeOutEffect == QQuick3DParticleModelParticle::FadeOpacity)
864 currentData.color.a *= fadeOut;
865 else if (particle->m_fadeOutEffect == QQuick3DParticleModelParticle::FadeScale)
866 currentData.scale *= fadeOut;
867 }
868}
869
870void QQuick3DParticleSystem::processParticleAlignment(QQuick3DParticleDataCurrent &currentData, const QQuick3DParticle *particle, const QQuick3DParticleData *d)
871{
872 if (particle->m_alignMode == QQuick3DParticle::AlignTowardsTarget) {
873 QQuaternion alignQuat = QQuick3DQuaternionUtils::lookAt(particle->alignTargetPosition(), currentData.position);
874 currentData.rotation = (alignQuat * QQuaternion::fromEulerAngles(currentData.rotation)).toEulerAngles();
875 } else if (particle->m_alignMode == QQuick3DParticle::AlignTowardsStartVelocity) {
876 QQuaternion alignQuat = QQuick3DQuaternionUtils::lookAt(d->startVelocity, QVector3D());
877 currentData.rotation = (alignQuat * QQuaternion::fromEulerAngles(currentData.rotation)).toEulerAngles();
878 }
879}
880
881bool QQuick3DParticleSystem::isGloballyDisabled()
882{
883 static const bool disabled = qEnvironmentVariableIntValue("QT_QUICK3D_DISABLE_PARTICLE_SYSTEMS");
884 return disabled;
885}
886
887bool QQuick3DParticleSystem::isEditorModeOn()
888{
889 static const bool editorMode = qEnvironmentVariableIntValue("QT_QUICK3D_EDITOR_PARTICLE_SYSTEMS");
890 return editorMode;
891}
892
893void QQuick3DParticleSystem::updateLoggingData()
894{
895 if (m_updates == 0)
896 return;
897
898 if (m_loggingData->m_particlesMax != m_particlesMax) {
899 m_loggingData->m_particlesMax = m_particlesMax;
900 Q_EMIT m_loggingData->particlesMaxChanged();
901 }
902 if (m_loggingData->m_particlesUsed != m_particlesUsed) {
903 m_loggingData->m_particlesUsed = m_particlesUsed;
904 Q_EMIT m_loggingData->particlesUsedChanged();
905 }
906 if (m_loggingData->m_updates != m_updates) {
907 m_loggingData->m_updates = m_updates;
908 Q_EMIT m_loggingData->updatesChanged();
909 }
910
911 m_loggingData->updateTimes(m_timeAnimation);
912
913 Q_EMIT loggingDataChanged();
914 resetLoggingVariables();
915}
916
917void QQuick3DParticleSystem::resetLoggingVariables()
918{
919 m_particlesMax = 0;
920 m_particlesUsed = 0;
921 m_updates = 0;
922 m_timeAnimation = 0;
923}
924
925QPRand *QQuick3DParticleSystem::rand()
926{
927 return &m_rand;
928}
929
930void QQuick3DParticleSystem::doSeedRandomization()
931{
932 // Random 1..INT32_MAX, making sure seed changes from the initial 0.
933 setSeed(QRandomGenerator::global()->bounded(1 + (INT32_MAX - 1)));
934}
935
936bool QQuick3DParticleSystem::isShared(const QQuick3DParticle *particle) const
937{
938 int count = 0;
939 for (auto emitter : std::as_const(m_emitters)) {
940 count += emitter->particle() == particle;
941 if (count > 1)
942 return true;
943 }
944 for (auto emitter : std::as_const(m_trailEmitters)) {
945 count += emitter->particle() == particle;
946 if (count > 1)
947 return true;
948 }
949 return false;
950}
951
952QT_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)