所谓场景视图,就是一个包含控件的容器。在场景视图中,可以通过鼠标或者触摸屏任意平移、缩放、旋转里面的控件。
要实现这个效果,最关键的部分就是计算控件的二维空间变换矩阵。
常见的二维空间变换矩阵是 3x3 的矩阵,通过这个矩阵可以实现平移、缩放、旋转的功能。
也有 4x4 的变换矩阵,可以增加实现斜变效果,不过不在本文讲解范围内。
本文侧重与思路分析,不做具体的公式推导。文后附有实现代码,可以用来快速体验实际效果。
二维变换分量
为了更容易理解,我们将平移、缩放、旋转分开,这样就有三个变换矩阵,他们的乘积还是原来的矩阵。
S(Scale)表示缩放矩阵,只有 m11,m22 有值,其他都是 0(除 m33 永远是1)。m 11 表示 x 轴方向缩放系数,m22 表示 y 轴方向缩放系数。
R(Rotate)表示旋转矩阵,只有左上角 2x2 的子矩阵有值,其他都是 0(除 m33 永远是1)。(Rz 表示围绕 z 轴旋转)
T(Translate)表示平移矩阵,只有 m31,m32有值,其他都是 0(除 m11,m22,m33 永远是1)。m31 表示 x 轴方向的偏移量,m32 表示 y 轴方向的偏移量。
需要注意的是,S、R、T 三者的顺序不能调整。缩放、旋转是有中心的,如果放在平移之后,效果就变成中心点偏移后的缩放、旋转了,从直观上很难理解。至于缩放为什么要放在旋转前面,是因为缩放有方向性(沿 x、y 轴),一旦旋转之后,再缩放,就偏离了初始的轴定义。
二维变换操作
接下来考虑常见的二维变换操作,如何影响变换矩阵。我们在三个矩阵的基础上作调整,最终反映到矩阵乘积 A 上。
-
平移
平移操作改变控件的位置,控件整体平移。
如上图,我们在已有变换的基础上增加了一个平移量。对应的修改 T 矩阵就可以了: 。
-
拖拽缩放
拖拽缩放是指拖动边框的一个顶点或者一个边,达到缩放控件的效果。如下图的 8 个白点是拖拽点。
此时除了缩放,其实还附加有平移操作。分析一下算法的输入是:
- 拖拽方向:4 个顶点,4个边,共有 8 个可能的方向
- 拖拽量:沿着拖拽方向的调整量,比如拖拽某个顶点的位移量,相对于控件的坐标轴
先计算缩放增量,拿新的宽高分别除以原来的宽高,得到一个缩放增量 ,那么:
。
然后计算平移量,是否就是控件前后两个中心点的差值 t 呢?不然,因为中心点平移是相对于控件的坐标轴的,实际平移是在旋转(坐标轴也旋转了)之后,所以平移量应该是通过 调整后的值。
-
拖拽旋转
拖拽旋转的操作是,拖拽控件上方的旋转句柄(见上图蓝色原点),围绕控件中心点旋转。
是不是只有旋转呢?不是的,除了旋转,还附加有平移操作,原因还是因为坐标轴旋转了。拖拽旋转并不会引起平移,但是在旋转之前控件是有平移的,相对于旋转了的坐标轴,这个平移是有变化的。
先计算旋转增量,以中心点和旋转拖拽点的前后点,三点的夹角,就是旋转角度 。代入到
中,可以得到旋转增量
,那么:
然后计算平移量,用 t 表示旋转之前偏移量的,那么新增平移量为 。
-
滚轮操作
-
手势操作
手势操作是指用双指按住控件上的两个点,然后移动两个手指,实现对控件同时平移、缩放、旋转的操作。手势操作能够极大的提升操作体验,但也是实现最为复杂的。
如上图,手指1从起点1向下移动,同时,手指2从起点2向右上移动。我们来分析一下这个过程中,三个变换矩阵分别有什么变化。
首先要明确两个定理:“旋转不变性”和“缩放同向性”。
旋转不变性是指在旋转过程中,刚体上任意两点的连线,其旋转的角度是一样的;同时也说明,以任意一点为中心点,旋转的角度是一样的
缩放同向性是指在手势操作中,任意方向的缩放倍数是一样的,也即任意两点的连接线段,其长度变化的比例是一样的。
基于此,我们容易得出下面的结论:
- 手势操作中,缩放增量是双指距离的变化系数,即:操作后双指的距离 / 操作前双指的距离
- 手势操作中,旋转增量是双指连线转过的角度,即上图中的夹角
所以比较容易的就计算出 和
,剩下的就是平移量了
。先给出平移增量公式:
其中:
- t:操作前的平移量
- S‘:本次操作的缩放增量
- R':本次操作的旋转增量
- s1:手指1的起点(也可以用手指2)
- d1:手指1的终点
怎么理解这个公式呢?
首先,操作前的平移量,经过新的缩放、旋转增量后,会加入到最终的平移量上。
然后 是表面上的新的平移量。
最后,但是因为计算的是平移增量,所以要减去操作前的平移量。
算法实现
以上算法,是电子白板中的一部分。
基于 Qt 的实现版本,可以从这里获取。