blob: ea890d20f419d44f0cc4f59c386be3c4490bb698 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtQuick module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef PARTICLESYSTEM_H
#define PARTICLESYSTEM_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtQuick/QQuickItem>
#include <QElapsedTimer>
#include <QVector>
#include <QHash>
#include <QPointer>
#include <private/qquicksprite_p.h>
#include <QAbstractAnimation>
#include <QtQml/qqml.h>
#include <private/qv4util_p.h>
#include <private/qv4global_p.h>
#include <private/qv4staticvalue_p.h>
#include "qtquickparticlesglobal_p.h"
#include "qquickparticleflatset_p.h"
QT_BEGIN_NAMESPACE
template<class T, int Prealloc>
class QQuickParticleVarLengthArray: public QVarLengthArray<T, Prealloc>
{
public:
void insert(const T &element)
{
if (!this->contains(element)) {
this->append(element);
}
}
bool removeOne(const T &element)
{
for (int i = 0; i < this->size(); ++i) {
if (this->at(i) == element) {
this->remove(i);
return true;
}
}
return false;
}
};
class QQuickParticleSystem;
class QQuickParticleAffector;
class QQuickParticleEmitter;
class QQuickParticlePainter;
class QQuickParticleData;
class QQuickParticleSystemAnimation;
class QQuickStochasticEngine;
class QQuickSprite;
class QQuickV4ParticleData;
class QQuickParticleGroup;
class QQuickImageParticle;
struct QQuickParticleDataHeapNode{
int time;//in ms
QSet<QQuickParticleData*> data;//Set ptrs instead?
};
class Q_QUICKPARTICLES_PRIVATE_EXPORT QQuickParticleDataHeap {
//Idea is to do a binary heap, but which also stores a set of int,Node* so that if the int already exists, you can
//add it to the data* list. Pops return the whole list at once.
public:
QQuickParticleDataHeap();
void insert(QQuickParticleData* data);
void insertTimed(QQuickParticleData* data, int time);
int top();
QSet<QQuickParticleData*> pop();
void clear();
bool contains(QQuickParticleData*);//O(n), for debugging purposes only
private:
void grow();
void swap(int, int);
void bubbleUp(int);
void bubbleDown(int);
int m_size;
int m_end;
QQuickParticleDataHeapNode m_tmp;
QVector<QQuickParticleDataHeapNode> m_data;
QHash<int,int> m_lookups;
};
class Q_QUICKPARTICLES_PRIVATE_EXPORT QQuickParticleGroupData {
class FreeList
{
public:
FreeList() {}
void resize(int newSize)
{
Q_ASSERT(newSize >= 0);
int oldSize = isUnused.size();
isUnused.resize(newSize, true);
if (newSize > oldSize) {
if (firstUnused == UINT_MAX) {
firstUnused = oldSize;
} else {
firstUnused = std::min(firstUnused, unsigned(oldSize));
}
} else if (firstUnused >= unsigned(newSize)) {
firstUnused = UINT_MAX;
}
}
void free(int index)
{
isUnused.setBit(index);
firstUnused = std::min(firstUnused, unsigned(index));
--allocated;
}
int count() const
{ return allocated; }
bool hasUnusedEntries() const
{ return firstUnused != UINT_MAX; }
int alloc()
{
if (hasUnusedEntries()) {
int nextFree = firstUnused;
isUnused.clearBit(firstUnused);
firstUnused = isUnused.findNext(firstUnused, true, false);
if (firstUnused >= unsigned(isUnused.size())) {
firstUnused = UINT_MAX;
}
++allocated;
return nextFree;
} else {
return -1;
}
}
private:
QV4::BitVector isUnused;
unsigned firstUnused = UINT_MAX;
int allocated = 0;
};
public: // types
typedef int ID;
enum { InvalidID = -1, DefaultGroupID = 0 };
public:
QQuickParticleGroupData(const QString &name, QQuickParticleSystem* sys);
~QQuickParticleGroupData();
int size()
{ return m_size; }
QString name();
void setSize(int newSize);
const ID index;
QQuickParticleVarLengthArray<QQuickParticlePainter*, 4> painters;//TODO: What if they are dynamically removed?
//TODO: Refactor particle data list out into a separate class
QVector<QQuickParticleData*> data;
FreeList freeList;
QQuickParticleDataHeap dataHeap;
bool recycle(); //Force recycling round, returns true if all indexes are now reusable
void initList();
void kill(QQuickParticleData* d);
//After calling this, initialize, then call prepareRecycler(d)
QQuickParticleData* newDatum(bool respectsLimits);
//TODO: Find and clean up those that don't get added to the recycler (currently they get lost)
void prepareRecycler(QQuickParticleData* d);
private:
int m_size;
QQuickParticleSystem* m_system;
// Only used in recycle() for tracking of alive particles after latest recycling round
QVector<QQuickParticleData*> m_latestAliveParticles;
};
struct Color4ub {
uchar r;
uchar g;
uchar b;
uchar a;
};
class Q_QUICKPARTICLES_PRIVATE_EXPORT QQuickParticleData {
public:
//TODO: QObject like memory management (without the cost, just attached to system)
QQuickParticleData();
~QQuickParticleData();
QQuickParticleData(const QQuickParticleData &other);
QQuickParticleData &operator=(const QQuickParticleData &other);
//Convenience functions for working backwards, because parameters are from the start of particle life
//If setting multiple parameters at once, doing the conversion yourself will be faster.
//sets the x accleration without affecting the instantaneous x velocity or position
void setInstantaneousAX(float ax, QQuickParticleSystem *particleSystem);
//sets the x velocity without affecting the instantaneous x postion
void setInstantaneousVX(float vx, QQuickParticleSystem *particleSystem);
//sets the instantaneous x postion
void setInstantaneousX(float x, QQuickParticleSystem *particleSystem);
//sets the y accleration without affecting the instantaneous y velocity or position
void setInstantaneousAY(float ay, QQuickParticleSystem *particleSystem);
//sets the y velocity without affecting the instantaneous y postion
void setInstantaneousVY(float vy, QQuickParticleSystem *particleSystem);
//sets the instantaneous Y postion
void setInstantaneousY(float y, QQuickParticleSystem *particleSystem);
//TODO: Slight caching?
float curX(QQuickParticleSystem *particleSystem) const;
float curVX(QQuickParticleSystem *particleSystem) const;
float curAX() const { return ax; }
float curAX(QQuickParticleSystem *) const { return ax; } // used by the macros in qquickv4particledata.cpp
float curY(QQuickParticleSystem *particleSystem) const;
float curVY(QQuickParticleSystem *particleSystem) const;
float curAY() const { return ay; }
float curAY(QQuickParticleSystem *) const { return ay; } // used by the macros in qquickv4particledata.cpp
int index;
int systemIndex;
//General Position Stuff
float x;
float y;
float t;
float lifeSpan;
float size;
float endSize;
float vx;
float vy;
float ax;
float ay;
//Painter-specific stuff, now universally shared
//Used by ImageParticle color mode
Color4ub color;
//Used by ImageParticle deform mode
float xx;
float xy;
float yx;
float yy;
float rotation;
float rotationVelocity;
float autoRotate;//Assume that GPUs prefer floats to bools
//Used by ImageParticle Sprite mode
float animIdx;
float frameDuration;
float frameAt;//Used for duration -1
float frameCount;
float animT;
float animX;
float animY;
float animWidth;
float animHeight;
QQuickParticleGroupData::ID groupId;
//Used by ImageParticle data shadowing
QQuickImageParticle* colorOwner;
QQuickImageParticle* rotationOwner;
QQuickImageParticle* deformationOwner;
QQuickImageParticle* animationOwner;
//Used by ItemParticle
QQuickItem* delegate;
int modelIndex;
//Used by custom affectors
float update;
//Used by CustomParticle
float r;
// 4 bytes wasted
void debugDump(QQuickParticleSystem *particleSystem) const;
bool stillAlive(QQuickParticleSystem *particleSystem) const; //Only checks end, because usually that's all you need and it's a little faster.
bool alive(QQuickParticleSystem *particleSystem) const;
float lifeLeft(QQuickParticleSystem *particleSystem) const;
float curSize(QQuickParticleSystem *particleSystem) const;
void clone(const QQuickParticleData& other);//Not =, leaves meta-data like index
QV4::ReturnedValue v4Value(QQuickParticleSystem *particleSystem);
void extendLife(float time, QQuickParticleSystem *particleSystem);
static inline Q_DECL_CONSTEXPR float EPSILON() Q_DECL_NOTHROW { return 0.001f; }
private:
QQuickV4ParticleData* v8Datum;
};
class Q_QUICKPARTICLES_PRIVATE_EXPORT QQuickParticleSystem : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(bool running READ isRunning WRITE setRunning NOTIFY runningChanged)
Q_PROPERTY(bool paused READ isPaused WRITE setPaused NOTIFY pausedChanged)
Q_PROPERTY(bool empty READ isEmpty NOTIFY emptyChanged)
QML_NAMED_ELEMENT(ParticleSystem)
public:
explicit QQuickParticleSystem(QQuickItem *parent = nullptr);
~QQuickParticleSystem();
bool isRunning() const
{
return m_running;
}
int count(){ return particleCount; }
static const int maxLife = 600000;
Q_SIGNALS:
void systemInitialized();
void runningChanged(bool arg);
void pausedChanged(bool arg);
void emptyChanged(bool arg);
public Q_SLOTS:
void start(){setRunning(true);}
void stop(){setRunning(false);}
void restart(){setRunning(false);setRunning(true);}
void pause(){setPaused(true);}
void resume(){setPaused(false);}
void reset();
void setRunning(bool arg);
void setPaused(bool arg);
virtual int duration() const { return -1; }
protected:
//This one only once per frame (effectively)
void componentComplete() override;
private Q_SLOTS:
void emittersChanged();
void loadPainter(QQuickParticlePainter *p);
void createEngine(); //Not invoked by sprite engine, unlike Sprite uses
void particleStateChange(int idx);
public:
//These can be called multiple times per frame, performance critical
void emitParticle(QQuickParticleData* p, QQuickParticleEmitter *particleEmitter);
QQuickParticleData* newDatum(int groupId, bool respectLimits = true, int sysIdx = -1);
void finishNewDatum(QQuickParticleData*);
void moveGroups(QQuickParticleData *d, int newGIdx);
int nextSystemIndex();
//This one only once per painter per frame
int systemSync(QQuickParticlePainter* p);
//Data members here for ease of related class and auto-test usage. Not "public" API. TODO: d_ptrize
QtQuickParticlesPrivate::QFlatSet<QQuickParticleData*> needsReset;
QVector<QQuickParticleData*> bySysIdx; //Another reference to the data (data owned by group), but by sysIdx
QQuickStochasticEngine* stateEngine;
QHash<QString, int> groupIds;
QVarLengthArray<QQuickParticleGroupData*, 32> groupData;
int nextFreeGroupId;
int registerParticleGroupData(const QString &name, QQuickParticleGroupData *pgd);
//Also only here for auto-test usage
void updateCurrentTime( int currentTime );
QQuickParticleSystemAnimation* m_animation;
bool m_running;
bool m_debugMode;
int timeInt;
bool initialized;
int particleCount;
void registerParticlePainter(QQuickParticlePainter* p);
void registerParticleEmitter(QQuickParticleEmitter* e);
void finishRegisteringParticleEmitter(QQuickParticleEmitter *e);
void registerParticleAffector(QQuickParticleAffector* a);
void registerParticleGroup(QQuickParticleGroup* g);
static void statePropertyRedirect(QQmlListProperty<QObject> *prop, QObject *value);
static void stateRedirect(QQuickParticleGroup* group, QQuickParticleSystem* sys, QObject *value);
bool isPaused() const
{
return m_paused;
}
bool isEmpty() const
{
return m_empty;
}
private:
void searchNextFreeGroupId();
private:
void initializeSystem();
void initGroups();
QList<QPointer<QQuickParticleEmitter> > m_emitters;
QList<QPointer<QQuickParticleAffector> > m_affectors;
QList<QPointer<QQuickParticlePainter> > m_painters;
QList<QPointer<QQuickParticlePainter> > m_syncList;
QList<QQuickParticleGroup*> m_groups;
int m_nextIndex;
QSet<int> m_reusableIndexes;
bool m_componentComplete;
bool m_paused;
bool m_allDead;
bool m_empty;
};
// Internally, this animation drives all the timing. Painters sync up in their updatePaintNode
class QQuickParticleSystemAnimation : public QAbstractAnimation
{
Q_OBJECT
public:
QQuickParticleSystemAnimation(QQuickParticleSystem* system)
: QAbstractAnimation(static_cast<QObject*>(system)), m_system(system)
{ }
protected:
void updateCurrentTime(int t) override
{
m_system->updateCurrentTime(t);
}
int duration() const override
{
return -1;
}
private:
QQuickParticleSystem* m_system;
};
inline void QQuickParticleData::setInstantaneousAX(float ax, QQuickParticleSystem* particleSystem)
{
float t = (particleSystem->timeInt / 1000.0f) - this->t;
float t_sq = t * t;
float vx = (this->vx + t * this->ax) - t * ax;
float ex = this->x + this->vx * t + 0.5f * this->ax * t_sq;
float x = ex - t * vx - 0.5f * t_sq * ax;
this->ax = ax;
this->vx = vx;
this->x = x;
}
inline void QQuickParticleData::setInstantaneousVX(float vx, QQuickParticleSystem* particleSystem)
{
float t = (particleSystem->timeInt / 1000.0f) - this->t;
float t_sq = t * t;
float evx = vx - t * this->ax;
float ex = this->x + this->vx * t + 0.5f * this->ax * t_sq;
float x = ex - t * evx - 0.5f * t_sq * this->ax;
this->vx = evx;
this->x = x;
}
inline void QQuickParticleData::setInstantaneousX(float x, QQuickParticleSystem* particleSystem)
{
float t = (particleSystem->timeInt / 1000.0f) - this->t;
float t_sq = t * t;
this->x = x - t * this->vx - 0.5f * t_sq * this->ax;
}
inline void QQuickParticleData::setInstantaneousAY(float ay, QQuickParticleSystem* particleSystem)
{
float t = (particleSystem->timeInt / 1000.0f) - this->t;
float t_sq = t * t;
float vy = (this->vy + t * this->ay) - t * ay;
float ey = this->y + this->vy * t + 0.5f * this->ay * t_sq;
float y = ey - t * vy - 0.5f * t_sq * ay;
this->ay = ay;
this->vy = vy;
this->y = y;
}
inline void QQuickParticleData::setInstantaneousVY(float vy, QQuickParticleSystem* particleSystem)
{
float t = (particleSystem->timeInt / 1000.0f) - this->t;
float t_sq = t * t;
float evy = vy - t * this->ay;
float ey = this->y + this->vy * t + 0.5f * this->ay * t_sq;
float y = ey - t*evy - 0.5f * t_sq * this->ay;
this->vy = evy;
this->y = y;
}
inline void QQuickParticleData::setInstantaneousY(float y, QQuickParticleSystem *particleSystem)
{
float t = (particleSystem->timeInt / 1000.0f) - this->t;
float t_sq = t * t;
this->y = y - t * this->vy - 0.5f * t_sq * this->ay;
}
inline float QQuickParticleData::curX(QQuickParticleSystem *particleSystem) const
{
float t = (particleSystem->timeInt / 1000.0f) - this->t;
float t_sq = t * t;
return this->x + this->vx * t + 0.5f * this->ax * t_sq;
}
inline float QQuickParticleData::curVX(QQuickParticleSystem *particleSystem) const
{
float t = (particleSystem->timeInt / 1000.0f) - this->t;
return this->vx + t * this->ax;
}
inline float QQuickParticleData::curY(QQuickParticleSystem *particleSystem) const
{
float t = (particleSystem->timeInt / 1000.0f) - this->t;
float t_sq = t * t;
return y + vy * t + 0.5f * ay * t_sq;
}
inline float QQuickParticleData::curVY(QQuickParticleSystem *particleSystem) const
{
float t = (particleSystem->timeInt / 1000.0f) - this->t;
return vy + t*ay;
}
inline bool QQuickParticleData::stillAlive(QQuickParticleSystem* system) const
{
if (!system)
return false;
return (t + lifeSpan - EPSILON()) > (system->timeInt / 1000.0f);
}
inline bool QQuickParticleData::alive(QQuickParticleSystem* system) const
{
if (!system)
return false;
float st = (system->timeInt / 1000.0f);
return (t + EPSILON()) < st && (t + lifeSpan - EPSILON()) > st;
}
inline float QQuickParticleData::lifeLeft(QQuickParticleSystem *particleSystem) const
{
if (!particleSystem)
return 0.0f;
return (t + lifeSpan) - (particleSystem->timeInt / 1000.0f);
}
inline float QQuickParticleData::curSize(QQuickParticleSystem *particleSystem) const
{
if (!particleSystem || lifeSpan == 0.0f)
return 0.0f;
return size + (endSize - size) * (1 - (lifeLeft(particleSystem) / lifeSpan));
}
QT_END_NAMESPACE
#endif // PARTICLESYSTEM_H