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 "qsgspriteengine_p.h"
43 #include "qsgsprite_p.h"
51 /* TODO: Split out image logic from stochastic state logic
53 Also solve the state data initialization/transfer issue so as to not need to make friends
56 QSGStochasticEngine::QSGStochasticEngine(QObject *parent) :
57 QObject(parent), m_timeOffset(0)
61 m_advanceTime.start();
64 QSGStochasticEngine::QSGStochasticEngine(QList<QSGStochasticState*> states, QObject *parent) :
65 QObject(parent), m_states(states), m_timeOffset(0)
69 m_advanceTime.start();
72 QSGStochasticEngine::~QSGStochasticEngine()
76 QSGSpriteEngine::QSGSpriteEngine(QObject *parent)
77 : QSGStochasticEngine(parent)
81 QSGSpriteEngine::QSGSpriteEngine(QList<QSGSprite*> sprites, QObject *parent)
82 : QSGStochasticEngine(parent)
84 foreach (QSGSprite* sprite, sprites)
85 m_states << (QSGStochasticState*)sprite;
88 QSGSpriteEngine::~QSGSpriteEngine()
93 int QSGSpriteEngine::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 QSGSpriteEngine::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 QSGSpriteEngine::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 QSGSpriteEngine::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 QSGSpriteEngine::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 QSGSpriteEngine::spriteCount()//TODO: Actually image state count, need to rename these things to make sense together
153 return m_imageStateCount;
156 void QSGStochasticEngine::setGoal(int state, int sprite, bool jump)
158 if (sprite >= m_things.count() || state >= m_states.count())
161 m_goals[sprite] = state;
165 if (m_things[sprite] == state)
166 return;//Already there
167 m_things[sprite] = state;
168 m_duration[sprite] = m_states[state]->variedDuration();
169 m_goals[sprite] = -1;
171 emit stateChanged(sprite);
172 emit m_states[state]->entered();
176 QImage QSGSpriteEngine::assembledImage()
181 m_imageStateCount = 0;
184 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize);
185 foreach (QSGStochasticState* s, m_states){
186 QSGSprite* sprite = qobject_cast<QSGSprite*>(s);
190 qDebug() << "Error: Non-sprite in QSGSpriteEngine";
193 foreach (QSGSprite* state, m_sprites){
194 if (state->frames() > m_maxFrames)
195 m_maxFrames = state->frames();
197 QImage img(state->source().toLocalFile());
199 qWarning() << "SpriteEngine: loading image failed..." << state->source().toLocalFile();
203 //Check that the frame sizes are the same within one engine
204 int imgWidth = state->frameWidth();
206 imgWidth = img.width() / state->frames();
208 if (imgWidth != frameWidth){
209 qWarning() << "SpriteEngine: Irregular frame width..." << state->source().toLocalFile();
213 frameWidth = imgWidth;
216 int imgHeight = state->frameHeight();
218 imgHeight = img.height();
220 if (imgHeight!=frameHeight){
221 qWarning() << "SpriteEngine: Irregular frame height..." << state->source().toLocalFile();
225 frameHeight = imgHeight;
228 if (state->frames() * frameWidth > maxSize){
230 static int divRoundUp(int a, int b){return (a+b-1)/b;}
232 int rowsNeeded = helper::divRoundUp(state->frames(), helper::divRoundUp(maxSize, frameWidth));
233 if (rowsNeeded * frameHeight > maxSize){
234 qWarning() << "SpriteEngine: Animation too large to fit in one texture..." << state->source().toLocalFile();
235 qWarning() << "SpriteEngine: Your texture max size today is " << maxSize;
237 state->m_generatedCount = rowsNeeded;
238 m_imageStateCount += rowsNeeded;
244 //maxFrames is max number in a line of the texture
245 if (m_maxFrames * frameWidth > maxSize)
246 m_maxFrames = maxSize/frameWidth;
247 QImage image(frameWidth * m_maxFrames, frameHeight * m_imageStateCount, QImage::Format_ARGB32);
251 foreach (QSGSprite* state, m_sprites){
252 QImage img(state->source().toLocalFile());
253 if (img.height() == frameHeight && img.width() < maxSize){//Simple case
254 p.drawImage(0,y,img);
257 state->m_framesPerRow = image.width()/frameWidth;
261 int framesLeft = state->frames();
262 while (framesLeft > 0){
263 if (image.width() - x + curX <= img.width()){//finish a row in image (dest)
264 int copied = image.width() - x;
265 Q_ASSERT(!(copied % frameWidth));//XXX: Just checking
266 framesLeft -= copied/frameWidth;
267 p.drawImage(x,y,img.copy(curX,curY,copied,frameHeight));
271 if (curX == img.width()){
275 }else{//finish a row in img (src)
276 int copied = img.width() - curX;
277 Q_ASSERT(!(copied % frameWidth));//XXX: Just checking
278 framesLeft -= copied/frameWidth;
279 p.drawImage(x,y,img.copy(curX,curY,copied,frameHeight));
290 if (image.height() > maxSize){
291 qWarning() << "SpriteEngine: Too many animations to fit in one texture...";
292 qWarning() << "SpriteEngine: Your texture max size today is " << maxSize;
298 void QSGStochasticEngine::setCount(int c)
302 m_duration.resize(c);
303 m_startTimes.resize(c);
306 void QSGStochasticEngine::start(int index, int state)
308 if (index >= m_things.count())
310 m_things[index] = state;
311 m_duration[index] = m_states[state]->variedDuration();
316 void QSGStochasticEngine::stop(int index)
318 if (index >= m_things.count())
320 //Will never change until start is called again with a new state - this is not a 'pause'
321 for (int i=0; i<m_stateUpdates.count(); i++)
322 m_stateUpdates[i].second.removeAll(index);
325 void QSGStochasticEngine::restart(int index)
327 m_startTimes[index] = m_timeOffset + m_advanceTime.elapsed();
328 int time = m_duration[index] * m_states[m_things[index]]->frames() + m_startTimes[index];
329 for (int i=0; i<m_stateUpdates.count(); i++)
330 m_stateUpdates[i].second.removeAll(index);
331 addToUpdateList(time, index);
334 uint QSGStochasticEngine::updateSprites(uint time)//### would returning a list of changed idxs be faster than signals?
336 //Sprite State Update;
337 QSet<int> changedIndexes;
338 while (!m_stateUpdates.isEmpty() && time >= m_stateUpdates.first().first){
339 foreach (int idx, m_stateUpdates.first().second){
340 if (idx >= m_things.count())
341 continue;//TODO: Proper fix(because this does happen and I'm just ignoring it)
342 int stateIdx = m_things[idx];
344 int goalPath = goalSeek(stateIdx, idx);
345 if (goalPath == -1){//Random
346 qreal r =(qreal) qrand() / (qreal) RAND_MAX;
348 for (QVariantMap::const_iterator iter=m_states[stateIdx]->m_to.constBegin();
349 iter!=m_states[stateIdx]->m_to.constEnd(); iter++)
350 total += (*iter).toReal();
352 for (QVariantMap::const_iterator iter= m_states[stateIdx]->m_to.constBegin();
353 iter!=m_states[stateIdx]->m_to.constEnd(); iter++){
354 if (r < (*iter).toReal()){
355 bool superBreak = false;
356 for (int i=0; i<m_states.count(); i++){
357 if (m_states[i]->name() == iter.key()){
366 r -= (*iter).toReal();
368 }else{//Random out of shortest paths to goal
371 if (nextIdx == -1)//No to states means stay here
374 m_things[idx] = nextIdx;
375 m_duration[idx] = m_states[nextIdx]->variedDuration();
376 m_startTimes[idx] = time;
377 if (nextIdx != stateIdx){
378 changedIndexes << idx;
379 emit m_states[nextIdx]->entered();
381 addToUpdateList((m_duration[idx] * m_states[nextIdx]->frames()) + time, idx);
383 m_stateUpdates.pop_front();
387 m_advanceTime.start();
388 //TODO: emit this when a psuedostate changes too
389 foreach (int idx, changedIndexes){//Batched so that update list doesn't change midway
390 emit stateChanged(idx);
392 if (m_stateUpdates.isEmpty())
394 return m_stateUpdates.first().first;
397 int QSGStochasticEngine::goalSeek(int curIdx, int spriteIdx, int dist)
400 if (m_goals[spriteIdx] != -1)
401 goalName = m_states[m_goals[spriteIdx]]->name();
403 goalName = m_globalGoal;
404 if (goalName.isEmpty())
406 //TODO: caching instead of excessively redoing iterative deepening (which was chosen arbitarily anyways)
407 // Paraphrased - implement in an *efficient* manner
408 for (int i=0; i<m_states.count(); i++)
409 if (m_states[curIdx]->name() == goalName)
412 dist = m_states.count();
413 QSGStochasticState* curState = m_states[curIdx];
414 for (QVariantMap::const_iterator iter = curState->m_to.constBegin();
415 iter!=curState->m_to.constEnd(); iter++){
416 if (iter.key() == goalName)
417 for (int i=0; i<m_states.count(); i++)
418 if (m_states[i]->name() == goalName)
422 for (int i=1; i<dist; i++){
423 for (QVariantMap::const_iterator iter = curState->m_to.constBegin();
424 iter!=curState->m_to.constEnd(); iter++){
426 for (int j=0; j<m_states.count(); j++)//One place that could be a lot more efficient...
427 if (m_states[j]->name() == iter.key())
428 if (goalSeek(j, spriteIdx, i) != -1)
433 if (!options.isEmpty()){
434 if (options.count()==1)
435 return *(options.begin());
437 qreal r =(qreal) qrand() / (qreal) RAND_MAX;
439 for (QSet<int>::const_iterator iter=options.constBegin();
440 iter!=options.constEnd(); iter++)
441 total += curState->m_to.value(m_states[(*iter)]->name()).toReal();
443 for (QVariantMap::const_iterator iter = curState->m_to.constBegin();
444 iter!=curState->m_to.constEnd(); iter++){
445 bool superContinue = true;
446 for (int j=0; j<m_states.count(); j++)
447 if (m_states[j]->name() == iter.key())
448 if (options.contains(j))
449 superContinue = false;
452 if (r < (*iter).toReal()){
453 bool superBreak = false;
454 for (int j=0; j<m_states.count(); j++){
455 if (m_states[j]->name() == iter.key()){
472 void QSGStochasticEngine::addToUpdateList(uint t, int idx)
474 for (int i=0; i<m_stateUpdates.count(); i++){
475 if (m_stateUpdates[i].first==t){
476 m_stateUpdates[i].second << idx;
478 }else if (m_stateUpdates[i].first > t){
481 m_stateUpdates.insert(i, qMakePair(t, tmpList));
487 m_stateUpdates << qMakePair(t, tmpList);