~loic.molinari/+junk/qtdeclarative-shadereffectsource-changes

« back to all changes in this revision

Viewing changes to src/particles/qquickparticlesystem.cpp

  • Committer: Loïc Molinari
  • Date: 2012-04-21 17:59:51 UTC
  • Revision ID: loic.molinari@canonical.com-20120421175951-bqx68caaf5zrp76l
Initial import

Show diffs side-by-side

added added

removed removed

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