Como detectar linha 2D em colisão de linha?

13

Eu sou um desenvolvedor de jogos em flash que é um pouco atrasado em matemática, embora eu ache a física interessante e legal.

Para referência, este é um jogo semelhante ao que estou fazendo: Jogo em flash desembaraçado

Eu fiz esse jogo desembaraçado quase até a conclusão completa da lógica. Porém, quando duas linhas se cruzam, preciso que as linhas entrelaçadas ou 'emaranhadas' mostrem uma cor diferente; vermelho.

Seria muito gentil da sua parte se você pudesse sugerir um algoritmo para detectar colisões de segmentos de linha . Eu sou basicamente uma pessoa que gosta de pensar 'visualmente' do que 'aritmeticamente' :)

Editar: gostaria de adicionar alguns diagramas para tornar a ideia mais clara

sem interseção sem interseção interseção sem interseção

PS Estou tentando fazer uma função como

private function isIntersecting(A:Point, B:Point, C:Point, D:Point):Boolean

Desde já, obrigado.

Vishnu
fonte
6
Esta é uma explicação decepcionante não-visual do problema, mas é um algoritmo e faz sentido se você pode trazer-te a ler as suas matemática: local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d Pode seja pesado se a matemática do seu vetor for fraca. Eu entendo - eu também prefiro explicações visuais. Tentarei encontrar tempo mais tarde para rabiscar isso, mas se alguém de alguma inclinação artística visualizar esse link e tiver tempo antes de mim, chegue a ele!
Anko

Respostas:

18

Eu uso o método a seguir, que é praticamente uma implementação desse algoritmo . Está em C #, mas a tradução para o ActionScript deve ser trivial.

bool IsIntersecting(Point a, Point b, Point c, Point d)
{
    float denominator = ((b.X - a.X) * (d.Y - c.Y)) - ((b.Y - a.Y) * (d.X - c.X));
    float numerator1 = ((a.Y - c.Y) * (d.X - c.X)) - ((a.X - c.X) * (d.Y - c.Y));
    float numerator2 = ((a.Y - c.Y) * (b.X - a.X)) - ((a.X - c.X) * (b.Y - a.Y));

    // Detect coincident lines (has a problem, read below)
    if (denominator == 0) return numerator1 == 0 && numerator2 == 0;

    float r = numerator1 / denominator;
    float s = numerator2 / denominator;

    return (r >= 0 && r <= 1) && (s >= 0 && s <= 1);
}

Há um problema sutil com o algoritmo, que é o caso em que duas linhas são coincidentes, mas não se sobrepõem. O algoritmo ainda retorna uma interseção nesse caso. Se você se importa com esse caso, acredito que esta resposta no stackoverflow tenha uma versão mais complexa que a aborda.

Editar

Não obtive um resultado desse algoritmo, desculpe!

Estranho, eu testei e está funcionando para mim, exceto no único caso que descrevi acima. Usando exatamente a mesma versão que publiquei acima, obtive esses resultados quando o testei:

insira a descrição da imagem aqui

David Gouveia
fonte
Não obtive um resultado desse algoritmo, desculpe!
Vishnu
4
@ Vish Que problema você teve? Testei essa cópia exata do algoritmo antes de postar e ele funcionou perfeitamente, exceto no caso único descrito.
David Gouveia
Então, deixe-me tentar novamente, eu posso ter misturado um pouco de matemática. Avisarei em breve. Muito obrigado, nyways :)
Vishnu
1
Obtive o resultado desejado do seu algoritmo, obrigado @DavidGouveia.
Vishnu
1
Bem, mas agora eu tenho outro problema :)! Eu preciso fazer as linhas cruzadas em vermelho e verde. A interseção funciona bem. Mas como eu entendi agora (embora não matematicamente) que um simples if-else não funcionará como colocar linhas vermelhas e verdes para linhas cruzadas e não cruzadas. O nó que estou arrastando tem uma linha esquerda e direita. Então, algo deu errado em algum lugar ao mudar a cor das linhas não cruzadas de volta para verde. Acho que preciso de outra condição também. Hmmm, de qualquer forma, graças a uma tonelada, estou marcando isso como a resposta correta.
Vishnu
4

Sem divisões! Portanto, não há problema com precisão nem por divisão por zero.

O segmento de linha 1 é de A a B O segmento de linha 2 é de C a D

Uma linha é uma linha sem fim, o segmento de linha é uma parte definida dessa linha.

Verifique se as duas caixas delimitadoras se cruzam: se não houver interseção -> Sem cruz! (cálculo feito, retorno falso)

Verifique se a linha seg 1 ultrapassa a linha seg 2 e se a linha seg 2 ultrapassa a linha seg 1 (ou seja, o segmento 1 da linha está nos dois lados da linha definida pelo segmento 2).

Isso pode ser feito traduzindo todos os pontos por -A (ou seja, você move as 2 linhas para que A fique em origo (0,0))

Então você verifica se os pontos C e D estão em lados diferentes da linha definida por 0,0 a B

//Cross Product (hope I got it right here)
float fC= (B.x*C.y) - (B.y*C.x); //<0 == to the left, >0 == to the right
float fD= (B.x*D.y) - (B.y*D.x);

if( (fc<0) && (fd<0)) //both to the left  -> No Cross!
if( (fc>0) && (fd>0)) //both to the right -> No Cross!

Se você ainda não recebeu um "No Cross", continue usando A, B versus C, D, mas C, D versus A, B (os mesmos cálculos, apenas troque A e C, B e D), se não houver "Sem cruz!" então você tem um cruzamento!

Pesquisei os cálculos exatos para o produto cruzado e encontrei Este post do blog que explica o método também.

Valmond
fonte
1
Sinto muito, mas não sou muito bom com matemática vetorial, implementei esse algoritmo como tal, mas não obtive resultado, desculpe!
Vishnu
1
Deve funcionar, então, se você puder nos mostrar seu código, podemos ajudá-lo por aí?
Valmond
Agradável! no entanto, a ligação é interrompida
clabe45
Há algo que você possa adicionar a isso para obter o ponto de interseção?
21719 SeanRamey
1

Eu só quero dizer que eu precisava dele para o meu jogo Gamemaker Studio e funciona bem:

///scr_line_collision(x1,y1,x2,y2,x3,y3,x4,y4)

var denominator= ((argument2 - argument0) * (argument7 - argument5)) - ((argument3 - argument1) * (argument6 - argument4));
var numerator1 = ((argument1 - argument5) * (argument6 - argument4)) - ((argument0 - argument4) * (argument7 - argument5));
var numerator2 = ((argument1 - argument5) * (argument2 - argument0)) - ((argument0 - argument4) * (argument3 - argument1));

// Detect coincident lines
if (denominator == 0) {return (numerator1 == 0 && numerator2 == 0)}

var r = numerator1 / denominator;
var s = numerator2 / denominator;

return ((r >= 0 && r <= 1) && (s >= 0 && s <= 1));
Lukáš Čulen
fonte
Eu acho que essa resposta poderia realmente melhorar se você explicasse o que o código faz.
TomTsagk
1

A resposta aceita deu uma resposta errada neste caso:

x1 = 0;
y1 = 0;
x2 = 10;
y2 = 10;

x3 = 10.1;
y3 = 10.1;
x4 = 15;
y4 = 15;

Essas linhas obviamente não se cruzam, mas de acordo com a função na "resposta correta" as linhas se cruzam.

Isto é o que eu uso:

function do_lines_intersect(px1,py1,px2,py2,px3,py3,px4,py4) {
  var ua = 0.0;
  var ub = 0.0;
  var ud = (py4 - py3) * (px2 - px1) - (px4 - px3) * (py2 - py1);


  if (ud != 0) {
    ua = ((px4 - px3) * (py1 - py3) - (py4 - py3) * (px1 - px3)) / ud;
    ub = ((px2 - px1) * (py1 - py3) - (py2 - py1) * (px1 - px3)) / ud;
        if (ua < 0.0 || ua > 1.0 || ub < 0.0 || ub > 1.0) ua = 0.0;
  }

  return ua;
}

retorna 0 = as linhas não se cruzam

retorna> 0 = as linhas se cruzam


Atualize para responder à pergunta:

Eu não criei esse código pessoalmente. Ele tem mais de 5 anos e não sei qual é a fonte original. Mas..

Eu acho que o valor de retorno é a posição relativa da primeira linha onde eles cruzam (para explicar mal). Para calcular o ponto de interseção, você provavelmente poderia usar o lerp assim:

l = do_lines_intersect(...)
if (l > 0) {
    intersect_pos_x = l * (px2-px1);
    intersect_pos_y = l * (py2-py1);
} else {
    // lines do not cross
}

(NÃO TESTEI ISSO)

Jorammer
fonte
Existe uma versão disso que retorna o ponto de interseção?
SeanRamey 19/06/19