Skip to content

Commit 8a9cf36

Browse files
authored
Merge pull request #369 from scratchcpp/move_boundingrect
Move bounding rect to ISpriteHandler
2 parents 7fb0dbd + 0a48b8b commit 8a9cf36

File tree

10 files changed

+136
-363
lines changed

10 files changed

+136
-363
lines changed

include/scratchcpp/ispritehandler.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ class LIBSCRATCHCPP_EXPORT ISpriteHandler
4040

4141
/*! Called when the rotation style changes. */
4242
virtual void onRotationStyleChanged(Sprite::RotationStyle rotationStyle) = 0;
43+
44+
/*!
45+
* Used to get the bounding rectangle of the sprite.
46+
* \note The rectangle must be relative to the stage, so make sure to use the sprite's coordinates.
47+
*/
48+
virtual Rect boundingRect() const = 0;
4349
};
4450

4551
} // namespace libscratchcpp

src/blocks/motionblocks.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -653,8 +653,8 @@ unsigned int MotionBlocks::ifOnEdgeBounce(VirtualMachine *vm)
653653

654654
// Measure distance to edges
655655
// Values are zero when the sprite is beyond
656-
unsigned int stageWidth = engine->stageWidth();
657-
unsigned int stageHeight = engine->stageHeight();
656+
double stageWidth = engine->stageWidth();
657+
double stageHeight = engine->stageHeight();
658658
double distLeft = std::max(0.0, (stageWidth / 2.0) + bounds.left());
659659
double distTop = std::max(0.0, (stageHeight / 2.0) - bounds.top());
660660
double distRight = std::max(0.0, (stageWidth / 2.0) - bounds.right());

src/scratch/sprite.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -350,10 +350,10 @@ void Sprite::setRotationStyle(const char *newRotationStyle)
350350
/*! Returns the bounding rectangle of the sprite. */
351351
Rect Sprite::boundingRect() const
352352
{
353-
Rect ret;
354-
impl->getBoundingRect(&ret);
353+
if (!impl->iface)
354+
return Rect();
355355

356-
return ret;
356+
return impl->iface->boundingRect();
357357
}
358358

359359
/*!
@@ -374,8 +374,7 @@ void Sprite::keepInFence(double newX, double newY, double *fencedX, double *fenc
374374
double stageWidth = eng->stageWidth();
375375
double stageHeight = eng->stageHeight();
376376
Rect fence(-stageWidth / 2, stageHeight / 2, stageWidth / 2, -stageHeight / 2);
377-
Rect bounds;
378-
impl->getBoundingRect(&bounds);
377+
Rect bounds = boundingRect();
379378

380379
// Adjust the known bounds to the target position
381380
bounds.setLeft(bounds.left() + newX - impl->x);

src/scratch/sprite_p.cpp

Lines changed: 6 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -31,61 +31,6 @@ void SpritePrivate::removeClone(Sprite *clone)
3131
}
3232
}
3333

34-
void SpritePrivate::getBoundingRect(Rect *out) const
35-
{
36-
assert(out);
37-
assert(sprite);
38-
auto costume = sprite->currentCostume();
39-
40-
if (!costume) {
41-
out->setLeft(x);
42-
out->setTop(y);
43-
out->setRight(x);
44-
out->setBottom(y);
45-
return;
46-
}
47-
48-
double cosTheta = std::cos((90 - direction) * pi / 180);
49-
double sinTheta = std::sin((90 - direction) * pi / 180);
50-
double maxX = 0, maxY = 0, minX = 0, minY = 0;
51-
bool firstPixel = true;
52-
unsigned int width = costume->width();
53-
unsigned int height = costume->height();
54-
double rotationCenterX = width / 2.0 + costume->rotationCenterX();
55-
double rotationCenterY = height / 2.0 + costume->rotationCenterY();
56-
Rgb **bitmap = costume->bitmap();
57-
58-
for (unsigned int y = 0; y < height; y++) {
59-
for (unsigned int x = 0; x < width; x++) {
60-
if (bitmap[y][x] != rgba(0, 0, 0, 0)) {
61-
double rotatedX = ((x - rotationCenterX) * cosTheta - (y - rotationCenterY) * sinTheta);
62-
double rotatedY = ((x - rotationCenterX) * sinTheta + (y - rotationCenterY) * cosTheta);
63-
64-
if (firstPixel) {
65-
firstPixel = false;
66-
minX = maxX = rotatedX;
67-
minY = maxY = rotatedY;
68-
} else {
69-
if (rotatedX < minX)
70-
minX = rotatedX;
71-
else if (rotatedX > maxX)
72-
maxX = rotatedX;
73-
74-
if (rotatedY < minY)
75-
minY = rotatedY;
76-
else if (rotatedY > maxY)
77-
maxY = rotatedY;
78-
}
79-
}
80-
}
81-
}
82-
83-
out->setLeft(x + minX);
84-
out->setTop(y + maxY);
85-
out->setRight(x + maxX);
86-
out->setBottom(y + minY);
87-
}
88-
8934
void SpritePrivate::getFencedPosition(double x, double y, double *outX, double *outY) const
9035
{
9136
assert(outX);
@@ -101,10 +46,13 @@ void SpritePrivate::getFencedPosition(double x, double y, double *outX, double *
10146
double dx = x - this->x;
10247
double dy = y - this->y;
10348
Rect rect;
104-
getBoundingRect(&rect);
49+
50+
if (iface)
51+
rect = iface->boundingRect();
52+
10553
double inset = std::floor(std::min(rect.width(), rect.height()) / 2);
10654

107-
double xRight = sprite->engine()->stageWidth() / 2;
55+
double xRight = static_cast<double>(sprite->engine()->stageWidth()) / 2;
10856
double sx = xRight - std::min(FENCE_WIDTH, inset);
10957

11058
if (rect.right() + dx < -sx) {
@@ -113,7 +61,7 @@ void SpritePrivate::getFencedPosition(double x, double y, double *outX, double *
11361
x = std::floor(this->x + (sx - rect.left()));
11462
}
11563

116-
double yTop = sprite->engine()->stageHeight() / 2;
64+
double yTop = static_cast<double>(sprite->engine()->stageHeight()) / 2;
11765
double sy = yTop - std::min(FENCE_WIDTH, inset);
11866

11967
if (rect.top() + dy < -sy) {

src/scratch/sprite_p.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ struct SpritePrivate
1717

1818
void removeClone(Sprite *clone);
1919

20-
void getBoundingRect(Rect *out) const;
2120
void getFencedPosition(double inX, double inY, double *outX, double *outY) const;
2221

2322
Sprite *sprite = nullptr;

test/blocks/motion_blocks_test.cpp

Lines changed: 29 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@
44
#include <scratchcpp/field.h>
55
#include <scratchcpp/sprite.h>
66
#include <scratchcpp/costume.h>
7-
#include <scratchcpp/scratchconfiguration.h>
7+
#include <scratchcpp/rect.h>
88
#include <enginemock.h>
99
#include <randomgeneratormock.h>
1010
#include <clockmock.h>
11-
#include <imageformatfactorymock.h>
12-
#include <imageformatmock.h>
11+
#include <spritehandlermock.h>
1312

1413
#include "../common.h"
1514
#include "blocks/motionblocks.h"
@@ -1117,54 +1116,27 @@ TEST_F(MotionBlocksTest, IfOnEdgeBounceImpl)
11171116
static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT };
11181117
static BlockFunc functions[] = { &MotionBlocks::ifOnEdgeBounce };
11191118

1120-
auto imageFormatFactory = std::make_shared<ImageFormatFactoryMock>();
1121-
auto imageFormat = std::make_shared<ImageFormatMock>();
1122-
1123-
ScratchConfiguration::registerImageFormat("test", imageFormatFactory);
1124-
EXPECT_CALL(*imageFormatFactory, createInstance()).WillOnce(Return(imageFormat));
1125-
EXPECT_CALL(*imageFormat, width()).WillOnce(Return(0));
1126-
EXPECT_CALL(*imageFormat, height()).WillOnce(Return(0));
1127-
auto costume = std::make_shared<Costume>("costume1", "a", "test");
1128-
11291119
Sprite sprite;
1130-
sprite.addCostume(costume);
1131-
sprite.setCostumeIndex(0);
1132-
1133-
static char data[5] = "abcd";
1134-
EXPECT_CALL(*imageFormat, setData(5, data));
1135-
EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4));
1136-
EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3));
1137-
1138-
EXPECT_CALL(*imageFormat, colorAt(0, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
1139-
EXPECT_CALL(*imageFormat, colorAt(1, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
1140-
EXPECT_CALL(*imageFormat, colorAt(2, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 255)));
1141-
EXPECT_CALL(*imageFormat, colorAt(3, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
1142-
1143-
EXPECT_CALL(*imageFormat, colorAt(0, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
1144-
EXPECT_CALL(*imageFormat, colorAt(1, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 255)));
1145-
EXPECT_CALL(*imageFormat, colorAt(2, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
1146-
EXPECT_CALL(*imageFormat, colorAt(3, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 255)));
1147-
1148-
EXPECT_CALL(*imageFormat, colorAt(0, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 255)));
1149-
EXPECT_CALL(*imageFormat, colorAt(1, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
1150-
EXPECT_CALL(*imageFormat, colorAt(2, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
1151-
EXPECT_CALL(*imageFormat, colorAt(3, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
1152-
costume->setData(5, data);
1120+
SpriteHandlerMock handler;
1121+
EXPECT_CALL(handler, init);
1122+
sprite.setInterface(&handler);
11531123

11541124
sprite.setEngine(&m_engineMock);
11551125

11561126
VirtualMachine vm(&sprite, &m_engineMock, nullptr);
11571127
vm.setBytecode(bytecode);
11581128
vm.setFunctions(functions);
11591129

1160-
EXPECT_CALL(*imageFormat, width()).Times(9).WillRepeatedly(Return(4));
1161-
EXPECT_CALL(*imageFormat, height()).Times(9).WillRepeatedly(Return(3));
11621130
EXPECT_CALL(m_engineMock, stageWidth()).Times(9).WillRepeatedly(Return(480));
11631131
EXPECT_CALL(m_engineMock, stageHeight()).Times(9).WillRepeatedly(Return(360));
11641132

11651133
// No edge
11661134
EXPECT_CALL(m_engineMock, requestRedraw()).Times(3);
11671135
EXPECT_CALL(m_engineMock, spriteFencingEnabled()).Times(2).WillRepeatedly(Return(false));
1136+
EXPECT_CALL(handler, onXChanged);
1137+
EXPECT_CALL(handler, onYChanged);
1138+
EXPECT_CALL(handler, onDirectionChanged);
1139+
EXPECT_CALL(handler, boundingRect()).WillOnce(Return(Rect(80, 80, 120, 40)));
11681140
sprite.setX(100);
11691141
sprite.setY(60);
11701142
sprite.setDirection(-45);
@@ -1178,19 +1150,27 @@ TEST_F(MotionBlocksTest, IfOnEdgeBounceImpl)
11781150
// Left edge
11791151
EXPECT_CALL(m_engineMock, requestRedraw()).Times(5);
11801152
EXPECT_CALL(m_engineMock, spriteFencingEnabled()).Times(4).WillRepeatedly(Return(false));
1153+
EXPECT_CALL(handler, onXChanged).Times(2);
1154+
EXPECT_CALL(handler, onYChanged).Times(2);
1155+
EXPECT_CALL(handler, onDirectionChanged);
1156+
EXPECT_CALL(handler, boundingRect()).Times(2).WillRepeatedly(Return(Rect(-260, 80, -220, 40)));
11811157
sprite.setX(-240);
11821158
sprite.setY(60);
11831159
vm.reset();
11841160
vm.run();
11851161

11861162
ASSERT_EQ(vm.registerCount(), 0);
1187-
ASSERT_EQ(std::round(sprite.x() * 100) / 100, -238.23);
1163+
ASSERT_EQ(std::round(sprite.x() * 100) / 100, -220);
11881164
ASSERT_EQ(sprite.y(), 60);
11891165
ASSERT_EQ(std::round(sprite.direction() * 100) / 100, 45);
11901166

11911167
// Top edge
11921168
EXPECT_CALL(m_engineMock, requestRedraw()).Times(6);
11931169
EXPECT_CALL(m_engineMock, spriteFencingEnabled()).Times(4).WillRepeatedly(Return(false));
1170+
EXPECT_CALL(handler, onXChanged).Times(2);
1171+
EXPECT_CALL(handler, onYChanged).Times(2);
1172+
EXPECT_CALL(handler, onDirectionChanged).Times(2);
1173+
EXPECT_CALL(handler, boundingRect()).Times(2).WillRepeatedly(Return(Rect(80, 200, 120, 160)));
11941174
sprite.setX(100);
11951175
sprite.setY(180);
11961176
sprite.setDirection(45);
@@ -1199,36 +1179,42 @@ TEST_F(MotionBlocksTest, IfOnEdgeBounceImpl)
11991179

12001180
ASSERT_EQ(vm.registerCount(), 0);
12011181
ASSERT_EQ(sprite.x(), 100);
1202-
ASSERT_EQ(std::round(sprite.y() * 100) / 100, 178.23);
1182+
ASSERT_EQ(std::round(sprite.y() * 100) / 100, 160);
12031183
ASSERT_EQ(sprite.direction(), 135);
12041184

12051185
// Right edge
12061186
EXPECT_CALL(m_engineMock, requestRedraw()).Times(5);
12071187
EXPECT_CALL(m_engineMock, spriteFencingEnabled()).Times(4).WillRepeatedly(Return(false));
1188+
EXPECT_CALL(handler, onXChanged).Times(2);
1189+
EXPECT_CALL(handler, onYChanged).Times(2);
1190+
EXPECT_CALL(handler, onDirectionChanged);
1191+
EXPECT_CALL(handler, boundingRect()).Times(2).WillRepeatedly(Return(Rect(220, 80, 260, 40)));
12081192
sprite.setX(240);
12091193
sprite.setY(60);
12101194
vm.reset();
12111195
vm.run();
12121196

12131197
ASSERT_EQ(vm.registerCount(), 0);
1214-
ASSERT_EQ(std::round(sprite.x() * 100) / 100, 238.23);
1198+
ASSERT_EQ(std::round(sprite.x() * 100) / 100, 220);
12151199
ASSERT_EQ(sprite.y(), 60);
12161200
ASSERT_EQ(sprite.direction(), -135);
12171201

12181202
// Bottom edge
12191203
EXPECT_CALL(m_engineMock, requestRedraw()).Times(5);
12201204
EXPECT_CALL(m_engineMock, spriteFencingEnabled()).Times(4).WillRepeatedly(Return(false));
1205+
EXPECT_CALL(handler, onXChanged).Times(2);
1206+
EXPECT_CALL(handler, onYChanged).Times(2);
1207+
EXPECT_CALL(handler, onDirectionChanged);
1208+
EXPECT_CALL(handler, boundingRect()).Times(2).WillRepeatedly(Return(Rect(-120, -160, -80, -200)));
12211209
sprite.setX(-100);
12221210
sprite.setY(-180);
12231211
vm.reset();
12241212
vm.run();
12251213

12261214
ASSERT_EQ(vm.registerCount(), 0);
12271215
ASSERT_EQ(sprite.x(), -100);
1228-
ASSERT_EQ(std::round(sprite.y() * 100) / 100, -178.23);
1216+
ASSERT_EQ(std::round(sprite.y() * 100) / 100, -160);
12291217
ASSERT_EQ(std::round(sprite.direction() * 100) / 100, -45);
1230-
1231-
ScratchConfiguration::removeImageFormat("test");
12321218
}
12331219

12341220
TEST_F(MotionBlocksTest, SetRotationStyle)

test/mocks/spritehandlermock.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ class SpriteHandlerMock : public ISpriteHandler
2020
MOCK_METHOD(void, onSizeChanged, (double), (override));
2121
MOCK_METHOD(void, onDirectionChanged, (double), (override));
2222
MOCK_METHOD(void, onRotationStyleChanged, (Sprite::RotationStyle), (override));
23+
MOCK_METHOD(Rect, boundingRect, (), (const, override));
2324
};

0 commit comments

Comments
 (0)