/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** 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 Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/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 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, Digia gives you certain additional ** rights. These rights are described in the Digia 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. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include #include "qquickimageparticle_p.h" #include "qquickparticleemitter_p.h" #include #include #include #include #include #include #include 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 velocity, 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 velocity, 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" #if defined (Q_OS_BLACKBERRY) " highp float fade = 1.;\n" #else " lowp float fade = 1.;\n" #endif " 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" " if (length(curVel) > 0.)\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 velocity\n" " + 0.5 * vVec.zw * pow(t * vData.y, 2.); // apply acceleration\n" "#else\n" " pos = vPos\n" " + vVec.xy * t * vData.y // apply velocity 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 { 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 attributes() const { return QList() << "vPosTex" << "vData" << "vVec" << "vColor" << "vDeformVec" << "vRotation"; }; void initialize() { QSGSimpleMaterialShader::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 { 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 attributes() const { return QList() << "vPosTex" << "vData" << "vVec" << "vColor" << "vDeformVec" << "vRotation"; }; void initialize() { QSGSimpleMaterialShader::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 { 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 attributes() const { return QList() << "vPosTex" << "vData" << "vVec" << "vColor" << "vDeformVec" << "vRotation" << "vAnimData" << "vAnimPos"; }; void initialize() { QSGSimpleMaterialShader::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 { 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::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::deactivate(); #if !defined(QT_OPENGL_ES_2) && !defined(Q_OS_WIN) glDisable(GL_POINT_SPRITE); glDisable(GL_VERTEX_PROGRAM_POINT_SIZE); #endif } QList attributes() const { return QList() << "vPos" << "vData" << "vVec" << "vColor"; } void initialize() { QSGSimpleMaterialShader::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 { 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::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::deactivate(); #if !defined(QT_OPENGL_ES_2) && !defined(Q_OS_WIN) glDisable(GL_POINT_SPRITE); glDisable(GL_VERTEX_PROGRAM_POINT_SIZE); #endif } QList attributes() const { return QList() << "vPos" << "vData" << "vVec"; } void initialize() { QSGSimpleMaterialShader::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 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. For full details, see the \l{Sprite Animation} overview. */ /*! \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::rotationVelocity If set particles will rotate at this velocity in degrees/second. */ /*! \qmlproperty real QtQuick.Particles2::ImageParticle::rotationVelocityVariation If set the rotationVelocity 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_rotationVelocity(0) , m_rotationVelocityVariation(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 QQuickImageParticle::sprites() { return QQmlListProperty(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; m_image = 0; 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::setRotationVelocity(qreal arg) { if (m_rotationVelocity != arg) { m_rotationVelocity = arg; emit rotationVelocityChanged(arg); } m_explicitRotation = true; if (perfLevel < Deformable) reset(); } void QQuickImageParticle::setRotationVelocityVariation(qreal arg) { if (m_rotationVelocityVariation != arg) { m_rotationVelocityVariation = arg; emit rotationVelocityVariationChanged(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(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_rotationVelocity = 0; m_rotationVelocityVariation = 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 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 data; for (int i=0; isize(); 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_rotationVelocity || m_rotationVelocityVariation || 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(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(m_material)->texture = QSGPlainTexture::fromImage(image); getState(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(m_material)->colorTable = QSGPlainTexture::fromImage(colortable); fillUniformArrayFromImage(getState(m_material)->sizeTable, sizetable, UNIFORM_ARRAY_SIZE); fillUniformArrayFromImage(getState(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 || !m_image->pix.isReady()) { if (m_image) qmlInfo(this) << m_image->pix.error(); delete m_material; return; } //getState(m_material)->texture //TODO: Shouldn't this be better? But not crash? // = QQuickItemPrivate::get(this)->sceneGraphContext()->textureForFactory(m_imagePix.textureFactory()); getState(m_material)->texture = QSGPlainTexture::fromImage(m_image->pix.image()); } getState(m_material)->texture->setFiltering(QSGTexture::Linear); getState(m_material)->entry = (qreal) m_entryEffect; m_material->setFlag(QSGMaterial::Blending | QSGMaterial::RequiresFullMatrix); } 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(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 " <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*)g->vertexData(), vCount); else if (perfLevel == Tabled) initTexCoords((DeformableVertex*)g->vertexData(), vCount); else if (perfLevel == Deformable) initTexCoords((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(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; iindex; 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(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; igroupData[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 rotationVelocity; 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(m_material)->animSheetSize.width(); writeTo->animHeight = getState(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; rotationVelocity = (m_rotationVelocity + (m_rotationVelocityVariation - 2*((qreal)rand()/RAND_MAX)*m_rotationVelocityVariation) ) * CONV; autoRotate = m_autoRotation?1.0:0.0; if (datum->rotationOwner == this) { datum->rotation = rotation; datum->rotationVelocity = rotationVelocity; datum->autoRotate = autoRotate; } else { getShadowDatum(datum)->rotation = rotation; getShadowDatum(datum)->rotationVelocity = rotationVelocity; 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].rotationVelocity = shadow->rotationVelocity; spriteVertices[i].autoRotate = shadow->autoRotate; } else { spriteVertices[i].rotation = datum->rotation; spriteVertices[i].rotationVelocity = datum->rotationVelocity; 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].rotationVelocity = shadow->rotationVelocity; deformableVertices[i].autoRotate = shadow->autoRotate; } else { deformableVertices[i].rotation = datum->rotation; deformableVertices[i].rotationVelocity = datum->rotationVelocity; 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