Como determino se duas linhas se cruzam ou não, e em que ponto x, y?
geometry
line-intersection
KingNestor
fonte
fonte
Respostas:
Existe uma boa abordagem para esse problema que usa produtos cruzados de vetor. Defina o produto cruzado de vetor bidimensional v × w como v x w y - v y w x .
Suponha que os dois segmentos de linha vão de p a p + re de q a q + s . Então, qualquer ponto na primeira linha é representável como p + t r (para um parâmetro escalar t ) e qualquer ponto na segunda linha como q + u s (para um parâmetro escalar u ).
As duas linhas se cruzam, se podemos encontrar t e u tal que:
Cruze ambos os lados com s , obtendo
E como s × s = 0, isso significa
E, portanto, resolvendo para t :
Da mesma maneira, podemos resolver para você :
Para reduzir o número de etapas da computação, é conveniente reescrever isso da seguinte maneira (lembrando que s × r = - r × s ):
Agora existem quatro casos:
Se r × s = 0 e ( q - p ) × r = 0, as duas linhas são colineares.
Nesse caso, expresse os pontos de extremidade do segundo segmento ( q e q + s ) em termos da equação do primeiro segmento de linha ( p + t r ):
Se o intervalo de tempo entre t 0 e t 1 intersecta o intervalo [0, 1], em seguida, os segmentos de linhas são colineares e sobrepostos; caso contrário, são colineares e desarticulados.
Observe que se s e r apontam para direções opostas, então s · r <0 e, portanto, o intervalo a ser verificado é [ t 1 , t 0 ] em vez de [ t 0 , t 1 ].
Se r × s = 0 e ( q - p ) × r ≠ 0, as duas linhas são paralelas e não se cruzam.
Se r × s ≠ 0 e 0 ≤ t ≤ 1 e 0 ≤ u ≤ 1, os dois segmentos de linha se encontram no ponto p + t r = q + u s .
Caso contrário, os dois segmentos de linha não são paralelos, mas não se cruzam.
Crédito: esse método é a especialização bidimensional do algoritmo de interseção de linhas 3D do artigo "Interseção de duas linhas em três espaços" de Ronald Goldman, publicado em Graphics Gems , página 304. Em três dimensões, o caso usual é o de que as linhas estão inclinadas (nem paralelas nem se cruzam). Nesse caso, o método fornece os pontos de aproximação mais próxima das duas linhas.
fonte
/ (r × s)
, mas(r × s)
é um vetor, certo? Um vector(0, 0, rx * sy - ry * sx)
. E o lado esquerdo é similarmente um vetor paralelo ao eixo z. Então ... eu apenas divido o componente z pelo outro componente z? A fórmula para t é realmente|(q − p) × s| / |(r × s)|
?FWIW, a seguinte função (em C) detecta interseções de linha e determina o ponto de interseção. É baseado em um algoritmo dos " Truques dos Gurus da Programação de Jogos do Windows ", de Andre LeMothe . Não é diferente de alguns dos algoritmos em outras respostas (por exemplo, de Gareth). LeMothe então usa a Regra de Cramer (não me pergunte) para resolver as equações.
Posso atestar que ele funciona no meu fraco clone de asteróides e parece lidar corretamente com os casos extremos descritos em outras respostas de Elemental, Dan e Wodzu. Provavelmente também é mais rápido que o código publicado pelo KingNestor, porque é tudo multiplicação e divisão, sem raízes quadradas!
Eu acho que há algum potencial para dividir por zero, apesar de não ter sido um problema no meu caso. Fácil de modificar para evitar a falha de qualquer maneira.
BTW, devo dizer que no livro de LeMothe, embora ele aparentemente acerte o algoritmo, o exemplo concreto que ele mostra se encaixa nos números errados e faz cálculos incorretos. Por exemplo:
Isso me confundiu por horas . :(
fonte
s
et
diretamente, teste a relação entre os dois numeradores e o denominador. Somente se as linhas estiverem confirmadas para se cruzarem, você realmente precisará calcular o valor det
(mas nãos
).O problema se reduz a esta pergunta: duas linhas de A a B e de C a D se cruzam? Em seguida, você pode perguntar quatro vezes (entre a linha e cada um dos quatro lados do retângulo).
Aqui está a matemática vetorial para fazer isso. Estou assumindo que a linha de A a B é a linha em questão e a linha de C a D é uma das linhas retangulares. Minha anotação é que
Ax
é a "coordenada x de A" eCy
é a "coordenada y de C." E "*
" meios dot-produto, por isso, por exemploA*B = Ax*Bx + Ay*By
.Este
h
número é a chave. Seh
está entre0
e1
, as linhas se cruzam, caso contrário não. SeF*P
for zero, é claro que você não pode fazer o cálculo, mas neste caso as linhas são paralelas e, portanto, só se cruzam nos casos óbvios.O ponto exato da interseção é
C + F*h
.Mais divertido:
Se
h
é exatamente0
ou1
as linhas tocam em um ponto final. Você pode considerar isso uma "interseção" ou não como achar melhor.Especificamente,
h
é quanto você precisa multiplicar o comprimento da linha para tocar exatamente na outra linha.Portanto, se
h<0
isso significa que a linha do retângulo está "atrás" da linha especificada (com "direção" sendo "de A a B") e seh>1
a linha do retângulo está "na frente" da linha especificada.Derivação:
A e C são vetores que apontam para o início da linha; E e F são os vetores das extremidades de A e C que formam a linha.
Para quaisquer duas linhas não paralelas no plano, deve haver exatamente um par de escalares
g
e deh
forma que esta equação seja válida:Por quê? Como duas linhas não paralelas devem se cruzar, o que significa que você pode dimensionar as duas linhas em certa quantidade cada uma e tocar uma na outra.
( No início, isso parece uma única equação com duas incógnitas! Mas não é quando você considera que se trata de uma equação de vetor 2D, o que significa que realmente é um par de equações em
x
ey
.)Temos que eliminar uma dessas variáveis. Uma maneira fácil é tornar o
E
termo zero. Para fazer isso, pegue o produto escalar de ambos os lados da equação usando um vetor que pontilhará zero com E. Esse vetor que eu chameiP
acima e fiz a transformação óbvia de E.Agora você tem:
fonte
A + E*g = C + F*h
As duas linhas se cruzam se, e somente se, a solução para essa equação (supondo que elas não sejam paralelas) possua ambasg
eh
entre 0 e 1 (in ou exclusivo, dependendo se você conta o toque em um ponto final).Eu tentei implementar o algoritmo tão elegantemente descrito por Jason acima; infelizmente, enquanto trabalhava com a matemática na depuração, encontrei muitos casos para os quais ela não funciona.
Por exemplo, considere os pontos A (10,10) B (20,20) C (10,1) D (1,10) produz h = 0,5 e, no entanto, é claro pelo exame que esses segmentos não estão próximos de cada um de outros.
Representar graficamente isso torna claro que 0 critério <h <1 indica apenas que o ponto de interceptação estaria no CD se ele existisse, mas não diz nada sobre se esse ponto está em AB. Para garantir que haja um ponto cruzado, você deve fazer o cálculo simétrico da variável ge o requisito de interceptação é: 0 <g <1 AND 0 <h <1
fonte
Aqui está uma melhoria na resposta de Gavin. a solução de marcp também é semelhante, mas nenhuma adia a divisão.
Na verdade, isso também é uma aplicação prática da resposta de Gareth Rees, porque o equivalente do produto cruzado em 2D é o produto perp-dot, que é o que esse código usa três. Mudar para 3D e usar o produto cruzado, interpolando s e t no final, resulta nos dois pontos mais próximos entre as linhas em 3D. Enfim, a solução 2D:
Basicamente, adia a divisão até o último momento e move a maioria dos testes até antes de certos cálculos serem feitos, adicionando antecipações. Finalmente, também evita a divisão por zero caso, que ocorre quando as linhas são paralelas.
Você também pode considerar o uso de um teste epsilon em vez de uma comparação com zero. As linhas extremamente próximas do paralelo podem produzir resultados ligeiramente desativados. Isso não é um bug, é uma limitação na matemática de ponto flutuante.
fonte
s32_y
vez de algo que descreve como époint2YDifference
?Pergunta C: Como você detecta se dois segmentos de linha se cruzam ou não?
Eu procurei pelo mesmo tópico e não fiquei satisfeito com as respostas. Então, eu escrevi um artigo que explica muito detalhadamente como verificar se dois segmentos de linha se cruzam com muitas imagens. Há código Java completo (e testado).
Aqui está o artigo, recortado nas partes mais importantes:
O algoritmo, que verifica se o segmento de linha a cruza com o segmento de linha b, se parece com o seguinte:
O que são caixas delimitadoras? Aqui estão duas caixas delimitadoras de dois segmentos de linha:
Se ambas as caixas delimitadoras tiverem uma interseção, mova o segmento de linha a para que um ponto esteja em (0 | 0). Agora você tem uma linha através da origem definida por a. Agora mova o segmento de linha b da mesma maneira e verifique se os novos pontos do segmento de linha b estão em lados diferentes da linha a. Se for esse o caso, verifique o contrário. Se esse também for o caso, os segmentos de linha se cruzam. Caso contrário, eles não se cruzam.
Pergunta A: Onde dois segmentos de linha se cruzam?
Você sabe que dois segmentos de linha a e b se cruzam. Se você não sabe disso, verifique com as ferramentas que eu lhe dei na "Pergunta C".
Agora você pode passar por alguns casos e obter a solução com a matemática da 7ª série (consulte o código e o exemplo interativo ).
Pergunta B: Como você detecta se duas linhas se cruzam ou não?
Vamos dizer que seu ponto
A = (x1, y1)
, pontoB = (x2, y2)
,C = (x_3, y_3)
,D = (x_4, y_4)
. Sua primeira linha é definida por AB (com A! = B) e a segunda, por CD (com C! = D).Pergunta D: Onde duas linhas se cruzam?
Verifique com a pergunta B se eles se cruzam.
As linhas aeb são definidas por dois pontos para cada linha. Basicamente, você pode aplicar a mesma lógica usada na pergunta A.
fonte
A resposta, uma vez aceita aqui, está incorreta (desde então não foi aceita, então viva!). Não elimina corretamente todas as não interseções. Trivialmente, pode parecer funcionar, mas pode falhar, especialmente no caso de 0 e 1 serem considerados válidos para h.
Considere o seguinte caso:
Linhas em (4,1) - (5,1) e (0,0) - (0,2)
Estas são linhas perpendiculares que claramente não se sobrepõem.
A = (4,1)
B = (5,1)
C = (0,0)
D = (0,2)
E = (5,1) - (4,1) = (- 1,0)
F = (0,2) - (0,0) = (0, -2)
P = (0,1)
h = ((4,1) - (0,0)) ponto (0,1) / ((0 , -2) ponto (0,1)) = 0
De acordo com a resposta acima, esses dois segmentos de linha se encontram em um ponto de extremidade (valores de 0 e 1). Esse ponto final seria:
(0,0) + (0, -2) * 0 = (0,0)
Portanto, aparentemente os dois segmentos de linha se encontram em (0,0), que está na linha CD, mas não na linha AB. Então, o que está errado? A resposta é que os valores de 0 e 1 não são válidos e apenas algumas vezes acontecem para prever corretamente a interseção do terminal. Quando a extensão de uma linha (mas não a outra) atenderia ao segmento de linha, o algoritmo prevê uma interseção de segmentos de linha, mas isso não está correto. Eu imagino que, testando começando com AB vs CD e depois testando com CD vs AB, esse problema seria eliminado. Somente se ambos estiverem entre 0 e 1, inclusive, eles poderão se cruzar.
Eu recomendo usar o método de produto cruzado vetorial se você precisar prever pontos finais.
-Dan
fonte
Versão em Python da resposta do iMalc:
fonte
denom = float(...)
Encontrar a interseção correta de dois segmentos de linha é uma tarefa não trivial com muitos casos de aresta. Aqui está uma solução bem documentada, funcional e testada em Java.
Em essência, há três coisas que podem acontecer ao encontrar a interseção de dois segmentos de linha:
Os segmentos não se cruzam
Existe um ponto de interseção único
A interseção é outro segmento
NOTA : No código, suponho que um segmento de linha (x1, y1), (x2, y2) com x1 = x2 e y1 = y2 seja um segmento de linha válido. Matematicamente falando, um segmento de linha consiste em pontos distintos, mas estou permitindo que os segmentos sejam pontos nesta implementação para garantir a integridade.
O código é retirado do meu repositório do github
Aqui está um exemplo simples de uso:
fonte
Só queria mencionar que uma boa explicação e solução explícita podem ser encontradas na série Numeric Recipes. Eu tenho a 3ª edição e a resposta está na página 1117, seção 21.4. Outra solução com uma nomenclatura diferente pode ser encontrada em um artigo de Marina Gavrilova, teste de interseção de seção de linha confiável . A solução dela é, na minha opinião, um pouco mais simples.
Minha implementação está abaixo:
fonte
Muitas soluções estão disponíveis acima, mas acho que a solução abaixo é bastante simples e fácil de entender.
Dois segmentos Vector AB e Vector CD se cruzam se e somente se
Mais especificamente a e b estão no lado oposto do segmento CD se e somente se exatamente um dos dois triplos a, c, d e b, c, d estiver no sentido anti-horário.
Aqui, CCW representa no sentido anti-horário, que retorna verdadeiro / falso com base na orientação dos pontos.
Fonte: http://compgeom.cs.uiuc.edu/~jeffe/teaching/373/notes/x06-sweepline.pdf Página 2
fonte
CCW
teste é definido? Com o sinal do produto externo?C e Objective-C
Baseado na resposta de Gareth Rees
Muitas das funções e estruturas são privadas, mas é fácil saber o que está acontecendo. Isso é público neste repositório https://github.com/hfossli/AGGeometryKit/
fonte
Isso está funcionando bem para mim. Retirado daqui .
fonte
Eu tentei algumas dessas respostas, mas elas não funcionaram para mim (desculpe, pessoal); depois de mais algumas pesquisas na net, encontrei isso .
Com uma pequena modificação em seu código, agora tenho esta função que retornará o ponto de interseção ou, se nenhuma interseção for encontrada, retornará -1, -1.
fonte
Parece haver algum interesse na resposta de Gavin, para a qual cortijon propôs uma versão javascript nos comentários e o iMalc forneceu uma versão com um pouco menos de cálculos . Alguns apontaram deficiências com várias propostas de código e outros comentaram a eficiência de algumas propostas de código.
O algoritmo fornecido pelo iMalc através da resposta de Gavin é o que eu estou usando atualmente em um projeto javascript e eu só queria fornecer uma versão limpa aqui, se isso puder ajudar alguém.
fonte
t = dx2 * dy3 - dx3 * dy2;
...t/d
entra.crossProduct = (line1XDifference * line2YDifference) - (line2XDifference * line1YDifference)
escaledResult = crossProduct / dotProduct
?p1x, p1y
etc. devem descrever os pontos pelos valores x e y, portanto,p1x
é uma abreviação parapoint1x
, da mesma formad1x
, na minha opinião, é uma abreviação para a letra grega,deltaX
ou você poderia dizerdifferenceInX
. (mais)Eu acho que existe uma solução muito mais simples para esse problema. Eu vim com outra idéia hoje e parece funcionar muito bem (pelo menos em 2D por enquanto). Tudo o que você precisa fazer é calcular a interseção entre duas linhas e verificar se o ponto de interseção calculado está dentro das caixas de limite dos dois segmentos de linha. Se for, os segmentos de linha se cruzam. É isso aí.
EDITAR:
É assim que calculo a interseção (não sei mais onde encontrei esse snippet de código)
vem de
e esta é minha classe (simplificada para a resposta) BoundingBox:
fonte
Esta solução pode ajudar
fonte
Eu portado a resposta acima de Kris para JavaScript. Depois de tentar várias respostas diferentes, ele forneceu os pontos corretos. Eu pensei que estava ficando louco por não conseguir os pontos de que precisava.
fonte
Eu tentei de várias maneiras e então decidi escrever minha própria. Então aqui está:
Com base nessas duas fórmulas: (simplifiquei-as a partir da equação de linhas e outras fórmulas)
fonte
Isso com base na resposta de Gareth Ree. Ele também retorna a sobreposição dos segmentos de linha, se o fizerem. Codificado em C ++, V é uma classe vetorial simples. Onde o produto cruzado de dois vetores em 2D retorna um único escalar. Foi testado e aprovado pelo sistema de teste automático de minhas escolas.
fonte
Aqui está uma implementação básica de um segmento de linha em C #, com o código de detecção de interseção correspondente. Requer uma estrutura de vetor / ponto 2D chamada
Vector2f
, embora você possa substituí-la por qualquer outro tipo que possua propriedades X / Y. Você também pode substituirfloat
pordouble
se isso for melhor para suas necessidades.Esse código é usado na minha biblioteca de física .NET, Boing .
fonte
Um programa C ++ para verificar se dois segmentos de linha determinados se cruzam
fonte
Com base na resposta @Gareth Rees, versão para Python:
fonte
Se cada lado do retângulo for um segmento de linha e a parte desenhada pelo usuário for um segmento de linha, será necessário apenas verificar o segmento desenhado pelo usuário quanto à interseção com os quatro segmentos de linha lateral. Este deve ser um exercício bastante simples, considerando os pontos inicial e final de cada segmento.
fonte
Com base na resposta de t3chb0t:
fonte
Eu li esses algoritmos no livro "geometria de múltiplas visualizações"
seguinte texto usando
'como sinal de transposição
* como produto escalar
x como produto cruzado, ao usar como operador
1. definição de linha
um ponto x_vec = (x, y) 'fica na linha ax + por + c = 0
denotamos L = (a, b, c) ', o ponto como (x, y, 1)' como coordenadas homogêneas
a equação da linha pode ser escrita como
(x, y, 1) (a, b, c) '= 0 ou x' * L = 0
2. interseção de linhas
temos duas linhas L1 = (a1, b1, c1) ', L2 = (a2, b2, c2)'
assuma que x é um ponto, um vetor ex = L1 x L2 (produto cruzado L2 L2).
tenha cuidado, x é sempre um ponto 2D, por favor, leia coordenadas homogêneas se você estiver confuso sobre (L1xL2) é um vetor de três elementos e x é uma coordenada 2D.
de acordo com o triplo produto, sabemos que
L1 * (L1 x L2) = 0 e L2 * (L1 x L2) = 0, devido ao co-plano de L1 e L2
substituímos (L1xL2) pelo vetor x, então temos L1 * x = 0, L2 * x = 0, o que significa que x está em L1 e L2, x é o ponto de interseção.
tenha cuidado, aqui x é coordenadas homogêneas; se o último elemento de x é zero, significa que L1 e L2 são paralelos.
fonte
Muitas respostas agruparam todos os cálculos em uma única função. Se você precisar calcular as inclinações de linha, interceptações em y ou interceptações em x para usar em outras partes do seu código, estará fazendo esses cálculos de forma redundante. Separei as respectivas funções, usei nomes óbvios de variáveis e comentei meu código para facilitar o acompanhamento. Eu precisava saber se as linhas se cruzam infinitamente além dos pontos de extremidade, então no JavaScript:
http://jsfiddle.net/skibulk/evmqq00u/
fonte