Círculo dentro da colisão do círculo

9

Em um dos meus projetos, tenho uma área de jogo na forma de um círculo. Dentro deste círculo, outro pequeno círculo está se movendo. O que eu quero fazer é impedir que o pequeno círculo se mova para fora do maior. Abaixo, você pode ver que, no quadro 2, o pequeno círculo está parcialmente fora, preciso de uma maneira de recolocá-lo antes que ele esteja prestes a sair. Como isso pode ser feito?

Exemplo básico

Além disso, preciso do ponto de colisão ao longo do arco do grande círculo para poder atualizar a velocidade do pequeno círculo. Como alguém calcularia esse ponto?

O que eu gostaria de fazer é antes de mover o pequeno círculo, prevejo sua próxima posição e, se estiver fora, encontro o tempo de colisão entre t = 0 et = 1 (t = 1 etapa em tempo integral). Se eu tiver o tempo de colisão t, basta mover o pequeno círculo durante t em vez de uma etapa em tempo integral. Mas, novamente, o problema é que não sei como detectar naquele momento a colisão quando se trata de dois círculos e um dentro do outro.

EDITAR:

Exemplo de ponto de colisão (verde) que desejo encontrar. Talvez a imagem esteja um pouco errada, mas você entendeu.

insira a descrição da imagem aqui

dbostream
fonte

Respostas:

10

Vamos supor que o círculo grande tenha centro Ae raio Re o pequeno círculo tenha centro Be raio se rmovendo em direção à localização C.

Existe uma maneira elegante de resolver esse problema, usando somas de Minkovski (subtrações, na verdade): substitua o disco de raio Rpor um disco de raio R-re o disco de raio rpor um disco de raio 0, ou seja. um ponto simples localizado em B. O problema se torna um problema de interseção de círculo de linha.

Você só precisa verificar se a distância ACé menor que R-r. Se for, os círculos não colidem. Se for maior, basta encontrar o ponto Dem BCuma distância R-rde Ae esta é a nova localização do centro de seu pequeno círculo. Isso é equivalente a encontrar ktais que:

  vec(BD) = k*vec(BC)
and
  norm(vec(AD)) = R-r

Substituindo vec(AD)por vec(AB) + vec(BD)dá:

AB² + k² BC² + 2k vec(AB).vec(BC) = (R-r

Desde que a posição inicial estivesse dentro do círculo maior, essa equação quadrática em ktem uma raiz positiva. Aqui está como resolver a equação, no pseudocódigo:

b = - vec(AB).vec(BC) / BC²    // dot product
c = (AB² - (R-r)²) / BC²
d = b*b - c
k = b - sqrt(d)
if (k < 0)
    k = b + sqrt(d)
if (k < 0)
    // no solution! we must be slightly out of the large circle

Com esse valor de k, o novo centro do pequeno círculo é Dtal que BD = kBC.

Editar : adicionar solução de equação quadrática

sam hocevar
fonte
Obrigado, parece elegante, mas não sei ao certo. Por exemplo: "encontre o ponto D em BC na distância Rr de A". Tirei uma foto para tentar entender melhor. Portanto, se começarmos em B (AX, AY- (Rr)) e C é onde terminaremos com a velocidade atual. A maneira como entendo o texto citado: Encontre um ponto D no segmento de linha BC que está a uma distância de Rr de A. Mas a maneira como eu o vejo na figura que desenhei é que apenas exatamente B está Rr de A. Todos outros pontos serão> Se afastar de A. O que estou perdendo?
Dbostream 26/05
@dbostream Você não está perdendo nada. Se os dois círculos já estiverem em contato, não haverá colisão real para detectar : a colisão ocorre em B, e k=0. Agora, se você deseja uma resolução de colisão , não cobri isso na minha resposta, pois exigiria conhecimento sobre as propriedades físicas dos objetos. O que deveria acontecer? O círculo interno deve saltar para dentro? Ou rolar? Varrer?
sam hocevar
Quero que o pequeno círculo comece a deslizar ao longo do arco do grande círculo. Portanto, se não me engano, quero o ponto de colisão no arco do grande círculo para que eu possa usar o normal para atualizar a velocidade.
Dbostream
@dbostream se o movimento deve ser restringido de tal maneira, sugiro que você siga essa restrição o mais rápido possível: se a velocidade for V, faça o círculo interno avançar V*tao longo da circunferência do R-rcírculo. Isso significa uma rotação de V*t/(R-r)radianos angulares em torno do ponto A. E o vetor de velocidade pode ser girado da mesma maneira. Não há necessidade de conhecer o normal (que está sempre orientado para o centro do círculo) ou de atualizar a velocidade de qualquer outra maneira.
sam hocevar
Ainda preciso mover o pequeno círculo para o ponto de colisão antes de girar. E quando tentei rotacionar a posição usando uma matriz de rotação, a nova posição não estava exatamente (mas quase) afastada do centro do grande círculo, mas essa pequena diferença foi suficiente para fazer meu teste de colisão falhar na próxima corrida. Preciso girar a posição para encontrar a nova, não é possível usar operações vetoriais como você pode, se algo colidir com uma parede reta?
Dbostream 27/05
4

Digamos que o grande círculo seja o círculo A e o pequeno círculo seja o círculo B.

Verifique se B está dentro de A:

distance = sqrt((B.x - A.x)^2 + (B.y - A.y)^2))
if(distance > A.Radius + B.Radius) { // B is outside A }

Se no quadro n-1B estava dentro de A e no quadro nB está fora de A e o tempo entre os quadros não era muito grande (também conhecido como B não estava se movendo muito rápido), podemos aproximar o ponto de colisão encontrando as coordenadas cartesianas de B relativas para A:

collision.X = B.X - A.X;
collision.Y = B.Y - A.Y;

Podemos então converter esses pontos em um ângulo:

collision.Normalize(); //not 100% certain if this step is necessary     
radians = atan2(collision.Y, collision.X)

Se você quiser saber mais exatamente o que tB está fora de A pela primeira vez, poderá fazer uma interseção de círculo de raios a cada quadro e comparar se a distância de B ao ponto de colisão é maior, então a distância que B pode percorrer, velocidade atual. Nesse caso, você pode calcular o tempo exato da colisão.

Roy T.
fonte
Obrigado, mas é realmente correto disparar um raio do centro do pequeno círculo ao fazer esse teste de interseção? Não vamos acabar com o cenário no meio desta foto ? Quero dizer, o primeiro ponto no arco do pequeno círculo a colidir com o grande círculo não é necessariamente aquele no arco na direção da velocidade. Acho que preciso de algo como no cenário inferior da imagem a que vinculei. Eu adicionei uma nova imagem no primeiro post mostrando um exemplo do que acho que preciso.
Dbostream
Hmm, suponho que esse cenário seja possível. Talvez teste com um novo círculo C que tenha B.Radius + o movimento máximo de B nesse quadro, verifique se ele colide com A e depois exercite o ponto em C que está mais distante de A. (Ainda não tentei isso)
Roy T.
3
Usando menos palavras: se (distância (A, B))> (Ra-Rb) ocorre uma colisão e você apenas move o pequeno círculo para obter uma distância igual a Ra-Rb. Caso contrário, você move o pequeno círculo normalmente. A propósito, @dbostream você está usando algo semelhante a uma forma simplificada de Contatos Especulativos, tente procurar por isso.
Darkwings
@Darkwings +1, você está absolutamente certo, e isso faz com que pareça muito mais simples!
Roy T.
Parece simples porque tirei toda a geometria básica necessária. Em vez de chamá-lo de 'colisão', você poderia chamá-lo de boundAB, pois é o que realmente é: o vetor livre AB vinculado a (0,0). Depois de normalizado, você obtém a equação do feixe paralelo ao AB de linhas retas e também um vetor de unidade útil. Em seguida, você pode multiplicar esse vetor de unidade por qualquer distância D e adicionar os parâmetros novos encontrados em A para encontrar o ponto de colisão necessário: C (Ax + Dx, Ay + Dy). Agora parece mais complicado, mas é a mesma coisa: P
Darkwings
0

Deixe (Xa, Ya) a posição do grande círculo e seu raio R, e (Xb, Yb) a posição do círculo menor e seu raio r.

Você pode verificar se esses dois círculos colidem se

DistanceTest = sqrt(((Xa - Xb) ^ 2) + ((Ya - Yb) ^ 2)) >= (R - r)

Para descobrir a posição da colisão, encontre o momento exato em que os círculos colidem, usando uma pesquisa binária, mas com um número fixo de etapas. Dependendo de como o seu jogo é feito, você pode otimizar essa parte do código (eu forneci esta solução para ser independente de como a pequena bola se comporta. Se ela tem aceleração constante ou velocidade constante, essa parte do código pode ser otimizada e substituído por uma fórmula simples).

left = 0 //the frame with no collision
right = 1 //the frame after collision
steps = 8 //or some other value, depending on how accurate you want it to be
while (steps > 0)
    checktime = left + (right - left) / 2
    if DistanceTest(checktime) is inside circle //if at the moment in the middle of the interval [left;right] the small circle is still inside the bigger one
        then left = checktime //the collision is after this moment of time
        else right = checktime //the collision is before
    steps -= 1
finaltime = left + (right - left) / 2 // the moment of time will be somewhere in between, so we take the moment in the middle of interval to have a small overall error

Depois de conhecer o tempo de colisão, calcule as posições dos dois círculos no momento final e o ponto de colisão final será

CollisionX = (Xb - Xa)*R/(R-r) + Xa
CollisionY = (Yb - Ya)*R/(R-r) + Ya
vlad
fonte
0

Implementei uma demonstração de uma bola quicando em círculo no jsfiddle usando o algoritmo descrito por Sam Hocevar :

http://jsfiddle.net/klenwell/3ZdXf/

Aqui está o javascript que identifica o ponto de contato:

find_contact_point: function(world, ball) {
    // see https://gamedev.stackexchange.com/a/29658
    var A = world.point();
    var B = ball.point().subtract(ball.velocity());
    var C = ball.point();
    var R = world.r;
    var r = ball.r;

    var AB = B.subtract(A);
    var BC = C.subtract(B);
    var AB_len = AB.get_length();
    var BC_len = BC.get_length();

    var b = AB.dot(BC) / Math.pow(BC_len, 2) * -1;
    var c = (Math.pow(AB_len, 2) - Math.pow(R - r, 2)) / Math.pow(BC_len, 2);
    var d = b * b - c;
    var k = b - Math.sqrt(d);

    if ( k < 0 ) {
        k = b + Math.sqrt(d);
    }

    var BD = C.subtract(B);
    var BD_len = BC_len * k;
    BD.set_length(BD_len);

    var D = B.add(BD);
    return D;
}
klenwell
fonte