扇区的二维边界框?

Posted

技术标签:

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

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

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

【问题讨论】:

史蒂夫,***.com/questions/622140/… 呢? @Matt:不完全是我想要的,但它确实给了我一些想法。 【参考方案1】:

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

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

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

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

    很难确定角度跨度是否包括角度 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 的答案,以便更清楚(无论如何对我来说)。

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

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

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

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

【讨论】:

格伦也为你+1。我得到了yairchu解释的要点,但你确实让它更清楚了一点。干杯。

以上是关于扇区的二维边界框?的主要内容,如果未能解决你的问题,请参考以下文章

二维对象相交

MBR主引导扇区解析

实现二维数组顺时针旋转的功能

python 二维平面中的分类问题画出决策边界

二维码相关知识

最小外接矩形的简介