【问题标题】:Convex hull of 4 points4点凸包
【发布时间】:2011-01-08 12:02:43
【问题描述】:

我想要一个算法来计算 4 个二维点的凸包。我已经查看了泛化问题的算法,但我想知道是否有 4 点的简单解决方案。

【问题讨论】:

    标签: algorithm graphics computational-geometry convex-hull


    【解决方案1】:

    取三个点,判断它们的三角形是顺时针还是逆时针::

    triangle_ABC= (A.y-B.y)*C.x + (B.x-A.x)*C.y + (A.x*B.y-B.x*A.y)
    

    对于右手坐标系,如果 ABC 是逆时针方向,则该值为正,顺时针方向为负,如果它们共线,则为零。但是,以下对于左手坐标系同样适用,因为方向是相对的。

    计算包含第四个点的三个三角形的可比较值:

    triangle_ABD= (A.y-B.y)*D.x + (B.x-A.x)*D.y + (A.x*B.y-B.x*A.y)
    triangle_BCD= (B.y-C.y)*D.x + (C.x-B.x)*D.y + (B.x*C.y-C.x*B.y)
    triangle_CAD= (C.y-A.y)*D.x + (A.x-C.x)*D.y + (C.x*A.y-A.x*C.y)
    

    如果 {ABD,BCD,CAD} 的三个符号都与 ABC 相同,则 D 在 ABC 内,而外壳是三角形 ABC。

    如果 {ABD,BCD,CAD} 中的两个与 ABC 符号相同,一个符号相反,则所有四个点都是极值点,并且外壳是四边形 ABCD。

    如果{ABD,BCD,CAD}中的一个与ABC的符号相同,而两个符号相反,则凸包是具有相同符号的三角形;剩下的点在里面。

    如果任何一个三角形值为零,则三个点共线且中点不是极值点。如果所有四个点共线,则所有四个值都应为零,并且外壳将是一条线或一个点。在这些情况下要注意数值鲁棒性问题!

    对于 ABC 为正的情况:

    ABC  ABD  BCD  CAD  hull
    ------------------------
     +    +    +    +   ABC
     +    +    +    -   ABCD
     +    +    -    +   ABDC
     +    +    -    -   ABD
     +    -    +    +   ADBC
     +    -    +    -   BCD
     +    -    -    +   CAD
     +    -    -    -   [should not happen]
    

    【讨论】:

    • 实际上,仔细看一下,如果你先做所有的差异,它应该会更有效准确:ABC=(Ay-By)*(Cx- Ax)+(Bx-Ax)*(Cy-Ay) [对于 ABD 等以此类推]
    • 是否可以确定精确的“四边形 ABCD”?我做了一些实验,发现在某些情况下凸包是 ABCD 而在其他 ACDB 中 - 我只是不太清楚如何映射它。
    • 我发现如果 {ABD,BCD,CAD} 中的一个恰好与 ABC 符号相反,那么凸包是:如果 ABD 相反 -> ACBD,如果 BCD 相反 -> ABDC 和如果 CAD 对面 -> ABCD
    • 不编辑答案以防我错了,但我是手动得出的。 +++- 大小写为 ABCD,+-+ 为 ABDC,+-++ 大小写为 ADBC。
    • 你是对的,@Warty,谢谢你注意到这一点!!我已检查以确保您是正确的,并适当地编辑了答案。
    【解决方案2】:

    这里有一个针对 4 点的更特别的算法:

    • 查找具有最小 X、最大 X、最小 Y 和最大 Y 的点的索引,并从中获取唯一值。例如,索引可能是 0,2,1,2,唯一值可能是 0,2,1。
    • 如果有 4 个唯一值,则凸包由所有 4 个点组成。
    • 如果有 3 个唯一值,那么这 3 个点肯定在凸包中。检查第 4 个点是否在这个三角形内;如果不是,它也是凸包的一部分。
    • 如果有 2 个唯一值,则这 2 个点在外壳上。在其他 2 点中,离连接这 2 点的这条线较远的点肯定在船体上。进行三角形遏制测试以检查另一点是否也在船体中。
    • 如果有 1 个唯一值,则所有 4 个点都重合。

    如果有 4 个点来正确排列它们,则需要进行一些计算以避免获得 bow-tie 形状。嗯....看起来有足够的特殊情况可以证明使用通用算法是合理的。但是,您可以调整它以比通用算法运行得更快。

    【讨论】:

      【解决方案3】:

      或者只使用Jarvis march

      【讨论】:

      【解决方案4】:

      我根据礼品包装算法的粗略版本做了a proof of concept fiddle

      一般情况下效率不高,但只够 4 分。

      function Point (x, y)
      {
          this.x = x;
          this.y = y;
      }
      
      Point.prototype.equals = function (p)
      {
          return this.x == p.x && this.y == p.y;
      };
      
      Point.prototype.distance = function (p)
      { 
          return Math.sqrt (Math.pow (this.x-p.x, 2) 
                          + Math.pow (this.y-p.y, 2));
      };
      
      function convex_hull (points)
      {
          function left_oriented (p1, p2, candidate)
          {
              var det = (p2.x - p1.x) * (candidate.y - p1.y) 
                      - (candidate.x - p1.x) * (p2.y - p1.y);
              if (det > 0) return true;  // left-oriented 
              if (det < 0) return false; // right oriented
              // select the farthest point in case of colinearity
              return p1.distance (candidate) > p1.distance (p2);
          }
      
          var N = points.length;
          var hull = [];
      
          // get leftmost point
          var min = 0;
          for (var i = 1; i != N; i++)
          {
              if (points[i].y < points[min].y) min = i;
          }
          hull_point = points[min];
      
          // walk the hull
          do
          {
              hull.push(hull_point);
      
              var end_point = points[0];
              for (var i = 1; i != N; i++) 
              {
                  if (  hull_point.equals (end_point)
                     || left_oriented (hull_point, 
                                       end_point, 
                                       points[i]))
                  {
                      end_point = points[i];
                  }
              }
              hull_point = end_point;
          }
          /*
           * must compare coordinates values (and not simply objects)
           * for the case of 4 co-incident points
           */
          while (!end_point.equals (hull[0])); 
          return hull;
      }
      

      这很有趣:)

      【讨论】:

        【解决方案5】:

        我已经使用查找表快速实现了comingstorm 的答案。所有四个点共线的情况处理,因为我的应用程序不需要它。如果这些点是共线的,则算法将第一个指针 point[0] 设置为空。如果 point[3] 是空指针,则外壳包含 3 个点,否则外壳有 4 个点。对于 y 轴指向顶部、x 轴指向右侧的坐标系,船体按逆时针顺序排列。

        const char hull4_table[] = {        
            1,2,3,0,1,2,3,0,1,2,4,3,1,2,3,0,1,2,3,0,1,2,4,0,1,2,3,4,1,2,4,0,1,2,4,0,
            1,2,3,0,1,2,3,0,1,4,3,0,1,2,3,0,0,0,0,0,0,0,0,0,2,3,4,0,0,0,0,0,0,0,0,0,
            1,4,2,3,1,4,3,0,1,4,3,0,2,3,4,0,0,0,0,0,0,0,0,0,2,3,4,0,0,0,0,0,0,0,0,0,
            0,0,0,0,0,0,0,0,2,4,3,0,0,0,0,0,0,0,0,0,1,2,4,0,1,3,4,0,1,2,4,0,1,2,4,0,
            0,0,0,0,0,0,0,0,1,4,3,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,4,0,0,0,0,0,0,0,0,0,
            1,4,2,0,1,4,2,0,1,4,3,0,1,4,2,0,0,0,0,0,0,0,0,0,2,3,4,0,0,0,0,0,0,0,0,0,
            0,0,0,0,0,0,0,0,2,4,3,0,0,0,0,0,0,0,0,0,2,4,3,0,1,3,4,0,1,3,4,0,1,3,2,4,
            0,0,0,0,0,0,0,0,2,4,3,0,0,0,0,0,0,0,0,0,1,3,2,0,1,3,4,0,1,3,2,0,1,3,2,0,
            1,4,2,0,1,4,2,0,1,4,3,2,1,4,2,0,1,3,2,0,1,3,2,0,1,3,4,2,1,3,2,0,1,3,2,0
        };
        struct Vec2i {
            int x, y;
        };
        typedef long long int64;    
        inline int sign(int64 x) {
            return (x > 0) - (x < 0);
        }
        inline int64 orientation(const Vec2i& a, const Vec2i& b, const Vec2i& c) {
            return (int64)(b.x - a.x) * (c.y - b.y) - (b.y - a.y) * (c.x - b.x);
        }
        
        void convex_hull4(const Vec2i** points) {
            const Vec2i* p[5] = {(Vec2i*)0, points[0], points[1], points[2], points[3]};
            char abc = (char)1 - sign(orientation(*points[0], *points[1], *points[2]));
            char abd = (char)1 - sign(orientation(*points[0], *points[1], *points[3]));
            char cad = (char)1 - sign(orientation(*points[2], *points[0], *points[3]));
            char bcd = (char)1 - sign(orientation(*points[1], *points[2], *points[3]));
        
            const char* t = hull4_table + (int)4 * (bcd + 3*cad + 9*abd + 27*abc);
            points[0] = p[t[0]];
            points[1] = p[t[1]];
            points[2] = p[t[2]];
            points[3] = p[t[3]];
        }
        

        【讨论】:

          【解决方案6】:

          基于@comingstorm 的回答,我创建了 Swift 解决方案:

          func convexHull4(a: Pt, b: Pt, c: Pt, d: Pt) -> [LineSegment]? {
          
              let abc = (a.y-b.y)*c.x + (b.x-a.x)*c.y + (a.x*b.y-b.x*a.y)
              let abd = (a.y-b.y)*d.x + (b.x-a.x)*d.y + (a.x*b.y-b.x*a.y)
              let bcd = (b.y-c.y)*d.x + (c.x-b.x)*d.y + (b.x*c.y-c.x*b.y)
              let cad = (c.y-a.y)*d.x + (a.x-c.x)*d.y + (c.x*a.y-a.x*c.y)
          
              if  (abc > 0 && abd > 0 && bcd > 0 && cad > 0) ||
                  (abc < 0 && abd < 0 && bcd < 0 && cad < 0) {
                  //abc
                  return [
                      LineSegment(p1: a, p2: b),
                      LineSegment(p1: b, p2: c),
                      LineSegment(p1: c, p2: a)
                  ]
              } else if   (abc > 0 && abd > 0 && bcd > 0 && cad < 0) ||
                          (abc < 0 && abd < 0 && bcd < 0 && cad > 0) {
                  //abcd
                  return [
                      LineSegment(p1: a, p2: b),
                      LineSegment(p1: b, p2: c),
                      LineSegment(p1: c, p2: d),
                      LineSegment(p1: d, p2: a)
                  ]
              } else if   (abc > 0 && abd > 0 && bcd < 0 && cad > 0) ||
                          (abc < 0 && abd < 0 && bcd > 0 && cad < 0) {
                  //abdc
                  return [
                      LineSegment(p1: a, p2: b),
                      LineSegment(p1: b, p2: d),
                      LineSegment(p1: d, p2: c),
                      LineSegment(p1: c, p2: a)
                  ]
              } else if   (abc > 0 && abd < 0 && bcd > 0 && cad > 0) ||
                          (abc < 0 && abd > 0 && bcd < 0 && cad < 0) {
                  //acbd
                  return [
                      LineSegment(p1: a, p2: c),
                      LineSegment(p1: c, p2: b),
                      LineSegment(p1: b, p2: d),
                      LineSegment(p1: d, p2: a)
                  ]
              } else if   (abc > 0 && abd > 0 && bcd < 0 && cad < 0) ||
                          (abc < 0 && abd < 0 && bcd > 0 && cad > 0) {
                  //abd
                  return [
                      LineSegment(p1: a, p2: b),
                      LineSegment(p1: b, p2: d),
                      LineSegment(p1: d, p2: a)
                  ]
              } else if   (abc > 0 && abd < 0 && bcd > 0 && cad < 0) ||
                          (abc < 0 && abd > 0 && bcd < 0 && cad > 0) {
                  //bcd
                  return [
                      LineSegment(p1: b, p2: c),
                      LineSegment(p1: c, p2: d),
                      LineSegment(p1: d, p2: b)
                  ]
              } else if   (abc > 0 && abd < 0 && bcd < 0 && cad > 0) ||
                          (abc < 0 && abd > 0 && bcd > 0 && cad < 0) {
                  //cad
                  return [
                      LineSegment(p1: c, p2: a),
                      LineSegment(p1: a, p2: d),
                      LineSegment(p1: d, p2: c)
                  ]
              }
          
              return nil
          
          }
          

          【讨论】:

            【解决方案7】:

            基于comingstorm的解决方案,我创建了一个处理退化情况的C#解决方案(例如4个点形成线或点)。

            https://gist.github.com/miyu/6e32e993d93d932c419f1f46020e23f0

              public static IntVector2[] ConvexHull3(IntVector2 a, IntVector2 b, IntVector2 c) {
                 var abc = Clockness(a, b, c);
                 if (abc == Clk.Neither) {
                    var (s, t) = FindCollinearBounds(a, b, c);
                    return s == t ? new[] { s } : new[] { s, t };
                 }
                 if (abc == Clk.Clockwise) {
                    return new[] { c, b, a };
                 }
                 return new[] { a, b, c };
              }
            
              public static (IntVector2, IntVector2) FindCollinearBounds(IntVector2 a, IntVector2 b, IntVector2 c) {
                 var ab = a.To(b).SquaredNorm2();
                 var ac = a.To(c).SquaredNorm2();
                 var bc = b.To(c).SquaredNorm2();
                 if (ab > ac) {
                    return ab > bc ? (a, b) : (b, c);
                 } else {
                    return ac > bc ? (a, c) : (b, c);
                 }
              }
            
              // See https://stackoverflow.com/questions/2122305/convex-hull-of-4-points
              public static IntVector2[] ConvexHull4(IntVector2 a, IntVector2 b, IntVector2 c, IntVector2 d) {
                 var abc = Clockness(a, b, c);
            
                 if (abc == Clk.Neither) {
                    var (s, t) = FindCollinearBounds(a, b, c);
                    return ConvexHull3(s, t, d);
                 }
            
                 // make abc ccw
                 if (abc == Clk.Clockwise) (a, c) = (c, a);
            
                 var abd = Clockness(a, b, d);
                 var bcd = Clockness(b, c, d);
                 var cad = Clockness(c, a, d);
            
                 if (abd == Clk.Neither) {
                    var (s, t) = FindCollinearBounds(a, b, d);
                    return ConvexHull3(s, t, c);
                 }
            
                 if (bcd == Clk.Neither) {
                    var (s, t) = FindCollinearBounds(b, c, d);
                    return ConvexHull3(s, t, a);
                 }
            
                 if (cad == Clk.Neither) {
                    var (s, t) = FindCollinearBounds(c, a, d);
                    return ConvexHull3(s, t, b);
                 }
            
                 if (abd == Clk.CounterClockwise) {
                    if (bcd == Clk.CounterClockwise && cad == Clk.CounterClockwise) return new[] { a, b, c };
                    if (bcd == Clk.CounterClockwise && cad == Clk.Clockwise) return new[] { a, b, c, d };
                    if (bcd == Clk.Clockwise && cad == Clk.CounterClockwise) return new[] { a, b, d, c };
                    if (bcd == Clk.Clockwise && cad == Clk.Clockwise) return new[] { a, b, d };
                    throw new InvalidStateException();
                 } else {
                    if (bcd == Clk.CounterClockwise && cad == Clk.CounterClockwise) return new[] { a, d, b, c };
                    if (bcd == Clk.CounterClockwise && cad == Clk.Clockwise) return new[] { d, b, c };
                    if (bcd == Clk.Clockwise && cad == Clk.CounterClockwise) return new[] { a, d, c };
                    // 4th state impossible
                    throw new InvalidStateException();
                 }
              }
            

            你需要为你的向量类型实现这个样板:

              // relative to screen coordinates, so top left origin, x+ right, y+ down.
              // clockwise goes from origin to x+ to x+/y+ to y+ to origin, like clockwise if
              // you were to stare at a clock on your screen
              //
              // That is, if you draw an angle between 3 points on your screen, the clockness of that
              // direction is the clockness this would return.
              public enum Clockness {
                 Clockwise = -1,
                 Neither = 0,
                 CounterClockwise = 1
              }
              public static Clockness Clockness(IntVector2 a, IntVector2 b, IntVector2 c) => Clockness(b - a, b - c);
              public static Clockness Clockness(IntVector2 ba, IntVector2 bc) => Clockness(ba.X, ba.Y, bc.X, bc.Y);
              public static Clockness Clockness(cInt ax, cInt ay, cInt bx, cInt by, cInt cx, cInt cy) => Clockness(bx - ax, by - ay, bx - cx, by - cy);
              public static Clockness Clockness(cInt bax, cInt bay, cInt bcx, cInt bcy) => (Clockness)Math.Sign(Cross(bax, bay, bcx, bcy));
            

            【讨论】:

              【解决方案8】:

              这里是对问题的完整分析和高效 ruby 代码(最小化比较次数)

              #   positions for d:
              #
              #           abc > 0                               abc < 0
              #         (+-+- doesn't exist)               (-+-+ doesn't exist)
              #
              #
              #                     |        /       ---+ \   --++    | -+++
              #                     |       /        bdc   \  acbd    | acd
              #                     | +-++ /                \         |
              #                     | abd /         ---------A--------B---------
              #                     |    /                    \  --+- |
              #                     |   /                      \ acb  |
              #                     |  /                        \     |
              #                     | /                          \    |
              #                     |/                  ----      \   | -++-
              #                     C                   adcb       \  | acdb
              #                    /|                               \ |
              #                   / |                                \|
              #         ++++     /  |                                 C
              #         abcd    /   |                                 |\
              #                /    | +--+                            | \
              #               /     | abdc                            |  \
              #              / ++-+ |                                 |   \
              #             /  abc  |                                 |    \
              #   ---------A--------B---------                        |     \
              #     +++-  /         |                                 |      \
              #     bcd  /   ++--   | +---                            | -+--  \
              #         /    adbc   | adc                             | adb    \
              #
              #   or as table
              #
              #   ++++ abcd       -+++ acd
              #   +++- bcd        -++- acdb
              #   ++-+ abc        -+-+ XXXX
              #   ++-- adbc       -+-- adb
              #   +-++ abd        --++ acbd
              #   +-+- XXXX       --+- acb
              #   +--+ abdc       ---+ bdc
              #   +--- adc        ---- adcb
              #
              # if there are some collinear points, the hull will be nil (for the moment)
              #
              def point_point_point_orientation(p, q, r)
                (q.x - p.x) * (r.y - q.y) - (q.y - p.y) * (r.x - q.x)
              end
              
              
              
              def convex_hull_4_points(a, b, c, d)
                abc = point_point_point_orientation(a, b, c)
                if abc.zero?
                  # todo
                  return nil
                end
              
                bcd = point_point_point_orientation(b, c, d)
                if bcd.zero?
                  # todo
                  return nil
                end
              
                cda = point_point_point_orientation(c, d, a)
                if cda.zero?
                  # todo
                  return nil
                end
              
                dab = point_point_point_orientation(d, a, b)
                if dab.zero?
                  # todo
                  return nil
                end
              
                if abc.positive?
                  if bcd.positive?
                    if cda.positive?
                      if dab.positive?
                        [a, b, c, d] # ++++
                      else
                        [b, c, d]    # +++-
                      end
                    else
                      if dab.positive?
                        [a, b, c]    # ++-+
                      else
                        [a, d, b, c] # ++--
                      end
                    end
                  else
                    if cda.positive?
                      if dab.positive?
                        [a, b, d]    # +-++
                      else
                        raise        # +-+-
                      end
                    else
                      if dab.positive?
                        [a, b, d, c] # +--+
                      else
                        [a, d, c]    # +---
                      end
                    end
                  end
                else
                  if bcd.positive?
                    if cda.positive?
                      if dab.positive?
                        [a, c, d]    # -+++
                      else
                        [a, c, d, b] # -++-
                      end
                    else
                      if dab.positive?
                        raise        # -+-+
                      else
                        [a, d, b]    # -+--
                      end
                    end
                  else
                    if cda.positive?
                      if dab.positive?
                        [a, c, b, d] # --++
                      else
                        [a, c, b]    # --+-
                      end
                    else
                      if dab.positive?
                        [b, d, c]    # ---+
                      else
                        [a, d, c, b] # ----
                      end
                    end
                  end
                end
              end
              

              【讨论】:

                猜你喜欢
                • 2014-12-12
                • 1970-01-01
                • 2023-03-31
                • 2017-05-22
                • 1970-01-01
                • 2018-05-07
                • 2015-01-31
                • 2016-01-02
                • 1970-01-01
                相关资源
                最近更新 更多