判断一个点是否在任意闭合多边形内

看了 https://sidvind.com/wiki/Point-in-polygon:_Jordan_Curve_Theorem 这个地址里面的算法。


Point-in-polygon: Jordan Curve Theorem

Calculating whenever a point is inside a polygon can sometimes be a hard and costly calculation. This article describes a quite cheap solution to calculate whenever a point is inside ANY closed polygon. In an open polygon it's hard to determine what's in and out so naturally it won't work.

A closed polygon with 3 points marked

The Jordan Curve Theorem states that a point is inside a polygon if the number of crossings from an arbitrary direction is odd. An image explains more than a thousand words so lets take a look at the picture. As you can see point 1 and 3 is inside the polygon but point 2 isn't. Follow the rays from each point and count each time you cross a line-segment. In this article I only deal with 2D polygons but it can easily used in a 3D-environment.

Contents

Find crossings

Casting a ray

One of the first things to do is to cast a ray from the point in an arbitrary direction. I use a ray along the Y axis (pointing upwards as in the picture) for simplicity. Along X-axis is good too, but use one of those or it gets a lot harder. Remember that I use the ray mentioned above throughout this article.

Finding the equation of a line-segment

As a first step, if the ray is along y-axis, check if the point x-coordinate is between the two points connecting the line. If not it don't cross it either. You can also check if the y-coordinate is above both points. The next step is to find the equation of the line. Hopefully you remember this from grade school. The equation of a straight line is y=kx+m (Swedish notation). The slope is k=Δ yΔ x and offset is m=ykx . Do the math we have the equation. Now, insert the x-coordinate of the point into the equation. If the result is larger than the y-coordinate the ray does not cross the line-segment.

Repeat this for each line-segment.

C/C++ implementation

Code: Sample (naive) implementation

  1. /* The points creating the polygon. */
  2. float x [ 8 ] ;
  3. float y [ 8 ] ;
  4. float x1,x2 ;
  5.  
  6. /* The coordinates of the point */
  7. float px, py ;
  8.  
  9. /* How many times the ray crosses a line-segment */
  10. int crossings = 0 ;
  11.  
  12. /* Coordinates of the points */
  13. x [ 0 ] = 100 ;     y [ 0 ] = 100 ;
  14. x [ 1 ] = 200 ;     y [ 1 ] = 200 ;
  15. x [ 2 ] = 300 ;     y [ 2 ] = 200 ;
  16. x [ 3 ] = 300 ;     y [ 3 ] = 170 ;
  17. x [ 4 ] = 240 ;     y [ 4 ] = 170 ;
  18. x [ 5 ] = 240 ;     y [ 5 ] = 90 ;
  19. x [ 6 ] = 330 ;     y [ 6 ] = 140 ;
  20. x [ 7 ] = 270 ;     y [ 7 ] = 30 ;
  21.  
  22. /* Iterate through each line */
  23. for ( int i = 0 ; i < 8 ; i ++ ) {
  24.        
  25.         /* This is done to ensure that we get the same result when
  26.            the line goes from left to right and right to left */
  27.         if ( x [i ] < x [ (i + 1 ) % 8 ] ) {
  28.                 x1 = x [i ] ;
  29.                 x2 = x [ (i + 1 ) % 8 ] ;
  30.         } else {
  31.                 x1 = x [ (i + 1 ) % 8 ] ;
  32.                 x2 = x [i ] ;
  33.         }
  34.        
  35.         /* First check if the ray is possible to cross the line */
  36.         if ( px > x1 && px <= x2 && ( py < y [i ] || py <= y [ (i + 1 ) % 8 ] ) ) {
  37.                 static const float eps = 0.000001 ;
  38.  
  39.                 /* Calculate the equation of the line */
  40.                 float dx = x [ (i + 1 ) % 8 ] - x [i ] ;
  41.                 float dy = y [ (i + 1 ) % 8 ] - y [i ] ;
  42.                 float k ;
  43.  
  44.                 if ( fabs (dx ) < eps ) {
  45.                         k = INFINITY ;   // math.h
  46.                 } else {
  47.                         k = dy /dx ;
  48.                 }
  49.  
  50.                 float m = y [i ] - k * x [i ] ;
  51.                
  52.                 /* Find if the ray crosses the line */
  53.                 float y2 = k * px + m ;
  54.                 if ( py <= y2 ) {
  55.                         crossings ++ ;
  56.                 }
  57.         }
  58. }
  59.  
  60. printf ( "The point is crossing %d lines", crossings ) ;
  61. if ( crossings % 2 == 1 ) {
  62.         printf ( " thus it is inside the polygon" ) ;
  63. }
  64. printf ( "\n" ) ;

判断一个点是否位于多边形内部是一个常见的计算机图形学任务,尤其在绘制地图、路径规划等领域有广泛应用。这里提供一种基于“奇偶交线规则”的简单算法解释。 ### 算法步骤: #### 1. **确定方向**: 首先需要确定多边形的方向。对于任意两个相邻顶点形成的向量 `v1 = (x2 - x1, y2 - y1)` 和 `v2 = (x3 - x2, y3 - y2)`,如果 `(y1 * v2.x + y2 * v1.x) < (x1 * v2.y + x2 * v1.y)`,则这条边与正方向相逆;反之,则与正方向相同。 #### 2. **计算交叉数**: 遍历多边形的所有边界线段,对每个线段判断它与给定点的连线是否穿过这个线段。可以通过计算线段两端点与给定点之间的叉积来判断这一点是否在该线段的左侧、右侧还是在线段上。公式为: \[ A \times B = (x_A - x_B)(y_C - y_D) - (y_A - y_B)(x_C - x_D) \] 其中,`A` 表示线段的一个端点,`B` 表示另一个端点,而 `C` 和 `D` 分别表示给定点和平行于线段的任一点。如果 `A \times B` 的值的绝对值大于某个非常小的正值(避免浮点数比较),说明从 `A` 看到 `B`,通过了边界线段的左边。否则,判断为右边。 #### 3. **统计交叉次数**: 如果多边形足够复杂以至于包含一些闭合的环路或者自交的部分,那么需要额外处理。一般来说,对于非闭合多边形没有自交的情况,我们只关心点到每条线段的实际穿过的次数,即奇数次表示点在多边形内部,偶数次表示外部。 #### 4. **最终判定**: 累加所有线段的交叉数,并检查结果是否为奇数。如果是,说明点在多边形内部;如果不是,则点在多边形外部。 ### TypeScript 实现示例: 下面提供了一个简单的 TypeScript 示例函数,用于判断是否多边形内: ```typescript function isPointInPolygon(x0: number, y0: number, vertices: [number, number][]): boolean { let inside = false; for (let i = 0; i < vertices.length; i++) { const j = (i + 1) % vertices.length; if ((vertices[i] > y0 && vertices[j][1] <= y0 || vertices[j] > y0 && vertices[i] <= y0) && (vertices[i] + (y0 - vertices[i]) / (vertices[j] - vertices[i]) * (vertices[j] - vertices[i]) < x0)) { inside = !inside; } } return inside; } // 使用示例 const polygonVertices = [[0, 0], [5, 0], [5, 5], [0, 5]]; const pointX = 3; const pointY = 3; console.log(isPointInPolygon(pointX, pointY, polygonVertices)); // 输出 true 或 false ``` ### 相关问题: 1. 这种算法如何应对复杂的多边形,如包含自我交叉的部分? 2. 当多边形由大量的顶点组成时,性能优化策略有哪些? 3. 在实际应用中,还有哪些其他常用的算法或库可以用来解决点在多边形内的判断问题? 这种算法适用于多数情况,但在某些特殊场景下可能会遇到精度问题或性能瓶颈,例如处理大量顶点或多边形自交的情况,这时可能需要更高级的几何算法或专门的图绘库来辅助处理。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值