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/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()); 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 a44509fd..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,10 +46,13 @@ 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 = 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 +61,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) { 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 b0bdda73..00000000 Binary files a/test/sprite_fencing.sb3 and /dev/null differ diff --git a/test/target_interfaces/ispritehandler_test.cpp b/test/target_interfaces/ispritehandler_test.cpp index d344727c..ba82dd94 100644 --- a/test/target_interfaces/ispritehandler_test.cpp +++ b/test/target_interfaces/ispritehandler_test.cpp @@ -1,4 +1,5 @@ #include +#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); +}