2010-08-15 31 views
14

Dados los puntos ABC, ¿cómo podría encontrar el ángulo ABC? Estoy haciendo una herramienta de mano para una aplicación de dibujo vectorial y para minimizar el número de puntos que genera, no agregaré puntos a menos que el ángulo de la posición del mouse y los últimos 2 puntos sea mayor que un cierto umbral. Gracias ¿Ángulo entre 3 puntos?

lo que tenía:

int CGlEngineFunctions::GetAngleABC(POINTFLOAT a, POINTFLOAT b, POINTFLOAT c) 
{ 
    POINTFLOAT ab; 
    POINTFLOAT ac; 

    ab.x = b.x - a.x; 
    ab.y = b.y - a.y; 

    ac.x = b.x - c.x; 
    ac.y = b.y - c.y; 

    float dotabac = (ab.x * ab.y + ac.x * ac.y); 
    float lenab = sqrt(ab.x * ab.x + ab.y * ab.y); 
    float lenac = sqrt(ac.x * ac.x + ac.y * ac.y); 

    float dacos = dotabac/lenab/lenac; 

    float rslt = acos(dacos); 
    float rs = (rslt * 180)/3.141592; 
    RoundNumber(rs); 
    return (int)rs; 


} 
+2

que estoy haciendo bastante bien, tengo una algorthm por ella, pero no estaba haciendo el truco. – jmasterx

+5

@abelenky: y eso hace que la pregunta sea "poco clara o no útil" ¿cómo, exactamente? Es posible que haya entendido mal el propósito de la rep. No está ahí para permitirle castigar a las personas por tratar de hacer algo que sea nuevo para ellos. – jalf

Respuesta

25

primeras sugerencias con respecto a su método:

Lo que llamas ac es en realidad cb. Pero está bien, esto es lo que realmente necesitaba. Siguiente,

float dotabac = (ab.x * ab.y + ac.x * ac.y); 

Este es su primer error. El producto real escalar de dos vectores es:

float dotabac = (ab.x * ac.x + ab.y * ac.y); 

Ahora,

float rslt = acos(dacos); 

Aquí debe tener en cuenta que, debido a una cierta pérdida de precisión en el cálculo que es teóricamente posible que dacos llegarán a ser más grande que 1 (o menor que -1). Por lo tanto, debe verificar esto explícitamente.

Más una nota de rendimiento: llama a una función pesada sqrt dos veces para calcular la longitud de dos vectores. Luego divide el producto de puntos por esas longitudes. En su lugar, puede llamar al sqrt en la multiplicación de cuadrados de longitud de ambos vectores.

Y, por último, debe tener en cuenta que su resultado es exacto hasta el sign. Es decir, su método no distinguirá 20 ° y -20 °, ya que el coseno de ambos es el mismo. Su método arrojará el mismo ángulo para ABC y CBA.

Un método correcto para calcular el ángulo es como "oslvbo" sugiere:

float angba = atan2(ab.y, ab.x); 
float angbc = atan2(cb.y, cb.x); 
float rslt = angba - angbc; 
float rs = (rslt * 180)/3.141592; 

(yo sólo he sustituido por atanatan2).

Es el método más simple, que siempre da el resultado correcto. El inconveniente de este método es que realmente llama a una función de trigonometría pesada atan2 dos veces.

Sugiero el siguiente método. Es un poco más complejo (requiere algunas habilidades de trigonometría para comprender), sin embargo, es superior desde el punto de vista del rendimiento. Simplemente llama una vez a la función de trigonometría atan2. Y no hay cálculos de raíz cuadrada.

int CGlEngineFunctions::GetAngleABC(POINTFLOAT a, POINTFLOAT b, POINTFLOAT c) 
{ 
    POINTFLOAT ab = { b.x - a.x, b.y - a.y }; 
    POINTFLOAT cb = { b.x - c.x, b.y - c.y }; 

    // dot product 
    float dot = (ab.x * cb.x + ab.y * cb.y); 

    // length square of both vectors 
    float abSqr = ab.x * ab.x + ab.y * ab.y; 
    float cbSqr = cb.x * cb.x + cb.y * cb.y; 

    // square of cosine of the needed angle  
    float cosSqr = dot * dot/abSqr/cbSqr; 

    // this is a known trigonometric equality: 
    // cos(alpha * 2) = [ cos(alpha) ]^2 * 2 - 1 
    float cos2 = 2 * cosSqr - 1; 

    // Here's the only invocation of the heavy function. 
    // It's a good idea to check explicitly if cos2 is within [-1 .. 1] range 

    const float pi = 3.141592f; 

    float alpha2 = 
     (cos2 <= -1) ? pi : 
     (cos2 >= 1) ? 0 : 
     acosf(cos2); 

    float rslt = alpha2/2; 

    float rs = rslt * 180./pi; 


    // Now revolve the ambiguities. 
    // 1. If dot product of two vectors is negative - the angle is definitely 
    // above 90 degrees. Still we have no information regarding the sign of the angle. 

    // NOTE: This ambiguity is the consequence of our method: calculating the cosine 
    // of the double angle. This allows us to get rid of calling sqrt. 

    if (dot < 0) 
     rs = 180 - rs; 

    // 2. Determine the sign. For this we'll use the Determinant of two vectors. 

    float det = (ab.x * cb.y - ab.y * cb.y); 
    if (det < 0) 
     rs = -rs; 

    return (int) floor(rs + 0.5); 


} 

EDIT:

Recientemente he estado trabajando en un tema relacionado. Y luego me di cuenta de que hay una mejor manera. En realidad es más o menos lo mismo (detrás de las escenas). Sin embargo, es más sencillo en mi humilde opinión.

La idea es rotar ambos vectores para que el primero se alinee con la dirección X (positiva). Obviamente, la rotación de ambos vectores no afecta el ángulo entre ellos. OTOH después de tal rotación uno solo tiene que descubrir el ángulo del segundo vector relativo al eje X. Y esto es exactamente para lo que es atan2.

rotación se consigue multiplicando un vector por la matriz siguiente:

  • Ax, Ay
  • -ay, hacha

Una vez puede ver que vector a multiplicada por una matriz tal efecto, gira hacia el eje X positivo.

Nota: Estrictamente hablando, la matriz anterior no solo está girando, sino que también está escalando. Pero esto está bien en nuestro caso, ya que lo único que importa es la dirección del vector, no su longitud.

girado vector b se convierte en:

  • a.x * b.x + a.y * b.y = un dot b
  • -a.y * b.x + a.x * b.y = un cruz b

Por último, la respuesta se puede expresar como:

int CGlEngineFunctions::GetAngleABC(POINTFLOAT a, POINTFLOAT b, POINTFLOAT c) 
{ 
    POINTFLOAT ab = { b.x - a.x, b.y - a.y }; 
    POINTFLOAT cb = { b.x - c.x, b.y - c.y }; 

    float dot = (ab.x * cb.x + ab.y * cb.y); // dot product 
    float cross = (ab.x * cb.y - ab.y * cb.x); // cross product 

    float alpha = atan2(cross, dot); 

    return (int) floor(alpha * 180./pi + 0.5); 
} 
+0

¡Buena solución! Me preocupa cómo tratar el problema del ángulo de señal hasta que leo su respuesta –

+0

La última función (que tiene 'return (int) floor (alpha * 180./pi + 0.5);') es buena y da una respuesta diferente a abc y cba. ¡Funciona bien! –

+0

única solución que no me dio problemas de precisión. ¡Gracias! – Tiago

1

Fuera de tema? Pero puedes hacerlo con la ley de los cosenos:

Encuentra la distancia entre A y B (llama a esta x), y la distancia entre B y C (llama a esto y), y la distancia entre A y C (llama esto z).

entonces usted sabe que z^2 = x^2 + y^2-2 * x y cos (ángulo que desee)

por lo tanto, que el ángulo es cos^-1 ((z^2 -x^2-y^2)/(2xy)) = ÁNGULO

+0

Has perdido el negativo en la fracción. Debería ser 'x^2 + y^2 - z^2' como en mi respuesta. –

4

beta = arccos ((a^2 + c^2 - b^2)/2AC)

donde a es el lado opuesto el ángulo α, b es el ángulo opuesto β, yc es el ángulo opuesto γ. Entonces, β es lo que llamas ángulo ABC.

+1

¿Cómo cuadras un punto? – jmasterx

+0

@Milo: no lo hace; está usando 'a',' b' y 'c' como distancias entre pares de puntos. –

+0

oh ok gracias :) – jmasterx

1
float angba = atan((a.y - b.y)/(a.x - b.x)); 
float angbc = atan((c.y - b.y)/(c.x - b.y)); 
float rslt = angba - angbc; 
float rs = (rslt * 180)/3.141592; 
+2

En lugar de usar atan (dy/dx) es mejor usar atan2 (dy, dx) – valdo

+1

@valdo: Eso es correcto. Gracias por tu recordatorio. – oslvbo

3

Acercamiento con arccos es peligroso, porque corremos el riesgo de tener que es igual al argumento, digamos, 1,0000001 y terminar con EDOMAIN error. Incluso el enfoque atan es peligroso, porque involucra divisiones, lo que puede llevar a una división por error cero. Mejor uso atan2, pasándole dx y dy valores.

2

Aquí es una manera rápida y correcta de calcular el valor del ángulo derecho:

double AngleBetweenThreePoints(POINTFLOAT pointA, POINTFLOAT pointB, POINTFLOAT pointC) 
{ 
    float a = pointB.x - pointA.x; 
    float b = pointB.y - pointA.y; 
    float c = pointB.x - pointC.x; 
    float d = pointB.y - pointC.y; 

    float atanA = atan2(a, b); 
    float atanB = atan2(c, d); 

    return atanB - atanA; 
} 
+0

Esto devuelve respuestas en el rango '[-2 * pi ... + 2 * pi]' (2 revoluciones). – chux

0

Aquí hay una forma OpenCV para obtener el ángulo entre 3 puntos (A, B, C) con B como el vértice:

int getAngleABC(cv::Point2d a, cv::Point2d b, cv::Point2d c) 
{ 
    cv::Point2d ab = { b.x - a.x, b.y - a.y }; 
    cv::Point2d cb = { b.x - c.x, b.y - c.y }; 

    float dot = (ab.x * cb.x + ab.y * cb.y); // dot product 
    float cross = (ab.x * cb.y - ab.y * cb.x); // cross product 

    float alpha = atan2(cross, dot); 

    return (int) floor(alpha * 180./M_PI + 0.5); 
} 

Sobre la base de la excelente solución mediante @valdo

Cuestiones relacionadas