大家好,我是前端西瓜哥。
今天我们来实现一个比较少用到的功能:对选中图形做水平翻转和垂直翻转。
翻转实现分成这么 3 步:
- 计算选中图形的中心位置,作为翻转的翻转中心;
- 得到翻转矩阵;
- 给所有的图形应用翻转矩阵。
选中图形的中心
选中图形如果是单个,我们 选择图形的 OBB (带朝向的包围盒)的中点位置作为翻转中心。
以矩形为例,就是计算给矩形的 [width/ 2, height / 2]
应用变形矩阵后的位置。
getCenter(): IPoint {
const tf = new Matrix(...this.attrs.transform);
return tf.apply({
x: this.attrs.width / 2,
y: this.attrs.height / 2,
});
}
这里用 width / height / transform 表达矩形的位置、大小、旋转、翻转等物理信息。
选中图形如果是多个,就计算每个图形的 AABB 包围盒(包围图形的最小矩形),然后将它们合并成一个大包围盒,取这个大包围盒的中心作为翻转中心。
代码语言:javascript复制const mergedBox = mergeBoxes(graphicsArr.map((item) => item.getBbox()));
// 翻转中心
const flipCenter = {
x: box.minX / 2 box.maxX / 2,
y: box.minY / 2 box.maxY / 2,
};
得到翻转矩阵
我们以水平翻转切入。
假设我们 基于 y 轴做水平翻转,本质就是 将图形的点的 x 值取反。
一个点原来在右边(x > 0),水平翻转一下,跑到右边去了(x < 0)。同理,一个点原来在左边,水平翻转一下,跑到左边去了。
也就是说,等价于 x 乘以 -1,其他保持不变。这个操作对应的矩阵是缩放矩阵 Scale(-1, 1)
。
回到我们的对选中图形水平翻转。我们不是基于 y 轴做翻转,是对选中图形的中心做翻转。
所以我们需要先把图形移动到原点 Translate(-cx, -cy)
,然后再做翻转 Scale(-1, 1)
,最后再移动回来 Translate(cx, cy)
。
记住在原矩阵应用新的矩阵是要左乘的,不是右乘。图形需要应用的矩阵是:
代码语言:javascript复制Translate(cx, cy) * Scale(-1, 1) * Translate(-cx, -cy)
用 pixi.js 的 Matrix 类的话,就是这样写。
代码语言:javascript复制const flipMatrix = new Matrix()
.translate(-flipCenter.x, -flipCenter.y) // 先左乘 “移动到原点” 的矩阵
.scale(-1, 1) // 再缩放
.translate(flipCenter.x, flipCenter.y); // 最后移动回来
也可以使用其他矩阵库,用法类似。
应用矩阵
最后给选中的每个图形 应用这个翻转矩阵(记住是左乘这个矩阵)。
代码语言:javascript复制for (const graphics of graphicsArr) {
graphics.attrs.transform = flipMatrix.append(graphics.attrs.transform);
}
至此,翻转就完成了。
就是如此简单。
拓展延伸
虽然这里只是讲如何翻转,但我想优秀的读者可能已经察觉到了,开始举一反三了。
这次做的是翻转需求,如果下次需求是要做个旋转,其实也是一个道理,将中间的缩放矩阵换成缩放矩阵就行了。
或者更复杂一些,基于某条线做镜像对称,其实也就是在原来缩放的基础上再补上一个旋转。
这就是引入矩阵这一数学工具的含金量。
你说我不用矩阵,用解析几何的做法也能解。的确可以,但这种方式难度高也容易写错,写久了自己回过头来发现自己也看不懂了,也实属正常,没有可持续性。
以前我是用几何算法去实现的,那可太痛苦了,纸上画来画去,推导一番好像想通了,翻译成代码,发现效果不对,再做调试,最后还是要重新看推导过程是不是哪来不对,反复几遍才做完。
当然,使用矩阵实现需求是有个前提的,就是图形要用矩阵来表达。
或者可以不用矩阵表达,但是可以转换成矩阵的表示,且能在做完矩阵变换后转换回来。
但通常来说非矩阵表达转换为矩阵,转换结果只是矩阵的子集,也就是说有些矩阵是无法转换回原表达,会丢失一些信息,这点要注意
此外就是对矩阵有一点点认识,不然不会用,看着干瞪眼,不过网上很多资料,对读者来说应该问题不大。
结尾
我是前端西瓜哥,欢迎关注我,学习更多图形编辑器知识。