From: Alan Alpert Date: Fri, 13 Jan 2012 03:54:29 +0000 (+1000) Subject: Per-frame Sprites patch one X-Git-Tag: qt-v5.0.0-alpha1~623 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=379e388568f5d5408b4be13ce6a1faba0b74be6b;p=profile%2Fivi%2Fqtdeclarative.git Per-frame Sprites patch one To allow for sprites to be advanced by the rendering framerate, two minor redesigns were needed. A) Sprite texture location is now calculated on the CPU and passed to the GPU per frame. B) Stochastic State engine now supports states that do not advance on a timer, and states can be advanced manually. This patch implements B and A for ImageParticle. A for SpriteImage will be done in a separate patch. Task-number: QTBUG-22236 Change-Id: If1c54a6a03fa48b95bb1e672283292859656457b Reviewed-by: Alan Alpert --- diff --git a/src/quick/items/qquicksprite.cpp b/src/quick/items/qquicksprite.cpp index 61e443d..4c7dafa 100644 --- a/src/quick/items/qquicksprite.cpp +++ b/src/quick/items/qquicksprite.cpp @@ -53,12 +53,13 @@ QT_BEGIN_NAMESPACE /*! \qmlproperty int QtQuick2::Sprite::duration - Time between frames. + Time between frames. Use -1 to indicate one sprite frame per rendered frame. */ /*! \qmlproperty int QtQuick2::Sprite::durationVariation - The time between frames can vary by up to this amount. + The time between frames can vary by up to this amount. Variation will never decrease the time + between frames to less than 0. Default is 0. */ @@ -105,17 +106,6 @@ QT_BEGIN_NAMESPACE If frameHeight and frameWidth are not specified, it is assumed to be a single long row of square frames. Otherwise, it can be multiple contiguous rows or rectangluar frames, when one row runs out the next will be used. */ - Q_PROPERTY(int duration READ duration WRITE setDuration NOTIFY durationChanged) - Q_PROPERTY(int durationVariation READ durationVariance WRITE setDurationVariance NOTIFY durationVarianceChanged) - Q_PROPERTY(QVariantMap to READ to WRITE setTo NOTIFY toChanged) - Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) - Q_PROPERTY(qreal speedModifiesDuration READ speedModifer WRITE setSpeedModifier NOTIFY speedModifierChanged) - Q_PROPERTY(int frames READ frames WRITE setFrames NOTIFY framesChanged) - Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) - //If frame height or width is not specified, it is assumed to be a single long row of square 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) QQuickSprite::QQuickSprite(QObject *parent) : QQuickStochasticState(parent) diff --git a/src/quick/items/qquickspriteengine.cpp b/src/quick/items/qquickspriteengine.cpp index 5a2d831..b48fc71 100644 --- a/src/quick/items/qquickspriteengine.cpp +++ b/src/quick/items/qquickspriteengine.cpp @@ -339,67 +339,73 @@ void QQuickStochasticEngine::restart(int index) int time = m_duration[index] * m_states[m_things[index]]->frames() + m_startTimes[index]; for (int i=0; i 0) + addToUpdateList(time, index); } -uint QQuickStochasticEngine::updateSprites(uint time)//### would returning a list of changed idxs be faster than signals? +// Doesn't remove from update list. If you want to cancel pending timed updates, call restart() first. +// Usually sprites are either manually advanced or in the list, and mixing is not recommended anyways. +void QQuickStochasticEngine::advance(int idx) { - //Sprite State Update; - QSet changedIndexes; - while (!m_stateUpdates.isEmpty() && time >= m_stateUpdates.first().first){ - foreach (int idx, m_stateUpdates.first().second){ - if (idx >= m_things.count()) - continue;//TODO: Proper fix(because this does happen and I'm just ignoring it) - int stateIdx = m_things[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; iname() == iter.key()){ - nextIdx = i; - superBreak = true; - break; - } - } - if (superBreak) - break; + if (idx >= m_things.count()) + return;//TODO: Proper fix(because this has happened and I just ignored it) + int stateIdx = m_things[idx]; + int nextIdx = nextState(stateIdx,idx); + m_things[idx] = nextIdx; + m_duration[idx] = m_states[nextIdx]->variedDuration(); + m_startTimes[idx] = m_timeOffset;//This will be the last time updateSprites was called + emit m_states[nextIdx]->entered(); + emit stateChanged(idx); //TODO: emit this when a psuedostate changes too(but manually in SpriteEngine) + if (m_duration[idx] >= 0) + addToUpdateList((m_duration[idx] * m_states[nextIdx]->frames()) + m_startTimes[idx], idx); +} + +int QQuickStochasticEngine::nextState(int curState, int curThing) +{ + int nextIdx = -1; + int goalPath = goalSeek(curState, curThing); + if (goalPath == -1){//Random + qreal r =(qreal) qrand() / (qreal) RAND_MAX; + qreal total = 0.0; + for (QVariantMap::const_iterator iter=m_states[curState]->m_to.constBegin(); + iter!=m_states[curState]->m_to.constEnd(); iter++) + total += (*iter).toReal(); + r*=total; + for (QVariantMap::const_iterator iter= m_states[curState]->m_to.constBegin(); + iter!=m_states[curState]->m_to.constEnd(); iter++){ + if (r < (*iter).toReal()){ + bool superBreak = false; + for (int i=0; iname() == iter.key()){ + nextIdx = i; + superBreak = true; + 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_things[idx] = nextIdx; - m_duration[idx] = m_states[nextIdx]->variedDuration(); - m_startTimes[idx] = time; - if (nextIdx != stateIdx){ - changedIndexes << idx; - emit m_states[nextIdx]->entered(); + if (superBreak) + break; } - addToUpdateList((m_duration[idx] * m_states[nextIdx]->frames()) + time, idx); + r -= (*iter).toReal(); } - m_stateUpdates.pop_front(); + }else{//Random out of shortest paths to goal + nextIdx = goalPath; } + if (nextIdx == -1)//No 'to' states means stay here + nextIdx = curState; + return nextIdx; +} +uint QQuickStochasticEngine::updateSprites(uint time)//### would returning a list of changed idxs be faster than signals? +{ + //Sprite State Update; m_timeOffset = time; - m_advanceTime.start(); - //TODO: emit this when a psuedostate changes too - foreach (int idx, changedIndexes){//Batched so that update list doesn't change midway - emit stateChanged(idx); + while (!m_stateUpdates.isEmpty() && time >= m_stateUpdates.first().first){ + foreach (int idx, m_stateUpdates.first().second) + advance(idx); + m_stateUpdates.pop_front(); } + + m_advanceTime.start(); if (m_stateUpdates.isEmpty()) return -1; return m_stateUpdates.first().first; diff --git a/src/quick/items/qquickspriteengine_p.h b/src/quick/items/qquickspriteengine_p.h index 050e0c2..926bb68 100644 --- a/src/quick/items/qquickspriteengine_p.h +++ b/src/quick/items/qquickspriteengine_p.h @@ -102,9 +102,9 @@ public: int variedDuration() const { - return m_duration + return qMax(qreal(0.0) , m_duration + (m_durationVariance * ((qreal)qrand()/RAND_MAX) * 2) - - m_durationVariance; + - m_durationVariance); } int frames() const @@ -212,6 +212,7 @@ public: void setGoal(int state, int sprite=0, bool jump=false); void start(int index=0, int state=0); + void advance(int index=0);//Sends state to the next chosen state, unlike goal. void stop(int index=0); int curState(int index=0) {return m_things[index];} @@ -246,6 +247,7 @@ protected: friend class QQuickParticleSystem; void restart(int index); void addToUpdateList(uint t, int idx); + int nextState(int curState, int idx=0); int goalSeek(int curState, int idx, int dist=-1); QList m_states; //### Consider struct or class for the four data variables? diff --git a/src/quick/particles/qquickimageparticle.cpp b/src/quick/particles/qquickimageparticle.cpp index 9aec234..e8aa036 100644 --- a/src/quick/particles/qquickimageparticle.cpp +++ b/src/quick/particles/qquickimageparticle.cpp @@ -53,6 +53,7 @@ #include #include #include +#include QT_BEGIN_NAMESPACE @@ -82,9 +83,8 @@ static const char vertexShaderCode[] = "attribute highp vec3 vRotation; //x = radians of rotation, y=rotation speed, z= bool autoRotate\n" "#endif\n" "#if defined(SPRITE)\n" - "attribute highp vec4 vAnimData;// interpolate(bool), duration, frameCount (this anim), timestamp (this anim)\n" - "attribute highp vec4 vAnimPos;//sheet x,y, width/height of this anim\n" - "uniform highp vec2 animSheetSize; //width/height of whole sheet\n" + "attribute highp vec3 vAnimData;// w,h(premultiplied of anim), interpolation progress\n" + "attribute highp vec4 vAnimPos;//x,y, x,y (two frames for interpolation)\n" "#endif\n" "\n" "uniform highp mat4 qt_Matrix;\n" @@ -117,18 +117,12 @@ static const char vertexShaderCode[] = "#endif\n" " } else {\n" "#if defined(SPRITE)\n" + " tt.y = vAnimData.z;\n" " //Calculate frame location in texture\n" - " highp float frameIndex = mod((((timestamp - vAnimData.w)*1000.)/vAnimData.y),vAnimData.z);\n" - " tt.y = mod((timestamp - vAnimData.w)*1000., vAnimData.y) / vAnimData.y;\n" - "\n" - " frameIndex = floor(frameIndex);\n" - " fTexS.xy = vec2(((frameIndex + vPosTex.z) * vAnimPos.z / animSheetSize.x), ((vAnimPos.y + vPosTex.w * vAnimPos.w) / animSheetSize.y));\n" - "\n" + " fTexS.xy = vAnimPos.xy + vPosTex.zw * vAnimData.xy;\n" " //Next frame is also passed, for interpolation\n" - " //### Should the next anim be precalculated to allow for interpolation there?\n" - " if (vAnimData.x == 1.0 && frameIndex != vAnimData.z - 1.)//Can't do it for the last frame though, this anim may not loop\n" - " frameIndex = mod(frameIndex+1., vAnimData.z);\n" - " fTexS.zw = vec2(((frameIndex + vPosTex.z) * vAnimPos.z / animSheetSize.x), ((vAnimPos.y + vPosTex.w * vAnimPos.w) / animSheetSize.y));\n" + " fTexS.zw = vAnimPos.zw + vPosTex.zw * vAnimData.xy;\n" + "\n" "#elif defined(DEFORM)\n" " fTex = vPosTex.zw;\n" "#endif\n" @@ -413,8 +407,8 @@ public: program()->setUniformValue("texture", 0); program()->setUniformValue("colortable", 1); glFuncs = QOpenGLContext::currentContext()->functions(); + //Don't actually expose the animSheetSize in the shader, it's currently only used for CPU calculations. m_timestamp_id = program()->uniformLocation("timestamp"); - m_animsize_id = program()->uniformLocation("animSheetSize"); m_entry_id = program()->uniformLocation("entry"); m_sizetable_id = program()->uniformLocation("sizetable"); m_opacitytable_id = program()->uniformLocation("opacitytable"); @@ -429,14 +423,12 @@ public: d->texture->bind(); program()->setUniformValue(m_timestamp_id, (float) d->timestamp); - 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_animsize_id; int m_entry_id; int m_sizetable_id; int m_opacitytable_id; @@ -1171,14 +1163,14 @@ static QSGGeometry::Attribute SpriteParticle_Attributes[] = { QSGGeometry::Attribute::create(3, 4, GL_UNSIGNED_BYTE), // Colors QSGGeometry::Attribute::create(4, 4, GL_FLOAT), // DeformationVectors QSGGeometry::Attribute::create(5, 3, GL_FLOAT), // Rotation - QSGGeometry::Attribute::create(6, 4, GL_FLOAT), // Anim Data + QSGGeometry::Attribute::create(6, 3, GL_FLOAT), // Anim Data QSGGeometry::Attribute::create(7, 4, GL_FLOAT) // Anim Pos }; static QSGGeometry::AttributeSet SpriteParticle_AttributeSet = { 8, // Attribute Count - (4 + 4 + 4 + 4 + 4 + 4 + 3) * sizeof(float) + 4 * sizeof(uchar), + (4 + 4 + 4 + 4 + 3 + 3 + 4) * sizeof(float) + 4 * sizeof(uchar), SpriteParticle_Attributes }; @@ -1386,9 +1378,11 @@ QSGGeometryNode* QQuickImageParticle::buildParticleNodes() indices += 6; } } - } + if (perfLevel == Sprites) + spritesUpdate();//Gives all vertexes the initial sprite data, then maintained per frame + foreach (QSGGeometryNode* node, m_nodes){ if (node == *(m_nodes.begin())) node->setFlag(QSGGeometryNode::OwnsMaterial);//Root node owns the material for memory management purposes @@ -1454,7 +1448,8 @@ void QQuickImageParticle::prepareNextFrame() case Sprites: //Advance State if (m_spriteEngine) - m_spriteEngine->updateSprites(timeStamp); + m_spriteEngine->updateSprites(timeStamp);//fires signals if anim changed + spritesUpdate(time); case Tabled: case Deformable: case Colored: @@ -1467,6 +1462,65 @@ void QQuickImageParticle::prepareNextFrame() node->markDirty(QSGNode::DirtyMaterial); } +void QQuickImageParticle::spritesUpdate(qreal time) +{ + // Sprite progression handled CPU side, so as to have per-frame control. + foreach (const QString &str, m_groups) { + int gIdx = m_system->groupIds[str]; + foreach (QQuickParticleData* mainDatum, m_system->groupData[gIdx]->data) { + QSGGeometryNode *node = m_nodes[gIdx]; + if (!node) + continue; + //TODO: Interpolate between two different animations if it's going to transition next frame + // This is particularly important for cut-up sprites. + QQuickParticleData* datum = (mainDatum->animationOwner == this ? mainDatum : getShadowDatum(mainDatum)); + double frameAt; + qreal progress; + if (datum->frameDuration > 0) { + qreal frame = (time - datum->animT)/(datum->frameDuration / 1000.0); + frame = qBound(0.0, frame, (qreal)datum->frameCount - 1.0);//Stop at count-1 frames until we have between anim interpolation + progress = modf(frame,&frameAt); + } else { + datum->frameAt++; + if (datum->frameAt >= datum->frameCount){ + datum->frameAt = 0; + for (int i = 0; iadvance(m_startsIdx[i].first + datum->index); + break; + } + } + } + frameAt = datum->frameAt; + progress = 0; + } + QSizeF sheetSize = getState(m_material)->animSheetSize; + qreal y = datum->animY / sheetSize.height(); + qreal w = datum->animWidth / sheetSize.width(); + qreal h = datum->animHeight / sheetSize.height(); + qreal x1 = datum->animX / sheetSize.width(); + x1 += frameAt * w; + qreal x2 = x1; + if (frameAt < (datum->frameCount-1)) + x2 += w; + + node->setFlag(QSGNode::OwnsGeometry, false); + SpriteVertex *spriteVertices = (SpriteVertex *) node->geometry()->vertexData(); + spriteVertices += datum->index*4; + for (int i=0; i<4; i++) { + spriteVertices[i].animX1 = x1; + spriteVertices[i].animY1 = y; + spriteVertices[i].animX2 = x2; + spriteVertices[i].animY2 = y; + spriteVertices[i].animW = w; + spriteVertices[i].animH = h; + spriteVertices[i].animProgress = progress; + } + node->setFlag(QSGNode::OwnsGeometry, true); + } + } +} + void QQuickImageParticle::spriteAdvance(int spriteIdx) { if (!m_startsIdx.count())//Probably overly defensive @@ -1484,19 +1538,17 @@ void QQuickImageParticle::spriteAdvance(int spriteIdx) gIdx = m_startsIdx[i-1].second; int pIdx = spriteIdx - m_startsIdx[i-1].first; - QQuickParticleData* datum = m_system->groupData[gIdx]->data[pIdx]; - QQuickParticleData* d = (datum->animationOwner == this ? datum : getShadowDatum(datum)); - - d->animIdx = m_spriteEngine->spriteState(spriteIdx); - Vertices* particles = (Vertices *) m_nodes[gIdx]->geometry()->vertexData(); - Vertices &p = particles[pIdx]; - d->animT = p.v1.animT = p.v2.animT = p.v3.animT = p.v4.animT = m_spriteEngine->spriteStart(spriteIdx)/1000.0; - d->frameCount = p.v1.frameCount = p.v2.frameCount = p.v3.frameCount = p.v4.frameCount = m_spriteEngine->spriteFrames(spriteIdx); - d->frameDuration = p.v1.frameDuration = p.v2.frameDuration = p.v3.frameDuration = p.v4.frameDuration = m_spriteEngine->spriteDuration(spriteIdx); - d->animX = p.v1.animX = p.v2.animX = p.v3.animX = p.v4.animX = m_spriteEngine->spriteX(spriteIdx); - d->animY = p.v1.animY = p.v2.animY = p.v3.animY = p.v4.animY = m_spriteEngine->spriteY(spriteIdx); - d->animWidth = p.v1.animWidth = p.v2.animWidth = p.v3.animWidth = p.v4.animWidth = m_spriteEngine->spriteWidth(spriteIdx); - d->animHeight = p.v1.animHeight = p.v2.animHeight = p.v3.animHeight = p.v4.animHeight = m_spriteEngine->spriteHeight(spriteIdx); + QQuickParticleData* mainDatum = m_system->groupData[gIdx]->data[pIdx]; + QQuickParticleData* datum = (mainDatum->animationOwner == this ? mainDatum : getShadowDatum(mainDatum)); + + datum->animIdx = m_spriteEngine->spriteState(spriteIdx); + datum->animT = m_spriteEngine->spriteStart(spriteIdx)/1000.0; + datum->frameCount = m_spriteEngine->spriteFrames(spriteIdx); + datum->frameDuration = m_spriteEngine->spriteDuration(spriteIdx); + datum->animX = m_spriteEngine->spriteX(spriteIdx); + datum->animY = m_spriteEngine->spriteY(spriteIdx); + datum->animWidth = m_spriteEngine->spriteWidth(spriteIdx); + datum->animHeight = m_spriteEngine->spriteHeight(spriteIdx); } void QQuickImageParticle::reloadColor(const Color4ub &c, QQuickParticleData* d) @@ -1536,6 +1588,7 @@ void QQuickImageParticle::initialize(int gIdx, int pIdx) writeTo->frameCount = m_spriteEngine->spriteFrames(spriteIdx); writeTo->frameDuration = m_spriteEngine->spriteDuration(spriteIdx); writeTo->animIdx = 0;//Always starts at 0 + writeTo->frameAt = -1; writeTo->animX = m_spriteEngine->spriteX(spriteIdx); writeTo->animY = m_spriteEngine->spriteY(spriteIdx); writeTo->animWidth = m_spriteEngine->spriteWidth(spriteIdx); @@ -1546,6 +1599,7 @@ void QQuickImageParticle::initialize(int gIdx, int pIdx) writeTo->animT = datum->t; writeTo->frameCount = 1; writeTo->frameDuration = 60000000.0; + writeTo->frameAt = -1; writeTo->animIdx = 0; writeTo->animT = 0; writeTo->animX = writeTo->animY = 0; @@ -1667,25 +1721,7 @@ void QQuickImageParticle::commit(int gIdx, int pIdx) spriteVertices[i].rotationSpeed = datum->rotationSpeed; spriteVertices[i].autoRotate = datum->autoRotate; } - spriteVertices[i].animInterpolate = m_spriteEngine ? (m_spritesInterpolate ? 1.0 : 0.0) : 0.0;//### Shadow? In particleData? Or uniform? - if (!m_spriteEngine || (m_explicitAnimation && datum->animationOwner != this)) { - QQuickParticleData* shadow = getShadowDatum(datum); - 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].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; - } + //Sprite-related vertices updated per-frame in spritesUpdate(), not on demand if (m_explicitColor && datum->colorOwner != this) { QQuickParticleData* shadow = getShadowDatum(datum); spriteVertices[i].color.r = shadow->color.r; diff --git a/src/quick/particles/qquickimageparticle_p.h b/src/quick/particles/qquickimageparticle_p.h index a7cf00d..3e50a5f 100644 --- a/src/quick/particles/qquickimageparticle_p.h +++ b/src/quick/particles/qquickimageparticle_p.h @@ -128,14 +128,13 @@ struct SpriteVertex { float rotation; float rotationSpeed; float autoRotate;//Assumed that GPUs prefer floats to bools - float animInterpolate; - float frameDuration; - float frameCount; - float animT; - float animX; - float animY; - float animWidth; - float animHeight; + float animW; + float animH; + float animProgress; + float animX1; + float animY1; + float animX2; + float animY2; }; template @@ -343,6 +342,7 @@ private slots: void createEngine(); //### method invoked by sprite list changing (in engine.h) - pretty nasty void spriteAdvance(int spriteIndex); + void spritesUpdate(qreal time = 0 ); private: QUrl m_image_name; QUrl m_colortable_name; diff --git a/src/quick/particles/qquickparticlesystem.cpp b/src/quick/particles/qquickparticlesystem.cpp index a701039..47aa4be 100644 --- a/src/quick/particles/qquickparticlesystem.cpp +++ b/src/quick/particles/qquickparticlesystem.cpp @@ -420,6 +420,7 @@ QQuickParticleData::QQuickParticleData(QQuickParticleSystem* sys) autoRotate = 0; animIdx = 0; frameDuration = 1; + frameAt = -1; frameCount = 1; animT = -1; animX = 0; diff --git a/src/quick/particles/qquickparticlesystem_p.h b/src/quick/particles/qquickparticlesystem_p.h index 343d48b..277dda1 100644 --- a/src/quick/particles/qquickparticlesystem_p.h +++ b/src/quick/particles/qquickparticlesystem_p.h @@ -200,6 +200,7 @@ public: float autoRotate;//Assume that GPUs prefer floats to bools float animIdx; float frameDuration; + float frameAt;//Used for duration -1 float frameCount; float animT; float animX; diff --git a/src/quick/particles/qquickv8particledata.cpp b/src/quick/particles/qquickv8particledata.cpp index fab9b8a..caf32b6 100644 --- a/src/quick/particles/qquickv8particledata.cpp +++ b/src/quick/particles/qquickv8particledata.cpp @@ -411,6 +411,7 @@ FLOAT_GETTER_AND_SETTER(rotation) FLOAT_GETTER_AND_SETTER(rotationSpeed) FLOAT_GETTER_AND_SETTER(animIdx) FLOAT_GETTER_AND_SETTER(frameDuration) +FLOAT_GETTER_AND_SETTER(frameAt) FLOAT_GETTER_AND_SETTER(frameCount) FLOAT_GETTER_AND_SETTER(animT) FLOAT_GETTER_AND_SETTER(r) @@ -450,6 +451,7 @@ QV8ParticleDataDeletable::QV8ParticleDataDeletable(QV8Engine *engine) REGISTER_ACCESSOR(ft, engine, autoRotate, autoRotate); REGISTER_ACCESSOR(ft, engine, animIdx, animationIndex); REGISTER_ACCESSOR(ft, engine, frameDuration, frameDuration); + REGISTER_ACCESSOR(ft, engine, frameAt, frameAt); REGISTER_ACCESSOR(ft, engine, frameCount, frameCount); REGISTER_ACCESSOR(ft, engine, animT, animationT); REGISTER_ACCESSOR(ft, engine, r, r);