Skip to content

Commit f16cd17

Browse files
authored
Merge pull request #299 from scratchcpp/sprite_fencing
Add sprite fencing
2 parents 350c8c3 + 34499d6 commit f16cd17

File tree

10 files changed

+198
-21
lines changed

10 files changed

+198
-21
lines changed

include/scratchcpp/sprite.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class LIBSCRATCHCPP_EXPORT Sprite : public Target
6767

6868
private:
6969
Target *dataSource() const override;
70+
void setXY(double x, double y);
7071

7172
spimpl::unique_impl_ptr<SpritePrivate> impl;
7273
};

src/scratch/costume.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ double Costume::bitmapResolution() const
2626
/*! Sets the reciprocal of the costume scaling factor for bitmap costumes. */
2727
void Costume::setBitmapResolution(double newBitmapResolution)
2828
{
29+
if (impl->bitmapResolution == newBitmapResolution)
30+
return;
31+
2932
impl->bitmapResolution = newBitmapResolution;
33+
impl->updateImage();
3034
}
3135

3236
/*! Returns the x-coordinate of the rotation center. */
@@ -56,13 +60,13 @@ void Costume::setRotationCenterY(int newRotationCenterY)
5660
/*! Returns the costume width. */
5761
unsigned int Costume::width() const
5862
{
59-
return impl->image->width() * impl->scale;
63+
return impl->image->width() * impl->scale / impl->bitmapResolution;
6064
}
6165

6266
/*! Returns the costume height. */
6367
unsigned int Costume::height() const
6468
{
65-
return impl->image->height() * impl->scale;
69+
return impl->image->height() * impl->scale / impl->bitmapResolution;
6670
}
6771

6872
/*! Returns the image scale. */

src/scratch/costume_p.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,30 @@ CostumePrivate::~CostumePrivate()
1616

1717
void CostumePrivate::updateImage()
1818
{
19-
unsigned int scaledWidth = image->width() * scale;
20-
unsigned int scaledHeight = image->height() * scale;
19+
unsigned int scaledWidth = image->width() * scale / bitmapResolution;
20+
unsigned int scaledHeight = image->height() * scale / bitmapResolution;
2121

2222
if (scaledWidth == 0 || scaledHeight == 0) {
2323
freeImage();
2424
return;
2525
}
2626

27-
if (!bitmap || (scale != oldScale)) {
27+
if (!bitmap || (scale != oldScale) || (bitmapResolution != oldBitmapResolution)) {
2828
freeImage();
2929
bitmap = static_cast<Rgb **>(malloc(sizeof(Rgb *) * scaledHeight));
3030

3131
for (unsigned int i = 0; i < scaledHeight; i++)
3232
bitmap[i] = static_cast<Rgb *>(malloc(sizeof(Rgb) * scaledWidth));
3333

3434
oldScale = scale;
35+
oldBitmapResolution = bitmapResolution;
3536
oldWidth = scaledWidth;
3637
oldHeight = scaledHeight;
3738
}
3839

3940
for (unsigned int i = 0; i < scaledHeight; i++) {
4041
for (unsigned int j = 0; j < scaledWidth; j++) {
41-
Rgb color = image->colorAt(mirrorHorizontally ? (scaledWidth - 1 - j) : j, i, scale);
42+
Rgb color = image->colorAt(mirrorHorizontally ? (scaledWidth - 1 - j) : j, i, scale / bitmapResolution);
4243

4344
// TODO: Apply graphics effects here
4445

src/scratch/costume_p.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ struct CostumePrivate
1919
void updateImage();
2020
void freeImage();
2121

22+
double oldBitmapResolution = 1;
2223
double bitmapResolution = 1;
2324
int rotationCenterX = 0;
2425
int rotationCenterY = 0;

src/scratch/sprite.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ double Sprite::x() const
162162
/*! Sets the X position of the sprite. */
163163
void Sprite::setX(double newX)
164164
{
165-
impl->x = newX;
165+
setXY(newX, impl->y);
166+
166167
if (impl->iface)
167168
impl->iface->onXChanged(impl->x);
168169
}
@@ -176,7 +177,8 @@ double Sprite::y() const
176177
/*! Sets the Y position of the sprite. */
177178
void Sprite::setY(double newY)
178179
{
179-
impl->y = newY;
180+
setXY(impl->x, newY);
181+
180182
if (impl->iface)
181183
impl->iface->onYChanged(impl->y);
182184
}
@@ -311,3 +313,8 @@ Target *Sprite::dataSource() const
311313
{
312314
return impl->cloneRoot;
313315
}
316+
317+
void Sprite::setXY(double x, double y)
318+
{
319+
impl->getFencedPosition(x, y, &impl->x, &impl->y);
320+
}

src/scratch/sprite_p.cpp

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
#include <scratchcpp/ispritehandler.h>
44
#include <scratchcpp/rect.h>
55
#include <scratchcpp/costume.h>
6+
#include <scratchcpp/iengine.h>
67
#include <cmath>
78

89
#include "sprite_p.h"
910

1011
using namespace libscratchcpp;
1112

1213
static const double pi = std::acos(-1); // TODO: Use std::numbers::pi in C++20
14+
static const double FENCE_WIDTH = 15;
1315

1416
SpritePrivate::SpritePrivate(Sprite *sprite) :
1517
sprite(sprite)
@@ -43,10 +45,10 @@ void SpritePrivate::getBoundingRect(Rect *out) const
4345
auto costume = sprite->costumeAt(sprite->currentCostume() - 1);
4446

4547
if (!costume) {
46-
out->setLeft(0);
47-
out->setTop(0);
48-
out->setRight(0);
49-
out->setBottom(0);
48+
out->setLeft(x);
49+
out->setTop(y);
50+
out->setRight(x);
51+
out->setBottom(y);
5052
return;
5153
}
5254

@@ -90,3 +92,43 @@ void SpritePrivate::getBoundingRect(Rect *out) const
9092
out->setRight(x + maxX);
9193
out->setBottom(y + minY);
9294
}
95+
96+
void SpritePrivate::getFencedPosition(double x, double y, double *outX, double *outY) const
97+
{
98+
assert(outX);
99+
assert(outY);
100+
101+
if (!sprite->engine()) {
102+
*outX = x;
103+
*outY = y;
104+
return;
105+
}
106+
107+
// https://github.com/scratchfoundation/scratch-render/blob/0b51e5a66ae1c8102fe881107145d7ef3d71a1ab/src/RenderWebGL.js#L1526
108+
double dx = x - this->x;
109+
double dy = y - this->y;
110+
Rect rect;
111+
getBoundingRect(&rect);
112+
double inset = std::floor(std::min(rect.width(), rect.height()) / 2);
113+
114+
double xRight = sprite->engine()->stageWidth() / 2;
115+
double sx = xRight - std::min(FENCE_WIDTH, inset);
116+
117+
if (rect.right() + dx < -sx) {
118+
x = std::ceil(this->x - (sx + rect.right()));
119+
} else if (rect.left() + dx > sx) {
120+
x = std::floor(this->x + (sx - rect.left()));
121+
}
122+
123+
double yTop = sprite->engine()->stageHeight() / 2;
124+
double sy = yTop - std::min(FENCE_WIDTH, inset);
125+
126+
if (rect.top() + dy < -sy) {
127+
y = std::ceil(this->y - (sy + rect.top()));
128+
} else if (rect.bottom() + dy > sy) {
129+
y = std::floor(this->y + (sy - rect.bottom()));
130+
}
131+
132+
*outX = x;
133+
*outY = y;
134+
}

src/scratch/sprite_p.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ struct SpritePrivate
1818

1919
void setCostumeData(const char *data);
2020
void getBoundingRect(Rect *out) const;
21+
void getFencedPosition(double inX, double inY, double *outX, double *outY) const;
2122

2223
Sprite *sprite = nullptr;
2324
ISpriteHandler *iface = nullptr;

test/scratch_classes/sprite_test.cpp

Lines changed: 123 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
#include <scratchcpp/sprite.h>
2+
#include <scratchcpp/stage.h>
23
#include <scratchcpp/variable.h>
34
#include <scratchcpp/list.h>
45
#include <scratchcpp/costume.h>
56
#include <scratchcpp/scratchconfiguration.h>
67
#include <scratchcpp/rect.h>
8+
#include <scratchcpp/project.h>
79
#include <enginemock.h>
810
#include <imageformatfactorymock.h>
911
#include <imageformatmock.h>
@@ -206,28 +208,142 @@ TEST(SpriteTest, Clone)
206208
sprite.reset();
207209
}
208210

209-
TEST(SpriteTest, X)
211+
TEST(SpriteTest, XY)
210212
{
211213
Sprite sprite;
212214
ASSERT_EQ(sprite.x(), 0);
215+
ASSERT_EQ(sprite.y(), 0);
213216

214217
sprite.setX(-53.25);
215218
ASSERT_EQ(sprite.x(), -53.25);
216219

217220
sprite.setX(239.999);
218221
ASSERT_EQ(sprite.x(), 239.999);
219-
}
220-
221-
TEST(SpriteTest, Y)
222-
{
223-
Sprite sprite;
224-
ASSERT_EQ(sprite.y(), 0);
225222

226223
sprite.setY(-53.25);
227224
ASSERT_EQ(sprite.y(), -53.25);
228225

229226
sprite.setY(179.999);
230227
ASSERT_EQ(sprite.y(), 179.999);
228+
229+
auto imageFormatFactory = std::make_shared<ImageFormatFactoryMock>();
230+
auto imageFormat = std::make_shared<ImageFormatMock>();
231+
232+
ScratchConfiguration::registerImageFormat("test", imageFormatFactory);
233+
EXPECT_CALL(*imageFormatFactory, createInstance()).WillOnce(Return(imageFormat));
234+
EXPECT_CALL(*imageFormat, width()).WillOnce(Return(0));
235+
EXPECT_CALL(*imageFormat, height()).WillOnce(Return(0));
236+
auto costume = std::make_shared<Costume>("costume1", "a", "test");
237+
238+
sprite.addCostume(costume);
239+
sprite.setCurrentCostume(1);
240+
241+
static char data[5] = "abcd";
242+
EXPECT_CALL(*imageFormat, setData(5, data));
243+
EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4));
244+
EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3));
245+
246+
EXPECT_CALL(*imageFormat, colorAt(0, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
247+
EXPECT_CALL(*imageFormat, colorAt(1, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
248+
EXPECT_CALL(*imageFormat, colorAt(2, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 255)));
249+
EXPECT_CALL(*imageFormat, colorAt(3, 0, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
250+
251+
EXPECT_CALL(*imageFormat, colorAt(0, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
252+
EXPECT_CALL(*imageFormat, colorAt(1, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 255)));
253+
EXPECT_CALL(*imageFormat, colorAt(2, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
254+
EXPECT_CALL(*imageFormat, colorAt(3, 1, 1)).WillOnce(Return(rgba(0, 0, 0, 255)));
255+
256+
EXPECT_CALL(*imageFormat, colorAt(0, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 255)));
257+
EXPECT_CALL(*imageFormat, colorAt(1, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
258+
EXPECT_CALL(*imageFormat, colorAt(2, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
259+
EXPECT_CALL(*imageFormat, colorAt(3, 2, 1)).WillOnce(Return(rgba(0, 0, 0, 0)));
260+
costume->setData(5, data);
261+
262+
EngineMock engine;
263+
sprite.setEngine(&engine);
264+
sprite.setDirection(34.45);
265+
266+
EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4));
267+
EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3));
268+
EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480));
269+
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360));
270+
sprite.setX(230);
271+
ASSERT_EQ(sprite.x(), 230);
272+
273+
EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4));
274+
EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3));
275+
EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480));
276+
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360));
277+
sprite.setX(-230);
278+
ASSERT_EQ(sprite.x(), -230);
279+
280+
EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4));
281+
EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3));
282+
EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480));
283+
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360));
284+
sprite.setX(250);
285+
ASSERT_EQ(sprite.x(), 241);
286+
287+
EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4));
288+
EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3));
289+
EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480));
290+
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360));
291+
sprite.setX(-250);
292+
ASSERT_EQ(sprite.x(), -241);
293+
294+
EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4));
295+
EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3));
296+
EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480));
297+
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360));
298+
sprite.setY(170);
299+
ASSERT_EQ(sprite.y(), 170);
300+
301+
EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4));
302+
EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3));
303+
EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480));
304+
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360));
305+
sprite.setY(-170);
306+
ASSERT_EQ(sprite.y(), -170);
307+
308+
EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4));
309+
EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3));
310+
EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480));
311+
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360));
312+
sprite.setY(190);
313+
ASSERT_EQ(sprite.y(), 181);
314+
315+
EXPECT_CALL(*imageFormat, width()).WillOnce(Return(4));
316+
EXPECT_CALL(*imageFormat, height()).WillOnce(Return(3));
317+
EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480));
318+
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360));
319+
sprite.setY(-190);
320+
ASSERT_EQ(sprite.y(), -180);
321+
322+
ScratchConfiguration::removeImageFormat("test");
323+
}
324+
325+
TEST(SpriteTest, Fencing)
326+
{
327+
Project p("sprite_fencing.sb3");
328+
ASSERT_TRUE(p.load());
329+
p.run();
330+
331+
auto engine = p.engine();
332+
333+
Stage *stage = engine->stage();
334+
ASSERT_TRUE(stage);
335+
336+
ASSERT_VAR(stage, "maxX");
337+
ASSERT_EQ(GET_VAR(stage, "maxX")->value().toDouble(), 240);
338+
339+
ASSERT_VAR(stage, "maxY");
340+
ASSERT_EQ(GET_VAR(stage, "maxY")->value().toDouble(), 205);
341+
342+
ASSERT_VAR(stage, "minX");
343+
ASSERT_EQ(GET_VAR(stage, "minX")->value().toDouble(), -362);
344+
345+
ASSERT_VAR(stage, "minY");
346+
ASSERT_EQ(GET_VAR(stage, "minY")->value().toDouble(), -213);
231347
}
232348

233349
TEST(SpriteTest, Size)

test/sprite_fencing.sb3

2.38 KB
Binary file not shown.

test/target_interfaces/ispritehandler_test.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,17 @@ TEST_F(ISpriteHandlerTest, Visible)
5151

5252
TEST_F(ISpriteHandlerTest, X)
5353
{
54-
EXPECT_CALL(m_handler, onXChanged(300.25)).Times(1);
55-
m_sprite.setX(300.25);
54+
EXPECT_CALL(m_handler, onXChanged(189.46)).Times(1);
55+
EXPECT_CALL(m_engine, stageWidth()).WillOnce(Return(480));
56+
EXPECT_CALL(m_engine, stageHeight()).WillOnce(Return(360));
57+
m_sprite.setX(189.46);
5658
}
5759

5860
TEST_F(ISpriteHandlerTest, Y)
5961
{
6062
EXPECT_CALL(m_handler, onYChanged(-153.7)).Times(1);
63+
EXPECT_CALL(m_engine, stageWidth()).WillOnce(Return(480));
64+
EXPECT_CALL(m_engine, stageHeight()).WillOnce(Return(360));
6165
m_sprite.setY(-153.7);
6266
}
6367

0 commit comments

Comments
 (0)