Parte 1. SVG e sistemas de coordenadas
Até recentemente, os tamanhos dos mapas no meu jogo
Dragons Abound eram fixos e um pouco não determinísticos. Eu os considerei “regionais” - não mapas de todo o mundo, mas suas partes significativas, como, por exemplo, a costa oeste dos EUA ou parte da Europa. Fiquei muito feliz com essa escala, mas queria experimentar um pouco o jogo para ver se conseguia gerar mapas de todo o mundo (ou pelo menos um maior). Mas antes de começar, vamos falar um pouco sobre os mapas do mundo da fantasia.
O mundo é um grande espaço. A maioria dos cartões "mundiais" de fantasia nem se parece com o tamanho real. Tomemos, por exemplo, a Terra Média, na qual a ação do Senhor dos Anéis ocorre:
Embora pareça que um mundo imenso seja capturado nele, de fato, a Terra-média foi criada com base na Europa.
Ou seja, o mapa real do “mundo” para o mundo Tolkien será aproximadamente 50 vezes maior que o mapa da Terra-média (!). De fato, a maioria dos mapas mundiais de fantasia que vi refletem um território do tamanho de um continente:
Esta parece ser a maior área que é bem visualizada no estilo de cartas de fantasia.
Ou seja, a tarefa de gerar "mapas do mundo" reais é provavelmente muito ambiciosa. É melhor procurar criar um mapa do continente (ou parte do continente). (No entanto, ainda é mais conveniente considerar o cartão como o tamanho do "mundo"). Então, qual é o tamanho do cartão? Se os
cards atuais dos
Dragons Abound têm um tamanho "subcontinental", podemos assumir que você precisa gerar cards 8 a 10 vezes maiores.
Antes de passar à tarefa de gerar mapas grandes, preciso entender melhor os vários sistemas de coordenadas que são usados no meu jogo. Peguei emprestado muitos sistemas de coordenadas do código-fonte de Martin O'Leary e suas interações podem ser confusas, mesmo quando você trabalha com eles por dois anos. Normalmente, eu era capaz de fazer isso sem experimentar com eles, mas é óbvio que precisarei fazer isso para gerar mapas grandes.
Para começar, o "mundo" do mapa regional está sendo gerado atualmente dentro de um quadrado de unidade. Cada mapa regional possui coordenadas de (-0,5, -0,5) a (0,5, 0,5) e a origem (0,0) fica no meio da região.
Uma das peculiaridades aqui é que o eixo Y é invertido em comparação com o que estudamos em geometria escolar. -0,5 está na parte superior do mapa e 0,5 está na parte inferior. Na computação gráfica, o eixo Y geralmente é invertido. Ouvi dizer que isso é explicado pela maneira como os primeiros monitores (TVs) realizam uma varredura de cima para baixo, ou seja, a primeira linha de varredura fica na parte superior, a seguinte imediatamente abaixo dela, e assim por diante, ou seja, o índice Y das linhas de varredura mudou de zero na parte superior para algum número positivo lá embaixo. Seja como for, o mesmo sistema de coordenadas é usado no formato SVG (Scalable Vector Graphics), e é por isso que os
Dragões também são
abundantes .
Este sistema de coordenadas é independente de como o mapa é exibido. Este é apenas um sistema sem dimensão para criar o mundo - a cidade está localizada em (0,12875, -0,223), a fronteira passa de (0,333, 0,010) a (0,333, 0,017) e assim por diante. E, embora meus mapas regionais atuais estejam limitados a um intervalo de 0,5 a -0,5, esse não é o limite do sistema de coordenadas. Eu posso criar um mundo além desses limites.
O próximo sistema de coordenadas é o que o SVG chama de viewbox. Ele define as verdadeiras coordenadas que serão usadas para desenhar os gráficos. Por exemplo,
Dragons Abound no início define a caixa de visualização com as coordenadas (-500, -500) e tem uma largura de 1000 e uma altura de 1000:
(Há um erro de digitação na imagem, a parte superior do eixo Y deve ser -500, desculpe.)
Você pode notar que, nesse caso, a transformação entre o primeiro e o segundo sistema de coordenadas consiste apenas na multiplicação de todos por 1000. Isso é. para desenhar algo, o jogo encontra as coordenadas desse objeto, multiplica-as por 1000 e desenha SVG nessas coordenadas.
Ou seja, posso usar as coordenadas da caixa de exibição para desenhar uma linha de (0, 0) a (250, 250). Mas, de fato, não quero desenhar uma linha de (0, 0) a (250, 250) na tela do computador. Isso significa que, se eu quiser exibir o mapa em outro ponto da tela, terei que alterar as coordenadas de todos os objetos do mapa e redesenhá-los. Isso seria um trabalho enorme.
Para controlar as coordenadas da exibição de gráficos na tela, o SVG possui um terceiro sistema de coordenadas chamado viewport. A janela de visualização é a parte da página na qual os gráficos devem ser desenhados (na página da web, esse é o elemento <svg>). Tem largura, altura e localização. A localização é as coordenadas do canto superior esquerdo da janela de visualização. Ou seja, se eu exibisse o mapa na viewport com coordenadas (30, 100), que tem altura e largura de 800, o sistema de coordenadas da viewport ficaria assim:

No SVG, a caixa de visualização e a porta de visualização dos sistemas de coordenadas são conectadas uma à outra, e o próprio SVG lida com a transição entre elas. Simplesmente desenhamos o sistema de coordenadas da caixa de visualização e tudo o que é renderizado é exibido no local da janela de visualização correspondente. (Existem alguns problemas ao criar uma viewbox e uma viewport com diferentes proporções. Em seguida, os objetos são cortados ou esticados, dependendo do valor do atributo
preserveAspectRatio
. Recomendo não fazer isso.)
Para resumir: uma cidade localizada nas coordenadas do mundo (0,10, 0,33) é desenhada nas coordenadas (100, 330) e exibida na tela em (110, 764).
Agora você pode entender por que isso pode ser confuso!
O que acontece se eu alterar cada um desses sistemas de coordenadas? Suponha que no primeiro sistema de coordenadas eu gere um mundo variando de -0,25 a 0,25 em cada eixo. O mundo resultante será quatro vezes menor que o mundo usual e preencherá apenas a parte do meio da janela de exibição:
(Você também pode observar artefatos nas bordas, que geralmente estão ocultos.) Da mesma forma, se eu dobrar o tamanho do primeiro sistema de coordenadas (SK), não veremos a maior parte do mapa, pois estará fora das bordas da janela de visualização.
O que acontece se eu dobrar o tamanho da caixa de visualização? Bem, se eu também dobrar a proporção entre o primeiro SC e a caixa de visualização (de 1000 a 2000), nada mudará muito. Se a proporção permanecer igual a 1000, o mapa será dividido pela metade novamente.
No entanto, desta vez o cartão tem uma área inicial de 1x1. Podemos observar novamente artefatos nas bordas que geralmente estão ocultas (por exemplo, partes convexas das florestas). Você também pode ver que o padrão do oceano está errado - devo ter feito algumas suposições sobre o tamanho da caixa de visualização. Além disso, parece que a bússola não está localizada no canto do mapa, mas no canto da caixa de visualização.
E vice-versa, se eu reduzir o tamanho da caixa de exibição pela metade, isso criará um efeito de zoom no mapa:
Aqui vemos apenas o quarto do meio do mapa. Essa não é uma maneira muito conveniente de ampliar, porque apenas metade do mapa apresenta alguns problemas - por exemplo, o marcador da cidade "South Owenson" foi além da tela. Além disso, ele dobra o tamanho das fontes e outras coisas que eu não preciso.
Um aspecto mais útil da caixa de visualização está mudando a origem. Até o momento, a caixa de visualização foi centralizada no mapa, mas isso não é necessário. Por exemplo, posso mover o mapa para a direita centralizando a caixa de visualização em um ponto no lado esquerdo do mapa:
Podemos notar novamente os efeitos nas fronteiras e alguns outros problemas, mas, em essência, o mapa foi movido para a direita. A utilidade disso pode não ser óbvia, mas imagine que eu gerarei um mapa cuja largura seja duas vezes a largura de um mapa regular. Por padrão, fica assim:
Parece com qualquer outro mapa, mas, na realidade, é apenas a parte central de um mapa maior. Ou seja, agora eu posso alterar a caixa de visualização para transferir outras partes do mapa para a janela de visão geral:
Aqui movi o ponto de vista para a esquerda, para ver uma parte do mundo a leste da vista original. Alguns nomes no mapa foram alterados porque o
Dragons Abound executa determinadas funções (por exemplo, atribui nomes aos objetos) com base em sua visibilidade. Vou precisar mudar isso para que, ao mover a caixa de visualização, o mapa permaneça constante. No entanto, mais tarde poderei mover a caixa de visualização em um mapa grande e gerar mapas regionais de qualquer área desejada. Ou seja, eu posso gerar e exibir mapas grandes do tamanho de um continente, mas também posso gerar mapas regionais de áreas dentro de um mapa grande.
Resumindo: o jogo usa três sistemas de coordenadas. O primeiro é um SC abstrato para os objetos do mundo. A segunda é a caixa de visualização, que define a área visível do mundo. A terceira é a janela de visualização, que controla onde o mapa será desenhado na tela. Para desenhar um mundo maior, preciso expandir o primeiro SC. Para exibir mais na tela, você precisa expandir a caixa de visualização. Também posso mover a caixa de exibição para exibir diferentes partes do grande mundo.
Parte 2. Tornando os cartões permanentes
Na parte anterior, explorei os sistemas de coordenadas e aprendi como mover o SVG da janela de exibição para exibir apenas partes individuais do grande mundo. No entanto, essa abordagem tem alguns problemas, porque anteriormente assumi que tudo o que é invisível para nós não importa. Nesta parte, eliminarei essas suposições para que seja possível gerar e visualizar diferentes partes de mapas grandes imutáveis.
O problema com a localização dos nomes, que descrevi na parte anterior, é obviamente perceptível nesses dois tipos de um cartão:
Aqui é o mesmo mundo, apenas a visão é deslocada para a esquerda:
Você pode perceber que a geografia é a mesma, mas muitos nomes foram alterados. Como em dois tipos diferentes objetos são visíveis e o processo de criação de nomes é controlado por números aleatórios, nomes diferentes são obtidos.
Olhando para o código, descobri que quase todos os objetos recebem nomes, dependendo de sua visibilidade. Mas há apenas uma exceção que dificulta atribuir nomes a todos os objetos subseqüentes. No nosso caso, a exceção é que os
Dragons Abound geram apenas linhas costeiras visíveis. As razões para isso são muito confusas. De fato, existe um “litoral” ao longo de todo o mundo, mas a criação dessa linha destrói parte da lógica do programa, porque abrange o mundo inteiro. Para evitar isso, acabei de gerar apenas o litoral visível. Agora que o mapa pode se expandir muito além da janela de visualização, esta solução não parece boa. Em vez disso, preciso parar de gerar linhas costeiras quando me aproximar da borda real do mapa. (Que ainda deixo fora da tela para ocultar problemas nas bordas.)
Depois de eliminar esse problema, os nomes nas duas placas permanecem constantes:
e:
Outra observação para o futuro: se eu usar o
recurso de interatividade para alterar os nomes dos objetos do mapa, essa alteração não será recriada em sua forma atual e provavelmente nem será reproduzível. Vale a pena considerar.
Se você olhar atentamente para o mapa anterior, poderá ver que a área oceânica próxima à parte central inferior do mapa tem um rótulo pendurado “Meb Island”. Isso aconteceu porque os
Dragões Abundam realmente acreditam que a região do oceano é uma ilha. Não entrarei em detalhes técnicos, mas é surpreendentemente difícil distinguir ilhas de lagos quando elas vão além do mapa. O algoritmo é confuso com a mudança que fiz na geração de linhas costeiras invisíveis e, para evitar esses problemas, isso precisa ser corrigido.
Agora vamos quadruplicar o tamanho do mapa e mostrar apenas um quarto dele na janela do mapa:
Em geral, tudo parece bom (existe um sistema interessante de rios no mapa, um lago grande, mas você pode ver que as cidades são muito raras. Isso aconteceu porque os
Dragons Abound geram de 10 a 20 cidades. Esse intervalo é adequado para um mundo de tamanho normal, mas ruim, quando o tamanho é quatro vezes maior, portanto, o intervalo precisa ser alterado de acordo com o tamanho relativo do mundo, e provavelmente em vários locais.
Aqui está o mesmo cartão após corrigir o problema:
Agora, no mapa, há um número mais lógico de cidades e vilas, mas isso nos mostra outro problema. Você pode ver muitos nomes extras nas bordas do mapa, por exemplo, Nanmrummombrook, Marwynnley e Noyewood no canto inferior esquerdo. Isso acontece porque o método do código de canal tenta colocá-los onde estão visíveis. Anteriormente, esse procedimento nunca precisava se preocupar com etiquetas fora da tela, porque em mapas de tamanho regional o mundo inteiro geralmente é visível. Mas agora pode haver cidades e outros objetos localizados fora da tela. Portanto, preciso adicionar lógica ao procedimento de posicionamento de rótulos que não tenta criar rótulos para objetos de mapa invisíveis.
Agora a imagem é mais lógica. À direita, Cumden mal está visível no mapa, mas o rótulo ainda está localizado onde está visível.
Há um aspecto que não é imediatamente perceptível em mapas grandes: o número de locais no mundo não mudou. Embora o mapa (em certo sentido) tenha se tornado quatro vezes maior, sua área total ainda é limitada pelo mesmo número de locais. O estágio inicial da geração de mapas era cobrir o mundo com um diagrama de Voronoi com um número constante de locais. Ou seja, quando o mapa se torna maior, as células de Voronoi também se tornam maiores.
Seria lógico escalar o número de locais de acordo com o tamanho do mapa, mas, infelizmente, a dependência da velocidade de execução dos
Dragões Abundados no número de locais é muito pior que a linear, ou seja, gerar um mapa com um grande número de locais pode levar muito tempo. Aqui está um exemplo de um cartão com resolução quad (número de locais):
Os locais adicionados alteram o processo de geração, portanto o terreno é diferente dos mapas mostrados acima, mas nele você pode ver os detalhes adicionados nas costas.
Felizmente, ao analisar o desempenho da geração de cartões grandes, notei que a maior parte do tempo do processador é ocupada por problemas óbvios. Após a depuração, eliminei os mais perturbadores, o que me permitiu criar mapas ainda mais. Aqui está o cartão 4x completo:
Zoom de 25% realizado. Parece mais ou menos o tamanho máximo do mapa que o Chrome pode exibir. O procedimento de geração mundial pode processar mapas maiores, mas, ao tentar exibi-los, o navegador trava. Nesse sentido, o Firefox parece ser mais funcional; Pode exibir cartões 9 vezes maiores que o tamanho original. Aqui está parte desse mapa - eu o deixei em tamanho real, para que você possa abri-lo em uma janela separada para entender melhor o tamanho e os detalhes.
O Firefox é capaz de gerar mapas desse tamanho, mas só posso tirar capturas de tela no tamanho máximo da janela do navegador. Eu tenho uma função para salvar o mapa como um arquivo PNG, mas ele pode salvar apenas a parte exibida do mapa. Eu acho que você pode rolar o mapa, capturar telas individuais e conectá-las, mas isso consumirá muito tempo.
A melhor solução é salvar o próprio SVG para que ele possa ser aberto em um programa como o Inkscape.
Eu costumava ser capaz de recortar e colar mapas SVG no Inkscape, mas os SVGs para mapas mundiais são tão grandes que, quando tento cortar o navegador, ele trava! Felizmente, encontrei o
FileSaver.js e você pode usá-lo para salvar o SVG diretamente em um arquivo e abri-lo no Inkscape, criando uma imagem muito grande.
Pelo menos teoricamente. Quando tento abrir esses mapas no Inkscape, encontro alguns problemas.
O primeiro problema é que as suposições do Inkscape são diferentes das suposições do Chrome e Firefox em como abrir o SVG. Em particular, se a cor do preenchimento não for especificada no caminho, os navegadores assumirão que não há preenchimento; O Inkscape assume que o contorno está preenchido com preto. Portanto, quando abro o SVG salvo no Inkscape, ele fica quase completamente preto, porque a camada superior do mapa não contém uma cor de preenchimento. Isso pode ser corrigido especificando “fill: none” nos locais necessários, para que os contornos apareçam igualmente no navegador e no Inkscape.
O segundo problema é que o Inkscape apresenta erros no processamento de máscaras. O Inkscape parece criar máscaras com apenas um elemento e lida mal com máscaras com vários elementos.
Dragons Abound cria muitas máscaras com vários elementos. Você pode solucionar esse problema agrupando todos os elementos de cada máscara de jogo em um elemento de grupo (opcional).
O terceiro problema está relacionado a imagens e outros recursos para download. No SVG original, as referências a eles são indicadas em forma relativa, por exemplo, "images / background0.png". Minhas fontes são organizadas de forma que o
servidor Web separado que eu use possa encontrar esses recursos nos locais especificados. Quando eu pego o mesmo SVG e o abro no Inkscape, esses caminhos relativos são tratados como o URL “file:” e o Inkscape procura recursos relacionados à pasta em que o SVG foi salvo. Esse problema pode ser facilmente contornado salvando o SVG em uma pasta na qual já existem recursos nos lugares certos; pode ser a mesma pasta raiz usada pelo servidor da Web ou outro local em que há cópias de recursos nos mesmos caminhos (relativos).
O quarto problema são as fontes.
Dragons Abound usa fontes da Web e fontes armazenadas localmente; ambos no formato WOFF2. No navegador, eles são aplicados ao texto usando o estilo de família de fontes CSS e, antes de gerar o mapa, todas as fontes possíveis são carregadas na página da Web para estarem prontas para uso. Quando o mesmo arquivo é aberto no jogo, ele procura por fontes no diretório de fontes do sistema e parece que nenhum outro diretório de fontes pode ser especificado de forma alguma. Uma solução simples (pelo menos na máquina em que estou desenvolvendo) é instalar as fontes usadas pelo jogo no diretório de fontes do sistema. No entanto, não é tão simples quanto parece, porque os nomes das fontes devem corresponder e, no Windows, não há maneiras fáceis de alterar o nome da fonte. Mas, é claro, esse esquema não funcionará em computadores nos quais todas as fontes necessárias não estejam instaladas. Uma solução mais portátil é
incorporar fontes SVG nos mapas . Isso estará na minha lista de tarefas.
No final, cheguei a essa interface de geração de mapas:
Os campos de entrada de extensão especificam o tamanho total do mundo, onde 1x1 é o tamanho dos mapas de origem. O tamanho da vbx (caixa de visualização) determina o tamanho de um fragmento do mundo exibido no mapa; na captura de tela, também possui um valor de 1x1, ou seja, o mapa exibirá o mundo inteiro. Os campos vbx center especificam a localização do centro do mapa no mundo; 0, 0 é o centro do mundo. Por fim, os parâmetros SVG especificam o número de pixels da tela por 1 unidade de tamanho da caixa de visualização; no valor de 775, um mapa 1x1 será exibido na tela no valor de 775x775 pixels. Isso é conveniente quando eu crio um mapa muito grande. Ao definir o parâmetro como um valor baixo (por exemplo, 150 pixels), posso ajustar um mapa grande na tela como um todo.
Alterando esses seis parâmetros, posso controlar o tamanho do mundo e a fração do mundo que é exibida no mapa. O botão Gerar funciona exatamente como você pode imaginar; o botão Exibir simplesmente exibe uma parte do mundo, ou seja, eu posso gerar um mundo e, em seguida, exibir suas partes individuais, alterando os parâmetros da caixa de visualização sem a necessidade de gerar novamente o mundo. (Um programador implementaria isso melhor como escala e rolagem.) O botão Salvar PNG salva o mapa visível como um arquivo PNG; O botão Salvar SVG salva o arquivo SVG de todo o mapa. O botão Testar é usado para executar o código de teste, que muda durante o desenvolvimento de várias funções.
Agora que posso gerar e refletir todas as partes do grande mundo, posso adaptar a forma da terra a mapas maiores.
Parte 3. Formas de Sushi
Tendo feito várias alterações na parte anterior, agora posso gerar mundos muito maiores que os anteriores (até 8 vezes mais) e salvá-los como grandes imagens gráficas:
(Abra a imagem em uma janela separada para ver o mapa em resolução máxima de 4800x2400 no Flickr.)
Eu gero esses mapas usando a mesma geração de procedimentos que criou os mapas regionais. O mapa mostrado acima tem uma forma continental bastante regular e algumas ilhas externas interessantes. No entanto, depende principalmente da sorte. Aqui está outro mapa:
Este cartão é apenas o caos das ilhas e queijo de sushi suíço.
Aqui está outro exemplo, algo entre as duas cartas anteriores. Não é completamente realista, mas pode ser interessante para ambientes de fantasia:
É uma enorme massa continental de terra, mas existem muitas formas estranhas de terra e, em geral, o mundo não parece "real". (Embora para alguém como esse mundo pareça bastante adequado para a fantasia.) Então, que formas o mapa do "mundo" deve ter?
A maioria dos mapas mundiais de fantasia que vi representam um grande continente insular (com pequenas ilhas ao redor), por exemplo, como este mapa de
Andelen :
Ou a península do continente, como neste mapa de
Angorun :
De tempos em tempos, um mapa é feito inteiramente de terra ou de vários continentes da ilha, mas é mais provável que sejam exceções à regra.
Para começar, vamos descobrir a geração de continentes "insulares". Como se vê no meu jogo, já havia uma função que gera uma grande ilha central no mapa, levando em consideração o tamanho do mapa; portanto, ela deve ser adequada para gerar a forma principal do continente. Ruído e ilhas adicionais cuidam do resto.
Eu não esperava um grande mar central neste mapa, mas é uma surpresa agradável. Aqui está outro exemplo:
O problema com a função da ilha central é que ela começa com um círculo que é adequado para os mapas quadrados mostrados por mim, mas não muito bom para os retangulares. (Abaixo estão exemplos com uma pequena distorção, para que as formas básicas sejam mais claramente visíveis.)
Isso é facilmente corrigido mascarando o sushi em vez de um círculo com uma elipse (distorcida) obtida pelo tamanho do mapa:
Essas ilhas centrais são dimensionadas para preencher o mapa, mas em muitos casos, para mapas continentais, precisamos deixar uma “fronteira” ao redor do continente. Dois parâmetros controlam o tamanho do mapa que preenche a ilha ao longo dos eixos X e Y.
Aqui está o mesmo sistema de gerenciamento de fronteiras com distorções mais lógicas:
Pode-se ver que as partes leste e oeste do mapa permanecem o oceano. (Você pode abrir o mapa em uma janela separada para estudá-lo com mais cuidado.) Isso significa que o mapa exibe o mundo inteiro (e suas bordas direita e esquerda podem ser conectadas) ou uma parte do mundo que pode ser conectada a outro mapa que também possui um oceano a partir da borda correspondente.
Um leitor atento que estudou o mapa anterior pode ter notado que os padrões do oceano e da terra param no meio do mapa.
Anteriormente, eu tinha mapas com tamanho apenas 1x1, então os tamanhos dos padrões do oceano e da terra eram adaptados a esses mapas. Em mapas maiores, preciso ladrilhar manualmente os padrões no mapa, então adicionei esse recurso. (Existe uma maneira no SVG de executar a criação de mosaicos de padrões, mas no Chrome ele contém um erro, portanto não posso usá-lo.) Essa é uma boa função, porque agora posso usar padrões menores de terra e oceano que serão automaticamente agrupados. Não sei por que não percebi isso antes!Portanto, agora os continentes da ilha estão funcionando bem e passaremos à implementação dos continentes “peninsulares” - mapas nos quais o continente aparece no mapa a partir de sua borda.Nesse caso, o continente não pode entrar em colapso em três arestas. Mas a principal característica desses mapas é que eles têm uma conexão terrestre significativa entre o continente exibido no mapa e a terra fora do mapa.A maneira mais fácil de fornecer essa conexão fora do mapa é definir um nível do mar baixo durante a geração. Portanto, aumentaremos a área de terra exibida no mapa, o que aumenta a probabilidade de grandes massas de terra e a ocorrência de terra (e não do mar) ao longo das bordas do mapa.Obviamente, isso não garante que a massa de terra seja muito interessante e, de fato, será unificada:A semelhança de um único continente pode ser criada usando a mesma geração do continente da ilha, mas ao mesmo tempo deslocando a ilha para a borda do mapa. Você obtém algo assim:Pode-se ver que o continente (principalmente) é a ilha central, deslocada para cima e para a direita. Como esse é um continente e não é necessário manter uma forma estrita de ilha, você pode adicionar mais distorção à forma.Obviamente, existem muitas outras abordagens para a geração de alívio, mas essas duas, pelo menos, me dão a oportunidade de gerar as formas mais comuns de terra em escala continental.Um leitor atento pode notar as estranhas formas de florestas em forma de listras em muitos mapas dos continentes. Da próxima vez, começarei a lidar com os problemas dos modelos e biomas eólicos que causam essas esquisitices.Parte 4. Modelo de vento
Como foi dito, o tamanho dos continentes nos mapas de Dragons Abound começou a mostrar padrões irreais de clima e biomas. Este exemplo mostra que a floresta está alinhada ao longo da direção predominante do vento:O motivo não está no código quebrado; ao contrário, os modelos climáticos e bioma são muito simples e, em larga escala, isso se torna aparente. Para lidar com esses problemas, comecei revisando o modelo de vento.Gostaria que meu modelo de vento refletisse melhor a dinâmica do vento da Terra: células Hadley , ventos alísios e similares. Essa dinâmica pode ajudar a se livrar de padrões climáticos estranhos nos mapas continentais. No entanto, quando foram adicionados, uma dolorosa insatisfação com o modelo de vento dos Dragões Abundam , lenta e muito complicada , foi novamente revelada . (Leia sobre a implementação inicial do modelo eólico aqui..) Depois de pensar nisso por vários dias, decidi que a maioria dos problemas se resume ao fato de o mapa do jogo ser apresentado como um diagrama de Voronoi . (Ou melhor, a triangulação de Delaunay do diagrama de Voronoi.) Possui muitas vantagens ao gerar terreno - em combinação com o ruído, pode criar massas de terra com aparência natural. É por isso que é tão frequentemente usado para gerar alívio. Porém, como os triângulos individuais têm tamanhos e orientações diferentes, qualquer cálculo, incluindo modelos de vento que usam células vizinhas, fica bastante complicado. Será muito mais fácil modelar o vento através de uma grade de áreas idênticas espaçadas uniformemente. Além disso, o modelo eólico provavelmente não precisa ser tão detalhado quanto a terra.Mas não quero abandonar completamente o diagrama de Voronoi, subjacente aos Dragões Abundantes . (No mínimo, isso exigirá a reescrita de quase todo o programa!) Em vez disso, quero experimentar vincular o mapa a uma grade uniforme, executar o modelo de vento nele e fazer a ligação inversa. Se a perda de cópias para frente e para trás não for muito grande, isso poderá tornar o modelo de vento mais rápido e fácil.Qual grade devo usar? Idealmente, a grade deve consistir em áreas iguais eqüidistantes dos seus vizinhos. E essa descrição é como uma grade de hexágonos.De fato, uma grade de hexágonos é a melhor maneira de dividir uma superfície plana em áreas iguais.O próximo passo é determinar como representar a grade de hexágonos no meu programa. Procurei um pouco na rede em busca de ajuda, e cada link retornou à página sobre as grades de hexágonos de Amit Patel ( tradução em Habré). Provavelmente, é necessário começar com isso; Também é bom explorar primeiro o site da Red Blob Games se você estiver procurando informações sobre como implementar a mecânica de jogos. Amit explica melhor do que eu, então, se algo não estiver claro, leia a página dele.A primeira escolha a ser feita é a maneira de armazenar a grade de hexágonos. A maneira mais fácil é armazená-lo como uma matriz bidimensional, então eu preciso da capacidade de vincular células da grade à matriz. Existem muitas opções aqui (leia a página de Amit ), mas usarei o que ele chama de ímpar-r:Os números em cada célula são os índices da célula na matriz bidimensional. (A imagem é roubada da página de Amit . Na página dele, eles são interativos, por isso recomendo que experimente.)Depois de fazer uma escolha, agora tenho que aprender a anexar índices a uma grade de hexágonos. Por exemplo, se estou procurando uma célula hexagonal (3, 3), quais serão seus vizinhos? Se cada célula tiver uma largura de 5 pixels, quais serão as coordenadas do centro da célula (3, 3)? E assim por diante
Lidar com isso pode ser difícil, então estou feliz que Amit tenha feito isso por mim.Supondo que possamos roubar tudo o que precisamos de Amit, primeiro preciso descobrir como organizar os hexágonos no mapa. Neste estágio, ainda não preciso de uma matriz, posso fingir que é e ver onde estarão os hexágonos. Se eu sei a localização dos hexágonos, simplesmente divido a largura do mapa pela distância horizontal entre eles para obter o número de colunas e faço o mesmo verticalmente para obter o número de linhas, após o que desenhei um hexágono em cada lugar:Esses hexágonos são muito maiores do que os que usarei para o modelo de vento, mas eles me mostram que tudo está colocado corretamente.Aqui abri as bordas do mapa e desenhei apenas o hexágono central e nas bordas para verificar se realmente fechei o mapa inteiro:As partes superior e inferior estão fora do mapa, mas a presença de várias células no exterior não é importante se eu não perder uma parte do mapa.O próximo passo é criar uma matriz para a grade de hexágonos e encaixar todos os triângulos de Delaunay nos hexágonos correspondentes. Como o Javascript não suporta índices negativos de matrizes , preciso mudar a célula (0, 0) do centro do mapa para o canto superior direito. Tendo feito isso, vou em volta de todos os triângulos de Delaunay e os adiciono às células correspondentes da grade hexagonal. Posso verificar isso colorindo os hexágonos que contêm terra:Para determinar se uma célula é terra, faço a média da altura de todos os locais que caem nessa célula. Você pode notar que, para alguns hexágonos costeiros, a média é inferior a zero, mesmo na presença de terra. Você também pode usar a altura máxima de todos os locais no hexágono:Nesse caso, a pesquisa ocorre na direção oposta - o hexágono é marcado como terra se houver alguma terra nele. Qual é o melhor depende do que você precisa.De qualquer forma, posso aumentar a precisão reduzindo o tamanho dos hexágonos:Agora a costa ficou muito melhor, mas surgiu um novo problema - muitos hexágonos internos que não são considerados terra. Isso acontece porque quando os hexágonos se tornam pequenos o suficiente, em alguns deles não há triângulos de Delaunay. Portanto, eles não têm "altura". (Isso também demonstra a irregularidade dos triângulos de Delaunay.)Você pode usar esta solução - considere a altura dos triângulos ausentes como a média de seus vizinhos ou o máximo de seus vizinhos.Em geral, quando a ligação entre hexágonos e locais não é um para um, são necessárias correções para preencher as informações ausentes.Agora que coloquei a grade de hexágonos no mapa, podemos começar a implementar o modelo de vento. A idéia principal do modelo de vento é simular alguns ventos (ventos alísios) e distribuí-los por todo o mapa até atingirem um estado de imobilidade. No nível dos hexágonos (ou no nível dos locais, se eu fizer isso nos triângulos de Delaunay), isso inclui dois estágios: (1) resumimos todos os ventos que entram no hexágono atual e (2) determinamos como o vento somado sai do hexágono.A primeira etapa é bastante simples. Cada hexágono tem seis vizinhos e cada um desses hexágonos contribui. Se considerarmos cada um dos ventos que entram no hexágono como um vetor, o vento total na célula será a soma desses vetores, ou seja, dois ventos estritamente opostos de igual força se anulam.O segundo estágio (determinar como o vento total sai do hexágono) requer reflexão. O caso mais simples é o vento soprando diretamente através do hexágono:Nesse caso, esperamos que o vento se mova para a próxima célula sem alterações. (Aqui, o vetor vermelho é o vento original e o azul é o vetor do vento em propagação.)Mas e se o vento não soprar diretamente para a célula vizinha?Nesse caso, parece lógico que parte do vento sopre em um hexágono diretamente acima dele, a outra parte em uma célula localizada no sentido anti-horário a partir deste, e as proporções devem depender da direção em que a seta aponta. No nosso caso, a parte principal do vento se moverá para a célula superior e menos para a célula vizinha.Também é necessário decidir a direção do vento que se espalha. Uma opção é manter a direção do vento original:Parece que esta é a opção mais realista, mas existe outra: alterar a direção do vento de acordo com a borda do hexágono em que ele se cruza:Essa abordagem é menos precisa, mas tem uma vantagem: os ventos sempre estarão em uma das seis direções, o que pode simplificar os cálculos.Outra dificuldade surge quando se considera o terreno. O que deve acontecer quando uma montanha sobe ao vento?Nesse caso, parte do vento passa sobre a montanha (possivelmente criando precipitação), mas parte do vento vira para os lados.Portanto, a maneira como o vento sai do hexágono depende de sua direção, bem como da topografia nas células vizinhas.Agora vamos falar sobre como representar vetores. Existem duas opções principais. Primeiramente, um vetor pode ser representado como valores X e Y, por exemplo:Se desenharmos um vetor começando em (0, 0), (X, Y) são as coordenadas dos pontos finais. Esse registro facilita muito a soma de vetores. Apenas somamos todos os valores (X, Y) e obtemos um novo vetor:Outra opção é usar o ângulo e o comprimento do vetor:Nesta forma, é fácil executar operações como girar vetores ou alterar seu comprimento.Para a maioria das operações exigidas no modelo eólico, a primeira opção é melhor, mas em alguns casos a segunda é melhor, portanto seria conveniente alternar entre elas, se necessário. Para não reinventar a roda, procurei uma biblioteca de vetores para Javascript e Victor.js apareceu , então aproveitei.Vou começar adicionando um vetor de vento a cada hexágono e ver se consigo visualizá-lo:Parece bom até agora.O próximo passo é verificar se posso dividir corretamente o vetor de vento e distribuí-lo para a próxima célula. Primeiro, você precisa calcular os ângulos que levam a outras células. Encontrei a resposta novamente na página de Amit :Ou seja, um vetor a 0 graus aponta para um hexágono à direita, a 60 graus - para um hexágono no canto inferior direito e assim por diante. Um vetor apontando entre essas duas direções é proporcionalmente dividido entre duas células - ou seja, um vetor em um ângulo de 30 graus será dividido igualmente entre a célula à direita e a célula de baixo para a direita. Cada vetor fica em algum lugar entre os cantos das faces de duas células vizinhas; portanto, basta olhar para o ângulo do vetor do vento, descobrir que ele fica entre os cantos centrais de dois hexágonos e dividi-lo proporcionalmente entre esses dois hexágonos.Por exemplo, se o vetor de vento tiver um ângulo de 22 graus:então 38/60 do valor se propaga para a célula à direita e 22/60 do valor do vetor para a célula no canto inferior direito. Se os vetores forem representados como um par de valores X e Y, você poderá distribuí-los multiplicando cada valor do vetor original por uma fração (por exemplo, 22/60) e adicionando-o ao vetor vento no novo hexágono.Para testar isso, posso organizar os ventos em direções diferentes e colocá-los na parte superior e lateral do mapa e ver se eles podem se espalhar corretamente no mapa. Quando os ventos colidem, eles devem ser combinados e escolher a direção média com maior velocidade:Aqui vemos que os ventos se encontram ao longo da diagonal e se combinam para soprar em direção ao canto inferior.O próximo passo é levar em consideração a influência da terra no vento. Obviamente, modelos de vento reais são muito complexos, mas estou principalmente interessado em como a geografia terrestre afeta os ventos de superfície. No nível mais simples, essa é a influência das alturas e terras baixas na direção e velocidade do vento. Eu experimentei muitas abordagens diferentes, mas como resultado, decidi por duas regras simples :- O vento se afasta dos obstáculos.
- O vento diminui quando sobe e acelera, caindo.
Um obstáculo ocorre quando o vento sopra em uma cela com uma altura mais alta, por exemplo, em uma cela com uma montanha. Quando isso acontece, olho as duas células nas quais o vento sopra e mudo o ângulo do vento para que ele aponte mais para a parte inferior dos dois hexágonos. A magnitude das mudanças de ângulo depende da diferença nas alturas de duas células; portanto, quando o vento sopra em duas células vizinhas com montanhas, sua direção muda pouco, mas se sopra em uma célula com uma montanha e uma célula com uma planície, ele se volta mais para a célula vazia:A força do vento pode ser ajustada. No mapa acima, ele é muito forte, o que levará ao surgimento de muitos biomas irrealistas. Aqui está um significado mais lógico:Uma grande parte do movimento do vento causado pelos relevos ainda permanece, mas as grandes brechas e vales do vento forte se tornaram menores.Outra característica que aumenta o realismo é a dispersão do vento. Por exemplo, o mapa acima mostra que o vento sopra para oeste logo acima da cidade de Breeches. Embora sopre longas distâncias, nunca se dissipa como seria de esperar. Quando um vento soprando encontra outro ar, ele geralmente puxa o vento junto com ele. Para simular isso, posso pegar uma pequena parte do vento soprando em cada hexágono e redistribuí-lo para todas as células vizinhas. Aqui está a aparência do mapa acima com uma pequena dispersão:Como você pode ver, agora o vento sobre os calções começou a se dissipar um pouco.Esta operação determina a maior parte das direções do vento. A segunda parte é a desaceleração do vento ao subir e a aceleração ao baixar. Eu posso perceber isso olhando a altura relativa da célula da qual o vento vem, e a altura da célula na qual o vento sopra, e acelerando / diminuindo a velocidade, se necessário.Aqui está como tudo parece:Agora vemos que parte do vento soprando através das altas montanhas na parte central da ilha foi cortada. E vice-versa - vários novos ventos apareceram na parte ocidental da ilha, onde o ar se move de terras relativamente altas para o mar. (Esta é uma brisa costeira ! Embora na verdade não: o mecanismo é diferente lá.)Agora posso substituir um novo vento pelo algoritmo de precipitação existente. Aqui está uma comparação (ventos antigos à esquerda, novos ventos à direita):(Clique na imagem para ver uma versão ampliada.) Obviamente, existem diferenças entre os modelos de vento. Nos dois mapas, o vento sopra do leste. Montanhas próximas ao centro do mapa viram o vento para o sul, causam fortes chuvas e criam um pântano e florestas ao sul das montanhas. Na parte inferior, o vento sopra sem nenhuma interferência, e as florestas se formam ao longo da metade oriental da ilha. No modelo original de vento, vento suficiente passava sobre as montanhas e pântanos centrais para criar uma floresta na parte ocidental da ilha. No novo modelo, a maior parte do vento é cortada e os biomas gramados se formam no lado mais distante das montanhas.O modelo antigo possui variabilidade de parâmetros aleatórios (dentro de um determinado intervalo) e é provável que alguma combinação desses parâmetros dê uma imagem mais parecida com um novo mapa. Mas, de fato, não precisamos reproduzir o comportamento exato do modelo antigo, apenas um modelo que crie resultados convincentes.O objetivo de tudo isso é acelerar e simplificar a geração de vento, para que o novo comportamento do vento possa ser adicionado aos mapas continentais. Eu fiz isso? Criei um perfil do modelo de vento original e do novo modelo baseado em hexágono. Verificou-se que o novo modelo é 15 a 20 vezes mais rápido que o original (!). Esta é uma aceleração muito significativa que tem pouco efeito nas cartas. As experiências deixam claro que o modelo não é particularmente sensível ao tamanho dos hexágonos; portanto, se necessário, posso acelerar o algoritmo ainda mais aumentando o tamanho das células.Da próxima vez, trabalharemos no uso de um novo modelo de vento para implementar padrões de vento em escala continental e, em seguida, conectá-los ao modelo e aos biomas de precipitação.