Skip to content

Commit 21e98f7

Browse files
authored
Merge pull request #154 from scratchcpp/remaining_graphic_effects
Implement remaining graphic effects
2 parents 95616e4 + 0efc1d8 commit 21e98f7

23 files changed

+927
-248
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ int main(int argc, char **argv) {
148148
- [x] Touching color blocks
149149
- [x] Pen blocks
150150
- [x] Monitors
151-
- [ ] Graphics effects (color, brightness and ghost are implemented)
151+
- [x] Graphics effects
152152
- [x] Speech and thought bubbles
153153
- [x] Question text box ("ask and wait" block)
154154

src/cputexturemanager.cpp

Lines changed: 172 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -33,35 +33,52 @@ GLubyte *CpuTextureManager::getTextureData(const Texture &texture)
3333
return it->second;
3434
}
3535

36-
const std::vector<QPoint> &CpuTextureManager::getTextureConvexHullPoints(const Texture &texture)
36+
void CpuTextureManager::getTextureConvexHullPoints(
37+
const Texture &texture,
38+
const QSize &skinSize,
39+
ShaderManager::Effect effectMask,
40+
const std::unordered_map<ShaderManager::Effect, double> &effects,
41+
std::vector<QPoint> &dst)
3742
{
38-
static const std::vector<QPoint> empty;
43+
dst.clear();
3944

4045
if (!texture.isValid())
41-
return empty;
46+
return;
4247

43-
const GLuint handle = texture.handle();
44-
auto it = m_convexHullPoints.find(handle);
48+
// Remove effects that don't change shape
49+
if (effectMask != 0) {
50+
const auto &allEffects = ShaderManager::effects();
4551

46-
if (it == m_convexHullPoints.cend()) {
47-
if (addTexture(texture))
48-
return m_convexHullPoints[handle];
49-
else
50-
return empty;
52+
for (ShaderManager::Effect effect : allEffects) {
53+
if ((effectMask & effect) != 0 && !ShaderManager::effectShapeChanges(effect))
54+
effectMask &= ~effect;
55+
}
56+
}
57+
58+
// If there are no shape-changing effects, use cached hull points
59+
if (effectMask == 0) {
60+
const GLuint handle = texture.handle();
61+
auto it = m_convexHullPoints.find(handle);
62+
63+
if (it == m_convexHullPoints.cend()) {
64+
if (addTexture(texture))
65+
dst = m_convexHullPoints[handle];
66+
} else
67+
dst = it->second;
5168
} else
52-
return it->second;
69+
readTexture(texture, skinSize, effectMask, effects, nullptr, dst);
5370
}
5471

55-
QRgb CpuTextureManager::getPointColor(const Texture &texture, int x, int y, const std::unordered_map<ShaderManager::Effect, double> &effects)
72+
QRgb CpuTextureManager::getPointColor(const Texture &texture, int x, int y, ShaderManager::Effect effectMask, const std::unordered_map<ShaderManager::Effect, double> &effects)
5673
{
5774
const int width = texture.width();
5875
const int height = texture.height();
5976

60-
if (!effects.empty()) {
77+
if (effectMask != 0) {
6178
// Get local position with effect transform
6279
QVector2D transformedCoords;
6380
const QVector2D localCoords(x / static_cast<float>(width), y / static_cast<float>(height));
64-
EffectTransform::transformPoint(effects, localCoords, transformedCoords);
81+
EffectTransform::transformPoint(effectMask, effects, texture.size(), localCoords, transformedCoords);
6582
x = transformedCoords.x() * width;
6683
y = transformedCoords.y() * height;
6784
}
@@ -72,25 +89,25 @@ QRgb CpuTextureManager::getPointColor(const Texture &texture, int x, int y, cons
7289
GLubyte *pixels = getTextureData(texture);
7390
QRgb color = qRgba(pixels[(y * width + x) * 4], pixels[(y * width + x) * 4 + 1], pixels[(y * width + x) * 4 + 2], pixels[(y * width + x) * 4 + 3]);
7491

75-
if (effects.empty())
92+
if (effectMask == 0)
7693
return color;
7794
else
78-
return EffectTransform::transformColor(effects, color);
95+
return EffectTransform::transformColor(effectMask, effects, color);
7996
}
8097

81-
bool CpuTextureManager::textureContainsPoint(const Texture &texture, const QPointF &localPoint, const std::unordered_map<ShaderManager::Effect, double> &effects)
98+
bool CpuTextureManager::textureContainsPoint(const Texture &texture, const QPointF &localPoint, ShaderManager::Effect effectMask, const std::unordered_map<ShaderManager::Effect, double> &effects)
8299
{
83100
// https://github.com/scratchfoundation/scratch-render/blob/7b823985bc6fe92f572cc3276a8915e550f7c5e6/src/Silhouette.js#L219-L226
84101
const int width = texture.width();
85102
const int height = texture.height();
86103
int x = localPoint.x();
87104
int y = localPoint.y();
88105

89-
if (!effects.empty()) {
106+
if (effectMask != 0) {
90107
// Get local position with effect transform
91108
QVector2D transformedCoords;
92109
const QVector2D localCoords(x / static_cast<float>(width), y / static_cast<float>(height));
93-
EffectTransform::transformPoint(effects, localCoords, transformedCoords);
110+
EffectTransform::transformPoint(effectMask, effects, texture.size(), localCoords, transformedCoords);
94111
x = transformedCoords.x() * width;
95112
y = transformedCoords.y() * height;
96113
}
@@ -118,7 +135,24 @@ void CpuTextureManager::removeTexture(const Texture &texture)
118135
}
119136
}
120137

121-
bool CpuTextureManager::addTexture(const Texture &texture)
138+
bool CpuTextureManager::addTexture(const Texture &tex)
139+
{
140+
if (!tex.isValid())
141+
return false;
142+
143+
const GLuint handle = tex.handle();
144+
m_textureData[handle] = nullptr;
145+
m_convexHullPoints[handle] = {};
146+
return readTexture(tex, QSize(), ShaderManager::Effect::NoEffect, {}, &m_textureData[handle], m_convexHullPoints[handle]);
147+
}
148+
149+
bool CpuTextureManager::readTexture(
150+
const Texture &texture,
151+
const QSize &skinSize,
152+
ShaderManager::Effect effectMask,
153+
const std::unordered_map<ShaderManager::Effect, double> &effects,
154+
GLubyte **data,
155+
std::vector<QPoint> &points) const
122156
{
123157
if (!texture.isValid())
124158
return false;
@@ -146,37 +180,134 @@ bool CpuTextureManager::addTexture(const Texture &texture)
146180
GLubyte *pixels = new GLubyte[width * height * 4]; // 4 channels (RGBA)
147181
glF.glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
148182

149-
// Flip vertically
150-
int rowSize = width * 4;
151-
GLubyte *tempRow = new GLubyte[rowSize];
152-
153-
for (size_t i = 0; i < height / 2; ++i) {
154-
size_t topRowIndex = i * rowSize;
155-
size_t bottomRowIndex = (height - 1 - i) * rowSize;
183+
std::vector<QPoint> leftHull;
184+
std::vector<QPoint> rightHull;
185+
leftHull.reserve(height);
186+
rightHull.reserve(height);
156187

157-
// Swap rows
158-
memcpy(tempRow, &pixels[topRowIndex], rowSize);
159-
memcpy(&pixels[topRowIndex], &pixels[bottomRowIndex], rowSize);
160-
memcpy(&pixels[bottomRowIndex], tempRow, rowSize);
188+
for (int x = 0; x < height; x++) {
189+
leftHull.push_back(QPoint(-1, -1));
190+
rightHull.push_back(QPoint(-1, -1));
161191
}
162192

163-
delete[] tempRow;
193+
int leftEndPointIndex = -1;
194+
int rightEndPointIndex = -1;
164195

165-
m_textureData[handle] = pixels;
166-
m_convexHullPoints[handle] = {};
167-
std::vector<QPoint> &hullPoints = m_convexHullPoints[handle];
196+
auto determinant = [](const QPoint &A, const QPoint &B, const QPoint &C) { return (B.x() - A.x()) * (C.y() - A.y()) - (B.y() - A.y()) * (C.x() - A.x()); };
168197

169-
// Get convex hull points
198+
// Get convex hull points (flipped vertically)
199+
// https://github.com/scratchfoundation/scratch-render/blob/0f6663f3148b4f994d58e19590e14c152f1cc2f8/src/RenderWebGL.js#L1829-L1955
170200
for (int y = 0; y < height; y++) {
171-
for (int x = 0; x < width; x++) {
172-
int index = (y * width + x) * 4; // 4 channels (RGBA)
201+
QPoint currentPoint;
202+
int x;
203+
const int flippedY = height - 1 - y;
204+
205+
for (x = 0; x < width; x++) {
206+
int transformedX = x;
207+
int transformedY = flippedY;
208+
209+
if (effectMask != 0) {
210+
// Get local position with effect transform
211+
QVector2D transformedCoords;
212+
const QVector2D localCoords(transformedX / static_cast<float>(width), transformedY / static_cast<float>(height));
213+
EffectTransform::transformPoint(effectMask, effects, skinSize, localCoords, transformedCoords);
214+
transformedX = transformedCoords.x() * width;
215+
transformedY = transformedCoords.y() * height;
216+
}
217+
218+
if ((transformedX >= 0 && transformedX < width) && (transformedY >= 0 && transformedY < height)) {
219+
int index = (transformedY * width + transformedX) * 4;
220+
221+
if (pixels[index + 3] > 0) {
222+
currentPoint.setX(x);
223+
currentPoint.setY(y);
224+
break;
225+
}
226+
}
227+
}
173228

174-
// Check alpha channel
175-
if (pixels[index + 3] > 0)
176-
hullPoints.push_back(QPoint(x, y));
229+
if (x >= width)
230+
continue;
231+
232+
while (leftEndPointIndex > 0) {
233+
if (determinant(leftHull[leftEndPointIndex], leftHull[leftEndPointIndex - 1], currentPoint) > 0)
234+
break;
235+
else {
236+
leftEndPointIndex--;
237+
}
238+
}
239+
240+
leftHull[++leftEndPointIndex] = currentPoint;
241+
242+
for (x = width - 1; x >= 0; x--) {
243+
int transformedX = x;
244+
int transformedY = flippedY;
245+
246+
if (effectMask != 0) {
247+
// Get local position with effect transform
248+
QVector2D transformedCoords;
249+
const QVector2D localCoords(transformedX / static_cast<float>(width), transformedY / static_cast<float>(height));
250+
EffectTransform::transformPoint(effectMask, effects, skinSize, localCoords, transformedCoords);
251+
transformedX = transformedCoords.x() * width;
252+
transformedY = transformedCoords.y() * height;
253+
}
254+
255+
if ((transformedX >= 0 && transformedX < width) && (transformedY >= 0 && transformedY < height)) {
256+
int index = (transformedY * width + transformedX) * 4;
257+
258+
if (pixels[index + 3] > 0) {
259+
currentPoint.setX(x);
260+
currentPoint.setY(y);
261+
break;
262+
}
263+
}
264+
}
265+
266+
while (rightEndPointIndex > 0) {
267+
if (determinant(rightHull[rightEndPointIndex], rightHull[rightEndPointIndex - 1], currentPoint) < 0)
268+
break;
269+
else
270+
rightEndPointIndex--;
177271
}
272+
273+
rightHull[++rightEndPointIndex] = currentPoint;
274+
}
275+
276+
points.clear();
277+
points.reserve((leftEndPointIndex + 1) + (rightEndPointIndex + 1));
278+
279+
long i;
280+
281+
for (i = 0; i < leftHull.size(); i++) {
282+
if (leftHull[i].x() >= 0)
283+
points.push_back(leftHull[i]);
178284
}
179285

286+
for (i = rightEndPointIndex; i >= 0; --i)
287+
if (rightHull[i].x() >= 0)
288+
points.push_back(rightHull[i]);
289+
290+
if (data) {
291+
// Flip vertically
292+
int rowSize = width * 4;
293+
GLubyte *tempRow = new GLubyte[rowSize];
294+
295+
for (size_t i = 0; i < height / 2; ++i) {
296+
size_t topRowIndex = i * rowSize;
297+
size_t bottomRowIndex = (height - 1 - i) * rowSize;
298+
299+
// Swap rows
300+
memcpy(tempRow, &pixels[topRowIndex], rowSize);
301+
memcpy(&pixels[topRowIndex], &pixels[bottomRowIndex], rowSize);
302+
memcpy(&pixels[bottomRowIndex], tempRow, rowSize);
303+
}
304+
305+
delete[] tempRow;
306+
307+
*data = pixels;
308+
} else
309+
delete[] pixels;
310+
180311
// Cleanup
181312
glF.glBindFramebuffer(GL_FRAMEBUFFER, 0);
182313
glF.glDeleteFramebuffers(1, &fbo);

src/cputexturemanager.h

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,27 @@ class CpuTextureManager
2020
~CpuTextureManager();
2121

2222
GLubyte *getTextureData(const Texture &texture);
23-
const std::vector<QPoint> &getTextureConvexHullPoints(const Texture &texture);
23+
void getTextureConvexHullPoints(
24+
const Texture &texture,
25+
const QSize &skinSize,
26+
ShaderManager::Effect effectMask,
27+
const std::unordered_map<ShaderManager::Effect, double> &effects,
28+
std::vector<QPoint> &dst);
2429

25-
QRgb getPointColor(const Texture &texture, int x, int y, const std::unordered_map<ShaderManager::Effect, double> &effects);
26-
bool textureContainsPoint(const Texture &texture, const QPointF &localPoint, const std::unordered_map<ShaderManager::Effect, double> &effects);
30+
QRgb getPointColor(const Texture &texture, int x, int y, ShaderManager::Effect effectMask, const std::unordered_map<ShaderManager::Effect, double> &effects);
31+
bool textureContainsPoint(const Texture &texture, const QPointF &localPoint, ShaderManager::Effect effectMask, const std::unordered_map<ShaderManager::Effect, double> &effects);
2732

2833
void removeTexture(const Texture &texture);
2934

3035
private:
31-
bool addTexture(const Texture &texture);
36+
bool addTexture(const Texture &tex);
37+
bool readTexture(
38+
const Texture &texture,
39+
const QSize &skinSize,
40+
ShaderManager::Effect effectMask,
41+
const std::unordered_map<ShaderManager::Effect, double> &effects,
42+
GLubyte **data,
43+
std::vector<QPoint> &points) const;
3244

3345
std::unordered_map<GLuint, GLubyte *> m_textureData;
3446
std::unordered_map<GLuint, std::vector<QPoint>> m_convexHullPoints;

0 commit comments

Comments
 (0)