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