diff options
Diffstat (limited to 'src/quick/items/qquickspriteimage.cpp')
-rw-r--r-- | src/quick/items/qquickspriteimage.cpp | 455 |
1 files changed, 455 insertions, 0 deletions
diff --git a/src/quick/items/qquickspriteimage.cpp b/src/quick/items/qquickspriteimage.cpp new file mode 100644 index 0000000000..1da46f2ae8 --- /dev/null +++ b/src/quick/items/qquickspriteimage.cpp @@ -0,0 +1,455 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation ([email protected]) +** +** This file is part of the Declarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickspriteimage_p.h" +#include "qquicksprite_p.h" +#include "qquickspriteengine_p.h" +#include <QtQuick/private/qsgcontext_p.h> +#include <private/qsgadaptationlayer_p.h> +#include <QtQuick/qsgnode.h> +#include <QtQuick/qsgengine.h> +#include <QtQuick/qsgtexturematerial.h> +#include <QtQuick/qsgtexture.h> +#include <QtQuick/qquickcanvas.h> +#include <QFile> +#include <cmath> +#include <qmath.h> +#include <QDebug> + +QT_BEGIN_NAMESPACE + +static const char vertexShaderCode[] = + "attribute highp vec2 vTex;\n" + "uniform highp vec4 animData;// interpolate(bool), duration, frameCount (this anim), timestamp (this anim)\n" + "uniform highp vec4 animPos;//sheet x,y, width/height of this anim\n" + "uniform highp vec4 animSheetSize; //width/height of whole sheet, width/height of element\n" + "\n" + "uniform highp mat4 qt_Matrix;\n" + "uniform highp float timestamp;\n" + "\n" + "varying highp vec4 fTexS;\n" + "varying lowp float progress;\n" + "\n" + "\n" + "void main() {\n" + " //Calculate frame location in texture\n" + " highp float frameIndex = mod((((timestamp - animData.w)*1000.)/animData.y),animData.z);\n" + " progress = mod((timestamp - animData.w)*1000., animData.y) / animData.y;\n" + "\n" + " frameIndex = floor(frameIndex);\n" + " fTexS.xy = vec2(((frameIndex + vTex.x) * animPos.z / animSheetSize.x), ((animPos.y + vTex.y * animPos.w) / animSheetSize.y));\n" + "\n" + " //Next frame is also passed, for interpolation\n" + " //### Should the next anim be precalculated to allow for interpolation there?\n" + " if (animData.x == 1.0 && frameIndex != animData.z - 1.)//Can't do it for the last frame though, this anim may not loop\n" + " frameIndex = mod(frameIndex+1., animData.z);\n" + " fTexS.zw = vec2(((frameIndex + vTex.x) * animPos.z / animSheetSize.x), ((animPos.y + vTex.y * animPos.w) / animSheetSize.y));\n" + "\n" + " gl_Position = qt_Matrix * vec4(animSheetSize.z * vTex.x, animSheetSize.w * vTex.y, 0, 1);\n" + "}\n"; + +static const char fragmentShaderCode[] = + "uniform sampler2D texture;\n" + "uniform lowp float qt_Opacity;\n" + "\n" + "varying highp vec4 fTexS;\n" + "varying lowp float progress;\n" + "\n" + "void main() {\n" + " gl_FragColor = mix(texture2D(texture, fTexS.xy), texture2D(texture, fTexS.zw), progress) * qt_Opacity;\n" + "}\n"; + +class QQuickSpriteMaterial : public QSGMaterial +{ +public: + QQuickSpriteMaterial(); + virtual ~QQuickSpriteMaterial(); + 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 QQuickSpriteMaterial *>(other); + } + + QSGTexture *texture; + + qreal timestamp; + float interpolate; + float frameDuration; + float frameCount; + float animT; + float animX; + float animY; + float animWidth; + float animHeight; + float sheetWidth; + float sheetHeight; + float elementWidth; + float elementHeight; +}; + +QQuickSpriteMaterial::QQuickSpriteMaterial() + : timestamp(0) + , interpolate(1.0f) + , frameDuration(1.0f) + , frameCount(1.0f) + , animT(0.0f) + , animX(0.0f) + , animY(0.0f) + , animWidth(1.0f) + , animHeight(1.0f) + , sheetWidth(1.0f) + , sheetHeight(1.0f) + , elementWidth(1.0f) + , elementHeight(1.0f) +{ + setFlag(Blending, true); +} + +QQuickSpriteMaterial::~QQuickSpriteMaterial() +{ + delete texture; +} + +class SpriteMaterialData : public QSGMaterialShader +{ +public: + SpriteMaterialData(const char * /* vertexFile */ = 0, const char * /* fragmentFile */ = 0) + { + } + + 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 *) + { + QQuickSpriteMaterial *m = static_cast<QQuickSpriteMaterial *>(newEffect); + m->texture->bind(); + + program()->setUniformValue(m_opacity_id, state.opacity()); + program()->setUniformValue(m_timestamp_id, (float) m->timestamp); + program()->setUniformValue(m_animData_id, m->interpolate, m->frameDuration, m->frameCount, m->animT); + program()->setUniformValue(m_animPos_id, m->animX, m->animY, m->animWidth, m->animHeight); + program()->setUniformValue(m_animSheetSize_id, m->sheetWidth, m->sheetHeight, m->elementWidth, m->elementHeight); + + if (state.isMatrixDirty()) + program()->setUniformValue(m_matrix_id, state.combinedMatrix()); + } + + virtual void initialize() { + m_matrix_id = program()->uniformLocation("qt_Matrix"); + m_opacity_id = program()->uniformLocation("qt_Opacity"); + m_timestamp_id = program()->uniformLocation("timestamp"); + m_animData_id = program()->uniformLocation("animData"); + m_animPos_id = program()->uniformLocation("animPos"); + m_animSheetSize_id = program()->uniformLocation("animSheetSize"); + } + + virtual const char *vertexShader() const { return vertexShaderCode; } + virtual const char *fragmentShader() const { return fragmentShaderCode; } + + virtual char const *const *attributeNames() const { + static const char *attr[] = { + "vTex", + 0 + }; + return attr; + } + + int m_matrix_id; + int m_opacity_id; + int m_timestamp_id; + int m_animData_id; + int m_animPos_id; + int m_animSheetSize_id; + + static float chunkOfBytes[1024]; +}; + +float SpriteMaterialData::chunkOfBytes[1024]; + +QSGMaterialShader *QQuickSpriteMaterial::createShader() const +{ + return new SpriteMaterialData; +} + +struct SpriteVertex { + float tx; + float ty; +}; + +struct SpriteVertices { + SpriteVertex v1; + SpriteVertex v2; + SpriteVertex v3; + SpriteVertex v4; +}; + +/*! + \qmlclass SpriteImage QQuickSpriteImage + \inqmlmodule QtQuick 2 + \inherits Item + \brief The SpriteImage element draws a sprite animation + +*/ +/*! + \qmlproperty bool QtQuick2::SpriteImage::running + + Whether the sprite is animating or not. + + Default is true +*/ +/*! + \qmlproperty bool QtQuick2::SpriteImage::interpolate + + If true, interpolation will occur between sprite frames to make the + animation appear smoother. + + Default is true. +*/ +/*! + \qmlproperty string QtQuick2::SpriteImage::goalState + + The name of the Sprite which the animation should move to. + + Sprite states have defined durations and transitions between them, setting goalState + will cause it to disregard any path weightings (including 0) and head down the path + which will reach the goalState quickest (fewest animations). It will pass through + intermediate states on that path, and animate them for their duration. + + If it is possible to return to the goalState from the starting point of the goalState + it will continue to do so until goalState is set to "" or an unreachable state. +*/ +/*! \qmlmethod void QtQuick2::SpriteImage::jumpTo(string sprite) + + This function causes the sprite to jump to the specified state immediately, intermediate + states are not played. +*/ +/*! + \qmlproperty list<Sprite> QtQuick2::SpriteImage::sprites + + The sprite or sprites to draw. Sprites will be scaled to the size of this element. +*/ + +//TODO: Implicitly size element to size of first sprite? +QQuickSpriteImage::QQuickSpriteImage(QQuickItem *parent) : + QQuickItem(parent) + , m_node(0) + , m_material(0) + , m_spriteEngine(0) + , m_pleaseReset(false) + , m_running(true) + , m_interpolate(true) +{ + setFlag(ItemHasContents); + connect(this, SIGNAL(runningChanged(bool)), + this, SLOT(update())); +} + +void QQuickSpriteImage::jumpTo(const QString &sprite) +{ + if (!m_spriteEngine) + return; + m_spriteEngine->setGoal(m_spriteEngine->stateIndex(sprite), 0, true); +} + +void QQuickSpriteImage::setGoalState(const QString &sprite) +{ + if (m_goalState != sprite){ + m_goalState = sprite; + emit goalStateChanged(sprite); + m_spriteEngine->setGoal(m_spriteEngine->stateIndex(sprite)); + } +} + +QDeclarativeListProperty<QQuickSprite> QQuickSpriteImage::sprites() +{ + return QDeclarativeListProperty<QQuickSprite>(this, &m_sprites, spriteAppend, spriteCount, spriteAt, spriteClear); +} + +void QQuickSpriteImage::createEngine() +{ + //TODO: delay until component complete + if (m_spriteEngine) + delete m_spriteEngine; + if (m_sprites.count()) + m_spriteEngine = new QQuickSpriteEngine(m_sprites, this); + else + m_spriteEngine = 0; + reset(); +} + +static QSGGeometry::Attribute SpriteImage_Attributes[] = { + QSGGeometry::Attribute::create(0, 2, GL_FLOAT), // tex +}; + +static QSGGeometry::AttributeSet SpriteImage_AttributeSet = +{ + 1, // Attribute Count + 2 * sizeof(float), + SpriteImage_Attributes +}; + +QSGGeometryNode* QQuickSpriteImage::buildNode() +{ + if (!m_spriteEngine) { + qWarning() << "SpriteImage: No sprite engine..."; + return 0; + } + + m_material = new QQuickSpriteMaterial(); + + QImage image = m_spriteEngine->assembledImage(); + if (image.isNull()) + return 0; + m_material->texture = canvas()->createTextureFromImage(image); + m_material->texture->setFiltering(QSGTexture::Linear); + m_spriteEngine->start(0); + m_material->interpolate = m_interpolate ? 1.0 : 0.0; + m_material->frameCount = m_spriteEngine->spriteFrames(); + m_material->frameDuration = m_spriteEngine->spriteDuration(); + m_material->animT = 0; + m_material->animX = m_spriteEngine->spriteX(); + m_material->animY = m_spriteEngine->spriteY(); + m_material->animWidth = m_spriteEngine->spriteWidth(); + m_material->animHeight = m_spriteEngine->spriteHeight(); + m_material->sheetWidth = image.width(); + m_material->sheetHeight = image.height(); + m_material->elementWidth = width(); + m_material->elementHeight = height(); + + int vCount = 4; + int iCount = 6; + QSGGeometry *g = new QSGGeometry(SpriteImage_AttributeSet, vCount, iCount); + g->setDrawingMode(GL_TRIANGLES); + + SpriteVertices *p = (SpriteVertices *) g->vertexData(); + + 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); + m_node->setFlag(QSGGeometryNode::OwnsMaterial); + return m_node; +} + +void QQuickSpriteImage::reset() +{ + m_pleaseReset = true; +} + +QSGNode *QQuickSpriteImage::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 QQuickSpriteImage::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->elementHeight = height(); + m_material->elementWidth = width(); + m_material->interpolate = m_interpolate; + + //Advance State + m_spriteEngine->updateSprites(timeInt); + int curY = m_spriteEngine->spriteY(); + if (curY != m_material->animY){ + m_material->animT = m_spriteEngine->spriteStart()/1000.0; + m_material->frameCount = m_spriteEngine->spriteFrames(); + m_material->frameDuration = m_spriteEngine->spriteDuration(); + m_material->animX = m_spriteEngine->spriteX(); + m_material->animY = m_spriteEngine->spriteY(); + m_material->animWidth = m_spriteEngine->spriteWidth(); + m_material->animHeight = m_spriteEngine->spriteHeight(); + } +} + +QT_END_NAMESPACE |