Primeiro de tudo, no caso de retângulos alinhados ao eixo, a resposta de Kevin Reid é a melhor e o algoritmo é o mais rápido.
Segundo, para formas simples, use velocidades relativas (como visto abaixo) e o teorema do eixo separador para detecção de colisão. Ele vai dizer se uma colisão acontece no caso de movimento linear (sem rotação). E se houver rotação, você precisará de um pequeno intervalo de tempo para que seja preciso. Agora, para responder à pergunta:
Como saber no caso geral se duas formas convexas se cruzam?
Vou lhe dar um algoritmo que funciona para todas as formas convexas e não apenas hexágonos.
Suponha que X e Y são duas formas convexas. Eles cruzam se e somente se tiverem um ponto em comum, ou seja, existe um ponto x ∈ X e um ponto y ∈ Y tal que x = y . Se você considerar o espaço como um espaço vetorial, isso significa dizer x - y = 0 . E agora chegamos a esse negócio de Minkowski:
A soma Minkowski de X e Y é o conjunto de todos os x + y para x ∈ X e Y ∈ Y .
Um exemplo para X e Y
X, Y e sua soma de Minkowski, X + Y
Supondo que (-Y) é o conjunto de todos -y para y ∈ Y , dado o parágrafo anterior, X e Y se cruzam se e somente se X + (-Y) contiver 0 , ou seja, a origem .
Observação lateral: por que escrevo X + (-Y) em vez de X - Y ? Bem, porque em matemática há uma operação chamada diferença de Minkowski de A e B, que às vezes é escrita X - Y, mas não tem nada a ver com o conjunto de todos os x - y para x ∈ X e y ∈ Y (o verdadeiro Minkowski diferença é um pouco mais complexa).
Então, gostaríamos de calcular a soma de Minkowski de X e -Y e descobrir se ela contém a origem. A origem não é especial em comparação com qualquer outro ponto; portanto, para descobrir se a origem está dentro de um determinado domínio, usamos um algoritmo que pode nos dizer se algum ponto específico pertence a esse domínio.
A soma de Minkowski de X e Y tem uma propriedade legal, que é se X e Y são convexos, então X + Y também é. E descobrir se um ponto pertence a um conjunto convexo é muito mais fácil do que se esse conjunto não fosse (conhecido por ser) convexo.
Não podemos calcular todos os x - y para x ∈ X e y ∈ Y porque há uma infinidade desses pontos x e y , então, esperançosamente, já que X , Y e X + Y são convexos, podemos apenas usar os pontos "mais externos" que definem as formas X e Y , que são seus vértices, e obteremos os pontos mais externos de X + Y e também mais alguns.
Esses pontos adicionais são "cercados" pelos pontos mais externos de X + Y, para que não contribuam para definir a forma convexa recém-obtida. Dizemos que eles não definem o " casco convexo " do conjunto de pontos. Então, o que fazemos é nos livrar deles em preparação para o algoritmo final que nos diz se a origem está dentro do casco convexo.
O casco convexo de X + Y. Removemos os vértices "internos".
Portanto, temos
Um primeiro algoritmo ingênuo
boolean intersect(Shape X, Shape Y) {
SetOfVertices minkowski = new SetOfVertices();
for (Vertice x in X) {
for (Vertice y in Y) {
minkowski.addVertice(x-y);
}
}
return contains(convexHull(minkowski), Vector2D(0,0));
}
Os loops obviamente têm complexidade O (mn), onde m e n são o número de vértices de cada forma. O minkoswki
conjunto contém no máximo elementos mn . O convexHull
algoritmo tem uma complexidade que depende do algoritmo usado e você pode apontar para O (k log (k)) em que k é o tamanho do conjunto de pontos; portanto, no nosso caso, obtemos O (mn log (mn) ) . O contains
algoritmo tem uma complexidade que é linear com o número de arestas (em 2D) ou faces (em 3D) do casco convexo, portanto depende realmente de suas formas iniciais, mas não será maior que O (mn) .
Vou deixar você procurar no google pelo contains
algoritmo para formas convexas, é bem comum. Posso colocá-lo aqui se tiver tempo.
Mas estamos fazendo a detecção de colisões, para que possamos otimizar muito
Originalmente, tínhamos dois corpos A e B se movendo sem rotação durante um intervalo de tempo dt (pelo que posso ver olhando suas fotos). Vamos chamar v A e v B as respectivas velocidades de A e B , que são constantes durante nosso timestep de duração dt . Temos o seguinte:
e, como você mostra nas fotos, esses corpos varrem áreas (ou volumes, em 3D) à medida que se movem:
e eles acabam como A ' e B' após o timestep.
Para aplicar nosso algoritmo ingênuo aqui, precisaríamos apenas calcular os volumes varridos. Mas não estamos fazendo isso.
No quadro de referência de B , B não se move (duh!). E A tem uma certa velocidade em relação a B que você obtém ao calcular v A - v B (você pode fazer o inverso, calcular a velocidade relativa de B no quadro de referência de A ).
Da esquerda para a direita: velocidades no quadro de referência base; velocidades relativas; computando velocidades relativas.
Por respeito B tão imóvel em seu próprio quadro de referência, você só tem que calcular o volume que A varre através de como ele se move durante dt com sua relativa velocidade v A - v B .
Isso diminui o número de vértices a serem usados no cálculo da soma de Minkowski (às vezes bastante).
Outra possível otimização é no ponto em que você calcula o volume varrido por um dos corpos, digamos A. Você não precisa traduzir todos os vértices que compõem A. Somente aqueles que pertencem às arestas (faces em 3D) cujas exterior normal "face" a direção da varredura. Certamente você já percebeu isso quando calculou suas áreas varridas pelos quadrados. Você pode dizer se um normal está na direção da varredura usando seu produto escalar com a direção da varredura, que deve ser positiva.
A última otimização, que não tem nada a ver com sua pergunta sobre interseções, é realmente útil no nosso caso. Ele usa as velocidades relativas mencionadas e o chamado método do eixo de separação. Certamente você já sabe disso.
Suponha que você conheça os raios de A e B em relação aos seus centros de massa (ou seja, a distância entre o centro de massa e o vértice mais distante), assim:
Uma colisão pode ocorrer somente se é possível que o círculo delimitadora de uma encontram a de B . Vemos aqui que ele não vai, ea maneira de dizer ao computador que é calcular a distância C B para I como na imagem a seguir e certifique-se que é maior do que a soma dos raios de A e B . Se for maior, sem colisão. Se for menor, colisão.
Isso não funciona muito bem com formas bastante longas, mas no caso de quadrados ou outras formas semelhantes, é uma heurística muito boa excluir a colisão .
O teorema do eixo de separação aplicado a B e o volume varrido por A , no entanto, informa se a colisão ocorre. A complexidade do algoritmo associado é linear com a soma dos números de vértices de cada forma convexa, mas é menos mágico quando chega a hora de realmente lidar com a colisão.
Nosso novo e melhor algoritmo que usa interseções para ajudar a detectar colisões, mas ainda não é tão bom quanto o teorema do eixo separador para dizer se uma colisão acontece
boolean mayCollide(Body A, Body B) {
Vector2D relativeVelocity = A.velocity - B.velocity;
if (radiiHeuristic(A, B, relativeVelocity)) {
return false; // there is a separating axis between them
}
Volume sweptA = sweptVolume(A, relativeVelocity);
return contains(convexHull(minkowskiMinus(sweptA, B)), Vector2D(0,0));
}
boolean radiiHeuristic(A, B, relativeVelocity)) {
// the code here
}
Volume convexHull(SetOfVertices s) {
// the code here
}
boolean contains(Volume v, Vector2D p) {
// the code here
}
SetOfVertices minkowskiMinus(Body X, Body Y) {
SetOfVertices result = new SetOfVertices();
for (Vertice x in X) {
for (Vertice y in Y) {
result.addVertice(x-y);
}
}
return result;
}