多边形的命中测试
这个代码示例依据的原理很简单,那就是从给定的要测试的点向下延伸,看看它能和多边形的几条边相交,如果有偶数条边相交,则说明该点不在多边形内,如果有奇数条边相交,则说明该点在多边形内部。
大体上实现的过程是:
points是个Point类型的数组,保存多边形的点,
ptMouse即要测试的点,
count为计数器,确定相交点的个数.
首先,将points里的点分成两组,第一组里所有的点的X坐标都小于等于ptMouse.X,第二组的X坐标都大于ptMouse.X
其次,对于第一组的每个点pt找到它的前驱ptPrevi和后继ptNext(如果当前点是points里第一个点,则其前驱是points的最后一个点,如果当前点是points里最后一个点,则其后继是points里的第一个点)。
第三,如果它的前驱点在第二组里,则进行如下计算:如果(ptPrevi.Y-pt.Y)/(float)(ptPrevi.X-pt.X)<(ptMouse.Y-pt.Y)/(float)(ptMouse.X-pt.X)则证明相交,count加1;如果它的后继点在第二组里,也进行同样的计算,如果也相交,count加1;
最后,看count,如果是奇数则点ptMouse位于多边形内,否则位于多边形之外。
private bool hitTesting(Point[] points, Point ptMouse )
{
int count = 0;
Point[] pts1 = (from Point pt in points where pt.X <= ptMouse.X select pt).ToArray();
Point[] pts2 = (from Point pt in points where pt.X > ptMouse.X select pt).ToArray();
if (pts1 == null || pts1.Count() < 0) return false;
if (pts2 == null || pts2.Count() < 0) return false;
foreach (Point item in pts1)
{
int index =points.IndexOf(item);
Point ptPrevi = index == 0 ? points[points.Count - 1] : points[index - 1];
Point ptNext = index == points.Count - 1 ? points[0] : points[index + 1];
float rate = GetQxRate(item, ptMouse);
if (pts2.Contains(ptPrevi))
{
if (GetQxRate(item,ptPrevi)>rate)
{
count++;
}
}
if (pts2.Contains(ptNext))
{
if (GetQxRate(item, ptNext) > rate)
{
count++;
}
}
}
return count % 2 != 0;
}
private float GetQxRate(Point item, Point ptMouse)
{
return (ptMouse.Y - item.Y) / (float)(ptMouse.X - item.X);
}
在后面的代码中将实现多边形命中测试的功能,实现的代码主要是hitTest方法,由于这个方法要结合测试示例,所以与上面的同名方法存在一些不同。
在这个示例中,窗体最初有个最简单的三角形,
当前你也可以定义更复杂的形状,点“重新定义多边形”来擦除现有形状,然后依次在需要的地方点击鼠标,在最后一个点上右击以完成多边形的定义,并退出定义状态,进入测试状态,
测试的时候,在要测试的地方点击鼠标,状态栏会给出测试结果,并且多边形上凡是与位于该测试点上的竖线相交的边都会有提示(命中时为绿色,否则为红色)!
(这里的多边形指所有的边都是直边,不能存在弧线,后者的情况可使用GraphicsPath类的实例方法IsVisible,但必须精心的添加各个线段)