Skip to content

Commit c7d006d

Browse files
authored
Merge pull request #100 from scratchcpp/fast_bounding_rect
Implement fast bounding rectangle getter
2 parents e12906d + c698762 commit c7d006d

File tree

9 files changed

+131
-1
lines changed

9 files changed

+131
-1
lines changed

src/irenderedtarget.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ class IRenderedTarget : public QNanoQuickItem
7171

7272
virtual libscratchcpp::Rect getBounds() const = 0;
7373
virtual QRectF getBoundsForBubble() const = 0;
74+
virtual libscratchcpp::Rect getFastBounds() const = 0;
7475

7576
virtual QPointF mapFromScene(const QPointF &point) const = 0;
7677

src/renderedtarget.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,39 @@ QRectF RenderedTarget::getBoundsForBubble() const
383383
return QRectF(QPointF(rect.left(), rect.top()), QPointF(rect.right(), rect.bottom()));
384384
}
385385

386+
Rect RenderedTarget::getFastBounds() const
387+
{
388+
if (!m_costume || !m_skin || !m_texture.isValid())
389+
return Rect(m_x, m_y, m_x, m_y);
390+
391+
const double scale = this->scale();
392+
const double width = this->width() / m_stageScale;
393+
const double height = this->height() / m_stageScale;
394+
const double originX = m_costume->rotationCenterX() * m_size / scale / m_costume->bitmapResolution() - width / 2;
395+
const double originY = -m_costume->rotationCenterY() * m_size / scale / m_costume->bitmapResolution() + height / 2;
396+
const double rot = -rotation() * pi / 180;
397+
398+
QPointF topLeft = transformPoint(-width / 2, height / 2, originX, originY, rot);
399+
QPointF topRight = transformPoint(width / 2, height / 2, originX, originY, rot);
400+
QPointF bottomRight = transformPoint(width / 2, -height / 2, originX, originY, rot);
401+
QPointF bottomLeft = transformPoint(-width / 2, -height / 2, originX, originY, rot);
402+
403+
if (m_mirrorHorizontally) {
404+
topLeft.setX(-topLeft.x());
405+
topRight.setX(-topRight.x());
406+
bottomRight.setX(-bottomRight.x());
407+
bottomLeft.setX(-bottomLeft.x());
408+
}
409+
const auto xList = { topLeft.x(), topRight.x(), bottomRight.x(), bottomLeft.x() };
410+
const auto yList = { topLeft.y(), topRight.y(), bottomRight.y(), bottomLeft.y() };
411+
const double minX = std::min(xList);
412+
const double maxX = std::max(xList);
413+
const double minY = std::min(yList);
414+
const double maxY = std::max(yList);
415+
416+
return Rect(minX * scale + m_x, maxY * scale + m_y, maxX * scale + m_x, minY * scale + m_y);
417+
}
418+
386419
QPointF RenderedTarget::mapFromScene(const QPointF &point) const
387420
{
388421
return QNanoQuickItem::mapFromScene(point);

src/renderedtarget.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ class RenderedTarget : public IRenderedTarget
7676

7777
libscratchcpp::Rect getBounds() const override;
7878
Q_INVOKABLE QRectF getBoundsForBubble() const override;
79+
libscratchcpp::Rect getFastBounds() const override;
7980

8081
QPointF mapFromScene(const QPointF &point) const override;
8182

src/spritemodel.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@ libscratchcpp::Rect SpriteModel::boundingRect() const
145145
return m_renderedTarget->getBounds();
146146
}
147147

148+
libscratchcpp::Rect SpriteModel::fastBoundingRect() const
149+
{
150+
return m_renderedTarget->getFastBounds();
151+
}
152+
148153
libscratchcpp::Sprite *SpriteModel::sprite() const
149154
{
150155
return m_sprite;

src/spritemodel.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class SpriteModel
5555
void onBubbleTextChanged(const std::string &text) override;
5656

5757
libscratchcpp::Rect boundingRect() const override;
58+
libscratchcpp::Rect fastBoundingRect() const override;
5859

5960
libscratchcpp::Sprite *sprite() const;
6061

test/mocks/renderedtargetmock.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class RenderedTargetMock : public IRenderedTarget
5656
MOCK_METHOD(QPointF, mapFromScene, (const QPointF &), (const, override));
5757

5858
MOCK_METHOD(libscratchcpp::Rect, getBounds, (), (const, override));
59+
MOCK_METHOD(libscratchcpp::Rect, getFastBounds, (), (const, override));
5960
MOCK_METHOD(QRectF, getBoundsForBubble, (), (const, override));
6061

6162
MOCK_METHOD(bool, mirrorHorizontally, (), (const, override));

test/renderedtarget/renderedtarget_test.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,3 +740,75 @@ TEST_F(RenderedTargetTest, GetBounds)
740740

741741
context.doneCurrent();
742742
}
743+
744+
TEST_F(RenderedTargetTest, GetFastBounds)
745+
{
746+
QOpenGLContext context;
747+
QOffscreenSurface surface;
748+
createContextAndSurface(&context, &surface);
749+
QOpenGLExtraFunctions glF(&context);
750+
glF.initializeOpenGLFunctions();
751+
RenderedTarget target;
752+
753+
Sprite sprite;
754+
sprite.setX(75.64);
755+
sprite.setY(-120.3);
756+
sprite.setDirection(-46.37);
757+
sprite.setSize(67.98);
758+
SpriteModel spriteModel;
759+
sprite.setInterface(&spriteModel);
760+
target.setSpriteModel(&spriteModel);
761+
EngineMock engine;
762+
target.setEngine(&engine);
763+
auto costume = std::make_shared<Costume>("", "", "png");
764+
std::string costumeData = readFileStr("image.png");
765+
costume->setData(costumeData.size(), static_cast<void *>(costumeData.data()));
766+
costume->setRotationCenterX(-15);
767+
costume->setRotationCenterY(48);
768+
costume->setBitmapResolution(3.25);
769+
sprite.addCostume(costume);
770+
771+
EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480));
772+
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360));
773+
target.loadCostumes();
774+
target.updateCostume(costume.get());
775+
target.beforeRedraw();
776+
777+
Rect bounds = target.getFastBounds();
778+
ASSERT_EQ(std::round(bounds.left() * 100) / 100, 65.84);
779+
ASSERT_EQ(std::round(bounds.top() * 100) / 100, -123.92);
780+
ASSERT_EQ(std::round(bounds.right() * 100) / 100, 67.31);
781+
ASSERT_EQ(std::round(bounds.bottom() * 100) / 100, -125.4);
782+
783+
EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480));
784+
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360));
785+
target.updateRotationStyle(Sprite::RotationStyle::LeftRight);
786+
787+
bounds = target.getFastBounds();
788+
ASSERT_EQ(std::round(bounds.left() * 100) / 100, 71.67);
789+
ASSERT_EQ(std::round(bounds.top() * 100) / 100, -110.26);
790+
ASSERT_EQ(std::round(bounds.right() * 100) / 100, 72.5);
791+
ASSERT_EQ(std::round(bounds.bottom() * 100) / 100, -111.51);
792+
793+
EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480));
794+
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360));
795+
target.setStageScale(20.75);
796+
797+
bounds = target.getFastBounds();
798+
ASSERT_EQ(std::round(bounds.left() * 100) / 100, 71.67);
799+
ASSERT_EQ(std::round(bounds.top() * 100) / 100, -110.26);
800+
ASSERT_EQ(std::round(bounds.right() * 100) / 100, 72.5);
801+
ASSERT_EQ(std::round(bounds.bottom() * 100) / 100, -111.51);
802+
803+
EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480));
804+
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360));
805+
target.updateSize(9780.6);
806+
807+
bounds = target.getFastBounds();
808+
ASSERT_EQ(std::round(bounds.left() * 100) / 100, -496.15);
809+
ASSERT_EQ(std::round(bounds.top() * 100) / 100, 1324.22);
810+
ASSERT_EQ(std::round(bounds.right() * 100) / 100, -375.77);
811+
ASSERT_EQ(std::round(bounds.bottom() * 100) / 100, 1143.65);
812+
813+
context.doneCurrent();
814+
}

test/target_models/spritemodel_test.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,22 @@ TEST(SpriteModelTest, BoundingRect)
302302
ASSERT_EQ(bounds.bottom(), rect.bottom());
303303
}
304304

305+
TEST(SpriteModelTest, FastBoundingRect)
306+
{
307+
SpriteModel model;
308+
309+
RenderedTargetMock renderedTarget;
310+
model.setRenderedTarget(&renderedTarget);
311+
312+
Rect rect(-1, 1, 1, -1);
313+
EXPECT_CALL(renderedTarget, getFastBounds()).WillOnce(Return(rect));
314+
Rect bounds = model.fastBoundingRect();
315+
ASSERT_EQ(bounds.left(), rect.left());
316+
ASSERT_EQ(bounds.top(), rect.top());
317+
ASSERT_EQ(bounds.right(), rect.right());
318+
ASSERT_EQ(bounds.bottom(), rect.bottom());
319+
}
320+
305321
TEST(SpriteModelTest, RenderedTarget)
306322
{
307323
SpriteModel model;

0 commit comments

Comments
 (0)