Silverlight数学引擎(8)——尺规作图之交点2

深圳又到了一年中最为尴尬的天气,盖被子又热不盖又冷,带伞又不下雨不带的话可能就成落汤鸡,就连夏天觅不找踪影的蚊子,这个季节也纷纷出来劫色了,不禁回忆起老家那种四季分明的气候,春花秋月夏雨冬雪…

我们继续来研究下交点,由于线和圆的交点相对比较简单我们就只讨论圆和圆相交的情况吧,其实也不是很难就是代数太多太繁琐,只要一步步理清了就好了。看看圆的方程:

(x-a)2 - (y-b)2 = r2

 其中(a,b)就是圆心,r就是半径,很直观。计算两圆交点就是解这样的方程组,首先,我们按照这个公式定义一个圆:

    //圆:(x-a)2+(y-b)2=r2
    public class LogicalCircle
    {
        public LogicalCircle(LogicalPoint center, LogicalLine radius)
        {
            Center = center;
            Radius = radius;
        }

        public LogicalPoint Center { get; set; }
        public LogicalLine Radius { get; set; }
        public double a { get { return Center.X; } }
        public double b { get { return Center.Y; } }
        public double r { get { return Radius.Length; } }
    }

解二元二次方程组就是先把它转化成一元二次方程:

       ax2+bx+c = 0

 虽然离开初中很久了,但大家对这个东西一定还似曾相识吧,当年的题海战术中,几乎每次都有它的影子,尤其是它的求根公式:

至于这样的求根公式是怎么来的,我也懒得去想了,当年老师就是叫我们这样背下来的,这样解题就不用动脑子啦。

要计算两圆交点,我们不防先把方程组转化成一元二次方程(当然还有其他方法),看看代码是怎么实现的:

        //圆:(x-a)2+(y-b)2=r2
        //圆:(x-a)2+(y-b)2=r2
        public static Tuple<LogicalPoint, LogicalPoint> CircleAndCircle(LogicalCircle circle1, LogicalCircle circle2)
        {
            var p1 = new LogicalPoint(double.PositiveInfinity, double.PositiveInfinity);
            var p2 = new LogicalPoint(double.PositiveInfinity, double.PositiveInfinity);
            if (((circle1.r + circle2.r) - circle1.Center.Distance(circle2.Center)) < 0)
            {
                //两圆相离,无交点
            }
            else
            {
                //左右相减可得x、y的二元一次方程:y=mx+n;
                var m = (circle1.a - circle2.a)/(circle2.b - circle1.b);
                var n = (circle1.r.Sqr() - circle2.r.Sqr() + circle2.a.Sqr() - circle1.a.Sqr() + circle2.b.Sqr() -
                         circle1.b.Sqr())/(2*(circle2.b - circle1.b));

                //y=mx+n代入circle的方程:(x-a)2+(mx+n-b)2=r2
                //转化成一元二次方程的标准式:ax2+bx2+c=0
                var q = n - circle1.b;
                var a = m.Sqr() + 1;
                var b = 2*(m*q - circle1.a);
                var c = circle1.a.Sqr() + q.Sqr() - circle1.r.Sqr();

                //求方程ax2+bx+c=0的两个根
                var d = b*b - 4*a*c;
                if (a == 0 && b != 0)
                    p1.X = -c/b;

                if (d == 0)
                    p1.X = -b/(2*a);
                else if (d > 0)
                {
                    d = d.SquareRoot();
                    p1.X = (-b - d)/(2*a);
                    p2.X = (-b + d)/(2*a);
                }
                //代入x到直线方程求y:
                p1.Y = m*p1.X + n;
                p2.Y = m*p2.X + n;
                if (p1.Distance(p2).IsZero())
                {
                    p2.X = double.PositiveInfinity;//两交点重合,认为P2不存在
                }
            }
            return new Tuple<LogicalPoint, LogicalPoint>(p1, p2);
        }

代码中我们处理了两个特殊情况——两圆相离和相切。相离直接把交点置为不存在了事,对于相切,其实是两交点重叠,按理说不处理就OK了,因为在屏幕上也是重叠显示的看起来也是一个点,但是为什么要把P2置为不存在呢?原因是如果我们给点加了名称如A、B并在屏幕上显示,当两点重合时,A、B也重叠了,不好看,所以选择去掉一个,此外别无他意!

这里又有另外一个问题,既然有两个交点,那如果我只要其中一个,那应该是取哪个呢?不理解吗?来看看下图,当两圆交于E、F,那E的坐标究竟是以上解中的P1还是P2呢?这里经过测试,我们发现按照顺时针的方向连接A、E、B、F,则始终有E是P1,F是P2。这为我们以后的鼠标画交点操作提供了理论依据,从而当鼠标在F点单击时,不会让交点跑到E点去。

此外,直线也圆也存在类似的问题,假设线AB和圆有两个交点E、F,其中E对应P1,F对应P2,则EF与AB的方向是一致的,如图:

OK,分析完毕,下面来完善我们的测试程序:

  1. 利用我们以前测试的元素(自由点P1、P2,自由线P1P2)
  2. 创建两个新的自由点(PO1,PO2)
  3. 以PO1、PO2为圆心,以P1P2为半径作两个圆
  4. 创建线P1P2和圆PO1的两个交点
  5. 创建圆PO1、PO2的两个交点
  6. 添加鼠标事件让自由点都可以拖拽。

 一切就绪后,两个圆也出来了,但是圆心不能被拖动,调试进去返现我们的HitTest方法有问题了:

        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;
        }

该方法没有按我们预期地返回自由点,而是返回了圆(因为点和圆都是Ellipse),为什么呢,因为圆是后创建的,其位于最上层,而HitTest只返回了一个Ellipse,当然是最上层的那个了。解决方法是在PointShapeCircleShape的构造函数中指定不同的ZIndex(保证圆在点的下面就OK了),如下:

        public PointShape(LogicalPoint center)
        {
            Center = center;
            Shape = new Ellipse { Width = Size, Height = Size, Fill = Brushes.Green, Stroke = Brushes.Black };
            Shape.Tag = this;
            Canvas.SetZIndex(Shape, 100);
        }

搞定,运行,来看看运行效果图吧:

 

【源代码和演示地址】

下一节我们介绍一下线上的点和圆上的点的情况,新的曙光即将到来!

posted @ 2012-11-30 15:59  地月银光  阅读(1419)  评论(0编辑  收藏  举报