Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ int main(int argc, char **argv) {
- [x] Clones
- [x] Sprite dragging
- [x] Touching sprite block
- [ ] Touching color blocks (color is touching color is not implemented yet)
- [x] Touching color blocks
- [x] Pen blocks
- [x] Monitors
- [ ] Graphics effects (color, brightness and ghost are implemented)
Expand Down
1 change: 1 addition & 0 deletions src/irenderedtarget.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class IRenderedTarget : public QNanoQuickItem

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

} // namespace scratchcpprender
113 changes: 76 additions & 37 deletions src/renderedtarget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -658,43 +658,12 @@ bool RenderedTarget::touchingClones(const std::vector<libscratchcpp::Sprite *> &

bool RenderedTarget::touchingColor(const Value &color) const
{
// https://github.com/scratchfoundation/scratch-render/blob/0a04c2fb165f5c20406ec34ab2ea5682ae45d6e0/src/RenderWebGL.js#L775-L841
if (!m_engine)
return false;

QRgb rgb = convertColor(color);

std::vector<Target *> targets;
m_engine->getVisibleTargets(targets);

QRectF myRect = touchingBounds();
std::vector<IRenderedTarget *> candidates;
QRectF bounds = candidatesBounds(myRect, targets, candidates);

if (colorMatches(rgb, qRgb(255, 255, 255))) {
// The color we're checking for is the background color which spans the entire stage
bounds = myRect;

if (bounds.isEmpty())
return false;
} else if (candidates.empty()) {
// If not checking for the background color, we can return early if there are no candidate drawables
return false;
}

// Loop through the points of the union
for (int y = bounds.top(); y <= bounds.bottom(); y++) {
for (int x = bounds.left(); x <= bounds.right(); x++) {
if (this->containsScratchPoint(x, y)) {
QRgb pixelColor = sampleColor3b(x, y, candidates);

if (colorMatches(rgb, pixelColor))
return true;
}
}
}
return touchingColor(color, false, Value());
}

return false;
bool RenderedTarget::touchingColor(const Value &color, const Value &mask) const
{
return touchingColor(color, true, mask);
}

void RenderedTarget::calculatePos()
Expand Down Expand Up @@ -896,6 +865,70 @@ CpuTextureManager *RenderedTarget::textureManager() const
return m_textureManager.get();
}

bool RenderedTarget::touchingColor(const libscratchcpp::Value &color, bool hasMask, const libscratchcpp::Value &mask) const
{
// https://github.com/scratchfoundation/scratch-render/blob/0a04c2fb165f5c20406ec34ab2ea5682ae45d6e0/src/RenderWebGL.js#L775-L841
if (!m_engine)
return false;

QRgb rgb = convertColor(color);
QRgb mask3b;
double ghostValue = 0;

if (hasMask) {
// Ignore ghost effect when checking mask
auto it = m_graphicEffects.find(ShaderManager::Effect::Ghost);

if (it != m_graphicEffects.cend()) {
ghostValue = it->second;
m_graphicEffects.erase(ShaderManager::Effect::Ghost);
}

mask3b = convertColor(mask);
}

std::vector<Target *> targets;
m_engine->getVisibleTargets(targets);

QRectF myRect = touchingBounds();
std::vector<IRenderedTarget *> candidates;
QRectF bounds = candidatesBounds(myRect, targets, candidates);

if (colorMatches(rgb, qRgb(255, 255, 255))) {
// The color we're checking for is the background color which spans the entire stage
bounds = myRect;

if (bounds.isEmpty())
return false;
} else if (candidates.empty()) {
// If not checking for the background color, we can return early if there are no candidate drawables
return false;
}

// Loop through the points of the union
for (int y = bounds.top(); y <= bounds.bottom(); y++) {
for (int x = bounds.left(); x <= bounds.right(); x++) {
if (hasMask ? maskMatches(colorAtScratchPoint(x, y), mask3b) : this->containsScratchPoint(x, y)) {
QRgb pixelColor = sampleColor3b(x, y, candidates);

if (colorMatches(rgb, pixelColor)) {
// Restore ghost effect value
if (hasMask && ghostValue != 0)
m_graphicEffects[ShaderManager::Effect::Ghost] = ghostValue;

return true;
}
}
}
}

// Restore ghost effect value
if (hasMask && ghostValue != 0)
m_graphicEffects[ShaderManager::Effect::Ghost] = ghostValue;

return false;
}

QRectF RenderedTarget::touchingBounds() const
{
// https://github.com/scratchfoundation/scratch-render/blob/0a04c2fb165f5c20406ec34ab2ea5682ae45d6e0/src/RenderWebGL.js#L1330-L1350
Expand Down Expand Up @@ -1051,7 +1084,13 @@ QRgb RenderedTarget::convertColor(const libscratchcpp::Value &color)
bool RenderedTarget::colorMatches(QRgb a, QRgb b)
{
// https://github.com/scratchfoundation/scratch-render/blob/0a04c2fb165f5c20406ec34ab2ea5682ae45d6e0/src/RenderWebGL.js#L77-L81
return (qRed(a) & 0b11111000) == (qRed(b) & 0b11111000) && (qGreen(a) & 0b11111000) == (qGreen(b) & 0b11111000) && (qBlue(a) & 0b11110000) == (qBlue(b) & 0b11110000);
return qAlpha(a) > 0 && (qRed(a) & 0b11111000) == (qRed(b) & 0b11111000) && (qGreen(a) & 0b11111000) == (qGreen(b) & 0b11111000) && (qBlue(a) & 0b11110000) == (qBlue(b) & 0b11110000);
}

bool RenderedTarget::maskMatches(QRgb a, QRgb b)
{
// https://github.com/scratchfoundation/scratch-render/blob/0a04c2fb165f5c20406ec34ab2ea5682ae45d6e0/src/RenderWebGL.js#L59-L65
return (qRed(a) & 0b11111000) == (qRed(b) & 0b11111000) && (qGreen(a) & 0b11111000) == (qGreen(b) & 0b11111000) && (qBlue(a) & 0b11111000) == (qBlue(b) & 0b11111000);
}

QRgb RenderedTarget::sampleColor3b(double x, double y, const std::vector<IRenderedTarget *> &targets) const
Expand Down
5 changes: 4 additions & 1 deletion src/renderedtarget.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ class RenderedTarget : public IRenderedTarget

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

signals:
void engineChanged();
Expand Down Expand Up @@ -131,6 +132,7 @@ class RenderedTarget : public IRenderedTarget
QPointF mapFromStageWithOriginPoint(const QPointF &scenePoint) const;
QPointF mapFromScratchToLocal(const QPointF &point) const;
CpuTextureManager *textureManager() const;
bool touchingColor(const libscratchcpp::Value &color, bool hasMask, const libscratchcpp::Value &mask) const;
QRectF touchingBounds() const;
QRectF candidatesBounds(const QRectF &targetRect, const std::vector<libscratchcpp::Target *> &candidates, std::vector<IRenderedTarget *> &dst) const;
QRectF candidatesBounds(const QRectF &targetRect, const std::vector<libscratchcpp::Sprite *> &candidates, std::vector<IRenderedTarget *> &dst) const;
Expand All @@ -139,6 +141,7 @@ class RenderedTarget : public IRenderedTarget
static void clampRect(libscratchcpp::Rect &rect, double left, double right, double bottom, double top);
static QRgb convertColor(const libscratchcpp::Value &color);
static bool colorMatches(QRgb a, QRgb b);
static bool maskMatches(QRgb a, QRgb b);
QRgb sampleColor3b(double x, double y, const std::vector<IRenderedTarget *> &targets) const;

libscratchcpp::IEngine *m_engine = nullptr;
Expand All @@ -156,7 +159,7 @@ class RenderedTarget : public IRenderedTarget
Texture m_cpuTexture; // without stage scale
mutable std::shared_ptr<CpuTextureManager> m_textureManager; // NOTE: Use textureManager()!
std::unique_ptr<QOpenGLFunctions> m_glF;
std::unordered_map<ShaderManager::Effect, double> m_graphicEffects;
mutable std::unordered_map<ShaderManager::Effect, double> m_graphicEffects;
double m_size = 1;
double m_x = 0;
double m_y = 0;
Expand Down
2 changes: 1 addition & 1 deletion src/spritemodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ bool SpriteModel::touchingColor(const libscratchcpp::Value &color) const

bool SpriteModel::touchingColor(const libscratchcpp::Value &color, const libscratchcpp::Value &mask) const
{
return false;
return m_renderedTarget->touchingColor(color, mask);
}

libscratchcpp::Sprite *SpriteModel::sprite() const
Expand Down
2 changes: 1 addition & 1 deletion src/stagemodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ bool StageModel::touchingColor(const libscratchcpp::Value &color) const

bool StageModel::touchingColor(const libscratchcpp::Value &color, const libscratchcpp::Value &mask) const
{
return false;
return m_renderedTarget->touchingColor(color, mask);
}

void StageModel::loadCostume()
Expand Down
1 change: 1 addition & 0 deletions test/mocks/renderedtargetmock.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class RenderedTargetMock : public IRenderedTarget

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

MOCK_METHOD(QNanoQuickItemPainter *, createItemPainter, (), (const, override));
MOCK_METHOD(void, hoverEnterEvent, (QHoverEvent *), (override));
Expand Down
27 changes: 27 additions & 0 deletions test/renderedtarget/renderedtarget_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1269,6 +1269,33 @@ TEST_F(RenderedTargetTest, TouchingColor)
EXPECT_CALL(stageTarget, colorAtScratchPoint).Times(0);
ASSERT_FALSE(target.touchingColor(color3));

// Mask (color is touching color)
EXPECT_CALL(stageTarget, getFastBounds()).WillOnce(Return(Rect(2, 1, 6, -5)));
EXPECT_CALL(target1, getFastBounds()).WillOnce(Return(Rect(2, 1, 6, -5)));
EXPECT_CALL(target2, getFastBounds()).WillOnce(Return(Rect(-5, -6, 2, -8)));
EXPECT_CALL(target2, colorAtScratchPoint(3, -3)).WillOnce(Return(color4.toInt()));
EXPECT_CALL(target1, colorAtScratchPoint(3, -3)).WillOnce(Return(color1.toInt()));
ASSERT_TRUE(target.touchingColor(color5, color3));

EXPECT_CALL(stageTarget, getFastBounds()).WillOnce(Return(Rect(5, 1, 6, -5)));
EXPECT_CALL(target1, getFastBounds()).WillOnce(Return(Rect(5, 1, 6, -5)));
EXPECT_CALL(target2, getFastBounds()).WillOnce(Return(Rect(-5, -6, 2, -8)));
EXPECT_CALL(target2, colorAtScratchPoint).Times(0);
EXPECT_CALL(target1, colorAtScratchPoint).Times(0);
EXPECT_CALL(penLayer, colorAtScratchPoint).Times(0);
EXPECT_CALL(stageTarget, colorAtScratchPoint).Times(0);
ASSERT_FALSE(target.touchingColor(color3, color3));

// Ghost effect shouldn't affect mask check
target.setGraphicEffect(ShaderManager::Effect::Ghost, 100);
EXPECT_CALL(stageTarget, getFastBounds()).WillOnce(Return(Rect(2, 1, 6, -5)));
EXPECT_CALL(target1, getFastBounds()).WillOnce(Return(Rect(2, 1, 6, -5)));
EXPECT_CALL(target2, getFastBounds()).WillOnce(Return(Rect(-5, -6, 2, -8)));
EXPECT_CALL(target2, colorAtScratchPoint(3, -3)).WillOnce(Return(color4.toInt()));
EXPECT_CALL(target1, colorAtScratchPoint(3, -3)).WillOnce(Return(color1.toInt()));
ASSERT_TRUE(target.touchingColor(color5, color3));
ASSERT_EQ(target.graphicEffects().at(ShaderManager::Effect::Ghost), 100);

// Out of bounds: top left
target.updateX(-300);
target.updateY(200);
Expand Down
16 changes: 11 additions & 5 deletions test/target_models/spritemodel_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -388,12 +388,18 @@ TEST(SpriteModelTest, TouchingColor)
RenderedTargetMock renderedTarget;
model.setRenderedTarget(&renderedTarget);

Value color = 123;
EXPECT_CALL(renderedTarget, touchingColor(color)).WillOnce(Return(false));
ASSERT_FALSE(model.touchingColor(color));
Value color1 = 123, color2 = 456;
EXPECT_CALL(renderedTarget, touchingColor(color1)).WillOnce(Return(false));
ASSERT_FALSE(model.touchingColor(color1));

EXPECT_CALL(renderedTarget, touchingColor(color)).WillOnce(Return(true));
ASSERT_TRUE(model.touchingColor(color));
EXPECT_CALL(renderedTarget, touchingColor(color1)).WillOnce(Return(true));
ASSERT_TRUE(model.touchingColor(color1));

EXPECT_CALL(renderedTarget, touchingColor(color1, color2)).WillOnce(Return(false));
ASSERT_FALSE(model.touchingColor(color1, color2));

EXPECT_CALL(renderedTarget, touchingColor(color1, color2)).WillOnce(Return(true));
ASSERT_TRUE(model.touchingColor(color1, color2));
}

TEST(SpriteModelTest, RenderedTarget)
Expand Down
16 changes: 11 additions & 5 deletions test/target_models/stagemodel_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,18 @@ TEST(StageModelTest, TouchingColor)
RenderedTargetMock renderedTarget;
model.setRenderedTarget(&renderedTarget);

Value color = 123;
EXPECT_CALL(renderedTarget, touchingColor(color)).WillOnce(Return(false));
ASSERT_FALSE(model.touchingColor(color));
Value color1 = 123, color2 = 456;
EXPECT_CALL(renderedTarget, touchingColor(color1)).WillOnce(Return(false));
ASSERT_FALSE(model.touchingColor(color1));

EXPECT_CALL(renderedTarget, touchingColor(color)).WillOnce(Return(true));
ASSERT_TRUE(model.touchingColor(color));
EXPECT_CALL(renderedTarget, touchingColor(color1)).WillOnce(Return(true));
ASSERT_TRUE(model.touchingColor(color1));

EXPECT_CALL(renderedTarget, touchingColor(color1, color2)).WillOnce(Return(false));
ASSERT_FALSE(model.touchingColor(color1, color2));

EXPECT_CALL(renderedTarget, touchingColor(color1, color2)).WillOnce(Return(true));
ASSERT_TRUE(model.touchingColor(color1, color2));
}

TEST(StageModelTest, RenderedTarget)
Expand Down