From b4dd704f5970a2a759452be6d45f38e852fe0db2 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Wed, 13 Dec 2023 14:47:08 +0100 Subject: [PATCH 1/3] Fix unsigned int conversion in "if on edge, bounce" block --- src/blocks/motionblocks.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index 73fbb129..88967065 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -653,8 +653,8 @@ unsigned int MotionBlocks::ifOnEdgeBounce(VirtualMachine *vm) // Measure distance to edges // Values are zero when the sprite is beyond - unsigned int stageWidth = engine->stageWidth(); - unsigned int stageHeight = engine->stageHeight(); + double stageWidth = engine->stageWidth(); + double stageHeight = engine->stageHeight(); double distLeft = std::max(0.0, (stageWidth / 2.0) + bounds.left()); double distTop = std::max(0.0, (stageHeight / 2.0) - bounds.top()); double distRight = std::max(0.0, (stageWidth / 2.0) - bounds.right()); From 7aec835c4fa470fdee4603ac043bcceef995dc7f Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Wed, 13 Dec 2023 14:47:42 +0100 Subject: [PATCH 2/3] Fix unsigned int conversion in getFencedPosition() --- src/scratch/sprite_p.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scratch/sprite_p.cpp b/src/scratch/sprite_p.cpp index a44509fd..f81db6ec 100644 --- a/src/scratch/sprite_p.cpp +++ b/src/scratch/sprite_p.cpp @@ -104,7 +104,7 @@ void SpritePrivate::getFencedPosition(double x, double y, double *outX, double * getBoundingRect(&rect); double inset = std::floor(std::min(rect.width(), rect.height()) / 2); - double xRight = sprite->engine()->stageWidth() / 2; + double xRight = static_cast(sprite->engine()->stageWidth()) / 2; double sx = xRight - std::min(FENCE_WIDTH, inset); if (rect.right() + dx < -sx) { @@ -113,7 +113,7 @@ void SpritePrivate::getFencedPosition(double x, double y, double *outX, double * x = std::floor(this->x + (sx - rect.left())); } - double yTop = sprite->engine()->stageHeight() / 2; + double yTop = static_cast(sprite->engine()->stageHeight()) / 2; double sy = yTop - std::min(FENCE_WIDTH, inset); if (rect.top() + dy < -sy) { From 0a48b8bd9cc8e18d6c116a12d8d92755d475cf71 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Wed, 13 Dec 2023 14:49:06 +0100 Subject: [PATCH 3/3] Move bounding rect to ISpriteHandler --- include/scratchcpp/ispritehandler.h | 6 + src/scratch/sprite.cpp | 9 +- src/scratch/sprite_p.cpp | 60 +--- src/scratch/sprite_p.h | 1 - test/blocks/motion_blocks_test.cpp | 72 ++-- test/mocks/spritehandlermock.h | 1 + test/scratch_classes/sprite_test.cpp | 329 ++++-------------- test/sprite_fencing.sb3 | Bin 2432 -> 0 bytes .../target_interfaces/ispritehandler_test.cpp | 13 + 9 files changed, 132 insertions(+), 359 deletions(-) delete mode 100644 test/sprite_fencing.sb3 diff --git a/include/scratchcpp/ispritehandler.h b/include/scratchcpp/ispritehandler.h index 582c3387..f71a9147 100644 --- a/include/scratchcpp/ispritehandler.h +++ b/include/scratchcpp/ispritehandler.h @@ -40,6 +40,12 @@ class LIBSCRATCHCPP_EXPORT ISpriteHandler /*! Called when the rotation style changes. */ virtual void onRotationStyleChanged(Sprite::RotationStyle rotationStyle) = 0; + + /*! + * Used to get the bounding rectangle of the sprite. + * \note The rectangle must be relative to the stage, so make sure to use the sprite's coordinates. + */ + virtual Rect boundingRect() const = 0; }; } // namespace libscratchcpp diff --git a/src/scratch/sprite.cpp b/src/scratch/sprite.cpp index f97f1dea..2b22b832 100644 --- a/src/scratch/sprite.cpp +++ b/src/scratch/sprite.cpp @@ -350,10 +350,10 @@ void Sprite::setRotationStyle(const char *newRotationStyle) /*! Returns the bounding rectangle of the sprite. */ Rect Sprite::boundingRect() const { - Rect ret; - impl->getBoundingRect(&ret); + if (!impl->iface) + return Rect(); - return ret; + return impl->iface->boundingRect(); } /*! @@ -374,8 +374,7 @@ void Sprite::keepInFence(double newX, double newY, double *fencedX, double *fenc double stageWidth = eng->stageWidth(); double stageHeight = eng->stageHeight(); Rect fence(-stageWidth / 2, stageHeight / 2, stageWidth / 2, -stageHeight / 2); - Rect bounds; - impl->getBoundingRect(&bounds); + Rect bounds = boundingRect(); // Adjust the known bounds to the target position bounds.setLeft(bounds.left() + newX - impl->x); diff --git a/src/scratch/sprite_p.cpp b/src/scratch/sprite_p.cpp index f81db6ec..3d828e77 100644 --- a/src/scratch/sprite_p.cpp +++ b/src/scratch/sprite_p.cpp @@ -31,61 +31,6 @@ void SpritePrivate::removeClone(Sprite *clone) } } -void SpritePrivate::getBoundingRect(Rect *out) const -{ - assert(out); - assert(sprite); - auto costume = sprite->currentCostume(); - - if (!costume) { - out->setLeft(x); - out->setTop(y); - out->setRight(x); - out->setBottom(y); - return; - } - - double cosTheta = std::cos((90 - direction) * pi / 180); - double sinTheta = std::sin((90 - direction) * pi / 180); - double maxX = 0, maxY = 0, minX = 0, minY = 0; - bool firstPixel = true; - unsigned int width = costume->width(); - unsigned int height = costume->height(); - double rotationCenterX = width / 2.0 + costume->rotationCenterX(); - double rotationCenterY = height / 2.0 + costume->rotationCenterY(); - Rgb **bitmap = costume->bitmap(); - - for (unsigned int y = 0; y < height; y++) { - for (unsigned int x = 0; x < width; x++) { - if (bitmap[y][x] != rgba(0, 0, 0, 0)) { - double rotatedX = ((x - rotationCenterX) * cosTheta - (y - rotationCenterY) * sinTheta); - double rotatedY = ((x - rotationCenterX) * sinTheta + (y - rotationCenterY) * cosTheta); - - if (firstPixel) { - firstPixel = false; - minX = maxX = rotatedX; - minY = maxY = rotatedY; - } else { - if (rotatedX < minX) - minX = rotatedX; - else if (rotatedX > maxX) - maxX = rotatedX; - - if (rotatedY < minY) - minY = rotatedY; - else if (rotatedY > maxY) - maxY = rotatedY; - } - } - } - } - - out->setLeft(x + minX); - out->setTop(y + maxY); - out->setRight(x + maxX); - out->setBottom(y + minY); -} - void SpritePrivate::getFencedPosition(double x, double y, double *outX, double *outY) const { assert(outX); @@ -101,7 +46,10 @@ void SpritePrivate::getFencedPosition(double x, double y, double *outX, double * double dx = x - this->x; double dy = y - this->y; Rect rect; - getBoundingRect(&rect); + + if (iface) + rect = iface->boundingRect(); + double inset = std::floor(std::min(rect.width(), rect.height()) / 2); double xRight = static_cast(sprite->engine()->stageWidth()) / 2; diff --git a/src/scratch/sprite_p.h b/src/scratch/sprite_p.h index 9dcff450..b093d203 100644 --- a/src/scratch/sprite_p.h +++ b/src/scratch/sprite_p.h @@ -17,7 +17,6 @@ struct SpritePrivate void removeClone(Sprite *clone); - void getBoundingRect(Rect *out) const; void getFencedPosition(double inX, double inY, double *outX, double *outY) const; Sprite *sprite = nullptr; diff --git a/test/blocks/motion_blocks_test.cpp b/test/blocks/motion_blocks_test.cpp index 74b5419d..c7938633 100644 --- a/test/blocks/motion_blocks_test.cpp +++ b/test/blocks/motion_blocks_test.cpp @@ -4,12 +4,11 @@ #include #include #include -#include +#include #include #include #include -#include -#include +#include #include "../common.h" #include "blocks/motionblocks.h" @@ -1117,39 +1116,10 @@ TEST_F(MotionBlocksTest, IfOnEdgeBounceImpl) static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT }; static BlockFunc functions[] = { &MotionBlocks::ifOnEdgeBounce }; - auto imageFormatFactory = std::make_shared(); - auto imageFormat = std::make_shared(); - - ScratchConfiguration::registerImageFormat("test", imageFormatFactory); - EXPECT_CALL(*imageFormatFactory, createInstance()).WillOnce(Return(imageFormat)); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(0)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(0)); - auto costume = std::make_shared("costume1", "a", "test"); - Sprite sprite; - sprite.addCostume(costume); - sprite.setCostumeIndex(0); - - static char data[5] = "abcd"; - EXPECT_CALL(*imageFormat, setData(5, data)); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3)); - - EXPECT_CALL(*imageFormat, colorAt(0, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(1, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(2, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 255))); - EXPECT_CALL(*imageFormat, colorAt(3, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - - EXPECT_CALL(*imageFormat, colorAt(0, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(1, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 255))); - EXPECT_CALL(*imageFormat, colorAt(2, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(3, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 255))); - - EXPECT_CALL(*imageFormat, colorAt(0, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 255))); - EXPECT_CALL(*imageFormat, colorAt(1, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(2, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(3, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - costume->setData(5, data); + SpriteHandlerMock handler; + EXPECT_CALL(handler, init); + sprite.setInterface(&handler); sprite.setEngine(&m_engineMock); @@ -1157,14 +1127,16 @@ TEST_F(MotionBlocksTest, IfOnEdgeBounceImpl) vm.setBytecode(bytecode); vm.setFunctions(functions); - EXPECT_CALL(*imageFormat, width()).Times(9).WillRepeatedly(Return(4)); - EXPECT_CALL(*imageFormat, height()).Times(9).WillRepeatedly(Return(3)); EXPECT_CALL(m_engineMock, stageWidth()).Times(9).WillRepeatedly(Return(480)); EXPECT_CALL(m_engineMock, stageHeight()).Times(9).WillRepeatedly(Return(360)); // No edge EXPECT_CALL(m_engineMock, requestRedraw()).Times(3); EXPECT_CALL(m_engineMock, spriteFencingEnabled()).Times(2).WillRepeatedly(Return(false)); + EXPECT_CALL(handler, onXChanged); + EXPECT_CALL(handler, onYChanged); + EXPECT_CALL(handler, onDirectionChanged); + EXPECT_CALL(handler, boundingRect()).WillOnce(Return(Rect(80, 80, 120, 40))); sprite.setX(100); sprite.setY(60); sprite.setDirection(-45); @@ -1178,19 +1150,27 @@ TEST_F(MotionBlocksTest, IfOnEdgeBounceImpl) // Left edge EXPECT_CALL(m_engineMock, requestRedraw()).Times(5); EXPECT_CALL(m_engineMock, spriteFencingEnabled()).Times(4).WillRepeatedly(Return(false)); + EXPECT_CALL(handler, onXChanged).Times(2); + EXPECT_CALL(handler, onYChanged).Times(2); + EXPECT_CALL(handler, onDirectionChanged); + EXPECT_CALL(handler, boundingRect()).Times(2).WillRepeatedly(Return(Rect(-260, 80, -220, 40))); sprite.setX(-240); sprite.setY(60); vm.reset(); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(std::round(sprite.x() * 100) / 100, -238.23); + ASSERT_EQ(std::round(sprite.x() * 100) / 100, -220); ASSERT_EQ(sprite.y(), 60); ASSERT_EQ(std::round(sprite.direction() * 100) / 100, 45); // Top edge EXPECT_CALL(m_engineMock, requestRedraw()).Times(6); EXPECT_CALL(m_engineMock, spriteFencingEnabled()).Times(4).WillRepeatedly(Return(false)); + EXPECT_CALL(handler, onXChanged).Times(2); + EXPECT_CALL(handler, onYChanged).Times(2); + EXPECT_CALL(handler, onDirectionChanged).Times(2); + EXPECT_CALL(handler, boundingRect()).Times(2).WillRepeatedly(Return(Rect(80, 200, 120, 160))); sprite.setX(100); sprite.setY(180); sprite.setDirection(45); @@ -1199,25 +1179,33 @@ TEST_F(MotionBlocksTest, IfOnEdgeBounceImpl) ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(sprite.x(), 100); - ASSERT_EQ(std::round(sprite.y() * 100) / 100, 178.23); + ASSERT_EQ(std::round(sprite.y() * 100) / 100, 160); ASSERT_EQ(sprite.direction(), 135); // Right edge EXPECT_CALL(m_engineMock, requestRedraw()).Times(5); EXPECT_CALL(m_engineMock, spriteFencingEnabled()).Times(4).WillRepeatedly(Return(false)); + EXPECT_CALL(handler, onXChanged).Times(2); + EXPECT_CALL(handler, onYChanged).Times(2); + EXPECT_CALL(handler, onDirectionChanged); + EXPECT_CALL(handler, boundingRect()).Times(2).WillRepeatedly(Return(Rect(220, 80, 260, 40))); sprite.setX(240); sprite.setY(60); vm.reset(); vm.run(); ASSERT_EQ(vm.registerCount(), 0); - ASSERT_EQ(std::round(sprite.x() * 100) / 100, 238.23); + ASSERT_EQ(std::round(sprite.x() * 100) / 100, 220); ASSERT_EQ(sprite.y(), 60); ASSERT_EQ(sprite.direction(), -135); // Bottom edge EXPECT_CALL(m_engineMock, requestRedraw()).Times(5); EXPECT_CALL(m_engineMock, spriteFencingEnabled()).Times(4).WillRepeatedly(Return(false)); + EXPECT_CALL(handler, onXChanged).Times(2); + EXPECT_CALL(handler, onYChanged).Times(2); + EXPECT_CALL(handler, onDirectionChanged); + EXPECT_CALL(handler, boundingRect()).Times(2).WillRepeatedly(Return(Rect(-120, -160, -80, -200))); sprite.setX(-100); sprite.setY(-180); vm.reset(); @@ -1225,10 +1213,8 @@ TEST_F(MotionBlocksTest, IfOnEdgeBounceImpl) ASSERT_EQ(vm.registerCount(), 0); ASSERT_EQ(sprite.x(), -100); - ASSERT_EQ(std::round(sprite.y() * 100) / 100, -178.23); + ASSERT_EQ(std::round(sprite.y() * 100) / 100, -160); ASSERT_EQ(std::round(sprite.direction() * 100) / 100, -45); - - ScratchConfiguration::removeImageFormat("test"); } TEST_F(MotionBlocksTest, SetRotationStyle) diff --git a/test/mocks/spritehandlermock.h b/test/mocks/spritehandlermock.h index da1f4e66..ff572d79 100644 --- a/test/mocks/spritehandlermock.h +++ b/test/mocks/spritehandlermock.h @@ -20,4 +20,5 @@ class SpriteHandlerMock : public ISpriteHandler MOCK_METHOD(void, onSizeChanged, (double), (override)); MOCK_METHOD(void, onDirectionChanged, (double), (override)); MOCK_METHOD(void, onRotationStyleChanged, (Sprite::RotationStyle), (override)); + MOCK_METHOD(Rect, boundingRect, (), (const, override)); }; diff --git a/test/scratch_classes/sprite_test.cpp b/test/scratch_classes/sprite_test.cpp index 61435581..2a14889b 100644 --- a/test/scratch_classes/sprite_test.cpp +++ b/test/scratch_classes/sprite_test.cpp @@ -3,13 +3,11 @@ #include #include #include -#include #include #include #include -#include -#include #include +#include #include "../common.h" @@ -248,7 +246,7 @@ TEST(SpriteTest, XY) EngineMock engine; sprite.setEngine(&engine); - EXPECT_CALL(engine, requestRedraw()).Times(18); + EXPECT_CALL(engine, requestRedraw()).Times(17); EXPECT_CALL(engine, spriteFencingEnabled()).Times(4).WillRepeatedly(Return(false)); sprite.setX(-53.25); @@ -263,147 +261,98 @@ TEST(SpriteTest, XY) sprite.setY(189.999); ASSERT_EQ(sprite.y(), 189.999); - auto imageFormatFactory = std::make_shared(); - auto imageFormat = std::make_shared(); - - ScratchConfiguration::registerImageFormat("test", imageFormatFactory); - EXPECT_CALL(*imageFormatFactory, createInstance()).WillOnce(Return(imageFormat)); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(0)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(0)); - auto costume = std::make_shared("costume1", "a", "test"); - - sprite.addCostume(costume); - sprite.setCostumeIndex(0); - - static char data[5] = "abcd"; - EXPECT_CALL(*imageFormat, setData(5, data)); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3)); - - EXPECT_CALL(*imageFormat, colorAt(0, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(1, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(2, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 255))); - EXPECT_CALL(*imageFormat, colorAt(3, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - - EXPECT_CALL(*imageFormat, colorAt(0, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(1, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 255))); - EXPECT_CALL(*imageFormat, colorAt(2, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(3, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 255))); - - EXPECT_CALL(*imageFormat, colorAt(0, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 255))); - EXPECT_CALL(*imageFormat, colorAt(1, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(2, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(3, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - costume->setData(5, data); + static const Rect rect(-44.6, 89.1, 20.5, -0.48); + SpriteHandlerMock handler; + EXPECT_CALL(handler, init); + sprite.setInterface(&handler); sprite.setEngine(&engine); + EXPECT_CALL(handler, onDirectionChanged); sprite.setDirection(34.45); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3)); EXPECT_CALL(engine, spriteFencingEnabled()).WillOnce(Return(true)); EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); - sprite.setX(230); - ASSERT_EQ(sprite.x(), 230); + EXPECT_CALL(handler, boundingRect()).WillOnce(Return(rect)); + EXPECT_CALL(handler, onXChanged); + sprite.setX(319); + ASSERT_EQ(sprite.x(), 319); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3)); EXPECT_CALL(engine, spriteFencingEnabled()).WillOnce(Return(true)); EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); - sprite.setX(-230); - ASSERT_EQ(sprite.x(), -230); + EXPECT_CALL(handler, boundingRect()).WillOnce(Return(rect)); + EXPECT_CALL(handler, onXChanged); + sprite.setX(75); + ASSERT_EQ(sprite.x(), 75); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3)); EXPECT_CALL(engine, spriteFencingEnabled()).WillOnce(Return(true)); EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); - sprite.setX(250); - ASSERT_EQ(sprite.x(), 241); + EXPECT_CALL(handler, boundingRect()).WillOnce(Return(rect)); + EXPECT_CALL(handler, onXChanged); + sprite.setX(400); + ASSERT_EQ(sprite.x(), 344); EXPECT_CALL(engine, spriteFencingEnabled()).WillOnce(Return(false)); - sprite.setX(250); - ASSERT_EQ(sprite.x(), 250); + EXPECT_CALL(handler, onXChanged); + sprite.setX(400); + ASSERT_EQ(sprite.x(), 400); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3)); EXPECT_CALL(engine, spriteFencingEnabled()).WillOnce(Return(true)); EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); - sprite.setX(-250); - ASSERT_EQ(sprite.x(), -241); + EXPECT_CALL(handler, boundingRect()).WillOnce(Return(rect)); + EXPECT_CALL(handler, onXChanged); + sprite.setX(-400); + ASSERT_EQ(sprite.x(), 155); EXPECT_CALL(engine, spriteFencingEnabled()).WillOnce(Return(false)); - sprite.setX(-250); - ASSERT_EQ(sprite.x(), -250); + EXPECT_CALL(handler, onXChanged); + sprite.setX(-400); + ASSERT_EQ(sprite.x(), -400); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3)); EXPECT_CALL(engine, spriteFencingEnabled()).WillOnce(Return(true)); EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); - sprite.setY(170); - ASSERT_EQ(sprite.y(), 170); + EXPECT_CALL(handler, boundingRect()).WillOnce(Return(rect)); + EXPECT_CALL(handler, onYChanged); + sprite.setY(150); + ASSERT_EQ(sprite.y(), 150); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3)); EXPECT_CALL(engine, spriteFencingEnabled()).WillOnce(Return(true)); EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); - sprite.setY(-170); - ASSERT_EQ(sprite.y(), -170); + EXPECT_CALL(handler, boundingRect()).WillOnce(Return(rect)); + EXPECT_CALL(handler, onYChanged); + sprite.setY(-103); + ASSERT_EQ(sprite.y(), -103); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3)); EXPECT_CALL(engine, spriteFencingEnabled()).WillOnce(Return(true)); EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); - sprite.setY(190); - ASSERT_EQ(sprite.y(), 181); + EXPECT_CALL(handler, boundingRect()).WillOnce(Return(rect)); + EXPECT_CALL(handler, onYChanged); + sprite.setY(340); + ASSERT_EQ(sprite.y(), 62); EXPECT_CALL(engine, spriteFencingEnabled()).WillOnce(Return(false)); - sprite.setY(190); - ASSERT_EQ(sprite.y(), 190); + EXPECT_CALL(handler, onYChanged); + sprite.setY(340); + ASSERT_EQ(sprite.y(), 340); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3)); EXPECT_CALL(engine, spriteFencingEnabled()).WillOnce(Return(true)); EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); - sprite.setY(-190); - ASSERT_EQ(sprite.y(), -180); + EXPECT_CALL(handler, boundingRect()).WillOnce(Return(rect)); + EXPECT_CALL(handler, onYChanged); + sprite.setY(-340); + ASSERT_EQ(sprite.y(), 86); EXPECT_CALL(engine, spriteFencingEnabled()).WillOnce(Return(false)); - sprite.setY(-190); - ASSERT_EQ(sprite.y(), -190); - - ScratchConfiguration::removeImageFormat("test"); -} - -TEST(SpriteTest, Fencing) -{ - Project p("sprite_fencing.sb3"); - ASSERT_TRUE(p.load()); - p.run(); - - auto engine = p.engine(); - - Stage *stage = engine->stage(); - ASSERT_TRUE(stage); - - ASSERT_VAR(stage, "maxX"); - ASSERT_EQ(GET_VAR(stage, "maxX")->value().toDouble(), 240); - - ASSERT_VAR(stage, "maxY"); - ASSERT_EQ(GET_VAR(stage, "maxY")->value().toDouble(), 205); - - ASSERT_VAR(stage, "minX"); - ASSERT_EQ(GET_VAR(stage, "minX")->value().toDouble(), -362); - - ASSERT_VAR(stage, "minY"); - ASSERT_EQ(GET_VAR(stage, "minY")->value().toDouble(), -213); + EXPECT_CALL(handler, onYChanged); + sprite.setY(-340); + ASSERT_EQ(sprite.y(), -340); } TEST(SpriteTest, Size) @@ -590,189 +539,61 @@ TEST(SpriteTest, RotationStyle) ASSERT_EQ(c2->mirrorHorizontally(), false); } -TEST(SpriteTest, BoundingRect) -{ - auto imageFormatFactory = std::make_shared(); - auto imageFormat = std::make_shared(); - - ScratchConfiguration::registerImageFormat("test", imageFormatFactory); - EXPECT_CALL(*imageFormatFactory, createInstance()).WillOnce(Return(imageFormat)); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(0)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(0)); - auto costume = std::make_shared("costume1", "a", "test"); - - Sprite sprite; - sprite.addCostume(costume); - sprite.setCostumeIndex(0); - - static char data[5] = "abcd"; - EXPECT_CALL(*imageFormat, setData(5, data)); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3)); - - EXPECT_CALL(*imageFormat, colorAt(0, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(1, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(2, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 255))); - EXPECT_CALL(*imageFormat, colorAt(3, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - - EXPECT_CALL(*imageFormat, colorAt(0, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(1, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 255))); - EXPECT_CALL(*imageFormat, colorAt(2, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(3, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 255))); - - EXPECT_CALL(*imageFormat, colorAt(0, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 255))); - EXPECT_CALL(*imageFormat, colorAt(1, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(2, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(3, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - costume->setData(5, data); - - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3)); - Rect rect = sprite.boundingRect(); - ASSERT_EQ(rect.left(), -2); - ASSERT_EQ(rect.top(), 0.5); - ASSERT_EQ(rect.right(), 1); - ASSERT_EQ(rect.bottom(), -1.5); - - sprite.setDirection(45); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3)); - rect = sprite.boundingRect(); - ASSERT_EQ(std::round(rect.left() * 10000) / 10000, -1.7678); - ASSERT_EQ(std::round(rect.top() * 10000) / 10000, 0.3536); - ASSERT_EQ(std::round(rect.right() * 10000) / 10000, 1.0607); - ASSERT_EQ(std::round(rect.bottom() * 10000) / 10000, -1.0607); - - sprite.setDirection(-160); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3)); - rect = sprite.boundingRect(); - ASSERT_EQ(std::round(rect.left() * 10000) / 10000, -1.4095); - ASSERT_EQ(std::round(rect.top() * 10000) / 10000, 1.7084); - ASSERT_EQ(std::round(rect.right() * 10000) / 10000, 1.1539); - ASSERT_EQ(std::round(rect.bottom() * 10000) / 10000, -0.7687); - - sprite.setX(86.48); - sprite.setY(-147.16); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3)); - rect = sprite.boundingRect(); - ASSERT_EQ(std::round(rect.left() * 10000) / 10000, 85.0705); - ASSERT_EQ(std::round(rect.top() * 10000) / 10000, -145.4516); - ASSERT_EQ(std::round(rect.right() * 10000) / 10000, 87.6339); - ASSERT_EQ(std::round(rect.bottom() * 10000) / 10000, -147.9287); - - costume->setRotationCenterX(-4); - costume->setRotationCenterY(8); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3)); - rect = sprite.boundingRect(); - ASSERT_EQ(std::round(rect.left() * 10000) / 10000, 76.1848); - ASSERT_EQ(std::round(rect.top() * 10000) / 10000, -146.4742); - ASSERT_EQ(std::round(rect.right() * 10000) / 10000, 78.7483); - ASSERT_EQ(std::round(rect.bottom() * 10000) / 10000, -148.9513); - - sprite.setDirection(90); - sprite.setX(0); - sprite.setY(0); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3)); - rect = sprite.boundingRect(); - ASSERT_EQ(rect.left(), 2); - ASSERT_EQ(rect.top(), -7.5); - ASSERT_EQ(rect.right(), 5); - ASSERT_EQ(rect.bottom(), -9.5); - - ScratchConfiguration::removeImageFormat("test"); -} - TEST(SpriteTest, KeepInFence) { - auto imageFormatFactory = std::make_shared(); - auto imageFormat = std::make_shared(); - - ScratchConfiguration::registerImageFormat("test", imageFormatFactory); - EXPECT_CALL(*imageFormatFactory, createInstance()).WillOnce(Return(imageFormat)); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(0)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(0)); - auto costume = std::make_shared("costume1", "a", "test"); - Sprite sprite; - sprite.addCostume(costume); - sprite.setCostumeIndex(0); - - static char data[5] = "abcd"; - EXPECT_CALL(*imageFormat, setData(5, data)); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3)); - - EXPECT_CALL(*imageFormat, colorAt(0, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(1, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(2, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 255))); - EXPECT_CALL(*imageFormat, colorAt(3, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - - EXPECT_CALL(*imageFormat, colorAt(0, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(1, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 255))); - EXPECT_CALL(*imageFormat, colorAt(2, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(3, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 255))); - - EXPECT_CALL(*imageFormat, colorAt(0, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 255))); - EXPECT_CALL(*imageFormat, colorAt(1, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(2, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - EXPECT_CALL(*imageFormat, colorAt(3, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0))); - costume->setData(5, data); + SpriteHandlerMock handler; + EXPECT_CALL(handler, init); + sprite.setInterface(&handler); double fencedX = -1, fencedY = -1; EngineMock engine; sprite.setEngine(&engine); + static const Rect rect(-44.6, 89.1, 20.5, -0.48); EXPECT_CALL(engine, requestRedraw()); + EXPECT_CALL(handler, onDirectionChanged); sprite.setDirection(45); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3)); EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); + EXPECT_CALL(handler, boundingRect()).WillOnce(Return(rect)); sprite.keepInFence(0, 0, &fencedX, &fencedY); ASSERT_EQ(fencedX, 0); ASSERT_EQ(fencedY, 0); EXPECT_CALL(engine, requestRedraw()).Times(2); EXPECT_CALL(engine, spriteFencingEnabled()).Times(2).WillRepeatedly(Return(false)); + EXPECT_CALL(handler, onXChanged); + EXPECT_CALL(handler, onYChanged); sprite.setX(100); sprite.setY(60); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3)); EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); - sprite.keepInFence(240, 180, &fencedX, &fencedY); - ASSERT_EQ(std::round(fencedX * 100) / 100, 238.94); - ASSERT_EQ(std::round(fencedY * 100) / 100, 179.65); + EXPECT_CALL(handler, boundingRect()).WillOnce(Return(rect)); + sprite.keepInFence(400, 340, &fencedX, &fencedY); + ASSERT_EQ(std::round(fencedX * 100) / 100, 319.5); + ASSERT_EQ(std::round(fencedY * 100) / 100, 150.9); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3)); EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); - sprite.keepInFence(240, -180, &fencedX, &fencedY); - ASSERT_EQ(std::round(fencedX * 100) / 100, 238.94); - ASSERT_EQ(std::round(fencedY * 100) / 100, -178.94); + EXPECT_CALL(handler, boundingRect()).WillOnce(Return(rect)); + sprite.keepInFence(400, -340, &fencedX, &fencedY); + ASSERT_EQ(std::round(fencedX * 100) / 100, 319.5); + ASSERT_EQ(std::round(fencedY * 100) / 100, -119.52); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3)); EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); - sprite.keepInFence(-240, -180, &fencedX, &fencedY); - ASSERT_EQ(std::round(fencedX * 100) / 100, -238.23); - ASSERT_EQ(std::round(fencedY * 100) / 100, -178.94); + EXPECT_CALL(handler, boundingRect()).WillOnce(Return(rect)); + sprite.keepInFence(-400, -340, &fencedX, &fencedY); + ASSERT_EQ(std::round(fencedX * 100) / 100, -95.4); + ASSERT_EQ(std::round(fencedY * 100) / 100, -119.52); - EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4)); - EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3)); EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480)); EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360)); - sprite.keepInFence(-240, 180, &fencedX, &fencedY); - ASSERT_EQ(std::round(fencedX * 100) / 100, -238.23); - ASSERT_EQ(std::round(fencedY * 100) / 100, 179.65); - - ScratchConfiguration::removeImageFormat("test"); + EXPECT_CALL(handler, boundingRect()).WillOnce(Return(rect)); + sprite.keepInFence(-400, 340, &fencedX, &fencedY); + ASSERT_EQ(std::round(fencedX * 100) / 100, -95.4); + ASSERT_EQ(std::round(fencedY * 100) / 100, 150.9); } TEST(SpriteTest, GraphicsEffects) diff --git a/test/sprite_fencing.sb3 b/test/sprite_fencing.sb3 deleted file mode 100644 index b0bdda73fc5d81c459b875354d41efaaf591e5ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2432 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 +#include #include #include @@ -59,6 +60,7 @@ TEST_F(ISpriteHandlerTest, Visible) TEST_F(ISpriteHandlerTest, X) { EXPECT_CALL(m_handler, onXChanged(189.46)).Times(1); + EXPECT_CALL(m_handler, boundingRect()).WillOnce(Return(Rect())); EXPECT_CALL(m_engine, spriteFencingEnabled()).WillOnce(Return(true)); EXPECT_CALL(m_engine, stageWidth()).WillOnce(Return(480)); EXPECT_CALL(m_engine, stageHeight()).WillOnce(Return(360)); @@ -74,6 +76,7 @@ TEST_F(ISpriteHandlerTest, X) TEST_F(ISpriteHandlerTest, Y) { EXPECT_CALL(m_handler, onYChanged(-153.7)).Times(1); + EXPECT_CALL(m_handler, boundingRect()).WillOnce(Return(Rect())); EXPECT_CALL(m_engine, spriteFencingEnabled()).WillOnce(Return(true)); EXPECT_CALL(m_engine, stageWidth()).WillOnce(Return(480)); EXPECT_CALL(m_engine, stageHeight()).WillOnce(Return(360)); @@ -126,3 +129,13 @@ TEST_F(ISpriteHandlerTest, Costume) EXPECT_CALL(m_engine, requestRedraw()); m_sprite.setCostumeIndex(1); } + +TEST_F(ISpriteHandlerTest, BoundingRect) +{ + EXPECT_CALL(m_handler, boundingRect()).WillOnce(Return(Rect(-44.6, 89.1, 20.5, -0.48))); + Rect rect = m_sprite.boundingRect(); + ASSERT_EQ(rect.left(), -44.6); + ASSERT_EQ(rect.top(), 89.1); + ASSERT_EQ(rect.right(), 20.5); + ASSERT_EQ(rect.bottom(), -0.48); +}