Skip to content

Commit e700c78

Browse files
committed
Refactor convex hull points
1 parent 83e3df6 commit e700c78

File tree

7 files changed

+73
-137
lines changed

7 files changed

+73
-137
lines changed

src/irenderedtarget.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,7 @@ class IRenderedTarget : public QNanoQuickItem
8383
virtual void setGraphicEffect(ShaderManager::Effect effect, double value) = 0;
8484
virtual void clearGraphicEffects() = 0;
8585

86-
virtual void updateHullPoints(QOpenGLFramebufferObject *fbo) = 0;
87-
virtual const std::vector<QPointF> &hullPoints() const = 0;
86+
virtual const std::vector<QPoint> &hullPoints() const = 0;
8887
};
8988

9089
} // namespace scratchcpprender

src/renderedtarget.cpp

Lines changed: 51 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "scenemousearea.h"
1414
#include "bitmapskin.h"
1515
#include "svgskin.h"
16+
#include "cputexturemanager.h"
1617

1718
using namespace scratchcpprender;
1819
using namespace libscratchcpp;
@@ -41,6 +42,7 @@ void RenderedTarget::updateVisibility(bool visible)
4142

4243
setVisible(visible);
4344
calculatePos();
45+
m_convexHullDirty = true;
4446
}
4547

4648
void RenderedTarget::updateX(double x)
@@ -212,6 +214,7 @@ void RenderedTarget::setEngine(IEngine *newEngine)
212214
m_skin = nullptr;
213215
m_texture = Texture();
214216
m_oldTexture = Texture();
217+
m_convexHullDirty = true;
215218
clearGraphicEffects();
216219
m_hullPoints.clear();
217220

@@ -256,9 +259,12 @@ void RenderedTarget::setSpriteModel(SpriteModel *newSpriteModel)
256259
SpriteModel *cloneRoot = m_spriteModel->cloneRoot();
257260

258261
if (cloneRoot) {
259-
// Inherit skins from the clone root
262+
// Inherit skins, texture mananger, convex hull points, etc. from the clone root
260263
RenderedTarget *target = dynamic_cast<RenderedTarget *>(cloneRoot->renderedTarget());
261264
Q_ASSERT(target);
265+
m_textureManager = target->m_textureManager;
266+
m_convexHullDirty = target->m_convexHullDirty;
267+
m_hullPoints = target->m_hullPoints;
262268

263269
if (target->costumesLoaded()) {
264270
m_skins = target->m_skins; // TODO: Avoid copying - maybe using a pointer?
@@ -365,7 +371,9 @@ Rect RenderedTarget::getBounds() const
365371
double right = -std::numeric_limits<double>::infinity();
366372
double bottom = std::numeric_limits<double>::infinity();
367373

368-
for (const QPointF &point : m_hullPoints) {
374+
const std::vector<QPoint> &points = hullPoints();
375+
376+
for (const QPointF &point : points) {
369377
QPointF transformed = transformPoint(point.x() - width / 2, height / 2 - point.y(), originX, originY, rot);
370378
const double x = transformed.x() * scale() / m_stageScale * (m_mirrorHorizontally ? -1 : 1);
371379
const double y = transformed.y() * scale() / m_stageScale;
@@ -516,67 +524,24 @@ void RenderedTarget::setGraphicEffect(ShaderManager::Effect effect, double value
516524

517525
if (changed)
518526
update();
527+
528+
// TODO: Set m_convexHullDirty to true if the effect changes shape
519529
}
520530

521531
void RenderedTarget::clearGraphicEffects()
522532
{
523533
if (!m_graphicEffects.empty())
524534
update();
525535

536+
// TODO: Set m_convexHullDirty to true if any of the previous effects changed shape
526537
m_graphicEffects.clear();
527538
}
528539

529-
void RenderedTarget::updateHullPoints(QOpenGLFramebufferObject *fbo)
540+
const std::vector<QPoint> &RenderedTarget::hullPoints() const
530541
{
531-
Q_ASSERT(fbo);
532-
533-
if (!m_glF) {
534-
m_glF = std::make_unique<QOpenGLFunctions>();
535-
m_glF->initializeOpenGLFunctions();
536-
}
537-
538-
int width = fbo->width();
539-
int height = fbo->height();
540-
m_hullPoints.clear();
541-
m_hullPoints.reserve(width * height);
542-
543-
// Read pixels from framebuffer
544-
size_t size = width * height * 4;
545-
GLubyte *pixelData = new GLubyte[size];
546-
m_glF->glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixelData);
547-
548-
// Flip vertically
549-
int rowSize = width * 4;
550-
GLubyte *tempRow = new GLubyte[rowSize];
551-
552-
for (size_t i = 0; i < height / 2; ++i) {
553-
size_t topRowIndex = i * rowSize;
554-
size_t bottomRowIndex = (height - 1 - i) * rowSize;
555-
556-
// Swap rows
557-
memcpy(tempRow, &pixelData[topRowIndex], rowSize);
558-
memcpy(&pixelData[topRowIndex], &pixelData[bottomRowIndex], rowSize);
559-
memcpy(&pixelData[bottomRowIndex], tempRow, rowSize);
560-
}
561-
562-
delete[] tempRow;
563-
564-
// Fill hull points vector
565-
for (int y = 0; y < height; y++) {
566-
for (int x = 0; x < width; x++) {
567-
int index = (y * width + x) * 4; // RGBA channels
568-
569-
// Check alpha channel
570-
if (pixelData[index + 3] > 0)
571-
m_hullPoints.push_back(QPointF(x, y));
572-
}
573-
}
574-
575-
delete[] pixelData;
576-
}
542+
if (convexHullPointsNeeded())
543+
const_cast<RenderedTarget *>(this)->updateHullPoints();
577544

578-
const std::vector<QPointF> &RenderedTarget::hullPoints() const
579-
{
580545
return m_hullPoints;
581546
}
582547

@@ -588,12 +553,12 @@ bool RenderedTarget::contains(const QPointF &point) const
588553
if (!boundingRect().contains(point))
589554
return false;
590555

556+
const std::vector<QPoint> &points = hullPoints();
557+
591558
QPoint intPoint = point.toPoint();
592-
auto it = std::lower_bound(m_hullPoints.begin(), m_hullPoints.end(), intPoint, [](const QPointF &lhs, const QPointF &rhs) {
593-
return (lhs.y() < rhs.y()) || (lhs.y() == rhs.y() && lhs.x() < rhs.x());
594-
});
559+
auto it = std::lower_bound(points.begin(), points.end(), intPoint, [](const QPointF &lhs, const QPointF &rhs) { return (lhs.y() < rhs.y()) || (lhs.y() == rhs.y() && lhs.x() < rhs.x()); });
595560

596-
if (it == m_hullPoints.end()) {
561+
if (it == points.end()) {
597562
// The point is beyond the last point in the convex hull
598563
return false;
599564
}
@@ -659,10 +624,15 @@ void RenderedTarget::calculateRotation()
659624
void RenderedTarget::calculateSize()
660625
{
661626
if (m_skin && m_costume) {
627+
GLuint oldTexture = m_texture.handle();
628+
bool wasValid = m_texture.isValid();
662629
m_texture = m_skin->getTexture(m_size * m_stageScale);
663630
m_width = m_texture.width();
664631
m_height = m_texture.height();
665632
setScale(m_size * m_stageScale / m_skin->getTextureScale(m_texture) / m_costume->bitmapResolution());
633+
634+
if (wasValid && m_texture.handle() != oldTexture)
635+
m_convexHullDirty = true;
666636
}
667637
}
668638

@@ -679,6 +649,24 @@ void RenderedTarget::handleSceneMouseMove(qreal x, qreal y)
679649
}
680650
}
681651

652+
bool RenderedTarget::convexHullPointsNeeded() const
653+
{
654+
return m_convexHullDirty || m_hullPoints.empty();
655+
}
656+
657+
void RenderedTarget::updateHullPoints()
658+
{
659+
m_convexHullDirty = false;
660+
661+
if (!isVisible()) {
662+
m_hullPoints.clear();
663+
return;
664+
}
665+
666+
m_hullPoints = textureManager()->getTextureConvexHullPoints(m_texture);
667+
// TODO: Apply graphic effects (#117)
668+
}
669+
682670
QPointF RenderedTarget::transformPoint(double scratchX, double scratchY, double originX, double originY, double rot) const
683671
{
684672
const double cosRot = std::cos(rot);
@@ -688,6 +676,14 @@ QPointF RenderedTarget::transformPoint(double scratchX, double scratchY, double
688676
return QPointF(x, y);
689677
}
690678

679+
CpuTextureManager *RenderedTarget::textureManager()
680+
{
681+
if (!m_textureManager)
682+
m_textureManager = std::make_shared<CpuTextureManager>();
683+
684+
return m_textureManager.get();
685+
}
686+
691687
bool RenderedTarget::mirrorHorizontally() const
692688
{
693689
return m_mirrorHorizontally;

src/renderedtarget.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ namespace scratchcpprender
1919
{
2020

2121
class Skin;
22+
class CpuTextureManager;
2223

2324
class RenderedTarget : public IRenderedTarget
2425
{
@@ -88,8 +89,7 @@ class RenderedTarget : public IRenderedTarget
8889
void setGraphicEffect(ShaderManager::Effect effect, double value) override;
8990
void clearGraphicEffects() override;
9091

91-
void updateHullPoints(QOpenGLFramebufferObject *fbo) override;
92-
const std::vector<QPointF> &hullPoints() const override;
92+
const std::vector<QPoint> &hullPoints() const override;
9393

9494
Q_INVOKABLE bool contains(const QPointF &point) const override;
9595

@@ -112,7 +112,10 @@ class RenderedTarget : public IRenderedTarget
112112
void calculateRotation();
113113
void calculateSize();
114114
void handleSceneMouseMove(qreal x, qreal y);
115+
bool convexHullPointsNeeded() const;
116+
void updateHullPoints();
115117
QPointF transformPoint(double scratchX, double scratchY, double originX, double originY, double rot) const;
118+
CpuTextureManager *textureManager();
116119

117120
libscratchcpp::IEngine *m_engine = nullptr;
118121
libscratchcpp::Costume *m_costume = nullptr;
@@ -125,6 +128,7 @@ class RenderedTarget : public IRenderedTarget
125128
Skin *m_skin = nullptr;
126129
Texture m_texture;
127130
Texture m_oldTexture;
131+
std::shared_ptr<CpuTextureManager> m_textureManager; // NOTE: Use textureManager()!
128132
std::unique_ptr<QOpenGLFunctions> m_glF;
129133
std::unordered_map<ShaderManager::Effect, double> m_graphicEffects;
130134
double m_size = 1;
@@ -138,7 +142,8 @@ class RenderedTarget : public IRenderedTarget
138142
double m_stageScale = 1;
139143
qreal m_maximumWidth = std::numeric_limits<double>::infinity();
140144
qreal m_maximumHeight = std::numeric_limits<double>::infinity();
141-
std::vector<QPointF> m_hullPoints;
145+
bool m_convexHullDirty = true;
146+
std::vector<QPoint> m_hullPoints;
142147
bool m_clicked = false; // left mouse button only!
143148
double m_dragDeltaX = 0;
144149
double m_dragDeltaY = 0;

src/targetpainter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ void TargetPainter::paint(QNanoPainter *painter)
9999

100100
// Process the resulting texture
101101
// NOTE: This must happen now, not later, because the alpha channel can be used here
102-
m_target->updateHullPoints(targetFbo);
102+
// Currently nothing is happening here...
103103

104104
// Cleanup
105105
shaderProgram->release();

test/mocks/renderedtargetmock.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@ class RenderedTargetMock : public IRenderedTarget
6767
MOCK_METHOD(void, setGraphicEffect, (ShaderManager::Effect effect, double value), (override));
6868
MOCK_METHOD(void, clearGraphicEffects, (), (override));
6969

70-
MOCK_METHOD(void, updateHullPoints, (QOpenGLFramebufferObject *), (override));
71-
MOCK_METHOD(const std::vector<QPointF> &, hullPoints, (), (const, override));
70+
MOCK_METHOD(const std::vector<QPoint> &, hullPoints, (), (const, override));
7271

7372
MOCK_METHOD(bool, contains, (const QPointF &), (const, override));
7473
MOCK_METHOD(QNanoQuickItemPainter *, createItemPainter, (), (const, override));

test/renderedtarget/renderedtarget_test.cpp

Lines changed: 11 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -314,27 +314,20 @@ TEST_F(RenderedTargetTest, HullPoints)
314314
QOffscreenSurface surface;
315315
createContextAndSurface(&context, &surface);
316316

317-
// Create a painter
318-
QNanoPainter painter;
319-
320-
QOpenGLFramebufferObjectFormat format;
321-
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
322-
323-
// Begin painting
324-
QOpenGLFramebufferObject fbo(4, 6, format);
325-
fbo.bind();
326-
painter.beginFrame(fbo.width(), fbo.height());
327-
328-
// Paint
329-
QNanoImage image = QNanoImage::fromCache(&painter, "image.png");
330-
painter.drawImage(image, 0, 0);
331-
painter.endFrame();
317+
// Load costume
318+
EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480));
319+
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360));
320+
auto costume = std::make_shared<Costume>("", "", "png");
321+
std::string costumeData = readFileStr("image.png");
322+
costume->setData(costumeData.size(), static_cast<void *>(costumeData.data()));
323+
sprite.addCostume(costume);
324+
target.loadCostumes();
325+
target.updateCostume(costume.get());
332326

333327
// Test hull points
334328
target.setWidth(3);
335329
target.setHeight(3);
336-
target.updateHullPoints(&fbo);
337-
ASSERT_EQ(target.hullPoints(), std::vector<QPointF>({ { 1, 1 }, { 2, 1 }, { 3, 1 }, { 1, 2 }, { 3, 2 }, { 1, 3 }, { 2, 3 }, { 3, 3 } }));
330+
ASSERT_EQ(target.hullPoints(), std::vector<QPoint>({ { 1, 1 }, { 2, 1 }, { 3, 1 }, { 1, 2 }, { 3, 2 }, { 1, 3 }, { 2, 3 }, { 3, 3 } }));
338331

339332
// Test contains()
340333
ASSERT_FALSE(target.contains({ 0, 0 }));
@@ -358,49 +351,7 @@ TEST_F(RenderedTargetTest, HullPoints)
358351
ASSERT_TRUE(target.contains({ 3, 3 }));
359352
ASSERT_FALSE(target.contains({ 3.3, 3.5 }));
360353

361-
// Stage: hull points
362-
Stage stage;
363-
StageModel stageModel;
364-
stageModel.init(&stage);
365-
target.setSpriteModel(nullptr);
366-
target.setStageModel(&stageModel);
367-
368-
target.setWidth(3);
369-
target.setHeight(3);
370-
fbo.release();
371-
QOpenGLFramebufferObject emptyFbo(fbo.size(), format);
372-
emptyFbo.bind();
373-
target.updateHullPoints(&emptyFbo); // clear the convex hull points list
374-
ASSERT_TRUE(target.hullPoints().empty());
375-
emptyFbo.release();
376-
fbo.bind();
377-
target.updateHullPoints(&fbo);
378-
ASSERT_EQ(target.hullPoints(), std::vector<QPointF>({ { 1, 1 }, { 2, 1 }, { 3, 1 }, { 1, 2 }, { 3, 2 }, { 1, 3 }, { 2, 3 }, { 3, 3 } }));
379-
380-
// Stage: contains()
381-
ASSERT_TRUE(target.contains({ 0, 0 }));
382-
ASSERT_TRUE(target.contains({ 1, 0 }));
383-
ASSERT_TRUE(target.contains({ 2, 0 }));
384-
ASSERT_TRUE(target.contains({ 3, 0 }));
385-
386-
ASSERT_TRUE(target.contains({ 0, 1 }));
387-
ASSERT_TRUE(target.contains({ 1, 1 }));
388-
ASSERT_TRUE(target.contains({ 1.4, 1.25 }));
389-
ASSERT_TRUE(target.contains({ 2, 1 }));
390-
ASSERT_TRUE(target.contains({ 3, 1 }));
391-
392-
ASSERT_TRUE(target.contains({ 1, 2 }));
393-
ASSERT_TRUE(target.contains({ 2, 2 }));
394-
ASSERT_TRUE(target.contains({ 3, 2 }));
395-
ASSERT_TRUE(target.contains({ 3.5, 2.1 }));
396-
397-
ASSERT_TRUE(target.contains({ 1, 3 }));
398-
ASSERT_TRUE(target.contains({ 2, 3 }));
399-
ASSERT_TRUE(target.contains({ 3, 3 }));
400-
ASSERT_TRUE(target.contains({ 3.3, 3.5 }));
401-
402-
// Release
403-
fbo.release();
354+
// Cleanup
404355
context.doneCurrent();
405356
}
406357

@@ -640,8 +591,6 @@ TEST_F(RenderedTargetTest, GetBounds)
640591
QOpenGLContext context;
641592
QOffscreenSurface surface;
642593
createContextAndSurface(&context, &surface);
643-
QOpenGLExtraFunctions glF(&context);
644-
glF.initializeOpenGLFunctions();
645594
RenderedTarget target;
646595

647596
Sprite sprite;
@@ -668,16 +617,6 @@ TEST_F(RenderedTargetTest, GetBounds)
668617
target.updateCostume(costume.get());
669618
target.beforeRedraw();
670619

671-
Texture texture = target.texture();
672-
QOpenGLFramebufferObjectFormat format;
673-
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
674-
675-
QOpenGLFramebufferObject fbo(texture.size(), format);
676-
fbo.bind();
677-
glF.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.handle(), 0);
678-
target.updateHullPoints(&fbo);
679-
fbo.release();
680-
681620
Rect bounds = target.getBounds();
682621
ASSERT_EQ(std::round(bounds.left() * 100) / 100, 66.13);
683622
ASSERT_EQ(std::round(bounds.top() * 100) / 100, -124.52);

0 commit comments

Comments
 (0)