From 603fd8778271095b16591d70b131cfca3d53bcc9 Mon Sep 17 00:00:00 2001 From: adroitwhiz Date: Sat, 20 Apr 2019 02:27:34 -0400 Subject: [PATCH 01/38] Clear _regionId when exiting draw region --- src/RenderWebGL.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js index 05b35e2..43b2852 100644 --- a/src/RenderWebGL.js +++ b/src/RenderWebGL.js @@ -1541,6 +1541,7 @@ class RenderWebGL extends EventEmitter { this._exitRegion(); } this._exitRegion = null; + this._regionId = null; } /** From 750f40ddf228c1404fdb6b94a3dd344f48b8cd43 Mon Sep 17 00:00:00 2001 From: adroitwhiz Date: Sat, 20 Apr 2019 02:31:04 -0400 Subject: [PATCH 02/38] Exit draw region in penStamp less --- src/RenderWebGL.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js index 05b35e2..eb277ad 100644 --- a/src/RenderWebGL.js +++ b/src/RenderWebGL.js @@ -1409,8 +1409,6 @@ class RenderWebGL extends EventEmitter { * @param {int} stampID - the unique ID of the Drawable to use as the stamp. */ penStamp (penSkinID, stampID) { - this._doExitDrawRegion(); - const stampDrawable = this._allDrawables[stampID]; if (!stampDrawable) { return; @@ -1421,6 +1419,8 @@ class RenderWebGL extends EventEmitter { return; } + this._doExitDrawRegion(); + const skin = /** @type {PenSkin} */ this._allSkins[penSkinID]; const gl = this._gl; From 6e755ea015c0ad3584070262203e2c0387866b4b Mon Sep 17 00:00:00 2001 From: adroitwhiz Date: Sat, 20 Apr 2019 02:47:25 -0400 Subject: [PATCH 03/38] Add "disappearing pen" test --- .../scratch-tests/disappearing-pen.sb3 | Bin 0 -> 42238 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/integration/scratch-tests/disappearing-pen.sb3 diff --git a/test/integration/scratch-tests/disappearing-pen.sb3 b/test/integration/scratch-tests/disappearing-pen.sb3 new file mode 100644 index 0000000000000000000000000000000000000000..38ab918d1e0a8b1450f1ee7d694cae860c6f5169 GIT binary patch literal 42238 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 p_`ksIzZL(T8UHBy7yMo0{~=G5 Date: Sat, 20 Apr 2019 07:59:09 -0400 Subject: [PATCH 04/38] Never useNearest on distorted vector skins --- src/Drawable.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Drawable.js b/src/Drawable.js index d8f6589..c7b9d0c 100644 --- a/src/Drawable.js +++ b/src/Drawable.js @@ -431,6 +431,11 @@ class Drawable { return true; } + // If the effect bits for mosaic, pixelate, whirl, or fisheye are set, use linear + if (this._effectBits & 0b0011110) { + return false; + } + // We can't use nearest neighbor unless we are a multiple of 90 rotation if (this._direction % 90 !== 0) { return false; From f9309b1ace69fe4bd6eb90f0c2f658b876c255d9 Mon Sep 17 00:00:00 2001 From: adroitwhiz Date: Sat, 20 Apr 2019 08:12:22 -0400 Subject: [PATCH 05/38] be more s e m a n t i c --- src/Drawable.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Drawable.js b/src/Drawable.js index c7b9d0c..c865796 100644 --- a/src/Drawable.js +++ b/src/Drawable.js @@ -432,7 +432,12 @@ class Drawable { } // If the effect bits for mosaic, pixelate, whirl, or fisheye are set, use linear - if (this._effectBits & 0b0011110) { + if (this._effectBits & ( + ShaderManager.EFFECT_INFO.fisheye.mask | + ShaderManager.EFFECT_INFO.whirl.mask | + ShaderManager.EFFECT_INFO.pixelate.mask | + ShaderManager.EFFECT_INFO.mosaic.mask + )) { return false; } From 6646041ba464c166e747c2d7e8fa7d9c949207d1 Mon Sep 17 00:00:00 2001 From: adroitwhiz Date: Mon, 22 Apr 2019 08:49:20 -0400 Subject: [PATCH 06/38] Fix Rectangle.Clamp() --- src/Rectangle.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Rectangle.js b/src/Rectangle.js index 7659e22..4e0af72 100644 --- a/src/Rectangle.js +++ b/src/Rectangle.js @@ -98,11 +98,11 @@ class Rectangle { this.right = Math.min(this.right, right); this.bottom = Math.max(this.bottom, bottom); this.top = Math.min(this.top, top); - // Ensure rectangle coordinates in order. - this.left = Math.min(this.left, this.right); - this.right = Math.max(this.right, this.left); - this.bottom = Math.min(this.bottom, this.top); - this.top = Math.max(this.top, this.bottom); + + this.left = Math.min(this.left, right); + this.right = Math.max(this.right, left); + this.bottom = Math.min(this.bottom, top); + this.top = Math.max(this.top, bottom); } /** From 34f265ab2238945126107f1a61ab132612d33aa7 Mon Sep 17 00:00:00 2001 From: adroitwhiz Date: Wed, 22 May 2019 04:52:30 -0400 Subject: [PATCH 07/38] strict inequality --- src/Drawable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Drawable.js b/src/Drawable.js index c865796..975ca95 100644 --- a/src/Drawable.js +++ b/src/Drawable.js @@ -437,7 +437,7 @@ class Drawable { ShaderManager.EFFECT_INFO.whirl.mask | ShaderManager.EFFECT_INFO.pixelate.mask | ShaderManager.EFFECT_INFO.mosaic.mask - )) { + ) !== 0) { return false; } From bf47f69b04f76f78a1ca749099655f2f0d0277be Mon Sep 17 00:00:00 2001 From: "Michael \"Z\" Goddard" Date: Wed, 5 Jun 2019 17:40:46 -0400 Subject: [PATCH 08/38] use a destination parameter for bounds; add initFromMatrixRadius - pass bounds as a destination parameter - add initFromMatrixRadius - use initFromMatrixRadius in getAABB --- src/Drawable.js | 25 ++++++++++--------------- src/Rectangle.js | 25 +++++++++++++++++++++++++ src/Skin.js | 4 ++-- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/Drawable.js b/src/Drawable.js index 1d90a29..8c87033 100644 --- a/src/Drawable.js +++ b/src/Drawable.js @@ -453,7 +453,7 @@ class Drawable { * Before calling this, ensure the renderer has updated convex hull points. * @return {!Rectangle} Bounds for a tight box around the Drawable. */ - getBounds () { + getBounds (bounds) { if (this.needsConvexHullPoints()) { throw new Error('Needs updated convex hull points before bounds calculation.'); } @@ -462,7 +462,7 @@ class Drawable { } const transformedHullPoints = this._getTransformedHullPoints(); // Search through transformed points to generate box on axes. - const bounds = new Rectangle(); + bounds = bounds || new Rectangle(); bounds.initFromPointsAABB(transformedHullPoints); return bounds; } @@ -473,7 +473,7 @@ class Drawable { * Before calling this, ensure the renderer has updated convex hull points. * @return {!Rectangle} Bounds for a tight box around a slice of the Drawable. */ - getBoundsForBubble () { + getBoundsForBubble (bounds) { if (this.needsConvexHullPoints()) { throw new Error('Needs updated convex hull points before bubble bounds calculation.'); } @@ -485,7 +485,7 @@ class Drawable { const maxY = Math.max.apply(null, transformedHullPoints.map(p => p[1])); const filteredHullPoints = transformedHullPoints.filter(p => p[1] > maxY - slice); // Search through filtered points to generate box on axes. - const bounds = new Rectangle(); + bounds = bounds || new Rectangle(); bounds.initFromPointsAABB(filteredHullPoints); return bounds; } @@ -499,18 +499,13 @@ class Drawable { * faster to calculate so may be desired for quick checks/optimizations. * @return {!Rectangle} Rough axis-aligned bounding box for Drawable. */ - getAABB () { + getAABB (bounds) { if (this._transformDirty) { this._calculateTransform(); } const tm = this._uniforms.u_modelMatrix; - const bounds = new Rectangle(); - bounds.initFromPointsAABB([ - twgl.m4.transformPoint(tm, [-0.5, -0.5, 0]), - twgl.m4.transformPoint(tm, [0.5, -0.5, 0]), - twgl.m4.transformPoint(tm, [-0.5, 0.5, 0]), - twgl.m4.transformPoint(tm, [0.5, 0.5, 0]) - ]); + bounds = bounds || new Rectangle(); + bounds.initFromMatrixRadius(tm, 0.5); return bounds; } @@ -520,12 +515,12 @@ class Drawable { * known, but otherwise return the rough AABB of the Drawable. * @return {!Rectangle} Bounds for the Drawable. */ - getFastBounds () { + getFastBounds (bounds) { this.updateMatrix(); if (!this.needsConvexHullPoints()) { - return this.getBounds(); + return this.getBounds(bounds); } - return this.getAABB(); + return this.getAABB(bounds); } /** diff --git a/src/Rectangle.js b/src/Rectangle.js index 7659e22..726a151 100644 --- a/src/Rectangle.js +++ b/src/Rectangle.js @@ -54,6 +54,31 @@ class Rectangle { } } + initFromMatrixRadius (m, r) { + // const v0 = r; + // const v1 = r; + // const v2 = r; + const m00 = m[(0 * 4) + 0]; + const m01 = m[(0 * 4) + 1]; + const m10 = m[(1 * 4) + 0]; + const m11 = m[(1 * 4) + 1]; + const m30 = m[(3 * 4) + 0]; + const m31 = m[(3 * 4) + 1]; + // var d = v0 * m03 + v1 * m13 + v2 * m23 + m33; + // dst[0] = ( + const x = Math.abs(r * m00) + Math.abs(r * m10); + // + v2 * m20 + m30) / d; + // dst[1] = ( + const y = Math.abs(r * m01) + Math.abs(r * m11); + // + v2 * m21 + m31) / d; + // dst[2] = (v0 * m02 + v1 * m12 + v2 * m22 + m32) / d; + + this.left = -x + m30; + this.right = x + m30; + this.top = y + m31; + this.bottom = -y + m31; + } + /** * Determine if this Rectangle intersects some other. * Note that this is a comparison assuming the Rectangle was diff --git a/src/Skin.js b/src/Skin.js index e0e7413..8d124be 100644 --- a/src/Skin.js +++ b/src/Skin.js @@ -148,8 +148,8 @@ class Skin extends EventEmitter { * @param {Array} drawable - The Drawable instance this skin is using. * @return {!Rectangle} The drawable's bounds. */ - getFenceBounds (drawable) { - return drawable.getFastBounds(); + getFenceBounds (drawable, bounds) { + return drawable.getFastBounds(bounds); } /** From 96aa930895b27248cbb0c2cfea80d967b70f1b7e Mon Sep 17 00:00:00 2001 From: "Michael \"Z\" Goddard" Date: Wed, 5 Jun 2019 10:05:45 -0400 Subject: [PATCH 09/38] update drawable calls --- src/Drawable.js | 115 +++++++++++++++++++++++++++++------------- src/RenderWebGL.js | 122 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 196 insertions(+), 41 deletions(-) diff --git a/src/Drawable.js b/src/Drawable.js index 1d90a29..dc17ff3 100644 --- a/src/Drawable.js +++ b/src/Drawable.js @@ -183,56 +183,99 @@ class Drawable { return this._visible; } + /** + * Update the position if it is different. Marks the transform as dirty. + * @param {Array.} position A new position. + */ + updatePosition (position) { + if (this._position[0] !== position[0] || + this._position[1] !== position[1]) { + this._position[0] = Math.round(position[0]); + this._position[1] = Math.round(position[1]); + this.setTransformDirty(); + } + } + + /** + * Update the direction if it is different. Marks the transform as dirty. + * @param {number} direction A new direction. + */ + updateDirection (direction) { + if (this._direction !== direction) { + this._direction = direction; + this._rotationTransformDirty = true; + this.setTransformDirty(); + } + } + + /** + * Update the scale if it is different. Marks the transform as dirty. + * @param {Array.} scale A new scale. + */ + updateScale (scale) { + if (this._scale[0] !== scale[0] || + this._scale[1] !== scale[1]) { + this._scale[0] = scale[0]; + this._scale[1] = scale[1]; + this._rotationCenterDirty = true; + this._skinScaleDirty = true; + this.setTransformDirty(); + } + } + + /** + * Update visibility if it is different. Marks the convex hull as dirty. + * @param {boolean} visible A new visibility state. + */ + updateVisible (visible) { + if (this._visible !== visible) { + this._visible = visible; + this.setConvexHullDirty(); + } + } + + /** + * Update an effect. Marks the convex hull as dirty if the effect changes shape. + * @param {string} effectName The name of the effect. + * @param {number} rawValue A new effect value. + */ + updateEffect (effectName, rawValue) { + const effectInfo = ShaderManager.EFFECT_INFO[effectName]; + if (rawValue) { + this._effectBits |= effectInfo.mask; + } else { + this._effectBits &= ~effectInfo.mask; + } + const converter = effectInfo.converter; + this._uniforms[effectInfo.uniformName] = converter(rawValue); + if (effectInfo.shapeChanges) { + this.setConvexHullDirty(); + } + } + /** * Update the position, direction, scale, or effect properties of this Drawable. + * @deprecated Use specific update* methods instead. * @param {object.} properties The new property values to set. */ updateProperties (properties) { - let dirty = false; - if ('position' in properties && ( - 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 ('position' in properties) { + this.updatePosition(properties.position); } - if ('direction' in properties && this._direction !== properties.direction) { - this._direction = properties.direction; - this._rotationTransformDirty = true; - dirty = true; + if ('direction' in properties) { + this.updateDirection(properties.direction); } - if ('scale' in properties && ( - this._scale[0] !== properties.scale[0] || - this._scale[1] !== properties.scale[1])) { - this._scale[0] = properties.scale[0]; - this._scale[1] = properties.scale[1]; - this._rotationCenterDirty = true; - this._skinScaleDirty = true; - dirty = true; + if ('scale' in properties) { + this.updateScale(properties.scale); } if ('visible' in properties) { - this._visible = properties.visible; - this.setConvexHullDirty(); - } - if (dirty) { - this.setTransformDirty(); + this.updateVisible(properties.visible); } const numEffects = ShaderManager.EFFECTS.length; for (let index = 0; index < numEffects; ++index) { const effectName = ShaderManager.EFFECTS[index]; if (effectName in properties) { - const rawValue = properties[effectName]; - const effectInfo = ShaderManager.EFFECT_INFO[effectName]; - if (rawValue) { - this._effectBits |= effectInfo.mask; - } else { - this._effectBits &= ~effectInfo.mask; - } - const converter = effectInfo.converter; - this._uniforms[effectInfo.uniformName] = converter(rawValue); - if (effectInfo.shapeChanges) { - this.setConvexHullDirty(); - } + this.updateEffect(effectName, properties[effectName]); } } } diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js index 4782455..99ba413 100644 --- a/src/RenderWebGL.js +++ b/src/RenderWebGL.js @@ -1313,9 +1313,122 @@ class RenderWebGL extends EventEmitter { }, null); } + /** + * Update a drawable's skin. + * @param {number} drawableID The drawable's id. + * @param {number} skinId The skin to update to. + */ + updateDrawableSkinId (drawableID, skinId) { + const drawable = this._allDrawables[drawableID]; + // TODO: vm's requests to drawableID that do not have a Drawable object. + if (!drawable) return; + drawable.skin = this._allSkins[skinId]; + } + + /** + * Update a drawable's skin rotation center. + * @param {number} drawableID The drawable's id. + * @param {Array.} rotationCenter The rotation center for the skin. + */ + updateDrawableRotationCenter (drawableID, rotationCenter) { + const drawable = this._allDrawables[drawableID]; + // TODO: vm's requests to drawableID that do not have a Drawable object. + if (!drawable) return; + drawable.skin.setRotationCenter(rotationCenter[0], rotationCenter[1]); + } + + /** + * Update a drawable's skin and rotation center together. + * @param {number} drawableID The drawable's id. + * @param {number} skinId The skin to update to. + * @param {Array.} rotationCenter The rotation center for the skin. + */ + updateDrawableSkinIdRotationCenter (drawableID, skinId, rotationCenter) { + const drawable = this._allDrawables[drawableID]; + // TODO: vm's requests to drawableID that do not have a Drawable object. + if (!drawable) return; + drawable.skin = this._allSkins[skinId]; + drawable.skin.setRotationCenter(rotationCenter[0], rotationCenter[1]); + } + + /** + * Update a drawable's position. + * @param {number} drawableID The drawable's id. + * @param {Array.} position The new position. + */ + updateDrawablePosition (drawableID, position) { + const drawable = this._allDrawables[drawableID]; + // TODO: vm's requests to drawableID that do not have a Drawable object. + if (!drawable) return; + drawable.updatePosition(position); + } + + /** + * Update a drawable's direction. + * @param {number} drawableID The drawable's id. + * @param {number} direction A new direction. + */ + updateDrawableDirection (drawableID, direction) { + const drawable = this._allDrawables[drawableID]; + // TODO: vm's requests to drawableID that do not have a Drawable object. + if (!drawable) return; + drawable.updateDirection(direction); + } + + /** + * Update a drawable's scale. + * @param {number} drawableID The drawable's id. + * @param {Array.} scale A new scale. + */ + updateDrawableScale (drawableID, scale) { + const drawable = this._allDrawables[drawableID]; + // TODO: vm's requests to drawableID that do not have a Drawable object. + if (!drawable) return; + drawable.updateScale(scale); + } + + /** + * Update a drawable's direction and scale together. + * @param {number} drawableID The drawable's id. + * @param {number} direction A new direction. + * @param {Array.} scale A new scale. + */ + updateDrawableDirectionScale (drawableID, direction, scale) { + const drawable = this._allDrawables[drawableID]; + // TODO: vm's requests to drawableID that do not have a Drawable object. + if (!drawable) return; + drawable.updateDirection(direction); + drawable.updateScale(scale); + } + + /** + * Update a drawable's visibility. + * @param {number} drawableID The drawable's id. + * @param {boolean} visible Will the drawable be visible? + */ + updateDrawableVisible (drawableID, visible) { + const drawable = this._allDrawables[drawableID]; + // TODO: vm's requests to drawableID that do not have a Drawable object. + if (!drawable) return; + drawable.updateVisible(visible); + } + + /** + * Update a drawable's visual effect. + * @param {number} drawableID The drawable's id. + * @param {string} effectName The effect to change. + * @param {number} value A new effect value. + */ + updateDrawableEffect (drawableID, effectName, value) { + const drawable = this._allDrawables[drawableID]; + // TODO: vm's requests to drawableID that do not have a Drawable object. + if (!drawable) return; + drawable.updateEffect(effectName, value); + } /** * Update the position, direction, scale, or effect properties of this Drawable. + * @deprecated Use specific updateDrawable* methods instead. * @param {int} drawableID The ID of the Drawable to update. * @param {object.} properties The new property values to set. */ @@ -1329,11 +1442,10 @@ class RenderWebGL extends EventEmitter { return; } if ('skinId' in properties) { - drawable.skin = this._allSkins[properties.skinId]; + this.updateDrawableSkinId(drawableID, properties.skinId); } if ('rotationCenter' in properties) { - const newRotationCenter = properties.rotationCenter; - drawable.skin.setRotationCenter(newRotationCenter[0], newRotationCenter[1]); + this.updateDrawableRotationCenter(drawableID, properties.rotationCenter); } drawable.updateProperties(properties); } @@ -1627,14 +1739,14 @@ class RenderWebGL extends EventEmitter { } twgl.setUniforms(currentShader, uniforms); - + /* adjust blend function for this skin */ if (drawable.skin.hasPremultipliedAlpha){ gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); } else { gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); } - + twgl.drawBufferInfo(gl, this._bufferInfo, gl.TRIANGLES); } From a840089bc94b410dc6a64298be5493ee03e46dd9 Mon Sep 17 00:00:00 2001 From: "Michael \"Z\" Goddard" Date: Wed, 5 Jun 2019 18:13:01 -0400 Subject: [PATCH 10/38] fence bounds --- src/BitmapSkin.js | 5 +++-- src/Drawable.js | 34 +++++++++++++++++++--------------- src/RenderWebGL.js | 7 ++++--- src/Skin.js | 5 +++-- 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/BitmapSkin.js b/src/BitmapSkin.js index b4b03d3..94f2984 100644 --- a/src/BitmapSkin.js +++ b/src/BitmapSkin.js @@ -62,10 +62,11 @@ class BitmapSkin extends Skin { /** * Get the bounds of the drawable for determining its fenced position. * @param {Array} drawable - The Drawable instance this skin is using. + * @param {?Rectangle} result - Optional destination for bounds calculation. * @return {!Rectangle} The drawable's bounds. For compatibility with Scratch 2, we always use getAABB for bitmaps. */ - getFenceBounds (drawable) { - return drawable.getAABB(); + getFenceBounds (drawable, result) { + return drawable.getAABB(result); } /** diff --git a/src/Drawable.js b/src/Drawable.js index 8c87033..6ab8a1c 100644 --- a/src/Drawable.js +++ b/src/Drawable.js @@ -451,9 +451,10 @@ class Drawable { * This function applies the transform matrix to the known convex hull, * and then finds the minimum box along the axes. * Before calling this, ensure the renderer has updated convex hull points. + * @param {?Rectangle} result optional destination for bounds calculation * @return {!Rectangle} Bounds for a tight box around the Drawable. */ - getBounds (bounds) { + getBounds (result) { if (this.needsConvexHullPoints()) { throw new Error('Needs updated convex hull points before bounds calculation.'); } @@ -462,18 +463,19 @@ class Drawable { } const transformedHullPoints = this._getTransformedHullPoints(); // Search through transformed points to generate box on axes. - bounds = bounds || new Rectangle(); - bounds.initFromPointsAABB(transformedHullPoints); - return bounds; + result = result || new Rectangle(); + result.initFromPointsAABB(transformedHullPoints); + return result; } /** * Get the precise bounds for the upper 8px slice of the Drawable. * Used for calculating where to position a text bubble. * Before calling this, ensure the renderer has updated convex hull points. + * @param {?Rectangle} result optional destination for bounds calculation * @return {!Rectangle} Bounds for a tight box around a slice of the Drawable. */ - getBoundsForBubble (bounds) { + getBoundsForBubble (result) { if (this.needsConvexHullPoints()) { throw new Error('Needs updated convex hull points before bubble bounds calculation.'); } @@ -485,9 +487,9 @@ class Drawable { const maxY = Math.max.apply(null, transformedHullPoints.map(p => p[1])); const filteredHullPoints = transformedHullPoints.filter(p => p[1] > maxY - slice); // Search through filtered points to generate box on axes. - bounds = bounds || new Rectangle(); - bounds.initFromPointsAABB(filteredHullPoints); - return bounds; + result = result || new Rectangle(); + result.initFromPointsAABB(filteredHullPoints); + return result; } /** @@ -497,30 +499,32 @@ class Drawable { * which is tightly snapped to account for a Drawable's transparent regions. * `getAABB` returns a much less accurate bounding box, but will be much * faster to calculate so may be desired for quick checks/optimizations. + * @param {?Rectangle} result optional destination for bounds calculation * @return {!Rectangle} Rough axis-aligned bounding box for Drawable. */ - getAABB (bounds) { + getAABB (result) { if (this._transformDirty) { this._calculateTransform(); } const tm = this._uniforms.u_modelMatrix; - bounds = bounds || new Rectangle(); - bounds.initFromMatrixRadius(tm, 0.5); - return bounds; + result = result || new Rectangle(); + result.initFromMatrixRadius(tm, 0.5); + return result; } /** * Return the best Drawable bounds possible without performing graphics queries. * I.e., returns the tight bounding box when the convex hull points are already * known, but otherwise return the rough AABB of the Drawable. + * @param {?Rectangle} result optional destination for bounds calculation * @return {!Rectangle} Bounds for the Drawable. */ - getFastBounds (bounds) { + getFastBounds (result) { this.updateMatrix(); if (!this.needsConvexHullPoints()) { - return this.getBounds(bounds); + return this.getBounds(result); } - return this.getAABB(bounds); + return this.getAABB(result); } /** diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js index 4782455..ba629ea 100644 --- a/src/RenderWebGL.js +++ b/src/RenderWebGL.js @@ -16,6 +16,7 @@ const log = require('./util/log'); const __isTouchingDrawablesPoint = twgl.v3.create(); const __candidatesBounds = new Rectangle(); +const __fenceBounds = new Rectangle(); const __touchingColor = new Uint8ClampedArray(4); const __blendColor = new Uint8ClampedArray(4); @@ -1357,7 +1358,7 @@ class RenderWebGL extends EventEmitter { const dx = x - drawable._position[0]; const dy = y - drawable._position[1]; - const aabb = drawable._skin.getFenceBounds(drawable); + const aabb = drawable._skin.getFenceBounds(drawable, __fenceBounds); const inset = Math.floor(Math.min(aabb.width, aabb.height) / 2); const sx = this._xRight - Math.min(FENCE_WIDTH, inset); @@ -1627,14 +1628,14 @@ class RenderWebGL extends EventEmitter { } twgl.setUniforms(currentShader, uniforms); - + /* adjust blend function for this skin */ if (drawable.skin.hasPremultipliedAlpha){ gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); } else { gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); } - + twgl.drawBufferInfo(gl, this._bufferInfo, gl.TRIANGLES); } diff --git a/src/Skin.js b/src/Skin.js index 8d124be..473c70b 100644 --- a/src/Skin.js +++ b/src/Skin.js @@ -146,10 +146,11 @@ class Skin extends EventEmitter { /** * Get the bounds of the drawable for determining its fenced position. * @param {Array} drawable - The Drawable instance this skin is using. + * @param {?Rectangle} result - Optional destination for bounds calculation. * @return {!Rectangle} The drawable's bounds. */ - getFenceBounds (drawable, bounds) { - return drawable.getFastBounds(bounds); + getFenceBounds (drawable, result) { + return drawable.getFastBounds(result); } /** From 5d7957ff9b13a23d043f236928fdc8e69c55e60c Mon Sep 17 00:00:00 2001 From: "Michael \"Z\" Goddard" Date: Tue, 11 Jun 2019 14:19:13 -0400 Subject: [PATCH 11/38] document out how initFromModelMatrix works --- src/Drawable.js | 2 +- src/Rectangle.js | 98 ++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 87 insertions(+), 13 deletions(-) diff --git a/src/Drawable.js b/src/Drawable.js index 6ab8a1c..ea94acd 100644 --- a/src/Drawable.js +++ b/src/Drawable.js @@ -508,7 +508,7 @@ class Drawable { } const tm = this._uniforms.u_modelMatrix; result = result || new Rectangle(); - result.initFromMatrixRadius(tm, 0.5); + result.initFromModelMatrix(tm); return result; } diff --git a/src/Rectangle.js b/src/Rectangle.js index 726a151..56204a0 100644 --- a/src/Rectangle.js +++ b/src/Rectangle.js @@ -54,25 +54,99 @@ class Rectangle { } } - initFromMatrixRadius (m, r) { - // const v0 = r; - // const v1 = r; - // const v2 = r; + /** + * Initialize a Rectangle to a 1 unit square transformed by a model matrix. + * @param {Array.} m A 4x4 matrix to transform the rectangle by. + */ + initFromModelMatrix (m) { + // Treat this function like we are transforming a vector with each + // component set to 0.5 by a matrix m. + // const v0 = 0.5; + // const v1 = 0.5; + // const v2 = 0.5; + + // Of the matrix to do this in 2D space, instead of the 3D provided by + // the matrix, we need the 2x2 "top left" that represents the scale and + // rotation ... const m00 = m[(0 * 4) + 0]; const m01 = m[(0 * 4) + 1]; const m10 = m[(1 * 4) + 0]; const m11 = m[(1 * 4) + 1]; + // ... and the 1x2 "top right" that represents position. const m30 = m[(3 * 4) + 0]; const m31 = m[(3 * 4) + 1]; - // var d = v0 * m03 + v1 * m13 + v2 * m23 + m33; - // dst[0] = ( - const x = Math.abs(r * m00) + Math.abs(r * m10); - // + v2 * m20 + m30) / d; - // dst[1] = ( - const y = Math.abs(r * m01) + Math.abs(r * m11); - // + v2 * m21 + m31) / d; - // dst[2] = (v0 * m02 + v1 * m12 + v2 * m22 + m32) / d; + // This is how we would normally transform the vector by the matrix. + // var determinant = v0 * m03 + v1 * m13 + v2 * m23 + m33; + // dst[0] = (v0 * m00 + v1 * m10 + v2 * m20 + m30) / determinant; + // dst[1] = (v0 * m01 + v1 * m11 + v2 * m21 + m31) / determinant; + // dst[2] = (v0 * m02 + v1 * m12 + v2 * m22 + m32) / determinant; + + // We can skip the v2 multiplications and the determinant. + + // Alternatively done with 4 vectors, those vectors would be reflected + // on the x and y axis. We can build those 4 vectors by transforming the + // parts of one vector and reflecting them on the axises after + // multiplication. + + // const x0 = 0.5 * m00; + // const x1 = 0.5 * m10; + // const y0 = 0.5 * m01; + // const y1 = 0.5 * m11; + + // const p0x = x0 + x1; + // const p0y = y0 + y1; + // const p1x = -x0 + x1; + // const p1y = -y0 + y1; + // const p2x = -x0 + -x1; + // const p2y = -y0 + -y1; + // const p3x = x0 + -x1; + // const p3y = y0 + -y1; + + // Since we want to reduce those 4 points to a min and max for each + // axis, we can use those multiplied components to build the min and max + // values without comparing the points. + + // We can start by getting the min and max for each of all the points. + // const left = Math.min(x0 + x1, -x0 + x1, -x0 + -x1, x0 + -x1); + // const right = Math.max(x0 + x1, -x0 + x1, -x0 + -x1, x0 + -x1); + // const top = Math.max(y0 + y1, -y0 + y1, -y0 + -y1, y0 + -y1); + // const bottom = Math.min(y0 + y1, -y0 + y1, -y0 + -y1, y0 + -y1); + + // Each of those can be replaced with min and max operations on the 0 + // and 1 matrix output components. + // const left = Math.min(x0, -x0) + Math.min(x1, -x1); + // const right = Math.max(x0, -x0) + Math.max(x1, -x1); + // const top = Math.max(y0, -y0) + Math.max(y1, -y1); + // const bottom = Math.min(y0, -y0) + Math.min(y1, -y1); + + // And they can be replaced with absolute values. + // const left = -Math.abs(x0) + -Math.abs(x1); + // const right = Math.abs(x0) + Math.abs(x1); + // const top = Math.abs(y0) + Math.abs(y1); + // const bottom = -Math.abs(y0) + -Math.abs(y1); + + // And those with positive and negative sums of the absolute values. + // const left = -(Math.abs(x0) + Math.abs(x1)); + // const right = +(Math.abs(x0) + Math.abs(x1)); + // const top = +(Math.abs(y0) + Math.abs(y1)); + // const bottom = -(Math.abs(y0) + -Math.abs(y1)); + + // We can perform those sums once and reuse them for the bounds. + // const x = Math.abs(x0) + Math.abs(x1); + // const y = Math.abs(y0) + Math.abs(y1); + // const left = -x; + // const right = x; + // const top = y; + // const bottom = -y; + + // Building those absolute sums for the 0.5 vector components by the + // matrix components ... + const x = Math.abs(0.5 * m00) + Math.abs(0.5 * m10); + const y = Math.abs(0.5 * m01) + Math.abs(0.5 * m11); + + // And adding them to the position components in the matrices + // initializes our Rectangle. this.left = -x + m30; this.right = x + m30; this.top = y + m31; From ab603ffa928a6f3eacf24edaee7fc810221160e9 Mon Sep 17 00:00:00 2001 From: "Michael \"Z\" Goddard" Date: Wed, 5 Jun 2019 10:04:14 -0400 Subject: [PATCH 12/38] push skin alteration down from renderwebgl --- src/Drawable.js | 9 --------- src/RenderWebGL.js | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Drawable.js b/src/Drawable.js index 1d90a29..9426ebf 100644 --- a/src/Drawable.js +++ b/src/Drawable.js @@ -3,7 +3,6 @@ const twgl = require('twgl.js'); const Rectangle = require('./Rectangle'); const RenderConstants = require('./RenderConstants'); const ShaderManager = require('./ShaderManager'); -const Skin = require('./Skin'); const EffectTransform = require('./EffectTransform'); /** @@ -101,8 +100,6 @@ class Drawable { /** @todo move convex hull functionality, maybe bounds functionality overall, to Skin classes */ this._convexHullPoints = null; this._convexHullDirty = true; - - this._skinWasAltered = this._skinWasAltered.bind(this); } /** @@ -141,13 +138,7 @@ class Drawable { */ set skin (newSkin) { if (this._skin !== newSkin) { - if (this._skin) { - this._skin.removeListener(Skin.Events.WasAltered, this._skinWasAltered); - } this._skin = newSkin; - if (this._skin) { - this._skin.addListener(Skin.Events.WasAltered, this._skinWasAltered); - } this._skinWasAltered(); } } diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js index 4782455..b0f2145 100644 --- a/src/RenderWebGL.js +++ b/src/RenderWebGL.js @@ -3,6 +3,7 @@ const EventEmitter = require('events'); const hull = require('hull.js'); const twgl = require('twgl.js'); +const Skin = require('./Skin'); const BitmapSkin = require('./BitmapSkin'); const Drawable = require('./Drawable'); const Rectangle = require('./Rectangle'); @@ -290,6 +291,20 @@ class RenderWebGL extends EventEmitter { this.emit(RenderConstants.Events.NativeSizeChanged, {newSize: this._nativeSize}); } + /** + * Notify Drawables whose skin is the skin that changed. + * @param {Skin} skin - the skin that changed. + * @private + */ + _skinWasAltered (skin) { + for (let i = 0; i < this._allDrawables.length; i++) { + const drawable = this._allDrawables[i]; + if (drawable && drawable._skin === skin) { + drawable._skinWasAltered(); + } + } + } + /** * Create a new bitmap skin from a snapshot of the provided bitmap data. * @param {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} bitmapData - new contents for this skin. @@ -302,6 +317,7 @@ class RenderWebGL extends EventEmitter { const skinId = this._nextSkinId++; const newSkin = new BitmapSkin(skinId, this); newSkin.setBitmap(bitmapData, costumeResolution, rotationCenter); + newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } @@ -317,6 +333,7 @@ class RenderWebGL extends EventEmitter { const skinId = this._nextSkinId++; const newSkin = new SVGSkin(skinId, this); newSkin.setSVG(svgData, rotationCenter); + newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } @@ -328,6 +345,7 @@ class RenderWebGL extends EventEmitter { createPenSkin () { const skinId = this._nextSkinId++; const newSkin = new PenSkin(skinId, this); + newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } @@ -344,6 +362,7 @@ class RenderWebGL extends EventEmitter { const skinId = this._nextSkinId++; const newSkin = new TextBubbleSkin(skinId, this); newSkin.setTextBubble(type, text, pointsLeft); + newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } From f9b1a04d1a3238d6f3fd9690d7a063489ac8f998 Mon Sep 17 00:00:00 2001 From: "Michael \"Z\" Goddard" Date: Wed, 5 Jun 2019 17:30:06 -0400 Subject: [PATCH 13/38] cache Skin.size --- src/BitmapSkin.js | 14 +++++++------- src/Skin.js | 15 +++++++-------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/BitmapSkin.js b/src/BitmapSkin.js index b4b03d3..99f8866 100644 --- a/src/BitmapSkin.js +++ b/src/BitmapSkin.js @@ -23,6 +23,12 @@ class BitmapSkin extends Skin { /** @type {Array} */ this._textureSize = [0, 0]; + + /** + * The "native" size, in texels, of this skin. + * @type {Array} + */ + this.size = [0, 0]; } /** @@ -43,13 +49,6 @@ class BitmapSkin extends Skin { return true; } - /** - * @return {Array} the "native" size, in texels, of this skin. - */ - get size () { - return [this._textureSize[0] / this._costumeResolution, this._textureSize[1] / this._costumeResolution]; - } - /** * @param {Array} scale - The scaling factors to be used. * @return {WebGLTexture} The GL texture representation of this skin when drawing at the given scale. @@ -109,6 +108,7 @@ class BitmapSkin extends Skin { // Do these last in case any of the above throws an exception this._costumeResolution = costumeResolution || 2; this._textureSize = BitmapSkin._getBitmapSize(bitmapData); + this.size = [this._textureSize[0] / this._costumeResolution, this._textureSize[1] / this._costumeResolution]; if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter(); this.setRotationCenter.apply(this, rotationCenter); diff --git a/src/Skin.js b/src/Skin.js index e0e7413..bccc9dc 100644 --- a/src/Skin.js +++ b/src/Skin.js @@ -33,6 +33,13 @@ class Skin extends EventEmitter { /** @type {Vec3} */ this._rotationCenter = twgl.v3.create(0, 0); + /** + * The "native" size, in texels, of this skin. + * @member size + * @abstract + * @type {Array} + */ + /** * The uniforms to be used by the vertex and pixel shaders. * Some of these are used by other parts of the renderer as well. @@ -97,14 +104,6 @@ class Skin extends EventEmitter { return this._rotationCenter; } - /** - * @abstract - * @return {Array} the "native" size, in texels, of this skin. - */ - get size () { - return [0, 0]; - } - /** * Set the origin, in object space, about which this Skin should rotate. * @param {number} x - The x coordinate of the new rotation center. From 24b535eb7666775c3f06129bdde0134da7e5d658 Mon Sep 17 00:00:00 2001 From: "Michael \"Z\" Goddard" Date: Wed, 5 Jun 2019 10:06:37 -0400 Subject: [PATCH 14/38] cache svg renderer size and view offset --- src/SVGSkin.js | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/SVGSkin.js b/src/SVGSkin.js index 90e3908..9c10e61 100644 --- a/src/SVGSkin.js +++ b/src/SVGSkin.js @@ -30,6 +30,24 @@ class SVGSkin extends Skin { /** @type {Number} */ this._maxTextureScale = 0; + + /** + * The natural size, in Scratch units, of this skin. + * @type {Array} + */ + this.size = [0, 0]; + + /** + * The viewbox offset of the svg. + * @type {Array} + */ + this._viewOffset = [0, 0]; + + /** + * The rotation center before offset by _viewOffset. + * @type {Array} + */ + this._rawRotationCenter = [NaN, NaN]; } /** @@ -43,21 +61,17 @@ class SVGSkin extends Skin { super.dispose(); } - /** - * @return {Array} the natural size, in Scratch units, of this skin. - */ - get size () { - return this._svgRenderer.size; - } - /** * Set the origin, in object space, about which this Skin should rotate. * @param {number} x - The x coordinate of the new rotation center. * @param {number} y - The y coordinate of the new rotation center. */ setRotationCenter (x, y) { - const viewOffset = this._svgRenderer.viewOffset; - super.setRotationCenter(x - viewOffset[0], y - viewOffset[1]); + if (x !== this._rawRotationCenter[0] || y !== this._rawRotationCenter[1]) { + this._rawRotationCenter[0] = x; + this._rawRotationCenter[1] = y; + super.setRotationCenter(x - this._viewOffset[0], y - this._viewOffset[1]); + } } /** @@ -134,7 +148,11 @@ class SVGSkin extends Skin { } if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter(); - this.setRotationCenter.apply(this, rotationCenter); + this.size = this._svgRenderer.size; + this._viewOffset = this._svgRenderer.viewOffset; + // Reset rawRotationCenter when we update viewOffset. + this._rawRotationCenter = [NaN, NaN]; + this.setRotationCenter(rotationCenter[0], rotationCenter[1]); this.emit(Skin.Events.WasAltered); }); } From 14b01bd63c65f1e135af60eddbf52681a461b545 Mon Sep 17 00:00:00 2001 From: "Michael \"Z\" Goddard" Date: Wed, 12 Jun 2019 16:09:07 -0400 Subject: [PATCH 15/38] cache PenSkin's _canvasSize --- src/PenSkin.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/PenSkin.js b/src/PenSkin.js index 1e500ba..273cac1 100644 --- a/src/PenSkin.js +++ b/src/PenSkin.js @@ -88,6 +88,9 @@ class PenSkin extends Skin { /** @type {HTMLCanvasElement} */ this._canvas = document.createElement('canvas'); + /** @type {Array} */ + this._canvasSize = twgl.v3.create(); + /** @type {WebGLTexture} */ this._texture = null; @@ -165,7 +168,7 @@ class PenSkin extends Skin { * @return {Array} the "native" size, in texels, of this skin. [width, height] */ get size () { - return [this._canvas.width, this._canvas.height]; + return this._canvasSize; } /** @@ -188,13 +191,13 @@ class PenSkin extends Skin { clear () { const gl = this._renderer.gl; twgl.bindFramebufferInfo(gl, this._framebuffer); - + /* Reset framebuffer to transparent black */ 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); + ctx.clearRect(0, 0, this._canvasSize[0], this._canvasSize[1]); this._silhouetteDirty = true; } @@ -451,7 +454,7 @@ class PenSkin extends Skin { * @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) { + _drawRectangle (currentShader, texture, bounds, x = -this._canvasSize[0] / 2, y = this._canvasSize[1] / 2) { const gl = this._renderer.gl; const projection = twgl.m4.ortho( @@ -514,7 +517,7 @@ class PenSkin extends Skin { * @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) { + _drawToBuffer (texture = this._texture, x = -this._canvasSize[0] / 2, y = this._canvasSize[1] / 2) { if (texture !== this._texture && this._canvasDirty) { this._drawToBuffer(); } @@ -528,7 +531,7 @@ class PenSkin extends Skin { 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); + ctx.clearRect(0, 0, this._canvasSize[0], this._canvasSize[1]); this._canvasDirty = false; } @@ -564,8 +567,8 @@ class PenSkin extends Skin { this._bounds = new Rectangle(); this._bounds.initFromBounds(width / 2, width / -2, height / 2, height / -2); - this._canvas.width = width; - this._canvas.height = height; + this._canvas.width = this._canvasSize[0] = width; + this._canvas.height = this._canvasSize[1] = height; this._rotationCenter[0] = width / 2; this._rotationCenter[1] = height / 2; @@ -651,8 +654,8 @@ class PenSkin extends Skin { this._renderer.enterDrawRegion(this._toBufferDrawRegionId); // Sample the framebuffer's pixels into the silhouette instance - const skinPixels = new Uint8Array(Math.floor(this._canvas.width * this._canvas.height * 4)); - gl.readPixels(0, 0, this._canvas.width, this._canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, skinPixels); + const skinPixels = new Uint8Array(Math.floor(this._canvasSize[0] * this._canvasSize[1] * 4)); + gl.readPixels(0, 0, this._canvasSize[0], this._canvasSize[1], gl.RGBA, gl.UNSIGNED_BYTE, skinPixels); const skinCanvas = this._canvas; skinCanvas.width = bounds.width; From f187be6b31afa7253e84fd0c3bb319ea775713c8 Mon Sep 17 00:00:00 2001 From: "Michael \"Z\" Goddard" Date: Thu, 13 Jun 2019 13:06:57 -0400 Subject: [PATCH 16/38] add MockSkinPool for testing --- test/fixtures/MockSkinPool.js | 35 +++++++++++++++++++++++++++++++++++ test/unit/DrawableTests.js | 7 +++++++ 2 files changed, 42 insertions(+) create mode 100644 test/fixtures/MockSkinPool.js diff --git a/test/fixtures/MockSkinPool.js b/test/fixtures/MockSkinPool.js new file mode 100644 index 0000000..208d933 --- /dev/null +++ b/test/fixtures/MockSkinPool.js @@ -0,0 +1,35 @@ +const Skin = require('../../src/Skin'); + +class MockSkinPool { + constructor () { + this._allDrawables = []; + } + + static forDrawableSkin (drawable) { + const pool = new MockSkinPool(); + pool.addDrawable(drawable); + pool.addSkin(drawable.skin); + return pool; + } + + _skinWasAltered (skin) { + for (let i = 0; i < this._allDrawables.length; i++) { + const drawable = this._allDrawables[i]; + if (drawable && drawable._skin === skin) { + drawable._skinWasAltered(); + } + } + } + + addDrawable (drawable) { + this._allDrawables.push(drawable); + return drawable; + } + + addSkin (skin) { + skin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, skin)); + return skin; + } +} + +module.exports = MockSkinPool; diff --git a/test/unit/DrawableTests.js b/test/unit/DrawableTests.js index f0f163b..235e3d3 100644 --- a/test/unit/DrawableTests.js +++ b/test/unit/DrawableTests.js @@ -8,6 +8,7 @@ global.document = { const Drawable = require('../../src/Drawable'); const MockSkin = require('../fixtures/MockSkin'); +const MockSkinPool = require('../fixtures/MockSkinPool'); const Rectangle = require('../../src/Rectangle'); /** @@ -31,6 +32,7 @@ test('translate by position', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; + MockSkinPool.forDrawableSkin(drawable); expected.initFromBounds(0, 200, -50, 0); t.same(snapToNearest(drawable.getAABB()), expected); @@ -47,6 +49,7 @@ test('translate by costume center', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; + MockSkinPool.forDrawableSkin(drawable); drawable.skin.setRotationCenter(1, 0); expected.initFromBounds(-1, 199, -50, 0); @@ -64,6 +67,7 @@ test('translate and rotate', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; + MockSkinPool.forDrawableSkin(drawable); drawable.updateProperties({position: [1, 2], direction: 0}); expected.initFromBounds(1, 51, 2, 202); @@ -90,6 +94,7 @@ test('rotate by non-right-angles', t => { drawable.skin = new MockSkin(); drawable.skin.size = [10, 10]; drawable.skin.setRotationCenter(5, 5); + MockSkinPool.forDrawableSkin(drawable); expected.initFromBounds(-5, 5, -5, 5); t.same(snapToNearest(drawable.getAABB()), expected); @@ -106,6 +111,7 @@ test('scale', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; + MockSkinPool.forDrawableSkin(drawable); drawable.updateProperties({scale: [100, 50]}); expected.initFromBounds(0, 200, -25, 0); @@ -128,6 +134,7 @@ test('rotate and scale', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [100, 1000]; + MockSkinPool.forDrawableSkin(drawable); drawable.skin.setRotationCenter(50, 50); expected.initFromBounds(-50, 50, -950, 50); From b7004878ff403cb2f50794568706f3712f45ed05 Mon Sep 17 00:00:00 2001 From: "Michael \"Z\" Goddard" Date: Wed, 19 Jun 2019 18:36:17 -0400 Subject: [PATCH 17/38] set jsdoc tutorials to come from docs --- .jsdoc.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.jsdoc.json b/.jsdoc.json index f4d8b9f..b047b69 100644 --- a/.jsdoc.json +++ b/.jsdoc.json @@ -15,6 +15,7 @@ "private": true, "readme": "README.md", "recurse": true, - "template": "node_modules/docdash" + "template": "node_modules/docdash", + "tutorials": "docs" } } From 994e9be00bb518cf243cdd52b8b26a5275b20424 Mon Sep 17 00:00:00 2001 From: "Michael \"Z\" Goddard" Date: Wed, 19 Jun 2019 18:36:41 -0400 Subject: [PATCH 18/38] move the initFromModelMatrix process docs into a jsdoc tutorial --- docs/Rectangle-AABB-Matrix.md | 192 ++++++++++++++++++++++++++++++++++ src/Rectangle.js | 96 ++--------------- 2 files changed, 203 insertions(+), 85 deletions(-) create mode 100644 docs/Rectangle-AABB-Matrix.md diff --git a/docs/Rectangle-AABB-Matrix.md b/docs/Rectangle-AABB-Matrix.md new file mode 100644 index 0000000..e0c33b4 --- /dev/null +++ b/docs/Rectangle-AABB-Matrix.md @@ -0,0 +1,192 @@ +# Rectangle AABB Matrix + +Initialize a Rectangle to a 1 unit square centered at 0 x 0 transformed by a model matrix. + +----- + +Every drawable is a 1 x 1 unit square that is rotated by its direction, scaled by its skin size and scale, and offset by its rotation center and position. The square representation is made up of 4 points that are transformed by the drawable properties. Often we want a shape that simplifies those 4 points into a non-rotated shape, a axis aligned bounding box. + +One approach is to compare the x and y components of each transformed vector and find the minimum and maximum x component and the minimum and maximum y component. + +We can start from this approach and determine an alternative one that prodcues the same output with less work. + +Starting with transforming one point, here is a 3D point, `v`, transformation by a matrix, `m`. + +```js +const v0 = v[0]; +const v1 = v[1]; +const v2 = v[2]; + +const d = v0 * m[(0 * 4) + 3] + v1 * m[(1 * 4) + 3] + v2 * m[(2 * 4) + 3] + m[(3 * 4) + 3]; +dst[0] = (v0 * m[(0 * 4) + 0] + v1 * m[(1 * 4) + 0] + v2 * m[(2 * 4) + 0] + m[(3 * 4) + 0]) / d; +dst[1] = (v0 * m[(0 * 4) + 1] + v1 * m[(1 * 4) + 1] + v2 * m[(2 * 4) + 1] + m[(3 * 4) + 1]) / d; +dst[2] = (v0 * m[(0 * 4) + 2] + v1 * m[(1 * 4) + 2] + v2 * m[(2 * 4) + 2] + m[(3 * 4) + 2]) / d; +``` + +As this is a 2D rectangle we can cancel out the third dimension, and the determinant, 'd'. + +```js +const v0 = v[0]; +const v1 = v[1]; + +dst = [ + v0 * m[(0 * 4) + 0] + v1 * m[(1 * 4) + 0] + m[(3 * 4) + 0, + v0 * m[(0 * 4) + 1] + v1 * m[(1 * 4) + 1] + m[(3 * 4) + 1 +]; +``` + +Let's set the matrix points to shorter names for convenience. + +```js +const m00 = m[(0 * 4) + 0]; +const m01 = m[(0 * 4) + 1]; +const m10 = m[(1 * 4) + 0]; +const m11 = m[(1 * 4) + 1]; +const m30 = m[(3 * 4) + 0]; +const m31 = m[(3 * 4) + 1]; +``` + +We need 4 points with positive and negative 0.5 values so the square has sides of length 1. + +```js +let p = [0.5, 0.5]; +let q = [-0.5, 0.5]; +let r = [-0.5, -0.5]; +let s = [0.5, -0.5]; +``` + +Transform the points by the matrix. + +```js +p = [ + 0.5 * m00 + 0.5 * m10 + m30, + 0.5 * m01 + 0.5 * m11 + m31 +]; +q = [ + -0.5 * m00 + -0.5 * m10 + m30, + 0.5 * m01 + 0.5 * m11 + m31 +]; +r = [ + -0.5 * m00 + -0.5 * m10 + m30, + -0.5 * m01 + -0.5 * m11 + m31 +]; +s = [ + 0.5 * m00 + 0.5 * m10 + m30, + -0.5 * m01 + -0.5 * m11 + m31 +]; +``` + +With 4 transformed points we can build the left, right, top, and bottom values for the Rectangle. Each will use the minimum or the maximum of one of the components of all points. + +```js +const left = Math.min(p[0], q[0], r[0], s[0]); +const right = Math.max(p[0], q[0], r[0], s[0]); +const top = Math.max(p[1], q[1], r[1], s[1]); +const bottom = Math.min(p[1], q[1], r[1], s[1]); +``` + +Fill those calls with the vector expressions. + +```js +const left = Math.min( + 0.5 * m00 + 0.5 * m10 + m30, + -0.5 * m00 + 0.5 * m10 + m30, + -0.5 * m00 + -0.5 * m10 + m30, + 0.5 * m00 + -0.5 * m10 + m30 +); +const right = Math.max( + 0.5 * m00 + 0.5 * m10 + m30, + -0.5 * m00 + 0.5 * m10 + m30, + -0.5 * m00 + -0.5 * m10 + m30, + 0.5 * m00 + -0.5 * m10 + m30 +); +const top = Math.max( + 0.5 * m01 + 0.5 * m11 + m31, + -0.5 * m01 + 0.5 * m11 + m31, + -0.5 * m01 + -0.5 * m11 + m31, + 0.5 * m01 + -0.5 * m11 + m31 +); +const bottom = Math.min( + 0.5 * m01 + 0.5 * m11 + m31, + -0.5 * m01 + 0.5 * m11 + m31, + -0.5 * m01 + -0.5 * m11 + m31, + 0.5 * m01 + -0.5 * m11 + m31 +); +``` + +Pull out the `0.5 * m??` patterns. + +```js +const x0 = 0.5 * m00; +const x1 = 0.5 * m10; +const y0 = 0.5 * m01; +const y1 = 0.5 * m11; + +const left = Math.min(x0 + x1 + m30, -x0 + x1 + m30, -x0 + -x1 + m30, x0 + -x1 + m30); +const right = Math.max(x0 + x1 + m30, -x0 + x1 + m30, -x0 + -x1 + m30, x0 + -x1 + m30); +const top = Math.max(y0 + y1 + m31, -y0 + y1 + m31, -y0 + -y1 + m31, y0 + -y1 + m31); +const bottom = Math.min(y0 + y1 + m31, -y0 + y1 + m31, -y0 + -y1 + m31, y0 + -y1 + m31); +``` + +Now each argument for the min and max calls take an expression like `(a * x0 + b * x1 + m3?)`. As each expression has the x0, x1, and m3? variables we can split the min and max calls on the addition operators. Each new call has all the coefficients of that variable. + +```js +const left = Math.min(x0, -x0) + Math.min(x1, -x1) + Math.min(m30, m30); +const right = Math.max(x0, -x0) + Math.max(x1, -x1) + Math.max(m30, m30); +const top = Math.max(y0, -y0) + Math.max(y1, -y1) + Math.max(m31, m31); +const bottom = Math.min(y0, -y0) + Math.min(y1, -y1) + Math.min(m31, m31); +``` + +The min or max of two copies of the same value will just be that value. + +```js +const left = Math.min(x0, -x0) + Math.min(x1, -x1) + m30; +const right = Math.max(x0, -x0) + Math.max(x1, -x1) + m30; +const top = Math.max(y0, -y0) + Math.max(y1, -y1) + m31; +const bottom = Math.min(y0, -y0) + Math.min(y1, -y1) + m31; +``` + +The max of a negative and positive variable will be the absolute value of that variable. The min of a negative and positive variable will the negated absolute value of that variable. + +```js +const left = -Math.abs(x0) + -Math.abs(x1) + m30; +const right = Math.abs(x0) + Math.abs(x1) + m30; +const top = Math.abs(y0) + Math.abs(y1) + m31; +const bottom = -Math.abs(y0) + -Math.abs(y1) + m31; +``` + +Pulling out the negations of the absolute values, left and right as well as top and bottom are the positive or negative sum of the absolute value of the saled and rotated unit value. + +```js +const left = -(Math.abs(x0) + Math.abs(x1)) + m30; +const right = Math.abs(x0) + Math.abs(x1) + m30; +const top = Math.abs(y0) + Math.abs(y1) + m31; +const bottom = -(Math.abs(y0) + Math.abs(y1)) + m31; +``` + +We call pull out those sums and use them twice. + +```js +const x = Math.abs(x0) + Math.abs(x1); +const y = Math.abs(y0) + Math.abs(y1); + +const left = -x + m30; +const right = x + m30; +const top = y + m31; +const bottom = -y + m31; +``` + +This lets us arrive at our goal. Inlining some of our variables we get this block that will initialize a Rectangle to a unit square transformed by a matrix. + +```js +const m30 = m[(3 * 4) + 0]; +const m31 = m[(3 * 4) + 1]; + +const x = Math.abs(0.5 * m[(0 * 4) + 0]) + Math.abs(0.5 * m[(1 * 4) + 0]); +const y = Math.abs(0.5 * m[(0 * 4) + 1]) + Math.abs(0.5 * m[(1 * 4) + 1]); + +const left = -x + m30; +const right = x + m30; +const top = y + m31; +const bottom = -y + m31; +``` diff --git a/src/Rectangle.js b/src/Rectangle.js index 56204a0..6556cb1 100644 --- a/src/Rectangle.js +++ b/src/Rectangle.js @@ -55,98 +55,24 @@ class Rectangle { } /** - * Initialize a Rectangle to a 1 unit square transformed by a model matrix. + * Initialize a Rectangle to a 1 unit square centered at 0 x 0 transformed + * by a model matrix. * @param {Array.} m A 4x4 matrix to transform the rectangle by. + * @tutorial Rectangle-AABB-Matrix */ initFromModelMatrix (m) { - // Treat this function like we are transforming a vector with each - // component set to 0.5 by a matrix m. - // const v0 = 0.5; - // const v1 = 0.5; - // const v2 = 0.5; - - // Of the matrix to do this in 2D space, instead of the 3D provided by - // the matrix, we need the 2x2 "top left" that represents the scale and - // rotation ... - const m00 = m[(0 * 4) + 0]; - const m01 = m[(0 * 4) + 1]; - const m10 = m[(1 * 4) + 0]; - const m11 = m[(1 * 4) + 1]; - // ... and the 1x2 "top right" that represents position. + // In 2D space, we will soon use the 2x2 "top left" scale and rotation + // submatrix, while we store and the 1x2 "top right" that position + // vector. const m30 = m[(3 * 4) + 0]; const m31 = m[(3 * 4) + 1]; - // This is how we would normally transform the vector by the matrix. - // var determinant = v0 * m03 + v1 * m13 + v2 * m23 + m33; - // dst[0] = (v0 * m00 + v1 * m10 + v2 * m20 + m30) / determinant; - // dst[1] = (v0 * m01 + v1 * m11 + v2 * m21 + m31) / determinant; - // dst[2] = (v0 * m02 + v1 * m12 + v2 * m22 + m32) / determinant; + // "Transform" a (0.5, 0.5) vector by the scale and rotation matrix but + // sum the absolute of each component instead of use the signed values. + const x = Math.abs(0.5 * m[(0 * 4) + 0]) + Math.abs(0.5 * m[(1 * 4) + 0]); + const y = Math.abs(0.5 * m[(0 * 4) + 1]) + Math.abs(0.5 * m[(1 * 4) + 1]); - // We can skip the v2 multiplications and the determinant. - - // Alternatively done with 4 vectors, those vectors would be reflected - // on the x and y axis. We can build those 4 vectors by transforming the - // parts of one vector and reflecting them on the axises after - // multiplication. - - // const x0 = 0.5 * m00; - // const x1 = 0.5 * m10; - // const y0 = 0.5 * m01; - // const y1 = 0.5 * m11; - - // const p0x = x0 + x1; - // const p0y = y0 + y1; - // const p1x = -x0 + x1; - // const p1y = -y0 + y1; - // const p2x = -x0 + -x1; - // const p2y = -y0 + -y1; - // const p3x = x0 + -x1; - // const p3y = y0 + -y1; - - // Since we want to reduce those 4 points to a min and max for each - // axis, we can use those multiplied components to build the min and max - // values without comparing the points. - - // We can start by getting the min and max for each of all the points. - // const left = Math.min(x0 + x1, -x0 + x1, -x0 + -x1, x0 + -x1); - // const right = Math.max(x0 + x1, -x0 + x1, -x0 + -x1, x0 + -x1); - // const top = Math.max(y0 + y1, -y0 + y1, -y0 + -y1, y0 + -y1); - // const bottom = Math.min(y0 + y1, -y0 + y1, -y0 + -y1, y0 + -y1); - - // Each of those can be replaced with min and max operations on the 0 - // and 1 matrix output components. - // const left = Math.min(x0, -x0) + Math.min(x1, -x1); - // const right = Math.max(x0, -x0) + Math.max(x1, -x1); - // const top = Math.max(y0, -y0) + Math.max(y1, -y1); - // const bottom = Math.min(y0, -y0) + Math.min(y1, -y1); - - // And they can be replaced with absolute values. - // const left = -Math.abs(x0) + -Math.abs(x1); - // const right = Math.abs(x0) + Math.abs(x1); - // const top = Math.abs(y0) + Math.abs(y1); - // const bottom = -Math.abs(y0) + -Math.abs(y1); - - // And those with positive and negative sums of the absolute values. - // const left = -(Math.abs(x0) + Math.abs(x1)); - // const right = +(Math.abs(x0) + Math.abs(x1)); - // const top = +(Math.abs(y0) + Math.abs(y1)); - // const bottom = -(Math.abs(y0) + -Math.abs(y1)); - - // We can perform those sums once and reuse them for the bounds. - // const x = Math.abs(x0) + Math.abs(x1); - // const y = Math.abs(y0) + Math.abs(y1); - // const left = -x; - // const right = x; - // const top = y; - // const bottom = -y; - - // Building those absolute sums for the 0.5 vector components by the - // matrix components ... - const x = Math.abs(0.5 * m00) + Math.abs(0.5 * m10); - const y = Math.abs(0.5 * m01) + Math.abs(0.5 * m11); - - // And adding them to the position components in the matrices - // initializes our Rectangle. + // And adding them to the position components initializes our Rectangle. this.left = -x + m30; this.right = x + m30; this.top = y + m31; From ae507dfabbc8a31533802527ecb140a6033aa0ca Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" <23040076+greenkeeper[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2019 15:16:46 +0000 Subject: [PATCH 19/38] fix(package): update scratch-svg-renderer to version 0.2.0-prerelease.20190715144718 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a02231b..e365135 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "minilog": "3.1.0", "raw-loader": "^0.5.1", "scratch-storage": "^1.0.0", - "scratch-svg-renderer": "0.2.0-prerelease.20190523193400", + "scratch-svg-renderer": "0.2.0-prerelease.20190715144718", "twgl.js": "4.4.0" } } From fcda622b5affdfac8c3e30b9915d36332c5b9910 Mon Sep 17 00:00:00 2001 From: DD Liu Date: Mon, 15 Jul 2019 11:50:14 -0400 Subject: [PATCH 20/38] Update to latest svg renderer --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e365135..144119d 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "minilog": "3.1.0", "raw-loader": "^0.5.1", "scratch-storage": "^1.0.0", - "scratch-svg-renderer": "0.2.0-prerelease.20190715144718", + "scratch-svg-renderer": "0.2.0-prerelease.20190715153806", "twgl.js": "4.4.0" } } From 471d4b91a4d98e140fa21910a10e0d5f46093347 Mon Sep 17 00:00:00 2001 From: adroitwhiz Date: Mon, 15 Jul 2019 23:44:18 -0400 Subject: [PATCH 21/38] Attach V2 adapters in CPU render playground --- test/integration/cpu-render.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/integration/cpu-render.html b/test/integration/cpu-render.html index 2fecd75..d6d3630 100644 --- a/test/integration/cpu-render.html +++ b/test/integration/cpu-render.html @@ -1,6 +1,7 @@ + @@ -21,6 +22,8 @@ vm.attachStorage(storage); vm.attachRenderer(render); + vm.attachV2SVGAdapter(new ScratchSVGRenderer.SVGRenderer()); + vm.attachV2BitmapAdapter(new ScratchSVGRenderer.BitmapAdapter()); document.getElementById('file').addEventListener('click', e => { document.body.removeChild(document.getElementById('loaded')); From dce90a3f56e0e1751dfb94e6b7485ba52e3efb0e Mon Sep 17 00:00:00 2001 From: adroitwhiz Date: Sat, 20 Jul 2019 17:27:45 -0400 Subject: [PATCH 22/38] Fix calculateRotationCenter for SVG skins --- src/SVGSkin.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/SVGSkin.js b/src/SVGSkin.js index 9c10e61..1d72c86 100644 --- a/src/SVGSkin.js +++ b/src/SVGSkin.js @@ -147,8 +147,9 @@ class SVGSkin extends Skin { this._maxTextureScale = testScale; } - if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter(); this.size = this._svgRenderer.size; + if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter(); + this._viewOffset = this._svgRenderer.viewOffset; // Reset rawRotationCenter when we update viewOffset. this._rawRotationCenter = [NaN, NaN]; From 4a28cffcd46d75fcbba01f32eba65c83824dbb0f Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Tue, 13 Aug 2019 11:22:27 -0400 Subject: [PATCH 23/38] Revert "Skin alter push" --- src/BitmapSkin.js | 14 ++++++------- src/Drawable.js | 9 +++++++++ src/PenSkin.js | 23 +++++++++------------ src/RenderWebGL.js | 19 ------------------ src/SVGSkin.js | 38 +++++++++-------------------------- src/Skin.js | 15 +++++++------- test/fixtures/MockSkinPool.js | 35 -------------------------------- test/unit/DrawableTests.js | 7 ------- 8 files changed, 44 insertions(+), 116 deletions(-) delete mode 100644 test/fixtures/MockSkinPool.js diff --git a/src/BitmapSkin.js b/src/BitmapSkin.js index 13d17bd..94f2984 100644 --- a/src/BitmapSkin.js +++ b/src/BitmapSkin.js @@ -23,12 +23,6 @@ class BitmapSkin extends Skin { /** @type {Array} */ this._textureSize = [0, 0]; - - /** - * The "native" size, in texels, of this skin. - * @type {Array} - */ - this.size = [0, 0]; } /** @@ -49,6 +43,13 @@ class BitmapSkin extends Skin { return true; } + /** + * @return {Array} the "native" size, in texels, of this skin. + */ + get size () { + return [this._textureSize[0] / this._costumeResolution, this._textureSize[1] / this._costumeResolution]; + } + /** * @param {Array} scale - The scaling factors to be used. * @return {WebGLTexture} The GL texture representation of this skin when drawing at the given scale. @@ -109,7 +110,6 @@ class BitmapSkin extends Skin { // Do these last in case any of the above throws an exception this._costumeResolution = costumeResolution || 2; this._textureSize = BitmapSkin._getBitmapSize(bitmapData); - this.size = [this._textureSize[0] / this._costumeResolution, this._textureSize[1] / this._costumeResolution]; if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter(); this.setRotationCenter.apply(this, rotationCenter); diff --git a/src/Drawable.js b/src/Drawable.js index f97b4e7..ea94acd 100644 --- a/src/Drawable.js +++ b/src/Drawable.js @@ -3,6 +3,7 @@ const twgl = require('twgl.js'); const Rectangle = require('./Rectangle'); const RenderConstants = require('./RenderConstants'); const ShaderManager = require('./ShaderManager'); +const Skin = require('./Skin'); const EffectTransform = require('./EffectTransform'); /** @@ -100,6 +101,8 @@ class Drawable { /** @todo move convex hull functionality, maybe bounds functionality overall, to Skin classes */ this._convexHullPoints = null; this._convexHullDirty = true; + + this._skinWasAltered = this._skinWasAltered.bind(this); } /** @@ -138,7 +141,13 @@ class Drawable { */ set skin (newSkin) { if (this._skin !== newSkin) { + if (this._skin) { + this._skin.removeListener(Skin.Events.WasAltered, this._skinWasAltered); + } this._skin = newSkin; + if (this._skin) { + this._skin.addListener(Skin.Events.WasAltered, this._skinWasAltered); + } this._skinWasAltered(); } } diff --git a/src/PenSkin.js b/src/PenSkin.js index 273cac1..1e500ba 100644 --- a/src/PenSkin.js +++ b/src/PenSkin.js @@ -88,9 +88,6 @@ class PenSkin extends Skin { /** @type {HTMLCanvasElement} */ this._canvas = document.createElement('canvas'); - /** @type {Array} */ - this._canvasSize = twgl.v3.create(); - /** @type {WebGLTexture} */ this._texture = null; @@ -168,7 +165,7 @@ class PenSkin extends Skin { * @return {Array} the "native" size, in texels, of this skin. [width, height] */ get size () { - return this._canvasSize; + return [this._canvas.width, this._canvas.height]; } /** @@ -191,13 +188,13 @@ class PenSkin extends Skin { clear () { const gl = this._renderer.gl; twgl.bindFramebufferInfo(gl, this._framebuffer); - + /* Reset framebuffer to transparent black */ gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); const ctx = this._canvas.getContext('2d'); - ctx.clearRect(0, 0, this._canvasSize[0], this._canvasSize[1]); + ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); this._silhouetteDirty = true; } @@ -454,7 +451,7 @@ class PenSkin extends Skin { * @param {number} x - centered at x * @param {number} y - centered at y */ - _drawRectangle (currentShader, texture, bounds, x = -this._canvasSize[0] / 2, y = this._canvasSize[1] / 2) { + _drawRectangle (currentShader, texture, bounds, x = -this._canvas.width / 2, y = this._canvas.height / 2) { const gl = this._renderer.gl; const projection = twgl.m4.ortho( @@ -517,7 +514,7 @@ class PenSkin extends Skin { * @param {number} x - texture centered at x * @param {number} y - texture centered at y */ - _drawToBuffer (texture = this._texture, x = -this._canvasSize[0] / 2, y = this._canvasSize[1] / 2) { + _drawToBuffer (texture = this._texture, x = -this._canvas.width / 2, y = this._canvas.height / 2) { if (texture !== this._texture && this._canvasDirty) { this._drawToBuffer(); } @@ -531,7 +528,7 @@ class PenSkin extends Skin { 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._canvasSize[0], this._canvasSize[1]); + ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); this._canvasDirty = false; } @@ -567,8 +564,8 @@ class PenSkin extends Skin { this._bounds = new Rectangle(); this._bounds.initFromBounds(width / 2, width / -2, height / 2, height / -2); - this._canvas.width = this._canvasSize[0] = width; - this._canvas.height = this._canvasSize[1] = height; + this._canvas.width = width; + this._canvas.height = height; this._rotationCenter[0] = width / 2; this._rotationCenter[1] = height / 2; @@ -654,8 +651,8 @@ class PenSkin extends Skin { this._renderer.enterDrawRegion(this._toBufferDrawRegionId); // Sample the framebuffer's pixels into the silhouette instance - const skinPixels = new Uint8Array(Math.floor(this._canvasSize[0] * this._canvasSize[1] * 4)); - gl.readPixels(0, 0, this._canvasSize[0], this._canvasSize[1], gl.RGBA, gl.UNSIGNED_BYTE, skinPixels); + const skinPixels = new Uint8Array(Math.floor(this._canvas.width * this._canvas.height * 4)); + gl.readPixels(0, 0, this._canvas.width, this._canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, skinPixels); const skinCanvas = this._canvas; skinCanvas.width = bounds.width; diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js index d9f5ae2..ba629ea 100644 --- a/src/RenderWebGL.js +++ b/src/RenderWebGL.js @@ -3,7 +3,6 @@ const EventEmitter = require('events'); const hull = require('hull.js'); const twgl = require('twgl.js'); -const Skin = require('./Skin'); const BitmapSkin = require('./BitmapSkin'); const Drawable = require('./Drawable'); const Rectangle = require('./Rectangle'); @@ -292,20 +291,6 @@ class RenderWebGL extends EventEmitter { this.emit(RenderConstants.Events.NativeSizeChanged, {newSize: this._nativeSize}); } - /** - * Notify Drawables whose skin is the skin that changed. - * @param {Skin} skin - the skin that changed. - * @private - */ - _skinWasAltered (skin) { - for (let i = 0; i < this._allDrawables.length; i++) { - const drawable = this._allDrawables[i]; - if (drawable && drawable._skin === skin) { - drawable._skinWasAltered(); - } - } - } - /** * Create a new bitmap skin from a snapshot of the provided bitmap data. * @param {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} bitmapData - new contents for this skin. @@ -318,7 +303,6 @@ class RenderWebGL extends EventEmitter { const skinId = this._nextSkinId++; const newSkin = new BitmapSkin(skinId, this); newSkin.setBitmap(bitmapData, costumeResolution, rotationCenter); - newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } @@ -334,7 +318,6 @@ class RenderWebGL extends EventEmitter { const skinId = this._nextSkinId++; const newSkin = new SVGSkin(skinId, this); newSkin.setSVG(svgData, rotationCenter); - newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } @@ -346,7 +329,6 @@ class RenderWebGL extends EventEmitter { createPenSkin () { const skinId = this._nextSkinId++; const newSkin = new PenSkin(skinId, this); - newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } @@ -363,7 +345,6 @@ class RenderWebGL extends EventEmitter { const skinId = this._nextSkinId++; const newSkin = new TextBubbleSkin(skinId, this); newSkin.setTextBubble(type, text, pointsLeft); - newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } diff --git a/src/SVGSkin.js b/src/SVGSkin.js index 9c10e61..90e3908 100644 --- a/src/SVGSkin.js +++ b/src/SVGSkin.js @@ -30,24 +30,6 @@ class SVGSkin extends Skin { /** @type {Number} */ this._maxTextureScale = 0; - - /** - * The natural size, in Scratch units, of this skin. - * @type {Array} - */ - this.size = [0, 0]; - - /** - * The viewbox offset of the svg. - * @type {Array} - */ - this._viewOffset = [0, 0]; - - /** - * The rotation center before offset by _viewOffset. - * @type {Array} - */ - this._rawRotationCenter = [NaN, NaN]; } /** @@ -61,17 +43,21 @@ class SVGSkin extends Skin { super.dispose(); } + /** + * @return {Array} the natural size, in Scratch units, of this skin. + */ + get size () { + return this._svgRenderer.size; + } + /** * Set the origin, in object space, about which this Skin should rotate. * @param {number} x - The x coordinate of the new rotation center. * @param {number} y - The y coordinate of the new rotation center. */ setRotationCenter (x, y) { - if (x !== this._rawRotationCenter[0] || y !== this._rawRotationCenter[1]) { - this._rawRotationCenter[0] = x; - this._rawRotationCenter[1] = y; - super.setRotationCenter(x - this._viewOffset[0], y - this._viewOffset[1]); - } + const viewOffset = this._svgRenderer.viewOffset; + super.setRotationCenter(x - viewOffset[0], y - viewOffset[1]); } /** @@ -148,11 +134,7 @@ class SVGSkin extends Skin { } if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter(); - this.size = this._svgRenderer.size; - this._viewOffset = this._svgRenderer.viewOffset; - // Reset rawRotationCenter when we update viewOffset. - this._rawRotationCenter = [NaN, NaN]; - this.setRotationCenter(rotationCenter[0], rotationCenter[1]); + this.setRotationCenter.apply(this, rotationCenter); this.emit(Skin.Events.WasAltered); }); } diff --git a/src/Skin.js b/src/Skin.js index 8bd89d6..473c70b 100644 --- a/src/Skin.js +++ b/src/Skin.js @@ -33,13 +33,6 @@ class Skin extends EventEmitter { /** @type {Vec3} */ this._rotationCenter = twgl.v3.create(0, 0); - /** - * The "native" size, in texels, of this skin. - * @member size - * @abstract - * @type {Array} - */ - /** * The uniforms to be used by the vertex and pixel shaders. * Some of these are used by other parts of the renderer as well. @@ -104,6 +97,14 @@ class Skin extends EventEmitter { return this._rotationCenter; } + /** + * @abstract + * @return {Array} the "native" size, in texels, of this skin. + */ + get size () { + return [0, 0]; + } + /** * Set the origin, in object space, about which this Skin should rotate. * @param {number} x - The x coordinate of the new rotation center. diff --git a/test/fixtures/MockSkinPool.js b/test/fixtures/MockSkinPool.js deleted file mode 100644 index 208d933..0000000 --- a/test/fixtures/MockSkinPool.js +++ /dev/null @@ -1,35 +0,0 @@ -const Skin = require('../../src/Skin'); - -class MockSkinPool { - constructor () { - this._allDrawables = []; - } - - static forDrawableSkin (drawable) { - const pool = new MockSkinPool(); - pool.addDrawable(drawable); - pool.addSkin(drawable.skin); - return pool; - } - - _skinWasAltered (skin) { - for (let i = 0; i < this._allDrawables.length; i++) { - const drawable = this._allDrawables[i]; - if (drawable && drawable._skin === skin) { - drawable._skinWasAltered(); - } - } - } - - addDrawable (drawable) { - this._allDrawables.push(drawable); - return drawable; - } - - addSkin (skin) { - skin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, skin)); - return skin; - } -} - -module.exports = MockSkinPool; diff --git a/test/unit/DrawableTests.js b/test/unit/DrawableTests.js index 235e3d3..f0f163b 100644 --- a/test/unit/DrawableTests.js +++ b/test/unit/DrawableTests.js @@ -8,7 +8,6 @@ global.document = { const Drawable = require('../../src/Drawable'); const MockSkin = require('../fixtures/MockSkin'); -const MockSkinPool = require('../fixtures/MockSkinPool'); const Rectangle = require('../../src/Rectangle'); /** @@ -32,7 +31,6 @@ test('translate by position', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; - MockSkinPool.forDrawableSkin(drawable); expected.initFromBounds(0, 200, -50, 0); t.same(snapToNearest(drawable.getAABB()), expected); @@ -49,7 +47,6 @@ test('translate by costume center', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; - MockSkinPool.forDrawableSkin(drawable); drawable.skin.setRotationCenter(1, 0); expected.initFromBounds(-1, 199, -50, 0); @@ -67,7 +64,6 @@ test('translate and rotate', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; - MockSkinPool.forDrawableSkin(drawable); drawable.updateProperties({position: [1, 2], direction: 0}); expected.initFromBounds(1, 51, 2, 202); @@ -94,7 +90,6 @@ test('rotate by non-right-angles', t => { drawable.skin = new MockSkin(); drawable.skin.size = [10, 10]; drawable.skin.setRotationCenter(5, 5); - MockSkinPool.forDrawableSkin(drawable); expected.initFromBounds(-5, 5, -5, 5); t.same(snapToNearest(drawable.getAABB()), expected); @@ -111,7 +106,6 @@ test('scale', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; - MockSkinPool.forDrawableSkin(drawable); drawable.updateProperties({scale: [100, 50]}); expected.initFromBounds(0, 200, -25, 0); @@ -134,7 +128,6 @@ test('rotate and scale', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [100, 1000]; - MockSkinPool.forDrawableSkin(drawable); drawable.skin.setRotationCenter(50, 50); expected.initFromBounds(-50, 50, -950, 50); From c8f7496fba819f3fa65f51778b56ffd7f916929d Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Tue, 13 Aug 2019 11:26:07 -0400 Subject: [PATCH 24/38] Revert "Initialialize AABB Rectangle " --- .jsdoc.json | 3 +- docs/Rectangle-AABB-Matrix.md | 192 ---------------------------------- src/BitmapSkin.js | 5 +- src/Drawable.js | 39 +++---- src/Rectangle.js | 25 ----- src/RenderWebGL.js | 7 +- src/Skin.js | 5 +- 7 files changed, 28 insertions(+), 248 deletions(-) delete mode 100644 docs/Rectangle-AABB-Matrix.md diff --git a/.jsdoc.json b/.jsdoc.json index b047b69..f4d8b9f 100644 --- a/.jsdoc.json +++ b/.jsdoc.json @@ -15,7 +15,6 @@ "private": true, "readme": "README.md", "recurse": true, - "template": "node_modules/docdash", - "tutorials": "docs" + "template": "node_modules/docdash" } } diff --git a/docs/Rectangle-AABB-Matrix.md b/docs/Rectangle-AABB-Matrix.md deleted file mode 100644 index e0c33b4..0000000 --- a/docs/Rectangle-AABB-Matrix.md +++ /dev/null @@ -1,192 +0,0 @@ -# Rectangle AABB Matrix - -Initialize a Rectangle to a 1 unit square centered at 0 x 0 transformed by a model matrix. - ------ - -Every drawable is a 1 x 1 unit square that is rotated by its direction, scaled by its skin size and scale, and offset by its rotation center and position. The square representation is made up of 4 points that are transformed by the drawable properties. Often we want a shape that simplifies those 4 points into a non-rotated shape, a axis aligned bounding box. - -One approach is to compare the x and y components of each transformed vector and find the minimum and maximum x component and the minimum and maximum y component. - -We can start from this approach and determine an alternative one that prodcues the same output with less work. - -Starting with transforming one point, here is a 3D point, `v`, transformation by a matrix, `m`. - -```js -const v0 = v[0]; -const v1 = v[1]; -const v2 = v[2]; - -const d = v0 * m[(0 * 4) + 3] + v1 * m[(1 * 4) + 3] + v2 * m[(2 * 4) + 3] + m[(3 * 4) + 3]; -dst[0] = (v0 * m[(0 * 4) + 0] + v1 * m[(1 * 4) + 0] + v2 * m[(2 * 4) + 0] + m[(3 * 4) + 0]) / d; -dst[1] = (v0 * m[(0 * 4) + 1] + v1 * m[(1 * 4) + 1] + v2 * m[(2 * 4) + 1] + m[(3 * 4) + 1]) / d; -dst[2] = (v0 * m[(0 * 4) + 2] + v1 * m[(1 * 4) + 2] + v2 * m[(2 * 4) + 2] + m[(3 * 4) + 2]) / d; -``` - -As this is a 2D rectangle we can cancel out the third dimension, and the determinant, 'd'. - -```js -const v0 = v[0]; -const v1 = v[1]; - -dst = [ - v0 * m[(0 * 4) + 0] + v1 * m[(1 * 4) + 0] + m[(3 * 4) + 0, - v0 * m[(0 * 4) + 1] + v1 * m[(1 * 4) + 1] + m[(3 * 4) + 1 -]; -``` - -Let's set the matrix points to shorter names for convenience. - -```js -const m00 = m[(0 * 4) + 0]; -const m01 = m[(0 * 4) + 1]; -const m10 = m[(1 * 4) + 0]; -const m11 = m[(1 * 4) + 1]; -const m30 = m[(3 * 4) + 0]; -const m31 = m[(3 * 4) + 1]; -``` - -We need 4 points with positive and negative 0.5 values so the square has sides of length 1. - -```js -let p = [0.5, 0.5]; -let q = [-0.5, 0.5]; -let r = [-0.5, -0.5]; -let s = [0.5, -0.5]; -``` - -Transform the points by the matrix. - -```js -p = [ - 0.5 * m00 + 0.5 * m10 + m30, - 0.5 * m01 + 0.5 * m11 + m31 -]; -q = [ - -0.5 * m00 + -0.5 * m10 + m30, - 0.5 * m01 + 0.5 * m11 + m31 -]; -r = [ - -0.5 * m00 + -0.5 * m10 + m30, - -0.5 * m01 + -0.5 * m11 + m31 -]; -s = [ - 0.5 * m00 + 0.5 * m10 + m30, - -0.5 * m01 + -0.5 * m11 + m31 -]; -``` - -With 4 transformed points we can build the left, right, top, and bottom values for the Rectangle. Each will use the minimum or the maximum of one of the components of all points. - -```js -const left = Math.min(p[0], q[0], r[0], s[0]); -const right = Math.max(p[0], q[0], r[0], s[0]); -const top = Math.max(p[1], q[1], r[1], s[1]); -const bottom = Math.min(p[1], q[1], r[1], s[1]); -``` - -Fill those calls with the vector expressions. - -```js -const left = Math.min( - 0.5 * m00 + 0.5 * m10 + m30, - -0.5 * m00 + 0.5 * m10 + m30, - -0.5 * m00 + -0.5 * m10 + m30, - 0.5 * m00 + -0.5 * m10 + m30 -); -const right = Math.max( - 0.5 * m00 + 0.5 * m10 + m30, - -0.5 * m00 + 0.5 * m10 + m30, - -0.5 * m00 + -0.5 * m10 + m30, - 0.5 * m00 + -0.5 * m10 + m30 -); -const top = Math.max( - 0.5 * m01 + 0.5 * m11 + m31, - -0.5 * m01 + 0.5 * m11 + m31, - -0.5 * m01 + -0.5 * m11 + m31, - 0.5 * m01 + -0.5 * m11 + m31 -); -const bottom = Math.min( - 0.5 * m01 + 0.5 * m11 + m31, - -0.5 * m01 + 0.5 * m11 + m31, - -0.5 * m01 + -0.5 * m11 + m31, - 0.5 * m01 + -0.5 * m11 + m31 -); -``` - -Pull out the `0.5 * m??` patterns. - -```js -const x0 = 0.5 * m00; -const x1 = 0.5 * m10; -const y0 = 0.5 * m01; -const y1 = 0.5 * m11; - -const left = Math.min(x0 + x1 + m30, -x0 + x1 + m30, -x0 + -x1 + m30, x0 + -x1 + m30); -const right = Math.max(x0 + x1 + m30, -x0 + x1 + m30, -x0 + -x1 + m30, x0 + -x1 + m30); -const top = Math.max(y0 + y1 + m31, -y0 + y1 + m31, -y0 + -y1 + m31, y0 + -y1 + m31); -const bottom = Math.min(y0 + y1 + m31, -y0 + y1 + m31, -y0 + -y1 + m31, y0 + -y1 + m31); -``` - -Now each argument for the min and max calls take an expression like `(a * x0 + b * x1 + m3?)`. As each expression has the x0, x1, and m3? variables we can split the min and max calls on the addition operators. Each new call has all the coefficients of that variable. - -```js -const left = Math.min(x0, -x0) + Math.min(x1, -x1) + Math.min(m30, m30); -const right = Math.max(x0, -x0) + Math.max(x1, -x1) + Math.max(m30, m30); -const top = Math.max(y0, -y0) + Math.max(y1, -y1) + Math.max(m31, m31); -const bottom = Math.min(y0, -y0) + Math.min(y1, -y1) + Math.min(m31, m31); -``` - -The min or max of two copies of the same value will just be that value. - -```js -const left = Math.min(x0, -x0) + Math.min(x1, -x1) + m30; -const right = Math.max(x0, -x0) + Math.max(x1, -x1) + m30; -const top = Math.max(y0, -y0) + Math.max(y1, -y1) + m31; -const bottom = Math.min(y0, -y0) + Math.min(y1, -y1) + m31; -``` - -The max of a negative and positive variable will be the absolute value of that variable. The min of a negative and positive variable will the negated absolute value of that variable. - -```js -const left = -Math.abs(x0) + -Math.abs(x1) + m30; -const right = Math.abs(x0) + Math.abs(x1) + m30; -const top = Math.abs(y0) + Math.abs(y1) + m31; -const bottom = -Math.abs(y0) + -Math.abs(y1) + m31; -``` - -Pulling out the negations of the absolute values, left and right as well as top and bottom are the positive or negative sum of the absolute value of the saled and rotated unit value. - -```js -const left = -(Math.abs(x0) + Math.abs(x1)) + m30; -const right = Math.abs(x0) + Math.abs(x1) + m30; -const top = Math.abs(y0) + Math.abs(y1) + m31; -const bottom = -(Math.abs(y0) + Math.abs(y1)) + m31; -``` - -We call pull out those sums and use them twice. - -```js -const x = Math.abs(x0) + Math.abs(x1); -const y = Math.abs(y0) + Math.abs(y1); - -const left = -x + m30; -const right = x + m30; -const top = y + m31; -const bottom = -y + m31; -``` - -This lets us arrive at our goal. Inlining some of our variables we get this block that will initialize a Rectangle to a unit square transformed by a matrix. - -```js -const m30 = m[(3 * 4) + 0]; -const m31 = m[(3 * 4) + 1]; - -const x = Math.abs(0.5 * m[(0 * 4) + 0]) + Math.abs(0.5 * m[(1 * 4) + 0]); -const y = Math.abs(0.5 * m[(0 * 4) + 1]) + Math.abs(0.5 * m[(1 * 4) + 1]); - -const left = -x + m30; -const right = x + m30; -const top = y + m31; -const bottom = -y + m31; -``` diff --git a/src/BitmapSkin.js b/src/BitmapSkin.js index 13d17bd..99f8866 100644 --- a/src/BitmapSkin.js +++ b/src/BitmapSkin.js @@ -61,11 +61,10 @@ class BitmapSkin extends Skin { /** * Get the bounds of the drawable for determining its fenced position. * @param {Array} drawable - The Drawable instance this skin is using. - * @param {?Rectangle} result - Optional destination for bounds calculation. * @return {!Rectangle} The drawable's bounds. For compatibility with Scratch 2, we always use getAABB for bitmaps. */ - getFenceBounds (drawable, result) { - return drawable.getAABB(result); + getFenceBounds (drawable) { + return drawable.getAABB(); } /** diff --git a/src/Drawable.js b/src/Drawable.js index f97b4e7..9426ebf 100644 --- a/src/Drawable.js +++ b/src/Drawable.js @@ -442,10 +442,9 @@ class Drawable { * This function applies the transform matrix to the known convex hull, * and then finds the minimum box along the axes. * Before calling this, ensure the renderer has updated convex hull points. - * @param {?Rectangle} result optional destination for bounds calculation * @return {!Rectangle} Bounds for a tight box around the Drawable. */ - getBounds (result) { + getBounds () { if (this.needsConvexHullPoints()) { throw new Error('Needs updated convex hull points before bounds calculation.'); } @@ -454,19 +453,18 @@ class Drawable { } const transformedHullPoints = this._getTransformedHullPoints(); // Search through transformed points to generate box on axes. - result = result || new Rectangle(); - result.initFromPointsAABB(transformedHullPoints); - return result; + const bounds = new Rectangle(); + bounds.initFromPointsAABB(transformedHullPoints); + return bounds; } /** * Get the precise bounds for the upper 8px slice of the Drawable. * Used for calculating where to position a text bubble. * Before calling this, ensure the renderer has updated convex hull points. - * @param {?Rectangle} result optional destination for bounds calculation * @return {!Rectangle} Bounds for a tight box around a slice of the Drawable. */ - getBoundsForBubble (result) { + getBoundsForBubble () { if (this.needsConvexHullPoints()) { throw new Error('Needs updated convex hull points before bubble bounds calculation.'); } @@ -478,9 +476,9 @@ class Drawable { const maxY = Math.max.apply(null, transformedHullPoints.map(p => p[1])); const filteredHullPoints = transformedHullPoints.filter(p => p[1] > maxY - slice); // Search through filtered points to generate box on axes. - result = result || new Rectangle(); - result.initFromPointsAABB(filteredHullPoints); - return result; + const bounds = new Rectangle(); + bounds.initFromPointsAABB(filteredHullPoints); + return bounds; } /** @@ -490,32 +488,35 @@ class Drawable { * which is tightly snapped to account for a Drawable's transparent regions. * `getAABB` returns a much less accurate bounding box, but will be much * faster to calculate so may be desired for quick checks/optimizations. - * @param {?Rectangle} result optional destination for bounds calculation * @return {!Rectangle} Rough axis-aligned bounding box for Drawable. */ - getAABB (result) { + getAABB () { if (this._transformDirty) { this._calculateTransform(); } const tm = this._uniforms.u_modelMatrix; - result = result || new Rectangle(); - result.initFromModelMatrix(tm); - return result; + const bounds = new Rectangle(); + bounds.initFromPointsAABB([ + twgl.m4.transformPoint(tm, [-0.5, -0.5, 0]), + twgl.m4.transformPoint(tm, [0.5, -0.5, 0]), + twgl.m4.transformPoint(tm, [-0.5, 0.5, 0]), + twgl.m4.transformPoint(tm, [0.5, 0.5, 0]) + ]); + return bounds; } /** * Return the best Drawable bounds possible without performing graphics queries. * I.e., returns the tight bounding box when the convex hull points are already * known, but otherwise return the rough AABB of the Drawable. - * @param {?Rectangle} result optional destination for bounds calculation * @return {!Rectangle} Bounds for the Drawable. */ - getFastBounds (result) { + getFastBounds () { this.updateMatrix(); if (!this.needsConvexHullPoints()) { - return this.getBounds(result); + return this.getBounds(); } - return this.getAABB(result); + return this.getAABB(); } /** diff --git a/src/Rectangle.js b/src/Rectangle.js index 6556cb1..7659e22 100644 --- a/src/Rectangle.js +++ b/src/Rectangle.js @@ -54,31 +54,6 @@ class Rectangle { } } - /** - * Initialize a Rectangle to a 1 unit square centered at 0 x 0 transformed - * by a model matrix. - * @param {Array.} m A 4x4 matrix to transform the rectangle by. - * @tutorial Rectangle-AABB-Matrix - */ - initFromModelMatrix (m) { - // In 2D space, we will soon use the 2x2 "top left" scale and rotation - // submatrix, while we store and the 1x2 "top right" that position - // vector. - const m30 = m[(3 * 4) + 0]; - const m31 = m[(3 * 4) + 1]; - - // "Transform" a (0.5, 0.5) vector by the scale and rotation matrix but - // sum the absolute of each component instead of use the signed values. - const x = Math.abs(0.5 * m[(0 * 4) + 0]) + Math.abs(0.5 * m[(1 * 4) + 0]); - const y = Math.abs(0.5 * m[(0 * 4) + 1]) + Math.abs(0.5 * m[(1 * 4) + 1]); - - // And adding them to the position components initializes our Rectangle. - this.left = -x + m30; - this.right = x + m30; - this.top = y + m31; - this.bottom = -y + m31; - } - /** * Determine if this Rectangle intersects some other. * Note that this is a comparison assuming the Rectangle was diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js index d9f5ae2..b0f2145 100644 --- a/src/RenderWebGL.js +++ b/src/RenderWebGL.js @@ -17,7 +17,6 @@ const log = require('./util/log'); const __isTouchingDrawablesPoint = twgl.v3.create(); const __candidatesBounds = new Rectangle(); -const __fenceBounds = new Rectangle(); const __touchingColor = new Uint8ClampedArray(4); const __blendColor = new Uint8ClampedArray(4); @@ -1377,7 +1376,7 @@ class RenderWebGL extends EventEmitter { const dx = x - drawable._position[0]; const dy = y - drawable._position[1]; - const aabb = drawable._skin.getFenceBounds(drawable, __fenceBounds); + const aabb = drawable._skin.getFenceBounds(drawable); const inset = Math.floor(Math.min(aabb.width, aabb.height) / 2); const sx = this._xRight - Math.min(FENCE_WIDTH, inset); @@ -1647,14 +1646,14 @@ class RenderWebGL extends EventEmitter { } twgl.setUniforms(currentShader, uniforms); - + /* adjust blend function for this skin */ if (drawable.skin.hasPremultipliedAlpha){ gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); } else { gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); } - + twgl.drawBufferInfo(gl, this._bufferInfo, gl.TRIANGLES); } diff --git a/src/Skin.js b/src/Skin.js index 8bd89d6..bccc9dc 100644 --- a/src/Skin.js +++ b/src/Skin.js @@ -145,11 +145,10 @@ class Skin extends EventEmitter { /** * Get the bounds of the drawable for determining its fenced position. * @param {Array} drawable - The Drawable instance this skin is using. - * @param {?Rectangle} result - Optional destination for bounds calculation. * @return {!Rectangle} The drawable's bounds. */ - getFenceBounds (drawable, result) { - return drawable.getFastBounds(result); + getFenceBounds (drawable) { + return drawable.getFastBounds(); } /** From a340b8a04bfe11d950b3fbea7332b5be87e280e1 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Tue, 13 Aug 2019 11:41:11 -0400 Subject: [PATCH 25/38] Revert "Revert "Initialialize AABB Rectangle "" --- .jsdoc.json | 3 +- docs/Rectangle-AABB-Matrix.md | 192 ++++++++++++++++++++++++++++++++++ src/BitmapSkin.js | 5 +- src/Drawable.js | 39 ++++--- src/Rectangle.js | 25 +++++ src/RenderWebGL.js | 7 +- src/Skin.js | 5 +- 7 files changed, 248 insertions(+), 28 deletions(-) create mode 100644 docs/Rectangle-AABB-Matrix.md diff --git a/.jsdoc.json b/.jsdoc.json index f4d8b9f..b047b69 100644 --- a/.jsdoc.json +++ b/.jsdoc.json @@ -15,6 +15,7 @@ "private": true, "readme": "README.md", "recurse": true, - "template": "node_modules/docdash" + "template": "node_modules/docdash", + "tutorials": "docs" } } diff --git a/docs/Rectangle-AABB-Matrix.md b/docs/Rectangle-AABB-Matrix.md new file mode 100644 index 0000000..e0c33b4 --- /dev/null +++ b/docs/Rectangle-AABB-Matrix.md @@ -0,0 +1,192 @@ +# Rectangle AABB Matrix + +Initialize a Rectangle to a 1 unit square centered at 0 x 0 transformed by a model matrix. + +----- + +Every drawable is a 1 x 1 unit square that is rotated by its direction, scaled by its skin size and scale, and offset by its rotation center and position. The square representation is made up of 4 points that are transformed by the drawable properties. Often we want a shape that simplifies those 4 points into a non-rotated shape, a axis aligned bounding box. + +One approach is to compare the x and y components of each transformed vector and find the minimum and maximum x component and the minimum and maximum y component. + +We can start from this approach and determine an alternative one that prodcues the same output with less work. + +Starting with transforming one point, here is a 3D point, `v`, transformation by a matrix, `m`. + +```js +const v0 = v[0]; +const v1 = v[1]; +const v2 = v[2]; + +const d = v0 * m[(0 * 4) + 3] + v1 * m[(1 * 4) + 3] + v2 * m[(2 * 4) + 3] + m[(3 * 4) + 3]; +dst[0] = (v0 * m[(0 * 4) + 0] + v1 * m[(1 * 4) + 0] + v2 * m[(2 * 4) + 0] + m[(3 * 4) + 0]) / d; +dst[1] = (v0 * m[(0 * 4) + 1] + v1 * m[(1 * 4) + 1] + v2 * m[(2 * 4) + 1] + m[(3 * 4) + 1]) / d; +dst[2] = (v0 * m[(0 * 4) + 2] + v1 * m[(1 * 4) + 2] + v2 * m[(2 * 4) + 2] + m[(3 * 4) + 2]) / d; +``` + +As this is a 2D rectangle we can cancel out the third dimension, and the determinant, 'd'. + +```js +const v0 = v[0]; +const v1 = v[1]; + +dst = [ + v0 * m[(0 * 4) + 0] + v1 * m[(1 * 4) + 0] + m[(3 * 4) + 0, + v0 * m[(0 * 4) + 1] + v1 * m[(1 * 4) + 1] + m[(3 * 4) + 1 +]; +``` + +Let's set the matrix points to shorter names for convenience. + +```js +const m00 = m[(0 * 4) + 0]; +const m01 = m[(0 * 4) + 1]; +const m10 = m[(1 * 4) + 0]; +const m11 = m[(1 * 4) + 1]; +const m30 = m[(3 * 4) + 0]; +const m31 = m[(3 * 4) + 1]; +``` + +We need 4 points with positive and negative 0.5 values so the square has sides of length 1. + +```js +let p = [0.5, 0.5]; +let q = [-0.5, 0.5]; +let r = [-0.5, -0.5]; +let s = [0.5, -0.5]; +``` + +Transform the points by the matrix. + +```js +p = [ + 0.5 * m00 + 0.5 * m10 + m30, + 0.5 * m01 + 0.5 * m11 + m31 +]; +q = [ + -0.5 * m00 + -0.5 * m10 + m30, + 0.5 * m01 + 0.5 * m11 + m31 +]; +r = [ + -0.5 * m00 + -0.5 * m10 + m30, + -0.5 * m01 + -0.5 * m11 + m31 +]; +s = [ + 0.5 * m00 + 0.5 * m10 + m30, + -0.5 * m01 + -0.5 * m11 + m31 +]; +``` + +With 4 transformed points we can build the left, right, top, and bottom values for the Rectangle. Each will use the minimum or the maximum of one of the components of all points. + +```js +const left = Math.min(p[0], q[0], r[0], s[0]); +const right = Math.max(p[0], q[0], r[0], s[0]); +const top = Math.max(p[1], q[1], r[1], s[1]); +const bottom = Math.min(p[1], q[1], r[1], s[1]); +``` + +Fill those calls with the vector expressions. + +```js +const left = Math.min( + 0.5 * m00 + 0.5 * m10 + m30, + -0.5 * m00 + 0.5 * m10 + m30, + -0.5 * m00 + -0.5 * m10 + m30, + 0.5 * m00 + -0.5 * m10 + m30 +); +const right = Math.max( + 0.5 * m00 + 0.5 * m10 + m30, + -0.5 * m00 + 0.5 * m10 + m30, + -0.5 * m00 + -0.5 * m10 + m30, + 0.5 * m00 + -0.5 * m10 + m30 +); +const top = Math.max( + 0.5 * m01 + 0.5 * m11 + m31, + -0.5 * m01 + 0.5 * m11 + m31, + -0.5 * m01 + -0.5 * m11 + m31, + 0.5 * m01 + -0.5 * m11 + m31 +); +const bottom = Math.min( + 0.5 * m01 + 0.5 * m11 + m31, + -0.5 * m01 + 0.5 * m11 + m31, + -0.5 * m01 + -0.5 * m11 + m31, + 0.5 * m01 + -0.5 * m11 + m31 +); +``` + +Pull out the `0.5 * m??` patterns. + +```js +const x0 = 0.5 * m00; +const x1 = 0.5 * m10; +const y0 = 0.5 * m01; +const y1 = 0.5 * m11; + +const left = Math.min(x0 + x1 + m30, -x0 + x1 + m30, -x0 + -x1 + m30, x0 + -x1 + m30); +const right = Math.max(x0 + x1 + m30, -x0 + x1 + m30, -x0 + -x1 + m30, x0 + -x1 + m30); +const top = Math.max(y0 + y1 + m31, -y0 + y1 + m31, -y0 + -y1 + m31, y0 + -y1 + m31); +const bottom = Math.min(y0 + y1 + m31, -y0 + y1 + m31, -y0 + -y1 + m31, y0 + -y1 + m31); +``` + +Now each argument for the min and max calls take an expression like `(a * x0 + b * x1 + m3?)`. As each expression has the x0, x1, and m3? variables we can split the min and max calls on the addition operators. Each new call has all the coefficients of that variable. + +```js +const left = Math.min(x0, -x0) + Math.min(x1, -x1) + Math.min(m30, m30); +const right = Math.max(x0, -x0) + Math.max(x1, -x1) + Math.max(m30, m30); +const top = Math.max(y0, -y0) + Math.max(y1, -y1) + Math.max(m31, m31); +const bottom = Math.min(y0, -y0) + Math.min(y1, -y1) + Math.min(m31, m31); +``` + +The min or max of two copies of the same value will just be that value. + +```js +const left = Math.min(x0, -x0) + Math.min(x1, -x1) + m30; +const right = Math.max(x0, -x0) + Math.max(x1, -x1) + m30; +const top = Math.max(y0, -y0) + Math.max(y1, -y1) + m31; +const bottom = Math.min(y0, -y0) + Math.min(y1, -y1) + m31; +``` + +The max of a negative and positive variable will be the absolute value of that variable. The min of a negative and positive variable will the negated absolute value of that variable. + +```js +const left = -Math.abs(x0) + -Math.abs(x1) + m30; +const right = Math.abs(x0) + Math.abs(x1) + m30; +const top = Math.abs(y0) + Math.abs(y1) + m31; +const bottom = -Math.abs(y0) + -Math.abs(y1) + m31; +``` + +Pulling out the negations of the absolute values, left and right as well as top and bottom are the positive or negative sum of the absolute value of the saled and rotated unit value. + +```js +const left = -(Math.abs(x0) + Math.abs(x1)) + m30; +const right = Math.abs(x0) + Math.abs(x1) + m30; +const top = Math.abs(y0) + Math.abs(y1) + m31; +const bottom = -(Math.abs(y0) + Math.abs(y1)) + m31; +``` + +We call pull out those sums and use them twice. + +```js +const x = Math.abs(x0) + Math.abs(x1); +const y = Math.abs(y0) + Math.abs(y1); + +const left = -x + m30; +const right = x + m30; +const top = y + m31; +const bottom = -y + m31; +``` + +This lets us arrive at our goal. Inlining some of our variables we get this block that will initialize a Rectangle to a unit square transformed by a matrix. + +```js +const m30 = m[(3 * 4) + 0]; +const m31 = m[(3 * 4) + 1]; + +const x = Math.abs(0.5 * m[(0 * 4) + 0]) + Math.abs(0.5 * m[(1 * 4) + 0]); +const y = Math.abs(0.5 * m[(0 * 4) + 1]) + Math.abs(0.5 * m[(1 * 4) + 1]); + +const left = -x + m30; +const right = x + m30; +const top = y + m31; +const bottom = -y + m31; +``` diff --git a/src/BitmapSkin.js b/src/BitmapSkin.js index b4b03d3..94f2984 100644 --- a/src/BitmapSkin.js +++ b/src/BitmapSkin.js @@ -62,10 +62,11 @@ class BitmapSkin extends Skin { /** * Get the bounds of the drawable for determining its fenced position. * @param {Array} drawable - The Drawable instance this skin is using. + * @param {?Rectangle} result - Optional destination for bounds calculation. * @return {!Rectangle} The drawable's bounds. For compatibility with Scratch 2, we always use getAABB for bitmaps. */ - getFenceBounds (drawable) { - return drawable.getAABB(); + getFenceBounds (drawable, result) { + return drawable.getAABB(result); } /** diff --git a/src/Drawable.js b/src/Drawable.js index 1d90a29..ea94acd 100644 --- a/src/Drawable.js +++ b/src/Drawable.js @@ -451,9 +451,10 @@ class Drawable { * This function applies the transform matrix to the known convex hull, * and then finds the minimum box along the axes. * Before calling this, ensure the renderer has updated convex hull points. + * @param {?Rectangle} result optional destination for bounds calculation * @return {!Rectangle} Bounds for a tight box around the Drawable. */ - getBounds () { + getBounds (result) { if (this.needsConvexHullPoints()) { throw new Error('Needs updated convex hull points before bounds calculation.'); } @@ -462,18 +463,19 @@ class Drawable { } const transformedHullPoints = this._getTransformedHullPoints(); // Search through transformed points to generate box on axes. - const bounds = new Rectangle(); - bounds.initFromPointsAABB(transformedHullPoints); - return bounds; + result = result || new Rectangle(); + result.initFromPointsAABB(transformedHullPoints); + return result; } /** * Get the precise bounds for the upper 8px slice of the Drawable. * Used for calculating where to position a text bubble. * Before calling this, ensure the renderer has updated convex hull points. + * @param {?Rectangle} result optional destination for bounds calculation * @return {!Rectangle} Bounds for a tight box around a slice of the Drawable. */ - getBoundsForBubble () { + getBoundsForBubble (result) { if (this.needsConvexHullPoints()) { throw new Error('Needs updated convex hull points before bubble bounds calculation.'); } @@ -485,9 +487,9 @@ class Drawable { const maxY = Math.max.apply(null, transformedHullPoints.map(p => p[1])); const filteredHullPoints = transformedHullPoints.filter(p => p[1] > maxY - slice); // Search through filtered points to generate box on axes. - const bounds = new Rectangle(); - bounds.initFromPointsAABB(filteredHullPoints); - return bounds; + result = result || new Rectangle(); + result.initFromPointsAABB(filteredHullPoints); + return result; } /** @@ -497,35 +499,32 @@ class Drawable { * which is tightly snapped to account for a Drawable's transparent regions. * `getAABB` returns a much less accurate bounding box, but will be much * faster to calculate so may be desired for quick checks/optimizations. + * @param {?Rectangle} result optional destination for bounds calculation * @return {!Rectangle} Rough axis-aligned bounding box for Drawable. */ - getAABB () { + getAABB (result) { if (this._transformDirty) { this._calculateTransform(); } const tm = this._uniforms.u_modelMatrix; - const bounds = new Rectangle(); - bounds.initFromPointsAABB([ - twgl.m4.transformPoint(tm, [-0.5, -0.5, 0]), - twgl.m4.transformPoint(tm, [0.5, -0.5, 0]), - twgl.m4.transformPoint(tm, [-0.5, 0.5, 0]), - twgl.m4.transformPoint(tm, [0.5, 0.5, 0]) - ]); - return bounds; + result = result || new Rectangle(); + result.initFromModelMatrix(tm); + return result; } /** * Return the best Drawable bounds possible without performing graphics queries. * I.e., returns the tight bounding box when the convex hull points are already * known, but otherwise return the rough AABB of the Drawable. + * @param {?Rectangle} result optional destination for bounds calculation * @return {!Rectangle} Bounds for the Drawable. */ - getFastBounds () { + getFastBounds (result) { this.updateMatrix(); if (!this.needsConvexHullPoints()) { - return this.getBounds(); + return this.getBounds(result); } - return this.getAABB(); + return this.getAABB(result); } /** diff --git a/src/Rectangle.js b/src/Rectangle.js index 7659e22..6556cb1 100644 --- a/src/Rectangle.js +++ b/src/Rectangle.js @@ -54,6 +54,31 @@ class Rectangle { } } + /** + * Initialize a Rectangle to a 1 unit square centered at 0 x 0 transformed + * by a model matrix. + * @param {Array.} m A 4x4 matrix to transform the rectangle by. + * @tutorial Rectangle-AABB-Matrix + */ + initFromModelMatrix (m) { + // In 2D space, we will soon use the 2x2 "top left" scale and rotation + // submatrix, while we store and the 1x2 "top right" that position + // vector. + const m30 = m[(3 * 4) + 0]; + const m31 = m[(3 * 4) + 1]; + + // "Transform" a (0.5, 0.5) vector by the scale and rotation matrix but + // sum the absolute of each component instead of use the signed values. + const x = Math.abs(0.5 * m[(0 * 4) + 0]) + Math.abs(0.5 * m[(1 * 4) + 0]); + const y = Math.abs(0.5 * m[(0 * 4) + 1]) + Math.abs(0.5 * m[(1 * 4) + 1]); + + // And adding them to the position components initializes our Rectangle. + this.left = -x + m30; + this.right = x + m30; + this.top = y + m31; + this.bottom = -y + m31; + } + /** * Determine if this Rectangle intersects some other. * Note that this is a comparison assuming the Rectangle was diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js index 4782455..ba629ea 100644 --- a/src/RenderWebGL.js +++ b/src/RenderWebGL.js @@ -16,6 +16,7 @@ const log = require('./util/log'); const __isTouchingDrawablesPoint = twgl.v3.create(); const __candidatesBounds = new Rectangle(); +const __fenceBounds = new Rectangle(); const __touchingColor = new Uint8ClampedArray(4); const __blendColor = new Uint8ClampedArray(4); @@ -1357,7 +1358,7 @@ class RenderWebGL extends EventEmitter { const dx = x - drawable._position[0]; const dy = y - drawable._position[1]; - const aabb = drawable._skin.getFenceBounds(drawable); + const aabb = drawable._skin.getFenceBounds(drawable, __fenceBounds); const inset = Math.floor(Math.min(aabb.width, aabb.height) / 2); const sx = this._xRight - Math.min(FENCE_WIDTH, inset); @@ -1627,14 +1628,14 @@ class RenderWebGL extends EventEmitter { } twgl.setUniforms(currentShader, uniforms); - + /* adjust blend function for this skin */ if (drawable.skin.hasPremultipliedAlpha){ gl.blendFuncSeparate(gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); } else { gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); } - + twgl.drawBufferInfo(gl, this._bufferInfo, gl.TRIANGLES); } diff --git a/src/Skin.js b/src/Skin.js index e0e7413..473c70b 100644 --- a/src/Skin.js +++ b/src/Skin.js @@ -146,10 +146,11 @@ class Skin extends EventEmitter { /** * Get the bounds of the drawable for determining its fenced position. * @param {Array} drawable - The Drawable instance this skin is using. + * @param {?Rectangle} result - Optional destination for bounds calculation. * @return {!Rectangle} The drawable's bounds. */ - getFenceBounds (drawable) { - return drawable.getFastBounds(); + getFenceBounds (drawable, result) { + return drawable.getFastBounds(result); } /** From 7c4393787b6f39bc0406c63a38233dc105d8640c Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Tue, 13 Aug 2019 11:42:51 -0400 Subject: [PATCH 26/38] Revert "Revert "Skin alter push"" --- src/BitmapSkin.js | 14 ++++++------- src/Drawable.js | 9 --------- src/PenSkin.js | 23 ++++++++++++--------- src/RenderWebGL.js | 19 ++++++++++++++++++ src/SVGSkin.js | 38 ++++++++++++++++++++++++++--------- src/Skin.js | 15 +++++++------- test/fixtures/MockSkinPool.js | 35 ++++++++++++++++++++++++++++++++ test/unit/DrawableTests.js | 7 +++++++ 8 files changed, 116 insertions(+), 44 deletions(-) create mode 100644 test/fixtures/MockSkinPool.js diff --git a/src/BitmapSkin.js b/src/BitmapSkin.js index b4b03d3..99f8866 100644 --- a/src/BitmapSkin.js +++ b/src/BitmapSkin.js @@ -23,6 +23,12 @@ class BitmapSkin extends Skin { /** @type {Array} */ this._textureSize = [0, 0]; + + /** + * The "native" size, in texels, of this skin. + * @type {Array} + */ + this.size = [0, 0]; } /** @@ -43,13 +49,6 @@ class BitmapSkin extends Skin { return true; } - /** - * @return {Array} the "native" size, in texels, of this skin. - */ - get size () { - return [this._textureSize[0] / this._costumeResolution, this._textureSize[1] / this._costumeResolution]; - } - /** * @param {Array} scale - The scaling factors to be used. * @return {WebGLTexture} The GL texture representation of this skin when drawing at the given scale. @@ -109,6 +108,7 @@ class BitmapSkin extends Skin { // Do these last in case any of the above throws an exception this._costumeResolution = costumeResolution || 2; this._textureSize = BitmapSkin._getBitmapSize(bitmapData); + this.size = [this._textureSize[0] / this._costumeResolution, this._textureSize[1] / this._costumeResolution]; if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter(); this.setRotationCenter.apply(this, rotationCenter); diff --git a/src/Drawable.js b/src/Drawable.js index 1d90a29..9426ebf 100644 --- a/src/Drawable.js +++ b/src/Drawable.js @@ -3,7 +3,6 @@ const twgl = require('twgl.js'); const Rectangle = require('./Rectangle'); const RenderConstants = require('./RenderConstants'); const ShaderManager = require('./ShaderManager'); -const Skin = require('./Skin'); const EffectTransform = require('./EffectTransform'); /** @@ -101,8 +100,6 @@ class Drawable { /** @todo move convex hull functionality, maybe bounds functionality overall, to Skin classes */ this._convexHullPoints = null; this._convexHullDirty = true; - - this._skinWasAltered = this._skinWasAltered.bind(this); } /** @@ -141,13 +138,7 @@ class Drawable { */ set skin (newSkin) { if (this._skin !== newSkin) { - if (this._skin) { - this._skin.removeListener(Skin.Events.WasAltered, this._skinWasAltered); - } this._skin = newSkin; - if (this._skin) { - this._skin.addListener(Skin.Events.WasAltered, this._skinWasAltered); - } this._skinWasAltered(); } } diff --git a/src/PenSkin.js b/src/PenSkin.js index 1e500ba..273cac1 100644 --- a/src/PenSkin.js +++ b/src/PenSkin.js @@ -88,6 +88,9 @@ class PenSkin extends Skin { /** @type {HTMLCanvasElement} */ this._canvas = document.createElement('canvas'); + /** @type {Array} */ + this._canvasSize = twgl.v3.create(); + /** @type {WebGLTexture} */ this._texture = null; @@ -165,7 +168,7 @@ class PenSkin extends Skin { * @return {Array} the "native" size, in texels, of this skin. [width, height] */ get size () { - return [this._canvas.width, this._canvas.height]; + return this._canvasSize; } /** @@ -188,13 +191,13 @@ class PenSkin extends Skin { clear () { const gl = this._renderer.gl; twgl.bindFramebufferInfo(gl, this._framebuffer); - + /* Reset framebuffer to transparent black */ 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); + ctx.clearRect(0, 0, this._canvasSize[0], this._canvasSize[1]); this._silhouetteDirty = true; } @@ -451,7 +454,7 @@ class PenSkin extends Skin { * @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) { + _drawRectangle (currentShader, texture, bounds, x = -this._canvasSize[0] / 2, y = this._canvasSize[1] / 2) { const gl = this._renderer.gl; const projection = twgl.m4.ortho( @@ -514,7 +517,7 @@ class PenSkin extends Skin { * @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) { + _drawToBuffer (texture = this._texture, x = -this._canvasSize[0] / 2, y = this._canvasSize[1] / 2) { if (texture !== this._texture && this._canvasDirty) { this._drawToBuffer(); } @@ -528,7 +531,7 @@ class PenSkin extends Skin { 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); + ctx.clearRect(0, 0, this._canvasSize[0], this._canvasSize[1]); this._canvasDirty = false; } @@ -564,8 +567,8 @@ class PenSkin extends Skin { this._bounds = new Rectangle(); this._bounds.initFromBounds(width / 2, width / -2, height / 2, height / -2); - this._canvas.width = width; - this._canvas.height = height; + this._canvas.width = this._canvasSize[0] = width; + this._canvas.height = this._canvasSize[1] = height; this._rotationCenter[0] = width / 2; this._rotationCenter[1] = height / 2; @@ -651,8 +654,8 @@ class PenSkin extends Skin { this._renderer.enterDrawRegion(this._toBufferDrawRegionId); // Sample the framebuffer's pixels into the silhouette instance - const skinPixels = new Uint8Array(Math.floor(this._canvas.width * this._canvas.height * 4)); - gl.readPixels(0, 0, this._canvas.width, this._canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, skinPixels); + const skinPixels = new Uint8Array(Math.floor(this._canvasSize[0] * this._canvasSize[1] * 4)); + gl.readPixels(0, 0, this._canvasSize[0], this._canvasSize[1], gl.RGBA, gl.UNSIGNED_BYTE, skinPixels); const skinCanvas = this._canvas; skinCanvas.width = bounds.width; diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js index 4782455..b0f2145 100644 --- a/src/RenderWebGL.js +++ b/src/RenderWebGL.js @@ -3,6 +3,7 @@ const EventEmitter = require('events'); const hull = require('hull.js'); const twgl = require('twgl.js'); +const Skin = require('./Skin'); const BitmapSkin = require('./BitmapSkin'); const Drawable = require('./Drawable'); const Rectangle = require('./Rectangle'); @@ -290,6 +291,20 @@ class RenderWebGL extends EventEmitter { this.emit(RenderConstants.Events.NativeSizeChanged, {newSize: this._nativeSize}); } + /** + * Notify Drawables whose skin is the skin that changed. + * @param {Skin} skin - the skin that changed. + * @private + */ + _skinWasAltered (skin) { + for (let i = 0; i < this._allDrawables.length; i++) { + const drawable = this._allDrawables[i]; + if (drawable && drawable._skin === skin) { + drawable._skinWasAltered(); + } + } + } + /** * Create a new bitmap skin from a snapshot of the provided bitmap data. * @param {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} bitmapData - new contents for this skin. @@ -302,6 +317,7 @@ class RenderWebGL extends EventEmitter { const skinId = this._nextSkinId++; const newSkin = new BitmapSkin(skinId, this); newSkin.setBitmap(bitmapData, costumeResolution, rotationCenter); + newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } @@ -317,6 +333,7 @@ class RenderWebGL extends EventEmitter { const skinId = this._nextSkinId++; const newSkin = new SVGSkin(skinId, this); newSkin.setSVG(svgData, rotationCenter); + newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } @@ -328,6 +345,7 @@ class RenderWebGL extends EventEmitter { createPenSkin () { const skinId = this._nextSkinId++; const newSkin = new PenSkin(skinId, this); + newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } @@ -344,6 +362,7 @@ class RenderWebGL extends EventEmitter { const skinId = this._nextSkinId++; const newSkin = new TextBubbleSkin(skinId, this); newSkin.setTextBubble(type, text, pointsLeft); + newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } diff --git a/src/SVGSkin.js b/src/SVGSkin.js index 90e3908..9c10e61 100644 --- a/src/SVGSkin.js +++ b/src/SVGSkin.js @@ -30,6 +30,24 @@ class SVGSkin extends Skin { /** @type {Number} */ this._maxTextureScale = 0; + + /** + * The natural size, in Scratch units, of this skin. + * @type {Array} + */ + this.size = [0, 0]; + + /** + * The viewbox offset of the svg. + * @type {Array} + */ + this._viewOffset = [0, 0]; + + /** + * The rotation center before offset by _viewOffset. + * @type {Array} + */ + this._rawRotationCenter = [NaN, NaN]; } /** @@ -43,21 +61,17 @@ class SVGSkin extends Skin { super.dispose(); } - /** - * @return {Array} the natural size, in Scratch units, of this skin. - */ - get size () { - return this._svgRenderer.size; - } - /** * Set the origin, in object space, about which this Skin should rotate. * @param {number} x - The x coordinate of the new rotation center. * @param {number} y - The y coordinate of the new rotation center. */ setRotationCenter (x, y) { - const viewOffset = this._svgRenderer.viewOffset; - super.setRotationCenter(x - viewOffset[0], y - viewOffset[1]); + if (x !== this._rawRotationCenter[0] || y !== this._rawRotationCenter[1]) { + this._rawRotationCenter[0] = x; + this._rawRotationCenter[1] = y; + super.setRotationCenter(x - this._viewOffset[0], y - this._viewOffset[1]); + } } /** @@ -134,7 +148,11 @@ class SVGSkin extends Skin { } if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter(); - this.setRotationCenter.apply(this, rotationCenter); + this.size = this._svgRenderer.size; + this._viewOffset = this._svgRenderer.viewOffset; + // Reset rawRotationCenter when we update viewOffset. + this._rawRotationCenter = [NaN, NaN]; + this.setRotationCenter(rotationCenter[0], rotationCenter[1]); this.emit(Skin.Events.WasAltered); }); } diff --git a/src/Skin.js b/src/Skin.js index e0e7413..bccc9dc 100644 --- a/src/Skin.js +++ b/src/Skin.js @@ -33,6 +33,13 @@ class Skin extends EventEmitter { /** @type {Vec3} */ this._rotationCenter = twgl.v3.create(0, 0); + /** + * The "native" size, in texels, of this skin. + * @member size + * @abstract + * @type {Array} + */ + /** * The uniforms to be used by the vertex and pixel shaders. * Some of these are used by other parts of the renderer as well. @@ -97,14 +104,6 @@ class Skin extends EventEmitter { return this._rotationCenter; } - /** - * @abstract - * @return {Array} the "native" size, in texels, of this skin. - */ - get size () { - return [0, 0]; - } - /** * Set the origin, in object space, about which this Skin should rotate. * @param {number} x - The x coordinate of the new rotation center. diff --git a/test/fixtures/MockSkinPool.js b/test/fixtures/MockSkinPool.js new file mode 100644 index 0000000..208d933 --- /dev/null +++ b/test/fixtures/MockSkinPool.js @@ -0,0 +1,35 @@ +const Skin = require('../../src/Skin'); + +class MockSkinPool { + constructor () { + this._allDrawables = []; + } + + static forDrawableSkin (drawable) { + const pool = new MockSkinPool(); + pool.addDrawable(drawable); + pool.addSkin(drawable.skin); + return pool; + } + + _skinWasAltered (skin) { + for (let i = 0; i < this._allDrawables.length; i++) { + const drawable = this._allDrawables[i]; + if (drawable && drawable._skin === skin) { + drawable._skinWasAltered(); + } + } + } + + addDrawable (drawable) { + this._allDrawables.push(drawable); + return drawable; + } + + addSkin (skin) { + skin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, skin)); + return skin; + } +} + +module.exports = MockSkinPool; diff --git a/test/unit/DrawableTests.js b/test/unit/DrawableTests.js index f0f163b..235e3d3 100644 --- a/test/unit/DrawableTests.js +++ b/test/unit/DrawableTests.js @@ -8,6 +8,7 @@ global.document = { const Drawable = require('../../src/Drawable'); const MockSkin = require('../fixtures/MockSkin'); +const MockSkinPool = require('../fixtures/MockSkinPool'); const Rectangle = require('../../src/Rectangle'); /** @@ -31,6 +32,7 @@ test('translate by position', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; + MockSkinPool.forDrawableSkin(drawable); expected.initFromBounds(0, 200, -50, 0); t.same(snapToNearest(drawable.getAABB()), expected); @@ -47,6 +49,7 @@ test('translate by costume center', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; + MockSkinPool.forDrawableSkin(drawable); drawable.skin.setRotationCenter(1, 0); expected.initFromBounds(-1, 199, -50, 0); @@ -64,6 +67,7 @@ test('translate and rotate', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; + MockSkinPool.forDrawableSkin(drawable); drawable.updateProperties({position: [1, 2], direction: 0}); expected.initFromBounds(1, 51, 2, 202); @@ -90,6 +94,7 @@ test('rotate by non-right-angles', t => { drawable.skin = new MockSkin(); drawable.skin.size = [10, 10]; drawable.skin.setRotationCenter(5, 5); + MockSkinPool.forDrawableSkin(drawable); expected.initFromBounds(-5, 5, -5, 5); t.same(snapToNearest(drawable.getAABB()), expected); @@ -106,6 +111,7 @@ test('scale', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; + MockSkinPool.forDrawableSkin(drawable); drawable.updateProperties({scale: [100, 50]}); expected.initFromBounds(0, 200, -25, 0); @@ -128,6 +134,7 @@ test('rotate and scale', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [100, 1000]; + MockSkinPool.forDrawableSkin(drawable); drawable.skin.setRotationCenter(50, 50); expected.initFromBounds(-50, 50, -950, 50); From 94257a421435a06103c4280ac2f744319e8e6344 Mon Sep 17 00:00:00 2001 From: adroitwhiz Date: Tue, 13 Aug 2019 16:38:45 -0400 Subject: [PATCH 27/38] Set SVG skin size synchronously --- src/SVGSkin.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/SVGSkin.js b/src/SVGSkin.js index 9c10e61..90852ca 100644 --- a/src/SVGSkin.js +++ b/src/SVGSkin.js @@ -114,7 +114,19 @@ class SVGSkin extends Skin { * @fires Skin.event:WasAltered */ setSVG (svgData, rotationCenter) { - this._svgRenderer.fromString(svgData, 1, () => { + this._svgRenderer.loadString(svgData); + + // Size must be updated synchronously because the VM sets the costume's `size` immediately after calling this. + // TODO: add either a callback to this function so the costume size can be set after this is done, + // or something in the VM to handle setting the costume size when Skin.Events.WasAltered is emitted. + this.size = this._svgRenderer.size; + if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter(); + this._viewOffset = this._svgRenderer.viewOffset; + // Reset rawRotationCenter when we update viewOffset. + this._rawRotationCenter = [NaN, NaN]; + this.setRotationCenter(rotationCenter[0], rotationCenter[1]); + + this._svgRenderer._draw(1, () => { const gl = this._renderer.gl; this._textureScale = this._maxTextureScale = 1; @@ -147,12 +159,6 @@ class SVGSkin extends Skin { this._maxTextureScale = testScale; } - if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter(); - this.size = this._svgRenderer.size; - this._viewOffset = this._svgRenderer.viewOffset; - // Reset rawRotationCenter when we update viewOffset. - this._rawRotationCenter = [NaN, NaN]; - this.setRotationCenter(rotationCenter[0], rotationCenter[1]); this.emit(Skin.Events.WasAltered); }); } From ef13d3bb08f5b984c1d49ae9f9098d432b813f49 Mon Sep 17 00:00:00 2001 From: DD Liu Date: Mon, 19 Aug 2019 11:40:51 -0400 Subject: [PATCH 28/38] =?UTF-8?q?Revert=20"Update=20scratch-svg-renderer?= =?UTF-8?q?=20to=20the=20latest=20version=20=F0=9F=9A=80"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 144119d..a02231b 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "minilog": "3.1.0", "raw-loader": "^0.5.1", "scratch-storage": "^1.0.0", - "scratch-svg-renderer": "0.2.0-prerelease.20190715153806", + "scratch-svg-renderer": "0.2.0-prerelease.20190523193400", "twgl.js": "4.4.0" } } From 3bfd4c65fbec09923ca5f726973884a3e6c96bda Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" <23040076+greenkeeper[bot]@users.noreply.github.com> Date: Tue, 20 Aug 2019 17:24:08 +0000 Subject: [PATCH 29/38] fix(package): update scratch-svg-renderer to version 0.2.0-prerelease.20190820171249 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a02231b..438d813 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "minilog": "3.1.0", "raw-loader": "^0.5.1", "scratch-storage": "^1.0.0", - "scratch-svg-renderer": "0.2.0-prerelease.20190523193400", + "scratch-svg-renderer": "0.2.0-prerelease.20190820171249", "twgl.js": "4.4.0" } } From d77afaa6c49d8c41a3b8640383e33637fba8dbac Mon Sep 17 00:00:00 2001 From: DD Liu Date: Thu, 22 Aug 2019 16:28:31 -0400 Subject: [PATCH 30/38] Update to latest svg-renderer --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 438d813..33d29c9 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "minilog": "3.1.0", "raw-loader": "^0.5.1", "scratch-storage": "^1.0.0", - "scratch-svg-renderer": "0.2.0-prerelease.20190820171249", + "scratch-svg-renderer": "0.2.0-prerelease.20190822202608", "twgl.js": "4.4.0" } } From afaa758615b74827ef09d7f76cd449f3571014d6 Mon Sep 17 00:00:00 2001 From: DD Liu Date: Wed, 28 Aug 2019 16:22:29 -0400 Subject: [PATCH 31/38] turn off antialias --- src/RenderWebGL.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js index 4782455..e78c71e 100644 --- a/src/RenderWebGL.js +++ b/src/RenderWebGL.js @@ -104,7 +104,7 @@ class RenderWebGL extends EventEmitter { * @private */ static _getContext (canvas) { - return twgl.getWebGLContext(canvas, {alpha: false, stencil: true}); + return twgl.getWebGLContext(canvas, {alpha: false, stencil: true , antialias: false}); } /** From b9732c222c060805101e2ccb79da282141240000 Mon Sep 17 00:00:00 2001 From: DD Liu Date: Wed, 28 Aug 2019 19:00:16 -0400 Subject: [PATCH 32/38] Fix space Co-Authored-By: adroitwhiz --- src/RenderWebGL.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js index e78c71e..3a2a302 100644 --- a/src/RenderWebGL.js +++ b/src/RenderWebGL.js @@ -104,7 +104,7 @@ class RenderWebGL extends EventEmitter { * @private */ static _getContext (canvas) { - return twgl.getWebGLContext(canvas, {alpha: false, stencil: true , antialias: false}); + return twgl.getWebGLContext(canvas, {alpha: false, stencil: true, antialias: false}); } /** From ad6ddd9f9984d4dd8557a9c7959b1bb412c6da9e Mon Sep 17 00:00:00 2001 From: adroitwhiz Date: Wed, 4 Sep 2019 15:40:49 -0400 Subject: [PATCH 33/38] add more parentheses --- src/Drawable.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Drawable.js b/src/Drawable.js index cc1d9ab..ab4d7aa 100644 --- a/src/Drawable.js +++ b/src/Drawable.js @@ -434,12 +434,12 @@ class Drawable { } // If the effect bits for mosaic, pixelate, whirl, or fisheye are set, use linear - if (this._effectBits & ( + if ((this._effectBits & ( ShaderManager.EFFECT_INFO.fisheye.mask | ShaderManager.EFFECT_INFO.whirl.mask | ShaderManager.EFFECT_INFO.pixelate.mask | ShaderManager.EFFECT_INFO.mosaic.mask - ) !== 0) { + )) !== 0) { return false; } From 1cd9e54834eea856a85e111b01b3c69181893640 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Thu, 12 Sep 2019 13:24:03 -0400 Subject: [PATCH 34/38] Revert "Revert "Revert "Skin alter push""" --- src/BitmapSkin.js | 14 +++++----- src/Drawable.js | 9 +++++++ src/PenSkin.js | 23 ++++++++--------- src/RenderWebGL.js | 19 -------------- src/SVGSkin.js | 48 +++++++++-------------------------- src/Skin.js | 15 ++++++----- test/fixtures/MockSkinPool.js | 35 ------------------------- test/unit/DrawableTests.js | 7 ----- 8 files changed, 46 insertions(+), 124 deletions(-) delete mode 100644 test/fixtures/MockSkinPool.js diff --git a/src/BitmapSkin.js b/src/BitmapSkin.js index 13d17bd..94f2984 100644 --- a/src/BitmapSkin.js +++ b/src/BitmapSkin.js @@ -23,12 +23,6 @@ class BitmapSkin extends Skin { /** @type {Array} */ this._textureSize = [0, 0]; - - /** - * The "native" size, in texels, of this skin. - * @type {Array} - */ - this.size = [0, 0]; } /** @@ -49,6 +43,13 @@ class BitmapSkin extends Skin { return true; } + /** + * @return {Array} the "native" size, in texels, of this skin. + */ + get size () { + return [this._textureSize[0] / this._costumeResolution, this._textureSize[1] / this._costumeResolution]; + } + /** * @param {Array} scale - The scaling factors to be used. * @return {WebGLTexture} The GL texture representation of this skin when drawing at the given scale. @@ -109,7 +110,6 @@ class BitmapSkin extends Skin { // Do these last in case any of the above throws an exception this._costumeResolution = costumeResolution || 2; this._textureSize = BitmapSkin._getBitmapSize(bitmapData); - this.size = [this._textureSize[0] / this._costumeResolution, this._textureSize[1] / this._costumeResolution]; if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter(); this.setRotationCenter.apply(this, rotationCenter); diff --git a/src/Drawable.js b/src/Drawable.js index 786dcac..cf00d3f 100644 --- a/src/Drawable.js +++ b/src/Drawable.js @@ -3,6 +3,7 @@ const twgl = require('twgl.js'); const Rectangle = require('./Rectangle'); const RenderConstants = require('./RenderConstants'); const ShaderManager = require('./ShaderManager'); +const Skin = require('./Skin'); const EffectTransform = require('./EffectTransform'); /** @@ -100,6 +101,8 @@ class Drawable { /** @todo move convex hull functionality, maybe bounds functionality overall, to Skin classes */ this._convexHullPoints = null; this._convexHullDirty = true; + + this._skinWasAltered = this._skinWasAltered.bind(this); } /** @@ -138,7 +141,13 @@ class Drawable { */ set skin (newSkin) { if (this._skin !== newSkin) { + if (this._skin) { + this._skin.removeListener(Skin.Events.WasAltered, this._skinWasAltered); + } this._skin = newSkin; + if (this._skin) { + this._skin.addListener(Skin.Events.WasAltered, this._skinWasAltered); + } this._skinWasAltered(); } } diff --git a/src/PenSkin.js b/src/PenSkin.js index 273cac1..1e500ba 100644 --- a/src/PenSkin.js +++ b/src/PenSkin.js @@ -88,9 +88,6 @@ class PenSkin extends Skin { /** @type {HTMLCanvasElement} */ this._canvas = document.createElement('canvas'); - /** @type {Array} */ - this._canvasSize = twgl.v3.create(); - /** @type {WebGLTexture} */ this._texture = null; @@ -168,7 +165,7 @@ class PenSkin extends Skin { * @return {Array} the "native" size, in texels, of this skin. [width, height] */ get size () { - return this._canvasSize; + return [this._canvas.width, this._canvas.height]; } /** @@ -191,13 +188,13 @@ class PenSkin extends Skin { clear () { const gl = this._renderer.gl; twgl.bindFramebufferInfo(gl, this._framebuffer); - + /* Reset framebuffer to transparent black */ gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); const ctx = this._canvas.getContext('2d'); - ctx.clearRect(0, 0, this._canvasSize[0], this._canvasSize[1]); + ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); this._silhouetteDirty = true; } @@ -454,7 +451,7 @@ class PenSkin extends Skin { * @param {number} x - centered at x * @param {number} y - centered at y */ - _drawRectangle (currentShader, texture, bounds, x = -this._canvasSize[0] / 2, y = this._canvasSize[1] / 2) { + _drawRectangle (currentShader, texture, bounds, x = -this._canvas.width / 2, y = this._canvas.height / 2) { const gl = this._renderer.gl; const projection = twgl.m4.ortho( @@ -517,7 +514,7 @@ class PenSkin extends Skin { * @param {number} x - texture centered at x * @param {number} y - texture centered at y */ - _drawToBuffer (texture = this._texture, x = -this._canvasSize[0] / 2, y = this._canvasSize[1] / 2) { + _drawToBuffer (texture = this._texture, x = -this._canvas.width / 2, y = this._canvas.height / 2) { if (texture !== this._texture && this._canvasDirty) { this._drawToBuffer(); } @@ -531,7 +528,7 @@ class PenSkin extends Skin { 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._canvasSize[0], this._canvasSize[1]); + ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); this._canvasDirty = false; } @@ -567,8 +564,8 @@ class PenSkin extends Skin { this._bounds = new Rectangle(); this._bounds.initFromBounds(width / 2, width / -2, height / 2, height / -2); - this._canvas.width = this._canvasSize[0] = width; - this._canvas.height = this._canvasSize[1] = height; + this._canvas.width = width; + this._canvas.height = height; this._rotationCenter[0] = width / 2; this._rotationCenter[1] = height / 2; @@ -654,8 +651,8 @@ class PenSkin extends Skin { this._renderer.enterDrawRegion(this._toBufferDrawRegionId); // Sample the framebuffer's pixels into the silhouette instance - const skinPixels = new Uint8Array(Math.floor(this._canvasSize[0] * this._canvasSize[1] * 4)); - gl.readPixels(0, 0, this._canvasSize[0], this._canvasSize[1], gl.RGBA, gl.UNSIGNED_BYTE, skinPixels); + const skinPixels = new Uint8Array(Math.floor(this._canvas.width * this._canvas.height * 4)); + gl.readPixels(0, 0, this._canvas.width, this._canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, skinPixels); const skinCanvas = this._canvas; skinCanvas.width = bounds.width; diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js index 4186b11..5277235 100644 --- a/src/RenderWebGL.js +++ b/src/RenderWebGL.js @@ -3,7 +3,6 @@ const EventEmitter = require('events'); const hull = require('hull.js'); const twgl = require('twgl.js'); -const Skin = require('./Skin'); const BitmapSkin = require('./BitmapSkin'); const Drawable = require('./Drawable'); const Rectangle = require('./Rectangle'); @@ -292,20 +291,6 @@ class RenderWebGL extends EventEmitter { this.emit(RenderConstants.Events.NativeSizeChanged, {newSize: this._nativeSize}); } - /** - * Notify Drawables whose skin is the skin that changed. - * @param {Skin} skin - the skin that changed. - * @private - */ - _skinWasAltered (skin) { - for (let i = 0; i < this._allDrawables.length; i++) { - const drawable = this._allDrawables[i]; - if (drawable && drawable._skin === skin) { - drawable._skinWasAltered(); - } - } - } - /** * Create a new bitmap skin from a snapshot of the provided bitmap data. * @param {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} bitmapData - new contents for this skin. @@ -318,7 +303,6 @@ class RenderWebGL extends EventEmitter { const skinId = this._nextSkinId++; const newSkin = new BitmapSkin(skinId, this); newSkin.setBitmap(bitmapData, costumeResolution, rotationCenter); - newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } @@ -334,7 +318,6 @@ class RenderWebGL extends EventEmitter { const skinId = this._nextSkinId++; const newSkin = new SVGSkin(skinId, this); newSkin.setSVG(svgData, rotationCenter); - newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } @@ -346,7 +329,6 @@ class RenderWebGL extends EventEmitter { createPenSkin () { const skinId = this._nextSkinId++; const newSkin = new PenSkin(skinId, this); - newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } @@ -363,7 +345,6 @@ class RenderWebGL extends EventEmitter { const skinId = this._nextSkinId++; const newSkin = new TextBubbleSkin(skinId, this); newSkin.setTextBubble(type, text, pointsLeft); - newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } diff --git a/src/SVGSkin.js b/src/SVGSkin.js index 90852ca..90e3908 100644 --- a/src/SVGSkin.js +++ b/src/SVGSkin.js @@ -30,24 +30,6 @@ class SVGSkin extends Skin { /** @type {Number} */ this._maxTextureScale = 0; - - /** - * The natural size, in Scratch units, of this skin. - * @type {Array} - */ - this.size = [0, 0]; - - /** - * The viewbox offset of the svg. - * @type {Array} - */ - this._viewOffset = [0, 0]; - - /** - * The rotation center before offset by _viewOffset. - * @type {Array} - */ - this._rawRotationCenter = [NaN, NaN]; } /** @@ -61,17 +43,21 @@ class SVGSkin extends Skin { super.dispose(); } + /** + * @return {Array} the natural size, in Scratch units, of this skin. + */ + get size () { + return this._svgRenderer.size; + } + /** * Set the origin, in object space, about which this Skin should rotate. * @param {number} x - The x coordinate of the new rotation center. * @param {number} y - The y coordinate of the new rotation center. */ setRotationCenter (x, y) { - if (x !== this._rawRotationCenter[0] || y !== this._rawRotationCenter[1]) { - this._rawRotationCenter[0] = x; - this._rawRotationCenter[1] = y; - super.setRotationCenter(x - this._viewOffset[0], y - this._viewOffset[1]); - } + const viewOffset = this._svgRenderer.viewOffset; + super.setRotationCenter(x - viewOffset[0], y - viewOffset[1]); } /** @@ -114,19 +100,7 @@ class SVGSkin extends Skin { * @fires Skin.event:WasAltered */ setSVG (svgData, rotationCenter) { - this._svgRenderer.loadString(svgData); - - // Size must be updated synchronously because the VM sets the costume's `size` immediately after calling this. - // TODO: add either a callback to this function so the costume size can be set after this is done, - // or something in the VM to handle setting the costume size when Skin.Events.WasAltered is emitted. - this.size = this._svgRenderer.size; - if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter(); - this._viewOffset = this._svgRenderer.viewOffset; - // Reset rawRotationCenter when we update viewOffset. - this._rawRotationCenter = [NaN, NaN]; - this.setRotationCenter(rotationCenter[0], rotationCenter[1]); - - this._svgRenderer._draw(1, () => { + this._svgRenderer.fromString(svgData, 1, () => { const gl = this._renderer.gl; this._textureScale = this._maxTextureScale = 1; @@ -159,6 +133,8 @@ class SVGSkin extends Skin { this._maxTextureScale = testScale; } + if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter(); + this.setRotationCenter.apply(this, rotationCenter); this.emit(Skin.Events.WasAltered); }); } diff --git a/src/Skin.js b/src/Skin.js index 8bd89d6..473c70b 100644 --- a/src/Skin.js +++ b/src/Skin.js @@ -33,13 +33,6 @@ class Skin extends EventEmitter { /** @type {Vec3} */ this._rotationCenter = twgl.v3.create(0, 0); - /** - * The "native" size, in texels, of this skin. - * @member size - * @abstract - * @type {Array} - */ - /** * The uniforms to be used by the vertex and pixel shaders. * Some of these are used by other parts of the renderer as well. @@ -104,6 +97,14 @@ class Skin extends EventEmitter { return this._rotationCenter; } + /** + * @abstract + * @return {Array} the "native" size, in texels, of this skin. + */ + get size () { + return [0, 0]; + } + /** * Set the origin, in object space, about which this Skin should rotate. * @param {number} x - The x coordinate of the new rotation center. diff --git a/test/fixtures/MockSkinPool.js b/test/fixtures/MockSkinPool.js deleted file mode 100644 index 208d933..0000000 --- a/test/fixtures/MockSkinPool.js +++ /dev/null @@ -1,35 +0,0 @@ -const Skin = require('../../src/Skin'); - -class MockSkinPool { - constructor () { - this._allDrawables = []; - } - - static forDrawableSkin (drawable) { - const pool = new MockSkinPool(); - pool.addDrawable(drawable); - pool.addSkin(drawable.skin); - return pool; - } - - _skinWasAltered (skin) { - for (let i = 0; i < this._allDrawables.length; i++) { - const drawable = this._allDrawables[i]; - if (drawable && drawable._skin === skin) { - drawable._skinWasAltered(); - } - } - } - - addDrawable (drawable) { - this._allDrawables.push(drawable); - return drawable; - } - - addSkin (skin) { - skin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, skin)); - return skin; - } -} - -module.exports = MockSkinPool; diff --git a/test/unit/DrawableTests.js b/test/unit/DrawableTests.js index 235e3d3..f0f163b 100644 --- a/test/unit/DrawableTests.js +++ b/test/unit/DrawableTests.js @@ -8,7 +8,6 @@ global.document = { const Drawable = require('../../src/Drawable'); const MockSkin = require('../fixtures/MockSkin'); -const MockSkinPool = require('../fixtures/MockSkinPool'); const Rectangle = require('../../src/Rectangle'); /** @@ -32,7 +31,6 @@ test('translate by position', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; - MockSkinPool.forDrawableSkin(drawable); expected.initFromBounds(0, 200, -50, 0); t.same(snapToNearest(drawable.getAABB()), expected); @@ -49,7 +47,6 @@ test('translate by costume center', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; - MockSkinPool.forDrawableSkin(drawable); drawable.skin.setRotationCenter(1, 0); expected.initFromBounds(-1, 199, -50, 0); @@ -67,7 +64,6 @@ test('translate and rotate', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; - MockSkinPool.forDrawableSkin(drawable); drawable.updateProperties({position: [1, 2], direction: 0}); expected.initFromBounds(1, 51, 2, 202); @@ -94,7 +90,6 @@ test('rotate by non-right-angles', t => { drawable.skin = new MockSkin(); drawable.skin.size = [10, 10]; drawable.skin.setRotationCenter(5, 5); - MockSkinPool.forDrawableSkin(drawable); expected.initFromBounds(-5, 5, -5, 5); t.same(snapToNearest(drawable.getAABB()), expected); @@ -111,7 +106,6 @@ test('scale', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; - MockSkinPool.forDrawableSkin(drawable); drawable.updateProperties({scale: [100, 50]}); expected.initFromBounds(0, 200, -25, 0); @@ -134,7 +128,6 @@ test('rotate and scale', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [100, 1000]; - MockSkinPool.forDrawableSkin(drawable); drawable.skin.setRotationCenter(50, 50); expected.initFromBounds(-50, 50, -950, 50); From 2a1d215e5040476387ffc7a4e09458ecb65fda46 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Thu, 12 Sep 2019 13:43:32 -0400 Subject: [PATCH 35/38] Revert "Revert "Revert "Revert "Skin alter push"""" --- src/BitmapSkin.js | 14 +++++----- src/Drawable.js | 9 ------- src/PenSkin.js | 23 +++++++++-------- src/RenderWebGL.js | 19 ++++++++++++++ src/SVGSkin.js | 48 ++++++++++++++++++++++++++--------- src/Skin.js | 15 +++++------ test/fixtures/MockSkinPool.js | 35 +++++++++++++++++++++++++ test/unit/DrawableTests.js | 7 +++++ 8 files changed, 124 insertions(+), 46 deletions(-) create mode 100644 test/fixtures/MockSkinPool.js diff --git a/src/BitmapSkin.js b/src/BitmapSkin.js index 94f2984..13d17bd 100644 --- a/src/BitmapSkin.js +++ b/src/BitmapSkin.js @@ -23,6 +23,12 @@ class BitmapSkin extends Skin { /** @type {Array} */ this._textureSize = [0, 0]; + + /** + * The "native" size, in texels, of this skin. + * @type {Array} + */ + this.size = [0, 0]; } /** @@ -43,13 +49,6 @@ class BitmapSkin extends Skin { return true; } - /** - * @return {Array} the "native" size, in texels, of this skin. - */ - get size () { - return [this._textureSize[0] / this._costumeResolution, this._textureSize[1] / this._costumeResolution]; - } - /** * @param {Array} scale - The scaling factors to be used. * @return {WebGLTexture} The GL texture representation of this skin when drawing at the given scale. @@ -110,6 +109,7 @@ class BitmapSkin extends Skin { // Do these last in case any of the above throws an exception this._costumeResolution = costumeResolution || 2; this._textureSize = BitmapSkin._getBitmapSize(bitmapData); + this.size = [this._textureSize[0] / this._costumeResolution, this._textureSize[1] / this._costumeResolution]; if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter(); this.setRotationCenter.apply(this, rotationCenter); diff --git a/src/Drawable.js b/src/Drawable.js index cf00d3f..786dcac 100644 --- a/src/Drawable.js +++ b/src/Drawable.js @@ -3,7 +3,6 @@ const twgl = require('twgl.js'); const Rectangle = require('./Rectangle'); const RenderConstants = require('./RenderConstants'); const ShaderManager = require('./ShaderManager'); -const Skin = require('./Skin'); const EffectTransform = require('./EffectTransform'); /** @@ -101,8 +100,6 @@ class Drawable { /** @todo move convex hull functionality, maybe bounds functionality overall, to Skin classes */ this._convexHullPoints = null; this._convexHullDirty = true; - - this._skinWasAltered = this._skinWasAltered.bind(this); } /** @@ -141,13 +138,7 @@ class Drawable { */ set skin (newSkin) { if (this._skin !== newSkin) { - if (this._skin) { - this._skin.removeListener(Skin.Events.WasAltered, this._skinWasAltered); - } this._skin = newSkin; - if (this._skin) { - this._skin.addListener(Skin.Events.WasAltered, this._skinWasAltered); - } this._skinWasAltered(); } } diff --git a/src/PenSkin.js b/src/PenSkin.js index 1e500ba..273cac1 100644 --- a/src/PenSkin.js +++ b/src/PenSkin.js @@ -88,6 +88,9 @@ class PenSkin extends Skin { /** @type {HTMLCanvasElement} */ this._canvas = document.createElement('canvas'); + /** @type {Array} */ + this._canvasSize = twgl.v3.create(); + /** @type {WebGLTexture} */ this._texture = null; @@ -165,7 +168,7 @@ class PenSkin extends Skin { * @return {Array} the "native" size, in texels, of this skin. [width, height] */ get size () { - return [this._canvas.width, this._canvas.height]; + return this._canvasSize; } /** @@ -188,13 +191,13 @@ class PenSkin extends Skin { clear () { const gl = this._renderer.gl; twgl.bindFramebufferInfo(gl, this._framebuffer); - + /* Reset framebuffer to transparent black */ 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); + ctx.clearRect(0, 0, this._canvasSize[0], this._canvasSize[1]); this._silhouetteDirty = true; } @@ -451,7 +454,7 @@ class PenSkin extends Skin { * @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) { + _drawRectangle (currentShader, texture, bounds, x = -this._canvasSize[0] / 2, y = this._canvasSize[1] / 2) { const gl = this._renderer.gl; const projection = twgl.m4.ortho( @@ -514,7 +517,7 @@ class PenSkin extends Skin { * @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) { + _drawToBuffer (texture = this._texture, x = -this._canvasSize[0] / 2, y = this._canvasSize[1] / 2) { if (texture !== this._texture && this._canvasDirty) { this._drawToBuffer(); } @@ -528,7 +531,7 @@ class PenSkin extends Skin { 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); + ctx.clearRect(0, 0, this._canvasSize[0], this._canvasSize[1]); this._canvasDirty = false; } @@ -564,8 +567,8 @@ class PenSkin extends Skin { this._bounds = new Rectangle(); this._bounds.initFromBounds(width / 2, width / -2, height / 2, height / -2); - this._canvas.width = width; - this._canvas.height = height; + this._canvas.width = this._canvasSize[0] = width; + this._canvas.height = this._canvasSize[1] = height; this._rotationCenter[0] = width / 2; this._rotationCenter[1] = height / 2; @@ -651,8 +654,8 @@ class PenSkin extends Skin { this._renderer.enterDrawRegion(this._toBufferDrawRegionId); // Sample the framebuffer's pixels into the silhouette instance - const skinPixels = new Uint8Array(Math.floor(this._canvas.width * this._canvas.height * 4)); - gl.readPixels(0, 0, this._canvas.width, this._canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, skinPixels); + const skinPixels = new Uint8Array(Math.floor(this._canvasSize[0] * this._canvasSize[1] * 4)); + gl.readPixels(0, 0, this._canvasSize[0], this._canvasSize[1], gl.RGBA, gl.UNSIGNED_BYTE, skinPixels); const skinCanvas = this._canvas; skinCanvas.width = bounds.width; diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js index 5277235..4186b11 100644 --- a/src/RenderWebGL.js +++ b/src/RenderWebGL.js @@ -3,6 +3,7 @@ const EventEmitter = require('events'); const hull = require('hull.js'); const twgl = require('twgl.js'); +const Skin = require('./Skin'); const BitmapSkin = require('./BitmapSkin'); const Drawable = require('./Drawable'); const Rectangle = require('./Rectangle'); @@ -291,6 +292,20 @@ class RenderWebGL extends EventEmitter { this.emit(RenderConstants.Events.NativeSizeChanged, {newSize: this._nativeSize}); } + /** + * Notify Drawables whose skin is the skin that changed. + * @param {Skin} skin - the skin that changed. + * @private + */ + _skinWasAltered (skin) { + for (let i = 0; i < this._allDrawables.length; i++) { + const drawable = this._allDrawables[i]; + if (drawable && drawable._skin === skin) { + drawable._skinWasAltered(); + } + } + } + /** * Create a new bitmap skin from a snapshot of the provided bitmap data. * @param {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} bitmapData - new contents for this skin. @@ -303,6 +318,7 @@ class RenderWebGL extends EventEmitter { const skinId = this._nextSkinId++; const newSkin = new BitmapSkin(skinId, this); newSkin.setBitmap(bitmapData, costumeResolution, rotationCenter); + newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } @@ -318,6 +334,7 @@ class RenderWebGL extends EventEmitter { const skinId = this._nextSkinId++; const newSkin = new SVGSkin(skinId, this); newSkin.setSVG(svgData, rotationCenter); + newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } @@ -329,6 +346,7 @@ class RenderWebGL extends EventEmitter { createPenSkin () { const skinId = this._nextSkinId++; const newSkin = new PenSkin(skinId, this); + newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } @@ -345,6 +363,7 @@ class RenderWebGL extends EventEmitter { const skinId = this._nextSkinId++; const newSkin = new TextBubbleSkin(skinId, this); newSkin.setTextBubble(type, text, pointsLeft); + newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } diff --git a/src/SVGSkin.js b/src/SVGSkin.js index 90e3908..90852ca 100644 --- a/src/SVGSkin.js +++ b/src/SVGSkin.js @@ -30,6 +30,24 @@ class SVGSkin extends Skin { /** @type {Number} */ this._maxTextureScale = 0; + + /** + * The natural size, in Scratch units, of this skin. + * @type {Array} + */ + this.size = [0, 0]; + + /** + * The viewbox offset of the svg. + * @type {Array} + */ + this._viewOffset = [0, 0]; + + /** + * The rotation center before offset by _viewOffset. + * @type {Array} + */ + this._rawRotationCenter = [NaN, NaN]; } /** @@ -43,21 +61,17 @@ class SVGSkin extends Skin { super.dispose(); } - /** - * @return {Array} the natural size, in Scratch units, of this skin. - */ - get size () { - return this._svgRenderer.size; - } - /** * Set the origin, in object space, about which this Skin should rotate. * @param {number} x - The x coordinate of the new rotation center. * @param {number} y - The y coordinate of the new rotation center. */ setRotationCenter (x, y) { - const viewOffset = this._svgRenderer.viewOffset; - super.setRotationCenter(x - viewOffset[0], y - viewOffset[1]); + if (x !== this._rawRotationCenter[0] || y !== this._rawRotationCenter[1]) { + this._rawRotationCenter[0] = x; + this._rawRotationCenter[1] = y; + super.setRotationCenter(x - this._viewOffset[0], y - this._viewOffset[1]); + } } /** @@ -100,7 +114,19 @@ class SVGSkin extends Skin { * @fires Skin.event:WasAltered */ setSVG (svgData, rotationCenter) { - this._svgRenderer.fromString(svgData, 1, () => { + this._svgRenderer.loadString(svgData); + + // Size must be updated synchronously because the VM sets the costume's `size` immediately after calling this. + // TODO: add either a callback to this function so the costume size can be set after this is done, + // or something in the VM to handle setting the costume size when Skin.Events.WasAltered is emitted. + this.size = this._svgRenderer.size; + if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter(); + this._viewOffset = this._svgRenderer.viewOffset; + // Reset rawRotationCenter when we update viewOffset. + this._rawRotationCenter = [NaN, NaN]; + this.setRotationCenter(rotationCenter[0], rotationCenter[1]); + + this._svgRenderer._draw(1, () => { const gl = this._renderer.gl; this._textureScale = this._maxTextureScale = 1; @@ -133,8 +159,6 @@ class SVGSkin extends Skin { this._maxTextureScale = testScale; } - if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter(); - this.setRotationCenter.apply(this, rotationCenter); this.emit(Skin.Events.WasAltered); }); } diff --git a/src/Skin.js b/src/Skin.js index 473c70b..8bd89d6 100644 --- a/src/Skin.js +++ b/src/Skin.js @@ -33,6 +33,13 @@ class Skin extends EventEmitter { /** @type {Vec3} */ this._rotationCenter = twgl.v3.create(0, 0); + /** + * The "native" size, in texels, of this skin. + * @member size + * @abstract + * @type {Array} + */ + /** * The uniforms to be used by the vertex and pixel shaders. * Some of these are used by other parts of the renderer as well. @@ -97,14 +104,6 @@ class Skin extends EventEmitter { return this._rotationCenter; } - /** - * @abstract - * @return {Array} the "native" size, in texels, of this skin. - */ - get size () { - return [0, 0]; - } - /** * Set the origin, in object space, about which this Skin should rotate. * @param {number} x - The x coordinate of the new rotation center. diff --git a/test/fixtures/MockSkinPool.js b/test/fixtures/MockSkinPool.js new file mode 100644 index 0000000..208d933 --- /dev/null +++ b/test/fixtures/MockSkinPool.js @@ -0,0 +1,35 @@ +const Skin = require('../../src/Skin'); + +class MockSkinPool { + constructor () { + this._allDrawables = []; + } + + static forDrawableSkin (drawable) { + const pool = new MockSkinPool(); + pool.addDrawable(drawable); + pool.addSkin(drawable.skin); + return pool; + } + + _skinWasAltered (skin) { + for (let i = 0; i < this._allDrawables.length; i++) { + const drawable = this._allDrawables[i]; + if (drawable && drawable._skin === skin) { + drawable._skinWasAltered(); + } + } + } + + addDrawable (drawable) { + this._allDrawables.push(drawable); + return drawable; + } + + addSkin (skin) { + skin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, skin)); + return skin; + } +} + +module.exports = MockSkinPool; diff --git a/test/unit/DrawableTests.js b/test/unit/DrawableTests.js index f0f163b..235e3d3 100644 --- a/test/unit/DrawableTests.js +++ b/test/unit/DrawableTests.js @@ -8,6 +8,7 @@ global.document = { const Drawable = require('../../src/Drawable'); const MockSkin = require('../fixtures/MockSkin'); +const MockSkinPool = require('../fixtures/MockSkinPool'); const Rectangle = require('../../src/Rectangle'); /** @@ -31,6 +32,7 @@ test('translate by position', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; + MockSkinPool.forDrawableSkin(drawable); expected.initFromBounds(0, 200, -50, 0); t.same(snapToNearest(drawable.getAABB()), expected); @@ -47,6 +49,7 @@ test('translate by costume center', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; + MockSkinPool.forDrawableSkin(drawable); drawable.skin.setRotationCenter(1, 0); expected.initFromBounds(-1, 199, -50, 0); @@ -64,6 +67,7 @@ test('translate and rotate', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; + MockSkinPool.forDrawableSkin(drawable); drawable.updateProperties({position: [1, 2], direction: 0}); expected.initFromBounds(1, 51, 2, 202); @@ -90,6 +94,7 @@ test('rotate by non-right-angles', t => { drawable.skin = new MockSkin(); drawable.skin.size = [10, 10]; drawable.skin.setRotationCenter(5, 5); + MockSkinPool.forDrawableSkin(drawable); expected.initFromBounds(-5, 5, -5, 5); t.same(snapToNearest(drawable.getAABB()), expected); @@ -106,6 +111,7 @@ test('scale', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; + MockSkinPool.forDrawableSkin(drawable); drawable.updateProperties({scale: [100, 50]}); expected.initFromBounds(0, 200, -25, 0); @@ -128,6 +134,7 @@ test('rotate and scale', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [100, 1000]; + MockSkinPool.forDrawableSkin(drawable); drawable.skin.setRotationCenter(50, 50); expected.initFromBounds(-50, 50, -950, 50); From 110371b029f456f5151dbb2ebed2db9d155d9cf3 Mon Sep 17 00:00:00 2001 From: "Michael \"Z\" Goddard" Date: Mon, 16 Sep 2019 17:22:27 -0400 Subject: [PATCH 36/38] synchronously store SVGRenderer props used in SVGSkin Some APIs need SVGSkin to update some of its values immediately when changing the SVG. Namely size needs to be synchronously set. However rotation center doesn't need to be set at that time so we can leave it to update where it currently is. --- src/SVGSkin.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/SVGSkin.js b/src/SVGSkin.js index 90852ca..7f49090 100644 --- a/src/SVGSkin.js +++ b/src/SVGSkin.js @@ -116,15 +116,13 @@ class SVGSkin extends Skin { setSVG (svgData, rotationCenter) { this._svgRenderer.loadString(svgData); - // Size must be updated synchronously because the VM sets the costume's `size` immediately after calling this. - // TODO: add either a callback to this function so the costume size can be set after this is done, - // or something in the VM to handle setting the costume size when Skin.Events.WasAltered is emitted. + // Size must be updated synchronously because the VM sets the costume's + // `size` immediately after calling this. this.size = this._svgRenderer.size; - if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter(); this._viewOffset = this._svgRenderer.viewOffset; - // Reset rawRotationCenter when we update viewOffset. + // Reset rawRotationCenter when we update viewOffset. The rotation + // center used to render will be updated later. this._rawRotationCenter = [NaN, NaN]; - this.setRotationCenter(rotationCenter[0], rotationCenter[1]); this._svgRenderer._draw(1, () => { const gl = this._renderer.gl; @@ -159,6 +157,9 @@ class SVGSkin extends Skin { this._maxTextureScale = testScale; } + if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter(); + this.setRotationCenter(rotationCenter[0], rotationCenter[1]); + this.emit(Skin.Events.WasAltered); }); } From 11665299bcc654630d53b9c59c35cc7eb04b4138 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Thu, 26 Sep 2019 09:39:05 -0500 Subject: [PATCH 37/38] Revert "Put Skin Alter Push Back In" --- src/BitmapSkin.js | 14 +++++------ src/Drawable.js | 9 +++++++ src/PenSkin.js | 23 ++++++++--------- src/RenderWebGL.js | 19 -------------- src/SVGSkin.js | 47 ++++++++--------------------------- src/Skin.js | 15 +++++------ test/fixtures/MockSkinPool.js | 35 -------------------------- test/unit/DrawableTests.js | 7 ------ 8 files changed, 45 insertions(+), 124 deletions(-) delete mode 100644 test/fixtures/MockSkinPool.js diff --git a/src/BitmapSkin.js b/src/BitmapSkin.js index 13d17bd..94f2984 100644 --- a/src/BitmapSkin.js +++ b/src/BitmapSkin.js @@ -23,12 +23,6 @@ class BitmapSkin extends Skin { /** @type {Array} */ this._textureSize = [0, 0]; - - /** - * The "native" size, in texels, of this skin. - * @type {Array} - */ - this.size = [0, 0]; } /** @@ -49,6 +43,13 @@ class BitmapSkin extends Skin { return true; } + /** + * @return {Array} the "native" size, in texels, of this skin. + */ + get size () { + return [this._textureSize[0] / this._costumeResolution, this._textureSize[1] / this._costumeResolution]; + } + /** * @param {Array} scale - The scaling factors to be used. * @return {WebGLTexture} The GL texture representation of this skin when drawing at the given scale. @@ -109,7 +110,6 @@ class BitmapSkin extends Skin { // Do these last in case any of the above throws an exception this._costumeResolution = costumeResolution || 2; this._textureSize = BitmapSkin._getBitmapSize(bitmapData); - this.size = [this._textureSize[0] / this._costumeResolution, this._textureSize[1] / this._costumeResolution]; if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter(); this.setRotationCenter.apply(this, rotationCenter); diff --git a/src/Drawable.js b/src/Drawable.js index 786dcac..cf00d3f 100644 --- a/src/Drawable.js +++ b/src/Drawable.js @@ -3,6 +3,7 @@ const twgl = require('twgl.js'); const Rectangle = require('./Rectangle'); const RenderConstants = require('./RenderConstants'); const ShaderManager = require('./ShaderManager'); +const Skin = require('./Skin'); const EffectTransform = require('./EffectTransform'); /** @@ -100,6 +101,8 @@ class Drawable { /** @todo move convex hull functionality, maybe bounds functionality overall, to Skin classes */ this._convexHullPoints = null; this._convexHullDirty = true; + + this._skinWasAltered = this._skinWasAltered.bind(this); } /** @@ -138,7 +141,13 @@ class Drawable { */ set skin (newSkin) { if (this._skin !== newSkin) { + if (this._skin) { + this._skin.removeListener(Skin.Events.WasAltered, this._skinWasAltered); + } this._skin = newSkin; + if (this._skin) { + this._skin.addListener(Skin.Events.WasAltered, this._skinWasAltered); + } this._skinWasAltered(); } } diff --git a/src/PenSkin.js b/src/PenSkin.js index 273cac1..1e500ba 100644 --- a/src/PenSkin.js +++ b/src/PenSkin.js @@ -88,9 +88,6 @@ class PenSkin extends Skin { /** @type {HTMLCanvasElement} */ this._canvas = document.createElement('canvas'); - /** @type {Array} */ - this._canvasSize = twgl.v3.create(); - /** @type {WebGLTexture} */ this._texture = null; @@ -168,7 +165,7 @@ class PenSkin extends Skin { * @return {Array} the "native" size, in texels, of this skin. [width, height] */ get size () { - return this._canvasSize; + return [this._canvas.width, this._canvas.height]; } /** @@ -191,13 +188,13 @@ class PenSkin extends Skin { clear () { const gl = this._renderer.gl; twgl.bindFramebufferInfo(gl, this._framebuffer); - + /* Reset framebuffer to transparent black */ gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); const ctx = this._canvas.getContext('2d'); - ctx.clearRect(0, 0, this._canvasSize[0], this._canvasSize[1]); + ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); this._silhouetteDirty = true; } @@ -454,7 +451,7 @@ class PenSkin extends Skin { * @param {number} x - centered at x * @param {number} y - centered at y */ - _drawRectangle (currentShader, texture, bounds, x = -this._canvasSize[0] / 2, y = this._canvasSize[1] / 2) { + _drawRectangle (currentShader, texture, bounds, x = -this._canvas.width / 2, y = this._canvas.height / 2) { const gl = this._renderer.gl; const projection = twgl.m4.ortho( @@ -517,7 +514,7 @@ class PenSkin extends Skin { * @param {number} x - texture centered at x * @param {number} y - texture centered at y */ - _drawToBuffer (texture = this._texture, x = -this._canvasSize[0] / 2, y = this._canvasSize[1] / 2) { + _drawToBuffer (texture = this._texture, x = -this._canvas.width / 2, y = this._canvas.height / 2) { if (texture !== this._texture && this._canvasDirty) { this._drawToBuffer(); } @@ -531,7 +528,7 @@ class PenSkin extends Skin { 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._canvasSize[0], this._canvasSize[1]); + ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); this._canvasDirty = false; } @@ -567,8 +564,8 @@ class PenSkin extends Skin { this._bounds = new Rectangle(); this._bounds.initFromBounds(width / 2, width / -2, height / 2, height / -2); - this._canvas.width = this._canvasSize[0] = width; - this._canvas.height = this._canvasSize[1] = height; + this._canvas.width = width; + this._canvas.height = height; this._rotationCenter[0] = width / 2; this._rotationCenter[1] = height / 2; @@ -654,8 +651,8 @@ class PenSkin extends Skin { this._renderer.enterDrawRegion(this._toBufferDrawRegionId); // Sample the framebuffer's pixels into the silhouette instance - const skinPixels = new Uint8Array(Math.floor(this._canvasSize[0] * this._canvasSize[1] * 4)); - gl.readPixels(0, 0, this._canvasSize[0], this._canvasSize[1], gl.RGBA, gl.UNSIGNED_BYTE, skinPixels); + const skinPixels = new Uint8Array(Math.floor(this._canvas.width * this._canvas.height * 4)); + gl.readPixels(0, 0, this._canvas.width, this._canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, skinPixels); const skinCanvas = this._canvas; skinCanvas.width = bounds.width; diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js index 4186b11..5277235 100644 --- a/src/RenderWebGL.js +++ b/src/RenderWebGL.js @@ -3,7 +3,6 @@ const EventEmitter = require('events'); const hull = require('hull.js'); const twgl = require('twgl.js'); -const Skin = require('./Skin'); const BitmapSkin = require('./BitmapSkin'); const Drawable = require('./Drawable'); const Rectangle = require('./Rectangle'); @@ -292,20 +291,6 @@ class RenderWebGL extends EventEmitter { this.emit(RenderConstants.Events.NativeSizeChanged, {newSize: this._nativeSize}); } - /** - * Notify Drawables whose skin is the skin that changed. - * @param {Skin} skin - the skin that changed. - * @private - */ - _skinWasAltered (skin) { - for (let i = 0; i < this._allDrawables.length; i++) { - const drawable = this._allDrawables[i]; - if (drawable && drawable._skin === skin) { - drawable._skinWasAltered(); - } - } - } - /** * Create a new bitmap skin from a snapshot of the provided bitmap data. * @param {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} bitmapData - new contents for this skin. @@ -318,7 +303,6 @@ class RenderWebGL extends EventEmitter { const skinId = this._nextSkinId++; const newSkin = new BitmapSkin(skinId, this); newSkin.setBitmap(bitmapData, costumeResolution, rotationCenter); - newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } @@ -334,7 +318,6 @@ class RenderWebGL extends EventEmitter { const skinId = this._nextSkinId++; const newSkin = new SVGSkin(skinId, this); newSkin.setSVG(svgData, rotationCenter); - newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } @@ -346,7 +329,6 @@ class RenderWebGL extends EventEmitter { createPenSkin () { const skinId = this._nextSkinId++; const newSkin = new PenSkin(skinId, this); - newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } @@ -363,7 +345,6 @@ class RenderWebGL extends EventEmitter { const skinId = this._nextSkinId++; const newSkin = new TextBubbleSkin(skinId, this); newSkin.setTextBubble(type, text, pointsLeft); - newSkin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, newSkin)); this._allSkins[skinId] = newSkin; return skinId; } diff --git a/src/SVGSkin.js b/src/SVGSkin.js index 7f49090..90e3908 100644 --- a/src/SVGSkin.js +++ b/src/SVGSkin.js @@ -30,24 +30,6 @@ class SVGSkin extends Skin { /** @type {Number} */ this._maxTextureScale = 0; - - /** - * The natural size, in Scratch units, of this skin. - * @type {Array} - */ - this.size = [0, 0]; - - /** - * The viewbox offset of the svg. - * @type {Array} - */ - this._viewOffset = [0, 0]; - - /** - * The rotation center before offset by _viewOffset. - * @type {Array} - */ - this._rawRotationCenter = [NaN, NaN]; } /** @@ -61,17 +43,21 @@ class SVGSkin extends Skin { super.dispose(); } + /** + * @return {Array} the natural size, in Scratch units, of this skin. + */ + get size () { + return this._svgRenderer.size; + } + /** * Set the origin, in object space, about which this Skin should rotate. * @param {number} x - The x coordinate of the new rotation center. * @param {number} y - The y coordinate of the new rotation center. */ setRotationCenter (x, y) { - if (x !== this._rawRotationCenter[0] || y !== this._rawRotationCenter[1]) { - this._rawRotationCenter[0] = x; - this._rawRotationCenter[1] = y; - super.setRotationCenter(x - this._viewOffset[0], y - this._viewOffset[1]); - } + const viewOffset = this._svgRenderer.viewOffset; + super.setRotationCenter(x - viewOffset[0], y - viewOffset[1]); } /** @@ -114,17 +100,7 @@ class SVGSkin extends Skin { * @fires Skin.event:WasAltered */ setSVG (svgData, rotationCenter) { - this._svgRenderer.loadString(svgData); - - // Size must be updated synchronously because the VM sets the costume's - // `size` immediately after calling this. - this.size = this._svgRenderer.size; - this._viewOffset = this._svgRenderer.viewOffset; - // Reset rawRotationCenter when we update viewOffset. The rotation - // center used to render will be updated later. - this._rawRotationCenter = [NaN, NaN]; - - this._svgRenderer._draw(1, () => { + this._svgRenderer.fromString(svgData, 1, () => { const gl = this._renderer.gl; this._textureScale = this._maxTextureScale = 1; @@ -158,8 +134,7 @@ class SVGSkin extends Skin { } if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter(); - this.setRotationCenter(rotationCenter[0], rotationCenter[1]); - + this.setRotationCenter.apply(this, rotationCenter); this.emit(Skin.Events.WasAltered); }); } diff --git a/src/Skin.js b/src/Skin.js index 8bd89d6..473c70b 100644 --- a/src/Skin.js +++ b/src/Skin.js @@ -33,13 +33,6 @@ class Skin extends EventEmitter { /** @type {Vec3} */ this._rotationCenter = twgl.v3.create(0, 0); - /** - * The "native" size, in texels, of this skin. - * @member size - * @abstract - * @type {Array} - */ - /** * The uniforms to be used by the vertex and pixel shaders. * Some of these are used by other parts of the renderer as well. @@ -104,6 +97,14 @@ class Skin extends EventEmitter { return this._rotationCenter; } + /** + * @abstract + * @return {Array} the "native" size, in texels, of this skin. + */ + get size () { + return [0, 0]; + } + /** * Set the origin, in object space, about which this Skin should rotate. * @param {number} x - The x coordinate of the new rotation center. diff --git a/test/fixtures/MockSkinPool.js b/test/fixtures/MockSkinPool.js deleted file mode 100644 index 208d933..0000000 --- a/test/fixtures/MockSkinPool.js +++ /dev/null @@ -1,35 +0,0 @@ -const Skin = require('../../src/Skin'); - -class MockSkinPool { - constructor () { - this._allDrawables = []; - } - - static forDrawableSkin (drawable) { - const pool = new MockSkinPool(); - pool.addDrawable(drawable); - pool.addSkin(drawable.skin); - return pool; - } - - _skinWasAltered (skin) { - for (let i = 0; i < this._allDrawables.length; i++) { - const drawable = this._allDrawables[i]; - if (drawable && drawable._skin === skin) { - drawable._skinWasAltered(); - } - } - } - - addDrawable (drawable) { - this._allDrawables.push(drawable); - return drawable; - } - - addSkin (skin) { - skin.addListener(Skin.Events.WasAltered, this._skinWasAltered.bind(this, skin)); - return skin; - } -} - -module.exports = MockSkinPool; diff --git a/test/unit/DrawableTests.js b/test/unit/DrawableTests.js index 235e3d3..f0f163b 100644 --- a/test/unit/DrawableTests.js +++ b/test/unit/DrawableTests.js @@ -8,7 +8,6 @@ global.document = { const Drawable = require('../../src/Drawable'); const MockSkin = require('../fixtures/MockSkin'); -const MockSkinPool = require('../fixtures/MockSkinPool'); const Rectangle = require('../../src/Rectangle'); /** @@ -32,7 +31,6 @@ test('translate by position', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; - MockSkinPool.forDrawableSkin(drawable); expected.initFromBounds(0, 200, -50, 0); t.same(snapToNearest(drawable.getAABB()), expected); @@ -49,7 +47,6 @@ test('translate by costume center', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; - MockSkinPool.forDrawableSkin(drawable); drawable.skin.setRotationCenter(1, 0); expected.initFromBounds(-1, 199, -50, 0); @@ -67,7 +64,6 @@ test('translate and rotate', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; - MockSkinPool.forDrawableSkin(drawable); drawable.updateProperties({position: [1, 2], direction: 0}); expected.initFromBounds(1, 51, 2, 202); @@ -94,7 +90,6 @@ test('rotate by non-right-angles', t => { drawable.skin = new MockSkin(); drawable.skin.size = [10, 10]; drawable.skin.setRotationCenter(5, 5); - MockSkinPool.forDrawableSkin(drawable); expected.initFromBounds(-5, 5, -5, 5); t.same(snapToNearest(drawable.getAABB()), expected); @@ -111,7 +106,6 @@ test('scale', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [200, 50]; - MockSkinPool.forDrawableSkin(drawable); drawable.updateProperties({scale: [100, 50]}); expected.initFromBounds(0, 200, -25, 0); @@ -134,7 +128,6 @@ test('rotate and scale', t => { const drawable = new Drawable(); drawable.skin = new MockSkin(); drawable.skin.size = [100, 1000]; - MockSkinPool.forDrawableSkin(drawable); drawable.skin.setRotationCenter(50, 50); expected.initFromBounds(-50, 50, -950, 50); From 8e836a7a1154d0483ff76d98cb46b30b18cdaa67 Mon Sep 17 00:00:00 2001 From: DD Liu Date: Tue, 1 Oct 2019 15:36:13 -0400 Subject: [PATCH 38/38] Add a bug to the todos for missing drawable --- src/RenderWebGL.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js index 99ba413..a8350e2 100644 --- a/src/RenderWebGL.js +++ b/src/RenderWebGL.js @@ -1320,7 +1320,7 @@ class RenderWebGL extends EventEmitter { */ updateDrawableSkinId (drawableID, skinId) { const drawable = this._allDrawables[drawableID]; - // TODO: vm's requests to drawableID that do not have a Drawable object. + // TODO: https://github.com/LLK/scratch-vm/issues/2288 if (!drawable) return; drawable.skin = this._allSkins[skinId]; } @@ -1332,7 +1332,7 @@ class RenderWebGL extends EventEmitter { */ updateDrawableRotationCenter (drawableID, rotationCenter) { const drawable = this._allDrawables[drawableID]; - // TODO: vm's requests to drawableID that do not have a Drawable object. + // TODO: https://github.com/LLK/scratch-vm/issues/2288 if (!drawable) return; drawable.skin.setRotationCenter(rotationCenter[0], rotationCenter[1]); } @@ -1345,7 +1345,7 @@ class RenderWebGL extends EventEmitter { */ updateDrawableSkinIdRotationCenter (drawableID, skinId, rotationCenter) { const drawable = this._allDrawables[drawableID]; - // TODO: vm's requests to drawableID that do not have a Drawable object. + // TODO: https://github.com/LLK/scratch-vm/issues/2288 if (!drawable) return; drawable.skin = this._allSkins[skinId]; drawable.skin.setRotationCenter(rotationCenter[0], rotationCenter[1]); @@ -1358,7 +1358,7 @@ class RenderWebGL extends EventEmitter { */ updateDrawablePosition (drawableID, position) { const drawable = this._allDrawables[drawableID]; - // TODO: vm's requests to drawableID that do not have a Drawable object. + // TODO: https://github.com/LLK/scratch-vm/issues/2288 if (!drawable) return; drawable.updatePosition(position); } @@ -1370,7 +1370,7 @@ class RenderWebGL extends EventEmitter { */ updateDrawableDirection (drawableID, direction) { const drawable = this._allDrawables[drawableID]; - // TODO: vm's requests to drawableID that do not have a Drawable object. + // TODO: https://github.com/LLK/scratch-vm/issues/2288 if (!drawable) return; drawable.updateDirection(direction); } @@ -1382,7 +1382,7 @@ class RenderWebGL extends EventEmitter { */ updateDrawableScale (drawableID, scale) { const drawable = this._allDrawables[drawableID]; - // TODO: vm's requests to drawableID that do not have a Drawable object. + // TODO: https://github.com/LLK/scratch-vm/issues/2288 if (!drawable) return; drawable.updateScale(scale); } @@ -1395,7 +1395,7 @@ class RenderWebGL extends EventEmitter { */ updateDrawableDirectionScale (drawableID, direction, scale) { const drawable = this._allDrawables[drawableID]; - // TODO: vm's requests to drawableID that do not have a Drawable object. + // TODO: https://github.com/LLK/scratch-vm/issues/2288 if (!drawable) return; drawable.updateDirection(direction); drawable.updateScale(scale); @@ -1408,7 +1408,7 @@ class RenderWebGL extends EventEmitter { */ updateDrawableVisible (drawableID, visible) { const drawable = this._allDrawables[drawableID]; - // TODO: vm's requests to drawableID that do not have a Drawable object. + // TODO: https://github.com/LLK/scratch-vm/issues/2288 if (!drawable) return; drawable.updateVisible(visible); } @@ -1421,7 +1421,7 @@ class RenderWebGL extends EventEmitter { */ updateDrawableEffect (drawableID, effectName, value) { const drawable = this._allDrawables[drawableID]; - // TODO: vm's requests to drawableID that do not have a Drawable object. + // TODO: https://github.com/LLK/scratch-vm/issues/2288 if (!drawable) return; drawable.updateEffect(effectName, value); } @@ -1436,7 +1436,7 @@ class RenderWebGL extends EventEmitter { const drawable = this._allDrawables[drawableID]; if (!drawable) { /** - * @todo fix whatever's wrong in the VM which causes this, then add a warning or throw here. + * @todo(https://github.com/LLK/scratch-vm/issues/2288) fix whatever's wrong in the VM which causes this, then add a warning or throw here. * Right now this happens so much on some projects that a warning or exception here can hang the browser. */ return; @@ -1462,7 +1462,7 @@ class RenderWebGL extends EventEmitter { const drawable = this._allDrawables[drawableID]; if (!drawable) { - // TODO: fix whatever's wrong in the VM which causes this, then add a warning or throw here. + // @todo(https://github.com/LLK/scratch-vm/issues/2288) fix whatever's wrong in the VM which causes this, then add a warning or throw here. // Right now this happens so much on some projects that a warning or exception here can hang the browser. return [x, y]; }