diff options
Diffstat (limited to 'src/declarative/items')
-rw-r--r-- | src/declarative/items/items.pri | 6 | ||||
-rw-r--r-- | src/declarative/items/qsgitemsmodule.cpp | 4 | ||||
-rw-r--r-- | src/declarative/items/qsgsprite.cpp | 57 | ||||
-rw-r--r-- | src/declarative/items/qsgsprite_p.h | 231 | ||||
-rw-r--r-- | src/declarative/items/qsgspriteengine.cpp | 437 | ||||
-rw-r--r-- | src/declarative/items/qsgspriteengine_p.h | 161 | ||||
-rw-r--r-- | src/declarative/items/qsgspriteimage.cpp | 353 | ||||
-rw-r--r-- | src/declarative/items/qsgspriteimage_p.h | 114 |
8 files changed, 1363 insertions, 0 deletions
diff --git a/src/declarative/items/items.pri b/src/declarative/items/items.pri index d6942973cd..f29a82e77e 100644 --- a/src/declarative/items/items.pri +++ b/src/declarative/items/items.pri @@ -61,6 +61,9 @@ HEADERS += \ $$PWD/qsgcanvasitem_p.h \ $$PWD/qsgcontext2d_p.h \ $$PWD/qsgcontext2d_p_p.h \ + $$PWD/qsgspriteengine_p.h \ + $$PWD/qsgsprite_p.h \ + $$PWD/qsgspriteimage_p.h \ SOURCES += \ $$PWD/qsgevents.cpp \ @@ -100,6 +103,9 @@ SOURCES += \ $$PWD/qsgimplicitsizeitem.cpp \ $$PWD/qsgcanvasitem.cpp \ $$PWD/qsgcontext2d.cpp \ + $$PWD/qsgspriteengine.cpp \ + $$PWD/qsgsprite.cpp \ + $$PWD/qsgspriteimage.cpp \ SOURCES += \ $$PWD/qsgshadereffectitem.cpp \ diff --git a/src/declarative/items/qsgitemsmodule.cpp b/src/declarative/items/qsgitemsmodule.cpp index 6ea20bb38b..a29776fc68 100644 --- a/src/declarative/items/qsgitemsmodule.cpp +++ b/src/declarative/items/qsgitemsmodule.cpp @@ -75,6 +75,8 @@ //#include "private/qsgpincharea_p.h" #include "qsgcanvasitem_p.h" #include "qsgcontext2d_p.h" +#include "qsgsprite_p.h" +#include "qsgspriteimage_p.h" static QDeclarativePrivate::AutoParentResult qsgitem_autoParent(QObject *obj, QObject *parent) { @@ -179,6 +181,8 @@ static void qt_sgitems_defineModule(const char *uri, int major, int minor) qmlRegisterType<QSGContext2D>(); qmlRegisterType<QSGCanvasGradient>(); + qmlRegisterType<QSGSprite>("QtQuick", 2, 0, "Sprite"); + qmlRegisterType<QSGSpriteImage>("QtQuick", 2, 0, "SpriteImage"); qmlRegisterType<QSGParentChange>(uri, major, minor,"ParentChange"); qmlRegisterType<QSGAnchorChanges>(uri, major, minor,"AnchorChanges"); diff --git a/src/declarative/items/qsgsprite.cpp b/src/declarative/items/qsgsprite.cpp new file mode 100644 index 0000000000..694976a540 --- /dev/null +++ b/src/declarative/items/qsgsprite.cpp @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Declarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgsprite_p.h" + +QT_BEGIN_NAMESPACE + +QSGSprite::QSGSprite(QObject *parent) : + QObject(parent) + , m_generatedCount(0) + , m_framesPerRow(0) + , m_frames(1) + , m_frameHeight(0) + , m_frameWidth(0) + , m_duration(1000) +{ +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgsprite_p.h b/src/declarative/items/qsgsprite_p.h new file mode 100644 index 0000000000..652a4cd482 --- /dev/null +++ b/src/declarative/items/qsgsprite_p.h @@ -0,0 +1,231 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Declarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SPRITESTATE_H +#define SPRITESTATE_H + +#include <QObject> +#include <QUrl> +#include <QVariantMap> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + + +class QSGSprite : public QObject +{ + Q_OBJECT + 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) + Q_PROPERTY(QVariantMap to READ to WRITE setTo NOTIFY toChanged) + +public: + explicit QSGSprite(QObject *parent = 0); + + QUrl source() const + { + return m_source; + } + + int frames() const + { + return m_frames; + } + + int frameHeight() const + { + return m_frameHeight; + } + + int frameWidth() const + { + return m_frameWidth; + } + + int duration() const + { + return m_duration; + } + + QString name() const + { + return m_name; + } + + QVariantMap to() const + { + return m_to; + } + + qreal speedModifer() const + { + return m_speedModifier; + } + + int durationVariance() const + { + return m_durationVariance; + } + +signals: + + void sourceChanged(QUrl arg); + + void framesChanged(int arg); + + void frameHeightChanged(int arg); + + void frameWidthChanged(int arg); + + void durationChanged(int arg); + + void nameChanged(QString arg); + + void toChanged(QVariantMap arg); + + void speedModifierChanged(qreal arg); + + void durationVarianceChanged(int arg); + +public slots: + + void setSource(QUrl arg) + { + if (m_source != arg) { + m_source = arg; + emit sourceChanged(arg); + } + } + + void setFrames(int arg) + { + if (m_frames != arg) { + m_frames = arg; + emit framesChanged(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) { + m_duration = arg; + emit durationChanged(arg); + } + } + + void setName(QString arg) + { + if (m_name != arg) { + m_name = arg; + emit nameChanged(arg); + } + } + + void setTo(QVariantMap arg) + { + if (m_to != arg) { + m_to = arg; + emit toChanged(arg); + } + } + + void setSpeedModifier(qreal arg) + { + if (m_speedModifier != arg) { + m_speedModifier = arg; + emit speedModifierChanged(arg); + } + } + + void setDurationVariance(int arg) + { + if (m_durationVariance != arg) { + m_durationVariance = arg; + emit durationVarianceChanged(arg); + } + } + +private: + friend class QSGImageParticle; + friend class QSGSpriteEngine; + 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; + qreal m_speedModifier; + int m_durationVariance; +}; + +QT_END_NAMESPACE +QT_END_HEADER +#endif // SPRITESTATE_H diff --git a/src/declarative/items/qsgspriteengine.cpp b/src/declarative/items/qsgspriteengine.cpp new file mode 100644 index 0000000000..27de0d94f6 --- /dev/null +++ b/src/declarative/items/qsgspriteengine.cpp @@ -0,0 +1,437 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Declarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgspriteengine_p.h" +#include "qsgsprite_p.h" +#include <QDebug> +#include <QPainter> +#include <QSet> +#include <QtOpenGL> + +QT_BEGIN_NAMESPACE + +QSGSpriteEngine::QSGSpriteEngine(QObject *parent) : + QObject(parent), m_timeOffset(0) +{ + //Default size 1 + setCount(1); + m_advanceTime.start(); +} + +QSGSpriteEngine::QSGSpriteEngine(QList<QSGSprite*> states, QObject *parent) : + QObject(parent), m_states(states), m_timeOffset(0) +{ + //Default size 1 + setCount(1); + m_advanceTime.start(); +} + +QSGSpriteEngine::~QSGSpriteEngine() +{ +} + +int QSGSpriteEngine::maxFrames() +{ + 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 QSGSpriteEngine::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 QSGSpriteEngine::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 QSGSpriteEngine::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 QSGSpriteEngine::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 QSGSpriteEngine::spriteCount()//TODO: Actually image state count, need to rename these things to make sense together +{ + return m_imageStateCount; +} + +void QSGSpriteEngine::setGoal(int state, int sprite, bool jump) +{ + if(sprite >= m_sprites.count() || state >= m_states.count()) + return; + if(!jump){ + m_goals[sprite] = state; + return; + } + + if(m_sprites[sprite] == state) + return;//Already there + m_sprites[sprite] = state; + m_goals[sprite] = -1; + restartSprite(sprite); + return; +} + +QImage QSGSpriteEngine::assembledImage() +{ + int frameHeight = 0; + int frameWidth = 0; + m_maxFrames = 0; + m_imageStateCount = 0; + + int maxSize; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize); + + foreach(QSGSprite* state, m_states){ + if(state->frames() > m_maxFrames) + m_maxFrames = state->frames(); + + QImage img(state->source().toLocalFile()); + if (img.isNull()) { + qWarning() << "SpriteEngine: loading image failed..." << state->source().toLocalFile(); + 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(imgWidth != frameWidth){ + qWarning() << "SpriteEngine: Irregular frame width..." << state->source().toLocalFile(); + return QImage(); + } + }else{ + frameWidth = imgWidth; + } + + 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->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++; + } + } + + //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(QSGSprite* state, m_states){ + QImage img(state->source().toLocalFile()); + 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; +} + +void QSGSpriteEngine::setCount(int c) +{ + m_sprites.resize(c); + m_goals.resize(c); + m_startTimes.resize(c); +} + +void QSGSpriteEngine::startSprite(int index) +{ + if(index >= m_sprites.count()) + return; + m_sprites[index] = 0; + m_goals[index] = -1; + restartSprite(index); +} + +void QSGSpriteEngine::restartSprite(int index) +{ + m_startTimes[index] = m_timeOffset + m_advanceTime.elapsed(); + int time = m_states[m_sprites[index]]->duration() * m_states[m_sprites[index]]->frames() + m_startTimes[index]; + for(int i=0; i<m_stateUpdates.count(); i++) + m_stateUpdates[i].second.removeAll(index); + addToUpdateList(time, index); +} + +uint QSGSpriteEngine::updateSprites(uint time) +{ + //Sprite State Update; + while(!m_stateUpdates.isEmpty() && time >= m_stateUpdates.first().first){ + foreach(int idx, m_stateUpdates.first().second){ + if(idx >= m_sprites.count()) + continue;//TODO: Proper fix(because this does happen and I'm just ignoring it) + int stateIdx = m_sprites[idx]; + int nextIdx = -1; + int goalPath = goalSeek(stateIdx, idx); + if(goalPath == -1){//Random + qreal r =(qreal) qrand() / (qreal) RAND_MAX; + qreal total = 0.0; + for(QVariantMap::const_iterator iter=m_states[stateIdx]->m_to.constBegin(); + iter!=m_states[stateIdx]->m_to.constEnd(); iter++) + total += (*iter).toReal(); + r*=total; + for(QVariantMap::const_iterator iter= m_states[stateIdx]->m_to.constBegin(); + iter!=m_states[stateIdx]->m_to.constEnd(); iter++){ + if(r < (*iter).toReal()){ + bool superBreak = false; + for(int i=0; i<m_states.count(); i++){ + if(m_states[i]->name() == iter.key()){ + nextIdx = i; + superBreak = true; + break; + } + } + if(superBreak) + break; + } + r -= (*iter).toReal(); + } + }else{//Random out of shortest paths to goal + nextIdx = goalPath; + } + if(nextIdx == -1)//No to states means stay here + nextIdx = stateIdx; + + m_sprites[idx] = nextIdx; + m_startTimes[idx] = time; + //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(); + } + + m_timeOffset = time; + m_advanceTime.start(); + if(m_stateUpdates.isEmpty()) + return -1; + return m_stateUpdates.first().first; +} + +int QSGSpriteEngine::goalSeek(int curIdx, int spriteIdx, int dist) +{ + QString goalName; + if(m_goals[spriteIdx] != -1) + goalName = m_states[m_goals[spriteIdx]]->name(); + else + goalName = m_globalGoal; + if(goalName.isEmpty()) + return -1; + //TODO: caching instead of excessively redoing iterative deepening (which was chosen arbitarily anyways) + // Paraphrased - implement in an *efficient* manner + for(int i=0; i<m_states.count(); i++) + if(m_states[curIdx]->name() == goalName) + return curIdx; + if(dist < 0) + dist = m_states.count(); + QSGSprite* curState = m_states[curIdx]; + for(QVariantMap::const_iterator iter = curState->m_to.constBegin(); + iter!=curState->m_to.constEnd(); iter++){ + if(iter.key() == goalName) + for(int i=0; i<m_states.count(); i++) + if(m_states[i]->name() == goalName) + return i; + } + QSet<int> options; + for(int i=1; i<dist; i++){ + for(QVariantMap::const_iterator iter = curState->m_to.constBegin(); + iter!=curState->m_to.constEnd(); iter++){ + int option = -1; + for(int j=0; j<m_states.count(); j++)//One place that could be a lot more efficient... + if(m_states[j]->name() == iter.key()) + if(goalSeek(j, spriteIdx, i) != -1) + option = j; + if(option != -1) + options << option; + } + if(!options.isEmpty()){ + if(options.count()==1) + return *(options.begin()); + int option = -1; + qreal r =(qreal) qrand() / (qreal) RAND_MAX; + qreal total; + for(QSet<int>::const_iterator iter=options.constBegin(); + iter!=options.constEnd(); iter++) + total += curState->m_to.value(m_states[(*iter)]->name()).toReal(); + r *= total; + for(QVariantMap::const_iterator iter = curState->m_to.constBegin(); + iter!=curState->m_to.constEnd(); iter++){ + bool superContinue = true; + for(int j=0; j<m_states.count(); j++) + if(m_states[j]->name() == iter.key()) + if(options.contains(j)) + superContinue = false; + if(superContinue) + continue; + if(r < (*iter).toReal()){ + bool superBreak = false; + for(int j=0; j<m_states.count(); j++){ + if(m_states[j]->name() == iter.key()){ + option = j; + superBreak = true; + break; + } + } + if(superBreak) + break; + } + r-=(*iter).toReal(); + } + return option; + } + } + return -1; +} + +void QSGSpriteEngine::addToUpdateList(uint t, int idx) +{ + for(int i=0; i<m_stateUpdates.count(); i++){ + if(m_stateUpdates[i].first==t){ + m_stateUpdates[i].second << idx; + return; + }else if(m_stateUpdates[i].first > t){ + QList<int> tmpList; + tmpList << idx; + m_stateUpdates.insert(i, qMakePair(t, tmpList)); + return; + } + } + QList<int> tmpList; + tmpList << idx; + m_stateUpdates << qMakePair(t, tmpList); +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgspriteengine_p.h b/src/declarative/items/qsgspriteengine_p.h new file mode 100644 index 0000000000..8ab6e3a30a --- /dev/null +++ b/src/declarative/items/qsgspriteengine_p.h @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Declarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SPRITEENGINE_H +#define SPRITEENGINE_H + +#include <QObject> +#include <QVector> +#include <QTimer> +#include <QTime> +#include <QList> +#include <QDeclarativeListProperty> +#include <QImage> +#include <QPair> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGSprite; + +class QSGSpriteEngine : public QObject +{ + Q_OBJECT + //TODO: Optimize single sprite case + Q_PROPERTY(QDeclarativeListProperty<QSGSprite> sprites READ sprites) + Q_PROPERTY(QString globalGoal READ globalGoal WRITE setGlobalGoal NOTIFY globalGoalChanged) +public: + explicit QSGSpriteEngine(QObject *parent = 0); + QSGSpriteEngine(QList<QSGSprite*> sprites, QObject *parent=0); + ~QSGSpriteEngine(); + + QDeclarativeListProperty<QSGSprite> sprites() + { + return QDeclarativeListProperty<QSGSprite>(this, m_states); + } + QString globalGoal() const + { + return m_globalGoal; + } + + 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 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); + QImage assembledImage(); + + void startSprite(int index=0); + +private://Nothing outside should use this? + friend class QSGSpriteGoalAffector;//XXX: Fix interface + int stateCount() {return m_states.count();} + int stateIndex(QSGSprite* s){return m_states.indexOf(s);}//TODO: Does this need to be hidden? + QSGSprite* state(int idx){return m_states[idx];}//Used by spritegoal affector +signals: + + void globalGoalChanged(QString arg); + +public slots: + void setGlobalGoal(QString arg) + { + if (m_globalGoal != arg) { + m_globalGoal = arg; + emit globalGoalChanged(arg); + } + } + + uint updateSprites(uint time); + +private: + void restartSprite(int sprite); + void addToUpdateList(uint t, int idx); + int goalSeek(int curState, int spriteIdx, int dist=-1); + QList<QSGSprite*> m_states; + QVector<int> m_sprites;//int is the index in m_states of the current state + QVector<int> m_goals; + QVector<int> m_startTimes; + QList<QPair<uint, QList<int> > > m_stateUpdates;//### This could be done faster + + QTime m_advanceTime; + 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 +inline void spriteAppend(QDeclarativeListProperty<QSGSprite> *p, QSGSprite* s) +{ + reinterpret_cast<QList<QSGSprite *> *>(p->data)->append(s); + p->object->metaObject()->invokeMethod(p->object, "createEngine"); +} + +inline QSGSprite* spriteAt(QDeclarativeListProperty<QSGSprite> *p, int idx) +{ + return reinterpret_cast<QList<QSGSprite *> *>(p->data)->at(idx); +} + +inline void spriteClear(QDeclarativeListProperty<QSGSprite> *p) +{ + reinterpret_cast<QList<QSGSprite *> *>(p->data)->clear(); + p->object->metaObject()->invokeMethod(p->object, "createEngine"); +} + +inline int spriteCount(QDeclarativeListProperty<QSGSprite> *p) +{ + return reinterpret_cast<QList<QSGSprite *> *>(p->data)->count(); +} + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // SPRITEENGINE_H diff --git a/src/declarative/items/qsgspriteimage.cpp b/src/declarative/items/qsgspriteimage.cpp new file mode 100644 index 0000000000..8cc0dc5b76 --- /dev/null +++ b/src/declarative/items/qsgspriteimage.cpp @@ -0,0 +1,353 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Declarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgspriteimage_p.h" +#include "qsgsprite_p.h" +#include "qsgspriteengine_p.h" +#include <private/qsgcontext_p.h> +#include <private/qsgadaptationlayer_p.h> +#include <qsgnode.h> +#include <qsgengine.h> +#include <qsgtexturematerial.h> +#include <qsgtexture.h> +#include <QFile> +#include <cmath> +#include <qmath.h> +#include <QDebug> + +QT_BEGIN_NAMESPACE + +class QSGSpriteMaterial : public QSGMaterial +{ +public: + QSGSpriteMaterial(); + virtual ~QSGSpriteMaterial(); + virtual QSGMaterialType *type() const { static QSGMaterialType type; return &type; } + virtual QSGMaterialShader *createShader() const; + virtual int compare(const QSGMaterial *other) const + { + return this - static_cast<const QSGSpriteMaterial *>(other); + } + + QSGTexture *texture; + + qreal timestamp; + qreal timelength; + int framecount; + int animcount; + int width; + int height; +}; + +QSGSpriteMaterial::QSGSpriteMaterial() + : timestamp(0) + , timelength(1) + , framecount(1) + , animcount(1) + , width(0) + , height(0) +{ + setFlag(Blending, true); +} + +QSGSpriteMaterial::~QSGSpriteMaterial() +{ + delete texture; +} + +class SpriteMaterialData : public QSGMaterialShader +{ +public: + SpriteMaterialData(const char *vertexFile = 0, const char *fragmentFile = 0) + { + QFile vf(vertexFile ? vertexFile : ":defaultshaders/spriteimagevertex.shader"); + vf.open(QFile::ReadOnly); + m_vertex_code = vf.readAll(); + + QFile ff(fragmentFile ? fragmentFile : ":defaultshaders/spriteimagefragment.shader"); + ff.open(QFile::ReadOnly); + m_fragment_code = ff.readAll(); + + Q_ASSERT(!m_vertex_code.isNull()); + Q_ASSERT(!m_fragment_code.isNull()); + } + + void deactivate() { + QSGMaterialShader::deactivate(); + + for (int i=0; i<8; ++i) { + program()->setAttributeArray(i, GL_FLOAT, chunkOfBytes, 1, 0); + } + } + + virtual void updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *) + { + QSGSpriteMaterial *m = static_cast<QSGSpriteMaterial *>(newEffect); + m->texture->bind(); + + program()->setUniformValue(m_opacity_id, state.opacity()); + program()->setUniformValue(m_timestamp_id, (float) m->timestamp); + program()->setUniformValue(m_framecount_id, (float) m->framecount); + program()->setUniformValue(m_animcount_id, (float) m->animcount); + program()->setUniformValue(m_width_id, (float) m->width); + program()->setUniformValue(m_height_id, (float) m->height); + + if (state.isMatrixDirty()) + program()->setUniformValue(m_matrix_id, state.combinedMatrix()); + } + + virtual void initialize() { + m_matrix_id = program()->uniformLocation("matrix"); + m_opacity_id = program()->uniformLocation("opacity"); + m_timestamp_id = program()->uniformLocation("timestamp"); + m_framecount_id = program()->uniformLocation("framecount"); + m_animcount_id = program()->uniformLocation("animcount"); + m_width_id = program()->uniformLocation("width"); + m_height_id = program()->uniformLocation("height"); + } + + virtual const char *vertexShader() const { return m_vertex_code.constData(); } + virtual const char *fragmentShader() const { return m_fragment_code.constData(); } + + virtual char const *const *attributeNames() const { + static const char *attr[] = { + "vTex", + "vAnimData", + 0 + }; + return attr; + } + + virtual bool isColorTable() const { return false; } + + int m_matrix_id; + int m_opacity_id; + int m_timestamp_id; + int m_framecount_id; + int m_animcount_id; + int m_width_id; + int m_height_id; + + QByteArray m_vertex_code; + QByteArray m_fragment_code; + + static float chunkOfBytes[1024]; +}; +float SpriteMaterialData::chunkOfBytes[1024]; + +QSGMaterialShader *QSGSpriteMaterial::createShader() const +{ + return new SpriteMaterialData; +} + +struct SpriteVertex { + float tx; + float ty; + float animIdx; + float frameDuration; + float frameCount; + float animT; +}; + +struct SpriteVertices { + SpriteVertex v1; + SpriteVertex v2; + SpriteVertex v3; + SpriteVertex v4; +}; + +QSGSpriteImage::QSGSpriteImage(QSGItem *parent) : + QSGItem(parent) + , m_node(0) + , m_material(0) + , m_spriteEngine(0) + , m_pleaseReset(false) + , m_running(true) +{ + setFlag(ItemHasContents); + connect(this, SIGNAL(runningChanged(bool)), + this, SLOT(update())); +} + +QDeclarativeListProperty<QSGSprite> QSGSpriteImage::sprites() +{ + return QDeclarativeListProperty<QSGSprite>(this, &m_sprites, spriteAppend, spriteCount, spriteAt, spriteClear); +} + +void QSGSpriteImage::createEngine() +{ + //TODO: delay until component complete + if(m_spriteEngine) + delete m_spriteEngine; + if(m_sprites.count()) + m_spriteEngine = new QSGSpriteEngine(m_sprites, this); + else + m_spriteEngine = 0; + reset(); +} + +static QSGGeometry::Attribute SpriteImage_Attributes[] = { + { 0, 2, GL_FLOAT }, // tex + { 1, 4, GL_FLOAT } // animData +}; + +static QSGGeometry::AttributeSet SpriteImage_AttributeSet = +{ + 2, // Attribute Count + (4 + 2) * sizeof(float), + SpriteImage_Attributes +}; + +QSGGeometryNode* QSGSpriteImage::buildNode() +{ + if (!m_spriteEngine) { + qWarning() << "SpriteImage: No sprite engine..."; + return 0; + } + + if (m_material) { + delete m_material; + m_material = 0; + } + + m_material = new QSGSpriteMaterial(); + + QImage image = m_spriteEngine->assembledImage(); + if(image.isNull()) + return 0; + m_material->texture = sceneGraphEngine()->createTextureFromImage(image); + m_material->texture->setFiltering(QSGTexture::Linear); + m_material->framecount = m_spriteEngine->maxFrames(); + + int vCount = 4; + int iCount = 6; + QSGGeometry *g = new QSGGeometry(SpriteImage_AttributeSet, vCount, iCount); + 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; + 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; + + p->v2.tx = 1.0; + p->v2.ty = 0; + + p->v3.tx = 0; + p->v3.ty = 1.0; + + p->v4.tx = 1.0; + p->v4.ty = 1.0; + + quint16 *indices = g->indexDataAsUShort(); + indices[0] = 0; + indices[1] = 1; + indices[2] = 2; + indices[3] = 1; + indices[4] = 3; + indices[5] = 2; + + + m_timestamp.start(); + m_node = new QSGGeometryNode(); + m_node->setGeometry(g); + m_node->setMaterial(m_material); + return m_node; +} + +void QSGSpriteImage::reset() +{ + m_pleaseReset = true; +} + +QSGNode *QSGSpriteImage::updatePaintNode(QSGNode *, UpdatePaintNodeData *) +{ + if(m_pleaseReset){ + delete m_node; + delete m_material; + + m_node = 0; + m_material = 0; + m_pleaseReset = false; + } + + prepareNextFrame(); + + if(m_running){ + update(); + if (m_node) + m_node->markDirty(QSGNode::DirtyMaterial); + } + + return m_node; +} + +void QSGSpriteImage::prepareNextFrame() +{ + if (m_node == 0) + m_node = buildNode(); + if (m_node == 0) //error creating node + return; + + uint timeInt = m_timestamp.elapsed(); + qreal time = timeInt / 1000.; + m_material->timestamp = time; + m_material->animcount = m_spriteEngine->spriteCount(); + m_material->height = height(); + m_material->width = width(); + + //Advance State + SpriteVertices *p = (SpriteVertices *) m_node->geometry()->vertexData(); + m_spriteEngine->updateSprites(timeInt); + int curIdx = m_spriteEngine->spriteState(); + 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->spriteFrames(); + p->v1.frameDuration = p->v2.frameDuration = p->v3.frameDuration = p->v4.frameDuration = m_spriteEngine->spriteDuration(); + } +} + +QT_END_NAMESPACE diff --git a/src/declarative/items/qsgspriteimage_p.h b/src/declarative/items/qsgspriteimage_p.h new file mode 100644 index 0000000000..f03fd869f0 --- /dev/null +++ b/src/declarative/items/qsgspriteimage_p.h @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Declarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SPRITEIMAGE_H +#define SPRITEIMAGE_H + +#include <QSGItem> +#include <QTime> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Declarative) + +class QSGContext; +class QSGSprite; +class QSGSpriteEngine; +class QSGGeometryNode; +class QSGSpriteMaterial; +class QSGSpriteImage : public QSGItem +{ + Q_OBJECT + Q_PROPERTY(bool running READ running WRITE setRunning NOTIFY runningChanged) + //###try to share similar spriteEngines for less overhead? + Q_PROPERTY(QDeclarativeListProperty<QSGSprite> sprites READ sprites) + Q_CLASSINFO("DefaultProperty", "sprites") + +public: + explicit QSGSpriteImage(QSGItem *parent = 0); + + QDeclarativeListProperty<QSGSprite> sprites(); + + bool running() const + { + return m_running; + } + +signals: + + + void runningChanged(bool arg); + +public slots: + +void setRunning(bool arg) +{ + if (m_running != arg) { + m_running = arg; + emit runningChanged(arg); + } +} + +private slots: + void createEngine(); +protected: + void reset(); + QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *); +private: + void prepareNextFrame(); + QSGGeometryNode* buildNode(); + QSGGeometryNode *m_node; + QSGSpriteMaterial *m_material; + QList<QSGSprite*> m_sprites; + QSGSpriteEngine* m_spriteEngine; + QTime m_timestamp; + int m_maxFrames; + bool m_pleaseReset; + bool m_running; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // SPRITEIMAGE_H |