关于你的方法,以下是建议:
你所称的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
函数。
最后,需要注意的是你的结果仅在符号上准确。也就是说,你的方法无法区分 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;
(我刚刚用 atan2
替换了 atan
。)
这是最简单的方法,总是得到正确的结果。这种方法的缺点是你实际上要调用一个复杂的三角函数 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 };
float dot = (ab.x * cb.x + ab.y * cb.y);
float abSqr = ab.x * ab.x + ab.y * ab.y;
float cbSqr = cb.x * cb.x + cb.y * cb.y;
float cosSqr = dot * dot / abSqr / cbSqr;
float cos2 = 2 * cosSqr - 1;
const float pi = 3.141592f;
float alpha2 =
(cos2 <= -1) ? pi :
(cos2 >= 1) ? 0 :
acosf(cos2);
float rslt = alpha2 / 2;
float rs = rslt * 180. / pi;
if (dot < 0)
rs = 180 - rs;
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 轴旋转。
注意: 严格来说,上述矩阵不仅仅是旋转,它也进行了缩放。但在我们的情况下这没关系,因为唯一重要的是向量方向,而不是其长度。
旋转后的向量 b
变为:
- a.x * b.x + a.y * b.y = a 点乘 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);
}