自学内容网 自学内容网

计算机图形学:多边形交点及实现

一、原理推导和公式说明

1.1 两条线段求交的数学原理

设有两条线段 A B AB AB C D CD CD,它们的端点坐标分别为 A ( x a , y a ) A(x_a, y_a) A(xa,ya), B ( x b , y b ) B(x_b, y_b) B(xb,yb) C ( x c , y c ) C(x_c, y_c) C(xc,yc), D ( x d , y d ) D(x_d, y_d) D(xd,yd)。要找出这两条线段是否相交,我们可以使用参数方程和行列式的概念。

首先,

  • 线段 A B AB AB 的方程:
    { x = x a + t ( x b − x a ) y = y a + t ( y b − y a ) \begin{cases} x = x_a + t(x_b - x_a) \\ y = y_a + t(y_b - y_a) \end{cases} {x=xa+t(xbxa)y=ya+t(ybya)
    其中, t t t [ 0 , 1 ] [0, 1] [0,1] 范围内变化,表示从点 A A A 到点 B B B 的相对位置。

  • 线段 C D CD CD 的方程:
    { x = x c + u ( x d − x c ) y = y c + u ( y d − y c ) \begin{cases} x = x_c + u(x_d - x_c) \\ y = y_c + u(y_d - y_c) \end{cases} {x=xc+u(xdxc)y=yc+u(ydyc)
    其中, u u u [ 0 , 1 ] [0, 1] [0,1] 范围内变化,表示从点 C C C 到点 D D D 的相对位置。

要使两个方程组表示同一个点,则交点的参数应满足:

{ x a + t ( x b − x a ) = x c + u ( x d − x c ) y a + t ( y b − y a ) = y c + u ( y d − y c ) \begin{cases} x_a + t(x_b - x_a) = x_c + u(x_d - x_c) \\ y_a + t(y_b - y_a) = y_c + u(y_d - y_c) \end{cases} {xa+t(xbxa)=xc+u(xdxc)ya+t(ybya)=yc+u(ydyc)
整理后得
{ t ( x b − x a ) − u ( x d − x c ) = x c − x a t ( y b − y a ) − u ( y d − y c ) = y c − y a \begin{cases} t(x_b-x_a)-u(x_d-x_c)=x_c-x_a \\ t(y_b-y_a)-u(y_d-y_c)=y_c-y_a \end{cases} {t(xbxa)u(xdxc)=xcxat(ybya)u(ydyc)=ycya
因此,对于行列式
d = ∣ x b − x a x d − x c y b − y a y d − y c ∣ d = \begin{vmatrix} x_b - x_a & x_d - x_c \\ y_b - y_a & y_d - y_c \end{vmatrix} d= xbxaybyaxdxcydyc
有以下结论:

  • d = 0 d=0 d=0,则 A B AB AB C D CD CD重合或者平行。

  • d ≠ 0 d \neq 0 d=0,则线段 A B AB AB C D CD CD不平行也不重合,有可能相交。求出两个线段所在的两条直线上交点对应的两个参数 t , u t,u t,u值:
    t = ∣ x c − x a x d − x a y c − y a y d − y a ∣ d u = ∣ x b − x a x b − x c y b − y a y b − y c ∣ d t = \frac{\begin{vmatrix} x_c - x_a & x_d - x_a \\ y_c - y_a & y_d - y_a \end{vmatrix}}{\text{d}}\\ u = \frac{\begin{vmatrix} x_b - x_a & x_b - x_c \\ y_b - y_a & y_b - y_c \end{vmatrix}}{\text{d}} t=d xcxaycyaxdxaydya u=d xbxaybyaxbxcybyc
    如果 t t t u u u都在 [ 0 , 1 ] [0, 1] [0,1]区间内,那么交点位于两条线段上,线段相交;否则,交点在两线段或其中某一条线段的延长线上,这时仍然认为是两线段不相交。

1.2 推导过程

  1. 将线段表示为参数方程。
  2. 构造参数 t t t u u u的方程组。
  3. 通过行列式方法消去参数 t t t u u u,得到交点的坐标。
  4. 检查 t t t u u u是否在 [ 0 , 1 ] [0, 1] [0,1] 区间内,以确定交点是否在两条线段上。

这就是两条线段求交的数学原理和公式推导。如果行列式 d d d为零,或者计算得到的 t t t u u u不在 [0, 1] 区间内,那么线段不相交。

二、代码设计与实现

2.1 点结构和多边形类定义

首先,定义了Point结构体来表示二维空间中的点,包含xy两个坐标成员。

struct Point {
  double x, y;
};

Polygon类用于表示多边形,它包含一个vertices向量,存储多边形的顶点。类中还包含getEdge方法,用于获取多边形的某条边,以及getNumberOfEdges方法,返回多边形边的数量。

class Polygon {
public:
  std::vector<Point> vertices;

  Polygon(const std::vector<Point> &points) : vertices(points) {}

  std::pair<Point, Point> getEdge(int i) const {
    int nextIndex = (i + 1) % vertices.size();
    return {vertices[i], vertices[nextIndex]};
  }

  int getNumberOfEdges() const { return vertices.size(); }
};

2.2 线段交点的计算

两条线段的交点可以通过解线性方程组得到。首先,通过计算行列式来判断线段是否平行或共线:

d = ( B . x − A . x ) ⋅ ( C . y − D . y ) − ( B . y − A . y ) ⋅ ( C . x − D . x ) d = (B.x - A.x) \cdot (C.y - D.y) - (B.y - A.y) \cdot (C.x - D.x) d=(B.xA.x)(C.yD.y)(B.yA.y)(C.xD.x)
如果d的绝对值小于一个很小的阈值(例如1e-6),则认为线段平行或共线,没有交点。

如果线段不平行,可以通过求解以下方程得到交点参数tu
t = ( C . x − A . x ) ⋅ ( C . y − D . y ) − ( C . y − A . y ) ⋅ ( C . x − D . x ) d u = ( B . x − A . x ) ⋅ ( C . y − A . y ) − ( B . y − A . y ) ⋅ ( C . x − A . x ) d t = \frac{(C.x - A.x) \cdot (C.y - D.y) - (C.y - A.y) \cdot (C.x - D.x)}{d}\\ u = \frac{(B.x - A.x) \cdot (C.y - A.y) - (B.y - A.y) \cdot (C.x - A.x)}{d} t=d(C.xA.x)(C.yD.y)(C.yA.y)(C.xD.x)u=d(B.xA.x)(C.yA.y)(B.yA.y)(C.xA.x)
如果tu都在区间[0, 1]内,则交点在线段上:
intersection . x = A . x + t ⋅ ( B . x − A . x ) intersection . y = A . y + t ⋅ ( B . y − A . y ) \text{intersection}.x = A.x + t \cdot (B.x - A.x) \\ \text{intersection}.y = A.y + t \cdot (B.y - A.y) intersection.x=A.x+t(B.xA.x)intersection.y=A.y+t(B.yA.y)

bool getLineSegmentIntersection(const Point &A, const Point &B, const Point &C,
                                const Point &D, Point &intersection) {
  double d = (B.x - A.x) * (C.y - D.y) - (B.y - A.y) * (C.x - D.x);
  if (std::abs(d) < 1e-6) {
    return false;
  }

  double t = ((C.x - A.x) * (C.y - D.y) - (C.y - A.y) * (C.x - D.x)) / d;
  double u = ((B.x - A.x) * (C.y - A.y) - (B.y - A.y) * (C.x - A.x)) / d;

  if (t >= 0.0 && t <= 1.0 && u >= 0.0 && u <= 1.0) {
    intersection.x = A.x + t * (B.x - A.x);
    intersection.y = A.y + t * (B.y - A.y);
    return true;
  }

  return false;
}

2.3 多边形交点的计算

对于两个多边形,程序遍历每条边的组合,使用getLineSegmentIntersection函数检查是否有交点。

std::vector<Point> getPolygonIntersections(const Polygon &poly_a,
                                           const Polygon &poly_b) {
  std::vector<Point> intersections;
  for (int i = 0; i < poly_a.getNumberOfEdges(); ++i) {
    for (int j = 0; j < poly_b.getNumberOfEdges(); ++j) {
      Point intersection;
      if (getLineSegmentIntersection(
              poly_a.getEdge(i).first, poly_a.getEdge(i).second,
              poly_b.getEdge(j).first, poly_b.getEdge(j).second, intersection)) {
          intersections.push_back(intersection);
      }
    }
  }
  return intersections;
}

三、完整代码示例

主函数创建了两个多边形poly_apoly_b,并调用getPolygonIntersections函数来计算它们的交点。然后,输出交点信息。

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

// 定义一个表示二维点的结构体
struct Point {
  double x, y;
};

// 定义一个表示多边形的类
class Polygon {
public:
  std::vector<Point> vertices; // 存储多边形顶点的向量

  // 构造函数,接受一个点的向量来初始化多边形的顶点
  Polygon(const std::vector<Point> &points) : vertices(points) {}

  // 获取多边形的第i条边,返回一个包含两个端点的pair
  std::pair<Point, Point> getEdge(int i) const {
    int nextIndex = (i + 1) % vertices.size(); // 计算下一条边的索引,实现循环
    return {vertices[i], vertices[nextIndex]}; // 返回当前边的两个端点
  }

  // 获取多边形的边数
  int getNumberOfEdges() const { return vertices.size(); }
};

// 计算两条线段AB和CD的交点
bool getLineSegmentIntersection(const Point &A, const Point &B, const Point &C,
                                const Point &D, Point &intersection) {
  double d = (B.x - A.x) * (C.y - D.y) - (B.y - A.y) * (C.x - D.x);
  if (std::abs(d) < 1e-6) {
    // 如果行列式的绝对值很小,则认为线段平行或共线
    return false;
  }

  // 计算交点参数t和u
  double t = ((C.x - A.x) * (C.y - D.y) - (C.y - A.y) * (C.x - D.x)) / d;
  double u = ((B.x - A.x) * (C.y - A.y) - (B.y - A.y) * (C.x - A.x)) / d;

  // 检查交点是否在两条线段上
  if (t >= 0.0 && t <= 1.0 && u >= 0.0 && u <= 1.0) {
    // 如果t和u都在[0, 1]区间内,则交点在线段上
    intersection.x = A.x + t * (B.x - A.x);
    intersection.y = A.y + t * (B.y - A.y);
    return true;
  }

  // 如果交点不在线段上,则返回false
  return false;
}

// 计算两个多边形的所有交点
std::vector<Point> getPolygonIntersections(const Polygon &poly_a,
                                           const Polygon &poly_b) {
  std::vector<Point> intersections;
  for (int i = 0; i < poly_a.getNumberOfEdges(); ++i) {
    for (int j = 0; j < poly_b.getNumberOfEdges(); ++j) {
      Point intersection;
      if (getLineSegmentIntersection(poly_a.getEdge(i).first,
                                     poly_a.getEdge(i).second,
                                     poly_b.getEdge(j).first,
                                     poly_b.getEdge(j).second, intersection)) {
        intersections.push_back(intersection); // 将交点添加到结果中
      }
    }
  }
  return intersections; // 返回所有交点
}

// 主函数
int main() {
  // 定义两个多边形
  Polygon poly_a({{0, 0}, {0, 4}, {-4, 4}, {-4, 0}});
  Polygon poly_b({{-2, 2}, {-2, -2}, {2, -2}, {2, 2}});

  // 计算两个多边形的交点
  auto intersections = getPolygonIntersections(poly_a, poly_b);

  // 输出交点信息
  if (intersections.empty()) {
    std::cout << "No intersection" << std::endl;
  } else {
    std::cout << "Intersections:" << std::endl;
    for (const auto &pt : intersections) {
      std::cout << "(" << pt.x << ", " << pt.y << ")" << std::endl;
    }
  }
  return 0;
}

原文地址:https://blog.csdn.net/jianmo1993/article/details/144063056

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!