矢量旋转,分别以三角变换,矩阵变换实现,C#源码。

最近用到了图形旋转,花了不少时间查找材料,编码测试。而且还用到了20年前老师教给的三角函数,还有大学里面早已淡忘的矩阵运算。

呵呵,整理一下把,希望对大家有些帮助。

功能: 已知矢量OP,顺时针旋转α度,求P2点的坐标。

根据三角函数,我们可以很自然的写出:

P2.X = O.X + (int)(Math.Cos(alpha) * r) ;
P2.Y = O.Y + (int)(Math.Sin(alpha) * r) ;    //哦,代码的颜色怎么不变呢? 噢,因为我刚开始在博客园写博客,所有不会使,呵呵,懒得理它了。

可惜,这样写是不对的。 原因嘛,一共有四个象限啊,当P1在不同的象限时候,结果是不同的。 论证过程。。。省掉N字。

嗯,先说个定理,可能大家会用到:

如上,半径 r 知道了,旋转角也知道了,P1P2的长度是多少? 

呵呵,献丑了,手绘的。

这个定理,可以帮我们得到边长,或者得到某边对应角的大小。 在我的项目里面用到了。

那么上面的P2点,究竟是多少? 哦,我就省去复杂的推理过程了,直接出代码:

 private Point GetNewPoint(double Rate, Point cirPos, Point startPos)
        {
            double Rage2 = Rate;// / 180 * Math.PI;
            //B点绕A点转R度得到C点坐标,flag: 顺时针1,反时针-1:B是转的点,A是圆心
            //C.X=(B.X-A.X)*COS(R*flag)-(B.Y-A.Y)*Sin(R*flag);
            //C.Y= (B.Y-A.Y)*COS(R*flag)+(B.X-A.X)*sin(R*flag);
            //转的点坐标-圆心坐标
            //圆心坐标+计算坐标=新位置的坐标
            double t1 = (startPos.X - cirPos.X) * Math.Cos(Rage2);
            double t2 = (startPos.Y - cirPos.Y) * Math.Sin(Rage2);
            int newx = (int)(t1 - t2);

            int newy = (int)((startPos.Y - cirPos.Y) * Math.Cos(Rage2) + (startPos.X - cirPos.X) * Math.Sin(Rage2));
            Point newpoint = new Point(cirPos.X + newx, cirPos.Y + newy);
            return newpoint;
        }

上面这个公式,不是个人推出来的,是教科书上的结论:

x0=|R|*cosA      y0=|R|*sinA                  x1 =|R|*cos(A +B)  y1=|R|*sin(A+B)
所以将x1,y1展开,有:
x1=|R|*(cosAcosB-sinAsinB)
y1=|R|*(sinAcosB+cosAsinB)
 
把    cosA = x0/|R|   sinA = y0/|R| 代入上面的式子,得到
 x1 = |R|*(x0*cosB/|R|-y0*sinB/|R|)
 y1 = |R|*(y0*cosB/|R|+x0*sinB/|R|)
最终结果:
x1 = x0 * cosB - y0 * sinB
y1 = x0 * sinB + y0 * cosB
 
呵呵,三角函数方式,代码与原理交代完毕。  下面就是矩阵了。
通常在二维中,我们使用的是三阶矩阵,为什么呢? 因为二阶不够使啊,为什么呢? 因为二阶只能实现:缩放,旋转,对称变换,无法实现平移啊,
为什么呢? 因为,二阶不是万能神啊,为什么呢?万能神是不问为什么的哈。。无语。
 
程序中矩阵的样子是这样的:
我们敲一个C#中的矩阵看看:
namespace System.Drawing.Drawing2D
{
    //
    // 摘要:
    //     封装表示几何变换的 3 x 3 仿射矩阵。此类不能被继承。
    public sealed class Matrix : MarshalByRefObject, IDisposable
    {
        //
        // 摘要:
        //     将 System.Drawing.Drawing2D.Matrix 类的一个新实例初始化为单位矩阵。
        public Matrix();
       
        public Matrix(RectangleF rect, PointF[] plgpts);
        //
        // 摘要:
        //     使用指定的元素初始化 System.Drawing.Drawing2D.Matrix 类的新实例。
        //
        // 参数:
        //   m11:
        //     新的 System.Drawing.Drawing2D.Matrix 的第一行和第一列中的值。
        //
        //   m12:
        //     新的 System.Drawing.Drawing2D.Matrix 的第一行和第二列中的值。
        //
        //   m21:
        //     新的 System.Drawing.Drawing2D.Matrix 的第二行和第一列中的值。
        //
        //   m22:
        //     新的 System.Drawing.Drawing2D.Matrix 的第二行和第二列中的值。
        //
        //   dx:
        //     新的 System.Drawing.Drawing2D.Matrix 的第三行和第一列中的值。
        //
        //   dy:
        //     新的 System.Drawing.Drawing2D.Matrix 的第三行和第二列中的值。
        public Matrix(float m11, float m12, float m21, float m22, float dx, float dy);

就是说,微软的matrix,我们可以定义为 x1 y1,x2 y2,x3 y3。 而微软会自动给我们补充第三列:0,0,1。这个查一下msdn就看到了。

哦,还是先上代码把,看看我们用matrix如何实现这种旋转。

        private void MyTest()
        {
            Point pa = new Point(100, 100);
            Point pb = new Point(200, 100);
            DrawPoint(Brushes.Red, pa);
            DrawPoint(Brushes.Black, pb);
            DrawLine(Pens.Red, pa, pb);
            
            double ang = Math.PI * 30 / 180;

            Matrix m = new Matrix();
            m.Translate(-pa.X, -pa.Y);
            m.Rotate(30, MatrixOrder.Append);
            m.Translate(pa.X, pa.Y, MatrixOrder.Append);

            Point[] ps = new Point[1];
            ps[0] = pb;

            m.TransformPoints(ps);
            DrawPoint(Brushes.Blue, ps[0]);

            Graphics g = Graphics.FromHwnd(this.Handle);
            Rectangle rect = new Rectangle(pa, new Size(0, 0));
            rect.Inflate(100, 100);           

            g.DrawArc(Pens.Red, rect, 0, 30);
            DrawPoint(Brushes.Green, ps[0]);
            DrawLine(Pens.Red, pa, ps[0]);

        }

        private void DrawPoint(Brush b, Point p)
        {
            Rectangle rect = new Rectangle(p, new Size(5, 5));
            Graphics g = Graphics.FromHwnd(this.Handle);
            g.FillRectangle(b, rect);           
        }

        private void DrawLine(Pen pen, Point p1, Point p2)
        {
            Graphics g = Graphics.FromHwnd(this.Handle);
            g.DrawLine(pen, p1, p2);
        }

这段代码就可以实现第一幅图的效果了。

我们在百度上查资料,可以看到:缩放矩阵,旋转矩阵,平移矩阵。 但是,如果进行一个点,绕另外一个点进行旋转,那么这个操作就是一个复合矩阵操作,是组合变换方式。

在实际应用过程中,我们在使用word、ppt、photoshop,经常会把图片旋转一下,可能是以左上角为圆心,旋转20度,或者以图片中心点为圆心旋转。问题来了,我们用的圆心,不同于屏幕左上角的圆心哦,图片的四个角点的坐标,都是基于屏幕左上角圆心的坐标。这个怎么整?

是的,从O,到O1(哈哈,我上面画了O撇,输入法输不上,干嘛不画个O1啊,砸到自己脚啦),我们是平移了一个位移。所以我们如果想旋转P1后得到P2(呵呵,你懂的),

我们应当这样做:第一步:将O1移到O。  第二步:将坐标系旋转。第三步:将O1移回原来位置。 这样P2就完成了在坐标系 X1O1Y1中,从P1点转到P2点的任务。

第一步:平移变换:

第二步:旋转变换:

第三步:平移变换:

注意,矩阵乘法是讲究先后次序的,如同领导人的排名,不可乱来哦。 呼。。。小工告成,同志们吉祥。

原文地址:https://www.cnblogs.com/hongsheng001/p/4127458.html