Skip to content

Commit adaf9e0

Browse files
committed
Add touchingColor() with mask param to RenderedTarget
1 parent 63dd35a commit adaf9e0

File tree

5 files changed

+109
-38
lines changed

5 files changed

+109
-38
lines changed

src/irenderedtarget.h

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

9494
virtual bool touchingClones(const std::vector<libscratchcpp::Sprite *> &clones) const = 0;
9595
virtual bool touchingColor(const libscratchcpp::Value &color) const = 0;
96+
virtual bool touchingColor(const libscratchcpp::Value &color, const libscratchcpp::Value &mask) const = 0;
9697
};
9798

9899
} // namespace scratchcpprender

src/renderedtarget.cpp

Lines changed: 76 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -658,43 +658,12 @@ bool RenderedTarget::touchingClones(const std::vector<libscratchcpp::Sprite *> &
658658

659659
bool RenderedTarget::touchingColor(const Value &color) const
660660
{
661-
// https://github.com/scratchfoundation/scratch-render/blob/0a04c2fb165f5c20406ec34ab2ea5682ae45d6e0/src/RenderWebGL.js#L775-L841
662-
if (!m_engine)
663-
return false;
664-
665-
QRgb rgb = convertColor(color);
666-
667-
std::vector<Target *> targets;
668-
m_engine->getVisibleTargets(targets);
669-
670-
QRectF myRect = touchingBounds();
671-
std::vector<IRenderedTarget *> candidates;
672-
QRectF bounds = candidatesBounds(myRect, targets, candidates);
673-
674-
if (colorMatches(rgb, qRgb(255, 255, 255))) {
675-
// The color we're checking for is the background color which spans the entire stage
676-
bounds = myRect;
677-
678-
if (bounds.isEmpty())
679-
return false;
680-
} else if (candidates.empty()) {
681-
// If not checking for the background color, we can return early if there are no candidate drawables
682-
return false;
683-
}
684-
685-
// Loop through the points of the union
686-
for (int y = bounds.top(); y <= bounds.bottom(); y++) {
687-
for (int x = bounds.left(); x <= bounds.right(); x++) {
688-
if (this->containsScratchPoint(x, y)) {
689-
QRgb pixelColor = sampleColor3b(x, y, candidates);
690-
691-
if (colorMatches(rgb, pixelColor))
692-
return true;
693-
}
694-
}
695-
}
661+
return touchingColor(color, false, Value());
662+
}
696663

697-
return false;
664+
bool RenderedTarget::touchingColor(const Value &color, const Value &mask) const
665+
{
666+
return touchingColor(color, true, mask);
698667
}
699668

700669
void RenderedTarget::calculatePos()
@@ -896,6 +865,70 @@ CpuTextureManager *RenderedTarget::textureManager() const
896865
return m_textureManager.get();
897866
}
898867

868+
bool RenderedTarget::touchingColor(const libscratchcpp::Value &color, bool hasMask, const libscratchcpp::Value &mask) const
869+
{
870+
// https://github.com/scratchfoundation/scratch-render/blob/0a04c2fb165f5c20406ec34ab2ea5682ae45d6e0/src/RenderWebGL.js#L775-L841
871+
if (!m_engine)
872+
return false;
873+
874+
QRgb rgb = convertColor(color);
875+
QRgb mask3b;
876+
double ghostValue = 0;
877+
878+
if (hasMask) {
879+
// Ignore ghost effect when checking mask
880+
auto it = m_graphicEffects.find(ShaderManager::Effect::Ghost);
881+
882+
if (it != m_graphicEffects.cend()) {
883+
ghostValue = it->second;
884+
m_graphicEffects.erase(ShaderManager::Effect::Ghost);
885+
}
886+
887+
mask3b = convertColor(mask);
888+
}
889+
890+
std::vector<Target *> targets;
891+
m_engine->getVisibleTargets(targets);
892+
893+
QRectF myRect = touchingBounds();
894+
std::vector<IRenderedTarget *> candidates;
895+
QRectF bounds = candidatesBounds(myRect, targets, candidates);
896+
897+
if (colorMatches(rgb, qRgb(255, 255, 255))) {
898+
// The color we're checking for is the background color which spans the entire stage
899+
bounds = myRect;
900+
901+
if (bounds.isEmpty())
902+
return false;
903+
} else if (candidates.empty()) {
904+
// If not checking for the background color, we can return early if there are no candidate drawables
905+
return false;
906+
}
907+
908+
// Loop through the points of the union
909+
for (int y = bounds.top(); y <= bounds.bottom(); y++) {
910+
for (int x = bounds.left(); x <= bounds.right(); x++) {
911+
if (hasMask ? maskMatches(colorAtScratchPoint(x, y), mask3b) : this->containsScratchPoint(x, y)) {
912+
QRgb pixelColor = sampleColor3b(x, y, candidates);
913+
914+
if (colorMatches(rgb, pixelColor)) {
915+
// Restore ghost effect value
916+
if (hasMask && ghostValue != 0)
917+
m_graphicEffects[ShaderManager::Effect::Ghost] = ghostValue;
918+
919+
return true;
920+
}
921+
}
922+
}
923+
}
924+
925+
// Restore ghost effect value
926+
if (hasMask && ghostValue != 0)
927+
m_graphicEffects[ShaderManager::Effect::Ghost] = ghostValue;
928+
929+
return false;
930+
}
931+
899932
QRectF RenderedTarget::touchingBounds() const
900933
{
901934
// https://github.com/scratchfoundation/scratch-render/blob/0a04c2fb165f5c20406ec34ab2ea5682ae45d6e0/src/RenderWebGL.js#L1330-L1350
@@ -1051,7 +1084,13 @@ QRgb RenderedTarget::convertColor(const libscratchcpp::Value &color)
10511084
bool RenderedTarget::colorMatches(QRgb a, QRgb b)
10521085
{
10531086
// https://github.com/scratchfoundation/scratch-render/blob/0a04c2fb165f5c20406ec34ab2ea5682ae45d6e0/src/RenderWebGL.js#L77-L81
1054-
return (qRed(a) & 0b11111000) == (qRed(b) & 0b11111000) && (qGreen(a) & 0b11111000) == (qGreen(b) & 0b11111000) && (qBlue(a) & 0b11110000) == (qBlue(b) & 0b11110000);
1087+
return qAlpha(a) > 0 && (qRed(a) & 0b11111000) == (qRed(b) & 0b11111000) && (qGreen(a) & 0b11111000) == (qGreen(b) & 0b11111000) && (qBlue(a) & 0b11110000) == (qBlue(b) & 0b11110000);
1088+
}
1089+
1090+
bool RenderedTarget::maskMatches(QRgb a, QRgb b)
1091+
{
1092+
// https://github.com/scratchfoundation/scratch-render/blob/0a04c2fb165f5c20406ec34ab2ea5682ae45d6e0/src/RenderWebGL.js#L59-L65
1093+
return (qRed(a) & 0b11111000) == (qRed(b) & 0b11111000) && (qGreen(a) & 0b11111000) == (qGreen(b) & 0b11111000) && (qBlue(a) & 0b11111000) == (qBlue(b) & 0b11111000);
10551094
}
10561095

10571096
QRgb RenderedTarget::sampleColor3b(double x, double y, const std::vector<IRenderedTarget *> &targets) const

src/renderedtarget.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ class RenderedTarget : public IRenderedTarget
102102

103103
bool touchingClones(const std::vector<libscratchcpp::Sprite *> &) const override;
104104
bool touchingColor(const libscratchcpp::Value &color) const override;
105+
bool touchingColor(const libscratchcpp::Value &color, const libscratchcpp::Value &mask) const override;
105106

106107
signals:
107108
void engineChanged();
@@ -131,6 +132,7 @@ class RenderedTarget : public IRenderedTarget
131132
QPointF mapFromStageWithOriginPoint(const QPointF &scenePoint) const;
132133
QPointF mapFromScratchToLocal(const QPointF &point) const;
133134
CpuTextureManager *textureManager() const;
135+
bool touchingColor(const libscratchcpp::Value &color, bool hasMask, const libscratchcpp::Value &mask) const;
134136
QRectF touchingBounds() const;
135137
QRectF candidatesBounds(const QRectF &targetRect, const std::vector<libscratchcpp::Target *> &candidates, std::vector<IRenderedTarget *> &dst) const;
136138
QRectF candidatesBounds(const QRectF &targetRect, const std::vector<libscratchcpp::Sprite *> &candidates, std::vector<IRenderedTarget *> &dst) const;
@@ -139,6 +141,7 @@ class RenderedTarget : public IRenderedTarget
139141
static void clampRect(libscratchcpp::Rect &rect, double left, double right, double bottom, double top);
140142
static QRgb convertColor(const libscratchcpp::Value &color);
141143
static bool colorMatches(QRgb a, QRgb b);
144+
static bool maskMatches(QRgb a, QRgb b);
142145
QRgb sampleColor3b(double x, double y, const std::vector<IRenderedTarget *> &targets) const;
143146

144147
libscratchcpp::IEngine *m_engine = nullptr;
@@ -156,7 +159,7 @@ class RenderedTarget : public IRenderedTarget
156159
Texture m_cpuTexture; // without stage scale
157160
mutable std::shared_ptr<CpuTextureManager> m_textureManager; // NOTE: Use textureManager()!
158161
std::unique_ptr<QOpenGLFunctions> m_glF;
159-
std::unordered_map<ShaderManager::Effect, double> m_graphicEffects;
162+
mutable std::unordered_map<ShaderManager::Effect, double> m_graphicEffects;
160163
double m_size = 1;
161164
double m_x = 0;
162165
double m_y = 0;

test/mocks/renderedtargetmock.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class RenderedTargetMock : public IRenderedTarget
7878

7979
MOCK_METHOD(bool, touchingClones, (const std::vector<libscratchcpp::Sprite *> &), (const, override));
8080
MOCK_METHOD(bool, touchingColor, (const libscratchcpp::Value &), (const, override));
81+
MOCK_METHOD(bool, touchingColor, (const libscratchcpp::Value &, const libscratchcpp::Value &), (const, override));
8182

8283
MOCK_METHOD(QNanoQuickItemPainter *, createItemPainter, (), (const, override));
8384
MOCK_METHOD(void, hoverEnterEvent, (QHoverEvent *), (override));

test/renderedtarget/renderedtarget_test.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1269,6 +1269,33 @@ TEST_F(RenderedTargetTest, TouchingColor)
12691269
EXPECT_CALL(stageTarget, colorAtScratchPoint).Times(0);
12701270
ASSERT_FALSE(target.touchingColor(color3));
12711271

1272+
// Mask (color is touching color)
1273+
EXPECT_CALL(stageTarget, getFastBounds()).WillOnce(Return(Rect(2, 1, 6, -5)));
1274+
EXPECT_CALL(target1, getFastBounds()).WillOnce(Return(Rect(2, 1, 6, -5)));
1275+
EXPECT_CALL(target2, getFastBounds()).WillOnce(Return(Rect(-5, -6, 2, -8)));
1276+
EXPECT_CALL(target2, colorAtScratchPoint(3, -3)).WillOnce(Return(color4.toInt()));
1277+
EXPECT_CALL(target1, colorAtScratchPoint(3, -3)).WillOnce(Return(color1.toInt()));
1278+
ASSERT_TRUE(target.touchingColor(color5, color3));
1279+
1280+
EXPECT_CALL(stageTarget, getFastBounds()).WillOnce(Return(Rect(5, 1, 6, -5)));
1281+
EXPECT_CALL(target1, getFastBounds()).WillOnce(Return(Rect(5, 1, 6, -5)));
1282+
EXPECT_CALL(target2, getFastBounds()).WillOnce(Return(Rect(-5, -6, 2, -8)));
1283+
EXPECT_CALL(target2, colorAtScratchPoint).Times(0);
1284+
EXPECT_CALL(target1, colorAtScratchPoint).Times(0);
1285+
EXPECT_CALL(penLayer, colorAtScratchPoint).Times(0);
1286+
EXPECT_CALL(stageTarget, colorAtScratchPoint).Times(0);
1287+
ASSERT_FALSE(target.touchingColor(color3, color3));
1288+
1289+
// Ghost effect shouldn't affect mask check
1290+
target.setGraphicEffect(ShaderManager::Effect::Ghost, 100);
1291+
EXPECT_CALL(stageTarget, getFastBounds()).WillOnce(Return(Rect(2, 1, 6, -5)));
1292+
EXPECT_CALL(target1, getFastBounds()).WillOnce(Return(Rect(2, 1, 6, -5)));
1293+
EXPECT_CALL(target2, getFastBounds()).WillOnce(Return(Rect(-5, -6, 2, -8)));
1294+
EXPECT_CALL(target2, colorAtScratchPoint(3, -3)).WillOnce(Return(color4.toInt()));
1295+
EXPECT_CALL(target1, colorAtScratchPoint(3, -3)).WillOnce(Return(color1.toInt()));
1296+
ASSERT_TRUE(target.touchingColor(color5, color3));
1297+
ASSERT_EQ(target.graphicEffects().at(ShaderManager::Effect::Ghost), 100);
1298+
12721299
// Out of bounds: top left
12731300
target.updateX(-300);
12741301
target.updateY(200);

0 commit comments

Comments
 (0)