Multi-line sprite files will now work
authorAlan Alpert <alan.alpert@nokia.com>
Thu, 26 May 2011 05:30:20 +0000 (15:30 +1000)
committerAlan Alpert <alan.alpert@nokia.com>
Thu, 26 May 2011 05:30:20 +0000 (15:30 +1000)
src/imports/particles/itemparticle.h
src/imports/particles/spriteengine.cpp
src/imports/particles/spriteengine.h
src/imports/particles/spriteimage.cpp
src/imports/particles/spriteparticle.cpp
src/imports/particles/spritestate.cpp
src/imports/particles/spritestate.h
src/imports/particles/ultraparticle.cpp

index 40dab74..50414c7 100644 (file)
@@ -73,6 +73,7 @@ signals:
     void fadeChanged();
 
 public slots:
+    //TODO: Add a follow mode, where moving the delegate causes the logical particle to go with it?
     void freeze(QSGItem* item);
     void unfreeze(QSGItem* item);
     void take(QSGItem* item,bool prioritize=false);//take by modelparticle
index b324f7a..7676d9e 100644 (file)
@@ -70,11 +70,64 @@ SpriteEngine::~SpriteEngine()
 
 int SpriteEngine::maxFrames()
 {
-   int max = 0;
-   foreach(SpriteState* s, m_states)
-       if(s->frames() > max)
-           max = s->frames();
-   return max;
+    return m_maxFrames;
+}
+
+/* States too large to fit in one row are split into multiple rows
+   This is more efficient for the implementation, but should remain an implementation detail (invisible from QML)
+   Therefore the below functions abstract sprite from the viewpoint of classes that pass the details onto shaders
+   But States maintain their listed index for internal structures
+TODO: All these calculations should be pre-calculated and cached during initialization for a significant performance boost
+*/
+int SpriteEngine::spriteState(int sprite)
+{
+    int state = m_sprites[sprite];
+    if(!m_states[state]->m_generatedCount)
+        return state;
+    int rowDuration = m_states[state]->duration() * m_states[state]->m_framesPerRow;
+    int extra = (m_timeOffset - m_startTimes[sprite])/rowDuration;
+    return state + extra;
+}
+
+int SpriteEngine::spriteStart(int sprite)
+{
+    int state = m_sprites[sprite];
+    if(!m_states[state]->m_generatedCount)
+        return m_startTimes[sprite];
+    int rowDuration = m_states[state]->duration() * m_states[state]->m_framesPerRow;
+    int extra = (m_timeOffset - m_startTimes[sprite])/rowDuration;
+    return state + extra*rowDuration;
+}
+
+int SpriteEngine::spriteFrames(int sprite)
+{
+    int state = m_sprites[sprite];
+    if(!m_states[state]->m_generatedCount)
+        return m_states[state]->frames();
+    int rowDuration = m_states[state]->duration() * m_states[state]->m_framesPerRow;
+    int extra = (m_timeOffset - m_startTimes[sprite])/rowDuration;
+    if(extra == m_states[state]->m_generatedCount - 1)//last state
+        return m_states[state]->frames() % m_states[state]->m_framesPerRow;
+    else
+        return m_states[state]->m_framesPerRow;
+}
+
+int SpriteEngine::spriteDuration(int sprite)
+{
+    int state = m_sprites[sprite];
+    if(!m_states[state]->m_generatedCount)
+        return m_states[state]->duration();
+    int rowDuration = m_states[state]->duration() * m_states[state]->m_framesPerRow;
+    int extra = (m_timeOffset - m_startTimes[sprite])/rowDuration;
+    if(extra == m_states[state]->m_generatedCount - 1)//last state
+        return (m_states[state]->duration() * m_states[state]->frames()) % rowDuration;
+    else
+        return rowDuration;
+}
+
+int SpriteEngine::spriteCount()//TODO: Actually image state count, need to rename these things to make sense together
+{
+    return m_imageStateCount;
 }
 
 void SpriteEngine::setGoal(int state, int sprite, bool jump)
@@ -99,6 +152,7 @@ QImage SpriteEngine::assembledImage()
     int frameHeight = 0;
     int frameWidth = 0;
     m_maxFrames = 0;
+    m_imageStateCount = 0;
 
     int maxSize;
     glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize);
@@ -113,46 +167,96 @@ QImage SpriteEngine::assembledImage()
             return QImage();
         }
 
+        //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(img.width() / state->frames() != frameWidth){
+            if(imgWidth != frameWidth){
                 qWarning() << "SpriteEngine: Irregular frame width..." << state->source().toLocalFile();
                 return QImage();
             }
         }else{
-            frameWidth = img.width() / state->frames();
-        }
-        if(img.width() > maxSize){
-            qWarning() << "SpriteEngine: Animation too wide..." << state->source().toLocalFile();
-            return QImage();
+            frameWidth = imgWidth;
         }
 
+        int imgHeight = state->frameHeight();
+        if(!imgHeight)
+            imgHeight = img.height();
         if(frameHeight){
-            if(img.height()!=frameHeight){
+            if(imgHeight!=frameHeight){
                 qWarning() << "SpriteEngine: Irregular frame height..." << state->source().toLocalFile();
                 return QImage();
             }
         }else{
-            frameHeight = img.height();
+            frameHeight = imgHeight;
         }
 
-        if(img.height() > maxSize){
-            qWarning() << "SpriteEngine: Animation too tall..." << state->source().toLocalFile();
-            return QImage();
+        if(state->frames() * 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){
+                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;
+            m_imageStateCount += rowsNeeded;
+        }else{
+            m_imageStateCount++;
         }
     }
 
-    QImage image(frameWidth * m_maxFrames, frameHeight * m_states.count(), QImage::Format_ARGB32);
+    //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);
     image.fill(0);
     QPainter p(&image);
     int y = 0;
     foreach(SpriteState* state, m_states){
         QImage img(state->source().toLocalFile());
-        p.drawImage(0,y,img);
-        y += frameHeight;
+        if(img.height() == frameHeight && img.width() <  maxSize){//Simple case
+            p.drawImage(0,y,img);
+            y += frameHeight;
+        }else{
+            state->m_framesPerRow = image.width()/frameWidth;
+            int x = 0;
+            int curX = 0;
+            int curY = 0;
+            int framesLeft = state->frames();
+            while(framesLeft > 0){
+                if(image.width() - x + curX <= img.width()){//finish a row in image (dest)
+                    int copied = image.width() - x;
+                    Q_ASSERT(!(copied % frameWidth));//XXX: Just checking
+                    framesLeft -= copied/frameWidth;
+                    p.drawImage(x,y,img.copy(curX,curY,copied,frameHeight));
+                    y += frameHeight;
+                    curX += copied;
+                    x = 0;
+                    if(curX == img.width()){
+                        curX = 0;
+                        curY += frameHeight;
+                    }
+                }else{//finish a row in img (src)
+                    int copied = img.width() - curX;
+                    Q_ASSERT(!(copied % frameWidth));//XXX: Just checking
+                    framesLeft -= copied/frameWidth;
+                    p.drawImage(x,y,img.copy(curX,curY,copied,frameHeight));
+                    curY += frameHeight;
+                    x += copied;
+                    curX = 0;
+                }
+            }
+            if(x)
+                y += frameHeight;
+        }
     }
 
     if(image.height() > maxSize){
         qWarning() << "SpriteEngine: Too many animations to fit in one texture...";
+        qWarning() << "SpriteEngine: Your texture max size today is " << maxSize;
         return QImage();
     }
     return image;
@@ -224,7 +328,7 @@ uint SpriteEngine::updateSprites(uint time)
 
             m_sprites[idx] = nextIdx;
             m_startTimes[idx] = time;
-            //TODO: emit something?
+            //TODO: emit something? Remember to emit this when a psuedostate changes too
             addToUpdateList((m_states[nextIdx]->duration() * m_states[nextIdx]->frames()) + time, idx);
         }
         m_stateUpdates.pop_front();
index 76a2e29..0180245 100644 (file)
@@ -82,11 +82,11 @@ public:
     int count() const {return m_sprites.count();}
     void setCount(int c);
 
-    int spriteState(int sprite=0) {return m_sprites[sprite];}
-    int spriteStart(int sprite=0) {return m_startTimes[sprite];}
-    int stateIndex(SpriteState* s){return m_states.indexOf(s);}
-    SpriteState* state(int idx){return m_states[idx];}
-    int stateCount() {return m_states.count();}
+    int spriteState(int sprite=0);// {return m_sprites[sprite];}
+    int spriteStart(int sprite=0);// {return m_startTimes[sprite];}
+    int spriteFrames(int sprite=0);
+    int spriteDuration(int sprite=0);
+    int spriteCount();//Like state count, but for the image states
     int maxFrames();
 
     void setGoal(int state, int sprite=0, bool jump=false);
@@ -94,6 +94,11 @@ public:
 
     void startSprite(int index=0);
 
+private://Nothing outside should use this?
+    friend class SpriteGoalAffector;//XXX: Fix interface
+    int stateCount() {return m_states.count();}
+    int stateIndex(SpriteState* s){return m_states.indexOf(s);}//TODO: Does this need to be hidden?
+    SpriteState* state(int idx){return m_states[idx];}//Used by spritegoal affector
 signals:
 
     void globalGoalChanged(QString arg);
@@ -123,6 +128,7 @@ private:
     uint m_timeOffset;
     QString m_globalGoal;
     int m_maxFrames;
+    int m_imageStateCount;
 };
 
 //Common use is to have your own list property which is transparently an engine
index 0ce3461..ea08ae4 100644 (file)
@@ -263,12 +263,11 @@ QSGGeometryNode* SpriteImage::buildNode()
     g->setDrawingMode(GL_TRIANGLES);
 
     SpriteVertices *p = (SpriteVertices *) g->vertexData();
+    m_spriteEngine->startSprite(0);
     p->v1.animT = p->v2.animT = p->v3.animT = p->v4.animT = 0;
     p->v1.animIdx = p->v2.animIdx = p->v3.animIdx = p->v4.animIdx = 0;
-    SpriteState* state = m_spriteEngine->state(0);
-    p->v1.frameCount = p->v2.frameCount = p->v3.frameCount = p->v4.frameCount = state->frames();
-    p->v1.frameDuration = p->v2.frameDuration = p->v3.frameDuration = p->v4.frameDuration = state->duration();
-    m_spriteEngine->startSprite(0);
+    p->v1.frameCount = p->v2.frameCount = p->v3.frameCount = p->v4.frameCount = m_spriteEngine->spriteFrames();
+    p->v1.frameDuration = p->v2.frameDuration = p->v3.frameDuration = p->v4.frameDuration = m_spriteEngine->spriteDuration();
 
     p->v1.tx = 0;
     p->v1.ty = 0;
@@ -335,7 +334,7 @@ void SpriteImage::prepareNextFrame()
     uint timeInt = m_timestamp.elapsed();
     qreal time =  timeInt / 1000.;
     m_material->timestamp = time;
-    m_material->animcount = m_spriteEngine->stateCount();
+    m_material->animcount = m_spriteEngine->spriteCount();
     m_material->height = height();
     m_material->width = width();
 
@@ -346,8 +345,8 @@ void SpriteImage::prepareNextFrame()
     if(curIdx != p->v1.animIdx){
         p->v1.animIdx = p->v2.animIdx = p->v3.animIdx = p->v4.animIdx = curIdx;
         p->v1.animT = p->v2.animT = p->v3.animT = p->v4.animT = m_spriteEngine->spriteStart()/1000.0;
-        p->v1.frameCount = p->v2.frameCount = p->v3.frameCount = p->v4.frameCount = m_spriteEngine->state(curIdx)->frames();
-        p->v1.frameDuration = p->v2.frameDuration = p->v3.frameDuration = p->v4.frameDuration = m_spriteEngine->state(curIdx)->duration();
+        p->v1.frameCount = p->v2.frameCount = p->v3.frameCount = p->v4.frameCount = m_spriteEngine->spriteFrames();
+        p->v1.frameDuration = p->v2.frameDuration = p->v3.frameDuration = p->v4.frameDuration = m_spriteEngine->spriteDuration();
     }
 }
 
index 1b62765..6039d28 100644 (file)
@@ -360,12 +360,11 @@ void SpriteParticle::load(ParticleData *d)
     SpriteParticleVertices &p = particles[pos];
 
     // Initial Sprite State
+    m_spriteEngine->startSprite(pos);
     p.v1.animT = p.v2.animT = p.v3.animT = p.v4.animT = p.v1.t;
     p.v1.animIdx = p.v2.animIdx = p.v3.animIdx = p.v4.animIdx = 0;
-    SpriteState* state = m_spriteEngine->state(0);
-    p.v1.frameCount = p.v2.frameCount = p.v3.frameCount = p.v4.frameCount = state->frames();
-    p.v1.frameDuration = p.v2.frameDuration = p.v3.frameDuration = p.v4.frameDuration = state->duration();
-    m_spriteEngine->startSprite(pos);
+    p.v1.frameCount = p.v2.frameCount = p.v3.frameCount = p.v4.frameCount = m_spriteEngine->spriteFrames(pos);
+    p.v1.frameDuration = p.v2.frameDuration = p.v3.frameDuration = p.v4.frameDuration = m_spriteEngine->spriteDuration(pos);
 
     vertexCopy(p.v1, d->pv);
     vertexCopy(p.v2, d->pv);
@@ -424,7 +423,7 @@ void SpriteParticle::prepareNextFrame()
 
     qreal time =  timeStamp / 1000.;
     m_material->timestamp = time;
-    m_material->animcount = m_spriteEngine->stateCount();
+    m_material->animcount = m_spriteEngine->spriteCount();
 
     //Advance State
     SpriteParticleVertices *particles = (SpriteParticleVertices *) m_node->geometry()->vertexData();
@@ -435,8 +434,8 @@ void SpriteParticle::prepareNextFrame()
         if(curIdx != p.v1.animIdx){
             p.v1.animIdx = p.v2.animIdx = p.v3.animIdx = p.v4.animIdx = curIdx;
             p.v1.animT = p.v2.animT = p.v3.animT = p.v4.animT = m_spriteEngine->spriteStart(i)/1000.0;
-            p.v1.frameCount = p.v2.frameCount = p.v3.frameCount = p.v4.frameCount = m_spriteEngine->state(curIdx)->frames();
-            p.v1.frameDuration = p.v2.frameDuration = p.v3.frameDuration = p.v4.frameDuration = m_spriteEngine->state(curIdx)->duration();
+            p.v1.frameCount = p.v2.frameCount = p.v3.frameCount = p.v4.frameCount = m_spriteEngine->spriteFrames(i);
+            p.v1.frameDuration = p.v2.frameDuration = p.v3.frameDuration = p.v4.frameDuration = m_spriteEngine->spriteDuration(i);
         }
     }
 }
index c3cc249..72535c0 100644 (file)
@@ -45,7 +45,11 @@ QT_BEGIN_NAMESPACE
 
 SpriteState::SpriteState(QObject *parent) :
     QObject(parent)
+    , m_generatedCount(0)
+    , m_framesPerRow(0)
     , m_frames(1)
+    , m_frameHeight(0)
+    , m_frameWidth(0)
     , m_duration(1000)
 {
 }
index 1dbc747..5157a8b 100644 (file)
@@ -59,6 +59,10 @@ class SpriteState : public QObject
     Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
     Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
     Q_PROPERTY(int frames READ frames WRITE setFrames NOTIFY framesChanged)
+    //If frame height or width is not specified, it is assumed to be a single long row of frames.
+    //Otherwise, it can be multiple contiguous rows, when one row runs out the next will be used.
+    Q_PROPERTY(int frameHeight READ frameHeight WRITE setFrameHeight NOTIFY frameHeightChanged)
+    Q_PROPERTY(int frameWidth READ frameWidth WRITE setFrameWidth NOTIFY frameWidthChanged)
     Q_PROPERTY(int duration READ duration WRITE setDuration NOTIFY durationChanged)
     Q_PROPERTY(int durationVariance READ durationVariance WRITE setDurationVariance NOTIFY durationVarianceChanged)
     Q_PROPERTY(qreal speedModifiesDuration READ speedModifer WRITE setSpeedModifier NOTIFY speedModifierChanged)
@@ -77,6 +81,16 @@ public:
         return m_frames;
     }
 
+    int frameHeight() const
+    {
+        return m_frameHeight;
+    }
+
+    int frameWidth() const
+    {
+        return m_frameWidth;
+    }
+
     int duration() const
     {
         return m_duration;
@@ -108,6 +122,10 @@ signals:
 
     void framesChanged(int arg);
 
+    void frameHeightChanged(int arg);
+
+    void frameWidthChanged(int arg);
+
     void durationChanged(int arg);
 
     void nameChanged(QString arg);
@@ -136,6 +154,22 @@ public slots:
         }
     }
 
+    void setFrameHeight(int arg)
+    {
+        if (m_frameHeight != arg) {
+            m_frameHeight = arg;
+            emit frameHeightChanged(arg);
+        }
+    }
+
+    void setFrameWidth(int arg)
+    {
+        if (m_frameWidth != arg) {
+            m_frameWidth = arg;
+            emit frameWidthChanged(arg);
+        }
+    }
+
     void setDuration(int arg)
     {
         if (m_duration != arg) {
@@ -179,8 +213,12 @@ public slots:
 private:
     friend class SpriteParticle;
     friend class SpriteEngine;
+    int m_generatedCount;
+    int m_framesPerRow;
     QUrl m_source;
     int m_frames;
+    int m_frameHeight;
+    int m_frameWidth;
     int m_duration;
     QString m_name;
     QVariantMap m_to;
index 5616c7f..fd49523 100644 (file)
@@ -858,7 +858,7 @@ void UltraParticle::prepareNextFrame()
 
     //Advance State
     if(m_spriteEngine){//perfLevel == Sprites?
-        m_material->animcount = m_spriteEngine->stateCount();
+        m_material->animcount = m_spriteEngine->spriteCount();
         UltraVertices *particles = (UltraVertices *) m_node->geometry()->vertexData();
         m_spriteEngine->updateSprites(timeStamp);
         for(int i=0; i<m_count; i++){
@@ -867,8 +867,8 @@ void UltraParticle::prepareNextFrame()
             if(curIdx != p.v1.animIdx){
                 p.v1.animIdx = p.v2.animIdx = p.v3.animIdx = p.v4.animIdx = curIdx;
                 p.v1.animT = p.v2.animT = p.v3.animT = p.v4.animT = m_spriteEngine->spriteStart(i)/1000.0;
-                p.v1.frameCount = p.v2.frameCount = p.v3.frameCount = p.v4.frameCount = m_spriteEngine->state(curIdx)->frames();
-                p.v1.frameDuration = p.v2.frameDuration = p.v3.frameDuration = p.v4.frameDuration = m_spriteEngine->state(curIdx)->duration();
+                p.v1.frameCount = p.v2.frameCount = p.v3.frameCount = p.v4.frameCount = m_spriteEngine->spriteFrames(i);
+                p.v1.frameDuration = p.v2.frameDuration = p.v3.frameDuration = p.v4.frameDuration = m_spriteEngine->spriteDuration(i);
             }
         }
     }else{
@@ -947,10 +947,9 @@ void UltraParticle::load(ParticleData *d)
             p->v1->animT = p->v2->animT = p->v3->animT = p->v4->animT = p->v1->t;
             p->v1->animIdx = p->v2->animIdx = p->v3->animIdx = p->v4->animIdx = 0;
             if(m_spriteEngine){
-                SpriteState* state = m_spriteEngine->state(0);
-                p->v1->frameCount = p->v2->frameCount = p->v3->frameCount = p->v4->frameCount = state->frames();
-                p->v1->frameDuration = p->v2->frameDuration = p->v3->frameDuration = p->v4->frameDuration = state->duration();
                 m_spriteEngine->startSprite(pos);
+                p->v1->frameCount = p->v2->frameCount = p->v3->frameCount = p->v4->frameCount = m_spriteEngine->spriteFrames(pos);
+                p->v1->frameDuration = p->v2->frameDuration = p->v3->frameDuration = p->v4->frameDuration = m_spriteEngine->spriteDuration(pos);
             }else{
                 p->v1->frameCount = p->v2->frameCount = p->v3->frameCount = p->v4->frameCount = 1;
                 p->v1->frameDuration = p->v2->frameDuration = p->v3->frameDuration = p->v4->frameDuration = 9999;