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
4#include "qquickparticlesystem_p.h"
5#include <QtQuick/qsgnode.h>
6#include "qquickparticleemitter_p.h"
7#include "qquickparticleaffector_p.h"
8#include "qquickparticlepainter_p.h"
9#include <private/qquickspriteengine_p.h>
10#include <private/qquicksprite_p.h>
11#include "qquickv4particledata_p.h"
12#include "qquickparticlegroup_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
21QT_BEGIN_NAMESPACE
22//###Switch to define later, for now user-friendly (no compilation) debugging is worth it
23DEFINE_BOOL_CONFIG_OPTION(qmlParticlesDebug, QML_PARTICLES_DEBUG)
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 QtQuick.Particles::ParticleSystem::pause()
124
125 Pauses the simulation if it is running.
126
127 \sa resume, paused
128*/
129
130/*!
131 \qmlmethod QtQuick.Particles::ParticleSystem::resume()
132
133 Resumes the simulation if it is paused.
134
135 \sa pause, paused
136*/
137
138/*!
139 \qmlmethod 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 QtQuick.Particles::ParticleSystem::stop()
148
149 Stops the simulation if it is running.
150
151 \sa start, restart, running
152*/
153
154/*!
155 \qmlmethod 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 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(d: a*1000.0);
171}
172
173QQuickParticleDataHeap::QQuickParticleDataHeap()
174 : m_data(0)
175{
176 m_data.reserve(asize: 1000);
177 clear();
178}
179
180void QQuickParticleDataHeap::grow() //###Consider automatic growth vs resize() calls from GroupData
181{
182 m_data.resize(size: qsizetype(1) << ++m_size);
183}
184
185void QQuickParticleDataHeap::insert(QQuickParticleData* data)
186{
187 insertTimed(data, time: roundedTime(a: 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(key: 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(value: data);
202 m_lookups.insert(key: time, value: 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(key: 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(size: 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(value: 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(a: idx, b: parent);
260 bubbleUp(idx: 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(a: idx, b: lesser);
277 bubbleDown(idx: lesser);
278 }
279}
280
281QQuickParticleGroupData::QQuickParticleGroupData(const QString &name, QQuickParticleSystem* sys)
282 : index(sys->registerParticleGroupData(name, pgd: this))
283 , m_size(0)
284 , m_system(sys)
285{
286 initList();
287}
288
289QQuickParticleGroupData::~QQuickParticleGroupData()
290{
291 for (QQuickParticleData *d : std::as_const(t&: data))
292 delete d;
293}
294
295QString QQuickParticleGroupData::name() const//### Worth caching as well?
296{
297 return m_system->groupIds.key(value: 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(size: 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(t&: 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(t&: painters))
328 p->reload(d);
329 freeList.free(index: 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(system: m_system)) {// ### This means resurrection of 'dead' particles. Is that allowed?
339 prepareRecycler(d: 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(system: m_system)) {
361 freeList.free(index: datum->index);
362 } else {
363 m_latestAliveParticles.push_back(t: datum);
364 }
365 }
366 }
367
368 for (auto particle : m_latestAliveParticles)
369 prepareRecycler(d: 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(data: d);
379 } else {
380 int extend = 2 * m_system->maxLife / 3;
381 while ((roundedTime(a: d->t) + extend) <= m_system->timeInt)
382 d->extendLife(time: m_system->maxLife / 3000.0, particleSystem: m_system);
383 dataHeap.insertTimed(data: d, time: roundedTime(a: 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(system: 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
440QQuickParticleSystem::~QQuickParticleSystem()
441{
442 for (QQuickParticleGroupData *gd : std::as_const(t&: groupData))
443 delete gd;
444}
445
446void QQuickParticleSystem::initGroups()
447{
448 m_reusableIndexes.clear();
449 m_nextIndex = 0;
450
451 qDeleteAll(c: groupData);
452 groupData.clear();
453 groupIds.clear();
454 nextFreeGroupId = 0;
455
456 for (auto e : std::as_const(t&: m_emitters)) {
457 e->reclaculateGroupId();
458 }
459 for (QQuickParticlePainter *p : std::as_const(t&: m_painters)) {
460 p->recalculateGroupIds();
461 }
462
463 QQuickParticleGroupData *pd = new QQuickParticleGroupData(QString(), this); // Default group
464 Q_ASSERT(pd->index == 0);
465 Q_UNUSED(pd);
466}
467
468void QQuickParticleSystem::registerParticlePainter(QQuickParticlePainter* p)
469{
470 if (m_debugMode)
471 qDebug() << "Registering Painter" << p << "to" << this;
472 //TODO: a way to Unregister emitters, painters and affectors
473 m_painters << QPointer<QQuickParticlePainter>(p);//###Set or uniqueness checking?
474
475 connect(sender: p, signal: &QQuickParticlePainter::groupsChanged, context: this, slot: [this, p] { this->loadPainter(p); }, type: Qt::QueuedConnection);
476 loadPainter(p);
477}
478
479void QQuickParticleSystem::registerParticleEmitter(QQuickParticleEmitter* e)
480{
481 if (m_debugMode)
482 qDebug() << "Registering Emitter" << e << "to" << this;
483 m_emitters << QPointer<QQuickParticleEmitter>(e);//###How to get them out?
484}
485
486void QQuickParticleSystem::finishRegisteringParticleEmitter(QQuickParticleEmitter* e)
487{
488 connect(sender: e, signal: &QQuickParticleEmitter::particleCountChanged,
489 context: this, slot: &QQuickParticleSystem::emittersChanged);
490 connect(sender: e, signal: &QQuickParticleEmitter::groupChanged,
491 context: this, slot: &QQuickParticleSystem::emittersChanged);
492 if (m_componentComplete)
493 emitterAdded(e);
494 e->reset();//Start, so that starttime factors appropriately
495}
496
497void QQuickParticleSystem::registerParticleAffector(QQuickParticleAffector* a)
498{
499 if (m_debugMode)
500 qDebug() << "Registering Affector" << a << "to" << this;
501 if (!m_affectors.contains(t: a))
502 m_affectors << QPointer<QQuickParticleAffector>(a);
503}
504
505void QQuickParticleSystem::registerParticleGroup(QQuickParticleGroup* g)
506{
507 if (m_debugMode)
508 qDebug() << "Registering Group" << g << "to" << this;
509 m_groups << QPointer<QQuickParticleGroup>(g);
510 createEngine();
511}
512
513void QQuickParticleSystem::setRunning(bool arg)
514{
515 if (m_running != arg) {
516 m_running = arg;
517 emit runningChanged(arg);
518 setPaused(false);
519 if (m_animation)//Not created until componentCompleted
520 m_running ? m_animation->start() : m_animation->stop();
521 reset();
522 }
523}
524
525void QQuickParticleSystem::setPaused(bool arg) {
526 if (m_paused != arg) {
527 m_paused = arg;
528 if (m_animation && m_animation->state() != QAbstractAnimation::Stopped)
529 m_paused ? m_animation->pause() : m_animation->resume();
530 if (!m_paused) {
531 for (QQuickParticlePainter *p : std::as_const(t&: m_painters)) {
532 if (p) {
533 p->update();
534 }
535 }
536 }
537 emit pausedChanged(arg);
538 }
539}
540
541void QQuickParticleSystem::statePropertyRedirect(QQmlListProperty<QObject> *prop, QObject *value)
542{
543 //Hooks up automatic state-associated stuff
544 QQuickParticleSystem* sys = qobject_cast<QQuickParticleSystem*>(object: prop->object->parent());
545 QQuickParticleGroup* group = qobject_cast<QQuickParticleGroup*>(object: prop->object);
546 if (!group || !sys || !value)
547 return;
548 stateRedirect(group, sys, value);
549}
550
551void QQuickParticleSystem::stateRedirect(QQuickParticleGroup* group, QQuickParticleSystem* sys, QObject *value)
552{
553 QStringList list;
554 list << group->name();
555 QQuickParticleAffector* a = qobject_cast<QQuickParticleAffector*>(object: value);
556 if (a) {
557 a->setParentItem(sys);
558 a->setGroups(list);
559 a->setSystem(sys);
560 return;
561 }
562 QQuickTrailEmitter* fe = qobject_cast<QQuickTrailEmitter*>(object: value);
563 if (fe) {
564 fe->setParentItem(sys);
565 fe->setFollow(group->name());
566 fe->setSystem(sys);
567 return;
568 }
569 QQuickParticleEmitter* e = qobject_cast<QQuickParticleEmitter*>(object: value);
570 if (e) {
571 e->setParentItem(sys);
572 e->setGroup(group->name());
573 e->setSystem(sys);
574 return;
575 }
576 QQuickParticlePainter* p = qobject_cast<QQuickParticlePainter*>(object: value);
577 if (p) {
578 p->setParentItem(sys);
579 p->setGroups(list);
580 p->setSystem(sys);
581 return;
582 }
583 qWarning() << value << " was placed inside a particle system state but cannot be taken into the particle system. It will be lost.";
584}
585
586
587int QQuickParticleSystem::registerParticleGroupData(const QString &name, QQuickParticleGroupData *pgd)
588{
589 Q_ASSERT(!groupIds.contains(name));
590 int id;
591 if (nextFreeGroupId >= groupData.size()) {
592 groupData.push_back(t: pgd);
593 nextFreeGroupId = groupData.size();
594 id = nextFreeGroupId - 1;
595 } else {
596 id = nextFreeGroupId;
597 groupData[id] = pgd;
598 searchNextFreeGroupId();
599 }
600 groupIds.insert(key: name, value: id);
601 return id;
602}
603
604void QQuickParticleSystem::searchNextFreeGroupId()
605{
606 ++nextFreeGroupId;
607 for (int ei = groupData.size(); nextFreeGroupId != ei; ++nextFreeGroupId) {
608 if (groupData[nextFreeGroupId] == nullptr) {
609 return;
610 }
611 }
612}
613
614void QQuickParticleSystem::componentComplete()
615
616{
617 QQuickItem::componentComplete();
618 m_componentComplete = true;
619 m_animation = new QQuickParticleSystemAnimation(this);
620 reset();//restarts animation as well
621}
622
623void QQuickParticleSystem::reset()
624{
625 if (!m_componentComplete)
626 return;
627
628 timeInt = 0;
629 //Clear guarded pointers which have been deleted
630 m_emitters.removeAll(t: nullptr);
631 m_painters.removeAll(t: nullptr);
632 m_affectors.removeAll(t: nullptr);
633
634 bySysIdx.resize(size: 0);
635 initGroups();//Also clears all logical particles
636
637 if (!m_running)
638 return;
639
640 for (QQuickParticleEmitter *e : std::as_const(t&: m_emitters))
641 e->reset();
642
643 emittersChanged();
644
645 for (QQuickParticlePainter *p : std::as_const(t&: m_painters)) {
646 loadPainter(p);
647 p->reset();
648 }
649
650 //### Do affectors need reset too?
651 if (m_animation) {//Animation is explicitly disabled in benchmarks
652 //reset restarts animation (if running)
653 if ((m_animation->state() == QAbstractAnimation::Running))
654 m_animation->stop();
655 m_animation->start();
656 if (m_paused)
657 m_animation->pause();
658 }
659
660 initialized = true;
661}
662
663
664void QQuickParticleSystem::loadPainter(QQuickParticlePainter *painter)
665{
666 if (!m_componentComplete || !painter)
667 return;
668
669 for (QQuickParticleGroupData *sg : groupData) {
670 sg->painters.removeOne(element: painter);
671 }
672
673 int particleCount = 0;
674 if (painter->groups().isEmpty()) {//Uses default particle
675 static QStringList def = QStringList() << QString();
676 painter->setGroups(def);
677 particleCount += groupData[0]->size();
678 groupData[0]->painters << painter;
679 } else {
680 for (auto groupId : painter->groupIds()) {
681 QQuickParticleGroupData *gd = groupData[groupId];
682 particleCount += gd->size();
683 gd->painters << painter;
684 }
685 }
686 painter->setCount(particleCount);
687 painter->update();//Initial update here
688 return;
689}
690
691void QQuickParticleSystem::emittersChanged()
692{
693 if (!m_componentComplete)
694 return;
695
696 QVector<int> previousSizes;
697 QVector<int> newSizes;
698 previousSizes.reserve(asize: groupData.size());
699 newSizes.reserve(asize: groupData.size());
700 for (int i = 0, ei = groupData.size(); i != ei; ++i) {
701 previousSizes << groupData[i]->size();
702 newSizes << 0;
703 }
704
705 // Populate groups and set sizes.
706 for (int i = 0; i < m_emitters.size(); ) {
707 QQuickParticleEmitter *e = m_emitters.at(i);
708 if (!e) {
709 m_emitters.removeAt(i);
710 continue;
711 }
712
713 int groupId = e->groupId();
714 if (groupId == QQuickParticleGroupData::InvalidID) {
715 groupId = (new QQuickParticleGroupData(e->group(), this))->index;
716 previousSizes << 0;
717 newSizes << 0;
718 }
719 newSizes[groupId] += e->particleCount();
720 //###: Cull emptied groups?
721
722 ++i;
723 }
724
725 //TODO: Garbage collection?
726 particleCount = 0;
727 for (int i = 0, ei = groupData.size(); i != ei; ++i) {
728 groupData[i]->setSize(qMax(a: newSizes[i], b: previousSizes[i]));
729 particleCount += groupData[i]->size();
730 }
731
732 postProcessEmitters();
733}
734
735void QQuickParticleSystem::postProcessEmitters()
736{
737 if (m_debugMode)
738 qDebug() << "Particle system emitters changed. New particle count: " << particleCount << "in" << groupData.size() << "groups.";
739
740 if (particleCount > bySysIdx.size())//New datum requests haven't updated it
741 bySysIdx.resize(size: particleCount);
742
743 for (QQuickParticleAffector *a : std::as_const(t&: m_affectors)) {//Groups may have changed
744 if (a) {
745 a->m_updateIntSet = true;
746 }
747 }
748
749 for (QQuickParticlePainter *p : std::as_const(t&: m_painters))
750 loadPainter(painter: p);
751
752 if (!m_groups.isEmpty())
753 createEngine();
754
755}
756
757void QQuickParticleSystem::emitterAdded(QQuickParticleEmitter *e)
758{
759 if (!m_componentComplete)
760 return;
761
762 // Populate group and set size.
763 const int groupId = e->groupId();
764 if (groupId == QQuickParticleGroupData::InvalidID) {
765 QQuickParticleGroupData *group = new QQuickParticleGroupData(e->group(), this);
766 group->setSize(e->particleCount());
767 } else {
768 QQuickParticleGroupData *group = groupData[groupId];
769 group->setSize(group->size() + e->particleCount());
770 }
771
772 // groupData can have changed independently, so we still have to iterate it all
773 // to count the particles.
774 particleCount = 0;
775 for (int i = 0, ei = groupData.size(); i != ei; ++i)
776 particleCount += groupData[i]->size();
777
778 postProcessEmitters();
779}
780
781void QQuickParticleSystem::createEngine()
782{
783 if (!m_componentComplete)
784 return;
785 if (stateEngine && m_debugMode)
786 qDebug() << "Resetting Existing Sprite Engine...";
787 //### Solve the losses if size/states go down
788 for (QQuickParticleGroup *group : std::as_const(t&: m_groups)) {
789 bool exists = false;
790 for (auto it = groupIds.keyBegin(), end = groupIds.keyEnd(); it != end; ++it) {
791 if (group->name() == *it) {
792 exists = true;
793 break;
794 }
795 }
796 if (!exists) {
797 new QQuickParticleGroupData(group->name(), this);
798 }
799 }
800
801 if (m_groups.size()) {
802 //Reorder groups List so as to have the same order as groupData
803 // TODO: can't we just merge the two lists?
804 QList<QQuickParticleGroup*> newList;
805 for (int i = 0, ei = groupData.size(); i != ei; ++i) {
806 bool exists = false;
807 QString name = groupData[i]->name();
808 for (QQuickParticleGroup *existing : std::as_const(t&: m_groups)) {
809 if (existing->name() == name) {
810 newList << existing;
811 exists = true;
812 }
813 }
814 if (!exists) {
815 newList << new QQuickParticleGroup(this);
816 newList.back()->setName(name);
817 }
818 }
819 m_groups = newList;
820 QList<QQuickStochasticState*> states;
821 states.reserve(asize: m_groups.size());
822 for (QQuickParticleGroup *g : std::as_const(t&: m_groups))
823 states << (QQuickStochasticState*)g;
824
825 if (!stateEngine)
826 stateEngine = new QQuickStochasticEngine(this);
827 stateEngine->setCount(particleCount);
828 stateEngine->m_states = states;
829
830 connect(sender: stateEngine, signal: &QQuickStochasticEngine::stateChanged,
831 context: this, slot: &QQuickParticleSystem::particleStateChange);
832
833 } else {
834 if (stateEngine)
835 delete stateEngine;
836 stateEngine = nullptr;
837 }
838
839}
840
841void QQuickParticleSystem::particleStateChange(int idx)
842{
843 moveGroups(d: bySysIdx[idx], newGIdx: stateEngine->curState(index: idx));
844}
845
846void QQuickParticleSystem::moveGroups(QQuickParticleData *d, int newGIdx)
847{
848 if (!d || newGIdx == d->groupId)
849 return;
850
851 QQuickParticleData *pd = newDatum(groupId: newGIdx, respectLimits: false, sysIdx: d->systemIndex, cloneFrom: d);
852 if (!pd)
853 return;
854
855 finishNewDatum(pd);
856
857 d->systemIndex = -1;
858 groupData[d->groupId]->kill(d);
859}
860
861int QQuickParticleSystem::nextSystemIndex()
862{
863 if (!m_reusableIndexes.isEmpty()) {
864 int ret = *(m_reusableIndexes.begin());
865 m_reusableIndexes.remove(value: ret);
866 return ret;
867 }
868 if (m_nextIndex >= bySysIdx.size()) {
869 bySysIdx.resize(size: bySysIdx.size() < 10 ? 10 : bySysIdx.size()*1.1);//###+1,10%,+10? Choose something non-arbitrarily
870 if (stateEngine)
871 stateEngine->setCount(bySysIdx.size());
872
873 }
874 return m_nextIndex++;
875}
876
877QQuickParticleData *QQuickParticleSystem::newDatum(
878 int groupId, bool respectLimits, int sysIndex,
879 const QQuickParticleData *cloneFrom)
880{
881 Q_ASSERT(groupId < groupData.size());//XXX shouldn't really be an assert
882
883 QQuickParticleData *ret = groupData[groupId]->newDatum(respectsLimits: respectLimits);
884 if (!ret)
885 return nullptr;
886
887 if (cloneFrom) {
888 // We need to retain the "identity" information of the new particle data since it may be
889 // "recycled" and still be tracked.
890 const int retainedIndex = ret->index;
891 const int retainedGroupId = ret->groupId;
892 const int retainedSystemIndex = ret->systemIndex;
893 *ret = *cloneFrom;
894 ret->index = retainedIndex;
895 ret->groupId = retainedGroupId;
896 ret->systemIndex = retainedSystemIndex;
897 }
898
899 if (sysIndex == -1) {
900 if (ret->systemIndex == -1)
901 ret->systemIndex = nextSystemIndex();
902 } else {
903 if (ret->systemIndex != -1) {
904 if (stateEngine)
905 stateEngine->stop(index: ret->systemIndex);
906 m_reusableIndexes << ret->systemIndex;
907 bySysIdx[ret->systemIndex] = 0;
908 }
909 ret->systemIndex = sysIndex;
910 }
911 bySysIdx[ret->systemIndex] = ret;
912
913 if (stateEngine)
914 stateEngine->start(index: ret->systemIndex, state: ret->groupId);
915
916 m_empty = false;
917 return ret;
918}
919
920void QQuickParticleSystem::emitParticle(QQuickParticleData* pd, QQuickParticleEmitter* particleEmitter)
921{// called from prepareNextFrame()->emitWindow - enforce?
922 //Account for relative emitter position
923 bool okay = false;
924 QTransform t = particleEmitter->itemTransform(this, &okay);
925 if (okay) {
926 qreal tx,ty;
927 t.map(x: pd->x, y: pd->y, tx: &tx, ty: &ty);
928 pd->x = tx;
929 pd->y = ty;
930 }
931
932 finishNewDatum(pd);
933}
934
935void QQuickParticleSystem::finishNewDatum(QQuickParticleData *pd)
936{
937 Q_ASSERT(pd);
938 groupData[pd->groupId]->prepareRecycler(d: pd);
939
940 for (QQuickParticleAffector *a : std::as_const(t&: m_affectors))
941 if (a && a->m_needsReset)
942 a->reset(pd);
943 for (QQuickParticlePainter *p : std::as_const(t&: groupData[pd->groupId]->painters))
944 if (p)
945 p->load(pd);
946}
947
948void QQuickParticleSystem::updateCurrentTime( int currentTime )
949{
950 if (!initialized)
951 return;//error in initialization
952
953 //### Elapsed time never shrinks - may cause problems if left emitting for weeks at a time.
954 qreal dt = timeInt / 1000.;
955 timeInt = currentTime;
956 qreal time = timeInt / 1000.;
957 dt = time - dt;
958 needsReset.clear();
959
960 m_emitters.removeAll(t: nullptr);
961 m_painters.removeAll(t: nullptr);
962 m_affectors.removeAll(t: nullptr);
963
964 bool oldClear = m_empty;
965 m_empty = true;
966 for (QQuickParticleGroupData *gd : std::as_const(t&: groupData))//Recycle all groups and see if they're out of live particles
967 m_empty = gd->recycle() && m_empty;
968
969 if (stateEngine)
970 stateEngine->updateSprites(time: timeInt);
971
972 for (QQuickParticleEmitter *emitter : std::as_const(t&: m_emitters))
973 emitter->emitWindow(timeStamp: timeInt);
974 for (QQuickParticleAffector *a : std::as_const(t&: m_affectors))
975 a->affectSystem(dt);
976 for (QQuickParticleData *d : needsReset)
977 for (QQuickParticlePainter *p : std::as_const(t&: groupData[d->groupId]->painters))
978 p->reload(d);
979
980 if (oldClear != m_empty)
981 emptyChanged(arg: m_empty);
982}
983
984int QQuickParticleSystem::systemSync(QQuickParticlePainter* p)
985{
986 if (!m_running)
987 return 0;
988 if (!initialized)
989 return 0;//error in initialization
990 p->performPendingCommits();
991 return timeInt;
992}
993
994
995QT_END_NAMESPACE
996
997#include "moc_qquickparticlesystem_p.cpp"
998

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qtdeclarative/src/particles/qquickparticlesystem.cpp