Làm thế nào để tính một góc từ ba điểm? [đóng cửa]


120

Giả sử bạn có cái này:

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

Giả sử đó P1là tâm của một đường tròn. Nó luôn luôn là như nhau. Tôi muốn góc được tạo thành bởi P2P3, hay nói cách khác là góc cạnh P1. Góc trong chính xác. Nó sẽ luôn là một góc nhọn, vì vậy nhỏ hơn -90 độ.

Tôi nghĩ: Trời đất, đó là một phép toán hình học đơn giản. Nhưng tôi đã tìm kiếm một công thức trong khoảng 6 giờ và chỉ thấy mọi người nói về những thứ phức tạp của NASA như arccos và những thứ sản phẩm vô hướng vectơ. Đầu tôi như đang ở trong tủ lạnh.

Một số chuyên gia toán học ở đây cho rằng đây là một bài toán đơn giản? Tôi không nghĩ ngôn ngữ lập trình quan trọng ở đây, nhưng đối với những người nghĩ nó thì có: java và obj-c. Tôi cần nó cho cả hai, nhưng chưa gắn thẻ nó cho những thứ này.

Câu trả lời:


87

Nếu ý của bạn là góc mà P1 là đỉnh thì việc sử dụng Định luật Cosin sẽ hoạt động:

arccos((P 12 2 + P 13 2 - P 23 2 ) / (2 * P 12 * P 13 ))

trong đó P 12 là độ dài của đoạn từ P1 đến P2, được tính bằng

sqrt ((P1 x - P2 x ) 2 + (P1 y - P2 y ) 2 )



@Rafa Firenze cos ^ -1 là ký hiệu chung cho acos, nhưng acos ít mơ hồ hơn. vi.wikipedia.org/wiki/Inverse_trigonometric_functions
geon

Tôi sẽ để lại bản chỉnh sửa vì nó không ảnh hưởng gì, nhưng có độ Toán / CS / EE, cos ^ -1 chắc chắn là ký hiệu phổ biến nhất.
Lance Roberts

1
Chỉ một số ít ngôn ngữ sử dụng dấu mũ cho 'power of', vì vậy nếu bạn không muốn gọi nó là arcos, vui lòng nhập cos⁻¹. (Nếu bạn đang sử dụng hệ điều hành thương mại gây khó khăn cho việc nhập số mũ, tôi hy vọng sẽ có các ứng dụng keycaps bạn có thể mua hoặc có thể cài đặt một trình cắm thêm của trình duyệt. Hoặc bạn có thể tìm kiếm trên web và sao chép và dán.)
Michael Scheper

1
@MichaelScheper, tôi chỉ sử dụng dấu mũ trong các nhận xét khi html bị hạn chế. Tôi chắc chắn sẽ chỉ sử dụng ký hiệu sub / superscript trong bất kỳ câu trả lời thực tế nào.
Lance Roberts

47

Sẽ rất đơn giản nếu bạn nghĩ nó là hai vectơ, một từ điểm P1 đến P2 và một từ P1 đến P3

do đó:
a = (p1.x - p2.x, p1.y - p2.y)
b = (p1.x - p3.x, p1.y - p3.y)

Sau đó, bạn có thể đảo ngược công thức tích số chấm:
sản phẩm chấm
để có được góc:
góc giữa hai vectơ

Hãy nhớ rằng sản phẩm chấmchỉ có nghĩa là: a1 * b1 + a2 * b2 (chỉ 2 kích thước ở đây ...)


1
Ah độ lớn của vectơ
Daniel Little

Kiểm tra dung dịch atan2.
Luc Boissaye

25

Cách tốt nhất để xử lý tính toán góc là sử dụng atan2(y, x)một điểm đã cho x, ytrả về góc từ điểm đó và X+trục so với điểm gốc.

Cho rằng việc tính toán là

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

tức là về cơ bản bạn dịch hai điểm bằng -P1(nói cách khác, bạn dịch mọi thứ để P1kết thúc ở điểm gốc) và sau đó bạn xem xét sự khác biệt của các góc tuyệt đối của P3và của P2.

Ưu điểm của atan2nó là hình tròn đầy đủ được biểu diễn (bạn có thể lấy bất kỳ số nào trong khoảng từ -π đến π), thay vào đó acosbạn cần phải xử lý một số trường hợp tùy thuộc vào các dấu hiệu để tính ra kết quả chính xác.

Điểm kỳ lạ duy nhất cho atan2(0, 0)... nghĩa là cả hai P2P3phải khác với P1như trong trường hợp đó không có ý nghĩa gì khi nói về một góc.


Cảm ơn câu trả lời của bạn. Đó chính xác là những gì tôi đang tìm kiếm. Giải pháp đơn giản và bạn có thể dễ dàng nhận được góc ngược chiều kim đồng hồ nếu tôi chỉ thêm 2pi khi giá trị âm.
Mario

@marcpt: atan2là chính xác những gì cần thiết cho vấn đề này, nhưng có vẻ như hầu hết mọi người nhận được câu hỏi này chỉ không thể đọc hoặc không thể hiểu tại sao acosgiải pháp dựa trên không tốt. May mắn đủ cho tôi, tôi rời khỏi "một người nào đó là sai trên internet" ( xkcd.com/386 ) giai đoạn nhiều năm trước đây và tôi sẽ không để bắt đầu một cuộc chiến để bảo vệ sự thật hiển nhiên :-)
6502

Cảm ơn bạn đã chỉ ra điều này, nhưng bạn có thể xử lý 3D theo cách này không?
nicoco

1
@nicoco: trong không gian ba chiều bạn xác định góc như thế nào? Cụ thể hơn là góc có thể âm hoặc lớn hơn pi (180 độ)? Hai vectơ không song song trong 3d xác định một mặt phẳng, nhưng mặt phẳng có thể được "nhìn thấy" từ hai phía: nhìn từ một phía A sẽ xuất hiện "bên trái" của B và từ bên kia nó sẽ xuất hiện "bên phải" .. .
6502

@ 6505 Cảm ơn câu trả lời của bạn, tôi đã đăng trước khi suy nghĩ kỹ vấn đề của mình. Tuy nhiên, tôi đã tìm ra nó ngay bây giờ.
nicoco

19

Hãy để tôi đưa ra một ví dụ trong JavaScript, tôi đã đấu tranh rất nhiều với điều đó:

/**
 * 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));
}

Phần thưởng: Ví dụ với HTML5-canvas


5
Bạn có thể làm cho điều này hiệu quả hơn bằng cách thực hiện ít hơn sqrtvà bình phương. Xem câu trả lời của tôi tại đây (viết bằng Ruby) hoặc trong bản demo cập nhật này (JavaScript).
Phrogz

Bạn có thể sử dụng atan2 để có một giải pháp đơn giản hơn.
Luc Boissaye

15

Về cơ bản những gì bạn có là hai vectơ, một vectơ từ P1 đến P2 và một vectơ khác từ P1 đến P3. Vì vậy, tất cả những gì bạn cần là một công thức để tính góc giữa hai vectơ.

Hãy xem ở đây để có lời giải thích tốt và công thức.

văn bản thay thế


12

Nếu bạn đang nghĩ P1 là tâm của một vòng tròn, bạn đang nghĩ quá phức tạp. Bạn có một tam giác đơn giản, vì vậy vấn đề của bạn có thể giải được bằng định luật cosin . Không cần bất kỳ chuyển đổi tọa độ cực hoặc somesuch nào. Giả sử khoảng cách là P1-P2 = A, P2-P3 = B và P3-P1 = C:

Góc = arccos ((B ^ 2-A ^ 2-C ^ 2) / 2AC)

Tất cả những gì bạn cần làm là tính toán độ dài của các khoảng cách A, B và C. Những khoảng cách đó có thể dễ dàng có được từ tọa độ x và y của các điểm của bạn và định lý Pythagoras

Chiều dài = sqrt ((X2-X1) ^ 2 + (Y2-Y1) ^ 2)


Tôi hơi bối rối làm thế nào để thực sự triển khai điều này vì bạn đang coi P1, v.v. là các giá trị riêng lẻ chứ không phải (x, y)
Dominic

@Dominic Tobias: Ký hiệu P1-P2 = Akhông được đọc là "Để tính A, trừ P2 cho P1", mà là "Tôi đang xác định A là khoảng cách từ P1 đến P2", sau đó có thể được tính bằng phương trình thứ hai. Tôi chỉ muốn xác định một cách viết tắt cho các khoảng cách, để làm cho các phương trình dễ đọc hơn.
Treb

8

Gần đây tôi đã gặp phải một vấn đề tương tự, chỉ là tôi cần phân biệt giữa góc độ tích cực và tiêu cực. Trong trường hợp điều này được sử dụng cho bất kỳ ai, tôi đề xuất đoạn mã tôi lấy từ danh sách gửi thư này về việc phát hiện xoay vòng qua một sự kiện chạm cho 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;
 }

7

Giải pháp hình học rất đơn giản với giải thích

Vài ngày trước, a rơi vào cùng một vấn đề và phải ngồi với cuốn sách toán học. Tôi đã giải quyết vấn đề bằng cách kết hợp và đơn giản hóa một số công thức cơ bản.


Hãy xem xét con số này-

góc

Chúng ta muốn biết ϴ , vì vậy chúng ta cần tìm ra αβ trước. Bây giờ, đối với bất kỳ đường thẳng nào-

y = m * x + c

Cho- A = (ax, ay) , B = (bx, by) , và O = (ox, oy) . Vì vậy, đối với dòng 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)

Theo cách tương tự, đối với dòng OB -

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

Bây giờ, chúng ta cần ϴ = β - α. Trong lượng giác, chúng ta có một công thức-

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

Sau khi thay thế giá trị của tan α(từ eqn-2) và tan b(từ eqn-3) trong eqn-4 và áp dụng đơn giản hóa, chúng tôi nhận được-

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

Vì thế,

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

Đó là nó!


Bây giờ, hãy xem hình sau-

góc

Phương thức C # hoặc, Java này tính toán góc ( ϴ ) -

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

Làm thế nào có thể sử dụng phương pháp này cho một tam giác đều?
Vikrant

1
Chà, câu trả lời của bạn hiện đang hoạt động tốt. Đó là một số vấn đề logic trong tuần mã của tôi trước đó.
Vikrant

6

Trong Objective-C, bạn có thể làm điều này bằng cách

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

Hoặc đọc thêm tại đây


7
Uh, không. Có ba điểm, tâm không nằm ở (0,0) và điều này tạo ra một góc của tam giác vuông, không phải là góc của khối chóp. Và tên "xpoint" cho một góc là gì?
Jim Balter,

4

Bạn đã đề cập đến một góc có dấu (-90). Trong nhiều góc độ ứng dụng có thể có các dấu hiệu (tích cực và tiêu cực, xem http://en.wikipedia.org/wiki/Angle ). Nếu các điểm là (giả sử) P2 (1,0), P1 (0,0), P3 (0,1) thì góc P3-P1-P2 được quy ước là dương (PI / 2) trong khi góc P2-P1- P3 là âm. Sử dụng độ dài của các cạnh sẽ không phân biệt giữa + và - vì vậy nếu điều này quan trọng, bạn sẽ cần sử dụng vectơ hoặc một hàm như Math.atan2 (a, b).

Angles cũng có thể mở rộng ra ngoài 2 * PI và trong khi điều này không liên quan đến câu hỏi hiện tại, điều quan trọng là tôi đã viết lớp Angle của riêng mình (cũng để đảm bảo rằng độ và radian không bị lẫn lộn). Các câu hỏi liệu góc1 có nhỏ hơn góc 2 hay không phụ thuộc rất nhiều vào cách xác định góc. Nó cũng có thể quan trọng để quyết định xem một dòng (-1,0) (0,0) (1,0) được biểu diễn dưới dạng Math.PI hay -Math.PI


4

chương trình demo góc của tôi

Gần đây, tôi cũng gặp phải vấn đề tương tự ... Trong Delphi Nó rất giống với 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;

2

Đây là phương pháp C # để trả về góc (0-360) ngược chiều kim đồng hồ từ phương ngang cho một điểm trên đường tròn.

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

Chúc mừng, Paul


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


0

Có một câu trả lời đơn giản cho điều này bằng cách sử dụng toán trung học ..

Giả sử rằng bạn có 3 điểm

Để có góc từ điểm A đến điểm B

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

Để có góc từ B đến C

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

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

Tôi vừa sử dụng mã này trong dự án gần đây mà tôi đã thực hiện, hãy thay đổi B thành P1 .. bạn cũng có thể xóa "180 +" nếu bạn muốn


-1

tốt, các câu trả lời khác dường như bao gồm mọi thứ cần thiết, vì vậy tôi chỉ muốn thêm điều này nếu bạn đang sử dụng JMonkeyEngine:

Vector3f.angleBetween(otherVector)

vì đó là những gì tôi đến đây tìm kiếm :)


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

}

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.