Compare commits
30 Commits
greenkeepe
...
greenkeepe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10f5439f7d | ||
|
|
40cbe50238 | ||
|
|
1a2b7a6253 | ||
|
|
077d2f1d69 | ||
|
|
4189377128 | ||
|
|
4ae7e32b8c | ||
|
|
1b86f2ca3a | ||
|
|
1703afbbd0 | ||
|
|
9ab14e34dd | ||
|
|
60210a4a11 | ||
|
|
2683c98469 | ||
|
|
aacbd5e518 | ||
|
|
858bff09e1 | ||
|
|
14813e590c | ||
|
|
5bcbc5414f | ||
|
|
94340deba8 | ||
|
|
bd14d53fb2 | ||
|
|
5f9ca5b4fa | ||
|
|
834c5eb984 | ||
|
|
ef91583603 | ||
|
|
3e084dfe26 | ||
|
|
fdea47d31c | ||
|
|
24737982f0 | ||
|
|
cfaadfcc75 | ||
|
|
997062c851 | ||
|
|
90b1c47c3e | ||
|
|
550cd7aacf | ||
|
|
9b5a33ce3c | ||
|
|
2eca0a8326 | ||
|
|
b901c1ac75 |
@@ -37,12 +37,12 @@
|
||||
"gh-pages": "^1.0.0",
|
||||
"jsdoc": "^3.5.5",
|
||||
"json": "^9.0.4",
|
||||
"scratch-vm": "0.2.0-prerelease.20180824135031",
|
||||
"scratch-vm": "0.2.0-prerelease.20181024204838",
|
||||
"tap": "^11.0.0",
|
||||
"travis-after-all": "^1.4.4",
|
||||
"uglifyjs-webpack-plugin": "^1.2.5",
|
||||
"webpack": "^4.8.0",
|
||||
"webpack-cli": "^2.0.15",
|
||||
"webpack-cli": "^3.1.0",
|
||||
"webpack-dev-server": "^3.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -51,9 +51,9 @@
|
||||
"ify-loader": "1.0.4",
|
||||
"linebreak": "0.3.0",
|
||||
"minilog": "3.1.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"raw-loader": "^1.0.0",
|
||||
"scratch-storage": "^1.0.0",
|
||||
"scratch-svg-renderer": "0.2.0-prerelease.20180817005452",
|
||||
"scratch-svg-renderer": "0.2.0-prerelease.20181126212715",
|
||||
"twgl.js": "4.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,8 +190,8 @@ class Drawable {
|
||||
if ('position' in properties && (
|
||||
this._position[0] !== properties.position[0] ||
|
||||
this._position[1] !== properties.position[1])) {
|
||||
this._position[0] = properties.position[0];
|
||||
this._position[1] = properties.position[1];
|
||||
this._position[0] = Math.round(properties.position[0]);
|
||||
this._position[1] = Math.round(properties.position[1]);
|
||||
dirty = true;
|
||||
}
|
||||
if ('direction' in properties && this._direction !== properties.direction) {
|
||||
|
||||
@@ -178,6 +178,9 @@ class RenderWebGL extends EventEmitter {
|
||||
/** @type {function} */
|
||||
this._exitRegion = null;
|
||||
|
||||
/** @type {Array.<snapshotCallback>} */
|
||||
this._snapshotCallbacks = [];
|
||||
|
||||
this._svgTextBubble = new SVGTextBubble();
|
||||
|
||||
this._createGeometry();
|
||||
@@ -201,6 +204,13 @@ class RenderWebGL extends EventEmitter {
|
||||
return this._gl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {HTMLCanvasElement} the canvas of the WebGL rendering context associated with this renderer.
|
||||
*/
|
||||
get canvas () {
|
||||
return this._gl && this._gl.canvas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the physical size of the stage in device-independent pixels.
|
||||
* This will be multiplied by the device's pixel ratio on high-DPI displays.
|
||||
@@ -589,6 +599,11 @@ class RenderWebGL extends EventEmitter {
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
this._drawThese(this._drawList, ShaderManager.DRAW_MODE.default, this._projection);
|
||||
if (this._snapshotCallbacks.length > 0) {
|
||||
const snapshot = gl.canvas.toDataURL();
|
||||
this._snapshotCallbacks.forEach(cb => cb(snapshot));
|
||||
this._snapshotCallbacks = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -915,6 +930,9 @@ class RenderWebGL extends EventEmitter {
|
||||
const bounds = this.clientSpaceToScratchBounds(centerX, centerY, touchWidth, touchHeight);
|
||||
const worldPos = twgl.v3.create();
|
||||
|
||||
drawable.updateMatrix();
|
||||
drawable.skin.updateSilhouette();
|
||||
|
||||
for (worldPos[1] = bounds.bottom; worldPos[1] <= bounds.top; worldPos[1]++) {
|
||||
for (worldPos[0] = bounds.left; worldPos[0] <= bounds.right; worldPos[0]++) {
|
||||
if (drawable.isTouching(worldPos)) {
|
||||
@@ -953,7 +971,12 @@ class RenderWebGL extends EventEmitter {
|
||||
if (candidateIDs.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bounds = this.clientSpaceToScratchBounds(centerX, centerY, touchWidth, touchHeight);
|
||||
if (bounds.left === -Infinity || bounds.bottom === -Infinity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hits = [];
|
||||
const worldPos = twgl.v3.create(0, 0, 0);
|
||||
// Iterate over the scratch pixels and check if any candidate can be
|
||||
@@ -1704,6 +1727,18 @@ class RenderWebGL extends EventEmitter {
|
||||
dst[2] += blendAlpha * 255;
|
||||
return dst;
|
||||
}
|
||||
|
||||
/**
|
||||
* @callback RenderWebGL#snapshotCallback
|
||||
* @param {string} dataURI Data URI of the snapshot of the renderer
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {snapshotCallback} callback Function called in the next frame with the snapshot data
|
||||
*/
|
||||
requestSnapshot (callback) {
|
||||
this._snapshotCallbacks.push(callback);
|
||||
}
|
||||
}
|
||||
|
||||
// :3
|
||||
|
||||
@@ -54,9 +54,9 @@ varying vec2 v_texCoord;
|
||||
// Smaller values can cause problems with "color" and "brightness" effects on some mobile devices
|
||||
const float epsilon = 1e-3;
|
||||
|
||||
// Convert an RGB color to Hue, Saturation, and Lightness.
|
||||
// Convert an RGB color to Hue, Saturation, and Value.
|
||||
// All components of input and output are expected to be in the [0,1] range.
|
||||
vec3 convertRGB2HSL(vec3 rgb)
|
||||
vec3 convertRGB2HSV(vec3 rgb)
|
||||
{
|
||||
// Hue calculation has 3 cases, depending on which RGB component is largest, and one of those cases involves a "mod"
|
||||
// operation. In order to avoid that "mod" we split the M==R case in two: one for G<B and one for B>G. The B>G case
|
||||
@@ -80,13 +80,13 @@ vec3 convertRGB2HSL(vec3 rgb)
|
||||
// Chroma = M - m
|
||||
float C = temp2.x - m;
|
||||
|
||||
// Lightness = 1/2 * (M + m)
|
||||
float L = 0.5 * (temp2.x + m);
|
||||
// Value = M
|
||||
float V = temp2.x;
|
||||
|
||||
return vec3(
|
||||
abs(temp2.z + (temp2.w - temp2.y) / (6.0 * C + epsilon)), // Hue
|
||||
C / (1.0 - abs(2.0 * L - 1.0) + epsilon), // Saturation
|
||||
L); // Lightness
|
||||
C / (temp2.x + epsilon), // Saturation
|
||||
V); // Value
|
||||
}
|
||||
|
||||
vec3 convertHue2RGB(float hue)
|
||||
@@ -97,11 +97,11 @@ vec3 convertHue2RGB(float hue)
|
||||
return clamp(vec3(r, g, b), 0.0, 1.0);
|
||||
}
|
||||
|
||||
vec3 convertHSL2RGB(vec3 hsl)
|
||||
vec3 convertHSV2RGB(vec3 hsv)
|
||||
{
|
||||
vec3 rgb = convertHue2RGB(hsl.x);
|
||||
float c = (1.0 - abs(2.0 * hsl.z - 1.0)) * hsl.y;
|
||||
return (rgb - 0.5) * c + hsl.z;
|
||||
vec3 rgb = convertHue2RGB(hsv.x);
|
||||
float c = hsv.z * hsv.y;
|
||||
return rgb * c + hsv.z - c;
|
||||
}
|
||||
#endif // !defined(DRAW_MODE_silhouette) && (defined(ENABLE_color) || defined(ENABLE_brightness))
|
||||
|
||||
@@ -166,7 +166,7 @@ void main()
|
||||
|
||||
#if defined(ENABLE_color) || defined(ENABLE_brightness)
|
||||
{
|
||||
vec3 hsl = convertRGB2HSL(gl_FragColor.xyz);
|
||||
vec3 hsv = convertRGB2HSV(gl_FragColor.xyz);
|
||||
|
||||
#ifdef ENABLE_color
|
||||
{
|
||||
@@ -174,19 +174,19 @@ void main()
|
||||
// so that some slight change of hue will be visible
|
||||
const float minLightness = 0.11 / 2.0;
|
||||
const float minSaturation = 0.09;
|
||||
if (hsl.z < minLightness) hsl = vec3(0.0, 1.0, minLightness);
|
||||
else if (hsl.y < minSaturation) hsl = vec3(0.0, minSaturation, hsl.z);
|
||||
if (hsv.z < minLightness) hsv = vec3(0.0, 1.0, minLightness);
|
||||
else if (hsv.y < minSaturation) hsv = vec3(0.0, minSaturation, hsv.z);
|
||||
|
||||
hsl.x = mod(hsl.x + u_color, 1.0);
|
||||
if (hsl.x < 0.0) hsl.x += 1.0;
|
||||
hsv.x = mod(hsv.x + u_color, 1.0);
|
||||
if (hsv.x < 0.0) hsv.x += 1.0;
|
||||
}
|
||||
#endif // ENABLE_color
|
||||
|
||||
#ifdef ENABLE_brightness
|
||||
hsl.z = clamp(hsl.z + u_brightness, 0.0, 1.0);
|
||||
hsv.z = clamp(hsv.z + u_brightness, 0.0, 1.0);
|
||||
#endif // ENABLE_brightness
|
||||
|
||||
gl_FragColor.rgb = convertHSL2RGB(hsl);
|
||||
gl_FragColor.rgb = convertHSV2RGB(hsv);
|
||||
}
|
||||
#endif // defined(ENABLE_color) || defined(ENABLE_brightness)
|
||||
|
||||
|
||||
@@ -145,11 +145,16 @@ class SVGTextBubble {
|
||||
</g>`;
|
||||
}
|
||||
|
||||
|
||||
_getTextSize () {
|
||||
const svgString = this._wrapSvgFragment(this._textFragment);
|
||||
_getTextSize (textFragment) {
|
||||
const svgString = this._wrapSvgFragment(textFragment);
|
||||
if (!this._textSizeCache[svgString]) {
|
||||
this._textSizeCache[svgString] = this.svgRenderer.measure(svgString);
|
||||
if (this._textSizeCache[svgString].height === 0) {
|
||||
// The speech bubble is empty, so use the height of a single line with content (or else it renders
|
||||
// weirdly, see issue #302).
|
||||
const dummyFragment = this._buildTextFragment('X');
|
||||
this._textSizeCache[svgString] = this._getTextSize(dummyFragment);
|
||||
}
|
||||
}
|
||||
return this._textSizeCache[svgString];
|
||||
}
|
||||
@@ -183,7 +188,7 @@ class SVGTextBubble {
|
||||
let fragment = '';
|
||||
|
||||
const radius = 16;
|
||||
const {x, y, width, height} = this._getTextSize();
|
||||
const {x, y, width, height} = this._getTextSize(this._textFragment);
|
||||
const padding = 10;
|
||||
const fullWidth = Math.max(MIN_WIDTH, width) + (2 * padding);
|
||||
const fullHeight = height + (2 * padding);
|
||||
|
||||
@@ -7,55 +7,94 @@ const chromeless = new Chromeless();
|
||||
const indexHTML = path.resolve(__dirname, 'index.html');
|
||||
const testDir = (...args) => path.resolve(__dirname, 'pick-tests', ...args);
|
||||
|
||||
const runFile = (file, script) =>
|
||||
const runFile = (file, action, script) =>
|
||||
// start each test by going to the index.html, and loading the scratch file
|
||||
chromeless.goto(`file://${indexHTML}`)
|
||||
.setFileInput('#file', testDir(file))
|
||||
// the index.html handler for file input will add a #loaded element when it
|
||||
// finishes.
|
||||
.wait('#loaded')
|
||||
.evaluate(script)
|
||||
.evaluate(`function () {return (${script})(${action});}`)
|
||||
;
|
||||
|
||||
// immediately invoked async function to let us wait for each test to finish before starting the next.
|
||||
(async () => {
|
||||
|
||||
await test('pick tests', async t => {
|
||||
const testOperation = async function (name, action, expect) {
|
||||
await test(name, async t => {
|
||||
|
||||
const results = await runFile('test-mouse-touch.sb2', () => {
|
||||
vm.greenFlag();
|
||||
const sendResults = [];
|
||||
const results = await runFile('test-mouse-touch.sb2', action, boundAction => {
|
||||
vm.greenFlag();
|
||||
const sendResults = [];
|
||||
|
||||
const idToTargetName = id => {
|
||||
const target = vm.runtime.targets.find(tar => tar.drawableID === id);
|
||||
if (!target) {
|
||||
return `[Unknown drawableID: ${id}]`;
|
||||
}
|
||||
return target.sprite.name;
|
||||
};
|
||||
const sprite = vm.runtime.targets.find(target => target.sprite.name === 'Sprite1');
|
||||
const idToTargetName = id => {
|
||||
const target = vm.runtime.targets.find(tar => tar.drawableID === id);
|
||||
if (!target) {
|
||||
return `[Unknown drawableID: ${id}]`;
|
||||
}
|
||||
return target.sprite.name;
|
||||
};
|
||||
const sprite = vm.runtime.targets.find(target => target.sprite.name === 'Sprite1');
|
||||
|
||||
sendResults.push(['center', idToTargetName(render.pick(240, 180))]);
|
||||
sendResults.push(['left', idToTargetName(render.pick(200, 180))]);
|
||||
sendResults.push(['over', render.drawableTouching(sprite.drawableID, 240, 180)]);
|
||||
sprite.setVisible(false);
|
||||
sendResults.push(['hidden sprite pick center', idToTargetName(render.pick(240, 180))]);
|
||||
sendResults.push(['hidden over', render.drawableTouching(sprite.drawableID, 240, 180)]);
|
||||
return sendResults;
|
||||
boundAction({
|
||||
sendResults,
|
||||
idToTargetName,
|
||||
render,
|
||||
sprite
|
||||
});
|
||||
return sendResults;
|
||||
});
|
||||
|
||||
t.plan(expect.length);
|
||||
for (let x = 0; x < expect.length; x++) {
|
||||
t.deepEqual(results[x], expect[x], expect[x][0]);
|
||||
}
|
||||
t.end();
|
||||
});
|
||||
const expect = [
|
||||
['center', 'Sprite1'],
|
||||
['left', 'Stage'],
|
||||
['over', true],
|
||||
['hidden sprite pick center', 'Stage'],
|
||||
['hidden over', true]
|
||||
];
|
||||
t.plan(expect.length);
|
||||
for (let x = 0; x < expect.length; x++) {
|
||||
t.deepEqual(results[x], expect[x], expect[x][0]);
|
||||
};
|
||||
|
||||
const tests = [
|
||||
{
|
||||
name: 'pick Sprite1',
|
||||
action: ({sendResults, render, idToTargetName}) => {
|
||||
sendResults.push(['center', idToTargetName(render.pick(360, 180))]);
|
||||
},
|
||||
expect: [['center', 'Sprite1']]
|
||||
},
|
||||
{
|
||||
name: 'pick Stage',
|
||||
action: ({sendResults, render, idToTargetName}) => {
|
||||
sendResults.push(['left', idToTargetName(render.pick(320, 180))]);
|
||||
},
|
||||
expect: [['left', 'Stage']]
|
||||
},
|
||||
{
|
||||
name: 'touching Sprite1',
|
||||
action: ({sprite, sendResults, render}) => {
|
||||
sendResults.push(['over', render.drawableTouching(sprite.drawableID, 360, 180)]);
|
||||
},
|
||||
expect: [['over', true]]
|
||||
},
|
||||
{
|
||||
name: 'pick Stage through hidden Sprite1',
|
||||
action: ({sprite, sendResults, render, idToTargetName}) => {
|
||||
sprite.setVisible(false);
|
||||
sendResults.push(['hidden sprite pick center', idToTargetName(render.pick(360, 180))]);
|
||||
},
|
||||
expect: [['hidden sprite pick center', 'Stage']]
|
||||
},
|
||||
{
|
||||
name: 'touching hidden Sprite1',
|
||||
action: ({sprite, sendResults, render}) => {
|
||||
sprite.setVisible(false);
|
||||
sendResults.push(['hidden over', render.drawableTouching(sprite.drawableID, 360, 180)]);
|
||||
},
|
||||
expect: [['hidden over', true]]
|
||||
}
|
||||
t.end();
|
||||
});
|
||||
];
|
||||
for (const {name, action, expect} of tests) {
|
||||
await testOperation(name, action, expect);
|
||||
}
|
||||
|
||||
// close the browser window we used
|
||||
await chromeless.end();
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user