【问题标题】:2D bounding box of a sector?扇区的二维边界框?
【发布时间】:2010-11-23 03:03:09
【问题描述】:

我一直在谷歌上搜索,直到脸色发青,除非我遗漏了一些非常明显的东西,否则我找不到任何算法来计算 2D 扇区的边界框。

给定封闭圆的中心点、半径和扇形范围的角度,计算该扇形的轴对齐边界矩形的最佳算法是什么?

【问题讨论】:

标签: algorithm geometry


【解决方案1】:

我尝试实现jairchu的答案,但发现了一些问题,我想分享一下:

我的圆圈坐标系从圆圈右侧的 0 度开始,逆时针穿过顶部 (90 度)、左侧 (180 度) 和底部 (270 度)。角度可以在 0 到 359,9999 度之间。

  1. 中心点不应该是点列表的一部分

  2. 您必须区分顺时针和逆时针弧才能列出位于 0,90,180,270 度上的点

  3. 很难确定角度跨度是否包括角度 0、90、180 或 270 度。

    public override Rect Box()
     {
         List<Point> potentialExtrema = new List<Point>();
    
         potentialExtrema.Add(StartPoint);
         potentialExtrema.Add(EndPoint);
         if (!ClockWise)
         {
             if (EndAngle < StartAngle || EndAngle == 0 || StartAngle == 0 || EndAngle == 360 || StartAngle == 360)
                 potentialExtrema.Add(new Point(Point.X + Radius, Point.Y));
             if ((StartAngle <= 90 || StartAngle > EndAngle) && EndAngle >= 90)
                 potentialExtrema.Add(new Point(Point.X, Point.Y + Radius));
             if ((StartAngle <= 180 || StartAngle > EndAngle) && EndAngle >= 180)
                 potentialExtrema.Add(new Point(Point.X - Radius, Point.Y));
             if ((StartAngle <= 270 || StartAngle > EndAngle) && EndAngle >= 270)
                 potentialExtrema.Add(new Point(Point.X, Point.Y - Radius));
         }
         else
         {
             if (StartAngle < EndAngle || EndAngle == 0 || StartAngle == 0 || EndAngle == 360 || StartAngle == 360)
                 potentialExtrema.Add(new Point(Point.X + Radius, Point.Y));
             if ((StartAngle >= 90 || StartAngle < EndAngle) && EndAngle <= 90)
                 potentialExtrema.Add(new Point(Point.X, Point.Y + Radius));
             if ((StartAngle >= 180 || StartAngle < EndAngle) && EndAngle <= 180)
                 potentialExtrema.Add(new Point(Point.X - Radius, Point.Y));
             if ((StartAngle >= 270 || StartAngle < EndAngle) && EndAngle <= 270)
                 potentialExtrema.Add(new Point(Point.X, Point.Y - Radius));
         }
         double maxX = double.NegativeInfinity;
         double maxY = double.NegativeInfinity;
         double minX = double.PositiveInfinity;
         double minY = double.PositiveInfinity;
         foreach (var point in potentialExtrema)
         {
             if (point.X > maxX)
                 maxX = point.X;
             if (point.Y > maxY)
                 maxY = point.Y;
             if (point.X < minX)
                 minX = point.X;
             if (point.Y < minY)
                 minY = point.Y;
         }
         return new Rect(minX, minY, maxX - minX, maxY - minY);
    
     }
    

    }

有一个更优雅的解决方案来确定角度跨度内是 0,90,180 还是 270 度:

    public override Rect Box()
    {
        List<Point> potentialExtrema = new List<Point>();

        potentialExtrema.Add(StartPoint);
        potentialExtrema.Add(EndPoint);
        if (AngleProduct(0))
            potentialExtrema.Add(new Point(Point.X + Radius, Point.Y));
        if (AngleProduct(90))
            potentialExtrema.Add(new Point(Point.X, Point.Y + Radius));
        if (AngleProduct(180))
            potentialExtrema.Add(new Point(Point.X - Radius, Point.Y));
        if (AngleProduct(270))
            potentialExtrema.Add(new Point(Point.X, Point.Y - Radius));
        double maxX = double.NegativeInfinity;
        double maxY = double.NegativeInfinity;
        double minX = double.PositiveInfinity;
        double minY = double.PositiveInfinity;
        foreach (var point in potentialExtrema)
        {
            if (point.X > maxX)
                maxX = point.X;
            if (point.Y > maxY)
                maxY = point.Y;
            if (point.X < minX)
                minX = point.X;
            if (point.Y < minY)
                minY = point.Y;
        }
        return new Rect(minX, minY, maxX - minX, maxY - minY);

    }

    private bool AngleProduct(int alpha)
    {
        if (StartAngle == EndAngle)
            if (StartAngle == alpha)
                return true;
            else
                return false;
        double prod = 0;
        if (ClockWise)
            prod = -1 * (alpha - StartAngle) * (EndAngle - alpha) * (EndAngle - StartAngle);
        else
            prod = (alpha - StartAngle) * (EndAngle - alpha) * (EndAngle - StartAngle);
        if (prod >= 0)
            return true;
        else
            return false;

    }

【讨论】:

    【解决方案2】:
    • 生成以下点:
      • 圆的中心
      • 扇形起始角和终止角的位置
      • 另外,对于扇区角度范围内的0、90、180、270度之间的角度,它们在扇区上的对应点
    • 根据上述点计算 x 和 y 的最小值和最大值。这是你的边界框

    【讨论】:

    • 不确定我是否理解这部分The points on the circle for every angle between the two that divides by 90o (maximum of 4 points)你能详细说明吗?
    • @WDUK:改了措辞,现在好点了吗?
    • 是的,谢谢我让它工作了:) 我的方法必须涉及 4 个 if 语句,试图找到一种纯数学方法来删除分支,但我想不出办法。
    【解决方案3】:

    在 C# 代码中:

        /// <summary>
        /// The input parameters describe a circular arc going _clockwise_ from E to F.
        /// The output is the bounding box.
        /// </summary> 
        public Rect BoundingBox(Point E, Point F, Point C, double radius)
        {
            // Put the endpoints into the bounding box:
            double x1 = E.X;
            double y1 = E.Y;
            double x2 = x1, y2 = y1;
            if (F.X < x1)
                x1 = F.X;
            if (F.X > x2)
                x2 = F.X;
            if (F.Y < y1)
                y1 = F.Y;
            if (F.Y > y2)
                y2 = F.Y;
    
            // Now consider the top/bottom/left/right extremities of the circle:
            double thetaE = Math.Atan2(E.Y - C.Y, E.X - C.X);
            double thetaF = Math.Atan2(F.Y - C.Y, F.X - C.X);
            if (AnglesInClockwiseSequence(thetaE, 0/*right*/, thetaF))
            {
                double x = (C.X + radius);
                if (x > x2)
                    x2 = x;
            }
            if (AnglesInClockwiseSequence(thetaE, Math.PI/2/*bottom*/, thetaF))
            {
                double y = (C.Y + radius);
                if (y > y2)
                    y2 = y;
            }
            if (AnglesInClockwiseSequence(thetaE, Math.PI/*left*/, thetaF))
            {
                double x = (C.X - radius);
                if (x < x1)
                    x1 = x;
            }
            if (AnglesInClockwiseSequence(thetaE, Math.PI*3/2/*top*/, thetaF))
            {
                double y = (C.Y - radius);
                if (y < y1)
                    y1 = y;
            }
            return new Rect(x1, y1, x2 - x1, y2 - y1);
        }
    
    
        /// <summary>
        /// Do these angles go in clockwise sequence?
        /// </summary>
        private static bool AnglesInClockwiseSequence(double x, double y, double z)
        {
            return AngularDiffSigned(x, y) + AngularDiffSigned(y, z) < 2*Math.PI;
        }
    
    
        /// <summary>
        /// Returns a number between 0 and 360 degrees, as radians, representing the
        /// angle required to go clockwise from 'theta1' to 'theta2'. If 'theta2' is 
        /// 5 degrees clockwise from 'theta1' then return 5 degrees. If it's 5 degrees
        /// anticlockwise then return 360-5 degrees.
        /// </summary>
        public static double AngularDiffSigned(double theta1, double theta2)
        {
            double dif = theta2 - theta1;
            while (dif >= 2 * Math.PI)
                dif -= 2 * Math.PI;
            while (dif <= 0)
                dif += 2 * Math.PI;
            return dif;
        }
    

    【讨论】:

      【解决方案4】:

      首先,如果我写错了,我深表歉意,但英语不是我的第一语言,西班牙语实际上是!

      我遇到了这个问题,我想我找到了一个有效的解决方案。

      首先让我们看一下情况的图像

      所以我们有一个椭圆(实际上是一个圆)和两个点(CD),它们表示我们的扇区。 我们还有圆心 (B) 和圆弧的角度 alpha

      现在,在这种情况下,我让它通过 porpouse 上的 360º 看看它是否可以工作。

      假设alpha -&gt; -251.1º(它是负数导致顺时针方向),让我们将其转换为正值360º - 251.1º = 108.9º 现在我们的目标是找到该角度的二等分角,以便我们可以找到边界框的最大点(图中E),实际上你可能已经意识到,线段BE的长度等于圆的半径,但我们必须有角度才能获得E点的实际坐标。

      所以108.9º / 2 -&gt; 54.45º 现在我们有了角度。

      为了找到 E 的坐标,我们使用极坐标,所以

      x = r * Cos(theta)
      y = r * Sin(theta)
      

      我们有 rtheta 所以我们可以计算 x 和 y

      在我的例子中r = 2.82...(实际上这是不合理的,但我取前两位小数是为了方便)

      我们知道我们的第一个半径是87.1º,所以theta 是87.1 - 54.45º -&gt; 32.65º

      我们知道 *theta * 是 32.65º 所以让我们做一些数学运算

      x = 2.82 * Cos(32.65º) -> 2.37552
      y = 2.82 * Sin(32.65º) -> 1.52213
      

      现在我们需要将这些值调整到圆的实际中心,所以

      x = x + centerX
      y = y + centerY 
      

      在示例中,圆圈以(1.86, 4.24)为中心

      x -> 4.23552
      y -> 5.76213
      

      在这个阶段,我们应该使用一些微积分。我们知道边界框的一条边将是通过我们刚刚计算的点的圆弧的切线,因此,让我们找到该切线(红线)。

      我们知道切线通过我们的点(4.23, 5.76),现在我们需要一个斜率。

      如您所见,斜率与通过我们半径的矩形的斜率相同,因此我们必须找到那个斜率。

      为此,我们需要获取半径的坐标(从极坐标快速转换为笛卡尔坐标)。

      x = r * Cos(theta)
      y = r * Sin(theta)
      

      所以

      p0 = (centerX + 2.82 * Cos(87.1º), centerY + 2.82 * Sin(87.1º))
      p1 = (centerX + 2.82 * Cos(-21.8º), centerY + 2.82 * Sin(-21.8º))
      

      21.8º 是顺时针方向从水平轴到其下方半径的角度,因此我将其设为负数)

      p0 (2, 7.06)
      p1 (4.48, 3.19)
      

      现在让我们找到斜率:

      m = (y - y0) / (x - x0)
      ...
      m = (3.19 - 7.06) / (4.48-2) = -3.87 / 2.48 = -1.56048
      ...
      m = -1.56 
      

      我们需要计算切线方程的斜率,基本上是一个具有已知斜率 (m = -1.56) 的矩形,它通过已知点 (E -&gt; (4.23, 5.76))

      所以我们有Y = mx + b,其中m = -1.56y = 5.76x = 4.23 所以b 必须是

      b = 5.76 - (-1.56) * 4.23 = 12.36
      

      现在我们有了完整的切线方程 -> Y = -1.56X + 12.36 我们所要做的就是将点 CD 投影到该矩形上。

      我们需要矩形 CHDI 的方程,所以让我们计算它们

      让我们从CH开始:

      我们知道(从 tanget 方程)我们的方向向量是(1.56, 1)

      我们需要找到一个通过点C -&gt; (2, 7.06)的矩形

      (x - 2) / 1.56 = (y - 7.06) / 1
      

      做一些代数 -> y = 0.64x + 5.78

      我们知道有矩形CH 的方程,我们必须计算点H

      我们必须解决如下线性系统

      y = -1.56x + 12.36
      y = 1.56x + 5.78
      

      解决这个问题,我们将找到要点H (3, 7.69)

      我们需要对 rect DI 做同样的事情,所以让我们这样做

      我们的方向向量又是(1.56, 1)

      D -> (4.48, 3.19)
      
      (x - 4.48) / 1.56 = (y -3.19) / 1
      

      做一些代数 -> y = 0.64x + 0.32

      让我们求解线性系统

      y = -1.56x + 12.36
      y = 0.64x + 0.32
      
      I (5.47, 3.82)
      

      在这个阶段,我们已经有了构成边界框的四个点 -> C, H, D , I

      如果你不知道或不记得如何用编程语言求解线性系统,我会给你一个小例子

      这是纯代数

      假设我们有以下系统

      Ax + By = C
      Dx + Ey = F
      

      然后

      Dx = F - Ey
      x = (F - Ey) / D
      x = F/D - (E/D)y
      

      替换另一个方程

      A(F/D - (E/D)y) + By = C
      AF/D - (AE/D)y + By = C
      (AE/D)y + By = C - AF/D
      y(-AE/D + B) = C - AF/D
      y = (C - AF/D) / (-AE/D + B)
        = ( (CD - AF) / D ) / ( (-AE + BD) / D) )
      

      所以

      y = (CD - AF) / (BD - AE)
      

      对于x,我们也这样做

      Dx = F - Ey
      Dx - F = -Ey
      Ey = F - Dx
      y = F/E - (D/E)x
      

      替换另一个方程

      Ax + B(F/E - (D/E)x) = C
      Ax + (BF/E - (DB/E)x) = C
      Ax - (DB/E)x = C - BF/E
      x (A-(DB/E)) = C - BF/E
      x = (C - BF/E)/(A-(DB/E))
        = ((CE - BF) / E) / ((AE-DB) / E)
      
      x = (CE - BF) / (AE - DB)
      

      我为我的回答范围道歉,但我的意思是尽可能清楚,因此我几乎是一步一步地回答的。

      【讨论】:

        【解决方案5】:

        我将重新表述 yairchu 的答案,以便更清楚(无论如何对我来说)。

        暂时忽略中心坐标,在原点画圆。说服自己以下几点:

        1. 圆弧与轴相交的任何地方都是最大值或最小值。
        2. 如果圆弧不与轴相交,则中心将是边界矩形的一个角,这是唯一的情况。
        3. 唯一需要考虑的其他可能的扇区极值点是半径的端点。

        您现在最多可以找到 4+1+2 个点。找到这些坐标的最大值和最小值以绘制矩形。

        通过将原始圆心的坐标添加到矩形的坐标,可以轻松地将矩形转换为原始圆。

        【讨论】:

        • 格伦也为你+1。我得到了yairchu解释的要点,但你确实让它更清楚了一点。干杯。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-07-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-08-17
        • 2020-08-25
        相关资源
        最近更新 更多