Compare commits

..

56 Commits

Author SHA1 Message Date
greenkeeper[bot]
f6e47f7173 chore(package): update uglifyjs-webpack-plugin to version 2.1.2
Closes #345
2019-02-25 13:30:35 +00:00
Chris Willis-Ford
c9f86ef53b Merge pull request #406 from cwillisf/playground-webpack
Add "query playground"
2019-02-14 10:33:31 -08:00
Katie Broida
4bf233ef36 Merge pull request #409 from ktbee/compat-bitmap-position-off-stage
Compatibility fix for bitmap position off stage
2019-02-13 13:29:26 -05:00
Karishma Chadha
253cbd019d Merge pull request #411 from LLK/greenkeeper/scratch-vm-0.2.0-prerelease.20190207224121
chore(package): update scratch-vm to version 0.2.0-prerelease.2019020…
2019-02-13 10:21:44 -05:00
Katie Broida
1f0f89920a Always use getAABB for bitmap skins when determining fenced position 2019-02-11 11:56:59 -05:00
Katie Broida
25df9f1ab7 Merge pull request #408 from ktbee/compat-integer-x-y-off-stage
Use Math.ceil and Math.floor to match Scratch 2 logic
2019-02-08 11:53:07 -05:00
greenkeeper[bot]
7680270f40 chore(package): update scratch-vm to version 0.2.0-prerelease.20190207224121
Closes #361
2019-02-07 22:43:47 +00:00
Chris Willis-Ford
c7b22b58c2 Merge pull request #410 from LLK/revert-407-coordinates-fixups
Revert "Adjust CPU isTouchingColor to match GPU results"
2019-02-07 13:11:57 -08:00
Chris Willis-Ford
f2d457a827 Revert "Adjust CPU isTouchingColor to match GPU results" 2019-02-07 13:00:15 -08:00
Chris Willis-Ford
e3c68e7122 Merge pull request #407 from cwillisf/coordinates-fixups
Adjust CPU isTouchingColor to match GPU results
2019-02-06 13:31:52 -08:00
Christopher Willis-Ford
e64d8727ec Fix (x,y) => point[] conversion comments 2019-02-06 11:08:17 -08:00
Christopher Willis-Ford
c390124df4 Convert 'force GPU' flag into 'useGpuMode' enum 2019-02-06 10:47:49 -08:00
Christopher Willis-Ford
8bd6241160 Fix direction for Y iteration on CPU path
For some reason the JavaScript engine insists on running the code
instead of doing what the comment says. I guess they should match.
2019-02-05 18:19:21 -08:00
Katie Broida
c8b9516219 Use Math.ceil and Math.floor to match Scratch 2 logic 2019-02-04 16:03:33 -05:00
Christopher Willis-Ford
1db67a474e Adjust CPU isTouchingColor to match GPU results 2019-02-04 11:20:59 -08:00
Christopher Willis-Ford
028b4eba3f Adjust cursor coordinates for devicePixelRatio 2019-01-30 16:37:59 -08:00
Christopher Willis-Ford
59cef02fdb Mark correct viewport corners with red dots 2019-01-30 15:58:51 -08:00
Christopher Willis-Ford
99d6e46f7e Adjust rendering for crisp pixels
- Adjust the rotation center of the cursor so that its single pixel is
  gets rendered onto a single stage pixel instead of being split across
  2-4 stage pixels.
- Add canvas CSS to make most browsers scale the canvases without
  interpolation.
2019-01-30 11:00:27 -08:00
Christopher Willis-Ford
992977d6c6 Add debug canvas support to isTouching CPU path 2019-01-30 11:00:14 -08:00
Christopher Willis-Ford
a358c8f916 Lint cleanup 2019-01-28 12:25:58 -08:00
Christopher Willis-Ford
e8d71277e2 Use query playground to compare GPU vs. CPU implementations 2019-01-28 11:43:38 -08:00
Paul Kaplan
b4f9f28417 Merge pull request #405 from LLK/greenkeeper/scratch-svg-renderer-0.2.0-prerelease.20190125192231
fix(package): update scratch-svg-renderer to version 0.2.0-prerelease…
2019-01-28 10:40:13 -05:00
Christopher Willis-Ford
fba2d90fda Stub queryPlayground.html 2019-01-25 17:28:51 -08:00
Christopher Willis-Ford
31db3d8596 Build playground using Webpack 2019-01-25 17:28:02 -08:00
greenkeeper[bot]
898d5d7885 fix(package): update scratch-svg-renderer to version 0.2.0-prerelease.20190125192231
Closes #397
2019-01-25 19:23:50 +00:00
Evelyn Eastmond
9b11ac894d Merge pull request #376 from evhan55/bug/extract-drawable
Fix extraction of drawable to not clip bounds.
2019-01-24 21:33:50 -05:00
Evelyn Eastmond
fc6fcd0543 Removing console log comment. 2019-01-24 10:56:11 -05:00
Evelyn Eastmond
b77f4c663a Removing console log. 2019-01-24 10:54:08 -05:00
Evelyn Eastmond
5e5a423d39 Fixing checkFramebufferstatus check. 2019-01-24 10:54:08 -05:00
Evelyn Eastmond
402cfbf99f Adding a console log for testing. 2019-01-24 10:54:08 -05:00
Evelyn Eastmond
a0dd716c23 Adding some sanity checks and error handling that aren't fully working yet. 2019-01-24 10:54:08 -05:00
Evelyn Eastmond
183919a20a Fixing a comment. 2019-01-24 10:54:08 -05:00
Evelyn Eastmond
3cfafebb2e Fixing extraction of a drawable to not clip bounds. 2019-01-24 10:54:08 -05:00
DD Liu
931ff270dd Merge pull request #404 from LLK/touchingColor2
Update silhouette after getting texture at a new scale
2019-01-22 17:29:43 -05:00
DD Liu
cc448951f9 Update silhouette after getting texture at a new scale 2019-01-22 13:58:59 -05:00
Katie Broida
cfa0194ab8 Merge pull request #402 from ktbee/fix-test-typo
Fix typo for sb3 test files
2019-01-17 15:47:33 -05:00
Katie Broida
2b224eb9da Merge pull request #400 from ktbee/fence-width-compat
Add inset logic that is closer to Scratch 2's inset
2019-01-17 13:25:37 -05:00
Katie Broida
735c7caaae Fix typo for sb3 test files 2019-01-17 12:40:30 -05:00
Katie Broida
ad1b7111c8 Add inset logic that is closer to Scratch 2's inset 2019-01-17 11:59:22 -05:00
Chris Willis-Ford
e54b590d56 Merge pull request #399 from cwillisf/fix-brightness-effect
Change brightness effect to match Scratch 2.0 in 2D mode
2019-01-16 12:22:22 -08:00
Christopher Willis-Ford
355a8c5395 Change brightness effect to match Scratch 2.0 in 2D mode 2019-01-15 19:45:45 -08:00
Paul Kaplan
f1a7aab5a6 Merge pull request #398 from paulkaplan/revert-silhouette
Revert "Merge pull request #394 from paulkaplan/defer-silhouette-upda…
2019-01-15 12:25:37 -05:00
Paul Kaplan
bb84abab87 Revert "Merge pull request #394 from paulkaplan/defer-silhouette-updates"
This reverts commit a5f852fcc2, reversing
changes made to e616ab5d35.
2019-01-14 16:51:26 -05:00
Paul Kaplan
75772989ea Merge pull request #395 from LLK/greenkeeper/scratch-svg-renderer-0.2.0-prerelease.20190109201344
Update scratch-svg-renderer to the latest version 🚀
2019-01-09 15:16:30 -05:00
greenkeeper[bot]
cf5aafc12f fix(package): update scratch-svg-renderer to version 0.2.0-prerelease.20190109201344 2019-01-09 20:15:01 +00:00
Paul Kaplan
a5f852fcc2 Merge pull request #394 from paulkaplan/defer-silhouette-updates
Implement updateSilhouette to allow updates to happen when needed
2019-01-09 14:59:56 -05:00
Paul Kaplan
07544595fd Implement updateSilhouette to allow updates to happen when needed 2019-01-09 14:22:08 -05:00
Paul Kaplan
e616ab5d35 Merge pull request #393 from LLK/revert-391-blurrybg
Revert "Increase maximum texture size and back it off if GL fails to render"
2019-01-08 16:00:30 -05:00
Paul Kaplan
6e072ea026 Revert "Increase maximum texture size and back it off if GL fails to render" 2019-01-08 15:59:09 -05:00
DD Liu
230a68d564 Merge pull request #391 from LLK/blurrybg
Increase maximum texture size and back it off if GL fails to render
2019-01-08 12:41:49 -05:00
Karishma Chadha
a0ce9e3dca Merge pull request #392 from kchadha/fix-uncaught-type-error
Fix uncaught error `cannot find property updateSilhouette of null`
2019-01-07 11:19:49 -05:00
DD Liu
0676e0f54a Use callback for loop, and update maxTextureScale during callback 2019-01-07 10:00:31 -05:00
Karishma Chadha
ba93f21795 Fix uncaught error when drawable.skin does not exist. Log a warning in this case instead. 2019-01-06 18:02:33 -05:00
DD Liu
5be561133d Increase maximum texture size and back it off if GL fails to render 2019-01-05 00:43:55 -05:00
Chris Willis-Ford
bfa90a07cc Merge pull request #385 from fsih/fixBlurry
Always use nearest for raster textures
2019-01-03 14:00:29 -05:00
DD
5f38cd1ee1 Always use nearest for raster textures 2019-01-03 12:00:28 -05:00
16 changed files with 610 additions and 201 deletions

View File

@@ -37,10 +37,10 @@
"gh-pages": "^1.0.0",
"jsdoc": "^3.5.5",
"json": "^9.0.4",
"scratch-vm": "0.2.0-prerelease.20181024204838",
"scratch-vm": "0.2.0-prerelease.20190207224121",
"tap": "^11.0.0",
"travis-after-all": "^1.4.4",
"uglifyjs-webpack-plugin": "^2.1.1",
"uglifyjs-webpack-plugin": "^2.1.2",
"webpack": "^4.8.0",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^3.1.4"
@@ -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.20181220183040",
"scratch-svg-renderer": "0.2.0-prerelease.20190125192231",
"twgl.js": "4.4.0"
}
}

View File

@@ -59,6 +59,15 @@ class BitmapSkin extends Skin {
return this._texture;
}
/**
* Get the bounds of the drawable for determining its fenced position.
* @param {Array<number>} drawable - The Drawable instance this skin is using.
* @return {!Rectangle} The drawable's bounds. For compatibility with Scratch 2, we always use getAABB for bitmaps.
*/
getFenceBounds (drawable) {
return drawable.getAABB();
}
/**
* 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.

View File

@@ -426,16 +426,16 @@ class Drawable {
* Should the drawable use NEAREST NEIGHBOR or LINEAR INTERPOLATION mode
*/
get useNearest () {
// We can't use nearest neighbor unless we are a multiple of 90 rotation
if (this._direction % 90 !== 0) {
return false;
}
// Raster skins (bitmaps) should always prefer nearest neighbor
if (this.skin.isRaster) {
return true;
}
// 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(this.scale[0]) > 99 && Math.abs(this.scale[0]) < 101 &&
Math.abs(this.scale[1]) > 99 && Math.abs(this.scale[1]) < 101) {

View File

@@ -132,6 +132,9 @@ class RenderWebGL extends EventEmitter {
throw new Error('Could not get WebGL context: this browser or environment may not support WebGL.');
}
/** @type {RenderWebGL.UseGpuModes} */
this._useGpuMode = RenderWebGL.UseGpuModes.Automatic;
/** @type {Drawable[]} */
this._allDrawables = [];
@@ -243,6 +246,14 @@ class RenderWebGL extends EventEmitter {
this._debugCanvas = canvas;
}
/**
* Control the use of the GPU or CPU paths in `isTouchingColor`.
* @param {RenderWebGL.UseGpuModes} useGpuMode - automatically decide, force CPU, or force GPU.
*/
setUseGpuMode (useGpuMode) {
this._useGpuMode = useGpuMode;
}
/**
* Set logical size of the stage in Scratch units.
* @param {int} xLeft The left edge's x-coordinate. Scratch 2 uses -240.
@@ -717,9 +728,16 @@ class RenderWebGL extends EventEmitter {
const bounds = this._candidatesBounds(candidates);
// if there are just too many pixels to CPU render efficently, we
// need to let readPixels happen
if (bounds.width * bounds.height * (candidates.length + 1) >= __cpuTouchingColorPixelCount) {
const maxPixelsForCPU = this._getMaxPixelsForCPU();
const debugCanvasContext = this._debugCanvas && this._debugCanvas.getContext('2d');
if (debugCanvasContext) {
this._debugCanvas.width = bounds.width;
this._debugCanvas.height = bounds.height;
}
// if there are just too many pixels to CPU render efficiently, we need to let readPixels happen
if (bounds.width * bounds.height * (candidates.length + 1) >= maxPixelsForCPU) {
this._isTouchingColorGpuStart(drawableID, candidates.map(({id}) => id).reverse(), bounds, color3b, mask3b);
}
@@ -728,29 +746,45 @@ class RenderWebGL extends EventEmitter {
const color = __touchingColor;
const hasMask = Boolean(mask3b);
// Scratch Space - +y is top
for (let y = bounds.bottom; y <= bounds.top; y++) {
if (bounds.width * (y - bounds.bottom) * (candidates.length + 1) >= __cpuTouchingColorPixelCount) {
if (bounds.width * (y - bounds.bottom) * (candidates.length + 1) >= maxPixelsForCPU) {
return this._isTouchingColorGpuFin(bounds, color3b, y - bounds.bottom);
}
// Scratch Space - +y is top
for (let x = bounds.left; x <= bounds.right; x++) {
point[1] = y;
point[0] = x;
if (
// if we use a mask, check our sample color
(hasMask ?
maskMatches(Drawable.sampleColor4b(point, drawable, color), mask3b) :
drawable.isTouching(point)) &&
// and the target color is drawn at this pixel
colorMatches(RenderWebGL.sampleColor3b(point, candidates, color), color3b, 0)
) {
return true;
// if we use a mask, check our sample color...
if (hasMask ?
maskMatches(Drawable.sampleColor4b(point, drawable, color), mask3b) :
drawable.isTouching(point)) {
RenderWebGL.sampleColor3b(point, candidates, color);
if (debugCanvasContext) {
debugCanvasContext.fillStyle = `rgb(${color[0]},${color[1]},${color[2]})`;
debugCanvasContext.fillRect(x - bounds.left, bounds.bottom - y, 1, 1);
}
// ...and the target color is drawn at this pixel
if (colorMatches(color, color3b, 0)) {
return true;
}
}
}
}
return false;
}
_getMaxPixelsForCPU () {
switch (this._useGpuMode) {
case RenderWebGL.UseGpuModes.ForceCPU:
return Infinity;
case RenderWebGL.UseGpuModes.ForceGPU:
return 0;
case RenderWebGL.UseGpuModes.Automatic:
default:
return __cpuTouchingColorPixelCount;
}
}
_isTouchingColorGpuStart (drawableID, candidateIDs, bounds, color3b, mask3b) {
this._doExitDrawRegion();
@@ -931,7 +965,11 @@ class RenderWebGL extends EventEmitter {
const worldPos = twgl.v3.create();
drawable.updateMatrix();
drawable.skin.updateSilhouette();
if (drawable.skin) {
drawable.skin.updateSilhouette();
} else {
log.warn(`Could not find skin for drawable with id: ${drawableID}`);
}
for (worldPos[1] = bounds.bottom; worldPos[1] <= bounds.top; worldPos[1]++) {
for (worldPos[0] = bounds.left; worldPos[0] <= bounds.right; worldPos[0]++) {
@@ -963,7 +1001,11 @@ class RenderWebGL extends EventEmitter {
// default pick list ignores visible and ghosted sprites.
if (drawable.getVisible() && drawable.getUniforms().u_ghost !== 0) {
drawable.updateMatrix();
drawable.skin.updateSilhouette();
if (drawable.skin) {
drawable.skin.updateSilhouette();
} else {
log.warn(`Could not find skin for drawable with id: ${id}`);
}
return true;
}
return false;
@@ -1040,11 +1082,28 @@ class RenderWebGL extends EventEmitter {
const scratchY = this._nativeSize[1] * ((y / this._gl.canvas.clientHeight) - 0.5);
const gl = this._gl;
twgl.bindFramebufferInfo(gl, this._queryBufferInfo);
const bounds = drawable.getFastBounds();
bounds.snapToInt();
// Set a reasonable max limit width and height for the bufferInfo bounds
const maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
const clampedWidth = Math.min(2048, bounds.width, maxTextureSize);
const clampedHeight = Math.min(2048, bounds.height, maxTextureSize);
// Make a new bufferInfo since this._queryBufferInfo is limited to 480x360
const attachments = [
{format: gl.RGBA},
{format: gl.DEPTH_STENCIL}
];
const bufferInfo = twgl.createFramebufferInfo(gl, attachments, clampedWidth, clampedHeight);
// If the new bufferInfo is invalid, fall back to using the smaller _queryBufferInfo
twgl.bindFramebufferInfo(gl, bufferInfo);
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
twgl.bindFramebufferInfo(gl, this._queryBufferInfo);
}
// Translate to scratch units relative to the drawable
const pickX = scratchX - bounds.left;
const pickY = scratchY + bounds.top;
@@ -1291,20 +1350,20 @@ class RenderWebGL extends EventEmitter {
const dx = x - drawable._position[0];
const dy = y - drawable._position[1];
const aabb = drawable._skin.getFenceBounds(drawable);
const inset = Math.floor(Math.min(aabb.width, aabb.height) / 2);
const aabb = drawable.getFastBounds();
const sx = this._xRight - Math.min(FENCE_WIDTH, Math.floor((aabb.right - aabb.left) / 2));
const sx = this._xRight - Math.min(FENCE_WIDTH, inset);
if (aabb.right + dx < -sx) {
x = drawable._position[0] - (sx + aabb.right);
x = Math.ceil(drawable._position[0] - (sx + aabb.right));
} else if (aabb.left + dx > sx) {
x = drawable._position[0] + (sx - aabb.left);
x = Math.floor(drawable._position[0] + (sx - aabb.left));
}
const sy = this._yTop - Math.min(FENCE_WIDTH, Math.floor((aabb.top - aabb.bottom) / 2));
const sy = this._yTop - Math.min(FENCE_WIDTH, inset);
if (aabb.top + dy < -sy) {
y = drawable._position[1] - (sy + aabb.top);
y = Math.ceil(drawable._position[1] - (sy + aabb.top));
} else if (aabb.bottom + dy > sy) {
y = drawable._position[1] + (sy - aabb.bottom);
y = Math.floor(drawable._position[1] + (sy - aabb.bottom));
}
return [x, y];
}
@@ -1744,4 +1803,25 @@ class RenderWebGL extends EventEmitter {
// :3
RenderWebGL.prototype.canHazPixels = RenderWebGL.prototype.extractDrawable;
/**
* Values for setUseGPU()
* @enum {string}
*/
RenderWebGL.UseGpuModes = {
/**
* Heuristically decide whether to use the GPU path, the CPU path, or a dynamic mixture of the two.
*/
Automatic: 'Automatic',
/**
* Always use the GPU path.
*/
ForceGPU: 'ForceGPU',
/**
* Always use the CPU path.
*/
ForceCPU: 'ForceCPU'
};
module.exports = RenderWebGL;

View File

@@ -80,6 +80,7 @@ class SVGSkin extends Skin {
const gl = this._renderer.gl;
gl.bindTexture(gl.TEXTURE_2D, this._texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._svgRenderer.canvas);
this._silhouette.update(this._svgRenderer.canvas);
}
});
}

View File

@@ -136,6 +136,15 @@ class Skin extends EventEmitter {
return null;
}
/**
* Get the bounds of the drawable for determining its fenced position.
* @param {Array<number>} drawable - The Drawable instance this skin is using.
* @return {!Rectangle} The drawable's bounds.
*/
getFenceBounds (drawable) {
return drawable.getFastBounds();
}
/**
* Update and returns the uniforms for this skin.
* @param {Array<number>} scale - The scaling factors to be used.

View File

@@ -1,7 +1,9 @@
module.exports = {
root: true,
extends: ['scratch'],
env: {
browser: true
},
rules: {
'no-console': 'off'
}
};

View File

@@ -0,0 +1,37 @@
// Adapted from code by Simon Sarris: http://stackoverflow.com/a/10450761
const getMousePos = function (event, element) {
const stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(element, null).paddingLeft, 10) || 0;
const stylePaddingTop = parseInt(document.defaultView.getComputedStyle(element, null).paddingTop, 10) || 0;
const styleBorderLeft = parseInt(document.defaultView.getComputedStyle(element, null).borderLeftWidth, 10) || 0;
const styleBorderTop = parseInt(document.defaultView.getComputedStyle(element, null).borderTopWidth, 10) || 0;
// Some pages have fixed-position bars at the top or left of the page
// They will mess up mouse coordinates and this fixes that
const html = document.body.parentNode;
const htmlTop = html.offsetTop;
const htmlLeft = html.offsetLeft;
// Compute the total offset. It's possible to cache this if you want
let offsetX = 0;
let offsetY = 0;
if (typeof element.offsetParent !== 'undefined') {
do {
offsetX += element.offsetLeft;
offsetY += element.offsetTop;
} while ((element = element.offsetParent));
}
// Add padding and border style widths to offset
// Also add the <html> offsets in case there's a position:fixed bar
// This part is not strictly necessary, it depends on your styling
offsetX += stylePaddingLeft + styleBorderLeft + htmlLeft;
offsetY += stylePaddingTop + styleBorderTop + htmlTop;
// We return a simple javascript object with x and y defined
return {
x: event.pageX - offsetX,
y: event.pageY - offsetY
};
};
module.exports = getMousePos;

View File

@@ -12,7 +12,7 @@
<canvas id="debug-canvas" width="10" height="10" style="border:3px dashed red"></canvas>
<p>
<label for="fudgeproperty">Property to tweak:</label>
<select id="fudgeproperty" onchange="onFudgePropertyChanged(this.value)">
<select id="fudgeproperty">
<option value="posx">Position X</option>
<option value="posy">Position Y</option>
<option value="direction">Direction</option>
@@ -27,149 +27,12 @@
<option value="ghost">Ghost</option>
</select>
<label for="fudge">Property Value:</label>
<input type="range" id="fudge" style="width:50%" value="90" min="-90" max="270" step="any" oninput="onFudgeChanged(this.value)" onchange="onFudgeChanged(this.value)">
<input type="range" id="fudge" style="width:50%" value="90" min="-90" max="270" step="any">
</p>
<p>
<label for="fudgeMin">Min:</label><input id="fudgeMin" type="number" onchange="onFudgeMinChanged(this.value)">
<label for="fudgeMax">Max:</label><input id="fudgeMax" type="number" onchange="onFudgeMaxChanged(this.value)">
<label for="fudgeMin">Min:</label><input id="fudgeMin" type="number">
<label for="fudgeMax">Max:</label><input id="fudgeMax" type="number">
</p>
<script src="scratch-render.js"></script>
<script>
var canvas = document.getElementById('scratch-stage');
var fudge = 90;
var renderer = new ScratchRender(canvas);
renderer.setLayerGroupOrdering(['group1']);
var drawableID = renderer.createDrawable('group1');
renderer.updateDrawableProperties(drawableID, {
position: [0, 0],
scale: [100, 100],
direction: 90
});
var drawableID2 = renderer.createDrawable('group1');
var wantBitmapSkin = false;
// Bitmap (squirrel)
var image = new Image();
image.onload = function () {
var bitmapSkinId = renderer.createBitmapSkin(image);
if (wantBitmapSkin) {
renderer.updateDrawableProperties(drawableID2, {
skinId: bitmapSkinId
});
}
};
image.crossOrigin = 'anonymous';
image.src = 'https://cdn.assets.scratch.mit.edu/internalapi/asset/7e24c99c1b853e52f8e7f9004416fa34.png/get/';
// SVG (cat 1-a)
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", function () {
var skinId = renderer.createSVGSkin(xhr.responseText);
if (!wantBitmapSkin) {
renderer.updateDrawableProperties(drawableID2, {
skinId: skinId
});
}
});
xhr.open('GET', 'https://cdn.assets.scratch.mit.edu/internalapi/asset/f88bf1935daea28f8ca098462a31dbb0.svg/get/');
xhr.send();
var posX = 0;
var posY = 0;
var scaleX = 100;
var scaleY = 100;
var fudgeProperty = 'posx';
function onFudgePropertyChanged(newValue) {
fudgeProperty = newValue;
}
var fudgeInput = document.getElementById('fudge');
function onFudgeMinChanged(newValue) {
fudgeInput.min = newValue;
}
function onFudgeMaxChanged(newValue) {
fudgeInput.max = newValue;
}
function onFudgeChanged(newValue) {
fudge = newValue;
var props = {};
switch (fudgeProperty) {
case 'posx': props.position = [fudge, posY]; posX = fudge; break;
case 'posy': props.position = [posX, fudge]; posY = fudge; break;
case 'direction': props.direction = fudge; break;
case 'scalex': props.scale = [fudge, scaleY]; scaleX = fudge; break;
case 'scaley': props.scale = [scaleX, fudge]; scaleY = fudge; break;
case 'color': props.color = fudge; break;
case 'whirl': props.whirl = fudge; break;
case 'fisheye': props.fisheye = fudge; break;
case 'pixelate': props.pixelate = fudge; break;
case 'mosaic': props.mosaic = fudge; break;
case 'brightness': props.brightness = fudge; break;
case 'ghost': props.ghost = fudge; break;
}
renderer.updateDrawableProperties(drawableID2, props);
}
// Adapted from code by Simon Sarris: http://stackoverflow.com/a/10450761
function getMousePos(event, element) {
var stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(element, null)['paddingLeft'], 10) || 0;
var stylePaddingTop = parseInt(document.defaultView.getComputedStyle(element, null)['paddingTop'], 10) || 0;
var styleBorderLeft = parseInt(document.defaultView.getComputedStyle(element, null)['borderLeftWidth'], 10) || 0;
var styleBorderTop = parseInt(document.defaultView.getComputedStyle(element, null)['borderTopWidth'], 10) || 0;
// Some pages have fixed-position bars at the top or left of the page
// They will mess up mouse coordinates and this fixes that
var html = document.body.parentNode;
var htmlTop = html.offsetTop;
var htmlLeft = html.offsetLeft;
// Compute the total offset. It's possible to cache this if you want
var offsetX = 0, offsetY = 0;
if (element.offsetParent !== undefined) {
do {
offsetX += element.offsetLeft;
offsetY += element.offsetTop;
} while ((element = element.offsetParent));
}
// Add padding and border style widths to offset
// Also add the <html> offsets in case there's a position:fixed bar
// This part is not strictly necessary, it depends on your styling
offsetX += stylePaddingLeft + styleBorderLeft + htmlLeft;
offsetY += stylePaddingTop + styleBorderTop + htmlTop;
// We return a simple javascript object with x and y defined
return {
x: event.pageX - offsetX,
y: event.pageY - offsetY
};
}
canvas.onmousemove = function(event) {
var mousePos = getMousePos(event, canvas);
renderer.extractColor(mousePos.x, mousePos.y, 30);
};
canvas.onclick = function(event) {
var mousePos = getMousePos(event, canvas);
var pickID = renderer.pick(mousePos.x, mousePos.y);
console.log('You clicked on ' + (pickID < 0 ? 'nothing' : 'ID# ' + pickID));
if (pickID >= 0) {
console.dir(renderer.extractDrawable(pickID, mousePos.x, mousePos.y));
}
};
function drawStep() {
renderer.draw();
// renderer.getBounds(drawableID2);
// renderer.isTouchingColor(drawableID2, [255,255,255]);
requestAnimationFrame(drawStep);
}
drawStep();
var debugCanvas = /** @type {canvas} */ document.getElementById('debug-canvas');
renderer.setDebugCanvas(debugCanvas);
</script>
<script src="playground.js"></script>
</body>
</html>

View File

@@ -0,0 +1,141 @@
const ScratchRender = require('../RenderWebGL');
const getMousePosition = require('./getMousePosition');
var canvas = document.getElementById('scratch-stage');
var fudge = 90;
var renderer = new ScratchRender(canvas);
renderer.setLayerGroupOrdering(['group1']);
var drawableID = renderer.createDrawable('group1');
renderer.updateDrawableProperties(drawableID, {
position: [0, 0],
scale: [100, 100],
direction: 90
});
var drawableID2 = renderer.createDrawable('group1');
var wantBitmapSkin = false;
// Bitmap (squirrel)
var image = new Image();
image.addEventListener('load', () => {
var bitmapSkinId = renderer.createBitmapSkin(image);
if (wantBitmapSkin) {
renderer.updateDrawableProperties(drawableID2, {
skinId: bitmapSkinId
});
}
});
image.crossOrigin = 'anonymous';
image.src = 'https://cdn.assets.scratch.mit.edu/internalapi/asset/7e24c99c1b853e52f8e7f9004416fa34.png/get/';
// SVG (cat 1-a)
var xhr = new XMLHttpRequest();
xhr.addEventListener('load', function () {
var skinId = renderer.createSVGSkin(xhr.responseText);
if (!wantBitmapSkin) {
renderer.updateDrawableProperties(drawableID2, {
skinId: skinId
});
}
});
xhr.open('GET', 'https://cdn.assets.scratch.mit.edu/internalapi/asset/f88bf1935daea28f8ca098462a31dbb0.svg/get/');
xhr.send();
var posX = 0;
var posY = 0;
var scaleX = 100;
var scaleY = 100;
var fudgeProperty = 'posx';
const fudgePropertyInput = document.getElementById('fudgeproperty');
fudgePropertyInput.addEventListener('change', event => {
fudgeProperty = event.target.value;
});
const fudgeInput = document.getElementById('fudge');
const fudgeMinInput = document.getElementById('fudgeMin');
fudgeMinInput.addEventListener('change', event => {
fudgeInput.min = event.target.valueAsNumber;
});
const fudgeMaxInput = document.getElementById('fudgeMax');
fudgeMaxInput.addEventListener('change', event => {
fudgeInput.max = event.target.valueAsNumber;
});
const handleFudgeChanged = function (event) {
fudge = event.target.valueAsNumber;
var props = {};
switch (fudgeProperty) {
case 'posx':
props.position = [fudge, posY];
posX = fudge;
break;
case 'posy':
props.position = [posX, fudge];
posY = fudge;
break;
case 'direction':
props.direction = fudge;
break;
case 'scalex':
props.scale = [fudge, scaleY];
scaleX = fudge;
break;
case 'scaley':
props.scale = [scaleX, fudge];
scaleY = fudge;
break;
case 'color':
props.color = fudge;
break;
case 'whirl':
props.whirl = fudge;
break;
case 'fisheye':
props.fisheye = fudge;
break;
case 'pixelate':
props.pixelate = fudge;
break;
case 'mosaic':
props.mosaic = fudge;
break;
case 'brightness':
props.brightness = fudge;
break;
case 'ghost':
props.ghost = fudge;
break;
}
renderer.updateDrawableProperties(drawableID2, props);
};
fudgeInput.addEventListener('input', handleFudgeChanged);
fudgeInput.addEventListener('change', handleFudgeChanged);
canvas.addEventListener('mousemove', event => {
var mousePos = getMousePosition(event, canvas);
renderer.extractColor(mousePos.x, mousePos.y, 30);
});
canvas.addEventListener('click', event => {
var mousePos = getMousePosition(event, canvas);
var pickID = renderer.pick(mousePos.x, mousePos.y);
console.log('You clicked on ' + (pickID < 0 ? 'nothing' : 'ID# ' + pickID));
if (pickID >= 0) {
console.dir(renderer.extractDrawable(pickID, mousePos.x, mousePos.y));
}
});
const drawStep = function () {
renderer.draw();
// renderer.getBounds(drawableID2);
// renderer.isTouchingColor(drawableID2, [255,255,255]);
requestAnimationFrame(drawStep);
};
drawStep();
var debugCanvas = /** @type {canvas} */ document.getElementById('debug-canvas');
renderer.setDebugCanvas(debugCanvas);

View File

@@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Scratch WebGL Query Playground</title>
<style>
input[type=range][orient=vertical] {
writing-mode: bt-lr; /* IE */
-webkit-appearance: slider-vertical;
width: 1rem;
padding: 0 0.5rem;
}
canvas {
border: 3px dashed black;
/* https://stackoverflow.com/a/7665647 */
image-rendering: optimizeSpeed; /* Older versions of FF */
image-rendering: -moz-crisp-edges; /* FF 6.0+ */
image-rendering: -webkit-optimize-contrast; /* Safari */
image-rendering: -o-crisp-edges; /* OS X & Windows Opera (12.02+) */
image-rendering: pixelated; /* Awesome future-browsers */
-ms-interpolation-mode: nearest-neighbor; /* IE */
}
</style>
</head>
<body style="background: lightsteelblue">
<div>
<fieldset>
<legend>Query Canvases</legend>
<table>
<tr>
<td>
<fieldset>
<legend>GPU</legend>
<div>Touching color A? <span id="gpuTouchingA">maybe</span></div>
<div>Touching color B? <span id="gpuTouchingB">maybe</span></div>
<canvas id="gpuQueryCanvas" width="480" height="360" style="height: 20rem"></canvas>
</fieldset>
</td>
<td>
<fieldset>
<legend>CPU</legend>
<div>Touching color A? <span id="cpuTouchingA">maybe</span></div>
<div>Touching color B? <span id="cpuTouchingB">maybe</span></div>
<canvas id="cpuQueryCanvas" width="480" height="360" style="height: 20rem"></canvas>
</fieldset>
</td>
</tr>
</table>
</fieldset>
<fieldset>
<legend>Render Canvas</legend>
<div>Cursor Position: <span id="cursorPosition">somewhere</span></div>
<table>
<tr>
<td></td>
<td>
<input id="cursorX" type="range" step="0.25" value="0" />
</td>
</tr>
<tr>
<td>
<input id="cursorY" type="range" orient="vertical" step="0.25" value="0" />
</td>
<td>
<canvas id="renderCanvas" width="480" height="360" style="border:3px dashed black"></canvas>
</td>
</tr>
</table>
</fieldset>
</div>
</body>
<script src="queryPlayground.js"></script>
</html>

View File

@@ -0,0 +1,196 @@
const ScratchRender = require('../RenderWebGL');
const getMousePosition = require('./getMousePosition');
const renderCanvas = document.getElementById('renderCanvas');
const gpuQueryCanvas = document.getElementById('gpuQueryCanvas');
const cpuQueryCanvas = document.getElementById('cpuQueryCanvas');
const inputCursorX = document.getElementById('cursorX');
const inputCursorY = document.getElementById('cursorY');
const labelCursorPosition = document.getElementById('cursorPosition');
const labelGpuTouchingA = document.getElementById('gpuTouchingA');
const labelGpuTouchingB = document.getElementById('gpuTouchingB');
const labelCpuTouchingA = document.getElementById('cpuTouchingA');
const labelCpuTouchingB = document.getElementById('cpuTouchingB');
const drawables = {
testPattern: -1,
cursor: -1
};
const colors = {
cursor: [255, 0, 0],
patternA: [0, 255, 0],
patternB: [0, 0, 255]
};
const renderer = new ScratchRender(renderCanvas);
const handleResizeRenderCanvas = () => {
const halfWidth = renderCanvas.clientWidth / 2;
const halfHeight = renderCanvas.clientHeight / 2;
inputCursorX.style.width = `${renderCanvas.clientWidth}px`;
inputCursorY.style.height = `${renderCanvas.clientHeight}px`;
inputCursorX.min = -halfWidth;
inputCursorX.max = halfWidth;
inputCursorY.min = -halfHeight;
inputCursorY.max = halfHeight;
};
renderCanvas.addEventListener('resize', handleResizeRenderCanvas);
handleResizeRenderCanvas();
const handleCursorPositionChanged = () => {
const devicePixelRatio = window.devicePixelRatio || 1;
const cursorX = inputCursorX.valueAsNumber / devicePixelRatio;
const cursorY = inputCursorY.valueAsNumber / devicePixelRatio;
const positionHTML = `${cursorX}, ${cursorY}`;
labelCursorPosition.innerHTML = positionHTML;
if (drawables.cursor >= 0) {
renderer.draw();
renderer.updateDrawableProperties(drawables.cursor, {
position: [cursorX, cursorY]
});
renderer.setUseGpuMode(ScratchRender.UseGpuModes.ForceGPU);
renderer.setDebugCanvas(gpuQueryCanvas);
const isGpuTouchingA = renderer.isTouchingColor(drawables.cursor, colors.patternA);
const isGpuTouchingB = renderer.isTouchingColor(drawables.cursor, colors.patternB);
labelGpuTouchingA.innerHTML = isGpuTouchingA ? 'yes' : 'no';
labelGpuTouchingB.innerHTML = isGpuTouchingB ? 'yes' : 'no';
renderer.setUseGpuMode(ScratchRender.UseGpuModes.ForceCPU);
renderer.setDebugCanvas(cpuQueryCanvas);
const isCpuTouchingA = renderer.isTouchingColor(drawables.cursor, colors.patternA);
const isCpuTouchingB = renderer.isTouchingColor(drawables.cursor, colors.patternB);
labelCpuTouchingA.innerHTML = isCpuTouchingA ? 'yes' : 'no';
labelCpuTouchingB.innerHTML = isCpuTouchingB ? 'yes' : 'no';
renderer.setUseGpuMode(ScratchRender.UseGpuModes.Automatic);
}
};
inputCursorX.addEventListener('change', handleCursorPositionChanged);
inputCursorY.addEventListener('change', handleCursorPositionChanged);
inputCursorX.addEventListener('input', handleCursorPositionChanged);
inputCursorY.addEventListener('input', handleCursorPositionChanged);
handleCursorPositionChanged();
let trackingMouse = true;
const handleMouseMove = event => {
if (trackingMouse) {
const mousePosition = getMousePosition(event, renderCanvas);
inputCursorX.value = mousePosition.x - (renderCanvas.clientWidth / 2);
inputCursorY.value = (renderCanvas.clientHeight / 2) - mousePosition.y;
handleCursorPositionChanged();
}
};
renderCanvas.addEventListener('mousemove', handleMouseMove);
renderCanvas.addEventListener('click', event => {
trackingMouse = !trackingMouse;
if (trackingMouse) {
handleMouseMove(event);
}
});
const rgb2fillStyle = (rgb) => {
return `rgb(${rgb[0]},${rgb[1]},${rgb[2]})`;
};
const makeCursorImage = () => {
const canvas = document.createElement('canvas');
canvas.width = canvas.height = 1;
const context = canvas.getContext('2d');
context.fillStyle = rgb2fillStyle(colors.cursor);
context.fillRect(0, 0, 1, 1);
return canvas;
};
const makeTestPatternImage = () => {
const canvas = document.createElement('canvas');
canvas.width = 480;
canvas.height = 360;
const patternA = rgb2fillStyle(colors.patternA);
const patternB = rgb2fillStyle(colors.patternB);
const context = canvas.getContext('2d');
context.fillStyle = patternA;
context.fillRect(0, 0, canvas.width, canvas.height);
context.fillStyle = patternB;
const xSplit1 = Math.floor(canvas.width * 0.25);
const xSplit2 = Math.floor(canvas.width * 0.5);
const xSplit3 = Math.floor(canvas.width * 0.75);
const ySplit = Math.floor(canvas.height * 0.5);
for (let y = 0; y < ySplit; y += 2) {
context.fillRect(0, y, xSplit2, 1);
}
for (let x = xSplit2; x < canvas.width; x += 2) {
context.fillRect(x, 0, 1, ySplit);
}
for (let x = 0; x < xSplit1; x += 2) {
for (let y = ySplit; y < canvas.height; y += 2) {
context.fillRect(x, y, 1, 1);
}
}
for (let x = xSplit1; x < xSplit2; x += 3) {
for (let y = ySplit; y < canvas.height; y += 3) {
context.fillRect(x, y, 2, 2);
}
}
for (let x = xSplit2; x < xSplit3; ++x) {
for (let y = ySplit; y < canvas.height; ++y) {
context.fillStyle = (x + y) % 2 ? patternB : patternA;
context.fillRect(x, y, 1, 1);
}
}
for (let x = xSplit3; x < canvas.width; x += 2) {
for (let y = ySplit; y < canvas.height; y += 2) {
context.fillStyle = (x + y) % 4 ? patternB : patternA;
context.fillRect(x, y, 2, 2);
}
}
return canvas;
};
const makeTestPatternDrawable = function (group) {
const image = makeTestPatternImage();
const skinId = renderer.createBitmapSkin(image, 1);
const drawableId = renderer.createDrawable(group);
renderer.updateDrawableProperties(drawableId, {skinId});
return drawableId;
};
const makeCursorDrawable = function (group) {
const image = makeCursorImage();
const skinId = renderer.createBitmapSkin(image, 1, [0, 0]);
const drawableId = renderer.createDrawable(group);
renderer.updateDrawableProperties(drawableId, {skinId});
return drawableId;
};
const initRendering = () => {
const layerGroup = {
testPattern: 'testPattern',
cursor: 'cursor'
};
renderer.setLayerGroupOrdering([layerGroup.testPattern, layerGroup.cursor]);
drawables.testPattern = makeTestPatternDrawable(layerGroup.testPattern);
drawables.cursor = makeCursorDrawable(layerGroup.cursor);
const corner00 = makeCursorDrawable(layerGroup.cursor);
const corner01 = makeCursorDrawable(layerGroup.cursor);
const corner10 = makeCursorDrawable(layerGroup.cursor);
const corner11 = makeCursorDrawable(layerGroup.cursor);
renderer.updateDrawableProperties(corner00, {position: [-240, -179]});
renderer.updateDrawableProperties(corner01, {position: [-240, 180]});
renderer.updateDrawableProperties(corner10, {position: [239, -179]});
renderer.updateDrawableProperties(corner11, {position: [239, 180]});
};
initRendering();
renderer.draw();

View File

@@ -45,13 +45,13 @@ uniform sampler2D u_skin;
varying vec2 v_texCoord;
#if !defined(DRAW_MODE_silhouette) && (defined(ENABLE_color) || defined(ENABLE_brightness))
#if !defined(DRAW_MODE_silhouette) && (defined(ENABLE_color))
// Branchless color conversions based on code from:
// http://www.chilliant.com/rgb2hsv.html by Ian Taylor
// Based in part on work by Sam Hocevar and Emil Persson
// See also: https://en.wikipedia.org/wiki/HSL_and_HSV#Formal_derivation
// Smaller values can cause problems with "color" and "brightness" effects on some mobile devices
// Smaller values can cause problems on some mobile devices
const float epsilon = 1e-3;
// Convert an RGB color to Hue, Saturation, and Value.
@@ -103,7 +103,7 @@ vec3 convertHSV2RGB(vec3 hsv)
float c = hsv.z * hsv.y;
return rgb * c + hsv.z - c;
}
#endif // !defined(DRAW_MODE_silhouette) && (defined(ENABLE_color) || defined(ENABLE_brightness))
#endif // !defined(DRAW_MODE_silhouette) && (defined(ENABLE_color))
const vec2 kCenter = vec2(0.5, 0.5);
@@ -164,31 +164,27 @@ void main()
gl_FragColor = u_silhouetteColor;
#else // DRAW_MODE_silhouette
#if defined(ENABLE_color) || defined(ENABLE_brightness)
#if defined(ENABLE_color)
{
vec3 hsv = convertRGB2HSV(gl_FragColor.xyz);
#ifdef ENABLE_color
{
// this code forces grayscale values to be slightly saturated
// so that some slight change of hue will be visible
const float minLightness = 0.11 / 2.0;
const float minSaturation = 0.09;
if (hsv.z < minLightness) hsv = vec3(0.0, 1.0, minLightness);
else if (hsv.y < minSaturation) hsv = vec3(0.0, minSaturation, hsv.z);
// this code forces grayscale values to be slightly saturated
// so that some slight change of hue will be visible
const float minLightness = 0.11 / 2.0;
const float minSaturation = 0.09;
if (hsv.z < minLightness) hsv = vec3(0.0, 1.0, minLightness);
else if (hsv.y < minSaturation) hsv = vec3(0.0, minSaturation, hsv.z);
hsv.x = mod(hsv.x + u_color, 1.0);
if (hsv.x < 0.0) hsv.x += 1.0;
}
#endif // ENABLE_color
#ifdef ENABLE_brightness
hsv.z = clamp(hsv.z + u_brightness, 0.0, 1.0);
#endif // ENABLE_brightness
hsv.x = mod(hsv.x + u_color, 1.0);
if (hsv.x < 0.0) hsv.x += 1.0;
gl_FragColor.rgb = convertHSV2RGB(hsv);
}
#endif // defined(ENABLE_color) || defined(ENABLE_brightness)
#endif // defined(ENABLE_color)
#if defined(ENABLE_brightness)
gl_FragColor.rgb = clamp(gl_FragColor.rgb + vec3(u_brightness), vec3(0), vec3(1));
#endif // defined(ENABLE_brightness)
#ifdef DRAW_MODE_colorMask
vec3 maskDistance = abs(gl_FragColor.rgb - u_colorMask);

View File

@@ -114,7 +114,7 @@ const testBubbles = () => test('bubble snapshot', async t => {
// immediately invoked async function to let us wait for each test to finish before starting the next.
(async () => {
const files = fs.readdirSync(testDir())
.filter(uri => uri.endsWith('.sb2') || uri.endsWidth('.sb3'));
.filter(uri => uri.endsWith('.sb2') || uri.endsWith('.sb3'));
for (const file of files) {
await testFile(file);

View File

@@ -39,10 +39,10 @@ module.exports = [
Object.assign({}, base, {
target: 'web',
entry: {
'scratch-render': './src/index.js'
playground: './src/playground/playground.js',
queryPlayground: './src/playground/queryPlayground.js'
},
output: {
library: 'ScratchRender',
libraryTarget: 'umd',
path: path.resolve('playground'),
filename: '[name].js'
@@ -50,7 +50,8 @@ module.exports = [
plugins: base.plugins.concat([
new CopyWebpackPlugin([
{
from: 'src/playground'
context: 'src/playground',
from: '*.html'
}
])
])