多边形凹凸性的判断、自相交判断

说明


该博文参考 弱花3kou 的文章 [OpenGL] 绘制并且判断凹凸多边形、自相交多边形

分析


  • 凸多边形

在这里插入图片描述

  • 凹多边形

在这里插入图片描述

  • 自相交

在这里插入图片描述

代码


#include <iostream>
#include <ctime>
#include <math.h>
#include <vector>

using namespace std;

// 定义点
struct Pos {
    int x;
    int y;
};

// 定义边
struct Edge {
    int x1, x2;
    int y1, y2;
    int vx;
    int vy;
    int a, b, c;
};

// 定义多边形
struct Poly {
    // 点集
    int xx[100];
    int yy[100];

    // 边集
    Edge Edges[100];

    int plotNums = 0; //点数量
    int edgeNums = 0; //边数量

    // 记录凹点
    int conv = 0;
};

Poly poly;


// 求交点坐标,这里的A、B、C是直线方程 Ax + By + C = 0 的参数,求交点是利用向量叉乘计算得到的
// 具体可查看博客:用叉积求二维直线交点
// https://www.jianshu.com/p/3468c9967fc7
Pos CrossPos(int p1, int p2) {
    Pos res;
    int A1 = poly.Edges[p1].a;
    int B1 = poly.Edges[p1].b;
    int A2 = poly.Edges[p2].a;
    int B2 = poly.Edges[p2].b;
    int C1 = poly.Edges[p1].c;
    int C2 = poly.Edges[p2].c;

    int m = A1 * B2 - A2 * B1;
    if (m == 0)
        cout << "第" << p1 << "边和第" << p2 << "边" << "无交点" << endl;
    else {
        res.x = (C2 * B1 - C1 * B2) / m;
        res.y = (C1 * A2 - C2 * A1) / m;
    }
    return res;

}

// 判断点是否在线段内,意思就是判断点是否在矩形框内
bool JudgeInLine(Pos pos, Edge edge) {
    int maxX = edge.x1 >= edge.x2 ? edge.x1 : edge.x2;
    int minX = edge.x1 <= edge.x2 ? edge.x1 : edge.x2;
    int maxY = edge.y1 >= edge.y2 ? edge.y1 : edge.y2;
    int minY = edge.y1 <= edge.y2 ? edge.y1 : edge.y2;
    if (pos.x <= maxX && pos.x >= minX && pos.y <= maxY && pos.y >= minY) {
        return true;
    }
    return false;
}

// 求叉积 返回正负值
int CrossProduct(int n) {
    n = n % poly.edgeNums;
    int np = (n + 1) % poly.edgeNums;
    return (poly.Edges[n].vx * poly.Edges[np].vy - poly.Edges[np].vx * poly.Edges[n].vy) >= 0 ? 1 : -1;
}


// 判断是什么多边形
bool Judge() {
    /*输出边信息*/
    for (int i = 0; i < poly.edgeNums; i++) {
        cout << "Vx:" << poly.Edges[i].vx << "  " << "Vy:" << poly.Edges[i].vy << "  " << "A:" << poly.Edges[i].a << "  " << "B:" << poly.Edges[i].b << "  " << "C:" << poly.Edges[i].c << endl;
    }

    /*判断自交*/
    Pos interPos;
    if (poly.edgeNums > 3)
        for (int i = 0; i < poly.edgeNums; i++) {
            interPos = CrossPos(i, (i + 2) % poly.edgeNums);
            if (JudgeInLine(interPos, poly.Edges[i]) && JudgeInLine(interPos, poly.Edges[(i + 2) % poly.edgeNums])) {
                cout << "该多边形为自相交多边形" << endl;
                return false;
            }
        }

    /*判断凹凸*/
    // 判断向量叉积 是否为同一正负
    int judge;
    if (CrossProduct(0) >= 0)
        judge = 1;
    else
        judge = -1;
    //判断每一个角,两边向量乘积是否同符号
    for (int i = 1; i <= poly.edgeNums; i++) {
        if (judge * CrossProduct(i) < 0) {
            poly.conv = i;
            ChangePoly();
            cout << "该多边形为凹多边形" << endl;
            return false;
        }
    }
    cout << "该多边形为凸多边形" << endl;
    return true;
}

关于自相交的理解


  • 在原博客中,自相交判断利用的是 点与线段的位置关系,也就是 线段与 X 和 Y 轴形成的区域 是否有交集来进行判断

  • 原博客中,它是先求两条直线的交点,然后判断 该交点是否同时位于两个线段所构成的矩形内

值得注意的是,这里是对 与其不相邻的所有边进行比较,也就是说,假如一个复杂凸多边形一共有 N 条边,且当前边为 i,那么其要与 [0, i - 2] ∪ [i + 2, N] 的边进行比较判断

看图说话


基于此,我绘制了以下两个图来说明情况

例1: 在下图中,对于左侧图形来讲(局部),对于红色边,因为其不与相邻边判断,所以不和绿色边判断,但是需要和蓝色边判断,通过直线求交可以得到玫红色的交点,可以看出,玫红色的交点没有落在红色和蓝色区域,那么就 不是自相交

在这里插入图片描述

例2: 再换一个图形,此时玫红色的交点只落在蓝色区域,没有落在红色区域,同样也 不是自相交

在这里插入图片描述

例3: 但是对于下图,玫红色交点同时落在红色和蓝色区域,那么就形成了自相交

在这里插入图片描述

现在你应当对自相交的判断有了一定解,如果你还不了解,你品,你细品,你细细品 …

上面 3 个例子描述的是局部情况,下面我们看一个完整的例子

原图如下:

在这里插入图片描述

  • 我们从红边开始,按照顺时针方向进行判断,红色边只和蓝色、黄色、紫色边判断
    在这里插入图片描述

  • 再判断绿色边,绿色边只和黄色、紫色、灰色边判断
    ...

  • 再判断蓝色边,蓝色边只和紫色、灰色、红色边判断
    在这里插入图片描述

over

  • 15
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
判断多边形是否自相交可以采用射线法、三角剖分法等多种方法,这里介绍一种基于求解交点的方法。 假设有一个简单多边形,可以将其顶点按逆时针顺序排列。对于任意两条边 $AB$ 和 $CD$,如果它们不共线且相交,则交点 $P$ 必定在边 $AB$ 和边 $CD$ 的延长线上。因此,可以将多边形的每条边看作一个线段,依次检查每对线段是否相交,如果相交,则判断是否在延长线上。如果存在交点且在延长线上,则说明多边形相交。 具体实现步骤如下: 1. 构造线段类,包含起点和终点坐标。 2. 对于多边形的每条边 $AB$,依次检查它和后面的每条边 $CD$ 是否相交。 3. 如果 $AB$ 和 $CD$ 不共线,则计算它们的交点,判断交点是否在 $AB$ 和 $CD$ 的延长线上。 4. 如果存在交点且在延长线上,则说明多边形相交。 代码实现如下(以C++为例): ```cpp #include <iostream> #include <vector> using namespace std; // 线段类 class Segment { public: int x1, y1, x2, y2; Segment(int a, int b, int c, int d) : x1(a), y1(b), x2(c), y2(d) {} }; // 判断两条线段是否相交 bool isIntersect(Segment s1, Segment s2) { int x1 = s1.x1, y1 = s1.y1, x2 = s1.x2, y2 = s1.y2; int x3 = s2.x1, y3 = s2.y1, x4 = s2.x2, y4 = s2.y2; int d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); if (d == 0) return false; // 两线段平行 double ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) * 1.0 / d; double ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) * 1.0 / d; if (ua < 0 || ua > 1 || ub < 0 || ub > 1) return false; // 交点不在线段上 return true; } // 判断多边形是否自相交 bool isSelfIntersect(vector<Segment>& segments) { int n = segments.size(); for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (isIntersect(segments[i], segments[j])) { // 计算交点 int x1 = segments[i].x1, y1 = segments[i].y1, x2 = segments[i].x2, y2 = segments[i].y2; int x3 = segments[j].x1, y3 = segments[j].y1, x4 = segments[j].x2, y4 = segments[j].y2; int x = ((x4 - x3) * (x2 - x1) * (y1 - y3) + y3 * (x2 - x1) * (x4 - x3) - y1 * (x4 - x3) * (x2 - x1)) / ((y4 - y3) * (x2 - x1) - (y2 - y1) * (x4 - x3)); int y = ((y4 - y3) * (y2 - y1) * (x1 - x3) + x3 * (y2 - y1) * (y4 - y3) - x1 * (y4 - y3) * (y2 - y1)) / ((x4 - x3) * (y2 - y1) - (x2 - x1) * (y4 - y3)); // 判断交点是否在延长线上 if ((x < x1 || x > x2) && (x < x2 || x > x1) && (x < x3 || x > x4) && (x < x4 || x > x3)) continue; if ((y < y1 || y > y2) && (y < y2 || y > y1) && (y < y3 || y > y4) && (y < y4 || y > y3)) continue; return true; } } } return false; } int main() { // 构造多边形(按逆时针顺序) vector<Segment> segments; segments.emplace_back(0, 0, 2, 0); segments.emplace_back(2, 0, 2, 2); segments.emplace_back(2, 2, 1, 1); segments.emplace_back(1, 1, 0, 2); segments.emplace_back(0, 2, 0, 0); // 判断多边形是否自相交 if (isSelfIntersect(segments)) { cout << "The polygon is self-intersect." << endl; } else { cout << "The polygon is simple." << endl; } return 0; } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值