任意图形的变换原理

518 阅读4分钟

前言

源码

github.com/buglas/canv…

学习目标

  • 基于某一基点变换任意图形

知识点

  • 矩阵变换

1-基点变换的思路

此时我突然想起一句让人午夜惊醒的话:来,咱们再把这件事情的来龙去脉从头捋一遍。

我现在就想着把这个基点变换的逻辑从头捋一遍。

我之前把图案变换的文章和课件放出去后,有的同学竟然无法照葫芦画瓢的实现文字变换。

这个问题是我的锅,这不是谦虚,而是事实,因为我的那几个同学的编程能力和学习能力都很强的。

矩阵变换是我们走图形学路线的同学必须要掌握且夯实的,所以我觉得我必须再把这件事情的来龙去脉从头捋一遍。

已知:

1.图形obj的初始状态如下,其所在的坐标系为m0,m0是初始矩阵,不可写。

image-20230617110033570

  • x0,y0是m0的基向量。
  • origin是变换基点,当前的origin是obj在m0中的右下角点。

2.obj只能通过本地模型矩阵localMatrix变换。

localMatrix中的位移量、旋转量、缩放量分别为localPosition_old、localRotate、localScale。

上面的localPosition_old之所以是old,是因为我们后面会根据基点origin和localMatrix算出一个新的localPosition。

现在obj被localMatrix变换成了下图的样式:

image-20230617110231817

  • parentMatrix是localMatrix的父级坐标系,x,y是其基向量。
  • localPosition是localMatrix*origin后得到的origin在父级坐标系parentMatrix中的点位。

求:以origin为变换基点,relativeMatrix为相对变换矩阵,对当前状态的obj进行相对变换后的矩阵localMatrix_next

思路:任意图形的基点变换都可以通过一个矩阵实现。我们可以通过localMatrix_next矩阵使obj的变换基点在origin上,并基于relativeMatrix做相对变换,如下图所示:

image-20230617111533147

解:

1.建立矩阵m1,使其可以在m0坐标系中,将origin移动到原点。

m1.position=-origin

m1*m0 的效果如下:

image-20230617114320361

假设当前变换的图形为obj',接下来需要在父级坐标系中,将其与obj重合。

2.建立矩阵m2,设置其位移量,使让obj'的origin点与obj的localPosition点对齐。

m2.position=localPosition

3.计算localMatrix_next矩阵。

m1.position=-origin
m2.position=localPosition
localMatrix_next=m2*m1*m0

使用localMatrix_next变换obj'的效果如下:

image-20230617115202346

现在基点就对齐了,接下来再设置obj'的缩放量和旋转量,使其与obj重合。

4.将localMatrix中的旋转量localRotate和缩放量localScale赋值给m2。

m1.position=-origin
m2.position=localPosition
m2.scale=localScale
m2.rotate=localRotate
localMatrix_next=m2*m1*m0

使用localMatrix_next变换obj'的效果如下:

image-20230617115314427

现在已经实现了obj'和obj的重合,接下来基于relativeMatrix对obj'做相对变换。

4.在m2中加上相对变换矩阵relativeMatrix的变换量,对obj'进行相对变换。

设relativeMatrix中的位移、缩放、旋转量分别为relativePosition、relativeRotate、relativeScale。

m1.position=-origin
m2.position=localPosition+relativePosition
m2.scale=localScale*relativeScale
m2.rotate=localRotate+relativeRotate
localMatrix_next=m2*m1*m0

使用localMatrix_next变换obj'的效果如下:

image-20230617122129666

接下来,我们在canvas里写一下这个过程。

2-基点变换的代码实现

1.声明已知变量。

/* 变换基点 */
const origin = new Vector2(150, 100)

/* localMatrix */
const localPosition_old = new Vector2(0, 0)
const localRotate = 0.3
const localScale = new Vector2(1.5, 1)
const localMatrix = new Matrix3()
    .scale(localScale.x, localScale.y)
    .rotate(localRotate)
    .translate(localPosition_old.x, localPosition_old.y)

/* 相对变换矩阵relativeMatrix中的变换量 */
const relativeScale = new Vector2(1, 2)
const relativeRotate = 0.2
const relativePosition = new Vector2(-100, -50)

上面的localPosition_old之所以是old,是因为我们后面会根据基点origin和localMatrix算出一个新的localPosition。

2.声明一个绘制矩形的方法。

/* 绘制矩形 */
function drawRect(
    ctx: CanvasRenderingContext2D,
    m: Matrix3,
    fillStyle = '#000'
) {
    const { elements: e } = m
    ctx.save()
    ctx.fillStyle = fillStyle
    ctx.transform(e[0], e[1], e[3], e[4], e[6], e[7])
    ctx.fillRect(0, 0, 150, 100)
    ctx.restore()
}

3.先绘制一个obj对象

drawRect(ctx, localMatrix)

效果如下:

image-20230613110032872

之前声明的变换基点origin便是在这个矩形的左下角。

4.创建m1矩阵,使其可以在m0坐标系中,将origin移动到原点。

const m1 = new Matrix3().makeTranslation(-origin.x, -origin.y)

基于m1绘制一个矩形看看:

drawRect(ctx, m1, 'orange')

效果如下:

image-20230613111238274

5.创建m2矩阵,先使其可以在父级坐标系中,将刚才画的橙色矩形obj'与黑色矩形obj对齐。

const localPosition = origin.clone().applyMatrix3(localMatrix)
const m2 = new Matrix3()
    .scale(scale.x, scale.y)
    .rotate(rotate)
    .translate(localPosition.x, localPosition.y)
const localMatrix_next = new Matrix3().multiplyMatrices(m2, m1)

使用m2和m1绘制一个红色的obj'。

drawRect(ctx, localMatrix_next, 'red')

效果如下:

image-20230613112103094

6.在m2中,基于矩阵relativeMatrix做相对变换。

const m2 = new Matrix3()
    .scale(localScale.x * relativeScale.x, localScale.y * relativeScale.y)
    .rotate(localRotate + relativeRotate)
    .translate(
        localPosition.x + relativePosition.x,
        localPosition.y + relativePosition.y
    )

效果如下:

image-20230613114921601

关于基点变换的代码实现就是这样,大家可以自己调着试试。

总结

我之前的图案变换主要是做一个热身,那个理解起来比较具象。

接下我们可以把之前的图案变换忘掉,用现在的变换方法来实现任意图形的变换。