UNPKG

@omjs/matrix2d

Version:

CSS tranform-2D matrix

401 lines (360 loc) 10.5 kB
/** * matrix2d 工具包,包含一个Matrix2D类及若干运算方法 * @author oyzhen * @link https://git.coding.net/oyzhen/matrix2d.git */ import { map, Iterable } from '@omjs/iterable'; import Angle from '@omjs/angle'; var version = "1.0.1"; /** * 根据指定的小数位四舍五入 * @param {number} digit 小数位数 */ const round = digit => (num, p) => (p = 10 ** digit, Math.round(num * p) / p); /** * Matrix2D矩阵操作函数 */ /** * 判断是否是类Matrix2D数据结构 * @param {*} mat */ function isMatrix2DLike(mat) { return Matrix2D.isMatrix2D(mat) || Object.keys(mat || {}).join('') === '012345'; } /** * 简化数值,根据指定的小数位数四舍五入 * @param {number} digit 小数位数 */ function simplify(digit = 3) { let m = isMatrix2DLike(this) ? this : new Matrix2D(this); return new Matrix2D(map.call(m, v => round(digit)(v))); } /** * 左乘 * @param {Matrix2D} mat1 被乘矩阵 * @param {Matrix2D} mat2 乘矩阵 * @returns {Matrix2D} */ function lmul(mat) { let m1 = isMatrix2DLike(this) ? this : new Matrix2D(this); let m2 = isMatrix2DLike(mat) ? mat : new Matrix2D(mat); return new Matrix2D(m1[0] * m2[0] + m1[1] * m2[2], m1[0] * m2[1] + m1[1] * m2[3], m1[2] * m2[0] + m1[3] * m2[2], m1[2] * m2[1] + m1[3] * m2[3], m1[4] * m2[0] + m1[5] * m2[2] + m2[4], m1[4] * m2[1] + m1[5] * m2[3] + m2[5]); } /** * 右乘 * @param {Matrix2D} mat 被乘矩阵 * @returns {Matrix2D} */ function rmul(mat) { return lmul.call(mat, this); } /** * 乘 * @param {Matrix2D} mat 被乘矩阵 * @returns {Matrix2D} */ function mul(mat, dir = Matrix2D.MUL_DIR.L) { if (dir === Matrix2D.MUL_DIR.L) { return lmul.call(this, mat); } return rmul.call(this, mat); } /** * 旋转变换 * @public * @param {Angle | number | string} angle 旋转角,默认 0rad,当传入为数值时单位为deg,当传入为字符串时 * @param {Symbol} dir 旋转方向,默认 顺时针方向 * @returns {Matrix2D} */ function rotate(angle = new Angle(), dir = Matrix2D.ROTATE_DIR.CW) { let rad = Angle.from(angle).rad; let cos = Math.cos(rad); let sin = Math.sin(rad); let mat = [cos, sin, -sin, cos, 0, 0]; if (dir === Matrix2D.ROTATE_DIR.ACW) { mat = [cos, -sin, sin, cos, 0, 0]; } return lmul.call(this, mat); } /** * 缩放变换 * @public * @param {number} x x方向缩放量 * @param {number} y y方向缩放量 * @returns {Matrix2D} */ function scale(x = 1, y = 1) { return lmul.call(this, [x, 0, 0, y, 0, 0]); } /** * 倾斜变换 * @public * @param {Angle | string | number} angX x轴倾斜角 * @param {Angle | string | number} angY y轴倾斜角 * @returns {Matrix2D} */ function skew(angX = new Angle(), angY = new Angle()) { let radX = Angle.from(angX).rad; let radY = Angle.from(angY).rad; return lmul.call(this, [1, Math.tan(radY), Math.tan(radX), 1, 0, 0]); } /** * 平移 * @param {number} x 水平方向平移量 * @param {number} y 垂直方向平移量 * @returns {Matrix2D} */ function translate(x = 0, y = 0) { return lmul.call(this, [1, 0, 0, 1, x, y]); } /** * 平移,同translate * @param {number} x 水平方向平移量 * @param {number} y 垂直方向平移量 * @returns {Matrix2D} */ function move(x = 0, y = 0) { return translate.call(this, x, y); } /** * 对点应用矩阵变换 * @param {number[]} pt [x, y] 变换点坐标 * @returns {number[]} [x, y] 变换后点坐标 */ function exec([x = 0, y = 0] = []) { let m = isMatrix2DLike(this) ? this : new Matrix2D(this); return [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]]; } /** * 对矩形应用矩阵变换 * @param {Object} rect {x, y, w, h} 矩形描述对象 * @param {number[]} center [cx, cy] 变换中心点 * @returns {Object} {x, y, w, h} 变换后的矩形描述对象 */ function execRect({ x = 0, y = 0, w = 0, h = 0 } = {}, [cx = 0, cy = 0] = []) { let bfr = [[x - cx, y - cy], [x + w - cx, y - cy], [x + w - cx, y + h - cy], [x - cx, y + h - cy]]; // 变换前矩形顶点坐标(左上、右上、右下、左下) let aft = bfr.map(v => exec.call(this, v)); let xArr = aft.map(v => v[0]); let yArr = aft.map(v => v[1]); let xMin = Math.min(...xArr); let xMax = Math.max(...xArr); let yMin = Math.min(...yArr); let yMax = Math.max(...yArr); return { x: xMin + cx, y: yMin + cy, w: xMax - xMin, h: yMax - yMin }; } /** * 由Matrix2D推导rotate、scale、skew、translate等值 * 先rotate,再scale,再skew,再translate * @todo 已知bug: 当rotate不是90的倍数时,计算值不准确 * @see http://www.wolframalpha.com/input/?i=%7B%7B1,0%7D,%7Bt1,1%7D%7D*%7B%7Bs1,0%7D,%7B0,s2%7D%7D*%7B%7Bcos%5Br%5D,+-sin%5Br%5D%7D,%7Bsin%5Br%5D,+cos%5Br%5D%7D%7D+%3D+%7B%7Bm0,m2%7D,%7Bm1,m3%7D%7D+and+m0*m3!%3Dm1*m2 * @returns {object} {rotate, scale, scaleX, scaleY, skew, skewX, skewY, translate, translateX, translateY} */ function decompose() { let m = isMatrix2DLike(this) ? this : new Matrix2D(this); let dm = m[0] * m[3] - m[1] * m[2]; if (dm === 0) { throw new Error('Only an invertible matrix can be decomposed.'); } let sm = m[0] ** 2 + m[2] ** 2; let rotate = Angle.fromRad(2 * Math.atan2(m[0] - Math.sqrt(sm), m[2])); let scaleX = Math.sqrt(sm); let scaleY = dm / scaleX; let skewX = Angle.fromRad(Math.atan2(m[0] * m[1] + m[2] * m[3], sm)); let skewY = Angle.fromRad(0); return { rotate, scaleX, scaleY, scale: [scaleX, scaleY], skewX, skewY, skew: [skewX, skewY], translateX: m[4], translateY: m[5], translate: [m[4], m[5]] }; } /** * CSS/Affine Transform-2D Matrix * matrix(a, b, c, d, m, n)<br /> * ┌ a c m ┐<br /> * | b d n |<br /> * └ 0 0 1 ┘<br /> */ const Matrix2D = (() => { // class definition class Matrix2D extends Iterable { /** * 构造函数 * @param {*} mat 矩阵初始化量,可为css transform-2d matrix、数组 * @todo 优化运算 */ constructor(...mat) { if (Matrix2D.isMatrix2D(mat[0])) { super(...mat[0]); } else { let [a = 1, b = 0, c = 0, d = 1, tx = 0, ty = 0] = mat.toString().split(/[^\d.\-+e]/).map(v => parseFloat(v)).filter(x => !isNaN(x)); super(a, b, c, d, tx, ty); } let dcache; Object.defineProperty(this, 'decompose', { /** * 矩阵分解,得到rotate/skew/translate/scale等值 * @returns {object} */ get: () => { if (!dcache) { dcache = decompose.call(this); } return dcache; } }); } /** * 判断是否是Matrix2D对象 * @param {*} mat 待判定对象 */ static isMatrix2D(mat) { return mat != null && mat instanceof Matrix2D; } /** * 转换成字符串 * @override * @returns {string} */ toString() { return `matrix(${Array.prototype.join.call(this, ',')})`; } /** * JSON.stringify预处理 * @override * @returns {string} */ toJSON() { return this.toString(); } /** * 简化数值,根据指定的小数位数四舍五入 * @param {number} digit * @returns {Matrix2D} */ toBrief(digit) { return simplify.call(this, digit); } /** * @override * @returns {number[]} */ valueOf() { return [...this]; } /** * deep clone a copy * @returns {Matrix2D} */ clone() { return new Matrix2D(this); } /** * 获得旋转量 * @returns {Angle} */ get rotate() { return this.decompose.rotate; } /** * 获得缩放量 * @returns {number[]} */ get scale() { return this.decompose.scale; } /** * 获得x方向缩放量 * @returns {number} */ get scaleX() { return this.decompose.scaleX; } /** * 获得y方向缩放量 * @returns {number} */ get scaleY() { return this.decompose.scaleY; } /** * 获得倾斜量 * @returns {Angle[]} */ get skew() { return this.decompose.skew; } /** * 获得x方向倾斜量 * @returns {Angle} */ get skewX() { return this.decompose.skewX; } /** * 获得y方向倾斜量 * @returns {Angle} */ get skewY() { return this.decompose.skewY; } /** * 获得平移量 * @returns {number[]} */ get translate() { return this.decompose.translate; } /** * 获得x方向平移量 * @returns {number} */ get translateX() { return this.decompose.translateX; } /** * 获得y方向平移量 * @returns {number} */ get translateY() { return this.decompose.translateY; } } /** * @prop {string} version 版本号 */ Matrix2D.version = version; // Object.defineProperty(Matrix2D, 'version', {value: version}); /** * 矩阵乘法方向 * @readOnly * @static * @property {Symbol} L 左乘 * @property {Symbol} R 右乘 */ Matrix2D.MUL_DIR = Object.freeze({ L: Symbol('left'), R: Symbol('right') }); /** * 旋转方向 * @readOnly * @static * @property {Symbol} CW 顺时针方向 * @property {Symbol} ACW 逆时针方向 */ Matrix2D.ROTATE_DIR = Object.freeze({ CW: Symbol('cw'), ACW: Symbol('acw') }); // CW - 顺时针,ACW - 逆时针 return Matrix2D; })(); export { Matrix2D, isMatrix2DLike, simplify, lmul, rmul, mul, rotate, scale, skew, translate, move, exec, execRect, decompose };