Fix nested tag with "Ping-Pong" repeat mode causes to skip the first frame of the parent tag (fix #4271)

This commit is contained in:
Gaspar Capello 2024-04-08 19:01:27 -03:00 committed by David Capello
parent d886e20f6c
commit 99e6e7bd82
2 changed files with 582 additions and 29 deletions

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (C) 2021-2022 Igara Studio S.A.
// Copyright (C) 2021-2024 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -185,8 +185,19 @@ void Playback::handleEnterFrame(const frame_t frameDelta, const bool firstTime)
}
else {
addTag(t, false, forward);
if (!firstTime)
if (!firstTime) {
goToFirstTagFrame(t);
// Handle cases where inner tags will jump to different
// frames several times recursively (e.g. one reverse
// inside other reverse).
//
// Consideration for tests:
// Playback.OnePingPongInsidePingPongReverse
// Playback.OneReverseInsidePingPongReverse
// Playback.OnePingPongReverseInsideReverse
if (frame != m_frame)
handleEnterFrame(frameDelta, false);
}
}
}
}
@ -457,10 +468,11 @@ bool Playback::decrementRepeat(const frame_t frameDelta)
// New frame outside the tag
frame_t newFrame;
if (rewind) {
if (rewind && !m_playing.empty()) {
newFrame = firstTagFrame(m_playing.back()->tag);
}
else {
// Note that 'tag' means 'the last tag removed from m_playing'
newFrame = (frameDelta * forward < 0 ? tag->fromFrame()-1: tag->toFrame()+1);
}
@ -473,10 +485,100 @@ bool Playback::decrementRepeat(const frame_t frameDelta)
stop();
return false;
}
if (newFrame < 0)
newFrame = m_sprite->lastFrame();
else if (newFrame > m_sprite->lastFrame())
newFrame = 0;
if (newFrame < 0) {
// m_playing.empty() should never happen, because the only
// way to have "newFrame < 0" is if we have a tag on
// m_playing in REVERSE or PING_PONG_REVERSE which frame 0
// is contained into that tag.
ASSERT(!m_playing.empty());
if (m_playing.empty()) {
newFrame = m_sprite->lastFrame();
}
else {
// Special cases arise with PING_PONG_REVERSE aniDir and when
// the begining of the tag range matches with the first frame of
// the sprite.
// Consideration for tests inside:
// Playback.OnePingPongInsideOther
// A A
// >-------< >-------<
// B B
// <---> >---<
// 0 1 2 3 4 0 1 2 3 4
PlayTag* parentPlaying = m_playing.back().get();
// When parentPlaying is PING_PONG_REVERSE
// the next frame will be defined according:
// 1. The playloop has more repetitions to decrement
// --> go to the next frame of the 'tag'
// 2. The playloop has no more repetitions to decrement
// --> Start all the playloop again.
if (parentPlaying->repeat > 1) {
if (parentPlaying->tag->aniDir() == AniDir::PING_PONG_REVERSE)
parentPlaying->invertForward();
--parentPlaying->repeat;
newFrame = tag->toFrame() + 1;
}
else
continue;
}
}
else if (newFrame > m_sprite->lastFrame()) {
// If all the tags were played and
// the 'tag' range == timeline range and
// the 'tag' is PING_PONG_REVERSE -->
// The playloop has to start on the last frame of
// the timeline, or the first frame of the most nested
// tag (on reverse direction).
// Consideration for tests:
// Playback.WithTagRepetitions
// Playback.OnePingPongInsideOther
// Playback.OnePingPongInsideOther14, 15, 18 and 19
// A <-- last tag removed from 'm_playing', i.e. 'tag'
// >-----<
// B <-- most nested tag
// ***-***
// 0 1 2 3
if (m_playing.empty() &&
tag->aniDir() == AniDir::PING_PONG_REVERSE &&
tag->fromFrame() == 0 &&
tag->toFrame() == m_sprite->lastFrame()) {
m_frame = m_sprite->lastFrame();
handleEnterFrame(frameDelta, false);
if (m_playing.size() > 1) {
m_playing.back()->invertForward();
goToFirstTagFrame(m_playing.back()->tag);
}
return false;
}
// 'tag' is contained by other tag and the last frame of each tag
// matches in the last frame of the sprite
if (!m_playing.empty() &&
tag->toFrame() == m_playing.back()->tag->toFrame()) {
PlayTag* parentPlaying = m_playing.back().get();
// The parentPlaying has no more repetitions to decrement
// --> continue to remove the 'parentTag'
if (parentPlaying->repeat <= 1)
continue;
// Consideration for test:
// Playback.OnePingPongInsideOther
if (parentPlaying->tag->aniDir() == AniDir::PING_PONG ||
parentPlaying->tag->aniDir() == AniDir::PING_PONG_REVERSE) {
parentPlaying->invertForward();
newFrame = tag->fromFrame() - 1;
}
// Consideration for test:
// Playback.OnePingPongInsideForward2
else if (parentPlaying->tag->aniDir() == AniDir::FORWARD) {
--parentPlaying->repeat;
newFrame = parentPlaying->tag->fromFrame();
}
else
newFrame = 0;
}
else
newFrame = 0;
}
}
m_frame = newFrame;

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2021-2022 Igara Studio S.A.
// Copyright (c) 2021-2024 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -152,6 +152,47 @@ TEST(Playback, WithTagRepetitions)
play = Playback(sprite.get(), 0, Playback::Mode::PlayAll);
expect_frames(play, {0,1,2,1,2,3,0,0,0});
EXPECT_TRUE(play.isStopped());
Tag* b = make_tag("B", 0, 3, AniDir::PING_PONG, 2);
sprite = make_sprite(4, { b });
play = Playback(sprite.get(), 0, Playback::Mode::PlayInLoop);
expect_frames(play, {0,1,2,3,2,1,0,
0,1,2,3,2,1,0,
0,1,2,3,2,1,0});
EXPECT_FALSE(play.isStopped());
Tag* c = make_tag("C", 0, 3, AniDir::PING_PONG_REVERSE, 2);
sprite = make_sprite(4, { c });
play = Playback(sprite.get(), 0, Playback::Mode::PlayInLoop);
expect_frames(play, {0,1,2,3,
3,2,1,0,1,2,3,
3,2,1,0,1,2,3});
EXPECT_FALSE(play.isStopped());
Tag* d = make_tag("D", 0, 3, AniDir::PING_PONG_REVERSE, 2);
sprite = make_sprite(4, { d });
play = Playback(sprite.get(), 1, Playback::Mode::PlayInLoop);
expect_frames(play, {1,0,1,2,3,
3,2,1,0,1,2,3,
3,2,1,0,1,2,3});
EXPECT_FALSE(play.isStopped());
Tag* e = make_tag("E", 0, 3, AniDir::PING_PONG_REVERSE, 1);
sprite = make_sprite(4, { e });
play = Playback(sprite.get(), 0, Playback::Mode::PlayInLoop);
expect_frames(play, {0,
3,2,1,0,
3,2,1,0,
3,2,1,0});
EXPECT_FALSE(play.isStopped());
Tag* f = make_tag("F", 0, 3, AniDir::REVERSE, 2);
sprite = make_sprite(4, { f });
play = Playback(sprite.get(), 0, Playback::Mode::PlayInLoop);
expect_frames(play, {0,3,2,1,0,
3,2,1,0, 3,2,1,0,
3,2,1,0, 3,2,1,0});
EXPECT_FALSE(play.isStopped());
}
TEST(Playback, LoopTagInfinite)
@ -464,39 +505,359 @@ TEST(Playback, PingPongWithInnerReverse)
EXPECT_FALSE(play.isStopped());
}
// OnePingPongInsideOther series
static std::vector<int> goRight(const int a, const int b) {
std::vector<int> out;
if (a > b)
return out;
for (int i=a; i<=b ; ++i)
out.push_back(i);
return out;
}
static std::vector<int> goLeft(const int a, const int b) {
std::vector<int> out;
if (a > b)
return out;
for (int i=b; i>=a ; --i)
out.push_back(i);
return out;
}
static void concat(std::vector<int>& a, const std::vector<int>& b)
{
for (size_t i=0; i<b.size(); ++i)
a.push_back(b[i]);
}
TEST(Playback, OnePingPongInsideOther)
{
// A
// <------->
// B
// >---<
// 0 1 2 3 4
// A repeat = 2 ; B repeat = 2
//
// A A A
// *-------* *-------* *-------*
// B B B
// *---* *---* *---*
// 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4
const int lastFrame = 4;
std::vector<AniDir> A_AniDirs = {AniDir::PING_PONG, AniDir::PING_PONG_REVERSE};
std::vector<AniDir> B_AniDirs = {AniDir::PING_PONG, AniDir::PING_PONG_REVERSE};
std::vector<int> A_Range = {0,lastFrame};
std::vector<std::vector<int>> rangeBs = {{0,2}, {1,3}, {2,4}};
std::vector<std::vector<int>> pingPongSeq1 = {{0,1,2,1,0}, {2,1,0,1,2}};
std::vector<std::vector<int>> pingPongSeq2 = {{1,2,3,2,1}, {3,2,1,2,3}};
std::vector<std::vector<int>> pingPongSeq3 = {{2,3,4,3,2}, {4,3,2,3,4}};
std::vector<int> right012 = {0,1,2};
Tag* tagA = make_tag("A", 0, 4, AniDir::PING_PONG, 2);
Tag* tagB = make_tag("B", 1, 3, AniDir::PING_PONG_REVERSE, 3);
auto sprite = make_sprite(5, { tagA, tagB });
for (auto A_aniDir : A_AniDirs) {
for (auto B_aniDir : B_AniDirs) {
for (auto B_Range : rangeBs) {
std::vector<int> expected;
std::vector<int> temp;
// A A A
// <-------> <-------> <------->
// B B B
// *---* *---* *---*
// 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4
if (A_aniDir == doc::AniDir::PING_PONG) {
// Start
temp = goRight(0, B_Range[0]-1);
concat(expected, temp);
// Tag B playback
if (B_Range[0] == 0)
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq1[0] : right012);
else if (B_Range[0] == 1)
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq2[0] : pingPongSeq2[1]);
else if (B_Range[0] == 2)
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq3[0] : pingPongSeq3[1]);
// Reproduce right side of the tag A
temp = goRight(B_Range[1]+1, lastFrame);
concat(expected, temp);
temp = goLeft(B_Range[1]+1, lastFrame-1);
concat(expected, temp);
// Tag B playback (only if tag B last frame doesn't match with the tag A last frame
if (B_Range[1] != A_Range[1]) {
if (B_Range[1] == lastFrame - 1)
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq2[1] : pingPongSeq2[0]);
else if (B_Range[1] == lastFrame - 2)
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq1[1] : pingPongSeq1[0]);
}
// Reproduce right side of the tag A
temp = goLeft(0, B_Range[0]-1);
concat(expected, temp);
// Sequence end
}
// A A A
// >-------< >-------< >-------<
// B B B
// *---* *---* *---*
// 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4
else {
// Start
temp = goRight(0, B_Range[0]-1);
concat(expected, temp);
// Tag B playback
if (B_Range[0] == 0)
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq1[0] : right012);
else if (B_Range[0] == 1)
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq2[0] : pingPongSeq2[1]);
else if (B_Range[0] == 2)
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq3[0] : pingPongSeq3[1]);
// Reproduce right side of the tag A
temp = goRight(B_Range[1]+1, lastFrame);
concat(expected, temp);
// Sequence end
// New Start
temp = goLeft(B_Range[1]+1, lastFrame);
concat(expected, temp);
// Tag B playback
if (B_Range[1] == lastFrame)
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq3[1] : pingPongSeq3[0]);
else if (B_Range[1] == lastFrame-1)
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq2[1] : pingPongSeq2[0]);
else if (B_Range[1] == lastFrame-2)
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq1[1] : pingPongSeq1[0]);
// Reproduce left side of the tag A
temp = goLeft(0, B_Range[0]-1);
concat(expected, temp);
temp = goRight(1, B_Range[0]-1);
concat(expected, temp);
// Tag B playback (only if tag B first frame doesn't match with the tag A first frame
if (B_Range[0] == 1)
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq2[0] : pingPongSeq2[1]);
else if (B_Range[0] == 2)
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq3[0] : pingPongSeq3[1]);
// Reproduce right side of the tag A
temp = goRight(B_Range[1]+1, lastFrame);
concat(expected, temp);
// Sequence end
}
// Test
Tag* tagA = make_tag("A", 0, 4, A_aniDir, 2);
Tag* tagB = make_tag("B", B_Range[0], B_Range[1], B_aniDir, 2);
auto sprite = make_sprite(lastFrame + 1, { tagA, tagB });
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
expect_frames(play, expected);
EXPECT_FALSE(play.isStopped());
}
}
}
}
TEST(Playback, OnePingPongInsideOther1Repeat)
{
// A repeat = 1 ; B repeat = 1
//
// A A A
// *-------* *-------* *-------*
// B B B
// *---* *---* *---*
// 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4
const int lastFrame = 4;
std::vector<AniDir> A_AniDirs = {AniDir::PING_PONG, AniDir::PING_PONG_REVERSE};
std::vector<AniDir> B_AniDirs = {AniDir::PING_PONG, AniDir::PING_PONG_REVERSE};
std::vector<int> A_Range = {0,lastFrame};
std::vector<std::vector<int>> rangeBs = {{0,2}, {1,3}, {2,4}};
std::vector<std::vector<int>> pingPongSeq1 = {{0,1,2}, {2,1,0}};
std::vector<std::vector<int>> pingPongSeq2 = {{1,2,3}, {3,2,1}};
std::vector<std::vector<int>> pingPongSeq3 = {{2,3,4}, {4,3,2}};
for (auto A_aniDir : A_AniDirs) {
for (auto B_aniDir : B_AniDirs) {
for (auto B_Range : rangeBs) {
std::vector<int> expected;
std::vector<int> temp;
// A A A
// <-------> <-------> <------->
// B B B
// *---* *---* *---*
// 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4
if (A_aniDir == doc::AniDir::PING_PONG) {
// Start
temp = goRight(0, B_Range[0]-1);
concat(expected, temp);
// Tag B playback
if (B_Range[0] == 0) {
temp = {0};
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq1[0] : temp);
}
else if (B_Range[0] == 1)
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq2[0] : pingPongSeq2[1]);
else if (B_Range[0] == 2)
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq3[0] : pingPongSeq3[1]);
// Reproduce right side of the tag A
temp = goRight(B_Range[1]+1, lastFrame);
concat(expected, temp);
// Sequence end
// Fresh sequence start
temp = goRight(0, B_Range[0]-1);
concat(expected, temp);
// Tag B playback
if (B_Range[0] == 0)
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq1[0] : pingPongSeq1[1]);
else if (B_Range[0] == 1)
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq2[0] : pingPongSeq2[1]);
else if (B_Range[0] == 2)
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq3[0] : pingPongSeq3[1]);
// Reproduce right side of the tag A
temp = goRight(B_Range[1]+1, lastFrame);
concat(expected, temp);
// Sequence end
}
// A A A
// >-------< >-------< >-------<
// B B B
// *---* *---* *---*
// 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4
else {
// Start
temp = {0};
// Tag B playback
if (B_Range[0] == 0 && B_aniDir == doc::AniDir::PING_PONG)
concat(expected, pingPongSeq1[0]);
else
concat(expected, temp);
// Sequence end
// Fresh sequence start
// Reproduce right side of the tag A
temp = goLeft(B_Range[1]+1, lastFrame);
concat(expected, temp);
// Tag B playback
if (B_Range[1] == lastFrame)
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq3[1] : pingPongSeq3[0]);
else if (B_Range[1] == lastFrame-1)
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq2[1] : pingPongSeq2[0]);
else if (B_Range[1] == lastFrame-2)
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq1[1] : pingPongSeq1[0]);
// Reproduce left side of the tag A
temp = goLeft(0, B_Range[0]-1);
concat(expected, temp);
// Sequence end
}
// Test
Tag* tagA = make_tag("A", 0, 4, A_aniDir, 1);
Tag* tagB = make_tag("B", B_Range[0], B_Range[1], B_aniDir, 1);
auto sprite = make_sprite(lastFrame + 1, { tagA, tagB });
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
expect_frames(play, expected);
EXPECT_FALSE(play.isStopped());
}
}
}
}
TEST(Playback, OnePingPongInsideForward)
{
// A
// -------->
// B
// <--->
// 0 1 2 3 4
Tag* tagA = make_tag("A", 0, 4, AniDir::FORWARD, 2);
Tag* tagB = make_tag("B", 2, 4, AniDir::PING_PONG, 2);
auto sprite = make_sprite(5, { tagA, tagB });
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
expect_frames(play, {0,1 , 2,3,4,3,2,
0,1 , 2,3,4,3,2});
EXPECT_FALSE(play.isStopped());
}
TEST(Playback, OnePingPongInsideForward2)
{
// A
// -------->
// B
// <--->
// 0 1 2 3 4 5
Tag* tagA = make_tag("A", 1, 5, AniDir::FORWARD, 2);
Tag* tagB = make_tag("B", 3, 5, AniDir::PING_PONG, 2);
auto sprite = make_sprite(6, { tagA, tagB });
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
expect_frames(play, {0, 3,2,1,2,3,2,1, 4, 1,2,3,2,1,2,3, 0,
0, 3,2,1,2,3,2,1, 4, 1,2,3,2,1,2,3, 0, });
expect_frames(play, {0 , 1,2 , 3,4,5,4,3, 1,2 , 3,4,5,4,3,
0 , 1,2 , 3,4,5,4,3, 1,2 , 3,4,5,4,3});
EXPECT_FALSE(play.isStopped());
}
TEST(Playback, OnePingPongInsideOther3)
TEST(Playback, OnePingPongInsidePingPongReverse)
{
// A
// <------->
// B
// >---<
// 0 1 2 3 4
// A
// >-------<
// B
// <--->
// 0 1 2 3 4 5
Tag* tagA = make_tag("A", 0, 4, AniDir::PING_PONG, 3);
Tag* tagB = make_tag("B", 1, 3, AniDir::PING_PONG_REVERSE, 2);
auto sprite = make_sprite(5, { tagA, tagB });
Tag* tagA = make_tag("A", 1, 5, AniDir::PING_PONG_REVERSE, 2);
Tag* tagB = make_tag("B", 3, 5, AniDir::PING_PONG, 2);
auto sprite = make_sprite(6, { tagA, tagB });
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
expect_frames(play, {0, 3,2,1,2,3, 4, 1,2,3,2,1, 0, 3,2,1,2,3, 4,
0, 3,2,1,2,3, 4, 1,2,3,2,1, 0, 3,2,1,2,3, 4, 0 });
expect_frames(play, {0 , 5,4,3,4,5 , 2,1,2 , 3,4,5,4,3,
0 , 5,4,3,4,5 , 2,1,2 , 3,4,5,4,3});
EXPECT_FALSE(play.isStopped());
}
TEST(Playback, OneReverseInsidePingPongReverse)
{
// A
// >-------<
// B
// <----
// 0 1 2 3 4 5
Tag* tagA = make_tag("A", 1, 5, AniDir::PING_PONG_REVERSE, 2);
Tag* tagB = make_tag("B", 3, 5, AniDir::REVERSE, 2);
auto sprite = make_sprite(6, { tagA, tagB });
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
expect_frames(play, {0 , 3,4,5,3,4,5 , 2,1,2 , 5,4,3,5,4,3,
0 , 3,4,5,3,4,5 , 2,1,2 , 5,4,3,5,4,3});
EXPECT_FALSE(play.isStopped());
}
TEST(Playback, OnePingPongReverseInsideReverse)
{
// A
// <--------
// B
// >---<
// 0 1 2 3 4 5
Tag* tagA = make_tag("A", 1, 5, AniDir::REVERSE, 2);
Tag* tagB = make_tag("B", 3, 5, AniDir::PING_PONG_REVERSE, 2);
auto sprite = make_sprite(6, { tagA, tagB });
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
expect_frames(play, {0 , 3,4,5,4,3 , 2,1, 3,4,5,4,3 , 2,1,
0 , 3,4,5,4,3 , 2,1, 3,4,5,4,3 , 2,1});
EXPECT_FALSE(play.isStopped());
}
@ -536,6 +897,96 @@ TEST(Playback, TwoLoopsInCascadeReverse)
EXPECT_FALSE(play.isStopped());
}
TEST(Playback, TwoLoopsInCascadeReversePingPongReverse1)
{
// A
// <----
// B
// >---<
// 0 1 2 3 4
Tag* a = make_tag("A", 1, 3, AniDir::REVERSE, 2);
Tag* b = make_tag("B", 2, 4, AniDir::PING_PONG_REVERSE, 2);
auto sprite = make_sprite(5, { a, b });
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
expect_frames(play, {0, 3,2,1,3,2,1, 4,3,2,3,4,
0, 3,2,1,3,2,1, 4,3,2,3,4, 0 });
EXPECT_FALSE(play.isStopped());
}
TEST(Playback, TwoLoopsInCascadeReversePingPongReverse2)
{
// A
// <------
// B
// >---<
// 0 1 2 3 4
Tag* a = make_tag("A", 0, 3, AniDir::REVERSE, 2);
Tag* b = make_tag("B", 2, 4, AniDir::PING_PONG_REVERSE, 2);
auto sprite = make_sprite(5, { a, b });
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
expect_frames(play, {0, 3,2,1,0, 4,3,2,3,4,
3,2,1,0,3,2,1,0, 4,3,2,3,4 });
EXPECT_FALSE(play.isStopped());
}
TEST(Playback, TwoLoopsInCascadeReversePingPongReverse3)
{
// A
// <--------
// B
// >---<
// 0 1 2 3 4
Tag* a = make_tag("A", 0, 4, AniDir::REVERSE, 2);
Tag* b = make_tag("B", 2, 4, AniDir::PING_PONG_REVERSE, 2);
auto sprite = make_sprite(5, { a, b });
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
expect_frames(play, {0, 2,3,4,3,2, 1,0,
2,3,4,3,2, 1,0, 2,3,4,3,2, 1,0,});
EXPECT_FALSE(play.isStopped());
}
TEST(Playback, TwoLoopsInCascadePingPongReverseReverse1)
{
// A
// >-----<
// B
// <----
// 0 1 2 3 4
Tag* a = make_tag("A", 0, 3, AniDir::PING_PONG_REVERSE, 2);
Tag* b = make_tag("B", 2, 4, AniDir::REVERSE, 2);
auto sprite = make_sprite(5, { a, b });
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
expect_frames(play, {0, 1,2,3, 4,3,2,4,3,2,
3,2,1,0,1,2,3, 4,3,2,4,3,2 });
EXPECT_FALSE(play.isStopped());
}
TEST(Playback, TwoLoopsInCascadePingPongReverseReverse2)
{
// A
// >---<
// B
// <----
// 0 1 2 3 4
Tag* a = make_tag("A", 1, 3, AniDir::PING_PONG_REVERSE, 2);
Tag* b = make_tag("B", 2, 4, AniDir::REVERSE, 2);
auto sprite = make_sprite(5, { a, b });
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
expect_frames(play, {0, 3,2,1,2,3, 4,3,2,4,3,2,
0, 3,2,1,2,3, 4,3,2,4,3,2, 0 });
EXPECT_FALSE(play.isStopped());
}
TEST(Playback, ThreeLoopsInCascade)
{
// A