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