Skip to content

Commit e3c68e7

Browse files
authored
Merge pull request #407 from cwillisf/coordinates-fixups
Adjust CPU isTouchingColor to match GPU results
2 parents b4f9f28 + e64d872 commit e3c68e7

File tree

3 files changed

+62
-57
lines changed

3 files changed

+62
-57
lines changed

src/Drawable.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,11 @@ const __isTouchingPosition = twgl.v3.create();
2424
* @return {twgl.v3} [x,y] texture space float vector - transformed by effects and matrix
2525
*/
2626
const getLocalPosition = (drawable, vec) => {
27-
// Transfrom from world coordinates to Drawable coordinates.
27+
// Transform from world coordinates to Drawable coordinates.
2828
const localPosition = __isTouchingPosition;
2929
const v0 = vec[0];
3030
const v1 = vec[1];
3131
const m = drawable._inverseMatrix;
32-
// var v2 = v[2];
3332
const d = (v0 * m[3]) + (v1 * m[7]) + m[15];
3433
// The RenderWebGL quad flips the texture's X axis. So rendered bottom
3534
// left is 1, 0 and the top right is 0, 1. Flip the X axis so
@@ -342,7 +341,7 @@ class Drawable {
342341
// Drawable configures a 3D matrix for drawing in WebGL, but most values
343342
// will never be set because the inputs are on the X and Y position axis
344343
// and the Z rotation axis. Drawable can bring the work inside
345-
// _calculateTransform and greatly reduce the ammount of math and array
344+
// _calculateTransform and greatly reduce the amount of math and array
346345
// assignments needed.
347346

348347
const scale0 = this._skinScale[0];
@@ -625,11 +624,6 @@ class Drawable {
625624
*/
626625
static sampleColor4b (vec, drawable, dst) {
627626
const localPosition = getLocalPosition(drawable, vec);
628-
if (localPosition[0] < 0 || localPosition[1] < 0 ||
629-
localPosition[0] > 1 || localPosition[1] > 1) {
630-
dst[3] = 0;
631-
return dst;
632-
}
633627
const textColor =
634628
// commenting out to only use nearest for now
635629
// drawable.useNearest ?

src/RenderWebGL.js

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,7 @@ class RenderWebGL extends EventEmitter {
256256
this._yBottom = yBottom;
257257
this._yTop = yTop;
258258

259-
// swap yBottom & yTop to fit Scratch convention of +y=up
260-
this._projection = twgl.m4.ortho(xLeft, xRight, yBottom, yTop, -1, 1);
259+
this._projection = this._makeOrthoProjection(xLeft, xRight, yBottom, yTop);
261260

262261
this._setNativeSize(Math.abs(xRight - xLeft), Math.abs(yBottom - yTop));
263262
}
@@ -281,6 +280,20 @@ class RenderWebGL extends EventEmitter {
281280
this.emit(RenderConstants.Events.NativeSizeChanged, {newSize: this._nativeSize});
282281
}
283282

283+
/**
284+
* Build a projection matrix for Scratch coordinates. For example, `_makeOrthoProjection(-240,240,-180,180)` will
285+
* mean the lower-left pixel is at (-240,-179) and the upper right pixel is at (239,180), matching Scratch 2.0.
286+
* @param {number} xLeft - the left edge of the projection volume (-240)
287+
* @param {number} xRight - the right edge of the projection volume (240)
288+
* @param {number} yBottom - the bottom edge of the projection volume (-180)
289+
* @param {number} yTop - the top edge of the projection volume (180)
290+
* @returns {module:twgl/m4.Mat4} - a projection matrix containing [xLeft,xRight) and (yBottom,yTop]
291+
*/
292+
_makeOrthoProjection (xLeft, xRight, yBottom, yTop) {
293+
// swap yBottom & yTop to fit Scratch convention of +y=up
294+
return twgl.m4.ortho(xLeft, xRight, yBottom, yTop, -1, 1);
295+
}
296+
284297
/**
285298
* Create a new bitmap skin from a snapshot of the provided bitmap data.
286299
* @param {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} bitmapData - new contents for this skin.
@@ -518,7 +531,7 @@ class RenderWebGL extends EventEmitter {
518531
* Returns the position of the given drawableID in the draw list. This is
519532
* the absolute position irrespective of layer group.
520533
* @param {number} drawableID The drawable ID to find.
521-
* @return {number} The postion of the given drawable ID.
534+
* @return {number} The position of the given drawable ID.
522535
*/
523536
getDrawableOrder (drawableID) {
524537
return this._drawList.indexOf(drawableID);
@@ -532,7 +545,7 @@ class RenderWebGL extends EventEmitter {
532545
* "go to back": setDrawableOrder(id, 1); (assuming stage at 0).
533546
* "go to front": setDrawableOrder(id, Infinity);
534547
* @param {int} drawableID ID of Drawable to reorder.
535-
* @param {number} order New absolute order or relative order adjusment.
548+
* @param {number} order New absolute order or relative order adjustment.
536549
* @param {string=} group Name of layer group drawable belongs to.
537550
* Reordering will not take place if drawable cannot be found within the bounds
538551
* of the layer group.
@@ -703,7 +716,7 @@ class RenderWebGL extends EventEmitter {
703716

704717
/**
705718
* Check if a particular Drawable is touching a particular color.
706-
* Unlike touching drawable, if the "tester" is invisble, we will still test.
719+
* Unlike touching drawable, if the "tester" is invisible, we will still test.
707720
* @param {int} drawableID The ID of the Drawable to check.
708721
* @param {Array<int>} color3b Test if the Drawable is touching this color.
709722
* @param {Array<int>} [mask3b] Optionally mask the check to this part of Drawable.
@@ -728,23 +741,23 @@ class RenderWebGL extends EventEmitter {
728741
const color = __touchingColor;
729742
const hasMask = Boolean(mask3b);
730743

731-
for (let y = bounds.bottom; y <= bounds.top; y++) {
732-
if (bounds.width * (y - bounds.bottom) * (candidates.length + 1) >= __cpuTouchingColorPixelCount) {
733-
return this._isTouchingColorGpuFin(bounds, color3b, y - bounds.bottom);
744+
// Scratch Space - +y is top
745+
for (let y = 0; y < bounds.height; ++y) {
746+
if (bounds.width * y * (candidates.length + 1) >= __cpuTouchingColorPixelCount) {
747+
return this._isTouchingColorGpuFin(bounds, color3b, y);
734748
}
735-
// Scratch Space - +y is top
736-
for (let x = bounds.left; x <= bounds.right; x++) {
737-
point[1] = y;
738-
point[0] = x;
739-
if (
740-
// if we use a mask, check our sample color
741-
(hasMask ?
742-
maskMatches(Drawable.sampleColor4b(point, drawable, color), mask3b) :
743-
drawable.isTouching(point)) &&
744-
// and the target color is drawn at this pixel
745-
colorMatches(RenderWebGL.sampleColor3b(point, candidates, color), color3b, 0)
746-
) {
747-
return true;
749+
for (let x = 0; x < bounds.width; ++x) {
750+
point[0] = bounds.left + x; // bounds.left <= point[0] < bounds.right
751+
point[1] = bounds.top - y; // bounds.bottom < point[1] <= bounds.top ("flipped")
752+
// if we use a mask, check our sample color...
753+
if (hasMask ?
754+
maskMatches(Drawable.sampleColor4b(point, drawable, color), mask3b) :
755+
drawable.isTouching(point)) {
756+
RenderWebGL.sampleColor3b(point, candidates, color);
757+
// ...and the target color is drawn at this pixel
758+
if (colorMatches(color, color3b, 0)) {
759+
return true;
760+
}
748761
}
749762
}
750763
}
@@ -760,7 +773,7 @@ class RenderWebGL extends EventEmitter {
760773
// Limit size of viewport to the bounds around the target Drawable,
761774
// and create the projection matrix for the draw.
762775
gl.viewport(0, 0, bounds.width, bounds.height);
763-
const projection = twgl.m4.ortho(bounds.left, bounds.right, bounds.top, bounds.bottom, -1, 1);
776+
const projection = this._makeOrthoProjection(bounds.left, bounds.right, bounds.top, bounds.bottom);
764777

765778
let fillBackgroundColor = this._backgroundColor;
766779

@@ -843,7 +856,7 @@ class RenderWebGL extends EventEmitter {
843856
const candidates = this._candidatesTouching(drawableID,
844857
// even if passed an invisible drawable, we will NEVER touch it!
845858
candidateIDs.filter(id => this._allDrawables[id]._visible));
846-
// if we are invisble we don't touch anything.
859+
// if we are invisible we don't touch anything.
847860
if (candidates.length === 0 || !this._allDrawables[drawableID]._visible) {
848861
return false;
849862
}
@@ -876,7 +889,7 @@ class RenderWebGL extends EventEmitter {
876889

877890
/**
878891
* Convert a client based x/y position on the canvas to a Scratch 3 world space
879-
* Rectangle. This creates recangles with a radius to cover selecting multiple
892+
* Rectangle. This creates rectangles with a radius to cover selecting multiple
880893
* scratch pixels with touch / small render areas.
881894
*
882895
* @param {int} centerX The client x coordinate of the picking location.
@@ -993,7 +1006,7 @@ class RenderWebGL extends EventEmitter {
9931006
for (worldPos[0] = bounds.left; worldPos[0] <= bounds.right; worldPos[0]++) {
9941007

9951008
// Check candidates in the reverse order they would have been
996-
// drawn. This will determine what candiate's silhouette pixel
1009+
// drawn. This will determine what candidate's silhouette pixel
9971010
// would have been drawn at the point.
9981011
for (let d = candidateIDs.length - 1; d >= 0; d--) {
9991012
const id = candidateIDs[d];
@@ -1077,7 +1090,7 @@ class RenderWebGL extends EventEmitter {
10771090
// Limit size of viewport to the bounds around the target Drawable,
10781091
// and create the projection matrix for the draw.
10791092
gl.viewport(0, 0, bounds.width, bounds.height);
1080-
const projection = twgl.m4.ortho(bounds.left, bounds.right, bounds.top, bounds.bottom, -1, 1);
1093+
const projection = this._makeOrthoProjection(bounds.left, bounds.right, bounds.top, bounds.bottom);
10811094

10821095
gl.clearColor(0, 0, 0, 0);
10831096
gl.clear(gl.COLOR_BUFFER_BIT);
@@ -1152,7 +1165,7 @@ class RenderWebGL extends EventEmitter {
11521165
const pickY = bounds.top - scratchY;
11531166

11541167
gl.viewport(0, 0, bounds.width, bounds.height);
1155-
const projection = twgl.m4.ortho(bounds.left, bounds.right, bounds.top, bounds.bottom, -1, 1);
1168+
const projection = this._makeOrthoProjection(bounds.left, bounds.right, bounds.top, bounds.bottom);
11561169

11571170
gl.clearColor.apply(gl, this._backgroundColor);
11581171
gl.clear(gl.COLOR_BUFFER_BIT);
@@ -1395,7 +1408,7 @@ class RenderWebGL extends EventEmitter {
13951408

13961409
// Limit size of viewport to the bounds around the stamp Drawable and create the projection matrix for the draw.
13971410
gl.viewport(0, 0, bounds.width, bounds.height);
1398-
const projection = twgl.m4.ortho(bounds.left, bounds.right, bounds.top, bounds.bottom, -1, 1);
1411+
const projection = this._makeOrthoProjection(bounds.left, bounds.right, bounds.top, bounds.bottom);
13991412

14001413
gl.clearColor(0, 0, 0, 0);
14011414
gl.clear(gl.COLOR_BUFFER_BIT);
@@ -1484,7 +1497,7 @@ class RenderWebGL extends EventEmitter {
14841497
* can skip superfluous extra state calls when it is already in that
14851498
* region. Since one region may be entered from within another a exit
14861499
* handle can also be registered that is called when a new region is about
1487-
* to be entered to restore a common inbetween state.
1500+
* to be entered to restore a common in-between state.
14881501
*
14891502
* @param {any} regionId - id of the region to enter
14901503
* @param {function} enter - handle to call when first entering a region
@@ -1616,7 +1629,7 @@ class RenderWebGL extends EventEmitter {
16161629
*
16171630
* The determinant is useful in this case to know if AC is counter
16181631
* clockwise from AB. A positive value means the AC is counter
1619-
* clockwise from AC. A negative value menas AC is clockwise from AB.
1632+
* clockwise from AC. A negative value means AC is clockwise from AB.
16201633
*
16211634
* @param {Float32Array} A A 2d vector in space.
16221635
* @param {Float32Array} B A 2d vector in space.

src/Silhouette.js

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@
1111
let __SilhouetteUpdateCanvas;
1212

1313
/**
14-
* Internal helper function (in hopes that compiler can inline). Get a pixel
15-
* from silhouette data, or 0 if outside it's bounds.
14+
* Internal helper function (in hopes that compiler can inline). Get the alpha value for a texel in the silhouette
15+
* data, or 0 if outside it's bounds.
1616
* @private
17-
* @param {Silhouette} silhouette - has data width and height
18-
* @param {number} x - x
19-
* @param {number} y - y
17+
* @param {Silhouette} $0 - has data, width, and height
18+
* @param {number} x - X position in texels (0..width).
19+
* @param {number} y - Y position in texels (0..height).
2020
* @return {number} Alpha value for x/y position
2121
*/
2222
const getPoint = ({_width: width, _height: height, _data: data}, x, y) => {
23-
// 0 if outside bouds, otherwise read from data.
23+
// 0 if outside bounds, otherwise read from data.
2424
if (x >= width || y >= height || x < 0 || y < 0) {
2525
return 0;
2626
}
@@ -39,14 +39,14 @@ const __cornerWork = [
3939

4040
/**
4141
* Get the color from a given silhouette at an x/y local texture position.
42-
* @param {Silhouette} The silhouette to sample.
43-
* @param {number} x X position of texture (0-1).
44-
* @param {number} y Y position of texture (0-1).
45-
* @param {Uint8ClampedArray} dst A color 4b space.
46-
* @return {Uint8ClampedArray} The dst vector.
42+
* @param {Silhouette} $0 - The silhouette to sample.
43+
* @param {number} x - X position in texels (0..width).
44+
* @param {number} y - Y position in texels (0..height).
45+
* @param {Uint8ClampedArray} dst - A color 4b space.
46+
* @return {Uint8ClampedArray} - The dst vector.
4747
*/
4848
const getColor4b = ({_width: width, _height: height, _colorData: data}, x, y, dst) => {
49-
// 0 if outside bouds, otherwise read from data.
49+
// 0 if outside bounds, otherwise read from data.
5050
if (x >= width || y >= height || x < 0 || y < 0) {
5151
return dst.fill(0);
5252
}
@@ -102,7 +102,7 @@ class Silhouette {
102102

103103
this._data = new Uint8ClampedArray(imageData.data.length / 4);
104104
this._colorData = imageData.data;
105-
// delete our custom overriden "uninitalized" color functions
105+
// delete our custom overridden "uninitialized" color functions
106106
// let the prototype work for itself
107107
delete this.colorAtNearest;
108108
delete this.colorAtLinear;
@@ -120,12 +120,10 @@ class Silhouette {
120120
* @returns {Uint8ClampedArray} dst
121121
*/
122122
colorAtNearest (vec, dst) {
123-
return getColor4b(
124-
this,
125-
Math.floor(vec[0] * (this._width - 1)),
126-
Math.floor(vec[1] * (this._height - 1)),
127-
dst
128-
);
123+
const x = Math.round(vec[0] * this._width);
124+
const y = Math.round(vec[1] * this._height);
125+
const color = getColor4b(this, x, y, dst);
126+
return color;
129127
}
130128

131129
/**

0 commit comments

Comments
 (0)