Initial import from qtquick2.
[profile/ivi/qtdeclarative.git] / src / imports / particles / spriteengine.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 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "spriteengine.h"
43 #include "spritestate.h"
44 #include <QDebug>
45 #include <QPainter>
46 #include <QSet>
47 #include <QtOpenGL>
48
49 QT_BEGIN_NAMESPACE
50
51 SpriteEngine::SpriteEngine(QObject *parent) :
52     QObject(parent), m_timeOffset(0)
53 {
54     //Default size 1
55     setCount(1);
56     m_advanceTime.start();
57 }
58
59 SpriteEngine::SpriteEngine(QList<SpriteState*> states, QObject *parent) :
60     QObject(parent), m_states(states), m_timeOffset(0)
61 {
62     //Default size 1
63     setCount(1);
64     m_advanceTime.start();
65 }
66
67 SpriteEngine::~SpriteEngine()
68 {
69 }
70
71 int SpriteEngine::maxFrames()
72 {
73    int max = 0;
74    foreach(SpriteState* s, m_states)
75        if(s->frames() > max)
76            max = s->frames();
77    return max;
78 }
79
80 void SpriteEngine::setGoal(int state, int sprite, bool jump)
81 {
82     if(sprite >= m_sprites.count() || state >= m_states.count())
83         return;
84     if(!jump){
85         m_goals[sprite] = state;
86         return;
87     }
88
89     if(m_sprites[sprite] == state)
90         return;//Already there
91     m_sprites[sprite] = state;
92     m_goals[sprite] = -1;
93     restartSprite(sprite);
94     return;
95 }
96
97 QImage SpriteEngine::assembledImage()
98 {
99     int frameHeight = 0;
100     int frameWidth = 0;
101     m_maxFrames = 0;
102
103     int maxSize;
104     glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize);
105
106     foreach(SpriteState* state, m_states){
107         if(state->frames() > m_maxFrames)
108             m_maxFrames = state->frames();
109
110         QImage img(state->source().toLocalFile());
111         if (img.isNull()) {
112             qWarning() << "SpriteEngine: loading image failed..." << state->source().toLocalFile();
113             return QImage();
114         }
115
116         if(frameWidth){
117             if(img.width() / state->frames() != frameWidth){
118                 qWarning() << "SpriteEngine: Irregular frame width..." << state->source().toLocalFile();
119                 return QImage();
120             }
121         }else{
122             frameWidth = img.width() / state->frames();
123         }
124         if(img.width() > maxSize){
125             qWarning() << "SpriteEngine: Animation too wide..." << state->source().toLocalFile();
126             return QImage();
127         }
128
129         if(frameHeight){
130             if(img.height()!=frameHeight){
131                 qWarning() << "SpriteEngine: Irregular frame height..." << state->source().toLocalFile();
132                 return QImage();
133             }
134         }else{
135             frameHeight = img.height();
136         }
137
138         if(img.height() > maxSize){
139             qWarning() << "SpriteEngine: Animation too tall..." << state->source().toLocalFile();
140             return QImage();
141         }
142     }
143
144     QImage image(frameWidth * m_maxFrames, frameHeight * m_states.count(), QImage::Format_ARGB32);
145     image.fill(0);
146     QPainter p(&image);
147     int y = 0;
148     foreach(SpriteState* state, m_states){
149         QImage img(state->source().toLocalFile());
150         p.drawImage(0,y,img);
151         y += frameHeight;
152     }
153
154     if(image.height() > maxSize){
155         qWarning() << "SpriteEngine: Too many animations to fit in one texture...";
156         return QImage();
157     }
158     return image;
159 }
160
161 void SpriteEngine::setCount(int c)
162 {
163     m_sprites.resize(c);
164     m_goals.resize(c);
165     m_startTimes.resize(c);
166 }
167
168 void SpriteEngine::startSprite(int index)
169 {
170     if(index >= m_sprites.count())
171         return;
172     m_sprites[index] = 0;
173     m_goals[index] = -1;
174     restartSprite(index);
175 }
176
177 void SpriteEngine::restartSprite(int index)
178 {
179     m_startTimes[index] = m_timeOffset + m_advanceTime.elapsed();
180     int time = m_states[m_sprites[index]]->duration() * m_states[m_sprites[index]]->frames() + m_startTimes[index];
181     for(int i=0; i<m_stateUpdates.count(); i++)
182         m_stateUpdates[i].second.removeAll(index);
183     addToUpdateList(time, index);
184 }
185
186 uint SpriteEngine::updateSprites(uint time)
187 {
188     //Sprite State Update;
189     while(!m_stateUpdates.isEmpty() && time >= m_stateUpdates.first().first){
190         foreach(int idx, m_stateUpdates.first().second){
191             if(idx >= m_sprites.count())
192                 continue;//TODO: Proper fix(because this does happen and I'm just ignoring it)
193             int stateIdx = m_sprites[idx];
194             int nextIdx = -1;
195             int goalPath = goalSeek(stateIdx, idx);
196             if(goalPath == -1){//Random
197                 qreal r =(qreal) qrand() / (qreal) RAND_MAX;
198                 qreal total = 0.0;
199                 for(QVariantMap::const_iterator iter=m_states[stateIdx]->m_to.constBegin();
200                     iter!=m_states[stateIdx]->m_to.constEnd(); iter++)
201                     total += (*iter).toReal();
202                 r*=total;
203                 for(QVariantMap::const_iterator iter= m_states[stateIdx]->m_to.constBegin();
204                         iter!=m_states[stateIdx]->m_to.constEnd(); iter++){
205                     if(r < (*iter).toReal()){
206                         bool superBreak = false;
207                         for(int i=0; i<m_states.count(); i++){
208                             if(m_states[i]->name() == iter.key()){
209                                 nextIdx = i;
210                                 superBreak = true;
211                                 break;
212                             }
213                         }
214                         if(superBreak)
215                             break;
216                     }
217                     r -= (*iter).toReal();
218                 }
219             }else{//Random out of shortest paths to goal
220                 nextIdx = goalPath;
221             }
222             if(nextIdx == -1)//No to states means stay here
223                 nextIdx = stateIdx;
224
225             m_sprites[idx] = nextIdx;
226             m_startTimes[idx] = time;
227             //TODO: emit something?
228             addToUpdateList((m_states[nextIdx]->duration() * m_states[nextIdx]->frames()) + time, idx);
229         }
230         m_stateUpdates.pop_front();
231     }
232
233     m_timeOffset = time;
234     m_advanceTime.start();
235     if(m_stateUpdates.isEmpty())
236         return -1;
237     return m_stateUpdates.first().first;
238 }
239
240 int SpriteEngine::goalSeek(int curIdx, int spriteIdx, int dist)
241 {
242     QString goalName;
243     if(m_goals[spriteIdx] != -1)
244         goalName = m_states[m_goals[spriteIdx]]->name();
245     else
246         goalName = m_globalGoal;
247     if(goalName.isEmpty())
248         return -1;
249     //TODO: caching instead of excessively redoing iterative deepening (which was chosen arbitarily anyways)
250     // Paraphrased - implement in an *efficient* manner
251     for(int i=0; i<m_states.count(); i++)
252         if(m_states[curIdx]->name() == goalName)
253             return curIdx;
254     if(dist < 0)
255         dist = m_states.count();
256     SpriteState* curState = m_states[curIdx];
257     for(QVariantMap::const_iterator iter = curState->m_to.constBegin();
258         iter!=curState->m_to.constEnd(); iter++){
259         if(iter.key() == goalName)
260             for(int i=0; i<m_states.count(); i++)
261                 if(m_states[i]->name() == goalName)
262                     return i;
263     }
264     QSet<int> options;
265     for(int i=1; i<dist; i++){
266         for(QVariantMap::const_iterator iter = curState->m_to.constBegin();
267             iter!=curState->m_to.constEnd(); iter++){
268             int option = -1;
269             for(int j=0; j<m_states.count(); j++)//One place that could be a lot more efficient...
270                 if(m_states[j]->name() == iter.key())
271                     if(goalSeek(j, spriteIdx, i) != -1)
272                         option = j;
273             if(option != -1)
274                 options << option;
275         }
276         if(!options.isEmpty()){
277             if(options.count()==1)
278                 return *(options.begin());
279             int option = -1;
280             qreal r =(qreal) qrand() / (qreal) RAND_MAX;
281             qreal total;
282             for(QSet<int>::const_iterator iter=options.constBegin();
283                 iter!=options.constEnd(); iter++)
284                 total += curState->m_to.value(m_states[(*iter)]->name()).toReal();
285             r *= total;
286             for(QVariantMap::const_iterator iter = curState->m_to.constBegin();
287                 iter!=curState->m_to.constEnd(); iter++){
288                 bool superContinue = true;
289                 for(int j=0; j<m_states.count(); j++)
290                     if(m_states[j]->name() == iter.key())
291                         if(options.contains(j))
292                             superContinue = false;
293                 if(superContinue)
294                     continue;
295                 if(r < (*iter).toReal()){
296                     bool superBreak = false;
297                     for(int j=0; j<m_states.count(); j++){
298                         if(m_states[j]->name() == iter.key()){
299                             option = j;
300                             superBreak = true;
301                             break;
302                         }
303                     }
304                     if(superBreak)
305                         break;
306                 }
307                 r-=(*iter).toReal();
308             }
309             return option;
310         }
311     }
312     return -1;
313 }
314
315 void SpriteEngine::addToUpdateList(uint t, int idx)
316 {
317     for(int i=0; i<m_stateUpdates.count(); i++){
318         if(m_stateUpdates[i].first==t){
319             m_stateUpdates[i].second << idx;
320             return;
321         }else if(m_stateUpdates[i].first > t){
322             QList<int> tmpList;
323             tmpList << idx;
324             m_stateUpdates.insert(i, qMakePair(t, tmpList));
325             return;
326         }
327     }
328     QList<int> tmpList;
329     tmpList << idx;
330     m_stateUpdates << qMakePair(t, tmpList);
331 }
332
333 QT_END_NAMESPACE