Jak obliczyć kąt z trzech punktów? [Zamknięte]

120

Powiedzmy, że masz to:

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

Załóżmy, że P1jest to środek koła. Jest zawsze taka sama. Chcę kąta, na który składa się P2i P3, innymi słowy, kąt, który jest obok P1. A dokładniej kąt wewnętrzny. Zawsze będzie to kąt ostry, a więc mniejszy niż -90 stopni.

Pomyślałem: stary, to prosta matematyka geometrii. Ale szukałem wzoru już od około 6 godzin i znajduję ludzi, którzy mówią tylko o skomplikowanych rzeczach NASA, takich jak arccos i produkty skalarne wektorowe. Mam wrażenie, że głowa jest w lodówce.

Niektórzy guru matematyki, którzy myślą, że to prosty problem? Nie sądzę, że język programowania ma tutaj znaczenie, ale dla tych, którzy myślą, że tak: java i objective-c. Potrzebuję go do obu, ale nie oznaczyłem go dla nich.

Lance Roberts
źródło

Odpowiedzi:

87

Jeśli masz na myśli kąt, którego wierzchołkiem jest P1, to użycie prawa cosinusów powinno działać:

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

gdzie P 12 to długość odcinka od P1 do P2, obliczona przez

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

Lance Roberts
źródło
@Rafa Firenze cos ^ -1 jest powszechnym zapisem dla acos, ale acos jest mniej niejednoznaczny. en.wikipedia.org/wiki/Inverse_trigonometric_functions
geon
Zostawię edycję, ponieważ nic to nie boli, ale mając stopnie Math / CS / EE, cos ^ -1 jest z pewnością najbardziej powszechną notacją.
Lance Roberts
1
Tylko nieliczne języki używają znaku „potęga” po daszku, więc jeśli nie chcesz nazywać tego arcos, po prostu wpisz cos⁻¹. (Jeśli korzystasz z komercyjnego systemu operacyjnego, który utrudnia wpisywanie wykładników, spodziewam się, że będą dostępne aplikacje z klawiszami, które możesz kupić, lub może wtyczka do przeglądarki, którą możesz zainstalować. Możesz też wyszukiwać w Internecie, kopiować i wklejać).
Michael Scheper
1
@MichaelScheper, używałem znaku daszka tylko w komentarzach, w których html jest ograniczony. Z pewnością użyłbym po prostu notacji z indeksem górnym / górnym w każdej rzeczywistej odpowiedzi.
Lance Roberts
47

To staje się bardzo proste, jeśli myślisz, że to dwa wektory, jeden od punktu P1 do P2 i jeden od P1 do P3

a więc:
a = (p1.x - p2.x, p1.y - p2.y)
b = (p1.x - p3.x, p1.y - p3.y)

Następnie możesz odwrócić wzór na iloczyn skalarny:
iloczyn skalarny
aby uzyskać kąt:
kąt między dwoma wektorami

Pamiętaj, że iloczyn skalarnyoznacza to po prostu: a1 * b1 + a2 * b2 (tutaj tylko 2 wymiary ...)

Andrea Ambu
źródło
1
Ah, wielkość wektora
Daniel Little
Sprawdź rozwiązanie atan2.
Luc Boissaye
25

Najlepszym sposobem radzenia sobie z obliczaniem kąta jest użycie atan2(y, x)tego, że dany punkt x, yzwraca kąt z tego punktu i X+oś względem początku.

Biorąc pod uwagę, że obliczenia są

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

tj. w zasadzie tłumaczysz dwa punkty przez -P1(innymi słowy tłumaczysz wszystko tak, że P1kończy się na początku), a następnie bierzesz pod uwagę różnicę kątów bezwzględnych zi P3od P2.

Zaletą atan2jest to, że reprezentowane jest pełne koło (można uzyskać dowolną liczbę między -π a π), gdzie zamiast tego acosmusisz obsłużyć kilka przypadków w zależności od znaków, aby obliczyć poprawny wynik.

Jedynym osobliwym punktem dla atan2jest (0, 0)... co oznacza, że ​​oba P2i P3muszą być różne od, P1ponieważ w tym przypadku nie ma sensu mówić o kącie.

6502
źródło
Dziękuję za odpowiedź. To było dokładnie to, czego szukałem. Proste rozwiązanie i możesz łatwo uzyskać kąt przeciwny do ruchu wskazówek zegara, jeśli dodam tylko 2pi, gdy wartość jest ujemna.
Mario
@marcpt: atan2jest dokładnie tym, co jest potrzebne do rozwiązania tego problemu, ale wygląda na to, że większość ludzi docierających do tego pytania po prostu nie może czytać lub nie może zrozumieć, dlaczego acosrozwiązania oparte na rozwiązaniach są złe. Na szczęście wiele lat temu opuściłem fazę "ktoś się myli w internecie" ( xkcd.com/386 ) i nie zamierzam zaczynać walki o obronę oczywistego :-)
6502
Dziękuję za zwrócenie uwagi, ale czy poradzisz sobie w ten sposób z 3D?
nicoco
1
@nicoco: w trzech wymiarach jak definiujecie kąt? Dokładniej, czy kąt może być ujemny lub większy niż pi (180 stopni)? Dwa nierównoległe wektory w 3D definiują płaszczyznę, ale płaszczyznę można „zobaczyć” z dwóch stron: patrząc z jednej strony A pojawi się „na lewo” od B, az drugiej „na prawo”. .
6502
@ 6505 Dzięki za odpowiedź, wysłałem wiadomość przed przemyśleniem mojego problemu. Jednak teraz już to rozgryzłem.
nicoco
19

Podam przykład w JavaScript, dużo z tym walczyłem:

/**
 * 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: przykład z płótnem HTML5

szaman.sir
źródło
5
Możesz uczynić to bardziej wydajnym, robiąc mniej sqrti podnosząc do kwadratu. Zobacz moją odpowiedź tutaj (napisaną w Rubim) lub w zaktualizowanej wersji demonstracyjnej (JavaScript).
Phrogz,
Możesz użyć atan2 jako prostszego rozwiązania.
Luc Boissaye
15

Zasadniczo masz dwa wektory, jeden wektor od P1 do P2 i drugi od P1 do P3. Wszystko, czego potrzebujesz, to wzór do obliczenia kąta między dwoma wektorami.

Zajrzyj tutaj, aby uzyskać dobre wyjaśnienie i wzór.

tekst alternatywny

Andre Miller
źródło
12

Jeśli myślisz o P1 jako środku koła, myślisz zbyt skomplikowanie. Masz prosty trójkąt, więc twój problem można rozwiązać za pomocą prawa cosinusów . Nie ma potrzeby jakiejkolwiek transformacji współrzędnych biegunowych ani czegoś podobnego. Powiedzmy, że odległości to P1-P2 = A, P2-P3 = B i P3-P1 = C:

Kąt = arccos ((B ^ 2-A ^ 2-C ^ 2) / 2AC)

Wszystko, co musisz zrobić, to obliczyć długość odległości A, B i C. Są one łatwo dostępne ze współrzędnych x i y punktów oraz twierdzenia Pitagorasa

Długość = sqrt ((X2-X1) ^ 2 + (Y2-Y1) ^ 2)

Treb
źródło
Jestem trochę zdezorientowany, jak to właściwie zaimplementować, ponieważ traktujesz P1 itp. Jako indywidualne wartości, a nie (x, y)
Dominic
@Dominic Tobias: Notacji P1-P2 = Anie należy odczytywać jako „Aby obliczyć A, odejmij P2 od P1”, ale jako „Definiuję A jako odległość od P1 do P2”, którą można następnie obliczyć za pomocą drugiego równania. Chciałem tylko zdefiniować skrót dla odległości, aby równania były bardziej czytelne.
Treb
8

Niedawno natknąłem się na podobny problem, tylko że potrzebowałem odróżnić kąt dodatni od ujemnego. Na wypadek, gdyby było to przydatne dla każdego, polecam fragment kodu, który wziąłem z tej listy mailingowej na temat wykrywania rotacji po zdarzeniu dotykowym dla Androida:

 @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;
 }
Marc
źródło
7

Bardzo proste rozwiązanie geometryczne z wyjaśnieniem

Kilka dni temu wpadłem w ten sam problem i musiałem siedzieć z książką do matematyki. Rozwiązałem problem, łącząc i upraszczając kilka podstawowych wzorów.


Rozważmy tę liczbę-

kąt

Chcemy wiedzieć ϴ , więc najpierw musimy znaleźć α i β . Teraz dla każdej prostej

y = m * x + c

Niech- A = (ax, ay) , B = (bx, by) i O = (ox, oy) . Więc dla linii 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)

W ten sam sposób dla linii OB -

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

Teraz potrzebujemy ϴ = β - α. W trygonometrii mamy wzór

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

Po zastąpieniu wartości tan α(z równania-2) i tan b(z równania-3) w równaniu-4 i zastosowaniu uproszczenia otrzymujemy:

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

Więc,

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

To jest to!


Teraz weźmy następujący rysunek-

kąt

Ta metoda C # lub Java oblicza kąt ( ϴ ) -

    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
źródło
W jaki sposób można zastosować tę metodę w przypadku trójkąta równobocznego?
Vikrant
1
Twoja odpowiedź działa teraz dobrze. Tydzień wcześniej był to problem logiczny w moim kodzie.
Vikrant
6

W Objective-C możesz to zrobić przez

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

Lub przeczytaj więcej tutaj

Adeem Maqsood Basraa
źródło
7
O nie. Istnieją trzy punkty, środek nie znajduje się w punkcie (0,0), a to daje kąt trójkąta prostokątnego, a nie kąt wierzchołka. A jaką nazwą jest „punkt x” dla kąta?
Jim Balter,
4

Wspomniałeś o kącie ze znakiem (-90). W wielu zastosowaniach kąty mogą mieć znaki (dodatnie i ujemne, patrz http://en.wikipedia.org/wiki/Angle ). Jeśli punkty to (powiedzmy) P2 (1,0), P1 (0,0), P3 (0,1), to kąt P3-P1-P2 jest umownie dodatni (PI / 2), podczas gdy kąt P2-P1- P3 jest ujemny. Używanie długości boków nie rozróżnia + i -, więc jeśli to ma znaczenie, będziesz musiał użyć wektorów lub funkcji takiej jak Math.atan2 (a, b).

Kąty mogą również wykraczać poza 2 * PI i chociaż nie jest to istotne dla obecnego pytania, wystarczająco ważne było, aby napisać własną klasę Angle (również po to, aby upewnić się, że stopnie i radiany nie zostaną pomieszane). Kwestia, czy kąt1 jest mniejszy niż kąt2, zależy w dużym stopniu od tego, jak definiuje się kąty. Może być również ważne, aby zdecydować, czy linia (-1,0) (0,0) (1,0) jest reprezentowana jako Math.PI czy -Math.PI

peter.murray.rust
źródło
4

mój program demonstracyjny kąta

Ostatnio ja też mam ten sam problem ... W Delphi jest bardzo podobny do 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;
antonio
źródło
2

Oto metoda C #, która zwraca kąt (0-360) przeciwnie do ruchu wskazówek zegara od poziomu dla punktu na okręgu.

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

Pozdrawiam, Paul

Paweł
źródło
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
źródło
0

jest na to prosta odpowiedź, używając matematyki w szkole średniej.

Powiedzmy, że masz 3 punkty

Aby uzyskać kąt z punktu A do B.

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

Aby uzyskać kąt z punktu B do C.

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

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

Właśnie użyłem tego kodu w ostatnim projekcie, który zrobiłem, zmień B na P1 .. równie dobrze możesz usunąć „180 +”, jeśli chcesz

James Penner
źródło
-1

cóż, inne odpowiedzi wydają się obejmować wszystko, czego potrzeba, więc chciałbym po prostu dodać to, jeśli używasz JMonkeyEngine:

Vector3f.angleBetween(otherVector)

bo właśnie tego tu przyjechałem :)

VeganEye
źródło
-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;
}

}

Daniel Quinn
źródło