旋转变换

图元绕基点旋转以后达新的位置称为旋转变换。基点可以是原点,也可以是其他点。本节讨论最通用的情况,即图元绕任意点旋转。绕任意点旋转得到的变换是一个组合变换,需要进行3次变换,第1次做平移变换,第2次做旋转变换,第3次做第1次变换的逆变换。

所以绕任意点(x0.y0)旋转的变换矩阵如下所示:"

Document Image
\[\]

这样,点(x,y)绕任意点(x0.y0)旋转以后点的坐标为:

Document Image
\[\]

其中,0是点(x.y)和(xo,yo)所确定的直线段的方位角。根据上面的计算公式,可以得到图元控制点旋转以后的坐标值。实际操作中,需要首先在CGElement类中添加一个需要重写的Rotate方法,然后在各派生类中重写它。图元类更新以后,创建一个旋转交互类CRotate,它定义触发鼠标事件时的绘图行为。

更新图元类

1. 在CGElement类中添加 Rotate方法

首先在CGElement类中添加一个Rotate方法,该方法有3个参数:g参数为Graphics对象,指定绘图表面:basePos为基点;aAngle为旋转角度,以弧度为单位。

code.vb.net
'旋转图元
Public MustOverride Sub Rotate(ByVal g As Graphics,
        ByVal basePos As PointF, ByVal aAngle As Single)

2. 在CLine类中重写基类的Rotate方法

在CLine类中重写基类的Rotate方法,计算旋转以后直线段起点和终点的位置。

code.vb.net
    '旋转变换
    Public Overrides Sub Rotate(ByVal g As Graphics, ByVal basePos As PointF, ByVal aAngle As Single)
        m_Begin = pRotate(basePos, m_Begin, aAngle)
        m_End = pRotate(basePos, m_End, aAngle)
    End Sub

上面的代码中用到了一个protate函数,该函数返回绕基点旋转以后图元控制点的坐标。函数有3个参数:baseP为基点,Pos为要旋转的点,angle为旋转角度。返回值为一个PointF对象,为旋转以后的Pos点对象。旋转以后的坐标根据前面绕任意点旋转以后点的坐标的计算公式得到。

code.vb.net
    '计算旋转变换以后点的坐标
    Public Function pRotate(ByVal baseP As PointF, ByVal Pos As PointF, ByVal angle As Single) As PointF
        Dim pr As New PointF()
        Dim cosv, sinv As Single
        cosv = Cos(angle)
        sinv = Sin(angle)
        With pr
            .X = Pos.X * cosv - Pos.Y * sinv + (1 - cosv) * baseP.X + baseP.Y * sinv
            .Y = sinv * Pos.X + cosv * Pos.Y + (1 - cosv) * baseP.Y - sinv * baseP.X
        End With
        Return pr
    End Function

3. 在CRectangle类中重写基类的Rotate方法

在CRectangle 类中重写基类的Rotate 方法,利用pRotate 函数计算旋转以后矩形的起点和终点的位置。

code.vb.net
    '旋转变换
    Public Overrides Sub Rotate(ByVal g As Graphics, ByVal basePos As PointF, ByVal aAngle As Single)
        m_basePos = pRotate(basePos, m_basePos, aAngle)
        m_desPos = pRotate(basePos, m_desPos, aAngle)
    End Sub

4. 在CCircle类中重写基类的Rotate 方法

在CCircle类中重写基类的Rotate方法,计算旋转以后圆的圆心和圆上一点的坐标。

code.vb.net
    '旋转变换
    Public Overrides Sub Rotate(ByVal g As Graphics, _
                ByVal basePos As PointF, ByVal aAngle As Single)
        m_Center = pRotate(basePos, m_Center, aAngle)
        m_PCircle = pRotate(basePos, m_PCircle, aAngle)
    End Sub

5. 在CArc类中重写基类的Rotate方法

在CCircle类中重写基类的Rotate方法,计算旋转以后圆弧的圆心、起点和终点的坐标。

code.vb.net
    '旋转变换
    Public Overrides Sub Rotate(ByVal g As Graphics, ByVal basePos As PointF, ByVal aAngle As Single)
        m_Center = pRotate(basePos, m_Center, aAngle)
        m_Begin = pRotate(basePos, m_Begin, aAngle)
        m_End = pRotate(basePos, m_End, aAngle)
    End Sub

创建CRotate类

CRotate类定义用鼠标进行交互旋转变换操作时的绘图行为。它实现了ICommand 接口。第1次单击鼠标左键时,定义旋转的基点。第2次单击鼠标左键时,确定第2点并旋转图元,旋转角度为它与基点之间的方位角。进行旋转的图元为选择集中的图元。删除图元旋转前位置上的显示,在新位置上绘制图元。

code.vb.net
Public Class CRotate
    Implements ICommand
    Private m_Step As Integer
    Private m_basePos, m_desPos As PointF
    '单击鼠标左键时的绘图行为
    Public Sub LButtonDown(ByVal g As Graphics, ByVal aPos As PointF) Implements ICommand.LButtonDown
        Dim i As Integer
        '鼠标单击次数加1
        m_Step += 1
        Select Case m_Step
            Case 1
                m_basePos = aPos
                m_desPos = aPos
            Case 2
                '清除最后一条橡皮线
                Dim tempLine As New CLine(m_basePos, m_desPos)
                tempLine.Draw(g, CGElement.geDrawMode.Drag)
                tempLine = Nothing
                '如果选择集中的图元个数大于0
                If geSels.Count > 0 Then
                    Dim aAngle As Single
                    '计算旋转角度
                    aAngle = GetAngle(m_basePos, m_desPos)
                    For i = 0 To geSels.Count - 1
                        '清除图元在当前位置的图形
                        geSels(i).Draw(g, CGElement.geDrawMode.Delete)
                        '将所有被选中的图元旋转到目标位置并进行绘制
                        geSels(i).Rotate(g, m_basePos, aAngle)
                        geSels(i).Draw(g, CGElement.geDrawMode.Selec)
                    Next i
                End If
                m_Step = 0
        End Select
    End Sub
    '移动鼠标时的绘图行为
    Public Sub MouseMove(ByVal g As Graphics, ByVal aPos As PointF) Implements ICommand.MouseMove
        Select Case m_Step
            Case 1
                Dim prePos As New PointF()
                Dim curPos As New PointF()
                prePos = m_desPos
                curPos = aPos
                '清除上一条橡皮线
                Dim tempLine1 As New CLine(m_basePos, prePos)
                tempLine1.Draw(g, CGElement.geDrawMode.Drag)
                tempLine1 = Nothing
                '绘当前位置的橡皮线
                Dim tempLine2 As New CLine(m_basePos, curPos)
                tempLine2.Draw(g, CGElement.geDrawMode.Drag)
                tempLine2 = Nothing
                m_desPos = aPos
        End Select
    End Sub
    '单击鼠标右键时的绘图行为
    Public Sub RButtonDown(ByVal g As Graphics, ByVal aPos As PointF) Implements ICommand.RButtonDown
        If m_Step = 1 Then
            '清除上一条橡皮线
            Dim tempLine As New CLine(m_basePos, m_desPos)
            tempLine.Draw(g, CGElement.geDrawMode.Drag)
            tempLine = Nothing
        End If
    End Sub
End Class
6.2.3 实现旋转图元
6.2.2节中已经创建了实现旋转图元功能的菜单资源,下面的程序先创建一个CRotate类实例,然后用ICommand对象aCommand引用它。
Public transRotate As New CRotate()
Private Sub mnu TRotate_Click(By Val sender As System.Object,_
        ByVal e As System.EventArgs) Handles mnuTRotate.Click
    aCommand = transRotate
End Sub

旋转图6-2中的图,效果如图6-4所示。

Document Image
\[\]

图6-4 旋转图元