我可以使用什么算法来确定半圆内的点?

时间:2009-02-11 00:50:01

标签: algorithm optimization geometry

我有一个二维点列表,我想获得它们中的哪一个属于半圆形。

最初,目标形状是与x和y轴对齐的矩形。因此,当前算法通过它们的X坐标和二进制搜索对对进行排序,以便第一个可以落入矩形内。然后它按顺序迭代每个点。当它击中超出目标矩形的X和Y上限的那个时,它会停止。

这对于半圆不起作用,因为您无法确定它的有效上/下x和y边界。半圆可以具有任何方向。

最糟糕的情况是,我会在半圆中找到维度(比如x)的最小值,二分搜索到超出它的第一个点,然后依次测试这些点,直到超出该值的上限尺寸。基本上测试整个乐队在网格上的价值点。这样的问题最终将检查许多不在范围内的点。

11 个答案:

答案 0 :(得分:18)

检查点是在半圆内部还是外部(或者是矩形)是一个恒定时间操作。

检查位于半圆或矩形内部或外部的N个点是O(N)。

对N点进行排序为O(N * lg(N))。

按顺序测试所有点的速度比逐行排序然后基于二进制搜索快速剔除点的速度更快。

这可能是那些看似快速和快速的东西的两种不同的事情之一。

修改

还有一种简单的方法可以测试半圆中一个点的包含,而不会在旋转,变换等情况下进行测量。

将半圆表示为两个组成部分:

  • a b 的线段,表示半圆的直径
  • 左侧右侧的方向,表示半圆位于线段左侧或右侧 ab a b
  • 旅行时

您可以利用右手规则来确定该点是否在半圆内。

然后用一些伪代码来测试点 p 是否在半圆形中:

procedure bool is_inside:
    radius = distance(a,b)/2
    center_pt = (a+b)/2    
    vec1 = b - center_pt
    vec2 = p - center_pt
    prod = cross_product(vec1,vec2) 
    if orientation == 'left-of'
        return prod.z >= 0 && distance(center_pt,p) <= radius
    else
        return prod.z <= 0 && distance(center_pt,p) <= radius

此方法具有不使用任何触发功能的额外好处,您可以通过与平方距离进行比较来消除所有平方根。您还可以通过缓存'vec1'计算,半径计算,center_pt计算以及重新排序几个操作来提前保释来加快速度。但我试图澄清。

'cross_product'返回(x,y,z)值。它检查z分量是正还是负。这也可以通过不使用真正的交叉乘积并仅计算z分量来加速。

答案 1 :(得分:7)

首先,翻译&amp;旋转半圆,使一端在负X轴上,另一端在正X轴上,以原点为中心(当然,你实际上不会平移和旋转它,你'只需得到适当的数字即可翻译并旋转它,并在下一步中使用它们。

然后,您可以将其视为圆形,忽略所有负y值,然后使用X&amp;的平方和的平方根进行测试。 Y,看它是否小于或等于半径。

答案 2 :(得分:5)

  

“也许他们可以蛮力,因为他们有专门的GPU专用。”

如果您拥有GPU,那么还有更多方法可以实现。例如,使用模板缓冲区:

  • 清除模板缓冲区并将模板操作设置为递增
  • 将您的半圆渲染到模板缓冲区
  • 渲染你的积分
  • 回读像素并检查点数值
  • 半圆内的点会增加两次。

This article描述了如何在OpenGL中使用模板缓冲区。

答案 3 :(得分:1)

如果有一个标准算法可以做到这一点,我相信其他人会提出它,但如果没有:你可以尝试按距离圆心的距离对点进行排序,并仅迭代那些距离为小于半圆的半径。或者,如果计算距离很贵,我只是尝试找到半圆的边界框(或者甚至是半圆所在的圆的边界正方形)并迭代该范围内的点。在某种程度上,它取决于点的分布,即你是否期望它们中的大多数或只有一小部分落在半圆内?

答案 4 :(得分:1)

您可以在一个圆圈中找到点,并在给定斜坡的一侧找到点,对吗?

将这两者结合起来。

答案 5 :(得分:1)

这是我编写的函数的一部分,在基于图块的游戏中为武器获得锥形射击弧。

float lineLength;
float lineAngle;
for(int i = centerX - maxRange; i < centerX + maxRange + 1; i++){
    if(i < 0){
        continue;
    }
    for(int j = centerY -  maxRange; j < centerY + maxRange + 1; j++){
        if(j < 0){
            continue;
        }

        lineLength = sqrt( (float)((centerX - i)*(centerX - i)) + (float)((centerY - j)*(centerY - j)));
        lineAngle = lineAngles(centerX, centerY, forwardX, forwardY, centerX, centerY, i, j);

        if(lineLength < (float)maxRange){
            if(lineAngle < arcAngle){
                if( (float)minRange <= lineLength){ 
                    AddToHighlightedTiles(i,j);
                }
            }
        }
    }
}

变量应该是自解释的,线角度函数需要2行并找到它们之间的角度。 forwardX和forwardY只是从中心X和Y开始的正确方向上的一个平铺,基于您指向的角度。使用switch语句可以轻松获得这些平铺。

float lineAngles(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4){
    int a = x2 - x1;
    int b = y2 - y1;
    int c = x4 - x3;
    int d = y4 - y3;

    float ohSnap = ( (a * c) + (b * d) )/(sqrt((float)a*a + b*b) * sqrt((float)c*c + d*d) );
    return acos(ohSnap) * 180 / 3.1415926545f;
}

答案 6 :(得分:0)

  • 将弧的中心转换为原点
  • 计算纵坐标轴与半螺旋半径终点之间的角度
  • 使用相同的dx,dy
  • 翻译相关点
  • 计算从原点到平移的x,y的距离,如果d>圆/弧消除半径

    • 计算纵坐标轴和终点之间的角度
    • 如果角度不在半螺旋的起始和结束弧之间,则消除

    点重新应该在半圆里面

答案 7 :(得分:0)

我猜有人在这里找到了与我相同的解决方案,但我没有代码可以显示它在我的记忆中相当远......

我是按步骤做的...
 我想看看我是否在一个圆圈内......如果是的话,看看圆圈的哪一边  2.通过绘制来自半球形矢量的法线矢量。我可以知道我是在后面还是在矢量前面...如果你知道哪一侧是半球形,哪一侧是空洞...如果你在内部那将是非常容易找到的半球形。你必须做点积。

我不确定它是否足够清楚,但测试不应该那么难......最后你必须寻找一个负面或正面的价值......如果它是0,你就是半球的矢量所以你可以说它是在半球的外面还是半球内。

答案 8 :(得分:0)

执行此操作的最快方法取决于您的典型数据。如果您要查看真实数据,请先执行此操作。当点在半圆之外时,通常是因为它们在圆外?你的半圆圈通常是薄饼片吗?

使用矢量有几种方法可以做到这一点。您可以将圆缩放为单位圆并使用交叉积并查看合成矢量。您可以使用点积,看看预期点如何落在其他向量上。

如果你想要一些非常容易理解的东西,首先要检查它是否在圆圈内,然后获得角度,并确保它在两个矢量的角度之间,这决定了半圆。


编辑:我忘记了半圆总是半圈。我在考虑一个圆圈的任意部分。

既然我记得半圆是什么,我就是这样做的。它与stbuton的解决方案类似,但它以不同的方式表示半圆。

我将半圆表示为将半圆一分为二的单位向量。您可以通过交换x和y并取消其中一个来轻松地从指示半圆边界的任何一个向量(因为它们与表示相距90度)中得到它。

现在,您只需越过通过从圆的中心减去要测试的点而创建的矢量。 z的符号告诉您该点是否在半圆中,并且z的长度可以与半径进行比较。

我为Cool Pool做了所有物理学(来自Sierra Online)。这一切都是在矢量中完成的,它充满了点和十字架。矢量解决方案很快。 Cool Pool能够在P60上运行,并且做了合理的休息甚至旋转。

注意:对于您正在检查sqrt(x x + y y)的解决方案,请不要执行sqrt。相反,保持半径的平方并与之比较。

答案 9 :(得分:0)

这似乎是一个简单的方案。

  1. 首先计算凸包,减少集合中的点数。只有凸包上的点才会促成与任何凸边界形状的任何相互作用。因此,只保留船体周边的点子集。

  2. 可以很容易地认为,最小半径边界半圆必须使凸包的一个边(两个点)沿着半圆的直径重合。即,如果船体的某些边缘不在直径中,那么存在一个不同的半圆,其直径较小,包含相同的一组点。

  3. 按顺序测试每个边缘。 (凸壳通常具有相对较少的边缘,因此这将很快。)现在它成为一个简单的一维最小化问题。如果我们选择假设有问题的边缘在于直径,那么我们只需要找到球体的中心。它必须位于我们考虑为直径的当前线上。因此,作为沿当前直径的点位置的函数,只需找到距离标称中心最远的点。通过最小化该距离,我们发现沿该线的最小半圆的半径为直径。

  4. 现在,只需选择在凸包的所有边缘找到的最佳半圆。

答案 10 :(得分:0)

如果您的点具有整数坐标,则最快的解决方案可能是查找表。由于半圆是凸的,对于每个y坐标,你得到一个固定的x范围,所以查找表中的每个条目都给出了最大和最小的X坐标。

当然你还需要预先计算表格,如果你的半圆没有固定,你可能会做很多事情。也就是说,这基本上是渲染半圆的一部分 - 通过反复调用水平线绘制函数,整个形状将呈现为一系列水平跨度。

要首先计算跨度(如果需要反复进行),您可能想要查找Michael Abrash的图形编程Zen的旧版本。这描述了Bresenhams众所周知的线算法,以及不那么着名的Hardenburghs圈算法。结合两者的跨度版本来快速计算半圆的跨度应该不会太难。

IIRC,Hardenburgh使用x ^ 2 + y ^ 2 = radius ^ 2,但利用了你跨越跨度以避免计算平方根的事实 - 我认为它使用的事实是(x + 1)^ 2 = x ^ 2 + 2x + 1和(y-1)^ 2 = y ^ 2 - 2y + 1,保持x,y,x ^ 2和(radius ^ 2 - y ^ 2)的运行值,因此每个步骤只需要比较(当前x ^ 2 + y ^ 2太大)和一些添加。它仅用于一个八分区(确保单像素步骤的唯一方法),并通过对称扩展到整圆。

一旦完成整个圆圈的跨度,就应该很容易使用Bresenhams切断你不想要的那一半。

当然,如果您完全确定需要(并且可以使用整数),那么您只会执行所有这些操作。否则坚持使用stbuton。