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::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 QSGSpriteEngine::spriteWidth(int sprite)
163 int state = m_things[sprite];
164 return m_sprites[state]->m_frameWidth;
167 int QSGSpriteEngine::spriteHeight(int sprite)
169 int state = m_things[sprite];
170 return m_sprites[state]->m_frameHeight;
173 int QSGSpriteEngine::spriteCount()//TODO: Actually image state count, need to rename these things to make sense together
175 return m_imageStateCount;
178 void QSGStochasticEngine::setGoal(int state, int sprite, bool jump)
180 if (sprite >= m_things.count() || state >= m_states.count())
183 m_goals[sprite] = state;
187 if (m_things[sprite] == state)
188 return;//Already there
189 m_things[sprite] = state;
190 m_duration[sprite] = m_states[state]->variedDuration();
191 m_goals[sprite] = -1;
193 emit stateChanged(sprite);
194 emit m_states[state]->entered();
198 QImage QSGSpriteEngine::assembledImage()
203 m_imageStateCount = 0;
206 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize);
207 foreach (QSGStochasticState* s, m_states){
208 QSGSprite* sprite = qobject_cast<QSGSprite*>(s);
212 qDebug() << "Error: Non-sprite in QSGSpriteEngine";
215 foreach (QSGSprite* state, m_sprites){
216 if (state->frames() > m_maxFrames)
217 m_maxFrames = state->frames();
219 QImage img(state->source().toLocalFile());
221 qWarning() << "SpriteEngine: loading image failed..." << state->source().toLocalFile();
225 //Check that the frame sizes are the same within one engine
226 if (!state->m_frameWidth)
227 state->m_frameWidth = img.width() / state->frames();
229 if (!state->m_frameHeight)
230 state->m_frameHeight = img.height();
232 if (state->frames() * state->frameWidth() > maxSize){
234 static int divRoundUp(int a, int b){return (a+b-1)/b;}
236 int rowsNeeded = helper::divRoundUp(state->frames(), helper::divRoundUp(maxSize, state->frameWidth()));
237 if (rowsNeeded * state->frameHeight() > maxSize){
238 qWarning() << "SpriteEngine: Animation too large to fit in one texture..." << state->source().toLocalFile();
239 qWarning() << "SpriteEngine: Your texture max size today is " << maxSize;
241 state->m_generatedCount = rowsNeeded;
242 h += state->frameHeight() * rowsNeeded;
243 w = qMax(w, helper::divRoundUp(maxSize, state->frameWidth()));
244 m_imageStateCount += rowsNeeded;
246 h += state->frameHeight();
247 w = qMax(w, state->frameWidth() * state->frames());
252 //maxFrames is max number in a line of the texture
253 QImage image(w, h, QImage::Format_ARGB32);
257 foreach (QSGSprite* state, m_sprites){
258 QImage img(state->source().toLocalFile());
259 int frameWidth = state->m_frameWidth;
260 int frameHeight = state->m_frameHeight;
261 if (img.height() == frameHeight && img.width() < maxSize){//Simple case
262 p.drawImage(0,y,img);
265 }else{//Chopping up image case
266 state->m_framesPerRow = image.width()/frameWidth;
271 int framesLeft = state->frames();
272 while (framesLeft > 0){
273 if (image.width() - x + curX <= img.width()){//finish a row in image (dest)
274 int copied = image.width() - x;
275 Q_ASSERT(!(copied % frameWidth));//XXX: Just checking
276 framesLeft -= copied/frameWidth;
277 p.drawImage(x,y,img.copy(curX,curY,copied,frameHeight));
281 if (curX == img.width()){
285 }else{//finish a row in img (src)
286 int copied = img.width() - curX;
287 Q_ASSERT(!(copied % frameWidth));//XXX: Just checking
288 framesLeft -= copied/frameWidth;
289 p.drawImage(x,y,img.copy(curX,curY,copied,frameHeight));
300 if (image.height() > maxSize){
301 qWarning() << "SpriteEngine: Too many animations to fit in one texture...";
302 qWarning() << "SpriteEngine: Your texture max size today is " << maxSize;
308 void QSGStochasticEngine::setCount(int c)
312 m_duration.resize(c);
313 m_startTimes.resize(c);
316 void QSGStochasticEngine::start(int index, int state)
318 if (index >= m_things.count())
320 m_things[index] = state;
321 m_duration[index] = m_states[state]->variedDuration();
326 void QSGStochasticEngine::stop(int index)
328 if (index >= m_things.count())
330 //Will never change until start is called again with a new state - this is not a 'pause'
331 for (int i=0; i<m_stateUpdates.count(); i++)
332 m_stateUpdates[i].second.removeAll(index);
335 void QSGStochasticEngine::restart(int index)
337 m_startTimes[index] = m_timeOffset + m_advanceTime.elapsed();
338 int time = m_duration[index] * m_states[m_things[index]]->frames() + m_startTimes[index];
339 for (int i=0; i<m_stateUpdates.count(); i++)
340 m_stateUpdates[i].second.removeAll(index);
341 addToUpdateList(time, index);
344 uint QSGStochasticEngine::updateSprites(uint time)//### would returning a list of changed idxs be faster than signals?
346 //Sprite State Update;
347 QSet<int> changedIndexes;
348 while (!m_stateUpdates.isEmpty() && time >= m_stateUpdates.first().first){
349 foreach (int idx, m_stateUpdates.first().second){
350 if (idx >= m_things.count())
351 continue;//TODO: Proper fix(because this does happen and I'm just ignoring it)
352 int stateIdx = m_things[idx];
354 int goalPath = goalSeek(stateIdx, idx);
355 if (goalPath == -1){//Random
356 qreal r =(qreal) qrand() / (qreal) RAND_MAX;
358 for (QVariantMap::const_iterator iter=m_states[stateIdx]->m_to.constBegin();
359 iter!=m_states[stateIdx]->m_to.constEnd(); iter++)
360 total += (*iter).toReal();
362 for (QVariantMap::const_iterator iter= m_states[stateIdx]->m_to.constBegin();
363 iter!=m_states[stateIdx]->m_to.constEnd(); iter++){
364 if (r < (*iter).toReal()){
365 bool superBreak = false;
366 for (int i=0; i<m_states.count(); i++){
367 if (m_states[i]->name() == iter.key()){
376 r -= (*iter).toReal();
378 }else{//Random out of shortest paths to goal
381 if (nextIdx == -1)//No to states means stay here
384 m_things[idx] = nextIdx;
385 m_duration[idx] = m_states[nextIdx]->variedDuration();
386 m_startTimes[idx] = time;
387 if (nextIdx != stateIdx){
388 changedIndexes << idx;
389 emit m_states[nextIdx]->entered();
391 addToUpdateList((m_duration[idx] * m_states[nextIdx]->frames()) + time, idx);
393 m_stateUpdates.pop_front();
397 m_advanceTime.start();
398 //TODO: emit this when a psuedostate changes too
399 foreach (int idx, changedIndexes){//Batched so that update list doesn't change midway
400 emit stateChanged(idx);
402 if (m_stateUpdates.isEmpty())
404 return m_stateUpdates.first().first;
407 int QSGStochasticEngine::goalSeek(int curIdx, int spriteIdx, int dist)
410 if (m_goals[spriteIdx] != -1)
411 goalName = m_states[m_goals[spriteIdx]]->name();
413 goalName = m_globalGoal;
414 if (goalName.isEmpty())
416 //TODO: caching instead of excessively redoing iterative deepening (which was chosen arbitarily anyways)
417 // Paraphrased - implement in an *efficient* manner
418 for (int i=0; i<m_states.count(); i++)
419 if (m_states[curIdx]->name() == goalName)
422 dist = m_states.count();
423 QSGStochasticState* curState = m_states[curIdx];
424 for (QVariantMap::const_iterator iter = curState->m_to.constBegin();
425 iter!=curState->m_to.constEnd(); iter++){
426 if (iter.key() == goalName)
427 for (int i=0; i<m_states.count(); i++)
428 if (m_states[i]->name() == goalName)
432 for (int i=1; i<dist; i++){
433 for (QVariantMap::const_iterator iter = curState->m_to.constBegin();
434 iter!=curState->m_to.constEnd(); iter++){
436 for (int j=0; j<m_states.count(); j++)//One place that could be a lot more efficient...
437 if (m_states[j]->name() == iter.key())
438 if (goalSeek(j, spriteIdx, i) != -1)
443 if (!options.isEmpty()){
444 if (options.count()==1)
445 return *(options.begin());
447 qreal r =(qreal) qrand() / (qreal) RAND_MAX;
449 for (QSet<int>::const_iterator iter=options.constBegin();
450 iter!=options.constEnd(); iter++)
451 total += curState->m_to.value(m_states[(*iter)]->name()).toReal();
453 for (QVariantMap::const_iterator iter = curState->m_to.constBegin();
454 iter!=curState->m_to.constEnd(); iter++){
455 bool superContinue = true;
456 for (int j=0; j<m_states.count(); j++)
457 if (m_states[j]->name() == iter.key())
458 if (options.contains(j))
459 superContinue = false;
462 if (r < (*iter).toReal()){
463 bool superBreak = false;
464 for (int j=0; j<m_states.count(); j++){
465 if (m_states[j]->name() == iter.key()){
482 void QSGStochasticEngine::addToUpdateList(uint t, int idx)
484 for (int i=0; i<m_stateUpdates.count(); i++){
485 if (m_stateUpdates[i].first==t){
486 m_stateUpdates[i].second << idx;
488 }else if (m_stateUpdates[i].first > t){
491 m_stateUpdates.insert(i, qMakePair(t, tmpList));
497 m_stateUpdates << qMakePair(t, tmpList);