1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the Declarative module of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qquickspriteengine_p.h"
43 #include "qquicksprite_p.h"
53 solve the state data initialization/transfer issue so as to not need to make friends
56 QQuickStochasticEngine::QQuickStochasticEngine(QObject *parent) :
57 QObject(parent), m_timeOffset(0)
61 m_advanceTime.start();
64 QQuickStochasticEngine::QQuickStochasticEngine(QList<QQuickStochasticState*> states, QObject *parent) :
65 QObject(parent), m_states(states), m_timeOffset(0)
69 m_advanceTime.start();
72 QQuickStochasticEngine::~QQuickStochasticEngine()
76 QQuickSpriteEngine::QQuickSpriteEngine(QObject *parent)
77 : QQuickStochasticEngine(parent)
81 QQuickSpriteEngine::QQuickSpriteEngine(QList<QQuickSprite*> sprites, QObject *parent)
82 : QQuickStochasticEngine(parent)
84 foreach (QQuickSprite* sprite, sprites)
85 m_states << (QQuickStochasticState*)sprite;
88 QQuickSpriteEngine::~QQuickSpriteEngine()
93 int QQuickSpriteEngine::maxFrames()
98 /* States too large to fit in one row are split into multiple rows
99 This is more efficient for the implementation, but should remain an implementation detail (invisible from QML)
100 Therefore the below functions abstract sprite from the viewpoint of classes that pass the details onto shaders
101 But States maintain their listed index for internal structures
102 TODO: All these calculations should be pre-calculated and cached during initialization for a significant performance boost
103 TODO: Above idea needs to have the varying duration offset added to it
105 int QQuickSpriteEngine::spriteState(int sprite)
107 int state = m_things[sprite];
108 if (!m_sprites[state]->m_generatedCount)
110 int rowDuration = m_duration[sprite] * m_sprites[state]->m_framesPerRow;
111 int extra = (m_timeOffset - m_startTimes[sprite])/rowDuration;
112 return state + extra;
115 int QQuickSpriteEngine::spriteStart(int sprite)
117 int state = m_things[sprite];
118 if (!m_sprites[state]->m_generatedCount)
119 return m_startTimes[sprite];
120 int rowDuration = m_duration[sprite] * m_sprites[state]->m_framesPerRow;
121 int extra = (m_timeOffset - m_startTimes[sprite])/rowDuration;
122 return state + extra*rowDuration;
125 int QQuickSpriteEngine::spriteFrames(int sprite)
127 int state = m_things[sprite];
128 if (!m_sprites[state]->m_generatedCount)
129 return m_sprites[state]->frames();
130 int rowDuration = m_duration[sprite] * m_sprites[state]->m_framesPerRow;
131 int extra = (m_timeOffset - m_startTimes[sprite])/rowDuration;
132 if (extra == m_sprites[state]->m_generatedCount - 1)//last state
133 return m_sprites[state]->frames() % m_sprites[state]->m_framesPerRow;
135 return m_sprites[state]->m_framesPerRow;
138 int QQuickSpriteEngine::spriteDuration(int sprite)
140 int state = m_things[sprite];
141 if (!m_sprites[state]->m_generatedCount)
142 return m_duration[sprite];
143 int rowDuration = m_duration[sprite] * m_sprites[state]->m_framesPerRow;
144 int extra = (m_timeOffset - m_startTimes[sprite])/rowDuration;
145 if (extra == m_sprites[state]->m_generatedCount - 1)//last state
146 return (m_duration[sprite] * m_sprites[state]->frames()) % rowDuration;
151 int QQuickSpriteEngine::spriteY(int sprite)
153 int state = m_things[sprite];
154 if (!m_sprites[state]->m_generatedCount)
155 return m_sprites[state]->m_rowY;
156 int rowDuration = m_duration[sprite] * m_sprites[state]->m_framesPerRow;
157 int extra = (m_timeOffset - m_startTimes[sprite])/rowDuration;
158 return m_sprites[state]->m_rowY + m_sprites[state]->m_frameHeight * extra;
161 int QQuickSpriteEngine::spriteWidth(int sprite)
163 int state = m_things[sprite];
164 return m_sprites[state]->m_frameWidth;
167 int QQuickSpriteEngine::spriteHeight(int sprite)
169 int state = m_things[sprite];
170 return m_sprites[state]->m_frameHeight;
173 int QQuickSpriteEngine::spriteCount()//TODO: Actually image state count, need to rename these things to make sense together
175 return m_imageStateCount;
178 void QQuickStochasticEngine::setGoal(int state, int sprite, bool jump)
180 if (sprite >= m_things.count() || state >= m_states.count()
181 || sprite < 0 || state < 0)
184 m_goals[sprite] = state;
188 if (m_things[sprite] == state)
189 return;//Already there
190 m_things[sprite] = state;
191 m_duration[sprite] = m_states[state]->variedDuration();
192 m_goals[sprite] = -1;
194 emit stateChanged(sprite);
195 emit m_states[state]->entered();
199 QImage QQuickSpriteEngine::assembledImage()
204 m_imageStateCount = 0;
207 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize);
208 foreach (QQuickStochasticState* s, m_states){
209 QQuickSprite* sprite = qobject_cast<QQuickSprite*>(s);
213 qDebug() << "Error: Non-sprite in QQuickSpriteEngine";
216 foreach (QQuickSprite* state, m_sprites){
217 if (state->frames() > m_maxFrames)
218 m_maxFrames = state->frames();
220 QImage img(state->source().toLocalFile());
222 qWarning() << "SpriteEngine: loading image failed..." << state->source().toLocalFile();
226 //Check that the frame sizes are the same within one engine
227 if (!state->m_frameWidth)
228 state->m_frameWidth = img.width() / state->frames();
230 if (!state->m_frameHeight)
231 state->m_frameHeight = img.height();
233 if (state->frames() * state->frameWidth() > maxSize){
235 static int divRoundUp(int a, int b){return (a+b-1)/b;}
237 int rowsNeeded = helper::divRoundUp(state->frames(), (maxSize / state->frameWidth()));
238 if (rowsNeeded * state->frameHeight() > maxSize){
239 qWarning() << "SpriteEngine: Animation too large to fit in one texture..." << state->source().toLocalFile();
240 qWarning() << "SpriteEngine: Your texture max size today is " << maxSize;
242 state->m_generatedCount = rowsNeeded;
243 h += state->frameHeight() * rowsNeeded;
244 w = qMax(w, ((int)(maxSize / state->frameWidth())) * state->frameWidth());
245 m_imageStateCount += rowsNeeded;
247 h += state->frameHeight();
248 w = qMax(w, state->frameWidth() * state->frames());
253 //maxFrames is max number in a line of the texture
254 QImage image(w, h, QImage::Format_ARGB32);
258 foreach (QQuickSprite* state, m_sprites){
259 QImage img(state->source().toLocalFile());
260 int frameWidth = state->m_frameWidth;
261 int frameHeight = state->m_frameHeight;
262 if (img.height() == frameHeight && img.width() < maxSize){//Simple case
263 p.drawImage(0,y,img);
266 }else{//Chopping up image case
267 state->m_framesPerRow = image.width()/frameWidth;
272 int framesLeft = state->frames();
273 while (framesLeft > 0){
274 if (image.width() - x + curX <= img.width()){//finish a row in image (dest)
275 int copied = image.width() - x;
276 Q_ASSERT(!(copied % frameWidth));//XXX: Just checking
277 framesLeft -= copied/frameWidth;
278 p.drawImage(x,y,img.copy(curX,curY,copied,frameHeight));
282 if (curX == img.width()){
286 }else{//finish a row in img (src)
287 int copied = img.width() - curX;
288 Q_ASSERT(!(copied % frameWidth));//XXX: Just checking
289 framesLeft -= copied/frameWidth;
290 p.drawImage(x,y,img.copy(curX,curY,copied,frameHeight));
301 if (image.height() > maxSize){
302 qWarning() << "SpriteEngine: Too many animations to fit in one texture...";
303 qWarning() << "SpriteEngine: Your texture max size today is " << maxSize;
309 void QQuickStochasticEngine::setCount(int c)
313 m_duration.resize(c);
314 m_startTimes.resize(c);
317 void QQuickStochasticEngine::start(int index, int state)
319 if (index >= m_things.count())
321 m_things[index] = state;
322 m_duration[index] = m_states[state]->variedDuration();
327 void QQuickStochasticEngine::stop(int index)
329 if (index >= m_things.count())
331 //Will never change until start is called again with a new state - this is not a 'pause'
332 for (int i=0; i<m_stateUpdates.count(); i++)
333 m_stateUpdates[i].second.removeAll(index);
336 void QQuickStochasticEngine::restart(int index)
338 m_startTimes[index] = m_timeOffset + m_advanceTime.elapsed();
339 int time = m_duration[index] * m_states[m_things[index]]->frames() + m_startTimes[index];
340 for (int i=0; i<m_stateUpdates.count(); i++)
341 m_stateUpdates[i].second.removeAll(index);
342 addToUpdateList(time, index);
345 uint QQuickStochasticEngine::updateSprites(uint time)//### would returning a list of changed idxs be faster than signals?
347 //Sprite State Update;
348 QSet<int> changedIndexes;
349 while (!m_stateUpdates.isEmpty() && time >= m_stateUpdates.first().first){
350 foreach (int idx, m_stateUpdates.first().second){
351 if (idx >= m_things.count())
352 continue;//TODO: Proper fix(because this does happen and I'm just ignoring it)
353 int stateIdx = m_things[idx];
355 int goalPath = goalSeek(stateIdx, idx);
356 if (goalPath == -1){//Random
357 qreal r =(qreal) qrand() / (qreal) RAND_MAX;
359 for (QVariantMap::const_iterator iter=m_states[stateIdx]->m_to.constBegin();
360 iter!=m_states[stateIdx]->m_to.constEnd(); iter++)
361 total += (*iter).toReal();
363 for (QVariantMap::const_iterator iter= m_states[stateIdx]->m_to.constBegin();
364 iter!=m_states[stateIdx]->m_to.constEnd(); iter++){
365 if (r < (*iter).toReal()){
366 bool superBreak = false;
367 for (int i=0; i<m_states.count(); i++){
368 if (m_states[i]->name() == iter.key()){
377 r -= (*iter).toReal();
379 }else{//Random out of shortest paths to goal
382 if (nextIdx == -1)//No to states means stay here
385 m_things[idx] = nextIdx;
386 m_duration[idx] = m_states[nextIdx]->variedDuration();
387 m_startTimes[idx] = time;
388 if (nextIdx != stateIdx){
389 changedIndexes << idx;
390 emit m_states[nextIdx]->entered();
392 addToUpdateList((m_duration[idx] * m_states[nextIdx]->frames()) + time, idx);
394 m_stateUpdates.pop_front();
398 m_advanceTime.start();
399 //TODO: emit this when a psuedostate changes too
400 foreach (int idx, changedIndexes){//Batched so that update list doesn't change midway
401 emit stateChanged(idx);
403 if (m_stateUpdates.isEmpty())
405 return m_stateUpdates.first().first;
408 int QQuickStochasticEngine::goalSeek(int curIdx, int spriteIdx, int dist)
411 if (m_goals[spriteIdx] != -1)
412 goalName = m_states[m_goals[spriteIdx]]->name();
414 goalName = m_globalGoal;
415 if (goalName.isEmpty())
417 //TODO: caching instead of excessively redoing iterative deepening (which was chosen arbitarily anyways)
418 // Paraphrased - implement in an *efficient* manner
419 for (int i=0; i<m_states.count(); i++)
420 if (m_states[curIdx]->name() == goalName)
423 dist = m_states.count();
424 QQuickStochasticState* curState = m_states[curIdx];
425 for (QVariantMap::const_iterator iter = curState->m_to.constBegin();
426 iter!=curState->m_to.constEnd(); iter++){
427 if (iter.key() == goalName)
428 for (int i=0; i<m_states.count(); i++)
429 if (m_states[i]->name() == goalName)
433 for (int i=1; i<dist; i++){
434 for (QVariantMap::const_iterator iter = curState->m_to.constBegin();
435 iter!=curState->m_to.constEnd(); iter++){
437 for (int j=0; j<m_states.count(); j++)//One place that could be a lot more efficient...
438 if (m_states[j]->name() == iter.key())
439 if (goalSeek(j, spriteIdx, i) != -1)
444 if (!options.isEmpty()){
445 if (options.count()==1)
446 return *(options.begin());
448 qreal r =(qreal) qrand() / (qreal) RAND_MAX;
450 for (QSet<int>::const_iterator iter=options.constBegin();
451 iter!=options.constEnd(); iter++)
452 total += curState->m_to.value(m_states[(*iter)]->name()).toReal();
454 for (QVariantMap::const_iterator iter = curState->m_to.constBegin();
455 iter!=curState->m_to.constEnd(); iter++){
456 bool superContinue = true;
457 for (int j=0; j<m_states.count(); j++)
458 if (m_states[j]->name() == iter.key())
459 if (options.contains(j))
460 superContinue = false;
463 if (r < (*iter).toReal()){
464 bool superBreak = false;
465 for (int j=0; j<m_states.count(); j++){
466 if (m_states[j]->name() == iter.key()){
483 void QQuickStochasticEngine::addToUpdateList(uint t, int idx)
485 for (int i=0; i<m_stateUpdates.count(); i++){
486 if (m_stateUpdates[i].first==t){
487 m_stateUpdates[i].second << idx;
489 }else if (m_stateUpdates[i].first > t){
492 m_stateUpdates.insert(i, qMakePair(t, tmpList));
498 m_stateUpdates << qMakePair(t, tmpList);