記事ディレクトリ
I.はじめに
最近、あるプロジェクトに取り組み、線分が円弧と交差するかどうかを判断する必要がありましたが、インターネット上で良い答えが見つかりませんでした(最も重要なことは、直接使用できるコードがないことです。アイデアが深すぎて理解できません)そこで、方法を考えてブログを書き、実装アイデアと完全なコードを共有することにしました
2. 線分と円弧のコード表現
2.1 線分コード表現
線分は 2 つの点で表現できます。点オブジェクトは次のとおりで、x 座標と y 座標の情報が含まれています。
class Point {
public:
Point(double px=0.0, double py=0.0) {
x = px;
y = py;
}
double x;
double y;
};
2.2 アークコード表現
円弧は中心座標、半径、開始角度と終了角度で構成されます。
class Arc
{
public:
Point centerpoint; // 圆心
double radius; // 圆弧半径
double bangle; // 起点角度
double eangle; // 终点角度
};
3. 実装アイデアと数学的導出
3.1 最初のステップ(大まかな判断)
第一ステップ(大雑把な判断):線分を直線、円弧を円とみなして、線分が円と交わらない場合は、線分と円弧は交わってはいけない、そうでない場合は次へ判断の段階
まず、線分を次の方程式の直線に拡張します: y = kx + cy=kx+cy=k ×+c
線分の 2 点に従って ( p 1 p_1としましょう)p1そしてp2p_2p2、そしてp 1 。x ≤ p 2 。x p_1.x\le p_2.xp1。バツ≤p2.x ) 情報、kk を簡単に見つけることができます。kとcccの値
p1p_1 _p1そしてp2p_2p2直線の方程式に代入します。
{p1. y = kp 1 。x + c (1) p 2 。y = kp 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{件}{ p1。y=kp _1。バツ+c ( 1 )p2。y=kp _2。バツ+c ( 2 )
( 1 ) − ( 2 ) (1)-(2)( 1 )−( 2 )利用可能:
p1.y − p 2 。y = ( p 1 . x − p 2 . x ) k ⇒ k = p 1 . y − p 2 。yp1 。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。バツ−p2。× )k⇒k=p1。バツ−p2。バツp1。y−p2。はい ( 3 )
意志( 3 ) (3)( 3 )を( 1 ) (1)( 1 )利用可能:
p1.y = p 1 。y − p 2 。yp1 。x − p 2 。XP 1 。x + c ⇒ c = p 1 。y − p 1 。y − p 2 。yp1 。x − p 2 。XP 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。バツ−p2。バツp1。y−p2。はいp1。バツ+c⇒c=p1。y−p1。バツ−p2。バツp1。y−p2。はいp1。バツ ( 4 )
円の中心の座標が( a , b ) (a,b)であるとします。( 、_b )半径rrrの場合、円弧の延長によって形成される円の方程式を次のように書くのは簡単です。
( x − a ) 2 + ( y − b ) 2 = r 2 ( 5 ) (xa)^2+(yb)^2=r^2 \ \ \ \ \ \ \ \ \ \ \ \ \left( 5 \右)( ×−)_2+( y−b )2=r2 ( 5 )
直線と円が交差するかどうかを判断するには、直線の方程式と円の方程式を組み合わせて次を得る必要があります。
{ y = kx + c ( x − a ) 2 + ( y − b ) 2 = r 2 ⇓ ( x − a ) 2 + ( kx + c − b ) 2 = r 2 , 令 d = c − b ⇓ x 2 + a 2 − 2 ax + k 2 x 2 + d 2 + 2 kdx = r 2 ⇓ ( 1 + k 2 ) x 2 + ( 2 kd − 2 a ) x + a 2 + d 2 − r 2 = 0 ( 6 ) \begin{cases} y=kx+c \\ (xa)^2+(yb)^2=r^2 \\ \end{cases} \\ \Downarrow \\ (xa)^2+( kx+cb)^2=r^2,令d=cb \\ \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 \右){ y=k ×+c( ×−)_2+( y−b )2=r2⇓( ×−)_2+( k x+c−b )2=r2、注文する=c−b⇓バツ2+ある2−2 × _+k2倍_2+d2+2kdx _ _ _=r2⇓( 1+k2 )×2+( 2kd _ _−2a ) x _+ある2+d2−r2=0 ( 6 )
ヴェーダの定理による一変数での二次方程式の判定( 6 ) (6)( 6 )本当の根があるかどうか:
Δ = ( 2 kd − 2 a ) 2 − 4 ( 1 + k 2 ) ( a 2 + d 2 − r 2 ) ⇒ { Δ < 0 : 式 ( 6 ) 実根は存在しない Δ ≥ 0 : 式 ( 6 ) ) 実根があります \varDelta=(2kd-2a)^2-4(1+k^2)(a^2+d^2-r^2) \Rightarrow \begin{cases} \varDelta<0: 式(6 ) は実数を持ちません root\\ \varDelta\ge0: 式 (6) は実数を持ちます root\end{cases}D=( 2kd _ _−2a ) _2−4 ( 1+k2 )(_2+d2−r2 )⇒{ D<0:方程式( 6 )には実根がありませんD≥0:方程式( 6 )には実根
式(6) (6)の場合( 6 )実数根がないということは、直線と円の交点がないということですが、このとき、線分と円弧の交点は存在しないはずで、プログラムは終了します。
式(6) (6)の場合( 6 )実根があれば、××X方向座標x 1 x_1バツ1そしてx 2 x_2バツ2:
x 1 = − ( 2 kd − 2 a ) + Δ 2 ( 1 + k 2 ) および x 2 = − ( 2 kd − 2 a ) − Δ 2 ( 1 + k 2 ) x_1=\frac{-(2kd- 2a)+\sqrt{\varDelta}}{2(1+k^2)}\and \x_2=\frac{-(2kd-2a)-\sqrt{\varDelta}}{2(1+k^2) )}バツ1=2 ( 1+k2 )− ( 2 k d−2a ) _+D そして× 2=2 ( 1+k2 )− ( 2 k d−2a ) _−D
x 1 x_1を入れるバツ1そしてx 2 x_2バツ2直線方程式y = kx + cy=kx+cに代入します。y=k ×+cは2つの交点のYYをY方向座標y 1 y_1y1和y 2 y_2y2:
y 1 = kx 1 + c 和 y 2 = kx 2 + c y_1=kx_1+c \ 和\ y_2=kx_2+cy1=k ×1+cとy 2=k ×2+c
その後、次の判断ステップに進みます
3.2 第 2 ステップ
ステップ 2: 直線と円の 2 つの交点が線分上にあるかどうかを判断し、そうでない場合は、線分と円弧は交わってはならないことを意味し、そうでない場合は次の判断に進みます。
線分は連続しているので、2つの交点が区間を通して線分上にあるかどうかで判断できます。
詳細には、線分の X 軸方向の間隔は[ p 1 . x , p 2 . x ] [p_1.x\ ,\ p_2.x] であることがわかっています。[ p1。× 、 p2。× ]
x 1 x_1の場合バツ1[ p 1 . x , p 2 . x ] [p_1.x\ ,\ p_2.x]にはありません[ p1。× 、 p2. x ]区間、その後点( x 1 , y 1 ) (x_1,y_1)( ×1、y1)は線分上にないので、線分と円弧の交点になることは不可能です
同様に、x 2 x_2の場合バツ2[ p 1 . x , p 2 . x ] [p_1.x\ ,\ p_2.x]にはありません[ p1。× 、 p2. x ]区間、その後点( x 2 , y 2 ) (x_2,y_2)( ×2、y2)を線分と円弧の交点にすることはできません
点( x 1 , y 1 ) (x_1,y_1)の場合( ×1、y1) 和点 ( x 2 , y 2 ) (x_2,y_2) ( ×2、y2)は線分と円弧の交点ではありません。これは、線分と円弧が交差してはならないことを意味します。
それ以外の場合は、次のステップに進みます
3.3 3番目のステップ
ステップ 3: 前の導出に従って、既知の直線と円の線分の交点が( x 1 , y 1 ) (x_1,y_1)であると仮定します。( ×1、y1) , このステップでは、点が円弧上にあるかどうかを判断する必要があります。円弧上にある場合は、線分と円弧が交差することを意味し、その交点は ( x 1 , y 1 ) (x_1,y_1 ) になります。 )( ×1、y1)
このステップでは、円のパラメトリック方程式を使用する必要がありますが、便宜上、円弧の開始角度と終了角度がθ 1 \theta_1であると仮定します。私1 和 θ 2 \theta_2 私2の場合、円弧のパラメトリック方程式は次のようになります。
{ x = a + rcos θ ( 7 ) y = b + rsin θ ( 8 ) , θ 1 ≤ θ ≤ θ 2 \begin{cases} x=a+rcos\theta \ \ \ \ \left( 7 \right) \\ y=b+rsin\theta \ \ \ \ \left( 8 \right) \\ \end{ケース} \ \ \ \ ,\theta_1\and\theta\and\theta_2{ バツ=ある+rcosθ _ ( 7 )y=b+θのrs ( 8 ) 、私1≤私≤私2
将( x 1 , y 1 ) (x_1,y_1)( ×1、y1)を式(7) (7)( 7 )と式( 8 ) (8)( 8 )では:
{ x 1 = a + rcos θ および 1 = b + rsin θ , θ 1 ≤ θ ≤ θ 2 ⇓ { x 1 − a = rcos θ ( 9 ) および 1 − b = rsin θ ( 10 ) , θ 1 ≤ θ ≤ θ 2 \begin{cases} x_1=a+rcos\theta \\ y_1=b+rsin\theta \\ \end{cases} \ \ \ \ ,\theta_1\theta\theta_2 \\ \Downarrow \\ \begin {ケース} x_1-a=rcos\theta \ \ \ \ \\left( 9 \right) \\ y_1-b=rsin\theta \ \ \ \ \\left( 10 \right) \\ \end{ ケース} \ \ \ \ 、\theta_1\and\theta\and\theta_2{ バツ1=ある+rcosθ _y1=b+θのrs 、私1≤私≤私2⇓{ バツ1−ある=rcosθ _ ( 9 )y1−b=θのrs ( 10 ) 、私1≤私≤私2
( 10 ) (10)( 10 ) ➗( 9 ) (9)( 9 )は以下を取得できます:
y 1 − bx 1 − a = Tan θ ⇓ θ = arctan ( y 1 − bx 1 − a ) \frac{y_1-b}{x_1-a}=tan\theta \\ \Downarrow \\ \theta = arctan( \frac{y_1-b}{x_1-a})バツ1−あるy1−b=θ _ _⇓私=安心ですよ( _バツ1−あるy1−b)
θ \thetaが解かれた場合θ は区間[ θ 1 , θ 2 ] [\theta_1,\theta_2][私1、私2]、次に点( x 1 , y 1 ) (x_1,y_1)( ×1、y1) は円弧上にありません
それ以外の場合、点( x 1 , y 1 ) (x_1,y_1)( ×1、y1) は円弧上にあり、線分と円弧の交点です。
これで証明は終わりです!
注:アタン アタンを使用してくださいa t an関数が逆正接値を計算する場合、戻り値の値の範囲は[ − π 2 , π 2 ] [-\frac{\pi}{2},\frac{\pi}{2}] です。[ −2p、2p],而 θ \theta θの値は[ 0 , 2 π ] [0,2\pi]です[ 0 ,2 π ]なので、実際のコードで角度を変換する必要があります。
4. 完全なコード
// 圆周率
#define PI acos(-1)
// 浮点型数据精度
#define ERROR 0.0000001
// lineIsIntersectArc 的辅助函数:接着第二步判断
bool lineIsIntersectArcAuxiliaryFunction(Point p1, Point p2, double a, double b, double theta1, double theta2, double x1, double y1){
// 2.3 判断交点是否在线段上
if (p1.x <= x1 && x1 <= p2.x){
// 第三步:交点(x1,y1)在线段上,再判断该点是否在圆弧上,如果在,则说明线段和圆弧相交,且交点为(x1,y1)
// 3,1 计算 theta ,并转化为角度值
double theta = atan((y1 - b) / (x1 - a)) / PI * 180.0;
// 3.2 修正角度值,确保 theta1 <= theta2
if (theta1 > theta2){
if (theta2 >= 0){
theta1 -= 360;
}
else{
theta2 += 360;
}
}
if (theta1 < 0 && theta2 < 0){
theta1 += 360;
theta2 += 360;
}
// 3.3 修正 tan 的角度
if (x1 < a){
theta += 180;
}
// 3.4 在端点时,由于精度误差,有时可能会出现判断出错 (即出现 10.000000001 > 10 的情况),故以极小的误差接受(x1,y1)为圆弧的某个端点
if (abs(theta1 - theta) < ERROR || abs(theta2 - theta) < ERROR){
return true;
}
// 3.5 判断 theta 是否在圆弧范围内,如果在则(x1,y1)是线段和圆弧的交点,否则不是
return theta1 <= theta && theta <= theta2;
}
return false;
}
// 判断一个线段是否和圆弧相交
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;
}
// 简化圆弧的相关变量表示
double a = arc.centerpoint.x;
double b = arc.centerpoint.y;
double r = arc.radius;
double theta1 = arc.bangle;
double theta2 = arc.eangle;
// 第一步(粗略判断):将线段当成直线,将圆弧当成圆,如果直线和圆不相交,则线段和圆弧必然不相交,否则进行下一步判断
// 1.1 根据公式,计算直线方程 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 根据韦达定理判断式(6)是否存在实数根
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 >= 0){
// 第二步:判断直线和圆的两个交点是否在线段上,如果不在,说明线段和圆弧必然不相交,否则进行下一步判断
// 2.1 计算两个交点的坐标(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 判断两个交点是否相等
if (varDelta == 0){
// 两个交点相等,只需要判断一个就好
return lineIsIntersectArcAuxiliaryFunction(p1, p2, a, b, theta1, theta2, x1, y1);
}
else{
// 两个交点不相等,分别进行判断,只要其中一个是线段和圆弧的交点就返回 true
return lineIsIntersectArcAuxiliaryFunction(p1, p2, a, b, theta1, theta2, x1, y1) || lineIsIntersectArcAuxiliaryFunction(p1, p2, a, b, theta1, theta2, x2, y2);
}
}
return false;
}
5.エフェクト表示
線分と円弧の交差判定機能を持たせた後、円弧と多角形の交差判定にも使用できます。最も単純な考え方は、円弧と多角形の各辺を使って順番に判断することですが、多角形のいずれかの辺が円弧と交差しない場合、円弧と多角形は交差してはなりません。
クロス判定前は、下図からわかるように、黄色の部分が円弧と多角形の交点・重なり部分です。
交差判定後、円弧と多角形の交差が完全に消えます~