扇区的二维边界框?
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】:首先,如果我写错了,我深表歉意,但英语不是我的第一语言,西班牙语实际上是!
我遇到了这个问题,我想我找到了一个有效的解决方案。
首先让我们看一下情况的图像
所以我们有一个椭圆(实际上是一个圆)和两个点(C
、D
),它们表示我们的扇区。
我们还有圆心 (B
) 和圆弧的角度 alpha
。
现在,在这种情况下,我让它通过 porpouse 上的 360º
看看它是否可以工作。
假设alpha -> -251.1º
(它是负数导致顺时针方向),让我们将其转换为正值360º - 251.1º = 108.9º
现在我们的目标是找到该角度的二等分角,以便我们可以找到边界框的最大点(图中E
),实际上你可能已经意识到,线段BE
的长度等于圆的半径,但我们必须有角度才能获得E
点的实际坐标。
所以108.9º / 2 -> 54.45º
现在我们有了角度。
为了找到 E 的坐标,我们使用极坐标,所以
x = r * Cos(theta)
y = r * Sin(theta)
我们有 r
和 theta
所以我们可以计算 x 和 y
在我的例子中r = 2.82
...(实际上这是不合理的,但我取前两位小数是为了方便)
我们知道我们的第一个半径是87.1º
,所以theta 是87.1 - 54.45º -> 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 -> (4.23, 5.76)
)
所以我们有Y = mx + b
,其中m = -1.56
、y = 5.76
和x = 4.23
所以b
必须是
b = 5.76 - (-1.56) * 4.23 = 12.36
现在我们有了完整的切线方程 -> Y = -1.56X + 12.36
我们所要做的就是将点 C
和 D
投影到该矩形上。
我们需要矩形 CH
和 DI
的方程,所以让我们计算它们
让我们从CH
开始:
我们知道(从 tanget 方程)我们的方向向量是(1.56, 1)
我们需要找到一个通过点C -> (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解释的要点,但你确实让它更清楚了一点。干杯。以上是关于扇区的二维边界框?的主要内容,如果未能解决你的问题,请参考以下文章