Skip to content

Commit 34499d6

Browse files
committed
Add sprite fencing
1 parent fb825cb commit 34499d6

File tree

7 files changed

+182
-11
lines changed

7 files changed

+182
-11
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/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: 42 additions & 0 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)
@@ -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)