Como lidar com a detecção de colisões com pixel perfeito com rotação?

8

Alguém tem alguma idéia de como conseguir a detecção de colisão rotacional com pixel perfeito com os Bitmaps no Android? Ou em geral para esse assunto? Atualmente, tenho matrizes de pixels, mas não sei como manipulá-las com base em um número arbitrário de graus.

DRiFTy
fonte

Respostas:

11

Eu não estou familiarizado com o Android, então não sei quais ferramentas você tem à sua disposição, mas posso lhe dizer uma maneira de implementar isso em termos gerais. O quão fácil será depende do que o Android fornecer para você. Você precisará de matrizes ou, pelo menos, elas simplificarão muito os cálculos.

Para iniciantes, verifique a colisão da caixa delimitadora e retorne imediatamente se não colidirem, a fim de evitar cálculos adicionais. Isso é lógico, porque se as caixas delimitadoras não colidirem, é garantido que nenhum pixel colidirá também.

Posteriormente, se uma verificação de colisão perfeita de pixels for necessária, o ponto mais importante é que você deve executar essa verificação no mesmo espaço . Isso pode ser feito pegando cada pixel do sprite A, aplicando uma série de transformações para colocá-los no espaço local do sprite B e, em seguida, verifique se ele colide com algum pixel nessa posição no sprite B. Uma colisão ocorre quando os dois pixels marcados são opacos.

Então, a primeira coisa que você precisa é construir uma matriz mundial para cada um dos sprites. Provavelmente, existem tutoriais on-line ensinando como criar um, mas deve ser basicamente uma concatenação de algumas matrizes mais simples na seguinte ordem:

Translation(-Origin) * Scale * Rotation * Translation(Position)

A utilidade dessa matriz é que, multiplicando um ponto no espaço local - e, por exemplo, se você obtiver os pixels usando um método como bitmap.getPixelAt(10,20)10,20, é definido no espaço local -, a matriz mundial correspondente o moverá para o espaço mundial:

LocalA * WorldMatrixA -> World
LocalB * WorldMatrixB -> World

E se você inverter as matrizes, também poderá ir na direção oposta, ou seja, transformar pontos do espaço do mundo em cada um dos espaços locais do sprite, dependendo da matriz usada:

World * InverseWorldMatrixA -> LocalA
World * InverseWorldMatrixB -> LocalB

Portanto, para mover um ponto do espaço local do sprite A para o espaço local do sprite B , você primeiro o transforma usando a matriz mundial do sprite A, a fim de inseri-lo no espaço mundial e, em seguida, usando a matriz mundial inversa do sprite B , para inseri-lo espaço local de sprite B:

LocalA * WorldMatrixA -> World * InverseWorldMatrixB -> LocalB

Após a transformação, você verifica se o novo ponto está dentro dos limites do sprite B e, se o fizer, verifica o pixel nesse local, exatamente como no sprite A. Portanto, todo o processo se torna algo assim (em pseudocódigo e não testado) :

bool PixelCollision(Sprite a, Sprite B)
{
    // Go over each pixel in A
    for(i=0; i<a.Width; ++i)
    {
        for(j=0; j<a.Height; ++j)
        {
            // Check if pixel is solid in sprite A
            bool solidA = a.getPixelAt(i,j).Alpha > 0;

            // Calculate where that pixel lies within sprite B's bounds
            Vector3 positionB = new Vector3(i,j,0) * a.WorldMatrix * b.InverseWorldMatrix;

            // If it's outside bounds skip to the next pixel
            if(positionB.X<0 || positionB.Y<0 || 
               positionB.X>=b.Width || positionB.Y>=b.Height) continue;

            // Check if pixel is solid in sprite B
            bool solidB = b.getPixelAt(positionB.X, positionB.Y).Alpha > 0;

            // If both are solid then report collision
            if(solidA && solidB) return true;
        }
    }
    return false;
}
David Gouveia
fonte
11
Gosto disso porque é elegante, mas o uso de matrizes como essa - multiplicando uma matriz 3x3 por pixel por quadro - introduzirá algumas penalidades de desempenho bastante sérias para todos os bitmaps, exceto os menores, especialmente na maioria dos dispositivos Android.
3Dave
2
@DavidLively De fato, este é um processo caro computacionalmente. E eu acho que mesmo com os cálculos de matriz sendo desenrolados em cálculos regulares com trigonometria - possivelmente deixando de fora o dimensionamento se não estiver sendo usado - ainda assim seria praticamente o mesmo. Pode haver outras soluções, mas não consegui pensar em nenhuma. Por fim, a melhor solução seria considerar se a detecção de colisão perfeita de pixels é realmente necessária. E alguns jogos que usam colisões perfeitas em pixels (como os primeiros jogos do Sonic) abstraem os personagens em uma série de pontos de colisão.
David Gouveia
bom ponto; Estou destruindo meu cérebro tentando encontrar uma solução alternativa que não envolva subtrair bitmaps e procurar picos. Provavelmente há uma oportunidade para um trabalho nisso em algum lugar. :) Mantra: óptima vs suficiente ... óptima vs suficiente ..
3Dave
Muito obrigado, é realmente uma ótima explicação. Atualmente, tenho a detecção de colisão de caixa delimitadora configurada e, se isso ocorrer, verifico a sobreposição de pixels entre os dois bitmaps, mas apenas no retângulo sobreposto. Tudo isso funciona muito bem e não exige muito processador. O problema ocorre quando eu giro a imagem. Os próprios pixels não são rotacionados no bitmap. Estou apenas desenhando uma imagem girada. Então, quando continuo verificando a colisão de pixels, ela ainda está usando os pixels antigos. Gosto do que você disse sobre o mapeamento de pixels para as coordenadas do mundo. Isso pode muito bem ser o que eu preciso fazer. Obrigado!!!
DRiFTy
+1 Eu sempre me perguntei como o algoritmo funciona. Obrigado @DavidGouveia
Vishnu
2

Embora a resposta de David Gouveia pareça correta, não é a melhor solução do ponto de vista do desempenho. Existem várias otimizações importantes que você precisa fazer:

  1. resolva e evite verificações desnecessárias verificando primeiro com uma simples colisão circular: para obter o centro e o raio de seus sprites em qualquer rotação, obtenha as coordenadas mínimas e máximas x e y de todos os 4 vértices (já rotacionados): então você pode construir um círculo se fechando no sprite por:

    center = max_x-min_x / 2, max_y-min_y / 2

    raio = max (max_x-min_x, max_y-min_y)

  2. agora você não deve ter muitos candidatos a verificar rasterizando as imagens já transformadas (giradas) basicamente usando um algoritmo simples de mapeamento de textura afim . Basicamente, você acompanha 4 linhas por sprite rasterizado: 2 linhas indo do vértice a para os próximos vértices da sua caixa girada (bec) 2 linhas indo no "espaço de bitmap" do vértice u1 / v1 para os próximos vértices u2 / v2 e u3 / v3: Nota: pesquisei esta imagem no Google e ela mostra um triângulo, suas caixas são apenas dois triângulos. É vital que esse algoritmo desenhe linhas horizontais (é por isso que é chamado de " rasterizador ") para evitar "buracos" dentro do bitmap devido a erros de arredondamento. Calculando as linhas com um algoritmo de Bresenham, você precisa para cada pixel apenas 4 adições e 4 comparações (e às vezes duas adições adicionais, dependendo da inclinação). O que você faz é escrever seu próprio redutor de textura de polígono, mas sem a correção 3D cara (e difícil de otimizar). mapeamento de textura afim

  3. você pode reduzir facilmente a resolução dos bitmaps de colisão (por exemplo, pelo fator 2) e economizar ainda mais tempo. uma verificação de colisão em metade da resolução deve ser imperceptível.

  4. Se você estiver usando aceleração gráfica, pode ser possível usar algum tipo de verificação de buffer acelerado por HW (estêncil?) Para evitar a codificação do rasterizador por conta própria.

O principal problema é: Java não é muito rápido ao acessar dados de bitmap armazenados em uma matriz 2D. Eu recomendaria armazenar os dados em uma matriz unidimensional para evitar pelo menos 1 verificação de indexOutOfBounds para cada acesso. Use também dimensões de potência de 2 (como 64x64, 128x128 etc.). Dessa forma, você pode calcular o deslocamento via deslocamento de bits e não multiplicação. Você também pode otimizar o acesso à segunda textura para executá-lo somente se a primeira tiver valor! = 0 (transparente)

Todos esses problemas foram resolvidos nos mecanismos de renderização de software; pode ser útil procurar no código fonte

Peter Parker
fonte