Per-frame Sprites patch one
[profile/ivi/qtdeclarative.git] / src / quick / items / qquickspriteengine.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: http://www.qt-project.org/
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     if (m_duration[index] > 0)
343         addToUpdateList(time, index);
344 }
345
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)
349 {
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);
361 }
362
363 int QQuickStochasticEngine::nextState(int curState, int curThing)
364 {
365     int nextIdx = -1;
366     int goalPath = goalSeek(curState, curThing);
367     if (goalPath == -1){//Random
368         qreal r =(qreal) qrand() / (qreal) RAND_MAX;
369         qreal total = 0.0;
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();
373         r*=total;
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()){
380                         nextIdx = i;
381                         superBreak = true;
382                         break;
383                     }
384                 }
385                 if (superBreak)
386                     break;
387             }
388             r -= (*iter).toReal();
389         }
390     }else{//Random out of shortest paths to goal
391         nextIdx = goalPath;
392     }
393     if (nextIdx == -1)//No 'to' states means stay here
394         nextIdx = curState;
395     return nextIdx;
396 }
397
398 uint QQuickStochasticEngine::updateSprites(uint time)//### would returning a list of changed idxs be faster than signals?
399 {
400     //Sprite State Update;
401     m_timeOffset = time;
402     while (!m_stateUpdates.isEmpty() && time >= m_stateUpdates.first().first){
403         foreach (int idx, m_stateUpdates.first().second)
404             advance(idx);
405         m_stateUpdates.pop_front();
406     }
407
408     m_advanceTime.start();
409     if (m_stateUpdates.isEmpty())
410         return -1;
411     return m_stateUpdates.first().first;
412 }
413
414 int QQuickStochasticEngine::goalSeek(int curIdx, int spriteIdx, int dist)
415 {
416     QString goalName;
417     if (m_goals[spriteIdx] != -1)
418         goalName = m_states[m_goals[spriteIdx]]->name();
419     else
420         goalName = m_globalGoal;
421     if (goalName.isEmpty())
422         return -1;
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)
427             return curIdx;
428     if (dist < 0)
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)
436                     return i;
437     }
438     QSet<int> options;
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++){
442             int option = -1;
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)
446                         option = j;
447             if (option != -1)
448                 options << option;
449         }
450         if (!options.isEmpty()){
451             if (options.count()==1)
452                 return *(options.begin());
453             int option = -1;
454             qreal r =(qreal) qrand() / (qreal) RAND_MAX;
455             qreal total = 0;
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();
459             r *= total;
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;
467                 if (superContinue)
468                     continue;
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()){
473                             option = j;
474                             superBreak = true;
475                             break;
476                         }
477                     }
478                     if (superBreak)
479                         break;
480                 }
481                 r-=(*iter).toReal();
482             }
483             return option;
484         }
485     }
486     return -1;
487 }
488
489 void QQuickStochasticEngine::addToUpdateList(uint t, int idx)
490 {
491     for (int i=0; i<m_stateUpdates.count(); i++){
492         if (m_stateUpdates[i].first==t){
493             m_stateUpdates[i].second << idx;
494             return;
495         }else if (m_stateUpdates[i].first > t){
496             QList<int> tmpList;
497             tmpList << idx;
498             m_stateUpdates.insert(i, qMakePair(t, tmpList));
499             return;
500         }
501     }
502     QList<int> tmpList;
503     tmpList << idx;
504     m_stateUpdates << qMakePair(t, tmpList);
505 }
506
507 QT_END_NAMESPACE