【问题标题】:How can I check wether a point is inside the circumcircle of 3 points?如何检查一个点是否在 3 个点的外接圆内?
【发布时间】:2020-09-23 17:12:50
【问题描述】:

有什么简单的解决方法吗?或者有人有实现的例子吗?

谢谢,乔纳斯

【问题讨论】:

标签: algorithm geometry


【解决方案1】:

我们打电话

  • a、b、c 我们的三点,
  • C (a, b, c)的外接圆
  • 还有一点。

确定 d 是否在 C 中的一种快速方法是计算此行列式:

      | ax-dx, ay-dy, (ax-dx)² + (ay-dy)² |
det = | bx-dx, by-dy, (bx-dx)² + (by-dy)² |
      | cx-dx, cy-dy, (cx-dx)² + (cy-dy)² |

如果a、b、c按逆时针顺序排列,则:

  • 如果 det 等于 0 则 d 在 C 上
  • 如果 det > 0 则 d 在 C 内
  • 如果 det

这里有一个 javascript 函数可以做到这一点:

function inCircle (ax, ay, bx, by, cx, cy, dx, dy) {
    let ax_ = ax-dx;
    let ay_ = ay-dy;
    let bx_ = bx-dx;
    let by_ = by-dy;
    let cx_ = cx-dx;
    let cy_ = cy-dy;
    return (
        (ax_*ax_ + ay_*ay_) * (bx_*cy_-cx_*by_) -
        (bx_*bx_ + by_*by_) * (ax_*cy_-cx_*ay_) +
        (cx_*cx_ + cy_*cy_) * (ax_*by_-bx_*ay_)
    ) > 0;
}

您可能还需要检查您的积分是否按逆时针顺序排列:

function ccw (ax, ay, bx, by, cx, cy) {
    return (bx - ax)*(cy - ay)-(cx - ax)*(by - ay) > 0;
}

我没有将 ccw 检查放在 inCircle 函数中,因为您不应该每次都检查它。

此过程不进行任何除法或平方根运算。

您可以在此处查看正在运行的代码:https://titouant.github.io/testTriangle/

来源:https://github.com/TitouanT/testTriangle

【讨论】:

    【解决方案2】:

    (如果您对不明显/“疯狂”的解决方案感兴趣。)

    Delaunay 三角剖分的一个等价性质如下:如果你在三角剖分中建立任何三角形的外接圆,保证不包含三角剖分的任何其他顶点。

    Delaunay 三角剖分的另一个等效特性是:它使最小三角角最大化(即在同一组点上的所有三角剖分中使其最大化)。

    这为您的测试建议了一个算法:

    1. 考虑三角形ABC 建立在原来的 3 点之上。
    2. 如果测试点P 位于三角形内,则它肯定在圆内
    3. 如果测试点P属于“角”区域之一(见下图中阴影区域),则肯定在圆外

    1. 否则(假设 P 位于区域 1)考虑四边形 ABCP 的两个三角剖分:原始一个包含原始三角形(红色对角线),替代 一个带有“翻转”对角线(蓝色对角线)

    1. 如果此三角剖分是 Delaunay 三角剖分,请通过测试 the "flip" condition 来确定哪一个,例如通过比较 α = min(∠1,∠4,∠5,∠8)β = min(∠2,∠3,∠6,∠7)
    2. 如果原始三角剖分是 Delaunay 三角剖分 (α > β),则 P 位于圆外。如果 alternate 三角剖分是 Delaunay 三角剖分 (α < β),则 P 位于圆内。

    完成。


    (一段时间后重温这个答案。)

    这个解决方案可能并不像乍一看那样“疯狂”。请注意,为了比较第 5 步和第 6 步的角度,无需计算实际角度值。知道它们的余弦就足够了(即不需要涉及三角函数)。

    代码的 C++ 版本

    #include <cmath>
    #include <array>
    #include <algorithm>
    
    struct pnt_t
    {
      int x, y;
    
      pnt_t ccw90() const
        { return { -y, x }; }
    
      double length() const
        { return std::hypot(x, y); }
    
      pnt_t &operator -=(const pnt_t &rhs)
      {
        x -= rhs.x;
        y -= rhs.y;
        return *this;
      }
    
      friend pnt_t operator -(const pnt_t &lhs, const pnt_t &rhs)
        { return pnt_t(lhs) -= rhs; }
    
      friend int operator *(const pnt_t &lhs, const pnt_t &rhs)
        { return lhs.x * rhs.x + lhs.y * rhs.y; }
    };
    
    int side(const pnt_t &a, const pnt_t &b, const pnt_t &p)
    {
      int cp = (b - a).ccw90() * (p - a);
      return (cp > 0) - (cp < 0);
    }
    
    void make_ccw(std::array<pnt_t, 3> &t)
    {
      if (side(t[0], t[1], t[2]) < 0)
        std::swap(t[0], t[1]);
    }
    
    double ncos(pnt_t a, const pnt_t &o, pnt_t b)
    {
      a -= o;
      b -= o;
      return -(a * b) / (a.length() * b.length());
    }
    
    bool inside_circle(std::array<pnt_t, 3> t, const pnt_t &p)
    {
      make_ccw(t);
    
      std::array<int, 3> s = 
        { side(t[0], t[1], p), side(t[1], t[2], p), side(t[2], t[0], p) };
    
      unsigned outside = std::count(std::begin(s), std::end(s), -1);
      if (outside != 1)
        return outside == 0;
    
      while (s[0] >= 0)
      {
        std::rotate(std::begin(t), std::begin(t) + 1, std::end(t));
        std::rotate(std::begin(s), std::begin(s) + 1, std::end(s));
      }
    
      double 
        min_org = std::min({
          ncos(t[0], t[1], t[2]), ncos(t[2], t[0], t[1]), 
          ncos(t[1], t[0], p), ncos(p, t[1], t[0]) }),
        min_alt = std::min({
          ncos(t[1], t[2], p), ncos(p, t[2], t[0]), 
          ncos(t[0], p, t[2]), ncos(t[2], p, t[1]) });
    
      return min_org <= min_alt;
    }
    

    以及一些任意选择的三角形和大量随机点的测试


    当然,整个事情可以很容易地重新表述,甚至不用提到德劳内三角剖分。从第 4 步开始,此解决方案基于 cyclic quadrilateral 的对角属性,其和必须为 180°。

    【讨论】:

    • 我试图理解这一点,因为它回答了我的一个问题,但我遇到了一个问题。您提到您的方法基于翻转条件,其中角度之和
    • @Nairou:为了比较角度,我使用了ncos 函数,它表示“负余弦”,即简单地取反实际余弦。它在整个[0, Pi) 范围内表现一致:角度越大 - ncos 越大。 (虽然在这个问题中,如果我没有遗漏任何东西,我们将只处理锐角。)。
    • 对于翻转条件,它有多个等价公式。链接上的那个(在维基百科中)是基于对角度求和。我使用了另一种(但等效)形式的翻转条件,我在答案的第 5 点中对此进行了描述:翻转以最大化最小角度。这个版本的翻转条件不需要对角度求和,甚至不需要知道角度值。它只需要能够比较角度。这就是我选择 this 形式的翻转条件的全部原因。
    • 如果您阅读更多有关 Delanay 三角剖分的信息,您会发现它有许多有趣的属性,实际上可以证明这些属性是相互依存的。这些属性可用于构建不同(但等效)的翻转条件。在这里,我们看到了两个最著名的。
    【解决方案3】:

    this Math SE post of mine 中,我包含了一个方程,它通过计算一个 4×4 行列式来检查四个点是否共圆。通过将该等式转化为不等式,您可以检查其内在性。

    如果您想知道不等式必须朝哪个方向发展,请考虑一个非常远的点的情况。在这种情况下,x²+y² 项将支配所有其他项。因此,您可以简单地假设对于所讨论的点,该术语为一,而其他三个为零。然后选择你的不等式的符号,使这个值满足它,因为这个点肯定在外面,但你想表征里面。

    如果数值精度是个问题,this page by Prof. Shewchuk 描述了如何为使用常规双精度浮点数表示的点获得一致的谓词。

    【讨论】:

      【解决方案4】:

      给定 3 个点 (x1,y1),(x2,y2),(x3,y3) 并且您要检查的点在上述 3 个点 (x,y) 定义的圆圈内,您可以执行类似的操作

      /**
       *
       * @param x coordinate of point want to check if inside
       * @param y coordinate of point want to check if inside
       * @param cx center x
       * @param cy center y
       * @param r radius of circle
       * @return whether (x,y) is inside circle
       */
      static boolean g(double x,double y,double cx,double cy,double r){
          return Math.sqrt((x-cx)*(x-cx)+(y-cy)*(y-cy))<r;
      }
      
      // check if (x,y) is inside circle defined by (x1,y1),(x2,y2),(x3,y3)
      static boolean isInside(double x,double y,double x1,double y1,double x2,double y2,double x3,double y3){
          double m1 = (x1-x2)/(y2-y1);
          double m2 = (x1-x3)/(y3-y1);
          double b1 = ((y1+y2)/2) - m1*(x1+x2)/2;
          double b2 = ((y1+y3)/2) - m2*(x1+x3)/2;
          double xx = (b2-b1)/(m1-m2);
          double yy = m1*xx + b1;
          return g(x,y,xx,yy,Math.sqrt((xx-x1)*(xx-x1)+(yy-y1)*(yy-y1)));
      }
      
      public static void main(String[] args) {
          // if (0,1) is inside the circle defined by (0,0),(0,2),(1,1)
          System.out.println(isInside(0,1,0,0,0,2,1,1));
      }
      

      从3点得到圆心表达式的方法是找到2条线段的2perpendicular bisectors的交点,上面我选择了(x1,y1)-(x2,y2)(x1,y1)-(x3,y3)。由于您知道每个垂直平分线上的一个点,即(x1+x2)/2(x1+x3)/2,并且由于您还分别从上述两条线段知道每个垂直平分线的斜率,即(x1-x2)/(y2-y1)(x1-x3)/(y3-y1),因此您可以求解对于它们相交的 (x,y)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-04-09
        • 2021-03-14
        • 2022-01-25
        • 1970-01-01
        • 2016-02-08
        • 2013-01-13
        • 1970-01-01
        相关资源
        最近更新 更多