圆弧总结
说明
工作中经常画图经常会遇到圆弧,开始的时候我并不是很理解,随着深入发现其实有些坑的,这里总结记录一下,可能并不是最优的解法,但肯定是自己理解后想出来的,如果这些能给你提供一些思路和帮助,那便是极好的。
G-Code的G02/G03
G02 顺时针圆弧/G03 逆时针圆弧
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/e76993ce764d46e98a9a48b0691acf39.png#pic_center)
圆心的计算
I, J, K模式
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;
R模式
G02 X20 Y10 R10 F300
注意相同的起点、终点、半径和方向,计算出来的圆心可能有两种。其中,
R>0时,圆弧和中心的夹角小于180°,即圆弧段小于或等于半圆;R0时,圆心是黑色圆的,如果R
//!< atan2 计算得到的弧度范围是[-PI, PI],可以转换成[0, 2 * PI]来计算
float startAngNew = atan2f(v1.y, v1.x);
float startAngNew2 = atan2f(v2.y, v2.x);
if (startAngNew
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));
}
}
在圆弧上
上面的方法中,可以已知圆弧圆心,圆弧起点,圆弧终点,方向,是可以计算判断出某个点在不在圆弧上。
//!< 两点的距离
double getLength(const Point& p1, const Point& p2)
{
return sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
}
//!< 计算角度,需要用到atan2f值域[-π, π],这个很重要
//!< 可以转换成[0, 2π]来计算
double getAngle(const Point& p1, const Point& p2)
{
double ang = atan2f((p2.y - p1.y), (p2.x - p1.x)); // [-π, π]
if (ang
//!< 如果圆是个整圆,任一角度都在圆弧上
if (start == end)
{
return true;
}
//[0, 2π]
double startAng = getAngle(center, start);
double endAng = getAngle(center, end);
double curAng = angle;
bool bIsOn = false;
//!< 顺时针
if (cw)
{
//!< 圆弧起始角度 > 终止角度的情况
if (startAng > endAng)
{
//!< curAng 满足区间 [endAng, startAng]
if (curAng >= endAng && curAng
//!< 顺时针时 起始角度 < 终止角度则说明圆弧经过了0°, 则此时可以
//!< 在两个区间判断角度, 满足任一区间即可, [endAng, 2π], [0, startAng]
if ((curAng >= endAng && curAng = eps && curAng
//!< 圆弧起始角度 < 终止角度的情况
if (startAng
bIsOn = true;
}
}
//!< 圆弧起始角度 > 终止角度
else
{
//!< 逆时针时 起始角度 > 终止角度则说明圆弧经过了0°, 则此时可以
//!< 在两个区间判断角度, 满足任一区间即可, [startAng, 2π], [0, endAng]
if ((curAng >= startAng && curAng = eps && curAng
double radius = getLength(center, start);
double len = getLength(center, pt);
//!< 半径不匹配肯定是不在圆上的
if (abs(len - radius) > eps)
return false;
double curAng = getAngle(m_center, pt);
return isOn(center, start, end, cw, curAng, eps);
}
圆弧外接矩形计算
不带角度
下图中黑色圆弧为实际圆弧,黑色点为圆的四个顶点位置,绿色为需要计算的圆弧外接矩形,这个时候求圆弧外接矩形是分两种情况的:
小于90° ![小于90°的圆弧](https://img-blog.csdnimg.cn/direct/f0b48605c10948db9589ee90ce8a86c2.png#pic_center) 大于等于90° ![大于90°的圆弧](https://img-blog.csdnimg.cn/direct/a53b4ee84c314ada94a628e4ae81bedd.png#pic_center)
计算
从圆的上下左右四个顶点中,找出在圆弧上的顶点计算出已知点集的外接矩形
#define MAX_NUM 1E20
#define MIN_NUM -1E20
//!< 外接矩形
struct rect
{
float left;
float top;
float right;
float bottom;
};
//!< 计算圆弧的外接矩形
rect calcRectangle(const Point& center, const Point& start, const Point& end, const bool& cw)
{
rect rc;
double radius = getLength(center, start);
//!< 如果是个整圆
if (start == end)
{
rc.left = center.x - radius;
rc.right = center.x + radius;
rc.top = center.y + radius;
rc.bottom = center.y - radius;
}
else
{
std::vector vArcVertex;
vArcVertex.push_back(start);
vArcVertex.push_back(end);
//!< 判断圆的四个顶点是不是在圆弧上,在圆弧上的顶点参与计算外接矩形
sPoint ptCirList[] = { center + Point(0, radius),
center + Point(radius, 0),
center + Point(-radius, 0),
center + Point(0, -radius) };
for (int i = 0; i
vArcVertex.push_back(ptCirList[i]);
}
}
rc.right = rc.top = MIN_NUM;
rc.left = rc.bottom = MAX_NUM;
for (auto& pt : vArcVertex)
{
rc.left = min(rc.left, pt.x);
rc.right = max(rc.right, pt.x);
rc.top = max(rc.top, pt.y);
rc.bottom = min(rc.bottom, pt.y);
}
}
return rc;
}
圆弧相对距离的点
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/43517ce659034946a5a6a30cab1273b7.png#pic_center)
//!< 根据某点旋转
void rotate(Point& src, const Point& center, const float& sweepAng)
{
float xOrg = src.x, yOrg = src.y;
src.x = center.x + (xOrg - center.x) * cos(angle) - (yOrg - center.y) * sin(angle);
src.y = center.y + (xOrg - center.x) * sin(angle) + (yOrg - center.y) * cos(angle);
}
//!< 圆弧上以起点位置,计算相对偏移distance的点
Point getSatrtOffsetOnArc(const Point& center, const Point& start, const Point& end, const bool& cw, const float& distance)
{
//!< 计算圆弧半径
double radius = getLength(center, start);
//!< 圆弧长度对应圆心角
float sweepAng = distance / radius;
Point curPoint = start;
if (cw)
sweepAng = -sweepAng;
rotate(curPoint, center, sweepAng);
return curPoint;
}
圆弧与线段相交
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/cc426361d23f4a28b367f6ee34e3bea8.png#pic_center)
计算
判断圆心到线段的最短距离是不是小于半径,大于半径的时候肯定是不会与圆弧相交的求出圆弧与直线的交点,最多两个,需要满足既在圆弧上,也在线段上
//!< 判断点p是否在线段(s, e)上
bool isOnLineSeg(const Point& s, const Point& e, const Point& p, const float& eps)
{
float c = getLength(s, e);
float a = getLength(s, p);
float b = getLength(e, p);
//!< 构成三角形,运用定理两边之和大于第三边,等于第三边则构成线段,点p在线段上
if (abs(a + b - c)
float t1 = (-b + sqrt(discriminant)) / (2 * a);
float t2 = (-b - sqrt(discriminant)) / (2 * a);
Point intersection1 = { x1 + t1 * dx, y1 + t1 * dy };
Point intersection2 = { x1 + t2 * dx, y1 + t2 * dy };
Point intersectionArray[] = {intersection1, intersection2};
int resultSize = 2;
//!< 相切的情况, 只有一个解
if (abs(t1 - t2)
if (isOn(center, start, end, cw, intersectionArray[i], eps) &&
isOnLineSeg(s, e, intersectionArray[i], eps))
{
vCrossPoint.push(intersection1);
}
}
}
return vCrossPoint;
}
线段外与圆相切的问题
需要做的效果
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/1b6db902bcc242b8b824ea9643db1e6d.gif#pic_center)
已知
已知线段起点终点鼠标位置需要构建一个相切与该线段的圆,圆弧终点为鼠标当前点,起点为线段端点(就认为是起点吧)
计算
鼠标位置与线段端点连线组成线段AB,提前可知圆上任意两点连线即(AB)的中垂线必过圆心,所以AB的中垂线必过圆心因为原线段需要与所求的圆弧相切,即原线段端点的垂线也必过该圆心以上所得两直线的交点即为圆心
//!< 计算线段的中垂线
void getMidPerpendicularLine(const Point& s1, const Point& e1, Point& s2, Point& e2, const float& eps)
{
Point midPoint = (s1 + e1) / 2.0f;
float len = getLength(s1, e1) / 2.0f;
if (abs(s1.x - e1.x)
s2.x = midPoint.x;
s2.y = midPoint.y + len;
e2.x = midPoint.x;
e2.y = midPoint.y - len;
}
else if (abs(s1.y - e1.y)
//!< 沿着中点随便旋转90°都可以组成中垂线
s2 = s1;
rotate(s2, midPoint, PI / 2);
e2 = e1;
rotate(e2, midPoint, PI / 2);
}
}
//!< 两直线求交点
bool getLineCross(const Point& p1, const Point& p2, const Point& p3, const Point& p4, sPoint& p, const float& eps)
{
//!< 构建直线L1
int sign = 1;
double a1 = p2.y - p1.y;
if (a1
sign = -1;
a2 = sign * a2;
}
double b2 = sign * (p3.x - p4.x);
double c2 = sign * (p3.y * p4.x - p3.x * p4.y);
//!< 求L1和L2的交点
double d = a1 * b2 - a2 * b1;
if (abs(d)
center = pArcCenter;
arcStart = s;
arcEnd = mousePos;
}
}
实现效果
下面的效果中,我是已知轮廓的方向的,所以做出了镜像的圆弧
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/2fb1544c99604531bff02fa40d6e61dc.gif#pic_center)
圆弧分割成多线段
一些时候画圆弧+线段连起来的时候,一些API并并不能完美的画出来,起点是对的,终点会有些偏差,可能缩小的时候看不出来差别,放大到了节点处会出现断开的情况。一些做法画圆弧,不用画圆弧的函数来做,把圆弧分成很多个线段,连起来。这样起点和终点可以保证正确。
//!< 圆弧分割成多线段集
void vector toPoints(const Point& center, const Point& start, const Point& end, const bool& cw, const int& ptCnts)
{
std::vector vPts;
// 起点,圆心,终点
float radius = getLength(center, start);
float startAngle = getAngle(center, start);
float sweepAngle = 0.0f;
calcArcAngle(center, start, end, cw, sweepAngle)
sPoint ptCur = start;
vPts.push_back(ptCur);
for (int i = 1; i |