@omjs/matrix2d
Version:
CSS tranform-2D matrix
205 lines (190 loc) • 5.66 kB
JavaScript
/**
* Matrix2D矩阵操作函数
*/
import { map } from '@omjs/iterable';
import { Matrix2D } from './core';
import Angle from '@omjs/angle';
import { round } from './util';
/**
* 判断是否是类Matrix2D数据结构
* @param {*} mat
*/
export function isMatrix2DLike(mat) {
return Matrix2D.isMatrix2D(mat) || (Object.keys(mat || {}).join('') === '012345');
}
/**
* 简化数值,根据指定的小数位数四舍五入
* @param {number} digit 小数位数
*/
export function simplify(digit = 3) {
let m = isMatrix2DLike(this) ? this : new Matrix2D(this);
return new Matrix2D(m::map(v => round(digit)(v)));
}
/**
* 左乘
* @param {Matrix2D} mat1 被乘矩阵
* @param {Matrix2D} mat2 乘矩阵
* @returns {Matrix2D}
*/
export 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}
*/
export function rmul(mat) {
return mat::lmul(this);
}
/**
* 乘
* @param {Matrix2D} mat 被乘矩阵
* @returns {Matrix2D}
*/
export function mul(mat, dir = Matrix2D.MUL_DIR.L) {
if (dir === Matrix2D.MUL_DIR.L) {
return this::lmul(mat);
}
return this::rmul(mat);
}
/**
* 旋转变换
* @public
* @param {Angle | number | string} angle 旋转角,默认 0rad,当传入为数值时单位为deg,当传入为字符串时
* @param {Symbol} dir 旋转方向,默认 顺时针方向
* @returns {Matrix2D}
*/
export 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 this::lmul(mat);
}
/**
* 缩放变换
* @public
* @param {number} x x方向缩放量
* @param {number} y y方向缩放量
* @returns {Matrix2D}
*/
export function scale(x = 1, y = 1) {
return this::lmul([x, 0, 0, y, 0, 0]);
}
/**
* 倾斜变换
* @public
* @param {Angle | string | number} angX x轴倾斜角
* @param {Angle | string | number} angY y轴倾斜角
* @returns {Matrix2D}
*/
export function skew(angX = new Angle(), angY = new Angle()) {
let radX = Angle.from(angX).rad;
let radY = Angle.from(angY).rad;
return this::lmul([1, Math.tan(radY), Math.tan(radX), 1, 0, 0]);
}
/**
* 平移
* @param {number} x 水平方向平移量
* @param {number} y 垂直方向平移量
* @returns {Matrix2D}
*/
export function translate(x = 0, y = 0) {
return this::lmul([1, 0, 0, 1, x, y]);
}
/**
* 平移,同translate
* @param {number} x 水平方向平移量
* @param {number} y 垂直方向平移量
* @returns {Matrix2D}
*/
export function move(x = 0, y = 0) {
return this::translate(x, y);
}
/**
* 对点应用矩阵变换
* @param {number[]} pt [x, y] 变换点坐标
* @returns {number[]} [x, y] 变换后点坐标
*/
export 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} 变换后的矩形描述对象
*/
export 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 => this::exec(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}
*/
export 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]]
};
}