Угол между 3 точками? - PullRequest
       9

Угол между 3 точками?

17 голосов
/ 15 августа 2010

Учитывая точки ABC, как я могу найти угол ABC?Я делаю простой инструмент для векторного рисования и, чтобы свести к минимуму количество точек, которые оно генерирует, я не буду добавлять точки, если угол положения мыши и последних 2 точек не превышает определенный порог.Спасибо

что у меня было:

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;


}

Ответы [ 7 ]

29 голосов
/ 15 августа 2010

Первые предложения относительно вашего метода:

То, что вы называете ac, на самом деле cb.Но это нормально, это то, что действительно нужно.Далее

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

Это ваша первая ошибка. действительное точечное произведение двух векторов:

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

Теперь,

float rslt = acos(dacos);

Здесь вы должны заметить, что из-за некоторой потери точности во время вычисления это теоретическивозможно, что dacos станет больше 1 (или меньше -1).Следовательно - вы должны проверить это явно.

Плюс примечание по производительности: вы дважды вызываете тяжелую функцию sqrt для вычисления длины двух векторов.Затем вы делите точечное произведение на эти длины.Вместо этого вы можете вызвать sqrt для умножения квадратов длины обоих векторов.

И, наконец, вы должны отметить, что ваш результат точен до sign.То есть ваш метод не будет различать 20 ° и -20 °, поскольку косинус обоих одинаков.Ваш метод даст один и тот же угол для ABC и CBA.

Один правильный метод расчета угла такой, как предлагает "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;

(я только что заменил atanна atan2).

Это самый простой метод, который всегда дает правильный результат.Недостаток этого метода в том, что вы фактически дважды вызываете тяжелую тригонометрическую функцию atan2.

Я предлагаю следующий метод.Это немного сложнее (требуются некоторые навыки тригонометрии, чтобы понять), однако это лучше с точки зрения производительности.Он просто вызывает тригонометрическую функцию atan2.И никаких вычислений квадратного корня.

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);


}

РЕДАКТИРОВАТЬ:

Недавно я работал над связанной темой.И тогда я понял, что есть лучший способ.Это на самом деле более или менее то же самое (за кадром).Однако ИМХО это более просто.

Идея состоит в том, чтобы повернуть оба вектора так, чтобы первый из них был выровнен по (положительному) X-направлению.Очевидно, что вращение обоих векторов не влияет на угол между ними.OTOH после такого поворота нужно только определить угол второго вектора относительно оси X.И это именно то, для чего atan2.

Вращение достигается путем умножения вектора на следующую матрицу:

  • ax, ay
  • -ay,ax

Однажды можно увидеть, что вектор a, умноженный на такую ​​матрицу, действительно поворачивается к положительной оси X.

Примечание: Строго говоря, приведенная выше матрицаэто не просто вращение, это также масштабирование.Но в нашем случае это нормально, так как единственное, что имеет значение, это направление вектора, а не его длина.

Повернутый вектор b становится:

  • ax * bx + ay* by = a dot b
  • -ay * bx + ax * by = a cross b

Наконец, ответ может быть выражен как:

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);
}
4 голосов
/ 15 августа 2010

β = arccos ((a ^ 2 + c ^ 2 - b ^ 2) / 2ac)

, где a - противоположный угол стороны α, b - противоположный угол β, а c - противоположный угол γ,Итак, β - это то, что вы назвали углом ABC.

3 голосов
/ 15 августа 2010

Подход с arccos опасен, потому что мы рискуем иметь его аргумент равным, скажем, 1.0000001 и в конечном итоге с ошибкой EDOMAIN.Даже atan подход опасен, потому что он включает в себя деления, которые могут привести к делению на ноль ошибок.Лучше использовать atan2, передав ему dx и dy значения.

2 голосов
/ 21 февраля 2014

Вот быстрый и правильный способ вычисления правильного значения угла:

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;
} 
1 голос
/ 15 августа 2010
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;
1 голос
/ 15 августа 2010

Не по теме? Но вы можете сделать это с помощью закона косинусов:

Найдите расстояние между A и B (назовите это x), расстояние между B и C (назовите это y) и расстояние между A и C (назовите это z).

Тогда вы знаете, что z ^ 2 = x ^ 2 + y ^ 2-2 * x y cos (УГОЛ ВЫ ХОТИТЕ)

следовательно, этот угол равен cos ^ -1 ((z ^ 2-x ^ 2-y ^ 2) / (2xy)) = ANGLE

0 голосов
/ 08 мая 2016

Вот способ OpenCV получить угол между 3 точками (A, B, C) с B в качестве вершины:

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

На основе превосходного решения @ valdo

...