Skip to content

Commit f3f58aa

Browse files
authored
Merge pull request #144 from scratchcpp/color_is_touching_color
Implement color is touching color
2 parents 63dd35a + fe0d32b commit f3f58aa

10 files changed

+134
-51
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ int main(int argc, char **argv) {
145145
- [x] Clones
146146
- [x] Sprite dragging
147147
- [x] Touching sprite block
148-
- [ ] Touching color blocks (color is touching color is not implemented yet)
148+
- [x] Touching color blocks
149149
- [x] Pen blocks
150150
- [x] Monitors
151151
- [ ] Graphics effects (color, brightness and ghost are implemented)

src/irenderedtarget.h

+1
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

+76-37
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

+4-1
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;

src/spritemodel.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ bool SpriteModel::touchingColor(const libscratchcpp::Value &color) const
177177

178178
bool SpriteModel::touchingColor(const libscratchcpp::Value &color, const libscratchcpp::Value &mask) const
179179
{
180-
return false;
180+
return m_renderedTarget->touchingColor(color, mask);
181181
}
182182

183183
libscratchcpp::Sprite *SpriteModel::sprite() const

src/stagemodel.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ bool StageModel::touchingColor(const libscratchcpp::Value &color) const
114114

115115
bool StageModel::touchingColor(const libscratchcpp::Value &color, const libscratchcpp::Value &mask) const
116116
{
117-
return false;
117+
return m_renderedTarget->touchingColor(color, mask);
118118
}
119119

120120
void StageModel::loadCostume()

test/mocks/renderedtargetmock.h

+1
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

+27
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);

test/target_models/spritemodel_test.cpp

+11-5
Original file line numberDiff line numberDiff line change
@@ -388,12 +388,18 @@ TEST(SpriteModelTest, TouchingColor)
388388
RenderedTargetMock renderedTarget;
389389
model.setRenderedTarget(&renderedTarget);
390390

391-
Value color = 123;
392-
EXPECT_CALL(renderedTarget, touchingColor(color)).WillOnce(Return(false));
393-
ASSERT_FALSE(model.touchingColor(color));
391+
Value color1 = 123, color2 = 456;
392+
EXPECT_CALL(renderedTarget, touchingColor(color1)).WillOnce(Return(false));
393+
ASSERT_FALSE(model.touchingColor(color1));
394394

395-
EXPECT_CALL(renderedTarget, touchingColor(color)).WillOnce(Return(true));
396-
ASSERT_TRUE(model.touchingColor(color));
395+
EXPECT_CALL(renderedTarget, touchingColor(color1)).WillOnce(Return(true));
396+
ASSERT_TRUE(model.touchingColor(color1));
397+
398+
EXPECT_CALL(renderedTarget, touchingColor(color1, color2)).WillOnce(Return(false));
399+
ASSERT_FALSE(model.touchingColor(color1, color2));
400+
401+
EXPECT_CALL(renderedTarget, touchingColor(color1, color2)).WillOnce(Return(true));
402+
ASSERT_TRUE(model.touchingColor(color1, color2));
397403
}
398404

399405
TEST(SpriteModelTest, RenderedTarget)

test/target_models/stagemodel_test.cpp

+11-5
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,18 @@ TEST(StageModelTest, TouchingColor)
181181
RenderedTargetMock renderedTarget;
182182
model.setRenderedTarget(&renderedTarget);
183183

184-
Value color = 123;
185-
EXPECT_CALL(renderedTarget, touchingColor(color)).WillOnce(Return(false));
186-
ASSERT_FALSE(model.touchingColor(color));
184+
Value color1 = 123, color2 = 456;
185+
EXPECT_CALL(renderedTarget, touchingColor(color1)).WillOnce(Return(false));
186+
ASSERT_FALSE(model.touchingColor(color1));
187187

188-
EXPECT_CALL(renderedTarget, touchingColor(color)).WillOnce(Return(true));
189-
ASSERT_TRUE(model.touchingColor(color));
188+
EXPECT_CALL(renderedTarget, touchingColor(color1)).WillOnce(Return(true));
189+
ASSERT_TRUE(model.touchingColor(color1));
190+
191+
EXPECT_CALL(renderedTarget, touchingColor(color1, color2)).WillOnce(Return(false));
192+
ASSERT_FALSE(model.touchingColor(color1, color2));
193+
194+
EXPECT_CALL(renderedTarget, touchingColor(color1, color2)).WillOnce(Return(true));
195+
ASSERT_TRUE(model.touchingColor(color1, color2));
190196
}
191197

192198
TEST(StageModelTest, RenderedTarget)

0 commit comments

Comments
 (0)