3

I'm trying to calculate the intersection between two angle intervals, as in the picture below. Unfortunately, the branch at -pi is making the code much uglier than I have hoped. Here is my first draft. Note that I have not tested this code for correctness, but rather have just gone through the scenarios in my head.

different types of angle interval intersections

As you can see in the function branchify, angle intervals are constrained such that from (p)a1 -> (p)a2 counter-clockwise, the difference is at most pi. In otherwise, the intervals are defined by the smallest difference in angle. [a1, a2] is the first interval, [pa1, pa2] the second.

// 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]
    ...
}

This code feels clunky and too "if case"y. Is there a better way? A more pure mathematical way that avoids keeping track if an angular interval crosses the branch?

Thanks!

bombax
  • 1,189
  • 8
  • 26
  • Hmm. Each of your angles could be considered a composition of two angles with respect to the circle: a starting angle, and an ending angle. Thus, the filled in part of the circle would be the angle between start and end. Couldn't you use the start and ending angles to determine if one angle overlaps the other? – h0r53 Jun 17 '16 at 14:38
  • For the first if in `branchify`, can you add `2*pi` to `a1` instead of swapping? This way, you essentially convert the test of overlapping to two linear intervals with no jumps in `-pi`. – triple_r Jun 17 '16 at 14:39
  • To be honest, I'm not sure I understand what exactly you want. In particular what you consider an "evil branch" is unclear. Also, why is the largest interval pi? Why can't it be tau (=2pi)? If they always start at a certain angle and then continue for some more in the positive direction, you could possibly simplify some calculations. Also, just for the calculations, consider offsetting the ranges so that one of them always starts at angle zero. Lastly, since you have a circle, you have also missed one way of overlapping, namely if beginning and end overlap but not the middles. – Ulrich Eckhardt Jun 17 '16 at 14:41
  • This is a branch: https://en.wikipedia.org/wiki/Branch_point Unfortunately, adding 2*pi only displaces the problem, because now checks will fail with negative angles. CaitLAN that's what's happening, only I have to consider the branch, because of how the differences work. – bombax Jun 17 '16 at 14:46
  • How about ordering the lines regardless of colors, then summing based on color transition? – lorro Jun 17 '16 at 15:10
  • 1
    I've had to write code for this in several different languages. It is inherently case-y. Discontinuities are NOT elegant, but obviously unavoidable in this case. – gariepy Jun 17 '16 at 17:06

3 Answers3

2

Let's end angles are a1, a2 and b1, b2

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

Then angle intervals intersect if

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

(First condition - angle between bisector of sector A and vector OB1 is less than half-angle da)

MBo
  • 77,366
  • 5
  • 53
  • 86
  • 1
    The question is to find the intersection, not just to determine if they intersects or not – Robin Hsu May 03 '17 at 10:13
  • @Robin Hsu Author did not specify that clearly. BTW, http://stackoverflow.com/questions/40584625 finds intersection. – MBo May 03 '17 at 11:18
1

I recently ran into this issue in a gaming project. My solution is to first normalise the angles to be between [0 and 360) degrees, and then check if any segments crossed an evil branch. If they do, just split them into two sections at the evil branch and then sum up their independent overlapping angles. I used recursion to simplify the branching scenarios.

Here is my code written in C#, specifically for 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;
}

You can easily check if two segments overlap if their overlap angle is close enough to 0.

bool areOverlapping = OverlapAngle(al, ar, bl, br) < 1e-6;
michasaurus
  • 184
  • 2
  • 6
0

Assuming you normalized your angles to the range [0..1], you can use this implementation of 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;
}
Michael Litvin
  • 3,976
  • 1
  • 34
  • 40