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

Учитывая точки ABC, как мне найти угол ABC? Я делаю инструмент feehand для приложения векторного рисования и для сведения к минимуму количества генерируемых очков, я не буду добавлять точки, если угол положения мыши и последние 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;


}

Ответы

Ответ 1

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

То, что вы называете 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 the only invocation of the heavy function.
    // It 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:

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

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

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

  • a.x, a.y
  • -a.y, a.x

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

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

Вращающийся вектор b становится:

  • a.x * b.x + a.y * b.y = a точка b
  • -a.y * b.x + a.x * b.y = a крест 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);
}

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

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

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

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

Ответ 6

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;

Ответ 7

Здесь путь 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