Merge pull request #557 from adroitwhiz/penskin-cleanup
Clean up PenSkin code
This commit is contained in:
339
src/PenSkin.js
339
src/PenSkin.js
@@ -3,7 +3,6 @@ const twgl = require('twgl.js');
|
||||
const RenderConstants = require('./RenderConstants');
|
||||
const Skin = require('./Skin');
|
||||
|
||||
const Rectangle = require('./Rectangle');
|
||||
const ShaderManager = require('./ShaderManager');
|
||||
|
||||
/**
|
||||
@@ -31,44 +30,6 @@ const DefaultPenAttributes = {
|
||||
*/
|
||||
const __premultipliedColor = [0, 0, 0, 0];
|
||||
|
||||
|
||||
/**
|
||||
* Reused memory location for projection matrices.
|
||||
* @type {FloatArray}
|
||||
*/
|
||||
const __projectionMatrix = twgl.m4.identity();
|
||||
|
||||
/**
|
||||
* Reused memory location for translation matrix for building a model matrix.
|
||||
* @type {FloatArray}
|
||||
*/
|
||||
const __modelTranslationMatrix = twgl.m4.identity();
|
||||
|
||||
|
||||
/**
|
||||
* Reused memory location for scaling matrix for building a model matrix.
|
||||
* @type {FloatArray}
|
||||
*/
|
||||
const __modelScalingMatrix = twgl.m4.identity();
|
||||
|
||||
/**
|
||||
* Reused memory location for a model matrix.
|
||||
* @type {FloatArray}
|
||||
*/
|
||||
const __modelMatrix = twgl.m4.identity();
|
||||
|
||||
/**
|
||||
* Reused memory location for a vector to create a translation matrix from.
|
||||
* @type {FloatArray}
|
||||
*/
|
||||
const __modelTranslationVector = twgl.v3.create();
|
||||
|
||||
/**
|
||||
* Reused memory location for a vector to create a scaling matrix from.
|
||||
* @type {FloatArray}
|
||||
*/
|
||||
const __modelScalingVector = twgl.v3.create();
|
||||
|
||||
class PenSkin extends Skin {
|
||||
/**
|
||||
* Create a Skin which implements a Scratch pen layer.
|
||||
@@ -86,21 +47,12 @@ class PenSkin extends Skin {
|
||||
*/
|
||||
this._renderer = renderer;
|
||||
|
||||
/** @type {HTMLCanvasElement} */
|
||||
this._canvas = document.createElement('canvas');
|
||||
|
||||
/** @type {WebGLTexture} */
|
||||
this._exportTexture = null;
|
||||
/** @type {Array<number>} */
|
||||
this._size = null;
|
||||
|
||||
/** @type {WebGLFramebuffer} */
|
||||
this._framebuffer = null;
|
||||
|
||||
/** @type {WebGLFramebuffer} */
|
||||
this._silhouetteBuffer = null;
|
||||
|
||||
/** @type {boolean} */
|
||||
this._canvasDirty = false;
|
||||
|
||||
/** @type {boolean} */
|
||||
this._silhouetteDirty = false;
|
||||
|
||||
@@ -117,23 +69,30 @@ class PenSkin extends Skin {
|
||||
};
|
||||
|
||||
/** @type {object} */
|
||||
this._toBufferDrawRegionId = {
|
||||
enter: () => this._enterDrawToBuffer(),
|
||||
exit: () => this._exitDrawToBuffer()
|
||||
this._usePenBufferDrawRegionId = {
|
||||
enter: () => this._enterUsePenBuffer(),
|
||||
exit: () => this._exitUsePenBuffer()
|
||||
};
|
||||
|
||||
/** @type {twgl.BufferInfo} */
|
||||
this._lineBufferInfo = null;
|
||||
this._lineBufferInfo = twgl.createBufferInfoFromArrays(this._renderer.gl, {
|
||||
a_position: {
|
||||
numComponents: 2,
|
||||
data: [
|
||||
1, 0,
|
||||
0, 0,
|
||||
1, 1,
|
||||
1, 1,
|
||||
0, 0,
|
||||
0, 1
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
const NO_EFFECTS = 0;
|
||||
/** @type {twgl.ProgramInfo} */
|
||||
this._stampShader = this._renderer._shaderManager.getShader(ShaderManager.DRAW_MODE.default, NO_EFFECTS);
|
||||
|
||||
/** @type {twgl.ProgramInfo} */
|
||||
this._lineShader = this._renderer._shaderManager.getShader(ShaderManager.DRAW_MODE.line, NO_EFFECTS);
|
||||
|
||||
this._createLineGeometry();
|
||||
|
||||
this.onNativeSizeChanged = this.onNativeSizeChanged.bind(this);
|
||||
this._renderer.on(RenderConstants.Events.NativeSizeChanged, this.onNativeSizeChanged);
|
||||
|
||||
@@ -146,7 +105,6 @@ class PenSkin extends Skin {
|
||||
dispose () {
|
||||
this._renderer.removeListener(RenderConstants.Events.NativeSizeChanged, this.onNativeSizeChanged);
|
||||
this._renderer.gl.deleteTexture(this._texture);
|
||||
this._renderer.gl.deleteTexture(this._exportTexture);
|
||||
this._texture = null;
|
||||
super.dispose();
|
||||
}
|
||||
@@ -162,37 +120,29 @@ class PenSkin extends Skin {
|
||||
* @return {Array<number>} the "native" size, in texels, of this skin. [width, height]
|
||||
*/
|
||||
get size () {
|
||||
return [this._canvas.width, this._canvas.height];
|
||||
return this._size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array<number>} scale The X and Y scaling factors to be used, as percentages of this skin's "native" size.
|
||||
* @return {WebGLTexture} The GL texture representation of this skin when drawing at the given size.
|
||||
* @param {int} pixelsWide - The width that the skin will be rendered at, in GPU pixels.
|
||||
* @param {int} pixelsTall - The height that the skin will be rendered at, in GPU pixels.
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
getTexture (pixelsWide, pixelsTall) {
|
||||
if (this._canvasDirty) {
|
||||
this._drawToBuffer();
|
||||
}
|
||||
|
||||
return this._exportTexture;
|
||||
getTexture (scale) {
|
||||
return this._texture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the pen layer.
|
||||
*/
|
||||
clear () {
|
||||
const gl = this._renderer.gl;
|
||||
twgl.bindFramebufferInfo(gl, this._framebuffer);
|
||||
this._renderer.enterDrawRegion(this._usePenBufferDrawRegionId);
|
||||
|
||||
/* Reset framebuffer to transparent black */
|
||||
const gl = this._renderer.gl;
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
const ctx = this._canvas.getContext('2d');
|
||||
ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
|
||||
|
||||
this._silhouetteDirty = true;
|
||||
}
|
||||
|
||||
@@ -203,7 +153,6 @@ class PenSkin extends Skin {
|
||||
* @param {number} y - the Y coordinate of the point to draw.
|
||||
*/
|
||||
drawPoint (penAttributes, x, y) {
|
||||
// Canvas renders a zero-length line as two end-caps back-to-back, which is what we want.
|
||||
this.drawLine(penAttributes, x, y, x, y);
|
||||
}
|
||||
|
||||
@@ -230,48 +179,23 @@ class PenSkin extends Skin {
|
||||
this._silhouetteDirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create 2D geometry for drawing lines to a framebuffer.
|
||||
*/
|
||||
_createLineGeometry () {
|
||||
const quads = {
|
||||
a_position: {
|
||||
numComponents: 2,
|
||||
data: [
|
||||
1, 0,
|
||||
0, 0,
|
||||
1, 1,
|
||||
1, 1,
|
||||
0, 0,
|
||||
0, 1
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
this._lineBufferInfo = twgl.createBufferInfoFromArrays(this._renderer.gl, quads);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare to draw lines in the _lineOnBufferDrawRegionId region.
|
||||
*/
|
||||
_enterDrawLineOnBuffer () {
|
||||
const gl = this._renderer.gl;
|
||||
|
||||
const bounds = this._bounds;
|
||||
const currentShader = this._lineShader;
|
||||
const projection = twgl.m4.ortho(0, bounds.width, 0, bounds.height, -1, 1, __projectionMatrix);
|
||||
|
||||
twgl.bindFramebufferInfo(gl, this._framebuffer);
|
||||
|
||||
gl.viewport(0, 0, bounds.width, bounds.height);
|
||||
gl.viewport(0, 0, this._size[0], this._size[1]);
|
||||
|
||||
const currentShader = this._lineShader;
|
||||
gl.useProgram(currentShader.program);
|
||||
|
||||
twgl.setBuffersAndAttributes(gl, currentShader, this._lineBufferInfo);
|
||||
|
||||
const uniforms = {
|
||||
u_skin: this._texture,
|
||||
u_projectionMatrix: projection
|
||||
u_stageSize: this._size
|
||||
};
|
||||
|
||||
twgl.setUniforms(currentShader, uniforms);
|
||||
@@ -286,6 +210,20 @@ class PenSkin extends Skin {
|
||||
twgl.bindFramebufferInfo(gl, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare to do things with this PenSkin's framebuffer
|
||||
*/
|
||||
_enterUsePenBuffer () {
|
||||
twgl.bindFramebufferInfo(this._renderer.gl, this._framebuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return to a base state
|
||||
*/
|
||||
_exitUsePenBuffer () {
|
||||
twgl.bindFramebufferInfo(this._renderer.gl, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a line on the framebuffer.
|
||||
* Note that the point coordinates are in the following coordinate space:
|
||||
@@ -323,8 +261,7 @@ class PenSkin extends Skin {
|
||||
u_lineColor: __premultipliedColor,
|
||||
u_lineThickness: penAttributes.diameter || DefaultPenAttributes.diameter,
|
||||
u_lineLength: lineLength,
|
||||
u_penPoints: [x0, -y0, lineDiffX, -lineDiffY],
|
||||
u_stageSize: this.size
|
||||
u_penPoints: [x0, -y0, lineDiffX, -lineDiffY]
|
||||
};
|
||||
|
||||
twgl.setUniforms(currentShader, uniforms);
|
||||
@@ -334,136 +271,6 @@ class PenSkin extends Skin {
|
||||
this._silhouetteDirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stamp an image onto the pen layer.
|
||||
* @param {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} stampElement - the element to use as the stamp.
|
||||
* @param {number} x - the X coordinate of the stamp to draw.
|
||||
* @param {number} y - the Y coordinate of the stamp to draw.
|
||||
*/
|
||||
drawStamp (stampElement, x, y) {
|
||||
const ctx = this._canvas.getContext('2d');
|
||||
|
||||
ctx.drawImage(stampElement, this._rotationCenter[0] + x, this._rotationCenter[1] - y);
|
||||
|
||||
this._canvasDirty = true;
|
||||
this._silhouetteDirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter a draw region to draw a rectangle.
|
||||
*
|
||||
* Multiple calls with the same regionId skip the callback reducing the
|
||||
* amount of GL state changes.
|
||||
* @param {twgl.ProgramInfo} currentShader - program info to draw rectangle
|
||||
* with
|
||||
* @param {Rectangle} bounds - viewport bounds to draw in
|
||||
* region
|
||||
*/
|
||||
_drawRectangleRegionEnter (currentShader, bounds) {
|
||||
const gl = this._renderer.gl;
|
||||
|
||||
gl.viewport(0, 0, bounds.width, bounds.height);
|
||||
|
||||
gl.useProgram(currentShader.program);
|
||||
twgl.setBuffersAndAttributes(gl, currentShader, this._renderer._bufferInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a rectangle.
|
||||
* @param {twgl.ProgramInfo} currentShader - program info to draw rectangle
|
||||
* with
|
||||
* @param {WebGLTexture} texture - texture to draw
|
||||
* @param {Rectangle} bounds - bounded area to draw in
|
||||
* @param {number} x - centered at x
|
||||
* @param {number} y - centered at y
|
||||
*/
|
||||
_drawRectangle (currentShader, texture, bounds, x = -this._canvas.width / 2, y = this._canvas.height / 2) {
|
||||
const gl = this._renderer.gl;
|
||||
|
||||
const projection = twgl.m4.ortho(
|
||||
bounds.left, bounds.right, bounds.top, bounds.bottom, -1, 1,
|
||||
__projectionMatrix
|
||||
);
|
||||
|
||||
const uniforms = {
|
||||
u_skin: texture,
|
||||
u_projectionMatrix: projection,
|
||||
u_modelMatrix: twgl.m4.multiply(
|
||||
twgl.m4.translation(twgl.v3.create(
|
||||
-x - (bounds.width / 2),
|
||||
-y + (bounds.height / 2),
|
||||
0
|
||||
), __modelTranslationMatrix),
|
||||
twgl.m4.scaling(twgl.v3.create(
|
||||
bounds.width,
|
||||
bounds.height,
|
||||
0
|
||||
), __modelScalingMatrix),
|
||||
__modelMatrix
|
||||
)
|
||||
};
|
||||
|
||||
twgl.setTextureParameters(gl, texture, {minMag: gl.NEAREST});
|
||||
twgl.setUniforms(currentShader, uniforms);
|
||||
|
||||
twgl.drawBufferInfo(gl, this._renderer._bufferInfo, gl.TRIANGLES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare to draw a rectangle in the _toBufferDrawRegionId region.
|
||||
*/
|
||||
_enterDrawToBuffer () {
|
||||
const gl = this._renderer.gl;
|
||||
|
||||
twgl.bindFramebufferInfo(gl, this._framebuffer);
|
||||
|
||||
this._drawRectangleRegionEnter(this._stampShader, this._bounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return to a base state from _toBufferDrawRegionId.
|
||||
*/
|
||||
_exitDrawToBuffer () {
|
||||
const gl = this._renderer.gl;
|
||||
|
||||
twgl.bindFramebufferInfo(gl, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the input texture to the framebuffer.
|
||||
* @param {WebGLTexture} texture - input texture to draw
|
||||
* @param {number} x - texture centered at x
|
||||
* @param {number} y - texture centered at y
|
||||
*/
|
||||
_drawToBuffer (texture = this._texture, x = -this._canvas.width / 2, y = this._canvas.height / 2) {
|
||||
if (texture !== this._texture && this._canvasDirty) {
|
||||
this._drawToBuffer();
|
||||
}
|
||||
|
||||
const gl = this._renderer.gl;
|
||||
|
||||
// If the input texture is the one that represents the pen's canvas
|
||||
// layer, update the texture with the canvas data.
|
||||
if (texture === this._texture) {
|
||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._canvas);
|
||||
|
||||
const ctx = this._canvas.getContext('2d');
|
||||
ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
|
||||
|
||||
this._canvasDirty = false;
|
||||
}
|
||||
|
||||
const currentShader = this._stampShader;
|
||||
const bounds = this._bounds;
|
||||
|
||||
this._renderer.enterDrawRegion(this._toBufferDrawRegionId);
|
||||
|
||||
this._drawRectangle(currentShader, texture, bounds, x, y);
|
||||
|
||||
this._silhouetteDirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* React to a change in the renderer's native size.
|
||||
* @param {object} event - The change event.
|
||||
@@ -480,31 +287,15 @@ class PenSkin extends Skin {
|
||||
_setCanvasSize (canvasSize) {
|
||||
const [width, height] = canvasSize;
|
||||
|
||||
const gl = this._renderer.gl;
|
||||
|
||||
this._bounds = new Rectangle();
|
||||
this._bounds.initFromBounds(width / 2, width / -2, height / 2, height / -2);
|
||||
|
||||
this._canvas.width = width;
|
||||
this._canvas.height = height;
|
||||
this._size = canvasSize;
|
||||
this._rotationCenter[0] = width / 2;
|
||||
this._rotationCenter[1] = height / 2;
|
||||
|
||||
const gl = this._renderer.gl;
|
||||
|
||||
this._texture = twgl.createTexture(
|
||||
gl,
|
||||
{
|
||||
auto: true,
|
||||
mag: gl.NEAREST,
|
||||
min: gl.NEAREST,
|
||||
wrap: gl.CLAMP_TO_EDGE,
|
||||
src: this._canvas
|
||||
}
|
||||
);
|
||||
|
||||
this._exportTexture = twgl.createTexture(
|
||||
gl,
|
||||
{
|
||||
auto: true,
|
||||
mag: gl.NEAREST,
|
||||
min: gl.NEAREST,
|
||||
wrap: gl.CLAMP_TO_EDGE,
|
||||
@@ -516,66 +307,36 @@ class PenSkin extends Skin {
|
||||
const attachments = [
|
||||
{
|
||||
format: gl.RGBA,
|
||||
attachment: this._exportTexture
|
||||
attachment: this._texture
|
||||
}
|
||||
];
|
||||
if (this._framebuffer) {
|
||||
twgl.resizeFramebufferInfo(gl, this._framebuffer, attachments, width, height);
|
||||
twgl.resizeFramebufferInfo(gl, this._silhouetteBuffer, [{format: gl.RGBA}], width, height);
|
||||
} else {
|
||||
this._framebuffer = twgl.createFramebufferInfo(gl, attachments, width, height);
|
||||
this._silhouetteBuffer = twgl.createFramebufferInfo(gl, [{format: gl.RGBA}], width, height);
|
||||
}
|
||||
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
this._silhouettePixels = new Uint8Array(Math.floor(width * height * 4));
|
||||
this._silhouetteImageData = this._canvas.getContext('2d').createImageData(width, height);
|
||||
this._silhouetteImageData = new ImageData(width, height);
|
||||
|
||||
this._silhouetteDirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set context state to match provided pen attributes.
|
||||
* @param {CanvasRenderingContext2D} context - the canvas rendering context to be modified.
|
||||
* @param {PenAttributes} penAttributes - the pen attributes to be used.
|
||||
* @private
|
||||
*/
|
||||
_setAttributes (context, penAttributes) {
|
||||
penAttributes = penAttributes || DefaultPenAttributes;
|
||||
const color4f = penAttributes.color4f || DefaultPenAttributes.color4f;
|
||||
const diameter = penAttributes.diameter || DefaultPenAttributes.diameter;
|
||||
|
||||
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)
|
||||
|
||||
context.strokeStyle = `rgba(${r},${g},${b},${a})`;
|
||||
context.lineCap = 'round';
|
||||
context.lineWidth = diameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* If there have been pen operations that have dirtied the canvas, update
|
||||
* now before someone wants to use our silhouette.
|
||||
*/
|
||||
updateSilhouette () {
|
||||
if (this._silhouetteDirty) {
|
||||
if (this._canvasDirty) {
|
||||
this._drawToBuffer();
|
||||
}
|
||||
|
||||
// Render export texture to another framebuffer
|
||||
const gl = this._renderer.gl;
|
||||
|
||||
this._renderer.enterDrawRegion(this._toBufferDrawRegionId);
|
||||
|
||||
this._renderer.enterDrawRegion(this._usePenBufferDrawRegionId);
|
||||
// Sample the framebuffer's pixels into the silhouette instance
|
||||
const gl = this._renderer.gl;
|
||||
gl.readPixels(
|
||||
0, 0,
|
||||
this._canvas.width, this._canvas.height,
|
||||
this._size[0], this._size[1],
|
||||
gl.RGBA, gl.UNSIGNED_BYTE, this._silhouettePixels
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user