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
qquickparticlesystem.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5#include <QtQuick/qsgnode.h>
9#include <private/qquickspriteengine_p.h>
10#include <private/qquicksprite_p.h>
13
14#include "qquicktrailemitter_p.h" //###For auto-follow on states, perhaps should be in emitter?
15#include <private/qqmlengine_p.h>
16#include <private/qqmlglobal_p.h>
17#include <private/qqmlvaluetypewrapper_p.h>
18#include <cmath>
19#include <QDebug>
20
22//###Switch to define later, for now user-friendly (no compilation) debugging is worth it
24
25
26/* \internal ParticleSystem internals documentation
27
28 Affectors, Painters, Emitters and Groups all register themselves on construction as a callback
29 from their setSystem (or componentComplete if they have a system from a parent).
30
31 Particle data is stored by group, They have a group index (used by the particle system almost
32 everywhere) and a global index (used by the Stochastic state engine powering stochastic group
33 transitions). Each group has a recycling list/heap that stores the particle data.
34
35 The recycling list/heap is a heap of particle data sorted by when they're expected to die. If
36 they die prematurely then they are marked as reusable (and will probably still be alive when
37 they exit the heap). If they have their life extended, then they aren't dead when expected.
38 If this happens, they go back in the heap with the new estimate. If they have died on schedule,
39 then the indexes are marked as reusable. If no indexes are reusable when new particles are
40 requested, then the list is extended. This relatively complex datastructure is because memory
41 allocation and deallocation on this scale proved to be a significant performance cost. In order
42 to reuse the indexes validly (even when particles can have their life extended or cut short
43 dynamically, or particle counts grow) this seemed to be the most efficient option for keeping
44 track of which indices could be reused.
45
46 When a new particle is emitted, the emitter gets a new datum from the group (through the
47 system), and sets properties on it. Then it's passed back to the group briefly so that it can
48 now guess when the particle will die. Then the painters get a change to initialize properties
49 as well, since particle data includes shared data from painters as well as logical particle
50 data.
51
52 Every animation advance, the simulation advances by running all emitters for the elapsed
53 duration, then running all affectors, then telling all particle painters to update changed
54 particles. The ParticlePainter superclass stores these changes, and they are implemented
55 when the painter is called to paint in the render thread.
56
57 Particle group changes move the particle from one group to another by killing the old particle
58 and then creating a new one with the same data in the new group.
59
60 Note that currently groups only grow. Given that data is stored in vectors, it is non-trivial
61 to pluck out the unused indexes when the count goes down. Given the dynamic nature of the
62 system, it is difficult to tell if those unused data instances will be used again. Still,
63 some form of garbage collection is on the long term plan.
64*/
65
66/*!
67 \qmltype ParticleSystem
68//! \nativetype QQuickParticleSystem
69 \inqmlmodule QtQuick.Particles
70 \brief A system which includes particle painter, emitter, and affector types.
71 \ingroup qtquick-particles
72
73*/
74
75/*!
76 \qmlproperty bool QtQuick.Particles::ParticleSystem::running
77
78 If running is set to false, the particle system will stop the simulation. All particles
79 will be destroyed when the system is set to running again.
80
81 It can also be controlled with the start() and stop() methods.
82*/
83
84
85/*!
86 \qmlproperty bool QtQuick.Particles::ParticleSystem::paused
87
88 If paused is set to true, the particle system will not advance the simulation. When
89 paused is set to false again, the simulation will resume from the same point it was
90 paused.
91
92 The simulation will automatically pause if it detects that there are no live particles
93 left, and unpause when new live particles are added.
94
95 It can also be controlled with the pause() and resume() methods.
96*/
97
98/*!
99 \qmlproperty bool QtQuick.Particles::ParticleSystem::empty
100
101 empty is set to true when there are no live particles left in the system.
102
103 You can use this to pause the system, keeping it from spending any time updating,
104 but you will need to resume it in order for additional particles to be generated
105 by the system.
106
107 To kill all the particles in the system, use an Age affector.
108*/
109
110/*!
111 \qmlproperty list<Sprite> QtQuick.Particles::ParticleSystem::particleStates
112
113 You can define a sub-set of particle groups in this property in order to provide them
114 with stochastic state transitions.
115
116 Each QtQuick::Sprite in this list is interpreted as corresponding to the particle group
117 with the same name. Any transitions defined in these sprites will take effect on the particle
118 groups as well. Additionally TrailEmitters, Affectors and ParticlePainters defined
119 inside one of these sprites are automatically associated with the corresponding particle group.
120*/
121
122/*!
123 \qmlmethod void QtQuick.Particles::ParticleSystem::pause()
124
125 Pauses the simulation if it is running.
126
127 \sa resume, paused
128*/
129
130/*!
131 \qmlmethod void QtQuick.Particles::ParticleSystem::resume()
132
133 Resumes the simulation if it is paused.
134
135 \sa pause, paused
136*/
137
138/*!
139 \qmlmethod void QtQuick.Particles::ParticleSystem::start()
140
141 Starts the simulation if it has not already running.
142
143 \sa stop, restart, running
144*/
145
146/*!
147 \qmlmethod void QtQuick.Particles::ParticleSystem::stop()
148
149 Stops the simulation if it is running.
150
151 \sa start, restart, running
152*/
153
154/*!
155 \qmlmethod void QtQuick.Particles::ParticleSystem::restart()
156
157 Stops the simulation if it is running, and then starts it.
158
159 \sa start, stop, running
160*/
161/*!
162 \qmlmethod void QtQuick.Particles::ParticleSystem::reset()
163
164 Discards all currently existing particles.
165
166*/
167
168static inline int roundedTime(qreal a)
169{// in ms
170 return (int)qRound(a*1000.0);
171}
172
173QQuickParticleDataHeap::QQuickParticleDataHeap()
174 : m_data(0)
175{
176 m_data.reserve(1000);
177 clear();
178}
179
180void QQuickParticleDataHeap::grow() //###Consider automatic growth vs resize() calls from GroupData
181{
182 m_data.resize(qsizetype(1) << ++m_size);
183}
184
185void QQuickParticleDataHeap::insert(QQuickParticleData* data)
186{
187 insertTimed(data, roundedTime(data->t + data->lifeSpan));
188}
189
190void QQuickParticleDataHeap::insertTimed(QQuickParticleData* data, int time)
191{
192 //TODO: Optimize 0 lifespan (or already dead) case
193 if (m_lookups.contains(time)) {
194 m_data[m_lookups[time]].data << data;
195 return;
196 }
197 if (m_end == (1 << m_size))
198 grow();
199 m_data[m_end].time = time;
200 m_data[m_end].data.clear();
201 m_data[m_end].data.insert(data);
202 m_lookups.insert(time, m_end);
203 bubbleUp(m_end++);
204}
205
206int QQuickParticleDataHeap::top()
207{
208 Q_ASSERT(!isEmpty());
209 return m_data[0].time;
210}
211
212QSet<QQuickParticleData*> QQuickParticleDataHeap::pop()
213{
214 if (!m_end)
215 return QSet<QQuickParticleData*> ();
216 QSet<QQuickParticleData*> ret = m_data[0].data;
217 m_lookups.remove(m_data[0].time);
218 if (m_end == 1) {
219 --m_end;
220 } else {
221 m_data[0] = m_data[--m_end];
222 bubbleDown(0);
223 }
224 return ret;
225}
226
227void QQuickParticleDataHeap::clear()
228{
229 m_size = 0;
230 m_end = 0;
231 //m_size is in powers of two. So to start at 0 we have one allocated
232 m_data.resize(1);
233 m_lookups.clear();
234}
235
236bool QQuickParticleDataHeap::contains(QQuickParticleData* d)
237{
238 for (int i=0; i<m_end; i++)
239 if (m_data[i].data.contains(d))
240 return true;
241 return false;
242}
243
244void QQuickParticleDataHeap::swap(int a, int b)
245{
246 m_tmp = m_data[a];
247 m_data[a] = m_data[b];
248 m_data[b] = m_tmp;
249 m_lookups[m_data[a].time] = a;
250 m_lookups[m_data[b].time] = b;
251}
252
253void QQuickParticleDataHeap::bubbleUp(int idx)//tends to be called once
254{
255 if (!idx)
256 return;
257 int parent = (idx-1)/2;
258 if (m_data[idx].time < m_data[parent].time) {
259 swap(idx, parent);
260 bubbleUp(parent);
261 }
262}
263
264void QQuickParticleDataHeap::bubbleDown(int idx)//tends to be called log n times
265{
266 int left = idx*2 + 1;
267 if (left >= m_end)
268 return;
269 int lesser = left;
270 int right = idx*2 + 2;
271 if (right < m_end) {
272 if (m_data[left].time > m_data[right].time)
273 lesser = right;
274 }
275 if (m_data[idx].time > m_data[lesser].time) {
276 swap(idx, lesser);
277 bubbleDown(lesser);
278 }
279}
280
281QQuickParticleGroupData::QQuickParticleGroupData(const QString &name, QQuickParticleSystem* sys)
282 : index(sys->registerParticleGroupData(name, this))
283 , m_size(0)
284 , m_system(sys)
285{
286 initList();
287}
288
289QQuickParticleGroupData::~QQuickParticleGroupData()
290{
291 for (QQuickParticleData *d : std::as_const(data))
292 delete d;
293}
294
295QString QQuickParticleGroupData::name() const//### Worth caching as well?
296{
297 return m_system->groupIds.key(index);
298}
299
300void QQuickParticleGroupData::setSize(int newSize)
301{
302 if (newSize == m_size)
303 return;
304 Q_ASSERT(newSize > m_size);//XXX allow shrinking
305 data.resize(newSize);
306 freeList.resize(newSize);
307 for (int i=m_size; i<newSize; i++) {
308 data[i] = new QQuickParticleData;
309 data[i]->groupId = index;
310 data[i]->index = i;
311 }
312 int delta = newSize - m_size;
313 m_size = newSize;
314 for (QQuickParticlePainter *p : std::as_const(painters))
315 p->setCount(p->count() + delta);
316}
317
318void QQuickParticleGroupData::initList()
319{
320 dataHeap.clear();
321}
322
323void QQuickParticleGroupData::kill(QQuickParticleData* d)
324{
325 Q_ASSERT(d->groupId == index);
326 d->lifeSpan = 0;//Kill off
327 for (QQuickParticlePainter *p : std::as_const(painters))
328 p->reload(d);
329 freeList.free(d->index);
330}
331
332QQuickParticleData* QQuickParticleGroupData::newDatum(bool respectsLimits)
333{
334 //recycle();//Extra recycler round to be sure?
335
336 while (freeList.hasUnusedEntries()) {
337 int idx = freeList.alloc();
338 if (data[idx]->stillAlive(m_system)) {// ### This means resurrection of 'dead' particles. Is that allowed?
339 prepareRecycler(data[idx]);
340 continue;
341 }
342 return data[idx];
343 }
344 if (respectsLimits)
345 return nullptr;
346
347 int oldSize = m_size;
348 setSize(oldSize + 10);//###+1,10%,+10? Choose something non-arbitrarily
349 int idx = freeList.alloc();
350 Q_ASSERT(idx == oldSize);
351 return data[idx];
352}
353
354bool QQuickParticleGroupData::recycle()
355{
356 m_latestAliveParticles.clear();
357
358 while (!dataHeap.isEmpty() && dataHeap.top() <= m_system->timeInt) {
359 for (QQuickParticleData *datum : dataHeap.pop()) {
360 if (!datum->stillAlive(m_system)) {
361 freeList.free(datum->index);
362 } else {
363 m_latestAliveParticles.push_back(datum);
364 }
365 }
366 }
367
368 for (auto particle : m_latestAliveParticles)
369 prepareRecycler(particle); //ttl has been altered mid-way, put it back
370
371 //TODO: If the data is clear, gc (consider shrinking stack size)?
372 return freeList.count() == 0;
373}
374
375void QQuickParticleGroupData::prepareRecycler(QQuickParticleData* d)
376{
377 if (d->lifeSpan*1000 < m_system->maxLife) {
378 dataHeap.insert(d);
379 } else {
380 int extend = 2 * m_system->maxLife / 3;
381 while ((roundedTime(d->t) + extend) <= m_system->timeInt)
382 d->extendLife(m_system->maxLife / 3000.0, m_system);
383 dataHeap.insertTimed(d, roundedTime(d->t) + extend);
384 }
385}
386
387QQuickV4ParticleData QQuickParticleData::v4Value(QQuickParticleSystem *particleSystem)
388{
389 return QQuickV4ParticleData(this, particleSystem);
390}
391
392void QQuickParticleData::debugDump(QQuickParticleSystem* particleSystem) const
393{
394 qDebug() << "Particle" << systemIndex << groupId << "/" << index << stillAlive(particleSystem)
395 << "Pos: " << x << "," << y
396 << "Vel: " << vx << "," << vy
397 << "Acc: " << ax << "," << ay
398 << "Size: " << size << "," << endSize
399 << "Time: " << t << "," <<lifeSpan << ";" << (particleSystem->timeInt / 1000.0) ;
400}
401
402void QQuickParticleData::extendLife(float time, QQuickParticleSystem* particleSystem)
403{
404 qreal newX = curX(particleSystem);
405 qreal newY = curY(particleSystem);
406 qreal newVX = curVX(particleSystem);
407 qreal newVY = curVY(particleSystem);
408
409 t += time;
410 animT += time;
411
412 qreal elapsed = (particleSystem->timeInt / 1000.0) - t;
413 qreal evy = newVY - elapsed*ay;
414 qreal ey = newY - elapsed*evy - 0.5 * elapsed*elapsed*ay;
415 qreal evx = newVX - elapsed*ax;
416 qreal ex = newX - elapsed*evx - 0.5 * elapsed*elapsed*ax;
417
418 x = ex;
419 vx = evx;
420 y = ey;
421 vy = evy;
422}
423
424QQuickParticleSystem::QQuickParticleSystem(QQuickItem *parent) :
425 QQuickItem(parent),
426 stateEngine(nullptr),
427 nextFreeGroupId(0),
428 m_animation(nullptr),
429 m_running(true),
430 initialized(0),
431 particleCount(0),
432 m_nextIndex(0),
433 m_componentComplete(false),
434 m_paused(false),
435 m_empty(true)
436{
437 m_debugMode = qmlParticlesDebug();
438}
439
440template<typename T>
441void unsetSystem(const QList<QPointer<T>> &elements)
442{
443 for (T *element : elements) {
444 if (element)
445 element->setSystem(nullptr);
446 }
447}
448
449QQuickParticleSystem::~QQuickParticleSystem()
450{
451 unsetSystem(std::exchange(m_emitters, {}));
452 unsetSystem(std::exchange(m_affectors, {}));
453 unsetSystem(std::exchange(m_painters, {}));
454 qDeleteAll(groupData);
455}
456
457void QQuickParticleSystem::initGroups()
458{
459 m_reusableIndexes.clear();
460 m_nextIndex = 0;
461
462 qDeleteAll(groupData);
463 groupData.clear();
464 groupIds.clear();
465 nextFreeGroupId = 0;
466
467 for (auto e : std::as_const(m_emitters)) {
468 e->reclaculateGroupId();
469 }
470 for (QQuickParticlePainter *p : std::as_const(m_painters)) {
471 p->recalculateGroupIds();
472 }
473
474 QQuickParticleGroupData *pd = new QQuickParticleGroupData(QString(), this); // Default group
475 Q_ASSERT(pd->index == 0);
476 Q_UNUSED(pd);
477}
478
479void QQuickParticleSystem::registerParticlePainter(QQuickParticlePainter* p)
480{
481 if (m_debugMode)
482 qDebug() << "Registering Painter" << p << "to" << this;
483 //TODO: a way to Unregister emitters, painters and affectors
484 m_painters << QPointer<QQuickParticlePainter>(p);//###Set or uniqueness checking?
485 loadPainter(p);
486}
487
488void QQuickParticleSystem::registerParticleEmitter(QQuickParticleEmitter* e)
489{
490 if (m_debugMode)
491 qDebug() << "Registering Emitter" << e << "to" << this;
492 m_emitters << QPointer<QQuickParticleEmitter>(e);
493}
494
495void QQuickParticleSystem::finishRegisteringParticleEmitter(QQuickParticleEmitter* e)
496{
497 if (m_componentComplete)
498 emitterAdded(e);
499 e->reset();//Start, so that starttime factors appropriately
500}
501
502void QQuickParticleSystem::registerParticleAffector(QQuickParticleAffector* a)
503{
504 if (m_debugMode)
505 qDebug() << "Registering Affector" << a << "to" << this;
506 if (!m_affectors.contains(a))
507 m_affectors << QPointer<QQuickParticleAffector>(a);
508}
509
510void QQuickParticleSystem::unregisterParticlePainter(QQuickParticlePainter *p)
511{
512 if (m_debugMode)
513 qDebug() << "Unregistering Painter" << p << "from" << this;
514 m_painters.removeAll(p);
515 m_syncList.removeAll(p);
516 for (QQuickParticleGroupData *gd : std::as_const(groupData))
517 gd->painters.removeOne(p);
518}
519
520void QQuickParticleSystem::unregisterParticleEmitter(QQuickParticleEmitter *e)
521{
522 if (m_debugMode)
523 qDebug() << "Unregistering Emitter" << e << "from" << this;
524 m_emitters.removeAll(e);
525}
526
527void QQuickParticleSystem::unregisterParticleAffector(QQuickParticleAffector *a)
528{
529 if (m_debugMode)
530 qDebug() << "Unregistering Affector" << a << "from" << this;
531 m_affectors.removeAll(a);
532}
533
534void QQuickParticleSystem::registerParticleGroup(QQuickParticleGroup* g)
535{
536 if (m_debugMode)
537 qDebug() << "Registering Group" << g << "to" << this;
538 m_groups << QPointer<QQuickParticleGroup>(g);
539 createEngine();
540}
541
542void QQuickParticleSystem::setRunning(bool arg)
543{
544 if (m_running != arg) {
545 m_running = arg;
546 emit runningChanged(arg);
547 setPaused(false);
548 if (m_animation)//Not created until componentCompleted
549 m_running ? m_animation->start() : m_animation->stop();
550 reset();
551 }
552}
553
554void QQuickParticleSystem::setPaused(bool arg) {
555 if (m_paused != arg) {
556 m_paused = arg;
557 if (m_animation && m_animation->state() != QAbstractAnimation::Stopped)
558 m_paused ? m_animation->pause() : m_animation->resume();
559 if (!m_paused) {
560 for (QQuickParticlePainter *p : std::as_const(m_painters)) {
561 if (p) {
562 p->update();
563 }
564 }
565 }
566 emit pausedChanged(arg);
567 }
568}
569
570void QQuickParticleSystem::statePropertyRedirect(QQmlListProperty<QObject> *prop, QObject *value)
571{
572 //Hooks up automatic state-associated stuff
573 QQuickParticleSystem* sys = qobject_cast<QQuickParticleSystem*>(prop->object->parent());
574 QQuickParticleGroup* group = qobject_cast<QQuickParticleGroup*>(prop->object);
575 if (!group || !sys || !value)
576 return;
577 stateRedirect(group, sys, value);
578}
579
580void QQuickParticleSystem::stateRedirect(QQuickParticleGroup* group, QQuickParticleSystem* sys, QObject *value)
581{
582 QStringList list;
583 list << group->name();
584 QQuickParticleAffector* a = qobject_cast<QQuickParticleAffector*>(value);
585 if (a) {
586 a->setParentItem(sys);
587 a->setGroups(list);
588 a->setSystem(sys);
589 return;
590 }
591 QQuickTrailEmitter* fe = qobject_cast<QQuickTrailEmitter*>(value);
592 if (fe) {
593 fe->setParentItem(sys);
594 fe->setFollow(group->name());
595 fe->setSystem(sys);
596 return;
597 }
598 QQuickParticleEmitter* e = qobject_cast<QQuickParticleEmitter*>(value);
599 if (e) {
600 e->setParentItem(sys);
601 e->setGroup(group->name());
602 e->setSystem(sys);
603 return;
604 }
605 QQuickParticlePainter* p = qobject_cast<QQuickParticlePainter*>(value);
606 if (p) {
607 p->setParentItem(sys);
608 p->setGroups(list);
609 p->setSystem(sys);
610 return;
611 }
612 qWarning() << value << " was placed inside a particle system state but cannot be taken into the particle system. It will be lost.";
613}
614
615
616int QQuickParticleSystem::registerParticleGroupData(const QString &name, QQuickParticleGroupData *pgd)
617{
618 Q_ASSERT(!groupIds.contains(name));
619 int id;
620 if (nextFreeGroupId >= groupData.size()) {
621 groupData.push_back(pgd);
622 nextFreeGroupId = groupData.size();
623 id = nextFreeGroupId - 1;
624 } else {
625 id = nextFreeGroupId;
626 groupData[id] = pgd;
627 searchNextFreeGroupId();
628 }
629 groupIds.insert(name, id);
630 return id;
631}
632
633void QQuickParticleSystem::searchNextFreeGroupId()
634{
635 ++nextFreeGroupId;
636 for (int ei = groupData.size(); nextFreeGroupId != ei; ++nextFreeGroupId) {
637 if (groupData[nextFreeGroupId] == nullptr) {
638 return;
639 }
640 }
641}
642
643void QQuickParticleSystem::componentComplete()
644
645{
646 QQuickItem::componentComplete();
647 m_componentComplete = true;
648 m_animation = new QQuickParticleSystemAnimation(this);
649 reset();//restarts animation as well
650}
651
652void QQuickParticleSystem::reset()
653{
654 if (!m_componentComplete)
655 return;
656
657 timeInt = 0;
658 //Clear guarded pointers which have been deleted
659 m_emitters.removeAll(nullptr);
660 m_painters.removeAll(nullptr);
661 m_affectors.removeAll(nullptr);
662
663 bySysIdx.resize(0);
664 initGroups();//Also clears all logical particles
665
666 if (!m_running)
667 return;
668
669 for (QQuickParticleEmitter *e : std::as_const(m_emitters))
670 e->reset();
671
672 emittersChanged();
673
674 for (QQuickParticlePainter *p : std::as_const(m_painters)) {
675 loadPainter(p);
676 p->reset();
677 }
678
679 //### Do affectors need reset too?
680 if (m_animation) {//Animation is explicitly disabled in benchmarks
681 //reset restarts animation (if running)
682 if ((m_animation->state() == QAbstractAnimation::Running))
683 m_animation->stop();
684 m_animation->start();
685 if (m_paused)
686 m_animation->pause();
687 }
688
689 initialized = true;
690}
691
692
693void QQuickParticleSystem::loadPainter(QQuickParticlePainter *painter)
694{
695 if (!m_componentComplete || !painter)
696 return;
697
698 for (QQuickParticleGroupData *sg : groupData) {
699 sg->painters.removeOne(painter);
700 }
701
702 int particleCount = 0;
703 if (painter->groups().isEmpty()) {//Uses default particle
704 static QStringList def = QStringList() << QString();
705 painter->setGroups(def);
706 particleCount += groupData[0]->size();
707 groupData[0]->painters << painter;
708 } else {
709 for (auto groupId : painter->groupIds()) {
710 QQuickParticleGroupData *gd = groupData[groupId];
711 particleCount += gd->size();
712 gd->painters << painter;
713 }
714 }
715 painter->setCount(particleCount);
716 painter->update();//Initial update here
717 return;
718}
719
720void QQuickParticleSystem::emittersChanged()
721{
722 if (!m_componentComplete)
723 return;
724
725 QList<int> previousSizes;
726 QList<int> newSizes;
727 previousSizes.reserve(groupData.size());
728 newSizes.reserve(groupData.size());
729 for (int i = 0, ei = groupData.size(); i != ei; ++i) {
730 previousSizes << groupData[i]->size();
731 newSizes << 0;
732 }
733
734 // Populate groups and set sizes.
735 for (int i = 0; i < m_emitters.size(); ) {
736 QQuickParticleEmitter *e = m_emitters.at(i);
737 if (!e) {
738 m_emitters.removeAt(i);
739 continue;
740 }
741
742 int groupId = e->groupId();
743 if (groupId == QQuickParticleGroupData::InvalidID) {
744 groupId = (new QQuickParticleGroupData(e->group(), this))->index;
745 previousSizes << 0;
746 newSizes << 0;
747 }
748 newSizes[groupId] += e->particleCount();
749 //###: Cull emptied groups?
750
751 ++i;
752 }
753
754 //TODO: Garbage collection?
755 particleCount = 0;
756 for (int i = 0, ei = groupData.size(); i != ei; ++i) {
757 groupData[i]->setSize(qMax(newSizes[i], previousSizes[i]));
758 particleCount += groupData[i]->size();
759 }
760
761 postProcessEmitters();
762}
763
764void QQuickParticleSystem::postProcessEmitters()
765{
766 if (m_debugMode)
767 qDebug() << "Particle system emitters changed. New particle count: " << particleCount << "in" << groupData.size() << "groups.";
768
769 if (particleCount > bySysIdx.size())//New datum requests haven't updated it
770 bySysIdx.resize(particleCount);
771
772 for (QQuickParticleAffector *a : std::as_const(m_affectors)) {//Groups may have changed
773 if (a) {
774 a->m_updateIntSet = true;
775 }
776 }
777
778 for (QQuickParticlePainter *p : std::as_const(m_painters))
779 loadPainter(p);
780
781 if (!m_groups.isEmpty())
782 createEngine();
783
784}
785
786void QQuickParticleSystem::emitterAdded(QQuickParticleEmitter *e)
787{
788 if (!m_componentComplete)
789 return;
790
791 // Populate group and set size.
792 const int groupId = e->groupId();
793 if (groupId == QQuickParticleGroupData::InvalidID) {
794 QQuickParticleGroupData *group = new QQuickParticleGroupData(e->group(), this);
795 group->setSize(e->particleCount());
796 } else {
797 QQuickParticleGroupData *group = groupData[groupId];
798 group->setSize(group->size() + e->particleCount());
799 }
800
801 // groupData can have changed independently, so we still have to iterate it all
802 // to count the particles.
803 particleCount = 0;
804 for (int i = 0, ei = groupData.size(); i != ei; ++i)
805 particleCount += groupData[i]->size();
806
807 postProcessEmitters();
808}
809
810void QQuickParticleSystem::createEngine()
811{
812 if (!m_componentComplete)
813 return;
814 if (stateEngine && m_debugMode)
815 qDebug() << "Resetting Existing Sprite Engine...";
816 //### Solve the losses if size/states go down
817 for (QQuickParticleGroup *group : std::as_const(m_groups)) {
818 bool exists = false;
819 for (auto it = groupIds.keyBegin(), end = groupIds.keyEnd(); it != end; ++it) {
820 if (group->name() == *it) {
821 exists = true;
822 break;
823 }
824 }
825 if (!exists) {
826 new QQuickParticleGroupData(group->name(), this);
827 }
828 }
829
830 if (m_groups.size()) {
831 //Reorder groups List so as to have the same order as groupData
832 // TODO: can't we just merge the two lists?
833 QList<QQuickParticleGroup*> newList;
834 for (int i = 0, ei = groupData.size(); i != ei; ++i) {
835 bool exists = false;
836 QString name = groupData[i]->name();
837 for (QQuickParticleGroup *existing : std::as_const(m_groups)) {
838 if (existing->name() == name) {
839 newList << existing;
840 exists = true;
841 }
842 }
843 if (!exists) {
844 newList << new QQuickParticleGroup(this);
845 newList.back()->setName(name);
846 }
847 }
848 m_groups = newList;
849 QList<QQuickStochasticState*> states;
850 states.reserve(m_groups.size());
851 for (QQuickParticleGroup *g : std::as_const(m_groups))
852 states << (QQuickStochasticState*)g;
853
854 if (!stateEngine)
855 stateEngine = new QQuickStochasticEngine(this);
856 stateEngine->setCount(particleCount);
857 stateEngine->m_states = states;
858
859 connect(stateEngine, &QQuickStochasticEngine::stateChanged,
860 this, &QQuickParticleSystem::particleStateChange);
861
862 } else {
863 if (stateEngine)
864 delete stateEngine;
865 stateEngine = nullptr;
866 }
867
868}
869
870void QQuickParticleSystem::particleStateChange(int idx)
871{
872 moveGroups(bySysIdx[idx], stateEngine->curState(idx));
873}
874
875void QQuickParticleSystem::moveGroups(QQuickParticleData *d, int newGIdx)
876{
877 if (!d || newGIdx == d->groupId)
878 return;
879
880 QQuickParticleData *pd = newDatum(newGIdx, false, d->systemIndex, d);
881 if (!pd)
882 return;
883
884 finishNewDatum(pd);
885
886 d->systemIndex = -1;
887 groupData[d->groupId]->kill(d);
888}
889
890int QQuickParticleSystem::nextSystemIndex()
891{
892 if (!m_reusableIndexes.isEmpty()) {
893 int ret = *(m_reusableIndexes.begin());
894 m_reusableIndexes.remove(ret);
895 return ret;
896 }
897 if (m_nextIndex >= bySysIdx.size()) {
898 bySysIdx.resize(bySysIdx.size() < 10 ? 10 : bySysIdx.size()*1.1);//###+1,10%,+10? Choose something non-arbitrarily
899 if (stateEngine)
900 stateEngine->setCount(bySysIdx.size());
901
902 }
903 return m_nextIndex++;
904}
905
906QQuickParticleData *QQuickParticleSystem::newDatum(
907 int groupId, bool respectLimits, int sysIndex,
908 const QQuickParticleData *cloneFrom)
909{
910 Q_ASSERT(groupId < groupData.size());//XXX shouldn't really be an assert
911
912 QQuickParticleData *ret = groupData[groupId]->newDatum(respectLimits);
913 if (!ret)
914 return nullptr;
915
916 if (cloneFrom) {
917 // We need to retain the "identity" information of the new particle data since it may be
918 // "recycled" and still be tracked.
919 const int retainedIndex = ret->index;
920 const int retainedGroupId = ret->groupId;
921 const int retainedSystemIndex = ret->systemIndex;
922 *ret = *cloneFrom;
923 ret->index = retainedIndex;
924 ret->groupId = retainedGroupId;
925 ret->systemIndex = retainedSystemIndex;
926 }
927
928 if (sysIndex == -1) {
929 if (ret->systemIndex == -1)
930 ret->systemIndex = nextSystemIndex();
931 } else {
932 if (ret->systemIndex != -1) {
933 if (stateEngine)
934 stateEngine->stop(ret->systemIndex);
935 m_reusableIndexes << ret->systemIndex;
936 bySysIdx[ret->systemIndex] = 0;
937 }
938 ret->systemIndex = sysIndex;
939 }
940 bySysIdx[ret->systemIndex] = ret;
941
942 if (stateEngine)
943 stateEngine->start(ret->systemIndex, ret->groupId);
944
945 m_empty = false;
946 return ret;
947}
948
949void QQuickParticleSystem::emitParticle(QQuickParticleData* pd, QQuickParticleEmitter* particleEmitter)
950{// called from prepareNextFrame()->emitWindow - enforce?
951 //Account for relative emitter position
952 bool okay = false;
953 QTransform t = particleEmitter->itemTransform(this, &okay);
954 if (okay) {
955 qreal tx,ty;
956 t.map(pd->x, pd->y, &tx, &ty);
957 pd->x = tx;
958 pd->y = ty;
959 }
960
961 finishNewDatum(pd);
962}
963
964void QQuickParticleSystem::finishNewDatum(QQuickParticleData *pd)
965{
966 Q_ASSERT(pd);
967 groupData[pd->groupId]->prepareRecycler(pd);
968
969 for (QQuickParticleAffector *a : std::as_const(m_affectors))
970 if (a && a->m_needsReset)
971 a->reset(pd);
972 for (QQuickParticlePainter *p : std::as_const(groupData[pd->groupId]->painters))
973 if (p)
974 p->load(pd);
975}
976
977void QQuickParticleSystem::updateCurrentTime( int currentTime )
978{
979 if (!initialized)
980 return;//error in initialization
981
982 //### Elapsed time never shrinks - may cause problems if left emitting for weeks at a time.
983 qreal dt = timeInt / 1000.;
984 timeInt = currentTime;
985 qreal time = timeInt / 1000.;
986 dt = time - dt;
987 needsReset.clear();
988
989 m_emitters.removeAll(nullptr);
990 m_painters.removeAll(nullptr);
991 m_affectors.removeAll(nullptr);
992
993 bool oldClear = m_empty;
994 m_empty = true;
995 for (QQuickParticleGroupData *gd : std::as_const(groupData))//Recycle all groups and see if they're out of live particles
996 m_empty = gd->recycle() && m_empty;
997
998 if (stateEngine)
999 stateEngine->updateSprites(timeInt);
1000
1001 for (QQuickParticleEmitter *emitter : std::as_const(m_emitters))
1002 emitter->emitWindow(timeInt);
1003 for (QQuickParticleAffector *a : std::as_const(m_affectors))
1004 a->affectSystem(dt);
1005 for (QQuickParticleData *d : needsReset)
1006 for (QQuickParticlePainter *p : std::as_const(groupData[d->groupId]->painters))
1007 p->reload(d);
1008
1009 if (oldClear != m_empty)
1010 emptyChanged(m_empty);
1011}
1012
1013int QQuickParticleSystem::systemSync(QQuickParticlePainter* p)
1014{
1015 if (!m_running)
1016 return 0;
1017 if (!initialized)
1018 return 0;//error in initialization
1019 p->performPendingCommits();
1020 return timeInt;
1021}
1022
1023
1024QT_END_NAMESPACE
1025
1026#include "moc_qquickparticlesystem_p.cpp"
static QT_BEGIN_NAMESPACE int roundedTime(qreal a)
A system which includes particle painter, emitter, and affector types.
void unsetSystem(const QList< QPointer< T > > &elements)
DEFINE_BOOL_CONFIG_OPTION(forceDiskCache, QML_FORCE_DISK_CACHE)