几何-判断点在多边形内部

1 什么是叉乘

a×b=c 其中|c|=|a||b|·sinθ c的方向遵守右手定则
二维向量的叉乘 ( x1 , y1 ) × ( x2 , y2 ) = x1y2 - y1x2 如果值大于0 , 则表明 ( x2 , y2 ) 在 ( x1 , y1 )左边(左旋) ,反之在右边(右旋) ,等于0则意味着两个向量共线。如果不记得可以用(1,0)(0,1)这两个向量来辅助记忆.

下面代码定义了二维坐标系的点,并且mulCross方法返回了向量 papb × papc 的值。

typedef struct s_Point{
    int x;
    int y;
    s_Point(int  _x, int _y){
        x=_x;
        y=_y;
    }
}Point;

int mulCross(Point pa, Point pb , Point pc){
    return (pb.x-pa.x)*(pc.y-pa.y) - (pb.y-pa.y)*(pc.x-pa.x);
}


2 判断一个点是否在凸多边形的内部。

3FA6A606-6B1B-44B4-B482-70D0CA019BF0.png
D1BD086B-EADC-432B-8336-43D4D0AE7147.png

上面两个图分别是点不在凸多边形内部和点在多边形内部的图片,我们沿着凸多边形顶点坐标和目标点做向量向量,若目标点在多边形内部,那么多边形顶点和目标点构成的向量会一直向一个方向旋转,即该顶点和目标点构成的向量和下一个顶点和目标点构成的向量的差集符号保持不变(下图)。

bool checkInRect(Point p[] , Point *pt , vector<int> p_vector){
    vector<int>::iterator ele;
    for (ele = p_vector.begin()+1; ele != p_vector.end(); ele++) {
        if (mulCross(*pt, p[*ele], p[*ele-1]) > 0) {
            return false;
        }
    }
    return true;
}

p_vector 维持着点集的排序的下标,保证遍历的p[i]是紧挨着的 , 会在下面给出解释。

如果点集并不是按照顺序排列

考虑这种情况,那么我们首先应该将点集按逆(顺)时针排序。(这里选择逆时针)

首先选择最左边的点最为基准点P0,设集合S为已经确定位置的点,并且按照逆时针排序 , 然后遍历点集, 以最左边的PkP0(按逆时针旋转的最后一个向量)作为基准向量, 将PiP0和PkP0叉乘,如果PiP0在PkP0左边,则Pi插入到S最后,并且将基准向量变为PiP0 , 如果PiP0在PkP0右边 , 则和集合S中Pk上一个点和P0构成的向量做叉集,直到在S中找到在Pi右边的那个点将pi插入到该点后面或者找到基准点直接插入到基准点后面。

例如上图2

我们 点集合 为p = [6,3,2,5,4,1] (序列和图中对应) 显然p并不是按照逆时针顺序排列

以6为基准点 加入S , 把3也加入S中 , 向量6-3就是基准向量 , S = [6,3]

接着找到2 ,向量6-2在6-3的右边,S = [6,2,3]

接着5 , 在基准左边6-3的左边 , S=[6,2,3,5] 并且基准左边变为6-5;

接着4, 6-4在6-5右边 , 但是在6-3左边 , 所以S= [6,2,3,4,5]

然后1,方法相同 S = [6,1,2,3,4,5]

排序完成了,用一个vector储存排序的下标序列。

vector<int> adjustThePoint( Point p[] , int pnum){
    
    vector<int> p_vector;
    
    if (pnum < 3) {
        return p_vector;
    }
    
    p_vector.push_back(0);
    p_vector.push_back(1);
    
    for (int i = 2; i < pnum; i++) {
        unsigned long int ii = p_vector.size()-1;
        while (ii > 0) {
            if (mulCross(p[0], p[i], p[ii]) < 0) {
                p_vector.insert(p_vector.begin()+ii+1, i);
                break;
            }
            ii--;
        }
        if(!ii){
            p_vector.insert(p_vector.begin()+1, i);
        }
    }
    
   /* vector<int>::iterator ele;
    for (ele = p_vector.begin(); ele != p_vector.end(); ele++) {
        cout << *ele << ' ';
    }*/
    
    return p_vector;
    
}

这样我们使用checkInRect的时候,就可以从p_vector中按照逆时针的顺序取出点。

如果多边形不是凸的

如果四边形不是凸的,那么上面的方法就不适用了 , 应该采用另外一种方法 :射线法

从目标点出发 , 向某一方向发出一条射线 , 如果该射线和四边形交点个数是奇数,则该点在四边形内部,若为偶数,则不在内部。(这里不再证明了)

判断两直线相交

1AFD9A8F-A70D-4587-AFD8-78943BDA0923.png
B576CA2C-2149-4C3F-9617-629EB5BF53E8.png

从上面两个图可以看出 ,当两个直线相交的时候 , 向量P1P2是夹在P1P4和P1P3中间,向量P3P4夹在P4P2和P4P1中间,这就是我们判断两直线相交的依据。

交点在其中一条直线上 和 交点在两条直线的端点上 这两种特殊情况我们应该考虑进去

bool checkCross(Point a, Point b, Point c, Point d ){
    return !(mulCross(a, b, c)*mulCross(a, b, d)<0 || mulCross(c, d, a)*mulCross(c, d, b)<0) ;
}

mulCross是上述的叉乘函数

另外我们要注意是两种特殊的情况

054EEE89-3E79-47FC-9E33-770E199E67B4.png

上述第一左边的图在两条线段上都会记做交点 , 所以我们需要排除他
右边的图也是一样的基准直线和多边形的某边共线, 计算时候交点会变成三个。

所以我们统一成 , 每条边的终点点坐标不算做线段内的点,共线的边也不算相交 ,代码就改写成


bool isCollineation(Point a, Point b, Point c, Point d){ //是否平行
    
    if ( (b.x - a.x) * (d.y - c.y) - (b.y - a.y) * (d.x - c.x) ){
        return 0;
    }else{
        return 1 ;
    }
}

bool checkCross(Point a, Point b, Point c, Point d ){

    if (isCollineation(a, b, c, d)) {
        
        return false;
    }
    
    if ( mulCross(c, d, a) * mulCross(c, d, b) <= 0 ) {
        if ( mulCross(a, b, c) * mulCross(a, b, d) <= 0 && mulCross(a, b, d)!= 0 ) {
            return true;
        }
    }
    
    return false;
    
}
bool checkInRectOther(Point p[] , Point *pt , int pNum){
    
    Point basicP = Point(pt->x,1000);
    int crossNum = 0;
    
    for (int i = 1; i<pNum; i++) {
        if (checkCross(*pt, basicP, p[i-1], p[i])) {
            crossNum++;
        }
    }
    
    return crossNum%2;
}

这样就能判断出点是否在任意多边形内部了,但局限性是点P的数组必须按照顺逆时针排序,因为这样我们才能找到多边形的边。

凸包

上面我们提到了凸多边形的概念,那么对于坐标点我们要怎么样判断其是不是多边形呢?或者从众多点中找到最多点围成的凸多边形呢?

A0872D04-B4BD-43A5-99D7-D26A266A9B62.png

在上图中,当我们按逆时针遍历点集的时候,为什么我们放弃边ab而选择了ab?

**当多边形为凸的时候,按照逆时针方向排列的边向量一定是按照同一个方向旋转。所以当我们发现一个边向量和它上一个边向量旋转方向反向了,我们应该回溯到前一个点。

void maxProtrudeRect(Point p[] , size_t l , int res[]){
 
    res[0] = 0;
    res[1] = 1;
    
    int k = 2;
    int ll = 1;
    
    while ( k <= l ) {
        
        if ( ll == 0 || mulCross(p[res[ll]], p[k%l], p[res[ll-1]]) >= 0 ) {
            ll++ ;
            res[ll] = k;
            k++;
        }else{   
            ll--;
        }
    }
}

为了简单起见 我默认了p[0]这个点是在多边形里面的,关于起始点的选择可以选择y轴最小的。res[]数组里面是最大凸变形点的下标。
如果只是需要判断点集组成的是不是凸多边形,只要在else中直接返回false就可以了。

如果点集并没有按照顺序排列,那么就需要采取别的方法了,另一个排序的方法是找到多边形内部的一个点,作为基准点,和上面的点排序算法几乎一模一样,唯一不同的是我们把基准点选在了内部的某一个点,而不是四边形边的端点。这两种排序方法几乎效果一样,并不能满足到非凸多边形的情况。

另一种思考

从数学的角度,如果一个点在凸多边形内部的话,那么他和每个端点连线线段的夹角相加应该等于360度。
显然这种方法比上面的算法更加复杂一点。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容