给定点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; }
有关您的方法的初步建议:
你所说ac的实际上是cb。没关系,这是真正需要的。下一个,
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)。因此-您应该明确检查。
dacos
加上性能说明:您调用了一个重sqrt函数两次来计算两个向量的长度。然后,将点积除以这些长度。相反,您可以调用sqrt两个向量的长度平方的乘积。
sqrt
最后,您应该注意的是,结果精确到sign。也就是说,您的方法无法区分20°和-20°,因为两者的余弦相同。您的方法将为ABC和CBA产生相同的角度。
sign
一种正确的角度计算方法是“ 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)。
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方向对齐。显然,旋转两个向量都不会影响它们之间的角度。在这样旋转之后,OTOH只需找出第二矢量相对于X轴的角度即可。而这正是atan2目的。
通过将向量乘以以下矩阵来实现旋转:
曾经可以看到,a乘以这样的矩阵的向量确实向正X轴旋转。
a
注意: 严格来说,上述矩阵不仅在旋转,而且还在扩展。但这在我们的情况下是可以的,因为唯一重要的是矢量方向,而不是它的长度。
旋转向量b变为:
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); }