Como você detecta onde dois segmentos de linha se cruzam? [fechadas]

518

Como determino se duas linhas se cruzam ou não, e em que ponto x, y?

KingNestor
fonte
Pode ser útil pensar nas bordas do retângulo como linhas separadas, em vez do polígono completo.
21713 Ryan Graham
Nota do moderador : a discussão sobre se esta publicação está ou não no tópico pertence ao Meta Stack Overflow Outros comentários sobre isso aqui serão excluídos.
Martijn Pieters

Respostas:

659

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 ).

Dois segmentos de linha se cruzam

As duas linhas se cruzam, se podemos encontrar t e u tal que:

p + t  r = q + u  s

Fórmulas para o ponto de interseção

Cruze ambos os lados com s , obtendo

( p + t  r ) × s = ( q + u  s ) × s

E como s  ×  s = 0, isso significa

t  ( r × s ) = ( q - p ) × s

E, portanto, resolvendo para t :

t = ( q - p ) × s / ( r × s )

Da mesma maneira, podemos resolver para você :

( p + t  r ) × r = ( q + u  s ) × r

u  ( s × r ) = ( p - q ) × r

u = ( p - q ) × r / ( s × r )

Para reduzir o número de etapas da computação, é conveniente reescrever isso da seguinte maneira (lembrando que s  ×  r = -  r  ×  s ):

u = ( q - p ) × r / ( r × s )

Agora existem quatro casos:

  1. 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 ):

    t 0 = ( q - p ) ·  r / ( r  ·  r )

    t 1 = ( q + s - p ) ·  r / ( r  ·  r ) = t 0 + s  ·  r / ( r  ·  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 ].

  2. Se r  ×  s  = 0 e ( q  -  p ) ×  r  ≠ 0, as duas linhas são paralelas e não se cruzam.

  3. 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 .

  4. 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.

Gareth Rees
fonte
5
@myrkos: Não. O primeiro segmento de linha é executado "de p a p + r", portanto, quando é representado em termos paramétricos como "p + tr", o segmento corresponde a 0 ≤ t ≤ 1. Da mesma forma, para o outro segmento.
precisa saber é o seguinte
7
Gareth, sinto que devo estar perdendo alguma coisa, mas como você divide (um vetor) por um vetor? Suas soluções para t e u terminar com / (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)|?
Larsh
7
@ LarsH: veja o primeiro parágrafo.
Gareth Rees
35
Para os interessados, aqui está uma implementação simples de C #, usando as coordenadas de início e fim do PointF para linhas, que parecem estar funcionando: ideone.com/PnPJgb
Matt
24
Eu montei uma implementação JavaScript seguindo @Matt. Fiz correções para os erros apontados por Tekito.
Pgkelley
230

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.

// Returns 1 if the lines intersect, otherwise 0. In addition, if the lines 
// intersect the intersection point may be stored in the floats i_x and i_y.
char get_line_intersection(float p0_x, float p0_y, float p1_x, float p1_y, 
    float p2_x, float p2_y, float p3_x, float p3_y, float *i_x, float *i_y)
{
    float s1_x, s1_y, s2_x, s2_y;
    s1_x = p1_x - p0_x;     s1_y = p1_y - p0_y;
    s2_x = p3_x - p2_x;     s2_y = p3_y - p2_y;

    float s, t;
    s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y);
    t = ( s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);

    if (s >= 0 && s <= 1 && t >= 0 && t <= 1)
    {
        // Collision detected
        if (i_x != NULL)
            *i_x = p0_x + (t * s1_x);
        if (i_y != NULL)
            *i_y = p0_y + (t * s1_y);
        return 1;
    }

    return 0; // No collision
}

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:

(4 * (4-1) + 12 * (7 - 1)) / (17 * 4 + 12 * 10)

= 844 / 0,88

= 0,44

Isso me confundiu por horas . :(

Gavin
fonte
9
função getLineIntersection (p0_x, p0_y, p1_x, p1_y, p2_x, p2_y, p3_x, p3_y) {var s1_x, s1_y, s2_x, s2_y; s1_x = p1_x - p0_x; s1_y = p1_y - p0_y; s2_x = p3_x - p2_x; s2_y = p3_y - p2_y; var s, t; s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y); t = (s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);
cortijon

5
if (s> = 0 && s <= 1 && t> = 0 && t <= 1) {// Colisão detectada var intX = p0_x + (t * s1_x); var intY = p0_y + (t * s1_y); retornar [intX, intY]; } retornar nulo; // No colision}
cortijon 19/12/12
13
bom algoritmo, no entanto, para sua informação, ele não lida com casos em que o determinante é 0. (o -s2_x * s1_y + s1_x * s2_y acima). Se for 0 (ou próximo de 0), as linhas são paralelas ou colineares. Se for colinear, a interseção pode ser outro segmento de linha.
seand
16
As operações de duas divisões podem ser evitadas por velocidade (a divisão custa mais do que multiplicação); se as linhas se cruzam, você precisa de uma divisão; se elas não se cruzam, você precisa de zero. Deve-se primeiro calcular o denominador e parar cedo se for zero (possivelmente adicionando código para detectar colinearidade.) Em seguida, em vez de calcular se tdiretamente, 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 de t(mas não s).
Qwertie
18
Fiz testes de desempenho em todos os algoritmos postados aqui, e este é pelo menos duas vezes mais rápido que qualquer outro. Obrigado por publicar!
Lajos
63

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" e Cyé a "coordenada y de C." E " *" meios dot-produto, por isso, por exemplo A*B = Ax*Bx + Ay*By.

E = B-A = ( Bx-Ax, By-Ay )
F = D-C = ( Dx-Cx, Dy-Cy ) 
P = ( -Ey, Ex )
h = ( (A-C) * P ) / ( F * P )

Este hnúmero é a chave. Se hestá entre 0e1 , as linhas se cruzam, caso contrário não. Se F*Pfor 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é exatamente 0 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<0isso significa que a linha do retângulo está "atrás" da linha especificada (com "direção" sendo "de A a B") e se h>1a 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 ge de hforma que esta equação seja válida:

A + E*g = C + F*h

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 xey .)

Temos que eliminar uma dessas variáveis. Uma maneira fácil é tornar o Etermo 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 chamei Pacima e fiz a transformação óbvia de E.

Agora você tem:

A*P = C*P + F*P*h
(A-C)*P = (F*P)*h
( (A-C)*P ) / (F*P) = h
Jason Cohen
fonte
29
Esse algoritmo é legal. Mas há um buraco, como apontado por Dan @ stackoverflow.com/questions/563198/… & Elemental @ stackoverflow.com/questions/563198/… Seria legal se você atualizasse sua resposta para referência futura. Obrigado.
Chantz 6/10/09
2
Esse algoritmo é numericamente estável? Eu tentei uma abordagem semelhante e acabou dando resultados estranhos ao trabalhar em carros alegóricos.
Milosz
3
Parece haver outro problema com esse algoritmo. Quando é alimentado os pontos A = {1, 0} B = {2, 0} C = {0, 0} D = {1,0}, embora os segmentos de linha toquem claramente no final, F P (e também E Q, de acordo com a correção do usuário abaixo) são ambos 0, fazendo com que a divisão por 0 encontre h e g. Ainda trabalhando na solução para este, mas achei que o problema valia a pena apontar.
candrews
12
Esta resposta está simplesmente incorreta. Tente A = {0,0}, B = {0,1}, C = {0,2} D = {2,0}
Tim Cooper
6
A + E*g = C + F*hAs duas linhas se cruzam se, e somente se, a solução para essa equação (supondo que elas não sejam paralelas) possua ambas geh entre 0 e 1 (in ou exclusivo, dependendo se você conta o toque em um ponto final).
Daniel Fischer
46

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

Elementar
fonte
2
Eu tenho puxado meu cabelo tentando descobrir por que a resposta aceita não estava funcionando para mim. Muito obrigado!
22610 Matt Bridges
1
Também é notável que as condições de contorno funcionem neste caso (ou seja, para h = 0 ou h = 1 ou g = 0 ou g = 1, as linhas 'apenas' tocam
Elemental
Para as pessoas que estão com problemas para visualizar o resultado, eu fiz uma implementação disso em Javascript: jsfiddle.net/ferrybig/eokwL9mp
Ferrybig
45

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:

int get_line_intersection(float p0_x, float p0_y, float p1_x, float p1_y, 
    float p2_x, float p2_y, float p3_x, float p3_y, float *i_x, float *i_y)
{
    float s02_x, s02_y, s10_x, s10_y, s32_x, s32_y, s_numer, t_numer, denom, t;
    s10_x = p1_x - p0_x;
    s10_y = p1_y - p0_y;
    s32_x = p3_x - p2_x;
    s32_y = p3_y - p2_y;

    denom = s10_x * s32_y - s32_x * s10_y;
    if (denom == 0)
        return 0; // Collinear
    bool denomPositive = denom > 0;

    s02_x = p0_x - p2_x;
    s02_y = p0_y - p2_y;
    s_numer = s10_x * s02_y - s10_y * s02_x;
    if ((s_numer < 0) == denomPositive)
        return 0; // No collision

    t_numer = s32_x * s02_y - s32_y * s02_x;
    if ((t_numer < 0) == denomPositive)
        return 0; // No collision

    if (((s_numer > denom) == denomPositive) || ((t_numer > denom) == denomPositive))
        return 0; // No collision
    // Collision detected
    t = t_numer / denom;
    if (i_x != NULL)
        *i_x = p0_x + (t * s10_x);
    if (i_y != NULL)
        *i_y = p0_y + (t * s10_y);

    return 1;
}

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.

iMalc
fonte
1
Falha se alguns dos pontos tiverem o valor 0 .. isso não deve acontecer, certo?
Hfossli
1
Fiz uma correção para um bug introduzido ao adiar a divisão. t poderia ser positivo quando o número e o denominador fossem ambos negativos.
iMalc 02/04
2
Não funciona se p0-p1 for vertical e p2-p3 for horizontal e os dois segmentos cruzados. (o primeiro retorno é executado)
Fabio Dalla Libera
O gabinete coolinear possui duas possibilidades: não sobreposição e sobreposição. O primeiro shoul retorna false e o segundo true. No seu código, isso não foi testado. sempre retorna falso como a maioria das respostas aqui. É uma pena que nenhuma solução realmente pareça funcionar.
AlexWien
3
Você pode me esclarecer por que todos esses nomes de variáveis ​​são vagos, em s32_yvez de algo que descreve como é point2YDifference?
precisa saber é o seguinte
40

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:

Digite a descrição da imagem aqui

O que são caixas delimitadoras? Aqui estão duas caixas delimitadoras de dois segmentos de linha:

insira a descrição da imagem aqui

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), ponto B = (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).

function doLinesIntersect(AB, CD) {
    if (x1 == x2) {
        return !(x3 == x4 && x1 != x3);
    } else if (x3 == x4) {
        return true;
    } else {
        // Both lines are not parallel to the y-axis
        m1 = (y1-y2)/(x1-x2);
        m2 = (y3-y4)/(x3-x4);
        return m1 != m2;
    }
}

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.

Martin Thoma
fonte
15
Para esclarecer, a pergunta B nesta resposta é realmente sobre duas linhas que se cruzam, não segmentos de linha. Eu não estou reclamando; não está incorreto. Só não quero que ninguém seja enganado.
phord
1
Não há "pergunta C". E Pergunta D única salta de volta para Pergunta A.
Konrad Viltersten
21

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

Dan
fonte
4
A resposta "aceita" pode mudar, então você deve chamá-la de outra coisa. (Na verdade, eu acho que mudou desde o seu comentário)
Johannes Hoff
14

Versão em Python da resposta do iMalc:

def find_intersection( p0, p1, p2, p3 ) :

    s10_x = p1[0] - p0[0]
    s10_y = p1[1] - p0[1]
    s32_x = p3[0] - p2[0]
    s32_y = p3[1] - p2[1]

    denom = s10_x * s32_y - s32_x * s10_y

    if denom == 0 : return None # collinear

    denom_is_positive = denom > 0

    s02_x = p0[0] - p2[0]
    s02_y = p0[1] - p2[1]

    s_numer = s10_x * s02_y - s10_y * s02_x

    if (s_numer < 0) == denom_is_positive : return None # no collision

    t_numer = s32_x * s02_y - s32_y * s02_x

    if (t_numer < 0) == denom_is_positive : return None # no collision

    if (s_numer > denom) == denom_is_positive or (t_numer > denom) == denom_is_positive : return None # no collision


    # collision detected

    t = t_numer / denom

    intersection_point = [ p0[0] + (t * s10_x), p0[1] + (t * s10_y) ]


    return intersection_point
Kris
fonte
Lembre-se de que você precisa fazer seus números flutuarem ou alterar a linha 8 para usardenom = float(...)
Jonno_FTW 4/17/17
11

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:

  1. Os segmentos não se cruzam

  2. Existe um ponto de interseção único

  3. 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

/**
 * This snippet finds the intersection of two line segments.
 * The intersection may either be empty, a single point or the
 * intersection is a subsegment there's an overlap.
 */

import static java.lang.Math.abs;
import static java.lang.Math.max;
import static java.lang.Math.min;

import java.util.ArrayList;
import java.util.List;

public class LineSegmentLineSegmentIntersection {

  // Small epsilon used for double value comparison.
  private static final double EPS = 1e-5;

  // 2D Point class.
  public static class Pt {
    double x, y;
    public Pt(double x, double y) {
      this.x = x; 
      this.y = y;
    }
    public boolean equals(Pt pt) {
      return abs(x - pt.x) < EPS && abs(y - pt.y) < EPS;
    }
  }

  // Finds the orientation of point 'c' relative to the line segment (a, b)
  // Returns  0 if all three points are collinear.
  // Returns -1 if 'c' is clockwise to segment (a, b), i.e right of line formed by the segment.
  // Returns +1 if 'c' is counter clockwise to segment (a, b), i.e left of line
  // formed by the segment.
  public static int orientation(Pt a, Pt b, Pt c) {
    double value = (b.y - a.y) * (c.x - b.x) - 
                   (b.x - a.x) * (c.y - b.y);
    if (abs(value) < EPS) return 0;
    return (value > 0) ? -1 : +1;
  }

  // Tests whether point 'c' is on the line segment (a, b).
  // Ensure first that point c is collinear to segment (a, b) and
  // then check whether c is within the rectangle formed by (a, b)
  public static boolean pointOnLine(Pt a, Pt b, Pt c) {
    return orientation(a, b, c) == 0 && 
           min(a.x, b.x) <= c.x && c.x <= max(a.x, b.x) && 
           min(a.y, b.y) <= c.y && c.y <= max(a.y, b.y);
  }

  // Determines whether two segments intersect.
  public static boolean segmentsIntersect(Pt p1, Pt p2, Pt p3, Pt p4) {

    // Get the orientation of points p3 and p4 in relation
    // to the line segment (p1, p2)
    int o1 = orientation(p1, p2, p3);
    int o2 = orientation(p1, p2, p4);
    int o3 = orientation(p3, p4, p1);
    int o4 = orientation(p3, p4, p2);

    // If the points p1, p2 are on opposite sides of the infinite
    // line formed by (p3, p4) and conversly p3, p4 are on opposite
    // sides of the infinite line formed by (p1, p2) then there is
    // an intersection.
    if (o1 != o2 && o3 != o4) return true;

    // Collinear special cases (perhaps these if checks can be simplified?)
    if (o1 == 0 && pointOnLine(p1, p2, p3)) return true;
    if (o2 == 0 && pointOnLine(p1, p2, p4)) return true;
    if (o3 == 0 && pointOnLine(p3, p4, p1)) return true;
    if (o4 == 0 && pointOnLine(p3, p4, p2)) return true;

    return false;
  }

  public static List<Pt> getCommonEndpoints(Pt p1, Pt p2, Pt p3, Pt p4) {

    List<Pt> points = new ArrayList<>();

    if (p1.equals(p3)) {
      points.add(p1);
      if (p2.equals(p4)) points.add(p2);

    } else if (p1.equals(p4)) {
      points.add(p1);
      if (p2.equals(p3)) points.add(p2);

    } else if (p2.equals(p3)) {
      points.add(p2);
      if (p1.equals(p4)) points.add(p1);

    } else if (p2.equals(p4)) {
      points.add(p2);
      if (p1.equals(p3)) points.add(p1);
    }

    return points;
  }

  // Finds the intersection point(s) of two line segments. Unlike regular line 
  // segments, segments which are points (x1 = x2 and y1 = y2) are allowed.
  public static Pt[] lineSegmentLineSegmentIntersection(Pt p1, Pt p2, Pt p3, Pt p4) {

    // No intersection.
    if (!segmentsIntersect(p1, p2, p3, p4)) return new Pt[]{};

    // Both segments are a single point.
    if (p1.equals(p2) && p2.equals(p3) && p3.equals(p4))
      return new Pt[]{p1};

    List<Pt> endpoints = getCommonEndpoints(p1, p2, p3, p4);
    int n = endpoints.size();

    // One of the line segments is an intersecting single point.
    // NOTE: checking only n == 1 is insufficient to return early
    // because the solution might be a sub segment.
    boolean singleton = p1.equals(p2) || p3.equals(p4);
    if (n == 1 && singleton) return new Pt[]{endpoints.get(0)};

    // Segments are equal.
    if (n == 2) return new Pt[]{endpoints.get(0), endpoints.get(1)};

    boolean collinearSegments = (orientation(p1, p2, p3) == 0) && 
                                (orientation(p1, p2, p4) == 0);

    // The intersection will be a sub-segment of the two
    // segments since they overlap each other.
    if (collinearSegments) {

      // Segment #2 is enclosed in segment #1
      if (pointOnLine(p1, p2, p3) && pointOnLine(p1, p2, p4))
        return new Pt[]{p3, p4};

      // Segment #1 is enclosed in segment #2
      if (pointOnLine(p3, p4, p1) && pointOnLine(p3, p4, p2))
        return new Pt[]{p1, p2};

      // The subsegment is part of segment #1 and part of segment #2.
      // Find the middle points which correspond to this segment.
      Pt midPoint1 = pointOnLine(p1, p2, p3) ? p3 : p4;
      Pt midPoint2 = pointOnLine(p3, p4, p1) ? p1 : p2;

      // There is actually only one middle point!
      if (midPoint1.equals(midPoint2)) return new Pt[]{midPoint1};

      return new Pt[]{midPoint1, midPoint2};
    }

    /* Beyond this point there is a unique intersection point. */

    // Segment #1 is a vertical line.
    if (abs(p1.x - p2.x) < EPS) {
      double m = (p4.y - p3.y) / (p4.x - p3.x);
      double b = p3.y - m * p3.x;
      return new Pt[]{new Pt(p1.x, m * p1.x + b)};
    }

    // Segment #2 is a vertical line.
    if (abs(p3.x - p4.x) < EPS) {
      double m = (p2.y - p1.y) / (p2.x - p1.x);
      double b = p1.y - m * p1.x;
      return new Pt[]{new Pt(p3.x, m * p3.x + b)};
    }

    double m1 = (p2.y - p1.y) / (p2.x - p1.x);
    double m2 = (p4.y - p3.y) / (p4.x - p3.x);
    double b1 = p1.y - m1 * p1.x;
    double b2 = p3.y - m2 * p3.x;
    double x = (b2 - b1) / (m1 - m2);
    double y = (m1 * b2 - m2 * b1) / (m1 - m2);

    return new Pt[]{new Pt(x, y)};
  }

}

Aqui está um exemplo simples de uso:

  public static void main(String[] args) {

    // Segment #1 is (p1, p2), segment #2 is (p3, p4)
    Pt p1, p2, p3, p4;

    p1 = new Pt(-2, 4); p2 = new Pt(3, 3);
    p3 = new Pt(0, 0);  p4 = new Pt(2, 4);
    Pt[] points = lineSegmentLineSegmentIntersection(p1, p2, p3, p4);
    Pt point = points[0];

    // Prints: (1.636, 3.273)
    System.out.printf("(%.3f, %.3f)\n", point.x, point.y);

    p1 = new Pt(-10, 0); p2 = new Pt(+10, 0);
    p3 = new Pt(-5, 0);  p4 = new Pt(+5, 0);
    points = lineSegmentLineSegmentIntersection(p1, p2, p3, p4);
    Pt point1 = points[0], point2 = points[1];

    // Prints: (-5.000, 0.000) (5.000, 0.000)
    System.out.printf("(%.3f, %.3f) (%.3f, %.3f)\n", point1.x, point1.y, point2.x, point2.y);
  }
will.fiset
fonte
funcionou para o meu sistema de coordenadas geográficas! obrigado! Mas é para interseção de linhas infinitas, e eu estou procurando mais por interseção de linhas finitas.
M. Usman Khan
8

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:

bool NuGeometry::IsBetween(const double& x0, const double& x, const double& x1){
   return (x >= x0) && (x <= x1);
}

bool NuGeometry::FindIntersection(const double& x0, const double& y0, 
     const double& x1, const double& y1,
     const double& a0, const double& b0, 
     const double& a1, const double& b1, 
     double& xy, double& ab) {
   // four endpoints are x0, y0 & x1,y1 & a0,b0 & a1,b1
   // returned values xy and ab are the fractional distance along xy and ab
   // and are only defined when the result is true

   bool partial = false;
   double denom = (b0 - b1) * (x0 - x1) - (y0 - y1) * (a0 - a1);
   if (denom == 0) {
      xy = -1;
      ab = -1;
   } else {
      xy = (a0 * (y1 - b1) + a1 * (b0 - y1) + x1 * (b1 - b0)) / denom;
      partial = NuGeometry::IsBetween(0, xy, 1);
      if (partial) {
         // no point calculating this unless xy is between 0 & 1
         ab = (y1 * (x0 - a1) + b1 * (x1 - x0) + y0 * (a1 - x1)) / denom; 
      }
   }
   if ( partial && NuGeometry::IsBetween(0, ab, 1)) {
      ab = 1-ab;
      xy = 1-xy;
      return true;
   }  else return false;
}
marcp
fonte
Não funciona para p1 = (0,0), p2 = (10,0), P3 = (9,0), P4 = (20,0)
padmalcom
Depende da sua definição de "não funciona", eu acho. Denom é 0, então ele retornará false, o que parece correto para mim, pois eles não se cruzam. Colinear não é o mesmo que cruzar.
marcp
8

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

  1. Os pontos de extremidade aeb estão em lados opostos do segmento CD.
  2. Os pontos de extremidade c e d estão no lado oposto do segmento AB.

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.

Intersect(a, b, c, d)
 if CCW(a, c, d) == CCW(b, c, d)
    return false;
 else if CCW(a, b, c) == CCW(a, b, d)
    return false;
 else
    return true;

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

zstring
fonte
2
Eu acho que você deveria ser um pouco mais específico: como o CCWteste é definido? Com o sinal do produto externo?
Ocramz 17/08/16
Obrigado; esse pseudocódigo permitiu uma implementação muito direta no Scratch; veja este projeto: scratch.mit.edu/projects/129319027
Ruud Helderman
8

C e Objective-C

Baseado na resposta de Gareth Rees

const AGKLine AGKLineZero = (AGKLine){(CGPoint){0.0, 0.0}, (CGPoint){0.0, 0.0}};

AGKLine AGKLineMake(CGPoint start, CGPoint end)
{
    return (AGKLine){start, end};
}

double AGKLineLength(AGKLine l)
{
    return CGPointLengthBetween_AGK(l.start, l.end);
}

BOOL AGKLineIntersection(AGKLine l1, AGKLine l2, CGPoint *out_pointOfIntersection)
{
    // http://stackoverflow.com/a/565282/202451

    CGPoint p = l1.start;
    CGPoint q = l2.start;
    CGPoint r = CGPointSubtract_AGK(l1.end, l1.start);
    CGPoint s = CGPointSubtract_AGK(l2.end, l2.start);

    double s_r_crossProduct = CGPointCrossProductZComponent_AGK(r, s);
    double t = CGPointCrossProductZComponent_AGK(CGPointSubtract_AGK(q, p), s) / s_r_crossProduct;
    double u = CGPointCrossProductZComponent_AGK(CGPointSubtract_AGK(q, p), r) / s_r_crossProduct;

    if(t < 0 || t > 1.0 || u < 0 || u > 1.0)
    {
        if(out_pointOfIntersection != NULL)
        {
            *out_pointOfIntersection = CGPointZero;
        }
        return NO;
    }
    else
    {
        if(out_pointOfIntersection != NULL)
        {
            CGPoint i = CGPointAdd_AGK(p, CGPointMultiply_AGK(r, t));
            *out_pointOfIntersection = i;
        }
        return YES;
    }
}

CGFloat CGPointCrossProductZComponent_AGK(CGPoint v1, CGPoint v2)
{
    return v1.x * v2.y - v1.y * v2.x;
}

CGPoint CGPointSubtract_AGK(CGPoint p1, CGPoint p2)
{
    return (CGPoint){p1.x - p2.x, p1.y - p2.y};
}

CGPoint CGPointAdd_AGK(CGPoint p1, CGPoint p2)
{
    return (CGPoint){p1.x + p2.x, p1.y + p2.y};
}

CGFloat CGPointCrossProductZComponent_AGK(CGPoint v1, CGPoint v2)
{
    return v1.x * v2.y - v1.y * v2.x;
}

CGPoint CGPointMultiply_AGK(CGPoint p1, CGFloat factor)
{
    return (CGPoint){p1.x * factor, p1.y * factor};
}

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/

hfossli
fonte
De onde vem o AGPointZero nesse código?
Seanicus
1
@seanicus exemplo atualizado para usar CGPoint vez
hfossli
6

Isso está funcionando bem para mim. Retirado daqui .

 // calculates intersection and checks for parallel lines.  
 // also checks that the intersection point is actually on  
 // the line segment p1-p2  
 Point findIntersection(Point p1,Point p2,  
   Point p3,Point p4) {  
   float xD1,yD1,xD2,yD2,xD3,yD3;  
   float dot,deg,len1,len2;  
   float segmentLen1,segmentLen2;  
   float ua,ub,div;  

   // calculate differences  
   xD1=p2.x-p1.x;  
   xD2=p4.x-p3.x;  
   yD1=p2.y-p1.y;  
   yD2=p4.y-p3.y;  
   xD3=p1.x-p3.x;  
   yD3=p1.y-p3.y;    

   // calculate the lengths of the two lines  
   len1=sqrt(xD1*xD1+yD1*yD1);  
   len2=sqrt(xD2*xD2+yD2*yD2);  

   // calculate angle between the two lines.  
   dot=(xD1*xD2+yD1*yD2); // dot product  
   deg=dot/(len1*len2);  

   // if abs(angle)==1 then the lines are parallell,  
   // so no intersection is possible  
   if(abs(deg)==1) return null;  

   // find intersection Pt between two lines  
   Point pt=new Point(0,0);  
   div=yD2*xD1-xD2*yD1;  
   ua=(xD2*yD3-yD2*xD3)/div;  
   ub=(xD1*yD3-yD1*xD3)/div;  
   pt.x=p1.x+ua*xD1;  
   pt.y=p1.y+ua*yD1;  

   // calculate the combined length of the two segments  
   // between Pt-p1 and Pt-p2  
   xD1=pt.x-p1.x;  
   xD2=pt.x-p2.x;  
   yD1=pt.y-p1.y;  
   yD2=pt.y-p2.y;  
   segmentLen1=sqrt(xD1*xD1+yD1*yD1)+sqrt(xD2*xD2+yD2*yD2);  

   // calculate the combined length of the two segments  
   // between Pt-p3 and Pt-p4  
   xD1=pt.x-p3.x;  
   xD2=pt.x-p4.x;  
   yD1=pt.y-p3.y;  
   yD2=pt.y-p4.y;  
   segmentLen2=sqrt(xD1*xD1+yD1*yD1)+sqrt(xD2*xD2+yD2*yD2);  

   // if the lengths of both sets of segments are the same as  
   // the lenghts of the two lines the point is actually  
   // on the line segment.  

   // if the point isn’t on the line, return null  
   if(abs(len1-segmentLen1)>0.01 || abs(len2-segmentLen2)>0.01)  
     return null;  

   // return the valid intersection  
   return pt;  
 }  

 class Point{  
   float x,y;  
   Point(float x, float y){  
     this.x = x;  
     this.y = y;  
   }  

   void set(float x, float y){  
     this.x = x;  
     this.y = y;  
   }  
 }  
KingNestor
fonte
8
Existem vários problemas com este código. Pode gerar uma exceção devido à divisão por zero; é lento porque tem raízes quadradas; e às vezes retorna falsos positivos porque usa um fator de falsificação. Você pode fazer melhor que isso!
21730 Gareth Rees
Ok como uma solução, mas que dado por Jason é definitivamente computacionalmente mais rápido e evita muitos dos problemas com esta solução
Elemental
6

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.

    Public Function intercetion(ByVal ax As Integer, ByVal ay As Integer, ByVal bx As Integer, ByVal by As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal dx As Integer, ByVal dy As Integer) As Point
    '//  Determines the intersection point of the line segment defined by points A and B
    '//  with the line segment defined by points C and D.
    '//
    '//  Returns YES if the intersection point was found, and stores that point in X,Y.
    '//  Returns NO if there is no determinable intersection point, in which case X,Y will
    '//  be unmodified.

    Dim distAB, theCos, theSin, newX, ABpos As Double

    '//  Fail if either line segment is zero-length.
    If ax = bx And ay = by Or cx = dx And cy = dy Then Return New Point(-1, -1)

    '//  Fail if the segments share an end-point.
    If ax = cx And ay = cy Or bx = cx And by = cy Or ax = dx And ay = dy Or bx = dx And by = dy Then Return New Point(-1, -1)

    '//  (1) Translate the system so that point A is on the origin.
    bx -= ax
    by -= ay
    cx -= ax
    cy -= ay
    dx -= ax
    dy -= ay

    '//  Discover the length of segment A-B.
    distAB = Math.Sqrt(bx * bx + by * by)

    '//  (2) Rotate the system so that point B is on the positive X axis.
    theCos = bx / distAB
    theSin = by / distAB
    newX = cx * theCos + cy * theSin
    cy = cy * theCos - cx * theSin
    cx = newX
    newX = dx * theCos + dy * theSin
    dy = dy * theCos - dx * theSin
    dx = newX

    '//  Fail if segment C-D doesn't cross line A-B.
    If cy < 0 And dy < 0 Or cy >= 0 And dy >= 0 Then Return New Point(-1, -1)

    '//  (3) Discover the position of the intersection point along line A-B.
    ABpos = dx + (cx - dx) * dy / (dy - cy)

    '//  Fail if segment C-D crosses line A-B outside of segment A-B.
    If ABpos < 0 Or ABpos > distAB Then Return New Point(-1, -1)

    '//  (4) Apply the discovered position to line A-B in the original coordinate system.
    '*X=Ax+ABpos*theCos
    '*Y=Ay+ABpos*theSin

    '//  Success.
    Return New Point(ax + ABpos * theCos, ay + ABpos * theSin)
End Function
Robert
fonte
6

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.

// Some variables for reuse, others may do this differently
var p0x, p1x, p2x, p3x, ix,
    p0y, p1y, p2y, p3y, iy,
    collisionDetected;

// do stuff, call other functions, set endpoints...

// note: for my purpose I use |t| < |d| as opposed to
// |t| <= |d| which is equivalent to 0 <= t < 1 rather than
// 0 <= t <= 1 as in Gavin's answer - results may vary

var lineSegmentIntersection = function(){
    var d, dx1, dx2, dx3, dy1, dy2, dy3, s, t;

    dx1 = p1x - p0x;      dy1 = p1y - p0y;
    dx2 = p3x - p2x;      dy2 = p3y - p2y;
    dx3 = p0x - p2x;      dy3 = p0y - p2y;

    collisionDetected = 0;

    d = dx1 * dy2 - dx2 * dy1;

    if(d !== 0){
        s = dx1 * dy3 - dx3 * dy1;
        if((s <= 0 && d < 0 && s >= d) || (s >= 0 && d > 0 && s <= d)){
            t = dx2 * dy3 - dx3 * dy2;
            if((t <= 0 && d < 0 && t > d) || (t >= 0 && d > 0 && t < d)){
                t = t / d;
                collisionDetected = 1;
                ix = p0x + t * dx1;
                iy = p0y + t * dy1;
            }
        }
    }
};
Nolo
fonte
Eu não entendo como você pode entender o que está acontecendo com linhas gosto t = dx2 * dy3 - dx3 * dy2;...
Supuhstar
@Supuhstar Tem a ver com matemática vetorial e a definição de produto escalar e produto cruzado. Por exemplo, o código que você postou representa uma operação entre produtos. É uma maneira de projetar um segmento de linha em outro para determinar onde ele cai no outro segmento de linha, antes do ponto de partida em algum lugar no meio ou após a linha. Então t é um valor normalizado. Se estiver entre 0 e 1, os dois segmentos se cruzam. Se for menor que 0 ou maior que um, eles não o fazem.
Nolo 23/01
@Supuhstar Observe também que, para que a projeção encontre o ponto real, o resultado deve ser redimensionado. É aí que t/dentra.
Nolo
1
Quero dizer, como você entende o que está acontecendo de relance com nomes de variáveis ​​como esse? Por que não algo como crossProduct = (line1XDifference * line2YDifference) - (line2XDifference * line1YDifference)e scaledResult = crossProduct / dotProduct?
precisa saber é o seguinte
1
@Supuhstar Ah, entendo o que você quer dizer. Bem, suponho que não haja realmente uma boa razão para se falar além da obsessão pela eficiência, mas essa não é uma boa razão porque os compiladores fazem um bom trabalho ao pegar quase todo código que você fornecer e torná-lo tão eficiente quanto possível sem alterar o que deve ser calculado. Por outro lado, os nomes p1x, p1yetc. devem descrever os pontos pelos valores x e y, portanto, p1xé uma abreviação para point1x, da mesma forma d1x, na minha opinião, é uma abreviação para a letra grega, deltaXou você poderia dizer differenceInX. (mais)
Nolo 24/01
5

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)

Point3D

vem de

System.Windows.Media.Media3D

public static Point3D? Intersection(Point3D start1, Point3D end1, Point3D start2, Point3D end2) {

        double a1 = end1.Y - start1.Y;
        double b1 = start1.X - end1.X;
        double c1 = a1 * start1.X + b1 * start1.Y;

        double a2 = end2.Y - start2.Y;
        double b2 = start2.X - end2.X;
        double c2 = a2 * start2.X + b2 * start2.Y;

        double det = a1 * b2 - a2 * b1;
        if (det == 0) { // lines are parallel
            return null;
        }

        double x = (b2 * c1 - b1 * c2) / det;
        double y = (a1 * c2 - a2 * c1) / det;

        return new Point3D(x, y, 0.0);
    }

e esta é minha classe (simplificada para a resposta) BoundingBox:

public class BoundingBox {
    private Point3D min = new Point3D();
    private Point3D max = new Point3D();

    public BoundingBox(Point3D point) {
        min = point;
        max = point;
    }

    public Point3D Min {
        get { return min; }
        set { min = value; }
    }

    public Point3D Max {
        get { return max; }
        set { max = value; }
    }

    public bool Contains(BoundingBox box) {
        bool contains =
            min.X <= box.min.X && max.X >= box.max.X &&
            min.Y <= box.min.Y && max.Y >= box.max.Y &&
            min.Z <= box.min.Z && max.Z >= box.max.Z;
        return contains;
    }

    public bool Contains(Point3D point) {
        return Contains(new BoundingBox(point));
    }

}
t3chb0t
fonte
3

Esta solução pode ajudar

public static float GetLineYIntesept(PointF p, float slope)
    {
        return p.Y - slope * p.X;
    }

    public static PointF FindIntersection(PointF line1Start, PointF line1End, PointF line2Start, PointF line2End)
    {

        float slope1 = (line1End.Y - line1Start.Y) / (line1End.X - line1Start.X);
        float slope2 = (line2End.Y - line2Start.Y) / (line2End.X - line2Start.X);

        float yinter1 = GetLineYIntesept(line1Start, slope1);
        float yinter2 = GetLineYIntesept(line2Start, slope2);

        if (slope1 == slope2 && yinter1 != yinter2)
            return PointF.Empty;

        float x = (yinter2 - yinter1) / (slope1 - slope2);

        float y = slope1 * x + yinter1;

        return new PointF(x, y);
    }
yazan
fonte
3

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.

function getLineLineCollision(p0, p1, p2, p3) {
    var s1, s2;
    s1 = {x: p1.x - p0.x, y: p1.y - p0.y};
    s2 = {x: p3.x - p2.x, y: p3.y - p2.y};

    var s10_x = p1.x - p0.x;
    var s10_y = p1.y - p0.y;
    var s32_x = p3.x - p2.x;
    var s32_y = p3.y - p2.y;

    var denom = s10_x * s32_y - s32_x * s10_y;

    if(denom == 0) {
        return false;
    }

    var denom_positive = denom > 0;

    var s02_x = p0.x - p2.x;
    var s02_y = p0.y - p2.y;

    var s_numer = s10_x * s02_y - s10_y * s02_x;

    if((s_numer < 0) == denom_positive) {
        return false;
    }

    var t_numer = s32_x * s02_y - s32_y * s02_x;

    if((t_numer < 0) == denom_positive) {
        return false;
    }

    if((s_numer > denom) == denom_positive || (t_numer > denom) == denom_positive) {
        return false;
    }

    var t = t_numer / denom;

    var p = {x: p0.x + (t * s10_x), y: p0.y + (t * s10_y)};
    return p;
}
Code Monkey
fonte
2

Eu tentei de várias maneiras e então decidi escrever minha própria. Então aqui está:

bool IsBetween (float x, float b1, float b2)
{
   return ( ((x >= (b1 - 0.1f)) && 
        (x <= (b2 + 0.1f))) || 
        ((x >= (b2 - 0.1f)) &&
        (x <= (b1 + 0.1f))));
}

bool IsSegmentsColliding(   POINTFLOAT lineA,
                POINTFLOAT lineB,
                POINTFLOAT line2A,
                POINTFLOAT line2B)
{
    float deltaX1 = lineB.x - lineA.x;
    float deltaX2 = line2B.x - line2A.x;
    float deltaY1 = lineB.y - lineA.y;
    float deltaY2 = line2B.y - line2A.y;

    if (abs(deltaX1) < 0.01f && 
        abs(deltaX2) < 0.01f) // Both are vertical lines
        return false;
    if (abs((deltaY1 / deltaX1) -
        (deltaY2 / deltaX2)) < 0.001f) // Two parallel line
        return false;

    float xCol = (  (   (deltaX1 * deltaX2) * 
                        (line2A.y - lineA.y)) - 
                    (line2A.x * deltaY2 * deltaX1) + 
                    (lineA.x * deltaY1 * deltaX2)) / 
                 ((deltaY1 * deltaX2) - (deltaY2 * deltaX1));
    float yCol = 0;
    if (deltaX1 < 0.01f) // L1 is a vertical line
        yCol = ((xCol * deltaY2) + 
                (line2A.y * deltaX2) - 
                (line2A.x * deltaY2)) / deltaX2;
    else // L1 is acceptable
        yCol = ((xCol * deltaY1) +
                (lineA.y * deltaX1) -
                (lineA.x * deltaY1)) / deltaX1;

    bool isCol =    IsBetween(xCol, lineA.x, lineB.x) &&
            IsBetween(yCol, lineA.y, lineB.y) &&
            IsBetween(xCol, line2A.x, line2B.x) &&
            IsBetween(yCol, line2A.y, line2B.y);
    return isCol;
}

Com base nessas duas fórmulas: (simplifiquei-as a partir da equação de linhas e outras fórmulas)

fórmula para x

fórmula para y

Soroush Falahati
fonte
Funciona, mas tente inserir esta coordenada (se for colinear / sobreposta, retornará resultado falso): PointA1 = (0,0) PointA2 = (0,2) e PointB1 = (0,1) PointB2 = (0,5)
dns
Bem, é porque o código retorna false para linhas paralelas. Vejo o problema, no entanto, ainda não sei o que a função deve retornar, pois há um número infinito de respostas para ela.
Soroush Falahati
2

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.

//Required input point must be colinear with the line
bool on_segment(const V& p, const LineSegment& l)
{
    //If a point is on the line, the sum of the vectors formed by the point to the line endpoints must be equal
    V va = p - l.pa;
    V vb = p - l.pb;
    R ma = va.magnitude();
    R mb = vb.magnitude();
    R ml = (l.pb - l.pa).magnitude();
    R s = ma + mb;
    bool r = s <= ml + epsilon;
    return r;
}

//Compute using vector math
// Returns 0 points if the lines do not intersect or overlap
// Returns 1 point if the lines intersect
//  Returns 2 points if the lines overlap, contain the points where overlapping start starts and stop
std::vector<V> intersect(const LineSegment& la, const LineSegment& lb)
{
    std::vector<V> r;

    //http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
    V oa, ob, da, db; //Origin and direction vectors
    R sa, sb; //Scalar values
    oa = la.pa;
    da = la.pb - la.pa;
    ob = lb.pa;
    db = lb.pb - lb.pa;

    if (da.cross(db) == 0 && (ob - oa).cross(da) == 0) //If colinear
    {
        if (on_segment(lb.pa, la) && on_segment(lb.pb, la))
        {
            r.push_back(lb.pa);
            r.push_back(lb.pb);
            dprintf("colinear, overlapping\n");
            return r;
        }

        if (on_segment(la.pa, lb) && on_segment(la.pb, lb))
        {
            r.push_back(la.pa);
            r.push_back(la.pb);
            dprintf("colinear, overlapping\n");
            return r;
        }

        if (on_segment(la.pa, lb))
            r.push_back(la.pa);

        if (on_segment(la.pb, lb))
            r.push_back(la.pb);

        if (on_segment(lb.pa, la))
            r.push_back(lb.pa);

        if (on_segment(lb.pb, la))
            r.push_back(lb.pb);

        if (r.size() == 0)
            dprintf("colinear, non-overlapping\n");
        else
            dprintf("colinear, overlapping\n");

        return r;
    }

    if (da.cross(db) == 0 && (ob - oa).cross(da) != 0)
    {
        dprintf("parallel non-intersecting\n");
        return r;
    }

    //Math trick db cross db == 0, which is a single scalar in 2D.
    //Crossing both sides with vector db gives:
    sa = (ob - oa).cross(db) / da.cross(db);

    //Crossing both sides with vector da gives
    sb = (oa - ob).cross(da) / db.cross(da);

    if (0 <= sa && sa <= 1 && 0 <= sb && sb <= 1)
    {
        dprintf("intersecting\n");
        r.push_back(oa + da * sa);
        return r;
    }

    dprintf("non-intersecting, non-parallel, non-colinear, non-overlapping\n");
    return r;
}
ColacX
fonte
2

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 substituir floatpor doublese isso for melhor para suas necessidades.

Esse código é usado na minha biblioteca de física .NET, Boing .

public struct LineSegment2f
{
    public Vector2f From { get; }
    public Vector2f To { get; }

    public LineSegment2f(Vector2f @from, Vector2f to)
    {
        From = @from;
        To = to;
    }

    public Vector2f Delta => new Vector2f(To.X - From.X, To.Y - From.Y);

    /// <summary>
    /// Attempt to intersect two line segments.
    /// </summary>
    /// <remarks>
    /// Even if the line segments do not intersect, <paramref name="t"/> and <paramref name="u"/> will be set.
    /// If the lines are parallel, <paramref name="t"/> and <paramref name="u"/> are set to <see cref="float.NaN"/>.
    /// </remarks>
    /// <param name="other">The line to attempt intersection of this line with.</param>
    /// <param name="intersectionPoint">The point of intersection if within the line segments, or empty..</param>
    /// <param name="t">The distance along this line at which intersection would occur, or NaN if lines are collinear/parallel.</param>
    /// <param name="u">The distance along the other line at which intersection would occur, or NaN if lines are collinear/parallel.</param>
    /// <returns><c>true</c> if the line segments intersect, otherwise <c>false</c>.</returns>
    public bool TryIntersect(LineSegment2f other, out Vector2f intersectionPoint, out float t, out float u)
    {
        var p = From;
        var q = other.From;
        var r = Delta;
        var s = other.Delta;

        // t = (q − p) × s / (r × s)
        // u = (q − p) × r / (r × s)

        var denom = Fake2DCross(r, s);

        if (denom == 0)
        {
            // lines are collinear or parallel
            t = float.NaN;
            u = float.NaN;
            intersectionPoint = default(Vector2f);
            return false;
        }

        var tNumer = Fake2DCross(q - p, s);
        var uNumer = Fake2DCross(q - p, r);

        t = tNumer / denom;
        u = uNumer / denom;

        if (t < 0 || t > 1 || u < 0 || u > 1)
        {
            // line segments do not intersect within their ranges
            intersectionPoint = default(Vector2f);
            return false;
        }

        intersectionPoint = p + r * t;
        return true;
    }

    private static float Fake2DCross(Vector2f a, Vector2f b)
    {
        return a.X * b.Y - a.Y * b.X;
    }
}
Drew Noakes
fonte
1

Um programa C ++ para verificar se dois segmentos de linha determinados se cruzam

#include <iostream>
using namespace std;

struct Point
{
    int x;
    int y;
};

// Given three colinear points p, q, r, the function checks if
// point q lies on line segment 'pr'
bool onSegment(Point p, Point q, Point r)
{
    if (q.x <= max(p.x, r.x) && q.x >= min(p.x, r.x) &&
        q.y <= max(p.y, r.y) && q.y >= min(p.y, r.y))
       return true;

    return false;
}

// To find orientation of ordered triplet (p, q, r).
// The function returns following values
// 0 --> p, q and r are colinear
// 1 --> Clockwise
// 2 --> Counterclockwise
int orientation(Point p, Point q, Point r)
{
    // See 10th slides from following link for derivation of the formula
    // http://www.dcs.gla.ac.uk/~pat/52233/slides/Geometry1x1.pdf
    int val = (q.y - p.y) * (r.x - q.x) -
              (q.x - p.x) * (r.y - q.y);

    if (val == 0) return 0;  // colinear

    return (val > 0)? 1: 2; // clock or counterclock wise
}

// The main function that returns true if line segment 'p1q1'
// and 'p2q2' intersect.
bool doIntersect(Point p1, Point q1, Point p2, Point q2)
{
    // Find the four orientations needed for general and
    // special cases
    int o1 = orientation(p1, q1, p2);
    int o2 = orientation(p1, q1, q2);
    int o3 = orientation(p2, q2, p1);
    int o4 = orientation(p2, q2, q1);

    // General case
    if (o1 != o2 && o3 != o4)
        return true;

    // Special Cases
    // p1, q1 and p2 are colinear and p2 lies on segment p1q1
    if (o1 == 0 && onSegment(p1, p2, q1)) return true;

    // p1, q1 and p2 are colinear and q2 lies on segment p1q1
    if (o2 == 0 && onSegment(p1, q2, q1)) return true;

    // p2, q2 and p1 are colinear and p1 lies on segment p2q2
    if (o3 == 0 && onSegment(p2, p1, q2)) return true;

     // p2, q2 and q1 are colinear and q1 lies on segment p2q2
    if (o4 == 0 && onSegment(p2, q1, q2)) return true;

    return false; // Doesn't fall in any of the above cases
}

// Driver program to test above functions
int main()
{
    struct Point p1 = {1, 1}, q1 = {10, 1};
    struct Point p2 = {1, 2}, q2 = {10, 2};

    doIntersect(p1, q1, p2, q2)? cout << "Yes\n": cout << "No\n";

    p1 = {10, 0}, q1 = {0, 10};
    p2 = {0, 0}, q2 = {10, 10};
    doIntersect(p1, q1, p2, q2)? cout << "Yes\n": cout << "No\n";

    p1 = {-5, -5}, q1 = {0, 0};
    p2 = {1, 1}, q2 = {10, 10};
    doIntersect(p1, q1, p2, q2)? cout << "Yes\n": cout << "No\n";

    return 0;
}
Ayush Srivastava
fonte
1

Com base na resposta @Gareth Rees, versão para Python:

import numpy as np

def np_perp( a ) :
    b = np.empty_like(a)
    b[0] = a[1]
    b[1] = -a[0]
    return b

def np_cross_product(a, b):
    return np.dot(a, np_perp(b))

def np_seg_intersect(a, b, considerCollinearOverlapAsIntersect = False):
    # /programming/563198/how-do-you-detect-where-two-line-segments-intersect/565282#565282
    # http://www.codeproject.com/Tips/862988/Find-the-intersection-point-of-two-line-segments
    r = a[1] - a[0]
    s = b[1] - b[0]
    v = b[0] - a[0]
    num = np_cross_product(v, r)
    denom = np_cross_product(r, s)
    # If r x s = 0 and (q - p) x r = 0, then the two lines are collinear.
    if np.isclose(denom, 0) and np.isclose(num, 0):
        # 1. If either  0 <= (q - p) * r <= r * r or 0 <= (p - q) * s <= * s
        # then the two lines are overlapping,
        if(considerCollinearOverlapAsIntersect):
            vDotR = np.dot(v, r)
            aDotS = np.dot(-v, s)
            if (0 <= vDotR  and vDotR <= np.dot(r,r)) or (0 <= aDotS  and aDotS <= np.dot(s,s)):
                return True
        # 2. If neither 0 <= (q - p) * r = r * r nor 0 <= (p - q) * s <= s * s
        # then the two lines are collinear but disjoint.
        # No need to implement this expression, as it follows from the expression above.
        return None
    if np.isclose(denom, 0) and not np.isclose(num, 0):
        # Parallel and non intersecting
        return None
    u = num / denom
    t = np_cross_product(v, s) / denom
    if u >= 0 and u <= 1 and t >= 0 and t <= 1:
        res = b[0] + (s*u)
        return res
    # Otherwise, the two line segments are not parallel but do not intersect.
    return None
Ibraim Ganiev
fonte
0

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.

Harper Shelby
fonte
3
Observe que essa foi uma resposta razoável para a pergunta como originalmente enquadrada, mas agora que a pergunta foi editada intensamente, não faz muito sentido.
GS - Desculpe-se com Monica
0

Com base na resposta de t3chb0t:

int intersezione_linee(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, int& p_x, int& p_y)
{
   //L1: estremi (x1,y1)(x2,y2) L2: estremi (x3,y3)(x3,y3)
   int d;
   d = (x1-x2)*(y3-y4) - (y1-y2)*(x3-x4);
   if(!d)
       return 0;
   p_x = ((x1*y2-y1*x2)*(x3-x4) - (x1-x2)*(x3*y4-y3*x4))/d;
   p_y = ((x1*y2-y1*x2)*(y3-y4) - (y1-y2)*(x3*y4-y3*x4))/d;
   return 1;
}

int in_bounding_box(int x1, int y1, int x2, int y2, int p_x, int p_y)
{
    return p_x>=x1 && p_x<=x2 && p_y>=y1 && p_y<=y2;

}

int intersezione_segmenti(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, int& p_x, int& p_y)
{
    if (!intersezione_linee(x1,y1,x2,y2,x3,y3,x4,y4,p_x,p_y))
        return 0;

    return in_bounding_box(x1,y1,x2,y2,p_x,p_y) && in_bounding_box(x3,y3,x4,y4,p_x,p_y);
}
volperossa
fonte
0

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.

Mass Zhou
fonte
0

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/

var point_a = {x:0, y:10},
    point_b = {x:12, y:12},
    point_c = {x:10, y:0},
    point_d = {x:0, y:0},
    slope_ab = slope(point_a, point_b),
    slope_bc = slope(point_b, point_c),
    slope_cd = slope(point_c, point_d),
    slope_da = slope(point_d, point_a),
    yint_ab = y_intercept(point_a, slope_ab),
    yint_bc = y_intercept(point_b, slope_bc),
    yint_cd = y_intercept(point_c, slope_cd),
    yint_da = y_intercept(point_d, slope_da),
    xint_ab = x_intercept(point_a, slope_ab, yint_ab),
    xint_bc = x_intercept(point_b, slope_bc, yint_bc),
    xint_cd = x_intercept(point_c, slope_cd, yint_cd),
    xint_da = x_intercept(point_d, slope_da, yint_da),
    point_aa = intersect(slope_da, yint_da, xint_da, slope_ab, yint_ab, xint_ab),
    point_bb = intersect(slope_ab, yint_ab, xint_ab, slope_bc, yint_bc, xint_bc),
    point_cc = intersect(slope_bc, yint_bc, xint_bc, slope_cd, yint_cd, xint_cd),
    point_dd = intersect(slope_cd, yint_cd, xint_cd, slope_da, yint_da, xint_da);

console.log(point_a, point_b, point_c, point_d);
console.log(slope_ab, slope_bc, slope_cd, slope_da);
console.log(yint_ab, yint_bc, yint_cd, yint_da);
console.log(xint_ab, xint_bc, xint_cd, xint_da);
console.log(point_aa, point_bb, point_cc, point_dd);

function slope(point_a, point_b) {
  var i = (point_b.y - point_a.y) / (point_b.x - point_a.x);
  if (i === -Infinity) return Infinity;
  if (i === -0) return 0;
  return i;
}

function y_intercept(point, slope) {
    // Horizontal Line
    if (slope == 0) return point.y;
  // Vertical Line
    if (slope == Infinity)
  {
    // THE Y-Axis
    if (point.x == 0) return Infinity;
    // No Intercept
    return null;
  }
  // Angled Line
  return point.y - (slope * point.x);
}

function x_intercept(point, slope, yint) {
    // Vertical Line
    if (slope == Infinity) return point.x;
  // Horizontal Line
    if (slope == 0)
  {
    // THE X-Axis
    if (point.y == 0) return Infinity;
    // No Intercept
    return null;
  }
  // Angled Line
  return -yint / slope;
}

// Intersection of two infinite lines
function intersect(slope_a, yint_a, xint_a, slope_b, yint_b, xint_b) {
  if (slope_a == slope_b)
  {
    // Equal Lines
    if (yint_a == yint_b && xint_a == xint_b) return Infinity;
    // Parallel Lines
    return null;
  }
  // First Line Vertical
    if (slope_a == Infinity)
  {
    return {
        x: xint_a,
      y: (slope_b * xint_a) + yint_b
    };
  }
  // Second Line Vertical
    if (slope_b == Infinity)
  {
    return {
        x: xint_b,
      y: (slope_a * xint_b) + yint_a
    };
  }
  // Not Equal, Not Parallel, Not Vertical
  var i = (yint_b - yint_a) / (slope_a - slope_b);
  return {
    x: i,
    y: (slope_a * i) + yint_a
  };
}
skibulk
fonte