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 bool extended = false;
381 int extend = 2 * m_system->maxLife / 3;
382 while ((roundedTime(d->t) + extend) <= m_system->timeInt) {
383 d->extendLife(m_system->maxLife / 3000.0, m_system);
384 extended = true;
385 }
386 if (extended) {
387 for (QQuickParticlePainter *p : std::as_const(painters))
388 p->reload(d);
389 }
390 dataHeap.insertTimed(d, roundedTime(d->t) + extend);
391 }
392}
393
394QQuickV4ParticleData QQuickParticleData::v4Value(QQuickParticleSystem *particleSystem)
395{
396 return QQuickV4ParticleData(this, particleSystem);
397}
398
399void QQuickParticleData::debugDump(QQuickParticleSystem* particleSystem) const
400{
401 qDebug() << "Particle" << systemIndex << groupId << "/" << index << stillAlive(particleSystem)
402 << "Pos: " << x << "," << y
403 << "Vel: " << vx << "," << vy
404 << "Acc: " << ax << "," << ay
405 << "Size: " << size << "," << endSize
406 << "Time: " << t << "," <<lifeSpan << ";" << (particleSystem->timeInt / 1000.0) ;
407}
408
409void QQuickParticleData::extendLife(float time, QQuickParticleSystem* particleSystem)
410{
411 qreal newX = curX(particleSystem);
412 qreal newY = curY(particleSystem);
413 qreal newVX = curVX(particleSystem);
414 qreal newVY = curVY(particleSystem);
415
416 t += time;
417 animT += time;
418
419 qreal elapsed = (particleSystem->timeInt / 1000.0) - t;
420 qreal evy = newVY - elapsed*ay;
421 qreal ey = newY - elapsed*evy - 0.5 * elapsed*elapsed*ay;
422 qreal evx = newVX - elapsed*ax;
423 qreal ex = newX - elapsed*evx - 0.5 * elapsed*elapsed*ax;
424
425 x = ex;
426 vx = evx;
427 y = ey;
428 vy = evy;
429}
430
431QQuickParticleSystem::QQuickParticleSystem(QQuickItem *parent) :
432 QQuickItem(parent),
433 stateEngine(nullptr),
434 nextFreeGroupId(0),
435 m_animation(nullptr),
436 m_running(true),
437 initialized(0),
438 particleCount(0),
439 m_nextIndex(0),
440 m_componentComplete(false),
441 m_paused(false),
442 m_empty(true)
443{
444 m_debugMode = qmlParticlesDebug();
445}
446
447template<typename T>
448void unsetSystem(const QList<QPointer<T>> &elements)
449{
450 for (T *element : elements) {
451 if (element)
452 element->setSystem(nullptr);
453 }
454}
455
456QQuickParticleSystem::~QQuickParticleSystem()
457{
458 unsetSystem(std::exchange(m_emitters, {}));
459 unsetSystem(std::exchange(m_affectors, {}));
460 unsetSystem(std::exchange(m_painters, {}));
461 qDeleteAll(groupData);
462}
463
464void QQuickParticleSystem::initGroups()
465{
466 m_reusableIndexes.clear();
467 m_nextIndex = 0;
468
469 qDeleteAll(groupData);
470 groupData.clear();
471 groupIds.clear();
472 nextFreeGroupId = 0;
473
474 for (auto e : std::as_const(m_emitters)) {
475 e->reclaculateGroupId();
476 }
477 for (QQuickParticlePainter *p : std::as_const(m_painters)) {
478 p->recalculateGroupIds();
479 }
480
481 QQuickParticleGroupData *pd = new QQuickParticleGroupData(QString(), this); // Default group
482 Q_ASSERT(pd->index == 0);
483 Q_UNUSED(pd);
484}
485
486void QQuickParticleSystem::registerParticlePainter(QQuickParticlePainter* p)
487{
488 if (m_debugMode)
489 qDebug() << "Registering Painter" << p << "to" << this;
490 //TODO: a way to Unregister emitters, painters and affectors
491 m_painters << QPointer<QQuickParticlePainter>(p);//###Set or uniqueness checking?
492 loadPainter(p);
493}
494
495void QQuickParticleSystem::registerParticleEmitter(QQuickParticleEmitter* e)
496{
497 if (m_debugMode)
498 qDebug() << "Registering Emitter" << e << "to" << this;
499 m_emitters << QPointer<QQuickParticleEmitter>(e);
500}
501
502void QQuickParticleSystem::finishRegisteringParticleEmitter(QQuickParticleEmitter* e)
503{
504 if (m_componentComplete)
505 emitterAdded(e);
506 e->reset();//Start, so that starttime factors appropriately
507}
508
509void QQuickParticleSystem::registerParticleAffector(QQuickParticleAffector* a)
510{
511 if (m_debugMode)
512 qDebug() << "Registering Affector" << a << "to" << this;
513 if (!m_affectors.contains(a))
514 m_affectors << QPointer<QQuickParticleAffector>(a);
515}
516
517void QQuickParticleSystem::unregisterParticlePainter(QQuickParticlePainter *p)
518{
519 if (m_debugMode)
520 qDebug() << "Unregistering Painter" << p << "from" << this;
521 m_painters.removeAll(p);
522 m_syncList.removeAll(p);
523 for (QQuickParticleGroupData *gd : std::as_const(groupData))
524 gd->painters.removeOne(p);
525}
526
527void QQuickParticleSystem::unregisterParticleEmitter(QQuickParticleEmitter *e)
528{
529 if (m_debugMode)
530 qDebug() << "Unregistering Emitter" << e << "from" << this;
531 m_emitters.removeAll(e);
532}
533
534void QQuickParticleSystem::unregisterParticleAffector(QQuickParticleAffector *a)
535{
536 if (m_debugMode)
537 qDebug() << "Unregistering Affector" << a << "from" << this;
538 m_affectors.removeAll(a);
539}
540
541void QQuickParticleSystem::registerParticleGroup(QQuickParticleGroup* g)
542{
543 if (m_debugMode)
544 qDebug() << "Registering Group" << g << "to" << this;
545 m_groups << QPointer<QQuickParticleGroup>(g);
546 createEngine();
547}
548
549void QQuickParticleSystem::setRunning(bool arg)
550{
551 if (m_running != arg) {
552 m_running = arg;
553 emit runningChanged(arg);
554 setPaused(false);
555 if (m_animation)//Not created until componentCompleted
556 m_running ? m_animation->start() : m_animation->stop();
557 reset();
558 }
559}
560
561void QQuickParticleSystem::setPaused(bool arg) {
562 if (m_paused != arg) {
563 m_paused = arg;
564 if (m_animation && m_animation->state() != QAbstractAnimation::Stopped)
565 m_paused ? m_animation->pause() : m_animation->resume();
566 if (!m_paused) {
567 for (QQuickParticlePainter *p : std::as_const(m_painters)) {
568 if (p) {
569 p->update();
570 }
571 }
572 }
573 emit pausedChanged(arg);
574 }
575}
576
577void QQuickParticleSystem::statePropertyRedirect(QQmlListProperty<QObject> *prop, QObject *value)
578{
579 //Hooks up automatic state-associated stuff
580 QQuickParticleSystem* sys = qobject_cast<QQuickParticleSystem*>(prop->object->parent());
581 QQuickParticleGroup* group = qobject_cast<QQuickParticleGroup*>(prop->object);
582 if (!group || !sys || !value)
583 return;
584 stateRedirect(group, sys, value);
585}
586
587void QQuickParticleSystem::stateRedirect(QQuickParticleGroup* group, QQuickParticleSystem* sys, QObject *value)
588{
589 QStringList list;
590 list << group->name();
591 QQuickParticleAffector* a = qobject_cast<QQuickParticleAffector*>(value);
592 if (a) {
593 a->setParentItem(sys);
594 a->setGroups(list);
595 a->setSystem(sys);
596 return;
597 }
598 QQuickTrailEmitter* fe = qobject_cast<QQuickTrailEmitter*>(value);
599 if (fe) {
600 fe->setParentItem(sys);
601 fe->setFollow(group->name());
602 fe->setSystem(sys);
603 return;
604 }
605 QQuickParticleEmitter* e = qobject_cast<QQuickParticleEmitter*>(value);
606 if (e) {
607 e->setParentItem(sys);
608 e->setGroup(group->name());
609 e->setSystem(sys);
610 return;
611 }
612 QQuickParticlePainter* p = qobject_cast<QQuickParticlePainter*>(value);
613 if (p) {
614 p->setParentItem(sys);
615 p->setGroups(list);
616 p->setSystem(sys);
617 return;
618 }
619 qWarning() << value << " was placed inside a particle system state but cannot be taken into the particle system. It will be lost.";
620}
621
622
623int QQuickParticleSystem::registerParticleGroupData(const QString &name, QQuickParticleGroupData *pgd)
624{
625 Q_ASSERT(!groupIds.contains(name));
626 int id;
627 if (nextFreeGroupId >= groupData.size()) {
628 groupData.push_back(pgd);
629 nextFreeGroupId = groupData.size();
630 id = nextFreeGroupId - 1;
631 } else {
632 id = nextFreeGroupId;
633 groupData[id] = pgd;
634 searchNextFreeGroupId();
635 }
636 groupIds.insert(name, id);
637 return id;
638}
639
640void QQuickParticleSystem::searchNextFreeGroupId()
641{
642 ++nextFreeGroupId;
643 for (int ei = groupData.size(); nextFreeGroupId != ei; ++nextFreeGroupId) {
644 if (groupData[nextFreeGroupId] == nullptr) {
645 return;
646 }
647 }
648}
649
650void QQuickParticleSystem::componentComplete()
651
652{
653 QQuickItem::componentComplete();
654 m_componentComplete = true;
655 m_animation = new QQuickParticleSystemAnimation(this);
656 reset();//restarts animation as well
657}
658
659void QQuickParticleSystem::reset()
660{
661 if (!m_componentComplete)
662 return;
663
664 timeInt = 0;
665 //Clear guarded pointers which have been deleted
666 m_emitters.removeAll(nullptr);
667 m_painters.removeAll(nullptr);
668 m_affectors.removeAll(nullptr);
669
670 bySysIdx.resize(0);
671 initGroups();//Also clears all logical particles
672
673 if (!m_running)
674 return;
675
676 for (QQuickParticleEmitter *e : std::as_const(m_emitters))
677 e->reset();
678
679 emittersChanged();
680
681 for (QQuickParticlePainter *p : std::as_const(m_painters)) {
682 loadPainter(p);
683 p->reset();
684 }
685
686 //### Do affectors need reset too?
687 if (m_animation) {//Animation is explicitly disabled in benchmarks
688 //reset restarts animation (if running)
689 if ((m_animation->state() == QAbstractAnimation::Running))
690 m_animation->stop();
691 m_animation->start();
692 if (m_paused)
693 m_animation->pause();
694 }
695
696 initialized = true;
697}
698
699
700void QQuickParticleSystem::loadPainter(QQuickParticlePainter *painter)
701{
702 if (!m_componentComplete || !painter)
703 return;
704
705 for (QQuickParticleGroupData *sg : groupData) {
706 sg->painters.removeOne(painter);
707 }
708
709 int particleCount = 0;
710 if (painter->groups().isEmpty()) {//Uses default particle
711 static QStringList def = QStringList() << QString();
712 painter->setGroups(def);
713 particleCount += groupData[0]->size();
714 groupData[0]->painters << painter;
715 } else {
716 for (auto groupId : painter->groupIds()) {
717 QQuickParticleGroupData *gd = groupData[groupId];
718 particleCount += gd->size();
719 gd->painters << painter;
720 }
721 }
722 painter->setCount(particleCount);
723 painter->update();//Initial update here
724 return;
725}
726
727void QQuickParticleSystem::emittersChanged()
728{
729 if (!m_componentComplete)
730 return;
731
732 QList<int> previousSizes;
733 QList<int> newSizes;
734 previousSizes.reserve(groupData.size());
735 newSizes.reserve(groupData.size());
736 for (int i = 0, ei = groupData.size(); i != ei; ++i) {
737 previousSizes << groupData[i]->size();
738 newSizes << 0;
739 }
740
741 // Populate groups and set sizes.
742 for (int i = 0; i < m_emitters.size(); ) {
743 QQuickParticleEmitter *e = m_emitters.at(i);
744 if (!e) {
745 m_emitters.removeAt(i);
746 continue;
747 }
748
749 int groupId = e->groupId();
750 if (groupId == QQuickParticleGroupData::InvalidID) {
751 groupId = (new QQuickParticleGroupData(e->group(), this))->index;
752 previousSizes << 0;
753 newSizes << 0;
754 }
755 newSizes[groupId] += e->particleCount();
756 //###: Cull emptied groups?
757
758 ++i;
759 }
760
761 //TODO: Garbage collection?
762 particleCount = 0;
763 for (int i = 0, ei = groupData.size(); i != ei; ++i) {
764 groupData[i]->setSize(qMax(newSizes[i], previousSizes[i]));
765 particleCount += groupData[i]->size();
766 }
767
768 postProcessEmitters();
769}
770
771void QQuickParticleSystem::postProcessEmitters()
772{
773 if (m_debugMode)
774 qDebug() << "Particle system emitters changed. New particle count: " << particleCount << "in" << groupData.size() << "groups.";
775
776 if (particleCount > bySysIdx.size())//New datum requests haven't updated it
777 bySysIdx.resize(particleCount);
778
779 for (QQuickParticleAffector *a : std::as_const(m_affectors)) {//Groups may have changed
780 if (a) {
781 a->m_updateIntSet = true;
782 }
783 }
784
785 for (QQuickParticlePainter *p : std::as_const(m_painters))
786 loadPainter(p);
787
788 if (!m_groups.isEmpty())
789 createEngine();
790
791}
792
793void QQuickParticleSystem::emitterAdded(QQuickParticleEmitter *e)
794{
795 if (!m_componentComplete)
796 return;
797
798 // Populate group and set size.
799 const int groupId = e->groupId();
800 if (groupId == QQuickParticleGroupData::InvalidID) {
801 QQuickParticleGroupData *group = new QQuickParticleGroupData(e->group(), this);
802 group->setSize(e->particleCount());
803 } else {
804 QQuickParticleGroupData *group = groupData[groupId];
805 group->setSize(group->size() + e->particleCount());
806 }
807
808 // groupData can have changed independently, so we still have to iterate it all
809 // to count the particles.
810 particleCount = 0;
811 for (int i = 0, ei = groupData.size(); i != ei; ++i)
812 particleCount += groupData[i]->size();
813
814 postProcessEmitters();
815}
816
817void QQuickParticleSystem::createEngine()
818{
819 if (!m_componentComplete)
820 return;
821 if (stateEngine && m_debugMode)
822 qDebug() << "Resetting Existing Sprite Engine...";
823 //### Solve the losses if size/states go down
824 for (QQuickParticleGroup *group : std::as_const(m_groups)) {
825 bool exists = false;
826 for (auto it = groupIds.keyBegin(), end = groupIds.keyEnd(); it != end; ++it) {
827 if (group->name() == *it) {
828 exists = true;
829 break;
830 }
831 }
832 if (!exists) {
833 new QQuickParticleGroupData(group->name(), this);
834 }
835 }
836
837 if (m_groups.size()) {
838 //Reorder groups List so as to have the same order as groupData
839 // TODO: can't we just merge the two lists?
840 QList<QQuickParticleGroup*> newList;
841 for (int i = 0, ei = groupData.size(); i != ei; ++i) {
842 bool exists = false;
843 QString name = groupData[i]->name();
844 for (QQuickParticleGroup *existing : std::as_const(m_groups)) {
845 if (existing->name() == name) {
846 newList << existing;
847 exists = true;
848 }
849 }
850 if (!exists) {
851 newList << new QQuickParticleGroup(this);
852 newList.back()->setName(name);
853 }
854 }
855 m_groups = newList;
856 QList<QQuickStochasticState*> states;
857 states.reserve(m_groups.size());
858 for (QQuickParticleGroup *g : std::as_const(m_groups))
859 states << (QQuickStochasticState*)g;
860
861 if (!stateEngine)
862 stateEngine = new QQuickStochasticEngine(this);
863 stateEngine->setCount(particleCount);
864 stateEngine->m_states = states;
865
866 connect(stateEngine, &QQuickStochasticEngine::stateChanged,
867 this, &QQuickParticleSystem::particleStateChange);
868
869 } else {
870 if (stateEngine)
871 delete stateEngine;
872 stateEngine = nullptr;
873 }
874
875}
876
877void QQuickParticleSystem::particleStateChange(int idx)
878{
879 moveGroups(bySysIdx[idx], stateEngine->curState(idx));
880}
881
882void QQuickParticleSystem::moveGroups(QQuickParticleData *d, int newGIdx)
883{
884 if (!d || newGIdx == d->groupId)
885 return;
886
887 QQuickParticleData *pd = newDatum(newGIdx, false, d->systemIndex, d);
888 if (!pd)
889 return;
890
891 finishNewDatum(pd);
892
893 d->systemIndex = -1;
894 groupData[d->groupId]->kill(d);
895}
896
897int QQuickParticleSystem::nextSystemIndex()
898{
899 if (!m_reusableIndexes.isEmpty()) {
900 int ret = *(m_reusableIndexes.begin());
901 m_reusableIndexes.remove(ret);
902 return ret;
903 }
904 if (m_nextIndex >= bySysIdx.size()) {
905 bySysIdx.resize(bySysIdx.size() < 10 ? 10 : bySysIdx.size()*1.1);//###+1,10%,+10? Choose something non-arbitrarily
906 if (stateEngine)
907 stateEngine->setCount(bySysIdx.size());
908
909 }
910 return m_nextIndex++;
911}
912
913QQuickParticleData *QQuickParticleSystem::newDatum(
914 int groupId, bool respectLimits, int sysIndex,
915 const QQuickParticleData *cloneFrom)
916{
917 Q_ASSERT(groupId < groupData.size());//XXX shouldn't really be an assert
918
919 QQuickParticleData *ret = groupData[groupId]->newDatum(respectLimits);
920 if (!ret)
921 return nullptr;
922
923 if (cloneFrom) {
924 // We need to retain the "identity" information of the new particle data since it may be
925 // "recycled" and still be tracked.
926 const int retainedIndex = ret->index;
927 const int retainedGroupId = ret->groupId;
928 const int retainedSystemIndex = ret->systemIndex;
929 *ret = *cloneFrom;
930 ret->index = retainedIndex;
931 ret->groupId = retainedGroupId;
932 ret->systemIndex = retainedSystemIndex;
933 }
934
935 if (sysIndex == -1) {
936 if (ret->systemIndex == -1)
937 ret->systemIndex = nextSystemIndex();
938 } else {
939 if (ret->systemIndex != -1) {
940 if (stateEngine)
941 stateEngine->stop(ret->systemIndex);
942 m_reusableIndexes << ret->systemIndex;
943 bySysIdx[ret->systemIndex] = 0;
944 }
945 ret->systemIndex = sysIndex;
946 }
947 bySysIdx[ret->systemIndex] = ret;
948
949 if (stateEngine)
950 stateEngine->start(ret->systemIndex, ret->groupId);
951
952 m_empty = false;
953 return ret;
954}
955
956void QQuickParticleSystem::emitParticle(QQuickParticleData* pd, QQuickParticleEmitter* particleEmitter)
957{// called from prepareNextFrame()->emitWindow - enforce?
958 //Account for relative emitter position
959 bool okay = false;
960 QTransform t = particleEmitter->itemTransform(this, &okay);
961 if (okay) {
962 qreal tx,ty;
963 t.map(pd->x, pd->y, &tx, &ty);
964 pd->x = tx;
965 pd->y = ty;
966 }
967
968 finishNewDatum(pd);
969}
970
971void QQuickParticleSystem::finishNewDatum(QQuickParticleData *pd)
972{
973 Q_ASSERT(pd);
974 groupData[pd->groupId]->prepareRecycler(pd);
975
976 for (QQuickParticleAffector *a : std::as_const(m_affectors))
977 if (a && a->m_needsReset)
978 a->reset(pd);
979 for (QQuickParticlePainter *p : std::as_const(groupData[pd->groupId]->painters))
980 if (p)
981 p->load(pd);
982}
983
984void QQuickParticleSystem::updateCurrentTime( int currentTime )
985{
986 if (!initialized)
987 return;//error in initialization
988
989 //### Elapsed time never shrinks - may cause problems if left emitting for weeks at a time.
990 qreal dt = timeInt / 1000.;
991 timeInt = currentTime;
992 qreal time = timeInt / 1000.;
993 dt = time - dt;
994 needsReset.clear();
995
996 m_emitters.removeAll(nullptr);
997 m_painters.removeAll(nullptr);
998 m_affectors.removeAll(nullptr);
999
1000 bool oldClear = m_empty;
1001 m_empty = true;
1002 for (QQuickParticleGroupData *gd : std::as_const(groupData))//Recycle all groups and see if they're out of live particles
1003 m_empty = gd->recycle() && m_empty;
1004
1005 if (stateEngine)
1006 stateEngine->updateSprites(timeInt);
1007
1008 for (QQuickParticleEmitter *emitter : std::as_const(m_emitters))
1009 emitter->emitWindow(timeInt);
1010 for (QQuickParticleAffector *a : std::as_const(m_affectors))
1011 a->affectSystem(dt);
1012 for (QQuickParticleData *d : needsReset)
1013 for (QQuickParticlePainter *p : std::as_const(groupData[d->groupId]->painters))
1014 p->reload(d);
1015
1016 if (oldClear != m_empty)
1017 emptyChanged(m_empty);
1018}
1019
1020int QQuickParticleSystem::systemSync(QQuickParticlePainter* p)
1021{
1022 if (!m_running)
1023 return 0;
1024 if (!initialized)
1025 return 0;//error in initialization
1026 p->performPendingCommits();
1027 return timeInt;
1028}
1029
1030
1031QT_END_NAMESPACE
1032
1033#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)