Угол между 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;


}
17
задан jmasterx 15 August 2010 в 04:32
поделиться

5 ответов

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

То, что вы называете 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 для умножения квадратов длины обоих векторов.

И, наконец, обратите внимание, что ваш результат точен до знака . То есть ваш метод не различает 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 после такого поворота остается только определить угол 2-го вектора относительно оси X. И это именно то, для чего предназначен atan2 .

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

  • ax, ay
  • -ay, ax

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

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

Повернутый вектор b принимает следующий вид:

  • ax * bx + ay * by = a dot b
  • -ay * bx + ax * by = 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);
}
30
ответ дан 30 November 2019 в 11:22
поделиться
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
ответ дан 30 November 2019 в 11:22
поделиться

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

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

4
ответ дан 30 November 2019 в 11:22
поделиться

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

3
ответ дан 30 November 2019 в 11:22
поделиться

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

Найдите расстояние между 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)) = УГОЛ

1
ответ дан 30 November 2019 в 11:22
поделиться
Другие вопросы по тегам:

Похожие вопросы: