Orbital Ape

Um blog sobre um grupo de desenvolvimento de jogos indie em constante e vagaroso crescimento.

Tag: sistema

Os pensamentos de Amit sobre Grades [Parte 6: Transformação das Coordenadas]

Em ambos os sistemas gráficos 2D e 3D, nós temos que transformar as coordenadas do “mundo” em coordenadas da “tela” e vice versa. Com grades, nós também temos que transformar as coordenadas da “grade” em coordenadas do “mundo” e vice e versa. Transformações ocorrem em pontos. De grades para coordenadas do mundo, nós transformamos vértices e ocasionalmente os centros das faces. Do mundo para as coordenadas da grade, nós podemos ou escolher encontrar o ponto anexado da face, a borda mais próxima à um ponto, ou o vértice mais próximo à um ponto.

Quadrados

Quadrados são fáceis de se trabalhar. Se um dos lados do quadrado possui comprimento “s” e as bordas deste quadrado são alinhadas com os eixos “x” e “y”, você pode simplesmente multiplicar as coordenadas do vértice da sua grade por “s” para obter a coordenada do mundo.

Agora fazendo o contrário, nós queremos determinar qual vértice é mais próxima de um ponto no espaço do mundo. Simplesmente divida as coordenadas do mundo por “s” e arredonde para cima a float para uma interger para obter o vértice mais próximo. Se ao invés você quer determinar que face se anexa a um ponto no espaço do mundo, arredonde para baixo ao invés de para cima.

Hexágonos

Figura 1: Medidas do Hexágono

Hexágonos são só um pouco mais complicados de se trabalhar do que quadrados. Computar o centro da face é simples. Na Figura 1, há um vetor “i” e um vetor “j” indo das coordenadas hexagonais às coordenadas do mundo é uma multiplicação de matriz(bem simples):

⎛ x ⎞     ⎡ ix jx ⎤ ⎛ u ⎞
⎝ y ⎠  =  ⎣ iy jy ⎦ ⎝ v ⎠

Expandido ficaria:

x = ix * u + jx * v
y = iy * u + jy * v

O diagrama mostra que “i” é (hexagon_narrow_width, 0.5*hexagon_height) e “j” é (0, hexagon_height), então isto nos da valores para “ix”, “iY”, “jx”, “jy”. O código resultante é:

# Centro da Face
x = hexagon_narrow_width * u
y = hexagon_height * (u*0.5 + v)

Computar vértices também é algo um tanto simples. No resto dos artigos eu rotulei as vértices do hexágono de “L” ou “R”. Esses dois vértices ocorrem da metade da largura do hexágono para a esquerda ou direita do centro da face do hexágono(veja a Figura 1), então tudo o que nós temos que fazer é somar ou subtrair hexagon_wide_width * 0.5:

# Se x,y são o centro da face, nós podemos ajustar para encontrar um vértice
case side
  when :L
    x -= hexagon_wide_width * 0.5
  when :R
    x += hexagon_wide_width * 0.5
end

Quando estiver trabalhando com hexágonos, trate o centro das faces como primário e os vértices como secundário.

Ir de coordenadas hexagonais (u, v) para coordenadas do mundo (x, y) foi uma multiplicação de matriz. Para ir de coordenadas do mundo de volta para hexágonos, você pode resolver a equação para (u, v). Eu vou pular a álgebra; aqui está o resultado:

u = x / hexagon_narrow_width
v = y / hexagon_height - u * 0.5

Isto funciona se você começar com (x, y) no centro de uma face. Se você tiver arbitrariamente (x, y) é um pouco mais trabalhoso. A maneira mais simples(porém não mais eficiente) é de considerar o calculado (u, v) mais todos os vizinhos e determinar qual é o mais próximo da dada coordenada do mundo. Esta abordagem funciona com qualquer um dos três tipos de grades. Se você estiver utilizando esta forma para seleção por mouse, está é bastante rápida. Pode ser melhorada mais ainda ao se ter mais atenção com os polígonos.

Triângulos

Triângulos são quadrados distorcidos e divididos. Converter as vértices de um triângulo das coordenadas da grade para coordenadas do mundo simplesmente envolve multiplicá-las pelos vetores do eixo, assim como as coordenadas da face do hexágono:

⎛ x ⎞     ⎡ ix jx ⎤ ⎛ u ⎞
⎝ y ⎠  =  ⎣ iy jy ⎦ ⎝ v ⎠

Converter as coordenadas da face triangular para coordenadas do mundo é fácil, envolve um ajuste. O “L” do centro da face triangular é (0.25*i, 0.25*j) do vértice inferior esquerdo. O “R” do centro da face triangular é (0.75*i, 0.75*j) do vértice inferior esquerdo.

Para converter de coordenadas do mundo para vértices do triângulo é fácil. Esclareça para (u, v) usando álgebra. Para determinar qual face o ponto do mundo está, considere a borda que está dividindo os dois triângulos dentro do quadrado retorcido; esta borda é rotulada “E”. Se o seu ponto é à esquerda desta linha, o ponto está na face “L”; se estiver na direita, o ponto está na face “R”. Acredito que seja “R” quando frac(u) + frac(v) >= 0.5.

Quando trabalhar com triângulos, trate as vértices como primárias, e o centro das faces como secundário. Este é o oposto de como tratamos os hexágonos.

Os pensamentos de Amit sobre Grades [Parte 5: Relação entre as Partes da Grade]

Dadas cada uma das 3 partes da grade, nós podemos definir relações com 3 partes da grade, nos dando 9 relações. Há mais relações que você pode definir, porém irei me focar nestas 9. Eu não sei o nome destas relações, portanto eu dei-lhes alguns nomes.

Parte A (preto) Parte B (vermelho)
Face Borda Vértice
Face Vizinhos:
Neighbors of a square
Fronteiras:
Borders of a square
Cantos:
Corners of a square
Borda Juntam-se:
Joins of an edge
Continua:
Continuation of an edge
Pontos Finais:
Endpoints of an edge
Vértice Toca:
Vertex touching
Sobressai:
Vertex protrusion
Adjacentes:
Adjacent vertices

Em seus jogos você poderá utilizar variantes dos que precede. Por exemplo, as relações vizinhosadjacentes poderiam incluir diagonais. A relação continua poderia incluir bordas que não são colineares com a borda original. Eu escolhi as relações mais simples.

Algoritmos

Cada uma dessas 9 relações pode ser expressa em algoritmos de A para uma lista de Bs; eu os escrevi como A → B1 B2 B3… . Há 3 formas, então isto nos da 27 algoritmos em potencial, expressos aqui de uma forma simples que possa ser traduzida em sua linguagem de programação favorita. Para algumas formas e algoritmos há algumas variantes de A, então eu listarei as regras para cada variante. Por exemplo, faces triangulares vêm nas variantes L e R. Aqui estão todos os algoritmos:

Relação Forma
Quadrado Hexágono Triângulo
Vizinhos:
(u,v) → (u,v+1) (u+1,v) (u,v-1), (u-1,v) (u,v) → (u,v+1) (u+1,v) (u+1,v-1) (u,v-1) (u-1,v) (u-1,v+1) (u,v,L) → (u,v,R) (u,v-1,R) (u-1,v,R)(u,v,R) → (u,v+1,L) (u+1,v,L) (u,v,L)
Fronteiras:
(u,v) → (u,v+1,S) (u+1,v,W) (u,v,S) (u,v,W) (u,v) → (u,v,N) (u,v,E) (u+1,v-1,W) (u,v-1,N) (u-1,v,E) (u,v,W) (u,v,L) → (u,v,E) (u,v,S) (u,v,W)(u,v,R) → (u,v+1,S) (u+1,v,W) (u,v,E)
Cantos:
(u,v) → (u+1,v+1) (u+1,v) (u,v) (u,v+1) (u,v) → (u+1,v,L) (u,v,R) (u+1,v-1,L) (u-1,v,R) (u,v,L) (u-1,v+1,R) (u,v,L) → (u,v+1) (u+1,v) (u,v)(u,v,R) → (u+1,v+1) (u+1,v) (u,v+1)
Juntam-se:
(u,v,W) → (u,v) (u-1,v)(u,v,S) → (u,v) (u,v-1) (u,v,N) → (u,v+1) (u,v)(u,v,E) → (u+1,v) (u,v)

(u,v,W) → (u,v) (u-1,v+1)

(u,v,S) → (u,v,L) (u,v-1,R)(u,v,E) → (u,v,R) (u,v,L)

(u,v,W) → (u,v,L) (u-1,v,R)

Continua:
(u,v,W) → (u,v+1,W) (u,v-1,W)(u,v,S) → (u+1,v,S) (u-1,v,S) Nenhuma borda continua reta em uma grade hexagonal (u,v,W) → (u,v+1,W) (u,v-1,W)(u,v,E) → (u+1,v-1,E) (u-1,v+1,E)

(u,v,S) → (u+1,v,S) (u-1,v,S)

Pontos Finais:
(u,v,W) → (u,v+1) (u,v)(u,v,S) → (u+1,v) (u,v) (u,v,N) → (u+1,v,L) (u-1,v+1,R)(u,v,E) → (u,v,R) (u+1,v,L)

(u,v,W) → (u-1,v+1,R) (u,v,L)

(u,v,W) → (u,v+1) (u,v)(u,v,E) → (u+1,v) (u,v+1)

(u,v,S) → (u+1,v) (u,v)

Toca:
(u,v) → (u,v) (u,v-1) (u-1,v-1) (u-1,v) (u,v,L) → (u,v) (u-1,v) (u-1,v+1)(u,v,R) → (u+1,v) (u+1,v-1) (u,v) (u,v) → (u-1,v,R) (u,v,L) (u,v-1,R) (u,v-1,L) (u-1,v-1,R) (u-1,v,L)
Sobressai:
(u,v) → (u,v,W) (u,v,S) (u,v-1,W) (u-1,v,S) (u,v,L) → (u,v,W) (u-1,v,E) (u-1,v,N)(u,v,R) → (u+1,v-1,N) (u+1,v-1,W) (u,v,E) (u,v) → (u,v,W) (u,v,S) (u,v-1,E) (u,v-1,W) (u-1,v,S) (u-1,v,E)
Adjacente:
(u,v) → (u,v+1) (u+1,v) (u,v-1) (u-1,v) (u,v,L) → (u-1,v+1,R) (u-1,v,R) (u-2,v+1,R)(u,v,R) → (u+2,v-1,L) (u+1,v-1,L) (u+1,v,L) (u,v) → (u,v+1) (u+1,v) (u+1,v-1)(u,v-1) (u-1,v) (u-1,v+1)

Relações

(Sinta-se livre para pular esta seção.) As relações listadas a cima são elas mesmas relacionadas umas as outras. Por exemplo, fronteiras vão de faces para bordas, e é o inverso de juntam-se, que vai de bordas para faces. Se alguma borda B está na lista fronteiras de algum A, então A estará na lista juntam-se da borda B. Estas 9 relações podem ser destiladas em 6 relações matemáticas.

Se você tivesse um banco de dados, você poderia expressar as relações diretamente. Por exemplo, aqui está a relação entre faces e bordas de uma pequena grade quadrangular 1×2:

Face Borda
0,0 0,0,S
0,0 0,0,W
0,0 0,1,S
0,0 1,0,W
1,0 1,0,S
1,0 1,0,W
1,0 1,1,S
1,0 2,0,W

Dada a relação, você pode olhar as coisas em cada coluna. Por exemplo, com a tabela a cima, procurando Face==(0, 0) resulta em 4 bordas, que é o que a relação fronteiras expressa. Procurando Borda==(1, 0, W), resulta em 2 faces, que é o que a relação juntam-se expressa. Uma relação é mais geral, e permite que você olhe as coisas de várias maneiras; cada relação(e algoritmo) é uma expressão de uma maneira particular de olhar as coisas.

Dadas 6 relações, deveria então existir 12 relacionamentos. Por que só temos 9? Porque as relações Face/ Face, Borda/ Borda, e Vértice/ Vértice são simétricas então olhando as coisas de outras maneiras produz mesmo resultado. Logo, 3 dos 12 relacionamentos são redundantes, o que nos deixa com 9.

Implementação

Todos os algoritmos são diretos. Como você poderia implementá-los então? Você primeiro irá escolher estruturas de dados para cada um dos três tipos de sistemas de coordenadas. Eu recomendo deixar bem simples e transparente. Todos os sistemas de coordenadas que utilizei possuem um inteiro “u” e um inteiro “v”, e alguns deles tem anotações como “L” ou “W”. Para a estrutura, em Ruby use uma classe com attrs públicas; em Lisp use uma lista; em C use uma struct; em Java use uma classe com campos públicos; em Python use um simples objeto ou dict. Para as anotações, em Ruby ou Lisp use símbolos(‘L ou :L); em C, use caracteres(‘L’) ou uma enumeração (L); em Java, use caracteres; em Python, use strings com um caractere.

O próximo passo é implementar os algoritmos que você precisa. A coisa mais simples a se fazer é escrever funções(ou métodos) que tomem A e retornem uma lista de B. Se houverem múltiplas variantes de A, use uma afirmação switch/case para ramificar para as anotações. Esta é a abordagem mais simples, mas não é a mais rápida. Para tornar as coisas mais rápidas, seu caller deve pré-alocar a lista, ou você pode prover um callback que estejam inline(inglês)(por exemplo, objetos funções STL em C++). Em algumas situações você irá querer procurar o máximo de As ao mesmo tempo então você poderá providenciar um algoritmo que funcione com listas de A que produzam listas de listas de B.

Eu geralmente evito dar implementações, porque elas são muito específicas para cada jogo, mas darei um exemplo da forma do Triângulo em Ruby. Eu escolhi usar uma lista como minha estrutura básica de dados. E eu usei os símbolos em Ruby(como :L) para anotações.

Algoritmo Código Ruby
(u,v,L) →
(u,v+1) (u+1,v) (u,v)(u,v,R) →
(u+1,v+1) (u+1,v) (u,v+1)
  def corners(face)
     u, v, side = face
    case side
    when :L 
      [[u, v+1], [u+1, v], [u, v]]
    when :R 
      [[u+1, v+1], [u+1, v], [u, v+1]]
    end
  end

É simples. Cada variante se torna um caso dentro da afirmação “case”.

Os pensamentos de Amit sobre Grades [Parte 4: Sistema de Coordenadas]

Existem três partes da grade, e nós precisamos de uma maneira para endereçar cada uma delas. Eu vou começar com simples coordenadas numéricas que corresponda aos eixos da grade. A soma F, E, V no diz quantas partes da grade compartilham as mesmas coordenadas. Se mais de uma parte possuir a mesma coordenada, utilizarei uma letra maiúscula para retirar a ambiguidade.

Grades Quadrangulares

Faces of a square     Edges of a square    Vertices of a square

Figura 1: Sistema de coordenadas da grade quadrangular: faces, bordas, e vértices

Grades quadrangulares são bem fáceis. Veja o primeiro diagrama na Figura 1 para o padrão do sistema de coordenadas para as faces. A soma F, E, V é 1, 2, 1. Isso significa que só as bordas precisarão de uma letra para retirar a ambiguidade. Para cada face, nós (arbitrariamente) atribuímos uma vértice para compartilhar sua coordenada. Eu escolhi o canto sudoeste da face. Compare o diagrama de face e o diagrama de vértice para ver como as coordenadas são relacionadas. Para cada face nós atribuímos duas bordas para dividir sua coordenada. Eu escolhi as bordas do sul e oeste e anotei as coordenadas com as letras s(Sul/ South) e w(Oeste/ West).  Compare o diagrama de face com o diagrama de bordas para ver como são relacionados. Há uma face, duas bordas, e uma vértice que compartilha a coordenada (1, 1). Isto corresponde a nossa soma F, E, V de 1, 2, 1.

Grades Hexagonais

Faces of a hexagon     Edges of a hexagon    Vertices of a hexagon

Figura 2: Sistema de coordenadas da grade hexagonal: faces, bordas, e vértices

Nós criamos a grade hexagonal de uma grade quadrangular. As coordenadas da face de um hexágono podem ser as mesmas coordenadas da face do quadrado que foi transformado em hexágono. Compare a Figura 1 com a Figura 2 e você verá como a coordenada das faces são relacionadas. Para cada face nós escolhemos três bordas e dois vértices para compartilhares a mesma coordenada. Eu escolhi as bordas NW, N, e NE e as rotulei W, N, E. Eu escolhi os vértices mais a esquerda e a mais a direita e os rotulei L, R. Muitas outras atribuições são possíveis.

Grades Triangulares

Faces of a triangle     Edges of a triangle    Vertices of a triangle

Figura 3: Sistema de coordenadas da grade triangular: faces, bordas e vértices

Nós criamos a grade triangular de uma grade quadrangular, e nós dividimos cada quadrado distorcido em dois triângulos. Isso significa que cada coordenada de uma face quadrangular agora terá que ser duas coordenadas de faces triangulares. Eu escolhi rotulá-los L e R, como visto na Figura 3. As bordas são as mesmas daquelas dos quadrados(W e S), exceto que nós agora temos uma borda adicional, criada a partir da divisão da face quadrangular em duas, e eu rotulei esta borda de E. O triângulo extra não cria nenhum vértice adicional, logo a rotulação dos vértices permanece a mesma da rotulação da grade quadrangular.