Skip to content

Commit 931990b

Browse files
fsihDD Liu
authored and
DD Liu
committed
Revert "Revert "Put back in "Merge pull request #489 from adroitwhiz/touching-white-fixes"""
1 parent 0ae9cdd commit 931990b

File tree

8 files changed

+126
-31
lines changed

8 files changed

+126
-31
lines changed

Diff for: package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"eslint": "^4.6.1",
3535
"eslint-config-scratch": "^5.0.0",
3636
"gh-pages": "^1.0.0",
37-
"jsdoc": "^3.5.5",
37+
"jsdoc": "^3.6.0",
3838
"json": "^9.0.4",
3939
"playwright-chromium": "^1.0.1",
4040
"scratch-vm": "0.2.0-prerelease.20200622143012",

Diff for: src/Drawable.js

+5
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ const getLocalPosition = (drawable, vec) => {
3737
// localPosition matches that transformation.
3838
localPosition[0] = 0.5 - (((v0 * m[0]) + (v1 * m[4]) + m[12]) / d);
3939
localPosition[1] = (((v0 * m[1]) + (v1 * m[5]) + m[13]) / d) + 0.5;
40+
// Fix floating point issues near 0.
41+
// TODO: Check if this can be removed after render pull 479 is merged
42+
if (Math.abs(localPosition[0] < 1e-8)) localPosition[0] = 0;
43+
if (Math.abs(localPosition[1] < 1e-8)) localPosition[1] = 0;
4044
// Apply texture effect transform if the localPosition is within the drawable's space,
4145
// and any effects are currently active.
4246
if (drawable.enabledEffects !== 0 &&
@@ -728,6 +732,7 @@ class Drawable {
728732
dst[3] = 0;
729733
return dst;
730734
}
735+
731736
const textColor =
732737
// commenting out to only use nearest for now
733738
// drawable.useNearest() ?

Diff for: src/RenderWebGL.js

+77-16
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,23 @@ class RenderWebGL extends EventEmitter {
188188
/** @type {function} */
189189
this._exitRegion = null;
190190

191+
/** @type {object} */
192+
this._backgroundDrawRegionId = {
193+
enter: () => this._enterDrawBackground(),
194+
exit: () => this._exitDrawBackground()
195+
};
196+
191197
/** @type {Array.<snapshotCallback>} */
192198
this._snapshotCallbacks = [];
193199

200+
/** @type {Array<number>} */
201+
// Don't set this directly-- use setBackgroundColor so it stays in sync with _backgroundColor3b
202+
this._backgroundColor4f = [0, 0, 0, 1];
203+
204+
/** @type {Uint8ClampedArray} */
205+
// Don't set this directly-- use setBackgroundColor so it stays in sync with _backgroundColor4f
206+
this._backgroundColor3b = new Uint8ClampedArray(3);
207+
194208
this._createGeometry();
195209

196210
this.on(RenderConstants.Events.NativeSizeChanged, this.onNativeSizeChanged);
@@ -250,7 +264,14 @@ class RenderWebGL extends EventEmitter {
250264
* @param {number} blue The blue component for the background.
251265
*/
252266
setBackgroundColor (red, green, blue) {
253-
this._backgroundColor = [red, green, blue, 1];
267+
this._backgroundColor4f[0] = red;
268+
this._backgroundColor4f[1] = green;
269+
this._backgroundColor4f[2] = blue;
270+
271+
this._backgroundColor3b[0] = red * 255;
272+
this._backgroundColor3b[1] = green * 255;
273+
this._backgroundColor3b[2] = blue * 255;
274+
254275
}
255276

256277
/**
@@ -629,7 +650,7 @@ class RenderWebGL extends EventEmitter {
629650

630651
twgl.bindFramebufferInfo(gl, null);
631652
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
632-
gl.clearColor.apply(gl, this._backgroundColor);
653+
gl.clearColor.apply(gl, this._backgroundColor4f);
633654
gl.clear(gl.COLOR_BUFFER_BIT);
634655

635656
this._drawThese(this._drawList, ShaderManager.DRAW_MODE.default, this._projection);
@@ -745,12 +766,22 @@ class RenderWebGL extends EventEmitter {
745766
*/
746767
isTouchingColor (drawableID, color3b, mask3b) {
747768
const candidates = this._candidatesTouching(drawableID, this._visibleDrawList);
748-
if (candidates.length === 0) {
769+
770+
let bounds;
771+
if (colorMatches(color3b, this._backgroundColor3b, 0)) {
772+
// If the color we're checking for is the background color, don't confine the check to
773+
// candidate drawables' bounds--since the background spans the entire stage, we must check
774+
// everything that lies inside the drawable.
775+
bounds = this._touchingBounds(drawableID);
776+
// e.g. empty costume, or off the stage
777+
if (bounds === null) return false;
778+
} else if (candidates.length === 0) {
779+
// If not checking for the background color, we can return early if there are no candidate drawables.
749780
return false;
781+
} else {
782+
bounds = this._candidatesBounds(candidates);
750783
}
751784

752-
const bounds = this._candidatesBounds(candidates);
753-
754785
const maxPixelsForCPU = this._getMaxPixelsForCPU();
755786

756787
const debugCanvasContext = this._debugCanvas && this._debugCanvas.getContext('2d');
@@ -811,6 +842,19 @@ class RenderWebGL extends EventEmitter {
811842
}
812843
}
813844

845+
_enterDrawBackground () {
846+
const gl = this.gl;
847+
const currentShader = this._shaderManager.getShader(ShaderManager.DRAW_MODE.background, 0);
848+
gl.disable(gl.BLEND);
849+
gl.useProgram(currentShader.program);
850+
twgl.setBuffersAndAttributes(gl, currentShader, this._bufferInfo);
851+
}
852+
853+
_exitDrawBackground () {
854+
const gl = this.gl;
855+
gl.enable(gl.BLEND);
856+
}
857+
814858
_isTouchingColorGpuStart (drawableID, candidateIDs, bounds, color3b, mask3b) {
815859
this._doExitDrawRegion();
816860

@@ -822,15 +866,8 @@ class RenderWebGL extends EventEmitter {
822866
gl.viewport(0, 0, bounds.width, bounds.height);
823867
const projection = twgl.m4.ortho(bounds.left, bounds.right, bounds.top, bounds.bottom, -1, 1);
824868

825-
let fillBackgroundColor = this._backgroundColor;
826-
827-
// When using masking such that the background fill color will showing through, ensure we don't
828-
// fill using the same color that we are trying to detect!
829-
if (color3b[0] > 196 && color3b[1] > 196 && color3b[2] > 196) {
830-
fillBackgroundColor = [0, 0, 0, 255];
831-
}
832-
833-
gl.clearColor.apply(gl, fillBackgroundColor);
869+
// Clear the query buffer to fully transparent. This will be the color of pixels that fail the stencil test.
870+
gl.clearColor(0, 0, 0, 0);
834871
gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
835872

836873
let extraUniforms;
@@ -842,6 +879,9 @@ class RenderWebGL extends EventEmitter {
842879
}
843880

844881
try {
882+
// Using the stencil buffer, mask out the drawing to either the drawable's alpha channel
883+
// or pixels of the drawable which match the mask color, depending on whether a mask color is given.
884+
// Masked-out pixels will not be checked.
845885
gl.enable(gl.STENCIL_TEST);
846886
gl.stencilFunc(gl.ALWAYS, 1, 1);
847887
gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);
@@ -862,12 +902,25 @@ class RenderWebGL extends EventEmitter {
862902
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
863903
gl.colorMask(true, true, true, true);
864904

905+
// Draw the background as a quad. Drawing a background with gl.clear will not mask to the stenciled area.
906+
this.enterDrawRegion(this._backgroundDrawRegionId);
907+
908+
const uniforms = {
909+
u_backgroundColor: this._backgroundColor4f
910+
};
911+
912+
const currentShader = this._shaderManager.getShader(ShaderManager.DRAW_MODE.background, 0);
913+
twgl.setUniforms(currentShader, uniforms);
914+
twgl.drawBufferInfo(gl, this._bufferInfo, gl.TRIANGLES);
915+
916+
// Draw the candidate drawables on top of the background.
865917
this._drawThese(candidateIDs, ShaderManager.DRAW_MODE.default, projection,
866918
{idFilterFunc: testID => testID !== drawableID}
867919
);
868920
} finally {
869921
gl.colorMask(true, true, true, true);
870922
gl.disable(gl.STENCIL_TEST);
923+
this._doExitDrawRegion();
871924
}
872925
}
873926

@@ -886,7 +939,8 @@ class RenderWebGL extends EventEmitter {
886939
}
887940

888941
for (let pixelBase = 0; pixelBase < pixels.length; pixelBase += 4) {
889-
if (colorMatches(color3b, pixels, pixelBase)) {
942+
// Transparent pixels are masked (either by the drawable's alpha channel or color mask).
943+
if (pixels[pixelBase + 3] !== 0 && colorMatches(color3b, pixels, pixelBase)) {
890944
return true;
891945
}
892946
}
@@ -1321,7 +1375,7 @@ class RenderWebGL extends EventEmitter {
13211375
gl.viewport(0, 0, bounds.width, bounds.height);
13221376
const projection = twgl.m4.ortho(bounds.left, bounds.right, bounds.top, bounds.bottom, -1, 1);
13231377

1324-
gl.clearColor.apply(gl, this._backgroundColor);
1378+
gl.clearColor.apply(gl, this._backgroundColor4f);
13251379
gl.clear(gl.COLOR_BUFFER_BIT);
13261380
this._drawThese(this._drawList, ShaderManager.DRAW_MODE.default, projection);
13271381

@@ -1411,6 +1465,13 @@ class RenderWebGL extends EventEmitter {
14111465
// Update the CPU position data
14121466
drawable.updateCPURenderAttributes();
14131467
const candidateBounds = drawable.getFastBounds();
1468+
1469+
// Push bounds out to integers. If a drawable extends out into half a pixel, that half-pixel still
1470+
// needs to be tested. Plus, in some areas we construct another rectangle from the union of these,
1471+
// and iterate over its pixels (width * height). Turns out that doesn't work so well when the
1472+
// width/height aren't integers.
1473+
candidateBounds.snapToInt();
1474+
14141475
if (bounds.intersects(candidateBounds)) {
14151476
result.push({
14161477
id,

Diff for: src/ShaderManager.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,12 @@ ShaderManager.DRAW_MODE = {
176176
/**
177177
* Draw a line with caps.
178178
*/
179-
line: 'line'
179+
line: 'line',
180+
181+
/**
182+
* Draw the background in a certain color. Must sometimes be used instead of gl.clear.
183+
*/
184+
background: 'background'
180185
};
181186

182187
module.exports = ShaderManager;

Diff for: src/Silhouette.js

+20-10
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
*/
1111
let __SilhouetteUpdateCanvas;
1212

13+
// Optimized Math.min and Math.max for integers;
14+
// taken from https://web.archive.org/web/20190716181049/http://guihaire.com/code/?p=549
15+
const intMin = (i, j) => j ^ ((i ^ j) & ((i - j) >> 31));
16+
const intMax = (i, j) => i ^ ((i ^ j) & ((i - j) >> 31));
17+
1318
/**
1419
* Internal helper function (in hopes that compiler can inline). Get a pixel
1520
* from silhouette data, or 0 if outside it's bounds.
@@ -40,13 +45,18 @@ const __cornerWork = [
4045
/**
4146
* Get the color from a given silhouette at an x/y local texture position.
4247
* Multiply color values by alpha for proper blending.
43-
* @param {Silhouette} The silhouette to sample.
44-
* @param {number} x X position of texture (0-1).
45-
* @param {number} y Y position of texture (0-1).
48+
* @param {Silhouette} $0 The silhouette to sample.
49+
* @param {number} x X position of texture [0, width).
50+
* @param {number} y Y position of texture [0, height).
4651
* @param {Uint8ClampedArray} dst A color 4b space.
4752
* @return {Uint8ClampedArray} The dst vector.
4853
*/
4954
const getColor4b = ({_width: width, _height: height, _colorData: data}, x, y, dst) => {
55+
// Clamp coords to edge, matching GL_CLAMP_TO_EDGE.
56+
// (See github.com/LLK/scratch-render/blob/954cfff02b08069a082cbedd415c1fecd9b1e4fb/src/BitmapSkin.js#L88)
57+
x = intMax(0, intMin(x, width - 1));
58+
y = intMax(0, intMin(y, height - 1));
59+
5060
// 0 if outside bounds, otherwise read from data.
5161
if (x >= width || y >= height || x < 0 || y < 0) {
5262
return dst.fill(0);
@@ -64,17 +74,17 @@ const getColor4b = ({_width: width, _height: height, _colorData: data}, x, y, ds
6474
/**
6575
* Get the color from a given silhouette at an x/y local texture position.
6676
* Do not multiply color values by alpha, as it has already been done.
67-
* @param {Silhouette} The silhouette to sample.
68-
* @param {number} x X position of texture (0-1).
69-
* @param {number} y Y position of texture (0-1).
77+
* @param {Silhouette} $0 The silhouette to sample.
78+
* @param {number} x X position of texture [0, width).
79+
* @param {number} y Y position of texture [0, height).
7080
* @param {Uint8ClampedArray} dst A color 4b space.
7181
* @return {Uint8ClampedArray} The dst vector.
7282
*/
7383
const getPremultipliedColor4b = ({_width: width, _height: height, _colorData: data}, x, y, dst) => {
74-
// 0 if outside bounds, otherwise read from data.
75-
if (x >= width || y >= height || x < 0 || y < 0) {
76-
return dst.fill(0);
77-
}
84+
// Clamp coords to edge, matching GL_CLAMP_TO_EDGE.
85+
x = intMax(0, intMin(x, width - 1));
86+
y = intMax(0, intMin(y, height - 1));
87+
7888
const offset = ((y * width) + x) * 4;
7989
dst[0] = data[offset];
8090
dst[1] = data[offset + 1];

Diff for: src/shaders/sprite.frag

+14-2
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,15 @@ uniform float u_lineThickness;
3939
uniform float u_lineLength;
4040
#endif // DRAW_MODE_line
4141

42+
#ifdef DRAW_MODE_background
43+
uniform vec4 u_backgroundColor;
44+
#endif // DRAW_MODE_background
45+
4246
uniform sampler2D u_skin;
4347

48+
#ifndef DRAW_MODE_background
4449
varying vec2 v_texCoord;
50+
#endif
4551

4652
// Add this to divisors to prevent division by 0, which results in NaNs propagating through calculations.
4753
// Smaller values can cause problems on some mobile devices.
@@ -109,7 +115,7 @@ const vec2 kCenter = vec2(0.5, 0.5);
109115

110116
void main()
111117
{
112-
#ifndef DRAW_MODE_line
118+
#if !(defined(DRAW_MODE_line) || defined(DRAW_MODE_background))
113119
vec2 texcoord0 = v_texCoord;
114120

115121
#ifdef ENABLE_mosaic
@@ -215,7 +221,9 @@ void main()
215221
gl_FragColor.rgb /= gl_FragColor.a + epsilon;
216222
#endif
217223

218-
#else // DRAW_MODE_line
224+
#endif // !(defined(DRAW_MODE_line) || defined(DRAW_MODE_background))
225+
226+
#ifdef DRAW_MODE_line
219227
// Maaaaagic antialiased-line-with-round-caps shader.
220228

221229
// "along-the-lineness". This increases parallel to the line.
@@ -234,4 +242,8 @@ void main()
234242
// the closer we are to the line, invert it.
235243
gl_FragColor = u_lineColor * clamp(1.0 - line, 0.0, 1.0);
236244
#endif // DRAW_MODE_line
245+
246+
#ifdef DRAW_MODE_background
247+
gl_FragColor = u_backgroundColor;
248+
#endif
237249
}

Diff for: src/shaders/sprite.vert

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ uniform vec4 u_penPoints;
1414
const float epsilon = 1e-3;
1515
#endif
1616

17-
#ifndef DRAW_MODE_line
17+
#if !(defined(DRAW_MODE_line) || defined(DRAW_MODE_background))
1818
uniform mat4 u_projectionMatrix;
1919
uniform mat4 u_modelMatrix;
2020
attribute vec2 a_texCoord;
@@ -66,6 +66,8 @@ void main() {
6666
// 4. Apply view transform
6767
position *= 2.0 / u_stageSize;
6868
gl_Position = vec4(position, 0, 1);
69+
#elif defined(DRAW_MODE_background)
70+
gl_Position = vec4(a_position * 2.0, 0, 1);
6971
#else
7072
gl_Position = u_projectionMatrix * u_modelMatrix * vec4(a_position, 0, 1);
7173
v_texCoord = a_texCoord;

Diff for: test/integration/scratch-tests/clear-color.sb3

54.6 KB
Binary file not shown.

0 commit comments

Comments
 (0)