¿Cómo calcular un ángulo a partir de tres puntos? [cerrado]

120

Digamos que tienes esto:

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

Suponga que P1es el punto central de un círculo. Siempre es el mismo. Quiero el ángulo que está formado por P2y P3, o en otras palabras, el ángulo que está al lado P1. El ángulo interior para ser precisos. Siempre será un ángulo agudo, menos de -90 grados.

Pensé: Hombre, eso es simple geometría matemática. Pero he buscado una fórmula durante alrededor de 6 horas, y solo encuentro personas que hablan de cosas complicadas de la NASA como arccos y productos escalares vectoriales. Mi cabeza se siente como si estuviera en un refrigerador.

¿Algunos gurús de las matemáticas que piensan que esto es un problema simple? No creo que el lenguaje de programación importe aquí, pero para aquellos que creen que sí lo es: java y aim-c. Lo necesito para ambos, pero no lo he etiquetado para estos.

Lance Roberts
fuente

Respuestas:

87

Si te refieres al ángulo del que P1 es el vértice, entonces usar la Ley de los cosenos debería funcionar:

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

donde P 12 es la longitud del segmento de P1 a P2, calculado por

raíz cuadrada ((P1 x - P2 x ) 2 + (P1 y - P2 y ) 2 )

Lance Roberts
fuente
@Rafa Firenze cos ^ -1 es una notación común para acos, pero acos es menos ambigua. en.wikipedia.org/wiki/Inverse_trigonometric_functions
geon
Dejaré la edición ya que no duele nada, pero teniendo grados de Matemáticas / CS / EE, cos ^ -1 es sin duda la notación más común.
Lance Roberts
1
Solo un puñado de idiomas usa un signo de intercalación para 'poder de', así que si no desea llamarlo arcos, simplemente escriba cos⁻¹. (Si está utilizando un sistema operativo comercial que dificulta la escritura de exponentes, espero que haya aplicaciones de teclas que pueda comprar, o tal vez un complemento de navegador que pueda instalar. O puede buscar en la web y copiar y pegar).
Michael Scheper
1
@MichaelScheper, solo estaba usando el signo de intercalación en los comentarios donde html es limitado. Ciertamente, solo usaría la notación sub / superíndice en cualquier respuesta real.
Lance Roberts
47

Se vuelve muy simple si lo piensa como dos vectores, uno del punto P1 al P2 y otro del P1 al P3

entonces:
a = (p1.x - p2.x, p1.y - p2.y)
b = (p1.x - p3.x, p1.y - p3.y)

Luego puede invertir la fórmula del producto escalar:
producto escalar
para obtener el ángulo:
ángulo entre dos vectores

Recuerde que producto escalarsolo significa: a1 * b1 + a2 * b2 (solo 2 dimensiones aquí ...)

Andrea Ambu
fuente
1
Ah magnitud del vector
Daniel Little
Verifique la solución de atan2.
Luc Boissaye
25

La mejor manera de lidiar con el cálculo de ángulos es usar atan2(y, x)que dado un punto x, ydevuelve el ángulo desde ese punto y el X+eje con respecto al origen.

Dado que el cálculo es

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

es decir, básicamente traduces los dos puntos por -P1(en otras palabras, traduces todo para que P1termine en el origen) y luego consideras la diferencia de los ángulos absolutos de P3y de P2.

La ventaja de atan2es que está representado el círculo completo (puede obtener cualquier número entre -π y π) donde, en cambio acos, debe manejar varios casos dependiendo de los signos para calcular el resultado correcto.

El único punto singular para atan2es (0, 0)... lo que significa que ambos P2y P3deben ser diferentes, P1ya que en ese caso no tiene sentido hablar de un ángulo.

6502
fuente
Gracias por tu respuesta. Eso era exactamente lo que estaba buscando. Solución simple y puede obtener fácilmente el ángulo en sentido antihorario si solo agrego 2pi cuando el valor es negativo.
Mario
@marcpt: atan2es exactamente lo que se necesita para este problema, pero parece que la mayoría de las personas que llegan a esta pregunta simplemente no pueden leer o no pueden entender por qué las acossoluciones basadas en datos son malas. Afortunadamente para mí, dejé la fase de "alguien está equivocado en Internet" ( xkcd.com/386 ) hace muchos años y no voy a comenzar una pelea por defender lo obvio :-)
6502
Gracias por señalar esto, pero ¿puedes manejar 3D de esta manera?
nicoco
1
@nicoco: en tres dimensiones ¿cómo se define el ángulo? Más específicamente, ¿el ángulo puede ser negativo o superior a pi (180 grados)? Dos vectores no paralelos en 3d definen un plano, pero el plano se puede "ver" desde dos lados: visto desde un lado A aparecerá "a la izquierda" de B y desde el otro aparecerá "a la derecha". .
6502
@ 6505 Gracias por tu respuesta, publiqué antes de pensar en mi problema. Sin embargo, lo tengo resuelto ahora.
nicoco
19

Permítanme darles un ejemplo en JavaScript, he luchado mucho con eso:

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

Bonificación: ejemplo con HTML5-canvas

chamán, señor
fuente
5
Puede hacer esto más eficiente haciendo menos sqrty elevando al cuadrado. Vea mi respuesta aquí (escrita en Ruby), o en esta demostración actualizada (JavaScript).
Phrogz
Podría usar atan2 para una solución más simple.
Luc Boissaye
15

Básicamente, lo que tienes son dos vectores, un vector de P1 a P2 y otro de P1 a P3. Entonces, todo lo que necesita es una fórmula para calcular el ángulo entre dos vectores.

Eche un vistazo aquí para obtener una buena explicación y la fórmula.

texto alternativo

Andre Miller
fuente
12

Si está pensando en P1 como el centro de un círculo, está pensando demasiado complicado. Tienes un triángulo simple, por lo que tu problema se puede resolver con la ley de los cosenos . No hay necesidad de ninguna transformación de coordenadas polares o algo por el estilo. Digamos que las distancias son P1-P2 = A, P2-P3 = B y P3-P1 = C:

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

Todo lo que necesita hacer es calcular la longitud de las distancias A, B y C.Estas están fácilmente disponibles en las coordenadas xey de sus puntos y el teorema de Pitágoras

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

Treb
fuente
Estoy un poco confundido sobre cómo implementar esto realmente, ya que está tratando P1, etc.como valores individuales en lugar de (x, y)
Dominic
@Dominic Tobias: La notación P1-P2 = Ano debe leerse como "Para calcular A, reste P2 de P1", sino como "Estoy definiendo A como la distancia de P1 a P2", que luego se puede calcular usando la segunda ecuación. Solo quería definir una abreviatura de las distancias, para hacer las ecuaciones más legibles.
Treb
8

Me encontré con un problema similar recientemente, solo necesitaba diferenciar entre ángulos positivos y negativos. En caso de que esto sea útil para alguien, recomiendo el fragmento de código que tomé de esta lista de correo sobre cómo detectar la rotación sobre un evento táctil 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;
 }
Bagazo
fuente
7

Solución geométrica muy simple con explicación

Hace unos días, tuve el mismo problema y tuve que sentarme con el libro de matemáticas. Resolví el problema combinando y simplificando algunas fórmulas básicas.


Consideremos esta figura-

ángulo

Queremos saber ϴ , por lo que primero debemos averiguar α y β . Ahora, para cualquier línea recta

y = m * x + c

Sea- A = (ax, ay) , B = (bx, by) y O = (ox, oy) . Entonces, para la línea 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)

Del mismo modo, para la línea OB -

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

Ahora, lo necesitamos ϴ = β - α. En trigonometría tenemos una fórmula:

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

Después de reemplazar el valor de tan α(de eqn-2) y tan b(de eqn-3) en eqn-4, y aplicar la simplificación obtenemos-

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

Entonces,

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

¡Eso es!


Ahora, tome la siguiente figura:

ángulo

Este método C # o Java calcula el á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
fuente
¿Cómo se puede utilizar este método para un triángulo equilátero?
Vikrant
1
Bueno, tu respuesta está funcionando bien ahora. Fue un problema de lógica en mi código la semana anterior.
Vikrant
6

En Objective-C puedes hacer esto

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

O leer más aquí

Adeem Maqsood Basraa
fuente
7
UH no. Hay tres puntos, el centro no está en (0,0) y esto da un ángulo de un triángulo rectángulo, no el ángulo del vértice. ¿Y qué tipo de nombre es "xpoint" para un ángulo?
Jim Balter
4

Mencionaste un ángulo con signo (-90). En muchas aplicaciones, los ángulos pueden tener signos (positivos y negativos, consulte http://en.wikipedia.org/wiki/Angle ). Si los puntos son (digamos) P2 (1,0), P1 (0,0), P3 (0,1) entonces el ángulo P3-P1-P2 es convencionalmente positivo (PI / 2) mientras que el ángulo P2-P1- P3 es negativo. El uso de las longitudes de los lados no distinguirá entre + y - por lo que, si esto importa, deberá usar vectores o una función como Math.atan2 (a, b).

Los ángulos también pueden extenderse más allá de 2 * PI y aunque esto no es relevante para la pregunta actual, fue lo suficientemente importante que escribí mi propia clase de Ángulo (también para asegurarme de que los grados y los radianes no se mezclaran). Las preguntas sobre si el ángulo1 es menor que el ángulo2 depende fundamentalmente de cómo se definen los ángulos. También puede ser importante decidir si una línea (-1,0) (0,0) (1,0) se representa como Math.PI o -Math.PI

peter.murray.rust
fuente
4

mi programa de demostración de ángulos

Recientemente, yo también tengo el mismo problema ... En Delphi es muy similar a 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
fuente
2

Aquí hay un método de C # para devolver el ángulo (0-360) en sentido antihorario desde la horizontal para un punto en un 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;
    }

Saludos, Paul

Pablo
fuente
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
fuente
0

Hay una respuesta simple para esto usando matemáticas de la escuela secundaria.

Digamos que tienes 3 puntos

Para obtener el ángulo del punto A al B

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

Para obtener el ángulo del punto B al C

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

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

Acabo de usar este código en el proyecto reciente que hice, cambie la B a P1 ... también puede eliminar el "180 +" si lo desea

James Penner
fuente
-1

bueno, las otras respuestas parecen cubrir todo lo requerido, así que me gustaría agregar esto si estás usando JMonkeyEngine:

Vector3f.angleBetween(otherVector)

ya que eso es lo que vine a buscar aquí :)

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