How to calculate an angle from three points? [clos

2019-01-01 08:42发布

问题:

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.

回答1:

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)



回答2:

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
to get the angle:
\"angle

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



回答3:

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.



回答4:

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



回答5:

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



回答6:

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 )



回答7:

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


回答8:

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


回答9:

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



回答10:

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



回答11:

\"my

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;


回答12:

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



回答13:

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))))



回答14:

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



回答15:

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 :)



回答16:

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

}