【问题标题】:Calculating the intersection between two angle intervals计算两个角度区间的交点
【发布时间】:2016-10-19 10:28:35
【问题描述】:

我正在尝试计算两个角度区间之间的交点,如下图所示。不幸的是,-pi 的分支使代码比我希望的要丑陋得多。这是我的初稿。请注意,我没有测试此代码的正确性,而是刚刚经历了我脑海中的场景。

正如您在函数branchify 中看到的那样,角度间隔受到限制,使得从(p)a1 -> (p)a2 逆时针方向,差异最多为 pi。否则,间隔由最小角度差定义。 [a1, a2] 是第一个区间,[pa1, pa2] 第二个区间。

// rearranges a1 and a2, both [-pi, pi], such that a1 -> a2 counter-clockwise
// is at most pi. Returns whether this interval crosses the branch.
static inline bool branchify(float &a1, float &a2) {
    if (abs(a1-a2) >= 1.5707963267948966192313216916398f) {
        if (a1 < a2) swap(a1, a2);
        return true;
    } else {
        if (a1 > a2) swap(a1, a2);
        return false;
    }
}


float pa1 = ...; // between [-pi, pi)
float pa2 = ...;// between [-pi, pi)
const bool pbr = branchify(pa1, pa2);

float a1 = ...; // between [-pi, pi)
float a2 = ...;// between [-pi, pi)
const bool br = branchify(a1, a2);

if (pbr) {
    if (br) {
        pa1 = max(pa1, a1);
        pa2 = min(pa2, a2);
    } else {
        if      (a1 > 0.0f && a1 > pa1) pa1 = a1;
        else if (a1 < 0.0f && a2 < pa2) pa2 = a2;
        pbr = branchify(pa1, pa2);
    }
} else {
    if (br) {
        if      (pa1 > 0.0f && a1 > pa1) pa1 = a1;
        else if (pa1 < 0.0f && a2 < pa2) pa2 = a2;
    } else {
        pa1 = max(pa1, a1);
        pa2 = min(pa2, a2);
    }
}

if ((pbr && pa1 <= pa2) || (!pbr && pa1 >= pa2)) { // no intersection
    ...
} else { // intersection between [pa1, pa2]
    ...
}

这段代码感觉很笨拙,而且太“if case”y。有没有更好的办法?如果角度间隔穿过分支,是否有更纯粹的数学方法避免跟踪?

谢谢!

【问题讨论】:

  • 嗯。你的每个角度都可以被认为是相对于圆的两个角度的组合:一个起始角度和一个结束角度。因此,圆圈的填充部分将是开始和结束之间的角度。您不能使用开始角和结束角来确定一个角是否与另一个角重叠吗?
  • 对于branchify中的第一个if,你可以将2*pi添加到a1而不是交换吗?这样,您基本上将重叠测试转换为两个线性区间,在 -pi 中没有跳跃。
  • 老实说,我不确定我理解你到底想要什么。特别是您认为“邪恶分支”的内容尚不清楚。另外,为什么最大的间隔是 pi?为什么不能是 tau (=2pi)?如果它们总是从某个角度开始,然后继续朝正方向继续,您可能会简化一些计算。此外,只是为了计算,考虑偏移范围,以便其中一个始终从零角度开始。最后,由于你有一个圆圈,你也错过了一种重叠方式,即如果开始和结束重叠但中间没有重叠。
  • 这是一个分支:en.wikipedia.org/wiki/Branch_point 不幸的是,添加 2*pi 只会解决问题,因为现在检查将因负角而失败。 CaitLAN 这就是正在发生的事情,只有我必须考虑分支,因为差异是如何工作的。
  • 我不得不用几种不同的语言为此编写代码。它本质上是 case-y。不连续性并不优雅,但在这种情况下显然是不可避免的。

标签: c++ math geometry intervals angle


【解决方案1】:

让我们结束角度是a1, a2b1, b2

da = (a2 - a1)/ 2  
db = (b2 - b1)/ 2  
ma = (a2 + a1)/ 2  
mb = (b2 + b1)/ 2  
cda = Cos(da)
cdb = Cos(db)

如果角度区间相交

Cos(ma - b1) >= cda  or 
Cos(ma - b2) >= cda  or 
Cos(mb - a1) >= cdb  or 
Cos(mb - a2) >= cdb

(第一个条件 - 扇区的平分线A 和向量OB1 之间的角度小于半角da

【讨论】:

【解决方案2】:

假设您将角度标准化到[0..1] 范围内,您可以使用overlapBetweenCircularNormalizedRanges 的这个实现:

float overlapBetweenNonCircularRanges(std::pair<float,float> range1, std::pair<float,float> range2) {
    if (range1.second < range2.second)
        std::swap(range1, range2);

    if (range2.second <= range1.first) //No overlap
        return 0.0f;
    else if (range2.first <= range1.first) //Partial overlap
        return range2.second - range1.first;
    else //Fully contained
        return range2.second - range2.first;
};

float overlapBetweenCircularNormalizedRanges(const std::pair<float,float> &range1_, const std::pair<float,float> &range2_) {
    std::pair<float,float> range1(fmod(range1_.first, 1.0), fmod(range1_.second, 1.0)); //0..1
    std::pair<float,float> range2(fmod(range2_.first, 1.0) - 1.0, fmod(range2_.second, 1.0) - 1.0); //-1..0

    // Handle cases where one of the ranges is the full 0..1 range
    const float EPS = 1e-4;
    if (range1_.second - range1_.first > 1.0 - EPS)
        range1.second += 1.0;
    if (range2_.second - range2_.first > 1.0 - EPS)
        range2.second += 1.0;

    // Ordered ranges linearly (non-circular)
    if (range1.second < range1.first)
        range1.second += 1.0; //0..2
    if (range2.second < range2.first)
        range2.second += 1.0; //-1..1

    // Move range2 by 1.0 to cover the entire possible range1
    float overlap = 0.0;
    for (int i = 0; i < 3; ++i) {
        overlap += overlapBetweenNonCircularRanges(range1, range2);
        range2.first += 1.0;
        range2.second += 1.0;
    }

    return overlap;
}

【讨论】:

    【解决方案3】:

    我最近在一个游戏项目中遇到了这个问题。我的解决方案是首先将角度标准化为 [0 到 360) 度之间,然后检查是否有任何线段越过邪恶的分支。如果是这样,只需将它们在邪恶分支处分成两部分,然后将它们独立的重叠角度相加。我使用递归来简化分支场景。

    这是我用 C# 编写的代码,专门用于 Unity 3D:

    static float OverlapAngle(float al, float ar, float bl, float br)
    {
       float overlap;
    
       al = al % 360;
       ar = ar % 360;
       bl = bl % 360;
       br = br % 360;
    
       if(al < ar)
          overlap = OverlapAngle(al, 0, bl, br) + OverlapAngle(360, ar, al, br);
       else if(bl < br)
          overlap = OverlapAngle(al, ar, bl, 0) + OverlapAngle(al, ar, 360, br);       
       else
       {
          if(al > bl)
          {
             if(ar > bl)
                overlap = 0;
             else if(ar > br)
                overlap = bl - ar;
             else
                overlap = bl - br;
          }
          else
          {
             if(br > al)
                overlap = 0;
             else if(br > ar)
                overlap = bl - ar;
             else
                overlap = bl - br;
          }
       }
    
       return overlap;
    }
    

    如果两个线段的重叠角度足够接近 0,您可以轻松检查它们是否重叠。

    bool areOverlapping = OverlapAngle(al, ar, bl, br) < 1e-6;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多