Silverlight数学引擎(7)——尺规作图之交点1

喜你入骨 提交于 2020-01-22 00:38:46

前面总结了尺规作图的三大元素(点、线、圆),而且得出其结论——所有图形也最终是依赖于一些自由点(FreePoint)。自由点是没有依赖的,可以在屏幕上随意绘制,因此除了基本的坐标转换(数学坐标系与屏幕坐标系)外没有复杂的数学计算,所以我们也就不讨论了。本节主要讨论线与线交点的坐标计算,本来想把三种交点(线与线,线与圆,圆与圆)放在一节介绍,后来写演示代码的时候发现工程量有点大,所以这一节还是只是介绍下线与线的交点(这叫做小步快跑,是TDD编程的推荐方法,因为步子迈太大容易扯着蛋啦,寡人这两天就觉得有点蛋疼)。

线与线的交点必须要有两条线,我们在后台计算坐标需要用到平面解析几何(就是代数与几何的结合体啦),用到的都是初中的数学知识,因此大家不要惊慌。

先来看看直线的方程:

      y=ax+b

当我们用两个自由点确定一条直线时,我们把两个自由点A(x1,y1),B(x2,y2)的坐标代入改方程可以求解出a和b的值。为了直观起见,我们定义一个线的类(LogicLine)。(本节定义的类基本上都是为了演示计算和与数学概念相衔接,我们计算是用数学坐标系为参考的

//表示直线:y=ax+b
    public class LogicalLine
    {
        public LogicalLine(LogicalPoint p1, LogicalPoint p2)
        {
            P1 = p1;
            P2 = p2;
        }

        public LogicalPoint P1 { get; set; }
        public LogicalPoint P2 { get; set; }

        //直线(x1,y1)(x2,y2):y=ax+b:a=(y1-y2)/(x1-x2),b=y1-a*x1
        private double dy { get { return P2.Y - P1.Y; } }
        private double dx { get { return P2.X - P1.X; } }
        public double a { get { return dy / dx; } }
        public double b { get { return P1.Y - a * P1.X; } }
    }

有了直线方程,那么求两线的交点顺理成章就变成了解方程组(二元一次方程组),假设有两条直线:

  1. y=a1x+b1
  2. y=a2x+b2

如果该方程组有一个解(x=x3,y=y3),则点(x3,y3)就是交点了。

有意思的是这个方程组中,如果a1=a2 且 b1=b2,则有无数个解。是不是就有无数个交点呢?这种情况是两条线重合,可以理解成两线平行的特殊情况,我们当做没有交点。解二元一次方程组很简单了是吧,都是代数带来带去,烦得很呐,所以寡人在初中时只喜欢纯几何,呵呵,眼根清净。

不管怎么说,一次方程还是挺简单的,逻辑都写在代码里了,如果你离开初中很多年了,需要温故一下的话就看一眼吧,否则完全可以忽略了:

//线与线的交点
        public static LogicalPoint LineAndLine(LogicalLine line1, LogicalLine line2)
        {
            double x;
            double y;

            if (line1.dx.IsZero()) //与Y轴平行
            {
                x = line1.P1.X;
                y = line2.a*x + line2.b;
            }
            else if (line2.dx.IsZero()) //与Y轴平行
            {
                x = line2.P1.X;
                y = line1.a*x + line1.b;
            }
            else
            {
                if (line1.a.IsEqual(line2.a)) //两线平行,认为交点无穷远
                {
                    x = double.NegativeInfinity;
                    y = double.NegativeInfinity;
                }
                else
                {
                    var d = (line1.a - line2.a);
                    x = (line2.b - line1.b)/d;
                    y = line1.a*x + line1.b;
                }
            }
            return new LogicalPoint(x, y);
        }

可以看到代码里面用到了一些扩展的方法,例如IsZero()什么的,如果感觉莫名其妙可以看看源代码,过多地使用扩展方法可能会使代码看起来有点脱离.net了,毕竟.net的类库啊功能啊什么的已经很完备了,不过偶尔用用还是可以的,为了书写方便而已。

接下来就是要测试我们的计算逻辑是否正确,这里用TDD虽然必不可少但是太不直观了,我们还是写一个可交互的界面来验证。具体做法是这样子的:

  1. 使用我们前面实现的坐标系(CoordinateSystem)
  2. 在坐标系上画两个自由点P1、P2(红色)和四个角上的点(绿色)。
  3. 画四条固定的直线(坐标系的所在的Canvas的四边)。
  4. 画一条自由线FreeLine(依赖于P1、P2)。
  5. 添加四个交点(FreeLine和四个边的交点)(蓝色)
  6. 添加坐标系的鼠标事件,使得可以拖动P1、P2。
  7. 当P1或者P2移动时,重新计算FreeLine和四个交点的坐标并更新显示。

我们先来设计几个坐标系上的元素(点、线)来实现我们的测试,虽然元素很少很简单,我们还是按照从抽象到具体的设计方式,这是一种习惯啦。其实随便画几个圆啊线啊什么的搞一下测试也可以,但是以寡人的经历来看这样也省不了多少时间,反而不利于培养良好的编程习惯。

有了第一节面向对象的设计方法讲解,这里就不再详细介绍了,直接给出了图吧:

这样的设计体现了依赖的顺序,比如线依赖于两个点,交点又依赖于两条线,好处就是只要一个自由点发生变化了,就可以依照依赖找出其相关的所有元素,已达到牵一发而动的目的,呵呵。

因为显示和计算用到的坐标系不同,接口中定义CS(坐标系)是必须的,因为我们计算都是用的数学坐标系,而在界面上显示又必须是按照屏幕坐标系统。

UpdateVisual()是用于计算和更新位置的,当然不同的图形元素有不同的逻辑,在代码里看会比较清楚.

这里顺便提一下Canvas鼠标事件的一个常用功能——从当前单击点取元素,比如Canvas上有一个Ellipse,如果我在Ellipse上单击,可以找出这个元素。

作为游戏编程的人肯定都会想到用HitTest,对,我们就用它,这里我把它扩展成一个静态方法,而且还是泛型哦,这样如果Point下面有多个元素,你就可以方便地找出你要的类型,比如我们的测试用例中自由点和自由线是部分重叠的,而我们要拖动的仅仅是点而已。代码如下,其中VisualTreeHelper.FindElementsInHostCoordinates是Silverlight类库中的方法:

public static T HitTest<T>(this Panel panel, Point p) where T : FrameworkElement
        {
            var t = VisualTreeHelper.FindElementsInHostCoordinates(p, panel).FirstOrDefault();
            if (t != null && t is T)
                return (T)t;
            return null;
        }

OK,这节就到这里了,来看看运行效果图吧:

 

【源代码和演示地址】

下一节我们再讲解一下更复杂一点的交点的计算,以及存在多个交点的情况下,交点顺序如何确定的,两交点重合又将如何处理!

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!