【问题标题】:Determine if angle lies between 2 other angles确定角度是否位于其他两个角度之间
【发布时间】:2012-07-10 03:29:17
【问题描述】:

我试图弄清楚一个角度是否位于其他两个角度之间。我一直在尝试创建一个简单的函数来执行此操作,但我的任何技术都不适用于所有可能的角度值。

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

在上图中;我使用绿点作为中心点,然后确定每条线与绿点的角度。然后我计算黑点到绿点的角度。我正在尝试检查黑点的角度是否在 BETWEEN 两条线的角度。

注意:就我而言;如果两个角度之间的差

以下代码应该可以工作,但对于这些(确实位于角度之间)失败:
- 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;
}

【问题讨论】:

  • 为什么不把这两个角和你的中心点当作一个三角形,看看黑点是否落在里面?
  • @NickSavage 好主意 :) 你能说出一个数学公式来检查点是否落在三角形内吗?或者我可以使用简单的 sin、cos、tan 来实现吗?
  • 几个问题。所有角度是否 >=0 且
  • @NickSavage 你要允许定义三角形的两条线多远?
  • 检查this 在三角形内的点

标签: c++ math trigonometry


【解决方案1】:
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) 的情况下不起作用
  • 我刚刚运行了代码,它按预期返回 true,因为 45 度介于 315 和 90 度之间。算法很简单,所以我觉得cmets就足够理解了。您希望澄清哪一部分?
  • 我的立场是正确的。我已将其转换为doubles,但未能将rAngle 转换为double。感谢您再次查看。
  • 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) = 真。 (-360 = -30, 52 不在 -76 和 -30 之间)@JonathanMee 我的代码确定它有两个额外的模块,但它得到了正确的答案。这是一个单元测试小提琴。 jsfiddle.net/5L5xjnkf
  • @Tata​​rize OP 确实指定所有输入都在 [0:360] stackoverflow.com/questions/11406189/… 范围内,因此您也可以删除 1 个 mod。但这是一个很好的澄清评论,因为答案和问题都没有明确指出这一点,只是评论。
【解决方案2】:

灵感来自关于 Intervals in modular arithmetic 的帖子:

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;
}

【讨论】:

  • 我刚刚为这个问题将你的代码翻译成 Python:create a circular list by using a range of angles python
  • 在很多情况下都不起作用,尤其是那些a 不是反身角中最左边的角的情况。例如:point_in_closed_interval(90, 135, 45)
  • @Tata​​rize 在重新阅读了 OP 的问题和其中的 NOTE 之后,我同意他/她确实不想处理定向角度,但总是覆盖该区域
  • @Tata​​rize 我更新的解决方案有时仍会返回与您的不同的结果,但这次我有点确信我的解决方案是正确的:-) 例如x = 44.191604750688704a = 92.99465847152985、@987654330 @我的解决方案返回true,而您的解决方案返回false
  • 是的,我的示例使用 ^ 来验证符号更改。 a^b>0,它是为整数构建的,所以在 ints 中 xor 确保符号发生变化。但是,在 javascript 中显然不是。将其切换为 * 修复了它。虽然你的解决方案比我的有一些优雅(即使它不起作用)。
【解决方案3】:
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;
}

【讨论】:

  • 一直在对这些代码元素进行单元测试。这是第一个(除了我的)实际上适用于所有值的。 jsfiddle.net/9vnncaas
  • 这个解决方案被低估了。它优雅地解决了我尝试过的所有测试。
【解决方案4】:

如果角度 2 始终为 0,角度 1 始终在 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) = false 之间的角度。是角度 53,在 49 和 68 之间。是的,这个答案说不。 -- 还有一个 JSFiddle 单元测试。 jsfiddle.net/4agzx30h
【解决方案5】:

我以前通过比较角度来做到这一点。

在上面的草图中,当且仅当,矢量 AD 将在 AB 和 AC 之间

angle BAD + angle CAD == angle BAC

由于浮点数不准确,我先将这些值四舍五入到小数点后 5 位后进行比较。

所以归结为在两个向量 pq 之间有一个角度算法,简单地说如下:

double a = p.DotProduct(q);
double b = p.Length() * q.Length();
return acos(a / b); // radians

我会将向量 DotProduct 和 Length 计算留作 google 搜索练习。您只需通过从另一个终端减去一个终端的坐标即可获得向量。

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

【讨论】:

    【解决方案6】:

    这里所有的热门答案都是错误的。因此,我觉得有必要发布一个答案。

    我只是重新发布我在此处发布的答案的一部分:https://stackoverflow.com/a/42424631/2642059 该答案还涉及您已经知道哪个角度是自反角的左侧和右侧的情况。但是你还需要确定角的哪一边是哪一边。


    1st 如果这些陈述中的任何一个为真,则找到最左边的角度angle1 是您的最左边的角度:

    1. angle1 &lt;= angle2 &amp;&amp; angle2 - angle1 &lt;= PI
    2. angle1 &gt; angle2 &amp;&amp; angle1 - angle2 &gt;= PI

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

    这里的问题是看起来。我们正在寻找的主要有 3 个阳性案例:

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

    由于您正在计算角度的左手边和右手边,您会注意到这里有一个优化机会同时执行这两个过程。您的函数将如下所示:

    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π] 范围内。

    Live Example

    【讨论】:

      【解决方案7】:

      是角度A和B之间的角度T,总是有两个答案:真假。

      我们需要说明我们的意思,在这种情况下,我们正在寻找归一化的小扫掠角以及我们的角度是否在这些值之间。给定任意两个角度,它们之间有一个反射角,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);
      }
      

      我们还可以做一些技巧来简化代码并避免任何不需要的模运算(参见下面的 cmets)。 Normalize 会将角度 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 test of the code

      【讨论】:

      • 所以至少你的代码中有一个错误,你使用 xor 运算符 (^) 我不知道你的意图,但这在大多数情况下不起作用,例如isAngleBetweenNormalizedSmallSweepRange(90, 45, 135)
      • 它旨在对第 32 位即符号位进行 XOR。如果您对任何正数进行 XOR 任何负数,那么无论答案是什么,答案都应该是否定的。我正在测试标志的差异。因此,如果是正/正或负/负,它应该是 0,因此是一个正数。任何负数/正数、正数/负数它都应该产生一个负数,从而使 vs 0 失败。你可以将它们相乘,应该得到相同的结果。
      • ((45-90) % 360) - 180 = -45 - 180, (((135 - 90) % 360) 45 - 180, 这是 -225 和 -135 都是负数所以 - 225 XOR -135 应该是正数 102。由于 102
      • 现在它几乎总是返回true,这不是一件好事。例如:isAngleBetweenNormalizedSmallSweepRange(180, 45, 135) 不应返回 true。这个答案大大my own 简单,但花了一些时间解决这个问题,如果你能用像符号 xor 这样简单的东西来解决它,我会感到震惊。
      • 值 (a - test + 180),范围为 [-180°,540°],需要在 [-180°,180°] 范围内,所以如果我们确定每个变量的范围为 [0°,360°],我们可以使用单个 if 语句简单地将范围 [180°,540°] 滚动到范围中。 if (n > 360) n -= 360;, 给定结果减法。这是测试值。尽管请注意 0,360 范围之外的任何内容都将开始失败。但是,它使用零模组。 jsfiddle.net/zLnseLjk
      【解决方案8】:

      我从this 线程中找到了这句话:

      如果点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)

      希望对你有所帮助

      【讨论】:

        【解决方案9】:

        使用与您的问题类似的函数样式,我对以下方法很幸运:

            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 题。
        • var 不是 Java。
        • 不起作用。 jsfiddle.net/at7nvy4e 单元测试小提琴,IsInsideRange(44, 120, -160) = true,它没有。等等。不考虑诸如 0、179、-179 之类的东西,而该角度似乎是该角度内的最小角度,它可能会返回 true。
        【解决方案10】:

        我知道这篇文章很旧,但似乎没有公认的答案,我发现以下方法非常可靠。尽管它可能超出您的需要。它支持大于 180 度的角度范围(以及大于 360 度和负角度)。它还支持小数精度。

        该方法使用此normalize() 辅助函数将角度转换为正确的空间:

        float normalize( float degrees )
        {
          //-- Converts the specified angle to an angle between 0 and 360 degrees
          float circleCount = (degrees / 360.0f);
          degrees -= (int)circleCount * 360;
          if( 0.0f > degrees )
          {
            degrees += 360.0f;
          }
          return degrees;
        }
        

        解决办法如下:

        bool isWithinRange( float start, float end, float angle )
        {
          if( fabsf( end - start ) >= 360.0f )
          {
            //-- Ranges greater or equal to 360 degrees cover everything
            return true;
          }
        
          //-- Put our angle between 0 and 360 degrees
          float degrees = normalize( angle );
        
          //-- Resolve degree value for the start angle; make sure it's
          //   smaller than our angle.
          float startDegrees = normalize( start );
          if( startDegrees > degrees )
          {
            startDegrees -= 360.0f;
          }
        
          //-- Resolve degree value for the end angle to be within the
          //   same 360 degree range as the start angle and make sure it
          //   comes after the start angle.
          float endDegrees = normalize( end );
          if( endDegrees < startDegrees )
          {
            endDegrees += 360.0f;
          }
          else if( (endDegrees - startDegrees) >= 360.0f )
          {
            endDegrees -= 360.0f;
          }
        
          //-- All that remains is to validate that our angle is between
          //   the start and the end.
          if( (degrees < startDegrees) || (degrees > endDegrees) )
          {
            return false;
          }
        
          return true;
        }
        

        希望这对某人有所帮助。

        【讨论】:

        • jsfiddle.net/rebbhxjg单元测试小提琴ISWithInRange(97.71171765113701,90.92818068907809,-241.47414859955634)= TRUE?,ISWithInRange(179.5807788226134,-188.05066205255324,14.247781098361713)= TRUE? - 我认为其中很多是以非标准化的方式处理角度。虽然这并不能解释 isWithinRange(177, 162, 355) = true 之类的事情,但 355 不在这两个角度之间。 (不是变量的顺序使她与往常不同)。
        【解决方案11】:

        如果您有角度 $$a$ 和 $b$,并且不想查看角度 x 是否在这些角度之间。

        您可以计算a-&gt;xa-&gt;b 之间的角度。 如果∠a-&gt;x 小于∠a-&gt;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);
        }
        

        这假设您使用的是弧度,而不是度数。它删除了很多不必要的代码。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-12-28
          • 1970-01-01
          • 2017-04-26
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多