3点之间的角度?

时间:2010-08-15 04:18:22

标签: c++ c algorithm trigonometry

鉴于ABC点,我怎么能找到角度ABC?我正在为矢量绘图应用程序制作一个手工工具,并尽量减少它产生的点数,除非鼠标位置和最后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;


}

7 个答案:

答案 0 :(得分:28)

关于您的方法的第一个建议:

您呼叫的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'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方向对齐。显然,旋转两个矢量不会影响它们之间的角度。在这样的旋转之后,只需要找出第二矢量相对于X轴的角度。这正是atan2的用途。

通过将矢量乘以下面的矩阵来实现旋转:

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

一旦看到向量a乘以这样的矩阵确实向正X轴旋转。

注意:严格来说,上面的矩阵不只是旋转,它也是缩放。但是在我们的情况下这是可以的,因为唯一重要的是矢量方向,而不是它的长度。

旋转的向量b变为:

  • a.x * b.x + a.y * b.y = a dot 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);
}

答案 1 :(得分:4)

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

其中a是角度α相反的一侧,b是角度β,c是角度γ。所以β就是你所说的角度ABC。

答案 2 :(得分:3)

使用arccos的方法很危险,因为我们冒险让它的参数等于,例如1.0000001,最终会出现EDOMAIN错误。即使atan方法也很危险,因为它涉及分裂,这可能导致零误差除法。更好地使用atan2,将dxdy值传递给它。

答案 3 :(得分:2)

以下是计算直角值的快速而正确的方法:

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

答案 4 :(得分:1)

关闭话题?但你可以用余弦定律来做到这一点:

找到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))= ANGLE

答案 5 :(得分:1)

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;

答案 6 :(得分:0)

这是一个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的优秀解决方案