您的位置:首页 > 其它

判断点是否在多边形内(2D空间)

2012-10-24 19:42 260 查看
1. 叉乘判别法(只适用于凸多边形)

想象一个凸多边形,其每一个边都将整个2D屏幕划分成为左右两边,连接每一边的第一个端点和要测试的点得到一个矢量v,将两个2维矢量扩展成3维的,然后将该边与v叉乘,判断结果3维矢量中Z分量的符号是否发生变化,进而推导出点是否处于凸多边形内外。这里要注意的是,多边形顶点究竟是左手序还是右手序,这对具体判断方式有影响。

2. 面积判别法(只适用于凸多边形)

第四点分别与三角形的两个点组成的面积分别设为S1,S2,S3,只要S1+S2+S3>原来的三角形面积就不在三角形范围中.可以使用海伦公式 。推广一下是否可以得到面向凸多边形的算法?(不确定)

3. 角度和判别法(适用于任意多边形)

该点与没对相邻顶点的夹角之和为360,则在内,否则在外

4. 水平/垂直交叉点数判别法(适用于任意多边形)

注意到如果从P作水平向左的射线的话,如果P在多边形内部,那么这条射线与多边形的交点必为奇数,如果P在多边形外部,则交点个数必为偶数(0也在内)。所以,我们可以顺序考虑多边形的每条边,求出交点的总个数。还有一些特殊情况要考虑。假如考虑边(P1,P2),

1)如果射线正好穿过P1或者P2,那么这个交点会被算作2次,处理办法是如果P的从坐标与P1,P2中较小的纵坐标相同,则直接忽略这种情况(需要考虑上下尖点问题)

2)如果射线水平,则射线要么与其无交点,要么有无数个,这种情况也直接忽略。

3)如果射线竖直,而P0的横坐标小于P1,P2的横坐标,则必然相交。

4)再判断相交之前,先判断P是否在边(P1,P2)的上面,如果在,则直接得出结论:P再多边形内部。

此处我实现了后两种方式:

#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>

#define MIN_NUMBER (10e-4)

struct MYPOINT
{
    float x;
    float y;
};

float getAngle_AOB(MYPOINT &pA,MYPOINT &pO,MYPOINT &pB);
bool isOnLineAB(MYPOINT &po,MYPOINT &pa,MYPOINT &pb);
bool isInPolygon_crossPoint(const std::vector<MYPOINT> & poly, MYPOINT p);
bool isInPolygon_angle(const std::vector<MYPOINT> & poly, MYPOINT p);

int main(int argc, const char * argv[])
{
    std::vector<MYPOINT> polygon;
    
    MYPOINT p1;
    p1.x = 0;
    p1.y = 0;
    
    MYPOINT p2;
    p2.x = 1;
    p2.y = 5;
    
    MYPOINT p3;
    p3.x = 5;
    p3.y = 5;
    
    MYPOINT p4;
    p4.x = 5;
    p4.y = 1;
    
    // 顺次加入顶点
    polygon.push_back(p1);
    polygon.push_back(p2);
    polygon.push_back(p3);
    polygon.push_back(p4);
    
    MYPOINT curPoint;
    curPoint.x = 5;
    curPoint.y = 5;
    
    if ( isInPolygon_crossPoint(polygon, curPoint) )
    {
        std::cout << "the point is in polygon.\n";
    }
    else
    {
        std::cout << "the point is out of polygon.\n";
    }
    
    if ( isInPolygon_angle(polygon, curPoint) )
    {
        std::cout << "the point is in polygon.\n";
    }
    else
    {
        std::cout << "the point is out of polygon.\n";
    }
    
    
    return 0;
}

/**
 水平/垂直交叉点数判别法(适用于任意多边形)
 @功能:判断点是否在多边形内(多边形顶点需要顺/逆时针顺序排列)
 @方法:求解通过该点的水平线与多边形各边的交点
 @结论:单边交点为奇数,成立!(右侧交点)
 */
bool isInPolygon_crossPoint(const std::vector<MYPOINT> & poly, MYPOINT p)
{
    int nCross = 0;
    bool isOnLine = false;
    int nCount = (int)poly.size();

    for (int i = 0; i < nCount; i++)
    {
        MYPOINT p1 = poly[i];
        MYPOINT p2 = poly[(i + 1) % nCount];
        
        // 首先判断是否在线段上
        if (isOnLineAB(p, p1, p2))
        {
            isOnLine = true;
            break;
        }
        // 求解 y=p.y 与 p1p2 的交点
        if ( p1.y == p2.y ) // p1p2 与 y=p0.y平行
            continue; 
        if ( p.y < fminf(p1.y, p2.y) ) // 交点在p1p2延长线上,或端点上
            continue;
        if ( p.y > fmaxf(p1.y, p2.y) ) // 交点在p1p2延长线上,或端点上
            continue;
        
        // 求交点的 X 坐标 
        float x = (p.y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y) + p1.x;
        
        // p点在线段p1p2上,即:在多边形边界上
        if ( fabsf(p.x-x) < MIN_NUMBER )
        {
            isOnLine = true;
            break;
        }
        // 交点在p1/p2上,忽略一次
        if (fabsf(p1.y-p.y) < MIN_NUMBER)
        {
            // 考虑点p 在 上尖点、下尖点 的正右侧 情况
            float maxY = poly[0].y;
            float minY = poly[0].y;
            for (int i=1; i<nCount; ++i)
            {
                if (maxY<poly[i].y)
                {
                    maxY = poly[i].y;
                }
                if (minY>poly[i].y)
                {
                    minY = poly[i].y;
                }
            }
            
            if ( (minY==p1.y || maxY==p1.y) && p.x<p1.x)
            {
                isOnLine = false;
                break;
            }
            
            
            continue; 
        }
        // 只统计单边交点 : 统计右侧交点
        if ( x > p.x )
        {
            nCross++; 
        }
        
        
    } // end loop
    
    // 点在边上;单边交点为偶数,则点在多边形外
    if ( isOnLine || (nCross % 2 == 1) )
    {
        return true;
    }
    
    return false;
    
    
}

/**
 角度和判别法(适用于任意多边形)
 计算这个点与多边形的点的连线的夹角之和,角度和为360度在内部,否则在外部。
 */
bool isInPolygon_angle(const std::vector<MYPOINT> & poly, MYPOINT p)
{
    float sumAngle = 0;
    bool isOnLine =false;
    
    int nCount = (int)poly.size();
    for (int i=0; i<nCount; ++i)
    {
        MYPOINT p1 = poly[i];
        MYPOINT p2 = poly[(i + 1) % nCount];
        
        // 首先判断是否在线段上
        if (isOnLineAB(p, p1, p2))
        {
            isOnLine = true;
            break;
        }
        
        sumAngle += getAngle_AOB(p1, p, p2);
    }
    
    //如果角度和等于360度则在内部,否则在外部 / 在多边形上
    if( isOnLine || (fabsf(sumAngle - M_PI * 2) < MIN_NUMBER) )
    {
        return true;
    }
    
    return false;
}

/**求 AOB 的角度*/
float getAngle_AOB(MYPOINT &pA,MYPOINT &pO,MYPOINT &pB)
{
    float oa = sqrtf( powf((pA.y - pO.y),2) + powf((pA.x - pO.x),2) );
    float ob = sqrtf( powf((pB.y - pO.y),2) + powf((pB.x - pO.x),2) );
    float ab = sqrtf( powf((pA.y - pB.y),2) + powf((pA.x - pB.x),2) );
    
    float cos_AOB = (oa*oa + ob*ob - ab*ab) / (2 * oa * ob);
    
    return acosf(cos_AOB);;
}

/** 
 @检测点po是否在线段AB上
 
 判断二维坐标系中是否三个点在一条直线上:
 {
 area(ABC) = 1/2 * ( AC X BC )  = 1/2 *((ax-cx)*(by-cy)-(bx-cx)*(ay-cy))
 判断 (ax-cx)*(by-cy) == (bx-cx)*(ay-cy) 即可。
 AC X BC   为两矢量的叉积
 }
 
 */
bool isOnLineAB(MYPOINT &po,MYPOINT &pa,MYPOINT &pb)
{
    if ( po.x>=fminf(pa.x, pb.x)
        && po.x<=fmaxf(pa.x, pb.x)
        && po.y>=fminf(pa.y, pb.y)
        && po.y<=fmaxf(pa.y, pb.y)
        )
    {
        float area1 = (pa.x-po.x)*(pb.y-po.y);
        float area2 = (pb.x-po.x)*(pa.y-po.y);
        
        if ( fabsf(area1-area2)< MIN_NUMBER )
        {
            return true;
        }
        return false;
    }
    else
        return false;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: