120

Lets say you have this:

P1 = (x=2, y=50)
P2 = (x=9, y=40)
P3 = (x=5, y=20)

Assume that P1 is the center point of a circle. It is always the same. I want the angle that is made up by P2 and P3, or in other words the angle that is next to P1. The inner angle to be precise. It will always be an acute angle, so less than -90 degrees.

I thought: Man, that's simple geometry math. But I have looked for a formula for around 6 hours now, and only find people talking about complicated NASA stuff like arccos and vector scalar product stuff. My head feels like it's in a fridge.

Some math gurus here that think this is a simple problem? I don't think the programming language matters here, but for those who think it does: java and objective-c. I need it for both, but haven't tagged it for these.

Lance Roberts
  • 22,383
  • 32
  • 112
  • 130

16 Answers16

87

If you mean the angle that P1 is the vertex of then using the Law of Cosines should work:

arccos((P122 + P132 - P232) / (2 * P12 * P13))

where P12 is the length of the segment from P1 to P2, calculated by

sqrt((P1x - P2x)2 + (P1y - P2y)2)

Peter G.
  • 14,786
  • 7
  • 57
  • 75
Lance Roberts
  • 22,383
  • 32
  • 112
  • 130
  • 2
    http://www.mathwords.com/c/cosine_inverse.htm – Matt W Feb 15 '12 at 09:26
  • @Rafa Firenze cos^-1 is a common notation for acos, but acos is less ambiguous. http://en.wikipedia.org/wiki/Inverse_trigonometric_functions – geon Oct 21 '14 at 13:27
  • I'll leave the edit since it doesn't hurt anything, but having Math/CS/EE degrees, cos^-1 is certainly the most common notation. – Lance Roberts Oct 21 '14 at 14:00
  • 1
    Only a handful of languages use a caret for 'power of', so if you don't want to call it arcos, please just type cos⁻¹. (If you're using a commercial operating system that makes it difficult to type exponents, I expect there would be keycaps applications you could buy, or maybe a browser plug-in you could install. Or you can websearch and copy and paste.) – Michael Scheper Mar 03 '15 at 20:55
  • 1
    @MichaelScheper, I was only using the caret in the comments where html is limited. I would certainly just use the sub/superscript notation in any actual answer. – Lance Roberts Mar 03 '15 at 22:43
  • it's `arccos`, not `arcos` – phuclv Mar 24 '16 at 10:16
  • @LưuVĩnhPhúc, yes, you're right, it was that way before the last edit. fixed. – Lance Roberts Mar 25 '16 at 03:05
  • Worth mentioning that this formula is derived from the "Law of Cosines". So people don't think that we're doing magic here :) – Wilmer E. Henao Jun 13 '16 at 15:08
  • it works, but how you can find the sign of the angle? – takluiper Dec 08 '17 at 13:07
  • Check atan2 solution below. – Luc Boissaye Mar 07 '18 at 15:16
47

It gets very simple if you think it as two vectors, one from point P1 to P2 and one from P1 to P3

so:
a = (p1.x - p2.x, p1.y - p2.y)
b = (p1.x - p3.x, p1.y - p3.y)

You can then invert the dot product formula:
dot product
to get the angle:
angle between two vectors

Remember that dot product just means: a1*b1 + a2*b2 (just 2 dimensions here...)

Boann
  • 48,794
  • 16
  • 117
  • 146
Andrea Ambu
  • 38,188
  • 14
  • 54
  • 77
25

The best way to deal with angle computation is to use atan2(y, x) that given a point x, y returns the angle from that point and the X+ axis in respect to the origin.

Given that the computation is

double result = atan2(P3.y - P1.y, P3.x - P1.x) -
                atan2(P2.y - P1.y, P2.x - P1.x);

i.e. you basically translate the two points by -P1 (in other words you translate everything so that P1 ends up in the origin) and then you consider the difference of the absolute angles of P3 and of P2.

The advantages of atan2 is that the full circle is represented (you can get any number between -π and π) where instead with acos you need to handle several cases depending on the signs to compute the correct result.

The only singular point for atan2 is (0, 0)... meaning that both P2 and P3 must be different from P1 as in that case doesn't make sense to talk about an angle.

6502
  • 112,025
  • 15
  • 165
  • 265
  • Thanks for your answer. That was exactly what I was looking for. Simple solution and you can get easily get the counter clockwise angle if I just add 2pi when the value is negative. – Mario Aug 08 '17 at 22:46
  • @marcpt: `atan2` is exactly what is needed for this problem, but looks like most people getting to this question just can't read or can't understand why `acos`-based solution are bad. Luckily enough for me I left the "someone is wrong on the internet" (https://xkcd.com/386/) phase many years ago and I'm not going to start a fight for defending the obvious :-) – 6502 Aug 09 '17 at 06:02
  • Thanks for pointing this out, but can you handle 3D this way? – nicoco Sep 28 '17 at 12:59
  • 1
    @nicoco: in three dimensions how do you define the angle? More specifically can the angle be negative or more than pi (180 degrees)? Two non parallel vectors in 3d define a plane, but the plane can be "seen" from two sides: looked from one side A will appear "to the left" of B and from the other it will appear "to the right"... – 6502 Sep 28 '17 at 15:00
  • @6505 Thanks for your answer, I posted before thinking my problem through. I got it figured out now, though. – nicoco Sep 28 '17 at 15:11
18

Let me give an example in JavaScript, I've fought a lot with that:

/**
 * Calculates the angle (in radians) between two vectors pointing outward from one center
 *
 * @param p0 first point
 * @param p1 second point
 * @param c center point
 */
function find_angle(p0,p1,c) {
    var p0c = Math.sqrt(Math.pow(c.x-p0.x,2)+
                        Math.pow(c.y-p0.y,2)); // p0->c (b)   
    var p1c = Math.sqrt(Math.pow(c.x-p1.x,2)+
                        Math.pow(c.y-p1.y,2)); // p1->c (a)
    var p0p1 = Math.sqrt(Math.pow(p1.x-p0.x,2)+
                         Math.pow(p1.y-p0.y,2)); // p0->p1 (c)
    return Math.acos((p1c*p1c+p0c*p0c-p0p1*p0p1)/(2*p1c*p0c));
}

Bonus: Example with HTML5-canvas

phuclv
  • 37,963
  • 15
  • 156
  • 475
shaman.sir
  • 3,198
  • 3
  • 28
  • 36
  • 5
    You can make this more efficient by doing less `sqrt` and squaring. See [my answer here](http://stackoverflow.com/a/21693144/405017) (written in Ruby), or in this [updated demo](http://jsfiddle.net/d3aZD/88/) (JavaScript). – Phrogz Feb 11 '14 at 04:46
  • You could use atan2 for a simpler solution. – Luc Boissaye Mar 07 '18 at 15:16
15

Basically what you have is two vectors, one vector from P1 to P2 and another from P1 to P3. So all you need is an formula to calculate the angle between two vectors.

Have a look here for a good explanation and the formula.

alt text

Boann
  • 48,794
  • 16
  • 117
  • 146
Andre Miller
  • 15,255
  • 6
  • 55
  • 53
12

If you are thinking of P1 as the center of a circle, you are thinking too complicated. You have a simple triangle, so your problem is solveable with the law of cosines. No need for any polar coordinate tranformation or somesuch. Say the distances are P1-P2 = A, P2-P3 = B and P3-P1 = C:

Angle = arccos ( (B^2-A^2-C^2) / 2AC )

All you need to do is calculate the length of the distances A, B and C. Those are easily available from the x- and y-coordinates of your points and Pythagoras' theorem

Length = sqrt( (X2-X1)^2 + (Y2-Y1)^2 )

Lance Roberts
  • 22,383
  • 32
  • 112
  • 130
Treb
  • 19,903
  • 7
  • 54
  • 87
  • I'm a bit confused how to actually implement this as you're treating P1 etc as individual values rather than (x,y) – Dominic Feb 22 '17 at 10:58
  • @Dominic Tobias: The Notation `P1-P2 = A` should not be read as "To calculate A, subtract P2 from P1", but as "I am defining A as the distance from P1 to P2", which can then be calculated using the second equation. I just wanted to define a shorthand for the distances, to make the equations more readable. – Treb Apr 20 '17 at 13:25
8

I ran into a similar problem recently, only I needed to differentiate between a positive and negative angles. In case this is of use to anyone, I recommend the code snippet I grabbed from this mailing list about detecting rotation over a touch event for Android:

 @Override
 public boolean onTouchEvent(MotionEvent e) {
    float x = e.getX();
    float y = e.getY();
    switch (e.getAction()) {
    case MotionEvent.ACTION_MOVE:
       //find an approximate angle between them.

       float dx = x-cx;
       float dy = y-cy;
       double a=Math.atan2(dy,dx);

       float dpx= mPreviousX-cx;
       float dpy= mPreviousY-cy;
       double b=Math.atan2(dpy, dpx);

       double diff  = a-b;
       this.bearing -= Math.toDegrees(diff);
       this.invalidate();
    }
    mPreviousX = x;
    mPreviousY = y;
    return true;
 }
phuclv
  • 37,963
  • 15
  • 156
  • 475
Marc
  • 2,593
  • 2
  • 18
  • 21
7

Very Simple Geometric Solution with Explanation

Few days ago, a fell into the same problem & had to sit with the math book. I solved the problem by combining and simplifying some basic formulas.


Lets consider this figure-

angle

We want to know ϴ, so we need to find out α and β first. Now, for any straight line-

y = m * x + c

Let- A = (ax, ay), B = (bx, by), and O = (ox, oy). So for the line OA-

oy = m1 * ox + c   ⇒ c = oy - m1 * ox   ...(eqn-1)

ay = m1 * ax + c   ⇒ ay = m1 * ax + oy - m1 * ox   [from eqn-1]
                   ⇒ ay = m1 * ax + oy - m1 * ox
                   ⇒ m1 = (ay - oy) / (ax - ox)
                   ⇒ tan α = (ay - oy) / (ax - ox)   [m = slope = tan ϴ]   ...(eqn-2)

In the same way, for line OB-

tan β = (by - oy) / (bx - ox)   ...(eqn-3)

Now, we need ϴ = β - α. In trigonometry we have a formula-

tan (β-α) = (tan β + tan α) / (1 - tan β * tan α)   ...(eqn-4)

After replacing the value of tan α (from eqn-2) and tan b (from eqn-3) in eqn-4, and applying simplification we get-

tan (β-α) = ( (ax-ox)*(by-oy)+(ay-oy)*(bx-ox) ) / ( (ax-ox)*(bx-ox)-(ay-oy)*(by-oy) )

So,

ϴ = β-α = tan^(-1) ( ((ax-ox)*(by-oy)+(ay-oy)*(bx-ox)) / ((ax-ox)*(bx-ox)-(ay-oy)*(by-oy)) )

That is it!


Now, take following figure-

angle

This C# or, Java method calculates the angle (ϴ)-

    private double calculateAngle(double P1X, double P1Y, double P2X, double P2Y,
            double P3X, double P3Y){

        double numerator = P2Y*(P1X-P3X) + P1Y*(P3X-P2X) + P3Y*(P2X-P1X);
        double denominator = (P2X-P1X)*(P1X-P3X) + (P2Y-P1Y)*(P1Y-P3Y);
        double ratio = numerator/denominator;

        double angleRad = Math.Atan(ratio);
        double angleDeg = (angleRad*180)/Math.PI;

        if(angleDeg<0){
            angleDeg = 180+angleDeg;
        }

        return angleDeg;
    }
Minhas Kamal
  • 20,752
  • 7
  • 62
  • 64
6

In Objective-C you could do this by

float xpoint = (((atan2((newPoint.x - oldPoint.x) , (newPoint.y - oldPoint.y)))*180)/M_PI);

Or read more here

phuclv
  • 37,963
  • 15
  • 156
  • 475
  • 7
    Uh, no. There are three points, the center is not at (0,0), and this gives an angle of a right triangle, not the angle of the apex. And what sort of name is "xpoint" for an angle? – Jim Balter Apr 09 '15 at 05:08
4

You mentioned a signed angle (-90). In many applications angles may have signs (positive and negative, see http://en.wikipedia.org/wiki/Angle). If the points are (say) P2(1,0), P1(0,0), P3(0,1) then the angle P3-P1-P2 is conventionally positive (PI/2) whereas the angle P2-P1-P3 is negative. Using the lengths of the sides will not distinguish between + and - so if this matters you will need to use vectors or a function such as Math.atan2(a, b).

Angles can also extend beyond 2*PI and while this is not relevant to the current question it was sufficiently important that I wrote my own Angle class (also to make sure that degrees and radians did not get mixed up). The questions as to whether angle1 is less than angle2 depends critically on how angles are defined. It may also be important to decide whether a line (-1,0)(0,0)(1,0) is represented as Math.PI or -Math.PI

peter.murray.rust
  • 37,407
  • 44
  • 153
  • 217
4

my angle demo program

Recently, I too have the same problem... In Delphi It's very similar to Objective-C.

procedure TForm1.FormPaint(Sender: TObject);
var ARect: TRect;
    AWidth, AHeight: Integer;
    ABasePoint: TPoint;
    AAngle: Extended;
begin
  FCenter := Point(Width div 2, Height div 2);
  AWidth := Width div 4;
  AHeight := Height div 4;
  ABasePoint := Point(FCenter.X+AWidth, FCenter.Y);
  ARect := Rect(Point(FCenter.X - AWidth, FCenter.Y - AHeight),
    Point(FCenter.X + AWidth, FCenter.Y + AHeight));
  AAngle := ArcTan2(ClickPoint.Y-Center.Y, ClickPoint.X-Center.X) * 180 / pi;
  AngleLabel.Caption := Format('Angle is %5.2f', [AAngle]);
  Canvas.Ellipse(ARect);
  Canvas.MoveTo(FCenter.X, FCenter.Y);
  Canvas.LineTo(FClickPoint.X, FClickPoint.Y);
  Canvas.MoveTo(FCenter.X, FCenter.Y);
  Canvas.LineTo(ABasePoint.X, ABasePoint.Y);
end;
phuclv
  • 37,963
  • 15
  • 156
  • 475
antonio
  • 548
  • 8
  • 16
2

Here's a C# method to return the angle (0-360) anticlockwise from the horizontal for a point on a circle.

    public static double GetAngle(Point centre, Point point1)
    {
        // Thanks to Dave Hill
        // Turn into a vector (from the origin)
        double x = point1.X - centre.X;
        double y = point1.Y - centre.Y;
        // Dot product u dot v = mag u * mag v * cos theta
        // Therefore theta = cos -1 ((u dot v) / (mag u * mag v))
        // Horizontal v = (1, 0)
        // therefore theta = cos -1 (u.x / mag u)
        // nb, there are 2 possible angles and if u.y is positive then angle is in first quadrant, negative then second quadrant
        double magnitude = Math.Sqrt(x * x + y * y);
        double angle = 0;
        if(magnitude > 0)
            angle = Math.Acos(x / magnitude);

        angle = angle * 180 / Math.PI;
        if (y < 0)
            angle = 360 - angle;

        return angle;
    }

Cheers, Paul

Paul
  • 41
  • 3
2

function p(x, y) {return {x,y}}

function normaliseToInteriorAngle(angle) {
 if (angle < 0) {
  angle += (2*Math.PI)
 }
 if (angle > Math.PI) {
  angle = 2*Math.PI - angle
 }
 return angle
}

function angle(p1, center, p2) {
 const transformedP1 = p(p1.x - center.x, p1.y - center.y)
 const transformedP2 = p(p2.x - center.x, p2.y - center.y)

 const angleToP1 = Math.atan2(transformedP1.y, transformedP1.x)
 const angleToP2 = Math.atan2(transformedP2.y, transformedP2.x)

 return normaliseToInteriorAngle(angleToP2 - angleToP1)
}

function toDegrees(radians) {
 return 360 * radians / (2 * Math.PI)
}

console.log(toDegrees(angle(p(-10, 0), p(0, 0), p(0, -10))))
Dominic
  • 62,658
  • 20
  • 139
  • 163
0

there IS a simple answer for this using high school math..

Let say that you have 3 points

To get angle from point A to B

angle = atan2(A.x - B.x, B.y - A.y)

To get angle from point B to C

angle2 = atan2(B.x - C.x, C.y - B.y)

Answer = 180 + angle2 - angle
If (answer < 0){
    return answer + 360
}else{
    return answer
}

I just used this code in the recent project that I made, change the B to P1.. you might as well remove the "180 +" if you want

ᴄʀᴏᴢᴇᴛ
  • 2,939
  • 26
  • 44
James Penner
  • 71
  • 1
  • 6
-1

well, the other answers seem to cover everything required, so I would like to just add this if you are using JMonkeyEngine:

Vector3f.angleBetween(otherVector)

as that is what I came here looking for :)

VeganEye
  • 66
  • 7
-2
      Atan2        output in degrees
       PI/2              +90
         |                | 
         |                |    
   PI ---.--- 0   +180 ---.--- 0       
         |                |
         |                |
       -PI/2             +270

public static double CalculateAngleFromHorizontal(double startX, double startY, double endX, double endY)
{
    var atan = Math.Atan2(endY - startY, endX - startX); // Angle in radians
    var angleDegrees = atan * (180 / Math.PI);  // Angle in degrees (can be +/-)
    if (angleDegrees < 0.0)
    {
        angleDegrees = 360.0 + angleDegrees;
    }
    return angleDegrees;
}

// Angle from point2 to point 3 counter clockwise
public static double CalculateAngle0To360(double centerX, double centerY, double x2, double y2, double x3, double y3)
{
    var angle2 = CalculateAngleFromHorizontal(centerX, centerY, x2, y2);
    var angle3 = CalculateAngleFromHorizontal(centerX, centerY, x3, y3);
    return (360.0 + angle3 - angle2)%360;
}

// Smaller angle from point2 to point 3
public static double CalculateAngle0To180(double centerX, double centerY, double x2, double y2, double x3, double y3)
{
    var angle = CalculateAngle0To360(centerX, centerY, x2, y2, x3, y3);
    if (angle > 180.0)
    {
        angle = 360 - angle;
    }
    return angle;
}

}