diff options
author | Matthew Vogt <matthew.vogt@nokia.com> | 2012-04-11 16:54:33 +1000 |
---|---|---|
committer | Qt by Nokia <qt-info@nokia.com> | 2012-04-16 01:25:54 +0200 |
commit | f189b8934dc405cbd45258abc5aba2713428b1cc (patch) | |
tree | dd3d4f61c7337343705faf7a603585c287b8ae50 /src/particles/qquickimageparticle.cpp | |
parent | e00c40a89a1172add896e86e805a7cd42a4ea0c5 (diff) |
Extract Particles code to an independent library
QtQuick clients that do not use particles features should not have
to load these classes.
Task-number: QTBUG-25178
Change-Id: Ib15f7655dc4d821595e06f9160d2770375279027
Reviewed-by: Glenn Watson <glenn.watson@nokia.com>
Diffstat (limited to 'src/particles/qquickimageparticle.cpp')
-rw-r--r-- | src/particles/qquickimageparticle.cpp | 1978 |
1 files changed, 1978 insertions, 0 deletions
diff --git a/src/particles/qquickimageparticle.cpp b/src/particles/qquickimageparticle.cpp new file mode 100644 index 0000000000..d9eb6ed01b --- /dev/null +++ b/src/particles/qquickimageparticle.cpp @@ -0,0 +1,1978 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtQuick/private/qsgcontext_p.h> +#include <private/qsgadaptationlayer_p.h> +#include <private/qquickitem_p.h> +#include <QtQuick/qsgnode.h> +#include <QtQuick/qsgtexturematerial.h> +#include <QtQuick/qsgtexture.h> +#include <QFile> +#include "qquickimageparticle_p.h" +#include "qquickparticleemitter_p.h" +#include <private/qquicksprite_p.h> +#include <private/qquickspriteengine_p.h> +#include <QOpenGLFunctions> +#include <QtQuick/qsgengine.h> +#include <QtQuick/private/qsgtexture_p.h> +#include <private/qqmlglobal_p.h> +#include <QtQml/qqmlinfo.h> +#include <cmath> + +QT_BEGIN_NAMESPACE + +#ifndef QT_OPENGL_ES_2 +#define SHADER_DEFINES "#version 120\n" +#else +#define SHADER_DEFINES "" +#endif + +//TODO: Make it larger on desktop? Requires fixing up shader code with the same define +#define UNIFORM_ARRAY_SIZE 64 + +static const char vertexShaderCode[] = + "#if defined(DEFORM)\n" + "attribute highp vec4 vPosTex;\n" + "#else\n" + "attribute highp vec2 vPos;\n" + "#endif\n" + "attribute highp vec4 vData; // x = time, y = lifeSpan, z = size, w = endSize\n" + "attribute highp vec4 vVec; // x,y = constant speed, z,w = acceleration\n" + "uniform highp float entry;\n" + "#if defined(COLOR)\n" + "attribute highp vec4 vColor;\n" + "#endif\n" + "#if defined(DEFORM)\n" + "attribute highp vec4 vDeformVec; //x,y x unit vector; z,w = y unit vector\n" + "attribute highp vec3 vRotation; //x = radians of rotation, y=rotation speed, z= bool autoRotate\n" + "#endif\n" + "#if defined(SPRITE)\n" + "attribute highp vec3 vAnimData;// w,h(premultiplied of anim), interpolation progress\n" + "attribute highp vec4 vAnimPos;//x,y, x,y (two frames for interpolation)\n" + "#endif\n" + "\n" + "uniform highp mat4 qt_Matrix;\n" + "uniform highp float timestamp;\n" + "#if defined(TABLE)\n" + "varying lowp vec2 tt;//y is progress if Sprite mode\n" + "uniform highp float sizetable[64];\n" + "uniform highp float opacitytable[64];\n" + "#endif\n" + "#if defined(SPRITE)\n" + "varying highp vec4 fTexS;\n" + "#elif defined(DEFORM)\n" + "varying highp vec2 fTex;\n" + "#endif\n" + "#if defined(COLOR)\n" + "varying lowp vec4 fColor;\n" + "#else\n" + "varying lowp float fFade;\n" + "#endif\n" + "\n" + "\n" + "void main() {\n" + "\n" + " highp float t = (timestamp - vData.x) / vData.y;\n" + " if (t < 0. || t > 1.) {\n" + "#if defined(DEFORM)\n" + " gl_Position = qt_Matrix * vec4(vPosTex.x, vPosTex.y, 0., 1.);\n" + "#else\n" + " gl_PointSize = 0.;\n" + "#endif\n" + " } else {\n" + "#if defined(SPRITE)\n" + " tt.y = vAnimData.z;\n" + " //Calculate frame location in texture\n" + " fTexS.xy = vAnimPos.xy + vPosTex.zw * vAnimData.xy;\n" + " //Next frame is also passed, for interpolation\n" + " fTexS.zw = vAnimPos.zw + vPosTex.zw * vAnimData.xy;\n" + "\n" + "#elif defined(DEFORM)\n" + " fTex = vPosTex.zw;\n" + "#endif\n" + " highp float currentSize = mix(vData.z, vData.w, t * t);\n" + " lowp float fade = 1.;\n" + " highp float fadeIn = min(t * 10., 1.);\n" + " highp float fadeOut = 1. - clamp((t - 0.75) * 4.,0., 1.);\n" + "\n" + "#if defined(TABLE)\n" + " currentSize = currentSize * sizetable[int(floor(t*64.))];\n" + " fade = fade * opacitytable[int(floor(t*64.))];\n" + "#endif\n" + "\n" + " if (entry == 1.)\n" + " fade = fade * fadeIn * fadeOut;\n" + " else if (entry == 2.)\n" + " currentSize = currentSize * fadeIn * fadeOut;\n" + "\n" + " if (currentSize <= 0.) {\n" + "#if defined(DEFORM)\n" + " gl_Position = qt_Matrix * vec4(vPosTex.x, vPosTex.y, 0., 1.);\n" + "#else\n" + " gl_PointSize = 0.;\n" + "#endif\n" + " } else {\n" + " if (currentSize < 3.)//Sizes too small look jittery as they move\n" + " currentSize = 3.;\n" + "\n" + " highp vec2 pos;\n" + "#if defined(DEFORM)\n" + " highp float rotation = vRotation.x + vRotation.y * t * vData.y;\n" + " if (vRotation.z == 1.0){\n" + " highp vec2 curVel = vVec.zw * t * vData.y + vVec.xy;\n" + " rotation += atan(curVel.y, curVel.x);\n" + " }\n" + " highp vec2 trigCalcs = vec2(cos(rotation), sin(rotation));\n" + " highp vec4 deform = vDeformVec * currentSize * (vPosTex.zzww - 0.5);\n" + " highp vec4 rotatedDeform = deform.xxzz * trigCalcs.xyxy;\n" + " rotatedDeform = rotatedDeform + (deform.yyww * trigCalcs.yxyx * vec4(-1.,1.,-1.,1.));\n" + " /* The readable version:\n" + " highp vec2 xDeform = vDeformVec.xy * currentSize * (vTex.x-0.5);\n" + " highp vec2 yDeform = vDeformVec.zw * currentSize * (vTex.y-0.5);\n" + " highp vec2 xRotatedDeform;\n" + " xRotatedDeform.x = trigCalcs.x*xDeform.x - trigCalcs.y*xDeform.y;\n" + " xRotatedDeform.y = trigCalcs.y*xDeform.x + trigCalcs.x*xDeform.y;\n" + " highp vec2 yRotatedDeform;\n" + " yRotatedDeform.x = trigCalcs.x*yDeform.x - trigCalcs.y*yDeform.y;\n" + " yRotatedDeform.y = trigCalcs.y*yDeform.x + trigCalcs.x*yDeform.y;\n" + " */\n" + " pos = vPosTex.xy\n" + " + rotatedDeform.xy\n" + " + rotatedDeform.zw\n" + " + vVec.xy * t * vData.y // apply speed\n" + " + 0.5 * vVec.zw * pow(t * vData.y, 2.); // apply acceleration\n" + "#else\n" + " pos = vPos\n" + " + vVec.xy * t * vData.y // apply speed vector..\n" + " + 0.5 * vVec.zw * pow(t * vData.y, 2.);\n" + " gl_PointSize = currentSize;\n" + "#endif\n" + " gl_Position = qt_Matrix * vec4(pos.x, pos.y, 0, 1);\n" + "\n" + "#if defined(COLOR)\n" + " fColor = vColor * fade;\n" + "#else\n" + " fFade = fade;\n" + "#endif\n" + "#if defined(TABLE)\n" + " tt.x = t;\n" + "#endif\n" + " }\n" + " }\n" + "}\n"; + +static const char fragmentShaderCode[] = + "uniform sampler2D texture;\n" + "uniform lowp float qt_Opacity;\n" + "\n" + "#if defined(SPRITE)\n" + "varying highp vec4 fTexS;\n" + "#elif defined(DEFORM)\n" + "varying highp vec2 fTex;\n" + "#endif\n" + "#if defined(COLOR)\n" + "varying lowp vec4 fColor;\n" + "#else\n" + "varying lowp float fFade;\n" + "#endif\n" + "#if defined(TABLE)\n" + "varying lowp vec2 tt;\n" + "uniform sampler2D colortable;\n" + "#endif\n" + "\n" + "void main() {\n" + "#if defined(SPRITE)\n" + " gl_FragColor = mix(texture2D(texture, fTexS.xy), texture2D(texture, fTexS.zw), tt.y)\n" + " * fColor\n" + " * texture2D(colortable, tt)\n" + " * qt_Opacity;\n" + "#elif defined(TABLE)\n" + " gl_FragColor = texture2D(texture, fTex)\n" + " * fColor\n" + " * texture2D(colortable, tt)\n" + " * qt_Opacity;\n" + "#elif defined(DEFORM)\n" + " gl_FragColor = (texture2D(texture, fTex)) * fColor * qt_Opacity;\n" + "#elif defined(COLOR)\n" + " gl_FragColor = (texture2D(texture, gl_PointCoord)) * fColor * qt_Opacity;\n" + "#else\n" + " gl_FragColor = texture2D(texture, gl_PointCoord) * (fFade * qt_Opacity);\n" + "#endif\n" + "}\n"; + +const qreal CONV = 0.017453292519943295; +class ImageMaterialData +{ + public: + ImageMaterialData() + : texture(0), colorTable(0) + {} + + ~ImageMaterialData(){ + delete texture; + delete colorTable; + } + + QSGTexture *texture; + QSGTexture *colorTable; + float sizeTable[UNIFORM_ARRAY_SIZE]; + float opacityTable[UNIFORM_ARRAY_SIZE]; + + qreal timestamp; + qreal entry; + QSizeF animSheetSize; +}; + +class TabledMaterialData : public ImageMaterialData {}; +class TabledMaterial : public QSGSimpleMaterialShader<TabledMaterialData> +{ + QSG_DECLARE_SIMPLE_SHADER(TabledMaterial, TabledMaterialData) + +public: + TabledMaterial() + { + m_vertex_code = QByteArray(SHADER_DEFINES) + + QByteArray("#define TABLE\n#define DEFORM\n#define COLOR\n") + + vertexShaderCode; + + m_fragment_code = QByteArray(SHADER_DEFINES) + + QByteArray("#define TABLE\n#define DEFORM\n#define COLOR\n") + + fragmentShaderCode; + + Q_ASSERT(!m_vertex_code.isNull()); + Q_ASSERT(!m_fragment_code.isNull()); + } + + const char *vertexShader() const { return m_vertex_code.constData(); } + const char *fragmentShader() const { return m_fragment_code.constData(); } + + QList<QByteArray> attributes() const { + return QList<QByteArray>() << "vPosTex" << "vData" << "vVec" + << "vColor" << "vDeformVec" << "vRotation"; + }; + + void initialize() { + QSGSimpleMaterialShader<TabledMaterialData>::initialize(); + program()->bind(); + program()->setUniformValue("texture", 0); + program()->setUniformValue("colortable", 1); + glFuncs = QOpenGLContext::currentContext()->functions(); + m_timestamp_id = program()->uniformLocation("timestamp"); + m_entry_id = program()->uniformLocation("entry"); + m_sizetable_id = program()->uniformLocation("sizetable"); + m_opacitytable_id = program()->uniformLocation("opacitytable"); + } + + void updateState(const TabledMaterialData* d, const TabledMaterialData*) { + glFuncs->glActiveTexture(GL_TEXTURE1); + d->colorTable->bind(); + + glFuncs->glActiveTexture(GL_TEXTURE0); + d->texture->bind(); + + program()->setUniformValue(m_timestamp_id, (float) d->timestamp); + program()->setUniformValue(m_entry_id, (float) d->entry); + program()->setUniformValueArray(m_sizetable_id, (float*) d->sizeTable, UNIFORM_ARRAY_SIZE, 1); + program()->setUniformValueArray(m_opacitytable_id, (float*) d->opacityTable, UNIFORM_ARRAY_SIZE, 1); + } + + int m_entry_id; + int m_timestamp_id; + int m_sizetable_id; + int m_opacitytable_id; + QByteArray m_vertex_code; + QByteArray m_fragment_code; + QOpenGLFunctions* glFuncs; +}; + +class DeformableMaterialData : public ImageMaterialData {}; +class DeformableMaterial : public QSGSimpleMaterialShader<DeformableMaterialData> +{ + QSG_DECLARE_SIMPLE_SHADER(DeformableMaterial, DeformableMaterialData) + +public: + DeformableMaterial() + { + m_vertex_code = QByteArray(SHADER_DEFINES) + + QByteArray("#define DEFORM\n#define COLOR\n") + + vertexShaderCode; + + m_fragment_code = QByteArray(SHADER_DEFINES) + + QByteArray("#define DEFORM\n#define COLOR\n") + + fragmentShaderCode; + + Q_ASSERT(!m_vertex_code.isNull()); + Q_ASSERT(!m_fragment_code.isNull()); + } + + const char *vertexShader() const { return m_vertex_code.constData(); } + const char *fragmentShader() const { return m_fragment_code.constData(); } + + QList<QByteArray> attributes() const { + return QList<QByteArray>() << "vPosTex" << "vData" << "vVec" + << "vColor" << "vDeformVec" << "vRotation"; + }; + + void initialize() { + QSGSimpleMaterialShader<DeformableMaterialData>::initialize(); + program()->bind(); + program()->setUniformValue("texture", 0); + glFuncs = QOpenGLContext::currentContext()->functions(); + m_timestamp_id = program()->uniformLocation("timestamp"); + m_entry_id = program()->uniformLocation("entry"); + } + + void updateState(const DeformableMaterialData* d, const DeformableMaterialData*) { + glFuncs->glActiveTexture(GL_TEXTURE0); + d->texture->bind(); + + program()->setUniformValue(m_timestamp_id, (float) d->timestamp); + program()->setUniformValue(m_entry_id, (float) d->entry); + } + + int m_entry_id; + int m_timestamp_id; + QByteArray m_vertex_code; + QByteArray m_fragment_code; + QOpenGLFunctions* glFuncs; +}; + +class SpriteMaterialData : public ImageMaterialData {}; +class SpriteMaterial : public QSGSimpleMaterialShader<SpriteMaterialData> +{ + QSG_DECLARE_SIMPLE_SHADER(SpriteMaterial, SpriteMaterialData) + +public: + SpriteMaterial() + { + m_vertex_code = QByteArray(SHADER_DEFINES) + + QByteArray("#define SPRITE\n#define TABLE\n#define DEFORM\n#define COLOR\n") + + vertexShaderCode; + + m_fragment_code = QByteArray(SHADER_DEFINES) + + QByteArray("#define SPRITE\n#define TABLE\n#define DEFORM\n#define COLOR\n") + + fragmentShaderCode; + + Q_ASSERT(!m_vertex_code.isNull()); + Q_ASSERT(!m_fragment_code.isNull()); + } + + const char *vertexShader() const { return m_vertex_code.constData(); } + const char *fragmentShader() const { return m_fragment_code.constData(); } + + QList<QByteArray> attributes() const { + return QList<QByteArray>() << "vPosTex" << "vData" << "vVec" + << "vColor" << "vDeformVec" << "vRotation" << "vAnimData" << "vAnimPos"; + }; + + void initialize() { + QSGSimpleMaterialShader<SpriteMaterialData>::initialize(); + program()->bind(); + program()->setUniformValue("texture", 0); + program()->setUniformValue("colortable", 1); + glFuncs = QOpenGLContext::currentContext()->functions(); + //Don't actually expose the animSheetSize in the shader, it's currently only used for CPU calculations. + m_timestamp_id = program()->uniformLocation("timestamp"); + m_entry_id = program()->uniformLocation("entry"); + m_sizetable_id = program()->uniformLocation("sizetable"); + m_opacitytable_id = program()->uniformLocation("opacitytable"); + } + + void updateState(const SpriteMaterialData* d, const SpriteMaterialData*) { + glFuncs->glActiveTexture(GL_TEXTURE1); + d->colorTable->bind(); + + // make sure we end by setting GL_TEXTURE0 as active texture + glFuncs->glActiveTexture(GL_TEXTURE0); + d->texture->bind(); + + program()->setUniformValue(m_timestamp_id, (float) d->timestamp); + program()->setUniformValue(m_entry_id, (float) d->entry); + program()->setUniformValueArray(m_sizetable_id, (float*) d->sizeTable, 64, 1); + program()->setUniformValueArray(m_opacitytable_id, (float*) d->opacityTable, UNIFORM_ARRAY_SIZE, 1); + } + + int m_timestamp_id; + int m_entry_id; + int m_sizetable_id; + int m_opacitytable_id; + QByteArray m_vertex_code; + QByteArray m_fragment_code; + QOpenGLFunctions* glFuncs; +}; + +class ColoredMaterialData : public ImageMaterialData {}; +class ColoredMaterial : public QSGSimpleMaterialShader<ColoredMaterialData> +{ + QSG_DECLARE_SIMPLE_SHADER(ColoredMaterial, ColoredMaterialData) + +public: + ColoredMaterial() + { + m_vertex_code = QByteArray(SHADER_DEFINES) + + QByteArray("#define COLOR\n") + + vertexShaderCode; + + m_fragment_code = QByteArray(SHADER_DEFINES) + + QByteArray("#define COLOR\n") + + fragmentShaderCode; + + Q_ASSERT(!m_vertex_code.isNull()); + Q_ASSERT(!m_fragment_code.isNull()); + } + + const char *vertexShader() const { return m_vertex_code.constData(); } + const char *fragmentShader() const { return m_fragment_code.constData(); } + + void activate() { + QSGSimpleMaterialShader<ColoredMaterialData>::activate(); +#if !defined(QT_OPENGL_ES_2) && !defined(Q_OS_WIN) + glEnable(GL_POINT_SPRITE); + glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); +#endif + } + + void deactivate() { + QSGSimpleMaterialShader<ColoredMaterialData>::deactivate(); +#if !defined(QT_OPENGL_ES_2) && !defined(Q_OS_WIN) + glDisable(GL_POINT_SPRITE); + glDisable(GL_VERTEX_PROGRAM_POINT_SIZE); +#endif + } + + QList<QByteArray> attributes() const { + return QList<QByteArray>() << "vPos" << "vData" << "vVec" << "vColor"; + } + + void initialize() { + QSGSimpleMaterialShader<ColoredMaterialData>::initialize(); + program()->bind(); + program()->setUniformValue("texture", 0); + glFuncs = QOpenGLContext::currentContext()->functions(); + m_timestamp_id = program()->uniformLocation("timestamp"); + m_entry_id = program()->uniformLocation("entry"); + } + + void updateState(const ColoredMaterialData* d, const ColoredMaterialData*) { + glFuncs->glActiveTexture(GL_TEXTURE0); + d->texture->bind(); + + program()->setUniformValue(m_timestamp_id, (float) d->timestamp); + program()->setUniformValue(m_entry_id, (float) d->entry); + } + + int m_timestamp_id; + int m_entry_id; + QByteArray m_vertex_code; + QByteArray m_fragment_code; + QOpenGLFunctions* glFuncs; +}; + +class SimpleMaterialData : public ImageMaterialData {}; +class SimpleMaterial : public QSGSimpleMaterialShader<SimpleMaterialData> +{ + QSG_DECLARE_SIMPLE_SHADER(SimpleMaterial, SimpleMaterialData) + +public: + SimpleMaterial() + { + m_vertex_code = QByteArray(SHADER_DEFINES) + + vertexShaderCode; + + m_fragment_code = QByteArray(SHADER_DEFINES) + + fragmentShaderCode; + + Q_ASSERT(!m_vertex_code.isNull()); + Q_ASSERT(!m_fragment_code.isNull()); + } + + const char *vertexShader() const { return m_vertex_code.constData(); } + const char *fragmentShader() const { return m_fragment_code.constData(); } + + void activate() { + QSGSimpleMaterialShader<SimpleMaterialData>::activate(); +#if !defined(QT_OPENGL_ES_2) && !defined(Q_OS_WIN) + glEnable(GL_POINT_SPRITE); + glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); +#endif + } + + void deactivate() { + QSGSimpleMaterialShader<SimpleMaterialData>::deactivate(); +#if !defined(QT_OPENGL_ES_2) && !defined(Q_OS_WIN) + glDisable(GL_POINT_SPRITE); + glDisable(GL_VERTEX_PROGRAM_POINT_SIZE); +#endif + } + + QList<QByteArray> attributes() const { + return QList<QByteArray>() << "vPos" << "vData" << "vVec"; + } + + void initialize() { + QSGSimpleMaterialShader<SimpleMaterialData>::initialize(); + program()->bind(); + program()->setUniformValue("texture", 0); + glFuncs = QOpenGLContext::currentContext()->functions(); + m_timestamp_id = program()->uniformLocation("timestamp"); + m_entry_id = program()->uniformLocation("entry"); + } + + void updateState(const SimpleMaterialData* d, const SimpleMaterialData*) { + glFuncs->glActiveTexture(GL_TEXTURE0); + d->texture->bind(); + + program()->setUniformValue(m_timestamp_id, (float) d->timestamp); + program()->setUniformValue(m_entry_id, (float) d->entry); + } + + int m_timestamp_id; + int m_entry_id; + QByteArray m_vertex_code; + QByteArray m_fragment_code; + QOpenGLFunctions* glFuncs; +}; + +void fillUniformArrayFromImage(float* array, const QImage& img, int size) +{ + if (img.isNull()){ + for (int i=0; i<size; i++) + array[i] = 1.0; + return; + } + QImage scaled = img.scaled(size,1); + for (int i=0; i<size; i++) + array[i] = qAlpha(scaled.pixel(i,0))/255.0; +} + +/*! + \qmlclass ImageParticle QQuickImageParticle + \inqmlmodule QtQuick.Particles 2 + \inherits ParticlePainter + \brief The ImageParticle element visualizes logical particles using an image + + This element renders a logical particle as an image. The image can be + \list + \li colorized + \li rotated + \li deformed + \li a sprite-based animation + \endlist + + ImageParticles implictly share data on particles if multiple ImageParticles are painting + the same logical particle group. This is broken down along the four capabilities listed + above. So if one ImageParticle defines data for rendering the particles in one of those + capabilities, and the other does not, then both will draw the particles the same in that + aspect automatically. This is primarily useful when there is some random variation on + the particle which is supposed to stay with it when switching painters. If both ImageParticles + define how they should appear for that aspect, they diverge and each appears as it is defined. + + This sharing of data happens behind the scenes based off of whether properties were implicitly or explicitly + set. One drawback of the current implementation is that it is only possible to reset the capabilities as a whole. + So if you explicity set an attribute affecting color, such as redVariation, and then reset it (by setting redVariation + to undefined), all color data will be reset and it will begin to have an implicit value of any shared color from + other ImageParticles. +*/ +/*! + \qmlproperty url QtQuick.Particles2::ImageParticle::source + + The source image to be used. + + If the image is a sprite animation, use the sprite property instead. +*/ +/*! + \qmlproperty list<Sprite> QtQuick.Particles2::ImageParticle::sprites + + The sprite or sprites used to draw this particle. + + Note that the sprite image will be scaled to a square based on the size of + the particle being rendered. +*/ +/*! + \qmlproperty url QtQuick.Particles2::ImageParticle::colorTable + + An image whose color will be used as a 1D texture to determine color over life. E.g. when + the particle is halfway through its lifetime, it will have the color specified halfway + across the image. + + This color is blended with the color property and the color of the source image. +*/ +/*! + \qmlproperty url QtQuick.Particles2::ImageParticle::sizeTable + + An image whose opacity will be used as a 1D texture to determine size over life. + + This property is expected to be removed shortly, in favor of custom easing curves to determine size over life. +*/ +/*! + \qmlproperty url QtQuick.Particles2::ImageParticle::opacityTable + + An image whose opacity will be used as a 1D texture to determine size over life. + + This property is expected to be removed shortly, in favor of custom easing curves to determine opacity over life. +*/ +/*! + \qmlproperty color QtQuick.Particles2::ImageParticle::color + + If a color is specified, the provided image will be colorized with it. + + Default is white (no change). +*/ +/*! + \qmlproperty real QtQuick.Particles2::ImageParticle::colorVariation + + This number represents the color variation applied to individual particles. + Setting colorVariation is the same as setting redVariation, greenVariation, + and blueVariation to the same number. + + Each channel can vary between particle by up to colorVariation from its usual color. + + Color is measured, per channel, from 0.0 to 1.0. + + Default is 0.0 +*/ +/*! + \qmlproperty real QtQuick.Particles2::ImageParticle::redVariation + The variation in the red color channel between particles. + + Color is measured, per channel, from 0.0 to 1.0. + + Default is 0.0 +*/ +/*! + \qmlproperty real QtQuick.Particles2::ImageParticle::greenVariation + The variation in the green color channel between particles. + + Color is measured, per channel, from 0.0 to 1.0. + + Default is 0.0 +*/ +/*! + \qmlproperty real QtQuick.Particles2::ImageParticle::blueVariation + The variation in the blue color channel between particles. + + Color is measured, per channel, from 0.0 to 1.0. + + Default is 0.0 +*/ +/*! + \qmlproperty real QtQuick.Particles2::ImageParticle::alpha + An alpha to be applied to the image. This value is multiplied by the value in + the image, and the value in the color property. + + Particles have additive blending, so lower alpha on single particles leads + to stronger effects when multiple particles overlap. + + Alpha is measured from 0.0 to 1.0. + + Default is 1.0 +*/ +/*! + \qmlproperty real QtQuick.Particles2::ImageParticle::alphaVariation + The variation in the alpha channel between particles. + + Alpha is measured from 0.0 to 1.0. + + Default is 0.0 +*/ +/*! + \qmlproperty real QtQuick.Particles2::ImageParticle::rotation + + If set the image will be rotated by this many degrees before it is drawn. + + The particle coordinates are not transformed. +*/ +/*! + \qmlproperty real QtQuick.Particles2::ImageParticle::rotationVariation + + If set the rotation of individual particles will vary by up to this much + between particles. + +*/ +/*! + \qmlproperty real QtQuick.Particles2::ImageParticle::rotationSpeed + + If set particles will rotate at this speed in degrees/second. +*/ +/*! + \qmlproperty real QtQuick.Particles2::ImageParticle::rotationSpeedVariation + + If set the rotationSpeed of individual particles will vary by up to this much + between particles. + +*/ +/*! + \qmlproperty bool QtQuick.Particles2::ImageParticle::autoRotation + + If set to true then a rotation will be applied on top of the particles rotation, so + that it faces the direction of travel. So to face away from the direction of travel, + set autoRotation to true and rotation to 180. + + Default is false +*/ +/*! + \qmlproperty StochasticDirection QtQuick.Particles2::ImageParticle::xVector + + Allows you to deform the particle image when drawn. The rectangular image will + be deformed so that the horizontal sides are in the shape of this vector instead + of (1,0). +*/ +/*! + \qmlproperty StochasticDirection QtQuick.Particles2::ImageParticle::yVector + + Allows you to deform the particle image when drawn. The rectangular image will + be deformed so that the vertical sides are in the shape of this vector instead + of (0,1). +*/ +/*! + \qmlproperty EntryEffect QtQuick.Particles2::ImageParticle::entryEffect + + This property provides basic and cheap entrance and exit effects for the particles. + For fine-grained control, see sizeTable and opacityTable. + + Acceptable values are + \list + \li ImageParticle.None: Particles just appear and disappear. + \li ImageParticle.Fade: Particles fade in from 0 opacity at the start of their life, and fade out to 0 at the end. + \li ImageParticle.Scale: Particles scale in from 0 size at the start of their life, and scale back to 0 at the end. + \endlist + + Default value is Fade. +*/ +/*! + \qmlproperty bool QtQuick.Particles2::ImageParticle::spritesInterpolate + + If set to true, sprite particles will interpolate between sprite frames each rendered frame, making + the sprites look smoother. + + Default is true. +*/ + +/*! + \qmlproperty Status QtQuick.Particles2::ImageParticle::status + + The status of loading the image. +*/ + + +QQuickImageParticle::QQuickImageParticle(QQuickItem* parent) + : QQuickParticlePainter(parent) + , m_image(0) + , m_colorTable(0) + , m_sizeTable(0) + , m_opacityTable(0) + , m_color_variation(0.0) + , m_rootNode(0) + , m_material(0) + , m_alphaVariation(0.0) + , m_alpha(1.0) + , m_redVariation(0.0) + , m_greenVariation(0.0) + , m_blueVariation(0.0) + , m_rotation(0) + , m_rotationVariation(0) + , m_rotationSpeed(0) + , m_rotationSpeedVariation(0) + , m_autoRotation(false) + , m_xVector(0) + , m_yVector(0) + , m_spriteEngine(0) + , m_spritesInterpolate(true) + , m_explicitColor(false) + , m_explicitRotation(false) + , m_explicitDeformation(false) + , m_explicitAnimation(false) + , m_bypassOptimizations(false) + , perfLevel(Unknown) + , m_lastLevel(Unknown) + , m_debugMode(false) + , m_entryEffect(Fade) + , m_buildingNodes(false) +{ + setFlag(ItemHasContents); +} + +QQuickImageParticle::~QQuickImageParticle() +{ +} + +QQmlListProperty<QQuickSprite> QQuickImageParticle::sprites() +{ + return QQmlListProperty<QQuickSprite>(this, &m_sprites, spriteAppend, spriteCount, spriteAt, spriteClear); +} + +void QQuickImageParticle::sceneGraphInvalidated() +{ + m_nodes.clear(); + m_rootNode = 0; + m_material = 0; +} + +void QQuickImageParticle::setImage(const QUrl &image) +{ + if (image.isEmpty()){ + if (m_image) { + delete m_image; + emit imageChanged(); + } + return; + } + + if (!m_image) + m_image = new ImageData; + if (image == m_image->source) + return; + m_image->source = image; + emit imageChanged(); + reset(); +} + + +void QQuickImageParticle::setColortable(const QUrl &table) +{ + if (table.isEmpty()){ + if (m_colorTable) { + delete m_colorTable; + emit colortableChanged(); + } + return; + } + + if (!m_colorTable) + m_colorTable = new ImageData; + if (table == m_colorTable->source) + return; + m_colorTable->source = table; + emit colortableChanged(); + reset(); +} + +void QQuickImageParticle::setSizetable(const QUrl &table) +{ + if (table.isEmpty()){ + if (m_sizeTable) { + delete m_sizeTable; + emit sizetableChanged(); + } + return; + } + + if (!m_sizeTable) + m_sizeTable = new ImageData; + if (table == m_sizeTable->source) + return; + m_sizeTable->source = table; + emit sizetableChanged(); + reset(); +} + +void QQuickImageParticle::setOpacitytable(const QUrl &table) +{ + if (table.isEmpty()){ + if (m_opacityTable) { + delete m_opacityTable; + emit opacitytableChanged(); + } + return; + } + + if (!m_opacityTable) + m_opacityTable = new ImageData; + if (table == m_opacityTable->source) + return; + m_opacityTable->source = table; + emit opacitytableChanged(); + reset(); +} + +void QQuickImageParticle::setColor(const QColor &color) +{ + if (color == m_color) + return; + m_color = color; + emit colorChanged(); + m_explicitColor = true; + if (perfLevel < Colored) + reset(); +} + +void QQuickImageParticle::setColorVariation(qreal var) +{ + if (var == m_color_variation) + return; + m_color_variation = var; + emit colorVariationChanged(); + m_explicitColor = true; + if (perfLevel < Colored) + reset(); +} + +void QQuickImageParticle::setAlphaVariation(qreal arg) +{ + if (m_alphaVariation != arg) { + m_alphaVariation = arg; + emit alphaVariationChanged(arg); + } + m_explicitColor = true; + if (perfLevel < Colored) + reset(); +} + +void QQuickImageParticle::setAlpha(qreal arg) +{ + if (m_alpha != arg) { + m_alpha = arg; + emit alphaChanged(arg); + } + m_explicitColor = true; + if (perfLevel < Colored) + reset(); +} + +void QQuickImageParticle::setRedVariation(qreal arg) +{ + if (m_redVariation != arg) { + m_redVariation = arg; + emit redVariationChanged(arg); + } + m_explicitColor = true; + if (perfLevel < Colored) + reset(); +} + +void QQuickImageParticle::setGreenVariation(qreal arg) +{ + if (m_greenVariation != arg) { + m_greenVariation = arg; + emit greenVariationChanged(arg); + } + m_explicitColor = true; + if (perfLevel < Colored) + reset(); +} + +void QQuickImageParticle::setBlueVariation(qreal arg) +{ + if (m_blueVariation != arg) { + m_blueVariation = arg; + emit blueVariationChanged(arg); + } + m_explicitColor = true; + if (perfLevel < Colored) + reset(); +} + +void QQuickImageParticle::setRotation(qreal arg) +{ + if (m_rotation != arg) { + m_rotation = arg; + emit rotationChanged(arg); + } + m_explicitRotation = true; + if (perfLevel < Deformable) + reset(); +} + +void QQuickImageParticle::setRotationVariation(qreal arg) +{ + if (m_rotationVariation != arg) { + m_rotationVariation = arg; + emit rotationVariationChanged(arg); + } + m_explicitRotation = true; + if (perfLevel < Deformable) + reset(); +} + +void QQuickImageParticle::setRotationSpeed(qreal arg) +{ + if (m_rotationSpeed != arg) { + m_rotationSpeed = arg; + emit rotationSpeedChanged(arg); + } + m_explicitRotation = true; + if (perfLevel < Deformable) + reset(); +} + +void QQuickImageParticle::setRotationSpeedVariation(qreal arg) +{ + if (m_rotationSpeedVariation != arg) { + m_rotationSpeedVariation = arg; + emit rotationSpeedVariationChanged(arg); + } + m_explicitRotation = true; + if (perfLevel < Deformable) + reset(); +} + +void QQuickImageParticle::setAutoRotation(bool arg) +{ + if (m_autoRotation != arg) { + m_autoRotation = arg; + emit autoRotationChanged(arg); + } + m_explicitRotation = true; + if (perfLevel < Deformable) + reset(); +} + +void QQuickImageParticle::setXVector(QQuickDirection* arg) +{ + if (m_xVector != arg) { + m_xVector = arg; + emit xVectorChanged(arg); + } + m_explicitDeformation = true; + if (perfLevel < Deformable) + reset(); +} + +void QQuickImageParticle::setYVector(QQuickDirection* arg) +{ + if (m_yVector != arg) { + m_yVector = arg; + emit yVectorChanged(arg); + } + m_explicitDeformation = true; + if (perfLevel < Deformable) + reset(); +} + +void QQuickImageParticle::setSpritesInterpolate(bool arg) +{ + if (m_spritesInterpolate != arg) { + m_spritesInterpolate = arg; + emit spritesInterpolateChanged(arg); + } +} + +void QQuickImageParticle::setBypassOptimizations(bool arg) +{ + if (m_bypassOptimizations != arg) { + m_bypassOptimizations = arg; + emit bypassOptimizationsChanged(arg); + } + if (perfLevel < 9999) + reset(); +} + +void QQuickImageParticle::setEntryEffect(EntryEffect arg) +{ + if (m_entryEffect != arg) { + m_entryEffect = arg; + if (m_material) + getState<ImageMaterialData>(m_material)->entry = (qreal) m_entryEffect; + emit entryEffectChanged(arg); + } +} + +void QQuickImageParticle::resetColor() +{ + m_explicitColor = false; + foreach (const QString &str, m_groups) + foreach (QQuickParticleData* d, m_system->groupData[m_system->groupIds[str]]->data) + if (d->colorOwner == this) + d->colorOwner = 0; + m_color = QColor(); + m_color_variation = 0.0f; + m_redVariation = 0.0f; + m_blueVariation = 0.0f; + m_greenVariation = 0.0f; + m_alpha = 1.0f; + m_alphaVariation = 0.0f; +} + +void QQuickImageParticle::resetRotation() +{ + m_explicitRotation = false; + foreach (const QString &str, m_groups) + foreach (QQuickParticleData* d, m_system->groupData[m_system->groupIds[str]]->data) + if (d->rotationOwner == this) + d->rotationOwner = 0; + m_rotation = 0; + m_rotationVariation = 0; + m_rotationSpeed = 0; + m_rotationSpeedVariation = 0; + m_autoRotation = false; +} + +void QQuickImageParticle::resetDeformation() +{ + m_explicitDeformation = false; + foreach (const QString &str, m_groups) + foreach (QQuickParticleData* d, m_system->groupData[m_system->groupIds[str]]->data) + if (d->deformationOwner == this) + d->deformationOwner = 0; + if (m_xVector) + delete m_xVector; + if (m_yVector) + delete m_yVector; + m_xVector = 0; + m_yVector = 0; +} + +void QQuickImageParticle::reset() +{ + QQuickParticlePainter::reset(); + m_pleaseReset = true; + update(); +} + +void QQuickImageParticle::createEngine() +{ + if (m_spriteEngine) + delete m_spriteEngine; + if (m_sprites.count()) { + m_spriteEngine = new QQuickSpriteEngine(m_sprites, this); + connect(m_spriteEngine, SIGNAL(stateChanged(int)), + this, SLOT(spriteAdvance(int)), Qt::DirectConnection); + m_explicitAnimation = true; + } else { + m_spriteEngine = 0; + m_explicitAnimation = false; + } + reset(); +} + +static QSGGeometry::Attribute SimpleParticle_Attributes[] = { + QSGGeometry::Attribute::create(0, 2, GL_FLOAT, true), // Position + QSGGeometry::Attribute::create(1, 4, GL_FLOAT), // Data + QSGGeometry::Attribute::create(2, 4, GL_FLOAT) // Vectors +}; + +static QSGGeometry::AttributeSet SimpleParticle_AttributeSet = +{ + 3, // Attribute Count + ( 2 + 4 + 4 ) * sizeof(float), + SimpleParticle_Attributes +}; + +static QSGGeometry::Attribute ColoredParticle_Attributes[] = { + QSGGeometry::Attribute::create(0, 2, GL_FLOAT, true), // Position + QSGGeometry::Attribute::create(1, 4, GL_FLOAT), // Data + QSGGeometry::Attribute::create(2, 4, GL_FLOAT), // Vectors + QSGGeometry::Attribute::create(3, 4, GL_UNSIGNED_BYTE), // Colors +}; + +static QSGGeometry::AttributeSet ColoredParticle_AttributeSet = +{ + 4, // Attribute Count + ( 2 + 4 + 4 ) * sizeof(float) + 4 * sizeof(uchar), + ColoredParticle_Attributes +}; + +static QSGGeometry::Attribute DeformableParticle_Attributes[] = { + QSGGeometry::Attribute::create(0, 4, GL_FLOAT), // Position & TexCoord + QSGGeometry::Attribute::create(1, 4, GL_FLOAT), // Data + QSGGeometry::Attribute::create(2, 4, GL_FLOAT), // Vectors + QSGGeometry::Attribute::create(3, 4, GL_UNSIGNED_BYTE), // Colors + QSGGeometry::Attribute::create(4, 4, GL_FLOAT), // DeformationVectors + QSGGeometry::Attribute::create(5, 3, GL_FLOAT), // Rotation +}; + +static QSGGeometry::AttributeSet DeformableParticle_AttributeSet = +{ + 6, // Attribute Count + (4 + 4 + 4 + 4 + 3) * sizeof(float) + 4 * sizeof(uchar), + DeformableParticle_Attributes +}; + +static QSGGeometry::Attribute SpriteParticle_Attributes[] = { + QSGGeometry::Attribute::create(0, 4, GL_FLOAT), // Position & TexCoord + QSGGeometry::Attribute::create(1, 4, GL_FLOAT), // Data + QSGGeometry::Attribute::create(2, 4, GL_FLOAT), // Vectors + QSGGeometry::Attribute::create(3, 4, GL_UNSIGNED_BYTE), // Colors + QSGGeometry::Attribute::create(4, 4, GL_FLOAT), // DeformationVectors + QSGGeometry::Attribute::create(5, 3, GL_FLOAT), // Rotation + QSGGeometry::Attribute::create(6, 3, GL_FLOAT), // Anim Data + QSGGeometry::Attribute::create(7, 4, GL_FLOAT) // Anim Pos +}; + +static QSGGeometry::AttributeSet SpriteParticle_AttributeSet = +{ + 8, // Attribute Count + (4 + 4 + 4 + 4 + 3 + 3 + 4) * sizeof(float) + 4 * sizeof(uchar), + SpriteParticle_Attributes +}; + +void QQuickImageParticle::clearShadows() +{ + foreach (const QVector<QQuickParticleData*> data, m_shadowData) + qDeleteAll(data); + m_shadowData.clear(); +} + +//Only call if you need to, may initialize the whole array first time +QQuickParticleData* QQuickImageParticle::getShadowDatum(QQuickParticleData* datum) +{ + //Will return datum if the datum is a sentinel or uninitialized, to centralize that one check + if (datum->systemIndex == -1) + return datum; + QQuickParticleGroupData* gd = m_system->groupData[datum->group]; + if (!m_shadowData.contains(datum->group)) { + QVector<QQuickParticleData*> data; + for (int i=0; i<gd->size(); i++){ + QQuickParticleData* datum = new QQuickParticleData(m_system); + *datum = *(gd->data[i]); + data << datum; + } + m_shadowData.insert(datum->group, data); + } + //### If dynamic resize is added, remember to potentially resize the shadow data on out-of-bounds access request + + return m_shadowData[datum->group][datum->index]; +} + +bool QQuickImageParticle::loadingSomething() +{ + return (m_image && m_image->pix.isLoading()) + || (m_colorTable && m_colorTable->pix.isLoading()) + || (m_sizeTable && m_sizeTable->pix.isLoading()) + || (m_opacityTable && m_opacityTable->pix.isLoading()) + || (m_spriteEngine && m_spriteEngine->isLoading()); +} + +void QQuickImageParticle::buildParticleNodes()//Starts async parts, like loading images. +{ + if (m_rootNode || loadingSomething()) + return; + + if (!m_buildingNodes) { + if (m_image) {//ImageData created on setSource + m_image->pix.clear(this); + m_image->pix.load(qmlEngine(this), m_image->source); + } + + if (m_spriteEngine) + m_spriteEngine->startAssemblingImage(); + + if (m_colorTable) + m_colorTable->pix.load(qmlEngine(this), m_colorTable->source); + + if (m_sizeTable) + m_sizeTable->pix.load(qmlEngine(this), m_sizeTable->source); + + if (m_opacityTable) + m_opacityTable->pix.load(qmlEngine(this), m_opacityTable->source); + + m_buildingNodes = true; + if (loadingSomething()) + return; + } + finishBuildParticleNodes(); +} + +void QQuickImageParticle::finishBuildParticleNodes() +{ + m_buildingNodes = false; +#ifdef QT_OPENGL_ES_2 + if (m_count * 4 > 0xffff) { + printf("ImageParticle: Too many particles - maximum 16,000 per ImageParticle.\n");//ES 2 vertex count limit is ushort + return; + } +#endif + + if (count() <= 0) + return; + + m_debugMode = m_system->m_debugMode; + + if (m_sprites.count() || m_bypassOptimizations) { + perfLevel = Sprites; + } else if (m_colorTable || m_sizeTable || m_opacityTable) { + perfLevel = Tabled; + } else if (m_autoRotation || m_rotation || m_rotationVariation + || m_rotationSpeed || m_rotationSpeedVariation + || m_xVector || m_yVector) { + perfLevel = Deformable; + } else if (m_alphaVariation || m_alpha != 1.0 || m_color.isValid() || m_color_variation + || m_redVariation || m_blueVariation || m_greenVariation) { + perfLevel = Colored; + } else { + perfLevel = Simple; + } + + foreach (const QString &str, m_groups){//For sharing higher levels, need to have highest used so it renders + int gIdx = m_system->groupIds[str]; + foreach (QQuickParticlePainter* p, m_system->groupData[gIdx]->painters){ + QQuickImageParticle* other = qobject_cast<QQuickImageParticle*>(p); + if (other){ + if (other->perfLevel > perfLevel) { + if (other->perfLevel >= Tabled){//Deformable is the highest level needed for this, anything higher isn't shared (or requires your own sprite) + if (perfLevel < Deformable) + perfLevel = Deformable; + } else { + perfLevel = other->perfLevel; + } + } else if (other->perfLevel < perfLevel) { + other->reset(); + } + } + } + } +#ifdef Q_OS_WIN + if (perfLevel < Deformable) //QTBUG-24540 , point sprite 'extension' isn't working on windows. + perfLevel = Deformable; +#endif + + if (perfLevel >= Colored && !m_color.isValid()) + m_color = QColor(Qt::white);//Hidden default, but different from unset + + clearShadows(); + if (m_material) + m_material = 0; + + //Setup material + QImage colortable; + QImage sizetable; + QImage opacitytable; + QImage image; + bool imageLoaded = false; + switch (perfLevel) {//Fallthrough intended + case Sprites: + if (!m_spriteEngine) { + qWarning() << "ImageParticle: No sprite engine..."; + //Sprite performance mode with static image is supported, but not advised + //Note that in this case it always uses shadow data + } else { + image = m_spriteEngine->assembledImage(); + if (image.isNull())//Warning is printed in engine + return; + imageLoaded = true; + } + m_material = SpriteMaterial::createMaterial(); + if (imageLoaded) + getState<ImageMaterialData>(m_material)->texture = QSGPlainTexture::fromImage(image); + getState<ImageMaterialData>(m_material)->animSheetSize = QSizeF(image.size()); + if (m_spriteEngine) + m_spriteEngine->setCount(m_count); + case Tabled: + if (!m_material) + m_material = TabledMaterial::createMaterial(); + + if (m_colorTable) { + if (m_colorTable->pix.isReady()) + colortable = m_colorTable->pix.image(); + else + qmlInfo(this) << "Error loading color table: " << m_colorTable->pix.error(); + } + + if (m_sizeTable) { + if (m_sizeTable->pix.isReady()) + sizetable = m_sizeTable->pix.image(); + else + qmlInfo(this) << "Error loading size table: " << m_sizeTable->pix.error(); + } + + if (m_opacityTable) { + if (m_opacityTable->pix.isReady()) + opacitytable = m_opacityTable->pix.image(); + else + qmlInfo(this) << "Error loading opacity table: " << m_opacityTable->pix.error(); + } + + if (colortable.isNull()){//###Goes through image just for this + colortable = QImage(1,1,QImage::Format_ARGB32_Premultiplied); + colortable.fill(Qt::white); + } + getState<ImageMaterialData>(m_material)->colorTable = QSGPlainTexture::fromImage(colortable); + fillUniformArrayFromImage(getState<ImageMaterialData>(m_material)->sizeTable, sizetable, UNIFORM_ARRAY_SIZE); + fillUniformArrayFromImage(getState<ImageMaterialData>(m_material)->opacityTable, opacitytable, UNIFORM_ARRAY_SIZE); + case Deformable: + if (!m_material) + m_material = DeformableMaterial::createMaterial(); + case Colored: + if (!m_material) + m_material = ColoredMaterial::createMaterial(); + default://Also Simple + if (!m_material) + m_material = SimpleMaterial::createMaterial(); + if (!imageLoaded) { + if (!m_image->pix.isReady()) { + qmlInfo(this) << m_image->pix.error(); + delete m_material; + return; + } + //getState<ImageMaterialData>(m_material)->texture //TODO: Shouldn't this be better? But not crash? + // = QQuickItemPrivate::get(this)->sceneGraphContext()->textureForFactory(m_imagePix.textureFactory()); + getState<ImageMaterialData>(m_material)->texture = QSGPlainTexture::fromImage(m_image->pix.image()); + } + getState<ImageMaterialData>(m_material)->texture->setFiltering(QSGTexture::Linear); + getState<ImageMaterialData>(m_material)->entry = (qreal) m_entryEffect; + m_material->setFlag(QSGMaterial::Blending); + } + + m_nodes.clear(); + foreach (const QString &str, m_groups){ + int gIdx = m_system->groupIds[str]; + int count = m_system->groupData[gIdx]->size(); + QSGGeometryNode* node = new QSGGeometryNode(); + node->setMaterial(m_material); + node->markDirty(QSGNode::DirtyMaterial); + + m_nodes.insert(gIdx, node); + m_idxStarts.insert(gIdx, m_lastIdxStart); + m_startsIdx.append(qMakePair<int,int>(m_lastIdxStart, gIdx)); + m_lastIdxStart += count; + + //Create Particle Geometry + int vCount = count * 4; + int iCount = count * 6; + + QSGGeometry *g; + if (perfLevel == Sprites) + g = new QSGGeometry(SpriteParticle_AttributeSet, vCount, iCount); + else if (perfLevel == Tabled) + g = new QSGGeometry(DeformableParticle_AttributeSet, vCount, iCount); + else if (perfLevel == Deformable) + g = new QSGGeometry(DeformableParticle_AttributeSet, vCount, iCount); + else if (perfLevel == Colored) + g = new QSGGeometry(ColoredParticle_AttributeSet, count, 0); + else //Simple + g = new QSGGeometry(SimpleParticle_AttributeSet, count, 0); + + node->setGeometry(g); + if (perfLevel <= Colored){ + g->setDrawingMode(GL_POINTS); + if (m_debugMode){ + GLfloat pointSizeRange[2]; + glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, pointSizeRange); + qDebug() << "Using point sprites, GL_ALIASED_POINT_SIZE_RANGE " <<pointSizeRange[0] << ":" << pointSizeRange[1]; + } + }else + g->setDrawingMode(GL_TRIANGLES); + + for (int p=0; p < count; ++p) + commit(gIdx, p);//commit sets geometry for the node, has its own perfLevel switch + + if (perfLevel == Sprites) + initTexCoords<SpriteVertex>((SpriteVertex*)g->vertexData(), vCount); + else if (perfLevel == Tabled) + initTexCoords<DeformableVertex>((DeformableVertex*)g->vertexData(), vCount); + else if (perfLevel == Deformable) + initTexCoords<DeformableVertex>((DeformableVertex*)g->vertexData(), vCount); + + if (perfLevel > Colored){ + quint16 *indices = g->indexDataAsUShort(); + for (int i=0; i < count; ++i) { + int o = i * 4; + indices[0] = o; + indices[1] = o + 1; + indices[2] = o + 2; + indices[3] = o + 1; + indices[4] = o + 3; + indices[5] = o + 2; + indices += 6; + } + } + } + + if (perfLevel == Sprites) + spritesUpdate();//Gives all vertexes the initial sprite data, then maintained per frame + + foreach (QSGGeometryNode* node, m_nodes){ + if (node == *(m_nodes.begin())) + node->setFlag(QSGGeometryNode::OwnsMaterial);//Root node owns the material for memory management purposes + else + (*(m_nodes.begin()))->appendChildNode(node); + } + + m_rootNode = *(m_nodes.begin()); + update(); +} + +QSGNode *QQuickImageParticle::updatePaintNode(QSGNode *, UpdatePaintNodeData *) +{ + if (m_pleaseReset){ + m_lastLevel = perfLevel; + + delete m_rootNode;//Automatically deletes children, and SG manages material lifetime + m_rootNode = 0; + m_nodes.clear(); + + m_idxStarts.clear(); + m_startsIdx.clear(); + m_lastIdxStart = 0; + + m_material = 0; + + m_pleaseReset = false; + m_buildingNodes = false;//Cancel a part-way build + } + + if (m_system && m_system->isRunning() && !m_system->isPaused()){ + prepareNextFrame(); + if (m_rootNode) { + update(); + foreach (QSGGeometryNode* node, m_nodes) + node->markDirty(QSGNode::DirtyGeometry); + } else if (m_buildingNodes) { + update();//To call prepareNextFrame() again from the renderThread + } + } + + return m_rootNode; +} + +void QQuickImageParticle::prepareNextFrame() +{ + if (m_rootNode == 0){//TODO: Staggered loading (as emitted) + buildParticleNodes(); + if (m_debugMode) { + qDebug() << "QQuickImageParticle Feature level: " << perfLevel; + qDebug() << "QQuickImageParticle Nodes: "; + int count = 0; + foreach (int i, m_nodes.keys()) { + qDebug() << "Group " << i << " (" << m_system->groupData[i]->size() << " particles)"; + count += m_system->groupData[i]->size(); + } + qDebug() << "Total count: " << count; + } + if (m_rootNode == 0) + return; + } + qint64 timeStamp = m_system->systemSync(this); + + qreal time = timeStamp / 1000.; + + switch (perfLevel){//Fall-through intended + case Sprites: + //Advance State + if (m_spriteEngine) + m_spriteEngine->updateSprites(timeStamp);//fires signals if anim changed + spritesUpdate(time); + case Tabled: + case Deformable: + case Colored: + case Simple: + default: //Also Simple + getState<ImageMaterialData>(m_material)->timestamp = time; + break; + } + foreach (QSGGeometryNode* node, m_nodes) + node->markDirty(QSGNode::DirtyMaterial); +} + +void QQuickImageParticle::spritesUpdate(qreal time) +{ + // Sprite progression handled CPU side, so as to have per-frame control. + foreach (const QString &str, m_groups) { + int gIdx = m_system->groupIds[str]; + foreach (QQuickParticleData* mainDatum, m_system->groupData[gIdx]->data) { + QSGGeometryNode *node = m_nodes[gIdx]; + if (!node) + continue; + //TODO: Interpolate between two different animations if it's going to transition next frame + // This is particularly important for cut-up sprites. + QQuickParticleData* datum = (mainDatum->animationOwner == this ? mainDatum : getShadowDatum(mainDatum)); + int spriteIdx = 0; + for (int i = 0; i<m_startsIdx.count(); i++) { + if (m_startsIdx[i].second == gIdx){ + spriteIdx = m_startsIdx[i].first + datum->index; + break; + } + } + + double frameAt; + qreal progress = 0; + + if (datum->frameDuration > 0) { + qreal frame = (time - datum->animT)/(datum->frameDuration / 1000.0); + frame = qBound((qreal)0.0, frame, (qreal)((qreal)datum->frameCount - 1.0));//Stop at count-1 frames until we have between anim interpolation + if (m_spritesInterpolate) + progress = modf(frame,&frameAt); + else + modf(frame,&frameAt); + } else { + datum->frameAt++; + if (datum->frameAt >= datum->frameCount){ + datum->frameAt = 0; + m_spriteEngine->advance(spriteIdx); + } + frameAt = datum->frameAt; + } + if (m_spriteEngine->sprite(spriteIdx)->reverse())//### Store this in datum too? + frameAt = (datum->frameCount - 1) - frameAt; + QSizeF sheetSize = getState<ImageMaterialData>(m_material)->animSheetSize; + qreal y = datum->animY / sheetSize.height(); + qreal w = datum->animWidth / sheetSize.width(); + qreal h = datum->animHeight / sheetSize.height(); + qreal x1 = datum->animX / sheetSize.width(); + x1 += frameAt * w; + qreal x2 = x1; + if (frameAt < (datum->frameCount-1)) + x2 += w; + + node->setFlag(QSGNode::OwnsGeometry, false); + SpriteVertex *spriteVertices = (SpriteVertex *) node->geometry()->vertexData(); + spriteVertices += datum->index*4; + for (int i=0; i<4; i++) { + spriteVertices[i].animX1 = x1; + spriteVertices[i].animY1 = y; + spriteVertices[i].animX2 = x2; + spriteVertices[i].animY2 = y; + spriteVertices[i].animW = w; + spriteVertices[i].animH = h; + spriteVertices[i].animProgress = progress; + } + node->setFlag(QSGNode::OwnsGeometry, true); + } + } +} + +void QQuickImageParticle::spriteAdvance(int spriteIdx) +{ + if (!m_startsIdx.count())//Probably overly defensive + return; + + int gIdx = -1; + int i; + for (i = 0; i<m_startsIdx.count(); i++) { + if (spriteIdx < m_startsIdx[i].first) { + gIdx = m_startsIdx[i-1].second; + break; + } + } + if (gIdx == -1) + gIdx = m_startsIdx[i-1].second; + int pIdx = spriteIdx - m_startsIdx[i-1].first; + + QQuickParticleData* mainDatum = m_system->groupData[gIdx]->data[pIdx]; + QQuickParticleData* datum = (mainDatum->animationOwner == this ? mainDatum : getShadowDatum(mainDatum)); + + datum->animIdx = m_spriteEngine->spriteState(spriteIdx); + datum->animT = m_spriteEngine->spriteStart(spriteIdx)/1000.0; + datum->frameCount = m_spriteEngine->spriteFrames(spriteIdx); + datum->frameDuration = m_spriteEngine->spriteDuration(spriteIdx) / datum->frameCount; + datum->animX = m_spriteEngine->spriteX(spriteIdx); + datum->animY = m_spriteEngine->spriteY(spriteIdx); + datum->animWidth = m_spriteEngine->spriteWidth(spriteIdx); + datum->animHeight = m_spriteEngine->spriteHeight(spriteIdx); +} + +void QQuickImageParticle::reloadColor(const Color4ub &c, QQuickParticleData* d) +{ + d->color = c; + //TODO: get index for reload - or make function take an index +} + +void QQuickImageParticle::initialize(int gIdx, int pIdx) +{ + Color4ub color; + QQuickParticleData* datum = m_system->groupData[gIdx]->data[pIdx]; + qreal redVariation = m_color_variation + m_redVariation; + qreal greenVariation = m_color_variation + m_greenVariation; + qreal blueVariation = m_color_variation + m_blueVariation; + int spriteIdx = 0; + if (m_spriteEngine) { + spriteIdx = m_idxStarts[gIdx] + datum->index; + if (spriteIdx >= m_spriteEngine->count()) + m_spriteEngine->setCount(spriteIdx+1); + } + + float rotation; + float rotationSpeed; + float autoRotate; + switch (perfLevel){//Fall-through is intended on all of them + case Sprites: + // Initial Sprite State + if (m_explicitAnimation && m_spriteEngine){ + if (!datum->animationOwner) + datum->animationOwner = this; + QQuickParticleData* writeTo = (datum->animationOwner == this ? datum : getShadowDatum(datum)); + writeTo->animT = writeTo->t; + //writeTo->animInterpolate = m_spritesInterpolate; + if (m_spriteEngine){ + m_spriteEngine->start(spriteIdx); + writeTo->frameCount = m_spriteEngine->spriteFrames(spriteIdx); + writeTo->frameDuration = m_spriteEngine->spriteDuration(spriteIdx) / writeTo->frameCount; + writeTo->animIdx = 0;//Always starts at 0 + writeTo->frameAt = -1; + writeTo->animX = m_spriteEngine->spriteX(spriteIdx); + writeTo->animY = m_spriteEngine->spriteY(spriteIdx); + writeTo->animWidth = m_spriteEngine->spriteWidth(spriteIdx); + writeTo->animHeight = m_spriteEngine->spriteHeight(spriteIdx); + } + } else { + QQuickParticleData* writeTo = getShadowDatum(datum); + writeTo->animT = datum->t; + writeTo->frameCount = 1; + writeTo->frameDuration = 60000000.0; + writeTo->frameAt = -1; + writeTo->animIdx = 0; + writeTo->animT = 0; + writeTo->animX = writeTo->animY = 0; + writeTo->animWidth = getState<ImageMaterialData>(m_material)->animSheetSize.width(); + writeTo->animHeight = getState<ImageMaterialData>(m_material)->animSheetSize.height(); + } + case Tabled: + case Deformable: + //Initial Rotation + if (m_explicitDeformation){ + if (!datum->deformationOwner) + datum->deformationOwner = this; + if (m_xVector){ + const QPointF &ret = m_xVector->sample(QPointF(datum->x, datum->y)); + if (datum->deformationOwner == this) { + datum->xx = ret.x(); + datum->xy = ret.y(); + } else { + getShadowDatum(datum)->xx = ret.x(); + getShadowDatum(datum)->xy = ret.y(); + } + } + if (m_yVector){ + const QPointF &ret = m_yVector->sample(QPointF(datum->x, datum->y)); + if (datum->deformationOwner == this) { + datum->yx = ret.x(); + datum->yy = ret.y(); + } else { + getShadowDatum(datum)->yx = ret.x(); + getShadowDatum(datum)->yy = ret.y(); + } + } + } + + if (m_explicitRotation){ + if (!datum->rotationOwner) + datum->rotationOwner = this; + rotation = + (m_rotation + (m_rotationVariation - 2*((qreal)rand()/RAND_MAX)*m_rotationVariation) ) * CONV; + rotationSpeed = + (m_rotationSpeed + (m_rotationSpeedVariation - 2*((qreal)rand()/RAND_MAX)*m_rotationSpeedVariation) ) * CONV; + autoRotate = m_autoRotation?1.0:0.0; + if (datum->rotationOwner == this) { + datum->rotation = rotation; + datum->rotationSpeed = rotationSpeed; + datum->autoRotate = autoRotate; + } else { + getShadowDatum(datum)->rotation = rotation; + getShadowDatum(datum)->rotationSpeed = rotationSpeed; + getShadowDatum(datum)->autoRotate = autoRotate; + } + } + case Colored: + //Color initialization + // Particle color + if (m_explicitColor) { + if (!datum->colorOwner) + datum->colorOwner = this; + color.r = m_color.red() * (1 - redVariation) + rand() % 256 * redVariation; + color.g = m_color.green() * (1 - greenVariation) + rand() % 256 * greenVariation; + color.b = m_color.blue() * (1 - blueVariation) + rand() % 256 * blueVariation; + color.a = m_alpha * m_color.alpha() * (1 - m_alphaVariation) + rand() % 256 * m_alphaVariation; + if (datum->colorOwner == this) + datum->color = color; + else + getShadowDatum(datum)->color = color; + } + default: + break; + } +} + +void QQuickImageParticle::commit(int gIdx, int pIdx) +{ + if (m_pleaseReset) + return; + QSGGeometryNode *node = m_nodes[gIdx]; + if (!node) + return; + QQuickParticleData* datum = m_system->groupData[gIdx]->data[pIdx]; + node->setFlag(QSGNode::OwnsGeometry, false); + SpriteVertex *spriteVertices = (SpriteVertex *) node->geometry()->vertexData(); + DeformableVertex *deformableVertices = (DeformableVertex *) node->geometry()->vertexData(); + ColoredVertex *coloredVertices = (ColoredVertex *) node->geometry()->vertexData(); + SimpleVertex *simpleVertices = (SimpleVertex *) node->geometry()->vertexData(); + switch (perfLevel){//No automatic fall through intended on this one + case Sprites: + spriteVertices += pIdx*4; + for (int i=0; i<4; i++){ + spriteVertices[i].x = datum->x - m_systemOffset.x(); + spriteVertices[i].y = datum->y - m_systemOffset.y(); + spriteVertices[i].t = datum->t; + spriteVertices[i].lifeSpan = datum->lifeSpan; + spriteVertices[i].size = datum->size; + spriteVertices[i].endSize = datum->endSize; + spriteVertices[i].vx = datum->vx; + spriteVertices[i].vy = datum->vy; + spriteVertices[i].ax = datum->ax; + spriteVertices[i].ay = datum->ay; + if (m_explicitDeformation && datum->deformationOwner != this) { + QQuickParticleData* shadow = getShadowDatum(datum); + spriteVertices[i].xx = shadow->xx; + spriteVertices[i].xy = shadow->xy; + spriteVertices[i].yx = shadow->yx; + spriteVertices[i].yy = shadow->yy; + } else { + spriteVertices[i].xx = datum->xx; + spriteVertices[i].xy = datum->xy; + spriteVertices[i].yx = datum->yx; + spriteVertices[i].yy = datum->yy; + } + if (m_explicitRotation && datum->rotationOwner != this) { + QQuickParticleData* shadow = getShadowDatum(datum); + spriteVertices[i].rotation = shadow->rotation; + spriteVertices[i].rotationSpeed = shadow->rotationSpeed; + spriteVertices[i].autoRotate = shadow->autoRotate; + } else { + spriteVertices[i].rotation = datum->rotation; + spriteVertices[i].rotationSpeed = datum->rotationSpeed; + spriteVertices[i].autoRotate = datum->autoRotate; + } + //Sprite-related vertices updated per-frame in spritesUpdate(), not on demand + if (m_explicitColor && datum->colorOwner != this) { + QQuickParticleData* shadow = getShadowDatum(datum); + spriteVertices[i].color.r = shadow->color.r; + spriteVertices[i].color.g = shadow->color.g; + spriteVertices[i].color.b = shadow->color.b; + spriteVertices[i].color.a = shadow->color.a; + } else { + spriteVertices[i].color.r = datum->color.r; + spriteVertices[i].color.g = datum->color.g; + spriteVertices[i].color.b = datum->color.b; + spriteVertices[i].color.a = datum->color.a; + } + } + break; + case Tabled: //Fall through until it has its own vertex class + case Deformable: + deformableVertices += pIdx*4; + for (int i=0; i<4; i++){ + deformableVertices[i].x = datum->x - m_systemOffset.x(); + deformableVertices[i].y = datum->y - m_systemOffset.y(); + deformableVertices[i].t = datum->t; + deformableVertices[i].lifeSpan = datum->lifeSpan; + deformableVertices[i].size = datum->size; + deformableVertices[i].endSize = datum->endSize; + deformableVertices[i].vx = datum->vx; + deformableVertices[i].vy = datum->vy; + deformableVertices[i].ax = datum->ax; + deformableVertices[i].ay = datum->ay; + if (m_explicitDeformation && datum->deformationOwner != this) { + QQuickParticleData* shadow = getShadowDatum(datum); + deformableVertices[i].xx = shadow->xx; + deformableVertices[i].xy = shadow->xy; + deformableVertices[i].yx = shadow->yx; + deformableVertices[i].yy = shadow->yy; + } else { + deformableVertices[i].xx = datum->xx; + deformableVertices[i].xy = datum->xy; + deformableVertices[i].yx = datum->yx; + deformableVertices[i].yy = datum->yy; + } + if (m_explicitRotation && datum->rotationOwner != this) { + QQuickParticleData* shadow = getShadowDatum(datum); + deformableVertices[i].rotation = shadow->rotation; + deformableVertices[i].rotationSpeed = shadow->rotationSpeed; + deformableVertices[i].autoRotate = shadow->autoRotate; + } else { + deformableVertices[i].rotation = datum->rotation; + deformableVertices[i].rotationSpeed = datum->rotationSpeed; + deformableVertices[i].autoRotate = datum->autoRotate; + } + if (m_explicitColor && datum->colorOwner != this) { + QQuickParticleData* shadow = getShadowDatum(datum); + deformableVertices[i].color.r = shadow->color.r; + deformableVertices[i].color.g = shadow->color.g; + deformableVertices[i].color.b = shadow->color.b; + deformableVertices[i].color.a = shadow->color.a; + } else { + deformableVertices[i].color.r = datum->color.r; + deformableVertices[i].color.g = datum->color.g; + deformableVertices[i].color.b = datum->color.b; + deformableVertices[i].color.a = datum->color.a; + } + } + break; + case Colored: + coloredVertices += pIdx*1; + for (int i=0; i<1; i++){ + coloredVertices[i].x = datum->x - m_systemOffset.x(); + coloredVertices[i].y = datum->y - m_systemOffset.y(); + coloredVertices[i].t = datum->t; + coloredVertices[i].lifeSpan = datum->lifeSpan; + coloredVertices[i].size = datum->size; + coloredVertices[i].endSize = datum->endSize; + coloredVertices[i].vx = datum->vx; + coloredVertices[i].vy = datum->vy; + coloredVertices[i].ax = datum->ax; + coloredVertices[i].ay = datum->ay; + if (m_explicitColor && datum->colorOwner != this) { + QQuickParticleData* shadow = getShadowDatum(datum); + coloredVertices[i].color.r = shadow->color.r; + coloredVertices[i].color.g = shadow->color.g; + coloredVertices[i].color.b = shadow->color.b; + coloredVertices[i].color.a = shadow->color.a; + } else { + coloredVertices[i].color.r = datum->color.r; + coloredVertices[i].color.g = datum->color.g; + coloredVertices[i].color.b = datum->color.b; + coloredVertices[i].color.a = datum->color.a; + } + } + break; + case Simple: + simpleVertices += pIdx*1; + for (int i=0; i<1; i++){ + simpleVertices[i].x = datum->x - m_systemOffset.x(); + simpleVertices[i].y = datum->y - m_systemOffset.y(); + simpleVertices[i].t = datum->t; + simpleVertices[i].lifeSpan = datum->lifeSpan; + simpleVertices[i].size = datum->size; + simpleVertices[i].endSize = datum->endSize; + simpleVertices[i].vx = datum->vx; + simpleVertices[i].vy = datum->vy; + simpleVertices[i].ax = datum->ax; + simpleVertices[i].ay = datum->ay; + } + break; + default: + break; + } + + node->setFlag(QSGNode::OwnsGeometry, true); +} + + + +QT_END_NAMESPACE |