工作中经常画图经常会遇到圆弧,开始的时候我并不是很理解,随着深入发现其实有些坑的,这里总结记录一下,可能并不是最优的解法,但肯定是自己理解后想出来的,如果这些能给你提供一些思路和帮助,那便是极好的。
G02 X20 Y10 I0 J-10
G03 X10.5 I3.0 J4.0
G02/G03
指令,是已知起始点S
(Xs, Ys),终止点E
(Xe, Ye),相对圆心的增量(I, J),顺逆时针状态。通过这些信息我们是可以算出圆心的。
一般来说是(I, J)是圆弧起点相对圆心的增量,即圆心O
坐标是(Xs + I, Ys + J)。而我遇到的情况,(I, J)的表示可能有以下几种情况:
这个就很简单了,针对(I, J)表示不同时计算方式也不同
O x = X s + I ; O y = Y s + J ; O_x = X_s + I;\\ O_y = Y_s + J;\\ Ox?=Xs?+I;Oy?=Ys?+J;
O x = X e + I ; O y = Y e + J ; O_x = X_e + I;\\ O_y = Y_e + J;\\ Ox?=Xe?+I;Oy?=Ye?+J;
O x = I ; O y = J ; O_x = I;\\ O_y = J; Ox?=I;Oy?=J;
G02 X20 Y10 R10 F300
注意相同的起点、终点、半径和方向,计算出来的圆心可能有两种。其中,
R
>0时,圆弧和中心的夹角小于180°,即圆弧段小于或等于半圆;R
<0时,圆弧和中心的夹角大于180°,即圆弧段大于半圆。例如下图,G02
指令时,如果R
>0时,圆心是黑色圆的,如果R
<0,则应该是黄色的圆。
以下图片是我画的分析用的简易图,方便自己理解如何计算出圆心的方法。假设以下是G02圆弧,起点为C,终点为D,半径为R,加粗圆弧部分是实际路径。
假设存在上面的情况,已知起点C
(Xc, Yc), 终点D
(Xd, Yd),半径R
。
X a = ( X c + X d ) 2 Y a = ( Y c + Y d ) 2 (1) X_a = \frac{(X_c + X_d)}{2}\\ Y_a = \frac{(Y_c + Y_d)}{2} \tag{1} Xa?=2(Xc?+Xd?)?Ya?=2(Yc?+Yd?)?(1)
现在可以得到向量
A
C
?
\vec{AC}
AC的单位向量了
∣
A
C
?
∣
=
(
X
a
?
X
c
)
2
+
(
Y
a
?
Y
c
)
2
A
C
^
=
A
C
?
∣
A
C
?
∣
(2)
|\vec{AC}| = \sqrt{(X_a - X_c)^2 + (Y_a - Y_c)^2} \\ \hat{AC} = \frac{\vec{AC}}{|\vec{AC}|} \tag{2}
∣AC∣=(Xa??Xc?)2+(Ya??Yc?)2?AC^=∣AC∣AC?(2)
圆上两点连线的中垂线必过圆心,这是个知识点,然后根据直角三角关系,可以知道(AO)2 = R2 - (AC)2,下面用向量的方式表示,后面会用到。
∣ A O ? ∣ = R 2 ? ∣ A C ? ∣ 2 (3) |\vec{AO}| = \sqrt{R^2 - |\vec{AC}|^2} \tag{3} ∣AO∣=R2?∣AC∣2?(3)
A O ? = A O ^ ? ? ∣ A O ? ∣ (4) \vec{AO} = \hat{AO}\ * |\vec{AO}|\tag{4} AO=AO^??∣AO∣(4)
R模式实际算法省略,因为经验有限,我并没有遇到过这个情况,当前仅讨论这个情况下计算出圆心的理论算法。
这个地方,我感觉相对是比较麻烦的,因为必须要考虑圆弧方向,角度的值域范围,并不能简单粗暴的 终点角度 ? 起点角度 终点角度- 起点角度 终点角度?起点角度。
#define PI 3.1415926535897932384626433
#define EPS 0.000001
//!< 基础点
struct Point
{
double x;
double y;
};
typedef Point Vector;
//!< 计算角度,值域[-π, π],这个很重要
double getAngle(const Point& p1, const Point& p2)
{
return atan2f((p2.y - p1.y), (p2.x - p1.x));
}
//!< 计算两向量夹角
float calc2VecAngle(const Vector& v1, const Vector& v2, const int& isCw)
{
//!< atan2 计算得到的弧度范围是[-PI, PI],可以转换成[0, 2 * PI]来计算
float startAngNew = atan2f(v1.y, v1.x);
float startAngNew2 = atan2f(v2.y, v2.x);
if (startAngNew < EPS)
startAngNew += 2 * PI;
if (startAngNew2 < EPS)
startAngNew2 += 2 * PI;
float sweepNew = abs(startAngNew - startAngNew2);
if (isCw != 0)
{
//!< 两向量之间的夹角,需要根据顺/逆时针,和所处角度象限判断
if (startAngNew < startAngNew2 && isCw > EPS ||
startAngNew > startAngNew2 && isCw < EPS)
{
sweepNew = abs(2 * PI - abs(sweepNew));
}
}
return sweepNew;
}
//!< 已知圆弧,圆心,起点,终点,方向,计算出圆弧夹角(弧度制)
//!<
void calcArcAngle(Point center, Point s, Point e, bool isClockWise, double& angle)
{
//!< 起点终点相同的情况,就是个整圆
if (s == e)
{
angle = PI * 2;
}
else
{
Vector v1, v2;
v1.x = start.x - center.x;
v1.y = start.y - center.y;
v2.x = end.x - center.x;
v2.y = end.y - center.y;
angle = calc2VecAngle(v1, v2, (cw ? 1 : -1));
}
}
感谢各位大佬的无私奉献。