文章目录
一、SVG路径的A指令的语法说明
A rx ry x-axis-rotation large-arc-flag sweep-flag x y
a rx ry x-axis-rotation large-arc-flag sweep-flag x y
二、DXF中的圆弧和椭圆弧对象
2.1 圆弧对象
class DXFArc
{
public:
double cx; // 圆心X坐标
double cy; // 圆心Y坐标
double radius; // 圆弧半径
double bangle; // 起点角度
double eangle; // 终点角度
};
2.2 椭圆弧对象
class DXFEllipse
{
public:
double cx; // 椭圆心X坐标
double cy; // 椭圆心Y坐标
double mx; // 长轴端点相对于中点的X坐标
double my; // 长轴端点相对于中点的Y坐标
double ratio; // 短半轴长度➗长半轴长度
double angle1; // 起始弧度
double angle2; // 终止弧度
};
三、转DXF圆弧
3.1 数学公式
圆弧可以看作一个长半轴等于短半轴(即满足 a = b = r a=b=r a=b=r)的特殊椭圆弧,因此,下面统一用长半轴 a a a 代替半径 r r r
另外,圆弧的 α = 0 \alpha = 0 α=0
x 1 ′ = ( x s − x e ) / 2 y 1 ′ = ( y s − y e ) / 2 m = ± ∣ a 4 − a 2 y 1 ′ 2 − a 2 x 1 ′ 2 ∣ / ( a 2 ( y 1 ′ 2 + x 1 ′ 2 ) ) ( 仅当 l f = l s 时取 − , 其余时候取 + ) ⇓ c x ′ = m y 1 ′ c y ′ = − m x 1 ′ ⇓ c x = c x ′ + ( x s + x e ) / 2 c y = c y ′ + ( y s + y e ) / 2 x_1'=(x_s-x_e)/2\\ y_1'= (y_s-y_e)/2\\ m=±\sqrt{|a^4-a^2y_1'^2-a^2x_1'^2|/(a^2(y_1'^2+x_1'^2))} \quad (仅当l_f=l_s时取-,其余时候取+)\\ \Downarrow\\ c_x'=m y_1'\\ c_y'=-mx_1'\\ \Downarrow\\ c_x=c_x'+(x_s+x_e)/2\\ c_y=c_y'+(y_s+y_e)/2\\ x1′=(xs−xe)/2y1′=(ys−ye)/2m=±∣a4−a2y1′2−a2x1′2∣/(a2(y1′2+x1′2)) (仅当lf=ls时取−,其余时候取+)⇓cx′=my1′cy′=−mx1′⇓cx=cx′+(xs+xe)/2cy=cy′+(ys+ye)/2
根据圆的参数方程,可以通过代入点 ( x , y ) (x,y) (x,y),从而反推出该点的角度 t t t:
{ x = a ⋅ c o s ( t ) + c x y = a ⋅ s i n ( t ) + c y ⇓ t = ± a r c c o s ( ( x − c x ) / a ) \begin{cases} x=a · cos(t)+c_x \\ y=a · sin(t) + c_y \end{cases}\\ \Downarrow\\ t=±arccos((x-c_x)/a) {x=a⋅cos(t)+cxy=a⋅sin(t)+cy⇓t=±arccos((x−cx)/a)
其中, t t t 的符号和 a r c s i n ( ( y − c y ) / a ) arcsin((y-c_y)/a) arcsin((y−cy)/a) 的相同。
基于此,我们可以通过将圆弧的起点 ( x s , y s ) (x_s,y_s) (xs,ys) 和终点 ( x e , y e ) (x_e,y_e) (xe,ye) 分别代入圆的参数方程,反推出圆弧的起始和终止角度 ( b a n g l e , e a n g l e ) (bangle,eangle) (bangle,eangle)
3.2 代码实现
// 圆周率
#define PI acos(-1)
// 浮点型数据精度
#define ERROR 0.00000001
// 根据圆的参数方程和圆上一个点的坐标,计算该点的角度
// r:圆弧半径
// (cx,cy):圆弧的中心点
// (x,y):圆弧上某点的坐标
double calcArcPointAngle(double r, double cx, double cy, double x, double y){
double res = acos((x - cx)/r);
if (asin((y - cy)/r) < 0){
res = -res;
}
return res / PI * 180;
}
// 传入SVG的A指令的相关参数,返回对应的DXF中的圆弧对象
// a:圆弧半径
// lf:是否优(大)弧:0否,1是
// sf:绘制方向:0逆时针,1顺时针
// (startX,startY):圆弧的起点
// (endX,endY):圆弧的终点
DXFArc getDXFArcBySvg(double a,int lf,int sf,double startX,double startY,double endX,double endY){
DXFArc arc = DXFArc();
// 圆弧半径
arc.radius = a;
// 计算中点(cx,cy)
double x1Pie = 0.5 * (startX - endX);
double y1Pie = 0.5 * (startY - endY);
double m = sqrt(abs(a*a*a*a - a*a*y1Pie*y1Pie - b*b*x1Pie*x1Pie) / (a*a* (y1Pie*y1Pie+x1Pie*x1Pie)));
if (lf == sf){
m = -m;
}
double cxPie = m * y1Pie;
double cyPie = m * -x1Pie;
arc.cx = cxPie + 0.5 * (startX + endX);
arc.cy = cyPie + 0.5 * (startY + endY);
// 计算角度(bangle,eangle)
arc.bangle = calcArcPointAngle(a, arc.cx, arc.cy,startX,startY);
arc.eangle = calcArcPointAngle(a, arc.cx, arc.cy, endX, endY);
// 修正角度
while (arc.eangle < arc.bangle){
arc.eangle += 360.0;
}
if ((lf == 1 && arc.eangle - arc.bangle < 180.0) || (lf == 0 && arc.eangle - arc.bangle > 180.0)){
double temp = arc.bangle;
arc.bangle = arc.eangle;
arc.eangle = temp;
}
// 返回转化好的圆弧
return arc;
}
3.3 转换效果展示
四、转DXF椭圆弧
4.1 数学公式
x 1 ′ = c o s ( α ) ⋅ ( x s − x e ) / 2 + s i n ( α ) ⋅ ( y s − y e ) / 2 y 1 ′ = − s i n ( α ) ⋅ ( x s − x e ) / 2 + c o s ( α ) ⋅ ( y s − y e ) / 2 m = ± ∣ a 2 b 2 − a 2 y 1 ′ 2 − b 2 x 1 ′ 2 ∣ / ( a 2 y 1 ′ 2 + b 2 x 1 ′ 2 ) ( 仅当 l f = l s 时取 − , 其余时候取 + ) ⇓ c x ′ = m ( a y 1 ′ ) / b c y ′ = − m ( b x 1 ′ ) / a ⇓ c x = c o s ( α ) ⋅ c x ′ − s i n ( α ) ⋅ c y ′ + ( x s + x e ) / 2 c y = s i n ( α ) ⋅ c x ′ + c o s ( α ) ⋅ c y ′ + ( y s + y e ) / 2 x_1'=cos(\alpha)·(x_s-x_e)/2+sin(\alpha)·(y_s-y_e)/2\\ y_1'= -sin(\alpha)·(x_s-x_e)/2+cos(\alpha)·(y_s-y_e)/2\\ m=±\sqrt{|a^2b^2-a^2y_1'^2-b^2x_1'^2|/(a^2y_1'^2+b^2x_1'^2)} \quad (仅当l_f=l_s时取-,其余时候取+)\\ \Downarrow\\ c_x'=m(ay_1')/b\\ c_y'=-m(bx_1')/a\\ \Downarrow\\ c_x=cos(\alpha)·c_x'-sin(\alpha)·c_y'+(x_s+x_e)/2\\ c_y=sin(\alpha)·c_x'+cos(\alpha)·c_y'+(y_s+y_e)/2\\ x1′=cos(α)⋅(xs−xe)/2+sin(α)⋅(ys−ye)/2y1′=−sin(α)⋅(xs−xe)/2+cos(α)⋅(ys−ye)/2m=±∣a2b2−a2y1′2−b2x1′2∣/(a2y1′2+b2x1′2) (仅当lf=ls时取−,其余时候取+)⇓cx′=m(ay1′)/bcy′=−m(bx1′)/a⇓cx=cos(α)⋅cx′−sin(α)⋅cy′+(xs+xe)/2cy=sin(α)⋅cx′+cos(α)⋅cy′+(ys+ye)/2
m x = a ⋅ c o s ( α ) m y = ± a 2 − m x 2 ( 仅当 α < 0 时取 − ,其余时候取 + ) m_x=a·cos(\alpha)\\ m_y=±\sqrt{a^2-m_x^2} \quad (仅当\alpha<0时取-,其余时候取+)\\ mx=a⋅cos(α)my=±a2−mx2 (仅当α<0时取−,其余时候取+)
根据椭圆的参数方程,可以通过代入点 ( x , y ) (x,y) (x,y),从而反推出该点的角度 t t t:
{ x = a ⋅ c o s ( t ) ⋅ c o s ( α ) − b ⋅ s i n ( t ) ⋅ s i n ( α ) + c x y = a ⋅ c o s ( t ) ⋅ s i n ( α ) + b ⋅ s i n ( t ) ⋅ c o s ( α ) + c y ⇓ { c o s ( t ) = ( ( x − c x ) ⋅ c o s ( α ) + ( y − c y ) ⋅ s i n ( α ) ) / a s i n ( t ) = ( − ( x − c x ) ⋅ s i n ( α ) + ( y − c y ) ⋅ c o s ( α ) ) / b ⇓ t = ± a r c c o s ( ( ( x − c x ) ⋅ c o s ( α ) + ( y − c y ) ⋅ s i n ( α ) ) / a ) \begin{cases} x = a·cos(t)·cos(\alpha)-b·sin(t)·sin(\alpha)+c_x\\ y = a·cos(t)·sin(\alpha)+b·sin(t)·cos(\alpha)+c_y \end{cases}\\ \Downarrow\\ \begin{cases} cos(t) = ((x-c_x)·cos(\alpha)+(y-c_y)·sin(\alpha))/a\\ sin(t) = (-(x-c_x)·sin(\alpha)+(y-c_y)·cos(\alpha))/b\\ \end{cases}\\ \Downarrow\\ t=±arccos(((x-c_x)·cos(\alpha)+(y-c_y)·sin(\alpha))/a) {x=a⋅cos(t)⋅cos(α)−b⋅sin(t)⋅sin(α)+cxy=a⋅cos(t)⋅sin(α)+b⋅sin(t)⋅cos(α)+cy⇓{cos(t)=((x−cx)⋅cos(α)+(y−cy)⋅sin(α))/asin(t)=(−(x−cx)⋅sin(α)+(y−cy)⋅cos(α))/b⇓t=±arccos(((x−cx)⋅cos(α)+(y−cy)⋅sin(α))/a)
其中, t t t 的符号和 a r c s i n ( ( − ( x − c x ) ⋅ s i n ( α ) + ( y − c y ) ⋅ c o s ( α ) ) / b ) arcsin((-(x-c_x)·sin(\alpha)+(y-c_y)·cos(\alpha))/b) arcsin((−(x−cx)⋅sin(α)+(y−cy)⋅cos(α))/b) 的相同。
基于此,我们可以通过将圆弧的起点 ( x s , y s ) (x_s,y_s) (xs,ys) 和终点 ( x e , y e ) (x_e,y_e) (xe,ye) 分别代入椭圆的参数方程,反推出椭圆弧的起始和终止角度 ( a n g l e 1 , a n g l e 2 ) (angle1,angle2) (angle1,angle2)
4.2 代码实现
// 圆周率
#define PI acos(-1)
// 浮点型数据精度
#define ERROR 0.00000001
// 根据椭圆的参数方程和椭圆上一个点的坐标,计算该点的角度
// a:长轴半径
// b:短轴半径
// alpha:椭圆弧长半轴与x正半轴的夹角(弧度)
// (cx,cy):椭圆弧的中心点
// (x,y):椭圆弧上某点的坐标
double calcEllipsePointAngle(double a,double b,double alpha,double cx,double cy,double x,double y){
double c = ((x - cx)*cos(alpha) + (y - cy)*sin(alpha)) / a;
// 防止出现 acos(1.00000001)=-1.#IND的情况
if (abs(c - 1) <= ERROR){
c = 1;
}
// 防止出现 acos(-1.00000001)=-1.#IND的情况
if (abs(c + 1) <= ERROR){
c = -1;
}
double res = acos(c);
c = (-(x - cx)*sin(alpha) + (y - cy)*cos(alpha)) / b;
// 防止出现 asin(1.00000001)=-1.#IND的情况
if (abs(c - 1) <= ERROR){
c = 1;
}
// 防止出现 asin(-1.00000001)=-1.#IND的情况
if (abs(c + 1) <= ERROR){
c = -1;
}
if (asin(c) < 0){
res = -res;
}
return res / PI * 180;
}
// 传入SVG的A指令的相关参数,返回对应的DXF中的椭圆弧对象
// a:长轴半径
// b:短轴半径
// lf:是否优(大)弧:0否,1是
// sf:绘制方向:0逆时针,1顺时针
// alpha:椭圆弧长半轴与x正半轴的夹角(弧度)
// (startX,startY):椭圆弧的起点
// (endX,endY):椭圆弧的终点
DXFEllipse getDXFEllipseBySvg(double a,double b,int lf,int sf,double alpha,double startX,double startY,double endX,double endY){
DXFEllipse ellipse = DXFEllipse();
// 短半轴长度➗长半轴长度
ellipse.ratio = b / a;
// 计算中点(cx,cy)
double x1Pie = cos(alpha) * 0.5 * (startX - endX) + sin(alpha) * 0.5 * (startY - endY);
double y1Pie = -sin(alpha) * 0.5 * (startX - endX) + cos(alpha) * 0.5 * (startY - endY);
double m = sqrt(abs(a*a*b*b - a*a*y1Pie*y1Pie - b*b*x1Pie*x1Pie) / (a*a*y1Pie*y1Pie + b*b*x1Pie*x1Pie));
if (lf == sf){
m = -m;
}
double cxPie = m * ((a * y1Pie) / b);
double cyPie = -m * ((b * x1Pie) / a);
ellipse.cx = cos(alpha) * cxPie - sin(alpha) * cyPie + 0.5 * (startX + endX);
ellipse.cy = sin(alpha) * cxPie + cos(alpha) * cyPie + 0.5 * (startY + endY);
// 计算长轴端点相对中点的坐标(mx,my)
ellipse.mx = a * cos(alpha);
ellipse.my = sqrt(a*a - ellipse.mx*ellipse.mx);
if (alpha < 0){
ellipse.my = -ellipse.my;
}
// 计算角度(angle1,angle2)
ellipse.angle1 = calcEllipsePointAngle(a, b, alpha, ellipse.cx, ellipse.cy, startX, startY) / 180.0 * PI;
ellipse.angle2 = calcEllipsePointAngle(a, b, alpha, ellipse.cx, ellipse.cy, endX, endY) / 180.0 * PI;
// 修正角度
while (ellipse.angle2 < ellipse.angle1){
ellipse.angle2 += (2 * PI);
}
if ((lf == 1 && ellipse.angle2 - ellipse.angle1 < PI) || (lf == 0 && ellipse.angle2 - ellipse.angle1 > PI)){
double temp = ellipse.angle1;
ellipse.angle1 = ellipse.angle2;
ellipse.angle2 = temp;
}
// 返回转化好的椭圆弧
return ellipse;
}