0

I need to be able to check whether the angle between three points (A, B and C) which make up part of a shape is reflex (> PI radians), as in the diagram below (sorry for poor paint skills!):

Angle ABC

My points should always be anti-clockwise, and I always want to measure the angle on the inside of the shape.

I am currently doing this using the following code:

//triangle[] is an array of the three points I am testing, corresponding
// to [A, B, C] on the diagram above

//Vectors from B to A and C
PointF toA = PointFVectorTools.difference(triangle[0], triangle[1]);
PointF toC = PointFVectorTools.difference(triangle[2], triangle[1]);

double angle = Math.Atan2(toB.Y, toB.X) - Math.Atan2(toA.Y, toA.X);

//Put angle in range 0 to 2 PI
if (angle < 0) angle += 2 * Math.PI;
return angle > Math.PI;

This has worked in all the cases I have tried up until now, but with these co-ords it does not work: enter image description here

(Where B=(2,3) )

The angle I get back is ~-0.5, whereas I would expect ~+0.5. Any ideas why this is wrong?

UPDATE

I've attempted to implement Nico's solution, and while I understand it in theory I'm getting a real headache trying to implement it. Here is the code so far:

//Vector A -> B
float dx = triangle[1].X - triangle[0].X;
float dy = triangle[1].Y - triangle[0].Y;

//Left normal = (y, -x)
PointF leftDir = new PointF(dy, -dx);

//Vector B -> C
dx = triangle[2].X - triangle[1].X;
dy = triangle[2].Y - triangle[1].Y;

//Dot product of B->C and Left normal
float dot = dx * leftDir.X + dy * leftDir.Y;
return dot < 0;
acernine
  • 729
  • 2
  • 6
  • 16

2 Answers2

1

I'm not sure how toB in your code is defined, and also I'm not familar with PointF.

Anyway you should use the cosine rule c^2 = a^2 + b^2 - 2ab cos(C) (where a,b,c are the lengths of the sides of the triangle, and C is the angle subtending c):

public bool IsReflex(... triangle)
{
    var a = GetVectorLength(triangle[0].x, triangle[0].y, triangle[1].x, triangle[1].y);
    var b = GetVectorLength(triangle[1].x, triangle[1].y, triangle[2].x, triangle[2].y);
    var c = GetVectorLength(triangle[2].x, triangle[2].y, triangle[0].x, triangle[0].y);

    var cosC = (c*c - a*a - b*b) / (2*a*b);
    var C = Math.Acos(cosC); // this returns a value between 0 and pi

    return Math.Abs(C) > (Math.PI/2);
}

private double GetVectorLength(double x0, double y0, double x1, double y1)
{
    // using Pythagoras
    var sideX = x0 - x1;
    var sideY = y0 - y1;
    return Math.Sqrt(sideX*sideX + sideY*sideY);
}
Mike Jerred
  • 9,551
  • 5
  • 22
  • 42
  • The cosine rule formula is c^2 = a^2 + b^2 - 2ab COS (c) isn't it? Cos(x) repeats at intervals of PI so wouldn't that mean that this method would give the same results for an angle of Pi/2 (90 degrees) and 3Pi/2? (270 degrees)? – acernine Mar 13 '16 at 15:46
  • Ooops my bad dunno why I put sin not cos xD – Mike Jerred Mar 14 '16 at 13:33
  • It happens to us all :) I'm still not sure how the cosine rule would help me tell if an angle is reflex, given that acos will always return the acute angle instead of the reflex. – acernine Mar 14 '16 at 18:01
  • Oh I see I misunderstood the question sorry! In that case don't you just need to check that the x-coord of B is greater than that of A & C? – Mike Jerred Mar 14 '16 at 19:23
  • Since the angles make up a shape (the above example is just part of a larger shape) this technique would only work on one side of the shape. – acernine Mar 14 '16 at 19:35
1

In the following, I assume that the x-axis points to the right and the y-axis points upwards. If this is not the case in your scenario, you might need to switch some signs.

If you have the line segment (x1, y1) - (x2, y2) and points are sorted counter-clockwise, you know that the shape is left of the line segment. The orthogonal direction vector that points to the line segment's left is:

leftDir = (y1 - y2, x2 - x1)

Together with the line segment, this direction defines a half space. If the following angle is convex, the third point must lie in this half space. If that's not the case, the angle is concave (which you apparently call reflex):

You can determine if the point lies in the same half space with the dot product:

isConcave = dot(p3 - p2, leftDir) < 0

In code:

float dx = x3 - x2;
float dy = y3 - y2;
float dot = dx * leftDir.x + dy * leftDir.y
return dot < 0;
Nico Schertler
  • 32,049
  • 4
  • 39
  • 70
  • Thanks for the answer. Is the line segment (x1, y1) - (x2, y2) the line segment joining A and B? – acernine Mar 14 '16 at 18:26
  • Yes, that's the one. – Nico Schertler Mar 14 '16 at 18:27
  • If my points are stored counter-clockwise (ie. A then B then C) then wouldn't the shape should always be on the right of line segment AB? – acernine Mar 14 '16 at 18:31
  • 1
    No, it's on the left (relative to the line). Consider your example image. Imagine that you're an ant standing at A and you're looking towards B. Then the angle you want to measure is to your left. – Nico Schertler Mar 14 '16 at 18:33
  • Sorry, my mistake! Thanks for the answer! – acernine Mar 14 '16 at 18:34
  • I'm trying to implement this solution and keep hitting a brick wall. I've updated the question to show my code. Is the line that calculates the 'left normal' at fault? – acernine Mar 14 '16 at 20:16
  • You have calculated the right direction (swapped signs). This should give you exactly the opposite – Nico Schertler Mar 14 '16 at 20:36
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/106282/discussion-between-acernine-and-nico-schertler). – acernine Mar 14 '16 at 20:38