1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: http://www.qt-project.org/
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 if (m_duration[index] > 0)
343 addToUpdateList(time, index);
346 // Doesn't remove from update list. If you want to cancel pending timed updates, call restart() first.
347 // Usually sprites are either manually advanced or in the list, and mixing is not recommended anyways.
348 void QQuickStochasticEngine::advance(int idx)
350 if (idx >= m_things.count())
351 return;//TODO: Proper fix(because this has happened and I just ignored it)
352 int stateIdx = m_things[idx];
353 int nextIdx = nextState(stateIdx,idx);
354 m_things[idx] = nextIdx;
355 m_duration[idx] = m_states[nextIdx]->variedDuration();
356 m_startTimes[idx] = m_timeOffset;//This will be the last time updateSprites was called
357 emit m_states[nextIdx]->entered();
358 emit stateChanged(idx); //TODO: emit this when a psuedostate changes too(but manually in SpriteEngine)
359 if (m_duration[idx] >= 0)
360 addToUpdateList((m_duration[idx] * m_states[nextIdx]->frames()) + m_startTimes[idx], idx);
363 int QQuickStochasticEngine::nextState(int curState, int curThing)
366 int goalPath = goalSeek(curState, curThing);
367 if (goalPath == -1){//Random
368 qreal r =(qreal) qrand() / (qreal) RAND_MAX;
370 for (QVariantMap::const_iterator iter=m_states[curState]->m_to.constBegin();
371 iter!=m_states[curState]->m_to.constEnd(); iter++)
372 total += (*iter).toReal();
374 for (QVariantMap::const_iterator iter= m_states[curState]->m_to.constBegin();
375 iter!=m_states[curState]->m_to.constEnd(); iter++){
376 if (r < (*iter).toReal()){
377 bool superBreak = false;
378 for (int i=0; i<m_states.count(); i++){
379 if (m_states[i]->name() == iter.key()){
388 r -= (*iter).toReal();
390 }else{//Random out of shortest paths to goal
393 if (nextIdx == -1)//No 'to' states means stay here
398 uint QQuickStochasticEngine::updateSprites(uint time)//### would returning a list of changed idxs be faster than signals?
400 //Sprite State Update;
402 while (!m_stateUpdates.isEmpty() && time >= m_stateUpdates.first().first){
403 foreach (int idx, m_stateUpdates.first().second)
405 m_stateUpdates.pop_front();
408 m_advanceTime.start();
409 if (m_stateUpdates.isEmpty())
411 return m_stateUpdates.first().first;
414 int QQuickStochasticEngine::goalSeek(int curIdx, int spriteIdx, int dist)
417 if (m_goals[spriteIdx] != -1)
418 goalName = m_states[m_goals[spriteIdx]]->name();
420 goalName = m_globalGoal;
421 if (goalName.isEmpty())
423 //TODO: caching instead of excessively redoing iterative deepening (which was chosen arbitarily anyways)
424 // Paraphrased - implement in an *efficient* manner
425 for (int i=0; i<m_states.count(); i++)
426 if (m_states[curIdx]->name() == goalName)
429 dist = m_states.count();
430 QQuickStochasticState* curState = m_states[curIdx];
431 for (QVariantMap::const_iterator iter = curState->m_to.constBegin();
432 iter!=curState->m_to.constEnd(); iter++){
433 if (iter.key() == goalName)
434 for (int i=0; i<m_states.count(); i++)
435 if (m_states[i]->name() == goalName)
439 for (int i=1; i<dist; i++){
440 for (QVariantMap::const_iterator iter = curState->m_to.constBegin();
441 iter!=curState->m_to.constEnd(); iter++){
443 for (int j=0; j<m_states.count(); j++)//One place that could be a lot more efficient...
444 if (m_states[j]->name() == iter.key())
445 if (goalSeek(j, spriteIdx, i) != -1)
450 if (!options.isEmpty()){
451 if (options.count()==1)
452 return *(options.begin());
454 qreal r =(qreal) qrand() / (qreal) RAND_MAX;
456 for (QSet<int>::const_iterator iter=options.constBegin();
457 iter!=options.constEnd(); iter++)
458 total += curState->m_to.value(m_states[(*iter)]->name()).toReal();
460 for (QVariantMap::const_iterator iter = curState->m_to.constBegin();
461 iter!=curState->m_to.constEnd(); iter++){
462 bool superContinue = true;
463 for (int j=0; j<m_states.count(); j++)
464 if (m_states[j]->name() == iter.key())
465 if (options.contains(j))
466 superContinue = false;
469 if (r < (*iter).toReal()){
470 bool superBreak = false;
471 for (int j=0; j<m_states.count(); j++){
472 if (m_states[j]->name() == iter.key()){
489 void QQuickStochasticEngine::addToUpdateList(uint t, int idx)
491 for (int i=0; i<m_stateUpdates.count(); i++){
492 if (m_stateUpdates[i].first==t){
493 m_stateUpdates[i].second << idx;
495 }else if (m_stateUpdates[i].first > t){
498 m_stateUpdates.insert(i, qMakePair(t, tmpList));
504 m_stateUpdates << qMakePair(t, tmpList);