Merge branch 'develop' into greenkeeper/twgl.js-3.5.0
This commit is contained in:
commit
1e54b1c894
@ -1,6 +1,6 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- "4.2"
|
- 6
|
||||||
- "node"
|
- "node"
|
||||||
sudo: false
|
sudo: false
|
||||||
cache:
|
cache:
|
||||||
|
|||||||
13
package.json
13
package.json
@ -17,22 +17,23 @@
|
|||||||
"prepublish": "npm run build",
|
"prepublish": "npm run build",
|
||||||
"prepublish-watch": "npm run watch",
|
"prepublish-watch": "npm run watch",
|
||||||
"start": "webpack-dev-server",
|
"start": "webpack-dev-server",
|
||||||
"test": "npm run lint && npm run docs",
|
"tap": "./node_modules/.bin/tap ./test/unit/*.js",
|
||||||
|
"test": "npm run lint && npm run docs && npm run tap",
|
||||||
"version": "json -f package.json -I -e \"this.repository.sha = '$(git log -n1 --pretty=format:%H)'\"",
|
"version": "json -f package.json -I -e \"this.repository.sha = '$(git log -n1 --pretty=format:%H)'\"",
|
||||||
"watch": "webpack --progress --colors --watch --watch-poll"
|
"watch": "webpack --progress --colors --watch --watch-poll"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-core": "^6.23.1",
|
"babel-core": "^6.23.1",
|
||||||
"babel-eslint": "^7.1.1",
|
"babel-eslint": "^7.1.1",
|
||||||
"babel-loader": "^6.3.2",
|
"babel-loader": "^7.1.2",
|
||||||
"babel-polyfill": "^6.22.0",
|
"babel-polyfill": "^6.22.0",
|
||||||
"babel-preset-es2015": "^6.22.0",
|
"babel-preset-es2015": "^6.22.0",
|
||||||
"base64-loader": "^1.0.0",
|
"base64-loader": "^1.0.0",
|
||||||
"copy-webpack-plugin": "^4.0.1",
|
"copy-webpack-plugin": "^4.0.1",
|
||||||
"docdash": "^0.4.0",
|
"docdash": "^0.4.0",
|
||||||
"eslint": "^3.16.1",
|
"eslint": "^4.6.1",
|
||||||
"eslint-config-scratch": "^3.1.0",
|
"eslint-config-scratch": "^4.0.0",
|
||||||
"gh-pages": "^0.12.0",
|
"gh-pages": "^1.0.0",
|
||||||
"hull.js": "0.2.10",
|
"hull.js": "0.2.10",
|
||||||
"jsdoc": "^3.5.3",
|
"jsdoc": "^3.5.3",
|
||||||
"json": "^9.0.4",
|
"json": "^9.0.4",
|
||||||
@ -41,7 +42,7 @@
|
|||||||
"tap": "^10.3.0",
|
"tap": "^10.3.0",
|
||||||
"travis-after-all": "^1.4.4",
|
"travis-after-all": "^1.4.4",
|
||||||
"twgl.js": "3.5.0",
|
"twgl.js": "3.5.0",
|
||||||
"webpack": "^2.2.1",
|
"webpack": "^3.5.6",
|
||||||
"webpack-dev-server": "^2.4.1"
|
"webpack-dev-server": "^2.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -201,7 +201,7 @@ class Drawable {
|
|||||||
twgl.m4.rotateZ(modelMatrix, rotation, modelMatrix);
|
twgl.m4.rotateZ(modelMatrix, rotation, modelMatrix);
|
||||||
|
|
||||||
// Adjust rotation center relative to the skin.
|
// Adjust rotation center relative to the skin.
|
||||||
var rotationAdjusted = twgl.v3.subtract(this.skin.rotationCenter, twgl.v3.divScalar(this.skin.size, 2));
|
let rotationAdjusted = twgl.v3.subtract(this.skin.rotationCenter, twgl.v3.divScalar(this.skin.size, 2));
|
||||||
rotationAdjusted = twgl.v3.multiply(rotationAdjusted, this.scale);
|
rotationAdjusted = twgl.v3.multiply(rotationAdjusted, this.scale);
|
||||||
rotationAdjusted = twgl.v3.divScalar(rotationAdjusted, 100);
|
rotationAdjusted = twgl.v3.divScalar(rotationAdjusted, 100);
|
||||||
rotationAdjusted[1] *= -1; // Y flipped to Scratch coordinate.
|
rotationAdjusted[1] *= -1; // Y flipped to Scratch coordinate.
|
||||||
|
|||||||
@ -189,7 +189,7 @@ class PenSkin extends Skin {
|
|||||||
const r = Math.round(color4f[0] * 255);
|
const r = Math.round(color4f[0] * 255);
|
||||||
const g = Math.round(color4f[1] * 255);
|
const g = Math.round(color4f[1] * 255);
|
||||||
const b = Math.round(color4f[2] * 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)
|
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.strokeStyle = `rgba(${r},${g},${b},${a})`;
|
||||||
context.lineCap = 'round';
|
context.lineCap = 'round';
|
||||||
|
|||||||
@ -22,7 +22,7 @@ module.exports = {
|
|||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
Events: {
|
Events: {
|
||||||
/**
|
/**
|
||||||
* NativeSizeChanged event
|
* NativeSizeChanged event
|
||||||
*
|
*
|
||||||
* @event RenderWebGL#event:NativeSizeChanged
|
* @event RenderWebGL#event:NativeSizeChanged
|
||||||
|
|||||||
@ -5,6 +5,7 @@ const twgl = require('twgl.js');
|
|||||||
|
|
||||||
const BitmapSkin = require('./BitmapSkin');
|
const BitmapSkin = require('./BitmapSkin');
|
||||||
const Drawable = require('./Drawable');
|
const Drawable = require('./Drawable');
|
||||||
|
const Rectangle = require('./Rectangle');
|
||||||
const PenSkin = require('./PenSkin');
|
const PenSkin = require('./PenSkin');
|
||||||
const RenderConstants = require('./RenderConstants');
|
const RenderConstants = require('./RenderConstants');
|
||||||
const ShaderManager = require('./ShaderManager');
|
const ShaderManager = require('./ShaderManager');
|
||||||
@ -222,6 +223,33 @@ class RenderWebGL extends EventEmitter {
|
|||||||
return skinId;
|
return skinId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an existing SVG skin, or create an SVG skin if the previous skin was not SVG.
|
||||||
|
* @param {!int} skinId the ID for the skin to change.
|
||||||
|
* @param {!string} svgData - new SVG to use.
|
||||||
|
* @param {?Array<number>} rotationCenter Optional: rotation center of the skin. If not supplied, the center of the
|
||||||
|
* skin will be used
|
||||||
|
*/
|
||||||
|
updateSVGSkin (skinId, svgData, rotationCenter) {
|
||||||
|
if (this._allSkins[skinId] instanceof SVGSkin) {
|
||||||
|
this._allSkins[skinId].setSVG(svgData, rotationCenter);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newSkin = new SVGSkin(skinId, this);
|
||||||
|
newSkin.setSVG(svgData, rotationCenter);
|
||||||
|
const oldSkin = this._allSkins[skinId];
|
||||||
|
this._allSkins[skinId] = newSkin;
|
||||||
|
|
||||||
|
// Tell drawables to update
|
||||||
|
for (const drawable of this._allDrawables) {
|
||||||
|
if (drawable && drawable.skin === oldSkin) {
|
||||||
|
drawable.skin = newSkin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
oldSkin.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroy an existing skin. Do not use the skin or its ID after calling this.
|
* Destroy an existing skin. Do not use the skin or its ID after calling this.
|
||||||
* @param {!int} skinId - The ID of the skin to destroy.
|
* @param {!int} skinId - The ID of the skin to destroy.
|
||||||
@ -684,6 +712,74 @@ class RenderWebGL extends EventEmitter {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef ColorExtraction
|
||||||
|
* @property {Uint8Array} data Raw pixel data for the drawable
|
||||||
|
* @property {int} width Drawable bounding box width
|
||||||
|
* @property {int} height Drawable bounding box height
|
||||||
|
* @property {object} color Color object with RGBA properties at picked location
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return drawable pixel data and color at a given position
|
||||||
|
* @param {int} x The client x coordinate of the picking location.
|
||||||
|
* @param {int} y The client y coordinate of the picking location.
|
||||||
|
* @param {int} radius The client radius to extract pixels with.
|
||||||
|
* @return {?ColorExtraction} Data about the picked color
|
||||||
|
*/
|
||||||
|
extractColor (x, y, radius) {
|
||||||
|
const scratchX = Math.round(this._nativeSize[0] * ((x / this._gl.canvas.clientWidth) - 0.5));
|
||||||
|
const scratchY = Math.round(-this._nativeSize[1] * ((y / this._gl.canvas.clientHeight) - 0.5));
|
||||||
|
|
||||||
|
const gl = this._gl;
|
||||||
|
twgl.bindFramebufferInfo(gl, this._queryBufferInfo);
|
||||||
|
|
||||||
|
const bounds = new Rectangle();
|
||||||
|
bounds.initFromBounds(scratchX - radius, scratchX + radius, scratchY - radius, scratchY + radius);
|
||||||
|
|
||||||
|
const pickX = scratchX - bounds.left;
|
||||||
|
const pickY = bounds.top - scratchY;
|
||||||
|
|
||||||
|
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._backgroundColor);
|
||||||
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
|
this._drawThese(this._drawList, ShaderManager.DRAW_MODE.default, projection);
|
||||||
|
|
||||||
|
const data = new Uint8Array(Math.floor(bounds.width * bounds.height * 4));
|
||||||
|
gl.readPixels(0, 0, bounds.width, bounds.height, gl.RGBA, gl.UNSIGNED_BYTE, data);
|
||||||
|
|
||||||
|
const pixelBase = Math.floor(4 * ((pickY * bounds.width) + pickX));
|
||||||
|
const color = {
|
||||||
|
r: data[pixelBase],
|
||||||
|
g: data[pixelBase + 1],
|
||||||
|
b: data[pixelBase + 2],
|
||||||
|
a: data[pixelBase + 3]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this._debugCanvas) {
|
||||||
|
this._debugCanvas.width = bounds.width;
|
||||||
|
this._debugCanvas.height = bounds.height;
|
||||||
|
const ctx = this._debugCanvas.getContext('2d');
|
||||||
|
const imageData = ctx.createImageData(bounds.width, bounds.height);
|
||||||
|
imageData.data.set(data);
|
||||||
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
ctx.strokeStyle = 'black';
|
||||||
|
ctx.fillStyle = `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`;
|
||||||
|
ctx.rect(pickX - 4, pickY - 4, 8, 8);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: data,
|
||||||
|
width: bounds.width,
|
||||||
|
height: bounds.height,
|
||||||
|
color: color
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the candidate bounding box for a touching query.
|
* Get the candidate bounding box for a touching query.
|
||||||
* @param {int} drawableID ID for drawable of query.
|
* @param {int} drawableID ID for drawable of query.
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
const twgl = require('twgl.js');
|
const twgl = require('twgl.js');
|
||||||
|
|
||||||
const vertexShaderText = require('raw-loader!./shaders/sprite.vert');
|
|
||||||
const fragmentShaderText = require('raw-loader!./shaders/sprite.frag');
|
|
||||||
|
|
||||||
|
|
||||||
class ShaderManager {
|
class ShaderManager {
|
||||||
/**
|
/**
|
||||||
@ -65,8 +62,11 @@ class ShaderManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const definesText = `${defines.join('\n')}\n`;
|
const definesText = `${defines.join('\n')}\n`;
|
||||||
const vsFullText = definesText + vertexShaderText;
|
|
||||||
const fsFullText = definesText + fragmentShaderText;
|
/* eslint-disable global-require */
|
||||||
|
const vsFullText = definesText + require('raw-loader!./shaders/sprite.vert');
|
||||||
|
const fsFullText = definesText + require('raw-loader!./shaders/sprite.frag');
|
||||||
|
/* eslint-enable global-require */
|
||||||
|
|
||||||
return twgl.createProgramInfo(this._gl, [vsFullText, fsFullText]);
|
return twgl.createProgramInfo(this._gl, [vsFullText, fsFullText]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -126,6 +126,11 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canvas.onmousemove = function(event) {
|
||||||
|
var mousePos = getMousePos(event, canvas);
|
||||||
|
renderer.extractColor(mousePos.x, mousePos.y, 30);
|
||||||
|
};
|
||||||
|
|
||||||
canvas.onclick = function(event) {
|
canvas.onclick = function(event) {
|
||||||
var mousePos = getMousePos(event, canvas);
|
var mousePos = getMousePos(event, canvas);
|
||||||
var pickID = renderer.pick(mousePos.x, mousePos.y);
|
var pickID = renderer.pick(mousePos.x, mousePos.y);
|
||||||
|
|||||||
13
test/fixtures/MockSkin.js
vendored
Normal file
13
test/fixtures/MockSkin.js
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
const Skin = require('../../src/Skin');
|
||||||
|
|
||||||
|
class MockSkin extends Skin {
|
||||||
|
set size (dimensions) {
|
||||||
|
this.dimensions = dimensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
get size () {
|
||||||
|
return this.dimensions || [0, 0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = MockSkin;
|
||||||
142
test/unit/DrawableTests.js
Normal file
142
test/unit/DrawableTests.js
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
const test = require('tap').test;
|
||||||
|
|
||||||
|
// Mock `window` and `document.createElement` for twgl.js.
|
||||||
|
global.window = {};
|
||||||
|
global.document = {
|
||||||
|
createElement: () => ({getContext: () => {}})
|
||||||
|
};
|
||||||
|
|
||||||
|
const Drawable = require('../../src/Drawable');
|
||||||
|
const MockSkin = require('../fixtures/MockSkin');
|
||||||
|
const Rectangle = require('../../src/Rectangle');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Rectangle-like object, with dimensions rounded to the given number
|
||||||
|
* of digits.
|
||||||
|
* @param {Rectangle} rect The source rectangle.
|
||||||
|
* @param {int} decimals The number of decimal points to snap to.
|
||||||
|
* @returns {object} An object with left/right/top/bottom attributes.
|
||||||
|
*/
|
||||||
|
const snapToNearest = function (rect, decimals = 3) {
|
||||||
|
return {
|
||||||
|
left: rect.left.toFixed(decimals),
|
||||||
|
right: rect.right.toFixed(decimals),
|
||||||
|
bottom: rect.bottom.toFixed(decimals),
|
||||||
|
top: rect.top.toFixed(decimals)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
test('translate by position', t => {
|
||||||
|
const expected = new Rectangle();
|
||||||
|
const drawable = new Drawable();
|
||||||
|
drawable.skin = new MockSkin();
|
||||||
|
drawable.skin.size = [200, 50];
|
||||||
|
|
||||||
|
expected.initFromBounds(0, 200, -50, 0);
|
||||||
|
t.same(snapToNearest(drawable.getAABB()), expected);
|
||||||
|
|
||||||
|
drawable.updateProperties({position: [1, 2]});
|
||||||
|
expected.initFromBounds(1, 201, -48, 2);
|
||||||
|
t.same(snapToNearest(drawable.getAABB()), expected);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('translate by costume center', t => {
|
||||||
|
const expected = new Rectangle();
|
||||||
|
const drawable = new Drawable();
|
||||||
|
drawable.skin = new MockSkin();
|
||||||
|
drawable.skin.size = [200, 50];
|
||||||
|
|
||||||
|
drawable.skin.setRotationCenter(1, 0);
|
||||||
|
expected.initFromBounds(-1, 199, -50, 0);
|
||||||
|
t.same(snapToNearest(drawable.getAABB()), expected);
|
||||||
|
|
||||||
|
drawable.skin.setRotationCenter(0, -2);
|
||||||
|
expected.initFromBounds(0, 200, -52, -2);
|
||||||
|
t.same(snapToNearest(drawable.getAABB()), expected);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('translate and rotate', t => {
|
||||||
|
const expected = new Rectangle();
|
||||||
|
const drawable = new Drawable();
|
||||||
|
drawable.skin = new MockSkin();
|
||||||
|
drawable.skin.size = [200, 50];
|
||||||
|
|
||||||
|
drawable.updateProperties({position: [1, 2], direction: 0});
|
||||||
|
expected.initFromBounds(1, 51, 2, 202);
|
||||||
|
t.same(snapToNearest(drawable.getAABB()), expected);
|
||||||
|
|
||||||
|
drawable.updateProperties({direction: 180});
|
||||||
|
expected.initFromBounds(-49, 1, -198, 2);
|
||||||
|
t.same(snapToNearest(drawable.getAABB()), expected);
|
||||||
|
|
||||||
|
drawable.skin.setRotationCenter(100, 25);
|
||||||
|
drawable.updateProperties({direction: 270, position: [0, 0]});
|
||||||
|
expected.initFromBounds(-100, 100, -25, 25);
|
||||||
|
t.same(snapToNearest(drawable.getAABB()), expected);
|
||||||
|
|
||||||
|
drawable.updateProperties({direction: 90});
|
||||||
|
t.same(snapToNearest(drawable.getAABB()), expected);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('rotate by non-right-angles', t => {
|
||||||
|
const expected = new Rectangle();
|
||||||
|
const drawable = new Drawable();
|
||||||
|
drawable.skin = new MockSkin();
|
||||||
|
drawable.skin.size = [10, 10];
|
||||||
|
drawable.skin.setRotationCenter(5, 5);
|
||||||
|
|
||||||
|
expected.initFromBounds(-5, 5, -5, 5);
|
||||||
|
t.same(snapToNearest(drawable.getAABB()), expected);
|
||||||
|
|
||||||
|
drawable.updateProperties({direction: 45});
|
||||||
|
expected.initFromBounds(-7.071, 7.071, -7.071, 7.071);
|
||||||
|
t.same(snapToNearest(drawable.getAABB()), expected);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('scale', t => {
|
||||||
|
const expected = new Rectangle();
|
||||||
|
const drawable = new Drawable();
|
||||||
|
drawable.skin = new MockSkin();
|
||||||
|
drawable.skin.size = [200, 50];
|
||||||
|
|
||||||
|
drawable.updateProperties({scale: [100, 50]});
|
||||||
|
expected.initFromBounds(0, 200, -25, 0);
|
||||||
|
t.same(snapToNearest(drawable.getAABB()), expected);
|
||||||
|
|
||||||
|
drawable.skin.setRotationCenter(0, 25);
|
||||||
|
expected.initFromBounds(0, 200, -12.5, 12.5);
|
||||||
|
t.same(snapToNearest(drawable.getAABB()), expected);
|
||||||
|
|
||||||
|
drawable.skin.setRotationCenter(150, 50);
|
||||||
|
drawable.updateProperties({scale: [50, 50]});
|
||||||
|
expected.initFromBounds(-75, 25, 0, 25);
|
||||||
|
t.same(snapToNearest(drawable.getAABB()), expected);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('rotate and scale', t => {
|
||||||
|
const expected = new Rectangle();
|
||||||
|
const drawable = new Drawable();
|
||||||
|
drawable.skin = new MockSkin();
|
||||||
|
drawable.skin.size = [100, 1000];
|
||||||
|
|
||||||
|
drawable.skin.setRotationCenter(50, 50);
|
||||||
|
expected.initFromBounds(-50, 50, -950, 50);
|
||||||
|
t.same(snapToNearest(drawable.getAABB()), expected);
|
||||||
|
|
||||||
|
drawable.updateProperties({scale: [40, 60]});
|
||||||
|
drawable.skin.setRotationCenter(50, 50);
|
||||||
|
expected.initFromBounds(-20, 20, -570, 30);
|
||||||
|
t.same(snapToNearest(drawable.getAABB()), expected);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user