如何找到一条直线和一个矩形之间的交点?

68

我有一条从点 A 到点 B 的直线;我知道这两个点的坐标 (x,y)。我还有一个以点 B 为中心、宽和高已知的矩形。

我需要找到这条直线与矩形相交的点的坐标 (x,y)。是否有一个公式可以给出这个点的坐标?


3
我们可以假设矩形与坐标轴对齐而且没有倾斜吗? - Grandpa
20
对于那些想要关闭此问题的人:传统上,我们允许这种数学问题作为编程问题的近似,因为它们在真实的编程和编程教育中都很常见。我会关注的是这些问题是否有可能是重复的。请注意,我只是翻译,不会添加解释或其他内容。 - dmckee --- ex-moderator kitten
14个回答

2
另一个选项是,如果您计划测试许多与同一矩形相交的线条,可以将坐标系转换为轴与矩形对角线对齐。然后,由于您的线条或射线从矩形的中心开始,因此您可以确定角度,然后通过角度告诉它将与哪个线段相交(即<90度为线段1,90度< <180度为线段2等)。然后,当然,您必须转换回原始坐标系。
虽然这似乎更费力,但变换矩阵及其逆矩阵可以计算一次,然后重复使用。这也更容易地扩展到更高维度的矩形中,在三维空间中需要考虑象限和面的相交等问题。

1
这是一种略显冗长的方法,仅使用基本数学运算返回一条(无限)直线和一个矩形之间的交集区间:
// Line2      - 2D line with origin (= offset from 0,0) and direction
// Rectangle2 - 2D rectangle by min and max points
// Contacts   - Stores entry and exit times of a line through a convex shape

Contacts findContacts(const Line2 &line, const Rectangle2 &rect) {
  Contacts contacts;

  // If the line is not parallel to the Y axis, find out when it will cross
  // the limits of the rectangle horizontally
  if(line.Direction.X != 0.0f) {
    float leftTouch = (rect.Min.X - line.Origin.X) / line.Direction.X;
    float rightTouch = (rect.Max.X - line.Origin.X) / line.Direction.X;
    contacts.Entry = std::fmin(leftTouch, rightTouch);
    contacts.Exit = std::fmax(leftTouch, rightTouch);
  } else if((line.Offset.X < rect.Min.X) || (line.Offset.X >= rect.Max.X)) {
    return Contacts::None; // Rectangle missed by vertical line
  }

  // If the line is not parallel to the X axis, find out when it will cross
  // the limits of the rectangle vertically
  if(line.Direction.Y != 0.0f) {
    float topTouch = (rectangle.Min.Y - line.Offset.Y) / line.Direction.Y;
    float bottomTouch = (rectangle.Max.Y - line.Offset.Y) / line.Direction.Y;

    // If the line is parallel to the Y axis (and it goes through
    // the rectangle), only the Y axis needs to be taken into account.
    if(line.Direction.X == 0.0f) {
      contacts.Entry = std::fmin(topTouch, bottomTouch);
      contacts.Exit = std::fmax(topTouch, bottomTouch);
    } else {
      float verticalEntry = std::fmin(topTouch, bottomTouch);
      float verticalExit = std::fmax(topTouch, bottomTouch);

      // If the line already left the rectangle on one axis before entering it
      // on the other, it has missed the rectangle.
      if((verticalExit < contacts.Entry) || (contacts.Exit < verticalEntry)) {
        return Contacts::None;
      }

      // Restrict the intervals from the X axis of the rectangle to where
      // the line is also within the limits of the rectangle on the Y axis
      contacts.Entry = std::fmax(verticalEntry, contacts.Entry);
      contacts.Exit = std::fmin(verticalExit, contacts.Exit);
    }
  } else if((line.Offset.Y < rect.Min.Y) || (line.Offset.Y > rect.Max.Y)) {
    return Contacts::None; // Rectangle missed by horizontal line
  }

  return contacts;
}

这种方法提供了高度的数值稳定性(在所有情况下,间隔都是单个减法和除法的结果),但涉及一些分支。
对于线段(具有起点和终点),您需要将线段的起点作为原点,并将方向设为end - start。计算两个交点的坐标只需使用entryPoint = origin + direction * contacts.EntryexitPoint = origin + direction * contacts.Exit即可。

1

我不知道这是否是最好的方法,但你可以计算出线段在矩形内部的比例。你可以通过矩形的宽度和点A和B的x坐标(或高度和y坐标)之差来得到这个比例;根据宽度和高度,你可以确定哪种情况适用,而另一种情况将在矩形边缘的延伸上。当你得到这个比例后,只需取从B到A向量的该比例,就可以得到交点的坐标。


0

通过一些简单的数学计算,你可以比大多数答案建议的方法更轻松地解决这个问题。

策略:

  1. 将线段和矩形变换,使得矩形的中心位于原点(0,0)。
  2. 按照矩形宽度/2和高度/2(在我的代码示例中称为xradius和yradius)缩放线段和矩形,因此问题本质上变成“找到[连接点p和原点的线]与[以原点为中心、大小为2的正方形]的交点”。
  3. 现在,我们可以通过缩小点p的坐标来找到正方形上最近一侧的交点(通过使其中一个坐标等于+1或-1)。
  4. 由于问题现在是对称的,如果我们取x和y的绝对值,我们可以将问题转化为网格的“第一象限”,其中x和y都是正数。这避免了需要单独处理盒子的每一面。
  5. 在第一象限中,找到交点。如果p在线x==y上方,我们需要除以y将该点放在最近的线上,如果在该线下方,则需要除以x。请注意,即使使用了x和y坐标的绝对值计算了缩放因子,它也可以用于缩放原始坐标。
  6. 现在,我们已经有了交点,请进行反向变换以撤消步骤2和步骤1中的变换。

在C++中,这看起来像:

struct point { double x, y; };
struct rect { point center; double xradius, yradius; }
point intersect(const point& p, const rect& r) {
    // Steps 1 and 2: Transform to origin and scale by xradius and yradius. 
    point q{ (p.x - r.center.x) / r.xradius, (p.y - r.center.y) / r.yradius };
    // Steps 3 and 4: Transform to Quadrant 1 and find scaling factor.
    double f = max(abs(q.x), abs(q.y));
    // Step 5: Divide by scaling factor to find intersection point on square.
    point intersect{ q.x/f, q.y/f };
    // Step 6: Inverse transformations from steps 1 and 2.
    return {intersect.x * r.xradius + r.center.x, intersect.y * r.yradius + r.center.y};
}

如果您可以访问具有运算符重载的真正向量和矩阵类,则代码变得更加容易:
point intersect(const point& p, const rect& r) {
    matrix m = scale_matrix(r.xradius, r.yradius)*translate_matrix(p - r.center)
    point q = m * p;
    return m.inverse() * q/(max(abs(q.x), abs(q.y))
}

警告:此代码未处理p.x==r.centerx && p.y==r.centery的情况,这将导致第5步出现除以零的错误。处理该错误条件将留给读者作为练习。 :-)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接