155int QQuickSpriteEngine::spriteFrames(
int sprite)
const
159 int state = m_things[sprite];
160 if (!m_sprites[state]->m_generatedCount)
161 return m_sprites[state]->frames();
164 if (m_sprites[state]->frameSync())
165 extra = m_startTimes[sprite];
166 else if (!m_duration[sprite])
167 return m_sprites[state]->frames();
169 extra = pseudospriteProgress(sprite, state);
170 if (m_sprites[state]->reverse())
171 extra = (m_sprites[state]->m_generatedCount - 1) - extra;
174 if (extra == m_sprites[state]->m_generatedCount - 1) {
175 const int framesRemaining = m_sprites[state]->frames() % m_sprites[state]->m_framesPerRow;
176 if (framesRemaining > 0)
177 return framesRemaining;
179 return m_sprites[state]->m_framesPerRow;
182int QQuickSpriteEngine::spriteDuration(
int sprite)
const
184 if (!m_duration[sprite] || !m_loaded)
185 return m_duration[sprite];
186 int state = m_things[sprite];
187 if (!m_sprites[state]->m_generatedCount)
188 return m_duration[sprite];
190 int extra = pseudospriteProgress(sprite, state, &rowDuration);
191 if (m_sprites[state]->reverse())
192 extra = (m_sprites[state]->m_generatedCount - 1) - extra;
194 if (extra == m_sprites[state]->m_generatedCount - 1) {
195 const int durationRemaining = m_duration[sprite] % rowDuration;
196 if (durationRemaining > 0)
197 return durationRemaining;
202int QQuickSpriteEngine::spriteY(
int sprite)
const
206 int state = m_things[sprite];
207 if (!m_sprites[state]->m_generatedCount)
208 return m_sprites[state]->m_rowY;
211 if (m_sprites[state]->frameSync())
212 extra = m_startTimes[sprite];
213 else if (!m_duration[sprite])
214 return m_sprites[state]->m_rowY;
216 extra = pseudospriteProgress(sprite, state);
217 if (m_sprites[state]->reverse())
218 extra = (m_sprites[state]->m_generatedCount - 1) - extra;
221 return m_sprites[state]->m_rowY + m_sprites[state]->m_frameHeight * extra;
224int QQuickSpriteEngine::spriteX(
int sprite)
const
228 int state = m_things[sprite];
229 if (!m_sprites[state]->m_generatedCount)
230 return m_sprites[state]->m_rowStartX;
233 if (m_sprites[state]->frameSync())
234 extra = m_startTimes[sprite];
235 else if (!m_duration[sprite])
236 return m_sprites[state]->m_rowStartX;
238 extra = pseudospriteProgress(sprite, state);
239 if (m_sprites[state]->reverse())
240 extra = (m_sprites[state]->m_generatedCount - 1) - extra;
244 return m_sprites[state]->m_rowStartX;
340QImage QQuickSpriteEngine::assembledImage(
int maxSize)
342 QQuickPixmap::Status stat = status();
343 if (!m_errorsPrinted && stat == QQuickPixmap::Error) {
344 for (QQuickSprite* s : std::as_const(m_sprites))
345 if (s->m_pix.isError())
346 qmlWarning(s) << s->m_pix.error();
347 m_errorsPrinted =
true;
350 if (stat != QQuickPixmap::Ready)
356 m_imageStateCount = 0;
357 qreal pixelRatio = 1.0;
359 for (QQuickSprite* state : std::as_const(m_sprites)) {
360 if (state->frames() > m_maxFrames)
361 m_maxFrames = state->frames();
363 QImage img = state->m_pix.image();
366 const QSize frameSize(state->m_frameWidth, state->m_frameHeight);
367 if (!(img.size() - frameSize).isValid()) {
368 qmlWarning(state).nospace() <<
"SpriteEngine: Invalid frame size " << frameSize <<
"."
369 " It's bigger than image size " << img.size() <<
".";
375 if (!state->m_frameWidth)
376 state->m_frameWidth = img.width() / state->frames();
378 if (!state->m_frameHeight)
379 state->m_frameHeight = img.height();
381 pixelRatio = qMax(pixelRatio, state->devicePixelRatio());
383 if (state->frames() * state->frameWidth() > maxSize){
385 static int divRoundUp(
int a,
int b){
return (a+b-1)/b;}
387 int rowsNeeded = helper::divRoundUp(state->frames(), (maxSize / state->frameWidth()));
388 if (h + rowsNeeded * state->frameHeight() > maxSize){
389 if (rowsNeeded * state->frameHeight() > maxSize)
390 qmlWarning(state) <<
"SpriteEngine: Animation too large to fit in one texture:" << state->source().toLocalFile();
392 qmlWarning(state) <<
"SpriteEngine: Animations too large to fit in one texture, pushed over the edge by:" << state->source().toLocalFile();
393 qmlWarning(state) <<
"SpriteEngine: Your texture max size today is " << maxSize;
396 state->m_generatedCount = rowsNeeded;
397 h += state->frameHeight() * rowsNeeded;
398 w = qMax(w, ((
int)(maxSize / state->frameWidth())) * state->frameWidth());
399 m_imageStateCount += rowsNeeded;
401 h += state->frameHeight();
402 w = qMax(w, state->frameWidth() * state->frames());
408 qWarning() <<
"SpriteEngine: Too many animations to fit in one texture...";
409 qWarning() <<
"SpriteEngine: Your texture max size today is " << maxSize;
414 QImage image(w * pixelRatio, h * pixelRatio, QImage::Format_ARGB32_Premultiplied);
415 image.setDevicePixelRatio(pixelRatio);
419 for (QQuickSprite* state : std::as_const(m_sprites)) {
420 QImage img(state->m_pix.image());
421 const int frameWidth = state->m_frameWidth;
422 const int frameHeight = state->m_frameHeight;
423 const int imgHeight = img.height() / img.devicePixelRatio();
424 const int imgWidth = img.width() / img.devicePixelRatio();
425 if (imgHeight == frameHeight && imgWidth < maxSize){
426 p.drawImage(QRect(0, y, state->m_frames * frameWidth, frameHeight),
428 QRect(state->m_frameX * img.devicePixelRatio(), 0, state->m_frames * frameWidth * img.devicePixelRatio(), frameHeight * img.devicePixelRatio()));
429 state->m_rowStartX = 0;
433 state->m_framesPerRow = w/frameWidth;
436 int curX = state->m_frameX;
437 int curY = state->m_frameY;
438 int framesLeft = state->frames();
439 while (framesLeft > 0){
440 if (w - x + curX <= imgWidth){
442 framesLeft -= copied/frameWidth;
443 p.drawImage(QRect(x, y, copied, frameHeight),
445 QRect(curX * img.devicePixelRatio(), curY * img.devicePixelRatio(), copied * img.devicePixelRatio(), frameHeight * img.devicePixelRatio()));
449 if (curX == imgWidth){
454 int copied = imgWidth - curX;
455 framesLeft -= copied/frameWidth;
456 p.drawImage(QRect(x, y, copied, frameHeight),
458 QRect(curX * img.devicePixelRatio(), curY * img.devicePixelRatio(), copied * img.devicePixelRatio(), frameHeight * img.devicePixelRatio()));
469#ifdef SPRITE_IMAGE_DEBUG
470 QString fPath = QDir::tempPath() +
"/SpriteImage.%1.png";
472 while (QFile::exists(fPath.arg(acc))) acc++;
473 image.save(fPath.arg(acc),
"PNG");
474 qDebug() <<
"Assembled image output to: " << fPath.arg(acc);
478 m_startedImageAssembly =
false;
516void QQuickStochasticEngine::restart(
int index)
518 bool randomStart = (m_startTimes.at(index) == NINF);
519 m_startTimes[index] = m_timeOffset;
521 m_startTimes[index] += m_advanceTimer.elapsed();
523 m_startTimes[index] -= QRandomGenerator::global()->bounded(m_duration.at(index));
524 int time = m_duration.at(index) + m_startTimes.at(index);
525 for (
int i=0; i<m_stateUpdates.size(); i++)
526 m_stateUpdates[i].second.removeAll(index);
527 if (m_duration.at(index) >= 0)
528 addToUpdateList(time, index);
531void QQuickSpriteEngine::restart(
int index)
533 bool randomStart = (m_startTimes.at(index) == NINF);
534 if (m_loaded && m_sprites.at(m_things.at(index))->frameSync()) {
535 m_startTimes[index] = 0;
536 if (randomStart && m_sprites.at(m_things.at(index))->m_generatedCount)
537 m_startTimes[index] += QRandomGenerator::global()->bounded(m_sprites.at(m_things.at(index))->m_generatedCount);
539 m_startTimes[index] = m_timeOffset;
541 m_startTimes[index] += m_advanceTimer.elapsed();
543 m_startTimes[index] -= QRandomGenerator::global()->bounded(m_duration.at(index));
544 int time = spriteDuration(index) + m_startTimes.at(index);
546 int curTime = m_timeOffset + (m_addAdvance ? m_advanceTimer.elapsed() : 0);
547 while (time < curTime)
548 time += spriteDuration(index);
551 for (
int i=0; i<m_stateUpdates.size(); i++)
552 m_stateUpdates[i].second.removeAll(index);
553 addToUpdateList(time, index);
569void QQuickSpriteEngine::advance(
int idx)
572 qWarning() << QLatin1String(
"QQuickSpriteEngine: Trying to advance sprites before sprites finish loading. Ignoring directive");
576 if (idx >= m_things.size())
578 if (m_duration.at(idx) == 0) {
579 if (m_sprites.at(m_things.at(idx))->frameSync()) {
582 if (m_startTimes.at(idx) < m_sprites.at(m_things.at(idx))->m_generatedCount) {
584 emit stateChanged(idx);
589 }
else if (m_startTimes.at(idx) + m_duration.at(idx)
590 >
int(m_timeOffset + (m_addAdvance ? m_advanceTimer.elapsed() : 0))) {
592 emit stateChanged(idx);
593 addToUpdateList(spriteStart(idx) + spriteDuration(idx) + (m_addAdvance ? m_advanceTimer.elapsed() : 0), idx);
596 int nextIdx = nextState(m_things.at(idx), idx);
597 m_things[idx] = nextIdx;
598 m_duration[idx] = m_states.at(nextIdx)->variedDuration();
600 emit m_states.at(nextIdx)->entered();
601 emit stateChanged(idx);
604int QQuickStochasticEngine::nextState(
int curState,
int curThing)
607 int goalPath = goalSeek(curState, curThing);
609 qreal r = QRandomGenerator::global()->generateDouble();
611 for (QVariantMap::const_iterator iter=m_states.at(curState)->m_to.constBegin();
612 iter!=m_states.at(curState)->m_to.constEnd(); ++iter)
613 total += (*iter).toReal();
615 for (QVariantMap::const_iterator iter= m_states.at(curState)->m_to.constBegin();
616 iter!=m_states.at(curState)->m_to.constEnd(); ++iter){
617 if (r < (*iter).toReal()){
618 bool superBreak =
false;
619 for (
int i=0; i<m_states.size(); i++){
620 if (m_states.at(i)->name() == iter.key()){
629 r -= (*iter).toReal();
659int QQuickStochasticEngine::goalSeek(
int curIdx,
int spriteIdx,
int dist)
662 if (m_goals.at(spriteIdx) != -1)
663 goalName = m_states.at(m_goals.at(spriteIdx))->name();
665 goalName = m_globalGoal;
666 if (goalName.isEmpty())
670 for (
int i=0; i<m_states.size(); i++)
671 if (m_states.at(curIdx)->name() == goalName)
674 dist = m_states.size();
675 QQuickStochasticState* curState = m_states.at(curIdx);
676 for (QVariantMap::const_iterator iter = curState->m_to.constBegin();
677 iter!=curState->m_to.constEnd(); ++iter){
678 if (iter.key() == goalName)
679 for (
int i=0; i<m_states.size(); i++)
680 if (m_states.at(i)->name() == goalName)
684 for (
int i=1; i<dist; i++){
685 for (QVariantMap::const_iterator iter = curState->m_to.constBegin();
686 iter!=curState->m_to.constEnd(); ++iter){
688 for (
int j=0; j<m_states.size(); j++)
689 if (m_states.at(j)->name() == iter.key())
690 if (goalSeek(j, spriteIdx, i) != -1)
695 if (!options.isEmpty()){
696 if (options.size()==1)
697 return *(options.begin());
699 qreal r = QRandomGenerator::global()->generateDouble();
701 for (QSet<
int>::const_iterator iter=options.constBegin();
702 iter!=options.constEnd(); ++iter)
703 total += curState->m_to.value(m_states.at((*iter))->name()).toReal();
705 for (QVariantMap::const_iterator iter = curState->m_to.constBegin();
706 iter!=curState->m_to.constEnd(); ++iter){
707 bool superContinue =
true;
708 for (
int j=0; j<m_states.size(); j++)
709 if (m_states.at(j)->name() == iter.key())
710 if (options.contains(j))
711 superContinue =
false;
714 if (r < (*iter).toReal()){
715 bool superBreak =
false;
716 for (
int j=0; j<m_states.size(); j++){
717 if (m_states.at(j)->name() == iter.key()){