工作中经常画图经常会遇到圆弧,开始的时候我并不是很理解,随着深入发现其实有些坑的,这里总结记录一下,可能并不是最优的解法,但肯定是自己理解后想出来的,如果这些能给你提供一些思路和帮助,那便是极好的。
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)2AC^=∣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;
//!< 计算两向量夹角
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));
}
}
上面的方法中,可以已知圆弧圆心,圆弧起点,圆弧终点,方向,是可以计算判断出某个点在不在圆弧上。
//!< 两点的距离
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 < EPS) {
// [-π, 0]
ang += PI * 2.0f;
}
return ang;
}
//!< angle的取值限制为圆上的任意角度
bool isOn(const Point& center, const Point& start, const Point& end, const bool& cw, const double& angle, const double& eps)
{
//!< 如果圆是个整圆,任一角度都在圆弧上
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 <= startAng)
{
bIsOn = true;
}
}
//!< 圆弧起始角度 < 终止角度
else
{
//!< 顺时针时 起始角度 < 终止角度则说明圆弧经过了0°, 则此时可以
//!< 在两个区间判断角度, 满足任一区间即可, [endAng, 2π], [0, startAng]
if ((curAng >= endAng && curAng <= 2*PI) ||
(curAng >= eps && curAng <= startAng)
{
bIsOn = true;
}
}
}
//!< 逆时针
else
{
//!< 圆弧起始角度 < 终止角度的情况
if (startAng < endAng)
{
//!< curAng的值域在[startAng, endAng]
if (curAng >= startAng && curAng <= endAng)
{
bIsOn = true;
}
}
//!< 圆弧起始角度 > 终止角度
else
{
//!< 逆时针时 起始角度 > 终止角度则说明圆弧经过了0°, 则此时可以
//!< 在两个区间判断角度, 满足任一区间即可, [startAng, 2π], [0, endAng]
if ((curAng >= startAng && curAng <= 2*PI) ||
(curAng >= eps && curAng <= endAng)
{
bIsOn = true;
}
}
}
return bIsOn;
}
//!< 判断点是不是在圆弧上
bool isOn(const Point& center, const Point& start, const Point& end, const bool& cw, const Point& curPt, const double& eps)
{
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°
#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<sPoint> 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 < 4; i++)
{
if (isOn(center, start, end, cw, ptCirList[i], EPS))
{
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;
}
//!< 根据某点旋转
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;
}
//!< 判断点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) < eps)
return true;
else
return false;
}
//!< 求出圆弧与线段相交的交点
vector<Point> getArcLineSegCross(const Point& center, const Point& start, const Point& end, const bool& cw, const Point& s, const Point& e, const float& eps)
{
float x1 = s.x;
float y1 = s.y;
float dx = e.x - s.x;
float dy = e.y - s.y;
float cx = center.x;
float cy = center.y;
float r = getLength(center, start);
//!< 圆弧与直线相交,求得两交点的公式
float a = dx * dx + dy * dy;
float b = 2 * (dx * (x1 - cx) + dy * (y1 - cy));
float c = (x1 - cx) * (x1 - cx) + (y1 - cy) * (y1 - cy) - r * r;
vector<Point> vCrossPoint;
float discriminant = b * b - 4 * a * c;
if (discriminant > eps)
{
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) < eps)
{
resultSize = 1;
}
for (int i = 0; i < resultSize; i++)
{
if (isOn(center, start, end, cw, intersectionArray[i], eps) &&
isOnLineSeg(s, e, intersectionArray[i], eps))
{
vCrossPoint.push(intersection1);
}
}
}
return vCrossPoint;
}
//!< 计算线段的中垂线
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) < eps && abs(s1.y - e1.y) < eps)
{
//!< 同一个点, 不做任何处理
}
else if (abs(s1.x - e1.x) < eps)
{
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) < eps)
{
s2.x = midPoint.x + len;
s2.y = midPoint.y;
e2.x = midPoint.x - len;
e2.y = midPoint.y;
}
else
{
//!< 沿着中点随便旋转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 < eps)
{
sign = -1;
a1 = sign * a1;
}
double b1 = sign * (p1.x - p2.x);
double c1 = sign * (p1.y * p2.x - p1.x * p2.y);
//!< 构建直线L2
sign = 1;
double a2 = p4.y - p3.y;
if (a2 < eps)
{
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) < eps) // 不相交
return false;
p.x = (c2 * b1 - c1 * b2) / d;
p.y = (a2 * c1 - a1 * c2) / d;
return true;
}
//!< 计算相切的圆弧, 假设以线段起点为圆弧起点,鼠标位置为圆弧终点来计算圆弧
//!< 这里需要注意的是,圆弧的方向,需要提前自己确定
void calcTangentArc(const Point& s, const Point& e, const Point& mousePos, Point& center, Point& arcStart, Point& arcEnd, const float& eps)
{
//!< 过线段起点的垂线s, p1
Point p1;
rotate(e, s, PI / 2);
//!< 线段的中垂线pArc1, pArc2
Point pArc1, pArc2;
getMidPerpendicularLine(s, mousePos, pArc1, pArc2, EPS);
Point pArcCenter;
//!< 如果有交点,算出圆心
if (getLineCross(s, p1, pArc1, pArc3, pArcCenter, EPS))
{
center = pArcCenter;
arcStart = s;
arcEnd = mousePos;
}
}
下面的效果中,我是已知轮廓的方向的,所以做出了镜像的圆弧
一些时候画圆弧+线段连起来的时候,一些API并并不能完美的画出来,起点是对的,终点会有些偏差,可能缩小的时候看不出来差别,放大到了节点处会出现断开的情况。一些做法画圆弧,不用画圆弧的函数来做,把圆弧分成很多个线段,连起来。这样起点和终点可以保证正确。
//!< 圆弧分割成多线段集
void vector<Point> toPoints(const Point& center, const Point& start, const Point& end, const bool& cw, const int& ptCnts)
{
std::vector<sPoint> 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 < ptCnts; i++)
{
ptCur.x = center.x + radius * cos(startAngle + (cw ? -1 : 1) * (sweepAngle * i / count));
ptCur.y = center.y + radius * sin(startAngle + (cw ? -1 : 1) * (sweepAngle * i / count));
vPts.push_back(ptCur);
}
ptCur = end;
vPts.push_back(ptCur);
return vPts;
}
感谢各位大佬的无私奉献。
文章浏览阅读1.1w次,点赞7次,收藏34次。vue-grid-layout的使用、实例、遇到的问题和解决方案_vue-grid-layout
文章浏览阅读218次。然后连接一个数据源,就会在下面自动产生一个添加附件的组件。把这个控件复制粘贴到页面里,就可以单独使用来上传了。插入一个“编辑”窗体。_powerapps点击按钮上传附件
文章浏览阅读264次。(1) Abstraction (抽象)(2) Polymorphism (多态)(3) Inheritance (继承)(4) Encapsulation (封装)_"object(cnofd[\"ofdrender\"])十条"
文章浏览阅读133次。删除node_modules,重新npm install看是否成功。在 package.json 文件中的 scripts 中加入。修改你的第三方库的bug等。然后目录会多出一个目录文件。_修改 node_modules
文章浏览阅读883次。【代码】【】kali--password:su的 Authentication failure问题,&sudo passwd root输入密码时Sorry, try again._password: su: authentication failure
文章浏览阅读1w次,点赞13次,收藏97次。整理5个优秀的微信小程序开源项目。收集了微信小程序开发过程中会使用到的资料、问题以及第三方组件库。_微信小程序开源模板
文章浏览阅读128次。Centos7最简搭建NFS服务器_centos7 搭建nfs server
文章浏览阅读1.2k次,点赞2次,收藏3次。前言mybatis在持久层框架中还是比较火的,一般项目都是基于ssm。虽然mybatis可以直接在xml中通过SQL语句操作数据库,很是灵活。但正其操作都要通过SQL语句进行,就必须写大量的xml文件,很是麻烦。mybatis-plus就很好的解决了这个问题。..._mybaitis-plus ruledataobjectattributemapper' and 'com.picc.rule.management.d
文章浏览阅读325次。EECE 1080C / Programming for ECESummer 2022Laboratory 4: Global Functions PracticePlagiarism will not be tolerated:Topics covered:function creation and call statements (emphasis on global functions)Objective:To practice program development b_eece1080c
文章浏览阅读53次。被同机房早就1年前就学过的东西我现在才学,wtcl。设要求的数为\(x\)。设当前处理到第\(k\)个同余式,设\(M = LCM ^ {k - 1} _ {i - 1}\) ,前\(k - 1\)个的通解就是\(x + i * M\)。那么其实第\(k\)个来说,其实就是求一个\(y\)使得\(x + y * M ≡ a_k(mod b_k)\)转化一下就是\(y * M ...
文章浏览阅读1.3k次。首先,问题是如何出现的?晚上复查代码,发现一个activity没有调用自己的ondestroy方法我表示非常的费解,于是我检查了下代码。发现再finish代码之后接了如下代码finish();System.exit(0);//这就是罪魁祸首为什么这样写会出现问题System.exit(0);////看一下函数的原型public static void exit (int code)//Added ..._android 手动杀死app,activity不执行ondestroy
文章浏览阅读894次。Q: SylixOS 版权是什么形式, 是否分为<开发版税>和<运行时版税>.A: SylixOS 是开源并免费的操作系统, 支持 BSD/GPL 协议(GPL 版本暂未确定). 没有任何的运行时版税. 您可以用她来做任何 您喜欢做的项目. 也可以修改 SylixOS 的源代码, 不需要支付任何费用. 当然笔者希望您可以将使用 SylixOS 开发的项目 (不需要开源)或对 SylixOS 源码的修改及时告知笔者.需要指出: SylixOS 本身仅是笔者用来提升自己水平而开发的_select函数 导致堆栈溢出 sylixos