import {Point} from "../geom/Point"; import {Vector3D} from "../geom/Vector3D"; import {ArgumentError} from "../errors/ArgumentError"; /** * The Matrix export class represents a transformation matrix that determines how to * map points from one coordinate space to another. You can perform various * graphical transformations on a display object by setting the properties of * a Matrix object, applying that Matrix object to the matrix * property of a Transform object, and then applying that Transform object as * the transform property of the display object. These * transformation functions include translation(x and y * repositioning), rotation, scaling, and skewing. * *

Together these types of transformations are known as affine * transformations. Affine transformations preserve the straightness of * lines while transforming, so that parallel lines stay parallel.

* *

To apply a transformation matrix to a display object, you create a * Transform object, set its matrix property to the * transformation matrix, and then set the transform property of * the display object to the Transform object. Matrix objects are also used as * parameters of some methods, such as the following:

* * * *

A transformation matrix object is a 3 x 3 matrix with the following * contents:

* *

In traditional transformation matrixes, the u, * v, and w properties provide extra capabilities. * The Matrix export class can only operate in two-dimensional space, so it always * assumes that the property values u and v are 0.0, * and that the property value w is 1.0. The effective values of * the matrix are as follows:

* *

You can get and set the values of all six of the other properties in a * Matrix object: a, b, c, * d, tx, and ty.

* *

The Matrix export class supports the four major types of transformations: * translation, scaling, rotation, and skewing. You can set three of these * transformations by using specialized methods, as described in the following * table:

* *

Each transformation function alters the current matrix properties so * that you can effectively combine multiple transformations. To do this, you * call more than one transformation function before applying the matrix to * its display object target(by using the transform property of * that display object).

* *

Use the new Matrix() constructor to create a Matrix object * before you can call the methods of the Matrix object.

*/ export class Matrix { public rawData:Float32Array = new Float32Array(6); /** * The value that affects the positioning of pixels along the x axis * when scaling or rotating an image. */ public get a():number { return this.rawData[0]; } public set a(value:number) { this.rawData[0] = value; } /** * The value that affects the positioning of pixels along the y axis * when rotating or skewing an image. */ public get b():number { return this.rawData[2]; } public set b(value:number) { this.rawData[2] = value; } /** * The value that affects the positioning of pixels along the x axis * when rotating or skewing an image. */ public get c():number { return this.rawData[1]; } public set c(value:number) { this.rawData[1] = value; } /** * The value that affects the positioning of pixels along the y axis * when scaling or rotating an image. */ public get d():number { return this.rawData[3]; } public set d(value:number) { this.rawData[3] = value; } /** * The distance by which to translate each point along the x axis. */ public get tx():number { return this.rawData[4]; } public set tx(value:number) { this.rawData[4] = value; } /** * The distance by which to translate each point along the y axis. */ public get ty():number { return this.rawData[5]; } public set ty(value:number) { this.rawData[5] = value; } /** * Creates a new Matrix object with the specified parameters. In matrix * notation, the properties are organized like this: * *

If you do not provide any parameters to the new Matrix() * constructor, it creates an identity matrix with the following * values:

* *

In matrix notation, the identity matrix looks like this:

* * @param a The value that affects the positioning of pixels along the * x axis when scaling or rotating an image. * @param b The value that affects the positioning of pixels along the * y axis when rotating or skewing an image. * @param c The value that affects the positioning of pixels along the * x axis when rotating or skewing an image. * @param d The value that affects the positioning of pixels along the * y axis when scaling or rotating an image.. * @param tx The distance by which to translate each point along the x * axis. * @param ty The distance by which to translate each point along the y * axis. */ constructor(rawData?:Float32Array); constructor(a?:number, b?:number, c?:number, d?:number, tx?:number, ty?:number); constructor(a:number | Float32Array = 1, b:number = 0, c:number = 0, d:number = 1, tx:number = 0, ty:number = 0) { if (a instanceof Float32Array) { this.copyRawDataFrom(a); } else { this.a = Number(a); this.b = b; this.c = c; this.d = d; this.tx = tx; this.ty = ty; } } public copyRawDataFrom(vector:Float32Array, index:number = 0):void { for (var c:number = 0; c < 6; c++) this.rawData[c] = vector[c + index]; } /** * Returns a new Matrix object that is a clone of this matrix, with an exact * copy of the contained object. * * @return A Matrix object. */ public clone():Matrix { return new Matrix(this.a, this.b, this.c, this.d, this.tx, this.ty); } /** * Concatenates a matrix with the current matrix, effectively combining the * geometric effects of the two. In mathematical terms, concatenating two * matrixes is the same as combining them using matrix multiplication. * *

For example, if matrix m1 scales an object by a factor of * four, and matrix m2 rotates an object by 1.5707963267949 * radians(Math.PI/2), then m1.concat(m2) * transforms m1 into a matrix that scales an object by a factor * of four and rotates the object by Math.PI/2 radians.

* *

This method replaces the source matrix with the concatenated matrix. If * you want to concatenate two matrixes without altering either of the two * source matrixes, first copy the source matrix by using the * clone() method, as shown in the Class Examples section.

* * @param matrix The matrix to be concatenated to the source matrix. */ public concat(matrix:Matrix):void { var a1 = this.a*matrix.a + this.b*matrix.c; this.b = this.a*matrix.b + this.b*matrix.d; this.a = a1; var c1 = this.c*matrix.a + this.d*matrix.c; this.d = this.c*matrix.b + this.d*matrix.d; this.c = c1; var tx1 = this.tx*matrix.a + this.ty*matrix.c + matrix.tx; this.ty = this.tx*matrix.b + this.ty*matrix.d + matrix.ty; this.tx = tx1; } /** * Copies a Vector3D object into specific column of the calling Matrix3D * object. * * @param column The column from which to copy the data from. * @param vector3D The Vector3D object from which to copy the data. */ public copyColumnFrom(column:number, vector3D:Vector3D):void { if (column > 2) { throw "Column " + column + " out of bounds (2)"; } else if (column == 0) { this.a = vector3D.x; this.c = vector3D.y; } else if (column == 1) { this.b = vector3D.x; this.d = vector3D.y; } else { this.tx = vector3D.x; this.ty = vector3D.y; } } /** * Copies specific column of the calling Matrix object into the Vector3D * object. The w element of the Vector3D object will not be changed. * * @param column The column from which to copy the data from. * @param vector3D The Vector3D object from which to copy the data. */ public copyColumnTo(column:number, vector3D:Vector3D):void { if (column > 2) { throw new ArgumentError("ArgumentError, Column " + column + " out of bounds [0, ..., 2]"); } else if (column == 0) { vector3D.x = this.a; vector3D.y = this.c; vector3D.z = 0; } else if (column == 1) { vector3D.x = this.b; vector3D.y = this.d; vector3D.z = 0; } else { vector3D.x = this.tx; vector3D.y = this.ty; vector3D.z = 1; } } /** * Copies all of the matrix data from the source Point object into the * calling Matrix object. * * @param sourceMatrix The Matrix object from which to copy the data. */ public copyFrom(sourceMatrix:Matrix):void { this.a = sourceMatrix.a; this.b = sourceMatrix.b; this.c = sourceMatrix.c; this.d = sourceMatrix.d; this.tx = sourceMatrix.tx; this.ty = sourceMatrix.ty; } /** * Copies a Vector3D object into specific row of the calling Matrix object. * * @param row The row from which to copy the data from. * @param vector3D The Vector3D object from which to copy the data. */ public copyRowFrom(row:number, vector3D:Vector3D):void { if (row > 2) { throw new ArgumentError("ArgumentError, Row " + row + " out of bounds [0, ..., 2]"); } else if (row == 0) { this.a = vector3D.x; this.c = vector3D.y; } else if (row == 1) { this.b = vector3D.x; this.d = vector3D.y; } else { this.tx = vector3D.x; this.ty = vector3D.y; } } /** * Copies specific row of the calling Matrix object into the Vector3D object. * The w element of the Vector3D object will not be changed. * * @param row The row from which to copy the data from. * @param vector3D The Vector3D object from which to copy the data. */ public copyRowTo(row:number, vector3D:Vector3D):void { if (row > 2) { throw new ArgumentError("ArgumentError, Row " + row + " out of bounds [0, ..., 2]"); } else if (row == 0) { vector3D.x = this.a; vector3D.y = this.b; vector3D.z = this.tx; } else if (row == 1) { vector3D.x = this.c; vector3D.y = this.d; vector3D.z = this.ty; } else { vector3D.setTo(0, 0, 1); } } /** * Includes parameters for scaling, rotation, and translation. When applied * to a matrix it sets the matrix's values based on those parameters. * *

Using the createBox() method lets you obtain the same * matrix as you would if you applied the identity(), * rotate(), scale(), and translate() * methods in succession. For example, mat1.createBox(2,2,Math.PI/4, * 100, 100) has the same effect as the following:

* * @param scaleX The factor by which to scale horizontally. * @param scaleY The factor by which scale vertically. * @param rotation The amount to rotate, in radians. * @param tx The number of pixels to translate(move) to the right * along the x axis. * @param ty The number of pixels to translate(move) down along the * y axis. */ public createBox(scaleX:number, scaleY:number, rotation:number = 0, tx:number = 0, ty:number = 0):void { this.a = scaleX; this.d = scaleY; this.b = rotation; this.tx = tx; this.ty = ty; } /** * Creates the specific style of matrix expected by the * beginGradientFill() and lineGradientStyle() * methods of the Graphics class. Width and height are scaled to a * scaleX/scaleY pair and the * tx/ty values are offset by half the width and * height. * *

For example, consider a gradient with the following * characteristics:

* * * *

The following illustrations show gradients in which the matrix was * defined using the createGradientBox() method with different * parameter settings:

* * @param width The width of the gradient box. * @param height The height of the gradient box. * @param rotation The amount to rotate, in radians. * @param tx The distance, in pixels, to translate to the right along * the x axis. This value is offset by half of the * width parameter. * @param ty The distance, in pixels, to translate down along the * y axis. This value is offset by half of the * height parameter. */ public createGradientBox(width:number, height:number, rotation:number = 0, tx:number = 0, ty:number = 0):void { this.a = width/1638.4; this.d = height/1638.4; if (rotation != 0.0) { var cos = Math.cos(rotation); var sin = Math.sin(rotation); this.b = sin*this.d; this.c = -sin*this.a; this.a *= cos; this.d *= cos; } else { this.b = this.c = 0; } this.tx = tx + width/2; this.ty = ty + height/2; } /** * Given a point in the pretransform coordinate space, returns the * coordinates of that point after the transformation occurs. Unlike the * standard transformation applied using the transformPoint() * method, the deltaTransformPoint() method's transformation * does not consider the translation parameters tx and * ty. * * @param point The point for which you want to get the result of the matrix * transformation. * @return The point resulting from applying the matrix transformation. */ public deltaTransformPoint(point:Point):Point { return new Point(point.x*this.a + point.y*this.c, point.x*this.b + point.y*this.d); } /** * Sets each matrix property to a value that causes a null transformation. An * object transformed by applying an identity matrix will be identical to the * original. * *

After calling the identity() method, the resulting matrix * has the following properties: a=1, b=0, * c=0, d=1, tx=0, * ty=0.

* *

In matrix notation, the identity matrix looks like this:

* */ public identity():void { this.a = 1; this.b = 0; this.c = 0; this.d = 1; this.tx = 0; this.ty = 0; } /** * Performs the opposite transformation of the original matrix. You can apply * an inverted matrix to an object to undo the transformation performed when * applying the original matrix. */ public invert():void { var norm = this.a*this.d - this.b*this.c; if (norm == 0) { this.a = this.b = this.c = this.d = 0; this.tx = -this.tx; this.ty = -this.ty; } else { norm = 1.0/norm; var a1 = this.d*norm; this.d = this.a*norm; this.a = a1; this.b *= -norm; this.c *= -norm; var tx1 = -this.a*this.tx - this.c*this.ty; this.ty = -this.b*this.tx - this.d*this.ty; this.tx = tx1; } } /** * Returns a new Matrix object that is a clone of this matrix, with an exact * copy of the contained object. * * @param matrix The matrix for which you want to get the result of the matrix * transformation. * @return A Matrix object. */ public multiply(matrix:Matrix):Matrix { var result = new Matrix(); result.a = this.a*matrix.a + this.b*matrix.c; result.b = this.a*matrix.b + this.b*matrix.d; result.c = this.c*matrix.a + this.d*matrix.c; result.d = this.c*matrix.b + this.d*matrix.d; result.tx = this.tx*matrix.a + this.ty*matrix.c + matrix.tx; result.ty = this.tx*matrix.b + this.ty*matrix.d + matrix.ty; return result; } /** * Applies a rotation transformation to the Matrix object. * *

The rotate() method alters the a, * b, c, and d properties of the * Matrix object. In matrix notation, this is the same as concatenating the * current matrix with the following:

* * @param angle The rotation angle in radians. */ public rotate(angle:number):void { var cos = Math.cos(angle); var sin = Math.sin(angle); var a1 = this.a*cos - this.b*sin; this.b = this.a*sin + this.b*cos; this.a = a1; var c1 = this.c*cos - this.d*sin; this.d = this.c*sin + this.d*cos; this.c = c1; var tx1 = this.tx*cos - this.ty*sin; this.ty = this.tx*sin + this.ty*cos; this.tx = tx1; } /** * Applies a scaling transformation to the matrix. The x axis is * multiplied by sx, and the y axis it is multiplied by * sy. * *

The scale() method alters the a and * d properties of the Matrix object. In matrix notation, this * is the same as concatenating the current matrix with the following * matrix:

* * @param sx A multiplier used to scale the object along the x axis. * @param sy A multiplier used to scale the object along the y axis. */ public scale(sx:number, sy:number):void { this.a *= sx; this.b *= sy; this.c *= sx; this.d *= sy; this.tx *= sx; this.ty *= sy; } /** * Sets the members of Matrix to the specified values. * * @param a The value that affects the positioning of pixels along the * x axis when scaling or rotating an image. * @param b The value that affects the positioning of pixels along the * y axis when rotating or skewing an image. * @param c The value that affects the positioning of pixels along the * x axis when rotating or skewing an image. * @param d The value that affects the positioning of pixels along the * y axis when scaling or rotating an image.. * @param tx The distance by which to translate each point along the x * axis. * @param ty The distance by which to translate each point along the y * axis. */ public setTo(a:number, b:number, c:number, d:number, tx:number, ty:number):void { this.a = a; this.b = b; this.c = c; this.d = d; this.tx = tx; this.ty = ty; } /** * Returns a text value listing the properties of the Matrix object. * * @return A string containing the values of the properties of the Matrix * object: a, b, c, * d, tx, and ty. */ public toString():string { return "[Matrix] (a=" + this.a + ", b=" + this.b + ", c=" + this.c + ", d=" + this.d + ", tx=" + this.tx + ", ty=" + this.ty + ")"; } /** * Returns the result of applying the geometric transformation represented by * the Matrix object to the specified point. * * @param point The point for which you want to get the result of the Matrix * transformation. * @return The point resulting from applying the Matrix transformation. */ public transformPoint(point:Point):Point { return new Point(point.x*this.a + point.y*this.c + this.tx, point.x*this.b + point.y*this.d + this.ty); } /** * Translates the matrix along the x and y axes, as specified * by the dx and dy parameters. * * @param dx The amount of movement along the x axis to the right, in * pixels. * @param dy The amount of movement down along the y axis, in pixels. */ public translate(dx:number, dy:number):void { this.tx += dx; this.ty += dy; } }