Como detecto a direção das colisões retangulares de objetos 2D?

11

Após esta pergunta , preciso de mais ajuda.

Como posso descobrir de que lado de um retângulo a colisão veio e reagir de acordo?

retângulos colididos com todos os lados

As setas azuis são os caminhos que alguns objetos circulares seguiriam se antes e depois da colisão com a caixa.

Como posso calcular isso?

NemoStein
fonte

Respostas:

8

Como isso se baseia em sua outra pergunta, darei uma solução para quando o retângulo estiver alinhado ao eixo.

Primeiro, você constrói o retângulo do objeto atual com os seguintes valores:

int boxLeft = box.X;
int boxRight = boxLeft + box.Width;
int boxTop = box.Y;
int boxBottom = boxTop + box.Height;

Em seguida, você deve ter a posição do objeto antigo (que você pode armazenar em cada objeto ou simplesmente passar para uma função) para criar o retângulo do objeto antigo (quando não estava colidindo):

int oldBoxLeft = box.OldX;
int oldBoxRight = oldBoxLeft + box.Width;
int oldBoxTop = box.OldY;
int oldBoxBottom = oldBoxTop + box.Height;

Agora, para saber de onde a colisão ocorreu, você deve encontrar o lado em que a posição antiga não estava na área de colisão e onde está a nova posição. Porque, quando você pensa sobre isso, é o que acontece quando você colide: um lado que não estava colidindo entra em outro retângulo.

Aqui está como você pode fazer isso (essas funções assumem que há uma colisão. Elas não devem ser chamadas se não houver colisão):

 bool collidedFromLeft(Object otherObj)
{
    return oldBoxRight < otherObj.Left && // was not colliding
           boxRight >= otherObj.Left;
}

Rince e repita.

bool collidedFromRight(Object otherObj)
{
    return oldBoxLeft >= otherObj.Right && // was not colliding
           boxLeft < otherObj.Right;
}

bool collidedFromTop(Object otherObj)
{
    return oldBoxBottom < otherObj.Top && // was not colliding
           boxBottom >= otherObj.Top;
}

bool collidedFromBottom(Object otherObj)
{
    return oldBoxTop >= otherObj.Bottom && // was not colliding
           boxTop < otherObj.Bottom;
}

Agora, para o uso real com a resposta de colisão da outra pergunta:

if (collidedFromTop(otherObj) || collidedFromBottom(otherObj))
    obj.Velocity.Y = -obj.Velocity.Y;
if (collidedFromLeft(otherObj) || collidedFromRight(otherObj))
    obj.Velocity.X = -obj.Velocity.X;

Novamente, essa pode não ser a melhor solução, mas é assim que costumo seguir para a detecção de colisões.

Jesse Emond
fonte
Mais uma vez, você estava certo! ; D Obrigado ... (envie-me mais cartões postais do seu quadro da próxima vez ... ^ ___ ^) #
NemoStein
Ahhh, infelizmente, eu não sabia o que poderia ter usado .. talvez da próxima vez!
precisa
7

Como a pergunta é parcialmente idêntica a esta , reutilizarei algumas partes da minha resposta para tentar responder à sua pergunta.


Vamos definir um contexto e algumas variáveis ​​para tornar a explicação a seguir mais compreensível. A forma de representação que usaremos aqui provavelmente não se encaixa na forma de seus próprios dados, mas deve ser mais simples de entender dessa maneira (de fato, você pode usar os métodos a seguir usando outros tipos de representação depois de entender o princípio)

Portanto, consideraremos uma caixa delimitadora alinhada ao eixo (ou uma caixa delimitada orientada ) e uma entidade em movimento .

  • A caixa delimitadora é composta por 4 lados, e definiremos cada um como:
    Lado1 = [x1, y1, x2, y2] (dois pontos [x1, y1] e [x2, y2])

  • A Entidade em movimento é definida como um vetor de velocidade (posição + velocidade):
    uma posição [posX, posY] e uma velocidade [ velocidadeX , velocidadeY] .


Você pode determinar qual lado de um AABB / OBB é atingido por um vetor usando o seguinte método:

  • 1 / Encontre os pontos de interseção entre as linhas infinitas que passam pelos quatro lados do AABB e a linha infinita que passa pela posição da entidade (pré-colisão) que usa o vetor de velocidade da entidade como slop. (Você pode encontrar um ponto de colisão ou um número indefinido, que corresponde a paralelos ou linhas sobrepostas)

  • 2 / Depois de conhecer os pontos de interseção (se existirem), você pode procurar aqueles que estão dentro dos limites do segmento.

  • 3 / Finalmente, se ainda houver vários pontos na lista (o vetor de velocidade pode passar por vários lados), você pode procurar o ponto mais próximo da origem da entidade usando as magnitudes do vetor da interseção à origem da entidade.

Em seguida, você pode determinar o ângulo de colisão usando um produto de ponto simples.

  • 4 / Encontre o ângulo entre a colisão usando um produto escalar do vetor entidade (provavelmente uma bola?) Com o vetor do lado da batida.

----------

Mais detalhes:

  • 1 / Encontrar interseções

    • a / Determine as linhas infinitas (Ax + Bx = D) usando suas formas paramétricas (P (t) = Po + tD).

      Origem do ponto: Po = [posX, posY]
      Vetor de direção: D = [velocidadeX, velocidadeY]

      A = Dy = velocidadeY
      B = -Dx = -velocidadeX
      D = (Po.x * Dy) - (Po.y * Dx) = (posX velocidadeY) - (posY velocidadeX)

      Ax + Por = D <====> (velocidadeY x) + (-velocidadeX y) = (posX velocidadeY) - (posY velocidadeX)

      Usei os valores dos pontos de entidade para ilustrar o método, mas este é exatamente o mesmo método para determinar as 4 linhas infinitas laterais da caixa delimitadora (Use Po = [x1, y1] e D = [x2-x1; y2-y1] em vez de).

    • b / Em seguida, para encontrar a interseção de duas linhas infinitas, podemos resolver o seguinte sistema:

      A1x + B1x = D1 <== Linha que passa pelo ponto de entidade com o vetor de velocidade como slop.
      A2x + B2x = D2 <== Uma das linhas que passam pelos lados da AABB.

      que produz as seguintes coordenadas para a interceptação:

      Intercepção x = (( B 2 * D 1) - ( B 1 * D 2)) / (( A 1 * B 2) - ( A 2 * B 1))
      Interceptação y = (( A 1 * D 2) - ( A 2 * D 1)) / (( A 1 * B 2) - ( A 2 * B 1))

      Se o denominador ((A1 * B2) - (A2 * B1)) for igual a zero, as duas linhas serão paralelas ou sobrepostas; caso contrário, você deverá encontrar uma interseção.

  • 2 / Teste para os limites do segmento. Como isso é fácil de verificar, não há necessidade de mais detalhes.

  • 3 / Procure o ponto mais próximo. Se ainda houver vários pontos na lista, podemos encontrar qual lado é o mais próximo ao ponto de origem da entidade.

    • a / Determine o vetor que vai do ponto de interseção ao ponto de origem da entidade

      V = Po - Int = [Po.x - Int.x; Po.y - Int.y]

    • b / Calcule a magnitude do vetor

      || V || = sqrt (V.x² + V.y²)

    • c / encontre o menor.
  • 4 / Agora que você sabe qual lado será atingido, é possível determinar o ângulo usando um produto escalar.

    • a / Seja S = [x2-x1; y2-y1] seja o vetor lateral que será atingido e E = [speedX; speedY] seja o vetor de velocidade da entidade.

      Usando a regra do produto com pontos vetoriais, sabemos que

      S · E = Sx Ex + Sy Ey
      e
      S · E = || S || || E || cos θ

      Então, podemos determinar θ manipulando um pouco essa equação ...

      cos θ = (S · E) / (|| S || || E ||)

      θ = acos ((S · E) / (|| S || || E ||))

      com

      S · E = Sx * Ex + Sy * Ey
      || S || = sqrt (Sx² + Sy²)
      || E || = sqrt (Ex² + Ey²)


Nota: Como eu disse no outro tópico de perguntas, essa provavelmente não é a maneira mais eficiente e nem a mais simples de fazê-lo; é exatamente isso que vem à mente, e alguma parte da matemática pode ajudar.

Não verifiquei com um exemplo concreto de OBB (fiz com um AABB), mas deve funcionar também.

Valkea
fonte
6

Uma maneira simples é resolver a colisão, depois converter a caixa de colisão do objeto em movimento em um pixel em cada direção uma por vez e ver quais resultam em colisão.

Se você quiser fazê-lo "adequadamente" e com formas de colisão rotacionadas ou polígonos arbitrários, sugiro a leitura do Teorema do Eixo Separador. O software Metanet (as pessoas que criaram o jogo N), por exemplo, tem um incrível artigo sobre desenvolvimento no SAT . Eles também discutem a física envolvida.

Anko
fonte
2

Uma maneira seria girar o mundo em torno do seu retângulo. "O mundo", neste caso, são apenas os objetos de que você gosta: o retângulo e a bola. Você gira o retângulo em torno de seu centro até que seus limites estejam alinhados com os eixos x / y e depois gira a bola na mesma quantidade.

O ponto importante aqui é que você gira a bola ao redor do centro do retângulo, não o seu.

Em seguida, você pode testar facilmente a colisão, como faria com qualquer outro retângulo não girado.


Outra opção é tratar o retângulo como quatro segmentos de linha distintos e testar a colisão com cada um deles separadamente. Isso permite testar a colisão e descobrir qual lado foi colidido ao mesmo tempo.

BlueRaja - Danny Pflughoeft
fonte
1

Usei ângulos fixos em meus cálculos, mas isso deve ajudá-lo

void Bullet::Ricochet(C_Rect *r)
{
    C_Line Line;
    //the next two lines are because I detected 
    // a collision in my main loop so I need to take a step back.

    x = x + ceil(speed * ((double)fcos(itofix(angle)) / 65536));
    y = y + ceil(speed * ((double)fsin(itofix(angle)) / 65536));
    C_Point Prev(x,y);

    //the following checks our position to all the lines will give us
    // an answer which line we will hit due to no lines
    // with angles > 90 lines of a rect always shield the other lines.

    Line = r->Get_Closest_Line(Prev);    
    int langle = 0;
    if(!Line.Is_Horizontal())   //we need to rotate the line to a horizontal position
    {
        langle = Line.Get_Point1().Find_Fixed_Angle(Line.Get_Point2());
        angle = angle - langle;  //to give us the new angle of approach
    }
    //at this point the line is horizontal and the bullet is ready to be fixed.
    angle = 256 - angle;
    angle += langle;
}
David Sopala
fonte