3点之间的角度?

jma*_*erx 17 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;


}
Run Code Online (Sandbox Code Playgroud)

val*_*ldo 30

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

你叫什么ac其实是cb.但没关系,这才是真正需要的.下一个,

float dotabac = (ab.x * ab.y + ac.x * ac.y);
Run Code Online (Sandbox Code Playgroud)

这是你的第一个错误.两个向量的真正点积是:

float dotabac = (ab.x * ac.x + ab.y * ac.y);
Run Code Online (Sandbox Code Playgroud)

现在,

float rslt = acos(dacos);
Run Code Online (Sandbox Code Playgroud)

在这里你应该注意到,由于计算过程中的一些精度损失,理论上可能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;
Run Code Online (Sandbox Code Playgroud)

(我刚刚换成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);


}
Run Code Online (Sandbox Code Playgroud)

编辑:

最近我一直在研究一个相关的主题.然后我意识到有更好的方法.它实际上或多或少相同(在幕后).然而,它更直接恕我直言.

想法是旋转两个矢量,使第一个矢量与(正)X方向对齐.显然,旋转两个矢量不会影响它们之间的角度.在这样的旋转之后,只需要找出第二矢量相对于X轴的角度.而这正是atan2为了什么.

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

  • 斧头,唉
  • - 斧头

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

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

旋转的矢量b变为:

  • 斧*BX + AY*=由一个b
  • -ay*bx + ax*by = a cross 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);
}
Run Code Online (Sandbox Code Playgroud)

  • 1) 后来的`GetAngleABC()`使用了`float`变量和`double`常量的混合。仅建议一种类型。2) 不要使用“+0.5”技巧,这对于负“alpha”和其他一些值来说是错误的,而是使用“lrint(alpha * (float)(180.0 / pi) )” (2认同)

Raf*_*fAl 5

这是计算直角值的快速且正确的方法:

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;
} 
Run Code Online (Sandbox Code Playgroud)