Como calcular um ângulo a partir de três pontos? [fechadas]

120

Vamos dizer que você tem isso:

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

Suponha que esse P1seja o ponto central de um círculo. É sempre a mesma coisa. Eu quero o ângulo que é composto por P2e P3, ou em outras palavras, o ângulo que está próximo P1. O ângulo interno para ser preciso. Sempre será um ângulo agudo, portanto menor que -90 graus.

Eu pensei: Cara, isso é matemática simples em geometria. Mas tenho procurado uma fórmula há cerca de 6 horas e só encontro pessoas falando sobre coisas complicadas da NASA, como arccos e produtos escalares vetoriais. Minha cabeça parece que está na geladeira.

Alguns gurus da matemática aqui que pensam que este é um problema simples? Eu não acho que a linguagem de programação seja importante aqui, mas para quem pensa que é: java e objetivo-c. Preciso disso para ambos, mas não o etiquetei para estes.

Lance Roberts
fonte

Respostas:

87

Se você quer dizer que o ângulo em que P1 é o vértice, a Lei dos Cossenos deve funcionar:

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

onde P 12 é o comprimento do segmento de P1 a P2, calculado por

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

Lance Roberts
fonte
@Rafa Firenze cos ^ -1 é uma notação comum para acos, mas acos é menos ambíguo. en.wikipedia.org/wiki/Inverse_trigonometric_functions
geon 21/10
Deixarei a edição, pois não faz mal a nada, mas, com os graus de Matemática / CS / EE, cos ^ -1 é certamente a notação mais comum.
Lance Roberts
1
Apenas um punhado de idiomas usa um sinal de intercalação para 'poder de'; portanto, se você não quiser chamá-lo de arcos, digite apenas cos⁻¹. (Se você estiver usando um sistema operacional comercial que dificulta a digitação de expoentes, espero que existam aplicativos de capas de chaves que você possa comprar ou talvez um plug-in de navegador que possa instalar. Ou pesquise na Web e copie e cole.)
Michael Scheper
1
@ MichaelScheper, eu estava usando apenas o cursor nos comentários em que o html é limitado. Eu certamente usaria a notação sub / sobrescrita em qualquer resposta real.
Lance Roberts
47

Fica muito simples se você pensar em dois vetores, um do ponto P1 ao P2 e outro do P1 ao P3

então:
a = (p1.x - p2.x, p1.y - p2.y)
b = (p1.x - p3.x, p1.y - p3.y)

Você pode inverter a fórmula do produto escalar:
ponto de produto
para obter o ângulo:
ângulo entre dois vetores

Lembre-se de que ponto de produtoapenas significa: a1 * b1 + a2 * b2 (apenas duas dimensões aqui ...)

Andrea Ambu
fonte
1
Ah magnitude do vetor
Daniel Little
Verifique a solução atan2.
Luc Boissaye 7/0318
25

A melhor maneira de lidar com a computação em ângulo é usar atan2(y, x)que, dado um ponto, x, yretorne o ângulo desse ponto e o X+eixo em relação à origem.

Dado que o cálculo é

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

ou seja, você basicamente traduz os dois pontos por -P1(em outras palavras, você traduz tudo para que P1acabe na origem) e depois considera a diferença dos ângulos absolutos de P3e de P2.

A vantagem atan2disso é que o círculo completo é representado (você pode obter qualquer número entre -π e π) onde, em vez disso, acosé necessário lidar com vários casos, dependendo dos sinais para calcular o resultado correto.

O único ponto singular para atan2é (0, 0)... significando que ambos P2e P3devem ser diferentes, P1como nesse caso, não faz sentido falar sobre um ângulo.

6502
fonte
Obrigado pela sua resposta. Era exatamente o que eu estava procurando. Solução simples e você pode obter facilmente o ângulo no sentido anti-horário se eu adicionar 2pi quando o valor for negativo.
Mario
@marcpt: atan2é exatamente o que é necessário para esse problema, mas parece que a maioria das pessoas que chega a essa pergunta simplesmente não consegue ler ou não consegue entender por que as acossoluções baseadas são ruins. Por sorte o suficiente para me deixei a "alguém está errado na internet" ( xkcd.com/386 fase) há muitos anos e eu não estou indo para começar uma luta para defender o óbvio :-)
6502
Obrigado por apontar isso, mas você pode lidar com 3D dessa maneira?
nicoco 28/09
1
@nicoco: em três dimensões, como você define o ângulo? Mais especificamente, o ângulo pode ser negativo ou superior a pi (180 graus)? Dois vetores não paralelos em 3d definem um plano, mas o plano pode ser "visto" de dois lados: visto de um lado A aparecerá "à esquerda" de B e do outro aparecerá "à direita". .
6502
@ 6505 Obrigado pela sua resposta, postei antes de pensar no meu problema. Eu já entendi agora.
nicoco 28/09
19

Deixe-me dar um exemplo em JavaScript, lutei muito com isso:

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

Bônus: exemplo com tela HTML5

shaman.sir
fonte
5
Você pode tornar isso mais eficiente, fazendo menos sqrte esquadrinhando. Veja minha resposta aqui (escrita em Ruby) ou nesta demonstração atualizada (JavaScript).
Phrogz
Você pode usar atan2 para uma solução mais simples.
Luc Boissaye 7/0318
15

Basicamente, o que você tem são dois vetores, um vetor de P1 a P2 e outro de P1 a P3. Então, tudo o que você precisa é de uma fórmula para calcular o ângulo entre dois vetores.

Dê uma olhada aqui para uma boa explicação e a fórmula.

texto alternativo

Andre Miller
fonte
12

Se você está pensando em P1 como o centro de um círculo, está pensando muito complicado. Você tem um triângulo simples, portanto seu problema é solucionável pela lei dos cossenos . Não há necessidade de qualquer transformação de coordenadas polares ou algo assim. Digamos que as distâncias sejam P1-P2 = A, P2-P3 = B e P3-P1 = C:

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

Tudo o que você precisa fazer é calcular o comprimento das distâncias A, B e C. Essas são facilmente disponíveis nas coordenadas x e y dos seus pontos e no teorema de Pitágoras

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

Treb
fonte
Estou um pouco confuso como realmente implementar isso como você está tratando P1 etc como valores individuais em vez de (x, y)
Dominic
@ Tobias Dominic: A Notação P1-P2 = Anão deve ser lida como "Para calcular A, subtraia P2 de P1", mas como "Estou definindo A como a distância de P1 a P2", que pode ser calculada usando a segunda equação. Eu só queria definir uma abreviação para as distâncias, para tornar as equações mais legíveis.
Treb
8

Encontrei recentemente um problema semelhante, mas precisava diferenciar entre ângulos positivos e negativos. Caso isso seja útil a alguém, recomendo o trecho de código que peguei nesta lista de e-mails sobre a detecção de rotação em um evento de toque para 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;
 }
Marc
fonte
7

Solução geométrica muito simples com explicação

Poucos dias atrás, um caiu no mesmo problema e teve que se sentar com o livro de matemática. Resolvi o problema combinando e simplificando algumas fórmulas básicas.


Vamos considerar esta figura-

ângulo

Queremos saber ϴ , então precisamos descobrir α e β primeiro. Agora, para qualquer linha reta

y = m * x + c

Seja A = (ax, ay) , B = (bx, by) e O = (boi, oy) . Então, para a linha 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)

Da mesma forma, para a linha OB -

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

Agora precisamos ϴ = β - α. Na trigonometria, temos uma fórmula

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

Após substituir o valor de tan α(do eqn-2) e tan b(do eqn-3) no eqn-4, e aplicar a simplificação, obtemos

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

Assim,

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

É isso!


Agora, veja a figura a seguir:

ângulo

Esse método C # ou Java calcula o ângulo ( ϴ ) -

    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
fonte
Como esse método pode ser usado para um triângulo equilátero?
21717 Vikrant
1
Bem, sua resposta está funcionando bem agora. Foi um problema de lógica no meu código semana antes.
Vikrant
6

No Objective-C, você poderia fazer isso

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

Ou leia mais aqui

Adeem Maqsood Basraa
fonte
7
Oh não. Existem três pontos, o centro não está em (0,0) e isso dá um ângulo de um triângulo retângulo, não o ângulo do ápice. E que tipo de nome é "xpoint" para um ângulo?
perfil completo de Jim Balter
4

Você mencionou um ângulo assinado (-90). Em muitas aplicações, os ângulos podem ter sinais (positivos e negativos, consulte http://en.wikipedia.org/wiki/Angle ). Se os pontos são (digamos) P2 (1,0), P1 (0,0), P3 (0,1), então o ângulo P3-P1-P2 é convencionalmente positivo (PI / 2), enquanto o ângulo P2-P1- P3 é negativo. O uso dos comprimentos dos lados não fará distinção entre + e - portanto, se isso for importante, você precisará usar vetores ou uma função como Math.atan2 (a, b).

Os ângulos também podem se estender além de 2 * PI e, embora isso não seja relevante para a pergunta atual, era suficientemente importante que eu escrevesse minha própria classe Angle (também para garantir que graus e radianos não se misturassem). As perguntas sobre se o ângulo1 é menor que o ângulo2 dependem criticamente de como os ângulos são definidos. Também pode ser importante decidir se uma linha (-1,0) (0,0) (1,0) é representada como Math.PI ou -Math.PI

peter.murray.rust
fonte
4

meu programa de demonstração de ângulo

Recentemente, eu também tenho o mesmo problema ... No Delphi, é muito semelhante ao 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
fonte
2

Aqui está um método C # para retornar o ângulo (0-360) no sentido anti-horário da horizontal para um ponto em um círculo.

    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

Paulo
fonte
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
fonte
0

existe uma resposta simples para isso usando matemática do ensino médio.

Digamos que você tenha 3 pontos

Para obter ângulo do ponto A a B

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

Para obter ângulo do ponto B a C

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

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

Acabei de usar esse código no projeto recente que fiz, altere B para P1. Você também pode remover o "180 +" se quiser

James Penner
fonte
-1

bem, as outras respostas parecem cobrir tudo o necessário, então eu gostaria de adicionar isso se você estiver usando o JMonkeyEngine:

Vector3f.angleBetween(otherVector)

como é isso que eu vim aqui procurando :)

VeganEye
fonte
-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
fonte