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 atan
atan2
).
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:
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);
}
que estoy haciendo bastante bien, tengo una algorthm por ella, pero no estaba haciendo el truco. – jmasterx
@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