Merge branch 'develop' into greenkeeper/twgl.js-3.5.0
This commit is contained in:
commit
1e54b1c894
@ -1,6 +1,6 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "4.2"
|
||||
- 6
|
||||
- "node"
|
||||
sudo: false
|
||||
cache:
|
||||
|
||||
13
package.json
13
package.json
@ -17,22 +17,23 @@
|
||||
"prepublish": "npm run build",
|
||||
"prepublish-watch": "npm run watch",
|
||||
"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)'\"",
|
||||
"watch": "webpack --progress --colors --watch --watch-poll"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.23.1",
|
||||
"babel-eslint": "^7.1.1",
|
||||
"babel-loader": "^6.3.2",
|
||||
"babel-loader": "^7.1.2",
|
||||
"babel-polyfill": "^6.22.0",
|
||||
"babel-preset-es2015": "^6.22.0",
|
||||
"base64-loader": "^1.0.0",
|
||||
"copy-webpack-plugin": "^4.0.1",
|
||||
"docdash": "^0.4.0",
|
||||
"eslint": "^3.16.1",
|
||||
"eslint-config-scratch": "^3.1.0",
|
||||
"gh-pages": "^0.12.0",
|
||||
"eslint": "^4.6.1",
|
||||
"eslint-config-scratch": "^4.0.0",
|
||||
"gh-pages": "^1.0.0",
|
||||
"hull.js": "0.2.10",
|
||||
"jsdoc": "^3.5.3",
|
||||
"json": "^9.0.4",
|
||||
@ -41,7 +42,7 @@
|
||||
"tap": "^10.3.0",
|
||||
"travis-after-all": "^1.4.4",
|
||||
"twgl.js": "3.5.0",
|
||||
"webpack": "^2.2.1",
|
||||
"webpack": "^3.5.6",
|
||||
"webpack-dev-server": "^2.4.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -201,7 +201,7 @@ class Drawable {
|
||||
twgl.m4.rotateZ(modelMatrix, rotation, modelMatrix);
|
||||
|
||||
// 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.divScalar(rotationAdjusted, 100);
|
||||
rotationAdjusted[1] *= -1; // Y flipped to Scratch coordinate.
|
||||
|
||||
@ -5,6 +5,7 @@ const twgl = require('twgl.js');
|
||||
|
||||
const BitmapSkin = require('./BitmapSkin');
|
||||
const Drawable = require('./Drawable');
|
||||
const Rectangle = require('./Rectangle');
|
||||
const PenSkin = require('./PenSkin');
|
||||
const RenderConstants = require('./RenderConstants');
|
||||
const ShaderManager = require('./ShaderManager');
|
||||
@ -222,6 +223,33 @@ class RenderWebGL extends EventEmitter {
|
||||
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.
|
||||
* @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.
|
||||
* @param {int} drawableID ID for drawable of query.
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
const twgl = require('twgl.js');
|
||||
|
||||
const vertexShaderText = require('raw-loader!./shaders/sprite.vert');
|
||||
const fragmentShaderText = require('raw-loader!./shaders/sprite.frag');
|
||||
|
||||
|
||||
class ShaderManager {
|
||||
/**
|
||||
@ -65,8 +62,11 @@ class ShaderManager {
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
@ -126,6 +126,11 @@
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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