【问题标题】:Inner angle between two lines两条线之间的内角
【发布时间】:2010-05-31 22:50:22
【问题描述】:

我有两条线: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));

但我总是遇到同样的问题;我不知道我什么时候有外角或内角!

【问题讨论】:

  • 你怎么知道你什么时候有正确的角度;即您如何定义内角?我想这会让你找到答案。
  • 我想要一个定义在 0º 和 180º 之间的角度;如果我画锐角,我想要一个介于 0º 和 90º 之间的角度。问题是,如果一条线的斜率为 0(平行于横坐标),我可以做到这一点,但当一条线有其他正斜率或负斜率时,我不能(或者我不知道怎么做):-(

标签: c++ math trigonometry lines angle


【解决方案1】:

我认为您正在寻找的是两个角度的inner product(您可能还想查看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;
}

上面的代码产生:

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

【讨论】:

  • 您好,感谢您的回复,但我遇到了同样的问题,当我计算从左到右(水平)的一条线与另一条从右到左的线(从quad1 到 quad3),在论文中这条线有一个锐角(小于 90 度),算法显示我的角度为 150 度或更多度,实际上我的角度为 30 度。对于其他情况,效果很好。非常感谢您的时间!奥斯卡。
  • @ocell:您只需添加float final_theta = theta &gt; 90degrees ? 180degrees - theta : theta 即可获得所需的结果。
  • @ocell:(x1, y1)、(x2, y2) 和 (x3, y3) 的实际值是多少,可以证明您所描述的行为?
  • @ocell:好的,我更新了代码以反映您给出的观点,结果与您所指出的大致相同。您确定在实现中使用点 p1 = (x1, y1) 作为顶点吗?
  • @所有参与回答的人:感谢您的时间并帮助我更好地理解平面角度的测量;我对一个方向感到非常困惑,最后我看到了可以帮助我的论文发展的不同选择。再次感谢everyb@dy 并特别感谢andand 最终提供了一个很棒的代码。再见!奥斯卡。
【解决方案2】:
if (result > 180)
{
     result = 360 - result;
}

这样它就永远是内角。得到结果后添加即可。

【讨论】:

  • 谢谢 thyrgle,但我不想要大于 180º 的角度。如果我想知道只知道三个点的三角形的一个角度,我也想知道。
  • 我不认为我理解你...外角总是大于 180 或等于它(在这种情况下外角 = 内角)所以你不能考虑到 180 是 360 的一半,使用这种方法获得大于 180 的角度...
  • 换句话说,你不能用这个得到大于 180 的角度。
  • 可以理解,相交的锐角和钝角是 180 度的互补角。即锐角 + 钝角 = PI。 mathworks.com/access/helpdesk/help/techdoc/ref/atan.html 表明 atan 在 +/- PI/2 或 90 度处是渐近的,我们都知道。因为 atan 的最大绝对结果是 90 度或 PI/2,所以两个 atan 结果之间的最大差异是 180 度或 PI。所以没有什么可以帮助我们处理 360 度阈值。所以投票给你的两个人也是错误的。
  • 此外,以弧度表示的测试 [if(result>PI/4)] 的算术精度是任何已知的超级计算机都无法解决的问题,因为 PI/4 不是精确/正确的分数。在 90 度附近,二进制表示的精度是锐的还是钝的都值得怀疑。
【解决方案3】:

如果您想要介于 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;

}

注意:顺时针旋转;

【讨论】:

  • 考虑向量v1=(-0.5,-0.866025,0)v2=(-0.5,0.866025)。两者之间的内角为60度。这个公式给出 240。问题显然是关于内角,而不是顺时针角。
【解决方案4】:

整点比给出的答案要容易得多:

当您使用 atan(slope) 时,您会丢失(字面意思)一点信息,即在 (0..2*PI) 范围内恰好有两个角度 (theta) 和 (theta+PI),它们给出函数 tan() 的值相同。

只需使用 atan2(deltax, deltay) 即可获得正确的角度。比如

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

然后减去,取绝对值,如果大于PI则减去2*PI。

【讨论】:

    【解决方案5】:

    两个向量之间的内角 (v1, v2) = arc cos ( 内积(v1,v2) / (module(v1) * module(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;
    }
    

    【讨论】:

    • 计算m1和m2的目的是什么? OP 正在寻找的答案是角度,您既不使用 m1 也不使用 m2 来计算它。
    • 使用 pow 作为二的幂?哎哟。它应该用于分数幂。
    • m1 和 m2 只是为了说明程序,显示每条线的各个角度。我使用 pow 只是因为读者更容易理解公式。我不是想在这里提高性能。我只是想帮助那个人理解数学公式。
    【解决方案6】:

    如果你使用绝对值,你总是会得到锐角。即 m1-m2 的切线 theta = abs 值超过 (1 +m1 * m2)。如果你取反正切,你的答案将以弧度或度为单位,但计算器已设置。对不起,这不是编程术语,我是数学老师,不是程序员……

    【讨论】:

      【解决方案7】:

      获得外角与内角完全取决于减法的顺序(考虑一下)。您需要从较大的 theta 中减去较小的 theta,以便始终可靠地获得内角。由于您期望的数据类型,您可能还想使用 atan2 函数。

      【讨论】:

      • 假设较小的 theta 是 10 度,较大的 theta 是 -10 度(170 度)。 170 - 10 = 160。160 几乎不是锐角。而它的补码 20 度就是答案。
      【解决方案8】:

      我希望我正确理解你的问题,因为我想要锐角而不是两条线相交的钝角。我说的对吗?

      相交的锐角和钝角是 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 邻域中的角度值,因为角度的二进制比较与该邻域中的结果是否表示钝角或锐角是有问题的。

      因此,我们必须比较两条线的点的坐标。 我们找出由[(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(Point 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
      

      【讨论】:

      • @h2g2java:OP 要求提供由 3 个点定义的两条线的内角(两条线共有一个点 (x1, y1))。在某些情况下,这个内角是锐角,在其他情况下是钝角。
      猜你喜欢
      • 2019-07-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多