Como faço para obter coordenadas hexadecimais de pixel em um mapa hexadecimal baseado em matriz?

10

Estou tentando fazer um pixel para coordenar a função de um mapa hexadecimal, mas não estou aprendendo a matemática corretamente, tudo o que tento parece um pouco complicado e os exemplos que encontrei foram baseados em mapas centralizados em círculo.

Por 'array array', quero dizer a maneira como os hexágonos são ordenados, veja a foto.

O resultado mais preciso que obtive foi com o código a seguir, mas ele ainda está desativado e piora quanto mais os valores aumentam:

public HexCell<T> coordsToHexCell(float x, float y){
    final float size = this.size; // cell size
    float q = (float) ((1f/3f* Math.sqrt(3) * x - 1f/3f * y) / size);
    float r = 2f/3f * y / size;
    return getHexCell((int) r, (int) q);
}

Hexmap

A tela começa com 0,0 no canto superior esquerdo, cada célula conhece seu centro.

Tudo o que preciso é de uma maneira de traduzir as coordenadas da tela em coordenadas hexadecimais. Como eu pude fazer isso?

petervaz
fonte

Respostas:

12

Existem muitos sistemas de coordenadas hexadecimais. As abordagens de “deslocamento” são ótimas para armazenar um mapa retangular, mas os algoritmos hexadecimais tendem a ser mais complicados.

No meu guia de grade hexadecimal (que acredito que você já encontrou), seu sistema de coordenadas é chamado de "par-r", exceto que você os está rotulando em r,qvez de q,r. Você pode converter locais de pixels em coordenadas hexadecimais com estas etapas:

  1. Converta as localizações dos pixels em coordenadas hexadecimais axiais usando o algoritmo descrito nesta seção . É isso que sua função faz. No entanto, você precisa dar mais um passo.
  2. Essas coordenadas axiais são fracionárias. Eles precisam ser arredondados para o hexágono mais próximo. No seu código, você usa, (int)r, (int)qmas isso só funciona para quadrados; para hexágonos, precisamos de uma abordagem de arredondamento mais complicada. Converter o r, qde cubo coordenadas usando o axial ao cubo fórmulas aqui . Então use a hex_roundfunção aqui .
  3. Agora você tem um conjunto inteiro de coordenadas do cubo . Seu mapa usa “even-r”, não cubo, então você precisa converter novamente. Use o cubo para equilibrar as fórmulas a partir daqui .

Preciso reescrever a seção de coordenadas de pixel para hexadecimal para torná-la muito mais clara. Desculpa!

Eu sei, isso parece complicado. Eu uso essa abordagem porque é a menos suscetível a erros (sem casos especiais!) E permite a reutilização. Essas rotinas de conversão podem ser reutilizadas. O arredondamento hexadecimal pode ser reutilizado. Se você quiser desenhar linhas ou girar em torno de uma coordenada hexadecimal ou executar um campo de visão ou outros algoritmos, algumas dessas rotinas também serão úteis.

amitp
fonte
Vou tentar isso. Obrigado. Eu já encontrei uma solução que funcionava, mas realmente quero me aprofundar mais na matemática hexadecimal, apenas tendo um pouco de dificuldade em envolver minha cabeça e seguir etapas de bebê.
precisa saber é
2
@amitp: Adoro o seu guia, me deparei com ele quando escrevi um gerador de grade hexagonal há alguns anos atrás. Aqui está a minha solução, se você estiver interessado: Stack Overflow - Algoritmo para gerar uma grade hexagonal com sistema de coordenadas .
Mr. Polywhirl
11
Onde está a origem das coordenadas do pixel? No centro do hexágono 0,0 em coordenadas de deslocamento?
Andrew
11
@ Andrew Sim. Você pode mudar a origem nas coordenadas de pixel antes de executar a transformação em coordenadas hexadecimais.
Amitp
9

Existem duas maneiras de lidar com esse problema, na minha opinião.

  1. Use um melhor sistema de coordenadas. Você pode facilitar muito a matemática se for inteligente sobre como numerar os hexágonos. Amit Patel tem a referência definitiva em grades hexagonais. Você deseja procurar coordenadas axiais nessa página.

  2. Emprestar código de alguém que já o resolveu. Eu tenho algum código que funciona, que tirei da fonte Battle for Wesnoth . Lembre-se de que minha versão tem a parte plana dos hexágonos na parte superior, então você terá que trocar xey.

Michael Kristofik
fonte
6

Acho que a resposta de Michael Kristofik está correta, especialmente por mencionar o site de Amit Patel, mas eu queria compartilhar minha abordagem iniciante às grades Hex.

Esse código foi retirado de um projeto em que perdi o interesse e abandonei a escrita em JavaScript, mas a posição do mouse em hexadecimal funcionou muito bem. Eu usei * este artigo GameDev * para minhas referências. A partir desse site, o autor tinha uma imagem que mostrava como representar matematicamente todos os lados e posições do Hex.

Na minha classe de renderização, eu defini isso em um método que me permitiu definir qualquer comprimento do lado Hex que eu desejasse. Mostrado aqui porque alguns desses valores foram referenciados no pixel para o código de coordenadas hexadecimais.

                this.s = Side; //Side length
                this.h = Math.floor(Math.sin(30 * Math.PI / 180) * this.s);
                this.r = Math.floor(Math.cos(30 * Math.PI / 180) * this.s);
                this.HEXWIDTH = 2 * this.r;
                this.HEXHEIGHT = this.h + this.s;
                this.HEXHEIGHT_CENTER = this.h + Math.floor(this.s / 2);

Na classe de entrada do mouse, criei um método que aceitou as coordenadas xey da tela e retornei um objeto com a coordenada hexadecimal em que o pixel reside. * Observe que eu tinha uma "câmera" falsa, então compensações para a posição de renderização também estão incluídas.

    ConvertToHexCoords:function (xpixel, ypixel) {
        var xSection = Math.floor(xpixel / ( this.Renderer.HEXWIDTH )),
            ySection = Math.floor(ypixel / ( this.Renderer.HEXHEIGHT )),
            xSectionPixel = Math.floor(xpixel % ( this.Renderer.HEXWIDTH )),
            ySectionPixel = Math.floor(ypixel % ( this.Renderer.HEXHEIGHT )),
            m = this.Renderer.h / this.Renderer.r, //slope of Hex points
            ArrayX = xSection,
            ArrayY = ySection,
            SectionType = 'A';
        if (ySection % 2 == 0) {
            /******************
             * http://www.gamedev.net/page/resources/_/technical/game-programming/coordinates-in-hexagon-based-tile-maps-r1800
             * Type A Section
             *************
             *     *     *
             *   *   *   *
             * *       * *
             * *       * *
             *************
             * If the pixel position in question lies within the big bottom area the array coordinate of the
             *      tile is the same as the coordinate of our section.
             * If the position lies within the top left edge we have to subtract one from the horizontal (x)
             *      and the vertical (y) component of our section coordinate.
             * If the position lies within the top right edge we reduce only the vertical component.
             ******************/
            if (ySectionPixel < (this.Renderer.h - xSectionPixel * m)) {// left Edge
                ArrayY = ySection - 1;
                ArrayX = xSection - 1;
            } else if (ySectionPixel < (-this.Renderer.h + xSectionPixel * m)) {// right Edge
                ArrayY = ySection - 1;
                ArrayX = xSection;
            }
        } else {
            /******************
             * Type B section
             *********
             * *   * *
             *   *   *
             *   *   *
             *********
             * If the pixel position in question lies within the right area the array coordinate of the
             *      tile is the same as the coordinate of our section.
             * If the position lies within the left area we have to subtract one from the horizontal (x) component
             *      of our section coordinate.
             * If the position lies within the top area we have to subtract one from the vertical (y) component.
             ******************/
            SectionType = 'B';
            if (xSectionPixel >= this.Renderer.r) {//Right side
                if (ySectionPixel < (2 * this.Renderer.h - xSectionPixel * m)) {
                    ArrayY = ySection - 1;
                    ArrayX = xSection;
                } else {
                    ArrayY = ySection;
                    ArrayX = xSection;
                }
            } else {//Left side
                if (ySectionPixel < ( xSectionPixel * m)) {
                    ArrayY = ySection - 1;
                    ArrayX = xSection;
                } else {
                    ArrayY = ySection;
                    ArrayX = xSection - 1;
                }
            }
        }
        return {
            x:ArrayX + this.Main.DrawPosition.x, //Draw position is the "camera" offset
            y:ArrayY + this.Main.DrawPosition.y
        };
    },

Finalmente, aqui está uma captura de tela do meu projeto com a depuração da renderização ativada. Ele mostra as linhas vermelhas nas quais o código verifica as células Tipo A vs Tipo B, juntamente com as coordenadas Hex e os contornos das células. insira a descrição da imagem aqui
Espero que isso ajude alguns.

user32959
fonte
4

Na verdade, eu encontrei uma solução sem matemática hexadecimal.
Como mencionei na pergunta, cada célula salva suas próprias cordas centrais, calculando o centro hexadecimal mais próximo das cordas de pixel, posso determinar a célula hexadecimal correspondente com precisão de pixel (ou muito próxima a ela).
Eu não acho que é a melhor maneira de fazer isso, pois tenho que iterar para cada célula e posso ver como isso pode ser desgastante, mas deixarei o código como uma solução alternativa:

public HexCell<T> coordsToHexCell(float x, float y){
    HexCell<T> cell;
    HexCell<T> result = null;
    float distance = Float.MAX_VALUE;
    for (int r = 0; r < rows; r++) {
        for (int c = 0; c < cols; c++) {
            cell = getHexCell(r, c);

            final float dx = x - cell.getX();
            final float dy = y - cell.getY();
            final float newdistance = (float) Math.sqrt(dx*dx + dy*dy);

            if (newdistance < distance) {
                distance = newdistance;
                result = cell;
            }           
        }
    }
    return result;
}
petervaz
fonte
3
Esta é uma abordagem razoável. Você pode acelerar digitalizando um intervalo menor de linhas / colunas em vez de digitalizar todas elas. Para fazer isso, você precisa de uma idéia aproximada de onde está o hexágono. Como você está usando grades de deslocamento, é possível obter uma estimativa aproximada dividindo x pelo espaçamento entre colunas e y pelo espaçamento entre linhas. Em vez de varrer todas as colunas 0…cols-1e todas as linhas 0…rows-1, você pode varrer col_guess - 1 … col_guess+1e row_guess - 1 … row_guess + 1. São apenas 9 hexágonos, portanto é rápido e não depende do tamanho do mapa.
Amitp
3

Aqui está a essência de uma implementação em C # de uma das técnicas publicadas no site de Amit Patel (tenho certeza que traduzir para Java não será um desafio):

public class Hexgrid : IHexgrid {
  /// <summary>Return a new instance of <c>Hexgrid</c>.</summary>
  public Hexgrid(IHexgridHost host) { Host = host; }

  /// <inheritdoc/>
  public virtual Point ScrollPosition { get { return Host.ScrollPosition; } }

/// <inheritdoc/>
public virtual Size  Size           { get { return Size.Ceiling(Host.MapSizePixels.Scale(Host.MapScale)); } }

/// <inheritdoc/>
public virtual HexCoords GetHexCoords(Point point, Size autoScroll) {
  if( Host == null ) return HexCoords.EmptyCanon;

  // Adjust for origin not as assumed by GetCoordinate().
  var grid    = new Size((int)(Host.GridSizeF.Width*2F/3F), (int)Host.GridSizeF.Height);
  var margin  = new Size((int)(Host.MapMargin.Width  * Host.MapScale), 
                         (int)(Host.MapMargin.Height * Host.MapScale));
  point      -= autoScroll + margin + grid;

  return HexCoords.NewCanonCoords( GetCoordinate(matrixX, point), 
                                   GetCoordinate(matrixY, point) );
}

/// <inheritdoc/>
public virtual Point   ScrollPositionToCenterOnHex(HexCoords coordsNewCenterHex) {
  return HexCenterPoint(HexCoords.NewUserCoords(
          coordsNewCenterHex.User - ( new IntVector2D(Host.VisibleRectangle.Size.User) / 2 )
  ));
}

/// <summary>Scrolling control hosting this HexGrid.</summary>
protected IHexgridHost Host { get; private set; }

/// <summary>Matrix2D for 'picking' the <B>X</B> hex coordinate</summary>
Matrix matrixX { 
  get { return new Matrix(
      (3.0F/2.0F)/Host.GridSizeF.Width,  (3.0F/2.0F)/Host.GridSizeF.Width,
             1.0F/Host.GridSizeF.Height,       -1.0F/Host.GridSizeF.Height,  -0.5F,-0.5F); } 
}
/// <summary>Matrix2D for 'picking' the <B>Y</B> hex coordinate</summary>
Matrix matrixY { 
  get { return new Matrix(
            0.0F,                        (3.0F/2.0F)/Host.GridSizeF.Width,
            2.0F/Host.GridSizeF.Height,         1.0F/Host.GridSizeF.Height,  -0.5F,-0.5F); } 
}

/// <summary>Calculates a (canonical X or Y) grid-coordinate for a point, from the supplied 'picking' matrix.</summary>
/// <param name="matrix">The 'picking' matrix</param>
/// <param name="point">The screen point identifying the hex to be 'picked'.</param>
/// <returns>A (canonical X or Y) grid coordinate of the 'picked' hex.</returns>
  static int GetCoordinate (Matrix matrix, Point point){
  var pts = new Point[] {point};
  matrix.TransformPoints(pts);
      return (int) Math.Floor( (pts[0].X + pts[0].Y + 2F) / 3F );
  }

O restante do projeto está disponível aqui como código aberto, incluindo as classes MatrixInt2D e VectorInt2D mencionadas acima:
http://hexgridutilities.codeplex.com/

Embora a implementação acima seja para hexágonos planos, a biblioteca HexgridUtilities inclui a opção de transpor a grade.

Pieter Geerkens
fonte
0

Encontrei uma abordagem simples e alternativa que usa a mesma lógica que um tabuleiro de xadrez regular. Ele cria um efeito de encaixe na grade com pontos no centro de cada bloco e em todo vértice (criando uma grade mais apertada e ignorando pontos alternados).

Essa abordagem funciona bem para jogos como Catan, onde os jogadores interagem com blocos e vértices, mas não é adequada para jogos nos quais os jogadores interagem apenas com blocos, pois retorna qual ponto central ou vértice as coordenadas estão mais próximas, em vez de qual bloco hexagonal o coordenadas estão dentro.

A geometria

Se você colocar pontos em uma grade com colunas que têm um quarto da largura de um bloco e linhas com metade da altura de um bloco, você obtém este padrão:

como descrito acima

Se você modificar o código para pular cada segundo ponto em um padrão quadriculado (pular if column % 2 + row % 2 == 1), você terminará com este padrão:

como descrito acima

Implementação

Com essa geometria em mente, você pode criar uma matriz 2D (como faria com uma grade quadrada), armazenando as x, ycoordenadas de cada ponto da grade (no primeiro diagrama) - algo como isto:

points = []
for x in numberOfColumns
    points.push([])
    for y in numberOfRows
        points[x].push({x: x * widthOfColumn, y: y * heightOfRow})

Nota: Como normal, ao criar uma grade em torno dos pontos (em vez de colocar pontos nos próprios pontos), é necessário compensar a origem (subtraindo metade da largura de uma coluna xe metade da altura de uma linha y).

Agora que você pointsinicializou sua matriz 2D ( ), pode encontrar o ponto mais próximo do mouse da mesma forma que faria em uma grade quadrada, apenas tendo que ignorar todos os outros pontos para produzir o padrão no segundo diagrama:

column, row = floor(mouse.x / columnWidth), floor(mouse.y / rowHeight)
point = null if column % 2 + row % 2 != 1 else points[column][row]

Isso funcionará, mas as coordenadas estão sendo arredondadas para o ponto mais próximo (ou nenhum ponto) com base no retângulo invisível em que o ponteiro está. Você realmente deseja uma zona circular em torno do ponto (para que o intervalo de snap seja igual em todas as direções). Agora que você sabe qual ponto verificar, pode facilmente encontrar a distância (usando o Teorema de Pitágoras). O círculo implícito ainda teria que caber dentro do retângulo delimitador original, limitando seu diâmetro máximo à largura de uma coluna (quarto da largura de um ladrilho), mas ainda é grande o suficiente para funcionar bem na prática.

Carl Smith
fonte