Skip to content

Instantly share code, notes, and snippets.

@gskinner
Last active August 29, 2015 14:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gskinner/1dfb0d274c2fd3aa5ede to your computer and use it in GitHub Desktop.
Save gskinner/1dfb0d274c2fd3aa5ede to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<!-- include the performance framework first, so it can initialize and inject its UI -->
<script src="perfFramework.js"></script>
<!-- this test will use PreloadJS to load required assets -->
<script src="../../examples/assets/preloadjs-NEXT.min.js"></script>
<script>
function runTest(version, qs) {
var ns = checkVersion("0.5") ? createjs : window;
var canvas = document.getElementById("testCanvas");
var w=canvas.width, h=canvas.height;
var stage = new ns.Stage("testCanvas");
stage.tickOnUpdate = false;
time("total");
time("instantiate");
var arr = [], l=100000;
for (var i=0; i<l; i++) {
arr.push(new ns.Matrix2D());
}
endTime("instantiate", true);
time("props");
for (var i=0; i<l; i++) {
var mtx = arr[i];
var props = i%16;
if (props&1) { mtx.compositeOperation = "foo"; }
if (props&2) { mtx.alpha = Math.random(); }
if (props&4) { mtx.shadow = {}; }
if (props&8) { mtx.visible = false; }
}
endTime("props", true);
time("calculate");
var sum = 0;
for (var i=0; i<l; i+=2) {
var mtx0 = arr[i], mtx1 = arr[i+1];
mtx0.appendMatrix(mtx1);
mtx1.appendMatrix(mtx0);
}
endTime("calculate", true);
endTime("total", true);
endTest();
}
</script>
<p>Tests performance of creating, populating, and appending a transform to 100k Matrix2D instances.</p>
<canvas id="testCanvas" width="900" height="450"></canvas>
</body>
</html>
/*
* Matrix2D
* Visit http://createjs.com/ for documentation, updates and examples.
*
* Copyright (c) 2010 gskinner.com, inc.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* @module EaselJS
*/
// namespace:
this.createjs = this.createjs||{};
(function() {
"use strict";
/**
* Represents an affine transformation matrix, and provides tools for constructing and concatenating matrixes.
* @class Matrix2D
* @param {Number} [a=1] Specifies the a property for the new matrix.
* @param {Number} [b=0] Specifies the b property for the new matrix.
* @param {Number} [c=0] Specifies the c property for the new matrix.
* @param {Number} [d=1] Specifies the d property for the new matrix.
* @param {Number} [tx=0] Specifies the tx property for the new matrix.
* @param {Number} [ty=0] Specifies the ty property for the new matrix.
* @constructor
**/
var Matrix2D = function(a, b, c, d, tx, ty) {
this.initialize(a, b, c, d, tx, ty);
};
var p = Matrix2D.prototype;
Matrix2D.prototype.constructor = Matrix2D;
// static public properties:
/**
* An identity matrix, representing a null transformation.
* @property identity
* @static
* @type Matrix2D
* @readonly
**/
Matrix2D.identity = null; // set at bottom of class definition.
/**
* Multiplier for converting degrees to radians. Used internally by Matrix2D.
* @property DEG_TO_RAD
* @static
* @final
* @type Number
* @readonly
**/
Matrix2D.DEG_TO_RAD = Math.PI/180;
// public properties:
/**
* Position (0, 0) in a 3x3 affine transformation matrix.
* @property a
* @type Number
**/
//p.a = 1;
/**
* Position (0, 1) in a 3x3 affine transformation matrix.
* @property b
* @type Number
**/
//p.b = 0;
/**
* Position (1, 0) in a 3x3 affine transformation matrix.
* @property c
* @type Number
**/
// p.c = 0;
/**
* Position (1, 1) in a 3x3 affine transformation matrix.
* @property d
* @type Number
**/
// p.d = 1;
/**
* Position (2, 0) in a 3x3 affine transformation matrix.
* @property tx
* @type Number
**/
// p.tx = 0;
/**
* Position (2, 1) in a 3x3 affine transformation matrix.
* @property ty
* @type Number
**/
// p.ty = 0;
/**
* Property representing the alpha that will be applied to a display object. This is not part of matrix
* operations, but is used for operations like getConcatenatedMatrix to provide concatenated alpha values.
* @property alpha
* @type Number
**/
//p.alpha = 1;
/**
* Property representing the shadow that will be applied to a display object. This is not part of matrix
* operations, but is used for operations like getConcatenatedMatrix to provide concatenated shadow values.
* @property shadow
* @type Shadow
**/
//p.shadow = null;
/**
* Property representing the compositeOperation that will be applied to a display object. This is not part of
* matrix operations, but is used for operations like getConcatenatedMatrix to provide concatenated
* compositeOperation values. You can find a list of valid composite operations at:
* <a href="https://developer.mozilla.org/en/Canvas_tutorial/Compositing">https://developer.mozilla.org/en/Canvas_tutorial/Compositing</a>
* @property compositeOperation
* @type String
**/
//p.compositeOperation = null;
/**
* Property representing the value for visible that will be applied to a display object. This is not part of matrix
* operations, but is used for operations like getConcatenatedMatrix to provide concatenated visible values.
* @property visible
* @type Boolean
**/
//p.visible = true;
// constructor:
/**
* Initialization method. Can also be used to reinitialize the instance.
* @method initialize
* @param {Number} [a=1] Specifies the a property for the new matrix.
* @param {Number} [b=0] Specifies the b property for the new matrix.
* @param {Number} [c=0] Specifies the c property for the new matrix.
* @param {Number} [d=1] Specifies the d property for the new matrix.
* @param {Number} [tx=0] Specifies the tx property for the new matrix.
* @param {Number} [ty=0] Specifies the ty property for the new matrix.
* @return {Matrix2D} This instance. Useful for chaining method calls.
*/
p.initialize = function(a, b, c, d, tx, ty) {
this.a = (a == null) ? 1 : a;
this.b = b || 0;
this.c = c || 0;
this.d = (d == null) ? 1 : d;
this.tx = tx || 0;
this.ty = ty || 0;
this.alpha = 1;
this.shadow = null;
this.compositeOperation = null;
this.visible = true;
return this;
};
// public methods:
/**
* Concatenates the specified matrix properties with this matrix. All parameters are required.
* @method prepend
* @param {Number} a
* @param {Number} b
* @param {Number} c
* @param {Number} d
* @param {Number} tx
* @param {Number} ty
* @return {Matrix2D} This matrix. Useful for chaining method calls.
**/
p.prepend = function(a, b, c, d, tx, ty) {
var tx1 = this.tx;
if (a != 1 || b != 0 || c != 0 || d != 1) {
var a1 = this.a;
var c1 = this.c;
this.a = a1*a+this.b*c;
this.b = a1*b+this.b*d;
this.c = c1*a+this.d*c;
this.d = c1*b+this.d*d;
}
this.tx = tx1*a+this.ty*c+tx;
this.ty = tx1*b+this.ty*d+ty;
return this;
};
/**
* Appends the specified matrix properties with this matrix. All parameters are required.
* @method append
* @param {Number} a
* @param {Number} b
* @param {Number} c
* @param {Number} d
* @param {Number} tx
* @param {Number} ty
* @return {Matrix2D} This matrix. Useful for chaining method calls.
**/
p.append = function(a, b, c, d, tx, ty) {
var a1 = this.a;
var b1 = this.b;
var c1 = this.c;
var d1 = this.d;
this.a = a*a1+b*c1;
this.b = a*b1+b*d1;
this.c = c*a1+d*c1;
this.d = c*b1+d*d1;
this.tx = tx*a1+ty*c1+this.tx;
this.ty = tx*b1+ty*d1+this.ty;
return this;
};
/**
* Prepends the specified matrix with this matrix.
* @method prependMatrix
* @param {Matrix2D} matrix
* @return {Matrix2D} This matrix. Useful for chaining method calls.
**/
p.prependMatrix = function(matrix) {
this.prepend(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty);
this.prependProperties(matrix.alpha, matrix.shadow, matrix.compositeOperation, matrix.visible);
return this;
};
/**
* Appends the specified matrix with this matrix.
* @method appendMatrix
* @param {Matrix2D} matrix
* @return {Matrix2D} This matrix. Useful for chaining method calls.
**/
p.appendMatrix = function(matrix) {
this.append(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty);
this.appendProperties(matrix.alpha, matrix.shadow, matrix.compositeOperation, matrix.visible);
return this;
};
/**
* Generates matrix properties from the specified display object transform properties, and prepends them with this matrix.
* For example, you can use this to generate a matrix from a display object: var mtx = new Matrix2D();
* mtx.prependTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation);
* @method prependTransform
* @param {Number} x
* @param {Number} y
* @param {Number} scaleX
* @param {Number} scaleY
* @param {Number} rotation
* @param {Number} skewX
* @param {Number} skewY
* @param {Number} regX Optional.
* @param {Number} regY Optional.
* @return {Matrix2D} This matrix. Useful for chaining method calls.
**/
p.prependTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, regX, regY) {
if (rotation%360) {
var r = rotation*Matrix2D.DEG_TO_RAD;
var cos = Math.cos(r);
var sin = Math.sin(r);
} else {
cos = 1;
sin = 0;
}
if (regX || regY) {
// append the registration offset:
this.tx -= regX; this.ty -= regY;
}
if (skewX || skewY) {
// TODO: can this be combined into a single prepend operation?
skewX *= Matrix2D.DEG_TO_RAD;
skewY *= Matrix2D.DEG_TO_RAD;
this.prepend(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, 0, 0);
this.prepend(Math.cos(skewY), Math.sin(skewY), -Math.sin(skewX), Math.cos(skewX), x, y);
} else {
this.prepend(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, x, y);
}
return this;
};
/**
* Generates matrix properties from the specified display object transform properties, and appends them with this matrix.
* For example, you can use this to generate a matrix from a display object: var mtx = new Matrix2D();
* mtx.appendTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation);
* @method appendTransform
* @param {Number} x
* @param {Number} y
* @param {Number} scaleX
* @param {Number} scaleY
* @param {Number} rotation
* @param {Number} skewX
* @param {Number} skewY
* @param {Number} regX Optional.
* @param {Number} regY Optional.
* @return {Matrix2D} This matrix. Useful for chaining method calls.
**/
p.appendTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, regX, regY) {
if (rotation%360) {
var r = rotation*Matrix2D.DEG_TO_RAD;
var cos = Math.cos(r);
var sin = Math.sin(r);
} else {
cos = 1;
sin = 0;
}
if (skewX || skewY) {
// TODO: can this be combined into a single append?
skewX *= Matrix2D.DEG_TO_RAD;
skewY *= Matrix2D.DEG_TO_RAD;
this.append(Math.cos(skewY), Math.sin(skewY), -Math.sin(skewX), Math.cos(skewX), x, y);
this.append(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, 0, 0);
} else {
this.append(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, x, y);
}
if (regX || regY) {
// prepend the registration offset:
this.tx -= regX*this.a+regY*this.c;
this.ty -= regX*this.b+regY*this.d;
}
return this;
};
/**
* Applies a rotation transformation to the matrix.
* @method rotate
* @param {Number} angle The angle in radians. To use degrees, multiply by <code>Math.PI/180</code>.
* @return {Matrix2D} This matrix. Useful for chaining method calls.
**/
p.rotate = function(angle) {
var cos = Math.cos(angle);
var sin = Math.sin(angle);
var a1 = this.a;
var c1 = this.c;
var tx1 = this.tx;
this.a = a1*cos-this.b*sin;
this.b = a1*sin+this.b*cos;
this.c = c1*cos-this.d*sin;
this.d = c1*sin+this.d*cos;
this.tx = tx1*cos-this.ty*sin;
this.ty = tx1*sin+this.ty*cos;
return this;
};
/**
* Applies a skew transformation to the matrix.
* @method skew
* @param {Number} skewX The amount to skew horizontally in degrees.
* @param {Number} skewY The amount to skew vertically in degrees.
* @return {Matrix2D} This matrix. Useful for chaining method calls.
*/
p.skew = function(skewX, skewY) {
skewX = skewX*Matrix2D.DEG_TO_RAD;
skewY = skewY*Matrix2D.DEG_TO_RAD;
this.append(Math.cos(skewY), Math.sin(skewY), -Math.sin(skewX), Math.cos(skewX), 0, 0);
return this;
};
/**
* Applies a scale transformation to the matrix.
* @method scale
* @param {Number} x The amount to scale horizontally
* @param {Number} y The amount to scale vertically
* @return {Matrix2D} This matrix. Useful for chaining method calls.
**/
p.scale = function(x, y) {
this.a *= x;
this.d *= y;
this.c *= x;
this.b *= y;
this.tx *= x;
this.ty *= y;
return this;
};
/**
* Translates the matrix on the x and y axes.
* @method translate
* @param {Number} x
* @param {Number} y
* @return {Matrix2D} This matrix. Useful for chaining method calls.
**/
p.translate = function(x, y) {
this.tx += x;
this.ty += y;
return this;
};
/**
* Sets the properties of the matrix to those of an identity matrix (one that applies a null transformation).
* @method identity
* @return {Matrix2D} This matrix. Useful for chaining method calls.
**/
p.identity = function() {
this.alpha = this.a = this.d = 1;
this.b = this.c = this.tx = this.ty = 0;
this.shadow = this.compositeOperation = null;
this.visible = true;
return this;
};
/**
* Inverts the matrix, causing it to perform the opposite transformation.
* @method invert
* @return {Matrix2D} This matrix. Useful for chaining method calls.
**/
p.invert = function() {
var a1 = this.a;
var b1 = this.b;
var c1 = this.c;
var d1 = this.d;
var tx1 = this.tx;
var n = a1*d1-b1*c1;
this.a = d1/n;
this.b = -b1/n;
this.c = -c1/n;
this.d = a1/n;
this.tx = (c1*this.ty-d1*tx1)/n;
this.ty = -(a1*this.ty-b1*tx1)/n;
return this;
};
/**
* Returns true if the matrix is an identity matrix.
* @method isIdentity
* @return {Boolean}
**/
p.isIdentity = function() {
return this.tx == 0 && this.ty == 0 && this.a == 1 && this.b == 0 && this.c == 0 && this.d == 1;
};
/**
* Transforms a point according to this matrix.
* @method transformPoint
* @param {Number} x The x component of the point to transform.
* @param {Number} y The y component of the point to transform.
* @param {Point | Object} [pt] An object to copy the result into. If omitted a generic object with x/y properties will be returned.
* @return {Point} This matrix. Useful for chaining method calls.
**/
p.transformPoint = function(x, y, pt) {
pt = pt||{};
pt.x = x*this.a+y*this.c+this.tx;
pt.y = x*this.b+y*this.d+this.ty;
return pt;
};
/**
* Decomposes the matrix into transform properties (x, y, scaleX, scaleY, and rotation). Note that this these values
* may not match the transform properties you used to generate the matrix, though they will produce the same visual
* results.
* @method decompose
* @param {Object} target The object to apply the transform properties to. If null, then a new object will be returned.
* @return {Matrix2D} This matrix. Useful for chaining method calls.
*/
p.decompose = function(target) {
// TODO: it would be nice to be able to solve for whether the matrix can be decomposed into only scale/rotation
// even when scale is negative
if (target == null) { target = {}; }
target.x = this.tx;
target.y = this.ty;
target.scaleX = Math.sqrt(this.a * this.a + this.b * this.b);
target.scaleY = Math.sqrt(this.c * this.c + this.d * this.d);
var skewX = Math.atan2(-this.c, this.d);
var skewY = Math.atan2(this.b, this.a);
if (skewX == skewY) {
target.rotation = skewY/Matrix2D.DEG_TO_RAD;
if (this.a < 0 && this.d >= 0) {
target.rotation += (target.rotation <= 0) ? 180 : -180;
}
target.skewX = target.skewY = 0;
} else {
target.skewX = skewX/Matrix2D.DEG_TO_RAD;
target.skewY = skewY/Matrix2D.DEG_TO_RAD;
}
return target;
};
/**
* Reinitializes all matrix properties to those specified.
* @method reinitialize
* @param {Number} [a=1] Specifies the a property for the new matrix.
* @param {Number} [b=0] Specifies the b property for the new matrix.
* @param {Number} [c=0] Specifies the c property for the new matrix.
* @param {Number} [d=1] Specifies the d property for the new matrix.
* @param {Number} [tx=0] Specifies the tx property for the new matrix.
* @param {Number} [ty=0] Specifies the ty property for the new matrix.
* @param {Number} [alpha=1] desired alpha value
* @param {Shadow} [shadow=null] desired shadow value
* @param {String} [compositeOperation=null] desired composite operation value
* @param {Boolean} [visible=true] desired visible value
* @return {Matrix2D} This matrix. Useful for chaining method calls.
*/
p.reinitialize = function(a, b, c, d, tx, ty, alpha, shadow, compositeOperation, visible) {
this.initialize(a,b,c,d,tx,ty);
this.alpha = alpha == null ? 1 : alpha;
this.shadow = shadow;
this.compositeOperation = compositeOperation;
this.visible = visible == null ? true : visible;
return this;
};
/**
* Copies all properties from the specified matrix to this matrix.
* @method copy
* @param {Matrix2D} matrix The matrix to copy properties from.
* @return {Matrix2D} This matrix. Useful for chaining method calls.
*/
p.copy = function(matrix) {
return this.reinitialize(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty, matrix.alpha, matrix.shadow, matrix.compositeOperation, matrix.visible);
};
/**
* Appends the specified visual properties to the current matrix.
* @method appendProperties
* @param {Number} alpha desired alpha value
* @param {Shadow} shadow desired shadow value
* @param {String} compositeOperation desired composite operation value
* @param {Boolean} visible desired visible value
* @return {Matrix2D} This matrix. Useful for chaining method calls.
*/
p.appendProperties = function(alpha, shadow, compositeOperation, visible) {
this.alpha *= alpha;
this.shadow = shadow || this.shadow;
this.compositeOperation = compositeOperation || this.compositeOperation;
this.visible = this.visible && visible;
return this;
};
/**
* Prepends the specified visual properties to the current matrix.
* @method prependProperties
* @param {Number} alpha desired alpha value
* @param {Shadow} shadow desired shadow value
* @param {String} compositeOperation desired composite operation value
* @param {Boolean} visible desired visible value
* @return {Matrix2D} This matrix. Useful for chaining method calls.
*/
p.prependProperties = function(alpha, shadow, compositeOperation, visible) {
this.alpha *= alpha;
this.shadow = this.shadow || shadow;
this.compositeOperation = this.compositeOperation || compositeOperation;
this.visible = this.visible && visible;
return this;
};
/**
* Returns a clone of the Matrix2D instance.
* @method clone
* @return {Matrix2D} a clone of the Matrix2D instance.
**/
p.clone = function() {
return (new Matrix2D()).copy(this);
};
/**
* Returns a string representation of this object.
* @method toString
* @return {String} a string representation of the instance.
**/
p.toString = function() {
return "[Matrix2D (a="+this.a+" b="+this.b+" c="+this.c+" d="+this.d+" tx="+this.tx+" ty="+this.ty+")]";
};
// this has to be populated after the class is defined:
Matrix2D.identity = new Matrix2D();
createjs.Matrix2D = Matrix2D;
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment