Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/BitmapSkin.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,11 @@ class BitmapSkin extends Skin {
/**
* Get the bounds of the drawable for determining its fenced position.
* @param {Array<number>} 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);
}

/**
Expand Down
39 changes: 19 additions & 20 deletions src/Drawable.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.');
}
Expand All @@ -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.');
}
Expand All @@ -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;
}

/**
Expand All @@ -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);
}

/**
Expand Down
99 changes: 99 additions & 0 deletions src/Rectangle.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,105 @@ class Rectangle {
}
}

/**
* Initialize a Rectangle to a 1 unit square transformed by a model matrix.
* @param {Array.<number>} 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];

// 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;
this.bottom = -y + m31;
}

/**
* Determine if this Rectangle intersects some other.
* Note that this is a comparison assuming the Rectangle was
Expand Down
7 changes: 4 additions & 3 deletions src/RenderWebGL.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}

Expand Down
5 changes: 3 additions & 2 deletions src/Skin.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,11 @@ class Skin extends EventEmitter {
/**
* Get the bounds of the drawable for determining its fenced position.
* @param {Array<number>} 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);
}

/**
Expand Down