Merge branch 'refactor'
[profile/ivi/qtdeclarative.git] / src / declarative / items / qsgspriteengine.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the Declarative module of the Qt Toolkit.
8 **
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.
17 **
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.
21 **
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.
29 **
30 ** Other Usage
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.
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qsgspriteengine_p.h"
43 #include "qsgsprite_p.h"
44 #include <QDebug>
45 #include <QPainter>
46 #include <QSet>
47 #include <QtGui>
48
49 QT_BEGIN_NAMESPACE
50
51 /* TODO: Split out image logic from stochastic state logic
52    Also make sharable
53    Also solve the state data initialization/transfer issue so as to not need to make friends
54 */
55
56 QSGStochasticEngine::QSGStochasticEngine(QObject *parent) :
57     QObject(parent), m_timeOffset(0)
58 {
59     //Default size 1
60     setCount(1);
61     m_advanceTime.start();
62 }
63
64 QSGStochasticEngine::QSGStochasticEngine(QList<QSGStochasticState*> states, QObject *parent) :
65     QObject(parent), m_states(states), m_timeOffset(0)
66 {
67     //Default size 1
68     setCount(1);
69     m_advanceTime.start();
70 }
71
72 QSGStochasticEngine::~QSGStochasticEngine()
73 {
74 }
75
76 QSGSpriteEngine::QSGSpriteEngine(QObject *parent)
77     : QSGStochasticEngine(parent)
78 {
79 }
80
81 QSGSpriteEngine::QSGSpriteEngine(QList<QSGSprite*> sprites, QObject *parent)
82     : QSGStochasticEngine(parent)
83 {
84     foreach (QSGSprite* sprite, sprites)
85         m_states << (QSGStochasticState*)sprite;
86 }
87
88 QSGSpriteEngine::~QSGSpriteEngine()
89 {
90 }
91
92
93 int QSGSpriteEngine::maxFrames()
94 {
95     return m_maxFrames;
96 }
97
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
104 */
105 int QSGSpriteEngine::spriteState(int sprite)
106 {
107     int state = m_things[sprite];
108     if (!m_sprites[state]->m_generatedCount)
109         return state;
110     int rowDuration = m_duration[sprite] * m_sprites[state]->m_framesPerRow;
111     int extra = (m_timeOffset - m_startTimes[sprite])/rowDuration;
112     return state + extra;
113 }
114
115 int QSGSpriteEngine::spriteStart(int sprite)
116 {
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;
123 }
124
125 int QSGSpriteEngine::spriteFrames(int sprite)
126 {
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;
134     else
135         return m_sprites[state]->m_framesPerRow;
136 }
137
138 int QSGSpriteEngine::spriteDuration(int sprite)
139 {
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;
147     else
148         return rowDuration;
149 }
150
151 int QSGSpriteEngine::spriteCount()//TODO: Actually image state count, need to rename these things to make sense together
152 {
153     return m_imageStateCount;
154 }
155
156 void QSGStochasticEngine::setGoal(int state, int sprite, bool jump)
157 {
158     if (sprite >= m_things.count() || state >= m_states.count())
159         return;
160     if (!jump){
161         m_goals[sprite] = state;
162         return;
163     }
164
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;
170     restart(sprite);
171     emit stateChanged(sprite);
172     emit m_states[state]->entered();
173     return;
174 }
175
176 QImage QSGSpriteEngine::assembledImage()
177 {
178     int frameHeight = 0;
179     int frameWidth = 0;
180     m_maxFrames = 0;
181     m_imageStateCount = 0;
182
183     int maxSize;
184     glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize);
185     foreach (QSGStochasticState* s, m_states){
186         QSGSprite* sprite = qobject_cast<QSGSprite*>(s);
187         if (sprite)
188             m_sprites << sprite;
189         else
190             qDebug() << "Error: Non-sprite in QSGSpriteEngine";
191     }
192
193     foreach (QSGSprite* state, m_sprites){
194         if (state->frames() > m_maxFrames)
195             m_maxFrames = state->frames();
196
197         QImage img(state->source().toLocalFile());
198         if (img.isNull()) {
199             qWarning() << "SpriteEngine: loading image failed..." << state->source().toLocalFile();
200             return QImage();
201         }
202
203         //Check that the frame sizes are the same within one engine
204         int imgWidth = state->frameWidth();
205         if (!imgWidth)
206             imgWidth = img.width() / state->frames();
207         if (frameWidth){
208             if (imgWidth != frameWidth){
209                 qWarning() << "SpriteEngine: Irregular frame width..." << state->source().toLocalFile();
210                 return QImage();
211             }
212         }else{
213             frameWidth = imgWidth;
214         }
215
216         int imgHeight = state->frameHeight();
217         if (!imgHeight)
218             imgHeight = img.height();
219         if (frameHeight){
220             if (imgHeight!=frameHeight){
221                 qWarning() << "SpriteEngine: Irregular frame height..." << state->source().toLocalFile();
222                 return QImage();
223             }
224         }else{
225             frameHeight = imgHeight;
226         }
227
228         if (state->frames() * frameWidth > maxSize){
229             struct helper{
230                 static int divRoundUp(int a, int b){return (a+b-1)/b;}
231             };
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;
236             }
237             state->m_generatedCount = rowsNeeded;
238             m_imageStateCount += rowsNeeded;
239         }else{
240             m_imageStateCount++;
241         }
242     }
243
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);
248     image.fill(0);
249     QPainter p(&image);
250     int y = 0;
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);
255             y += frameHeight;
256         }else{
257             state->m_framesPerRow = image.width()/frameWidth;
258             int x = 0;
259             int curX = 0;
260             int curY = 0;
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));
268                     y += frameHeight;
269                     curX += copied;
270                     x = 0;
271                     if (curX == img.width()){
272                         curX = 0;
273                         curY += frameHeight;
274                     }
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));
280                     curY += frameHeight;
281                     x += copied;
282                     curX = 0;
283                 }
284             }
285             if (x)
286                 y += frameHeight;
287         }
288     }
289
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;
293         return QImage();
294     }
295     return image;
296 }
297
298 void QSGStochasticEngine::setCount(int c)
299 {
300     m_things.resize(c);
301     m_goals.resize(c);
302     m_duration.resize(c);
303     m_startTimes.resize(c);
304 }
305
306 void QSGStochasticEngine::start(int index, int state)
307 {
308     if (index >= m_things.count())
309         return;
310     m_things[index] = state;
311     m_duration[index] = m_states[state]->variedDuration();
312     m_goals[index] = -1;
313     restart(index);
314 }
315
316 void QSGStochasticEngine::stop(int index)
317 {
318     if (index >= m_things.count())
319         return;
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);
323 }
324
325 void QSGStochasticEngine::restart(int index)
326 {
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);
332 }
333
334 uint QSGStochasticEngine::updateSprites(uint time)//### would returning a list of changed idxs be faster than signals?
335 {
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];
343             int nextIdx = -1;
344             int goalPath = goalSeek(stateIdx, idx);
345             if (goalPath == -1){//Random
346                 qreal r =(qreal) qrand() / (qreal) RAND_MAX;
347                 qreal total = 0.0;
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();
351                 r*=total;
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()){
358                                 nextIdx = i;
359                                 superBreak = true;
360                                 break;
361                             }
362                         }
363                         if (superBreak)
364                             break;
365                     }
366                     r -= (*iter).toReal();
367                 }
368             }else{//Random out of shortest paths to goal
369                 nextIdx = goalPath;
370             }
371             if (nextIdx == -1)//No to states means stay here
372                 nextIdx = stateIdx;
373
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();
380             }
381             addToUpdateList((m_duration[idx] * m_states[nextIdx]->frames()) + time, idx);
382         }
383         m_stateUpdates.pop_front();
384     }
385
386     m_timeOffset = time;
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);
391     }
392     if (m_stateUpdates.isEmpty())
393         return -1;
394     return m_stateUpdates.first().first;
395 }
396
397 int QSGStochasticEngine::goalSeek(int curIdx, int spriteIdx, int dist)
398 {
399     QString goalName;
400     if (m_goals[spriteIdx] != -1)
401         goalName = m_states[m_goals[spriteIdx]]->name();
402     else
403         goalName = m_globalGoal;
404     if (goalName.isEmpty())
405         return -1;
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)
410             return curIdx;
411     if (dist < 0)
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)
419                     return i;
420     }
421     QSet<int> options;
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++){
425             int option = -1;
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)
429                         option = j;
430             if (option != -1)
431                 options << option;
432         }
433         if (!options.isEmpty()){
434             if (options.count()==1)
435                 return *(options.begin());
436             int option = -1;
437             qreal r =(qreal) qrand() / (qreal) RAND_MAX;
438             qreal total;
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();
442             r *= total;
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;
450                 if (superContinue)
451                     continue;
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()){
456                             option = j;
457                             superBreak = true;
458                             break;
459                         }
460                     }
461                     if (superBreak)
462                         break;
463                 }
464                 r-=(*iter).toReal();
465             }
466             return option;
467         }
468     }
469     return -1;
470 }
471
472 void QSGStochasticEngine::addToUpdateList(uint t, int idx)
473 {
474     for (int i=0; i<m_stateUpdates.count(); i++){
475         if (m_stateUpdates[i].first==t){
476             m_stateUpdates[i].second << idx;
477             return;
478         }else if (m_stateUpdates[i].first > t){
479             QList<int> tmpList;
480             tmpList << idx;
481             m_stateUpdates.insert(i, qMakePair(t, tmpList));
482             return;
483         }
484     }
485     QList<int> tmpList;
486     tmpList << idx;
487     m_stateUpdates << qMakePair(t, tmpList);
488 }
489
490 QT_END_NAMESPACE