最近工作中遇到了这个问题,检索之后发现这种实现方式挺有意思的,无论是凸多边形还是凹多边形都可以判断。
射线法是用被测点向任意方向(通常为了好算,使其射向右侧)做一条射线,判断射线与多边形的交点。如果交点的数量为奇数,则被测点在多边形内;如果交点的数量为偶数,则被测点在多边形以外。
期间,有些特殊情况需要判断,比如:
1. 射线刚好经过凸多边形两条相邻边的交点上的情况会导致重复判断;
2.射线和多边形的边重合的情况。
先上js代码。
function isDotInPolygon(point, polygonPoints) { var flag = false, p1, p2; for(var i = 0, j = polygonPoints.length - 1; i < polygonPoints.length; j = i++) { p1 = polygonPoints[i]; p2 = polygonPoints[j]; // 这里判断是否刚好被测点在多边形的边上 if(isDotInLineSegment(point, p1, p2)) return true; if((p1.y > point.y != p2.y > point.y) && (point.x < (point.y - p1.y) * (p1.x - p2.x) / (p1.y - p2.y) + p1.x)) { flag = !flag; } } return flag; }
判断点是否在线段上的 isDotInLineSegment 的函数我懒得写了,这个很好实现。
关键点在于判断射线与多边形边相交的部分,这段代码原理不是我想出来的,它实现的方式很是精妙。
首先咱们看 表达式1: p1.y > point.y != p2.y > point.y
表面上看,表达式1是用了一个类似于异或的判断,要求被测点的y坐标在循环中多边形当前边的y轴投影范围内;
它其实还隐藏了另外一层条件,可以过滤掉前面提到的特殊情况1 和 2。 举个例子,多边形相邻的俩个边所在线段(p1, p2)和 (p2, p3),为了解释方便,咱们假设其中p3.y > p2.y > p1.y。 如果从被测点发射出来的射线经过了p2 ,那么上面这段表达式1其实在判断(p1, p2)时为false,判断(p2, p3)时为true,这样就巧妙地避免了重复计数的问题。 而如果是个凹多边形,存在p3.y > p2.y < p1.y , 那么表达式1 在判断(p1, p2)与(p2, p3)时都为true, 可以正确地计数两次。
然后看 表达式2 : point.x < (point.y - p1.y) * (p1.x - p2.x) / (p1.y - p2.y) + p1.x
同样为了解释方便,咱们假设其中p2.y > point.y > p1.y, 表达式2 等同于 (point.x - p1.x) / (point.y - p1.y) < (p1.x - p2.x) / (p1.y - p2.y) ,其中 (p1.x - p2.x) / (p1.y - p2.y) 可以理解为是线段(p1, p2)斜率的倒数, (point.x - p1.x) / (point.y - p1.y) 则是线段 (p1, point) 斜率的倒数。如果线段(p1, p2)的斜率的倒数要大于线段 (p1, point) 的斜率的倒数,则点point就只能在线段(p1, p2)的左侧。这样就确保了射线与线段(p1, p2)的相交。
这段看起来有点复杂,其实拿动笔画一画就很容易明白(没错,我又懒得上图了)。
原文地址:https://www.cnblogs.com/roay/p/9029133.html