Revision control

++ // Copyright (C) 2021 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include <QtCore/qtconfigmacros.h>
++ #undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
++
++ #include <QtQuick/private/qsgcontext_p.h>
++ #include <private/qsgadaptationlayer_p.h>
++ #include <private/qquickitem_p.h>
++ #include <QtQuick/qsgnode.h>
++ #include <QtQuick/qsgtexture.h>
++ #include <QFile>
++ #include <QRandomGenerator>
++ #include "qquickimageparticle_p.h"
++ #include "qquickparticleemitter_p.h"
++ #include <private/qquicksprite_p.h>
++ #include <private/qquickspriteengine_p.h>
++ #include <QSGRendererInterface>
++ #include <QtQuick/private/qsgplaintexture_p.h>
++ #include <private/qqmlglobal_p.h>
++ #include <QtQml/qqmlinfo.h>
++ #include <QtCore/QtMath>
++ #include <rhi/qrhi.h>
++
++ #include <cmath>
++
++ QT_BEGIN_NAMESPACE
++
++ // Must match the shader code
++ #define UNIFORM_ARRAY_SIZE 64
++
++ class ImageMaterialData
++ {
++ public:
++ ImageMaterialData()
++ : texture(nullptr), colorTable(nullptr)
++ {}
++
++ ~ImageMaterialData(){
++ delete texture;
++ delete colorTable;
++ }
++
++ QSGTexture *texture;
++ QSGTexture *colorTable;
++ float sizeTable[UNIFORM_ARRAY_SIZE];
++ float opacityTable[UNIFORM_ARRAY_SIZE];
++
++ qreal dpr;
++ qreal timestamp;
++ qreal entry;
++ QSizeF animSheetSize;
++ };
++
++ class TabledMaterialRhiShader : public QSGMaterialShader
++ {
++ public:
++ TabledMaterialRhiShader(int viewCount)
++ {
++ setShaderFileName(VertexStage, QStringLiteral(":/particles/shaders_ng/imageparticle_tabled.vert.qsb"), viewCount);
++ setShaderFileName(FragmentStage, QStringLiteral(":/particles/shaders_ng/imageparticle_tabled.frag.qsb"), viewCount);
++ }
++
++ bool updateUniformData(RenderState &renderState, QSGMaterial *newMaterial, QSGMaterial *) override
++ {
++ QByteArray *buf = renderState.uniformData();
++ Q_ASSERT(buf->size() >= 80 + 2 * (UNIFORM_ARRAY_SIZE * 4 * 4));
++ const int shaderMatrixCount = newMaterial->viewCount();
++ const int matrixCount = qMin(renderState.projectionMatrixCount(), shaderMatrixCount);
++
++ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
++ if (renderState.isMatrixDirty()) {
++ const QMatrix4x4 m = renderState.combinedMatrix(viewIndex);
++ memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
++ }
++ }
++
++ if (renderState.isOpacityDirty()) {
++ const float opacity = renderState.opacity();
++ memcpy(buf->data() + 64 * shaderMatrixCount, &opacity, 4);
++ }
++
++ ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state();
++
++ float entry = float(state->entry);
++ memcpy(buf->data() + 64 * shaderMatrixCount + 4, &entry, 4);
++
++ float timestamp = float(state->timestamp);
++ memcpy(buf->data() + 64 * shaderMatrixCount + 8, &timestamp, 4);
++
++ float *p = reinterpret_cast<float *>(buf->data() + 64 * shaderMatrixCount + 16);
++ for (int i = 0; i < UNIFORM_ARRAY_SIZE; ++i) {
++ *p = state->sizeTable[i];
++ p += 4;
++ }
++ p = reinterpret_cast<float *>(buf->data() + 64 * shaderMatrixCount + 16 + (UNIFORM_ARRAY_SIZE * 4 * 4));
++ for (int i = 0; i < UNIFORM_ARRAY_SIZE; ++i) {
++ *p = state->opacityTable[i];
++ p += 4;
++ }
++
++ return true;
++ }
++
++ void updateSampledImage(RenderState &renderState, int binding, QSGTexture **texture,
++ QSGMaterial *newMaterial, QSGMaterial *) override
++ {
++ ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state();
++ if (binding == 2) {
++ state->colorTable->commitTextureOperations(renderState.rhi(), renderState.resourceUpdateBatch());
++ *texture = state->colorTable;
++ } else if (binding == 1) {
++ state->texture->commitTextureOperations(renderState.rhi(), renderState.resourceUpdateBatch());
++ *texture = state->texture;
++ }
++ }
++ };
++
++ class TabledMaterial : public ImageMaterial
++ {
++ public:
++ QSGMaterialShader *createShader(QSGRendererInterface::RenderMode renderMode) const override {
++ Q_UNUSED(renderMode);
++ return new TabledMaterialRhiShader(viewCount());
++ }
++ QSGMaterialType *type() const override { return &m_type; }
++
++ ImageMaterialData *state() override { return &m_state; }
++
++ private:
++ static QSGMaterialType m_type;
++ ImageMaterialData m_state;
++ };
++
++ QSGMaterialType TabledMaterial::m_type;
++
++ class DeformableMaterialRhiShader : public QSGMaterialShader
++ {
++ public:
++ DeformableMaterialRhiShader(int viewCount)
++ {
++ setShaderFileName(VertexStage, QStringLiteral(":/particles/shaders_ng/imageparticle_deformed.vert.qsb"), viewCount);
++ setShaderFileName(FragmentStage, QStringLiteral(":/particles/shaders_ng/imageparticle_deformed.frag.qsb"), viewCount);
++ }
++
++ bool updateUniformData(RenderState &renderState, QSGMaterial *newMaterial, QSGMaterial *) override
++ {
++ QByteArray *buf = renderState.uniformData();
++ Q_ASSERT(buf->size() >= 80 + 2 * (UNIFORM_ARRAY_SIZE * 4 * 4));
++ const int shaderMatrixCount = newMaterial->viewCount();
++ const int matrixCount = qMin(renderState.projectionMatrixCount(), shaderMatrixCount);
++
++ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
++ if (renderState.isMatrixDirty()) {
++ const QMatrix4x4 m = renderState.combinedMatrix(viewIndex);
++ memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
++ }
++ }
++
++ if (renderState.isOpacityDirty()) {
++ const float opacity = renderState.opacity();
++ memcpy(buf->data() + 64 * shaderMatrixCount, &opacity, 4);
++ }
++
++ ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state();
++
++ float entry = float(state->entry);
++ memcpy(buf->data() + 64 * shaderMatrixCount + 4, &entry, 4);
++
++ float timestamp = float(state->timestamp);
++ memcpy(buf->data() + 64 * shaderMatrixCount + 8, &timestamp, 4);
++
++ return true;
++ }
++
++ void updateSampledImage(RenderState &renderState, int binding, QSGTexture **texture,
++ QSGMaterial *newMaterial, QSGMaterial *) override
++ {
++ ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state();
++ if (binding == 1) {
++ state->texture->commitTextureOperations(renderState.rhi(), renderState.resourceUpdateBatch());
++ *texture = state->texture;
++ }
++ }
++ };
++
++ class DeformableMaterial : public ImageMaterial
++ {
++ public:
++ QSGMaterialShader *createShader(QSGRendererInterface::RenderMode renderMode) const override {
++ Q_UNUSED(renderMode);
++ return new DeformableMaterialRhiShader(viewCount());
++ }
++ QSGMaterialType *type() const override { return &m_type; }
++
++ ImageMaterialData *state() override { return &m_state; }
++
++ private:
++ static QSGMaterialType m_type;
++ ImageMaterialData m_state;
++ };
++
++ QSGMaterialType DeformableMaterial::m_type;
++
++ class ParticleSpriteMaterialRhiShader : public QSGMaterialShader
++ {
++ public:
++ ParticleSpriteMaterialRhiShader(int viewCount)
++ {
++ setShaderFileName(VertexStage, QStringLiteral(":/particles/shaders_ng/imageparticle_sprite.vert.qsb"), viewCount);
++ setShaderFileName(FragmentStage, QStringLiteral(":/particles/shaders_ng/imageparticle_sprite.frag.qsb"), viewCount);
++ }
++
++ bool updateUniformData(RenderState &renderState, QSGMaterial *newMaterial, QSGMaterial *) override
++ {
++ QByteArray *buf = renderState.uniformData();
++ Q_ASSERT(buf->size() >= 80 + 2 * (UNIFORM_ARRAY_SIZE * 4 * 4));
++ const int shaderMatrixCount = newMaterial->viewCount();
++ const int matrixCount = qMin(renderState.projectionMatrixCount(), shaderMatrixCount);
++
++ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
++ if (renderState.isMatrixDirty()) {
++ const QMatrix4x4 m = renderState.combinedMatrix(viewIndex);
++ memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
++ }
++ }
++
++ if (renderState.isOpacityDirty()) {
++ const float opacity = renderState.opacity();
++ memcpy(buf->data() + 64 * shaderMatrixCount, &opacity, 4);
++ }
++
++ ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state();
++
++ float entry = float(state->entry);
++ memcpy(buf->data() + 64 * shaderMatrixCount + 4, &entry, 4);
++
++ float timestamp = float(state->timestamp);
++ memcpy(buf->data() + 64 * shaderMatrixCount + 8, &timestamp, 4);
++
++ float *p = reinterpret_cast<float *>(buf->data() + 64 * shaderMatrixCount + 16);
++ for (int i = 0; i < UNIFORM_ARRAY_SIZE; ++i) {
++ *p = state->sizeTable[i];
++ p += 4;
++ }
++ p = reinterpret_cast<float *>(buf->data() + 64 * shaderMatrixCount + 16 + (UNIFORM_ARRAY_SIZE * 4 * 4));
++ for (int i = 0; i < UNIFORM_ARRAY_SIZE; ++i) {
++ *p = state->opacityTable[i];
++ p += 4;
++ }
++
++ return true;
++ }
++
++ void updateSampledImage(RenderState &renderState, int binding, QSGTexture **texture,
++ QSGMaterial *newMaterial, QSGMaterial *) override
++ {
++ ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state();
++ if (binding == 2) {
++ state->colorTable->commitTextureOperations(renderState.rhi(), renderState.resourceUpdateBatch());
++ *texture = state->colorTable;
++ } else if (binding == 1) {
++ state->texture->commitTextureOperations(renderState.rhi(), renderState.resourceUpdateBatch());
++ *texture = state->texture;
++ }
++ }
++ };
++
++ class SpriteMaterial : public ImageMaterial
++ {
++ public:
++ QSGMaterialShader *createShader(QSGRendererInterface::RenderMode renderMode) const override {
++ Q_UNUSED(renderMode);
++ return new ParticleSpriteMaterialRhiShader(viewCount());
++ }
++ QSGMaterialType *type() const override { return &m_type; }
++
++ ImageMaterialData *state() override { return &m_state; }
++
++ private:
++ static QSGMaterialType m_type;
++ ImageMaterialData m_state;
++ };
++
++ QSGMaterialType SpriteMaterial::m_type;
++
++ class ColoredPointMaterialRhiShader : public QSGMaterialShader
++ {
++ public:
++ ColoredPointMaterialRhiShader(int viewCount)
++ {
++ setShaderFileName(VertexStage, QStringLiteral(":/particles/shaders_ng/imageparticle_coloredpoint.vert.qsb"), viewCount);
++ setShaderFileName(FragmentStage, QStringLiteral(":/particles/shaders_ng/imageparticle_coloredpoint.frag.qsb"), viewCount);
++ }
++
++ bool updateUniformData(RenderState &renderState, QSGMaterial *newMaterial, QSGMaterial *) override
++ {
++ QByteArray *buf = renderState.uniformData();
++ Q_ASSERT(buf->size() >= 80 + 2 * (UNIFORM_ARRAY_SIZE * 4 * 4));
++ const int shaderMatrixCount = newMaterial->viewCount();
++ const int matrixCount = qMin(renderState.projectionMatrixCount(), shaderMatrixCount);
++
++ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
++ if (renderState.isMatrixDirty()) {
++ const QMatrix4x4 m = renderState.combinedMatrix(viewIndex);
++ memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
++ }
++ }
++
++ if (renderState.isOpacityDirty()) {
++ const float opacity = renderState.opacity();
++ memcpy(buf->data() + 64 * shaderMatrixCount, &opacity, 4);
++ }
++
++ ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state();
++
++ float entry = float(state->entry);
++ memcpy(buf->data() + 64 * shaderMatrixCount + 4, &entry, 4);
++
++ float timestamp = float(state->timestamp);
++ memcpy(buf->data() + 64 * shaderMatrixCount + 8, &timestamp, 4);
++
++ float dpr = float(state->dpr);
++ memcpy(buf->data() + 64 * shaderMatrixCount + 12, &dpr, 4);
++
++ return true;
++ }
++
++ void updateSampledImage(RenderState &renderState, int binding, QSGTexture **texture,
++ QSGMaterial *newMaterial, QSGMaterial *) override
++ {
++ ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state();
++ if (binding == 1) {
++ state->texture->commitTextureOperations(renderState.rhi(), renderState.resourceUpdateBatch());
++ *texture = state->texture;
++ }
++ }
++ };
++
++ class ColoredPointMaterial : public ImageMaterial
++ {
++ public:
++ QSGMaterialShader *createShader(QSGRendererInterface::RenderMode renderMode) const override {
++ Q_UNUSED(renderMode);
++ return new ColoredPointMaterialRhiShader(viewCount());
++ }
++ QSGMaterialType *type() const override { return &m_type; }
++
++ ImageMaterialData *state() override { return &m_state; }
++
++ private:
++ static QSGMaterialType m_type;
++ ImageMaterialData m_state;
++ };
++
++ QSGMaterialType ColoredPointMaterial::m_type;
++
++ class ColoredMaterialRhiShader : public ColoredPointMaterialRhiShader
++ {
++ public:
++ ColoredMaterialRhiShader(int viewCount)
++ : ColoredPointMaterialRhiShader(viewCount)
++ {
++ setShaderFileName(VertexStage, QStringLiteral(":/particles/shaders_ng/imageparticle_colored.vert.qsb"), viewCount);
++ setShaderFileName(FragmentStage, QStringLiteral(":/particles/shaders_ng/imageparticle_colored.frag.qsb"), viewCount);
++ }
++ };
++
++ class ColoredMaterial : public ImageMaterial
++ {
++ public:
++ QSGMaterialShader *createShader(QSGRendererInterface::RenderMode renderMode) const override {
++ Q_UNUSED(renderMode);
++ return new ColoredMaterialRhiShader(viewCount());
++ }
++ QSGMaterialType *type() const override { return &m_type; }
++
++ ImageMaterialData *state() override { return &m_state; }
++
++ private:
++ static QSGMaterialType m_type;
++ ImageMaterialData m_state;
++ };
++
++ QSGMaterialType ColoredMaterial::m_type;
++
++ class SimplePointMaterialRhiShader : public QSGMaterialShader
++ {
++ public:
++ SimplePointMaterialRhiShader(int viewCount)
++ {
++ setShaderFileName(VertexStage, QStringLiteral(":/particles/shaders_ng/imageparticle_simplepoint.vert.qsb"), viewCount);
++ setShaderFileName(FragmentStage, QStringLiteral(":/particles/shaders_ng/imageparticle_simplepoint.frag.qsb"), viewCount);
++ }
++
++ bool updateUniformData(RenderState &renderState, QSGMaterial *newMaterial, QSGMaterial *) override
++ {
++ QByteArray *buf = renderState.uniformData();
++ Q_ASSERT(buf->size() >= 80 + 2 * (UNIFORM_ARRAY_SIZE * 4 * 4));
++ const int shaderMatrixCount = newMaterial->viewCount();
++ const int matrixCount = qMin(renderState.projectionMatrixCount(), shaderMatrixCount);
++
++ for (int viewIndex = 0; viewIndex < matrixCount; ++viewIndex) {
++ if (renderState.isMatrixDirty()) {
++ const QMatrix4x4 m = renderState.combinedMatrix(viewIndex);
++ memcpy(buf->data() + 64 * viewIndex, m.constData(), 64);
++ }
++ }
++
++ if (renderState.isOpacityDirty()) {
++ const float opacity = renderState.opacity();
++ memcpy(buf->data() + 64 * shaderMatrixCount, &opacity, 4);
++ }
++
++ ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state();
++
++ float entry = float(state->entry);
++ memcpy(buf->data() + 64 * shaderMatrixCount + 4, &entry, 4);
++
++ float timestamp = float(state->timestamp);
++ memcpy(buf->data() + 64 * shaderMatrixCount + 8, &timestamp, 4);
++
++ float dpr = float(state->dpr);
++ memcpy(buf->data() + 64 * shaderMatrixCount + 12, &dpr, 4);
++
++ return true;
++ }
++
++ void updateSampledImage(RenderState &renderState, int binding, QSGTexture **texture,
++ QSGMaterial *newMaterial, QSGMaterial *) override
++ {
++ ImageMaterialData *state = static_cast<ImageMaterial *>(newMaterial)->state();
++ if (binding == 1) {
++ state->texture->commitTextureOperations(renderState.rhi(), renderState.resourceUpdateBatch());
++ *texture = state->texture;
++ }
++ }
++ };
++
++ class SimplePointMaterial : public ImageMaterial
++ {
++ public:
++ QSGMaterialShader *createShader(QSGRendererInterface::RenderMode renderMode) const override {
++ Q_UNUSED(renderMode);
++ return new SimplePointMaterialRhiShader(viewCount());
++ }
++ QSGMaterialType *type() const override { return &m_type; }
++
++ ImageMaterialData *state() override { return &m_state; }
++
++ private:
++ static QSGMaterialType m_type;
++ ImageMaterialData m_state;
++ };
++
++ QSGMaterialType SimplePointMaterial::m_type;
++
++ 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;
++ }
++
++ /*!
++ \qmltype ImageParticle
++ \nativetype QQuickImageParticle
++ \inqmlmodule QtQuick.Particles
++ \inherits ParticlePainter
++ \brief For visualizing logical particles using an image.
++ \ingroup qtquick-particles
++
++ 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 explicitly 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.
++
++ \note The maximum number of image particles is limited to 16383.
++ */
++ /*!
++ \qmlproperty url QtQuick.Particles::ImageParticle::source
++
++ The source image to be used.
++
++ If the image is a sprite animation, use the sprite property instead.
++
++ Since Qt 5.2, some default images are provided as resources to aid prototyping:
++ \table
++ \row
++ \li \inlineimage particles/star.png {White star particle}
++ \row
++ \li \inlineimage particles/glowdot.png {White glowing dot particle}
++ \row
++ \li \inlineimage particles/fuzzydot.png {White fuzzy dot particle}
++ \endtable
++
++ Note that the images are white and semi-transparent, to allow colorization
++ and alpha levels to have maximum effect.
++ */
++ /*!
++ \qmlproperty list<Sprite> QtQuick.Particles::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 Animations} overview.
++ */
++ /*!
++ \qmlproperty url QtQuick.Particles::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.Particles::ImageParticle::sizeTable
++
++ An image whose alpha channel is used as a 1D lookup table to scale the particle's size over
++ its lifetime.
++
++ The image is read from left to right: the left edge corresponds to the start of the
++ particle's life, and the right edge to its end. Only the alpha channel is used. A fully
++ opaque pixel leaves the size unchanged, and a fully transparent pixel scales the particle
++ down to nothing. The image is scaled to 64 samples wide and one pixel high, so a narrow
++ image with a horizontal alpha gradient is sufficient.
++
++ The value from the table multiplies the size that the particle has at that point in its
++ life, as determined by the \l Emitter's \l {Emitter::}{size} and \l {Emitter::}{endSize}.
++
++ \sa opacityTable, colorTable, entryEffect
++ */
++ /*!
++ \qmlproperty url QtQuick.Particles::ImageParticle::opacityTable
++
++ An image whose alpha channel is used as a 1D lookup table to scale the particle's opacity
++ over its lifetime.
++
++ The image is read from left to right: the left edge corresponds to the start of the
++ particle's life, and the right edge to its end. Only the alpha channel is used. A fully
++ opaque pixel leaves the opacity unchanged, and a fully transparent pixel makes the particle
++ invisible. The image is scaled to 64 samples wide and one pixel high, so a narrow image
++ with a horizontal alpha gradient is sufficient.
++
++ The value from the table is combined with the particle's \l alpha and with any fading
++ applied by \l entryEffect.
++
++ \sa sizeTable, colorTable, entryEffect
++ */
++ /*!
++ \qmlproperty color QtQuick.Particles::ImageParticle::color
++
++ If a color is specified, the provided image will be colorized with it.
++
++ Default is white (no change).
++ */
++ /*!
++ \qmlproperty real QtQuick.Particles::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.Particles::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.Particles::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.Particles::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.Particles::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.Particles::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.Particles::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.Particles::ImageParticle::rotationVariation
++
++ If set the rotation of individual particles will vary by up to this much
++ between particles.
++
++ */
++ /*!
++ \qmlproperty real QtQuick.Particles::ImageParticle::rotationVelocity
++
++ If set particles will rotate at this velocity in degrees/second.
++ */
++ /*!
++ \qmlproperty real QtQuick.Particles::ImageParticle::rotationVelocityVariation
++
++ If set the rotationVelocity of individual particles will vary by up to this much
++ between particles.
++
++ */
++ /*!
++ \qmlproperty bool QtQuick.Particles::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.Particles::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.Particles::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.Particles::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
++
++ \value ImageParticle.None Particles just appear and disappear.
++ \value ImageParticle.Fade Particles fade in from 0 opacity at the start of their life, and fade out to 0 at the end.
++ \value ImageParticle.Scale Particles scale in from 0 size at the start of their life, and scale back to 0 at the end.
++
++ The default value is \c ImageParticle.Fade.
++ */
++ /*!
++ \qmlproperty bool QtQuick.Particles::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.Particles::ImageParticle::status
++
++ The status of loading the image.
++ */
++
++
++ QQuickImageParticle::QQuickImageParticle(QQuickItem* parent)
++ : QQuickParticlePainter(parent)
++ , m_color_variation(0.0)
++ , m_outgoingNode(nullptr)
++ , m_material(nullptr)
++ , 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(nullptr)
++ , m_yVector(nullptr)
++ , m_spriteEngine(nullptr)
++ , m_spritesInterpolate(true)
++ , m_explicitColor(false)
++ , m_explicitRotation(false)
++ , m_explicitDeformation(false)
++ , m_explicitAnimation(false)
++ , m_bypassOptimizations(false)
++ , perfLevel(Unknown)
++ , m_targetPerfLevel(Unknown)
++ , m_debugMode(false)
++ , m_entryEffect(Fade)
++ , m_startedImageLoading(0)
++ , m_rhi(nullptr)
++ , m_apiChecked(false)
++ , m_dpr(1.0)
++ , m_previousActive(false)
++ {
++ setFlag(ItemHasContents);
++ }
++
++ QQuickImageParticle::~QQuickImageParticle()
++ {
++ clearShadows();
++ }
++
++ QQmlListProperty<QQuickSprite> QQuickImageParticle::sprites()
++ {
++ return QQmlListProperty<QQuickSprite>(this, &m_sprites,
++ spriteAppend, spriteCount, spriteAt,
++ spriteClear, spriteReplace, spriteRemoveLast);
++ }
++
++ void QQuickImageParticle::sceneGraphInvalidated()
++ {
++ m_nodes.clear();
++ m_material = nullptr;
++ delete m_outgoingNode;
++ m_outgoingNode = nullptr;
++ m_apiChecked = false;
++ }
++
++ void QQuickImageParticle::setImage(const QUrl &image)
++ {
++ if (image.isEmpty()){
++ if (m_image) {
++ m_image.reset();
++ emit imageChanged();
++ }
++ return;
++ }
++
++ if (!m_image)
++ m_image.reset(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) {
++ m_colorTable.reset();
++ emit colortableChanged();
++ }
++ return;
++ }
++
++ if (!m_colorTable)
++ m_colorTable.reset(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) {
++ m_sizeTable.reset();
++ emit sizetableChanged();
++ }
++ return;
++ }
++
++ if (!m_sizeTable)
++ m_sizeTable.reset(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) {
++ m_opacityTable.reset();
++ emit opacitytableChanged();
++ }
++ return;
++ }
++
++ if (!m_opacityTable)
++ m_opacityTable.reset(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;
++ checkPerfLevel(ColoredPoint);
++ }
++
++ void QQuickImageParticle::setColorVariation(qreal var)
++ {
++ if (var == m_color_variation)
++ return;
++ m_color_variation = var;
++ emit colorVariationChanged();
++ m_explicitColor = true;
++ checkPerfLevel(ColoredPoint);
++ }
++
++ void QQuickImageParticle::setAlphaVariation(qreal arg)
++ {
++ if (m_alphaVariation != arg) {
++ m_alphaVariation = arg;
++ emit alphaVariationChanged(arg);
++ }
++ m_explicitColor = true;
++ checkPerfLevel(ColoredPoint);
++ }
++
++ void QQuickImageParticle::setAlpha(qreal arg)
++ {
++ if (m_alpha != arg) {
++ m_alpha = arg;
++ emit alphaChanged(arg);
++ }
++ m_explicitColor = true;
++ checkPerfLevel(ColoredPoint);
++ }
++
++ void QQuickImageParticle::setRedVariation(qreal arg)
++ {
++ if (m_redVariation != arg) {
++ m_redVariation = arg;
++ emit redVariationChanged(arg);
++ }
++ m_explicitColor = true;
++ checkPerfLevel(ColoredPoint);
++ }
++
++ void QQuickImageParticle::setGreenVariation(qreal arg)
++ {
++ if (m_greenVariation != arg) {
++ m_greenVariation = arg;
++ emit greenVariationChanged(arg);
++ }
++ m_explicitColor = true;
++ checkPerfLevel(ColoredPoint);
++ }
++
++ void QQuickImageParticle::setBlueVariation(qreal arg)
++ {
++ if (m_blueVariation != arg) {
++ m_blueVariation = arg;
++ emit blueVariationChanged(arg);
++ }
++ m_explicitColor = true;
++ checkPerfLevel(ColoredPoint);
++ }
++
++ void QQuickImageParticle::setRotation(qreal arg)
++ {
++ if (m_rotation != arg) {
++ m_rotation = arg;
++ emit rotationChanged(arg);
++ }
++ m_explicitRotation = true;
++ checkPerfLevel(Deformable);
++ }
++
++ void QQuickImageParticle::setRotationVariation(qreal arg)
++ {
++ if (m_rotationVariation != arg) {
++ m_rotationVariation = arg;
++ emit rotationVariationChanged(arg);
++ }
++ m_explicitRotation = true;
++ checkPerfLevel(Deformable);
++ }
++
++ void QQuickImageParticle::setRotationVelocity(qreal arg)
++ {
++ if (m_rotationVelocity != arg) {
++ m_rotationVelocity = arg;
++ emit rotationVelocityChanged(arg);
++ }
++ m_explicitRotation = true;
++ checkPerfLevel(Deformable);
++ }
++
++ void QQuickImageParticle::setRotationVelocityVariation(qreal arg)
++ {
++ if (m_rotationVelocityVariation != arg) {
++ m_rotationVelocityVariation = arg;
++ emit rotationVelocityVariationChanged(arg);
++ }
++ m_explicitRotation = true;
++ checkPerfLevel(Deformable);
++ }
++
++ void QQuickImageParticle::setAutoRotation(bool arg)
++ {
++ if (m_autoRotation != arg) {
++ m_autoRotation = arg;
++ emit autoRotationChanged(arg);
++ }
++ m_explicitRotation = true;
++ checkPerfLevel(Deformable);
++ }
++
++ void QQuickImageParticle::setXVector(QQuickDirection* arg)
++ {
++ if (m_xVector != arg) {
++ m_xVector = arg;
++ emit xVectorChanged(arg);
++ }
++ m_explicitDeformation = true;
++ checkPerfLevel(Deformable);
++ }
++
++ void QQuickImageParticle::setYVector(QQuickDirection* arg)
++ {
++ if (m_yVector != arg) {
++ m_yVector = arg;
++ emit yVectorChanged(arg);
++ }
++ m_explicitDeformation = true;
++ checkPerfLevel(Deformable);
++ }
++
++ 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);
++ }
++ // Applies regardless of perfLevel
++ 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;
++ for (auto groupId : groupIds()) {
++ for (QQuickParticleData* d : std::as_const(m_system->groupData[groupId]->data)) {
++ if (d->colorOwner == this) {
++ d->colorOwner = nullptr;
++ }
++ }
++ }
++ 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;
++ for (auto groupId : groupIds()) {
++ for (QQuickParticleData* d : std::as_const(m_system->groupData[groupId]->data)) {
++ if (d->rotationOwner == this) {
++ d->rotationOwner = nullptr;
++ }
++ }
++ }
++ m_rotation = 0;
++ m_rotationVariation = 0;
++ m_rotationVelocity = 0;
++ m_rotationVelocityVariation = 0;
++ m_autoRotation = false;
++ }
++
++ void QQuickImageParticle::resetDeformation()
++ {
++ m_explicitDeformation = false;
++ for (auto groupId : groupIds()) {
++ for (QQuickParticleData* d : std::as_const(m_system->groupData[groupId]->data)) {
++ if (d->deformationOwner == this) {
++ d->deformationOwner = nullptr;
++ }
++ }
++ }
++ if (m_xVector)
++ delete m_xVector;
++ if (m_yVector)
++ delete m_yVector;
++ m_xVector = nullptr;
++ m_yVector = nullptr;
++ }
++
++ void QQuickImageParticle::reset()
++ {
++ QQuickParticlePainter::reset();
++ m_pleaseReset = true;
++ update();
++ }
++
++
++ void QQuickImageParticle::invalidateSceneGraph()
++ {
++ reset();
++ }
++
++ void QQuickImageParticle::createEngine()
++ {
++ if (m_spriteEngine)
++ delete m_spriteEngine;
++ if (m_sprites.size()) {
++ m_spriteEngine = new QQuickSpriteEngine(m_sprites, this);
++ connect(m_spriteEngine, &QQuickStochasticEngine::stateChanged,
++ this, &QQuickImageParticle::spriteAdvance, Qt::DirectConnection);
++ m_explicitAnimation = true;
++ } else {
++ m_spriteEngine = nullptr;
++ m_explicitAnimation = false;
++ }
++ reset();
++ }
++
++ static QSGGeometry::Attribute SimplePointParticle_Attributes[] = {
++ QSGGeometry::Attribute::create(0, 2, QSGGeometry::FloatType, true), // Position
++ QSGGeometry::Attribute::create(1, 4, QSGGeometry::FloatType), // Data
++ QSGGeometry::Attribute::create(2, 4, QSGGeometry::FloatType) // Vectors
++ };
++
++ static QSGGeometry::AttributeSet SimplePointParticle_AttributeSet =
++ {
++ 3, // Attribute Count
++ ( 2 + 4 + 4 ) * sizeof(float),
++ SimplePointParticle_Attributes
++ };
++
++ static QSGGeometry::Attribute ColoredPointParticle_Attributes[] = {
++ QSGGeometry::Attribute::create(0, 2, QSGGeometry::FloatType, true), // Position
++ QSGGeometry::Attribute::create(1, 4, QSGGeometry::FloatType), // Data
++ QSGGeometry::Attribute::create(2, 4, QSGGeometry::FloatType), // Vectors
++ QSGGeometry::Attribute::create(3, 4, QSGGeometry::UnsignedByteType), // Colors
++ };
++
++ static QSGGeometry::AttributeSet ColoredPointParticle_AttributeSet =
++ {
++ 4, // Attribute Count
++ ( 2 + 4 + 4 ) * sizeof(float) + 4 * sizeof(uchar),
++ ColoredPointParticle_Attributes
++ };
++
++ static QSGGeometry::Attribute ColoredParticle_Attributes[] = {
++ QSGGeometry::Attribute::create(0, 2, QSGGeometry::FloatType, true), // Position
++ QSGGeometry::Attribute::create(1, 4, QSGGeometry::FloatType), // Data
++ QSGGeometry::Attribute::create(2, 4, QSGGeometry::FloatType), // Vectors
++ QSGGeometry::Attribute::create(3, 4, QSGGeometry::UnsignedByteType), // Colors
++ QSGGeometry::Attribute::create(4, 4, QSGGeometry::UnsignedByteType), // TexCoord
++ };
++
++ static QSGGeometry::AttributeSet ColoredParticle_AttributeSet =
++ {
++ 5, // Attribute Count
++ ( 2 + 4 + 4 ) * sizeof(float) + (4 + 4) * sizeof(uchar),
++ ColoredParticle_Attributes
++ };
++
++ static QSGGeometry::Attribute DeformableParticle_Attributes[] = {
++ QSGGeometry::Attribute::create(0, 4, QSGGeometry::FloatType), // Position & Rotation
++ QSGGeometry::Attribute::create(1, 4, QSGGeometry::FloatType), // Data
++ QSGGeometry::Attribute::create(2, 4, QSGGeometry::FloatType), // Vectors
++ QSGGeometry::Attribute::create(3, 4, QSGGeometry::UnsignedByteType), // Colors
++ QSGGeometry::Attribute::create(4, 4, QSGGeometry::FloatType), // DeformationVectors
++ QSGGeometry::Attribute::create(5, 4, QSGGeometry::UnsignedByteType), // TexCoord & autoRotate
++ };
++
++ static QSGGeometry::AttributeSet DeformableParticle_AttributeSet =
++ {
++ 6, // Attribute Count
++ (4 + 4 + 4 + 4) * sizeof(float) + (4 + 4) * sizeof(uchar),
++ DeformableParticle_Attributes
++ };
++
++ static QSGGeometry::Attribute SpriteParticle_Attributes[] = {
++ QSGGeometry::Attribute::create(0, 4, QSGGeometry::FloatType), // Position & Rotation
++ QSGGeometry::Attribute::create(1, 4, QSGGeometry::FloatType), // Data
++ QSGGeometry::Attribute::create(2, 4, QSGGeometry::FloatType), // Vectors
++ QSGGeometry::Attribute::create(3, 4, QSGGeometry::UnsignedByteType), // Colors
++ QSGGeometry::Attribute::create(4, 4, QSGGeometry::FloatType), // DeformationVectors
++ QSGGeometry::Attribute::create(5, 4, QSGGeometry::UnsignedByteType), // TexCoord & autoRotate
++ QSGGeometry::Attribute::create(6, 3, QSGGeometry::FloatType), // Anim Data
++ QSGGeometry::Attribute::create(7, 3, QSGGeometry::FloatType) // Anim Pos
++ };
++
++ static QSGGeometry::AttributeSet SpriteParticle_AttributeSet =
++ {
++ 8, // Attribute Count
++ (4 + 4 + 4 + 4 + 3 + 3) * sizeof(float) + (4 + 4) * sizeof(uchar),
++ SpriteParticle_Attributes
++ };
++
++ void QQuickImageParticle::clearShadows()
++ {
++ foreach (const QList<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;
++ if (!m_shadowData.contains(datum->groupId)) {
++ QQuickParticleGroupData* gd = m_system->groupData[datum->groupId];
++ QList<QQuickParticleData*> data;
++ const int gdSize = gd->size();
++ data.reserve(gdSize);
++ for (int i = 0; i < gdSize; i++) {
++ QQuickParticleData* datum = new QQuickParticleData;
++ *datum = *(gd->data[i]);
++ data << datum;
++ }
++ m_shadowData.insert(datum->groupId, data);
++ }
++ //### If dynamic resize is added, remember to potentially resize the shadow data on out-of-bounds access request
++
++ return m_shadowData[datum->groupId][datum->index];
++ }
++
++ void QQuickImageParticle::checkPerfLevel(PerformanceLevel level)
++ {
++ if (m_targetPerfLevel < level) {
++ m_targetPerfLevel = level;
++ reset();
++ }
++ }
++
++ 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::mainThreadFetchImageData()
++ {
++ const QQmlContext *context = nullptr;
++ QQmlEngine *engine = nullptr;
++ const auto loadPix = [&](ImageData *image) {
++ if (!engine) {
++ context = qmlContext(this);
++ engine = context->engine();
++ }
++ image->pix.load(engine, context->resolvedUrl(image->source));
++ };
++
++
++ if (m_image) {//ImageData created on setSource
++ m_image->pix.clear(this);
++ loadPix(m_image.get());
++ }
++
++ if (m_spriteEngine)
++ m_spriteEngine->startAssemblingImage();
++
++ if (m_colorTable)
++ loadPix(m_colorTable.get());
++
++ if (m_sizeTable)
++ loadPix(m_sizeTable.get());
++
++ if (m_opacityTable)
++ loadPix(m_opacityTable.get());
++
++ m_startedImageLoading = 2;
++ }
++
++ void QQuickImageParticle::buildParticleNodes(QSGNode** passThrough)
++ {
++ // Starts async parts, like loading images, on gui thread
++ // Not on individual properties, because we delay until system is running
++ if (*passThrough || loadingSomething())
++ return;
++
++ if (m_startedImageLoading == 0) {
++ m_startedImageLoading = 1;
++ //stage 1 is in gui thread
++ QQuickImageParticle::staticMetaObject.invokeMethod(this, "mainThreadFetchImageData", Qt::QueuedConnection);
++ } else if (m_startedImageLoading == 2) {
++ finishBuildParticleNodes(passThrough); //rest happens in render thread
++ }
++
++ //No mutex, because it's slow and a compare that fails due to a race condition means just a dropped frame
++ }
++
++ void QQuickImageParticle::finishBuildParticleNodes(QSGNode** node)
++ {
++ if (!m_rhi)
++ return;
++
++ if (m_count * 4 > 0xffff) {
++ // Index data is ushort.
++ qmlInfo(this) << "ImageParticle: Too many particles - maximum 16383 per ImageParticle";
++ return;
++ }
++
++ if (m_count <= 0)
++ return;
++
++ m_debugMode = m_system->m_debugMode;
++
++ if (m_sprites.size() || 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 = ColoredPoint;
++ } else {
++ perfLevel = SimplePoint;
++ }
++
++ for (auto groupId : groupIds()) {
++ //For sharing higher levels, need to have highest used so it renders
++ for (QQuickParticlePainter* p : std::as_const(m_system->groupData[groupId]->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();
++ }
++ }
++ }
++ }
++
++ // Points with a size other than 1 are an optional feature with QRhi
++ // because some of the underlying APIs have no support for this.
++ // Therefore, avoid the point sprite path with APIs like Direct3D.
++ if (perfLevel < Colored && !m_rhi->isFeatureSupported(QRhi::VertexShaderPointSize))
++ perfLevel = Colored;
++
++ if (perfLevel >= ColoredPoint && !m_color.isValid())
++ m_color = QColor(Qt::white);//Hidden default, but different from unset
++
++ m_targetPerfLevel = perfLevel;
++
++ clearShadows();
++ if (m_material)
++ m_material = nullptr;
++
++ //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 = new SpriteMaterial;
++ ImageMaterialData *state = getState(m_material);
++ if (imageLoaded)
++ state->texture = QSGPlainTexture::fromImage(image);
++ state->animSheetSize = QSizeF(image.size() / image.devicePixelRatio());
++ if (m_spriteEngine)
++ m_spriteEngine->setCount(m_count);
++ }
++ Q_FALLTHROUGH();
++ case Tabled:
++ {
++ if (!m_material)
++ m_material = new TabledMaterial;
++
++ if (m_colorTable) {
++ if (m_colorTable->pix.isReady())
++ colortable = m_colorTable->pix.image();
++ else
++ qmlWarning(this) << "Error loading color table: " << m_colorTable->pix.error();
++ }
++
++ if (m_sizeTable) {
++ if (m_sizeTable->pix.isReady())
++ sizetable = m_sizeTable->pix.image();
++ else
++ qmlWarning(this) << "Error loading size table: " << m_sizeTable->pix.error();
++ }
++
++ if (m_opacityTable) {
++ if (m_opacityTable->pix.isReady())
++ opacitytable = m_opacityTable->pix.image();
++ else
++ qmlWarning(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);
++ }
++ ImageMaterialData *state = getState(m_material);
++ state->colorTable = QSGPlainTexture::fromImage(colortable);
++ fillUniformArrayFromImage(state->sizeTable, sizetable, UNIFORM_ARRAY_SIZE);
++ fillUniformArrayFromImage(state->opacityTable, opacitytable, UNIFORM_ARRAY_SIZE);
++ }
++ Q_FALLTHROUGH();
++ case Deformable:
++ {
++ if (!m_material)
++ m_material = new DeformableMaterial;
++ }
++ Q_FALLTHROUGH();
++ case Colored:
++ {
++ if (!m_material)
++ m_material = new ColoredMaterial;
++ }
++ Q_FALLTHROUGH();
++ case ColoredPoint:
++ {
++ if (!m_material)
++ m_material = new ColoredPointMaterial;
++ }
++ Q_FALLTHROUGH();
++ default://Also Simple
++ {
++ if (!m_material)
++ m_material = new SimplePointMaterial;
++ ImageMaterialData *state = getState(m_material);
++ if (!imageLoaded) {
++ if (!m_image || !m_image->pix.isReady()) {
++ if (m_image)
++ qmlWarning(this) << m_image->pix.error();
++ delete m_material;
++ return;
++ }
++ //state->texture //TODO: Shouldn't this be better? But not crash?
++ // = QQuickItemPrivate::get(this)->sceneGraphContext()->textureForFactory(m_imagePix.textureFactory());
++ state->texture = QSGPlainTexture::fromImage(m_image->pix.image());
++ }
++ state->texture->setFiltering(QSGTexture::Linear);
++ state->entry = (qreal) m_entryEffect;
++ state->dpr = m_dpr;
++
++ m_material->setFlag(QSGMaterial::Blending | QSGMaterial::RequiresFullMatrix);
++ }
++ }
++
++ m_nodes.clear();
++ for (auto groupId : groupIds()) {
++ int count = m_system->groupData[groupId]->size();
++ QSGGeometryNode* node = new QSGGeometryNode();
++ node->setMaterial(m_material);
++ node->markDirty(QSGNode::DirtyMaterial);
++
++ m_nodes.insert(groupId, node);
++ m_idxStarts.insert(groupId, m_lastIdxStart);
++ m_startsIdx.append(std::make_pair(m_lastIdxStart, groupId));
++ 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, vCount, iCount);
++ else if (perfLevel == ColoredPoint)
++ g = new QSGGeometry(ColoredPointParticle_AttributeSet, count, 0);
++ else //Simple
++ g = new QSGGeometry(SimplePointParticle_AttributeSet, count, 0);
++
++ node->setFlag(QSGNode::OwnsGeometry);
++ node->setGeometry(g);
++ if (perfLevel <= ColoredPoint){
++ g->setDrawingMode(QSGGeometry::DrawPoints);
++ if (m_debugMode)
++ qDebug("Using point sprites");
++ } else {
++ g->setDrawingMode(QSGGeometry::DrawTriangles);
++ }
++
++ for (int p=0; p < count; ++p)
++ commit(groupId, 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);
++ else if (perfLevel == Colored)
++ initTexCoords<ColoredVertex>((ColoredVertex*)g->vertexData(), vCount);
++
++ if (perfLevel > ColoredPoint){
++ 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);
++ }
++
++ *node = *(m_nodes.begin());
++ update();
++ }
++
++ QSGNode *QQuickImageParticle::updatePaintNode(QSGNode *node, UpdatePaintNodeData *)
++ {
++ if (!m_apiChecked || m_windowChanged) {
++ m_apiChecked = true;
++ m_windowChanged = false;
++
++ QSGRenderContext *rc = QQuickItemPrivate::get(this)->sceneGraphRenderContext();
++ QSGRendererInterface *rif = rc->sceneGraphContext()->rendererInterface(rc);
++ if (!rif)
++ return nullptr;
++
++ QSGRendererInterface::GraphicsApi api = rif->graphicsApi();
++ const bool isRhi = QSGRendererInterface::isApiRhiBased(api);
++
++ if (!node && !isRhi)
++ return nullptr;
++
++ if (isRhi)
++ m_rhi = static_cast<QRhi *>(rif->getResource(m_window, QSGRendererInterface::RhiResource));
++ else
++ m_rhi = nullptr;
++
++ if (isRhi && !m_rhi) {
++ qWarning("Failed to query QRhi, particles disabled");
++ return nullptr;
++ }
++ // Get the pixel ratio of the window, used for pointsize scaling
++ m_dpr = m_window ? m_window->devicePixelRatio() : 1.0;
++ }
++
++ if (m_pleaseReset){
++ // Cannot just destroy the node and then return null (in case image
++ // loading is still in progress). Rather, keep track of the old node
++ // until we have a new one.
++ delete m_outgoingNode;
++ m_outgoingNode = node;
++ node = nullptr;
++
++ m_nodes.clear();
++
++ m_idxStarts.clear();
++ m_startsIdx.clear();
++ m_lastIdxStart = 0;
++
++ m_material = nullptr;
++
++ m_pleaseReset = false;
++ m_startedImageLoading = 0;//Cancel a part-way build (may still have a pending load)
++ } else if (!m_material) {
++ delete node;
++ node = nullptr;
++ }
++
++ if (m_system && m_system->isRunning() && !m_system->isPaused()){
++ bool dirty = prepareNextFrame(&node);
++ if (node) {
++ update();
++ if (dirty) {
++ foreach (QSGGeometryNode* n, m_nodes)
++ n->markDirty(QSGNode::DirtyGeometry);
++ }
++ } else if (m_startedImageLoading < 2) {
++ update();//To call prepareNextFrame() again from the renderThread
++ }
++ }
++
++ if (!node) {
++ node = m_outgoingNode;
++ m_outgoingNode = nullptr;
++ }
++
++ return node;
++ }
++
++ bool QQuickImageParticle::prepareNextFrame(QSGNode **node)
++ {
++ if (*node == nullptr){//TODO: Staggered loading (as emitted)
++ buildParticleNodes(node);
++ if (m_debugMode) {
++ qDebug() << "QQuickImageParticle Feature level: " << perfLevel;
++ qDebug() << "QQuickImageParticle Nodes: ";
++ int count = 0;
++ for (auto it = m_nodes.keyBegin(), end = m_nodes.keyEnd(); it != end; ++it) {
++ qDebug() << "Group " << *it << " (" << m_system->groupData[*it]->size()
++ << " particles)";
++ count += m_system->groupData[*it]->size();
++ }
++ qDebug() << "Total count: " << count;
++ }
++ if (*node == nullptr)
++ return false;
++ }
++ 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);
++ Q_FALLTHROUGH();
++ case Tabled:
++ case Deformable:
++ case Colored:
++ case ColoredPoint:
++ case SimplePoint:
++ default: //Also Simple
++ getState(m_material)->timestamp = time;
++ break;
++ }
++
++ bool active = false;
++ for (auto groupId : groupIds()) {
++ if (m_system->groupData[groupId]->isActive()) {
++ active = true;
++ break;
++ }
++ }
++
++ const bool dirty = active || m_previousActive;
++ if (dirty) {
++ foreach (QSGGeometryNode* node, m_nodes)
++ node->markDirty(QSGNode::DirtyMaterial);
++ }
++
++ m_previousActive = active;
++ return dirty;
++ }
++
++ void QQuickImageParticle::spritesUpdate(qreal time)
++ {
++ ImageMaterialData *state = getState(m_material);
++ // Sprite progression handled CPU side, so as to have per-frame control.
++ for (auto groupId : groupIds()) {
++ for (QQuickParticleData* mainDatum : std::as_const(m_system->groupData[groupId]->data)) {
++ QSGGeometryNode *node = m_nodes[groupId];
++ 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.size(); i++) {
++ if (m_startsIdx[i].second == groupId){
++ 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 = std::modf(frame,&frameAt);
++ else
++ std::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 = state->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;
++
++ 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].animW = w;
++ spriteVertices[i].animH = h;
++ spriteVertices[i].animProgress = progress;
++ }
++ }
++ }
++ }
++
++ void QQuickImageParticle::spriteAdvance(int spriteIdx)
++ {
++ if (!m_startsIdx.size())//Probably overly defensive
++ return;
++
++ int gIdx = -1;
++ int i;
++ for (i = 0; i<m_startsIdx.size(); 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::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;
++ uchar 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 {
++ ImageMaterialData *state = getState(m_material);
++ 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 = state->animSheetSize.width();
++ writeTo->animHeight = state->animSheetSize.height();
++ }
++ Q_FALLTHROUGH();
++ 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 {
++ QQuickParticleData* shadow = getShadowDatum(datum);
++ shadow->xx = ret.x();
++ shadow->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 {
++ QQuickParticleData* shadow = getShadowDatum(datum);
++ shadow->yx = ret.x();
++ shadow->yy = ret.y();
++ }
++ }
++ }
++
++ if (m_explicitRotation){
++ if (!datum->rotationOwner)
++ datum->rotationOwner = this;
++ rotation = qDegreesToRadians(
++ m_rotation + (m_rotationVariation
++ - 2 * QRandomGenerator::global()->bounded(m_rotationVariation)));
++ rotationVelocity = qDegreesToRadians(
++ m_rotationVelocity
++ + (m_rotationVelocityVariation
++ - 2 * QRandomGenerator::global()->bounded(m_rotationVelocityVariation)));
++ autoRotate = m_autoRotation ? 1 : 0;
++ if (datum->rotationOwner == this) {
++ datum->rotation = rotation;
++ datum->rotationVelocity = rotationVelocity;
++ datum->autoRotate = autoRotate;
++ } else {
++ QQuickParticleData* shadow = getShadowDatum(datum);
++ shadow->rotation = rotation;
++ shadow->rotationVelocity = rotationVelocity;
++ shadow->autoRotate = autoRotate;
++ }
++ }
++ Q_FALLTHROUGH();
++ case Colored:
++ Q_FALLTHROUGH();
++ case ColoredPoint:
++ //Color initialization
++ // Particle color
++ if (m_explicitColor) {
++ if (!datum->colorOwner)
++ datum->colorOwner = this;
++ const auto rgbColor = m_color.toRgb();
++ color.r = rgbColor.red() * (1 - redVariation) + QRandomGenerator::global()->bounded(256) * redVariation;
++ color.g = rgbColor.green() * (1 - greenVariation) + QRandomGenerator::global()->bounded(256) * greenVariation;
++ color.b = rgbColor.blue() * (1 - blueVariation) + QRandomGenerator::global()->bounded(256) * blueVariation;
++ color.a = m_alpha * rgbColor.alpha() * (1 - m_alphaVariation) + QRandomGenerator::global()->bounded(256) * m_alphaVariation;
++ if (datum->colorOwner == this)
++ datum->color = color;
++ else
++ getShadowDatum(datum)->color = color;
++ }
++ break;
++ 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];
++ SpriteVertex *spriteVertices = (SpriteVertex *) node->geometry()->vertexData();
++ DeformableVertex *deformableVertices = (DeformableVertex *) node->geometry()->vertexData();
++ ColoredVertex *coloredVertices = (ColoredVertex *) node->geometry()->vertexData();
++ ColoredPointVertex *coloredPointVertices = (ColoredPointVertex *) node->geometry()->vertexData();
++ SimplePointVertex *simplePointVertices = (SimplePointVertex *) 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 = shadow->color;
++ } else {
++ spriteVertices[i].color = datum->color;
++ }
++ }
++ 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 = shadow->color;
++ } else {
++ deformableVertices[i].color = datum->color;
++ }
++ }
++ break;
++ case Colored:
++ coloredVertices += pIdx*4;
++ for (int i=0; i<4; 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 = shadow->color;
++ } else {
++ coloredVertices[i].color = datum->color;
++ }
++ }
++ break;
++ case ColoredPoint:
++ coloredPointVertices += pIdx*1;
++ for (int i=0; i<1; i++){
++ coloredPointVertices[i].x = datum->x - m_systemOffset.x();
++ coloredPointVertices[i].y = datum->y - m_systemOffset.y();
++ coloredPointVertices[i].t = datum->t;
++ coloredPointVertices[i].lifeSpan = datum->lifeSpan;
++ coloredPointVertices[i].size = datum->size;
++ coloredPointVertices[i].endSize = datum->endSize;
++ coloredPointVertices[i].vx = datum->vx;
++ coloredPointVertices[i].vy = datum->vy;
++ coloredPointVertices[i].ax = datum->ax;
++ coloredPointVertices[i].ay = datum->ay;
++ if (m_explicitColor && datum->colorOwner != this) {
++ QQuickParticleData* shadow = getShadowDatum(datum);
++ coloredPointVertices[i].color = shadow->color;
++ } else {
++ coloredPointVertices[i].color = datum->color;
++ }
++ }
++ break;
++ case SimplePoint:
++ simplePointVertices += pIdx*1;
++ for (int i=0; i<1; i++){
++ simplePointVertices[i].x = datum->x - m_systemOffset.x();
++ simplePointVertices[i].y = datum->y - m_systemOffset.y();
++ simplePointVertices[i].t = datum->t;
++ simplePointVertices[i].lifeSpan = datum->lifeSpan;
++ simplePointVertices[i].size = datum->size;
++ simplePointVertices[i].endSize = datum->endSize;
++ simplePointVertices[i].vx = datum->vx;
++ simplePointVertices[i].vy = datum->vy;
++ simplePointVertices[i].ax = datum->ax;
++ simplePointVertices[i].ay = datum->ay;
++ }
++ break;
++ default:
++ break;
++ }
++ }
++
++
++
++ QT_END_NAMESPACE
++
++ #include "moc_qquickimageparticle_p.cpp"