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

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

 

【源代码和演示地址】

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

本文链接



You must enable javascript to see captcha here!

Copyright © All Rights Reserved · Green Hope Theme by Sivan & schiy · Proudly powered by WordPress

无觅相关文章插件,快速提升流量