[Geometria Computacional] Julgando se um segmento de linha faz interseção com um arco e implementação de código C++


I. Introdução

Recentemente, trabalhei em um projeto e precisava julgar se um segmento de linha se cruza com um arco circular e não consegui encontrar uma boa resposta na Internet (o mais importante é que não há código que possa ser usado diretamente ou a ideia é muito profunda, não consigo entender), então decidi pensar em uma maneira, escrever um blog e compartilhar as ideias de implementação e o código completo


2. Representação de código do segmento de linha e arco

2.1 Representação de código de segmento de linha

Um segmento de linha pode ser representado por dois pontos. O objeto de ponto é o seguinte, que contém as informações das coordenadas x e y:

class Point {
    
    
public:
    Point(double px=0.0, double py=0.0) {
    
    
        x = px;
        y = py;
    }
    double x;
    double y;
};

2.2 Representação do código de arco

Um arco consiste em coordenadas centrais, raio, ângulos iniciais e finais:

class Arc
{
    
    
public:
	Point centerpoint; // 圆心
	double radius; // 圆弧半径
	double bangle; // 起点角度
	double eangle; // 终点角度
};

3. Ideias de implementação e derivação matemática

3.1 O primeiro passo (julgamento aproximado)

O primeiro passo (julgamento aproximado): considere o segmento de linha como uma linha reta e o arco como um círculo. Se o segmento de linha não cruzar o círculo, então o segmento de linha e o arco não devem se cruzar. Caso contrário, prossiga para o próximo passo de julgamento

Primeiro, expanda o segmento de linha em uma linha reta cuja equação é: y = kx + cy=kx+cy=k x+c

De acordo com os dois pontos do segmento de linha (digamos p 1 p_1p1e p 2 p_2p2,且p 1 . x ≤ p 2 . x p_1.x\le p_2.xp1. xp2. x ), podemos encontrar facilmentekkk eccvalor de c

p 1 p_1p1e p 2 p_2p2Substituindo na equação da reta:

{ p 1 . 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{cases}{ p1. y=k p1. x+c             ( 1 )p2. y=k p2. x+c             ( 2 )

( 1 ) − ( 2 ) (1)-(2)( 1 )( 2 ) disponível:

p 1 . y − p 2 . y = ( p 1 . x − p 2 . x ) k ⇒ k = p 1 . y − p 2 . sim 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} \ \ \ \ \ \ \ \ \ \ \ \ \ \esquerda( 3 \direita)p1. yp2. y=( p1. xp2. x )kk=p1. xp2. xp1. yp2. você             ( 3 )

Vontade (3) (3)( 3 ) em(1) (1)( 1 ) disponível:

p 1 . y = p 1 . y − p 2 . sim 1 . x − p 2 . xp 1 . x + c ⇒ c = p 1 . y − p 1 . y − p 2 . sim 1 . 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. xp2. xp1. yp2. vocêp1. x+cc=p1. yp1. xp2. xp1. yp2. vocêp1. x             ( 4 )

Suponha que as coordenadas do centro do círculo sejam ( a , b ) (a,b)( um ,b ) com raiorrr , é fácil escrever a equação do círculo formado pela extensão do arco da seguinte forma:

( x − a ) 2 + ( y − b ) 2 = r 2 ( 5 ) (xa)^2+(yb)^2=r^2 \ \ \ \ \ \ \ \ \ \ \ \ \ \left( 5 \à direita)( xa )2+( yb )2=r2             ( 5 )

Para determinar se uma linha e um círculo se cruzam, você precisa combinar a equação da linha e a equação do círculo para obter:

{ 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 \certo){ y=k x+c( xa )2+( yb )2=r2( xa )2+( k x+cb )2=r2 ,ordem d=cbx2+a22 x _+k2x_ _2+d2+2 mil d x=r2( 1+k2 )x2+( 2 mil dias2a ) x _+a2+d2r2=0             ( 6 )

Julgamento da equação quadrática em uma variável de acordo com o teorema de Veda (6) (6)( 6 ) Se existem raízes reais:

Δ = ( 2 kd − 2 a ) 2 − 4 ( 1 + k 2 ) ( a 2 + d 2 − r 2 ) ⇒ { Δ < 0 : Fórmula ( 6 ) Não existe raiz real Δ ≥ 0 : Fórmula ( 6 ) Existem raízes reais \varDelta=(2kd-2a)^2-4(1+k^2)(a^2+d^2-r^2) \Rightarrow \begin{cases} \varDelta<0: Fórmula (6 ) não tem raiz de número real\\ \varDelta\ge0: Fórmula (6) tem raiz de número real\end{cases}D=( 2 mil dias2a ) _24 ( 1+k2 )(um2+d2r2 ){ D<0:A equação ( 6 ) não tem raízes reaisD0:A equação ( 6 ) tem raízes

Se a fórmula (6) (6)( 6 ) Não há raiz numérica real, o que significa que não há ponto de interseção entre a reta e o círculo, neste momento não deve haver ponto de interseção entre o segmento de linha e o arco e o programa é encerrado.

Se a fórmula (6) (6)( 6 ) Se houver raízes reais, entãoo XXCoordenadas de direção X x 1 x_1x1e x 2 x_2x2

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)}\e \x_2=\frac{-(2kd-2a)-\sqrt{\varDelta}}{2(1+k^2 )}x1=2 ( 1+k2 )( 2 k d2a ) _+D  e x 2=2 ( 1+k2 )( 2 k d2a ) _D

colocar x 1 x_1x1e x 2 x_2x2Substitua na equação da reta y = kx + cy=kx+cy=k x+c pode obter YYde dois pontos de interseçãoCoordenada da direção Y y 1 y_1y1y 2 y_2y2

y 1 = kx 1 + c 和 y 2 = kx 2 + c y_1=kx_1+c \ 和\ y_2=kx_2+cy1=k x1+c e y  2=k x2+c

Em seguida, prossiga para a próxima etapa do julgamento

3.2 O segundo passo

Etapa 2: determine se os dois pontos de interseção da linha e do círculo estão no segmento de linha. Caso contrário, isso significa que o segmento de linha e o arco não devem se cruzar. Caso contrário, prossiga para a próxima etapa de julgamento

O segmento de linha é contínuo, então você pode julgar se dois pontos de interseção estão no segmento de linha através do intervalo

Em detalhes, já sabemos que o intervalo da direção do eixo X do segmento de linha é [ p 1 . x , p 2 . x ] [p_1.x\ ,\ p_2.x][ p1. x ,  p2. x ]

se x 1 x_1x1não em [ p 1 . x , p 2 . x ] [ p_1.x\ ,\ p_2.x][ p1. x ,  p2. x ] intervalo, então aponte( x 1 , y 1 ) (x_1,y_1)( x1,y1) não está no segmento de linha, é impossível ser o ponto de interseção do segmento de linha e o arco

Da mesma forma, se x 2 x_2x2não em [ p 1 . x , p 2 . x ] [ p_1.x\ ,\ p_2.x][ p1. x ,  p2. x ] intervalo, então aponte( x 2 , y 2 ) (x_2,y_2)( x2,y2) não pode ser o ponto de interseção de um segmento de linha e um arco

Se ponto ( x 1 , y 1 ) (x_1,y_1)( x1,y1) ponto de soma( x 2 , y 2 ) (x_2,y_2)( x2,y2) não é o ponto de interseção do segmento de linha e do arco, significa que o segmento de linha e o arco não devem se cruzar

Caso contrário, prossiga para a próxima etapa

3.3 O terceiro passo

Etapa 3: De acordo com a derivação anterior, suponha que o ponto de interseção de um segmento de linha de uma linha e círculo conhecidos seja ( x 1 , y 1 ) (x_1,y_1)( x1,y1) , nesta etapa, é necessário julgar se o ponto está no arco, se estiver, significa que o segmento de reta e o arco se cruzam, e o ponto de interseção é ( x 1 , y 1 ) (x_1,y_1 )( x1,y1)

Nesta etapa, a equação paramétrica do círculo precisa ser usada. Por uma questão de conveniência, assume-se que o ângulo inicial e o ângulo final do arco são θ 1 \ theta_1eu1Soma θ 2 \theta_2eu2, então a equação paramétrica do arco é:

{ 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{casos} \ \ \ \ ,\theta_1\and\theta\and\theta_2{ x=a+rcos θ    ( 7 )y=b+rs em θ    ( 8 )    ,eu1eueu2

( x 1 , y 1 ) (x_1,y_1)( x1,y1) na fórmula( 7 ) (7)( 7 ) e fórmula( 8 ) (8)Em ( 8 ) :

{ x 1 = a + rcos θ e 1 = b + rsin θ , θ 1 ≤ θ ≤ θ 2 ⇓ { x 1 − a = rcos θ ( 9 ) e 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 {cases} x_1-a=rcos\theta \ \ \ \ \\left( 9 \right) \\ y_1-b=rsin\theta \ \ \ \ \\left( 10 \right) \\ \end{ cases} \ \ \ \ ,\theta_1\and\theta\and\theta_2{ x1=a+rcos θy1=b+rs em θ    ,eu1eueu2{ x1a=rcos θ    ( 9 )y1b=rs em θ    ( 10 )    ,eu1eueu2

(10) (10)( 10 )( 9 ) (9)( 9 ) pode obter:

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})x1ay1b=t e θeu=estou segura ( _ _x1ay1b)

Se o θ \theta resolvidoθ não está no intervalo[ θ 1 , θ 2 ] [\theta_1,\theta_2][ eu1,eu2] , então aponte( x 1 , y 1 ) (x_1,y_1)( x1,y1) não está no arco

Caso contrário, o ponto ( x 1 , y 1 ) (x_1,y_1)( x1,y1) no arco e é uma interseção do segmento de linha e o arco

Neste ponto, a prova acabou!

Nota: use atan atanQuando a função a t an calcula o valor do arco tangente, o intervalo de valores do valor retornado é[ − π 2 , π 2 ] [-\frac{\pi}{2},\frac{\pi}{2}][ -2p,2p] , eθ \thetaO valor de θ é[ 0 , 2 π ] [0,2\pi][ 0 ,2 π ] , então o ângulo precisa ser convertido no código real


4. Código completo

// 圆周率
#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. Exibição de efeitos

Depois de ter uma função para julgar se um segmento de linha faz interseção com um arco, ela pode ser usada para julgar se um arco faz interseção com um polígono. A ideia mais simples é usar o arco e cada lado do polígono para fazer julgamentos sucessivamente.Se algum lado do polígono não cruzar o arco, o arco e o polígono não devem se cruzar.

Antes do julgamento cruzado, como pode ser visto na figura abaixo, a parte amarela é a interseção e sobreposição do arco e do polígono

insira a descrição da imagem aqui

Após o julgamento da interseção, a interseção do arco e do polígono desaparece completamente~

insira a descrição da imagem aqui

Acho que você gosta

Origin blog.csdn.net/weixin_51545953/article/details/130419439
Recomendado
Clasificación