乍一看这标题,多像是中学数学课本里的章节标题。
点和多边形就是本文的两位主角。本文主要探讨的问题是:如何判断点在多边形内部。
如果要判断点是否在矩形或者圆内部,其实很简单。
/**
* 勾股定理
* 圆心:cx, cy
* 半径: r
*/
const inside = (p.x - cx) ** 2 + (p.y - cy) ** 2 <= r ** 2
/**
* 矩形
* 左上角坐标: x, y
* 宽高: w, h
*/
const inside = (p.x - x) <= w && (p.y - y) <= h
说完了简单的,下面进入正题。
如何判断P1, P2, P3在三角形内(外)呢?
这里向大家介绍的方法叫Winding Number (wn) 。
由目标点水平向右发出射线,如果多边形的边向上穿过这条射线计数 +1,向下穿过射线则 -1。
很难想象?看下图就明白了。
从上图可以看出wn = 0时则点在多边形外。
图看明白了,怎么用代码实现呢?
在这之前,先了解一下如何判断点在有向线段的左(右)边还是在线段上。这是实现上面算法的关键。
从上图可以看出:
- P在线段BA的左边,在线段CD的右边。
- E在线段BA的右边,在线段CD上。
不多废话了,直接上代码。
/**
* @param {Array<number>} p - 点坐标: [x, y]
* @param {Array<number>} p1 - 线段起点: [x, y]
* @param {Array<number>} p2 - 线段终点: [x, y]
* @return {number}
* 0: p 在线段上
* <0: p 在线段右侧
* >0: p 在线段左侧
*/
// Y 轴正方形向下则 p1,p2 位置调换
function calc(p, p1, p2) {
return (p1[0] - p2[0]) * (p[1] - p2[1]) - (p[0] - p2[0]) * (p1[1] - p2[1])
}
也可以简单验证一下:
// P@BA: -52
calc([42, 12], [46, 6], [48, 16])
// E@CD: 0
calc([53, 11], [52, 16], [54, 6])
回到点与多边形的判断。这里直接上代码了。因为代码很简单。
/**
* @param {Array<number>} point - 点坐标: [x, y]
* @param {Array<number>} points - 多边形坐标点: [x1,y1, x2,y2, x3,y3...]
*/
function wind(point, points) {
let wn = 0
for (let i = 0; i < points.length; i += 2) {
const
p1 = [points[i], points[i + 1]],
p2 = i + 2 === points.length ? [points[0], points[1]] : [points[i + 2], points[i + 3]]
if (point[1] >= p1[1]) {
if (point[1] < p2[1] && calc(point, p1, p2) > 0) wn++
} else if (point[1] >= p2[1] && calc(point, p1, p2) < 0) wn--
}
return wn
}
简单解释一下为什么会有关于Y轴坐标的判断:
if (point[1] >= p1[1]) {
if (point[1] < p2[1] && calc(point, p1, p2) > 0) wn++
} else if (point[1] >= p2[1] && calc(point, p1, p2) < 0) wn--
通过calc计算的结果可知:P2在线段a的左边,但实际上发出的射线并没有与线段a相交。考虑线段e,d的情况,即代码中的约束条件。
在线示例:https://codepen.io/jerald/full/OJLOqwv
参考资料
- http://geomalgorithms.com/a03-_inclusion.html