一尘不染

3点之间的角度?

algorithm

给定点ABC,我如何找到角度ABC?我正在为矢量绘图应用程序制作一个收费工具,并尽量减少其生成的点数,除非鼠标位置和最后两个点的角度大于某个阈值,否则我不会添加点数。谢谢

我所拥有的:

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;


}

阅读 289

收藏
2020-07-28

共1个答案

一尘不染

有关您的方法的初步建议:

你所说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;

(我刚刚更换atanatan2)。

这是最简单的方法,总是可以产生正确的结果。这种方法的缺点是您实际上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只需找出第二矢量相对于X轴的角度即可。而这正是atan2目的。

通过将向量乘以以下矩阵来实现旋转:

  • 斧头
  • -好,斧头

曾经可以看到,a乘以这样的矩阵的向量确实向正X轴旋转。

注意: 严格来说,上述矩阵不仅在旋转,而且还在扩展。但这在我们的情况下是可以的,因为唯一重要的是矢量方向,而不是它的长度。

旋转向量b变为:

  • BX + AY * =由 一个b*
  • -ay * BX +斧 =由 一个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);
}
2020-07-28