前言
源码
学习目标
- 基于某一基点变换任意图形
知识点
- 矩阵变换
1-基点变换的思路
此时我突然想起一句让人午夜惊醒的话:来,咱们再把这件事情的来龙去脉从头捋一遍。
我现在就想着把这个基点变换的逻辑从头捋一遍。
我之前把图案变换的文章和课件放出去后,有的同学竟然无法照葫芦画瓢的实现文字变换。
这个问题是我的锅,这不是谦虚,而是事实,因为我的那几个同学的编程能力和学习能力都很强的。
矩阵变换是我们走图形学路线的同学必须要掌握且夯实的,所以我觉得我必须再把这件事情的来龙去脉从头捋一遍。
已知:
1.图形obj的初始状态如下,其所在的坐标系为m0,m0是初始矩阵,不可写。
- x0,y0是m0的基向量。
- origin是变换基点,当前的origin是obj在m0中的右下角点。
2.obj只能通过本地模型矩阵localMatrix变换。
localMatrix中的位移量、旋转量、缩放量分别为localPosition_old、localRotate、localScale。
上面的localPosition_old之所以是old,是因为我们后面会根据基点origin和localMatrix算出一个新的localPosition。
现在obj被localMatrix变换成了下图的样式:
- parentMatrix是localMatrix的父级坐标系,x,y是其基向量。
- localPosition是localMatrix*origin后得到的origin在父级坐标系parentMatrix中的点位。
求:以origin为变换基点,relativeMatrix为相对变换矩阵,对当前状态的obj进行相对变换后的矩阵localMatrix_next
思路:任意图形的基点变换都可以通过一个矩阵实现。我们可以通过localMatrix_next矩阵使obj的变换基点在origin上,并基于relativeMatrix做相对变换,如下图所示:
解:
1.建立矩阵m1,使其可以在m0坐标系中,将origin移动到原点。
m1.position=-origin
m1*m0 的效果如下:
假设当前变换的图形为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'的效果如下:
现在基点就对齐了,接下来再设置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'的效果如下:
现在已经实现了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'的效果如下:
接下来,我们在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)
效果如下:
之前声明的变换基点origin便是在这个矩形的左下角。
4.创建m1矩阵,使其可以在m0坐标系中,将origin移动到原点。
const m1 = new Matrix3().makeTranslation(-origin.x, -origin.y)
基于m1绘制一个矩形看看:
drawRect(ctx, m1, 'orange')
效果如下:
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')
效果如下:
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
)
效果如下:
关于基点变换的代码实现就是这样,大家可以自己调着试试。
总结
我之前的图案变换主要是做一个热身,那个理解起来比较具象。
接下我们可以把之前的图案变换忘掉,用现在的变换方法来实现任意图形的变换。