Fix sprite chopping
[profile/ivi/qtdeclarative.git] / src / quick / items / qquickspriteengine.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 "qquickspriteengine_p.h"
43 #include "qquicksprite_p.h"
44 #include <QDebug>
45 #include <QPainter>
46 #include <QSet>
47 #include <QtGui>
48
49 QT_BEGIN_NAMESPACE
50
51 /* TODO:
52    make sharable?
53    solve the state data initialization/transfer issue so as to not need to make friends
54 */
55
56 QQuickStochasticEngine::QQuickStochasticEngine(QObject *parent) :
57     QObject(parent), m_timeOffset(0)
58 {
59     //Default size 1
60     setCount(1);
61     m_advanceTime.start();
62 }
63
64 QQuickStochasticEngine::QQuickStochasticEngine(QList<QQuickStochasticState*> 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 QQuickStochasticEngine::~QQuickStochasticEngine()
73 {
74 }
75
76 QQuickSpriteEngine::QQuickSpriteEngine(QObject *parent)
77     : QQuickStochasticEngine(parent)
78 {
79 }
80
81 QQuickSpriteEngine::QQuickSpriteEngine(QList<QQuickSprite*> sprites, QObject *parent)
82     : QQuickStochasticEngine(parent)
83 {
84     foreach (QQuickSprite* sprite, sprites)
85         m_states << (QQuickStochasticState*)sprite;
86 }
87
88 QQuickSpriteEngine::~QQuickSpriteEngine()
89 {
90 }
91
92
93 int QQuickSpriteEngine::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 QQuickSpriteEngine::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 QQuickSpriteEngine::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 QQuickSpriteEngine::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 QQuickSpriteEngine::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 QQuickSpriteEngine::spriteY(int sprite)
152 {
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;
159 }
160
161 int QQuickSpriteEngine::spriteWidth(int sprite)
162 {
163     int state = m_things[sprite];
164     return m_sprites[state]->m_frameWidth;
165 }
166
167 int QQuickSpriteEngine::spriteHeight(int sprite)
168 {
169     int state = m_things[sprite];
170     return m_sprites[state]->m_frameHeight;
171 }
172
173 int QQuickSpriteEngine::spriteCount()//TODO: Actually image state count, need to rename these things to make sense together
174 {
175     return m_imageStateCount;
176 }
177
178 void QQuickStochasticEngine::setGoal(int state, int sprite, bool jump)
179 {
180     if (sprite >= m_things.count() || state >= m_states.count()
181             || sprite < 0 || state < 0)
182         return;
183     if (!jump){
184         m_goals[sprite] = state;
185         return;
186     }
187
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;
193     restart(sprite);
194     emit stateChanged(sprite);
195     emit m_states[state]->entered();
196     return;
197 }
198
199 QImage QQuickSpriteEngine::assembledImage()
200 {
201     int h = 0;
202     int w = 0;
203     m_maxFrames = 0;
204     m_imageStateCount = 0;
205     int maxSize = 0;
206
207     glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize);
208     foreach (QQuickStochasticState* s, m_states){
209         QQuickSprite* sprite = qobject_cast<QQuickSprite*>(s);
210         if (sprite)
211             m_sprites << sprite;
212         else
213             qDebug() << "Error: Non-sprite in QQuickSpriteEngine";
214     }
215
216     foreach (QQuickSprite* state, m_sprites){
217         if (state->frames() > m_maxFrames)
218             m_maxFrames = state->frames();
219
220         QImage img(state->source().toLocalFile());
221         if (img.isNull()) {
222             qWarning() << "SpriteEngine: loading image failed..." << state->source().toLocalFile();
223             return QImage();
224         }
225
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();
229
230         if (!state->m_frameHeight)
231             state->m_frameHeight = img.height();
232
233         if (state->frames() * state->frameWidth() > maxSize){
234             struct helper{
235                 static int divRoundUp(int a, int b){return (a+b-1)/b;}
236             };
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;
241             }
242             state->m_generatedCount = rowsNeeded;
243             h += state->frameHeight() * rowsNeeded;
244             w = qMax(w, ((int)(maxSize / state->frameWidth())) * state->frameWidth());
245             m_imageStateCount += rowsNeeded;
246         }else{
247             h += state->frameHeight();
248             w = qMax(w, state->frameWidth() * state->frames());
249             m_imageStateCount++;
250         }
251     }
252
253     //maxFrames is max number in a line of the texture
254     QImage image(w, h, QImage::Format_ARGB32);
255     image.fill(0);
256     QPainter p(&image);
257     int y = 0;
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);
264             state->m_rowY = y;
265             y += frameHeight;
266         }else{//Chopping up image case
267             state->m_framesPerRow = image.width()/frameWidth;
268             state->m_rowY = y;
269             int x = 0;
270             int curX = 0;
271             int curY = 0;
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));
279                     y += frameHeight;
280                     curX += copied;
281                     x = 0;
282                     if (curX == img.width()){
283                         curX = 0;
284                         curY += frameHeight;
285                     }
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));
291                     curY += frameHeight;
292                     x += copied;
293                     curX = 0;
294                 }
295             }
296             if (x)
297                 y += frameHeight;
298         }
299     }
300
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;
304         return QImage();
305     }
306     return image;
307 }
308
309 void QQuickStochasticEngine::setCount(int c)
310 {
311     m_things.resize(c);
312     m_goals.resize(c);
313     m_duration.resize(c);
314     m_startTimes.resize(c);
315 }
316
317 void QQuickStochasticEngine::start(int index, int state)
318 {
319     if (index >= m_things.count())
320         return;
321     m_things[index] = state;
322     m_duration[index] = m_states[state]->variedDuration();
323     m_goals[index] = -1;
324     restart(index);
325 }
326
327 void QQuickStochasticEngine::stop(int index)
328 {
329     if (index >= m_things.count())
330         return;
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);
334 }
335
336 void QQuickStochasticEngine::restart(int index)
337 {
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);
343 }
344
345 uint QQuickStochasticEngine::updateSprites(uint time)//### would returning a list of changed idxs be faster than signals?
346 {
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];
354             int nextIdx = -1;
355             int goalPath = goalSeek(stateIdx, idx);
356             if (goalPath == -1){//Random
357                 qreal r =(qreal) qrand() / (qreal) RAND_MAX;
358                 qreal total = 0.0;
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();
362                 r*=total;
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()){
369                                 nextIdx = i;
370                                 superBreak = true;
371                                 break;
372                             }
373                         }
374                         if (superBreak)
375                             break;
376                     }
377                     r -= (*iter).toReal();
378                 }
379             }else{//Random out of shortest paths to goal
380                 nextIdx = goalPath;
381             }
382             if (nextIdx == -1)//No to states means stay here
383                 nextIdx = stateIdx;
384
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();
391             }
392             addToUpdateList((m_duration[idx] * m_states[nextIdx]->frames()) + time, idx);
393         }
394         m_stateUpdates.pop_front();
395     }
396
397     m_timeOffset = time;
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);
402     }
403     if (m_stateUpdates.isEmpty())
404         return -1;
405     return m_stateUpdates.first().first;
406 }
407
408 int QQuickStochasticEngine::goalSeek(int curIdx, int spriteIdx, int dist)
409 {
410     QString goalName;
411     if (m_goals[spriteIdx] != -1)
412         goalName = m_states[m_goals[spriteIdx]]->name();
413     else
414         goalName = m_globalGoal;
415     if (goalName.isEmpty())
416         return -1;
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)
421             return curIdx;
422     if (dist < 0)
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)
430                     return i;
431     }
432     QSet<int> options;
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++){
436             int option = -1;
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)
440                         option = j;
441             if (option != -1)
442                 options << option;
443         }
444         if (!options.isEmpty()){
445             if (options.count()==1)
446                 return *(options.begin());
447             int option = -1;
448             qreal r =(qreal) qrand() / (qreal) RAND_MAX;
449             qreal total = 0;
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();
453             r *= total;
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;
461                 if (superContinue)
462                     continue;
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()){
467                             option = j;
468                             superBreak = true;
469                             break;
470                         }
471                     }
472                     if (superBreak)
473                         break;
474                 }
475                 r-=(*iter).toReal();
476             }
477             return option;
478         }
479     }
480     return -1;
481 }
482
483 void QQuickStochasticEngine::addToUpdateList(uint t, int idx)
484 {
485     for (int i=0; i<m_stateUpdates.count(); i++){
486         if (m_stateUpdates[i].first==t){
487             m_stateUpdates[i].second << idx;
488             return;
489         }else if (m_stateUpdates[i].first > t){
490             QList<int> tmpList;
491             tmpList << idx;
492             m_stateUpdates.insert(i, qMakePair(t, tmpList));
493             return;
494         }
495     }
496     QList<int> tmpList;
497     tmpList << idx;
498     m_stateUpdates << qMakePair(t, tmpList);
499 }
500
501 QT_END_NAMESPACE