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
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)
int frameHeight = 0;
int frameWidth = 0;
m_maxFrames = 0;
+ m_imageStateCount = 0;
int maxSize;
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize);
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;
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();
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);
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);
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
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;
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();
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();
}
}
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);
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();
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);
}
}
}
SpriteState::SpriteState(QObject *parent) :
QObject(parent)
+ , m_generatedCount(0)
+ , m_framesPerRow(0)
, m_frames(1)
+ , m_frameHeight(0)
+ , m_frameWidth(0)
, m_duration(1000)
{
}
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)
return m_frames;
}
+ int frameHeight() const
+ {
+ return m_frameHeight;
+ }
+
+ int frameWidth() const
+ {
+ return m_frameWidth;
+ }
+
int duration() const
{
return m_duration;
void framesChanged(int arg);
+ void frameHeightChanged(int arg);
+
+ void frameWidthChanged(int arg);
+
void durationChanged(int arg);
void nameChanged(QString arg);
}
}
+ 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) {
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;
//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++){
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{
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;