鉴于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;
}
答案 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轴旋转。
注意:严格来说,上面的矩阵不只是旋转,它也是缩放。但是在我们的情况下这是可以的,因为唯一重要的是矢量方向,而不是它的长度。
旋转的向量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
,将dx
和dy
值传递给它。
答案 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的优秀解决方案