确定角度是否位于另外两个角度之间

19

我正在尝试确定一个角度是否位于另外两个角度之间。我一直在试图创建一个简单的函数来执行此操作,但是我的所有技术都无法适用于所有可能的角度值。

你能帮我编辑我的函数以正确确定一个角度是否位于另外两个角度之间吗?

enter image description here

在上面的图片中,我使用绿点作为中心点,然后确定每条线相对于绿点的角度。然后计算黑点相对于绿点的角度。我想检查黑点的角度是否在两条线的角度之间。
注意:在我的情况下,如果两个角度之间的差小于180度,并且目标角度位于这两个角度所形成的凹形区域内,则称一个角度(targetAngle)位于另外两个角度之间。
以下代码应该可以工作,但它却失败了(因为这些角度确实位于两个角度之间): - is_angle_between(150, 190, 110) - is_angle_between(3, 41, 345)
bool is_angle_between(int target, int angle1, int angle2) 
{  
  int rAngle1 = ((iTarget - iAngle1) % 360 + 360) % 360;  
  int rAngle2 = ((iAngle2 - iAngle1) % 360 + 360) % 360;  
  return (0 <= rAngle1 && rAngle1 <= rAngle2);  
}  

// Example usage  
is_angle_between(3, 41, 345);  

我尝试过的另一种技术,也无法实现:

int is_angle_between(int target, int angle1, int angle2)
{
  int dif1  = angle1-angle2;
  int dif2  = angle2-angle1;
  int uDif1 = convert_to_positive_angle( dif1 ); // for eg; convert -15 to 345
  int uDif2 = convert_to_positive_angle( dif2 );

  if (uDif1 <= uDif2) {
    if (dif1 < 0) {
      return (target <= angle1 && target >= angle2);
    }
    else return (in_between_numbers(iTarget, iAngle1, iAngle2));
  }
  else {
    if (dif2 < 0) {
      return (target <= angle1 && target >= angle2);
    }
    else return (in_between_numbers(iTarget, iAngle1, iAngle2));
  }

  return -1;
}

2
为什么不将两个角和中心点视为三角形,并检查黑点是否落在其中? - Nick Savage
@NickSavage 不错的主意 :) 你能给出一个可以检验一个点是否在三角形内部的数学公式吗?或者我能否使用简单的正弦、余弦、正切来实现呢? - sazr
1
几个问题。所有角度都是 >=0 和 <360 吗?另外,鉴于您的测试用例为 3、41 和 345,您是在询问 3 是否在 41 和 345 度之间吗?这应该是错误的,对吧? - HeatfanJohn
所有角度都大于等于0且小于等于360。对于您的第二个问题,不,它是正确的,3度位于由41度和345度角所形成的最小凹腔内。 - sazr
这个回答解决了你的问题吗?计算一个角度是否在两个角度之间 - iacob
显示剩余7条评论
11个回答

11
bool is_angle_between(int target, int angle1, int angle2) 
{
  // make the angle from angle1 to angle2 to be <= 180 degrees
  int rAngle = ((angle2 - angle1) % 360 + 360) % 360;
  if (rAngle >= 180)
    std::swap(angle1, angle2);

  // check if it passes through zero
  if (angle1 <= angle2)
    return target >= angle1 && target <= angle2;
  else
    return target >= angle1 || target <= angle2;
}  

请不要只发布代码答案。同时提供代码的解释,说明它在做什么以及为什么有效。例如,您的代码在 is_angle_between(45, 90, 315) 的情况下无法正常工作。 - Jonathan Mee
4
我刚刚运行了代码,如预期一样返回了true,因为45度在315度和90度之间。该算法相当简单,因此我认为注释足以理解它。您希望澄清哪个部分? - fdermishin
我错了。我已经将这个转换为“doubles”,但没有将“rAngle”转换为“double”。谢谢你再次查看它。 - Jonathan Mee
1
is_angle_between(450, 45, 135) = false,is_angle_between(90, 405, -225) = false,is_angle_between(45,180,-230) =true。is_angle_between(52, -76, -390) = true。(-360 = -30,52不在-76和-30之间)@JonathanMee 我的代码确实有两个额外的模块,但它得到了正确的答案。这是一个单元测试fiddle。https://jsfiddle.net/5L5xjnkf/ - Tatarize
@Tatarize,OP确实指定了所有输入都在[0:360]范围内https://dev59.com/kmgu5IYBdhLWcg3wU1mN#11412077?noredirect=1#comment15040614_11406189,所以您可能可以删除其中一个mod。但这是一个很好的澄清评论,因为答案和问题都没有明确说明,只有评论。 - Jonathan Mee

9

受到有关模算术中区间的帖子启发:

static bool is_angle_between(int x, int a, int b) {
    b = modN(b - a);
    x = modN(x - a);

    if (b < 180) {
        return x < b;
    } else {
        return b < x;
    }
}

在检查角度时,modN() 的实现方式如下:

// modN(x) is assumed to calculate Euclidean (=non-negative) x % N.
static int modN(int x) {
    const int N = 360;
    int m = x % N;
    if (m < 0) {
        m += N;
    } 
    return m;
}

1
我刚刚为这个问题将您的代码翻译成了Python:create a circular list by using a range of angles python - PM 2Ring
1
在许多情况下不起作用,特别是当a不是反射角中最左边的角时。例如:point_in_closed_interval(90, 135, 45) - Jonathan Mee
1
@Tatarize 在重新阅读原帖问题和其中的注意后,我同意他/她确实不想处理定向角度,而是始终覆盖小于等于180°的区域。我会调整我的答案。 - sschuberth
1
@Tatarize 我更新的解决方案有时仍然会返回与你的不同的结果,但这次我有些自信我的解决方案是正确的 :-) 对于 x = 44.191604750688704a = 92.99465847152985b = 43.93893024446933 的示例,我的解决方案返回 true,而你的返回 false - sschuberth
1
是的,我的示例中使用^来验证符号变化。a ^ b>0,它是针对整数构建的,因此在整数中,异或确保存在符号变化。但是,在JavaScript中显然不是这样。将其更改为*就可以解决了。尽管您的解决方案比我的更简洁(即使在它不起作用时也是如此)。 - Tatarize
显示剩余5条评论

5
void normalize( float& angle ) 
{
    while ( angle < -180 ) angle += 360;
    while ( angle >  180 ) angle -= 360;
}

bool isWithinRange( float testAngle, float a, float b )
{
    a -= testAngle;
    b -= testAngle;
    normalize( a );
    normalize( b );
    if ( a * b >= 0 )
        return false;
    return fabs( a - b ) < 180;
}

1
一直在对这些代码元素进行单元测试。这是第一个(除了我的之外),可以适用于所有值的代码。 https://jsfiddle.net/9vnncaas/ - Tatarize
这个解决方案被低估了。它优雅地解决了我尝试的所有测试。 - Yoav Moran

2
如果angle2始终为0,而angle1始终在0到180之间,那么这将很容易:
return angle1 < 180 && 0 < target && target < angle1;

如果我正确理解了要求。

但是达到这个目标并不难。

int reduced1 = (angle1 - angle2 + 360) % 360; // and imagine reduced2 = 0
if (180 < reduced1) { angle2 = angle1; reduced1 = 360 - reduced1; } // swap if backwards
int reducedTarget = (target - angle2 + 360) % 360;
return reduced1 < 180 && 0 < reducedTarget && reducedTarget < reduced1;

(-307, -311, 68)的角度是否为假:否。53度是否在49和68之间:是的,这个答案说不是。-- 还有一个JSFiddle单元测试。https://jsfiddle.net/4agzx30h/ - Tatarize

1
如果角度T在角度A和B之间,就会有两个答案:真和假。
我们需要明确我们的意思,在这种情况下,我们正在寻找标准化的小扫描角度,以及我们的角度是否在这些值之间。给定任意两个角度,它们之间存在一个反射角度,T的标准化值是否在该反射角度内?
如果我们旋转A、B和T,使得T=0,并将A和B标准化为+-半圆周(180°或2PI)内。那么我们的答案是A和B是否有不同的符号,并且彼此之间在半圆周内。
如果我们从测试中减去角度,然后添加180°(使A相对于T+180),然后对360取模,得到一个范围在[-360°,360°]之间的值,再加上360°并再次取模(注意,您也可以检查它是否为负数,如果是则加上360),得到一个确定在[0°,360°]之间的值。我们减去180°,得到一个相对于T+180°-180°即T的值在[-180°,180°]之间的值。所以T现在是零度角,所有角度都在标准化范围内。现在我们检查角度是否有符号变化,并且它们之间的距离不超过180°,我们就得到了答案。
因为问题是用C++提出的:
bool isAngleBetweenNormalizedSmallSweepRange(int test, int a, int b) {
    int a_adjust = ((((a - test + 180)) % 360) + 360) % 360 - 180;
    int b_adjust = ((((b - test + 180)) % 360) + 360) % 360 - 180;
    return ((a_adjust ^ b_adjust) < 0) && ((a_adjust - b_adjust) < 180) && ((a_adjust - b_adjust) > -180);
}

我们也可以使用一些技巧来简化代码并避免不必要的模运算(请参见下面的注释)。规范化将角度a相对于角度t移动到范围[-180°,180°]内。
int normalized(int a, int test) {
    int n = a - test + 180;
    if ((n > 360) || (n < -360)) n %= 360;
    return (n > 0)? n - 180: n + 180;
}

bool isAngleBetweenNormalizedSmallSweepRange(int test, int a, int b) {
    int a_adjust = normalized(a,test);
    int b_adjust = normalized(b,test);
    return ((a_adjust ^ b_adjust) < 0) &&
            ((a_adjust > b_adjust)? a_adjust-b_adjust: b_adjust-a_adjust) < 180;
}

如果我们可以确定范围是[0,360],我们可以使用更简单的if语句。
bool isAngleBetweenNormalizedSmallSweepRange(int test, int a, int b) {
    int dA = a - test + 180;
    if (dA > 360) {
        dA -= 360;
    }
    int a_adjust = (dA > 0) ? dA - 180 : dA + 180;
    int dB = b - test + 180;
    if (dB > 360) {
        dB -= 360;
    }
    int b_adjust = (dB > 0) ? dB - 180 : dB + 180;
    return ((a_adjust ^ b_adjust) < 0)
            && ((a_adjust > b_adjust) ? a_adjust - b_adjust : b_adjust - a_adjust) < 180;
}

JS Fiddle代码测试


所以,至少你的代码中存在一个错误,即你正在使用异或运算符(^)。我不知道你的意图是什么,但在大多数情况下,这是行不通的,例如 isAngleBetweenNormalizedSmallSweepRange(90, 45, 135) - Jonathan Mee
旨在对第32位即符号位进行异或运算。如果您将任何正数与任何负数进行异或运算,则答案应为负数,无论它是什么。我正在测试符号之间的差异。因此,如果是正/正或负/负,则应为0,因此是正数。任何负/正、正/负都应该得到一个负数,从而失败于vs 0。不过,您也可以将它们相乘,应该会得到相同的结果。 - Tatarize
((45-90) % 360) - 180 = -45 - 180,(((135 - 90) % 360) 45 - 180,这是-225和-135都是负数,所以-225 XOR -135应该是正数102。由于102 < 0,它只是被翻转了。--与其说在大多数情况下不起作用,不如说它在任何情况下都不起作用。 - Tatarize
1
值(a-test+180)的范围为[-180°,540°],需要在[-180°,180°]范围内,因此如果我们确保每个变量的范围为[0°,360°],我们可以使用一个if语句将范围[180°,540°]简单地转换为范围。如果(n> 360)n- = 360;,给出了减法结果。这是测试值。请注意,任何超出0,360范围的内容都将开始失败。但是,它使用零模数。 https://jsfiddle.net/zLnseLjk/ - Tatarize
看起来不错的解决方案。我看到了:9个算术运算,7个条件运算和1个逻辑/乘法运算。这比得到最多赞的解决方案严格优越:2个算术运算,4个条件运算,2个模运算和一个交换。 - Jonathan Mee
显示剩余13条评论

1

我以前通过比较角度做过这个。

enter image description here

在上面的草图中,向量AD将仅在AB和AC之间,如果且仅如果。
angle BAD + angle CAD == angle BAC

由于浮点数不精确,我先将它们四舍五入到5位小数后再进行比较。
因此,这就涉及到两个向量p和q之间的角度算法,简单来说就是:
double a = p.DotProduct(q);
double b = p.Length() * q.Length();
return acos(a / b); // radians

我会把向量点积和长度计算留给你自己在谷歌上搜索。而且你可以通过将一个端点的坐标减去另一个端点的坐标来获得向量。

当然,你应该先检查AB和AC是否平行或反平行。


1
所有的高赞答案都是错的。因此,我觉得有必要发表一个回答。
我只是转载了我在这里发布的答案的一部分:https://stackoverflow.com/a/42424631/2642059该答案还涉及您已经知道哪个角度是反射角的左侧和右侧的情况。但您还需要确定角的哪一侧是哪一侧。

如果以下任何语句为真,则需要找到最左边的角度 angle1

  1. angle1 <= angle2 && angle2 - angle1 <= PI
  2. angle1 > angle2 && angle1 - angle2 >= PI

为了简单起见,假设您的最左边的角度为 l ,最右边的角度为 r ,并且您正在尝试查找是否在它们之间。 g

问题在于外观。 实际上,有三种正面案例我们正在寻找:

  1. l ≤ g ≤ r
  2. l ≤ g ∧ r < l
  3. g ≤ r ∧ r < l

由于您正在计算角度的左侧和右侧,因此您会注意到这里存在优化机会。 您的函数将如下所示:

if(angle1 <= angle2) {
    if(angle2 - angle1 <= PI) {
        return angle1 <= target && target <= angle2;
    } else {
        return angle2 <= target || target <= angle1;
    }
} else {
    if(angle1 - angle2 <= PI) {
        return angle2 <= target && target <= angle1;
    } else {
        return angle1 <= target || target <= angle2;
    }
}

或者,如果您需要,您可以扩展到这个噩梦般的情况:

angle1 <= angle2 ?
(angle2 - angle1 <= PI && angle1 <= target && target <= angle2) || (angle2 - angle1 > PI && (angle2 <= target || target <= angle1)) :
(angle1 - angle2 <= PI && angle2 <= target && target <= angle1) || (angle1 - angle2 > PI && (angle1 <= target || target <= angle2))

请注意,所有这些数学假设您的输入是弧度制,并且在[0:2π]范围内。

实时示例


0

如果你有角度$a$和$b$,并且想要判断角度x是否在这两个角度之间。

你可以计算出a->xa->b之间的角度。 如果∠a->x小于∠a->b,那么x必须在ab之间。

两个角度ab之间的距离。

function distanceBetweenAngles(a, b) {
    distance = b - a;
    if (a > b) {
       distance += 2*pi;
    }
    return distance;
}

然后你可以这样做

// Checks if angle 'x' is between angle 'a' and 'b'
function isAngleBetween(x, a, b) {
    return distanceBetweenAngles(a, b) >= distanceBetweenAngles(a, x);
}

这假设您正在使用弧度,而不是度数,因为应该如此。它可以消除许多不必要的代码。


0
我在这个帖子中找到了这句话:
如果点P在三角形ABC内部,则有:
PAB的面积+PBC的面积+PAC的面积=ABC的面积
请注意,如果P在AB、BC或CA的边缘上,上述公式仍然成立。但是,实际上,PAB、PBC或PAC中的一个面积为0(因此请确保您检查了这一点)。
如果P在外部,则上述等式不成立...
如何确定面积?您有两个选择:1)海伦公式,涉及sqrt,速度较慢;2)更可取的方法是叉积(或有效地是下积和减去上积之和的绝对值的一半)。
例如,如果A =(x1,y1),B =(x2,y2),C =(x3,y3),则面积= abs(x1 * y2 + x2 * y3 + x3 * y1-x1 * y3-x3 * y2-x2 * y1)/ 2
此外,您可能需要小心浮点错误...而不是检查严格的不等式,请检查abs(b-a)。
希望这能帮到您。

0

使用与您问题中类似的函数风格,我尝试了以下方法并取得了良好的效果:

    public static bool IsInsideRange(double testAngle, double startAngle, double endAngle)
    {
        var a1 = System.Math.Abs(AngleBetween(startAngle, testAngle));
        var a2 = System.Math.Abs(AngleBetween(testAngle, endAngle));
        var a3 = System.Math.Abs(AngleBetween(startAngle, endAngle));
        return a1 + a2 == a3;
    }

    public static double AngleBetween(double start, double end)
    {
        return (end - start) % 360;
    }

这是一个C++问题,不是Java问题。 - Jonathan Mee
var 不是Java。 - Tatarize
不起作用。https://jsfiddle.net/at7nvy4e/ 单元测试 fiddle,IsInsideRange(44, 120, -160) = true,实际上并非如此。等等。不考虑0、179、-179这样的情况,尽管该角度似乎是最小的内部角度,但它仍将返回true。 - Tatarize

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接