Внутренний угол между двумя линиями - PullRequest
12 голосов
/ 01 июня 2010

У меня есть две строки: 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º, чтобы узнать внутренний угол? Есть какой-нибудь алгоритм лучше сделать это? Потому что я попробовал несколько методов: точечный продукт, следующая формула:

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

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

Ответы [ 8 ]

19 голосов
/ 01 июня 2010

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

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

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

РЕДАКТИРОВАТЬ: Вот полная реализация. Подставьте проблемные значения в 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;
}

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

<code>rad = 2.12667   deg = 121.849
rad = 0.0939257 deg = 5.38155
4 голосов
/ 01 июня 2010
if (result > 180)
{
     result = 360 - result;
}

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

3 голосов
/ 07 апреля 2015

Если вы хотите получить угол между 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;

}

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

1 голос
/ 04 февраля 2011

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

1 голос
/ 07 июля 2010

Все гораздо проще, чем данные ответы:

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

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

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

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

1 голос
/ 01 июня 2010

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

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

module (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;
}
0 голосов
/ 01 июня 2010

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

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

 acute + obtuse = PI.

http://www.mathworks.com/access/helpdesk/help/techdoc/ref/atan.html показывает, что атан асимптотичен при +/- пи / 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 или 90 град. Назовем его гипотетическую линию гипотенузы 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
0 голосов
/ 01 июня 2010

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

...