Skip to content

Commit

Permalink
Merge branch 'develop' into greenkeeper/twgl.js-3.5.0
Browse files Browse the repository at this point in the history
  • Loading branch information
cwillisf authored Sep 6, 2017
2 parents 7e78f84 + 8248eb4 commit 1e54b1c
Show file tree
Hide file tree
Showing 10 changed files with 272 additions and 15 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
language: node_js
node_js:
- "4.2"
- 6
- "node"
sudo: false
cache:
Expand Down
13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
}
}
2 changes: 1 addition & 1 deletion src/Drawable.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/PenSkin.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ class PenSkin extends Skin {
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)
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';
Expand Down
2 changes: 1 addition & 1 deletion src/RenderConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module.exports = {
* @enum {string}
*/
Events: {
/**
/**
* NativeSizeChanged event
*
* @event RenderWebGL#event:NativeSizeChanged
Expand Down
96 changes: 96 additions & 0 deletions src/RenderWebGL.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
10 changes: 5 additions & 5 deletions src/ShaderManager.js
Original file line number Diff line number Diff line change
@@ -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 {
/**
Expand Down Expand Up @@ -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]);
}
Expand Down
5 changes: 5 additions & 0 deletions src/playground/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
13 changes: 13 additions & 0 deletions test/fixtures/MockSkin.js
Original file line number Diff line number Diff line change
@@ -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 changes: 142 additions & 0 deletions test/unit/DrawableTests.js
Original file line number Diff line number Diff line change
@@ -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();
});

0 comments on commit 1e54b1c

Please sign in to comment.