import {Vector2} from "./Vector2.js"; /** * 2D 3x2 transformation matrix, used to represent linear geometric transformations over objects. * * The values of the matrix are stored as numeric array. The matrix can be applied to the canvas or DOM elements using CSS transforms. * * @class * @param {number[]} values Array of matrix values by row, needs to have exactly 6 values. Default is the identity matrix. */ function Matrix(values) { if(values !== undefined) { /** * Array that contains the matrix data by row. This matrix should have 6 values. * * Matrix can be directly edited by accessing this attribute. * * @type {number[]} */ this.m = values; } else { this.identity(); } } /** * Copy the content of another matrix and store in this one. * * @param {Matrix} mat Matrix to copy values from. */ Matrix.prototype.copy = function(mat) { this.m = mat.m.slice(0); }; /** * Create a new matrix object with a copy of the content of this one. * * @return {Matrix} Copy of this matrix. */ Matrix.prototype.clone = function() { return new Matrix(this.m.slice(0)) }; /** * Reset this matrix to identity. */ Matrix.prototype.identity = function() { this.m = [1, 0, 0, 1, 0, 0]; }; /** * Multiply another matrix by this one and store the result. * * @param {Matrix} mat Matrix to multiply by. */ Matrix.prototype.multiply = function(mat) { var m0 = this.m[0] * mat.m[0] + this.m[2] * mat.m[1]; var m1 = this.m[1] * mat.m[0] + this.m[3] * mat.m[1]; var m2 = this.m[0] * mat.m[2] + this.m[2] * mat.m[3]; var m3 = this.m[1] * mat.m[2] + this.m[3] * mat.m[3]; var m4 = this.m[0] * mat.m[4] + this.m[2] * mat.m[5] + this.m[4]; var m5 = this.m[1] * mat.m[4] + this.m[3] * mat.m[5] + this.m[5]; this.m = [m0, m1, m2, m3, m4, m5]; }; /** * Premultiply another matrix by this one and store the result. * * @param {Matrix} mat Matrix to premultiply by. */ Matrix.prototype.premultiply = function(mat) { var m0 = mat.m[0] * this.m[0] + mat.m[2] * this.m[1]; var m1 = mat.m[1] * this.m[0] + mat.m[3] * this.m[1]; var m2 = mat.m[0] * this.m[2] + mat.m[2] * this.m[3]; var m3 = mat.m[1] * this.m[2] + mat.m[3] * this.m[3]; var m4 = mat.m[0] * this.m[4] + mat.m[2] * this.m[5] + mat.m[4]; var m5 = mat.m[1] * this.m[4] + mat.m[3] * this.m[5] + mat.m[5]; this.m = [m0, m1, m2, m3, m4, m5]; }; /** * Compose this transformation matrix with position scale and rotation and origin point. * * @param {number} px Position X * @param {number} py Position Y * @param {number} sx Scale X * @param {number} sy Scale Y * @param {number} ox Origin X (applied before scale and rotation) * @param {number} oy Origin Y (applied before scale and rotation) * @param {number} rot Rotation angle (radians). */ Matrix.prototype.compose = function(px, py, sx, sy, ox, oy, rot) { // Position this.m = [1, 0, 0, 1, px, py]; // Rotation if(rot !== 0) { var c = Math.cos(rot); var s = Math.sin(rot); this.multiply(new Matrix([c, s, -s, c, 0, 0])); } // Scale if(sx !== 1 || sy !== 1) { this.scale(sx, sy); } // Origin if(ox !== 0 || oy !== 0) { this.multiply(new Matrix([1, 0, 0, 1, -ox, -oy])); } }; /** * Apply translation to this matrix. * * Adds position over the transformation already stored in the matrix. * * @param {number} x * @param {number} y */ Matrix.prototype.translate = function(x, y) { this.m[4] += this.m[0] * x + this.m[2] * y; this.m[5] += this.m[1] * x + this.m[3] * y; }; /** * Apply rotation to this matrix. * * @param {number} rad Angle to rotate the matrix in radians. */ Matrix.prototype.rotate = function(rad) { var c = Math.cos(rad); var s = Math.sin(rad); var m11 = this.m[0] * c + this.m[2] * s; var m12 = this.m[1] * c + this.m[3] * s; var m21 = this.m[0] * -s + this.m[2] * c; var m22 = this.m[1] * -s + this.m[3] * c; this.m[0] = m11; this.m[1] = m12; this.m[2] = m21; this.m[3] = m22; }; /** * Apply scale to this matrix. * * @param {number} sx * @param {number} sy */ Matrix.prototype.scale = function(sx, sy) { this.m[0] *= sx; this.m[1] *= sx; this.m[2] *= sy; this.m[3] *= sy; }; /** * Set the position of the transformation matrix. * * @param {number} x * @param {number} y */ Matrix.prototype.setPosition = function(x, y) { this.m[4] = x; this.m[5] = y; }; /** * Extract the scale from the transformation matrix. * * @return {Vector2} Scale of the matrix transformation. */ Matrix.prototype.getScale = function() { return new Vector2(this.m[0], this.m[3]); }; /** * Extract the position from the transformation matrix. * * @return {Vector2} Position of the matrix transformation. */ Matrix.prototype.getPosition = function() { return new Vector2(this.m[4], this.m[5]); }; /** * Apply skew to this matrix. * * @param {number} radianX * @param {number} radianY */ Matrix.prototype.skew = function(radianX, radianY) { this.multiply(new Matrix([1, Math.tan(radianY), Math.tan(radianX), 1, 0, 0])); }; /** * Get the matrix determinant. * * @return {number} Determinant of this matrix. */ Matrix.prototype.determinant = function() { return 1 / (this.m[0] * this.m[3] - this.m[1] * this.m[2]); }; /** * Get the inverse matrix. * * @return {Matrix} New matrix instance containing the inverse matrix. */ Matrix.prototype.getInverse = function() { var d = this.determinant(); return new Matrix([this.m[3] * d, -this.m[1] * d, -this.m[2] * d, this.m[0] * d, d * (this.m[2] * this.m[5] - this.m[3] * this.m[4]), d * (this.m[1] * this.m[4] - this.m[0] * this.m[5])]); }; /** * Transform a point using this matrix. * * @param {Vector2} p Point to be transformed. * @return {Vector2} Transformed point. */ Matrix.prototype.transformPoint = function(p) { var px = p.x * this.m[0] + p.y * this.m[2] + this.m[4]; var py = p.x * this.m[1] + p.y * this.m[3] + this.m[5]; return new Vector2(px, py); }; /** * Set a canvas context to use this transformation. * * @param {CanvasRenderingContext2D} context Canvas context to apply this matrix transform. */ Matrix.prototype.setContextTransform = function(context) { context.setTransform(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5]); }; /** * Transform on top of the current context transformation. * * @param {CanvasRenderingContext2D} context Canvas context to apply this matrix transform. */ Matrix.prototype.tranformContext = function(context) { context.transform(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5]); }; /** * Generate a CSS transform string that can be applied to the transform style of any DOM element. * * @returns {string} CSS transform matrix. */ Matrix.prototype.cssTransform = function() { return "matrix(" + this.m[0] + "," + this.m[1] + "," + this.m[2] + "," + this.m[3] + "," + this.m[4] + "," + this.m[5] + ")"; }; export {Matrix};