c++ - triangulo - puntos colineales ejercicios
¿Ángulo entre 3 puntos? (7)
Dados los puntos ABC, ¿cómo podría encontrar el ángulo ABC? Estoy creando una herramienta de pago manual para una aplicación de dibujo vectorial y para minimizar la cantidad de puntos que genera, no agregaré puntos a menos que el ángulo de la posición del mouse y los últimos 2 puntos sean mayores que un cierto umbral. Gracias
lo que yo tenia
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;
}
β = arccos ((a ^ 2 + c ^ 2 - b ^ 2) / 2ac)
donde a es el lado opuesto del ángulo α, b es el ángulo opuesto β, y c es el ángulo opuesto γ. Entonces β es lo que llamaste ángulo ABC.
¿Fuera de contexto? Pero puedes hacerlo con la ley de los cosenos:
Encuentre la distancia entre A y B (llame a este x), y la distancia entre B y C (llame a este y), y la distancia entre A y C (llame a este z).
Entonces sabes que z ^ 2 = x ^ 2 + y ^ 2-2 * x y cos (ÁNGULO QUE QUIERES)
por lo tanto, ese ángulo es cos ^ -1 ((z ^ 2-x ^ 2-y ^ 2) / (2xy)) = ANGLE
Aquí hay una manera 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);
}
Basado en la excelente solución de @valdo.
Aquí hay una manera rápida y correcta de calcular el valor del ángulo correcto:
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;
}
El enfoque con arccos
es peligroso, porque corremos el riesgo de que su argumento sea igual a, digamos, 1.0000001 y termine con EDOMAIN
error EDOMAIN
. Incluso atan
aproximación es peligrosa, porque involucra divisiones, lo que puede llevar a una división por error cero. Mejor uso atan2
, pasándole valores dx
y dy
.
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 tu primer error. El producto de punto real 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 cierta pérdida de precisión durante el cálculo, es teóricamente posible que los dacos
se vuelvan más grandes que 1 (o menos que -1). Por lo tanto, debe comprobar esto explícitamente.
Más una nota de rendimiento: se llama a una función sqrt
pesada dos veces para calcular la longitud de dos vectores. Luego divides el producto de puntos por esas longitudes. En su lugar, podría llamar a 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. Tu método producirá el mismo ángulo para ABC y CBA.
Un método correcto para calcular el ángulo es como sugiere "oslvbo":
float angba = atan2(ab.y, ab.x);
float angbc = atan2(cb.y, cb.x);
float rslt = angba - angbc;
float rs = (rslt * 180) / 3.141592;
(Acabo de reemplazar atan
por atan2
).
Es el método más simple, que siempre da el resultado correcto. El inconveniente de este método es que en realidad 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 entenderlo), sin embargo, es superior desde el punto de vista del rendimiento. Simplemente llama una vez a una 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);
}
EDITAR:
Recientemente he estado trabajando en un tema relacionado. Y luego me he dado 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 en la dirección X (positiva). Obviamente, la rotación de ambos vectores no afecta el ángulo entre ellos. OTOH después de dicha rotación, solo hay que averiguar el ángulo del segundo vector en relación con el eje X. Y esto es exactamente para lo que está atan2
.
La rotación se logra multiplicando un vector por la siguiente matriz:
- hacha, ay
- -ay, hacha
Una vez puede ver que el vector a
multiplicado por dicha matriz de hecho gira hacia el eje X positivo.
Nota: Estrictamente hablando, la matriz anterior no solo gira, sino que también se escala. Pero esto está bien en nuestro caso, ya que lo único que importa es la dirección del vector, no su longitud.
El vector girado b
convierte en:
- ax * bx + ay * by = un punto b
- -ay * bx + ax * by = una cruz b
Finalmente, 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);
}
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;