一、前言
最近做項(xiàng)目,需要判斷一條線段是否和一段圓弧相交,網(wǎng)上也沒找到很好的解答(最主要是沒有直接可以搬來用的代碼,或者思路寫得太過高深,我看不懂),于是決定自己想一個(gè)方法,寫一個(gè)博客,將實(shí)現(xiàn)思路和完整代碼都分享出來
二、線段與圓弧的代碼表示
2.1 線段代碼表示
線段可用兩個(gè)點(diǎn)表示,點(diǎn)的對象如下所示,包含x和y坐標(biāo)信息:
class Point {
public:
Point(double px=0.0, double py=0.0) {
x = px;
y = py;
}
double x;
double y;
};
2.2 圓弧代碼表示
圓弧由圓心坐標(biāo)、半徑、起始和終止角度組成:
class Arc
{
public:
Point centerpoint; // 圓心
double radius; // 圓弧半徑
double bangle; // 起點(diǎn)角度
double eangle; // 終點(diǎn)角度
};
三、實(shí)現(xiàn)思路及數(shù)學(xué)推導(dǎo)
3.1 第一步(粗略判斷)
第一步(粗略判斷):將線段當(dāng)成直線,將圓弧當(dāng)成圓,如果直線和圓不相交,則線段和圓弧必然不相交,否則進(jìn)行下一步判斷
首先,將線段擴(kuò)展成一條直線,它的方程為: y = k x + c y=kx+c y=kx+c
根據(jù)線段的兩個(gè)點(diǎn)(假設(shè)為 p 1 p_1 p1? 和 p 2 p_2 p2?,且 p 1 . x ≤ p 2 . x p_1.x\le p_2.x p1?.x≤p2?.x)信息,我們可以很輕易求出 k k k 和 c c c 的取值
將 p 1 p_1 p1? 和 p 2 p_2 p2? 代入直線方程:
{ p 1 . y = k p 1 . x + c ?? ????????????? ( 1 ) p 2 . y = k p 2 . x + c ?? ????????????? ( 2 ) \begin{cases} p_1.y=kp_1.x+c\,\, \ \ \ \ \ \ \ \ \ \ \ \ \ \left( 1 \right)\\ p_2.y=kp_2.x+c\,\, \ \ \ \ \ \ \ \ \ \ \ \ \ \left( 2 \right) \end{cases} {p1?.y=kp1?.x+c?????????????(1)p2?.y=kp2?.x+c?????????????(2)?
( 1 ) ? ( 2 ) (1)-(2) (1)?(2) 可得:
p 1 . y ? p 2 . y = ( p 1 . x ? p 2 . x ) k ? k = p 1 . y ? p 2 . y p 1 . x ? p 2 . x ????????????? ( 3 ) p_1.y-p_2.y=\left( p_1.x-p_2.x \right) k \\ \Rightarrow k=\frac{p_1.y-p_2.y}{p_1.x-p_2.x} \ \ \ \ \ \ \ \ \ \ \ \ \ \left( 3 \right) p1?.y?p2?.y=(p1?.x?p2?.x)k?k=p1?.x?p2?.xp1?.y?p2?.y??????????????(3)
將 ( 3 ) (3) (3) 代入 ( 1 ) (1) (1) 可得:
p 1 . y = p 1 . y ? p 2 . y p 1 . x ? p 2 . x p 1 . x + c ? c = p 1 . y ? p 1 . y ? p 2 . y p 1 . x ? p 2 . x p 1 . x ????????????? ( 4 ) p_1.y=\frac{p_1.y-p_2.y}{p_1.x-p_2.x}p_1.x+c \\ \Rightarrow c=p_1.y-\frac{p_1.y-p_2.y}{p_1.x-p_2.x}p_1.x \ \ \ \ \ \ \ \ \ \ \ \ \ \left( 4 \right) p1?.y=p1?.x?p2?.xp1?.y?p2?.y?p1?.x+c?c=p1?.y?p1?.x?p2?.xp1?.y?p2?.y?p1?.x?????????????(4)
假設(shè)圓心坐標(biāo)為 ( a , b ) (a,b) (a,b) ,半徑為 r r r ,容易寫出圓弧擴(kuò)展而成的圓的方程如下所示:
( x ? a ) 2 + ( y ? b ) 2 = r 2 ????????????? ( 5 ) (x-a)^2+(y-b)^2=r^2 \ \ \ \ \ \ \ \ \ \ \ \ \ \left( 5 \right) (x?a)2+(y?b)2=r2?????????????(5)
要判斷直線和圓是否相交,需要將直線方程和圓方程進(jìn)行聯(lián)立得:
{ y = k x + c ( x ? a ) 2 + ( y ? b ) 2 = r 2 ? ( x ? a ) 2 + ( k x + c ? b ) 2 = r 2 , 令 d = c ? b ? x 2 + a 2 ? 2 a x + k 2 x 2 + d 2 + 2 k d x = r 2 ? ( 1 + k 2 ) x 2 + ( 2 k d ? 2 a ) x + a 2 + d 2 ? r 2 = 0 ????????????? ( 6 ) \begin{cases} y=kx+c \\ (x-a)^2+(y-b)^2=r^2 \\ \end{cases} \\ \Downarrow \\ (x-a)^2+(kx+c-b)^2=r^2,令d=c-b \\ \Downarrow \\ x^2+a^2-2ax+k^2x^2+d^2+2kdx=r^2 \\ \Downarrow \\ (1+k^2)x^2+(2kd-2a)x+a^2+d^2-r^2=0 \ \ \ \ \ \ \ \ \ \ \ \ \ \left( 6 \right) {y=kx+c(x?a)2+(y?b)2=r2??(x?a)2+(kx+c?b)2=r2,令d=c?b?x2+a2?2ax+k2x2+d2+2kdx=r2?(1+k2)x2+(2kd?2a)x+a2+d2?r2=0?????????????(6)
根據(jù)韋達(dá)定理判斷一元二次方程 ( 6 ) (6) (6) 是否存在實(shí)數(shù)根:
Δ = ( 2 k d ? 2 a ) 2 ? 4 ( 1 + k 2 ) ( a 2 + d 2 ? r 2 ) ? { Δ < 0 : 式 ( 6 ) 不存在實(shí)數(shù)根 Δ ≥ 0 : 式 ( 6 ) 存在實(shí)數(shù)根 \varDelta=(2kd-2a)^2-4(1+k^2)(a^2+d^2-r^2) \Rightarrow \begin{cases} \varDelta<0: 式 (6) 不存在實(shí)數(shù)根 \\ \varDelta\ge0:式 (6) 存在實(shí)數(shù)根 \end{cases} Δ=(2kd?2a)2?4(1+k2)(a2+d2?r2)?{Δ<0:式(6)不存在實(shí)數(shù)根Δ≥0:式(6)存在實(shí)數(shù)根?
如果式 ( 6 ) (6) (6) 不存在實(shí)數(shù)根,意味著直線和圓沒有交點(diǎn),此時(shí)線段和圓弧必然也沒有交點(diǎn),程序結(jié)束。
如果式 ( 6 ) (6) (6) 存在實(shí)數(shù)根,則可以解出直線與圓的兩個(gè)交點(diǎn)的 X X X 方向坐標(biāo) x 1 x_1 x1? 和 x 2 x_2 x2?:
x 1 = ? ( 2 k d ? 2 a ) + Δ 2 ( 1 + k 2 ) ?和? x 2 = ? ( 2 k d ? 2 a ) ? Δ 2 ( 1 + k 2 ) x_1=\frac{-(2kd-2a)+\sqrt{\varDelta}}{2(1+k^2)} \ 和 \ x_2=\frac{-(2kd-2a)-\sqrt{\varDelta}}{2(1+k^2)} x1?=2(1+k2)?(2kd?2a)+Δ???和?x2?=2(1+k2)?(2kd?2a)?Δ??
將 x 1 x_1 x1? 和 x 2 x_2 x2? 分別代入直線方程 y = k x + c y=kx+c y=kx+c 可得兩個(gè)交點(diǎn)的 Y Y Y 方向坐標(biāo) y 1 y_1 y1? 和 y 2 y_2 y2?:
y 1 = k x 1 + c ?和? y 2 = k x 2 + c y_1=kx_1+c \ 和\ y_2=kx_2+c y1?=kx1?+c?和?y2?=kx2?+c
需要注意的是:上面我們令直線方程為 y = k x + c y=kx+c y=kx+c,但當(dāng)直線垂直時(shí), k k k 其實(shí)是不存在的,上面的公式也就不再適用了。幸運(yùn)的是,在這種情況下,直線方程會(huì)變得更加簡單,即 x = c x=c x=c, c c c 為一個(gè)常數(shù)。要求這個(gè)直線與圓的交點(diǎn),只需要將 x = c x=c x=c 代入圓的方程中即可,如下所示:
{ x = c ( x ? a ) 2 + ( y ? b ) 2 = r 2 ? ( c ? a ) 2 + ( y ? b ) 2 = r 2 ? ( y ? b ) 2 = r 2 ? ( c ? a ) 2 \begin{cases} x=c \\ (x-a)^2+(y-b)^2=r^2 \\ \end{cases} \\ \Downarrow \\ (c-a)^2+(y-b)^2=r^2\\ \Downarrow \\ (y-b)^2 = r^2 - (c-a)^2\\ {x=c(x?a)2+(y?b)2=r2??(c?a)2+(y?b)2=r2?(y?b)2=r2?(c?a)2
顯然,當(dāng)且僅當(dāng) r 2 ? ( c ? a ) 2 ≥ 0 r^2 - (c-a)^2\ge0 r2?(c?a)2≥0 時(shí),直線與圓存在交點(diǎn),且交點(diǎn)的橫坐標(biāo)相同,即 x 1 = x 2 = c x_1=x_2=c x1?=x2?=c;縱坐標(biāo)分別為:
y 1 = b + r 2 ? ( c ? a ) 2 ?和? y 2 = b ? r 2 ? ( c ? a ) 2 y_1=b+\sqrt{r^2 - (c-a)^2} \ 和\ y_2=b-\sqrt{r^2 - (c-a)^2} y1?=b+r2?(c?a)2??和?y2?=b?r2?(c?a)2?
如果直線和圓存在交點(diǎn),則進(jìn)行下一步判斷
3.2 第二步
第二步:判斷直線和圓的兩個(gè)交點(diǎn)是否在線段上,如果不在,說明線段和圓弧必然不相交,否則進(jìn)行下一步判斷
線段是連續(xù)的,所以可以通過區(qū)間判斷兩個(gè)交點(diǎn)是否在線段上
詳細(xì)地說,我們已經(jīng)知道線段的X軸方向的區(qū)間為 [ p 1 . x ? , ? p 2 . x ] [p_1.x\ ,\ p_2.x] [p1?.x?,?p2?.x]
如果 x 1 x_1 x1? 不在 [ p 1 . x ? , ? p 2 . x ] [p_1.x\ ,\ p_2.x] [p1?.x?,?p2?.x] 區(qū)間內(nèi),則點(diǎn) ( x 1 , y 1 ) (x_1,y_1) (x1?,y1?) 不在線段上,也就不可能是線段和圓弧的交點(diǎn)
同理,如果 x 2 x_2 x2? 不在 [ p 1 . x ? , ? p 2 . x ] [p_1.x\ ,\ p_2.x] [p1?.x?,?p2?.x] 區(qū)間內(nèi),則點(diǎn) ( x 2 , y 2 ) (x_2,y_2) (x2?,y2?) 也不可能是線段和圓弧的交點(diǎn)
如果點(diǎn) ( x 1 , y 1 ) (x_1,y_1) (x1?,y1?) 和點(diǎn) ( x 2 , y 2 ) (x_2,y_2) (x2?,y2?) 都不是線段和圓弧的交點(diǎn),則說明線段和圓弧必然不相交
否則進(jìn)行下一步判斷
3.3 第三步
第三步:根據(jù)前面的推導(dǎo),假設(shè)已知直線和圓的一個(gè)在線段上的交點(diǎn)為 ( x 1 , y 1 ) (x_1,y_1) (x1?,y1?),在這一步中,需要判斷該點(diǎn)是否在圓弧上,如果在,則說明線段和圓弧相交,且交點(diǎn)為 ( x 1 , y 1 ) (x_1,y_1) (x1?,y1?)
在這一步中,需要使用圓的參數(shù)方程,為了方便表示,假設(shè)圓弧的起始角度和終止角度分別為 θ 1 \theta_1 θ1? 和 θ 2 \theta_2 θ2?,則圓弧的參數(shù)方程為:
{ x = a + r c o s θ ???? ( 7 ) y = b + r s i n θ ???? ( 8 ) ???? , θ 1 ≤ θ ≤ θ 2 \begin{cases} x=a+rcos\theta \ \ \ \ \left( 7 \right) \\ y=b+rsin\theta \ \ \ \ \left( 8 \right) \\ \end{cases} \ \ \ \ ,\theta_1\le\theta\le\theta_2 {x=a+rcosθ????(7)y=b+rsinθ????(8)?????,θ1?≤θ≤θ2?
將 ( x 1 , y 1 ) (x_1,y_1) (x1?,y1?) 代入式 ( 7 ) (7) (7) 和式 ( 8 ) (8) (8) 中:
{ x 1 = a + r c o s θ y 1 = b + r s i n θ ???? , θ 1 ≤ θ ≤ θ 2 ? { x 1 ? a = r c o s θ ???? ( 9 ) y 1 ? b = r s i n θ ???? ( 10 ) ???? , θ 1 ≤ θ ≤ θ 2 \begin{cases} x_1=a+rcos\theta \\ y_1=b+rsin\theta \\ \end{cases} \ \ \ \ ,\theta_1\le\theta\le\theta_2 \\ \Downarrow \\ \begin{cases} x_1-a=rcos\theta \ \ \ \ \left( 9 \right) \\ y_1-b=rsin\theta \ \ \ \ \left( 10 \right) \\ \end{cases} \ \ \ \ ,\theta_1\le\theta\le\theta_2 {x1?=a+rcosθy1?=b+rsinθ?????,θ1?≤θ≤θ2??{x1??a=rcosθ????(9)y1??b=rsinθ????(10)?????,θ1?≤θ≤θ2?
( 10 ) (10) (10)? ( 9 ) (9) (9) 可得:
y 1 ? b x 1 ? a = t a n θ ? θ = a r c t a n ( y 1 ? b x 1 ? a ) \frac{y_1-b}{x_1-a}=tan\theta \\ \Downarrow \\ \theta = arctan(\frac{y_1-b}{x_1-a}) x1??ay1??b?=tanθ?θ=arctan(x1??ay1??b?)
如果解出的 θ \theta θ 不在區(qū)間 [ θ 1 , θ 2 ] [\theta_1,\theta_2] [θ1?,θ2?] 內(nèi),則說明點(diǎn) ( x 1 , y 1 ) (x_1,y_1) (x1?,y1?) 不在圓弧上
否則,點(diǎn) ( x 1 , y 1 ) (x_1,y_1) (x1?,y1?) 在圓弧上,并且是線段和圓弧的一個(gè)交點(diǎn)
至此,證畢!
注意:使用 a t a n atan atan 函數(shù)計(jì)算反正切值時(shí),返回值的取值范圍是 [ ? π 2 , π 2 ] [-\frac{\pi}{2},\frac{\pi}{2}] [?2π?,2π?],而 θ \theta θ 的取值是 [ 0 , 2 π ] [0,2\pi] [0,2π],所以在實(shí)際代碼中需要對角度進(jìn)行轉(zhuǎn)換
四、完整代碼
// 圓周率
#define PI acos(-1)
// lineIsIntersectArc 的輔助函數(shù):接著第二步判斷(這個(gè)函數(shù)可能存在浮點(diǎn)數(shù)誤差問題,導(dǎo)致判斷結(jié)果有偏差)
bool lineIsIntersectArcAuxiliaryFunction(const DL_VertexData& p1, const DL_VertexData& p2, double a, double b, double theta1, double theta2, double x1, double y1) {
// 2.3 判斷交點(diǎn)是否在線段上
if (p1.x <= x1 && x1 <= p2.x) {
// 第三步:交點(diǎn)(x1,y1)在線段上,再判斷該點(diǎn)是否在圓弧上,如果在,則說明線段和圓弧相交,且交點(diǎn)為(x1,y1)
double theta = atan((y1 - b) / (x1 - a)) * 180.0 / PI;
if (theta1 > theta2) {
if (theta2 >= 0) {
theta1 -= 360;
}
else {
theta2 += 360;
}
}
if (theta1 < 0 && theta2 < 0) {
theta1 += 360;
theta2 += 360;
}
// 修正 tan 的角度
if (x1 < a) {
theta += 180;
}
if (theta < 0) {
theta += 360;
}
// 判斷是否在邊界,在邊界其實(shí)就不算相交
if (abs(theta1 - theta) <= 1e-12 || abs(theta2 - theta) <= 1e-12) {
return false;
}
// 判斷 theta 是否在圓弧范圍內(nèi),如果在則(x1,y1)是線段和圓弧的交點(diǎn),否則不是
return theta1 < theta && theta < theta2;
}
return false;
}
// 判斷一個(gè)線段是否和圓弧相交
bool lineIsIntersectArc(Point p1, Point p2, Arc arc){
// 確保 p1.x < p2.x
if (p1.x > p2.x) {
DL_VertexData temp = p1;
p1 = p2;
p2 = temp;
}
// 簡化圓弧的相關(guān)變量表示
double a = arc.centerpoint.x;
double b = arc.centerpoint.y;
double r = arc.radius;
double theta1 = arc.bangle;
double theta2 = arc.eangle;
// 開始判斷
if (abs(p1.x - p2.x) < 1e-12) {
// 特殊處理 p1.x = p2.x 的情況
double c = p1.x;
double temp = r * r - (c - a) * (c - a);
if (temp >= -1e-12) {
// 第二步:判斷直線和圓的兩個(gè)交點(diǎn)是否在線段上,如果不在,說明線段和圓弧必然不相交,否則進(jìn)行下一步判斷
// 2.1 計(jì)算兩個(gè)交點(diǎn)的坐標(biāo)(x1,y1)和(x2,y2)
double x1 = c;
double x2 = c;
double y1 = b + sqrt(temp);
double y2 = b - sqrt(temp);
// 2.2 判斷兩個(gè)交點(diǎn)是否相等3
if (abs(y1 - y2) < 1e-12) {
// 兩個(gè)交點(diǎn)相等,只需要對其中任意一個(gè)點(diǎn)進(jìn)行判斷即可
return lineIsIntersectArcAuxiliaryFunction(p1, p2, a, b, theta1, theta2, x1, y1);
}
else {
// 兩個(gè)交點(diǎn)不相等,分別進(jìn)行判斷,只要其中一個(gè)是線段和圓弧的交點(diǎn)就返回 true
return lineIsIntersectArcAuxiliaryFunction(p1, p2, a, b, theta1, theta2, x1, y1) || lineIsIntersectArcAuxiliaryFunction(p1, p2, a, b, theta1, theta2, x2, y2);
}
}
}
else {
// 第一步(粗略判斷):將線段當(dāng)成直線,將圓弧當(dāng)成圓,如果直線和圓不相交,則線段和圓弧必然不相交,否則進(jìn)行下一步判斷
// 1.1 根據(jù)公式,計(jì)算直線方程 y=kx+c 中的 k 和 c
double k = (p1.y - p2.y) / (p1.x - p2.x);
double c = p1.y - (p1.y - p2.y) / (p1.x - p2.x) * p1.x;
// 1.2 根據(jù)韋達(dá)定理判斷式(6)是否存在實(shí)數(shù)根
double d = c - b;
double varDelta = pow(2 * k * d - 2 * a, 2) - 4 * (1 + k * k) * (a * a + d * d - r * r);
if (varDelta >= -1e-12) {
// 第二步:判斷直線和圓的兩個(gè)交點(diǎn)是否在線段上,如果不在,說明線段和圓弧必然不相交,否則進(jìn)行下一步判斷
// 2.1 計(jì)算兩個(gè)交點(diǎn)的坐標(biāo)(x1,y1)和(x2,y2)
double x1 = (2 * a - 2 * k * d + sqrt(varDelta)) / (2 * (1 + k * k));
double x2 = (2 * a - 2 * k * d - sqrt(varDelta)) / (2 * (1 + k * k));
double y1 = k * x1 + c;
double y2 = k * x2 + c;
// 2.2 判斷兩個(gè)交點(diǎn)是否相等
if (varDelta < 1e-12) {
// 兩個(gè)交點(diǎn)相等,只需要對其中任意一個(gè)點(diǎn)進(jìn)行判斷即可
return lineIsIntersectArcAuxiliaryFunction(p1, p2, a, b, theta1, theta2, x1, y1);
}
else {
// 兩個(gè)交點(diǎn)不相等,分別進(jìn)行判斷,只要其中一個(gè)是線段和圓弧的交點(diǎn)就返回 true
return lineIsIntersectArcAuxiliaryFunction(p1, p2, a, b, theta1, theta2, x1, y1) || lineIsIntersectArcAuxiliaryFunction(p1, p2, a, b, theta1, theta2, x2, y2);
}
}
}
return false;
}
五、效果展示
有了判斷一條線段和一段圓弧是否相交的函數(shù)之后,就可以用來判斷圓弧是否和一個(gè)多邊形相交了。最簡單的思路就是用圓弧和多邊形的每個(gè)邊依次做判斷,如果多邊形的任意一條邊和圓弧都不相交,則圓弧與多邊形必然不相交。
交叉判斷前,從下圖可以看到,黃色部分就是圓弧和多邊形出現(xiàn)了交叉重疊的情況
交叉判斷后,圓弧與多邊形的交叉情況完全消失~文章來源:http://www.zghlxwxcb.cn/news/detail-429840.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-429840.html
到了這里,關(guān)于【計(jì)算幾何】判斷一條線段和一段圓弧是否相交 & C++代碼實(shí)現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!