/* * 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"; // constructor: /** * Represents an affine transformation matrix, and provides tools for constructing and concatenating matrices. * * This matrix can be visualized as: * * [ a c tx * b d ty * 0 0 1 ] * * Note the locations of b and c. * * @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 **/ function Matrix2D(a, b, c, d, tx, ty) { this.setValues(a,b,c,d,tx,ty); // public properties: // assigned in the setValues method. /** * Position (0, 0) in a 3x3 affine transformation matrix. * @property a * @type Number **/ /** * Position (0, 1) in a 3x3 affine transformation matrix. * @property b * @type Number **/ /** * Position (1, 0) in a 3x3 affine transformation matrix. * @property c * @type Number **/ /** * Position (1, 1) in a 3x3 affine transformation matrix. * @property d * @type Number **/ /** * Position (2, 0) in a 3x3 affine transformation matrix. * @property tx * @type Number **/ /** * Position (2, 1) in a 3x3 affine transformation matrix. * @property ty * @type Number **/ } var p = Matrix2D.prototype; // constants: /** * 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; // 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. // public methods: /** * Sets the specified values on this instance. * @method setValues * @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.setValues = function(a, b, c, d, tx, ty) { // don't forget to update docs in the constructor if these change: 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; return this; }; /** * Appends the specified matrix properties to this matrix. All parameters are required. * This is the equivalent of multiplying `(this matrix) * (specified matrix)`. * @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; if (a != 1 || b != 0 || c != 0 || d != 1) { this.a = a1*a+c1*b; this.b = b1*a+d1*b; this.c = a1*c+c1*d; this.d = b1*c+d1*d; } this.tx = a1*tx+c1*ty+this.tx; this.ty = b1*tx+d1*ty+this.ty; return this; }; /** * Prepends the specified matrix properties to this matrix. * This is the equivalent of multiplying `(specified matrix) * (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 a1 = this.a; var c1 = this.c; var tx1 = this.tx; this.a = a*a1+c*this.b; this.b = b*a1+d*this.b; this.c = a*c1+c*this.d; this.d = b*c1+d*this.d; this.tx = a*tx1+c*this.ty+tx; this.ty = b*tx1+d*this.ty+ty; return this; }; /** * Appends the specified matrix to this matrix. * This is the equivalent of multiplying `(this matrix) * (specified matrix)`. * @method appendMatrix * @param {Matrix2D} matrix * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ p.appendMatrix = function(matrix) { return this.append(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty); }; /** * Prepends the specified matrix to this matrix. * This is the equivalent of multiplying `(specified matrix) * (this matrix)`. * For example, you could calculate the combined transformation for a child object using: * * var o = myDisplayObject; * var mtx = o.getMatrix(); * while (o = o.parent) { * // prepend each parent's transformation in turn: * o.prependMatrix(o.getMatrix()); * } * @method prependMatrix * @param {Matrix2D} matrix * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ p.prependMatrix = function(matrix) { return this.prepend(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty); }; /** * Generates matrix properties from the specified display object transform properties, and appends them to this matrix. * For example, you can use this to generate a matrix representing the transformations of a display object: * * var mtx = new createjs.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 operation? 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) { // append the registration offset: this.tx -= regX*this.a+regY*this.c; this.ty -= regX*this.b+regY*this.d; } return this; }; /** * Generates matrix properties from the specified display object transform properties, and prepends them to this matrix. * For example, you could calculate the combined transformation for a child object using: * * var o = myDisplayObject; * var mtx = new createjs.Matrix2D(); * do { * // prepend each parent's transformation in turn: * mtx.prependTransform(o.x, o.y, o.scaleX, o.scaleY, o.rotation, o.skewX, o.skewY, o.regX, o.regY); * } while (o = o.parent); * * Note that the above example would not account for {{#crossLink "DisplayObject/transformMatrix:property"}}{{/crossLink}} * values. See {{#crossLink "Matrix2D/prependMatrix"}}{{/crossLink}} for an example that does. * @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) { // prepend 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; }; /** * Applies a clockwise rotation transformation to the matrix. * @method rotate * @param {Number} angle The angle to rotate by, in degrees. To use a value in radians, multiply it by `180/Math.PI`. * @return {Matrix2D} This matrix. Useful for chaining method calls. **/ p.rotate = function(angle) { angle = angle*Matrix2D.DEG_TO_RAD; var cos = Math.cos(angle); var sin = Math.sin(angle); var a1 = this.a; var b1 = this.b; this.a = a1*cos+this.c*sin; this.b = b1*cos+this.d*sin; this.c = -a1*sin+this.c*cos; this.d = -b1*sin+this.d*cos; return this; }; /** * Applies a skew transformation to the matrix. * @method skew * @param {Number} skewX The amount to skew horizontally in degrees. To use a value in radians, multiply it by `180/Math.PI`. * @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. E.G. a value of 2 will double the size in the X direction, and 0.5 will halve it. * @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.b *= x; this.c *= y; this.d *= 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 += this.a*x + this.c*y; this.ty += this.b*x + this.d*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.a = this.d = 1; this.b = this.c = this.tx = this.ty = 0; 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; }; /** * Returns true if this matrix is equal to the specified matrix (all property values are equal). * @method equals * @param {Matrix2D} matrix The matrix to compare. * @return {Boolean} **/ p.equals = function(matrix) { return this.tx === matrix.tx && this.ty === matrix.ty && this.a === matrix.a && this.b === matrix.b && this.c === matrix.c && this.d === matrix.d; }; /** * 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 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 {Object} The target, or a new generic object with the transform properties applied. */ 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); var delta = Math.abs(1-skewX/skewY); if (delta < 0.00001) { // effectively identical, can use rotation: 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; }; /** * 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.setValues(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty); }; /** * Returns a clone of the Matrix2D instance. * @method clone * @return {Matrix2D} a clone of the Matrix2D instance. **/ p.clone = function() { return new Matrix2D(this.a, this.b, this.c, this.d, this.tx, this.ty); }; /** * 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; }());