Compare commits
21 Commits
master
...
revert-597
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
813792359f | ||
|
|
7349c1967c | ||
|
|
b873b6fec4 | ||
|
|
51c867fa6d | ||
|
|
ddccd0fa79 | ||
|
|
b35adaef88 | ||
|
|
3612461f9c | ||
|
|
7c710c0680 | ||
|
|
fe91133565 | ||
|
|
409df2a74d | ||
|
|
4b03eb29fd | ||
|
|
51bceb68e5 | ||
|
|
70c64f035c | ||
|
|
4b7558f2d2 | ||
|
|
84f878e553 | ||
|
|
0b47d87f2f | ||
|
|
5081a71fe9 | ||
|
|
0a214a989d | ||
|
|
3d02a6c769 | ||
|
|
60e7e568d5 | ||
|
|
b448aac1f3 |
@@ -53,7 +53,7 @@
|
|||||||
"minilog": "3.1.0",
|
"minilog": "3.1.0",
|
||||||
"raw-loader": "^0.5.1",
|
"raw-loader": "^0.5.1",
|
||||||
"scratch-storage": "^1.0.0",
|
"scratch-storage": "^1.0.0",
|
||||||
"scratch-svg-renderer": "0.2.0-prerelease.20200205003400",
|
"scratch-svg-renderer": "0.2.0-prerelease.20200507183648",
|
||||||
"twgl.js": "4.4.0"
|
"twgl.js": "4.4.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,16 +56,6 @@ class BitmapSkin extends Skin {
|
|||||||
return this._texture || super.getTexture();
|
return this._texture || super.getTexture();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the bounds of the drawable for determining its fenced position.
|
|
||||||
* @param {Array<number>} drawable - The Drawable instance this skin is using.
|
|
||||||
* @param {?Rectangle} result - Optional destination for bounds calculation.
|
|
||||||
* @return {!Rectangle} The drawable's bounds. For compatibility with Scratch 2, we always use getAABB for bitmaps.
|
|
||||||
*/
|
|
||||||
getFenceBounds (drawable, result) {
|
|
||||||
return drawable.getAABB(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the contents of this skin to a snapshot of the provided bitmap data.
|
* Set the contents of this skin to a snapshot of the provided bitmap data.
|
||||||
* @param {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} bitmapData - new contents for this skin.
|
* @param {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} bitmapData - new contents for this skin.
|
||||||
|
|||||||
@@ -1765,9 +1765,7 @@ class RenderWebGL extends EventEmitter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the convex hull points for a particular Drawable.
|
* Get the convex hull points for a particular Drawable.
|
||||||
* To do this, draw the Drawable unrotated, unscaled, and untranslated.
|
* To do this, calculate it based on the drawable's Silhouette.
|
||||||
* Read back the pixels and find all boundary points.
|
|
||||||
* Finally, apply a convex hull algorithm to simplify the set.
|
|
||||||
* @param {int} drawableID The Drawable IDs calculate convex hull for.
|
* @param {int} drawableID The Drawable IDs calculate convex hull for.
|
||||||
* @return {Array<Array<number>>} points Convex hull points, as [[x, y], ...]
|
* @return {Array<Array<number>>} points Convex hull points, as [[x, y], ...]
|
||||||
*/
|
*/
|
||||||
@@ -1784,110 +1782,120 @@ class RenderWebGL extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the determinant of two vectors, the vector from A to B and
|
* Return the determinant of two vectors, the vector from A to B and the vector from A to C.
|
||||||
* the vector from A to C.
|
|
||||||
*
|
*
|
||||||
* The determinant is useful in this case to know if AC is counter
|
* The determinant is useful in this case to know if AC is counter-clockwise from AB.
|
||||||
* clockwise from AB. A positive value means the AC is counter
|
* A positive value means that AC is counter-clockwise from AB. A negative value means AC is clockwise from AB.
|
||||||
* clockwise from AC. A negative value menas AC is clockwise from AB.
|
|
||||||
*
|
*
|
||||||
* @param {Float32Array} A A 2d vector in space.
|
* @param {Float32Array} A A 2d vector in space.
|
||||||
* @param {Float32Array} B A 2d vector in space.
|
* @param {Float32Array} B A 2d vector in space.
|
||||||
* @param {Float32Array} C A 2d vector in space.
|
* @param {Float32Array} C A 2d vector in space.
|
||||||
* @return {number} Greater than 0 if counter clockwise, less than if
|
* @return {number} Greater than 0 if counter clockwise, less than if clockwise, 0 if all points are on a line.
|
||||||
* clockwise, 0 if all points are on a line.
|
|
||||||
*/
|
*/
|
||||||
const CCW = function (A, B, C) {
|
const determinant = function (A, B, C) {
|
||||||
// AB = B - A
|
// AB = B - A
|
||||||
// AC = C - A
|
// AC = C - A
|
||||||
// det (AB BC) = AB0 * AC1 - AB1 * AC0
|
// det (AB BC) = AB0 * AC1 - AB1 * AC0
|
||||||
return (((B[0] - A[0]) * (C[1] - A[1])) - ((B[1] - A[1]) * (C[0] - A[0])));
|
return (((B[0] - A[0]) * (C[1] - A[1])) - ((B[1] - A[1]) * (C[0] - A[0])));
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://github.com/LLK/scratch-flash/blob/dcbeeb59d44c3be911545dfe54d
|
// This algorithm for calculating the convex hull somewhat resembles the monotone chain algorithm.
|
||||||
// 46a32404f8e69/src/scratch/ScratchCostume.as#L369-L413 Following
|
// The main difference is that instead of sorting the points by x-coordinate, and y-coordinate in case of ties,
|
||||||
// RasterHull creation, compare and store left and right values that
|
// it goes through them by y-coordinate in the outer loop and x-coordinate in the inner loop.
|
||||||
// maintain a convex shape until that data can be passed to `hull` for
|
// This gives us "left" and "right" hulls, whereas the monotone chain algorithm gives "top" and "bottom" hulls.
|
||||||
// further work.
|
// Adapted from https://github.com/LLK/scratch-flash/blob/dcbeeb59d44c3be911545dfe54d46a32404f8e69/src/scratch/ScratchCostume.as#L369-L413
|
||||||
const L = [];
|
|
||||||
const R = [];
|
const leftHull = [];
|
||||||
|
const rightHull = [];
|
||||||
|
|
||||||
|
// While convex hull algorithms usually push and pop values from the list of hull points,
|
||||||
|
// here, we keep indices for the "last" point in each array. Any points past these indices are ignored.
|
||||||
|
// This is functionally equivalent to pushing and popping from a "stack" of hull points.
|
||||||
|
let leftEndPointIndex = -1;
|
||||||
|
let rightEndPointIndex = -1;
|
||||||
|
|
||||||
const _pixelPos = twgl.v3.create();
|
const _pixelPos = twgl.v3.create();
|
||||||
const _effectPos = twgl.v3.create();
|
const _effectPos = twgl.v3.create();
|
||||||
let ll = -1;
|
|
||||||
let rr = -1;
|
let currentPoint;
|
||||||
let Q;
|
|
||||||
|
// *Not* Scratch Space-- +y is bottom
|
||||||
|
// Loop over all rows of pixels, starting at the top
|
||||||
for (let y = 0; y < height; y++) {
|
for (let y = 0; y < height; y++) {
|
||||||
_pixelPos[1] = y / height;
|
_pixelPos[1] = y / height;
|
||||||
// Scan from left to right, looking for a touchable spot in the
|
|
||||||
// skin.
|
// We start at the leftmost point, then go rightwards until we hit an opaque pixel
|
||||||
let x = 0;
|
let x = 0;
|
||||||
for (; x < width; x++) {
|
for (; x < width; x++) {
|
||||||
_pixelPos[0] = x / width;
|
_pixelPos[0] = x / width;
|
||||||
EffectTransform.transformPoint(drawable, _pixelPos, _effectPos);
|
EffectTransform.transformPoint(drawable, _pixelPos, _effectPos);
|
||||||
if (drawable.skin.isTouchingLinear(_effectPos)) {
|
if (drawable.skin.isTouchingLinear(_effectPos)) {
|
||||||
Q = [x, y];
|
currentPoint = [x, y];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If x is equal to the width there are no touchable points in the
|
|
||||||
// skin. Nothing we can add to L. And looping for R would find the
|
// If we managed to loop all the way through, there are no opaque pixels on this row. Go to the next one
|
||||||
// same thing.
|
|
||||||
if (x >= width) {
|
if (x >= width) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Decrement ll until Q is clockwise (CCW returns negative) from the
|
|
||||||
// last two points in L.
|
// Because leftEndPointIndex is initialized to -1, this is skipped for the first two rows.
|
||||||
while (ll > 0) {
|
// It runs only when there are enough points in the left hull to make at least one line.
|
||||||
if (CCW(L[ll - 1], L[ll], Q) < 0) {
|
// If appending the current point to the left hull makes a counter-clockwise turn,
|
||||||
|
// we want to append the current point. Otherwise, we decrement the index of the "last" hull point until the
|
||||||
|
// current point makes a counter-clockwise turn.
|
||||||
|
// This decrementing has the same effect as popping from the point list, but is hopefully faster.
|
||||||
|
while (leftEndPointIndex > 0) {
|
||||||
|
if (determinant(leftHull[leftEndPointIndex], leftHull[leftEndPointIndex - 1], currentPoint) > 0) {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
--ll;
|
// leftHull.pop();
|
||||||
|
--leftEndPointIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Increment ll and then set L[ll] to Q. If ll was -1 before this
|
|
||||||
// line, this will set L[0] to Q. If ll was 0 before this line, this
|
|
||||||
// will set L[1] to Q.
|
|
||||||
L[++ll] = Q;
|
|
||||||
|
|
||||||
// Scan from right to left, looking for a touchable spot in the
|
// This has the same effect as pushing to the point list.
|
||||||
// skin.
|
// This "list head pointer" coding style leaves excess points dangling at the end of the list,
|
||||||
|
// but that doesn't matter; we simply won't copy them over to the final hull.
|
||||||
|
|
||||||
|
// leftHull.push(currentPoint);
|
||||||
|
leftHull[++leftEndPointIndex] = currentPoint;
|
||||||
|
|
||||||
|
// Now we repeat the process for the right side, looking leftwards for a pixel.
|
||||||
for (x = width - 1; x >= 0; x--) {
|
for (x = width - 1; x >= 0; x--) {
|
||||||
_pixelPos[0] = x / width;
|
_pixelPos[0] = x / width;
|
||||||
EffectTransform.transformPoint(drawable, _pixelPos, _effectPos);
|
EffectTransform.transformPoint(drawable, _pixelPos, _effectPos);
|
||||||
if (drawable.skin.isTouchingLinear(_effectPos)) {
|
if (drawable.skin.isTouchingLinear(_effectPos)) {
|
||||||
Q = [x, y];
|
currentPoint = [x, y];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Decrement rr until Q is counter clockwise (CCW returns positive)
|
|
||||||
// from the last two points in L. L takes clockwise points and R
|
|
||||||
// takes counter clockwise points. if y was decremented instead of
|
|
||||||
// incremented R would take clockwise points. We are going in the
|
|
||||||
// right direction for L and the wrong direction for R, so we
|
|
||||||
// compare the opposite value for R from L.
|
|
||||||
while (rr > 0) {
|
|
||||||
if (CCW(R[rr - 1], R[rr], Q) > 0) {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
--rr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Increment rr and then set R[rr] to Q.
|
|
||||||
R[++rr] = Q;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Known boundary points on left/right edges of pixels.
|
// Because we're coming at this from the right, it goes clockwise this time.
|
||||||
const boundaryPoints = L;
|
while (rightEndPointIndex > 0) {
|
||||||
// Truncate boundaryPoints to the index of the last added Q to L. L may
|
if (determinant(rightHull[rightEndPointIndex], rightHull[rightEndPointIndex - 1], currentPoint) < 0) {
|
||||||
// have more entries than the index for the last Q.
|
break;
|
||||||
boundaryPoints.length = ll + 1;
|
} else {
|
||||||
// Add points in R to boundaryPoints in reverse so all points in
|
--rightEndPointIndex;
|
||||||
// boundaryPoints are clockwise from each other.
|
|
||||||
for (let j = rr; j >= 0; --j) {
|
|
||||||
boundaryPoints.push(R[j]);
|
|
||||||
}
|
}
|
||||||
// Simplify boundary points using convex hull.
|
}
|
||||||
return hull(boundaryPoints, Infinity);
|
|
||||||
|
rightHull[++rightEndPointIndex] = currentPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start off "hullPoints" with the left hull points.
|
||||||
|
const hullPoints = leftHull;
|
||||||
|
// This is where we get rid of those dangling extra points.
|
||||||
|
hullPoints.length = leftEndPointIndex + 1;
|
||||||
|
// Add points from the right side in reverse order so all points are ordered clockwise.
|
||||||
|
for (let j = rightEndPointIndex; j >= 0; --j) {
|
||||||
|
hullPoints.push(rightHull[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simplify boundary points using hull.js.
|
||||||
|
// TODO: Remove this; this algorithm already generates convex hulls.
|
||||||
|
return hull(hullPoints, Infinity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -143,10 +143,10 @@ class Skin extends EventEmitter {
|
|||||||
* Get the bounds of the drawable for determining its fenced position.
|
* Get the bounds of the drawable for determining its fenced position.
|
||||||
* @param {Array<number>} drawable - The Drawable instance this skin is using.
|
* @param {Array<number>} drawable - The Drawable instance this skin is using.
|
||||||
* @param {?Rectangle} result - Optional destination for bounds calculation.
|
* @param {?Rectangle} result - Optional destination for bounds calculation.
|
||||||
* @return {!Rectangle} The drawable's bounds.
|
* @return {!Rectangle} The drawable's bounds. For compatibility with Scratch 2, we always use getAABB.
|
||||||
*/
|
*/
|
||||||
getFenceBounds (drawable, result) {
|
getFenceBounds (drawable, result) {
|
||||||
return drawable.getFastBounds(result);
|
return drawable.getAABB(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -220,14 +220,28 @@ void main()
|
|||||||
// Adapted from Inigo Quilez' 2D distance function cheat sheet
|
// Adapted from Inigo Quilez' 2D distance function cheat sheet
|
||||||
// https://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm
|
// https://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm
|
||||||
|
|
||||||
|
// On (some?) devices with 16-bit float precision, sufficiently long lines will overflow the float's range.
|
||||||
|
// Avoid this by scaling these problematic values down to fit within (-1, 1) then scaling them back up later.
|
||||||
|
// TODO: Is this a problem on all drivers with 16-bit mediump floats, or just Mali?
|
||||||
|
vec2 pointDiff = abs(u_penPoints.zw - u_penPoints.xy);
|
||||||
|
float FLOAT_SCALING_INVERSE = max(1.0, max(pointDiff.x, pointDiff.y));
|
||||||
|
float FLOAT_SCALING = 1.0 / FLOAT_SCALING_INVERSE;
|
||||||
|
|
||||||
// The xy component of u_penPoints is the first point; the zw is the second point.
|
// The xy component of u_penPoints is the first point; the zw is the second point.
|
||||||
// This is done to minimize the number of gl.uniform calls, which can add up.
|
// This is done to minimize the number of gl.uniform calls, which can add up.
|
||||||
vec2 pa = v_texCoord - u_penPoints.xy, ba = u_penPoints.zw - u_penPoints.xy;
|
// vec2 pa = v_texCoord - u_penPoints.xy, ba = u_penPoints.zw - u_penPoints.xy;
|
||||||
|
vec2 pa = (v_texCoord - u_penPoints.xy) * FLOAT_SCALING, ba = (u_penPoints.zw - u_penPoints.xy) * FLOAT_SCALING;
|
||||||
|
|
||||||
|
// Avoid division by zero
|
||||||
|
float baDot = dot(ba, ba);
|
||||||
|
// the dot product of a vector and itself is always positive
|
||||||
|
baDot = max(baDot, epsilon);
|
||||||
|
|
||||||
// Magnitude of vector projection of this fragment onto the line (both relative to the line's start point).
|
// Magnitude of vector projection of this fragment onto the line (both relative to the line's start point).
|
||||||
// This results in a "linear gradient" which goes from 0.0 at the start point to 1.0 at the end point.
|
// This results in a "linear gradient" which goes from 0.0 at the start point to 1.0 at the end point.
|
||||||
float projMagnitude = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
|
float projMagnitude = clamp(dot(pa, ba) / baDot, 0.0, 1.0);
|
||||||
|
|
||||||
float lineDistance = length(pa - (ba * projMagnitude));
|
float lineDistance = length(pa - (ba * projMagnitude)) * FLOAT_SCALING_INVERSE;
|
||||||
|
|
||||||
// The distance to the line allows us to create lines of any thickness.
|
// The distance to the line allows us to create lines of any thickness.
|
||||||
// Instead of checking whether this fragment's distance < the line thickness,
|
// Instead of checking whether this fragment's distance < the line thickness,
|
||||||
|
|||||||
@@ -40,7 +40,13 @@ void main() {
|
|||||||
position -= expandedRadius;
|
position -= expandedRadius;
|
||||||
|
|
||||||
// Rotate quad to line angle
|
// Rotate quad to line angle
|
||||||
vec2 normalized = (u_penPoints.zw - u_penPoints.xy + epsilon) / (lineLength + epsilon);
|
vec2 pointDiff = u_penPoints.zw - u_penPoints.xy;
|
||||||
|
// Ensure line has a nonzero length so it's rendered properly
|
||||||
|
// As long as either component is nonzero, the line length will be nonzero
|
||||||
|
pointDiff.x = abs(pointDiff.x) < epsilon ? epsilon : pointDiff.x;
|
||||||
|
// The `normalized` vector holds rotational values equivalent to sine/cosine
|
||||||
|
// We're applying the standard rotation matrix formula to the position to rotate the quad to the line angle
|
||||||
|
vec2 normalized = pointDiff / max(lineLength, epsilon);
|
||||||
position = mat2(normalized.x, normalized.y, -normalized.y, normalized.x) * position;
|
position = mat2(normalized.x, normalized.y, -normalized.y, normalized.x) * position;
|
||||||
// Translate quad
|
// Translate quad
|
||||||
position += u_penPoints.xy;
|
position += u_penPoints.xy;
|
||||||
|
|||||||
BIN
test/integration/scratch-tests/fencing-bounds.sb3
Normal file
BIN
test/integration/scratch-tests/fencing-bounds.sb3
Normal file
Binary file not shown.
Reference in New Issue
Block a user