矩阵分解:pixijs 中的 Matrix 和 Transform

矩阵分解:pixijs 中的 Matrix 和 Transform大家好 我是前端西瓜哥 在二维中 对于图形 模型 它会有一个模型矩阵 matrix 来表达图形的形变 比如图形先做了缩放 然后再位移 则模型矩阵为缩放矩阵左乘位移矩阵得到的复合矩阵

大家好,欢迎来到IT知识分享网。

大家好,我是前端西瓜哥。

在二维中,对于图形(模型),它会有一个模型矩阵 matrix 来表达图形的形变。

比如图形先做了缩放,然后再位移,则模型矩阵为缩放矩阵左乘位移矩阵得到的复合矩阵。

矩阵的优点是计算方便,比如父节点和子节点都有 matrix,那子节点最终在画布的 matrix 就是它们的矩阵直接相乘。

缺点也明显,就是它的值是几个多种矩阵变换得到的数字,语义糟糕,看不出图形做了什么形变。

这不利于我们对图形的表达。

那么,有没有办法对矩阵做分解,得到多个形变的表达呢?

我们不妨看看 pixijs 怎么做的。

pixijs 里面有两个类:Matrix 和 Transform。

Matrix

Matrix 是平面矩阵类,提供矩阵相关的各种方法

Matrix 使用 6 个数字表达,代表一个 3×3 的矩阵,用于平面矩阵变换。值有 abcdtxty

| a | c | tx| | b | d | ty| | 0 | 0 | 1 | 

支持缩放、旋转、位移、左乘、右乘、逆矩阵、计算点应用矩阵后的结果等方法。支持链式写法。

缩放、旋转、位移:

import { Matrix } from 'pixi.js'; const matrix = new Matrix(); // 返回一个默认额单位矩阵 // [pixi.js:Matrix a=1 b=0 c=0 d=1 tx=0 ty=0] // 1, 0, 0, // 0, 1, 0, // 0, 0, 1, matrix.scale(3, 3); // 放大为原来的 3 倍 // [pixi.js:Matrix a=3 b=0 c=0 d=3 tx=0 ty=0] // 3, 0, 0, // 0, 3, 0, // 0, 0, 1, // 支持链式写法(等价连续多个变换矩阵左乘) matrix.rotate(Math.PI / 2).translate(10, 10); // [pixi.js:Matrix a=1.10297e-16 b=3 c=-3 d=1.10297e-16 tx=10 ty=10] // 上面这个 a 应该为 0,但因为浮点数误差导致一个非常小的小数。 

左乘、右乘、逆矩阵:

const leftMatrix = new Matrix(); const rightMatrix = new Matrix(); // 右乘 const newMatrix = leftMatrix.append(rightMatrix); // 左乘 const newMatrix2 = rightMatrix.prepend(leftMatrix); // 逆矩阵 const inverseMatrix = leftMatrix.invert(); 

计算点应用矩阵后的结果、应用逆矩阵的结果:

const matrix = new Matrix(); // 点应用矩阵后的结果 const point = matrix.apply({ x: 100, y: 100 }); // 应用逆矩阵的结果 const inversePoint = matrix.applyInverse({ x: 100, y: 100 }); 

Transform

Transform 是 Matrix 的等价表达,但是对用户友好。

The Transform class facilitates the manipulation of a 2D transformation matrix through user-friendly properties: position, scale, rotation, skew, and pivot.

Transform 是一些属性的组合,可以表达一个图形的任意形变效果。

属性有:

  1. postion:位置,类型为 point { x: number, y: number }
  2. scale:缩放,类型为 point;
  3. pivot:基准位置,类型为 point。它作为旋转、缩放的中心点,默认为原点;
  4. skew:斜切,类型为 point。弧度值,表示基向量方向和另一方向形成的角;
  5. rotation:旋转角,弧度单位。

用 typescript 类型表达为:

interface TransformableObject { position: PointData; scale: PointData; pivot: PointData; skew: PointData; rotation: number; } interface PointData { x: number; y: number; } 

pixijs 的图形使用了 Transform 的这一套表达,让用户能够很简单直观地表达一些简单的形变。

Transform 下有一个 _matrix 属性,维护等价的 matrix 对象,当 transform 的属性更新时,matrix 会标记为 dirty,之后读取的时候会重新生成。

transform 这个名字其实有点迷惑,因为有时候我们也会把用在形变的矩阵 matrix,也叫做 transform。只是 pixijs 这里的命名比较特别,里面也有点乱。

下面看看 Matrix 和 Transform 之间的转换算法。

Transform 转 Matrix

pixijs 中 Transform 转 Matrix 的实现如下。

class Transform { / * This matrix is computed by combining this Transforms position, scale, rotation, skew, and pivot * properties into a single matrix. * @readonly */ get matrix(): Matrix { const lt = this._matrix; if (!this.dirty) return lt; lt.a = this._cx * this.scale.x; lt.b = this._sx * this.scale.x; lt.c = this._cy * this.scale.y; lt.d = this._sy * this.scale.y; lt.tx = this.position.x - ((this.pivot.x * lt.a) + (this.pivot.y * lt.c)); lt.ty = this.position.y - ((this.pivot.x * lt.b) + (this.pivot.y * lt.d)); this.dirty = false; return lt; } / Called when the skew or the rotation changes. */ protected updateSkew(): void { this._cx = Math.cos(this._rotation + this.skew.y); this._sx = Math.sin(this._rotation + this.skew.y); this._cy = -Math.sin(this._rotation - this.skew.x); // cos, added PI/2 this._sy = Math.cos(this._rotation - this.skew.x); // sin, added PI/2 this.dirty = true; } } 

_cx_sx_cysy 会在更新 skew 或 rotataion 时进行更新,是缓存数据。

我们抽出算法。

上面为了提高计算效率,没有用矩阵类的方法,这里给矩阵相乘表达。

import { Matrix } from 'pixi.js'; const transformToMatrix = (tf: TransformableObject) => { const cosX = Math.cos(tf.rotation + tf.skew.y); const sinX = Math.sin(tf.rotation + tf.skew.y); const cosY = -Math.sin(tf.rotation - tf.skew.x); const sinY = Math.cos(tf.rotation - tf.skew.x); const skewMatrix = new Matrix(cosX, sinX, cosY, sinY, 0, 0); return new Matrix() .translate(-tf.pivot.x, -tf.pivot.y) .prepend(skewMatrix) .scale(tf.scale.x, tf.scale.y) .translate(tf.position.x, tf.position.y); }; 

斜切和旋转二者需要合并为一个斜切矩阵。因为旋转本质是一种斜切,只是刚好两个斜切角的和为 360 度的倍数。

所以这里要把 skew 和 rotation 加起来,计算一个斜切矩阵。

结果矩阵为下面几个矩阵连续左乘:

  1. pivot 负方向的位移矩阵。表示图形上的某个点,移动到坐标原点。pivot 可以理解为前置版位移;
  2. skew 和 rotation 得到的斜切矩阵
  3. scale 对应的缩放矩阵
  4. position 对应的位移矩阵

Matrix 转 Transform

pixi.js 的实现为:

class Matrix { / * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. * @param transform - The transform to apply the properties to. * @returns The transform with the newly applied properties */ public decompose(transform: TransformableObject): TransformableObject { // sort out rotation / skew.. const a = this.a; const b = this.b; const c = this.c; const d = this.d; const pivot = transform.pivot; const skewX = -Math.atan2(-c, d); const skewY = Math.atan2(b, a); const delta = Math.abs(skewX + skewY); if (delta < 0.00001 || Math.abs(PI_2 - delta) < 0.00001) { transform.rotation = skewY; transform.skew.x = transform.skew.y = 0; } else { transform.rotation = 0; transform.skew.x = skewX; transform.skew.y = skewY; } // next set scale transform.scale.x = Math.sqrt((a * a) + (b * b)); transform.scale.y = Math.sqrt((c * c) + (d * d)); // next set position transform.position.x = this.tx + ((pivot.x * a) + (pivot.y * c)); transform.position.y = this.ty + ((pivot.x * b) + (pivot.y * d)); return transform; } } 

上面这个是 matrix 对象的方法,接收一个 transform 对象,修改它的值,并返回它自身。

pivot 这个就直接取传入的 transform 的 pivot。

计算斜切值 skew。即求图形两条相邻边各自的余弦值对应的角。

如果刚好两个斜切角之和为 0 或 360 度,说明是特殊的斜切——旋转,那就给 rotation 设置为 skewY。skew 设置为 0。

如果不是,rotation 设置为 0,skew 设置为斜切角。

scale 分别为 a 和 b、c 和 d 的平方和开方。

最后是 position,理论上直接取 tx 和 ty 即可,不过有个 pivot。pivot 是图形斜切缩放前的前置位移,所以给它应用去掉 tx 和 ty 的矩阵做一个运算,然后再加上 tx 和 ty 即可。

结尾

矩阵 matrix 体现了数学的简洁之美,只用几个数字,就能表达图形的各种变换的组合。

但问题是可读性差,无法直接看出图形的特性,比如旋转了多少,缩放了多少。

为了提高易用性,pixijs 引入了一套和 matrix 等价的 transform,让开发者使用图形时,能够快速上手,很好地解决了 Matrix 的弊端。

我是前端西瓜哥,关注我,学习更多平面几何知识。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/97161.html

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信