Sprites can now have varying width and height
authorAlan Alpert <alan.alpert@nokia.com>
Wed, 5 Oct 2011 01:26:57 +0000 (11:26 +1000)
committerQt by Nokia <qt-info@nokia.com>
Wed, 5 Oct 2011 05:33:29 +0000 (07:33 +0200)
Varying between Sprites, or between width and height, not within a
single Sprite. For ImageParticle only, SpriteImage changes will be in a
later commit.

Also adds spriteInterpolation boolean.

Change-Id: I80681e44f26985a6f6a6b83bd162f6231c7f28c4
Reviewed-on: http://codereview.qt-project.org/6002
Reviewed-by: Qt Sanity Bot <qt_sanity_bot@ovi.com>
Reviewed-by: Martin Jones <martin.jones@nokia.com>

src/declarative/items/qsgsprite.cpp
src/declarative/items/qsgsprite_p.h
src/declarative/items/qsgspriteengine.cpp
src/declarative/items/qsgspriteengine_p.h
src/declarative/particles/defaultshaders/imagevertex.shader
src/declarative/particles/qsgimageparticle.cpp
src/declarative/particles/qsgimageparticle_p.h
src/declarative/particles/qsgparticleaffector.cpp
src/declarative/particles/qsgparticlesystem_p.h

index 63d1951..68f2e78 100644 (file)
@@ -50,6 +50,7 @@ QSGSprite::QSGSprite(QObject *parent) :
     , m_framesPerRow(0)
     , m_frameHeight(0)
     , m_frameWidth(0)
+    , m_rowY(0)
 {
 }
 
index ed7c6c4..58b6c13 100644 (file)
@@ -127,6 +127,7 @@ private:
     QUrl m_source;
     int m_frameHeight;
     int m_frameWidth;
+    int m_rowY;
 
 };
 
index f02d229..2ef36df 100644 (file)
@@ -148,6 +148,28 @@ int QSGSpriteEngine::spriteDuration(int sprite)
         return rowDuration;
 }
 
+int QSGSpriteEngine::spriteY(int sprite)
+{
+    int state = m_things[sprite];
+    if (!m_sprites[state]->m_generatedCount)
+        return m_sprites[state]->m_rowY;
+    int rowDuration = m_duration[sprite] * m_sprites[state]->m_framesPerRow;
+    int extra = (m_timeOffset - m_startTimes[sprite])/rowDuration;
+    return m_sprites[state]->m_rowY + m_sprites[state]->m_frameHeight * extra;
+}
+
+int QSGSpriteEngine::spriteWidth(int sprite)
+{
+    int state = m_things[sprite];
+    return m_sprites[state]->m_frameWidth;
+}
+
+int QSGSpriteEngine::spriteHeight(int sprite)
+{
+    int state = m_things[sprite];
+    return m_sprites[state]->m_frameHeight;
+}
+
 int QSGSpriteEngine::spriteCount()//TODO: Actually image state count, need to rename these things to make sense together
 {
     return m_imageStateCount;
@@ -175,12 +197,12 @@ void QSGStochasticEngine::setGoal(int state, int sprite, bool jump)
 
 QImage QSGSpriteEngine::assembledImage()
 {
-    int frameHeight = 0;
-    int frameWidth = 0;
+    int h = 0;
+    int w = 0;
     m_maxFrames = 0;
     m_imageStateCount = 0;
+    int maxSize = 0;
 
-    int maxSize;
     glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize);
     foreach (QSGStochasticState* s, m_states){
         QSGSprite* sprite = qobject_cast<QSGSprite*>(s);
@@ -201,60 +223,49 @@ QImage QSGSpriteEngine::assembledImage()
         }
 
         //Check that the frame sizes are the same within one engine
-        int imgWidth = state->frameWidth();
-        if (!imgWidth)
-            imgWidth = img.width() / state->frames();
-        if (frameWidth){
-            if (imgWidth != frameWidth){
-                qWarning() << "SpriteEngine: Irregular frame width..." << state->source().toLocalFile();
-                return QImage();
-            }
-        }else{
-            frameWidth = imgWidth;
-        }
+        if (!state->m_frameWidth)
+            state->m_frameWidth = img.width() / state->frames();
 
         int imgHeight = state->frameHeight();
-        if (!imgHeight)
-            imgHeight = img.height();
-        if (frameHeight){
-            if (imgHeight!=frameHeight){
-                qWarning() << "SpriteEngine: Irregular frame height..." << state->source().toLocalFile();
-                return QImage();
-            }
-        }else{
-            frameHeight = imgHeight;
-        }
+        if (!state->m_frameHeight)
+            state->m_frameHeight = img.height();
 
-        if (state->frames() * frameWidth > maxSize){
+        if (state->frames() * state->frameWidth() > maxSize){
             struct helper{
                 static int divRoundUp(int a, int b){return (a+b-1)/b;}
             };
-            int rowsNeeded = helper::divRoundUp(state->frames(), helper::divRoundUp(maxSize, frameWidth));
-            if (rowsNeeded * frameHeight > maxSize){
+            int rowsNeeded = helper::divRoundUp(state->frames(), helper::divRoundUp(maxSize, state->frameWidth()));
+            if (rowsNeeded * state->frameHeight() > maxSize){
                 qWarning() << "SpriteEngine: Animation too large to fit in one texture..." << state->source().toLocalFile();
                 qWarning() << "SpriteEngine: Your texture max size today is " << maxSize;
             }
             state->m_generatedCount = rowsNeeded;
+            h += state->frameHeight() * rowsNeeded;
+            w = qMax(w, helper::divRoundUp(maxSize, state->frameWidth()));
             m_imageStateCount += rowsNeeded;
         }else{
+            h += state->frameHeight();
+            w = qMax(w, state->frameWidth() * state->frames());
             m_imageStateCount++;
         }
     }
 
     //maxFrames is max number in a line of the texture
-    if (m_maxFrames * frameWidth > maxSize)
-        m_maxFrames = maxSize/frameWidth;
-    QImage image(frameWidth * m_maxFrames, frameHeight * m_imageStateCount, QImage::Format_ARGB32);
+    QImage image(w, h, QImage::Format_ARGB32);
     image.fill(0);
     QPainter p(&image);
     int y = 0;
     foreach (QSGSprite* state, m_sprites){
         QImage img(state->source().toLocalFile());
+        int frameWidth = state->m_frameWidth;
+        int frameHeight = state->m_frameHeight;
         if (img.height() == frameHeight && img.width() <  maxSize){//Simple case
             p.drawImage(0,y,img);
+            state->m_rowY = y;
             y += frameHeight;
-        }else{
+        }else{//Chopping up image case
             state->m_framesPerRow = image.width()/frameWidth;
+            state->m_rowY = y;
             int x = 0;
             int curX = 0;
             int curY = 0;
index 0561849..30a041a 100644 (file)
@@ -277,6 +277,10 @@ public:
     int spriteStart(int sprite=0);
     int spriteFrames(int sprite=0);
     int spriteDuration(int sprite=0);
+    int spriteX(int sprite=0) { return 0; }//Currently all rows are 0 aligned, if we get more space efficient we might change this
+    int spriteY(int sprite=0);
+    int spriteWidth(int sprite=0);
+    int spriteHeight(int sprite=0);
     int spriteCount();//Like state count, but for the image states
     int maxFrames();
     QImage assembledImage();
index daf49fd..132771a 100644 (file)
@@ -11,9 +11,9 @@ attribute highp vec4 vDeformVec; //x,y x unit vector; z,w = y unit vector
 attribute highp vec3 vRotation; //x = radians of rotation, y=rotation speed, z= bool autoRotate
 #endif
 #ifdef SPRITE
-attribute highp vec4 vAnimData;// idx, duration, frameCount (this anim), timestamp (this anim)
-uniform highp float framecount; //maximum of all anims
-uniform highp float animcount;
+attribute highp vec4 vAnimData;// interpolate(bool), duration, frameCount (this anim), timestamp (this anim)
+attribute highp vec4 vAnimPos;//sheet x,y, width/height of this anim
+uniform highp vec2 animSheetSize; //width/height of whole sheet
 #endif
 
 uniform highp mat4 qt_Matrix;
@@ -54,13 +54,13 @@ void main() {
     tt.y = mod((timestamp - vAnimData.w)*1000., vAnimData.y) / vAnimData.y;
 
     frameIndex = floor(frameIndex);
-    fTexS.xy = vec2(((frameIndex + vTex.x) / framecount), ((vAnimData.x + vTex.y) / animcount));
+    fTexS.xy = vec2(((frameIndex + vTex.x) * vAnimPos.z / animSheetSize.x), ((vAnimPos.y + vTex.y * vAnimPos.w) / animSheetSize.y));
 
     //Next frame is also passed, for interpolation
     //### Should the next anim be precalculated to allow for interpolation there?
-    if(frameIndex != vAnimData.z - 1.)//Can't do it for the last frame though, this anim may not loop
+    if(vAnimData.x == 1.0 && frameIndex != vAnimData.z - 1.)//Can't do it for the last frame though, this anim may not loop
         frameIndex = mod(frameIndex+1., vAnimData.z);
-    fTexS.zw = vec2(((frameIndex + vTex.x) / framecount), ((vAnimData.x + vTex.y) / animcount));
+    fTexS.zw = vec2(((frameIndex + vTex.x) * vAnimPos.z / animSheetSize.x), ((vAnimPos.y + vTex.y * vAnimPos.w) / animSheetSize.y));
 #else
 #ifdef DEFORM
     fTex = vTex;
index c6b5138..f0304b4 100644 (file)
@@ -85,8 +85,7 @@ class ImageMaterialData
 
     qreal timestamp;
     qreal entry;
-    qreal framecount;
-    qreal animcount;
+    QSizeF animSheetSize;
 };
 
 //TODO: Move shaders inline once they've stablilized
@@ -142,8 +141,6 @@ public:
         d->texture->bind();
 
         program()->setUniformValue(m_timestamp_id, (float) d->timestamp);
-        program()->setUniformValue("framecount", (float) 1);
-        program()->setUniformValue("animcount", (float) 1);
         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);
@@ -243,7 +240,7 @@ public:
 
     QList<QByteArray> attributes() const {
         return QList<QByteArray>() << "vPos" << "vTex" << "vData" << "vVec"
-            << "vColor" << "vDeformVec" << "vRotation" << "vAnimData";
+            << "vColor" << "vDeformVec" << "vRotation" << "vAnimData" << "vAnimPos";
     };
 
     void initialize() {
@@ -253,8 +250,7 @@ public:
         program()->setUniformValue("colortable", 1);
         glFuncs = QOpenGLContext::currentContext()->functions();
         m_timestamp_id = program()->uniformLocation("timestamp");
-        m_framecount_id = program()->uniformLocation("framecount");
-        m_animcount_id = program()->uniformLocation("animcount");
+        m_animsize_id = program()->uniformLocation("animSheetSize");
         m_entry_id = program()->uniformLocation("entry");
         m_sizetable_id = program()->uniformLocation("sizetable");
         m_opacitytable_id = program()->uniformLocation("opacitytable");
@@ -269,16 +265,14 @@ public:
         d->texture->bind();
 
         program()->setUniformValue(m_timestamp_id, (float) d->timestamp);
-        program()->setUniformValue(m_framecount_id, (float) d->framecount);
-        program()->setUniformValue(m_animcount_id, (float) d->animcount);
+        program()->setUniformValue(m_animsize_id, d->animSheetSize);
         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_framecount_id;
-    int m_animcount_id;
+    int m_animsize_id;
     int m_entry_id;
     int m_sizetable_id;
     int m_opacitytable_id;
@@ -630,6 +624,14 @@ void fillUniformArrayFromImage(float* array, const QImage& img, int size)
 
     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.
+*/
 
 
 QSGImageParticle::QSGImageParticle(QSGItem* parent)
@@ -650,6 +652,7 @@ QSGImageParticle::QSGImageParticle(QSGItem* parent)
     , m_xVector(0)
     , m_yVector(0)
     , m_spriteEngine(0)
+    , m_spritesInterpolate(true)
     , m_explicitColor(false)
     , m_explicitRotation(false)
     , m_explicitDeformation(false)
@@ -864,6 +867,14 @@ void QSGImageParticle::setYVector(QSGDirection* arg)
         reset();
 }
 
+void QSGImageParticle::setSpritesInterpolate(bool arg)
+{
+    if (m_spritesInterpolate != arg) {
+        m_spritesInterpolate = arg;
+        emit spritesInterpolateChanged(arg);
+    }
+}
+
 void QSGImageParticle::setBloat(bool arg)
 {
     if (m_bloat != arg) {
@@ -1000,13 +1011,14 @@ static QSGGeometry::Attribute SpriteParticle_Attributes[] = {
     QSGGeometry::Attribute::create(4, 4, GL_UNSIGNED_BYTE),     // Colors
     QSGGeometry::Attribute::create(5, 4, GL_FLOAT),             // DeformationVectors
     QSGGeometry::Attribute::create(6, 3, GL_FLOAT),             // Rotation
-    QSGGeometry::Attribute::create(7, 4, GL_FLOAT)              // Anim Data
+    QSGGeometry::Attribute::create(7, 4, GL_FLOAT),             // Anim Data
+    QSGGeometry::Attribute::create(8, 4, GL_FLOAT)              // Anim Pos
 };
 
 static QSGGeometry::AttributeSet SpriteParticle_AttributeSet =
 {
-    8, // Attribute Count
-    (2 + 2 + 4 + 4 + 4 + 4 + 3) * sizeof(float) + 4 * sizeof(uchar),
+    9, // Attribute Count
+    (2 + 2 + 4 + 4 + 4 + 4 + 4 + 3) * sizeof(float) + 4 * sizeof(uchar),
     SpriteParticle_Attributes
 };
 
@@ -1114,7 +1126,7 @@ QSGGeometryNode* QSGImageParticle::buildParticleNodes()
     switch (perfLevel) {//Fallthrough intended
     case Sprites:
         m_material = SpriteMaterial::createMaterial();
-        getState<ImageMaterialData>(m_material)->framecount = m_spriteEngine->maxFrames();
+        getState<ImageMaterialData>(m_material)->animSheetSize = QSizeF(image.size());
         m_spriteEngine->setCount(m_count);
     case Tabled:
         if (!m_material)
@@ -1270,7 +1282,6 @@ void QSGImageParticle::prepareNextFrame()
     switch (perfLevel){//Fall-through intended
     case Sprites:
         //Advance State
-        getState<ImageMaterialData>(m_material)->animcount = m_spriteEngine->spriteCount();
         m_spriteEngine->updateSprites(timeStamp);
         foreach (const QString &str, m_groups){
             int gIdx = m_system->groupIds[str];
@@ -1280,12 +1291,15 @@ void QSGImageParticle::prepareNextFrame()
             for (int i=0; i < count; i++){
                 int spriteIdx = m_idxStarts[gIdx] + i;
                 Vertices<SpriteVertex> &p = particles[i];
-                int curIdx = m_spriteEngine->spriteState(spriteIdx);
-                if (curIdx != p.v1.animIdx){
-                    p.v1.animIdx = p.v2.animIdx = p.v3.animIdx = p.v4.animIdx = curIdx;
+                int curY = m_spriteEngine->spriteY(spriteIdx);//Y is fixed per sprite row, used to distinguish rows here
+                if (curY != p.v1.animY){
                     p.v1.animT = p.v2.animT = p.v3.animT = p.v4.animT = m_spriteEngine->spriteStart(spriteIdx)/1000.0;
                     p.v1.frameCount = p.v2.frameCount = p.v3.frameCount = p.v4.frameCount = m_spriteEngine->spriteFrames(spriteIdx);
                     p.v1.frameDuration = p.v2.frameDuration = p.v3.frameDuration = p.v4.frameDuration = m_spriteEngine->spriteDuration(spriteIdx);
+                    p.v1.animX = p.v2.animX = p.v3.animX = p.v4.animX = m_spriteEngine->spriteX(spriteIdx);
+                    p.v1.animY = p.v2.animY = p.v3.animY = p.v4.animY = m_spriteEngine->spriteY(spriteIdx);
+                    p.v1.animWidth = p.v2.animWidth = p.v3.animWidth = p.v4.animWidth = m_spriteEngine->spriteWidth(spriteIdx);
+                    p.v1.animHeight = p.v2.animHeight = p.v3.animHeight = p.v4.animHeight = m_spriteEngine->spriteHeight(spriteIdx);
                 }
             }
         }
@@ -1327,14 +1341,19 @@ void QSGImageParticle::initialize(int gIdx, int pIdx)
                     datum->animationOwner = this;
                 QSGParticleData* writeTo = (datum->animationOwner == this ? datum : getShadowDatum(datum));
                 writeTo->animT = writeTo->t;
-                writeTo->animIdx = 0;
+                //writeTo->animInterpolate = m_spritesInterpolate;
                 if (m_spriteEngine){
                     m_spriteEngine->start(spriteIdx);
                     writeTo->frameCount = m_spriteEngine->spriteFrames(spriteIdx);
                     writeTo->frameDuration = m_spriteEngine->spriteDuration(spriteIdx);
+                    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{
                     writeTo->frameCount = 1;
                     writeTo->frameDuration = 9999;
+                    writeTo->animX = writeTo->animY = writeTo->animWidth = writeTo->animHeight = 0;
                 }
             }
         case Tabled:
@@ -1452,17 +1471,24 @@ void QSGImageParticle::commit(int gIdx, int pIdx)
                 spriteVertices[i].rotationSpeed = datum->rotationSpeed;
                 spriteVertices[i].autoRotate = datum->autoRotate;
             }
+            spriteVertices[i].animInterpolate = m_spritesInterpolate ? 1.0 : 0.0;//### Shadow? In particleData? Or uniform?
             if (m_explicitAnimation && datum->animationOwner != this) {
                 QSGParticleData* shadow = getShadowDatum(datum);
-                spriteVertices[i].animIdx = shadow->animIdx;
                 spriteVertices[i].frameDuration = shadow->frameDuration;
                 spriteVertices[i].frameCount = shadow->frameCount;
                 spriteVertices[i].animT = shadow->animT;
+                spriteVertices[i].animX = shadow->animX;
+                spriteVertices[i].animY = shadow->animY;
+                spriteVertices[i].animWidth = shadow->animWidth;
+                spriteVertices[i].animHeight = shadow->animHeight;
             } else {
-                spriteVertices[i].animIdx = datum->animIdx;
                 spriteVertices[i].frameDuration = datum->frameDuration;
                 spriteVertices[i].frameCount = datum->frameCount;
                 spriteVertices[i].animT = datum->animT;
+                spriteVertices[i].animX = datum->animX;
+                spriteVertices[i].animY = datum->animY;
+                spriteVertices[i].animWidth = datum->animWidth;
+                spriteVertices[i].animHeight = datum->animHeight;
             }
             if (m_explicitColor && datum->colorOwner != this) {
                 QSGParticleData* shadow = getShadowDatum(datum);
index 274ff42..89796da 100644 (file)
@@ -130,10 +130,14 @@ struct SpriteVertex {
     float rotation;
     float rotationSpeed;
     float autoRotate;//Assumed that GPUs prefer floats to bools
-    float animIdx;
+    float animInterpolate;
     float frameDuration;
     float frameCount;
     float animT;
+    float animX;
+    float animY;
+    float animWidth;
+    float animHeight;
 };
 
 template <typename Vertex>
@@ -177,6 +181,7 @@ class QSGImageParticle : public QSGParticlePainter
     //yVector is the same, but top-left to bottom-left. The particle is always a parallelogram.
     Q_PROPERTY(QSGDirection* yVector READ yVector WRITE setYVector NOTIFY yVectorChanged RESET resetDeformation)
     Q_PROPERTY(QDeclarativeListProperty<QSGSprite> sprites READ sprites)
+    Q_PROPERTY(bool spritesInterpolate READ spritesInterpolate WRITE setSpritesInterpolate NOTIFY spritesInterpolateChanged)
 
     Q_PROPERTY(EntryEffect entryEffect READ entryEffect WRITE setEntryEffect NOTIFY entryEffectChanged)
     Q_PROPERTY(bool bloat READ bloat WRITE setBloat NOTIFY bloatChanged)//Just a debugging property to bypass optimizations
@@ -248,6 +253,8 @@ public:
 
     QSGDirection* yVector() const { return m_yVector; }
 
+    bool spritesInterpolate() const { return m_spritesInterpolate; }
+
     bool bloat() const { return m_bloat; }
 
     EntryEffect entryEffect() const { return m_entryEffect; }
@@ -291,6 +298,8 @@ signals:
 
     void yVectorChanged(QSGDirection* arg);
 
+    void spritesInterpolateChanged(bool arg);
+
     void bloatChanged(bool arg);
 
     void entryEffectChanged(EntryEffect arg);
@@ -321,6 +330,8 @@ public slots:
 
     void setYVector(QSGDirection* arg);
 
+    void setSpritesInterpolate(bool arg);
+
     void setBloat(bool arg);
 
     void setEntryEffect(EntryEffect arg);
@@ -372,6 +383,7 @@ private:
 
     QList<QSGSprite*> m_sprites;
     QSGSpriteEngine* m_spriteEngine;
+    bool m_spritesInterpolate;
 
     bool m_explicitColor;
     bool m_explicitRotation;
index 62f5ccd..24bddea 100644 (file)
@@ -208,7 +208,7 @@ bool QSGParticleAffector::affectParticle(QSGParticleData *, qreal )
 void QSGParticleAffector::reset(QSGParticleData* pd)
 {//TODO: This, among other ones, should be restructured so they don't all need to remember to call the superclass
     if (m_onceOff)
-        if (activeGroup(d->group))
+        if (activeGroup(pd->group))
             m_onceOffed.remove(qMakePair(pd->group, pd->index));
 }
 
index 5dd93d2..e88aa49 100644 (file)
@@ -203,6 +203,10 @@ public:
     float frameDuration;
     float frameCount;
     float animT;
+    float animX;
+    float animY;
+    float animWidth;
+    float animHeight;
     float r;
     QSGItem* delegate;
     int modelIndex;