Compare commits

...

38 Commits

Author SHA1 Message Date
dependabot-preview[bot]
da21131f49 Merge pull request #804 from LLK/dependabot/npm_and_yarn/scratch-svg-renderer-0.2.0-prerelease.20210225205629 2021-02-25 21:11:14 +00:00
dependabot-preview[bot]
8873170d2a Bump scratch-svg-renderer
Bumps [scratch-svg-renderer](https://github.com/LLK/scratch-svg-renderer) from 0.2.0-prerelease.20210225202240 to 0.2.0-prerelease.20210225205629.
- [Release notes](https://github.com/LLK/scratch-svg-renderer/releases)
- [Commits](https://github.com/LLK/scratch-svg-renderer/compare/0.2.0-prerelease.20210225202240...0.2.0-prerelease.20210225205629)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-25 21:00:54 +00:00
dependabot-preview[bot]
6a486d7cb4 Merge pull request #803 from LLK/dependabot/npm_and_yarn/scratch-svg-renderer-0.2.0-prerelease.20210225202240 2021-02-25 20:38:15 +00:00
dependabot-preview[bot]
3105dd5fbe Bump scratch-svg-renderer
Bumps [scratch-svg-renderer](https://github.com/LLK/scratch-svg-renderer) from 0.2.0-prerelease.20210219193204 to 0.2.0-prerelease.20210225202240.
- [Release notes](https://github.com/LLK/scratch-svg-renderer/releases)
- [Commits](https://github.com/LLK/scratch-svg-renderer/commits/0.2.0-prerelease.20210225202240)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-25 20:27:26 +00:00
dependabot-preview[bot]
20e5aed5a5 Merge pull request #787 from LLK/dependabot/npm_and_yarn/scratch-svg-renderer-0.2.0-prerelease.20210219193204 2021-02-19 20:50:26 +00:00
dependabot-preview[bot]
821868947f Bump scratch-svg-renderer
Bumps [scratch-svg-renderer](https://github.com/LLK/scratch-svg-renderer) from 0.2.0-prerelease.20210114214521 to 0.2.0-prerelease.20210219193204.
- [Release notes](https://github.com/LLK/scratch-svg-renderer/releases)
- [Commits](https://github.com/LLK/scratch-svg-renderer/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-19 19:37:03 +00:00
dependabot-preview[bot]
f1d8ba34d9 Merge pull request #747 from LLK/dependabot/npm_and_yarn/scratch-svg-renderer-0.2.0-prerelease.20210114214521 2021-01-15 00:07:07 +00:00
dependabot-preview[bot]
c795bc53d6 Merge pull request #739 from LLK/dependabot/npm_and_yarn/scratch-vm-0.2.0-prerelease.20201125065300 2021-01-15 00:06:47 +00:00
dependabot-preview[bot]
30bee7febd Bump scratch-svg-renderer
Bumps [scratch-svg-renderer](https://github.com/LLK/scratch-svg-renderer) from 0.2.0-prerelease.20201019174008 to 0.2.0-prerelease.20210114214521.
- [Release notes](https://github.com/LLK/scratch-svg-renderer/releases)
- [Commits](https://github.com/LLK/scratch-svg-renderer/compare/0.2.0-prerelease.20201019174008...0.2.0-prerelease.20210114214521)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-01-14 22:00:33 +00:00
dependabot-preview[bot]
8c06ddaa3d Bump scratch-vm
Bumps [scratch-vm](https://github.com/LLK/scratch-vm) from 0.2.0-prerelease.20200924195620 to 0.2.0-prerelease.20201125065300.
- [Release notes](https://github.com/LLK/scratch-vm/releases)
- [Commits](https://github.com/LLK/scratch-vm/commits/0.2.0-prerelease.20201125065300)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-25 09:06:25 +00:00
Chris Willis-Ford
c19971b219 Merge pull request #566 from adroitwhiz/usenearest-skin
Move useNearest from Drawable to Skin
2020-11-13 13:03:35 -08:00
Sarah Otts
50b4ccd42a Merge pull request #723 from seotts/update-eslint
Update eslint, babel-eslint, and eslint-scratch-config and fix resulting errors
2020-11-10 14:16:57 -05:00
Sarah Otts
00710f3347 Fix spacing in 'You clicked on' console log 2020-11-10 09:28:26 -05:00
seotts
1e5ce914a2 replace .apply() with spread 2020-11-09 17:05:56 -05:00
seotts
c855e8d143 use eslint-scratch-config version 6.0.0 2020-11-09 17:05:56 -05:00
seotts
aa704383b4 Remove built-in global variables in comments 2020-11-09 17:05:56 -05:00
seotts
261dfb7536 update eslint, babel-eslint, and eslint-config-scratch 2020-11-09 17:05:56 -05:00
seotts
65fca3e3c7 fix lint errors in playground 2020-11-09 17:05:56 -05:00
seotts
ad1831ebfa Don’t call hasOwnProperty directly on object 2020-11-09 17:05:56 -05:00
dependabot-preview[bot]
527733c249 Merge pull request #699 from LLK/dependabot/npm_and_yarn/scratch-vm-0.2.0-prerelease.20200924195620 2020-11-09 18:51:11 +00:00
DD Liu
96f36ff50f Merge pull request #624 from adroitwhiz/update-silhouette-differently
Move `updateCPURenderAttributes` calls to where they're actually needed
2020-10-29 15:49:33 -04:00
DD Liu
98caee67c3 Merge pull request #557 from adroitwhiz/penskin-cleanup
Clean up PenSkin code
2020-10-29 15:25:58 -04:00
picklesrus
2bc1528dac Merge pull request #712 from LLK/dependabot/npm_and_yarn/scratch-svg-renderer-0.2.0-prerelease.20201019174008
Bump scratch-svg-renderer from 0.2.0-prerelease.20201016121710 to 0.2.0-prerelease.20201019174008
2020-10-20 06:37:09 -04:00
dependabot-preview[bot]
59d66f562c Bump scratch-svg-renderer
Bumps [scratch-svg-renderer](https://github.com/LLK/scratch-svg-renderer) from 0.2.0-prerelease.20201016121710 to 0.2.0-prerelease.20201019174008.
- [Release notes](https://github.com/LLK/scratch-svg-renderer/releases)
- [Commits](https://github.com/LLK/scratch-svg-renderer/commits/0.2.0-prerelease.20201019174008)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-20 02:34:34 +00:00
picklesrus
ec053748cd Merge pull request #710 from LLK/picklesrus-patch-1-1
Bump scratch-svg-renderer
2020-10-16 13:36:56 -04:00
picklesrus
bf2820aeda Bump scratch-svg-renderer 2020-10-16 13:05:13 -04:00
Ray Schamp
0120a77b8e Merge pull request #703 from LLK/hotfix/enable-hotfixing
add hotfix capability to .travis.yml
2020-10-16 08:09:56 -04:00
picklesrus
e8d6f957fa Merge pull request #709 from LLK/dependabot/npm_and_yarn/scratch-svg-renderer-0.2.0-prerelease.20201015194358
Bump scratch-svg-renderer from 0.2.0-prerelease.20200610220938 to 0.2.0-prerelease.20201015194358
2020-10-16 06:36:49 -04:00
dependabot-preview[bot]
c0f1afe104 Bump scratch-svg-renderer
Bumps [scratch-svg-renderer](https://github.com/LLK/scratch-svg-renderer) from 0.2.0-prerelease.20200610220938 to 0.2.0-prerelease.20201015194358.
- [Release notes](https://github.com/LLK/scratch-svg-renderer/releases)
- [Commits](https://github.com/LLK/scratch-svg-renderer/commits/0.2.0-prerelease.20201015194358)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-15 21:44:55 +00:00
Chris Willis-Ford
4ebea93adf Merge pull request #445 from adroitwhiz/optimize-transformed-hull
Cache and don't re-create transformed hull points
2020-10-08 12:46:53 -07:00
adroitwhiz
205a8c9131 Use draw region for accessing pen framebuffer 2020-09-30 17:20:39 -04:00
adroitwhiz
8ac7ed20da Clean up PenSkin code 2020-09-30 17:09:49 -04:00
adroitwhiz
26d2677409 Also pass in drawable to useNearest
This makes the code messier but I'm not sure what else to do since the
texture filtering method to be used depends on the drawable's properties
(e.g. transform, enabled effects). We still need to pass in the scale
separately because in the main rendering path, we multiply it by the
screen-space scale factor.
2020-09-30 17:05:17 -04:00
adroitwhiz
f4d5e52a8f Clarify SVGSkin.useNearest comments 2020-09-30 17:04:08 -04:00
adroitwhiz
97605f9e55 Move useNearest to skin classes 2020-09-30 17:04:06 -04:00
dependabot-preview[bot]
9d02a016e1 [Security] Bump scratch-vm
Bumps [scratch-vm](https://github.com/LLK/scratch-vm) from 0.2.0-prerelease.20200622143012 to 0.2.0-prerelease.20200924195620. **This update includes a security fix.**
- [Release notes](https://github.com/LLK/scratch-vm/releases)
- [Commits](https://github.com/LLK/scratch-vm/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-09-25 09:51:21 +00:00
adroitwhiz
ade45c0363 Move CPU render updates only where they're needed 2020-07-24 13:19:09 -04:00
adroitwhiz
75a4792f93 Cache and don't re-create transformed hull points 2020-06-25 16:33:22 -04:00
13 changed files with 163 additions and 385 deletions

View File

@@ -25,19 +25,19 @@
},
"devDependencies": {
"babel-core": "^6.23.1",
"babel-eslint": "^8.2.1",
"babel-eslint": "^10.1.0",
"babel-loader": "^7.1.4",
"babel-polyfill": "^6.22.0",
"babel-preset-env": "^1.6.1",
"copy-webpack-plugin": "^4.5.1",
"docdash": "^0.4.0",
"eslint": "^4.6.1",
"eslint-config-scratch": "^5.0.0",
"eslint": "^7.13.0",
"eslint-config-scratch": "^6.0.0",
"gh-pages": "^1.0.0",
"jsdoc": "^3.6.0",
"json": "^9.0.4",
"playwright-chromium": "^1.0.1",
"scratch-vm": "0.2.0-prerelease.20200622143012",
"scratch-vm": "0.2.0-prerelease.20201125065300",
"tap": "^11.0.0",
"travis-after-all": "^1.4.4",
"uglifyjs-webpack-plugin": "^1.2.5",
@@ -53,7 +53,7 @@
"minilog": "3.1.0",
"raw-loader": "^0.5.1",
"scratch-storage": "^1.0.0",
"scratch-svg-renderer": "0.2.0-prerelease.20200610220938",
"scratch-svg-renderer": "0.2.0-prerelease.20210225205629",
"twgl.js": "4.4.0"
}
}

View File

@@ -33,13 +33,6 @@ class BitmapSkin extends Skin {
super.dispose();
}
/**
* @returns {boolean} true for a raster-style skin (like a BitmapSkin), false for vector-style (like SVGSkin).
*/
get isRaster () {
return true;
}
/**
* @return {Array<number>} the "native" size, in texels, of this skin.
*/

View File

@@ -117,6 +117,12 @@ class Drawable {
this._convexHullPoints = null;
this._convexHullDirty = true;
// The precise bounding box will be from the transformed convex hull points,
// so initialize the array of transformed hull points in setConvexHullPoints.
// Initializing it once per convex hull recalculation avoids unnecessary creation of twgl.v3 objects.
this._transformedHullPoints = null;
this._transformedHullDirty = true;
this._skinWasAltered = this._skinWasAltered.bind(this);
this.isTouching = this._isTouchingNever;
@@ -137,6 +143,7 @@ class Drawable {
setTransformDirty () {
this._transformDirty = true;
this._inverseTransformDirty = true;
this._transformedHullDirty = true;
}
/**
@@ -457,6 +464,14 @@ class Drawable {
setConvexHullPoints (points) {
this._convexHullPoints = points;
this._convexHullDirty = false;
// Re-create the "transformed hull points" array.
// We only do this when the hull points change to avoid unnecessary allocations and GC.
this._transformedHullPoints = [];
for (let i = 0; i < points.length; i++) {
this._transformedHullPoints.push(twgl.v3.create());
}
this._transformedHullDirty = true;
}
/**
@@ -489,40 +504,6 @@ class Drawable {
return this.skin.isTouchingLinear(getLocalPosition(this, vec));
}
/**
* Should the drawable use NEAREST NEIGHBOR or LINEAR INTERPOLATION mode
* @param {?Array<Number>} scale Optionally, the screen-space scale of the drawable.
* @return {boolean} True if the drawable should use nearest-neighbor interpolation.
*/
useNearest (scale = this.scale) {
// Raster skins (bitmaps) should always prefer nearest neighbor
if (this.skin.isRaster) {
return true;
}
// If the effect bits for mosaic, pixelate, whirl, or fisheye are set, use linear
if ((this.enabledEffects & (
ShaderManager.EFFECT_INFO.fisheye.mask |
ShaderManager.EFFECT_INFO.whirl.mask |
ShaderManager.EFFECT_INFO.pixelate.mask |
ShaderManager.EFFECT_INFO.mosaic.mask
)) !== 0) {
return false;
}
// We can't use nearest neighbor unless we are a multiple of 90 rotation
if (this._direction % 90 !== 0) {
return false;
}
// If the scale of the skin is very close to 100 (0.99999 variance is okay I guess)
if (Math.abs(scale[0]) > 99 && Math.abs(scale[0]) < 101 &&
Math.abs(scale[1]) > 99 && Math.abs(scale[1]) < 101) {
return true;
}
return false;
}
/**
* Get the precise bounds for a Drawable.
* This function applies the transform matrix to the known convex hull,
@@ -611,23 +592,27 @@ class Drawable {
* @private
*/
_getTransformedHullPoints () {
if (!this._transformedHullDirty) {
return this._transformedHullPoints;
}
const projection = twgl.m4.ortho(-1, 1, -1, 1, -1, 1);
const skinSize = this.skin.size;
const halfXPixel = 1 / skinSize[0] / 2;
const halfYPixel = 1 / skinSize[1] / 2;
const tm = twgl.m4.multiply(this._uniforms.u_modelMatrix, projection);
const transformedHullPoints = [];
for (let i = 0; i < this._convexHullPoints.length; i++) {
const point = this._convexHullPoints[i];
const glPoint = twgl.v3.create(
0.5 + (-point[0] / skinSize[0]) - halfXPixel,
(point[1] / skinSize[1]) - 0.5 + halfYPixel,
0
);
twgl.m4.transformPoint(tm, glPoint, glPoint);
transformedHullPoints.push(glPoint);
const dstPoint = this._transformedHullPoints[i];
dstPoint[0] = 0.5 + (-point[0] / skinSize[0]) - halfXPixel;
dstPoint[1] = (point[1] / skinSize[1]) - 0.5 + halfYPixel;
twgl.m4.transformPoint(tm, dstPoint, dstPoint);
}
return transformedHullPoints;
this._transformedHullDirty = false;
return this._transformedHullPoints;
}
/**
@@ -660,7 +645,7 @@ class Drawable {
if (this.skin) {
this.skin.updateSilhouette(this._scale);
if (this.useNearest()) {
if (this.skin.useNearest(this._scale, this)) {
this.isTouching = this._isTouchingNearest;
} else {
this.isTouching = this._isTouchingLinear;
@@ -734,10 +719,10 @@ class Drawable {
dst[3] = 0;
return dst;
}
const textColor =
// commenting out to only use nearest for now
// drawable.useNearest() ?
// drawable.skin.useNearest(drawable._scale, drawable) ?
drawable.skin._silhouette.colorAtNearest(localPosition, dst);
// : drawable.skin._silhouette.colorAtLinear(localPosition, dst);

View File

@@ -3,7 +3,6 @@ const twgl = require('twgl.js');
const RenderConstants = require('./RenderConstants');
const Skin = require('./Skin');
const Rectangle = require('./Rectangle');
const ShaderManager = require('./ShaderManager');
/**
@@ -31,44 +30,6 @@ const DefaultPenAttributes = {
*/
const __premultipliedColor = [0, 0, 0, 0];
/**
* Reused memory location for projection matrices.
* @type {FloatArray}
*/
const __projectionMatrix = twgl.m4.identity();
/**
* Reused memory location for translation matrix for building a model matrix.
* @type {FloatArray}
*/
const __modelTranslationMatrix = twgl.m4.identity();
/**
* Reused memory location for scaling matrix for building a model matrix.
* @type {FloatArray}
*/
const __modelScalingMatrix = twgl.m4.identity();
/**
* Reused memory location for a model matrix.
* @type {FloatArray}
*/
const __modelMatrix = twgl.m4.identity();
/**
* Reused memory location for a vector to create a translation matrix from.
* @type {FloatArray}
*/
const __modelTranslationVector = twgl.v3.create();
/**
* Reused memory location for a vector to create a scaling matrix from.
* @type {FloatArray}
*/
const __modelScalingVector = twgl.v3.create();
class PenSkin extends Skin {
/**
* Create a Skin which implements a Scratch pen layer.
@@ -86,21 +47,12 @@ class PenSkin extends Skin {
*/
this._renderer = renderer;
/** @type {HTMLCanvasElement} */
this._canvas = document.createElement('canvas');
/** @type {WebGLTexture} */
this._exportTexture = null;
/** @type {Array<number>} */
this._size = null;
/** @type {WebGLFramebuffer} */
this._framebuffer = null;
/** @type {WebGLFramebuffer} */
this._silhouetteBuffer = null;
/** @type {boolean} */
this._canvasDirty = false;
/** @type {boolean} */
this._silhouetteDirty = false;
@@ -117,23 +69,30 @@ class PenSkin extends Skin {
};
/** @type {object} */
this._toBufferDrawRegionId = {
enter: () => this._enterDrawToBuffer(),
exit: () => this._exitDrawToBuffer()
this._usePenBufferDrawRegionId = {
enter: () => this._enterUsePenBuffer(),
exit: () => this._exitUsePenBuffer()
};
/** @type {twgl.BufferInfo} */
this._lineBufferInfo = null;
this._lineBufferInfo = twgl.createBufferInfoFromArrays(this._renderer.gl, {
a_position: {
numComponents: 2,
data: [
1, 0,
0, 0,
1, 1,
1, 1,
0, 0,
0, 1
]
}
});
const NO_EFFECTS = 0;
/** @type {twgl.ProgramInfo} */
this._stampShader = this._renderer._shaderManager.getShader(ShaderManager.DRAW_MODE.default, NO_EFFECTS);
/** @type {twgl.ProgramInfo} */
this._lineShader = this._renderer._shaderManager.getShader(ShaderManager.DRAW_MODE.line, NO_EFFECTS);
this._createLineGeometry();
this.onNativeSizeChanged = this.onNativeSizeChanged.bind(this);
this._renderer.on(RenderConstants.Events.NativeSizeChanged, this.onNativeSizeChanged);
@@ -146,53 +105,43 @@ class PenSkin extends Skin {
dispose () {
this._renderer.removeListener(RenderConstants.Events.NativeSizeChanged, this.onNativeSizeChanged);
this._renderer.gl.deleteTexture(this._texture);
this._renderer.gl.deleteTexture(this._exportTexture);
this._texture = null;
super.dispose();
}
/**
* @returns {boolean} true for a raster-style skin (like a BitmapSkin), false for vector-style (like SVGSkin).
*/
get isRaster () {
return true;
}
/**
* @return {Array<number>} the "native" size, in texels, of this skin. [width, height]
*/
get size () {
return [this._canvas.width, this._canvas.height];
return this._size;
}
useNearest (scale) {
// Use nearest-neighbor interpolation when scaling up the pen skin-- this matches Scratch 2.0.
// When scaling it down, use linear interpolation to avoid giving pen lines a "dashed" appearance.
return Math.max(scale[0], scale[1]) >= 100;
}
/**
* @param {Array<number>} scale The X and Y scaling factors to be used, as percentages of this skin's "native" size.
* @return {WebGLTexture} The GL texture representation of this skin when drawing at the given size.
* @param {int} pixelsWide - The width that the skin will be rendered at, in GPU pixels.
* @param {int} pixelsTall - The height that the skin will be rendered at, in GPU pixels.
*/
// eslint-disable-next-line no-unused-vars
getTexture (pixelsWide, pixelsTall) {
if (this._canvasDirty) {
this._drawToBuffer();
}
return this._exportTexture;
getTexture (scale) {
return this._texture;
}
/**
* Clear the pen layer.
*/
clear () {
const gl = this._renderer.gl;
twgl.bindFramebufferInfo(gl, this._framebuffer);
this._renderer.enterDrawRegion(this._usePenBufferDrawRegionId);
/* Reset framebuffer to transparent black */
const gl = this._renderer.gl;
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
const ctx = this._canvas.getContext('2d');
ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
this._silhouetteDirty = true;
}
@@ -203,7 +152,6 @@ class PenSkin extends Skin {
* @param {number} y - the Y coordinate of the point to draw.
*/
drawPoint (penAttributes, x, y) {
// Canvas renders a zero-length line as two end-caps back-to-back, which is what we want.
this.drawLine(penAttributes, x, y, x, y);
}
@@ -230,48 +178,23 @@ class PenSkin extends Skin {
this._silhouetteDirty = true;
}
/**
* Create 2D geometry for drawing lines to a framebuffer.
*/
_createLineGeometry () {
const quads = {
a_position: {
numComponents: 2,
data: [
1, 0,
0, 0,
1, 1,
1, 1,
0, 0,
0, 1
]
}
};
this._lineBufferInfo = twgl.createBufferInfoFromArrays(this._renderer.gl, quads);
}
/**
* Prepare to draw lines in the _lineOnBufferDrawRegionId region.
*/
_enterDrawLineOnBuffer () {
const gl = this._renderer.gl;
const bounds = this._bounds;
const currentShader = this._lineShader;
const projection = twgl.m4.ortho(0, bounds.width, 0, bounds.height, -1, 1, __projectionMatrix);
twgl.bindFramebufferInfo(gl, this._framebuffer);
gl.viewport(0, 0, bounds.width, bounds.height);
gl.viewport(0, 0, this._size[0], this._size[1]);
const currentShader = this._lineShader;
gl.useProgram(currentShader.program);
twgl.setBuffersAndAttributes(gl, currentShader, this._lineBufferInfo);
const uniforms = {
u_skin: this._texture,
u_projectionMatrix: projection
u_stageSize: this._size
};
twgl.setUniforms(currentShader, uniforms);
@@ -286,6 +209,20 @@ class PenSkin extends Skin {
twgl.bindFramebufferInfo(gl, null);
}
/**
* Prepare to do things with this PenSkin's framebuffer
*/
_enterUsePenBuffer () {
twgl.bindFramebufferInfo(this._renderer.gl, this._framebuffer);
}
/**
* Return to a base state
*/
_exitUsePenBuffer () {
twgl.bindFramebufferInfo(this._renderer.gl, null);
}
/**
* Draw a line on the framebuffer.
* Note that the point coordinates are in the following coordinate space:
@@ -323,8 +260,7 @@ class PenSkin extends Skin {
u_lineColor: __premultipliedColor,
u_lineThickness: penAttributes.diameter || DefaultPenAttributes.diameter,
u_lineLength: lineLength,
u_penPoints: [x0, -y0, lineDiffX, -lineDiffY],
u_stageSize: this.size
u_penPoints: [x0, -y0, lineDiffX, -lineDiffY]
};
twgl.setUniforms(currentShader, uniforms);
@@ -334,136 +270,6 @@ class PenSkin extends Skin {
this._silhouetteDirty = true;
}
/**
* Stamp an image onto the pen layer.
* @param {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} stampElement - the element to use as the stamp.
* @param {number} x - the X coordinate of the stamp to draw.
* @param {number} y - the Y coordinate of the stamp to draw.
*/
drawStamp (stampElement, x, y) {
const ctx = this._canvas.getContext('2d');
ctx.drawImage(stampElement, this._rotationCenter[0] + x, this._rotationCenter[1] - y);
this._canvasDirty = true;
this._silhouetteDirty = true;
}
/**
* Enter a draw region to draw a rectangle.
*
* Multiple calls with the same regionId skip the callback reducing the
* amount of GL state changes.
* @param {twgl.ProgramInfo} currentShader - program info to draw rectangle
* with
* @param {Rectangle} bounds - viewport bounds to draw in
* region
*/
_drawRectangleRegionEnter (currentShader, bounds) {
const gl = this._renderer.gl;
gl.viewport(0, 0, bounds.width, bounds.height);
gl.useProgram(currentShader.program);
twgl.setBuffersAndAttributes(gl, currentShader, this._renderer._bufferInfo);
}
/**
* Draw a rectangle.
* @param {twgl.ProgramInfo} currentShader - program info to draw rectangle
* with
* @param {WebGLTexture} texture - texture to draw
* @param {Rectangle} bounds - bounded area to draw in
* @param {number} x - centered at x
* @param {number} y - centered at y
*/
_drawRectangle (currentShader, texture, bounds, x = -this._canvas.width / 2, y = this._canvas.height / 2) {
const gl = this._renderer.gl;
const projection = twgl.m4.ortho(
bounds.left, bounds.right, bounds.top, bounds.bottom, -1, 1,
__projectionMatrix
);
const uniforms = {
u_skin: texture,
u_projectionMatrix: projection,
u_modelMatrix: twgl.m4.multiply(
twgl.m4.translation(twgl.v3.create(
-x - (bounds.width / 2),
-y + (bounds.height / 2),
0
), __modelTranslationMatrix),
twgl.m4.scaling(twgl.v3.create(
bounds.width,
bounds.height,
0
), __modelScalingMatrix),
__modelMatrix
)
};
twgl.setTextureParameters(gl, texture, {minMag: gl.NEAREST});
twgl.setUniforms(currentShader, uniforms);
twgl.drawBufferInfo(gl, this._renderer._bufferInfo, gl.TRIANGLES);
}
/**
* Prepare to draw a rectangle in the _toBufferDrawRegionId region.
*/
_enterDrawToBuffer () {
const gl = this._renderer.gl;
twgl.bindFramebufferInfo(gl, this._framebuffer);
this._drawRectangleRegionEnter(this._stampShader, this._bounds);
}
/**
* Return to a base state from _toBufferDrawRegionId.
*/
_exitDrawToBuffer () {
const gl = this._renderer.gl;
twgl.bindFramebufferInfo(gl, null);
}
/**
* Draw the input texture to the framebuffer.
* @param {WebGLTexture} texture - input texture to draw
* @param {number} x - texture centered at x
* @param {number} y - texture centered at y
*/
_drawToBuffer (texture = this._texture, x = -this._canvas.width / 2, y = this._canvas.height / 2) {
if (texture !== this._texture && this._canvasDirty) {
this._drawToBuffer();
}
const gl = this._renderer.gl;
// If the input texture is the one that represents the pen's canvas
// layer, update the texture with the canvas data.
if (texture === this._texture) {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._canvas);
const ctx = this._canvas.getContext('2d');
ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
this._canvasDirty = false;
}
const currentShader = this._stampShader;
const bounds = this._bounds;
this._renderer.enterDrawRegion(this._toBufferDrawRegionId);
this._drawRectangle(currentShader, texture, bounds, x, y);
this._silhouetteDirty = true;
}
/**
* React to a change in the renderer's native size.
* @param {object} event - The change event.
@@ -480,31 +286,15 @@ class PenSkin extends Skin {
_setCanvasSize (canvasSize) {
const [width, height] = canvasSize;
const gl = this._renderer.gl;
this._bounds = new Rectangle();
this._bounds.initFromBounds(width / 2, width / -2, height / 2, height / -2);
this._canvas.width = width;
this._canvas.height = height;
this._size = canvasSize;
this._rotationCenter[0] = width / 2;
this._rotationCenter[1] = height / 2;
const gl = this._renderer.gl;
this._texture = twgl.createTexture(
gl,
{
auto: true,
mag: gl.NEAREST,
min: gl.NEAREST,
wrap: gl.CLAMP_TO_EDGE,
src: this._canvas
}
);
this._exportTexture = twgl.createTexture(
gl,
{
auto: true,
mag: gl.NEAREST,
min: gl.NEAREST,
wrap: gl.CLAMP_TO_EDGE,
@@ -516,66 +306,36 @@ class PenSkin extends Skin {
const attachments = [
{
format: gl.RGBA,
attachment: this._exportTexture
attachment: this._texture
}
];
if (this._framebuffer) {
twgl.resizeFramebufferInfo(gl, this._framebuffer, attachments, width, height);
twgl.resizeFramebufferInfo(gl, this._silhouetteBuffer, [{format: gl.RGBA}], width, height);
} else {
this._framebuffer = twgl.createFramebufferInfo(gl, attachments, width, height);
this._silhouetteBuffer = twgl.createFramebufferInfo(gl, [{format: gl.RGBA}], width, height);
}
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
this._silhouettePixels = new Uint8Array(Math.floor(width * height * 4));
this._silhouetteImageData = this._canvas.getContext('2d').createImageData(width, height);
this._silhouetteImageData = new ImageData(width, height);
this._silhouetteDirty = true;
}
/**
* Set context state to match provided pen attributes.
* @param {CanvasRenderingContext2D} context - the canvas rendering context to be modified.
* @param {PenAttributes} penAttributes - the pen attributes to be used.
* @private
*/
_setAttributes (context, penAttributes) {
penAttributes = penAttributes || DefaultPenAttributes;
const color4f = penAttributes.color4f || DefaultPenAttributes.color4f;
const diameter = penAttributes.diameter || DefaultPenAttributes.diameter;
const r = Math.round(color4f[0] * 255);
const g = Math.round(color4f[1] * 255);
const b = Math.round(color4f[2] * 255);
const a = color4f[3]; // Alpha is 0 to 1 (not 0 to 255 like r,g,b)
context.strokeStyle = `rgba(${r},${g},${b},${a})`;
context.lineCap = 'round';
context.lineWidth = diameter;
}
/**
* If there have been pen operations that have dirtied the canvas, update
* now before someone wants to use our silhouette.
*/
updateSilhouette () {
if (this._silhouetteDirty) {
if (this._canvasDirty) {
this._drawToBuffer();
}
// Render export texture to another framebuffer
const gl = this._renderer.gl;
this._renderer.enterDrawRegion(this._toBufferDrawRegionId);
this._renderer.enterDrawRegion(this._usePenBufferDrawRegionId);
// Sample the framebuffer's pixels into the silhouette instance
const gl = this._renderer.gl;
gl.readPixels(
0, 0,
this._canvas.width, this._canvas.height,
this._size[0], this._size[1],
gl.RGBA, gl.UNSIGNED_BYTE, this._silhouettePixels
);

View File

@@ -472,7 +472,7 @@ class RenderWebGL extends EventEmitter {
* @returns {int} The ID of the new Drawable.
*/
createDrawable (group) {
if (!group || !this._layerGroups.hasOwnProperty(group)) {
if (!group || !Object.prototype.hasOwnProperty.call(this._layerGroups, group)) {
log.warn('Cannot create a drawable without a known layer group');
return;
}
@@ -542,7 +542,7 @@ class RenderWebGL extends EventEmitter {
* @param {string} group Group name that the drawable belongs to
*/
destroyDrawable (drawableID, group) {
if (!group || !this._layerGroups.hasOwnProperty(group)) {
if (!group || !Object.prototype.hasOwnProperty.call(this._layerGroups, group)) {
log.warn('Cannot destroy drawable without known layer group.');
return;
}
@@ -596,7 +596,7 @@ class RenderWebGL extends EventEmitter {
* @return {?number} New order if changed, or null.
*/
setDrawableOrder (drawableID, order, group, optIsRelative, optMin) {
if (!group || !this._layerGroups.hasOwnProperty(group)) {
if (!group || !Object.prototype.hasOwnProperty.call(this._layerGroups, group)) {
log.warn('Cannot set the order of a drawable without a known layer group.');
return;
}
@@ -650,7 +650,7 @@ class RenderWebGL extends EventEmitter {
twgl.bindFramebufferInfo(gl, null);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor.apply(gl, this._backgroundColor4f);
gl.clearColor(...this._backgroundColor4f);
gl.clear(gl.COLOR_BUFFER_BIT);
this._drawThese(this._drawList, ShaderManager.DRAW_MODE.default, this._projection);
@@ -800,6 +800,8 @@ class RenderWebGL extends EventEmitter {
const color = __touchingColor;
const hasMask = Boolean(mask3b);
drawable.updateCPURenderAttributes();
// Masked drawable ignores ghost effect
const effectMask = ~ShaderManager.EFFECT_INFO.ghost.mask;
@@ -969,6 +971,8 @@ class RenderWebGL extends EventEmitter {
const drawable = this._allDrawables[drawableID];
const point = __isTouchingDrawablesPoint;
drawable.updateCPURenderAttributes();
// This is an EXTREMELY brute force collision detector, but it is
// still faster than asking the GPU to give us the pixels.
for (let x = bounds.left; x <= bounds.right; x++) {
@@ -1120,7 +1124,7 @@ class RenderWebGL extends EventEmitter {
let hit = RenderConstants.ID_NONE;
for (const hitID in hits) {
if (hits.hasOwnProperty(hitID) && (hits[hitID] > hits[hit])) {
if (Object.prototype.hasOwnProperty.call(hits, hitID) && (hits[hitID] > hits[hit])) {
hit = hitID;
}
}
@@ -1375,7 +1379,7 @@ class RenderWebGL extends EventEmitter {
gl.viewport(0, 0, bounds.width, bounds.height);
const projection = twgl.m4.ortho(bounds.left, bounds.right, bounds.top, bounds.bottom, -1, 1);
gl.clearColor.apply(gl, this._backgroundColor4f);
gl.clearColor(...this._backgroundColor4f);
gl.clear(gl.COLOR_BUFFER_BIT);
this._drawThese(this._drawList, ShaderManager.DRAW_MODE.default, projection);
@@ -1423,8 +1427,6 @@ class RenderWebGL extends EventEmitter {
/** @todo remove this once URL-based skin setting is removed. */
if (!drawable.skin || !drawable.skin.getTexture([100, 100])) return null;
drawable.updateCPURenderAttributes();
const bounds = drawable.getFastBounds();
// Limit queries to the stage size.
@@ -1871,7 +1873,7 @@ class RenderWebGL extends EventEmitter {
const uniforms = {};
let effectBits = drawable.enabledEffects;
effectBits &= opts.hasOwnProperty('effectMask') ? opts.effectMask : effectBits;
effectBits &= Object.prototype.hasOwnProperty.call(opts, 'effectMask') ? opts.effectMask : effectBits;
const newShader = this._shaderManager.getShader(drawMode, effectBits);
// Manually perform region check. Do not create functions inside a
@@ -1899,7 +1901,9 @@ class RenderWebGL extends EventEmitter {
if (uniforms.u_skin) {
twgl.setTextureParameters(
gl, uniforms.u_skin, {minMag: drawable.useNearest(drawableScale) ? gl.NEAREST : gl.LINEAR}
gl, uniforms.u_skin, {
minMag: drawable.skin.useNearest(drawableScale, drawable) ? gl.NEAREST : gl.LINEAR
}
);
}
@@ -1919,14 +1923,14 @@ class RenderWebGL extends EventEmitter {
_getConvexHullPointsForDrawable (drawableID) {
const drawable = this._allDrawables[drawableID];
drawable.updateCPURenderAttributes();
const [width, height] = drawable.skin.size;
// No points in the hull if invisible or size is 0.
if (!drawable.getVisible() || width === 0 || height === 0) {
return [];
}
drawable.updateCPURenderAttributes();
/**
* Return the determinant of two vectors, the vector from A to B and the vector from A to C.
*

View File

@@ -2,6 +2,7 @@ const twgl = require('twgl.js');
const Skin = require('./Skin');
const SvgRenderer = require('scratch-svg-renderer').SVGRenderer;
const ShaderManager = require('./ShaderManager');
const MAX_TEXTURE_DIMENSION = 2048;
@@ -58,6 +59,35 @@ class SVGSkin extends Skin {
return this._svgRenderer.size;
}
useNearest (scale, drawable) {
// If the effect bits for mosaic, pixelate, whirl, or fisheye are set, use linear
if ((drawable.enabledEffects & (
ShaderManager.EFFECT_INFO.fisheye.mask |
ShaderManager.EFFECT_INFO.whirl.mask |
ShaderManager.EFFECT_INFO.pixelate.mask |
ShaderManager.EFFECT_INFO.mosaic.mask
)) !== 0) {
return false;
}
// We can't use nearest neighbor unless we are a multiple of 90 rotation
if (drawable._direction % 90 !== 0) {
return false;
}
// Because SVG skins' bounding boxes are currently not pixel-aligned, the idea here is to hide blurriness
// by using nearest-neighbor scaling if one screen-space pixel is "close enough" to one texture pixel.
// If the scale of the skin is very close to 100 (0.99999 variance is okay I guess)
// TODO: Make this check more precise. We should use nearest if there's less than one pixel's difference
// between the screen-space and texture-space sizes of the skin. Mipmaps make this harder because there are
// multiple textures (and hence multiple texture spaces) and we need to know which one to choose.
if (Math.abs(scale[0]) > 99 && Math.abs(scale[0]) < 101 &&
Math.abs(scale[1]) > 99 && Math.abs(scale[1]) < 101) {
return true;
}
return false;
}
/**
* Create a MIP for a given scale.
* @param {number} scale - The relative size of the MIP

View File

@@ -16,7 +16,7 @@ class ShaderManager {
*/
this._shaderCache = {};
for (const modeName in ShaderManager.DRAW_MODE) {
if (ShaderManager.DRAW_MODE.hasOwnProperty(modeName)) {
if (Object.prototype.hasOwnProperty.call(ShaderManager.DRAW_MODE, modeName)) {
this._shaderCache[modeName] = [];
}
}

View File

@@ -59,13 +59,6 @@ class Skin extends EventEmitter {
this._id = RenderConstants.ID_NONE;
}
/**
* @returns {boolean} true for a raster-style skin (like a BitmapSkin), false for vector-style (like SVGSkin).
*/
get isRaster () {
return false;
}
/**
* @return {int} the unique ID for this Skin.
*/
@@ -88,6 +81,19 @@ class Skin extends EventEmitter {
return [0, 0];
}
/**
* Should this skin's texture be filtered with nearest-neighbor or linear interpolation at the given scale?
* @param {?Array<Number>} scale The screen-space X and Y scaling factors at which this skin's texture will be
* displayed, as percentages (100 means 1 "native size" unit is 1 screen pixel; 200 means 2 screen pixels, etc).
* @param {Drawable} drawable The drawable that this skin's texture will be applied to.
* @return {boolean} True if this skin's texture, as returned by {@link getTexture}, should be filtered with
* nearest-neighbor interpolation.
*/
// eslint-disable-next-line no-unused-vars
useNearest (scale, drawable) {
return true;
}
/**
* Get the center of the current bounding box
* @return {Array<number>} the center of the current bounding box

View File

@@ -37,7 +37,7 @@ image.src = 'https://cdn.assets.scratch.mit.edu/internalapi/asset/7e24c99c1b853e
// SVG (cat 1-a)
const xhr = new XMLHttpRequest();
xhr.addEventListener('load', function () {
xhr.addEventListener('load', () => {
const skinId = renderer.createSVGSkin(xhr.responseText);
if (wantedSkin === WantedSkinType.vector) {
renderer.updateDrawableProperties(drawableID2, {
@@ -56,10 +56,10 @@ if (wantedSkin === WantedSkinType.pen) {
});
canvas.addEventListener('click', event => {
let rect = canvas.getBoundingClientRect();
const rect = canvas.getBoundingClientRect();
let x = event.clientX - rect.left;
let y = event.clientY - rect.top;
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
renderer.penLine(penSkinID, {
color4f: [Math.random(), Math.random(), Math.random(), 1],
@@ -184,7 +184,7 @@ canvas.addEventListener('mousemove', event => {
canvas.addEventListener('click', event => {
const mousePos = getMousePosition(event, canvas);
const pickID = renderer.pick(mousePos.x, mousePos.y);
console.log('You clicked on ' + (pickID < 0 ? 'nothing' : 'ID# ' + pickID));
console.log(`You clicked on ${(pickID < 0 ? 'nothing' : `ID# ${pickID}`)}`);
if (pickID >= 0) {
console.dir(renderer.extractDrawable(pickID, mousePos.x, mousePos.y));
}

View File

@@ -92,9 +92,9 @@ renderCanvas.addEventListener('click', event => {
}
});
const rgb2fillStyle = (rgb) => {
return `rgb(${rgb[0]},${rgb[1]},${rgb[2]})`;
};
const rgb2fillStyle = rgb => (
`rgb(${rgb[0]},${rgb[1]},${rgb[2]})`
);
const makeCursorImage = () => {
const canvas = document.createElement('canvas');

View File

@@ -1,4 +1,4 @@
/* global window, VirtualMachine, ScratchStorage, ScratchSVGRenderer */
/* global VirtualMachine, ScratchStorage, ScratchSVGRenderer */
/* eslint-env browser */
// Wait for all SVG skins to be loaded.

View File

@@ -1,4 +1,4 @@
/* global vm, render, Promise */
/* global vm, render */
const {chromium} = require('playwright-chromium');
const test = require('tap').test;
const path = require('path');

View File

@@ -1,4 +1,4 @@
/* global vm, Promise */
/* global vm */
const {chromium} = require('playwright-chromium');
const test = require('tap').test;
const path = require('path');