最近同事想让要做一个绘图的控件。VC里面的画弧函数Arc需要提供外接矩形的坐标。同事觉得不好用,他更习惯圆弧插补的那种方式。于是看了看圆弧插补的东西。其实这种画弧方式就是提供圆弧的起点、终点和半径来画弧。
首先来简单介绍下圆弧插补:
有两种圆弧插补:
G02 顺时针圆弧插补
G03 逆时针圆弧插补
圆弧插补编程(半径编程):
圆弧用编程功能G02 或G03 和其后圆弧终点坐标和半径值定义。
圆弧半径用字母“R”表示。如果圆弧小于180 度,半径用正数符号,如果大于180 度用负数符号。这样基于所选圆弧插补(G02 或G03),可定义所选圆弧。
结合圆弧插补,设计绘制圆弧的函数:函数可分为两种,顺时针绘制和逆时针绘制(分布对应G02 和G03)。函数的参数为圆弧起点,终点,半径。其中的半径若为正数,则绘制的圆弧为弧度小于180 的弧,这里称为小圆弧。若半径为负数,则绘制的弧为大雨180度的弧,这里成之为大圆弧。
圆弧的绘制最终还是要使用C++ 提供的画弧函数Arc 。 因此我们需要找出来圆所在的外接矩形(这里是正方形)。因为我们已知半径,所以找到圆心就可以推导出圆所在的矩形。
圆心的推导过程参考文章 已知圆上两点坐标和半径,求圆心 已知两点坐标和半径,求圆心 。圆心解出来有两个(x01,y01)(x02,y02)。如图所示,过相同的点并且半径相同的圆也确实有两个。那么到底哪一个是符合条件的圆呢。
首先来讨论逆时针画弧的函数。如上图,从起点A到终点B,小圆弧就指的红色部分的弧,大圆弧是指的蓝色部分的弧。小圆弧的圆心是O2,大圆弧的圆心是O1;
那么由什么条件能判断出所得的两个圆心(x01,y01)(x02,y02)哪一个是逆时针里的大圆弧的圆心O1,哪一个是逆时针里的小圆弧圆心O2呢? 这里我采用的是向量叉乘的方式判断的。
也就是起点到终点组成的向量,与起点与大弧圆心组成的向量叉乘结果是小于0 的。(这个从图上使用右手法则可以判断出来,由AB 向AO1 弯曲,拇指垂直屏幕向里)。
(有关向量知识参考 C语言-向量基本概念 向量叉乘判断点的位置)。
所以在上一步所得的两个圆心坐标,与起点坐标组成向量。
设A(x1,y1) B(x2,y2)
向量AB={x2-x1,y2-y1}
向量a={x01-x1,y01-y1}
向量b={x02-x1,y02-y1}
若
则(x01,y01)为大圆弧圆心 (x02,y02)为小圆弧圆心
否则 反之。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | //已知圆弧上两点 和半径,求圆心 void CircleCenter( double x1, double y1, double x2, double y2, double R, double &x01, double &y01, double &x02, double &y02) { //x1 == x2 if ( abs (x1-x2)<0.0000001) { //(x1,y1)(x2,y2)之间的距离 /2 double dis = abs (y1-y2)/2; double dx = sqrt (R*R-dis*dis); double dy = (y1+y2)/2; x01 = x1-dx; y01 = dy; x02 = x1+dx; y02 = dy; return ; } double c1 = (x2*x2 - x1*x1 + y2*y2 - y1*y1) / (2 *(x2 - x1)); double c2 = (y2 - y1) / (x2 - x1); //斜率 double A = (c2*c2 + 1); double B = (2 * x1*c2 - 2 * c1*c2 - 2 * y1); double C = x1*x1 - 2 * x1*c1 + c1*c1 + y1*y1 - R*R; y01 = (-B + sqrt (B*B - 4 * A*C)) / (2 * A); x01 = c1 - c2 * y01; y02 = (-B - sqrt (B*B - 4 * A*C)) / (2 * A); x02 = c1 - c2*y02; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | //逆时针画弧 void CDrawShapeCtrl::Arc_AntiClock(DOUBLE StartX, DOUBLE StartY, DOUBLE EndX, DOUBLE EndY, DOUBLE R) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); // TODO: Add your dispatch handler code here //圆心坐标 double x01,y01,x02,y02; double x_big,y_big; //大弧圆心 double x_small,y_small; //小弧圆心 LONG nLeftRect, nTopRect,nRightRect,nBottomRect; CircleCenter(StartX,StartY,EndX,EndY,R,x01,y01,x02,y02); //向量 double ax = EndX- StartX; double ay = EndY - StartY; double bx = x01 - StartX; double by = y01 - StartY; //利用向量的叉乘判断圆心位置 //叉乘<0 则为大弧圆心;否则为小弧圆心 double mulRt = ax*by-bx*ay; if (mulRt<0) { x_big = x01; y_big = y01; x_small = x02; y_small = y02; } else { x_big = x02; y_big = y02; x_small = x01; y_small = y01; } CClientDC dc( this ); CRect rc; GetClientRect(rc); dc.SetMapMode(MM_ISOTROPIC); //MM_ISOTROPIC //逻辑坐标原点 dc.SetViewportOrg(rc.right/2,rc.bottom/2); //设置映射比例为1,逻辑坐标Y轴方向与设备坐标相反 dc.SetWindowExt(100,100); dc.SetViewportExt(100,-100); //R>0 弧<180度; R<0 弧>180度 if (R<0) //大弧 { nLeftRect = x_big-R; nTopRect = y_big + R; nRightRect = x_big+R; nBottomRect = y_big -R; dc.Arc(nLeftRect,nTopRect,nRightRect,nBottomRect,StartX,StartY,EndX,EndY); } else //小弧 { nLeftRect = x_small-R; nTopRect = y_small+R; nRightRect = x_small+R; nBottomRect = y_small - R; dc.Arc(nLeftRect,nTopRect,nRightRect,nBottomRect,StartX,StartY,EndX,EndY); } } |
顺时针函数,只要将起点终点坐标对换,直接调用逆时针函数即可。
arc 函数参考:
https://blog.csdn.net/u012513234/article/details/45460783
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)