如何从三点计算角度?

假设你有这个:

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

假设 P1是一个圆的中心点,它总是相同的。 我想要的角度,是由 P2P3,或换句话说,角度旁边的 P1。确切地说是内角。它总是一个锐角,所以小于 -90度。

我想: 伙计,这是简单的几何数学。但我已经找了6个小时左右的公式,只找到人们谈论复杂的 NASA 的东西,如弧长和矢量标量积的东西。我的头感觉像在冰箱里。

一些数学大师认为这是一个简单的问题吗?我不认为编程语言在这里很重要,但是对于那些认为它很重要的人: java 和 Objective-c。我两个都需要,但这些还没有标记。

265455 次浏览

基本上你有两个向量,一个从 P1到 P2,另一个从 P1到 P3。所以你只需要一个公式来计算两个向量之间的夹角。

看一下 给你,找一个好的解释和公式。

alt text

If you mean the angle that P1 is the vertex of then using the 余弦定律 should work:

((P12 < sup > 2 + P132-P232)/(2 * P12 * P13)

其中 P12是 P1到 P2段的长度,由

sqrt((P1X - P2X) 2 + (P1- P2) 2)

如果你认为 P1是一个圆的中心,那么你的想法就太复杂了。 你有一个简单的三角形,所以你的问题是可以用 余弦定律解决的。不需要任何极坐标转换之类的。假设距离是 P1-P2 = A,P2-P3 = B,P3-P1 = C:

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

你需要做的就是计算距离 A,B 和 C 的长度。 Those are easily available from the x- and y-coordinates of your points and Pythagoras' theorem

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

你提到了一个有符号的角度(-90)。在许多应用角度可能有符号(正面和负面,见 http://en.wikipedia.org/wiki/Angle)。如果点是(比如) P2(1,0) ,P1(0,0) ,P3(0,1) ,那么角 P3-P1-P2通常是正的(PI/2) ,而角 P2-P1-P3是负的。使用边的长度将不会区分 + 和-,因此如果这很重要,您将需要使用向量或函数,如 Math.atan2(a,b)。

角度也可以扩展到2 * PI 以外,虽然这与当前的问题无关,但是它足够重要,所以我写了自己的 Angle 类(也是为了确保角度和弧度没有混淆)。关于角度1是否小于角度2的问题主要取决于角度是如何定义的。确定一行(- 1,0)(0,0)(1,0)是否表示为 Math.PI 或-Math.PI 也很重要

如果你把它想象成两个向量,一个从点 P1到 P2,一个从点 P1到 P3,就很简单了

所以:
A = (p1.x-p2.x,p1.y-p2.y)
B = (p1.x-p3.x,p1.y-p3.y)

然后你可以反转点乘公式:
dot product
为了得到角度:
angle between two vectors

请记住 < img src = “ https://i.stack.imgur.com/u6DBf.png”alt = “ dot product”> 仅仅意味着: A1 * b1 + a2 * b2(这里只有2个维度...)

我最近遇到了一个类似的问题,只是我需要区分正面和负面的角度。如果这对任何人都有用,我推荐我从 这个邮件列表中获得的关于检测 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;
}

在 Objective-C 中,您可以通过

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

或者阅读更多的 here

让我举一个 JavaScript 的例子,我已经为此付出了很多努力:

/**
* 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: 示例与 HTML5-画布

my angle demo program

最近,我也有同样的问题... 在德尔斐 它与 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;

处理角度计算的最佳方法是使用 atan2(y, x),它给定一个点 x, y返回该点和 X+轴相对于原点的角度。

考虑到计算结果是

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

也就是说,你基本上是通过 -P1来翻译这两个点(换句话说,你翻译所有的点,使得 P1最终到达原点) ,然后你考虑 P3P2的绝对角度的差异。

atan2的优点在于,它表示完整的圆(你可以得到 -π 和 π 之间的任意数字) ,而使用 acos时,你需要根据符号处理几种情况才能计算出正确的结果。

atan2的唯一奇点是 (0, 0)... ... 这意味着 P2P3都必须与 P1不同,因为在这种情况下谈论一个角度是没有意义的。

下面是一个 C # 方法,用于从水平方向逆时针方向返回圆上某点的角度(0-360)。

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

干杯, 保罗

Very Simple Geometric Solution with Explanation

几天前,一个人陷入了同样的问题,不得不坐下来看数学书。我通过组合和简化一些基本公式来解决这个问题。


让我们考虑这个数字-

angle

我们想知道 Θ,所以我们需要先找出 ΑΒ。现在,对于任何直线-

y = m * x + c

让-[俄语]B = (bx,by),和 (牛,哦)。因此,对于线 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)

同样,对于线 产科医生-

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

现在,我们需要 ϴ = β - α。在三角学中,我们有一个公式-

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

在将 eqn-4中的 tan α(from eqn-2)和 tan b(from eqn-3)的值替换为 eqn-4后,通过简化得到-

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

那么,

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

That is it!


现在,请看下面的图

angle

这个 C # 或 Java 方法计算角度(Θ)-

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

}

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

好吧,其他的答案似乎涵盖了所有需要的东西,所以如果你正在使用 JMonkeyEngine,我想补充一下:

Vector3f.angleBetween(otherVector)

因为这正是我来这里的目的:)

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

假设你有3个点

To get angle from point A to B

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

从 B 点到 C 点的角度

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

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

我只是在最近的项目中使用了这个代码,将 B 改为 P1。.如果你愿意,你也可以删除“180 +”