Внутренний угол между двумя линиями

у меня есть две строки: Line1 и Line2. Каждая линия определяется двумя точками (P1L1(x1, y1), P2L1(x2, y2) и P1L1(x1, y1), P2L3(x2, y3)). Я хочу знать внутренний угол, определенный этими двумя линиями.

для этого я вычисляю угол каждой линии с абсциссой:

double theta1 = atan(m1) * (180.0 / PI);
double theta2 = atan(m2) * (180.0 / PI);

после того, чтобы узнать угол, я вычисляю следующее:

double angle = abs(theta2 - theta1);

проблема или сомнение, которое у меня есть: иногда я получаю правильный угол, но иногда я получаю дополнительный угол (для меня внешний). Откуда мне знать? при вычитании 180º знать внутренний угол? Есть какой-нибудь алгоритм лучше для этого? Потому что я пробовал некоторые методы: dot product, следующая формула:

result = (m1 - m2) / (1.0 + (m1 * m2));

но у меня всегда одна и та же проблема; я никогда не знал, когда у меня есть внешний угол или внутренний угол!

8 ответов


Я думаю, что вы ищете это внутренний продукт (вы также можете посмотреть на скалярное произведение ввод) двух углов. В вашем случае это дано:

float dx21 = x2-x1;
float dx31 = x3-x1;
float dy21 = y2-y1;
float dy31 = y3-y1;
float m12 = sqrt( dx21*dx21 + dy21*dy21 );
float m13 = sqrt( dx31*dx31 + dy31*dy31 );
float theta = acos( (dx21*dx31 + dy21*dy31) / (m12 * m13) );

ответ в радианах.

EDIT: вот полная реализация. Замените проблемные значения в p1, p2 и p3 и дайте мне знать, что вы получите. Точка p1-это вершина, в которой пересекаются две линии, в соответствии с определение двух линий.

#include <math.h>
#include <iostream>

template <typename T> class Vector2D
{
private:
    T x;
    T y;

public:
    explicit Vector2D(const T& x=0, const T& y=0) : x(x), y(y) {}
    Vector2D(const Vector2D&ltT>& src) : x(src.x), y(src.y) {}
    virtual ~Vector2D() {}

    // Accessors
    inline T X() const { return x; }
    inline T Y() const { return y; }
    inline T X(const T& x) { this->x = x; }
    inline T Y(const T& y) { this->y = y; }

    // Vector arithmetic
    inline Vector2D<T> operator-() const
        { return Vector2D<T>(-x, -y); }

    inline Vector2D<T> operator+() const
        { return Vector2D<T>(+x, +y); }

    inline Vector2D<T> operator+(const Vector2D<T>& v) const
        { return Vector2D<T>(x+v.x, y+v.y); }

    inline Vector2D<T> operator-(const Vector2D<T>& v) const
        { return Vector2D<T>(x-v.x, y-v.y); }

    inline Vector2D<T> operator*(const T& s) const
        { return Vector2D<T>(x*s, y*s); }

    // Dot product
    inline T operator*(const Vector2D<T>& v) const
        { return x*v.x + y*v.y; }

    // l-2 norm
    inline T norm() const { return sqrt(x*x + y*y); }

    // inner angle (radians)
    static T angle(const Vector2D<T>& v1, const Vector2D<T>& v2)
    {
        return acos( (v1 * v2) / (v1.norm() * v2.norm()) );
    }
};

int main()
{
    Vector2D<double> p1(215, 294);
    Vector2D<double> p2(174, 228);
    Vector2D<double> p3(303, 294);

    double rad = Vector2D<double>::angle(p2-p1, p3-p1);
    double deg = rad * 180.0 / M_PI;

    std::cout << "rad = " << rad << "\tdeg = " << deg << std::endl;

    p1 = Vector2D<double>(153, 457);
    p2 = Vector2D<double>(19, 457);
    p3 = Vector2D<double>(15, 470);

    rad = Vector2D<double>::angle(p2-p1, p3-p1);
    deg = rad * 180.0 / M_PI;

    std::cout << "rad = " << rad << "\tdeg = " << deg << std::endl;

    return 0;
}

код выше дает:

rad = 2.12667   deg = 121.849
rad = 0.0939257 deg = 5.38155

if (result > 180)
{
     result = 360 - result;
}

таким образом, это всегда будет внутренний угол. Просто добавьте его после получения результата.


Если вы хотите между углом в 0 градусов до 360 градусов, то используйте следующий код; его полностью протестировано и функционально:

static inline CGFloat angleBetweenLinesInRadians(CGPoint line1Start, CGPoint line1End, CGPoint line2Start, CGPoint line2End) {
double angle1 = atan2(line1Start.y-line1End.y, line1Start.x-line1End.x);
double angle2 = atan2(line2Start.y-line2End.y, line2Start.x-line2End.x);
double result = (angle2-angle1) * 180 / 3.14;
if (result<0) {
    result+=360;
}
return result;

}

Примечание: вращение будет по часовой стрелке;


внутренний угол между 2 векторами ( v1, v2) = arc cos(внутренний продукт (v1,v2) / (модуль(v1) * модуль (v2)) ).

где внутренний продукт (v1,v2) = xv1*xv2 + yv1*yv2

модуль (v) = sqrt(pow(xv,2) + pow(yv,2))

Итак, ответ на ваш вопрос реализован на следующем примере:

#define PI   3.14159258

int main()
{
    double x1,y1,x2,y2,y3;
    double m1, m2;
    double mod1, mod2, innerp, angle;

    cout << "x1 :";
    cin >> x1;
    cout << "y1 :";
    cin >> y1;
    cout << "x2 :";
    cin >> x2;
    cout << "y2 :";
    cin >> y2;
    cout << "y3 :";
    cin >> y3;

    m1 = atan((y2-y1)/(x2-x1)) * 180 / PI;
    m2 = atan((y3-y1)/(x2-x1)) * 180 / PI;

    mod1   = sqrt(pow(y2-y1,2)+pow(x2-x1,2));
    mod2   = sqrt(pow(y3-y1,2)+pow(x2-x1,2));
    innerp = (x2-x1)*(x2-x1) + (y2-y1)*(y3-y1);
    angle  = acos(innerp / (mod1 * mod2)) * 180 / PI;

    cout << "m1 : " << m1 << endl;
    cout << "m2 : " << m2 << endl;
    cout << "angle : " << angle << endl;
}

весь смысл намного проще, чем приведенные ответы:

когда вы используете atan (наклон), вы теряете (буквально) один бит информации, то есть есть ровно два угла (тета) и (тета+ПИ) в диапазоне (0..2 * PI), которые дают одинаковое значение для функции tan ().

просто используйте atan2 (deltax, deltay), и вы получите правильный угол. Например,

atan2(1,1) == PI/4
atan2(-1,-1) == 5*PI/4

затем вычесть, принять абсолютное значение, и если больше, чем PI вычесть из 2*PI.


Если вы используете значение abolute, вы всегда получите острый угол. Это тангенс тета = значение abs m1-m2 over (1 +m1 * m2). Если вы берете арктангенс ваш ответ будет в радианах или градусах, однако Калькулятор находится. Извините, это не язык программирования, я учитель математики, а не программист...


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


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

острые и тупые углы пересечения дополняют друг друга на 180 градусов. т. е.

 acute + obtuse = PI.

http://www.mathworks.com/access/helpdesk/help/techdoc/ref/atan.html показывает, что atan асимптотичен при +/- pi/2.

таким образом, максимальная разница между двумя результатами atan-это pi или 180 град, используете ли вы +/- обозначение или положительный 0 to pi обозначение градиента.

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

acuteAngle(m1, m2){
  a = atan(m1) - atan(m2);

  // if obtuse get the complementary acute angle:
  if (a>PI/2) 
    a = PI - a;
  return a;
} 

функции acuteAngle иллюстрирует то, что вам нужно сделать, математически.

однако его нельзя использовать для значений углов в окрестности PI / 2, потому что двоичные сравнения углов с результатами в этой окрестности сомнительны, является ли тупой или острый угол представленный.

поэтому мы должны сравнить координаты точек двух линий. Узнаем, образовалась ли 3-я строка из [(x2,y2)(x3,y3)] короче, равно или длиннее гипотетической гипотенузы.

в силу теоремы Пифагора, Гипотенуза образуется, если угол равен точно PI / 2 или 90o. Назовем его гипотетическую гипотенузную линию L3Hypo.

геометрической визуализацией в вашем уме,

  • если 3-й линия длиннее L3Hypo, угол тупой.
  • если короче, угол острый.
  • в противном случае, идеальный 90.

таким образом,

L1.lengthSquared = sq(x2-x1) + sq(y2-y1)
L2.lengthSquared = sq(x3-x1) + sq(y3-y1)
L3Hypo.lengthSquared = L1.lengthSquared + L2.lengthSquared
L3.lengthSquared = sq(x3-x2) + sq(y3-y2)

поэтому следующий псевдо-код,

struct Point{
  double x, y;
}

// no need to struct, for clarity only
struct Line{
  double lengthSquared;
}

#define sq(n) (n*n)
int isObtuse(Point P1, P2, P3){
  Line L1, L2, L3, L3Hypo;

  L1.lengthSquared = sq(P2.x-P1.x) + sq(P2.y-P1.y);
  L2.lengthSquared = sq(P3.x-P1.x) + sq(P3.y-P1.y);
  L3Hypo.lengthSquared = L1.lengthSquared + L2.lengthSquared;
  L3.lengthSquared = sq(P3.x-P2.x) + sq(P3.y-P2.y);

  if (L3>L3Hypo) return 1; //obtuse
  else if (L3<L3Hypo) return -1; //acute
  else return 0;
}

предполагая, что у вас уже есть функция getGradient (точка P, Q):

double m1m2 = getGradient(P1,P2);
double m1m3 = getGradient(P1,P3);
double a = Abs(atan(m1m2) - atan(m1m3));
if (isObtuse(P1, P2, P3)>0)
  a = PI - a;

возможно, я совершил некоторые опечатки в псевдо-коде (надеюсь, нет), но я продемонстрировал суть концепции. Если так, кто-то может быть так добр, чтобы удалить опечатки.

дальше Однако, поразмыслив над этим, я обнаружил, что борьба за точность вращается на самом слабом звене из-за директивы

#define PI 3.14159blah..blah..blah.

Итак, мы могли бы также сэкономить все проблемы и просто сделать это:

double m1m2 = getGradient(P1,P2);
double m1m3 = getGradient(P1,P3);
double a = Abs(atan(m1m2) - atan(m1m3));
double b = PI - a;
return min(a, b);//the smaller of the two is the acute